milkio 0.1.1 → 0.2.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/api-test/index.ts +65 -65
- package/c.ts +37 -35
- package/defines/define-api-test.ts +24 -17
- package/defines/define-api.ts +9 -9
- package/defines/define-command-handler.ts +41 -41
- package/defines/define-http-handler.ts +181 -181
- package/defines/define-middleware.ts +6 -6
- package/defines/define-use.ts +10 -10
- package/index.ts +22 -22
- package/kernel/context.ts +17 -17
- package/kernel/fail.ts +13 -13
- package/kernel/logger.ts +96 -96
- package/kernel/meta.ts +6 -6
- package/kernel/middleware.ts +32 -32
- package/kernel/milkio.ts +250 -224
- package/kernel/runtime.ts +9 -9
- package/kernel/validate.ts +9 -9
- package/package.json +1 -1
- package/scripts/gen-insignificant.ts +239 -239
- package/scripts/gen-significant.ts +118 -120
- package/utils/create-ulid.ts +3 -3
- package/utils/env-to-boolean.ts +5 -5
- package/utils/env-to-number.ts +2 -2
- package/utils/env-to-string.ts +2 -2
- package/utils/exec.ts +18 -18
- package/utils/handle-catch-error.ts +37 -37
- package/utils/header-to-plain-object.ts +7 -7
- package/utils/remove-dir.ts +19 -19
- package/utils/tson.ts +2 -2
- package/.co.toml +0 -2
|
@@ -1,94 +1,92 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
2
|
|
|
3
|
-
import ejs from "ejs"
|
|
4
|
-
import { join } from "node:path"
|
|
5
|
-
import { existsSync, mkdirSync } from "node:fs"
|
|
6
|
-
import { cwd, exit } from "node:process"
|
|
7
|
-
import { unlink, writeFile } from "node:fs/promises"
|
|
8
|
-
import { camel, hyphen } from "@poech/camel-hump-under"
|
|
9
|
-
import { $, Glob } from "bun"
|
|
10
|
-
import { MilkioConfig } from ".."
|
|
3
|
+
import ejs from "ejs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
6
|
+
import { cwd, exit } from "node:process";
|
|
7
|
+
import { unlink, writeFile } from "node:fs/promises";
|
|
8
|
+
import { camel, hyphen } from "@poech/camel-hump-under";
|
|
9
|
+
import { $, Glob } from "bun";
|
|
10
|
+
import type { MilkioConfig } from "..";
|
|
11
11
|
|
|
12
12
|
export default async () => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
13
|
+
// Delete the files generated in the past and regenerate them
|
|
14
|
+
try {
|
|
15
|
+
await unlink(join(cwd(), "generated", "api-schema.ts"));
|
|
16
|
+
} catch (error) {} // Maybe the file does not exist
|
|
17
|
+
|
|
18
|
+
// Make sure that the existing directories are all present
|
|
19
|
+
existsSync(join("generated")) || mkdirSync(join("generated"));
|
|
20
|
+
existsSync(join("generated", "raw")) || mkdirSync(join("generated", "raw"));
|
|
21
|
+
existsSync(join("generated", "raw", "apps")) || mkdirSync(join("generated", "raw", "apps"));
|
|
22
|
+
|
|
23
|
+
if (!existsSync(join("generated", "README.md"))) {
|
|
24
|
+
await writeFile(join("generated", "README.md"), "⚠️ All files in this directory are generated by milkio. Please do not modify the content, otherwise your modifications will be overwritten in the next generation.");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const utils = {
|
|
28
|
+
camel: (str: string) => camel(str).replaceAll("-", "").replaceAll("_", ""),
|
|
29
|
+
hyphen: (str: string) => hyphen(str).replaceAll("_", ""),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Write a basic framework to ensure that there are no errors when reading later
|
|
33
|
+
const apiSchemaSkeleton = `
|
|
34
34
|
export default {
|
|
35
35
|
apiValidator: {},
|
|
36
36
|
apiMethodsSchema: {},
|
|
37
37
|
apiMethodsTypeSchema: {},
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
importPath = importPath + "src/apps"
|
|
91
|
-
const template = `
|
|
39
|
+
`;
|
|
40
|
+
await writeFile(join(cwd(), "generated", "api-schema.ts"), ejs.render(apiSchemaSkeleton, { utils }));
|
|
41
|
+
|
|
42
|
+
// Generate api-schema.ts file through templates
|
|
43
|
+
const templateVars = {
|
|
44
|
+
utils,
|
|
45
|
+
apiPaths: [] as Array<string>,
|
|
46
|
+
apiTestPaths: [] as Array<string>,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const glob = new Glob("**/*.ts");
|
|
50
|
+
const appFiles = await Array.fromAsync(glob.scan({ cwd: join(cwd(), "src", "apps") }));
|
|
51
|
+
|
|
52
|
+
console.time(`File Stage`);
|
|
53
|
+
|
|
54
|
+
for (const path of appFiles) {
|
|
55
|
+
if (!path.endsWith(".ts")) continue;
|
|
56
|
+
const module = await import(/* @vite-ignore */ join(cwd(), "src", "apps", path));
|
|
57
|
+
|
|
58
|
+
if (module?.api?.isApi === true) {
|
|
59
|
+
// Exclude disallowed characters
|
|
60
|
+
if (path.includes("_")) {
|
|
61
|
+
console.error(`\n\nPath: "${path.slice(0, -3)}"`);
|
|
62
|
+
console.error(`Do not use "_" in the path. If you want to add a separator between words, please use "-".\n`);
|
|
63
|
+
exit(1);
|
|
64
|
+
}
|
|
65
|
+
if (!/^[a-z0-9/$/-]+$/.test(path.slice(0, -3))) {
|
|
66
|
+
console.error(`\n\nPath: "${path.slice(0, -3)}"`);
|
|
67
|
+
console.error(`The path can only contain lowercase letters, numbers, and "-".\n`);
|
|
68
|
+
exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
templateVars.apiPaths.push(path);
|
|
72
|
+
|
|
73
|
+
if (module?.test?.isApiTest === true) {
|
|
74
|
+
templateVars.apiTestPaths.push(path);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// typia
|
|
78
|
+
const filePath = join(cwd(), "generated", "raw", "apps", path);
|
|
79
|
+
const dirPath = join(cwd(), "generated", "raw", "apps", path).split("/").slice(0, -1).join("/");
|
|
80
|
+
if (!existsSync(dirPath)) {
|
|
81
|
+
mkdirSync(dirPath, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
let importPath = "../../../";
|
|
84
|
+
|
|
85
|
+
for (let i = 0; i < path.split("/").length - 1; i++) {
|
|
86
|
+
importPath = `${importPath}../`;
|
|
87
|
+
}
|
|
88
|
+
importPath = `${importPath}src/apps`;
|
|
89
|
+
const template = `
|
|
92
90
|
import typia from "typia";
|
|
93
91
|
import { _validate, type ExecuteResultFail, type ExecuteResultSuccess } from "milkio";
|
|
94
92
|
import { type TSONEncode } from "@southern-aurora/tson";
|
|
@@ -99,17 +97,17 @@ export const validateParams = async (params: any) => typia.misc.validatePrune<Pa
|
|
|
99
97
|
type ResultsT = Awaited<ReturnType<typeof <%= utils.camel(path.replaceAll('/', '$').slice(0, -${3})) %>['api']['action']>>;
|
|
100
98
|
export const validateResults = async (results: any) => { _validate(typia.validate<TSONEncode<ExecuteResultSuccess<ResultsT> | ExecuteResultFail>>(results)); return typia.json.stringify<TSONEncode<ExecuteResultSuccess<ResultsT> | ExecuteResultFail>>(results); };
|
|
101
99
|
export const randParams = async () => typia.random<ParamsT>();
|
|
102
|
-
`.trim()
|
|
103
|
-
|
|
100
|
+
`.trim();
|
|
101
|
+
// export const paramsSchema = typia.json.application<[{ data: ParamsT }], "swagger">();
|
|
104
102
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
await writeFile(filePath, ejs.render(template, { ...templateVars, path }));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
108
106
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
107
|
+
await writeFile(
|
|
108
|
+
join(cwd(), "generated", "api-schema.ts"),
|
|
109
|
+
ejs.render(
|
|
110
|
+
`
|
|
113
111
|
/**
|
|
114
112
|
* ⚠️ This file is generated and modifications will be overwritten
|
|
115
113
|
*/
|
|
@@ -135,12 +133,12 @@ export default {
|
|
|
135
133
|
},
|
|
136
134
|
}
|
|
137
135
|
`.trim(),
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
136
|
+
templateVars,
|
|
137
|
+
),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
// api
|
|
141
|
+
const apiValidatorTemplate = `/**
|
|
144
142
|
* ⚠️This file is generated and modifications will be overwritten
|
|
145
143
|
*/
|
|
146
144
|
|
|
@@ -151,26 +149,26 @@ export default {
|
|
|
151
149
|
<% } %>
|
|
152
150
|
},
|
|
153
151
|
}
|
|
154
|
-
`.trim()
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
152
|
+
`.trim();
|
|
153
|
+
await writeFile(join(cwd(), "generated", "raw", "api-validator.ts"), ejs.render(apiValidatorTemplate, templateVars));
|
|
154
|
+
|
|
155
|
+
console.timeEnd(`File Stage`);
|
|
156
|
+
console.log(``);
|
|
157
|
+
|
|
158
|
+
// typia
|
|
159
|
+
console.time(`Typia Stage`);
|
|
160
|
+
await $`bun run ./node_modules/typia/lib/executable/typia.js generate --input generated/raw --output generated/products --project tsconfig.json`;
|
|
161
|
+
console.timeEnd(`Typia Stage`);
|
|
162
|
+
console.log(``);
|
|
163
|
+
|
|
164
|
+
if (!existsSync(join(cwd(), "milkio.toml"))) return;
|
|
165
|
+
const milkioConfig = (await import(join(cwd(), "milkio.toml"))).default as MilkioConfig;
|
|
166
|
+
if (!milkioConfig?.generate?.significant) return;
|
|
167
|
+
let i = 0;
|
|
168
|
+
for (const command of milkioConfig.generate.significant) {
|
|
169
|
+
++i;
|
|
170
|
+
console.time(`Significant Stage (LINE ${i})`);
|
|
171
|
+
await $`${{ raw: command }}`;
|
|
172
|
+
console.timeEnd(`Significant Stage (LINE ${i})`);
|
|
173
|
+
}
|
|
174
|
+
};
|
package/utils/create-ulid.ts
CHANGED
package/utils/env-to-boolean.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export function envToBoolean(value: string | number | undefined, defaultValue: boolean) {
|
|
2
|
-
|
|
2
|
+
if (value === "true") return true;
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
if (value === "false") return false;
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
if (value === "") return false;
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
if (undefined === value) return defaultValue;
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
return Boolean(value);
|
|
11
11
|
}
|
package/utils/env-to-number.ts
CHANGED
package/utils/env-to-string.ts
CHANGED
package/utils/exec.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { env, type SpawnOptions } from "bun"
|
|
1
|
+
import { env, type SpawnOptions } from "bun";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* This is a legacy wrapper that was written before $ Shell was created.
|
|
@@ -6,22 +6,22 @@ import { env, type SpawnOptions } from "bun"
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
export const exec = async (cwd: string, command: Array<string>, options: Partial<SpawnOptions.OptionsObject> = {}) => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
if (!("cwd" in options)) options.cwd = cwd;
|
|
11
|
+
if (!("stdin" in options)) options.stdin = "inherit";
|
|
12
|
+
if (!("stdout" in options)) options.stdout = "inherit";
|
|
13
|
+
if (!("env" in options)) options.env = { ...env };
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
options.onExit = (proc, exitCode, signalCode, error) => {
|
|
16
|
+
// eslint-disable-next-line prefer-promise-reject-errors
|
|
17
|
+
if (exitCode !== 0) reject({ proc, exitCode, signalCode, error });
|
|
18
|
+
else resolve({ proc, exitCode, signalCode, error });
|
|
19
|
+
};
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
21
|
+
try {
|
|
22
|
+
Bun.spawn(command, options);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
reject(error);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
};
|
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import { failCode } from "../../../src/fail-code"
|
|
2
|
-
import { useLogger, type ExecuteId, type ExecuteResult } from ".."
|
|
3
|
-
import { configMilkio } from "../../../src/config/milkio"
|
|
1
|
+
import { failCode } from "../../../src/fail-code";
|
|
2
|
+
import { useLogger, type ExecuteId, type ExecuteResult } from "..";
|
|
3
|
+
import { configMilkio } from "../../../src/config/milkio";
|
|
4
4
|
|
|
5
5
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
6
|
export function hanldeCatchError(error: any, executeId: ExecuteId): ExecuteResult<any> {
|
|
7
|
-
|
|
7
|
+
const logger = useLogger(executeId);
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
if (configMilkio.debug === true) {
|
|
10
|
+
logger.error(`\nError Data: ${JSON.stringify(error)}`);
|
|
11
|
+
if (error.stack) logger.error("\nError Stack: ", error.stack);
|
|
12
|
+
else logger.error("\nError Stack: ", error);
|
|
13
|
+
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
if (error.name !== "MilkioReject") {
|
|
16
|
+
if (configMilkio.debug === true) {
|
|
17
|
+
// If it is not MilkioReject, it is considered an internal server error that should not be exposed
|
|
18
|
+
logger.error(`FailCode: INTERNAL_SERVER_ERROR`);
|
|
19
|
+
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (configMilkio.debug === true) {
|
|
32
|
-
logger.error(`FailCode: ${error.code}`)
|
|
33
|
-
}
|
|
21
|
+
return {
|
|
22
|
+
executeId,
|
|
23
|
+
success: false,
|
|
24
|
+
fail: {
|
|
25
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
26
|
+
message: failCode.INTERNAL_SERVER_ERROR(),
|
|
27
|
+
data: undefined,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
34
31
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
32
|
+
if (configMilkio.debug === true) {
|
|
33
|
+
logger.error(`FailCode: ${error.code}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
executeId,
|
|
38
|
+
success: false,
|
|
39
|
+
fail: {
|
|
40
|
+
code: error.code,
|
|
41
|
+
message: error.message,
|
|
42
|
+
data: error.data,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
45
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export const headerToPlainObject = (headers: Headers) => {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
2
|
+
if (headers.toJSON) return headers.toJSON();
|
|
3
|
+
const plainHeaders: Record<string, string> = {};
|
|
4
|
+
headers.forEach((value, key) => {
|
|
5
|
+
plainHeaders[key] = value;
|
|
6
|
+
});
|
|
7
|
+
return headers;
|
|
8
|
+
};
|
package/utils/remove-dir.ts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { existsSync, lstatSync, readdirSync, rmdirSync, unlinkSync } from "node:fs"
|
|
2
|
-
import path from "node:path"
|
|
1
|
+
import { existsSync, lstatSync, readdirSync, rmdirSync, unlinkSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
export function removeDir(pathstr: string, skips: Array<string> = []) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
5
|
+
if (!existsSync(pathstr)) return;
|
|
6
|
+
const files = readdirSync(pathstr);
|
|
7
|
+
files.forEach((file) => {
|
|
8
|
+
const dirname = path.resolve(pathstr, file);
|
|
9
|
+
const stats = lstatSync(dirname);
|
|
10
|
+
for (const skip of skips) {
|
|
11
|
+
if (dirname.startsWith(skip)) return;
|
|
12
|
+
}
|
|
13
|
+
if (stats.isDirectory()) {
|
|
14
|
+
removeDir(dirname);
|
|
15
|
+
} else {
|
|
16
|
+
unlinkSync(dirname);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
try {
|
|
20
|
+
rmdirSync(pathstr);
|
|
21
|
+
} catch (error) {}
|
|
22
22
|
}
|
package/utils/tson.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { TSON as _TSON } from "@southern-aurora/tson"
|
|
1
|
+
import { TSON as _TSON } from "@southern-aurora/tson";
|
|
2
2
|
|
|
3
|
-
export const TSON = _TSON
|
|
3
|
+
export const TSON = _TSON;
|
package/.co.toml
DELETED