@nudojs/harvester 0.1.0

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.
@@ -0,0 +1,19 @@
1
+ import { TypeValue } from '@nudojs/core';
2
+
3
+ /**
4
+ * The result of harvesting a set of `.d.ts` files: a Nudo env shaped exactly
5
+ * like `EnvDefinition` (`{ globals, modules }`), plus harvest statistics.
6
+ */
7
+ type HarvestedEnv = {
8
+ globals: Record<string, TypeValue>;
9
+ modules: Record<string, Record<string, TypeValue>>;
10
+ stats: {
11
+ files: number;
12
+ symbols: number;
13
+ skipped: number;
14
+ };
15
+ };
16
+ declare function harvestDts(files: string[]): HarvestedEnv;
17
+ declare function emitEnvModule(env: HarvestedEnv, pkgName: string): string;
18
+
19
+ export { type HarvestedEnv, emitEnvModule, harvestDts };
package/dist/index.js ADDED
@@ -0,0 +1,562 @@
1
+ // src/index.ts
2
+ import { readFileSync } from "fs";
3
+ import ts from "typescript";
4
+ import { typeValueEquals, getFnSig, T } from "@nudojs/core";
5
+ var MAX_ALIAS_DEPTH = 8;
6
+ function scopeKey(scope) {
7
+ return scope.moduleName ?? "\0global";
8
+ }
9
+ function harvestDts(files) {
10
+ const ctx = {
11
+ globals: {},
12
+ modules: {},
13
+ interfaces: /* @__PURE__ */ new Map(),
14
+ qualifiedInterfaces: /* @__PURE__ */ new Map(),
15
+ aliases: /* @__PURE__ */ new Map(),
16
+ qualifiedAliases: /* @__PURE__ */ new Map(),
17
+ classes: /* @__PURE__ */ new Map(),
18
+ instanceCache: /* @__PURE__ */ new Map(),
19
+ expanding: /* @__PURE__ */ new Set(),
20
+ moduleAliases: [],
21
+ importEquals: /* @__PURE__ */ new Map(),
22
+ declaredModules: /* @__PURE__ */ new Set(),
23
+ pending: [],
24
+ symbols: 0,
25
+ skipped: 0
26
+ };
27
+ for (const file of files) {
28
+ const content = readFileSync(file, "utf8");
29
+ const sourceFile = ts.createSourceFile(file, content, ts.ScriptTarget.Latest, true);
30
+ collectStatements(ctx, sourceFile.statements, { moduleName: void 0, ns: [] });
31
+ }
32
+ materialize(ctx);
33
+ return {
34
+ globals: ctx.globals,
35
+ modules: ctx.modules,
36
+ stats: { files: files.length, symbols: ctx.symbols, skipped: ctx.skipped }
37
+ };
38
+ }
39
+ function emitEnvModule(env, pkgName) {
40
+ const moduleEntries = Object.entries(env.modules);
41
+ const keysByRecord = /* @__PURE__ */ new Map();
42
+ for (const [moduleName, record] of moduleEntries) {
43
+ const keys = keysByRecord.get(record);
44
+ if (keys) keys.push(moduleName);
45
+ else keysByRecord.set(record, [moduleName]);
46
+ }
47
+ const constNameByRecord = /* @__PURE__ */ new Map();
48
+ const constLines = [];
49
+ let constIndex = 0;
50
+ for (const [record, keys] of keysByRecord) {
51
+ if (keys.length < 2) continue;
52
+ const constName = `mod${constIndex++}`;
53
+ constNameByRecord.set(record, constName);
54
+ constLines.push(` const ${constName}: Record<string, TypeValue> = {`);
55
+ emitEntries(constLines, record, 2);
56
+ constLines.push(` };`);
57
+ constLines.push("");
58
+ }
59
+ const lines = [];
60
+ lines.push("// Auto-generated by nudo harvest \u2014 DO NOT EDIT");
61
+ lines.push(`// Source package: ${pkgName}`);
62
+ if (constLines.length > 0) {
63
+ lines.push('import { type TypeValue, T } from "@nudojs/core";');
64
+ } else {
65
+ lines.push(`import { T } from "@nudojs/core";`);
66
+ }
67
+ lines.push("");
68
+ lines.push("export function defineEnv() {");
69
+ lines.push(...constLines);
70
+ lines.push(" return {");
71
+ lines.push(" globals: {");
72
+ emitEntries(lines, env.globals, 3);
73
+ lines.push(" },");
74
+ lines.push(" modules: {");
75
+ for (const [moduleName, record] of moduleEntries) {
76
+ const constName = constNameByRecord.get(record);
77
+ if (constName !== void 0) {
78
+ lines.push(`${indent(3)}${emitKey(moduleName)}: ${constName},`);
79
+ continue;
80
+ }
81
+ lines.push(`${indent(3)}${emitKey(moduleName)}: {`);
82
+ emitEntries(lines, record, 4);
83
+ lines.push(`${indent(3)}},`);
84
+ }
85
+ lines.push(" },");
86
+ lines.push(" };");
87
+ lines.push("}");
88
+ lines.push("");
89
+ return lines.join("\n");
90
+ }
91
+ function recordFor(ctx, scope) {
92
+ if (scope.moduleName === void 0) return ctx.globals;
93
+ let rec = ctx.modules[scope.moduleName];
94
+ if (!rec) {
95
+ rec = {};
96
+ ctx.modules[scope.moduleName] = rec;
97
+ }
98
+ return rec;
99
+ }
100
+ function collectStatements(ctx, statements, scope) {
101
+ for (const stmt of statements) {
102
+ collectStatement(ctx, stmt, scope);
103
+ }
104
+ }
105
+ function collectStatement(ctx, stmt, scope) {
106
+ if (ts.isModuleDeclaration(stmt)) {
107
+ const body = stmt.body;
108
+ if (!body || !ts.isModuleBlock(body)) {
109
+ ctx.skipped++;
110
+ return;
111
+ }
112
+ if (ts.isStringLiteral(stmt.name)) {
113
+ ctx.declaredModules.add(stmt.name.text);
114
+ collectStatements(ctx, body.statements, { moduleName: stmt.name.text, ns: [] });
115
+ } else if (stmt.name.text === "global") {
116
+ collectStatements(ctx, body.statements, { moduleName: void 0, ns: [] });
117
+ } else {
118
+ collectStatements(ctx, body.statements, { moduleName: scope.moduleName, ns: [...scope.ns, stmt.name.text] });
119
+ }
120
+ return;
121
+ }
122
+ if (ts.isFunctionDeclaration(stmt)) {
123
+ if (!stmt.name) {
124
+ ctx.skipped++;
125
+ return;
126
+ }
127
+ const name = stmt.name.text;
128
+ const existing = ctx.pending.find((p) => p.t === "fn" && p.name === name && scopeKey(p.scope) === scopeKey(scope));
129
+ if (existing && existing.t === "fn") {
130
+ existing.decls.push(stmt);
131
+ return;
132
+ }
133
+ ctx.pending.push({ t: "fn", scope, name, decls: [stmt] });
134
+ ctx.symbols++;
135
+ return;
136
+ }
137
+ if (ts.isVariableStatement(stmt)) {
138
+ for (const decl of stmt.declarationList.declarations) {
139
+ if (!ts.isIdentifier(decl.name)) {
140
+ ctx.skipped++;
141
+ continue;
142
+ }
143
+ ctx.pending.push({ t: "var", scope, name: decl.name.text, typeNode: decl.type });
144
+ ctx.symbols++;
145
+ }
146
+ return;
147
+ }
148
+ if (ts.isInterfaceDeclaration(stmt)) {
149
+ registerTypeDecl(ctx, scope, stmt.name.text, stmt);
150
+ return;
151
+ }
152
+ if (ts.isTypeAliasDeclaration(stmt)) {
153
+ registerAlias(ctx, scope, stmt);
154
+ return;
155
+ }
156
+ if (ts.isClassDeclaration(stmt)) {
157
+ if (!stmt.name) {
158
+ ctx.skipped++;
159
+ return;
160
+ }
161
+ registerTypeDecl(ctx, scope, stmt.name.text, stmt);
162
+ if (!ctx.classes.has(stmt.name.text)) ctx.classes.set(stmt.name.text, stmt);
163
+ ctx.pending.push({ t: "value", scope, name: stmt.name.text });
164
+ ctx.symbols++;
165
+ return;
166
+ }
167
+ if (ts.isEnumDeclaration(stmt)) {
168
+ if (stmt.name) {
169
+ ctx.pending.push({ t: "enum", scope, name: stmt.name.text });
170
+ ctx.symbols++;
171
+ }
172
+ return;
173
+ }
174
+ if (ts.isImportDeclaration(stmt)) {
175
+ return;
176
+ }
177
+ if (ts.isImportEqualsDeclaration(stmt)) {
178
+ if (ts.isIdentifier(stmt.name) && stmt.moduleReference && ts.isExternalModuleReference(stmt.moduleReference) && ts.isStringLiteral(stmt.moduleReference.expression)) {
179
+ const key = scopeKey(scope);
180
+ let bindings = ctx.importEquals.get(key);
181
+ if (!bindings) {
182
+ bindings = /* @__PURE__ */ new Map();
183
+ ctx.importEquals.set(key, bindings);
184
+ }
185
+ bindings.set(stmt.name.text, stmt.moduleReference.expression.text);
186
+ }
187
+ return;
188
+ }
189
+ if (ts.isExportDeclaration(stmt)) {
190
+ const specifier = stmt.moduleSpecifier;
191
+ if (specifier && ts.isStringLiteral(specifier)) {
192
+ if (stmt.exportClause && ts.isNamespaceExport(stmt.exportClause)) {
193
+ ctx.skipped++;
194
+ return;
195
+ }
196
+ if (!stmt.exportClause) {
197
+ if (scope.moduleName !== void 0) {
198
+ ctx.moduleAliases.push([scope.moduleName, specifier.text]);
199
+ }
200
+ return;
201
+ }
202
+ ctx.skipped++;
203
+ return;
204
+ }
205
+ if (stmt.exportClause && ts.isNamedExports(stmt.exportClause)) {
206
+ for (const el of stmt.exportClause.elements) {
207
+ const localName = (el.propertyName ?? el.name).text;
208
+ const exportedName = el.name.text;
209
+ const isValue = ctx.classes.has(localName) || !el.isTypeOnly && (ctx.interfaces.has(localName) || ctx.qualifiedInterfaces.has(localName));
210
+ if (isValue) {
211
+ ctx.pending.push({ t: "value", scope, name: exportedName, lookup: localName });
212
+ ctx.symbols++;
213
+ } else {
214
+ ctx.skipped++;
215
+ }
216
+ }
217
+ return;
218
+ }
219
+ ctx.skipped++;
220
+ return;
221
+ }
222
+ if (ts.isExportAssignment(stmt)) {
223
+ if (stmt.expression && ts.isIdentifier(stmt.expression) && scope.moduleName !== void 0) {
224
+ const target = ctx.importEquals.get(scopeKey(scope))?.get(stmt.expression.text);
225
+ if (target !== void 0) {
226
+ ctx.moduleAliases.push([scope.moduleName, target]);
227
+ return;
228
+ }
229
+ }
230
+ ctx.skipped++;
231
+ return;
232
+ }
233
+ ctx.skipped++;
234
+ }
235
+ function registerTypeDecl(ctx, scope, name, decl) {
236
+ if (!ctx.interfaces.has(name)) ctx.interfaces.set(name, decl);
237
+ if (ts.isClassDeclaration(decl)) return;
238
+ const qualified = [...scope.ns, name].join(".");
239
+ if (qualified !== name && !ctx.qualifiedInterfaces.has(qualified)) {
240
+ ctx.qualifiedInterfaces.set(qualified, decl);
241
+ }
242
+ }
243
+ function registerAlias(ctx, scope, stmt) {
244
+ const name = stmt.name.text;
245
+ if (!ctx.aliases.has(name)) ctx.aliases.set(name, stmt);
246
+ const qualified = [...scope.ns, name].join(".");
247
+ if (qualified !== name && !ctx.qualifiedAliases.has(qualified)) {
248
+ ctx.qualifiedAliases.set(qualified, stmt);
249
+ }
250
+ }
251
+ function materialize(ctx) {
252
+ for (const p of ctx.pending) {
253
+ const rec = recordFor(ctx, p.scope);
254
+ if (p.t === "fn") {
255
+ if (rec[p.name] !== void 0) continue;
256
+ const value = signatureToFnSig(ctx, p.decls[0]);
257
+ const sig = getFnSig(value);
258
+ if (sig) {
259
+ const returns = [];
260
+ for (const decl of p.decls) {
261
+ if (!decl.type) continue;
262
+ const ret = mapType(ctx, decl.type, 0);
263
+ if (ret.kind !== "unknown") returns.push(ret);
264
+ }
265
+ sig.returnType = returns.length > 0 ? returns.reduce((acc, r) => typeValueEquals(acc, r) ? acc : T.union(acc, r)) : T.unknown;
266
+ }
267
+ rec[p.name] = value;
268
+ continue;
269
+ }
270
+ if (rec[p.name] !== void 0) continue;
271
+ switch (p.t) {
272
+ case "var": {
273
+ const mapped = p.typeNode ? mapType(ctx, p.typeNode, 0) : T.unknown;
274
+ if (mapped.kind === "unknown") {
275
+ ctx.skipped++;
276
+ break;
277
+ }
278
+ rec[p.name] = mapped;
279
+ break;
280
+ }
281
+ case "value": {
282
+ const cls = ctx.classes.get(p.lookup ?? p.name);
283
+ if (cls) {
284
+ const ctor = cls.members.find(ts.isConstructorDeclaration);
285
+ const params = [];
286
+ if (ctor) {
287
+ for (const param of ctor.parameters) {
288
+ let type = param.type ? mapType(ctx, param.type, 0) : T.unknown;
289
+ if (param.questionToken) type = T.union(type, T.undefined);
290
+ params.push(type);
291
+ }
292
+ }
293
+ rec[p.name] = T.fnSig(params, instanceFor(ctx, p.lookup ?? p.name));
294
+ } else {
295
+ rec[p.name] = instanceFor(ctx, p.lookup ?? p.name);
296
+ }
297
+ break;
298
+ }
299
+ case "enum":
300
+ rec[p.name] = T.unknown;
301
+ break;
302
+ }
303
+ }
304
+ for (const [alias, target] of ctx.moduleAliases) {
305
+ if (ctx.modules[target] && !ctx.modules[alias]) {
306
+ ctx.modules[alias] = ctx.modules[target];
307
+ }
308
+ }
309
+ }
310
+ function typeNameString(name) {
311
+ if (ts.isIdentifier(name)) return name.text;
312
+ return `${typeNameString(name.left)}.${name.right.text}`;
313
+ }
314
+ function memberName(node) {
315
+ const n = node.name;
316
+ if (!n) return void 0;
317
+ if (ts.isIdentifier(n) || ts.isStringLiteral(n) || ts.isNumericLiteral(n)) return n.text;
318
+ return void 0;
319
+ }
320
+ function instanceFor(ctx, key) {
321
+ const cached = ctx.instanceCache.get(key);
322
+ if (cached !== void 0) return cached;
323
+ const decl = ctx.interfaces.get(key) ?? ctx.qualifiedInterfaces.get(key);
324
+ if (!decl) return T.unknown;
325
+ if (ctx.expanding.has(key)) {
326
+ return T.instanceOf(decl.name?.text ?? key, {});
327
+ }
328
+ ctx.expanding.add(key);
329
+ const properties = {};
330
+ try {
331
+ const members = decl.members;
332
+ for (const member of members) {
333
+ const modifiers = member.modifiers;
334
+ if (modifiers?.some((mod) => mod.kind === ts.SyntaxKind.StaticKeyword)) continue;
335
+ if (ts.isPropertySignature(member) || ts.isPropertyDeclaration(member)) {
336
+ const name = memberName(member);
337
+ if (name === void 0) continue;
338
+ let type = member.type ? mapType(ctx, member.type, 0) : T.unknown;
339
+ if (member.questionToken) type = T.union(type, T.undefined);
340
+ properties[name] = type;
341
+ } else if (ts.isMethodSignature(member) || ts.isMethodDeclaration(member)) {
342
+ const name = memberName(member);
343
+ if (name === void 0 || properties[name] !== void 0) continue;
344
+ properties[name] = signatureToFnSig(ctx, member);
345
+ } else if (ts.isGetAccessorDeclaration(member) || ts.isSetAccessorDeclaration(member)) {
346
+ const name = memberName(member);
347
+ if (name === void 0 || properties[name] !== void 0 || !member.type) continue;
348
+ properties[name] = mapType(ctx, member.type, 0);
349
+ }
350
+ }
351
+ } finally {
352
+ ctx.expanding.delete(key);
353
+ }
354
+ const value = T.instanceOf(decl.name?.text ?? key, properties);
355
+ ctx.instanceCache.set(key, value);
356
+ return value;
357
+ }
358
+ function signatureToFnSig(ctx, sig) {
359
+ const paramTypes = sig.parameters.map((param) => {
360
+ let type;
361
+ if (param.dotDotDotToken) {
362
+ const inner = param.type && ts.isArrayTypeNode(param.type) ? param.type.elementType : param.type;
363
+ type = inner ? T.array(mapType(ctx, inner, 0)) : T.unknown;
364
+ } else {
365
+ type = param.type ? mapType(ctx, param.type, 0) : T.unknown;
366
+ }
367
+ if (param.questionToken && !param.dotDotDotToken) type = T.union(type, T.undefined);
368
+ return type;
369
+ });
370
+ const returnType = sig.type ? mapType(ctx, sig.type, 0) : T.unknown;
371
+ return T.fnSig(paramTypes, returnType);
372
+ }
373
+ function mapTypeMembers(ctx, members, depth) {
374
+ const properties = {};
375
+ for (const member of members) {
376
+ if (ts.isPropertySignature(member)) {
377
+ const name = memberName(member);
378
+ if (name === void 0) continue;
379
+ let type = member.type ? mapType(ctx, member.type, depth) : T.unknown;
380
+ if (member.questionToken) type = T.union(type, T.undefined);
381
+ properties[name] = type;
382
+ } else if (ts.isMethodSignature(member)) {
383
+ const name = memberName(member);
384
+ if (name === void 0 || properties[name] !== void 0) continue;
385
+ properties[name] = signatureToFnSig(ctx, member);
386
+ }
387
+ }
388
+ return properties;
389
+ }
390
+ function mapTypeRef(ctx, node, depth) {
391
+ const name = typeNameString(node.typeName);
392
+ const args = node.typeArguments ?? [];
393
+ if ((name === "Array" || name === "ReadonlyArray") && args.length >= 1) {
394
+ return T.array(mapType(ctx, args[0], depth));
395
+ }
396
+ if (name === "Promise" && args.length >= 1) {
397
+ return T.promise(mapType(ctx, args[0], depth));
398
+ }
399
+ if (name === "Record") {
400
+ return T.object({});
401
+ }
402
+ if (ctx.interfaces.has(name) || ctx.qualifiedInterfaces.has(name)) {
403
+ return instanceFor(ctx, name);
404
+ }
405
+ const alias = ctx.aliases.get(name) ?? ctx.qualifiedAliases.get(name);
406
+ if (alias) return mapType(ctx, alias.type, depth + 1);
407
+ return T.unknown;
408
+ }
409
+ function mapType(ctx, node, depth) {
410
+ if (!node || depth > MAX_ALIAS_DEPTH) return T.unknown;
411
+ if (ts.isParenthesizedTypeNode(node)) return mapType(ctx, node.type, depth);
412
+ if (ts.isTypeReferenceNode(node)) return mapTypeRef(ctx, node, depth);
413
+ if (ts.isArrayTypeNode(node)) return T.array(mapType(ctx, node.elementType, depth));
414
+ if (ts.isTupleTypeNode(node)) {
415
+ for (const element of node.elements) {
416
+ const inner = ts.isNamedTupleMember(element) ? element.type : element;
417
+ return T.array(mapType(ctx, inner, depth));
418
+ }
419
+ return T.unknown;
420
+ }
421
+ if (ts.isUnionTypeNode(node)) {
422
+ return T.union(...node.types.map((t) => mapType(ctx, t, depth)));
423
+ }
424
+ if (ts.isIntersectionTypeNode(node)) {
425
+ const members = node.types.map((t) => mapType(ctx, t, depth));
426
+ if (members.length > 0 && members.every((m) => m.kind === "object")) {
427
+ const properties = {};
428
+ for (const member of members) {
429
+ Object.assign(properties, member.properties);
430
+ }
431
+ return T.object(properties);
432
+ }
433
+ return members[0] ?? T.unknown;
434
+ }
435
+ if (ts.isLiteralTypeNode(node)) {
436
+ const lit = node.literal;
437
+ if (ts.isStringLiteral(lit)) return T.literal(lit.text);
438
+ if (ts.isNumericLiteral(lit)) return T.literal(Number(lit.text));
439
+ if (lit.kind === ts.SyntaxKind.TrueKeyword) return T.literal(true);
440
+ if (lit.kind === ts.SyntaxKind.FalseKeyword) return T.literal(false);
441
+ if (ts.isPrefixUnaryExpression(lit) && lit.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(lit.operand)) {
442
+ return T.literal(-Number(lit.operand.text));
443
+ }
444
+ return T.unknown;
445
+ }
446
+ if (ts.isFunctionTypeNode(node)) return signatureToFnSig(ctx, node);
447
+ if (ts.isTypeLiteralNode(node)) {
448
+ const ctor = node.members.find(ts.isConstructSignatureDeclaration);
449
+ if (ctor) {
450
+ const params = [];
451
+ for (const param of ctor.parameters) {
452
+ let type = param.type ? mapType(ctx, param.type, depth) : T.unknown;
453
+ if (param.questionToken) type = T.union(type, T.undefined);
454
+ params.push(type);
455
+ }
456
+ return T.fnSig(params, ctor.type ? mapType(ctx, ctor.type, depth) : T.unknown);
457
+ }
458
+ return T.object(mapTypeMembers(ctx, node.members, depth));
459
+ }
460
+ if (ts.isTypeOperatorNode(node)) {
461
+ return node.operator === ts.SyntaxKind.ReadonlyKeyword ? mapType(ctx, node.type, depth) : T.unknown;
462
+ }
463
+ switch (node.kind) {
464
+ case ts.SyntaxKind.StringKeyword:
465
+ return T.string;
466
+ case ts.SyntaxKind.NumberKeyword:
467
+ return T.number;
468
+ case ts.SyntaxKind.BooleanKeyword:
469
+ return T.boolean;
470
+ case ts.SyntaxKind.BigIntKeyword:
471
+ return T.bigint;
472
+ case ts.SyntaxKind.SymbolKeyword:
473
+ return T.symbol;
474
+ case ts.SyntaxKind.AnyKeyword:
475
+ case ts.SyntaxKind.UnknownKeyword:
476
+ case ts.SyntaxKind.ObjectKeyword:
477
+ return T.unknown;
478
+ case ts.SyntaxKind.VoidKeyword:
479
+ case ts.SyntaxKind.UndefinedKeyword:
480
+ return T.undefined;
481
+ case ts.SyntaxKind.NeverKeyword:
482
+ return T.never;
483
+ case ts.SyntaxKind.NullKeyword:
484
+ return T.null;
485
+ default:
486
+ return T.unknown;
487
+ }
488
+ }
489
+ function indent(level) {
490
+ return " ".repeat(level);
491
+ }
492
+ function emitKey(key) {
493
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
494
+ }
495
+ function emitEntries(lines, record, level) {
496
+ for (const [key, value] of Object.entries(record)) {
497
+ lines.push(`${indent(level)}${emitKey(key)}: ${emitType(value, /* @__PURE__ */ new Set())},`);
498
+ }
499
+ }
500
+ function emitType(tv, expanding) {
501
+ switch (tv.kind) {
502
+ case "literal":
503
+ if (tv.value === null) return "T.null";
504
+ if (tv.value === void 0) return "T.undefined";
505
+ return `T.literal(${JSON.stringify(tv.value)})`;
506
+ case "primitive":
507
+ return `T.${tv.type}`;
508
+ case "unknown":
509
+ return "T.unknown";
510
+ case "never":
511
+ return "T.never";
512
+ case "array":
513
+ return `T.array(${emitType(tv.element, expanding)})`;
514
+ case "tuple":
515
+ return `T.tuple([${tv.elements.map((e) => emitType(e, expanding)).join(", ")}])`;
516
+ case "promise":
517
+ return `T.promise(${emitType(tv.value, expanding)})`;
518
+ case "object": {
519
+ const entries = Object.entries(tv.properties).map(
520
+ ([key, value]) => `${emitKey(key)}: ${emitType(value, expanding)}`
521
+ );
522
+ return `T.object({ ${entries.join(", ")} })`;
523
+ }
524
+ case "instance": {
525
+ if (expanding.has(tv.className)) {
526
+ return `T.instanceOf(${JSON.stringify(tv.className)}, {})`;
527
+ }
528
+ expanding.add(tv.className);
529
+ try {
530
+ const entries = Object.entries(tv.properties).map(
531
+ ([key, value]) => `${emitKey(key)}: ${emitType(value, expanding)}`
532
+ );
533
+ return `T.instanceOf(${JSON.stringify(tv.className)}, { ${entries.join(", ")} })`;
534
+ } finally {
535
+ expanding.delete(tv.className);
536
+ }
537
+ }
538
+ case "union": {
539
+ if (tv.members.length === 0) return "T.never";
540
+ if (tv.members.length === 1) return emitType(tv.members[0], expanding);
541
+ return `T.union(${tv.members.map((m) => emitType(m, expanding)).join(", ")})`;
542
+ }
543
+ case "function": {
544
+ const sig = getFnSig(tv);
545
+ if (!sig) return "T.unknown";
546
+ const params = sig.paramTypes.map((p) => emitType(p, expanding)).join(", ");
547
+ const ret = emitType(sig.returnType, expanding);
548
+ if (sig.throwsType && sig.throwsType.kind !== "never") {
549
+ return `T.fnSig([${params}], ${ret}, ${emitType(sig.throwsType, expanding)})`;
550
+ }
551
+ return `T.fnSig([${params}], ${ret})`;
552
+ }
553
+ case "refined":
554
+ return emitType(tv.base, expanding);
555
+ default:
556
+ return "T.unknown";
557
+ }
558
+ }
559
+ export {
560
+ emitEnvModule,
561
+ harvestDts
562
+ };
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@nudojs/harvester",
3
+ "version": "0.1.0",
4
+ "description": "Harvests TypeScript .d.ts declarations into Nudo env definitions",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "main": "src/index.ts",
8
+ "exports": {
9
+ ".": "./src/index.ts"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/nudojs/nudo.git",
14
+ "directory": "packages/harvester"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public",
18
+ "provenance": true,
19
+ "registry": "https://registry.npmjs.org"
20
+ },
21
+ "scripts": {
22
+ "build": "tsup src/index.ts --format esm --dts"
23
+ },
24
+ "dependencies": {
25
+ "@nudojs/core": "workspace:*",
26
+ "typescript": "^5.8.0"
27
+ }
28
+ }