@milkio/astra 1.0.0-alpha.97 → 1.0.0-alpha.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +149 -151
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -1,262 +1,260 @@
|
|
|
1
|
-
import { join, dirname } from
|
|
2
|
-
import { fileURLToPath } from
|
|
3
|
-
import { existsSync } from
|
|
4
|
-
import { readFile } from
|
|
5
|
-
import { cwd } from
|
|
6
|
-
import { load } from
|
|
7
|
-
import { TSON } from
|
|
8
|
-
import { format } from
|
|
9
|
-
import type { CookbookOptions } from
|
|
1
|
+
import { join, dirname } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { cwd } from "node:process";
|
|
6
|
+
import { load } from "js-toml";
|
|
7
|
+
import { TSON } from "@southern-aurora/tson";
|
|
8
|
+
import { format } from "date-fns";
|
|
9
|
+
import type { CookbookOptions } from "./utils/cookbook-dto-types";
|
|
10
10
|
|
|
11
11
|
export type AstraOptionsInit = {
|
|
12
|
-
stargate: { $types: any
|
|
13
|
-
bootstrap: () => Promise<Record<string, any
|
|
14
|
-
}
|
|
12
|
+
stargate: { $types: any; execute: any; ping: any; __cookbook: any };
|
|
13
|
+
bootstrap: () => Promise<Record<string, any>>;
|
|
14
|
+
};
|
|
15
15
|
|
|
16
|
-
type GeneratorGeneric<T> = T extends AsyncGenerator<infer I> ? I : never
|
|
16
|
+
type GeneratorGeneric<T> = T extends AsyncGenerator<infer I> ? I : never;
|
|
17
17
|
|
|
18
|
-
type Mixin<T, U> = U & Omit<T, keyof U
|
|
18
|
+
type Mixin<T, U> = U & Omit<T, keyof U>;
|
|
19
19
|
|
|
20
20
|
type ExecuteOptions = {
|
|
21
|
-
headers?: Record<string, string
|
|
22
|
-
timeout?: number
|
|
23
|
-
type?:
|
|
24
|
-
}
|
|
21
|
+
headers?: Record<string, string>;
|
|
22
|
+
timeout?: number;
|
|
23
|
+
type?: "action" | "stream";
|
|
24
|
+
};
|
|
25
25
|
|
|
26
|
-
type ExecuteResultsOption = { executeId: string }
|
|
26
|
+
type ExecuteResultsOption = { executeId: string };
|
|
27
27
|
|
|
28
|
-
type
|
|
29
|
-
logger: Logger
|
|
30
|
-
}
|
|
28
|
+
type Context = {
|
|
29
|
+
logger: Logger;
|
|
30
|
+
};
|
|
31
31
|
|
|
32
32
|
type Logger = {
|
|
33
|
-
debug: (description: string, ...params: Array<unknown>) => Log
|
|
34
|
-
info: (description: string, ...params: Array<unknown>) => Log
|
|
35
|
-
warn: (description: string, ...params: Array<unknown>) => Log
|
|
36
|
-
error: (description: string, ...params: Array<unknown>) => Log
|
|
37
|
-
response: (description: string, ...params: Array<unknown>) => Log
|
|
38
|
-
}
|
|
33
|
+
debug: (description: string, ...params: Array<unknown>) => Log;
|
|
34
|
+
info: (description: string, ...params: Array<unknown>) => Log;
|
|
35
|
+
warn: (description: string, ...params: Array<unknown>) => Log;
|
|
36
|
+
error: (description: string, ...params: Array<unknown>) => Log;
|
|
37
|
+
response: (description: string, ...params: Array<unknown>) => Log;
|
|
38
|
+
};
|
|
39
39
|
|
|
40
|
-
type Log = [string /* executeId */,
|
|
40
|
+
type Log = [string /* executeId */, "[DEBUG]" | "[INFO]" | "[WARN]" | "[ERROR]" | "[RESPONSE]", string, string, ...Array<unknown>];
|
|
41
41
|
|
|
42
|
-
type Reject = (description: string, ...params: Array<unknown>) => Error
|
|
42
|
+
type Reject = (description: string, ...params: Array<unknown>) => Error;
|
|
43
43
|
|
|
44
|
-
export async function createAstra<AstraOptions extends AstraOptionsInit, Generated extends AstraOptions[
|
|
45
|
-
if (!existsSync(join(cwd(),
|
|
46
|
-
const cookbookOptions = load((await readFile(join(cwd(),
|
|
44
|
+
export async function createAstra<AstraOptions extends AstraOptionsInit, Generated extends AstraOptions["stargate"]["$types"]["generated"]>(astraOptions: AstraOptions) {
|
|
45
|
+
if (!existsSync(join(cwd(), "cookbook.toml"))) throw new Error(`The "cookbook.toml" file does not exist in the current directory. If you are running the test with the VS Code extension, make sure it exists in the root directory of the folder you are opening with VS Code.`);
|
|
46
|
+
const cookbookOptions = load((await readFile(join(cwd(), "cookbook.toml"))).toString()) as CookbookOptions;
|
|
47
47
|
// wait for all milkio projects to start and can be accessed
|
|
48
48
|
// the reason why stargate's ping method is not used directly is that even if only one project is tested, it is necessary to wait for all milkio projects to start
|
|
49
49
|
await Promise.all([
|
|
50
50
|
...(() => {
|
|
51
|
-
const projectStatus = new Map<string, { promise: Promise<undefined
|
|
51
|
+
const projectStatus = new Map<string, { promise: Promise<undefined>; resolve: (value?: undefined | PromiseLike<undefined>) => void; reject: (reason?: any) => void }>();
|
|
52
52
|
for (const projectName in cookbookOptions.projects) {
|
|
53
|
-
const project = cookbookOptions.projects[projectName]
|
|
54
|
-
if (project.type !==
|
|
55
|
-
projectStatus.set(projectName, withResolvers())
|
|
56
|
-
let counter = 65
|
|
53
|
+
const project = cookbookOptions.projects[projectName];
|
|
54
|
+
if (project.type !== "milkio") continue;
|
|
55
|
+
projectStatus.set(projectName, withResolvers());
|
|
56
|
+
let counter = 65;
|
|
57
57
|
let timer: Timer | null = setInterval(async () => {
|
|
58
58
|
if (--counter <= 0) {
|
|
59
|
-
clearInterval(timer!)
|
|
60
|
-
timer = null
|
|
61
|
-
console.warn(`[cookbook] Your project ${projectName} (http://localhost:${project.port}/) HTTP server hasn't started for too long.`)
|
|
62
|
-
projectStatus.get(projectName)!.resolve(undefined)
|
|
63
|
-
return
|
|
59
|
+
clearInterval(timer!);
|
|
60
|
+
timer = null;
|
|
61
|
+
console.warn(`[cookbook] Your project ${projectName} (http://localhost:${project.port}/) HTTP server hasn't started for too long.`);
|
|
62
|
+
projectStatus.get(projectName)!.resolve(undefined);
|
|
63
|
+
return;
|
|
64
64
|
}
|
|
65
65
|
try {
|
|
66
|
-
console.log(
|
|
67
|
-
const response = await fetchWithTimeout(`http://localhost:${project.port}/generate_204`, { method:
|
|
66
|
+
console.log("\n[ASTRA]", `connecting.. ${counter >= 64 ? "" : `(${counter})`}`);
|
|
67
|
+
const response = await fetchWithTimeout(`http://localhost:${project.port}/generate_204`, { method: "HEAD", timeout: 1024 });
|
|
68
68
|
if (response.status === 204) {
|
|
69
|
-
if (timer) clearTimeout(timer)
|
|
70
|
-
timer = null
|
|
71
|
-
return projectStatus.get(projectName)!.resolve(undefined)
|
|
69
|
+
if (timer) clearTimeout(timer);
|
|
70
|
+
timer = null;
|
|
71
|
+
return projectStatus.get(projectName)!.resolve(undefined);
|
|
72
72
|
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
}, 100)
|
|
73
|
+
} catch (error) {}
|
|
74
|
+
}, 100);
|
|
76
75
|
}
|
|
77
|
-
return Array.from(projectStatus.values()).map(v => v.promise)
|
|
76
|
+
return Array.from(projectStatus.values()).map((v) => v.promise);
|
|
78
77
|
})(),
|
|
79
|
-
])
|
|
78
|
+
]);
|
|
80
79
|
|
|
81
|
-
type Execute = <Path extends keyof Generated[
|
|
80
|
+
type Execute = <Path extends keyof Generated["routeSchema"]>(
|
|
82
81
|
path: Path,
|
|
83
82
|
options?: Mixin<
|
|
84
83
|
ExecuteOptions,
|
|
85
84
|
| {
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
params?: Generated["routeSchema"][Path]["types"]["params"];
|
|
86
|
+
}
|
|
88
87
|
| {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
params?: Partial<Generated["routeSchema"][Path]["types"]["params"]>;
|
|
89
|
+
generateParams: true;
|
|
90
|
+
}
|
|
92
91
|
>,
|
|
93
92
|
) => Promise<
|
|
94
|
-
Generated[
|
|
93
|
+
Generated["routeSchema"][Path]["types"]["🐣"] extends boolean
|
|
95
94
|
? // action
|
|
96
|
-
[Partial<Generated[
|
|
95
|
+
[Partial<Generated["rejectCode"]>, null, ExecuteResultsOption] | [null, Generated["routeSchema"][Path]["types"]["result"], ExecuteResultsOption]
|
|
97
96
|
: // stream
|
|
98
|
-
[Partial<Generated[
|
|
99
|
-
|
|
97
|
+
[Partial<Generated["rejectCode"]>, null, ExecuteResultsOption] | [null, AsyncGenerator<[Partial<Generated["rejectCode"]>, null] | [null, GeneratorGeneric<Generated["routeSchema"][Path]["types"]["result"]>], ExecuteResultsOption>]
|
|
98
|
+
>;
|
|
100
99
|
|
|
101
100
|
type MirrorWorld = Mixin<
|
|
102
|
-
Awaited<ReturnType<AstraOptions[
|
|
101
|
+
Awaited<ReturnType<AstraOptions["bootstrap"]>>,
|
|
103
102
|
{
|
|
104
|
-
paths: { cwd: string
|
|
105
|
-
execute: Execute
|
|
103
|
+
paths: { cwd: string; milkio: string; generated: string };
|
|
104
|
+
execute: Execute;
|
|
106
105
|
}
|
|
107
|
-
|
|
106
|
+
>;
|
|
108
107
|
|
|
109
108
|
return {
|
|
110
109
|
options: astraOptions,
|
|
111
110
|
async createMirrorWorld(importMetaUrl: string): Promise<[Context, Reject, MirrorWorld]> {
|
|
112
|
-
const thisFilePath = join(fileURLToPath(importMetaUrl))
|
|
113
|
-
const thisFileDirPath = join(dirname(thisFilePath)).replaceAll(
|
|
114
|
-
const thisFileDirPathArr = thisFileDirPath.split(
|
|
115
|
-
let projectName =
|
|
111
|
+
const thisFilePath = join(fileURLToPath(importMetaUrl));
|
|
112
|
+
const thisFileDirPath = join(dirname(thisFilePath)).replaceAll("\\", "/");
|
|
113
|
+
const thisFileDirPathArr = thisFileDirPath.split("/");
|
|
114
|
+
let projectName = "";
|
|
116
115
|
|
|
117
116
|
await (async () => {
|
|
118
|
-
let isProjectsDirectory = false
|
|
117
|
+
let isProjectsDirectory = false;
|
|
119
118
|
for (let i = 0; i < thisFileDirPathArr.length; i++) {
|
|
120
|
-
if (thisFileDirPathArr[i] ===
|
|
121
|
-
isProjectsDirectory = true
|
|
122
|
-
continue
|
|
119
|
+
if (thisFileDirPathArr[i] === "projects") {
|
|
120
|
+
isProjectsDirectory = true;
|
|
121
|
+
continue;
|
|
123
122
|
}
|
|
124
|
-
if (isProjectsDirectory === false) continue
|
|
125
|
-
projectName = thisFileDirPathArr[i]
|
|
126
|
-
break
|
|
123
|
+
if (isProjectsDirectory === false) continue;
|
|
124
|
+
projectName = thisFileDirPathArr[i];
|
|
125
|
+
break;
|
|
127
126
|
}
|
|
128
|
-
if (projectName ===
|
|
129
|
-
let projectNameChecked = false
|
|
127
|
+
if (projectName === "") throw new Error("Unable to determine the path of the current test, make sure the test is under a milkio project.");
|
|
128
|
+
let projectNameChecked = false;
|
|
130
129
|
for (const projectNameForCookbookOptions in cookbookOptions.projects) {
|
|
131
130
|
if (projectNameForCookbookOptions === projectName) {
|
|
132
|
-
projectNameChecked = true
|
|
133
|
-
break
|
|
131
|
+
projectNameChecked = true;
|
|
132
|
+
break;
|
|
134
133
|
}
|
|
135
134
|
}
|
|
136
|
-
if (projectNameChecked === false) throw new Error(`Project name "${projectName}" not found in "cookbook.toml" in "projects.${projectName}".`)
|
|
137
|
-
})()
|
|
135
|
+
if (projectNameChecked === false) throw new Error(`Project name "${projectName}" not found in "cookbook.toml" in "projects.${projectName}".`);
|
|
136
|
+
})();
|
|
138
137
|
|
|
139
138
|
const paths = {
|
|
140
|
-
cwd: join(cwd(),
|
|
141
|
-
milkio: join(cwd(),
|
|
142
|
-
generated: join(cwd(),
|
|
143
|
-
}
|
|
139
|
+
cwd: join(cwd(), "projects", projectName),
|
|
140
|
+
milkio: join(cwd(), "projects", projectName, ".milkio"),
|
|
141
|
+
generated: join(cwd(), "projects", projectName, ".milkio"),
|
|
142
|
+
};
|
|
144
143
|
|
|
145
|
-
const execute = async (path: Parameters<MirrorWorld[
|
|
146
|
-
const options = (optionsInit as any) ?? {}
|
|
144
|
+
const execute = async (path: Parameters<MirrorWorld["execute"]>[0], optionsInit?: Parameters<MirrorWorld["execute"]>[1]) => {
|
|
145
|
+
const options = (optionsInit as any) ?? {};
|
|
147
146
|
if (options?.generateParams === true) {
|
|
148
|
-
if (!options?.params) options.params = {}
|
|
149
|
-
options.params.$milkioGenerateParams =
|
|
147
|
+
if (!options?.params) options.params = {};
|
|
148
|
+
options.params.$milkioGenerateParams = "enable";
|
|
150
149
|
}
|
|
151
150
|
|
|
152
|
-
const results = await this.options.stargate.__cookbook.subscribe(`http://localhost:${cookbookOptions.general.cookbookPort}`)
|
|
151
|
+
const results = await this.options.stargate.__cookbook.subscribe(`http://localhost:${cookbookOptions.general.cookbookPort}`);
|
|
153
152
|
void (async () => {
|
|
154
153
|
for await (const result of results) {
|
|
155
|
-
if (result.type !==
|
|
156
|
-
console.log(
|
|
154
|
+
if (result.type !== "milkio@logger") continue;
|
|
155
|
+
console.log("\n[MILKIO]", ...(result.log ?? []));
|
|
157
156
|
}
|
|
158
|
-
})()
|
|
157
|
+
})();
|
|
159
158
|
|
|
160
|
-
const response = await this.options.stargate.execute(path, options)
|
|
159
|
+
const response = await this.options.stargate.execute(path, options);
|
|
161
160
|
|
|
162
|
-
await new Promise(resolve => setTimeout(resolve, 40))
|
|
163
|
-
context.logger.response(path as string, `\nerror - ${TSON.stringify(response[0])}`, `\nresult - ${typeof response[1]?.next ===
|
|
161
|
+
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
162
|
+
context.logger.response(path as string, `\nerror - ${TSON.stringify(response[0])}`, `\nresult - ${typeof response[1]?.next === "function" ? "AsyncGenerator" : TSON.stringify(response[1])}`);
|
|
164
163
|
|
|
165
|
-
return response
|
|
166
|
-
}
|
|
164
|
+
return response;
|
|
165
|
+
};
|
|
167
166
|
|
|
168
|
-
const getNow = () => format(new Date(),
|
|
167
|
+
const getNow = () => format(new Date(), "(yyyy-MM-dd hh:mm:ss)");
|
|
169
168
|
const onLoggerInserting = (log: Log) => {
|
|
170
169
|
// biome-ignore lint/style/noParameterAssign: <explanation>
|
|
171
|
-
log = [...log]
|
|
172
|
-
log[0] = `\n${log[0]}` as any
|
|
173
|
-
console.log(...log)
|
|
174
|
-
return true
|
|
175
|
-
}
|
|
170
|
+
log = [...log];
|
|
171
|
+
log[0] = `\n${log[0]}` as any;
|
|
172
|
+
console.log(...log);
|
|
173
|
+
return true;
|
|
174
|
+
};
|
|
176
175
|
|
|
177
176
|
const context = {
|
|
178
177
|
logger: {
|
|
179
178
|
debug: (description: string, ...params: Array<unknown>): Log => {
|
|
180
|
-
const log: Log = [
|
|
181
|
-
onLoggerInserting(log)
|
|
182
|
-
return log
|
|
179
|
+
const log: Log = ["[TEST]", "[DEBUG]", description, getNow(), ...params];
|
|
180
|
+
onLoggerInserting(log);
|
|
181
|
+
return log;
|
|
183
182
|
},
|
|
184
183
|
info: (description: string, ...params: Array<unknown>): Log => {
|
|
185
|
-
const log: Log = [
|
|
186
|
-
onLoggerInserting(log)
|
|
187
|
-
return log
|
|
184
|
+
const log: Log = ["[TEST]", "[INFO]", description, getNow(), ...params];
|
|
185
|
+
onLoggerInserting(log);
|
|
186
|
+
return log;
|
|
188
187
|
},
|
|
189
188
|
warn: (description: string, ...params: Array<unknown>): Log => {
|
|
190
|
-
const log: Log = [
|
|
191
|
-
onLoggerInserting(log)
|
|
192
|
-
return log
|
|
189
|
+
const log: Log = ["[TEST]", "[WARN]", description, getNow(), ...params];
|
|
190
|
+
onLoggerInserting(log);
|
|
191
|
+
return log;
|
|
193
192
|
},
|
|
194
193
|
error: (description: string, ...params: Array<unknown>): Log => {
|
|
195
|
-
const log: Log = [
|
|
196
|
-
onLoggerInserting(log)
|
|
197
|
-
return log
|
|
194
|
+
const log: Log = ["[TEST]", "[ERROR]", description, getNow(), ...params];
|
|
195
|
+
onLoggerInserting(log);
|
|
196
|
+
return log;
|
|
198
197
|
},
|
|
199
198
|
response: (path: string, ...params: Array<unknown>): Log => {
|
|
200
|
-
const log: Log = [
|
|
201
|
-
onLoggerInserting(log)
|
|
202
|
-
return log
|
|
199
|
+
const log: Log = ["[TEST]", "[RESPONSE]", path, getNow(), ...params];
|
|
200
|
+
onLoggerInserting(log);
|
|
201
|
+
return log;
|
|
203
202
|
},
|
|
204
203
|
},
|
|
205
|
-
} as Context
|
|
204
|
+
} as Context;
|
|
206
205
|
|
|
207
206
|
const world = {
|
|
208
207
|
...(await astraOptions.bootstrap()),
|
|
209
208
|
paths,
|
|
210
209
|
execute,
|
|
211
|
-
} as any
|
|
210
|
+
} as any;
|
|
212
211
|
|
|
213
212
|
const reject = (...params: Array<unknown>): Error => {
|
|
214
213
|
const output: Array<any> = [
|
|
215
|
-
|
|
214
|
+
"[REJECT]",
|
|
216
215
|
...params.map((param) => {
|
|
217
216
|
try {
|
|
218
|
-
const result = TSON.stringify(param)
|
|
219
|
-
return result
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return error?.toString?.() ?? typeof error
|
|
217
|
+
const result = TSON.stringify(param);
|
|
218
|
+
return result;
|
|
219
|
+
} catch (error) {
|
|
220
|
+
return error?.toString?.() ?? typeof error;
|
|
223
221
|
}
|
|
224
222
|
}),
|
|
225
|
-
]
|
|
226
|
-
console.log(...output)
|
|
223
|
+
];
|
|
224
|
+
console.log(...output);
|
|
227
225
|
for (let index = 1; index < output.length; index++) {
|
|
228
|
-
if (typeof output[index] ===
|
|
229
|
-
output[index] = output[index].toString()
|
|
226
|
+
if (typeof output[index] === "object") {
|
|
227
|
+
output[index] = output[index].toString();
|
|
230
228
|
}
|
|
231
229
|
}
|
|
232
|
-
const message = output.join(
|
|
233
|
-
return new Error(message)
|
|
234
|
-
}
|
|
230
|
+
const message = output.join(" ");
|
|
231
|
+
return new Error(message);
|
|
232
|
+
};
|
|
235
233
|
|
|
236
|
-
return [context, reject, world]
|
|
234
|
+
return [context, reject, world];
|
|
237
235
|
},
|
|
238
|
-
}
|
|
236
|
+
};
|
|
239
237
|
}
|
|
240
238
|
|
|
241
239
|
export async function fetchWithTimeout(url: string, options: FetchRequestInit & { timeout?: number } = {}) {
|
|
242
|
-
const { timeout = 8000 } = options
|
|
240
|
+
const { timeout = 8000 } = options;
|
|
243
241
|
|
|
244
|
-
const controller = new AbortController()
|
|
245
|
-
const id = setTimeout(() => controller.abort(), timeout)
|
|
242
|
+
const controller = new AbortController();
|
|
243
|
+
const id = setTimeout(() => controller.abort(), timeout);
|
|
246
244
|
const response = await fetch(url, {
|
|
247
245
|
...options,
|
|
248
246
|
signal: controller.signal,
|
|
249
|
-
})
|
|
250
|
-
clearTimeout(id)
|
|
251
|
-
return response
|
|
247
|
+
});
|
|
248
|
+
clearTimeout(id);
|
|
249
|
+
return response;
|
|
252
250
|
}
|
|
253
251
|
|
|
254
252
|
function withResolvers<T = any>(): PromiseWithResolvers<T> {
|
|
255
|
-
let resolve: PromiseWithResolvers<T>[
|
|
256
|
-
let reject: PromiseWithResolvers<T>[
|
|
253
|
+
let resolve: PromiseWithResolvers<T>["resolve"];
|
|
254
|
+
let reject: PromiseWithResolvers<T>["reject"];
|
|
257
255
|
const promise = new Promise<T>((res, rej) => {
|
|
258
|
-
resolve = res
|
|
259
|
-
reject = rej
|
|
260
|
-
})
|
|
261
|
-
return { promise, resolve: resolve!, reject: reject! }
|
|
256
|
+
resolve = res;
|
|
257
|
+
reject = rej;
|
|
258
|
+
});
|
|
259
|
+
return { promise, resolve: resolve!, reject: reject! };
|
|
262
260
|
}
|