@jskit-ai/jskit-cli 0.2.27 → 0.2.29
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,171 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mkdir,
|
|
3
|
+
readFile,
|
|
4
|
+
writeFile
|
|
5
|
+
} from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
import { createCliError } from "../../shared/cliError.js";
|
|
9
|
+
import {
|
|
10
|
+
ensureArray,
|
|
11
|
+
ensureObject
|
|
12
|
+
} from "../../shared/collectionUtils.js";
|
|
13
|
+
import { interpolateOptionValue } from "../../shared/optionInterpolation.js";
|
|
14
|
+
import {
|
|
15
|
+
fileExists,
|
|
16
|
+
resolveAppRelativePathWithinRoot
|
|
17
|
+
} from "../ioAndMigrations.js";
|
|
18
|
+
|
|
19
|
+
function interpolateFileMutationRecord(mutation, options, packageId) {
|
|
20
|
+
const mutationKey = String(
|
|
21
|
+
mutation?.id || mutation?.to || mutation?.toSurface || mutation?.from || "files"
|
|
22
|
+
).trim();
|
|
23
|
+
const interpolate = (value, field) =>
|
|
24
|
+
interpolateOptionValue(String(value || ""), options, packageId, `${mutationKey}.${field}`);
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
...mutation,
|
|
28
|
+
from: interpolate(mutation.from, "from"),
|
|
29
|
+
to: interpolate(mutation.to, "to"),
|
|
30
|
+
toSurface: interpolate(mutation.toSurface, "toSurface"),
|
|
31
|
+
toSurfacePath: interpolate(mutation.toSurfacePath, "toSurfacePath"),
|
|
32
|
+
toDir: interpolate(mutation.toDir, "toDir"),
|
|
33
|
+
extension: interpolate(mutation.extension, "extension"),
|
|
34
|
+
id: interpolate(mutation.id, "id"),
|
|
35
|
+
category: interpolate(mutation.category, "category"),
|
|
36
|
+
reason: interpolate(mutation.reason, "reason"),
|
|
37
|
+
templateContext: mutation.templateContext
|
|
38
|
+
? {
|
|
39
|
+
entrypoint: interpolate(mutation.templateContext.entrypoint, "templateContext.entrypoint"),
|
|
40
|
+
export: interpolate(mutation.templateContext.export, "templateContext.export")
|
|
41
|
+
}
|
|
42
|
+
: null
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function applyTemplateContextReplacements(sourceContent, replacements) {
|
|
47
|
+
let output = String(sourceContent || "");
|
|
48
|
+
for (const [placeholder, value] of Object.entries(ensureObject(replacements))) {
|
|
49
|
+
const normalizedPlaceholder = String(placeholder || "");
|
|
50
|
+
if (!normalizedPlaceholder) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
output = output.split(normalizedPlaceholder).join(String(value == null ? "" : value));
|
|
54
|
+
}
|
|
55
|
+
return output;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function copyTemplateFile(
|
|
59
|
+
sourcePath,
|
|
60
|
+
targetPath,
|
|
61
|
+
options,
|
|
62
|
+
packageId,
|
|
63
|
+
interpolationKey,
|
|
64
|
+
templateContextReplacements = null
|
|
65
|
+
) {
|
|
66
|
+
const sourceContent = await readFile(sourcePath, "utf8");
|
|
67
|
+
let renderedContent = sourceContent.includes("${")
|
|
68
|
+
? interpolateOptionValue(sourceContent, options, packageId, interpolationKey)
|
|
69
|
+
: sourceContent;
|
|
70
|
+
if (templateContextReplacements) {
|
|
71
|
+
renderedContent = applyTemplateContextReplacements(renderedContent, templateContextReplacements);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
75
|
+
await writeFile(targetPath, renderedContent, "utf8");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function resolveTemplateContextReplacementsForMutation({
|
|
79
|
+
packageEntry,
|
|
80
|
+
mutation,
|
|
81
|
+
options,
|
|
82
|
+
appRoot,
|
|
83
|
+
sourcePath,
|
|
84
|
+
targetPaths,
|
|
85
|
+
mutationContext = "files mutation"
|
|
86
|
+
} = {}) {
|
|
87
|
+
const templateContext = ensureObject(mutation?.templateContext);
|
|
88
|
+
const hasTemplateContext = Object.keys(templateContext).length > 0;
|
|
89
|
+
const entrypoint = String(templateContext.entrypoint || "").trim();
|
|
90
|
+
const resolvedMutationContext = String(mutationContext || "files mutation").trim() || "files mutation";
|
|
91
|
+
if (!hasTemplateContext) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
if (!entrypoint) {
|
|
95
|
+
throw createCliError(
|
|
96
|
+
`Invalid ${resolvedMutationContext} in ${packageEntry.packageId}: templateContext.entrypoint is required when templateContext is set.`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
const exportName = String(templateContext.export || "").trim() || "buildTemplateContext";
|
|
100
|
+
const resolvedEntrypointPath = resolveAppRelativePathWithinRoot(
|
|
101
|
+
packageEntry.rootDir,
|
|
102
|
+
entrypoint,
|
|
103
|
+
`${packageEntry.packageId} ${resolvedMutationContext} templateContext.entrypoint`
|
|
104
|
+
);
|
|
105
|
+
const absoluteEntrypointPath = resolvedEntrypointPath.absolutePath;
|
|
106
|
+
if (!(await fileExists(absoluteEntrypointPath))) {
|
|
107
|
+
throw createCliError(
|
|
108
|
+
`Invalid ${resolvedMutationContext} in ${packageEntry.packageId}: templateContext.entrypoint not found at ${entrypoint}.`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let moduleNamespace = null;
|
|
113
|
+
try {
|
|
114
|
+
moduleNamespace = await import(`${pathToFileURL(absoluteEntrypointPath).href}?t=${Date.now()}_${Math.random()}`);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
throw createCliError(
|
|
117
|
+
`Unable to load templateContext entrypoint ${entrypoint} for ${packageEntry.packageId}: ${String(error?.message || error || "unknown error")}`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const resolver = moduleNamespace?.[exportName];
|
|
122
|
+
if (typeof resolver !== "function") {
|
|
123
|
+
throw createCliError(
|
|
124
|
+
`Invalid ${resolvedMutationContext} in ${packageEntry.packageId}: templateContext export "${exportName}" is not a function.`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let replacements = null;
|
|
129
|
+
try {
|
|
130
|
+
replacements = await resolver({
|
|
131
|
+
packageId: packageEntry.packageId,
|
|
132
|
+
packageRoot: packageEntry.rootDir,
|
|
133
|
+
appRoot,
|
|
134
|
+
options: Object.freeze({ ...ensureObject(options) }),
|
|
135
|
+
mutation: Object.freeze({ ...ensureObject(mutation) }),
|
|
136
|
+
sourcePath,
|
|
137
|
+
targetPaths: Object.freeze([...ensureArray(targetPaths)])
|
|
138
|
+
});
|
|
139
|
+
} catch (error) {
|
|
140
|
+
throw createCliError(
|
|
141
|
+
`templateContext export "${exportName}" failed for ${packageEntry.packageId}: ${String(error?.message || error || "unknown error")}`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (replacements == null) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
if (!replacements || typeof replacements !== "object" || Array.isArray(replacements)) {
|
|
149
|
+
throw createCliError(
|
|
150
|
+
`Invalid ${resolvedMutationContext} in ${packageEntry.packageId}: templateContext export "${exportName}" must return an object map of placeholder replacements.`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const normalizedReplacements = {};
|
|
155
|
+
for (const [placeholder, value] of Object.entries(replacements)) {
|
|
156
|
+
const normalizedPlaceholder = String(placeholder || "").trim();
|
|
157
|
+
if (!normalizedPlaceholder) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
normalizedReplacements[normalizedPlaceholder] = String(value == null ? "" : value);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return Object.freeze(normalizedReplacements);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export {
|
|
167
|
+
applyTemplateContextReplacements,
|
|
168
|
+
copyTemplateFile,
|
|
169
|
+
interpolateFileMutationRecord,
|
|
170
|
+
resolveTemplateContextReplacementsForMutation
|
|
171
|
+
};
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mkdir,
|
|
3
|
+
writeFile
|
|
4
|
+
} from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { createCliError } from "../../shared/cliError.js";
|
|
7
|
+
import {
|
|
8
|
+
ensureArray,
|
|
9
|
+
ensureObject
|
|
10
|
+
} from "../../shared/collectionUtils.js";
|
|
11
|
+
import {
|
|
12
|
+
appendTextSnippet,
|
|
13
|
+
interpolateOptionValue,
|
|
14
|
+
normalizeSkipChecks
|
|
15
|
+
} from "../../shared/optionInterpolation.js";
|
|
16
|
+
import {
|
|
17
|
+
normalizeFileMutationRecord,
|
|
18
|
+
normalizeMutationWhen,
|
|
19
|
+
shouldApplyMutationWhen
|
|
20
|
+
} from "../mutationWhen.js";
|
|
21
|
+
import {
|
|
22
|
+
loadMutationWhenConfigContext,
|
|
23
|
+
normalizeRelativePath,
|
|
24
|
+
readFileBufferIfExists
|
|
25
|
+
} from "../ioAndMigrations.js";
|
|
26
|
+
import { upsertEnvValue } from "../appState.js";
|
|
27
|
+
import {
|
|
28
|
+
applyTemplateContextReplacements,
|
|
29
|
+
resolveTemplateContextReplacementsForMutation
|
|
30
|
+
} from "./templateContext.js";
|
|
31
|
+
import { normalizeMutationRelativeFilePath } from "./mutationPathUtils.js";
|
|
32
|
+
|
|
33
|
+
const SETTINGS_FIELDS_CONTRACT_TARGETS = Object.freeze({
|
|
34
|
+
"packages/main/src/shared/resources/consoleSettingsFields.js": Object.freeze({
|
|
35
|
+
contractId: "users.settings-fields.console.v1",
|
|
36
|
+
marker: "@jskit-contract users.settings-fields.console.v1",
|
|
37
|
+
requiredSnippets: Object.freeze([
|
|
38
|
+
"defineField",
|
|
39
|
+
"resetConsoleSettingsFields"
|
|
40
|
+
])
|
|
41
|
+
}),
|
|
42
|
+
"packages/main/src/shared/resources/workspaceSettingsFields.js": Object.freeze({
|
|
43
|
+
contractId: "users.settings-fields.workspace.v1",
|
|
44
|
+
marker: "@jskit-contract users.settings-fields.workspace.v1",
|
|
45
|
+
requiredSnippets: Object.freeze([
|
|
46
|
+
"defineField",
|
|
47
|
+
"resetWorkspaceSettingsFields"
|
|
48
|
+
])
|
|
49
|
+
})
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
function resolveSettingsFieldsContractTarget(relativeFile = "") {
|
|
53
|
+
const normalizedRelativeFile = normalizeMutationRelativeFilePath(relativeFile);
|
|
54
|
+
if (!normalizedRelativeFile) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const target = SETTINGS_FIELDS_CONTRACT_TARGETS[normalizedRelativeFile];
|
|
58
|
+
if (!target) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
normalizedRelativeFile,
|
|
63
|
+
target
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function validateSettingsFieldsContractMutationTarget({
|
|
68
|
+
appRoot,
|
|
69
|
+
relativeFile,
|
|
70
|
+
packageId
|
|
71
|
+
} = {}) {
|
|
72
|
+
const contractTarget = resolveSettingsFieldsContractTarget(relativeFile);
|
|
73
|
+
if (!contractTarget) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const { normalizedRelativeFile, target } = contractTarget;
|
|
78
|
+
const absoluteFile = path.join(appRoot, normalizedRelativeFile);
|
|
79
|
+
const existing = await readFileBufferIfExists(absoluteFile);
|
|
80
|
+
if (!existing.exists) {
|
|
81
|
+
throw createCliError(
|
|
82
|
+
`Invalid append-text mutation in ${packageId}: ${normalizedRelativeFile} is missing. ` +
|
|
83
|
+
`Install @jskit-ai/users-core to scaffold ${target.contractId}.`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const source = existing.buffer.toString("utf8");
|
|
88
|
+
if (!source.includes(target.marker)) {
|
|
89
|
+
throw createCliError(
|
|
90
|
+
`Invalid append-text mutation in ${packageId}: ${normalizedRelativeFile} is missing contract marker "${target.marker}".`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
for (const snippet of target.requiredSnippets) {
|
|
94
|
+
if (source.includes(snippet)) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
throw createCliError(
|
|
98
|
+
`Invalid append-text mutation in ${packageId}: ${normalizedRelativeFile} must include "${snippet}" for ${target.contractId}.`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function applyTextMutations(packageEntry, appRoot, textMutations, options, managedText, touchedFiles) {
|
|
104
|
+
for (const mutation of textMutations) {
|
|
105
|
+
const when = normalizeMutationWhen(mutation?.when);
|
|
106
|
+
const configContext = when?.config ? await loadMutationWhenConfigContext(appRoot) : {};
|
|
107
|
+
if (
|
|
108
|
+
!shouldApplyMutationWhen(when, {
|
|
109
|
+
options,
|
|
110
|
+
configContext,
|
|
111
|
+
packageId: packageEntry.packageId,
|
|
112
|
+
mutationContext: "text mutation"
|
|
113
|
+
})
|
|
114
|
+
) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const operation = String(mutation?.op || "").trim();
|
|
119
|
+
if (operation === "upsert-env") {
|
|
120
|
+
const relativeFile = String(mutation?.file || "").trim();
|
|
121
|
+
const key = String(mutation?.key || "").trim();
|
|
122
|
+
if (!relativeFile || !key) {
|
|
123
|
+
throw createCliError(`Invalid upsert-env mutation in ${packageEntry.packageId}: "file" and "key" are required.`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const absoluteFile = path.join(appRoot, relativeFile);
|
|
127
|
+
const previous = await readFileBufferIfExists(absoluteFile);
|
|
128
|
+
const previousContent = previous.exists ? previous.buffer.toString("utf8") : "";
|
|
129
|
+
const resolvedValue = interpolateOptionValue(mutation?.value || "", options, packageEntry.packageId, key);
|
|
130
|
+
const upserted = upsertEnvValue(previousContent, key, resolvedValue);
|
|
131
|
+
|
|
132
|
+
await mkdir(path.dirname(absoluteFile), { recursive: true });
|
|
133
|
+
await writeFile(absoluteFile, upserted.content, "utf8");
|
|
134
|
+
|
|
135
|
+
const recordKey = `${relativeFile}::${String(mutation?.id || key)}`;
|
|
136
|
+
managedText[recordKey] = {
|
|
137
|
+
file: relativeFile,
|
|
138
|
+
op: "upsert-env",
|
|
139
|
+
key,
|
|
140
|
+
value: resolvedValue,
|
|
141
|
+
hadPrevious: upserted.hadPrevious,
|
|
142
|
+
previousValue: upserted.previousValue,
|
|
143
|
+
reason: String(mutation?.reason || ""),
|
|
144
|
+
category: String(mutation?.category || ""),
|
|
145
|
+
id: String(mutation?.id || "")
|
|
146
|
+
};
|
|
147
|
+
touchedFiles.add(normalizeRelativePath(appRoot, absoluteFile));
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (operation === "append-text") {
|
|
152
|
+
const relativeFile = String(mutation?.file || "").trim();
|
|
153
|
+
const snippet = String(mutation?.value || "");
|
|
154
|
+
const position = String(mutation?.position || "bottom").trim().toLowerCase();
|
|
155
|
+
if (!relativeFile) {
|
|
156
|
+
throw createCliError(`Invalid append-text mutation in ${packageEntry.packageId}: "file" is required.`);
|
|
157
|
+
}
|
|
158
|
+
if (position !== "top" && position !== "bottom") {
|
|
159
|
+
throw createCliError(`Invalid append-text mutation in ${packageEntry.packageId}: "position" must be "top" or "bottom".`);
|
|
160
|
+
}
|
|
161
|
+
await validateSettingsFieldsContractMutationTarget({
|
|
162
|
+
appRoot,
|
|
163
|
+
relativeFile,
|
|
164
|
+
packageId: packageEntry.packageId
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const absoluteFile = path.join(appRoot, relativeFile);
|
|
168
|
+
const previous = await readFileBufferIfExists(absoluteFile);
|
|
169
|
+
const previousContent = previous.exists ? previous.buffer.toString("utf8") : "";
|
|
170
|
+
const mutationId = String(mutation?.id || "").trim() || "append-text";
|
|
171
|
+
const resolvedSnippet = interpolateOptionValue(snippet, options, packageEntry.packageId, mutationId);
|
|
172
|
+
const templateContextReplacements = await resolveTemplateContextReplacementsForMutation({
|
|
173
|
+
packageEntry,
|
|
174
|
+
mutation,
|
|
175
|
+
options,
|
|
176
|
+
appRoot,
|
|
177
|
+
sourcePath: absoluteFile,
|
|
178
|
+
targetPaths: [absoluteFile],
|
|
179
|
+
mutationContext: "text mutation"
|
|
180
|
+
});
|
|
181
|
+
const renderedSnippet = templateContextReplacements
|
|
182
|
+
? applyTemplateContextReplacements(resolvedSnippet, templateContextReplacements)
|
|
183
|
+
: resolvedSnippet;
|
|
184
|
+
const skipChecks = normalizeSkipChecks(mutation?.skipIfContains)
|
|
185
|
+
.map((entry) => interpolateOptionValue(entry, options, packageEntry.packageId, `${mutationId}.skipIfContains`))
|
|
186
|
+
.map((entry) => {
|
|
187
|
+
if (!templateContextReplacements) {
|
|
188
|
+
return entry;
|
|
189
|
+
}
|
|
190
|
+
return applyTemplateContextReplacements(entry, templateContextReplacements);
|
|
191
|
+
})
|
|
192
|
+
.filter((entry) => String(entry || "").trim().length > 0);
|
|
193
|
+
|
|
194
|
+
const shouldSkip = skipChecks.some((pattern) => previousContent.includes(String(pattern)));
|
|
195
|
+
if (shouldSkip) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const appended = appendTextSnippet(previousContent, renderedSnippet, position);
|
|
200
|
+
if (!appended.changed) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
await mkdir(path.dirname(absoluteFile), { recursive: true });
|
|
205
|
+
await writeFile(absoluteFile, appended.content, "utf8");
|
|
206
|
+
|
|
207
|
+
const recordKey = `${relativeFile}::${mutationId}`;
|
|
208
|
+
managedText[recordKey] = {
|
|
209
|
+
file: relativeFile,
|
|
210
|
+
op: "append-text",
|
|
211
|
+
value: renderedSnippet,
|
|
212
|
+
position,
|
|
213
|
+
reason: String(mutation?.reason || ""),
|
|
214
|
+
category: String(mutation?.category || ""),
|
|
215
|
+
id: String(mutation?.id || "")
|
|
216
|
+
};
|
|
217
|
+
touchedFiles.add(normalizeRelativePath(appRoot, absoluteFile));
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
throw createCliError(`Unsupported text mutation op "${operation}" in ${packageEntry.packageId}.`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function isPositioningTextMutation(value = {}) {
|
|
226
|
+
const mutation = ensureObject(value);
|
|
227
|
+
const operation = String(mutation.op || "").trim();
|
|
228
|
+
if (operation !== "append-text") {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
return normalizeMutationRelativeFilePath(mutation.file) === "src/placement.js";
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function resolvePositioningMutations(descriptorMutations = {}) {
|
|
235
|
+
const mutations = ensureObject(descriptorMutations);
|
|
236
|
+
const files = ensureArray(mutations.files).filter((mutationValue) => {
|
|
237
|
+
const normalized = normalizeFileMutationRecord(mutationValue);
|
|
238
|
+
return Boolean(normalized.toSurface);
|
|
239
|
+
});
|
|
240
|
+
const text = ensureArray(mutations.text).filter((mutationValue) => isPositioningTextMutation(mutationValue));
|
|
241
|
+
return {
|
|
242
|
+
files,
|
|
243
|
+
text
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export {
|
|
248
|
+
applyTextMutations,
|
|
249
|
+
resolvePositioningMutations
|
|
250
|
+
};
|