create-aiko 0.1.0 → 0.1.2
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/package.json
CHANGED
package/template/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scaffold-monorepo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "A clean scaffold template for api, admin, mobile, shared, and shared-auth packages.",
|
|
6
6
|
"type": "module",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"pnpm": ">=9.0.0"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
|
+
"postinstall": "node ./scripts/postinstall.cjs",
|
|
12
13
|
"dev": "pnpm --parallel dev:api dev:admin dev:mobile",
|
|
13
14
|
"dev:api": "pnpm --filter @scaffold/api dev",
|
|
14
15
|
"dev:admin": "pnpm --filter @scaffold/admin dev",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* 运行: pnpm init-db
|
|
4
4
|
*/
|
|
5
5
|
import 'reflect-metadata';
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
6
7
|
import { fileURLToPath } from 'url';
|
|
7
8
|
import { pathToFileURL } from 'url';
|
|
8
9
|
import { dirname, join } from 'path';
|
|
@@ -17,118 +18,185 @@ if (!existsSync(dir)) {
|
|
|
17
18
|
mkdirSync(dir, { recursive: true });
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
.
|
|
30
|
-
.addColumn('user_name', 'varchar(50)', (col: any) => col.notNull().unique())
|
|
31
|
-
.addColumn('password_hash', 'varchar(255)', (col: any) => col.notNull())
|
|
32
|
-
.addColumn('real_name', 'varchar(50)')
|
|
33
|
-
.addColumn('email', 'varchar(100)')
|
|
34
|
-
.addColumn('phone', 'varchar(20)')
|
|
35
|
-
.addColumn('status', 'integer', (col: any) => col.notNull().defaultTo(1))
|
|
36
|
-
.addColumn('created_at', 'datetime')
|
|
37
|
-
.addColumn('updated_at', 'datetime')
|
|
38
|
-
.execute();
|
|
39
|
-
|
|
40
|
-
await db.schema.createTable('sys_role').ifNotExists()
|
|
41
|
-
.addColumn('id', 'integer', (col: any) => col.primaryKey().autoIncrement())
|
|
42
|
-
.addColumn('role_code', 'varchar(50)', (col: any) => col.notNull().unique())
|
|
43
|
-
.addColumn('role_name', 'varchar(50)', (col: any) => col.notNull())
|
|
44
|
-
.addColumn('description', 'varchar(255)')
|
|
45
|
-
.addColumn('status', 'integer', (col: any) => col.notNull().defaultTo(1))
|
|
46
|
-
.addColumn('created_at', 'datetime')
|
|
47
|
-
.execute();
|
|
48
|
-
|
|
49
|
-
await db.schema.createTable('sys_menu').ifNotExists()
|
|
50
|
-
.addColumn('id', 'integer', (col: any) => col.primaryKey().autoIncrement())
|
|
51
|
-
.addColumn('parent_id', 'integer', (col: any) => col.notNull().defaultTo(0))
|
|
52
|
-
.addColumn('menu_name', 'varchar(50)', (col: any) => col.notNull())
|
|
53
|
-
.addColumn('menu_type', 'integer', (col: any) => col.notNull())
|
|
54
|
-
.addColumn('path', 'varchar(255)')
|
|
55
|
-
.addColumn('component', 'varchar(255)')
|
|
56
|
-
.addColumn('permission', 'varchar(100)')
|
|
57
|
-
.addColumn('icon', 'varchar(100)')
|
|
58
|
-
.addColumn('sort_order', 'integer', (col: any) => col.notNull().defaultTo(0))
|
|
59
|
-
.addColumn('status', 'integer', (col: any) => col.notNull().defaultTo(1))
|
|
60
|
-
.execute();
|
|
61
|
-
|
|
62
|
-
await db.schema.createTable('sys_user_role').ifNotExists()
|
|
63
|
-
.addColumn('id', 'integer', (col: any) => col.primaryKey().autoIncrement())
|
|
64
|
-
.addColumn('user_id', 'integer', (col: any) => col.notNull())
|
|
65
|
-
.addColumn('role_id', 'integer', (col: any) => col.notNull())
|
|
66
|
-
.execute();
|
|
67
|
-
|
|
68
|
-
await db.schema.createTable('sys_role_menu').ifNotExists()
|
|
69
|
-
.addColumn('id', 'integer', (col: any) => col.primaryKey().autoIncrement())
|
|
70
|
-
.addColumn('role_id', 'integer', (col: any) => col.notNull())
|
|
71
|
-
.addColumn('menu_id', 'integer', (col: any) => col.notNull())
|
|
72
|
-
.execute();
|
|
73
|
-
|
|
74
|
-
console.log('✅ 表结构创建完成');
|
|
75
|
-
|
|
76
|
-
// 初始化超级管理员角色
|
|
77
|
-
const roles = await db.selectFrom('sys_role').where('role_code', '=', 'SUPER_ADMIN').selectAll().execute();
|
|
78
|
-
let adminRoleId: number;
|
|
79
|
-
if (!roles.length) {
|
|
80
|
-
const result = await db.insertInto('sys_role')
|
|
81
|
-
.values({ role_code: 'SUPER_ADMIN', role_name: '超级管理员', description: '拥有全部权限', status: 1, created_at: new Date().toISOString() })
|
|
82
|
-
.executeTakeFirst();
|
|
83
|
-
adminRoleId = Number(result.insertId);
|
|
84
|
-
console.log('✅ 超级管理员角色创建完成');
|
|
85
|
-
} else {
|
|
86
|
-
adminRoleId = roles[0].id as number;
|
|
21
|
+
try {
|
|
22
|
+
await main();
|
|
23
|
+
process.exit(0);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
const handled = await tryRepairBetterSqlite(error);
|
|
26
|
+
if (handled) {
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
console.error(error);
|
|
30
|
+
process.exit(1);
|
|
87
31
|
}
|
|
88
32
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
33
|
+
async function main() {
|
|
34
|
+
await createKyselyDatabase({
|
|
35
|
+
type: 'sqlite',
|
|
36
|
+
filename: join(__dirname, '../../data/app.db'),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const db = getKyselyDatabase();
|
|
40
|
+
|
|
41
|
+
// 建表
|
|
42
|
+
await db.schema.createTable('sys_user').ifNotExists()
|
|
43
|
+
.addColumn('id', 'integer', (col: any) => col.primaryKey().autoIncrement())
|
|
44
|
+
.addColumn('user_name', 'varchar(50)', (col: any) => col.notNull().unique())
|
|
45
|
+
.addColumn('password_hash', 'varchar(255)', (col: any) => col.notNull())
|
|
46
|
+
.addColumn('real_name', 'varchar(50)')
|
|
47
|
+
.addColumn('email', 'varchar(100)')
|
|
48
|
+
.addColumn('phone', 'varchar(20)')
|
|
49
|
+
.addColumn('status', 'integer', (col: any) => col.notNull().defaultTo(1))
|
|
50
|
+
.addColumn('created_at', 'datetime')
|
|
51
|
+
.addColumn('updated_at', 'datetime')
|
|
52
|
+
.execute();
|
|
53
|
+
|
|
54
|
+
await db.schema.createTable('sys_role').ifNotExists()
|
|
55
|
+
.addColumn('id', 'integer', (col: any) => col.primaryKey().autoIncrement())
|
|
56
|
+
.addColumn('role_code', 'varchar(50)', (col: any) => col.notNull().unique())
|
|
57
|
+
.addColumn('role_name', 'varchar(50)', (col: any) => col.notNull())
|
|
58
|
+
.addColumn('description', 'varchar(255)')
|
|
59
|
+
.addColumn('status', 'integer', (col: any) => col.notNull().defaultTo(1))
|
|
60
|
+
.addColumn('created_at', 'datetime')
|
|
61
|
+
.execute();
|
|
62
|
+
|
|
63
|
+
await db.schema.createTable('sys_menu').ifNotExists()
|
|
64
|
+
.addColumn('id', 'integer', (col: any) => col.primaryKey().autoIncrement())
|
|
65
|
+
.addColumn('parent_id', 'integer', (col: any) => col.notNull().defaultTo(0))
|
|
66
|
+
.addColumn('menu_name', 'varchar(50)', (col: any) => col.notNull())
|
|
67
|
+
.addColumn('menu_type', 'integer', (col: any) => col.notNull())
|
|
68
|
+
.addColumn('path', 'varchar(255)')
|
|
69
|
+
.addColumn('component', 'varchar(255)')
|
|
70
|
+
.addColumn('permission', 'varchar(100)')
|
|
71
|
+
.addColumn('icon', 'varchar(100)')
|
|
72
|
+
.addColumn('sort_order', 'integer', (col: any) => col.notNull().defaultTo(0))
|
|
73
|
+
.addColumn('status', 'integer', (col: any) => col.notNull().defaultTo(1))
|
|
74
|
+
.execute();
|
|
75
|
+
|
|
76
|
+
await db.schema.createTable('sys_user_role').ifNotExists()
|
|
77
|
+
.addColumn('id', 'integer', (col: any) => col.primaryKey().autoIncrement())
|
|
78
|
+
.addColumn('user_id', 'integer', (col: any) => col.notNull())
|
|
79
|
+
.addColumn('role_id', 'integer', (col: any) => col.notNull())
|
|
80
|
+
.execute();
|
|
81
|
+
|
|
82
|
+
await db.schema.createTable('sys_role_menu').ifNotExists()
|
|
83
|
+
.addColumn('id', 'integer', (col: any) => col.primaryKey().autoIncrement())
|
|
84
|
+
.addColumn('role_id', 'integer', (col: any) => col.notNull())
|
|
85
|
+
.addColumn('menu_id', 'integer', (col: any) => col.notNull())
|
|
86
|
+
.execute();
|
|
87
|
+
|
|
88
|
+
console.log('✅ 表结构创建完成');
|
|
89
|
+
|
|
90
|
+
// 初始化超级管理员角色
|
|
91
|
+
const roles = await db.selectFrom('sys_role').where('role_code', '=', 'SUPER_ADMIN').selectAll().execute();
|
|
92
|
+
let adminRoleId: number;
|
|
93
|
+
if (!roles.length) {
|
|
94
|
+
const result = await db.insertInto('sys_role')
|
|
95
|
+
.values({ role_code: 'SUPER_ADMIN', role_name: '超级管理员', description: '拥有全部权限', status: 1, created_at: new Date().toISOString() })
|
|
96
|
+
.executeTakeFirst();
|
|
97
|
+
adminRoleId = Number(result.insertId);
|
|
98
|
+
console.log('✅ 超级管理员角色创建完成');
|
|
99
|
+
} else {
|
|
100
|
+
adminRoleId = roles[0].id as number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 初始化默认菜单
|
|
104
|
+
const menuCount = await db.selectFrom('sys_menu').select(db.fn.count('id').as('cnt')).executeTakeFirst();
|
|
105
|
+
const cntValue = menuCount && menuCount.cnt;
|
|
106
|
+
if (Number(cntValue) === 0) {
|
|
107
|
+
const sysDir = await db.insertInto('sys_menu')
|
|
108
|
+
.values({ parent_id: 0, menu_name: '系统管理', menu_type: 1, icon: 'Settings', sort_order: 100, status: 1 })
|
|
109
|
+
.executeTakeFirst();
|
|
110
|
+
const sysDirId = Number(sysDir.insertId);
|
|
111
|
+
|
|
112
|
+
const menus = [
|
|
113
|
+
{ parent_id: sysDirId, menu_name: '用户管理', menu_type: 2, path: '/sys/user', permission: 'sys:user:list', sort_order: 1, status: 1 },
|
|
114
|
+
{ parent_id: sysDirId, menu_name: '角色管理', menu_type: 2, path: '/sys/role', permission: 'sys:role:list', sort_order: 2, status: 1 },
|
|
115
|
+
{ parent_id: sysDirId, menu_name: '菜单管理', menu_type: 2, path: '/sys/menu', permission: 'sys:menu:list', sort_order: 3, status: 1 },
|
|
116
|
+
];
|
|
117
|
+
await db.insertInto('sys_menu').values(menus).executeTakeFirst();
|
|
118
|
+
console.log('✅ 默认菜单创建完成');
|
|
119
|
+
|
|
120
|
+
const allMenus = await db.selectFrom('sys_menu').select('id').execute();
|
|
121
|
+
for (const m of allMenus) {
|
|
122
|
+
await db.insertInto('sys_role_menu').values({ role_id: adminRoleId, menu_id: m.id as number }).execute();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const users = await db.selectFrom('sys_user').where('user_name', '=', 'admin').selectAll().execute();
|
|
127
|
+
if (!users.length) {
|
|
128
|
+
const hashed = await bcrypt.hash('admin123', 10);
|
|
129
|
+
const result = await db.insertInto('sys_user')
|
|
130
|
+
.values({ user_name: 'admin', password_hash: hashed, email: 'admin@example.com', real_name: '超级管理员', status: 1, created_at: new Date().toISOString(), updated_at: new Date().toISOString() })
|
|
131
|
+
.executeTakeFirst();
|
|
132
|
+
const adminUserId = Number(result.insertId);
|
|
133
|
+
await db.insertInto('sys_user_role').values({ user_id: adminUserId, role_id: adminRoleId }).execute();
|
|
134
|
+
console.log('✅ admin 账号创建完成 (密码: admin123)');
|
|
135
|
+
} else {
|
|
136
|
+
console.log('ℹ️ admin 账号已存在,跳过');
|
|
111
137
|
}
|
|
112
|
-
}
|
|
113
138
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const hashed = await bcrypt.hash('admin123', 10);
|
|
118
|
-
const result = await db.insertInto('sys_user')
|
|
119
|
-
.values({ user_name: 'admin', password_hash: hashed, email: 'admin@example.com', real_name: '超级管理员', status: 1, created_at: new Date().toISOString(), updated_at: new Date().toISOString() })
|
|
120
|
-
.executeTakeFirst();
|
|
121
|
-
const adminUserId = Number(result.insertId);
|
|
122
|
-
await db.insertInto('sys_user_role').values({ user_id: adminUserId, role_id: adminRoleId }).execute();
|
|
123
|
-
console.log('✅ admin 账号创建完成 (密码: Admin@123)');
|
|
124
|
-
} else {
|
|
125
|
-
console.log('ℹ️ admin 账号已存在,跳过');
|
|
139
|
+
await runGeneratedModuleInitializers(db);
|
|
140
|
+
|
|
141
|
+
console.log('\n🎉 数据库初始化完成!');
|
|
126
142
|
}
|
|
127
143
|
|
|
128
|
-
|
|
144
|
+
async function tryRepairBetterSqlite(error: unknown): Promise<boolean> {
|
|
145
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
146
|
+
const isBindingsError =
|
|
147
|
+
message.includes('Could not locate the bindings file') ||
|
|
148
|
+
message.includes('better_sqlite3.node');
|
|
149
|
+
|
|
150
|
+
if (!isBindingsError || process.env.AIKO_SQLITE_REBUILD_ATTEMPTED === '1') {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.warn('\n[init-db] 检测到 better-sqlite3 原生绑定缺失,正在尝试自动重建...');
|
|
129
155
|
|
|
130
|
-
|
|
131
|
-
|
|
156
|
+
const projectRoot = join(__dirname, '../../../..');
|
|
157
|
+
const rebuild = spawnSync(
|
|
158
|
+
'pnpm',
|
|
159
|
+
['rebuild', 'better-sqlite3'],
|
|
160
|
+
{
|
|
161
|
+
cwd: projectRoot,
|
|
162
|
+
stdio: 'inherit',
|
|
163
|
+
shell: process.platform === 'win32',
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
if (rebuild.error || rebuild.status !== 0) {
|
|
168
|
+
console.error('\n[init-db] 自动重建失败,请手动执行:pnpm run rebuild:sqlite');
|
|
169
|
+
if (rebuild.error) {
|
|
170
|
+
console.error(rebuild.error);
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
console.log('\n[init-db] better-sqlite3 重建成功,正在重试数据库初始化...');
|
|
176
|
+
|
|
177
|
+
const retry = spawnSync(
|
|
178
|
+
process.execPath,
|
|
179
|
+
['--import', '@swc-node/register/esm-register', process.argv[1]!],
|
|
180
|
+
{
|
|
181
|
+
cwd: projectRoot,
|
|
182
|
+
stdio: 'inherit',
|
|
183
|
+
env: {
|
|
184
|
+
...process.env,
|
|
185
|
+
AIKO_SQLITE_REBUILD_ATTEMPTED: '1',
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
if (retry.error) {
|
|
191
|
+
throw retry.error;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if ((retry.status ?? 1) !== 0) {
|
|
195
|
+
throw new Error(`[init-db] 重试数据库初始化失败,退出码 ${retry.status ?? 1}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
132
200
|
|
|
133
201
|
async function runGeneratedModuleInitializers(dbInstance: any) {
|
|
134
202
|
const generatedDir = join(__dirname, 'generated');
|