@stripe/extensibility-tool-utils 0.6.2
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/LICENSE.md +19 -0
- package/dist/cli/cli-ux.d.ts +30 -0
- package/dist/cli/cli-ux.d.ts.map +1 -0
- package/dist/cli/context.d.ts +24 -0
- package/dist/cli/context.d.ts.map +1 -0
- package/dist/cli/guards.d.ts +24 -0
- package/dist/cli/guards.d.ts.map +1 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/output-path.d.ts +11 -0
- package/dist/cli/output-path.d.ts.map +1 -0
- package/dist/extensibility-tool-utils-alpha.d.ts +383 -0
- package/dist/extensibility-tool-utils-beta.d.ts +383 -0
- package/dist/extensibility-tool-utils-internal.d.ts +1866 -0
- package/dist/extensibility-tool-utils-public.d.ts +383 -0
- package/dist/file-editor/assertions/index.d.ts +68 -0
- package/dist/file-editor/assertions/index.d.ts.map +1 -0
- package/dist/file-editor/document.d.ts +107 -0
- package/dist/file-editor/document.d.ts.map +1 -0
- package/dist/file-editor/errors.d.ts +66 -0
- package/dist/file-editor/errors.d.ts.map +1 -0
- package/dist/file-editor/facades/api-extractor.d.ts +34 -0
- package/dist/file-editor/facades/api-extractor.d.ts.map +1 -0
- package/dist/file-editor/facades/brands.d.ts +45 -0
- package/dist/file-editor/facades/brands.d.ts.map +1 -0
- package/dist/file-editor/facades/package-json.d.ts +55 -0
- package/dist/file-editor/facades/package-json.d.ts.map +1 -0
- package/dist/file-editor/facades/stripe-app-manifest.d.ts +62 -0
- package/dist/file-editor/facades/stripe-app-manifest.d.ts.map +1 -0
- package/dist/file-editor/facades/tsconfig-options.d.ts +76 -0
- package/dist/file-editor/facades/tsconfig-options.d.ts.map +1 -0
- package/dist/file-editor/facades/tsconfig.d.ts +43 -0
- package/dist/file-editor/facades/tsconfig.d.ts.map +1 -0
- package/dist/file-editor/fingerprint.d.ts +39 -0
- package/dist/file-editor/fingerprint.d.ts.map +1 -0
- package/dist/file-editor/formats/adapter.d.ts +29 -0
- package/dist/file-editor/formats/adapter.d.ts.map +1 -0
- package/dist/file-editor/formats/detect.d.ts +9 -0
- package/dist/file-editor/formats/detect.d.ts.map +1 -0
- package/dist/file-editor/formats/index.d.ts +13 -0
- package/dist/file-editor/formats/index.d.ts.map +1 -0
- package/dist/file-editor/formats/jsonc.d.ts +14 -0
- package/dist/file-editor/formats/jsonc.d.ts.map +1 -0
- package/dist/file-editor/formats/yaml.d.ts +11 -0
- package/dist/file-editor/formats/yaml.d.ts.map +1 -0
- package/dist/file-editor/index.d.ts +42 -0
- package/dist/file-editor/index.d.ts.map +1 -0
- package/dist/file-editor/pointer.d.ts +74 -0
- package/dist/file-editor/pointer.d.ts.map +1 -0
- package/dist/file-editor/schema.d.ts +72 -0
- package/dist/file-editor/schema.d.ts.map +1 -0
- package/dist/file-editor/state/fs-manifest.d.ts +30 -0
- package/dist/file-editor/state/fs-manifest.d.ts.map +1 -0
- package/dist/file-editor/state/in-memory.d.ts +5 -0
- package/dist/file-editor/state/in-memory.d.ts.map +1 -0
- package/dist/file-editor/state/store.d.ts +19 -0
- package/dist/file-editor/state/store.d.ts.map +1 -0
- package/dist/file-editor/transaction.d.ts +60 -0
- package/dist/file-editor/transaction.d.ts.map +1 -0
- package/dist/file-editor/types.d.ts +131 -0
- package/dist/file-editor/types.d.ts.map +1 -0
- package/dist/file-editor/util/atomic-write.d.ts +7 -0
- package/dist/file-editor/util/atomic-write.d.ts.map +1 -0
- package/dist/file-editor/util/diff.d.ts +20 -0
- package/dist/file-editor/util/diff.d.ts.map +1 -0
- package/dist/file-editor/value-at-pointer.d.ts +46 -0
- package/dist/file-editor/value-at-pointer.d.ts.map +1 -0
- package/dist/index.cjs +2967 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2847 -0
- package/dist/logging/create-logger.d.ts +29 -0
- package/dist/logging/create-logger.d.ts.map +1 -0
- package/dist/logging/index.d.ts +10 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/levels.d.ts +21 -0
- package/dist/logging/levels.d.ts.map +1 -0
- package/dist/naming/inflection.d.ts +15 -0
- package/dist/naming/inflection.d.ts.map +1 -0
- package/dist/naming/metadata-policy.d.ts +35 -0
- package/dist/naming/metadata-policy.d.ts.map +1 -0
- package/dist/naming/stripe-api-case.d.ts +9 -0
- package/dist/naming/stripe-api-case.d.ts.map +1 -0
- package/dist/naming/types.d.ts +11 -0
- package/dist/naming/types.d.ts.map +1 -0
- package/dist/naming/validate.d.ts +6 -0
- package/dist/naming/validate.d.ts.map +1 -0
- package/dist/templates/filesystem-fs.d.ts +20 -0
- package/dist/templates/filesystem-fs.d.ts.map +1 -0
- package/dist/templates/generator.d.ts +305 -0
- package/dist/templates/generator.d.ts.map +1 -0
- package/dist/templates/in-memory-fs.d.ts +44 -0
- package/dist/templates/in-memory-fs.d.ts.map +1 -0
- package/dist/templates/index.d.ts +28 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/simple-templates.d.ts +75 -0
- package/dist/templates/simple-templates.d.ts.map +1 -0
- package/dist/templates/template-manager.d.ts +54 -0
- package/dist/templates/template-manager.d.ts.map +1 -0
- package/dist/templates/types.d.ts +87 -0
- package/dist/templates/types.d.ts.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/workspace-versions.d.ts +30 -0
- package/dist/workspace-versions.d.ts.map +1 -0
- package/package.json +60 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2847 @@
|
|
|
1
|
+
// src/naming/stripe-api-case.ts
|
|
2
|
+
var UPPERCASE_ACRONYM_TOKENS = [
|
|
3
|
+
"HTTPS",
|
|
4
|
+
"HTTP",
|
|
5
|
+
"UUID",
|
|
6
|
+
"JSON",
|
|
7
|
+
"XML",
|
|
8
|
+
"OAuth",
|
|
9
|
+
"URL",
|
|
10
|
+
"URI",
|
|
11
|
+
"API",
|
|
12
|
+
"SQL",
|
|
13
|
+
"MFA",
|
|
14
|
+
"2FA",
|
|
15
|
+
"3DS",
|
|
16
|
+
"CSV",
|
|
17
|
+
"HTML",
|
|
18
|
+
"PDF",
|
|
19
|
+
"JWT",
|
|
20
|
+
"SHA",
|
|
21
|
+
"TLS",
|
|
22
|
+
"SSL",
|
|
23
|
+
"ACH",
|
|
24
|
+
"ID"
|
|
25
|
+
];
|
|
26
|
+
var COMPOSITE_CASE_TOKENS = ["OAuth"];
|
|
27
|
+
function isUppercaseRun(token) {
|
|
28
|
+
return /^[A-Z0-9]+$/.test(token) && /[A-Z]/.test(token);
|
|
29
|
+
}
|
|
30
|
+
function splitUppercaseRun(token) {
|
|
31
|
+
const pieces = [];
|
|
32
|
+
let rest = token;
|
|
33
|
+
while (rest.length > 0) {
|
|
34
|
+
const matched = UPPERCASE_ACRONYM_TOKENS.find(
|
|
35
|
+
(candidate) => rest.startsWith(candidate.toUpperCase())
|
|
36
|
+
);
|
|
37
|
+
if (matched) {
|
|
38
|
+
pieces.push(matched);
|
|
39
|
+
rest = rest.slice(matched.length);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
pieces.push(rest);
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
return pieces;
|
|
46
|
+
}
|
|
47
|
+
function mergeCompositeTokens(tokens) {
|
|
48
|
+
const merged = [];
|
|
49
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
50
|
+
const current = tokens[index];
|
|
51
|
+
const next = tokens[index + 1];
|
|
52
|
+
if (!current) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (next) {
|
|
56
|
+
const combined = `${current}${next}`;
|
|
57
|
+
const matched = COMPOSITE_CASE_TOKENS.find(
|
|
58
|
+
(candidate) => candidate.toLowerCase() === combined.toLowerCase()
|
|
59
|
+
);
|
|
60
|
+
if (matched) {
|
|
61
|
+
merged.push(matched);
|
|
62
|
+
index += 1;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
merged.push(current);
|
|
67
|
+
}
|
|
68
|
+
return merged;
|
|
69
|
+
}
|
|
70
|
+
function tokenizeSegment(segment) {
|
|
71
|
+
const matches = segment.match(
|
|
72
|
+
/(?:[0-9]+[A-Z]+(?=$|[A-Z][a-z]))|(?:[A-Z]+(?=[A-Z][a-z]))|(?:[A-Z]?[a-z]+)|(?:[A-Z]+)|(?:[0-9]+)/g
|
|
73
|
+
) ?? [segment];
|
|
74
|
+
return mergeCompositeTokens(matches).flatMap((token) => {
|
|
75
|
+
if (/^[0-9]+[A-Z]+$/.test(token)) {
|
|
76
|
+
const digitPrefix = /^[0-9]+/.exec(token)?.[0] ?? "";
|
|
77
|
+
const upperSuffix = token.slice(digitPrefix.length);
|
|
78
|
+
return [digitPrefix, ...splitUppercaseRun(upperSuffix)];
|
|
79
|
+
}
|
|
80
|
+
return isUppercaseRun(token) && token.length > 1 ? splitUppercaseRun(token) : [token];
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function isAcronymToken(token) {
|
|
84
|
+
const upper = token.toUpperCase();
|
|
85
|
+
return token === upper && UPPERCASE_ACRONYM_TOKENS.some((candidate) => candidate.toUpperCase() === upper) || COMPOSITE_CASE_TOKENS.some(
|
|
86
|
+
(candidate) => candidate.toLowerCase() === token.toLowerCase()
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
function _tokenizeIdentifier(input) {
|
|
90
|
+
return input.replace(/[\s-]+/g, "_").split("_").filter(Boolean).flatMap((segment) => tokenizeSegment(segment)).filter(Boolean);
|
|
91
|
+
}
|
|
92
|
+
function _toStripeApiCase(input) {
|
|
93
|
+
return _tokenizeIdentifier(input).map((token) => token.replace(/[^A-Za-z0-9]/g, "").toLowerCase()).filter(Boolean).join("_");
|
|
94
|
+
}
|
|
95
|
+
function _toSentenceDisplayName(input) {
|
|
96
|
+
const tokens = _tokenizeIdentifier(input);
|
|
97
|
+
if (tokens.length === 0) {
|
|
98
|
+
return input;
|
|
99
|
+
}
|
|
100
|
+
return tokens.map((token, index) => {
|
|
101
|
+
if (/^[0-9]+$/.test(token)) {
|
|
102
|
+
return token;
|
|
103
|
+
}
|
|
104
|
+
if (isAcronymToken(token)) {
|
|
105
|
+
return token.toUpperCase();
|
|
106
|
+
}
|
|
107
|
+
const lower = token.toLowerCase();
|
|
108
|
+
if (index === 0) {
|
|
109
|
+
return lower.charAt(0).toUpperCase() + lower.slice(1);
|
|
110
|
+
}
|
|
111
|
+
return lower;
|
|
112
|
+
}).join(" ");
|
|
113
|
+
}
|
|
114
|
+
function _truncateName(value, maxLength) {
|
|
115
|
+
return value.slice(0, maxLength);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/naming/inflection.ts
|
|
119
|
+
import { camelize, capitalize, pluralize, singularize, underscore } from "inflected";
|
|
120
|
+
function _toPascalCase(value) {
|
|
121
|
+
return camelize(value);
|
|
122
|
+
}
|
|
123
|
+
function _toSnakeCase(value) {
|
|
124
|
+
return underscore(value);
|
|
125
|
+
}
|
|
126
|
+
function _toPlural(value) {
|
|
127
|
+
return pluralize(value);
|
|
128
|
+
}
|
|
129
|
+
function _toSingular(value) {
|
|
130
|
+
return singularize(value);
|
|
131
|
+
}
|
|
132
|
+
function _looksPlural(value) {
|
|
133
|
+
const singular = singularize(value);
|
|
134
|
+
return singular !== value;
|
|
135
|
+
}
|
|
136
|
+
function _toCapitalized(value) {
|
|
137
|
+
return capitalize(value);
|
|
138
|
+
}
|
|
139
|
+
function _looksSingular(value) {
|
|
140
|
+
return pluralize(value) !== value;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/naming/metadata-policy.ts
|
|
144
|
+
function _inferStripeApiName(context) {
|
|
145
|
+
return _toStripeApiCase(context.logicalName);
|
|
146
|
+
}
|
|
147
|
+
function inferStripeDisplayName(context) {
|
|
148
|
+
return _toSentenceDisplayName(context.logicalName);
|
|
149
|
+
}
|
|
150
|
+
function pluralizeStripeMetadata(context) {
|
|
151
|
+
return _toPlural(context.singular);
|
|
152
|
+
}
|
|
153
|
+
var _stripeMetadataPolicy = {
|
|
154
|
+
type: {
|
|
155
|
+
apiName: {
|
|
156
|
+
mode: "infer-if-missing",
|
|
157
|
+
infer: _inferStripeApiName,
|
|
158
|
+
pluralization: {
|
|
159
|
+
mode: "infer-if-missing",
|
|
160
|
+
inflect: pluralizeStripeMetadata
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
displayName: {
|
|
164
|
+
mode: "infer-if-missing",
|
|
165
|
+
infer: inferStripeDisplayName,
|
|
166
|
+
pluralization: {
|
|
167
|
+
mode: "infer-if-missing",
|
|
168
|
+
inflect: pluralizeStripeMetadata
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
field: {
|
|
173
|
+
apiName: {
|
|
174
|
+
mode: "infer-if-missing",
|
|
175
|
+
infer: _inferStripeApiName
|
|
176
|
+
},
|
|
177
|
+
displayName: {
|
|
178
|
+
mode: "infer-if-missing",
|
|
179
|
+
infer: inferStripeDisplayName
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
method: {
|
|
183
|
+
apiName: {
|
|
184
|
+
mode: "infer-if-missing",
|
|
185
|
+
infer: _inferStripeApiName
|
|
186
|
+
},
|
|
187
|
+
displayName: {
|
|
188
|
+
mode: "infer-if-missing",
|
|
189
|
+
infer: inferStripeDisplayName
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// src/naming/validate.ts
|
|
195
|
+
function _validateApiName(value) {
|
|
196
|
+
const reasons = [];
|
|
197
|
+
if (value.length === 0) {
|
|
198
|
+
reasons.push("API name must not be empty.");
|
|
199
|
+
}
|
|
200
|
+
if (value.length > 40) {
|
|
201
|
+
reasons.push("API name must not exceed 40 characters.");
|
|
202
|
+
}
|
|
203
|
+
const firstCharacter = value[0];
|
|
204
|
+
if (firstCharacter && !/[a-z]/.test(firstCharacter)) {
|
|
205
|
+
reasons.push("API name must start with a lowercase letter.");
|
|
206
|
+
}
|
|
207
|
+
if (value.endsWith("_")) {
|
|
208
|
+
reasons.push("API name must not end with an underscore.");
|
|
209
|
+
}
|
|
210
|
+
if (/[^a-z0-9_]/.test(value)) {
|
|
211
|
+
reasons.push(
|
|
212
|
+
"API name must contain only lowercase letters, digits, and underscores."
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
if (value.includes("__")) {
|
|
216
|
+
reasons.push("API name must not contain consecutive underscores.");
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
valid: reasons.length === 0,
|
|
220
|
+
reasons
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function _validateDisplayName(value) {
|
|
224
|
+
const reasons = [];
|
|
225
|
+
if (value.length === 0) {
|
|
226
|
+
reasons.push("Display name must not be empty.");
|
|
227
|
+
}
|
|
228
|
+
if (value.length > 40) {
|
|
229
|
+
reasons.push("Display name must not exceed 40 characters.");
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
valid: reasons.length === 0,
|
|
233
|
+
reasons
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/logging/levels.ts
|
|
238
|
+
var VALID_LOG_LEVELS = /* @__PURE__ */ new Set([
|
|
239
|
+
"trace",
|
|
240
|
+
"debug",
|
|
241
|
+
"info",
|
|
242
|
+
"warn",
|
|
243
|
+
"error",
|
|
244
|
+
"fatal",
|
|
245
|
+
"silent"
|
|
246
|
+
]);
|
|
247
|
+
function isValidLogLevel(value) {
|
|
248
|
+
return VALID_LOG_LEVELS.has(value);
|
|
249
|
+
}
|
|
250
|
+
function _parseLogLevel(input) {
|
|
251
|
+
if (input === void 0 || input === "") return "info";
|
|
252
|
+
const normalized = input.trim().toLowerCase();
|
|
253
|
+
if (isValidLogLevel(normalized)) return normalized;
|
|
254
|
+
return "info";
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/logging/create-logger.ts
|
|
258
|
+
import pino from "pino";
|
|
259
|
+
function _createLogger(options) {
|
|
260
|
+
const level = options?.level ?? _parseLogLevel(process.env.LOG_LEVEL);
|
|
261
|
+
const colorize = process.env.NO_COLOR === void 0 && process.stderr.isTTY;
|
|
262
|
+
return pino({
|
|
263
|
+
...options?.name !== void 0 && { name: options.name },
|
|
264
|
+
level,
|
|
265
|
+
transport: {
|
|
266
|
+
target: "pino-pretty",
|
|
267
|
+
options: { destination: 2, colorize }
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/cli/cli-ux.ts
|
|
273
|
+
var _CliUx = class {
|
|
274
|
+
#stdout;
|
|
275
|
+
#stderr;
|
|
276
|
+
constructor(options) {
|
|
277
|
+
this.#stdout = options?.stdout ?? process.stdout;
|
|
278
|
+
this.#stderr = options?.stderr ?? process.stderr;
|
|
279
|
+
}
|
|
280
|
+
/** Write a product output line to stdout (or the configured stream). */
|
|
281
|
+
print(message) {
|
|
282
|
+
this.#stdout.write(message + "\n");
|
|
283
|
+
}
|
|
284
|
+
/** Write a status/progress line to stderr (or the configured stream). */
|
|
285
|
+
log(message) {
|
|
286
|
+
this.#stderr.write(message + "\n");
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Error message to **stderr** (or the configured stderr stream).
|
|
290
|
+
*
|
|
291
|
+
* Behaviorally identical to {@link _CliUx.log} today. Use `error()` for failures
|
|
292
|
+
* and `log()` for progress/status — the distinction supports future
|
|
293
|
+
* formatting differentiation (color, prefixes, severity filtering).
|
|
294
|
+
*/
|
|
295
|
+
error(message) {
|
|
296
|
+
this.#stderr.write(message + "\n");
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// src/cli/context.ts
|
|
301
|
+
function _createCliContext(options) {
|
|
302
|
+
return { ux: options?.ux ?? new _CliUx() };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// src/cli/output-path.ts
|
|
306
|
+
import { writeFileSync } from "fs";
|
|
307
|
+
function _writeJsonOutput(outputPath, data) {
|
|
308
|
+
if (outputPath === void 0) return;
|
|
309
|
+
writeFileSync(outputPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/cli/guards.ts
|
|
313
|
+
function _isRecord(value) {
|
|
314
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
315
|
+
}
|
|
316
|
+
function _isRecordWithValueType(guard) {
|
|
317
|
+
return (value) => {
|
|
318
|
+
if (!_isRecord(value)) return false;
|
|
319
|
+
return Object.values(value).every(guard);
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/templates/template-manager.ts
|
|
324
|
+
var _TemplateManager = class {
|
|
325
|
+
templates;
|
|
326
|
+
fs;
|
|
327
|
+
/**
|
|
328
|
+
* Create a new TemplateManager
|
|
329
|
+
* @param templates - Object mapping string keys to templates
|
|
330
|
+
* @param fs - Filesystem abstraction for template file reading
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* ```typescript
|
|
334
|
+
* import { fs } from './fs/index.js';
|
|
335
|
+
*
|
|
336
|
+
* const manager = new TemplateManager({
|
|
337
|
+
* foo: fooTemplate,
|
|
338
|
+
* bar: barTemplate,
|
|
339
|
+
* }, fs)
|
|
340
|
+
*
|
|
341
|
+
* await manager.generate('foo', params)
|
|
342
|
+
* ```
|
|
343
|
+
*/
|
|
344
|
+
constructor(templates, fs) {
|
|
345
|
+
this.templates = new Map(Object.entries(templates));
|
|
346
|
+
this.fs = fs;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Generate output from a template
|
|
350
|
+
* @param key - Template key to generate from
|
|
351
|
+
* @param params - Template-specific parameters
|
|
352
|
+
* @returns Template output with files and any additional fields
|
|
353
|
+
* @throws Error if template key is not found
|
|
354
|
+
*/
|
|
355
|
+
async generate(key, params) {
|
|
356
|
+
const template = this.templates.get(key);
|
|
357
|
+
if (!template) {
|
|
358
|
+
let msg = `Template not found for key: ${key}.`;
|
|
359
|
+
const supported = [...this.templates.keys()];
|
|
360
|
+
if (supported.length > 0) {
|
|
361
|
+
msg += ` Supported: ${supported.join(", ")}`;
|
|
362
|
+
}
|
|
363
|
+
throw new Error(msg);
|
|
364
|
+
}
|
|
365
|
+
const context = {
|
|
366
|
+
fs: this.fs
|
|
367
|
+
};
|
|
368
|
+
return await template.generate(params, context);
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Get all template entries as [key, template] pairs
|
|
372
|
+
*/
|
|
373
|
+
getTemplateEntries() {
|
|
374
|
+
return [...this.templates.entries()];
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Get list of all supported template keys
|
|
378
|
+
*/
|
|
379
|
+
getSupportedKeys() {
|
|
380
|
+
return [...this.templates.keys()];
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
// src/templates/in-memory-fs.ts
|
|
385
|
+
import Mustache from "mustache";
|
|
386
|
+
function _createInMemoryTemplateFS(image, pathPrefix = "") {
|
|
387
|
+
const index = /* @__PURE__ */ new Map();
|
|
388
|
+
const allPaths = [];
|
|
389
|
+
for (const entry of image) {
|
|
390
|
+
index.set(entry.path, entry.content);
|
|
391
|
+
allPaths.push(entry.path);
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
textFile(...segments) {
|
|
395
|
+
const fullSegments = pathPrefix ? [pathPrefix, ...segments] : segments;
|
|
396
|
+
const requestedPath = fullSegments.join("/");
|
|
397
|
+
const content = index.get(requestedPath);
|
|
398
|
+
if (content === void 0) {
|
|
399
|
+
const available = Array.from(index.keys()).slice(0, 5).join(", ");
|
|
400
|
+
throw new Error(
|
|
401
|
+
`Template not found: ${requestedPath}
|
|
402
|
+
Available paths (first 5): ${available}...`
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
return content;
|
|
406
|
+
},
|
|
407
|
+
mustache(params, ...segments) {
|
|
408
|
+
const template = this.textFile(...segments);
|
|
409
|
+
return Mustache.render(template, params, void 0, {
|
|
410
|
+
escape: (text) => text
|
|
411
|
+
});
|
|
412
|
+
},
|
|
413
|
+
scope(prefix) {
|
|
414
|
+
const newPrefix = pathPrefix ? `${pathPrefix}/${prefix}` : prefix;
|
|
415
|
+
return _createInMemoryTemplateFS(image, newPrefix);
|
|
416
|
+
},
|
|
417
|
+
_getPathsUnder(prefix) {
|
|
418
|
+
const normalizedPrefix = prefix.endsWith("/") ? prefix : prefix + "/";
|
|
419
|
+
return allPaths.filter((p) => p.startsWith(normalizedPrefix));
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// src/templates/simple-templates.ts
|
|
425
|
+
function _createSimpleTemplate(generateContent, pathTemplate) {
|
|
426
|
+
return {
|
|
427
|
+
generate: (params, _context) => ({
|
|
428
|
+
files: [
|
|
429
|
+
{
|
|
430
|
+
path: pathTemplate(params),
|
|
431
|
+
content: generateContent(params)
|
|
432
|
+
}
|
|
433
|
+
]
|
|
434
|
+
})
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
var _SingleTemplateManager = class {
|
|
438
|
+
template;
|
|
439
|
+
fs;
|
|
440
|
+
/**
|
|
441
|
+
* Create a new SingleTemplateManager
|
|
442
|
+
* @param template - The single template to use
|
|
443
|
+
* @param fs - Filesystem for template file reading
|
|
444
|
+
*/
|
|
445
|
+
constructor(template, fs) {
|
|
446
|
+
this.template = template;
|
|
447
|
+
this.fs = fs;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Generate output from the template
|
|
451
|
+
* @param params - Template parameters
|
|
452
|
+
* @returns Template output
|
|
453
|
+
*/
|
|
454
|
+
async generate(params) {
|
|
455
|
+
const context = {
|
|
456
|
+
fs: this.fs
|
|
457
|
+
};
|
|
458
|
+
return await this.template.generate(params, context);
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
function _createSimpleSingleFileTemplate(generateContent, pathTemplate) {
|
|
462
|
+
const template = _createSimpleTemplate(generateContent, pathTemplate);
|
|
463
|
+
return new _SingleTemplateManager(template, _createInMemoryTemplateFS([]));
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// src/templates/filesystem-fs.ts
|
|
467
|
+
import { readFileSync } from "fs";
|
|
468
|
+
import { join } from "path";
|
|
469
|
+
import Mustache2 from "mustache";
|
|
470
|
+
function _createFilesystemTemplateFS(templateDir = process.cwd()) {
|
|
471
|
+
return {
|
|
472
|
+
textFile(...segments) {
|
|
473
|
+
return readFileSync(join(templateDir, ...segments), "utf-8");
|
|
474
|
+
},
|
|
475
|
+
mustache(params, ...segments) {
|
|
476
|
+
const template = readFileSync(join(templateDir, ...segments), "utf-8");
|
|
477
|
+
return Mustache2.render(template, params, void 0, {
|
|
478
|
+
escape: (text) => text
|
|
479
|
+
});
|
|
480
|
+
},
|
|
481
|
+
scope(prefix) {
|
|
482
|
+
return _createFilesystemTemplateFS(join(templateDir, prefix));
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// src/templates/generator.ts
|
|
488
|
+
import { existsSync, readdirSync, statSync } from "fs";
|
|
489
|
+
import { isAbsolute, join as join2, posix, relative, sep } from "path";
|
|
490
|
+
import Mustache3 from "mustache";
|
|
491
|
+
var _GeneratorInputError = class extends Error {
|
|
492
|
+
remediation;
|
|
493
|
+
constructor(message, remediation, options) {
|
|
494
|
+
super(message, options);
|
|
495
|
+
this.name = "_GeneratorInputError";
|
|
496
|
+
this.remediation = remediation;
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
var _GeneratorDefectError = class extends Error {
|
|
500
|
+
generatorId;
|
|
501
|
+
constructor(message, generatorId, options) {
|
|
502
|
+
super(message, options);
|
|
503
|
+
this.name = "_GeneratorDefectError";
|
|
504
|
+
this.generatorId = generatorId;
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
var PATH_TOKEN_PATTERN = /___([a-zA-Z][a-zA-Z0-9]*)___/g;
|
|
508
|
+
function resolvePathTokens(segment, locals) {
|
|
509
|
+
PATH_TOKEN_PATTERN.lastIndex = 0;
|
|
510
|
+
return segment.replace(PATH_TOKEN_PATTERN, (_match, token) => {
|
|
511
|
+
const value = locals[token];
|
|
512
|
+
if (value === void 0) {
|
|
513
|
+
throw new _GeneratorDefectError(
|
|
514
|
+
`Path token "___${token}___" has no corresponding value in locals(). Available keys: ${Object.keys(locals).join(", ")}`
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
if (typeof value !== "string") {
|
|
518
|
+
throw new _GeneratorDefectError(
|
|
519
|
+
`Path token "___${token}___" must resolve to a string, got ${typeof value}`
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
return value;
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
function walkDir(root, dir = root) {
|
|
526
|
+
const entries = readdirSync(dir).sort();
|
|
527
|
+
const paths = [];
|
|
528
|
+
for (const entry of entries) {
|
|
529
|
+
const fullPath = join2(dir, entry);
|
|
530
|
+
const stat2 = statSync(fullPath);
|
|
531
|
+
if (stat2.isDirectory()) {
|
|
532
|
+
paths.push(...walkDir(root, fullPath));
|
|
533
|
+
} else {
|
|
534
|
+
paths.push(relative(root, fullPath).split(sep).join("/"));
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return paths;
|
|
538
|
+
}
|
|
539
|
+
function assertSafePath(outputPath) {
|
|
540
|
+
if (isAbsolute(outputPath)) {
|
|
541
|
+
throw new _GeneratorDefectError(
|
|
542
|
+
`Generator produced an unsafe path: ${outputPath}. Paths must be relative and cannot contain '..' segments.`
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
const segments = outputPath.split(/[\\/]/);
|
|
546
|
+
for (const segment of segments) {
|
|
547
|
+
if (segment === "..") {
|
|
548
|
+
throw new _GeneratorDefectError(
|
|
549
|
+
`Generator produced an unsafe path: ${outputPath}. Paths must be relative and cannot contain '..' segments.`
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function assertNoMalformedTokens(templatePath) {
|
|
555
|
+
const segments = templatePath.split("/");
|
|
556
|
+
const anyTokenLike = /___[^_/].*?___/g;
|
|
557
|
+
for (const segment of segments) {
|
|
558
|
+
anyTokenLike.lastIndex = 0;
|
|
559
|
+
let match;
|
|
560
|
+
while ((match = anyTokenLike.exec(segment)) !== null) {
|
|
561
|
+
const occurrence = match[0];
|
|
562
|
+
PATH_TOKEN_PATTERN.lastIndex = 0;
|
|
563
|
+
const isValid = PATH_TOKEN_PATTERN.test(occurrence);
|
|
564
|
+
PATH_TOKEN_PATTERN.lastIndex = 0;
|
|
565
|
+
if (!isValid) {
|
|
566
|
+
throw new _GeneratorDefectError(
|
|
567
|
+
`Path "${templatePath}" contains what looks like an unresolved ___token___. Token names must be camelCase alphanumeric (e.g., ___packageName___, not ___package_name___).`
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
var MUSTACHE_EXT = ".mustache";
|
|
574
|
+
var FILES_SUBDIR = "files";
|
|
575
|
+
var _GeneratorRunner = class {
|
|
576
|
+
#generators;
|
|
577
|
+
constructor(generator) {
|
|
578
|
+
this.#generators = Array.isArray(generator) ? generator : [generator];
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Plan phase — compute all files that would be generated, without writing
|
|
582
|
+
* anything to disk.
|
|
583
|
+
*
|
|
584
|
+
* Also runs `beforeExecute` hooks to collect preflight results, and
|
|
585
|
+
* `describeSideEffects` to surface planned side effects. Neither of these
|
|
586
|
+
* write anything or produce observable external effects during plan.
|
|
587
|
+
*
|
|
588
|
+
* Use this for dry runs, previews, and testing.
|
|
589
|
+
*
|
|
590
|
+
* Validates that all output paths are safe (no `..`, no leading `/`) and
|
|
591
|
+
* that no two generators produce the same output path.
|
|
592
|
+
*
|
|
593
|
+
* @param params - User-provided parameters for the generator
|
|
594
|
+
* @param context - Generator context (standalone or project). Passed to
|
|
595
|
+
* `beforeExecute` hooks. Callers must supply a real context — there is no
|
|
596
|
+
* synthetic plan-time sentinel. For dry-run calls outside of a project,
|
|
597
|
+
* use a standalone context with an appropriate `targetDir`.
|
|
598
|
+
*/
|
|
599
|
+
async plan(params, context) {
|
|
600
|
+
const files = [];
|
|
601
|
+
const preflightResults = [];
|
|
602
|
+
const plannedSideEffects = [];
|
|
603
|
+
for (const generator of this.#generators) {
|
|
604
|
+
if (generator.beforeExecute) {
|
|
605
|
+
const result = await generator.beforeExecute(params, context);
|
|
606
|
+
preflightResults.push(result);
|
|
607
|
+
}
|
|
608
|
+
if (generator.describeSideEffects) {
|
|
609
|
+
const effects = await generator.describeSideEffects(params);
|
|
610
|
+
plannedSideEffects.push(...effects);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
for (const generator of this.#generators) {
|
|
614
|
+
const locals = generator.locals(params);
|
|
615
|
+
const filesDir = join2(generator.filesDir, FILES_SUBDIR);
|
|
616
|
+
const fs = _createFilesystemTemplateFS(filesDir);
|
|
617
|
+
if (!existsSync(filesDir)) {
|
|
618
|
+
throw new _GeneratorDefectError(
|
|
619
|
+
`Generator at "${generator.filesDir}" is missing a files/ subdirectory. Expected to find templates at "${filesDir}".`
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
const templatePaths = walkDir(filesDir);
|
|
623
|
+
for (const templatePath of templatePaths) {
|
|
624
|
+
assertNoMalformedTokens(templatePath);
|
|
625
|
+
const resolvedPath = templatePath.split("/").map((segment) => {
|
|
626
|
+
const resolved = resolvePathTokens(segment, locals);
|
|
627
|
+
if (resolved === "") {
|
|
628
|
+
throw new _GeneratorDefectError(
|
|
629
|
+
`Template token in path segment "${segment}" resolved to empty string`
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
return resolved;
|
|
633
|
+
}).join("/");
|
|
634
|
+
const stripped = resolvedPath.endsWith(MUSTACHE_EXT) ? resolvedPath.slice(0, -MUSTACHE_EXT.length) : resolvedPath;
|
|
635
|
+
if (stripped === "") {
|
|
636
|
+
throw new _GeneratorDefectError(
|
|
637
|
+
`Generator produced an empty output path after processing template "${templatePath}".`
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
assertSafePath(stripped);
|
|
641
|
+
const outputPath = posix.normalize(stripped);
|
|
642
|
+
const pathSegments = templatePath.split("/");
|
|
643
|
+
let content;
|
|
644
|
+
if (templatePath.endsWith(MUSTACHE_EXT)) {
|
|
645
|
+
content = fs.mustache(locals, ...pathSegments);
|
|
646
|
+
} else {
|
|
647
|
+
content = fs.textFile(...pathSegments);
|
|
648
|
+
}
|
|
649
|
+
const description = resolveDescription(
|
|
650
|
+
templatePath,
|
|
651
|
+
generator.descriptions,
|
|
652
|
+
locals,
|
|
653
|
+
fs
|
|
654
|
+
);
|
|
655
|
+
files.push({
|
|
656
|
+
path: outputPath,
|
|
657
|
+
content,
|
|
658
|
+
...description !== void 0 && { description }
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
const seen = /* @__PURE__ */ new Set();
|
|
663
|
+
for (const file of files) {
|
|
664
|
+
if (seen.has(file.path)) {
|
|
665
|
+
throw new _GeneratorDefectError(
|
|
666
|
+
`Generator produced duplicate output path: ${file.path}`
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
seen.add(file.path);
|
|
670
|
+
}
|
|
671
|
+
return { files, preflightResults, plannedSideEffects };
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Execute phase — runs the full lifecycle:
|
|
675
|
+
* 1. `plan()` to collect preflight results and planned files
|
|
676
|
+
* 2. If any preflight returned `proceed: false`, return early without writing
|
|
677
|
+
* 3. `context.writeFiles()` to write files to disk (delegated to caller)
|
|
678
|
+
* 4. If the write was aborted, return early without calling `afterExecute`
|
|
679
|
+
* 5. `afterExecute` hooks for all generators with the write result
|
|
680
|
+
*/
|
|
681
|
+
async execute(params, context) {
|
|
682
|
+
const plan = await this.plan(params, context);
|
|
683
|
+
const rejected = plan.preflightResults.some((r) => !r.proceed);
|
|
684
|
+
if (rejected) {
|
|
685
|
+
return {
|
|
686
|
+
files: plan.files,
|
|
687
|
+
preflightResults: plan.preflightResults,
|
|
688
|
+
plannedSideEffects: plan.plannedSideEffects,
|
|
689
|
+
writeResult: void 0,
|
|
690
|
+
afterExecuteResults: []
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
const writeResult = await context.writeFiles(plan.files);
|
|
694
|
+
if (writeResult.aborted) {
|
|
695
|
+
return {
|
|
696
|
+
files: plan.files,
|
|
697
|
+
preflightResults: plan.preflightResults,
|
|
698
|
+
plannedSideEffects: plan.plannedSideEffects,
|
|
699
|
+
writeResult,
|
|
700
|
+
afterExecuteResults: []
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
const afterExecuteResults = [];
|
|
704
|
+
for (const generator of this.#generators) {
|
|
705
|
+
if (generator.afterExecute) {
|
|
706
|
+
const result = await generator.afterExecute(params, context, writeResult);
|
|
707
|
+
afterExecuteResults.push(result);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return {
|
|
711
|
+
files: plan.files,
|
|
712
|
+
preflightResults: plan.preflightResults,
|
|
713
|
+
plannedSideEffects: plan.plannedSideEffects,
|
|
714
|
+
writeResult,
|
|
715
|
+
afterExecuteResults
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
function resolveDescription(templatePath, descriptions, locals, _fs) {
|
|
720
|
+
if (!descriptions) return void 0;
|
|
721
|
+
const template = descriptions[templatePath];
|
|
722
|
+
if (template === void 0) return void 0;
|
|
723
|
+
return Mustache3.render(template, locals, void 0, {
|
|
724
|
+
escape: (t) => t
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
function _compositeGenerator(...generators) {
|
|
728
|
+
return new _GeneratorRunner(generators);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// src/workspace-versions.json
|
|
732
|
+
var workspace_versions_default = {
|
|
733
|
+
"@stripe/extensibility-api-objects": "0.3.1",
|
|
734
|
+
"@stripe/extensibility-custom-objects": "0.7.2",
|
|
735
|
+
"@stripe/extensibility-custom-objects-tools": "0.39.2",
|
|
736
|
+
"@stripe/extensibility-dev-tools": "0.23.1",
|
|
737
|
+
"@stripe/extensibility-doc-helpers": "0.3.0",
|
|
738
|
+
"@stripe/extensibility-eslint-plugin": "0.15.2",
|
|
739
|
+
"@stripe/extensibility-jsonschema-tools": "0.6.3",
|
|
740
|
+
"@stripe/extensibility-language-server": "0.2.8",
|
|
741
|
+
"@stripe/extensibility-script-build-tools": "1.5.4",
|
|
742
|
+
"@stripe/extensibility-sdk": "0.22.2",
|
|
743
|
+
"@stripe/extensibility-test-helpers": "0.2.5",
|
|
744
|
+
"@stripe/extensibility-tool-utils": "0.6.2"
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
// src/workspace-versions.ts
|
|
748
|
+
var _workspaceVersions = workspace_versions_default;
|
|
749
|
+
function _workspaceVersion(packageName) {
|
|
750
|
+
const v = _workspaceVersions[packageName];
|
|
751
|
+
if (v === void 0) {
|
|
752
|
+
throw new Error(
|
|
753
|
+
`Unknown workspace package "${packageName}". Check workspace-versions.json or run: tsx scripts/src/sync-workspace-versions.ts`
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
return v;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// src/file-editor/document.ts
|
|
760
|
+
import { readFile } from "fs/promises";
|
|
761
|
+
|
|
762
|
+
// src/file-editor/assertions/index.ts
|
|
763
|
+
import semver from "semver";
|
|
764
|
+
|
|
765
|
+
// src/file-editor/errors.ts
|
|
766
|
+
var _FileEditorError = class extends Error {
|
|
767
|
+
code;
|
|
768
|
+
constructor(code, message) {
|
|
769
|
+
super(message);
|
|
770
|
+
this.name = "_FileEditorError";
|
|
771
|
+
this.code = code;
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
var _ParseError = class extends _FileEditorError {
|
|
775
|
+
format;
|
|
776
|
+
offset;
|
|
777
|
+
snippet;
|
|
778
|
+
constructor(format, message, details = {}) {
|
|
779
|
+
super("PARSE_ERROR", message);
|
|
780
|
+
this.name = "_ParseError";
|
|
781
|
+
this.format = format;
|
|
782
|
+
this.offset = details.offset ?? null;
|
|
783
|
+
this.snippet = details.snippet ?? null;
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
var _PathError = class extends _FileEditorError {
|
|
787
|
+
pointer;
|
|
788
|
+
constructor(pointer, message) {
|
|
789
|
+
super("PATH_ERROR", message);
|
|
790
|
+
this.name = "_PathError";
|
|
791
|
+
this.pointer = pointer;
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
var _TypeMismatchError = class extends _FileEditorError {
|
|
795
|
+
pointer;
|
|
796
|
+
constructor(pointer, message) {
|
|
797
|
+
super("TYPE_MISMATCH", message);
|
|
798
|
+
this.name = "_TypeMismatchError";
|
|
799
|
+
this.pointer = pointer;
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
var _AssertionError = class extends _FileEditorError {
|
|
803
|
+
pointer;
|
|
804
|
+
expected;
|
|
805
|
+
actual;
|
|
806
|
+
constructor(pointer, message, details = {}) {
|
|
807
|
+
super("ASSERTION_ERROR", message);
|
|
808
|
+
this.name = "_AssertionError";
|
|
809
|
+
this.pointer = pointer;
|
|
810
|
+
this.expected = details.expected;
|
|
811
|
+
this.actual = details.actual;
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
var _ConflictError = class extends _FileEditorError {
|
|
815
|
+
conflict;
|
|
816
|
+
constructor(conflict) {
|
|
817
|
+
super(
|
|
818
|
+
"CONFLICT",
|
|
819
|
+
`Drift detected for ${conflict.path}: file was modified outside the tool.`
|
|
820
|
+
);
|
|
821
|
+
this.name = "_ConflictError";
|
|
822
|
+
this.conflict = conflict;
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
var _ConflictAbortedError = class extends _FileEditorError {
|
|
826
|
+
path;
|
|
827
|
+
constructor(path) {
|
|
828
|
+
super("CONFLICT_ABORTED", `Conflict resolution aborted for ${path}.`);
|
|
829
|
+
this.name = "_ConflictAbortedError";
|
|
830
|
+
this.path = path;
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
var _WriteAbortedError = class extends _FileEditorError {
|
|
834
|
+
path;
|
|
835
|
+
constructor(path) {
|
|
836
|
+
super("WRITE_ABORTED", `Write aborted for ${path} via confirmWrite hook.`);
|
|
837
|
+
this.name = "_WriteAbortedError";
|
|
838
|
+
this.path = path;
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
var _FormatNotSupportedError = class extends _FileEditorError {
|
|
842
|
+
path;
|
|
843
|
+
constructor(path, message) {
|
|
844
|
+
super(
|
|
845
|
+
"FORMAT_NOT_SUPPORTED",
|
|
846
|
+
message ?? `Cannot infer format for ${path}; pass options.format explicitly.`
|
|
847
|
+
);
|
|
848
|
+
this.name = "_FormatNotSupportedError";
|
|
849
|
+
this.path = path;
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
// src/file-editor/pointer.ts
|
|
854
|
+
function _parsePointer(pointer) {
|
|
855
|
+
if (pointer === "") return [];
|
|
856
|
+
if (!pointer.startsWith("/")) {
|
|
857
|
+
throw new _PathError(
|
|
858
|
+
pointer,
|
|
859
|
+
`Invalid JSON Pointer: must start with "/" or be empty.`
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
const parts = pointer.slice(1).split("/");
|
|
863
|
+
return parts.map(_unescapeToken);
|
|
864
|
+
}
|
|
865
|
+
function _compilePointer(segments) {
|
|
866
|
+
if (segments.length === 0) return "";
|
|
867
|
+
return "/" + segments.map((s) => _escapeToken(String(s))).join("/");
|
|
868
|
+
}
|
|
869
|
+
function _unescapeToken(token) {
|
|
870
|
+
return token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
871
|
+
}
|
|
872
|
+
function _escapeToken(raw) {
|
|
873
|
+
return raw.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
874
|
+
}
|
|
875
|
+
function _resolvePointer(root, pointer) {
|
|
876
|
+
const segments = _parsePointer(pointer);
|
|
877
|
+
let current = root;
|
|
878
|
+
for (const seg of segments) {
|
|
879
|
+
if (current === null || current === void 0) return void 0;
|
|
880
|
+
if (Array.isArray(current)) {
|
|
881
|
+
if (seg === "-") return void 0;
|
|
882
|
+
const idx = _parseArrayIndex(seg);
|
|
883
|
+
if (idx === null) return void 0;
|
|
884
|
+
current = current[idx];
|
|
885
|
+
} else if (typeof current === "object") {
|
|
886
|
+
const obj = current;
|
|
887
|
+
if (!Object.prototype.hasOwnProperty.call(obj, seg)) return void 0;
|
|
888
|
+
current = obj[seg];
|
|
889
|
+
} else {
|
|
890
|
+
return void 0;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
return current;
|
|
894
|
+
}
|
|
895
|
+
function _hasPointer(root, pointer) {
|
|
896
|
+
const segments = _parsePointer(pointer);
|
|
897
|
+
let current = root;
|
|
898
|
+
for (const seg of segments) {
|
|
899
|
+
if (current === null || current === void 0) return false;
|
|
900
|
+
if (Array.isArray(current)) {
|
|
901
|
+
if (seg === "-") return false;
|
|
902
|
+
const idx = _parseArrayIndex(seg);
|
|
903
|
+
if (idx === null || idx >= current.length) return false;
|
|
904
|
+
current = current[idx];
|
|
905
|
+
} else if (typeof current === "object") {
|
|
906
|
+
const obj = current;
|
|
907
|
+
if (!Object.prototype.hasOwnProperty.call(obj, seg)) return false;
|
|
908
|
+
current = obj[seg];
|
|
909
|
+
} else {
|
|
910
|
+
return false;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
return true;
|
|
914
|
+
}
|
|
915
|
+
function _parseArrayIndex(token) {
|
|
916
|
+
if (token === "0") return 0;
|
|
917
|
+
if (!/^[1-9][0-9]*$/.test(token)) return null;
|
|
918
|
+
return Number.parseInt(token, 10);
|
|
919
|
+
}
|
|
920
|
+
function _pointer() {
|
|
921
|
+
return makePointerProxy([]);
|
|
922
|
+
}
|
|
923
|
+
var _POINTER_STRING = "$";
|
|
924
|
+
function makePointerProxy(segments) {
|
|
925
|
+
return new Proxy(() => void 0, {
|
|
926
|
+
get(_t, prop) {
|
|
927
|
+
if (prop === _POINTER_STRING) return _compilePointer(segments);
|
|
928
|
+
if (typeof prop === "symbol") return void 0;
|
|
929
|
+
const next = /^\d+$/.test(prop) ? Number.parseInt(prop, 10) : prop;
|
|
930
|
+
return makePointerProxy([...segments, next]);
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// src/file-editor/assertions/index.ts
|
|
936
|
+
var DEFAULT_SEVERITY = "error";
|
|
937
|
+
var _check = {
|
|
938
|
+
/** The pointer must resolve to something (including `null`). @internal */
|
|
939
|
+
exists(pointer, opts = {}) {
|
|
940
|
+
return ({ path, value }) => {
|
|
941
|
+
if (_hasPointer(value, pointer)) return [];
|
|
942
|
+
return [
|
|
943
|
+
{
|
|
944
|
+
path,
|
|
945
|
+
pointer,
|
|
946
|
+
code: "MISSING_KEY",
|
|
947
|
+
severity: opts.severity ?? DEFAULT_SEVERITY,
|
|
948
|
+
message: opts.message ?? `required value is missing at ${pointer}`,
|
|
949
|
+
expected: "a value to be present",
|
|
950
|
+
actual: void 0
|
|
951
|
+
}
|
|
952
|
+
];
|
|
953
|
+
};
|
|
954
|
+
},
|
|
955
|
+
/** The value at the pointer must deep-equal `expected`. @internal */
|
|
956
|
+
equals(pointer, expected, opts = {}) {
|
|
957
|
+
return ({ path, value }) => {
|
|
958
|
+
const actual = _resolvePointer(value, pointer);
|
|
959
|
+
if (deepEqual(actual, expected)) return [];
|
|
960
|
+
return [
|
|
961
|
+
{
|
|
962
|
+
path,
|
|
963
|
+
pointer,
|
|
964
|
+
code: "VALUE_MISMATCH",
|
|
965
|
+
severity: opts.severity ?? DEFAULT_SEVERITY,
|
|
966
|
+
message: opts.message ?? `expected ${pointer} to equal ${JSON.stringify(expected)}`,
|
|
967
|
+
expected,
|
|
968
|
+
actual
|
|
969
|
+
}
|
|
970
|
+
];
|
|
971
|
+
};
|
|
972
|
+
},
|
|
973
|
+
/** The value at the pointer must equal one of `allowed`. @internal */
|
|
974
|
+
oneOf(pointer, allowed, opts = {}) {
|
|
975
|
+
return ({ path, value }) => {
|
|
976
|
+
const actual = _resolvePointer(value, pointer);
|
|
977
|
+
if (allowed.some((candidate) => deepEqual(actual, candidate))) return [];
|
|
978
|
+
return [
|
|
979
|
+
{
|
|
980
|
+
path,
|
|
981
|
+
pointer,
|
|
982
|
+
code: "VALUE_NOT_ALLOWED",
|
|
983
|
+
severity: opts.severity ?? DEFAULT_SEVERITY,
|
|
984
|
+
message: opts.message ?? `expected ${pointer} to be one of ${allowed.map((a) => JSON.stringify(a)).join(", ")}`,
|
|
985
|
+
expected: allowed,
|
|
986
|
+
actual
|
|
987
|
+
}
|
|
988
|
+
];
|
|
989
|
+
};
|
|
990
|
+
},
|
|
991
|
+
/** The string value at the pointer must match the given pattern. @internal */
|
|
992
|
+
matches(pointer, pattern, opts = {}) {
|
|
993
|
+
return ({ path, value }) => {
|
|
994
|
+
const actual = _resolvePointer(value, pointer);
|
|
995
|
+
if (typeof actual === "string" && pattern.test(actual)) return [];
|
|
996
|
+
return [
|
|
997
|
+
{
|
|
998
|
+
path,
|
|
999
|
+
pointer,
|
|
1000
|
+
code: typeof actual === "string" ? "PATTERN_MISMATCH" : "TYPE_MISMATCH",
|
|
1001
|
+
severity: opts.severity ?? DEFAULT_SEVERITY,
|
|
1002
|
+
message: opts.message ?? `expected ${pointer} to match ${pattern.toString()}`,
|
|
1003
|
+
expected: pattern.toString(),
|
|
1004
|
+
actual
|
|
1005
|
+
}
|
|
1006
|
+
];
|
|
1007
|
+
};
|
|
1008
|
+
},
|
|
1009
|
+
/**
|
|
1010
|
+
* The value at the pointer must satisfy a caller predicate.
|
|
1011
|
+
* @internal
|
|
1012
|
+
*/
|
|
1013
|
+
satisfies(pointer, predicate, opts = {}) {
|
|
1014
|
+
return ({ path, value }) => {
|
|
1015
|
+
const actual = _resolvePointer(value, pointer);
|
|
1016
|
+
if (predicate(actual)) return [];
|
|
1017
|
+
return [
|
|
1018
|
+
{
|
|
1019
|
+
path,
|
|
1020
|
+
pointer,
|
|
1021
|
+
code: "PREDICATE_FAILED",
|
|
1022
|
+
severity: opts.severity ?? DEFAULT_SEVERITY,
|
|
1023
|
+
message: opts.message ?? `predicate failed at ${pointer}`,
|
|
1024
|
+
actual
|
|
1025
|
+
}
|
|
1026
|
+
];
|
|
1027
|
+
};
|
|
1028
|
+
},
|
|
1029
|
+
/**
|
|
1030
|
+
* The value at the pointer must validate against the supplied Standard
|
|
1031
|
+
* Schema (Zod v4, Valibot, ArkType, etc.). Each schema issue becomes a
|
|
1032
|
+
* `SCHEMA_VIOLATION` diagnostic with the pointer extended by the issue's
|
|
1033
|
+
* path.
|
|
1034
|
+
*
|
|
1035
|
+
* NOTE: the validator is invoked synchronously and its result awaited at
|
|
1036
|
+
* call time, so this returns a `Promise<_Diagnostic[]>` rather than a
|
|
1037
|
+
* synchronous `_Assertion`. Wrap the result with `_runAsyncAssertions` if
|
|
1038
|
+
* you need to compose it with other async checks.
|
|
1039
|
+
* @internal
|
|
1040
|
+
*/
|
|
1041
|
+
matchesSchema(pointer, schema, opts = {}) {
|
|
1042
|
+
return async ({ path, value }) => {
|
|
1043
|
+
const actual = _resolvePointer(value, pointer);
|
|
1044
|
+
const result = await schema["~standard"].validate(actual);
|
|
1045
|
+
if (!result.issues) return [];
|
|
1046
|
+
return result.issues.map(
|
|
1047
|
+
(issue) => ({
|
|
1048
|
+
path,
|
|
1049
|
+
pointer: appendIssuePath(pointer, issue.path),
|
|
1050
|
+
code: "SCHEMA_VIOLATION",
|
|
1051
|
+
severity: opts.severity ?? DEFAULT_SEVERITY,
|
|
1052
|
+
message: opts.message ?? issue.message,
|
|
1053
|
+
actual
|
|
1054
|
+
})
|
|
1055
|
+
);
|
|
1056
|
+
};
|
|
1057
|
+
},
|
|
1058
|
+
/**
|
|
1059
|
+
* The string value at the pointer must be a valid semver range satisfying
|
|
1060
|
+
* the supplied constraint.
|
|
1061
|
+
* @internal
|
|
1062
|
+
*/
|
|
1063
|
+
semverSatisfies(pointer, range, opts = {}) {
|
|
1064
|
+
return ({ path, value }) => {
|
|
1065
|
+
const actual = _resolvePointer(value, pointer);
|
|
1066
|
+
if (typeof actual !== "string") {
|
|
1067
|
+
return [
|
|
1068
|
+
{
|
|
1069
|
+
path,
|
|
1070
|
+
pointer,
|
|
1071
|
+
code: "TYPE_MISMATCH",
|
|
1072
|
+
severity: opts.severity ?? DEFAULT_SEVERITY,
|
|
1073
|
+
message: opts.message ?? `expected a string version at ${pointer}`,
|
|
1074
|
+
expected: "semver string",
|
|
1075
|
+
actual
|
|
1076
|
+
}
|
|
1077
|
+
];
|
|
1078
|
+
}
|
|
1079
|
+
const valid = semver.validRange(actual);
|
|
1080
|
+
if (!valid) {
|
|
1081
|
+
return [
|
|
1082
|
+
{
|
|
1083
|
+
path,
|
|
1084
|
+
pointer,
|
|
1085
|
+
code: "INVALID_SEMVER",
|
|
1086
|
+
severity: opts.severity ?? DEFAULT_SEVERITY,
|
|
1087
|
+
message: opts.message ?? `value at ${pointer} is not a valid semver range`,
|
|
1088
|
+
expected: "valid semver range",
|
|
1089
|
+
actual
|
|
1090
|
+
}
|
|
1091
|
+
];
|
|
1092
|
+
}
|
|
1093
|
+
if (!semver.subset(valid, range)) {
|
|
1094
|
+
return [
|
|
1095
|
+
{
|
|
1096
|
+
path,
|
|
1097
|
+
pointer,
|
|
1098
|
+
code: "SEMVER_OUT_OF_RANGE",
|
|
1099
|
+
severity: opts.severity ?? DEFAULT_SEVERITY,
|
|
1100
|
+
message: opts.message ?? `value at ${pointer} (${actual}) is not a subset of ${range}`,
|
|
1101
|
+
expected: range,
|
|
1102
|
+
actual
|
|
1103
|
+
}
|
|
1104
|
+
];
|
|
1105
|
+
}
|
|
1106
|
+
return [];
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
function _runAssertions(path, value, assertions) {
|
|
1111
|
+
const results = [];
|
|
1112
|
+
for (const a of assertions) {
|
|
1113
|
+
results.push(...a({ path, value }));
|
|
1114
|
+
}
|
|
1115
|
+
return results;
|
|
1116
|
+
}
|
|
1117
|
+
async function _runAsyncAssertions(path, value, assertions) {
|
|
1118
|
+
const results = [];
|
|
1119
|
+
for (const a of assertions) {
|
|
1120
|
+
results.push(...await a({ path, value }));
|
|
1121
|
+
}
|
|
1122
|
+
return results;
|
|
1123
|
+
}
|
|
1124
|
+
function appendIssuePath(base, issuePath) {
|
|
1125
|
+
if (!issuePath || issuePath.length === 0) return base;
|
|
1126
|
+
const suffix = issuePath.map((seg) => {
|
|
1127
|
+
const key = typeof seg === "object" ? seg.key : seg;
|
|
1128
|
+
const s = typeof key === "symbol" ? key.description ?? "" : String(key);
|
|
1129
|
+
return s.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
1130
|
+
}).join("/");
|
|
1131
|
+
return base + "/" + suffix;
|
|
1132
|
+
}
|
|
1133
|
+
function deepEqual(a, b) {
|
|
1134
|
+
if (Object.is(a, b)) return true;
|
|
1135
|
+
if (a === null || b === null) return false;
|
|
1136
|
+
if (typeof a !== typeof b) return false;
|
|
1137
|
+
if (Array.isArray(a)) {
|
|
1138
|
+
if (!Array.isArray(b) || a.length !== b.length) return false;
|
|
1139
|
+
for (let i = 0; i < a.length; i++) {
|
|
1140
|
+
if (!deepEqual(a[i], b[i])) return false;
|
|
1141
|
+
}
|
|
1142
|
+
return true;
|
|
1143
|
+
}
|
|
1144
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
1145
|
+
const ao = a;
|
|
1146
|
+
const bo = b;
|
|
1147
|
+
const keys = Object.keys(ao);
|
|
1148
|
+
if (keys.length !== Object.keys(bo).length) return false;
|
|
1149
|
+
for (const k of keys) {
|
|
1150
|
+
if (!deepEqual(ao[k], bo[k])) return false;
|
|
1151
|
+
}
|
|
1152
|
+
return true;
|
|
1153
|
+
}
|
|
1154
|
+
return false;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// src/file-editor/fingerprint.ts
|
|
1158
|
+
import { createHash } from "crypto";
|
|
1159
|
+
import {
|
|
1160
|
+
parse as parseJsonc,
|
|
1161
|
+
printParseErrorCode
|
|
1162
|
+
} from "jsonc-parser";
|
|
1163
|
+
import { parse as parseYaml } from "yaml";
|
|
1164
|
+
function _canonicalNormalize(text, format) {
|
|
1165
|
+
const value = parseForCanonical(text, format);
|
|
1166
|
+
return _canonicalStringify(value);
|
|
1167
|
+
}
|
|
1168
|
+
function _fingerprint(text, format) {
|
|
1169
|
+
const canonical = _canonicalNormalize(text, format);
|
|
1170
|
+
return _sha256Hex(canonical);
|
|
1171
|
+
}
|
|
1172
|
+
function _sha256Hex(input) {
|
|
1173
|
+
const hash = createHash("sha256");
|
|
1174
|
+
hash.update(input);
|
|
1175
|
+
return hash.digest("hex");
|
|
1176
|
+
}
|
|
1177
|
+
function parseForCanonical(text, format) {
|
|
1178
|
+
const stripped = stripBom(text);
|
|
1179
|
+
switch (format) {
|
|
1180
|
+
case "json":
|
|
1181
|
+
return parseStrictJson(stripped);
|
|
1182
|
+
case "jsonc":
|
|
1183
|
+
return parseJsoncForCanonical(stripped);
|
|
1184
|
+
case "yaml":
|
|
1185
|
+
return parseYamlForCanonical(stripped);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
function stripBom(text) {
|
|
1189
|
+
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
1190
|
+
}
|
|
1191
|
+
function parseStrictJson(text) {
|
|
1192
|
+
try {
|
|
1193
|
+
return JSON.parse(text);
|
|
1194
|
+
} catch (err) {
|
|
1195
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1196
|
+
throw new _ParseError("json", `Invalid JSON: ${message}`);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
function parseJsoncForCanonical(text) {
|
|
1200
|
+
const errors = [];
|
|
1201
|
+
const value = parseJsonc(text, errors, {
|
|
1202
|
+
allowTrailingComma: true,
|
|
1203
|
+
disallowComments: false
|
|
1204
|
+
});
|
|
1205
|
+
if (errors.length > 0) {
|
|
1206
|
+
const first = errors[0];
|
|
1207
|
+
if (first === void 0) return value;
|
|
1208
|
+
throw new _ParseError(
|
|
1209
|
+
"jsonc",
|
|
1210
|
+
`Invalid JSONC at offset ${String(first.offset)}: ${printParseErrorCode(first.error)}`,
|
|
1211
|
+
{ offset: first.offset }
|
|
1212
|
+
);
|
|
1213
|
+
}
|
|
1214
|
+
return value;
|
|
1215
|
+
}
|
|
1216
|
+
function parseYamlForCanonical(text) {
|
|
1217
|
+
try {
|
|
1218
|
+
return parseYaml(text, { merge: true, prettyErrors: false });
|
|
1219
|
+
} catch (err) {
|
|
1220
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1221
|
+
throw new _ParseError("yaml", `Invalid YAML: ${message}`);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
function _canonicalStringify(value) {
|
|
1225
|
+
return stringify(value);
|
|
1226
|
+
}
|
|
1227
|
+
function stringify(value) {
|
|
1228
|
+
if (value === null) return "null";
|
|
1229
|
+
if (value === void 0) return "null";
|
|
1230
|
+
if (typeof value === "string") return JSON.stringify(value.normalize("NFC"));
|
|
1231
|
+
if (typeof value === "number") {
|
|
1232
|
+
if (!Number.isFinite(value)) {
|
|
1233
|
+
throw new _ParseError(
|
|
1234
|
+
"json",
|
|
1235
|
+
`Cannot canonicalize non-finite number: ${value.toString()}`
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
return JSON.stringify(value);
|
|
1239
|
+
}
|
|
1240
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
1241
|
+
if (typeof value === "bigint") return value.toString();
|
|
1242
|
+
if (Array.isArray(value)) {
|
|
1243
|
+
return "[" + value.map(stringify).join(",") + "]";
|
|
1244
|
+
}
|
|
1245
|
+
if (typeof value === "object") {
|
|
1246
|
+
const obj = value;
|
|
1247
|
+
const keys = Object.keys(obj).sort();
|
|
1248
|
+
const parts = [];
|
|
1249
|
+
for (const key of keys) {
|
|
1250
|
+
const v = obj[key];
|
|
1251
|
+
if (v === void 0) continue;
|
|
1252
|
+
parts.push(JSON.stringify(key.normalize("NFC")) + ":" + stringify(v));
|
|
1253
|
+
}
|
|
1254
|
+
return "{" + parts.join(",") + "}";
|
|
1255
|
+
}
|
|
1256
|
+
throw new _ParseError("json", `Cannot canonicalize value of type ${typeof value}`);
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// src/file-editor/formats/jsonc.ts
|
|
1260
|
+
import {
|
|
1261
|
+
applyEdits,
|
|
1262
|
+
modify,
|
|
1263
|
+
parse as jsoncParse,
|
|
1264
|
+
printParseErrorCode as printParseErrorCode2
|
|
1265
|
+
} from "jsonc-parser";
|
|
1266
|
+
|
|
1267
|
+
// src/file-editor/formats/adapter.ts
|
|
1268
|
+
function _defaultMeta() {
|
|
1269
|
+
return { indent: " ", eol: "\n", hasFinalNewline: true, hasBom: false };
|
|
1270
|
+
}
|
|
1271
|
+
function _detectMeta(text) {
|
|
1272
|
+
const hasBom = text.charCodeAt(0) === 65279;
|
|
1273
|
+
const body = hasBom ? text.slice(1) : text;
|
|
1274
|
+
const eol = body.includes("\r\n") ? "\r\n" : "\n";
|
|
1275
|
+
const hasFinalNewline = body.endsWith("\n");
|
|
1276
|
+
const indent = detectIndent(body);
|
|
1277
|
+
return { indent, eol, hasFinalNewline, hasBom };
|
|
1278
|
+
}
|
|
1279
|
+
function detectIndent(text) {
|
|
1280
|
+
const lines = text.split(/\r?\n/);
|
|
1281
|
+
for (const line of lines) {
|
|
1282
|
+
const m = /^([ \t]+)\S/.exec(line);
|
|
1283
|
+
if (m?.[1]) return m[1];
|
|
1284
|
+
}
|
|
1285
|
+
return " ";
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// src/file-editor/formats/jsonc.ts
|
|
1289
|
+
function createJsoncFlavor(format, allowComments) {
|
|
1290
|
+
return {
|
|
1291
|
+
format,
|
|
1292
|
+
permitsComments: allowComments,
|
|
1293
|
+
parse(text) {
|
|
1294
|
+
const errors = [];
|
|
1295
|
+
const body = stripBom2(text);
|
|
1296
|
+
const value = jsoncParse(body, errors, {
|
|
1297
|
+
allowTrailingComma: allowComments,
|
|
1298
|
+
disallowComments: !allowComments
|
|
1299
|
+
});
|
|
1300
|
+
if (errors.length > 0) {
|
|
1301
|
+
const first = errors[0];
|
|
1302
|
+
if (first === void 0) return { value, meta: _detectMeta(text) };
|
|
1303
|
+
throw new _ParseError(
|
|
1304
|
+
format,
|
|
1305
|
+
`Invalid ${format.toUpperCase()} at offset ${String(first.offset)}: ${printParseErrorCode2(first.error)}`,
|
|
1306
|
+
{ offset: first.offset, snippet: text.slice(first.offset, first.offset + 40) }
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
return { value, meta: _detectMeta(text) };
|
|
1310
|
+
},
|
|
1311
|
+
applyEdits(text, edits, meta) {
|
|
1312
|
+
let current = text === "" ? "{}" : text;
|
|
1313
|
+
for (const edit of edits) {
|
|
1314
|
+
current = applyOne(current, edit, meta, format);
|
|
1315
|
+
}
|
|
1316
|
+
if (meta.hasFinalNewline && !current.endsWith(meta.eol)) {
|
|
1317
|
+
current = current + meta.eol;
|
|
1318
|
+
}
|
|
1319
|
+
if (meta.hasBom && !current.startsWith("\uFEFF")) {
|
|
1320
|
+
current = "\uFEFF" + current;
|
|
1321
|
+
}
|
|
1322
|
+
return current;
|
|
1323
|
+
}
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
function applyOne(text, edit, meta, format) {
|
|
1327
|
+
const path = pointerToPath(edit.pointer);
|
|
1328
|
+
const formattingOptions = {
|
|
1329
|
+
tabSize: meta.indent.startsWith(" ") ? 1 : meta.indent.length || 2,
|
|
1330
|
+
insertSpaces: !meta.indent.startsWith(" "),
|
|
1331
|
+
eol: meta.eol
|
|
1332
|
+
};
|
|
1333
|
+
const currentValue = parseCurrent(text, format);
|
|
1334
|
+
switch (edit.kind) {
|
|
1335
|
+
case "set": {
|
|
1336
|
+
const jsonEdits = modify(text, path, edit.value, { formattingOptions });
|
|
1337
|
+
return applyEdits(text, jsonEdits);
|
|
1338
|
+
}
|
|
1339
|
+
case "addIfMissing": {
|
|
1340
|
+
if (_hasPointer(currentValue, edit.pointer)) return text;
|
|
1341
|
+
const jsonEdits = modify(text, path, edit.value, { formattingOptions });
|
|
1342
|
+
return applyEdits(text, jsonEdits);
|
|
1343
|
+
}
|
|
1344
|
+
case "updateIfPresent": {
|
|
1345
|
+
if (!_hasPointer(currentValue, edit.pointer)) return text;
|
|
1346
|
+
if (!edit.updater) {
|
|
1347
|
+
throw new _PathError(
|
|
1348
|
+
edit.pointer,
|
|
1349
|
+
"updateIfPresent requires an updater function."
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1352
|
+
const prior = _resolvePointer(currentValue, edit.pointer);
|
|
1353
|
+
const next = edit.updater(prior);
|
|
1354
|
+
const jsonEdits = modify(text, path, next, { formattingOptions });
|
|
1355
|
+
return applyEdits(text, jsonEdits);
|
|
1356
|
+
}
|
|
1357
|
+
case "remove": {
|
|
1358
|
+
if (!_hasPointer(currentValue, edit.pointer)) return text;
|
|
1359
|
+
const jsonEdits = modify(text, path, void 0, { formattingOptions });
|
|
1360
|
+
return applyEdits(text, jsonEdits);
|
|
1361
|
+
}
|
|
1362
|
+
case "merge": {
|
|
1363
|
+
const prior = _resolvePointer(currentValue, edit.pointer);
|
|
1364
|
+
const merged = mergeValues(prior, edit.value, edit.mergeOptions, edit.pointer);
|
|
1365
|
+
const jsonEdits = modify(text, path, merged, { formattingOptions });
|
|
1366
|
+
return applyEdits(text, jsonEdits);
|
|
1367
|
+
}
|
|
1368
|
+
case "arrayAppend": {
|
|
1369
|
+
const prior = _resolvePointer(currentValue, edit.pointer);
|
|
1370
|
+
if (prior === void 0) {
|
|
1371
|
+
const jsonEdits2 = modify(text, path, [edit.value], { formattingOptions });
|
|
1372
|
+
return applyEdits(text, jsonEdits2);
|
|
1373
|
+
}
|
|
1374
|
+
if (!Array.isArray(prior)) {
|
|
1375
|
+
throw new _TypeMismatchError(
|
|
1376
|
+
edit.pointer,
|
|
1377
|
+
`arrayAppend requires an array at ${edit.pointer}`
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
const appendPath = [...path, prior.length];
|
|
1381
|
+
const jsonEdits = modify(text, appendPath, edit.value, {
|
|
1382
|
+
formattingOptions,
|
|
1383
|
+
isArrayInsertion: true
|
|
1384
|
+
});
|
|
1385
|
+
return applyEdits(text, jsonEdits);
|
|
1386
|
+
}
|
|
1387
|
+
case "arrayUpsert": {
|
|
1388
|
+
const prior = _resolvePointer(currentValue, edit.pointer);
|
|
1389
|
+
const keyFn = edit.arrayKey;
|
|
1390
|
+
if (!keyFn) throw new _PathError(edit.pointer, "arrayUpsert requires a keyFn.");
|
|
1391
|
+
const newElement = edit.value;
|
|
1392
|
+
if (prior === void 0) {
|
|
1393
|
+
const jsonEdits2 = modify(text, path, [newElement], { formattingOptions });
|
|
1394
|
+
return applyEdits(text, jsonEdits2);
|
|
1395
|
+
}
|
|
1396
|
+
if (!Array.isArray(prior)) {
|
|
1397
|
+
throw new _TypeMismatchError(
|
|
1398
|
+
edit.pointer,
|
|
1399
|
+
`arrayUpsert requires an array at ${edit.pointer}`
|
|
1400
|
+
);
|
|
1401
|
+
}
|
|
1402
|
+
const targetKey = keyFn(newElement);
|
|
1403
|
+
const idx = prior.findIndex((el) => keyFn(el) === targetKey);
|
|
1404
|
+
if (idx >= 0) {
|
|
1405
|
+
const jsonEdits2 = modify(text, [...path, idx], newElement, { formattingOptions });
|
|
1406
|
+
return applyEdits(text, jsonEdits2);
|
|
1407
|
+
}
|
|
1408
|
+
const appendPath = [...path, prior.length];
|
|
1409
|
+
const jsonEdits = modify(text, appendPath, newElement, {
|
|
1410
|
+
formattingOptions,
|
|
1411
|
+
isArrayInsertion: true
|
|
1412
|
+
});
|
|
1413
|
+
return applyEdits(text, jsonEdits);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
function pointerToPath(pointer) {
|
|
1418
|
+
const segments = _parsePointer(pointer);
|
|
1419
|
+
return segments.map((seg) => {
|
|
1420
|
+
const idx = _parseArrayIndex(seg);
|
|
1421
|
+
return idx ?? seg;
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
function stripBom2(text) {
|
|
1425
|
+
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
1426
|
+
}
|
|
1427
|
+
function parseCurrent(text, format) {
|
|
1428
|
+
const errors = [];
|
|
1429
|
+
const value = jsoncParse(stripBom2(text), errors, {
|
|
1430
|
+
allowTrailingComma: format === "jsonc",
|
|
1431
|
+
disallowComments: format === "json"
|
|
1432
|
+
});
|
|
1433
|
+
if (errors.length > 0) {
|
|
1434
|
+
const first = errors[0];
|
|
1435
|
+
if (first === void 0) return value;
|
|
1436
|
+
throw new _ParseError(
|
|
1437
|
+
format,
|
|
1438
|
+
`Invalid ${format}: ${printParseErrorCode2(first.error)}`,
|
|
1439
|
+
{
|
|
1440
|
+
offset: first.offset
|
|
1441
|
+
}
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
return value;
|
|
1445
|
+
}
|
|
1446
|
+
function mergeValues(prior, incoming, options, pointer) {
|
|
1447
|
+
const deep = options?.deep ?? true;
|
|
1448
|
+
const arrayStrategy = options?.arrays ?? "replace";
|
|
1449
|
+
if (prior === void 0 || prior === null) return incoming;
|
|
1450
|
+
if (!isPlainObject(prior) || !isPlainObject(incoming)) {
|
|
1451
|
+
if (Array.isArray(prior) && Array.isArray(incoming)) {
|
|
1452
|
+
return mergeArrays(prior, incoming, arrayStrategy);
|
|
1453
|
+
}
|
|
1454
|
+
throw new _TypeMismatchError(
|
|
1455
|
+
pointer,
|
|
1456
|
+
"merge requires an object at the target pointer."
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
const out = { ...prior };
|
|
1460
|
+
for (const [key, val] of Object.entries(incoming)) {
|
|
1461
|
+
const existing = out[key];
|
|
1462
|
+
if (deep && isPlainObject(existing) && isPlainObject(val)) {
|
|
1463
|
+
out[key] = mergeValues(existing, val, options, pointer + "/" + key);
|
|
1464
|
+
} else if (Array.isArray(existing) && Array.isArray(val)) {
|
|
1465
|
+
out[key] = mergeArrays(existing, val, arrayStrategy);
|
|
1466
|
+
} else {
|
|
1467
|
+
out[key] = val;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
return out;
|
|
1471
|
+
}
|
|
1472
|
+
function mergeArrays(a, b, strategy) {
|
|
1473
|
+
switch (strategy) {
|
|
1474
|
+
case "replace":
|
|
1475
|
+
return [...b];
|
|
1476
|
+
case "concat":
|
|
1477
|
+
return [...a, ...b];
|
|
1478
|
+
case "concatUnique": {
|
|
1479
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1480
|
+
const out = [];
|
|
1481
|
+
for (const v of [...a, ...b]) {
|
|
1482
|
+
const key = stableStringify(v);
|
|
1483
|
+
if (!seen.has(key)) {
|
|
1484
|
+
seen.add(key);
|
|
1485
|
+
out.push(v);
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
return out;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
function stableStringify(v) {
|
|
1493
|
+
return _canonicalNormalize(JSON.stringify(v), "json");
|
|
1494
|
+
}
|
|
1495
|
+
function isPlainObject(v) {
|
|
1496
|
+
if (v === null || typeof v !== "object") return false;
|
|
1497
|
+
if (Array.isArray(v)) return false;
|
|
1498
|
+
const proto = Object.getPrototypeOf(v);
|
|
1499
|
+
return proto === Object.prototype || proto === null;
|
|
1500
|
+
}
|
|
1501
|
+
var jsonAdapter = createJsoncFlavor("json", false);
|
|
1502
|
+
var jsoncAdapter = createJsoncFlavor("jsonc", true);
|
|
1503
|
+
|
|
1504
|
+
// src/file-editor/formats/yaml.ts
|
|
1505
|
+
import YAML, { isCollection, isMap, isSeq } from "yaml";
|
|
1506
|
+
var yamlAdapter = {
|
|
1507
|
+
format: "yaml",
|
|
1508
|
+
permitsComments: true,
|
|
1509
|
+
parse(text) {
|
|
1510
|
+
const doc = YAML.parseDocument(text, { merge: true, prettyErrors: false });
|
|
1511
|
+
const value = doc.toJS();
|
|
1512
|
+
if (doc.errors.length > 0) {
|
|
1513
|
+
const first = doc.errors[0];
|
|
1514
|
+
if (first === void 0) return { value, meta: _detectMeta(text) };
|
|
1515
|
+
throw new _ParseError("yaml", `Invalid YAML: ${first.message}`, {
|
|
1516
|
+
offset: first.pos[0],
|
|
1517
|
+
snippet: text.slice(first.pos[0], first.pos[0] + 40)
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1520
|
+
return { value, meta: _detectMeta(text) };
|
|
1521
|
+
},
|
|
1522
|
+
applyEdits(text, edits, meta) {
|
|
1523
|
+
const source = text === "" ? "{}\n" : text;
|
|
1524
|
+
const doc = YAML.parseDocument(source, { merge: true });
|
|
1525
|
+
if (doc.errors.length > 0) {
|
|
1526
|
+
const first = doc.errors[0];
|
|
1527
|
+
if (first !== void 0) {
|
|
1528
|
+
throw new _ParseError("yaml", `Invalid YAML: ${first.message}`);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
for (const edit of edits) {
|
|
1532
|
+
applyYamlEdit(doc, edit);
|
|
1533
|
+
}
|
|
1534
|
+
let result = doc.toString();
|
|
1535
|
+
if (meta.eol === "\r\n") {
|
|
1536
|
+
result = result.replace(/\r?\n/g, "\r\n");
|
|
1537
|
+
}
|
|
1538
|
+
const endsWithEol = result.endsWith(meta.eol);
|
|
1539
|
+
if (meta.hasFinalNewline && !endsWithEol) {
|
|
1540
|
+
result = result + meta.eol;
|
|
1541
|
+
} else if (!meta.hasFinalNewline && endsWithEol) {
|
|
1542
|
+
result = result.slice(0, -meta.eol.length);
|
|
1543
|
+
}
|
|
1544
|
+
if (meta.hasBom && !result.startsWith("\uFEFF")) {
|
|
1545
|
+
result = "\uFEFF" + result;
|
|
1546
|
+
}
|
|
1547
|
+
return result;
|
|
1548
|
+
}
|
|
1549
|
+
};
|
|
1550
|
+
function applyYamlEdit(doc, edit) {
|
|
1551
|
+
const segments = toYamlPath(edit.pointer);
|
|
1552
|
+
switch (edit.kind) {
|
|
1553
|
+
case "set": {
|
|
1554
|
+
setInWithParents(doc, segments, edit.value);
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
case "addIfMissing": {
|
|
1558
|
+
if (doc.hasIn(segments)) return;
|
|
1559
|
+
setInWithParents(doc, segments, edit.value);
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
case "updateIfPresent": {
|
|
1563
|
+
if (!doc.hasIn(segments)) return;
|
|
1564
|
+
if (!edit.updater)
|
|
1565
|
+
throw new _PathError(edit.pointer, "updateIfPresent requires an updater.");
|
|
1566
|
+
const prior = jsValueAt(doc, segments);
|
|
1567
|
+
const next = edit.updater(prior);
|
|
1568
|
+
doc.setIn(segments, next);
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
case "remove": {
|
|
1572
|
+
if (!doc.hasIn(segments)) return;
|
|
1573
|
+
doc.deleteIn(segments);
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
case "merge": {
|
|
1577
|
+
const prior = jsValueAt(doc, segments);
|
|
1578
|
+
const merged = mergeValues2(prior, edit.value, edit.mergeOptions, edit.pointer);
|
|
1579
|
+
setInWithParents(doc, segments, merged);
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
case "arrayAppend": {
|
|
1583
|
+
const node = segments.length === 0 ? doc.contents : doc.getIn(segments, true);
|
|
1584
|
+
if (node === void 0 || node === null) {
|
|
1585
|
+
setInWithParents(doc, segments, [edit.value]);
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
if (!isSeq(node)) {
|
|
1589
|
+
throw new _TypeMismatchError(
|
|
1590
|
+
edit.pointer,
|
|
1591
|
+
`arrayAppend requires a sequence at ${edit.pointer}`
|
|
1592
|
+
);
|
|
1593
|
+
}
|
|
1594
|
+
node.add(edit.value);
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
case "arrayUpsert": {
|
|
1598
|
+
const keyFn = edit.arrayKey;
|
|
1599
|
+
if (!keyFn) throw new _PathError(edit.pointer, "arrayUpsert requires a keyFn.");
|
|
1600
|
+
const priorJs = jsValueAt(doc, segments);
|
|
1601
|
+
const newElement = edit.value;
|
|
1602
|
+
if (priorJs === void 0 || priorJs === null) {
|
|
1603
|
+
setInWithParents(doc, segments, [newElement]);
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
if (!Array.isArray(priorJs)) {
|
|
1607
|
+
throw new _TypeMismatchError(
|
|
1608
|
+
edit.pointer,
|
|
1609
|
+
`arrayUpsert requires a sequence at ${edit.pointer}`
|
|
1610
|
+
);
|
|
1611
|
+
}
|
|
1612
|
+
const targetKey = keyFn(newElement);
|
|
1613
|
+
const idx = priorJs.findIndex((el) => keyFn(el) === targetKey);
|
|
1614
|
+
if (idx >= 0) {
|
|
1615
|
+
doc.setIn([...segments, idx], newElement);
|
|
1616
|
+
} else {
|
|
1617
|
+
const seqNode = segments.length === 0 ? doc.contents : doc.getIn(segments, true);
|
|
1618
|
+
if (isSeq(seqNode)) {
|
|
1619
|
+
seqNode.add(newElement);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
function toYamlPath(pointer) {
|
|
1627
|
+
const segments = _parsePointer(pointer);
|
|
1628
|
+
return segments.map((seg) => {
|
|
1629
|
+
const idx = _parseArrayIndex(seg);
|
|
1630
|
+
return idx ?? seg;
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
function jsValueAt(doc, segments) {
|
|
1634
|
+
if (segments.length === 0) {
|
|
1635
|
+
return doc.toJS();
|
|
1636
|
+
}
|
|
1637
|
+
return _resolvePointer(
|
|
1638
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- yaml's toJS() returns any; widening to unknown is safe
|
|
1639
|
+
doc.toJS(),
|
|
1640
|
+
"/" + segments.map((s) => String(s)).join("/")
|
|
1641
|
+
);
|
|
1642
|
+
}
|
|
1643
|
+
function setInWithParents(doc, segments, value) {
|
|
1644
|
+
if (segments.length === 0) {
|
|
1645
|
+
doc.contents = doc.createNode(value);
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
const partial = [];
|
|
1649
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
1650
|
+
const seg = segments[i];
|
|
1651
|
+
if (seg === void 0) break;
|
|
1652
|
+
partial.push(seg);
|
|
1653
|
+
if (!doc.hasIn(partial)) break;
|
|
1654
|
+
const cur = doc.getIn(partial, true);
|
|
1655
|
+
if (!isCollection(cur)) {
|
|
1656
|
+
throw new _TypeMismatchError(
|
|
1657
|
+
"/" + partial.map(String).join("/"),
|
|
1658
|
+
`cannot descend through non-collection value`
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
const needsIndex = typeof segments[i + 1] === "number";
|
|
1662
|
+
if (needsIndex && !isSeq(cur)) {
|
|
1663
|
+
throw new _TypeMismatchError(
|
|
1664
|
+
"/" + partial.map(String).join("/"),
|
|
1665
|
+
`expected sequence, found mapping`
|
|
1666
|
+
);
|
|
1667
|
+
}
|
|
1668
|
+
if (!needsIndex && !isMap(cur)) {
|
|
1669
|
+
throw new _TypeMismatchError(
|
|
1670
|
+
"/" + partial.map(String).join("/"),
|
|
1671
|
+
`expected mapping, found sequence`
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
doc.setIn(segments, doc.createNode(value));
|
|
1676
|
+
}
|
|
1677
|
+
function mergeValues2(prior, incoming, options, pointer) {
|
|
1678
|
+
const deep = options?.deep ?? true;
|
|
1679
|
+
const arrayStrategy = options?.arrays ?? "replace";
|
|
1680
|
+
if (prior === void 0 || prior === null) return incoming;
|
|
1681
|
+
if (!isPlainObject2(prior) || !isPlainObject2(incoming)) {
|
|
1682
|
+
if (Array.isArray(prior) && Array.isArray(incoming)) {
|
|
1683
|
+
return mergeArrays2(prior, incoming, arrayStrategy);
|
|
1684
|
+
}
|
|
1685
|
+
throw new _TypeMismatchError(
|
|
1686
|
+
pointer,
|
|
1687
|
+
"merge requires a mapping at the target pointer."
|
|
1688
|
+
);
|
|
1689
|
+
}
|
|
1690
|
+
const out = { ...prior };
|
|
1691
|
+
for (const [key, val] of Object.entries(incoming)) {
|
|
1692
|
+
const existing = out[key];
|
|
1693
|
+
if (deep && isPlainObject2(existing) && isPlainObject2(val)) {
|
|
1694
|
+
out[key] = mergeValues2(existing, val, options, pointer + "/" + key);
|
|
1695
|
+
} else if (Array.isArray(existing) && Array.isArray(val)) {
|
|
1696
|
+
out[key] = mergeArrays2(existing, val, arrayStrategy);
|
|
1697
|
+
} else {
|
|
1698
|
+
out[key] = val;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
return out;
|
|
1702
|
+
}
|
|
1703
|
+
function mergeArrays2(a, b, strategy) {
|
|
1704
|
+
switch (strategy) {
|
|
1705
|
+
case "replace":
|
|
1706
|
+
return [...b];
|
|
1707
|
+
case "concat":
|
|
1708
|
+
return [...a, ...b];
|
|
1709
|
+
case "concatUnique": {
|
|
1710
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1711
|
+
const out = [];
|
|
1712
|
+
for (const v of [...a, ...b]) {
|
|
1713
|
+
const key = _canonicalNormalize(JSON.stringify(v), "json");
|
|
1714
|
+
if (!seen.has(key)) {
|
|
1715
|
+
seen.add(key);
|
|
1716
|
+
out.push(v);
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
return out;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
function isPlainObject2(v) {
|
|
1724
|
+
if (v === null || typeof v !== "object") return false;
|
|
1725
|
+
if (Array.isArray(v)) return false;
|
|
1726
|
+
const proto = Object.getPrototypeOf(v);
|
|
1727
|
+
return proto === Object.prototype || proto === null;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
// src/file-editor/formats/detect.ts
|
|
1731
|
+
import { extname } from "path";
|
|
1732
|
+
function _detectFormat(path, text) {
|
|
1733
|
+
const ext = extname(path).toLowerCase();
|
|
1734
|
+
switch (ext) {
|
|
1735
|
+
case ".json":
|
|
1736
|
+
if (text !== void 0 && containsJsoncMarkers(text)) return "jsonc";
|
|
1737
|
+
return inferJsonFlavor(path);
|
|
1738
|
+
case ".jsonc":
|
|
1739
|
+
return "jsonc";
|
|
1740
|
+
case ".yaml":
|
|
1741
|
+
case ".yml":
|
|
1742
|
+
return "yaml";
|
|
1743
|
+
default:
|
|
1744
|
+
throw new _FormatNotSupportedError(path);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
var JSONC_PATH_HINTS = [
|
|
1748
|
+
"tsconfig.json",
|
|
1749
|
+
"tsconfig.base.json",
|
|
1750
|
+
"jsconfig.json",
|
|
1751
|
+
"api-extractor.json",
|
|
1752
|
+
".vscode/settings.json",
|
|
1753
|
+
".vscode/launch.json"
|
|
1754
|
+
];
|
|
1755
|
+
function inferJsonFlavor(path) {
|
|
1756
|
+
const normalized = path.replace(/\\/g, "/").toLowerCase();
|
|
1757
|
+
for (const hint of JSONC_PATH_HINTS) {
|
|
1758
|
+
if (normalized.endsWith("/" + hint) || normalized === hint) return "jsonc";
|
|
1759
|
+
if (/\/tsconfig\.[^/]*\.json$/.test(normalized)) return "jsonc";
|
|
1760
|
+
}
|
|
1761
|
+
return "json";
|
|
1762
|
+
}
|
|
1763
|
+
function containsJsoncMarkers(text) {
|
|
1764
|
+
return scanForCommentOrTrailingComma(text);
|
|
1765
|
+
}
|
|
1766
|
+
function scanForCommentOrTrailingComma(text) {
|
|
1767
|
+
let inString = false;
|
|
1768
|
+
let escape = false;
|
|
1769
|
+
for (let i = 0; i < text.length; i++) {
|
|
1770
|
+
const ch = text.charCodeAt(i);
|
|
1771
|
+
if (escape) {
|
|
1772
|
+
escape = false;
|
|
1773
|
+
continue;
|
|
1774
|
+
}
|
|
1775
|
+
if (inString) {
|
|
1776
|
+
if (ch === 92) escape = true;
|
|
1777
|
+
else if (ch === 34) inString = false;
|
|
1778
|
+
continue;
|
|
1779
|
+
}
|
|
1780
|
+
if (ch === 34) {
|
|
1781
|
+
inString = true;
|
|
1782
|
+
continue;
|
|
1783
|
+
}
|
|
1784
|
+
if (ch === 47) {
|
|
1785
|
+
const next = text.charCodeAt(i + 1);
|
|
1786
|
+
if (next === 47 || next === 42) return true;
|
|
1787
|
+
}
|
|
1788
|
+
if (ch === 44) {
|
|
1789
|
+
for (let j = i + 1; j < text.length; j++) {
|
|
1790
|
+
const nc = text.charCodeAt(j);
|
|
1791
|
+
if (nc === 32 || nc === 9 || nc === 10 || nc === 13) continue;
|
|
1792
|
+
if (nc === 93 || nc === 125) return true;
|
|
1793
|
+
break;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
return false;
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
// src/file-editor/formats/index.ts
|
|
1801
|
+
var REGISTRY = /* @__PURE__ */ new Map([
|
|
1802
|
+
["json", jsonAdapter],
|
|
1803
|
+
["jsonc", jsoncAdapter],
|
|
1804
|
+
["yaml", yamlAdapter]
|
|
1805
|
+
]);
|
|
1806
|
+
function _getAdapter(format) {
|
|
1807
|
+
const adapter = REGISTRY.get(format);
|
|
1808
|
+
if (!adapter) {
|
|
1809
|
+
throw new Error(`No adapter registered for format "${format}"`);
|
|
1810
|
+
}
|
|
1811
|
+
return adapter;
|
|
1812
|
+
}
|
|
1813
|
+
function _registerFormatAdapter(format, adapter) {
|
|
1814
|
+
REGISTRY.set(format, adapter);
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
// src/file-editor/document.ts
|
|
1818
|
+
var _Document = class {
|
|
1819
|
+
absolutePath;
|
|
1820
|
+
format;
|
|
1821
|
+
/** Optional Standard Schema; when set, {@link _Document.validate} runs it. */
|
|
1822
|
+
schema;
|
|
1823
|
+
/** Original on-disk text (may be `""` for new files). */
|
|
1824
|
+
originalText;
|
|
1825
|
+
meta;
|
|
1826
|
+
edits = [];
|
|
1827
|
+
staticDiagnostics = [];
|
|
1828
|
+
constructor(init) {
|
|
1829
|
+
this.absolutePath = init.absolutePath;
|
|
1830
|
+
this.format = init.format;
|
|
1831
|
+
this.originalText = init.originalText;
|
|
1832
|
+
this.meta = init.meta;
|
|
1833
|
+
this.schema = init.schema ?? null;
|
|
1834
|
+
}
|
|
1835
|
+
// ── Reads ──────────────────────────────────────────────────────────
|
|
1836
|
+
/** Get the value at a pointer. Reflects pending edits via `preview()`. */
|
|
1837
|
+
get(pointer) {
|
|
1838
|
+
const current = this.parseCurrentValue();
|
|
1839
|
+
return _resolvePointer(current, pointer);
|
|
1840
|
+
}
|
|
1841
|
+
/** Like `get` but throws {@link _AssertionError} on miss. */
|
|
1842
|
+
getRequired(pointer) {
|
|
1843
|
+
const current = this.parseCurrentValue();
|
|
1844
|
+
if (!_hasPointer(current, pointer)) {
|
|
1845
|
+
throw new _AssertionError(pointer, `required value is missing at ${pointer}`);
|
|
1846
|
+
}
|
|
1847
|
+
return _resolvePointer(current, pointer);
|
|
1848
|
+
}
|
|
1849
|
+
has(pointer) {
|
|
1850
|
+
return _hasPointer(this.parseCurrentValue(), pointer);
|
|
1851
|
+
}
|
|
1852
|
+
// ── Mutations (staged) ─────────────────────────────────────────────
|
|
1853
|
+
set(pointer, value, opts) {
|
|
1854
|
+
return this.stage({ kind: "set", pointer, value, ...this.opts(opts) });
|
|
1855
|
+
}
|
|
1856
|
+
addIfMissing(pointer, value, opts) {
|
|
1857
|
+
return this.stage({ kind: "addIfMissing", pointer, value, ...this.opts(opts) });
|
|
1858
|
+
}
|
|
1859
|
+
updateIfPresent(pointer, updater, opts) {
|
|
1860
|
+
return this.stage({
|
|
1861
|
+
kind: "updateIfPresent",
|
|
1862
|
+
pointer,
|
|
1863
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-unsafe-type-assertion -- _PendingEdit.updater is typed as (unknown) => unknown; widening the typed updater is safe since it will receive the same runtime value
|
|
1864
|
+
updater,
|
|
1865
|
+
...this.opts(opts)
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1868
|
+
remove(pointer, opts) {
|
|
1869
|
+
return this.stage({ kind: "remove", pointer, ...this.opts(opts) });
|
|
1870
|
+
}
|
|
1871
|
+
merge(pointer, partial, opts) {
|
|
1872
|
+
const base = {
|
|
1873
|
+
kind: "merge",
|
|
1874
|
+
pointer,
|
|
1875
|
+
value: partial,
|
|
1876
|
+
...this.opts(opts)
|
|
1877
|
+
};
|
|
1878
|
+
return this.stage(
|
|
1879
|
+
opts?.mergeOptions ? { ...base, mergeOptions: opts.mergeOptions } : base
|
|
1880
|
+
);
|
|
1881
|
+
}
|
|
1882
|
+
arrayAppend(pointer, value, opts) {
|
|
1883
|
+
return this.stage({ kind: "arrayAppend", pointer, value, ...this.opts(opts) });
|
|
1884
|
+
}
|
|
1885
|
+
arrayUpsert(pointer, value, keyFn, opts) {
|
|
1886
|
+
return this.stage({
|
|
1887
|
+
kind: "arrayUpsert",
|
|
1888
|
+
pointer,
|
|
1889
|
+
value,
|
|
1890
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-unsafe-type-assertion -- _PendingEdit.arrayKey is typed as (unknown) => unknown; widening the typed keyFn is safe since it will receive the same runtime value
|
|
1891
|
+
arrayKey: keyFn,
|
|
1892
|
+
...this.opts(opts)
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
// ── Assertions ─────────────────────────────────────────────────────
|
|
1896
|
+
assert(...checks) {
|
|
1897
|
+
const results = _runAssertions(this.absolutePath, this.parseCurrentValue(), checks);
|
|
1898
|
+
this.staticDiagnostics.push(...results);
|
|
1899
|
+
return results;
|
|
1900
|
+
}
|
|
1901
|
+
/**
|
|
1902
|
+
* If a schema is configured, validate the current preview against it and
|
|
1903
|
+
* return a diagnostic per issue. Returns `[]` when no schema is set or
|
|
1904
|
+
* validation passes.
|
|
1905
|
+
*
|
|
1906
|
+
* Each call replaces any previously accumulated `SCHEMA_VIOLATION` diagnostics
|
|
1907
|
+
* so that calling `validate()` multiple times does not duplicate findings.
|
|
1908
|
+
*/
|
|
1909
|
+
async validate() {
|
|
1910
|
+
if (!this.schema) return [];
|
|
1911
|
+
const result = await this.schema["~standard"].validate(this.parseCurrentValue());
|
|
1912
|
+
const withoutPriorSchema = this.staticDiagnostics.filter(
|
|
1913
|
+
(d) => d.code !== "SCHEMA_VIOLATION"
|
|
1914
|
+
);
|
|
1915
|
+
this.staticDiagnostics.length = 0;
|
|
1916
|
+
this.staticDiagnostics.push(...withoutPriorSchema);
|
|
1917
|
+
if (!result.issues) return [];
|
|
1918
|
+
const diagnostics = result.issues.map((issue) => ({
|
|
1919
|
+
path: this.absolutePath,
|
|
1920
|
+
pointer: issuePathToPointer(issue.path),
|
|
1921
|
+
code: "SCHEMA_VIOLATION",
|
|
1922
|
+
severity: "error",
|
|
1923
|
+
message: issue.message
|
|
1924
|
+
}));
|
|
1925
|
+
this.staticDiagnostics.push(...diagnostics);
|
|
1926
|
+
return diagnostics;
|
|
1927
|
+
}
|
|
1928
|
+
diagnostics() {
|
|
1929
|
+
return this.staticDiagnostics;
|
|
1930
|
+
}
|
|
1931
|
+
// ── Introspection ──────────────────────────────────────────────────
|
|
1932
|
+
pendingEdits() {
|
|
1933
|
+
return this.edits;
|
|
1934
|
+
}
|
|
1935
|
+
/** Serialize with all pending edits applied. Pure — no disk I/O. */
|
|
1936
|
+
preview() {
|
|
1937
|
+
if (this.edits.length === 0) return this.originalText;
|
|
1938
|
+
const adapter = _getAdapter(this.format);
|
|
1939
|
+
return adapter.applyEdits(this.originalText, this.edits, this.meta);
|
|
1940
|
+
}
|
|
1941
|
+
/** Reset all pending edits. Used by transactions on rollback. */
|
|
1942
|
+
rollback() {
|
|
1943
|
+
this.edits.length = 0;
|
|
1944
|
+
}
|
|
1945
|
+
/** @internal Metadata for transaction serialization. */
|
|
1946
|
+
getMeta() {
|
|
1947
|
+
return this.meta;
|
|
1948
|
+
}
|
|
1949
|
+
/** @internal Original text, for transaction fingerprinting. */
|
|
1950
|
+
getOriginalText() {
|
|
1951
|
+
return this.originalText;
|
|
1952
|
+
}
|
|
1953
|
+
// ── Internals ──────────────────────────────────────────────────────
|
|
1954
|
+
parseCurrentValue() {
|
|
1955
|
+
const text = this.preview();
|
|
1956
|
+
if (text === "") return void 0;
|
|
1957
|
+
const adapter = _getAdapter(this.format);
|
|
1958
|
+
return adapter.parse(text).value;
|
|
1959
|
+
}
|
|
1960
|
+
stage(edit) {
|
|
1961
|
+
if (edit.guard && !edit.guard(this.parseCurrentValue())) return this;
|
|
1962
|
+
this.edits.push(edit);
|
|
1963
|
+
return this;
|
|
1964
|
+
}
|
|
1965
|
+
opts(opts) {
|
|
1966
|
+
const out = {};
|
|
1967
|
+
if (opts?.conflictPolicy !== void 0) out.conflictPolicy = opts.conflictPolicy;
|
|
1968
|
+
if (opts?.guard !== void 0) {
|
|
1969
|
+
const g = opts.guard;
|
|
1970
|
+
out.guard = () => g(this);
|
|
1971
|
+
}
|
|
1972
|
+
return out;
|
|
1973
|
+
}
|
|
1974
|
+
};
|
|
1975
|
+
function issuePathToPointer(path) {
|
|
1976
|
+
if (!path || path.length === 0) return "";
|
|
1977
|
+
const parts = path.map((seg) => {
|
|
1978
|
+
const key = typeof seg === "object" ? seg.key : seg;
|
|
1979
|
+
const s = typeof key === "symbol" ? key.description ?? "" : String(key);
|
|
1980
|
+
return s.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
1981
|
+
});
|
|
1982
|
+
return "/" + parts.join("/");
|
|
1983
|
+
}
|
|
1984
|
+
async function _openDocumentFromDisk(absolutePath, options = {}) {
|
|
1985
|
+
const schema = options.schema ?? null;
|
|
1986
|
+
let text;
|
|
1987
|
+
try {
|
|
1988
|
+
text = await readFile(absolutePath, "utf8");
|
|
1989
|
+
} catch (err) {
|
|
1990
|
+
if (isNodeNotFound(err)) {
|
|
1991
|
+
const format2 = options.format ?? _detectFormat(absolutePath);
|
|
1992
|
+
return new _Document({
|
|
1993
|
+
absolutePath,
|
|
1994
|
+
format: format2,
|
|
1995
|
+
originalText: "",
|
|
1996
|
+
meta: _defaultMeta(),
|
|
1997
|
+
schema
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
2000
|
+
throw err;
|
|
2001
|
+
}
|
|
2002
|
+
const format = options.format ?? _detectFormat(absolutePath, text);
|
|
2003
|
+
const adapter = _getAdapter(format);
|
|
2004
|
+
const { meta } = adapter.parse(text);
|
|
2005
|
+
return new _Document({ absolutePath, format, originalText: text, meta, schema });
|
|
2006
|
+
}
|
|
2007
|
+
function isNodeNotFound(err) {
|
|
2008
|
+
return err !== null && typeof err === "object" && "code" in err && err.code === "ENOENT";
|
|
2009
|
+
}
|
|
2010
|
+
function _previewFingerprint(doc) {
|
|
2011
|
+
return _fingerprint(doc.preview(), doc.format);
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
// src/file-editor/transaction.ts
|
|
2015
|
+
import { readFile as readFile2, stat } from "fs/promises";
|
|
2016
|
+
|
|
2017
|
+
// src/file-editor/state/in-memory.ts
|
|
2018
|
+
function _InMemoryStateStore() {
|
|
2019
|
+
const records = /* @__PURE__ */ new Map();
|
|
2020
|
+
return {
|
|
2021
|
+
// eslint-disable-next-line @typescript-eslint/require-await -- synchronous Map operation wrapped in async interface
|
|
2022
|
+
async read(path) {
|
|
2023
|
+
return records.get(path) ?? null;
|
|
2024
|
+
},
|
|
2025
|
+
// eslint-disable-next-line @typescript-eslint/require-await -- synchronous Map operation wrapped in async interface
|
|
2026
|
+
async write(path, record) {
|
|
2027
|
+
records.set(path, record);
|
|
2028
|
+
},
|
|
2029
|
+
// eslint-disable-next-line @typescript-eslint/require-await -- synchronous Map operation wrapped in async interface
|
|
2030
|
+
async delete(path) {
|
|
2031
|
+
records.delete(path);
|
|
2032
|
+
},
|
|
2033
|
+
// eslint-disable-next-line @typescript-eslint/require-await -- synchronous Map operation wrapped in async interface
|
|
2034
|
+
async list() {
|
|
2035
|
+
return [...records.entries()].map(([path, record]) => ({ path, record }));
|
|
2036
|
+
}
|
|
2037
|
+
};
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
// src/file-editor/util/atomic-write.ts
|
|
2041
|
+
import { randomBytes } from "crypto";
|
|
2042
|
+
import { mkdir, rename, unlink, writeFile } from "fs/promises";
|
|
2043
|
+
import { dirname } from "path";
|
|
2044
|
+
async function _atomicWriteText(path, text) {
|
|
2045
|
+
await mkdir(dirname(path), { recursive: true });
|
|
2046
|
+
const suffix = randomBytes(6).toString("hex");
|
|
2047
|
+
const tmp = `${path}.${suffix}.tmp`;
|
|
2048
|
+
await writeFile(tmp, text, "utf8");
|
|
2049
|
+
try {
|
|
2050
|
+
await rename(tmp, path);
|
|
2051
|
+
} catch (err) {
|
|
2052
|
+
await unlink(tmp).catch((_unlinkErr) => void 0);
|
|
2053
|
+
throw err;
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
// src/file-editor/util/diff.ts
|
|
2058
|
+
function _unified(a, b, opts = {}) {
|
|
2059
|
+
const context = opts.context ?? 3;
|
|
2060
|
+
const nameA = opts.fileNameA ?? "a";
|
|
2061
|
+
const nameB = opts.fileNameB ?? "b";
|
|
2062
|
+
const hunksList = _hunks(a, b, { context });
|
|
2063
|
+
if (hunksList.length === 0) return "";
|
|
2064
|
+
const lines = [`--- ${nameA}`, `+++ ${nameB}`];
|
|
2065
|
+
for (const h of hunksList) {
|
|
2066
|
+
lines.push(
|
|
2067
|
+
`@@ -${String(h.oldStart)},${String(h.oldLines)} +${String(h.newStart)},${String(h.newLines)} @@`
|
|
2068
|
+
);
|
|
2069
|
+
for (const l of h.lines) {
|
|
2070
|
+
const prefix = l.kind === "context" ? " " : l.kind === "add" ? "+" : "-";
|
|
2071
|
+
lines.push(prefix + l.text);
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
return lines.join("\n") + "\n";
|
|
2075
|
+
}
|
|
2076
|
+
function _hunks(a, b, opts = {}) {
|
|
2077
|
+
const context = opts.context ?? 3;
|
|
2078
|
+
const aLines = splitLines(a);
|
|
2079
|
+
const bLines = splitLines(b);
|
|
2080
|
+
const ops = diffLines(aLines, bLines);
|
|
2081
|
+
return groupIntoHunks(ops, context);
|
|
2082
|
+
}
|
|
2083
|
+
function splitLines(text) {
|
|
2084
|
+
if (text === "") return [];
|
|
2085
|
+
const parts = text.split(/\r?\n/);
|
|
2086
|
+
if (parts.length > 0 && parts.at(-1) === "" && /\r?\n$/.test(text)) {
|
|
2087
|
+
parts.pop();
|
|
2088
|
+
}
|
|
2089
|
+
return parts;
|
|
2090
|
+
}
|
|
2091
|
+
var Dp2D = class {
|
|
2092
|
+
data;
|
|
2093
|
+
cols;
|
|
2094
|
+
constructor(rows, cols) {
|
|
2095
|
+
this.cols = cols;
|
|
2096
|
+
this.data = new Array(rows * cols).fill(0);
|
|
2097
|
+
}
|
|
2098
|
+
get(row, col) {
|
|
2099
|
+
return this.data[row * this.cols + col] ?? 0;
|
|
2100
|
+
}
|
|
2101
|
+
set(row, col, val) {
|
|
2102
|
+
this.data[row * this.cols + col] = val;
|
|
2103
|
+
}
|
|
2104
|
+
};
|
|
2105
|
+
function diffLines(a, b) {
|
|
2106
|
+
const n = a.length;
|
|
2107
|
+
const m = b.length;
|
|
2108
|
+
const dp = new Dp2D(n + 1, m + 1);
|
|
2109
|
+
for (let i2 = n - 1; i2 >= 0; i2--) {
|
|
2110
|
+
for (let j2 = m - 1; j2 >= 0; j2--) {
|
|
2111
|
+
if (a[i2] === b[j2]) dp.set(i2, j2, dp.get(i2 + 1, j2 + 1) + 1);
|
|
2112
|
+
else dp.set(i2, j2, Math.max(dp.get(i2 + 1, j2), dp.get(i2, j2 + 1)));
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
const ops = [];
|
|
2116
|
+
let i = 0;
|
|
2117
|
+
let j = 0;
|
|
2118
|
+
while (i < n && j < m) {
|
|
2119
|
+
const aLine = a[i] ?? "";
|
|
2120
|
+
const bLine = b[j] ?? "";
|
|
2121
|
+
if (aLine === bLine) {
|
|
2122
|
+
ops.push({ kind: "context", text: aLine, oldIdx: i, newIdx: j });
|
|
2123
|
+
i++;
|
|
2124
|
+
j++;
|
|
2125
|
+
} else if (dp.get(i + 1, j) >= dp.get(i, j + 1)) {
|
|
2126
|
+
ops.push({ kind: "del", text: aLine, oldIdx: i });
|
|
2127
|
+
i++;
|
|
2128
|
+
} else {
|
|
2129
|
+
ops.push({ kind: "add", text: bLine, newIdx: j });
|
|
2130
|
+
j++;
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
while (i < n) {
|
|
2134
|
+
ops.push({ kind: "del", text: a[i] ?? "", oldIdx: i });
|
|
2135
|
+
i++;
|
|
2136
|
+
}
|
|
2137
|
+
while (j < m) {
|
|
2138
|
+
ops.push({ kind: "add", text: b[j] ?? "", newIdx: j });
|
|
2139
|
+
j++;
|
|
2140
|
+
}
|
|
2141
|
+
return ops;
|
|
2142
|
+
}
|
|
2143
|
+
function groupIntoHunks(ops, context) {
|
|
2144
|
+
const changeIndices = [];
|
|
2145
|
+
for (let k = 0; k < ops.length; k++) {
|
|
2146
|
+
const op = ops[k];
|
|
2147
|
+
if (op !== void 0 && op.kind !== "context") changeIndices.push(k);
|
|
2148
|
+
}
|
|
2149
|
+
if (changeIndices.length === 0) return [];
|
|
2150
|
+
const groups = [];
|
|
2151
|
+
let groupStart = Math.max(0, (changeIndices[0] ?? 0) - context);
|
|
2152
|
+
let groupEnd = Math.min(ops.length - 1, (changeIndices[0] ?? 0) + context);
|
|
2153
|
+
for (let k = 1; k < changeIndices.length; k++) {
|
|
2154
|
+
const idx = changeIndices[k] ?? 0;
|
|
2155
|
+
if (idx - context <= groupEnd + 1) {
|
|
2156
|
+
groupEnd = Math.min(ops.length - 1, idx + context);
|
|
2157
|
+
} else {
|
|
2158
|
+
groups.push({ start: groupStart, end: groupEnd });
|
|
2159
|
+
groupStart = Math.max(0, idx - context);
|
|
2160
|
+
groupEnd = Math.min(ops.length - 1, idx + context);
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
groups.push({ start: groupStart, end: groupEnd });
|
|
2164
|
+
const result = [];
|
|
2165
|
+
for (const g of groups) {
|
|
2166
|
+
const slice = ops.slice(g.start, g.end + 1);
|
|
2167
|
+
const first = slice[0];
|
|
2168
|
+
if (first === void 0) continue;
|
|
2169
|
+
const lines = slice.map((op) => toDiffLine(op));
|
|
2170
|
+
const oldLines = slice.filter((op) => op.kind !== "add").length;
|
|
2171
|
+
const newLines = slice.filter((op) => op.kind !== "del").length;
|
|
2172
|
+
let oldStart;
|
|
2173
|
+
let newStart;
|
|
2174
|
+
if (first.kind === "context") {
|
|
2175
|
+
oldStart = first.oldIdx + 1;
|
|
2176
|
+
newStart = first.newIdx + 1;
|
|
2177
|
+
} else if (first.kind === "del") {
|
|
2178
|
+
oldStart = first.oldIdx + 1;
|
|
2179
|
+
const prevNew = findNewIndex(ops, g.start, "before");
|
|
2180
|
+
newStart = prevNew === 0 && newLines === 0 ? 0 : prevNew + 1;
|
|
2181
|
+
} else {
|
|
2182
|
+
const prevOld = findOldIndex(ops, g.start, "before");
|
|
2183
|
+
oldStart = prevOld === 0 && oldLines === 0 ? 0 : prevOld + 1;
|
|
2184
|
+
newStart = first.newIdx + 1;
|
|
2185
|
+
}
|
|
2186
|
+
result.push({ oldStart, oldLines, newStart, newLines, lines });
|
|
2187
|
+
}
|
|
2188
|
+
return result;
|
|
2189
|
+
}
|
|
2190
|
+
function toDiffLine(op) {
|
|
2191
|
+
if (op.kind === "context") {
|
|
2192
|
+
return {
|
|
2193
|
+
kind: "context",
|
|
2194
|
+
text: op.text,
|
|
2195
|
+
oldLineNo: op.oldIdx + 1,
|
|
2196
|
+
newLineNo: op.newIdx + 1
|
|
2197
|
+
};
|
|
2198
|
+
}
|
|
2199
|
+
if (op.kind === "del") {
|
|
2200
|
+
return { kind: "del", text: op.text, oldLineNo: op.oldIdx + 1, newLineNo: null };
|
|
2201
|
+
}
|
|
2202
|
+
return { kind: "add", text: op.text, oldLineNo: null, newLineNo: op.newIdx + 1 };
|
|
2203
|
+
}
|
|
2204
|
+
function findOldIndex(ops, from, _direction) {
|
|
2205
|
+
for (let k = from - 1; k >= 0; k--) {
|
|
2206
|
+
const op = ops[k];
|
|
2207
|
+
if (op === void 0) continue;
|
|
2208
|
+
if (op.kind === "context") return op.oldIdx + 1;
|
|
2209
|
+
if (op.kind === "del") return op.oldIdx + 1;
|
|
2210
|
+
}
|
|
2211
|
+
return 0;
|
|
2212
|
+
}
|
|
2213
|
+
function findNewIndex(ops, from, _direction) {
|
|
2214
|
+
for (let k = from - 1; k >= 0; k--) {
|
|
2215
|
+
const op = ops[k];
|
|
2216
|
+
if (op === void 0) continue;
|
|
2217
|
+
if (op.kind === "context") return op.newIdx + 1;
|
|
2218
|
+
if (op.kind === "add") return op.newIdx + 1;
|
|
2219
|
+
}
|
|
2220
|
+
return 0;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
// src/file-editor/transaction.ts
|
|
2224
|
+
var _Transaction = class {
|
|
2225
|
+
documents = /* @__PURE__ */ new Map();
|
|
2226
|
+
options;
|
|
2227
|
+
constructor(options = {}) {
|
|
2228
|
+
this.options = options;
|
|
2229
|
+
}
|
|
2230
|
+
/** Open or return the cached document for a path. Memoized per transaction. */
|
|
2231
|
+
async open(absolutePath) {
|
|
2232
|
+
const cached = this.documents.get(absolutePath);
|
|
2233
|
+
if (cached) return cached;
|
|
2234
|
+
const opts = this.options.format === void 0 ? {} : { format: this.options.format };
|
|
2235
|
+
const doc = await _openDocumentFromDisk(absolutePath, opts);
|
|
2236
|
+
this.documents.set(absolutePath, doc);
|
|
2237
|
+
return doc;
|
|
2238
|
+
}
|
|
2239
|
+
/** @internal — register a document that was opened externally (e.g. via a façade). */
|
|
2240
|
+
adopt(doc) {
|
|
2241
|
+
this.documents.set(doc.absolutePath, doc);
|
|
2242
|
+
}
|
|
2243
|
+
/** Drop all staged edits across every document in the transaction. */
|
|
2244
|
+
rollback() {
|
|
2245
|
+
for (const doc of this.documents.values()) doc.rollback();
|
|
2246
|
+
}
|
|
2247
|
+
/** Plan without writing. Invokes `onConflict`/`confirmWrite` as a real commit would. */
|
|
2248
|
+
async dryRun(options = {}) {
|
|
2249
|
+
return this.run(options, { write: false });
|
|
2250
|
+
}
|
|
2251
|
+
/** Apply staged edits to disk. */
|
|
2252
|
+
async commit(options = {}) {
|
|
2253
|
+
return this.run(options, { write: true });
|
|
2254
|
+
}
|
|
2255
|
+
async run(options, mode) {
|
|
2256
|
+
const store = this.options.stateStore ?? _InMemoryStateStore();
|
|
2257
|
+
const defaultPolicy = options.defaultPolicy ?? "error";
|
|
2258
|
+
const confirmationPolicy = this.options.confirmationPolicy ?? "on-conflict";
|
|
2259
|
+
const written = [];
|
|
2260
|
+
const skipped = [];
|
|
2261
|
+
const conflicts = [];
|
|
2262
|
+
const diagnostics = [];
|
|
2263
|
+
for (const doc of this.documents.values()) {
|
|
2264
|
+
const schemaDiagnostics = await doc.validate();
|
|
2265
|
+
diagnostics.push(...schemaDiagnostics);
|
|
2266
|
+
diagnostics.push(
|
|
2267
|
+
...doc.diagnostics().filter((d) => !schemaDiagnostics.includes(d))
|
|
2268
|
+
);
|
|
2269
|
+
if (schemaDiagnostics.some((d) => d.severity === "error")) {
|
|
2270
|
+
skipped.push({ path: doc.absolutePath, reason: "schema-invalid" });
|
|
2271
|
+
continue;
|
|
2272
|
+
}
|
|
2273
|
+
const nextText = doc.preview();
|
|
2274
|
+
const hasEdits = doc.pendingEdits().length > 0;
|
|
2275
|
+
const fileExists = await pathExists(doc.absolutePath);
|
|
2276
|
+
if (!hasEdits && fileExists) {
|
|
2277
|
+
skipped.push({ path: doc.absolutePath, reason: "no-change" });
|
|
2278
|
+
continue;
|
|
2279
|
+
}
|
|
2280
|
+
if (!hasEdits && !fileExists) {
|
|
2281
|
+
skipped.push({ path: doc.absolutePath, reason: "no-change" });
|
|
2282
|
+
continue;
|
|
2283
|
+
}
|
|
2284
|
+
const stored = await store.read(doc.absolutePath);
|
|
2285
|
+
const diskText = fileExists ? await readExistingText(doc.absolutePath) : null;
|
|
2286
|
+
const conflict = await resolveDrift({
|
|
2287
|
+
doc,
|
|
2288
|
+
nextText,
|
|
2289
|
+
diskText,
|
|
2290
|
+
stored,
|
|
2291
|
+
defaultPolicy,
|
|
2292
|
+
onConflict: this.options.onConflict
|
|
2293
|
+
});
|
|
2294
|
+
if (conflict.decision === "skip") {
|
|
2295
|
+
skipped.push({ path: doc.absolutePath, reason: "conflict-skipped" });
|
|
2296
|
+
if (conflict.conflict) conflicts.push(conflict.conflict);
|
|
2297
|
+
continue;
|
|
2298
|
+
}
|
|
2299
|
+
if (conflict.conflict) conflicts.push(conflict.conflict);
|
|
2300
|
+
const reason = decideReason(conflict.conflict !== null, diskText !== null);
|
|
2301
|
+
const shouldConfirm = confirmationPolicy === "always" || confirmationPolicy === "on-conflict" && reason === "conflict";
|
|
2302
|
+
if (shouldConfirm && this.options.confirmWrite) {
|
|
2303
|
+
const req = buildWriteRequest({
|
|
2304
|
+
path: doc.absolutePath,
|
|
2305
|
+
format: doc.format,
|
|
2306
|
+
reason,
|
|
2307
|
+
currentText: diskText,
|
|
2308
|
+
nextText,
|
|
2309
|
+
conflict: conflict.conflict
|
|
2310
|
+
});
|
|
2311
|
+
const decision = await this.options.confirmWrite(req);
|
|
2312
|
+
if (decision === "abort") throw new _WriteAbortedError(doc.absolutePath);
|
|
2313
|
+
if (decision === "skip") {
|
|
2314
|
+
skipped.push({ path: doc.absolutePath, reason: "write-skipped" });
|
|
2315
|
+
continue;
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
if (diskText === nextText) {
|
|
2319
|
+
skipped.push({ path: doc.absolutePath, reason: "no-change" });
|
|
2320
|
+
continue;
|
|
2321
|
+
}
|
|
2322
|
+
if (mode.write) {
|
|
2323
|
+
await _atomicWriteText(doc.absolutePath, nextText);
|
|
2324
|
+
const record = {
|
|
2325
|
+
fingerprint: _previewFingerprint(doc),
|
|
2326
|
+
rawSha256: _sha256Hex(nextText),
|
|
2327
|
+
writtenAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2328
|
+
toolId: this.options.toolId ?? null,
|
|
2329
|
+
toolVersion: this.options.toolVersion ?? null,
|
|
2330
|
+
format: doc.format,
|
|
2331
|
+
baseText: nextText
|
|
2332
|
+
};
|
|
2333
|
+
await store.write(doc.absolutePath, record);
|
|
2334
|
+
}
|
|
2335
|
+
written.push({
|
|
2336
|
+
path: doc.absolutePath,
|
|
2337
|
+
bytesBefore: Buffer.byteLength(diskText ?? "", "utf8"),
|
|
2338
|
+
bytesAfter: Buffer.byteLength(nextText, "utf8"),
|
|
2339
|
+
unifiedDiff: _unified(diskText ?? "", nextText, {
|
|
2340
|
+
fileNameA: "a/" + doc.absolutePath,
|
|
2341
|
+
fileNameB: "b/" + doc.absolutePath
|
|
2342
|
+
})
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
return { written, skipped, conflicts, diagnostics };
|
|
2346
|
+
}
|
|
2347
|
+
};
|
|
2348
|
+
async function resolveDrift(args) {
|
|
2349
|
+
const { doc, nextText, diskText, stored, defaultPolicy, onConflict } = args;
|
|
2350
|
+
if (diskText === nextText) return { decision: "proceed", conflict: null };
|
|
2351
|
+
if (stored === null) return { decision: "proceed", conflict: null };
|
|
2352
|
+
if (diskText !== null) {
|
|
2353
|
+
const diskFp = _fingerprint(diskText, doc.format);
|
|
2354
|
+
if (diskFp === stored.fingerprint) return { decision: "proceed", conflict: null };
|
|
2355
|
+
}
|
|
2356
|
+
const conflict = {
|
|
2357
|
+
path: doc.absolutePath,
|
|
2358
|
+
ours: nextText,
|
|
2359
|
+
theirs: diskText ?? "",
|
|
2360
|
+
base: stored.baseText ?? null,
|
|
2361
|
+
lastWriteAt: stored.writtenAt,
|
|
2362
|
+
lastWriteTool: stored.toolId
|
|
2363
|
+
};
|
|
2364
|
+
const policy = pickPolicy(doc, defaultPolicy);
|
|
2365
|
+
switch (policy) {
|
|
2366
|
+
case "error":
|
|
2367
|
+
throw new _ConflictError(conflict);
|
|
2368
|
+
case "skip":
|
|
2369
|
+
return { decision: "skip", conflict };
|
|
2370
|
+
case "overwrite":
|
|
2371
|
+
return { decision: "proceed", conflict };
|
|
2372
|
+
case "ask": {
|
|
2373
|
+
if (!onConflict) {
|
|
2374
|
+
throw new _FileEditorError(
|
|
2375
|
+
"CONFLICT_RESOLVER_MISSING",
|
|
2376
|
+
`conflictPolicy "ask" requires an onConflict resolver for ${doc.absolutePath}.`
|
|
2377
|
+
);
|
|
2378
|
+
}
|
|
2379
|
+
const resolution = await onConflict(conflict);
|
|
2380
|
+
if (resolution === "abort") throw new _ConflictAbortedError(doc.absolutePath);
|
|
2381
|
+
if (resolution === "skip") return { decision: "skip", conflict };
|
|
2382
|
+
return { decision: "proceed", conflict };
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
function pickPolicy(doc, fallback) {
|
|
2387
|
+
for (const edit of doc.pendingEdits()) {
|
|
2388
|
+
if (edit.conflictPolicy !== void 0) return edit.conflictPolicy;
|
|
2389
|
+
}
|
|
2390
|
+
return fallback;
|
|
2391
|
+
}
|
|
2392
|
+
function decideReason(isConflict, fileExisted) {
|
|
2393
|
+
if (isConflict) return "conflict";
|
|
2394
|
+
return fileExisted ? "update" : "new-file";
|
|
2395
|
+
}
|
|
2396
|
+
function buildWriteRequest(args) {
|
|
2397
|
+
const base = args.currentText ?? "";
|
|
2398
|
+
return {
|
|
2399
|
+
path: args.path,
|
|
2400
|
+
format: args.format,
|
|
2401
|
+
reason: args.reason,
|
|
2402
|
+
currentText: args.currentText,
|
|
2403
|
+
nextText: args.nextText,
|
|
2404
|
+
unifiedDiff: _unified(base, args.nextText, {
|
|
2405
|
+
fileNameA: "a/" + args.path,
|
|
2406
|
+
fileNameB: "b/" + args.path
|
|
2407
|
+
}),
|
|
2408
|
+
hunks: _hunks(base, args.nextText),
|
|
2409
|
+
conflict: args.conflict
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
async function pathExists(path) {
|
|
2413
|
+
try {
|
|
2414
|
+
const s = await stat(path);
|
|
2415
|
+
return s.isFile();
|
|
2416
|
+
} catch {
|
|
2417
|
+
return false;
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
async function readExistingText(path) {
|
|
2421
|
+
return readFile2(path, "utf8");
|
|
2422
|
+
}
|
|
2423
|
+
function _beginTransaction(options = {}) {
|
|
2424
|
+
return new _Transaction(options);
|
|
2425
|
+
}
|
|
2426
|
+
async function _openDocument(absolutePath, options = {}) {
|
|
2427
|
+
const tx = _beginTransaction(options);
|
|
2428
|
+
return tx.open(absolutePath);
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
// src/file-editor/schema.ts
|
|
2432
|
+
async function _validateWithSchema(schema, value) {
|
|
2433
|
+
const result = await schema["~standard"].validate(value);
|
|
2434
|
+
if (result.issues) {
|
|
2435
|
+
const summary = result.issues.map(formatIssue).join("; ");
|
|
2436
|
+
throw new _SchemaValidationError(summary, result.issues);
|
|
2437
|
+
}
|
|
2438
|
+
return result.value;
|
|
2439
|
+
}
|
|
2440
|
+
function _tryValidateWithSchema(schema, value) {
|
|
2441
|
+
return Promise.resolve(schema["~standard"].validate(value));
|
|
2442
|
+
}
|
|
2443
|
+
var _SchemaValidationError = class extends Error {
|
|
2444
|
+
issues;
|
|
2445
|
+
constructor(message, issues) {
|
|
2446
|
+
super(message);
|
|
2447
|
+
this.name = "_SchemaValidationError";
|
|
2448
|
+
this.issues = issues;
|
|
2449
|
+
}
|
|
2450
|
+
};
|
|
2451
|
+
function formatIssue(issue) {
|
|
2452
|
+
if (!issue.path || issue.path.length === 0) return issue.message;
|
|
2453
|
+
const path = issue.path.map((seg) => typeof seg === "object" ? String(seg.key) : String(seg)).join(".");
|
|
2454
|
+
return `${path}: ${issue.message}`;
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
// src/file-editor/state/fs-manifest.ts
|
|
2458
|
+
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
2459
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, relative as relative2, resolve, sep as sep2 } from "path";
|
|
2460
|
+
function _FsCentralManifestStore(options = {}) {
|
|
2461
|
+
const root = resolve(options.rootDir ?? process.cwd());
|
|
2462
|
+
const manifestPath = resolve(root, options.filename ?? ".file-editor/state.json");
|
|
2463
|
+
const keepBase = options.keepBaseSnapshots ?? false;
|
|
2464
|
+
const toKey = (p) => {
|
|
2465
|
+
const abs = isAbsolute2(p) ? p : resolve(root, p);
|
|
2466
|
+
const rel = relative2(root, abs);
|
|
2467
|
+
return rel.split(sep2).join("/");
|
|
2468
|
+
};
|
|
2469
|
+
const load = async () => {
|
|
2470
|
+
try {
|
|
2471
|
+
const text = await readFile3(manifestPath, "utf8");
|
|
2472
|
+
const raw = JSON.parse(text);
|
|
2473
|
+
const version = raw !== null && typeof raw === "object" && "version" in raw ? raw.version : void 0;
|
|
2474
|
+
if (version !== 1) {
|
|
2475
|
+
throw new Error(`Unsupported state manifest version: ${String(version)}`);
|
|
2476
|
+
}
|
|
2477
|
+
return raw;
|
|
2478
|
+
} catch (err) {
|
|
2479
|
+
if (isNodeNotFound2(err)) return { version: 1, records: {} };
|
|
2480
|
+
throw err;
|
|
2481
|
+
}
|
|
2482
|
+
};
|
|
2483
|
+
const save = async (manifest) => {
|
|
2484
|
+
await mkdir2(dirname2(manifestPath), { recursive: true });
|
|
2485
|
+
const text = JSON.stringify(manifest, null, 2) + "\n";
|
|
2486
|
+
await writeFile2(manifestPath, text, "utf8");
|
|
2487
|
+
};
|
|
2488
|
+
const stripBase = (record) => {
|
|
2489
|
+
if (keepBase) return record;
|
|
2490
|
+
const { baseText: _baseText, ...rest } = record;
|
|
2491
|
+
return rest;
|
|
2492
|
+
};
|
|
2493
|
+
return {
|
|
2494
|
+
async read(path) {
|
|
2495
|
+
const manifest = await load();
|
|
2496
|
+
return manifest.records[toKey(path)] ?? null;
|
|
2497
|
+
},
|
|
2498
|
+
async write(path, record) {
|
|
2499
|
+
const manifest = await load();
|
|
2500
|
+
const next = {
|
|
2501
|
+
version: 1,
|
|
2502
|
+
records: { ...manifest.records, [toKey(path)]: stripBase(record) }
|
|
2503
|
+
};
|
|
2504
|
+
await save(next);
|
|
2505
|
+
},
|
|
2506
|
+
async delete(path) {
|
|
2507
|
+
const manifest = await load();
|
|
2508
|
+
const key = toKey(path);
|
|
2509
|
+
if (!(key in manifest.records)) return;
|
|
2510
|
+
const { [key]: _removed, ...nextRecords } = manifest.records;
|
|
2511
|
+
await save({ version: 1, records: nextRecords });
|
|
2512
|
+
},
|
|
2513
|
+
async list() {
|
|
2514
|
+
const manifest = await load();
|
|
2515
|
+
return Object.entries(manifest.records).map(([key, record]) => ({
|
|
2516
|
+
path: resolve(root, key),
|
|
2517
|
+
record
|
|
2518
|
+
}));
|
|
2519
|
+
}
|
|
2520
|
+
};
|
|
2521
|
+
}
|
|
2522
|
+
function isNodeNotFound2(err) {
|
|
2523
|
+
return err !== null && typeof err === "object" && "code" in err && err.code === "ENOENT";
|
|
2524
|
+
}
|
|
2525
|
+
|
|
2526
|
+
// src/file-editor/facades/package-json.ts
|
|
2527
|
+
var _PackageJsonDoc = class extends _Document {
|
|
2528
|
+
setScript(name, command, opts) {
|
|
2529
|
+
return this.set(scriptPointer(name), command, opts);
|
|
2530
|
+
}
|
|
2531
|
+
requireScript(name, command) {
|
|
2532
|
+
if (command !== void 0) this.addIfMissing(scriptPointer(name), command);
|
|
2533
|
+
this.assert(_check.exists(scriptPointer(name)));
|
|
2534
|
+
return this;
|
|
2535
|
+
}
|
|
2536
|
+
addDependency(name, range, kind = "dependencies", opts) {
|
|
2537
|
+
return this.set(depPointer(kind, name), range, opts);
|
|
2538
|
+
}
|
|
2539
|
+
updateDependency(name, range, kind = "dependencies", opts) {
|
|
2540
|
+
return this.updateIfPresent(depPointer(kind, name), () => range, opts);
|
|
2541
|
+
}
|
|
2542
|
+
removeDependency(name, kind = "dependencies", opts) {
|
|
2543
|
+
return this.remove(depPointer(kind, name), opts);
|
|
2544
|
+
}
|
|
2545
|
+
ensureDependency(name, range, kind = "dependencies", opts) {
|
|
2546
|
+
return this.addIfMissing(depPointer(kind, name), range, opts);
|
|
2547
|
+
}
|
|
2548
|
+
mustHaveScript(name, opts = {}) {
|
|
2549
|
+
const checks = [_check.exists(scriptPointer(name))];
|
|
2550
|
+
if (opts.equals !== void 0)
|
|
2551
|
+
checks.push(_check.equals(scriptPointer(name), opts.equals));
|
|
2552
|
+
if (opts.matches !== void 0)
|
|
2553
|
+
checks.push(_check.matches(scriptPointer(name), opts.matches));
|
|
2554
|
+
return this.assert(...checks);
|
|
2555
|
+
}
|
|
2556
|
+
mustHaveDependency(name, opts = {}) {
|
|
2557
|
+
const kind = opts.kind ?? "dependencies";
|
|
2558
|
+
const checks = [_check.exists(depPointer(kind, name))];
|
|
2559
|
+
if (opts.range !== void 0) {
|
|
2560
|
+
checks.push(_check.semverSatisfies(depPointer(kind, name), opts.range));
|
|
2561
|
+
}
|
|
2562
|
+
return this.assert(...checks);
|
|
2563
|
+
}
|
|
2564
|
+
};
|
|
2565
|
+
function scriptPointer(name) {
|
|
2566
|
+
return _compilePointer(["scripts", name]);
|
|
2567
|
+
}
|
|
2568
|
+
function depPointer(kind, name) {
|
|
2569
|
+
return "/" + kind + "/" + _escapeToken(name);
|
|
2570
|
+
}
|
|
2571
|
+
async function _openPackageJson(absolutePath, options = {}) {
|
|
2572
|
+
const openOpts = {
|
|
2573
|
+
format: options.format ?? "json"
|
|
2574
|
+
};
|
|
2575
|
+
if (options.schema) openOpts.schema = options.schema;
|
|
2576
|
+
const base = await _openDocumentFromDisk(absolutePath, openOpts);
|
|
2577
|
+
return new _PackageJsonDoc({
|
|
2578
|
+
absolutePath: base.absolutePath,
|
|
2579
|
+
format: base.format,
|
|
2580
|
+
originalText: base.getOriginalText(),
|
|
2581
|
+
meta: base.getMeta(),
|
|
2582
|
+
schema: base.schema
|
|
2583
|
+
});
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
// src/file-editor/facades/tsconfig.ts
|
|
2587
|
+
var _TsConfigDoc = class extends _Document {
|
|
2588
|
+
setCompilerOption(name, value, opts) {
|
|
2589
|
+
return this.set(coPointer(name), value, opts);
|
|
2590
|
+
}
|
|
2591
|
+
ensureCompilerOption(name, value, opts) {
|
|
2592
|
+
return this.addIfMissing(coPointer(name), value, opts);
|
|
2593
|
+
}
|
|
2594
|
+
addInclude(pattern, opts) {
|
|
2595
|
+
return this.arrayUpsert("/include", pattern, (p) => p, opts);
|
|
2596
|
+
}
|
|
2597
|
+
addExclude(pattern, opts) {
|
|
2598
|
+
return this.arrayUpsert("/exclude", pattern, (p) => p, opts);
|
|
2599
|
+
}
|
|
2600
|
+
addReference(path, opts) {
|
|
2601
|
+
return this.arrayUpsert(
|
|
2602
|
+
"/references",
|
|
2603
|
+
{ path },
|
|
2604
|
+
(ref) => ref.path,
|
|
2605
|
+
opts
|
|
2606
|
+
);
|
|
2607
|
+
}
|
|
2608
|
+
mustHaveCompilerOption(name, opts = {}) {
|
|
2609
|
+
const checks = [_check.exists(coPointer(name))];
|
|
2610
|
+
if (opts.equals !== void 0)
|
|
2611
|
+
checks.push(_check.equals(coPointer(name), opts.equals));
|
|
2612
|
+
if (opts.oneOf !== void 0) checks.push(_check.oneOf(coPointer(name), opts.oneOf));
|
|
2613
|
+
return this.assert(...checks);
|
|
2614
|
+
}
|
|
2615
|
+
};
|
|
2616
|
+
function coPointer(name) {
|
|
2617
|
+
return _compilePointer(["compilerOptions", name]);
|
|
2618
|
+
}
|
|
2619
|
+
async function _openTsConfig(absolutePath, options = {}) {
|
|
2620
|
+
const opts = {
|
|
2621
|
+
format: options.format ?? "jsonc"
|
|
2622
|
+
};
|
|
2623
|
+
if (options.schema) opts.schema = options.schema;
|
|
2624
|
+
const base = await _openDocumentFromDisk(absolutePath, opts);
|
|
2625
|
+
return new _TsConfigDoc({
|
|
2626
|
+
absolutePath: base.absolutePath,
|
|
2627
|
+
format: base.format,
|
|
2628
|
+
originalText: base.getOriginalText(),
|
|
2629
|
+
meta: base.getMeta(),
|
|
2630
|
+
schema: base.schema
|
|
2631
|
+
});
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2634
|
+
// src/file-editor/facades/api-extractor.ts
|
|
2635
|
+
var _ApiExtractorDoc = class extends _Document {
|
|
2636
|
+
setMainEntryPoint(path, opts) {
|
|
2637
|
+
return this.set("/mainEntryPointFilePath", path, opts);
|
|
2638
|
+
}
|
|
2639
|
+
enableApiReport(folder, fileName, opts) {
|
|
2640
|
+
this.set("/apiReport/enabled", true, opts);
|
|
2641
|
+
this.set("/apiReport/reportFolder", folder, opts);
|
|
2642
|
+
this.set("/apiReport/reportFileName", fileName, opts);
|
|
2643
|
+
return this;
|
|
2644
|
+
}
|
|
2645
|
+
enableDtsRollup(publicPath, opts) {
|
|
2646
|
+
this.set("/dtsRollup/enabled", true, opts);
|
|
2647
|
+
this.set("/dtsRollup/publicTrimmedFilePath", publicPath, opts);
|
|
2648
|
+
return this;
|
|
2649
|
+
}
|
|
2650
|
+
};
|
|
2651
|
+
async function _openApiExtractor(absolutePath, options = {}) {
|
|
2652
|
+
const opts = {
|
|
2653
|
+
format: options.format ?? "jsonc"
|
|
2654
|
+
};
|
|
2655
|
+
if (options.schema)
|
|
2656
|
+
opts.schema = options.schema;
|
|
2657
|
+
const base = await _openDocumentFromDisk(absolutePath, opts);
|
|
2658
|
+
return new _ApiExtractorDoc({
|
|
2659
|
+
absolutePath: base.absolutePath,
|
|
2660
|
+
format: base.format,
|
|
2661
|
+
originalText: base.getOriginalText(),
|
|
2662
|
+
meta: base.getMeta(),
|
|
2663
|
+
schema: base.schema
|
|
2664
|
+
});
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
// src/file-editor/facades/stripe-app-manifest.ts
|
|
2668
|
+
var _StripeAppManifestDoc = class extends _Document {
|
|
2669
|
+
setName(name, opts) {
|
|
2670
|
+
return this.set("/name", name, opts);
|
|
2671
|
+
}
|
|
2672
|
+
setVersion(version, opts) {
|
|
2673
|
+
return this.set("/version", version, opts);
|
|
2674
|
+
}
|
|
2675
|
+
addPermission(permission, opts) {
|
|
2676
|
+
return this.arrayUpsert(
|
|
2677
|
+
"/permissions",
|
|
2678
|
+
permission,
|
|
2679
|
+
(p) => p,
|
|
2680
|
+
opts
|
|
2681
|
+
);
|
|
2682
|
+
}
|
|
2683
|
+
removePermission(permission, opts) {
|
|
2684
|
+
const existing = this.get("/permissions");
|
|
2685
|
+
const permissions = (existing ?? []).filter((p) => p !== permission);
|
|
2686
|
+
return this.set("/permissions", permissions, opts);
|
|
2687
|
+
}
|
|
2688
|
+
/**
|
|
2689
|
+
* Add a custom object definition. Uses arrayUpsert to avoid duplicates
|
|
2690
|
+
* (keyed on `id`). Creates the `custom_object_definitions.definitions`
|
|
2691
|
+
* array if it doesn't exist.
|
|
2692
|
+
*/
|
|
2693
|
+
addCustomObject(id, content, type = "typescript", opts) {
|
|
2694
|
+
this.addIfMissing("/custom_object_definitions", {}, opts);
|
|
2695
|
+
this.addIfMissing("/custom_object_definitions/definitions", [], opts);
|
|
2696
|
+
return this.arrayUpsert(
|
|
2697
|
+
"/custom_object_definitions/definitions",
|
|
2698
|
+
{ id, specification: { type, content } },
|
|
2699
|
+
(entry) => entry.id,
|
|
2700
|
+
opts
|
|
2701
|
+
);
|
|
2702
|
+
}
|
|
2703
|
+
/**
|
|
2704
|
+
* Check if a custom object definition exists by id.
|
|
2705
|
+
*/
|
|
2706
|
+
hasCustomObject(id) {
|
|
2707
|
+
const defs = this.get("/custom_object_definitions");
|
|
2708
|
+
if (!defs?.definitions) return false;
|
|
2709
|
+
return defs.definitions.some((d) => d.id === id);
|
|
2710
|
+
}
|
|
2711
|
+
};
|
|
2712
|
+
async function _openStripeAppManifest(absolutePath, options = {}) {
|
|
2713
|
+
const opts = {};
|
|
2714
|
+
if (options.format !== void 0) opts.format = options.format;
|
|
2715
|
+
if (options.schema)
|
|
2716
|
+
opts.schema = options.schema;
|
|
2717
|
+
const base = await _openDocumentFromDisk(absolutePath, opts);
|
|
2718
|
+
return new _StripeAppManifestDoc({
|
|
2719
|
+
absolutePath: base.absolutePath,
|
|
2720
|
+
format: base.format,
|
|
2721
|
+
originalText: base.getOriginalText(),
|
|
2722
|
+
meta: base.getMeta(),
|
|
2723
|
+
schema: base.schema
|
|
2724
|
+
});
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2727
|
+
// src/file-editor/facades/brands.ts
|
|
2728
|
+
import semver2 from "semver";
|
|
2729
|
+
function _semverRange(input) {
|
|
2730
|
+
const valid = semver2.validRange(input);
|
|
2731
|
+
if (valid === null) {
|
|
2732
|
+
throw new TypeError(`Invalid semver range: ${JSON.stringify(input)}`);
|
|
2733
|
+
}
|
|
2734
|
+
return input;
|
|
2735
|
+
}
|
|
2736
|
+
function _tryParseSemverRange(input) {
|
|
2737
|
+
return semver2.validRange(input) === null ? null : input;
|
|
2738
|
+
}
|
|
2739
|
+
function _isSemverRange(input) {
|
|
2740
|
+
return typeof input === "string" && semver2.validRange(input) !== null;
|
|
2741
|
+
}
|
|
2742
|
+
function _semverVersion(input) {
|
|
2743
|
+
if (semver2.valid(input) === null) {
|
|
2744
|
+
throw new TypeError(`Invalid semver version: ${JSON.stringify(input)}`);
|
|
2745
|
+
}
|
|
2746
|
+
return input;
|
|
2747
|
+
}
|
|
2748
|
+
function _tryParseSemverVersion(input) {
|
|
2749
|
+
return semver2.valid(input) === null ? null : input;
|
|
2750
|
+
}
|
|
2751
|
+
function _isSemverVersion(input) {
|
|
2752
|
+
return typeof input === "string" && semver2.valid(input) !== null;
|
|
2753
|
+
}
|
|
2754
|
+
export {
|
|
2755
|
+
_ApiExtractorDoc,
|
|
2756
|
+
_AssertionError,
|
|
2757
|
+
_CliUx,
|
|
2758
|
+
_ConflictAbortedError,
|
|
2759
|
+
_ConflictError,
|
|
2760
|
+
_Document,
|
|
2761
|
+
_FileEditorError,
|
|
2762
|
+
_FormatNotSupportedError,
|
|
2763
|
+
_FsCentralManifestStore,
|
|
2764
|
+
_GeneratorDefectError,
|
|
2765
|
+
_GeneratorInputError,
|
|
2766
|
+
_GeneratorRunner,
|
|
2767
|
+
_InMemoryStateStore,
|
|
2768
|
+
_POINTER_STRING,
|
|
2769
|
+
_PackageJsonDoc,
|
|
2770
|
+
_ParseError,
|
|
2771
|
+
_PathError,
|
|
2772
|
+
_SchemaValidationError,
|
|
2773
|
+
_SingleTemplateManager,
|
|
2774
|
+
_StripeAppManifestDoc,
|
|
2775
|
+
_TemplateManager,
|
|
2776
|
+
_Transaction,
|
|
2777
|
+
_TsConfigDoc,
|
|
2778
|
+
_TypeMismatchError,
|
|
2779
|
+
_WriteAbortedError,
|
|
2780
|
+
_beginTransaction,
|
|
2781
|
+
_canonicalNormalize,
|
|
2782
|
+
_canonicalStringify,
|
|
2783
|
+
_check,
|
|
2784
|
+
_compilePointer,
|
|
2785
|
+
_compositeGenerator,
|
|
2786
|
+
_createCliContext,
|
|
2787
|
+
_createFilesystemTemplateFS,
|
|
2788
|
+
_createInMemoryTemplateFS,
|
|
2789
|
+
_createLogger,
|
|
2790
|
+
_createSimpleSingleFileTemplate,
|
|
2791
|
+
_createSimpleTemplate,
|
|
2792
|
+
_defaultMeta,
|
|
2793
|
+
_detectFormat,
|
|
2794
|
+
_detectMeta,
|
|
2795
|
+
_hunks as _diffHunks,
|
|
2796
|
+
_unified as _diffUnified,
|
|
2797
|
+
_escapeToken,
|
|
2798
|
+
_fingerprint,
|
|
2799
|
+
_getAdapter,
|
|
2800
|
+
_hasPointer,
|
|
2801
|
+
_inferStripeApiName,
|
|
2802
|
+
_isRecord,
|
|
2803
|
+
_isRecordWithValueType,
|
|
2804
|
+
_isSemverRange,
|
|
2805
|
+
_isSemverVersion,
|
|
2806
|
+
jsonAdapter as _jsonAdapter,
|
|
2807
|
+
jsoncAdapter as _jsoncAdapter,
|
|
2808
|
+
_looksPlural,
|
|
2809
|
+
_looksSingular,
|
|
2810
|
+
_openApiExtractor,
|
|
2811
|
+
_openDocument,
|
|
2812
|
+
_openDocumentFromDisk,
|
|
2813
|
+
_openPackageJson,
|
|
2814
|
+
_openStripeAppManifest,
|
|
2815
|
+
_openTsConfig,
|
|
2816
|
+
_parseArrayIndex,
|
|
2817
|
+
_parseLogLevel,
|
|
2818
|
+
_parsePointer,
|
|
2819
|
+
_pointer,
|
|
2820
|
+
_registerFormatAdapter,
|
|
2821
|
+
_resolvePointer,
|
|
2822
|
+
_runAssertions,
|
|
2823
|
+
_runAsyncAssertions,
|
|
2824
|
+
_semverRange,
|
|
2825
|
+
_semverVersion,
|
|
2826
|
+
_sha256Hex,
|
|
2827
|
+
_stripeMetadataPolicy,
|
|
2828
|
+
_toCapitalized,
|
|
2829
|
+
_toPascalCase,
|
|
2830
|
+
_toPlural,
|
|
2831
|
+
_toSentenceDisplayName,
|
|
2832
|
+
_toSingular,
|
|
2833
|
+
_toSnakeCase,
|
|
2834
|
+
_toStripeApiCase,
|
|
2835
|
+
_tokenizeIdentifier,
|
|
2836
|
+
_truncateName,
|
|
2837
|
+
_tryParseSemverRange,
|
|
2838
|
+
_tryParseSemverVersion,
|
|
2839
|
+
_tryValidateWithSchema,
|
|
2840
|
+
_unescapeToken,
|
|
2841
|
+
_validateApiName,
|
|
2842
|
+
_validateDisplayName,
|
|
2843
|
+
_validateWithSchema,
|
|
2844
|
+
_workspaceVersion,
|
|
2845
|
+
_writeJsonOutput,
|
|
2846
|
+
yamlAdapter as _yamlAdapter
|
|
2847
|
+
};
|