carrick 0.3.97 → 0.3.99
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/bin/carrick.mjs +19 -14
- package/dist/auth/run.d.ts +1 -1
- package/dist/auth/run.js +1 -1
- package/dist/contract.d.ts +15 -0
- package/dist/contract.js.map +1 -1
- package/dist/global-install.js +6 -5
- package/dist/global-install.js.map +1 -1
- package/dist/hook/reuse.d.ts +8 -3
- package/dist/hook/reuse.js +15 -3
- package/dist/hook/reuse.js.map +1 -1
- package/dist/init/connect.js +1 -1
- package/dist/init/connect.js.map +1 -1
- package/dist/init/doctor.js +5 -2
- package/dist/init/doctor.js.map +1 -1
- package/dist/init/hosted.d.ts +18 -1
- package/dist/init/hosted.js +84 -16
- package/dist/init/hosted.js.map +1 -1
- package/dist/init/install-id.d.ts +2 -2
- package/dist/init/install-id.js +3 -3
- package/dist/init/install-id.js.map +1 -1
- package/dist/init/mcp.d.ts +2 -2
- package/dist/init/mcp.js +2 -2
- package/dist/init/mcp.js.map +1 -1
- package/dist/init/outdated.d.ts +1 -1
- package/dist/init/outdated.js +1 -1
- package/dist/init/outdated.js.map +1 -1
- package/dist/init/output.d.ts +18 -1
- package/dist/init/output.js +40 -1
- package/dist/init/output.js.map +1 -1
- package/dist/init/remove.d.ts +71 -2
- package/dist/init/remove.js +474 -145
- package/dist/init/remove.js.map +1 -1
- package/dist/init/repo-copies.d.ts +16 -4
- package/dist/init/repo-copies.js +20 -6
- package/dist/init/repo-copies.js.map +1 -1
- package/dist/init/run.d.ts +12 -16
- package/dist/init/run.js +25 -42
- package/dist/init/run.js.map +1 -1
- package/dist/update.js +1 -0
- package/dist/update.js.map +1 -1
- package/package.json +6 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/sidecar/dist/src/bundler.d.ts +2 -0
- package/sidecar/dist/src/bundler.js +115 -39
- package/sidecar/dist/src/capture/index.d.ts +1 -0
- package/sidecar/dist/src/capture/index.js +91 -10
- package/sidecar/dist/src/capture/project-references.d.ts +67 -0
- package/sidecar/dist/src/capture/project-references.js +157 -0
- package/sidecar/dist/src/client-semantics.d.ts +36 -0
- package/sidecar/dist/src/client-semantics.js +1008 -0
- package/sidecar/dist/src/index.js +110 -10
- package/sidecar/dist/src/module-format.d.ts +32 -0
- package/sidecar/dist/src/module-format.js +102 -0
- package/sidecar/dist/src/project-loader.d.ts +24 -0
- package/sidecar/dist/src/project-loader.js +53 -5
- package/sidecar/dist/src/types.d.ts +86 -4
- package/sidecar/dist/src/validators.d.ts +463 -0
- package/sidecar/dist/src/validators.js +50 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which project types a file of a service whose tsconfig references others
|
|
3
|
+
* (carrick#1604).
|
|
4
|
+
*
|
|
5
|
+
* A "solution" tsconfig (`"files": []` plus `references`) carries no compiler
|
|
6
|
+
* options: they live in the projects it references. Parsing the named file
|
|
7
|
+
* alone typed every file with none, so `customConditions`, `paths` and the
|
|
8
|
+
* module resolution mode never applied and workspace imports fell through to
|
|
9
|
+
* unbuilt output.
|
|
10
|
+
*
|
|
11
|
+
* TypeScript's editor answers "which project types this file" by searching
|
|
12
|
+
* the named config, then its references depth-first in declared order, for
|
|
13
|
+
* the first project whose file list includes the file. That project is the
|
|
14
|
+
* file's owner here too, and a file is only ever typed under its owner's
|
|
15
|
+
* options: the loader builds one program per owner it is asked about, and
|
|
16
|
+
* the capture types each anchor in its owner's program. A file no reachable
|
|
17
|
+
* project includes is owned by the named config, which is how it was typed
|
|
18
|
+
* before references were read.
|
|
19
|
+
*/
|
|
20
|
+
import * as path from 'node:path';
|
|
21
|
+
import ts from 'typescript';
|
|
22
|
+
/** Parse one config; throws on a file that cannot be read or parsed. */
|
|
23
|
+
function parseConfig(configPath, extendedConfigCache) {
|
|
24
|
+
const host = {
|
|
25
|
+
...ts.sys,
|
|
26
|
+
onUnRecoverableConfigFileDiagnostic: (d) => {
|
|
27
|
+
throw new Error(ts.flattenDiagnosticMessageText(d.messageText, '\n'));
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, host, extendedConfigCache);
|
|
31
|
+
if (!parsed)
|
|
32
|
+
throw new Error(`failed to parse ${configPath}`);
|
|
33
|
+
return { configPath: path.resolve(configPath), parsed };
|
|
34
|
+
}
|
|
35
|
+
const fileKey = (file) => path.resolve(file);
|
|
36
|
+
/**
|
|
37
|
+
* The projects a named config reaches and the owner of each file among them.
|
|
38
|
+
* References are read only when a file the named config does not include is
|
|
39
|
+
* asked about, so a service whose files the named config includes costs one
|
|
40
|
+
* parse, as before.
|
|
41
|
+
*/
|
|
42
|
+
export class ProjectGraph {
|
|
43
|
+
named;
|
|
44
|
+
/** References that could not be read, one line each. */
|
|
45
|
+
diagnostics = [];
|
|
46
|
+
extendedConfigCache = new Map();
|
|
47
|
+
walked;
|
|
48
|
+
namedFiles;
|
|
49
|
+
/** Throws when the named config cannot be read, as a bare parse would. */
|
|
50
|
+
constructor(configPath) {
|
|
51
|
+
this.named = parseConfig(configPath, this.extendedConfigCache);
|
|
52
|
+
this.namedFiles = new Set(this.named.parsed.fileNames.map(fileKey));
|
|
53
|
+
}
|
|
54
|
+
/** Whether the named config references other projects at all. */
|
|
55
|
+
get hasReferences() {
|
|
56
|
+
return (this.named.parsed.projectReferences?.length ?? 0) > 0;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The project that owns `file`: the first in search order whose file list
|
|
60
|
+
* includes it (the named config, then its references depth-first in
|
|
61
|
+
* declared order; a config reached twice is visited once). The named config
|
|
62
|
+
* when none does.
|
|
63
|
+
*/
|
|
64
|
+
ownerOf(file) {
|
|
65
|
+
const key = fileKey(file);
|
|
66
|
+
if (this.namedFiles.has(key) || !this.hasReferences)
|
|
67
|
+
return this.named;
|
|
68
|
+
return this.projects().find((entry) => entry.files.has(key))?.project ?? this.named;
|
|
69
|
+
}
|
|
70
|
+
/** Position in search order, 0 for the named config. */
|
|
71
|
+
rank(project) {
|
|
72
|
+
if (project === this.named)
|
|
73
|
+
return 0;
|
|
74
|
+
const index = this.projects().findIndex((entry) => entry.project === project);
|
|
75
|
+
return index === -1 ? Number.MAX_SAFE_INTEGER : index;
|
|
76
|
+
}
|
|
77
|
+
projects() {
|
|
78
|
+
if (this.walked)
|
|
79
|
+
return this.walked;
|
|
80
|
+
const visited = new Set([this.named.configPath]);
|
|
81
|
+
const order = [];
|
|
82
|
+
const visit = (project, files) => {
|
|
83
|
+
order.push({ project, files });
|
|
84
|
+
for (const reference of project.parsed.projectReferences ?? []) {
|
|
85
|
+
const referencePath = path.resolve(ts.resolveProjectReferencePath(reference));
|
|
86
|
+
if (visited.has(referencePath))
|
|
87
|
+
continue;
|
|
88
|
+
visited.add(referencePath);
|
|
89
|
+
let child;
|
|
90
|
+
try {
|
|
91
|
+
child = parseConfig(referencePath, this.extendedConfigCache);
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
this.diagnostics.push(`referenced tsconfig ${referencePath} (from ${project.configPath}) could not be read and was skipped: ` +
|
|
95
|
+
(err instanceof Error ? err.message : String(err)));
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
visit(child, new Set(child.parsed.fileNames.map(fileKey)));
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
visit(this.named, this.namedFiles);
|
|
102
|
+
this.walked = order;
|
|
103
|
+
return order;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* For the loader: the config each file of the service is typed under. The
|
|
108
|
+
* returned function gives the owner's config path for a file, and the named
|
|
109
|
+
* config for no file; a reference that cannot be read goes to `report`. A named config that references nothing is not parsed
|
|
110
|
+
* here (`references` is never inherited through `extends`), so the function
|
|
111
|
+
* answers the named path for every file and the common case costs nothing.
|
|
112
|
+
*/
|
|
113
|
+
export function serviceConfigPath(configPath, report = () => { }) {
|
|
114
|
+
const raw = ts.readConfigFile(configPath, ts.sys.readFile).config;
|
|
115
|
+
if (!Array.isArray(raw?.references) || raw.references.length === 0) {
|
|
116
|
+
return () => configPath;
|
|
117
|
+
}
|
|
118
|
+
let graph;
|
|
119
|
+
return (file) => {
|
|
120
|
+
if (file === undefined)
|
|
121
|
+
return configPath;
|
|
122
|
+
graph ??= new ProjectGraph(configPath);
|
|
123
|
+
const reported = graph.diagnostics.length;
|
|
124
|
+
const owner = graph.ownerOf(file);
|
|
125
|
+
graph.diagnostics.slice(reported).forEach(report);
|
|
126
|
+
return owner === graph.named ? configPath : owner.configPath;
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/** Compiler options that only place output, so two projects differing only in these emit the same declarations. */
|
|
130
|
+
const OUTPUT_ONLY_OPTIONS = new Set([
|
|
131
|
+
'rootDir',
|
|
132
|
+
'outDir',
|
|
133
|
+
'declarationDir',
|
|
134
|
+
'outFile',
|
|
135
|
+
'composite',
|
|
136
|
+
'declaration',
|
|
137
|
+
'declarationMap',
|
|
138
|
+
'emitDeclarationOnly',
|
|
139
|
+
'incremental',
|
|
140
|
+
'tsBuildInfoFile',
|
|
141
|
+
'configFilePath',
|
|
142
|
+
'noEmit',
|
|
143
|
+
'sourceMap',
|
|
144
|
+
'inlineSourceMap',
|
|
145
|
+
'inlineSources',
|
|
146
|
+
]);
|
|
147
|
+
/**
|
|
148
|
+
* Whether two projects' options would emit a declaration the same way, so a
|
|
149
|
+
* type one of them names can be emitted by the other.
|
|
150
|
+
*/
|
|
151
|
+
export function emitsAlike(a, b) {
|
|
152
|
+
const relevant = (options) => JSON.stringify(Object.keys(options)
|
|
153
|
+
.filter((key) => !OUTPUT_ONLY_OPTIONS.has(key))
|
|
154
|
+
.sort()
|
|
155
|
+
.map((key) => [key, options[key]]));
|
|
156
|
+
return relevant(a.parsed.options) === relevant(b.parsed.options);
|
|
157
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `verify_client_semantics` (carrick#1564): check what a model said about an
|
|
3
|
+
* HTTP client library against the package's own type declarations.
|
|
4
|
+
*
|
|
5
|
+
* A claim names a member of a package export (or of an instance one of its
|
|
6
|
+
* factories returns) and says how that member is called: which option key
|
|
7
|
+
* carries the base URL, which HTTP method a verb sends, where the path, method
|
|
8
|
+
* and body sit. The scanner reads call sites through a claim only when this
|
|
9
|
+
* check verified it, and a verified claim becomes a fact that can fail a pull
|
|
10
|
+
* request check. So a claim is `verified` only when the declarations say so
|
|
11
|
+
* positively. `failed` means the declarations resolved and contradict the
|
|
12
|
+
* claim; `unchecked` means they could not be read, or say nothing at the place
|
|
13
|
+
* the claim needs (`any`, `unknown`, `{}`, an unconstrained type parameter).
|
|
14
|
+
* The scanner drops both.
|
|
15
|
+
*
|
|
16
|
+
* The export's type is read the way the service's own code would read it: a
|
|
17
|
+
* probe file in `from_dir` imports it, inside the service's program, under the
|
|
18
|
+
* service's compiler options. The declarations must be the installed
|
|
19
|
+
* package's: a `paths` alias or a `declare module` block the service writes
|
|
20
|
+
* for itself is not evidence about the library.
|
|
21
|
+
*/
|
|
22
|
+
import { type Project } from 'ts-morph';
|
|
23
|
+
import type { SemanticsCheck, SemanticsModule, SemanticsResult } from './types.js';
|
|
24
|
+
export interface VerifyResult {
|
|
25
|
+
semantics: SemanticsResult[];
|
|
26
|
+
modules: SemanticsModule[];
|
|
27
|
+
}
|
|
28
|
+
export declare class ClientSemanticsVerifier {
|
|
29
|
+
private readonly project;
|
|
30
|
+
constructor(project: Project);
|
|
31
|
+
/**
|
|
32
|
+
* Judge every check, in request order, spending at most `budgetMs`. Checks
|
|
33
|
+
* the budget does not reach come back `unchecked` with reason `budget`.
|
|
34
|
+
*/
|
|
35
|
+
run(fromDir: string, checks: SemanticsCheck[], budgetMs: number): VerifyResult;
|
|
36
|
+
}
|