|
| 1 | +# Plan: `barrel` Command |
| 2 | + |
| 3 | +## Goal |
| 4 | + |
| 5 | +Add `dosido barrel <directory>` as a mutating command that: |
| 6 | + |
| 7 | +- finds incoming static references from outside a directory to files inside it, |
| 8 | +- rewrites supported references to point at the directory itself, |
| 9 | +- creates or updates `<directory>/index.ts`, |
| 10 | +- exports only the names required by those rewritten references, |
| 11 | +- honors `--root`, `--dry-run`, `--json`, `--include`, and `--ignore`. |
| 12 | + |
| 13 | +## Accepted Semantics |
| 14 | + |
| 15 | +- Rewrite only references from outside the target directory. Leave internal |
| 16 | + references alone. |
| 17 | +- Use `<directory>/index.ts` as the barrel file. |
| 18 | +- If a sibling barrel file already exists, such as `index.tsx`, `index.js`, |
| 19 | + `index.mts`, or `index.mjs`, fail instead of reusing it. |
| 20 | +- If `index.ts` already exists, merge into it idempotently. Do not overwrite it. |
| 21 | +- Export only symbols needed by incoming references. |
| 22 | +- Rewrite default imports to named imports using the local default import name. |
| 23 | +- For namespace imports, generate a namespace value in the barrel: |
| 24 | + |
| 25 | +```ts |
| 26 | +import * as util from './util'; |
| 27 | +export {util}; |
| 28 | +``` |
| 29 | + |
| 30 | + Then rewrite the importer to `import {util} from './shared';`. |
| 31 | +- Skip string-literal dynamic imports with a warning. |
| 32 | +- Skip side-effect imports with a warning. |
| 33 | +- Do not support CommonJS `require(...)`. |
| 34 | +- Fail on public-name collisions rather than inventing aliases. |
| 35 | +- Generated paths from the barrel should prefer directory-index paths. For a |
| 36 | + target like `src/shared/sub/index.ts`, render `./sub`, not `./sub/index`. |
| 37 | + |
| 38 | +## Phase 1: Fixtures And API Contract |
| 39 | + |
| 40 | +Add fixtures first so the behavior is locked before implementation details get |
| 41 | +too deep. |
| 42 | + |
| 43 | +1. Add `test/fixtures/barrel/named-imports-apply`. |
| 44 | + - External imports reference two files inside a directory. |
| 45 | + - Expected tree includes a new `index.ts`. |
| 46 | + - External imports point at the directory. |
| 47 | +2. Add `test/fixtures/barrel/default-import`. |
| 48 | + - Covers `import Button from './components/Button'`. |
| 49 | + - Expected import is `import {Button} from './components';`. |
| 50 | + - Expected barrel line is `export {default as Button} from './Button';`. |
| 51 | +3. Add `test/fixtures/barrel/mixed-default-and-named`. |
| 52 | + - Covers one import declaration containing default and named specifiers. |
| 53 | +4. Add `test/fixtures/barrel/type-only`. |
| 54 | + - Covers `import type`. |
| 55 | + - Expected barrel uses `export type`. |
| 56 | +5. Add `test/fixtures/barrel/namespace-import`. |
| 57 | + - Covers namespace import rewriting and generated namespace import/export in |
| 58 | + the barrel. |
| 59 | +6. Add `test/fixtures/barrel/re-export-source`. |
| 60 | + - Covers `export {x} from './shared/x'`. |
| 61 | +7. Add `test/fixtures/barrel/existing-index-idempotent`. |
| 62 | + - Existing `index.ts` gets missing generated lines appended. |
| 63 | + - A second run is a no-op or produces stable no-op output. |
| 64 | +8. Add negative fixtures: |
| 65 | + - `name-collision` |
| 66 | + - `dynamic-import-warning` |
| 67 | + - `side-effect-import-warning` |
| 68 | + - `sibling-index-conflict` |
| 69 | + - `missing-directory` |
| 70 | +9. Add a dry-run JSON fixture. |
| 71 | + - Assert planned changes and unchanged input tree. |
| 72 | +10. Add `include-ignore` coverage. |
| 73 | + |
| 74 | +Expected command shape: |
| 75 | + |
| 76 | +```bash |
| 77 | +dosido barrel <directory> [--include <glob>] [--ignore <glob>] [--json] |
| 78 | +``` |
| 79 | + |
| 80 | +Expected public API shape: |
| 81 | + |
| 82 | +```ts |
| 83 | +makeBarrel({directory}, options) |
| 84 | +``` |
| 85 | + |
| 86 | +## Phase 2: Command Skeleton |
| 87 | + |
| 88 | +Add the command entry points without the full rewrite logic yet. |
| 89 | + |
| 90 | +1. Create `src/commands/barrel.ts`. |
| 91 | +2. Define result and option types: |
| 92 | + - `MakeBarrelArgs` |
| 93 | + - `MakeBarrelOptions` |
| 94 | + - `BarrelExport` |
| 95 | + - `MakeBarrelResult` |
| 96 | +3. Add `makeBarrel(...)`. |
| 97 | +4. Validate: |
| 98 | + - project root |
| 99 | + - target directory exists and is a directory |
| 100 | + - sibling `index.*` conflicts |
| 101 | +5. Return `CommandResult`-style fields: |
| 102 | + - `changes` |
| 103 | + - `messages` |
| 104 | + - optional `warnings` |
| 105 | +6. Wire the CLI in `src/cli.ts`. |
| 106 | + - Parse repeated `--include`. |
| 107 | + - Parse repeated `--ignore`. |
| 108 | + - Support `--json` via global parser. |
| 109 | +7. Export the API from `src/index.ts`. |
| 110 | +8. Update CLI help text. |
| 111 | + |
| 112 | +## Phase 3: Usage Collection |
| 113 | + |
| 114 | +Build a command-local AST collector that keeps source ranges and enough |
| 115 | +specifier detail to render replacements. |
| 116 | + |
| 117 | +1. Scan source files with `findSourceFiles(projectRoot, include, ignore)`. |
| 118 | +2. Skip files inside the target directory. |
| 119 | +3. Parse with `parseModule(...)`. |
| 120 | +4. For each supported declaration, resolve its source with |
| 121 | + `resolveImportPath(...)`. |
| 122 | +5. Keep records only when the resolved target is inside the target directory. |
| 123 | +6. Record warnings and continue on parse errors. |
| 124 | +7. Collect these usage kinds: |
| 125 | + - named import specifier |
| 126 | + - default import specifier |
| 127 | + - namespace import specifier |
| 128 | + - named export-from specifier |
| 129 | + - export namespace specifier, if Babel syntax support is already available |
| 130 | + - dynamic import as skipped warning |
| 131 | + - side-effect import as skipped warning |
| 132 | +8. Preserve: |
| 133 | + - source file path |
| 134 | + - resolved target file path |
| 135 | + - declaration range |
| 136 | + - source literal range |
| 137 | + - import/export kind |
| 138 | + - type-only status |
| 139 | + - imported name, exported name, and local name as applicable |
| 140 | + |
| 141 | +Do not use `listIncomingDeps(...)` directly for this phase; it loses the |
| 142 | +specifier-level detail required for default and namespace rewrites. |
| 143 | + |
| 144 | +## Phase 4: Barrel Export Planner |
| 145 | + |
| 146 | +Transform usage records into deterministic barrel additions. |
| 147 | + |
| 148 | +1. Render barrel-relative source paths with a helper. |
| 149 | + - Use `getRelativeImportPath(indexFile, targetFile)`. |
| 150 | + - If the target file is an index module, strip trailing `/index`. |
| 151 | +2. Generate needed additions: |
| 152 | + - Named value: `export {name} from './file';` |
| 153 | + - Named type: `export type {Name} from './file';` |
| 154 | + - Default: `export {default as LocalName} from './file';` |
| 155 | + - Namespace: `import * as LocalName from './file';` plus `export {LocalName};` |
| 156 | +3. Track public barrel names. |
| 157 | + - Named import public name is the imported/exported name. |
| 158 | + - Default import public name is the local default name. |
| 159 | + - Namespace import public name is the local namespace name. |
| 160 | +4. Detect collisions. |
| 161 | + - Same public name from same target and same export form is okay. |
| 162 | + - Same public name from different targets is an error. |
| 163 | + - Same public name already exported by existing `index.ts` from another |
| 164 | + target is an error. |
| 165 | +5. Sort additions for stable output. |
| 166 | + - Value exports before type exports is fine, as long as it is deterministic. |
| 167 | + - Keep namespace import/export pairs adjacent or otherwise predictable. |
| 168 | + |
| 169 | +## Phase 5: Import And Export Rewrites |
| 170 | + |
| 171 | +Apply source edits to each outside file. |
| 172 | + |
| 173 | +1. Compute the directory import specifier for each importer: |
| 174 | + |
| 175 | +```ts |
| 176 | +getRelativeImportPath(importerFile, targetDirectory) |
| 177 | +``` |
| 178 | + |
| 179 | +2. For named-only import declarations: |
| 180 | + - Preserve specifiers and aliases. |
| 181 | + - Replace only the source literal. |
| 182 | +3. For default imports: |
| 183 | + - Convert the default specifier to a named specifier using the local name. |
| 184 | + - Preserve existing named specifiers and aliases. |
| 185 | + - Preserve `import type` where valid. |
| 186 | +4. For mixed default plus named imports: |
| 187 | + - Render one named import from the directory. |
| 188 | +5. For namespace imports: |
| 189 | + - Rewrite `import * as util from './shared/util'` to |
| 190 | + `import {util} from './shared';`. |
| 191 | +6. For named export-from declarations: |
| 192 | + - Replace the source literal. |
| 193 | +7. For skipped dynamic and side-effect imports: |
| 194 | + - Do not rewrite. |
| 195 | + - Emit warnings that include the project-relative file path and original |
| 196 | + source value. |
| 197 | +8. Use `applySourceEdits(...)` with ranges sorted through the existing helper. |
| 198 | +9. Do not write any files until every change has been planned successfully. |
| 199 | + |
| 200 | +## Phase 6: Existing `index.ts` Merge |
| 201 | + |
| 202 | +Make the barrel file update idempotent. |
| 203 | + |
| 204 | +1. If `index.ts` does not exist, create it with generated additions. |
| 205 | +2. If `index.ts` exists: |
| 206 | + - Parse existing exports and namespace import/export pairs. |
| 207 | + - Preserve all existing content. |
| 208 | + - Append only missing generated additions. |
| 209 | + - Ensure a trailing newline. |
| 210 | +3. Treat existing exports as part of collision detection. |
| 211 | +4. If parsing the existing index fails, fail the command before any writes. |
| 212 | + |
| 213 | +The first implementation can use generated line matching for duplicate |
| 214 | +detection, but it should still parse enough existing exports to detect public |
| 215 | +name conflicts. |
| 216 | + |
| 217 | +## Phase 7: CLI, Docs, And Public API |
| 218 | + |
| 219 | +1. Update `src/cli.ts` usage text. |
| 220 | +2. Add command branch for `barrel`. |
| 221 | +3. Update alias map only if a short alias is wanted. |
| 222 | + - No alias is required for the first version. |
| 223 | +4. Update `src/index.ts` exports. |
| 224 | +5. Update `README.md`: |
| 225 | + - command usage |
| 226 | + - behavior notes for default and namespace imports |
| 227 | + - skipped dynamic and side-effect imports |
| 228 | +6. Add a changeset if this repo expects one for user-facing command additions. |
| 229 | + |
| 230 | +## Phase 8: Verification |
| 231 | + |
| 232 | +Run focused tests first, then package-level checks. |
| 233 | + |
| 234 | +1. `pnpm test:run` |
| 235 | +2. `pnpm build` |
| 236 | +3. `pnpm test:dist` |
| 237 | +4. `pnpm lint` |
| 238 | + |
| 239 | +For fixtures that include `tsconfig.json`, use the existing `typecheck(project)` |
| 240 | +helper in the compiled CLI tests where practical. |
| 241 | + |
| 242 | +## Implementation Order |
| 243 | + |
| 244 | +1. Add the smallest API-level test for named imports. |
| 245 | +2. Implement enough `barrel.ts` to pass named imports. |
| 246 | +3. Add default import coverage and implement declaration rewriting. |
| 247 | +4. Add type-only and re-export coverage. |
| 248 | +5. Add namespace coverage. |
| 249 | +6. Add existing-index merge and idempotence. |
| 250 | +7. Add skipped dynamic and side-effect warning coverage. |
| 251 | +8. Add collision and sibling-index negative coverage. |
| 252 | +9. Wire compiled CLI fixtures. |
| 253 | +10. Update README and public exports. |
| 254 | + |
| 255 | +This order keeps the core path working early while delaying the high-risk |
| 256 | +collision and merge details until the shape of generated output is stable. |
0 commit comments