styled-components-to-stylex-codemod 0.0.1 → 0.0.2

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
@@ -2,6 +2,12 @@
2
2
 
3
3
  Transform styled-components to StyleX.
4
4
 
5
+ **[Try it in the online playground](https://skovhus.github.io/styled-components-to-stylex-codemod/)** — experiment with the transform in your browser.
6
+
7
+ > [!WARNING]
8
+ >
9
+ > **Very much under construction (alpha):** this codemod is still early in development — expect rough edges! 🚧
10
+
5
11
  ## Installation
6
12
 
7
13
  ```bash
@@ -140,6 +146,42 @@ Adapters are the main extension point. They let you control:
140
146
  - how theme paths and CSS variables are turned into StyleX-compatible JS values (`resolveValue`)
141
147
  - what extra imports to inject into transformed files (returned from `resolveValue`)
142
148
  - how helper calls are resolved (via `resolveValue({ kind: "call", ... })`)
149
+ - which exported components should support external className/style extension (`shouldSupportExternalStyling`)
150
+
151
+ #### External Styles Support
152
+
153
+ Transformed components are "closed" by default — they don't accept external `className` or `style` props. Use `shouldSupportExternalStyling` to control which exported components should support external styling:
154
+
155
+ ```ts
156
+ const adapter = defineAdapter({
157
+ resolveValue(ctx) {
158
+ // ... value resolution logic
159
+ return null;
160
+ },
161
+
162
+ shouldSupportExternalStyling(ctx) {
163
+ // ctx: { filePath, componentName, exportName, isDefaultExport }
164
+
165
+ // Example: Enable for all exports in shared components folder
166
+ if (ctx.filePath.includes("/shared/components/")) {
167
+ return true;
168
+ }
169
+
170
+ // Example: Enable for specific component names
171
+ if (ctx.componentName === "Button" || ctx.componentName === "Card") {
172
+ return true;
173
+ }
174
+
175
+ return false;
176
+ },
177
+ });
178
+ ```
179
+
180
+ When `shouldSupportExternalStyling` returns `true`, the generated component will:
181
+
182
+ - Accept `className` and `style` props
183
+ - Merge them with the StyleX-generated styles
184
+ - Forward remaining props via `...rest`
143
185
 
144
186
  #### Dynamic interpolations
145
187
 
@@ -154,23 +196,12 @@ If the pipeline can’t resolve an interpolation:
154
196
  - for `withConfig({ shouldForwardProp })` wrappers, the transform preserves the value as an inline style so output keeps visual parity
155
197
  - otherwise, the declaration containing that interpolation is **dropped** and a warning is produced (manual follow-up required)
156
198
 
157
- ### Notes / Limitations
199
+ ### Limitations
158
200
 
201
+ - **Flow** type generation is non-existing, works best with TypeScript or plain JS right now. Contributions more than welcome!
159
202
  - **ThemeProvider**: if a file imports and uses `ThemeProvider` from `styled-components`, the transform **skips the entire file** (theming strategy is project-specific).
160
203
  - **createGlobalStyle**: detected usage is reported as an **unsupported-feature** warning (StyleX does not support global styles in the same way).
161
204
 
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
205
  ## License
175
206
 
176
207
  MIT
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as defineAdapter, i as Adapter, r as TransformWarning } from "./transform-types-CCX_WVPh.mjs";
1
+ import { a as defineAdapter, i as Adapter, r as TransformWarning } from "./transform-types-Cry4CAGJ.mjs";
2
2
 
3
3
  //#region src/internal/logger.d.ts
4
4
  interface CollectedWarning extends TransformWarning {
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as logWarning, t as flushWarnings } from "./logger-D09HOyqF.mjs";
1
+ import { a as describeValue, i as assertValidAdapter, n as logWarning, t as flushWarnings } from "./logger-Ckk2VkqY.mjs";
2
2
  import { run } from "jscodeshift/src/Runner.js";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { dirname, join } from "node:path";
@@ -8,6 +8,9 @@ import { spawn } from "node:child_process";
8
8
 
9
9
  //#region src/adapter.ts
10
10
  /**
11
+ * Adapter - Single user entry point for customizing the codemod.
12
+ */
13
+ /**
11
14
  * Helper for nicer user authoring + type inference.
12
15
  *
13
16
  * Usage:
@@ -24,14 +27,15 @@ import { spawn } from "node:child_process";
24
27
  * return null;
25
28
  * },
26
29
  *
27
- * // Optional: Enable className/style/rest support for exported components
28
- * shouldSupportExternalStyles(ctx) {
30
+ * // Enable className/style/rest support for exported components
31
+ * shouldSupportExternalStyling(ctx) {
29
32
  * // Example: Enable for all exported components in a shared components folder
30
33
  * return ctx.filePath.includes("/shared/components/");
31
34
  * },
32
35
  * });
33
36
  */
34
37
  function defineAdapter(adapter) {
38
+ assertValidAdapter(adapter, "defineAdapter(adapter)");
35
39
  return adapter;
36
40
  }
37
41
 
@@ -64,19 +68,45 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
64
68
  * ```
65
69
  */
66
70
  async function runTransform(options) {
71
+ if (!options || typeof options !== "object") throw new Error([
72
+ "[styled-components-to-stylex] runTransform(options) was called with an invalid argument.",
73
+ "Expected: runTransform({ files: string | string[], adapter: Adapter, ... })",
74
+ `Received: ${describeValue(options)}`,
75
+ "",
76
+ "Example (plain JS):",
77
+ " import { runTransform, defineAdapter } from \"styled-components-to-stylex-codemod\";",
78
+ " const adapter = defineAdapter({ resolveValue() { return null; } });",
79
+ " await runTransform({ files: \"src/**/*.tsx\", adapter });"
80
+ ].join("\n"));
81
+ const filesValue = options.files;
82
+ if (typeof filesValue !== "string" && !Array.isArray(filesValue)) throw new Error([
83
+ "[styled-components-to-stylex] runTransform(options): `files` is required.",
84
+ "Expected: files: string | string[]",
85
+ `Received: files=${describeValue(filesValue)}`
86
+ ].join("\n"));
87
+ if (typeof filesValue === "string" && filesValue.trim() === "") throw new Error(["[styled-components-to-stylex] runTransform(options): `files` must be a non-empty string.", "Example: files: \"src/**/*.tsx\""].join("\n"));
88
+ if (Array.isArray(filesValue)) {
89
+ if (filesValue.length === 0) throw new Error(["[styled-components-to-stylex] runTransform(options): `files` must not be an empty array.", "Example: files: [\"src/**/*.ts\", \"src/**/*.tsx\"]"].join("\n"));
90
+ if (filesValue.find((p) => typeof p !== "string" || p.trim() === "") !== void 0) throw new Error(["[styled-components-to-stylex] runTransform(options): `files` array must contain non-empty strings.", `Received: files=${describeValue(filesValue)}`].join("\n"));
91
+ }
67
92
  const { files, dryRun = false, print = false, parser = "tsx", formatterCommand } = options;
68
93
  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;
94
+ assertValidAdapter(adapter, "runTransform(options)");
95
+ const adapterWithLogging = {
96
+ shouldSupportExternalStyling(ctx) {
97
+ return adapter.shouldSupportExternalStyling(ctx);
98
+ },
99
+ resolveValue(ctx) {
100
+ try {
101
+ return adapter.resolveValue(ctx);
102
+ } catch (e) {
103
+ const kind = ctx?.kind;
104
+ 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 ?? "")}` : "";
105
+ logWarning(`[styled-components-to-stylex] adapter.resolveValue threw${kind ? ` (kind=${kind}${details ? ` ${details}` : ""})` : ""}: ${e?.stack ?? String(e)}\n`);
106
+ throw e;
107
+ }
78
108
  }
79
- } };
109
+ };
80
110
  const patterns = Array.isArray(files) ? files : [files];
81
111
  const filePaths = [];
82
112
  const cwd = process.cwd();
@@ -0,0 +1,89 @@
1
+ //#region src/internal/public-api-validation.ts
2
+ const ADAPTER_DOCS_URL = `https://github.com/skovhus/styled-components-to-stylex-codemod#adapter`;
3
+ function describeValue(value) {
4
+ if (value === null) return "null";
5
+ if (value === void 0) return "undefined";
6
+ if (Array.isArray(value)) return `Array(${value.length})`;
7
+ if (typeof value === "string") return `"${value}"`;
8
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
9
+ if (typeof value === "bigint") return value.toString();
10
+ if (typeof value === "symbol") return value.description ? `Symbol(${value.description})` : "Symbol()";
11
+ if (typeof value === "function") return "[Function]";
12
+ if (typeof value === "object") {
13
+ const ctor = value?.constructor?.name ?? "Object";
14
+ let keys = [];
15
+ try {
16
+ keys = Object.keys(value);
17
+ } catch {}
18
+ const preview = keys.slice(0, 5).join(", ");
19
+ const suffix = keys.length > 5 ? ", ..." : "";
20
+ return keys.length ? `${ctor} { ${preview}${suffix} }` : ctor;
21
+ }
22
+ return "[Unknown]";
23
+ }
24
+ function assertValidAdapter(candidate, where) {
25
+ const obj = candidate;
26
+ const resolveValue = obj?.resolveValue;
27
+ const shouldSupportExternalStyling = obj?.shouldSupportExternalStyling;
28
+ if (!candidate || typeof candidate !== "object") throw new Error([
29
+ `[styled-components-to-stylex] ${where}: expected an adapter object.`,
30
+ `Received: ${describeValue(candidate)}`,
31
+ "",
32
+ "Adapter requirements:",
33
+ " - adapter.resolveValue(context) is required",
34
+ " - adapter.shouldSupportExternalStyling(context) is required",
35
+ "",
36
+ "resolveValue(context) is called with one of these shapes:",
37
+ " - { kind: \"theme\", path }",
38
+ " - { kind: \"cssVariable\", name, fallback?, definedValue? }",
39
+ " - { kind: \"call\", callSiteFilePath, calleeImportedName, calleeSource, args }",
40
+ "",
41
+ `Docs/examples: ${ADAPTER_DOCS_URL}`
42
+ ].join("\n"));
43
+ if (typeof resolveValue !== "function") throw new Error([
44
+ `[styled-components-to-stylex] ${where}: adapter.resolveValue must be a function.`,
45
+ `Received: resolveValue=${describeValue(resolveValue)}`,
46
+ "",
47
+ "Adapter shape:",
48
+ " {",
49
+ " resolveValue(context) { return { expr: string, imports: ImportSpec[] } | null }",
50
+ " }",
51
+ "",
52
+ `Docs/examples: ${ADAPTER_DOCS_URL}`
53
+ ].join("\n"));
54
+ if (typeof shouldSupportExternalStyling !== "function") throw new Error([`[styled-components-to-stylex] ${where}: adapter.shouldSupportExternalStyling must be a function.`, `Received: shouldSupportExternalStyling=${describeValue(shouldSupportExternalStyling)}`].join("\n"));
55
+ }
56
+
57
+ //#endregion
58
+ //#region src/internal/logger.ts
59
+ let collected = [];
60
+ /**
61
+ * Clear collected warnings and return them.
62
+ */
63
+ function flushWarnings() {
64
+ const result = collected;
65
+ collected = [];
66
+ return result;
67
+ }
68
+ /**
69
+ * Log a warning message to stderr.
70
+ * All codemod warnings go through this so tests can mock it.
71
+ */
72
+ function logWarning(message) {
73
+ process.stderr.write(message);
74
+ }
75
+ /**
76
+ * Log transform warnings to stderr and collect them.
77
+ */
78
+ function logWarnings(warnings, filePath) {
79
+ for (const warning of warnings) {
80
+ collected.push({
81
+ ...warning,
82
+ filePath
83
+ });
84
+ logWarning(`[styled-components-to-stylex] Warning${warning.loc ? ` (${filePath}:${warning.loc.line}:${warning.loc.column})` : ` (${filePath})`}: ${warning.message}\n`);
85
+ }
86
+ }
87
+
88
+ //#endregion
89
+ export { describeValue as a, assertValidAdapter as i, logWarning as n, logWarnings as r, flushWarnings as t };
@@ -93,9 +93,9 @@ interface Adapter {
93
93
  /**
94
94
  * Called for exported styled components to determine if they should support
95
95
  * external className/style extension. Return true to generate wrapper with
96
- * className/style/rest merging. Default: false.
96
+ * className/style/rest merging.
97
97
  */
98
- shouldSupportExternalStyles?: (context: ExternalStylesContext) => boolean;
98
+ shouldSupportExternalStyling: (context: ExternalStylesContext) => boolean;
99
99
  }
100
100
  /**
101
101
  * Helper for nicer user authoring + type inference.
@@ -114,8 +114,8 @@ interface Adapter {
114
114
  * return null;
115
115
  * },
116
116
  *
117
- * // Optional: Enable className/style/rest support for exported components
118
- * shouldSupportExternalStyles(ctx) {
117
+ * // Enable className/style/rest support for exported components
118
+ * shouldSupportExternalStyling(ctx) {
119
119
  * // Example: Enable for all exported components in a shared components folder
120
120
  * return ctx.filePath.includes("/shared/components/");
121
121
  * },
@@ -1,4 +1,4 @@
1
- import { n as TransformResult, r as TransformWarning, t as TransformOptions } from "./transform-types-CCX_WVPh.mjs";
1
+ import { n as TransformResult, r as TransformWarning, t as TransformOptions } from "./transform-types-Cry4CAGJ.mjs";
2
2
  import { API, FileInfo, Options } from "jscodeshift";
3
3
 
4
4
  //#region src/transform.d.ts