@oj-module/cli 0.2.17 → 0.2.20
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 +260 -162
- package/devkit/menu-rules.md +3 -4
- 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 +2 -2
- 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__/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/build.ts +41 -9
- package/src/bundle.ts +25 -13
- package/src/config.ts +82 -9
- package/src/contract/__tests__/market-client.integration.test.ts +2 -2
- 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 +22 -7
- package/src/index.ts +1 -1
- package/src/info.ts +20 -3
- package/src/init.ts +91 -21
- package/src/layout.ts +282 -24
- package/src/lifecycle/__tests__/bundle.test.ts +16 -1
- package/src/lifecycle/__tests__/manifest-contract.test.ts +17 -58
- package/src/manifest-contract.ts +2 -25
- package/src/market/__tests__/bootstrap.test.ts +17 -9
- package/src/market/bootstrap.ts +30 -17
- 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 +6 -6
- 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 +1 -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}/market/api.ts +25 -15
- 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/defaultClient.ts +7 -1
- package/templates/{web/src/oj-market → src/oj-market/web}/components/InstallDialog.tsx +80 -5
- package/templates/{web/src/oj-market → src/oj-market/web}/hooks/useMarket.ts +6 -1
- 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/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}/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 +1 -1
- 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/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/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/{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}/fixtures/l1-test-user.sql +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/{api/src/_platform → src/_platform/api}/seed.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/seed.pg.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/seed.sql +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}/api/context.ts +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}/components/RollbackDialog.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/components/UninstallConfirm.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}/locales/en-US.json +0 -0
- /package/templates/{web/src/system → src/system/web}/locales/zh-CN.json +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}/l1-builder.test.ts +0 -0
- /package/templates/{api/tests → tests}/rbac-tables.spec.ts +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { InstallContext } from "../../../templates/
|
|
1
|
+
import type { InstallContext } from "../../../templates/src/_platform/api/_lifecycle/steps/preflight";
|
|
2
2
|
|
|
3
3
|
// BDD:bundle 级 manifest.yaml 契约校验(P1-11,C2 规则构建列)。
|
|
4
4
|
// 规则真源:docs/prd/solution/202610171254-lifecycle-manifest-contract.md(C2-01~33 构建列,§4.1 矩阵);
|
|
@@ -13,8 +13,8 @@ import { describe, expect, it } from "vitest";
|
|
|
13
13
|
import { parse } from "yaml";
|
|
14
14
|
// 跨端引用(双端一致钉):被测实现同仓分居两运行时(计划 §1「两运行时取舍」),
|
|
15
15
|
// 测试直接互调,比「两套件各读同一夹具」更强——漂移即本文件红。
|
|
16
|
-
import { preflight } from "../../../templates/
|
|
17
|
-
import { checkLocalesComplete, checkTablesAgainstSchema,
|
|
16
|
+
import { preflight } from "../../../templates/src/_platform/api/_lifecycle/steps/preflight";
|
|
17
|
+
import { checkLocalesComplete, checkTablesAgainstSchema, parseManifest, validateContract } from "../../manifest-contract";
|
|
18
18
|
|
|
19
19
|
const FIXTURES = join(dirname(fileURLToPath(import.meta.url)), "fixtures");
|
|
20
20
|
const readText = (p: string) => readFileSync(join(FIXTURES, p), "utf8");
|
|
@@ -214,72 +214,31 @@ describe("checkLocalesComplete(C2-25:构建期缺 key fail-fast)", () => {
|
|
|
214
214
|
});
|
|
215
215
|
});
|
|
216
216
|
|
|
217
|
-
// ──
|
|
217
|
+
// ── 归组父标题不再进引用面(0.2.18:`menu.main` 是模块级标题的唯一真源)──
|
|
218
218
|
|
|
219
|
-
describe("
|
|
219
|
+
describe("main/admin 路由不再要求归组父标题", () => {
|
|
220
220
|
const baseManifest = (): any => ({
|
|
221
221
|
name: "demo-solution",
|
|
222
222
|
version: "0.1.0",
|
|
223
223
|
routes: [
|
|
224
224
|
{ path: "/demo-solution", category: "main", order: 200, title_key: "demo-solution:menu.main" },
|
|
225
|
+
{ path: "/demo-solution/users", category: "admin", order: 210, title_key: "demo-solution:menu.users" },
|
|
225
226
|
],
|
|
226
227
|
});
|
|
227
|
-
const ZH_FULL = JSON.stringify({ menu: { main: "主", group: "分组" } });
|
|
228
|
-
const EN_FULL = JSON.stringify({ menu: { main: "Main", group: "Group" } });
|
|
229
228
|
|
|
230
|
-
it("
|
|
231
|
-
|
|
229
|
+
it("声明 main/admin 路由、locale 只有 menu.main → 零违规", () => {
|
|
230
|
+
const zh = JSON.stringify({ menu: { main: "主", users: "用户" } });
|
|
231
|
+
const en = JSON.stringify({ menu: { main: "Main", users: "Users" } });
|
|
232
|
+
expect(checkLocalesComplete(baseManifest(), zh, en)).toEqual([]);
|
|
232
233
|
});
|
|
233
234
|
|
|
234
|
-
it("
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
];
|
|
240
|
-
expect(
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it("groupI18nKeys:仅 hidden 路由不产 group key(C3)", () => {
|
|
244
|
-
const m = baseManifest();
|
|
245
|
-
m.routes = [{ path: "/x", category: "hidden", order: 210, title_key: "demo-solution:menu.x" }];
|
|
246
|
-
expect(groupI18nKeys(m)).toEqual([]);
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it("c1 正例:menu.group 双语齐备 → 零违规", () => {
|
|
250
|
-
expect(checkLocalesComplete(baseManifest(), ZH_FULL, EN_FULL)).toEqual([]);
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
it("c2 反例:zh-CN 缺 menu.group → C2-43(非 C2-25,去重生效)", () => {
|
|
254
|
-
const zh = JSON.stringify({ menu: { main: "主" } });
|
|
255
|
-
const v = checkLocalesComplete(baseManifest(), zh, EN_FULL);
|
|
256
|
-
const hit = v.find(x => x.includes("C2-43"));
|
|
257
|
-
expect(hit, `期望 C2-43,实际:\n${v.join("\n")}`).toBeTruthy();
|
|
258
|
-
expect(hit).not.toContain("C2-25");
|
|
259
|
-
expect(hit).toContain("menu.group");
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
it("c4 反例:en-US 缺 menu.group → C2-43 指明 en-US", () => {
|
|
263
|
-
const en = JSON.stringify({ menu: { main: "Main" } });
|
|
264
|
-
const v = checkLocalesComplete(baseManifest(), ZH_FULL, en);
|
|
265
|
-
const hit = v.find(x => x.includes("C2-43"));
|
|
266
|
-
expect(hit).toBeTruthy();
|
|
267
|
-
expect(hit).toContain("en-US");
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
it("去重:多条 main 路由只产一条 group key,缺失时按语言各报一次(非按路由重复)", () => {
|
|
271
|
-
const m = baseManifest();
|
|
272
|
-
m.routes = [
|
|
273
|
-
{ path: "/a", category: "main", order: 200, title_key: "demo-solution:menu.a" },
|
|
274
|
-
{ path: "/b", category: "main", order: 210, title_key: "demo-solution:menu.b" },
|
|
275
|
-
];
|
|
276
|
-
const v = checkLocalesComplete(m, JSON.stringify({ menu: { main: "主" } }), JSON.stringify({ menu: { main: "Main" } }));
|
|
277
|
-
const c243 = v.filter(x => x.includes("C2-43"));
|
|
278
|
-
// 关键:2 条路由只产 1 个 group key → 缺失时按语言各报 1 次 = 2 条 C2-43,
|
|
279
|
-
// 而非 2 路由 × 2 语言 = 4 条(去重生效的判据是「< 路由数 × 语言数」)
|
|
280
|
-
expect(c243.length).toBe(2);
|
|
281
|
-
expect(c243.some(x => x.includes("zh-CN 缺少归组父标题 key「demo-solution:menu.group」"))).toBe(true);
|
|
282
|
-
expect(c243.some(x => x.includes("en-US 缺少归组父标题 key「demo-solution:menu.group」"))).toBe(true);
|
|
235
|
+
it("根标题 menu.main 仍受 C2-25 约束:缺则报 C2-25", () => {
|
|
236
|
+
const zh = JSON.stringify({ menu: { users: "用户" } });
|
|
237
|
+
const en = JSON.stringify({ menu: { main: "Main", users: "Users" } });
|
|
238
|
+
const v = checkLocalesComplete(baseManifest(), zh, en);
|
|
239
|
+
expect(v).toHaveLength(1);
|
|
240
|
+
expect(v[0]).toContain("C2-25");
|
|
241
|
+
expect(v[0]).toContain("demo-solution:menu.main");
|
|
283
242
|
});
|
|
284
243
|
});
|
|
285
244
|
|
package/src/manifest-contract.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// bundle 级 manifest.yaml 契约校验(P1-11,`ojm build --bundle` 构建期执行点)。
|
|
2
|
-
// 规则真源:docs/prd/solution/202610171254-lifecycle-manifest-contract.md §2/§4(C2-01~33 构建列;C2-34~42 为归档签名与信任,见 sign.ts
|
|
2
|
+
// 规则真源:docs/prd/solution/202610171254-lifecycle-manifest-contract.md §2/§4(C2-01~33 构建列;C2-34~42 为归档签名与信任,见 sign.ts);
|
|
3
3
|
// 期望真源:src/lifecycle/__tests__/fixtures/(fixture-valid 零违规 / fixture-invalid 七码齐报)。
|
|
4
4
|
// 双端一致:本实现与 isolate 侧 preflight(templates/api/.../steps/preflight.ts)共享同一组夹具,
|
|
5
5
|
// 正则与规则**逐字一致**(§4.1 矩阵构建列 vs 安装列的执行点差异见各规则注释)——
|
|
@@ -581,15 +581,6 @@ export function validateContract(m: unknown): string[] {
|
|
|
581
581
|
return v0;
|
|
582
582
|
}
|
|
583
583
|
|
|
584
|
-
/** C2-43 引用面:声明 main/admin 路由的方案须提供归组父标题 `<name>:menu.group`(单 bundle 单 name,去重只产一条) */
|
|
585
|
-
export function groupI18nKeys(m: BundleManifest): string[] {
|
|
586
|
-
if (!isObj(m) || typeof m.name !== "string")
|
|
587
|
-
return [];
|
|
588
|
-
const routes = Array.isArray(m.routes) ? m.routes : [];
|
|
589
|
-
const hasGroupable = routes.some(r => isObj(r) && (r.category === "main" || r.category === "admin"));
|
|
590
|
-
return hasGroupable ? [`${m.name}:menu.group`] : [];
|
|
591
|
-
}
|
|
592
|
-
|
|
593
584
|
/** 收集 manifest 引用的全部 i18n key(C2-25 locales 齐备检查的引用面;bundle.ts 以此判定 locales 文件是否必查) */
|
|
594
585
|
export function referencedI18nKeys(m: BundleManifest): string[] {
|
|
595
586
|
const keys: string[] = [];
|
|
@@ -620,10 +611,6 @@ export function referencedI18nKeys(m: BundleManifest): string[] {
|
|
|
620
611
|
}
|
|
621
612
|
}
|
|
622
613
|
}
|
|
623
|
-
// C2-43:归组父标题纳入引用面,交由 checkLocalesComplete 双语齐备(改报 C2-43)
|
|
624
|
-
for (const k of groupI18nKeys(m)) {
|
|
625
|
-
keys.push(k);
|
|
626
|
-
}
|
|
627
614
|
return keys;
|
|
628
615
|
}
|
|
629
616
|
|
|
@@ -646,12 +633,9 @@ export function checkLocalesComplete(m: unknown, zhText: string, enText: string)
|
|
|
646
633
|
if (!isObj(m))
|
|
647
634
|
return [v("C2-25", "manifest 不是键值对象,无法核对 locales 引用")];
|
|
648
635
|
const locales: [string, Record<string, unknown> | null][] = [["zh-CN", parseJson(zhText)], ["en-US", parseJson(enText)]];
|
|
649
|
-
// group key 识别(C2-43):归组父标题缺失须归因 C2-43 而非笼统 C2-25,避免重复报错
|
|
650
|
-
const groupKeys = new Set(groupI18nKeys(m));
|
|
651
636
|
const out: string[] = [];
|
|
652
637
|
for (const key of referencedI18nKeys(m)) {
|
|
653
638
|
const nsKey = key.includes(":") ? key.slice(key.indexOf(":") + 1) : key;
|
|
654
|
-
const isGroup = groupKeys.has(key);
|
|
655
639
|
for (const [lang, tree] of locales) {
|
|
656
640
|
if (tree === null) {
|
|
657
641
|
out.push(v("C2-25", `${lang} locale 不是合法 JSON 对象,无法核对引用 key`));
|
|
@@ -659,14 +643,7 @@ export function checkLocalesComplete(m: unknown, zhText: string, enText: string)
|
|
|
659
643
|
}
|
|
660
644
|
const val = lookupKey(tree, nsKey);
|
|
661
645
|
if (typeof val !== "string" || val === "") {
|
|
662
|
-
out.push(
|
|
663
|
-
v(
|
|
664
|
-
isGroup ? "C2-43" : "C2-25",
|
|
665
|
-
isGroup
|
|
666
|
-
? `${lang} 缺少归组父标题 key「${key}」(须为嵌套 menu.group,C2-43;声明 main/admin 路由即须提供)`
|
|
667
|
-
: `${lang} 缺少 manifest 引用的 key「${key}」(双语齐备,D4;构建期 fail-fast)`,
|
|
668
|
-
),
|
|
669
|
-
);
|
|
646
|
+
out.push(v("C2-25", `${lang} 缺少 manifest 引用的 key「${key}」(双语齐备,D4;构建期 fail-fast)`));
|
|
670
647
|
}
|
|
671
648
|
}
|
|
672
649
|
}
|
|
@@ -28,8 +28,16 @@ function fakeStarter() {
|
|
|
28
28
|
function newSourceProject(): string {
|
|
29
29
|
const root = mkdtempSync(join(tmpdir(), "ojm-bootstrap-src-"));
|
|
30
30
|
mkdirSync(join(root, "bin", "plugins"), { recursive: true });
|
|
31
|
-
|
|
32
|
-
writeFileSync(
|
|
31
|
+
// 假 oj 必须过 §7 双探:`-V` 报版本、`build --help` 含 --module-root,否则 buildBackend 在闸门处 fail-fast
|
|
32
|
+
writeFileSync(
|
|
33
|
+
join(root, "bin", "oj"),
|
|
34
|
+
"#!/bin/sh\n"
|
|
35
|
+
+ "if [ \"$1\" = \"-V\" ]; then echo oj 0.1.85; exit 0; fi\n"
|
|
36
|
+
+ "if [ \"$1\" = \"build\" ]; then echo \" --module-root <MODULE_ROOT> 源码树根\"; exit 0; fi\n"
|
|
37
|
+
+ "echo fake-oj\n",
|
|
38
|
+
{ mode: 0o755 },
|
|
39
|
+
);
|
|
40
|
+
writeFileSync(join(root, "bin", ".oj-version"), "v0.1.85\n");
|
|
33
41
|
writeFileSync(join(root, "bin", "plugins", "auth.wasm"), "plugin", "utf-8");
|
|
34
42
|
mkdirSync(join(root, "api", "src", "_platform"), { recursive: true });
|
|
35
43
|
writeFileSync(join(root, "api", "src", "_platform", "seed.sql"), "-- seed\n", "utf-8");
|
|
@@ -60,7 +68,7 @@ describe("given 一个装了 oj 二进制的源工程 When 自举隔离站点",
|
|
|
60
68
|
|
|
61
69
|
const site = await bootstrapIsolatedSite({ projectRoot: src, starter });
|
|
62
70
|
|
|
63
|
-
expect(existsSync(join(site.root, "
|
|
71
|
+
expect(existsSync(join(site.root, "src", "_platform", "api", "seed.sql"))).toBe(true);
|
|
64
72
|
expect(existsSync(join(site.root, "bin", "oj"))).toBe(true);
|
|
65
73
|
expect(existsSync(join(site.root, "bin", "plugins", "auth.wasm"))).toBe(true);
|
|
66
74
|
expect(site.root).not.toBe(src);
|
|
@@ -75,8 +83,8 @@ describe("given 一个装了 oj 二进制的源工程 When 自举隔离站点",
|
|
|
75
83
|
|
|
76
84
|
expect(site.port).toBeGreaterThan(1024);
|
|
77
85
|
expect(site.port).not.toBe(9778);
|
|
78
|
-
expect(started[0]?.configPath).toBe(join(site.root, "
|
|
79
|
-
const cfg = readFileSync(join(site.root, "
|
|
86
|
+
expect(started[0]?.configPath).toBe(join(site.root, "config.yaml"));
|
|
87
|
+
const cfg = readFileSync(join(site.root, "config.yaml"), "utf-8");
|
|
80
88
|
expect(cfg).toContain(`port: ${site.port}`);
|
|
81
89
|
await site.cleanup();
|
|
82
90
|
});
|
|
@@ -87,7 +95,7 @@ describe("given 一个装了 oj 二进制的源工程 When 自举隔离站点",
|
|
|
87
95
|
|
|
88
96
|
const site = await bootstrapIsolatedSite({ projectRoot: src, starter });
|
|
89
97
|
|
|
90
|
-
const cfg = readFileSync(join(site.root, "
|
|
98
|
+
const cfg = readFileSync(join(site.root, "config.yaml"), "utf-8");
|
|
91
99
|
expect(cfg).not.toContain("__JWT_SECRET__");
|
|
92
100
|
await site.cleanup();
|
|
93
101
|
});
|
|
@@ -113,7 +121,8 @@ describe("given 一个装了 oj 二进制的源工程 When 自举隔离站点",
|
|
|
113
121
|
|
|
114
122
|
expect(site.publisher.privateKeyPem).toContain("PRIVATE KEY");
|
|
115
123
|
const pemFiles = listFiles(site.root).filter(f => f.endsWith(".pem"));
|
|
116
|
-
|
|
124
|
+
// listFiles 返回相对隔离根的路径;站点自身证书三件套在 config/ 下,其余任何 PEM 都是泄漏
|
|
125
|
+
const outside = pemFiles.filter(f => !f.startsWith("config/"));
|
|
117
126
|
expect(outside).toEqual([]);
|
|
118
127
|
await site.cleanup();
|
|
119
128
|
});
|
|
@@ -144,8 +153,7 @@ describe("given 一个装了 oj 二进制的源工程 When 自举隔离站点",
|
|
|
144
153
|
describe("given 源工程没装 oj 二进制 When 自举", () => {
|
|
145
154
|
it("then 人话报错指向 ojm vendor,且不留半成品临时根", async () => {
|
|
146
155
|
const root = mkdtempSync(join(tmpdir(), "ojm-bootstrap-nooj-"));
|
|
147
|
-
|
|
148
|
-
writeFileSync(join(root, "api", "config.yaml"), "server:\n port: 9778\n", "utf-8");
|
|
156
|
+
writeFileSync(join(root, "config.yaml"), "server:\n port: 9778\n", "utf-8");
|
|
149
157
|
const { starter } = fakeStarter();
|
|
150
158
|
|
|
151
159
|
await expect(bootstrapIsolatedSite({ projectRoot: root, starter }))
|
package/src/market/bootstrap.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import type { OjProcess } from "../oj";
|
|
19
19
|
import { Buffer } from "node:buffer";
|
|
20
20
|
import { createHash, createPrivateKey, randomBytes } from "node:crypto";
|
|
21
|
-
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
21
|
+
import { cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
22
22
|
import net from "node:net";
|
|
23
23
|
import { tmpdir } from "node:os";
|
|
24
24
|
import path from "node:path";
|
|
@@ -26,6 +26,7 @@ import path from "node:path";
|
|
|
26
26
|
import process from "node:process";
|
|
27
27
|
import { fileURLToPath } from "node:url";
|
|
28
28
|
import { buildBackend } from "../build";
|
|
29
|
+
import { resolveLayout } from "../layout";
|
|
29
30
|
import { exportRawPublicKeyHex, fingerprintOfPublicKey } from "../lifecycle/sign";
|
|
30
31
|
import { startOj } from "../oj";
|
|
31
32
|
import { mintDevCert } from "../oj-cert";
|
|
@@ -49,7 +50,7 @@ export interface IsolatedPublisher {
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
export interface IsolatedSite {
|
|
52
|
-
/** 临时根(含 api/ bin/ cdn/) */
|
|
53
|
+
/** 临时根(含 config.yaml / src/ / api/dist/ / bin/ / cdn/) */
|
|
53
54
|
root: string
|
|
54
55
|
port: number
|
|
55
56
|
baseUrl: string
|
|
@@ -94,19 +95,26 @@ export async function bootstrapIsolatedSite(opts: BootstrapOptions = {}): Promis
|
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
const root = mkdtempSync(path.join(tmpdir(), "ojm-trial-"));
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
// 新布局(`src/<name>/{api,web}`):config.yaml 在隔离根,源码树根为 <root>/src,
|
|
99
|
+
// 构建产物仍是扁平根 <root>/api/dist(与真实工程同形)。
|
|
100
|
+
const srcDir = path.join(root, "src");
|
|
101
|
+
const configPath = path.join(root, "config.yaml");
|
|
99
102
|
const cdnDir = path.join(root, "cdn");
|
|
100
|
-
const dbPath = path.join(
|
|
103
|
+
const dbPath = path.join(root, "db.sqlite");
|
|
101
104
|
|
|
102
|
-
// 1)
|
|
105
|
+
// 1) 后端源码树 + config(模板由 cli 包内置,init 同源;与 cli→templates 依赖红线不冲突——那是运行期文件而非 import)
|
|
103
106
|
const cliRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
104
|
-
const
|
|
105
|
-
if (!existsSync(path.join(
|
|
106
|
-
throw new Error(`[ojm] cli 内置模板缺失:${
|
|
107
|
+
const templateDir = path.join(cliRoot, "templates");
|
|
108
|
+
if (!existsSync(path.join(templateDir, "config.yaml"))) {
|
|
109
|
+
throw new Error(`[ojm] cli 内置模板缺失:${templateDir}(@oj-module/cli 安装不完整?)`);
|
|
107
110
|
}
|
|
108
|
-
|
|
109
|
-
|
|
111
|
+
// 只拷后端侧(`src/<name>/api`):隔离站点不构建前端,web 源码拷过去只是噪音
|
|
112
|
+
for (const entry of readdirSync(path.join(templateDir, "src"), { withFileTypes: true })) {
|
|
113
|
+
const apiSide = path.join(templateDir, "src", entry.name, "api");
|
|
114
|
+
if (entry.isDirectory() && existsSync(apiSide))
|
|
115
|
+
cpSync(apiSide, path.join(srcDir, entry.name, "api"), { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
writeFileSync(configPath, readFileSync(path.join(templateDir, "config.yaml"), "utf-8"), "utf-8");
|
|
110
118
|
|
|
111
119
|
// 2) oj 二进制与插件(startOj 按 <root>/bin/oj 解析;plugins 让 auth 插件可用)
|
|
112
120
|
mkdirSync(path.join(root, "bin"), { recursive: true });
|
|
@@ -134,10 +142,13 @@ export async function bootstrapIsolatedSite(opts: BootstrapOptions = {}): Promis
|
|
|
134
142
|
const siteDir = path.join(root, "site");
|
|
135
143
|
mkdirSync(siteDir, { recursive: true });
|
|
136
144
|
writeFileSync(path.join(siteDir, "index.html"), "<!doctype html><html><body>ojm trial site</body></html>\n", "utf-8");
|
|
137
|
-
cfg = cfg.replace("web:
|
|
145
|
+
cfg = cfg.replace("web: ./cdn", `web: ${cdnDir}\n - prefix: /\n web: ${siteDir}\n spa: true`);
|
|
138
146
|
// 安装目标恒为 release 部署:装好的模块落在 api/dist,dev 形态(--api-path src)发现不了。
|
|
139
147
|
// 故隔离站点以 dist 起站,platform.root 同步改写(oj 校验 root 必须是生效的 api 挂载目录)。
|
|
140
|
-
|
|
148
|
+
// root 相对 **config 所在目录** 解析——config 在隔离根,产物在 <root>/api/dist,故不能写死
|
|
149
|
+
// `dist`(那是 config 还在 api/ 时的旧口径),按布局取值。
|
|
150
|
+
const apiDistRel = path.relative(root, resolveLayout(root).apiDist).split(path.sep).join("/");
|
|
151
|
+
cfg = cfg.replace(" root: src", ` root: ${apiDistRel}`);
|
|
141
152
|
// auth 守卫在装配期读 anonymous_paths,安装器注册的新匿名端点要重启才生效(首次安装 smoke 会 401)。
|
|
142
153
|
// 隔离站点直接预置被测方案的探活路径——与 scripts/verify-solution-smoke/bootstrap-host.mjs 同手法,
|
|
143
154
|
// 让 trial 验的是安装机制本身,而不是重启时序。
|
|
@@ -149,8 +160,8 @@ export async function bootstrapIsolatedSite(opts: BootstrapOptions = {}): Promis
|
|
|
149
160
|
cfg = `${cfg.trimEnd()}\nfs:\n root: .\n readonly: false\n`;
|
|
150
161
|
writeFileSync(configPath, cfg, "utf-8");
|
|
151
162
|
|
|
152
|
-
// 4) 站点开发证书(oj serve 无绕过开关;mintDevCert 落
|
|
153
|
-
await mintDevCert(path.join(
|
|
163
|
+
// 4) 站点开发证书(oj serve 无绕过开关;mintDevCert 落 config.yaml 同级的 config/ 三件套)
|
|
164
|
+
await mintDevCert(path.join(root, "config"));
|
|
154
165
|
|
|
155
166
|
// 5) 一次性发布方身份:seed 派生,私钥只在内存
|
|
156
167
|
const keyId = "trial.publisher";
|
|
@@ -168,7 +179,7 @@ export async function bootstrapIsolatedSite(opts: BootstrapOptions = {}): Promis
|
|
|
168
179
|
const starter = opts.starter ?? ((cfgPath: string, base: string, apiSrc: string) => startOj(cfgPath, base, apiSrc));
|
|
169
180
|
let oj: OjProcess;
|
|
170
181
|
try {
|
|
171
|
-
oj = starter(configPath, "/api", path.join(
|
|
182
|
+
oj = starter(configPath, "/api", path.join(root, "api", "dist"));
|
|
172
183
|
await oj.ready;
|
|
173
184
|
}
|
|
174
185
|
catch (error) {
|
|
@@ -245,7 +256,9 @@ function freePort(): Promise<number> {
|
|
|
245
256
|
function teardownReport(root: string, port: number): string[] {
|
|
246
257
|
const lines = [
|
|
247
258
|
`隔离根:${root}`,
|
|
248
|
-
"
|
|
259
|
+
" config.yaml + config/ 证书 + db.sqlite 站点配置面",
|
|
260
|
+
" src/ 后端模块源码树(src/<name>/api,拷自 cli 内置模板)",
|
|
261
|
+
" api/dist/ 构建产物(安装目标)",
|
|
249
262
|
" bin/ oj 二进制与插件(拷贝自源工程)",
|
|
250
263
|
" cdn/ 隔离 CDN(fixture 包 + 两份索引 + 签名)",
|
|
251
264
|
`监听端口:127.0.0.1:${port}(进程已停)`,
|
package/src/oj-caps.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* oj 二进制**能力**探针(设计 202610220010 §7「双探」)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么不能只看版本号:`bin/oj` 允许手改/手替(vendor.ts:143 的逃生门),
|
|
5
|
+
* `bin/.oj-version` 标记会漂移;且 vendor.ts:102-110 已实证「`oj -V` 正常但
|
|
6
|
+
* JsRuntime 能力缺失」真实存在。版本号是声明,能跑通才是事实。
|
|
7
|
+
*
|
|
8
|
+
* 为什么单独探 `--module-root`:module-root 布局(`src/<name>/{api,web}`)依赖
|
|
9
|
+
* oj 的模块根可配置。能力缺失时必须 fail-fast 并给处置命令——回落旧布局会让
|
|
10
|
+
* 「源码搬了、构建产物还是旧的」这种半坏状态长期存在,比直接失败难排查得多。
|
|
11
|
+
*
|
|
12
|
+
* 缓存:`bin/.oj-caps`,以二进制的 size+mtime 为指纹。指纹变了就重探,
|
|
13
|
+
* 所以手替 `bin/oj` 不会吃到旧缓存。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { execFileSync } from "node:child_process";
|
|
17
|
+
import fs from "node:fs";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import { resolveOjBin } from "./vendor";
|
|
20
|
+
|
|
21
|
+
export interface OjCaps {
|
|
22
|
+
/** `oj build --help` 是否接受 `--module-root <DIR>`(源码树根,与 `-d` 互斥) */
|
|
23
|
+
moduleRoot: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 探针执行器(测试注入):执行 oj 子命令并返回合并输出 */
|
|
27
|
+
export type OjProbeRun = (ojBin: string, args: string[]) => string;
|
|
28
|
+
|
|
29
|
+
const defaultRun: OjProbeRun = (ojBin, args) =>
|
|
30
|
+
execFileSync(ojBin, args, { encoding: "utf-8", timeout: 10_000, stdio: ["ignore", "pipe", "pipe"] });
|
|
31
|
+
|
|
32
|
+
export const CAPS_FILE = ".oj-caps";
|
|
33
|
+
|
|
34
|
+
/** 二进制指纹:size + mtime 足以识别「换了文件」,比哈希便宜且无 IO 放大 */
|
|
35
|
+
function fingerprint(ojBin: string): string {
|
|
36
|
+
const st = fs.statSync(ojBin);
|
|
37
|
+
return `${st.size}:${Math.floor(st.mtimeMs)}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function capsPath(binDir: string): string {
|
|
41
|
+
return path.join(binDir, CAPS_FILE);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 缓存文件形状:指纹 + 能力位 */
|
|
45
|
+
interface CapsCache {
|
|
46
|
+
fingerprint?: string
|
|
47
|
+
caps?: Partial<OjCaps>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 读缓存;指纹不匹配或结构不对 → null(重探) */
|
|
51
|
+
export function readCapsCache(ojBin: string): OjCaps | null {
|
|
52
|
+
try {
|
|
53
|
+
const raw = JSON.parse(fs.readFileSync(capsPath(path.dirname(ojBin)), "utf-8")) as CapsCache | null;
|
|
54
|
+
if (!raw || raw.fingerprint !== fingerprint(ojBin) || typeof raw.caps?.moduleRoot !== "boolean")
|
|
55
|
+
return null;
|
|
56
|
+
return { moduleRoot: raw.caps.moduleRoot };
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 写缓存(失败不影响主流程:缓存只是加速,不是正确性来源) */
|
|
64
|
+
export function writeCapsCache(ojBin: string, caps: OjCaps): void {
|
|
65
|
+
try {
|
|
66
|
+
fs.writeFileSync(
|
|
67
|
+
capsPath(path.dirname(ojBin)),
|
|
68
|
+
`${JSON.stringify({ fingerprint: fingerprint(ojBin), caps }, null, 2)}\n`,
|
|
69
|
+
"utf-8",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// 只读目录 / 权限不足:忽略
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** 现场探测:解析 `oj build --help` 的旗标列表(不缓存) */
|
|
78
|
+
export function probeOjCaps(ojBin: string, run: OjProbeRun = defaultRun): OjCaps {
|
|
79
|
+
let help = "";
|
|
80
|
+
try {
|
|
81
|
+
help = run(ojBin, ["build", "--help"]);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`[ojm] 无法执行 ${ojBin} build --help:${(error as Error).message}\n`
|
|
86
|
+
+ "处置:`ojm vendor` 重装 oj 二进制。",
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
// 只认长旗标整词:`--module-root <DIR>`;避免帮助文本里的说明文字误命中
|
|
90
|
+
return { moduleRoot: /--module-root\b/.test(help) };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* module-root 布局的能力闸门:缺失即抛,文案给处置命令。
|
|
95
|
+
* 有缓存走缓存(热路径每次 build/dev 都会经过这里)。
|
|
96
|
+
*/
|
|
97
|
+
export function requireModuleRootCapability(
|
|
98
|
+
projectRoot: string,
|
|
99
|
+
run: OjProbeRun = defaultRun,
|
|
100
|
+
): OjCaps {
|
|
101
|
+
const ojBin = resolveOjBin(projectRoot);
|
|
102
|
+
if (!fs.existsSync(ojBin)) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`[ojm] 缺少 ${ojBin}:module-root 布局需要 oj 提供 --module-root。\n`
|
|
105
|
+
+ "处置:`ojm vendor` 安装 oj 二进制。",
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const caps = readCapsCache(ojBin) ?? (() => {
|
|
110
|
+
const probed = probeOjCaps(ojBin, run);
|
|
111
|
+
writeCapsCache(ojBin, probed);
|
|
112
|
+
return probed;
|
|
113
|
+
})();
|
|
114
|
+
|
|
115
|
+
if (caps.moduleRoot)
|
|
116
|
+
return caps;
|
|
117
|
+
|
|
118
|
+
const version = (() => {
|
|
119
|
+
try {
|
|
120
|
+
return run(ojBin, ["-V"]).trim() || "(未知)";
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return "(不可探测)";
|
|
124
|
+
}
|
|
125
|
+
})();
|
|
126
|
+
throw new Error(
|
|
127
|
+
`[ojm] 当前 oj 不支持 --module-root(版本 ${version}),无法构建 src/<name>/{api,web} 布局。\n`
|
|
128
|
+
+ "原因:module-root 布局要求 oj build / serve 的模块根可配置;本版 oj 只认 `-d` 首层子目录为模块。\n"
|
|
129
|
+
+ "处置:升级 oj 后重试——`ojm vendor <tag>`(需 >= 支持 --module-root 的版本),"
|
|
130
|
+
+ "或把工程改回 web/src/<name> 布局。\n"
|
|
131
|
+
+ `缓存:${capsPath(path.dirname(ojBin))}(换二进制后指纹变化会自动重探)。`,
|
|
132
|
+
);
|
|
133
|
+
}
|
package/src/oj.ts
CHANGED
|
@@ -40,17 +40,40 @@ export interface StartOjPoll {
|
|
|
40
40
|
timeoutMs?: number
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* 工程根:从 config 所在目录向上找最近的含 `bin/` 的目录(ojm 的 vendor 布局)。
|
|
45
|
+
*
|
|
46
|
+
* 两种布局:
|
|
47
|
+
* <root>/api/config.yaml → <root>/bin/oj (历史布局,命中第二层)
|
|
48
|
+
* <root>/config.yaml → <root>/bin/oj (module-root,命中第一层)
|
|
49
|
+
* 找不到 `bin/` 时退回旧的「往上两级」,保持既有报错行为(缺 bin/oj 的文案不变)。
|
|
50
|
+
*/
|
|
51
|
+
function resolveProjectRoot(configPath: string): string {
|
|
52
|
+
const configDir = path.dirname(configPath);
|
|
53
|
+
for (const dir of [configDir, path.dirname(configDir)]) {
|
|
54
|
+
if (fs.existsSync(path.join(dir, "bin")))
|
|
55
|
+
return dir;
|
|
56
|
+
}
|
|
57
|
+
return path.dirname(configDir);
|
|
58
|
+
}
|
|
59
|
+
|
|
43
60
|
export function startOj(
|
|
44
61
|
configPath: string,
|
|
45
62
|
base: string,
|
|
46
|
-
|
|
63
|
+
apiSource: string | string[],
|
|
47
64
|
poll: StartOjPoll = {},
|
|
48
65
|
extraArgs: string[] = [],
|
|
49
66
|
): OjProcess {
|
|
50
67
|
const port = readOjPort(configPath);
|
|
51
|
-
// 约定:
|
|
52
|
-
|
|
68
|
+
// 约定:vendor 二进制在 <project>/bin/oj。工程根不能按「config 往上两级」算:
|
|
69
|
+
// module-root 布局的 config 在仓库根(<project>/config.yaml),往上两级会越过工程,
|
|
70
|
+
// 落到父目录找 bin/oj。改为「向上找最近的含 bin/ 的目录」,两种布局都成立。
|
|
71
|
+
const projectRoot = resolveProjectRoot(configPath);
|
|
53
72
|
const binPath = resolveOjBin(projectRoot);
|
|
73
|
+
// 后端源码参数二选一(oj v0.1.85:`--api-path` 与 `--module-root` 互斥,同时给会被
|
|
74
|
+
// clap 拒掉):历史布局传目录 → `--api-path <dir>`;module-root 布局传**已拼好的
|
|
75
|
+
// 旗标数组** → `["--module-root", "<project>/src"]`。
|
|
76
|
+
const apiArgs = Array.isArray(apiSource) ? apiSource : ["--api-path", apiSource];
|
|
54
77
|
const healthUrl = `http://127.0.0.1:${port}${base}/health`;
|
|
55
78
|
|
|
56
79
|
// 插件目录发现显式化:oj 的 resolve_plugins_dir 依赖 current_exe() 启发式,而
|
|
@@ -69,7 +92,7 @@ export function startOj(
|
|
|
69
92
|
let stderrTail = "";
|
|
70
93
|
// --console-log:oj 新版终端默认静默(console_log 缺省 false,只落 logs/),
|
|
71
94
|
// ojm dev/preview 的 [oj] 透传管道必须显式打开终端输出才能看到日志(手册 §10)。
|
|
72
|
-
const child = spawn(binPath, ["serve", "-c", configPath, "-b", base,
|
|
95
|
+
const child = spawn(binPath, ["serve", "-c", configPath, "-b", base, ...apiArgs, "--console-log", ...extraArgs], {
|
|
73
96
|
stdio: ["ignore", "pipe", "pipe"],
|
|
74
97
|
...(env ? { env } : {}),
|
|
75
98
|
});
|
package/src/preview-config.ts
CHANGED
|
@@ -44,7 +44,7 @@ export interface DerivePreviewConfigOptions {
|
|
|
44
44
|
apiRoot?: string
|
|
45
45
|
/** 显式端口时覆盖 server.port;缺省沿用原 config(oj 代码默认 9778) */
|
|
46
46
|
port?: number
|
|
47
|
-
/** 原 config 文本(测试注入);缺省读
|
|
47
|
+
/** 原 config 文本(测试注入);缺省读 config.yaml */
|
|
48
48
|
configText?: string
|
|
49
49
|
}
|
|
50
50
|
|
package/src/preview.ts
CHANGED
|
@@ -59,7 +59,8 @@ export async function runPreview(projectRoot: string, opts: PreviewOptions = {})
|
|
|
59
59
|
const siteDir = opts.siteDir ?? layout.distDir;
|
|
60
60
|
const ojBin = resolveOjBin(projectRoot);
|
|
61
61
|
const configPath = resolveConfigPath(projectRoot);
|
|
62
|
-
|
|
62
|
+
// release 根恒为扁平 api/dist(module-root 只改源码布局,不改产物形状——设计 C-3/C-7)
|
|
63
|
+
const apiDist = layout.apiDist;
|
|
63
64
|
|
|
64
65
|
// fail-fast 四查:缺一即提示对应补救(init 幂等补缺 / ojm build 产物)
|
|
65
66
|
if (!existsSync(ojBin))
|
package/src/site.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import process from "node:process";
|
|
8
8
|
|
|
9
|
-
/** 任务记录(线形状镜像;真源 templates/
|
|
9
|
+
/** 任务记录(线形状镜像;真源 templates/src/_platform/api/_lifecycle/types.ts:65,客户端不 import 模板内部) */
|
|
10
10
|
export interface TaskRecord {
|
|
11
11
|
/**
|
|
12
12
|
* 记录 schema 版本(P2-10 线形状镜像):缺省(undefined)=旧记录=v1 语义照常渲染;
|
|
@@ -30,7 +30,7 @@ export interface TaskRecord {
|
|
|
30
30
|
finishedAt?: number
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
/** 模板 serve 缺省端口(templates/
|
|
33
|
+
/** 模板 serve 缺省端口(templates/config.yaml server.port: 9778;host 显式钉 127.0.0.1) */
|
|
34
34
|
export const DEFAULT_SITE_URL = "http://127.0.0.1:9778";
|
|
35
35
|
|
|
36
36
|
/** 本 CLI 已知的任务记录 schema 版本(P2-10;与服务端 tasks.ts TASK_SCHEMA_VERSION 手工同步,更高版本触发降级提示) */
|
package/src/usage.ts
CHANGED
|
@@ -27,7 +27,7 @@ export function usageText(): string {
|
|
|
27
27
|
ojm api [dir] [--check] [--docs] [--exempt <path>]
|
|
28
28
|
契约代码生成(client / routes / openapi / stub)
|
|
29
29
|
--check 四重对账(生成物同步 / route 双向 / routes.js / WS 路由鉴权)
|
|
30
|
-
--exempt <path> 指定豁免清单,覆盖默认
|
|
30
|
+
--exempt <path> 指定豁免清单,覆盖默认 .ojm-api-exempt.json(工程根;历史布局在 api/ 下)
|
|
31
31
|
--docs 出自包含文档站(单文件离线可看)
|
|
32
32
|
ojm info
|
|
33
33
|
输出版本矩阵与模块清单(报障用,US-7)
|
|
@@ -68,7 +68,7 @@ export function usageText(): string {
|
|
|
68
68
|
--check 离线自检两份索引签名
|
|
69
69
|
ojm market setup [--url <站点地址>] [--user <u> --pass <p>] [--new-pass <p>]
|
|
70
70
|
把系统设置 framework.market_cdn_url 指向本地模拟 CDN(工程根 cdn/,由
|
|
71
|
-
|
|
71
|
+
config.yaml 的 /cdn 挂载直出)。首跑若账号处于首次改密期(seed admin
|
|
72
72
|
must_change=1),会自动改密换全权 token 后再写入。
|
|
73
73
|
ojm market publish [--fixture <name>@<version>] [--real <方案工程>] [--id <key>] [--key <pem>] [--rebuild-index] [--check]
|
|
74
74
|
把方案包落进 cdn/:合成 fixture(或 --real 走 ojm build --bundle)→ 确定性 tgz
|
|
@@ -86,7 +86,7 @@ export function usageText(): string {
|
|
|
86
86
|
在一次性隔离站点(随机端口 + 一次性 DB/密钥/凭据/信任库/CDN)里跑完整安装,
|
|
87
87
|
跑完即毁;--keep 保留现场供排查。用于确认「这个包到底能不能装」,不污染你的工程。
|
|
88
88
|
ojm test|exec|openapi|migrate|schema [args...]
|
|
89
|
-
透传给 oj 同名子命令(自动补 -c <project>/
|
|
89
|
+
透传给 oj 同名子命令(自动补 -c <project>/config.yaml;其余参数原样转发)
|
|
90
90
|
例:ojm test --anonymous / ojm migrate --db report / ojm openapi --check
|
|
91
91
|
`;
|
|
92
92
|
}
|
|
@@ -104,7 +104,7 @@ const COMMAND_HELP: Record<string, string> = {
|
|
|
104
104
|
开发模式快捷键(仅 TTY 终端,类 Vite):
|
|
105
105
|
r 强制重建并热更新 h 显示帮助 c 清屏 o 浏览器打开
|
|
106
106
|
q 退出(后端 oj 一并终止)
|
|
107
|
-
port 缺省 5174(oj 后端端口以
|
|
107
|
+
port 缺省 5174(oj 后端端口以 config.yaml 的 server.port 为准)`,
|
|
108
108
|
"build": `ojm build [--bundle] [--out <dir>]
|
|
109
109
|
构建后端(oj build)与模块产物(含全站合并)
|
|
110
110
|
--bundle 方案包构建:manifest.yaml 契约校验(fail-fast)→ 确定性 tgz
|
|
@@ -117,7 +117,7 @@ const COMMAND_HELP: Record<string, string> = {
|
|
|
117
117
|
"api": `ojm api [dir] [--check] [--docs] [--exempt <path>]
|
|
118
118
|
契约代码生成(client / routes / openapi / stub)
|
|
119
119
|
--check 四重对账(生成物同步 / route 双向 / routes.js / WS 路由鉴权)
|
|
120
|
-
--exempt <path> 指定豁免清单,覆盖默认
|
|
120
|
+
--exempt <path> 指定豁免清单,覆盖默认 .ojm-api-exempt.json(工程根;历史布局在 api/ 下)
|
|
121
121
|
--docs 出自包含文档站(单文件离线可看)
|
|
122
122
|
dir 缺省当前目录;显式目录须先存在`,
|
|
123
123
|
"info": `ojm info
|
|
@@ -168,7 +168,7 @@ const COMMAND_HELP: Record<string, string> = {
|
|
|
168
168
|
本地模拟 CDN 与市场链路子命令:setup / publish / build-index / verify / trial
|
|
169
169
|
详见各子命令帮助:ojm market setup --help 等`,
|
|
170
170
|
"market setup": `ojm market setup [--url <站点地址>] [--user <u> --pass <p>] [--new-pass <p>]
|
|
171
|
-
把系统设置 framework.market_cdn_url 指向本地模拟 CDN(工程根 cdn/,由
|
|
171
|
+
把系统设置 framework.market_cdn_url 指向本地模拟 CDN(工程根 cdn/,由 config.yaml 的 /cdn 挂载直出)。首跑若账号处于首次改密期(seed admin must_change=1),会自动改密换全权 token 后再写入`,
|
|
172
172
|
"market publish": `ojm market publish [--fixture <name>@<version>] [--real <方案工程>] [--id <key>] [--key <pem>] [--rebuild-index] [--check]
|
|
173
173
|
把方案包落进 cdn/:合成 fixture(或 --real 走 ojm build --bundle)→ 确定性 tgz → 包级 detached 签名 <name>-<version>.tgz.sig
|
|
174
174
|
不碰索引(索引由 build-index 扫描重建,多人发布才不会互相覆盖)`,
|
package/src/vendor.ts
CHANGED
|
@@ -35,8 +35,12 @@ export const NPM_PKG = "@oj-bin/oj";
|
|
|
35
35
|
* (见 CHANGELOG 0.1.20 / docs/guides/api-codegen-openapi.md)。**0.2.13 抬到 0.1.81**:
|
|
36
36
|
* 模板侧 `solution-perms.ts` 权限级联清理用 `startswith` 操作键(v0.1.81 补齐,
|
|
37
37
|
* 构造器迁移收尾),oj < 0.1.81 时卸载方案报 `unknown variant startswith` fail-closed。
|
|
38
|
+
* **本版抬到 0.1.85**:`src/<name>/{web,api}` 布局依赖 `--module-root <DIR>`(源码树根,
|
|
39
|
+
* 与 `-d`/`--api-path` 互斥);旧版收到该旗标即报错。0.1.85 同时落地了
|
|
40
|
+
* `manifest.name` 与目录名脱钩、`#/m/x` 按 name 解析(见
|
|
41
|
+
* docs/prd/202610220010-layout-module-root-design.md §5)。
|
|
38
42
|
*/
|
|
39
|
-
export const MIN_OJ_VERSION = "0.1.
|
|
43
|
+
export const MIN_OJ_VERSION = "0.1.85";
|
|
40
44
|
|
|
41
45
|
/** 按段数值比较(字符串比较会把 0.1.9 判成大于 0.1.69);非数字段按 0 处理 */
|
|
42
46
|
export function isSupportedOjVersion(version: string): boolean {
|