mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-06-14 16:16:32 +00:00
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 <a.kitta@arduino.cc>
This commit is contained in:
parent
3735553003
commit
d24a3911f8
@ -102,7 +102,7 @@ export class OpenSketchFiles extends SketchContribution {
|
|||||||
): Promise<Sketch | undefined> {
|
): Promise<Sketch | undefined> {
|
||||||
const { invalidMainSketchUri } = err.data;
|
const { invalidMainSketchUri } = err.data;
|
||||||
requestAnimationFrame(() => this.messageService.error(err.message));
|
requestAnimationFrame(() => this.messageService.error(err.message));
|
||||||
await wait(10); // let IDE2 toast the error message.
|
await wait(250); // let IDE2 open the editor and toast the error message, then open the modal dialog
|
||||||
const movedSketch = await promptMoveSketch(invalidMainSketchUri, {
|
const movedSketch = await promptMoveSketch(invalidMainSketchUri, {
|
||||||
fileService: this.fileService,
|
fileService: this.fileService,
|
||||||
sketchService: this.sketchService,
|
sketchService: this.sketchService,
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
import { fork } from 'child_process';
|
import { fork } from 'child_process';
|
||||||
import { AddressInfo } from 'net';
|
import { AddressInfo } from 'net';
|
||||||
import { join, isAbsolute, resolve } from 'path';
|
import { join, isAbsolute, resolve } from 'path';
|
||||||
import { promises as fs, Stats } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import { MaybePromise } from '@theia/core/lib/common/types';
|
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||||
import { ElectronSecurityToken } from '@theia/core/lib/electron-common/electron-token';
|
import { ElectronSecurityToken } from '@theia/core/lib/electron-common/electron-token';
|
||||||
import { FrontendApplicationConfig } from '@theia/application-package/lib/application-props';
|
import { FrontendApplicationConfig } from '@theia/application-package/lib/application-props';
|
||||||
@ -28,6 +28,7 @@ import {
|
|||||||
SHOW_PLOTTER_WINDOW,
|
SHOW_PLOTTER_WINDOW,
|
||||||
} from '../../common/ipc-communication';
|
} from '../../common/ipc-communication';
|
||||||
import { ErrnoException } from '../../node/utils/errors';
|
import { ErrnoException } from '../../node/utils/errors';
|
||||||
|
import { isAccessibleSketchPath } from '../../node/sketches-service-impl';
|
||||||
|
|
||||||
app.commandLine.appendSwitch('disable-http-cache');
|
app.commandLine.appendSwitch('disable-http-cache');
|
||||||
|
|
||||||
@ -145,7 +146,10 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const resolvedPath = await this.resolvePath(path, cwd);
|
const resolvedPath = await this.resolvePath(path, cwd);
|
||||||
if (resolvedPath) {
|
if (resolvedPath) {
|
||||||
const sketchFolderPath = await this.isValidSketchPath(resolvedPath);
|
const sketchFolderPath = await isAccessibleSketchPath(
|
||||||
|
resolvedPath,
|
||||||
|
true
|
||||||
|
);
|
||||||
if (sketchFolderPath) {
|
if (sketchFolderPath) {
|
||||||
this.openFilePromise.reject(new InterruptWorkspaceRestoreError());
|
this.openFilePromise.reject(new InterruptWorkspaceRestoreError());
|
||||||
await this.openSketch(sketchFolderPath);
|
await this.openSketch(sketchFolderPath);
|
||||||
@ -158,49 +162,6 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The `path` argument is valid, if accessible and either pointing to a `.ino` file,
|
|
||||||
* or it's a directory, and one of the files in the directory is an `.ino` file.
|
|
||||||
*
|
|
||||||
* If `undefined`, `path` was pointing to neither an accessible sketch file nor a sketch folder.
|
|
||||||
*
|
|
||||||
* The sketch folder name and sketch file name can be different. This method is not sketch folder name compliant.
|
|
||||||
* The `path` must be an absolute, resolved path.
|
|
||||||
*/
|
|
||||||
private async isValidSketchPath(path: string): Promise<string | undefined> {
|
|
||||||
let stats: Stats | undefined = undefined;
|
|
||||||
try {
|
|
||||||
stats = await fs.stat(path);
|
|
||||||
} catch (err) {
|
|
||||||
if (ErrnoException.isENOENT(err)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
if (!stats) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (stats.isFile()) {
|
|
||||||
return path.endsWith('.ino') ? path : undefined;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const entries = await fs.readdir(path, { withFileTypes: true });
|
|
||||||
const sketchFilename = entries
|
|
||||||
.filter((entry) => entry.isFile() && entry.name.endsWith('.ino'))
|
|
||||||
.map(({ name }) => name)
|
|
||||||
.sort((left, right) => left.localeCompare(right))[0];
|
|
||||||
if (sketchFilename) {
|
|
||||||
return join(path, sketchFilename);
|
|
||||||
}
|
|
||||||
// If no sketches found in the folder, but the folder exists,
|
|
||||||
// return with the path of the empty folder and let IDE2's frontend
|
|
||||||
// figure out the workspace root.
|
|
||||||
return path;
|
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async resolvePath(
|
private async resolvePath(
|
||||||
maybePath: string,
|
maybePath: string,
|
||||||
cwd: string
|
cwd: string
|
||||||
@ -253,7 +214,10 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
|
|||||||
if (!resolvedPath) {
|
if (!resolvedPath) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const sketchFolderPath = await this.isValidSketchPath(resolvedPath);
|
const sketchFolderPath = await isAccessibleSketchPath(
|
||||||
|
resolvedPath,
|
||||||
|
true
|
||||||
|
);
|
||||||
if (sketchFolderPath) {
|
if (sketchFolderPath) {
|
||||||
workspace.file = sketchFolderPath;
|
workspace.file = sketchFolderPath;
|
||||||
if (this.isTempSketch.is(workspace.file)) {
|
if (this.isTempSketch.is(workspace.file)) {
|
||||||
@ -284,7 +248,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
|
|||||||
if (!resolvedPath) {
|
if (!resolvedPath) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const sketchFolderPath = await this.isValidSketchPath(resolvedPath);
|
const sketchFolderPath = await isAccessibleSketchPath(resolvedPath, true);
|
||||||
if (sketchFolderPath) {
|
if (sketchFolderPath) {
|
||||||
path = sketchFolderPath;
|
path = sketchFolderPath;
|
||||||
break;
|
break;
|
||||||
|
@ -734,62 +734,68 @@ function isNotFoundError(err: unknown): err is ServiceError {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to detect whether the error was caused by an invalid main sketch file name.
|
* Tries to detect whether the error was caused by an invalid main sketch file name.
|
||||||
* 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.
|
* IDE2 should handle gracefully when there is an invalid sketch folder name.
|
||||||
* 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.
|
* See the [spec](https://arduino.github.io/arduino-cli/latest/sketch-specification/#sketch-root-folder) for details.
|
||||||
|
* The CLI does not have error codes (https://github.com/arduino/arduino-cli/issues/1762),
|
||||||
|
* IDE2 cannot parse the error message (https://github.com/arduino/arduino-cli/issues/1968#issuecomment-1306936142)
|
||||||
|
* so it checks if a sketch even if it's invalid can be discovered from the requested path.
|
||||||
* Nothing guarantees that the invalid existing main sketch file still exits by the time client performs the sketch move.
|
* Nothing guarantees that the invalid existing main sketch file still exits by the time client performs the sketch move.
|
||||||
*/
|
*/
|
||||||
async function isInvalidSketchNameError(
|
async function isInvalidSketchNameError(
|
||||||
cliErr: unknown,
|
cliErr: unknown,
|
||||||
requestSketchPath: string
|
requestSketchPath: string
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
if (isNotFoundError(cliErr)) {
|
return isNotFoundError(cliErr)
|
||||||
const ino = requestSketchPath.endsWith('.ino');
|
? isAccessibleSketchPath(requestSketchPath)
|
||||||
if (ino) {
|
: undefined;
|
||||||
const sketchFolderPath = path.dirname(requestSketchPath);
|
}
|
||||||
const sketchName = path.basename(sketchFolderPath);
|
|
||||||
const pattern = escapeRegExpCharacters(
|
/**
|
||||||
`${invalidSketchNameErrorRegExpPrefix}${path.join(
|
* The `path` argument is valid, if accessible and either pointing to a `.ino` file,
|
||||||
sketchFolderPath,
|
* or it's a directory, and one of the files in the directory is an `.ino` file.
|
||||||
`${sketchName}.ino`
|
*
|
||||||
)}`
|
* `undefined` if `path` was pointing to neither an accessible sketch file nor a sketch folder.
|
||||||
);
|
*
|
||||||
if (new RegExp(pattern, 'i').test(cliErr.details)) {
|
* The sketch folder name and sketch file name can be different. This method is not sketch folder name compliant.
|
||||||
try {
|
* The `path` must be an absolute, resolved path. This method does not handle EACCES (Permission denied) errors.
|
||||||
await fs.access(requestSketchPath);
|
*
|
||||||
return requestSketchPath;
|
* When `fallbackToInvalidFolderPath` is `true`, and the `path` is an accessible folder without any sketch files,
|
||||||
} catch {
|
* this method returns with the `path` argument instead of `undefined`.
|
||||||
return undefined;
|
*/
|
||||||
}
|
export async function isAccessibleSketchPath(
|
||||||
}
|
path: string,
|
||||||
} else {
|
fallbackToInvalidFolderPath = false
|
||||||
try {
|
): Promise<string | undefined> {
|
||||||
const resources = await fs.readdir(requestSketchPath, {
|
let stats: Stats | undefined = undefined;
|
||||||
withFileTypes: true,
|
try {
|
||||||
});
|
stats = await fs.stat(path);
|
||||||
return (
|
} catch (err) {
|
||||||
resources
|
if (ErrnoException.isENOENT(err)) {
|
||||||
.filter((resource) => resource.isFile())
|
return undefined;
|
||||||
.filter((resource) => resource.name.endsWith('.ino'))
|
}
|
||||||
// A folder might contain multiple sketches. It's OK to ick the first one as IDE2 cannot do much,
|
throw err;
|
||||||
// but ensure a deterministic behavior as `readdir(3)` does not guarantee an order. Sort them.
|
}
|
||||||
.sort(({ name: left }, { name: right }) =>
|
if (!stats) {
|
||||||
left.localeCompare(right)
|
return undefined;
|
||||||
)
|
}
|
||||||
.map(({ name }) => name)
|
if (stats.isFile()) {
|
||||||
.map((name) => path.join(requestSketchPath, name))[0]
|
return path.endsWith('.ino') ? path : undefined;
|
||||||
);
|
}
|
||||||
} catch (err) {
|
const entries = await fs.readdir(path, { withFileTypes: true });
|
||||||
if (ErrnoException.isENOENT(err) || ErrnoException.isENOTDIR(err)) {
|
const sketchFilename = entries
|
||||||
return undefined;
|
.filter((entry) => entry.isFile() && entry.name.endsWith('.ino'))
|
||||||
}
|
.map(({ name }) => name)
|
||||||
throw err;
|
// A folder might contain multiple sketches. It's OK to pick the first one as IDE2 cannot do much,
|
||||||
}
|
// but ensure a deterministic behavior as `readdir(3)` does not guarantee an order. Sort them.
|
||||||
}
|
.sort((left, right) => left.localeCompare(right))[0];
|
||||||
}
|
if (sketchFilename) {
|
||||||
return undefined;
|
return join(path, sketchFilename);
|
||||||
|
}
|
||||||
|
// If no sketches found in the folder, but the folder exists,
|
||||||
|
// return with the path of the empty folder and let IDE2's frontend
|
||||||
|
// figure out the workspace root.
|
||||||
|
return fallbackToInvalidFolderPath ? path : undefined;
|
||||||
}
|
}
|
||||||
const invalidSketchNameErrorRegExpPrefix =
|
|
||||||
'.*: main file missing from sketch: ';
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* When a new sketch is created, add a suffix to distinguish it
|
* When a new sketch is created, add a suffix to distinguish it
|
||||||
|
Loading…
x
Reference in New Issue
Block a user