@theia/application-manager 1.75.0-next.8 → 1.75.0
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/application-package-manager.d.ts +1 -0
- package/lib/application-package-manager.d.ts.map +1 -1
- package/lib/application-package-manager.js +14 -9
- package/lib/application-package-manager.js.map +1 -1
- package/lib/browser-only/prepare-browser-only-plugins.d.ts +12 -0
- package/lib/browser-only/prepare-browser-only-plugins.d.ts.map +1 -0
- package/lib/browser-only/prepare-browser-only-plugins.js +313 -0
- package/lib/browser-only/prepare-browser-only-plugins.js.map +1 -0
- package/lib/browser-only/prepare-browser-only-plugins.spec.d.ts +2 -0
- package/lib/browser-only/prepare-browser-only-plugins.spec.d.ts.map +1 -0
- package/lib/browser-only/prepare-browser-only-plugins.spec.js +136 -0
- package/lib/browser-only/prepare-browser-only-plugins.spec.js.map +1 -0
- package/lib/browser-only/write-browser-only-extensions-list.d.ts +7 -0
- package/lib/browser-only/write-browser-only-extensions-list.d.ts.map +1 -0
- package/lib/browser-only/write-browser-only-extensions-list.js +28 -0
- package/lib/browser-only/write-browser-only-extensions-list.js.map +1 -0
- package/lib/generator/bundler-generator.d.ts +7 -8
- package/lib/generator/bundler-generator.d.ts.map +1 -1
- package/lib/generator/bundler-generator.js +32 -563
- package/lib/generator/bundler-generator.js.map +1 -1
- package/lib/generator/frontend-generator.d.ts.map +1 -1
- package/lib/generator/frontend-generator.js +1 -3
- package/lib/generator/frontend-generator.js.map +1 -1
- package/package.json +11 -33
- package/src/application-package-manager.ts +16 -8
- package/src/browser-only/prepare-browser-only-plugins.spec.ts +169 -0
- package/src/browser-only/prepare-browser-only-plugins.ts +376 -0
- package/src/browser-only/write-browser-only-extensions-list.ts +27 -0
- package/src/generator/bundler-generator.ts +36 -568
- package/src/generator/frontend-generator.ts +1 -3
- package/lib/expose-loader.d.ts +0 -9
- package/lib/expose-loader.d.ts.map +0 -1
- package/lib/expose-loader.js +0 -73
- package/lib/expose-loader.js.map +0 -1
- package/src/expose-loader.ts +0 -83
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 Maksim Kachurin 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 { realpath } from 'fs/promises';
|
|
18
|
+
import * as path from 'path';
|
|
19
|
+
import { fileURLToPath } from 'url';
|
|
20
|
+
import * as fs from 'fs-extra';
|
|
21
|
+
import { ApplicationPackage } from '@theia/application-package';
|
|
22
|
+
import {
|
|
23
|
+
DEFAULT_PLUGINS_DIR,
|
|
24
|
+
LIST_JSON,
|
|
25
|
+
PLUGINS_BASE_PATH,
|
|
26
|
+
PLUGIN_COPY_IGNORE,
|
|
27
|
+
UNPUBLISHED,
|
|
28
|
+
} from '@theia/plugin-utils/lib/common/constants';
|
|
29
|
+
import { stripVscodeBuiltinNamePrefix } from '@theia/plugin-utils/lib/common/plugin-manifest';
|
|
30
|
+
import { updateActivationEvents } from '@theia/plugin-utils/lib/common/plugin-activation-events';
|
|
31
|
+
import {
|
|
32
|
+
applyTrustExtraction,
|
|
33
|
+
buildLifecycle,
|
|
34
|
+
buildModel,
|
|
35
|
+
getPluginId,
|
|
36
|
+
pickEngineType,
|
|
37
|
+
toPluginUrl
|
|
38
|
+
} from '@theia/plugin-utils/lib/common/plugin-model';
|
|
39
|
+
import { getPluginRootFileUrl } from '@theia/plugin-utils/lib/node/plugin-model';
|
|
40
|
+
import { normalizeContributions } from '@theia/plugin-utils/lib/node/normalize-contributions';
|
|
41
|
+
import { readGrammarFromDisk } from '@theia/plugin-utils/lib/node/read-grammars';
|
|
42
|
+
import { localizePackage } from '@theia/plugin-utils/lib/common/package-nls';
|
|
43
|
+
import { loadPackageTranslations } from '@theia/plugin-utils/lib/node/package-nls';
|
|
44
|
+
import { deepClone } from '@theia/plugin-utils/lib/common/utils';
|
|
45
|
+
import type {
|
|
46
|
+
NormalizedPluginContribution,
|
|
47
|
+
} from '@theia/plugin-utils/lib/common/contribution-types';
|
|
48
|
+
|
|
49
|
+
import {
|
|
50
|
+
PLUGIN_HOST_BACKEND,
|
|
51
|
+
PluginType,
|
|
52
|
+
type DeployedPlugin,
|
|
53
|
+
type PluginEntryPoint,
|
|
54
|
+
type PluginManifest,
|
|
55
|
+
type PluginModel,
|
|
56
|
+
} from '@theia/plugin-utils/lib/common/manifest-types';
|
|
57
|
+
|
|
58
|
+
export async function prepareBrowserOnlyPlugins(applicationPackage: ApplicationPackage): Promise<void> {
|
|
59
|
+
const hostedPluginDir = applicationPackage.lib('frontend', PLUGINS_BASE_PATH);
|
|
60
|
+
await fs.remove(hostedPluginDir);
|
|
61
|
+
await fs.ensureDir(hostedPluginDir);
|
|
62
|
+
|
|
63
|
+
const theiaPluginsDir = applicationPackage.pck.theiaPluginsDir;
|
|
64
|
+
const pluginsDir = path.resolve(
|
|
65
|
+
applicationPackage.projectPath,
|
|
66
|
+
typeof theiaPluginsDir === 'string' ? theiaPluginsDir : DEFAULT_PLUGINS_DIR
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const deployedPlugins: DeployedPlugin[] = [];
|
|
70
|
+
const skippedByReason: Partial<Record<NonNullable<ProcessPluginResult['skipReason']>, string[]>> = {};
|
|
71
|
+
const errored: string[] = [];
|
|
72
|
+
|
|
73
|
+
if (await fs.pathExists(pluginsDir)) {
|
|
74
|
+
const names = await fs.readdir(pluginsDir);
|
|
75
|
+
for (const name of names) {
|
|
76
|
+
const result = await processPlugin(path.join(pluginsDir, name), hostedPluginDir);
|
|
77
|
+
if (result?.plugin) {
|
|
78
|
+
deployedPlugins.push(result.plugin);
|
|
79
|
+
} else if (result?.skipReason) {
|
|
80
|
+
(skippedByReason[result.skipReason] ??= []).push(result.label ?? name);
|
|
81
|
+
} else {
|
|
82
|
+
errored.push(result?.label ?? name);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
await fs.writeJson(path.join(hostedPluginDir, LIST_JSON), deployedPlugins);
|
|
88
|
+
|
|
89
|
+
const lines = [`browser-only: prepared ${deployedPlugins.length} plugins`];
|
|
90
|
+
for (const [reason, labels] of Object.entries(skippedByReason)) {
|
|
91
|
+
if (labels?.length) {
|
|
92
|
+
lines.push(` skipped ${labels.length} ${reason}:`);
|
|
93
|
+
lines.push(` ${labels.join(', ')}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (errored.length > 0) {
|
|
97
|
+
lines.push(` ${errored.length} errors:`);
|
|
98
|
+
lines.push(` ${errored.join(', ')}`);
|
|
99
|
+
}
|
|
100
|
+
console.log(lines.join('\n'));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
interface ProcessPluginResult {
|
|
104
|
+
plugin?: DeployedPlugin;
|
|
105
|
+
/** Set when the plugin was intentionally omitted (not a hard error). */
|
|
106
|
+
skipReason?: 'backend-only' | 'no-browser-surface' | 'no-package' | 'malformed';
|
|
107
|
+
/** Plugin id/name or source directory basename */
|
|
108
|
+
label?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function processPlugin(pluginSourceDir: string, hostedPluginDir: string): Promise<ProcessPluginResult | undefined> {
|
|
112
|
+
const dirLabel = path.basename(pluginSourceDir);
|
|
113
|
+
try {
|
|
114
|
+
const packageRoot = resolvePluginRoot(pluginSourceDir);
|
|
115
|
+
if (!packageRoot) {
|
|
116
|
+
return { skipReason: 'no-package', label: dirLabel };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const buildTimePackageRoot = await realpath(packageRoot);
|
|
120
|
+
const rawManifest = await fs.readJson(path.join(buildTimePackageRoot, 'package.json')) as PluginManifest;
|
|
121
|
+
stripVscodeBuiltinNamePrefix(rawManifest);
|
|
122
|
+
|
|
123
|
+
const skipReason = getBrowserOnlySkipReason(rawManifest);
|
|
124
|
+
if (skipReason) {
|
|
125
|
+
const label = rawManifest.name || dirLabel;
|
|
126
|
+
if (skipReason === 'backend-only') {
|
|
127
|
+
console.warn(`browser-only: skip '${label}' (backend-only)`);
|
|
128
|
+
} else if (skipReason === 'malformed') {
|
|
129
|
+
console.warn(`browser-only: skip '${dirLabel}' (malformed manifest: missing name)`);
|
|
130
|
+
}
|
|
131
|
+
return { skipReason, label };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let normalized = deepClone(rawManifest);
|
|
135
|
+
normalized.packagePath = buildTimePackageRoot;
|
|
136
|
+
let contributes = await normalizeManifestForBrowserOnly(normalized);
|
|
137
|
+
|
|
138
|
+
delete normalized.contributes;
|
|
139
|
+
|
|
140
|
+
const translations = await loadPackageTranslations(buildTimePackageRoot);
|
|
141
|
+
if (translations.default && Object.keys(translations.default).length > 0) {
|
|
142
|
+
const resolve = (_: string, defaultVal: string): string => defaultVal;
|
|
143
|
+
normalized = localizePackage(normalized, translations, resolve);
|
|
144
|
+
contributes = localizePackage(contributes, translations, resolve);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const engineType = pickEngineType(normalized);
|
|
148
|
+
const model = buildModel(normalized, engineType, { uiKind: 'web' });
|
|
149
|
+
model.licenseUrl = getPluginRootFileUrl(normalized, ['license', 'license.txt', 'license.md']);
|
|
150
|
+
model.readmeUrl = getPluginRootFileUrl(normalized, ['readme.md', 'readme.txt', 'readme']);
|
|
151
|
+
applyTrustExtraction(normalized, model);
|
|
152
|
+
const lifecycle = buildLifecycle(normalized, engineType);
|
|
153
|
+
|
|
154
|
+
const pluginId = getPluginId(normalized);
|
|
155
|
+
const dst = path.join(hostedPluginDir, pluginId);
|
|
156
|
+
|
|
157
|
+
await fs.copy(buildTimePackageRoot, dst, {
|
|
158
|
+
overwrite: true,
|
|
159
|
+
dereference: true,
|
|
160
|
+
filter: (src: string) => shouldCopyPluginPath(src, buildTimePackageRoot)
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
resolveHostedEntryPoint(model.entryPoint, dst);
|
|
164
|
+
rewriteModelPathsForHostedStatic(model, buildTimePackageRoot, pluginId);
|
|
165
|
+
|
|
166
|
+
// Raw VS Code-style package.json for worker rawModel (contributes stay unnormalized).
|
|
167
|
+
const diskManifest = deepClone(rawManifest);
|
|
168
|
+
prepareHostedPackageJson(diskManifest, pluginId, model.entryPoint);
|
|
169
|
+
await fs.writeJson(path.join(dst, 'package.json'), diskManifest, { spaces: 2 });
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
plugin: {
|
|
173
|
+
type: PluginType.System,
|
|
174
|
+
metadata: {
|
|
175
|
+
host: PLUGIN_HOST_BACKEND,
|
|
176
|
+
model,
|
|
177
|
+
lifecycle,
|
|
178
|
+
outOfSync: false
|
|
179
|
+
},
|
|
180
|
+
...(Object.keys(contributes).length > 0 ? { contributes } : {})
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
} catch (err) {
|
|
184
|
+
console.warn(`browser-only: skip plugin at ${pluginSourceDir}`, err);
|
|
185
|
+
return { label: dirLabel };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Copy filter: ignore node_modules/.git relative to the plugin root (not absolute path). */
|
|
190
|
+
export function shouldCopyPluginPath(src: string, pluginRoot: string): boolean {
|
|
191
|
+
return !PLUGIN_COPY_IGNORE.test(`${path.sep}${path.relative(pluginRoot, src)}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function resolvePluginRoot(dir: string): string | undefined {
|
|
195
|
+
const direct = path.join(dir, 'package.json');
|
|
196
|
+
if (fs.pathExistsSync(direct)) {
|
|
197
|
+
return dir;
|
|
198
|
+
}
|
|
199
|
+
const inExtension = path.join(dir, 'extension', 'package.json');
|
|
200
|
+
if (fs.pathExistsSync(inExtension)) {
|
|
201
|
+
return path.join(dir, 'extension');
|
|
202
|
+
}
|
|
203
|
+
const inPackage = path.join(dir, 'package', 'package.json');
|
|
204
|
+
if (fs.pathExistsSync(inPackage)) {
|
|
205
|
+
return path.join(dir, 'package');
|
|
206
|
+
}
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function hasContributes(pkg: PluginManifest): boolean {
|
|
211
|
+
const c = pkg.contributes;
|
|
212
|
+
return !!(c && typeof c === 'object' && Object.keys(c).length > 0);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function hasFrontendEntry(manifest: PluginManifest): boolean {
|
|
216
|
+
return !!manifest.theiaPlugin?.frontend || !!manifest.browser;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function hasBackendEntry(manifest: PluginManifest): boolean {
|
|
220
|
+
return !!manifest.main || !!manifest.theiaPlugin?.backend || !!manifest.theiaPlugin?.headless;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** `undefined` = include; backend-only even with contributes is excluded. */
|
|
224
|
+
export function getBrowserOnlySkipReason(
|
|
225
|
+
manifest: PluginManifest
|
|
226
|
+
): 'backend-only' | 'no-browser-surface' | 'malformed' | undefined {
|
|
227
|
+
if (!manifest.name) {
|
|
228
|
+
return 'malformed';
|
|
229
|
+
}
|
|
230
|
+
if (hasFrontendEntry(manifest)) {
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
if (hasBackendEntry(manifest)) {
|
|
234
|
+
return 'backend-only';
|
|
235
|
+
}
|
|
236
|
+
if (hasContributes(manifest)) {
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
return 'no-browser-surface';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function shouldIncludePluginInBrowserOnlyBuild(manifest: PluginManifest): boolean {
|
|
243
|
+
return getBrowserOnlySkipReason(manifest) === undefined;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Drop Node/Electron-only entry fields shared by list.json and on-disk package.json. */
|
|
247
|
+
function stripNonFrontendHostFields(manifest: PluginManifest): void {
|
|
248
|
+
manifest.publisher ??= UNPUBLISHED;
|
|
249
|
+
delete manifest.main;
|
|
250
|
+
if (manifest.theiaPlugin) {
|
|
251
|
+
delete manifest.theiaPlugin.backend;
|
|
252
|
+
delete manifest.theiaPlugin.headless;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function normalizePluginPackageForBrowserOnly(manifest: PluginManifest): void {
|
|
257
|
+
stripNonFrontendHostFields(manifest);
|
|
258
|
+
|
|
259
|
+
if (!manifest.engines) {
|
|
260
|
+
manifest.engines = { theiaPlugin: '*' };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (!manifest.theiaPlugin && manifest.browser) {
|
|
264
|
+
manifest.theiaPlugin = { frontend: manifest.browser };
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function normalizeManifestForBrowserOnly(manifest: PluginManifest): Promise<NormalizedPluginContribution> {
|
|
269
|
+
normalizePluginPackageForBrowserOnly(manifest);
|
|
270
|
+
updateActivationEvents(manifest);
|
|
271
|
+
|
|
272
|
+
const contributes: NormalizedPluginContribution = {};
|
|
273
|
+
const onError = (type: string, err: unknown, detail?: unknown): void => {
|
|
274
|
+
console.warn(`browser-only: [${manifest.name}] contribution '${type}'`, detail, err);
|
|
275
|
+
};
|
|
276
|
+
const onWarn = (msg: string): void => {
|
|
277
|
+
console.warn(`browser-only: [${manifest.name}] ${msg}`);
|
|
278
|
+
};
|
|
279
|
+
await normalizeContributions({
|
|
280
|
+
plugin: manifest,
|
|
281
|
+
resolveUrl: relative => toPluginUrl(manifest, relative),
|
|
282
|
+
resolveUri: (pck, relative) => toPluginUrl(pck, relative),
|
|
283
|
+
readGrammars: async (grammars, pluginPath) => {
|
|
284
|
+
const result = [];
|
|
285
|
+
for (const rawGrammar of grammars) {
|
|
286
|
+
const grammar = await readGrammarFromDisk(rawGrammar, pluginPath, { onError });
|
|
287
|
+
if (grammar) {
|
|
288
|
+
result.push(grammar);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return result;
|
|
292
|
+
},
|
|
293
|
+
onError,
|
|
294
|
+
onWarn,
|
|
295
|
+
}, contributes);
|
|
296
|
+
|
|
297
|
+
if (manifest.activationEvents?.length) {
|
|
298
|
+
contributes.activationEvents = [...manifest.activationEvents];
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return contributes;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* `list.json` carries normalized contributes + plugin metadata (single source of truth for Theia).
|
|
306
|
+
* `hostedPlugin/<id>/package.json` stays a VS Code-style raw manifest for worker `rawModel`:
|
|
307
|
+
* name-prefix strip, `main` removed, entry paths synced, and `packagePath`/`packageUri` set to the
|
|
308
|
+
* static hosted root (needed so relative assets resolve via `toPluginUrl`).
|
|
309
|
+
*/
|
|
310
|
+
function prepareHostedPackageJson(manifest: PluginManifest, pluginId: string, entryPoint: PluginEntryPoint): void {
|
|
311
|
+
stripNonFrontendHostFields(manifest);
|
|
312
|
+
|
|
313
|
+
const packageRoot = `${PLUGINS_BASE_PATH}/${pluginId}/`;
|
|
314
|
+
manifest.packagePath = packageRoot;
|
|
315
|
+
manifest.packageUri = packageRoot;
|
|
316
|
+
|
|
317
|
+
if (entryPoint.frontend) {
|
|
318
|
+
if (manifest.theiaPlugin) {
|
|
319
|
+
manifest.theiaPlugin.frontend = entryPoint.frontend;
|
|
320
|
+
}
|
|
321
|
+
if (manifest.browser) {
|
|
322
|
+
manifest.browser = entryPoint.frontend;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function resolveHostedEntryPoint(entryPoint: PluginEntryPoint, pluginRoot: string): void {
|
|
328
|
+
if (!entryPoint.frontend) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const absoluteEntry = path.resolve(pluginRoot, entryPoint.frontend);
|
|
332
|
+
const resolved = resolvePluginEntryFileSync(absoluteEntry);
|
|
333
|
+
if (resolved) {
|
|
334
|
+
entryPoint.frontend = path.relative(pluginRoot, resolved).split(path.sep).join('/');
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function rewriteModelPathsForHostedStatic(model: PluginModel, buildTimePackageRoot: string, pluginId: string): void {
|
|
339
|
+
model.packageUri = toHostedPluginUri(model.packageUri, buildTimePackageRoot, pluginId);
|
|
340
|
+
model.packagePath = model.packageUri;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export function resolvePluginEntryFileSync(absolutePath: string): string | undefined {
|
|
344
|
+
const candidates = [absolutePath];
|
|
345
|
+
const pathExtension = path.extname(absolutePath).toLowerCase();
|
|
346
|
+
if (!pathExtension) {
|
|
347
|
+
candidates.push(absolutePath + '.js');
|
|
348
|
+
} else if (pathExtension === '.js') {
|
|
349
|
+
candidates.push(absolutePath.replace(/\.js$/i, '.cjs'));
|
|
350
|
+
candidates.push(absolutePath.replace(/\.js$/i, '.mjs'));
|
|
351
|
+
}
|
|
352
|
+
for (const candidate of candidates) {
|
|
353
|
+
if (fs.pathExistsSync(candidate)) {
|
|
354
|
+
return candidate;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return undefined;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export function toHostedPluginUri(fileUri: string, pluginRoot: string, pluginId: string): string {
|
|
361
|
+
if (!fileUri.startsWith('file://')) {
|
|
362
|
+
return fileUri;
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
const filePath = fileURLToPath(fileUri);
|
|
366
|
+
const normalizedRoot = path.resolve(pluginRoot);
|
|
367
|
+
const normalizedPath = path.resolve(filePath);
|
|
368
|
+
if (!normalizedPath.startsWith(normalizedRoot + path.sep) && normalizedPath !== normalizedRoot) {
|
|
369
|
+
return fileUri;
|
|
370
|
+
}
|
|
371
|
+
const relative = path.relative(normalizedRoot, normalizedPath);
|
|
372
|
+
return `${PLUGINS_BASE_PATH}/${pluginId}/${relative.split(path.sep).join('/')}`;
|
|
373
|
+
} catch {
|
|
374
|
+
return fileUri;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 Maksim Kachurin 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 fs from 'fs-extra';
|
|
18
|
+
import { ApplicationPackage } from '@theia/application-package';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Write `lib/frontend/extensions.json` (Theia extension packages) for the About dialog,
|
|
22
|
+
* matching the backend Theia extension build output.
|
|
23
|
+
*/
|
|
24
|
+
export async function writeBrowserOnlyExtensionsList(applicationPackage: ApplicationPackage): Promise<void> {
|
|
25
|
+
const extensions = applicationPackage.extensionPackages.map(({ name, version }) => ({ name, version }));
|
|
26
|
+
await fs.writeJson(applicationPackage.lib('frontend', 'extensions.json'), extensions, { spaces: 2 });
|
|
27
|
+
}
|