milkio 0.0.1
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/.co.toml +2 -0
- package/LICENSE +21 -0
- package/README.md +15 -0
- package/c.ts +83 -0
- package/defines/define-api-test-handler.ts +70 -0
- package/defines/define-api-test.ts +14 -0
- package/defines/define-api.ts +15 -0
- package/defines/define-http-handler.ts +203 -0
- package/defines/define-middleware.ts +9 -0
- package/defines/define-use.ts +13 -0
- package/index.ts +33 -0
- package/kernel/config.ts +15 -0
- package/kernel/context.ts +24 -0
- package/kernel/fail.ts +22 -0
- package/kernel/logger.ts +113 -0
- package/kernel/loongbao.ts +269 -0
- package/kernel/meta.ts +9 -0
- package/kernel/middleware.ts +50 -0
- package/kernel/runtime.ts +16 -0
- package/kernel/validate.ts +13 -0
- package/package.json +24 -0
- package/scripts/build-cookbook.ts +233 -0
- package/scripts/build-dto.ts +65 -0
- package/scripts/generate/generate-app-partial.ts +74 -0
- package/scripts/generate/generate-app.ts +153 -0
- package/scripts/generate/generate-database.ts +23 -0
- package/scripts/generate-database.ts +23 -0
- package/scripts/generate-partial.ts +15 -0
- package/scripts/generate.ts +23 -0
- package/templates/api.ts +49 -0
- package/tsconfig.json +33 -0
- package/types.ts +42 -0
- package/utils/create-template.ts +32 -0
- package/utils/create-ulid.ts +5 -0
- package/utils/env-to-boolean.ts +11 -0
- package/utils/env-to-number.ts +5 -0
- package/utils/env-to-string.ts +5 -0
- package/utils/exec.ts +27 -0
- package/utils/handle-catch-error.ts +37 -0
- package/utils/remove-dir.ts +22 -0
- package/utils/tson.ts +3 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
|
|
3
|
+
import ejs from "ejs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
6
|
+
import { cwd, env, exit } from "node:process";
|
|
7
|
+
import { writeFile, readFile } from "node:fs/promises";
|
|
8
|
+
import { exec as nodeExec } from "node:child_process";
|
|
9
|
+
import { camel, hump, hyphen } from "@poech/camel-hump-under";
|
|
10
|
+
|
|
11
|
+
const utils = {
|
|
12
|
+
camel: (str: string) => camel(str).replaceAll("-", "").replaceAll("_", ""),
|
|
13
|
+
hump: (str: string) => hump(str).replaceAll("-", "").replaceAll("_", ""),
|
|
14
|
+
hyphen: (str: string) => hyphen(str).replaceAll("_", "")
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export async function generateAppPartial(path?: string) {
|
|
18
|
+
const partialPath = path ?? env.GENERATE_PARTIAL_PATH!;
|
|
19
|
+
const partialDir = partialPath.split("/").slice(0, -1).join("/");
|
|
20
|
+
// Generate api-schema.ts file through templates
|
|
21
|
+
const templateVars = {
|
|
22
|
+
utils
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
if (!partialPath.endsWith(".ts")) return;
|
|
26
|
+
const module = await import(/* @vite-ignore */ `../../../../src/apps/${partialPath}`);
|
|
27
|
+
if (module?.api?.isApi === true) {
|
|
28
|
+
// Exclude disallowed characters
|
|
29
|
+
if (partialPath.includes("_")) {
|
|
30
|
+
console.error(`\n\nPath: ` + partialPath);
|
|
31
|
+
console.error(`Do not use "_" in the path. If you want to add a separator between words, please use "-".\n`);
|
|
32
|
+
exit(1);
|
|
33
|
+
}
|
|
34
|
+
if (/^[a-z0-9/-]+$/.test(partialPath)) {
|
|
35
|
+
console.error(`\n\nPath: ` + partialPath);
|
|
36
|
+
console.error(`The path can only contain lowercase letters, numbers, and "-".\n`);
|
|
37
|
+
exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// typia
|
|
42
|
+
const filePathTmp = join(cwd(), "generate", "raw-tmp", "apps", partialPath);
|
|
43
|
+
const dirPathTmp = join(cwd(), "generate", "raw-tmp", "apps", partialPath).split("/").slice(0, -1).join("/");
|
|
44
|
+
const filePath = join(cwd(), "generate", "raw", "apps", partialPath);
|
|
45
|
+
if (!existsSync(dirPathTmp)) {
|
|
46
|
+
mkdirSync(dirPathTmp, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
let importPath = "../../../";
|
|
49
|
+
|
|
50
|
+
for (let i = 0; i < partialPath.split("/").length - 1; i++) {
|
|
51
|
+
importPath = importPath + "../";
|
|
52
|
+
}
|
|
53
|
+
importPath = importPath + "src/apps";
|
|
54
|
+
const template = `
|
|
55
|
+
import typia from "typia";
|
|
56
|
+
import { _validate, type ExecuteResultSuccess } from "loongbao";
|
|
57
|
+
import { type TSONEncode } from "@southern-aurora/tson";
|
|
58
|
+
import type * as <%= utils.camel(path.slice(0, -3).replaceAll('/', '$')) %> from '${importPath}/<%= path.slice(0, -3) %>';
|
|
59
|
+
|
|
60
|
+
type ParamsT = Parameters<typeof <%= utils.camel(path.replaceAll('/', '$').slice(0, -${3})) %>['api']['action']>[0];
|
|
61
|
+
export const params = async (params: any) => typia.misc.validatePrune<ParamsT>(params);
|
|
62
|
+
type ResultsT = Awaited<ReturnType<typeof <%= utils.camel(path.replaceAll('/', '$').slice(0, -${3})) %>['api']['action']>>;
|
|
63
|
+
export const results = async (results: any) => { _validate(typia.validate<TSONEncode<ExecuteResultSuccess<ResultsT>>>(results)); return typia.json.stringify<TSONEncode<ExecuteResultSuccess<ResultsT>>>(results); };
|
|
64
|
+
`.trim();
|
|
65
|
+
|
|
66
|
+
await writeFile(filePathTmp, ejs.render(template, { ...templateVars, path: partialPath }));
|
|
67
|
+
|
|
68
|
+
await new Promise((resolve) =>
|
|
69
|
+
nodeExec(`bun run ./node_modules/typia/lib/executable/typia.js generate --input generate/raw-tmp/apps/${partialDir} --output generate/products-tmp/apps/${partialDir} --project tsconfig.json`, (e) => {
|
|
70
|
+
resolve(e);
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
await Promise.all([writeFile(filePath, ejs.render(template, { ...templateVars, path: partialPath })), writeFile(join(cwd(), "generate", "products", "apps", partialPath), (await readFile(join(cwd(), "generate", "products-tmp", "apps", partialPath))).toString())]);
|
|
74
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
|
|
3
|
+
import ejs from "ejs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import walkSync from "walk-sync";
|
|
6
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
7
|
+
import { cwd, exit } from "node:process";
|
|
8
|
+
import { unlink, writeFile } from "node:fs/promises";
|
|
9
|
+
import { exec as nodeExec } from "node:child_process";
|
|
10
|
+
import { camel, hyphen } from "@poech/camel-hump-under";
|
|
11
|
+
|
|
12
|
+
const utils = {
|
|
13
|
+
camel: (str: string) => camel(str).replaceAll("-", "").replaceAll("_", ""),
|
|
14
|
+
hyphen: (str: string) => hyphen(str).replaceAll("_", "")
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export async function generateApp() {
|
|
18
|
+
// Delete the files generated in the past and regenerate them
|
|
19
|
+
try {
|
|
20
|
+
await unlink(join(cwd(), "generate", "api-schema.ts"));
|
|
21
|
+
} catch (error) {} // Maybe the file does not exist
|
|
22
|
+
|
|
23
|
+
if (!existsSync(join("generate", "README.md"))) {
|
|
24
|
+
await writeFile(join("generate", "README.md"), "⚠️ All files in this directory are generated by Loongbao. Please do not modify the content, otherwise your modifications will be overwritten in the next generation.");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Write a basic framework to ensure that there are no errors when reading later
|
|
28
|
+
const apiSchemaSkeleton = `
|
|
29
|
+
export default {
|
|
30
|
+
apiValidator: {},
|
|
31
|
+
apiMethodsSchema: {},
|
|
32
|
+
apiMethodsTypeSchema: {},
|
|
33
|
+
}
|
|
34
|
+
`;
|
|
35
|
+
await writeFile(join(cwd(), "generate", "api-schema.ts"), ejs.render(apiSchemaSkeleton, { utils }));
|
|
36
|
+
|
|
37
|
+
// Generate api-schema.ts file through templates
|
|
38
|
+
const templateVars = {
|
|
39
|
+
utils,
|
|
40
|
+
apiPaths: [] as Array<string>,
|
|
41
|
+
apiTestPaths: [] as Array<string>
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const appFiles = walkSync(join(cwd(), "src", "apps"), {
|
|
45
|
+
directories: false
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
for (const path of appFiles) {
|
|
49
|
+
if (!path.endsWith(".ts")) continue;
|
|
50
|
+
const module = await import(/* @vite-ignore */ `../../../../src/apps/${path}`);
|
|
51
|
+
if (module?.api?.isApi === true) {
|
|
52
|
+
// Exclude disallowed characters
|
|
53
|
+
if (path.includes("_")) {
|
|
54
|
+
console.error(`\n\nPath: ` + `"${path}"`);
|
|
55
|
+
console.error(`Do not use "_" in the path. If you want to add a separator between words, please use "-".\n`);
|
|
56
|
+
exit(1);
|
|
57
|
+
}
|
|
58
|
+
if (!/^[a-z0-9/-]+$/.test(path.slice(0, -3))) {
|
|
59
|
+
console.error(`\n\nPath: ` + `"${path}"`);
|
|
60
|
+
console.error(`The path can only contain lowercase letters, numbers, and "-".\n`);
|
|
61
|
+
exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
templateVars.apiPaths.push(path);
|
|
65
|
+
|
|
66
|
+
if (module?.test?.isApiTest === true) {
|
|
67
|
+
templateVars.apiTestPaths.push(path);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// typia
|
|
71
|
+
const filePath = join(cwd(), "generate", "raw", "apps", path);
|
|
72
|
+
const dirPath = join(cwd(), "generate", "raw", "apps", path).split("/").slice(0, -1).join("/");
|
|
73
|
+
if (!existsSync(dirPath)) {
|
|
74
|
+
mkdirSync(dirPath, { recursive: true });
|
|
75
|
+
}
|
|
76
|
+
let importPath = "../../../";
|
|
77
|
+
|
|
78
|
+
for (let i = 0; i < path.split("/").length - 1; i++) {
|
|
79
|
+
importPath = importPath + "../";
|
|
80
|
+
}
|
|
81
|
+
importPath = importPath + "src/apps";
|
|
82
|
+
const template = `
|
|
83
|
+
import typia from "typia";
|
|
84
|
+
import { _validate, type ExecuteResultSuccess } from "loongbao";
|
|
85
|
+
import { type TSONEncode } from "@southern-aurora/tson";
|
|
86
|
+
import type * as <%= utils.camel(path.slice(0, -3).replaceAll('/', '$')) %> from '${importPath}/<%= path.slice(0, -3) %>';
|
|
87
|
+
|
|
88
|
+
type ParamsT = Parameters<typeof <%= utils.camel(path.replaceAll('/', '$').slice(0, -${3})) %>['api']['action']>[0];
|
|
89
|
+
export const params = async (params: any) => typia.misc.validatePrune<ParamsT>(params);
|
|
90
|
+
type ResultsT = Awaited<ReturnType<typeof <%= utils.camel(path.replaceAll('/', '$').slice(0, -${3})) %>['api']['action']>>;
|
|
91
|
+
export const results = async (results: any) => { _validate(typia.validate<TSONEncode<ExecuteResultSuccess<ResultsT>>>(results)); return typia.json.stringify<TSONEncode<ExecuteResultSuccess<ResultsT>>>(results); };
|
|
92
|
+
|
|
93
|
+
`.trim();
|
|
94
|
+
// export const paramsSchema = typia.json.application<[{ data: ParamsT }], "swagger">();
|
|
95
|
+
|
|
96
|
+
await writeFile(filePath, ejs.render(template, { ...templateVars, path }));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await writeFile(
|
|
101
|
+
join(cwd(), "generate", "api-schema.ts"),
|
|
102
|
+
ejs.render(
|
|
103
|
+
`
|
|
104
|
+
/**
|
|
105
|
+
* ⚠️ This file is generated and modifications will be overwritten
|
|
106
|
+
*/
|
|
107
|
+
|
|
108
|
+
// api
|
|
109
|
+
<% for (const path of ${"apiPaths"}) { %>import type * as <%= utils.camel(path.slice(0, -3).replaceAll('/', '$')) %> from '${"../src/apps"}/<%= path.slice(0, -3) %>'
|
|
110
|
+
<% } %>
|
|
111
|
+
import _apiValidator from './products/api-validator.ts'
|
|
112
|
+
|
|
113
|
+
export default {
|
|
114
|
+
apiValidator: _apiValidator,
|
|
115
|
+
${"apiMethodsSchema"}: {
|
|
116
|
+
<% for (const path of apiPaths) { %>'<%= utils.hyphen(path.slice(0, -${3})) %>': () => ({ module: import('../src/apps/<%= path.slice(0, -${3}) %>') }),
|
|
117
|
+
<% } %>
|
|
118
|
+
},
|
|
119
|
+
${"apiMethodsTypeSchema"}: {
|
|
120
|
+
<% for (const path of apiPaths) { %>'<%= utils.hyphen(path.slice(0, -${3})) %>': undefined as unknown as typeof <%= utils.camel(path.slice(0, -${3}).replaceAll('/', '$')) %>,
|
|
121
|
+
<% } %>
|
|
122
|
+
},
|
|
123
|
+
${"apiTestsSchema"}: {
|
|
124
|
+
<% for (const path of apiTestPaths) { %>'<%= utils.hyphen(path.slice(0, -${3})) %>': () => ({ module: import('../src/apps/<%= path.slice(0, -${3}) %>') }),
|
|
125
|
+
<% } %>
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
`.trim(),
|
|
129
|
+
templateVars
|
|
130
|
+
)
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// api
|
|
134
|
+
const apiValidatorTemplate = `/**
|
|
135
|
+
* ⚠️This file is generated and modifications will be overwritten
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
export default {
|
|
139
|
+
generatedAt: ${new Date().getTime()},
|
|
140
|
+
${"validate"}: {
|
|
141
|
+
<% for (const path of apiPaths) { %>'<%= utils.hyphen(path.slice(0, -${3})) %>': () => import('./apps/<%= utils.hyphen(path) %>'),
|
|
142
|
+
<% } %>
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
`.trim();
|
|
146
|
+
await writeFile(join(cwd(), "generate", "raw", "api-validator.ts"), ejs.render(apiValidatorTemplate, templateVars));
|
|
147
|
+
|
|
148
|
+
await new Promise((resolve) =>
|
|
149
|
+
nodeExec("bun run ./node_modules/typia/lib/executable/typia.js generate --input generate/raw --output generate/products --project tsconfig.json", (e) => {
|
|
150
|
+
resolve(e);
|
|
151
|
+
})
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
|
|
3
|
+
import ejs from "ejs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import walkSync from "walk-sync";
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
import { cwd } from "node:process";
|
|
8
|
+
import { writeFile } from "node:fs/promises";
|
|
9
|
+
|
|
10
|
+
export async function generateDatabase() {
|
|
11
|
+
if (existsSync(join(cwd(), "src", "databases"))) {
|
|
12
|
+
if (!existsSync(join("generate", "database-schema.ts"))) {
|
|
13
|
+
await writeFile(join("generate", "database-schema.ts"), ``);
|
|
14
|
+
}
|
|
15
|
+
const filePath = join(cwd(), "generate", "database-schema.ts");
|
|
16
|
+
const databaseFiles = walkSync(join(cwd(), "src", "databases"), {
|
|
17
|
+
directories: false
|
|
18
|
+
}).filter((file) => file.endsWith(".ts"));
|
|
19
|
+
const template = `<% for (const path of ${"databaseFiles"}) { %>export * from '${"../src/databases"}/<%= path.slice(0, -3) %>'
|
|
20
|
+
<% } %>`;
|
|
21
|
+
await writeFile(filePath, ejs.render(template, { databaseFiles }));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
5
|
+
import { exit } from "node:process";
|
|
6
|
+
import { generateDatabase } from "./generate/generate-database";
|
|
7
|
+
|
|
8
|
+
export async function generate() {
|
|
9
|
+
// Make sure that the existing directories are all present
|
|
10
|
+
existsSync(join("generate")) || mkdirSync(join("generate"));
|
|
11
|
+
existsSync(join("generate", "raw")) || mkdirSync(join("generate", "raw"));
|
|
12
|
+
existsSync(join("generate", "raw", "apps")) || mkdirSync(join("generate", "raw", "apps"));
|
|
13
|
+
|
|
14
|
+
await generateDatabase();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
console.log("Loongbao Database Generating..");
|
|
18
|
+
|
|
19
|
+
await generate();
|
|
20
|
+
|
|
21
|
+
console.log("\n✅ Loongbao Database Generated!\n");
|
|
22
|
+
|
|
23
|
+
exit(0);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument */
|
|
2
|
+
|
|
3
|
+
import { argv, exit } from "node:process";
|
|
4
|
+
import { generateApp } from "./generate/generate-app";
|
|
5
|
+
console.log("Loongbao Quick Generating..");
|
|
6
|
+
|
|
7
|
+
export async function generatePartial(path?: string) {
|
|
8
|
+
await generateApp();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
await generatePartial(...(argv.slice(3) as any));
|
|
12
|
+
|
|
13
|
+
console.log("\n✅ Loongbao Generated!");
|
|
14
|
+
|
|
15
|
+
exit(0);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
5
|
+
import { exit } from "node:process";
|
|
6
|
+
import { generateApp } from "./generate/generate-app";
|
|
7
|
+
|
|
8
|
+
export async function generate() {
|
|
9
|
+
// Make sure that the existing directories are all present
|
|
10
|
+
existsSync(join("generate")) || mkdirSync(join("generate"));
|
|
11
|
+
existsSync(join("generate", "raw")) || mkdirSync(join("generate", "raw"));
|
|
12
|
+
existsSync(join("generate", "raw", "apps")) || mkdirSync(join("generate", "raw", "apps"));
|
|
13
|
+
|
|
14
|
+
await generateApp();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
console.log("Loongbao Generating..");
|
|
18
|
+
|
|
19
|
+
await generate();
|
|
20
|
+
|
|
21
|
+
console.log("\n✅ Loongbao Generated!");
|
|
22
|
+
|
|
23
|
+
exit(0);
|
package/templates/api.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createTemplate } from "../utils/create-template";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
|
|
4
|
+
await createTemplate(async (tools) => {
|
|
5
|
+
return {
|
|
6
|
+
path: join(tools.directory, `${tools.hyphen(tools.name)}.ts`),
|
|
7
|
+
content: `
|
|
8
|
+
import { defineApi, defineApiTest } from "loongbao";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* ${tools.hyphen(tools.name)}
|
|
12
|
+
*/
|
|
13
|
+
export const api = defineApi({
|
|
14
|
+
meta: {
|
|
15
|
+
//
|
|
16
|
+
},
|
|
17
|
+
async action(
|
|
18
|
+
params: {
|
|
19
|
+
//
|
|
20
|
+
},
|
|
21
|
+
context
|
|
22
|
+
) {
|
|
23
|
+
const message = \`hello world!\`;
|
|
24
|
+
|
|
25
|
+
// ..
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
say: message
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const test = defineApiTest(api, [
|
|
34
|
+
{
|
|
35
|
+
name: "Basic",
|
|
36
|
+
handler: async (test) => {
|
|
37
|
+
const result = await test.execute({
|
|
38
|
+
//
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// ..
|
|
42
|
+
|
|
43
|
+
if (!result.success) return test.reject(\`The result was not success\`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
]);
|
|
47
|
+
`.trim()
|
|
48
|
+
};
|
|
49
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"rootDir": "./../../../",
|
|
4
|
+
"lib": [
|
|
5
|
+
"ESNext"
|
|
6
|
+
],
|
|
7
|
+
"module": "esnext",
|
|
8
|
+
"target": "esnext",
|
|
9
|
+
"moduleResolution": "bundler",
|
|
10
|
+
"moduleDetection": "force",
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"composite": true,
|
|
14
|
+
"strict": true,
|
|
15
|
+
"downlevelIteration": true,
|
|
16
|
+
"skipLibCheck": true,
|
|
17
|
+
"jsx": "react-jsx",
|
|
18
|
+
"allowSyntheticDefaultImports": true,
|
|
19
|
+
"forceConsistentCasingInFileNames": true,
|
|
20
|
+
"allowJs": true,
|
|
21
|
+
"types": [
|
|
22
|
+
"bun-types"
|
|
23
|
+
],
|
|
24
|
+
"paths": {
|
|
25
|
+
"../../*": [
|
|
26
|
+
"../../*"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"include": [
|
|
31
|
+
"../../../**/*"
|
|
32
|
+
]
|
|
33
|
+
}
|
package/types.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any */
|
|
2
|
+
|
|
3
|
+
import { type createLoongbaoApp } from ".";
|
|
4
|
+
import type { failCode } from "../../src/fail-code";
|
|
5
|
+
|
|
6
|
+
export type LoongbaoApp = Awaited<ReturnType<typeof createLoongbaoApp>>;
|
|
7
|
+
|
|
8
|
+
export type ExecuteId = string | "global";
|
|
9
|
+
|
|
10
|
+
export type FailEnumerates = typeof failCode;
|
|
11
|
+
|
|
12
|
+
export type HTTPRequest = Request;
|
|
13
|
+
|
|
14
|
+
export type HTTPResponse = Override<ResponseInit & { body: string | null | undefined }, { headers: NonNullable<ResponseInit["headers"]> }>;
|
|
15
|
+
|
|
16
|
+
export type Fail<FailCode extends keyof FailEnumerates> = {
|
|
17
|
+
code: FailCode;
|
|
18
|
+
message: string;
|
|
19
|
+
data: Parameters<FailEnumerates[FailCode]>[0];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type LoongbaoMeta = {
|
|
23
|
+
//
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type Cookbook = Record<string, CookbookItem>;
|
|
27
|
+
|
|
28
|
+
export type CookbookItem = {
|
|
29
|
+
title?: string;
|
|
30
|
+
desc?: string;
|
|
31
|
+
params: string;
|
|
32
|
+
cases: Array<{
|
|
33
|
+
name: string;
|
|
34
|
+
handler: string;
|
|
35
|
+
}>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type DatabaseType<Table extends { findFirst: () => unknown }, Override = {}> = Mixin<NonNullable<Awaited<ReturnType<Table["findFirst"]>>>, Override>;
|
|
39
|
+
|
|
40
|
+
export type Override<P, S> = Omit<P, keyof S> & S;
|
|
41
|
+
|
|
42
|
+
export type Mixin<T, U> = U & Omit<T, keyof U>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { argv } from "bun";
|
|
2
|
+
import { camel, hump, hyphen } from "@poech/camel-hump-under";
|
|
3
|
+
|
|
4
|
+
export type CreateTemplateTools = {
|
|
5
|
+
name: string;
|
|
6
|
+
directory: string;
|
|
7
|
+
camel: (str: string) => string;
|
|
8
|
+
hump: (str: string) => string;
|
|
9
|
+
hyphen: (str: string) => string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type CreateTemplateFn = (tools: CreateTemplateTools) =>
|
|
13
|
+
| {
|
|
14
|
+
path: string;
|
|
15
|
+
content: string;
|
|
16
|
+
}
|
|
17
|
+
| Promise<{
|
|
18
|
+
path: string;
|
|
19
|
+
content: string;
|
|
20
|
+
}>;
|
|
21
|
+
|
|
22
|
+
export async function createTemplate(fn: CreateTemplateFn) {
|
|
23
|
+
const tools = {
|
|
24
|
+
name: argv[2],
|
|
25
|
+
directory: argv[3],
|
|
26
|
+
camel: (str: string) => camel(str).replaceAll("-", "").replaceAll("_", ""),
|
|
27
|
+
hump: (str: string) => hump(str).replaceAll("-", "").replaceAll("_", ""),
|
|
28
|
+
hyphen: (str: string) => hyphen(str).replaceAll("_", "")
|
|
29
|
+
};
|
|
30
|
+
const file = await fn(tools);
|
|
31
|
+
await Bun.write(file.path, file.content);
|
|
32
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function envToBoolean(value: string | number | undefined, defaultValue: boolean) {
|
|
2
|
+
if (value === "true") return true;
|
|
3
|
+
|
|
4
|
+
if (value === "false") return false;
|
|
5
|
+
|
|
6
|
+
if (value === "") return false;
|
|
7
|
+
|
|
8
|
+
if (undefined === value) return defaultValue;
|
|
9
|
+
|
|
10
|
+
return Boolean(value);
|
|
11
|
+
}
|
package/utils/exec.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { env, type SpawnOptions } from "bun";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This is a legacy wrapper that was written before $ Shell was created.
|
|
5
|
+
* All relevant code should be replaced with $ Shell in the future.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const exec = async (cwd: string, command: Array<string>, options: Partial<SpawnOptions.OptionsObject> = {}) => {
|
|
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
|
+
|
|
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
|
+
|
|
21
|
+
try {
|
|
22
|
+
Bun.spawn(command, options);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
reject(error);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { failCode } from "../../../src/fail-code";
|
|
2
|
+
import { useLogger, type ExecuteId, type ExecuteResult } from "..";
|
|
3
|
+
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
|
+
export function hanldeCatchError(error: any, executeId: ExecuteId): ExecuteResult<any> {
|
|
6
|
+
const logger = useLogger(executeId);
|
|
7
|
+
|
|
8
|
+
logger.error("\nError Data: " + JSON.stringify(error));
|
|
9
|
+
if (error.stack) logger.error("\nError Stack: ", error.stack);
|
|
10
|
+
else logger.error("\nError Stack: ", error);
|
|
11
|
+
|
|
12
|
+
if (error.name !== "LoongbaoReject") {
|
|
13
|
+
// If it is not LoongbaoReject, it is considered an internal server error that should not be exposed
|
|
14
|
+
logger.error(`FailCode: internal-server-error`);
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
executeId,
|
|
18
|
+
success: false,
|
|
19
|
+
fail: {
|
|
20
|
+
code: "internal-server-error",
|
|
21
|
+
message: failCode["internal-server-error"](),
|
|
22
|
+
data: undefined
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
} else {
|
|
26
|
+
logger.error(`FailCode: ${error.code}`);
|
|
27
|
+
return {
|
|
28
|
+
executeId,
|
|
29
|
+
success: false,
|
|
30
|
+
fail: {
|
|
31
|
+
code: error.code,
|
|
32
|
+
message: error.message,
|
|
33
|
+
data: error.data
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { existsSync, lstatSync, readdirSync, rmdirSync, unlinkSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export function removeDir(pathstr: string, skips: Array<string> = []) {
|
|
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
|
+
}
|
package/utils/tson.ts
ADDED