@nipe-solutions/flex-layout-codemod 2.0.0-beta.3 → 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.3",
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: [
@@ -55,7 +826,8 @@ var package_default = {
55
826
  "release:verify": "node scripts/release-artifact.mjs --verify-retained",
56
827
  "verify:website-assets": "node scripts/verify-website-assets.mjs",
57
828
  "release:version": "changeset version && npm install --package-lock-only --ignore-scripts --no-audit --no-fund",
58
- prepare: "husky"
829
+ prepare: "husky",
830
+ check: "npm run verify && npm run verify:website"
59
831
  },
60
832
  repository: {
61
833
  type: "git",
@@ -80,6 +852,7 @@ var package_default = {
80
852
  commander: "^15.0.0",
81
853
  "fs-extra": "^11.4.0",
82
854
  ignore: "5.2.4",
855
+ postcss: "^8.5.26",
83
856
  winston: "^3.19.0"
84
857
  },
85
858
  bin: {
@@ -107,7 +880,6 @@ var package_default = {
107
880
  jsdom: "^30.0.1",
108
881
  "lint-staged": "^17.4.1",
109
882
  "mock-fs": "^5.5.0",
110
- postcss: "^8.5.26",
111
883
  prettier: "^3.9.6",
112
884
  react: "^19.2.8",
113
885
  "react-dom": "^19.2.8",
@@ -220,130 +992,6 @@ function isKnownBreakpoint(value) {
220
992
  return breakpointNames.has(value);
221
993
  }
222
994
 
223
- // src/util/sha-256.ts
224
- var ROUND_CONSTANTS = Object.freeze([
225
- 1116352408,
226
- 1899447441,
227
- 3049323471,
228
- 3921009573,
229
- 961987163,
230
- 1508970993,
231
- 2453635748,
232
- 2870763221,
233
- 3624381080,
234
- 310598401,
235
- 607225278,
236
- 1426881987,
237
- 1925078388,
238
- 2162078206,
239
- 2614888103,
240
- 3248222580,
241
- 3835390401,
242
- 4022224774,
243
- 264347078,
244
- 604807628,
245
- 770255983,
246
- 1249150122,
247
- 1555081692,
248
- 1996064986,
249
- 2554220882,
250
- 2821834349,
251
- 2952996808,
252
- 3210313671,
253
- 3336571891,
254
- 3584528711,
255
- 113926993,
256
- 338241895,
257
- 666307205,
258
- 773529912,
259
- 1294757372,
260
- 1396182291,
261
- 1695183700,
262
- 1986661051,
263
- 2177026350,
264
- 2456956037,
265
- 2730485921,
266
- 2820302411,
267
- 3259730800,
268
- 3345764771,
269
- 3516065817,
270
- 3600352804,
271
- 4094571909,
272
- 275423344,
273
- 430227734,
274
- 506948616,
275
- 659060556,
276
- 883997877,
277
- 958139571,
278
- 1322822218,
279
- 1537002063,
280
- 1747873779,
281
- 1955562222,
282
- 2024104815,
283
- 2227730452,
284
- 2361852424,
285
- 2428436474,
286
- 2756734187,
287
- 3204031479,
288
- 3329325298
289
- ]);
290
- var INITIAL_HASH = Object.freeze([
291
- 1779033703,
292
- 3144134277,
293
- 1013904242,
294
- 2773480762,
295
- 1359893119,
296
- 2600822924,
297
- 528734635,
298
- 1541459225
299
- ]);
300
- function sha256(value) {
301
- const source = new TextEncoder().encode(value);
302
- const paddedLength = Math.ceil((source.length + 9) / 64) * 64;
303
- const bytes = new Uint8Array(paddedLength);
304
- bytes.set(source);
305
- bytes[source.length] = 128;
306
- const bitLength = source.length * 8;
307
- const view = new DataView(bytes.buffer);
308
- view.setUint32(paddedLength - 8, Math.floor(bitLength / 4294967296));
309
- view.setUint32(paddedLength - 4, bitLength >>> 0);
310
- const hash = [...INITIAL_HASH];
311
- const schedule = new Uint32Array(64);
312
- for (let offset = 0; offset < bytes.length; offset += 64) {
313
- for (let index = 0; index < 16; index += 1) schedule[index] = view.getUint32(offset + index * 4);
314
- for (let index = 16; index < schedule.length; index += 1) {
315
- const earlier = schedule[index - 15];
316
- const recent = schedule[index - 2];
317
- const sigma0 = rotateRight(earlier, 7) ^ rotateRight(earlier, 18) ^ earlier >>> 3;
318
- const sigma1 = rotateRight(recent, 17) ^ rotateRight(recent, 19) ^ recent >>> 10;
319
- schedule[index] = schedule[index - 16] + sigma0 + schedule[index - 7] + sigma1 >>> 0;
320
- }
321
- let [a, b, c, d, e, f, g, h] = hash;
322
- for (let index = 0; index < schedule.length; index += 1) {
323
- const sum1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
324
- const choice = e & f ^ ~e & g;
325
- const temporary1 = h + sum1 + choice + ROUND_CONSTANTS[index] + schedule[index] >>> 0;
326
- const sum0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
327
- const majority = a & b ^ a & c ^ b & c;
328
- const temporary2 = sum0 + majority >>> 0;
329
- h = g;
330
- g = f;
331
- f = e;
332
- e = d + temporary1 >>> 0;
333
- d = c;
334
- c = b;
335
- b = a;
336
- a = temporary1 + temporary2 >>> 0;
337
- }
338
- const round = [a, b, c, d, e, f, g, h];
339
- for (let index = 0; index < hash.length; index += 1) hash[index] = hash[index] + round[index] >>> 0;
340
- }
341
- return hash.map((word) => word.toString(16).padStart(8, "0")).join("");
342
- }
343
- function rotateRight(value, count) {
344
- return value >>> count | value << 32 - count;
345
- }
346
-
347
995
  // src/adapter/css/css-invariant.error.ts
348
996
  var CssInvariantError = class extends Error {
349
997
  constructor(message) {
@@ -427,8 +1075,8 @@ function canonicalIdentity(family, declarations, context) {
427
1075
  });
428
1076
  }
429
1077
  var CssArtifactRegistry = class {
430
- constructor(digest = sha256) {
431
- this.digest = digest;
1078
+ constructor(digest2 = sha256) {
1079
+ this.digest = digest2;
432
1080
  }
433
1081
  rulesByCanonicalIdentity = /* @__PURE__ */ new Map();
434
1082
  canonicalIdentityByDigest = /* @__PURE__ */ new Map();
@@ -650,7 +1298,8 @@ var CssRenderer = class {
650
1298
  eligibility(input) {
651
1299
  const family = this.cssFamily(input);
652
1300
  if (family === void 0) return targetUnsupported(input);
653
- 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);
654
1303
  return void 0;
655
1304
  }
656
1305
  render(plan, _context) {
@@ -713,18 +1362,119 @@ var CssRenderer = class {
713
1362
  }
714
1363
  };
715
1364
 
716
- // src/migrator/migration-application.error.ts
717
- var MigrationApplicationError = class extends Error {
718
- constructor(code, message, paths = [], options) {
719
- super(message, options);
720
- this.code = code;
721
- this.paths = paths;
722
- this.name = "MigrationApplicationError";
723
- this.paths = Object.freeze([...paths]);
724
- 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]}`;
725
1460
  }
726
- recoveryFailures;
727
- };
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
+ }
728
1478
 
729
1479
  // src/edit/html-attribute-value.ts
730
1480
  var htmlSourceWhitespace = /[\t\n\f\r ]/u;
@@ -898,7 +1648,10 @@ var BreakpointCatalog = class {
898
1648
  this.configuredDefinitions = new Map([
899
1649
  ...definitionsByAlias,
900
1650
  ...config.orientationBreakpoints ? orientations.map((definition) => [definition.alias, definition]) : [],
901
- ...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
+ )
902
1655
  ]);
903
1656
  }
904
1657
  classify(alias) {
@@ -2719,18 +3472,25 @@ var TailwindCandidateClassifier = class {
2719
3472
 
2720
3473
  // src/evidence/tailwind-source-property.evidence.ts
2721
3474
  var TailwindSourcePropertyEvidence = class {
3475
+ constructor(profile) {
3476
+ this.profile = profile;
3477
+ }
2722
3478
  classifier = new TailwindCandidateClassifier();
2723
3479
  styleEncoder = new TailwindArbitraryPropertyEncoder();
2724
3480
  classifyClassToken(token) {
2725
- 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);
2726
3486
  if (classification.status === "unverified") return classification;
2727
- const display = describeTailwindDisplay(token);
3487
+ const display = describeTailwindDisplay(normalized);
2728
3488
  return {
2729
3489
  status: "verified",
2730
3490
  evidence: {
2731
3491
  source: token,
2732
3492
  properties: classification.descriptor.cssProperties,
2733
- important: classification.descriptor.important,
3493
+ important: classification.descriptor.important || this.profile?.important.value === "important",
2734
3494
  activation: classification.descriptor.activation,
2735
3495
  ...display === void 0 ? {} : { display: display.utility }
2736
3496
  }
@@ -3314,15 +4074,29 @@ function compatibleVisibilityClasses(plan, existingClassNames) {
3314
4074
  var TailwindRenderer = class {
3315
4075
  target = "tailwind";
3316
4076
  breakpointConfig;
3317
- sourcePropertyEvidence = new TailwindSourcePropertyEvidence();
4077
+ sourcePropertyEvidence;
4078
+ targetProfile;
3318
4079
  responsiveEmitter = new ResponsiveVariantEmitter();
3319
4080
  visibilityEmitter = new VisibilityEmitter();
3320
4081
  extendedEmitter = new ExtendedResponsiveEmitter();
3321
4082
  gridRenderer = new TailwindGridRenderer();
3322
4083
  constructor(config = { orientationBreakpoints: false }) {
4084
+ this.targetProfile = config.targetProfile ?? resolveTailwindTargetProfile();
3323
4085
  this.breakpointConfig = Object.freeze({ ...config });
4086
+ this.sourcePropertyEvidence = new TailwindSourcePropertyEvidence(this.targetProfile);
3324
4087
  }
3325
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
+ };
3326
4100
  if (input.binding !== "property") {
3327
4101
  if (!sharedDirectives.has(input.directive) && !visibilityDirectives.has(input.directive) && !extendedDirectives.has(input.directive) && !gridDirectives.has(input.directive)) {
3328
4102
  return {
@@ -3336,7 +4110,12 @@ var TailwindRenderer = class {
3336
4110
  }
3337
4111
  return void 0;
3338
4112
  }
3339
- 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) {
3340
4119
  const inputFamily = directiveFamily(plan.input.directive);
3341
4120
  if (inputFamily !== plan.family) {
3342
4121
  throw new MigrationApplicationError(
@@ -3414,7 +4193,15 @@ var TailwindRenderer = class {
3414
4193
  };
3415
4194
  }
3416
4195
  resolveConflicts(plans, context) {
3417
- 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);
3418
4205
  }
3419
4206
  record(_plans) {
3420
4207
  }
@@ -3448,6 +4235,10 @@ var TailwindRenderer = class {
3448
4235
  } : plan
3449
4236
  );
3450
4237
  }
4238
+ unprefix(token) {
4239
+ const prefix = this.targetProfile.prefix.value;
4240
+ return prefix && token.startsWith(`${prefix}:`) ? token.slice(prefix.length + 1) : token;
4241
+ }
3451
4242
  decorate(classNames, plan) {
3452
4243
  return plan.activations.flatMap(
3453
4244
  (planActivation) => planActivation.kind === "base" ? classNames : classNames.flatMap((className) => this.responsiveEmitter.emit(planActivation.definition, className))
@@ -3490,7 +4281,7 @@ var TailwindRenderer = class {
3490
4281
  return this.extendedEmitter.emitClass({
3491
4282
  input: plan.input,
3492
4283
  activation: itemActivation,
3493
- value: { tokens: state.tokens.map((token) => token.source) }
4284
+ value: { tokens: state.tokens.map((token) => this.unprefix(token.source)) }
3494
4285
  });
3495
4286
  })
3496
4287
  )
@@ -3616,7 +4407,7 @@ var AdapterFactory = class {
3616
4407
  };
3617
4408
 
3618
4409
  // src/pipeline/analyze/analyze-project.stage.ts
3619
- import { readFile } from "fs/promises";
4410
+ import { readFile as readFile2 } from "fs/promises";
3620
4411
 
3621
4412
  // src/analyzer/flex-layout-attribute.analyzer.ts
3622
4413
  var responsiveOnlyDirectives = /* @__PURE__ */ new Set(["class", "ngClass", "style", "ngStyle"]);
@@ -3802,10 +4593,10 @@ var AngularTemplateParser = class {
3802
4593
  };
3803
4594
 
3804
4595
  // src/pipeline/analyzed-project.ts
3805
- import * as path2 from "path";
4596
+ import * as path4 from "path";
3806
4597
 
3807
4598
  // src/pipeline/project-manifest.ts
3808
- import * as path from "path";
4599
+ import * as path3 from "path";
3809
4600
  function migrationInvocation(invocation) {
3810
4601
  return freezeMigrationInvocation({
3811
4602
  inputPath: invocation.inputPath,
@@ -3841,7 +4632,7 @@ function freezeMigrationInvocation(invocation) {
3841
4632
  });
3842
4633
  }
3843
4634
  function normalizedAbsolutePath(value) {
3844
- return path.normalize(path.resolve(value));
4635
+ return path3.normalize(path3.resolve(value));
3845
4636
  }
3846
4637
 
3847
4638
  // src/pipeline/analyzed-project.ts
@@ -3936,7 +4727,7 @@ function freezeLocatedInput(input) {
3936
4727
  });
3937
4728
  }
3938
4729
  function normalizedAbsolutePath2(value) {
3939
- return path2.normalize(path2.resolve(value));
4730
+ return path4.normalize(path4.resolve(value));
3940
4731
  }
3941
4732
  function sequenceInvariant(paths = []) {
3942
4733
  return internalInvariant(
@@ -3950,7 +4741,7 @@ function internalInvariant(message, paths = []) {
3950
4741
 
3951
4742
  // src/pipeline/analyze/analyze-project.stage.ts
3952
4743
  var nodeSourceReader = Object.freeze({
3953
- read: (path18) => readFile(path18, "utf8")
4744
+ read: (path19) => readFile2(path19, "utf8")
3954
4745
  });
3955
4746
  var AnalyzeProjectStage = class {
3956
4747
  constructor(sourceReader = nodeSourceReader, parser = new AngularTemplateParser(), analyzer = new TemplateAnalyzer()) {
@@ -3997,13 +4788,13 @@ function runtimeArtifact(context, artifact) {
3997
4788
  [artifact.path]
3998
4789
  );
3999
4790
  }
4000
- function identity(stat4) {
4791
+ function identity2(stat4) {
4001
4792
  return { dev: String(stat4.dev), ino: String(stat4.ino) };
4002
4793
  }
4003
4794
  function fileMode(stat4) {
4004
4795
  return Number(stat4.mode) & 4095;
4005
4796
  }
4006
- function sameIdentity(left, right) {
4797
+ function sameIdentity2(left, right) {
4007
4798
  return left.dev === right.dev && left.ino === right.ino;
4008
4799
  }
4009
4800
  function sameArtifactState(left, right) {
@@ -4015,14 +4806,14 @@ function required(value) {
4015
4806
  if (value === void 0) throw new Error("Missing transaction state.");
4016
4807
  return value;
4017
4808
  }
4018
- function isEnoent(error) {
4809
+ function isEnoent2(error) {
4019
4810
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
4020
4811
  }
4021
4812
  function isDirectoryNotEmpty(error) {
4022
4813
  return typeof error === "object" && error !== null && "code" in error && (error.code === "ENOTEMPTY" || error.code === "EEXIST");
4023
4814
  }
4024
- function pathDepth(path18) {
4025
- return path18.split(/[\\/]/u).filter(Boolean).length;
4815
+ function pathDepth(path19) {
4816
+ return path19.split(/[\\/]/u).filter(Boolean).length;
4026
4817
  }
4027
4818
  function recoveryOutcome(paths, failures) {
4028
4819
  return { paths: sortedUnique(paths), failures: Object.freeze([...failures]) };
@@ -4082,13 +4873,13 @@ var FileSystemCleanupUnit = class {
4082
4873
  try {
4083
4874
  await this.port.assertNamespace(item);
4084
4875
  const before = await this.port.lstat(owned.path);
4085
- if (!sameIdentity(identity(before), owned.identity) || before.isSymbolicLink() || !before.isFile()) {
4876
+ if (!sameIdentity2(identity2(before), owned.identity) || before.isSymbolicLink() || !before.isFile()) {
4086
4877
  paths.add(owned.publicPath);
4087
4878
  failures.push(new Error("Invocation-owned file identity could not be confirmed."));
4088
4879
  return;
4089
4880
  }
4090
4881
  } catch (error) {
4091
- if (isEnoent(error)) {
4882
+ if (isEnoent2(error)) {
4092
4883
  this.port.journal.markOwnedFileAbsent(item, owned.path);
4093
4884
  return;
4094
4885
  }
@@ -4099,13 +4890,13 @@ var FileSystemCleanupUnit = class {
4099
4890
  try {
4100
4891
  await this.port.unlink(owned.path);
4101
4892
  } catch (error) {
4102
- if (!isEnoent(error)) failures.push(error);
4893
+ if (!isEnoent2(error)) failures.push(error);
4103
4894
  }
4104
4895
  try {
4105
4896
  await this.port.lstat(owned.path);
4106
4897
  paths.add(owned.publicPath);
4107
4898
  } catch (error) {
4108
- if (isEnoent(error)) this.port.journal.markOwnedFileAbsent(item, owned.path);
4899
+ if (isEnoent2(error)) this.port.journal.markOwnedFileAbsent(item, owned.path);
4109
4900
  else {
4110
4901
  paths.add(owned.publicPath);
4111
4902
  failures.push(error);
@@ -4121,13 +4912,13 @@ var FileSystemCleanupUnit = class {
4121
4912
  }
4122
4913
  try {
4123
4914
  const namespaceStat = await this.port.lstat(namespace.path);
4124
- if (!sameIdentity(identity(namespaceStat), namespace.identity) || namespaceStat.isSymbolicLink() || !namespaceStat.isDirectory()) {
4915
+ if (!sameIdentity2(identity2(namespaceStat), namespace.identity) || namespaceStat.isSymbolicLink() || !namespaceStat.isDirectory()) {
4125
4916
  paths.add(namespace.publicPath);
4126
4917
  failures.push(new Error("Invocation namespace identity could not be confirmed."));
4127
4918
  return;
4128
4919
  }
4129
4920
  } catch (error) {
4130
- if (isEnoent(error)) {
4921
+ if (isEnoent2(error)) {
4131
4922
  this.port.journal.markNamespaceAbsent(item);
4132
4923
  return;
4133
4924
  }
@@ -4138,13 +4929,13 @@ var FileSystemCleanupUnit = class {
4138
4929
  try {
4139
4930
  await this.port.rmdir(namespace.path);
4140
4931
  } catch (error) {
4141
- if (!isDirectoryNotEmpty(error) && !isEnoent(error)) failures.push(error);
4932
+ if (!isDirectoryNotEmpty(error) && !isEnoent2(error)) failures.push(error);
4142
4933
  }
4143
4934
  try {
4144
4935
  await this.port.lstat(namespace.path);
4145
4936
  paths.add(namespace.publicPath);
4146
4937
  } catch (error) {
4147
- if (isEnoent(error)) this.port.journal.markNamespaceAbsent(item);
4938
+ if (isEnoent2(error)) this.port.journal.markNamespaceAbsent(item);
4148
4939
  else {
4149
4940
  paths.add(namespace.publicPath);
4150
4941
  failures.push(error);
@@ -4170,7 +4961,7 @@ var FileSystemCleanupUnit = class {
4170
4961
  try {
4171
4962
  before = await this.port.lstat(directory.path);
4172
4963
  } catch (error) {
4173
- if (isEnoent(error)) {
4964
+ if (isEnoent2(error)) {
4174
4965
  this.port.journal.markCreatedDirectoryAbsent(directory.path);
4175
4966
  continue;
4176
4967
  }
@@ -4178,7 +4969,7 @@ var FileSystemCleanupUnit = class {
4178
4969
  failures.push(error);
4179
4970
  continue;
4180
4971
  }
4181
- if (!sameIdentity(identity(before), directory.identity) || before.isSymbolicLink() || !before.isDirectory()) {
4972
+ if (!sameIdentity2(identity2(before), directory.identity) || before.isSymbolicLink() || !before.isDirectory()) {
4182
4973
  for (const publicPath of directory.publicPaths) paths.add(publicPath);
4183
4974
  continue;
4184
4975
  }
@@ -4193,17 +4984,17 @@ var FileSystemCleanupUnit = class {
4193
4984
  after = await this.port.lstatOrAbsent(directory.path);
4194
4985
  } catch (error) {
4195
4986
  for (const publicPath of directory.publicPaths) paths.add(publicPath);
4196
- if (removalFailure !== void 0 && !isEnoent(removalFailure)) failures.push(removalFailure);
4987
+ if (removalFailure !== void 0 && !isEnoent2(removalFailure)) failures.push(removalFailure);
4197
4988
  failures.push(error);
4198
4989
  continue;
4199
4990
  }
4200
4991
  if (after === "absent") {
4201
4992
  this.port.journal.markCreatedDirectoryAbsent(directory.path);
4202
- if (removalFailure !== void 0 && !isEnoent(removalFailure)) failures.push(removalFailure);
4993
+ if (removalFailure !== void 0 && !isEnoent2(removalFailure)) failures.push(removalFailure);
4203
4994
  continue;
4204
4995
  }
4205
4996
  for (const publicPath of directory.publicPaths) paths.add(publicPath);
4206
- if (removalFailure !== void 0 && !isEnoent(removalFailure)) failures.push(removalFailure);
4997
+ if (removalFailure !== void 0 && !isEnoent2(removalFailure)) failures.push(removalFailure);
4207
4998
  }
4208
4999
  }
4209
5000
  async collectUnconfirmedPaths(paths, failures) {
@@ -4211,7 +5002,7 @@ var FileSystemCleanupUnit = class {
4211
5002
  try {
4212
5003
  await this.port.lstat(candidate);
4213
5004
  } catch (error) {
4214
- if (isEnoent(error)) continue;
5005
+ if (isEnoent2(error)) continue;
4215
5006
  failures.push(error);
4216
5007
  }
4217
5008
  for (const publicPath of publicPaths) paths.add(publicPath);
@@ -4229,7 +5020,7 @@ var FileSystemCleanupUnit = class {
4229
5020
  if (!sameArtifactState(observed, item.artifact.original)) return false;
4230
5021
  if (observed.status === "absent") return true;
4231
5022
  return [item.originalIdentity, item.restoredIdentity].some(
4232
- (expected) => expected !== void 0 && sameIdentity(observed.identity, expected)
5023
+ (expected) => expected !== void 0 && sameIdentity2(observed.identity, expected)
4233
5024
  );
4234
5025
  }
4235
5026
  };
@@ -4282,7 +5073,7 @@ var FileSystemCommitUnit = class {
4282
5073
  }
4283
5074
  async captureOriginal(item, signal) {
4284
5075
  const firstCapture = await this.port.observePublic(item);
4285
- 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)) {
4286
5077
  throw this.port.concurrentModification(item.artifact.path);
4287
5078
  }
4288
5079
  const backup = await this.port.createBackupFile(item, firstCapture.contents, signal, firstCapture.mode);
@@ -4290,7 +5081,7 @@ var FileSystemCommitUnit = class {
4290
5081
  throw this.port.ownershipFailure(item.artifact.path);
4291
5082
  }
4292
5083
  const secondCapture = await this.port.observePublic(item);
4293
- 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) {
4294
5085
  throw this.port.concurrentModification(item.artifact.path);
4295
5086
  }
4296
5087
  await this.quarantineOriginal(item, firstCapture);
@@ -4299,7 +5090,7 @@ var FileSystemCommitUnit = class {
4299
5090
  await this.port.assertParentChain(item);
4300
5091
  await this.port.assertNamespace(item);
4301
5092
  const immediatelyBefore = await this.port.observePublic(item);
4302
- 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) {
4303
5094
  throw this.port.concurrentModification(item.artifact.path);
4304
5095
  }
4305
5096
  let quarantine = this.port.journal.addQuarantine(
@@ -4315,9 +5106,9 @@ var FileSystemCommitUnit = class {
4315
5106
  }
4316
5107
  const quarantinedStat = await this.port.lstatOrAbsent(quarantine.path);
4317
5108
  if (quarantinedStat !== "absent") {
4318
- quarantine = this.port.journal.confirmOwnedFile(item, quarantine.path, identity(quarantinedStat));
5109
+ quarantine = this.port.journal.confirmOwnedFile(item, quarantine.path, identity2(quarantinedStat));
4319
5110
  const quarantinedContents = await this.port.readOwnedFile(item, quarantine.path);
4320
- if (!sameIdentity(required(quarantine.identity), captured.identity) || quarantinedContents !== captured.contents) {
5111
+ if (!sameIdentity2(required(quarantine.identity), captured.identity) || quarantinedContents !== captured.contents) {
4321
5112
  this.port.journal.setOwnedFilePreserved(item, quarantine.path, true);
4322
5113
  await this.restorePreservedQuarantine(item, quarantine);
4323
5114
  throw this.port.concurrentModification(item.artifact.path, renameFailure);
@@ -4329,7 +5120,7 @@ var FileSystemCommitUnit = class {
4329
5120
  return;
4330
5121
  }
4331
5122
  const destination = await this.port.lstatOrAbsent(item.artifact.path);
4332
- if (renameFailure !== void 0 && destination !== "absent" && sameIdentity(identity(destination), captured.identity)) {
5123
+ if (renameFailure !== void 0 && destination !== "absent" && sameIdentity2(identity2(destination), captured.identity)) {
4333
5124
  throw renameFailure;
4334
5125
  }
4335
5126
  throw this.port.concurrentModification(item.artifact.path, renameFailure);
@@ -4346,7 +5137,7 @@ var FileSystemCommitUnit = class {
4346
5137
  linkFailure = error;
4347
5138
  }
4348
5139
  const destination = await this.port.lstatOrAbsent(item.artifact.path);
4349
- if (destination !== "absent" && stage.identity && sameIdentity(identity(destination), stage.identity)) {
5140
+ if (destination !== "absent" && stage.identity && sameIdentity2(identity2(destination), stage.identity)) {
4350
5141
  this.port.journal.recordInstalledIdentity(item, stage.identity);
4351
5142
  await this.port.assertParentChain(item);
4352
5143
  if (linkFailure !== void 0) throw linkFailure;
@@ -4365,7 +5156,7 @@ var FileSystemCommitUnit = class {
4365
5156
  this.port.journal.recordRecoveryFailure(error);
4366
5157
  }
4367
5158
  const destination = await this.port.lstatOrAbsent(item.artifact.path);
4368
- if (destination !== "absent" && sameIdentity(identity(destination), quarantineIdentity)) {
5159
+ if (destination !== "absent" && sameIdentity2(identity2(destination), quarantineIdentity)) {
4369
5160
  this.port.journal.setOwnedFilePreserved(item, quarantine.path, false);
4370
5161
  }
4371
5162
  }
@@ -4427,8 +5218,8 @@ var FileSystemRollbackUnit = class {
4427
5218
  }
4428
5219
  if (current !== "unknown" && this.isConfirmedOriginal(item, current)) return;
4429
5220
  if (current !== "unknown" && current.status === "present") {
4430
- if (!item.installedIdentity || !sameIdentity(current.identity, item.installedIdentity) || item.artifact.proposed.status !== "present" || current.contents !== item.artifact.proposed.contents) {
4431
- 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)) {
4432
5223
  this.port.journal.setOwnedFilePreserved(item, item.stage.path, true);
4433
5224
  }
4434
5225
  return;
@@ -4450,7 +5241,7 @@ var FileSystemRollbackUnit = class {
4450
5241
  failures.push(error);
4451
5242
  }
4452
5243
  const restored = await this.port.lstatOrAbsent(item.artifact.path);
4453
- 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)) {
4454
5245
  this.port.journal.recordRestoredIdentity(item, backup.identity);
4455
5246
  }
4456
5247
  } catch (error) {
@@ -4466,7 +5257,7 @@ var FileSystemRollbackUnit = class {
4466
5257
  await this.port.assertParentChain(item);
4467
5258
  await this.port.assertNamespace(item);
4468
5259
  const before = await this.port.observePublic(item);
4469
- 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) {
4470
5261
  return false;
4471
5262
  }
4472
5263
  await this.port.assertExpectedAbsent(quarantine.path, item.artifact.path);
@@ -4477,9 +5268,9 @@ var FileSystemRollbackUnit = class {
4477
5268
  }
4478
5269
  const quarantined = await this.port.lstatOrAbsent(quarantine.path);
4479
5270
  if (quarantined === "absent") return false;
4480
- quarantine = this.port.journal.confirmOwnedFile(item, quarantine.path, identity(quarantined));
5271
+ quarantine = this.port.journal.confirmOwnedFile(item, quarantine.path, identity2(quarantined));
4481
5272
  this.port.journal.setOwnedFilePreserved(item, quarantine.path, true);
4482
- if (!sameIdentity(required(quarantine.identity), current.identity)) {
5273
+ if (!sameIdentity2(required(quarantine.identity), current.identity)) {
4483
5274
  await this.restorePreservedQuarantine(item, quarantine, failures);
4484
5275
  return false;
4485
5276
  }
@@ -4503,7 +5294,7 @@ var FileSystemRollbackUnit = class {
4503
5294
  failures.push(error);
4504
5295
  }
4505
5296
  const destination = await this.port.lstatOrAbsent(item.artifact.path);
4506
- if (destination !== "absent" && sameIdentity(identity(destination), quarantineIdentity)) {
5297
+ if (destination !== "absent" && sameIdentity2(identity2(destination), quarantineIdentity)) {
4507
5298
  this.port.journal.setOwnedFilePreserved(item, quarantine.path, false);
4508
5299
  }
4509
5300
  }
@@ -4519,7 +5310,7 @@ var FileSystemRollbackUnit = class {
4519
5310
  if (!sameArtifactState(observed, item.artifact.original)) return false;
4520
5311
  if (observed.status === "absent") return true;
4521
5312
  return [item.originalIdentity, item.restoredIdentity].some(
4522
- (expected) => expected !== void 0 && sameIdentity(observed.identity, expected)
5313
+ (expected) => expected !== void 0 && sameIdentity2(observed.identity, expected)
4523
5314
  );
4524
5315
  }
4525
5316
  };
@@ -4594,7 +5385,7 @@ var FileSystemStagingUnit = class {
4594
5385
  if (createdStat.isSymbolicLink() || !createdStat.isDirectory()) {
4595
5386
  throw this.port.concurrentModification(item.artifact.path);
4596
5387
  }
4597
- this.port.journal.confirmCreatedDirectory(expectation.path, identity(createdStat));
5388
+ this.port.journal.confirmCreatedDirectory(expectation.path, identity2(createdStat));
4598
5389
  if (parentExpectation) await this.port.assertExpectedDirectory(parentExpectation, item);
4599
5390
  }
4600
5391
  await this.port.assertParentChain(item);
@@ -4614,7 +5405,7 @@ var FileSystemStagingUnit = class {
4614
5405
  if (namespaceStat.isSymbolicLink() || !namespaceStat.isDirectory()) {
4615
5406
  throw this.port.concurrentModification(item.artifact.path);
4616
5407
  }
4617
- this.port.journal.confirmNamespace(item, identity(namespaceStat));
5408
+ this.port.journal.confirmNamespace(item, identity2(namespaceStat));
4618
5409
  await this.port.assertParentChain(item);
4619
5410
  }
4620
5411
  };
@@ -4647,17 +5438,17 @@ var TransactionSignalRegistrar = class {
4647
5438
 
4648
5439
  // src/transaction/transaction-unit.session.ts
4649
5440
  import { constants } from "fs";
4650
- 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";
4651
5442
  import { dirname as dirname2, join as join4, resolve as resolve3 } from "path";
4652
5443
  var nodeTransactionOperations = {
4653
5444
  access,
4654
5445
  link,
4655
- lstat,
5446
+ lstat: lstat2,
4656
5447
  mkdir,
4657
5448
  open: (target, flags) => open(target, flags),
4658
5449
  rename,
4659
5450
  rmdir,
4660
- stat,
5451
+ stat: stat2,
4661
5452
  unlink
4662
5453
  };
4663
5454
  var TransactionUnitSession = class {
@@ -4678,30 +5469,30 @@ var TransactionUnitSession = class {
4678
5469
  const journal = Object.freeze({
4679
5470
  prepare: (artifacts) => this.prepare(artifacts),
4680
5471
  artifacts: () => Object.freeze(this.context.items.map(stagingArtifactView)),
4681
- createdDirectory: (path18) => createdDirectoryView(this.context.createdDirectories.get(path18)),
4682
- addCreatedDirectoryPublicPath: (path18, publicPath) => required(this.context.createdDirectories.get(path18)).publicPaths.add(publicPath),
4683
- recordUnconfirmedEntry: (path18, publicPath) => this.recordUnconfirmedEntry(path18, publicPath),
4684
- recordCreatedDirectory: (path18, publicPath) => this.recordCreatedDirectory(path18, publicPath),
4685
- confirmCreatedDirectory: (path18, expected) => {
4686
- 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;
4687
5478
  },
4688
- recordNamespace: (item, path18) => this.recordNamespace(item.artifact, path18),
5479
+ recordNamespace: (item, path19) => this.recordNamespace(item.artifact, path19),
4689
5480
  confirmNamespace: (item, expected) => {
4690
5481
  required(this.runtimeArtifact(item.artifact).namespace).identity = expected;
4691
5482
  }
4692
5483
  });
4693
5484
  return Object.freeze({
4694
5485
  journal,
4695
- assertDirectoryExpectation: (path18, expected, publicPath) => this.assertDirectoryExpectation(path18, expected, publicPath),
4696
- assertDirectoryIdentity: (path18, expected, publicPath) => this.assertDirectoryIdentity(path18, expected, publicPath),
4697
- 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),
4698
5489
  assertExpectedDirectory: (expectation, item) => this.assertExpectedDirectory(expectation, this.runtimeArtifact(item.artifact), this.context),
4699
5490
  assertNotInterrupted: (signal) => this.assertNotInterrupted(signal),
4700
5491
  assertParentChain: (item) => this.assertParentChain(this.runtimeArtifact(item.artifact), this.context),
4701
5492
  concurrentModification: (publicPath, cause) => this.concurrentModification(publicPath, cause),
4702
5493
  createStageFile: (item, contents, signal, mode) => this.createStageFile(this.runtimeArtifact(item.artifact), contents, signal, mode),
4703
- lstat: (path18) => this.operations.lstat(path18),
4704
- mkdir: (path18, options) => this.operations.mkdir(path18, options),
5494
+ lstat: (path19) => this.operations.lstat(path19),
5495
+ mkdir: (path19, options) => this.operations.mkdir(path19, options),
4705
5496
  readOwnedFile: (item, ownedPath) => {
4706
5497
  const runtime = this.runtimeArtifact(item.artifact);
4707
5498
  return this.readOwnedFile(runtime, this.ownedFile(runtime, ownedPath));
@@ -4713,7 +5504,7 @@ var TransactionUnitSession = class {
4713
5504
  const journal = Object.freeze({
4714
5505
  artifact: (artifact) => commitArtifactView(this.runtimeArtifact(artifact)),
4715
5506
  artifacts: () => Object.freeze(this.context.items.map(commitArtifactView)),
4716
- addQuarantine: (item, path18) => ownedFileView(this.addQuarantine(this.runtimeArtifact(item.artifact), path18)),
5507
+ addQuarantine: (item, path19) => ownedFileView(this.addQuarantine(this.runtimeArtifact(item.artifact), path19)),
4717
5508
  confirmOwnedFile: (item, ownedPath, expected) => ownedFileView(this.confirmOwnedFile(this.runtimeArtifact(item.artifact), ownedPath, expected)),
4718
5509
  setOwnedFilePreserved: (item, ownedPath, preserve) => {
4719
5510
  this.ownedFile(this.runtimeArtifact(item.artifact), ownedPath).preserve = preserve;
@@ -4725,7 +5516,7 @@ var TransactionUnitSession = class {
4725
5516
  });
4726
5517
  return Object.freeze({
4727
5518
  journal,
4728
- assertExpectedAbsent: (path18, publicPath) => this.assertExpectedAbsent(path18, publicPath),
5519
+ assertExpectedAbsent: (path19, publicPath) => this.assertExpectedAbsent(path19, publicPath),
4729
5520
  assertNamespace: (item) => this.assertNamespace(this.runtimeArtifact(item.artifact)),
4730
5521
  assertNotInterrupted: (signal) => this.assertNotInterrupted(signal),
4731
5522
  assertOwnedIdentity: (item, ownedPath) => {
@@ -4736,7 +5527,7 @@ var TransactionUnitSession = class {
4736
5527
  concurrentModification: (publicPath, cause) => this.concurrentModification(publicPath, cause),
4737
5528
  createBackupFile: (item, contents, signal, mode) => this.createBackupFile(this.runtimeArtifact(item.artifact), contents, signal, mode),
4738
5529
  link: (existingPath, newPath) => this.operations.link(existingPath, newPath),
4739
- lstatOrAbsent: (path18) => this.lstatOrAbsent(path18),
5530
+ lstatOrAbsent: (path19) => this.lstatOrAbsent(path19),
4740
5531
  observePublic: (item) => this.observePublic(this.runtimeArtifact(item.artifact), this.context),
4741
5532
  ownershipFailure: (publicPath) => this.ownershipFailure(publicPath),
4742
5533
  readOwnedFile: (item, ownedPath) => {
@@ -4751,7 +5542,7 @@ var TransactionUnitSession = class {
4751
5542
  artifact: (artifact) => rollbackArtifactView(this.runtimeArtifact(artifact)),
4752
5543
  artifacts: () => Object.freeze(this.context.items.map(rollbackArtifactView)),
4753
5544
  recoveryFailures: () => Object.freeze([...this.context.recoveryFailures]),
4754
- addQuarantine: (item, path18) => ownedFileView(this.addQuarantine(this.runtimeArtifact(item.artifact), path18)),
5545
+ addQuarantine: (item, path19) => ownedFileView(this.addQuarantine(this.runtimeArtifact(item.artifact), path19)),
4755
5546
  confirmOwnedFile: (item, ownedPath, expected) => ownedFileView(this.confirmOwnedFile(this.runtimeArtifact(item.artifact), ownedPath, expected)),
4756
5547
  setOwnedFilePreserved: (item, ownedPath, preserve) => {
4757
5548
  this.ownedFile(this.runtimeArtifact(item.artifact), ownedPath).preserve = preserve;
@@ -4765,11 +5556,11 @@ var TransactionUnitSession = class {
4765
5556
  });
4766
5557
  return Object.freeze({
4767
5558
  journal,
4768
- assertExpectedAbsent: (path18, publicPath) => this.assertExpectedAbsent(path18, publicPath),
5559
+ assertExpectedAbsent: (path19, publicPath) => this.assertExpectedAbsent(path19, publicPath),
4769
5560
  assertNamespace: (item) => this.assertNamespace(this.runtimeArtifact(item.artifact)),
4770
5561
  assertParentChain: (item) => this.assertParentChain(this.runtimeArtifact(item.artifact), this.context),
4771
5562
  link: (existingPath, newPath) => this.operations.link(existingPath, newPath),
4772
- lstatOrAbsent: (path18) => this.lstatOrAbsent(path18),
5563
+ lstatOrAbsent: (path19) => this.lstatOrAbsent(path19),
4773
5564
  observePublic: (item) => this.observePublic(this.runtimeArtifact(item.artifact), this.context),
4774
5565
  readOwnedFile: (item, ownedPath) => {
4775
5566
  const runtime = this.runtimeArtifact(item.artifact);
@@ -4791,8 +5582,8 @@ var TransactionUnitSession = class {
4791
5582
  markNamespaceAbsent: (item) => {
4792
5583
  required(this.runtimeArtifact(item.artifact).namespace).exists = false;
4793
5584
  },
4794
- markCreatedDirectoryAbsent: (path18) => {
4795
- required(this.context.createdDirectories.get(path18)).exists = false;
5585
+ markCreatedDirectoryAbsent: (path19) => {
5586
+ required(this.context.createdDirectories.get(path19)).exists = false;
4796
5587
  this.context.ownershipChanged();
4797
5588
  },
4798
5589
  finishArtifactCleanup: () => this.context.ownershipChanged()
@@ -4802,11 +5593,11 @@ var TransactionUnitSession = class {
4802
5593
  assertNamespace: (item) => this.assertNamespace(this.runtimeArtifact(item.artifact)),
4803
5594
  closeReadHandles: (item, failures) => this.closeReadHandles(this.runtimeArtifact(item.artifact), failures),
4804
5595
  closeOpenHandle: (item, failures) => this.closeOpenHandle(this.runtimeArtifact(item.artifact), failures),
4805
- lstat: (path18) => this.operations.lstat(path18),
4806
- lstatOrAbsent: (path18) => this.lstatOrAbsent(path18),
5596
+ lstat: (path19) => this.operations.lstat(path19),
5597
+ lstatOrAbsent: (path19) => this.lstatOrAbsent(path19),
4807
5598
  observePublic: (item) => this.observePublic(this.runtimeArtifact(item.artifact), this.context),
4808
- rmdir: (path18) => this.operations.rmdir(path18),
4809
- unlink: (path18) => this.operations.unlink(path18)
5599
+ rmdir: (path19) => this.operations.rmdir(path19),
5600
+ unlink: (path19) => this.operations.unlink(path19)
4810
5601
  });
4811
5602
  }
4812
5603
  async prepareForPreflight(artifacts) {
@@ -4881,16 +5672,16 @@ var TransactionUnitSession = class {
4881
5672
  result2.push({
4882
5673
  path: candidate,
4883
5674
  original: {
4884
- identity: identity(candidateStat),
5675
+ identity: identity2(candidateStat),
4885
5676
  kind: "symbolic-link",
4886
- followedIdentity: identity(followed)
5677
+ followedIdentity: identity2(followed)
4887
5678
  }
4888
5679
  });
4889
5680
  } else {
4890
- result2.push({ path: candidate, original: { identity: identity(candidateStat), kind: "directory" } });
5681
+ result2.push({ path: candidate, original: { identity: identity2(candidateStat), kind: "directory" } });
4891
5682
  }
4892
5683
  } catch (error) {
4893
- if (!isEnoent(error)) throw error;
5684
+ if (!isEnoent2(error)) throw error;
4894
5685
  missing = true;
4895
5686
  result2.push({ path: candidate, original: "absent" });
4896
5687
  }
@@ -4915,9 +5706,9 @@ var TransactionUnitSession = class {
4915
5706
  await this.writeOwnedFile(item, owned, contents, signal, mode);
4916
5707
  return ownedFileView(owned);
4917
5708
  }
4918
- registerOwnedFile(item, path18) {
5709
+ registerOwnedFile(item, path19) {
4919
5710
  const owned = {
4920
- path: path18,
5711
+ path: path19,
4921
5712
  publicPath: item.artifact.path,
4922
5713
  exists: false,
4923
5714
  preserve: false
@@ -4932,7 +5723,7 @@ var TransactionUnitSession = class {
4932
5723
  item.openHandle = handle;
4933
5724
  this.context.ownershipChanged();
4934
5725
  if (mode !== void 0) await handle.chmod(mode);
4935
- owned.identity = identity(await handle.stat());
5726
+ owned.identity = identity2(await handle.stat());
4936
5727
  this.assertNotInterrupted(signal);
4937
5728
  await handle.writeFile(contents, "utf8");
4938
5729
  this.assertNotInterrupted(signal);
@@ -4946,48 +5737,48 @@ var TransactionUnitSession = class {
4946
5737
  runtimeArtifact(artifact) {
4947
5738
  return runtimeArtifact(this.context, artifact);
4948
5739
  }
4949
- ownedFile(item, path18) {
4950
- const owned = item.ownedFiles.find((candidate) => candidate.path === path18);
5740
+ ownedFile(item, path19) {
5741
+ const owned = item.ownedFiles.find((candidate) => candidate.path === path19);
4951
5742
  if (owned !== void 0) return owned;
4952
5743
  throw new MigrationApplicationError(
4953
5744
  "internal-invariant",
4954
- `Migration transaction journal contains an unknown invocation-owned file: ${path18}`,
5745
+ `Migration transaction journal contains an unknown invocation-owned file: ${path19}`,
4955
5746
  [item.artifact.path]
4956
5747
  );
4957
5748
  }
4958
- addQuarantine(item, path18) {
4959
- const quarantine = this.registerOwnedFile(item, path18);
5749
+ addQuarantine(item, path19) {
5750
+ const quarantine = this.registerOwnedFile(item, path19);
4960
5751
  item.quarantines.push(quarantine);
4961
5752
  return quarantine;
4962
5753
  }
4963
- confirmOwnedFile(item, path18, expected) {
4964
- const owned = this.ownedFile(item, path18);
5754
+ confirmOwnedFile(item, path19, expected) {
5755
+ const owned = this.ownedFile(item, path19);
4965
5756
  owned.exists = true;
4966
5757
  owned.identity = expected;
4967
5758
  return owned;
4968
5759
  }
4969
- recordNamespace(artifact, path18) {
5760
+ recordNamespace(artifact, path19) {
4970
5761
  const item = this.runtimeArtifact(artifact);
4971
- item.namespace = { path: path18, publicPath: item.artifact.path, exists: true };
5762
+ item.namespace = { path: path19, publicPath: item.artifact.path, exists: true };
4972
5763
  this.context.ownershipChanged();
4973
5764
  }
4974
- recordCreatedDirectory(path18, publicPath) {
4975
- this.context.createdDirectories.set(path18, {
4976
- path: path18,
5765
+ recordCreatedDirectory(path19, publicPath) {
5766
+ this.context.createdDirectories.set(path19, {
5767
+ path: path19,
4977
5768
  publicPaths: /* @__PURE__ */ new Set([publicPath]),
4978
5769
  exists: true
4979
5770
  });
4980
5771
  this.context.ownershipChanged();
4981
5772
  }
4982
- recordUnconfirmedEntry(path18, publicPath) {
4983
- 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();
4984
5775
  publicPaths.add(publicPath);
4985
- this.context.unconfirmedEntries.set(path18, publicPaths);
5776
+ this.context.unconfirmedEntries.set(path19, publicPaths);
4986
5777
  }
4987
5778
  unconfirmedEntryViews() {
4988
5779
  return Object.freeze(
4989
5780
  [...this.context.unconfirmedEntries].map(
4990
- ([path18, publicPaths]) => Object.freeze({ path: path18, publicPaths: Object.freeze([...publicPaths]) })
5781
+ ([path19, publicPaths]) => Object.freeze({ path: path19, publicPaths: Object.freeze([...publicPaths]) })
4991
5782
  )
4992
5783
  );
4993
5784
  }
@@ -5019,18 +5810,18 @@ var TransactionUnitSession = class {
5019
5810
  [item.artifact.path]
5020
5811
  );
5021
5812
  }
5022
- const beforeIdentity = identity(before);
5813
+ const beforeIdentity = identity2(before);
5023
5814
  const handle = await this.operations.open(item.artifact.path, "r");
5024
5815
  const contents = await this.readThroughHandle(item, handle, async () => {
5025
- const handleBefore = identity(await handle.stat());
5026
- 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);
5027
5818
  const read = await handle.readFile({ encoding: "utf8" });
5028
- const handleAfter = identity(await handle.stat());
5029
- 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);
5030
5821
  return read;
5031
5822
  });
5032
5823
  const after = await this.lstatOrAbsent(item.artifact.path);
5033
- if (after === "absent" || after.isSymbolicLink() || !after.isFile() || !sameIdentity(beforeIdentity, identity(after))) {
5824
+ if (after === "absent" || after.isSymbolicLink() || !after.isFile() || !sameIdentity2(beforeIdentity, identity2(after))) {
5034
5825
  throw this.concurrentModification(item.artifact.path);
5035
5826
  }
5036
5827
  await this.assertParentChain(item, context);
@@ -5041,11 +5832,11 @@ var TransactionUnitSession = class {
5041
5832
  const expected = required(owned.identity);
5042
5833
  const handle = await this.operations.open(owned.path, "r");
5043
5834
  const contents = await this.readThroughHandle(item, handle, async () => {
5044
- const before = identity(await handle.stat());
5045
- 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);
5046
5837
  const read = await handle.readFile({ encoding: "utf8" });
5047
- const after = identity(await handle.stat());
5048
- 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);
5049
5840
  return read;
5050
5841
  });
5051
5842
  await this.assertOwnedIdentity(item, owned);
@@ -5104,7 +5895,7 @@ var TransactionUnitSession = class {
5104
5895
  await this.assertNamespace(item);
5105
5896
  if (!owned.identity) throw this.ownershipFailure(owned.publicPath);
5106
5897
  const ownedStat = await this.operations.lstat(owned.path);
5107
- if (ownedStat.isSymbolicLink() || !ownedStat.isFile() || !sameIdentity(identity(ownedStat), owned.identity)) {
5898
+ if (ownedStat.isSymbolicLink() || !ownedStat.isFile() || !sameIdentity2(identity2(ownedStat), owned.identity)) {
5108
5899
  throw this.ownershipFailure(owned.publicPath);
5109
5900
  }
5110
5901
  await this.assertNamespace(item);
@@ -5113,7 +5904,7 @@ var TransactionUnitSession = class {
5113
5904
  const namespace = required(item.namespace);
5114
5905
  const expectedIdentity = required(namespace.identity);
5115
5906
  const namespaceStat = await this.operations.lstat(namespace.path);
5116
- if (namespaceStat.isSymbolicLink() || !namespaceStat.isDirectory() || !sameIdentity(identity(namespaceStat), expectedIdentity)) {
5907
+ if (namespaceStat.isSymbolicLink() || !namespaceStat.isDirectory() || !sameIdentity2(identity2(namespaceStat), expectedIdentity)) {
5117
5908
  throw this.ownershipFailure(namespace.publicPath);
5118
5909
  }
5119
5910
  }
@@ -5132,54 +5923,54 @@ var TransactionUnitSession = class {
5132
5923
  }
5133
5924
  await this.assertDirectoryExpectation(expectation.path, expectation.original, item.artifact.path);
5134
5925
  }
5135
- async assertDirectoryExpectation(path18, expected, publicPath) {
5926
+ async assertDirectoryExpectation(path19, expected, publicPath) {
5136
5927
  let pathStat;
5137
5928
  try {
5138
- pathStat = await this.operations.lstat(path18);
5929
+ pathStat = await this.operations.lstat(path19);
5139
5930
  } catch (error) {
5140
5931
  throw this.concurrentModification(publicPath, error);
5141
5932
  }
5142
5933
  const kind = pathStat.isSymbolicLink() ? "symbolic-link" : pathStat.isDirectory() ? "directory" : void 0;
5143
- if (kind !== expected.kind || !sameIdentity(identity(pathStat), expected.identity)) {
5934
+ if (kind !== expected.kind || !sameIdentity2(identity2(pathStat), expected.identity)) {
5144
5935
  throw this.concurrentModification(publicPath);
5145
5936
  }
5146
5937
  if (expected.kind === "symbolic-link") {
5147
5938
  let followed;
5148
5939
  try {
5149
- followed = await this.operations.stat(path18);
5940
+ followed = await this.operations.stat(path19);
5150
5941
  } catch (error) {
5151
5942
  throw this.concurrentModification(publicPath, error);
5152
5943
  }
5153
- if (!followed.isDirectory() || !sameIdentity(identity(followed), expected.followedIdentity)) {
5944
+ if (!followed.isDirectory() || !sameIdentity2(identity2(followed), expected.followedIdentity)) {
5154
5945
  throw this.concurrentModification(publicPath);
5155
5946
  }
5156
5947
  }
5157
5948
  }
5158
- async assertDirectoryIdentity(path18, expected, publicPath) {
5949
+ async assertDirectoryIdentity(path19, expected, publicPath) {
5159
5950
  let pathStat;
5160
5951
  try {
5161
- pathStat = await this.operations.lstat(path18);
5952
+ pathStat = await this.operations.lstat(path19);
5162
5953
  } catch (error) {
5163
5954
  throw this.concurrentModification(publicPath, error);
5164
5955
  }
5165
- if (pathStat.isSymbolicLink() || !pathStat.isDirectory() || !sameIdentity(identity(pathStat), expected)) {
5956
+ if (pathStat.isSymbolicLink() || !pathStat.isDirectory() || !sameIdentity2(identity2(pathStat), expected)) {
5166
5957
  throw this.concurrentModification(publicPath);
5167
5958
  }
5168
5959
  }
5169
- async assertExpectedAbsent(path18, publicPath) {
5960
+ async assertExpectedAbsent(path19, publicPath) {
5170
5961
  try {
5171
- await this.operations.lstat(path18);
5962
+ await this.operations.lstat(path19);
5172
5963
  } catch (error) {
5173
- if (isEnoent(error)) return;
5964
+ if (isEnoent2(error)) return;
5174
5965
  throw error;
5175
5966
  }
5176
5967
  throw this.concurrentModification(publicPath);
5177
5968
  }
5178
- async lstatOrAbsent(path18) {
5969
+ async lstatOrAbsent(path19) {
5179
5970
  try {
5180
- return await this.operations.lstat(path18);
5971
+ return await this.operations.lstat(path19);
5181
5972
  } catch (error) {
5182
- if (isEnoent(error)) return "absent";
5973
+ if (isEnoent2(error)) return "absent";
5183
5974
  throw error;
5184
5975
  }
5185
5976
  }
@@ -5213,9 +6004,9 @@ var TransactionUnitSession = class {
5213
6004
  );
5214
6005
  }
5215
6006
  };
5216
- function directoryChain(path18) {
6007
+ function directoryChain(path19) {
5217
6008
  const result2 = [];
5218
- let current = resolve3(path18);
6009
+ let current = resolve3(path19);
5219
6010
  while (true) {
5220
6011
  result2.unshift(current);
5221
6012
  const parent = dirname2(current);
@@ -5402,7 +6193,7 @@ var MigrationTransaction = class {
5402
6193
  }
5403
6194
  }
5404
6195
  rejectParseErrors(plan) {
5405
- 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);
5406
6197
  if (paths.length === 0) return;
5407
6198
  throw new MigrationApplicationError(
5408
6199
  "internal-invariant",
@@ -5521,6 +6312,13 @@ var ApplyProjectStage = class {
5521
6312
  );
5522
6313
  }
5523
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
+ }
5524
6322
  const hasParseError = plan.files.some((file) => file.results.some((result2) => result2.status === "parse-error"));
5525
6323
  if (this.mode === "plan") {
5526
6324
  if (!hasParseError) await this.transaction.preflight(plan);
@@ -5530,27 +6328,28 @@ var ApplyProjectStage = class {
5530
6328
  return appliedProject({ validated, application: { status: "skipped", reason: "parse-errors" } });
5531
6329
  }
5532
6330
  await this.transaction.preflight(plan);
6331
+ await assertConfigurationUnchanged(snapshots);
5533
6332
  if (plan.artifacts.length > 0) await this.transaction.apply(plan);
5534
6333
  return appliedProject({ validated, application: { status: "applied" } });
5535
6334
  }
5536
6335
  };
5537
6336
 
5538
6337
  // src/pipeline/discover/discover-project.stage.ts
5539
- import { readdir, stat as stat2 } from "fs/promises";
5540
- import * as path4 from "path";
6338
+ import { readdir, stat as stat3 } from "fs/promises";
6339
+ import * as path6 from "path";
5541
6340
 
5542
6341
  // src/lib/gitignore.helper.ts
5543
6342
  import fs from "fs-extra";
5544
6343
  import ignore from "ignore";
5545
- import path3 from "path";
6344
+ import path5 from "path";
5546
6345
  async function createGitIgnoreMatcher(root, displayRoot = root) {
5547
6346
  const matcher = ignore();
5548
- const gitignorePath = path3.join(root, ".gitignore");
6347
+ const gitignorePath = path5.join(root, ".gitignore");
5549
6348
  if (await fs.pathExists(gitignorePath)) {
5550
6349
  matcher.add(await fs.readFile(gitignorePath, "utf8"));
5551
- logger.debug(`Loaded .gitignore file from ${path3.join(displayRoot, ".gitignore")}`);
6350
+ logger.debug(`Loaded .gitignore file from ${path5.join(displayRoot, ".gitignore")}`);
5552
6351
  }
5553
- const relativeIgnorePath = (candidate) => path3.relative(root, candidate).split(path3.sep).join("/");
6352
+ const relativeIgnorePath = (candidate) => path5.relative(root, candidate).split(path5.sep).join("/");
5554
6353
  return Object.freeze({
5555
6354
  ignores: (candidate) => matcher.ignores(relativeIgnorePath(candidate)),
5556
6355
  ignoresDirectory: (candidate) => matcher.ignores(`${relativeIgnorePath(candidate)}/`)
@@ -5560,7 +6359,7 @@ async function createGitIgnoreMatcher(root, displayRoot = root) {
5560
6359
  // src/pipeline/discover/discover-project.stage.ts
5561
6360
  var nodeFileSystem = Object.freeze({
5562
6361
  async kind(candidate) {
5563
- const candidateStat = await stat2(candidate);
6362
+ const candidateStat = await stat3(candidate);
5564
6363
  if (candidateStat.isFile()) return "file";
5565
6364
  if (candidateStat.isDirectory()) return "directory";
5566
6365
  return "other";
@@ -5595,10 +6394,10 @@ var DiscoverProjectStage = class {
5595
6394
  return projectManifest({ invocation, templates });
5596
6395
  }
5597
6396
  singleFile(invocation) {
5598
- if (path4.extname(invocation.canonicalInputPath).toLowerCase() !== ".html") {
6397
+ if (path6.extname(invocation.canonicalInputPath).toLowerCase() !== ".html") {
5599
6398
  throw new Error(`Unsupported file type: ${invocation.inputPath}`);
5600
6399
  }
5601
- if (path4.extname(invocation.canonicalOutputPath).toLowerCase() !== ".html") {
6400
+ if (path6.extname(invocation.canonicalOutputPath).toLowerCase() !== ".html") {
5602
6401
  throw new Error("Single-file output path must have a .html extension.");
5603
6402
  }
5604
6403
  return [{ inputPath: invocation.canonicalInputPath, outputPath: invocation.canonicalOutputPath }];
@@ -5611,7 +6410,7 @@ var DiscoverProjectStage = class {
5611
6410
  inputs.sort(compareCodeUnits);
5612
6411
  return inputs.map((inputPath) => ({
5613
6412
  inputPath,
5614
- outputPath: path4.join(invocation.canonicalOutputPath, path4.relative(root, inputPath))
6413
+ outputPath: path6.join(invocation.canonicalOutputPath, path6.relative(root, inputPath))
5615
6414
  }));
5616
6415
  }
5617
6416
  async collectInputs(directory, displayDirectory, matcher, exclusions) {
@@ -5620,30 +6419,30 @@ var DiscoverProjectStage = class {
5620
6419
  );
5621
6420
  const inputs = [];
5622
6421
  for (const entry of entries) {
5623
- const candidate = path4.join(directory, entry.name);
5624
- const displayCandidate = path4.join(displayDirectory, entry.name);
6422
+ const candidate = path6.join(directory, entry.name);
6423
+ const displayCandidate = path6.join(displayDirectory, entry.name);
5625
6424
  logger.debug(`Processing ${displayCandidate}`);
5626
6425
  const ignored = entry.kind === "directory" ? matcher.ignoresDirectory(candidate) : matcher.ignores(candidate);
5627
- if (ignored || exclusions.has(path4.normalize(candidate))) continue;
6426
+ if (ignored || exclusions.has(path6.normalize(candidate))) continue;
5628
6427
  const kind = entry.kind === "other" ? await this.fileSystem.kind(candidate) : entry.kind;
5629
6428
  if (entry.kind === "other" && kind === "directory" && matcher.ignoresDirectory(candidate)) continue;
5630
6429
  if (kind === "directory") {
5631
6430
  inputs.push(...await this.collectInputs(candidate, displayCandidate, matcher, exclusions));
5632
- } else if (kind === "file" && path4.extname(entry.name).toLowerCase() === ".html") {
5633
- 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)));
5634
6433
  }
5635
6434
  }
5636
6435
  return inputs;
5637
6436
  }
5638
6437
  excludedPaths(invocation) {
5639
- 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)));
5640
6439
  const outputRoot = invocation.canonicalOutputPath;
5641
- if (path4.relative(invocation.canonicalInputPath, outputRoot) !== "") candidates.push(outputRoot);
6440
+ if (path6.relative(invocation.canonicalInputPath, outputRoot) !== "") candidates.push(outputRoot);
5642
6441
  return new Set(candidates);
5643
6442
  }
5644
6443
  };
5645
6444
  function preserveTrailingSeparator(canonicalPath, rawPath) {
5646
- return /[\\/]$/u.test(rawPath) && !/[\\/]$/u.test(canonicalPath) ? `${canonicalPath}${path4.sep}` : canonicalPath;
6445
+ return /[\\/]$/u.test(rawPath) && !/[\\/]$/u.test(canonicalPath) ? `${canonicalPath}${path6.sep}` : canonicalPath;
5647
6446
  }
5648
6447
 
5649
6448
  // src/pipeline/pipeline-stage.error.ts
@@ -5685,7 +6484,7 @@ async function runStage(stage, action) {
5685
6484
  }
5686
6485
 
5687
6486
  // src/report/migration-report.builder.ts
5688
- import path5 from "path";
6487
+ import path7 from "path";
5689
6488
  var MigrationReportBuilder = class {
5690
6489
  build(inputRoot, outputRoot, target, mode, application, durationMs, files, stylesheet) {
5691
6490
  const pathApi = this.pathApi(
@@ -5719,9 +6518,9 @@ var MigrationReportBuilder = class {
5719
6518
  return { path: this.forwardSlashes(pathApi, displayPath), change: stylesheet.change };
5720
6519
  }
5721
6520
  pathApi(...values) {
5722
- if (values.some((value) => /^[A-Za-z]:[\\/]/.test(value) || value.includes("\\"))) return path5.win32;
5723
- if (values.some((value) => value.includes("/"))) return path5.posix;
5724
- 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;
5725
6524
  }
5726
6525
  samePath(pathApi, left, right) {
5727
6526
  return this.absolutePath(pathApi, left) === this.absolutePath(pathApi, right);
@@ -5798,9 +6597,16 @@ var MigrationReportBuilder = class {
5798
6597
  };
5799
6598
  }
5800
6599
  };
6600
+ function withReportEnvironment(report, options) {
6601
+ return {
6602
+ ...report,
6603
+ ...options.targetProfile ? { targetProfile: options.targetProfile } : {},
6604
+ ...options.sourceBreakpoints ? { sourceBreakpoints: options.sourceBreakpoints } : {}
6605
+ };
6606
+ }
5801
6607
 
5802
6608
  // src/pipeline/invocation-error-path.mapper.ts
5803
- import * as path6 from "path";
6609
+ import * as path8 from "path";
5804
6610
  function remapInvocationErrorPaths(error, invocation) {
5805
6611
  if (!(error instanceof Error)) return error;
5806
6612
  const roots = invocationRoots(invocation);
@@ -5824,12 +6630,12 @@ function remapErrorPath(error, field, roots) {
5824
6630
  error[field] = mapped;
5825
6631
  }
5826
6632
  function mappedInvocationPath(candidate, roots) {
5827
- if (!path6.isAbsolute(candidate)) return candidate;
6633
+ if (!path8.isAbsolute(candidate)) return candidate;
5828
6634
  for (const root of roots) {
5829
- const relativePath = path6.relative(root.canonical, candidate);
6635
+ const relativePath = path8.relative(root.canonical, candidate);
5830
6636
  if (relativePath === "") return root.raw;
5831
- if (relativePath === ".." || relativePath.startsWith(`..${path6.sep}`) || path6.isAbsolute(relativePath)) continue;
5832
- return path6.join(root.raw, relativePath);
6637
+ if (relativePath === ".." || relativePath.startsWith(`..${path8.sep}`) || path8.isAbsolute(relativePath)) continue;
6638
+ return path8.join(root.raw, relativePath);
5833
6639
  }
5834
6640
  return candidate;
5835
6641
  }
@@ -5847,7 +6653,7 @@ var MigrationRunner = class {
5847
6653
  const durationMs = this.now() - startedAt;
5848
6654
  const { validated, application } = applied;
5849
6655
  const { plan, rendered, stylesheet } = validated;
5850
- return this.reports.build(
6656
+ const report = this.reports.build(
5851
6657
  rendered.analyzed.manifest.invocation.inputPath,
5852
6658
  rendered.analyzed.manifest.invocation.outputPath,
5853
6659
  plan.target,
@@ -5857,6 +6663,7 @@ var MigrationRunner = class {
5857
6663
  plan.files,
5858
6664
  stylesheet
5859
6665
  );
6666
+ return withReportEnvironment(report, invocation.options);
5860
6667
  }
5861
6668
  async execute(invocation) {
5862
6669
  try {
@@ -8899,7 +9706,7 @@ var ConversionPlanner = class {
8899
9706
  };
8900
9707
 
8901
9708
  // src/pipeline/rendered-project.ts
8902
- import * as path7 from "path";
9709
+ import * as path9 from "path";
8903
9710
  function renderedProject(project) {
8904
9711
  const analyzed = analyzedProject(project.analyzed);
8905
9712
  if (project.files.length !== analyzed.templates.length) throw sequenceInvariant2();
@@ -9026,7 +9833,7 @@ function samePathPair2(left, right) {
9026
9833
  return normalizedAbsolutePath3(left.inputPath) === normalizedAbsolutePath3(right.inputPath) && normalizedAbsolutePath3(left.outputPath) === normalizedAbsolutePath3(right.outputPath);
9027
9834
  }
9028
9835
  function normalizedAbsolutePath3(value) {
9029
- return path7.normalize(path7.resolve(value));
9836
+ return path9.normalize(path9.resolve(value));
9030
9837
  }
9031
9838
  function sameOwnedValue(left, right) {
9032
9839
  if (Object.is(left, right)) return true;
@@ -9115,10 +9922,10 @@ function parseErrorPlan(template) {
9115
9922
  }
9116
9923
 
9117
9924
  // src/pipeline/validate/validate-project.stage.ts
9118
- import * as path14 from "path";
9925
+ import * as path15 from "path";
9119
9926
 
9120
9927
  // src/migrator/migration-plan.ts
9121
- import * as path8 from "path";
9928
+ import * as path10 from "path";
9122
9929
 
9123
9930
  // src/migrator/file-migration-result.ts
9124
9931
  function fileMigrationResult(result2) {
@@ -9141,7 +9948,7 @@ function freezeValue2(value) {
9141
9948
 
9142
9949
  // src/migrator/migration-plan.ts
9143
9950
  function plannedOutputArtifact(artifact) {
9144
- if (!path8.isAbsolute(artifact.path)) {
9951
+ if (!path10.isAbsolute(artifact.path)) {
9145
9952
  throw new Error(`Planned artifact paths must be absolute: ${artifact.path}`);
9146
9953
  }
9147
9954
  if (artifact.kind === "template" && artifact.proposed.status === "absent") {
@@ -9152,7 +9959,7 @@ function plannedOutputArtifact(artifact) {
9152
9959
  }
9153
9960
  return Object.freeze({
9154
9961
  kind: artifact.kind,
9155
- path: path8.resolve(artifact.path),
9962
+ path: path10.resolve(artifact.path),
9156
9963
  original: artifactState(artifact.original),
9157
9964
  proposed: artifactState(artifact.proposed)
9158
9965
  });
@@ -9179,216 +9986,9 @@ function sameState(left, right) {
9179
9986
  return right.status === "present" && left.contents === right.contents;
9180
9987
  }
9181
9988
 
9182
- // src/migrator/migration-path.validator.ts
9183
- import { lstat as lstat2, stat as stat3 } from "fs/promises";
9184
- import * as path9 from "path";
9185
- async function validateMigrationPaths(request, pathApi = path9) {
9186
- const claims = normalizedClaims(request, pathApi);
9187
- await validateCollisions(claims, pathApi);
9188
- const destinations = claims.filter((claim) => claim.kind !== "template-input");
9189
- for (const destination of destinations) {
9190
- await validateDestination(destination.path);
9191
- }
9192
- }
9193
- async function validateStylesheetRootTopology(request, pathApi = path9) {
9194
- if (request.stylesheetPath === void 0) return;
9195
- const stylesheetPath = pathApi.resolve(request.stylesheetPath);
9196
- const templateRoots = [request.inputPath, request.outputPath].map((claim) => pathApi.resolve(claim));
9197
- const reportPath = request.reportPath === void 0 ? void 0 : pathApi.resolve(request.reportPath);
9198
- const exactCollision = (await Promise.all(
9199
- [...templateRoots, reportPath].map(
9200
- (claim) => claim === void 0 ? Promise.resolve(false) : pathsEquivalentOnFileSystem(stylesheetPath, claim, pathApi)
9201
- )
9202
- )).some(Boolean);
9203
- const reportHierarchyCollision = reportPath !== void 0 && await pathsOverlapOnFileSystem(stylesheetPath, reportPath, pathApi);
9204
- if (exactCollision || reportHierarchyCollision) {
9205
- const collisionPaths = reportHierarchyCollision && reportPath !== void 0 && !await pathsEquivalentOnFileSystem(stylesheetPath, reportPath, pathApi) ? [stylesheetPath, reportPath] : [stylesheetPath];
9206
- throw new MigrationApplicationError(
9207
- "path-collision",
9208
- `Stylesheet path collides with another migration path: ${request.stylesheetPathInput ?? request.stylesheetPath}`,
9209
- collisionPaths
9210
- );
9211
- }
9212
- let stylesheetStat;
9213
- try {
9214
- stylesheetStat = await lstat2(stylesheetPath);
9215
- } catch (error) {
9216
- if (isEnoent2(error)) return;
9217
- throw error;
9218
- }
9219
- const sourcePath = request.stylesheetPathInput ?? request.stylesheetPath;
9220
- if (stylesheetStat.isSymbolicLink()) {
9221
- throw new MigrationApplicationError(
9222
- "unsupported-path-type",
9223
- `Stylesheet path must not be a symbolic link: ${sourcePath}`,
9224
- [stylesheetPath]
9225
- );
9226
- }
9227
- if (!stylesheetStat.isFile()) {
9228
- throw new MigrationApplicationError(
9229
- "unsupported-path-type",
9230
- `Stylesheet path must be a regular file: ${sourcePath}`,
9231
- [stylesheetPath]
9232
- );
9233
- }
9234
- }
9235
- function normalizedClaims(request, pathApi) {
9236
- return [
9237
- ...request.templates.flatMap((template, templateIndex) => [
9238
- { path: pathApi.resolve(template.inputPath), kind: "template-input", templateIndex },
9239
- { path: pathApi.resolve(template.outputPath), kind: "template-output", templateIndex }
9240
- ]),
9241
- ...request.stylesheetPath ? [{ path: pathApi.resolve(request.stylesheetPath), kind: "stylesheet" }] : [],
9242
- ...request.reportPath ? [{ path: pathApi.resolve(request.reportPath), kind: "report" }] : []
9243
- ];
9244
- }
9245
- async function validateCollisions(claims, pathApi) {
9246
- const observations = /* @__PURE__ */ new Map();
9247
- const observe = (candidate) => {
9248
- const normalized = pathApi.resolve(candidate);
9249
- const existing = observations.get(normalized);
9250
- if (existing) return existing;
9251
- const pending = observePath(normalized, pathApi);
9252
- observations.set(normalized, pending);
9253
- return pending;
9254
- };
9255
- for (let leftIndex = 0; leftIndex < claims.length; leftIndex++) {
9256
- const left = claims[leftIndex];
9257
- if (!left) continue;
9258
- for (let rightIndex = leftIndex + 1; rightIndex < claims.length; rightIndex++) {
9259
- const right = claims[rightIndex];
9260
- if (!right) continue;
9261
- const relationship = await fileSystemPathRelationship(left.path, right.path, pathApi, observe);
9262
- if (relationship === "distinct" || isIntentionalInPlacePair(left, right, pathApi)) continue;
9263
- const collisionPaths = relationship === "equivalent" ? [left.path] : [left.path, right.path];
9264
- throw new MigrationApplicationError(
9265
- "path-collision",
9266
- `Migration paths collide: ${collisionPaths.join(" and ")}`,
9267
- collisionPaths
9268
- );
9269
- }
9270
- }
9271
- }
9272
- function isIntentionalInPlacePair(left, right, pathApi) {
9273
- 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-");
9274
- }
9275
- function pathsEquivalent(left, right, pathApi = path9) {
9276
- return normalizedPathsEquivalent(pathApi, pathApi.resolve(left), pathApi.resolve(right));
9277
- }
9278
- async function pathsEquivalentOnFileSystem(left, right, pathApi = path9) {
9279
- return await fileSystemPathRelationship(left, right, pathApi) === "equivalent";
9280
- }
9281
- async function pathsOverlapOnFileSystem(left, right, pathApi = path9) {
9282
- return await fileSystemPathRelationship(left, right, pathApi) !== "distinct";
9283
- }
9284
- function normalizedPathsEquivalent(pathApi, left, right) {
9285
- return pathApi.relative(left, right) === "";
9286
- }
9287
- function isAncestor(pathApi, ancestor, descendant) {
9288
- const relative3 = pathApi.relative(ancestor, descendant);
9289
- return relative3 !== "" && relative3 !== ".." && !relative3.startsWith(`..${pathApi.sep}`) && !pathApi.isAbsolute(relative3);
9290
- }
9291
- async function fileSystemPathRelationship(left, right, pathApi, observe = (candidate) => observePath(candidate, pathApi)) {
9292
- const normalizedLeft = pathApi.resolve(left);
9293
- const normalizedRight = pathApi.resolve(right);
9294
- if (normalizedPathsEquivalent(pathApi, normalizedLeft, normalizedRight)) return "equivalent";
9295
- if (isAncestor(pathApi, normalizedLeft, normalizedRight)) return "ancestor";
9296
- if (isAncestor(pathApi, normalizedRight, normalizedLeft)) return "descendant";
9297
- const [observedLeft, observedRight] = await Promise.all([observe(normalizedLeft), observe(normalizedRight)]);
9298
- if (observedLeft.exactIdentity && observedRight.exactIdentity) {
9299
- if (sameIdentity2(observedLeft.exactIdentity, observedRight.exactIdentity)) return "equivalent";
9300
- if (hasIdentityBelow(observedRight, observedLeft.exactIdentity)) return "ancestor";
9301
- if (hasIdentityBelow(observedLeft, observedRight.exactIdentity)) return "descendant";
9302
- return "distinct";
9303
- }
9304
- return relationshipThroughExistingPrefixes(observedLeft, observedRight);
9305
- }
9306
- async function observePath(candidate, pathApi) {
9307
- const prefixes = [];
9308
- const suffix = [];
9309
- let current = candidate;
9310
- let exactIdentity;
9311
- while (true) {
9312
- try {
9313
- const currentStat = await stat3(current, { bigint: true });
9314
- const currentIdentity = identity2(currentStat);
9315
- if (suffix.length === 0) exactIdentity = currentIdentity;
9316
- prefixes.push({ identity: currentIdentity, suffix: [...suffix] });
9317
- } catch (error) {
9318
- if (!isMissingPath(error)) throw error;
9319
- }
9320
- const parent = pathApi.dirname(current);
9321
- if (parent === current) break;
9322
- suffix.unshift(pathApi.basename(current));
9323
- current = parent;
9324
- }
9325
- return { ...exactIdentity ? { exactIdentity } : {}, prefixes };
9326
- }
9327
- function relationshipThroughExistingPrefixes(left, right) {
9328
- for (const leftPrefix of left.prefixes) {
9329
- for (const rightPrefix of right.prefixes) {
9330
- if (!sameIdentity2(leftPrefix.identity, rightPrefix.identity)) continue;
9331
- const relationship = suffixRelationship(leftPrefix.suffix, rightPrefix.suffix);
9332
- if (relationship !== "distinct") return relationship;
9333
- }
9334
- }
9335
- return "distinct";
9336
- }
9337
- function hasIdentityBelow(observed, candidate) {
9338
- return observed.prefixes.some((prefix) => prefix.suffix.length > 0 && sameIdentity2(prefix.identity, candidate));
9339
- }
9340
- function suffixRelationship(left, right) {
9341
- const normalizedLeft = left.map(portablePathSegment);
9342
- const normalizedRight = right.map(portablePathSegment);
9343
- const sharedLength = Math.min(normalizedLeft.length, normalizedRight.length);
9344
- for (let index = 0; index < sharedLength; index++) {
9345
- if (normalizedLeft[index] !== normalizedRight[index]) return "distinct";
9346
- }
9347
- if (normalizedLeft.length === normalizedRight.length) return "equivalent";
9348
- return normalizedLeft.length < normalizedRight.length ? "ancestor" : "descendant";
9349
- }
9350
- function portablePathSegment(value) {
9351
- return value.normalize("NFC").toLowerCase();
9352
- }
9353
- function identity2(value) {
9354
- return { device: String(value.dev), inode: String(value.ino) };
9355
- }
9356
- function sameIdentity2(left, right) {
9357
- return left.device === right.device && left.inode === right.inode;
9358
- }
9359
- async function validateDestination(destination) {
9360
- let stat4;
9361
- try {
9362
- stat4 = await lstat2(destination);
9363
- } catch (error) {
9364
- if (isEnoent2(error)) return;
9365
- throw error;
9366
- }
9367
- if (stat4.isSymbolicLink()) {
9368
- throw new MigrationApplicationError(
9369
- "unsupported-path-type",
9370
- `Migration destination must not be a symbolic link: ${destination}`,
9371
- [destination]
9372
- );
9373
- }
9374
- if (!stat4.isFile()) {
9375
- throw new MigrationApplicationError(
9376
- "unsupported-path-type",
9377
- `Migration destination must be a regular file: ${destination}`,
9378
- [destination]
9379
- );
9380
- }
9381
- }
9382
- function isEnoent2(error) {
9383
- return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
9384
- }
9385
- function isMissingPath(error) {
9386
- return typeof error === "object" && error !== null && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
9387
- }
9388
-
9389
9989
  // src/migrator/stylesheet.planner.ts
9390
- import { lstat as lstat3, readFile as readFile2 } from "fs/promises";
9391
- import * as path10 from "path";
9990
+ import { lstat as lstat3, readFile as readFile3 } from "fs/promises";
9991
+ import * as path11 from "path";
9392
9992
 
9393
9993
  // src/adapter/css/stylesheet/css-stylesheet.error.ts
9394
9994
  var CssStylesheetError = class extends Error {
@@ -9917,7 +10517,7 @@ function mergeStylesheetContents(existing, rules, references = new Set(rules.map
9917
10517
 
9918
10518
  // src/migrator/stylesheet.planner.ts
9919
10519
  var nodeFileSystem2 = {
9920
- readFile: (target) => readFile2(target, "utf8"),
10520
+ readFile: (target) => readFile3(target, "utf8"),
9921
10521
  lstat: lstat3
9922
10522
  };
9923
10523
  var StylesheetPlanner = class {
@@ -9925,7 +10525,7 @@ var StylesheetPlanner = class {
9925
10525
  this.fileSystem = fileSystem;
9926
10526
  }
9927
10527
  async plan(stylesheetPath, rules, references = new Set(rules.map((rule) => rule.className))) {
9928
- const outputPath = path10.resolve(stylesheetPath);
10528
+ const outputPath = path11.resolve(stylesheetPath);
9929
10529
  const original = await this.originalState(outputPath);
9930
10530
  const existing = original.status === "present" ? original.contents : "";
9931
10531
  let merged;
@@ -9985,7 +10585,7 @@ function isEnoent3(error) {
9985
10585
  }
9986
10586
 
9987
10587
  // src/pipeline/validated-project-plan.ts
9988
- import * as path11 from "path";
10588
+ import * as path12 from "path";
9989
10589
  function validatedProjectPlan(project) {
9990
10590
  const rendered = renderedProject(project.rendered);
9991
10591
  if (rendered.target !== rendered.session.target) {
@@ -10098,7 +10698,7 @@ function isPlainRecord2(value) {
10098
10698
  return prototype === Object.prototype || prototype === null;
10099
10699
  }
10100
10700
  function normalizedAbsolutePath4(value) {
10101
- return path11.normalize(path11.resolve(value));
10701
+ return path12.normalize(path12.resolve(value));
10102
10702
  }
10103
10703
  function fileCongruenceInvariant() {
10104
10704
  return internalInvariant4(
@@ -10110,12 +10710,12 @@ function internalInvariant4(message) {
10110
10710
  }
10111
10711
 
10112
10712
  // src/pipeline/validate/css-reference.collector.ts
10113
- import * as path12 from "path";
10713
+ import * as path13 from "path";
10114
10714
 
10115
10715
  // src/migrator/destination-template-source.ts
10116
- import { readFile as readFile3 } from "fs/promises";
10716
+ import { readFile as readFile4 } from "fs/promises";
10117
10717
  var nodeDestinationTemplateSource = Object.freeze({
10118
- read: (path18) => readFile3(path18, "utf8")
10718
+ read: (path19) => readFile4(path19, "utf8")
10119
10719
  });
10120
10720
 
10121
10721
  // src/pipeline/validate/css-reference.collector.ts
@@ -10134,10 +10734,10 @@ var CssReferenceCollector = class {
10134
10734
  if (file.artifact?.kind === "template" && file.artifact.proposed.status === "present") {
10135
10735
  return { contents: file.artifact.proposed.contents, complete: true };
10136
10736
  }
10137
- if (path12.resolve(file.file.inputPath) === path12.resolve(file.file.outputPath)) {
10737
+ if (path13.resolve(file.file.inputPath) === path13.resolve(file.file.outputPath)) {
10138
10738
  return { contents: analyzedTemplate.source, complete: true };
10139
10739
  }
10140
- const outputPath = path12.normalize(path12.resolve(file.file.outputPath));
10740
+ const outputPath = path13.normalize(path13.resolve(file.file.outputPath));
10141
10741
  const existing = destinationSources.get(outputPath);
10142
10742
  if (existing !== void 0) return existing;
10143
10743
  const pending = this.readDestination(outputPath);
@@ -10203,8 +10803,20 @@ function isEnoent4(error) {
10203
10803
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
10204
10804
  }
10205
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
+
10206
10818
  // src/pipeline/validate/template-proposal.validator.ts
10207
- import * as path13 from "path";
10819
+ import * as path14 from "path";
10208
10820
  var TemplateProposalValidator = class {
10209
10821
  constructor(validationParser = new AngularTemplateParser(), destinationTemplates = nodeDestinationTemplateSource) {
10210
10822
  this.validationParser = validationParser;
@@ -10219,21 +10831,8 @@ var TemplateProposalValidator = class {
10219
10831
  );
10220
10832
  }
10221
10833
  if (edited.output === template.source) return planResult(template, rendered, false, rendered.results);
10222
- const reparsed = this.validationParser.parse(edited.output, template.file.outputPath);
10223
- if (reparsed.status === "parse-error") {
10224
- return planResult(
10225
- template,
10226
- rendered,
10227
- false,
10228
- reparsed.diagnostics.map((diagnostic2) => ({
10229
- status: "parse-error",
10230
- fileName: template.file.outputPath,
10231
- code: "generated-template-parse-error",
10232
- reason: diagnostic2.message,
10233
- source: diagnostic2.source
10234
- }))
10235
- );
10236
- }
10834
+ const errors = generatedTemplateErrors(edited.output, template.file.outputPath, this.validationParser);
10835
+ if (errors.length) return planResult(template, rendered, false, errors);
10237
10836
  const original = await originalState(template, this.destinationTemplates);
10238
10837
  const proposed = { status: "present", contents: edited.output };
10239
10838
  if (sameState3(original, proposed)) return planResult(template, rendered, false, rendered.results);
@@ -10241,7 +10840,7 @@ var TemplateProposalValidator = class {
10241
10840
  file: result(template, rendered, true, rendered.results),
10242
10841
  artifact: plannedOutputArtifact({
10243
10842
  kind: "template",
10244
- path: path13.normalize(path13.resolve(template.file.outputPath)),
10843
+ path: path14.normalize(path14.resolve(template.file.outputPath)),
10245
10844
  original,
10246
10845
  proposed
10247
10846
  })
@@ -10249,7 +10848,7 @@ var TemplateProposalValidator = class {
10249
10848
  }
10250
10849
  };
10251
10850
  async function originalState(template, destinationTemplates) {
10252
- if (path13.resolve(template.file.inputPath) === path13.resolve(template.file.outputPath)) {
10851
+ if (path14.resolve(template.file.inputPath) === path14.resolve(template.file.outputPath)) {
10253
10852
  return { status: "present", contents: template.source };
10254
10853
  }
10255
10854
  try {
@@ -10317,7 +10916,7 @@ var ValidateProjectStage = class {
10317
10916
  let stylesheetArtifact;
10318
10917
  let stylesheet;
10319
10918
  if (rendered.session.target === "css" && stylesheetPath !== void 0) {
10320
- const canonicalStylesheetPath = path14.resolve(stylesheetPath);
10919
+ const canonicalStylesheetPath = path15.resolve(stylesheetPath);
10321
10920
  const references = await this.cssReferences.collect(rendered, files);
10322
10921
  stylesheetArtifact = await this.stylesheetPlanner.plan(
10323
10922
  canonicalStylesheetPath,
@@ -10403,19 +11002,19 @@ var AtomicFileWriter = class {
10403
11002
  throw cleanupError(targetPath);
10404
11003
  }
10405
11004
  }
10406
- async captureNamespaceIdentity(path18, targetPath) {
10407
- const stat4 = await this.operations.lstat(path18);
11005
+ async captureNamespaceIdentity(path19, targetPath) {
11006
+ const stat4 = await this.operations.lstat(path19);
10408
11007
  if (stat4.isSymbolicLink() || !stat4.isDirectory()) throw ownershipError(targetPath);
10409
11008
  return identity3(stat4);
10410
11009
  }
10411
- async assertNamespaceIdentity(path18, expected, targetPath) {
10412
- const stat4 = await this.operations.lstat(path18);
11010
+ async assertNamespaceIdentity(path19, expected, targetPath) {
11011
+ const stat4 = await this.operations.lstat(path19);
10413
11012
  if (stat4.isSymbolicLink() || !stat4.isDirectory() || !sameIdentity3(identity3(stat4), expected)) {
10414
11013
  throw ownershipError(targetPath);
10415
11014
  }
10416
11015
  }
10417
- async assertTemporaryIdentity(path18, expected, targetPath) {
10418
- const stat4 = await this.operations.lstat(path18);
11016
+ async assertTemporaryIdentity(path19, expected, targetPath) {
11017
+ const stat4 = await this.operations.lstat(path19);
10419
11018
  if (stat4.isSymbolicLink() || !stat4.isFile() || !sameIdentity3(identity3(stat4), expected)) {
10420
11019
  throw ownershipError(targetPath);
10421
11020
  }
@@ -10479,19 +11078,19 @@ var JsonReportWriter = class {
10479
11078
  constructor(writer = new AtomicFileWriter()) {
10480
11079
  this.writer = writer;
10481
11080
  }
10482
- async write(path18, report, options = {}) {
11081
+ async write(path19, report, options = {}) {
10483
11082
  for (const protectedPath of options.protectedPaths ?? []) {
10484
- if (!await pathsOverlapOnFileSystem(protectedPath, path18)) continue;
10485
- 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];
10486
11085
  throw new MigrationApplicationError(
10487
11086
  "path-collision",
10488
- `Report path collides with a migration output: ${path18}`,
11087
+ `Report path collides with a migration output: ${path19}`,
10489
11088
  collisionPaths
10490
11089
  );
10491
11090
  }
10492
11091
  const contents = `${JSON.stringify(report, null, 2)}
10493
11092
  `;
10494
- await this.writer.write(path18, contents);
11093
+ await this.writer.write(path19, contents);
10495
11094
  }
10496
11095
  };
10497
11096
 
@@ -10509,8 +11108,23 @@ var TerminalPresenter = class {
10509
11108
  const diagnostics = report.files.flatMap(
10510
11109
  (file) => file.results.filter((result2) => result2.status !== "converted").map((result2) => `${file.path}:${result2.offset} [${result2.code}] ${result2.reason}`)
10511
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
+ ] : [];
10512
11126
  output.write(
10513
- [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")
10514
11128
  );
10515
11129
  }
10516
11130
  applicationPresentation(report) {
@@ -10611,17 +11225,17 @@ function getErrorMessage(error) {
10611
11225
  // src/cli/exit-policy.ts
10612
11226
  function resolveExitCode(report, allowUnresolved) {
10613
11227
  if (report.summary.parseErrors > 0) return 1;
10614
- 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;
10615
11229
  return hasUnresolved && !allowUnresolved ? 2 : 0;
10616
11230
  }
10617
11231
 
10618
11232
  // src/cli/report-path.validator.ts
10619
- import path15 from "path";
11233
+ import path16 from "path";
10620
11234
  function validateReportPath(reportPath) {
10621
11235
  if (reportPath.trim().length === 0) {
10622
11236
  throw new Error("Report path must not be empty.");
10623
11237
  }
10624
- if (path15.extname(reportPath).toLowerCase() !== ".json") {
11238
+ if (path16.extname(reportPath).toLowerCase() !== ".json") {
10625
11239
  throw new Error("Report path must have a .json extension.");
10626
11240
  }
10627
11241
  }
@@ -10654,8 +11268,8 @@ function parsePrintWithBreakpoints(value, orientationEnabled) {
10654
11268
  }
10655
11269
 
10656
11270
  // src/cli/stylesheet-path.validator.ts
10657
- import * as path16 from "path";
10658
- async function validateStylesheetPath(request, pathApi = path16) {
11271
+ import * as path17 from "path";
11272
+ async function validateStylesheetPath(request, pathApi = path17) {
10659
11273
  if (request.target !== "css") {
10660
11274
  if (request.stylesheetPath !== void 0) {
10661
11275
  throw new MigrationApplicationError("invalid-configuration", "--stylesheet can only be used with --target css.", [
@@ -10685,6 +11299,7 @@ function resolveMigrationMode(argv, write) {
10685
11299
  if (optionArguments.filter((argument) => argument === "--write").length > 1) {
10686
11300
  throw new Error("--write may only be specified once.");
10687
11301
  }
11302
+ if (write && optionArguments.includes("--plan")) throw new Error("--plan and --write cannot be combined.");
10688
11303
  return write ? "write" : "plan";
10689
11304
  }
10690
11305
 
@@ -10713,7 +11328,7 @@ async function runCli(argv, output = processOutput, dependencies = {}) {
10713
11328
  new Option("--stylesheet <path>", "companion stylesheet; required when --target css").argParser(
10714
11329
  parseSingleStylesheet
10715
11330
  )
10716
- ).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(
10717
11332
  "--responsive-images",
10718
11333
  "wrap eligible responsive images in picture elements; acknowledges selector and layout risk",
10719
11334
  false
@@ -10724,23 +11339,38 @@ async function runCli(argv, output = processOutput, dependencies = {}) {
10724
11339
  const mode = resolveMigrationMode(argv, options.write);
10725
11340
  debug = options.debug;
10726
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.");
10727
11350
  const destination = options.output ?? input;
10728
11351
  let reportPath;
10729
11352
  if (options.report !== void 0) {
10730
11353
  validateReportPath(options.report);
10731
- reportPath = path17.resolve(options.report);
11354
+ reportPath = path18.resolve(options.report);
10732
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
+ }
10733
11361
  const stylesheetPath = await validateStylesheetPath({
10734
- target: options.target,
11362
+ target,
10735
11363
  stylesheetPath: options.stylesheet,
10736
11364
  inputPath: input,
10737
11365
  outputPath: destination,
10738
11366
  reportPath
10739
11367
  });
10740
11368
  const printWithBreakpoints = options.printWithBreakpoints === void 0 ? void 0 : parsePrintWithBreakpoints(options.printWithBreakpoints, options.orientationBreakpoints);
10741
- const session = AdapterFactory.createRenderSession(options.target, {
11369
+ const session = AdapterFactory.createRenderSession(target, {
10742
11370
  orientationBreakpoints: options.orientationBreakpoints,
10743
- printWithBreakpoints
11371
+ printWithBreakpoints,
11372
+ targetProfile: target === "tailwind" ? configuration.targetProfile : void 0,
11373
+ sourceBreakpoints: configuration.sourceBreakpoints
10744
11374
  });
10745
11375
  const render = new RenderProjectStage(session);
10746
11376
  const pipeline = new MigrationPipeline(
@@ -10757,6 +11387,9 @@ async function runCli(argv, output = processOutput, dependencies = {}) {
10757
11387
  outputPath: destination,
10758
11388
  options: {
10759
11389
  mode,
11390
+ targetProfile: target === "tailwind" ? configuration.targetProfile : void 0,
11391
+ sourceBreakpoints: configuration.sourceBreakpoints,
11392
+ configurationSnapshots: configuration.snapshots,
10760
11393
  responsiveImages: options.responsiveImages,
10761
11394
  stylesheetPath,
10762
11395
  stylesheetPathInput: options.stylesheet,
@@ -10768,7 +11401,10 @@ async function runCli(argv, output = processOutput, dependencies = {}) {
10768
11401
  new TerminalPresenter().present(report, reportOutput);
10769
11402
  if (reportPath !== void 0) {
10770
11403
  await new JsonReportWriter().write(reportPath, report, {
10771
- protectedPaths: stylesheetPath === void 0 ? [] : [stylesheetPath]
11404
+ protectedPaths: [
11405
+ ...configuration.snapshots.map((snapshot) => snapshot.path),
11406
+ ...stylesheetPath === void 0 ? [] : [stylesheetPath]
11407
+ ]
10772
11408
  });
10773
11409
  }
10774
11410
  exitCode = resolveExitCode(report, options.allowUnresolved);