@jskit-ai/jskit-cli 0.2.26 → 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 -4853
  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,247 @@
1
+ import {
2
+ readFile
3
+ } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { createCliError } from "../../shared/cliError.js";
6
+ import {
7
+ ensureArray,
8
+ ensureObject
9
+ } from "../../shared/collectionUtils.js";
10
+ import {
11
+ normalizeFileMutationRecord,
12
+ shouldApplyMutationWhen
13
+ } from "../mutationWhen.js";
14
+ import {
15
+ normalizeRelativePath,
16
+ hashBuffer,
17
+ fileExists,
18
+ readFileBufferIfExists,
19
+ loadMutationWhenConfigContext
20
+ } from "../ioAndMigrations.js";
21
+ import {
22
+ copyTemplateFile,
23
+ interpolateFileMutationRecord,
24
+ resolveTemplateContextReplacementsForMutation
25
+ } from "./templateContext.js";
26
+ import { resolveSurfaceTargetPathsForMutation } from "./surfaceTargets.js";
27
+ import { applyInstallMigrationMutation } from "./installMigrationMutation.js";
28
+
29
+ async function applyFileMutations(
30
+ packageEntry,
31
+ options,
32
+ appRoot,
33
+ fileMutations,
34
+ managedFiles,
35
+ managedMigrations,
36
+ touchedFiles,
37
+ warnings = [],
38
+ precomputedTemplateContextByMutationIndex = null
39
+ ) {
40
+ const mutationList = ensureArray(fileMutations);
41
+ const managedMigrationById = new Map();
42
+ for (const managedMigrationValue of ensureArray(managedMigrations)) {
43
+ const managedMigration = ensureObject(managedMigrationValue);
44
+ const migrationId = String(managedMigration.id || "").trim();
45
+ if (!migrationId) {
46
+ continue;
47
+ }
48
+ managedMigrationById.set(migrationId, managedMigration);
49
+ }
50
+
51
+ for (const [mutationIndex, mutationValue] of mutationList.entries()) {
52
+ const normalizedMutation = normalizeFileMutationRecord(mutationValue);
53
+ const requiresConfigContext = Boolean(normalizedMutation.when?.config || normalizedMutation.toSurface);
54
+ const configContext = requiresConfigContext ? await loadMutationWhenConfigContext(appRoot) : {};
55
+ if (
56
+ !shouldApplyMutationWhen(normalizedMutation.when, {
57
+ options,
58
+ configContext,
59
+ packageId: packageEntry.packageId,
60
+ mutationContext: "files mutation"
61
+ })
62
+ ) {
63
+ continue;
64
+ }
65
+
66
+ const mutation = interpolateFileMutationRecord(normalizedMutation, options, packageEntry.packageId);
67
+ const operation = mutation.op || "copy-file";
68
+
69
+ if (operation === "install-migration") {
70
+ await applyInstallMigrationMutation({
71
+ packageEntry,
72
+ mutation,
73
+ rawMutation: mutationValue,
74
+ mutationIndex,
75
+ options,
76
+ appRoot,
77
+ managedMigrations,
78
+ managedMigrationById,
79
+ touchedFiles,
80
+ warnings,
81
+ precomputedTemplateContextByMutationIndex
82
+ });
83
+ continue;
84
+ }
85
+
86
+ if (operation !== "copy-file") {
87
+ throw createCliError(`Unsupported files mutation op \"${operation}\" in ${packageEntry.packageId}.`);
88
+ }
89
+
90
+ const from = mutation.from;
91
+ const to = mutation.to;
92
+ const toSurface = mutation.toSurface;
93
+ if (to && toSurface) {
94
+ throw createCliError(
95
+ `Invalid files mutation in ${packageEntry.packageId}: "to" and "toSurface" cannot both be set.`
96
+ );
97
+ }
98
+ if (!from || (!to && !toSurface)) {
99
+ throw createCliError(
100
+ `Invalid files mutation in ${packageEntry.packageId}: "from" plus one destination ("to" or "toSurface") are required.`
101
+ );
102
+ }
103
+
104
+ const sourcePath = path.join(packageEntry.rootDir, from);
105
+ if (!(await fileExists(sourcePath))) {
106
+ throw createCliError(`Missing template source ${sourcePath} for ${packageEntry.packageId}.`);
107
+ }
108
+
109
+ const targetPaths = toSurface
110
+ ? resolveSurfaceTargetPathsForMutation({
111
+ appRoot,
112
+ packageId: packageEntry.packageId,
113
+ mutation,
114
+ configContext
115
+ })
116
+ : [path.join(appRoot, to)];
117
+ const hasPrecomputedTemplateContext =
118
+ precomputedTemplateContextByMutationIndex instanceof Map &&
119
+ precomputedTemplateContextByMutationIndex.has(mutationIndex);
120
+ const templateContextReplacements = hasPrecomputedTemplateContext
121
+ ? precomputedTemplateContextByMutationIndex.get(mutationIndex)
122
+ : await resolveTemplateContextReplacementsForMutation({
123
+ packageEntry,
124
+ mutation,
125
+ options,
126
+ appRoot,
127
+ sourcePath,
128
+ targetPaths
129
+ });
130
+
131
+ for (const targetPath of targetPaths) {
132
+ const previous = await readFileBufferIfExists(targetPath);
133
+ await copyTemplateFile(
134
+ sourcePath,
135
+ targetPath,
136
+ options,
137
+ packageEntry.packageId,
138
+ `${mutation.id || to || from}.source`,
139
+ templateContextReplacements
140
+ );
141
+ const nextBuffer = await readFile(targetPath);
142
+
143
+ managedFiles.push({
144
+ path: normalizeRelativePath(appRoot, targetPath),
145
+ hash: hashBuffer(nextBuffer),
146
+ hadPrevious: previous.exists,
147
+ previousContentBase64: previous.exists ? previous.buffer.toString("base64") : "",
148
+ preserveOnRemove: mutation.preserveOnRemove,
149
+ reason: mutation.reason,
150
+ category: mutation.category,
151
+ id: mutation.id
152
+ });
153
+ touchedFiles.add(normalizeRelativePath(appRoot, targetPath));
154
+ }
155
+ }
156
+ }
157
+
158
+ async function preflightFileMutationTemplateContexts(
159
+ packageEntry,
160
+ options,
161
+ appRoot,
162
+ fileMutations
163
+ ) {
164
+ const mutationList = ensureArray(fileMutations);
165
+ const replacementsByMutationIndex = new Map();
166
+
167
+ for (const [mutationIndex, mutationValue] of mutationList.entries()) {
168
+ const normalizedMutation = normalizeFileMutationRecord(mutationValue);
169
+ const requiresConfigContext = Boolean(normalizedMutation.when?.config || normalizedMutation.toSurface);
170
+ const configContext = requiresConfigContext ? await loadMutationWhenConfigContext(appRoot) : {};
171
+ if (
172
+ !shouldApplyMutationWhen(normalizedMutation.when, {
173
+ options,
174
+ configContext,
175
+ packageId: packageEntry.packageId,
176
+ mutationContext: "files mutation"
177
+ })
178
+ ) {
179
+ continue;
180
+ }
181
+
182
+ const mutation = interpolateFileMutationRecord(normalizedMutation, options, packageEntry.packageId);
183
+ const templateContext = ensureObject(mutation.templateContext);
184
+ if (Object.keys(templateContext).length < 1) {
185
+ continue;
186
+ }
187
+
188
+ const operation = mutation.op || "copy-file";
189
+ if (operation !== "copy-file" && operation !== "install-migration") {
190
+ continue;
191
+ }
192
+
193
+ const from = mutation.from;
194
+ const to = mutation.to;
195
+ const toSurface = mutation.toSurface;
196
+ if (!from) {
197
+ throw createCliError(
198
+ `Invalid files mutation in ${packageEntry.packageId}: "from" is required.`
199
+ );
200
+ }
201
+ if (operation === "copy-file") {
202
+ if (to && toSurface) {
203
+ throw createCliError(
204
+ `Invalid files mutation in ${packageEntry.packageId}: "to" and "toSurface" cannot both be set.`
205
+ );
206
+ }
207
+ if (!to && !toSurface) {
208
+ throw createCliError(
209
+ `Invalid files mutation in ${packageEntry.packageId}: "from" plus one destination ("to" or "toSurface") are required.`
210
+ );
211
+ }
212
+ }
213
+
214
+ const sourcePath = path.join(packageEntry.rootDir, from);
215
+ if (!(await fileExists(sourcePath))) {
216
+ throw createCliError(`Missing template source ${sourcePath} for ${packageEntry.packageId}.`);
217
+ }
218
+
219
+ const targetPaths = operation === "copy-file"
220
+ ? toSurface
221
+ ? resolveSurfaceTargetPathsForMutation({
222
+ appRoot,
223
+ packageId: packageEntry.packageId,
224
+ mutation,
225
+ configContext
226
+ })
227
+ : [path.join(appRoot, to)]
228
+ : [path.join(appRoot, mutation.toDir || "migrations")];
229
+ const replacements = await resolveTemplateContextReplacementsForMutation({
230
+ packageEntry,
231
+ mutation,
232
+ options,
233
+ appRoot,
234
+ sourcePath,
235
+ targetPaths,
236
+ mutationContext: "files mutation"
237
+ });
238
+ replacementsByMutationIndex.set(mutationIndex, replacements);
239
+ }
240
+
241
+ return replacementsByMutationIndex;
242
+ }
243
+
244
+ export {
245
+ applyFileMutations,
246
+ preflightFileMutationTemplateContexts
247
+ };
@@ -0,0 +1,213 @@
1
+ import {
2
+ mkdir,
3
+ readFile,
4
+ writeFile
5
+ } from "node:fs/promises";
6
+ import path from "node:path";
7
+ import { createCliError } from "../../shared/cliError.js";
8
+ import { ensureObject } from "../../shared/collectionUtils.js";
9
+ import { interpolateOptionValue } from "../../shared/optionInterpolation.js";
10
+ import {
11
+ hashBuffer,
12
+ normalizeMigrationExtension,
13
+ normalizeMigrationId,
14
+ resolveAppRelativePathWithinRoot,
15
+ formatMigrationTimestamp,
16
+ buildManagedMigrationRelativePath,
17
+ findExistingManagedMigrationPathById,
18
+ upsertManagedMigrationRecord,
19
+ fileExists
20
+ } from "../ioAndMigrations.js";
21
+ import { normalizeRelativePosixPath } from "../localPackageSupport.js";
22
+ import {
23
+ applyTemplateContextReplacements,
24
+ resolveTemplateContextReplacementsForMutation
25
+ } from "./templateContext.js";
26
+
27
+ async function applyInstallMigrationMutation({
28
+ packageEntry,
29
+ mutation,
30
+ rawMutation,
31
+ mutationIndex,
32
+ options,
33
+ appRoot,
34
+ managedMigrations,
35
+ managedMigrationById,
36
+ touchedFiles,
37
+ warnings,
38
+ precomputedTemplateContextByMutationIndex
39
+ } = {}) {
40
+ if (Object.hasOwn(ensureObject(rawMutation), "preserveOnRemove")) {
41
+ warnings.push(
42
+ `${packageEntry.packageId}: install-migration ignores preserveOnRemove (migrations are always preserved on remove).`
43
+ );
44
+ }
45
+
46
+ const from = mutation.from;
47
+ const toDir = mutation.toDir || "migrations";
48
+ if (!from) {
49
+ throw createCliError(`Invalid install-migration mutation in ${packageEntry.packageId}: \"from\" is required.`);
50
+ }
51
+ const migrationId = normalizeMigrationId(mutation.id, packageEntry.packageId);
52
+
53
+ const sourcePath = path.join(packageEntry.rootDir, from);
54
+ if (!(await fileExists(sourcePath))) {
55
+ throw createCliError(`Missing migration template source ${sourcePath} for ${packageEntry.packageId}.`);
56
+ }
57
+
58
+ const hasPrecomputedTemplateContext =
59
+ precomputedTemplateContextByMutationIndex instanceof Map &&
60
+ precomputedTemplateContextByMutationIndex.has(mutationIndex);
61
+ const templateContextReplacements = hasPrecomputedTemplateContext
62
+ ? precomputedTemplateContextByMutationIndex.get(mutationIndex)
63
+ : await resolveTemplateContextReplacementsForMutation({
64
+ packageEntry,
65
+ mutation,
66
+ options,
67
+ appRoot,
68
+ sourcePath,
69
+ targetPaths: [path.join(appRoot, toDir)]
70
+ });
71
+
72
+ const sourceContent = await readFile(sourcePath, "utf8");
73
+ let renderedSourceContent = sourceContent.includes("${")
74
+ ? interpolateOptionValue(sourceContent, options, packageEntry.packageId, `${mutation.id || from}.source`)
75
+ : sourceContent;
76
+ if (templateContextReplacements) {
77
+ renderedSourceContent = applyTemplateContextReplacements(renderedSourceContent, templateContextReplacements);
78
+ }
79
+ const sourceExtension = normalizeMigrationExtension(path.extname(from), ".cjs");
80
+ const extension = normalizeMigrationExtension(mutation.extension, sourceExtension);
81
+ const sourceHash = hashBuffer(Buffer.from(renderedSourceContent, "utf8"));
82
+
83
+ const existingManagedRecord = managedMigrationById.get(migrationId);
84
+ if (existingManagedRecord) {
85
+ const existingManagedPath = normalizeRelativePosixPath(String(existingManagedRecord.path || "").trim());
86
+ if (!existingManagedPath) {
87
+ throw createCliError(
88
+ `${packageEntry.packageId}: managed migration ${migrationId} is missing path in lock.`
89
+ );
90
+ }
91
+ const resolvedManagedPath = resolveAppRelativePathWithinRoot(
92
+ appRoot,
93
+ existingManagedPath,
94
+ `${packageEntry.packageId} managed migration path for ${migrationId}`
95
+ );
96
+ const relativePath = resolvedManagedPath.relativePath;
97
+ const absolutePath = resolvedManagedPath.absolutePath;
98
+ let existingSourceHash = String(existingManagedRecord.hash || "").trim();
99
+ if (!existingSourceHash && existingManagedPath && (await fileExists(absolutePath))) {
100
+ const existingSource = await readFile(absolutePath);
101
+ existingSourceHash = hashBuffer(existingSource);
102
+ }
103
+
104
+ if (existingSourceHash && existingSourceHash !== sourceHash) {
105
+ throw createCliError(
106
+ `${packageEntry.packageId}: migration ${migrationId} changed after install. Keep migrations immutable and create a new migration id.`
107
+ );
108
+ }
109
+
110
+ if (!(await fileExists(absolutePath))) {
111
+ await mkdir(path.dirname(absolutePath), { recursive: true });
112
+ await writeFile(absolutePath, renderedSourceContent, "utf8");
113
+ touchedFiles.add(relativePath);
114
+ }
115
+
116
+ const nextManagedRecord = {
117
+ ...existingManagedRecord,
118
+ id: migrationId,
119
+ path: relativePath,
120
+ hash: sourceHash,
121
+ skipped: true,
122
+ reason: mutation.reason || String(existingManagedRecord.reason || ""),
123
+ category: mutation.category || String(existingManagedRecord.category || "")
124
+ };
125
+ managedMigrationById.set(migrationId, nextManagedRecord);
126
+ upsertManagedMigrationRecord(managedMigrations, nextManagedRecord);
127
+ warnings.push(
128
+ `${packageEntry.packageId}: skipped migration ${migrationId} (already managed at ${nextManagedRecord.path}).`
129
+ );
130
+ return;
131
+ }
132
+
133
+ const existingPathById = await findExistingManagedMigrationPathById({
134
+ appRoot,
135
+ toDir,
136
+ packageId: packageEntry.packageId,
137
+ migrationId,
138
+ extension
139
+ });
140
+ if (existingPathById) {
141
+ const existingSource = await readFile(existingPathById.absolutePath);
142
+ const existingSourceHash = hashBuffer(existingSource);
143
+ if (existingSourceHash !== sourceHash) {
144
+ throw createCliError(
145
+ `${packageEntry.packageId}: migration ${migrationId} changed after install. Keep migrations immutable and create a new migration id.`
146
+ );
147
+ }
148
+ const nextManagedRecord = {
149
+ id: migrationId,
150
+ path: existingPathById.relativePath,
151
+ hash: sourceHash,
152
+ skipped: true,
153
+ reason: mutation.reason,
154
+ category: mutation.category
155
+ };
156
+ managedMigrationById.set(migrationId, nextManagedRecord);
157
+ upsertManagedMigrationRecord(managedMigrations, nextManagedRecord);
158
+ warnings.push(
159
+ `${packageEntry.packageId}: skipped migration ${migrationId} (already exists at ${nextManagedRecord.path}).`
160
+ );
161
+ return;
162
+ }
163
+
164
+ const baseNowMs = Date.now();
165
+ let targetPath = null;
166
+ for (let secondOffset = 0; secondOffset < 86400; secondOffset += 1) {
167
+ const timestamp = formatMigrationTimestamp(new Date(baseNowMs + secondOffset * 1000));
168
+ const candidateRelativePath = buildManagedMigrationRelativePath({
169
+ toDir,
170
+ packageId: packageEntry.packageId,
171
+ migrationId,
172
+ extension,
173
+ timestamp
174
+ });
175
+ const candidatePath = resolveAppRelativePathWithinRoot(
176
+ appRoot,
177
+ candidateRelativePath,
178
+ `${packageEntry.packageId} migration path for ${migrationId}`
179
+ );
180
+ if (await fileExists(candidatePath.absolutePath)) {
181
+ continue;
182
+ }
183
+ targetPath = candidatePath;
184
+ break;
185
+ }
186
+
187
+ if (!targetPath) {
188
+ throw createCliError(
189
+ `${packageEntry.packageId}: unable to allocate migration filename for ${migrationId} in ${toDir}.`
190
+ );
191
+ }
192
+
193
+ const relativePath = targetPath.relativePath;
194
+ const absolutePath = targetPath.absolutePath;
195
+ await mkdir(path.dirname(absolutePath), { recursive: true });
196
+ await writeFile(absolutePath, renderedSourceContent, "utf8");
197
+ touchedFiles.add(relativePath);
198
+
199
+ const nextManagedRecord = {
200
+ id: migrationId,
201
+ path: relativePath,
202
+ hash: sourceHash,
203
+ skipped: false,
204
+ reason: mutation.reason,
205
+ category: mutation.category
206
+ };
207
+ managedMigrationById.set(migrationId, nextManagedRecord);
208
+ upsertManagedMigrationRecord(managedMigrations, nextManagedRecord);
209
+ }
210
+
211
+ export {
212
+ applyInstallMigrationMutation
213
+ };
@@ -0,0 +1,12 @@
1
+ function normalizeMutationRelativeFilePath(value = "") {
2
+ return String(value || "")
3
+ .trim()
4
+ .replace(/\\/g, "/")
5
+ .replace(/\/{2,}/g, "/")
6
+ .replace(/^\.\/+/, "")
7
+ .replace(/^\/+/, "");
8
+ }
9
+
10
+ export {
11
+ normalizeMutationRelativeFilePath
12
+ };
@@ -0,0 +1,155 @@
1
+ import path from "node:path";
2
+ import { createCliError } from "../../shared/cliError.js";
3
+ import { ensureObject } from "../../shared/collectionUtils.js";
4
+ import {
5
+ normalizeSurfaceIdForMutation,
6
+ parseSurfaceIdListForMutation
7
+ } from "../packageOptions.js";
8
+
9
+ function normalizeSurfacePagesRootForMutation(value = "") {
10
+ const rawValue = String(value || "").trim();
11
+ if (!rawValue || rawValue === "/") {
12
+ return "";
13
+ }
14
+
15
+ return rawValue
16
+ .replace(/\\/g, "/")
17
+ .replace(/\/{2,}/g, "/")
18
+ .replace(/^\/+|\/+$/g, "");
19
+ }
20
+
21
+ function normalizeSurfacePathForMutation(value = "", { context = "toSurfacePath" } = {}) {
22
+ const rawValue = String(value || "").trim();
23
+ if (!rawValue) {
24
+ return "";
25
+ }
26
+
27
+ const normalized = rawValue
28
+ .replace(/\\/g, "/")
29
+ .replace(/\/{2,}/g, "/")
30
+ .replace(/^\.\/+/, "")
31
+ .replace(/^\/+/, "");
32
+ const segments = normalized.split("/");
33
+ const materializedSegments = [];
34
+ for (const segmentValue of segments) {
35
+ const segment = String(segmentValue || "").trim();
36
+ if (!segment || segment === ".") {
37
+ continue;
38
+ }
39
+ if (segment === "..") {
40
+ throw createCliError(`Invalid ${context}: path traversal is not allowed.`);
41
+ }
42
+ materializedSegments.push(segment);
43
+ }
44
+
45
+ return materializedSegments.join("/");
46
+ }
47
+
48
+ function resolveSurfaceDefinitionFromConfigForMutation({
49
+ configContext = {},
50
+ surfaceId = "",
51
+ packageId = ""
52
+ } = {}) {
53
+ const normalizedSurfaceId = normalizeSurfaceIdForMutation(surfaceId);
54
+ if (!normalizedSurfaceId) {
55
+ throw createCliError(`Invalid files mutation in ${packageId}: "toSurface" is required when using surface targeting.`);
56
+ }
57
+
58
+ const publicConfig = ensureObject(configContext.public);
59
+ const mergedConfig = ensureObject(configContext.merged);
60
+ const sourceDefinitions = ensureObject(publicConfig.surfaceDefinitions);
61
+ const fallbackDefinitions = ensureObject(mergedConfig.surfaceDefinitions);
62
+ const surfaceDefinitions =
63
+ Object.keys(sourceDefinitions).length > 0 ? sourceDefinitions : fallbackDefinitions;
64
+
65
+ for (const [key, value] of Object.entries(surfaceDefinitions)) {
66
+ const definition = ensureObject(value);
67
+ const definitionId = normalizeSurfaceIdForMutation(definition.id || key);
68
+ if (definitionId !== normalizedSurfaceId) {
69
+ continue;
70
+ }
71
+ if (definition.enabled === false) {
72
+ throw createCliError(
73
+ `Invalid files mutation in ${packageId}: surface "${normalizedSurfaceId}" is disabled.`
74
+ );
75
+ }
76
+ if (!Object.prototype.hasOwnProperty.call(definition, "pagesRoot")) {
77
+ throw createCliError(
78
+ `Invalid files mutation in ${packageId}: surface "${normalizedSurfaceId}" is missing pagesRoot in config/public.js.`
79
+ );
80
+ }
81
+
82
+ return Object.freeze({
83
+ ...definition,
84
+ id: definitionId,
85
+ pagesRoot: normalizeSurfacePagesRootForMutation(definition.pagesRoot)
86
+ });
87
+ }
88
+
89
+ throw createCliError(
90
+ `Invalid files mutation in ${packageId}: unknown surface "${normalizedSurfaceId}" in config/public.js.`
91
+ );
92
+ }
93
+
94
+ function resolveSurfaceTargetPathsForMutation({
95
+ appRoot,
96
+ packageId,
97
+ mutation,
98
+ configContext
99
+ } = {}) {
100
+ const normalizedSurfaceIds = parseSurfaceIdListForMutation(mutation.toSurface);
101
+ if (normalizedSurfaceIds.length < 1) {
102
+ throw createCliError(`Invalid files mutation in ${packageId}: "toSurface" is required when using surface targeting.`);
103
+ }
104
+
105
+ if (mutation.toSurfaceRoot === true) {
106
+ if (String(mutation.toSurfacePath || "").trim()) {
107
+ throw createCliError(
108
+ `Invalid files mutation in ${packageId}: "toSurfacePath" cannot be combined with "toSurfaceRoot".`
109
+ );
110
+ }
111
+
112
+ const targetPaths = [];
113
+ for (const surfaceId of normalizedSurfaceIds) {
114
+ const definition = resolveSurfaceDefinitionFromConfigForMutation({
115
+ configContext,
116
+ surfaceId,
117
+ packageId
118
+ });
119
+ if (!definition.pagesRoot) {
120
+ throw createCliError(
121
+ `Invalid files mutation in ${packageId}: root surface "${surfaceId}" cannot use "toSurfaceRoot".`
122
+ );
123
+ }
124
+ targetPaths.push(path.join(appRoot, "src/pages", `${definition.pagesRoot}.vue`));
125
+ }
126
+ return Object.freeze(targetPaths);
127
+ }
128
+
129
+ const normalizedSurfacePath = normalizeSurfacePathForMutation(mutation.toSurfacePath, {
130
+ context: "toSurfacePath"
131
+ });
132
+ if (!normalizedSurfacePath) {
133
+ throw createCliError(
134
+ `Invalid files mutation in ${packageId}: "toSurfacePath" is required when using "toSurface".`
135
+ );
136
+ }
137
+
138
+ const targetPaths = [];
139
+ for (const surfaceId of normalizedSurfaceIds) {
140
+ const definition = resolveSurfaceDefinitionFromConfigForMutation({
141
+ configContext,
142
+ surfaceId,
143
+ packageId
144
+ });
145
+ const basePagesDirectory = definition.pagesRoot
146
+ ? path.join(appRoot, "src/pages", definition.pagesRoot)
147
+ : path.join(appRoot, "src/pages");
148
+ targetPaths.push(path.join(basePagesDirectory, normalizedSurfacePath));
149
+ }
150
+ return Object.freeze(targetPaths);
151
+ }
152
+
153
+ export {
154
+ resolveSurfaceTargetPathsForMutation
155
+ };