carrick 0.3.53
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/LICENSE.md +99 -0
- package/README.md +79 -0
- package/bin/carrick.mjs +168 -0
- package/dist/channel.d.ts +16 -0
- package/dist/channel.js +37 -0
- package/dist/channel.js.map +1 -0
- package/dist/cli.d.ts +29 -0
- package/dist/cli.js +60 -0
- package/dist/cli.js.map +1 -0
- package/dist/contract.d.ts +154 -0
- package/dist/contract.js +141 -0
- package/dist/contract.js.map +1 -0
- package/dist/diagnostics.d.ts +54 -0
- package/dist/diagnostics.js +199 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/hook/post-edit.d.ts +13 -0
- package/dist/hook/post-edit.js +76 -0
- package/dist/hook/post-edit.js.map +1 -0
- package/dist/hook/session-start.d.ts +2 -0
- package/dist/hook/session-start.js +44 -0
- package/dist/hook/session-start.js.map +1 -0
- package/dist/init/identity.d.ts +20 -0
- package/dist/init/identity.js +60 -0
- package/dist/init/identity.js.map +1 -0
- package/dist/init/repos.d.ts +19 -0
- package/dist/init/repos.js +46 -0
- package/dist/init/repos.js.map +1 -0
- package/dist/init/run.d.ts +11 -0
- package/dist/init/run.js +228 -0
- package/dist/init/run.js.map +1 -0
- package/dist/init/settings.d.ts +50 -0
- package/dist/init/settings.js +138 -0
- package/dist/init/settings.js.map +1 -0
- package/dist/log.d.ts +2 -0
- package/dist/log.js +35 -0
- package/dist/log.js.map +1 -0
- package/dist/native.d.ts +48 -0
- package/dist/native.js +127 -0
- package/dist/native.js.map +1 -0
- package/dist/render.d.ts +65 -0
- package/dist/render.js +334 -0
- package/dist/render.js.map +1 -0
- package/dist/root.d.ts +30 -0
- package/dist/root.js +82 -0
- package/dist/root.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +255 -0
- package/dist/server.js.map +1 -0
- package/dist/templates.d.ts +18 -0
- package/dist/templates.js +66 -0
- package/dist/templates.js.map +1 -0
- package/package.json +72 -0
- package/plugin/.claude-plugin/plugin.json +6 -0
- package/plugin/.lsp.json +14 -0
- package/plugin/hooks/hooks.json +27 -0
- package/sidecar/dist/src/bundler.d.ts +141 -0
- package/sidecar/dist/src/bundler.js +680 -0
- package/sidecar/dist/src/capture/anchors.d.ts +61 -0
- package/sidecar/dist/src/capture/anchors.js +1132 -0
- package/sidecar/dist/src/capture/api.d.ts +378 -0
- package/sidecar/dist/src/capture/api.js +10 -0
- package/sidecar/dist/src/capture/augmentations.d.ts +20 -0
- package/sidecar/dist/src/capture/augmentations.js +60 -0
- package/sidecar/dist/src/capture/check-classify.d.ts +58 -0
- package/sidecar/dist/src/capture/check-classify.js +189 -0
- package/sidecar/dist/src/capture/check-deep.d.ts +32 -0
- package/sidecar/dist/src/capture/check-deep.js +91 -0
- package/sidecar/dist/src/capture/check-poison.d.ts +40 -0
- package/sidecar/dist/src/capture/check-poison.js +155 -0
- package/sidecar/dist/src/capture/check-probe.d.ts +70 -0
- package/sidecar/dist/src/capture/check-probe.js +147 -0
- package/sidecar/dist/src/capture/check-scrub.d.ts +31 -0
- package/sidecar/dist/src/capture/check-scrub.js +71 -0
- package/sidecar/dist/src/capture/check-workspace.d.ts +52 -0
- package/sidecar/dist/src/capture/check-workspace.js +218 -0
- package/sidecar/dist/src/capture/check.d.ts +39 -0
- package/sidecar/dist/src/capture/check.js +450 -0
- package/sidecar/dist/src/capture/deep-walk.d.ts +62 -0
- package/sidecar/dist/src/capture/deep-walk.js +243 -0
- package/sidecar/dist/src/capture/index.d.ts +36 -0
- package/sidecar/dist/src/capture/index.js +477 -0
- package/sidecar/dist/src/capture/lockfile.d.ts +48 -0
- package/sidecar/dist/src/capture/lockfile.js +490 -0
- package/sidecar/dist/src/capture/machinery.d.ts +59 -0
- package/sidecar/dist/src/capture/machinery.js +160 -0
- package/sidecar/dist/src/capture/node-builder.d.ts +37 -0
- package/sidecar/dist/src/capture/node-builder.js +123 -0
- package/sidecar/dist/src/capture/paths-rewrite.d.ts +34 -0
- package/sidecar/dist/src/capture/paths-rewrite.js +100 -0
- package/sidecar/dist/src/capture/self-check.d.ts +38 -0
- package/sidecar/dist/src/capture/self-check.js +317 -0
- package/sidecar/dist/src/capture/specifiers.d.ts +39 -0
- package/sidecar/dist/src/capture/specifiers.js +56 -0
- package/sidecar/dist/src/definition-resolver.d.ts +57 -0
- package/sidecar/dist/src/definition-resolver.js +153 -0
- package/sidecar/dist/src/index.d.ts +14 -0
- package/sidecar/dist/src/index.js +564 -0
- package/sidecar/dist/src/monorepo-builder.d.ts +129 -0
- package/sidecar/dist/src/monorepo-builder.js +584 -0
- package/sidecar/dist/src/project-loader.d.ts +130 -0
- package/sidecar/dist/src/project-loader.js +399 -0
- package/sidecar/dist/src/type-inferrer.d.ts +940 -0
- package/sidecar/dist/src/type-inferrer.js +3540 -0
- package/sidecar/dist/src/type-structural-expander.d.ts +61 -0
- package/sidecar/dist/src/type-structural-expander.js +283 -0
- package/sidecar/dist/src/type-text-canonicalizer.d.ts +40 -0
- package/sidecar/dist/src/type-text-canonicalizer.js +297 -0
- package/sidecar/dist/src/types.d.ts +669 -0
- package/sidecar/dist/src/types.js +5 -0
- package/sidecar/dist/src/validators.d.ts +2214 -0
- package/sidecar/dist/src/validators.js +336 -0
- package/sidecar/package.json +6 -0
- package/templates/carrick.json +7 -0
- package/templates/carrick.yml +45 -0
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main entry point for the type-sidecar
|
|
3
|
+
*
|
|
4
|
+
* This module implements a message loop that:
|
|
5
|
+
* 1. Listens on stdin for JSON requests
|
|
6
|
+
* 2. Processes each request (init, bundle, emit_surface, infer, build_workspace, check_compatibility, health, shutdown)
|
|
7
|
+
* 3. Writes JSON responses to stdout
|
|
8
|
+
*
|
|
9
|
+
* IMPORTANT:
|
|
10
|
+
* - stdout is ONLY for JSON responses
|
|
11
|
+
* - stderr is for logging
|
|
12
|
+
* - Process stays alive between requests (warm standby)
|
|
13
|
+
*/
|
|
14
|
+
import * as readline from 'node:readline';
|
|
15
|
+
import { parseRequest } from './validators.js';
|
|
16
|
+
import { ProjectLoader } from './project-loader.js';
|
|
17
|
+
import { TypeBundler, SurfaceEmitter } from './bundler.js';
|
|
18
|
+
import { TypeInferrer } from './type-inferrer.js';
|
|
19
|
+
import { MonorepoBuilder } from './monorepo-builder.js';
|
|
20
|
+
import { DefinitionResolver } from './definition-resolver.js';
|
|
21
|
+
import { captureStub, runCheck } from './capture/index.js';
|
|
22
|
+
// ===========================================================================
|
|
23
|
+
// Module-level state
|
|
24
|
+
// ===========================================================================
|
|
25
|
+
let projectLoader = null;
|
|
26
|
+
let monorepoBuilder = null;
|
|
27
|
+
let initTimeMs = null;
|
|
28
|
+
let components = null;
|
|
29
|
+
/**
|
|
30
|
+
* Get the project-backed components, building the project if this is the
|
|
31
|
+
* first request that needs it.
|
|
32
|
+
*
|
|
33
|
+
* @throws if init has not run, or if the project cannot be built
|
|
34
|
+
*/
|
|
35
|
+
function projectComponents() {
|
|
36
|
+
if (!projectLoader?.isInitialized()) {
|
|
37
|
+
throw new Error('Sidecar not initialized. Call init first.');
|
|
38
|
+
}
|
|
39
|
+
if (!components) {
|
|
40
|
+
const project = projectLoader.getProject();
|
|
41
|
+
const repoRoot = projectLoader.getRepoRoot();
|
|
42
|
+
components = {
|
|
43
|
+
typeBundler: new TypeBundler({ project, repoRoot }),
|
|
44
|
+
surfaceEmitter: new SurfaceEmitter({ project, repoRoot }),
|
|
45
|
+
typeInferrer: new TypeInferrer({ project }),
|
|
46
|
+
definitionResolver: new DefinitionResolver({ project }),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return components;
|
|
50
|
+
}
|
|
51
|
+
// ===========================================================================
|
|
52
|
+
// Request Handlers
|
|
53
|
+
// ===========================================================================
|
|
54
|
+
/**
|
|
55
|
+
* Handle the 'init' action - initialize the TypeScript project
|
|
56
|
+
*/
|
|
57
|
+
function handleInit(request) {
|
|
58
|
+
const startTime = performance.now();
|
|
59
|
+
try {
|
|
60
|
+
log(`Initializing with repo_root: ${request.repo_root}`);
|
|
61
|
+
// Re-init re-scopes the sidecar to another root: drop everything built
|
|
62
|
+
// over the previous project before resolving the new one.
|
|
63
|
+
components = null;
|
|
64
|
+
projectLoader = new ProjectLoader({
|
|
65
|
+
repoRoot: request.repo_root,
|
|
66
|
+
tsconfigPath: request.tsconfig_path,
|
|
67
|
+
tsconfigSnapshot: request.tsconfig_snapshot,
|
|
68
|
+
pinnedDependencies: request.pinned_dependencies,
|
|
69
|
+
});
|
|
70
|
+
const result = projectLoader.load();
|
|
71
|
+
if (!result.success) {
|
|
72
|
+
return {
|
|
73
|
+
request_id: request.request_id,
|
|
74
|
+
status: 'error',
|
|
75
|
+
errors: [result.error || 'Unknown initialization error'],
|
|
76
|
+
init_time_ms: result.initTimeMs,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
// The project and everything that reads it are built by the first request
|
|
80
|
+
// that needs them, so readiness costs the same on a bare checkout as on
|
|
81
|
+
// one with its dependencies installed (carrick#749).
|
|
82
|
+
// Initialize monorepo builder (doesn't need project)
|
|
83
|
+
monorepoBuilder = new MonorepoBuilder();
|
|
84
|
+
initTimeMs = result.initTimeMs || Math.round(performance.now() - startTime);
|
|
85
|
+
log(`Initialization complete in ${initTimeMs}ms`);
|
|
86
|
+
return {
|
|
87
|
+
request_id: request.request_id,
|
|
88
|
+
status: 'ready',
|
|
89
|
+
init_time_ms: initTimeMs,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
94
|
+
logError(`Initialization failed: ${error}`);
|
|
95
|
+
return {
|
|
96
|
+
request_id: request.request_id,
|
|
97
|
+
status: 'error',
|
|
98
|
+
errors: [error],
|
|
99
|
+
init_time_ms: Math.round(performance.now() - startTime),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Handle the 'bundle' action - bundle explicit types (legacy)
|
|
105
|
+
*/
|
|
106
|
+
function handleBundle(request) {
|
|
107
|
+
try {
|
|
108
|
+
log(`Bundling ${request.symbols.length} symbol(s)`);
|
|
109
|
+
const result = projectComponents().typeBundler.bundle(request.symbols);
|
|
110
|
+
if (!result.success) {
|
|
111
|
+
return {
|
|
112
|
+
request_id: request.request_id,
|
|
113
|
+
status: 'error',
|
|
114
|
+
dts_content: result.dts_content,
|
|
115
|
+
manifest: result.manifest,
|
|
116
|
+
symbol_failures: result.symbol_failures,
|
|
117
|
+
errors: result.errors,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
request_id: request.request_id,
|
|
122
|
+
status: 'success',
|
|
123
|
+
dts_content: result.dts_content,
|
|
124
|
+
manifest: result.manifest,
|
|
125
|
+
symbol_failures: result.symbol_failures,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
130
|
+
logError(`Bundle failed: ${error}`);
|
|
131
|
+
return {
|
|
132
|
+
request_id: request.request_id,
|
|
133
|
+
status: 'error',
|
|
134
|
+
errors: [error],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Handle the 'emit_surface' action - emit a surface .d.ts with rewritten specifiers
|
|
140
|
+
*/
|
|
141
|
+
function handleEmitSurface(request) {
|
|
142
|
+
try {
|
|
143
|
+
log(`Emitting surface for repo '${request.repo_name}' with ${request.payloads.length} payload(s)`);
|
|
144
|
+
const result = projectComponents().surfaceEmitter.emit(request.repo_name, request.payloads, request.output_path);
|
|
145
|
+
if (!result.success) {
|
|
146
|
+
return {
|
|
147
|
+
request_id: request.request_id,
|
|
148
|
+
status: 'error',
|
|
149
|
+
errors: result.errors,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
request_id: request.request_id,
|
|
154
|
+
status: 'success',
|
|
155
|
+
output_path: result.output_path,
|
|
156
|
+
surface_content: result.surface_content,
|
|
157
|
+
manifest: result.manifest,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
162
|
+
logError(`Surface emission failed: ${error}`);
|
|
163
|
+
return {
|
|
164
|
+
request_id: request.request_id,
|
|
165
|
+
status: 'error',
|
|
166
|
+
errors: [error],
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Handle the 'capture_v2' action - v2 "tsc as serializer" capture.
|
|
172
|
+
* Stateless by design: unlike bundle/infer it needs no init'd ts-morph
|
|
173
|
+
* project, only the repo's own tsconfig — this is the point of v2. The
|
|
174
|
+
* capture bundle behind this action is the seam ("seam, not split"): this
|
|
175
|
+
* dispatcher call is the only non-type reach-in.
|
|
176
|
+
*/
|
|
177
|
+
function handleCaptureV2(request) {
|
|
178
|
+
try {
|
|
179
|
+
log(`capture_v2 for service '${request.service_name}' (${request.anchors.length} anchor(s))`);
|
|
180
|
+
const result = captureStub({
|
|
181
|
+
repoRoot: request.repo_root,
|
|
182
|
+
serviceName: request.service_name,
|
|
183
|
+
anchors: request.anchors,
|
|
184
|
+
outDir: request.out_dir,
|
|
185
|
+
tsconfigPath: request.tsconfig_path,
|
|
186
|
+
});
|
|
187
|
+
return {
|
|
188
|
+
request_id: request.request_id,
|
|
189
|
+
status: result.success ? 'success' : 'error',
|
|
190
|
+
result,
|
|
191
|
+
errors: result.errors.length > 0 ? result.errors : undefined,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
196
|
+
logError(`capture_v2 failed: ${error}`);
|
|
197
|
+
return {
|
|
198
|
+
request_id: request.request_id,
|
|
199
|
+
status: 'error',
|
|
200
|
+
errors: [error],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Handle the 'check_v2' action - v2 "tsc as judge" compatibility check.
|
|
206
|
+
*
|
|
207
|
+
* Async by necessity: the vendored pnpm install can exceed the Rust client's
|
|
208
|
+
* read deadline, and running it off the event loop (spawn, not execSync) keeps
|
|
209
|
+
* the sidecar responsive. Emits `status: 'progress'` keepalive frames during
|
|
210
|
+
* install/check and one terminal `success`/`error` frame. Writes its own
|
|
211
|
+
* frames, so processLine hands off and does not write a response for it.
|
|
212
|
+
*/
|
|
213
|
+
async function handleCheckV2Async(request) {
|
|
214
|
+
let phase = 'assembling';
|
|
215
|
+
const keepalive = setInterval(() => {
|
|
216
|
+
writeProgress(request.request_id, phase, `check ${phase}`);
|
|
217
|
+
}, 1500);
|
|
218
|
+
keepalive.unref();
|
|
219
|
+
try {
|
|
220
|
+
log(`check_v2: ${request.stubs.length} stub(s), ${request.pairs.length} pair(s)`);
|
|
221
|
+
const result = await runCheck({
|
|
222
|
+
stubs: request.stubs,
|
|
223
|
+
pairs: request.pairs,
|
|
224
|
+
workspaceRoot: request.workspace_root,
|
|
225
|
+
cleanup: request.keep_workspace !== true,
|
|
226
|
+
}, (p) => {
|
|
227
|
+
phase = p;
|
|
228
|
+
});
|
|
229
|
+
clearInterval(keepalive);
|
|
230
|
+
const response = {
|
|
231
|
+
request_id: request.request_id,
|
|
232
|
+
status: result.success ? 'success' : 'error',
|
|
233
|
+
result,
|
|
234
|
+
errors: result.errors.length > 0 ? result.errors : undefined,
|
|
235
|
+
};
|
|
236
|
+
writeResponse(response);
|
|
237
|
+
}
|
|
238
|
+
catch (err) {
|
|
239
|
+
clearInterval(keepalive);
|
|
240
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
241
|
+
logError(`check_v2 failed: ${error}`);
|
|
242
|
+
writeResponse({
|
|
243
|
+
request_id: request.request_id,
|
|
244
|
+
status: 'error',
|
|
245
|
+
errors: [error],
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Handle the 'infer' action - infer implicit types
|
|
251
|
+
*/
|
|
252
|
+
function handleInfer(request) {
|
|
253
|
+
try {
|
|
254
|
+
log(`Inferring ${request.requests.length} type(s)`);
|
|
255
|
+
const result = projectComponents().typeInferrer.infer(request.requests, request.extraction_config);
|
|
256
|
+
return {
|
|
257
|
+
request_id: request.request_id,
|
|
258
|
+
status: result.success ? 'success' : 'error',
|
|
259
|
+
inferred_types: result.inferred_types,
|
|
260
|
+
errors: result.errors,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
265
|
+
logError(`Inference failed: ${error}`);
|
|
266
|
+
return {
|
|
267
|
+
request_id: request.request_id,
|
|
268
|
+
status: 'error',
|
|
269
|
+
errors: [error],
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Handle the 'build_workspace' action - build synthetic monorepo workspace
|
|
275
|
+
*/
|
|
276
|
+
function handleBuildWorkspace(request) {
|
|
277
|
+
// MonorepoBuilder doesn't require project initialization
|
|
278
|
+
if (!monorepoBuilder) {
|
|
279
|
+
monorepoBuilder = new MonorepoBuilder();
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
log(`Building synthetic workspace with ${request.repos.length} repo(s)`);
|
|
283
|
+
const result = monorepoBuilder.build(request.repos, request.workspace_root);
|
|
284
|
+
if (!result.success) {
|
|
285
|
+
return {
|
|
286
|
+
request_id: request.request_id,
|
|
287
|
+
status: 'error',
|
|
288
|
+
errors: result.errors,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
request_id: request.request_id,
|
|
293
|
+
status: 'success',
|
|
294
|
+
workspace_path: result.workspace_path,
|
|
295
|
+
stub_packages: result.stub_packages,
|
|
296
|
+
checker_path: result.checker_path,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
catch (err) {
|
|
300
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
301
|
+
logError(`Workspace build failed: ${error}`);
|
|
302
|
+
return {
|
|
303
|
+
request_id: request.request_id,
|
|
304
|
+
status: 'error',
|
|
305
|
+
errors: [error],
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Handle the 'check_compatibility' action - run type compatibility checks
|
|
311
|
+
*/
|
|
312
|
+
function handleCheckCompatibility(request) {
|
|
313
|
+
if (!monorepoBuilder) {
|
|
314
|
+
monorepoBuilder = new MonorepoBuilder();
|
|
315
|
+
}
|
|
316
|
+
try {
|
|
317
|
+
log(`Running compatibility checks: ${request.checks.length} check(s)`);
|
|
318
|
+
const result = monorepoBuilder.checkCompatibility(request.workspace_root, request.checks);
|
|
319
|
+
if (!result.success) {
|
|
320
|
+
return {
|
|
321
|
+
request_id: request.request_id,
|
|
322
|
+
status: 'error',
|
|
323
|
+
errors: result.errors,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
request_id: request.request_id,
|
|
328
|
+
status: 'success',
|
|
329
|
+
results: result.results,
|
|
330
|
+
diagnostics: result.diagnostics,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
catch (err) {
|
|
334
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
335
|
+
logError(`Compatibility check failed: ${error}`);
|
|
336
|
+
return {
|
|
337
|
+
request_id: request.request_id,
|
|
338
|
+
status: 'error',
|
|
339
|
+
errors: [error],
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Handle the 'resolve_definitions' action - resolve surface aliases from a
|
|
345
|
+
* v2 capture stub package's declaration tree.
|
|
346
|
+
*/
|
|
347
|
+
function handleResolveDefinitions(request) {
|
|
348
|
+
try {
|
|
349
|
+
log(`Resolving ${request.aliases.length} type alias(es) from ${request.stub_dir}`);
|
|
350
|
+
const results = projectComponents().definitionResolver.resolveFromStub(request.stub_dir, request.aliases);
|
|
351
|
+
return {
|
|
352
|
+
request_id: request.request_id,
|
|
353
|
+
status: 'success',
|
|
354
|
+
definitions: results.map((r) => ({
|
|
355
|
+
type_alias: r.type_alias,
|
|
356
|
+
definition: r.definition,
|
|
357
|
+
expanded: r.expanded,
|
|
358
|
+
})),
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
catch (err) {
|
|
362
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
363
|
+
logError(`Definition resolution failed: ${error}`);
|
|
364
|
+
return {
|
|
365
|
+
request_id: request.request_id,
|
|
366
|
+
status: 'error',
|
|
367
|
+
errors: [error],
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Handle the 'health' action - report initialization status
|
|
373
|
+
*/
|
|
374
|
+
function handleHealth(request) {
|
|
375
|
+
const isReady = projectLoader?.isInitialized() ?? false;
|
|
376
|
+
return {
|
|
377
|
+
request_id: request.request_id,
|
|
378
|
+
status: isReady ? 'ready' : 'not_ready',
|
|
379
|
+
init_time_ms: initTimeMs ?? undefined,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Handle the 'shutdown' action - exit gracefully
|
|
384
|
+
*/
|
|
385
|
+
function handleShutdown(request) {
|
|
386
|
+
log('Shutdown requested');
|
|
387
|
+
// Schedule exit after response is sent
|
|
388
|
+
setImmediate(() => {
|
|
389
|
+
log('Exiting');
|
|
390
|
+
process.exit(0);
|
|
391
|
+
});
|
|
392
|
+
return {
|
|
393
|
+
request_id: request.request_id,
|
|
394
|
+
status: 'success',
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
// ===========================================================================
|
|
398
|
+
// Request Router
|
|
399
|
+
// ===========================================================================
|
|
400
|
+
/**
|
|
401
|
+
* Route a request to the appropriate handler
|
|
402
|
+
*/
|
|
403
|
+
function handleRequest(request) {
|
|
404
|
+
switch (request.action) {
|
|
405
|
+
case 'init':
|
|
406
|
+
return handleInit(request);
|
|
407
|
+
case 'bundle':
|
|
408
|
+
return handleBundle(request);
|
|
409
|
+
case 'emit_surface':
|
|
410
|
+
return handleEmitSurface(request);
|
|
411
|
+
case 'capture_v2':
|
|
412
|
+
return handleCaptureV2(request);
|
|
413
|
+
case 'infer':
|
|
414
|
+
return handleInfer(request);
|
|
415
|
+
case 'build_workspace':
|
|
416
|
+
return handleBuildWorkspace(request);
|
|
417
|
+
case 'check_compatibility':
|
|
418
|
+
return handleCheckCompatibility(request);
|
|
419
|
+
case 'resolve_definitions':
|
|
420
|
+
return handleResolveDefinitions(request);
|
|
421
|
+
case 'health':
|
|
422
|
+
return handleHealth(request);
|
|
423
|
+
case 'shutdown':
|
|
424
|
+
return handleShutdown(request);
|
|
425
|
+
default:
|
|
426
|
+
// TypeScript should catch this, but just in case
|
|
427
|
+
return {
|
|
428
|
+
request_id: request.request_id || 'unknown',
|
|
429
|
+
status: 'error',
|
|
430
|
+
errors: [`Unknown action: ${request.action}`],
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
// ===========================================================================
|
|
435
|
+
// Response Writer
|
|
436
|
+
// ===========================================================================
|
|
437
|
+
/**
|
|
438
|
+
* Write a JSON response to stdout
|
|
439
|
+
*/
|
|
440
|
+
function writeResponse(response) {
|
|
441
|
+
const json = JSON.stringify(response);
|
|
442
|
+
process.stdout.write(json + '\n');
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Write a non-terminal progress/keepalive frame (async install protocol).
|
|
446
|
+
* Distinct `status: 'progress'` so clients skip it and wait for the terminal
|
|
447
|
+
* success/error frame.
|
|
448
|
+
*/
|
|
449
|
+
function writeProgress(requestId, phase, message) {
|
|
450
|
+
process.stdout.write(JSON.stringify({ request_id: requestId, status: 'progress', phase, message }) + '\n');
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Write an error response for an invalid request
|
|
454
|
+
*/
|
|
455
|
+
function writeErrorResponse(requestId, error) {
|
|
456
|
+
const response = {
|
|
457
|
+
request_id: requestId,
|
|
458
|
+
status: 'error',
|
|
459
|
+
errors: [error],
|
|
460
|
+
};
|
|
461
|
+
writeResponse(response);
|
|
462
|
+
}
|
|
463
|
+
// ===========================================================================
|
|
464
|
+
// Logging (to stderr only)
|
|
465
|
+
// ===========================================================================
|
|
466
|
+
function log(message) {
|
|
467
|
+
console.error(`[sidecar] ${message}`);
|
|
468
|
+
}
|
|
469
|
+
function logError(message) {
|
|
470
|
+
console.error(`[sidecar:error] ${message}`);
|
|
471
|
+
}
|
|
472
|
+
// ===========================================================================
|
|
473
|
+
// Main Entry Point
|
|
474
|
+
// ===========================================================================
|
|
475
|
+
/**
|
|
476
|
+
* Process a single line of input
|
|
477
|
+
*/
|
|
478
|
+
function processLine(line) {
|
|
479
|
+
// Skip empty lines
|
|
480
|
+
const trimmed = line.trim();
|
|
481
|
+
if (!trimmed)
|
|
482
|
+
return;
|
|
483
|
+
// Parse JSON
|
|
484
|
+
let json;
|
|
485
|
+
try {
|
|
486
|
+
json = JSON.parse(trimmed);
|
|
487
|
+
}
|
|
488
|
+
catch (err) {
|
|
489
|
+
logError(`Invalid JSON: ${trimmed}`);
|
|
490
|
+
writeErrorResponse('unknown', `Invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
// Validate request
|
|
494
|
+
const parseResult = parseRequest(json);
|
|
495
|
+
if (!parseResult.success) {
|
|
496
|
+
logError(`Invalid request: ${parseResult.error}`);
|
|
497
|
+
writeErrorResponse(json?.request_id || 'unknown', parseResult.error);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
// check_v2 runs async (spawned pnpm install + tsc) and writes its own
|
|
501
|
+
// progress + terminal frames; hand off and return.
|
|
502
|
+
if (parseResult.request.action === 'check_v2') {
|
|
503
|
+
void handleCheckV2Async(parseResult.request);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
// Handle request
|
|
507
|
+
const response = handleRequest(parseResult.request);
|
|
508
|
+
writeResponse(response);
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Start the message loop
|
|
512
|
+
*/
|
|
513
|
+
function main() {
|
|
514
|
+
log('Process started');
|
|
515
|
+
const rl = readline.createInterface({
|
|
516
|
+
input: process.stdin,
|
|
517
|
+
output: process.stdout,
|
|
518
|
+
terminal: false,
|
|
519
|
+
});
|
|
520
|
+
rl.on('line', processLine);
|
|
521
|
+
rl.on('close', () => {
|
|
522
|
+
log('stdin closed, exiting');
|
|
523
|
+
process.exit(0);
|
|
524
|
+
});
|
|
525
|
+
// Handle process signals
|
|
526
|
+
process.on('SIGINT', () => {
|
|
527
|
+
log('SIGINT received, exiting');
|
|
528
|
+
process.exit(0);
|
|
529
|
+
});
|
|
530
|
+
process.on('SIGTERM', () => {
|
|
531
|
+
log('SIGTERM received, exiting');
|
|
532
|
+
process.exit(0);
|
|
533
|
+
});
|
|
534
|
+
// Fail fast on uncaught errors. Continuing after one means serving later
|
|
535
|
+
// requests from a possibly corrupted state and returning garbage types
|
|
536
|
+
// with a success status; exiting surfaces as ProcessDied on the Rust side,
|
|
537
|
+
// which degrades cleanly (scan continues, type_extraction_status records
|
|
538
|
+
// the loss).
|
|
539
|
+
process.on('uncaughtException', (err) => {
|
|
540
|
+
logError(`Uncaught exception (exiting): ${err.message}`);
|
|
541
|
+
logError(err.stack || '');
|
|
542
|
+
process.exit(1);
|
|
543
|
+
});
|
|
544
|
+
process.on('unhandledRejection', (reason) => {
|
|
545
|
+
// This log is the last diagnostic before exit, so it must be actionable:
|
|
546
|
+
// a bare interpolation renders non-Error reasons as [object Object].
|
|
547
|
+
let detail;
|
|
548
|
+
if (reason instanceof Error) {
|
|
549
|
+
detail = reason.stack || reason.message;
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
try {
|
|
553
|
+
detail = JSON.stringify(reason) ?? String(reason);
|
|
554
|
+
}
|
|
555
|
+
catch {
|
|
556
|
+
detail = String(reason);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
logError(`Unhandled rejection (exiting): ${detail}`);
|
|
560
|
+
process.exit(1);
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
// Start the sidecar
|
|
564
|
+
main();
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monorepo Builder - Builds synthetic monorepo workspaces for type compatibility checking
|
|
3
|
+
*
|
|
4
|
+
* This module implements the "Synthetic Monorepo (Stub Snapshot)" architecture:
|
|
5
|
+
* - Creates a workspace with stub packages for each repo
|
|
6
|
+
* - Each stub package has its own isolated node_modules with pinned dependencies
|
|
7
|
+
* - Generates path mappings so @carrick/{repoName}/{spec} resolves correctly
|
|
8
|
+
* - Creates a checker package that imports surfaces and asserts type compatibility
|
|
9
|
+
*
|
|
10
|
+
* Key design decisions:
|
|
11
|
+
* - Uses pnpm for better isolation between packages
|
|
12
|
+
* - Does NOT copy full repos - only uses captured artifacts
|
|
13
|
+
* - Deterministic: uses pinned dependency snapshots
|
|
14
|
+
*/
|
|
15
|
+
import type { RepoMetadata, WorkspaceBuildResult, CompatibilityCheck, CompatibilityCheckResult } from './types.js';
|
|
16
|
+
/**
|
|
17
|
+
* MonorepoBuilder - Creates synthetic workspaces for cross-repo type checking
|
|
18
|
+
*
|
|
19
|
+
* Usage:
|
|
20
|
+
* const builder = new MonorepoBuilder();
|
|
21
|
+
* const result = builder.build(repos);
|
|
22
|
+
* const checkResult = builder.checkCompatibility(workspacePath, checks);
|
|
23
|
+
*/
|
|
24
|
+
export declare class MonorepoBuilder {
|
|
25
|
+
/**
|
|
26
|
+
* Build the synthetic monorepo workspace
|
|
27
|
+
*
|
|
28
|
+
* @param repos - Metadata for each repository to include
|
|
29
|
+
* @param workspaceRoot - Root directory for the workspace (defaults to .carrick/workspace)
|
|
30
|
+
* @returns WorkspaceBuildResult
|
|
31
|
+
*/
|
|
32
|
+
build(repos: RepoMetadata[], workspaceRoot?: string): WorkspaceBuildResult;
|
|
33
|
+
/**
|
|
34
|
+
* Run type compatibility checks in the workspace
|
|
35
|
+
*
|
|
36
|
+
* @param workspaceRoot - Path to the workspace root
|
|
37
|
+
* @param checks - Compatibility checks to run
|
|
38
|
+
* @returns CompatibilityCheckResult
|
|
39
|
+
*/
|
|
40
|
+
checkCompatibility(workspaceRoot: string, checks: CompatibilityCheck[]): CompatibilityCheckResult;
|
|
41
|
+
/**
|
|
42
|
+
* Ensure a clean directory exists at the given path
|
|
43
|
+
*/
|
|
44
|
+
private ensureCleanDirectory;
|
|
45
|
+
/**
|
|
46
|
+
* Create the root package.json for the workspace
|
|
47
|
+
*/
|
|
48
|
+
private createRootPackageJson;
|
|
49
|
+
/**
|
|
50
|
+
* Create pnpm-workspace.yaml
|
|
51
|
+
*/
|
|
52
|
+
private createPnpmWorkspaceYaml;
|
|
53
|
+
/**
|
|
54
|
+
* Create .npmrc to prevent hoisting that would blur package boundaries
|
|
55
|
+
*/
|
|
56
|
+
private createNpmrc;
|
|
57
|
+
/**
|
|
58
|
+
* Create a stub package for a repository
|
|
59
|
+
*
|
|
60
|
+
* @returns Path to the created package
|
|
61
|
+
*/
|
|
62
|
+
private createStubPackage;
|
|
63
|
+
/**
|
|
64
|
+
* Create package.json for a stub package
|
|
65
|
+
*/
|
|
66
|
+
private createStubPackageJson;
|
|
67
|
+
/**
|
|
68
|
+
* Create tsconfig.json for a stub package from the snapshot
|
|
69
|
+
*/
|
|
70
|
+
private createStubTsconfig;
|
|
71
|
+
/**
|
|
72
|
+
* Create the surface.d.ts file
|
|
73
|
+
*/
|
|
74
|
+
private createSurfaceFile;
|
|
75
|
+
/**
|
|
76
|
+
* Create a placeholder surface file
|
|
77
|
+
*/
|
|
78
|
+
private createPlaceholderSurface;
|
|
79
|
+
/**
|
|
80
|
+
* Get default tsconfig snapshot
|
|
81
|
+
*/
|
|
82
|
+
private getDefaultTsconfigSnapshot;
|
|
83
|
+
/**
|
|
84
|
+
* Create the checker package that runs type compatibility checks
|
|
85
|
+
*
|
|
86
|
+
* @returns Path to the checker package
|
|
87
|
+
*/
|
|
88
|
+
private createCheckerPackage;
|
|
89
|
+
/**
|
|
90
|
+
* Create package.json for the checker package
|
|
91
|
+
*/
|
|
92
|
+
private createCheckerPackageJson;
|
|
93
|
+
/**
|
|
94
|
+
* Create tsconfig.json for the checker package with path mappings
|
|
95
|
+
*/
|
|
96
|
+
private createCheckerTsconfig;
|
|
97
|
+
/**
|
|
98
|
+
* Create initial checks.ts file
|
|
99
|
+
*/
|
|
100
|
+
private createInitialChecksFile;
|
|
101
|
+
/**
|
|
102
|
+
* Install dependencies using pnpm
|
|
103
|
+
*/
|
|
104
|
+
private installDependencies;
|
|
105
|
+
/**
|
|
106
|
+
* Fallback: Install dependencies using npm
|
|
107
|
+
*/
|
|
108
|
+
private installWithNpm;
|
|
109
|
+
/**
|
|
110
|
+
* Generate the checks.ts file with type assertions
|
|
111
|
+
*/
|
|
112
|
+
private generateCheckFile;
|
|
113
|
+
/**
|
|
114
|
+
* Create a unique import name for a repo/alias combination
|
|
115
|
+
*/
|
|
116
|
+
private makeImportName;
|
|
117
|
+
/**
|
|
118
|
+
* Run TypeScript type checking on the checker package
|
|
119
|
+
*
|
|
120
|
+
* @returns Object with success status and any error output
|
|
121
|
+
*/
|
|
122
|
+
private runTypeCheck;
|
|
123
|
+
/**
|
|
124
|
+
* Parse the result of a type check for a specific compatibility check
|
|
125
|
+
*/
|
|
126
|
+
private parseCheckResult;
|
|
127
|
+
private log;
|
|
128
|
+
private logError;
|
|
129
|
+
}
|