tsrx-typegen 0.2.2 → 0.3.1

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.
@@ -4,7 +4,7 @@ import { tmpdir } from 'node:os'
4
4
  import { dirname, extname, isAbsolute, join, relative, resolve, sep } from 'node:path'
5
5
 
6
6
  const moduleSpecifierNodes = (ts, sourceFile) => {
7
- const nodes = []
7
+ const nodes: any[] = []
8
8
  const visit = (node) => {
9
9
  if (
10
10
  (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) &&
@@ -50,15 +50,19 @@ const stringsIn = (value) => {
50
50
  }
51
51
 
52
52
  const codeTarget = (target) =>
53
- /\.(?:[cm]?[jt]sx?|tsrx)$/.test(target) && !/\.d\.[cm]?ts$/.test(target)
53
+ (!extname(target) || /\.(?:[cm]?[jt]sx?|tsrx)$/.test(target)) && !/\.d\.[cm]?ts$/.test(target)
54
54
 
55
- function exportBranches(exportsMap, fallbackTypes = []) {
56
- const branches = []
55
+ function exportBranches(exportsMap, fallbackTypes: any[] = []) {
56
+ const branches: any[] = []
57
57
  for (const [subpath, mapping] of Object.entries(exportsMap ?? {})) {
58
58
  const visit = (value, inheritedTypes) => {
59
59
  if (typeof value === 'string') {
60
60
  if (codeTarget(value)) {
61
- branches.push({ subpath, runtime: value, types: inheritedTypes })
61
+ branches.push({
62
+ subpath,
63
+ runtime: value,
64
+ types: inheritedTypes.length ? inheritedTypes : fallbackTypes,
65
+ })
62
66
  }
63
67
 
64
68
  return
@@ -68,22 +72,27 @@ function exportBranches(exportsMap, fallbackTypes = []) {
68
72
  return
69
73
  }
70
74
 
71
- const types = Object.hasOwn(value, 'types') ? stringsIn(value.types) : inheritedTypes
75
+ // An earlier condition cannot use a later sibling's type fallback.
76
+ let types = inheritedTypes
72
77
  for (const [condition, child] of Object.entries(value)) {
73
- if (condition !== 'types') {
78
+ if (condition === 'types') {
79
+ if (!types.length) {
80
+ types = stringsIn(child)
81
+ }
82
+ } else {
74
83
  visit(child, types)
75
84
  }
76
85
  }
77
86
  }
78
87
 
79
- visit(mapping, fallbackTypes)
88
+ visit(mapping, [])
80
89
  }
81
90
 
82
91
  return branches
83
92
  }
84
93
 
85
94
  function packageFiles(root) {
86
- const files = []
95
+ const files: any[] = []
87
96
  const visit = (dir) => {
88
97
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
89
98
  if (entry.name === 'node_modules') {
@@ -104,11 +113,12 @@ function packageFiles(root) {
104
113
  }
105
114
 
106
115
  function expandTarget(target, files, packageRoot) {
107
- if (!target.includes('*')) {
116
+ const extensionless = !extname(target)
117
+ if (!target.includes('*') && !extensionless) {
108
118
  return [{ path: target, capture: undefined }]
109
119
  }
110
120
 
111
- if ((target.match(/\*/g) ?? []).length !== 1) {
121
+ if ((target.match(/\*/g) ?? []).length > 1) {
112
122
  throw new Error(`packed export target uses more than one wildcard: ${target}`)
113
123
  }
114
124
 
@@ -118,19 +128,31 @@ function expandTarget(target, files, packageRoot) {
118
128
 
119
129
  const pattern = target.slice(2)
120
130
  const marker = pattern.indexOf('*')
121
- const prefix = pattern.slice(0, marker)
122
- const suffix = pattern.slice(marker + 1)
131
+ const prefix = marker === -1 ? pattern : pattern.slice(0, marker)
132
+ const suffix = marker === -1 ? '' : pattern.slice(marker + 1)
123
133
  const matches = files.flatMap((file) => {
124
134
  const path = relative(packageRoot, file).replaceAll('\\', '/')
135
+ // Extensionless source exports let the app choose platform siblings.
136
+ // Their wildcard captures are module stems, not source extensions.
137
+ if (!codeTarget(path) || (extensionless && !extname(path))) {
138
+ return []
139
+ }
140
+
141
+ const matchedPath = extensionless ? path.slice(0, -extname(path).length) : path
125
142
  if (
126
- !path.startsWith(prefix) ||
127
- !path.endsWith(suffix) ||
128
- path.length < prefix.length + suffix.length
143
+ !matchedPath.startsWith(prefix) ||
144
+ !matchedPath.endsWith(suffix) ||
145
+ matchedPath.length < prefix.length + suffix.length ||
146
+ (marker === -1 && matchedPath !== pattern)
129
147
  ) {
130
148
  return []
131
149
  }
132
150
 
133
- const capture = path.slice(prefix.length, path.length - suffix.length)
151
+ const capture =
152
+ marker === -1
153
+ ? undefined
154
+ : matchedPath.slice(prefix.length, matchedPath.length - suffix.length)
155
+
134
156
  return [{ path: `./${path}`, capture }]
135
157
  })
136
158
 
@@ -163,13 +185,11 @@ function packedPath(packageRoot, target) {
163
185
  function sourceFile(ts, file) {
164
186
  const extension = extname(file).toLowerCase()
165
187
  const scriptKind =
166
- extension === '.tsx'
188
+ extension === '.tsx' || extension === '.tsrx'
167
189
  ? ts.ScriptKind.TSX
168
190
  : extension === '.jsx'
169
191
  ? ts.ScriptKind.JSX
170
- : extension === '.tsrx'
171
- ? ts.ScriptKind.Deferred
172
- : ts.ScriptKind.TS
192
+ : ts.ScriptKind.TS
173
193
 
174
194
  return ts.createSourceFile(
175
195
  file,
@@ -212,7 +232,7 @@ function localSourceModule(file, specifier) {
212
232
  return null
213
233
  }
214
234
 
215
- function staticRuntimeExports(ts, file, seen = new Set()) {
235
+ function staticRuntimeExports(ts, file, seen: Set<any> = new Set()) {
216
236
  const absolute = resolve(file)
217
237
  if (seen.has(absolute)) {
218
238
  return new Set()
@@ -221,7 +241,7 @@ function staticRuntimeExports(ts, file, seen = new Set()) {
221
241
  seen.add(absolute)
222
242
 
223
243
  const source = sourceFile(ts, absolute)
224
- const names = new Set()
244
+ const names = new Set<any>()
225
245
  const addBindingName = (name) => {
226
246
  if (ts.isIdentifier(name)) {
227
247
  names.add(name.text)
@@ -294,7 +314,13 @@ function staticRuntimeExports(ts, file, seen = new Set()) {
294
314
  return names
295
315
  }
296
316
 
297
- function compilerExports(ts, program, file, cache = new Map(), pending = new Set()) {
317
+ function compilerExports(
318
+ ts,
319
+ program,
320
+ file,
321
+ cache: Map<any, any> = new Map(),
322
+ pending: Set<any> = new Set(),
323
+ ) {
298
324
  const absolute = resolve(file)
299
325
  if (cache.has(absolute)) {
300
326
  return cache.get(absolute)
@@ -315,8 +341,8 @@ function compilerExports(ts, program, file, cache = new Map(), pending = new Set
315
341
  return undefined
316
342
  }
317
343
 
318
- const all = new Set()
319
- const values = new Set()
344
+ const all = new Set<any>()
345
+ const values = new Set<any>()
320
346
  for (const symbol of checker.getExportsOfModule(module)) {
321
347
  all.add(symbol.name)
322
348
  let resolved = symbol
@@ -335,9 +361,23 @@ function compilerExports(ts, program, file, cache = new Map(), pending = new Set
335
361
  // target's symbols with their original flags — an ambient `declare const`
336
362
  // still reads as a value even though no value binding crosses the edge.
337
363
  // Subtract names whose only export edges are type-only.
338
- const typeOnly = new Set()
364
+ const typeOnly = new Set<any>()
339
365
  for (const statement of source.statements) {
340
- if (!ts.isExportDeclaration(statement) || !statement.isTypeOnly) {
366
+ if (!ts.isExportDeclaration(statement)) {
367
+ continue
368
+ }
369
+
370
+ if (statement.exportClause && ts.isNamedExports(statement.exportClause)) {
371
+ for (const element of statement.exportClause.elements) {
372
+ if (statement.isTypeOnly || element.isTypeOnly) {
373
+ typeOnly.add(element.name.text)
374
+ }
375
+ }
376
+
377
+ continue
378
+ }
379
+
380
+ if (!statement.isTypeOnly) {
341
381
  continue
342
382
  }
343
383
 
@@ -392,6 +432,20 @@ function moduleResolutionOptions(ts) {
392
432
  allowImportingTsExtensions: true,
393
433
  module: ts.ModuleKind.NodeNext,
394
434
  moduleResolution: ts.ModuleResolutionKind.NodeNext,
435
+ // Same precedence as repository typecheck/typegen programs
436
+ // (packages/cli tsconfig-envs.mjs PLATFORM_MODULE_SUFFIXES):
437
+ // extensionless first, then every platform suffix.
438
+ moduleSuffixes: [
439
+ '',
440
+ '.web',
441
+ '.ios',
442
+ '.android',
443
+ '.mobile',
444
+ '.native',
445
+ '.macos',
446
+ '.linux',
447
+ '.windows',
448
+ ],
395
449
  noEmit: true,
396
450
  skipLibCheck: true,
397
451
  target: ts.ScriptTarget.Latest,
@@ -421,6 +475,10 @@ function verifyDeclarationClosure(ts, packageRoot, files, manifest, failures) {
421
475
  )
422
476
  } else if (!pathInside(packageRoot, resolve(resolved.resolvedFileName))) {
423
477
  failures.push(`${relative(packageRoot, file)} resolves ${specifier} outside the package`)
478
+ } else if (!/\.d\.[cm]?ts$/.test(resolved.resolvedFileName)) {
479
+ failures.push(
480
+ `${relative(packageRoot, file)} resolves ${specifier} to implementation source; publish its declaration`,
481
+ )
424
482
  }
425
483
 
426
484
  continue
@@ -465,7 +523,7 @@ function verifyExportMap(ts, packageRoot, files, manifest, exportsMap, failures,
465
523
  return
466
524
  }
467
525
 
468
- const pairs = []
526
+ const pairs: any[] = []
469
527
  for (const branch of branches) {
470
528
  if (!branch.types.length) {
471
529
  failures.push(`${branch.subpath} runtime ${branch.runtime} has no types condition`)
@@ -546,7 +604,7 @@ function verifyExportMap(ts, packageRoot, files, manifest, exportsMap, failures,
546
604
  }
547
605
 
548
606
  export function verifyPackedPackage(ts, packageRoot) {
549
- const failures = []
607
+ const failures: any[] = []
550
608
  const manifestPath = join(packageRoot, 'package.json')
551
609
  let manifest
552
610
  try {