styled-components-to-stylex-codemod 0.0.1
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/LICENSE +21 -0
- package/README.md +176 -0
- package/dist/index.d.mts +83 -0
- package/dist/index.mjs +142 -0
- package/dist/logger-D09HOyqF.mjs +32 -0
- package/dist/transform-types-CCX_WVPh.d.mts +157 -0
- package/dist/transform.d.mts +18 -0
- package/dist/transform.mjs +5723 -0
- package/package.json +79 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Kenneth Skovhus
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# styled-components-to-stylex-codemod
|
|
2
|
+
|
|
3
|
+
Transform styled-components to StyleX.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install styled-components-to-stylex-codemod
|
|
9
|
+
# or
|
|
10
|
+
pnpm add styled-components-to-stylex-codemod
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Use `runTransform` to transform files matching a glob pattern:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import {
|
|
19
|
+
runTransform,
|
|
20
|
+
defineAdapter,
|
|
21
|
+
} from "styled-components-to-stylex-codemod";
|
|
22
|
+
|
|
23
|
+
const adapter = defineAdapter({
|
|
24
|
+
resolveValue(ctx) {
|
|
25
|
+
if (ctx.kind === "theme") {
|
|
26
|
+
// Called for patterns like: ${(props) => props.theme.colors.primary}
|
|
27
|
+
// `ctx.path` is the dotted path after `theme.`
|
|
28
|
+
const varName = ctx.path.replace(/\./g, "_");
|
|
29
|
+
return {
|
|
30
|
+
expr: `tokens.${varName}`,
|
|
31
|
+
imports: [
|
|
32
|
+
{
|
|
33
|
+
from: { kind: "specifier", value: "./design-system.stylex" },
|
|
34
|
+
names: [{ imported: "tokens" }],
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (ctx.kind === "cssVariable") {
|
|
41
|
+
// Called for CSS values containing `var(--...)`
|
|
42
|
+
// Note: `fallback` is the raw fallback string inside `var(--x, <fallback>)` (if present).
|
|
43
|
+
// Note: `definedValue` is populated when the transformer sees a local `--x: <value>` definition.
|
|
44
|
+
const { name, fallback, definedValue } = ctx;
|
|
45
|
+
|
|
46
|
+
// Example: lift `var(--base-size)` to StyleX vars, and optionally drop a matching local definition.
|
|
47
|
+
if (name === "--base-size") {
|
|
48
|
+
return {
|
|
49
|
+
expr: "calcVars.baseSize",
|
|
50
|
+
imports: [
|
|
51
|
+
{
|
|
52
|
+
from: { kind: "specifier", value: "./css-calc.stylex" },
|
|
53
|
+
names: [{ imported: "calcVars" }],
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
...(definedValue === "16px" ? { dropDefinition: true } : {}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Generic mapping: `--kebab-case` -> `vars.kebabCase`
|
|
61
|
+
// e.g. `--color-primary` -> `vars.colorPrimary`
|
|
62
|
+
const toCamelCase = (cssVarName: string) =>
|
|
63
|
+
cssVarName
|
|
64
|
+
.replace(/^--/, "")
|
|
65
|
+
.split("-")
|
|
66
|
+
.filter(Boolean)
|
|
67
|
+
.map((part, i) =>
|
|
68
|
+
i === 0 ? part : part[0]?.toUpperCase() + part.slice(1)
|
|
69
|
+
)
|
|
70
|
+
.join("");
|
|
71
|
+
|
|
72
|
+
// If you care about fallbacks, you can use `fallback` here to decide whether to resolve or not.
|
|
73
|
+
void fallback;
|
|
74
|
+
return {
|
|
75
|
+
expr: `vars.${toCamelCase(name)}`,
|
|
76
|
+
imports: [
|
|
77
|
+
{
|
|
78
|
+
from: { kind: "specifier", value: "./css-variables.stylex" },
|
|
79
|
+
names: [{ imported: "vars" }],
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (ctx.kind === "call") {
|
|
86
|
+
// Called for template interpolations like: ${transitionSpeed("slowTransition")}
|
|
87
|
+
// `calleeImportedName` is the imported symbol name (works even with aliasing).
|
|
88
|
+
// `calleeSource` tells you where it came from:
|
|
89
|
+
// - { kind: "absolutePath", value: "/abs/path" } for relative imports
|
|
90
|
+
// - { kind: "specifier", value: "some-package/foo" } for package imports
|
|
91
|
+
|
|
92
|
+
if (ctx.calleeImportedName !== "transitionSpeed") {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// If you need to scope resolution to a particular module, you can use:
|
|
97
|
+
// - ctx.calleeSource
|
|
98
|
+
|
|
99
|
+
const arg0 = ctx.args[0];
|
|
100
|
+
const key =
|
|
101
|
+
arg0?.kind === "literal" && typeof arg0.value === "string"
|
|
102
|
+
? arg0.value
|
|
103
|
+
: null;
|
|
104
|
+
if (!key) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
expr: `transitionSpeedVars.${key}`,
|
|
110
|
+
imports: [
|
|
111
|
+
{
|
|
112
|
+
from: { kind: "specifier", value: "./lib/helpers.stylex" },
|
|
113
|
+
names: [
|
|
114
|
+
{ imported: "transitionSpeed", local: "transitionSpeedVars" },
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return null;
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const result = await runTransform({
|
|
126
|
+
files: "src/**/*.tsx",
|
|
127
|
+
adapter,
|
|
128
|
+
dryRun: false,
|
|
129
|
+
parser: "tsx", // "babel" | "babylon" | "flow" | "ts" | "tsx"
|
|
130
|
+
formatterCommand: "pnpm prettier --write", // optional: format transformed files
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
console.log(result);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Adapter
|
|
137
|
+
|
|
138
|
+
Adapters are the main extension point. They let you control:
|
|
139
|
+
|
|
140
|
+
- how theme paths and CSS variables are turned into StyleX-compatible JS values (`resolveValue`)
|
|
141
|
+
- what extra imports to inject into transformed files (returned from `resolveValue`)
|
|
142
|
+
- how helper calls are resolved (via `resolveValue({ kind: "call", ... })`)
|
|
143
|
+
|
|
144
|
+
#### Dynamic interpolations
|
|
145
|
+
|
|
146
|
+
When the codemod encounters an interpolation inside a styled template literal, it runs an internal dynamic resolution pipeline which covers common cases like:
|
|
147
|
+
|
|
148
|
+
- theme access (`props.theme...`) via `resolveValue({ kind: "theme", path })`
|
|
149
|
+
- prop access (`props.foo`) and conditionals (`props.foo ? "a" : "b"`, `props.foo && "color: red;"`)
|
|
150
|
+
- simple helper calls (`transitionSpeed("slowTransition")`) via `resolveValue({ kind: "call", calleeImportedName, calleeSource, args, ... })`
|
|
151
|
+
|
|
152
|
+
If the pipeline can’t resolve an interpolation:
|
|
153
|
+
|
|
154
|
+
- for `withConfig({ shouldForwardProp })` wrappers, the transform preserves the value as an inline style so output keeps visual parity
|
|
155
|
+
- otherwise, the declaration containing that interpolation is **dropped** and a warning is produced (manual follow-up required)
|
|
156
|
+
|
|
157
|
+
### Notes / Limitations
|
|
158
|
+
|
|
159
|
+
- **ThemeProvider**: if a file imports and uses `ThemeProvider` from `styled-components`, the transform **skips the entire file** (theming strategy is project-specific).
|
|
160
|
+
- **createGlobalStyle**: detected usage is reported as an **unsupported-feature** warning (StyleX does not support global styles in the same way).
|
|
161
|
+
|
|
162
|
+
### Transform Result
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
interface RunTransformResult {
|
|
166
|
+
errors: number; // Files that had errors
|
|
167
|
+
unchanged: number; // Files that were unchanged
|
|
168
|
+
skipped: number; // Files that were skipped
|
|
169
|
+
transformed: number; // Files that were transformed
|
|
170
|
+
timeElapsed: number; // Total time in seconds
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { a as defineAdapter, i as Adapter, r as TransformWarning } from "./transform-types-CCX_WVPh.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/internal/logger.d.ts
|
|
4
|
+
interface CollectedWarning extends TransformWarning {
|
|
5
|
+
filePath: string;
|
|
6
|
+
}
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/run.d.ts
|
|
9
|
+
interface RunTransformOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Glob pattern(s) for files to transform
|
|
12
|
+
* @example "src/**\/*.tsx" or ["src/**\/*.ts", "src/**\/*.tsx"]
|
|
13
|
+
*/
|
|
14
|
+
files: string | string[];
|
|
15
|
+
/**
|
|
16
|
+
* Adapter for customizing the transform.
|
|
17
|
+
* Controls value resolution and resolver-provided imports.
|
|
18
|
+
*/
|
|
19
|
+
adapter: Adapter;
|
|
20
|
+
/**
|
|
21
|
+
* Dry run - don't write changes to files
|
|
22
|
+
* @default false
|
|
23
|
+
*/
|
|
24
|
+
dryRun?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Print transformed output to stdout
|
|
27
|
+
* @default false
|
|
28
|
+
*/
|
|
29
|
+
print?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* jscodeshift parser to use
|
|
32
|
+
* @default "tsx"
|
|
33
|
+
*/
|
|
34
|
+
parser?: "babel" | "babylon" | "flow" | "ts" | "tsx";
|
|
35
|
+
/**
|
|
36
|
+
* Command to run after transformation to format the output files.
|
|
37
|
+
* The transformed file paths will be appended as arguments.
|
|
38
|
+
* @example "pnpm prettier --write"
|
|
39
|
+
*/
|
|
40
|
+
formatterCommand?: string;
|
|
41
|
+
}
|
|
42
|
+
interface RunTransformResult {
|
|
43
|
+
/** Number of files that had errors */
|
|
44
|
+
errors: number;
|
|
45
|
+
/** Number of files that were unchanged */
|
|
46
|
+
unchanged: number;
|
|
47
|
+
/** Number of files that were skipped */
|
|
48
|
+
skipped: number;
|
|
49
|
+
/** Number of files that were transformed */
|
|
50
|
+
transformed: number;
|
|
51
|
+
/** Total time in seconds */
|
|
52
|
+
timeElapsed: number;
|
|
53
|
+
/** Warnings emitted during transformation */
|
|
54
|
+
warnings: CollectedWarning[];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Run the styled-components to StyleX transform on files matching the glob pattern.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* import { runTransform } from 'styled-components-to-stylex-codemod';
|
|
62
|
+
* import { defineAdapter } from 'styled-components-to-stylex-codemod';
|
|
63
|
+
*
|
|
64
|
+
* const adapter = defineAdapter({
|
|
65
|
+
* resolveValue(ctx) {
|
|
66
|
+
* if (ctx.kind !== "theme") return null;
|
|
67
|
+
* return {
|
|
68
|
+
* expr: `themeVars.${ctx.path.replace(/\\./g, "_")}`,
|
|
69
|
+
* imports: [{ from: { kind: "specifier", value: "./theme.stylex" }, names: [{ imported: "themeVars" }] }],
|
|
70
|
+
* };
|
|
71
|
+
* },
|
|
72
|
+
* });
|
|
73
|
+
*
|
|
74
|
+
* await runTransform({
|
|
75
|
+
* files: 'src/**\/*.tsx',
|
|
76
|
+
* adapter,
|
|
77
|
+
* dryRun: true,
|
|
78
|
+
* });
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
declare function runTransform(options: RunTransformOptions): Promise<RunTransformResult>;
|
|
82
|
+
//#endregion
|
|
83
|
+
export { defineAdapter, runTransform };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { n as logWarning, t as flushWarnings } from "./logger-D09HOyqF.mjs";
|
|
2
|
+
import { run } from "jscodeshift/src/Runner.js";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import { glob } from "node:fs/promises";
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
|
|
9
|
+
//#region src/adapter.ts
|
|
10
|
+
/**
|
|
11
|
+
* Helper for nicer user authoring + type inference.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* export default defineAdapter({
|
|
15
|
+
* resolveValue(ctx) {
|
|
16
|
+
* if (ctx.kind === "theme") {
|
|
17
|
+
* return {
|
|
18
|
+
* expr: `tokens.${ctx.path}`,
|
|
19
|
+
* imports: [
|
|
20
|
+
* { from: { kind: "specifier", value: "./tokens" }, names: [{ imported: "tokens" }] },
|
|
21
|
+
* ],
|
|
22
|
+
* };
|
|
23
|
+
* }
|
|
24
|
+
* return null;
|
|
25
|
+
* },
|
|
26
|
+
*
|
|
27
|
+
* // Optional: Enable className/style/rest support for exported components
|
|
28
|
+
* shouldSupportExternalStyles(ctx) {
|
|
29
|
+
* // Example: Enable for all exported components in a shared components folder
|
|
30
|
+
* return ctx.filePath.includes("/shared/components/");
|
|
31
|
+
* },
|
|
32
|
+
* });
|
|
33
|
+
*/
|
|
34
|
+
function defineAdapter(adapter) {
|
|
35
|
+
return adapter;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/run.ts
|
|
40
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
41
|
+
/**
|
|
42
|
+
* Run the styled-components to StyleX transform on files matching the glob pattern.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* import { runTransform } from 'styled-components-to-stylex-codemod';
|
|
47
|
+
* import { defineAdapter } from 'styled-components-to-stylex-codemod';
|
|
48
|
+
*
|
|
49
|
+
* const adapter = defineAdapter({
|
|
50
|
+
* resolveValue(ctx) {
|
|
51
|
+
* if (ctx.kind !== "theme") return null;
|
|
52
|
+
* return {
|
|
53
|
+
* expr: `themeVars.${ctx.path.replace(/\\./g, "_")}`,
|
|
54
|
+
* imports: [{ from: { kind: "specifier", value: "./theme.stylex" }, names: [{ imported: "themeVars" }] }],
|
|
55
|
+
* };
|
|
56
|
+
* },
|
|
57
|
+
* });
|
|
58
|
+
*
|
|
59
|
+
* await runTransform({
|
|
60
|
+
* files: 'src/**\/*.tsx',
|
|
61
|
+
* adapter,
|
|
62
|
+
* dryRun: true,
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
async function runTransform(options) {
|
|
67
|
+
const { files, dryRun = false, print = false, parser = "tsx", formatterCommand } = options;
|
|
68
|
+
const adapter = options.adapter;
|
|
69
|
+
if (!adapter || typeof adapter.resolveValue !== "function") throw new Error("Adapter must provide resolveValue(ctx) => { expr, imports } | null");
|
|
70
|
+
const adapterWithLogging = { resolveValue(ctx) {
|
|
71
|
+
try {
|
|
72
|
+
return adapter.resolveValue(ctx);
|
|
73
|
+
} catch (e) {
|
|
74
|
+
const kind = ctx?.kind;
|
|
75
|
+
const details = kind === "theme" ? `path=${String(ctx.path ?? "")}` : kind === "cssVariable" ? `name=${String(ctx.name ?? "")}` : kind === "call" ? `callee=${String(ctx.calleeImportedName ?? "")} source=${String(ctx.calleeSource?.value ?? "")} file=${String(ctx.callSiteFilePath ?? "")}` : "";
|
|
76
|
+
logWarning(`[styled-components-to-stylex] adapter.resolveValue threw${kind ? ` (kind=${kind}${details ? ` ${details}` : ""})` : ""}: ${e?.stack ?? String(e)}\n`);
|
|
77
|
+
throw e;
|
|
78
|
+
}
|
|
79
|
+
} };
|
|
80
|
+
const patterns = Array.isArray(files) ? files : [files];
|
|
81
|
+
const filePaths = [];
|
|
82
|
+
const cwd = process.cwd();
|
|
83
|
+
for (const pattern of patterns) for await (const file of glob(pattern, { cwd })) filePaths.push(file);
|
|
84
|
+
if (filePaths.length === 0) {
|
|
85
|
+
logWarning("No files matched the provided glob pattern(s)\n");
|
|
86
|
+
return {
|
|
87
|
+
errors: 0,
|
|
88
|
+
unchanged: 0,
|
|
89
|
+
skipped: 0,
|
|
90
|
+
transformed: 0,
|
|
91
|
+
timeElapsed: 0,
|
|
92
|
+
warnings: []
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const result = await run((() => {
|
|
96
|
+
const adjacent = join(__dirname, "transform.mjs");
|
|
97
|
+
if (existsSync(adjacent)) return adjacent;
|
|
98
|
+
const distSibling = join(__dirname, "..", "dist", "transform.mjs");
|
|
99
|
+
if (existsSync(distSibling)) return distSibling;
|
|
100
|
+
throw new Error([
|
|
101
|
+
"Could not locate transform module.",
|
|
102
|
+
`Tried: ${adjacent}`,
|
|
103
|
+
` ${distSibling}`,
|
|
104
|
+
"Run `pnpm build` to generate dist artifacts."
|
|
105
|
+
].join("\n"));
|
|
106
|
+
})(), filePaths, {
|
|
107
|
+
parser,
|
|
108
|
+
dry: dryRun,
|
|
109
|
+
print,
|
|
110
|
+
adapter: adapterWithLogging,
|
|
111
|
+
runInBand: true
|
|
112
|
+
});
|
|
113
|
+
if (formatterCommand && result.ok > 0 && !dryRun) {
|
|
114
|
+
const [cmd, ...cmdArgs] = formatterCommand.split(/\s+/);
|
|
115
|
+
if (cmd) try {
|
|
116
|
+
await new Promise((resolve$1, reject) => {
|
|
117
|
+
const proc = spawn(cmd, [...cmdArgs, ...filePaths], {
|
|
118
|
+
stdio: "inherit",
|
|
119
|
+
shell: true
|
|
120
|
+
});
|
|
121
|
+
proc.on("close", (code) => {
|
|
122
|
+
if (code === 0) resolve$1();
|
|
123
|
+
else reject(/* @__PURE__ */ new Error(`Formatter command exited with code ${code}`));
|
|
124
|
+
});
|
|
125
|
+
proc.on("error", reject);
|
|
126
|
+
});
|
|
127
|
+
} catch (e) {
|
|
128
|
+
logWarning(`[styled-components-to-stylex] Formatter command failed: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
errors: result.error,
|
|
133
|
+
unchanged: result.nochange,
|
|
134
|
+
skipped: result.skip,
|
|
135
|
+
transformed: result.ok,
|
|
136
|
+
timeElapsed: parseFloat(result.timeElapsed) || 0,
|
|
137
|
+
warnings: flushWarnings()
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
//#endregion
|
|
142
|
+
export { defineAdapter, runTransform };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/internal/logger.ts
|
|
2
|
+
let collected = [];
|
|
3
|
+
/**
|
|
4
|
+
* Clear collected warnings and return them.
|
|
5
|
+
*/
|
|
6
|
+
function flushWarnings() {
|
|
7
|
+
const result = collected;
|
|
8
|
+
collected = [];
|
|
9
|
+
return result;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Log a warning message to stderr.
|
|
13
|
+
* All codemod warnings go through this so tests can mock it.
|
|
14
|
+
*/
|
|
15
|
+
function logWarning(message) {
|
|
16
|
+
process.stderr.write(message);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Log transform warnings to stderr and collect them.
|
|
20
|
+
*/
|
|
21
|
+
function logWarnings(warnings, filePath) {
|
|
22
|
+
for (const warning of warnings) {
|
|
23
|
+
collected.push({
|
|
24
|
+
...warning,
|
|
25
|
+
filePath
|
|
26
|
+
});
|
|
27
|
+
logWarning(`[styled-components-to-stylex] Warning${warning.loc ? ` (${filePath}:${warning.loc.line}:${warning.loc.column})` : ` (${filePath})`}: ${warning.message}\n`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
export { logWarning as n, logWarnings as r, flushWarnings as t };
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import "stylis";
|
|
2
|
+
import { Options } from "jscodeshift";
|
|
3
|
+
|
|
4
|
+
//#region src/adapter.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Adapter - Single user entry point for customizing the codemod.
|
|
7
|
+
*/
|
|
8
|
+
type ResolveContext = {
|
|
9
|
+
kind: "theme";
|
|
10
|
+
path: string;
|
|
11
|
+
} | {
|
|
12
|
+
kind: "cssVariable";
|
|
13
|
+
name: string;
|
|
14
|
+
fallback?: string;
|
|
15
|
+
definedValue?: string;
|
|
16
|
+
} | {
|
|
17
|
+
kind: "call";
|
|
18
|
+
/**
|
|
19
|
+
* Absolute path of the file currently being transformed.
|
|
20
|
+
* Useful for adapter logic that wants to branch by caller file.
|
|
21
|
+
*/
|
|
22
|
+
callSiteFilePath: string;
|
|
23
|
+
/**
|
|
24
|
+
* Imported name when the callee is a named import (including aliases).
|
|
25
|
+
* Example: `import { transitionSpeed as ts } ...; ts("x")` -> "transitionSpeed"
|
|
26
|
+
*/
|
|
27
|
+
calleeImportedName: string;
|
|
28
|
+
/**
|
|
29
|
+
* Import source for this call: either an absolute file path (relative imports)
|
|
30
|
+
* or the module specifier (package imports).
|
|
31
|
+
*/
|
|
32
|
+
calleeSource: {
|
|
33
|
+
kind: "absolutePath";
|
|
34
|
+
value: string;
|
|
35
|
+
} | {
|
|
36
|
+
kind: "specifier";
|
|
37
|
+
value: string;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Call arguments (only literals are surfaced precisely; everything else is `unknown`).
|
|
41
|
+
*/
|
|
42
|
+
args: Array<{
|
|
43
|
+
kind: "literal";
|
|
44
|
+
value: string | number | boolean | null;
|
|
45
|
+
} | {
|
|
46
|
+
kind: "unknown";
|
|
47
|
+
}>;
|
|
48
|
+
};
|
|
49
|
+
type ResolveResult = {
|
|
50
|
+
/**
|
|
51
|
+
* JS expression string to inline into generated output.
|
|
52
|
+
* Example: `vars.spacingSm` or `calcVars.baseSize`
|
|
53
|
+
*/
|
|
54
|
+
expr: string;
|
|
55
|
+
/**
|
|
56
|
+
* Import statements required by `expr`.
|
|
57
|
+
* These are rendered and merged into the file by the codemod.
|
|
58
|
+
*/
|
|
59
|
+
imports: ImportSpec[];
|
|
60
|
+
/**
|
|
61
|
+
* If true, the transformer should drop the corresponding `--name: ...` definition
|
|
62
|
+
* from the emitted style object (useful when replacing with StyleX vars).
|
|
63
|
+
*/
|
|
64
|
+
dropDefinition?: boolean;
|
|
65
|
+
};
|
|
66
|
+
type ImportSource = {
|
|
67
|
+
kind: "absolutePath";
|
|
68
|
+
value: string;
|
|
69
|
+
} | {
|
|
70
|
+
kind: "specifier";
|
|
71
|
+
value: string;
|
|
72
|
+
};
|
|
73
|
+
type ImportSpec = {
|
|
74
|
+
from: ImportSource;
|
|
75
|
+
names: Array<{
|
|
76
|
+
imported: string;
|
|
77
|
+
local?: string;
|
|
78
|
+
}>;
|
|
79
|
+
};
|
|
80
|
+
interface ExternalStylesContext {
|
|
81
|
+
/** Absolute path of the file being transformed */
|
|
82
|
+
filePath: string;
|
|
83
|
+
/** Local name of the styled component */
|
|
84
|
+
componentName: string;
|
|
85
|
+
/** The export name (may differ from componentName for renamed exports) */
|
|
86
|
+
exportName: string;
|
|
87
|
+
/** Whether it's a default export */
|
|
88
|
+
isDefaultExport: boolean;
|
|
89
|
+
}
|
|
90
|
+
interface Adapter {
|
|
91
|
+
/** Unified resolver for theme paths + CSS variables. Return null to leave unresolved. */
|
|
92
|
+
resolveValue: (context: ResolveContext) => ResolveResult | null;
|
|
93
|
+
/**
|
|
94
|
+
* Called for exported styled components to determine if they should support
|
|
95
|
+
* external className/style extension. Return true to generate wrapper with
|
|
96
|
+
* className/style/rest merging. Default: false.
|
|
97
|
+
*/
|
|
98
|
+
shouldSupportExternalStyles?: (context: ExternalStylesContext) => boolean;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Helper for nicer user authoring + type inference.
|
|
102
|
+
*
|
|
103
|
+
* Usage:
|
|
104
|
+
* export default defineAdapter({
|
|
105
|
+
* resolveValue(ctx) {
|
|
106
|
+
* if (ctx.kind === "theme") {
|
|
107
|
+
* return {
|
|
108
|
+
* expr: `tokens.${ctx.path}`,
|
|
109
|
+
* imports: [
|
|
110
|
+
* { from: { kind: "specifier", value: "./tokens" }, names: [{ imported: "tokens" }] },
|
|
111
|
+
* ],
|
|
112
|
+
* };
|
|
113
|
+
* }
|
|
114
|
+
* return null;
|
|
115
|
+
* },
|
|
116
|
+
*
|
|
117
|
+
* // Optional: Enable className/style/rest support for exported components
|
|
118
|
+
* shouldSupportExternalStyles(ctx) {
|
|
119
|
+
* // Example: Enable for all exported components in a shared components folder
|
|
120
|
+
* return ctx.filePath.includes("/shared/components/");
|
|
121
|
+
* },
|
|
122
|
+
* });
|
|
123
|
+
*/
|
|
124
|
+
declare function defineAdapter(adapter: Adapter): Adapter;
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/internal/transform-types.d.ts
|
|
127
|
+
/**
|
|
128
|
+
* Warning emitted during transformation for unsupported features
|
|
129
|
+
*/
|
|
130
|
+
interface TransformWarning {
|
|
131
|
+
type: "unsupported-feature" | "dynamic-node";
|
|
132
|
+
feature: string;
|
|
133
|
+
message: string;
|
|
134
|
+
loc?: {
|
|
135
|
+
line: number;
|
|
136
|
+
column: number;
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Result of the transform including any warnings
|
|
141
|
+
*/
|
|
142
|
+
interface TransformResult {
|
|
143
|
+
code: string | null;
|
|
144
|
+
warnings: TransformWarning[];
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Options for the transform
|
|
148
|
+
*/
|
|
149
|
+
interface TransformOptions extends Options {
|
|
150
|
+
/**
|
|
151
|
+
* Adapter for customizing the transform.
|
|
152
|
+
* Controls value resolution and resolver-provided imports.
|
|
153
|
+
*/
|
|
154
|
+
adapter: Adapter;
|
|
155
|
+
}
|
|
156
|
+
//#endregion
|
|
157
|
+
export { defineAdapter as a, Adapter as i, TransformResult as n, TransformWarning as r, TransformOptions as t };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { n as TransformResult, r as TransformWarning, t as TransformOptions } from "./transform-types-CCX_WVPh.mjs";
|
|
2
|
+
import { API, FileInfo, Options } from "jscodeshift";
|
|
3
|
+
|
|
4
|
+
//#region src/transform.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Transform styled-components to StyleX
|
|
8
|
+
*
|
|
9
|
+
* This is a sample transform that serves as a starting point.
|
|
10
|
+
* You'll need to implement the actual transformation logic based on your needs.
|
|
11
|
+
*/
|
|
12
|
+
declare function transform(file: FileInfo, api: API, options: Options): string | null;
|
|
13
|
+
/**
|
|
14
|
+
* Transform with detailed warnings returned (for testing)
|
|
15
|
+
*/
|
|
16
|
+
declare function transformWithWarnings(file: FileInfo, api: API, options: TransformOptions): TransformResult;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { type TransformOptions, type TransformResult, type TransformWarning, transform as default, transformWithWarnings };
|