@nudojs/cli 0.3.0 → 0.4.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.
- package/dist/evaluator-api.d.ts +162 -0
- package/dist/evaluator-api.js +27683 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +281241 -0
- package/package.json +20 -13
- package/src/abs-route.ts +0 -273
- package/src/builtins/builtin-intl.ts +0 -17
- package/src/builtins/builtin-map.ts +0 -174
- package/src/builtins/builtin-promise.ts +0 -116
- package/src/builtins/builtin-prototype.ts +0 -279
- package/src/builtins/builtin-reflect.ts +0 -30
- package/src/builtins/builtin-regexp.ts +0 -34
- package/src/builtins/builtin-set.ts +0 -131
- package/src/builtins/builtin-static.ts +0 -260
- package/src/builtins/builtin-symbol.ts +0 -16
- package/src/builtins/builtin-url.ts +0 -58
- package/src/builtins/builtin-weak.ts +0 -22
- package/src/builtins/builtin-web.ts +0 -111
- package/src/config.ts +0 -32
- package/src/env-loader.ts +0 -174
- package/src/eval-binary.ts +0 -113
- package/src/evaluator-api.ts +0 -39
- package/src/evaluator.ts +0 -5164
- package/src/index.ts +0 -1034
- package/src/narrowing.ts +0 -544
- package/src/phi-from-test.ts +0 -42
- package/src/resolve-npm.ts +0 -63
- package/src/run-test.ts +0 -92
- package/src/term-registry.ts +0 -96
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 内置静态命名空间与直接全局值(process/Math/JSON/Object statics/Number
|
|
3
|
-
* 常量…)及其实现辅助(JSON 字面量解码、属性描述符、结构 instanceof 判定、
|
|
4
|
-
* fromCharCode 实参收割)。
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { T, simplifyUnion, type TypeValue } from "@nudojs/core";
|
|
8
|
-
import { protoOfValue } from "./builtin-prototype.ts";
|
|
9
|
-
import { PROMISE_STATIC_METHODS } from "./builtin-promise.ts";
|
|
10
|
-
import { SYMBOL_STATIC_METHODS, SYMBOL_STATIC_PROPS } from "./builtin-symbol.ts";
|
|
11
|
-
import { REFLECT_METHODS } from "./builtin-reflect.ts";
|
|
12
|
-
import { INTL_DATETIMEFORMAT_METHODS, INTL_NUMBERFORMAT_METHODS } from "./builtin-intl.ts";
|
|
13
|
-
|
|
14
|
-
// hasOwnProp 守卫:普通 Record 表查找会从原型链泄漏原生函数(与
|
|
15
|
-
// builtin-prototype.ts / evaluator 内同名 helper 同语义)。
|
|
16
|
-
function hasOwnProp(props: Record<string, unknown>, name: string): boolean {
|
|
17
|
-
return Object.prototype.hasOwnProperty.call(props, name);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Built-in JavaScript API type mappings
|
|
21
|
-
// Namespace objects (e.g. Math.floor) and direct global values (e.g. parseInt)
|
|
22
|
-
const BUILTIN_STATIC_METHODS: Record<string, Record<string, TypeValue> | TypeValue> = {
|
|
23
|
-
// Node CJS 库普遍依赖的 process 全局——缺它时 process.nextTick(cb) 走
|
|
24
|
-
// 未知全局 → undefined → no-method 假错(hoek wait 的 usage-site 执行)
|
|
25
|
-
process: {
|
|
26
|
-
nextTick: T.fn(["callback", "...args"], { type: "BlockStatement", body: [], directives: [] } as any, undefined as any),
|
|
27
|
-
env: T.object({}),
|
|
28
|
-
platform: T.string,
|
|
29
|
-
versions: T.object({}),
|
|
30
|
-
argv: T.array(T.string),
|
|
31
|
-
cwd: T.fn([], { type: "BlockStatement", body: [], directives: [] } as any, undefined as any),
|
|
32
|
-
exit: T.fn(["code"], { type: "BlockStatement", body: [], directives: [] } as any, undefined as any),
|
|
33
|
-
hrtime: T.fn(["time"], { type: "BlockStatement", body: [], directives: [] } as any, undefined as any),
|
|
34
|
-
stdout: T.instanceOf("Socket"),
|
|
35
|
-
stderr: T.instanceOf("Socket"),
|
|
36
|
-
},
|
|
37
|
-
Date: {
|
|
38
|
-
now: T.number,
|
|
39
|
-
parse: T.number,
|
|
40
|
-
UTC: T.number,
|
|
41
|
-
},
|
|
42
|
-
Math: {
|
|
43
|
-
random: T.number,
|
|
44
|
-
floor: T.fn(["x"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
45
|
-
ceil: T.fn(["x"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
46
|
-
round: T.fn(["x"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
47
|
-
abs: T.fn(["x"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
48
|
-
max: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
49
|
-
min: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
50
|
-
sqrt: T.fn(["x"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
51
|
-
pow: T.fn(["base", "exp"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
52
|
-
// Math 常量与 @nudo:env es 的 Math 定义对齐(env 绑定优先);无 env
|
|
53
|
-
// 指令时也在此解析,避免 Math.PI 走 unknown-property。
|
|
54
|
-
PI: T.number,
|
|
55
|
-
E: T.number,
|
|
56
|
-
LN2: T.number,
|
|
57
|
-
LN10: T.number,
|
|
58
|
-
LOG2E: T.number,
|
|
59
|
-
LOG10E: T.number,
|
|
60
|
-
SQRT2: T.number,
|
|
61
|
-
SQRT1_2: T.number,
|
|
62
|
-
},
|
|
63
|
-
JSON: {
|
|
64
|
-
// JSON.parse(literal-string): evaluate with the real JSON.parse and
|
|
65
|
-
// re-encode through jsonToTypeValue, so fixture-style calls
|
|
66
|
-
// (`JSON.parse('{"a":1}')`) keep literal-level precision instead of
|
|
67
|
-
// poisoning downstream clone/deepEqual args with unknown. Degrades to
|
|
68
|
-
// the unknown fallback when the argument is not a literal string or
|
|
69
|
-
// the text is not valid JSON. A reviver argument also degrades: it can
|
|
70
|
-
// replace every value (and the root) with an arbitrary one, so the
|
|
71
|
-
// parse structure is not a sound answer (hoek/json-ext never pass one).
|
|
72
|
-
parse: T.fnSig([T.string], T.unknown, T.never, (args) => {
|
|
73
|
-
const text = args[0];
|
|
74
|
-
if (text?.kind !== "literal" || typeof text.value !== "string") return undefined;
|
|
75
|
-
const reviver = args[1];
|
|
76
|
-
if (reviver && !(reviver.kind === "literal" && reviver.value === undefined)) return undefined;
|
|
77
|
-
try {
|
|
78
|
-
return jsonToTypeValue(JSON.parse(text.value));
|
|
79
|
-
} catch {
|
|
80
|
-
return undefined;
|
|
81
|
-
}
|
|
82
|
-
}),
|
|
83
|
-
stringify: T.string,
|
|
84
|
-
},
|
|
85
|
-
Object: {
|
|
86
|
-
keys: T.array(T.string),
|
|
87
|
-
values: T.array(T.unknown),
|
|
88
|
-
entries: T.array(T.tuple([T.string, T.unknown])),
|
|
89
|
-
assign: T.fnSig([T.unknown], T.unknown, T.never, (args) => {
|
|
90
|
-
// Object.assign(target, ...sources): later sources overwrite; a
|
|
91
|
-
// symbolic source keeps the symbolic unknown fallback.
|
|
92
|
-
const target = args[0];
|
|
93
|
-
if (target?.kind !== "object") return undefined;
|
|
94
|
-
const props: Record<string, TypeValue> = { ...target.properties };
|
|
95
|
-
for (const src of args.slice(1)) {
|
|
96
|
-
if (src?.kind !== "object") return undefined;
|
|
97
|
-
Object.assign(props, src.properties);
|
|
98
|
-
}
|
|
99
|
-
return T.object(props);
|
|
100
|
-
}),
|
|
101
|
-
// Prototype-reflection statics. getPrototypeOf maps receivers onto the
|
|
102
|
-
// cached prototype singletons so `getPrototypeOf(x) !== getPrototypeOf(y)`
|
|
103
|
-
// resolves literally; create/setPrototypeOf cover the clone-style
|
|
104
|
-
// prototype dance; getOwnPropertyDescriptor feeds descriptor.get/set
|
|
105
|
-
// branches with the property's value type.
|
|
106
|
-
getPrototypeOf: T.fnSig([T.unknown], T.unknown, T.never, (args) => protoOfValue(args[0])),
|
|
107
|
-
create: T.fnSig([T.unknown], T.object({}), T.never, () => T.object({})),
|
|
108
|
-
setPrototypeOf: T.fnSig([T.unknown, T.unknown], T.unknown, T.never, (args) => args[0]),
|
|
109
|
-
defineProperty: T.fnSig([T.unknown, T.unknown, T.unknown], T.unknown, T.never, (args) => args[0]),
|
|
110
|
-
getOwnPropertyDescriptor: T.fnSig([T.unknown, T.unknown], T.union(T.object({}), T.undefined), T.never, (args) => {
|
|
111
|
-
const obj = args[0];
|
|
112
|
-
const key = args[1];
|
|
113
|
-
if (obj?.kind !== "object" && obj?.kind !== "instance") return T.undefined;
|
|
114
|
-
if (!key) return T.undefined;
|
|
115
|
-
if (key.kind === "literal" && typeof key.value === "string") {
|
|
116
|
-
const prop = hasOwnProp(obj.properties, key.value) ? obj.properties[key.value] : undefined;
|
|
117
|
-
if (!prop) return T.undefined;
|
|
118
|
-
return descriptorOf(prop);
|
|
119
|
-
}
|
|
120
|
-
if (key.kind === "primitive" && key.type === "string") {
|
|
121
|
-
const props = Object.keys(obj.properties);
|
|
122
|
-
if (props.length === 0) return T.undefined;
|
|
123
|
-
return descriptorOf(simplifyUnion(props.map((k) => obj.properties[k])));
|
|
124
|
-
}
|
|
125
|
-
return T.undefined;
|
|
126
|
-
}),
|
|
127
|
-
},
|
|
128
|
-
Buffer: {
|
|
129
|
-
from: T.fnSig([T.unknown], T.instanceOf("Buffer")),
|
|
130
|
-
alloc: T.fnSig([T.number], T.instanceOf("Buffer")),
|
|
131
|
-
concat: T.fnSig([T.unknown], T.instanceOf("Buffer")),
|
|
132
|
-
byteLength: T.fnSig([T.unknown], T.number),
|
|
133
|
-
isEncoding: T.fnSig([T.unknown], T.boolean),
|
|
134
|
-
isBuffer: T.fnSig([T.unknown], T.boolean, T.never, (args) => {
|
|
135
|
-
const lit = builtinInstanceTest(args[0], "Buffer");
|
|
136
|
-
return lit === undefined ? undefined : T.literal(lit);
|
|
137
|
-
}),
|
|
138
|
-
},
|
|
139
|
-
Array: {
|
|
140
|
-
isArray: T.boolean,
|
|
141
|
-
from: T.array(T.unknown),
|
|
142
|
-
},
|
|
143
|
-
Number: {
|
|
144
|
-
isNaN: T.boolean,
|
|
145
|
-
isFinite: T.boolean,
|
|
146
|
-
parseInt: T.number,
|
|
147
|
-
parseFloat: T.number,
|
|
148
|
-
// Number 常量与 @nudo:env es 的 Number 定义对齐(env 绑定优先);
|
|
149
|
-
// 均为 number(case 序列化器对非有限数返回 null,不产出字面量值)。
|
|
150
|
-
MAX_SAFE_INTEGER: T.number,
|
|
151
|
-
MIN_SAFE_INTEGER: T.number,
|
|
152
|
-
MAX_VALUE: T.number,
|
|
153
|
-
MIN_VALUE: T.number,
|
|
154
|
-
POSITIVE_INFINITY: T.number,
|
|
155
|
-
NEGATIVE_INFINITY: T.number,
|
|
156
|
-
NaN: T.number,
|
|
157
|
-
EPSILON: T.number,
|
|
158
|
-
},
|
|
159
|
-
String: {
|
|
160
|
-
// fromCharCode/fromCodePoint over all-number-literal args evaluate
|
|
161
|
-
// with the real String static (hoek's escaper builds its 0x2028
|
|
162
|
-
// separator literal this way before handing it to escapeJson); any
|
|
163
|
-
// non-literal arg keeps only the string return type (the result is
|
|
164
|
-
// always a string, just not a knowable one). fromCodePoint also
|
|
165
|
-
// falls back on out-of-range literals (real RangeError → string).
|
|
166
|
-
fromCharCode: T.fnSig([T.number], T.string, T.never, (args) => {
|
|
167
|
-
const codes = literalCodeUnits(args);
|
|
168
|
-
return codes === null ? T.string : T.literal(String.fromCharCode(...codes));
|
|
169
|
-
}),
|
|
170
|
-
fromCodePoint: T.fnSig([T.number], T.string, T.never, (args) => {
|
|
171
|
-
const codes = literalCodeUnits(args);
|
|
172
|
-
if (codes === null) return T.string;
|
|
173
|
-
try {
|
|
174
|
-
return T.literal(String.fromCodePoint(...codes));
|
|
175
|
-
} catch {
|
|
176
|
-
return undefined;
|
|
177
|
-
}
|
|
178
|
-
}),
|
|
179
|
-
},
|
|
180
|
-
Promise: PROMISE_STATIC_METHODS,
|
|
181
|
-
Symbol: { ...SYMBOL_STATIC_METHODS, ...SYMBOL_STATIC_PROPS },
|
|
182
|
-
Reflect: REFLECT_METHODS as unknown as Record<string, TypeValue>,
|
|
183
|
-
Intl: {
|
|
184
|
-
DateTimeFormat: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
185
|
-
NumberFormat: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
186
|
-
},
|
|
187
|
-
parseInt: T.number,
|
|
188
|
-
parseFloat: T.number,
|
|
189
|
-
isNaN: T.boolean,
|
|
190
|
-
isFinite: T.boolean,
|
|
191
|
-
// 全局数值常量:与 @nudo:env es 的 globals 对齐(env 绑定优先)。无 env
|
|
192
|
-
// 指令时在此解析为 number——否则 Infinity/NaN 命中大写开头的未知全局
|
|
193
|
-
// 检测,触发 nudo:unknown-global 与 nudo:builtin-unknown 诊断。
|
|
194
|
-
Infinity: T.number,
|
|
195
|
-
NaN: T.number,
|
|
196
|
-
};
|
|
197
|
-
/** Harvest String.fromCharCode/fromCodePoint arguments: a list of number
|
|
198
|
-
* literals ready for real evaluation, or null when any argument is
|
|
199
|
-
* non-literal (symbolic → the caller keeps its plain string fallback).
|
|
200
|
-
* An empty argument list yields [] (both statics return ""). */
|
|
201
|
-
function literalCodeUnits(args: TypeValue[]): number[] | null {
|
|
202
|
-
const codes: number[] = [];
|
|
203
|
-
for (const a of args) {
|
|
204
|
-
if (a?.kind !== "literal" || typeof a.value !== "number") return null;
|
|
205
|
-
codes.push(a.value);
|
|
206
|
-
}
|
|
207
|
-
return codes;
|
|
208
|
-
}
|
|
209
|
-
function descriptorOf(prop: TypeValue): TypeValue {
|
|
210
|
-
return T.object({
|
|
211
|
-
value: prop,
|
|
212
|
-
writable: T.boolean,
|
|
213
|
-
enumerable: T.boolean,
|
|
214
|
-
configurable: T.boolean,
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
// `x instanceof C` / `Buffer.isBuffer(x)` / `Array.isArray(x)` literal
|
|
218
|
-
// answer for structurally-known receivers; undefined keeps the symbolic
|
|
219
|
-
// fallback (boolean).
|
|
220
|
-
function builtinInstanceTest(v: TypeValue | undefined, className: string): boolean | undefined {
|
|
221
|
-
if (!v) return undefined;
|
|
222
|
-
let base: TypeValue = v;
|
|
223
|
-
while (base.kind === "refined") base = base.base;
|
|
224
|
-
switch (base.kind) {
|
|
225
|
-
case "array":
|
|
226
|
-
case "tuple": return className === "Array" || className === "Object";
|
|
227
|
-
case "object": return className === "Object";
|
|
228
|
-
case "function": return className === "Function" || className === "Object";
|
|
229
|
-
case "promise": return className === "Promise" || className === "Object";
|
|
230
|
-
case "literal":
|
|
231
|
-
case "primitive": return false;
|
|
232
|
-
default: return undefined;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
/** JSON 值 → TypeValue(字面量级精确:package.json 版本号等成为 string 字面量) */
|
|
236
|
-
function jsonToTypeValue(v: unknown): TypeValue {
|
|
237
|
-
if (v === null) return T.literal(null);
|
|
238
|
-
if (typeof v === "string") return T.literal(v);
|
|
239
|
-
if (typeof v === "number") return T.literal(v);
|
|
240
|
-
if (typeof v === "boolean") return T.literal(v);
|
|
241
|
-
if (Array.isArray(v)) return T.tuple(v.map(jsonToTypeValue));
|
|
242
|
-
if (typeof v === "object") {
|
|
243
|
-
const props: Record<string, TypeValue> = {};
|
|
244
|
-
for (const [k, val] of Object.entries(v)) {
|
|
245
|
-
// Define-as-own: plain assignment would let a "__proto__" key trip
|
|
246
|
-
// the setter on the props record (dropping it and corrupting the
|
|
247
|
-
// record's prototype) — JSON.parse creates an own property, and
|
|
248
|
-
// hoek's prototype-poisoning fixtures exercise exactly that key.
|
|
249
|
-
Object.defineProperty(props, k, {
|
|
250
|
-
value: jsonToTypeValue(val),
|
|
251
|
-
enumerable: true,
|
|
252
|
-
writable: true,
|
|
253
|
-
configurable: true,
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
return T.object(props);
|
|
257
|
-
}
|
|
258
|
-
return T.unknown;
|
|
259
|
-
}
|
|
260
|
-
export { BUILTIN_STATIC_METHODS, builtinInstanceTest, jsonToTypeValue };
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { type TypeValue, T } from "@nudojs/core";
|
|
2
|
-
|
|
3
|
-
export const SYMBOL_STATIC_METHODS: Record<string, TypeValue> = {
|
|
4
|
-
for: T.symbol,
|
|
5
|
-
keyFor: T.union(T.string, T.undefined),
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export const SYMBOL_STATIC_PROPS: Record<string, TypeValue> = {
|
|
9
|
-
asyncIterator: T.symbol,
|
|
10
|
-
iterator: T.symbol,
|
|
11
|
-
toPrimitive: T.symbol,
|
|
12
|
-
toStringTag: T.symbol,
|
|
13
|
-
hasInstance: T.symbol,
|
|
14
|
-
species: T.symbol,
|
|
15
|
-
isConcatSpreadable: T.symbol,
|
|
16
|
-
};
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { type TypeValue, T } from "@nudojs/core";
|
|
2
|
-
|
|
3
|
-
export const URL_PROPS: Record<string, TypeValue> = {
|
|
4
|
-
href: T.string,
|
|
5
|
-
origin: T.string,
|
|
6
|
-
protocol: T.string,
|
|
7
|
-
host: T.string,
|
|
8
|
-
hostname: T.string,
|
|
9
|
-
port: T.string,
|
|
10
|
-
pathname: T.string,
|
|
11
|
-
search: T.string,
|
|
12
|
-
hash: T.string,
|
|
13
|
-
username: T.string,
|
|
14
|
-
password: T.string,
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const URL_INSTANCE_METHODS: Record<string, (...args: TypeValue[]) => TypeValue> = {
|
|
18
|
-
toString: () => T.string,
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export const URLSearchParams_INSTANCE_METHODS: Record<string, (...args: TypeValue[]) => TypeValue> = {
|
|
22
|
-
get: () => T.union(T.string, T.null),
|
|
23
|
-
set: () => T.undefined,
|
|
24
|
-
has: () => T.boolean,
|
|
25
|
-
delete: () => T.undefined,
|
|
26
|
-
append: () => T.undefined,
|
|
27
|
-
toString: () => T.string,
|
|
28
|
-
getAll: () => T.array(T.string),
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export function createURLType(): TypeValue {
|
|
32
|
-
return T.instanceOf("URL", {
|
|
33
|
-
href: T.string,
|
|
34
|
-
origin: T.string,
|
|
35
|
-
protocol: T.string,
|
|
36
|
-
host: T.string,
|
|
37
|
-
hostname: T.string,
|
|
38
|
-
port: T.string,
|
|
39
|
-
pathname: T.string,
|
|
40
|
-
search: T.string,
|
|
41
|
-
hash: T.string,
|
|
42
|
-
username: T.string,
|
|
43
|
-
password: T.string,
|
|
44
|
-
toString: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function createURLSearchParamsType(): TypeValue {
|
|
49
|
-
return T.instanceOf("URLSearchParams", {
|
|
50
|
-
get: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
51
|
-
set: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
52
|
-
has: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
53
|
-
delete: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
54
|
-
append: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
55
|
-
toString: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
56
|
-
getAll: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
57
|
-
});
|
|
58
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { type TypeValue, T } from "@nudojs/core";
|
|
2
|
-
|
|
3
|
-
export const WEAKMAP_INSTANCE_METHODS: Record<string, (...args: TypeValue[]) => TypeValue> = {
|
|
4
|
-
get: (_key: TypeValue) => T.union(T.unknown, T.undefined),
|
|
5
|
-
set: (_key: TypeValue, _value: TypeValue, map?: TypeValue) => map ?? T.unknown,
|
|
6
|
-
has: () => T.boolean,
|
|
7
|
-
delete: () => T.boolean,
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export const WEAKSET_INSTANCE_METHODS: Record<string, (...args: TypeValue[]) => TypeValue> = {
|
|
11
|
-
add: (_value: TypeValue, set?: TypeValue) => set ?? T.unknown,
|
|
12
|
-
has: () => T.boolean,
|
|
13
|
-
delete: () => T.boolean,
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export function createWeakMapType(): TypeValue {
|
|
17
|
-
return T.instanceOf("WeakMap", {});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function createWeakSetType(): TypeValue {
|
|
21
|
-
return T.instanceOf("WeakSet", {});
|
|
22
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { type TypeValue, T } from "@nudojs/core";
|
|
2
|
-
|
|
3
|
-
export const RESPONSE_PROPS: Record<string, TypeValue> = {
|
|
4
|
-
ok: T.boolean,
|
|
5
|
-
status: T.number,
|
|
6
|
-
statusText: T.string,
|
|
7
|
-
url: T.string,
|
|
8
|
-
type: T.string,
|
|
9
|
-
redirected: T.boolean,
|
|
10
|
-
headers: T.unknown, // Headers instance
|
|
11
|
-
body: T.unknown,
|
|
12
|
-
bodyUsed: T.boolean,
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export const RESPONSE_INSTANCE_METHODS: Record<string, (...args: TypeValue[]) => TypeValue> = {
|
|
16
|
-
json: () => T.promise(T.unknown),
|
|
17
|
-
text: () => T.promise(T.string),
|
|
18
|
-
arrayBuffer: () => T.promise(T.unknown),
|
|
19
|
-
blob: () => T.promise(T.unknown),
|
|
20
|
-
clone: () => T.unknown,
|
|
21
|
-
formData: () => T.promise(T.unknown),
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export const HEADERS_INSTANCE_METHODS: Record<string, (...args: TypeValue[]) => TypeValue> = {
|
|
25
|
-
get: () => T.union(T.string, T.null),
|
|
26
|
-
set: () => T.undefined,
|
|
27
|
-
has: () => T.boolean,
|
|
28
|
-
delete: () => T.undefined,
|
|
29
|
-
append: () => T.undefined,
|
|
30
|
-
entries: () => T.unknown,
|
|
31
|
-
keys: () => T.unknown,
|
|
32
|
-
values: () => T.unknown,
|
|
33
|
-
forEach: () => T.undefined,
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export const FORMDATA_INSTANCE_METHODS: Record<string, (...args: TypeValue[]) => TypeValue> = {
|
|
37
|
-
get: () => T.union(T.string, T.null),
|
|
38
|
-
set: () => T.undefined,
|
|
39
|
-
has: () => T.boolean,
|
|
40
|
-
delete: () => T.undefined,
|
|
41
|
-
append: () => T.undefined,
|
|
42
|
-
getAll: () => T.array(T.unknown),
|
|
43
|
-
entries: () => T.unknown,
|
|
44
|
-
keys: () => T.unknown,
|
|
45
|
-
values: () => T.unknown,
|
|
46
|
-
forEach: () => T.undefined,
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export const ABORTCONTROLLER_PROPS: Record<string, TypeValue> = {
|
|
50
|
-
signal: T.unknown,
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export const ABORTCONTROLLER_INSTANCE_METHODS: Record<string, (...args: TypeValue[]) => TypeValue> = {
|
|
54
|
-
abort: () => T.undefined,
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export function createResponseType(): TypeValue {
|
|
58
|
-
return T.instanceOf("Response", {
|
|
59
|
-
ok: T.boolean,
|
|
60
|
-
status: T.number,
|
|
61
|
-
statusText: T.string,
|
|
62
|
-
url: T.string,
|
|
63
|
-
type: T.string,
|
|
64
|
-
redirected: T.boolean,
|
|
65
|
-
headers: T.unknown,
|
|
66
|
-
body: T.unknown,
|
|
67
|
-
bodyUsed: T.boolean,
|
|
68
|
-
json: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
69
|
-
text: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
70
|
-
arrayBuffer: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
71
|
-
blob: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
72
|
-
clone: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
73
|
-
formData: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function createHeadersType(): TypeValue {
|
|
78
|
-
return T.instanceOf("Headers", {
|
|
79
|
-
get: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
80
|
-
set: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
81
|
-
has: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
82
|
-
delete: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
83
|
-
append: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
84
|
-
entries: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
85
|
-
keys: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
86
|
-
values: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
87
|
-
forEach: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function createFormDataType(): TypeValue {
|
|
92
|
-
return T.instanceOf("FormData", {
|
|
93
|
-
get: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
94
|
-
set: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
95
|
-
has: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
96
|
-
delete: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
97
|
-
append: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
98
|
-
getAll: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
99
|
-
entries: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
100
|
-
keys: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
101
|
-
values: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
102
|
-
forEach: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function createAbortControllerType(): TypeValue {
|
|
107
|
-
return T.instanceOf("AbortController", {
|
|
108
|
-
signal: T.unknown,
|
|
109
|
-
abort: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
110
|
-
});
|
|
111
|
-
}
|
package/src/config.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
-
import { resolve, dirname } from "node:path";
|
|
3
|
-
|
|
4
|
-
export type NudoConfig = {
|
|
5
|
-
env?: string[];
|
|
6
|
-
mocks?: Record<string, string>;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export function findProjectConfig(startDir: string): { config: NudoConfig; projectDir: string } | null {
|
|
10
|
-
let dir = resolve(startDir);
|
|
11
|
-
const root = resolve("/");
|
|
12
|
-
|
|
13
|
-
while (dir !== root) {
|
|
14
|
-
const pkgPath = resolve(dir, "package.json");
|
|
15
|
-
if (existsSync(pkgPath)) {
|
|
16
|
-
try {
|
|
17
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
18
|
-
if (pkg.nudo) {
|
|
19
|
-
return { config: pkg.nudo as NudoConfig, projectDir: dir };
|
|
20
|
-
}
|
|
21
|
-
} catch {
|
|
22
|
-
// ignore parse errors
|
|
23
|
-
}
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
const parent = dirname(dir);
|
|
27
|
-
if (parent === dir) break;
|
|
28
|
-
dir = parent;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return null;
|
|
32
|
-
}
|
package/src/env-loader.ts
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import { readFileSync, existsSync, statSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { resolve as resolvePath, join as joinPath } from "node:path";
|
|
3
|
-
import { pathToFileURL } from "node:url";
|
|
4
|
-
import { createRequire } from "node:module";
|
|
5
|
-
import { createHash } from "node:crypto";
|
|
6
|
-
import { tmpdir } from "node:os";
|
|
7
|
-
import { type TypeValue, type Environment } from "@nudojs/core";
|
|
8
|
-
import { defineEnv as defineEsEnv } from "@nudojs/env/es";
|
|
9
|
-
import { defineEnv as defineWebEnv } from "@nudojs/env/web";
|
|
10
|
-
import { defineEnv as defineNodeEnv } from "@nudojs/env/node";
|
|
11
|
-
|
|
12
|
-
type EnvDefinition = {
|
|
13
|
-
globals: Record<string, TypeValue>;
|
|
14
|
-
modules?: Record<string, Record<string, TypeValue>>;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const envFactories: Record<string, () => EnvDefinition> = {
|
|
18
|
-
es: defineEsEnv,
|
|
19
|
-
web: defineWebEnv,
|
|
20
|
-
node: defineNodeEnv,
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const impliedDeps: Record<string, string[]> = {
|
|
24
|
-
web: ["es"],
|
|
25
|
-
node: ["es"],
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
function resolveEnvNames(names: string[]): string[] {
|
|
29
|
-
const resolved = new Set<string>();
|
|
30
|
-
const visit = (name: string) => {
|
|
31
|
-
if (resolved.has(name)) return;
|
|
32
|
-
const deps = impliedDeps[name];
|
|
33
|
-
if (deps) deps.forEach(visit);
|
|
34
|
-
resolved.add(name);
|
|
35
|
-
};
|
|
36
|
-
names.forEach(visit);
|
|
37
|
-
return [...resolved];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export type LoadedEnv = {
|
|
41
|
-
modules: Record<string, Record<string, TypeValue>>;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
// Path-based env files (`/// @nudo:env ./nudo-harvest-node.ts`) are imported
|
|
45
|
-
// asynchronously and cached here; the sync loadEnvs() below consults this map
|
|
46
|
-
// so sync consumers (analyzeFile & friends) see them after a preload pass.
|
|
47
|
-
const pathEnvCache = new Map<string, () => EnvDefinition>();
|
|
48
|
-
// resolvedPath → factory; looked up by trying the directive spelling as-is and
|
|
49
|
-
// resolved against every baseDir seen during preload (covers `./env.ts`,
|
|
50
|
-
// `../env.ts`, and bare `env.ts` spellings from different analyzed files).
|
|
51
|
-
const pathEnvByPath = new Map<string, () => EnvDefinition>();
|
|
52
|
-
const pathEnvBaseDirs = new Set<string>();
|
|
53
|
-
|
|
54
|
-
function isPathEnvName(name: string, baseDir: string): boolean {
|
|
55
|
-
if (name in envFactories) return false;
|
|
56
|
-
if (name.includes("/") || name.startsWith("./") || name.startsWith("../")) return true;
|
|
57
|
-
const resolved = resolvePath(baseDir, name);
|
|
58
|
-
return resolved.endsWith(".ts") && existsSync(resolved);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// A harvested env file lives anywhere on disk (e.g. /tmp) and imports
|
|
62
|
-
// "@nudojs/core" — a bare specifier that only resolves from inside the repo's
|
|
63
|
-
// packages. When the direct import fails, rewrite those specifiers to absolute
|
|
64
|
-
// file URLs (resolved from this package, which depends on them) and import a
|
|
65
|
-
// cached copy under the OS tmpdir.
|
|
66
|
-
function rewriteBareImports(text: string): string | null {
|
|
67
|
-
const require = createRequire(import.meta.url);
|
|
68
|
-
let rewrote = false;
|
|
69
|
-
const out = text.replace(/(["'])(@nudojs\/[a-z0-9-]+)\1/g, (_m, quote: string, spec: string) => {
|
|
70
|
-
try {
|
|
71
|
-
const entry = require.resolve(spec);
|
|
72
|
-
const url = pathToFileURL(entry).href;
|
|
73
|
-
rewrote = true;
|
|
74
|
-
return `${quote}${url}${quote}`;
|
|
75
|
-
} catch {
|
|
76
|
-
return `${quote}${spec}${quote}`;
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
return rewrote ? out : null;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function importPathEnv(resolvedPath: string, mtimeMs: number): Promise<void> {
|
|
83
|
-
const cacheKey = `${resolvedPath}:${mtimeMs}`;
|
|
84
|
-
if (pathEnvCache.has(cacheKey)) return;
|
|
85
|
-
|
|
86
|
-
let mod: { defineEnv?: unknown } | null = null;
|
|
87
|
-
try {
|
|
88
|
-
const url = pathToFileURL(resolvedPath).href + `?mtime=${mtimeMs}`;
|
|
89
|
-
mod = (await import(url)) as { defineEnv?: unknown };
|
|
90
|
-
} catch {
|
|
91
|
-
// Fall through to the rewritten-copy fallback below.
|
|
92
|
-
mod = null;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (!mod) {
|
|
96
|
-
try {
|
|
97
|
-
const text = readFileSync(resolvedPath, "utf-8");
|
|
98
|
-
const rewritten = rewriteBareImports(text);
|
|
99
|
-
if (rewritten === null) return; // nothing to fix; the import failure was something else
|
|
100
|
-
const cacheDir = joinPath(tmpdir(), "nudo-env");
|
|
101
|
-
mkdirSync(cacheDir, { recursive: true });
|
|
102
|
-
const hash = createHash("md5").update(`${resolvedPath}:${mtimeMs}`).digest("hex").slice(0, 16);
|
|
103
|
-
const copyPath = joinPath(cacheDir, `${hash}.ts`);
|
|
104
|
-
writeFileSync(copyPath, rewritten, "utf-8");
|
|
105
|
-
mod = (await import(pathToFileURL(copyPath).href)) as { defineEnv?: unknown };
|
|
106
|
-
} catch {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (mod && typeof mod.defineEnv === "function") {
|
|
112
|
-
pathEnvCache.set(cacheKey, mod.defineEnv as () => EnvDefinition);
|
|
113
|
-
pathEnvByPath.set(resolvedPath, mod.defineEnv as () => EnvDefinition);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function lookupPathEnv(name: string): (() => EnvDefinition) | undefined {
|
|
118
|
-
if (pathEnvByPath.size === 0) return undefined;
|
|
119
|
-
const candidates = name.startsWith("/")
|
|
120
|
-
? [name]
|
|
121
|
-
: [name, ...[...pathEnvBaseDirs].map((d) => resolvePath(d, name))];
|
|
122
|
-
for (const candidate of candidates) {
|
|
123
|
-
const hit = pathEnvByPath.get(candidate);
|
|
124
|
-
if (hit) return hit;
|
|
125
|
-
}
|
|
126
|
-
return undefined;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export async function preloadPathEnvs(envNames: string[], baseDir: string): Promise<void> {
|
|
130
|
-
pathEnvBaseDirs.add(baseDir);
|
|
131
|
-
for (const name of envNames) {
|
|
132
|
-
if (!isPathEnvName(name, baseDir)) continue;
|
|
133
|
-
const resolved = resolvePath(baseDir, name);
|
|
134
|
-
if (!existsSync(resolved)) continue; // silent skip, matching registry behavior
|
|
135
|
-
try {
|
|
136
|
-
const { mtimeMs } = statSync(resolved);
|
|
137
|
-
await importPathEnv(resolved, mtimeMs);
|
|
138
|
-
} catch {
|
|
139
|
-
// unreadable/unstattable — skip
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export async function loadEnvsAsync(
|
|
145
|
-
envNames: string[],
|
|
146
|
-
globalEnv: Environment,
|
|
147
|
-
baseDir: string = process.cwd(),
|
|
148
|
-
): Promise<LoadedEnv> {
|
|
149
|
-
await preloadPathEnvs(envNames, baseDir);
|
|
150
|
-
return loadEnvs(envNames, globalEnv);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export function loadEnvs(envNames: string[], globalEnv: Environment): LoadedEnv {
|
|
154
|
-
const allModules: Record<string, Record<string, TypeValue>> = {};
|
|
155
|
-
const resolved = resolveEnvNames(envNames);
|
|
156
|
-
|
|
157
|
-
for (const name of resolved) {
|
|
158
|
-
const factory = envFactories[name] ?? lookupPathEnv(name);
|
|
159
|
-
if (!factory) continue;
|
|
160
|
-
const def = factory();
|
|
161
|
-
|
|
162
|
-
for (const [key, value] of Object.entries(def.globals)) {
|
|
163
|
-
globalEnv.bind(key, value);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (def.modules) {
|
|
167
|
-
for (const [modName, exports] of Object.entries(def.modules)) {
|
|
168
|
-
allModules[modName] = { ...allModules[modName], ...exports };
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return { modules: allModules };
|
|
174
|
-
}
|