code-workspace-zhuiyi 1.0.0 → 1.1.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/README.md +2 -0
- package/README.zh-CN.md +2 -0
- package/artifacts/manifest.json +1 -1
- package/docs/extension-development/extension-development-guide.zh-CN.md +9 -0
- package/package.json +1 -1
- package/src/cli/commands/extension.js +57 -0
- package/src/cli/commands/init.js +11 -8
- package/src/cli/registry.js +1 -1
- package/src/core/extension-local.js +66 -0
- package/src/core/extension-registry-lifecycle.js +9 -0
- package/src/core/extension-store.js +10 -2
- package/src/init/wizard.js +17 -2
package/README.md
CHANGED
|
@@ -25,6 +25,8 @@ codew init .code-workspace
|
|
|
25
25
|
|
|
26
26
|
`.code-workspace` is the directory name and can be determined by yourself.
|
|
27
27
|
|
|
28
|
+
Interactive initialization asks for “Development mode: Yes/No”. New Workspaces default to “Yes”; existing Workspaces use their saved value as the default. Pass `--dev true` or `--dev false` to select it explicitly without prompting.
|
|
29
|
+
|
|
28
30
|
Non-interactive initialization:
|
|
29
31
|
|
|
30
32
|
```bash
|
package/README.zh-CN.md
CHANGED
package/artifacts/manifest.json
CHANGED
|
@@ -124,6 +124,15 @@ codew extension digest update ./example-extension --yes
|
|
|
124
124
|
|
|
125
125
|
该命令会先更新 `extension/manifest.json.entrySha256`,再更新 `package.json.codeWorkspace.packageSha256`。
|
|
126
126
|
|
|
127
|
+
完成开发后,可以在上传 Registry 前使用最终 tarball 在本地 Workspace 安装测试:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
codew extension pack ./example-extension --output ./dist
|
|
131
|
+
codew extension install --local ./dist/codew-ext-example-extension-0.1.0.tgz --yes
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`extension install --local` 只接受 `.tgz`,会复用正式安装的完整 tarball 校验、Extension Store 导入和 Workspace 事务;不支持直接安装源码目录,也不会覆盖相同 id/version 下的不同 package digest。`--local` 模式不能与 `--version`、`--allow-deprecated` 或 `--offline` 同时使用。
|
|
135
|
+
|
|
127
136
|
### 3.1 身份与展示
|
|
128
137
|
|
|
129
138
|
- `schemaVersion` 必须是 `3`;
|
package/package.json
CHANGED
|
@@ -14,6 +14,8 @@ const {
|
|
|
14
14
|
const { loadInitManifest } = require("../../core/init");
|
|
15
15
|
const { acquireInitLock } = require("../../core/init-lock");
|
|
16
16
|
const { defaultExtensionStoreRoot } = require("../../core/extension-store");
|
|
17
|
+
const { planLocalExtensionPackage } = require("../../core/extension-registry-lifecycle");
|
|
18
|
+
const { importLocalExtensionTarball, prepareLocalExtensionTarball } = require("../../core/extension-local");
|
|
17
19
|
const { packExtensionSourceToDirectory } = require("../../core/extension-package");
|
|
18
20
|
const { digestUpdate, scaffoldExtensionPackage } = require("../../core/extension-scaffold");
|
|
19
21
|
const { resolveExtensionSettings } = require("../../core/extension-settings");
|
|
@@ -207,6 +209,7 @@ function decorateBatchResults(batch, plans, options) {
|
|
|
207
209
|
}
|
|
208
210
|
|
|
209
211
|
async function executeExtensionInstall(invocation) {
|
|
212
|
+
if (invocation.options.local === true) return await executeExtensionInstallLocal(invocation);
|
|
210
213
|
const command = "extension.install";
|
|
211
214
|
const dependencies = registryDependencies(invocation.dependencies || {});
|
|
212
215
|
validateInstallOptions(invocation);
|
|
@@ -315,6 +318,59 @@ async function executeExtensionInstall(invocation) {
|
|
|
315
318
|
});
|
|
316
319
|
}
|
|
317
320
|
|
|
321
|
+
async function executeExtensionInstallLocal(invocation) {
|
|
322
|
+
const command = "extension.install";
|
|
323
|
+
const dependencies = invocation.dependencies || {};
|
|
324
|
+
if (invocation.args.length !== 1) {
|
|
325
|
+
throw new WorkspaceError("EXTENSION_INSTALL_MODE_CONFLICT", "--local requires exactly one tarball path and no extension names.", {
|
|
326
|
+
remediation: "Use `codew extension install --local <tarball> --yes`.",
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
if (invocation.options.version || invocation.options.allowDeprecated || invocation.options.offline) {
|
|
330
|
+
throw new WorkspaceError("EXTENSION_INSTALL_MODE_CONFLICT", "--local cannot be combined with Registry version, deprecation, or offline options.", {
|
|
331
|
+
remediation: "Remove --version, --allow-deprecated, and --offline when installing a local tarball.",
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
const storeRoot = dependencies.extensionStoreRoot || defaultExtensionStoreRoot();
|
|
335
|
+
const prepared = await prepareLocalExtensionTarball(invocation.args[0], dependencies);
|
|
336
|
+
try {
|
|
337
|
+
const stateInspection = inspectExtensionState(invocation.root);
|
|
338
|
+
const tools = resolveWorkspaceTools({ state: loadState(invocation.root), manifestTools: loadInitManifest().tools }).tools;
|
|
339
|
+
const plan = planLocalExtensionPackage(prepared.packageRecord, { tools, state: stateInspection.state });
|
|
340
|
+
const planText = formatInstallPlan([{ ...plan, source: "local" }]);
|
|
341
|
+
if (!(await (dependencies.confirm || confirm)(`${planText}\nContinue?`, invocation.options))) {
|
|
342
|
+
throw new WorkspaceError("CLI_CANCELLED", "Local extension installation cancelled.");
|
|
343
|
+
}
|
|
344
|
+
await importLocalExtensionTarball(invocation.args[0], { ...dependencies, extensionStoreRoot: storeRoot });
|
|
345
|
+
const workspace = invocation.config.workspace;
|
|
346
|
+
const batch = runExtensionBatch(invocation.root, [plan], (extension) => ({
|
|
347
|
+
schemaVersion: 1,
|
|
348
|
+
extensionSpecVersion: extension.extensionSpecVersion,
|
|
349
|
+
extension: { id: extension.id, version: extension.version },
|
|
350
|
+
workspace: { name: workspace.name, uuid: workspace.uuid, language: workspace.language },
|
|
351
|
+
tools,
|
|
352
|
+
}), {
|
|
353
|
+
requested: [plan.id],
|
|
354
|
+
useExtensionStore: true,
|
|
355
|
+
extensionStoreRoot: storeRoot,
|
|
356
|
+
});
|
|
357
|
+
const results = decorateBatchResults(batch, [plan], invocation.options);
|
|
358
|
+
const diagnostics = batch.results.filter((item) => item.status === "failed").map((item) => ({
|
|
359
|
+
code: item.code || "EXTENSION_LOCAL_INSTALL_FAILED",
|
|
360
|
+
severity: "error",
|
|
361
|
+
message: item.message || `Extension ${item.id} failed to install.`,
|
|
362
|
+
extension: item.id,
|
|
363
|
+
version: item.version,
|
|
364
|
+
}));
|
|
365
|
+
return selectionResult(command, [plan.id], results.map((item) => ({ ...lifecycleResultEntry(item, "install"), archive: prepared.archive })), {
|
|
366
|
+
diagnostics,
|
|
367
|
+
text: lifecycleResultText(results, "install"),
|
|
368
|
+
});
|
|
369
|
+
} finally {
|
|
370
|
+
prepared.cleanup();
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
318
374
|
async function executeExtensionSearch(invocation) {
|
|
319
375
|
const dependencies = registryDependencies(invocation.dependencies || {});
|
|
320
376
|
const interactive = dependencies.interactive ?? (!invocation.options.json && invocation.options.yes !== true && process.stdin.isTTY && process.stdout.isTTY);
|
|
@@ -648,6 +704,7 @@ module.exports = {
|
|
|
648
704
|
collectRegistryExtensionInstallSelection,
|
|
649
705
|
executeExtension,
|
|
650
706
|
executeExtensionInstall,
|
|
707
|
+
executeExtensionInstallLocal,
|
|
651
708
|
executeExtensionPack,
|
|
652
709
|
executeExtensionInit,
|
|
653
710
|
executeExtensionDigestUpdate,
|
package/src/cli/commands/init.js
CHANGED
|
@@ -99,7 +99,7 @@ async function executeInit(invocation) {
|
|
|
99
99
|
async function executeInitUnlocked(invocation, root) {
|
|
100
100
|
const { options } = invocation;
|
|
101
101
|
const requestedDev = parseDevOption(options.dev);
|
|
102
|
-
|
|
102
|
+
let dev = resolveWorkspaceDev(root, requestedDev);
|
|
103
103
|
const dependencies = invocation.dependencies && Object.prototype.hasOwnProperty.call(invocation.dependencies, "defaultRegistryUrl")
|
|
104
104
|
? invocation.dependencies
|
|
105
105
|
: { ...(invocation.dependencies || {}), defaultRegistryUrl: DEFAULT_NEXUS_REGISTRY };
|
|
@@ -132,15 +132,11 @@ async function executeInitUnlocked(invocation, root) {
|
|
|
132
132
|
const extensionState = extensionStateInspection.state;
|
|
133
133
|
const systemCatalogResult = inspectExtensions ? discoverSystemExtensions({ tolerant: true }) : { catalog: [], invalid: [] };
|
|
134
134
|
const systemExtensionIds = systemCatalogResult.catalog.filter((entry) => entry.latestSupported).map((entry) => entry.id);
|
|
135
|
-
|
|
136
|
-
const systemRemovalPlans = dev || extensionStateInspection.error
|
|
137
|
-
? []
|
|
138
|
-
: Object.entries(extensionState.extensions)
|
|
139
|
-
.filter(([id, entry]) => entry.installed?.system === true || systemExtensionIds.includes(id))
|
|
140
|
-
.map(([id]) => planExtensionUninstall(root, id, { systemManaged: true }));
|
|
135
|
+
let systemRequestedExtensions = dev ? systemExtensionIds : [];
|
|
141
136
|
let systemPreparation = { plans: [], failures: [], diagnostics: [] };
|
|
142
137
|
let planningState = extensionState;
|
|
143
|
-
const prepareSystemForTools = (tools) => {
|
|
138
|
+
const prepareSystemForTools = (tools, selectedDev = dev) => {
|
|
139
|
+
systemRequestedExtensions = selectedDev ? systemExtensionIds : [];
|
|
144
140
|
systemPreparation = prepareExtensionPlans(systemCatalogResult, systemRequestedExtensions, {
|
|
145
141
|
tools,
|
|
146
142
|
state: extensionState,
|
|
@@ -183,6 +179,7 @@ async function executeInitUnlocked(invocation, root) {
|
|
|
183
179
|
initialTools: resolvedTools.tools,
|
|
184
180
|
workspaceName: options["workspace-name"],
|
|
185
181
|
language: options.language,
|
|
182
|
+
dev: requestedDev,
|
|
186
183
|
extensionState,
|
|
187
184
|
extensions: explicitExtensions === null ? undefined : explicitExtensions,
|
|
188
185
|
initialExtensions: explicitExtensions === null ? undefined : explicitExtensions,
|
|
@@ -198,6 +195,12 @@ async function executeInitUnlocked(invocation, root) {
|
|
|
198
195
|
throw error;
|
|
199
196
|
}
|
|
200
197
|
}
|
|
198
|
+
if (plan?.workspace?.dev !== undefined) dev = plan.workspace.dev;
|
|
199
|
+
const systemRemovalPlans = dev || extensionStateInspection.error
|
|
200
|
+
? []
|
|
201
|
+
: Object.entries(extensionState.extensions)
|
|
202
|
+
.filter(([id, entry]) => entry.installed?.system === true || systemExtensionIds.includes(id))
|
|
203
|
+
.map(([id]) => planExtensionUninstall(root, id, { systemManaged: true }));
|
|
201
204
|
const tools = plan?.tools || resolvedTools.tools;
|
|
202
205
|
const ordinaryRequestedExtensions = explicitExtensions !== null
|
|
203
206
|
? explicitExtensions
|
package/src/cli/registry.js
CHANGED
|
@@ -68,7 +68,7 @@ const COMMANDS = [
|
|
|
68
68
|
tools: { type: "string" }, yes: { type: "boolean" },
|
|
69
69
|
} },
|
|
70
70
|
{ path: ["extension", "install"], args: [{ name: "name", required: false, variadic: true }], workspace: "required", config: ["identity", "language"], interaction: "required", effects: "planned-write", options: {
|
|
71
|
-
yes: { type: "boolean" }, version: { type: "string" }, "allow-deprecated": { type: "boolean" }, offline: { type: "boolean" },
|
|
71
|
+
yes: { type: "boolean" }, local: { type: "boolean" }, version: { type: "string" }, "allow-deprecated": { type: "boolean" }, offline: { type: "boolean" },
|
|
72
72
|
} },
|
|
73
73
|
{ path: ["extension", "uninstall"], args: [{ name: "name", required: true }], workspace: "required", config: [], interaction: "required", effects: "planned-write", options: { yes: { type: "boolean" } } },
|
|
74
74
|
{ path: ["extension", "pack"], args: [{ name: "source", required: true }], workspace: "none", config: [], interaction: "never", effects: "planned-write", options: { output: { type: "string", required: true } } },
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const crypto = require("node:crypto");
|
|
2
|
+
const fs = require("node:fs");
|
|
3
|
+
const os = require("node:os");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
const { pipeline } = require("node:stream/promises");
|
|
6
|
+
|
|
7
|
+
const { WorkspaceError } = require("./errors");
|
|
8
|
+
const { DEFAULT_PACKAGE_LIMITS, extractExtensionTransportTarball } = require("./extension-package");
|
|
9
|
+
const { ensureStoredExtensionPackage, packageRecordFromDirectory } = require("./extension-store");
|
|
10
|
+
|
|
11
|
+
function localError(code, message, details = {}) {
|
|
12
|
+
return new WorkspaceError(code, message, details);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function sha512File(file) {
|
|
16
|
+
const hash = crypto.createHash("sha512");
|
|
17
|
+
await pipeline(fs.createReadStream(file), hash);
|
|
18
|
+
return `sha512-${hash.digest("base64")}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function prepareLocalExtensionTarball(file, options = {}) {
|
|
22
|
+
const archive = path.resolve(String(file || ""));
|
|
23
|
+
if (!archive.toLowerCase().endsWith(".tgz")) {
|
|
24
|
+
throw localError("EXTENSION_LOCAL_ARCHIVE_INVALID", "Local extension installation accepts only a .tgz tarball.", { path: archive });
|
|
25
|
+
}
|
|
26
|
+
let stat;
|
|
27
|
+
try { stat = fs.lstatSync(archive); } catch (error) {
|
|
28
|
+
throw localError("EXTENSION_LOCAL_ARCHIVE_MISSING", `Local extension tarball is missing: ${archive}`, { path: archive, cause: error.code });
|
|
29
|
+
}
|
|
30
|
+
if (!stat.isFile() || stat.isSymbolicLink()) throw localError("EXTENSION_LOCAL_ARCHIVE_INVALID", `Local extension tarball must be a regular file: ${archive}`, { path: archive });
|
|
31
|
+
const temporaryRoot = fs.mkdtempSync(path.join(os.tmpdir(), "code-workspace-local-extension-"));
|
|
32
|
+
try {
|
|
33
|
+
const limits = options.packageLimits || DEFAULT_PACKAGE_LIMITS;
|
|
34
|
+
const inspected = await extractExtensionTransportTarball(archive, path.join(temporaryRoot, "unpacked"), { limits });
|
|
35
|
+
const integrity = await sha512File(archive);
|
|
36
|
+
const packageRecord = packageRecordFromDirectory(inspected.sourceRoot, {
|
|
37
|
+
id: inspected.manifest.id,
|
|
38
|
+
version: inspected.manifest.version,
|
|
39
|
+
validate: options.validate,
|
|
40
|
+
});
|
|
41
|
+
return Object.freeze({ sourceRoot: inspected.sourceRoot, packageRecord, archive: Object.freeze({ path: archive, integrity }), cleanup: () => fs.rmSync(temporaryRoot, { recursive: true, force: true }) });
|
|
42
|
+
} catch (error) {
|
|
43
|
+
fs.rmSync(temporaryRoot, { recursive: true, force: true });
|
|
44
|
+
if (error instanceof WorkspaceError) throw error;
|
|
45
|
+
throw localError("EXTENSION_LOCAL_ARCHIVE_INVALID", `Cannot import local extension tarball: ${error.message}`, { path: archive, cause: error.code });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function importLocalExtensionTarball(file, options = {}) {
|
|
50
|
+
const prepared = await prepareLocalExtensionTarball(file, options);
|
|
51
|
+
try {
|
|
52
|
+
const stored = ensureStoredExtensionPackage({
|
|
53
|
+
sourceRoot: prepared.sourceRoot,
|
|
54
|
+
storeRoot: options.extensionStoreRoot,
|
|
55
|
+
id: prepared.packageRecord.id,
|
|
56
|
+
version: prepared.packageRecord.version,
|
|
57
|
+
provenance: { kind: "local", archiveIntegrity: prepared.archive.integrity },
|
|
58
|
+
validate: options.validate,
|
|
59
|
+
});
|
|
60
|
+
return Object.freeze({ ...stored, source: stored.source || "local", archive: prepared.archive, extensionSpecVersion: prepared.packageRecord.manifest.extensionSpecVersion });
|
|
61
|
+
} finally {
|
|
62
|
+
prepared.cleanup();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = { importLocalExtensionTarball, prepareLocalExtensionTarball };
|
|
@@ -717,6 +717,14 @@ function planFromPackageRecord(id, packageRecord, options = {}) {
|
|
|
717
717
|
});
|
|
718
718
|
}
|
|
719
719
|
|
|
720
|
+
function planLocalExtensionPackage(packageRecord, options = {}) {
|
|
721
|
+
return planFromPackageRecord(packageRecord.id, packageRecord, {
|
|
722
|
+
...options,
|
|
723
|
+
source: "local",
|
|
724
|
+
resolutionScope: "local",
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
|
|
720
728
|
async function prepareRegistryExtensionPlans(options = {}) {
|
|
721
729
|
const requested = normalizeExtensionNames(options.requested || []);
|
|
722
730
|
if (options.version && requested.length !== 1) {
|
|
@@ -791,6 +799,7 @@ module.exports = {
|
|
|
791
799
|
getRegistryExtensionInfo,
|
|
792
800
|
listRegistryExtensionChoices,
|
|
793
801
|
prepareRegistryExtensionPlans,
|
|
802
|
+
planLocalExtensionPackage,
|
|
794
803
|
resolveRegistryExtensionCandidate,
|
|
795
804
|
searchRegistryExtensions,
|
|
796
805
|
};
|
|
@@ -113,6 +113,14 @@ function normalizeProvenance(value, key = "package") {
|
|
|
113
113
|
archiveIntegrity,
|
|
114
114
|
});
|
|
115
115
|
}
|
|
116
|
+
if (value.kind === "local") {
|
|
117
|
+
const allowed = new Set(["kind", "archiveIntegrity"]);
|
|
118
|
+
const unknown = Object.keys(value).filter((field) => !allowed.has(field));
|
|
119
|
+
if (unknown.length > 0) throw storeError("EXTENSION_STORE_PROVENANCE_INVALID", `Local Store provenance contains unsupported field ${unknown[0]} for ${key}`, { package: key, field: unknown[0] });
|
|
120
|
+
const archiveIntegrity = String(value.archiveIntegrity || "");
|
|
121
|
+
if (!SHA512_SRI_PATTERN.test(archiveIntegrity)) throw storeError("EXTENSION_STORE_PROVENANCE_INVALID", `Invalid local archive integrity for ${key}`, { package: key });
|
|
122
|
+
return Object.freeze({ kind: "local", archiveIntegrity });
|
|
123
|
+
}
|
|
116
124
|
throw storeError("EXTENSION_STORE_PROVENANCE_INVALID", `Unsupported Store provenance kind for ${key}: ${value.kind || "<missing>"}`, { package: key, kind: value.kind || null });
|
|
117
125
|
}
|
|
118
126
|
|
|
@@ -142,7 +150,7 @@ function validatePackageRecord(key, value) {
|
|
|
142
150
|
manifestSha256: value.manifestSha256,
|
|
143
151
|
entrySha256: value.entrySha256,
|
|
144
152
|
packageSha256: value.packageSha256,
|
|
145
|
-
source: provenance.kind === "builtin" ? "builtin" : "nexus",
|
|
153
|
+
source: provenance.kind === "builtin" ? "builtin" : provenance.kind === "local" ? "local" : "nexus",
|
|
146
154
|
provenance,
|
|
147
155
|
importedAt: value.importedAt || null,
|
|
148
156
|
references: normalizeReferences(value.references),
|
|
@@ -276,7 +284,7 @@ function ensureStoredExtensionPackage(options = {}) {
|
|
|
276
284
|
manifestSha256: copied.manifestSha256,
|
|
277
285
|
entrySha256: copied.entrySha256,
|
|
278
286
|
packageSha256: copied.packageSha256,
|
|
279
|
-
source: provenance.kind === "builtin" ? "builtin" : "nexus",
|
|
287
|
+
source: provenance.kind === "builtin" ? "builtin" : provenance.kind === "local" ? "local" : "nexus",
|
|
280
288
|
provenance,
|
|
281
289
|
importedAt: new Date().toISOString(),
|
|
282
290
|
references: registry.packages[packageKey(source.id, source.version)]?.references || normalizeReferences(),
|
package/src/init/wizard.js
CHANGED
|
@@ -90,6 +90,17 @@ async function collectInitPlan(root, manifest, options = {}) {
|
|
|
90
90
|
});
|
|
91
91
|
const existing = fs.existsSync(configPath(root)) ? loadConfig(root, { defaultLanguage: existingLanguage }) : null;
|
|
92
92
|
const name = existing?.workspace?.name || options.workspaceName || await ui.text("Workspace name", DEFAULT_WORKSPACE_NAME);
|
|
93
|
+
const initialDev = options.dev ?? existing?.workspace?.dev ?? true;
|
|
94
|
+
const dev = options.dev !== undefined
|
|
95
|
+
? options.dev
|
|
96
|
+
: await ui.select(
|
|
97
|
+
"Development mode",
|
|
98
|
+
[
|
|
99
|
+
{ value: true, label: "Yes" },
|
|
100
|
+
{ value: false, label: "No" },
|
|
101
|
+
],
|
|
102
|
+
initialDev ? 0 : 1
|
|
103
|
+
);
|
|
93
104
|
const languageChoices = SUPPORTED_LANGUAGES.map((entry) => ({
|
|
94
105
|
value: entry.value,
|
|
95
106
|
label: `${entry.label} · ${entry.value}`,
|
|
@@ -109,14 +120,17 @@ async function collectInitPlan(root, manifest, options = {}) {
|
|
|
109
120
|
options.initialTools || ["claude", "codex"]
|
|
110
121
|
);
|
|
111
122
|
const systemExtensions = typeof options.prepareSystemExtensions === "function"
|
|
112
|
-
? await options.prepareSystemExtensions(tools)
|
|
123
|
+
? await options.prepareSystemExtensions(tools, dev)
|
|
113
124
|
: (options.systemExtensions || []);
|
|
114
125
|
const ordinary = await collectExtensionPlans(options, ui, tools);
|
|
115
126
|
const extensions = [
|
|
116
127
|
...systemExtensions,
|
|
117
128
|
...ordinary.plans,
|
|
118
129
|
];
|
|
119
|
-
const workspace =
|
|
130
|
+
const workspace = {
|
|
131
|
+
...(existing?.workspace || { name, uuid: randomUUID() }),
|
|
132
|
+
dev,
|
|
133
|
+
};
|
|
120
134
|
const extensionRemovals = ordinary.uninstallPlans || [];
|
|
121
135
|
const plan = createInitPlan({ root, workspace, tools, language, extensions, extensionRemovals });
|
|
122
136
|
const extensionChanges = [
|
|
@@ -125,6 +139,7 @@ async function collectInitPlan(root, manifest, options = {}) {
|
|
|
125
139
|
];
|
|
126
140
|
ui.note("Ready to initialize", [
|
|
127
141
|
`Workspace ${workspace.name}`,
|
|
142
|
+
`Development mode ${dev ? "Yes" : "No"}`,
|
|
128
143
|
`Language ${language}`,
|
|
129
144
|
`Tools ${tools.length ? tools.join(", ") : "none"}`,
|
|
130
145
|
`Extensions ${extensions.length ? extensions.map((entry) => `${entry.id}@${entry.version} [Spec ${entry.extensionSpecVersion}] (${entry.manifestSha256})`).join(", ") : "none"}`,
|