@plumeria/codemod 18.2.17 → 18.2.18
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 +122 -0
- package/dist/cli.js +73 -4
- package/dist/release.d.ts +22 -0
- package/dist/release.js +268 -0
- package/dist/transforms/from-plumeria.d.ts +37 -0
- package/dist/transforms/from-plumeria.js +436 -0
- package/dist/transforms/release-styles.d.ts +22 -0
- package/dist/transforms/release-styles.js +344 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@ No install needed — run it with `npx`.
|
|
|
5
5
|
|
|
6
6
|
```sh
|
|
7
7
|
npx @plumeria/codemod migrate --from css-modules
|
|
8
|
+
npx @plumeria/codemod migrate --from plumeria
|
|
8
9
|
npx @plumeria/codemod rename-prop classStyle sx
|
|
9
10
|
```
|
|
10
11
|
|
|
@@ -93,6 +94,127 @@ Any rules that cannot be converted are reported in the same run, and the exit
|
|
|
93
94
|
code remains 1 while such rules are present. Remove `--dry-run` to write the
|
|
94
95
|
generated modules and consumer changes.
|
|
95
96
|
|
|
97
|
+
## `migrate --from plumeria`
|
|
98
|
+
|
|
99
|
+
The reverse of the forward migration. Every `css.create` definition under the
|
|
100
|
+
paths becomes a `*.module.css` beside it, and the files that use it are rewritten
|
|
101
|
+
— the import, the `classStyle` prop, and the style composition:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
npx @plumeria/codemod migrate --from plumeria [paths...]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
/* Card.tsx — before */
|
|
109
|
+
import * as css from '@plumeria/core';
|
|
110
|
+
|
|
111
|
+
export const styles = css.create({
|
|
112
|
+
base: { fontSize: 12 },
|
|
113
|
+
card: {
|
|
114
|
+
padding: 16,
|
|
115
|
+
':hover': { background: 'teal' },
|
|
116
|
+
},
|
|
117
|
+
size: (width: number) => ({ width }),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
export function Card({ width }: { width: number }) {
|
|
121
|
+
return <div classStyle={[styles.base, styles.card, styles.size(width)]} />;
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```css
|
|
126
|
+
/* Card.module.css — generated */
|
|
127
|
+
.base { font-size: 12px }
|
|
128
|
+
.card { padding: 16px }
|
|
129
|
+
.card:hover { background: teal }
|
|
130
|
+
.size { width: var(--styles-size-width) }
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```diff
|
|
134
|
+
/* Card.tsx — after */
|
|
135
|
+
- import * as css from '@plumeria/core';
|
|
136
|
+
+ import styles from './Card.module.css';
|
|
137
|
+
|
|
138
|
+
- export const styles = css.create({
|
|
139
|
+
- base: { fontSize: 12 },
|
|
140
|
+
- card: {
|
|
141
|
+
- padding: 16,
|
|
142
|
+
- ':hover': { background: 'teal' },
|
|
143
|
+
- },
|
|
144
|
+
- size: (width: number) => ({ width }),
|
|
145
|
+
- });
|
|
146
|
+
+ export { styles };
|
|
147
|
+
|
|
148
|
+
export function Card({ width }: { width: number }) {
|
|
149
|
+
- return <div classStyle={[styles.base, styles.card, styles.size(width)]} />;
|
|
150
|
+
+ return (
|
|
151
|
+
+ <div
|
|
152
|
+
+ className={`${styles.base} ${styles.card} ${styles.size}`}
|
|
153
|
+
+ style={{ '--styles-size-width': width }}
|
|
154
|
+
+ />
|
|
155
|
+
+ );
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
A function style key such as `(width) => ({ width })` has no CSS Modules
|
|
160
|
+
equivalent, so each parameter becomes a CSS custom property named
|
|
161
|
+
`--<binding>-<key>-<param>`. The class name stays in `className`; the argument
|
|
162
|
+
values move to the inline `style` prop. If a `style` prop already exists, the
|
|
163
|
+
custom properties are merged into it.
|
|
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:
|
|
168
|
+
|
|
169
|
+
```diff
|
|
170
|
+
- <div classStyle={[styles.base, active && styles.active]} />
|
|
171
|
+
+ <div className={[styles.base, active && styles.active].filter(Boolean).join(' ')} />
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Global styles
|
|
175
|
+
|
|
176
|
+
`css.createTheme`, `css.keyframes`, and `css.viewTransition` produce rules that
|
|
177
|
+
belong in a global stylesheet, not in a CSS Module. The codemod extracts them and
|
|
178
|
+
appends them to `src/styles/global.css` (or `styles/global.css` when `src` does
|
|
179
|
+
not exist). Theme tokens become CSS custom properties under `:where(:root)` and
|
|
180
|
+
the theme selector; keyframes become `@keyframes kf-<hash>`; view transitions
|
|
181
|
+
become `::view-transition-*` pseudo-element rules. References in style objects
|
|
182
|
+
are replaced by their generated names.
|
|
183
|
+
|
|
184
|
+
`css.createStatic` values are statically evaluated and inlined directly into the
|
|
185
|
+
generated CSS rules — no separate output file is needed.
|
|
186
|
+
|
|
187
|
+
### What it reports instead of converting
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
src/Card.tsx
|
|
191
|
+
8:5 dynamic-value
|
|
192
|
+
The value of `color` cannot be represented statically.
|
|
193
|
+
12:3 spread-create
|
|
194
|
+
Top-level spreads cannot name a CSS Module class.
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
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.
|
|
201
|
+
|
|
202
|
+
The exit code is 1 while anything remains, so the command composes with a script.
|
|
203
|
+
|
|
204
|
+
Start with `-d, --dry-run` to preview the export without writing files:
|
|
205
|
+
|
|
206
|
+
```sh
|
|
207
|
+
npx @plumeria/codemod migrate --from plumeria --dry-run [paths...]
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
src/Card.tsx -> src/Card.module.css
|
|
212
|
+
global styles -> src/styles/global.css
|
|
213
|
+
|
|
214
|
+
1 style module(s) would be exported, 1 source file(s) rewritten.
|
|
215
|
+
Run without --dry-run to apply.
|
|
216
|
+
```
|
|
217
|
+
|
|
96
218
|
## `rename-prop`
|
|
97
219
|
|
|
98
220
|
Renames the styling prop across your source. The names are arguments, so it
|
package/dist/cli.js
CHANGED
|
@@ -43,10 +43,12 @@ const eslint_1 = require("eslint");
|
|
|
43
43
|
const tsParser = __importStar(require("@typescript-eslint/parser"));
|
|
44
44
|
const rename_prop_1 = require("./transforms/rename-prop");
|
|
45
45
|
const adopt_styles_1 = require("./transforms/adopt-styles");
|
|
46
|
+
const release_styles_1 = require("./transforms/release-styles");
|
|
46
47
|
const migrate_1 = require("./migrate");
|
|
48
|
+
const release_1 = require("./release");
|
|
47
49
|
const { version } = require('../package.json');
|
|
48
50
|
const TRANSFORMS = ['rename-prop', 'migrate'];
|
|
49
|
-
const SOURCES = ['css-modules'];
|
|
51
|
+
const SOURCES = ['css-modules', 'plumeria'];
|
|
50
52
|
const SOURCE_FILES = '**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}';
|
|
51
53
|
const IGNORES = [
|
|
52
54
|
'**/dist/**',
|
|
@@ -62,6 +64,7 @@ const USAGE = `plumeria-codemod — codemods for migrating Plumeria APIs
|
|
|
62
64
|
Usage
|
|
63
65
|
npx @plumeria/codemod rename-prop <from> <to> [paths...]
|
|
64
66
|
npx @plumeria/codemod migrate --from css-modules [paths...]
|
|
67
|
+
npx @plumeria/codemod migrate --from plumeria [paths...]
|
|
65
68
|
|
|
66
69
|
Options
|
|
67
70
|
-d, --dry-run report what would change without writing
|
|
@@ -74,6 +77,7 @@ Examples
|
|
|
74
77
|
npx @plumeria/codemod rename-prop classStyle sx
|
|
75
78
|
npx @plumeria/codemod rename-prop classStyle sx src app --dry-run
|
|
76
79
|
npx @plumeria/codemod migrate --from css-modules
|
|
80
|
+
npx @plumeria/codemod migrate --from plumeria --dry-run
|
|
77
81
|
|
|
78
82
|
Paths default to the current directory. After the rewrite, point the toolchain
|
|
79
83
|
at the new name with \`styleProp\` on your bundler plugin, and with
|
|
@@ -200,8 +204,11 @@ async function main(argv) {
|
|
|
200
204
|
}
|
|
201
205
|
if (!options)
|
|
202
206
|
return 0;
|
|
203
|
-
if (options.transform === 'migrate')
|
|
204
|
-
return
|
|
207
|
+
if (options.transform === 'migrate') {
|
|
208
|
+
return options.source === 'plumeria'
|
|
209
|
+
? migrateFromPlumeria(options)
|
|
210
|
+
: migrateFromCssModules(options);
|
|
211
|
+
}
|
|
205
212
|
const { from, to, targets, dryRun } = options;
|
|
206
213
|
const baseConfig = {
|
|
207
214
|
overrideConfigFile: true,
|
|
@@ -285,7 +292,7 @@ function migrateConfig(modules) {
|
|
|
285
292
|
},
|
|
286
293
|
];
|
|
287
294
|
}
|
|
288
|
-
async function
|
|
295
|
+
async function migrateFromCssModules(options) {
|
|
289
296
|
const { targets, dryRun } = options;
|
|
290
297
|
const cwd = process.cwd();
|
|
291
298
|
const relative = (filePath) => node_path_1.default.relative(cwd, filePath) || '.';
|
|
@@ -324,3 +331,65 @@ async function migrate(options) {
|
|
|
324
331
|
console.log(line);
|
|
325
332
|
return 1;
|
|
326
333
|
}
|
|
334
|
+
function releaseConfig(modules, themes, animations) {
|
|
335
|
+
return [
|
|
336
|
+
{ ignores: IGNORES },
|
|
337
|
+
{
|
|
338
|
+
files: [SOURCE_FILES],
|
|
339
|
+
languageOptions: {
|
|
340
|
+
parser: tsParser,
|
|
341
|
+
parserOptions: {
|
|
342
|
+
sourceType: 'module',
|
|
343
|
+
ecmaFeatures: { jsx: true },
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
plugins: { codemod: { rules: { 'release-styles': release_styles_1.releaseStyles } } },
|
|
347
|
+
rules: {
|
|
348
|
+
'codemod/release-styles': ['error', { modules, themes, animations }],
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
];
|
|
352
|
+
}
|
|
353
|
+
async function migrateFromPlumeria(options) {
|
|
354
|
+
const { targets, dryRun } = options;
|
|
355
|
+
const cwd = process.cwd();
|
|
356
|
+
const relative = (filePath) => node_path_1.default.relative(cwd, filePath) || '.';
|
|
357
|
+
const planned = (0, release_1.planRelease)(targets);
|
|
358
|
+
if (planned.stylesheets.length === 0 &&
|
|
359
|
+
Object.keys(planned.themes).length === 0 &&
|
|
360
|
+
Object.keys(planned.animations).length === 0) {
|
|
361
|
+
console.log('No Plumeria styles found.');
|
|
362
|
+
return 0;
|
|
363
|
+
}
|
|
364
|
+
for (const sheet of planned.stylesheets) {
|
|
365
|
+
console.log(` ${relative(sheet.source)} -> ${relative(sheet.target)}`);
|
|
366
|
+
}
|
|
367
|
+
if (planned.global) {
|
|
368
|
+
console.log(` global styles -> ${relative(planned.global.target)}`);
|
|
369
|
+
}
|
|
370
|
+
const config = {
|
|
371
|
+
overrideConfigFile: true,
|
|
372
|
+
overrideConfig: releaseConfig(planned.modules, planned.themes, planned.animations),
|
|
373
|
+
errorOnUnmatchedPattern: false,
|
|
374
|
+
};
|
|
375
|
+
const preview = await new eslint_1.ESLint(config).lintFiles(targets);
|
|
376
|
+
const touched = preview.filter((result) => result.messages.some((message) => message.fix));
|
|
377
|
+
if (dryRun) {
|
|
378
|
+
console.log(`\n${Object.keys(planned.modules).length} style module(s) would be exported, ${touched.length} source file(s) rewritten.`);
|
|
379
|
+
console.log('Run without --dry-run to apply.');
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
warnIfDirty();
|
|
383
|
+
(0, release_1.writeRelease)(planned);
|
|
384
|
+
const applied = await new eslint_1.ESLint({ ...config, fix: true }).lintFiles(targets);
|
|
385
|
+
await eslint_1.ESLint.outputFixes(applied);
|
|
386
|
+
console.log(`\n✔ exported ${Object.keys(planned.modules).length} style module(s) and rewrote ${touched.length} source file(s).`);
|
|
387
|
+
}
|
|
388
|
+
const lines = (0, release_1.formatReleaseReports)(planned.stylesheets, cwd);
|
|
389
|
+
if (lines.length === 0)
|
|
390
|
+
return 0;
|
|
391
|
+
console.log('\nLeft in Plumeria — these styles were not exported:\n');
|
|
392
|
+
for (const line of lines)
|
|
393
|
+
console.log(line);
|
|
394
|
+
return 1;
|
|
395
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PlumeriaReport } from './transforms/from-plumeria';
|
|
2
|
+
import type { ReleaseModule } from './transforms/release-styles';
|
|
3
|
+
export interface ReleasedStylesheet {
|
|
4
|
+
source: string;
|
|
5
|
+
target: string;
|
|
6
|
+
css: string;
|
|
7
|
+
reports: PlumeriaReport[];
|
|
8
|
+
}
|
|
9
|
+
export interface ReleasePlan {
|
|
10
|
+
stylesheets: ReleasedStylesheet[];
|
|
11
|
+
modules: Record<string, ReleaseModule>;
|
|
12
|
+
themes: Record<string, string[]>;
|
|
13
|
+
animations: Record<string, string[]>;
|
|
14
|
+
global?: {
|
|
15
|
+
target: string;
|
|
16
|
+
css: string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export declare const releasedPath: (source: string) => string;
|
|
20
|
+
export declare function planRelease(targets: string[]): ReleasePlan;
|
|
21
|
+
export declare function writeRelease(plan: ReleasePlan): void;
|
|
22
|
+
export declare function formatReleaseReports(sheets: ReleasedStylesheet[], cwd: string): string[];
|
package/dist/release.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.releasedPath = void 0;
|
|
37
|
+
exports.planRelease = planRelease;
|
|
38
|
+
exports.writeRelease = writeRelease;
|
|
39
|
+
exports.formatReleaseReports = formatReleaseReports;
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
41
|
+
const path = __importStar(require("node:path"));
|
|
42
|
+
const parser_1 = require("@typescript-eslint/parser");
|
|
43
|
+
const from_plumeria_1 = require("./transforms/from-plumeria");
|
|
44
|
+
const EXTENSION = /\.(?:[cm]?[jt]sx?)$/;
|
|
45
|
+
const IGNORED = new Set([
|
|
46
|
+
'node_modules',
|
|
47
|
+
'dist',
|
|
48
|
+
'build',
|
|
49
|
+
'out',
|
|
50
|
+
'.next',
|
|
51
|
+
'.git',
|
|
52
|
+
'coverage',
|
|
53
|
+
]);
|
|
54
|
+
const findSources = (targets) => {
|
|
55
|
+
const found = [];
|
|
56
|
+
const walk = (entry) => {
|
|
57
|
+
const stats = fs.statSync(entry);
|
|
58
|
+
if (stats.isFile()) {
|
|
59
|
+
if (EXTENSION.test(entry))
|
|
60
|
+
found.push(path.resolve(entry));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
for (const name of fs.readdirSync(entry)) {
|
|
64
|
+
if (!IGNORED.has(name))
|
|
65
|
+
walk(path.join(entry, name));
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
for (const target of targets) {
|
|
69
|
+
try {
|
|
70
|
+
walk(target);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return found.sort();
|
|
76
|
+
};
|
|
77
|
+
const releasedPath = (source) => source.replace(/\.[^.]+$/, '.module.css');
|
|
78
|
+
exports.releasedPath = releasedPath;
|
|
79
|
+
const globalPath = (targets) => {
|
|
80
|
+
const first = path.resolve(targets[0]);
|
|
81
|
+
const root = fs.existsSync(first) && fs.statSync(first).isDirectory()
|
|
82
|
+
? first
|
|
83
|
+
: path.dirname(first);
|
|
84
|
+
const candidates = [
|
|
85
|
+
path.join(root, 'src', 'styles', 'global.css'),
|
|
86
|
+
path.join(root, 'styles', 'global.css'),
|
|
87
|
+
];
|
|
88
|
+
return (candidates.find((candidate) => fs.existsSync(candidate)) ??
|
|
89
|
+
(fs.existsSync(path.join(root, 'src')) ? candidates[0] : candidates[1]));
|
|
90
|
+
};
|
|
91
|
+
function planRelease(targets) {
|
|
92
|
+
const stylesheets = [];
|
|
93
|
+
const modules = {};
|
|
94
|
+
const themes = {};
|
|
95
|
+
const animations = {};
|
|
96
|
+
const globalRules = [];
|
|
97
|
+
const sources = findSources(targets);
|
|
98
|
+
const sourceText = new Map(sources.map((source) => [source, fs.readFileSync(source, 'utf8')]));
|
|
99
|
+
const themeValues = new Map();
|
|
100
|
+
const animationValues = new Map();
|
|
101
|
+
for (const source of sources) {
|
|
102
|
+
try {
|
|
103
|
+
const extracted = (0, from_plumeria_1.extractPlumeriaThemes)(sourceText.get(source));
|
|
104
|
+
if (Object.keys(extracted.bindings).length === 0)
|
|
105
|
+
continue;
|
|
106
|
+
themeValues.set(source, extracted.bindings);
|
|
107
|
+
themes[source] = Object.keys(extracted.bindings);
|
|
108
|
+
globalRules.push(extracted.globalCss.trim());
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
for (const source of sources) {
|
|
114
|
+
try {
|
|
115
|
+
const extracted = (0, from_plumeria_1.extractPlumeriaAnimations)(sourceText.get(source), {
|
|
116
|
+
...(themeValues.get(source) ?? {}),
|
|
117
|
+
});
|
|
118
|
+
if (Object.keys(extracted.bindings).length > 0)
|
|
119
|
+
animationValues.set(source, extracted.bindings);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
}
|
|
123
|
+
}
|
|
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
|
+
const importedValues = (source, availableBySource) => {
|
|
130
|
+
const result = {};
|
|
131
|
+
const ast = (0, parser_1.parse)(sourceText.get(source), {
|
|
132
|
+
sourceType: 'module',
|
|
133
|
+
ecmaFeatures: { jsx: true },
|
|
134
|
+
});
|
|
135
|
+
for (const node of ast.body) {
|
|
136
|
+
if (node.type !== 'ImportDeclaration' ||
|
|
137
|
+
typeof node.source.value !== 'string' ||
|
|
138
|
+
!node.source.value.startsWith('.'))
|
|
139
|
+
continue;
|
|
140
|
+
const target = sourceForImport(source, node.source.value, availableBySource.keys());
|
|
141
|
+
if (!target)
|
|
142
|
+
continue;
|
|
143
|
+
const available = availableBySource.get(target);
|
|
144
|
+
for (const specifier of node.specifiers) {
|
|
145
|
+
if (specifier.type === 'ImportSpecifier') {
|
|
146
|
+
const imported = specifier.imported.name ?? specifier.imported.value;
|
|
147
|
+
if (available[imported])
|
|
148
|
+
result[specifier.local.name] = available[imported];
|
|
149
|
+
}
|
|
150
|
+
else if (specifier.type === 'ImportNamespaceSpecifier') {
|
|
151
|
+
result[specifier.local.name] = available;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return result;
|
|
156
|
+
};
|
|
157
|
+
const importedThemes = (source) => importedValues(source, themeValues);
|
|
158
|
+
const importedAnimations = (source) => importedValues(source, animationValues);
|
|
159
|
+
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;
|
|
170
|
+
}
|
|
171
|
+
return merged;
|
|
172
|
+
};
|
|
173
|
+
for (const source of sources) {
|
|
174
|
+
try {
|
|
175
|
+
const extracted = (0, from_plumeria_1.extractPlumeriaAnimations)(sourceText.get(source), {
|
|
176
|
+
...importedPlumeriaValues(source),
|
|
177
|
+
...(themeValues.get(source) ?? {}),
|
|
178
|
+
});
|
|
179
|
+
if (Object.keys(extracted.bindings).length === 0)
|
|
180
|
+
continue;
|
|
181
|
+
animationValues.set(source, extracted.bindings);
|
|
182
|
+
animations[source] = [
|
|
183
|
+
...extracted.keyframes,
|
|
184
|
+
...extracted.viewTransitions,
|
|
185
|
+
];
|
|
186
|
+
globalRules.push(extracted.globalCss.trim());
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
for (const source of sources) {
|
|
192
|
+
let converted;
|
|
193
|
+
try {
|
|
194
|
+
converted = (0, from_plumeria_1.convertPlumeriaModule)(sourceText.get(source), importedPlumeriaValues(source));
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (!converted)
|
|
200
|
+
continue;
|
|
201
|
+
const target = (0, exports.releasedPath)(source);
|
|
202
|
+
const specifier = `./${path.basename(target)}`;
|
|
203
|
+
const reports = [...converted.reports];
|
|
204
|
+
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.`,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
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
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
stylesheets,
|
|
230
|
+
modules,
|
|
231
|
+
themes,
|
|
232
|
+
animations,
|
|
233
|
+
global: globalRules.length > 0
|
|
234
|
+
? {
|
|
235
|
+
target: globalPath(targets),
|
|
236
|
+
css: `/* Generated from css.createTheme, css.keyframes, and css.viewTransition by @plumeria/codemod */\n${globalRules.join('\n\n')}\n`,
|
|
237
|
+
}
|
|
238
|
+
: undefined,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function writeRelease(plan) {
|
|
242
|
+
for (const sheet of plan.stylesheets) {
|
|
243
|
+
if (sheet.reports.length > 0)
|
|
244
|
+
continue;
|
|
245
|
+
fs.writeFileSync(sheet.target, sheet.css, 'utf8');
|
|
246
|
+
}
|
|
247
|
+
if (plan.global) {
|
|
248
|
+
fs.mkdirSync(path.dirname(plan.global.target), { recursive: true });
|
|
249
|
+
const previous = fs.existsSync(plan.global.target)
|
|
250
|
+
? fs.readFileSync(plan.global.target, 'utf8')
|
|
251
|
+
: '';
|
|
252
|
+
const separator = previous.length > 0 && !previous.endsWith('\n') ? '\n\n' : '';
|
|
253
|
+
fs.writeFileSync(plan.global.target, `${previous}${separator}${plan.global.css}`, 'utf8');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
function formatReleaseReports(sheets, cwd) {
|
|
257
|
+
const lines = [];
|
|
258
|
+
for (const sheet of sheets) {
|
|
259
|
+
if (sheet.reports.length === 0)
|
|
260
|
+
continue;
|
|
261
|
+
lines.push(path.relative(cwd, sheet.source) || sheet.source);
|
|
262
|
+
for (const report of sheet.reports) {
|
|
263
|
+
lines.push(` ${report.line}:${report.column} ${report.kind}`);
|
|
264
|
+
lines.push(` ${report.hint}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return lines;
|
|
268
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface PlumeriaReport {
|
|
2
|
+
line: number;
|
|
3
|
+
column: number;
|
|
4
|
+
kind: string;
|
|
5
|
+
hint: string;
|
|
6
|
+
}
|
|
7
|
+
export interface PlumeriaModule {
|
|
8
|
+
binding: string;
|
|
9
|
+
css: string;
|
|
10
|
+
globalCss: string;
|
|
11
|
+
keys: string[];
|
|
12
|
+
functions: Record<string, {
|
|
13
|
+
params: string[];
|
|
14
|
+
variables: string[];
|
|
15
|
+
}>;
|
|
16
|
+
reports: PlumeriaReport[];
|
|
17
|
+
}
|
|
18
|
+
export interface ThemeExtraction {
|
|
19
|
+
bindings: Record<string, Record<string, string>>;
|
|
20
|
+
globalCss: string;
|
|
21
|
+
reports: PlumeriaReport[];
|
|
22
|
+
}
|
|
23
|
+
export interface AnimationExtraction {
|
|
24
|
+
bindings: Record<string, string>;
|
|
25
|
+
keyframes: string[];
|
|
26
|
+
viewTransitions: string[];
|
|
27
|
+
globalCss: string;
|
|
28
|
+
reports: PlumeriaReport[];
|
|
29
|
+
}
|
|
30
|
+
export declare const __private: {
|
|
31
|
+
propertyKey: (node: any) => string | undefined;
|
|
32
|
+
memberPath: (node: any) => string[] | undefined;
|
|
33
|
+
evaluate: (node: any, values: Map<string, unknown>) => unknown;
|
|
34
|
+
};
|
|
35
|
+
export declare function extractPlumeriaThemes(source: string): ThemeExtraction;
|
|
36
|
+
export declare function extractPlumeriaAnimations(source: string, externalValues?: Record<string, unknown>): AnimationExtraction;
|
|
37
|
+
export declare function convertPlumeriaModule(source: string, externalValues?: Record<string, unknown>): PlumeriaModule | null;
|