@oj-module/cli 0.2.18 → 0.2.23
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/devkit/README.md +4 -4
- package/devkit/SKILL.md +41 -37
- package/devkit/framework-dev.md +6 -5
- package/devkit/manual.md +352 -168
- package/devkit/menu-rules.md +3 -3
- package/devkit/module-dev-and-cdn-playground.md +31 -22
- package/devkit/sql-builder-missing-capabilities-pr.md +11 -6
- package/package.json +2 -2
- package/shell-dist/assets/runtime.js +59 -34
- package/shell-dist/index.html +2 -2
- package/shell-dist/versions.json +3 -3
- package/src/__tests__/build-backend.test.ts +54 -0
- package/src/__tests__/init-packagejson.test.ts +29 -0
- package/src/__tests__/layout.test.ts +176 -0
- package/src/__tests__/module-list.test.ts +66 -0
- package/src/__tests__/oj-caps.test.ts +114 -0
- package/src/alias-paths.ts +102 -0
- package/src/args.ts +54 -4
- package/src/build.ts +41 -9
- package/src/builtin.ts +93 -0
- package/src/bundle.ts +254 -38
- package/src/config.ts +82 -9
- package/src/contract/__tests__/market-client.integration.test.ts +14 -14
- package/src/contract/check.ts +56 -28
- package/src/contract/emit-stub.ts +21 -9
- package/src/contract/handler-scan.ts +19 -10
- package/src/contract/run.ts +14 -4
- package/src/dev.ts +80 -9
- package/src/index.ts +27 -7
- package/src/info.ts +69 -10
- package/src/init.ts +114 -28
- package/src/layout.ts +282 -24
- package/src/lifecycle/__tests__/args.test.ts +20 -0
- package/src/lifecycle/__tests__/builtin.test.ts +97 -0
- package/src/lifecycle/__tests__/bundle.test.ts +184 -6
- package/src/lifecycle/__tests__/manifest-contract.test.ts +2 -2
- package/src/lifecycle/__tests__/new.test.ts +129 -0
- package/src/lifecycle/__tests__/smoke.test.ts +2 -1
- package/src/manifest-contract.ts +16 -3
- package/src/market/__tests__/bootstrap.test.ts +17 -9
- package/src/market/bootstrap.ts +30 -17
- package/src/market/publish.ts +10 -2
- package/src/new.ts +208 -0
- package/src/oj-caps.ts +133 -0
- package/src/oj.ts +27 -4
- package/src/preview-config.ts +1 -1
- package/src/preview.ts +2 -1
- package/src/site.ts +2 -2
- package/src/usage.ts +24 -12
- package/src/vendor.ts +5 -1
- package/templates/{api/config.yaml → config.yaml} +13 -8
- package/templates/global.d.ts +7 -6
- package/templates/init.include.yaml +41 -36
- package/templates/package.json +3 -1
- package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/installer.ts +4 -0
- package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/smoke.ts +21 -2
- package/templates/{api/src/_platform → src/_platform/api}/fixtures/l1-test-user.sql +4 -5
- package/templates/{api/src/_platform → src/_platform/api}/market/api.ts +25 -15
- package/templates/src/_platform/api/seed.mysql.sql +16 -0
- package/templates/src/_platform/api/seed.pg.sql +16 -0
- package/templates/{api/src/_platform → src/_platform/api}/seed.sql +16 -5
- package/templates/{api/src/_platform → src/_platform/api}/solutions/api.ts +7 -3
- package/templates/{api/src/auth → src/auth/api}/_shared/login-throttle.ts +2 -2
- package/templates/{api/src/auth → src/auth/api}/_shared/session.ts +2 -2
- package/templates/{api/src/auth → src/auth/api}/api.ts +1 -1
- package/templates/{api/src/auth → src/auth/api}/ldap-login/api.ts +1 -1
- package/templates/src/auth/api/manifest.yaml +6 -0
- package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/InstallDialog.test.tsx +53 -1
- package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/marketClient.fake.ts +6 -0
- package/templates/{web/src/oj-market → src/oj-market/web}/api/client.ts +5 -0
- package/templates/{web/src/oj-market → src/oj-market/web}/api/context.ts +2 -2
- package/templates/{web/src/oj-market → src/oj-market/web}/api/defaultClient.ts +7 -1
- package/templates/src/oj-market/web/components/InstallDialog.tsx +194 -0
- package/templates/{web/src/oj-market → src/oj-market/web}/components/RollbackDialog.tsx +7 -3
- package/templates/{web/src/oj-market → src/oj-market/web}/components/UninstallConfirm.tsx +18 -7
- package/templates/{web/src/oj-market → src/oj-market/web}/hooks/useMarket.ts +29 -12
- package/templates/{web/src/oj-market → src/oj-market/web}/locales/en-US.json +10 -1
- package/templates/{web/src/oj-market → src/oj-market/web}/locales/zh-CN.json +10 -1
- package/templates/{web/src/oj-market → src/oj-market/web}/types.ts +15 -2
- package/templates/{api/src/system → src/system/api}/login-methods/api.ts +2 -2
- package/templates/src/system/api/manifest.yaml +6 -0
- package/templates/{api/src/system → src/system/api}/oplog/api.ts +4 -4
- package/templates/{api/src/system → src/system/api}/permissions/api.ts +3 -3
- package/templates/{api/src/system → src/system/api}/roles/api.ts +3 -3
- package/templates/{api/src/system → src/system/api}/roles/bindings/api.ts +3 -3
- package/templates/{api/src/system → src/system/api}/settings/api.ts +3 -3
- package/templates/{api/src/system → src/system/api}/users/api.ts +3 -3
- package/templates/{api/src/system → src/system/api}/users/forbidden/api.ts +3 -3
- package/templates/{api/src/system → src/system/api}/users/password/api.ts +3 -3
- package/templates/{api/src/system → src/system/api}/users/roles/api.ts +3 -3
- package/templates/{web/src/system → src/system/web}/locales/en-US.json +1 -1
- package/templates/{web/src/system → src/system/web}/locales/zh-CN.json +1 -1
- package/templates/src/tsconfig.json +16 -0
- package/templates/{api/tests → tests}/auth-ldap.spec.ts +2 -2
- package/templates/{api/tests → tests}/auth-session.spec.ts +3 -3
- package/templates/{api/tests → tests}/auth-throttle.spec.ts +1 -1
- package/templates/tests/fixture.ts +434 -0
- package/templates/{api/tests → tests}/guard.spec.ts +1 -1
- package/templates/{api/tests → tests}/l1-builder.test.ts +2 -2
- package/templates/{api/tests → tests}/lifecycle/atomic-state.spec.ts +3 -3
- package/templates/{api/tests → tests}/lifecycle/bootstrap.spec.ts +8 -4
- package/templates/{api/tests → tests}/lifecycle/deploy-smoke.spec.ts +31 -8
- package/templates/{api/tests → tests}/lifecycle/installer.spec.ts +15 -15
- package/templates/{api/tests → tests}/lifecycle/market-api.spec.ts +30 -29
- package/templates/{api/tests → tests}/lifecycle/migrate.spec.ts +6 -6
- package/templates/{api/tests → tests}/lifecycle/preflight.spec.ts +4 -4
- package/templates/{api/tests → tests}/lifecycle/register.spec.ts +1 -1
- package/templates/{api/tests → tests}/lifecycle/semver.spec.ts +1 -1
- package/templates/{api/tests → tests}/lifecycle/sha384.spec.ts +1 -1
- package/templates/{api/tests → tests}/lifecycle/sig-fixtures.spec.ts +1 -1
- package/templates/{api/tests → tests}/lifecycle/solutions-api.spec.ts +1 -1
- package/templates/{api/tests → tests}/lifecycle/steps-fs.spec.ts +12 -12
- package/templates/{api/tests → tests}/lifecycle/text-edit.spec.ts +1 -1
- package/templates/{api/tests → tests}/lifecycle/tgz-guard.spec.ts +1 -1
- package/templates/{api/tests → tests}/lifecycle/trust.spec.ts +5 -5
- package/templates/{api/tests → tests}/lifecycle/uninstall.spec.ts +7 -7
- package/templates/{api/tests → tests}/lifecycle/verify-checks.spec.ts +9 -6
- package/templates/{api/tests → tests}/lifecycle/verify-report.spec.ts +7 -5
- package/templates/{api/tests → tests}/query-utils.spec.ts +1 -1
- package/templates/{api/tests → tests}/rbac-audit.spec.ts +1 -1
- package/templates/{api/tests → tests}/rbac-contacts.spec.ts +2 -2
- package/templates/{api/tests → tests}/rbac-guard.spec.ts +1 -1
- package/templates/{api/tests → tests}/rbac-roles.spec.ts +2 -2
- package/templates/{api/tests → tests}/rbac-seed.spec.ts +13 -11
- package/templates/{api/tests → tests}/rbac-settings.spec.ts +2 -2
- package/templates/{api/tests → tests}/rbac-solution-perms.spec.ts +1 -1
- package/templates/{api/tests → tests}/rbac-users.spec.ts +3 -3
- package/templates/tests/seed-role-accounts.spec.ts +87 -0
- package/templates/tsconfig.json +5 -1
- package/templates/tsconfig.paths.json +19 -0
- package/templates/types/oj-modules.d.ts +17 -0
- package/templates/vitest.config.ts +22 -0
- package/templates/web.config.ts +23 -13
- package/vendor/host-versions.json +3 -3
- package/templates/api/src/_platform/seed.mysql.sql +0 -8
- package/templates/api/src/_platform/seed.pg.sql +0 -8
- package/templates/api/src/auth/manifest.yaml +0 -5
- package/templates/api/src/system/manifest.yaml +0 -5
- package/templates/api/tests/fixture.ts +0 -58
- package/templates/api/tsconfig.json +0 -8
- package/templates/api/vitest.config.ts +0 -12
- package/templates/logs/task-events.jsonl +0 -0
- package/templates/web/src/oj-market/components/InstallDialog.tsx +0 -112
- /package/templates/{api/.ojm-api-exempt.json → .ojm-api-exempt.json} +0 -0
- /package/templates/{api/seed.mysql.sql → seed.mysql.sql} +0 -0
- /package/templates/{api/seed.pg.sql → seed.pg.sql} +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/README.md +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/atomic.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/history.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/lock.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/semver.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/sha384.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/state.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/deploy-web.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/download.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/extract.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/migrate.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/preflight.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/register.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/reload.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/verify.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/tasks.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/text-edit.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/tgz-guard.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/trust.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/types.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/uninstall.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/verify-checks.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/verify-report.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/respond.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/contacts.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/roles.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/settings.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/solution-perms.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/users.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/access.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/audit.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/guard.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/oplog.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/query.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/rate.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/site-facade.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/contacts/api.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/contacts/detail/api.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/manifest.yaml +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/market/bootstrap.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0001__create_users.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0001__create_users.pg.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0001__create_users.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0002__add_users_avatar_base64.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0002__add_users_avatar_base64.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0003__users_source_display_name.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0003__users_source_display_name.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0004__sys_rbac.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0004__sys_rbac.pg.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0004__sys_rbac.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0005__users_must_change.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0005__users_must_change.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0006__users_is_forbidden.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0006__users_is_forbidden.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0007__users_email.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0008__sys_refresh_token.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0008__sys_refresh_token.pg.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0008__sys_refresh_token.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0009__sys_throttle.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0009__sys_throttle.pg.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0009__sys_throttle.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0010__sys_solution_route.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0010__sys_solution_route.pg.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0010__sys_solution_route.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0011__sys_setting_group_label.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0011__sys_setting_group_label.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0012__market_history.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0012__market_history.pg.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0012__market_history.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0013__users_profile.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/schema.yaml +0 -0
- /package/templates/{web/src/_platform → src/_platform/web}/__client/api.schemas.ts +0 -0
- /package/templates/{web/src/_platform → src/_platform/web}/__client/api.ts +0 -0
- /package/templates/{web/src/about → src/about/web}/entry.ts +0 -0
- /package/templates/{web/src/about → src/about/web}/locales/en-US.json +0 -0
- /package/templates/{web/src/about → src/about/web}/locales/zh-CN.json +0 -0
- /package/templates/{web/src/about → src/about/web}/pages/constants.ts +0 -0
- /package/templates/{web/src/about → src/about/web}/pages/index.tsx +0 -0
- /package/templates/{web/src/access → src/access/web}/entry.ts +0 -0
- /package/templates/{web/src/access → src/access/web}/locales/en-US.json +0 -0
- /package/templates/{web/src/access → src/access/web}/locales/zh-CN.json +0 -0
- /package/templates/{web/src/access → src/access/web}/pages/access-mode/index.tsx +0 -0
- /package/templates/{web/src/access → src/access/web}/pages/admin-visible/index.tsx +0 -0
- /package/templates/{web/src/access → src/access/web}/pages/button-control/index.tsx +0 -0
- /package/templates/{web/src/access → src/access/web}/pages/common-visible/index.tsx +0 -0
- /package/templates/{web/src/access → src/access/web}/pages/page-control/index.tsx +0 -0
- /package/templates/{api/src/auth → src/auth/api}/_shared/ldap-login.ts +0 -0
- /package/templates/{api/src/auth → src/auth/api}/_shared/ldap.ts +0 -0
- /package/templates/{web/src/auth → src/auth/web}/__client/api.schemas.ts +0 -0
- /package/templates/{web/src/auth → src/auth/web}/__client/api.ts +0 -0
- /package/templates/{api/src/demo → src/demo/api}/manifest.yaml +0 -0
- /package/templates/{api/src/demo → src/demo/api}/migrations/0001__create_todos.sql +0 -0
- /package/templates/{api/src/demo → src/demo/api}/schema.yaml +0 -0
- /package/templates/{api/src/demo → src/demo/api}/seed.sql +0 -0
- /package/templates/{api/src/demo → src/demo/api}/todos/api.ts +0 -0
- /package/templates/{web/src/demo → src/demo/web}/__client/api.schemas.ts +0 -0
- /package/templates/{web/src/demo → src/demo/web}/__client/api.ts +0 -0
- /package/templates/{web/src/demo → src/demo/web}/entry.ts +0 -0
- /package/templates/{web/src/demo → src/demo/web}/locales/en-US.json +0 -0
- /package/templates/{web/src/demo → src/demo/web}/locales/zh-CN.json +0 -0
- /package/templates/{web/src/demo → src/demo/web}/pages/index.tsx +0 -0
- /package/templates/{web/src/exception → src/exception/web}/entry.ts +0 -0
- /package/templates/{web/src/exception → src/exception/web}/locales/en-US.json +0 -0
- /package/templates/{web/src/exception → src/exception/web}/locales/zh-CN.json +0 -0
- /package/templates/{web/src/exception → src/exception/web}/pages/403/index.tsx +0 -0
- /package/templates/{web/src/exception → src/exception/web}/pages/404/index.tsx +0 -0
- /package/templates/{web/src/exception → src/exception/web}/pages/500/index.tsx +0 -0
- /package/templates/{web/src/exception → src/exception/web}/pages/unknown-component/index.tsx +0 -0
- /package/templates/{api/src/home → src/home/api}/line/api.ts +0 -0
- /package/templates/{api/src/home → src/home/api}/manifest.yaml +0 -0
- /package/templates/{api/src/home → src/home/api}/migrations/0001__create_home_tables.sql +0 -0
- /package/templates/{api/src/home → src/home/api}/pie/api.ts +0 -0
- /package/templates/{api/src/home → src/home/api}/schema.yaml +0 -0
- /package/templates/{api/src/home → src/home/api}/seed.sql +0 -0
- /package/templates/{web/src/home → src/home/web}/__client/api.schemas.ts +0 -0
- /package/templates/{web/src/home → src/home/web}/__client/api.ts +0 -0
- /package/templates/{web/src/home → src/home/web}/entry.ts +0 -0
- /package/templates/{web/src/home → src/home/web}/locales/en-US.json +0 -0
- /package/templates/{web/src/home → src/home/web}/locales/zh-CN.json +0 -0
- /package/templates/{web/src/home → src/home/web}/pages/components/bar-chart.tsx +0 -0
- /package/templates/{web/src/home → src/home/web}/pages/components/card-list.tsx +0 -0
- /package/templates/{web/src/home → src/home/web}/pages/components/line-chart.tsx +0 -0
- /package/templates/{web/src/home → src/home/web}/pages/components/pie-chart.tsx +0 -0
- /package/templates/{web/src/home → src/home/web}/pages/index.tsx +0 -0
- /package/templates/{web/src/login → src/login/web}/entry.ts +0 -0
- /package/templates/{web/src/login → src/login/web}/locales/en-US.json +0 -0
- /package/templates/{web/src/login → src/login/web}/locales/zh-CN.json +0 -0
- /package/templates/{web/src/login → src/login/web}/pages/change-password.tsx +0 -0
- /package/templates/{web/src/login → src/login/web}/pages/login.tsx +0 -0
- /package/templates/{api/src/notification → src/notification/api}/_shared/notify.ts +0 -0
- /package/templates/{api/src/notification → src/notification/api}/api.ts +0 -0
- /package/templates/{api/src/notification → src/notification/api}/del/api.ts +0 -0
- /package/templates/{api/src/notification → src/notification/api}/manifest.yaml +0 -0
- /package/templates/{api/src/notification → src/notification/api}/migrations/0001__create_notifications.sql +0 -0
- /package/templates/{api/src/notification → src/notification/api}/migrations/0002__notifications_user.sql +0 -0
- /package/templates/{api/src/notification → src/notification/api}/migrations/0003__notifications_broadcast.sql +0 -0
- /package/templates/{api/src/notification → src/notification/api}/migrations/0004__notifications_broadcast_hidden.sql +0 -0
- /package/templates/{api/src/notification → src/notification/api}/schema.yaml +0 -0
- /package/templates/{api/src/notification → src/notification/api}/seed.sql +0 -0
- /package/templates/{web/src/notification → src/notification/web}/__client/api.schemas.ts +0 -0
- /package/templates/{web/src/notification → src/notification/web}/__client/api.ts +0 -0
- /package/templates/{web/src/notification → src/notification/web}/entry.ts +0 -0
- /package/templates/{web/src/notification → src/notification/web}/locales/en-US.json +0 -0
- /package/templates/{web/src/notification → src/notification/web}/locales/zh-CN.json +0 -0
- /package/templates/{web/src/notification → src/notification/web}/pages/center/index.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/CardGrid.test.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/HistoryDrawer.test.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/RollbackDialog.test.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/UninstallConfirm.test.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/poll.test.ts +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/render.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/runtime-stub.ts +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/semver.test.ts +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/api/MarketClientProvider.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/components/CardGrid.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/components/CardItem.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/components/DetailDrawer.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/components/HistoryDrawer.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/entry.ts +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/pages/MarketApp.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/utils/poll.ts +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/utils/semver.ts +0 -0
- /package/templates/{web/src/outside → src/outside/web}/entry.ts +0 -0
- /package/templates/{web/src/outside → src/outside/web}/locales/en-US.json +0 -0
- /package/templates/{web/src/outside → src/outside/web}/locales/zh-CN.json +0 -0
- /package/templates/{api/src/personal-center → src/personal-center/api}/manifest.yaml +0 -0
- /package/templates/{api/src/personal-center → src/personal-center/api}/profile/api.ts +0 -0
- /package/templates/{api/src/personal-center → src/personal-center/api}/upload/api.ts +0 -0
- /package/templates/{web/src/personal-center → src/personal-center/web}/__client/api.schemas.ts +0 -0
- /package/templates/{web/src/personal-center → src/personal-center/web}/__client/api.ts +0 -0
- /package/templates/{web/src/personal-center → src/personal-center/web}/entry.ts +0 -0
- /package/templates/{web/src/personal-center → src/personal-center/web}/locales/en-US.json +0 -0
- /package/templates/{web/src/personal-center → src/personal-center/web}/locales/zh-CN.json +0 -0
- /package/templates/{web/src/personal-center → src/personal-center/web}/pages/my-profile/index.tsx +0 -0
- /package/templates/{web/src/route-nest → src/route-nest/web}/entry.ts +0 -0
- /package/templates/{web/src/route-nest → src/route-nest/web}/locales/en-US.json +0 -0
- /package/templates/{web/src/route-nest → src/route-nest/web}/locales/zh-CN.json +0 -0
- /package/templates/{web/src/route-nest → src/route-nest/web}/pages/menu1/menu1-1/index.tsx +0 -0
- /package/templates/{web/src/route-nest → src/route-nest/web}/pages/menu1/menu1-2/index.tsx +0 -0
- /package/templates/{web/src/route-nest → src/route-nest/web}/pages/menu2/index.tsx +0 -0
- /package/templates/{web/src → src}/styles.css +0 -0
- /package/templates/{web/src/system → src/system/web}/__client/api.schemas.ts +0 -0
- /package/templates/{web/src/system → src/system/web}/__client/api.ts +0 -0
- /package/templates/{web/src/system → src/system/web}/entry.ts +0 -0
- /package/templates/{web/src/system → src/system/web}/pages/errors.ts +0 -0
- /package/templates/{web/src/system → src/system/web}/pages/forbidden.tsx +0 -0
- /package/templates/{web/src/system → src/system/web}/pages/login-methods/index.tsx +0 -0
- /package/templates/{web/src/system → src/system/web}/pages/oplog/index.tsx +0 -0
- /package/templates/{web/src/system → src/system/web}/pages/roles/index.tsx +0 -0
- /package/templates/{web/src/system → src/system/web}/pages/settings/index.tsx +0 -0
- /package/templates/{web/src/system → src/system/web}/pages/users/index.tsx +0 -0
- /package/templates/{web/src/system → src/system/web}/pages/users/roles.tsx +0 -0
- /package/templates/{api/src/web → src/web/api}/get-async-routes/api.ts +0 -0
- /package/templates/{api/src/web → src/web/api}/hello/api.ts +0 -0
- /package/templates/{api/src/web → src/web/api}/manifest.yaml +0 -0
- /package/templates/{api/src/web → src/web/api}/user-info/api.ts +0 -0
- /package/templates/{web/src → src/web}/web/__client/api.schemas.ts +0 -0
- /package/templates/{web/src → src/web}/web/__client/api.ts +0 -0
- /package/templates/{api/tests → tests}/rbac-tables.spec.ts +0 -0
package/src/layout.ts
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 模块工程布局探测(设计 D11,2026-09-11
|
|
2
|
+
* 模块工程布局探测(设计 D11,2026-09-11 硬切换;2026-10 增补 module-root)。
|
|
3
3
|
*
|
|
4
4
|
* dev/build/preview 对「源码目录、产物目录、watch 目录」的全部路径取值
|
|
5
5
|
* 一律经此模块,禁止各自手拼——布局分叉点收敛一处。
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* 三种布局(互斥):
|
|
8
|
+
* module-root <project>/src/<name>/{api,web} 新布局:一目录一模块(设计 v2)
|
|
9
|
+
* new <project>/web/src/<name> 历史 uni-dev 布局
|
|
10
|
+
* flat <project>/web/<name> 框架根仓 dogfooding(产物落 build/)
|
|
11
|
+
*
|
|
12
|
+
* module-root 的判定只认「模块根标记文件」而不是单纯的 src/ 存在性:工程里
|
|
13
|
+
* 常有不属于模块的 src/(脚本、共享库),凭目录名猜会误判。
|
|
14
|
+
* `src/<x>/api/manifest.yaml` 与 `src/<x>/web/entry.ts` 是唯一真源(设计 C-6:
|
|
15
|
+
* 一侧可缺省,两侧皆无则不是模块)。
|
|
16
|
+
*
|
|
17
|
+
* 产物根在两套布局里语义相同、位置不变:
|
|
18
|
+
* api/dist oj 扁平 release 根(<name>-<version>/ 同层兄弟 + manifests.yaml)
|
|
19
|
+
* web/dist 站点产物(shell + modules/ + modules.json)
|
|
20
|
+
* 产物按模块聚合已被设计 §2.2 否决(实证见该节),禁止在此引入第二套形状。
|
|
21
|
+
*
|
|
11
22
|
* watch 目标绝不含产物目录:若 watch `web/`,重建产物(web/dist)
|
|
12
23
|
* 会反过来触发重建,形成自触发循环(设计 §4)。
|
|
13
24
|
*/
|
|
@@ -15,40 +26,270 @@
|
|
|
15
26
|
import fs from "node:fs";
|
|
16
27
|
import path from "node:path";
|
|
17
28
|
|
|
29
|
+
export type LayoutKind = "module-root" | "new" | "flat";
|
|
30
|
+
|
|
31
|
+
/** 模块一侧的可缺省组合:两侧皆无不是模块(设计 C-6) */
|
|
32
|
+
export interface ModuleSides {
|
|
33
|
+
name: string
|
|
34
|
+
/** 后端源码目录 `<project>/src/<name>/api`(无此侧为 undefined) */
|
|
35
|
+
apiSrc?: string
|
|
36
|
+
/** 前端源码目录 `<project>/src/<name>/web`(无此侧为 undefined) */
|
|
37
|
+
webSrc?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
18
40
|
export interface ProjectLayout {
|
|
19
|
-
kind:
|
|
20
|
-
|
|
41
|
+
kind: LayoutKind
|
|
42
|
+
projectRoot: string
|
|
43
|
+
/** 模块源码目录:module-root → `<project>/src`;new → `<project>/web/src`;flat → `<project>/web` */
|
|
21
44
|
modulesSrc: string
|
|
22
|
-
/**
|
|
45
|
+
/** 站点产物根:module-root / new → `<project>/web/dist`;flat → `<project>/build` */
|
|
23
46
|
distDir: string
|
|
47
|
+
/** oj 扁平 release 根:恒为 `<project>/api/dist`(设计 C-3/C-7,不随布局变化) */
|
|
48
|
+
apiDist: string
|
|
24
49
|
/** fs.watch 目标(纯源码,永不落产物) */
|
|
25
50
|
watchTarget: string
|
|
51
|
+
/**
|
|
52
|
+
* oj 的 `--module-root` 取值(源码树根目录),仅 module-root 有值。
|
|
53
|
+
*
|
|
54
|
+
* v0.1.85 语义:在**该目录的子树内递归**找 `manifest.yaml`,命中即停;模块身份取
|
|
55
|
+
* manifest 的 `name`(目录名只作定位)。它与 `-d`/`--api-path` **互斥**——两者都是
|
|
56
|
+
* 「源码树根」,同时给会被 clap 拒掉,所以调用方是二选一,不是「-d + 附加旗标」。
|
|
57
|
+
*/
|
|
58
|
+
moduleRoot?: string
|
|
26
59
|
}
|
|
27
60
|
|
|
28
|
-
|
|
29
|
-
|
|
61
|
+
function isDir(p: string): boolean {
|
|
62
|
+
try {
|
|
63
|
+
return fs.statSync(p).isDirectory();
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isFile(p: string): boolean {
|
|
71
|
+
try {
|
|
72
|
+
return fs.statSync(p).isFile();
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 扫描 module-root 布局的 `src/`:返回模块清单与「像模块但缺标记文件」的坏目录。
|
|
81
|
+
*
|
|
82
|
+
* 坏目录只在 module-root 被判定为当前布局后才报错(见 resolveLayout),
|
|
83
|
+
* 否则一个恰好带 `src/utils/api/` 的历史工程会被误伤。
|
|
84
|
+
*/
|
|
85
|
+
function scanModuleRoot(projectRoot: string): { modules: ModuleSides[], broken: string[] } | null {
|
|
86
|
+
const srcDir = path.join(projectRoot, "src");
|
|
87
|
+
if (!isDir(srcDir))
|
|
88
|
+
return null;
|
|
89
|
+
|
|
90
|
+
const modules: ModuleSides[] = [];
|
|
91
|
+
const broken: string[] = [];
|
|
92
|
+
// 按名字排序:readdir 的顺序取决于文件系统与创建历史(同一份源码在不同机器上可能不同),
|
|
93
|
+
// 而枚举顺序会进入构建顺序与产物内容——不排序则「同布局两次构建 sha256 相等」无法保证。
|
|
94
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true })
|
|
95
|
+
.filter(ent => ent.isDirectory() && !ent.name.startsWith("."))
|
|
96
|
+
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
97
|
+
for (const ent of entries) {
|
|
98
|
+
const dir = path.join(srcDir, ent.name);
|
|
99
|
+
const apiDir = path.join(dir, "api");
|
|
100
|
+
const webDir = path.join(dir, "web");
|
|
101
|
+
const hasApi = isFile(path.join(apiDir, "manifest.yaml"));
|
|
102
|
+
const hasWeb = isFile(path.join(webDir, "entry.ts"));
|
|
103
|
+
if (!hasApi && !hasWeb) {
|
|
104
|
+
// 只把「有 api/ 或 web/ 子目录但缺标记」算坏目录;纯共享库目录(无两者)静默跳过
|
|
105
|
+
if (isDir(apiDir) || isDir(webDir))
|
|
106
|
+
broken.push(ent.name);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
modules.push({
|
|
110
|
+
name: ent.name,
|
|
111
|
+
apiSrc: hasApi ? apiDir : undefined,
|
|
112
|
+
webSrc: hasWeb ? webDir : undefined,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return { modules, broken };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** 历史布局:`web/src` 为 uni-dev 工程;`web/<name>/entry.ts` 为框架根仓 */
|
|
119
|
+
function detectLegacyKind(projectRoot: string): LayoutKind | null {
|
|
120
|
+
if (isDir(path.join(projectRoot, "web", "src")))
|
|
121
|
+
return "new";
|
|
122
|
+
const webDir = path.join(projectRoot, "web");
|
|
123
|
+
if (isDir(webDir)) {
|
|
124
|
+
for (const ent of fs.readdirSync(webDir, { withFileTypes: true })) {
|
|
125
|
+
if (ent.isDirectory() && isFile(path.join(webDir, ent.name, "entry.ts")))
|
|
126
|
+
return "flat";
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 探测布局;工程里没有任何模块源码时返回 null(供 `buildBackend` 这类「没有源码就
|
|
134
|
+
* 跳过」的调用方使用)。布局存在但**自相矛盾**(双树并存 / src 下有坏模块目录)仍抛错。
|
|
135
|
+
*/
|
|
136
|
+
export function tryResolveLayout(projectRoot: string): ProjectLayout | null {
|
|
137
|
+
const scanned = scanModuleRoot(projectRoot);
|
|
138
|
+
const isModuleRoot = !!scanned && scanned.modules.length > 0;
|
|
139
|
+
const legacyKind = detectLegacyKind(projectRoot);
|
|
140
|
+
|
|
141
|
+
// 互斥:两套源码树同时存在时静默选一个,会让 `ojm build` 构建 A 而 `ojm dev` 预览 B
|
|
142
|
+
if (isModuleRoot && legacyKind) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`[ojm] ${projectRoot} 同时存在两套模块源码树:src/<name>/{api,web} 与 `
|
|
145
|
+
+ `${legacyKind === "new" ? "web/src/<name>" : "web/<name>"}。\n`
|
|
146
|
+
+ "布局互斥,无法判断以哪套为准——请显式移除其中一套后重试。",
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (scanned && (isModuleRoot || legacyKind === null) && scanned.broken.length > 0) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`[ojm] ${projectRoot}/src 下这些目录既无 api/manifest.yaml 也无 web/entry.ts:\n${
|
|
153
|
+
scanned.broken.map(n => ` - src/${n}/`).join("\n")}\n`
|
|
154
|
+
+ "module-root 布局里一个目录 = 一个模块,至少需要其中一侧;"
|
|
155
|
+
+ "若这些目录不是模块(脚本、共享库),请移到 src/ 之外。",
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (isModuleRoot) {
|
|
160
|
+
return {
|
|
161
|
+
kind: "module-root",
|
|
162
|
+
projectRoot,
|
|
163
|
+
modulesSrc: path.join(projectRoot, "src"),
|
|
164
|
+
distDir: path.join(projectRoot, "web", "dist"),
|
|
165
|
+
apiDist: path.join(projectRoot, "api", "dist"),
|
|
166
|
+
watchTarget: path.join(projectRoot, "src"),
|
|
167
|
+
moduleRoot: path.join(projectRoot, "src"),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (legacyKind === "new") {
|
|
30
172
|
return {
|
|
31
173
|
kind: "new",
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
174
|
+
projectRoot,
|
|
175
|
+
modulesSrc: path.join(projectRoot, "web", "src"),
|
|
176
|
+
distDir: path.join(projectRoot, "web", "dist"),
|
|
177
|
+
apiDist: path.join(projectRoot, "api", "dist"),
|
|
178
|
+
watchTarget: path.join(projectRoot, "web", "src"),
|
|
35
179
|
};
|
|
36
180
|
}
|
|
181
|
+
|
|
37
182
|
// 框架根仓:平铺 web/<name>/(readModuleDefinition 的裸说明符扫描复用此探测,B10)
|
|
38
|
-
if (
|
|
183
|
+
if (legacyKind === "flat") {
|
|
39
184
|
return {
|
|
40
185
|
kind: "flat",
|
|
186
|
+
projectRoot,
|
|
41
187
|
modulesSrc: path.join(projectRoot, "web"),
|
|
42
188
|
distDir: path.join(projectRoot, "build"),
|
|
189
|
+
apiDist: path.join(projectRoot, "api", "dist"),
|
|
43
190
|
watchTarget: path.join(projectRoot, "web"),
|
|
44
191
|
};
|
|
45
192
|
}
|
|
193
|
+
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function resolveLayout(projectRoot: string): ProjectLayout {
|
|
198
|
+
const layout = tryResolveLayout(projectRoot);
|
|
199
|
+
if (layout)
|
|
200
|
+
return layout;
|
|
46
201
|
throw new Error(
|
|
47
|
-
`[ojm] ${projectRoot}
|
|
202
|
+
`[ojm] ${projectRoot} 下未发现模块源码(硬切换后只认 web/src;2026-10 起也认 src/<name>/{api,web}):\n`
|
|
203
|
+
+ " - 新布局:src/<name>/{api/manifest.yaml 或 web/entry.ts}\n"
|
|
204
|
+
+ " - 历史布局:web/src/<name>(uni-dev)或 web/<name>/entry.ts(框架根仓)\n"
|
|
48
205
|
+ "存量 modules/ 工程请迁移:modules/ → web/,modules.config.ts → web.config.ts。",
|
|
49
206
|
);
|
|
50
207
|
}
|
|
51
208
|
|
|
209
|
+
/**
|
|
210
|
+
* 枚举 module-root 布局的模块(设计 C-6:真源是标记文件,不是人工登记的 web.config.ts)。
|
|
211
|
+
* 历史布局的模块顺序由 `web.config.ts` 的 `modules` 决定,此处不适用。
|
|
212
|
+
*/
|
|
213
|
+
export function scanModules(layout: ProjectLayout): ModuleSides[] {
|
|
214
|
+
if (layout.kind !== "module-root")
|
|
215
|
+
throw new Error(`[ojm] scanModules 只适用于 module-root 布局,当前为 ${layout.kind}`);
|
|
216
|
+
return scanModuleRoot(layout.projectRoot)?.modules ?? [];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** 契约工具的处理单元:模块名 + 该模块 api 侧目录(handler 目录镜像的根) */
|
|
220
|
+
export interface ApiModuleRoot {
|
|
221
|
+
name: string
|
|
222
|
+
dir: string
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* 历史布局的模块 api 根:`<project>/api/src/<name>`。
|
|
227
|
+
*
|
|
228
|
+
* 单独导出是因为**纯后端工程**可能只有 `api/src` 而没有 `web/src`——`tryResolveLayout`
|
|
229
|
+
* 会返回 null,契约工具仍要能对账。
|
|
230
|
+
*/
|
|
231
|
+
export function legacyApiModuleRoots(projectRoot: string): ApiModuleRoot[] {
|
|
232
|
+
const root = path.join(projectRoot, "api", "src");
|
|
233
|
+
if (!isDir(root))
|
|
234
|
+
return [];
|
|
235
|
+
return fs.readdirSync(root, { withFileTypes: true })
|
|
236
|
+
.filter(entry => entry.isDirectory() && !entry.name.startsWith("."))
|
|
237
|
+
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
|
|
238
|
+
.map(entry => ({ name: entry.name, dir: path.join(root, entry.name) }));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* 枚举后端模块的 api 根,供 `ojm api` 生成 / `--check` 对账**按模块**扫描。
|
|
243
|
+
*
|
|
244
|
+
* 为什么不直接 walk `apiModulesSrc(layout)`:module-root 布局的树根是 `src`,模块的
|
|
245
|
+
* api 侧在 `<src>/<name>/api`,中间那层 `api` 是**布局分隔符、不是路由段**。拿 `src`
|
|
246
|
+
* 当扫描根会双重出错——路由镜像多出 `api` 段(`system/api/users` ≠ 真实 `system/users`),
|
|
247
|
+
* 且 `src/<name>/web/__client/api.ts` 这种前端生成物也会被当 handler 扫进来。
|
|
248
|
+
*
|
|
249
|
+
* module-root → 按 `<name>/api/manifest.yaml` 判模块(与 build 同源,name 取 manifest);
|
|
250
|
+
* 历史布局 → `<project>/api/src` 的一级目录即模块。
|
|
251
|
+
*/
|
|
252
|
+
export function apiModuleRoots(layout: ProjectLayout): ApiModuleRoot[] {
|
|
253
|
+
if (layout.kind !== "module-root")
|
|
254
|
+
return legacyApiModuleRoots(layout.projectRoot);
|
|
255
|
+
return scanModules(layout)
|
|
256
|
+
.filter((m): m is ModuleSides & { apiSrc: string } => !!m.apiSrc)
|
|
257
|
+
.map(m => ({ name: m.name, dir: m.apiSrc }));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** `<project>/src/<name>` */
|
|
261
|
+
export function moduleDir(layout: ProjectLayout, name: string): string {
|
|
262
|
+
return path.join(layout.modulesSrc, name);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 后端源码根。module-root 为 `<modulesSrc>/<name>/api`(模块的 api 侧目录);
|
|
267
|
+
* 历史布局为 `<project>/api/src/<name>`——不在 modulesSrc 之下,必须走 projectRoot。
|
|
268
|
+
*/
|
|
269
|
+
export function apiSrcOf(layout: ProjectLayout, name: string): string {
|
|
270
|
+
return layout.kind === "module-root"
|
|
271
|
+
? path.join(layout.modulesSrc, name, "api")
|
|
272
|
+
: path.join(layout.projectRoot, "api", "src", name);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* 后端源码的**根**目录:
|
|
277
|
+
* module-root → `<project>/src`(oj 的 `--module-root` 取源码树根,模块在每层 api 子目录)
|
|
278
|
+
* 历史布局 → `<project>/api/src`(oj 的 `-d` / `--api-path` 取模块目录的父级)
|
|
279
|
+
*/
|
|
280
|
+
export function apiModulesSrc(layout: ProjectLayout): string {
|
|
281
|
+
return layout.kind === "module-root"
|
|
282
|
+
? layout.modulesSrc
|
|
283
|
+
: path.join(layout.projectRoot, "api", "src");
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** 前端源码目录(历史布局与模块目录同体) */
|
|
287
|
+
export function webSrcOf(layout: ProjectLayout, name: string): string {
|
|
288
|
+
return layout.kind === "module-root"
|
|
289
|
+
? path.join(layout.modulesSrc, name, "web")
|
|
290
|
+
: path.join(layout.modulesSrc, name);
|
|
291
|
+
}
|
|
292
|
+
|
|
52
293
|
export function resolveWatchTarget(projectRoot: string): string {
|
|
53
294
|
return resolveLayout(projectRoot).watchTarget;
|
|
54
295
|
}
|
|
@@ -56,21 +297,38 @@ export function resolveWatchTarget(projectRoot: string): string {
|
|
|
56
297
|
/**
|
|
57
298
|
* 定位工程 config.yaml。
|
|
58
299
|
*
|
|
59
|
-
*
|
|
300
|
+
* 唯一规范位置由布局决定:
|
|
301
|
+
* module-root → `<project>/config.yaml`
|
|
302
|
+
* 历史布局 → `<project>/api/config.yaml`
|
|
60
303
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* (实测:26 个 api 文件 → 只剩 8 个,18 条路由被跳过)。
|
|
304
|
+
* module-root 下没有回退到 `api/config.yaml`:oj 把「项目根」锚定在 **config 文件所在目录**,
|
|
305
|
+
* 相对 import 被钳制在该根内(devkit 手册第 5 章)。源码搬到 `src/` 而 config 留在 `api/`
|
|
306
|
+
* 时,project root 收缩成 `<project>/api`,`src/` 整体落到根之外——凡是带 `../` 相对 import
|
|
307
|
+
* 的 handler 在 `ojm dev` 下会被判 `escapes project root` 而整条路由加载失败
|
|
308
|
+
* (实测:26 个 api 文件 → 只剩 8 个,18 条路由被跳过)。回退会把这个陷阱伪装成「能跑」。
|
|
67
309
|
*/
|
|
68
310
|
export function resolveConfigPath(projectRoot: string): string {
|
|
69
|
-
|
|
311
|
+
const rootConfig = path.join(projectRoot, "config.yaml");
|
|
312
|
+
if (isFile(rootConfig))
|
|
313
|
+
return rootConfig;
|
|
314
|
+
const legacyConfig = path.join(projectRoot, "api", "config.yaml");
|
|
315
|
+
// 源码已是 module-root 但 config 还在 api/ ⇒ 迁移未完成。此时回退会踩上面那个陷阱,
|
|
316
|
+
// 且症状是「部分路由静默消失」,比直接失败更难排查。
|
|
317
|
+
if (isFile(legacyConfig) && (scanModuleRoot(projectRoot)?.modules.length ?? 0) > 0) {
|
|
318
|
+
throw new Error(
|
|
319
|
+
`[ojm] ${projectRoot} 已是 src/<name>/{api,web} 布局,但 config.yaml 仍在 api/ 下。\n`
|
|
320
|
+
+ `请移到 ${rootConfig}(证书目录随之变为 ${path.join(projectRoot, "config")})。\n`
|
|
321
|
+
+ "oj 以 config 所在目录为 project root:config 留在 api/ 会把 src/ 甩到根外,"
|
|
322
|
+
+ "带 ../ 相对 import 的 handler 会被判 escapes project root 而整条路由不加载。",
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
return legacyConfig;
|
|
70
326
|
}
|
|
71
327
|
|
|
72
328
|
/**
|
|
73
|
-
*
|
|
329
|
+
* 证书与密钥的唯一落点:config.yaml 同级的 `config/` 子目录。
|
|
330
|
+
*
|
|
331
|
+
* module-root → `<project>/config/`;历史布局 → `<project>/api/config/`。
|
|
74
332
|
*
|
|
75
333
|
* 三个入口都指向这里,别再各自手拼:
|
|
76
334
|
* ① `server.public_key_path` → ./config/public.pem(oj 验签公钥)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// BDD:ojm new 参数解析(统一审查 F8——多余位置参数须显式拒绝,不能静默丢弃)。
|
|
2
|
+
// 静默丢弃的恶果:ojm new crm erp(手滑/粘贴)只建 crm,用户以为两份都建了。
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { parseNewArgs } from "../../args";
|
|
5
|
+
|
|
6
|
+
describe("parseNewArgs", () => {
|
|
7
|
+
it("name + 旗标组合", () => {
|
|
8
|
+
expect(parseNewArgs(["crm"])).toEqual({ name: "crm", layout: undefined, api: false });
|
|
9
|
+
expect(parseNewArgs(["crm", "--layout", "none", "--api"])).toEqual({ name: "crm", layout: "none", api: true });
|
|
10
|
+
expect(parseNewArgs([])).toEqual({ name: undefined, layout: undefined, api: false });
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("多余位置参数 → 报错而非静默丢弃", () => {
|
|
14
|
+
expect(() => parseNewArgs(["crm", "erp"])).toThrow(/只接受一个方案名/);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("非法 --layout 值 → 报错", () => {
|
|
18
|
+
expect(() => parseNewArgs(["crm", "--layout", "sidebar"])).toThrow(/container \| none/);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// BDD:脚手架自带模块身份(`src/.ojm-builtin.json`)。
|
|
2
|
+
// 消费契约:bundle 排除打包 / ojm info 折叠 / ojm new 拒名;文件缺失 → 空名单(旧工程兼容)。
|
|
3
|
+
// 用例对应设计 docs/plans/2026-10-22-1640-multi-solution-bundle-design.md:
|
|
4
|
+
// B12(坏文件 fail-fast)/ T1(名单 == 白名单派生集)/ T2(copyIfAbsent,不覆盖用户手改)
|
|
5
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
9
|
+
import { BUILTIN_LIST_FILE, deriveBuiltinModules, readBuiltinModules, writeBuiltinList } from "../../builtin";
|
|
10
|
+
|
|
11
|
+
let project: string;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
project = mkdtempSync(join(tmpdir(), "ojm-builtin-"));
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
rmSync(project, { recursive: true, force: true });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
function writeList(content: string): void {
|
|
22
|
+
mkdirSync(join(project, "src"), { recursive: true });
|
|
23
|
+
writeFileSync(join(project, "src", BUILTIN_LIST_FILE), content);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe("readBuiltinModules", () => {
|
|
27
|
+
it("文件缺失 → 空名单(旧工程/框架仓无内置概念,行为退化兼容)", () => {
|
|
28
|
+
expect(readBuiltinModules(project).size).toBe(0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("合法文件 → 返回 modules 集合", () => {
|
|
32
|
+
writeList(JSON.stringify({ modules: ["auth", "home"] }));
|
|
33
|
+
expect([...readBuiltinModules(project)].sort()).toEqual(["auth", "home"]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("b12 坏 JSON → fail-fast,报错含文件路径与错因(不静默降级)", () => {
|
|
37
|
+
writeList("{ not json");
|
|
38
|
+
expect(() => readBuiltinModules(project)).toThrow(new RegExp(`${BUILTIN_LIST_FILE.replace(".", "\\.")}`));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("b12 modules 非数组 → fail-fast", () => {
|
|
42
|
+
writeList(JSON.stringify({ modules: "auth" }));
|
|
43
|
+
expect(() => readBuiltinModules(project)).toThrow(/modules/);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("b12 modules 含非字符串项 → fail-fast", () => {
|
|
47
|
+
writeList(JSON.stringify({ modules: ["auth", 42] }));
|
|
48
|
+
expect(() => readBuiltinModules(project)).toThrow(/modules/);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("deriveBuiltinModules(名单派生自 init.include.yaml,单一真源)", () => {
|
|
53
|
+
it("t1 取 src/<name>/(api|web) 条目的模块名(含 web-only 模块),忽略无关路径", () => {
|
|
54
|
+
const include = new Set([
|
|
55
|
+
"src/auth/api",
|
|
56
|
+
"src/notification/api",
|
|
57
|
+
"src/login/web/entry.ts",
|
|
58
|
+
"src/oj-market/web/pages",
|
|
59
|
+
"src/styles.css",
|
|
60
|
+
"src/tsconfig.json",
|
|
61
|
+
"config.yaml",
|
|
62
|
+
"web.config.ts",
|
|
63
|
+
]);
|
|
64
|
+
expect([...deriveBuiltinModules(include)].sort())
|
|
65
|
+
.toEqual(["auth", "login", "notification", "oj-market"]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("白名单无模块条目 → 空集(不凭空造身份)", () => {
|
|
69
|
+
expect(deriveBuiltinModules(new Set(["config.yaml", "README.md"])).size).toBe(0);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("writeBuiltinList(init 接线:copyIfAbsent)", () => {
|
|
74
|
+
it("t1 写入派生名单(排序)+ 自注释头,返回 created", () => {
|
|
75
|
+
const include = new Set(["src/home/web/entry.ts", "src/auth/api", "src/login/web"]);
|
|
76
|
+
expect(writeBuiltinList(project, include)).toBe("created");
|
|
77
|
+
const p = join(project, "src", BUILTIN_LIST_FILE);
|
|
78
|
+
expect(existsSync(p)).toBe(true);
|
|
79
|
+
const doc = JSON.parse(readFileSync(p, "utf-8"));
|
|
80
|
+
expect(doc.modules).toEqual(["auth", "home", "login"]);
|
|
81
|
+
expect(typeof doc["//"]).toBe("string");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("t2 已存在 → 返回 skipped 且内容原样不动(用户手改权)", () => {
|
|
85
|
+
const include = new Set(["src/auth/api"]);
|
|
86
|
+
expect(writeBuiltinList(project, include)).toBe("created");
|
|
87
|
+
const p = join(project, "src", BUILTIN_LIST_FILE);
|
|
88
|
+
writeFileSync(p, JSON.stringify({ modules: ["my-edit"] }));
|
|
89
|
+
expect(writeBuiltinList(project, include)).toBe("skipped");
|
|
90
|
+
expect(JSON.parse(readFileSync(p, "utf-8")).modules).toEqual(["my-edit"]);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("派生集为空 → 不写文件,返回 none", () => {
|
|
94
|
+
expect(writeBuiltinList(project, new Set(["config.yaml"]))).toBe("none");
|
|
95
|
+
expect(existsSync(join(project, "src", BUILTIN_LIST_FILE))).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
});
|