@theia/plugin-ext 1.75.0-next.21 → 1.75.0-next.28
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/common/errors.d.ts +1 -14
- package/lib/common/errors.d.ts.map +1 -1
- package/lib/common/errors.js +3 -16
- package/lib/common/errors.js.map +1 -1
- package/lib/common/plugin-protocol.d.ts +45 -648
- package/lib/common/plugin-protocol.d.ts.map +1 -1
- package/lib/common/plugin-protocol.js +11 -34
- package/lib/common/plugin-protocol.js.map +1 -1
- package/lib/hosted/browser/worker/plugin-manifest-loader.d.ts.map +1 -1
- package/lib/hosted/browser/worker/plugin-manifest-loader.js +5 -36
- package/lib/hosted/browser/worker/plugin-manifest-loader.js.map +1 -1
- package/lib/hosted/browser/worker/worker-main.js +2 -1
- package/lib/hosted/browser/worker/worker-main.js.map +1 -1
- package/lib/hosted/node/hosted-plugin-localization-service.d.ts.map +1 -1
- package/lib/hosted/node/hosted-plugin-localization-service.js +4 -68
- package/lib/hosted/node/hosted-plugin-localization-service.js.map +1 -1
- package/lib/hosted/node/plugin-host-rpc.d.ts.map +1 -1
- package/lib/hosted/node/plugin-host-rpc.js +2 -2
- package/lib/hosted/node/plugin-host-rpc.js.map +1 -1
- package/lib/hosted/node/plugin-reader.d.ts.map +1 -1
- package/lib/hosted/node/plugin-reader.js +4 -3
- package/lib/hosted/node/plugin-reader.js.map +1 -1
- package/lib/hosted/node/scanners/grammars-reader.d.ts +3 -2
- package/lib/hosted/node/scanners/grammars-reader.d.ts.map +1 -1
- package/lib/hosted/node/scanners/grammars-reader.js +5 -23
- package/lib/hosted/node/scanners/grammars-reader.js.map +1 -1
- package/lib/hosted/node/scanners/scanner-theia.d.ts +38 -71
- package/lib/hosted/node/scanners/scanner-theia.d.ts.map +1 -1
- package/lib/hosted/node/scanners/scanner-theia.js +117 -768
- package/lib/hosted/node/scanners/scanner-theia.js.map +1 -1
- package/lib/hosted/node/scanners/scanner-theia.spec.js +191 -0
- package/lib/hosted/node/scanners/scanner-theia.spec.js.map +1 -1
- package/lib/main/browser/plugin-contribution-handler.js +1 -1
- package/lib/main/browser/plugin-contribution-handler.js.map +1 -1
- package/lib/main/browser/plugin-icon-service.d.ts.map +1 -1
- package/lib/main/browser/plugin-icon-service.js +2 -1
- package/lib/main/browser/plugin-icon-service.js.map +1 -1
- package/lib/main/browser/plugin-icon-theme-service.d.ts.map +1 -1
- package/lib/main/browser/plugin-icon-theme-service.js +2 -1
- package/lib/main/browser/plugin-icon-theme-service.js.map +1 -1
- package/lib/main/browser/plugin-shared-style.d.ts.map +1 -1
- package/lib/main/browser/plugin-shared-style.js +2 -1
- package/lib/main/browser/plugin-shared-style.js.map +1 -1
- package/package.json +31 -30
- package/src/common/errors.ts +1 -25
- package/src/common/plugin-protocol.ts +195 -662
- package/src/hosted/browser/worker/plugin-manifest-loader.ts +8 -41
- package/src/hosted/browser/worker/worker-main.ts +2 -1
- package/src/hosted/node/hosted-plugin-localization-service.ts +3 -82
- package/src/hosted/node/plugin-host-rpc.ts +3 -3
- package/src/hosted/node/plugin-reader.ts +4 -3
- package/src/hosted/node/scanners/grammars-reader.ts +14 -23
- package/src/hosted/node/scanners/scanner-theia.spec.ts +225 -1
- package/src/hosted/node/scanners/scanner-theia.ts +204 -881
- package/src/main/browser/plugin-contribution-handler.ts +1 -1
- package/src/main/browser/plugin-icon-service.ts +2 -1
- package/src/main/browser/plugin-icon-theme-service.ts +2 -1
- package/src/main/browser/plugin-shared-style.ts +2 -1
- package/lib/hosted/node/plugin-activation-events.d.ts +0 -7
- package/lib/hosted/node/plugin-activation-events.d.ts.map +0 -1
- package/lib/hosted/node/plugin-activation-events.js +0 -95
- package/lib/hosted/node/plugin-activation-events.js.map +0 -1
- package/lib/hosted/node/plugin-manifest-loader.d.ts +0 -3
- package/lib/hosted/node/plugin-manifest-loader.d.ts.map +0 -1
- package/lib/hosted/node/plugin-manifest-loader.js +0 -34
- package/lib/hosted/node/plugin-manifest-loader.js.map +0 -1
- package/lib/main/node/errors.spec.d.ts +0 -2
- package/lib/main/node/errors.spec.d.ts.map +0 -1
- package/lib/main/node/errors.spec.js +0 -36
- package/lib/main/node/errors.spec.js.map +0 -1
- package/src/hosted/node/plugin-activation-events.ts +0 -112
- package/src/hosted/node/plugin-manifest-loader.ts +0 -32
- package/src/main/node/errors.spec.ts +0 -37
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
|
|
17
17
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import { PluginModel, PluginPackage } from '../../../common/plugin-protocol';
|
|
20
20
|
import { Endpoint } from '@theia/core/lib/browser/endpoint';
|
|
21
21
|
import URI from '@theia/core/lib/common/uri';
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
import { localizeWithResolver } from '@theia/plugin-utils/lib/common/package-nls';
|
|
23
|
+
import { prepareLoadedManifest } from '@theia/plugin-utils/lib/common/plugin-manifest';
|
|
24
24
|
|
|
25
25
|
function getUri(pluginModel: PluginModel, relativePath: string): URI {
|
|
26
26
|
const ownURI = new Endpoint().getRestUrl();
|
|
@@ -54,23 +54,18 @@ function readContents(uri: string): Promise<string> {
|
|
|
54
54
|
|
|
55
55
|
async function readPluginJson(pluginModel: PluginModel, relativePath: string): Promise<any> {
|
|
56
56
|
const content = await readPluginFile(pluginModel, relativePath);
|
|
57
|
-
|
|
58
|
-
json.publisher ??= PluginIdentifiers.UNPUBLISHED;
|
|
59
|
-
return json;
|
|
57
|
+
return JSON.parse(content);
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
export async function loadManifest(pluginModel: PluginModel): Promise<any> {
|
|
63
61
|
const [manifest, translations] = await Promise.all([
|
|
64
|
-
readPluginJson(pluginModel, 'package.json')
|
|
62
|
+
readPluginJson(pluginModel, 'package.json').then(raw =>
|
|
63
|
+
prepareLoadedManifest(raw as PluginPackage, { updateActivationEvents: false })
|
|
64
|
+
),
|
|
65
65
|
loadTranslations(pluginModel)
|
|
66
66
|
]);
|
|
67
|
-
// translate vscode builtins, as they are published with a prefix.
|
|
68
|
-
const built_prefix = '@theia/vscode-builtin-';
|
|
69
|
-
if (manifest && manifest.name && manifest.name.startsWith(built_prefix)) {
|
|
70
|
-
manifest.name = manifest.name.substring(built_prefix.length);
|
|
71
|
-
}
|
|
72
67
|
return manifest && translations && Object.keys(translations).length ?
|
|
73
|
-
|
|
68
|
+
localizeWithResolver(manifest, key => translations[key]) :
|
|
74
69
|
manifest;
|
|
75
70
|
}
|
|
76
71
|
|
|
@@ -84,31 +79,3 @@ async function loadTranslations(pluginModel: PluginModel): Promise<any> {
|
|
|
84
79
|
return {};
|
|
85
80
|
}
|
|
86
81
|
}
|
|
87
|
-
|
|
88
|
-
function localize(value: any, translations: {
|
|
89
|
-
[key: string]: string
|
|
90
|
-
}): any {
|
|
91
|
-
if (typeof value === 'string') {
|
|
92
|
-
const match = NLS_REGEX.exec(value);
|
|
93
|
-
return match && translations[match[1]] || value;
|
|
94
|
-
}
|
|
95
|
-
if (Array.isArray(value)) {
|
|
96
|
-
const result = [];
|
|
97
|
-
for (const item of value) {
|
|
98
|
-
result.push(localize(item, translations));
|
|
99
|
-
}
|
|
100
|
-
return result;
|
|
101
|
-
}
|
|
102
|
-
if (value === null) {
|
|
103
|
-
return value;
|
|
104
|
-
}
|
|
105
|
-
if (typeof value === 'object') {
|
|
106
|
-
const result: { [key: string]: any } = {};
|
|
107
|
-
// eslint-disable-next-line guard-for-in
|
|
108
|
-
for (const propertyName in value) {
|
|
109
|
-
result[propertyName] = localize(value[propertyName], translations);
|
|
110
|
-
}
|
|
111
|
-
return result;
|
|
112
|
-
}
|
|
113
|
-
return value;
|
|
114
|
-
}
|
|
@@ -35,6 +35,7 @@ import { EnvExtImpl } from '../../../plugin/env';
|
|
|
35
35
|
import { DebugExtImpl } from '../../../plugin/debug/debug-ext';
|
|
36
36
|
import { LocalizationExtImpl } from '../../../plugin/localization-ext';
|
|
37
37
|
import pluginHostModule from './worker-plugin-module';
|
|
38
|
+
import { PLUGINS_BASE_PATH } from '@theia/plugin-utils/lib/common/constants';
|
|
38
39
|
|
|
39
40
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
41
|
const ctx = self as any;
|
|
@@ -70,7 +71,7 @@ pluginManager.setPluginHost({
|
|
|
70
71
|
ctx.frontendModuleName = plugin.lifecycle.frontendModuleName;
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
ctx.importScripts(
|
|
74
|
+
ctx.importScripts(`./${PLUGINS_BASE_PATH}/` + getPluginId(plugin.model) + '/' + plugin.pluginPath);
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
|
|
@@ -22,9 +22,11 @@ import { inject, injectable, named } from '@theia/core/shared/inversify';
|
|
|
22
22
|
import { DeployedPlugin, Localization as PluginLocalization, PluginIdentifiers, Translation } from '../../common';
|
|
23
23
|
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
|
24
24
|
import { BackendApplicationContribution } from '@theia/core/lib/node';
|
|
25
|
-
import { Disposable, DisposableCollection,
|
|
25
|
+
import { Disposable, DisposableCollection, MaybePromise, nls, Path, URI, ILogger } from '@theia/core';
|
|
26
26
|
import { Deferred } from '@theia/core/lib/common/promise-util';
|
|
27
27
|
import { LanguagePackBundle, LanguagePackService } from '../../common/language-pack-service';
|
|
28
|
+
import { localizePackage } from '@theia/plugin-utils/lib/common/package-nls';
|
|
29
|
+
import { loadPackageTranslations } from '@theia/plugin-utils/lib/node/package-nls';
|
|
28
30
|
|
|
29
31
|
export interface VSCodeNlsConfig {
|
|
30
32
|
locale: string
|
|
@@ -327,84 +329,3 @@ async function loadTranslations(packagePath: Path, translations: Translation[]):
|
|
|
327
329
|
function buildTranslationKey(pluginId: string, scope: string, key: string): string {
|
|
328
330
|
return `${pluginId}/${Localization.transformKey(scope)}/${key}`;
|
|
329
331
|
}
|
|
330
|
-
|
|
331
|
-
// Localization logic for `package.json` entries
|
|
332
|
-
// Extensions can use `package.nls.json` files to store translations for values in their package.json
|
|
333
|
-
// This logic has not changed with the introduction of the vscode.l10n API
|
|
334
|
-
|
|
335
|
-
interface PackageTranslation {
|
|
336
|
-
translation?: Record<string, string>
|
|
337
|
-
default?: Record<string, string>
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
async function loadPackageTranslations(pluginPath: string, locale: string): Promise<PackageTranslation> {
|
|
341
|
-
const localizedPluginPath = path.join(pluginPath, `package.nls.${locale}.json`);
|
|
342
|
-
try {
|
|
343
|
-
const defaultValue = coerceLocalizations(await fs.readJson(path.join(pluginPath, 'package.nls.json')));
|
|
344
|
-
if (await fs.pathExists(localizedPluginPath)) {
|
|
345
|
-
return {
|
|
346
|
-
translation: coerceLocalizations(await fs.readJson(localizedPluginPath)),
|
|
347
|
-
default: defaultValue
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
return {
|
|
351
|
-
default: defaultValue
|
|
352
|
-
};
|
|
353
|
-
} catch (e) {
|
|
354
|
-
if (e.code !== 'ENOENT') {
|
|
355
|
-
throw e;
|
|
356
|
-
}
|
|
357
|
-
return {};
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
interface LocalizeInfo {
|
|
362
|
-
message: string
|
|
363
|
-
comment?: string
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
function isLocalizeInfo(obj: unknown): obj is LocalizeInfo {
|
|
367
|
-
return isObject(obj) && 'message' in obj || false;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
function coerceLocalizations(translations: Record<string, string | LocalizeInfo>): Record<string, string> {
|
|
371
|
-
for (const [key, value] of Object.entries(translations)) {
|
|
372
|
-
if (isLocalizeInfo(value)) {
|
|
373
|
-
translations[key] = value.message;
|
|
374
|
-
} else if (typeof value !== 'string') {
|
|
375
|
-
// Only strings or LocalizeInfo values are valid
|
|
376
|
-
translations[key] = 'INVALID TRANSLATION VALUE';
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
return translations as Record<string, string>;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
function localizePackage(value: unknown, translations: PackageTranslation, callback: (key: string, defaultValue: string) => string): unknown {
|
|
383
|
-
if (typeof value === 'string') {
|
|
384
|
-
let result = value;
|
|
385
|
-
if (value.length > 2 && value.startsWith('%') && value.endsWith('%')) {
|
|
386
|
-
const key = value.slice(1, -1);
|
|
387
|
-
if (translations.translation && key in translations.translation) {
|
|
388
|
-
result = translations.translation[key];
|
|
389
|
-
} else if (translations.default && key in translations.default) {
|
|
390
|
-
result = callback(key, translations.default[key]);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
return result;
|
|
394
|
-
}
|
|
395
|
-
if (Array.isArray(value)) {
|
|
396
|
-
const result = [];
|
|
397
|
-
for (const item of value) {
|
|
398
|
-
result.push(localizePackage(item, translations, callback));
|
|
399
|
-
}
|
|
400
|
-
return result;
|
|
401
|
-
}
|
|
402
|
-
if (isObject(value)) {
|
|
403
|
-
const result: Record<string, unknown> = {};
|
|
404
|
-
for (const [name, item] of Object.entries(value)) {
|
|
405
|
-
result[name] = localizePackage(item, translations, callback);
|
|
406
|
-
}
|
|
407
|
-
return result;
|
|
408
|
-
}
|
|
409
|
-
return value;
|
|
410
|
-
}
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
MAIN_RPC_CONTEXT, Plugin, PluginAPIFactory, PluginManager,
|
|
26
26
|
LocalizationExt
|
|
27
27
|
} from '../../common/plugin-api-rpc';
|
|
28
|
-
import { PluginMetadata, PluginModel } from '../../common/plugin-protocol';
|
|
28
|
+
import { PluginMetadata, PluginModel, PluginPackage } from '../../common/plugin-protocol';
|
|
29
29
|
import { createAPIFactory } from '../../plugin/plugin-context';
|
|
30
30
|
import { EnvExtImpl } from '../../plugin/env';
|
|
31
31
|
import { PreferenceRegistryExtImpl } from '../../plugin/preference-registry';
|
|
@@ -35,7 +35,7 @@ import { EditorsAndDocumentsExtImpl } from '../../plugin/editors-and-documents';
|
|
|
35
35
|
import { WorkspaceExtImpl } from '../../plugin/workspace';
|
|
36
36
|
import { MessageRegistryExt } from '../../plugin/message-registry';
|
|
37
37
|
import { ClipboardExt } from '../../plugin/clipboard-ext';
|
|
38
|
-
import { loadManifest } from '
|
|
38
|
+
import { loadManifest } from '@theia/plugin-utils/lib/node/plugin-manifest';
|
|
39
39
|
import { KeyValueStorageProxy } from '../../plugin/plugin-storage';
|
|
40
40
|
import { WebviewsExtImpl } from '../../plugin/webviews';
|
|
41
41
|
import { TerminalServiceExtImpl } from '../../plugin/terminal-ext';
|
|
@@ -214,7 +214,7 @@ export abstract class AbstractPluginHostRPC<PM extends AbstractPluginManagerExtI
|
|
|
214
214
|
const pluginModel = plg.model;
|
|
215
215
|
const pluginLifecycle = plg.lifecycle;
|
|
216
216
|
|
|
217
|
-
const rawModel = await loadManifest(pluginModel.packagePath);
|
|
217
|
+
const rawModel = await loadManifest<PluginPackage>(pluginModel.packagePath);
|
|
218
218
|
rawModel.packagePath = pluginModel.packagePath;
|
|
219
219
|
if (pluginModel.entryPoint!.frontend) {
|
|
220
220
|
foreign.push({
|
|
@@ -23,7 +23,8 @@ import { inject, injectable, optional, multiInject } from '@theia/core/shared/in
|
|
|
23
23
|
import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application';
|
|
24
24
|
import { PluginMetadata, getPluginId, MetadataProcessor, PluginPackage, PluginContribution } from '../../common/plugin-protocol';
|
|
25
25
|
import { MetadataScanner } from './metadata-scanner';
|
|
26
|
-
import { loadManifest } from '
|
|
26
|
+
import { loadManifest } from '@theia/plugin-utils/lib/node/plugin-manifest';
|
|
27
|
+
import { PLUGINS_BASE_PATH } from '@theia/plugin-utils/lib/common/constants';
|
|
27
28
|
|
|
28
29
|
@injectable()
|
|
29
30
|
export class HostedPluginReader implements BackendApplicationContribution {
|
|
@@ -43,7 +44,7 @@ export class HostedPluginReader implements BackendApplicationContribution {
|
|
|
43
44
|
protected pluginsIdsFiles: Map<string, string> = new Map();
|
|
44
45
|
|
|
45
46
|
configure(app: express.Application): void {
|
|
46
|
-
app.get(
|
|
47
|
+
app.get(`/${PLUGINS_BASE_PATH}/:pluginId/:path(*)`, async (req, res) => {
|
|
47
48
|
const pluginId = req.params.pluginId;
|
|
48
49
|
const filePath = req.params.path;
|
|
49
50
|
|
|
@@ -141,7 +142,7 @@ export class HostedPluginReader implements BackendApplicationContribution {
|
|
|
141
142
|
return undefined;
|
|
142
143
|
}
|
|
143
144
|
const resolvedPluginPath = await realpath(pluginPath);
|
|
144
|
-
const manifest = await loadManifest(resolvedPluginPath);
|
|
145
|
+
const manifest = await loadManifest<PluginPackage>(resolvedPluginPath);
|
|
145
146
|
if (!manifest) {
|
|
146
147
|
return undefined;
|
|
147
148
|
}
|
|
@@ -16,16 +16,20 @@
|
|
|
16
16
|
|
|
17
17
|
import { injectable } from '@theia/core/shared/inversify';
|
|
18
18
|
import { PluginPackageGrammarsContribution, GrammarsContribution } from '../../../common';
|
|
19
|
-
import
|
|
20
|
-
import
|
|
19
|
+
import { readGrammarFromDisk } from '@theia/plugin-utils/lib/node/read-grammars';
|
|
20
|
+
import type { NormalizeContributionsContext } from '@theia/plugin-utils/lib/common/contribution-types';
|
|
21
21
|
|
|
22
22
|
@injectable()
|
|
23
23
|
export class GrammarsReader {
|
|
24
24
|
|
|
25
|
-
async readGrammars(
|
|
25
|
+
async readGrammars(
|
|
26
|
+
rawGrammars: readonly PluginPackageGrammarsContribution[],
|
|
27
|
+
pluginPath: string,
|
|
28
|
+
log?: Pick<NormalizeContributionsContext, 'onError'>
|
|
29
|
+
): Promise<GrammarsContribution[]> {
|
|
26
30
|
const result = new Array<GrammarsContribution>();
|
|
27
31
|
for (const rawGrammar of rawGrammars) {
|
|
28
|
-
const grammar = await this.readGrammar(rawGrammar, pluginPath);
|
|
32
|
+
const grammar = await this.readGrammar(rawGrammar, pluginPath, log);
|
|
29
33
|
if (grammar) {
|
|
30
34
|
result.push(grammar);
|
|
31
35
|
}
|
|
@@ -34,24 +38,11 @@ export class GrammarsReader {
|
|
|
34
38
|
return result;
|
|
35
39
|
}
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
} else {
|
|
44
|
-
grammar = await fs.readFile(path.resolve(pluginPath, rawGrammar.path), 'utf8');
|
|
45
|
-
}
|
|
46
|
-
return {
|
|
47
|
-
language: rawGrammar.language,
|
|
48
|
-
scope: rawGrammar.scopeName,
|
|
49
|
-
format: rawGrammar.path.endsWith('json') ? 'json' : 'plist',
|
|
50
|
-
grammar: grammar,
|
|
51
|
-
grammarLocation: rawGrammar.path,
|
|
52
|
-
injectTo: rawGrammar.injectTo,
|
|
53
|
-
embeddedLanguages: rawGrammar.embeddedLanguages,
|
|
54
|
-
tokenTypes: rawGrammar.tokenTypes
|
|
55
|
-
};
|
|
41
|
+
protected async readGrammar(
|
|
42
|
+
rawGrammar: PluginPackageGrammarsContribution,
|
|
43
|
+
pluginPath: string,
|
|
44
|
+
log?: Pick<NormalizeContributionsContext, 'onError'>
|
|
45
|
+
): Promise<GrammarsContribution | undefined> {
|
|
46
|
+
return readGrammarFromDisk(rawGrammar, pluginPath, log);
|
|
56
47
|
}
|
|
57
48
|
}
|
|
@@ -21,7 +21,7 @@ import * as os from 'os';
|
|
|
21
21
|
import * as path from 'path';
|
|
22
22
|
import * as fs from 'fs';
|
|
23
23
|
import { AbstractPluginScanner, TheiaPluginScanner } from './scanner-theia';
|
|
24
|
-
import { PluginPackage, PluginEntryPoint } from '../../../common/plugin-protocol';
|
|
24
|
+
import { PluginPackage, PluginEntryPoint, PluginPackageWalkthrough, WalkthroughContribution } from '../../../common/plugin-protocol';
|
|
25
25
|
import { PreferenceSchema } from '@theia/core/lib/common/preferences/preference-schema';
|
|
26
26
|
import URI from '@theia/core/lib/common/uri';
|
|
27
27
|
|
|
@@ -206,3 +206,227 @@ describe('TheiaPluginScanner configuration default derivation', () => {
|
|
|
206
206
|
expect(props['ext.nullable'].default).to.equal(null);
|
|
207
207
|
});
|
|
208
208
|
});
|
|
209
|
+
|
|
210
|
+
describe('TheiaPluginScanner - readWalkthroughs', () => {
|
|
211
|
+
let scannerInstance: TheiaPluginScanner;
|
|
212
|
+
|
|
213
|
+
before(() => {
|
|
214
|
+
scannerInstance = new (TheiaPluginScanner as any)();
|
|
215
|
+
(scannerInstance as any).logger = { error: () => { }, warn: () => { }, info: () => { }, debug: () => { } };
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
function callReadWalkthroughs(
|
|
219
|
+
walkthroughs: PluginPackageWalkthrough[],
|
|
220
|
+
publisher = 'test-publisher',
|
|
221
|
+
name = 'test-plugin',
|
|
222
|
+
icon?: string
|
|
223
|
+
): WalkthroughContribution[] | undefined {
|
|
224
|
+
const pkg = {
|
|
225
|
+
name,
|
|
226
|
+
publisher,
|
|
227
|
+
version: '1.0.0',
|
|
228
|
+
icon,
|
|
229
|
+
contributes: { walkthroughs }
|
|
230
|
+
} as any;
|
|
231
|
+
return (scannerInstance as any).readWalkthroughs(pkg);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
it('should resolve the icon of the contributing extension', () => {
|
|
235
|
+
const result = callReadWalkthroughs([{
|
|
236
|
+
id: 'wt1',
|
|
237
|
+
title: 'WT',
|
|
238
|
+
description: 'desc',
|
|
239
|
+
steps: [{ id: 's1', title: 'S1', description: 'd1' }]
|
|
240
|
+
}], 'test-publisher', 'test-plugin', 'images/icon.png');
|
|
241
|
+
|
|
242
|
+
expect(result![0].pluginIcon).to.equal('hostedPlugin/test_publisher_test_plugin/images/icon.png');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should leave the extension icon undefined when the package declares none', () => {
|
|
246
|
+
const result = callReadWalkthroughs([{
|
|
247
|
+
id: 'wt1',
|
|
248
|
+
title: 'WT',
|
|
249
|
+
description: 'desc',
|
|
250
|
+
steps: [{ id: 's1', title: 'S1', description: 'd1' }]
|
|
251
|
+
}]);
|
|
252
|
+
|
|
253
|
+
expect(result![0].pluginIcon).to.be.undefined;
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should read a valid walkthrough contribution', () => {
|
|
257
|
+
const result = callReadWalkthroughs([{
|
|
258
|
+
id: 'my-walkthrough',
|
|
259
|
+
title: 'My Walkthrough',
|
|
260
|
+
description: 'A test walkthrough',
|
|
261
|
+
steps: [{
|
|
262
|
+
id: 'step1',
|
|
263
|
+
title: 'Step 1',
|
|
264
|
+
description: 'First step',
|
|
265
|
+
completionEvents: ['onCommand:my.command']
|
|
266
|
+
}]
|
|
267
|
+
}]);
|
|
268
|
+
|
|
269
|
+
expect(result).to.not.be.undefined;
|
|
270
|
+
expect(result).to.have.lengthOf(1);
|
|
271
|
+
expect(result![0].id).to.equal('my-walkthrough');
|
|
272
|
+
expect(result![0].title).to.equal('My Walkthrough');
|
|
273
|
+
expect(result![0].pluginId).to.equal('test-publisher.test-plugin');
|
|
274
|
+
expect(result![0].steps).to.have.lengthOf(1);
|
|
275
|
+
expect(result![0].steps[0].id).to.equal('step1');
|
|
276
|
+
expect(result![0].steps[0].completionEvents).to.deep.equal(['onCommand:my.command']);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should skip walkthroughs with missing id', () => {
|
|
280
|
+
const result = callReadWalkthroughs([{
|
|
281
|
+
id: '',
|
|
282
|
+
title: 'No ID Walkthrough',
|
|
283
|
+
description: 'Missing id',
|
|
284
|
+
steps: []
|
|
285
|
+
}]);
|
|
286
|
+
|
|
287
|
+
expect(result).to.be.undefined;
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should skip walkthroughs with missing title', () => {
|
|
291
|
+
const result = callReadWalkthroughs([{
|
|
292
|
+
id: 'valid-id',
|
|
293
|
+
title: '',
|
|
294
|
+
description: 'Missing title',
|
|
295
|
+
steps: []
|
|
296
|
+
}]);
|
|
297
|
+
|
|
298
|
+
expect(result).to.be.undefined;
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('should skip walkthrough steps with missing id', () => {
|
|
302
|
+
const result = callReadWalkthroughs([{
|
|
303
|
+
id: 'wt1',
|
|
304
|
+
title: 'WT1',
|
|
305
|
+
description: 'desc',
|
|
306
|
+
steps: [
|
|
307
|
+
{ id: '', title: 'Step', description: 'desc' },
|
|
308
|
+
{ id: 'valid-step', title: 'Valid Step', description: 'desc' }
|
|
309
|
+
]
|
|
310
|
+
}]);
|
|
311
|
+
|
|
312
|
+
expect(result).to.have.lengthOf(1);
|
|
313
|
+
expect(result![0].steps).to.have.lengthOf(1);
|
|
314
|
+
expect(result![0].steps[0].id).to.equal('valid-step');
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('should return undefined when no walkthroughs are contributed', () => {
|
|
318
|
+
const pkg = { name: 'test', publisher: 'pub', contributes: {} } as any;
|
|
319
|
+
const result = (scannerInstance as any).readWalkthroughs(pkg);
|
|
320
|
+
expect(result).to.be.undefined;
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should read multiple walkthroughs', () => {
|
|
324
|
+
const result = callReadWalkthroughs([
|
|
325
|
+
{
|
|
326
|
+
id: 'wt1',
|
|
327
|
+
title: 'Walkthrough 1',
|
|
328
|
+
description: 'First',
|
|
329
|
+
steps: [{ id: 's1', title: 'S1', description: 'd1' }]
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
id: 'wt2',
|
|
333
|
+
title: 'Walkthrough 2',
|
|
334
|
+
description: 'Second',
|
|
335
|
+
steps: [{ id: 's2', title: 'S2', description: 'd2' }]
|
|
336
|
+
}
|
|
337
|
+
]);
|
|
338
|
+
|
|
339
|
+
expect(result).to.have.lengthOf(2);
|
|
340
|
+
expect(result![0].id).to.equal('wt1');
|
|
341
|
+
expect(result![1].id).to.equal('wt2');
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('should preserve walkthrough optional fields', () => {
|
|
345
|
+
const result = callReadWalkthroughs([{
|
|
346
|
+
id: 'wt-full',
|
|
347
|
+
title: 'Full Walkthrough',
|
|
348
|
+
description: 'Full desc',
|
|
349
|
+
steps: [{
|
|
350
|
+
id: 's1',
|
|
351
|
+
title: 'S1',
|
|
352
|
+
description: 'step desc',
|
|
353
|
+
when: 'isLinux',
|
|
354
|
+
media: { markdown: 'content.md' },
|
|
355
|
+
completionEvents: ['onCommand:test']
|
|
356
|
+
}],
|
|
357
|
+
when: 'workspacePlatform == linux',
|
|
358
|
+
icon: 'book'
|
|
359
|
+
}]);
|
|
360
|
+
|
|
361
|
+
expect(result).to.have.lengthOf(1);
|
|
362
|
+
expect(result![0].when).to.equal('workspacePlatform == linux');
|
|
363
|
+
expect(result![0].icon).to.equal('book');
|
|
364
|
+
expect(result![0].steps[0].when).to.equal('isLinux');
|
|
365
|
+
expect(result![0].steps[0].media).to.deep.equal({ markdown: 'hostedPlugin/test_publisher_test_plugin/content.md' });
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('should lowercase pluginId', () => {
|
|
369
|
+
const result = callReadWalkthroughs([{
|
|
370
|
+
id: 'wt1',
|
|
371
|
+
title: 'WT',
|
|
372
|
+
description: 'desc',
|
|
373
|
+
steps: [{ id: 's1', title: 'S1', description: 'd1' }]
|
|
374
|
+
}], 'MyPublisher', 'MyPlugin');
|
|
375
|
+
|
|
376
|
+
expect(result).to.have.lengthOf(1);
|
|
377
|
+
expect(result![0].pluginId).to.equal('mypublisher.myplugin');
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('should resolve media image paths through toPluginUrl', () => {
|
|
381
|
+
const result = callReadWalkthroughs([{
|
|
382
|
+
id: 'wt1',
|
|
383
|
+
title: 'WT',
|
|
384
|
+
description: 'desc',
|
|
385
|
+
steps: [{
|
|
386
|
+
id: 's1',
|
|
387
|
+
title: 'S1',
|
|
388
|
+
description: 'd1',
|
|
389
|
+
media: { image: 'media/image.png' }
|
|
390
|
+
}]
|
|
391
|
+
}]);
|
|
392
|
+
|
|
393
|
+
expect(result).to.have.lengthOf(1);
|
|
394
|
+
expect(result![0].steps[0].media).to.deep.equal({ image: 'hostedPlugin/test_publisher_test_plugin/media/image.png' });
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it('should keep the alt text of svg media', () => {
|
|
398
|
+
const result = callReadWalkthroughs([{
|
|
399
|
+
id: 'wt1',
|
|
400
|
+
title: 'WT',
|
|
401
|
+
description: 'desc',
|
|
402
|
+
steps: [{
|
|
403
|
+
id: 's1',
|
|
404
|
+
title: 'S1',
|
|
405
|
+
description: 'd1',
|
|
406
|
+
media: { svg: 'media/icon.svg', altText: 'An icon' }
|
|
407
|
+
}]
|
|
408
|
+
}]);
|
|
409
|
+
|
|
410
|
+
expect(result![0].steps[0].media).to.deep.equal({
|
|
411
|
+
svg: 'hostedPlugin/test_publisher_test_plugin/media/icon.svg',
|
|
412
|
+
altText: 'An icon'
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it('should resolve media svg paths through toPluginUrl', () => {
|
|
417
|
+
const result = callReadWalkthroughs([{
|
|
418
|
+
id: 'wt1',
|
|
419
|
+
title: 'WT',
|
|
420
|
+
description: 'desc',
|
|
421
|
+
steps: [{
|
|
422
|
+
id: 's1',
|
|
423
|
+
title: 'S1',
|
|
424
|
+
description: 'd1',
|
|
425
|
+
media: { svg: 'media/icon.svg' }
|
|
426
|
+
}]
|
|
427
|
+
}]);
|
|
428
|
+
|
|
429
|
+
expect(result).to.have.lengthOf(1);
|
|
430
|
+
expect(result![0].steps[0].media).to.deep.equal({ svg: 'hostedPlugin/test_publisher_test_plugin/media/icon.svg' });
|
|
431
|
+
});
|
|
432
|
+
});
|