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,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Loader - Loads TypeScript projects using ts-morph
|
|
3
|
+
*
|
|
4
|
+
* This module handles initialization of the ts-morph Project,
|
|
5
|
+
* including finding and loading tsconfig.json files.
|
|
6
|
+
*
|
|
7
|
+
* Supports:
|
|
8
|
+
* - Traditional tsconfig.json file loading
|
|
9
|
+
* - Tsconfig snapshot (closed/merged extends chains) for synthetic monorepo
|
|
10
|
+
* - Pinned dependency snapshots for deterministic builds
|
|
11
|
+
*/
|
|
12
|
+
import { Project } from 'ts-morph';
|
|
13
|
+
import type { TsconfigSnapshot, PinnedDependencySnapshot } from './types.js';
|
|
14
|
+
/**
|
|
15
|
+
* Options for ProjectLoader construction
|
|
16
|
+
*/
|
|
17
|
+
export interface ProjectLoaderOptions {
|
|
18
|
+
/** The repository root directory (absolute path) */
|
|
19
|
+
repoRoot: string;
|
|
20
|
+
/** Optional path to tsconfig.json (relative to repo root or absolute) */
|
|
21
|
+
tsconfigPath?: string;
|
|
22
|
+
/** Optional tsconfig snapshot (closed/merged) - preferred over tsconfigPath */
|
|
23
|
+
tsconfigSnapshot?: TsconfigSnapshot;
|
|
24
|
+
/** Optional pinned dependencies for this repo */
|
|
25
|
+
pinnedDependencies?: PinnedDependencySnapshot;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Result of project loading
|
|
29
|
+
*/
|
|
30
|
+
export interface LoadResult {
|
|
31
|
+
success: boolean;
|
|
32
|
+
error?: string;
|
|
33
|
+
initTimeMs?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* ProjectLoader - Manages ts-morph Project initialization and access
|
|
37
|
+
*
|
|
38
|
+
* `load()` decides what the project will be built from and returns; the
|
|
39
|
+
* ts-morph project itself is built on first use and memoised. Readiness is a
|
|
40
|
+
* gate on the whole type layer — when it was gated on program construction, a
|
|
41
|
+
* repo with its dependencies installed could walk past the caller's budget and
|
|
42
|
+
* lose types for every service at once (carrick#749). Building on demand puts
|
|
43
|
+
* that cost inside the request that needs it, where a failure costs one
|
|
44
|
+
* service instead of all of them.
|
|
45
|
+
*
|
|
46
|
+
* Usage:
|
|
47
|
+
* const loader = new ProjectLoader({ repoRoot: '/path/to/repo' });
|
|
48
|
+
* const result = loader.load();
|
|
49
|
+
* if (result.success) {
|
|
50
|
+
* const project = loader.getProject();
|
|
51
|
+
* }
|
|
52
|
+
*/
|
|
53
|
+
export declare class ProjectLoader {
|
|
54
|
+
private project;
|
|
55
|
+
/** Set by `load()`; builds the ts-morph project on first `getProject()`. */
|
|
56
|
+
private buildProject;
|
|
57
|
+
private readonly repoRoot;
|
|
58
|
+
private readonly tsconfigPath;
|
|
59
|
+
private readonly tsconfigSnapshot;
|
|
60
|
+
private readonly pinnedDependencies;
|
|
61
|
+
private initialized;
|
|
62
|
+
private initError;
|
|
63
|
+
private initTimeMs;
|
|
64
|
+
constructor(options: ProjectLoaderOptions);
|
|
65
|
+
/**
|
|
66
|
+
* Decide what the project will be built from.
|
|
67
|
+
*
|
|
68
|
+
* Validates the repo root and resolves the tsconfig, both cheap; the
|
|
69
|
+
* ts-morph project is built by the first `getProject()`.
|
|
70
|
+
*
|
|
71
|
+
* @returns LoadResult indicating success or failure
|
|
72
|
+
*/
|
|
73
|
+
load(): LoadResult;
|
|
74
|
+
/**
|
|
75
|
+
* Convert a TsconfigSnapshot to ts-morph CompilerOptions
|
|
76
|
+
*/
|
|
77
|
+
private snapshotToCompilerOptions;
|
|
78
|
+
/**
|
|
79
|
+
* Get the ts-morph Project, building it on first call.
|
|
80
|
+
*
|
|
81
|
+
* @returns The Project instance
|
|
82
|
+
* @throws Error if load() has not succeeded, or if the build fails
|
|
83
|
+
*/
|
|
84
|
+
getProject(): Project;
|
|
85
|
+
/**
|
|
86
|
+
* Check if the project has been successfully initialized
|
|
87
|
+
*/
|
|
88
|
+
isInitialized(): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Get initialization time in milliseconds
|
|
91
|
+
*/
|
|
92
|
+
getInitTimeMs(): number | null;
|
|
93
|
+
/**
|
|
94
|
+
* Get the last initialization error, if any
|
|
95
|
+
*/
|
|
96
|
+
getInitError(): string | null;
|
|
97
|
+
/**
|
|
98
|
+
* Get the repository root path
|
|
99
|
+
*/
|
|
100
|
+
getRepoRoot(): string;
|
|
101
|
+
/**
|
|
102
|
+
* Get the pinned dependencies, if any
|
|
103
|
+
*/
|
|
104
|
+
getPinnedDependencies(): PinnedDependencySnapshot | undefined;
|
|
105
|
+
/**
|
|
106
|
+
* Find the tsconfig.json file to use
|
|
107
|
+
*
|
|
108
|
+
* @returns Absolute path to tsconfig.json, or undefined if not found
|
|
109
|
+
*/
|
|
110
|
+
private findTsConfig;
|
|
111
|
+
/**
|
|
112
|
+
* Add source files from common project locations when no tsconfig is found.
|
|
113
|
+
*
|
|
114
|
+
* Files are globbed and added one by one rather than through
|
|
115
|
+
* `addSourceFilesAtPaths`, which registers every descendant directory of any
|
|
116
|
+
* directory a pattern hit in. One `.ts` file at the repo root is enough to
|
|
117
|
+
* make that walk the entire installed tree: on a large monorepo with
|
|
118
|
+
* dependencies installed it cost 40 s and added nothing the compiler needs
|
|
119
|
+
* (carrick#749).
|
|
120
|
+
*/
|
|
121
|
+
private addDefaultSourceFiles;
|
|
122
|
+
/**
|
|
123
|
+
* Log a message to stderr (stdout is reserved for JSON responses)
|
|
124
|
+
*/
|
|
125
|
+
private log;
|
|
126
|
+
/**
|
|
127
|
+
* Log an error message to stderr
|
|
128
|
+
*/
|
|
129
|
+
private logError;
|
|
130
|
+
}
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Loader - Loads TypeScript projects using ts-morph
|
|
3
|
+
*
|
|
4
|
+
* This module handles initialization of the ts-morph Project,
|
|
5
|
+
* including finding and loading tsconfig.json files.
|
|
6
|
+
*
|
|
7
|
+
* Supports:
|
|
8
|
+
* - Traditional tsconfig.json file loading
|
|
9
|
+
* - Tsconfig snapshot (closed/merged extends chains) for synthetic monorepo
|
|
10
|
+
* - Pinned dependency snapshots for deterministic builds
|
|
11
|
+
*/
|
|
12
|
+
import { Project } from 'ts-morph';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
import * as fs from 'node:fs';
|
|
15
|
+
/**
|
|
16
|
+
* Source-file patterns used when the repo declares no tsconfig, relative to
|
|
17
|
+
* the repo root. `node_modules` is excluded explicitly: a glob that matches
|
|
18
|
+
* anything at the repo root would otherwise pull the whole installed tree in.
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_SOURCE_PATTERNS = [
|
|
21
|
+
'src/**/*.ts',
|
|
22
|
+
'src/**/*.tsx',
|
|
23
|
+
'lib/**/*.ts',
|
|
24
|
+
'app/**/*.ts',
|
|
25
|
+
'app/**/*.tsx',
|
|
26
|
+
'*.ts',
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* Default compiler options used when no tsconfig.json is found
|
|
30
|
+
*/
|
|
31
|
+
const DEFAULT_COMPILER_OPTIONS = {
|
|
32
|
+
target: 99, // ESNext
|
|
33
|
+
module: 99, // ESNext
|
|
34
|
+
moduleResolution: 99, // NodeNext
|
|
35
|
+
strict: true,
|
|
36
|
+
esModuleInterop: true,
|
|
37
|
+
skipLibCheck: true,
|
|
38
|
+
declaration: true,
|
|
39
|
+
allowJs: true,
|
|
40
|
+
checkJs: false,
|
|
41
|
+
resolveJsonModule: true,
|
|
42
|
+
isolatedModules: true,
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Map string module values to ts-morph enum values
|
|
46
|
+
*/
|
|
47
|
+
const MODULE_MAP = {
|
|
48
|
+
'CommonJS': 1,
|
|
49
|
+
'AMD': 2,
|
|
50
|
+
'UMD': 3,
|
|
51
|
+
'System': 4,
|
|
52
|
+
'ES2015': 5,
|
|
53
|
+
'ES2020': 6,
|
|
54
|
+
'ES2022': 7,
|
|
55
|
+
'ESNext': 99,
|
|
56
|
+
'Node16': 100,
|
|
57
|
+
'NodeNext': 199,
|
|
58
|
+
'Preserve': 200,
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Map string moduleResolution values to ts-morph enum values
|
|
62
|
+
*/
|
|
63
|
+
const MODULE_RESOLUTION_MAP = {
|
|
64
|
+
'Classic': 1,
|
|
65
|
+
'Node': 2,
|
|
66
|
+
'Node10': 2,
|
|
67
|
+
'Node16': 3,
|
|
68
|
+
'NodeNext': 99,
|
|
69
|
+
'Bundler': 100,
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Map string target values to ts-morph enum values
|
|
73
|
+
*/
|
|
74
|
+
const TARGET_MAP = {
|
|
75
|
+
'ES3': 0,
|
|
76
|
+
'ES5': 1,
|
|
77
|
+
'ES2015': 2,
|
|
78
|
+
'ES2016': 3,
|
|
79
|
+
'ES2017': 4,
|
|
80
|
+
'ES2018': 5,
|
|
81
|
+
'ES2019': 6,
|
|
82
|
+
'ES2020': 7,
|
|
83
|
+
'ES2021': 8,
|
|
84
|
+
'ES2022': 9,
|
|
85
|
+
'ES2023': 10,
|
|
86
|
+
'ESNext': 99,
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* ProjectLoader - Manages ts-morph Project initialization and access
|
|
90
|
+
*
|
|
91
|
+
* `load()` decides what the project will be built from and returns; the
|
|
92
|
+
* ts-morph project itself is built on first use and memoised. Readiness is a
|
|
93
|
+
* gate on the whole type layer — when it was gated on program construction, a
|
|
94
|
+
* repo with its dependencies installed could walk past the caller's budget and
|
|
95
|
+
* lose types for every service at once (carrick#749). Building on demand puts
|
|
96
|
+
* that cost inside the request that needs it, where a failure costs one
|
|
97
|
+
* service instead of all of them.
|
|
98
|
+
*
|
|
99
|
+
* Usage:
|
|
100
|
+
* const loader = new ProjectLoader({ repoRoot: '/path/to/repo' });
|
|
101
|
+
* const result = loader.load();
|
|
102
|
+
* if (result.success) {
|
|
103
|
+
* const project = loader.getProject();
|
|
104
|
+
* }
|
|
105
|
+
*/
|
|
106
|
+
export class ProjectLoader {
|
|
107
|
+
project = null;
|
|
108
|
+
/** Set by `load()`; builds the ts-morph project on first `getProject()`. */
|
|
109
|
+
buildProject = null;
|
|
110
|
+
repoRoot;
|
|
111
|
+
tsconfigPath;
|
|
112
|
+
tsconfigSnapshot;
|
|
113
|
+
pinnedDependencies;
|
|
114
|
+
initialized = false;
|
|
115
|
+
initError = null;
|
|
116
|
+
initTimeMs = null;
|
|
117
|
+
constructor(options) {
|
|
118
|
+
// Normalize the repo root to an absolute path
|
|
119
|
+
this.repoRoot = path.isAbsolute(options.repoRoot)
|
|
120
|
+
? options.repoRoot
|
|
121
|
+
: path.resolve(process.cwd(), options.repoRoot);
|
|
122
|
+
// Resolve tsconfig path if provided
|
|
123
|
+
if (options.tsconfigPath) {
|
|
124
|
+
this.tsconfigPath = path.isAbsolute(options.tsconfigPath)
|
|
125
|
+
? options.tsconfigPath
|
|
126
|
+
: path.resolve(this.repoRoot, options.tsconfigPath);
|
|
127
|
+
}
|
|
128
|
+
// Store snapshot if provided
|
|
129
|
+
this.tsconfigSnapshot = options.tsconfigSnapshot;
|
|
130
|
+
this.pinnedDependencies = options.pinnedDependencies;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Decide what the project will be built from.
|
|
134
|
+
*
|
|
135
|
+
* Validates the repo root and resolves the tsconfig, both cheap; the
|
|
136
|
+
* ts-morph project is built by the first `getProject()`.
|
|
137
|
+
*
|
|
138
|
+
* @returns LoadResult indicating success or failure
|
|
139
|
+
*/
|
|
140
|
+
load() {
|
|
141
|
+
const startTime = performance.now();
|
|
142
|
+
try {
|
|
143
|
+
// Validate repo root exists
|
|
144
|
+
if (!fs.existsSync(this.repoRoot)) {
|
|
145
|
+
const error = `Repository root does not exist: ${this.repoRoot}`;
|
|
146
|
+
this.logError(error);
|
|
147
|
+
this.initError = error;
|
|
148
|
+
return { success: false, error };
|
|
149
|
+
}
|
|
150
|
+
if (!fs.statSync(this.repoRoot).isDirectory()) {
|
|
151
|
+
const error = `Repository root is not a directory: ${this.repoRoot}`;
|
|
152
|
+
this.logError(error);
|
|
153
|
+
this.initError = error;
|
|
154
|
+
return { success: false, error };
|
|
155
|
+
}
|
|
156
|
+
// Priority 1: Use tsconfig snapshot if provided (for synthetic monorepo)
|
|
157
|
+
if (this.tsconfigSnapshot) {
|
|
158
|
+
const snapshot = this.tsconfigSnapshot;
|
|
159
|
+
this.log('Project will load with tsconfig snapshot');
|
|
160
|
+
this.buildProject = () => {
|
|
161
|
+
const project = new Project({
|
|
162
|
+
compilerOptions: this.snapshotToCompilerOptions(snapshot),
|
|
163
|
+
skipAddingFilesFromTsConfig: true,
|
|
164
|
+
});
|
|
165
|
+
this.addDefaultSourceFiles(project);
|
|
166
|
+
return project;
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
// Priority 2: Use tsconfig.json file
|
|
170
|
+
else {
|
|
171
|
+
const tsconfigPath = this.findTsConfig();
|
|
172
|
+
if (tsconfigPath) {
|
|
173
|
+
this.log(`Project will load with tsconfig: ${tsconfigPath}`);
|
|
174
|
+
this.buildProject = () => new Project({
|
|
175
|
+
tsConfigFilePath: tsconfigPath,
|
|
176
|
+
skipAddingFilesFromTsConfig: false,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
this.log('No tsconfig.json found, using default compiler options');
|
|
181
|
+
this.buildProject = () => {
|
|
182
|
+
const project = new Project({
|
|
183
|
+
compilerOptions: DEFAULT_COMPILER_OPTIONS,
|
|
184
|
+
skipAddingFilesFromTsConfig: true,
|
|
185
|
+
});
|
|
186
|
+
// Add source files from common locations
|
|
187
|
+
this.addDefaultSourceFiles(project);
|
|
188
|
+
return project;
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Log pinned dependencies if provided
|
|
193
|
+
if (this.pinnedDependencies) {
|
|
194
|
+
const depCount = Object.keys(this.pinnedDependencies).length;
|
|
195
|
+
this.log(`Using ${depCount} pinned dependencies`);
|
|
196
|
+
}
|
|
197
|
+
this.initialized = true;
|
|
198
|
+
this.initTimeMs = Math.round(performance.now() - startTime);
|
|
199
|
+
this.log(`Project resolved in ${this.initTimeMs}ms (built on first use)`);
|
|
200
|
+
return {
|
|
201
|
+
success: true,
|
|
202
|
+
initTimeMs: this.initTimeMs,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
207
|
+
this.logError(`Failed to load project: ${error}`);
|
|
208
|
+
this.initError = error;
|
|
209
|
+
return {
|
|
210
|
+
success: false,
|
|
211
|
+
error,
|
|
212
|
+
initTimeMs: Math.round(performance.now() - startTime),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Convert a TsconfigSnapshot to ts-morph CompilerOptions
|
|
218
|
+
*/
|
|
219
|
+
snapshotToCompilerOptions(snapshot) {
|
|
220
|
+
const opts = snapshot.compilerOptions;
|
|
221
|
+
const result = {};
|
|
222
|
+
// Map module
|
|
223
|
+
if (opts.module) {
|
|
224
|
+
const moduleValue = MODULE_MAP[opts.module];
|
|
225
|
+
if (moduleValue !== undefined) {
|
|
226
|
+
result.module = moduleValue;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// Map moduleResolution
|
|
230
|
+
if (opts.moduleResolution) {
|
|
231
|
+
const moduleResValue = MODULE_RESOLUTION_MAP[opts.moduleResolution];
|
|
232
|
+
if (moduleResValue !== undefined) {
|
|
233
|
+
result.moduleResolution = moduleResValue;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Map target
|
|
237
|
+
if (opts.target) {
|
|
238
|
+
const targetValue = TARGET_MAP[opts.target];
|
|
239
|
+
if (targetValue !== undefined) {
|
|
240
|
+
result.target = targetValue;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Pass through other options directly
|
|
244
|
+
if (opts.lib)
|
|
245
|
+
result.lib = opts.lib;
|
|
246
|
+
if (opts.types)
|
|
247
|
+
result.types = opts.types;
|
|
248
|
+
if (opts.typeRoots)
|
|
249
|
+
result.typeRoots = opts.typeRoots;
|
|
250
|
+
if (opts.strict !== undefined)
|
|
251
|
+
result.strict = opts.strict;
|
|
252
|
+
if (opts.esModuleInterop !== undefined)
|
|
253
|
+
result.esModuleInterop = opts.esModuleInterop;
|
|
254
|
+
if (opts.skipLibCheck !== undefined)
|
|
255
|
+
result.skipLibCheck = opts.skipLibCheck;
|
|
256
|
+
if (opts.declaration !== undefined)
|
|
257
|
+
result.declaration = opts.declaration;
|
|
258
|
+
if (opts.declarationMap !== undefined)
|
|
259
|
+
result.declarationMap = opts.declarationMap;
|
|
260
|
+
if (opts.paths)
|
|
261
|
+
result.paths = opts.paths;
|
|
262
|
+
if (opts.baseUrl)
|
|
263
|
+
result.baseUrl = opts.baseUrl;
|
|
264
|
+
// Map jsx if present
|
|
265
|
+
if (opts.jsx) {
|
|
266
|
+
const jsxMap = {
|
|
267
|
+
'preserve': 1,
|
|
268
|
+
'react': 2,
|
|
269
|
+
'react-native': 3,
|
|
270
|
+
'react-jsx': 4,
|
|
271
|
+
'react-jsxdev': 5,
|
|
272
|
+
};
|
|
273
|
+
const jsxValue = jsxMap[opts.jsx.toLowerCase()];
|
|
274
|
+
if (jsxValue !== undefined) {
|
|
275
|
+
result.jsx = jsxValue;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Get the ts-morph Project, building it on first call.
|
|
282
|
+
*
|
|
283
|
+
* @returns The Project instance
|
|
284
|
+
* @throws Error if load() has not succeeded, or if the build fails
|
|
285
|
+
*/
|
|
286
|
+
getProject() {
|
|
287
|
+
if (!this.initialized || !this.buildProject) {
|
|
288
|
+
throw new Error('Project not initialized. Call load() first and ensure it succeeds.');
|
|
289
|
+
}
|
|
290
|
+
if (!this.project) {
|
|
291
|
+
const startTime = performance.now();
|
|
292
|
+
this.project = this.buildProject();
|
|
293
|
+
const buildTimeMs = Math.round(performance.now() - startTime);
|
|
294
|
+
this.log(`Project built in ${buildTimeMs}ms ` +
|
|
295
|
+
`(${this.project.getSourceFiles().length} source files)`);
|
|
296
|
+
}
|
|
297
|
+
return this.project;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Check if the project has been successfully initialized
|
|
301
|
+
*/
|
|
302
|
+
isInitialized() {
|
|
303
|
+
return this.initialized;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Get initialization time in milliseconds
|
|
307
|
+
*/
|
|
308
|
+
getInitTimeMs() {
|
|
309
|
+
return this.initTimeMs;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Get the last initialization error, if any
|
|
313
|
+
*/
|
|
314
|
+
getInitError() {
|
|
315
|
+
return this.initError;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Get the repository root path
|
|
319
|
+
*/
|
|
320
|
+
getRepoRoot() {
|
|
321
|
+
return this.repoRoot;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Get the pinned dependencies, if any
|
|
325
|
+
*/
|
|
326
|
+
getPinnedDependencies() {
|
|
327
|
+
return this.pinnedDependencies;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Find the tsconfig.json file to use
|
|
331
|
+
*
|
|
332
|
+
* @returns Absolute path to tsconfig.json, or undefined if not found
|
|
333
|
+
*/
|
|
334
|
+
findTsConfig() {
|
|
335
|
+
// If a specific path was provided, try to use it
|
|
336
|
+
if (this.tsconfigPath) {
|
|
337
|
+
if (fs.existsSync(this.tsconfigPath)) {
|
|
338
|
+
return this.tsconfigPath;
|
|
339
|
+
}
|
|
340
|
+
this.log(`Specified tsconfig not found: ${this.tsconfigPath}`);
|
|
341
|
+
}
|
|
342
|
+
// Try common tsconfig locations
|
|
343
|
+
const candidates = [
|
|
344
|
+
path.join(this.repoRoot, 'tsconfig.json'),
|
|
345
|
+
path.join(this.repoRoot, 'tsconfig.build.json'),
|
|
346
|
+
path.join(this.repoRoot, 'tsconfig.app.json'),
|
|
347
|
+
];
|
|
348
|
+
for (const candidate of candidates) {
|
|
349
|
+
if (fs.existsSync(candidate)) {
|
|
350
|
+
return candidate;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return undefined;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Add source files from common project locations when no tsconfig is found.
|
|
357
|
+
*
|
|
358
|
+
* Files are globbed and added one by one rather than through
|
|
359
|
+
* `addSourceFilesAtPaths`, which registers every descendant directory of any
|
|
360
|
+
* directory a pattern hit in. One `.ts` file at the repo root is enough to
|
|
361
|
+
* make that walk the entire installed tree: on a large monorepo with
|
|
362
|
+
* dependencies installed it cost 40 s and added nothing the compiler needs
|
|
363
|
+
* (carrick#749).
|
|
364
|
+
*/
|
|
365
|
+
addDefaultSourceFiles(project) {
|
|
366
|
+
const globs = DEFAULT_SOURCE_PATTERNS.map((pattern) => path.join(this.repoRoot, pattern));
|
|
367
|
+
// Negated last so it applies to every pattern above it.
|
|
368
|
+
globs.push(`!${path.join(this.repoRoot, '**/node_modules/**')}`);
|
|
369
|
+
let filePaths = [];
|
|
370
|
+
try {
|
|
371
|
+
filePaths = project.getFileSystem().globSync(globs);
|
|
372
|
+
}
|
|
373
|
+
catch (err) {
|
|
374
|
+
this.logError(`Default source file glob failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
for (const filePath of filePaths) {
|
|
378
|
+
try {
|
|
379
|
+
project.addSourceFileAtPath(filePath);
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
// A file that disappeared between glob and read is not fatal.
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
this.log(`Added ${project.getSourceFiles().length} source files from default patterns`);
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Log a message to stderr (stdout is reserved for JSON responses)
|
|
389
|
+
*/
|
|
390
|
+
log(message) {
|
|
391
|
+
console.error(`[sidecar:project-loader] ${message}`);
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Log an error message to stderr
|
|
395
|
+
*/
|
|
396
|
+
logError(message) {
|
|
397
|
+
console.error(`[sidecar:project-loader:error] ${message}`);
|
|
398
|
+
}
|
|
399
|
+
}
|