@jskit-ai/jskit-cli 0.2.41 → 0.2.43
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/package.json +4 -3
- package/src/server/cliRuntime/completion.js +1177 -0
- package/src/server/cliRuntime/descriptorValidation.js +18 -3
- package/src/server/cliRuntime/ioAndMigrations.js +2 -2
- package/src/server/cliRuntime/mutationApplication.js +1 -1
- package/src/server/cliRuntime/mutationWhen.js +2 -0
- package/src/server/cliRuntime/mutations/fileMutations.js +188 -143
- package/src/server/cliRuntime/mutations/installMigrationMutation.js +11 -38
- package/src/server/cliRuntime/mutations/templateContext.js +8 -14
- package/src/server/cliRuntime/mutations/textMutations.js +11 -6
- package/src/server/cliRuntime/packageInstallFlow.js +36 -21
- package/src/server/cliRuntime/packageIntrospection/placementNormalization.js +13 -22
- package/src/server/cliRuntime/packageOptions.js +149 -3
- package/src/server/cliRuntime/packageRegistries.js +3 -2
- package/src/server/commandHandlers/completion.js +129 -0
- package/src/server/commandHandlers/list.js +4 -6
- package/src/server/commandHandlers/packageCommands/add.js +31 -11
- package/src/server/commandHandlers/packageCommands/discoverabilityHelp.js +10 -2
- package/src/server/commandHandlers/packageCommands/generate.js +29 -31
- package/src/server/commandHandlers/packageCommands/tabLinkItemProvisioning.js +123 -164
- package/src/server/commandHandlers/shared.js +23 -3
- package/src/server/commandHandlers/show/renderPackageText.js +3 -3
- package/src/server/core/argParser.js +12 -2
- package/src/server/core/commandCatalog.js +36 -13
- package/src/server/core/createCommandHandlers.js +3 -0
- package/src/server/shared/optionInterpolation.js +93 -0
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
import {
|
|
33
33
|
applyFileMutations,
|
|
34
34
|
applyTextMutations,
|
|
35
|
-
|
|
35
|
+
prepareFileMutations,
|
|
36
36
|
resolvePositioningMutations
|
|
37
37
|
} from "./mutationApplication.js";
|
|
38
38
|
function createManagedRecordBase(packageEntry, options) {
|
|
@@ -138,15 +138,23 @@ async function applyPackagePositioning({
|
|
|
138
138
|
const positioningMutations = resolvePositioningMutations(mutations);
|
|
139
139
|
const appliedManagedFiles = [];
|
|
140
140
|
const appliedManagedText = {};
|
|
141
|
+
const preparedFileMutations = await prepareFileMutations(
|
|
142
|
+
packageEntryForMutations,
|
|
143
|
+
packageOptions,
|
|
144
|
+
appRoot,
|
|
145
|
+
positioningMutations.files,
|
|
146
|
+
nextManaged.files
|
|
147
|
+
);
|
|
141
148
|
if (positioningMutations.files.length > 0) {
|
|
142
149
|
await applyFileMutations(
|
|
143
150
|
packageEntryForMutations,
|
|
144
|
-
packageOptions,
|
|
145
151
|
appRoot,
|
|
146
|
-
|
|
152
|
+
preparedFileMutations,
|
|
147
153
|
appliedManagedFiles,
|
|
148
154
|
[],
|
|
149
|
-
touchedFiles
|
|
155
|
+
touchedFiles,
|
|
156
|
+
[],
|
|
157
|
+
nextManaged.files
|
|
150
158
|
);
|
|
151
159
|
}
|
|
152
160
|
if (positioningMutations.text.length > 0) {
|
|
@@ -236,17 +244,24 @@ async function applyPackageMigrationsOnly({
|
|
|
236
244
|
return operation === "install-migration";
|
|
237
245
|
});
|
|
238
246
|
const mutationWarnings = [];
|
|
247
|
+
const preparedFileMutations = await prepareFileMutations(
|
|
248
|
+
packageEntryForMutations,
|
|
249
|
+
packageOptions,
|
|
250
|
+
appRoot,
|
|
251
|
+
migrationFileMutations,
|
|
252
|
+
nextManaged.files
|
|
253
|
+
);
|
|
239
254
|
|
|
240
255
|
if (migrationFileMutations.length > 0) {
|
|
241
256
|
await applyFileMutations(
|
|
242
257
|
packageEntryForMutations,
|
|
243
|
-
packageOptions,
|
|
244
258
|
appRoot,
|
|
245
|
-
|
|
259
|
+
preparedFileMutations,
|
|
246
260
|
[],
|
|
247
261
|
nextManaged.migrations,
|
|
248
262
|
touchedFiles,
|
|
249
|
-
mutationWarnings
|
|
263
|
+
mutationWarnings,
|
|
264
|
+
nextManaged.files
|
|
250
265
|
);
|
|
251
266
|
}
|
|
252
267
|
|
|
@@ -280,15 +295,6 @@ async function applyPackageInstall({
|
|
|
280
295
|
}) {
|
|
281
296
|
const existingInstall = ensureObject(lock.installedPackages[packageEntry.packageId]);
|
|
282
297
|
const existingManaged = ensureObject(existingInstall.managed);
|
|
283
|
-
await removeManagedViteProxyEntries({
|
|
284
|
-
appRoot,
|
|
285
|
-
packageId: packageEntry.packageId,
|
|
286
|
-
managedViteChanges: ensureObject(existingManaged.vite),
|
|
287
|
-
touchedFiles
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
const managedRecord = createManagedRecordBase(packageEntry, packageOptions);
|
|
291
|
-
managedRecord.managed.migrations = cloneManagedArray(existingManaged.migrations);
|
|
292
298
|
const generatorPackage = isGeneratorPackageEntry(packageEntry);
|
|
293
299
|
const mutationWarnings = [];
|
|
294
300
|
const mutations = ensureObject(packageEntry.descriptor.mutations);
|
|
@@ -306,12 +312,22 @@ async function applyPackageInstall({
|
|
|
306
312
|
rootDir: templateRoot
|
|
307
313
|
};
|
|
308
314
|
|
|
309
|
-
const
|
|
315
|
+
const preparedFileMutations = await prepareFileMutations(
|
|
310
316
|
packageEntryForMutations,
|
|
311
317
|
packageOptions,
|
|
312
318
|
appRoot,
|
|
313
|
-
fileMutations
|
|
319
|
+
fileMutations,
|
|
320
|
+
ensureArray(existingManaged.files)
|
|
314
321
|
);
|
|
322
|
+
await removeManagedViteProxyEntries({
|
|
323
|
+
appRoot,
|
|
324
|
+
packageId: packageEntry.packageId,
|
|
325
|
+
managedViteChanges: ensureObject(existingManaged.vite),
|
|
326
|
+
touchedFiles
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
const managedRecord = createManagedRecordBase(packageEntry, packageOptions);
|
|
330
|
+
managedRecord.managed.migrations = cloneManagedArray(existingManaged.migrations);
|
|
315
331
|
|
|
316
332
|
const mutationDependencies = ensureObject(mutations.dependencies);
|
|
317
333
|
const runtimeDependencies = ensureObject(mutationDependencies.runtime);
|
|
@@ -416,14 +432,13 @@ async function applyPackageInstall({
|
|
|
416
432
|
|
|
417
433
|
await applyFileMutations(
|
|
418
434
|
packageEntryForMutations,
|
|
419
|
-
packageOptions,
|
|
420
435
|
appRoot,
|
|
421
|
-
|
|
436
|
+
preparedFileMutations,
|
|
422
437
|
managedRecord.managed.files,
|
|
423
438
|
managedRecord.managed.migrations,
|
|
424
439
|
touchedFiles,
|
|
425
440
|
mutationWarnings,
|
|
426
|
-
|
|
441
|
+
ensureArray(existingManaged.files)
|
|
427
442
|
);
|
|
428
443
|
|
|
429
444
|
await applyTextMutations(
|
|
@@ -2,15 +2,17 @@ import {
|
|
|
2
2
|
ensureArray,
|
|
3
3
|
ensureObject
|
|
4
4
|
} from "../../shared/collectionUtils.js";
|
|
5
|
+
import {
|
|
6
|
+
normalizeShellOutletTargetId
|
|
7
|
+
} from "@jskit-ai/kernel/shared/support/shellLayoutTargets";
|
|
5
8
|
|
|
6
9
|
function normalizePlacementOutlets(value) {
|
|
7
10
|
const outlets = [];
|
|
8
11
|
const source = ensureArray(value);
|
|
9
12
|
for (const entry of source) {
|
|
10
13
|
const record = ensureObject(entry);
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
if (!host || !position) {
|
|
14
|
+
const target = normalizeShellOutletTargetId(record.target);
|
|
15
|
+
if (!target) {
|
|
14
16
|
continue;
|
|
15
17
|
}
|
|
16
18
|
|
|
@@ -19,8 +21,7 @@ function normalizePlacementOutlets(value) {
|
|
|
19
21
|
const sourceLabel = String(record.source || "").trim();
|
|
20
22
|
outlets.push(
|
|
21
23
|
Object.freeze({
|
|
22
|
-
|
|
23
|
-
position,
|
|
24
|
+
target,
|
|
24
25
|
surfaces: Object.freeze(surfaces),
|
|
25
26
|
description,
|
|
26
27
|
source: sourceLabel
|
|
@@ -30,11 +31,7 @@ function normalizePlacementOutlets(value) {
|
|
|
30
31
|
|
|
31
32
|
return Object.freeze(
|
|
32
33
|
[...outlets].sort((left, right) => {
|
|
33
|
-
|
|
34
|
-
if (hostCompare !== 0) {
|
|
35
|
-
return hostCompare;
|
|
36
|
-
}
|
|
37
|
-
return left.position.localeCompare(right.position);
|
|
34
|
+
return left.target.localeCompare(right.target);
|
|
38
35
|
})
|
|
39
36
|
);
|
|
40
37
|
}
|
|
@@ -44,9 +41,8 @@ function normalizePlacementContributions(value) {
|
|
|
44
41
|
for (const entry of ensureArray(value)) {
|
|
45
42
|
const record = ensureObject(entry);
|
|
46
43
|
const id = String(record.id || "").trim();
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
if (!id || !host || !position) {
|
|
44
|
+
const target = normalizeShellOutletTargetId(record.target);
|
|
45
|
+
if (!id || !target) {
|
|
50
46
|
continue;
|
|
51
47
|
}
|
|
52
48
|
|
|
@@ -60,8 +56,7 @@ function normalizePlacementContributions(value) {
|
|
|
60
56
|
contributions.push(
|
|
61
57
|
Object.freeze({
|
|
62
58
|
id,
|
|
63
|
-
|
|
64
|
-
position,
|
|
59
|
+
target,
|
|
65
60
|
surfaces: Object.freeze(surfaces),
|
|
66
61
|
order,
|
|
67
62
|
componentToken,
|
|
@@ -74,13 +69,9 @@ function normalizePlacementContributions(value) {
|
|
|
74
69
|
|
|
75
70
|
return Object.freeze(
|
|
76
71
|
[...contributions].sort((left, right) => {
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
79
|
-
return
|
|
80
|
-
}
|
|
81
|
-
const positionCompare = left.position.localeCompare(right.position);
|
|
82
|
-
if (positionCompare !== 0) {
|
|
83
|
-
return positionCompare;
|
|
72
|
+
const targetCompare = left.target.localeCompare(right.target);
|
|
73
|
+
if (targetCompare !== 0) {
|
|
74
|
+
return targetCompare;
|
|
84
75
|
}
|
|
85
76
|
const leftOrder = Number.isFinite(left.order) ? left.order : Number.POSITIVE_INFINITY;
|
|
86
77
|
const rightOrder = Number.isFinite(right.order) ? right.order : Number.POSITIVE_INFINITY;
|
|
@@ -17,6 +17,8 @@ import { loadMutationWhenConfigContext } from "./ioAndMigrations.js";
|
|
|
17
17
|
const WORKSPACE_VISIBILITY_LEVELS = Object.freeze(["workspace", "workspace_user"]);
|
|
18
18
|
const WORKSPACE_VISIBILITY_SET = new Set(WORKSPACE_VISIBILITY_LEVELS);
|
|
19
19
|
const OPTION_VALIDATION_ENABLED_SURFACE_ID = "enabled-surface-id";
|
|
20
|
+
const OPTION_VALIDATION_ENUM = "enum";
|
|
21
|
+
const OPTION_VALIDATION_CSV_ENUM = "csv-enum";
|
|
20
22
|
const OPTION_NORMALIZATION_PAGES_RELATIVE_TARGET_ROOT = "pages-relative-target-root";
|
|
21
23
|
|
|
22
24
|
function normalizeSurfaceIdForMutation(value = "") {
|
|
@@ -91,6 +93,7 @@ function resolveSurfaceDefinitionsForOptionPolicy(configContext = {}) {
|
|
|
91
93
|
|
|
92
94
|
normalizedDefinitions[definitionId] = Object.freeze({
|
|
93
95
|
id: definitionId,
|
|
96
|
+
label: String(definition.label || "").trim(),
|
|
94
97
|
enabled: definition.enabled !== false,
|
|
95
98
|
requiresWorkspace: definition.requiresWorkspace === true
|
|
96
99
|
});
|
|
@@ -152,6 +155,114 @@ function resolveSchemaValidatedOptionNames(packageEntry = {}, validationType = "
|
|
|
152
155
|
];
|
|
153
156
|
}
|
|
154
157
|
|
|
158
|
+
function resolveAllowedValuesForSchema(schema = {}) {
|
|
159
|
+
const values = [];
|
|
160
|
+
const seen = new Set();
|
|
161
|
+
for (const rawValue of Array.isArray(schema?.allowedValues) ? schema.allowedValues : []) {
|
|
162
|
+
const value = String(
|
|
163
|
+
typeof rawValue === "string"
|
|
164
|
+
? rawValue
|
|
165
|
+
: ensureObject(rawValue).value
|
|
166
|
+
).trim();
|
|
167
|
+
if (!value || seen.has(value.toLowerCase())) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
seen.add(value.toLowerCase());
|
|
171
|
+
values.push(value);
|
|
172
|
+
}
|
|
173
|
+
return Object.freeze(values);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function parseCsvEnumOptionValues(value = "") {
|
|
177
|
+
return String(value || "")
|
|
178
|
+
.split(",")
|
|
179
|
+
.map((entry) => String(entry || "").trim())
|
|
180
|
+
.filter(Boolean);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function validateEnumOptionValues({
|
|
184
|
+
packageEntry,
|
|
185
|
+
resolvedOptions = {},
|
|
186
|
+
optionNames = null
|
|
187
|
+
} = {}) {
|
|
188
|
+
const validatedOptionNames = resolveSchemaValidatedOptionNames(
|
|
189
|
+
packageEntry,
|
|
190
|
+
OPTION_VALIDATION_ENUM,
|
|
191
|
+
{ optionNames }
|
|
192
|
+
);
|
|
193
|
+
if (validatedOptionNames.length < 1) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const packageId = String(packageEntry?.packageId || "").trim() || "unknown-package";
|
|
198
|
+
const optionSchemas = ensureObject(packageEntry?.descriptor?.options);
|
|
199
|
+
for (const optionName of validatedOptionNames) {
|
|
200
|
+
const schema = ensureObject(optionSchemas[optionName]);
|
|
201
|
+
const value = String(resolvedOptions?.[optionName] || "").trim();
|
|
202
|
+
if (!value) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const allowedValues = resolveAllowedValuesForSchema(schema);
|
|
207
|
+
if (allowedValues.length < 1) {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
const allowedValueSet = new Set(allowedValues.map((entry) => entry.toLowerCase()));
|
|
211
|
+
if (!allowedValueSet.has(value.toLowerCase())) {
|
|
212
|
+
throw createCliError(
|
|
213
|
+
`Invalid option for package ${packageId}: --${optionName} must be one of: ${allowedValues.join(", ")}.`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function validateCsvEnumOptionValues({
|
|
220
|
+
packageEntry,
|
|
221
|
+
resolvedOptions = {},
|
|
222
|
+
optionNames = null
|
|
223
|
+
} = {}) {
|
|
224
|
+
const validatedOptionNames = resolveSchemaValidatedOptionNames(
|
|
225
|
+
packageEntry,
|
|
226
|
+
OPTION_VALIDATION_CSV_ENUM,
|
|
227
|
+
{ optionNames }
|
|
228
|
+
);
|
|
229
|
+
if (validatedOptionNames.length < 1) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const packageId = String(packageEntry?.packageId || "").trim() || "unknown-package";
|
|
234
|
+
const optionSchemas = ensureObject(packageEntry?.descriptor?.options);
|
|
235
|
+
for (const optionName of validatedOptionNames) {
|
|
236
|
+
const schema = ensureObject(optionSchemas[optionName]);
|
|
237
|
+
const value = String(resolvedOptions?.[optionName] || "").trim();
|
|
238
|
+
if (!value) {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const allowedValues = resolveAllowedValuesForSchema(schema);
|
|
243
|
+
if (allowedValues.length < 1) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const allowedValueSet = new Set(allowedValues.map((entry) => entry.toLowerCase()));
|
|
247
|
+
const providedValues = parseCsvEnumOptionValues(value);
|
|
248
|
+
if (providedValues.length < 1) {
|
|
249
|
+
throw createCliError(
|
|
250
|
+
`Invalid option for package ${packageId}: --${optionName} must include at least one value from: ${allowedValues.join(", ")}.`
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
const invalidValues = [
|
|
254
|
+
...new Set(
|
|
255
|
+
providedValues.filter((entry) => !allowedValueSet.has(entry.toLowerCase()))
|
|
256
|
+
)
|
|
257
|
+
];
|
|
258
|
+
if (invalidValues.length > 0) {
|
|
259
|
+
throw createCliError(
|
|
260
|
+
`Invalid option for package ${packageId}: --${optionName} includes unsupported value(s): ${invalidValues.join(", ")}. Allowed values: ${allowedValues.join(", ")}.`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
155
266
|
function validateEnabledSurfaceOptionValues({
|
|
156
267
|
packageEntry,
|
|
157
268
|
resolvedOptions = {},
|
|
@@ -251,15 +362,45 @@ async function validateResolvedOptionPolicies({
|
|
|
251
362
|
});
|
|
252
363
|
}
|
|
253
364
|
|
|
365
|
+
function resolvePromptChoicesForOption({ schema = {}, configContext = {} } = {}) {
|
|
366
|
+
const validationType = normalizeResolvedOptionValue(schema.validationType);
|
|
367
|
+
if (validationType === OPTION_VALIDATION_ENUM) {
|
|
368
|
+
return resolveAllowedValuesForSchema(schema).map((value) => Object.freeze({
|
|
369
|
+
value,
|
|
370
|
+
label: value
|
|
371
|
+
}));
|
|
372
|
+
}
|
|
373
|
+
if (validationType !== OPTION_VALIDATION_ENABLED_SURFACE_ID) {
|
|
374
|
+
return [];
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const surfaceDefinitions = resolveSurfaceDefinitionsForOptionPolicy(configContext);
|
|
378
|
+
return Object.values(surfaceDefinitions)
|
|
379
|
+
.filter((entry) => entry.enabled === true)
|
|
380
|
+
.map((entry) => Object.freeze({
|
|
381
|
+
value: entry.id,
|
|
382
|
+
label: entry.label && entry.label.toLowerCase() !== entry.id.toLowerCase()
|
|
383
|
+
? `${entry.id} (${entry.label})`
|
|
384
|
+
: entry.id
|
|
385
|
+
}));
|
|
386
|
+
}
|
|
387
|
+
|
|
254
388
|
async function validateOptionValuesForPackage({
|
|
255
389
|
packageEntry,
|
|
256
390
|
resolvedOptions = {},
|
|
257
391
|
appRoot = "",
|
|
258
392
|
optionNames = null
|
|
259
393
|
} = {}) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
394
|
+
validateEnumOptionValues({
|
|
395
|
+
packageEntry,
|
|
396
|
+
resolvedOptions,
|
|
397
|
+
optionNames
|
|
398
|
+
});
|
|
399
|
+
validateCsvEnumOptionValues({
|
|
400
|
+
packageEntry,
|
|
401
|
+
resolvedOptions,
|
|
402
|
+
optionNames
|
|
403
|
+
});
|
|
263
404
|
|
|
264
405
|
const validatedOptionNames = resolveSchemaValidatedOptionNames(
|
|
265
406
|
packageEntry,
|
|
@@ -269,6 +410,9 @@ async function validateOptionValuesForPackage({
|
|
|
269
410
|
if (validatedOptionNames.length < 1) {
|
|
270
411
|
return;
|
|
271
412
|
}
|
|
413
|
+
if (!appRoot) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
272
416
|
|
|
273
417
|
const configContext = await loadMutationWhenConfigContext(appRoot);
|
|
274
418
|
validateEnabledSurfaceOptionValues({
|
|
@@ -379,11 +523,13 @@ async function resolvePackageOptions(packageEntry, inlineOptions, io, { appRoot
|
|
|
379
523
|
}
|
|
380
524
|
|
|
381
525
|
if (schema.required) {
|
|
526
|
+
const promptConfigContext = appRoot ? await loadConfigContext() : {};
|
|
382
527
|
assignResolvedOption(await promptForRequiredOption({
|
|
383
528
|
ownerType: "package",
|
|
384
529
|
ownerId: packageEntry.packageId,
|
|
385
530
|
optionName,
|
|
386
531
|
optionSchema: schema,
|
|
532
|
+
promptChoices: resolvePromptChoicesForOption({ schema, configContext: promptConfigContext }),
|
|
387
533
|
stdin: io.stdin,
|
|
388
534
|
stdout: io.stdout
|
|
389
535
|
}));
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readdir } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { importFreshModuleFromAbsolutePath } from "@jskit-ai/kernel/server/support";
|
|
4
5
|
import { createCliError } from "../shared/cliError.js";
|
|
5
6
|
import {
|
|
6
7
|
ensureArray,
|
|
@@ -92,7 +93,7 @@ async function loadAppLocalPackageRegistry(appRoot) {
|
|
|
92
93
|
throw createCliError(`Invalid app-local package at ${normalizeRelativePath(appRoot, packageRoot)}: package.json missing name.`);
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
const descriptorModule = await
|
|
96
|
+
const descriptorModule = await importFreshModuleFromAbsolutePath(descriptorPath);
|
|
96
97
|
const descriptor = validateAppLocalPackageDescriptorShape(descriptorModule?.default, descriptorPath, {
|
|
97
98
|
expectedPackageId: packageId,
|
|
98
99
|
fallbackVersion: String(packageJson?.version || "").trim()
|
|
@@ -207,7 +208,7 @@ async function loadInstalledNodeModulePackageEntry({ appRoot, packageId }) {
|
|
|
207
208
|
return null;
|
|
208
209
|
}
|
|
209
210
|
|
|
210
|
-
const descriptorModule = await
|
|
211
|
+
const descriptorModule = await importFreshModuleFromAbsolutePath(descriptorPath);
|
|
211
212
|
const descriptor = validateAppLocalPackageDescriptorShape(descriptorModule?.default, descriptorPath, {
|
|
212
213
|
expectedPackageId: resolvedPackageId,
|
|
213
214
|
fallbackVersion: String(packageJson?.version || "").trim()
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import {
|
|
5
|
+
getCompletions,
|
|
6
|
+
renderBashCompletionScript
|
|
7
|
+
} from "../cliRuntime/completion.js";
|
|
8
|
+
|
|
9
|
+
const INSTALL_MARKER_BEGIN = "# >>> jskit completion >>>";
|
|
10
|
+
const INSTALL_MARKER_END = "# <<< jskit completion <<<";
|
|
11
|
+
|
|
12
|
+
function resolveInstallBlock(relativeCompletionPath = "") {
|
|
13
|
+
return [
|
|
14
|
+
INSTALL_MARKER_BEGIN,
|
|
15
|
+
`if [ -f "$HOME/${relativeCompletionPath}" ]; then`,
|
|
16
|
+
` source "$HOME/${relativeCompletionPath}"`,
|
|
17
|
+
"fi",
|
|
18
|
+
INSTALL_MARKER_END
|
|
19
|
+
].join("\n");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function readTextFileIfExists(filePath = "") {
|
|
23
|
+
try {
|
|
24
|
+
return await readFile(filePath, "utf8");
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (String(error?.code || "").trim().toUpperCase() === "ENOENT") {
|
|
27
|
+
return "";
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function installBashCompletion() {
|
|
34
|
+
const homeDirectory = os.homedir();
|
|
35
|
+
const completionRelativePath = ".jskit/completion/bash/jskit.bash";
|
|
36
|
+
const completionAbsolutePath = path.join(homeDirectory, completionRelativePath);
|
|
37
|
+
const bashrcAbsolutePath = path.join(homeDirectory, ".bashrc");
|
|
38
|
+
const installBlock = resolveInstallBlock(completionRelativePath);
|
|
39
|
+
|
|
40
|
+
await mkdir(path.dirname(completionAbsolutePath), { recursive: true });
|
|
41
|
+
await writeFile(completionAbsolutePath, renderBashCompletionScript(), "utf8");
|
|
42
|
+
|
|
43
|
+
const existingBashrc = await readTextFileIfExists(bashrcAbsolutePath);
|
|
44
|
+
const markerPattern = new RegExp(
|
|
45
|
+
`${INSTALL_MARKER_BEGIN.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${INSTALL_MARKER_END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`,
|
|
46
|
+
"u"
|
|
47
|
+
);
|
|
48
|
+
const normalizedExisting = String(existingBashrc || "");
|
|
49
|
+
const nextBashrc = markerPattern.test(normalizedExisting)
|
|
50
|
+
? normalizedExisting.replace(markerPattern, installBlock)
|
|
51
|
+
: `${normalizedExisting.replace(/\s*$/u, "")}${normalizedExisting.trim() ? "\n\n" : ""}${installBlock}\n`;
|
|
52
|
+
|
|
53
|
+
if (nextBashrc !== normalizedExisting) {
|
|
54
|
+
await writeFile(bashrcAbsolutePath, nextBashrc, "utf8");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
completionAbsolutePath,
|
|
59
|
+
bashrcAbsolutePath
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function createCompletionCommands(ctx = {}) {
|
|
64
|
+
const {
|
|
65
|
+
createCliError,
|
|
66
|
+
resolveAppRootFromCwd
|
|
67
|
+
} = ctx;
|
|
68
|
+
|
|
69
|
+
async function commandCompletion({ positional = [], options = {}, cwd = "", stdout }) {
|
|
70
|
+
const shell = String(positional[0] || "").trim().toLowerCase();
|
|
71
|
+
if (shell !== "bash") {
|
|
72
|
+
throw createCliError(`Unsupported completion shell: ${shell || "<empty>"}.`, { showUsage: true });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const installRequested = String(options?.inlineOptions?.install || "").trim().toLowerCase() === "true";
|
|
76
|
+
const mode = String(positional[1] || "").trim();
|
|
77
|
+
if (installRequested && mode) {
|
|
78
|
+
throw createCliError("jskit completion bash --install does not accept internal completion mode arguments.", {
|
|
79
|
+
showUsage: true
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (installRequested) {
|
|
84
|
+
const {
|
|
85
|
+
completionAbsolutePath,
|
|
86
|
+
bashrcAbsolutePath
|
|
87
|
+
} = await installBashCompletion();
|
|
88
|
+
stdout.write(`Installed Bash completion to ${completionAbsolutePath}.\n`);
|
|
89
|
+
stdout.write(`Updated ${bashrcAbsolutePath}.\n`);
|
|
90
|
+
stdout.write("Run: source ~/.bashrc\n");
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!mode) {
|
|
95
|
+
stdout.write(renderBashCompletionScript());
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (mode !== "__complete__") {
|
|
100
|
+
throw createCliError(`Unknown completion mode: ${mode}.`, { showUsage: true });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const rawCword = Number.parseInt(String(positional[2] || "0"), 10);
|
|
104
|
+
const words = positional.slice(3).map((value) => String(value ?? ""));
|
|
105
|
+
|
|
106
|
+
let appRoot = String(cwd || process.cwd());
|
|
107
|
+
try {
|
|
108
|
+
appRoot = await resolveAppRootFromCwd(appRoot);
|
|
109
|
+
} catch {
|
|
110
|
+
// Fall back to cwd so top-level completion still works outside an app root.
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const completions = await getCompletions({
|
|
114
|
+
appRoot,
|
|
115
|
+
words,
|
|
116
|
+
cword: Number.isInteger(rawCword) ? rawCword : 0
|
|
117
|
+
});
|
|
118
|
+
if (completions.length > 0) {
|
|
119
|
+
stdout.write(`${completions.join("\n")}\n`);
|
|
120
|
+
}
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
commandCompletion
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export { createCompletionCommands };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { readdir, readFile } from "node:fs/promises";
|
|
3
|
-
import {
|
|
3
|
+
import { importFreshModuleFromAbsolutePath } from "@jskit-ai/kernel/server/support";
|
|
4
4
|
import {
|
|
5
5
|
ensureArray,
|
|
6
6
|
ensureObject,
|
|
@@ -72,7 +72,7 @@ async function resolveDescriptorFromLockEntry({ appRoot = "", packageId = "", in
|
|
|
72
72
|
|
|
73
73
|
let descriptorModule = null;
|
|
74
74
|
try {
|
|
75
|
-
descriptorModule = await
|
|
75
|
+
descriptorModule = await importFreshModuleFromAbsolutePath(descriptorAbsolutePath);
|
|
76
76
|
} catch {
|
|
77
77
|
return null;
|
|
78
78
|
}
|
|
@@ -180,7 +180,7 @@ function createListCommands(ctx = {}) {
|
|
|
180
180
|
}
|
|
181
181
|
if (mode === "placement-component-tokens") {
|
|
182
182
|
throw createCliError(
|
|
183
|
-
'list mode "placement-component-tokens" moved to a dedicated command: jskit list-
|
|
183
|
+
'list mode "placement-component-tokens" moved to a dedicated command: jskit list-component-tokens.'
|
|
184
184
|
);
|
|
185
185
|
}
|
|
186
186
|
|
|
@@ -385,9 +385,7 @@ function createListCommands(ctx = {}) {
|
|
|
385
385
|
if (options.json) {
|
|
386
386
|
const payload = {
|
|
387
387
|
placements: placementTargets.map((placementTarget) => ({
|
|
388
|
-
|
|
389
|
-
host: String(placementTarget.host || "").trim(),
|
|
390
|
-
position: String(placementTarget.position || "").trim(),
|
|
388
|
+
target: String(placementTarget.id || "").trim(),
|
|
391
389
|
default: placementTarget.default === true,
|
|
392
390
|
sourcePath: String(placementTarget.sourcePath || "").trim()
|
|
393
391
|
}))
|
|
@@ -10,10 +10,31 @@ import {
|
|
|
10
10
|
renderAddBundleHelp
|
|
11
11
|
} from "./discoverabilityHelp.js";
|
|
12
12
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
ensureLocalMainPlacementComponentProvisioning,
|
|
14
|
+
resolveProvisionableLocalPlacementComponentTokens
|
|
15
15
|
} from "./tabLinkItemProvisioning.js";
|
|
16
16
|
|
|
17
|
+
const COMPONENT_TOKEN_PATTERN = /\bcomponentToken\s*:\s*["']([^"']+)["']/g;
|
|
18
|
+
|
|
19
|
+
function collectPlacementComponentTokensFromManagedRecords(installedPackageRecords = []) {
|
|
20
|
+
const collectedTokens = new Set();
|
|
21
|
+
|
|
22
|
+
for (const record of ensureArray(installedPackageRecords)) {
|
|
23
|
+
const managedTextMutations = ensureObject(ensureObject(ensureObject(record).managed).text);
|
|
24
|
+
for (const mutationRecord of Object.values(managedTextMutations)) {
|
|
25
|
+
const source = String(ensureObject(mutationRecord).value || "");
|
|
26
|
+
for (const match of source.matchAll(COMPONENT_TOKEN_PATTERN)) {
|
|
27
|
+
const componentToken = String(match[1] || "").trim();
|
|
28
|
+
if (componentToken) {
|
|
29
|
+
collectedTokens.add(componentToken);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return sortStrings([...collectedTokens]);
|
|
36
|
+
}
|
|
37
|
+
|
|
17
38
|
async function runPackageAddCommand(ctx = {}, { positional, options, cwd, io }) {
|
|
18
39
|
const {
|
|
19
40
|
createCliError,
|
|
@@ -325,18 +346,17 @@ async function runPackageAddCommand(ctx = {}, { positional, options, cwd, io })
|
|
|
325
346
|
|
|
326
347
|
const finalResolvedPackageIds = sortStrings([...resolvedPackageIds, ...adoptedPackageIds]);
|
|
327
348
|
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
) {
|
|
335
|
-
await ensureLocalMainTabLinkItemProvisioning({
|
|
349
|
+
const generatedPlacementComponentTokens = await resolveProvisionableLocalPlacementComponentTokens({
|
|
350
|
+
appRoot,
|
|
351
|
+
componentTokens: collectPlacementComponentTokensFromManagedRecords(installedPackageRecords)
|
|
352
|
+
});
|
|
353
|
+
if (generatedPlacementComponentTokens.length > 0) {
|
|
354
|
+
await ensureLocalMainPlacementComponentProvisioning({
|
|
336
355
|
appRoot,
|
|
337
356
|
createCliError,
|
|
338
357
|
dryRun: options.dryRun === true,
|
|
339
|
-
touchedFiles
|
|
358
|
+
touchedFiles,
|
|
359
|
+
componentTokens: generatedPlacementComponentTokens
|
|
340
360
|
});
|
|
341
361
|
}
|
|
342
362
|
|