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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-aiko",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "CLI to create aiko-boot scaffold project (monorepo with api, admin, mobile, shared)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scaffold-monorepo",
3
- "version": "0.1.0",
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
- await createKyselyDatabase({
21
- type: 'sqlite',
22
- filename: join(__dirname, '../../data/app.db'),
23
- });
24
-
25
- const db = getKyselyDatabase();
26
-
27
- // 建表
28
- await db.schema.createTable('sys_user').ifNotExists()
29
- .addColumn('id', 'integer', (col: any) => col.primaryKey().autoIncrement())
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
- const menuCount = await db.selectFrom('sys_menu').select(db.fn.count('id').as('cnt')).executeTakeFirst();
91
- const cntValue = menuCount && menuCount.cnt;
92
- if (Number(cntValue) === 0) {
93
- // 系统管理目录
94
- const sysDir = await db.insertInto('sys_menu')
95
- .values({ parent_id: 0, menu_name: '系统管理', menu_type: 1, icon: 'Settings', sort_order: 100, status: 1 })
96
- .executeTakeFirst();
97
- const sysDirId = Number(sysDir.insertId);
98
-
99
- const menus = [
100
- { parent_id: sysDirId, menu_name: '用户管理', menu_type: 2, path: '/sys/user', permission: 'sys:user:list', sort_order: 1, status: 1 },
101
- { parent_id: sysDirId, menu_name: '角色管理', menu_type: 2, path: '/sys/role', permission: 'sys:role:list', sort_order: 2, status: 1 },
102
- { parent_id: sysDirId, menu_name: '菜单管理', menu_type: 2, path: '/sys/menu', permission: 'sys:menu:list', sort_order: 3, status: 1 },
103
- ];
104
- const insertedMenus = await db.insertInto('sys_menu').values(menus).executeTakeFirst();
105
- console.log(' 默认菜单创建完成');
106
-
107
- // 给超级管理员角色分配所有菜单
108
- const allMenus = await db.selectFrom('sys_menu').select('id').execute();
109
- for (const m of allMenus) {
110
- await db.insertInto('sys_role_menu').values({ role_id: adminRoleId, menu_id: m.id as number }).execute();
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
- // 初始化 admin 账号
115
- const users = await db.selectFrom('sys_user').where('user_name', '=', 'admin').selectAll().execute();
116
- if (!users.length) {
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
- await runGeneratedModuleInitializers(db);
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
- console.log('\n🎉 数据库初始化完成!');
131
- process.exit(0);
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');