@jskit-ai/jskit-cli 0.2.39 → 0.2.41

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/jskit-cli",
3
- "version": "0.2.39",
3
+ "version": "0.2.41",
4
4
  "description": "Bundle and package orchestration CLI for JSKIT apps.",
5
5
  "type": "module",
6
6
  "files": [
@@ -20,8 +20,8 @@
20
20
  "test": "node --test"
21
21
  },
22
22
  "dependencies": {
23
- "@jskit-ai/jskit-catalog": "0.1.39",
24
- "@jskit-ai/kernel": "0.1.31"
23
+ "@jskit-ai/jskit-catalog": "0.1.40",
24
+ "@jskit-ai/kernel": "0.1.32"
25
25
  },
26
26
  "engines": {
27
27
  "node": "20.x"
@@ -16,7 +16,8 @@ import {
16
16
  hashBuffer,
17
17
  fileExists,
18
18
  readFileBufferIfExists,
19
- loadMutationWhenConfigContext
19
+ loadMutationWhenConfigContext,
20
+ resolveAppRelativePathWithinRoot
20
21
  } from "../ioAndMigrations.js";
21
22
  import {
22
23
  copyTemplateFile,
@@ -113,7 +114,7 @@ async function applyFileMutations(
113
114
  mutation,
114
115
  configContext
115
116
  })
116
- : [path.join(appRoot, to)];
117
+ : [resolveAppRelativePathWithinRoot(appRoot, to, `${packageEntry.packageId} files mutation.to`).absolutePath];
117
118
  const hasPrecomputedTemplateContext =
118
119
  precomputedTemplateContextByMutationIndex instanceof Map &&
119
120
  precomputedTemplateContextByMutationIndex.has(mutationIndex);
@@ -224,7 +225,7 @@ async function preflightFileMutationTemplateContexts(
224
225
  mutation,
225
226
  configContext
226
227
  })
227
- : [path.join(appRoot, to)]
228
+ : [resolveAppRelativePathWithinRoot(appRoot, to, `${packageEntry.packageId} files mutation.to`).absolutePath]
228
229
  : [path.join(appRoot, mutation.toDir || "migrations")];
229
230
  const replacements = await resolveTemplateContextReplacementsForMutation({
230
231
  packageEntry,
@@ -275,7 +275,8 @@ async function applyPackageInstall({
275
275
  appPackageJson,
276
276
  lock,
277
277
  packageRegistry,
278
- touchedFiles
278
+ touchedFiles,
279
+ reportTemplateFetchStatus = null
279
280
  }) {
280
281
  const existingInstall = ensureObject(lock.installedPackages[packageEntry.packageId]);
281
282
  const existingManaged = ensureObject(existingInstall.managed);
@@ -292,7 +293,11 @@ async function applyPackageInstall({
292
293
  const mutationWarnings = [];
293
294
  const mutations = ensureObject(packageEntry.descriptor.mutations);
294
295
  const fileMutations = ensureArray(mutations.files);
295
- const templateRoot = await resolvePackageTemplateRoot({ packageEntry, appRoot });
296
+ const templateRoot = await resolvePackageTemplateRoot({
297
+ packageEntry,
298
+ appRoot,
299
+ reportTemplateFetchStatus
300
+ });
296
301
  const packageEntryForMutations =
297
302
  templateRoot === packageEntry.rootDir
298
303
  ? packageEntry
@@ -1,3 +1,4 @@
1
+ import { normalizePagesRelativeTargetRoot } from "@jskit-ai/kernel/server/support";
1
2
  import { createCliError } from "../shared/cliError.js";
2
3
  import {
3
4
  ensureObject,
@@ -15,6 +16,8 @@ import { loadMutationWhenConfigContext } from "./ioAndMigrations.js";
15
16
 
16
17
  const WORKSPACE_VISIBILITY_LEVELS = Object.freeze(["workspace", "workspace_user"]);
17
18
  const WORKSPACE_VISIBILITY_SET = new Set(WORKSPACE_VISIBILITY_LEVELS);
19
+ const OPTION_VALIDATION_ENABLED_SURFACE_ID = "enabled-surface-id";
20
+ const OPTION_NORMALIZATION_PAGES_RELATIVE_TARGET_ROOT = "pages-relative-target-root";
18
21
 
19
22
  function normalizeSurfaceIdForMutation(value = "") {
20
23
  return String(value || "").trim().toLowerCase();
@@ -100,6 +103,95 @@ function normalizeResolvedOptionValue(value = "") {
100
103
  return String(value || "").trim().toLowerCase();
101
104
  }
102
105
 
106
+ function normalizeResolvedOptionSchemaValue({
107
+ packageEntry,
108
+ optionName = "",
109
+ schema = {},
110
+ value = ""
111
+ } = {}) {
112
+ const normalizedValue = String(value || "").trim();
113
+ if (!normalizedValue) {
114
+ return "";
115
+ }
116
+
117
+ const normalizationType = normalizeResolvedOptionValue(schema?.normalizationType);
118
+ if (!normalizationType) {
119
+ return normalizedValue;
120
+ }
121
+
122
+ if (normalizationType === OPTION_NORMALIZATION_PAGES_RELATIVE_TARGET_ROOT) {
123
+ return normalizePagesRelativeTargetRoot(normalizedValue, {
124
+ context: `package ${String(packageEntry?.packageId || "unknown-package")}`,
125
+ label: `option "${String(optionName || "").trim() || "unknown"}"`
126
+ }).slice("src/pages/".length);
127
+ }
128
+
129
+ throw createCliError(
130
+ `Invalid option normalization type in package ${String(packageEntry?.packageId || "unknown-package")}: ${String(schema?.normalizationType || "").trim()}.`
131
+ );
132
+ }
133
+
134
+ function resolveSchemaValidatedOptionNames(packageEntry = {}, validationType = "", { optionNames = null } = {}) {
135
+ const normalizedValidationType = String(validationType || "").trim().toLowerCase();
136
+ if (!normalizedValidationType) {
137
+ return [];
138
+ }
139
+
140
+ const optionSchemas = ensureObject(packageEntry?.descriptor?.options);
141
+ const candidateOptionNames = Array.isArray(optionNames) && optionNames.length > 0
142
+ ? optionNames
143
+ : Object.keys(optionSchemas);
144
+
145
+ return [
146
+ ...new Set(
147
+ candidateOptionNames.filter((optionName) => {
148
+ const schema = ensureObject(optionSchemas[optionName]);
149
+ return normalizeResolvedOptionValue(schema.validationType) === normalizedValidationType;
150
+ })
151
+ )
152
+ ];
153
+ }
154
+
155
+ function validateEnabledSurfaceOptionValues({
156
+ packageEntry,
157
+ resolvedOptions = {},
158
+ optionNames = null,
159
+ configContext = {}
160
+ } = {}) {
161
+ const validatedOptionNames = resolveSchemaValidatedOptionNames(
162
+ packageEntry,
163
+ OPTION_VALIDATION_ENABLED_SURFACE_ID,
164
+ { optionNames }
165
+ );
166
+ if (validatedOptionNames.length < 1) {
167
+ return;
168
+ }
169
+
170
+ const providedSurfaceOptionNames = validatedOptionNames.filter((optionName) => {
171
+ return normalizeSurfaceIdForMutation(resolvedOptions?.[optionName]).length > 0;
172
+ });
173
+ if (providedSurfaceOptionNames.length < 1) {
174
+ return;
175
+ }
176
+
177
+ const packageId = String(packageEntry?.packageId || "").trim() || "unknown-package";
178
+ const surfaceDefinitions = resolveSurfaceDefinitionsForOptionPolicy(configContext);
179
+ for (const optionName of providedSurfaceOptionNames) {
180
+ const surfaceId = normalizeSurfaceIdForMutation(resolvedOptions?.[optionName]);
181
+ const surfaceDefinition = surfaceDefinitions[surfaceId];
182
+ if (!surfaceDefinition) {
183
+ throw createCliError(
184
+ `Invalid option for package ${packageId}: --${optionName} references unknown surface "${surfaceId}" in config/public.js.`
185
+ );
186
+ }
187
+ if (surfaceDefinition.enabled !== true) {
188
+ throw createCliError(
189
+ `Invalid option for package ${packageId}: --${optionName} references disabled surface "${surfaceId}" in config/public.js.`
190
+ );
191
+ }
192
+ }
193
+ }
194
+
103
195
  function validateSurfaceVisibilityOptionPolicy({
104
196
  packageEntry,
105
197
  resolvedOptions = {},
@@ -159,6 +251,34 @@ async function validateResolvedOptionPolicies({
159
251
  });
160
252
  }
161
253
 
254
+ async function validateOptionValuesForPackage({
255
+ packageEntry,
256
+ resolvedOptions = {},
257
+ appRoot = "",
258
+ optionNames = null
259
+ } = {}) {
260
+ if (!appRoot) {
261
+ return;
262
+ }
263
+
264
+ const validatedOptionNames = resolveSchemaValidatedOptionNames(
265
+ packageEntry,
266
+ OPTION_VALIDATION_ENABLED_SURFACE_ID,
267
+ { optionNames }
268
+ );
269
+ if (validatedOptionNames.length < 1) {
270
+ return;
271
+ }
272
+
273
+ const configContext = await loadMutationWhenConfigContext(appRoot);
274
+ validateEnabledSurfaceOptionValues({
275
+ packageEntry,
276
+ resolvedOptions,
277
+ optionNames: validatedOptionNames,
278
+ configContext
279
+ });
280
+ }
281
+
162
282
  async function resolvePackageOptions(packageEntry, inlineOptions, io, { appRoot = "" } = {}) {
163
283
  const optionSchemas = ensureObject(packageEntry.descriptor.options);
164
284
  const optionNames = Object.keys(optionSchemas);
@@ -211,10 +331,23 @@ async function resolvePackageOptions(packageEntry, inlineOptions, io, { appRoot
211
331
  for (const optionName of optionNames) {
212
332
  const schema = ensureObject(optionSchemas[optionName]);
213
333
  const allowEmpty = schema.allowEmpty === true;
334
+ const assignResolvedOption = (rawValue = "") => {
335
+ const normalizedOptionValue = normalizeResolvedOptionSchemaValue({
336
+ packageEntry,
337
+ optionName,
338
+ schema,
339
+ value: rawValue
340
+ });
341
+ if (normalizedOptionValue || allowEmpty) {
342
+ resolved[optionName] = normalizedOptionValue;
343
+ return true;
344
+ }
345
+ return false;
346
+ };
214
347
  if (hasInlineOption(optionName)) {
215
348
  const inlineValue = String(inlineOptionValues[optionName] || "").trim();
216
349
  if (inlineValue || allowEmpty) {
217
- resolved[optionName] = inlineValue;
350
+ assignResolvedOption(inlineValue);
218
351
  continue;
219
352
  }
220
353
  if (schema.required) {
@@ -226,7 +359,7 @@ async function resolvePackageOptions(packageEntry, inlineOptions, io, { appRoot
226
359
  if (defaultFromConfigPath) {
227
360
  const defaultFromConfigValue = await resolveOptionDefaultFromConfig(defaultFromConfigPath);
228
361
  if (defaultFromConfigValue || allowEmpty) {
229
- resolved[optionName] = defaultFromConfigValue;
362
+ assignResolvedOption(defaultFromConfigValue);
230
363
  continue;
231
364
  }
232
365
  }
@@ -235,31 +368,37 @@ async function resolvePackageOptions(packageEntry, inlineOptions, io, { appRoot
235
368
  if (defaultFromOptionTemplate) {
236
369
  const derivedOptionValue = resolveOptionDefaultFromTemplate(defaultFromOptionTemplate, optionName);
237
370
  if (derivedOptionValue || allowEmpty) {
238
- resolved[optionName] = derivedOptionValue;
371
+ assignResolvedOption(derivedOptionValue);
239
372
  continue;
240
373
  }
241
374
  }
242
375
 
243
376
  if (typeof schema.defaultValue === "string" && schema.defaultValue.trim()) {
244
- resolved[optionName] = schema.defaultValue.trim();
377
+ assignResolvedOption(schema.defaultValue.trim());
245
378
  continue;
246
379
  }
247
380
 
248
381
  if (schema.required) {
249
- resolved[optionName] = await promptForRequiredOption({
382
+ assignResolvedOption(await promptForRequiredOption({
250
383
  ownerType: "package",
251
384
  ownerId: packageEntry.packageId,
252
385
  optionName,
253
386
  optionSchema: schema,
254
387
  stdin: io.stdin,
255
388
  stdout: io.stdout
256
- });
389
+ }));
257
390
  continue;
258
391
  }
259
392
 
260
393
  resolved[optionName] = "";
261
394
  }
262
395
 
396
+ await validateOptionValuesForPackage({
397
+ packageEntry,
398
+ resolvedOptions: resolved,
399
+ appRoot
400
+ });
401
+
263
402
  await validateResolvedOptionPolicies({
264
403
  packageEntry,
265
404
  resolvedOptions: resolved,
@@ -291,9 +430,23 @@ function validateInlineOptionsForPackage(packageEntry, inlineOptions) {
291
430
  );
292
431
  }
293
432
 
433
+ async function validateInlineOptionValuesForPackage(
434
+ packageEntry,
435
+ inlineOptions,
436
+ { appRoot = "", optionNames = null } = {}
437
+ ) {
438
+ await validateOptionValuesForPackage({
439
+ packageEntry,
440
+ resolvedOptions: ensureObject(inlineOptions),
441
+ appRoot,
442
+ optionNames
443
+ });
444
+ }
445
+
294
446
  export {
295
447
  normalizeSurfaceIdForMutation,
296
448
  parseSurfaceIdListForMutation,
297
449
  resolvePackageOptions,
298
- validateInlineOptionsForPackage
450
+ validateInlineOptionsForPackage,
451
+ validateInlineOptionValuesForPackage
299
452
  };
@@ -68,7 +68,10 @@ async function ensureMaterializedInstallWorkspace(installRoot) {
68
68
  );
69
69
  }
70
70
 
71
- async function installCatalogPackageIntoCache({ installRoot, packageEntry }) {
71
+ async function installCatalogPackageIntoCache({
72
+ installRoot,
73
+ packageEntry
74
+ }) {
72
75
  const packageId = String(packageEntry?.packageId || "").trim();
73
76
  const version = String(packageEntry?.version || "").trim();
74
77
  const packageSpec = version ? `${packageId}@${version}` : packageId;
@@ -124,6 +127,7 @@ async function installCatalogPackageIntoCache({ installRoot, packageEntry }) {
124
127
  async function materializeCatalogPackageRoot({
125
128
  packageEntry,
126
129
  appRoot,
130
+ reportTemplateFetchStatus = null,
127
131
  installCatalogPackage = installCatalogPackageIntoCache
128
132
  } = {}) {
129
133
  if (!isInternalCatalogPackageEntry(packageEntry)) {
@@ -139,12 +143,20 @@ async function materializeCatalogPackageRoot({
139
143
  const installRoot = buildMaterializedInstallRoot({ appRoot, packageEntry });
140
144
  const candidateRoot = path.join(installRoot, "node_modules", ...packageId.split("/"));
141
145
  const descriptorPath = path.join(candidateRoot, "package.descriptor.mjs");
146
+ let didInstallPackage = false;
142
147
 
143
148
  if (!(await fileExists(descriptorPath))) {
149
+ if (typeof reportTemplateFetchStatus === "function") {
150
+ reportTemplateFetchStatus({
151
+ packageEntry,
152
+ state: "start"
153
+ });
154
+ }
144
155
  await installCatalogPackage({
145
156
  installRoot,
146
157
  packageEntry
147
158
  });
159
+ didInstallPackage = true;
148
160
  }
149
161
 
150
162
  if (!(await fileExists(descriptorPath))) {
@@ -153,6 +165,13 @@ async function materializeCatalogPackageRoot({
153
165
  );
154
166
  }
155
167
 
168
+ if (didInstallPackage && typeof reportTemplateFetchStatus === "function") {
169
+ reportTemplateFetchStatus({
170
+ packageEntry,
171
+ state: "complete"
172
+ });
173
+ }
174
+
156
175
  MATERIALIZED_PACKAGE_ROOTS.set(cacheKey, candidateRoot);
157
176
  return candidateRoot;
158
177
  }
@@ -243,6 +262,7 @@ async function resolvePackageRootFromLocalWorkspace({ packageId }) {
243
262
  async function resolvePackageTemplateRoot({
244
263
  packageEntry,
245
264
  appRoot,
265
+ reportTemplateFetchStatus = null,
246
266
  materializeCatalogRoot = materializeCatalogPackageRoot
247
267
  } = {}) {
248
268
  const packageRoot = String(packageEntry?.rootDir || "").trim();
@@ -267,7 +287,8 @@ async function resolvePackageTemplateRoot({
267
287
 
268
288
  const materializedCatalogPackageRoot = await materializeCatalogRoot({
269
289
  packageEntry,
270
- appRoot
290
+ appRoot,
291
+ reportTemplateFetchStatus
271
292
  });
272
293
  if (materializedCatalogPackageRoot) {
273
294
  return materializedCatalogPackageRoot;
@@ -38,7 +38,8 @@ async function runPackageAddCommand(ctx = {}, { positional, options, cwd, io })
38
38
  adoptAppLocalPackageDependencies,
39
39
  writeJsonFile,
40
40
  runNpmInstall,
41
- renderResolvedSummary
41
+ renderResolvedSummary,
42
+ createCatalogFetchStatusReporter = () => () => {}
42
43
  } = ctx;
43
44
 
44
45
  const invocationMode = options?.commandMode === "generate" ? "generate" : "add";
@@ -242,6 +243,9 @@ async function runPackageAddCommand(ctx = {}, { positional, options, cwd, io })
242
243
 
243
244
  const packagesToInstall = [];
244
245
  const resolvedOptionsByPackage = {};
246
+ const reportTemplateFetchStatus = createCatalogFetchStatusReporter(io, {
247
+ enabled: options.json !== true
248
+ });
245
249
  const forceReapplyTarget = options?.forceReapplyTarget === true;
246
250
  const hasInlineOptions = Object.keys(ensureObject(options.inlineOptions)).length > 0;
247
251
  for (const packageId of resolvedPackageIds) {
@@ -293,7 +297,8 @@ async function runPackageAddCommand(ctx = {}, { positional, options, cwd, io })
293
297
  appPackageJson: packageJson,
294
298
  lock,
295
299
  packageRegistry: combinedPackageRegistry,
296
- touchedFiles
300
+ touchedFiles,
301
+ reportTemplateFetchStatus
297
302
  });
298
303
  installedPackageRecords.push(managedRecord);
299
304
  }