Skip to content

Commit d24a391

Browse files
Akos Kittakittaakos
Akos Kitta
authored andcommitted
fix: workaround for arduino/arduino-cli#1968
Do not try to parse the original `NotFound` error message, but look for a sketch somewhere in the requested path. Signed-off-by: Akos Kitta <[email protected]>
1 parent 3735553 commit d24a391

File tree

3 files changed

+65
-95
lines changed

3 files changed

+65
-95
lines changed

Diff for: arduino-ide-extension/src/browser/contributions/open-sketch-files.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export class OpenSketchFiles extends SketchContribution {
102102
): Promise<Sketch | undefined> {
103103
const { invalidMainSketchUri } = err.data;
104104
requestAnimationFrame(() => this.messageService.error(err.message));
105-
await wait(10); // let IDE2 toast the error message.
105+
await wait(250); // let IDE2 open the editor and toast the error message, then open the modal dialog
106106
const movedSketch = await promptMoveSketch(invalidMainSketchUri, {
107107
fileService: this.fileService,
108108
sketchService: this.sketchService,

Diff for: arduino-ide-extension/src/electron-main/theia/electron-main-application.ts

+11-47
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
import { fork } from 'child_process';
1010
import { AddressInfo } from 'net';
1111
import { join, isAbsolute, resolve } from 'path';
12-
import { promises as fs, Stats } from 'fs';
12+
import { promises as fs } from 'fs';
1313
import { MaybePromise } from '@theia/core/lib/common/types';
1414
import { ElectronSecurityToken } from '@theia/core/lib/electron-common/electron-token';
1515
import { FrontendApplicationConfig } from '@theia/application-package/lib/application-props';
@@ -28,6 +28,7 @@ import {
2828
SHOW_PLOTTER_WINDOW,
2929
} from '../../common/ipc-communication';
3030
import { ErrnoException } from '../../node/utils/errors';
31+
import { isAccessibleSketchPath } from '../../node/sketches-service-impl';
3132

3233
app.commandLine.appendSwitch('disable-http-cache');
3334

@@ -145,7 +146,10 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
145146
event.preventDefault();
146147
const resolvedPath = await this.resolvePath(path, cwd);
147148
if (resolvedPath) {
148-
const sketchFolderPath = await this.isValidSketchPath(resolvedPath);
149+
const sketchFolderPath = await isAccessibleSketchPath(
150+
resolvedPath,
151+
true
152+
);
149153
if (sketchFolderPath) {
150154
this.openFilePromise.reject(new InterruptWorkspaceRestoreError());
151155
await this.openSketch(sketchFolderPath);
@@ -158,49 +162,6 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
158162
}
159163
}
160164

161-
/**
162-
* The `path` argument is valid, if accessible and either pointing to a `.ino` file,
163-
* or it's a directory, and one of the files in the directory is an `.ino` file.
164-
*
165-
* If `undefined`, `path` was pointing to neither an accessible sketch file nor a sketch folder.
166-
*
167-
* The sketch folder name and sketch file name can be different. This method is not sketch folder name compliant.
168-
* The `path` must be an absolute, resolved path.
169-
*/
170-
private async isValidSketchPath(path: string): Promise<string | undefined> {
171-
let stats: Stats | undefined = undefined;
172-
try {
173-
stats = await fs.stat(path);
174-
} catch (err) {
175-
if (ErrnoException.isENOENT(err)) {
176-
return undefined;
177-
}
178-
throw err;
179-
}
180-
if (!stats) {
181-
return undefined;
182-
}
183-
if (stats.isFile()) {
184-
return path.endsWith('.ino') ? path : undefined;
185-
}
186-
try {
187-
const entries = await fs.readdir(path, { withFileTypes: true });
188-
const sketchFilename = entries
189-
.filter((entry) => entry.isFile() && entry.name.endsWith('.ino'))
190-
.map(({ name }) => name)
191-
.sort((left, right) => left.localeCompare(right))[0];
192-
if (sketchFilename) {
193-
return join(path, sketchFilename);
194-
}
195-
// If no sketches found in the folder, but the folder exists,
196-
// return with the path of the empty folder and let IDE2's frontend
197-
// figure out the workspace root.
198-
return path;
199-
} catch (err) {
200-
throw err;
201-
}
202-
}
203-
204165
private async resolvePath(
205166
maybePath: string,
206167
cwd: string
@@ -253,7 +214,10 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
253214
if (!resolvedPath) {
254215
continue;
255216
}
256-
const sketchFolderPath = await this.isValidSketchPath(resolvedPath);
217+
const sketchFolderPath = await isAccessibleSketchPath(
218+
resolvedPath,
219+
true
220+
);
257221
if (sketchFolderPath) {
258222
workspace.file = sketchFolderPath;
259223
if (this.isTempSketch.is(workspace.file)) {
@@ -284,7 +248,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
284248
if (!resolvedPath) {
285249
continue;
286250
}
287-
const sketchFolderPath = await this.isValidSketchPath(resolvedPath);
251+
const sketchFolderPath = await isAccessibleSketchPath(resolvedPath, true);
288252
if (sketchFolderPath) {
289253
path = sketchFolderPath;
290254
break;

Diff for: arduino-ide-extension/src/node/sketches-service-impl.ts

+53-47
Original file line numberDiff line numberDiff line change
@@ -734,62 +734,68 @@ function isNotFoundError(err: unknown): err is ServiceError {
734734

735735
/**
736736
* Tries to detect whether the error was caused by an invalid main sketch file name.
737-
* IDE2 should handle gracefully when there is an invalid sketch folder name. See the [spec](https://arduino.github.io/arduino-cli/latest/sketch-specification/#sketch-root-folder) for details.
738-
* The CLI does not have error codes (https://github.com/arduino/arduino-cli/issues/1762), so IDE2 parses the error message and tries to guess it.
737+
* IDE2 should handle gracefully when there is an invalid sketch folder name.
738+
* See the [spec](https://arduino.github.io/arduino-cli/latest/sketch-specification/#sketch-root-folder) for details.
739+
* The CLI does not have error codes (https://github.com/arduino/arduino-cli/issues/1762),
740+
* IDE2 cannot parse the error message (https://github.com/arduino/arduino-cli/issues/1968#issuecomment-1306936142)
741+
* so it checks if a sketch even if it's invalid can be discovered from the requested path.
739742
* Nothing guarantees that the invalid existing main sketch file still exits by the time client performs the sketch move.
740743
*/
741744
async function isInvalidSketchNameError(
742745
cliErr: unknown,
743746
requestSketchPath: string
744747
): Promise<string | undefined> {
745-
if (isNotFoundError(cliErr)) {
746-
const ino = requestSketchPath.endsWith('.ino');
747-
if (ino) {
748-
const sketchFolderPath = path.dirname(requestSketchPath);
749-
const sketchName = path.basename(sketchFolderPath);
750-
const pattern = escapeRegExpCharacters(
751-
`${invalidSketchNameErrorRegExpPrefix}${path.join(
752-
sketchFolderPath,
753-
`${sketchName}.ino`
754-
)}`
755-
);
756-
if (new RegExp(pattern, 'i').test(cliErr.details)) {
757-
try {
758-
await fs.access(requestSketchPath);
759-
return requestSketchPath;
760-
} catch {
761-
return undefined;
762-
}
763-
}
764-
} else {
765-
try {
766-
const resources = await fs.readdir(requestSketchPath, {
767-
withFileTypes: true,
768-
});
769-
return (
770-
resources
771-
.filter((resource) => resource.isFile())
772-
.filter((resource) => resource.name.endsWith('.ino'))
773-
// A folder might contain multiple sketches. It's OK to ick the first one as IDE2 cannot do much,
774-
// but ensure a deterministic behavior as `readdir(3)` does not guarantee an order. Sort them.
775-
.sort(({ name: left }, { name: right }) =>
776-
left.localeCompare(right)
777-
)
778-
.map(({ name }) => name)
779-
.map((name) => path.join(requestSketchPath, name))[0]
780-
);
781-
} catch (err) {
782-
if (ErrnoException.isENOENT(err) || ErrnoException.isENOTDIR(err)) {
783-
return undefined;
784-
}
785-
throw err;
786-
}
748+
return isNotFoundError(cliErr)
749+
? isAccessibleSketchPath(requestSketchPath)
750+
: undefined;
751+
}
752+
753+
/**
754+
* The `path` argument is valid, if accessible and either pointing to a `.ino` file,
755+
* or it's a directory, and one of the files in the directory is an `.ino` file.
756+
*
757+
* `undefined` if `path` was pointing to neither an accessible sketch file nor a sketch folder.
758+
*
759+
* The sketch folder name and sketch file name can be different. This method is not sketch folder name compliant.
760+
* The `path` must be an absolute, resolved path. This method does not handle EACCES (Permission denied) errors.
761+
*
762+
* When `fallbackToInvalidFolderPath` is `true`, and the `path` is an accessible folder without any sketch files,
763+
* this method returns with the `path` argument instead of `undefined`.
764+
*/
765+
export async function isAccessibleSketchPath(
766+
path: string,
767+
fallbackToInvalidFolderPath = false
768+
): Promise<string | undefined> {
769+
let stats: Stats | undefined = undefined;
770+
try {
771+
stats = await fs.stat(path);
772+
} catch (err) {
773+
if (ErrnoException.isENOENT(err)) {
774+
return undefined;
787775
}
776+
throw err;
777+
}
778+
if (!stats) {
779+
return undefined;
780+
}
781+
if (stats.isFile()) {
782+
return path.endsWith('.ino') ? path : undefined;
783+
}
784+
const entries = await fs.readdir(path, { withFileTypes: true });
785+
const sketchFilename = entries
786+
.filter((entry) => entry.isFile() && entry.name.endsWith('.ino'))
787+
.map(({ name }) => name)
788+
// A folder might contain multiple sketches. It's OK to pick the first one as IDE2 cannot do much,
789+
// but ensure a deterministic behavior as `readdir(3)` does not guarantee an order. Sort them.
790+
.sort((left, right) => left.localeCompare(right))[0];
791+
if (sketchFilename) {
792+
return join(path, sketchFilename);
788793
}
789-
return undefined;
794+
// If no sketches found in the folder, but the folder exists,
795+
// return with the path of the empty folder and let IDE2's frontend
796+
// figure out the workspace root.
797+
return fallbackToInvalidFolderPath ? path : undefined;
790798
}
791-
const invalidSketchNameErrorRegExpPrefix =
792-
'.*: main file missing from sketch: ';
793799

794800
/*
795801
* When a new sketch is created, add a suffix to distinguish it

0 commit comments

Comments
 (0)