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 +75 -11
- package/package.json +8 -8
- package/src/cli.mjs +222 -66
- package/src/pack-check.mjs +7 -6
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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Generate consumer-ready TypeScript declarations for TSRX libraries",
|
|
5
5
|
"keywords": [
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"typescript",
|
|
6
|
+
"build-tool",
|
|
7
|
+
"codegen",
|
|
9
8
|
"declarations",
|
|
10
9
|
"dts",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
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": "
|
|
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 (
|
|
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) {
|
|
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) {
|
|
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
|
|
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(
|
|
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) {
|
|
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)) {
|
|
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(
|
|
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) {
|
|
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) {
|
|
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) {
|
|
249
|
-
|
|
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(
|
|
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
|
-
|
|
318
|
+
!stringsIn(entry.runtime).length ||
|
|
286
319
|
!stringsIn(entry.types).length
|
|
287
320
|
) {
|
|
288
|
-
throw new Error(
|
|
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(
|
|
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 = {
|
|
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(
|
|
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) {
|
|
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()) {
|
|
394
|
-
|
|
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) =>
|
|
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('.')) {
|
|
444
|
-
|
|
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) {
|
|
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) {
|
|
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') {
|
|
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
|
-
|
|
553
|
-
|
|
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)) {
|
|
564
|
-
|
|
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) {
|
|
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') {
|
|
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
|
-
|
|
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)) {
|
|
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') {
|
|
610
|
-
|
|
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)) {
|
|
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(
|
|
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
|
-
|
|
656
|
-
|
|
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) {
|
|
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('*')) {
|
|
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)) {
|
|
823
|
+
if (pathInside(outDir, absolute) && generated.has(generatedPath)) {
|
|
824
|
+
continue
|
|
825
|
+
}
|
|
826
|
+
|
|
700
827
|
try {
|
|
701
|
-
if (!statSync(absolute).isFile())
|
|
702
|
-
|
|
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()) {
|
|
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) {
|
|
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 = [
|
|
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) {
|
|
762
|
-
|
|
763
|
-
|
|
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) {
|
|
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)) {
|
|
961
|
+
if (!runTarget(projectRoot, args)) {
|
|
962
|
+
process.exitCode = 1
|
|
963
|
+
}
|
|
964
|
+
|
|
809
965
|
return
|
|
810
966
|
}
|
|
811
967
|
|
package/src/pack-check.mjs
CHANGED
|
@@ -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 (
|
|
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
|
|
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
|
}
|