@theia/filesystem 1.73.0-next.2 → 1.73.0-next.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/file-service.d.ts +2 -0
- package/lib/browser/file-service.d.ts.map +1 -1
- package/lib/browser/file-service.js +10 -5
- package/lib/browser/file-service.js.map +1 -1
- package/lib/browser/file-tree/file-tree-widget.d.ts +2 -0
- package/lib/browser/file-tree/file-tree-widget.d.ts.map +1 -1
- package/lib/browser/file-tree/file-tree-widget.js +6 -1
- package/lib/browser/file-tree/file-tree-widget.js.map +1 -1
- package/lib/browser/filesystem-frontend-contribution.d.ts +2 -1
- package/lib/browser/filesystem-frontend-contribution.d.ts.map +1 -1
- package/lib/browser/filesystem-frontend-contribution.js +7 -2
- package/lib/browser/filesystem-frontend-contribution.js.map +1 -1
- package/lib/node/disk-file-system-provider.d.ts +2 -0
- package/lib/node/disk-file-system-provider.d.ts.map +1 -1
- package/lib/node/disk-file-system-provider.js +9 -3
- package/lib/node/disk-file-system-provider.js.map +1 -1
- package/lib/node/parcel-watcher/parcel-filesystem-service.d.ts +8 -0
- package/lib/node/parcel-watcher/parcel-filesystem-service.d.ts.map +1 -1
- package/lib/node/parcel-watcher/parcel-filesystem-service.js +38 -2
- package/lib/node/parcel-watcher/parcel-filesystem-service.js.map +1 -1
- package/lib/node/parcel-watcher/parcel-watcher-exclude.spec.d.ts +2 -0
- package/lib/node/parcel-watcher/parcel-watcher-exclude.spec.d.ts.map +1 -0
- package/lib/node/parcel-watcher/parcel-watcher-exclude.spec.js +79 -0
- package/lib/node/parcel-watcher/parcel-watcher-exclude.spec.js.map +1 -0
- package/lib/node/parcel-watcher/parcel-watcher-retry.spec.d.ts +2 -0
- package/lib/node/parcel-watcher/parcel-watcher-retry.spec.d.ts.map +1 -0
- package/lib/node/parcel-watcher/parcel-watcher-retry.spec.js +102 -0
- package/lib/node/parcel-watcher/parcel-watcher-retry.spec.js.map +1 -0
- package/lib/node/upload/node-file-upload-service.d.ts +2 -0
- package/lib/node/upload/node-file-upload-service.d.ts.map +1 -1
- package/lib/node/upload/node-file-upload-service.js +9 -3
- package/lib/node/upload/node-file-upload-service.js.map +1 -1
- package/package.json +3 -3
- package/src/browser/file-service.ts +9 -6
- package/src/browser/file-tree/file-tree-widget.tsx +6 -3
- package/src/browser/filesystem-frontend-contribution.ts +7 -4
- package/src/node/disk-file-system-provider.ts +8 -4
- package/src/node/parcel-watcher/parcel-filesystem-service.ts +45 -2
- package/src/node/parcel-watcher/parcel-watcher-exclude.spec.ts +91 -0
- package/src/node/parcel-watcher/parcel-watcher-retry.spec.ts +117 -0
- package/src/node/upload/node-file-upload-service.ts +9 -4
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import * as chai from 'chai';
|
|
18
|
+
import * as sinon from 'sinon';
|
|
19
|
+
import * as temp from 'temp';
|
|
20
|
+
import * as fs from '@theia/core/shared/fs-extra';
|
|
21
|
+
import URI from '@theia/core/lib/common/uri';
|
|
22
|
+
import { FileUri } from '@theia/core/lib/node';
|
|
23
|
+
import { ParcelFileSystemWatcherService } from './parcel-filesystem-service';
|
|
24
|
+
|
|
25
|
+
// We require the *same* module object that the production code imports from, so that
|
|
26
|
+
// stubbing its `subscribe` export is observed by `ParcelWatcher`. The `@theia/core/shared`
|
|
27
|
+
// shim simply re-exports `require('@parcel/watcher')`, so this is the identical reference.
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
29
|
+
const parcel = require('@theia/core/shared/@parcel/watcher');
|
|
30
|
+
|
|
31
|
+
const expect = chai.expect;
|
|
32
|
+
const track = temp.track();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Covers the inotify-tree-race fix in `ParcelWatcher.start()`:
|
|
36
|
+
*
|
|
37
|
+
* parcel-watcher walks the directory tree and only then calls `inotify_add_watch`
|
|
38
|
+
* on each subdirectory. If a subdirectory disappears between the walk and the add
|
|
39
|
+
* (common when watching dirs that contain rotated logs/temp folders), the syscall
|
|
40
|
+
* returns ENOENT and parcel-watcher fails the *entire* subscribe. The fix retries
|
|
41
|
+
* `createWatcher` a few times, but only when (a) the underlying error indicates a
|
|
42
|
+
* missing path and (b) the watched root itself still exists.
|
|
43
|
+
*/
|
|
44
|
+
describe('parcel-filesystem-watcher transient ENOENT handling', function (): void {
|
|
45
|
+
|
|
46
|
+
this.timeout(20000);
|
|
47
|
+
|
|
48
|
+
let root: URI;
|
|
49
|
+
let service: ParcelFileSystemWatcherService;
|
|
50
|
+
let subscribeStub: sinon.SinonStub | undefined;
|
|
51
|
+
let consoleErrorStub: sinon.SinonStub;
|
|
52
|
+
let onError: sinon.SinonStub;
|
|
53
|
+
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
const tempPath = temp.mkdirSync('parcel-enoent-root');
|
|
56
|
+
root = FileUri.create(fs.realpathSync(tempPath));
|
|
57
|
+
// start() now logs the underlying error to stderr on failure; silence it
|
|
58
|
+
// so the test output stays readable.
|
|
59
|
+
consoleErrorStub = sinon.stub(console, 'error');
|
|
60
|
+
service = new ParcelFileSystemWatcherService({ verbose: false });
|
|
61
|
+
onError = sinon.stub();
|
|
62
|
+
service.setClient({
|
|
63
|
+
onDidFilesChanged: () => undefined,
|
|
64
|
+
onError,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
afterEach(() => {
|
|
69
|
+
subscribeStub?.restore();
|
|
70
|
+
consoleErrorStub.restore();
|
|
71
|
+
track.cleanupSync();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('retries when subscribe throws a transient ENOENT and the watched root still exists', async () => {
|
|
75
|
+
let attempts = 0;
|
|
76
|
+
subscribeStub = sinon.stub(parcel, 'subscribe').callsFake(async () => {
|
|
77
|
+
attempts++;
|
|
78
|
+
if (attempts < 3) {
|
|
79
|
+
throw new Error('No such file or directory at /tmp/rotated-log');
|
|
80
|
+
}
|
|
81
|
+
return { unsubscribe: async () => undefined };
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await service.watchFileChanges(0, root.toString());
|
|
85
|
+
// Backoff schedule for two retries: 100 + 200 = 300ms. Leave generous margin.
|
|
86
|
+
await new Promise(resolve => setTimeout(resolve, 800));
|
|
87
|
+
|
|
88
|
+
expect(attempts, 'subscribe should have been retried until it succeeded').to.equal(3);
|
|
89
|
+
expect(onError.called, 'no error should surface to the client once the retry recovered').to.equal(false);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('does not retry on non-ENOENT errors and surfaces the failure immediately', async () => {
|
|
93
|
+
subscribeStub = sinon.stub(parcel, 'subscribe').callsFake(async () => {
|
|
94
|
+
throw new Error('EACCES: permission denied');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
await service.watchFileChanges(0, root.toString());
|
|
98
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
99
|
+
|
|
100
|
+
expect(subscribeStub.callCount, 'non-transient errors must not trigger any retry').to.equal(1);
|
|
101
|
+
expect(onError.called, 'error must be reported to the client').to.equal(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('gives up after the retry budget is exhausted on persistent ENOENT', async () => {
|
|
105
|
+
subscribeStub = sinon.stub(parcel, 'subscribe').callsFake(async () => {
|
|
106
|
+
throw new Error('No such file or directory at /tmp/rotated-log');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
await service.watchFileChanges(0, root.toString());
|
|
110
|
+
// Total backoff is 100+200+300+400 = 1000ms; leave a margin for scheduling.
|
|
111
|
+
await new Promise(resolve => setTimeout(resolve, 1700));
|
|
112
|
+
|
|
113
|
+
// Initial attempt + 4 retries = 5 total subscribe calls.
|
|
114
|
+
expect(subscribeStub.callCount, 'should retry up to the budget then give up').to.equal(5);
|
|
115
|
+
expect(onError.called, 'error must surface once the retry budget is exhausted').to.equal(true);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -20,11 +20,16 @@ import os = require('os');
|
|
|
20
20
|
import express = require('@theia/core/shared/express');
|
|
21
21
|
import fs = require('@theia/core/shared/fs-extra');
|
|
22
22
|
import { BackendApplicationContribution, FileUri } from '@theia/core/lib/node';
|
|
23
|
-
import { injectable } from '@theia/core/shared/inversify';
|
|
23
|
+
import { injectable, inject, named } from '@theia/core/shared/inversify';
|
|
24
24
|
import { HTTP_FILE_UPLOAD_PATH } from '../../common/file-upload';
|
|
25
|
+
import { ILogger } from '@theia/core';
|
|
25
26
|
|
|
26
27
|
@injectable()
|
|
27
28
|
export class NodeFileUploadService implements BackendApplicationContribution {
|
|
29
|
+
|
|
30
|
+
@inject(ILogger) @named('filesystem:NodeFileUploadService')
|
|
31
|
+
protected readonly logger: ILogger;
|
|
32
|
+
|
|
28
33
|
private static readonly UPLOAD_DIR = 'theia_upload';
|
|
29
34
|
|
|
30
35
|
async configure(app: express.Application): Promise<void> {
|
|
@@ -32,8 +37,8 @@ export class NodeFileUploadService implements BackendApplicationContribution {
|
|
|
32
37
|
this.getTemporaryUploadDest(),
|
|
33
38
|
this.getHttpFileUploadPath()
|
|
34
39
|
]);
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
this.logger.debug(`HTTP file upload URL path: ${http_path}`);
|
|
41
|
+
this.logger.debug(`Backend file upload cache path: ${dest}`);
|
|
37
42
|
app.post(
|
|
38
43
|
http_path,
|
|
39
44
|
// `multer` handles `multipart/form-data` containing our file to upload.
|
|
@@ -72,7 +77,7 @@ export class NodeFileUploadService implements BackendApplicationContribution {
|
|
|
72
77
|
}
|
|
73
78
|
response.status(200).send(target); // ok
|
|
74
79
|
} catch (error) {
|
|
75
|
-
|
|
80
|
+
this.logger.error(error);
|
|
76
81
|
if (error.message) {
|
|
77
82
|
// internal server error with error message as response
|
|
78
83
|
response.status(500).send(error.message);
|