@deepseek-ai/dsh-typert-generator 0.2.1-alpha.1 → 0.2.1-alpha.3
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/README.i18n.yaml +16 -10
- package/README.md +30 -3
- package/README.zh.md +30 -3
- package/lib/index.js +714 -105
- package/lib/types/analyzer.d.ts +34 -2
- package/lib/types/analyzer.js +201 -49
- package/lib/types/artifact-files.d.ts +30 -0
- package/lib/types/artifact-files.js +57 -0
- package/lib/types/build-output-guard.d.ts +61 -0
- package/lib/types/build-output-guard.js +193 -0
- package/lib/types/emitter.d.ts +3 -0
- package/lib/types/emitter.js +148 -48
- package/lib/types/index.d.ts +3 -2
- package/lib/types/index.js +2 -1
- package/lib/types/model.d.ts +6 -2
- package/lib/types/tool-schema.d.ts +20 -0
- package/lib/types/tool-schema.js +156 -0
- package/lib/types/tsdown-plugin.d.ts +21 -6
- package/lib/types/tsdown-plugin.js +176 -77
- package/lib/types/worker.d.ts +28 -0
- package/lib/types/worker.js +49 -0
- package/lib/types/workspace.d.ts +34 -2
- package/lib/types/workspace.js +55 -7
- package/package.json +7 -6
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detection of workspace build outputs loaded into an analyzed face program,
|
|
3
|
+
* and of workspace imports that resolve to nothing. Typert reads workspace
|
|
4
|
+
* packages only from source when a caller rejects build outputs, so its
|
|
5
|
+
* result cannot depend on whether, or when, package compilation ran: an
|
|
6
|
+
* import that reaches `lib/` on a compiled tree resolves to nothing on a
|
|
7
|
+
* clean one, and both are rejected.
|
|
8
|
+
* @module @deepseek-ai/dsh-typert-generator/build-output-guard
|
|
9
|
+
*/
|
|
10
|
+
import ts from 'typescript';
|
|
11
|
+
/** One program file that a workspace package's `lib/` holds, with the imports that load it. */
|
|
12
|
+
export interface BuildOutputLoad {
|
|
13
|
+
/** Absolute path of the build output. */
|
|
14
|
+
readonly file: string;
|
|
15
|
+
/** Program files outside build outputs whose import specifier resolves to {@link file}. */
|
|
16
|
+
readonly importers: readonly BuildOutputImporter[];
|
|
17
|
+
/** Absolute path of the workspace source compiled to {@link file}, when one exists. */
|
|
18
|
+
readonly source: string | undefined;
|
|
19
|
+
}
|
|
20
|
+
/** One import of a build output from a program file outside build outputs. */
|
|
21
|
+
export interface BuildOutputImporter {
|
|
22
|
+
/** Absolute path of the importing file. */
|
|
23
|
+
readonly file: string;
|
|
24
|
+
/** Import specifier as written. */
|
|
25
|
+
readonly specifier: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* List the program files that a workspace package holds under its `lib/`
|
|
29
|
+
* directory. A package is the nearest directory with a `package.json` at or
|
|
30
|
+
* below `root`; files under `node_modules` are external. Importers are found
|
|
31
|
+
* by resolving each program file's import specifiers again, so this pass
|
|
32
|
+
* costs extra only when an output is present.
|
|
33
|
+
* @param program - the analyzed face program.
|
|
34
|
+
* @param root - workspace root.
|
|
35
|
+
* @param host - module resolution host of the program.
|
|
36
|
+
* @returns loads in file-path order; empty when every workspace file comes from source.
|
|
37
|
+
*/
|
|
38
|
+
export declare function findBuildOutputLoads(program: ts.Program, root: string, host: ts.ModuleResolutionHost): BuildOutputLoad[];
|
|
39
|
+
/** One import of a workspace package from a program source file that module resolution cannot resolve. */
|
|
40
|
+
export interface UnresolvedWorkspaceImport {
|
|
41
|
+
/** Absolute path of the importing file. */
|
|
42
|
+
readonly file: string;
|
|
43
|
+
/** Import specifier as written. */
|
|
44
|
+
readonly specifier: string;
|
|
45
|
+
/** Absolute path of the workspace source the specifier's package export compiles from, when one exists. */
|
|
46
|
+
readonly source: string | undefined;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* List the imports in workspace program files that name a workspace package
|
|
50
|
+
* and resolve to nothing. A workspace package is one whose `node_modules` link,
|
|
51
|
+
* found from the importing file's directories, leads inside `root` outside any
|
|
52
|
+
* `node_modules`; without that link or a `paths` alias the package cannot be
|
|
53
|
+
* identified, and type checking reports the import. The source of a subpath is
|
|
54
|
+
* derived from the package's `exports` `types` condition under `lib/types/`.
|
|
55
|
+
* @param program - the analyzed face program.
|
|
56
|
+
* @param root - workspace root.
|
|
57
|
+
* @param host - module resolution host of the program.
|
|
58
|
+
* @returns unresolved imports in file and specifier order.
|
|
59
|
+
*/
|
|
60
|
+
export declare function findUnresolvedWorkspaceImports(program: ts.Program, root: string, host: ts.ModuleResolutionHost): UnresolvedWorkspaceImport[];
|
|
61
|
+
//# sourceMappingURL=build-output-guard.d.ts.map
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detection of workspace build outputs loaded into an analyzed face program,
|
|
3
|
+
* and of workspace imports that resolve to nothing. Typert reads workspace
|
|
4
|
+
* packages only from source when a caller rejects build outputs, so its
|
|
5
|
+
* result cannot depend on whether, or when, package compilation ran: an
|
|
6
|
+
* import that reaches `lib/` on a compiled tree resolves to nothing on a
|
|
7
|
+
* clean one, and both are rejected.
|
|
8
|
+
* @module @deepseek-ai/dsh-typert-generator/build-output-guard
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
11
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
12
|
+
import ts from 'typescript';
|
|
13
|
+
/**
|
|
14
|
+
* List the program files that a workspace package holds under its `lib/`
|
|
15
|
+
* directory. A package is the nearest directory with a `package.json` at or
|
|
16
|
+
* below `root`; files under `node_modules` are external. Importers are found
|
|
17
|
+
* by resolving each program file's import specifiers again, so this pass
|
|
18
|
+
* costs extra only when an output is present.
|
|
19
|
+
* @param program - the analyzed face program.
|
|
20
|
+
* @param root - workspace root.
|
|
21
|
+
* @param host - module resolution host of the program.
|
|
22
|
+
* @returns loads in file-path order; empty when every workspace file comes from source.
|
|
23
|
+
*/
|
|
24
|
+
export function findBuildOutputLoads(program, root, host) {
|
|
25
|
+
const classifier = new BuildOutputClassifier(root);
|
|
26
|
+
const outputs = new Map();
|
|
27
|
+
for (const sourceFile of program.getSourceFiles()) {
|
|
28
|
+
if (classifier.isBuildOutput(sourceFile.fileName))
|
|
29
|
+
outputs.set(classifier.canonical(sourceFile.fileName), []);
|
|
30
|
+
}
|
|
31
|
+
if (outputs.size === 0)
|
|
32
|
+
return [];
|
|
33
|
+
const options = program.getCompilerOptions();
|
|
34
|
+
for (const sourceFile of program.getSourceFiles()) {
|
|
35
|
+
if (!classifier.isWorkspaceFile(sourceFile.fileName) || classifier.isBuildOutput(sourceFile.fileName))
|
|
36
|
+
continue;
|
|
37
|
+
for (const { fileName: specifier } of ts.preProcessFile(sourceFile.text, true, true).importedFiles) {
|
|
38
|
+
const resolved = ts.resolveModuleName(specifier, sourceFile.fileName, options, host).resolvedModule;
|
|
39
|
+
const importers = resolved === undefined ? undefined : outputs.get(classifier.canonical(resolved.resolvedFileName));
|
|
40
|
+
importers?.push({ file: classifier.display(sourceFile.fileName), specifier });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return [...outputs]
|
|
44
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
45
|
+
.map(([file, importers]) => ({ file: classifier.display(file), importers, source: classifier.sourceFor(file) }));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* List the imports in workspace program files that name a workspace package
|
|
49
|
+
* and resolve to nothing. A workspace package is one whose `node_modules` link,
|
|
50
|
+
* found from the importing file's directories, leads inside `root` outside any
|
|
51
|
+
* `node_modules`; without that link or a `paths` alias the package cannot be
|
|
52
|
+
* identified, and type checking reports the import. The source of a subpath is
|
|
53
|
+
* derived from the package's `exports` `types` condition under `lib/types/`.
|
|
54
|
+
* @param program - the analyzed face program.
|
|
55
|
+
* @param root - workspace root.
|
|
56
|
+
* @param host - module resolution host of the program.
|
|
57
|
+
* @returns unresolved imports in file and specifier order.
|
|
58
|
+
*/
|
|
59
|
+
export function findUnresolvedWorkspaceImports(program, root, host) {
|
|
60
|
+
const classifier = new BuildOutputClassifier(root);
|
|
61
|
+
const options = program.getCompilerOptions();
|
|
62
|
+
const cache = ts.createModuleResolutionCache(root, file => file, options);
|
|
63
|
+
const found = [];
|
|
64
|
+
for (const sourceFile of program.getSourceFiles()) {
|
|
65
|
+
if (!classifier.isWorkspaceFile(sourceFile.fileName) || classifier.isBuildOutput(sourceFile.fileName))
|
|
66
|
+
continue;
|
|
67
|
+
for (const { fileName: specifier } of ts.preProcessFile(sourceFile.text, true, true).importedFiles) {
|
|
68
|
+
if (specifier.startsWith('.') || isAbsolute(specifier) || specifier.includes(':'))
|
|
69
|
+
continue;
|
|
70
|
+
if (ts.resolveModuleName(specifier, sourceFile.fileName, options, host, cache).resolvedModule !== undefined)
|
|
71
|
+
continue;
|
|
72
|
+
const packageRoot = classifier.linkedWorkspacePackage(sourceFile.fileName, packageNameOf(specifier));
|
|
73
|
+
if (packageRoot === undefined)
|
|
74
|
+
continue;
|
|
75
|
+
const source = exportSource(packageRoot, specifier);
|
|
76
|
+
found.push({
|
|
77
|
+
file: classifier.display(sourceFile.fileName),
|
|
78
|
+
specifier,
|
|
79
|
+
source: source === undefined ? undefined : classifier.display(source),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return found.sort((left, right) => left.file.localeCompare(right.file) || left.specifier.localeCompare(right.specifier));
|
|
84
|
+
}
|
|
85
|
+
function packageNameOf(specifier) {
|
|
86
|
+
const parts = specifier.split('/');
|
|
87
|
+
return parts.slice(0, specifier.startsWith('@') ? 2 : 1).join('/');
|
|
88
|
+
}
|
|
89
|
+
function exportSource(packageRoot, specifier) {
|
|
90
|
+
const packageName = packageNameOf(specifier);
|
|
91
|
+
const subpath = specifier === packageName ? '.' : `.${specifier.slice(packageName.length)}`;
|
|
92
|
+
let manifest;
|
|
93
|
+
try {
|
|
94
|
+
manifest = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'));
|
|
95
|
+
}
|
|
96
|
+
catch (_unreadableManifest) {
|
|
97
|
+
// The error names the import without a suggested source path.
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
const entry = property(property(manifest, 'exports'), subpath);
|
|
101
|
+
const types = typeof entry === 'string' ? entry : property(entry, 'types');
|
|
102
|
+
if (typeof types !== 'string' || !types.startsWith('./lib/types/'))
|
|
103
|
+
return undefined;
|
|
104
|
+
const stem = join(packageRoot, 'src', types.slice('./lib/types/'.length).replace(/\.d\.([cm]?)ts$/u, '.$1ts'));
|
|
105
|
+
return [stem, `${stem}x`].find(candidate => existsSync(candidate));
|
|
106
|
+
}
|
|
107
|
+
function property(value, key) {
|
|
108
|
+
return typeof value === 'object' && value !== null ? value[key] : undefined;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Classifies program files by their real paths, because module resolution
|
|
112
|
+
* reports symbolic-link targets and Windows long names while the workspace
|
|
113
|
+
* root may be spelled through a link or an 8.3 short name; reported paths are
|
|
114
|
+
* spelled under the caller's root.
|
|
115
|
+
*/
|
|
116
|
+
class BuildOutputClassifier {
|
|
117
|
+
root;
|
|
118
|
+
packageRoots = new Map();
|
|
119
|
+
realPaths = new Map();
|
|
120
|
+
realRoot;
|
|
121
|
+
constructor(root) {
|
|
122
|
+
this.root = root;
|
|
123
|
+
this.realRoot = this.canonical(root);
|
|
124
|
+
}
|
|
125
|
+
canonical(file) {
|
|
126
|
+
const absolute = resolve(file);
|
|
127
|
+
const cached = this.realPaths.get(absolute);
|
|
128
|
+
if (cached !== undefined)
|
|
129
|
+
return cached;
|
|
130
|
+
let real = absolute;
|
|
131
|
+
try {
|
|
132
|
+
real = realpathSync.native(absolute);
|
|
133
|
+
}
|
|
134
|
+
catch (_missingPath) {
|
|
135
|
+
// A path that does not exist, such as an unresolved candidate, keeps its resolved spelling.
|
|
136
|
+
}
|
|
137
|
+
this.realPaths.set(absolute, real);
|
|
138
|
+
return real;
|
|
139
|
+
}
|
|
140
|
+
display(file) {
|
|
141
|
+
const path = relative(this.realRoot, this.canonical(file));
|
|
142
|
+
return path.startsWith('..') || isAbsolute(path) ? resolve(file) : join(this.root, path);
|
|
143
|
+
}
|
|
144
|
+
isWorkspaceFile(file) {
|
|
145
|
+
const path = relative(this.realRoot, this.canonical(file));
|
|
146
|
+
return path !== '' && !path.startsWith('..') && !isAbsolute(path) && !path.split(sep).includes('node_modules');
|
|
147
|
+
}
|
|
148
|
+
isBuildOutput(file) {
|
|
149
|
+
if (!this.isWorkspaceFile(file))
|
|
150
|
+
return false;
|
|
151
|
+
const real = this.canonical(file);
|
|
152
|
+
const packageRoot = this.packageRoot(dirname(real));
|
|
153
|
+
return packageRoot !== undefined && relative(packageRoot, real).split(sep)[0] === 'lib';
|
|
154
|
+
}
|
|
155
|
+
sourceFor(file) {
|
|
156
|
+
const real = this.canonical(file);
|
|
157
|
+
const packageRoot = this.packageRoot(dirname(real));
|
|
158
|
+
if (packageRoot === undefined)
|
|
159
|
+
return undefined;
|
|
160
|
+
const [lib, types, ...rest] = relative(packageRoot, real).split(sep);
|
|
161
|
+
const last = rest.at(-1);
|
|
162
|
+
if (lib !== 'lib' || types !== 'types' || last === undefined)
|
|
163
|
+
return undefined;
|
|
164
|
+
const stem = join(packageRoot, 'src', ...rest.slice(0, -1), last.replace(/\.d\.([cm]?)ts$/u, '.$1ts'));
|
|
165
|
+
const source = [stem, `${stem}x`].find(candidate => existsSync(candidate));
|
|
166
|
+
return source === undefined ? undefined : this.display(source);
|
|
167
|
+
}
|
|
168
|
+
linkedWorkspacePackage(importer, packageName) {
|
|
169
|
+
let directory = dirname(resolve(importer));
|
|
170
|
+
for (;;) {
|
|
171
|
+
const link = join(directory, 'node_modules', packageName);
|
|
172
|
+
if (existsSync(link)) {
|
|
173
|
+
const target = this.canonical(link);
|
|
174
|
+
return this.isWorkspaceFile(join(target, 'package.json')) ? target : undefined;
|
|
175
|
+
}
|
|
176
|
+
if (this.canonical(directory) === this.realRoot || dirname(directory) === directory)
|
|
177
|
+
return undefined;
|
|
178
|
+
directory = dirname(directory);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
packageRoot(directory) {
|
|
182
|
+
if (this.packageRoots.has(directory))
|
|
183
|
+
return this.packageRoots.get(directory);
|
|
184
|
+
const found = existsSync(join(directory, 'package.json'))
|
|
185
|
+
? directory
|
|
186
|
+
: directory === this.realRoot || dirname(directory) === directory
|
|
187
|
+
? undefined
|
|
188
|
+
: this.packageRoot(dirname(directory));
|
|
189
|
+
this.packageRoots.set(directory, found);
|
|
190
|
+
return found;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=build-output-guard.js.map
|
package/lib/types/emitter.d.ts
CHANGED
|
@@ -44,6 +44,9 @@ export declare class FaceModelEmitter {
|
|
|
44
44
|
private renderJs;
|
|
45
45
|
private renderDts;
|
|
46
46
|
private emitRemote;
|
|
47
|
+
private remoteMethodTypes;
|
|
48
|
+
private remoteInputType;
|
|
49
|
+
private remoteInfoLiteral;
|
|
47
50
|
private invocationLiteral;
|
|
48
51
|
private renderRemoteDts;
|
|
49
52
|
private pushRemoteSignature;
|