carrick 0.3.101 → 0.3.103
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 +6 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/sidecar/dist/src/bundler.d.ts +3 -92
- package/sidecar/dist/src/bundler.js +5 -265
- package/sidecar/dist/src/capture/anchors.js +113 -8
- package/sidecar/dist/src/capture/check-fields.js +19 -5
- package/sidecar/dist/src/capture/check-probe.d.ts +5 -0
- package/sidecar/dist/src/capture/check-probe.js +31 -2
- package/sidecar/dist/src/capture/check-workspace.d.ts +3 -0
- package/sidecar/dist/src/capture/check-workspace.js +13 -16
- package/sidecar/dist/src/capture/check.js +5 -5
- package/sidecar/dist/src/capture/deep-walk.js +45 -12
- package/sidecar/dist/src/capture/deno-project.d.ts +2 -1
- package/sidecar/dist/src/capture/deno-project.js +22 -18
- package/sidecar/dist/src/capture/guarded-fs.d.ts +76 -0
- package/sidecar/dist/src/capture/guarded-fs.js +182 -0
- package/sidecar/dist/src/capture/index.js +60 -26
- package/sidecar/dist/src/capture/outside-root.d.ts +41 -0
- package/sidecar/dist/src/capture/outside-root.js +101 -0
- package/sidecar/dist/src/capture/paths-rewrite.d.ts +8 -0
- package/sidecar/dist/src/capture/paths-rewrite.js +9 -7
- package/sidecar/dist/src/capture/repair-dangling.d.ts +11 -1
- package/sidecar/dist/src/capture/repair-dangling.js +15 -4
- package/sidecar/dist/src/capture/self-check.d.ts +3 -0
- package/sidecar/dist/src/capture/self-check.js +3 -3
- package/sidecar/dist/src/index.d.ts +1 -1
- package/sidecar/dist/src/index.js +2 -115
- package/sidecar/dist/src/origin.d.ts +49 -8
- package/sidecar/dist/src/origin.js +81 -8
- package/sidecar/dist/src/project-loader.js +15 -5
- package/sidecar/dist/src/type-inferrer.d.ts +72 -0
- package/sidecar/dist/src/type-inferrer.js +248 -8
- package/sidecar/dist/src/type-structural-expander.d.ts +5 -1
- package/sidecar/dist/src/type-structural-expander.js +1 -1
- package/sidecar/dist/src/types.d.ts +31 -178
- package/sidecar/dist/src/validators.d.ts +58 -914
- package/sidecar/dist/src/validators.js +0 -55
- package/sidecar/dist/src/monorepo-builder.d.ts +0 -129
- package/sidecar/dist/src/monorepo-builder.js +0 -584
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "carrick",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.103",
|
|
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",
|
|
@@ -58,11 +58,11 @@
|
|
|
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.103",
|
|
62
|
+
"@carrick-tools/cli-darwin-x64": "0.3.103",
|
|
63
|
+
"@carrick-tools/cli-linux-arm64": "0.3.103",
|
|
64
|
+
"@carrick-tools/cli-linux-x64": "0.3.103",
|
|
65
|
+
"@carrick-tools/cli-win32-x64": "0.3.103"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@types/node": "^24.13.3",
|
|
@@ -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.103",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Carrick",
|
|
7
7
|
"email": "hello@carrick.tools"
|
|
@@ -1,27 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* This module replaces the previous dts-bundle-generator approach with a
|
|
5
|
-
* ts-morph-based AST transformation and emission pipeline.
|
|
6
|
-
*
|
|
7
|
-
* Key features:
|
|
8
|
-
* - Does NOT bundle dependencies (unlike dts-bundle-generator)
|
|
9
|
-
* - Rewrites module specifiers to repo-specific aliases (@carrick/{repoName}/{spec})
|
|
10
|
-
* - Handles ImportDeclaration, ImportTypeNode, and ExportDeclaration
|
|
11
|
-
* - Supports scoped packages and subpaths
|
|
12
|
-
* - Includes a safety pass for any compiler-introduced raw specifiers
|
|
2
|
+
* TypeBundler - the `bundle` action: each requested symbol's declaration text,
|
|
3
|
+
* read from the init'd ts-morph project.
|
|
13
4
|
*/
|
|
14
5
|
import { Project } from 'ts-morph';
|
|
15
|
-
import type {
|
|
16
|
-
/**
|
|
17
|
-
* Options for SurfaceEmitter construction
|
|
18
|
-
*/
|
|
19
|
-
export interface SurfaceEmitterOptions {
|
|
20
|
-
/** The ts-morph Project instance */
|
|
21
|
-
project: Project;
|
|
22
|
-
/** The repository root directory (absolute path) */
|
|
23
|
-
repoRoot: string;
|
|
24
|
-
}
|
|
6
|
+
import type { SymbolRequest, BundleResult } from './types.js';
|
|
25
7
|
/**
|
|
26
8
|
* Options for TypeBundler construction (legacy)
|
|
27
9
|
*/
|
|
@@ -31,82 +13,11 @@ export interface TypeBundlerOptions {
|
|
|
31
13
|
/** The repository root directory (absolute path) */
|
|
32
14
|
repoRoot: string;
|
|
33
15
|
}
|
|
34
|
-
/**
|
|
35
|
-
* SurfaceEmitter - Generates .d.ts surface files with rewritten imports
|
|
36
|
-
*
|
|
37
|
-
* Usage:
|
|
38
|
-
* const emitter = new SurfaceEmitter({ project, repoRoot });
|
|
39
|
-
* const result = emitter.emit(repoName, payloads, outputPath);
|
|
40
|
-
*/
|
|
41
|
-
export declare class SurfaceEmitter {
|
|
42
|
-
private readonly project;
|
|
43
|
-
private readonly repoRoot;
|
|
44
|
-
constructor(options: SurfaceEmitterOptions);
|
|
45
|
-
/**
|
|
46
|
-
* Emit a surface .d.ts file containing payload type declarations
|
|
47
|
-
*
|
|
48
|
-
* @param repoName - Repository name for specifier rewriting
|
|
49
|
-
* @param payloads - Payload definitions to include
|
|
50
|
-
* @param outputPath - Path to write the surface file
|
|
51
|
-
* @returns SurfaceEmitResult
|
|
52
|
-
*/
|
|
53
|
-
emit(repoName: string, payloads: PayloadDefinition[], outputPath: string): SurfaceEmitResult;
|
|
54
|
-
/**
|
|
55
|
-
* Extract potential import references from a type string.
|
|
56
|
-
* These are module specifiers that might need rewriting.
|
|
57
|
-
*/
|
|
58
|
-
private extractImportReferences;
|
|
59
|
-
/**
|
|
60
|
-
* Rewrite all module specifiers in a source file to use repo-scoped aliases.
|
|
61
|
-
*
|
|
62
|
-
* Targets:
|
|
63
|
-
* 1. ImportDeclaration.moduleSpecifier
|
|
64
|
-
* 2. ImportTypeNode argument (import("..."))
|
|
65
|
-
* 3. ExportDeclaration.moduleSpecifier
|
|
66
|
-
*
|
|
67
|
-
* @returns Array of rewritten specifiers
|
|
68
|
-
*/
|
|
69
|
-
private rewriteModuleSpecifiers;
|
|
70
|
-
/**
|
|
71
|
-
* Determine if a module specifier should be rewritten.
|
|
72
|
-
* Only non-relative specifiers should be rewritten.
|
|
73
|
-
*/
|
|
74
|
-
private shouldRewriteSpecifier;
|
|
75
|
-
/**
|
|
76
|
-
* Rewrite a module specifier to the repo-scoped format.
|
|
77
|
-
*
|
|
78
|
-
* Format: @carrick/{repoName}/{originalSpecifier}
|
|
79
|
-
*
|
|
80
|
-
* Preserves:
|
|
81
|
-
* - Scoped packages: "@scope/pkg" → "@carrick/repo-a/@scope/pkg"
|
|
82
|
-
* - Subpaths: "zod/lib/helpers" → "@carrick/repo-a/zod/lib/helpers"
|
|
83
|
-
* - Combined: "@scope/pkg/subpath" → "@carrick/repo-a/@scope/pkg/subpath"
|
|
84
|
-
*/
|
|
85
|
-
private rewriteSpecifier;
|
|
86
|
-
/**
|
|
87
|
-
* Safety pass: Scan emitted content for any raw specifiers that might have
|
|
88
|
-
* been introduced by the TypeScript compiler and rewrite them.
|
|
89
|
-
*
|
|
90
|
-
* This catches edge cases where the compiler emits types with their original
|
|
91
|
-
* module specifiers that weren't caught by AST traversal.
|
|
92
|
-
*/
|
|
93
|
-
private safetyRewritePass;
|
|
94
|
-
/**
|
|
95
|
-
* Log a message to stderr
|
|
96
|
-
*/
|
|
97
|
-
private log;
|
|
98
|
-
/**
|
|
99
|
-
* Log an error to stderr
|
|
100
|
-
*/
|
|
101
|
-
private logError;
|
|
102
|
-
}
|
|
103
16
|
/**
|
|
104
17
|
* TypeBundler - Legacy bundler using simpler approach
|
|
105
18
|
*
|
|
106
19
|
* This is a simplified replacement that doesn't use dts-bundle-generator.
|
|
107
20
|
* It extracts type definitions directly using ts-morph.
|
|
108
|
-
*
|
|
109
|
-
* @deprecated Use SurfaceEmitter instead for the new architecture
|
|
110
21
|
*/
|
|
111
22
|
export declare class TypeBundler {
|
|
112
23
|
private readonly project;
|
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* This module replaces the previous dts-bundle-generator approach with a
|
|
5
|
-
* ts-morph-based AST transformation and emission pipeline.
|
|
6
|
-
*
|
|
7
|
-
* Key features:
|
|
8
|
-
* - Does NOT bundle dependencies (unlike dts-bundle-generator)
|
|
9
|
-
* - Rewrites module specifiers to repo-specific aliases (@carrick/{repoName}/{spec})
|
|
10
|
-
* - Handles ImportDeclaration, ImportTypeNode, and ExportDeclaration
|
|
11
|
-
* - Supports scoped packages and subpaths
|
|
12
|
-
* - Includes a safety pass for any compiler-introduced raw specifiers
|
|
2
|
+
* TypeBundler - the `bundle` action: each requested symbol's declaration text,
|
|
3
|
+
* read from the init'd ts-morph project.
|
|
13
4
|
*/
|
|
14
5
|
import { Node, ts, } from 'ts-morph';
|
|
15
6
|
import * as path from 'node:path';
|
|
16
7
|
import * as fs from 'node:fs';
|
|
17
8
|
import { expandTypeStructural, } from './type-structural-expander.js';
|
|
9
|
+
import { externalImportsOf } from './origin.js';
|
|
18
10
|
/**
|
|
19
11
|
* #248: upper bound on `SymbolRequest.array_depth`. SDL list nesting is
|
|
20
12
|
* realistically 1-3 levels (`[Order!]!`, at most `[[Order!]!]!`); this is a
|
|
@@ -116,267 +108,14 @@ function declarationSite(sourceFile, name, label) {
|
|
|
116
108
|
}
|
|
117
109
|
return { reason: `Symbol '${name}' not found in ${label}` };
|
|
118
110
|
}
|
|
119
|
-
/**
|
|
120
|
-
* SurfaceEmitter - Generates .d.ts surface files with rewritten imports
|
|
121
|
-
*
|
|
122
|
-
* Usage:
|
|
123
|
-
* const emitter = new SurfaceEmitter({ project, repoRoot });
|
|
124
|
-
* const result = emitter.emit(repoName, payloads, outputPath);
|
|
125
|
-
*/
|
|
126
|
-
export class SurfaceEmitter {
|
|
127
|
-
project;
|
|
128
|
-
repoRoot;
|
|
129
|
-
constructor(options) {
|
|
130
|
-
this.project = options.project;
|
|
131
|
-
this.repoRoot = options.repoRoot;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Emit a surface .d.ts file containing payload type declarations
|
|
135
|
-
*
|
|
136
|
-
* @param repoName - Repository name for specifier rewriting
|
|
137
|
-
* @param payloads - Payload definitions to include
|
|
138
|
-
* @param outputPath - Path to write the surface file
|
|
139
|
-
* @returns SurfaceEmitResult
|
|
140
|
-
*/
|
|
141
|
-
emit(repoName, payloads, outputPath) {
|
|
142
|
-
const errors = [];
|
|
143
|
-
const manifest = [];
|
|
144
|
-
try {
|
|
145
|
-
// Phase 1: Generate surface content as a string
|
|
146
|
-
const surfaceLines = [
|
|
147
|
-
'// Generated surface file for type compatibility checking',
|
|
148
|
-
'// Do not edit - this file is auto-generated by Carrick',
|
|
149
|
-
'',
|
|
150
|
-
];
|
|
151
|
-
// Collect all import specifiers we need to track for rewriting
|
|
152
|
-
const importSpecifiers = new Set();
|
|
153
|
-
// Phase 2: Add payload type declarations
|
|
154
|
-
for (const payload of payloads) {
|
|
155
|
-
const { alias, type_string, source_file } = payload;
|
|
156
|
-
// Analyze type string for import references
|
|
157
|
-
const detectedImports = this.extractImportReferences(type_string);
|
|
158
|
-
for (const imp of detectedImports) {
|
|
159
|
-
importSpecifiers.add(imp);
|
|
160
|
-
}
|
|
161
|
-
// Add type alias declaration
|
|
162
|
-
surfaceLines.push(`export type ${alias} = ${type_string};`);
|
|
163
|
-
surfaceLines.push('');
|
|
164
|
-
manifest.push({
|
|
165
|
-
alias,
|
|
166
|
-
type_string,
|
|
167
|
-
rewritten_imports: [],
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
// Phase 3: Create a temporary source file for AST manipulation
|
|
171
|
-
const tempFileName = `__carrick_surface_${repoName}_${Date.now()}.d.ts`;
|
|
172
|
-
const tempFilePath = path.join(this.repoRoot, tempFileName);
|
|
173
|
-
const initialContent = surfaceLines.join('\n');
|
|
174
|
-
const tempSourceFile = this.project.createSourceFile(tempFilePath, initialContent, { overwrite: true });
|
|
175
|
-
// Phase 4: Rewrite module specifiers in the AST
|
|
176
|
-
const rewrittenImports = this.rewriteModuleSpecifiers(tempSourceFile, repoName);
|
|
177
|
-
// Update manifest with rewritten imports
|
|
178
|
-
for (const entry of manifest) {
|
|
179
|
-
entry.rewritten_imports = rewrittenImports;
|
|
180
|
-
}
|
|
181
|
-
// Phase 5: Get the transformed content
|
|
182
|
-
let surfaceContent = tempSourceFile.getFullText();
|
|
183
|
-
// Phase 6: Safety pass - catch any raw specifiers the compiler might have added
|
|
184
|
-
surfaceContent = this.safetyRewritePass(surfaceContent, repoName);
|
|
185
|
-
// Phase 7: Clean up temporary source file from project
|
|
186
|
-
this.project.removeSourceFile(tempSourceFile);
|
|
187
|
-
// Phase 8: Write output file
|
|
188
|
-
const absoluteOutputPath = path.isAbsolute(outputPath)
|
|
189
|
-
? outputPath
|
|
190
|
-
: path.join(this.repoRoot, outputPath);
|
|
191
|
-
// Ensure directory exists
|
|
192
|
-
const outputDir = path.dirname(absoluteOutputPath);
|
|
193
|
-
if (!fs.existsSync(outputDir)) {
|
|
194
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
195
|
-
}
|
|
196
|
-
fs.writeFileSync(absoluteOutputPath, surfaceContent, 'utf-8');
|
|
197
|
-
this.log(`Emitted surface file: ${absoluteOutputPath}`);
|
|
198
|
-
return {
|
|
199
|
-
success: true,
|
|
200
|
-
surface_content: surfaceContent,
|
|
201
|
-
output_path: absoluteOutputPath,
|
|
202
|
-
manifest,
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
catch (err) {
|
|
206
|
-
const error = err instanceof Error ? err.message : String(err);
|
|
207
|
-
this.logError(`Surface emission failed: ${error}`);
|
|
208
|
-
errors.push(error);
|
|
209
|
-
return {
|
|
210
|
-
success: false,
|
|
211
|
-
errors,
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Extract potential import references from a type string.
|
|
217
|
-
* These are module specifiers that might need rewriting.
|
|
218
|
-
*/
|
|
219
|
-
extractImportReferences(typeString) {
|
|
220
|
-
const imports = [];
|
|
221
|
-
// Match import("...") syntax
|
|
222
|
-
const importTypeRegex = /import\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
223
|
-
let match;
|
|
224
|
-
while ((match = importTypeRegex.exec(typeString)) !== null) {
|
|
225
|
-
imports.push(match[1]);
|
|
226
|
-
}
|
|
227
|
-
// Match from "..." syntax (in case of inline imports)
|
|
228
|
-
const fromRegex = /from\s+["']([^"']+)["']/g;
|
|
229
|
-
while ((match = fromRegex.exec(typeString)) !== null) {
|
|
230
|
-
imports.push(match[1]);
|
|
231
|
-
}
|
|
232
|
-
return imports;
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Rewrite all module specifiers in a source file to use repo-scoped aliases.
|
|
236
|
-
*
|
|
237
|
-
* Targets:
|
|
238
|
-
* 1. ImportDeclaration.moduleSpecifier
|
|
239
|
-
* 2. ImportTypeNode argument (import("..."))
|
|
240
|
-
* 3. ExportDeclaration.moduleSpecifier
|
|
241
|
-
*
|
|
242
|
-
* @returns Array of rewritten specifiers
|
|
243
|
-
*/
|
|
244
|
-
rewriteModuleSpecifiers(sourceFile, repoName) {
|
|
245
|
-
const rewrittenSpecifiers = [];
|
|
246
|
-
// 1. Rewrite ImportDeclaration specifiers
|
|
247
|
-
const importDecls = sourceFile.getImportDeclarations();
|
|
248
|
-
for (const importDecl of importDecls) {
|
|
249
|
-
const specifier = importDecl.getModuleSpecifierValue();
|
|
250
|
-
if (this.shouldRewriteSpecifier(specifier)) {
|
|
251
|
-
const newSpecifier = this.rewriteSpecifier(specifier, repoName);
|
|
252
|
-
importDecl.setModuleSpecifier(newSpecifier);
|
|
253
|
-
rewrittenSpecifiers.push(`${specifier} → ${newSpecifier}`);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
// 2. Rewrite ExportDeclaration specifiers
|
|
257
|
-
const exportDecls = sourceFile.getExportDeclarations();
|
|
258
|
-
for (const exportDecl of exportDecls) {
|
|
259
|
-
const specifier = exportDecl.getModuleSpecifierValue();
|
|
260
|
-
if (specifier && this.shouldRewriteSpecifier(specifier)) {
|
|
261
|
-
const newSpecifier = this.rewriteSpecifier(specifier, repoName);
|
|
262
|
-
exportDecl.setModuleSpecifier(newSpecifier);
|
|
263
|
-
rewrittenSpecifiers.push(`${specifier} → ${newSpecifier}`);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
// 3. Rewrite ImportTypeNode arguments
|
|
267
|
-
// These appear in type positions: import("module").Type
|
|
268
|
-
sourceFile.forEachDescendant((node) => {
|
|
269
|
-
if (Node.isImportTypeNode(node)) {
|
|
270
|
-
const arg = node.getArgument();
|
|
271
|
-
if (Node.isLiteralTypeNode(arg)) {
|
|
272
|
-
const literal = arg.getLiteral();
|
|
273
|
-
if (Node.isStringLiteral(literal)) {
|
|
274
|
-
const specifier = literal.getLiteralValue();
|
|
275
|
-
if (this.shouldRewriteSpecifier(specifier)) {
|
|
276
|
-
const newSpecifier = this.rewriteSpecifier(specifier, repoName);
|
|
277
|
-
literal.setLiteralValue(newSpecifier);
|
|
278
|
-
rewrittenSpecifiers.push(`${specifier} → ${newSpecifier}`);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
return rewrittenSpecifiers;
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* Determine if a module specifier should be rewritten.
|
|
288
|
-
* Only non-relative specifiers should be rewritten.
|
|
289
|
-
*/
|
|
290
|
-
shouldRewriteSpecifier(specifier) {
|
|
291
|
-
// Don't rewrite relative imports
|
|
292
|
-
if (specifier.startsWith('./') || specifier.startsWith('../')) {
|
|
293
|
-
return false;
|
|
294
|
-
}
|
|
295
|
-
// Don't rewrite already-rewritten specifiers
|
|
296
|
-
if (specifier.startsWith('@carrick/')) {
|
|
297
|
-
return false;
|
|
298
|
-
}
|
|
299
|
-
// Don't rewrite node built-ins
|
|
300
|
-
if (specifier.startsWith('node:')) {
|
|
301
|
-
return false;
|
|
302
|
-
}
|
|
303
|
-
// Some common node built-ins without prefix
|
|
304
|
-
const nodeBuiltins = [
|
|
305
|
-
'fs', 'path', 'os', 'util', 'events', 'stream', 'http', 'https',
|
|
306
|
-
'url', 'querystring', 'crypto', 'buffer', 'child_process', 'cluster',
|
|
307
|
-
'dgram', 'dns', 'net', 'readline', 'tls', 'tty', 'zlib', 'assert',
|
|
308
|
-
'async_hooks', 'console', 'constants', 'domain', 'inspector', 'module',
|
|
309
|
-
'perf_hooks', 'process', 'punycode', 'string_decoder', 'timers',
|
|
310
|
-
'trace_events', 'v8', 'vm', 'worker_threads'
|
|
311
|
-
];
|
|
312
|
-
if (nodeBuiltins.includes(specifier) || nodeBuiltins.includes(specifier.split('/')[0])) {
|
|
313
|
-
return false;
|
|
314
|
-
}
|
|
315
|
-
return true;
|
|
316
|
-
}
|
|
317
|
-
/**
|
|
318
|
-
* Rewrite a module specifier to the repo-scoped format.
|
|
319
|
-
*
|
|
320
|
-
* Format: @carrick/{repoName}/{originalSpecifier}
|
|
321
|
-
*
|
|
322
|
-
* Preserves:
|
|
323
|
-
* - Scoped packages: "@scope/pkg" → "@carrick/repo-a/@scope/pkg"
|
|
324
|
-
* - Subpaths: "zod/lib/helpers" → "@carrick/repo-a/zod/lib/helpers"
|
|
325
|
-
* - Combined: "@scope/pkg/subpath" → "@carrick/repo-a/@scope/pkg/subpath"
|
|
326
|
-
*/
|
|
327
|
-
rewriteSpecifier(specifier, repoName) {
|
|
328
|
-
return `@carrick/${repoName}/${specifier}`;
|
|
329
|
-
}
|
|
330
|
-
/**
|
|
331
|
-
* Safety pass: Scan emitted content for any raw specifiers that might have
|
|
332
|
-
* been introduced by the TypeScript compiler and rewrite them.
|
|
333
|
-
*
|
|
334
|
-
* This catches edge cases where the compiler emits types with their original
|
|
335
|
-
* module specifiers that weren't caught by AST traversal.
|
|
336
|
-
*/
|
|
337
|
-
safetyRewritePass(content, repoName) {
|
|
338
|
-
// Common patterns to catch:
|
|
339
|
-
// - from "package"
|
|
340
|
-
// - import("package")
|
|
341
|
-
// - export * from "package"
|
|
342
|
-
// We need to be careful not to double-rewrite @carrick/ specifiers
|
|
343
|
-
// Pattern for import("...") that's not already @carrick
|
|
344
|
-
const importTypePattern = /import\s*\(\s*["'](?!@carrick\/|\.\.?\/|node:)([^"']+)["']\s*\)/g;
|
|
345
|
-
content = content.replace(importTypePattern, (match, specifier) => {
|
|
346
|
-
const newSpecifier = this.rewriteSpecifier(specifier, repoName);
|
|
347
|
-
return match.replace(`"${specifier}"`, `"${newSpecifier}"`).replace(`'${specifier}'`, `'${newSpecifier}'`);
|
|
348
|
-
});
|
|
349
|
-
// Pattern for from "..." that's not already @carrick
|
|
350
|
-
const fromPattern = /from\s+["'](?!@carrick\/|\.\.?\/|node:)([^"']+)["']/g;
|
|
351
|
-
content = content.replace(fromPattern, (match, specifier) => {
|
|
352
|
-
const newSpecifier = this.rewriteSpecifier(specifier, repoName);
|
|
353
|
-
return match.replace(`"${specifier}"`, `"${newSpecifier}"`).replace(`'${specifier}'`, `'${newSpecifier}'`);
|
|
354
|
-
});
|
|
355
|
-
return content;
|
|
356
|
-
}
|
|
357
|
-
/**
|
|
358
|
-
* Log a message to stderr
|
|
359
|
-
*/
|
|
360
|
-
log(message) {
|
|
361
|
-
console.error(`[sidecar:surface-emitter] ${message}`);
|
|
362
|
-
}
|
|
363
|
-
/**
|
|
364
|
-
* Log an error to stderr
|
|
365
|
-
*/
|
|
366
|
-
logError(message) {
|
|
367
|
-
console.error(`[sidecar:surface-emitter:error] ${message}`);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
111
|
// =============================================================================
|
|
371
|
-
//
|
|
112
|
+
// TypeBundler
|
|
372
113
|
// =============================================================================
|
|
373
114
|
/**
|
|
374
115
|
* TypeBundler - Legacy bundler using simpler approach
|
|
375
116
|
*
|
|
376
117
|
* This is a simplified replacement that doesn't use dts-bundle-generator.
|
|
377
118
|
* It extracts type definitions directly using ts-morph.
|
|
378
|
-
*
|
|
379
|
-
* @deprecated Use SurfaceEmitter instead for the new architecture
|
|
380
119
|
*/
|
|
381
120
|
export class TypeBundler {
|
|
382
121
|
project;
|
|
@@ -558,6 +297,7 @@ export class TypeBundler {
|
|
|
558
297
|
return {
|
|
559
298
|
program: this.project.getProgram().compilerObject,
|
|
560
299
|
repoRoot: this.repoRoot,
|
|
300
|
+
imports: externalImportsOf(this.project),
|
|
561
301
|
};
|
|
562
302
|
}
|
|
563
303
|
extractTypeDefinition(symbol) {
|
|
@@ -54,13 +54,20 @@ export function resolveAnchor(program, request, args) {
|
|
|
54
54
|
// (a generated model that was never generated). The stub then self-checks
|
|
55
55
|
// such a name as an error placeholder, which no walk flags. A name a
|
|
56
56
|
// sibling symbol anchor imports is resolved by that import.
|
|
57
|
+
// carrick#1774: the text names types as they read where it was printed,
|
|
58
|
+
// so a name its source file means by a repo module's export is imported
|
|
59
|
+
// from that module (a string-union alias printed bare read `any`).
|
|
60
|
+
const scoped = siblingSpec
|
|
61
|
+
? undefined
|
|
62
|
+
: qualifyNamesFromSource(text, program, request.source_file, args);
|
|
63
|
+
const scopedText = scoped ?? text;
|
|
57
64
|
// carrick#1377: rewrite what nothing declares to `unknown` in place, so
|
|
58
65
|
// one member typed by a module the checkout does not have stops taking
|
|
59
66
|
// every member around it down with it.
|
|
60
67
|
const rewritten = siblingSpec || !args.placeholder
|
|
61
68
|
? undefined
|
|
62
|
-
: substituteUndeclaredNamesInText(
|
|
63
|
-
const aliasBody = rewritten?.text ??
|
|
69
|
+
: substituteUndeclaredNamesInText(scopedText, program, args.placeholder);
|
|
70
|
+
const aliasBody = rewritten?.text ?? scopedText;
|
|
64
71
|
const undeclaredNames = siblingSpec || !args.placeholder
|
|
65
72
|
? []
|
|
66
73
|
: undeclaredNamesInText(aliasBody, program, args.placeholder);
|
|
@@ -349,6 +356,95 @@ function substituteUndeclaredNamesInText(text, program, destination) {
|
|
|
349
356
|
paths: rewritten.substitutions.map((entry) => entry.path),
|
|
350
357
|
};
|
|
351
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* carrick#1774: literal text with every type name read where it was printed.
|
|
361
|
+
*
|
|
362
|
+
* The text was printed in `sourceFileRel` (the v1 inferrer prints at the node,
|
|
363
|
+
* where the module's own declarations and imports are in scope), so a type
|
|
364
|
+
* reference in it means what its leftmost name resolves to in that file. The
|
|
365
|
+
* surface entry is another scope: there such a name names nothing (TS2304, a
|
|
366
|
+
* member that reads `any`) or a global of the same name (`Notification`).
|
|
367
|
+
* Each reference whose name the source resolves to a type that a module
|
|
368
|
+
* inside the repo exports becomes `import('<module>').<export>`, with its
|
|
369
|
+
* qualifier and type arguments kept. Anything else is left as written: a
|
|
370
|
+
* global, a name only a function body declares, an unexported local, and a
|
|
371
|
+
* type declared outside the repo or under `node_modules`, which the stub does
|
|
372
|
+
* not ship.
|
|
373
|
+
*
|
|
374
|
+
* Returns the rewritten text, or undefined when nothing was rewritten, so a
|
|
375
|
+
* text with no such name stays byte-identical.
|
|
376
|
+
*/
|
|
377
|
+
function qualifyNamesFromSource(text, program, sourceFileRel, args) {
|
|
378
|
+
if (!sourceFileRel)
|
|
379
|
+
return undefined;
|
|
380
|
+
const source = program.getSourceFile(path.join(args.repoRoot, sourceFileRel));
|
|
381
|
+
const parsed = parseLiteralAnchor(text);
|
|
382
|
+
if (!source || !parsed)
|
|
383
|
+
return undefined;
|
|
384
|
+
const checker = program.getTypeChecker();
|
|
385
|
+
const meaning = ts.SymbolFlags.Type | ts.SymbolFlags.Namespace | ts.SymbolFlags.Alias;
|
|
386
|
+
// Names the text declares itself (`[K in ...]`, `<T>(...)`, `infer U`).
|
|
387
|
+
const typeParameters = new Set();
|
|
388
|
+
const collect = (node) => {
|
|
389
|
+
if (ts.isTypeParameterDeclaration(node))
|
|
390
|
+
typeParameters.add(node.name.text);
|
|
391
|
+
ts.forEachChild(node, collect);
|
|
392
|
+
};
|
|
393
|
+
collect(parsed);
|
|
394
|
+
const imports = new Map();
|
|
395
|
+
const importFor = (name) => {
|
|
396
|
+
if (imports.has(name))
|
|
397
|
+
return imports.get(name);
|
|
398
|
+
let found;
|
|
399
|
+
const atSource = checker.resolveName(name, source, meaning, false);
|
|
400
|
+
const target = atSource && resolveSymbolAliases(checker, atSource);
|
|
401
|
+
const declaringFile = target?.declarations?.[0]?.getSourceFile();
|
|
402
|
+
const rel = declaringFile && path.relative(args.repoRoot, declaringFile.fileName);
|
|
403
|
+
if (target &&
|
|
404
|
+
declaringFile &&
|
|
405
|
+
rel &&
|
|
406
|
+
!rel.startsWith('..') &&
|
|
407
|
+
!rel.split(path.sep).includes('node_modules')) {
|
|
408
|
+
const moduleSymbol = checker.getSymbolAtLocation(declaringFile);
|
|
409
|
+
const exported = moduleSymbol
|
|
410
|
+
? checker
|
|
411
|
+
.getExportsOfModule(moduleSymbol)
|
|
412
|
+
.filter((candidate) => resolveSymbolAliases(checker, candidate) === target)
|
|
413
|
+
: [];
|
|
414
|
+
const chosen = exported.find((candidate) => candidate.getName() === name) ?? exported[0];
|
|
415
|
+
if (chosen) {
|
|
416
|
+
found = {
|
|
417
|
+
spec: entryRelativeSpecifier(args.entryDir, args.repoRoot, rel.split(path.sep).join('/')),
|
|
418
|
+
exportName: chosen.getName(),
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
imports.set(name, found);
|
|
423
|
+
return found;
|
|
424
|
+
};
|
|
425
|
+
const leftmost = (name) => ts.isIdentifier(name) ? name : leftmost(name.left);
|
|
426
|
+
const renameLeftmost = (name, to) => ts.isIdentifier(name)
|
|
427
|
+
? ts.factory.createIdentifier(to)
|
|
428
|
+
: ts.factory.createQualifiedName(renameLeftmost(name.left, to), name.right);
|
|
429
|
+
let rewrites = 0;
|
|
430
|
+
const rewrite = (node) => {
|
|
431
|
+
if (ts.isTypeReferenceNode(node)) {
|
|
432
|
+
const name = leftmost(node.typeName).text;
|
|
433
|
+
const target = typeParameters.has(name) ? undefined : importFor(name);
|
|
434
|
+
if (target) {
|
|
435
|
+
rewrites += 1;
|
|
436
|
+
return ts.factory.createImportTypeNode(ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(target.spec)), undefined, renameLeftmost(node.typeName, target.exportName), node.typeArguments?.map((argument) => rewrite(argument)), false);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return ts.visitEachChild(node, rewrite, /* context */ undefined);
|
|
440
|
+
};
|
|
441
|
+
const rewritten = rewrite(parsed);
|
|
442
|
+
if (rewrites === 0)
|
|
443
|
+
return undefined;
|
|
444
|
+
return ts
|
|
445
|
+
.createPrinter({ removeComments: true })
|
|
446
|
+
.printNode(ts.EmitHint.Unspecified, rewritten, parsed.getSourceFile());
|
|
447
|
+
}
|
|
352
448
|
/** The type node of `type __LiteralAnchor = <text>;`, or undefined. */
|
|
353
449
|
function parseLiteralAnchor(text) {
|
|
354
450
|
const parsed = ts.createSourceFile('literal-anchor.ts', `type __LiteralAnchor = ${text};`, ts.ScriptTarget.Latest, true);
|
|
@@ -839,17 +935,26 @@ function isPreferredTarget(node) {
|
|
|
839
935
|
ts.isParameter(node) ||
|
|
840
936
|
ts.isPropertyAssignment(node));
|
|
841
937
|
}
|
|
938
|
+
/**
|
|
939
|
+
* The deepest node a span names: a value-space target covering it, or a TYPE
|
|
940
|
+
* node it covers exactly (carrick#1775). A span is the scanner naming a node,
|
|
941
|
+
* and a declared type is named by its type node: the property type a generated
|
|
942
|
+
* GraphQL document declaration states for a field sits inside a type alias,
|
|
943
|
+
* where no value-space node covers it. Exactness keeps a span that names no
|
|
944
|
+
* node from being read as whatever type happens to enclose it.
|
|
945
|
+
*/
|
|
842
946
|
function tightestCoveringNode(sourceFile, start, end) {
|
|
843
947
|
let best;
|
|
844
948
|
const visit = (node) => {
|
|
845
|
-
|
|
846
|
-
|
|
949
|
+
const nodeStart = node.getStart(sourceFile);
|
|
950
|
+
const nodeEnd = node.getEnd();
|
|
951
|
+
if (nodeStart <= start && nodeEnd >= end) {
|
|
952
|
+
if (isPreferredTarget(node) ||
|
|
953
|
+
(ts.isTypeNode(node) && nodeStart === start && nodeEnd === end)) {
|
|
847
954
|
best = node;
|
|
848
|
-
|
|
849
|
-
}
|
|
850
|
-
else {
|
|
851
|
-
node.forEachChild(visit);
|
|
955
|
+
}
|
|
852
956
|
}
|
|
957
|
+
node.forEachChild(visit);
|
|
853
958
|
};
|
|
854
959
|
visit(sourceFile);
|
|
855
960
|
return best;
|
|
@@ -40,6 +40,15 @@ const MAX_TYPE_TEXT = 80;
|
|
|
40
40
|
/** Depth cap on the structural descent. Deeper differences are reported at the
|
|
41
41
|
* deepest ancestor the walk reached, never dropped. */
|
|
42
42
|
const MAX_FIELD_DEPTH = 4;
|
|
43
|
+
/**
|
|
44
|
+
* Members a GraphQL server adds to every object a selection asks for
|
|
45
|
+
* (carrick#1759). The probe reads them as optional on the consumer, which is
|
|
46
|
+
* the probe's reading and not the consumer's source, so the walk states no
|
|
47
|
+
* optionality gap for them and does not count one the producer omits as a
|
|
48
|
+
* field the consumer is waiting on. A type they disagree on is still named.
|
|
49
|
+
*/
|
|
50
|
+
const GRAPHQL_SERVER_SUPPLIED = new Set(['__typename']);
|
|
51
|
+
const NONE_SERVER_SUPPLIED = new Set();
|
|
43
52
|
function assignabilityOf(checker) {
|
|
44
53
|
const fn = checker.isTypeAssignableTo;
|
|
45
54
|
return typeof fn === 'function' ? fn.bind(checker) : undefined;
|
|
@@ -89,7 +98,7 @@ export function pairFieldReports(opened, plans) {
|
|
|
89
98
|
// did change the type the wire form is a mapped type with real members,
|
|
90
99
|
// and it stays the thing compared, because that is what the judge judged.
|
|
91
100
|
const compared = wireChanges ? wire.type : declared.type;
|
|
92
|
-
const report = diffReport(compared, expected.type, checker, isAssignableTo, expected.node);
|
|
101
|
+
const report = diffReport(compared, expected.type, checker, isAssignableTo, expected.node, plan.spec.protocol === 'graphql' ? GRAPHQL_SERVER_SUPPLIED : NONE_SERVER_SUPPLIED);
|
|
93
102
|
report.wireApplied = wireChanges;
|
|
94
103
|
results.set(plan.pairId, report);
|
|
95
104
|
}
|
|
@@ -111,9 +120,9 @@ function declaredConstType(file, checker, name) {
|
|
|
111
120
|
}
|
|
112
121
|
return undefined;
|
|
113
122
|
}
|
|
114
|
-
function diffReport(sent, expected, checker, isAssignableTo, at) {
|
|
123
|
+
function diffReport(sent, expected, checker, isAssignableTo, at, serverSupplied) {
|
|
115
124
|
const found = [];
|
|
116
|
-
walk(sent, expected, '', 0, { checker, isAssignableTo, at, found });
|
|
125
|
+
walk(sent, expected, '', 0, { checker, isAssignableTo, at, found, serverSupplied });
|
|
117
126
|
found.sort((a, b) => a.path === b.path ? compareText(a.nature, b.nature) : compareText(a.path, b.path));
|
|
118
127
|
return {
|
|
119
128
|
differences: found.slice(0, MAX_NAMED_FIELDS),
|
|
@@ -165,7 +174,11 @@ function walk(sent, expected, path, depth, ctx) {
|
|
|
165
174
|
for (const [name, expectedProp] of expectedProps) {
|
|
166
175
|
const at = join(path, name);
|
|
167
176
|
const sentProp = sentProps.get(name);
|
|
177
|
+
const supplied = ctx.serverSupplied.has(name);
|
|
168
178
|
if (!sentProp) {
|
|
179
|
+
// The server sends it whether or not the producer's type states it.
|
|
180
|
+
if (supplied)
|
|
181
|
+
continue;
|
|
169
182
|
absent += 1;
|
|
170
183
|
// An optional member the sender omits is what optional MEANS. Naming it
|
|
171
184
|
// would state that the receiver requires it, which is false, and it is
|
|
@@ -180,9 +193,10 @@ function walk(sent, expected, path, depth, ctx) {
|
|
|
180
193
|
if (sentOptional && !expectedOptional) {
|
|
181
194
|
ctx.found.push({ path: at, nature: 'optional_in_sent' });
|
|
182
195
|
}
|
|
183
|
-
else if (!sentOptional && expectedOptional) {
|
|
196
|
+
else if (!sentOptional && expectedOptional && !supplied) {
|
|
184
197
|
// No assignment error exists for this direction, which is exactly why
|
|
185
|
-
// the judge cannot report it and this walk must.
|
|
198
|
+
// the judge cannot report it and this walk must. A server-supplied
|
|
199
|
+
// member is optional only in the probe's reading, so no gap is stated.
|
|
186
200
|
ctx.found.push({ path: at, nature: 'optional_in_expected' });
|
|
187
201
|
}
|
|
188
202
|
const sentType = memberType(sentProp, ctx);
|
|
@@ -23,6 +23,11 @@
|
|
|
23
23
|
* at compare time (ts_check type-checker `unwrapGraphqlPayload`, deleted in
|
|
24
24
|
* WP8); the type-level port here is its v2-native equivalent.
|
|
25
25
|
*
|
|
26
|
+
* GraphQL pairs also read the consumer's `__typename` as optional, at any depth
|
|
27
|
+
* (carrick#1759): the server adds that meta-field to every object a selection
|
|
28
|
+
* asks for, so a resolver's return type never states it, while a consumer type
|
|
29
|
+
* generated from a document that selects it declares it required.
|
|
30
|
+
*
|
|
26
31
|
* Seam: node builtins + `typescript` + this bundle only. No imports needed here.
|
|
27
32
|
*/
|
|
28
33
|
import type { CheckPairEndpoint, CheckPairSpec, ProbeProtocol, ProbeTypeKind } from './api.js';
|