@esportsplus/typescript 0.29.6 → 0.31.0

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.
Files changed (70) hide show
  1. package/README.md +11 -3
  2. package/bin/tsc-alias +1 -1
  3. package/build/cli/diagnostics.d.ts +4 -0
  4. package/build/cli/diagnostics.js +90 -0
  5. package/build/cli/tsc.d.ts +10 -2
  6. package/build/cli/tsc.js +352 -51
  7. package/build/compiler/ast.d.ts +7 -5
  8. package/build/compiler/ast.js +6 -6
  9. package/build/compiler/code.d.ts +5 -4
  10. package/build/compiler/code.js +12 -1
  11. package/build/compiler/coordinator.d.ts +17 -7
  12. package/build/compiler/coordinator.js +54 -31
  13. package/build/compiler/imports.d.ts +6 -3
  14. package/build/compiler/imports.js +26 -23
  15. package/build/compiler/index.d.ts +3 -0
  16. package/build/compiler/index.js +1 -0
  17. package/build/compiler/language-service.d.ts +24 -5
  18. package/build/compiler/language-service.js +215 -53
  19. package/build/compiler/plugins/index.d.ts +4 -2
  20. package/build/compiler/plugins/tsc.d.ts +1 -1
  21. package/build/compiler/plugins/vite.d.ts +6 -3
  22. package/build/compiler/plugins/vite.js +12 -7
  23. package/build/compiler/sourcemap.d.ts +50 -0
  24. package/build/compiler/sourcemap.js +247 -0
  25. package/build/compiler/types.d.ts +7 -6
  26. package/build/compiler/uid.d.ts +5 -2
  27. package/build/compiler/uid.js +33 -4
  28. package/build/index.d.ts +1 -1
  29. package/build/index.js +1 -1
  30. package/build/ts.d.ts +3 -0
  31. package/build/ts.js +3 -0
  32. package/package.json +22 -8
  33. package/tsconfig.dev.json +13 -0
  34. package/tsconfig.package.json +4 -1
  35. package/.claude/skills/code-audit/registry-typescript.json +0 -326
  36. package/.editorconfig +0 -9
  37. package/.gitattributes +0 -2
  38. package/.github/dependabot.yml +0 -25
  39. package/.github/workflows/bump.yml +0 -27
  40. package/.github/workflows/dependabot.yml +0 -58
  41. package/.github/workflows/publish.yml +0 -42
  42. package/.github/workflows/templates/bump.yml +0 -9
  43. package/.github/workflows/templates/dependabot.yml +0 -12
  44. package/.github/workflows/templates/publish.yml +0 -16
  45. package/pnpm-workspace.yaml +0 -6
  46. package/src/cli/tsc.ts +0 -235
  47. package/src/compiler/ast.ts +0 -60
  48. package/src/compiler/code.ts +0 -27
  49. package/src/compiler/coordinator.ts +0 -284
  50. package/src/compiler/imports.ts +0 -185
  51. package/src/compiler/index.ts +0 -7
  52. package/src/compiler/language-service.ts +0 -121
  53. package/src/compiler/plugins/index.ts +0 -5
  54. package/src/compiler/plugins/tsc.ts +0 -6
  55. package/src/compiler/plugins/vite.ts +0 -91
  56. package/src/compiler/types.ts +0 -83
  57. package/src/compiler/uid.ts +0 -10
  58. package/src/constants.ts +0 -4
  59. package/src/index.ts +0 -1
  60. package/tests/cli/tsc.test.ts +0 -155
  61. package/tests/compiler/ast.test.ts +0 -131
  62. package/tests/compiler/code.test.ts +0 -73
  63. package/tests/compiler/coordinator.bench.ts +0 -101
  64. package/tests/compiler/coordinator.test.ts +0 -872
  65. package/tests/compiler/imports.test.ts +0 -167
  66. package/tests/compiler/language-service.test.ts +0 -90
  67. package/tests/compiler/plugins.test.ts +0 -172
  68. package/tests/compiler/uid.test.ts +0 -70
  69. package/tsconfig.json +0 -3
  70. package/vitest.config.ts +0 -14
package/README.md CHANGED
@@ -72,13 +72,13 @@ export default defineConfig({
72
72
  tsc
73
73
  ```
74
74
 
75
- The CLI detects plugins in `tsconfig.json` `compilerOptions.plugins`, loads them, runs coordinated compilation, and automatically calls `tsc-alias` afterward.
75
+ The CLI detects plugins in `tsconfig.json` `compilerOptions.plugins`, loads them, runs coordinated compilation, and automatically calls `tsc-alias` afterward. The package installs `tsc`/`tsc-alias` bins (also under the unambiguous `esportsplus-tsc`/`esportsplus-tsc-alias` names).
76
76
 
77
77
  ## API
78
78
 
79
79
  ### `@esportsplus/typescript`
80
80
 
81
- Re-exports the TypeScript compiler API (`ts`).
81
+ No exports. Import the TypeScript compiler API directly from `typescript/unstable/*` (see [TypeScript 7 migration](#typescript-7-migration)).
82
82
 
83
83
  ### `@esportsplus/typescript/compiler`
84
84
 
@@ -90,7 +90,6 @@ Re-exports the TypeScript compiler API (`ts`).
90
90
  | `imports` | Import detection and modification (WeakMap cached) |
91
91
  | `plugin` | Built-in plugins (`tsc`, `vite`) |
92
92
  | `uid` | Unique identifier generation |
93
- | `languageService` | Cached TypeScript language service |
94
93
 
95
94
  ### Types
96
95
 
@@ -137,6 +136,15 @@ Importable base tsconfig files:
137
136
  { "extends": "@esportsplus/typescript/tsconfig.package.json" }
138
137
  ```
139
138
 
139
+ ## TypeScript 7 migration
140
+
141
+ This package targets `typescript@^7`, shipped as a regular `dependencies` entry. Breaking changes for consumers:
142
+
143
+ - **Root export removed.** `@esportsplus/typescript` no longer re-exports `ts`. Import the compiler API directly from the `typescript/unstable/*` subpaths you need — `typescript/unstable/ast`, `typescript/unstable/ast/is`, `typescript/unstable/sync`, `typescript/unstable/fs`, etc.
144
+ - **Plugin-facing types.** `TransformContext` and `ReplacementIntent` now carry `typescript/unstable/ast` + `typescript/unstable/sync` identities instead of the classic `ts` namespace types.
145
+ - **`coordinator.transform` signature.** Takes the `{ checker, program }` project pair instead of a single `ts.Program`.
146
+ - **`imports.includes` signature.** Takes the API `Checker` type, and symbol declarations are represented as NodeHandles rather than `ts.Node`.
147
+
140
148
  ## Scripts
141
149
 
142
150
  ```bash
package/bin/tsc-alias CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- (await import ('@esportsplus/cli-passthrough')).default('tsc-alias');
3
+ (await import ('@esportsplus/cli-passthrough')).default('tsc-alias');
@@ -0,0 +1,4 @@
1
+ import { type Diagnostic } from 'typescript/unstable/sync';
2
+ declare const flatten: (diagnostic: Diagnostic, indent?: number) => string;
3
+ declare const format: (diagnostics: readonly Diagnostic[], root: string) => string;
4
+ export { flatten, format };
@@ -0,0 +1,90 @@
1
+ import { readFileSync } from 'fs';
2
+ import { computeLineStarts } from 'typescript/unstable/ast/scanner';
3
+ import { DiagnosticCategory } from 'typescript/unstable/sync';
4
+ import path from 'path';
5
+ const ANSI_BLUE = '\x1b[94m';
6
+ const ANSI_CYAN = '\x1b[96m';
7
+ const ANSI_GREY = '\x1b[90m';
8
+ const ANSI_RED = '\x1b[91m';
9
+ const ANSI_RESET = '\x1b[0m';
10
+ const ANSI_YELLOW = '\x1b[93m';
11
+ const BACKSLASH_REGEX = /\\/g;
12
+ const TRAILING_NEWLINE_REGEX = /\r?\n$/;
13
+ function categoryColor(category) {
14
+ switch (category) {
15
+ case DiagnosticCategory.Error:
16
+ return ANSI_RED;
17
+ case DiagnosticCategory.Suggestion:
18
+ return ANSI_BLUE;
19
+ case DiagnosticCategory.Warning:
20
+ return ANSI_YELLOW;
21
+ default:
22
+ return ANSI_GREY;
23
+ }
24
+ }
25
+ function categoryLabel(category) {
26
+ switch (category) {
27
+ case DiagnosticCategory.Error:
28
+ return 'error';
29
+ case DiagnosticCategory.Suggestion:
30
+ return 'suggestion';
31
+ case DiagnosticCategory.Warning:
32
+ return 'warning';
33
+ default:
34
+ return 'message';
35
+ }
36
+ }
37
+ function formatOne(diagnostic, root) {
38
+ let category = categoryLabel(diagnostic.category), code = `${ANSI_GREY}TS${diagnostic.code}${ANSI_RESET}`, color = categoryColor(diagnostic.category), message = flatten(diagnostic);
39
+ if (diagnostic.fileName === undefined) {
40
+ return `${color}${category}${ANSI_RESET} ${code}: ${message}`;
41
+ }
42
+ let location = `${ANSI_CYAN}${path.relative(root, diagnostic.fileName).replace(BACKSLASH_REGEX, '/')}${ANSI_RESET}`, text = readSource(diagnostic.fileName);
43
+ if (text === undefined) {
44
+ return `${location} - ${color}${category}${ANSI_RESET} ${code}: ${message}`;
45
+ }
46
+ let lineStarts = computeLineStarts(text), line = lineOfPosition(lineStarts, diagnostic.pos), character = diagnostic.pos - lineStarts[line], lineEnd = line + 1 < lineStarts.length ? lineStarts[line + 1] : text.length, source = text.slice(lineStarts[line], lineEnd).replace(TRAILING_NEWLINE_REGEX, ''), width = Math.max(1, Math.min(diagnostic.end, lineEnd) - diagnostic.pos), underline = `${' '.repeat(character)}${color}${'~'.repeat(width)}${ANSI_RESET}`, header = `${location}:${ANSI_YELLOW}${line + 1}${ANSI_RESET}:${ANSI_YELLOW}${character + 1}${ANSI_RESET} - ${color}${category}${ANSI_RESET} ${code}: ${message}`;
47
+ return `${header}\n\n${source}\n${underline}`;
48
+ }
49
+ function lineOfPosition(lineStarts, position) {
50
+ let high = lineStarts.length - 1, low = 0;
51
+ while (low <= high) {
52
+ let middle = (low + high) >> 1;
53
+ if (lineStarts[middle] <= position) {
54
+ low = middle + 1;
55
+ }
56
+ else {
57
+ high = middle - 1;
58
+ }
59
+ }
60
+ return high < 0 ? 0 : high;
61
+ }
62
+ function readSource(fileName) {
63
+ try {
64
+ return readFileSync(fileName, 'utf8');
65
+ }
66
+ catch {
67
+ return undefined;
68
+ }
69
+ }
70
+ const flatten = (diagnostic, indent = 0) => {
71
+ let result = '';
72
+ if (indent > 0) {
73
+ result += `\n${' '.repeat(indent)}`;
74
+ }
75
+ result += diagnostic.text;
76
+ if (diagnostic.messageChain !== undefined) {
77
+ for (let i = 0, n = diagnostic.messageChain.length; i < n; i++) {
78
+ result += flatten(diagnostic.messageChain[i], indent + 1);
79
+ }
80
+ }
81
+ return result;
82
+ };
83
+ const format = (diagnostics, root) => {
84
+ let parts = [];
85
+ for (let i = 0, n = diagnostics.length; i < n; i++) {
86
+ parts.push(formatOne(diagnostics[i], root));
87
+ }
88
+ return parts.join('\n\n');
89
+ };
90
+ export { flatten, format };
@@ -1,10 +1,18 @@
1
1
  import type { Plugin } from '../compiler/types.js';
2
+ import { API } from 'typescript/unstable/sync';
2
3
  type PluginConfig = {
3
4
  transform: string;
4
5
  };
5
- declare function build(config: object, tsconfig: string, pluginConfigs: PluginConfig[]): Promise<void>;
6
+ declare function build(tsconfig: string, pluginConfigs: PluginConfig[], instance?: API, noEmit?: boolean): Promise<void>;
7
+ declare function classifyFlags(args: string[]): {
8
+ informational: boolean;
9
+ noEmit: boolean;
10
+ watch: boolean;
11
+ };
6
12
  declare function isPlugin(value: unknown): value is Plugin;
7
13
  declare function loadPlugins(configs: PluginConfig[], root: string): Promise<Plugin[]>;
14
+ declare function main(): void;
8
15
  declare function normalizePath(fileName: string): string;
16
+ declare function resolvePluginConfigs(tsconfig: string): PluginConfig[];
9
17
  declare function runTscAlias(args: string[]): Promise<number>;
10
- export { build, isPlugin, loadPlugins, normalizePath, runTscAlias };
18
+ export { build, classifyFlags, isPlugin, loadPlugins, main, normalizePath, resolvePluginConfigs, runTscAlias };
package/build/cli/tsc.js CHANGED
@@ -1,59 +1,221 @@
1
+ import { API, DiagnosticCategory } from 'typescript/unstable/sync';
1
2
  import { createRequire } from 'module';
3
+ import { format } from './diagnostics.js';
2
4
  import { PACKAGE_NAME } from '../constants.js';
3
5
  import { pathToFileURL } from 'url';
4
6
  import { spawn } from 'child_process';
5
7
  import coordinator from '../compiler/coordinator.js';
8
+ import fs from 'fs';
9
+ import languageService from '../compiler/language-service.js';
6
10
  import path from 'path';
7
- import ts from 'typescript';
11
+ import sourcemap from '../compiler/sourcemap.js';
8
12
  const BACKSLASH_REGEX = /\\/g;
13
+ const INFORMATIONAL_FLAGS = new Set(['--help', '--init', '--showConfig', '--version', '-h', '-v']);
14
+ const NO_EMIT_FLAGS = new Set(['--noEmit', '-noEmit']);
15
+ const WATCH_FLAGS = new Set(['--watch', '-w']);
9
16
  let require = createRequire(import.meta.url), skipFlags = new Set(['--help', '--init', '--noEmit', '--showConfig', '--version', '-h', '-noEmit', '-v']);
10
- async function build(config, tsconfig, pluginConfigs) {
11
- let root = path.dirname(path.resolve(tsconfig)), parsed = ts.parseJsonConfigFileContent(config, ts.sys, root);
12
- if (parsed.errors.length > 0) {
13
- for (let i = 0, n = parsed.errors.length; i < n; i++) {
14
- console.error(ts.flattenDiagnosticMessageText(parsed.errors[i].messageText, '\n'));
15
- }
17
+ async function build(tsconfig, pluginConfigs, instance, noEmit = false) {
18
+ let root = path.dirname(path.resolve(tsconfig)), owned = instance === undefined, api = instance ?? new API({ cwd: root }), snapshot = api.updateSnapshot({ openProjects: [tsconfig] }), project = snapshot.getProject(tsconfig);
19
+ if (!project) {
20
+ teardown(snapshot, api, root, owned);
21
+ throw new Error(`${PACKAGE_NAME}: project not found for ${tsconfig}`);
22
+ }
23
+ let configDiagnostics = project.program.getConfigFileParsingDiagnostics();
24
+ if (configDiagnostics.some((diagnostic) => diagnostic.category === DiagnosticCategory.Error)) {
25
+ console.error(format(configDiagnostics, root));
26
+ teardown(snapshot, api, root, owned);
27
+ process.exit(1);
28
+ }
29
+ let { fileNames, options } = api.parseConfigFile(tsconfig), plugins, shared = new Map(), transformedFiles = new Map();
30
+ try {
31
+ plugins = await loadPlugins(pluginConfigs, root);
32
+ }
33
+ catch (error) {
34
+ console.error(error instanceof Error ? error.message : String(error));
35
+ teardown(snapshot, api, root, owned);
16
36
  process.exit(1);
17
37
  }
18
- let plugins = await loadPlugins(pluginConfigs, root);
19
- let program = ts.createProgram(parsed.fileNames, parsed.options), shared = new Map(), transformedFiles = new Map();
20
- for (let i = 0, n = parsed.fileNames.length; i < n; i++) {
21
- let fileName = parsed.fileNames[i], sourceFile = program.getSourceFile(fileName);
38
+ for (let i = 0, n = fileNames.length; i < n; i++) {
39
+ let fileName = fileNames[i], sourceFile = project.program.getSourceFile(fileName);
22
40
  if (!sourceFile) {
23
41
  continue;
24
42
  }
25
- let result = coordinator.transform(plugins, sourceFile.getFullText(), sourceFile, program, root, shared);
43
+ let result = coordinator.transform(plugins, sourceFile.getFullText(), sourceFile, { checker: project.checker, program: project.program }, root, shared);
26
44
  if (result.changed) {
27
- transformedFiles.set(normalizePath(fileName), result.code);
45
+ transformedFiles.set(normalizePath(fileName), { code: result.code, mapping: result.map });
28
46
  }
29
47
  }
30
- if (transformedFiles.size > 0) {
31
- let customHost = ts.createCompilerHost(parsed.options), originalGetSourceFile = customHost.getSourceFile.bind(customHost), originalReadFile = customHost.readFile.bind(customHost);
32
- customHost.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
33
- let transformed = transformedFiles.get(normalizePath(fileName));
34
- if (transformed) {
35
- return ts.createSourceFile(fileName, transformed, languageVersion, true);
36
- }
37
- return originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
38
- };
39
- customHost.readFile = (fileName) => {
40
- return transformedFiles.get(normalizePath(fileName)) ?? originalReadFile(fileName);
41
- };
42
- program = ts.createProgram(parsed.fileNames, parsed.options, customHost);
48
+ let program = project.program;
49
+ for (let [fileName, entry] of transformedFiles) {
50
+ program = languageService.update(root, fileName, entry.code).program;
43
51
  }
44
- let { diagnostics, emitSkipped } = program.emit();
45
- diagnostics = ts.getPreEmitDiagnostics(program).concat(diagnostics);
52
+ let diagnostics = [
53
+ ...program.getConfigFileParsingDiagnostics(),
54
+ ...program.getSyntacticDiagnostics(),
55
+ ...program.getBindDiagnostics(),
56
+ ...program.getSemanticDiagnostics(),
57
+ ...program.getGlobalDiagnostics(),
58
+ ...program.getProgramDiagnostics()
59
+ ];
46
60
  if (diagnostics.length > 0) {
47
- console.error(ts.formatDiagnosticsWithColorAndContext(diagnostics, {
48
- getCanonicalFileName: (fileName) => fileName,
49
- getCurrentDirectory: () => root,
50
- getNewLine: () => '\n'
51
- }));
61
+ console.error(format(diagnostics, root));
52
62
  }
53
- if (emitSkipped) {
63
+ if (diagnostics.some((diagnostic) => diagnostic.category === DiagnosticCategory.Error)) {
64
+ teardown(snapshot, api, root, owned);
54
65
  process.exit(1);
55
66
  }
56
- return runTscAlias(process.argv.slice(2)).then((code) => process.exit(code));
67
+ if (noEmit) {
68
+ teardown(snapshot, api, root, owned);
69
+ process.exit(0);
70
+ }
71
+ let code = await emit(tsconfig, fileNames, transformedFiles, root, options);
72
+ teardown(snapshot, api, root, owned);
73
+ if (code !== 0) {
74
+ process.exit(code);
75
+ }
76
+ return runTscAlias(process.argv.slice(2)).then((exitCode) => process.exit(exitCode));
77
+ }
78
+ function classifyFlags(args) {
79
+ let informational = false, noEmit = false, watch = false;
80
+ for (let i = 0, n = args.length; i < n; i++) {
81
+ let arg = args[i];
82
+ if (INFORMATIONAL_FLAGS.has(arg)) {
83
+ informational = true;
84
+ }
85
+ else if (NO_EMIT_FLAGS.has(arg)) {
86
+ noEmit = true;
87
+ }
88
+ else if (WATCH_FLAGS.has(arg)) {
89
+ watch = true;
90
+ }
91
+ }
92
+ return { informational, noEmit, watch };
93
+ }
94
+ function collectMaps(dir, results) {
95
+ for (let entry of fs.readdirSync(dir, { withFileTypes: true })) {
96
+ let full = path.join(dir, entry.name);
97
+ if (entry.isDirectory()) {
98
+ collectMaps(full, results);
99
+ }
100
+ else if (entry.name.endsWith('.js.map')) {
101
+ results.push(full);
102
+ }
103
+ }
104
+ }
105
+ function composeSourceMaps(mirrorOut, mirror, root, transformedFiles) {
106
+ let maps = [];
107
+ collectMaps(mirrorOut, maps);
108
+ for (let i = 0, n = maps.length; i < n; i++) {
109
+ let mapPath = maps[i], raw;
110
+ try {
111
+ raw = JSON.parse(fs.readFileSync(mapPath, 'utf8'));
112
+ }
113
+ catch {
114
+ continue;
115
+ }
116
+ if (!raw.sources || raw.sources.length === 0 || typeof raw.sources[0] !== 'string' || typeof raw.mappings !== 'string') {
117
+ continue;
118
+ }
119
+ let mirrorSource = path.resolve(path.dirname(mapPath), raw.sources[0]), relative = path.relative(mirror, mirrorSource), realSource = normalizePath(path.join(root, relative)), entry = transformedFiles.get(realSource);
120
+ if (!entry) {
121
+ continue;
122
+ }
123
+ let composed = sourcemap.composeEmittedMap({ mappings: raw.mappings, names: raw.names ?? [], sources: raw.sources, version: 3 }, entry.mapping, entry.code, fs.readFileSync(path.join(root, relative), 'utf8'));
124
+ if (raw.file !== undefined) {
125
+ composed.file = raw.file;
126
+ }
127
+ if (raw.sourceRoot !== undefined) {
128
+ composed.sourceRoot = raw.sourceRoot;
129
+ }
130
+ fs.writeFileSync(mapPath, JSON.stringify(composed));
131
+ }
132
+ }
133
+ async function emit(tsconfig, fileNames, transformedFiles, root, options) {
134
+ let tscJs = path.join(path.dirname(require.resolve('typescript/package.json')), 'lib', 'tsc.js');
135
+ if (transformedFiles.size === 0) {
136
+ return spawnTsc(tscJs, ['-p', tsconfig]);
137
+ }
138
+ let mirror = fs.mkdtempSync(path.join(root, '.esportsplus-tsc-'));
139
+ try {
140
+ let declaration = options.declaration === true, files = [], mirrorDecl = path.join(mirror, '__decl'), mirrorOut = path.join(mirror, '__emit'), realOutDir = typeof options.outDir === 'string' ? path.resolve(options.outDir) : root, realDeclDir = declaration
141
+ ? (typeof options.declarationDir === 'string' ? path.resolve(options.declarationDir) : realOutDir)
142
+ : undefined, realRootDir = typeof options.rootDir === 'string' ? path.resolve(options.rootDir) : undefined, separateDecl = realDeclDir !== undefined && normalizePath(realDeclDir) !== normalizePath(realOutDir);
143
+ for (let i = 0, n = fileNames.length; i < n; i++) {
144
+ let source = path.resolve(fileNames[i]), relative = path.relative(root, source);
145
+ if (relative.startsWith('..')) {
146
+ throw new Error(`${PACKAGE_NAME}: source ${normalizePath(source)} resolves outside the project root ${normalizePath(root)}; monorepo-external sources are not supported by the transform emit path`);
147
+ }
148
+ let target = path.join(mirror, relative), transformed = transformedFiles.get(normalizePath(fileNames[i]));
149
+ fs.mkdirSync(path.dirname(target), { recursive: true });
150
+ if (transformed === undefined) {
151
+ fs.copyFileSync(source, target);
152
+ }
153
+ else {
154
+ fs.writeFileSync(target, transformed.code);
155
+ }
156
+ files.push(normalizePath(target));
157
+ }
158
+ let compilerOptions = {
159
+ outDir: normalizePath(mirrorOut)
160
+ };
161
+ if (declaration) {
162
+ compilerOptions.declarationDir = normalizePath(separateDecl ? mirrorDecl : mirrorOut);
163
+ }
164
+ if (realRootDir !== undefined) {
165
+ compilerOptions.rootDir = normalizePath(path.join(mirror, path.relative(root, realRootDir)));
166
+ }
167
+ fs.writeFileSync(path.join(mirror, 'tsconfig.json'), JSON.stringify({
168
+ compilerOptions,
169
+ exclude: [],
170
+ extends: normalizePath(tsconfig),
171
+ files,
172
+ include: []
173
+ }));
174
+ let code = await spawnTsc(tscJs, ['-p', path.join(mirror, 'tsconfig.json')]);
175
+ if (code === 0) {
176
+ if (fs.existsSync(mirrorOut)) {
177
+ composeSourceMaps(mirrorOut, mirror, root, transformedFiles);
178
+ fs.cpSync(mirrorOut, realOutDir, { force: true, recursive: true });
179
+ }
180
+ if (separateDecl && realDeclDir !== undefined && fs.existsSync(mirrorDecl)) {
181
+ fs.cpSync(mirrorDecl, realDeclDir, { force: true, recursive: true });
182
+ }
183
+ }
184
+ return code;
185
+ }
186
+ finally {
187
+ fs.rmSync(mirror, { force: true, recursive: true });
188
+ }
189
+ }
190
+ function extendsTarget(specifier, fromDir) {
191
+ if (typeof specifier !== 'string') {
192
+ return null;
193
+ }
194
+ if (specifier.startsWith('.')) {
195
+ let resolved = path.resolve(fromDir, specifier);
196
+ if (fs.existsSync(resolved) && fs.statSync(resolved).isFile()) {
197
+ return resolved;
198
+ }
199
+ if (fs.existsSync(resolved + '.json')) {
200
+ return resolved + '.json';
201
+ }
202
+ let nested = path.join(resolved, 'tsconfig.json');
203
+ if (fs.existsSync(nested)) {
204
+ return nested;
205
+ }
206
+ return null;
207
+ }
208
+ try {
209
+ return require.resolve(specifier, { paths: [fromDir] });
210
+ }
211
+ catch {
212
+ try {
213
+ return require.resolve(specifier + '/tsconfig.json', { paths: [fromDir] });
214
+ }
215
+ catch {
216
+ return null;
217
+ }
218
+ }
57
219
  }
58
220
  function isPlugin(value) {
59
221
  return typeof value === 'object' && value !== null && 'transform' in value && typeof value.transform === 'function';
@@ -75,18 +237,15 @@ async function loadPlugins(configs, root) {
75
237
  }
76
238
  if (Array.isArray(plugin)) {
77
239
  for (let j = 0, m = plugin.length; j < m; j++) {
78
- if (isPlugin(plugin[j])) {
79
- plugins.push(plugin[j]);
80
- }
81
- else {
82
- console.error(`${PACKAGE_NAME}: plugin ${config.transform}[${j}] uses an invalid plugin format`);
240
+ if (!isPlugin(plugin[j])) {
241
+ throw new Error(`${PACKAGE_NAME}: plugin ${config.transform}[${j}] uses an invalid plugin format, expected { transform: Function }`);
83
242
  }
243
+ plugins.push(plugin[j]);
84
244
  }
85
245
  return;
86
246
  }
87
247
  if (!isPlugin(plugin)) {
88
- console.error(`${PACKAGE_NAME}: plugin ${config.transform} uses an invalid plugin format, expected { transform: Function } or Plugin[]`);
89
- return;
248
+ throw new Error(`${PACKAGE_NAME}: plugin ${config.transform} uses an invalid plugin format, expected { transform: Function } or Plugin[]`);
90
249
  }
91
250
  plugins.push(plugin);
92
251
  }));
@@ -95,20 +254,25 @@ async function loadPlugins(configs, root) {
95
254
  return plugins;
96
255
  }
97
256
  function main() {
98
- let tsconfig = ts.findConfigFile(process.cwd(), ts.sys.fileExists, 'tsconfig.json');
257
+ let tsconfig = languageService.findConfig(process.cwd());
99
258
  if (!tsconfig) {
100
259
  return passthrough();
101
260
  }
102
- let { config, error } = ts.readConfigFile(tsconfig, ts.sys.readFile);
103
- if (error) {
261
+ let pluginConfigs = resolvePluginConfigs(tsconfig);
262
+ if (pluginConfigs.length === 0) {
104
263
  return passthrough();
105
264
  }
106
- let pluginConfigs = config?.compilerOptions?.plugins?.filter((p) => typeof p === 'object' && p !== null && 'transform' in p) ?? [];
107
- if (pluginConfigs.length === 0) {
265
+ let flags = classifyFlags(process.argv.slice(2));
266
+ if (flags.informational) {
108
267
  return passthrough();
109
268
  }
269
+ if (flags.watch) {
270
+ console.error(`${PACKAGE_NAME}: --watch is not supported on the transformer plugin path; run a one-shot build or use real tsc directly`);
271
+ process.exit(1);
272
+ return;
273
+ }
110
274
  console.log(`${PACKAGE_NAME}: found ${pluginConfigs.length} transformer plugin(s), using coordinated build...`);
111
- build(config, tsconfig, pluginConfigs).catch((err) => {
275
+ build(tsconfig, pluginConfigs, undefined, flags.noEmit).catch((err) => {
112
276
  console.error(`${PACKAGE_NAME}: ${err}`);
113
277
  process.exit(1);
114
278
  });
@@ -117,8 +281,8 @@ function normalizePath(fileName) {
117
281
  return path.resolve(fileName).replace(BACKSLASH_REGEX, '/');
118
282
  }
119
283
  function passthrough() {
120
- let args = process.argv.slice(2);
121
- spawn(process.execPath, [require.resolve('typescript/lib/tsc.js'), ...args], { stdio: 'inherit' })
284
+ let args = process.argv.slice(2), tsDir = path.dirname(require.resolve('typescript/package.json'));
285
+ spawn(process.execPath, [path.join(tsDir, 'lib', 'tsc.js'), ...args], { stdio: 'inherit' })
122
286
  .on('exit', async (code) => {
123
287
  if (code === 0) {
124
288
  code = await runTscAlias(args);
@@ -126,6 +290,44 @@ function passthrough() {
126
290
  process.exit(code ?? 0);
127
291
  });
128
292
  }
293
+ function readPlugins(tsconfig, seen) {
294
+ let id = path.resolve(tsconfig);
295
+ if (seen.has(id)) {
296
+ return undefined;
297
+ }
298
+ seen.add(id);
299
+ let config;
300
+ try {
301
+ config = JSON.parse(stripJsonc(fs.readFileSync(id, 'utf8')));
302
+ }
303
+ catch {
304
+ return undefined;
305
+ }
306
+ let plugins;
307
+ if (config?.extends !== undefined) {
308
+ let bases = Array.isArray(config.extends) ? config.extends : [config.extends];
309
+ for (let i = 0, n = bases.length; i < n; i++) {
310
+ let target = extendsTarget(bases[i], path.dirname(id));
311
+ if (target) {
312
+ let inherited = readPlugins(target, seen);
313
+ if (inherited !== undefined) {
314
+ plugins = inherited;
315
+ }
316
+ }
317
+ }
318
+ }
319
+ if (Array.isArray(config?.compilerOptions?.plugins)) {
320
+ plugins = config.compilerOptions.plugins;
321
+ }
322
+ return plugins;
323
+ }
324
+ function resolvePluginConfigs(tsconfig) {
325
+ let plugins = readPlugins(tsconfig, new Set());
326
+ if (!Array.isArray(plugins)) {
327
+ return [];
328
+ }
329
+ return plugins.filter((p) => typeof p === 'object' && p !== null && 'transform' in p);
330
+ }
129
331
  function runTscAlias(args) {
130
332
  for (let i = 0, n = args.length; i < n; i++) {
131
333
  if (skipFlags.has(args[i])) {
@@ -137,7 +339,106 @@ function runTscAlias(args) {
137
339
  child.on('exit', (code) => resolve(code ?? 0));
138
340
  });
139
341
  }
342
+ function spawnTsc(tscJs, args) {
343
+ return new Promise((resolve) => {
344
+ let child = spawn(process.execPath, [tscJs, ...args], { stdio: 'inherit' });
345
+ child.on('exit', (code) => resolve(code ?? 0));
346
+ });
347
+ }
348
+ function stripJsonc(text) {
349
+ let escaped = false, inBlockComment = false, inLineComment = false, inString = false, stripped = '';
350
+ for (let i = 0, n = text.length; i < n; i++) {
351
+ let char = text[i], next = text[i + 1];
352
+ if (inLineComment) {
353
+ if (char === '\n') {
354
+ inLineComment = false;
355
+ stripped += char;
356
+ }
357
+ continue;
358
+ }
359
+ if (inBlockComment) {
360
+ if (char === '*' && next === '/') {
361
+ inBlockComment = false;
362
+ i++;
363
+ }
364
+ continue;
365
+ }
366
+ if (inString) {
367
+ stripped += char;
368
+ if (escaped) {
369
+ escaped = false;
370
+ }
371
+ else if (char === '\\') {
372
+ escaped = true;
373
+ }
374
+ else if (char === '"') {
375
+ inString = false;
376
+ }
377
+ continue;
378
+ }
379
+ if (char === '"') {
380
+ inString = true;
381
+ stripped += char;
382
+ continue;
383
+ }
384
+ if (char === '/' && next === '/') {
385
+ inLineComment = true;
386
+ i++;
387
+ continue;
388
+ }
389
+ if (char === '/' && next === '*') {
390
+ inBlockComment = true;
391
+ i++;
392
+ continue;
393
+ }
394
+ stripped += char;
395
+ }
396
+ escaped = false;
397
+ inString = false;
398
+ let result = '';
399
+ for (let i = 0, n = stripped.length; i < n; i++) {
400
+ let char = stripped[i];
401
+ if (inString) {
402
+ result += char;
403
+ if (escaped) {
404
+ escaped = false;
405
+ }
406
+ else if (char === '\\') {
407
+ escaped = true;
408
+ }
409
+ else if (char === '"') {
410
+ inString = false;
411
+ }
412
+ continue;
413
+ }
414
+ if (char === '"') {
415
+ inString = true;
416
+ result += char;
417
+ continue;
418
+ }
419
+ if (char === ',') {
420
+ let j = i + 1;
421
+ while (j < n && (stripped[j] === ' ' || stripped[j] === '\n' || stripped[j] === '\r' || stripped[j] === '\t')) {
422
+ j++;
423
+ }
424
+ if (stripped[j] === ']' || stripped[j] === '}') {
425
+ continue;
426
+ }
427
+ }
428
+ result += char;
429
+ }
430
+ return result;
431
+ }
432
+ function teardown(snapshot, api, root, owned) {
433
+ if (!snapshot.isDisposed()) {
434
+ snapshot.dispose();
435
+ }
436
+ if (owned) {
437
+ api.close();
438
+ }
439
+ languageService.dispose(root);
440
+ }
140
441
  if (process.env.VITEST === undefined) {
141
442
  main();
142
443
  }
143
- export { build, isPlugin, loadPlugins, normalizePath, runTscAlias };
444
+ export { build, classifyFlags, isPlugin, loadPlugins, main, normalizePath, resolvePluginConfigs, runTscAlias };
@@ -1,14 +1,16 @@
1
1
  import type { Range } from './types.js';
2
- import { ts } from '../index.js';
2
+ import type { Expression, Node } from 'typescript/unstable/ast';
3
+ declare const inRange: (ranges: Range[], start: number, end: number) => boolean;
4
+ declare const test: (node: Node, fn: (n: Node) => boolean) => boolean;
3
5
  declare const _default: {
4
6
  expression: {
5
- name: (node: ts.Expression) => string | null;
7
+ name: (node: Expression) => string | null;
6
8
  };
7
- inRange: (ranges: Range[], start: number, end: number) => boolean;
9
+ inRange: typeof inRange;
8
10
  property: {
9
- path: (node: ts.Expression) => string | null;
11
+ path: (node: Expression) => string | null;
10
12
  };
11
- test: (node: ts.Node, fn: (n: ts.Node) => boolean) => boolean;
13
+ test: typeof test;
12
14
  };
13
15
  export default _default;
14
16
  export type { Range };