create-aiko 0.1.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/README.md +46 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +7 -0
- package/dist/create.d.ts +1 -0
- package/dist/create.js +174 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/scaffold.d.ts +7 -0
- package/dist/scaffold.js +310 -0
- package/package.json +37 -0
- package/template/README.md +63 -0
- package/template/docs/better-sqlite3-design.md +101 -0
- package/template/docs/shared-vs-shared-auth.md +54 -0
- package/template/package.json +23 -0
- package/template/packages/admin/.env.dev +1 -0
- package/template/packages/admin/.env.prod +4 -0
- package/template/packages/admin/.env.stage +4 -0
- package/template/packages/admin/Dockerfile +37 -0
- package/template/packages/admin/README.MD +27 -0
- package/template/packages/admin/components.json +21 -0
- package/template/packages/admin/eslint.config.js +28 -0
- package/template/packages/admin/index.html +50 -0
- package/template/packages/admin/package.json +100 -0
- package/template/packages/admin/public/vite.svg +1 -0
- package/template/packages/admin/src/App.css +82 -0
- package/template/packages/admin/src/App.tsx +128 -0
- package/template/packages/admin/src/app.config.ts +3 -0
- package/template/packages/admin/src/components/admin-ui/approval-flow.tsx +205 -0
- package/template/packages/admin/src/components/admin-ui/data-table/data-table-pagination.tsx +148 -0
- package/template/packages/admin/src/components/admin-ui/data-table/data-table-sorter.tsx +50 -0
- package/template/packages/admin/src/components/admin-ui/data-table/index.tsx +296 -0
- package/template/packages/admin/src/components/admin-ui/editable-table.tsx +292 -0
- package/template/packages/admin/src/components/admin-ui/form/input-password.tsx +38 -0
- package/template/packages/admin/src/components/admin-ui/form/sign-in-form.tsx +104 -0
- package/template/packages/admin/src/components/admin-ui/layout/error-component.tsx +71 -0
- package/template/packages/admin/src/components/admin-ui/layout/header.tsx +148 -0
- package/template/packages/admin/src/components/admin-ui/layout/language-switcher.tsx +47 -0
- package/template/packages/admin/src/components/admin-ui/layout/layout.tsx +42 -0
- package/template/packages/admin/src/components/admin-ui/layout/loading-overlay.tsx +36 -0
- package/template/packages/admin/src/components/admin-ui/layout/shell-bar.tsx +232 -0
- package/template/packages/admin/src/components/admin-ui/layout/sidebar.tsx +193 -0
- package/template/packages/admin/src/components/admin-ui/layout/user-avatar.tsx +31 -0
- package/template/packages/admin/src/components/admin-ui/list-report.tsx +313 -0
- package/template/packages/admin/src/components/admin-ui/master-detail.tsx +382 -0
- package/template/packages/admin/src/components/admin-ui/notification/toaster.tsx +23 -0
- package/template/packages/admin/src/components/admin-ui/notification/undoable-notification.tsx +84 -0
- package/template/packages/admin/src/components/admin-ui/object-page.tsx +539 -0
- package/template/packages/admin/src/components/admin-ui/process-stepper.tsx +204 -0
- package/template/packages/admin/src/components/admin-ui/theme/theme-provider.tsx +160 -0
- package/template/packages/admin/src/components/admin-ui/theme/theme-select.tsx +129 -0
- package/template/packages/admin/src/components/admin-ui/theme/theme-toggle.tsx +90 -0
- package/template/packages/admin/src/components/admin-ui/timeline.tsx +137 -0
- package/template/packages/admin/src/components/admin-ui/tree-navigator.tsx +243 -0
- package/template/packages/admin/src/components/ui/accordion.tsx +64 -0
- package/template/packages/admin/src/components/ui/alert-dialog.tsx +157 -0
- package/template/packages/admin/src/components/ui/alert.tsx +66 -0
- package/template/packages/admin/src/components/ui/aspect-ratio.tsx +9 -0
- package/template/packages/admin/src/components/ui/avatar.tsx +53 -0
- package/template/packages/admin/src/components/ui/badge.tsx +47 -0
- package/template/packages/admin/src/components/ui/breadcrumb.tsx +111 -0
- package/template/packages/admin/src/components/ui/button.tsx +59 -0
- package/template/packages/admin/src/components/ui/calendar.tsx +74 -0
- package/template/packages/admin/src/components/ui/card.tsx +92 -0
- package/template/packages/admin/src/components/ui/carousel.tsx +237 -0
- package/template/packages/admin/src/components/ui/chart.tsx +351 -0
- package/template/packages/admin/src/components/ui/checkbox.tsx +32 -0
- package/template/packages/admin/src/components/ui/collapsible.tsx +33 -0
- package/template/packages/admin/src/components/ui/command.tsx +182 -0
- package/template/packages/admin/src/components/ui/context-menu.tsx +252 -0
- package/template/packages/admin/src/components/ui/dialog.tsx +141 -0
- package/template/packages/admin/src/components/ui/drawer.tsx +130 -0
- package/template/packages/admin/src/components/ui/dropdown-menu.tsx +255 -0
- package/template/packages/admin/src/components/ui/form.tsx +166 -0
- package/template/packages/admin/src/components/ui/hover-card.tsx +42 -0
- package/template/packages/admin/src/components/ui/input-otp.tsx +77 -0
- package/template/packages/admin/src/components/ui/input.tsx +21 -0
- package/template/packages/admin/src/components/ui/label.tsx +22 -0
- package/template/packages/admin/src/components/ui/menubar.tsx +274 -0
- package/template/packages/admin/src/components/ui/navigation-menu.tsx +168 -0
- package/template/packages/admin/src/components/ui/pagination.tsx +127 -0
- package/template/packages/admin/src/components/ui/popover.tsx +48 -0
- package/template/packages/admin/src/components/ui/progress.tsx +29 -0
- package/template/packages/admin/src/components/ui/radio-group.tsx +45 -0
- package/template/packages/admin/src/components/ui/resizable.tsx +54 -0
- package/template/packages/admin/src/components/ui/scroll-area.tsx +58 -0
- package/template/packages/admin/src/components/ui/select.tsx +183 -0
- package/template/packages/admin/src/components/ui/separator.tsx +26 -0
- package/template/packages/admin/src/components/ui/sheet.tsx +139 -0
- package/template/packages/admin/src/components/ui/sidebar.tsx +740 -0
- package/template/packages/admin/src/components/ui/skeleton.tsx +13 -0
- package/template/packages/admin/src/components/ui/slider.tsx +63 -0
- package/template/packages/admin/src/components/ui/sonner.tsx +23 -0
- package/template/packages/admin/src/components/ui/switch.tsx +31 -0
- package/template/packages/admin/src/components/ui/table.tsx +114 -0
- package/template/packages/admin/src/components/ui/tabs.tsx +66 -0
- package/template/packages/admin/src/components/ui/textarea.tsx +18 -0
- package/template/packages/admin/src/components/ui/toggle-group.tsx +73 -0
- package/template/packages/admin/src/components/ui/toggle.tsx +45 -0
- package/template/packages/admin/src/components/ui/tooltip.tsx +59 -0
- package/template/packages/admin/src/hooks/use-mobile.ts +21 -0
- package/template/packages/admin/src/i18n.ts +20 -0
- package/template/packages/admin/src/index.tsx +18 -0
- package/template/packages/admin/src/layouts/menu-layout.tsx +211 -0
- package/template/packages/admin/src/layouts/tile-layout.tsx +355 -0
- package/template/packages/admin/src/lib/utils.ts +6 -0
- package/template/packages/admin/src/locales/en.json +68 -0
- package/template/packages/admin/src/locales/zh.json +68 -0
- package/template/packages/admin/src/pages/dashboard.tsx +12 -0
- package/template/packages/admin/src/pages/goods-receipt/CreatePage.tsx +302 -0
- package/template/packages/admin/src/pages/goods-receipt/EditPage.tsx +221 -0
- package/template/packages/admin/src/pages/goods-receipt/ListPage.tsx +283 -0
- package/template/packages/admin/src/pages/goods-receipt/ViewPage.tsx +280 -0
- package/template/packages/admin/src/pages/goods-receipt/index.ts +4 -0
- package/template/packages/admin/src/pages/home-page.tsx +244 -0
- package/template/packages/admin/src/pages/login-page.tsx +91 -0
- package/template/packages/admin/src/pages/master-data/cost-centers/index.tsx +461 -0
- package/template/packages/admin/src/pages/master-data/currencies/index.tsx +255 -0
- package/template/packages/admin/src/pages/master-data/materials/ListPage.tsx +271 -0
- package/template/packages/admin/src/pages/master-data/materials/ViewPage.tsx +240 -0
- package/template/packages/admin/src/pages/master-data/materials/index.ts +2 -0
- package/template/packages/admin/src/pages/master-data/plants/ListPage.tsx +279 -0
- package/template/packages/admin/src/pages/master-data/plants/ViewPage.tsx +380 -0
- package/template/packages/admin/src/pages/master-data/plants/index.ts +2 -0
- package/template/packages/admin/src/pages/master-data/purchase-organizations/index.tsx +341 -0
- package/template/packages/admin/src/pages/master-data/units-of-measure/index.tsx +295 -0
- package/template/packages/admin/src/pages/master-data/vendors/ListPage.tsx +266 -0
- package/template/packages/admin/src/pages/master-data/vendors/ViewPage.tsx +274 -0
- package/template/packages/admin/src/pages/master-data/vendors/index.ts +2 -0
- package/template/packages/admin/src/pages/placeholder-page.tsx +13 -0
- package/template/packages/admin/src/pages/purchase-orders/ListPage.tsx +289 -0
- package/template/packages/admin/src/pages/purchase-orders/ViewPage.tsx +343 -0
- package/template/packages/admin/src/pages/purchase-orders/index.ts +2 -0
- package/template/packages/admin/src/pages/purchase-requisitions/CreatePage.tsx +398 -0
- package/template/packages/admin/src/pages/purchase-requisitions/EditPage.tsx +473 -0
- package/template/packages/admin/src/pages/purchase-requisitions/ListPage.tsx +307 -0
- package/template/packages/admin/src/pages/purchase-requisitions/ViewPage.tsx +304 -0
- package/template/packages/admin/src/pages/purchase-requisitions/constants.ts +51 -0
- package/template/packages/admin/src/pages/purchase-requisitions/index.ts +4 -0
- package/template/packages/admin/src/pages/reports/PurchaseOrderReport.tsx +312 -0
- package/template/packages/admin/src/pages/reports/PurchaseRequisitionReport.tsx +303 -0
- package/template/packages/admin/src/pages/reports/index.ts +2 -0
- package/template/packages/admin/src/pages/settings-page.tsx +335 -0
- package/template/packages/admin/src/providers/app-config.tsx +50 -0
- package/template/packages/admin/src/routes/auth.ts +6 -0
- package/template/packages/admin/src/routes/index.ts +85 -0
- package/template/packages/admin/src/routes/menu.ts +176 -0
- package/template/packages/admin/src/routes/modules/goods-receipt.ts +31 -0
- package/template/packages/admin/src/routes/modules/master-data.ts +41 -0
- package/template/packages/admin/src/routes/modules/purchase-orders.ts +27 -0
- package/template/packages/admin/src/routes/modules/purchase-requisitions.ts +39 -0
- package/template/packages/admin/src/routes/modules/reports.ts +33 -0
- package/template/packages/admin/src/routes/modules/settings.ts +19 -0
- package/template/packages/admin/src/routes/withSuspense.tsx +21 -0
- package/template/packages/admin/src/theme/amber.css +27 -0
- package/template/packages/admin/src/theme/blue.css +27 -0
- package/template/packages/admin/src/theme/default.css +75 -0
- package/template/packages/admin/src/theme/fiori.css +180 -0
- package/template/packages/admin/src/theme/green.css +27 -0
- package/template/packages/admin/src/theme/index.css +12 -0
- package/template/packages/admin/src/theme/rose.css +27 -0
- package/template/packages/admin/src/theme/violet.css +27 -0
- package/template/packages/admin/src/vite-env.d.ts +1 -0
- package/template/packages/admin/tsconfig.json +28 -0
- package/template/packages/admin/tsconfig.node.json +21 -0
- package/template/packages/admin/vite.config.ts +26 -0
- package/template/packages/api/.eslintrc.json +6 -0
- package/template/packages/api/.swcrc +17 -0
- package/template/packages/api/app.config.ts +160 -0
- package/template/packages/api/docs/api-document.md +497 -0
- package/template/packages/api/examples/security/README.md +664 -0
- package/template/packages/api/examples/security/complete/.env.example +26 -0
- package/template/packages/api/examples/security/complete/PROJECT_STRUCTURE.md +220 -0
- package/template/packages/api/examples/security/complete/README.md +847 -0
- package/template/packages/api/examples/security/complete/app.config.ts +69 -0
- package/template/packages/api/examples/security/complete/app.ts +63 -0
- package/template/packages/api/examples/security/complete/controller/auth.controller.ts +131 -0
- package/template/packages/api/examples/security/complete/controller/index.ts +4 -0
- package/template/packages/api/examples/security/complete/controller/permission.controller.ts +41 -0
- package/template/packages/api/examples/security/complete/controller/role.controller.ts +53 -0
- package/template/packages/api/examples/security/complete/controller/user.controller.ts +53 -0
- package/template/packages/api/examples/security/complete/dto/change-password.dto.ts +10 -0
- package/template/packages/api/examples/security/complete/dto/create-permission.dto.ts +14 -0
- package/template/packages/api/examples/security/complete/dto/create-role.dto.ts +11 -0
- package/template/packages/api/examples/security/complete/dto/create-user.dto.ts +15 -0
- package/template/packages/api/examples/security/complete/dto/index.ts +7 -0
- package/template/packages/api/examples/security/complete/dto/login.dto.ts +10 -0
- package/template/packages/api/examples/security/complete/dto/oauth-profile.dto.ts +7 -0
- package/template/packages/api/examples/security/complete/dto/register.dto.ts +17 -0
- package/template/packages/api/examples/security/complete/entity/index.ts +6 -0
- package/template/packages/api/examples/security/complete/entity/oauth-account.entity.ts +39 -0
- package/template/packages/api/examples/security/complete/entity/permission.entity.ts +31 -0
- package/template/packages/api/examples/security/complete/entity/role-permission.entity.ts +19 -0
- package/template/packages/api/examples/security/complete/entity/role.entity.ts +25 -0
- package/template/packages/api/examples/security/complete/entity/user-role.entity.ts +19 -0
- package/template/packages/api/examples/security/complete/entity/user.entity.ts +46 -0
- package/template/packages/api/examples/security/complete/init.sql +81 -0
- package/template/packages/api/examples/security/complete/middleware/auth.interceptor.ts +39 -0
- package/template/packages/api/examples/security/complete/middleware/index.ts +2 -0
- package/template/packages/api/examples/security/complete/middleware/permission.interceptor.ts +61 -0
- package/template/packages/api/examples/security/complete/package.json +54 -0
- package/template/packages/api/examples/security/complete/seed.sql +42 -0
- package/template/packages/api/examples/security/complete/service/auth.service.ts +41 -0
- package/template/packages/api/examples/security/complete/service/index.ts +5 -0
- package/template/packages/api/examples/security/complete/service/oauth.service.ts +82 -0
- package/template/packages/api/examples/security/complete/service/permission.service.ts +113 -0
- package/template/packages/api/examples/security/complete/service/role.service.ts +85 -0
- package/template/packages/api/examples/security/complete/service/user.service.ts +132 -0
- package/template/packages/api/examples/security/complete/tests/TEST_REPORT.md +318 -0
- package/template/packages/api/examples/security/complete/tests/generate-report.js +335 -0
- package/template/packages/api/examples/security/complete/tests/helpers/api-helpers.ts +116 -0
- package/template/packages/api/examples/security/complete/tests/helpers/index.ts +2 -0
- package/template/packages/api/examples/security/complete/tests/helpers/test-helpers.ts +129 -0
- package/template/packages/api/examples/security/complete/tests/integration/auth.api.test.ts +429 -0
- package/template/packages/api/examples/security/complete/tests/integration/role.api.test.ts +400 -0
- package/template/packages/api/examples/security/complete/tests/integration/user.api.test.ts +459 -0
- package/template/packages/api/examples/security/complete/tests/jest.config.js +40 -0
- package/template/packages/api/examples/security/complete/tests/run-all-tests.js +135 -0
- package/template/packages/api/examples/security/complete/tests/run-tests.js +109 -0
- package/template/packages/api/examples/security/complete/tests/setup.ts +19 -0
- package/template/packages/api/examples/security/complete/tests/unit/auth.service.test.ts +199 -0
- package/template/packages/api/examples/security/complete/tests/unit/permission.service.test.ts +377 -0
- package/template/packages/api/examples/security/complete/tests/unit/user.service.test.ts +288 -0
- package/template/packages/api/examples/security/complete/tsconfig.json +35 -0
- package/template/packages/api/examples/security/jwt/README.md +424 -0
- package/template/packages/api/examples/security/local/README.md +499 -0
- package/template/packages/api/examples/security/oauth2/README.md +637 -0
- package/template/packages/api/examples/security/permission/README.md +943 -0
- package/template/packages/api/examples/security/session/README.md +753 -0
- package/template/packages/api/package.json +56 -0
- package/template/packages/api/src/controller/auth.controller.ts +127 -0
- package/template/packages/api/src/controller/cache.controller.ts +106 -0
- package/template/packages/api/src/controller/menu.controller.ts +46 -0
- package/template/packages/api/src/controller/mq.controller.ts +35 -0
- package/template/packages/api/src/controller/role.controller.ts +51 -0
- package/template/packages/api/src/controller/upload.controller.ts +85 -0
- package/template/packages/api/src/controller/user.controller.ts +57 -0
- package/template/packages/api/src/dto/auth.dto.ts +30 -0
- package/template/packages/api/src/dto/cache.dto.ts +24 -0
- package/template/packages/api/src/dto/menu.dto.ts +37 -0
- package/template/packages/api/src/dto/mq.dto.ts +16 -0
- package/template/packages/api/src/dto/role.dto.ts +16 -0
- package/template/packages/api/src/dto/user.dto.ts +35 -0
- package/template/packages/api/src/entity/menu.entity.ts +34 -0
- package/template/packages/api/src/entity/role-menu.entity.ts +13 -0
- package/template/packages/api/src/entity/role.entity.ts +22 -0
- package/template/packages/api/src/entity/user-role.entity.ts +13 -0
- package/template/packages/api/src/entity/user.entity.ts +31 -0
- package/template/packages/api/src/mapper/menu.mapper.ts +6 -0
- package/template/packages/api/src/mapper/role-menu.mapper.ts +6 -0
- package/template/packages/api/src/mapper/role.mapper.ts +6 -0
- package/template/packages/api/src/mapper/user-role.mapper.ts +6 -0
- package/template/packages/api/src/mapper/user.mapper.ts +11 -0
- package/template/packages/api/src/scripts/init-db.ts +185 -0
- package/template/packages/api/src/server.ts +76 -0
- package/template/packages/api/src/service/auth.service.ts +106 -0
- package/template/packages/api/src/service/cache.service.ts +80 -0
- package/template/packages/api/src/service/log.request.service.ts +158 -0
- package/template/packages/api/src/service/log.service.ts +123 -0
- package/template/packages/api/src/service/menu.service.ts +94 -0
- package/template/packages/api/src/service/mq.consumer.service.ts +26 -0
- package/template/packages/api/src/service/role.service.ts +88 -0
- package/template/packages/api/src/service/user.service.ts +170 -0
- package/template/packages/api/src/types/sqljs.d.ts +18 -0
- package/template/packages/api/src/utils/auth.utils.js +0 -0
- package/template/packages/api/src/utils/jwt.util.ts +29 -0
- package/template/packages/api/tsconfig.json +17 -0
- package/template/packages/api/tsup.config.ts +9 -0
- package/template/packages/api/uploads/.gitkeep +0 -0
- package/template/packages/core/package.json +31 -0
- package/template/packages/core/src/auth/auth-client-middleware.ts +22 -0
- package/template/packages/core/src/auth/auth-service.ts +65 -0
- package/template/packages/core/src/auth/default-auth-provider.ts +37 -0
- package/template/packages/core/src/auth/index.ts +10 -0
- package/template/packages/core/src/auth/types.ts +38 -0
- package/template/packages/core/src/authorization/authorization-client-middleware.ts +38 -0
- package/template/packages/core/src/authorization/authorization-config.ts +13 -0
- package/template/packages/core/src/authorization/authorization-provider.tsx +116 -0
- package/template/packages/core/src/authorization/default-authorization-provider.ts +26 -0
- package/template/packages/core/src/authorization/index.ts +15 -0
- package/template/packages/core/src/authorization/types.ts +42 -0
- package/template/packages/core/src/index.ts +3 -0
- package/template/packages/core/src/utils/promise-result-cache.ts +18 -0
- package/template/packages/core/tsconfig.json +19 -0
- package/template/packages/mobile/README.md +55 -0
- package/template/packages/mobile/index.html +13 -0
- package/template/packages/mobile/package.json +30 -0
- package/template/packages/mobile/postcss.config.mjs +7 -0
- package/template/packages/mobile/src/App.tsx +5 -0
- package/template/packages/mobile/src/app/globals.css +1 -0
- package/template/packages/mobile/src/components/LoginForm.tsx +76 -0
- package/template/packages/mobile/src/hooks/index.ts +5 -0
- package/template/packages/mobile/src/lib/utils.ts +7 -0
- package/template/packages/mobile/src/main.tsx +13 -0
- package/template/packages/mobile/src/pages/HomePage.tsx +35 -0
- package/template/packages/mobile/src/pages/LoginPage.tsx +35 -0
- package/template/packages/mobile/src/pages/index.ts +2 -0
- package/template/packages/mobile/src/routes/ProtectedRoute.tsx +29 -0
- package/template/packages/mobile/src/routes/index.tsx +24 -0
- package/template/packages/mobile/src/routes/routes.ts +11 -0
- package/template/packages/mobile/src/types/index.ts +5 -0
- package/template/packages/mobile/src/vite-env.d.ts +1 -0
- package/template/packages/mobile/tsconfig.json +23 -0
- package/template/packages/mobile/tsconfig.node.json +11 -0
- package/template/packages/mobile/vite.config.ts +19 -0
- package/template/packages/shared/package.json +20 -0
- package/template/packages/shared/src/constants.ts +8 -0
- package/template/packages/shared/src/index.ts +5 -0
- package/template/packages/shared/tsconfig.json +13 -0
- package/template/packages/shared-auth/package.json +29 -0
- package/template/packages/shared-auth/src/AuthContext.tsx +177 -0
- package/template/packages/shared-auth/src/AuthProviderWrapper.tsx +29 -0
- package/template/packages/shared-auth/src/index.ts +10 -0
- package/template/packages/shared-auth/tsconfig.json +14 -0
- package/template/pnpm-lock.yaml +16349 -0
- package/template/pnpm-workspace.yaml +3 -0
- package/template/scripts/postinstall.cjs +42 -0
- package/template/scripts/rebuild-sqlite.cjs +23 -0
package/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# create-aiko
|
|
2
|
+
|
|
3
|
+
CLI to create a new aiko-boot scaffold project (monorepo: api, admin, mobile, shared, optionally shared-auth).
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
Published package usage:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm dlx create-aiko my-app
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Repository-local usage:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm run create
|
|
17
|
+
node packages/aiko-boot-create/dist/cli.js
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Options
|
|
21
|
+
|
|
22
|
+
| Option | Description |
|
|
23
|
+
|--------|-------------|
|
|
24
|
+
| `[targetDir]` | Target directory for the new project. Default: `<cwd>/<projectName>` |
|
|
25
|
+
| `-n, --name <name>` | Project name, used as npm scope (e.g. `my-app` → `@my-app/*`) |
|
|
26
|
+
| `--with-base-system` | Include base system: login, shared-auth, user/menu management (later) |
|
|
27
|
+
| `--no-base-system` | Bare project without login/auth (default when not specified) |
|
|
28
|
+
| `--template-dir <dir>` | Path to scaffold template. Default: bundled template in npm package; if missing, fallback to `<cwd>/scaffold` |
|
|
29
|
+
|
|
30
|
+
## Base system vs bare
|
|
31
|
+
|
|
32
|
+
- **With base system** (`--with-base-system`): Copies full scaffold including `shared-auth`, auth controller/service/dto, user entity/mapper, init-db with `sys_user`, login pages and protected routes in admin/mobile.
|
|
33
|
+
- **Bare** (`--no-base-system`): Same monorepo structure (api, admin, mobile, shared) but no `shared-auth`, no auth-related files in api, no login UI or protected routes; shared has no `AUTH_STORAGE_KEY`.
|
|
34
|
+
|
|
35
|
+
## After generation
|
|
36
|
+
|
|
37
|
+
1. `cd <targetDir>`
|
|
38
|
+
2. `pnpm install`
|
|
39
|
+
3. If base system was included: `pnpm init-db`
|
|
40
|
+
4. `pnpm dev`
|
|
41
|
+
|
|
42
|
+
## Build
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pnpm build
|
|
46
|
+
```
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
package/dist/create.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createCommand(): void;
|
package/dist/create.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { program } from 'commander';
|
|
2
|
+
import * as readline from 'readline';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
import { createScaffold } from './scaffold.js';
|
|
7
|
+
const SCAFFOLD_DIR_NAME = 'scaffold';
|
|
8
|
+
const PACKAGE_TEMPLATE_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../template');
|
|
9
|
+
function getTemplateDir(cwd) {
|
|
10
|
+
const cwdTemplateDir = path.join(cwd, SCAFFOLD_DIR_NAME);
|
|
11
|
+
if (fs.existsSync(cwdTemplateDir)) {
|
|
12
|
+
return cwdTemplateDir;
|
|
13
|
+
}
|
|
14
|
+
return PACKAGE_TEMPLATE_DIR;
|
|
15
|
+
}
|
|
16
|
+
function prompt(question, defaultValue) {
|
|
17
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
const suffix = defaultValue !== undefined ? ` (${defaultValue})` : '';
|
|
20
|
+
rl.question(`${question}${suffix}: `, (answer) => {
|
|
21
|
+
rl.close();
|
|
22
|
+
resolve((answer.trim() || (defaultValue ?? '')).trim());
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function promptConfirm(question, defaultValue) {
|
|
27
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
28
|
+
const hint = defaultValue ? 'Y/n' : 'y/N';
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
rl.question(`${question} [${hint}]: `, (answer) => {
|
|
31
|
+
rl.close();
|
|
32
|
+
const lower = answer.trim().toLowerCase();
|
|
33
|
+
if (lower === '')
|
|
34
|
+
resolve(defaultValue);
|
|
35
|
+
else
|
|
36
|
+
resolve(lower === 'y' || lower === 'yes');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/** Normalize project name to valid npm scope (lowercase, no spaces). */
|
|
41
|
+
function toScope(name) {
|
|
42
|
+
const normalized = name
|
|
43
|
+
.trim()
|
|
44
|
+
.toLowerCase()
|
|
45
|
+
.replace(/\s+/g, '-')
|
|
46
|
+
.replace(/[^a-z0-9-]/g, '')
|
|
47
|
+
.replace(/-+/g, '-')
|
|
48
|
+
.replace(/^-+|-+$/g, '');
|
|
49
|
+
return normalized || 'my-app';
|
|
50
|
+
}
|
|
51
|
+
const RESERVED_SCOPES = new Set([
|
|
52
|
+
'node',
|
|
53
|
+
'npm',
|
|
54
|
+
'js',
|
|
55
|
+
'javascript',
|
|
56
|
+
'typescript',
|
|
57
|
+
'react',
|
|
58
|
+
'vue',
|
|
59
|
+
'angular',
|
|
60
|
+
'core',
|
|
61
|
+
'admin',
|
|
62
|
+
'root',
|
|
63
|
+
'system',
|
|
64
|
+
]);
|
|
65
|
+
function assertValidScope(scope) {
|
|
66
|
+
if (!scope) {
|
|
67
|
+
console.error('Invalid scope: empty value. Please provide a non-empty project name.');
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
if (RESERVED_SCOPES.has(scope)) {
|
|
71
|
+
console.error(`Invalid scope "${scope}": this name is reserved. Please choose a different project name.`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Show destination default as relative path when under cwd (e.g. "./my-app"). */
|
|
76
|
+
function defaultDirDisplay(cwd, absoluteDir) {
|
|
77
|
+
const relative = path.relative(cwd, absoluteDir);
|
|
78
|
+
if (relative && !relative.startsWith('..') && relative !== absoluteDir) {
|
|
79
|
+
return relative.startsWith('.') ? relative : `./${relative}`;
|
|
80
|
+
}
|
|
81
|
+
return absoluteDir;
|
|
82
|
+
}
|
|
83
|
+
export function createCommand() {
|
|
84
|
+
program
|
|
85
|
+
.name('create-aiko')
|
|
86
|
+
.description('Create a new aiko-boot scaffold project (monorepo: api, admin, mobile, shared)')
|
|
87
|
+
.version('0.1.0')
|
|
88
|
+
.argument('[projectNameOrPath]', 'Project name or path (e.g. my-app or ./my-app; default: prompt)')
|
|
89
|
+
.option('-n, --name <name>', 'Project name (folder + npm scope, e.g. my-app)')
|
|
90
|
+
.option('--with-base-system', 'Include base system: login, shared-auth, user/menu management later')
|
|
91
|
+
.option('--no-base-system', 'Bare project without login/auth (default when not specified)')
|
|
92
|
+
.option('--template-dir <dir>', 'Path to scaffold template (default: package bundled template, fallback to <cwd>/scaffold)')
|
|
93
|
+
.action(async (targetDirArg, options) => {
|
|
94
|
+
const cwd = process.cwd();
|
|
95
|
+
let projectName = options.name;
|
|
96
|
+
let withBaseSystem;
|
|
97
|
+
let targetDir;
|
|
98
|
+
const templateDir = options.templateDir
|
|
99
|
+
? path.resolve(cwd, options.templateDir)
|
|
100
|
+
: getTemplateDir(cwd);
|
|
101
|
+
if (!projectName || !targetDirArg) {
|
|
102
|
+
console.log('\n create-aiko\n');
|
|
103
|
+
const defaultDir = targetDirArg
|
|
104
|
+
? path.resolve(cwd, targetDirArg)
|
|
105
|
+
: path.join(cwd, projectName ? toScope(projectName) : 'my-app');
|
|
106
|
+
if (!projectName) {
|
|
107
|
+
const defaultName = targetDirArg ? path.basename(path.resolve(cwd, targetDirArg)) : 'my-app';
|
|
108
|
+
projectName = await prompt('Project name (e.g. my-app)', defaultName);
|
|
109
|
+
if (!projectName) {
|
|
110
|
+
console.error('Project name is required.');
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (!targetDirArg) {
|
|
115
|
+
const dirPromptDefault = defaultDirDisplay(cwd, defaultDir);
|
|
116
|
+
const dirAnswer = await prompt('Destination (folder path for the project)', dirPromptDefault);
|
|
117
|
+
targetDir = path.resolve(cwd, dirAnswer || defaultDir);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
targetDir = path.resolve(cwd, targetDirArg);
|
|
121
|
+
}
|
|
122
|
+
if (options.withBaseSystem === true)
|
|
123
|
+
withBaseSystem = true;
|
|
124
|
+
else if (options.baseSystem === false)
|
|
125
|
+
withBaseSystem = false;
|
|
126
|
+
else {
|
|
127
|
+
withBaseSystem = await promptConfirm('Include base system (login, shared-auth, user/menu management)?', false);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
projectName = projectName.trim();
|
|
132
|
+
targetDir = path.resolve(cwd, targetDirArg);
|
|
133
|
+
// --with-base-system -> true; --no-base-system -> baseSystem false; neither -> default bare (false)
|
|
134
|
+
withBaseSystem = options.baseSystem === false ? false : options.withBaseSystem === true;
|
|
135
|
+
}
|
|
136
|
+
const scope = toScope(projectName);
|
|
137
|
+
assertValidScope(scope);
|
|
138
|
+
// If target dir exists and is not empty, create project in a subdir named after scope
|
|
139
|
+
if (await fs.pathExists(targetDir)) {
|
|
140
|
+
const files = await fs.readdir(targetDir);
|
|
141
|
+
if (files.length > 0) {
|
|
142
|
+
const subDir = path.join(targetDir, scope);
|
|
143
|
+
console.log(`\nDestination is not empty; creating project in: ${subDir}`);
|
|
144
|
+
targetDir = subDir;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
console.log('\nOptions:');
|
|
148
|
+
console.log(' Destination:', targetDir);
|
|
149
|
+
console.log(' Scope:', scope, `(@${scope}/*)`);
|
|
150
|
+
console.log(' With base system:', withBaseSystem);
|
|
151
|
+
console.log(' Template:', templateDir);
|
|
152
|
+
try {
|
|
153
|
+
await createScaffold({
|
|
154
|
+
templateDir,
|
|
155
|
+
targetDir,
|
|
156
|
+
scope,
|
|
157
|
+
withBaseSystem,
|
|
158
|
+
});
|
|
159
|
+
console.log('\nDone. Next steps:');
|
|
160
|
+
console.log(` cd ${targetDir}`);
|
|
161
|
+
console.log(' pnpm install');
|
|
162
|
+
if (withBaseSystem) {
|
|
163
|
+
console.log(' pnpm init-db # first time');
|
|
164
|
+
console.log(' pnpm run rebuild:sqlite # if api fails with "bindings file", build better-sqlite3 native');
|
|
165
|
+
}
|
|
166
|
+
console.log(' pnpm dev');
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
console.error(err);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
program.parse();
|
|
174
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/scaffold.js
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
const COPY_IGNORE = [
|
|
4
|
+
'node_modules',
|
|
5
|
+
'.next',
|
|
6
|
+
'dist',
|
|
7
|
+
'.pnpm-store',
|
|
8
|
+
'*.tsbuildinfo',
|
|
9
|
+
'.vite',
|
|
10
|
+
'*.log',
|
|
11
|
+
];
|
|
12
|
+
/** Paths to skip when withBaseSystem is false (relative to scaffold root). */
|
|
13
|
+
const SKIP_WHEN_BARE = [
|
|
14
|
+
'packages/shared-auth',
|
|
15
|
+
'packages/api/src/controller/auth.controller.ts',
|
|
16
|
+
'packages/api/src/service/auth.service.ts',
|
|
17
|
+
'packages/api/src/dto/auth.dto.ts',
|
|
18
|
+
'packages/api/src/entity/user.entity.ts',
|
|
19
|
+
'packages/api/src/mapper/user.mapper.ts',
|
|
20
|
+
'packages/mobile/src/components/LoginForm.tsx',
|
|
21
|
+
'packages/mobile/src/pages/LoginPage.tsx',
|
|
22
|
+
'packages/mobile/src/routes/ProtectedRoute.tsx',
|
|
23
|
+
'packages/admin/src/components/LoginForm.tsx',
|
|
24
|
+
];
|
|
25
|
+
const FRAMEWORK_SCOPE = '@ai-partner-x/';
|
|
26
|
+
/** Registry version for @ai-partner-x/*,发布到中央仓库后从此版本安装;本地开发通过 pnpm.overrides 覆盖 */
|
|
27
|
+
const FRAMEWORK_REGISTRY_VERSION = '^0.1.0';
|
|
28
|
+
/** 脚手架中可能用到的框架包名(scope 后缀),用于生成 overrides */
|
|
29
|
+
const FRAMEWORK_PACKAGE_NAMES = [
|
|
30
|
+
'aiko-boot',
|
|
31
|
+
'aiko-boot-starter-cache',
|
|
32
|
+
'aiko-boot-starter-orm',
|
|
33
|
+
'aiko-boot-starter-validation',
|
|
34
|
+
'aiko-boot-starter-web',
|
|
35
|
+
'aiko-boot-starter-storage',
|
|
36
|
+
'aiko-boot-starter-security',
|
|
37
|
+
];
|
|
38
|
+
export async function createScaffold(options) {
|
|
39
|
+
const { templateDir, targetDir, scope, withBaseSystem } = options;
|
|
40
|
+
const frameworkRoot = path.dirname(templateDir);
|
|
41
|
+
if (!(await fs.pathExists(templateDir))) {
|
|
42
|
+
throw new Error(`Template directory not found: ${templateDir}. Run from ai-frist-framework root (scaffold/ must exist).`);
|
|
43
|
+
}
|
|
44
|
+
if (await fs.pathExists(targetDir)) {
|
|
45
|
+
const files = await fs.readdir(targetDir);
|
|
46
|
+
if (files.length > 0) {
|
|
47
|
+
throw new Error(`Target directory already exists and is not empty: ${targetDir}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
await fs.ensureDir(targetDir);
|
|
51
|
+
await copyRecursive(templateDir, templateDir, targetDir, withBaseSystem);
|
|
52
|
+
await replaceScopeInFiles(targetDir, scope);
|
|
53
|
+
// 依赖写 registry 版本,便于将来从中央仓库安装;本地开发通过根目录 pnpm.overrides 覆盖
|
|
54
|
+
await replaceFrameworkDepsWithRegistryVersion(targetDir);
|
|
55
|
+
await addFrameworkOverridesInRoot(targetDir, frameworkRoot);
|
|
56
|
+
// 使生成的项目成为独立 pnpm workspace,在项目目录内执行 pnpm 时不会回溯到父仓库
|
|
57
|
+
await fs.writeFile(path.join(targetDir, 'pnpm-workspace.yaml'), `packages:
|
|
58
|
+
- 'packages/*'
|
|
59
|
+
`, 'utf-8');
|
|
60
|
+
if (!withBaseSystem) {
|
|
61
|
+
await writeBareTemplates(targetDir, scope);
|
|
62
|
+
}
|
|
63
|
+
// 再次替换,确保子包中 @ai-partner-x/* 均为 registry 版本(与根 pnpm.overrides 配合)
|
|
64
|
+
await replaceFrameworkDepsWithRegistryVersion(targetDir);
|
|
65
|
+
}
|
|
66
|
+
/** 将各 package.json 中 @ai-partner-x/* 的 workspace: 协议改为 registry 版本(如 ^0.1.0),与根目录 pnpm.overrides 配合使用 */
|
|
67
|
+
async function replaceFrameworkDepsWithRegistryVersion(targetDir) {
|
|
68
|
+
const packageJsonPaths = [];
|
|
69
|
+
async function collect(dir) {
|
|
70
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
71
|
+
for (const e of entries) {
|
|
72
|
+
const full = path.join(dir, e.name);
|
|
73
|
+
if (e.name === 'package.json') {
|
|
74
|
+
packageJsonPaths.push(full);
|
|
75
|
+
}
|
|
76
|
+
else if (e.isDirectory() && e.name !== 'node_modules' && !e.name.startsWith('.')) {
|
|
77
|
+
await collect(full);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
await collect(targetDir);
|
|
82
|
+
for (const pkgPath of packageJsonPaths) {
|
|
83
|
+
const pkg = await fs.readJson(pkgPath);
|
|
84
|
+
let changed = false;
|
|
85
|
+
for (const key of ['dependencies', 'devDependencies']) {
|
|
86
|
+
if (!pkg[key])
|
|
87
|
+
continue;
|
|
88
|
+
for (const [name, value] of Object.entries(pkg[key])) {
|
|
89
|
+
const isWorkspaceProtocol = typeof value === 'string' && (value === 'workspace:*' || value.startsWith('workspace:'));
|
|
90
|
+
if (name.startsWith(FRAMEWORK_SCOPE) && isWorkspaceProtocol) {
|
|
91
|
+
pkg[key][name] = FRAMEWORK_REGISTRY_VERSION;
|
|
92
|
+
changed = true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (changed)
|
|
97
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** 在根 package.json 中增加 pnpm.overrides,指向本地框架路径;需要从中央仓库安装时删除该段即可 */
|
|
101
|
+
async function addFrameworkOverridesInRoot(targetDir, frameworkRoot) {
|
|
102
|
+
const frameworkPackagesDir = path.join(frameworkRoot, 'packages');
|
|
103
|
+
if (!(await fs.pathExists(frameworkPackagesDir))) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const rootPkgPath = path.join(targetDir, 'package.json');
|
|
107
|
+
const rootPkg = await fs.readJson(rootPkgPath);
|
|
108
|
+
const relativeToFramework = path.relative(targetDir, frameworkRoot).replace(/\\/g, '/');
|
|
109
|
+
const filePrefix = relativeToFramework.startsWith('.') ? relativeToFramework : `./${relativeToFramework}`;
|
|
110
|
+
const overrides = {};
|
|
111
|
+
for (const pkgName of FRAMEWORK_PACKAGE_NAMES) {
|
|
112
|
+
overrides[`${FRAMEWORK_SCOPE}${pkgName}`] = `file:${filePrefix}/packages/${pkgName}`;
|
|
113
|
+
}
|
|
114
|
+
rootPkg.pnpm = { ...rootPkg.pnpm, overrides };
|
|
115
|
+
await fs.writeJson(rootPkgPath, rootPkg, { spaces: 2 });
|
|
116
|
+
}
|
|
117
|
+
function shouldIgnore(name) {
|
|
118
|
+
if (COPY_IGNORE.some((x) => x.startsWith('*') && name.endsWith(x.slice(1))))
|
|
119
|
+
return true;
|
|
120
|
+
if (COPY_IGNORE.includes(name))
|
|
121
|
+
return true;
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
function shouldSkipWhenBare(relativePath) {
|
|
125
|
+
const normalized = relativePath.replace(/\\/g, '/');
|
|
126
|
+
return SKIP_WHEN_BARE.some((skip) => normalized === skip || normalized.startsWith(skip + '/'));
|
|
127
|
+
}
|
|
128
|
+
async function copyRecursive(templateRoot, src, dest, withBaseSystem) {
|
|
129
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
130
|
+
for (const entry of entries) {
|
|
131
|
+
const srcPath = path.join(src, entry.name);
|
|
132
|
+
const destPath = path.join(dest, entry.name);
|
|
133
|
+
const relativePath = path.relative(templateRoot, srcPath).replace(/\\/g, '/');
|
|
134
|
+
if (shouldIgnore(entry.name))
|
|
135
|
+
continue;
|
|
136
|
+
if (!withBaseSystem && shouldSkipWhenBare(relativePath))
|
|
137
|
+
continue;
|
|
138
|
+
if (entry.isDirectory()) {
|
|
139
|
+
await fs.ensureDir(destPath);
|
|
140
|
+
await copyRecursive(templateRoot, srcPath, destPath, withBaseSystem);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
await fs.copy(srcPath, destPath);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function replaceScopeInFiles(dir, scope) {
|
|
148
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
149
|
+
for (const entry of entries) {
|
|
150
|
+
const full = path.join(dir, entry.name);
|
|
151
|
+
if (entry.isDirectory() && !shouldIgnore(entry.name)) {
|
|
152
|
+
await replaceScopeInFiles(full, scope);
|
|
153
|
+
}
|
|
154
|
+
else if (entry.isFile()) {
|
|
155
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
156
|
+
if (!['.ts', '.tsx', '.js', '.jsx', '.json', '.md', '.mjs', '.cjs', '.css', '.html'].includes(ext))
|
|
157
|
+
continue;
|
|
158
|
+
let content = await fs.readFile(full, 'utf-8');
|
|
159
|
+
const before = content;
|
|
160
|
+
content = content.replace(/@scaffold\//g, `@${scope}/`);
|
|
161
|
+
content = content.replace(/"@scaffold\//g, `"@${scope}/`);
|
|
162
|
+
content = content.replace(/scaffold-monorepo/g, `${scope}-monorepo`);
|
|
163
|
+
content = content.replace(/--filter "@scaffold\/\*"/g, `--filter "@${scope}/*"`);
|
|
164
|
+
content = content.replace(/-F @scaffold\//g, `-F @${scope}/`);
|
|
165
|
+
if (content !== before)
|
|
166
|
+
await fs.writeFile(full, content, 'utf-8');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/** Write bare (no auth) template files when withBaseSystem is false. */
|
|
171
|
+
async function writeBareTemplates(targetDir, scope) {
|
|
172
|
+
const packagesDir = path.join(targetDir, 'packages');
|
|
173
|
+
await fs.writeFile(path.join(packagesDir, 'api/src/scripts/init-db.ts'), `/**
|
|
174
|
+
* Initialize SQLite database (bare scaffold, no user table).
|
|
175
|
+
*/
|
|
176
|
+
import { fileURLToPath } from 'url';
|
|
177
|
+
import { dirname, join } from 'path';
|
|
178
|
+
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
179
|
+
// @ts-expect-error sql.js has no types
|
|
180
|
+
import initSqlJs from 'sql.js';
|
|
181
|
+
|
|
182
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
183
|
+
const dbPath = join(__dirname, '../../data/app.db');
|
|
184
|
+
|
|
185
|
+
console.log('📁 Database path:', dbPath);
|
|
186
|
+
|
|
187
|
+
const SQL = await initSqlJs();
|
|
188
|
+
const db = new SQL.Database();
|
|
189
|
+
|
|
190
|
+
// Add your tables here when needed.
|
|
191
|
+
|
|
192
|
+
const data = db.export();
|
|
193
|
+
db.close();
|
|
194
|
+
|
|
195
|
+
const dir = dirname(dbPath);
|
|
196
|
+
if (!existsSync(dir)) {
|
|
197
|
+
mkdirSync(dir, { recursive: true });
|
|
198
|
+
}
|
|
199
|
+
writeFileSync(dbPath, Buffer.from(data));
|
|
200
|
+
|
|
201
|
+
console.log('\\n🎉 Database initialization complete!');
|
|
202
|
+
`, 'utf-8');
|
|
203
|
+
await fs.writeFile(path.join(packagesDir, 'shared/src/constants.ts'), `/** API base URL env key (admin: VITE_API_URL, mobile: etc.) */
|
|
204
|
+
export const API_BASE_URL_KEY = 'API_URL';
|
|
205
|
+
|
|
206
|
+
/** Default API base URL */
|
|
207
|
+
export const DEFAULT_API_BASE_URL = 'http://localhost:3001';
|
|
208
|
+
`, 'utf-8');
|
|
209
|
+
await fs.writeFile(path.join(packagesDir, 'shared/src/index.ts'), `export {
|
|
210
|
+
API_BASE_URL_KEY,
|
|
211
|
+
DEFAULT_API_BASE_URL,
|
|
212
|
+
} from './constants';
|
|
213
|
+
`, 'utf-8');
|
|
214
|
+
const mobileMain = `import { StrictMode } from 'react';
|
|
215
|
+
import { createRoot } from 'react-dom/client';
|
|
216
|
+
import App from './App';
|
|
217
|
+
import './app/globals.css';
|
|
218
|
+
|
|
219
|
+
createRoot(document.getElementById('root')!).render(
|
|
220
|
+
<StrictMode>
|
|
221
|
+
<App />
|
|
222
|
+
</StrictMode>
|
|
223
|
+
);
|
|
224
|
+
`;
|
|
225
|
+
await fs.writeFile(path.join(packagesDir, 'mobile/src/main.tsx'), mobileMain, 'utf-8');
|
|
226
|
+
const mobileRoutesIndex = `import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
|
227
|
+
import { HomePage } from '@/pages/HomePage';
|
|
228
|
+
import { ROUTES } from './routes';
|
|
229
|
+
|
|
230
|
+
export function AppRouter() {
|
|
231
|
+
return (
|
|
232
|
+
<BrowserRouter>
|
|
233
|
+
<Routes>
|
|
234
|
+
<Route path={ROUTES.HOME} element={<HomePage />} />
|
|
235
|
+
<Route path="*" element={<Navigate to={ROUTES.HOME} replace />} />
|
|
236
|
+
</Routes>
|
|
237
|
+
</BrowserRouter>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
`;
|
|
241
|
+
await fs.writeFile(path.join(packagesDir, 'mobile/src/routes/index.tsx'), mobileRoutesIndex, 'utf-8');
|
|
242
|
+
await fs.writeFile(path.join(packagesDir, 'mobile/src/routes/routes.ts'), `export const ROUTES = {
|
|
243
|
+
HOME: '/',
|
|
244
|
+
} as const;
|
|
245
|
+
|
|
246
|
+
export type RoutePath = (typeof ROUTES)[keyof typeof ROUTES];
|
|
247
|
+
`, 'utf-8');
|
|
248
|
+
const homePage = `/**
|
|
249
|
+
* Mobile home page (bare scaffold).
|
|
250
|
+
*/
|
|
251
|
+
export function HomePage() {
|
|
252
|
+
return (
|
|
253
|
+
<div className="min-h-screen bg-gray-50 flex flex-col">
|
|
254
|
+
<header className="flex items-center justify-between px-4 py-3 bg-white border-b border-gray-200 safe-area-top">
|
|
255
|
+
<span className="text-lg font-medium text-gray-800">AIKO-BOOT</span>
|
|
256
|
+
</header>
|
|
257
|
+
<main className="flex-1 flex flex-col items-center justify-center px-4">
|
|
258
|
+
<h1 className="text-2xl font-semibold text-gray-900 mb-1">AIKO-BOOT</h1>
|
|
259
|
+
<p className="text-gray-500 text-sm">Scaffold ready. Add your pages.</p>
|
|
260
|
+
</main>
|
|
261
|
+
</div>
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
`;
|
|
265
|
+
await fs.writeFile(path.join(packagesDir, 'mobile/src/pages/HomePage.tsx'), homePage, 'utf-8');
|
|
266
|
+
await fs.writeFile(path.join(packagesDir, 'mobile/src/pages/index.ts'), `export { HomePage } from './HomePage';
|
|
267
|
+
`, 'utf-8');
|
|
268
|
+
const adminMain = `import { StrictMode } from 'react';
|
|
269
|
+
import { createRoot } from 'react-dom/client';
|
|
270
|
+
import App from './App';
|
|
271
|
+
import './index.css';
|
|
272
|
+
|
|
273
|
+
createRoot(document.getElementById('root')!).render(
|
|
274
|
+
<StrictMode>
|
|
275
|
+
<App />
|
|
276
|
+
</StrictMode>
|
|
277
|
+
);
|
|
278
|
+
`;
|
|
279
|
+
await fs.writeFile(path.join(packagesDir, 'admin/src/main.tsx'), adminMain, 'utf-8');
|
|
280
|
+
const adminApp = `export default function App() {
|
|
281
|
+
return (
|
|
282
|
+
<div className="min-h-screen flex items-center justify-center bg-gray-100">
|
|
283
|
+
<p className="text-gray-600">Scaffold Admin — add your pages.</p>
|
|
284
|
+
</div>
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
`;
|
|
288
|
+
await fs.writeFile(path.join(packagesDir, 'admin/src/App.tsx'), adminApp, 'utf-8');
|
|
289
|
+
const sharedAuthPkg = `@${scope}/shared-auth`;
|
|
290
|
+
const mobilePkgPath = path.join(packagesDir, 'mobile/package.json');
|
|
291
|
+
const mobilePkg = await fs.readJson(mobilePkgPath);
|
|
292
|
+
if (mobilePkg.dependencies) {
|
|
293
|
+
delete mobilePkg.dependencies[sharedAuthPkg];
|
|
294
|
+
mobilePkg.dependencies = Object.fromEntries(Object.entries(mobilePkg.dependencies).filter(([k]) => k !== sharedAuthPkg));
|
|
295
|
+
}
|
|
296
|
+
await fs.writeJson(mobilePkgPath, mobilePkg, { spaces: 2 });
|
|
297
|
+
const adminPkgPath = path.join(packagesDir, 'admin/package.json');
|
|
298
|
+
const adminPkg = await fs.readJson(adminPkgPath);
|
|
299
|
+
if (adminPkg.dependencies) {
|
|
300
|
+
delete adminPkg.dependencies[sharedAuthPkg];
|
|
301
|
+
adminPkg.dependencies = Object.fromEntries(Object.entries(adminPkg.dependencies).filter(([k]) => k !== sharedAuthPkg));
|
|
302
|
+
}
|
|
303
|
+
await fs.writeJson(adminPkgPath, adminPkg, { spaces: 2 });
|
|
304
|
+
const mobileVitePath = path.join(packagesDir, 'mobile/vite.config.ts');
|
|
305
|
+
if (await fs.pathExists(mobileVitePath)) {
|
|
306
|
+
let viteContent = await fs.readFile(mobileVitePath, 'utf-8');
|
|
307
|
+
viteContent = viteContent.replace(/\s*exclude:\s*\[\s*'@[^']+\/shared-auth'\s*\],?\s*/g, '\n');
|
|
308
|
+
await fs.writeFile(mobileVitePath, viteContent, 'utf-8');
|
|
309
|
+
}
|
|
310
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-aiko",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI to create aiko-boot scaffold project (monorepo with api, admin, mobile, shared)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"create-aiko": "./dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"template"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc && node ./scripts/build-template.mjs",
|
|
23
|
+
"dev": "tsc --watch"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"commander": "^12.1.0",
|
|
27
|
+
"fs-extra": "^11.2.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/fs-extra": "^11.0.4",
|
|
31
|
+
"@types/node": "^20.11.0",
|
|
32
|
+
"typescript": "^5.3.0"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18.0.0"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Scaffold Monorepo
|
|
2
|
+
|
|
3
|
+
用于发布项目初始化模板的干净脚手架,包含:
|
|
4
|
+
|
|
5
|
+
- `api`
|
|
6
|
+
- `admin`
|
|
7
|
+
- `mobile`
|
|
8
|
+
- `shared`
|
|
9
|
+
- `shared-auth`
|
|
10
|
+
|
|
11
|
+
这份模板聚焦在“可运行项目骨架”和“可复用后台组件能力”,不再承载需求驱动生成器、spec 规则和实验性产物。
|
|
12
|
+
|
|
13
|
+
## 快速启动
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cd scaffold
|
|
17
|
+
pnpm install
|
|
18
|
+
pnpm init-db
|
|
19
|
+
pnpm dev
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
默认地址:
|
|
23
|
+
|
|
24
|
+
- API: `http://localhost:3001/api`
|
|
25
|
+
- Admin: `http://localhost:5173`
|
|
26
|
+
- Mobile: `http://localhost:3002`
|
|
27
|
+
|
|
28
|
+
默认测试账号:
|
|
29
|
+
|
|
30
|
+
- 用户名:`admin`
|
|
31
|
+
- 密码:`admin123`
|
|
32
|
+
|
|
33
|
+
## 目录结构
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
scaffold/
|
|
37
|
+
├── packages/
|
|
38
|
+
│ ├── api
|
|
39
|
+
│ ├── admin
|
|
40
|
+
│ ├── mobile
|
|
41
|
+
│ ├── shared
|
|
42
|
+
│ └── shared-auth
|
|
43
|
+
├── scripts/
|
|
44
|
+
└── package.json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 常用命令
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pnpm dev
|
|
51
|
+
pnpm dev:api
|
|
52
|
+
pnpm dev:admin
|
|
53
|
+
pnpm dev:mobile
|
|
54
|
+
pnpm init-db
|
|
55
|
+
pnpm build
|
|
56
|
+
pnpm lint
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
如果 `better-sqlite3` 原生模块异常,可执行:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pnpm run rebuild:sqlite
|
|
63
|
+
```
|