@jskit-ai/jskit-cli 0.2.27 → 0.2.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/package.json +3 -2
  2. package/src/server/cliRuntime/appState.js +226 -0
  3. package/src/server/cliRuntime/capabilitySupport.js +194 -0
  4. package/src/server/cliRuntime/descriptorValidation.js +150 -0
  5. package/src/server/cliRuntime/ioAndMigrations.js +381 -0
  6. package/src/server/cliRuntime/localPackageSupport.js +390 -0
  7. package/src/server/cliRuntime/mutationApplication.js +9 -0
  8. package/src/server/cliRuntime/mutationWhen.js +285 -0
  9. package/src/server/cliRuntime/mutations/fileMutations.js +247 -0
  10. package/src/server/cliRuntime/mutations/installMigrationMutation.js +213 -0
  11. package/src/server/cliRuntime/mutations/mutationPathUtils.js +12 -0
  12. package/src/server/cliRuntime/mutations/surfaceTargets.js +155 -0
  13. package/src/server/cliRuntime/mutations/templateContext.js +171 -0
  14. package/src/server/cliRuntime/mutations/textMutations.js +250 -0
  15. package/src/server/cliRuntime/packageInstallFlow.js +489 -0
  16. package/src/server/cliRuntime/packageIntrospection/exportEntries.js +259 -0
  17. package/src/server/cliRuntime/packageIntrospection/exportedSymbols.js +216 -0
  18. package/src/server/cliRuntime/packageIntrospection/placementNormalization.js +98 -0
  19. package/src/server/cliRuntime/packageIntrospection/providerBindingIntrospection.js +377 -0
  20. package/src/server/cliRuntime/packageIntrospection.js +137 -0
  21. package/src/server/cliRuntime/packageOptions.js +299 -0
  22. package/src/server/cliRuntime/packageRegistries.js +343 -0
  23. package/src/server/cliRuntime/packageTemplateResolution.js +131 -0
  24. package/src/server/cliRuntime/viteProxy.js +356 -0
  25. package/src/server/commandHandlers/health.js +292 -0
  26. package/src/server/commandHandlers/list.js +292 -0
  27. package/src/server/commandHandlers/package.js +23 -0
  28. package/src/server/commandHandlers/packageCommands/add.js +282 -0
  29. package/src/server/commandHandlers/packageCommands/create.js +155 -0
  30. package/src/server/commandHandlers/packageCommands/generate.js +116 -0
  31. package/src/server/commandHandlers/packageCommands/migrations.js +155 -0
  32. package/src/server/commandHandlers/packageCommands/position.js +103 -0
  33. package/src/server/commandHandlers/packageCommands/remove.js +181 -0
  34. package/src/server/commandHandlers/packageCommands/update.js +40 -0
  35. package/src/server/commandHandlers/shared.js +314 -0
  36. package/src/server/commandHandlers/show/payloads.js +92 -0
  37. package/src/server/commandHandlers/show/renderBundleText.js +16 -0
  38. package/src/server/commandHandlers/show/renderHelpers.js +82 -0
  39. package/src/server/commandHandlers/show/renderPackageCapabilities.js +124 -0
  40. package/src/server/commandHandlers/show/renderPackageExports.js +203 -0
  41. package/src/server/commandHandlers/show/renderPackageText.js +332 -0
  42. package/src/server/commandHandlers/show.js +114 -0
  43. package/src/server/core/argParser.js +144 -0
  44. package/src/server/{runtimeDeps.js → core/buildCommandDeps.js} +2 -1
  45. package/src/server/core/commandCatalog.js +47 -0
  46. package/src/server/core/createCliRunner.js +150 -0
  47. package/src/server/core/createCommandHandlers.js +43 -0
  48. package/src/server/{runCli.js → core/dispatchCli.js} +14 -1
  49. package/src/server/core/usageHelp.js +344 -0
  50. package/src/server/index.js +1 -1
  51. package/src/server/{optionInterpolation.js → shared/optionInterpolation.js} +12 -1
  52. package/src/server/{pathResolution.js → shared/pathResolution.js} +1 -1
  53. package/src/server/argParser.js +0 -206
  54. package/src/server/cliRuntime.js +0 -4956
  55. package/src/server/commandHandlers.js +0 -2109
  56. /package/src/server/{cliError.js → shared/cliError.js} +0 -0
  57. /package/src/server/{collectionUtils.js → shared/collectionUtils.js} +0 -0
  58. /package/src/server/{outputFormatting.js → shared/outputFormatting.js} +0 -0
  59. /package/src/server/{packageIdHelpers.js → shared/packageIdHelpers.js} +0 -0
@@ -0,0 +1,390 @@
1
+ import { createCliError } from "../shared/cliError.js";
2
+ import {
3
+ ensureArray,
4
+ ensureObject,
5
+ sortStrings
6
+ } from "../shared/collectionUtils.js";
7
+
8
+ function resolvePackageDependencySpecifier(packageEntry, { existingValue = "" } = {}) {
9
+ const source = ensureObject(packageEntry?.source);
10
+ const sourceType = String(source.type || packageEntry?.sourceType || "").trim();
11
+ if (sourceType === "app-local-package" || sourceType === "local-package") {
12
+ const packagePath = normalizeRelativePosixPath(String(source.packagePath || packageEntry?.relativeDir || "").trim());
13
+ if (!packagePath) {
14
+ throw createCliError(`Unable to resolve local package path for ${String(packageEntry?.packageId || "unknown package")}.`);
15
+ }
16
+ return toFileDependencySpecifier(packagePath);
17
+ }
18
+ if (sourceType === "npm-installed-package") {
19
+ const normalizedExisting = String(existingValue || "").trim();
20
+ if (normalizedExisting) {
21
+ return normalizedExisting;
22
+ }
23
+ }
24
+
25
+ const descriptorVersion = String(packageEntry?.version || "").trim();
26
+ if (descriptorVersion) {
27
+ return normalizeJskitDependencySpecifier(packageEntry?.packageId, descriptorVersion);
28
+ }
29
+ const packageJsonVersion = String(packageEntry?.packageJson?.version || "").trim();
30
+ if (packageJsonVersion) {
31
+ return normalizeJskitDependencySpecifier(packageEntry?.packageId, packageJsonVersion);
32
+ }
33
+ throw createCliError(`Unable to resolve dependency specifier for ${String(packageEntry?.packageId || "unknown package")}.`);
34
+ }
35
+
36
+ function normalizeJskitDependencySpecifier(packageId, dependencySpecifier) {
37
+ const normalizedPackageId = String(packageId || "").trim();
38
+ const normalizedSpecifier = String(dependencySpecifier || "").trim();
39
+ if (!normalizedSpecifier || !normalizedPackageId.startsWith("@jskit-ai/")) {
40
+ return normalizedSpecifier;
41
+ }
42
+
43
+ const semverMatch = /^(\d+)\.\d+\.\d+(?:[.+-][0-9A-Za-z.-]+)?$/.exec(normalizedSpecifier);
44
+ if (!semverMatch) {
45
+ return normalizedSpecifier;
46
+ }
47
+
48
+ return `${semverMatch[1]}.x`;
49
+ }
50
+
51
+ function normalizePackageNameSegment(rawValue, { label = "package name" } = {}) {
52
+ const lowered = String(rawValue || "")
53
+ .trim()
54
+ .toLowerCase()
55
+ .replace(/[^a-z0-9._-]+/g, "-")
56
+ .replace(/-+/g, "-")
57
+ .replace(/^[._-]+|[._-]+$/g, "");
58
+ if (!lowered) {
59
+ throw createCliError(`Invalid ${label}. Use letters, numbers, dash, underscore, or dot.`);
60
+ }
61
+ return lowered;
62
+ }
63
+
64
+ function normalizeScopeName(rawScope) {
65
+ const normalized = String(rawScope || "").trim().replace(/^@+/, "");
66
+ return normalizePackageNameSegment(normalized, { label: "scope" });
67
+ }
68
+
69
+ function resolveDefaultLocalScopeFromAppName(appPackageName) {
70
+ const appName = String(appPackageName || "").trim();
71
+ if (!appName) {
72
+ return "app";
73
+ }
74
+
75
+ const unscoped = appName.startsWith("@")
76
+ ? appName.slice(appName.indexOf("/") + 1)
77
+ : appName;
78
+ return normalizeScopeName(unscoped || "app");
79
+ }
80
+
81
+ function normalizeRelativePosixPath(pathValue) {
82
+ return String(pathValue || "")
83
+ .trim()
84
+ .replace(/\\/g, "/")
85
+ .replace(/^\/+|\/+$/g, "")
86
+ .replace(/\/{2,}/g, "/");
87
+ }
88
+
89
+ function toFileDependencySpecifier(relativePath) {
90
+ const normalized = normalizeRelativePosixPath(relativePath);
91
+ if (!normalized) {
92
+ throw createCliError("Cannot create file: dependency specifier from empty relative path.");
93
+ }
94
+ return `file:${normalized}`;
95
+ }
96
+
97
+ function resolveLocalPackageId({ rawName, appPackageName, inlineOptions }) {
98
+ const explicitPackageId = String(inlineOptions["package-id"] || "").trim();
99
+ if (explicitPackageId) {
100
+ const scopedPattern = /^@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*$/;
101
+ if (!scopedPattern.test(explicitPackageId)) {
102
+ throw createCliError(
103
+ `Invalid --package-id ${explicitPackageId}. Expected format: @scope/name (lowercase alphanumeric, ., _, -).`
104
+ );
105
+ }
106
+ const packageName = explicitPackageId.slice(explicitPackageId.indexOf("/") + 1);
107
+ return {
108
+ packageId: explicitPackageId,
109
+ packageDirName: normalizePackageNameSegment(packageName)
110
+ };
111
+ }
112
+
113
+ const packageDirName = normalizePackageNameSegment(rawName);
114
+ const scopeName = String(inlineOptions.scope || "").trim()
115
+ ? normalizeScopeName(inlineOptions.scope)
116
+ : resolveDefaultLocalScopeFromAppName(appPackageName);
117
+ return {
118
+ packageId: `@${scopeName}/${packageDirName}`,
119
+ packageDirName
120
+ };
121
+ }
122
+
123
+ function createLocalPackageDescriptorTemplate({ packageId, description }) {
124
+ return `export default Object.freeze({
125
+ packageVersion: 1,
126
+ packageId: "${packageId}",
127
+ version: "0.1.0",
128
+ kind: "runtime",
129
+ description: ${JSON.stringify(String(description || ""))},
130
+ dependsOn: [
131
+ // "@jskit-ai/kernel"
132
+ ],
133
+ capabilities: {
134
+ provides: [
135
+ // "example.feature"
136
+ ],
137
+ requires: [
138
+ // "example.dependency"
139
+ ]
140
+ },
141
+ options: {
142
+ // "example-option": {
143
+ // required: true,
144
+ // promptLabel: "Enter option value",
145
+ // promptHint: "Used by mutations.text interpolation",
146
+ // defaultValue: "example"
147
+ // }
148
+ },
149
+ runtime: {
150
+ server: {
151
+ providers: [
152
+ // {
153
+ // entrypoint: "src/server/providers/ExampleServerProvider.js",
154
+ // export: "ExampleServerProvider"
155
+ // }
156
+ ]
157
+ },
158
+ client: {
159
+ providers: [
160
+ // {
161
+ // entrypoint: "src/client/providers/ExampleClientProvider.js",
162
+ // export: "ExampleClientProvider"
163
+ // }
164
+ ]
165
+ }
166
+ },
167
+ metadata: {
168
+ server: {
169
+ routes: [
170
+ // {
171
+ // method: "GET",
172
+ // path: "/api/example",
173
+ // summary: "Describe server route validator"
174
+ // }
175
+ ]
176
+ },
177
+ ui: {
178
+ routes: [
179
+ // {
180
+ // id: "example.route",
181
+ // path: "/example",
182
+ // scope: "global",
183
+ // name: "example-route",
184
+ // componentKey: "example-route",
185
+ // autoRegister: true,
186
+ // guard: {
187
+ // policy: "public"
188
+ // },
189
+ // purpose: "Describe what this route is for."
190
+ // }
191
+ ],
192
+ elements: [
193
+ // {
194
+ // key: "example-route",
195
+ // export: "ExampleView",
196
+ // entrypoint: "src/client/views/ExampleView.vue",
197
+ // purpose: "UI element exposed by this package."
198
+ // }
199
+ ],
200
+ overrides: [
201
+ // {
202
+ // targetId: "some.existing.route",
203
+ // mode: "replace",
204
+ // reason: "Explain override intent."
205
+ // }
206
+ ]
207
+ }
208
+ },
209
+ mutations: {
210
+ dependencies: {
211
+ runtime: {
212
+ // "@example/runtime-dependency": "^1.0.0"
213
+ },
214
+ dev: {
215
+ // "@example/dev-dependency": "^1.0.0"
216
+ }
217
+ },
218
+ packageJson: {
219
+ scripts: {
220
+ // "lint:example": "eslint src/example"
221
+ }
222
+ },
223
+ procfile: {
224
+ // worker: "node ./bin/worker.js"
225
+ },
226
+ vite: {
227
+ proxy: [
228
+ // {
229
+ // id: "example-socket-proxy",
230
+ // path: "/socket.io",
231
+ // changeOrigin: true,
232
+ // ws: true,
233
+ // target: "http://localhost:3000",
234
+ // reason: "Explain why this proxy is needed."
235
+ // }
236
+ ]
237
+ },
238
+ text: [
239
+ // {
240
+ // op: "upsert-env",
241
+ // file: ".env",
242
+ // key: "EXAMPLE_ENV",
243
+ // value: "\${option:example-option}",
244
+ // reason: "Explain why this env var is needed.",
245
+ // category: "runtime-config",
246
+ // id: "example-env"
247
+ // }
248
+ ],
249
+ files: [
250
+ // {
251
+ // from: "templates/src/pages/example/index.vue",
252
+ // to: "src/pages/example/index.vue",
253
+ // reason: "Explain what is scaffolded.",
254
+ // category: "example",
255
+ // id: "example-file"
256
+ // }
257
+ ]
258
+ }
259
+ });
260
+ `;
261
+ }
262
+
263
+ function createLocalPackageScaffoldFiles({ packageId, packageDescription }) {
264
+ return [
265
+ {
266
+ relativePath: "package.json",
267
+ content: `${JSON.stringify(
268
+ {
269
+ name: packageId,
270
+ version: "0.1.0",
271
+ private: true,
272
+ type: "module",
273
+ exports: {
274
+ ".": "./src/index.js",
275
+ "./client": "./src/client/index.js",
276
+ "./server": "./src/server/index.js",
277
+ "./shared": "./src/shared/index.js"
278
+ }
279
+ },
280
+ null,
281
+ 2
282
+ )}\n`
283
+ },
284
+ {
285
+ relativePath: "package.descriptor.mjs",
286
+ content: createLocalPackageDescriptorTemplate({
287
+ packageId,
288
+ description: packageDescription
289
+ })
290
+ },
291
+ {
292
+ relativePath: "src/index.js",
293
+ content: "export {};\n"
294
+ },
295
+ {
296
+ relativePath: "src/server/index.js",
297
+ content: "export {};\n"
298
+ },
299
+ {
300
+ relativePath: "src/client/index.js",
301
+ content: [
302
+ "const routeComponents = Object.freeze({});",
303
+ "",
304
+ "async function bootClient({ logger } = {}) {",
305
+ " if (logger && typeof logger.debug === \"function\") {",
306
+ ` logger.debug({ packageId: ${JSON.stringify(packageId)} }, "bootClient executed.");`,
307
+ " }",
308
+ "}",
309
+ "",
310
+ "export { routeComponents, bootClient };",
311
+ ""
312
+ ].join("\n")
313
+ },
314
+ {
315
+ relativePath: "src/shared/index.js",
316
+ content: "export {};\n"
317
+ },
318
+ {
319
+ relativePath: "README.md",
320
+ content: [
321
+ `# ${packageId}`,
322
+ "",
323
+ "App-local JSKIT module scaffold.",
324
+ "",
325
+ "## Next Steps",
326
+ "",
327
+ "- Define runtime providers in `package.descriptor.mjs`.",
328
+ "- Add client/server exports under `src/`.",
329
+ "- Keep package version in sync with descriptor version.",
330
+ ""
331
+ ].join("\n")
332
+ }
333
+ ];
334
+ }
335
+
336
+ function resolveLocalDependencyOrder(initialPackageIds, packageRegistry) {
337
+ const ordered = [];
338
+ const visited = new Set();
339
+ const visiting = new Set();
340
+ const externalDependencies = new Set();
341
+
342
+ function visit(packageId, lineage = []) {
343
+ if (visited.has(packageId)) {
344
+ return;
345
+ }
346
+ if (visiting.has(packageId)) {
347
+ const cyclePath = [...lineage, packageId].join(" -> ");
348
+ throw createCliError(`Dependency cycle detected: ${cyclePath}`);
349
+ }
350
+
351
+ const packageEntry = packageRegistry.get(packageId);
352
+ if (!packageEntry) {
353
+ throw createCliError(`Unknown package: ${packageId}`);
354
+ }
355
+
356
+ visiting.add(packageId);
357
+ for (const dependencyId of ensureArray(packageEntry.descriptor.dependsOn).map((value) => String(value))) {
358
+ if (packageRegistry.has(dependencyId)) {
359
+ visit(dependencyId, [...lineage, packageId]);
360
+ } else {
361
+ externalDependencies.add(dependencyId);
362
+ }
363
+ }
364
+ visiting.delete(packageId);
365
+ visited.add(packageId);
366
+ ordered.push(packageId);
367
+ }
368
+
369
+ for (const packageId of initialPackageIds) {
370
+ visit(packageId);
371
+ }
372
+
373
+ return {
374
+ ordered,
375
+ externalDependencies: sortStrings([...externalDependencies])
376
+ };
377
+ }
378
+
379
+ export {
380
+ resolvePackageDependencySpecifier,
381
+ normalizeJskitDependencySpecifier,
382
+ normalizePackageNameSegment,
383
+ normalizeScopeName,
384
+ resolveDefaultLocalScopeFromAppName,
385
+ normalizeRelativePosixPath,
386
+ toFileDependencySpecifier,
387
+ resolveLocalPackageId,
388
+ createLocalPackageScaffoldFiles,
389
+ resolveLocalDependencyOrder
390
+ };
@@ -0,0 +1,9 @@
1
+ export {
2
+ applyFileMutations,
3
+ preflightFileMutationTemplateContexts
4
+ } from "./mutations/fileMutations.js";
5
+
6
+ export {
7
+ applyTextMutations,
8
+ resolvePositioningMutations
9
+ } from "./mutations/textMutations.js";
@@ -0,0 +1,285 @@
1
+ import { createCliError } from "../shared/cliError.js";
2
+ import {
3
+ ensureArray,
4
+ ensureObject
5
+ } from "../shared/collectionUtils.js";
6
+
7
+ function normalizeMutationExtension(value) {
8
+ const extension = String(value || "").trim();
9
+ if (!extension) {
10
+ return "";
11
+ }
12
+ if (extension.startsWith(".")) {
13
+ return extension;
14
+ }
15
+ return `.${extension}`;
16
+ }
17
+
18
+ function normalizeTemplateContextRecord(value) {
19
+ const record = ensureObject(value);
20
+ const entrypoint = String(record.entrypoint || "").trim();
21
+ const exportName = String(record.export || "").trim();
22
+ if (!entrypoint && !exportName) {
23
+ return null;
24
+ }
25
+
26
+ return Object.freeze({
27
+ entrypoint,
28
+ export: exportName || "buildTemplateContext"
29
+ });
30
+ }
31
+
32
+ function normalizeFileMutationRecord(value) {
33
+ const record = ensureObject(value);
34
+ const op = String(record.op || "copy-file").trim().toLowerCase() || "copy-file";
35
+ return {
36
+ op,
37
+ from: String(record.from || "").trim(),
38
+ to: String(record.to || "").trim(),
39
+ toSurface: String(record.toSurface || "").trim(),
40
+ toSurfacePath: String(record.toSurfacePath || "").trim(),
41
+ toSurfaceRoot: record.toSurfaceRoot === true,
42
+ toDir: String(record.toDir || "").trim(),
43
+ extension: normalizeMutationExtension(record.extension),
44
+ preserveOnRemove: record.preserveOnRemove === true,
45
+ id: String(record.id || "").trim(),
46
+ category: String(record.category || "").trim(),
47
+ reason: String(record.reason || "").trim(),
48
+ templateContext: normalizeTemplateContextRecord(record.templateContext),
49
+ when: normalizeMutationWhen(record.when)
50
+ };
51
+ }
52
+
53
+ function normalizeMutationWhen(value) {
54
+ const source = ensureObject(value);
55
+ const allConditions = ensureArray(source.all)
56
+ .map((entry) => normalizeMutationWhen(entry))
57
+ .filter(Boolean);
58
+ if (allConditions.length > 0) {
59
+ return {
60
+ all: allConditions
61
+ };
62
+ }
63
+
64
+ const option = String(source.option || "").trim();
65
+ const config = String(source.config || "").trim();
66
+ const equals = String(source.equals || "").trim();
67
+ const notEquals = String(source.notEquals || "").trim();
68
+ const contains = String(source.contains || "").trim();
69
+ const notContains = String(source.notContains || "").trim();
70
+ const includes = ensureArray(source.in).map((entry) => String(entry || "").trim()).filter(Boolean);
71
+ const excludes = ensureArray(source.notIn).map((entry) => String(entry || "").trim()).filter(Boolean);
72
+
73
+ if (!option && !config) {
74
+ return null;
75
+ }
76
+
77
+ return {
78
+ option,
79
+ config,
80
+ equals,
81
+ notEquals,
82
+ contains,
83
+ notContains,
84
+ includes,
85
+ excludes
86
+ };
87
+ }
88
+
89
+ function readObjectPath(source, rawPath) {
90
+ const valueSource = ensureObject(source);
91
+ const fullPath = String(rawPath || "").trim();
92
+ if (!fullPath) {
93
+ return undefined;
94
+ }
95
+
96
+ if (Object.prototype.hasOwnProperty.call(valueSource, fullPath)) {
97
+ return valueSource[fullPath];
98
+ }
99
+
100
+ const segments = fullPath
101
+ .split(".")
102
+ .map((entry) => String(entry || "").trim())
103
+ .filter(Boolean);
104
+ if (segments.length < 1) {
105
+ return undefined;
106
+ }
107
+
108
+ let cursor = valueSource;
109
+ for (const segment of segments) {
110
+ if (!cursor || typeof cursor !== "object" || Array.isArray(cursor)) {
111
+ return undefined;
112
+ }
113
+ if (!Object.prototype.hasOwnProperty.call(cursor, segment)) {
114
+ return undefined;
115
+ }
116
+ cursor = cursor[segment];
117
+ }
118
+
119
+ return cursor;
120
+ }
121
+
122
+ function normalizeWhenSourceValue(value) {
123
+ if (value == null) {
124
+ return "";
125
+ }
126
+ if (typeof value === "string") {
127
+ return value.trim();
128
+ }
129
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
130
+ return String(value).trim();
131
+ }
132
+ return "";
133
+ }
134
+
135
+ function normalizeWhenComparisonValue(value) {
136
+ const normalizedValue = normalizeWhenSourceValue(value);
137
+ if (!normalizedValue.includes(",")) {
138
+ return normalizedValue;
139
+ }
140
+
141
+ return normalizedValue
142
+ .split(",")
143
+ .map((entry) => String(entry || "").trim())
144
+ .filter(Boolean)
145
+ .join(",");
146
+ }
147
+
148
+ function splitWhenComparisonTokens(value) {
149
+ return normalizeWhenComparisonValue(value)
150
+ .split(",")
151
+ .map((entry) => String(entry || "").trim())
152
+ .filter(Boolean);
153
+ }
154
+
155
+ function matchesWhenComparisonValue(optionValue, optionTokens, expectedValue) {
156
+ const expected = normalizeWhenComparisonValue(expectedValue);
157
+ if (!expected) {
158
+ return false;
159
+ }
160
+
161
+ const expectedTokens = splitWhenComparisonTokens(expected);
162
+ if (expectedTokens.length > 1) {
163
+ return optionValue === expected;
164
+ }
165
+
166
+ if (optionTokens.length > 1) {
167
+ return optionTokens.includes(expectedTokens[0]);
168
+ }
169
+
170
+ return optionValue === expectedTokens[0];
171
+ }
172
+
173
+ function resolveWhenConfigValue(configContext = {}, configPath = "") {
174
+ const normalizedPath = String(configPath || "").trim();
175
+ if (!normalizedPath) {
176
+ return "";
177
+ }
178
+
179
+ const publicConfig = ensureObject(configContext.public);
180
+ const serverConfig = ensureObject(configContext.server);
181
+ const mergedConfig = ensureObject(configContext.merged);
182
+ if (normalizedPath === "public") {
183
+ return publicConfig;
184
+ }
185
+ if (normalizedPath === "server") {
186
+ return serverConfig;
187
+ }
188
+ if (normalizedPath === "merged") {
189
+ return mergedConfig;
190
+ }
191
+
192
+ if (normalizedPath.startsWith("public.")) {
193
+ return readObjectPath(publicConfig, normalizedPath.slice("public.".length));
194
+ }
195
+ if (normalizedPath.startsWith("server.")) {
196
+ return readObjectPath(serverConfig, normalizedPath.slice("server.".length));
197
+ }
198
+ if (normalizedPath.startsWith("merged.")) {
199
+ return readObjectPath(mergedConfig, normalizedPath.slice("merged.".length));
200
+ }
201
+
202
+ return readObjectPath(mergedConfig, normalizedPath);
203
+ }
204
+
205
+ function shouldApplyMutationWhen(
206
+ when,
207
+ {
208
+ options = {},
209
+ configContext = {},
210
+ packageId = "",
211
+ mutationContext = "mutation"
212
+ } = {}
213
+ ) {
214
+ if (!when || typeof when !== "object") {
215
+ return true;
216
+ }
217
+
218
+ const allConditions = ensureArray(when.all).filter((entry) => entry && typeof entry === "object");
219
+ if (allConditions.length > 0) {
220
+ return allConditions.every((entry) =>
221
+ shouldApplyMutationWhen(entry, {
222
+ options,
223
+ configContext,
224
+ packageId,
225
+ mutationContext
226
+ })
227
+ );
228
+ }
229
+
230
+ const optionName = String(when.option || "").trim();
231
+ const configPath = String(when.config || "").trim();
232
+ if (!optionName && !configPath) {
233
+ return true;
234
+ }
235
+ if (optionName && configPath) {
236
+ const packagePrefix = packageId ? `${packageId} ` : "";
237
+ throw createCliError(
238
+ `Invalid ${packagePrefix}${mutationContext}: when cannot declare both "option" and "config".`
239
+ );
240
+ }
241
+
242
+ const sourceValue = optionName
243
+ ? readObjectPath(options, optionName)
244
+ : resolveWhenConfigValue(configContext, configPath);
245
+ const optionValue = normalizeWhenComparisonValue(sourceValue);
246
+ const optionTokens = splitWhenComparisonTokens(optionValue);
247
+ const equals = normalizeWhenComparisonValue(when.equals);
248
+ const notEquals = normalizeWhenComparisonValue(when.notEquals);
249
+ const contains = normalizeWhenComparisonValue(when.contains);
250
+ const notContains = normalizeWhenComparisonValue(when.notContains);
251
+ const includes = ensureArray(when.includes).map((entry) => normalizeWhenComparisonValue(entry)).filter(Boolean);
252
+ const excludes = ensureArray(when.excludes).map((entry) => normalizeWhenComparisonValue(entry)).filter(Boolean);
253
+
254
+ if (equals && optionValue !== equals) {
255
+ return false;
256
+ }
257
+ if (notEquals && optionValue === notEquals) {
258
+ return false;
259
+ }
260
+ if (contains && !optionValue.includes(contains)) {
261
+ return false;
262
+ }
263
+ if (notContains && optionValue.includes(notContains)) {
264
+ return false;
265
+ }
266
+ if (includes.length > 0 && !includes.some((entry) => matchesWhenComparisonValue(optionValue, optionTokens, entry))) {
267
+ return false;
268
+ }
269
+ if (excludes.length > 0 && excludes.some((entry) => matchesWhenComparisonValue(optionValue, optionTokens, entry))) {
270
+ return false;
271
+ }
272
+
273
+ return true;
274
+ }
275
+
276
+ export {
277
+ normalizeMutationExtension,
278
+ normalizeTemplateContextRecord,
279
+ normalizeFileMutationRecord,
280
+ normalizeMutationWhen,
281
+ readObjectPath,
282
+ normalizeWhenSourceValue,
283
+ resolveWhenConfigValue,
284
+ shouldApplyMutationWhen
285
+ };