swizzle silently drops nested subdirectories, producing a broken partial eject (e.g. Table/plugins)
- Dominant language
- TypeScript
- Stars
- 13k
- Forks
- 1.1k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 690
Description
## Summary
`astryx swizzle ` copies only the **top-level files** of a component directory and silently skips nested subdirectories. For components whose source is split across subfolders (e.g. `Table/plugins/*`), this produces an **incomplete and broken eject** — the copied entry file still imports from the dropped subdirectory, so the swizzled component fails module resolution — yet the CLI reports success. This contradicts the documented promise that swizzle "ejects a component's full source into your project to own."
## Root cause
`packages/cli/src/commands/swizzle.mjs` — the copy loop reads the component directory non-recursively and skips anything that isn't a file:
```js
// packages/cli/src/commands/swizzle.mjs (~L340)
const files = fs.readdirSync(componentDir);
for (const file of files) {
if (isExcludedFromCopy(file)) continue;
const srcPath = path.join(componentDir, file);
const stat = fs.statSync(srcPath);
if (!stat.isFile()) continue; // <-- subdirectories are silently dropped, no recursion
...
}
```
The pre-flight collision check (~L304) and the reported `copiedFiles` list (~L363) apply the same `stat.isFile()` filter, so nested files are invisible end-to-end — including in the success count.
Compounding it: `rewriteImports()` only rewrites **upward** relative imports (`/^\.\.\//`). A component's own **downward** imports into its subfolders (`./plugins/...`) are left untouched — now pointing at a directory that was never copied.
## Reproduction
```bash
node packages/cli/bin/astryx.mjs swizzle Table --out ./swizzled
```
`Table` has a `plugins/` subdirectory. After swizzling, the ejected `Table/index.ts` still contains (unmodified):
```ts
export {useTableSelection} from './plugins/selection';
export {useTableSortable} from './plugins/sortable';
export {useTablePagination, paginateData} from './plugins/pagination';
// ...15+ more re-exports from ./plugins/*
```
…but `./plugins/` does not exist in the output. The swizzled component fails to resolve on import/build, while the CLI reports a successful copy.
## Impact
- **Broken output, reported as success.** No error, no warning — the failure only surfaces later when the consumer tries to build/import the swizzled component.
- **Undermines a headline feature.** "Open internals / own the source" is a core selling point; it silently doesn't hold for any multi-directory component.
- Confirmed affected: `Table` (`plugins/`). Other core components ship nested source dirs too (`Calendar`, `FormLayout`) and are candidates for the same failure; any current or future component that organizes source into subfolders is at risk.
## Suggested fix
Recurse into subdirectories when copying (preserving relative structure under the output dir), and keep the collision check + reported file list consistent with the recursive walk. `rewriteImports` should continue to leave intra-component `./`-relative paths intact once the subtree is actually copied.
Alternatively, if partial ejects are ever intentional, `swizzle` should at minimum **detect and refuse/warn** when a component directory contains subdirectories it isn't copying, rather than reporting success.
---
*Filed after an architecture review of the repo at current `main`. Happy to open a PR for the recursive-copy fix if that's useful.*
Contributor guide
Assessment
This issue has not been assessed yet.