tw-migrate 0.1.0 → 0.2.0
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/README.md +13 -1
- package/bin/tw-migrate.js +15 -10
- package/html.js +74 -36
- package/index.d.ts +1 -1
- package/index.js +1283 -312
- package/native.js +18 -11
- package/package.json +14 -12
- package/style-compiler.js +52 -37
- package/vue.js +541 -0
- package/rfcs/batch-css-migration.md +0 -297
- package/rfcs/browser-ecosystem-e2e.md +0 -338
- package/rfcs/css-to-tailwind-migration-cli.md +0 -521
- package/rfcs/preprocessor-and-html-migration.md +0 -497
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
# RFC: Batch CSS Migration
|
|
2
|
-
|
|
3
|
-
## Status
|
|
4
|
-
|
|
5
|
-
Accepted
|
|
6
|
-
|
|
7
|
-
## Summary
|
|
8
|
-
|
|
9
|
-
`tw-migrate` currently requires one CSS file per invocation. This RFC extends it so an invocation without a CSS path discovers and migrates every eligible CSS file in the current package. A supplied CSS path continues to scope conversion to that file. Workspace-wide migration is explicit through `--workspaces`.
|
|
10
|
-
|
|
11
|
-
The batch operation reads the project once, builds one deterministic edit plan, validates the complete result, and applies it atomically. Independent per-CSS migrations are not written one after another because they can edit the same JavaScript or Tailwind entry from stale source.
|
|
12
|
-
|
|
13
|
-
## Goals
|
|
14
|
-
|
|
15
|
-
1. Migrate every eligible `.css` file in the current package when no CSS path is supplied.
|
|
16
|
-
2. Preserve single-file migration when a CSS path is supplied.
|
|
17
|
-
3. Find every supported JavaScript/TypeScript reference to each selected stylesheet.
|
|
18
|
-
4. Group migration by the nearest package boundary and that package's Tailwind v4 entry.
|
|
19
|
-
5. Support an explicit `--workspaces` mode for all packages below the workspace root.
|
|
20
|
-
6. Respect `.gitignore` during automatic discovery.
|
|
21
|
-
7. Produce one deterministic preview and one validated write transaction.
|
|
22
|
-
8. Retain only conflicting rules while continuing independent conversions.
|
|
23
|
-
9. Allow `--force` to skip failed package groups without weakening edit, Tailwind, or write validation.
|
|
24
|
-
10. Establish a measurable baseline before adding parallel parsing.
|
|
25
|
-
|
|
26
|
-
## Non-Goals
|
|
27
|
-
|
|
28
|
-
1. SCSS, Sass, and Less parsing or migration in this release.
|
|
29
|
-
2. Emotion or styled-components discovery.
|
|
30
|
-
3. Selecting migration scope from a JavaScript or TypeScript positional path.
|
|
31
|
-
4. Directory, glob, or include/exclude configuration beyond current package and workspace scope.
|
|
32
|
-
5. Guessing ownership across package boundaries when a package has no unique Tailwind entry.
|
|
33
|
-
6. Parallel analysis in the initial implementation.
|
|
34
|
-
7. Automatic deletion of global CSS rules.
|
|
35
|
-
|
|
36
|
-
Preprocessor extensions must not be treated as CSS merely because they are discovered. They will be added only with parsers and source-editing rules that preserve their original syntax.
|
|
37
|
-
|
|
38
|
-
## User Experience
|
|
39
|
-
|
|
40
|
-
### CLI
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
# Preview every eligible CSS file in the current package.
|
|
44
|
-
tw-migrate
|
|
45
|
-
|
|
46
|
-
# Preview one CSS file while scanning all relevant source references.
|
|
47
|
-
tw-migrate src/components/Button.module.css
|
|
48
|
-
|
|
49
|
-
# Apply the current package migration.
|
|
50
|
-
tw-migrate --write
|
|
51
|
-
|
|
52
|
-
# Apply every discovered workspace package.
|
|
53
|
-
tw-migrate --workspaces --write
|
|
54
|
-
|
|
55
|
-
# Skip recoverably failed package groups and apply successful groups.
|
|
56
|
-
tw-migrate --workspaces --write --force
|
|
57
|
-
|
|
58
|
-
# Resolve a package with an ambiguous Tailwind entry.
|
|
59
|
-
tw-migrate --tailwind-css src/app/globals.css --write
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
The command accepts at most one positional CSS file. `--workspaces` cannot be combined with a positional CSS file because the positional path already defines the requested scope.
|
|
63
|
-
|
|
64
|
-
Preview remains the default. `--force` has no effect on unsupported rules that are already represented as warnings; those rules remain unchanged in every mode.
|
|
65
|
-
|
|
66
|
-
### Node.js API
|
|
67
|
-
|
|
68
|
-
```ts
|
|
69
|
-
interface MigrateOptions {
|
|
70
|
-
cssFile?: string;
|
|
71
|
-
cwd?: string;
|
|
72
|
-
write?: boolean;
|
|
73
|
-
tailwindCss?: string;
|
|
74
|
-
workspaces?: boolean;
|
|
75
|
-
force?: boolean;
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
`migrate()` with no `cssFile` processes the current package. `workspaces: true` processes all package groups below the workspace root. Supplying both `cssFile` and `workspaces: true` is an error.
|
|
80
|
-
|
|
81
|
-
The existing aggregate report fields remain. Package failures skipped by `force` are returned separately:
|
|
82
|
-
|
|
83
|
-
```ts
|
|
84
|
-
interface MigrationFailure {
|
|
85
|
-
package: string;
|
|
86
|
-
message: string;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
interface MigrationReport {
|
|
90
|
-
changedFiles: string[];
|
|
91
|
-
diff: string;
|
|
92
|
-
convertedRules: number;
|
|
93
|
-
retainedRules: number;
|
|
94
|
-
rules: RuleReport[];
|
|
95
|
-
candidates: string[];
|
|
96
|
-
warnings: MigrationWarning[];
|
|
97
|
-
failures: MigrationFailure[];
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## Scope Discovery
|
|
102
|
-
|
|
103
|
-
### Package Boundaries
|
|
104
|
-
|
|
105
|
-
The current package is the nearest ancestor containing `package.json`. The workspace root is the Git root when available, otherwise the current package root.
|
|
106
|
-
|
|
107
|
-
In default mode only the current package contributes migration targets. In `--workspaces` mode, package directories below the workspace root are discovered from non-ignored `package.json` files. Every selected CSS file belongs to its nearest package boundary.
|
|
108
|
-
|
|
109
|
-
A package group must resolve exactly one Tailwind v4 CSS entry unless `tailwindCss` explicitly selects one for a single-package invocation. Tailwind entry files are inputs and possible edit destinations, never migration targets.
|
|
110
|
-
|
|
111
|
-
A package with no Tailwind entry or multiple entries fails before planning. In default mode this prevents every write. With `--force`, the package group is skipped.
|
|
112
|
-
|
|
113
|
-
### CSS Targets
|
|
114
|
-
|
|
115
|
-
Automatic discovery selects `.css` files within the requested package groups after excluding:
|
|
116
|
-
|
|
117
|
-
- Tailwind entry files;
|
|
118
|
-
- `.git`, `.next`, `build`, `dist`, and `node_modules`;
|
|
119
|
-
- paths ignored by Git.
|
|
120
|
-
|
|
121
|
-
A positional CSS path overrides ignore rules and selects exactly that file. It must end in `.css` and belong to the current package.
|
|
122
|
-
|
|
123
|
-
### Source References
|
|
124
|
-
|
|
125
|
-
Migration targets and writable sources are scoped separately:
|
|
126
|
-
|
|
127
|
-
- default mode writes CSS and supported JS/TS files in the current package;
|
|
128
|
-
- workspace mode writes files in all selected package groups;
|
|
129
|
-
- safety analysis scans supported JS/TS and CSS references throughout the workspace root.
|
|
130
|
-
|
|
131
|
-
If a selected CSS Module has a consumer outside the writable scope, the affected module rule and import are retained with a warning. The external consumer is not modified. This permits safe current-package migration without silently expanding its write scope.
|
|
132
|
-
|
|
133
|
-
## Batch Architecture
|
|
134
|
-
|
|
135
|
-
```text
|
|
136
|
-
resolve scope
|
|
137
|
-
-> discover non-ignored files once
|
|
138
|
-
-> snapshot CSS, JS/TS, package manifests, and Tailwind entries
|
|
139
|
-
-> group targets by package and Tailwind entry
|
|
140
|
-
-> plan every group against the immutable snapshot
|
|
141
|
-
-> merge edits by absolute path and original byte span
|
|
142
|
-
-> retain cross-stylesheet conflicts
|
|
143
|
-
-> validate edited syntax and Tailwind candidates
|
|
144
|
-
-> verify inputs still match the snapshot
|
|
145
|
-
-> print preview or atomically write
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### One Snapshot
|
|
149
|
-
|
|
150
|
-
Every relevant file is read once before planning. The planner never reads a source file after another CSS plan has modified it. All edits target the same immutable source snapshot.
|
|
151
|
-
|
|
152
|
-
Before writing, every destination is read again and compared with its snapshot. A concurrent user or tool edit aborts the operation instead of being overwritten.
|
|
153
|
-
|
|
154
|
-
### One Combined Plan
|
|
155
|
-
|
|
156
|
-
The native planner receives all CSS targets for a package group together. It resolves all CSS Module bindings and global selector candidates before producing JS edits. This avoids these unsafe patterns:
|
|
157
|
-
|
|
158
|
-
- two CSS files replacing the same `className` from different source versions;
|
|
159
|
-
- one CSS plan deleting an import needed by another stylesheet;
|
|
160
|
-
- multiple plans independently appending to the same Tailwind entry;
|
|
161
|
-
- the last file write silently discarding an earlier migration.
|
|
162
|
-
|
|
163
|
-
Edits are sorted deterministically by path and descending byte offset. Overlapping edits remaining after conflict handling are an internal planning error and abort the requested operation.
|
|
164
|
-
|
|
165
|
-
### Cross-Stylesheet Conflicts
|
|
166
|
-
|
|
167
|
-
If different selected stylesheets generate utilities that affect the same Tailwind property and variant on the same JSX element, class attribute order cannot preserve the original cascade.
|
|
168
|
-
|
|
169
|
-
The planner therefore:
|
|
170
|
-
|
|
171
|
-
1. identifies every contributing CSS rule;
|
|
172
|
-
2. retains those rules in their original stylesheets;
|
|
173
|
-
3. leaves their JS references unchanged;
|
|
174
|
-
4. emits a stable warning naming the conflicting candidates;
|
|
175
|
-
5. continues planning unrelated rules.
|
|
176
|
-
|
|
177
|
-
A retained conflict is a warning, not a package failure.
|
|
178
|
-
|
|
179
|
-
### Tailwind Validation
|
|
180
|
-
|
|
181
|
-
Each package group loads its Tailwind installation and design system once. All candidates in that group are validated against that group's prefix, theme, config, and plugins.
|
|
182
|
-
|
|
183
|
-
A candidate validation failure is an integrity failure, not a recoverable unsupported rule. It aborts the complete requested operation even with `--force` because it indicates that the generated edit plan cannot be trusted.
|
|
184
|
-
|
|
185
|
-
## Failure and Transaction Policy
|
|
186
|
-
|
|
187
|
-
### Default
|
|
188
|
-
|
|
189
|
-
Without `--force`, the requested scope is one transaction. Discovery, parsing, planning, candidate validation, source validation, or pre-write snapshot failure causes no file changes.
|
|
190
|
-
|
|
191
|
-
### Force
|
|
192
|
-
|
|
193
|
-
`--force` changes only package-group admission. A group with a recoverable discovery or parse failure is reported and excluded. Successful groups are then merged and written through one transaction.
|
|
194
|
-
|
|
195
|
-
`--force` never ignores:
|
|
196
|
-
|
|
197
|
-
- overlapping final edit ranges;
|
|
198
|
-
- invalid generated JavaScript, TypeScript, or CSS;
|
|
199
|
-
- invalid Tailwind candidates;
|
|
200
|
-
- changed inputs detected before commit;
|
|
201
|
-
- staging, rename, or rollback failures.
|
|
202
|
-
|
|
203
|
-
The package group is the minimum failure-isolation unit. CSS-file-level isolation is deferred because files in a group can share JS destinations and a Tailwind entry.
|
|
204
|
-
|
|
205
|
-
### Atomic Write
|
|
206
|
-
|
|
207
|
-
All final files are staged before any destination is replaced. Existing files are renamed to backups, staged files are installed, deletions remain represented by backups, and backups are removed only after the complete operation succeeds. A failure restores every available backup.
|
|
208
|
-
|
|
209
|
-
File permissions are preserved. Leftover staging or backup files continue to block a later run until the user restores or removes them.
|
|
210
|
-
|
|
211
|
-
## Git Ignore Behavior
|
|
212
|
-
|
|
213
|
-
Automatic discovery uses Git's ignore semantics when the workspace is a Git repository. Explicit CSS paths bypass ignore filtering. Outside Git, the fixed ignored-directory list still applies.
|
|
214
|
-
|
|
215
|
-
No new ignore-pattern parser or glob dependency is introduced for the initial implementation.
|
|
216
|
-
|
|
217
|
-
## Determinism
|
|
218
|
-
|
|
219
|
-
Package groups, CSS targets, source files, warnings, candidates, and output operations are sorted by normalized path and stable secondary keys. Repeated previews from the same snapshot must be byte-for-byte identical.
|
|
220
|
-
|
|
221
|
-
The preview and write paths consume the same final plan. A successful second invocation must produce no additional changes.
|
|
222
|
-
|
|
223
|
-
## Parallelism
|
|
224
|
-
|
|
225
|
-
Initial implementation is sequential after eliminating repeated project scans and repeated Tailwind loads. This provides the performance baseline and keeps the planner deterministic.
|
|
226
|
-
|
|
227
|
-
If benchmarks show CSS parsing is material, a later implementation may use a bounded worker pool for immutable CSS reads, parsing, and candidate extraction. Module resolution, edit merging, conflict handling, validation, and commit remain coordinated stages. Running complete per-CSS migrations concurrently is explicitly disallowed.
|
|
228
|
-
|
|
229
|
-
## Testing Strategy
|
|
230
|
-
|
|
231
|
-
### Discovery
|
|
232
|
-
|
|
233
|
-
- no positional path discovers all non-ignored CSS in the current package;
|
|
234
|
-
- a positional ignored CSS file is still processed;
|
|
235
|
-
- Tailwind entries and fixed generated directories are excluded;
|
|
236
|
-
- `--workspaces` discovers nested package groups;
|
|
237
|
-
- default mode does not write another package;
|
|
238
|
-
- workspace references outside writable scope prevent unsafe module cleanup.
|
|
239
|
-
|
|
240
|
-
### Grouping
|
|
241
|
-
|
|
242
|
-
- one package entry is selected automatically;
|
|
243
|
-
- missing and duplicate entries fail deterministically;
|
|
244
|
-
- workspace packages use their own Tailwind installations and entries;
|
|
245
|
-
- a shared package without a unique entry is skipped only with `--force`.
|
|
246
|
-
|
|
247
|
-
### Planning
|
|
248
|
-
|
|
249
|
-
- two CSS Modules can update different references in one JS file without lost edits;
|
|
250
|
-
- multiple selected rules can update one static `className`;
|
|
251
|
-
- cross-stylesheet utility conflicts retain only contributing rules;
|
|
252
|
-
- imports and CSS Module files are deleted only after all batch references are safe;
|
|
253
|
-
- candidates and warnings are deterministically ordered.
|
|
254
|
-
|
|
255
|
-
### Transactions
|
|
256
|
-
|
|
257
|
-
- preview writes nothing;
|
|
258
|
-
- default failure writes nothing;
|
|
259
|
-
- `--force` excludes a failed package group and applies successful groups;
|
|
260
|
-
- candidate or edit-integrity failure aborts even with `--force`;
|
|
261
|
-
- a source changed after planning aborts the write;
|
|
262
|
-
- an interrupted write restores prior content;
|
|
263
|
-
- a second run is a no-op.
|
|
264
|
-
|
|
265
|
-
## Implementation Sequence
|
|
266
|
-
|
|
267
|
-
1. Extend CLI and API options while preserving single-file behavior.
|
|
268
|
-
2. Add package-aware, Git-ignore-aware discovery and Tailwind grouping.
|
|
269
|
-
3. Replace the single-CSS native request with a package batch request.
|
|
270
|
-
4. Resolve all batch CSS Module and global candidates before planning source edits.
|
|
271
|
-
5. Add cross-stylesheet conflict retention and combined edit validation.
|
|
272
|
-
6. Aggregate package reports and implement `--force` package admission.
|
|
273
|
-
7. Add pre-write snapshot checks and retain the existing atomic writer.
|
|
274
|
-
8. Benchmark representative repositories before deciding whether to parallelize parsing.
|
|
275
|
-
|
|
276
|
-
## Success Criteria
|
|
277
|
-
|
|
278
|
-
1. `tw-migrate` previews every eligible CSS target in the current package.
|
|
279
|
-
2. `tw-migrate <file>` preserves exact single-file scope.
|
|
280
|
-
3. `--workspaces` processes package groups with their own Tailwind entries.
|
|
281
|
-
4. No batch edit can overwrite another edit produced from a different source version.
|
|
282
|
-
5. Cross-stylesheet conflicts retain their contributing rules without blocking independent work.
|
|
283
|
-
6. Default writes are all-or-nothing for the requested scope.
|
|
284
|
-
7. `--force` skips only recoverably failed package groups.
|
|
285
|
-
8. Automatic discovery respects Git ignore rules.
|
|
286
|
-
9. External workspace references prevent unsafe CSS Module deletion.
|
|
287
|
-
10. Preview, write, and repeated-run behavior are deterministic.
|
|
288
|
-
|
|
289
|
-
## Deferred Work
|
|
290
|
-
|
|
291
|
-
- SCSS, Sass, and Less parsers and source-preserving edits;
|
|
292
|
-
- Emotion and styled-components discovery from JS/TS inputs;
|
|
293
|
-
- JavaScript positional scope;
|
|
294
|
-
- configurable include/exclude globs;
|
|
295
|
-
- CSS-file-level failure isolation;
|
|
296
|
-
- dependency-graph ownership for shared CSS across multiple Tailwind applications;
|
|
297
|
-
- bounded parallel CSS parsing after benchmarks justify it.
|
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
# RFC: Browser Ecosystem End-to-End Validation
|
|
2
|
-
|
|
3
|
-
## Status
|
|
4
|
-
|
|
5
|
-
Implemented
|
|
6
|
-
|
|
7
|
-
## Summary
|
|
8
|
-
|
|
9
|
-
`tw-migrate` currently verifies planner output, source edits, CLI behavior, and transactional writes without proving that a migrated application renders the same result in a browser. This RFC adds an opt-in Vitest Browser Mode suite that compares migration results in Chromium before and after migration.
|
|
10
|
-
|
|
11
|
-
The suite has two permanent coverage layers:
|
|
12
|
-
|
|
13
|
-
1. Twelve repository-owned controlled fixtures cover the Cartesian product of React+Vite, Next.js, and Vite+HTML with CSS, SCSS, indented Sass, and Less.
|
|
14
|
-
2. Two to five pinned public projects cover installation and runtime compatibility in existing Tailwind CSS v4 applications.
|
|
15
|
-
|
|
16
|
-
External projects supplement the controlled fixtures. They do not replace them. Public projects cannot reliably provide every runtime and stylesheet combination, stable source expectations, or long-lived interaction probes. Controlled fixtures provide that deterministic contract; external projects detect integration assumptions that the fixtures do not model.
|
|
17
|
-
|
|
18
|
-
External repositories are checked out only by the dedicated GitHub Actions workflow. The repository does not vendor them or require contributors to maintain local sibling clones.
|
|
19
|
-
|
|
20
|
-
## Background
|
|
21
|
-
|
|
22
|
-
The existing test layers answer different questions:
|
|
23
|
-
|
|
24
|
-
- Rust tests verify parsing, selector analysis, and utility planning.
|
|
25
|
-
- Node tests verify the public API, source maps, and byte-exact rewrites.
|
|
26
|
-
- Packaged CLI snapshots verify installation, output, exit status, and filesystem behavior.
|
|
27
|
-
|
|
28
|
-
None of these layers starts a real application toolchain and asks Chromium whether the migrated utilities preserve the computed result. A candidate can compile and a source rewrite can look correct while Vite, Next.js, PostCSS, a preprocessor, or Tailwind produces different browser styles.
|
|
29
|
-
|
|
30
|
-
An external-only matrix was considered and rejected. Public Tailwind v4 projects do not provide a dependable twelve-cell set, particularly for `.module.sass` and Next.js Less. Their source, scripts, routes, and selectors also change independently of this repository. Filling the gaps by patching those projects would turn them into less deterministic controlled fixtures with a network dependency.
|
|
31
|
-
|
|
32
|
-
## Goals
|
|
33
|
-
|
|
34
|
-
1. Prove that migrated utilities preserve standard computed styles in Chromium.
|
|
35
|
-
2. Cover React+Vite, Next.js, and Vite+HTML across CSS, SCSS, Sass, and Less.
|
|
36
|
-
3. Exercise CSS Modules in React and Next.js and global stylesheets in static HTML.
|
|
37
|
-
4. Verify exact migration reports and source bytes for controlled cases.
|
|
38
|
-
5. Verify second-run idempotency for every case.
|
|
39
|
-
6. Run against packed root and native packages rather than checkout entrypoints or addons.
|
|
40
|
-
7. Exercise a small set of unmodified public Tailwind v4 projects at immutable commits.
|
|
41
|
-
8. Produce bounded, allowlisted diagnostics for CI failures.
|
|
42
|
-
9. Keep the default Rust, Node, and packaged snapshot suites independent of browsers and external clones.
|
|
43
|
-
|
|
44
|
-
## Non-Goals
|
|
45
|
-
|
|
46
|
-
1. Replacing planner, Node, or packaged CLI tests.
|
|
47
|
-
2. Using screenshots as the pass/fail oracle.
|
|
48
|
-
3. Testing Firefox or WebKit before Chromium coverage proves stable.
|
|
49
|
-
4. Comparing Tailwind or application custom properties.
|
|
50
|
-
5. Exhaustively testing animation timelines or pseudo-elements.
|
|
51
|
-
6. Adding Tailwind, routes, selectors, or migration targets to external projects.
|
|
52
|
-
7. Automatically updating external project pins.
|
|
53
|
-
8. Running external repository code with secrets, write permissions, or persisted checkout credentials.
|
|
54
|
-
9. Making the browser suite part of the default `pnpm test` command.
|
|
55
|
-
|
|
56
|
-
## Coverage Model
|
|
57
|
-
|
|
58
|
-
### Controlled fixtures
|
|
59
|
-
|
|
60
|
-
The controlled corpus owns combination completeness:
|
|
61
|
-
|
|
62
|
-
| Runtime | CSS | SCSS | Sass | Less |
|
|
63
|
-
| --- | --- | --- | --- | --- |
|
|
64
|
-
| React+Vite | required | required | required | required |
|
|
65
|
-
| Next.js | required | required | required | required |
|
|
66
|
-
| Vite+HTML | required | required | required | required |
|
|
67
|
-
|
|
68
|
-
React+Vite and Next.js use CSS Modules. Vite+HTML uses a linked global stylesheet. Each fixture is a small runnable application with exact dependency versions and migration expectations.
|
|
69
|
-
|
|
70
|
-
Every controlled case declares independent probes for:
|
|
71
|
-
|
|
72
|
-
- base state;
|
|
73
|
-
- hover;
|
|
74
|
-
- focus;
|
|
75
|
-
- focus-visible;
|
|
76
|
-
- below the target breakpoint;
|
|
77
|
-
- above the target breakpoint.
|
|
78
|
-
|
|
79
|
-
Each probe uses a stable role, accessible name, text, data attribute, or id selector. Class selectors are prohibited because migration changes classes. A stable `data-identity` sequence proves that baseline and post-migration captures inspect the same elements in the same order.
|
|
80
|
-
|
|
81
|
-
Controlled cases also assert:
|
|
82
|
-
|
|
83
|
-
- the exact first `MigrationReport`;
|
|
84
|
-
- expected candidate tokens and warning output;
|
|
85
|
-
- exact bytes for every migration-owned changed file;
|
|
86
|
-
- an empty second-run diff and `changedFiles` list;
|
|
87
|
-
- an unchanged source-scoped tree after the second run.
|
|
88
|
-
|
|
89
|
-
These fixtures remain after external coverage is added. Removing them would discard the only deterministic proof of the twelve runtime/style cells and the exact rewrite contract.
|
|
90
|
-
|
|
91
|
-
### External ecosystem projects
|
|
92
|
-
|
|
93
|
-
The external corpus owns real-project compatibility rather than matrix completeness. It contains two to five public, non-archived, non-fork repositories pinned by full commit SHA.
|
|
94
|
-
|
|
95
|
-
A project qualifies only when it already has:
|
|
96
|
-
|
|
97
|
-
- Tailwind CSS v4;
|
|
98
|
-
- a committed npm, pnpm, or Yarn lockfile;
|
|
99
|
-
- a portable install and start path;
|
|
100
|
-
- a route that does not require authentication or an external API;
|
|
101
|
-
- a stable non-class selector;
|
|
102
|
-
- an existing stylesheet rule that `tw-migrate` converts;
|
|
103
|
-
- reproducible execution on Ubuntu, macOS, and Windows.
|
|
104
|
-
|
|
105
|
-
External cases keep their original source, package manager, lockfile, and scripts. The harness does not inject routes or probes. Each case verifies the declared migration target, browser equality, and source-scoped idempotency. It does not duplicate the controlled corpus's exhaustive report and byte expectations.
|
|
106
|
-
|
|
107
|
-
External repositories are checked out only in CI under the runner's temporary directory. The workflow uses a pinned repository and commit from typed manifest data, verifies `HEAD`, and discards the checkout with the ephemeral runner. Local controlled runs never depend on `../vite-plus`, another sibling checkout, or a previously cloned project.
|
|
108
|
-
|
|
109
|
-
Network, checkout, install, or upstream script failures fail the external case. The workflow does not silently skip them.
|
|
110
|
-
|
|
111
|
-
#### Admitted external projects
|
|
112
|
-
|
|
113
|
-
The manifest SHA is authoritative. The immutable links below record the evidence used to admit each CI-only project.
|
|
114
|
-
|
|
115
|
-
| Case | Repository status | Tailwind v4 and runtime | Migration witness | Auth/API-free route and selector | Reproducibility |
|
|
116
|
-
| --- | --- | --- | --- | --- | --- |
|
|
117
|
-
| `external-namechecker` (`285e10d3627f3eac5217d69e9eaccee956d7ac70`) | Public, non-archived, non-fork ([repository](https://github.com/toddcooke/namechecker/tree/285e10d3627f3eac5217d69e9eaccee956d7ac70)) | [Next.js and Tailwind v4 dependencies](https://github.com/toddcooke/namechecker/blob/285e10d3627f3eac5217d69e9eaccee956d7ac70/package.json) | [`page.module.css`](https://github.com/toddcooke/namechecker/blob/285e10d3627f3eac5217d69e9eaccee956d7ac70/app/page.module.css) is consumed by [`TailwindLayout.tsx`](https://github.com/toddcooke/namechecker/blob/285e10d3627f3eac5217d69e9eaccee956d7ac70/app/TailwindLayout.tsx); `.main` emits `p-4` | `/` renders without submitting its optional lookup API; unique `main` target. Next.js updates the reviewed `tsconfig.json` path on startup; the harness rejects any other tracked write and restores its exact bytes after every server run | macOS arm64 lifecycle passed 2026-07-24; Linux, macOS, and Windows are required by `.github/workflows/ecosystem.yml` before merge |
|
|
118
|
-
| `external-stylized-components` (`a26df5d21457095e466a41966822edb2ff016cff`) | Public, non-archived, non-fork ([repository](https://github.com/cortiz2894/stylized-components/tree/a26df5d21457095e466a41966822edb2ff016cff)) | [Next.js and Tailwind v4 dependencies](https://github.com/cortiz2894/stylized-components/blob/a26df5d21457095e466a41966822edb2ff016cff/package.json) | [`landing.module.css`](https://github.com/cortiz2894/stylized-components/blob/a26df5d21457095e466a41966822edb2ff016cff/src/app/landing.module.css) is consumed by [`page.tsx`](https://github.com/cortiz2894/stylized-components/blob/a26df5d21457095e466a41966822edb2ff016cff/src/app/page.tsx); `.page` emits `h-[calc(100vh_-_53px)]` | `/` renders its local showcase without credentials; `body > div:not([hidden])` target with `Import GLB` readiness | macOS arm64 lifecycle passed 2026-07-24; Linux, macOS, and Windows are required by `.github/workflows/ecosystem.yml` before merge |
|
|
119
|
-
|
|
120
|
-
A pin is updated only after repeating repository-status review, migration preview, source/idempotency checks, utilities-only comparison, and the three-OS workflow run.
|
|
121
|
-
|
|
122
|
-
### Production CLI smoke
|
|
123
|
-
|
|
124
|
-
One React+Vite CSS fixture also exercises the installed CLI and production build boundary. It captures a clean production baseline, runs package-wide migration through the installed binary, verifies a second CLI no-op, deletes generated output, rebuilds, and compares the production preview.
|
|
125
|
-
|
|
126
|
-
Targeted controlled cases continue to use the installed public `migrate()` API because its structured report supports exact assertions.
|
|
127
|
-
|
|
128
|
-
## Browser Oracle
|
|
129
|
-
|
|
130
|
-
### Dynamic baseline
|
|
131
|
-
|
|
132
|
-
Each case captures its baseline immediately before migration on the same OS and Chromium installation used for the post-migration capture. The suite does not commit computed-style golden files. This avoids treating platform-specific browser serialization as a product regression.
|
|
133
|
-
|
|
134
|
-
### Computed-style comparison
|
|
135
|
-
|
|
136
|
-
For every matched element, the harness reads every property exposed by `getComputedStyle()`, sorts property names, and compares exact values. Properties beginning with `--` are excluded because Tailwind uses internal custom properties whose representation can change while the resulting standard properties remain equal.
|
|
137
|
-
|
|
138
|
-
The exclusion does not permit a no-op migration to pass. Controlled cases pair the browser comparison with exact candidates, reports, and source bytes.
|
|
139
|
-
|
|
140
|
-
### Causal witness
|
|
141
|
-
|
|
142
|
-
Before migration, the harness temporarily withholds the target stylesheet and captures every probe. Each probe must differ from its baseline in at least one standard computed-style property. This proves that the selected stylesheet contributes to every asserted state.
|
|
143
|
-
|
|
144
|
-
After migration, the harness also captures the app with the migrated legacy stylesheet withheld. That utilities-only capture must equal the baseline. Retained legacy CSS therefore cannot hide missing or incorrect generated utilities.
|
|
145
|
-
|
|
146
|
-
### Capture stability
|
|
147
|
-
|
|
148
|
-
Each probe runs in a fresh detached Playwright page. The harness:
|
|
149
|
-
|
|
150
|
-
1. sets the viewport before navigation;
|
|
151
|
-
2. navigates to the declared route;
|
|
152
|
-
3. waits for the declared readiness selector and fonts;
|
|
153
|
-
4. disables transitions and animations;
|
|
154
|
-
5. performs one trusted Playwright interaction when declared;
|
|
155
|
-
6. waits for layout to settle;
|
|
156
|
-
7. checks cardinality and identity;
|
|
157
|
-
8. records standard computed styles.
|
|
158
|
-
|
|
159
|
-
Only capture operations may retry. They receive one initial attempt and at most three retries, each with a fresh page. Migration, source, report, idempotency, and semantic comparison failures fail immediately.
|
|
160
|
-
|
|
161
|
-
## Package Isolation
|
|
162
|
-
|
|
163
|
-
The suite tests the publication boundary rather than the checkout boundary.
|
|
164
|
-
|
|
165
|
-
For each operating system, one package job:
|
|
166
|
-
|
|
167
|
-
1. builds the native addon;
|
|
168
|
-
2. packs the root package and current-platform native package;
|
|
169
|
-
3. records package names, versions, commit, platform, addon digest, and tarball digests in a provenance manifest;
|
|
170
|
-
4. uploads those exact artifacts for the case jobs on that OS.
|
|
171
|
-
|
|
172
|
-
A case job publishes the artifacts to a fresh Verdaccio registry, installs a temporary driver with `--ignore-scripts`, and verifies the resolved root package, native package, and addon against provenance. It rejects checkout paths, workspace links, wrong-platform addons, stale artifacts, and digest mismatches.
|
|
173
|
-
|
|
174
|
-
Verdaccio may proxy normal application dependencies to npm. It must not fetch `tw-migrate` or `tw-migrate-*` packages from upstream. Publishing is enabled only during bootstrap; the install phase uses sealed storage. The registry stops before application code starts.
|
|
175
|
-
|
|
176
|
-
## Case Lifecycle
|
|
177
|
-
|
|
178
|
-
A targeted case follows this order:
|
|
179
|
-
|
|
180
|
-
1. Validate the manifest before executing project data.
|
|
181
|
-
2. Install and verify packed packages.
|
|
182
|
-
3. Start the baseline server and capture every probe.
|
|
183
|
-
4. Stop the server.
|
|
184
|
-
5. Capture the stylesheet-withheld causal witness.
|
|
185
|
-
6. Restore the source and run the first migration.
|
|
186
|
-
7. Verify reports, candidates, changed files, and expected bytes.
|
|
187
|
-
8. Snapshot migration-owned sources and run migration again.
|
|
188
|
-
9. Verify the second report and source tree are unchanged.
|
|
189
|
-
10. Clear generated output and framework caches.
|
|
190
|
-
11. Capture the utilities-only result with migrated legacy CSS withheld.
|
|
191
|
-
12. Restore migrated source, start a fresh server, and capture post-migration probes.
|
|
192
|
-
13. Compare baseline and post-migration cardinality, identity, and computed styles.
|
|
193
|
-
14. Stop all processes and delete temporary successful-run data.
|
|
194
|
-
|
|
195
|
-
Every child process has a bounded timeout and bounded process-tree termination. A teardown failure fails an otherwise successful case and does not replace an earlier primary failure.
|
|
196
|
-
|
|
197
|
-
## Manifest Contract
|
|
198
|
-
|
|
199
|
-
`ecosystem-ci/projects.json` is typed data, not executable configuration. Controlled, production-smoke, and external entries use separate schemas.
|
|
200
|
-
|
|
201
|
-
Validation rejects:
|
|
202
|
-
|
|
203
|
-
- unknown fields or duplicate IDs;
|
|
204
|
-
- duplicate controlled runtime/style cells;
|
|
205
|
-
- class-based selectors;
|
|
206
|
-
- missing readiness, cardinality, source, or probe declarations;
|
|
207
|
-
- non-full external commit SHAs;
|
|
208
|
-
- local, SSH, or non-HTTPS external repository URLs;
|
|
209
|
-
- command strings, shell fragments, custom environment variables, and eval predicates;
|
|
210
|
-
- absolute paths, traversal, and symlinks escaping the case root;
|
|
211
|
-
- package-manager and lockfile mismatches.
|
|
212
|
-
|
|
213
|
-
External commands come from reviewed identifiers and argument arrays. Package managers use exact numeric npm or pnpm versions. Install steps are limited to script-free locked installs and named pnpm workspace build scripts; servers are limited to `run <script>`. Manifest values never become shell programs, runner labels, action names, artifact roots, or workflow expressions.
|
|
214
|
-
|
|
215
|
-
An external entry may declare up to three reviewed `runtimeWrites` paths for framework startup behavior. The harness snapshots those tracked files at the pinned SHA, rejects every undeclared tracked write after each server phase, and restores declared writes to exact bytes. Runtime writes cannot overlap the lockfile, migration source, or Tailwind entry. All other external projects must keep the tracked checkout clean.
|
|
216
|
-
|
|
217
|
-
## CI and Contributor Workflow
|
|
218
|
-
|
|
219
|
-
The browser suite runs in a separate GitHub Actions workflow on:
|
|
220
|
-
|
|
221
|
-
- a designated pull-request label;
|
|
222
|
-
- pushes to `main`;
|
|
223
|
-
- manual dispatch.
|
|
224
|
-
|
|
225
|
-
It does not run on a cron schedule. The workflow uses GitHub-hosted Ubuntu, macOS, and Windows runners with Chromium. Package jobs run once per OS; case jobs run independently for each OS and case with `fail-fast: false`.
|
|
226
|
-
|
|
227
|
-
The workflow uses `pull_request`, never `pull_request_target`. Repository permissions are limited to `contents: read`, checkout credentials are not persisted, and external child processes receive an explicit environment containing only path, home, temporary-directory, required Windows system variables, and `CI=true`. They receive no GitHub, Actions, OIDC, cloud, registry, or repository credentials. The verified installed migration module is loaded before any external command runs.
|
|
228
|
-
|
|
229
|
-
Contributors can run one controlled case with:
|
|
230
|
-
|
|
231
|
-
```bash
|
|
232
|
-
pnpm test:ecosystem --case react-vite-css
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
Running the complete controlled corpus requires an explicit `--all`. External cases are CI-only because they execute pinned third-party source and require network cloning. Contributors use manual workflow dispatch when they need to reproduce an external case in the supported environment.
|
|
236
|
-
|
|
237
|
-
## Failure Diagnostics
|
|
238
|
-
|
|
239
|
-
Each case receives a workflow-owned artifact root and writes a phase ledger before setup begins. On failure, the suite may retain only explicitly declared regular files under that root:
|
|
240
|
-
|
|
241
|
-
- phase ledger;
|
|
242
|
-
- computed-style JSON;
|
|
243
|
-
- migration reports or CLI output;
|
|
244
|
-
- source diff;
|
|
245
|
-
- per-attempt screenshots;
|
|
246
|
-
- browser console and page errors;
|
|
247
|
-
- registry, install, build, and server logs.
|
|
248
|
-
|
|
249
|
-
Upload preparation rejects undeclared files, symlinks, paths outside the root, and oversized artifacts. Raw external child output is written to inert files; workflow logs contain harness-owned summaries.
|
|
250
|
-
|
|
251
|
-
## Version Contract
|
|
252
|
-
|
|
253
|
-
The harness pins compatible versions rather than inheriting application toolchain drift:
|
|
254
|
-
|
|
255
|
-
- Vitest and `@vitest/browser-playwright`: 4.1.10;
|
|
256
|
-
- Playwright: 1.61.1;
|
|
257
|
-
- Verdaccio: 6.8.0;
|
|
258
|
-
- Tailwind CSS integrations: 4.3.3;
|
|
259
|
-
- Vite: 8.1.5;
|
|
260
|
-
- Next.js: 15.5.21;
|
|
261
|
-
- React and ReactDOM: 19.2.8;
|
|
262
|
-
- `next-with-less`: 3.0.1.
|
|
263
|
-
|
|
264
|
-
External projects retain their own pinned dependencies.
|
|
265
|
-
|
|
266
|
-
## Delivery
|
|
267
|
-
|
|
268
|
-
### PR 1: Harness and runtime proof
|
|
269
|
-
|
|
270
|
-
The first PR provides:
|
|
271
|
-
|
|
272
|
-
- strict manifest and case selection;
|
|
273
|
-
- package staging, provenance, and sealed registry installation;
|
|
274
|
-
- browser lifecycle and computed-style oracle;
|
|
275
|
-
- React+Vite, Next.js, and Vite+HTML plain CSS fixtures;
|
|
276
|
-
- the initial three-OS workflow.
|
|
277
|
-
|
|
278
|
-
This slice proves the shared architecture with one stylesheet language across all runtimes.
|
|
279
|
-
|
|
280
|
-
### PR 2: Matrix and ecosystem completion
|
|
281
|
-
|
|
282
|
-
The second PR adds:
|
|
283
|
-
|
|
284
|
-
- the remaining nine SCSS, Sass, and Less controlled cells;
|
|
285
|
-
- the production CLI smoke;
|
|
286
|
-
- two to five admitted external projects with CI-only checkout;
|
|
287
|
-
- final contributor and diagnostics documentation.
|
|
288
|
-
|
|
289
|
-
External projects do not retire or reduce the controlled matrix.
|
|
290
|
-
|
|
291
|
-
## Testing Strategy
|
|
292
|
-
|
|
293
|
-
Harness unit tests cover:
|
|
294
|
-
|
|
295
|
-
- strict manifest validation and case selection;
|
|
296
|
-
- complete controlled inventory;
|
|
297
|
-
- package provenance and installed layout checks;
|
|
298
|
-
- source-scoped idempotency;
|
|
299
|
-
- retry boundaries and process teardown;
|
|
300
|
-
- artifact allowlisting;
|
|
301
|
-
- workflow-to-manifest matrix consistency.
|
|
302
|
-
|
|
303
|
-
Focused browser tests cover one case at a time through the installed package boundary. The full workflow covers every admitted case on all three operating systems.
|
|
304
|
-
|
|
305
|
-
Existing commands retain their current scope:
|
|
306
|
-
|
|
307
|
-
- `pnpm test` runs Rust and Node regression tests without a browser or clone;
|
|
308
|
-
- `pnpm test:snapshots` runs packaged CLI snapshots;
|
|
309
|
-
- `pnpm test:ecosystem --case <id>` runs one controlled browser case;
|
|
310
|
-
- the dedicated workflow owns external project checkout and execution.
|
|
311
|
-
|
|
312
|
-
## Success Criteria
|
|
313
|
-
|
|
314
|
-
1. All twelve controlled runtime/style cells pass exact source, report, idempotency, and browser checks on Ubuntu, macOS, and Windows.
|
|
315
|
-
2. Every controlled probe proves a stylesheet-dependent baseline and utilities-only equivalence.
|
|
316
|
-
3. The production CLI smoke passes clean pre/post builds through packed packages.
|
|
317
|
-
4. Two to five unmodified external projects pass at reviewed full SHAs on all three operating systems.
|
|
318
|
-
5. Package provenance proves that no case loaded the checkout entrypoint, checkout addon, workspace link, or upstream product package.
|
|
319
|
-
6. External code runs only in ephemeral, read-only, no-secret CI jobs.
|
|
320
|
-
7. Failures retain enough bounded evidence to identify the reached phase without uploading arbitrary workspace contents.
|
|
321
|
-
8. Default tests and packaged snapshots require no Chromium, Verdaccio, or external repository checkout.
|
|
322
|
-
|
|
323
|
-
## Accepted Trade-offs
|
|
324
|
-
|
|
325
|
-
1. Controlled fixtures cost maintenance but provide deterministic Cartesian coverage that public projects cannot supply.
|
|
326
|
-
2. External pins may fail because an upstream dependency or registry is unavailable; visible failure is preferred to silent skipping.
|
|
327
|
-
3. Chromium-only coverage does not prove browser-engine equivalence. Firefox and WebKit remain follow-up work until Chromium coverage is stable.
|
|
328
|
-
4. Exact standard computed-style comparison may expose OS serialization differences. Runtime-local baselines reduce this risk without weakening equality on one runner.
|
|
329
|
-
5. Excluding custom properties can miss a custom-property-only regression, so controlled source and report assertions remain mandatory.
|
|
330
|
-
6. OS-by-case jobs cost more than grouped jobs but isolate native-package, project, and diagnostic failures.
|
|
331
|
-
|
|
332
|
-
## Deferred Work
|
|
333
|
-
|
|
334
|
-
- Firefox and WebKit coverage.
|
|
335
|
-
- Automated external pin updates.
|
|
336
|
-
- Remote dependency caches or measured sharding.
|
|
337
|
-
- Shared package-staging code with the packaged snapshot runner.
|
|
338
|
-
- Screenshot-based visual regression coverage for differences that computed styles cannot represent.
|