carrick 0.3.108 → 0.3.109
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/package.json +9 -10
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/sidecar/dist/src/capture/check-deep.js +2 -1
- package/sidecar/dist/src/capture/check-workspace.d.ts +1 -0
- package/sidecar/dist/src/capture/check-workspace.js +13 -2
- package/sidecar/dist/src/capture/compiler-options.d.ts +39 -0
- package/sidecar/dist/src/capture/compiler-options.js +100 -0
- package/sidecar/dist/src/capture/deno-project.js +3 -2
- package/sidecar/dist/src/capture/index.d.ts +1 -0
- package/sidecar/dist/src/capture/index.js +7 -5
- package/sidecar/dist/src/capture/installed-package.js +3 -2
- package/sidecar/dist/src/capture/self-check.js +3 -2
- package/sidecar/dist/src/definition-resolver.js +3 -2
- package/sidecar/dist/src/index.js +91 -7
- package/sidecar/dist/src/print-type.d.ts +48 -0
- package/sidecar/dist/src/print-type.js +71 -0
- package/sidecar/dist/src/printed-names.js +2 -1
- package/sidecar/dist/src/program-files.d.ts +44 -0
- package/sidecar/dist/src/program-files.js +64 -0
- package/sidecar/dist/src/project-loader.d.ts +5 -0
- package/sidecar/dist/src/project-loader.js +14 -5
- package/sidecar/dist/src/retype.js +7 -1
- package/sidecar/dist/src/type-inferrer.d.ts +11 -0
- package/sidecar/dist/src/type-inferrer.js +30 -3
- package/sidecar/dist/src/types.d.ts +34 -2
- package/sidecar/dist/src/validators.d.ts +120 -72
- package/sidecar/dist/src/validators.js +9 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "carrick",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.109",
|
|
4
4
|
"description": "Maps your entire TypeScript codebase across services and repositories, giving AI agents full context on existing types, routes, and function behaviours over MCP before they write duplicate or breaking code.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -53,20 +53,19 @@
|
|
|
53
53
|
"@clack/prompts": "1.8.1",
|
|
54
54
|
"picocolors": "1.1.1",
|
|
55
55
|
"pnpm": "10.34.5",
|
|
56
|
-
"ts-morph": "
|
|
57
|
-
"typescript": "
|
|
56
|
+
"ts-morph": "28.0.0",
|
|
57
|
+
"typescript": "6.0.2",
|
|
58
58
|
"zod": "^3.23.0"
|
|
59
59
|
},
|
|
60
60
|
"optionalDependencies": {
|
|
61
|
-
"@carrick-tools/cli-darwin-arm64": "0.3.
|
|
62
|
-
"@carrick-tools/cli-darwin-x64": "0.3.
|
|
63
|
-
"@carrick-tools/cli-linux-arm64": "0.3.
|
|
64
|
-
"@carrick-tools/cli-linux-x64": "0.3.
|
|
65
|
-
"@carrick-tools/cli-win32-x64": "0.3.
|
|
61
|
+
"@carrick-tools/cli-darwin-arm64": "0.3.109",
|
|
62
|
+
"@carrick-tools/cli-darwin-x64": "0.3.109",
|
|
63
|
+
"@carrick-tools/cli-linux-arm64": "0.3.109",
|
|
64
|
+
"@carrick-tools/cli-linux-x64": "0.3.109",
|
|
65
|
+
"@carrick-tools/cli-win32-x64": "0.3.109"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@types/node": "^24.13.3"
|
|
69
|
-
"typescript": "^5.9.2"
|
|
68
|
+
"@types/node": "^24.13.3"
|
|
70
69
|
},
|
|
71
70
|
"publishConfig": {
|
|
72
71
|
"access": "public",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "carrick",
|
|
3
3
|
"description": "Claude Code plugin for Carrick, which indexes TypeScript codebases across service and repository boundaries. After each edit it adds the file's routes, calls, and cross-service type mismatches to the session, and it registers Carrick's language server. It pairs with the Carrick MCP server, which lets agents search functions by intent rather than name.",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.109",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Carrick",
|
|
7
7
|
"email": "hello@carrick.tools"
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import ts from 'typescript';
|
|
19
19
|
import * as fs from 'node:fs';
|
|
20
20
|
import * as path from 'node:path';
|
|
21
|
+
import { sidecarCompilerOptions } from './compiler-options.js';
|
|
21
22
|
import { findDisqualifyingTopTypes, provenanceOf } from './deep-walk.js';
|
|
22
23
|
export function openProbeProgram(probesDir, plans) {
|
|
23
24
|
if (plans.length === 0)
|
|
@@ -35,7 +36,7 @@ export function openProbeProgram(probesDir, plans) {
|
|
|
35
36
|
.filter((f) => fs.existsSync(f));
|
|
36
37
|
if (fileNames.length === 0)
|
|
37
38
|
return undefined;
|
|
38
|
-
const program = ts.createProgram(fileNames, { ...parsed.options, noEmit: true });
|
|
39
|
+
const program = ts.createProgram(fileNames, sidecarCompilerOptions({ ...parsed.options, noEmit: true }));
|
|
39
40
|
return { program, checker: program.getTypeChecker(), probesDir };
|
|
40
41
|
}
|
|
41
42
|
catch {
|
|
@@ -53,3 +53,4 @@ export interface AssembleOptions {
|
|
|
53
53
|
export declare function assembleWorkspace(opts: AssembleOptions): AssembledWorkspace;
|
|
54
54
|
/** Write the generated probe files into the assembled probes package. */
|
|
55
55
|
export declare function writeProbes(ws: AssembledWorkspace, plans: ProbePlan[]): void;
|
|
56
|
+
export declare const CHECKER_TSCONFIG: string;
|
|
@@ -195,8 +195,11 @@ const NPMRC = [
|
|
|
195
195
|
// TS2717 collisions so the poison rule can convert them to honest
|
|
196
196
|
// unverifiables). noUnusedLocals/Parameters:false keep TS6133/6196 off the
|
|
197
197
|
// gate/assignment lines. moduleResolution bundler accepts both node16
|
|
198
|
-
// `.js`-suffixed and extensionless specifiers in one program.
|
|
199
|
-
|
|
198
|
+
// `.js`-suffixed and extensionless specifiers in one program. The rest is
|
|
199
|
+
// what `sidecarCompilerOptions` sets on every other program (carrick#2019):
|
|
200
|
+
// `tsc` reads this file itself, so each value is written out, and a test
|
|
201
|
+
// holds the file to what that function would make of it.
|
|
202
|
+
export const CHECKER_TSCONFIG = JSON.stringify({
|
|
200
203
|
compilerOptions: {
|
|
201
204
|
strict: true,
|
|
202
205
|
exactOptionalPropertyTypes: false,
|
|
@@ -210,6 +213,14 @@ const CHECKER_TSCONFIG = JSON.stringify({
|
|
|
210
213
|
types: [],
|
|
211
214
|
noEmit: true,
|
|
212
215
|
forceConsistentCasingInFileNames: true,
|
|
216
|
+
esModuleInterop: false,
|
|
217
|
+
allowSyntheticDefaultImports: true,
|
|
218
|
+
resolveJsonModule: true,
|
|
219
|
+
alwaysStrict: true,
|
|
220
|
+
noUncheckedSideEffectImports: false,
|
|
221
|
+
libReplacement: true,
|
|
222
|
+
stableTypeOrdering: true,
|
|
223
|
+
ignoreDeprecations: '6.0',
|
|
213
224
|
},
|
|
214
225
|
include: ['probes/**/*.ts'],
|
|
215
226
|
}, null, 2) + '\n';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The options every program the sidecar builds carries (carrick#2019).
|
|
3
|
+
*
|
|
4
|
+
* Every `ts.createProgram`, every ts-morph `Project` and the `tsconfig.json`
|
|
5
|
+
* the check hands to `tsc` goes through `sidecarCompilerOptions`, whatever
|
|
6
|
+
* its options came from: a project's tsconfig, a snapshot of one, a Deno
|
|
7
|
+
* config, or the sidecar's own literal set.
|
|
8
|
+
*
|
|
9
|
+
* Two things are added:
|
|
10
|
+
*
|
|
11
|
+
* - `stableTypeOrdering`. Without it the compiler prints a union's members in
|
|
12
|
+
* the order the checker first met them, and an object made by a mapped type
|
|
13
|
+
* lists its properties in the order its keys were created, so the same type
|
|
14
|
+
* printed text that depended on what the process had read before. With it
|
|
15
|
+
* the checker orders types by their content, and one process prints the
|
|
16
|
+
* same text whatever it did first.
|
|
17
|
+
* - TypeScript 5's effective value for every option whose default TypeScript 6
|
|
18
|
+
* changed, where the options leave it unset (`typescript5Defaults`). A
|
|
19
|
+
* project written against TypeScript 5 is read as its own compiler reads
|
|
20
|
+
* it, and as the sidecar read it before it moved to 6. A project that sets
|
|
21
|
+
* an option keeps its own value. Several of those values are deprecated in
|
|
22
|
+
* TypeScript 6 (target ES5, moduleResolution node10, esModuleInterop false),
|
|
23
|
+
* and so are options many projects still set themselves (baseUrl), so
|
|
24
|
+
* `ignoreDeprecations` is set too: a deprecated option still works in 6, and
|
|
25
|
+
* the error it would raise is not the project's.
|
|
26
|
+
*/
|
|
27
|
+
import ts from 'typescript';
|
|
28
|
+
/**
|
|
29
|
+
* For each option whose default TypeScript 6 changed and `options` leaves
|
|
30
|
+
* unset, the value TypeScript 5 (5.9, its last minor) computes for it from
|
|
31
|
+
* the options that are set. Only unset options appear in the result.
|
|
32
|
+
*/
|
|
33
|
+
export declare function typescript5Defaults(options: ts.CompilerOptions): ts.CompilerOptions;
|
|
34
|
+
/**
|
|
35
|
+
* `options` with TypeScript 5's defaults filled in and the sidecar's own set.
|
|
36
|
+
* Takes ts-morph's `CompilerOptions` as well as the compiler's: the two
|
|
37
|
+
* declare the same option enums separately, and the values are the same.
|
|
38
|
+
*/
|
|
39
|
+
export declare function sidecarCompilerOptions<O extends object>(options: O): O;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The options every program the sidecar builds carries (carrick#2019).
|
|
3
|
+
*
|
|
4
|
+
* Every `ts.createProgram`, every ts-morph `Project` and the `tsconfig.json`
|
|
5
|
+
* the check hands to `tsc` goes through `sidecarCompilerOptions`, whatever
|
|
6
|
+
* its options came from: a project's tsconfig, a snapshot of one, a Deno
|
|
7
|
+
* config, or the sidecar's own literal set.
|
|
8
|
+
*
|
|
9
|
+
* Two things are added:
|
|
10
|
+
*
|
|
11
|
+
* - `stableTypeOrdering`. Without it the compiler prints a union's members in
|
|
12
|
+
* the order the checker first met them, and an object made by a mapped type
|
|
13
|
+
* lists its properties in the order its keys were created, so the same type
|
|
14
|
+
* printed text that depended on what the process had read before. With it
|
|
15
|
+
* the checker orders types by their content, and one process prints the
|
|
16
|
+
* same text whatever it did first.
|
|
17
|
+
* - TypeScript 5's effective value for every option whose default TypeScript 6
|
|
18
|
+
* changed, where the options leave it unset (`typescript5Defaults`). A
|
|
19
|
+
* project written against TypeScript 5 is read as its own compiler reads
|
|
20
|
+
* it, and as the sidecar read it before it moved to 6. A project that sets
|
|
21
|
+
* an option keeps its own value. Several of those values are deprecated in
|
|
22
|
+
* TypeScript 6 (target ES5, moduleResolution node10, esModuleInterop false),
|
|
23
|
+
* and so are options many projects still set themselves (baseUrl), so
|
|
24
|
+
* `ignoreDeprecations` is set too: a deprecated option still works in 6, and
|
|
25
|
+
* the error it would raise is not the project's.
|
|
26
|
+
*/
|
|
27
|
+
import ts from 'typescript';
|
|
28
|
+
/** Options the sidecar sets on every program, over anything else. */
|
|
29
|
+
const ALWAYS = {
|
|
30
|
+
stableTypeOrdering: true,
|
|
31
|
+
ignoreDeprecations: '6.0',
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* For each option whose default TypeScript 6 changed and `options` leaves
|
|
35
|
+
* unset, the value TypeScript 5 (5.9, its last minor) computes for it from
|
|
36
|
+
* the options that are set. Only unset options appear in the result.
|
|
37
|
+
*/
|
|
38
|
+
export function typescript5Defaults(options) {
|
|
39
|
+
const fill = {};
|
|
40
|
+
const { ModuleKind: M, ModuleResolutionKind: R, ScriptTarget: T } = ts;
|
|
41
|
+
// 5.x ignores ES3 as it ignores no target: both take the default.
|
|
42
|
+
const statedTarget = options.target === T.ES3 ? undefined : options.target;
|
|
43
|
+
const target = statedTarget ??
|
|
44
|
+
(options.module === M.Node16 || options.module === M.Node18
|
|
45
|
+
? T.ES2022
|
|
46
|
+
: options.module === M.Node20
|
|
47
|
+
? T.ES2023
|
|
48
|
+
: options.module === M.NodeNext
|
|
49
|
+
? T.ESNext
|
|
50
|
+
: T.ES5);
|
|
51
|
+
if (statedTarget === undefined)
|
|
52
|
+
fill.target = target;
|
|
53
|
+
const module = options.module ?? (target >= T.ES2015 ? M.ES2015 : M.CommonJS);
|
|
54
|
+
if (options.module === undefined)
|
|
55
|
+
fill.module = module;
|
|
56
|
+
const moduleResolution = options.moduleResolution ??
|
|
57
|
+
(module === M.CommonJS
|
|
58
|
+
? R.Node10
|
|
59
|
+
: module === M.Node16 || module === M.Node18 || module === M.Node20
|
|
60
|
+
? R.Node16
|
|
61
|
+
: module === M.NodeNext
|
|
62
|
+
? R.NodeNext
|
|
63
|
+
: module === M.Preserve
|
|
64
|
+
? R.Bundler
|
|
65
|
+
: R.Classic);
|
|
66
|
+
if (options.moduleResolution === undefined)
|
|
67
|
+
fill.moduleResolution = moduleResolution;
|
|
68
|
+
const nodeFamily = module === M.Node16 || module === M.Node18 || module === M.Node20 || module === M.NodeNext;
|
|
69
|
+
const esModuleInterop = options.esModuleInterop ?? (nodeFamily || module === M.Preserve);
|
|
70
|
+
if (options.esModuleInterop === undefined)
|
|
71
|
+
fill.esModuleInterop = esModuleInterop;
|
|
72
|
+
if (options.allowSyntheticDefaultImports === undefined) {
|
|
73
|
+
fill.allowSyntheticDefaultImports = esModuleInterop || module === M.System || moduleResolution === R.Bundler;
|
|
74
|
+
}
|
|
75
|
+
if (options.resolveJsonModule === undefined) {
|
|
76
|
+
fill.resolveJsonModule = module === M.Node20 || module === M.NodeNext || moduleResolution === R.Bundler;
|
|
77
|
+
}
|
|
78
|
+
// Each strict flag a project leaves unset follows `strict`, which was off
|
|
79
|
+
// unless set; `alwaysStrict` followed it too.
|
|
80
|
+
if (options.strict === undefined)
|
|
81
|
+
fill.strict = false;
|
|
82
|
+
if (options.alwaysStrict === undefined)
|
|
83
|
+
fill.alwaysStrict = options.strict ?? false;
|
|
84
|
+
// Every package under each `typeRoots` directory (`@types/*`), not none.
|
|
85
|
+
if (options.types === undefined)
|
|
86
|
+
fill.types = ['*'];
|
|
87
|
+
if (options.noUncheckedSideEffectImports === undefined)
|
|
88
|
+
fill.noUncheckedSideEffectImports = false;
|
|
89
|
+
if (options.libReplacement === undefined)
|
|
90
|
+
fill.libReplacement = true;
|
|
91
|
+
return fill;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* `options` with TypeScript 5's defaults filled in and the sidecar's own set.
|
|
95
|
+
* Takes ts-morph's `CompilerOptions` as well as the compiler's: the two
|
|
96
|
+
* declare the same option enums separately, and the values are the same.
|
|
97
|
+
*/
|
|
98
|
+
export function sidecarCompilerOptions(options) {
|
|
99
|
+
return { ...options, ...typescript5Defaults(options), ...ALWAYS };
|
|
100
|
+
}
|
|
@@ -5,6 +5,7 @@ import * as path from 'node:path';
|
|
|
5
5
|
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
6
6
|
import { createHash } from 'node:crypto';
|
|
7
7
|
import { execFileSync } from 'node:child_process';
|
|
8
|
+
import { sidecarCompilerOptions } from './compiler-options.js';
|
|
8
9
|
import { rewriteSpecifiers } from './specifiers.js';
|
|
9
10
|
import { WriteGuard } from './guarded-fs.js';
|
|
10
11
|
function readConfig(file) {
|
|
@@ -452,11 +453,11 @@ export class DenoProject {
|
|
|
452
453
|
/** Runtime declarations belong to their producer, not the checker's globals. */
|
|
453
454
|
function isolateRuntime(guard, typesDir, files, runtimeRel) {
|
|
454
455
|
const runtimePath = path.join(typesDir, runtimeRel);
|
|
455
|
-
const program = ts.createProgram(files.map(file => path.join(typesDir, file)), {
|
|
456
|
+
const program = ts.createProgram(files.map(file => path.join(typesDir, file)), sidecarCompilerOptions({
|
|
456
457
|
strict: true, skipLibCheck: true, target: ts.ScriptTarget.ESNext,
|
|
457
458
|
module: ts.ModuleKind.ESNext, moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
458
459
|
types: [],
|
|
459
|
-
});
|
|
460
|
+
}));
|
|
460
461
|
const checker = program.getTypeChecker();
|
|
461
462
|
const runtime = program.getSourceFile(runtimePath);
|
|
462
463
|
const retained = new Set();
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
import type { CaptureStubOptions, CaptureStubResult } from './api.js';
|
|
31
31
|
export type { CaptureStubOptions, CaptureStubResult } from './api.js';
|
|
32
32
|
export { DenoProject, findDenoConfig } from './deno-project.js';
|
|
33
|
+
export { sidecarCompilerOptions } from './compiler-options.js';
|
|
33
34
|
export { serviceConfigPath } from './project-references.js';
|
|
34
35
|
export { findServiceTsconfig } from './service-config.js';
|
|
35
36
|
export { runCheck } from './check.js';
|
|
@@ -42,7 +42,9 @@ import { emitsAlike, ProjectGraph } from './project-references.js';
|
|
|
42
42
|
import { findServiceTsconfig } from './service-config.js';
|
|
43
43
|
import { placeEmittedTree, surfaceModuleInTree } from './outside-root.js';
|
|
44
44
|
import { WriteGuard } from './guarded-fs.js';
|
|
45
|
+
import { sidecarCompilerOptions } from './compiler-options.js';
|
|
45
46
|
export { DenoProject, findDenoConfig } from './deno-project.js';
|
|
47
|
+
export { sidecarCompilerOptions } from './compiler-options.js';
|
|
46
48
|
export { serviceConfigPath } from './project-references.js';
|
|
47
49
|
export { findServiceTsconfig } from './service-config.js';
|
|
48
50
|
// v2 check core ("tsc as the judge"). Same bundle, same seam: the sidecar
|
|
@@ -282,7 +284,7 @@ export function captureStub(opts) {
|
|
|
282
284
|
try {
|
|
283
285
|
progress('emit', 'emitting declarations');
|
|
284
286
|
guard.writeFile(entryPath, entryLines.join('\n') + '\n');
|
|
285
|
-
const emitOptions = {
|
|
287
|
+
const emitOptions = sidecarCompilerOptions({
|
|
286
288
|
...parsed.options,
|
|
287
289
|
// The load-bearing trio: emit declarations without checking, so
|
|
288
290
|
// type-error-laden and bare (no node_modules) checkouts still emit.
|
|
@@ -295,7 +297,7 @@ export function captureStub(opts) {
|
|
|
295
297
|
incremental: false,
|
|
296
298
|
outDir: staging,
|
|
297
299
|
rootDir: entryDir,
|
|
298
|
-
};
|
|
300
|
+
});
|
|
299
301
|
written = emitDeclarations({
|
|
300
302
|
rootNames: [entryPath, ...augmentationSources],
|
|
301
303
|
options: emitOptions,
|
|
@@ -523,7 +525,7 @@ function emitDeclarations(args) {
|
|
|
523
525
|
const emitted = new Map();
|
|
524
526
|
const declarationSources = new Map();
|
|
525
527
|
const sourceByEmitted = new Map();
|
|
526
|
-
const program = ts.createProgram(args.rootNames, args.options, args.host);
|
|
528
|
+
const program = ts.createProgram(args.rootNames, sidecarCompilerOptions(args.options), args.host);
|
|
527
529
|
const emitResult = program.emit(undefined, (fileName, text, _bom, _error, sources) => {
|
|
528
530
|
emitted.set(fileName, text);
|
|
529
531
|
if (sources?.[0])
|
|
@@ -690,10 +692,10 @@ function resolveAnchors(opts, parsed, ctx, deno) {
|
|
|
690
692
|
...new Set(opts.anchors
|
|
691
693
|
.flatMap((a) => (a.source_file ? [path.join(ctx.repoRoot, a.source_file)] : []))),
|
|
692
694
|
].filter((f) => fs.existsSync(f));
|
|
693
|
-
const options = {
|
|
695
|
+
const options = sidecarCompilerOptions({
|
|
694
696
|
...parsed.options,
|
|
695
697
|
noEmit: true,
|
|
696
|
-
};
|
|
698
|
+
});
|
|
697
699
|
const program = ts.createProgram([ctx.entryPath, ...anchorSources, ...(deno?.globals ?? [])], options, deno?.host(options));
|
|
698
700
|
// The checker binds every file of the program when it is first asked for,
|
|
699
701
|
// which the first anchor would otherwise do: asked for here, so that the
|
|
@@ -24,16 +24,17 @@
|
|
|
24
24
|
import ts from 'typescript';
|
|
25
25
|
import * as fs from 'node:fs';
|
|
26
26
|
import * as path from 'node:path';
|
|
27
|
+
import { sidecarCompilerOptions } from './compiler-options.js';
|
|
27
28
|
import { isPublishedSemver } from './lockfile.js';
|
|
28
29
|
const DECLARATION_SUFFIXES = ['', '.d.ts', '.d.mts', '.d.cts', '.ts', '.mts', '.cts', '/index.d.ts'];
|
|
29
|
-
const RESOLUTION_OPTIONS = {
|
|
30
|
+
const RESOLUTION_OPTIONS = sidecarCompilerOptions({
|
|
30
31
|
module: ts.ModuleKind.ESNext,
|
|
31
32
|
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
32
33
|
target: ts.ScriptTarget.ESNext,
|
|
33
34
|
noEmit: true,
|
|
34
35
|
skipLibCheck: true,
|
|
35
36
|
types: [],
|
|
36
|
-
};
|
|
37
|
+
});
|
|
37
38
|
/** DefinitelyTyped packages are imported by the runtime name (TS6137). */
|
|
38
39
|
function publicNameOf(name) {
|
|
39
40
|
if (!name.startsWith('@types/'))
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
import ts from 'typescript';
|
|
33
33
|
import * as fs from 'node:fs';
|
|
34
34
|
import * as path from 'node:path';
|
|
35
|
+
import { sidecarCompilerOptions } from './compiler-options.js';
|
|
35
36
|
import { collectSpecifiers, declarationCandidates, isDeclarationFileName, isRelative, packageNameOf, } from './specifiers.js';
|
|
36
37
|
import { repairDanglingImports } from './repair-dangling.js';
|
|
37
38
|
import { findDisqualifyingTopTypes, findUnresolvedPlaceholders, isErrorPlaceholder, provenanceOf, unemittedModuleProvenance, unresolvedInTreeProvenance, } from './deep-walk.js';
|
|
@@ -78,7 +79,7 @@ export function selfCheckStub(args) {
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
function runSelfCheck(args, treeFiles, repaired) {
|
|
81
|
-
const options = {
|
|
82
|
+
const options = sidecarCompilerOptions({
|
|
82
83
|
noEmit: true,
|
|
83
84
|
strict: true,
|
|
84
85
|
// MUST be false: the whole stub tree is .d.ts (see module header).
|
|
@@ -86,7 +87,7 @@ function runSelfCheck(args, treeFiles, repaired) {
|
|
|
86
87
|
module: ts.ModuleKind.ESNext,
|
|
87
88
|
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
88
89
|
types: [],
|
|
89
|
-
};
|
|
90
|
+
});
|
|
90
91
|
const program = ts.createProgram(treeFiles, options, args.compilerHost?.(options));
|
|
91
92
|
const checker = program.getTypeChecker();
|
|
92
93
|
const diagnostics = ts.getPreEmitDiagnostics(program);
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
import * as path from 'node:path';
|
|
36
36
|
import * as fs from 'node:fs';
|
|
37
37
|
import { Project, Node } from 'ts-morph';
|
|
38
|
+
import { sidecarCompilerOptions } from './capture/index.js';
|
|
38
39
|
import { expandTypeStructural, } from './type-structural-expander.js';
|
|
39
40
|
export class DefinitionResolver {
|
|
40
41
|
/**
|
|
@@ -57,13 +58,13 @@ export class DefinitionResolver {
|
|
|
57
58
|
}
|
|
58
59
|
try {
|
|
59
60
|
const stubProject = new Project({
|
|
60
|
-
compilerOptions: {
|
|
61
|
+
compilerOptions: sidecarCompilerOptions({
|
|
61
62
|
target: 99, // ESNext
|
|
62
63
|
module: 99, // ESNext
|
|
63
64
|
moduleResolution: 100, // Bundler
|
|
64
65
|
strict: true,
|
|
65
66
|
skipLibCheck: true,
|
|
66
|
-
},
|
|
67
|
+
}),
|
|
67
68
|
skipAddingFilesFromTsConfig: true,
|
|
68
69
|
});
|
|
69
70
|
for (const filePath of walkDtsFiles(typesDir)) {
|
|
@@ -28,6 +28,7 @@ import { Retyper } from './retype.js';
|
|
|
28
28
|
import { LibraryClaimsVerifier, httpCheck } from './library-claims.js';
|
|
29
29
|
import { PROGRESS_INTERVAL_MS, atMostEvery } from './progress.js';
|
|
30
30
|
import { mergeInferTimings } from './infer-timing.js';
|
|
31
|
+
import { addProgramFiles, listProgramFiles } from './program-files.js';
|
|
31
32
|
// ===========================================================================
|
|
32
33
|
// Module-level state
|
|
33
34
|
// ===========================================================================
|
|
@@ -46,6 +47,13 @@ let components = new Map();
|
|
|
46
47
|
* (carrick#1927).
|
|
47
48
|
*/
|
|
48
49
|
const definitionResolver = new DefinitionResolver();
|
|
50
|
+
/** The init'd project loader, or the error every project-backed action gives without one. */
|
|
51
|
+
function initializedLoader() {
|
|
52
|
+
if (!projectLoader?.isInitialized()) {
|
|
53
|
+
throw new Error('Sidecar not initialized. Call init first.');
|
|
54
|
+
}
|
|
55
|
+
return projectLoader;
|
|
56
|
+
}
|
|
49
57
|
/**
|
|
50
58
|
* Get the project-backed components, building the project if this is the
|
|
51
59
|
* first request that needs it.
|
|
@@ -53,15 +61,12 @@ const definitionResolver = new DefinitionResolver();
|
|
|
53
61
|
* @throws if init has not run, or if the project cannot be built
|
|
54
62
|
*/
|
|
55
63
|
function projectComponents(key = '') {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
64
|
+
// Bound here rather than read from the module slot inside the components:
|
|
65
|
+
// a re-init drops `components` and points the slot at another service, and
|
|
66
|
+
// nothing built over this project may follow it there.
|
|
67
|
+
const loader = initializedLoader();
|
|
59
68
|
let built = components.get(key);
|
|
60
69
|
if (!built) {
|
|
61
|
-
// Bound here rather than read from the module slot inside the components:
|
|
62
|
-
// a re-init drops `components` and points the slot at another service, and
|
|
63
|
-
// nothing built over this project may follow it there.
|
|
64
|
-
const loader = projectLoader;
|
|
65
70
|
const project = loader.getProjectFor(key);
|
|
66
71
|
const repoRoot = loader.getRepoRoot();
|
|
67
72
|
// The module graph, where the project resolved through one, is the only
|
|
@@ -501,6 +506,55 @@ function handleResolveDefinitions(request) {
|
|
|
501
506
|
};
|
|
502
507
|
}
|
|
503
508
|
}
|
|
509
|
+
/**
|
|
510
|
+
* Handle the 'list_program_files' action - the root files the default
|
|
511
|
+
* project's program was built from, in order (carrick#2027). It is the only
|
|
512
|
+
* program a request grows: a file a referenced project owns is in that
|
|
513
|
+
* project's program from its tsconfig, so a program built for an owning
|
|
514
|
+
* project (carrick#1604) holds the same files in every process. A process
|
|
515
|
+
* that has not built the default project lists nothing, and does not build
|
|
516
|
+
* it to answer.
|
|
517
|
+
*/
|
|
518
|
+
function handleListProgramFiles(request) {
|
|
519
|
+
try {
|
|
520
|
+
const project = initializedLoader().builtProject();
|
|
521
|
+
const files = project ? listProgramFiles(project) : [];
|
|
522
|
+
log(`Listing ${files.length} program file(s)`);
|
|
523
|
+
return { request_id: request.request_id, status: 'success', files };
|
|
524
|
+
}
|
|
525
|
+
catch (err) {
|
|
526
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
527
|
+
logError(`Listing program files failed: ${error}`);
|
|
528
|
+
return { request_id: request.request_id, status: 'error', errors: [error] };
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Handle the 'add_program_files' action - add the given files to the default
|
|
533
|
+
* project's program in the given order, rebuilding it at most once
|
|
534
|
+
* (carrick#2027). A file a referenced project owns is skipped, as an `infer`
|
|
535
|
+
* on it would never add it to this program: its owner's program already holds
|
|
536
|
+
* it.
|
|
537
|
+
*/
|
|
538
|
+
function handleAddProgramFiles(request) {
|
|
539
|
+
try {
|
|
540
|
+
const loader = initializedLoader();
|
|
541
|
+
const startTime = performance.now();
|
|
542
|
+
const files = request.files
|
|
543
|
+
.map((file) => path.resolve(loader.getRepoRoot(), file))
|
|
544
|
+
.filter((file) => loader.projectKeyFor(file) === '');
|
|
545
|
+
const added = files.length === 0
|
|
546
|
+
? 0
|
|
547
|
+
: addProgramFiles(loader.getProject(), files, (file, reason) => logError(`Not added to the program: ${file}: ${reason}`));
|
|
548
|
+
log(`Added ${added} of ${request.files.length} file(s) to the program in ` +
|
|
549
|
+
`${Math.round(performance.now() - startTime)}ms`);
|
|
550
|
+
return { request_id: request.request_id, status: 'success', added };
|
|
551
|
+
}
|
|
552
|
+
catch (err) {
|
|
553
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
554
|
+
logError(`Adding program files failed: ${error}`);
|
|
555
|
+
return { request_id: request.request_id, status: 'error', errors: [error] };
|
|
556
|
+
}
|
|
557
|
+
}
|
|
504
558
|
/**
|
|
505
559
|
* Handle the 'health' action - report initialization status
|
|
506
560
|
*/
|
|
@@ -553,6 +607,10 @@ function handleRequest(request) {
|
|
|
553
607
|
return handleVerifyLibraryClaims(request);
|
|
554
608
|
case 'list_library_surface':
|
|
555
609
|
return handleListLibrarySurface(request);
|
|
610
|
+
case 'list_program_files':
|
|
611
|
+
return handleListProgramFiles(request);
|
|
612
|
+
case 'add_program_files':
|
|
613
|
+
return handleAddProgramFiles(request);
|
|
556
614
|
case 'health':
|
|
557
615
|
return handleHealth(request);
|
|
558
616
|
case 'shutdown':
|
|
@@ -648,6 +706,31 @@ function processLine(line) {
|
|
|
648
706
|
const response = handleRequest(parseResult.request);
|
|
649
707
|
writeResponse(response);
|
|
650
708
|
}
|
|
709
|
+
/** How often the sidecar looks for the process that started it. */
|
|
710
|
+
const PARENT_CHECK_MS = 1000;
|
|
711
|
+
/**
|
|
712
|
+
* Exit once the process that started this one is gone (carrick#2029).
|
|
713
|
+
*
|
|
714
|
+
* Its death closes stdin, which ends this process, unless another process
|
|
715
|
+
* still holds the write end. Then the only sign is that this process has a
|
|
716
|
+
* new parent: whatever adopts it (init, or a subreaper on Linux) has a pid of
|
|
717
|
+
* its own. So the parent at startup is kept and compared, never 1. A process
|
|
718
|
+
* already adopted at startup has no parent to watch, and stdin alone decides.
|
|
719
|
+
*
|
|
720
|
+
* The timer is unref'd: it never keeps an otherwise finished process alive.
|
|
721
|
+
* A handler that blocks the event loop holds it off until it returns.
|
|
722
|
+
*/
|
|
723
|
+
function exitWhenParentIsGone() {
|
|
724
|
+
const startedBy = process.ppid;
|
|
725
|
+
if (startedBy <= 1)
|
|
726
|
+
return;
|
|
727
|
+
setInterval(() => {
|
|
728
|
+
if (process.ppid !== startedBy) {
|
|
729
|
+
log(`parent ${startedBy} is gone, exiting`);
|
|
730
|
+
process.exit(0);
|
|
731
|
+
}
|
|
732
|
+
}, PARENT_CHECK_MS).unref();
|
|
733
|
+
}
|
|
651
734
|
/**
|
|
652
735
|
* Start the message loop
|
|
653
736
|
*/
|
|
@@ -663,6 +746,7 @@ function main() {
|
|
|
663
746
|
log('stdin closed, exiting');
|
|
664
747
|
process.exit(0);
|
|
665
748
|
});
|
|
749
|
+
exitWhenParentIsGone();
|
|
666
750
|
// Handle process signals
|
|
667
751
|
process.on('SIGINT', () => {
|
|
668
752
|
log('SIGINT received, exiting');
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The compiler's own print of a type, with a module the print names written
|
|
3
|
+
* as the file that declares it (carrick#2019).
|
|
4
|
+
*
|
|
5
|
+
* When a type comes from a module the enclosing file does not import, the
|
|
6
|
+
* compiler writes it as `import("<module>").Name`. TypeScript 5 wrote the
|
|
7
|
+
* declaring file's path there: the node builder had no module-specifier host
|
|
8
|
+
* unless a declaration emit gave it one. TypeScript 6 gives every print a
|
|
9
|
+
* host, so the same type printed at an enclosing node reads
|
|
10
|
+
* `import("../model").Name` or `import("<package>").Name`: a specifier that
|
|
11
|
+
* depends on which file it was printed from, so one type printed from two
|
|
12
|
+
* files gives two texts. Everything downstream reads the declaring file's
|
|
13
|
+
* path: the scanner's path scrub writes it repo-relative or as
|
|
14
|
+
* `<package>@<version>`, and a capture pastes an inferred text into its
|
|
15
|
+
* surface entry, where a specifier relative to another file names nothing.
|
|
16
|
+
*
|
|
17
|
+
* These functions run the compiler's own code with no host: a tracker whose
|
|
18
|
+
* `moduleResolverHost` is `false` (falsy, so the builder writes the declaring
|
|
19
|
+
* file; not nullish, so the builder does not supply its own). Nothing in the
|
|
20
|
+
* text is rewritten afterwards. `typeToStringAsDeclared` is the checker's
|
|
21
|
+
* `typeToString`, step for step, with that one tracker.
|
|
22
|
+
*
|
|
23
|
+
* Only prints with an enclosing node differ: with none, the compiler has no
|
|
24
|
+
* file to write a specifier relative to and writes the declaring file
|
|
25
|
+
* anyway. A print that must name modules the way a declaration file would
|
|
26
|
+
* (the capture's surface emit) passes a host of its own and does not use
|
|
27
|
+
* this.
|
|
28
|
+
*/
|
|
29
|
+
import { ts } from 'ts-morph';
|
|
30
|
+
/**
|
|
31
|
+
* The compiler's internal `SymbolTracker`: callbacks the node builder reports
|
|
32
|
+
* to, and the module-specifier host it writes specifiers with.
|
|
33
|
+
*/
|
|
34
|
+
export interface SymbolTracker {
|
|
35
|
+
moduleResolverHost?: unknown;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The checker's `typeToTypeNode`, writing each module a node names as the
|
|
39
|
+
* file that declares it. A caller's own tracker keeps its callbacks.
|
|
40
|
+
*/
|
|
41
|
+
export declare function typeToTypeNodeAsDeclared(checker: ts.TypeChecker, type: ts.Type, enclosing: ts.Node | undefined, flags: ts.NodeBuilderFlags, tracker?: SymbolTracker): ts.TypeNode | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* The checker's `typeToString(type, enclosing, flags)`, writing each module
|
|
44
|
+
* the print names as the file that declares it. `noErrorTruncation` is the
|
|
45
|
+
* program's option of that name, which `typeToString` reads when `flags`
|
|
46
|
+
* leave truncation on.
|
|
47
|
+
*/
|
|
48
|
+
export declare function typeToStringAsDeclared(checker: ts.TypeChecker, type: ts.Type, enclosing: ts.Node | undefined, flags?: ts.TypeFormatFlags, noErrorTruncation?: boolean): string;
|