@nipe-solutions/flex-layout-codemod 2.0.0-beta.2 → 2.0.0-beta.4

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/dist/cli.js CHANGED
@@ -1,17 +1,788 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/migrator/migration-path.validator.ts
4
+ import { lstat, stat } from "fs/promises";
5
+ import * as path from "path";
6
+
7
+ // src/migrator/migration-application.error.ts
8
+ var MigrationApplicationError = class extends Error {
9
+ constructor(code, message, paths = [], options) {
10
+ super(message, options);
11
+ this.code = code;
12
+ this.paths = paths;
13
+ this.name = "MigrationApplicationError";
14
+ this.paths = Object.freeze([...paths]);
15
+ this.recoveryFailures = Object.freeze([...options?.recoveryFailures ?? []]);
16
+ }
17
+ recoveryFailures;
18
+ };
19
+
20
+ // src/migrator/migration-path.validator.ts
21
+ async function validateMigrationPaths(request, pathApi = path) {
22
+ const claims = normalizedClaims(request, pathApi);
23
+ await validateCollisions(claims, pathApi);
24
+ const destinations = claims.filter((claim) => claim.kind !== "template-input");
25
+ for (const destination of destinations) {
26
+ await validateDestination(destination.path);
27
+ }
28
+ }
29
+ async function validateStylesheetRootTopology(request, pathApi = path) {
30
+ if (request.stylesheetPath === void 0) return;
31
+ const stylesheetPath = pathApi.resolve(request.stylesheetPath);
32
+ const templateRoots = [request.inputPath, request.outputPath].map((claim) => pathApi.resolve(claim));
33
+ const reportPath = request.reportPath === void 0 ? void 0 : pathApi.resolve(request.reportPath);
34
+ const exactCollision = (await Promise.all(
35
+ [...templateRoots, reportPath].map(
36
+ (claim) => claim === void 0 ? Promise.resolve(false) : pathsEquivalentOnFileSystem(stylesheetPath, claim, pathApi)
37
+ )
38
+ )).some(Boolean);
39
+ const reportHierarchyCollision = reportPath !== void 0 && await pathsOverlapOnFileSystem(stylesheetPath, reportPath, pathApi);
40
+ if (exactCollision || reportHierarchyCollision) {
41
+ const collisionPaths = reportHierarchyCollision && reportPath !== void 0 && !await pathsEquivalentOnFileSystem(stylesheetPath, reportPath, pathApi) ? [stylesheetPath, reportPath] : [stylesheetPath];
42
+ throw new MigrationApplicationError(
43
+ "path-collision",
44
+ `Stylesheet path collides with another migration path: ${request.stylesheetPathInput ?? request.stylesheetPath}`,
45
+ collisionPaths
46
+ );
47
+ }
48
+ let stylesheetStat;
49
+ try {
50
+ stylesheetStat = await lstat(stylesheetPath);
51
+ } catch (error) {
52
+ if (isEnoent(error)) return;
53
+ throw error;
54
+ }
55
+ const sourcePath = request.stylesheetPathInput ?? request.stylesheetPath;
56
+ if (stylesheetStat.isSymbolicLink()) {
57
+ throw new MigrationApplicationError(
58
+ "unsupported-path-type",
59
+ `Stylesheet path must not be a symbolic link: ${sourcePath}`,
60
+ [stylesheetPath]
61
+ );
62
+ }
63
+ if (!stylesheetStat.isFile()) {
64
+ throw new MigrationApplicationError(
65
+ "unsupported-path-type",
66
+ `Stylesheet path must be a regular file: ${sourcePath}`,
67
+ [stylesheetPath]
68
+ );
69
+ }
70
+ }
71
+ function normalizedClaims(request, pathApi) {
72
+ return [
73
+ ...request.templates.flatMap((template, templateIndex) => [
74
+ { path: pathApi.resolve(template.inputPath), kind: "template-input", templateIndex },
75
+ { path: pathApi.resolve(template.outputPath), kind: "template-output", templateIndex }
76
+ ]),
77
+ ...request.stylesheetPath ? [{ path: pathApi.resolve(request.stylesheetPath), kind: "stylesheet" }] : [],
78
+ ...request.reportPath ? [{ path: pathApi.resolve(request.reportPath), kind: "report" }] : []
79
+ ];
80
+ }
81
+ async function validateCollisions(claims, pathApi) {
82
+ const observations = /* @__PURE__ */ new Map();
83
+ const observe = (candidate) => {
84
+ const normalized = pathApi.resolve(candidate);
85
+ const existing = observations.get(normalized);
86
+ if (existing) return existing;
87
+ const pending = observePath(normalized, pathApi);
88
+ observations.set(normalized, pending);
89
+ return pending;
90
+ };
91
+ for (let leftIndex = 0; leftIndex < claims.length; leftIndex++) {
92
+ const left = claims[leftIndex];
93
+ if (!left) continue;
94
+ for (let rightIndex = leftIndex + 1; rightIndex < claims.length; rightIndex++) {
95
+ const right = claims[rightIndex];
96
+ if (!right) continue;
97
+ const relationship = await fileSystemPathRelationship(left.path, right.path, pathApi, observe);
98
+ if (relationship === "distinct" || isIntentionalInPlacePair(left, right, pathApi)) continue;
99
+ const collisionPaths = relationship === "equivalent" ? [left.path] : [left.path, right.path];
100
+ throw new MigrationApplicationError(
101
+ "path-collision",
102
+ `Migration paths collide: ${collisionPaths.join(" and ")}`,
103
+ collisionPaths
104
+ );
105
+ }
106
+ }
107
+ }
108
+ function isIntentionalInPlacePair(left, right, pathApi) {
109
+ return pathsEquivalent(left.path, right.path, pathApi) && left.templateIndex !== void 0 && left.templateIndex === right.templateIndex && left.kind !== right.kind && left.kind.startsWith("template-") && right.kind.startsWith("template-");
110
+ }
111
+ function pathsEquivalent(left, right, pathApi = path) {
112
+ return normalizedPathsEquivalent(pathApi, pathApi.resolve(left), pathApi.resolve(right));
113
+ }
114
+ async function pathsEquivalentOnFileSystem(left, right, pathApi = path) {
115
+ return await fileSystemPathRelationship(left, right, pathApi) === "equivalent";
116
+ }
117
+ async function pathsOverlapOnFileSystem(left, right, pathApi = path) {
118
+ return await fileSystemPathRelationship(left, right, pathApi) !== "distinct";
119
+ }
120
+ function normalizedPathsEquivalent(pathApi, left, right) {
121
+ return pathApi.relative(left, right) === "";
122
+ }
123
+ function isAncestor(pathApi, ancestor, descendant) {
124
+ const relative3 = pathApi.relative(ancestor, descendant);
125
+ return relative3 !== "" && relative3 !== ".." && !relative3.startsWith(`..${pathApi.sep}`) && !pathApi.isAbsolute(relative3);
126
+ }
127
+ async function fileSystemPathRelationship(left, right, pathApi, observe = (candidate) => observePath(candidate, pathApi)) {
128
+ const normalizedLeft = pathApi.resolve(left);
129
+ const normalizedRight = pathApi.resolve(right);
130
+ if (normalizedPathsEquivalent(pathApi, normalizedLeft, normalizedRight)) return "equivalent";
131
+ if (isAncestor(pathApi, normalizedLeft, normalizedRight)) return "ancestor";
132
+ if (isAncestor(pathApi, normalizedRight, normalizedLeft)) return "descendant";
133
+ const [observedLeft, observedRight] = await Promise.all([observe(normalizedLeft), observe(normalizedRight)]);
134
+ if (observedLeft.exactIdentity && observedRight.exactIdentity) {
135
+ if (sameIdentity(observedLeft.exactIdentity, observedRight.exactIdentity)) return "equivalent";
136
+ if (hasIdentityBelow(observedRight, observedLeft.exactIdentity)) return "ancestor";
137
+ if (hasIdentityBelow(observedLeft, observedRight.exactIdentity)) return "descendant";
138
+ return "distinct";
139
+ }
140
+ return relationshipThroughExistingPrefixes(observedLeft, observedRight);
141
+ }
142
+ async function observePath(candidate, pathApi) {
143
+ const prefixes = [];
144
+ const suffix = [];
145
+ let current = candidate;
146
+ let exactIdentity;
147
+ while (true) {
148
+ try {
149
+ const currentStat = await stat(current, { bigint: true });
150
+ const currentIdentity = identity(currentStat);
151
+ if (suffix.length === 0) exactIdentity = currentIdentity;
152
+ prefixes.push({ identity: currentIdentity, suffix: [...suffix] });
153
+ } catch (error) {
154
+ if (!isMissingPath(error)) throw error;
155
+ }
156
+ const parent = pathApi.dirname(current);
157
+ if (parent === current) break;
158
+ suffix.unshift(pathApi.basename(current));
159
+ current = parent;
160
+ }
161
+ return { ...exactIdentity ? { exactIdentity } : {}, prefixes };
162
+ }
163
+ function relationshipThroughExistingPrefixes(left, right) {
164
+ for (const leftPrefix of left.prefixes) {
165
+ for (const rightPrefix of right.prefixes) {
166
+ if (!sameIdentity(leftPrefix.identity, rightPrefix.identity)) continue;
167
+ const relationship = suffixRelationship(leftPrefix.suffix, rightPrefix.suffix);
168
+ if (relationship !== "distinct") return relationship;
169
+ }
170
+ }
171
+ return "distinct";
172
+ }
173
+ function hasIdentityBelow(observed, candidate) {
174
+ return observed.prefixes.some((prefix) => prefix.suffix.length > 0 && sameIdentity(prefix.identity, candidate));
175
+ }
176
+ function suffixRelationship(left, right) {
177
+ const normalizedLeft = left.map(portablePathSegment);
178
+ const normalizedRight = right.map(portablePathSegment);
179
+ const sharedLength = Math.min(normalizedLeft.length, normalizedRight.length);
180
+ for (let index = 0; index < sharedLength; index++) {
181
+ if (normalizedLeft[index] !== normalizedRight[index]) return "distinct";
182
+ }
183
+ if (normalizedLeft.length === normalizedRight.length) return "equivalent";
184
+ return normalizedLeft.length < normalizedRight.length ? "ancestor" : "descendant";
185
+ }
186
+ function portablePathSegment(value) {
187
+ return value.normalize("NFC").toLowerCase();
188
+ }
189
+ function identity(value) {
190
+ return { device: String(value.dev), inode: String(value.ino) };
191
+ }
192
+ function sameIdentity(left, right) {
193
+ return left.device === right.device && left.inode === right.inode;
194
+ }
195
+ async function validateDestination(destination) {
196
+ let stat4;
197
+ try {
198
+ stat4 = await lstat(destination);
199
+ } catch (error) {
200
+ if (isEnoent(error)) return;
201
+ throw error;
202
+ }
203
+ if (stat4.isSymbolicLink()) {
204
+ throw new MigrationApplicationError(
205
+ "unsupported-path-type",
206
+ `Migration destination must not be a symbolic link: ${destination}`,
207
+ [destination]
208
+ );
209
+ }
210
+ if (!stat4.isFile()) {
211
+ throw new MigrationApplicationError(
212
+ "unsupported-path-type",
213
+ `Migration destination must be a regular file: ${destination}`,
214
+ [destination]
215
+ );
216
+ }
217
+ }
218
+ function isEnoent(error) {
219
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
220
+ }
221
+ function isMissingPath(error) {
222
+ return typeof error === "object" && error !== null && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
223
+ }
224
+
225
+ // src/config/migration-config.ts
226
+ import { readFile, realpath } from "fs/promises";
227
+ import path2 from "path";
228
+ import { createHash } from "crypto";
229
+ import postcss2 from "postcss";
230
+
231
+ // src/util/sha-256.ts
232
+ var ROUND_CONSTANTS = Object.freeze([
233
+ 1116352408,
234
+ 1899447441,
235
+ 3049323471,
236
+ 3921009573,
237
+ 961987163,
238
+ 1508970993,
239
+ 2453635748,
240
+ 2870763221,
241
+ 3624381080,
242
+ 310598401,
243
+ 607225278,
244
+ 1426881987,
245
+ 1925078388,
246
+ 2162078206,
247
+ 2614888103,
248
+ 3248222580,
249
+ 3835390401,
250
+ 4022224774,
251
+ 264347078,
252
+ 604807628,
253
+ 770255983,
254
+ 1249150122,
255
+ 1555081692,
256
+ 1996064986,
257
+ 2554220882,
258
+ 2821834349,
259
+ 2952996808,
260
+ 3210313671,
261
+ 3336571891,
262
+ 3584528711,
263
+ 113926993,
264
+ 338241895,
265
+ 666307205,
266
+ 773529912,
267
+ 1294757372,
268
+ 1396182291,
269
+ 1695183700,
270
+ 1986661051,
271
+ 2177026350,
272
+ 2456956037,
273
+ 2730485921,
274
+ 2820302411,
275
+ 3259730800,
276
+ 3345764771,
277
+ 3516065817,
278
+ 3600352804,
279
+ 4094571909,
280
+ 275423344,
281
+ 430227734,
282
+ 506948616,
283
+ 659060556,
284
+ 883997877,
285
+ 958139571,
286
+ 1322822218,
287
+ 1537002063,
288
+ 1747873779,
289
+ 1955562222,
290
+ 2024104815,
291
+ 2227730452,
292
+ 2361852424,
293
+ 2428436474,
294
+ 2756734187,
295
+ 3204031479,
296
+ 3329325298
297
+ ]);
298
+ var INITIAL_HASH = Object.freeze([
299
+ 1779033703,
300
+ 3144134277,
301
+ 1013904242,
302
+ 2773480762,
303
+ 1359893119,
304
+ 2600822924,
305
+ 528734635,
306
+ 1541459225
307
+ ]);
308
+ function sha256(value) {
309
+ const source = new TextEncoder().encode(value);
310
+ const paddedLength = Math.ceil((source.length + 9) / 64) * 64;
311
+ const bytes = new Uint8Array(paddedLength);
312
+ bytes.set(source);
313
+ bytes[source.length] = 128;
314
+ const bitLength = source.length * 8;
315
+ const view = new DataView(bytes.buffer);
316
+ view.setUint32(paddedLength - 8, Math.floor(bitLength / 4294967296));
317
+ view.setUint32(paddedLength - 4, bitLength >>> 0);
318
+ const hash = [...INITIAL_HASH];
319
+ const schedule = new Uint32Array(64);
320
+ for (let offset = 0; offset < bytes.length; offset += 64) {
321
+ for (let index = 0; index < 16; index += 1) schedule[index] = view.getUint32(offset + index * 4);
322
+ for (let index = 16; index < schedule.length; index += 1) {
323
+ const earlier = schedule[index - 15];
324
+ const recent = schedule[index - 2];
325
+ const sigma0 = rotateRight(earlier, 7) ^ rotateRight(earlier, 18) ^ earlier >>> 3;
326
+ const sigma1 = rotateRight(recent, 17) ^ rotateRight(recent, 19) ^ recent >>> 10;
327
+ schedule[index] = schedule[index - 16] + sigma0 + schedule[index - 7] + sigma1 >>> 0;
328
+ }
329
+ let [a, b, c, d, e, f, g, h] = hash;
330
+ for (let index = 0; index < schedule.length; index += 1) {
331
+ const sum1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
332
+ const choice = e & f ^ ~e & g;
333
+ const temporary1 = h + sum1 + choice + ROUND_CONSTANTS[index] + schedule[index] >>> 0;
334
+ const sum0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
335
+ const majority = a & b ^ a & c ^ b & c;
336
+ const temporary2 = sum0 + majority >>> 0;
337
+ h = g;
338
+ g = f;
339
+ f = e;
340
+ e = d + temporary1 >>> 0;
341
+ d = c;
342
+ c = b;
343
+ b = a;
344
+ a = temporary1 + temporary2 >>> 0;
345
+ }
346
+ const round = [a, b, c, d, e, f, g, h];
347
+ for (let index = 0; index < hash.length; index += 1) hash[index] = hash[index] + round[index] >>> 0;
348
+ }
349
+ return hash.map((word) => word.toString(16).padStart(8, "0")).join("");
350
+ }
351
+ function rotateRight(value, count) {
352
+ return value >>> count | value << 32 - count;
353
+ }
354
+
355
+ // src/config/tailwind-target-profile.ts
356
+ import postcss from "postcss";
357
+ var defaults = { sm: "40rem", md: "48rem", lg: "64rem", xl: "80rem", "2xl": "96rem" };
358
+ function validBreakpoint(value) {
359
+ return /^(?:0|[1-9]\d*)(?:\.\d+)?(?:px|rem|em)$/.test(value) && Number.parseFloat(value) < 1e7;
360
+ }
361
+ function validPrefix(value) {
362
+ return /^[a-z]+$/.test(value);
363
+ }
364
+ function analyzeTailwindStylesheet(css, source = "pasted stylesheet") {
365
+ const operations = [];
366
+ const diagnostics = [];
367
+ const imports = [];
368
+ let tailwind = false;
369
+ let defaultTheme = false;
370
+ let utilities = false;
371
+ const warn = (code, message) => diagnostics.push({ code, message: `${source}: ${message}` });
372
+ if (css.length > 2e6) throw new Error("Stylesheet exceeds the 2 MB static analysis limit.");
373
+ const root = postcss.parse(css, { from: void 0 });
374
+ root.walkAtRules((rule) => {
375
+ if (rule.name === "custom-variant" || rule.name === "utility")
376
+ warn("tailwind-target-unknown", `@${rule.name} may change generated utility semantics and is not evaluated.`);
377
+ if (rule.name === "config" || rule.name === "plugin") {
378
+ warn(
379
+ rule.name === "config" ? "tailwind-config-external" : "tailwind-plugin-external",
380
+ `@${rule.name} ${rule.params} was not executed; additional configuration is unknown.`
381
+ );
382
+ }
383
+ if (rule.name === "source" && /^not\b/.test(rule.params)) {
384
+ warn("tailwind-source-excluded", `Source exclusion ${rule.params} may exclude migrated templates.`);
385
+ }
386
+ if (rule.name === "import") {
387
+ const match = /^(["'])([^"']+)\1(.*)$/.exec(rule.params);
388
+ if (!match) {
389
+ warn("tailwind-import-unresolved", `Import ${rule.params} was not loaded.`);
390
+ return;
391
+ }
392
+ const imported = match[2];
393
+ const modifiers = match[3];
394
+ if (/^tailwindcss(?:\/(?:theme|utilities|preflight)\.css)?$/.test(imported)) {
395
+ tailwind = true;
396
+ if (rule.parent?.type !== "root") {
397
+ warn("tailwind-target-unknown", "Conditional Tailwind import is unresolved.");
398
+ return;
399
+ }
400
+ let remaining = modifiers.trim();
401
+ let prefix = null;
402
+ let important = false;
403
+ const seen = /* @__PURE__ */ new Set();
404
+ while (remaining) {
405
+ const modifier = /^(prefix\(([^)]*)\)|important|source\((?:none|"[^"]*"|'[^']*')\)|layer\([a-zA-Z0-9_.-]+\))(?=\s|$)/.exec(
406
+ remaining
407
+ );
408
+ if (!modifier) {
409
+ warn("tailwind-target-unknown", `Unsupported Tailwind import modifiers: ${remaining}`);
410
+ break;
411
+ }
412
+ const name = modifier[1].split("(")[0];
413
+ if (seen.has(name)) warn("tailwind-target-unknown", `Repeated Tailwind import modifier ${name}.`);
414
+ seen.add(name);
415
+ if (name === "prefix") {
416
+ if (!validPrefix(modifier[2])) warn("tailwind-target-unknown", "Invalid Tailwind v4 prefix.");
417
+ else prefix = modifier[2];
418
+ }
419
+ if (name === "important") important = true;
420
+ if (modifier[1] === "source(none)")
421
+ warn("tailwind-source-excluded", "source(none) disables automatic template discovery.");
422
+ remaining = remaining.slice(modifier[0].length).trim();
423
+ }
424
+ if (imported === "tailwindcss" || imported === "tailwindcss/theme.css") defaultTheme = true;
425
+ if (imported !== "tailwindcss/preflight.css") operations.push({ kind: "prefix", value: prefix, source });
426
+ if (imported === "tailwindcss" || imported === "tailwindcss/utilities.css") {
427
+ utilities = true;
428
+ operations.push({ kind: "important", value: important ? "important" : "normal", source });
429
+ }
430
+ } else if (imported.startsWith("./") || imported.startsWith("../")) {
431
+ if (rule.parent?.type !== "root" || modifiers.trim())
432
+ warn("tailwind-import-unresolved", `Conditional import ${imported} was not loaded.`);
433
+ else imports.push(imported);
434
+ } else warn("tailwind-import-unresolved", `External import ${imported} was not loaded.`);
435
+ }
436
+ if (rule.name !== "theme") return;
437
+ if (rule.parent?.type !== "root" || rule.params.trim() && !/^(?:inline|static)(?:\s+(?:inline|static))*$/.test(rule.params.trim())) {
438
+ warn("tailwind-target-unknown", "Conditional or unsupported @theme block was not resolved.");
439
+ return;
440
+ }
441
+ rule.each((node) => {
442
+ if (node.type !== "decl") return;
443
+ if (node.prop === "--*") {
444
+ if (node.value.trim() === "initial" && !node.important) {
445
+ operations.push({ kind: "breakpoint", name: "*", value: null, source });
446
+ } else warn("tailwind-target-unknown", "Cannot resolve global theme reset.");
447
+ return;
448
+ }
449
+ if (!node.prop.startsWith("--breakpoint-")) return;
450
+ const name = node.prop.slice("--breakpoint-".length);
451
+ const value = node.value.trim();
452
+ if (!/^(?:\*|[a-zA-Z0-9][a-zA-Z0-9_-]*)$/.test(name) || node.important || value !== "initial" && (name === "*" || !validBreakpoint(value))) {
453
+ warn("tailwind-breakpoint-unknown", `Cannot resolve ${node.prop}: ${value}.`);
454
+ if (/^(?:\*|[a-zA-Z0-9][a-zA-Z0-9_-]*)$/.test(name))
455
+ operations.push({ kind: "breakpoint", name, value: null, source });
456
+ return;
457
+ }
458
+ operations.push({ kind: "breakpoint", name, value: value === "initial" ? null : value, source });
459
+ });
460
+ });
461
+ return { operations, diagnostics, imports, tailwind, defaultTheme, utilities, source };
462
+ }
463
+ function resolveTailwindTargetProfile(input = {}) {
464
+ let coreUtilities = {
465
+ value: "standard",
466
+ source: input.detected?.source ?? "default",
467
+ confidence: input.detected ? "detected" : "defaulted"
468
+ };
469
+ let prefix = { value: null, source: "default", confidence: "defaulted" };
470
+ let important = { value: "normal", source: "default", confidence: "defaulted" };
471
+ let breakpoints = Object.fromEntries(
472
+ Object.entries(input.detected && !input.detected.defaultTheme ? {} : defaults).map(([name, value]) => [
473
+ name,
474
+ { value, source: "default", confidence: "defaulted" }
475
+ ])
476
+ );
477
+ const diagnostics = [
478
+ ...input.detected?.diagnostics ?? [],
479
+ ...(input.detected?.imports ?? []).map((imported) => ({
480
+ code: "tailwind-import-unresolved",
481
+ message: `${input.detected.source}: Local import ${imported} was not loaded in this analysis.`
482
+ }))
483
+ ];
484
+ function apply(operation, confidence) {
485
+ const { value, source } = operation;
486
+ if (operation.kind === "prefix") {
487
+ if (value !== null && !validPrefix(value))
488
+ throw new Error("Tailwind v4 prefix must contain only lowercase letters.");
489
+ if (prefix.confidence !== "defaulted" && prefix.value !== value)
490
+ diagnostics.push({
491
+ code: "tailwind-prefix-conflict",
492
+ message: `Using prefix ${String(value)} from ${source}; ${prefix.source} declared ${String(prefix.value)}.`
493
+ });
494
+ prefix = { value, source, confidence };
495
+ } else if (operation.kind === "important") important = { value: operation.value, source, confidence };
496
+ else if (operation.name === "*" && value === null) breakpoints = {};
497
+ else if (value === null) delete breakpoints[operation.name];
498
+ else {
499
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(operation.name) || !validBreakpoint(value))
500
+ throw new Error(`Invalid Tailwind breakpoint ${operation.name}: ${value}`);
501
+ breakpoints[operation.name] = { value, source, confidence };
502
+ }
503
+ }
504
+ input.detected?.operations.forEach((operation) => apply(operation, "detected"));
505
+ for (const [overrides, source] of [
506
+ [input.explicit, input.explicitSource ?? "migration profile"],
507
+ [input.cli, "CLI"]
508
+ ]) {
509
+ if (!overrides) continue;
510
+ if (overrides.version !== void 0 && overrides.version !== 4)
511
+ throw new Error("Only Tailwind v4 targets are supported.");
512
+ if (overrides.coreUtilities !== void 0) {
513
+ if (overrides.coreUtilities !== "standard")
514
+ throw new Error("coreUtilities must be standard when explicitly asserted.");
515
+ coreUtilities = { value: "standard", source, confidence: "explicit" };
516
+ }
517
+ if (overrides.prefix !== void 0) apply({ kind: "prefix", value: overrides.prefix, source }, "explicit");
518
+ if (overrides.important !== void 0) {
519
+ if (!["normal", "important"].includes(overrides.important)) throw new Error("Invalid Tailwind important mode.");
520
+ apply({ kind: "important", value: overrides.important, source }, "explicit");
521
+ }
522
+ const entries = Object.entries(overrides.breakpoints ?? {}).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
523
+ for (const [name, value] of entries)
524
+ apply({ kind: "breakpoint", name, value: value === "initial" ? null : value, source }, "explicit");
525
+ }
526
+ breakpoints = Object.fromEntries(Object.entries(breakpoints).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
527
+ if (new Set(Object.values(breakpoints).map((setting) => setting.value.match(/[a-z]+$/)[0])).size > 1)
528
+ diagnostics.push({
529
+ code: "tailwind-mixed-breakpoint-units",
530
+ message: "Mixed breakpoint units may affect Tailwind variant ordering; units were preserved."
531
+ });
532
+ if (input.detected && !input.detected.utilities)
533
+ diagnostics.push({
534
+ code: "tailwind-target-unknown",
535
+ message: "The supplied stylesheet does not declare Tailwind utility generation."
536
+ });
537
+ if (input.detected && !input.detected.tailwind)
538
+ diagnostics.push({ code: "tailwind-target-unknown", message: "No supported Tailwind v4 import was detected." });
539
+ if (diagnostics.some(
540
+ (item) => [
541
+ "tailwind-config-external",
542
+ "tailwind-plugin-external",
543
+ "tailwind-import-unresolved",
544
+ "tailwind-target-unknown"
545
+ ].includes(item.code)
546
+ )) {
547
+ if (coreUtilities.confidence !== "explicit")
548
+ coreUtilities = { ...coreUtilities, value: "unknown", confidence: "unknown" };
549
+ breakpoints = Object.fromEntries(
550
+ Object.entries(breakpoints).map(([name, setting]) => [
551
+ name,
552
+ setting.confidence === "explicit" ? setting : { ...setting, confidence: "unknown" }
553
+ ])
554
+ );
555
+ if (prefix.confidence !== "explicit") prefix = { ...prefix, confidence: "unknown" };
556
+ if (important.confidence !== "explicit")
557
+ important = { value: "unknown", source: important.source, confidence: "unknown" };
558
+ }
559
+ const assumptions = [
560
+ "Custom application CSS is not globally analyzed.",
561
+ "Responsive source ranges are preserved, including screen and print conditions."
562
+ ];
563
+ if (!input.detected && !input.explicit && !input.cli)
564
+ assumptions.push("Target profile uses Tailwind v4 defaults; project configuration was not discovered.");
565
+ if (diagnostics.some((d) => d.code === "tailwind-config-external" || d.code === "tailwind-plugin-external"))
566
+ assumptions.push("Legacy JavaScript configuration and plugins were not executed.");
567
+ if (coreUtilities.confidence === "explicit")
568
+ assumptions.push(
569
+ "The migration profile explicitly asserts standard Tailwind core utility semantics; this assertion was not verified by executing project code."
570
+ );
571
+ if (important.value === "important")
572
+ assumptions.push("Generated Tailwind utilities are globally important; existing styles still require review.");
573
+ const canonical = JSON.stringify({
574
+ version: 4,
575
+ prefix: prefix.value,
576
+ important: important.value,
577
+ coreUtilities: coreUtilities.value,
578
+ breakpoints: Object.fromEntries(Object.entries(breakpoints).map(([name, setting]) => [name, setting.value]))
579
+ });
580
+ return deepFreeze({
581
+ target: "tailwind",
582
+ version: 4,
583
+ prefix,
584
+ important,
585
+ coreUtilities,
586
+ breakpoints,
587
+ diagnostics,
588
+ assumptions,
589
+ fingerprint: `tw4-${sha256(canonical)}`,
590
+ ...input.detected ? { stylesheet: input.detected.source } : {}
591
+ });
592
+ }
593
+ function deepFreeze(value) {
594
+ if (value && typeof value === "object") {
595
+ Object.values(value).forEach(deepFreeze);
596
+ Object.freeze(value);
597
+ }
598
+ return value;
599
+ }
600
+
601
+ // src/config/source-breakpoints.ts
602
+ function sourceBreakpointDefinition(alias, input) {
603
+ if (!/^[a-zA-Z][a-zA-Z0-9_.-]*$/.test(alias) || !Number.isSafeInteger(input.priority))
604
+ throw new Error(`Invalid source breakpoint ${alias}; an integer priority is required.`);
605
+ if (typeof input.mediaQuery !== "string") throw new Error(`Invalid source media query for ${alias}.`);
606
+ const parts = input.mediaQuery.trim().split(/\s+and\s+/);
607
+ if (parts.shift() !== "screen" || !parts.length)
608
+ throw new Error(`Source breakpoint ${alias} requires screen and explicit px width/orientation conditions.`);
609
+ const range2 = {};
610
+ for (const part of parts) {
611
+ const width = /^\((min|max)-width:\s*(\d+(?:\.\d+)?)px\)$/.exec(part);
612
+ const orientation = /^\(orientation:\s*(portrait|landscape)\)$/.exec(part);
613
+ if (width) {
614
+ const key = width[1];
615
+ if (range2[key] !== void 0 || !Number.isFinite(Number(width[2])))
616
+ throw new Error(`Duplicate or invalid source condition in ${alias}.`);
617
+ range2[key] = Number(width[2]);
618
+ } else if (orientation && range2.orientation === void 0)
619
+ range2.orientation = orientation[1];
620
+ else throw new Error(`Unsupported source media condition in ${alias}: ${part}`);
621
+ }
622
+ if ((range2.min ?? 0) > (range2.max ?? Infinity)) throw new Error(`Inverted source media range in ${alias}.`);
623
+ const clause = Object.freeze(range2);
624
+ return Object.freeze({
625
+ alias,
626
+ priority: input.priority,
627
+ range: clause,
628
+ media: Object.freeze({ type: "screen", clauses: Object.freeze([clause]) })
629
+ });
630
+ }
631
+
632
+ // src/config/migration-config.ts
633
+ function object(value, allowed, label) {
634
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object.`);
635
+ for (const key of Object.keys(value)) if (!allowed.includes(key)) throw new Error(`Unknown ${label} setting: ${key}`);
636
+ return value;
637
+ }
638
+ function parseConfig(contents) {
639
+ const config = object(JSON.parse(contents), ["target", "tailwind", "source"], "migration config");
640
+ if (config.target !== void 0 && config.target !== "tailwind" && config.target !== "css")
641
+ throw new Error("Invalid migration target.");
642
+ if (config.tailwind !== void 0) {
643
+ const tw = object(
644
+ config.tailwind,
645
+ ["version", "stylesheet", "prefix", "breakpoints", "important", "coreUtilities"],
646
+ "tailwind"
647
+ );
648
+ if (tw.stylesheet !== void 0 && typeof tw.stylesheet !== "string")
649
+ throw new Error("Tailwind stylesheet must be a path.");
650
+ if (tw.prefix !== void 0 && tw.prefix !== null && typeof tw.prefix !== "string")
651
+ throw new Error("Tailwind prefix must be a string or null.");
652
+ if (tw.breakpoints !== void 0) {
653
+ if (!tw.breakpoints || typeof tw.breakpoints !== "object" || Array.isArray(tw.breakpoints))
654
+ throw new Error("Tailwind breakpoints must be an object.");
655
+ for (const value of Object.values(tw.breakpoints))
656
+ if (value !== null && typeof value !== "string")
657
+ throw new Error("Tailwind breakpoint values must be lengths or null.");
658
+ }
659
+ }
660
+ if (config.source !== void 0) {
661
+ const source = object(config.source, ["flexLayout"], "source");
662
+ if (source.flexLayout !== void 0) {
663
+ const flex = object(source.flexLayout, ["breakpoints"], "source.flexLayout");
664
+ if (flex.breakpoints !== void 0) {
665
+ if (!flex.breakpoints || typeof flex.breakpoints !== "object" || Array.isArray(flex.breakpoints))
666
+ throw new Error("Source breakpoints must be an object.");
667
+ for (const [alias, value] of Object.entries(flex.breakpoints)) {
668
+ object(value, ["mediaQuery", "priority"], `source breakpoint ${alias}`);
669
+ sourceBreakpointDefinition(alias, value);
670
+ }
671
+ }
672
+ }
673
+ }
674
+ return config;
675
+ }
676
+ var digest = (value) => createHash("sha256").update(value).digest("hex");
677
+ function inside(root, candidate) {
678
+ const relative3 = path2.relative(root, candidate);
679
+ return relative3 !== ".." && !relative3.startsWith(`..${path2.sep}`) && !path2.isAbsolute(relative3);
680
+ }
681
+ async function assertConfigurationUnchanged(snapshots) {
682
+ for (const snapshot of snapshots) {
683
+ let current;
684
+ try {
685
+ if (await realpath(snapshot.path) !== snapshot.canonicalPath) throw new Error("Configuration path changed.");
686
+ current = digest(await readFile(snapshot.path, "utf8"));
687
+ } catch {
688
+ throw new Error(`Target configuration changed or disappeared since planning: ${snapshot.path}`);
689
+ }
690
+ if (current !== snapshot.digest) throw new Error(`Target configuration changed since planning: ${snapshot.path}`);
691
+ }
692
+ }
693
+ async function loadMigrationConfig(options) {
694
+ const root = await realpath(options.cwd ?? process.cwd());
695
+ const snapshots = [];
696
+ const read = async (file) => {
697
+ const contents = await readFile(file, "utf8");
698
+ if (contents.length > 2e6) throw new Error("Configuration file exceeds the 2 MB static analysis limit.");
699
+ if (!snapshots.some((snapshot) => snapshot.path === file))
700
+ snapshots.push(Object.freeze({ path: file, canonicalPath: await realpath(file), digest: digest(contents) }));
701
+ return contents;
702
+ };
703
+ const configPath = path2.resolve(root, options.config ?? "flex-layout-migrator.config.json");
704
+ if (!configPath.endsWith(".json")) throw new Error("Migration configuration must be declarative JSON.");
705
+ let config = {};
706
+ try {
707
+ config = parseConfig(await read(configPath));
708
+ } catch (error) {
709
+ if (options.config !== void 0 || !(error && typeof error === "object" && "code" in error && error.code === "ENOENT"))
710
+ throw error;
711
+ }
712
+ const stylesheet = options.stylesheet === void 0 ? config.tailwind?.stylesheet === void 0 ? void 0 : path2.resolve(path2.dirname(configPath), config.tailwind.stylesheet) : path2.resolve(root, options.stylesheet);
713
+ let detected;
714
+ if (stylesheet) {
715
+ const operations = [];
716
+ const diagnostics = [];
717
+ let tailwind = false;
718
+ let defaultTheme = false;
719
+ let utilities = false;
720
+ let visited = 0;
721
+ const relative3 = (file) => path2.relative(root, file).split(path2.sep).join("/");
722
+ const visit = async (file, ancestors, depth) => {
723
+ if (++visited > 128 || depth > 16)
724
+ throw new Error("CSS import analysis limit exceeded (128 imports / depth 16).");
725
+ const canonical = await realpath(file);
726
+ if (!inside(root, canonical)) throw new Error("CSS import is outside the project root.");
727
+ if (ancestors.includes(canonical)) throw new Error("CSS import cycle detected.");
728
+ const contents = await read(file);
729
+ const parsed2 = postcss2.parse(contents, { from: void 0 });
730
+ for (const node of parsed2.nodes) {
731
+ const analysis = analyzeTailwindStylesheet(node.toString(), relative3(canonical));
732
+ operations.push(...analysis.operations);
733
+ diagnostics.push(...analysis.diagnostics);
734
+ tailwind ||= analysis.tailwind;
735
+ defaultTheme ||= analysis.defaultTheme;
736
+ utilities ||= analysis.utilities;
737
+ for (const imported of analysis.imports) {
738
+ try {
739
+ await visit(path2.resolve(path2.dirname(canonical), imported), [...ancestors, canonical], depth + 1);
740
+ } catch (error) {
741
+ diagnostics.push({
742
+ code: "tailwind-import-unresolved",
743
+ message: `${relative3(canonical)}: ${imported}: ${error instanceof Error ? error.message : "Import unresolved"}`
744
+ });
745
+ }
746
+ }
747
+ }
748
+ };
749
+ await visit(stylesheet, [], 0);
750
+ detected = {
751
+ source: relative3(stylesheet),
752
+ operations,
753
+ diagnostics,
754
+ imports: [],
755
+ defaultTheme,
756
+ utilities,
757
+ tailwind
758
+ };
759
+ }
760
+ const targetProfile = resolveTailwindTargetProfile({
761
+ detected,
762
+ explicit: config.tailwind,
763
+ explicitSource: path2.relative(root, configPath).split(path2.sep).join("/"),
764
+ ...options.prefix !== void 0 ? { cli: { prefix: options.prefix || null } } : {}
765
+ });
766
+ return Object.freeze({
767
+ target: config.target,
768
+ targetProfile,
769
+ sourceBreakpoints: config.source?.flexLayout?.breakpoints,
770
+ snapshots: Object.freeze(snapshots)
771
+ });
772
+ }
773
+
3
774
  // src/cli/run-cli.ts
4
775
  import { Command, CommanderError, InvalidArgumentError, Option } from "commander";
5
- import * as path17 from "path";
776
+ import * as path18 from "path";
6
777
 
7
778
  // package.json
8
779
  var package_default = {
9
780
  name: "@nipe-solutions/flex-layout-codemod",
10
- version: "2.0.0-beta.2",
781
+ version: "2.0.0-beta.4",
11
782
  description: "A safety-first codemod for migrating Angular Flex-Layout templates to native CSS or Tailwind CSS.",
12
783
  type: "module",
13
784
  engines: {
14
- node: ">=24"
785
+ node: ">=22.12.0"
15
786
  },
16
787
  packageManager: "npm@11.19.0",
17
788
  files: [
@@ -45,7 +816,8 @@ var package_default = {
45
816
  format: "prettier --check .",
46
817
  "format:write": "prettier --write .",
47
818
  verify: "npm run format && npm run lint && npm run typecheck && npm run test:coverage && npm run build && npm run package:check",
48
- "verify:website": "npm run format && npm run lint && npm run test:website && npm run typecheck:website && npm run verify:website-assets && npm run build:website && npm run verify:website-static && npm run test:e2e:website",
819
+ "verify:website": "npm run format && npm run lint && npm run test:website && npm run typecheck:website && npm run verify:website-assets && npm run verify:docs && npm run build:website && npm run verify:website-static && npm run test:e2e:website",
820
+ "verify:docs": "node scripts/verify-documentation-contract.mjs",
49
821
  "verify:website-static": "node scripts/verify-website-static.mjs",
50
822
  "package:check": "node scripts/verify-package.mjs",
51
823
  changeset: "changeset add",
@@ -54,7 +826,8 @@ var package_default = {
54
826
  "release:verify": "node scripts/release-artifact.mjs --verify-retained",
55
827
  "verify:website-assets": "node scripts/verify-website-assets.mjs",
56
828
  "release:version": "changeset version && npm install --package-lock-only --ignore-scripts --no-audit --no-fund",
57
- prepare: "husky"
829
+ prepare: "husky",
830
+ check: "npm run verify && npm run verify:website"
58
831
  },
59
832
  repository: {
60
833
  type: "git",
@@ -79,6 +852,7 @@ var package_default = {
79
852
  commander: "^15.0.0",
80
853
  "fs-extra": "^11.4.0",
81
854
  ignore: "5.2.4",
855
+ postcss: "^8.5.26",
82
856
  winston: "^3.19.0"
83
857
  },
84
858
  bin: {
@@ -106,7 +880,6 @@ var package_default = {
106
880
  jsdom: "^30.0.1",
107
881
  "lint-staged": "^17.4.1",
108
882
  "mock-fs": "^5.5.0",
109
- postcss: "^8.5.26",
110
883
  prettier: "^3.9.6",
111
884
  react: "^19.2.8",
112
885
  "react-dom": "^19.2.8",
@@ -219,130 +992,6 @@ function isKnownBreakpoint(value) {
219
992
  return breakpointNames.has(value);
220
993
  }
221
994
 
222
- // src/util/sha-256.ts
223
- var ROUND_CONSTANTS = Object.freeze([
224
- 1116352408,
225
- 1899447441,
226
- 3049323471,
227
- 3921009573,
228
- 961987163,
229
- 1508970993,
230
- 2453635748,
231
- 2870763221,
232
- 3624381080,
233
- 310598401,
234
- 607225278,
235
- 1426881987,
236
- 1925078388,
237
- 2162078206,
238
- 2614888103,
239
- 3248222580,
240
- 3835390401,
241
- 4022224774,
242
- 264347078,
243
- 604807628,
244
- 770255983,
245
- 1249150122,
246
- 1555081692,
247
- 1996064986,
248
- 2554220882,
249
- 2821834349,
250
- 2952996808,
251
- 3210313671,
252
- 3336571891,
253
- 3584528711,
254
- 113926993,
255
- 338241895,
256
- 666307205,
257
- 773529912,
258
- 1294757372,
259
- 1396182291,
260
- 1695183700,
261
- 1986661051,
262
- 2177026350,
263
- 2456956037,
264
- 2730485921,
265
- 2820302411,
266
- 3259730800,
267
- 3345764771,
268
- 3516065817,
269
- 3600352804,
270
- 4094571909,
271
- 275423344,
272
- 430227734,
273
- 506948616,
274
- 659060556,
275
- 883997877,
276
- 958139571,
277
- 1322822218,
278
- 1537002063,
279
- 1747873779,
280
- 1955562222,
281
- 2024104815,
282
- 2227730452,
283
- 2361852424,
284
- 2428436474,
285
- 2756734187,
286
- 3204031479,
287
- 3329325298
288
- ]);
289
- var INITIAL_HASH = Object.freeze([
290
- 1779033703,
291
- 3144134277,
292
- 1013904242,
293
- 2773480762,
294
- 1359893119,
295
- 2600822924,
296
- 528734635,
297
- 1541459225
298
- ]);
299
- function sha256(value) {
300
- const source = new TextEncoder().encode(value);
301
- const paddedLength = Math.ceil((source.length + 9) / 64) * 64;
302
- const bytes = new Uint8Array(paddedLength);
303
- bytes.set(source);
304
- bytes[source.length] = 128;
305
- const bitLength = source.length * 8;
306
- const view = new DataView(bytes.buffer);
307
- view.setUint32(paddedLength - 8, Math.floor(bitLength / 4294967296));
308
- view.setUint32(paddedLength - 4, bitLength >>> 0);
309
- const hash = [...INITIAL_HASH];
310
- const schedule = new Uint32Array(64);
311
- for (let offset = 0; offset < bytes.length; offset += 64) {
312
- for (let index = 0; index < 16; index += 1) schedule[index] = view.getUint32(offset + index * 4);
313
- for (let index = 16; index < schedule.length; index += 1) {
314
- const earlier = schedule[index - 15];
315
- const recent = schedule[index - 2];
316
- const sigma0 = rotateRight(earlier, 7) ^ rotateRight(earlier, 18) ^ earlier >>> 3;
317
- const sigma1 = rotateRight(recent, 17) ^ rotateRight(recent, 19) ^ recent >>> 10;
318
- schedule[index] = schedule[index - 16] + sigma0 + schedule[index - 7] + sigma1 >>> 0;
319
- }
320
- let [a, b, c, d, e, f, g, h] = hash;
321
- for (let index = 0; index < schedule.length; index += 1) {
322
- const sum1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
323
- const choice = e & f ^ ~e & g;
324
- const temporary1 = h + sum1 + choice + ROUND_CONSTANTS[index] + schedule[index] >>> 0;
325
- const sum0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
326
- const majority = a & b ^ a & c ^ b & c;
327
- const temporary2 = sum0 + majority >>> 0;
328
- h = g;
329
- g = f;
330
- f = e;
331
- e = d + temporary1 >>> 0;
332
- d = c;
333
- c = b;
334
- b = a;
335
- a = temporary1 + temporary2 >>> 0;
336
- }
337
- const round = [a, b, c, d, e, f, g, h];
338
- for (let index = 0; index < hash.length; index += 1) hash[index] = hash[index] + round[index] >>> 0;
339
- }
340
- return hash.map((word) => word.toString(16).padStart(8, "0")).join("");
341
- }
342
- function rotateRight(value, count) {
343
- return value >>> count | value << 32 - count;
344
- }
345
-
346
995
  // src/adapter/css/css-invariant.error.ts
347
996
  var CssInvariantError = class extends Error {
348
997
  constructor(message) {
@@ -426,8 +1075,8 @@ function canonicalIdentity(family, declarations, context) {
426
1075
  });
427
1076
  }
428
1077
  var CssArtifactRegistry = class {
429
- constructor(digest = sha256) {
430
- this.digest = digest;
1078
+ constructor(digest2 = sha256) {
1079
+ this.digest = digest2;
431
1080
  }
432
1081
  rulesByCanonicalIdentity = /* @__PURE__ */ new Map();
433
1082
  canonicalIdentityByDigest = /* @__PURE__ */ new Map();
@@ -649,7 +1298,8 @@ var CssRenderer = class {
649
1298
  eligibility(input) {
650
1299
  const family = this.cssFamily(input);
651
1300
  if (family === void 0) return targetUnsupported(input);
652
- if (input.breakpoint !== void 0 && !supportedBreakpoints.has(input.breakpoint)) return targetUnsupported(input);
1301
+ if (input.breakpoint !== void 0 && !supportedBreakpoints.has(input.breakpoint) && !Object.hasOwn(this.breakpointConfig.sourceBreakpoints ?? {}, input.breakpoint))
1302
+ return targetUnsupported(input);
653
1303
  return void 0;
654
1304
  }
655
1305
  render(plan, _context) {
@@ -712,18 +1362,119 @@ var CssRenderer = class {
712
1362
  }
713
1363
  };
714
1364
 
715
- // src/migrator/migration-application.error.ts
716
- var MigrationApplicationError = class extends Error {
717
- constructor(code, message, paths = [], options) {
718
- super(message, options);
719
- this.code = code;
720
- this.paths = paths;
721
- this.name = "MigrationApplicationError";
722
- this.paths = Object.freeze([...paths]);
723
- this.recoveryFailures = Object.freeze([...options?.recoveryFailures ?? []]);
1365
+ // src/config/tailwind-builtin-variants.ts
1366
+ var tailwindBuiltinVariants = Object.freeze([
1367
+ "*",
1368
+ "**",
1369
+ "@",
1370
+ "@max",
1371
+ "@min",
1372
+ "active",
1373
+ "after",
1374
+ "any-pointer-coarse",
1375
+ "any-pointer-fine",
1376
+ "any-pointer-none",
1377
+ "aria",
1378
+ "autofill",
1379
+ "backdrop",
1380
+ "before",
1381
+ "checked",
1382
+ "contrast-less",
1383
+ "contrast-more",
1384
+ "dark",
1385
+ "data",
1386
+ "default",
1387
+ "details-content",
1388
+ "disabled",
1389
+ "empty",
1390
+ "enabled",
1391
+ "even",
1392
+ "file",
1393
+ "first",
1394
+ "first-letter",
1395
+ "first-line",
1396
+ "first-of-type",
1397
+ "focus",
1398
+ "focus-visible",
1399
+ "focus-within",
1400
+ "forced-colors",
1401
+ "group",
1402
+ "has",
1403
+ "hover",
1404
+ "in",
1405
+ "in-range",
1406
+ "indeterminate",
1407
+ "inert",
1408
+ "invalid",
1409
+ "inverted-colors",
1410
+ "landscape",
1411
+ "last",
1412
+ "last-of-type",
1413
+ "ltr",
1414
+ "marker",
1415
+ "max",
1416
+ "min",
1417
+ "motion-reduce",
1418
+ "motion-safe",
1419
+ "noscript",
1420
+ "not",
1421
+ "nth",
1422
+ "nth-last",
1423
+ "nth-last-of-type",
1424
+ "nth-of-type",
1425
+ "odd",
1426
+ "only",
1427
+ "only-of-type",
1428
+ "open",
1429
+ "optional",
1430
+ "out-of-range",
1431
+ "peer",
1432
+ "placeholder",
1433
+ "placeholder-shown",
1434
+ "pointer-coarse",
1435
+ "pointer-fine",
1436
+ "pointer-none",
1437
+ "portrait",
1438
+ "print",
1439
+ "read-only",
1440
+ "required",
1441
+ "rtl",
1442
+ "selection",
1443
+ "starting",
1444
+ "supports",
1445
+ "target",
1446
+ "user-invalid",
1447
+ "user-valid",
1448
+ "valid",
1449
+ "visited"
1450
+ ]);
1451
+
1452
+ // src/config/tailwind-candidate-profile.ts
1453
+ function renderTargetCandidate(candidate, profile) {
1454
+ const match = /^\[@media_screen_and_\(min-width:_(\d+(?:\.\d+)?)px\)\]:(.+)$/.exec(candidate);
1455
+ if (match) {
1456
+ const exact = Object.entries(profile.breakpoints).find(
1457
+ ([name, setting]) => setting.confidence !== "unknown" && setting.value === `${match[1]}px` && isNamedBreakpoint(name)
1458
+ );
1459
+ if (exact) candidate = `[@media_screen]:${exact[0]}:${match[2]}`;
724
1460
  }
725
- recoveryFailures;
726
- };
1461
+ return profile.prefix.value ? `${profile.prefix.value}:${candidate}` : candidate;
1462
+ }
1463
+ function normalizeTargetCandidate(candidate, profile) {
1464
+ const prefix = profile.prefix.value;
1465
+ if (prefix && candidate.startsWith(`${prefix}:`)) candidate = candidate.slice(prefix.length + 1);
1466
+ const match = /^\[@media_screen\]:([^:]+):(.+)$/.exec(candidate);
1467
+ if (match) {
1468
+ const setting = profile.breakpoints[match[1]];
1469
+ const width = setting?.value;
1470
+ if (setting?.confidence !== "unknown" && isNamedBreakpoint(match[1]) && width?.endsWith("px"))
1471
+ return `[@media_screen_and_(min-width:_${width})]:${match[2]}`;
1472
+ }
1473
+ return candidate;
1474
+ }
1475
+ function isNamedBreakpoint(name) {
1476
+ return !tailwindBuiltinVariants.some((root) => name === root || name.startsWith(`${root}-`));
1477
+ }
727
1478
 
728
1479
  // src/edit/html-attribute-value.ts
729
1480
  var htmlSourceWhitespace = /[\t\n\f\r ]/u;
@@ -897,7 +1648,10 @@ var BreakpointCatalog = class {
897
1648
  this.configuredDefinitions = new Map([
898
1649
  ...definitionsByAlias,
899
1650
  ...config.orientationBreakpoints ? orientations.map((definition) => [definition.alias, definition]) : [],
900
- ...config.printWithBreakpoints === void 0 ? [] : [[frozenPrintDefinition.alias, frozenPrintDefinition]]
1651
+ ...config.printWithBreakpoints === void 0 ? [] : [[frozenPrintDefinition.alias, frozenPrintDefinition]],
1652
+ ...Object.entries(config.sourceBreakpoints ?? {}).map(
1653
+ ([alias, value]) => [alias, sourceBreakpointDefinition(alias, value)]
1654
+ )
901
1655
  ]);
902
1656
  }
903
1657
  classify(alias) {
@@ -2718,18 +3472,25 @@ var TailwindCandidateClassifier = class {
2718
3472
 
2719
3473
  // src/evidence/tailwind-source-property.evidence.ts
2720
3474
  var TailwindSourcePropertyEvidence = class {
3475
+ constructor(profile) {
3476
+ this.profile = profile;
3477
+ }
2721
3478
  classifier = new TailwindCandidateClassifier();
2722
3479
  styleEncoder = new TailwindArbitraryPropertyEncoder();
2723
3480
  classifyClassToken(token) {
2724
- const classification = this.classifier.classify(token);
3481
+ const prefix = this.profile?.prefix.value;
3482
+ if (prefix && !token.startsWith(`${prefix}:`))
3483
+ return { status: "unverified", reason: "Class is not a utility in the declared prefixed target." };
3484
+ const normalized = prefix ? token.slice(prefix.length + 1) : token;
3485
+ const classification = this.classifier.classify(normalized);
2725
3486
  if (classification.status === "unverified") return classification;
2726
- const display = describeTailwindDisplay(token);
3487
+ const display = describeTailwindDisplay(normalized);
2727
3488
  return {
2728
3489
  status: "verified",
2729
3490
  evidence: {
2730
3491
  source: token,
2731
3492
  properties: classification.descriptor.cssProperties,
2732
- important: classification.descriptor.important,
3493
+ important: classification.descriptor.important || this.profile?.important.value === "important",
2733
3494
  activation: classification.descriptor.activation,
2734
3495
  ...display === void 0 ? {} : { display: display.utility }
2735
3496
  }
@@ -3313,15 +4074,29 @@ function compatibleVisibilityClasses(plan, existingClassNames) {
3313
4074
  var TailwindRenderer = class {
3314
4075
  target = "tailwind";
3315
4076
  breakpointConfig;
3316
- sourcePropertyEvidence = new TailwindSourcePropertyEvidence();
4077
+ sourcePropertyEvidence;
4078
+ targetProfile;
3317
4079
  responsiveEmitter = new ResponsiveVariantEmitter();
3318
4080
  visibilityEmitter = new VisibilityEmitter();
3319
4081
  extendedEmitter = new ExtendedResponsiveEmitter();
3320
4082
  gridRenderer = new TailwindGridRenderer();
3321
4083
  constructor(config = { orientationBreakpoints: false }) {
4084
+ this.targetProfile = config.targetProfile ?? resolveTailwindTargetProfile();
3322
4085
  this.breakpointConfig = Object.freeze({ ...config });
4086
+ this.sourcePropertyEvidence = new TailwindSourcePropertyEvidence(this.targetProfile);
3323
4087
  }
3324
4088
  eligibility(input) {
4089
+ const unresolved2 = this.targetProfile.diagnostics.filter(
4090
+ (item) => item.code === "tailwind-prefix-conflict" || item.code === "tailwind-target-unknown" || ["tailwind-config-external", "tailwind-plugin-external", "tailwind-import-unresolved"].includes(item.code) && (this.targetProfile.coreUtilities.value === "unknown" || this.targetProfile.prefix.confidence !== "explicit" || this.targetProfile.important.confidence !== "explicit")
4091
+ );
4092
+ if (unresolved2.length)
4093
+ return {
4094
+ status: "review",
4095
+ input,
4096
+ code: "context-unverified",
4097
+ reason: `Target configuration is unresolved: ${unresolved2.map((item) => item.message).join(" ")}`,
4098
+ suggestion: "Resolve the target stylesheet or supply explicit migration profile settings before migrating."
4099
+ };
3325
4100
  if (input.binding !== "property") {
3326
4101
  if (!sharedDirectives.has(input.directive) && !visibilityDirectives.has(input.directive) && !extendedDirectives.has(input.directive) && !gridDirectives.has(input.directive)) {
3327
4102
  return {
@@ -3335,7 +4110,12 @@ var TailwindRenderer = class {
3335
4110
  }
3336
4111
  return void 0;
3337
4112
  }
3338
- render(plan, _context) {
4113
+ render(plan, context) {
4114
+ const result2 = this.renderUnprefixed(plan, context);
4115
+ if (result2.status !== "converted") return result2;
4116
+ return { ...result2, classNames: result2.classNames.map((token) => renderTargetCandidate(token, this.targetProfile)) };
4117
+ }
4118
+ renderUnprefixed(plan, _context) {
3339
4119
  const inputFamily = directiveFamily(plan.input.directive);
3340
4120
  if (inputFamily !== plan.family) {
3341
4121
  throw new MigrationApplicationError(
@@ -3413,7 +4193,15 @@ var TailwindRenderer = class {
3413
4193
  };
3414
4194
  }
3415
4195
  resolveConflicts(plans, context) {
3416
- return this.resolveClassConflicts(plans, context.existingClassNames);
4196
+ const normalize8 = (token) => normalizeTargetCandidate(token, this.targetProfile);
4197
+ const normalized = plans.map(
4198
+ (plan) => plan.status === "converted" ? { ...plan, classNames: plan.classNames.map((token) => normalize8(token)) } : plan
4199
+ );
4200
+ const resolved2 = this.resolveClassConflicts(
4201
+ normalized,
4202
+ context.existingClassNames.map((token) => normalize8(token))
4203
+ );
4204
+ return resolved2.map((plan, index) => plan.status === "converted" ? plans[index] : plan);
3417
4205
  }
3418
4206
  record(_plans) {
3419
4207
  }
@@ -3447,6 +4235,10 @@ var TailwindRenderer = class {
3447
4235
  } : plan
3448
4236
  );
3449
4237
  }
4238
+ unprefix(token) {
4239
+ const prefix = this.targetProfile.prefix.value;
4240
+ return prefix && token.startsWith(`${prefix}:`) ? token.slice(prefix.length + 1) : token;
4241
+ }
3450
4242
  decorate(classNames, plan) {
3451
4243
  return plan.activations.flatMap(
3452
4244
  (planActivation) => planActivation.kind === "base" ? classNames : classNames.flatMap((className) => this.responsiveEmitter.emit(planActivation.definition, className))
@@ -3489,7 +4281,7 @@ var TailwindRenderer = class {
3489
4281
  return this.extendedEmitter.emitClass({
3490
4282
  input: plan.input,
3491
4283
  activation: itemActivation,
3492
- value: { tokens: state.tokens.map((token) => token.source) }
4284
+ value: { tokens: state.tokens.map((token) => this.unprefix(token.source)) }
3493
4285
  });
3494
4286
  })
3495
4287
  )
@@ -3615,7 +4407,7 @@ var AdapterFactory = class {
3615
4407
  };
3616
4408
 
3617
4409
  // src/pipeline/analyze/analyze-project.stage.ts
3618
- import { readFile } from "fs/promises";
4410
+ import { readFile as readFile2 } from "fs/promises";
3619
4411
 
3620
4412
  // src/analyzer/flex-layout-attribute.analyzer.ts
3621
4413
  var responsiveOnlyDirectives = /* @__PURE__ */ new Set(["class", "ngClass", "style", "ngStyle"]);
@@ -3801,10 +4593,10 @@ var AngularTemplateParser = class {
3801
4593
  };
3802
4594
 
3803
4595
  // src/pipeline/analyzed-project.ts
3804
- import * as path2 from "path";
4596
+ import * as path4 from "path";
3805
4597
 
3806
4598
  // src/pipeline/project-manifest.ts
3807
- import * as path from "path";
4599
+ import * as path3 from "path";
3808
4600
  function migrationInvocation(invocation) {
3809
4601
  return freezeMigrationInvocation({
3810
4602
  inputPath: invocation.inputPath,
@@ -3840,7 +4632,7 @@ function freezeMigrationInvocation(invocation) {
3840
4632
  });
3841
4633
  }
3842
4634
  function normalizedAbsolutePath(value) {
3843
- return path.normalize(path.resolve(value));
4635
+ return path3.normalize(path3.resolve(value));
3844
4636
  }
3845
4637
 
3846
4638
  // src/pipeline/analyzed-project.ts
@@ -3935,7 +4727,7 @@ function freezeLocatedInput(input) {
3935
4727
  });
3936
4728
  }
3937
4729
  function normalizedAbsolutePath2(value) {
3938
- return path2.normalize(path2.resolve(value));
4730
+ return path4.normalize(path4.resolve(value));
3939
4731
  }
3940
4732
  function sequenceInvariant(paths = []) {
3941
4733
  return internalInvariant(
@@ -3949,7 +4741,7 @@ function internalInvariant(message, paths = []) {
3949
4741
 
3950
4742
  // src/pipeline/analyze/analyze-project.stage.ts
3951
4743
  var nodeSourceReader = Object.freeze({
3952
- read: (path18) => readFile(path18, "utf8")
4744
+ read: (path19) => readFile2(path19, "utf8")
3953
4745
  });
3954
4746
  var AnalyzeProjectStage = class {
3955
4747
  constructor(sourceReader = nodeSourceReader, parser = new AngularTemplateParser(), analyzer = new TemplateAnalyzer()) {
@@ -3996,13 +4788,13 @@ function runtimeArtifact(context, artifact) {
3996
4788
  [artifact.path]
3997
4789
  );
3998
4790
  }
3999
- function identity(stat4) {
4791
+ function identity2(stat4) {
4000
4792
  return { dev: String(stat4.dev), ino: String(stat4.ino) };
4001
4793
  }
4002
4794
  function fileMode(stat4) {
4003
4795
  return Number(stat4.mode) & 4095;
4004
4796
  }
4005
- function sameIdentity(left, right) {
4797
+ function sameIdentity2(left, right) {
4006
4798
  return left.dev === right.dev && left.ino === right.ino;
4007
4799
  }
4008
4800
  function sameArtifactState(left, right) {
@@ -4014,14 +4806,14 @@ function required(value) {
4014
4806
  if (value === void 0) throw new Error("Missing transaction state.");
4015
4807
  return value;
4016
4808
  }
4017
- function isEnoent(error) {
4809
+ function isEnoent2(error) {
4018
4810
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
4019
4811
  }
4020
4812
  function isDirectoryNotEmpty(error) {
4021
4813
  return typeof error === "object" && error !== null && "code" in error && (error.code === "ENOTEMPTY" || error.code === "EEXIST");
4022
4814
  }
4023
- function pathDepth(path18) {
4024
- return path18.split(/[\\/]/u).filter(Boolean).length;
4815
+ function pathDepth(path19) {
4816
+ return path19.split(/[\\/]/u).filter(Boolean).length;
4025
4817
  }
4026
4818
  function recoveryOutcome(paths, failures) {
4027
4819
  return { paths: sortedUnique(paths), failures: Object.freeze([...failures]) };
@@ -4081,13 +4873,13 @@ var FileSystemCleanupUnit = class {
4081
4873
  try {
4082
4874
  await this.port.assertNamespace(item);
4083
4875
  const before = await this.port.lstat(owned.path);
4084
- if (!sameIdentity(identity(before), owned.identity) || before.isSymbolicLink() || !before.isFile()) {
4876
+ if (!sameIdentity2(identity2(before), owned.identity) || before.isSymbolicLink() || !before.isFile()) {
4085
4877
  paths.add(owned.publicPath);
4086
4878
  failures.push(new Error("Invocation-owned file identity could not be confirmed."));
4087
4879
  return;
4088
4880
  }
4089
4881
  } catch (error) {
4090
- if (isEnoent(error)) {
4882
+ if (isEnoent2(error)) {
4091
4883
  this.port.journal.markOwnedFileAbsent(item, owned.path);
4092
4884
  return;
4093
4885
  }
@@ -4098,13 +4890,13 @@ var FileSystemCleanupUnit = class {
4098
4890
  try {
4099
4891
  await this.port.unlink(owned.path);
4100
4892
  } catch (error) {
4101
- if (!isEnoent(error)) failures.push(error);
4893
+ if (!isEnoent2(error)) failures.push(error);
4102
4894
  }
4103
4895
  try {
4104
4896
  await this.port.lstat(owned.path);
4105
4897
  paths.add(owned.publicPath);
4106
4898
  } catch (error) {
4107
- if (isEnoent(error)) this.port.journal.markOwnedFileAbsent(item, owned.path);
4899
+ if (isEnoent2(error)) this.port.journal.markOwnedFileAbsent(item, owned.path);
4108
4900
  else {
4109
4901
  paths.add(owned.publicPath);
4110
4902
  failures.push(error);
@@ -4120,13 +4912,13 @@ var FileSystemCleanupUnit = class {
4120
4912
  }
4121
4913
  try {
4122
4914
  const namespaceStat = await this.port.lstat(namespace.path);
4123
- if (!sameIdentity(identity(namespaceStat), namespace.identity) || namespaceStat.isSymbolicLink() || !namespaceStat.isDirectory()) {
4915
+ if (!sameIdentity2(identity2(namespaceStat), namespace.identity) || namespaceStat.isSymbolicLink() || !namespaceStat.isDirectory()) {
4124
4916
  paths.add(namespace.publicPath);
4125
4917
  failures.push(new Error("Invocation namespace identity could not be confirmed."));
4126
4918
  return;
4127
4919
  }
4128
4920
  } catch (error) {
4129
- if (isEnoent(error)) {
4921
+ if (isEnoent2(error)) {
4130
4922
  this.port.journal.markNamespaceAbsent(item);
4131
4923
  return;
4132
4924
  }
@@ -4137,13 +4929,13 @@ var FileSystemCleanupUnit = class {
4137
4929
  try {
4138
4930
  await this.port.rmdir(namespace.path);
4139
4931
  } catch (error) {
4140
- if (!isDirectoryNotEmpty(error) && !isEnoent(error)) failures.push(error);
4932
+ if (!isDirectoryNotEmpty(error) && !isEnoent2(error)) failures.push(error);
4141
4933
  }
4142
4934
  try {
4143
4935
  await this.port.lstat(namespace.path);
4144
4936
  paths.add(namespace.publicPath);
4145
4937
  } catch (error) {
4146
- if (isEnoent(error)) this.port.journal.markNamespaceAbsent(item);
4938
+ if (isEnoent2(error)) this.port.journal.markNamespaceAbsent(item);
4147
4939
  else {
4148
4940
  paths.add(namespace.publicPath);
4149
4941
  failures.push(error);
@@ -4169,7 +4961,7 @@ var FileSystemCleanupUnit = class {
4169
4961
  try {
4170
4962
  before = await this.port.lstat(directory.path);
4171
4963
  } catch (error) {
4172
- if (isEnoent(error)) {
4964
+ if (isEnoent2(error)) {
4173
4965
  this.port.journal.markCreatedDirectoryAbsent(directory.path);
4174
4966
  continue;
4175
4967
  }
@@ -4177,7 +4969,7 @@ var FileSystemCleanupUnit = class {
4177
4969
  failures.push(error);
4178
4970
  continue;
4179
4971
  }
4180
- if (!sameIdentity(identity(before), directory.identity) || before.isSymbolicLink() || !before.isDirectory()) {
4972
+ if (!sameIdentity2(identity2(before), directory.identity) || before.isSymbolicLink() || !before.isDirectory()) {
4181
4973
  for (const publicPath of directory.publicPaths) paths.add(publicPath);
4182
4974
  continue;
4183
4975
  }
@@ -4192,17 +4984,17 @@ var FileSystemCleanupUnit = class {
4192
4984
  after = await this.port.lstatOrAbsent(directory.path);
4193
4985
  } catch (error) {
4194
4986
  for (const publicPath of directory.publicPaths) paths.add(publicPath);
4195
- if (removalFailure !== void 0 && !isEnoent(removalFailure)) failures.push(removalFailure);
4987
+ if (removalFailure !== void 0 && !isEnoent2(removalFailure)) failures.push(removalFailure);
4196
4988
  failures.push(error);
4197
4989
  continue;
4198
4990
  }
4199
4991
  if (after === "absent") {
4200
4992
  this.port.journal.markCreatedDirectoryAbsent(directory.path);
4201
- if (removalFailure !== void 0 && !isEnoent(removalFailure)) failures.push(removalFailure);
4993
+ if (removalFailure !== void 0 && !isEnoent2(removalFailure)) failures.push(removalFailure);
4202
4994
  continue;
4203
4995
  }
4204
4996
  for (const publicPath of directory.publicPaths) paths.add(publicPath);
4205
- if (removalFailure !== void 0 && !isEnoent(removalFailure)) failures.push(removalFailure);
4997
+ if (removalFailure !== void 0 && !isEnoent2(removalFailure)) failures.push(removalFailure);
4206
4998
  }
4207
4999
  }
4208
5000
  async collectUnconfirmedPaths(paths, failures) {
@@ -4210,7 +5002,7 @@ var FileSystemCleanupUnit = class {
4210
5002
  try {
4211
5003
  await this.port.lstat(candidate);
4212
5004
  } catch (error) {
4213
- if (isEnoent(error)) continue;
5005
+ if (isEnoent2(error)) continue;
4214
5006
  failures.push(error);
4215
5007
  }
4216
5008
  for (const publicPath of publicPaths) paths.add(publicPath);
@@ -4228,7 +5020,7 @@ var FileSystemCleanupUnit = class {
4228
5020
  if (!sameArtifactState(observed, item.artifact.original)) return false;
4229
5021
  if (observed.status === "absent") return true;
4230
5022
  return [item.originalIdentity, item.restoredIdentity].some(
4231
- (expected) => expected !== void 0 && sameIdentity(observed.identity, expected)
5023
+ (expected) => expected !== void 0 && sameIdentity2(observed.identity, expected)
4232
5024
  );
4233
5025
  }
4234
5026
  };
@@ -4281,7 +5073,7 @@ var FileSystemCommitUnit = class {
4281
5073
  }
4282
5074
  async captureOriginal(item, signal) {
4283
5075
  const firstCapture = await this.port.observePublic(item);
4284
- if (!sameArtifactState(firstCapture, item.artifact.original) || firstCapture.status !== "present" || !item.originalIdentity || !sameIdentity(firstCapture.identity, item.originalIdentity)) {
5076
+ if (!sameArtifactState(firstCapture, item.artifact.original) || firstCapture.status !== "present" || !item.originalIdentity || !sameIdentity2(firstCapture.identity, item.originalIdentity)) {
4285
5077
  throw this.port.concurrentModification(item.artifact.path);
4286
5078
  }
4287
5079
  const backup = await this.port.createBackupFile(item, firstCapture.contents, signal, firstCapture.mode);
@@ -4289,7 +5081,7 @@ var FileSystemCommitUnit = class {
4289
5081
  throw this.port.ownershipFailure(item.artifact.path);
4290
5082
  }
4291
5083
  const secondCapture = await this.port.observePublic(item);
4292
- if (secondCapture.status !== "present" || !sameIdentity(secondCapture.identity, firstCapture.identity) || secondCapture.contents !== firstCapture.contents) {
5084
+ if (secondCapture.status !== "present" || !sameIdentity2(secondCapture.identity, firstCapture.identity) || secondCapture.contents !== firstCapture.contents) {
4293
5085
  throw this.port.concurrentModification(item.artifact.path);
4294
5086
  }
4295
5087
  await this.quarantineOriginal(item, firstCapture);
@@ -4298,7 +5090,7 @@ var FileSystemCommitUnit = class {
4298
5090
  await this.port.assertParentChain(item);
4299
5091
  await this.port.assertNamespace(item);
4300
5092
  const immediatelyBefore = await this.port.observePublic(item);
4301
- if (immediatelyBefore.status !== "present" || !sameIdentity(immediatelyBefore.identity, captured.identity) || immediatelyBefore.contents !== captured.contents) {
5093
+ if (immediatelyBefore.status !== "present" || !sameIdentity2(immediatelyBefore.identity, captured.identity) || immediatelyBefore.contents !== captured.contents) {
4302
5094
  throw this.port.concurrentModification(item.artifact.path);
4303
5095
  }
4304
5096
  let quarantine = this.port.journal.addQuarantine(
@@ -4314,9 +5106,9 @@ var FileSystemCommitUnit = class {
4314
5106
  }
4315
5107
  const quarantinedStat = await this.port.lstatOrAbsent(quarantine.path);
4316
5108
  if (quarantinedStat !== "absent") {
4317
- quarantine = this.port.journal.confirmOwnedFile(item, quarantine.path, identity(quarantinedStat));
5109
+ quarantine = this.port.journal.confirmOwnedFile(item, quarantine.path, identity2(quarantinedStat));
4318
5110
  const quarantinedContents = await this.port.readOwnedFile(item, quarantine.path);
4319
- if (!sameIdentity(required(quarantine.identity), captured.identity) || quarantinedContents !== captured.contents) {
5111
+ if (!sameIdentity2(required(quarantine.identity), captured.identity) || quarantinedContents !== captured.contents) {
4320
5112
  this.port.journal.setOwnedFilePreserved(item, quarantine.path, true);
4321
5113
  await this.restorePreservedQuarantine(item, quarantine);
4322
5114
  throw this.port.concurrentModification(item.artifact.path, renameFailure);
@@ -4328,7 +5120,7 @@ var FileSystemCommitUnit = class {
4328
5120
  return;
4329
5121
  }
4330
5122
  const destination = await this.port.lstatOrAbsent(item.artifact.path);
4331
- if (renameFailure !== void 0 && destination !== "absent" && sameIdentity(identity(destination), captured.identity)) {
5123
+ if (renameFailure !== void 0 && destination !== "absent" && sameIdentity2(identity2(destination), captured.identity)) {
4332
5124
  throw renameFailure;
4333
5125
  }
4334
5126
  throw this.port.concurrentModification(item.artifact.path, renameFailure);
@@ -4345,7 +5137,7 @@ var FileSystemCommitUnit = class {
4345
5137
  linkFailure = error;
4346
5138
  }
4347
5139
  const destination = await this.port.lstatOrAbsent(item.artifact.path);
4348
- if (destination !== "absent" && stage.identity && sameIdentity(identity(destination), stage.identity)) {
5140
+ if (destination !== "absent" && stage.identity && sameIdentity2(identity2(destination), stage.identity)) {
4349
5141
  this.port.journal.recordInstalledIdentity(item, stage.identity);
4350
5142
  await this.port.assertParentChain(item);
4351
5143
  if (linkFailure !== void 0) throw linkFailure;
@@ -4364,7 +5156,7 @@ var FileSystemCommitUnit = class {
4364
5156
  this.port.journal.recordRecoveryFailure(error);
4365
5157
  }
4366
5158
  const destination = await this.port.lstatOrAbsent(item.artifact.path);
4367
- if (destination !== "absent" && sameIdentity(identity(destination), quarantineIdentity)) {
5159
+ if (destination !== "absent" && sameIdentity2(identity2(destination), quarantineIdentity)) {
4368
5160
  this.port.journal.setOwnedFilePreserved(item, quarantine.path, false);
4369
5161
  }
4370
5162
  }
@@ -4426,8 +5218,8 @@ var FileSystemRollbackUnit = class {
4426
5218
  }
4427
5219
  if (current !== "unknown" && this.isConfirmedOriginal(item, current)) return;
4428
5220
  if (current !== "unknown" && current.status === "present") {
4429
- if (!item.installedIdentity || !sameIdentity(current.identity, item.installedIdentity) || item.artifact.proposed.status !== "present" || current.contents !== item.artifact.proposed.contents) {
4430
- if (item.stage && item.installedIdentity && sameIdentity(current.identity, item.installedIdentity)) {
5221
+ if (!item.installedIdentity || !sameIdentity2(current.identity, item.installedIdentity) || item.artifact.proposed.status !== "present" || current.contents !== item.artifact.proposed.contents) {
5222
+ if (item.stage && item.installedIdentity && sameIdentity2(current.identity, item.installedIdentity)) {
4431
5223
  this.port.journal.setOwnedFilePreserved(item, item.stage.path, true);
4432
5224
  }
4433
5225
  return;
@@ -4449,7 +5241,7 @@ var FileSystemRollbackUnit = class {
4449
5241
  failures.push(error);
4450
5242
  }
4451
5243
  const restored = await this.port.lstatOrAbsent(item.artifact.path);
4452
- if (restored !== "absent" && !restored.isSymbolicLink() && restored.isFile() && backup.identity && sameIdentity(identity(restored), backup.identity)) {
5244
+ if (restored !== "absent" && !restored.isSymbolicLink() && restored.isFile() && backup.identity && sameIdentity2(identity2(restored), backup.identity)) {
4453
5245
  this.port.journal.recordRestoredIdentity(item, backup.identity);
4454
5246
  }
4455
5247
  } catch (error) {
@@ -4465,7 +5257,7 @@ var FileSystemRollbackUnit = class {
4465
5257
  await this.port.assertParentChain(item);
4466
5258
  await this.port.assertNamespace(item);
4467
5259
  const before = await this.port.observePublic(item);
4468
- if (before.status !== "present" || !sameIdentity(before.identity, current.identity) || before.contents !== current.contents) {
5260
+ if (before.status !== "present" || !sameIdentity2(before.identity, current.identity) || before.contents !== current.contents) {
4469
5261
  return false;
4470
5262
  }
4471
5263
  await this.port.assertExpectedAbsent(quarantine.path, item.artifact.path);
@@ -4476,9 +5268,9 @@ var FileSystemRollbackUnit = class {
4476
5268
  }
4477
5269
  const quarantined = await this.port.lstatOrAbsent(quarantine.path);
4478
5270
  if (quarantined === "absent") return false;
4479
- quarantine = this.port.journal.confirmOwnedFile(item, quarantine.path, identity(quarantined));
5271
+ quarantine = this.port.journal.confirmOwnedFile(item, quarantine.path, identity2(quarantined));
4480
5272
  this.port.journal.setOwnedFilePreserved(item, quarantine.path, true);
4481
- if (!sameIdentity(required(quarantine.identity), current.identity)) {
5273
+ if (!sameIdentity2(required(quarantine.identity), current.identity)) {
4482
5274
  await this.restorePreservedQuarantine(item, quarantine, failures);
4483
5275
  return false;
4484
5276
  }
@@ -4502,7 +5294,7 @@ var FileSystemRollbackUnit = class {
4502
5294
  failures.push(error);
4503
5295
  }
4504
5296
  const destination = await this.port.lstatOrAbsent(item.artifact.path);
4505
- if (destination !== "absent" && sameIdentity(identity(destination), quarantineIdentity)) {
5297
+ if (destination !== "absent" && sameIdentity2(identity2(destination), quarantineIdentity)) {
4506
5298
  this.port.journal.setOwnedFilePreserved(item, quarantine.path, false);
4507
5299
  }
4508
5300
  }
@@ -4518,7 +5310,7 @@ var FileSystemRollbackUnit = class {
4518
5310
  if (!sameArtifactState(observed, item.artifact.original)) return false;
4519
5311
  if (observed.status === "absent") return true;
4520
5312
  return [item.originalIdentity, item.restoredIdentity].some(
4521
- (expected) => expected !== void 0 && sameIdentity(observed.identity, expected)
5313
+ (expected) => expected !== void 0 && sameIdentity2(observed.identity, expected)
4522
5314
  );
4523
5315
  }
4524
5316
  };
@@ -4593,7 +5385,7 @@ var FileSystemStagingUnit = class {
4593
5385
  if (createdStat.isSymbolicLink() || !createdStat.isDirectory()) {
4594
5386
  throw this.port.concurrentModification(item.artifact.path);
4595
5387
  }
4596
- this.port.journal.confirmCreatedDirectory(expectation.path, identity(createdStat));
5388
+ this.port.journal.confirmCreatedDirectory(expectation.path, identity2(createdStat));
4597
5389
  if (parentExpectation) await this.port.assertExpectedDirectory(parentExpectation, item);
4598
5390
  }
4599
5391
  await this.port.assertParentChain(item);
@@ -4613,7 +5405,7 @@ var FileSystemStagingUnit = class {
4613
5405
  if (namespaceStat.isSymbolicLink() || !namespaceStat.isDirectory()) {
4614
5406
  throw this.port.concurrentModification(item.artifact.path);
4615
5407
  }
4616
- this.port.journal.confirmNamespace(item, identity(namespaceStat));
5408
+ this.port.journal.confirmNamespace(item, identity2(namespaceStat));
4617
5409
  await this.port.assertParentChain(item);
4618
5410
  }
4619
5411
  };
@@ -4646,17 +5438,17 @@ var TransactionSignalRegistrar = class {
4646
5438
 
4647
5439
  // src/transaction/transaction-unit.session.ts
4648
5440
  import { constants } from "fs";
4649
- import { access, link, lstat, mkdir, open, rename, rmdir, stat, unlink } from "fs/promises";
5441
+ import { access, link, lstat as lstat2, mkdir, open, rename, rmdir, stat as stat2, unlink } from "fs/promises";
4650
5442
  import { dirname as dirname2, join as join4, resolve as resolve3 } from "path";
4651
5443
  var nodeTransactionOperations = {
4652
5444
  access,
4653
5445
  link,
4654
- lstat,
5446
+ lstat: lstat2,
4655
5447
  mkdir,
4656
5448
  open: (target, flags) => open(target, flags),
4657
5449
  rename,
4658
5450
  rmdir,
4659
- stat,
5451
+ stat: stat2,
4660
5452
  unlink
4661
5453
  };
4662
5454
  var TransactionUnitSession = class {
@@ -4677,30 +5469,30 @@ var TransactionUnitSession = class {
4677
5469
  const journal = Object.freeze({
4678
5470
  prepare: (artifacts) => this.prepare(artifacts),
4679
5471
  artifacts: () => Object.freeze(this.context.items.map(stagingArtifactView)),
4680
- createdDirectory: (path18) => createdDirectoryView(this.context.createdDirectories.get(path18)),
4681
- addCreatedDirectoryPublicPath: (path18, publicPath) => required(this.context.createdDirectories.get(path18)).publicPaths.add(publicPath),
4682
- recordUnconfirmedEntry: (path18, publicPath) => this.recordUnconfirmedEntry(path18, publicPath),
4683
- recordCreatedDirectory: (path18, publicPath) => this.recordCreatedDirectory(path18, publicPath),
4684
- confirmCreatedDirectory: (path18, expected) => {
4685
- required(this.context.createdDirectories.get(path18)).identity = expected;
5472
+ createdDirectory: (path19) => createdDirectoryView(this.context.createdDirectories.get(path19)),
5473
+ addCreatedDirectoryPublicPath: (path19, publicPath) => required(this.context.createdDirectories.get(path19)).publicPaths.add(publicPath),
5474
+ recordUnconfirmedEntry: (path19, publicPath) => this.recordUnconfirmedEntry(path19, publicPath),
5475
+ recordCreatedDirectory: (path19, publicPath) => this.recordCreatedDirectory(path19, publicPath),
5476
+ confirmCreatedDirectory: (path19, expected) => {
5477
+ required(this.context.createdDirectories.get(path19)).identity = expected;
4686
5478
  },
4687
- recordNamespace: (item, path18) => this.recordNamespace(item.artifact, path18),
5479
+ recordNamespace: (item, path19) => this.recordNamespace(item.artifact, path19),
4688
5480
  confirmNamespace: (item, expected) => {
4689
5481
  required(this.runtimeArtifact(item.artifact).namespace).identity = expected;
4690
5482
  }
4691
5483
  });
4692
5484
  return Object.freeze({
4693
5485
  journal,
4694
- assertDirectoryExpectation: (path18, expected, publicPath) => this.assertDirectoryExpectation(path18, expected, publicPath),
4695
- assertDirectoryIdentity: (path18, expected, publicPath) => this.assertDirectoryIdentity(path18, expected, publicPath),
4696
- assertExpectedAbsent: (path18, publicPath) => this.assertExpectedAbsent(path18, publicPath),
5486
+ assertDirectoryExpectation: (path19, expected, publicPath) => this.assertDirectoryExpectation(path19, expected, publicPath),
5487
+ assertDirectoryIdentity: (path19, expected, publicPath) => this.assertDirectoryIdentity(path19, expected, publicPath),
5488
+ assertExpectedAbsent: (path19, publicPath) => this.assertExpectedAbsent(path19, publicPath),
4697
5489
  assertExpectedDirectory: (expectation, item) => this.assertExpectedDirectory(expectation, this.runtimeArtifact(item.artifact), this.context),
4698
5490
  assertNotInterrupted: (signal) => this.assertNotInterrupted(signal),
4699
5491
  assertParentChain: (item) => this.assertParentChain(this.runtimeArtifact(item.artifact), this.context),
4700
5492
  concurrentModification: (publicPath, cause) => this.concurrentModification(publicPath, cause),
4701
5493
  createStageFile: (item, contents, signal, mode) => this.createStageFile(this.runtimeArtifact(item.artifact), contents, signal, mode),
4702
- lstat: (path18) => this.operations.lstat(path18),
4703
- mkdir: (path18, options) => this.operations.mkdir(path18, options),
5494
+ lstat: (path19) => this.operations.lstat(path19),
5495
+ mkdir: (path19, options) => this.operations.mkdir(path19, options),
4704
5496
  readOwnedFile: (item, ownedPath) => {
4705
5497
  const runtime = this.runtimeArtifact(item.artifact);
4706
5498
  return this.readOwnedFile(runtime, this.ownedFile(runtime, ownedPath));
@@ -4712,7 +5504,7 @@ var TransactionUnitSession = class {
4712
5504
  const journal = Object.freeze({
4713
5505
  artifact: (artifact) => commitArtifactView(this.runtimeArtifact(artifact)),
4714
5506
  artifacts: () => Object.freeze(this.context.items.map(commitArtifactView)),
4715
- addQuarantine: (item, path18) => ownedFileView(this.addQuarantine(this.runtimeArtifact(item.artifact), path18)),
5507
+ addQuarantine: (item, path19) => ownedFileView(this.addQuarantine(this.runtimeArtifact(item.artifact), path19)),
4716
5508
  confirmOwnedFile: (item, ownedPath, expected) => ownedFileView(this.confirmOwnedFile(this.runtimeArtifact(item.artifact), ownedPath, expected)),
4717
5509
  setOwnedFilePreserved: (item, ownedPath, preserve) => {
4718
5510
  this.ownedFile(this.runtimeArtifact(item.artifact), ownedPath).preserve = preserve;
@@ -4724,7 +5516,7 @@ var TransactionUnitSession = class {
4724
5516
  });
4725
5517
  return Object.freeze({
4726
5518
  journal,
4727
- assertExpectedAbsent: (path18, publicPath) => this.assertExpectedAbsent(path18, publicPath),
5519
+ assertExpectedAbsent: (path19, publicPath) => this.assertExpectedAbsent(path19, publicPath),
4728
5520
  assertNamespace: (item) => this.assertNamespace(this.runtimeArtifact(item.artifact)),
4729
5521
  assertNotInterrupted: (signal) => this.assertNotInterrupted(signal),
4730
5522
  assertOwnedIdentity: (item, ownedPath) => {
@@ -4735,7 +5527,7 @@ var TransactionUnitSession = class {
4735
5527
  concurrentModification: (publicPath, cause) => this.concurrentModification(publicPath, cause),
4736
5528
  createBackupFile: (item, contents, signal, mode) => this.createBackupFile(this.runtimeArtifact(item.artifact), contents, signal, mode),
4737
5529
  link: (existingPath, newPath) => this.operations.link(existingPath, newPath),
4738
- lstatOrAbsent: (path18) => this.lstatOrAbsent(path18),
5530
+ lstatOrAbsent: (path19) => this.lstatOrAbsent(path19),
4739
5531
  observePublic: (item) => this.observePublic(this.runtimeArtifact(item.artifact), this.context),
4740
5532
  ownershipFailure: (publicPath) => this.ownershipFailure(publicPath),
4741
5533
  readOwnedFile: (item, ownedPath) => {
@@ -4750,7 +5542,7 @@ var TransactionUnitSession = class {
4750
5542
  artifact: (artifact) => rollbackArtifactView(this.runtimeArtifact(artifact)),
4751
5543
  artifacts: () => Object.freeze(this.context.items.map(rollbackArtifactView)),
4752
5544
  recoveryFailures: () => Object.freeze([...this.context.recoveryFailures]),
4753
- addQuarantine: (item, path18) => ownedFileView(this.addQuarantine(this.runtimeArtifact(item.artifact), path18)),
5545
+ addQuarantine: (item, path19) => ownedFileView(this.addQuarantine(this.runtimeArtifact(item.artifact), path19)),
4754
5546
  confirmOwnedFile: (item, ownedPath, expected) => ownedFileView(this.confirmOwnedFile(this.runtimeArtifact(item.artifact), ownedPath, expected)),
4755
5547
  setOwnedFilePreserved: (item, ownedPath, preserve) => {
4756
5548
  this.ownedFile(this.runtimeArtifact(item.artifact), ownedPath).preserve = preserve;
@@ -4764,11 +5556,11 @@ var TransactionUnitSession = class {
4764
5556
  });
4765
5557
  return Object.freeze({
4766
5558
  journal,
4767
- assertExpectedAbsent: (path18, publicPath) => this.assertExpectedAbsent(path18, publicPath),
5559
+ assertExpectedAbsent: (path19, publicPath) => this.assertExpectedAbsent(path19, publicPath),
4768
5560
  assertNamespace: (item) => this.assertNamespace(this.runtimeArtifact(item.artifact)),
4769
5561
  assertParentChain: (item) => this.assertParentChain(this.runtimeArtifact(item.artifact), this.context),
4770
5562
  link: (existingPath, newPath) => this.operations.link(existingPath, newPath),
4771
- lstatOrAbsent: (path18) => this.lstatOrAbsent(path18),
5563
+ lstatOrAbsent: (path19) => this.lstatOrAbsent(path19),
4772
5564
  observePublic: (item) => this.observePublic(this.runtimeArtifact(item.artifact), this.context),
4773
5565
  readOwnedFile: (item, ownedPath) => {
4774
5566
  const runtime = this.runtimeArtifact(item.artifact);
@@ -4790,8 +5582,8 @@ var TransactionUnitSession = class {
4790
5582
  markNamespaceAbsent: (item) => {
4791
5583
  required(this.runtimeArtifact(item.artifact).namespace).exists = false;
4792
5584
  },
4793
- markCreatedDirectoryAbsent: (path18) => {
4794
- required(this.context.createdDirectories.get(path18)).exists = false;
5585
+ markCreatedDirectoryAbsent: (path19) => {
5586
+ required(this.context.createdDirectories.get(path19)).exists = false;
4795
5587
  this.context.ownershipChanged();
4796
5588
  },
4797
5589
  finishArtifactCleanup: () => this.context.ownershipChanged()
@@ -4801,11 +5593,11 @@ var TransactionUnitSession = class {
4801
5593
  assertNamespace: (item) => this.assertNamespace(this.runtimeArtifact(item.artifact)),
4802
5594
  closeReadHandles: (item, failures) => this.closeReadHandles(this.runtimeArtifact(item.artifact), failures),
4803
5595
  closeOpenHandle: (item, failures) => this.closeOpenHandle(this.runtimeArtifact(item.artifact), failures),
4804
- lstat: (path18) => this.operations.lstat(path18),
4805
- lstatOrAbsent: (path18) => this.lstatOrAbsent(path18),
5596
+ lstat: (path19) => this.operations.lstat(path19),
5597
+ lstatOrAbsent: (path19) => this.lstatOrAbsent(path19),
4806
5598
  observePublic: (item) => this.observePublic(this.runtimeArtifact(item.artifact), this.context),
4807
- rmdir: (path18) => this.operations.rmdir(path18),
4808
- unlink: (path18) => this.operations.unlink(path18)
5599
+ rmdir: (path19) => this.operations.rmdir(path19),
5600
+ unlink: (path19) => this.operations.unlink(path19)
4809
5601
  });
4810
5602
  }
4811
5603
  async prepareForPreflight(artifacts) {
@@ -4880,16 +5672,16 @@ var TransactionUnitSession = class {
4880
5672
  result2.push({
4881
5673
  path: candidate,
4882
5674
  original: {
4883
- identity: identity(candidateStat),
5675
+ identity: identity2(candidateStat),
4884
5676
  kind: "symbolic-link",
4885
- followedIdentity: identity(followed)
5677
+ followedIdentity: identity2(followed)
4886
5678
  }
4887
5679
  });
4888
5680
  } else {
4889
- result2.push({ path: candidate, original: { identity: identity(candidateStat), kind: "directory" } });
5681
+ result2.push({ path: candidate, original: { identity: identity2(candidateStat), kind: "directory" } });
4890
5682
  }
4891
5683
  } catch (error) {
4892
- if (!isEnoent(error)) throw error;
5684
+ if (!isEnoent2(error)) throw error;
4893
5685
  missing = true;
4894
5686
  result2.push({ path: candidate, original: "absent" });
4895
5687
  }
@@ -4914,9 +5706,9 @@ var TransactionUnitSession = class {
4914
5706
  await this.writeOwnedFile(item, owned, contents, signal, mode);
4915
5707
  return ownedFileView(owned);
4916
5708
  }
4917
- registerOwnedFile(item, path18) {
5709
+ registerOwnedFile(item, path19) {
4918
5710
  const owned = {
4919
- path: path18,
5711
+ path: path19,
4920
5712
  publicPath: item.artifact.path,
4921
5713
  exists: false,
4922
5714
  preserve: false
@@ -4931,7 +5723,7 @@ var TransactionUnitSession = class {
4931
5723
  item.openHandle = handle;
4932
5724
  this.context.ownershipChanged();
4933
5725
  if (mode !== void 0) await handle.chmod(mode);
4934
- owned.identity = identity(await handle.stat());
5726
+ owned.identity = identity2(await handle.stat());
4935
5727
  this.assertNotInterrupted(signal);
4936
5728
  await handle.writeFile(contents, "utf8");
4937
5729
  this.assertNotInterrupted(signal);
@@ -4945,48 +5737,48 @@ var TransactionUnitSession = class {
4945
5737
  runtimeArtifact(artifact) {
4946
5738
  return runtimeArtifact(this.context, artifact);
4947
5739
  }
4948
- ownedFile(item, path18) {
4949
- const owned = item.ownedFiles.find((candidate) => candidate.path === path18);
5740
+ ownedFile(item, path19) {
5741
+ const owned = item.ownedFiles.find((candidate) => candidate.path === path19);
4950
5742
  if (owned !== void 0) return owned;
4951
5743
  throw new MigrationApplicationError(
4952
5744
  "internal-invariant",
4953
- `Migration transaction journal contains an unknown invocation-owned file: ${path18}`,
5745
+ `Migration transaction journal contains an unknown invocation-owned file: ${path19}`,
4954
5746
  [item.artifact.path]
4955
5747
  );
4956
5748
  }
4957
- addQuarantine(item, path18) {
4958
- const quarantine = this.registerOwnedFile(item, path18);
5749
+ addQuarantine(item, path19) {
5750
+ const quarantine = this.registerOwnedFile(item, path19);
4959
5751
  item.quarantines.push(quarantine);
4960
5752
  return quarantine;
4961
5753
  }
4962
- confirmOwnedFile(item, path18, expected) {
4963
- const owned = this.ownedFile(item, path18);
5754
+ confirmOwnedFile(item, path19, expected) {
5755
+ const owned = this.ownedFile(item, path19);
4964
5756
  owned.exists = true;
4965
5757
  owned.identity = expected;
4966
5758
  return owned;
4967
5759
  }
4968
- recordNamespace(artifact, path18) {
5760
+ recordNamespace(artifact, path19) {
4969
5761
  const item = this.runtimeArtifact(artifact);
4970
- item.namespace = { path: path18, publicPath: item.artifact.path, exists: true };
5762
+ item.namespace = { path: path19, publicPath: item.artifact.path, exists: true };
4971
5763
  this.context.ownershipChanged();
4972
5764
  }
4973
- recordCreatedDirectory(path18, publicPath) {
4974
- this.context.createdDirectories.set(path18, {
4975
- path: path18,
5765
+ recordCreatedDirectory(path19, publicPath) {
5766
+ this.context.createdDirectories.set(path19, {
5767
+ path: path19,
4976
5768
  publicPaths: /* @__PURE__ */ new Set([publicPath]),
4977
5769
  exists: true
4978
5770
  });
4979
5771
  this.context.ownershipChanged();
4980
5772
  }
4981
- recordUnconfirmedEntry(path18, publicPath) {
4982
- const publicPaths = this.context.unconfirmedEntries.get(path18) ?? /* @__PURE__ */ new Set();
5773
+ recordUnconfirmedEntry(path19, publicPath) {
5774
+ const publicPaths = this.context.unconfirmedEntries.get(path19) ?? /* @__PURE__ */ new Set();
4983
5775
  publicPaths.add(publicPath);
4984
- this.context.unconfirmedEntries.set(path18, publicPaths);
5776
+ this.context.unconfirmedEntries.set(path19, publicPaths);
4985
5777
  }
4986
5778
  unconfirmedEntryViews() {
4987
5779
  return Object.freeze(
4988
5780
  [...this.context.unconfirmedEntries].map(
4989
- ([path18, publicPaths]) => Object.freeze({ path: path18, publicPaths: Object.freeze([...publicPaths]) })
5781
+ ([path19, publicPaths]) => Object.freeze({ path: path19, publicPaths: Object.freeze([...publicPaths]) })
4990
5782
  )
4991
5783
  );
4992
5784
  }
@@ -5018,18 +5810,18 @@ var TransactionUnitSession = class {
5018
5810
  [item.artifact.path]
5019
5811
  );
5020
5812
  }
5021
- const beforeIdentity = identity(before);
5813
+ const beforeIdentity = identity2(before);
5022
5814
  const handle = await this.operations.open(item.artifact.path, "r");
5023
5815
  const contents = await this.readThroughHandle(item, handle, async () => {
5024
- const handleBefore = identity(await handle.stat());
5025
- if (!sameIdentity(beforeIdentity, handleBefore)) throw this.concurrentModification(item.artifact.path);
5816
+ const handleBefore = identity2(await handle.stat());
5817
+ if (!sameIdentity2(beforeIdentity, handleBefore)) throw this.concurrentModification(item.artifact.path);
5026
5818
  const read = await handle.readFile({ encoding: "utf8" });
5027
- const handleAfter = identity(await handle.stat());
5028
- if (!sameIdentity(handleBefore, handleAfter)) throw this.concurrentModification(item.artifact.path);
5819
+ const handleAfter = identity2(await handle.stat());
5820
+ if (!sameIdentity2(handleBefore, handleAfter)) throw this.concurrentModification(item.artifact.path);
5029
5821
  return read;
5030
5822
  });
5031
5823
  const after = await this.lstatOrAbsent(item.artifact.path);
5032
- if (after === "absent" || after.isSymbolicLink() || !after.isFile() || !sameIdentity(beforeIdentity, identity(after))) {
5824
+ if (after === "absent" || after.isSymbolicLink() || !after.isFile() || !sameIdentity2(beforeIdentity, identity2(after))) {
5033
5825
  throw this.concurrentModification(item.artifact.path);
5034
5826
  }
5035
5827
  await this.assertParentChain(item, context);
@@ -5040,11 +5832,11 @@ var TransactionUnitSession = class {
5040
5832
  const expected = required(owned.identity);
5041
5833
  const handle = await this.operations.open(owned.path, "r");
5042
5834
  const contents = await this.readThroughHandle(item, handle, async () => {
5043
- const before = identity(await handle.stat());
5044
- if (!sameIdentity(before, expected)) throw this.ownershipFailure(owned.publicPath);
5835
+ const before = identity2(await handle.stat());
5836
+ if (!sameIdentity2(before, expected)) throw this.ownershipFailure(owned.publicPath);
5045
5837
  const read = await handle.readFile({ encoding: "utf8" });
5046
- const after = identity(await handle.stat());
5047
- if (!sameIdentity(after, expected)) throw this.ownershipFailure(owned.publicPath);
5838
+ const after = identity2(await handle.stat());
5839
+ if (!sameIdentity2(after, expected)) throw this.ownershipFailure(owned.publicPath);
5048
5840
  return read;
5049
5841
  });
5050
5842
  await this.assertOwnedIdentity(item, owned);
@@ -5103,7 +5895,7 @@ var TransactionUnitSession = class {
5103
5895
  await this.assertNamespace(item);
5104
5896
  if (!owned.identity) throw this.ownershipFailure(owned.publicPath);
5105
5897
  const ownedStat = await this.operations.lstat(owned.path);
5106
- if (ownedStat.isSymbolicLink() || !ownedStat.isFile() || !sameIdentity(identity(ownedStat), owned.identity)) {
5898
+ if (ownedStat.isSymbolicLink() || !ownedStat.isFile() || !sameIdentity2(identity2(ownedStat), owned.identity)) {
5107
5899
  throw this.ownershipFailure(owned.publicPath);
5108
5900
  }
5109
5901
  await this.assertNamespace(item);
@@ -5112,7 +5904,7 @@ var TransactionUnitSession = class {
5112
5904
  const namespace = required(item.namespace);
5113
5905
  const expectedIdentity = required(namespace.identity);
5114
5906
  const namespaceStat = await this.operations.lstat(namespace.path);
5115
- if (namespaceStat.isSymbolicLink() || !namespaceStat.isDirectory() || !sameIdentity(identity(namespaceStat), expectedIdentity)) {
5907
+ if (namespaceStat.isSymbolicLink() || !namespaceStat.isDirectory() || !sameIdentity2(identity2(namespaceStat), expectedIdentity)) {
5116
5908
  throw this.ownershipFailure(namespace.publicPath);
5117
5909
  }
5118
5910
  }
@@ -5131,54 +5923,54 @@ var TransactionUnitSession = class {
5131
5923
  }
5132
5924
  await this.assertDirectoryExpectation(expectation.path, expectation.original, item.artifact.path);
5133
5925
  }
5134
- async assertDirectoryExpectation(path18, expected, publicPath) {
5926
+ async assertDirectoryExpectation(path19, expected, publicPath) {
5135
5927
  let pathStat;
5136
5928
  try {
5137
- pathStat = await this.operations.lstat(path18);
5929
+ pathStat = await this.operations.lstat(path19);
5138
5930
  } catch (error) {
5139
5931
  throw this.concurrentModification(publicPath, error);
5140
5932
  }
5141
5933
  const kind = pathStat.isSymbolicLink() ? "symbolic-link" : pathStat.isDirectory() ? "directory" : void 0;
5142
- if (kind !== expected.kind || !sameIdentity(identity(pathStat), expected.identity)) {
5934
+ if (kind !== expected.kind || !sameIdentity2(identity2(pathStat), expected.identity)) {
5143
5935
  throw this.concurrentModification(publicPath);
5144
5936
  }
5145
5937
  if (expected.kind === "symbolic-link") {
5146
5938
  let followed;
5147
5939
  try {
5148
- followed = await this.operations.stat(path18);
5940
+ followed = await this.operations.stat(path19);
5149
5941
  } catch (error) {
5150
5942
  throw this.concurrentModification(publicPath, error);
5151
5943
  }
5152
- if (!followed.isDirectory() || !sameIdentity(identity(followed), expected.followedIdentity)) {
5944
+ if (!followed.isDirectory() || !sameIdentity2(identity2(followed), expected.followedIdentity)) {
5153
5945
  throw this.concurrentModification(publicPath);
5154
5946
  }
5155
5947
  }
5156
5948
  }
5157
- async assertDirectoryIdentity(path18, expected, publicPath) {
5949
+ async assertDirectoryIdentity(path19, expected, publicPath) {
5158
5950
  let pathStat;
5159
5951
  try {
5160
- pathStat = await this.operations.lstat(path18);
5952
+ pathStat = await this.operations.lstat(path19);
5161
5953
  } catch (error) {
5162
5954
  throw this.concurrentModification(publicPath, error);
5163
5955
  }
5164
- if (pathStat.isSymbolicLink() || !pathStat.isDirectory() || !sameIdentity(identity(pathStat), expected)) {
5956
+ if (pathStat.isSymbolicLink() || !pathStat.isDirectory() || !sameIdentity2(identity2(pathStat), expected)) {
5165
5957
  throw this.concurrentModification(publicPath);
5166
5958
  }
5167
5959
  }
5168
- async assertExpectedAbsent(path18, publicPath) {
5960
+ async assertExpectedAbsent(path19, publicPath) {
5169
5961
  try {
5170
- await this.operations.lstat(path18);
5962
+ await this.operations.lstat(path19);
5171
5963
  } catch (error) {
5172
- if (isEnoent(error)) return;
5964
+ if (isEnoent2(error)) return;
5173
5965
  throw error;
5174
5966
  }
5175
5967
  throw this.concurrentModification(publicPath);
5176
5968
  }
5177
- async lstatOrAbsent(path18) {
5969
+ async lstatOrAbsent(path19) {
5178
5970
  try {
5179
- return await this.operations.lstat(path18);
5971
+ return await this.operations.lstat(path19);
5180
5972
  } catch (error) {
5181
- if (isEnoent(error)) return "absent";
5973
+ if (isEnoent2(error)) return "absent";
5182
5974
  throw error;
5183
5975
  }
5184
5976
  }
@@ -5212,9 +6004,9 @@ var TransactionUnitSession = class {
5212
6004
  );
5213
6005
  }
5214
6006
  };
5215
- function directoryChain(path18) {
6007
+ function directoryChain(path19) {
5216
6008
  const result2 = [];
5217
- let current = resolve3(path18);
6009
+ let current = resolve3(path19);
5218
6010
  while (true) {
5219
6011
  result2.unshift(current);
5220
6012
  const parent = dirname2(current);
@@ -5401,7 +6193,7 @@ var MigrationTransaction = class {
5401
6193
  }
5402
6194
  }
5403
6195
  rejectParseErrors(plan) {
5404
- const paths = plan.files.flatMap((file) => file.results.filter((result2) => result2.status === "parse-error").map((result2) => result2.fileName)).filter((path18, index, all) => all.indexOf(path18) === index).sort(compareCodeUnits);
6196
+ const paths = plan.files.flatMap((file) => file.results.filter((result2) => result2.status === "parse-error").map((result2) => result2.fileName)).filter((path19, index, all) => all.indexOf(path19) === index).sort(compareCodeUnits);
5405
6197
  if (paths.length === 0) return;
5406
6198
  throw new MigrationApplicationError(
5407
6199
  "internal-invariant",
@@ -5520,6 +6312,13 @@ var ApplyProjectStage = class {
5520
6312
  );
5521
6313
  }
5522
6314
  const plan = validated.plan;
6315
+ const snapshots = validated.rendered.analyzed.manifest.invocation.options.configurationSnapshots ?? [];
6316
+ await assertConfigurationUnchanged(snapshots);
6317
+ for (const artifact of plan.artifacts)
6318
+ for (const snapshot of snapshots) {
6319
+ if (await pathsOverlapOnFileSystem(artifact.path, snapshot.path))
6320
+ throw new Error(`Migration output collides with target configuration: ${artifact.path}`);
6321
+ }
5523
6322
  const hasParseError = plan.files.some((file) => file.results.some((result2) => result2.status === "parse-error"));
5524
6323
  if (this.mode === "plan") {
5525
6324
  if (!hasParseError) await this.transaction.preflight(plan);
@@ -5529,27 +6328,28 @@ var ApplyProjectStage = class {
5529
6328
  return appliedProject({ validated, application: { status: "skipped", reason: "parse-errors" } });
5530
6329
  }
5531
6330
  await this.transaction.preflight(plan);
6331
+ await assertConfigurationUnchanged(snapshots);
5532
6332
  if (plan.artifacts.length > 0) await this.transaction.apply(plan);
5533
6333
  return appliedProject({ validated, application: { status: "applied" } });
5534
6334
  }
5535
6335
  };
5536
6336
 
5537
6337
  // src/pipeline/discover/discover-project.stage.ts
5538
- import { readdir, stat as stat2 } from "fs/promises";
5539
- import * as path4 from "path";
6338
+ import { readdir, stat as stat3 } from "fs/promises";
6339
+ import * as path6 from "path";
5540
6340
 
5541
6341
  // src/lib/gitignore.helper.ts
5542
6342
  import fs from "fs-extra";
5543
6343
  import ignore from "ignore";
5544
- import path3 from "path";
6344
+ import path5 from "path";
5545
6345
  async function createGitIgnoreMatcher(root, displayRoot = root) {
5546
6346
  const matcher = ignore();
5547
- const gitignorePath = path3.join(root, ".gitignore");
6347
+ const gitignorePath = path5.join(root, ".gitignore");
5548
6348
  if (await fs.pathExists(gitignorePath)) {
5549
6349
  matcher.add(await fs.readFile(gitignorePath, "utf8"));
5550
- logger.debug(`Loaded .gitignore file from ${path3.join(displayRoot, ".gitignore")}`);
6350
+ logger.debug(`Loaded .gitignore file from ${path5.join(displayRoot, ".gitignore")}`);
5551
6351
  }
5552
- const relativeIgnorePath = (candidate) => path3.relative(root, candidate).split(path3.sep).join("/");
6352
+ const relativeIgnorePath = (candidate) => path5.relative(root, candidate).split(path5.sep).join("/");
5553
6353
  return Object.freeze({
5554
6354
  ignores: (candidate) => matcher.ignores(relativeIgnorePath(candidate)),
5555
6355
  ignoresDirectory: (candidate) => matcher.ignores(`${relativeIgnorePath(candidate)}/`)
@@ -5559,7 +6359,7 @@ async function createGitIgnoreMatcher(root, displayRoot = root) {
5559
6359
  // src/pipeline/discover/discover-project.stage.ts
5560
6360
  var nodeFileSystem = Object.freeze({
5561
6361
  async kind(candidate) {
5562
- const candidateStat = await stat2(candidate);
6362
+ const candidateStat = await stat3(candidate);
5563
6363
  if (candidateStat.isFile()) return "file";
5564
6364
  if (candidateStat.isDirectory()) return "directory";
5565
6365
  return "other";
@@ -5594,10 +6394,10 @@ var DiscoverProjectStage = class {
5594
6394
  return projectManifest({ invocation, templates });
5595
6395
  }
5596
6396
  singleFile(invocation) {
5597
- if (path4.extname(invocation.canonicalInputPath).toLowerCase() !== ".html") {
6397
+ if (path6.extname(invocation.canonicalInputPath).toLowerCase() !== ".html") {
5598
6398
  throw new Error(`Unsupported file type: ${invocation.inputPath}`);
5599
6399
  }
5600
- if (path4.extname(invocation.canonicalOutputPath).toLowerCase() !== ".html") {
6400
+ if (path6.extname(invocation.canonicalOutputPath).toLowerCase() !== ".html") {
5601
6401
  throw new Error("Single-file output path must have a .html extension.");
5602
6402
  }
5603
6403
  return [{ inputPath: invocation.canonicalInputPath, outputPath: invocation.canonicalOutputPath }];
@@ -5610,7 +6410,7 @@ var DiscoverProjectStage = class {
5610
6410
  inputs.sort(compareCodeUnits);
5611
6411
  return inputs.map((inputPath) => ({
5612
6412
  inputPath,
5613
- outputPath: path4.join(invocation.canonicalOutputPath, path4.relative(root, inputPath))
6413
+ outputPath: path6.join(invocation.canonicalOutputPath, path6.relative(root, inputPath))
5614
6414
  }));
5615
6415
  }
5616
6416
  async collectInputs(directory, displayDirectory, matcher, exclusions) {
@@ -5619,30 +6419,30 @@ var DiscoverProjectStage = class {
5619
6419
  );
5620
6420
  const inputs = [];
5621
6421
  for (const entry of entries) {
5622
- const candidate = path4.join(directory, entry.name);
5623
- const displayCandidate = path4.join(displayDirectory, entry.name);
6422
+ const candidate = path6.join(directory, entry.name);
6423
+ const displayCandidate = path6.join(displayDirectory, entry.name);
5624
6424
  logger.debug(`Processing ${displayCandidate}`);
5625
6425
  const ignored = entry.kind === "directory" ? matcher.ignoresDirectory(candidate) : matcher.ignores(candidate);
5626
- if (ignored || exclusions.has(path4.normalize(candidate))) continue;
6426
+ if (ignored || exclusions.has(path6.normalize(candidate))) continue;
5627
6427
  const kind = entry.kind === "other" ? await this.fileSystem.kind(candidate) : entry.kind;
5628
6428
  if (entry.kind === "other" && kind === "directory" && matcher.ignoresDirectory(candidate)) continue;
5629
6429
  if (kind === "directory") {
5630
6430
  inputs.push(...await this.collectInputs(candidate, displayCandidate, matcher, exclusions));
5631
- } else if (kind === "file" && path4.extname(entry.name).toLowerCase() === ".html") {
5632
- inputs.push(path4.normalize(path4.resolve(candidate)));
6431
+ } else if (kind === "file" && path6.extname(entry.name).toLowerCase() === ".html") {
6432
+ inputs.push(path6.normalize(path6.resolve(candidate)));
5633
6433
  }
5634
6434
  }
5635
6435
  return inputs;
5636
6436
  }
5637
6437
  excludedPaths(invocation) {
5638
- const candidates = [invocation.options.stylesheetPath, invocation.options.reportPath].filter((candidate) => candidate !== void 0).map((candidate) => path4.normalize(path4.resolve(candidate)));
6438
+ const candidates = [invocation.options.stylesheetPath, invocation.options.reportPath].filter((candidate) => candidate !== void 0).map((candidate) => path6.normalize(path6.resolve(candidate)));
5639
6439
  const outputRoot = invocation.canonicalOutputPath;
5640
- if (path4.relative(invocation.canonicalInputPath, outputRoot) !== "") candidates.push(outputRoot);
6440
+ if (path6.relative(invocation.canonicalInputPath, outputRoot) !== "") candidates.push(outputRoot);
5641
6441
  return new Set(candidates);
5642
6442
  }
5643
6443
  };
5644
6444
  function preserveTrailingSeparator(canonicalPath, rawPath) {
5645
- return /[\\/]$/u.test(rawPath) && !/[\\/]$/u.test(canonicalPath) ? `${canonicalPath}${path4.sep}` : canonicalPath;
6445
+ return /[\\/]$/u.test(rawPath) && !/[\\/]$/u.test(canonicalPath) ? `${canonicalPath}${path6.sep}` : canonicalPath;
5646
6446
  }
5647
6447
 
5648
6448
  // src/pipeline/pipeline-stage.error.ts
@@ -5684,7 +6484,7 @@ async function runStage(stage, action) {
5684
6484
  }
5685
6485
 
5686
6486
  // src/report/migration-report.builder.ts
5687
- import path5 from "path";
6487
+ import path7 from "path";
5688
6488
  var MigrationReportBuilder = class {
5689
6489
  build(inputRoot, outputRoot, target, mode, application, durationMs, files, stylesheet) {
5690
6490
  const pathApi = this.pathApi(
@@ -5718,9 +6518,9 @@ var MigrationReportBuilder = class {
5718
6518
  return { path: this.forwardSlashes(pathApi, displayPath), change: stylesheet.change };
5719
6519
  }
5720
6520
  pathApi(...values) {
5721
- if (values.some((value) => /^[A-Za-z]:[\\/]/.test(value) || value.includes("\\"))) return path5.win32;
5722
- if (values.some((value) => value.includes("/"))) return path5.posix;
5723
- return path5;
6521
+ if (values.some((value) => /^[A-Za-z]:[\\/]/.test(value) || value.includes("\\"))) return path7.win32;
6522
+ if (values.some((value) => value.includes("/"))) return path7.posix;
6523
+ return path7;
5724
6524
  }
5725
6525
  samePath(pathApi, left, right) {
5726
6526
  return this.absolutePath(pathApi, left) === this.absolutePath(pathApi, right);
@@ -5797,9 +6597,16 @@ var MigrationReportBuilder = class {
5797
6597
  };
5798
6598
  }
5799
6599
  };
6600
+ function withReportEnvironment(report, options) {
6601
+ return {
6602
+ ...report,
6603
+ ...options.targetProfile ? { targetProfile: options.targetProfile } : {},
6604
+ ...options.sourceBreakpoints ? { sourceBreakpoints: options.sourceBreakpoints } : {}
6605
+ };
6606
+ }
5800
6607
 
5801
6608
  // src/pipeline/invocation-error-path.mapper.ts
5802
- import * as path6 from "path";
6609
+ import * as path8 from "path";
5803
6610
  function remapInvocationErrorPaths(error, invocation) {
5804
6611
  if (!(error instanceof Error)) return error;
5805
6612
  const roots = invocationRoots(invocation);
@@ -5823,12 +6630,12 @@ function remapErrorPath(error, field, roots) {
5823
6630
  error[field] = mapped;
5824
6631
  }
5825
6632
  function mappedInvocationPath(candidate, roots) {
5826
- if (!path6.isAbsolute(candidate)) return candidate;
6633
+ if (!path8.isAbsolute(candidate)) return candidate;
5827
6634
  for (const root of roots) {
5828
- const relativePath = path6.relative(root.canonical, candidate);
6635
+ const relativePath = path8.relative(root.canonical, candidate);
5829
6636
  if (relativePath === "") return root.raw;
5830
- if (relativePath === ".." || relativePath.startsWith(`..${path6.sep}`) || path6.isAbsolute(relativePath)) continue;
5831
- return path6.join(root.raw, relativePath);
6637
+ if (relativePath === ".." || relativePath.startsWith(`..${path8.sep}`) || path8.isAbsolute(relativePath)) continue;
6638
+ return path8.join(root.raw, relativePath);
5832
6639
  }
5833
6640
  return candidate;
5834
6641
  }
@@ -5846,7 +6653,7 @@ var MigrationRunner = class {
5846
6653
  const durationMs = this.now() - startedAt;
5847
6654
  const { validated, application } = applied;
5848
6655
  const { plan, rendered, stylesheet } = validated;
5849
- return this.reports.build(
6656
+ const report = this.reports.build(
5850
6657
  rendered.analyzed.manifest.invocation.inputPath,
5851
6658
  rendered.analyzed.manifest.invocation.outputPath,
5852
6659
  plan.target,
@@ -5856,6 +6663,7 @@ var MigrationRunner = class {
5856
6663
  plan.files,
5857
6664
  stylesheet
5858
6665
  );
6666
+ return withReportEnvironment(report, invocation.options);
5859
6667
  }
5860
6668
  async execute(invocation) {
5861
6669
  try {
@@ -8898,7 +9706,7 @@ var ConversionPlanner = class {
8898
9706
  };
8899
9707
 
8900
9708
  // src/pipeline/rendered-project.ts
8901
- import * as path7 from "path";
9709
+ import * as path9 from "path";
8902
9710
  function renderedProject(project) {
8903
9711
  const analyzed = analyzedProject(project.analyzed);
8904
9712
  if (project.files.length !== analyzed.templates.length) throw sequenceInvariant2();
@@ -9025,7 +9833,7 @@ function samePathPair2(left, right) {
9025
9833
  return normalizedAbsolutePath3(left.inputPath) === normalizedAbsolutePath3(right.inputPath) && normalizedAbsolutePath3(left.outputPath) === normalizedAbsolutePath3(right.outputPath);
9026
9834
  }
9027
9835
  function normalizedAbsolutePath3(value) {
9028
- return path7.normalize(path7.resolve(value));
9836
+ return path9.normalize(path9.resolve(value));
9029
9837
  }
9030
9838
  function sameOwnedValue(left, right) {
9031
9839
  if (Object.is(left, right)) return true;
@@ -9114,10 +9922,10 @@ function parseErrorPlan(template) {
9114
9922
  }
9115
9923
 
9116
9924
  // src/pipeline/validate/validate-project.stage.ts
9117
- import * as path14 from "path";
9925
+ import * as path15 from "path";
9118
9926
 
9119
9927
  // src/migrator/migration-plan.ts
9120
- import * as path8 from "path";
9928
+ import * as path10 from "path";
9121
9929
 
9122
9930
  // src/migrator/file-migration-result.ts
9123
9931
  function fileMigrationResult(result2) {
@@ -9140,7 +9948,7 @@ function freezeValue2(value) {
9140
9948
 
9141
9949
  // src/migrator/migration-plan.ts
9142
9950
  function plannedOutputArtifact(artifact) {
9143
- if (!path8.isAbsolute(artifact.path)) {
9951
+ if (!path10.isAbsolute(artifact.path)) {
9144
9952
  throw new Error(`Planned artifact paths must be absolute: ${artifact.path}`);
9145
9953
  }
9146
9954
  if (artifact.kind === "template" && artifact.proposed.status === "absent") {
@@ -9151,7 +9959,7 @@ function plannedOutputArtifact(artifact) {
9151
9959
  }
9152
9960
  return Object.freeze({
9153
9961
  kind: artifact.kind,
9154
- path: path8.resolve(artifact.path),
9962
+ path: path10.resolve(artifact.path),
9155
9963
  original: artifactState(artifact.original),
9156
9964
  proposed: artifactState(artifact.proposed)
9157
9965
  });
@@ -9178,216 +9986,9 @@ function sameState(left, right) {
9178
9986
  return right.status === "present" && left.contents === right.contents;
9179
9987
  }
9180
9988
 
9181
- // src/migrator/migration-path.validator.ts
9182
- import { lstat as lstat2, stat as stat3 } from "fs/promises";
9183
- import * as path9 from "path";
9184
- async function validateMigrationPaths(request, pathApi = path9) {
9185
- const claims = normalizedClaims(request, pathApi);
9186
- await validateCollisions(claims, pathApi);
9187
- const destinations = claims.filter((claim) => claim.kind !== "template-input");
9188
- for (const destination of destinations) {
9189
- await validateDestination(destination.path);
9190
- }
9191
- }
9192
- async function validateStylesheetRootTopology(request, pathApi = path9) {
9193
- if (request.stylesheetPath === void 0) return;
9194
- const stylesheetPath = pathApi.resolve(request.stylesheetPath);
9195
- const templateRoots = [request.inputPath, request.outputPath].map((claim) => pathApi.resolve(claim));
9196
- const reportPath = request.reportPath === void 0 ? void 0 : pathApi.resolve(request.reportPath);
9197
- const exactCollision = (await Promise.all(
9198
- [...templateRoots, reportPath].map(
9199
- (claim) => claim === void 0 ? Promise.resolve(false) : pathsEquivalentOnFileSystem(stylesheetPath, claim, pathApi)
9200
- )
9201
- )).some(Boolean);
9202
- const reportHierarchyCollision = reportPath !== void 0 && await pathsOverlapOnFileSystem(stylesheetPath, reportPath, pathApi);
9203
- if (exactCollision || reportHierarchyCollision) {
9204
- const collisionPaths = reportHierarchyCollision && reportPath !== void 0 && !await pathsEquivalentOnFileSystem(stylesheetPath, reportPath, pathApi) ? [stylesheetPath, reportPath] : [stylesheetPath];
9205
- throw new MigrationApplicationError(
9206
- "path-collision",
9207
- `Stylesheet path collides with another migration path: ${request.stylesheetPathInput ?? request.stylesheetPath}`,
9208
- collisionPaths
9209
- );
9210
- }
9211
- let stylesheetStat;
9212
- try {
9213
- stylesheetStat = await lstat2(stylesheetPath);
9214
- } catch (error) {
9215
- if (isEnoent2(error)) return;
9216
- throw error;
9217
- }
9218
- const sourcePath = request.stylesheetPathInput ?? request.stylesheetPath;
9219
- if (stylesheetStat.isSymbolicLink()) {
9220
- throw new MigrationApplicationError(
9221
- "unsupported-path-type",
9222
- `Stylesheet path must not be a symbolic link: ${sourcePath}`,
9223
- [stylesheetPath]
9224
- );
9225
- }
9226
- if (!stylesheetStat.isFile()) {
9227
- throw new MigrationApplicationError(
9228
- "unsupported-path-type",
9229
- `Stylesheet path must be a regular file: ${sourcePath}`,
9230
- [stylesheetPath]
9231
- );
9232
- }
9233
- }
9234
- function normalizedClaims(request, pathApi) {
9235
- return [
9236
- ...request.templates.flatMap((template, templateIndex) => [
9237
- { path: pathApi.resolve(template.inputPath), kind: "template-input", templateIndex },
9238
- { path: pathApi.resolve(template.outputPath), kind: "template-output", templateIndex }
9239
- ]),
9240
- ...request.stylesheetPath ? [{ path: pathApi.resolve(request.stylesheetPath), kind: "stylesheet" }] : [],
9241
- ...request.reportPath ? [{ path: pathApi.resolve(request.reportPath), kind: "report" }] : []
9242
- ];
9243
- }
9244
- async function validateCollisions(claims, pathApi) {
9245
- const observations = /* @__PURE__ */ new Map();
9246
- const observe = (candidate) => {
9247
- const normalized = pathApi.resolve(candidate);
9248
- const existing = observations.get(normalized);
9249
- if (existing) return existing;
9250
- const pending = observePath(normalized, pathApi);
9251
- observations.set(normalized, pending);
9252
- return pending;
9253
- };
9254
- for (let leftIndex = 0; leftIndex < claims.length; leftIndex++) {
9255
- const left = claims[leftIndex];
9256
- if (!left) continue;
9257
- for (let rightIndex = leftIndex + 1; rightIndex < claims.length; rightIndex++) {
9258
- const right = claims[rightIndex];
9259
- if (!right) continue;
9260
- const relationship = await fileSystemPathRelationship(left.path, right.path, pathApi, observe);
9261
- if (relationship === "distinct" || isIntentionalInPlacePair(left, right, pathApi)) continue;
9262
- const collisionPaths = relationship === "equivalent" ? [left.path] : [left.path, right.path];
9263
- throw new MigrationApplicationError(
9264
- "path-collision",
9265
- `Migration paths collide: ${collisionPaths.join(" and ")}`,
9266
- collisionPaths
9267
- );
9268
- }
9269
- }
9270
- }
9271
- function isIntentionalInPlacePair(left, right, pathApi) {
9272
- return pathsEquivalent(left.path, right.path, pathApi) && left.templateIndex !== void 0 && left.templateIndex === right.templateIndex && left.kind !== right.kind && left.kind.startsWith("template-") && right.kind.startsWith("template-");
9273
- }
9274
- function pathsEquivalent(left, right, pathApi = path9) {
9275
- return normalizedPathsEquivalent(pathApi, pathApi.resolve(left), pathApi.resolve(right));
9276
- }
9277
- async function pathsEquivalentOnFileSystem(left, right, pathApi = path9) {
9278
- return await fileSystemPathRelationship(left, right, pathApi) === "equivalent";
9279
- }
9280
- async function pathsOverlapOnFileSystem(left, right, pathApi = path9) {
9281
- return await fileSystemPathRelationship(left, right, pathApi) !== "distinct";
9282
- }
9283
- function normalizedPathsEquivalent(pathApi, left, right) {
9284
- return pathApi.relative(left, right) === "";
9285
- }
9286
- function isAncestor(pathApi, ancestor, descendant) {
9287
- const relative3 = pathApi.relative(ancestor, descendant);
9288
- return relative3 !== "" && relative3 !== ".." && !relative3.startsWith(`..${pathApi.sep}`) && !pathApi.isAbsolute(relative3);
9289
- }
9290
- async function fileSystemPathRelationship(left, right, pathApi, observe = (candidate) => observePath(candidate, pathApi)) {
9291
- const normalizedLeft = pathApi.resolve(left);
9292
- const normalizedRight = pathApi.resolve(right);
9293
- if (normalizedPathsEquivalent(pathApi, normalizedLeft, normalizedRight)) return "equivalent";
9294
- if (isAncestor(pathApi, normalizedLeft, normalizedRight)) return "ancestor";
9295
- if (isAncestor(pathApi, normalizedRight, normalizedLeft)) return "descendant";
9296
- const [observedLeft, observedRight] = await Promise.all([observe(normalizedLeft), observe(normalizedRight)]);
9297
- if (observedLeft.exactIdentity && observedRight.exactIdentity) {
9298
- if (sameIdentity2(observedLeft.exactIdentity, observedRight.exactIdentity)) return "equivalent";
9299
- if (hasIdentityBelow(observedRight, observedLeft.exactIdentity)) return "ancestor";
9300
- if (hasIdentityBelow(observedLeft, observedRight.exactIdentity)) return "descendant";
9301
- return "distinct";
9302
- }
9303
- return relationshipThroughExistingPrefixes(observedLeft, observedRight);
9304
- }
9305
- async function observePath(candidate, pathApi) {
9306
- const prefixes = [];
9307
- const suffix = [];
9308
- let current = candidate;
9309
- let exactIdentity;
9310
- while (true) {
9311
- try {
9312
- const currentStat = await stat3(current, { bigint: true });
9313
- const currentIdentity = identity2(currentStat);
9314
- if (suffix.length === 0) exactIdentity = currentIdentity;
9315
- prefixes.push({ identity: currentIdentity, suffix: [...suffix] });
9316
- } catch (error) {
9317
- if (!isMissingPath(error)) throw error;
9318
- }
9319
- const parent = pathApi.dirname(current);
9320
- if (parent === current) break;
9321
- suffix.unshift(pathApi.basename(current));
9322
- current = parent;
9323
- }
9324
- return { ...exactIdentity ? { exactIdentity } : {}, prefixes };
9325
- }
9326
- function relationshipThroughExistingPrefixes(left, right) {
9327
- for (const leftPrefix of left.prefixes) {
9328
- for (const rightPrefix of right.prefixes) {
9329
- if (!sameIdentity2(leftPrefix.identity, rightPrefix.identity)) continue;
9330
- const relationship = suffixRelationship(leftPrefix.suffix, rightPrefix.suffix);
9331
- if (relationship !== "distinct") return relationship;
9332
- }
9333
- }
9334
- return "distinct";
9335
- }
9336
- function hasIdentityBelow(observed, candidate) {
9337
- return observed.prefixes.some((prefix) => prefix.suffix.length > 0 && sameIdentity2(prefix.identity, candidate));
9338
- }
9339
- function suffixRelationship(left, right) {
9340
- const normalizedLeft = left.map(portablePathSegment);
9341
- const normalizedRight = right.map(portablePathSegment);
9342
- const sharedLength = Math.min(normalizedLeft.length, normalizedRight.length);
9343
- for (let index = 0; index < sharedLength; index++) {
9344
- if (normalizedLeft[index] !== normalizedRight[index]) return "distinct";
9345
- }
9346
- if (normalizedLeft.length === normalizedRight.length) return "equivalent";
9347
- return normalizedLeft.length < normalizedRight.length ? "ancestor" : "descendant";
9348
- }
9349
- function portablePathSegment(value) {
9350
- return value.normalize("NFC").toLowerCase();
9351
- }
9352
- function identity2(value) {
9353
- return { device: String(value.dev), inode: String(value.ino) };
9354
- }
9355
- function sameIdentity2(left, right) {
9356
- return left.device === right.device && left.inode === right.inode;
9357
- }
9358
- async function validateDestination(destination) {
9359
- let stat4;
9360
- try {
9361
- stat4 = await lstat2(destination);
9362
- } catch (error) {
9363
- if (isEnoent2(error)) return;
9364
- throw error;
9365
- }
9366
- if (stat4.isSymbolicLink()) {
9367
- throw new MigrationApplicationError(
9368
- "unsupported-path-type",
9369
- `Migration destination must not be a symbolic link: ${destination}`,
9370
- [destination]
9371
- );
9372
- }
9373
- if (!stat4.isFile()) {
9374
- throw new MigrationApplicationError(
9375
- "unsupported-path-type",
9376
- `Migration destination must be a regular file: ${destination}`,
9377
- [destination]
9378
- );
9379
- }
9380
- }
9381
- function isEnoent2(error) {
9382
- return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
9383
- }
9384
- function isMissingPath(error) {
9385
- return typeof error === "object" && error !== null && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
9386
- }
9387
-
9388
9989
  // src/migrator/stylesheet.planner.ts
9389
- import { lstat as lstat3, readFile as readFile2 } from "fs/promises";
9390
- import * as path10 from "path";
9990
+ import { lstat as lstat3, readFile as readFile3 } from "fs/promises";
9991
+ import * as path11 from "path";
9391
9992
 
9392
9993
  // src/adapter/css/stylesheet/css-stylesheet.error.ts
9393
9994
  var CssStylesheetError = class extends Error {
@@ -9916,7 +10517,7 @@ function mergeStylesheetContents(existing, rules, references = new Set(rules.map
9916
10517
 
9917
10518
  // src/migrator/stylesheet.planner.ts
9918
10519
  var nodeFileSystem2 = {
9919
- readFile: (target) => readFile2(target, "utf8"),
10520
+ readFile: (target) => readFile3(target, "utf8"),
9920
10521
  lstat: lstat3
9921
10522
  };
9922
10523
  var StylesheetPlanner = class {
@@ -9924,7 +10525,7 @@ var StylesheetPlanner = class {
9924
10525
  this.fileSystem = fileSystem;
9925
10526
  }
9926
10527
  async plan(stylesheetPath, rules, references = new Set(rules.map((rule) => rule.className))) {
9927
- const outputPath = path10.resolve(stylesheetPath);
10528
+ const outputPath = path11.resolve(stylesheetPath);
9928
10529
  const original = await this.originalState(outputPath);
9929
10530
  const existing = original.status === "present" ? original.contents : "";
9930
10531
  let merged;
@@ -9984,7 +10585,7 @@ function isEnoent3(error) {
9984
10585
  }
9985
10586
 
9986
10587
  // src/pipeline/validated-project-plan.ts
9987
- import * as path11 from "path";
10588
+ import * as path12 from "path";
9988
10589
  function validatedProjectPlan(project) {
9989
10590
  const rendered = renderedProject(project.rendered);
9990
10591
  if (rendered.target !== rendered.session.target) {
@@ -10097,7 +10698,7 @@ function isPlainRecord2(value) {
10097
10698
  return prototype === Object.prototype || prototype === null;
10098
10699
  }
10099
10700
  function normalizedAbsolutePath4(value) {
10100
- return path11.normalize(path11.resolve(value));
10701
+ return path12.normalize(path12.resolve(value));
10101
10702
  }
10102
10703
  function fileCongruenceInvariant() {
10103
10704
  return internalInvariant4(
@@ -10109,12 +10710,12 @@ function internalInvariant4(message) {
10109
10710
  }
10110
10711
 
10111
10712
  // src/pipeline/validate/css-reference.collector.ts
10112
- import * as path12 from "path";
10713
+ import * as path13 from "path";
10113
10714
 
10114
10715
  // src/migrator/destination-template-source.ts
10115
- import { readFile as readFile3 } from "fs/promises";
10716
+ import { readFile as readFile4 } from "fs/promises";
10116
10717
  var nodeDestinationTemplateSource = Object.freeze({
10117
- read: (path18) => readFile3(path18, "utf8")
10718
+ read: (path19) => readFile4(path19, "utf8")
10118
10719
  });
10119
10720
 
10120
10721
  // src/pipeline/validate/css-reference.collector.ts
@@ -10133,10 +10734,10 @@ var CssReferenceCollector = class {
10133
10734
  if (file.artifact?.kind === "template" && file.artifact.proposed.status === "present") {
10134
10735
  return { contents: file.artifact.proposed.contents, complete: true };
10135
10736
  }
10136
- if (path12.resolve(file.file.inputPath) === path12.resolve(file.file.outputPath)) {
10737
+ if (path13.resolve(file.file.inputPath) === path13.resolve(file.file.outputPath)) {
10137
10738
  return { contents: analyzedTemplate.source, complete: true };
10138
10739
  }
10139
- const outputPath = path12.normalize(path12.resolve(file.file.outputPath));
10740
+ const outputPath = path13.normalize(path13.resolve(file.file.outputPath));
10140
10741
  const existing = destinationSources.get(outputPath);
10141
10742
  if (existing !== void 0) return existing;
10142
10743
  const pending = this.readDestination(outputPath);
@@ -10202,8 +10803,20 @@ function isEnoent4(error) {
10202
10803
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
10203
10804
  }
10204
10805
 
10806
+ // src/template/generated-template-validation.ts
10807
+ function generatedTemplateErrors(source, fileName, parser = new AngularTemplateParser()) {
10808
+ const parsed2 = parser.parse(source, fileName);
10809
+ return parsed2.status === "parse-error" ? parsed2.diagnostics.map((diagnostic2) => ({
10810
+ status: "parse-error",
10811
+ fileName,
10812
+ code: "generated-template-parse-error",
10813
+ reason: diagnostic2.message,
10814
+ source: diagnostic2.source
10815
+ })) : [];
10816
+ }
10817
+
10205
10818
  // src/pipeline/validate/template-proposal.validator.ts
10206
- import * as path13 from "path";
10819
+ import * as path14 from "path";
10207
10820
  var TemplateProposalValidator = class {
10208
10821
  constructor(validationParser = new AngularTemplateParser(), destinationTemplates = nodeDestinationTemplateSource) {
10209
10822
  this.validationParser = validationParser;
@@ -10218,21 +10831,8 @@ var TemplateProposalValidator = class {
10218
10831
  );
10219
10832
  }
10220
10833
  if (edited.output === template.source) return planResult(template, rendered, false, rendered.results);
10221
- const reparsed = this.validationParser.parse(edited.output, template.file.outputPath);
10222
- if (reparsed.status === "parse-error") {
10223
- return planResult(
10224
- template,
10225
- rendered,
10226
- false,
10227
- reparsed.diagnostics.map((diagnostic2) => ({
10228
- status: "parse-error",
10229
- fileName: template.file.outputPath,
10230
- code: "generated-template-parse-error",
10231
- reason: diagnostic2.message,
10232
- source: diagnostic2.source
10233
- }))
10234
- );
10235
- }
10834
+ const errors = generatedTemplateErrors(edited.output, template.file.outputPath, this.validationParser);
10835
+ if (errors.length) return planResult(template, rendered, false, errors);
10236
10836
  const original = await originalState(template, this.destinationTemplates);
10237
10837
  const proposed = { status: "present", contents: edited.output };
10238
10838
  if (sameState3(original, proposed)) return planResult(template, rendered, false, rendered.results);
@@ -10240,7 +10840,7 @@ var TemplateProposalValidator = class {
10240
10840
  file: result(template, rendered, true, rendered.results),
10241
10841
  artifact: plannedOutputArtifact({
10242
10842
  kind: "template",
10243
- path: path13.normalize(path13.resolve(template.file.outputPath)),
10843
+ path: path14.normalize(path14.resolve(template.file.outputPath)),
10244
10844
  original,
10245
10845
  proposed
10246
10846
  })
@@ -10248,7 +10848,7 @@ var TemplateProposalValidator = class {
10248
10848
  }
10249
10849
  };
10250
10850
  async function originalState(template, destinationTemplates) {
10251
- if (path13.resolve(template.file.inputPath) === path13.resolve(template.file.outputPath)) {
10851
+ if (path14.resolve(template.file.inputPath) === path14.resolve(template.file.outputPath)) {
10252
10852
  return { status: "present", contents: template.source };
10253
10853
  }
10254
10854
  try {
@@ -10316,7 +10916,7 @@ var ValidateProjectStage = class {
10316
10916
  let stylesheetArtifact;
10317
10917
  let stylesheet;
10318
10918
  if (rendered.session.target === "css" && stylesheetPath !== void 0) {
10319
- const canonicalStylesheetPath = path14.resolve(stylesheetPath);
10919
+ const canonicalStylesheetPath = path15.resolve(stylesheetPath);
10320
10920
  const references = await this.cssReferences.collect(rendered, files);
10321
10921
  stylesheetArtifact = await this.stylesheetPlanner.plan(
10322
10922
  canonicalStylesheetPath,
@@ -10402,19 +11002,19 @@ var AtomicFileWriter = class {
10402
11002
  throw cleanupError(targetPath);
10403
11003
  }
10404
11004
  }
10405
- async captureNamespaceIdentity(path18, targetPath) {
10406
- const stat4 = await this.operations.lstat(path18);
11005
+ async captureNamespaceIdentity(path19, targetPath) {
11006
+ const stat4 = await this.operations.lstat(path19);
10407
11007
  if (stat4.isSymbolicLink() || !stat4.isDirectory()) throw ownershipError(targetPath);
10408
11008
  return identity3(stat4);
10409
11009
  }
10410
- async assertNamespaceIdentity(path18, expected, targetPath) {
10411
- const stat4 = await this.operations.lstat(path18);
11010
+ async assertNamespaceIdentity(path19, expected, targetPath) {
11011
+ const stat4 = await this.operations.lstat(path19);
10412
11012
  if (stat4.isSymbolicLink() || !stat4.isDirectory() || !sameIdentity3(identity3(stat4), expected)) {
10413
11013
  throw ownershipError(targetPath);
10414
11014
  }
10415
11015
  }
10416
- async assertTemporaryIdentity(path18, expected, targetPath) {
10417
- const stat4 = await this.operations.lstat(path18);
11016
+ async assertTemporaryIdentity(path19, expected, targetPath) {
11017
+ const stat4 = await this.operations.lstat(path19);
10418
11018
  if (stat4.isSymbolicLink() || !stat4.isFile() || !sameIdentity3(identity3(stat4), expected)) {
10419
11019
  throw ownershipError(targetPath);
10420
11020
  }
@@ -10478,19 +11078,19 @@ var JsonReportWriter = class {
10478
11078
  constructor(writer = new AtomicFileWriter()) {
10479
11079
  this.writer = writer;
10480
11080
  }
10481
- async write(path18, report, options = {}) {
11081
+ async write(path19, report, options = {}) {
10482
11082
  for (const protectedPath of options.protectedPaths ?? []) {
10483
- if (!await pathsOverlapOnFileSystem(protectedPath, path18)) continue;
10484
- const collisionPaths = await pathsEquivalentOnFileSystem(protectedPath, path18) ? [protectedPath] : [protectedPath, path18];
11083
+ if (!await pathsOverlapOnFileSystem(protectedPath, path19)) continue;
11084
+ const collisionPaths = await pathsEquivalentOnFileSystem(protectedPath, path19) ? [protectedPath] : [protectedPath, path19];
10485
11085
  throw new MigrationApplicationError(
10486
11086
  "path-collision",
10487
- `Report path collides with a migration output: ${path18}`,
11087
+ `Report path collides with a migration output: ${path19}`,
10488
11088
  collisionPaths
10489
11089
  );
10490
11090
  }
10491
11091
  const contents = `${JSON.stringify(report, null, 2)}
10492
11092
  `;
10493
- await this.writer.write(path18, contents);
11093
+ await this.writer.write(path19, contents);
10494
11094
  }
10495
11095
  };
10496
11096
 
@@ -10508,8 +11108,23 @@ var TerminalPresenter = class {
10508
11108
  const diagnostics = report.files.flatMap(
10509
11109
  (file) => file.results.filter((result2) => result2.status !== "converted").map((result2) => `${file.path}:${result2.offset} [${result2.code}] ${result2.reason}`)
10510
11110
  );
11111
+ const profile = report.targetProfile;
11112
+ const environment = profile ? [
11113
+ "Target environment",
11114
+ `Tailwind: v${profile.version} (${profile.fingerprint})`,
11115
+ `Stylesheet: ${profile.stylesheet ?? "not supplied"}`,
11116
+ `Prefix: ${profile.prefix.value ?? "none"} (${profile.prefix.source}; ${profile.prefix.confidence})`,
11117
+ `Important utilities: ${profile.important.value} (${profile.important.source})`,
11118
+ `Core utilities: ${profile.coreUtilities.value} (${profile.coreUtilities.source}; ${profile.coreUtilities.confidence})`,
11119
+ ...Object.entries(profile.breakpoints).map(
11120
+ ([name, setting]) => `Breakpoint ${name}: ${setting.value} (${setting.source}; ${setting.confidence})`
11121
+ ),
11122
+ ...profile.diagnostics.map((item) => `[${item.code}] ${item.message}`),
11123
+ ...profile.assumptions.map((item) => `Assumption: ${item}`),
11124
+ ""
11125
+ ] : [];
10511
11126
  output.write(
10512
- [presentation.outcome, totals, stylesheet, ...diagnostics, presentation.footer, ""].filter((line) => line !== void 0).join("\n")
11127
+ [...environment, presentation.outcome, totals, stylesheet, ...diagnostics, presentation.footer, ""].filter((line) => line !== void 0).join("\n")
10513
11128
  );
10514
11129
  }
10515
11130
  applicationPresentation(report) {
@@ -10610,17 +11225,17 @@ function getErrorMessage(error) {
10610
11225
  // src/cli/exit-policy.ts
10611
11226
  function resolveExitCode(report, allowUnresolved) {
10612
11227
  if (report.summary.parseErrors > 0) return 1;
10613
- const hasUnresolved = report.summary.review > 0 || report.summary.unsupported > 0 || report.summary.invalid > 0;
11228
+ const hasUnresolved = (report.targetProfile?.diagnostics.length ?? 0) > 0 || report.summary.review > 0 || report.summary.unsupported > 0 || report.summary.invalid > 0;
10614
11229
  return hasUnresolved && !allowUnresolved ? 2 : 0;
10615
11230
  }
10616
11231
 
10617
11232
  // src/cli/report-path.validator.ts
10618
- import path15 from "path";
11233
+ import path16 from "path";
10619
11234
  function validateReportPath(reportPath) {
10620
11235
  if (reportPath.trim().length === 0) {
10621
11236
  throw new Error("Report path must not be empty.");
10622
11237
  }
10623
- if (path15.extname(reportPath).toLowerCase() !== ".json") {
11238
+ if (path16.extname(reportPath).toLowerCase() !== ".json") {
10624
11239
  throw new Error("Report path must have a .json extension.");
10625
11240
  }
10626
11241
  }
@@ -10653,8 +11268,8 @@ function parsePrintWithBreakpoints(value, orientationEnabled) {
10653
11268
  }
10654
11269
 
10655
11270
  // src/cli/stylesheet-path.validator.ts
10656
- import * as path16 from "path";
10657
- async function validateStylesheetPath(request, pathApi = path16) {
11271
+ import * as path17 from "path";
11272
+ async function validateStylesheetPath(request, pathApi = path17) {
10658
11273
  if (request.target !== "css") {
10659
11274
  if (request.stylesheetPath !== void 0) {
10660
11275
  throw new MigrationApplicationError("invalid-configuration", "--stylesheet can only be used with --target css.", [
@@ -10684,6 +11299,7 @@ function resolveMigrationMode(argv, write) {
10684
11299
  if (optionArguments.filter((argument) => argument === "--write").length > 1) {
10685
11300
  throw new Error("--write may only be specified once.");
10686
11301
  }
11302
+ if (write && optionArguments.includes("--plan")) throw new Error("--plan and --write cannot be combined.");
10687
11303
  return write ? "write" : "plan";
10688
11304
  }
10689
11305
 
@@ -10712,7 +11328,7 @@ async function runCli(argv, output = processOutput, dependencies = {}) {
10712
11328
  new Option("--stylesheet <path>", "companion stylesheet; required when --target css").argParser(
10713
11329
  parseSingleStylesheet
10714
11330
  )
10715
- ).option("--write", "apply the validated migration plan", false).option("--report <path>", "atomically write a JSON report; path must end in .json").option("--allow-unresolved", "return success when unresolved inputs remain", false).option("--orientation-breakpoints", "confirm the source enables the archived orientation breakpoints", false).option(
11331
+ ).option("--config <path>", "declarative migration JSON configuration").option("--tailwind-stylesheet <path>", "statically analyze a Tailwind v4 target stylesheet").option("--tailwind-prefix <prefix>", "override Tailwind v4 prefix; empty string means none").option("--plan", "explicitly request the default review-only plan").option("--write", "apply the validated migration plan", false).option("--report <path>", "atomically write a JSON report; path must end in .json").option("--allow-unresolved", "return success when unresolved inputs remain", false).option("--orientation-breakpoints", "confirm the source enables the archived orientation breakpoints", false).option(
10716
11332
  "--responsive-images",
10717
11333
  "wrap eligible responsive images in picture elements; acknowledges selector and layout risk",
10718
11334
  false
@@ -10723,23 +11339,38 @@ async function runCli(argv, output = processOutput, dependencies = {}) {
10723
11339
  const mode = resolveMigrationMode(argv, options.write);
10724
11340
  debug = options.debug;
10725
11341
  logger.level = debug ? "debug" : "warn";
11342
+ const configuration = await loadMigrationConfig({
11343
+ config: options.config,
11344
+ stylesheet: options.tailwindStylesheet,
11345
+ prefix: options.tailwindPrefix
11346
+ });
11347
+ const target = program.getOptionValueSource("target") === "cli" ? options.target : configuration.target ?? options.target;
11348
+ if (target !== "tailwind" && (options.tailwindStylesheet !== void 0 || options.tailwindPrefix !== void 0))
11349
+ throw new Error("Tailwind target options require --target tailwind.");
10726
11350
  const destination = options.output ?? input;
10727
11351
  let reportPath;
10728
11352
  if (options.report !== void 0) {
10729
11353
  validateReportPath(options.report);
10730
- reportPath = path17.resolve(options.report);
11354
+ reportPath = path18.resolve(options.report);
10731
11355
  }
11356
+ if (reportPath)
11357
+ for (const snapshot of configuration.snapshots) {
11358
+ if (await pathsOverlapOnFileSystem(reportPath, snapshot.path))
11359
+ throw new Error("Report path collides with a target configuration input.");
11360
+ }
10732
11361
  const stylesheetPath = await validateStylesheetPath({
10733
- target: options.target,
11362
+ target,
10734
11363
  stylesheetPath: options.stylesheet,
10735
11364
  inputPath: input,
10736
11365
  outputPath: destination,
10737
11366
  reportPath
10738
11367
  });
10739
11368
  const printWithBreakpoints = options.printWithBreakpoints === void 0 ? void 0 : parsePrintWithBreakpoints(options.printWithBreakpoints, options.orientationBreakpoints);
10740
- const session = AdapterFactory.createRenderSession(options.target, {
11369
+ const session = AdapterFactory.createRenderSession(target, {
10741
11370
  orientationBreakpoints: options.orientationBreakpoints,
10742
- printWithBreakpoints
11371
+ printWithBreakpoints,
11372
+ targetProfile: target === "tailwind" ? configuration.targetProfile : void 0,
11373
+ sourceBreakpoints: configuration.sourceBreakpoints
10743
11374
  });
10744
11375
  const render = new RenderProjectStage(session);
10745
11376
  const pipeline = new MigrationPipeline(
@@ -10756,6 +11387,9 @@ async function runCli(argv, output = processOutput, dependencies = {}) {
10756
11387
  outputPath: destination,
10757
11388
  options: {
10758
11389
  mode,
11390
+ targetProfile: target === "tailwind" ? configuration.targetProfile : void 0,
11391
+ sourceBreakpoints: configuration.sourceBreakpoints,
11392
+ configurationSnapshots: configuration.snapshots,
10759
11393
  responsiveImages: options.responsiveImages,
10760
11394
  stylesheetPath,
10761
11395
  stylesheetPathInput: options.stylesheet,
@@ -10767,7 +11401,10 @@ async function runCli(argv, output = processOutput, dependencies = {}) {
10767
11401
  new TerminalPresenter().present(report, reportOutput);
10768
11402
  if (reportPath !== void 0) {
10769
11403
  await new JsonReportWriter().write(reportPath, report, {
10770
- protectedPaths: stylesheetPath === void 0 ? [] : [stylesheetPath]
11404
+ protectedPaths: [
11405
+ ...configuration.snapshots.map((snapshot) => snapshot.path),
11406
+ ...stylesheetPath === void 0 ? [] : [stylesheetPath]
11407
+ ]
10771
11408
  });
10772
11409
  }
10773
11410
  exitCode = resolveExitCode(report, options.allowUnresolved);