@plumeria/codemod 18.2.18 → 18.2.20

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 CHANGED
@@ -134,6 +134,7 @@ export function Card({ width }: { width: number }) {
134
134
  /* Card.tsx — after */
135
135
  - import * as css from '@plumeria/core';
136
136
  + import styles from './Card.module.css';
137
+ + export { styles };
137
138
 
138
139
  - export const styles = css.create({
139
140
  - base: { fontSize: 12 },
@@ -143,14 +144,13 @@ export function Card({ width }: { width: number }) {
143
144
  - },
144
145
  - size: (width: number) => ({ width }),
145
146
  - });
146
- + export { styles };
147
147
 
148
148
  export function Card({ width }: { width: number }) {
149
149
  - return <div classStyle={[styles.base, styles.card, styles.size(width)]} />;
150
150
  + return (
151
151
  + <div
152
- + className={`${styles.base} ${styles.card} ${styles.size}`}
153
- + style={{ '--styles-size-width': width }}
152
+ + className={[styles.base, styles.card, styles.size].join(' ')}
153
+ + style={{ ['--styles-size-width' as string]: width }}
154
154
  + />
155
155
  + );
156
156
  }
@@ -162,15 +162,41 @@ equivalent, so each parameter becomes a CSS custom property named
162
162
  values move to the inline `style` prop. If a `style` prop already exists, the
163
163
  custom properties are merged into it.
164
164
 
165
- An array on the styling prop is joined into a template literal. When the array
166
- contains a conditional expression, it uses `.filter(Boolean).join(' ')` instead,
167
- so the falsy branch drops out at runtime:
165
+ An array with no condition in it collapses into one class that `composes` its
166
+ members, so nothing is duplicated:
168
167
 
169
168
  ```diff
169
+ - <div classStyle={[styles.base, styles.card]} />
170
+ + <div className={styles.baseCard} />
171
+ ```
172
+
173
+ Otherwise the array is joined as it stands. `filter` is added only when a
174
+ condition can make a member falsy, so that branch drops out at runtime:
175
+
176
+ ```diff
177
+ - <div classStyle={[styles.base, styles.card, styles.size(width)]} />
178
+ + <div className={[styles.base, styles.card, styles.size].join(' ')} style={{ … }} />
179
+
170
180
  - <div classStyle={[styles.base, active && styles.active]} />
171
181
  + <div className={[styles.base, active && styles.active].filter(Boolean).join(' ')} />
172
182
  ```
173
183
 
184
+ `classStyle={[b, a]}` gives `a` the last word, while a stylesheet gives it to
185
+ whichever rule is written later. The export reproduces the array order by
186
+ emitting the classes in an order that satisfies every call site at once. Where
187
+ two call sites compose the same pair in opposite orders, the one that cannot be
188
+ satisfied carries an extra class holding only the disputed declarations, applied
189
+ under the same condition as the style that has to win:
190
+
191
+ ```diff
192
+ - <div classStyle={[styles.raised, flat && styles.surface]} />
193
+ + <div className={[styles.raised, flat && styles.surface, flat && styles.surfaceOverRaised].filter(Boolean).join(' ')} />
194
+ ```
195
+
196
+ Order is only forced where the two classes actually disagree, measured per
197
+ at-rule and per selector. Anything left unsettled is reported as
198
+ `composition-order`.
199
+
174
200
  ### Global styles
175
201
 
176
202
  `css.createTheme`, `css.keyframes`, and `css.viewTransition` produce rules that
@@ -195,12 +221,25 @@ src/Card.tsx
195
221
  ```
196
222
 
197
223
  Constructs that cannot be resolved statically — dynamic values, computed keys,
198
- runtime function calls, object spreads, and multiple `css.create` calls in one
199
- file — are reported and left in place. A target `*.module.css` that already
200
- exists is reported as `target-exists` and will not be overwritten.
224
+ runtime function calls, and object spreads — are reported and left in place,
225
+ together with every file whose definitions they still need. A target
226
+ `*.module.css` that already exists is reported as `target-exists` and will not be
227
+ overwritten.
228
+
229
+ Several `css.create` calls in one file are not reported: they are written to the
230
+ same stylesheet, and a key an earlier call claimed is renamed.
201
231
 
202
232
  The exit code is 1 while anything remains, so the command composes with a script.
203
233
 
234
+ The migration is idempotent — the first run converts, every run after it is a
235
+ fixed point:
236
+
237
+ ```
238
+ f(f(x)) = f(x)
239
+ ```
240
+
241
+ Clear a report and run again, and only the newly resolvable files move.
242
+
204
243
  Start with `-d, --dry-run` to preview the export without writing files:
205
244
 
206
245
  ```sh
package/dist/cli.js CHANGED
@@ -331,7 +331,7 @@ async function migrateFromCssModules(options) {
331
331
  console.log(line);
332
332
  return 1;
333
333
  }
334
- function releaseConfig(modules, themes, animations) {
334
+ function releaseConfig(modules, themes, animations, statics, values, complete) {
335
335
  return [
336
336
  { ignores: IGNORES },
337
337
  {
@@ -345,7 +345,10 @@ function releaseConfig(modules, themes, animations) {
345
345
  },
346
346
  plugins: { codemod: { rules: { 'release-styles': release_styles_1.releaseStyles } } },
347
347
  rules: {
348
- 'codemod/release-styles': ['error', { modules, themes, animations }],
348
+ 'codemod/release-styles': [
349
+ 'error',
350
+ { modules, themes, animations, statics, values, complete },
351
+ ],
349
352
  },
350
353
  },
351
354
  ];
@@ -362,6 +365,8 @@ async function migrateFromPlumeria(options) {
362
365
  return 0;
363
366
  }
364
367
  for (const sheet of planned.stylesheets) {
368
+ if (sheet.reports.length > 0)
369
+ continue;
365
370
  console.log(` ${relative(sheet.source)} -> ${relative(sheet.target)}`);
366
371
  }
367
372
  if (planned.global) {
@@ -369,7 +374,7 @@ async function migrateFromPlumeria(options) {
369
374
  }
370
375
  const config = {
371
376
  overrideConfigFile: true,
372
- overrideConfig: releaseConfig(planned.modules, planned.themes, planned.animations),
377
+ overrideConfig: releaseConfig(planned.modules, planned.themes, planned.animations, planned.statics, planned.values, planned.complete),
373
378
  errorOnUnmatchedPattern: false,
374
379
  };
375
380
  const preview = await new eslint_1.ESLint(config).lintFiles(targets);
package/dist/migrate.js CHANGED
@@ -83,10 +83,12 @@ function plan(targets) {
83
83
  const converted = (0, css_modules_1.convertStylesheet)(fs.readFileSync(source, 'utf-8'));
84
84
  const target = (0, exports.targetPath)(source);
85
85
  stylesheets.push({ source, target, reports: converted.reports });
86
- modules[path.basename(source)] = {
86
+ modules[source] = {
87
87
  source: `./${path.basename(target, '.ts')}`,
88
+ target,
88
89
  names: converted.names,
89
90
  composes: converted.composes,
91
+ functions: converted.functions,
90
92
  };
91
93
  }
92
94
  return { stylesheets, modules };
package/dist/release.d.ts CHANGED
@@ -11,6 +11,9 @@ export interface ReleasePlan {
11
11
  modules: Record<string, ReleaseModule>;
12
12
  themes: Record<string, string[]>;
13
13
  animations: Record<string, string[]>;
14
+ statics: Record<string, string[]>;
15
+ values: Record<string, Record<string, unknown>>;
16
+ complete: boolean;
14
17
  global?: {
15
18
  target: string;
16
19
  css: string;
package/dist/release.js CHANGED
@@ -40,7 +40,10 @@ exports.formatReleaseReports = formatReleaseReports;
40
40
  const fs = __importStar(require("node:fs"));
41
41
  const path = __importStar(require("node:path"));
42
42
  const parser_1 = require("@typescript-eslint/parser");
43
+ const resolve_1 = require("./resolve");
43
44
  const from_plumeria_1 = require("./transforms/from-plumeria");
45
+ const GLOBAL_OPEN = '/* Generated from css.createTheme, css.keyframes, and css.viewTransition by @plumeria/codemod */';
46
+ const GLOBAL_CLOSE = '/* End of @plumeria/codemod generated styles */';
44
47
  const EXTENSION = /\.(?:[cm]?[jt]sx?)$/;
45
48
  const IGNORED = new Set([
46
49
  'node_modules',
@@ -74,7 +77,45 @@ const findSources = (targets) => {
74
77
  }
75
78
  return found.sort();
76
79
  };
77
- const releasedPath = (source) => source.replace(/\.[^.]+$/, '.module.css');
80
+ const STYLE_PROP = 'classStyle';
81
+ const memberOf = (node) => {
82
+ if (node?.type !== 'MemberExpression' || node.object.type !== 'Identifier')
83
+ return undefined;
84
+ const key = node.computed
85
+ ? node.property.type === 'Literal'
86
+ ? String(node.property.value)
87
+ : undefined
88
+ : node.property.name;
89
+ return key === undefined ? undefined : { object: node.object.name, key };
90
+ };
91
+ const slotOf = (node) => {
92
+ const member = memberOf(node);
93
+ if (member)
94
+ return [member];
95
+ if (node?.type === 'LogicalExpression')
96
+ return slotOf(node.right);
97
+ if (node?.type === 'ConditionalExpression')
98
+ return [...slotOf(node.consequent), ...slotOf(node.alternate)];
99
+ return [];
100
+ };
101
+ const walk = (node, visit) => {
102
+ if (!node || typeof node !== 'object')
103
+ return;
104
+ if (Array.isArray(node)) {
105
+ for (const child of node)
106
+ walk(child, visit);
107
+ return;
108
+ }
109
+ if (typeof node.type !== 'string')
110
+ return;
111
+ visit(node);
112
+ for (const key of Object.keys(node)) {
113
+ if (key === 'parent' || key === 'loc' || key === 'range')
114
+ continue;
115
+ walk(node[key], visit);
116
+ }
117
+ };
118
+ const releasedPath = (source) => source.replace(/(?:\.styles)?\.[^.]+$/, '.module.css');
78
119
  exports.releasedPath = releasedPath;
79
120
  const globalPath = (targets) => {
80
121
  const first = path.resolve(targets[0]);
@@ -93,19 +134,50 @@ function planRelease(targets) {
93
134
  const modules = {};
94
135
  const themes = {};
95
136
  const animations = {};
96
- const globalRules = [];
137
+ const statics = {};
97
138
  const sources = findSources(targets);
98
139
  const sourceText = new Map(sources.map((source) => [source, fs.readFileSync(source, 'utf8')]));
99
140
  const themeValues = new Map();
100
141
  const animationValues = new Map();
142
+ const staticValues = new Map();
143
+ const reportsBySource = new Map();
144
+ const globalBySource = new Map();
145
+ const themeKeys = new Map();
146
+ const animationKeys = new Map();
147
+ const staticKeys = new Map();
148
+ const dependencies = new Map();
149
+ const note = (source, reports) => {
150
+ if (reports.length === 0)
151
+ return;
152
+ reportsBySource.set(source, [
153
+ ...(reportsBySource.get(source) ?? []),
154
+ ...reports,
155
+ ]);
156
+ };
157
+ const addGlobal = (source, css) => {
158
+ const trimmed = css.trim();
159
+ if (trimmed.length === 0)
160
+ return;
161
+ globalBySource.set(source, [
162
+ ...(globalBySource.get(source) ?? []),
163
+ trimmed,
164
+ ]);
165
+ };
166
+ for (const source of sources) {
167
+ try {
168
+ const extracted = (0, from_plumeria_1.extractPlumeriaStatics)(sourceText.get(source));
169
+ if (Object.keys(extracted.bindings).length > 0)
170
+ staticValues.set(source, extracted.bindings);
171
+ }
172
+ catch {
173
+ }
174
+ }
101
175
  for (const source of sources) {
102
176
  try {
103
177
  const extracted = (0, from_plumeria_1.extractPlumeriaThemes)(sourceText.get(source));
104
178
  if (Object.keys(extracted.bindings).length === 0)
105
179
  continue;
106
180
  themeValues.set(source, extracted.bindings);
107
- themes[source] = Object.keys(extracted.bindings);
108
- globalRules.push(extracted.globalCss.trim());
109
181
  }
110
182
  catch {
111
183
  }
@@ -121,11 +193,6 @@ function planRelease(targets) {
121
193
  catch {
122
194
  }
123
195
  }
124
- const sourceForImport = (owner, specifier, candidates) => {
125
- const resolved = path.resolve(path.dirname(owner), specifier);
126
- const stem = resolved.replace(/\.[^.]+$/, '');
127
- return [...candidates].find((candidate) => candidate.replace(/\.[^.]+$/, '') === stem);
128
- };
129
196
  const importedValues = (source, availableBySource) => {
130
197
  const result = {};
131
198
  const ast = (0, parser_1.parse)(sourceText.get(source), {
@@ -134,106 +201,314 @@ function planRelease(targets) {
134
201
  });
135
202
  for (const node of ast.body) {
136
203
  if (node.type !== 'ImportDeclaration' ||
137
- typeof node.source.value !== 'string' ||
138
- !node.source.value.startsWith('.'))
204
+ typeof node.source.value !== 'string')
139
205
  continue;
140
- const target = sourceForImport(source, node.source.value, availableBySource.keys());
206
+ const target = (0, resolve_1.resolveSourcePath)(node.source.value, source);
141
207
  if (!target)
142
208
  continue;
143
209
  const available = availableBySource.get(target);
210
+ if (!available)
211
+ continue;
144
212
  for (const specifier of node.specifiers) {
145
213
  if (specifier.type === 'ImportSpecifier') {
146
214
  const imported = specifier.imported.name ?? specifier.imported.value;
147
- if (available[imported])
215
+ if (available[imported]) {
148
216
  result[specifier.local.name] = available[imported];
217
+ dependencies.set(source, (dependencies.get(source) ?? new Set()).add(target));
218
+ }
149
219
  }
150
220
  else if (specifier.type === 'ImportNamespaceSpecifier') {
151
221
  result[specifier.local.name] = available;
222
+ dependencies.set(source, (dependencies.get(source) ?? new Set()).add(target));
152
223
  }
153
224
  }
154
225
  }
155
226
  return result;
156
227
  };
157
- const importedThemes = (source) => importedValues(source, themeValues);
158
- const importedAnimations = (source) => importedValues(source, animationValues);
159
228
  const importedPlumeriaValues = (source) => {
160
- const merged = { ...importedThemes(source) };
161
- for (const [key, value] of Object.entries(importedAnimations(source))) {
162
- const previous = merged[key];
163
- merged[key] =
164
- previous &&
165
- typeof previous === 'object' &&
166
- value &&
167
- typeof value === 'object'
168
- ? { ...previous, ...value }
169
- : value;
229
+ const merged = {};
230
+ for (const table of [staticValues, themeValues, animationValues]) {
231
+ for (const [key, value] of Object.entries(importedValues(source, table))) {
232
+ const previous = merged[key];
233
+ merged[key] =
234
+ previous &&
235
+ typeof previous === 'object' &&
236
+ value &&
237
+ typeof value === 'object'
238
+ ? { ...previous, ...value }
239
+ : value;
240
+ }
170
241
  }
171
242
  return merged;
172
243
  };
244
+ for (const source of sources) {
245
+ try {
246
+ const extracted = (0, from_plumeria_1.extractPlumeriaThemes)(sourceText.get(source));
247
+ note(source, extracted.reports);
248
+ if (Object.keys(extracted.bindings).length === 0)
249
+ continue;
250
+ themeKeys.set(source, Object.keys(extracted.bindings));
251
+ addGlobal(source, extracted.globalCss);
252
+ }
253
+ catch {
254
+ }
255
+ }
256
+ for (const source of sources) {
257
+ try {
258
+ const extracted = (0, from_plumeria_1.extractPlumeriaStatics)(sourceText.get(source), importedPlumeriaValues(source));
259
+ note(source, extracted.reports);
260
+ if (Object.keys(extracted.bindings).length === 0)
261
+ continue;
262
+ staticValues.set(source, extracted.bindings);
263
+ staticKeys.set(source, Object.keys(extracted.bindings));
264
+ }
265
+ catch {
266
+ }
267
+ }
173
268
  for (const source of sources) {
174
269
  try {
175
270
  const extracted = (0, from_plumeria_1.extractPlumeriaAnimations)(sourceText.get(source), {
176
271
  ...importedPlumeriaValues(source),
177
272
  ...(themeValues.get(source) ?? {}),
178
273
  });
274
+ note(source, extracted.reports);
179
275
  if (Object.keys(extracted.bindings).length === 0)
180
276
  continue;
181
277
  animationValues.set(source, extracted.bindings);
182
- animations[source] = [
278
+ animationKeys.set(source, [
183
279
  ...extracted.keyframes,
184
280
  ...extracted.viewTransitions,
185
- ];
186
- globalRules.push(extracted.globalCss.trim());
281
+ ]);
282
+ addGlobal(source, extracted.globalCss);
187
283
  }
188
284
  catch {
189
285
  }
190
286
  }
287
+ const createBindings = new Map();
191
288
  for (const source of sources) {
192
- let converted;
193
289
  try {
194
- converted = (0, from_plumeria_1.convertPlumeriaModule)(sourceText.get(source), importedPlumeriaValues(source));
290
+ const ast = (0, parser_1.parse)(sourceText.get(source), {
291
+ sourceType: 'module',
292
+ ecmaFeatures: { jsx: true },
293
+ });
294
+ const declared = new Set();
295
+ for (const raw of ast.body) {
296
+ const statement = raw.type === 'ExportNamedDeclaration' ? raw.declaration : raw;
297
+ if (statement?.type !== 'VariableDeclaration')
298
+ continue;
299
+ for (const declaration of statement.declarations) {
300
+ if (declaration.id.type === 'Identifier' &&
301
+ declaration.init?.type === 'CallExpression' &&
302
+ declaration.init.callee.type === 'MemberExpression' &&
303
+ declaration.init.callee.property?.name === 'create')
304
+ declared.add(declaration.id.name);
305
+ }
306
+ }
307
+ if (declared.size > 0)
308
+ createBindings.set(source, declared);
309
+ }
310
+ catch {
311
+ }
312
+ }
313
+ const compositions = new Map();
314
+ for (const source of sources) {
315
+ let ast;
316
+ try {
317
+ ast = (0, parser_1.parse)(sourceText.get(source), {
318
+ sourceType: 'module',
319
+ ecmaFeatures: { jsx: true },
320
+ loc: true,
321
+ range: true,
322
+ });
323
+ }
324
+ catch {
325
+ continue;
326
+ }
327
+ const owners = new Map();
328
+ for (const binding of createBindings.get(source) ?? [])
329
+ owners.set(binding, { source, binding });
330
+ for (const node of ast.body) {
331
+ if (node.type !== 'ImportDeclaration')
332
+ continue;
333
+ const target = (0, resolve_1.resolveSourcePath)(String(node.source.value), source);
334
+ const declared = target ? createBindings.get(target) : undefined;
335
+ if (!target || !declared)
336
+ continue;
337
+ for (const specifier of node.specifiers) {
338
+ if (specifier.type !== 'ImportSpecifier')
339
+ continue;
340
+ const imported = specifier.imported.name ?? specifier.imported.value;
341
+ if (declared.has(imported))
342
+ owners.set(specifier.local.name, {
343
+ source: target,
344
+ binding: imported,
345
+ });
346
+ }
347
+ }
348
+ walk(ast.body, (node) => {
349
+ if (node.type !== 'MemberExpression' ||
350
+ !node.computed ||
351
+ node.property.type === 'Literal' ||
352
+ node.object.type !== 'Identifier')
353
+ return;
354
+ const owner = owners.get(node.object.name);
355
+ if (!owner)
356
+ return;
357
+ note(owner.source, [
358
+ {
359
+ line: node.loc.start.line,
360
+ column: node.loc.start.column + 1,
361
+ kind: 'dynamic-style-access',
362
+ hint: `\`${node.object.name}\` is read with a computed key, which has no class name until it runs.`,
363
+ },
364
+ ]);
365
+ });
366
+ walk(ast.body, (node) => {
367
+ if (node.type !== 'JSXAttribute' ||
368
+ node.name?.name !== STYLE_PROP ||
369
+ node.value?.type !== 'JSXExpressionContainer')
370
+ return;
371
+ const expression = node.value.expression;
372
+ if (expression?.type !== 'ArrayExpression' ||
373
+ expression.elements.length < 2)
374
+ return;
375
+ const slots = expression.elements.map(slotOf);
376
+ if (slots.some((slot) => slot.length === 0))
377
+ return;
378
+ const owners_ = slots.flat().map((part) => owners.get(part.object));
379
+ if (owners_.some((candidate) => !candidate))
380
+ return;
381
+ const owner = owners_[0].source;
382
+ if (owners_.some((candidate) => candidate.source !== owner))
383
+ return;
384
+ const named = slots.map((slot) => slot.map((part) => ({
385
+ binding: owners.get(part.object).binding,
386
+ key: part.key,
387
+ })));
388
+ const collapsible = expression.elements.every((element) => memberOf(element)) &&
389
+ named.every((slot) => slot.length === 1);
390
+ compositions.set(owner, [
391
+ ...(compositions.get(owner) ?? []),
392
+ {
393
+ parts: collapsible ? named.map((slot) => slot[0]) : [],
394
+ slots: named,
395
+ file: source,
396
+ line: node.loc.start.line,
397
+ column: node.loc.start.column + 1,
398
+ },
399
+ ]);
400
+ });
401
+ }
402
+ const converted = new Map();
403
+ for (const source of sources) {
404
+ let module;
405
+ try {
406
+ module = (0, from_plumeria_1.convertPlumeriaModule)(sourceText.get(source), importedPlumeriaValues(source), compositions.get(source) ?? []);
195
407
  }
196
408
  catch {
197
409
  continue;
198
410
  }
199
- if (!converted)
411
+ if (!module)
200
412
  continue;
201
413
  const target = (0, exports.releasedPath)(source);
202
- const specifier = `./${path.basename(target)}`;
203
- const reports = [...converted.reports];
414
+ converted.set(source, module);
415
+ note(source, module.reports);
204
416
  if (fs.existsSync(target)) {
205
- reports.push({
206
- line: 0,
207
- column: 0,
208
- kind: 'target-exists',
209
- hint: `${path.basename(target)} already exists and was not overwritten.`,
417
+ note(source, [
418
+ {
419
+ line: 0,
420
+ column: 0,
421
+ kind: 'target-exists',
422
+ hint: `${path.basename(target)} already exists and was not overwritten.`,
423
+ },
424
+ ]);
425
+ }
426
+ }
427
+ const blocked = new Set(reportsBySource.keys());
428
+ for (let changed = true; changed;) {
429
+ changed = false;
430
+ for (const source of blocked) {
431
+ for (const dependency of dependencies.get(source) ?? []) {
432
+ if (blocked.has(dependency))
433
+ continue;
434
+ blocked.add(dependency);
435
+ note(dependency, [
436
+ {
437
+ line: 0,
438
+ column: 0,
439
+ kind: 'blocked-dependency',
440
+ hint: `${path.relative(path.dirname(dependency), source)} still needs these definitions.`,
441
+ },
442
+ ]);
443
+ changed = true;
444
+ }
445
+ }
446
+ }
447
+ const globalRules = [];
448
+ const values = {};
449
+ for (const source of sources) {
450
+ const reports = reportsBySource.get(source) ?? [];
451
+ const module = converted.get(source);
452
+ if (module) {
453
+ stylesheets.push({
454
+ source,
455
+ target: (0, exports.releasedPath)(source),
456
+ css: module.css,
457
+ reports,
210
458
  });
211
459
  }
212
- stylesheets.push({
213
- source,
214
- target,
215
- css: converted.css,
216
- reports,
217
- });
218
- if (reports.length === 0) {
219
- modules[source] = {
220
- source: specifier,
221
- binding: converted.binding,
222
- ...(Object.keys(converted.functions).length > 0
223
- ? { functions: converted.functions }
224
- : {}),
225
- };
460
+ else if (reports.length > 0) {
461
+ stylesheets.push({ source, target: '', css: '', reports });
226
462
  }
463
+ if (blocked.has(source))
464
+ continue;
465
+ const resolved = {
466
+ ...(staticValues.get(source) ?? {}),
467
+ ...(themeValues.get(source) ?? {}),
468
+ ...(animationValues.get(source) ?? {}),
469
+ };
470
+ if (Object.keys(resolved).length > 0)
471
+ values[source] = resolved;
472
+ globalRules.push(...(globalBySource.get(source) ?? []));
473
+ if (themeKeys.has(source))
474
+ themes[source] = themeKeys.get(source);
475
+ if (animationKeys.has(source))
476
+ animations[source] = animationKeys.get(source);
477
+ if (staticKeys.has(source))
478
+ statics[source] = staticKeys.get(source);
479
+ if (!module)
480
+ continue;
481
+ modules[source] = {
482
+ source: `./${path.basename((0, exports.releasedPath)(source))}`,
483
+ target: (0, exports.releasedPath)(source),
484
+ binding: module.binding,
485
+ ...(module.definitionOnly ? { definitionOnly: true } : {}),
486
+ ...(Object.keys(module.aliases).length > 0
487
+ ? { aliases: module.aliases }
488
+ : {}),
489
+ ...(Object.keys(module.functions).length > 0
490
+ ? { functions: module.functions }
491
+ : {}),
492
+ ...(Object.keys(module.merges).length > 0
493
+ ? { merges: module.merges }
494
+ : {}),
495
+ ...(Object.keys(module.overrides).length > 0
496
+ ? { overrides: module.overrides }
497
+ : {}),
498
+ };
227
499
  }
228
500
  return {
229
501
  stylesheets,
230
502
  modules,
231
503
  themes,
232
504
  animations,
505
+ statics,
506
+ values,
507
+ complete: stylesheets.every((sheet) => sheet.reports.length === 0),
233
508
  global: globalRules.length > 0
234
509
  ? {
235
510
  target: globalPath(targets),
236
- css: `/* Generated from css.createTheme, css.keyframes, and css.viewTransition by @plumeria/codemod */\n${globalRules.join('\n\n')}\n`,
511
+ css: `${GLOBAL_OPEN}\n${globalRules.join('\n\n')}\n${GLOBAL_CLOSE}\n`,
237
512
  }
238
513
  : undefined,
239
514
  };
@@ -249,6 +524,13 @@ function writeRelease(plan) {
249
524
  const previous = fs.existsSync(plan.global.target)
250
525
  ? fs.readFileSync(plan.global.target, 'utf8')
251
526
  : '';
527
+ const opening = previous.indexOf(GLOBAL_OPEN);
528
+ const closing = previous.lastIndexOf(GLOBAL_CLOSE);
529
+ if (opening !== -1 && closing > opening) {
530
+ const kept = `${previous.slice(0, opening)}${previous.slice(closing + GLOBAL_CLOSE.length)}`;
531
+ fs.writeFileSync(plan.global.target, `${kept.replace(/\n+$/, '\n')}${plan.global.css}`, 'utf8');
532
+ return;
533
+ }
252
534
  const separator = previous.length > 0 && !previous.endsWith('\n') ? '\n\n' : '';
253
535
  fs.writeFileSync(plan.global.target, `${previous}${separator}${plan.global.css}`, 'utf8');
254
536
  }
@@ -0,0 +1,2 @@
1
+ export declare const resetTsconfigCache: () => void;
2
+ export declare function resolveSourcePath(specifier: string, importer: string): string | null;