@jskit-ai/jskit-cli 0.2.42 → 0.2.44
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 +2 -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 +32 -0
- package/src/server/cliRuntime/packageInstallFlow.js +61 -22
- package/src/server/cliRuntime/packageIntrospection/placementNormalization.js +13 -22
- package/src/server/cliRuntime/packageOptions.js +129 -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 +28 -1
- 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 +9 -0
- package/src/server/core/commandCatalog.js +36 -13
- package/src/server/core/createCommandHandlers.js +3 -0
|
@@ -32,7 +32,8 @@ import {
|
|
|
32
32
|
import {
|
|
33
33
|
applyFileMutations,
|
|
34
34
|
applyTextMutations,
|
|
35
|
-
|
|
35
|
+
partitionPreFileConfigTextMutations,
|
|
36
|
+
prepareFileMutations,
|
|
36
37
|
resolvePositioningMutations
|
|
37
38
|
} from "./mutationApplication.js";
|
|
38
39
|
function createManagedRecordBase(packageEntry, options) {
|
|
@@ -138,15 +139,23 @@ async function applyPackagePositioning({
|
|
|
138
139
|
const positioningMutations = resolvePositioningMutations(mutations);
|
|
139
140
|
const appliedManagedFiles = [];
|
|
140
141
|
const appliedManagedText = {};
|
|
142
|
+
const preparedFileMutations = await prepareFileMutations(
|
|
143
|
+
packageEntryForMutations,
|
|
144
|
+
packageOptions,
|
|
145
|
+
appRoot,
|
|
146
|
+
positioningMutations.files,
|
|
147
|
+
nextManaged.files
|
|
148
|
+
);
|
|
141
149
|
if (positioningMutations.files.length > 0) {
|
|
142
150
|
await applyFileMutations(
|
|
143
151
|
packageEntryForMutations,
|
|
144
|
-
packageOptions,
|
|
145
152
|
appRoot,
|
|
146
|
-
|
|
153
|
+
preparedFileMutations,
|
|
147
154
|
appliedManagedFiles,
|
|
148
155
|
[],
|
|
149
|
-
touchedFiles
|
|
156
|
+
touchedFiles,
|
|
157
|
+
[],
|
|
158
|
+
nextManaged.files
|
|
150
159
|
);
|
|
151
160
|
}
|
|
152
161
|
if (positioningMutations.text.length > 0) {
|
|
@@ -236,17 +245,24 @@ async function applyPackageMigrationsOnly({
|
|
|
236
245
|
return operation === "install-migration";
|
|
237
246
|
});
|
|
238
247
|
const mutationWarnings = [];
|
|
248
|
+
const preparedFileMutations = await prepareFileMutations(
|
|
249
|
+
packageEntryForMutations,
|
|
250
|
+
packageOptions,
|
|
251
|
+
appRoot,
|
|
252
|
+
migrationFileMutations,
|
|
253
|
+
nextManaged.files
|
|
254
|
+
);
|
|
239
255
|
|
|
240
256
|
if (migrationFileMutations.length > 0) {
|
|
241
257
|
await applyFileMutations(
|
|
242
258
|
packageEntryForMutations,
|
|
243
|
-
packageOptions,
|
|
244
259
|
appRoot,
|
|
245
|
-
|
|
260
|
+
preparedFileMutations,
|
|
246
261
|
[],
|
|
247
262
|
nextManaged.migrations,
|
|
248
263
|
touchedFiles,
|
|
249
|
-
mutationWarnings
|
|
264
|
+
mutationWarnings,
|
|
265
|
+
nextManaged.files
|
|
250
266
|
);
|
|
251
267
|
}
|
|
252
268
|
|
|
@@ -280,19 +296,23 @@ async function applyPackageInstall({
|
|
|
280
296
|
}) {
|
|
281
297
|
const existingInstall = ensureObject(lock.installedPackages[packageEntry.packageId]);
|
|
282
298
|
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
299
|
const generatorPackage = isGeneratorPackageEntry(packageEntry);
|
|
293
300
|
const mutationWarnings = [];
|
|
294
301
|
const mutations = ensureObject(packageEntry.descriptor.mutations);
|
|
295
302
|
const fileMutations = ensureArray(mutations.files);
|
|
303
|
+
const textMutations = ensureArray(mutations.text);
|
|
304
|
+
const hasSurfaceTargetedFileMutations = fileMutations.some((mutationValue) =>
|
|
305
|
+
Boolean(normalizeFileMutationRecord(mutationValue).toSurface)
|
|
306
|
+
);
|
|
307
|
+
const {
|
|
308
|
+
preFileTextMutations,
|
|
309
|
+
postFileTextMutations
|
|
310
|
+
} = hasSurfaceTargetedFileMutations
|
|
311
|
+
? partitionPreFileConfigTextMutations(textMutations)
|
|
312
|
+
: {
|
|
313
|
+
preFileTextMutations: [],
|
|
314
|
+
postFileTextMutations: textMutations
|
|
315
|
+
};
|
|
296
316
|
const templateRoot = await resolvePackageTemplateRoot({
|
|
297
317
|
packageEntry,
|
|
298
318
|
appRoot,
|
|
@@ -305,13 +325,33 @@ async function applyPackageInstall({
|
|
|
305
325
|
...packageEntry,
|
|
306
326
|
rootDir: templateRoot
|
|
307
327
|
};
|
|
328
|
+
const managedRecord = createManagedRecordBase(packageEntry, packageOptions);
|
|
329
|
+
managedRecord.managed.migrations = cloneManagedArray(existingManaged.migrations);
|
|
308
330
|
|
|
309
|
-
|
|
331
|
+
if (preFileTextMutations.length > 0) {
|
|
332
|
+
await applyTextMutations(
|
|
333
|
+
packageEntryForMutations,
|
|
334
|
+
appRoot,
|
|
335
|
+
preFileTextMutations,
|
|
336
|
+
packageOptions,
|
|
337
|
+
managedRecord.managed.text,
|
|
338
|
+
touchedFiles
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const preparedFileMutations = await prepareFileMutations(
|
|
310
343
|
packageEntryForMutations,
|
|
311
344
|
packageOptions,
|
|
312
345
|
appRoot,
|
|
313
|
-
fileMutations
|
|
346
|
+
fileMutations,
|
|
347
|
+
ensureArray(existingManaged.files)
|
|
314
348
|
);
|
|
349
|
+
await removeManagedViteProxyEntries({
|
|
350
|
+
appRoot,
|
|
351
|
+
packageId: packageEntry.packageId,
|
|
352
|
+
managedViteChanges: ensureObject(existingManaged.vite),
|
|
353
|
+
touchedFiles
|
|
354
|
+
});
|
|
315
355
|
|
|
316
356
|
const mutationDependencies = ensureObject(mutations.dependencies);
|
|
317
357
|
const runtimeDependencies = ensureObject(mutationDependencies.runtime);
|
|
@@ -416,20 +456,19 @@ async function applyPackageInstall({
|
|
|
416
456
|
|
|
417
457
|
await applyFileMutations(
|
|
418
458
|
packageEntryForMutations,
|
|
419
|
-
packageOptions,
|
|
420
459
|
appRoot,
|
|
421
|
-
|
|
460
|
+
preparedFileMutations,
|
|
422
461
|
managedRecord.managed.files,
|
|
423
462
|
managedRecord.managed.migrations,
|
|
424
463
|
touchedFiles,
|
|
425
464
|
mutationWarnings,
|
|
426
|
-
|
|
465
|
+
ensureArray(existingManaged.files)
|
|
427
466
|
);
|
|
428
467
|
|
|
429
468
|
await applyTextMutations(
|
|
430
469
|
packageEntryForMutations,
|
|
431
470
|
appRoot,
|
|
432
|
-
|
|
471
|
+
postFileTextMutations,
|
|
433
472
|
packageOptions,
|
|
434
473
|
managedRecord.managed.text,
|
|
435
474
|
touchedFiles
|
|
@@ -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 = "") {
|
|
@@ -153,6 +155,114 @@ function resolveSchemaValidatedOptionNames(packageEntry = {}, validationType = "
|
|
|
153
155
|
];
|
|
154
156
|
}
|
|
155
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
|
+
|
|
156
266
|
function validateEnabledSurfaceOptionValues({
|
|
157
267
|
packageEntry,
|
|
158
268
|
resolvedOptions = {},
|
|
@@ -254,6 +364,12 @@ async function validateResolvedOptionPolicies({
|
|
|
254
364
|
|
|
255
365
|
function resolvePromptChoicesForOption({ schema = {}, configContext = {} } = {}) {
|
|
256
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
|
+
}
|
|
257
373
|
if (validationType !== OPTION_VALIDATION_ENABLED_SURFACE_ID) {
|
|
258
374
|
return [];
|
|
259
375
|
}
|
|
@@ -275,9 +391,16 @@ async function validateOptionValuesForPackage({
|
|
|
275
391
|
appRoot = "",
|
|
276
392
|
optionNames = null
|
|
277
393
|
} = {}) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
394
|
+
validateEnumOptionValues({
|
|
395
|
+
packageEntry,
|
|
396
|
+
resolvedOptions,
|
|
397
|
+
optionNames
|
|
398
|
+
});
|
|
399
|
+
validateCsvEnumOptionValues({
|
|
400
|
+
packageEntry,
|
|
401
|
+
resolvedOptions,
|
|
402
|
+
optionNames
|
|
403
|
+
});
|
|
281
404
|
|
|
282
405
|
const validatedOptionNames = resolveSchemaValidatedOptionNames(
|
|
283
406
|
packageEntry,
|
|
@@ -287,6 +410,9 @@ async function validateOptionValuesForPackage({
|
|
|
287
410
|
if (validatedOptionNames.length < 1) {
|
|
288
411
|
return;
|
|
289
412
|
}
|
|
413
|
+
if (!appRoot) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
290
416
|
|
|
291
417
|
const configContext = await loadMutationWhenConfigContext(appRoot);
|
|
292
418
|
validateEnabledSurfaceOptionValues({
|
|
@@ -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
|
|