Skip to content

Commit bc78f4e

Browse files
author
Akos Kitta
committed
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 b6c4777 commit bc78f4e

File tree

3 files changed

+65
-93
lines changed

3 files changed

+65
-93
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';
@@ -29,6 +29,7 @@ import {
2929
} from '../../common/ipc-communication';
3030
import isValidPath = require('is-valid-path');
3131
import { ErrnoException } from '../../node/utils/errors';
32+
import { isAccessibleSketchPath } from '../../node/sketches-service-impl';
3233

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

@@ -146,7 +147,10 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
146147
event.preventDefault();
147148
const resolvedPath = await this.resolvePath(path, cwd);
148149
if (resolvedPath) {
149-
const sketchFolderPath = await this.isValidSketchPath(resolvedPath);
150+
const sketchFolderPath = await isAccessibleSketchPath(
151+
resolvedPath,
152+
true
153+
);
150154
if (sketchFolderPath) {
151155
this.openFilePromise.reject(new InterruptWorkspaceRestoreError());
152156
await this.openSketch(sketchFolderPath);
@@ -159,49 +163,6 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
159163
}
160164
}
161165

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

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

+53-45
Original file line numberDiff line numberDiff line change
@@ -733,60 +733,68 @@ function isNotFoundError(err: unknown): err is ServiceError {
733733

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

791799
/*
792800
* When a new sketch is created, add a suffix to distinguish it

0 commit comments

Comments
 (0)