@oj-module/cli 0.2.18 → 0.2.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/devkit/README.md +4 -4
- package/devkit/SKILL.md +41 -37
- package/devkit/framework-dev.md +6 -5
- package/devkit/manual.md +352 -168
- package/devkit/menu-rules.md +3 -3
- package/devkit/module-dev-and-cdn-playground.md +31 -22
- package/devkit/sql-builder-missing-capabilities-pr.md +11 -6
- package/package.json +2 -2
- package/shell-dist/assets/runtime.js +59 -34
- package/shell-dist/index.html +2 -2
- package/shell-dist/versions.json +3 -3
- package/src/__tests__/build-backend.test.ts +54 -0
- package/src/__tests__/init-packagejson.test.ts +29 -0
- package/src/__tests__/layout.test.ts +176 -0
- package/src/__tests__/module-list.test.ts +66 -0
- package/src/__tests__/oj-caps.test.ts +114 -0
- package/src/alias-paths.ts +102 -0
- package/src/args.ts +54 -4
- package/src/build.ts +41 -9
- package/src/builtin.ts +93 -0
- package/src/bundle.ts +254 -38
- package/src/config.ts +82 -9
- package/src/contract/__tests__/market-client.integration.test.ts +14 -14
- package/src/contract/check.ts +56 -28
- package/src/contract/emit-stub.ts +21 -9
- package/src/contract/handler-scan.ts +19 -10
- package/src/contract/run.ts +14 -4
- package/src/dev.ts +80 -9
- package/src/index.ts +27 -7
- package/src/info.ts +69 -10
- package/src/init.ts +114 -28
- package/src/layout.ts +282 -24
- package/src/lifecycle/__tests__/args.test.ts +20 -0
- package/src/lifecycle/__tests__/builtin.test.ts +97 -0
- package/src/lifecycle/__tests__/bundle.test.ts +184 -6
- package/src/lifecycle/__tests__/manifest-contract.test.ts +2 -2
- package/src/lifecycle/__tests__/new.test.ts +129 -0
- package/src/lifecycle/__tests__/smoke.test.ts +2 -1
- package/src/manifest-contract.ts +16 -3
- package/src/market/__tests__/bootstrap.test.ts +17 -9
- package/src/market/bootstrap.ts +30 -17
- package/src/market/publish.ts +10 -2
- package/src/new.ts +208 -0
- package/src/oj-caps.ts +133 -0
- package/src/oj.ts +27 -4
- package/src/preview-config.ts +1 -1
- package/src/preview.ts +2 -1
- package/src/site.ts +2 -2
- package/src/usage.ts +24 -12
- package/src/vendor.ts +5 -1
- package/templates/{api/config.yaml → config.yaml} +13 -8
- package/templates/global.d.ts +7 -6
- package/templates/init.include.yaml +41 -36
- package/templates/package.json +3 -1
- package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/installer.ts +4 -0
- package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/smoke.ts +21 -2
- package/templates/{api/src/_platform → src/_platform/api}/fixtures/l1-test-user.sql +4 -5
- package/templates/{api/src/_platform → src/_platform/api}/market/api.ts +25 -15
- package/templates/src/_platform/api/seed.mysql.sql +16 -0
- package/templates/src/_platform/api/seed.pg.sql +16 -0
- package/templates/{api/src/_platform → src/_platform/api}/seed.sql +16 -5
- package/templates/{api/src/_platform → src/_platform/api}/solutions/api.ts +7 -3
- package/templates/{api/src/auth → src/auth/api}/_shared/login-throttle.ts +2 -2
- package/templates/{api/src/auth → src/auth/api}/_shared/session.ts +2 -2
- package/templates/{api/src/auth → src/auth/api}/api.ts +1 -1
- package/templates/{api/src/auth → src/auth/api}/ldap-login/api.ts +1 -1
- package/templates/src/auth/api/manifest.yaml +6 -0
- package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/InstallDialog.test.tsx +53 -1
- package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/marketClient.fake.ts +6 -0
- package/templates/{web/src/oj-market → src/oj-market/web}/api/client.ts +5 -0
- package/templates/{web/src/oj-market → src/oj-market/web}/api/context.ts +2 -2
- package/templates/{web/src/oj-market → src/oj-market/web}/api/defaultClient.ts +7 -1
- package/templates/src/oj-market/web/components/InstallDialog.tsx +194 -0
- package/templates/{web/src/oj-market → src/oj-market/web}/components/RollbackDialog.tsx +7 -3
- package/templates/{web/src/oj-market → src/oj-market/web}/components/UninstallConfirm.tsx +18 -7
- package/templates/{web/src/oj-market → src/oj-market/web}/hooks/useMarket.ts +29 -12
- package/templates/{web/src/oj-market → src/oj-market/web}/locales/en-US.json +10 -1
- package/templates/{web/src/oj-market → src/oj-market/web}/locales/zh-CN.json +10 -1
- package/templates/{web/src/oj-market → src/oj-market/web}/types.ts +15 -2
- package/templates/{api/src/system → src/system/api}/login-methods/api.ts +2 -2
- package/templates/src/system/api/manifest.yaml +6 -0
- package/templates/{api/src/system → src/system/api}/oplog/api.ts +4 -4
- package/templates/{api/src/system → src/system/api}/permissions/api.ts +3 -3
- package/templates/{api/src/system → src/system/api}/roles/api.ts +3 -3
- package/templates/{api/src/system → src/system/api}/roles/bindings/api.ts +3 -3
- package/templates/{api/src/system → src/system/api}/settings/api.ts +3 -3
- package/templates/{api/src/system → src/system/api}/users/api.ts +3 -3
- package/templates/{api/src/system → src/system/api}/users/forbidden/api.ts +3 -3
- package/templates/{api/src/system → src/system/api}/users/password/api.ts +3 -3
- package/templates/{api/src/system → src/system/api}/users/roles/api.ts +3 -3
- package/templates/{web/src/system → src/system/web}/locales/en-US.json +1 -1
- package/templates/{web/src/system → src/system/web}/locales/zh-CN.json +1 -1
- package/templates/src/tsconfig.json +16 -0
- package/templates/{api/tests → tests}/auth-ldap.spec.ts +2 -2
- package/templates/{api/tests → tests}/auth-session.spec.ts +3 -3
- package/templates/{api/tests → tests}/auth-throttle.spec.ts +1 -1
- package/templates/tests/fixture.ts +434 -0
- package/templates/{api/tests → tests}/guard.spec.ts +1 -1
- package/templates/{api/tests → tests}/l1-builder.test.ts +2 -2
- package/templates/{api/tests → tests}/lifecycle/atomic-state.spec.ts +3 -3
- package/templates/{api/tests → tests}/lifecycle/bootstrap.spec.ts +8 -4
- package/templates/{api/tests → tests}/lifecycle/deploy-smoke.spec.ts +31 -8
- package/templates/{api/tests → tests}/lifecycle/installer.spec.ts +15 -15
- package/templates/{api/tests → tests}/lifecycle/market-api.spec.ts +30 -29
- package/templates/{api/tests → tests}/lifecycle/migrate.spec.ts +6 -6
- package/templates/{api/tests → tests}/lifecycle/preflight.spec.ts +4 -4
- package/templates/{api/tests → tests}/lifecycle/register.spec.ts +1 -1
- package/templates/{api/tests → tests}/lifecycle/semver.spec.ts +1 -1
- package/templates/{api/tests → tests}/lifecycle/sha384.spec.ts +1 -1
- package/templates/{api/tests → tests}/lifecycle/sig-fixtures.spec.ts +1 -1
- package/templates/{api/tests → tests}/lifecycle/solutions-api.spec.ts +1 -1
- package/templates/{api/tests → tests}/lifecycle/steps-fs.spec.ts +12 -12
- package/templates/{api/tests → tests}/lifecycle/text-edit.spec.ts +1 -1
- package/templates/{api/tests → tests}/lifecycle/tgz-guard.spec.ts +1 -1
- package/templates/{api/tests → tests}/lifecycle/trust.spec.ts +5 -5
- package/templates/{api/tests → tests}/lifecycle/uninstall.spec.ts +7 -7
- package/templates/{api/tests → tests}/lifecycle/verify-checks.spec.ts +9 -6
- package/templates/{api/tests → tests}/lifecycle/verify-report.spec.ts +7 -5
- package/templates/{api/tests → tests}/query-utils.spec.ts +1 -1
- package/templates/{api/tests → tests}/rbac-audit.spec.ts +1 -1
- package/templates/{api/tests → tests}/rbac-contacts.spec.ts +2 -2
- package/templates/{api/tests → tests}/rbac-guard.spec.ts +1 -1
- package/templates/{api/tests → tests}/rbac-roles.spec.ts +2 -2
- package/templates/{api/tests → tests}/rbac-seed.spec.ts +13 -11
- package/templates/{api/tests → tests}/rbac-settings.spec.ts +2 -2
- package/templates/{api/tests → tests}/rbac-solution-perms.spec.ts +1 -1
- package/templates/{api/tests → tests}/rbac-users.spec.ts +3 -3
- package/templates/tests/seed-role-accounts.spec.ts +87 -0
- package/templates/tsconfig.json +5 -1
- package/templates/tsconfig.paths.json +19 -0
- package/templates/types/oj-modules.d.ts +17 -0
- package/templates/vitest.config.ts +22 -0
- package/templates/web.config.ts +23 -13
- package/vendor/host-versions.json +3 -3
- package/templates/api/src/_platform/seed.mysql.sql +0 -8
- package/templates/api/src/_platform/seed.pg.sql +0 -8
- package/templates/api/src/auth/manifest.yaml +0 -5
- package/templates/api/src/system/manifest.yaml +0 -5
- package/templates/api/tests/fixture.ts +0 -58
- package/templates/api/tsconfig.json +0 -8
- package/templates/api/vitest.config.ts +0 -12
- package/templates/logs/task-events.jsonl +0 -0
- package/templates/web/src/oj-market/components/InstallDialog.tsx +0 -112
- /package/templates/{api/.ojm-api-exempt.json → .ojm-api-exempt.json} +0 -0
- /package/templates/{api/seed.mysql.sql → seed.mysql.sql} +0 -0
- /package/templates/{api/seed.pg.sql → seed.pg.sql} +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/README.md +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/atomic.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/history.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/lock.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/semver.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/sha384.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/state.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/deploy-web.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/download.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/extract.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/migrate.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/preflight.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/register.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/reload.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/verify.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/tasks.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/text-edit.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/tgz-guard.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/trust.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/types.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/uninstall.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/verify-checks.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/verify-report.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/respond.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/contacts.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/roles.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/settings.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/solution-perms.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/users.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/access.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/audit.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/guard.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/oplog.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/query.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/rate.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/_shared/site-facade.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/contacts/api.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/contacts/detail/api.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/manifest.yaml +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/market/bootstrap.ts +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0001__create_users.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0001__create_users.pg.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0001__create_users.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0002__add_users_avatar_base64.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0002__add_users_avatar_base64.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0003__users_source_display_name.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0003__users_source_display_name.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0004__sys_rbac.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0004__sys_rbac.pg.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0004__sys_rbac.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0005__users_must_change.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0005__users_must_change.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0006__users_is_forbidden.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0006__users_is_forbidden.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0007__users_email.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0008__sys_refresh_token.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0008__sys_refresh_token.pg.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0008__sys_refresh_token.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0009__sys_throttle.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0009__sys_throttle.pg.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0009__sys_throttle.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0010__sys_solution_route.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0010__sys_solution_route.pg.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0010__sys_solution_route.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0011__sys_setting_group_label.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0011__sys_setting_group_label.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0012__market_history.mysql.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0012__market_history.pg.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0012__market_history.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/migrations/0013__users_profile.sql +0 -0
- /package/templates/{api/src/_platform → src/_platform/api}/schema.yaml +0 -0
- /package/templates/{web/src/_platform → src/_platform/web}/__client/api.schemas.ts +0 -0
- /package/templates/{web/src/_platform → src/_platform/web}/__client/api.ts +0 -0
- /package/templates/{web/src/about → src/about/web}/entry.ts +0 -0
- /package/templates/{web/src/about → src/about/web}/locales/en-US.json +0 -0
- /package/templates/{web/src/about → src/about/web}/locales/zh-CN.json +0 -0
- /package/templates/{web/src/about → src/about/web}/pages/constants.ts +0 -0
- /package/templates/{web/src/about → src/about/web}/pages/index.tsx +0 -0
- /package/templates/{web/src/access → src/access/web}/entry.ts +0 -0
- /package/templates/{web/src/access → src/access/web}/locales/en-US.json +0 -0
- /package/templates/{web/src/access → src/access/web}/locales/zh-CN.json +0 -0
- /package/templates/{web/src/access → src/access/web}/pages/access-mode/index.tsx +0 -0
- /package/templates/{web/src/access → src/access/web}/pages/admin-visible/index.tsx +0 -0
- /package/templates/{web/src/access → src/access/web}/pages/button-control/index.tsx +0 -0
- /package/templates/{web/src/access → src/access/web}/pages/common-visible/index.tsx +0 -0
- /package/templates/{web/src/access → src/access/web}/pages/page-control/index.tsx +0 -0
- /package/templates/{api/src/auth → src/auth/api}/_shared/ldap-login.ts +0 -0
- /package/templates/{api/src/auth → src/auth/api}/_shared/ldap.ts +0 -0
- /package/templates/{web/src/auth → src/auth/web}/__client/api.schemas.ts +0 -0
- /package/templates/{web/src/auth → src/auth/web}/__client/api.ts +0 -0
- /package/templates/{api/src/demo → src/demo/api}/manifest.yaml +0 -0
- /package/templates/{api/src/demo → src/demo/api}/migrations/0001__create_todos.sql +0 -0
- /package/templates/{api/src/demo → src/demo/api}/schema.yaml +0 -0
- /package/templates/{api/src/demo → src/demo/api}/seed.sql +0 -0
- /package/templates/{api/src/demo → src/demo/api}/todos/api.ts +0 -0
- /package/templates/{web/src/demo → src/demo/web}/__client/api.schemas.ts +0 -0
- /package/templates/{web/src/demo → src/demo/web}/__client/api.ts +0 -0
- /package/templates/{web/src/demo → src/demo/web}/entry.ts +0 -0
- /package/templates/{web/src/demo → src/demo/web}/locales/en-US.json +0 -0
- /package/templates/{web/src/demo → src/demo/web}/locales/zh-CN.json +0 -0
- /package/templates/{web/src/demo → src/demo/web}/pages/index.tsx +0 -0
- /package/templates/{web/src/exception → src/exception/web}/entry.ts +0 -0
- /package/templates/{web/src/exception → src/exception/web}/locales/en-US.json +0 -0
- /package/templates/{web/src/exception → src/exception/web}/locales/zh-CN.json +0 -0
- /package/templates/{web/src/exception → src/exception/web}/pages/403/index.tsx +0 -0
- /package/templates/{web/src/exception → src/exception/web}/pages/404/index.tsx +0 -0
- /package/templates/{web/src/exception → src/exception/web}/pages/500/index.tsx +0 -0
- /package/templates/{web/src/exception → src/exception/web}/pages/unknown-component/index.tsx +0 -0
- /package/templates/{api/src/home → src/home/api}/line/api.ts +0 -0
- /package/templates/{api/src/home → src/home/api}/manifest.yaml +0 -0
- /package/templates/{api/src/home → src/home/api}/migrations/0001__create_home_tables.sql +0 -0
- /package/templates/{api/src/home → src/home/api}/pie/api.ts +0 -0
- /package/templates/{api/src/home → src/home/api}/schema.yaml +0 -0
- /package/templates/{api/src/home → src/home/api}/seed.sql +0 -0
- /package/templates/{web/src/home → src/home/web}/__client/api.schemas.ts +0 -0
- /package/templates/{web/src/home → src/home/web}/__client/api.ts +0 -0
- /package/templates/{web/src/home → src/home/web}/entry.ts +0 -0
- /package/templates/{web/src/home → src/home/web}/locales/en-US.json +0 -0
- /package/templates/{web/src/home → src/home/web}/locales/zh-CN.json +0 -0
- /package/templates/{web/src/home → src/home/web}/pages/components/bar-chart.tsx +0 -0
- /package/templates/{web/src/home → src/home/web}/pages/components/card-list.tsx +0 -0
- /package/templates/{web/src/home → src/home/web}/pages/components/line-chart.tsx +0 -0
- /package/templates/{web/src/home → src/home/web}/pages/components/pie-chart.tsx +0 -0
- /package/templates/{web/src/home → src/home/web}/pages/index.tsx +0 -0
- /package/templates/{web/src/login → src/login/web}/entry.ts +0 -0
- /package/templates/{web/src/login → src/login/web}/locales/en-US.json +0 -0
- /package/templates/{web/src/login → src/login/web}/locales/zh-CN.json +0 -0
- /package/templates/{web/src/login → src/login/web}/pages/change-password.tsx +0 -0
- /package/templates/{web/src/login → src/login/web}/pages/login.tsx +0 -0
- /package/templates/{api/src/notification → src/notification/api}/_shared/notify.ts +0 -0
- /package/templates/{api/src/notification → src/notification/api}/api.ts +0 -0
- /package/templates/{api/src/notification → src/notification/api}/del/api.ts +0 -0
- /package/templates/{api/src/notification → src/notification/api}/manifest.yaml +0 -0
- /package/templates/{api/src/notification → src/notification/api}/migrations/0001__create_notifications.sql +0 -0
- /package/templates/{api/src/notification → src/notification/api}/migrations/0002__notifications_user.sql +0 -0
- /package/templates/{api/src/notification → src/notification/api}/migrations/0003__notifications_broadcast.sql +0 -0
- /package/templates/{api/src/notification → src/notification/api}/migrations/0004__notifications_broadcast_hidden.sql +0 -0
- /package/templates/{api/src/notification → src/notification/api}/schema.yaml +0 -0
- /package/templates/{api/src/notification → src/notification/api}/seed.sql +0 -0
- /package/templates/{web/src/notification → src/notification/web}/__client/api.schemas.ts +0 -0
- /package/templates/{web/src/notification → src/notification/web}/__client/api.ts +0 -0
- /package/templates/{web/src/notification → src/notification/web}/entry.ts +0 -0
- /package/templates/{web/src/notification → src/notification/web}/locales/en-US.json +0 -0
- /package/templates/{web/src/notification → src/notification/web}/locales/zh-CN.json +0 -0
- /package/templates/{web/src/notification → src/notification/web}/pages/center/index.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/CardGrid.test.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/HistoryDrawer.test.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/RollbackDialog.test.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/UninstallConfirm.test.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/poll.test.ts +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/render.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/runtime-stub.ts +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/semver.test.ts +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/api/MarketClientProvider.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/components/CardGrid.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/components/CardItem.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/components/DetailDrawer.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/components/HistoryDrawer.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/entry.ts +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/pages/MarketApp.tsx +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/utils/poll.ts +0 -0
- /package/templates/{web/src/oj-market → src/oj-market/web}/utils/semver.ts +0 -0
- /package/templates/{web/src/outside → src/outside/web}/entry.ts +0 -0
- /package/templates/{web/src/outside → src/outside/web}/locales/en-US.json +0 -0
- /package/templates/{web/src/outside → src/outside/web}/locales/zh-CN.json +0 -0
- /package/templates/{api/src/personal-center → src/personal-center/api}/manifest.yaml +0 -0
- /package/templates/{api/src/personal-center → src/personal-center/api}/profile/api.ts +0 -0
- /package/templates/{api/src/personal-center → src/personal-center/api}/upload/api.ts +0 -0
- /package/templates/{web/src/personal-center → src/personal-center/web}/__client/api.schemas.ts +0 -0
- /package/templates/{web/src/personal-center → src/personal-center/web}/__client/api.ts +0 -0
- /package/templates/{web/src/personal-center → src/personal-center/web}/entry.ts +0 -0
- /package/templates/{web/src/personal-center → src/personal-center/web}/locales/en-US.json +0 -0
- /package/templates/{web/src/personal-center → src/personal-center/web}/locales/zh-CN.json +0 -0
- /package/templates/{web/src/personal-center → src/personal-center/web}/pages/my-profile/index.tsx +0 -0
- /package/templates/{web/src/route-nest → src/route-nest/web}/entry.ts +0 -0
- /package/templates/{web/src/route-nest → src/route-nest/web}/locales/en-US.json +0 -0
- /package/templates/{web/src/route-nest → src/route-nest/web}/locales/zh-CN.json +0 -0
- /package/templates/{web/src/route-nest → src/route-nest/web}/pages/menu1/menu1-1/index.tsx +0 -0
- /package/templates/{web/src/route-nest → src/route-nest/web}/pages/menu1/menu1-2/index.tsx +0 -0
- /package/templates/{web/src/route-nest → src/route-nest/web}/pages/menu2/index.tsx +0 -0
- /package/templates/{web/src → src}/styles.css +0 -0
- /package/templates/{web/src/system → src/system/web}/__client/api.schemas.ts +0 -0
- /package/templates/{web/src/system → src/system/web}/__client/api.ts +0 -0
- /package/templates/{web/src/system → src/system/web}/entry.ts +0 -0
- /package/templates/{web/src/system → src/system/web}/pages/errors.ts +0 -0
- /package/templates/{web/src/system → src/system/web}/pages/forbidden.tsx +0 -0
- /package/templates/{web/src/system → src/system/web}/pages/login-methods/index.tsx +0 -0
- /package/templates/{web/src/system → src/system/web}/pages/oplog/index.tsx +0 -0
- /package/templates/{web/src/system → src/system/web}/pages/roles/index.tsx +0 -0
- /package/templates/{web/src/system → src/system/web}/pages/settings/index.tsx +0 -0
- /package/templates/{web/src/system → src/system/web}/pages/users/index.tsx +0 -0
- /package/templates/{web/src/system → src/system/web}/pages/users/roles.tsx +0 -0
- /package/templates/{api/src/web → src/web/api}/get-async-routes/api.ts +0 -0
- /package/templates/{api/src/web → src/web/api}/hello/api.ts +0 -0
- /package/templates/{api/src/web → src/web/api}/manifest.yaml +0 -0
- /package/templates/{api/src/web → src/web/api}/user-info/api.ts +0 -0
- /package/templates/{web/src → src/web}/web/__client/api.schemas.ts +0 -0
- /package/templates/{web/src → src/web}/web/__client/api.ts +0 -0
- /package/templates/{api/tests → tests}/rbac-tables.spec.ts +0 -0
package/devkit/menu-rules.md
CHANGED
|
@@ -144,10 +144,10 @@ export default defineModule({
|
|
|
144
144
|
- 因此你只需在双语 locale 里声明**一条** `<模块名>:menu.main`:
|
|
145
145
|
|
|
146
146
|
```jsonc
|
|
147
|
-
// web/
|
|
147
|
+
// src/<模块>/web/locales/zh-CN.json
|
|
148
148
|
{ "menu": { "main": "Wiki" } } // 命中 <模块名>:menu.main(根菜单 + 系统设置里的管理子组都读它)
|
|
149
149
|
|
|
150
|
-
// web/
|
|
150
|
+
// src/<模块>/web/locales/en-US.json
|
|
151
151
|
{ "menu": { "main": "Wiki" } }
|
|
152
152
|
```
|
|
153
153
|
|
|
@@ -190,7 +190,7 @@ handle: { currentActiveMenu: wikiPath("space") } // → "/wiki/space",随模
|
|
|
190
190
|
推荐在模块内放一个 `paths.ts`:
|
|
191
191
|
|
|
192
192
|
```ts
|
|
193
|
-
// web/
|
|
193
|
+
// src/<模块>/web/paths.ts
|
|
194
194
|
export const WIKI_ROOT = "wiki"; // 模块挂载根(唯一定义处)
|
|
195
195
|
export const wikiBase = `/${WIKI_ROOT}`;
|
|
196
196
|
export const wikiPath = (...segments: Array<string | number>): string =>
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
> 一篇写给「第一次在本工程开发模块」的人的教学文档。读完之后你应该能回答三个问题:
|
|
4
4
|
> **① 开发中的模块要不要「安装」?② 要的话装到哪、会不会重复?③ 怎么在本地把市场链路完整跑一遍?**
|
|
5
5
|
> 适用版本:`@oj-module/cli` 0.2.12+(`ojm market` 子命令),运行期 `oj` ≥ 0.1.73。
|
|
6
|
+
> 本文按**当前模板的 module-root 布局**书写(`src/<模块>/{api,web}` + 工程根 `config.yaml`),
|
|
7
|
+
> 建新工程另需 `oj` ≥ 0.1.85(`--module-root`)。历史布局(`api/src` + `web/src`)对照写在括注里。
|
|
6
8
|
|
|
7
9
|
## 1. 先建立心智模型:开发态 ≠ 分发态
|
|
8
10
|
|
|
@@ -10,12 +12,12 @@
|
|
|
10
12
|
|
|
11
13
|
| | 开发态 | 分发态 |
|
|
12
14
|
|---|---|---|
|
|
13
|
-
| 模块在哪 | 工程内源码:`
|
|
14
|
-
| 谁登记它 | `web.config.ts`
|
|
15
|
+
| 模块在哪 | 工程内源码:`src/A/api` + `src/A/web`(历史布局:`api/src/A` + `web/src/A`) | 一个 `.tgz` 包(`ojm build --bundle` 产出) |
|
|
16
|
+
| 谁登记它 | `ojm build` 扫描 `src/*/web/entry.ts`(`web.config.ts` 可选覆盖)→ 生成 `web/dist/modules.json` | 安装器往目标站点 upsert |
|
|
15
17
|
| 跑起来靠 | `ojm dev` 直跑源码,改一行刷新即见 | 安装器十阶段(下载→验签→解压→迁移→注册→热更→冒烟) |
|
|
16
18
|
| 典型场景 | 你在写它 | 别人(或另一个站点)装它 |
|
|
17
19
|
|
|
18
|
-
一句话:**开发态没有「安装」这个动作**。`ojm dev` 起的站点直接扫描 `
|
|
20
|
+
一句话:**开发态没有「安装」这个动作**。`ojm dev` 起的站点直接扫描 `src/` 产生路由(`oj build --module-root src`)、读 `modules.json` 装配菜单——你写的模块 A 此刻「已经在跑」,只是还没打过包。
|
|
19
21
|
|
|
20
22
|
## 2. 常见问题:我开发了模块 A,怎么「安装」我自己?
|
|
21
23
|
|
|
@@ -26,7 +28,7 @@
|
|
|
26
28
|
真正想验证「A 这个包能不能装、安装流程对不对」时,有三个去处,按推荐顺序:
|
|
27
29
|
|
|
28
30
|
1. **一次性隔离站点**:`ojm market trial --fixture <name>@<version>`(随机端口 + 一次性 DB + 一次性密钥 + 隔离信任库/CDN),跑完即毁,不碰你的工程、DB、信任库。这是唯一「装了也不心疼」的地方,也是最快的「这包能不能装」判据(实测 11 段全 ok 约 12 秒)。
|
|
29
|
-
2. **另一个工程**:`ojm install dist/A-0.1.0.tgz`(file 源,multipart
|
|
31
|
+
2. **另一个工程**:`ojm install dist/A-0.1.0.tgz`(file 源,multipart 直传;`dist/` 是 `--bundle` 的默认输出目录,与后端产物 `api/dist` 无关)。
|
|
30
32
|
3. **市场链路**:`ojm market publish` 落 CDN → 市场页点安装(§4 全套)。
|
|
31
33
|
|
|
32
34
|
### 2.2 偏要在本工程试怎么办
|
|
@@ -56,7 +58,7 @@
|
|
|
56
58
|
|
|
57
59
|
注意 §3.1 的 upsert 是**按 name/path** 而不是按来源。于是存在这种状态:
|
|
58
60
|
|
|
59
|
-
> 你源码里的 A(`
|
|
61
|
+
> 你源码里的 A(`src/A/api`,路由由 oj 扫描源码产生)和某次安装产物 `api/dist/A-0.1.0/` 同时存在。
|
|
60
62
|
|
|
61
63
|
表现是:路由与模块条目各自只有一份(不重复),但**哪一份生效取决于挂载命中顺序**——症状为「我改了源码却不生效」或「行为怎么都不对」。这是本主题下最难查的一类问题:没有报错、没有重复,只是「被另一个自己顶替了」。
|
|
62
64
|
|
|
@@ -64,7 +66,7 @@
|
|
|
64
66
|
|
|
65
67
|
## 4. 本地 CDN 演练场:把市场链路完整跑一遍
|
|
66
68
|
|
|
67
|
-
模板自带一个**本地模拟 CDN**(工程根 `cdn/` + `
|
|
69
|
+
模板自带一个**本地模拟 CDN**(工程根 `cdn/` + 工程根 `config.yaml` 里的 `/cdn` 挂载),配上 `ojm market` 四条命令,就能在本地复刻「发包 → 上架 → 索引 → 安装」全链路。不需要外部服务。
|
|
68
70
|
|
|
69
71
|
### 4.1 全流程(五步)
|
|
70
72
|
|
|
@@ -91,7 +93,7 @@ ojm market verify --rebuild-index
|
|
|
91
93
|
|
|
92
94
|
| 命令 | 做什么 | **不**做什么 |
|
|
93
95
|
|---|---|---|
|
|
94
|
-
| `market setup` | 写系统设置 `framework.market_cdn_url` |
|
|
96
|
+
| `market setup` | 写系统设置 `framework.market_cdn_url` | 不改写工程根 `config.yaml`(`/cdn` 挂载由模板自带) |
|
|
95
97
|
| `market publish` | 落 `<name>-<version>.tgz` + 包级 `.tgz.sig` | **不碰索引**(索引是共享单文件,多人发布会互相覆盖) |
|
|
96
98
|
| `market build-index` | 扫 `cdn/*.tgz` 提取包内 manifest,全量重建两份索引并签名 | 不落包 |
|
|
97
99
|
| `market verify` | 完整性 / 包级签 / 索引签 / manifest 结构四类校验 + 漂移检出 | 不起站点、不装、不访问任何远端 |
|
|
@@ -129,19 +131,26 @@ ojm market verify --rebuild-index
|
|
|
129
131
|
|
|
130
132
|
```text
|
|
131
133
|
solution-demo/
|
|
132
|
-
├── manifest.yaml
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
│ ├──
|
|
136
|
-
│ ├──
|
|
137
|
-
│ ├──
|
|
138
|
-
│
|
|
139
|
-
└──
|
|
134
|
+
├── manifest.yaml # 方案契约清单(Form A,恒在工程根)
|
|
135
|
+
└── src/<name>/
|
|
136
|
+
├── api/
|
|
137
|
+
│ ├── manifest.yaml # oj 原生清单(name/desc/version/tables)
|
|
138
|
+
│ ├── schema.yaml # 表结构真源(必须与 manifest.tables 对齐)
|
|
139
|
+
│ ├── routes.js
|
|
140
|
+
│ ├── migrations/0001__init.sql
|
|
141
|
+
│ └── ping/api.ts # handler 必须是 .ts
|
|
142
|
+
└── web/
|
|
143
|
+
└── locales/{zh-CN,en-US}.json # 引用 i18n key 时必需
|
|
140
144
|
```
|
|
141
145
|
|
|
146
|
+
> 历史布局把这棵树换成 `api/src/<name>/…` + `web/src/<name>/locales/…`,构建命令里的
|
|
147
|
+
> `--module-root src` 换成 `-d api/src`(两者互斥,都是「源码树根」)。布局判定看标记文件
|
|
148
|
+
> 而不是目录名:`src/<name>/api/manifest.yaml` 或 `src/<name>/web/entry.ts` 存在即成模块。
|
|
149
|
+
> 方案工程若由 `ojm init` 起(会带工程根 `config.yaml`),构建时加 `-c config.yaml`。
|
|
150
|
+
|
|
142
151
|
```bash
|
|
143
|
-
<host>/bin/oj build -
|
|
144
|
-
node "$CLI_PKG/bin/ojm.mjs" build --bundle
|
|
152
|
+
<host>/bin/oj build --module-root src -o api/dist # 后端构建(只用宿主已装的 oj)
|
|
153
|
+
node "$CLI_PKG/bin/ojm.mjs" build --bundle # 打包(构建即校验)
|
|
145
154
|
node "$CLI_PKG/bin/ojm.mjs" keygen --id demo.publisher --out ./keys --yes
|
|
146
155
|
node "$CLI_PKG/bin/ojm.mjs" sign dist/<name>-<version>.tgz --id demo.publisher --key ./keys/private.pem --check
|
|
147
156
|
```
|
|
@@ -154,8 +163,8 @@ node "$CLI_PKG/bin/ojm.mjs" market publish --real ../solution-demo --id demo.pub
|
|
|
154
163
|
node "$CLI_PKG/bin/ojm.mjs" market verify --rebuild-index
|
|
155
164
|
|
|
156
165
|
node "$CLI_PKG/bin/ojm.mjs" build # 生成 api/dist + web/dist(含 api/dist/versions.json)
|
|
157
|
-
# config:platform.root 改成 dist(安装目标恒为 release
|
|
158
|
-
(
|
|
166
|
+
# config:platform.root 改成 api/dist(安装目标恒为 release 部署,root 必须与 --api-path 同源)
|
|
167
|
+
(bin/oj serve -c config.yaml -b /api --api-path api/dist --console-log &)
|
|
159
168
|
node "$CLI_PKG/bin/ojm.mjs" market setup --url http://127.0.0.1:9778 --user admin --pass 123456 --new-pass <新口令>
|
|
160
169
|
# 市场页点安装 → 首次停在「待信任」→ 核对指纹 → ojm trust confirm --task <id> → 重试 → done
|
|
161
170
|
```
|
|
@@ -166,11 +175,11 @@ node "$CLI_PKG/bin/ojm.mjs" market setup --url http://127.0.0.1:9778 --user admi
|
|
|
166
175
|
|---|---|---|
|
|
167
176
|
| ① | `oj build` 报 `tables 声明 "x" 不在 schema.yaml` | oj 用 `schema.yaml` 做表结构真源,`manifest.tables` 只是引用面;补 `schema.yaml`(`tables: {<t>: {pk, columns}}`),与迁移语句对齐 |
|
|
168
177
|
| ② | `oj build` 成功但 `routes.js` 是空表、dist 里没有 handler | **只编译 `.ts` handler**;写成 `ping/api.js` 会被静默丢弃 → 改 `api.ts` |
|
|
169
|
-
| ③ | `build --bundle` 报 `缺少 web/src/<name>/locales
|
|
178
|
+
| ③ | `build --bundle` 报 `缺少 src/<name>/web/locales/…`(历史布局是 `web/src/<name>/locales/…`) | manifest 一旦引用 `title_key`/`label_key` 就必须双语 locale(后端方案也要);且 key 必须**嵌套** `{"menu":{"main":"…"}}`,平铺 `"menu.main"` 查不到 |
|
|
170
179
|
| ④ | 安装 POST 返回 200,但 `market/task` 恒 404 | 宿主 config 缺 `fs:` 段 → 安装器落盘被 jail 拒、失败发生在任务文件写入前;0.2.13 起模板自带,旧工程手补 |
|
|
171
180
|
| ⑤ | 安装卡 `preflight`:`无法读取宿主 runtime 版本` | 安装器读 `api/dist/versions.json`;0.2.13 起 `ojm build` 双落点(旧版手抄 `web/dist/versions.json`) |
|
|
172
181
|
| ⑥ | 安装卡 `reloading`:`NotCapable: module '_platform' is not platform-privileged` | 宿主 config 的 `platform.modules` 未列 `_platform`(0.2.13 起模板自带) |
|
|
173
|
-
| ⑦ | 安装卡 `reloading`:`reloadConfig 返回 unchanged` / 卡 `smoke`:探活 404 | 起站形态不对:`ojm preview` 服务派生 config
|
|
182
|
+
| ⑦ | 安装卡 `reloading`:`reloadConfig 返回 unchanged` / 卡 `smoke`:探活 404 | 起站形态不对:`ojm preview` 服务派生 config(安装器热更写的是真实 config,派生文件没变);dev 用 `--module-root src`(历史布局 `--api-path src`)读源码,装到 `api/dist` 的模块不被发现。**安装必须对真实 config 以 `--api-path api/dist`(历史布局 `--api-path dist`)起站** |
|
|
174
183
|
|
|
175
184
|
### 6.4 装成之后怎么确认
|
|
176
185
|
|
|
@@ -186,7 +195,7 @@ ls api/dist/<name>-<version>/ # 安装产物目录
|
|
|
186
195
|
写模块 → 源码直跑(ojm dev),不要装
|
|
187
196
|
验证能装 → ojm market trial(隔离站点,12s),或 ojm install <tgz> 到另一个工程
|
|
188
197
|
重复? → 不会。注册面全 upsert;同名再装被前置校验直接拒
|
|
189
|
-
市场链路 → setup → publish → build-index → verify → 市场页安装(release 形态:--api-path dist)
|
|
198
|
+
市场链路 → setup → publish → build-index → verify → 市场页安装(release 形态:--api-path api/dist)
|
|
190
199
|
绝对 URL → 市场地址端口取自 config(9778),不是 dev server 的 5174
|
|
191
200
|
信任 → 包级签=锚,索引签=完整性,TOFU 管陌生发布方,verify 不背书内容安全
|
|
192
201
|
```
|
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
> **闭环状态(2026-10-20):7 项诉求全部闭环**——6 项随 oj 0.1.81/0.1.82 发布落地,第 4 项
|
|
4
4
|
> (orIgnore + fromSelect)能力本身已发布但**组合在 sqlite 上被上游明文拒绝**,模板按上游
|
|
5
5
|
> 文档指引保留一处 sanctioned 裸 SQL 绕行(`notify.ts:216`,api-manual §"orIgnore + fromSelect
|
|
6
|
-
> 组合在 sqlite 不支持"原文即指向该绕行)。逐项映射见 §6;模板 `api
|
|
7
|
-
>
|
|
6
|
+
> 组合在 sqlite 不支持"原文即指向该绕行)。逐项映射见 §6;模板 `src/*/api`(布局 v2 前为
|
|
7
|
+
> `api/src`)内 `保留 db.query` 注释已归零(仅剩 `notify.ts` 里 `markAllRead` / `clearNotices`
|
|
8
|
+
> 两处 `保留 db.exec`)。
|
|
8
9
|
>
|
|
9
10
|
> 目的:把模板 `db.query` 全面迁移到 `db.table(...)` 构造器以获得多库(sqlite / mysql / postgres)可移植性时,
|
|
10
11
|
> 识别出构造器**当时无法表达**、只能回退 `db.query` 的 7 类能力。本文档作为向上游 `oj` 运行时 SQL 构造器提 PR 的依据。
|
|
11
12
|
>
|
|
12
13
|
> 证据来源:`~/.local/share/oj/devkit/api-manual.md`(查询构造器章节,含 `like` 操作清单 §1142、子查询限制 §1343、
|
|
13
|
-
> `union` §1346、CTE §1378 等);框架 `README.md` v0.1.66(ESCAPE 纪律);模板代码 `packages/cli/templates/api
|
|
14
|
+
> `union` §1346、CTE §1378 等);框架 `README.md` v0.1.66(ESCAPE 纪律);模板代码 `packages/cli/templates/src/*/api/**`。
|
|
14
15
|
|
|
15
16
|
---
|
|
16
17
|
|
|
@@ -243,12 +244,15 @@ db.table("sys_refresh_token").select("*").where({ field: "token_hash", op: "eq",
|
|
|
243
244
|
6. `demo/todos/api.ts:12` / `uninstall.ts:166` → 动态 WHERE 安全出口。
|
|
244
245
|
7. `session.ts:114-115` → `select("*")`。
|
|
245
246
|
|
|
246
|
-
全部关闭后,模板 `api
|
|
247
|
+
全部关闭后,模板 `src/*/api` 内 `保留 db.query` / `保留 db.exec` 注释应归零,达成"零原生 SQL 可移植"目标。
|
|
247
248
|
|
|
248
249
|
---
|
|
249
250
|
|
|
250
251
|
## 6. 闭环状态(2026-10-20 实测)
|
|
251
252
|
|
|
253
|
+
> 表中 `notify.ts:NN` 等行号是 **2026-10-20 的快照**,代码此后有增删(文件也已迁到
|
|
254
|
+
> `src/notification/api/_shared/notify.ts`)——按函数名定位,别按行号跳。
|
|
255
|
+
|
|
252
256
|
| # | 诉求 | 上游落地(版本) | 模板现状 |
|
|
253
257
|
|---|------|------------------|----------|
|
|
254
258
|
| 1 | `like` 的 `ESCAPE` 转义子句 | `contains` / `startswith` / `endswith` 语义化模糊匹配(**v0.1.81**),引擎自动转义 `%`/`_`/`!` | 已闭环:`contacts.ts` 等模糊搜索全部走 `contains`。**订正**:原判「`like` 无 escape 出口」系误报——escape 逃生舱本就存在(白名单 `! / | ~ # @`);真正缺的是业务零转义语义 op,上游按此落地(L1 实测 `%` 元字符不泛化) |
|
|
@@ -259,9 +263,10 @@ db.table("sys_refresh_token").select("*").where({ field: "token_hash", op: "eq",
|
|
|
259
263
|
| 6 | 动态 WHERE 组合 | `whereIf(test, cond)`(**v0.1.81**):`undefined/null/false/[]` 整条跳过(注意 `""`/`0` 是合法值会保留——可选过滤的缺失值必须归一化为 `undefined`,模板 oplog 曾踩此坑并修复) | 已闭环:`demo/todos`、`uninstall.ts` 等全部改 `whereIf`;未采纳裸片段出口(whereIf 已覆盖诉求场景) |
|
|
260
264
|
| 7 | `select *` | `select(["*"])`(**v0.1.81**,必须是唯一元素) | 已闭环:`session.ts` refresh 查询改 `select(["*"])` |
|
|
261
265
|
|
|
262
|
-
**结论**:模板 `api
|
|
266
|
+
**结论**:模板 `src/*/api` 内 `保留 db.query` 注释归零;`保留 db.exec` 仅剩 `notify.ts` 的
|
|
267
|
+
`markAllRead` / `clearNotices` 两处(`src/notification/api/_shared/notify.ts`),
|
|
263
268
|
且属上游文档明文记载的 sqlite 组合限制绕行(非能力缺口)。"零原生 SQL 可移植"目标以
|
|
264
|
-
|
|
269
|
+
「上游 sanctioned 绕行」的形态达成。
|
|
265
270
|
|
|
266
271
|
> 上游侧复核记录(oj 0.1.81 release notes,devkit `oj-api-dev/README.md`):7 条诉求落地时
|
|
267
272
|
> 判定「1 条误报、1 条部分误报」——误报即 §3.1 的 escape 出口(本就存在),部分误报即 §3.3
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oj-module/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.23",
|
|
5
5
|
"description": "React Antd Admin 模块工程命令行工具(含预构建宿主 shell-dist)",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"exports": {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
],
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@faker-js/faker": "^10.4.0",
|
|
52
|
-
"@oj-module/runtime": "0.2.
|
|
52
|
+
"@oj-module/runtime": "0.2.23",
|
|
53
53
|
"@redocly/cli": "^2.51.0",
|
|
54
54
|
"@tailwindcss/vite": "^4.3.0",
|
|
55
55
|
"esbuild": "^0.28.2",
|
|
@@ -204,6 +204,7 @@ var init_site_base = __esmMin((() => {
|
|
|
204
204
|
* 子路径部署(/oj/wiki/...)下直接与 loginPath 比较恒为 false,401 时会重复 goLogin。
|
|
205
205
|
* React 组件内请用 `useLocation()`(react-router 已剥离 basename),无需本函数。
|
|
206
206
|
*
|
|
207
|
+
* @param pathname 原始路径(含部署 base,如 `location.pathname`)
|
|
207
208
|
* @param base 部署 base,恒以 `/` 结尾;缺省取 `resolveSiteBase()`(部署期注入优先,
|
|
208
209
|
* 见 utils/site-base.ts —— runtime 产物里的 `import.meta.env.BASE_URL` 是构建期
|
|
209
210
|
* 内联的 `/`,子路径部署下直接用它恒错)
|
|
@@ -303,10 +304,10 @@ function getAppInfo() {
|
|
|
303
304
|
return {
|
|
304
305
|
"pkg": {
|
|
305
306
|
"name": "@oj-module/runtime",
|
|
306
|
-
"version": "0.2.
|
|
307
|
+
"version": "0.2.23",
|
|
307
308
|
"license": "MIT"
|
|
308
309
|
},
|
|
309
|
-
"lastBuildTime": "2026-10-
|
|
310
|
+
"lastBuildTime": "2026-10-22 18:50:31"
|
|
310
311
|
};
|
|
311
312
|
}
|
|
312
313
|
var init_get_app_info = __esmMin((() => {}));
|
|
@@ -429,7 +430,7 @@ function PageError({ error, resetErrorBoundary }) {
|
|
|
429
430
|
};
|
|
430
431
|
useEffect(() => {
|
|
431
432
|
if (enableDynamicTitle) document.title = t("exception.pageErrorTitle");
|
|
432
|
-
}, [enableDynamicTitle]);
|
|
433
|
+
}, [enableDynamicTitle, t]);
|
|
433
434
|
return /* @__PURE__ */ jsx(Result, {
|
|
434
435
|
status: "error",
|
|
435
436
|
icon: /* @__PURE__ */ jsx("div", {
|
|
@@ -3495,7 +3496,7 @@ function useCurrentRoute() {
|
|
|
3495
3496
|
const matches = useMatches();
|
|
3496
3497
|
return useMemo(() => {
|
|
3497
3498
|
return matches[matches.length - 1];
|
|
3498
|
-
}, [matches
|
|
3499
|
+
}, [matches]);
|
|
3499
3500
|
}
|
|
3500
3501
|
var init_use_current_route = __esmMin((() => {}));
|
|
3501
3502
|
//#endregion
|
|
@@ -3595,7 +3596,7 @@ function useCssVar(name, root = defaultRoot, options = {}) {
|
|
|
3595
3596
|
}), [name, root]);
|
|
3596
3597
|
useEffect(() => {
|
|
3597
3598
|
if (options?.initialValue) controls.set(options.initialValue);
|
|
3598
|
-
}, [options?.initialValue]);
|
|
3599
|
+
}, [options?.initialValue, controls]);
|
|
3599
3600
|
useEffect(() => {
|
|
3600
3601
|
return () => controls.remove();
|
|
3601
3602
|
}, [controls]);
|
|
@@ -3656,7 +3657,7 @@ var init_dom = __esmMin((() => {}));
|
|
|
3656
3657
|
* @en Get the style of the layout content area
|
|
3657
3658
|
*/
|
|
3658
3659
|
function useLayoutContentStyle() {
|
|
3659
|
-
const
|
|
3660
|
+
const contentElementRef = useRef(null);
|
|
3660
3661
|
const [visibleDomRect, setVisibleDomRect] = useState(null);
|
|
3661
3662
|
const resizeObserverRef = useRef(null);
|
|
3662
3663
|
const contentHeightControls = useCssVar(CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT);
|
|
@@ -3673,7 +3674,7 @@ function useLayoutContentStyle() {
|
|
|
3673
3674
|
};
|
|
3674
3675
|
}, [visibleDomRect]);
|
|
3675
3676
|
const debouncedCalcHeight = useDebounceFn((_entries) => {
|
|
3676
|
-
const rect = getElementVisibleRect(
|
|
3677
|
+
const rect = getElementVisibleRect(contentElementRef.current);
|
|
3677
3678
|
setVisibleDomRect(rect);
|
|
3678
3679
|
if (rect) {
|
|
3679
3680
|
contentHeightControls.set(`${rect.height}px`);
|
|
@@ -3681,10 +3682,10 @@ function useLayoutContentStyle() {
|
|
|
3681
3682
|
}
|
|
3682
3683
|
}, { wait: 16 });
|
|
3683
3684
|
useEffect(() => {
|
|
3684
|
-
if (
|
|
3685
|
+
if (contentElementRef.current && !resizeObserverRef.current) {
|
|
3685
3686
|
const resizeObserver = new ResizeObserver(debouncedCalcHeight.run);
|
|
3686
3687
|
resizeObserverRef.current = resizeObserver;
|
|
3687
|
-
resizeObserver.observe(
|
|
3688
|
+
resizeObserver.observe(contentElementRef.current);
|
|
3688
3689
|
}
|
|
3689
3690
|
return () => {
|
|
3690
3691
|
resizeObserverRef.current?.disconnect();
|
|
@@ -3692,7 +3693,7 @@ function useLayoutContentStyle() {
|
|
|
3692
3693
|
};
|
|
3693
3694
|
}, [debouncedCalcHeight]);
|
|
3694
3695
|
return {
|
|
3695
|
-
|
|
3696
|
+
contentElementRef,
|
|
3696
3697
|
overlayStyle,
|
|
3697
3698
|
visibleDomRect
|
|
3698
3699
|
};
|
|
@@ -4173,7 +4174,7 @@ function KeepAliveLayer() {
|
|
|
4173
4174
|
(aliveRef.current?.getCacheNodes?.())?.forEach((node) => {
|
|
4174
4175
|
if (!openTabs.has(node.cacheKey)) aliveRef.current?.destroy(node.cacheKey);
|
|
4175
4176
|
});
|
|
4176
|
-
}, [openTabs]);
|
|
4177
|
+
}, [openTabs, aliveRef]);
|
|
4177
4178
|
/**
|
|
4178
4179
|
* 关闭多 tab 功能时,清空所有非当前页面缓存
|
|
4179
4180
|
*/
|
|
@@ -4181,10 +4182,18 @@ function KeepAliveLayer() {
|
|
|
4181
4182
|
if (!tabbarEnable) (aliveRef.current?.getCacheNodes?.())?.forEach((node) => {
|
|
4182
4183
|
if (node.cacheKey !== cacheKey) aliveRef.current?.destroy(node.cacheKey);
|
|
4183
4184
|
});
|
|
4184
|
-
}, [
|
|
4185
|
+
}, [
|
|
4186
|
+
tabbarEnable,
|
|
4187
|
+
aliveRef,
|
|
4188
|
+
cacheKey
|
|
4189
|
+
]);
|
|
4185
4190
|
useEffect(() => {
|
|
4186
4191
|
if (tabbarEnable && isRefresh) aliveRef.current?.refresh();
|
|
4187
|
-
}, [
|
|
4192
|
+
}, [
|
|
4193
|
+
isRefresh,
|
|
4194
|
+
aliveRef,
|
|
4195
|
+
tabbarEnable
|
|
4196
|
+
]);
|
|
4188
4197
|
const keepAliveExclude = useMemo(() => {
|
|
4189
4198
|
/**
|
|
4190
4199
|
* 不开启多 tab 时不需要 KeepAlive,把所有路由放进 exclude 仅保留切换动画
|
|
@@ -4212,7 +4221,7 @@ var init_keep_alive_layer = __esmMin((() => {
|
|
|
4212
4221
|
//#region src/layout/layout-content/index.tsx
|
|
4213
4222
|
function LayoutContent() {
|
|
4214
4223
|
const { token: { colorBgLayout } } = theme.useToken();
|
|
4215
|
-
const {
|
|
4224
|
+
const { contentElementRef } = useLayoutContentStyle();
|
|
4216
4225
|
const enableFooter = usePreferencesStore((state) => state.enableFooter);
|
|
4217
4226
|
const fixedFooter = usePreferencesStore((state) => state.fixedFooter);
|
|
4218
4227
|
/**
|
|
@@ -4221,7 +4230,7 @@ function LayoutContent() {
|
|
|
4221
4230
|
*/
|
|
4222
4231
|
return /* @__PURE__ */ jsx("main", {
|
|
4223
4232
|
id: ELEMENT_ID_MAIN_CONTENT,
|
|
4224
|
-
ref:
|
|
4233
|
+
ref: contentElementRef,
|
|
4225
4234
|
className: "relative overflow-y-auto overflow-x-hidden grow",
|
|
4226
4235
|
style: { backgroundColor: colorBgLayout },
|
|
4227
4236
|
children: /* @__PURE__ */ jsx(Scrollbar, { children: /* @__PURE__ */ jsx(GlobalSpin, { children: /* @__PURE__ */ jsxs("div", {
|
|
@@ -4447,7 +4456,11 @@ function GlobalSearch() {
|
|
|
4447
4456
|
useKeyPress("downarrow", handleDown);
|
|
4448
4457
|
useEffect(() => {
|
|
4449
4458
|
if (!keyword.length && Array.isArray(searchHistory)) setResultOptions(searchMenuList.filter((item) => searchHistory?.includes(item.key)));
|
|
4450
|
-
}, [
|
|
4459
|
+
}, [
|
|
4460
|
+
keyword,
|
|
4461
|
+
searchHistory,
|
|
4462
|
+
searchMenuList
|
|
4463
|
+
]);
|
|
4451
4464
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
4452
4465
|
onClick: () => setOpen((open) => !open),
|
|
4453
4466
|
className: "group flex justify-center items-center gap-2 md:bg-colorBgLayout px-3 py-1.5 rounded-full cursor-pointer text-colorTextSecondary hover:text-colorText md:mr-2.5",
|
|
@@ -5113,18 +5126,18 @@ function Sidebar() {
|
|
|
5113
5126
|
name: item.name,
|
|
5114
5127
|
checked: item.value,
|
|
5115
5128
|
onChange: handleChange,
|
|
5116
|
-
|
|
5117
|
-
|
|
5129
|
+
disabled: item.disabled,
|
|
5130
|
+
children: item.label
|
|
5118
5131
|
}, item.name);
|
|
5119
5132
|
}),
|
|
5120
5133
|
/* @__PURE__ */ jsx(SwitchItem, {
|
|
5121
5134
|
name: "sidebarTheme",
|
|
5122
5135
|
checked: sidebarTheme === "light",
|
|
5123
5136
|
onChange: (name, value) => setPreferences(name, value ? "light" : "dark"),
|
|
5124
|
-
children: t("preferences.sidebar.sidebarTheme"),
|
|
5125
5137
|
disabled: !sidebarEnable,
|
|
5126
5138
|
unCheckedChildren: /* @__PURE__ */ jsx(RiContrastFill, {}),
|
|
5127
|
-
checkedChildren: /* @__PURE__ */ jsx(RiMoonIcon, {})
|
|
5139
|
+
checkedChildren: /* @__PURE__ */ jsx(RiMoonIcon, {}),
|
|
5140
|
+
children: t("preferences.sidebar.sidebarTheme")
|
|
5128
5141
|
}),
|
|
5129
5142
|
/* @__PURE__ */ jsx(NumberInputSpinner, {
|
|
5130
5143
|
min: 40,
|
|
@@ -5702,7 +5715,7 @@ function UserMenu({ ...restProps }) {
|
|
|
5702
5715
|
}
|
|
5703
5716
|
if (key === "personal-center") navigate("/personal-center/my-profile");
|
|
5704
5717
|
};
|
|
5705
|
-
const altView = useMemo(() => isWindowsOs() ? "Alt" : "⌥", [
|
|
5718
|
+
const altView = useMemo(() => isWindowsOs() ? "Alt" : "⌥", []);
|
|
5706
5719
|
const items = [{
|
|
5707
5720
|
label: t("personal-center:menu.personalCenter"),
|
|
5708
5721
|
key: "personal-center",
|
|
@@ -5973,7 +5986,7 @@ function LayoutMenu({ mode = "inline", autoExpandCurrentMenu, handleMenuSelect,
|
|
|
5973
5986
|
isMobile,
|
|
5974
5987
|
sidebarCollapsed
|
|
5975
5988
|
]);
|
|
5976
|
-
const handleOpenChange = (keys) => {
|
|
5989
|
+
const handleOpenChange = useCallback((keys) => {
|
|
5977
5990
|
/**
|
|
5978
5991
|
* 1. 手风琴模式,点击菜单项,自动关闭其他菜单
|
|
5979
5992
|
* 2. 非手风琴模式且菜单是收起的,鼠标悬浮菜单自动关闭其他菜单
|
|
@@ -5992,7 +6005,12 @@ function LayoutMenu({ mode = "inline", autoExpandCurrentMenu, handleMenuSelect,
|
|
|
5992
6005
|
if (currentCloseKey) setOpenKeys(menuParentKeys[currentCloseKey]);
|
|
5993
6006
|
}
|
|
5994
6007
|
} else setOpenKeys(keys);
|
|
5995
|
-
}
|
|
6008
|
+
}, [
|
|
6009
|
+
accordion,
|
|
6010
|
+
sidebarCollapsed,
|
|
6011
|
+
openKeys,
|
|
6012
|
+
menuParentKeys
|
|
6013
|
+
]);
|
|
5996
6014
|
const menuOpenProps = useMemo(() => {
|
|
5997
6015
|
if (autoExpandCurrentMenu) return {
|
|
5998
6016
|
openKeys,
|
|
@@ -6019,7 +6037,8 @@ function LayoutMenu({ mode = "inline", autoExpandCurrentMenu, handleMenuSelect,
|
|
|
6019
6037
|
}, [
|
|
6020
6038
|
matches,
|
|
6021
6039
|
sidebarCollapsed,
|
|
6022
|
-
getSelectedKeys
|
|
6040
|
+
getSelectedKeys,
|
|
6041
|
+
accordion
|
|
6023
6042
|
]);
|
|
6024
6043
|
return /* @__PURE__ */ jsx(Menu, {
|
|
6025
6044
|
/**
|
|
@@ -6552,7 +6571,6 @@ function useDropdownMenu() {
|
|
|
6552
6571
|
}, [
|
|
6553
6572
|
t,
|
|
6554
6573
|
activeKey,
|
|
6555
|
-
homePath,
|
|
6556
6574
|
openTabs
|
|
6557
6575
|
]);
|
|
6558
6576
|
/**
|
|
@@ -6569,6 +6587,8 @@ function useDropdownMenu() {
|
|
|
6569
6587
|
[TabActionKeys.CLOSE_OTHERS]: closeOtherTabs,
|
|
6570
6588
|
[TabActionKeys.CLOSE_ALL]: closeAllTabs
|
|
6571
6589
|
}), [
|
|
6590
|
+
refresh,
|
|
6591
|
+
setIsRefresh,
|
|
6572
6592
|
removeTab,
|
|
6573
6593
|
closeRightTabs,
|
|
6574
6594
|
closeLeftTabs,
|
|
@@ -6814,7 +6834,7 @@ function LayoutTabbar() {
|
|
|
6814
6834
|
hash: ""
|
|
6815
6835
|
};
|
|
6816
6836
|
navigate(key + historyState.search + historyState.hash);
|
|
6817
|
-
}, [openTabs]);
|
|
6837
|
+
}, [openTabs, navigate]);
|
|
6818
6838
|
/**
|
|
6819
6839
|
* 处理标签页编辑(关闭)
|
|
6820
6840
|
* @param {React.MouseEvent | React.KeyboardEvent | string} key - 被编辑的标签页的key
|
|
@@ -6941,7 +6961,8 @@ function LayoutTabbar() {
|
|
|
6941
6961
|
location,
|
|
6942
6962
|
currentRoute,
|
|
6943
6963
|
setActiveKey,
|
|
6944
|
-
addTab
|
|
6964
|
+
addTab,
|
|
6965
|
+
activeKey
|
|
6945
6966
|
]);
|
|
6946
6967
|
return /* @__PURE__ */ jsx("div", {
|
|
6947
6968
|
className: classes.tabsContainer,
|
|
@@ -7023,7 +7044,7 @@ function ContainerLayout() {
|
|
|
7023
7044
|
if (screens.lg && !screens.xl) setPreferences("sidebarCollapsed", true);
|
|
7024
7045
|
else if (screens.xl) setPreferences("sidebarCollapsed", false);
|
|
7025
7046
|
else if (screens.xs || screens.sm && !screens.md) setPreferences("sidebarCollapsed", false);
|
|
7026
|
-
}, [screens]);
|
|
7047
|
+
}, [screens, setPreferences]);
|
|
7027
7048
|
const sidebarEnableState = useMemo(() => !isTopNav && sidebarEnable, [isTopNav, sidebarEnable]);
|
|
7028
7049
|
const computedSidebarWidth = useMemo(() => {
|
|
7029
7050
|
if (isMaximize || isMobile) return 0;
|
|
@@ -7049,7 +7070,7 @@ function ContainerLayout() {
|
|
|
7049
7070
|
let height = 48;
|
|
7050
7071
|
if (tabbarEnable) height += 35;
|
|
7051
7072
|
return height;
|
|
7052
|
-
}, [tabbarEnable
|
|
7073
|
+
}, [tabbarEnable]);
|
|
7053
7074
|
useEffect(() => {
|
|
7054
7075
|
setLayoutHeaderHeight(isMaximize ? 35 : headerWrapperHeight);
|
|
7055
7076
|
}, [headerWrapperHeight, isMaximize]);
|
|
@@ -7934,7 +7955,7 @@ async function generateRoutesFromBackend(backendRoutes) {
|
|
|
7934
7955
|
/**
|
|
7935
7956
|
* 转换路由配置
|
|
7936
7957
|
* @param route 原始路由配置
|
|
7937
|
-
* @param
|
|
7958
|
+
* @param parentComponentPath 父级组件路径(用于嵌套路由)
|
|
7938
7959
|
* @returns 转换后的路由配置
|
|
7939
7960
|
*/
|
|
7940
7961
|
const transformRoute = async (route, parentComponentPath) => {
|
|
@@ -8918,8 +8939,6 @@ var init_remember_route = __esmMin((() => {
|
|
|
8918
8939
|
* 必须带上部署 base(`resolveSiteBase()`):子路径部署下 `location.href = "/login"`
|
|
8919
8940
|
* 会跳到宿主机根,而不是 `/oj/login`。不能用 `import.meta.env.BASE_URL`——预构建
|
|
8920
8941
|
* 产物里它是构建期内联的 `/`。
|
|
8921
|
-
*
|
|
8922
|
-
* @returns 无返回值
|
|
8923
8942
|
*/
|
|
8924
8943
|
function goLogin() {
|
|
8925
8944
|
useAuthStore.getState().reset();
|
|
@@ -9022,6 +9041,8 @@ function onRefreshFailed(error) {
|
|
|
9022
9041
|
* 订阅者对象应包含 resolve 和 reject 方法。
|
|
9023
9042
|
*
|
|
9024
9043
|
* @param subscriber 订阅者对象,包含 resolve 和 reject 方法
|
|
9044
|
+
* @param subscriber.resolve 当 token 刷新成功时调用的函数,传入新的 token
|
|
9045
|
+
* @param subscriber.reject 当 token 刷新失败时调用的函数,传入错误信息
|
|
9025
9046
|
*/
|
|
9026
9047
|
function addRefreshSubscriber(subscriber) {
|
|
9027
9048
|
refreshSubscribers.push(subscriber);
|
|
@@ -9447,6 +9468,10 @@ function useAccess() {
|
|
|
9447
9468
|
* 权限验证组件
|
|
9448
9469
|
*
|
|
9449
9470
|
* @param AccessControlProps 权限验证组件的属性
|
|
9471
|
+
* @param AccessControlProps.type 权限类型,默认为 code
|
|
9472
|
+
* @param AccessControlProps.codes 权限值,可以是字符串或字符串数组
|
|
9473
|
+
* @param AccessControlProps.children 有权限时渲染的子组件
|
|
9474
|
+
* @param AccessControlProps.fallback 无权限时显示的内容
|
|
9450
9475
|
* @returns 若子组件存在,并且传入的权限值有效,则返回子组件;否则返回 null
|
|
9451
9476
|
*/
|
|
9452
9477
|
function AccessControl({ type = "code", codes, children, fallback }) {
|
|
@@ -9800,7 +9825,7 @@ function BasicTable(props) {
|
|
|
9800
9825
|
if (enableFooter && fixedFooter) return 40;
|
|
9801
9826
|
return 0;
|
|
9802
9827
|
}, [enableFooter, fixedFooter]);
|
|
9803
|
-
const getPaginationProps = () => {
|
|
9828
|
+
const getPaginationProps = useCallback(() => {
|
|
9804
9829
|
if (props.pagination === false) return false;
|
|
9805
9830
|
return {
|
|
9806
9831
|
placement: ["bottomStart"],
|
|
@@ -9810,7 +9835,7 @@ function BasicTable(props) {
|
|
|
9810
9835
|
showTotal: (total) => t("common.pagination", { total }),
|
|
9811
9836
|
...props.pagination
|
|
9812
9837
|
};
|
|
9813
|
-
};
|
|
9838
|
+
}, [props.pagination, t]);
|
|
9814
9839
|
/**
|
|
9815
9840
|
* @description 计算分页器的高度
|
|
9816
9841
|
* 如果分页器被禁用,则返回 0,否则根据分页器的大小返回相应的高度
|
package/shell-dist/index.html
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" href="/favicon.ico" />
|
|
7
7
|
<title>React Antd Admin · Shell</title>
|
|
8
|
-
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' https://modules.cdn.example.com 'wasm-unsafe-eval' 'nonce-
|
|
8
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' https://modules.cdn.example.com 'wasm-unsafe-eval' 'nonce-KUwgkL5p/qZWtW8VUNiLzg=='; script-src-attr 'none'; connect-src 'self' data: https://modules.cdn.example.com; style-src 'self' https://modules.cdn.example.com 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; frame-src https://ant.design https://react.dev https://condorheroblog.github.io; form-action 'none'; base-uri 'none'; object-src 'none'; upgrade-insecure-requests" />
|
|
9
9
|
|
|
10
|
-
<script type="importmap" nonce="
|
|
10
|
+
<script type="importmap" nonce="KUwgkL5p/qZWtW8VUNiLzg==">{
|
|
11
11
|
"imports": {
|
|
12
12
|
"react": "/assets/react.js",
|
|
13
13
|
"react/jsx-runtime": "/assets/jsx-runtime.js",
|
package/shell-dist/versions.json
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
"react-router": "7.18.2",
|
|
8
8
|
"react-router/dom": "7.18.2",
|
|
9
9
|
"@tanstack/react-query": "5.102.8",
|
|
10
|
-
"@oj-module/runtime": "0.2.
|
|
11
|
-
"@oj-module/runtime/contract": "0.2.
|
|
12
|
-
"@oj-module/runtime/contract/errors": "0.2.
|
|
10
|
+
"@oj-module/runtime": "0.2.23",
|
|
11
|
+
"@oj-module/runtime/contract": "0.2.23",
|
|
12
|
+
"@oj-module/runtime/contract/errors": "0.2.23",
|
|
13
13
|
"@plane/kit": "0.1.20",
|
|
14
14
|
"@plane/editor": "0.1.20",
|
|
15
15
|
"antd": "6.6.1",
|
|
@@ -62,3 +62,57 @@ describe("given 纯前端工程(无 api/src)When ojm build 后端", () => {
|
|
|
62
62
|
expect(existsSync(join(root, "api", "dist"))).toBe(false);
|
|
63
63
|
});
|
|
64
64
|
});
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 假 oj:把 argv 落盘供断言;`build --help` 的输出可切(模拟是否支持 --module-root)。
|
|
68
|
+
* 能力探针会先跑 `build --help`,所以脚本必须区分这两次调用。
|
|
69
|
+
*/
|
|
70
|
+
function fakeOjRecording(projectRoot: string, supportsModuleRoot: boolean): void {
|
|
71
|
+
mkdirSync(join(projectRoot, "bin"), { recursive: true });
|
|
72
|
+
const bin = join(projectRoot, "bin", "oj");
|
|
73
|
+
writeFileSync(bin, [
|
|
74
|
+
"#!/bin/sh",
|
|
75
|
+
"if [ \"$1\" = \"build\" ] && [ \"$2\" = \"--help\" ]; then",
|
|
76
|
+
` ${supportsModuleRoot ? "echo ' --module-root <GLOB> 模块根 glob'" : "echo ' -d, --dir <DIR>'"}`,
|
|
77
|
+
" exit 0",
|
|
78
|
+
"fi",
|
|
79
|
+
"if [ \"$1\" = \"-V\" ]; then echo 'oj 0.1.84'; exit 0; fi",
|
|
80
|
+
"printf '%s\\n' \"$*\" > \"$(dirname \"$0\")/invoked\"",
|
|
81
|
+
"exit 0",
|
|
82
|
+
"",
|
|
83
|
+
].join("\n"), { mode: 0o755 });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** 新布局工程:src/<name>/{api,web} + 仓库根 config.yaml */
|
|
87
|
+
function newModuleRootProject(): string {
|
|
88
|
+
const root = mkdtempSync(join(tmpdir(), "ojm-build-backend-mr-"));
|
|
89
|
+
mkdirSync(join(root, "src", "wiki", "api"), { recursive: true });
|
|
90
|
+
writeFileSync(join(root, "src", "wiki", "api", "manifest.yaml"), "name: wiki\nversion: 0.1.0\n", "utf-8");
|
|
91
|
+
writeFileSync(join(root, "config.yaml"), "server:\n port: 9778\n", "utf-8");
|
|
92
|
+
return root;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
describe("given module-root 布局 When ojm build 后端", () => {
|
|
96
|
+
it("then 一次调用、单一输出根:--module-root src + -o api/dist(与 -d 互斥)", async () => {
|
|
97
|
+
const root = newModuleRootProject();
|
|
98
|
+
fakeOjRecording(root, true);
|
|
99
|
+
|
|
100
|
+
expect(await buildBackend(root)).toBe(true);
|
|
101
|
+
|
|
102
|
+
// 逐模块 `-o <tmp>` 会让每个 tmp 各写一份 manifests.yaml,锁与兄弟目录关系全丢
|
|
103
|
+
const invoked = readFileSync(join(root, "bin", "invoked"), "utf-8").trim();
|
|
104
|
+
expect(invoked).toBe(
|
|
105
|
+
`build -c ${join(root, "config.yaml")} --module-root ${join(root, "src")} -o ${join(root, "api", "dist")} --no-minify`,
|
|
106
|
+
);
|
|
107
|
+
expect(existsSync(join(root, "api", "dist", "versions.json"))).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("then oj 缺 --module-root 能力时 fail-fast,不落任何产物", async () => {
|
|
111
|
+
const root = newModuleRootProject();
|
|
112
|
+
fakeOjRecording(root, false);
|
|
113
|
+
|
|
114
|
+
await expect(buildBackend(root)).rejects.toThrow(/不支持 --module-root/);
|
|
115
|
+
expect(existsSync(join(root, "bin", "invoked"))).toBe(false); // 没跑 oj build
|
|
116
|
+
expect(existsSync(join(root, "api", "dist"))).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
});
|