tsrx-typegen 0.2.0 → 0.2.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
@@ -5,6 +5,10 @@ command uses the consuming package's TypeScript and tsrx compiler, then maps
5
5
  source-extension imports in the emitted declarations to JavaScript module
6
6
  specifiers.
7
7
 
8
+ ```sh
9
+ pnpm exec tsrx-typegen --target octane
10
+ ```
11
+
8
12
  Install `tsrx-typegen` as a development tool alongside `typescript` and
9
13
  `@tsrx/typescript-plugin`. Each target in `tsrx-typegen.json` names its
10
14
  renderer, project config, declaration output directory, and public subpaths.
@@ -26,7 +30,13 @@ missing or stale output, unresolved `.tsrx` references, and code export paths
26
30
  without declaration targets. It refuses to overwrite an unmanaged declaration
27
31
  and removes stale files only when the manifest says it owns them. Same-name
28
32
  sources such as `Button.ts` and `Button.tsrx` need separate target configs or an
29
- explicit declaration override.
33
+ explicit declaration override. Case-only source renames replace the prior
34
+ manifest-owned declaration, including on case-insensitive filesystems; manual
35
+ declarations remain protected.
36
+
37
+ ```sh
38
+ pnpm exec tsrx-typegen --target octane --check
39
+ ```
30
40
 
31
41
  Treat the output directory, including its ownership manifest, as generated
32
42
  build artifacts: ignore it in Git, formatting, and linting. Keep handwritten
@@ -36,7 +46,7 @@ the tarball. Generate before local consumer typechecks, and regenerate in
36
46
  `prepack` before validating the package. Workspaces that resolve generated
37
47
  types directly also need generation during setup and refreshes during
38
48
  development; this repository's workflow is documented in
39
- [Develop against generated declarations](../../docs/toolchain-notes.md#develop-against-generated-declarations).
49
+ [Develop against generated declarations](../../docs/notes/toolchain-notes.md#develop-against-generated-declarations).
40
50
 
41
51
  `--project` selects the target whose project path matches. Use `--target` to
42
52
  select a named target directly. A target can look like this:
@@ -56,7 +66,7 @@ select a named target directly. A target can look like this:
56
66
  }
57
67
  }
58
68
  }
59
- }
69
+ }
60
70
  }
61
71
  ```
62
72
 
@@ -65,9 +75,9 @@ generated directory, then map their generated output path explicitly:
65
75
 
66
76
  ```json
67
77
  {
68
- "overrides": {
69
- "Button.d.ts": "types/overrides/Button.d.ts"
70
- }
78
+ "overrides": {
79
+ "Button.d.ts": "types/overrides/Button.d.ts"
80
+ }
71
81
  }
72
82
  ```
73
83
 
@@ -76,10 +86,22 @@ manifest; check mode detects edits to either the source mapping or generated
76
86
  copy. Override declarations must use publishable module specifiers and may not
77
87
  reference `.tsrx` files.
78
88
 
89
+ ```sh
90
+ pnpm exec tsrx-typegen --target octane --check
91
+ ```
92
+
79
93
  Relative imports between generated declarations receive runtime extensions
80
94
  from the source map, including extensionless imports. This supports NodeNext
81
95
  consumers as well as bundler resolution without requiring `allowArbitraryExtensions`.
82
96
 
97
+ ```ts
98
+ // Plain TypeScript consumer of the package whose exports are configured above.
99
+ import { Button } from 'my-component-library'
100
+
101
+ // Button is a component exported by your library's src/index.ts.
102
+ export { Button }
103
+ ```
104
+
83
105
  Use separate target records and project configs when public signatures differ.
84
106
  Each config must select the same source variants as its runtime build.
85
107
  `runtime` records the published runtime path or condition map, while `types`
@@ -89,6 +111,12 @@ and `.ts` to `.js`, with `.mts` to `.mjs` and `.cts` to `.cjs`. Packages with
89
111
  another runtime layout can override these mappings in `sourceExtensions` at
90
112
  the target or root level.
91
113
 
114
+ ```json
115
+ {
116
+ "sourceExtensions": { ".tsrx": ".js", ".mts": ".mjs", ".cts": ".cjs" }
117
+ }
118
+ ```
119
+
92
120
  ## Publish checks
93
121
 
94
122
  Use `--pack-check` before publishing. It checks every configured target unless
@@ -101,6 +129,11 @@ bare package imports are declared dependencies or peers. The command needs
101
129
  TypeScript consumer test for the module-resolution modes and public prop
102
130
  contracts your package supports.
103
131
 
132
+ ```sh
133
+ pnpm exec tsrx-typegen --pack-check
134
+ pnpm exec tsrx-typegen --pack-check --target octane
135
+ ```
136
+
104
137
  A target with `"emit": false` exists only to drive packed-package
105
138
  verification. Packages whose declarations are handwritten or produced by
106
139
  another tool declare one so `--pack-check` skips generation and freshness
@@ -109,15 +142,35 @@ selected for generation; a plain `--target` run against them fails. Every code
109
142
  export path still needs a `types` condition — pointing at a `.d.ts` file or,
110
143
  for source-published packages, the `.ts` source itself.
111
144
 
145
+ ```json
146
+ {
147
+ "targets": {
148
+ "manual": {
149
+ "emit": false,
150
+ "renderer": "octane",
151
+ "project": "tsconfig.types.json",
152
+ "outDir": "types/generated",
153
+ "entrypoints": {
154
+ ".": {
155
+ "source": "src/index.ts",
156
+ "runtime": "./dist/index.js",
157
+ "types": "./types/index.d.ts"
158
+ }
159
+ }
160
+ }
161
+ }
162
+ }
163
+ ```
164
+
112
165
  Run the same gate from `prepack` so `pnpm pack` and publication share it:
113
166
 
114
167
  ```json
115
168
  {
116
- "scripts": {
117
- "typegen": "tsrx-typegen --target octane",
118
- "build": "vite build && pnpm typegen",
119
- "prepack": "pnpm build && tsrx-typegen --pack-check"
120
- }
169
+ "scripts": {
170
+ "typegen": "tsrx-typegen --target octane",
171
+ "build": "vite build && pnpm typegen",
172
+ "prepack": "pnpm build && tsrx-typegen --pack-check"
173
+ }
121
174
  }
122
175
  ```
123
176
 
@@ -125,6 +178,10 @@ When run from a package root that has `tsrx-typegen.json`, `xplat doctor`
125
178
  delegates to `tsrx-typegen --pack-check` and returns a failing exit status if
126
179
  that declaration gate fails.
127
180
 
181
+ ```sh
182
+ pnpm exec xplat doctor
183
+ ```
184
+
128
185
  The first supported backend is the classic `tsrx-tsc` path with TypeScript
129
186
  5.9.x. TypeScript 7 content-mapper output needs upstream declaration naming and
130
187
  specifier support before it can replace this backend. Generation rejects
@@ -137,3 +194,10 @@ packed package with a plain TypeScript consumer; generated declarations may
137
194
  still express a different contract even when their paths and exports are
138
195
  valid. Use explicit overrides for exceptional signatures and keep those
139
196
  overrides outside the generated output directory.
197
+
198
+ ```ts
199
+ // Source of your component package; use an explicit return type for helpers.
200
+ export function formatCount(count: number): string {
201
+ return `${count} packed`
202
+ }
203
+ ```
package/package.json CHANGED
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "name": "tsrx-typegen",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Generate consumer-ready TypeScript declarations for TSRX libraries",
5
5
  "keywords": [
6
- "tsrx",
7
- "typegen",
8
- "typescript",
6
+ "build-tool",
7
+ "codegen",
9
8
  "declarations",
10
9
  "dts",
11
- "codegen",
12
- "build-tool"
10
+ "tsrx",
11
+ "typegen",
12
+ "typescript"
13
13
  ],
14
- "license": "MIT",
15
14
  "homepage": "https://github.com/octane-xplat/octane-xplat/tree/main/packages/tsrx-typegen",
15
+ "license": "MIT",
16
16
  "repository": {
17
17
  "type": "git",
18
- "url": "git+https://github.com/octane-xplat/octane-xplat.git",
18
+ "url": "https://github.com/octane-xplat/octane-xplat",
19
19
  "directory": "packages/tsrx-typegen"
20
20
  },
21
21
  "bin": {
package/src/cli.mjs CHANGED
@@ -92,7 +92,12 @@ function configuredTargetNames(projectRoot, selectedTarget) {
92
92
  }
93
93
 
94
94
  const targets = config?.targets
95
- if (!targets || typeof targets !== 'object' || Array.isArray(targets) || !Object.keys(targets).length) {
95
+ if (
96
+ !targets ||
97
+ typeof targets !== 'object' ||
98
+ Array.isArray(targets) ||
99
+ !Object.keys(targets).length
100
+ ) {
96
101
  throw new Error(`${path}: --pack-check requires at least one configured target`)
97
102
  }
98
103
 
@@ -124,16 +129,24 @@ function readProjectConfig(ts, configPath, projectRoot, target) {
124
129
  [{ extension: '.tsrx', isMixedContent: true, scriptKind: ts.ScriptKind.Deferred }],
125
130
  )
126
131
 
127
- if (!parsed) {throw new Error(`Could not read ${configPath}`)}
132
+ if (!parsed) {
133
+ throw new Error(`Could not read ${configPath}`)
134
+ }
135
+
128
136
  const errors = parsed.errors.map((diagnostic) =>
129
137
  ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
130
138
  )
131
139
 
132
- if (errors.length) {throw new Error(errors.join('\n'))}
140
+ if (errors.length) {
141
+ throw new Error(errors.join('\n'))
142
+ }
143
+
133
144
  const options = parsed.options
134
145
  const jsxRenderer =
135
146
  options.jsxImportSource ??
136
- (options.jsx === ts.JsxEmit.ReactJSX || options.jsx === ts.JsxEmit.ReactJSXDev ? 'react' : undefined)
147
+ (options.jsx === ts.JsxEmit.ReactJSX || options.jsx === ts.JsxEmit.ReactJSXDev
148
+ ? 'react'
149
+ : undefined)
137
150
 
138
151
  if (target?.renderer && jsxRenderer && target.renderer !== jsxRenderer) {
139
152
  throw new Error(
@@ -149,7 +162,9 @@ function readProjectConfig(ts, configPath, projectRoot, target) {
149
162
 
150
163
  const outputDirectory = target?.outDir ?? options.declarationDir ?? options.outDir
151
164
  if (!outputDirectory) {
152
- throw new Error(`${configPath} must set target outDir, compilerOptions.outDir, or declarationDir.`)
165
+ throw new Error(
166
+ `${configPath} must set target outDir, compilerOptions.outDir, or declarationDir.`,
167
+ )
153
168
  }
154
169
 
155
170
  return {
@@ -163,7 +178,10 @@ function readProjectConfig(ts, configPath, projectRoot, target) {
163
178
  }
164
179
 
165
180
  function commonDirectory(paths) {
166
- if (!paths.length) {throw new Error('TypeScript project has no root files.')}
181
+ if (!paths.length) {
182
+ throw new Error('TypeScript project has no root files.')
183
+ }
184
+
167
185
  const segments = paths.map((path) => resolve(dirname(path)).split(sep))
168
186
  const common = []
169
187
  for (let index = 0; segments.every((parts) => parts[index] === segments[0][index]); index += 1) {
@@ -178,7 +196,10 @@ function sourceDeclarationMap(rootDir, fileNames, extensions, overrides) {
178
196
  const runtimeByDeclaration = new Map()
179
197
  for (const source of fileNames) {
180
198
  const sourceExtension = extname(source).toLowerCase()
181
- if (!Object.hasOwn(extensions, sourceExtension) || /\.d\.[cm]?ts$/i.test(source)) {continue}
199
+ if (!Object.hasOwn(extensions, sourceExtension) || /\.d\.[cm]?ts$/i.test(source)) {
200
+ continue
201
+ }
202
+
182
203
  const sourcePath = relative(rootDir, source)
183
204
  if (!sourcePath || sourcePath.startsWith(`..${sep}`) || isAbsolute(sourcePath)) {
184
205
  throw new Error(`Source file is outside compilerOptions.rootDir: ${source}`)
@@ -187,7 +208,10 @@ function sourceDeclarationMap(rootDir, fileNames, extensions, overrides) {
187
208
  const declarationExtension =
188
209
  sourceExtension === '.mts' ? '.d.mts' : sourceExtension === '.cts' ? '.d.cts' : '.d.ts'
189
210
 
190
- const declaration = normalize(sourcePath.slice(0, -sourceExtension.length) + declarationExtension)
211
+ const declaration = normalize(
212
+ sourcePath.slice(0, -sourceExtension.length) + declarationExtension,
213
+ )
214
+
191
215
  const prior = sourceOutputs.get(declaration)
192
216
  if (prior && !overrides.has(declaration)) {
193
217
  throw new Error(
@@ -211,7 +235,10 @@ function configOptions(projectRoot, projectArgument, targetArgument) {
211
235
  config = JSON.parse(readFileSync(path, 'utf8'))
212
236
  } catch (error) {
213
237
  if (error.code === 'ENOENT') {
214
- if (targetArgument) {throw new Error(`No tsrx-typegen.json defines target ${targetArgument}.`)}
238
+ if (targetArgument) {
239
+ throw new Error(`No tsrx-typegen.json defines target ${targetArgument}.`)
240
+ }
241
+
215
242
  return {
216
243
  project: projectArgument,
217
244
  target: null,
@@ -235,7 +262,10 @@ function configOptions(projectRoot, projectArgument, targetArgument) {
235
262
  const resolveProject = (value) => resolve(projectRoot, value)
236
263
  let selectedName = targetArgument
237
264
  let selected = selectedName ? targets[selectedName] : undefined
238
- if (selectedName && !selected) {throw new Error(`${path}: unknown target ${selectedName}`)}
265
+ if (selectedName && !selected) {
266
+ throw new Error(`${path}: unknown target ${selectedName}`)
267
+ }
268
+
239
269
  if (!selected && projectArgument) {
240
270
  const matches = Object.entries(targets).filter(
241
271
  ([, target]) => target?.project && resolveProject(target.project) === projectArgument,
@@ -245,15 +275,18 @@ function configOptions(projectRoot, projectArgument, targetArgument) {
245
275
  throw new Error(`${path}: project matches multiple targets; pass --target <name>`)
246
276
  }
247
277
 
248
- if (matches.length === 1) {[selectedName, selected] = matches[0]}
249
- else if (Object.keys(targets).length) {
278
+ if (matches.length === 1) {
279
+ ;[selectedName, selected] = matches[0]
280
+ } else if (Object.keys(targets).length) {
250
281
  throw new Error(`${path}: ${projectArgument} is not declared by a target`)
251
282
  }
252
283
  }
253
284
 
254
285
  if (selected) {
255
286
  if (selected.emit === false) {
256
- throw new Error(`${path}: target ${selectedName} has emit: false — it configures --pack-check only`)
287
+ throw new Error(
288
+ `${path}: target ${selectedName} has emit: false — it configures --pack-check only`,
289
+ )
257
290
  }
258
291
 
259
292
  if (typeof selected.project !== 'string' || !selected.project) {
@@ -282,10 +315,12 @@ function configOptions(projectRoot, projectArgument, targetArgument) {
282
315
  !entry ||
283
316
  typeof entry !== 'object' ||
284
317
  typeof entry.source !== 'string' ||
285
- !stringsIn(entry.runtime).length ||
318
+ !stringsIn(entry.runtime).length ||
286
319
  !stringsIn(entry.types).length
287
320
  ) {
288
- throw new Error(`${path}: target ${selectedName} entrypoint ${subpath} needs source, runtime, and types`)
321
+ throw new Error(
322
+ `${path}: target ${selectedName} entrypoint ${subpath} needs source, runtime, and types`,
323
+ )
289
324
  }
290
325
 
291
326
  for (const publishedPath of [...stringsIn(entry.runtime), ...stringsIn(entry.types)]) {
@@ -304,7 +339,9 @@ function configOptions(projectRoot, projectArgument, targetArgument) {
304
339
  } catch {}
305
340
 
306
341
  if (!pathInside(projectRoot, source) || !isFile) {
307
- throw new Error(`${path}: target ${selectedName} entrypoint source is missing: ${entry.source}`)
342
+ throw new Error(
343
+ `${path}: target ${selectedName} entrypoint source is missing: ${entry.source}`,
344
+ )
308
345
  }
309
346
  }
310
347
 
@@ -325,10 +362,17 @@ function configOptions(projectRoot, projectArgument, targetArgument) {
325
362
  }
326
363
 
327
364
  const project = projectArgument ?? resolveProject(selected.project)
328
- const sourceExtensions = { ...defaultExtensions, ...config.sourceExtensions, ...selected?.sourceExtensions }
365
+ const sourceExtensions = {
366
+ ...defaultExtensions,
367
+ ...config.sourceExtensions,
368
+ ...selected?.sourceExtensions,
369
+ }
370
+
329
371
  for (const [source, runtime] of Object.entries(sourceExtensions)) {
330
372
  if (!source.startsWith('.') || typeof runtime !== 'string' || !runtime.startsWith('.')) {
331
- throw new Error(`${path}: sourceExtensions must map dot-prefixed extensions to dot-prefixed runtime extensions`)
373
+ throw new Error(
374
+ `${path}: sourceExtensions must map dot-prefixed extensions to dot-prefixed runtime extensions`,
375
+ )
332
376
  }
333
377
  }
334
378
 
@@ -378,7 +422,10 @@ function applyOverrides(ts, projectRoot, outputDir, generated, overrides) {
378
422
  normalized.set(outputFile, content)
379
423
  }
380
424
 
381
- for (const [outputPath, content] of normalized) {generated.set(outputPath, content)}
425
+ for (const [outputPath, content] of normalized) {
426
+ generated.set(outputPath, content)
427
+ }
428
+
382
429
  return new Set(normalized.keys())
383
430
  }
384
431
 
@@ -390,8 +437,11 @@ function collectFiles(root, output = []) {
390
437
 
391
438
  for (const entry of readdirSync(root, { withFileTypes: true })) {
392
439
  const path = join(root, entry.name)
393
- if (entry.isDirectory()) {collectFiles(path, output)}
394
- else if (entry.isFile()) {output.push(path)}
440
+ if (entry.isDirectory()) {
441
+ collectFiles(path, output)
442
+ } else if (entry.isFile()) {
443
+ output.push(path)
444
+ }
395
445
  }
396
446
 
397
447
  return output
@@ -428,7 +478,10 @@ function moduleSpecifierNodes(ts, sourceFile) {
428
478
 
429
479
  function assertNoTsrxModuleReferences(ts, path, text) {
430
480
  const source = ts.createSourceFile(path, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
431
- const unresolved = moduleSpecifierNodes(ts, source).find((node) => /\.tsrx(?:$|[?#])/.test(node.text))
481
+ const unresolved = moduleSpecifierNodes(ts, source).find((node) =>
482
+ /\.tsrx(?:$|[?#])/.test(node.text),
483
+ )
484
+
432
485
  if (unresolved) {
433
486
  throw new Error(`${path}: unresolved .tsrx module specifier ${unresolved.text}`)
434
487
  }
@@ -440,8 +493,14 @@ function rewriteDeclaration(ts, path, outputDir, extensions, generatedFiles, run
440
493
  const edits = []
441
494
  for (const node of moduleSpecifierNodes(ts, source)) {
442
495
  const specifier = node.text
443
- if (!specifier.startsWith('.')) {continue}
444
- if (/\.d\.[cm]?ts$/i.test(specifier)) {continue}
496
+ if (!specifier.startsWith('.')) {
497
+ continue
498
+ }
499
+
500
+ if (/\.d\.[cm]?ts$/i.test(specifier)) {
501
+ continue
502
+ }
503
+
445
504
  const extension = Object.keys(extensions).find(
446
505
  (candidate) => !candidate.startsWith('.d.') && specifier.endsWith(candidate),
447
506
  )
@@ -464,7 +523,10 @@ function rewriteDeclaration(ts, path, outputDir, extensions, generatedFiles, run
464
523
  generatedFiles.has(relative(outputDir, candidate)),
465
524
  )
466
525
 
467
- if (!target) {continue}
526
+ if (!target) {
527
+ continue
528
+ }
529
+
468
530
  const declaration = relative(outputDir, target)
469
531
  const runtimeExtension = runtimeByDeclaration.get(declaration) ?? '.js'
470
532
  const isIndex = /[\\/]index\.d\.[cm]?ts$/.test(target)
@@ -498,19 +560,15 @@ function normalizeOutput(ts, outputDir, extensions, runtimeByDeclaration) {
498
560
  .filter((path) => /\.d\.[cm]?ts$/.test(path))
499
561
  .sort()
500
562
 
501
- if (!files.length) {throw new Error('TypeScript emitted no declaration files.')}
563
+ if (!files.length) {
564
+ throw new Error('TypeScript emitted no declaration files.')
565
+ }
566
+
502
567
  const generatedFiles = new Set(files.map((path) => relative(outputDir, path)))
503
568
  return new Map(
504
569
  files.map((path) => [
505
570
  relative(outputDir, path),
506
- rewriteDeclaration(
507
- ts,
508
- path,
509
- outputDir,
510
- extensions,
511
- generatedFiles,
512
- runtimeByDeclaration,
513
- ),
571
+ rewriteDeclaration(ts, path, outputDir, extensions, generatedFiles, runtimeByDeclaration),
514
572
  ]),
515
573
  )
516
574
  }
@@ -532,7 +590,10 @@ function previousManifest(outputDir) {
532
590
 
533
591
  return new Set(manifest.files)
534
592
  } catch (error) {
535
- if (error.code === 'ENOENT') {return null}
593
+ if (error.code === 'ENOENT') {
594
+ return null
595
+ }
596
+
536
597
  throw new Error(`${path}: ${error.message}`)
537
598
  }
538
599
  }
@@ -547,21 +608,54 @@ function pathInside(root, file) {
547
608
  )
548
609
  }
549
610
 
611
+ function existingOutputPath(outputDir, file) {
612
+ let current = outputDir
613
+ const actualParts = []
614
+
615
+ for (const part of normalize(file).split(sep)) {
616
+ let entries
617
+ try {
618
+ entries = readdirSync(current)
619
+ } catch (error) {
620
+ if (error.code === 'ENOENT') {
621
+ return null
622
+ }
623
+
624
+ throw error
625
+ }
626
+
627
+ const actual = entries.includes(part)
628
+ ? part
629
+ : entries.find((entry) => entry.toLowerCase() === part.toLowerCase())
630
+
631
+ if (!actual) {
632
+ return null
633
+ }
634
+
635
+ actualParts.push(actual)
636
+ current = join(current, actual)
637
+ }
638
+
639
+ return actualParts.join(sep)
640
+ }
641
+
550
642
  function writeOutput(outputDir, generated, oldFiles, overrideFiles) {
551
643
  for (const file of generated.keys()) {
552
- if (oldFiles?.has(file) || overrideFiles.has(file)) {continue}
553
- try {
554
- statSync(join(outputDir, file))
644
+ const existing = existingOutputPath(outputDir, file)
645
+ if (existing && !oldFiles?.has(existing) && !overrideFiles.has(existing)) {
555
646
  throw new Error(`refusing to overwrite unmanaged declaration: ${file}`)
556
- } catch (error) {
557
- if (error.code !== 'ENOENT') {throw error}
558
647
  }
559
648
  }
560
649
 
561
650
  for (const file of oldFiles ?? []) {
562
651
  const path = resolve(outputDir, file)
563
- if (!pathInside(outputDir, path)) {throw new Error(`Invalid generated path in manifest: ${file}`)}
564
- if (!generated.has(file)) {rmSync(path, { force: true })}
652
+ if (!pathInside(outputDir, path)) {
653
+ throw new Error(`Invalid generated path in manifest: ${file}`)
654
+ }
655
+
656
+ if (!generated.has(file)) {
657
+ rmSync(path, { force: true })
658
+ }
565
659
  }
566
660
 
567
661
  for (const [file, content] of generated) {
@@ -579,40 +673,57 @@ function writeOutput(outputDir, generated, oldFiles, overrideFiles) {
579
673
 
580
674
  function compareOutput(outputDir, generated, oldFiles, overrideFiles) {
581
675
  const errors = []
582
- if (!oldFiles) {errors.push(`missing ${manifestName}; run tsrx-typegen --project <tsconfig> first`)}
676
+ if (!oldFiles) {
677
+ errors.push(`missing ${manifestName}; run tsrx-typegen --project <tsconfig> first`)
678
+ }
679
+
583
680
  for (const file of generated.keys()) {
584
681
  if (!oldFiles?.has(file) && !overrideFiles.has(file)) {
585
682
  try {
586
683
  statSync(join(outputDir, file))
587
684
  errors.push(`${file} conflicts with unmanaged output`)
588
685
  } catch (error) {
589
- if (error.code !== 'ENOENT') {throw error}
686
+ if (error.code !== 'ENOENT') {
687
+ throw error
688
+ }
590
689
  }
591
690
  }
592
691
 
593
692
  try {
594
- if (readFileSync(join(outputDir, file), 'utf8') !== generated.get(file))
595
- {errors.push(`${file} is stale`)}
693
+ if (readFileSync(join(outputDir, file), 'utf8') !== generated.get(file)) {
694
+ errors.push(`${file} is stale`)
695
+ }
596
696
  } catch {
597
697
  errors.push(`${file} is missing`)
598
698
  }
599
699
  }
600
700
 
601
701
  for (const file of oldFiles ?? []) {
602
- if (!generated.has(file)) {errors.push(`${file} is stale`)}
702
+ if (!generated.has(file)) {
703
+ errors.push(`${file} is stale`)
704
+ }
603
705
  }
604
706
 
605
707
  return errors
606
708
  }
607
709
 
608
710
  function stringsIn(value) {
609
- if (typeof value === 'string') {return [value]}
610
- if (!value || typeof value !== 'object') {return []}
711
+ if (typeof value === 'string') {
712
+ return [value]
713
+ }
714
+
715
+ if (!value || typeof value !== 'object') {
716
+ return []
717
+ }
718
+
611
719
  return Object.values(value).flatMap(stringsIn)
612
720
  }
613
721
 
614
722
  function typeTargets(value) {
615
- if (!value || typeof value !== 'object' || Array.isArray(value)) {return []}
723
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
724
+ return []
725
+ }
726
+
616
727
  return [
617
728
  ...(Object.hasOwn(value, 'types') ? stringsIn(value.types) : []),
618
729
  ...Object.entries(value)
@@ -647,13 +758,20 @@ function verifyPublicTypes(projectRoot, generated, outDir, target, sourceOutputs
647
758
  for (const [subpath, entry] of Object.entries(target.entrypoints)) {
648
759
  const mapping = exportsMap[subpath]
649
760
  if (!mapping) {
650
- failures.push(`${subpath} is configured for ${target.name} but missing from effective package exports`)
761
+ failures.push(
762
+ `${subpath} is configured for ${target.name} but missing from effective package exports`,
763
+ )
764
+
651
765
  continue
652
766
  }
653
767
 
654
768
  const source = resolve(projectRoot, entry.source)
655
- if (![...sourceOutputs.values()].includes(source)) {
656
- failures.push(`${subpath} source is not included by its TypeScript project: ${entry.source}`)
769
+ // TypeScript emits forward-slash paths even on Windows; resolve both
770
+ // sides before comparing them with the platform-native entry path.
771
+ if (![...sourceOutputs.values()].some((included) => resolve(included) === source)) {
772
+ failures.push(
773
+ `${subpath} source is not included by its TypeScript project: ${entry.source}`,
774
+ )
657
775
  }
658
776
 
659
777
  const declaredRuntime = stringsIn(entry.runtime)
@@ -680,7 +798,10 @@ function verifyPublicTypes(projectRoot, generated, outDir, target, sourceOutputs
680
798
  /\.(?:[cm]?[jt]sx?|tsrx)$/.test(target),
681
799
  )
682
800
 
683
- if (!runtimeTargets.length) {continue}
801
+ if (!runtimeTargets.length) {
802
+ continue
803
+ }
804
+
684
805
  const targets = typeTargets(mapping)
685
806
  if (!targets.length) {
686
807
  failures.push(`${subpath} has code exports but no types condition`)
@@ -688,7 +809,10 @@ function verifyPublicTypes(projectRoot, generated, outDir, target, sourceOutputs
688
809
  }
689
810
 
690
811
  for (const target of targets) {
691
- if (!target.startsWith('./') || target.includes('*')) {continue}
812
+ if (!target.startsWith('./') || target.includes('*')) {
813
+ continue
814
+ }
815
+
692
816
  const absolute = resolve(projectRoot, target)
693
817
  if (!pathInside(projectRoot, absolute)) {
694
818
  failures.push(`${subpath} types target escapes the package: ${target}`)
@@ -696,10 +820,14 @@ function verifyPublicTypes(projectRoot, generated, outDir, target, sourceOutputs
696
820
  }
697
821
 
698
822
  const generatedPath = relative(outDir, absolute)
699
- if (pathInside(outDir, absolute) && generated.has(generatedPath)) {continue}
823
+ if (pathInside(outDir, absolute) && generated.has(generatedPath)) {
824
+ continue
825
+ }
826
+
700
827
  try {
701
- if (!statSync(absolute).isFile())
702
- {failures.push(`${subpath} types target is not a file: ${target}`)}
828
+ if (!statSync(absolute).isFile()) {
829
+ failures.push(`${subpath} types target is not a file: ${target}`)
830
+ }
703
831
  } catch {
704
832
  failures.push(`${subpath} types target is missing: ${target}`)
705
833
  }
@@ -712,14 +840,18 @@ function verifyPublicTypes(projectRoot, generated, outDir, target, sourceOutputs
712
840
  failures.push(`package types target escapes the package: ${targetPath}`)
713
841
  } else if (!(pathInside(outDir, target) && generated.has(relative(outDir, target)))) {
714
842
  try {
715
- if (!statSync(target).isFile()) {failures.push(`package types target is not a file: ${targetPath}`)}
843
+ if (!statSync(target).isFile()) {
844
+ failures.push(`package types target is not a file: ${targetPath}`)
845
+ }
716
846
  } catch {
717
847
  failures.push(`package types target is missing: ${targetPath}`)
718
848
  }
719
849
  }
720
850
  }
721
851
 
722
- if (failures.length) {throw new Error(failures.join('\n'))}
852
+ if (failures.length) {
853
+ throw new Error(failures.join('\n'))
854
+ }
723
855
  }
724
856
 
725
857
  function runTarget(projectRoot, args) {
@@ -751,16 +883,34 @@ function runTarget(projectRoot, args) {
751
883
  const temporaryOut = join(temporaryRoot, 'types')
752
884
  const outputDir = temporaryOut
753
885
  try {
754
- const compilerArgs = [compilerPath, '--pretty', 'false', '--project', project, outputFlag, temporaryOut]
886
+ const compilerArgs = [
887
+ compilerPath,
888
+ '--pretty',
889
+ 'false',
890
+ '--project',
891
+ project,
892
+ outputFlag,
893
+ temporaryOut,
894
+ ]
895
+
755
896
  const result = spawnSync(process.execPath, compilerArgs, {
756
897
  cwd: projectRoot,
757
898
  encoding: 'utf8',
758
899
  maxBuffer: 32 * 1024 * 1024,
759
900
  })
760
901
 
761
- if (result.stdout) {process.stdout.write(result.stdout)}
762
- if (result.stderr) {process.stderr.write(result.stderr)}
763
- if (result.error) {throw result.error}
902
+ if (result.stdout) {
903
+ process.stdout.write(result.stdout)
904
+ }
905
+
906
+ if (result.stderr) {
907
+ process.stderr.write(result.stderr)
908
+ }
909
+
910
+ if (result.error) {
911
+ throw result.error
912
+ }
913
+
764
914
  if (/\[tsrx-tsc\]/.test(result.stderr ?? '')) {
765
915
  throw new Error(
766
916
  'tsrx compiler reported transform diagnostics; declarations were not written. Fix the source or configure an explicit declaration override.',
@@ -783,7 +933,10 @@ function runTarget(projectRoot, args) {
783
933
  if (args.check) {
784
934
  const errors = compareOutput(outDir, generated, oldFiles, managedOverrides)
785
935
  if (errors.length) {
786
- for (const error of errors) {console.error(`tsrx-typegen: ${error}`)}
936
+ for (const error of errors) {
937
+ console.error(`tsrx-typegen: ${error}`)
938
+ }
939
+
787
940
  return false
788
941
  }
789
942
 
@@ -805,7 +958,10 @@ function run() {
805
958
  const args = parseArgs(process.argv.slice(2))
806
959
  const projectRoot = process.cwd()
807
960
  if (!args.packCheck) {
808
- if (!runTarget(projectRoot, args)) {process.exitCode = 1}
961
+ if (!runTarget(projectRoot, args)) {
962
+ process.exitCode = 1
963
+ }
964
+
809
965
  return
810
966
  }
811
967
 
@@ -262,7 +262,11 @@ function staticRuntimeExports(ts, file, seen = new Set()) {
262
262
  }
263
263
 
264
264
  if (ts.isExportDeclaration(statement) && !statement.isTypeOnly) {
265
- if (!statement.exportClause && statement.moduleSpecifier && ts.isStringLiteralLike(statement.moduleSpecifier)) {
265
+ if (
266
+ !statement.exportClause &&
267
+ statement.moduleSpecifier &&
268
+ ts.isStringLiteralLike(statement.moduleSpecifier)
269
+ ) {
266
270
  const target = localSourceModule(absolute, statement.moduleSpecifier.text)
267
271
  if (target) {
268
272
  for (const name of staticRuntimeExports(ts, target, seen)) {
@@ -350,8 +354,7 @@ function compilerExports(ts, program, file, cache = new Map(), pending = new Set
350
354
  ts.sys,
351
355
  ).resolvedModule?.resolvedFileName
352
356
 
353
- const targetExports =
354
- target && compilerExports(ts, program, target, cache, pending)
357
+ const targetExports = target && compilerExports(ts, program, target, cache, pending)
355
358
 
356
359
  pending.delete(absolute)
357
360
  for (const name of targetExports?.all ?? []) {
@@ -596,8 +599,6 @@ export function checkPackedPackage(projectRoot, ts) {
596
599
  mkdirSync(packOutput)
597
600
  mkdirSync(extractedRoot)
598
601
  try {
599
- verifyPackedPackage(ts, projectRoot)
600
- console.log('tsrx-typegen: workspace exports and declaration graph are valid')
601
602
  const packed = spawnSync(
602
603
  'pnpm',
603
604
  ['--config.ignore-scripts=true', 'pack', '--pack-destination', packOutput],
@@ -651,7 +652,7 @@ export function checkPackedPackage(projectRoot, ts) {
651
652
  }
652
653
 
653
654
  verifyPackedPackage(ts, join(extractedRoot, 'package'))
654
- console.log('tsrx-typegen: packed runtime exports and declaration graph are valid')
655
+ console.log('tsrx-typegen: packed exports and declaration graph are valid')
655
656
  } finally {
656
657
  rmSync(temporary, { recursive: true, force: true })
657
658
  }