@zyno-io/ts-reflection 26.803.2224
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.md +13 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +966 -0
- package/dist/reflection/annotations.d.ts +15 -0
- package/dist/reflection/annotations.d.ts.map +1 -0
- package/dist/reflection/compact-metadata.d.ts +18 -0
- package/dist/reflection/compact-metadata.d.ts.map +1 -0
- package/dist/reflection/conversion.d.ts +15 -0
- package/dist/reflection/conversion.d.ts.map +1 -0
- package/dist/reflection/deserializer.d.ts +15 -0
- package/dist/reflection/deserializer.d.ts.map +1 -0
- package/dist/reflection/errors.d.ts +8 -0
- package/dist/reflection/errors.d.ts.map +1 -0
- package/dist/reflection/index.d.ts +10 -0
- package/dist/reflection/index.d.ts.map +1 -0
- package/dist/reflection/metadata-store.d.ts +20 -0
- package/dist/reflection/metadata-store.d.ts.map +1 -0
- package/dist/reflection/model.d.ts +273 -0
- package/dist/reflection/model.d.ts.map +1 -0
- package/dist/reflection/primitive-conversion.d.ts +2 -0
- package/dist/reflection/primitive-conversion.d.ts.map +1 -0
- package/dist/reflection/reflection-class.d.ts +60 -0
- package/dist/reflection/reflection-class.d.ts.map +1 -0
- package/dist/reflection/type-utils.d.ts +34 -0
- package/dist/reflection/type-utils.d.ts.map +1 -0
- package/dist/type-compiler/download-prebuilt.cjs +114 -0
- package/dist/type-compiler/go/ast_expression.go +187 -0
- package/dist/type-compiler/go/ast_metadata.go +388 -0
- package/dist/type-compiler/go/collect.go +963 -0
- package/dist/type-compiler/go/compact_metadata.go +553 -0
- package/dist/type-compiler/go/emission_plan.go +340 -0
- package/dist/type-compiler/go/emit_ast.go +557 -0
- package/dist/type-compiler/go/emit_ast_test.go +558 -0
- package/dist/type-compiler/go/go.mod +10 -0
- package/dist/type-compiler/go/plugin.go +359 -0
- package/dist/type-compiler/go/plugin_test.go +1206 -0
- package/dist/type-compiler/go/precompute.go +86 -0
- package/dist/type-compiler/go/receive_type.go +912 -0
- package/dist/type-compiler/go/resolve.go +265 -0
- package/dist/type-compiler/go/source_scan.go +51 -0
- package/dist/type-compiler/go/text_parse.go +734 -0
- package/dist/type-compiler/go/type_expr.go +1291 -0
- package/dist/type-compiler/go/typia_expr.go +2316 -0
- package/dist/type-compiler/index.cjs +43 -0
- package/dist/type-compiler/pnp.cjs +474 -0
- package/dist/type-compiler/prebuilt.cjs +324 -0
- package/dist/type-metadata-runtime.cjs +1 -0
- package/dist/type-metadata-runtime.d.ts +2 -0
- package/dist/type-metadata-runtime.d.ts.map +1 -0
- package/dist/type-metadata-runtime.js +107 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/primitives.d.ts +33 -0
- package/dist/types/primitives.d.ts.map +1 -0
- package/dist/types/runtime.d.ts +2 -0
- package/dist/types/runtime.d.ts.map +1 -0
- package/dist/types/type-annotations.d.ts +28 -0
- package/dist/types/type-annotations.d.ts.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* oxlint-disable typescript/no-require-imports -- ttsc loads this descriptor as CommonJS. */
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const { tryInstallPrebuiltTypeCompiler } = require('./prebuilt.cjs');
|
|
5
|
+
const { preparePnpPaths } = require('./pnp.cjs');
|
|
6
|
+
|
|
7
|
+
module.exports = context => {
|
|
8
|
+
const dirname = resolvePluginDirectory(context?.dirname);
|
|
9
|
+
const pluginContext = { ...context, dirname };
|
|
10
|
+
const source = preparePnpPaths(pluginContext, path.join(dirname, 'go'));
|
|
11
|
+
tryInstallPrebuiltTypeCompiler(pluginContext, source);
|
|
12
|
+
return {
|
|
13
|
+
name: 'tsf-type-metadata',
|
|
14
|
+
source
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function resolvePluginDirectory(contextDirectory) {
|
|
19
|
+
const candidate = typeof contextDirectory === 'string' ? resolveVirtualPath(contextDirectory) : undefined;
|
|
20
|
+
return candidate !== undefined && belongsToReflectionPackage(candidate) ? candidate : resolveVirtualPath(__dirname);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function belongsToReflectionPackage(directory) {
|
|
24
|
+
let current = path.resolve(directory);
|
|
25
|
+
while (true) {
|
|
26
|
+
try {
|
|
27
|
+
if (JSON.parse(fs.readFileSync(path.join(current, 'package.json'), 'utf8')).name === '@zyno-io/ts-reflection') return true;
|
|
28
|
+
} catch {
|
|
29
|
+
// Continue searching ancestor directories.
|
|
30
|
+
}
|
|
31
|
+
const parent = path.dirname(current);
|
|
32
|
+
if (parent === current) return false;
|
|
33
|
+
current = parent;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolveVirtualPath(directory) {
|
|
38
|
+
try {
|
|
39
|
+
return require('pnpapi').resolveVirtual(directory) ?? directory;
|
|
40
|
+
} catch {
|
|
41
|
+
return directory;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/* oxlint-disable typescript/no-require-imports -- this helper runs inside the CommonJS ttsc descriptor. */
|
|
3
|
+
|
|
4
|
+
const crypto = require('node:crypto');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
const CACHE_DIRECTORY = '.yarn/tsf-pnp';
|
|
9
|
+
const EXTERNAL_ROOTS_FILE = 'external-package-roots.json';
|
|
10
|
+
const MATERIALIZATION_SCHEMA_VERSION = 2;
|
|
11
|
+
const TYPE_SCRIPT_SOURCE = /\.(?:[cm]?ts|tsx)$/i;
|
|
12
|
+
const PRUNED_DIRECTORIES = new Set(['.git', '.hg', '.svn', '.yarn', 'node_modules', 'coverage']);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Yarn's ZipFS is available to Node but not to ttsc's Go subprocess. Keep the
|
|
16
|
+
* handoff deliberately narrow: only this plugin's Go source and declaration
|
|
17
|
+
* files for packages imported by the project are copied to a writable cache.
|
|
18
|
+
*/
|
|
19
|
+
function preparePnpPaths(context, source) {
|
|
20
|
+
const pnpapi = loadPnpApi(context.projectRoot);
|
|
21
|
+
if (pnpapi === undefined) return source;
|
|
22
|
+
|
|
23
|
+
const cacheRoot = path.join(context.projectRoot, CACHE_DIRECTORY);
|
|
24
|
+
const materializedSource = isArchivePath(source) ? materializeGoSource(source, cacheRoot) : source;
|
|
25
|
+
const packageRoots = materializeImportedPackageRoots(context, pnpapi, cacheRoot);
|
|
26
|
+
writeExternalPackageRoots(cacheRoot, packageRoots);
|
|
27
|
+
return materializedSource;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function loadPnpApi(projectRoot) {
|
|
31
|
+
if (!hasPnpManifest(projectRoot)) return undefined;
|
|
32
|
+
try {
|
|
33
|
+
return require('pnpapi');
|
|
34
|
+
} catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function hasPnpManifest(projectRoot) {
|
|
40
|
+
let directory = path.resolve(projectRoot);
|
|
41
|
+
while (true) {
|
|
42
|
+
if (fs.existsSync(path.join(directory, '.pnp.cjs'))) return true;
|
|
43
|
+
const parent = path.dirname(directory);
|
|
44
|
+
if (parent === directory) return false;
|
|
45
|
+
directory = parent;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function materializeGoSource(source, cacheRoot) {
|
|
50
|
+
const key = hashDirectory(source);
|
|
51
|
+
const destination = path.join(cacheRoot, 'plugin-source', key);
|
|
52
|
+
if (fs.existsSync(path.join(destination, '.complete'))) return destination;
|
|
53
|
+
|
|
54
|
+
const temporary = `${destination}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
55
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
56
|
+
try {
|
|
57
|
+
copyDirectory(source, temporary);
|
|
58
|
+
fs.writeFileSync(path.join(temporary, '.complete'), '1\n');
|
|
59
|
+
publishDirectory(temporary, destination);
|
|
60
|
+
} finally {
|
|
61
|
+
fs.rmSync(temporary, { force: true, recursive: true });
|
|
62
|
+
}
|
|
63
|
+
return destination;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function copyDirectory(source, destination) {
|
|
67
|
+
fs.mkdirSync(destination, { recursive: true });
|
|
68
|
+
for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
|
|
69
|
+
if (PRUNED_DIRECTORIES.has(entry.name)) continue;
|
|
70
|
+
const from = path.join(source, entry.name);
|
|
71
|
+
const to = path.join(destination, entry.name);
|
|
72
|
+
if (entry.isDirectory()) copyDirectory(from, to);
|
|
73
|
+
else if (entry.isFile()) fs.copyFileSync(from, to);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function materializeImportedPackageRoots(context, pnpapi, cacheRoot) {
|
|
78
|
+
const roots = {};
|
|
79
|
+
const queued = new Set();
|
|
80
|
+
const queue = [];
|
|
81
|
+
const enqueue = (packageName, issuer, parent, direct) => {
|
|
82
|
+
if (packageName === '') return;
|
|
83
|
+
const key = `${issuer}\0${packageName}\0${parent ?? ''}`;
|
|
84
|
+
if (!queued.has(key)) {
|
|
85
|
+
queued.add(key);
|
|
86
|
+
queue.push({ packageName, issuer, parent, direct });
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
const projectIssuer = path.join(context.projectRoot, 'package.json');
|
|
90
|
+
for (const packageName of importedPackageNames(context)) {
|
|
91
|
+
enqueue(packageName, projectIssuer, undefined, true);
|
|
92
|
+
enqueue(definitelyTypedPackageName(packageName), projectIssuer, undefined, true);
|
|
93
|
+
}
|
|
94
|
+
for (const packageName of configuredTypePackages(context, pnpapi)) enqueue(packageName, projectIssuer, undefined, true);
|
|
95
|
+
enqueue('tslib', projectIssuer, undefined, true);
|
|
96
|
+
|
|
97
|
+
for (const request of queue) {
|
|
98
|
+
let packageRoot;
|
|
99
|
+
try {
|
|
100
|
+
packageRoot = pnpapi.resolveToUnqualified(request.packageName, request.issuer);
|
|
101
|
+
} catch {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (typeof packageRoot !== 'string' || !fs.existsSync(packageRoot)) continue;
|
|
105
|
+
try {
|
|
106
|
+
if (!fs.statSync(packageRoot).isDirectory()) continue;
|
|
107
|
+
} catch {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const normalized = cleanPackageRoot(packageRoot);
|
|
111
|
+
const source = materializeTypeScriptPackage(request.packageName, normalized, cacheRoot);
|
|
112
|
+
const bridgePackage = request.parent === undefined
|
|
113
|
+
? path.join(context.projectRoot, 'node_modules', ...request.packageName.split('/'))
|
|
114
|
+
: path.join(request.parent, 'node_modules', ...request.packageName.split('/'));
|
|
115
|
+
linkBridgePackage(bridgePackage, source);
|
|
116
|
+
for (const dependency of importedPackageNamesFromDirectory(normalized)) {
|
|
117
|
+
enqueue(dependency, path.join(normalized, 'package.json'), source, false);
|
|
118
|
+
}
|
|
119
|
+
if (request.direct) roots[request.packageName] = bridgePackage;
|
|
120
|
+
}
|
|
121
|
+
return roots;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function configuredTypePackages(context, pnpapi) {
|
|
125
|
+
const types = new Set();
|
|
126
|
+
const visit = (configPath, seen = new Set()) => {
|
|
127
|
+
if (seen.has(configPath)) return;
|
|
128
|
+
seen.add(configPath);
|
|
129
|
+
let config;
|
|
130
|
+
try {
|
|
131
|
+
config = parseJsonc(fs.readFileSync(configPath, 'utf8'));
|
|
132
|
+
} catch {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const inherited = typeof config.extends === 'string' ? [config.extends] : Array.isArray(config.extends) ? config.extends : [];
|
|
136
|
+
for (const specifier of inherited) {
|
|
137
|
+
if (typeof specifier !== 'string') continue;
|
|
138
|
+
const resolved = resolveExtendedConfig(specifier, configPath, pnpapi);
|
|
139
|
+
if (resolved !== undefined) visit(resolved, seen);
|
|
140
|
+
}
|
|
141
|
+
if (!Array.isArray(config.compilerOptions?.types)) return;
|
|
142
|
+
for (const name of config.compilerOptions.types) {
|
|
143
|
+
if (typeof name !== 'string') continue;
|
|
144
|
+
types.add(name.startsWith('@') || name.includes('/') ? name : `@types/${name}`);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
visit(context.tsconfig);
|
|
148
|
+
return [...types].sort();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function resolveExtendedConfig(specifier, issuer, pnpapi) {
|
|
152
|
+
if (specifier.startsWith('.') || path.isAbsolute(specifier)) {
|
|
153
|
+
const candidate = path.resolve(path.dirname(issuer), specifier);
|
|
154
|
+
return candidate.endsWith('.json') ? candidate : `${candidate}.json`;
|
|
155
|
+
}
|
|
156
|
+
for (const request of [specifier, `${specifier}.json`]) {
|
|
157
|
+
try {
|
|
158
|
+
const resolved = pnpapi.resolveRequest(request, issuer);
|
|
159
|
+
if (typeof resolved === 'string') return resolved;
|
|
160
|
+
} catch {
|
|
161
|
+
// Try the package root below.
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const root = cleanPackageRoot(pnpapi.resolveToUnqualified(specifier, issuer));
|
|
166
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
167
|
+
const config = typeof manifest.tsconfig === 'string' ? manifest.tsconfig : 'tsconfig.json';
|
|
168
|
+
return path.resolve(root, config);
|
|
169
|
+
} catch {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function parseJsonc(source) {
|
|
175
|
+
let output = '';
|
|
176
|
+
let quoted = false;
|
|
177
|
+
let quote = '';
|
|
178
|
+
let escaped = false;
|
|
179
|
+
for (let index = 0; index < source.length; index++) {
|
|
180
|
+
const char = source[index];
|
|
181
|
+
const next = source[index + 1];
|
|
182
|
+
if (quoted) {
|
|
183
|
+
output += char;
|
|
184
|
+
if (escaped) escaped = false;
|
|
185
|
+
else if (char === '\\') escaped = true;
|
|
186
|
+
else if (char === quote) quoted = false;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (char === '"' || char === "'") {
|
|
190
|
+
quoted = true;
|
|
191
|
+
quote = char;
|
|
192
|
+
output += char;
|
|
193
|
+
} else if (char === '/' && next === '/') {
|
|
194
|
+
while (index < source.length && source[index] !== '\n') index++;
|
|
195
|
+
output += '\n';
|
|
196
|
+
} else if (char === '/' && next === '*') {
|
|
197
|
+
index += 2;
|
|
198
|
+
while (index < source.length && !(source[index] === '*' && source[index + 1] === '/')) index++;
|
|
199
|
+
index++;
|
|
200
|
+
} else {
|
|
201
|
+
output += char;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return JSON.parse(output.replace(/,\s*([}\]])/g, '$1'));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function linkBridgePackage(destination, source) {
|
|
208
|
+
try {
|
|
209
|
+
if (fs.realpathSync(destination) === fs.realpathSync(source)) return;
|
|
210
|
+
} catch {
|
|
211
|
+
// Create the link below.
|
|
212
|
+
}
|
|
213
|
+
const parent = path.dirname(destination);
|
|
214
|
+
fs.mkdirSync(parent, { recursive: true });
|
|
215
|
+
try {
|
|
216
|
+
if (!fs.lstatSync(destination).isSymbolicLink()) return;
|
|
217
|
+
} catch {
|
|
218
|
+
// The destination does not exist yet.
|
|
219
|
+
}
|
|
220
|
+
const temporary = `${destination}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
221
|
+
try {
|
|
222
|
+
fs.symlinkSync(source, temporary, process.platform === 'win32' ? 'junction' : 'dir');
|
|
223
|
+
try {
|
|
224
|
+
fs.renameSync(temporary, destination);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
try {
|
|
227
|
+
if (fs.realpathSync(destination) === fs.realpathSync(source)) return;
|
|
228
|
+
if (!fs.lstatSync(destination).isSymbolicLink()) return;
|
|
229
|
+
fs.unlinkSync(destination);
|
|
230
|
+
fs.renameSync(temporary, destination);
|
|
231
|
+
} catch {
|
|
232
|
+
if (fs.realpathSync(destination) !== fs.realpathSync(source)) throw error;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
} finally {
|
|
236
|
+
fs.rmSync(temporary, { force: true });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function importedPackageNames(context) {
|
|
241
|
+
const specs = new Set();
|
|
242
|
+
for (const file of projectSourceFiles(context)) {
|
|
243
|
+
let source;
|
|
244
|
+
try {
|
|
245
|
+
source = fs.readFileSync(file, 'utf8');
|
|
246
|
+
} catch {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
for (const name of packageNamesFromSource(source)) specs.add(name);
|
|
250
|
+
}
|
|
251
|
+
return [...specs].sort();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function importedPackageNamesFromDirectory(directory) {
|
|
255
|
+
const specs = new Set();
|
|
256
|
+
const visit = current => {
|
|
257
|
+
let entries;
|
|
258
|
+
try {
|
|
259
|
+
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
260
|
+
} catch {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
for (const entry of entries) {
|
|
264
|
+
if (entry.isDirectory()) {
|
|
265
|
+
if (!PRUNED_DIRECTORIES.has(entry.name)) visit(path.join(current, entry.name));
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (!entry.isFile() || !TYPE_SCRIPT_SOURCE.test(entry.name)) continue;
|
|
269
|
+
let source;
|
|
270
|
+
try {
|
|
271
|
+
source = fs.readFileSync(path.join(current, entry.name), 'utf8');
|
|
272
|
+
} catch {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
for (const name of packageNamesFromSource(source)) specs.add(name);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
visit(directory);
|
|
279
|
+
return [...specs].sort();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function packageNamesFromSource(source) {
|
|
283
|
+
const names = new Set();
|
|
284
|
+
const expression =
|
|
285
|
+
/\b(?:import|export)\s+(?:[^'"`]*?\s+from\s+)?['"]([^'"]+)['"]|\b(?:import|require)\s*\(\s*['"]([^'"]+)['"]\s*\)|\bimport\s+[^=]+?=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
286
|
+
for (const match of source.matchAll(expression)) {
|
|
287
|
+
const name = packageNameFromSpecifier(match[1] ?? match[2] ?? match[3]);
|
|
288
|
+
if (name !== '') names.add(name);
|
|
289
|
+
}
|
|
290
|
+
for (const match of source.matchAll(/^\s*\/\/\/\s*<reference\s+types=['"]([^'"]+)['"]/gm)) {
|
|
291
|
+
const name = match[1];
|
|
292
|
+
names.add(name.startsWith('@types/') ? name : `@types/${name}`);
|
|
293
|
+
}
|
|
294
|
+
return names;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function projectSourceFiles(context) {
|
|
298
|
+
const files = [];
|
|
299
|
+
const visit = directory => {
|
|
300
|
+
let entries;
|
|
301
|
+
try {
|
|
302
|
+
entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
303
|
+
} catch {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
for (const entry of entries) {
|
|
307
|
+
const file = path.join(directory, entry.name);
|
|
308
|
+
if (entry.isDirectory()) {
|
|
309
|
+
if (!PRUNED_DIRECTORIES.has(entry.name) && entry.name !== 'dist' && entry.name !== 'build') visit(file);
|
|
310
|
+
} else if (entry.isFile() && TYPE_SCRIPT_SOURCE.test(entry.name)) {
|
|
311
|
+
files.push(file);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
visit(context.projectRoot);
|
|
316
|
+
return files;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function packageNameFromSpecifier(specifier) {
|
|
320
|
+
if (specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('#') || specifier.startsWith('node:')) return '';
|
|
321
|
+
const parts = specifier.split('/');
|
|
322
|
+
if (specifier.startsWith('@')) return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : '';
|
|
323
|
+
return parts[0] ?? '';
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function definitelyTypedPackageName(packageName) {
|
|
327
|
+
if (packageName.startsWith('@')) {
|
|
328
|
+
const [scope, name] = packageName.slice(1).split('/');
|
|
329
|
+
return scope && name ? `@types/${scope}__${name}` : '';
|
|
330
|
+
}
|
|
331
|
+
return `@types/${packageName}`;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function materializeTypeScriptPackage(packageName, source, cacheRoot) {
|
|
335
|
+
const packageJson = path.join(source, 'package.json');
|
|
336
|
+
let manifest = '';
|
|
337
|
+
try {
|
|
338
|
+
manifest = fs.readFileSync(packageJson, 'utf8');
|
|
339
|
+
} catch {
|
|
340
|
+
// The package directory itself is still sufficient for the fallback scanner.
|
|
341
|
+
}
|
|
342
|
+
const hash = crypto
|
|
343
|
+
.createHash('sha256')
|
|
344
|
+
.update(`${MATERIALIZATION_SCHEMA_VERSION}\0${packageName}\0${source}\0${manifest}`);
|
|
345
|
+
if (!isArchivePath(source)) hashTypeScriptFiles(source, source, hash);
|
|
346
|
+
const key = hash.digest('hex');
|
|
347
|
+
const container = path.join(cacheRoot, 'packages', key);
|
|
348
|
+
const destination = path.join(container, 'node_modules', ...packageName.split('/'));
|
|
349
|
+
if (fs.existsSync(path.join(container, '.complete'))) return destination;
|
|
350
|
+
|
|
351
|
+
const temporary = `${container}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
352
|
+
const temporaryPackage = path.join(temporary, 'node_modules', ...packageName.split('/'));
|
|
353
|
+
fs.mkdirSync(temporaryPackage, { recursive: true });
|
|
354
|
+
try {
|
|
355
|
+
copyTypeScriptFiles(source, temporaryPackage);
|
|
356
|
+
fs.writeFileSync(path.join(temporary, '.complete'), '1\n');
|
|
357
|
+
publishDirectory(temporary, container);
|
|
358
|
+
} finally {
|
|
359
|
+
fs.rmSync(temporary, { force: true, recursive: true });
|
|
360
|
+
}
|
|
361
|
+
return destination;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function hashTypeScriptFiles(root, directory, hash) {
|
|
365
|
+
let entries;
|
|
366
|
+
try {
|
|
367
|
+
entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
368
|
+
} catch (error) {
|
|
369
|
+
if (error?.code === 'ENOENT') return;
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
373
|
+
if (entry.isDirectory()) {
|
|
374
|
+
if (!PRUNED_DIRECTORIES.has(entry.name)) hashTypeScriptFiles(root, path.join(directory, entry.name), hash);
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (!entry.isFile() || (entry.name !== 'package.json' && !TYPE_SCRIPT_SOURCE.test(entry.name))) continue;
|
|
378
|
+
const file = path.join(directory, entry.name);
|
|
379
|
+
let source;
|
|
380
|
+
try {
|
|
381
|
+
source = fs.readFileSync(file);
|
|
382
|
+
} catch (error) {
|
|
383
|
+
if (error?.code === 'ENOENT') continue;
|
|
384
|
+
throw error;
|
|
385
|
+
}
|
|
386
|
+
hash.update(`${path.relative(root, file).replaceAll(path.sep, '/')}\n`);
|
|
387
|
+
hash.update(source);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function copyTypeScriptFiles(source, destination) {
|
|
392
|
+
const walk = (from, to) => {
|
|
393
|
+
let entries;
|
|
394
|
+
try {
|
|
395
|
+
entries = fs.readdirSync(from, { withFileTypes: true });
|
|
396
|
+
} catch {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
for (const entry of entries) {
|
|
400
|
+
if (entry.isDirectory()) {
|
|
401
|
+
if (!PRUNED_DIRECTORIES.has(entry.name)) walk(path.join(from, entry.name), path.join(to, entry.name));
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (!entry.isFile() || (entry.name !== 'package.json' && !TYPE_SCRIPT_SOURCE.test(entry.name))) continue;
|
|
405
|
+
const target = path.join(to, entry.name);
|
|
406
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
407
|
+
fs.copyFileSync(path.join(from, entry.name), target);
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
walk(source, destination);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function writeExternalPackageRoots(cacheRoot, roots) {
|
|
414
|
+
const destination = path.join(cacheRoot, EXTERNAL_ROOTS_FILE);
|
|
415
|
+
const serialized = `${JSON.stringify(roots)}\n`;
|
|
416
|
+
try {
|
|
417
|
+
if (fs.readFileSync(destination, 'utf8') === serialized) return;
|
|
418
|
+
} catch {
|
|
419
|
+
// Write the first map below.
|
|
420
|
+
}
|
|
421
|
+
fs.mkdirSync(cacheRoot, { recursive: true });
|
|
422
|
+
const temporary = `${destination}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
423
|
+
try {
|
|
424
|
+
fs.writeFileSync(temporary, serialized);
|
|
425
|
+
fs.renameSync(temporary, destination);
|
|
426
|
+
} finally {
|
|
427
|
+
fs.rmSync(temporary, { force: true });
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function publishDirectory(temporary, destination) {
|
|
432
|
+
try {
|
|
433
|
+
fs.renameSync(temporary, destination);
|
|
434
|
+
} catch (error) {
|
|
435
|
+
if (!fs.existsSync(path.join(destination, '.complete'))) throw error;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function cleanPackageRoot(root) {
|
|
440
|
+
return root.endsWith(path.sep) ? root.slice(0, -1) : root;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function isArchivePath(value) {
|
|
444
|
+
return /\.zip[\\/]node_modules[\\/]/.test(value);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function hashDirectory(root) {
|
|
448
|
+
const hash = crypto.createHash('sha256');
|
|
449
|
+
const visit = directory => {
|
|
450
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
451
|
+
if (PRUNED_DIRECTORIES.has(entry.name)) continue;
|
|
452
|
+
const file = path.join(directory, entry.name);
|
|
453
|
+
if (entry.isDirectory()) {
|
|
454
|
+
visit(file);
|
|
455
|
+
} else if (entry.isFile()) {
|
|
456
|
+
hash.update(`${path.relative(root, file).replaceAll(path.sep, '/')}\n`);
|
|
457
|
+
hash.update(fs.readFileSync(file));
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
visit(root);
|
|
462
|
+
return hash.digest('hex');
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
module.exports = {
|
|
466
|
+
CACHE_DIRECTORY,
|
|
467
|
+
EXTERNAL_ROOTS_FILE,
|
|
468
|
+
importedPackageNames,
|
|
469
|
+
isArchivePath,
|
|
470
|
+
materializeImportedPackageRoots,
|
|
471
|
+
materializeTypeScriptPackage,
|
|
472
|
+
packageNameFromSpecifier,
|
|
473
|
+
preparePnpPaths
|
|
474
|
+
};
|