@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.
- package/package.json +3 -2
- package/src/server/cliRuntime/appState.js +226 -0
- package/src/server/cliRuntime/capabilitySupport.js +194 -0
- package/src/server/cliRuntime/descriptorValidation.js +150 -0
- package/src/server/cliRuntime/ioAndMigrations.js +381 -0
- package/src/server/cliRuntime/localPackageSupport.js +390 -0
- package/src/server/cliRuntime/mutationApplication.js +9 -0
- package/src/server/cliRuntime/mutationWhen.js +285 -0
- package/src/server/cliRuntime/mutations/fileMutations.js +247 -0
- package/src/server/cliRuntime/mutations/installMigrationMutation.js +213 -0
- package/src/server/cliRuntime/mutations/mutationPathUtils.js +12 -0
- package/src/server/cliRuntime/mutations/surfaceTargets.js +155 -0
- package/src/server/cliRuntime/mutations/templateContext.js +171 -0
- package/src/server/cliRuntime/mutations/textMutations.js +250 -0
- package/src/server/cliRuntime/packageInstallFlow.js +489 -0
- package/src/server/cliRuntime/packageIntrospection/exportEntries.js +259 -0
- package/src/server/cliRuntime/packageIntrospection/exportedSymbols.js +216 -0
- package/src/server/cliRuntime/packageIntrospection/placementNormalization.js +98 -0
- package/src/server/cliRuntime/packageIntrospection/providerBindingIntrospection.js +377 -0
- package/src/server/cliRuntime/packageIntrospection.js +137 -0
- package/src/server/cliRuntime/packageOptions.js +299 -0
- package/src/server/cliRuntime/packageRegistries.js +343 -0
- package/src/server/cliRuntime/packageTemplateResolution.js +131 -0
- package/src/server/cliRuntime/viteProxy.js +356 -0
- package/src/server/commandHandlers/health.js +292 -0
- package/src/server/commandHandlers/list.js +292 -0
- package/src/server/commandHandlers/package.js +23 -0
- package/src/server/commandHandlers/packageCommands/add.js +282 -0
- package/src/server/commandHandlers/packageCommands/create.js +155 -0
- package/src/server/commandHandlers/packageCommands/generate.js +116 -0
- package/src/server/commandHandlers/packageCommands/migrations.js +155 -0
- package/src/server/commandHandlers/packageCommands/position.js +103 -0
- package/src/server/commandHandlers/packageCommands/remove.js +181 -0
- package/src/server/commandHandlers/packageCommands/update.js +40 -0
- package/src/server/commandHandlers/shared.js +314 -0
- package/src/server/commandHandlers/show/payloads.js +92 -0
- package/src/server/commandHandlers/show/renderBundleText.js +16 -0
- package/src/server/commandHandlers/show/renderHelpers.js +82 -0
- package/src/server/commandHandlers/show/renderPackageCapabilities.js +124 -0
- package/src/server/commandHandlers/show/renderPackageExports.js +203 -0
- package/src/server/commandHandlers/show/renderPackageText.js +332 -0
- package/src/server/commandHandlers/show.js +114 -0
- package/src/server/core/argParser.js +144 -0
- package/src/server/{runtimeDeps.js → core/buildCommandDeps.js} +2 -1
- package/src/server/core/commandCatalog.js +47 -0
- package/src/server/core/createCliRunner.js +150 -0
- package/src/server/core/createCommandHandlers.js +43 -0
- package/src/server/{runCli.js → core/dispatchCli.js} +14 -1
- package/src/server/core/usageHelp.js +344 -0
- package/src/server/index.js +1 -1
- package/src/server/{optionInterpolation.js → shared/optionInterpolation.js} +12 -1
- package/src/server/{pathResolution.js → shared/pathResolution.js} +1 -1
- package/src/server/argParser.js +0 -206
- package/src/server/cliRuntime.js +0 -4956
- package/src/server/commandHandlers.js +0 -2109
- /package/src/server/{cliError.js → shared/cliError.js} +0 -0
- /package/src/server/{collectionUtils.js → shared/collectionUtils.js} +0 -0
- /package/src/server/{outputFormatting.js → shared/outputFormatting.js} +0 -0
- /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,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
|
+
};
|