Skip to content

Commit ecef606

Browse files
refactor: validate specifiers and rewrite policy. (#103)
1 parent 229b4ec commit ecef606

11 files changed

Lines changed: 91 additions & 32 deletions

File tree

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@ These are the CLI options `duel` supports to work alongside your project's `tsco
128128
- `--exports-config` Provide a JSON file with `{ "entries": ["./dist/index.js", ...], "main": "./dist/index.js" }` to limit which outputs become exports.
129129
- `--exports-validate` Dry-run exports generation/validation without writing package.json; combine with `--exports` or `--exports-config` to emit after validation.
130130
- `--rewrite-policy [safe|warn|skip]` Control how specifier rewrites behave when a matching target is missing (`safe` warns and skips, `warn` rewrites and warns, `skip` leaves specifiers untouched).
131-
- `--validate-specifiers` Validate that rewritten specifiers resolve to outputs; defaults to `true` when `--rewrite-policy` is `safe`.
132131
- `--detect-dual-package-hazard, -H [off|warn|error]` Flag mixed import/require usage of dual packages; `error` exits non-zero. If project-scope checks lack file paths, or file-scope checks return pathless diagnostics, Duel falls back to file-scope reporting during transforms so diagnostics include locations.
133132
- `--dual-package-hazard-allowlist <pkg>[,<pkg>...]` Comma-separated packages to ignore when reporting dual package hazards (e.g., `react`).
134133
- `--dual-package-hazard-scope [file|project]` Run hazard checks per file (default) or aggregate across the project.

docs/faq.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44

55
Duel emits JavaScript and declaration files into `dist/`, and the same `tsconfig.json` is reused for both emitting and type-checking. In workspace setups, sibling packages often import `@scope/pkg` via its package exports, so TypeScript resolves into the generated `.d.ts` files inside `dist/`. When `tsc` later tries to emit, it refuses to overwrite files it now treats as inputs (TS5055). Adding `"exclude": ["dist"]` keeps build artifacts out of the compilation so Duel can regenerate them safely.
66

7-
## Why is this mostly a workspace issue?
7+
### Why is this mostly a workspace issue?
88

99
Single-package projects seldom import their own published outputs, but monorepos routinely do. When another package in the workspace references `@scope/pkg`, TypeScript follows that path right back into the freshly built `dist/` directory. Without the exclusion, the repo ends up both producing and consuming those artifacts during the same build, confusing the compiler.
1010

11-
## Do I still need `"outDir": "dist"` if I exclude it?
11+
### Do I still need `"outDir": "dist"` if I exclude it?
1212

1313
Yes. `outDir` controls where Duel (and `tsc`) place emit results. The exclusion only affects what the compiler considers source inputs; it does not change the emit destination.
1414

15-
## Can I avoid editing `tsconfig.json`?
15+
### Can I avoid editing `tsconfig.json`?
1616

1717
You could maintain separate configs (one for emit, one for checking) or lean on project references, but Duel's default workflow assumes a single package-level config. Excluding `dist/` is the simplest, least error-prone way to ensure dual builds, incremental type-checks, and workspace consumers all cooperate.
1818

docs/roadmap.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Roadmap
22

33
- Consider auto-enabling `globals` mode when the build would hit TypeScript 58658 scenarios, with `--mode none` as an explicit opt-out.
4-
- Decide coupling of `--rewrite-policy` with `--validate-specifiers` (fail fast when `warn|safe` + validation=false, or document decoupling).
54
- Consider a `--quiet` flag to reduce log chatter alongside warnings/hazards.
5+
- Revisit a gated `--validate-specifiers` flag for advanced workflows that need explicit validation separate from rewrite policy.
66
- Memoize resolver existence checks to trim repeated sync fs hits during rewrite, if profiling shows it matters.
77
- Optionally prune stale `_duel_*` temp workspaces on startup (behind env flag and skipped in CI) to keep project roots tidy.
88
- Deprecate `copyMode=full` (announce as compatibility-only, plan removal if unused) and favor the selective copy path by default.

docs/v4-migration.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This guide highlights behavior changes introduced in v4 and how to adapt existin
44

55
## Breaking/Behavioral Changes
66

7-
- **Specifier rewrites now default to safer behavior.** `--rewrite-policy` now defaults to `safe`, and `--validate-specifiers` is forced on when policy is `safe`. Missing targets skip rewrites and emit warnings instead of silently rewriting.
7+
- **Specifier rewrites now default to safer behavior.** `--rewrite-policy` now defaults to `safe`; missing targets skip rewrites and emit warnings instead of silently rewriting.
88
- **Dual-package hazard detection enabled by default.** `--detect-dual-package-hazard` now defaults to `warn`, and `--dual-package-hazard-scope` defaults to `file`. You may see new warnings (or errors if configured).
99
- **Build pipeline runs in a temp workspace copy.** Dual builds no longer mutate the root `package.json`; a temp copy is created with an adjusted `type`. External tools that watched in-place `package.json` edits will see different behavior.
1010
- **IMPORTANT:** The temp-copy flow adds some I/O for large repos (copying sources/reference packages and running transforms there). `node_modules` is skipped; when references exist, existing `dist` may be reused. Very large projects may see modestly slower runs compared to the old in-place mutation.
@@ -18,7 +18,7 @@ This guide highlights behavior changes introduced in v4 and how to adapt existin
1818

1919
## Restoring v3-like Behavior
2020

21-
- **Specifier rewrites:** use `--rewrite-policy warn --validate-specifiers false` to continue rewriting even when targets are missing (previous behavior). To fully bypass rewrites, set `--rewrite-policy skip`.
21+
- **Specifier rewrites:** use `--rewrite-policy warn` to continue rewriting even when targets are missing (previous behavior). To fully bypass rewrites, set `--rewrite-policy skip`.
2222
- **Hazard detection:** disable by passing `--detect-dual-package-hazard off` (or set scope to `project` only if you want aggregated warnings).
2323
- **Build/package.json side effects:** if tooling depended on in-place `package.json` mutation, update it to read outputs from the temp dual build outputs (`dist/esm` / `dist/cjs` or `outDir` variants). No flag restores the old mutation pattern.
2424
- **TypeScript references:** if build mode changes output undesirably, remove `references` or run your own `tsc -p` before calling `duel`.
@@ -27,7 +27,7 @@ This guide highlights behavior changes introduced in v4 and how to adapt existin
2727

2828
1. Pick a rewrite policy:
2929
- Safety-first (default): keep `--rewrite-policy safe` (default) and address any missing-target warnings by fixing paths or adding files.
30-
- Legacy: add `--rewrite-policy warn --validate-specifiers false` to mimic v3 rewrites.
30+
- Legacy: set `--rewrite-policy warn` to mimic v3 rewrites.
3131
2. Decide on hazard handling:
3232
- Keep defaults to surface hazards.
3333
- Silence: `--detect-dual-package-hazard off`.
@@ -38,9 +38,9 @@ This guide highlights behavior changes introduced in v4 and how to adapt existin
3838
## New/Notable Flags
3939

4040
- `--rewrite-policy [safe|warn|skip]` (default: `safe`)
41-
- `--validate-specifiers` (defaults to `true` when policy is `safe`; otherwise `false`)
4241
- `--detect-dual-package-hazard [off|warn|error]` (default: `warn`)
4342
- `--dual-package-hazard-scope [file|project]` (default: `file`)
43+
- `--dual-package-hazard-allowlist <pkg1,pkg2>`
4444
- `--exports-config <path>`
4545
- `--exports-validate`
4646
- `--verbose`

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@knighted/duel",
3-
"version": "4.0.0-rc.6",
3+
"version": "4.0.0-rc.7",
44
"description": "TypeScript dual packages.",
55
"type": "module",
66
"main": "dist/esm/duel.js",

src/duel.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ const duel = async args => {
8484
exportsConfig,
8585
exportsValidate,
8686
rewritePolicy,
87-
validateSpecifiers,
8887
detectDualPackageHazard,
8988
dualPackageHazardAllowlist,
9089
dualPackageHazardScope,
@@ -884,7 +883,6 @@ const duel = async args => {
884883
dualPackageHazardAllowlist: [...hazardAllowlist],
885884
onDiagnostics: handleRewriteDiagnostic,
886885
rewritePolicy,
887-
validateSpecifiers,
888886
onWarn: message => logWarn(message),
889887
onRewrite: (from, to) => logVerbose(`Rewrote specifiers in ${from} -> ${to}`),
890888
})
@@ -907,7 +905,6 @@ const duel = async args => {
907905
dualPackageHazardAllowlist: [...hazardAllowlist],
908906
onDiagnostics: handleRewriteDiagnostic,
909907
rewritePolicy,
910-
validateSpecifiers,
911908
onWarn: message => logWarn(message),
912909
onRewrite: (from, to) => logVerbose(`Rewrote specifiers in ${from} -> ${to}`),
913910
})

src/init.js

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,6 @@ const cliOptions = [
5050
value: '[safe|warn|skip]',
5151
desc: 'Control specifier rewriting behavior.',
5252
},
53-
{
54-
long: 'validate-specifiers',
55-
desc: 'Validate rewritten specifiers against outputs.',
56-
},
5753
{
5854
long: 'detect-dual-package-hazard',
5955
short: 'H',
@@ -143,10 +139,6 @@ const init = async args => {
143139
type: 'string',
144140
default: 'safe',
145141
},
146-
'validate-specifiers': {
147-
type: 'boolean',
148-
default: false,
149-
},
150142
'detect-dual-package-hazard': {
151143
type: 'string',
152144
short: 'H',
@@ -197,7 +189,6 @@ const init = async args => {
197189
'exports-config': exportsConfig,
198190
'exports-validate': exportsValidate,
199191
'rewrite-policy': rewritePolicy,
200-
'validate-specifiers': validateSpecifiers,
201192
'detect-dual-package-hazard': detectDualPackageHazard,
202193
'dual-package-hazard-allowlist': dualPackageHazardAllowlist,
203194
'dual-package-hazard-scope': dualPackageHazardScope,
@@ -311,7 +302,6 @@ const init = async args => {
311302

312303
let modulesFinal = false
313304
let transformSyntaxFinal = false
314-
const validateSpecifiersFinal = rewritePolicy === 'safe' ? true : validateSpecifiers
315305

316306
if (mode) {
317307
if (mode === 'none') {
@@ -335,7 +325,6 @@ const init = async args => {
335325
exportsConfig,
336326
exportsValidate,
337327
rewritePolicy,
338-
validateSpecifiers: validateSpecifiersFinal,
339328
detectDualPackageHazard,
340329
dualPackageHazardAllowlist: hazardAllowlist,
341330
dualPackageHazardScope,

src/resolver.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,12 @@ const rewriteSpecifiersAndExtensions = async (filenames, options = {}) => {
8383
dualPackageHazardScope,
8484
onDiagnostics,
8585
rewritePolicy = 'safe',
86-
validateSpecifiers = false,
86+
validateSpecifiers,
8787
onRewrite = () => {},
8888
onWarn = () => {},
8989
} = options
90-
90+
const validateSpecifiersFinal =
91+
validateSpecifiers ?? (rewritePolicy === 'skip' ? false : true)
9192
const rewrites = []
9293

9394
for (const filename of filenames) {
@@ -183,7 +184,7 @@ const rewriteSpecifiersAndExtensions = async (filenames, options = {}) => {
183184
return next
184185
}
185186

186-
if (validateSpecifiers) {
187+
if (validateSpecifiersFinal) {
187188
const fileDir = dirname(filename)
188189
const base = collapsed.replace(/\.js$/, '')
189190
const candidates = []

test/rewritePolicy.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ describe('rewrite-policy', () => {
3232
target: 'commonjs',
3333
ext: '.cjs',
3434
rewritePolicy: 'safe',
35-
validateSpecifiers: true,
3635
onWarn: msg => warnings.push(msg),
3736
})
3837

@@ -52,7 +51,6 @@ describe('rewrite-policy', () => {
5251
target: 'commonjs',
5352
ext: '.cjs',
5453
rewritePolicy: 'warn',
55-
validateSpecifiers: true,
5654
onWarn: msg => warnings.push(msg),
5755
})
5856

@@ -72,7 +70,6 @@ describe('rewrite-policy', () => {
7270
target: 'commonjs',
7371
ext: '.cjs',
7472
rewritePolicy: 'skip',
75-
validateSpecifiers: true,
7673
onWarn: msg => warnings.push(msg),
7774
})
7875

0 commit comments

Comments
 (0)