@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
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// BDD:脚手架 package.json scripts 与模板的双写守卫(T3,发布工程师评审建议)。
|
|
2
|
+
// 生成器(init.ts generatePackageJson)与模板(templates/package.json)各写一份 scripts,
|
|
3
|
+
// 人肉同步必漂移(实证:typecheck:api 曾漏发生成器)——钉成测试:生成 scripts 的每个键
|
|
4
|
+
// 都必须存在于模板 scripts(值一致),新增 script 先改模板再改生成器。
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { describe, expect, it } from "vitest";
|
|
9
|
+
import { generatePackageJson } from "../init";
|
|
10
|
+
|
|
11
|
+
const cliRoot = join(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
12
|
+
|
|
13
|
+
describe("脚手架 package.json scripts 双写守卫(T3)", () => {
|
|
14
|
+
it("生成 scripts 的每个键都存在于模板且值一致", () => {
|
|
15
|
+
const template = JSON.parse(readFileSync(join(cliRoot, "templates/package.json"), "utf-8"));
|
|
16
|
+
const generated = generatePackageJson(cliRoot, "guard-check");
|
|
17
|
+
for (const [key, value] of Object.entries(generated.scripts)) {
|
|
18
|
+
expect(template.scripts[key], `生成器有而模板无的 script:${key}(先改 templates/package.json)`)
|
|
19
|
+
.toBe(value);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("多方案 scripts 齐备:new / bundle / typecheck:api", () => {
|
|
24
|
+
const generated = generatePackageJson(cliRoot, "guard-check");
|
|
25
|
+
expect(generated.scripts.new).toBe("ojm new");
|
|
26
|
+
expect(generated.scripts.bundle).toBe("ojm build --bundle");
|
|
27
|
+
expect(generated.scripts["typecheck:api"]).toBe("tsc --noEmit -p src/tsconfig.json");
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// layout spec:三种布局的探测、互斥、模块枚举与 config 落点。
|
|
2
|
+
//
|
|
3
|
+
// 这些取值是 dev/build/preview/bundle 的共同底座:探测错一次,`ojm build` 会构建 A
|
|
4
|
+
// 而 `ojm dev` 预览 B,或产物写进空目录后表现为「health 200、模块 0 个」的假绿。
|
|
5
|
+
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { describe, expect, it } from "vitest";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
apiSrcOf,
|
|
12
|
+
moduleDir,
|
|
13
|
+
resolveCertDir,
|
|
14
|
+
resolveConfigPath,
|
|
15
|
+
resolveLayout,
|
|
16
|
+
scanModules,
|
|
17
|
+
webSrcOf,
|
|
18
|
+
} from "../layout";
|
|
19
|
+
|
|
20
|
+
function newRoot(): string {
|
|
21
|
+
return mkdtempSync(join(tmpdir(), "ojm-layout-"));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function writeFile(root: string, rel: string, content = ""): void {
|
|
25
|
+
const abs = join(root, rel);
|
|
26
|
+
mkdirSync(join(abs, ".."), { recursive: true });
|
|
27
|
+
writeFileSync(abs, content, "utf-8");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** 新布局的最小工程:src/wiki/{api/manifest.yaml, web/entry.ts} + 根 config.yaml */
|
|
31
|
+
function newModuleRootProject(): string {
|
|
32
|
+
const root = newRoot();
|
|
33
|
+
writeFile(root, "src/wiki/api/manifest.yaml", "name: wiki\nversion: 0.1.0\n");
|
|
34
|
+
writeFile(root, "src/wiki/web/entry.ts", "export default {};\n");
|
|
35
|
+
writeFile(root, "config.yaml", "server:\n port: 9778\n");
|
|
36
|
+
return root;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("resolveLayout / module-root", () => {
|
|
40
|
+
it("src/<name>/{api,web} 命中 module-root,产物根语义不变", () => {
|
|
41
|
+
const root = newModuleRootProject();
|
|
42
|
+
const layout = resolveLayout(root);
|
|
43
|
+
|
|
44
|
+
expect(layout.kind).toBe("module-root");
|
|
45
|
+
expect(layout.modulesSrc).toBe(join(root, "src"));
|
|
46
|
+
expect(layout.distDir).toBe(join(root, "web", "dist"));
|
|
47
|
+
expect(layout.apiDist).toBe(join(root, "api", "dist"));
|
|
48
|
+
expect(layout.watchTarget).toBe(join(root, "src"));
|
|
49
|
+
// --module-root 取「源码树根目录」(oj 在其子树内递归找 manifest.yaml)
|
|
50
|
+
expect(layout.moduleRoot).toBe(join(root, "src"));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("web/dist 存在不会被误判成 flat 布局", () => {
|
|
54
|
+
const root = newModuleRootProject();
|
|
55
|
+
mkdirSync(join(root, "web", "dist", "modules", "wiki", "0.1.0"), { recursive: true });
|
|
56
|
+
expect(resolveLayout(root).kind).toBe("module-root");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("枚举真源是标记文件:一侧可缺省,纯后端 / 纯前端各算一个模块", () => {
|
|
60
|
+
const root = newRoot();
|
|
61
|
+
writeFile(root, "src/_platform/api/manifest.yaml", "name: _platform\nversion: 0.1.0\n");
|
|
62
|
+
writeFile(root, "src/auth/api/manifest.yaml", "name: auth\nversion: 0.1.0\n");
|
|
63
|
+
writeFile(root, "src/about/web/entry.ts", "export default {};\n");
|
|
64
|
+
|
|
65
|
+
// 按名字排序(不依赖 readdir 顺序,产物才可复现)
|
|
66
|
+
const modules = scanModules(resolveLayout(root));
|
|
67
|
+
expect(modules.map(m => m.name)).toEqual(["_platform", "about", "auth"]);
|
|
68
|
+
expect(modules.map(m => !!m.apiSrc)).toEqual([true, false, true]);
|
|
69
|
+
expect(modules.map(m => !!m.webSrc)).toEqual([false, true, false]);
|
|
70
|
+
expect(modules[0].apiSrc).toBe(join(root, "src", "_platform", "api"));
|
|
71
|
+
expect(modules[1].webSrc).toBe(join(root, "src", "about", "web"));
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("非模块目录(既无 api/ 也无 web/,如共享库)静默跳过", () => {
|
|
75
|
+
const root = newRoot();
|
|
76
|
+
writeFile(root, "src/wiki/api/manifest.yaml", "name: wiki\nversion: 0.1.0\n");
|
|
77
|
+
writeFile(root, "src/shared/util.ts", "export const x = 1;\n");
|
|
78
|
+
expect(scanModules(resolveLayout(root)).map(m => m.name)).toEqual(["wiki"]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("有 api/ 却缺 manifest.yaml:报错并指名目录(不静默当成非模块)", () => {
|
|
82
|
+
const root = newRoot();
|
|
83
|
+
writeFile(root, "src/wiki/api/manifest.yaml", "name: wiki\nversion: 0.1.0\n");
|
|
84
|
+
writeFile(root, "src/broken/api/index.js", "export {};\n");
|
|
85
|
+
expect(() => resolveLayout(root)).toThrow(/src\/broken\//);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("src/ 下没有任何模块标记时不算 module-root(避免把脚本 src/ 当模块树)", () => {
|
|
89
|
+
const root = newRoot();
|
|
90
|
+
mkdirSync(join(root, "src", "scripts"), { recursive: true });
|
|
91
|
+
expect(() => resolveLayout(root)).toThrow(/未发现模块源码/);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("resolveLayout / 历史布局", () => {
|
|
96
|
+
it("web/src 仍识别为 new,模块根不在 src/ 之下", () => {
|
|
97
|
+
const root = newRoot();
|
|
98
|
+
writeFile(root, "web/src/wiki/entry.ts", "export default {};\n");
|
|
99
|
+
writeFile(root, "api/src/wiki/manifest.yaml", "name: wiki\nversion: 0.1.0\n");
|
|
100
|
+
|
|
101
|
+
const layout = resolveLayout(root);
|
|
102
|
+
expect(layout.kind).toBe("new");
|
|
103
|
+
expect(layout.modulesSrc).toBe(join(root, "web", "src"));
|
|
104
|
+
expect(layout.moduleRoot).toBeUndefined();
|
|
105
|
+
expect(apiSrcOf(layout, "wiki")).toBe(join(root, "api", "src", "wiki"));
|
|
106
|
+
expect(webSrcOf(layout, "wiki")).toBe(join(root, "web", "src", "wiki"));
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("web/<name>/entry.ts 识别为 flat,产物落 build/", () => {
|
|
110
|
+
const root = newRoot();
|
|
111
|
+
writeFile(root, "web/home/entry.ts", "export default {};\n");
|
|
112
|
+
writeFile(root, "web/home/__client/api.ts", "export {};\n");
|
|
113
|
+
|
|
114
|
+
const layout = resolveLayout(root);
|
|
115
|
+
expect(layout.kind).toBe("flat");
|
|
116
|
+
expect(layout.modulesSrc).toBe(join(root, "web"));
|
|
117
|
+
expect(layout.distDir).toBe(join(root, "build"));
|
|
118
|
+
expect(webSrcOf(layout, "home")).toBe(join(root, "web", "home"));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("历史布局不支持 scanModules(顺序由 web.config.ts 决定)", () => {
|
|
122
|
+
const root = newRoot();
|
|
123
|
+
writeFile(root, "web/src/wiki/entry.ts", "export default {};\n");
|
|
124
|
+
expect(() => scanModules(resolveLayout(root))).toThrow(/只适用于 module-root/);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("resolveLayout / 互斥与缺省", () => {
|
|
129
|
+
it("两套源码树同时存在时报错,而不是静默选一个", () => {
|
|
130
|
+
const root = newModuleRootProject();
|
|
131
|
+
writeFile(root, "web/src/legacy/entry.ts", "export default {};\n");
|
|
132
|
+
expect(() => resolveLayout(root)).toThrow(/同时存在两套模块源码树/);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("什么布局都没有时报错并列出两种合法形状", () => {
|
|
136
|
+
const root = newRoot();
|
|
137
|
+
expect(() => resolveLayout(root)).toThrow(/未发现模块源码/);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe("config 落点", () => {
|
|
142
|
+
it("module-root:config 与证书目录都在仓库根", () => {
|
|
143
|
+
const root = newModuleRootProject();
|
|
144
|
+
expect(resolveConfigPath(root)).toBe(join(root, "config.yaml"));
|
|
145
|
+
expect(resolveCertDir(root)).toBe(join(root, "config"));
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("module-root 仍把 config 留在 api/ 时报错(回退会让路由静默消失)", () => {
|
|
149
|
+
const root = newRoot();
|
|
150
|
+
writeFile(root, "src/wiki/api/manifest.yaml", "name: wiki\nversion: 0.1.0\n");
|
|
151
|
+
writeFile(root, "api/config.yaml", "server:\n port: 9778\n");
|
|
152
|
+
expect(() => resolveConfigPath(root)).toThrow(/仍在 api\/ 下/);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("历史布局:config 与证书目录仍在 api/ 下", () => {
|
|
156
|
+
const root = newRoot();
|
|
157
|
+
writeFile(root, "web/src/wiki/entry.ts", "export default {};\n");
|
|
158
|
+
writeFile(root, "api/config.yaml", "server:\n port: 9778\n");
|
|
159
|
+
expect(resolveConfigPath(root)).toBe(join(root, "api", "config.yaml"));
|
|
160
|
+
expect(resolveCertDir(root)).toBe(join(root, "api", "config"));
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("空目录(init 目标)不报错:落默认位置供脚手架写入", () => {
|
|
164
|
+
const root = newRoot();
|
|
165
|
+
expect(resolveConfigPath(root)).toBe(join(root, "api", "config.yaml"));
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("路径取值函数", () => {
|
|
170
|
+
it("module-root:moduleDir / apiSrcOf / webSrcOf 都在 src/<name>/ 之下", () => {
|
|
171
|
+
const layout = resolveLayout(newModuleRootProject());
|
|
172
|
+
expect(moduleDir(layout, "wiki")).toBe(join(layout.modulesSrc, "wiki"));
|
|
173
|
+
expect(apiSrcOf(layout, "wiki")).toBe(join(layout.modulesSrc, "wiki", "api"));
|
|
174
|
+
expect(webSrcOf(layout, "wiki")).toBe(join(layout.modulesSrc, "wiki", "web"));
|
|
175
|
+
});
|
|
176
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// 前端模块清单 spec(设计 D3:扫描为准 + web.config.ts 可选覆盖)。
|
|
2
|
+
//
|
|
3
|
+
// 关键不变量是**并集而不是替换**:新增一个模块目录后忘了登记,也必须被构建;
|
|
4
|
+
// 反过来登记了一个已经不存在的目录,必须报错而不是静默少一个模块。
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
|
|
7
|
+
import { mergeModuleList } from "../config";
|
|
8
|
+
|
|
9
|
+
const ROOT = "/proj";
|
|
10
|
+
|
|
11
|
+
function scanned(...names: string[]) {
|
|
12
|
+
return names.map(name => ({ name, webSrc: `${ROOT}/src/${name}/web` }));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe("mergeModuleList", () => {
|
|
16
|
+
it("无覆盖时按扫描顺序(layout 已按名字排序)", () => {
|
|
17
|
+
expect(mergeModuleList(scanned("about", "home", "wiki"), undefined, ROOT)).toEqual([
|
|
18
|
+
{ name: "about", entry: "src/about/web/entry.ts", enabled: true },
|
|
19
|
+
{ name: "home", entry: "src/home/web/entry.ts", enabled: true },
|
|
20
|
+
{ name: "wiki", entry: "src/wiki/web/entry.ts", enabled: true },
|
|
21
|
+
]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("覆盖项按声明顺序排前,未登记的按扫描顺序追加(并集)", () => {
|
|
25
|
+
const list = mergeModuleList(scanned("about", "home", "wiki"), [
|
|
26
|
+
{ name: "home", entry: "ignored" },
|
|
27
|
+
{ name: "wiki", entry: "ignored" },
|
|
28
|
+
], ROOT);
|
|
29
|
+
expect(list.map(m => m.name)).toEqual(["home", "wiki", "about"]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("enabled:false 仍生效(覆盖项与未登记项都按自己的声明走)", () => {
|
|
33
|
+
const list = mergeModuleList(scanned("about", "home"), [
|
|
34
|
+
{ name: "home", entry: "x", enabled: false },
|
|
35
|
+
], ROOT);
|
|
36
|
+
expect(list).toEqual([
|
|
37
|
+
{ name: "home", entry: "src/home/web/entry.ts", enabled: false },
|
|
38
|
+
{ name: "about", entry: "src/about/web/entry.ts", enabled: true },
|
|
39
|
+
]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("登记了不存在的模块 → 报错并提示以目录为准", () => {
|
|
43
|
+
expect(() => mergeModuleList(scanned("home"), [
|
|
44
|
+
{ name: "gone", entry: "src/gone/web/entry.ts" },
|
|
45
|
+
], ROOT)).toThrow(/src\/gone\/web\/entry\.ts 不存在/);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("纯后端模块(无 web/entry.ts)不进前端清单", () => {
|
|
49
|
+
const list = mergeModuleList([
|
|
50
|
+
{ name: "auth" }, // 纯后端:只有 api/manifest.yaml,扫描结果没有 webSrc
|
|
51
|
+
{ name: "home", webSrc: `${ROOT}/src/home/web` },
|
|
52
|
+
], undefined, ROOT);
|
|
53
|
+
expect(list.map(m => m.name)).toEqual(["home"]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("登记一个纯后端模块也算错(该目录没有前端入口)", () => {
|
|
57
|
+
expect(() => mergeModuleList([
|
|
58
|
+
{ name: "auth" },
|
|
59
|
+
], [{ name: "auth", entry: "x" }], ROOT)).toThrow(/不存在/);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("entry 用相对工程根的路径(与 web.config.ts 历史口径一致)", () => {
|
|
63
|
+
const [only] = mergeModuleList(scanned("wiki"), [{ name: "wiki", entry: "/abs/should-be-ignored" }], ROOT);
|
|
64
|
+
expect(only.entry).toBe("src/wiki/web/entry.ts");
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// oj 能力探针 spec:双探的第二探(设计 202610220010 §7)。
|
|
2
|
+
//
|
|
3
|
+
// 失败方式很隐蔽:能力缺失却在构建入口回落到旧布局,会得到「源码搬了、产物还是旧的」
|
|
4
|
+
// 半坏状态。这里钉住三件事:解析只看整词旗标、缓存按二进制指纹失效、缺失即抛且带版本。
|
|
5
|
+
import { mkdirSync, mkdtempSync, readFileSync, statSync, utimesSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { describe, expect, it } from "vitest";
|
|
9
|
+
|
|
10
|
+
import { CAPS_FILE, probeOjCaps, readCapsCache, requireModuleRootCapability, writeCapsCache } from "../oj-caps";
|
|
11
|
+
|
|
12
|
+
function newProject(): { root: string, ojBin: string } {
|
|
13
|
+
const root = mkdtempSync(join(tmpdir(), "ojm-caps-"));
|
|
14
|
+
mkdirSync(join(root, "bin"), { recursive: true });
|
|
15
|
+
const ojBin = join(root, "bin", "oj");
|
|
16
|
+
writeFileSync(ojBin, "#!/bin/sh\necho fake\n", { mode: 0o755 });
|
|
17
|
+
return { root, ojBin };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const HELP_WITH = "Options:\n -d, --dir <DIR> 源码目录\n --module-root <GLOB> 模块根 glob\n";
|
|
21
|
+
const HELP_WITHOUT = "Options:\n -d, --dir <DIR> 源码目录\n";
|
|
22
|
+
|
|
23
|
+
describe("probeOjCaps", () => {
|
|
24
|
+
it("帮助里有 --module-root 即支持", () => {
|
|
25
|
+
const { ojBin } = newProject();
|
|
26
|
+
expect(probeOjCaps(ojBin, () => HELP_WITH).moduleRoot).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("帮助里没有则不支持", () => {
|
|
30
|
+
const { ojBin } = newProject();
|
|
31
|
+
expect(probeOjCaps(ojBin, () => HELP_WITHOUT).moduleRoot).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("同名前缀不算命中(--module-rooted 之类的说明文字)", () => {
|
|
35
|
+
const { ojBin } = newProject();
|
|
36
|
+
expect(probeOjCaps(ojBin, () => "--module-rooted 是别的意思\n").moduleRoot).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("探测本身失败时报错带处置命令", () => {
|
|
40
|
+
const { ojBin } = newProject();
|
|
41
|
+
expect(() => probeOjCaps(ojBin, () => {
|
|
42
|
+
throw new Error("boom");
|
|
43
|
+
})).toThrow(/ojm vendor/);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("能力缓存", () => {
|
|
48
|
+
it("写入后能读回", () => {
|
|
49
|
+
const { ojBin } = newProject();
|
|
50
|
+
writeCapsCache(ojBin, { moduleRoot: true });
|
|
51
|
+
expect(readCapsCache(ojBin)).toEqual({ moduleRoot: true });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("换二进制(size 变)后缓存失效", () => {
|
|
55
|
+
const { ojBin } = newProject();
|
|
56
|
+
writeCapsCache(ojBin, { moduleRoot: true });
|
|
57
|
+
writeFileSync(ojBin, "#!/bin/sh\necho replaced-and-longer\n", { mode: 0o755 });
|
|
58
|
+
expect(readCapsCache(ojBin)).toBeNull();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("mtime 变(同尺寸手替)后缓存失效", () => {
|
|
62
|
+
const { ojBin } = newProject();
|
|
63
|
+
writeCapsCache(ojBin, { moduleRoot: true });
|
|
64
|
+
const future = new Date(Date.now() + 60_000);
|
|
65
|
+
utimesSync(ojBin, future, future);
|
|
66
|
+
expect(readCapsCache(ojBin)).toBeNull();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("缓存文件损坏不影响主流程(视为未缓存)", () => {
|
|
70
|
+
const { root, ojBin } = newProject();
|
|
71
|
+
writeFileSync(join(root, "bin", CAPS_FILE), "{not json", "utf-8");
|
|
72
|
+
expect(readCapsCache(ojBin)).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("requireModuleRootCapability", () => {
|
|
77
|
+
it("支持时通过并落缓存,第二次不再探测", () => {
|
|
78
|
+
const { root } = newProject();
|
|
79
|
+
let calls = 0;
|
|
80
|
+
const run = () => {
|
|
81
|
+
calls += 1;
|
|
82
|
+
return HELP_WITH;
|
|
83
|
+
};
|
|
84
|
+
expect(requireModuleRootCapability(root, run).moduleRoot).toBe(true);
|
|
85
|
+
expect(requireModuleRootCapability(root, run).moduleRoot).toBe(true);
|
|
86
|
+
expect(calls).toBe(1);
|
|
87
|
+
expect(readFileSync(join(root, "bin", CAPS_FILE), "utf-8")).toContain("\"moduleRoot\": true");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("二进制缺失时报错指向 ojm vendor", () => {
|
|
91
|
+
const { root } = newProject();
|
|
92
|
+
const run = () => HELP_WITH;
|
|
93
|
+
expect(() => requireModuleRootCapability(join(root, "nope"), run)).toThrow(/ojm vendor/);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("能力缺失时抛错,文案含现场版本与两种处置", () => {
|
|
97
|
+
const { root } = newProject();
|
|
98
|
+
const run = (_bin: string, args: string[]) =>
|
|
99
|
+
args[0] === "-V" ? "oj 0.1.84\n" : HELP_WITHOUT;
|
|
100
|
+
expect(() => requireModuleRootCapability(root, run)).toThrow(/oj 0\.1\.84/);
|
|
101
|
+
expect(() => requireModuleRootCapability(root, run)).toThrow(/ojm vendor/);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("能力缺失也写缓存(避免每次构建重探),换二进制后指纹变化自动重探", () => {
|
|
105
|
+
const { root, ojBin } = newProject();
|
|
106
|
+
expect(() => requireModuleRootCapability(root, (_b, args) => (args[0] === "-V" ? "oj 0.1.84" : HELP_WITHOUT))).toThrow();
|
|
107
|
+
expect(readCapsCache(ojBin)?.moduleRoot).toBe(false);
|
|
108
|
+
// 二进制更新后指纹变化 → 重探,能力补齐即放行
|
|
109
|
+
const before = statSync(ojBin).size;
|
|
110
|
+
writeFileSync(ojBin, "#!/bin/sh\necho upgraded\n", { mode: 0o755 });
|
|
111
|
+
expect(statSync(ojBin).size).not.toBe(before);
|
|
112
|
+
expect(requireModuleRootCapability(root, () => HELP_WITH).moduleRoot).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* oj 原生别名 `#/<模块名>/…` 在 TS 侧的投影(生成 `<project>/tsconfig.paths.json`)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么必须生成而不能手写一条通配:oj 的 `#/<name>/<tail>` 锚定「目标模块根」,在
|
|
5
|
+
* `src/<name>/{api,web}` 布局下即 `src/<name>/api`。tsconfig 的 `paths` 做不了这件事:
|
|
6
|
+
* - 每条规则的替换值只允许一个 `*`——想用单条 `"#/*"` 同时匹配模块名与尾路径,会被
|
|
7
|
+
* TS 以 TS5062 直接拒掉(两处通配只能二选一);
|
|
8
|
+
* - `paths` 是静态全局映射,表达不了 `#x`(本模块根,随引用方变化)。
|
|
9
|
+
* 所以只能**按模块逐条枚举** `"#/<name>/*": ["./src/<name>/api/*"]`。模块清单必须与
|
|
10
|
+
* `src/<name>/api/manifest.yaml` 同源,手写必然漂移 → 由 `ojm init` / `ojm build` / `ojm dev` 生成。
|
|
11
|
+
*
|
|
12
|
+
* 生成物只被 `src/tsconfig.json`(后端)`extends`:前端 tsconfig 不引,因为 web 侧
|
|
13
|
+
* 没有 `#` 解析(`build.ts` 的 esbuild/vite 不认别名),让编辑器「能过」而打包失败
|
|
14
|
+
* 比漏补 path 更坏。
|
|
15
|
+
*
|
|
16
|
+
* 模式串用 manifest 的 `name`(oj 的身份口径,v0.1.85 起与目录名脱钩),替换值用
|
|
17
|
+
* **目录名**——两者在正常工程里同值,但真分叉时只有按此口径才不会一边错位。
|
|
18
|
+
*/
|
|
19
|
+
import type { ProjectLayout } from "./layout";
|
|
20
|
+
import fs from "node:fs";
|
|
21
|
+
import path from "node:path";
|
|
22
|
+
import { parse } from "yaml";
|
|
23
|
+
import { tryResolveLayout } from "./layout";
|
|
24
|
+
|
|
25
|
+
/** 生成物文件名(工程根)。`src/tsconfig.json` 以 `../tsconfig.paths.json` 引用 */
|
|
26
|
+
export const ALIAS_PATHS_FILE = "tsconfig.paths.json";
|
|
27
|
+
|
|
28
|
+
/** 生成物头注释:tsconfig 允许注释,且这是唯一会被 `ojm` 覆写的 tsconfig */
|
|
29
|
+
const HEADER = [
|
|
30
|
+
"// 由 ojm 生成:ojm init / ojm build / ojm dev 会按 src/*/api/manifest.yaml 重写本文件,请勿手改。",
|
|
31
|
+
"//",
|
|
32
|
+
"// oj 别名 `#/<模块名>/…` 锚在目标模块根(本布局下即 src/<模块名>/api)。tsconfig 的",
|
|
33
|
+
"// paths 只能静态全局映射,且每条规则的替换值只允许一个 `*`(`./src/*/api/*` 会被 TS",
|
|
34
|
+
"// 以 TS5062 拒掉),故按模块逐条枚举。",
|
|
35
|
+
].join("\n");
|
|
36
|
+
|
|
37
|
+
/** 读模块根 manifest 的 `name`(oj 的模块身份);读不到/格式坏时回落到目录名 */
|
|
38
|
+
function manifestName(apiDir: string, fallback: string): string {
|
|
39
|
+
try {
|
|
40
|
+
const doc: unknown = parse(fs.readFileSync(path.join(apiDir, "manifest.yaml"), "utf-8"));
|
|
41
|
+
if (doc && typeof doc === "object" && "name" in doc) {
|
|
42
|
+
const name = (doc as { name?: unknown }).name;
|
|
43
|
+
if (typeof name === "string" && name.trim())
|
|
44
|
+
return name.trim();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {}
|
|
48
|
+
return fallback;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 模块名 → tsconfig `paths` 规则(仅 module-root 布局)。
|
|
53
|
+
*
|
|
54
|
+
* 键序按目录名排序(`scanModules` 已排序),保证同布局两次生成字节一致——
|
|
55
|
+
* 否则「生成物是否过期」的比对会因文件系统枚举顺序抖动而误报。
|
|
56
|
+
*/
|
|
57
|
+
export function aliasPathEntries(layout: ProjectLayout): Record<string, string[]> {
|
|
58
|
+
if (layout.kind !== "module-root")
|
|
59
|
+
return {};
|
|
60
|
+
const entries: Record<string, string[]> = {};
|
|
61
|
+
for (const mod of fs.readdirSync(layout.modulesSrc, { withFileTypes: true })) {
|
|
62
|
+
if (!mod.isDirectory() || mod.name.startsWith("."))
|
|
63
|
+
continue;
|
|
64
|
+
const apiDir = path.join(layout.modulesSrc, mod.name, "api");
|
|
65
|
+
if (!fs.existsSync(path.join(apiDir, "manifest.yaml")))
|
|
66
|
+
continue;
|
|
67
|
+
const name = manifestName(apiDir, mod.name);
|
|
68
|
+
entries[`#/${name}/*`] = [`./${path.posix.join("src", mod.name, "api", "*")}`];
|
|
69
|
+
}
|
|
70
|
+
return entries;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** 渲染 `tsconfig.paths.json`(含头注释;tsconfig 解析器接受 JSONC) */
|
|
74
|
+
export function renderAliasPaths(layout: ProjectLayout): string {
|
|
75
|
+
const entries = Object.entries(aliasPathEntries(layout));
|
|
76
|
+
const body = entries.length === 0
|
|
77
|
+
? "\t\t\"paths\": {}"
|
|
78
|
+
: `\t\t"paths": {\n${
|
|
79
|
+
entries
|
|
80
|
+
.map(([pattern, targets]) => `\t\t\t${JSON.stringify(pattern)}: [${targets.map(t => JSON.stringify(t)).join(", ")}]`)
|
|
81
|
+
.join(",\n")
|
|
82
|
+
}\n\t\t}`;
|
|
83
|
+
return `${HEADER}\n{\n\t"compilerOptions": {\n${body}\n\t}\n}\n`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 按当前模块清单同步生成物,**内容未变则不写**(避免每次 build 都产生无意义 diff)。
|
|
88
|
+
*
|
|
89
|
+
* 返回 `skipped` 表示不是 module-root 布局(历史布局的别名口径不同,不在此生成)。
|
|
90
|
+
*/
|
|
91
|
+
export function syncAliasPaths(projectRoot: string): "created" | "updated" | "unchanged" | "skipped" {
|
|
92
|
+
const layout = tryResolveLayout(projectRoot);
|
|
93
|
+
if (layout?.kind !== "module-root")
|
|
94
|
+
return "skipped";
|
|
95
|
+
const file = path.join(projectRoot, ALIAS_PATHS_FILE);
|
|
96
|
+
const next = renderAliasPaths(layout);
|
|
97
|
+
const prev = fs.existsSync(file) ? fs.readFileSync(file, "utf-8") : null;
|
|
98
|
+
if (prev === next)
|
|
99
|
+
return "unchanged";
|
|
100
|
+
fs.writeFileSync(file, next);
|
|
101
|
+
return prev === null ? "created" : "updated";
|
|
102
|
+
}
|
package/src/args.ts
CHANGED
|
@@ -40,13 +40,16 @@ export function parseApiArgs(argv: string[]): { dir: string, check: boolean, doc
|
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* `ojm build [--bundle] [--out <dir>]`。
|
|
43
|
-
* `--bundle
|
|
44
|
-
*
|
|
43
|
+
* `--bundle`:方案包构建(契约校验 → 确定性 tgz + sha256)。bundle 级契约位置:优先工程根
|
|
44
|
+
* `solution.yaml`、或 `src/<模块>/solution.yaml`(模块级方案,契约与所打包模块同位);旧方案回退
|
|
45
|
+
* `manifest.yaml`(同两处位置)。与站点级 `ojm build`(后端 oj build + 模块产物合并)互斥分工
|
|
46
|
+
* (devkit 手册「打包与分发」)。
|
|
45
47
|
* `--out` 的值不得以 `--` 开头(同 parseApiArgs 的消费式扫描,P6 同款坑)。
|
|
46
48
|
*/
|
|
47
|
-
export function parseBuildArgs(argv: string[]): { bundle: boolean, out?: string } {
|
|
49
|
+
export function parseBuildArgs(argv: string[]): { bundle: boolean, bundleName?: string, out?: string } {
|
|
48
50
|
const flags = new Set<string>();
|
|
49
51
|
let out: string | undefined;
|
|
52
|
+
let bundleName: string | undefined;
|
|
50
53
|
for (let i = 0; i < argv.length; i++) {
|
|
51
54
|
const a = argv[i];
|
|
52
55
|
if (a === "--out") {
|
|
@@ -54,11 +57,58 @@ export function parseBuildArgs(argv: string[]): { bundle: boolean, out?: string
|
|
|
54
57
|
i++; // 消费紧随的值,不被当 flag
|
|
55
58
|
continue;
|
|
56
59
|
}
|
|
60
|
+
// --bundle <name> / --bundle=<name>:只打这一份方案(无值 = 全部用户方案)
|
|
61
|
+
if (a === "--bundle") {
|
|
62
|
+
flags.add(a);
|
|
63
|
+
const next = argv[i + 1];
|
|
64
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
65
|
+
bundleName = next;
|
|
66
|
+
i++;
|
|
67
|
+
}
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (a.startsWith("--bundle=")) {
|
|
71
|
+
flags.add("--bundle");
|
|
72
|
+
bundleName = a.slice("--bundle=".length);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
57
75
|
if (a.startsWith("--")) {
|
|
58
76
|
flags.add(a);
|
|
59
77
|
}
|
|
60
78
|
}
|
|
61
|
-
return { bundle: flags.has("--bundle"), out };
|
|
79
|
+
return { bundle: flags.has("--bundle"), bundleName, out };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* `ojm new <name> [--layout container|none] [--api]`:新方案骨架生成参数。
|
|
84
|
+
* name 为首个非 flag 位置参数(必填,由调用方判空——非交互/CI 友好)。
|
|
85
|
+
*/
|
|
86
|
+
export function parseNewArgs(argv: string[]): { name?: string, layout?: "container" | "none", api?: boolean } {
|
|
87
|
+
let name: string | undefined;
|
|
88
|
+
let layout: "container" | "none" | undefined;
|
|
89
|
+
let api = false;
|
|
90
|
+
for (let i = 0; i < argv.length; i++) {
|
|
91
|
+
const a = argv[i];
|
|
92
|
+
if (a === "--layout") {
|
|
93
|
+
const v = argv[i + 1];
|
|
94
|
+
if (v !== "container" && v !== "none")
|
|
95
|
+
throw new Error(`[ojm] --layout 只接受 container | none(实际:${v})`);
|
|
96
|
+
layout = v;
|
|
97
|
+
i++;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (a === "--api") {
|
|
101
|
+
api = true;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (a !== undefined && !a.startsWith("--")) {
|
|
105
|
+
if (name !== undefined)
|
|
106
|
+
throw new Error(`[ojm] ojm new 只接受一个方案名(多余参数:${a})`);
|
|
107
|
+
name = a;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return { name, layout, api };
|
|
62
112
|
}
|
|
63
113
|
|
|
64
114
|
/**
|
package/src/build.ts
CHANGED
|
@@ -10,8 +10,10 @@ import { pathToFileURL } from "node:url";
|
|
|
10
10
|
import tailwindcss from "@tailwindcss/vite";
|
|
11
11
|
import { build as esbuild } from "esbuild";
|
|
12
12
|
import { build } from "vite";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
13
|
+
import { ALIAS_PATHS_FILE, syncAliasPaths } from "./alias-paths";
|
|
14
|
+
import { assertModuleList, loadSiteConfig, mergeModuleList, resolveModuleEntry } from "./config";
|
|
15
|
+
import { resolveConfigPath, resolveLayout, scanModules, tryResolveLayout } from "./layout";
|
|
16
|
+
import { requireModuleRootCapability } from "./oj-caps";
|
|
15
17
|
import { isSharedDep, SHARED_DEPS } from "./shared-deps";
|
|
16
18
|
import { normalizeSiteBase, rewriteIndexHtml } from "./site-base";
|
|
17
19
|
import { resolveOjBin } from "./vendor";
|
|
@@ -494,18 +496,33 @@ function assertResolvableSpecifiers(
|
|
|
494
496
|
* 无后端源码返回 false(纯前端工程跳过)。
|
|
495
497
|
*/
|
|
496
498
|
export async function buildBackend(projectRoot: string): Promise<boolean> {
|
|
497
|
-
|
|
499
|
+
// 没有模块源码的工程(如 market 的隔离站点根)直接跳过——不能因为探测不到布局就抛
|
|
500
|
+
const layout = tryResolveLayout(projectRoot);
|
|
501
|
+
const moduleRoot = layout?.kind === "module-root";
|
|
502
|
+
const apiSrc = moduleRoot
|
|
503
|
+
? layout.modulesSrc
|
|
504
|
+
: path.join(projectRoot, "api/src");
|
|
498
505
|
if (!fs.existsSync(apiSrc))
|
|
499
506
|
return false;
|
|
500
507
|
|
|
501
508
|
const oj = resolveOjBin(projectRoot);
|
|
502
509
|
if (!fs.existsSync(oj)) {
|
|
503
510
|
throw new Error(
|
|
504
|
-
`[ojm] 找到
|
|
511
|
+
`[ojm] 找到 ${path.relative(projectRoot, apiSrc) || "."} 但缺少 ${oj}。\n`
|
|
505
512
|
+ "请重跑 ojm init 幂等补缺(不会覆盖 config 与用户代码)。",
|
|
506
513
|
);
|
|
507
514
|
}
|
|
508
|
-
|
|
515
|
+
// 能力闸门先于任何写入:失败时磁盘上不留半成品
|
|
516
|
+
if (moduleRoot)
|
|
517
|
+
requireModuleRootCapability(projectRoot);
|
|
518
|
+
|
|
519
|
+
// 别名投影:模块增删后要让 TS 的 paths 跟上(否则 `#/<模块名>/…` 在编辑器/typecheck
|
|
520
|
+
// 里解析不到,退化成 any)。内容未变不写盘。
|
|
521
|
+
const syncedPaths = syncAliasPaths(projectRoot);
|
|
522
|
+
if (syncedPaths === "created" || syncedPaths === "updated")
|
|
523
|
+
console.log(`[ojm] 已${syncedPaths === "created" ? "生成" : "更新"} ${ALIAS_PATHS_FILE}(\`#/<模块名>/…\` 的 TS paths 投影)`);
|
|
524
|
+
|
|
525
|
+
const apiDist = layout?.apiDist ?? path.join(projectRoot, "api", "dist");
|
|
509
526
|
console.log("[ojm] oj build(后端)…");
|
|
510
527
|
// 必须显式给 -c:oj build 的 -c 缺省是 ./config.yaml(相对 CWD),而 config 在
|
|
511
528
|
// <project>/config/ 下——漏传会静默读不到配置,连带 tasks 镜像目录回落默认 "tasks"、
|
|
@@ -518,7 +535,17 @@ export async function buildBackend(projectRoot: string): Promise<boolean> {
|
|
|
518
535
|
// 2) `if (cond) x = y` → 压成 `cond && x = y`,赋值作 && 右项缺括号 →
|
|
519
536
|
// SyntaxError: Invalid left-hand side in assignment(端点 500)。
|
|
520
537
|
// 0.1.32 / 0.1.50 均复现。产物体积换正确性,等上游修好再撤这个旗标。
|
|
521
|
-
|
|
538
|
+
// v0.1.85:`--module-root <dir>` 与 `-d` 互斥(都是「源码树根」)。新布局给 src,
|
|
539
|
+
// oj 在子树内递归找 manifest.yaml(命中 src/<name>/api/manifest.yaml 即停)。
|
|
540
|
+
const sourceArgs = layout?.moduleRoot ? ["--module-root", layout.moduleRoot] : ["-d", apiSrc];
|
|
541
|
+
execFileSync(oj, [
|
|
542
|
+
"build",
|
|
543
|
+
...configArgs,
|
|
544
|
+
...sourceArgs,
|
|
545
|
+
"-o",
|
|
546
|
+
apiDist,
|
|
547
|
+
"--no-minify",
|
|
548
|
+
], { stdio: "inherit" });
|
|
522
549
|
// 宿主 versions.json 双落点:站点壳从 shell-dist 拷进 web/dist(mergeShellSite),
|
|
523
550
|
// 而方案安装器读的是 `<api>/dist/versions.json`(B-11:与 manifests.yaml 同层,用于
|
|
524
551
|
// peerRuntime 强制比对)。两处必须同源,否则新工程 `ojm build` 完仍装不上方案——
|
|
@@ -569,8 +596,13 @@ export async function buildModules(
|
|
|
569
596
|
projectRoot: string,
|
|
570
597
|
opts: { mergeSite?: boolean } = {},
|
|
571
598
|
): Promise<BuiltModule[]> {
|
|
572
|
-
const config = await loadModulesConfig(projectRoot);
|
|
573
599
|
const layout = resolveLayout(projectRoot);
|
|
600
|
+
const config = await loadSiteConfig(projectRoot);
|
|
601
|
+
// module-root:模块清单由 src/*/web/entry.ts 扫描得出(web.config.ts 仅可选覆盖);
|
|
602
|
+
// 历史布局:仍以 web.config.ts 的 modules 为唯一真源。
|
|
603
|
+
const modules = layout.kind === "module-root"
|
|
604
|
+
? mergeModuleList(scanModules(layout), config.modules, projectRoot)
|
|
605
|
+
: assertModuleList(projectRoot, config.modules);
|
|
574
606
|
const outDir = layout.distDir;
|
|
575
607
|
const baseUrl = config.baseUrl ?? "";
|
|
576
608
|
|
|
@@ -584,7 +616,7 @@ export async function buildModules(
|
|
|
584
616
|
|
|
585
617
|
const built: BuiltModule[] = [];
|
|
586
618
|
|
|
587
|
-
for (const item of
|
|
619
|
+
for (const item of modules) {
|
|
588
620
|
if (item.enabled === false) {
|
|
589
621
|
console.log(`[ojm] 跳过已禁用模块 ${item.name}`);
|
|
590
622
|
continue;
|
|
@@ -595,7 +627,7 @@ export async function buildModules(
|
|
|
595
627
|
|
|
596
628
|
if (definition.name !== item.name) {
|
|
597
629
|
throw new Error(
|
|
598
|
-
|
|
630
|
+
`模块名不一致:${layout.kind === "module-root" ? `src/${item.name}` : "web.config.ts"} 中为 "${item.name}",entry.ts 中为 "${definition.name}"`,
|
|
599
631
|
);
|
|
600
632
|
}
|
|
601
633
|
|