styled-components-to-stylex-codemod 0.0.13 → 0.0.15

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/dist/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
- import { n as assertValidAdapter, r as describeValue, t as Logger } from "./logger-D3j-qxgZ.mjs";
1
+ import { i as describeValue, r as assertValidAdapterInput, t as Logger } from "./logger-D-R2KB6I.mjs";
2
2
  import { run } from "jscodeshift/src/Runner.js";
3
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";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { existsSync, readFileSync, realpathSync } from "node:fs";
6
+ import { glob, writeFile } from "node:fs/promises";
7
7
  import { spawn } from "node:child_process";
8
8
 
9
9
  //#region src/adapter.ts
@@ -63,7 +63,7 @@ import { spawn } from "node:child_process";
63
63
  * });
64
64
  */
65
65
  function defineAdapter(adapter) {
66
- assertValidAdapter(adapter, "defineAdapter(adapter)");
66
+ assertValidAdapterInput(adapter, "defineAdapter(adapter)");
67
67
  return adapter;
68
68
  }
69
69
 
@@ -94,6 +94,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
94
94
  *
95
95
  * await runTransform({
96
96
  * files: 'src/**\/*.tsx',
97
+ * consumerPaths: null,
97
98
  * adapter,
98
99
  * dryRun: true,
99
100
  * });
@@ -108,7 +109,7 @@ async function runTransform(options) {
108
109
  "Example (plain JS):",
109
110
  " import { runTransform, defineAdapter } from \"styled-components-to-stylex-codemod\";",
110
111
  " const adapter = defineAdapter({ resolveValue() { return null; } });",
111
- " await runTransform({ files: \"src/**/*.tsx\", adapter });"
112
+ " await runTransform({ files: \"src/**/*.tsx\", consumerPaths: null, adapter });"
112
113
  ].join("\n"));
113
114
  const filesValue = options.files;
114
115
  if (typeof filesValue !== "string" && !Array.isArray(filesValue)) throw new Error([
@@ -121,47 +122,52 @@ async function runTransform(options) {
121
122
  if (filesValue.length === 0) throw new Error(["runTransform(options): `files` must not be an empty array.", "Example: files: [\"src/**/*.ts\", \"src/**/*.tsx\"]"].join("\n"));
122
123
  if (filesValue.find((p) => typeof p !== "string" || p.trim() === "") !== void 0) throw new Error(["runTransform(options): `files` array must contain non-empty strings.", `Received: files=${describeValue(filesValue)}`].join("\n"));
123
124
  }
124
- const { files, dryRun = false, print = false, parser = "tsx", formatterCommands, maxExamples } = options;
125
+ if (options.consumerPaths === void 0) throw new Error([
126
+ "runTransform(options): `consumerPaths` is required.",
127
+ "Pass a glob pattern to enable cross-file selector scanning, or `null` to opt out.",
128
+ "Example: consumerPaths: \"src/**/*.tsx\" // scan for cross-file usage",
129
+ "Example: consumerPaths: null // opt out"
130
+ ].join("\n"));
131
+ const { files, consumerPaths: consumerPathsOption, dryRun = false, print = false, parser = "tsx", formatterCommands, maxExamples } = options;
125
132
  if (maxExamples !== void 0) Logger.setMaxExamples(maxExamples);
126
- const adapter = options.adapter;
127
- assertValidAdapter(adapter, "runTransform(options)");
133
+ const adapterInput = options.adapter;
134
+ assertValidAdapterInput(adapterInput, "runTransform(options)");
135
+ if (adapterInput.externalInterface === "auto" && consumerPathsOption === null) throw new Error([
136
+ "runTransform(options): externalInterface is \"auto\" but consumerPaths is null.",
137
+ "Auto-detection needs consumer file globs to scan for styled(Component) and as-prop usage.",
138
+ "Example: consumerPaths: \"src/**/*.tsx\""
139
+ ].join("\n"));
128
140
  const resolveValueWithLogging = (ctx) => {
129
141
  try {
130
- return adapter.resolveValue(ctx);
142
+ return adapterInput.resolveValue(ctx);
131
143
  } catch (e) {
132
144
  const msg = `adapter.resolveValue threw an error: ${e instanceof Error ? e.message : String(e)}`;
133
145
  const filePath = ctx.filePath ?? "<unknown>";
134
146
  Logger.logError(msg, filePath, ctx.loc, ctx);
147
+ Logger.markErrorAsLogged(e);
135
148
  throw e;
136
149
  }
137
150
  };
138
151
  const resolveCallWithLogging = (ctx) => {
139
152
  try {
140
- return adapter.resolveCall(ctx);
153
+ return adapterInput.resolveCall(ctx);
141
154
  } catch (e) {
142
155
  const msg = `adapter.resolveCall threw an error: ${e instanceof Error ? e.message : String(e)}`;
143
156
  Logger.logError(msg, ctx.callSiteFilePath, ctx.loc, ctx);
157
+ Logger.markErrorAsLogged(e);
144
158
  throw e;
145
159
  }
146
160
  };
147
161
  const resolveSelectorWithLogging = (ctx) => {
148
162
  try {
149
- return adapter.resolveSelector(ctx);
163
+ return adapterInput.resolveSelector(ctx);
150
164
  } catch (e) {
151
165
  const msg = `adapter.resolveSelector threw an error: ${e instanceof Error ? e.message : String(e)}`;
152
166
  Logger.logError(msg, ctx.filePath, ctx.loc, ctx);
167
+ Logger.markErrorAsLogged(e);
153
168
  throw e;
154
169
  }
155
170
  };
156
- const adapterWithLogging = {
157
- styleMerger: adapter.styleMerger,
158
- externalInterface(ctx) {
159
- return adapter.externalInterface(ctx);
160
- },
161
- resolveValue: resolveValueWithLogging,
162
- resolveCall: resolveCallWithLogging,
163
- resolveSelector: resolveSelectorWithLogging
164
- };
165
171
  const patterns = Array.isArray(files) ? files : [files];
166
172
  const filePaths = [];
167
173
  const cwd = process.cwd();
@@ -178,7 +184,73 @@ async function runTransform(options) {
178
184
  };
179
185
  }
180
186
  Logger.setFileCount(filePaths.length);
181
- const result = await run((() => {
187
+ const consumerPatterns = consumerPathsOption ? Array.isArray(consumerPathsOption) ? consumerPathsOption : [consumerPathsOption] : [];
188
+ const consumerFilePaths = [];
189
+ for (const pattern of consumerPatterns) for await (const file of glob(pattern, { cwd })) consumerFilePaths.push(file);
190
+ if (consumerPatterns.length > 0 && consumerFilePaths.length === 0) throw new Error([
191
+ "runTransform(options): consumerPaths matched no files.",
192
+ `Pattern(s): ${consumerPatterns.join(", ")}`,
193
+ "Check that the glob pattern is correct and files exist."
194
+ ].join("\n"));
195
+ const { createModuleResolver } = await import("./resolve-imports-BDk6Ms09.mjs");
196
+ const sharedResolver = createModuleResolver();
197
+ const { runPrepass } = await import("./run-prepass-BcidTT9f.mjs");
198
+ const absoluteFiles = filePaths.map((f) => resolve(f));
199
+ const absoluteConsumers = consumerFilePaths.map((f) => resolve(f));
200
+ let prepassResult;
201
+ try {
202
+ prepassResult = await runPrepass({
203
+ filesToTransform: absoluteFiles,
204
+ consumerPaths: absoluteConsumers,
205
+ resolver: sharedResolver,
206
+ parserName: parser,
207
+ createExternalInterface: adapterInput.externalInterface === "auto",
208
+ enableAstCache: true
209
+ });
210
+ } catch (err) {
211
+ if (adapterInput.externalInterface === "auto") throw createAutoPrepassFailureError(err, consumerPatterns, parser);
212
+ Logger.warn(`Prepass failed, continuing without cross-file analysis: ${err instanceof Error ? err.message : String(err)}`);
213
+ prepassResult = {
214
+ crossFileInfo: {
215
+ selectorUsages: /* @__PURE__ */ new Map(),
216
+ componentsNeedingMarkerSidecar: /* @__PURE__ */ new Map(),
217
+ componentsNeedingGlobalSelectorBridge: /* @__PURE__ */ new Map()
218
+ },
219
+ consumerAnalysis: void 0
220
+ };
221
+ }
222
+ const crossFilePrepassResult = prepassResult.crossFileInfo;
223
+ const resolvedAdapter = (() => {
224
+ if (adapterInput.externalInterface === "auto" && prepassResult.consumerAnalysis) {
225
+ const analysisMap = prepassResult.consumerAnalysis;
226
+ return {
227
+ ...adapterInput,
228
+ externalInterface: (ctx) => {
229
+ let realPath;
230
+ try {
231
+ realPath = realpathSync(resolve(ctx.filePath));
232
+ } catch {
233
+ realPath = resolve(ctx.filePath);
234
+ }
235
+ return analysisMap.get(`${realPath}:${ctx.componentName}`) ?? {
236
+ styles: false,
237
+ as: false
238
+ };
239
+ }
240
+ };
241
+ }
242
+ return adapterInput;
243
+ })();
244
+ const adapterWithLogging = {
245
+ styleMerger: resolvedAdapter.styleMerger,
246
+ externalInterface(ctx) {
247
+ return resolvedAdapter.externalInterface(ctx);
248
+ },
249
+ resolveValue: resolveValueWithLogging,
250
+ resolveCall: resolveCallWithLogging,
251
+ resolveSelector: resolveSelectorWithLogging
252
+ };
253
+ const transformPath = (() => {
182
254
  const adjacent = join(__dirname, "transform.mjs");
183
255
  if (existsSync(adjacent)) return adjacent;
184
256
  const distSibling = join(__dirname, "..", "dist", "transform.mjs");
@@ -189,31 +261,34 @@ async function runTransform(options) {
189
261
  ` ${distSibling}`,
190
262
  "Run `pnpm build` to generate dist artifacts."
191
263
  ].join("\n"));
192
- })(), filePaths, {
264
+ })();
265
+ const sidecarFiles = /* @__PURE__ */ new Map();
266
+ const bridgeResults = /* @__PURE__ */ new Map();
267
+ const result = await run(transformPath, filePaths, {
193
268
  parser,
194
269
  dry: dryRun,
195
270
  print,
196
271
  adapter: adapterWithLogging,
272
+ crossFilePrepassResult,
273
+ sidecarFiles,
274
+ bridgeResults,
197
275
  runInBand: true
198
276
  });
199
- if (formatterCommands && formatterCommands.length > 0 && result.ok > 0 && !dryRun) for (const formatterCommand of formatterCommands) {
200
- const [cmd, ...cmdArgs] = formatterCommand.split(/\s+/);
201
- if (cmd) try {
202
- await new Promise((resolve, reject) => {
203
- const proc = spawn(cmd, [...cmdArgs, ...filePaths], {
204
- stdio: "inherit",
205
- shell: true
206
- });
207
- proc.on("close", (code) => {
208
- if (code === 0) resolve();
209
- else reject(/* @__PURE__ */ new Error(`Formatter command exited with code ${code}`));
210
- });
211
- proc.on("error", reject);
212
- });
213
- } catch (e) {
214
- Logger.warn(`Formatter command failed: ${e instanceof Error ? e.message : String(e)}`);
277
+ if (sidecarFiles.size > 0 && !dryRun) for (const [sidecarPath, content] of sidecarFiles) await writeFile(sidecarPath, mergeSidecarContent(sidecarPath, content), "utf-8");
278
+ if (bridgeResults.size > 0 && !dryRun) {
279
+ const { buildConsumerReplacements, patchConsumerFile } = await import("./bridge-consumer-patcher-D3fRIEkZ.mjs");
280
+ const consumerReplacements = buildConsumerReplacements(crossFilePrepassResult.selectorUsages, bridgeResults);
281
+ const patchedFiles = [];
282
+ for (const [consumerPath, replacements] of consumerReplacements) {
283
+ const patched = patchConsumerFile(consumerPath, replacements);
284
+ if (patched !== null) {
285
+ await writeFile(consumerPath, patched, "utf-8");
286
+ patchedFiles.push(consumerPath);
287
+ }
215
288
  }
289
+ if (formatterCommands && patchedFiles.length > 0) await runFormatters(formatterCommands, patchedFiles);
216
290
  }
291
+ if (formatterCommands && formatterCommands.length > 0 && result.ok > 0 && !dryRun) await runFormatters(formatterCommands, filePaths);
217
292
  const report = Logger.createReport();
218
293
  report.print();
219
294
  return {
@@ -225,6 +300,70 @@ async function runTransform(options) {
225
300
  warnings: report.getWarnings()
226
301
  };
227
302
  }
303
+ function createAutoPrepassFailureError(err, consumerPatterns, parser) {
304
+ const reason = err instanceof Error ? err.message : String(err);
305
+ return new Error([
306
+ "runTransform(options): prepass failed while using externalInterface: \"auto\".",
307
+ "\"auto\" depends on successful prepass scanning and cannot continue without it.",
308
+ `Underlying error: ${reason}`,
309
+ "",
310
+ "Troubleshooting:",
311
+ " - Verify `consumerPaths` glob(s) and file syntax.",
312
+ ` - Confirm parser setting matches your code (current parser: ${JSON.stringify(parser)}).`,
313
+ " - Check module resolution inputs (tsconfig paths / imports).",
314
+ " - Use a manual `externalInterface(ctx)` function to continue without auto-detection.",
315
+ "",
316
+ `consumerPaths: ${consumerPatterns.length > 0 ? consumerPatterns.join(", ") : "(none)"}`
317
+ ].join("\n"));
318
+ }
319
+ /**
320
+ * Merge new sidecar marker content into an existing .stylex.ts file, preserving
321
+ * user-owned exports (e.g. defineVars). If the file doesn't exist, returns content as-is.
322
+ *
323
+ * New marker declarations (`export const XMarker = stylex.defineMarker()`) are
324
+ * appended only if they don't already exist in the file. The stylex import is
325
+ * ensured at the top.
326
+ */
327
+ function mergeSidecarContent(sidecarPath, newContent) {
328
+ let existing;
329
+ try {
330
+ existing = readFileSync(sidecarPath, "utf-8");
331
+ } catch {
332
+ return newContent;
333
+ }
334
+ const markerLineRe = /^export const \w+ = stylex\.defineMarker\(\);$/gm;
335
+ const newMarkers = [];
336
+ for (const m of newContent.matchAll(markerLineRe)) newMarkers.push(m[0]);
337
+ if (newMarkers.length === 0) return newContent;
338
+ const markersToAdd = newMarkers.filter((line) => !existing.includes(line));
339
+ if (markersToAdd.length === 0) return existing;
340
+ let merged = existing;
341
+ if (!merged.includes("@stylexjs/stylex")) merged = `import * as stylex from "@stylexjs/stylex";\n\n${merged}`;
342
+ const trailingNewline = merged.endsWith("\n") ? "" : "\n";
343
+ merged = merged + trailingNewline + markersToAdd.join("\n") + "\n";
344
+ return merged;
345
+ }
346
+ /** Run formatter commands on a list of files, logging warnings on failure. */
347
+ async function runFormatters(commands, files) {
348
+ for (const formatterCommand of commands) {
349
+ const [cmd, ...cmdArgs] = formatterCommand.split(/\s+/);
350
+ if (cmd) try {
351
+ await new Promise((res, rej) => {
352
+ const proc = spawn(cmd, [...cmdArgs, ...files], {
353
+ stdio: "inherit",
354
+ shell: true
355
+ });
356
+ proc.on("close", (code) => {
357
+ if (code === 0) res();
358
+ else rej(/* @__PURE__ */ new Error(`Formatter command exited with code ${code}`));
359
+ });
360
+ proc.on("error", rej);
361
+ });
362
+ } catch (e) {
363
+ Logger.warn(`Formatter command failed: ${e instanceof Error ? e.message : String(e)}`);
364
+ }
365
+ }
366
+ }
228
367
 
229
368
  //#endregion
230
369
  export { defineAdapter, runTransform };
@@ -80,6 +80,12 @@ type CallResolveContext = {
80
80
  * Example: `import { transitionSpeed as ts } ...; ts("x")` -> "transitionSpeed"
81
81
  */
82
82
  calleeImportedName: string;
83
+ /**
84
+ * Member path segments on the callee (e.g., for `ColorConverter.cssWithAlpha()`, `["cssWithAlpha"]`).
85
+ * Only present when the callee is a member expression. The root object's import info
86
+ * is in `calleeImportedName`/`calleeSource`.
87
+ */
88
+ calleeMemberPath?: string[];
83
89
  /**
84
90
  * Import source for this call: either an absolute file path (relative imports)
85
91
  * or the module specifier (package imports).
@@ -190,6 +196,16 @@ type CallResolveResult = {
190
196
  * returns a StyleX styles object even when used with a CSS property like `border:`.
191
197
  */
192
198
  usage?: "create" | "props";
199
+ /**
200
+ * When the resolved expression is combined with a dynamic argument (e.g., from a prop),
201
+ * this field controls how they are joined:
202
+ * - `"call"` (default): `resolvedExpr(arg)` — treat as a function call
203
+ * - `"memberAccess"`: `resolvedExpr[arg]` — treat as a computed member access
204
+ *
205
+ * Only relevant when the original code uses a pattern like `${(props) => helper(props.x)}`
206
+ * and the adapter returns a resolved expression for the helper.
207
+ */
208
+ dynamicArgUsage?: "call" | "memberAccess";
193
209
  /**
194
210
  * Optional raw CSS text for helpers that return CSS declaration blocks.
195
211
  *
@@ -396,6 +412,28 @@ interface Adapter {
396
412
  */
397
413
  styleMerger: StyleMergerConfig | null;
398
414
  }
415
+ /**
416
+ * User-facing adapter input type accepted by `defineAdapter()`.
417
+ *
418
+ * Same as `Adapter` except `externalInterface` may also be the string `"auto"`.
419
+ * When `"auto"` is used, `runTransform()` automatically scans consumer code
420
+ * (using `consumerPaths` / `files` globs) to detect which exported components
421
+ * are re-styled or used with the `as` prop.
422
+ */
423
+ interface AdapterInput {
424
+ resolveValue: Adapter["resolveValue"];
425
+ resolveCall: Adapter["resolveCall"];
426
+ resolveSelector: Adapter["resolveSelector"];
427
+ /**
428
+ * Called for exported styled components to determine their external interface.
429
+ *
430
+ * - Pass a function for manual control.
431
+ * - Pass `"auto"` to auto-detect usage from consumer code (requires `consumerPaths`
432
+ * in `runTransform()`).
433
+ */
434
+ externalInterface: "auto" | Adapter["externalInterface"];
435
+ styleMerger: Adapter["styleMerger"];
436
+ }
399
437
  /**
400
438
  * Helper for nicer user authoring + type inference.
401
439
  *
@@ -447,11 +485,11 @@ interface Adapter {
447
485
  * styleMerger: null,
448
486
  * });
449
487
  */
450
- declare function defineAdapter(adapter: Adapter): Adapter;
488
+ declare function defineAdapter<T extends AdapterInput>(adapter: T): T;
451
489
  //#endregion
452
490
  //#region src/internal/logger.d.ts
453
491
  type Severity = "info" | "warning" | "error";
454
- type WarningType = "`css` helper function switch must return css templates in all branches" | "`css` helper usage as a function call (css(...)) is not supported" | "`css` helper used outside of a styled component template cannot be statically transformed" | "Adapter helper call in border interpolation did not resolve to a single CSS value" | "Adapter resolveCall returned an unparseable styles expression" | "Adapter resolveCall returned an unparseable value expression" | "Adapter resolveCall returned StyleX styles for helper call where a CSS value was expected" | "Adapter resolveCall returned undefined for helper call" | "Adapter resolved StyleX styles cannot be applied under nested selectors/at-rules" | "Adapter resolved StyleX styles inside pseudo selector but did not provide cssText for property expansion — add cssText to resolveCall result to enable pseudo-wrapping" | 'Adapter resolveCall cssText could not be parsed as CSS declarations — expected semicolon-separated property: value pairs (e.g. "white-space: nowrap; overflow: hidden;")' | "Adapter resolveValue returned an unparseable value expression" | "Adapter resolveValue returned undefined for imported value" | "Arrow function: body is not a recognized pattern (expected ternary, logical, call, or member expression)" | "Arrow function: conditional branches could not be resolved to static or theme values" | "Arrow function: helper call body is not supported" | "Arrow function: indexed theme lookup pattern not matched" | "Arrow function: logical expression pattern not supported" | "Arrow function: prop access cannot be converted to style function for this CSS property" | "Arrow function: theme access path could not be resolved" | "Component selectors like `${OtherComponent}:hover &` are not directly representable in StyleX. Manual refactor is required" | "Conditional `css` block: !important is not supported in StyleX" | "Conditional `css` block: @-rules (e.g., @media, @supports) are not supported" | "Conditional `css` block: failed to parse expression" | "Conditional `css` block: missing CSS property name" | "Conditional `css` block: missing interpolation expression" | "Conditional `css` block: mixed static/dynamic values with non-theme expressions cannot be safely transformed" | "Conditional `css` block: multiple interpolation slots in a single property value" | "Conditional `css` block: ternary branch value could not be resolved (imported values require adapter support)" | "Conditional `css` block: ternary expressions inside pseudo selectors are not supported" | "Conditional `css` block: unsupported selector" | "Directional border helper styles are not supported" | "createGlobalStyle is not supported in StyleX. Global styles should be handled separately (e.g., in a CSS file or using CSS reset libraries)" | "Dynamic styles inside pseudo elements (::before/::after) are not supported by StyleX. See https://github.com/facebook/stylex/issues/1396" | "Failed to parse theme expressions" | "Heterogeneous background values (mix of gradients and colors) not currently supported" | "Higher-order styled factory wrappers (e.g. hoc(styled)) are not supported" | "Imported CSS helper mixins: cannot determine inherited properties for correct pseudo selector handling" | "Styled-components specificity hacks like `&&` / `&&&` are not representable in StyleX" | "Theme-dependent block-level conditional could not be fully resolved (branches may contain dynamic interpolations)" | "Theme-dependant call expression could not be resolved (e.g. theme helper calls like theme.highlight() are not supported)" | "Theme value with fallback (props.theme.X ?? / || default) cannot be resolved statically — use adapter.resolveValue to map theme paths to StyleX tokens" | "Theme-dependent nested prop access requires a project-specific theme source (e.g. useTheme())" | "Theme-dependent template literals require a project-specific theme source (e.g. useTheme())" | "Theme prop overrides on styled components are not supported" | "Universal selectors (`*`) are currently unsupported" | "Unsupported call expression (expected imported helper(...) or imported helper(...)(...))" | "Unsupported conditional test in shouldForwardProp" | "Unsupported shouldForwardProp pattern (only !prop.startsWith(), ![].includes(prop), and prop !== are supported)" | "Unsupported interpolation: arrow function" | "Unsupported interpolation: call expression" | "Unsupported interpolation: identifier" | "Unsupported interpolation: member expression" | "Unsupported interpolation: property" | "Unsupported interpolation: unknown" | "Unsupported nested conditional interpolation" | "Unsupported prop-based inline style expression cannot be safely inlined" | "Unsupported prop-based inline style props.theme access is not supported" | "Unsupported selector interpolation: imported value in selector position" | "Unsupported selector: class selector" | "Unsupported selector: comma-separated selectors must all be simple pseudos" | "Unsupported selector: descendant pseudo selector (space before pseudo)" | "Unsupported selector: descendant/child/sibling selector" | "Unsupported selector: interpolated pseudo selector" | "Unsupported selector: sibling combinator" | "Unsupported selector: ambiguous element selector" | "Unsupported selector: attribute selector on unsupported element" | "Unsupported selector: element selector on exported component" | "Unsupported selector: element selector with combined ancestor and child pseudos" | "Unsupported selector: element selector with dynamic children" | "Unsupported selector: element selector with plain intrinsic children" | "Unsupported selector: element selector pseudo collision" | "Unsupported selector: unresolved interpolation in descendant component selector" | "Unsupported selector: unresolved interpolation in element selector" | "Unsupported selector: unresolved interpolation in reverse component selector" | "Unsupported selector: unknown component selector" | "Unsupported css`` mixin: after-base mixin style is not a plain object" | "Unsupported css`` mixin: nested contextual conditions in after-base mixin" | "Unsupported css`` mixin: cannot infer base default for after-base contextual override (base value is non-literal)" | "css`` helper function interpolation references closure variable that cannot be hoisted" | "Using styled-components components as mixins is not supported; use css`` mixins or strings instead";
492
+ type WarningType = "`css` helper function switch must return css templates in all branches" | "`css` helper usage as a function call (css(...)) is not supported" | "`css` helper used outside of a styled component template cannot be statically transformed" | "Adapter helper call in border interpolation did not resolve to a single CSS value" | "Adapter resolveCall returned an unparseable styles expression" | "Adapter resolveCall returned an unparseable value expression" | "Adapter resolveCall returned StyleX styles for helper call where a CSS value was expected" | "Adapter resolveCall returned undefined for helper call" | "Adapter resolved StyleX styles cannot be applied under nested selectors/at-rules" | "Adapter resolved StyleX styles inside pseudo selector but did not provide cssText for property expansion — add cssText to resolveCall result to enable pseudo-wrapping" | 'Adapter resolveCall cssText could not be parsed as CSS declarations — expected semicolon-separated property: value pairs (e.g. "white-space: nowrap; overflow: hidden;")' | "Adapter resolveValue returned an unparseable value expression" | "Adapter resolveValue returned undefined for imported value" | "Arrow function: body is not a recognized pattern (expected ternary, logical, call, or member expression)" | "Arrow function: conditional branches could not be resolved to static or theme values" | "Arrow function: helper call body is not supported" | "Arrow function: indexed theme lookup pattern not matched" | "Arrow function: logical expression pattern not supported" | "Arrow function: prop access cannot be converted to style function for this CSS property" | "Arrow function: theme access path could not be resolved" | "Component selectors like `${OtherComponent}:hover &` are not directly representable in StyleX. Manual refactor is required" | "Conditional `css` block: !important is not supported in StyleX" | "Conditional `css` block: @-rules (e.g., @media, @supports) are not supported" | "Conditional `css` block: failed to parse expression" | "Conditional `css` block: missing CSS property name" | "Conditional `css` block: missing interpolation expression" | "Conditional `css` block: mixed static/dynamic values with non-theme expressions cannot be safely transformed" | "Conditional `css` block: multiple interpolation slots in a single property value" | "Conditional `css` block: ternary branch value could not be resolved (imported values require adapter support)" | "Conditional `css` block: ternary expressions inside pseudo selectors are not supported" | "Conditional `css` block: unsupported selector" | "Directional border helper styles are not supported" | "Multi-slot border interpolation could not be resolved" | "createGlobalStyle is not supported in StyleX. Global styles should be handled separately (e.g., in a CSS file or using CSS reset libraries)" | "Dynamic styles inside pseudo elements (::before/::after) are not supported by StyleX. See https://github.com/facebook/stylex/issues/1396" | "Failed to parse theme expressions" | "Heterogeneous background values (mix of gradients and colors) not currently supported" | "Higher-order styled factory wrappers (e.g. hoc(styled)) are not supported" | "Imported CSS helper mixins: cannot determine inherited properties for correct pseudo selector handling" | "Styled-components specificity hacks like `&&` / `&&&` are not representable in StyleX" | "Theme-dependent block-level conditional could not be fully resolved (branches may contain dynamic interpolations)" | "Theme-dependant call expression could not be resolved (e.g. theme helper calls like theme.highlight() are not supported)" | "Theme value with fallback (props.theme.X ?? / || default) cannot be resolved statically — use adapter.resolveValue to map theme paths to StyleX tokens" | "Theme-dependent nested prop access requires a project-specific theme source (e.g. useTheme())" | "Theme-dependent template literals require a project-specific theme source (e.g. useTheme())" | "Theme prop overrides on styled components are not supported" | "Universal selectors (`*`) are currently unsupported" | "Unsupported call expression (expected imported helper(...) or imported helper(...)(...))" | "Unsupported conditional test in shouldForwardProp" | "Unsupported shouldForwardProp pattern (only !prop.startsWith(), ![].includes(prop), and prop !== are supported)" | "Unsupported interpolation: arrow function" | "Unsupported interpolation: call expression" | "Unsupported interpolation: identifier" | "Unsupported interpolation: member expression" | "Unsupported interpolation: property" | "Unsupported interpolation: unknown" | "Unsupported nested conditional interpolation" | "Unsupported prop-based inline style expression cannot be safely inlined" | "Unsupported prop-based inline style props.theme access is not supported" | "Unsupported selector interpolation: imported value in selector position" | "Unsupported selector: class selector" | "Unsupported selector: comma-separated selectors must all be simple pseudos or pseudo-elements" | "Unsupported selector: descendant pseudo selector (space before pseudo)" | "Unsupported selector: descendant/child/sibling selector" | "Unsupported selector: interpolated pseudo selector" | "Unsupported selector: sibling combinator" | "Unsupported selector: unresolved interpolation in sibling selector" | "Unsupported selector: ambiguous element selector" | "Unsupported selector: attribute selector on unsupported element" | "Unsupported selector: element selector on exported component" | "Unsupported selector: element selector with combined ancestor and child pseudos" | "Unsupported selector: element selector with dynamic children" | "Unsupported selector: element selector with plain intrinsic children" | "Unsupported selector: element selector pseudo collision" | "Unsupported selector: unresolved interpolation in cross-file component selector" | "Unsupported selector: unresolved interpolation in descendant component selector" | "Unsupported selector: unresolved interpolation in element selector" | "Unsupported selector: unresolved interpolation in reverse component selector" | "Unsupported selector: grouped reverse selector references different components" | "Unsupported selector: unknown component selector" | "Unsupported css`` mixin: after-base mixin style is not a plain object" | "Unsupported css`` mixin: nested contextual conditions in after-base mixin" | "Unsupported css`` mixin: cannot infer base default for after-base contextual override (base value is non-literal)" | "css`` helper function interpolation references closure variable that cannot be hoisted" | "Sibling selector broadened: & + & (adjacent) becomes general sibling (~) in StyleX — interleaved non-matching elements will no longer block the match" | "Using styled-components components as mixins is not supported; use css`` mixins or strings instead";
455
493
  interface WarningLog {
456
494
  severity: Severity;
457
495
  type: WarningType;
@@ -465,4 +503,4 @@ interface CollectedWarning extends WarningLog {
465
503
  filePath: string;
466
504
  }
467
505
  //#endregion
468
- export { defineAdapter as i, WarningLog as n, Adapter as r, CollectedWarning as t };
506
+ export { defineAdapter as a, AdapterInput as i, WarningLog as n, Adapter as r, CollectedWarning as t };
@@ -22,7 +22,15 @@ function describeValue(value) {
22
22
  }
23
23
  return "[Unknown]";
24
24
  }
25
+ /** Validates that the candidate is a fully-resolved `Adapter` (externalInterface must be a function). */
25
26
  function assertValidAdapter(candidate, where) {
27
+ assertAdapterShape(candidate, where, false);
28
+ }
29
+ /** Validates that the candidate is a valid `AdapterInput` (externalInterface may be `"auto"` or a function). */
30
+ function assertValidAdapterInput(candidate, where) {
31
+ assertAdapterShape(candidate, where, true);
32
+ }
33
+ function assertAdapterShape(candidate, where, allowAutoExtIf) {
26
34
  const obj = candidate;
27
35
  const resolveValue = obj?.resolveValue;
28
36
  const resolveCall = obj?.resolveCall;
@@ -87,7 +95,10 @@ function assertValidAdapter(candidate, where) {
87
95
  "",
88
96
  `Docs/examples: ${ADAPTER_DOCS_URL}`
89
97
  ].join("\n"));
90
- if (typeof externalInterface !== "function") throw new Error([`${where}: adapter.externalInterface must be a function.`, `Received: externalInterface=${describeValue(externalInterface)}`].join("\n"));
98
+ if (!(typeof externalInterface === "function" || allowAutoExtIf && externalInterface === "auto")) {
99
+ const expected = allowAutoExtIf ? "adapter.externalInterface must be a function or \"auto\"." : "adapter.externalInterface must be a function.";
100
+ throw new Error([`${where}: ${expected}`, `Received: externalInterface=${describeValue(externalInterface)}`].join("\n"));
101
+ }
91
102
  const styleMerger = obj?.styleMerger;
92
103
  if (styleMerger !== null && styleMerger !== void 0) {
93
104
  if (typeof styleMerger !== "object") throw new Error([
@@ -151,6 +162,18 @@ var Logger = class Logger {
151
162
  Logger.writeWithSpacing(message, context);
152
163
  }
153
164
  /**
165
+ * Mark an Error instance as already logged so downstream catch blocks can skip it.
166
+ */
167
+ static markErrorAsLogged(e) {
168
+ if (e instanceof Error) Logger.loggedErrors.add(e);
169
+ }
170
+ /**
171
+ * Check whether an error was already logged via `markErrorAsLogged`.
172
+ */
173
+ static isErrorLogged(e) {
174
+ return e instanceof Error && Logger.loggedErrors.has(e);
175
+ }
176
+ /**
154
177
  * Log an error message to stdout with file path and optional location.
155
178
  * Formats like warnings: "Error filepath:line:column\nmessage"
156
179
  * Always prints regardless of file count.
@@ -189,10 +212,12 @@ var Logger = class Logger {
189
212
  static _clearCollected() {
190
213
  Logger.collected = [];
191
214
  Logger.fileCount = null;
215
+ Logger.loggedErrors = /* @__PURE__ */ new WeakSet();
192
216
  }
193
217
  static collected = [];
194
218
  static fileCount = null;
195
- static maxExamples = 15;
219
+ static maxExamples = 3;
220
+ static loggedErrors = /* @__PURE__ */ new WeakSet();
196
221
  static writeWithSpacing(message, context) {
197
222
  const trimmed = message.replace(/\s+$/u, "");
198
223
  const serialized = Logger.formatContext(context);
@@ -225,7 +250,7 @@ var LoggerReport = class {
225
250
  fileCount;
226
251
  maxExamples;
227
252
  fileCache = /* @__PURE__ */ new Map();
228
- constructor(warnings, fileCount, maxExamples = 15) {
253
+ constructor(warnings, fileCount, maxExamples = 3) {
229
254
  this.warnings = warnings;
230
255
  this.fileCount = fileCount;
231
256
  this.maxExamples = maxExamples;
@@ -336,4 +361,4 @@ const SECTION_COLOR = "\x1B[36m";
336
361
  const RESET_COLOR = "\x1B[0m";
337
362
 
338
363
  //#endregion
339
- export { assertValidAdapter as n, describeValue as r, Logger as t };
364
+ export { describeValue as i, assertValidAdapter as n, assertValidAdapterInput as r, Logger as t };
@@ -0,0 +1,66 @@
1
+ import { ResolverFactory } from "oxc-resolver";
2
+
3
+ //#region src/internal/prepass/resolve-imports.ts
4
+ /**
5
+ * Module resolution using oxc-resolver.
6
+ * Resolves import specifiers to absolute file paths, handling:
7
+ * - Extension probing (.ts, .tsx, .js, .jsx, /index.*)
8
+ * - tsconfig.json paths aliases (auto-discovered)
9
+ * - tsconfig.json project references
10
+ * - .js → .ts remapping (ESM with moduleResolution: "bundler")
11
+ * - Package exports field
12
+ * - Symlink resolution (pnpm/Yarn workspaces)
13
+ */
14
+ /**
15
+ * Shared resolver config for TypeScript projects.
16
+ *
17
+ * - `.tsx` before `.ts` so React component files win when both exist
18
+ * - `"types"` condition for type-aware resolution; `"default"` as fallback
19
+ * - `tsconfig: "auto"` auto-discovers the nearest tsconfig.json per file
20
+ * for path alias resolution
21
+ * - `extensionAlias` handles ESM `.js`→`.ts` remapping AND `.ts`→`.tsx`
22
+ * fallback for package.json `"exports"` wildcards
23
+ */
24
+ const DEFAULT_CONFIG = {
25
+ extensions: [
26
+ ".tsx",
27
+ ".ts",
28
+ ".jsx",
29
+ ".js"
30
+ ],
31
+ conditionNames: [
32
+ "import",
33
+ "types",
34
+ "default"
35
+ ],
36
+ mainFields: ["module", "main"],
37
+ extensionAlias: {
38
+ ".js": [
39
+ ".ts",
40
+ ".tsx",
41
+ ".js"
42
+ ],
43
+ ".jsx": [".tsx", ".jsx"],
44
+ ".ts": [".ts", ".tsx"]
45
+ },
46
+ tsconfig: "auto"
47
+ };
48
+ /**
49
+ * Create a module resolver with configurable options.
50
+ *
51
+ * The returned `resolve` function resolves a specifier relative to
52
+ * a source file path, returning the absolute path or `undefined` on failure.
53
+ */
54
+ function createModuleResolver(config = DEFAULT_CONFIG) {
55
+ const resolver = new ResolverFactory(config);
56
+ return { resolve(fromFile, specifier) {
57
+ try {
58
+ return resolver.resolveFileSync(fromFile, specifier).path ?? void 0;
59
+ } catch {
60
+ return;
61
+ }
62
+ } };
63
+ }
64
+
65
+ //#endregion
66
+ export { createModuleResolver };