@oj-module/cli 0.2.54 → 0.2.57
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 +7 -4
- package/devkit/SKILL.md +7 -4
- package/devkit/manual.md +63 -54
- package/devkit/module-dev-and-cdn-playground.md +7 -0
- package/devkit/publish-walkthrough.md +199 -0
- package/package.json +2 -2
- package/shell-dist/assets/{index-e_Mp2mb5.js → index-CImwskAz.js} +24 -11
- package/shell-dist/assets/runtime.js +5 -5
- package/shell-dist/index.html +3 -3
- package/shell-dist/versions.json +3 -3
- package/src/__tests__/layout.test.ts +30 -1
- package/src/__tests__/templates-schema-columns.test.ts +99 -0
- package/src/__tests__/vendor-devkit.test.ts +87 -0
- package/src/alias-paths.ts +11 -12
- package/src/build/backend.ts +131 -0
- package/src/build/module-build.ts +364 -0
- package/src/build/module-def.ts +381 -0
- package/src/build/site-merge.ts +33 -0
- package/src/build.ts +16 -885
- package/src/builtin.ts +10 -3
- package/src/bundle.ts +61 -0
- package/src/cli.ts +4 -1
- package/src/contract/__tests__/emit-client.test.ts +4 -4
- package/src/contract/__tests__/market-client.integration.test.ts +2 -2
- package/src/contract/__tests__/run-api.test.ts +2 -2
- package/src/contract/check.ts +6 -2
- package/src/contract/emit-client.ts +10 -3
- package/src/contract/run.ts +10 -2
- package/src/init.ts +31 -46
- package/src/layout.ts +72 -16
- package/src/lifecycle/__tests__/bundle-summary.test.ts +106 -0
- package/src/lifecycle/__tests__/bundle.test.ts +6 -0
- package/src/lifecycle/__tests__/manifest-contract.test.ts +2 -2
- package/src/market/__tests__/bootstrap.test.ts +1 -1
- package/src/site.ts +1 -1
- package/src/vendor.ts +34 -0
- package/templates/README.md +30 -20
- package/templates/init.include.yaml +19 -11
- package/templates/openapi.json +102 -86
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/installer.ts +8 -3
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/register.ts +53 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/roles.ts +4 -1
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/solution-perms.ts +7 -3
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/users.ts +27 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/oplog.ts +4 -2
- package/templates/src/app/api/_platform/migrations/0017__users_employee_no.mysql.sql +7 -0
- package/templates/src/app/api/_platform/migrations/0017__users_employee_no.pg.sql +4 -0
- package/templates/src/app/api/_platform/migrations/0017__users_employee_no.sql +8 -0
- package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.mysql.sql +3 -0
- package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.pg.sql +3 -0
- package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.sql +8 -0
- package/templates/src/{_platform/api → app/api/_platform}/schema.yaml +7 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/replay.ts +9 -3
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/tenant-admin.ts +18 -27
- package/templates/src/{system/api → app/api/system}/oplog/api.ts +5 -3
- package/templates/src/{system/api → app/api/system}/roles/api.ts +2 -1
- package/templates/src/{_platform/web/__client/api.ts → app/web/_platform/__client/index.ts} +5 -2
- package/templates/src/{auth/web/__client/api.ts → app/web/auth/__client/index.ts} +5 -2
- package/templates/src/app/web/notification/pages/center/index.tsx +1 -1
- package/templates/src/app/web/notification/routes.ts +1 -1
- package/templates/src/app/web/personal-center/pages/my-profile/index.tsx +1 -1
- package/templates/src/app/web/personal-center/routes.ts +1 -1
- package/templates/src/app/web/system/__client/api.schemas.ts +2 -0
- package/templates/src/app/web/system/locales/en-US.json +2 -3
- package/templates/src/app/web/system/locales/zh-CN.json +2 -3
- package/templates/src/app/web/system/pages/departments/index.tsx +2 -2
- package/templates/src/app/web/system/pages/login-methods/index.tsx +1 -1
- package/templates/src/app/web/system/pages/oplog/index.tsx +44 -19
- package/templates/src/app/web/system/pages/roles/index.tsx +12 -4
- package/templates/src/app/web/system/pages/roles/visible-roles.ts +14 -0
- package/templates/src/app/web/system/pages/settings/index.tsx +2 -2
- package/templates/src/app/web/system/pages/users/can-manage.ts +21 -0
- package/templates/src/app/web/system/pages/users/index.tsx +25 -17
- package/templates/src/app/web/system/pages/users/roles.tsx +1 -1
- package/templates/src/app/web/system/routes.ts +1 -1
- package/templates/src/{web/web/__client/api.ts → app/web/web/__client/index.ts} +5 -2
- package/templates/src/oj-market/web/api/defaultClient.ts +1 -1
- package/templates/tests/auth-ldap.spec.ts +2 -2
- package/templates/tests/auth-session.spec.ts +3 -3
- package/templates/tests/auth-throttle.spec.ts +1 -1
- package/templates/tests/fixture.ts +6 -4
- package/templates/tests/guard.spec.ts +1 -1
- package/templates/tests/lifecycle/atomic-state.spec.ts +3 -3
- package/templates/tests/lifecycle/bootstrap.spec.ts +3 -3
- package/templates/tests/lifecycle/deploy-smoke.spec.ts +8 -8
- package/templates/tests/lifecycle/installer.spec.ts +10 -10
- package/templates/tests/lifecycle/market-api.spec.ts +14 -14
- package/templates/tests/lifecycle/migrate.spec.ts +6 -6
- package/templates/tests/lifecycle/preflight.spec.ts +3 -3
- package/templates/tests/lifecycle/register.spec.ts +1 -1
- package/templates/tests/lifecycle/role-title-translate.spec.ts +171 -0
- package/templates/tests/lifecycle/semver.spec.ts +1 -1
- package/templates/tests/lifecycle/sha384.spec.ts +1 -1
- package/templates/tests/lifecycle/solutions-api.spec.ts +1 -1
- package/templates/tests/lifecycle/source-seq.spec.ts +1 -1
- package/templates/tests/lifecycle/steps-fs.spec.ts +9 -9
- package/templates/tests/lifecycle/text-edit.spec.ts +1 -1
- package/templates/tests/lifecycle/tgz-guard.spec.ts +2 -2
- package/templates/tests/lifecycle/trust.spec.ts +5 -5
- package/templates/tests/lifecycle/uninstall.spec.ts +7 -7
- package/templates/tests/lifecycle/verify-checks.spec.ts +5 -5
- package/templates/tests/lifecycle/verify-report.spec.ts +4 -4
- package/templates/tests/org-departments.spec.ts +10 -6
- package/templates/tests/org-read.spec.ts +7 -7
- package/templates/tests/query-utils.spec.ts +1 -1
- package/templates/tests/rbac-audit.spec.ts +1 -1
- package/templates/tests/rbac-contacts.spec.ts +2 -2
- package/templates/tests/rbac-guard.spec.ts +1 -1
- package/templates/tests/rbac-roles.spec.ts +19 -2
- package/templates/tests/rbac-seed.spec.ts +1 -1
- package/templates/tests/rbac-settings.spec.ts +77 -7
- package/templates/tests/rbac-solution-perms.spec.ts +1 -1
- package/templates/tests/rbac-tables.spec.ts +29 -0
- package/templates/tests/rbac-users.spec.ts +55 -3
- package/templates/tests/roles-page.spec.ts +40 -0
- package/templates/tests/seed-role-accounts.spec.ts +1 -1
- package/templates/tests/tenant-access-cache.spec.ts +1 -1
- package/templates/tests/tenant-forgery.spec.ts +5 -5
- package/templates/tests/tenant-isolation.spec.ts +5 -4
- package/templates/tests/username-namespace.spec.ts +3 -3
- package/templates/tests/users-page.spec.ts +39 -0
- package/templates/tests/wecom-admin.spec.ts +6 -6
- package/templates/tests/wecom-client-timeout.spec.ts +1 -1
- package/templates/tests/wecom-connector.spec.ts +9 -9
- package/templates/tests/wecom-corp.spec.ts +9 -9
- package/templates/tests/wecom-crypt.spec.ts +1 -1
- package/templates/tests/wecom-endpoints.spec.ts +2 -2
- package/templates/tests/wecom-landing.spec.ts +1 -1
- package/templates/tests/wecom-org-sync.spec.ts +7 -7
- package/templates/tests/wecom-sync.spec.ts +3 -3
- package/templates/tsconfig.paths.json +11 -11
- package/templates/vitest.config.ts +2 -2
- package/templates/web.config.ts +5 -2
- package/vendor/host-versions.json +3 -3
- package/templates/src/{_platform/api → app/api/_platform}/README.md +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/atomic.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/history.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/limits.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/lock.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/semver.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/sha384.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/source-seq.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/state.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/deploy-web.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/download.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/extract.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/migrate.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/preflight.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/reload.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/smoke.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/verify.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/tasks.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/text-edit.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/tgz-guard.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/trust.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/types.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/uninstall.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/verify-checks.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/verify-report.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/respond.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/contacts.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/org-read.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/org.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/settings.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/access.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/audit.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/guard.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/query.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/rate.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/site-facade.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/contacts/api.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/contacts/detail/api.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/fixtures/l1-test-user.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/manifest.yaml +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/market/api.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/market/bootstrap.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/market/index-source.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0001__create_users.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0001__create_users.pg.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0001__create_users.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0002__add_users_avatar_base64.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0002__add_users_avatar_base64.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0003__users_source_display_name.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0003__users_source_display_name.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0004__sys_rbac.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0004__sys_rbac.pg.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0004__sys_rbac.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0005__users_must_change.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0005__users_must_change.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0006__users_is_forbidden.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0006__users_is_forbidden.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0007__users_email.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0008__sys_refresh_token.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0008__sys_refresh_token.pg.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0008__sys_refresh_token.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0009__sys_throttle.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0009__sys_throttle.pg.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0009__sys_throttle.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0010__sys_solution_route.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0010__sys_solution_route.pg.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0010__sys_solution_route.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0011__sys_setting_group_label.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0011__sys_setting_group_label.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0012__market_history.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0012__market_history.pg.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0012__market_history.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0013__users_profile.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0014__reg_source_seq.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0014__reg_source_seq.pg.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0014__reg_source_seq.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0015__tenant.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0015__tenant.pg.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0015__tenant.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0016__org.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0016__org.pg.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0016__org.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/org/departments/api.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/org/is-member/api.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/org/members/api.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/org/picker/api.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/org/resolve/api.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/org/tree/api.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/org/user/api.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/seed.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/seed.pg.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/seed.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/solutions/api.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/ldap-login.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/ldap.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/login-throttle.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/session.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/admin.ts +1 -1
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/client.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/config.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/contacts.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/crypt.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/events.ts +1 -1
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/login.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/payload.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/store.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/sync.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/api.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/ldap-login/api.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/manifest.yaml +0 -0
- package/templates/src/{auth/api → app/api/auth}/migrations/0001__wecom.mysql.sql +0 -0
- package/templates/src/{auth/api → app/api/auth}/migrations/0001__wecom.pg.sql +0 -0
- package/templates/src/{auth/api → app/api/auth}/migrations/0001__wecom.sql +0 -0
- package/templates/src/{auth/api → app/api/auth}/migrations/0002__org_sync.mysql.sql +0 -0
- package/templates/src/{auth/api → app/api/auth}/migrations/0002__org_sync.pg.sql +0 -0
- package/templates/src/{auth/api → app/api/auth}/migrations/0002__org_sync.sql +0 -0
- package/templates/src/{auth/api → app/api/auth}/migrations/0003__credential_kind.mysql.sql +0 -0
- package/templates/src/{auth/api → app/api/auth}/migrations/0003__credential_kind.pg.sql +0 -0
- package/templates/src/{auth/api → app/api/auth}/migrations/0003__credential_kind.sql +0 -0
- package/templates/src/{auth/api → app/api/auth}/schema.yaml +0 -0
- package/templates/src/{auth/api → app/api/auth}/wecom/admin/api.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/wecom/admin/tenant-admin/api.ts +1 -1
- package/templates/src/{auth/api → app/api/auth}/wecom/admin/tenant-admin/password/api.ts +1 -1
- /package/templates/src/{auth/api → app/api/auth}/wecom/api.ts +0 -0
- /package/templates/src/{auth/api → app/api/auth}/wecom/sync/api.ts +0 -0
- /package/templates/src/{home/api → app/api/home}/line/api.ts +0 -0
- /package/templates/src/{home/api → app/api/home}/manifest.yaml +0 -0
- /package/templates/src/{home/api → app/api/home}/migrations/0001__create_home_tables.sql +0 -0
- /package/templates/src/{home/api → app/api/home}/migrations/0002__tenant.mysql.sql +0 -0
- /package/templates/src/{home/api → app/api/home}/migrations/0002__tenant.sql +0 -0
- /package/templates/src/{home/api → app/api/home}/pie/api.ts +0 -0
- /package/templates/src/{home/api → app/api/home}/schema.yaml +0 -0
- /package/templates/src/{home/api → app/api/home}/seed.sql +0 -0
- /package/templates/src/{notification/api → app/api/notification}/_shared/notify.ts +0 -0
- /package/templates/src/{notification/api → app/api/notification}/api.ts +0 -0
- /package/templates/src/{notification/api → app/api/notification}/del/api.ts +0 -0
- /package/templates/src/{notification/api → app/api/notification}/manifest.yaml +0 -0
- /package/templates/src/{notification/api → app/api/notification}/migrations/0001__create_notifications.sql +0 -0
- /package/templates/src/{notification/api → app/api/notification}/migrations/0002__notifications_user.sql +0 -0
- /package/templates/src/{notification/api → app/api/notification}/migrations/0003__notifications_broadcast.sql +0 -0
- /package/templates/src/{notification/api → app/api/notification}/migrations/0004__notifications_broadcast_hidden.sql +0 -0
- /package/templates/src/{notification/api → app/api/notification}/migrations/0005__tenant.mysql.sql +0 -0
- /package/templates/src/{notification/api → app/api/notification}/migrations/0005__tenant.sql +0 -0
- /package/templates/src/{notification/api → app/api/notification}/schema.yaml +0 -0
- /package/templates/src/{notification/api → app/api/notification}/seed.sql +0 -0
- /package/templates/src/{personal-center/api → app/api/personal-center}/manifest.yaml +0 -0
- /package/templates/src/{personal-center/api → app/api/personal-center}/profile/api.ts +0 -0
- /package/templates/src/{personal-center/api → app/api/personal-center}/upload/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/departments/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/departments/detach/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/departments/members/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/departments/move/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/login-methods/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/manifest.yaml +0 -0
- /package/templates/src/{system/api → app/api/system}/permissions/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/roles/bindings/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/settings/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/users/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/users/forbidden/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/users/password/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/users/profile/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/users/roles/api.ts +0 -0
- /package/templates/src/{web/api → app/api/web}/get-async-routes/api.ts +0 -0
- /package/templates/src/{web/api → app/api/web}/hello/api.ts +0 -0
- /package/templates/src/{web/api → app/api/web}/manifest.yaml +0 -0
- /package/templates/src/{web/api → app/api/web}/user-info/api.ts +0 -0
- /package/templates/src/{_platform/web → app/web/_platform}/__client/api.schemas.ts +0 -0
- /package/templates/src/{auth/web → app/web/auth}/__client/api.schemas.ts +0 -0
- /package/templates/src/app/web/home/__client/{api.ts → index.ts} +0 -0
- /package/templates/src/app/web/notification/__client/{api.ts → index.ts} +0 -0
- /package/templates/src/app/web/personal-center/__client/{api.ts → index.ts} +0 -0
- /package/templates/src/app/web/system/__client/{api.ts → index.ts} +0 -0
- /package/templates/src/{web → app/web}/web/__client/api.schemas.ts +0 -0
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.57",
|
|
5
5
|
"description": "React Antd 模块工程命令行工具(含预构建宿主 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.57",
|
|
53
53
|
"@redocly/cli": "^2.51.0",
|
|
54
54
|
"@tailwindcss/vite": "^4.3.0",
|
|
55
55
|
"cac": "^6.7.14",
|
|
@@ -276,9 +276,30 @@ function buildShellChildren() {
|
|
|
276
276
|
* 路由表;随后 SPA 登录(不刷新页面)菜单回来了,但 /system/* 仍不在路由表里,
|
|
277
277
|
* 直达恒落 catch-all 404「返回首页」,须硬刷新才恢复。
|
|
278
278
|
*/
|
|
279
|
+
/**
|
|
280
|
+
* 宿主路由根(Boot 建表与登录后重登记共用同一形状,children 按当前角色快照生成)。
|
|
281
|
+
*
|
|
282
|
+
* 路由根只渲染容器(Outlet),整站 chrome 由模块自带的 ContainerLayout 提供;
|
|
283
|
+
* 宿主不叠加自己的 Layout chrome,避免「宿主侧栏 + 模块侧栏」双层嵌套。
|
|
284
|
+
* element 里同时挂 LayoutEffects(暗色类/动态标题/NProgress,与 App 链同源,偏差 4)
|
|
285
|
+
* 与页面级 ErrorBoundary(模块路由不经 runtime LayoutRoot,缺它时渲染抛错整页白屏)。
|
|
286
|
+
*/
|
|
287
|
+
function buildShellRoot() {
|
|
288
|
+
return {
|
|
289
|
+
path: "/",
|
|
290
|
+
id: SHELL_ROOT_ID,
|
|
291
|
+
element: /* @__PURE__ */ jsxs(ErrorBoundary, {
|
|
292
|
+
FallbackComponent: PageError,
|
|
293
|
+
children: [/* @__PURE__ */ jsx(LayoutEffects, {}), /* @__PURE__ */ jsx(Outlet, {})]
|
|
294
|
+
}),
|
|
295
|
+
children: buildShellChildren()
|
|
296
|
+
};
|
|
297
|
+
}
|
|
279
298
|
function repatchShellRoutes() {
|
|
280
299
|
if (!shellRouter) return;
|
|
281
|
-
shellRouter.
|
|
300
|
+
const internal = shellRouter._internalSetRoutes;
|
|
301
|
+
if (internal) internal.call(shellRouter, [buildShellRoot()]);
|
|
302
|
+
else shellRouter.patchRoutes(SHELL_ROOT_ID, buildShellChildren());
|
|
282
303
|
const { pathname, search } = shellRouter.state.location;
|
|
283
304
|
shellRouter.navigate(`${pathname}${search}`, { replace: true }).catch(() => {});
|
|
284
305
|
}
|
|
@@ -313,7 +334,7 @@ function Boot() {
|
|
|
313
334
|
(async () => {
|
|
314
335
|
try {
|
|
315
336
|
if (typeof navigator !== "undefined" && /wxwork/i.test(navigator.userAgent) && location.pathname.endsWith("/login") && !new URLSearchParams(location.search).get("wecom_ticket")) {
|
|
316
|
-
window.location.assign(resolveApiBase()
|
|
337
|
+
window.location.assign(`${resolveApiBase()}/auth/wecom/login`);
|
|
317
338
|
return;
|
|
318
339
|
}
|
|
319
340
|
ensureI18n();
|
|
@@ -348,15 +369,7 @@ function Boot() {
|
|
|
348
369
|
await hydrateUser();
|
|
349
370
|
await loadAll(manifest);
|
|
350
371
|
if (cancelled) return;
|
|
351
|
-
shellRouter = createBrowserRouter([{
|
|
352
|
-
path: "/",
|
|
353
|
-
id: SHELL_ROOT_ID,
|
|
354
|
-
element: /* @__PURE__ */ jsxs(ErrorBoundary, {
|
|
355
|
-
FallbackComponent: PageError,
|
|
356
|
-
children: [/* @__PURE__ */ jsx(LayoutEffects, {}), /* @__PURE__ */ jsx(Outlet, {})]
|
|
357
|
-
}),
|
|
358
|
-
children: buildShellChildren()
|
|
359
|
-
}], {
|
|
372
|
+
shellRouter = createBrowserRouter([buildShellRoot()], {
|
|
360
373
|
/**
|
|
361
374
|
* 子路径部署(oj `mounts: [{ prefix: /oj, web: dist, spa: true }]`):
|
|
362
375
|
* 不设 basename 时 react-router 看到的是含前缀的 `/oj/wiki/...`,
|
|
@@ -304,10 +304,10 @@ function getAppInfo() {
|
|
|
304
304
|
return {
|
|
305
305
|
"pkg": {
|
|
306
306
|
"name": "@oj-module/runtime",
|
|
307
|
-
"version": "0.2.
|
|
307
|
+
"version": "0.2.57",
|
|
308
308
|
"license": "MIT"
|
|
309
309
|
},
|
|
310
|
-
"lastBuildTime": "2026-11-
|
|
310
|
+
"lastBuildTime": "2026-11-03 11:30:53"
|
|
311
311
|
};
|
|
312
312
|
}
|
|
313
313
|
var init_get_app_info = __esmMin((() => {}));
|
|
@@ -365,9 +365,9 @@ var init_preferences$3 = __esmMin((() => {
|
|
|
365
365
|
accordion: true,
|
|
366
366
|
enableFooter: true,
|
|
367
367
|
fixedFooter: true,
|
|
368
|
-
companyName: "
|
|
369
|
-
companyWebsite: "http://github.com/
|
|
370
|
-
copyrightDate: "
|
|
368
|
+
companyName: "Omni Solution",
|
|
369
|
+
companyWebsite: "http://github.com/everpan",
|
|
370
|
+
copyrightDate: "2026",
|
|
371
371
|
ICPNumber: "",
|
|
372
372
|
ICPLink: ""
|
|
373
373
|
};
|
package/shell-dist/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
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-
|
|
9
|
-
<script type="importmap" nonce="
|
|
8
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' https://modules.cdn.example.com 'wasm-unsafe-eval' 'nonce-p8IkoQDMiluaMnE2ANd5og=='; 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
|
+
<script type="importmap" nonce="p8IkoQDMiluaMnE2ANd5og==">{
|
|
10
10
|
"imports": {
|
|
11
11
|
"react": "/assets/react.js",
|
|
12
12
|
"react/jsx-runtime": "/assets/jsx-runtime.js",
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
.boot-skeleton:after{content:"";width:18px;height:18px;margin-left:8px;border:2px solid #d9d9d9;border-top-color:#1677ff;border-radius:50%;animation:bs-spin .8s linear infinite}
|
|
131
131
|
@keyframes bs-spin{to{transform:rotate(360deg)}}
|
|
132
132
|
</style>
|
|
133
|
-
<script nonce="
|
|
133
|
+
<script nonce="p8IkoQDMiluaMnE2ANd5og==" data-ojm-entry="/assets/index-CImwskAz.js">(function(){try{var ua=(navigator.userAgent||"").toLowerCase(),p=location.pathname;while(p.length>1&&p.charAt(p.length-1)==="/")p=p.slice(0,-1);if(ua.indexOf("wxwork")>=0&&p.slice(-6)==="/login"&&!new URLSearchParams(location.search).get("wecom_ticket")){var a=(typeof window.__OJ_API_BASE__==="string"&&window.__OJ_API_BASE__.trim())||"/api";while(a.length>1&&a.charAt(a.length-1)==="/")a=a.slice(0,-1);location.replace(a+"/auth/wecom/login");return}}catch(e){}var s=document.createElement("script");s.type="module";s.src=document.currentScript.getAttribute("data-ojm-entry");document.head.appendChild(s);})();</script>
|
|
134
134
|
</head>
|
|
135
135
|
<body>
|
|
136
136
|
<div id="root"><div class="boot-skeleton">加载中…</div></div>
|
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.57",
|
|
11
|
+
"@oj-module/runtime/contract": "0.2.57",
|
|
12
|
+
"@oj-module/runtime/contract/errors": "0.2.57",
|
|
13
13
|
"@plane/kit": "0.1.20",
|
|
14
14
|
"@plane/editor": "0.1.20",
|
|
15
15
|
"antd": "6.6.1",
|
|
@@ -111,7 +111,7 @@ describe("resolveLayout / 历史布局", () => {
|
|
|
111
111
|
it("web/<name>/entry.ts 识别为 flat,web 产物落 build/(框架根仓约定不变),api 产物归 dist/api", () => {
|
|
112
112
|
const root = newRoot();
|
|
113
113
|
writeFile(root, "web/home/entry.ts", "export default {};\n");
|
|
114
|
-
writeFile(root, "web/home/__client/
|
|
114
|
+
writeFile(root, "web/home/__client/index.ts", "export {};\n");
|
|
115
115
|
|
|
116
116
|
const layout = resolveLayout(root);
|
|
117
117
|
expect(layout.kind).toBe("flat");
|
|
@@ -177,3 +177,32 @@ describe("路径取值函数", () => {
|
|
|
177
177
|
expect(webSrcOf(layout, "wiki")).toBe(join(layout.modulesSrc, "wiki", "web"));
|
|
178
178
|
});
|
|
179
179
|
});
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 聚合容器:src/app 同时有 web 侧(多根前端模块)与 api 下多个后端模块。
|
|
183
|
+
* 模块身份取 api 下那层目录名(= URL 前缀),与平铺 src/<name>/api 并存且互不干扰。
|
|
184
|
+
*/
|
|
185
|
+
describe("resolveLayout / 聚合容器(src/app/api/<name>)", () => {
|
|
186
|
+
it("api 子目录各自成模块,web 侧仍是 app 模块", () => {
|
|
187
|
+
const root = newRoot();
|
|
188
|
+
writeFile(root, "src/app/api/system/manifest.yaml", "name: system\nversion: 0.1.0\n");
|
|
189
|
+
writeFile(root, "src/app/api/system/users/api.ts", "export default { get() {} };\n");
|
|
190
|
+
writeFile(root, "src/app/api/notification/manifest.yaml", "name: notification\nversion: 0.1.0\n");
|
|
191
|
+
writeFile(root, "src/app/web/entry.ts", "export default {};\n");
|
|
192
|
+
const layout = resolveLayout(root);
|
|
193
|
+
const mods = scanModules(layout);
|
|
194
|
+
|
|
195
|
+
expect(mods.map(m => m.name)).toEqual(["app", "notification", "system"]);
|
|
196
|
+
expect(mods.find(m => m.name === "system")?.apiSrc).toBe(join(root, "src/app/api/system"));
|
|
197
|
+
expect(mods.find(m => m.name === "system")?.webSrc).toBeUndefined();
|
|
198
|
+
expect(apiSrcOf(layout, "system")).toBe(join(root, "src/app/api/system"));
|
|
199
|
+
expect(webSrcOf(layout, "app")).toBe(join(root, "src/app/web"));
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("同名后端模块重复(旧 src/system/api + 新 src/app/api/system)→ 明确报错", () => {
|
|
203
|
+
const root = newRoot();
|
|
204
|
+
writeFile(root, "src/system/api/manifest.yaml", "name: system\nversion: 0.1.0\n");
|
|
205
|
+
writeFile(root, "src/app/api/system/manifest.yaml", "name: system\nversion: 0.1.0\n");
|
|
206
|
+
expect(() => resolveLayout(root)).toThrowError(/模块名重复:system/);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { parse } from "yaml";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* BDD:模板后端「迁移落库的列」必须在所属模块 schema.yaml 的列白名单里声明。
|
|
9
|
+
*
|
|
10
|
+
* 为什么要这条:oj 的 SQL 构造器是二进制内的 Rust 实现,select/insert/update 的列会**先**按
|
|
11
|
+
* schema.yaml 校验,列在库里但不在白名单时,SQL 还没发就被拒成
|
|
12
|
+
* `unknown column 'x' in <site>`(site 随语句位置变:join on / insert values / update sets …)。
|
|
13
|
+
* 线上表现是端点 500、日志里没有 SQL,看起来像「列不存在」,实际是声明漏了。
|
|
14
|
+
*
|
|
15
|
+
* 真实事故(2026-11):_platform 0017 加了 users.employee_no,schema.yaml 漏声明,
|
|
16
|
+
* /system/departments(选人列表依赖 /system/users)整页 500。
|
|
17
|
+
*
|
|
18
|
+
* 校验面取 `ALTER TABLE … ADD COLUMN`:已有表新增列的唯一入口,也是最容易「只写迁移、
|
|
19
|
+
* 忘了补声明」的一类。整表声明由 t-2(租户定性)与表数快照兜底。
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const templatesRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../../templates");
|
|
23
|
+
const srcRoot = join(templatesRoot, "src");
|
|
24
|
+
|
|
25
|
+
interface ColumnDecl {
|
|
26
|
+
type?: string
|
|
27
|
+
[key: string]: unknown
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface SchemaDoc {
|
|
31
|
+
tables?: Record<string, { columns?: Record<string, ColumnDecl> }>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** `ALTER TABLE <表> ADD [COLUMN] <列>`(忽略方言专属文件,sqlite 基础件是唯一真相) */
|
|
35
|
+
const ADD_COLUMN_RE = /ALTER\s+TABLE\s+[`"']?(\w+)[`"']?\s+ADD\s+(?:COLUMN\s+)?[`"']?(\w+)[`"']?/gi;
|
|
36
|
+
|
|
37
|
+
function walk(dir: string, out: string[] = []): string[] {
|
|
38
|
+
for (const entry of readdirSync(dir)) {
|
|
39
|
+
const full = join(dir, entry);
|
|
40
|
+
if (statSync(full).isDirectory())
|
|
41
|
+
walk(full, out);
|
|
42
|
+
else
|
|
43
|
+
out.push(full);
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** 表名 → 声明的列集合(跨模块同名表由 templates-tenant-scope 的 S002 断言拦,这里取并集) */
|
|
49
|
+
function loadDeclaredColumns(files: string[]): Map<string, Set<string>> {
|
|
50
|
+
const declared = new Map<string, Set<string>>();
|
|
51
|
+
for (const file of files.filter(f => f.endsWith("schema.yaml"))) {
|
|
52
|
+
const doc = parse(readFileSync(file, "utf-8")) as SchemaDoc | null;
|
|
53
|
+
for (const [table, decl] of Object.entries(doc?.tables ?? {}))
|
|
54
|
+
declared.set(table, new Set(Object.keys(decl.columns ?? {})));
|
|
55
|
+
}
|
|
56
|
+
return declared;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** 基础迁移文件:`migrations/*.sql`,排除 `<名>.mysql.sql` / `<名>.pg.sql` */
|
|
60
|
+
function isBaseMigration(file: string): boolean {
|
|
61
|
+
return /[/\\]migrations[/\\][^/\\]+\.sql$/.test(file) && !/\.(?:mysql|pg)\.sql$/.test(file);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
describe("templates 后端 schema.yaml 列白名单", () => {
|
|
65
|
+
it("s-1 迁移里 ADD COLUMN 的列都在 schema.yaml 有声明(漏声明 = 构造器拒查)", () => {
|
|
66
|
+
const files = walk(srcRoot);
|
|
67
|
+
const declared = loadDeclaredColumns(files);
|
|
68
|
+
const violations: string[] = [];
|
|
69
|
+
|
|
70
|
+
for (const file of files.filter(isBaseMigration)) {
|
|
71
|
+
for (const m of readFileSync(file, "utf-8").matchAll(ADD_COLUMN_RE)) {
|
|
72
|
+
const [, table, column] = m;
|
|
73
|
+
const where = relative(templatesRoot, file);
|
|
74
|
+
const cols = declared.get(table);
|
|
75
|
+
if (!cols) {
|
|
76
|
+
violations.push(`${where}: 表 ${table} 未在任何 schema.yaml 声明`);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (!cols.has(column)) {
|
|
80
|
+
violations.push(
|
|
81
|
+
`${where}: ${table}.${column} 未在 schema.yaml 声明`
|
|
82
|
+
+ `(构造器会拒为 unknown column '${column}' in on '${table}';补到 owning 模块的 tables.${table}.columns)`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
expect(violations).toEqual([]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("s-2 回归锚点:0017 三方言迁移齐备,users.employee_no 已声明为 text", () => {
|
|
92
|
+
const migDir = join(srcRoot, "app", "api", "_platform", "migrations");
|
|
93
|
+
for (const file of ["0017__users_employee_no.sql", "0017__users_employee_no.mysql.sql", "0017__users_employee_no.pg.sql"])
|
|
94
|
+
expect(existsSync(join(migDir, file)), `缺迁移 ${file}`).toBe(true);
|
|
95
|
+
|
|
96
|
+
const schema = parse(readFileSync(join(srcRoot, "app", "api", "_platform", "schema.yaml"), "utf-8")) as SchemaDoc;
|
|
97
|
+
expect(schema.tables?.users?.columns?.employee_no).toMatchObject({ type: "text" });
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// BDD:devkit 全链路落地与文档同步(计划 docs/plans/20261103-1020-vendor-devkit-sync)。
|
|
2
|
+
// vendor 装/升 oj 后把 bin/devkit 文档**覆盖同步**进 .claude/skills/oj-api-dev/(R2/R3/R4);
|
|
3
|
+
// init 步骤 4 走同一出口(R5,global.d.ts 模板变体保护);ojm devkit 4b 现状回归(R1)。
|
|
4
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
8
|
+
import { initProject } from "../init";
|
|
9
|
+
import { syncOjDevkit } from "../vendor";
|
|
10
|
+
|
|
11
|
+
/* eslint-disable test/prefer-lowercase-title -- 计划编号是验收锚点 */
|
|
12
|
+
let root: string;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
root = mkdtempSync(join(tmpdir(), "ojm-devkit-sync-"));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
rmSync(root, { recursive: true, force: true });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/** 模拟 @oj-bin/oj postinstall 落盘的 bin/devkit(版本标记进内容以断言覆盖方向) */
|
|
23
|
+
function makeDevkit(version: string, dir = join(root, "bin", "devkit")): void {
|
|
24
|
+
mkdirSync(dir, { recursive: true });
|
|
25
|
+
writeFileSync(join(dir, "SKILL.md"), `# oj-api-dev skill ${version}\n`);
|
|
26
|
+
writeFileSync(join(dir, "api-manual.md"), `manual ${version}\n`);
|
|
27
|
+
// 旧 init 清单之外的文件——按目录扫描(R6)应收进来
|
|
28
|
+
writeFileSync(join(dir, "scenarios.md"), `scenarios ${version}\n`);
|
|
29
|
+
writeFileSync(join(dir, "global.d.ts"), `// devkit ${version}\n`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const skillOf = (name: string) => join(root, ".claude/skills/oj-api-dev", name);
|
|
33
|
+
|
|
34
|
+
describe("syncOjDevkit(vendor/init 共用同步出口)", () => {
|
|
35
|
+
it("R2/R6 bin/devkit 齐备 → 全部 .md 同步进 skill 目录(含旧清单外文件;global.d.ts 不进)", () => {
|
|
36
|
+
makeDevkit("v2");
|
|
37
|
+
const r = syncOjDevkit(root, join(root, "bin"), () => {});
|
|
38
|
+
expect(r.ok).toBe(true);
|
|
39
|
+
expect(r.files.sort()).toEqual(["SKILL.md", "api-manual.md", "scenarios.md"]);
|
|
40
|
+
expect(readFileSync(skillOf("SKILL.md"), "utf-8")).toContain("v2");
|
|
41
|
+
expect(readFileSync(skillOf("scenarios.md"), "utf-8")).toContain("v2");
|
|
42
|
+
expect(existsSync(skillOf("global.d.ts"))).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("R3 同步是覆盖语义:oj 升级后旧 skill 内容被当前版本刷新(非首拷赢)", () => {
|
|
46
|
+
makeDevkit("v1");
|
|
47
|
+
syncOjDevkit(root, join(root, "bin"), () => {});
|
|
48
|
+
writeFileSync(join(root, "bin", "devkit", "SKILL.md"), "# oj-api-dev skill v2\n");
|
|
49
|
+
syncOjDevkit(root, join(root, "bin"), () => {});
|
|
50
|
+
expect(readFileSync(skillOf("SKILL.md"), "utf-8")).toContain("v2");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("R3 bin/devkit 缺 SKILL.md → ok:false,skill 目录不创建、不抛错", () => {
|
|
54
|
+
const r = syncOjDevkit(root, join(root, "bin"), () => {});
|
|
55
|
+
expect(r.ok).toBe(false);
|
|
56
|
+
expect(existsSync(join(root, ".claude/skills/oj-api-dev"))).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("ojm init 集成(注入 ojInstaller 模拟 vendor 落盘)", () => {
|
|
61
|
+
it("R1/R5 init 后两侧 skill 都在:oj devkit 同步 + ojm devkit 六文件", async () => {
|
|
62
|
+
await initProject(root, {
|
|
63
|
+
yes: true,
|
|
64
|
+
seal: async () => "ENC[test]",
|
|
65
|
+
ojInstaller: async (binDir) => {
|
|
66
|
+
makeDevkit("v1", join(binDir, "devkit"));
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
expect(readFileSync(join(root, ".claude/skills/oj-api-dev/SKILL.md"), "utf-8")).toBe("# oj-api-dev skill v1\n");
|
|
70
|
+
expect(existsSync(join(root, ".claude/skills/oj-api-dev/scenarios.md"))).toBe(true);
|
|
71
|
+
for (const name of ["SKILL.md", "manual.md", "README.md", "wecom-daikaifa.md", "wecom-internal-app.md", "publish-walkthrough.md"])
|
|
72
|
+
expect(existsSync(join(root, ".claude/skills/ojm-module-dev", name)), name).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("R5 工程 global.d.ts 保模板变体:devkit 同名文件不覆盖(init/vendor 均不碰)", async () => {
|
|
76
|
+
await initProject(root, {
|
|
77
|
+
yes: true,
|
|
78
|
+
seal: async () => "ENC[test]",
|
|
79
|
+
ojInstaller: async (binDir) => {
|
|
80
|
+
makeDevkit("v1", join(binDir, "devkit"));
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
const rootGlobal = readFileSync(join(root, "global.d.ts"), "utf-8");
|
|
84
|
+
expect(rootGlobal).not.toContain("// devkit v1");
|
|
85
|
+
expect(rootGlobal).toContain(".route");
|
|
86
|
+
});
|
|
87
|
+
});
|
package/src/alias-paths.ts
CHANGED
|
@@ -20,18 +20,18 @@ import type { ProjectLayout } from "./layout";
|
|
|
20
20
|
import fs from "node:fs";
|
|
21
21
|
import path from "node:path";
|
|
22
22
|
import { parse } from "yaml";
|
|
23
|
-
import { tryResolveLayout } from "./layout";
|
|
23
|
+
import { scanModules, tryResolveLayout } from "./layout";
|
|
24
24
|
|
|
25
25
|
/** 生成物文件名(工程根)。`src/tsconfig.json` 以 `../tsconfig.paths.json` 引用 */
|
|
26
26
|
export const ALIAS_PATHS_FILE = "tsconfig.paths.json";
|
|
27
27
|
|
|
28
28
|
/** 生成物头注释:tsconfig 允许注释,且这是唯一会被 `ojm` 覆写的 tsconfig */
|
|
29
29
|
const HEADER = [
|
|
30
|
-
"// 由 ojm 生成:ojm init / ojm build / ojm dev
|
|
30
|
+
"// 由 ojm 生成:ojm init / ojm build / ojm dev 会按模块清单重写本文件,请勿手改。",
|
|
31
31
|
"//",
|
|
32
|
-
"// oj 别名 `#/<模块名>/…`
|
|
33
|
-
"// paths 只能静态全局映射,且每条规则的替换值只允许一个 `*`(`./src/*/api/*`
|
|
34
|
-
"// 以 TS5062 拒掉),故按模块逐条枚举。",
|
|
32
|
+
"// oj 别名 `#/<模块名>/…` 锚在目标模块根(src/<模块名>/api 或 src/app/api/<模块名>)。",
|
|
33
|
+
"// tsconfig 的 paths 只能静态全局映射,且每条规则的替换值只允许一个 `*`(`./src/*/api/*`",
|
|
34
|
+
"// 会被 TS 以 TS5062 拒掉),故按模块逐条枚举。",
|
|
35
35
|
].join("\n");
|
|
36
36
|
|
|
37
37
|
/** 读模块根 manifest 的 `name`(oj 的模块身份);读不到/格式坏时回落到目录名 */
|
|
@@ -58,14 +58,13 @@ export function aliasPathEntries(layout: ProjectLayout): Record<string, string[]
|
|
|
58
58
|
if (layout.kind !== "module-root")
|
|
59
59
|
return {};
|
|
60
60
|
const entries: Record<string, string[]> = {};
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
// 以扫描结果为准:既覆盖平铺 src/<name>/api,也覆盖聚合容器 src/app/api/<name>
|
|
62
|
+
for (const mod of scanModules(layout)) {
|
|
63
|
+
if (!mod.apiSrc)
|
|
63
64
|
continue;
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const name = manifestName(apiDir, mod.name);
|
|
68
|
-
entries[`#/${name}/*`] = [`./${path.posix.join("src", mod.name, "api", "*")}`];
|
|
65
|
+
const name = manifestName(mod.apiSrc, mod.name);
|
|
66
|
+
const rel = path.relative(layout.projectRoot, mod.apiSrc).split(path.sep).join("/");
|
|
67
|
+
entries[`#/${name}/*`] = [`./${rel}/*`];
|
|
69
68
|
}
|
|
70
69
|
return entries;
|
|
71
70
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// 后端构建编排(SRP 拆分自 build.ts,2026-11-03,计划 docs/plans/20261103-0930):
|
|
2
|
+
// 编排 `bin/oj build` 生成 api/dist 产物——别名投影、能力闸门、密封值试解、
|
|
3
|
+
// 宿主 versions.json 双落点都在这一段,前端构建见 module-build.ts。
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { ALIAS_PATHS_FILE, syncAliasPaths } from "../alias-paths";
|
|
8
|
+
import { resolveConfigPath, tryResolveLayout } from "../layout";
|
|
9
|
+
import { requireModuleRootCapability } from "../oj-caps";
|
|
10
|
+
import { resolveOjBin } from "../vendor";
|
|
11
|
+
import { resolveShellDist } from "../versions";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 构建后端(api/src 存在时编排 `bin/oj build`,D8)。
|
|
15
|
+
*
|
|
16
|
+
* 绝不在此处跑 migrate:oj build 本身零磁盘副作用,`ojm build` 保持零 DB
|
|
17
|
+
* 副作用(CI/无 DB 环境可跑);迁移由 preview 在起服务前应用(设计 §6)。
|
|
18
|
+
* 无后端源码返回 false(纯前端工程跳过)。
|
|
19
|
+
*/
|
|
20
|
+
export async function buildBackend(projectRoot: string): Promise<boolean> {
|
|
21
|
+
// 没有模块源码的工程(如 market 的隔离站点根)直接跳过——不能因为探测不到布局就抛
|
|
22
|
+
const layout = tryResolveLayout(projectRoot);
|
|
23
|
+
const moduleRoot = layout?.kind === "module-root";
|
|
24
|
+
const apiSrc = moduleRoot
|
|
25
|
+
? layout.modulesSrc
|
|
26
|
+
: path.join(projectRoot, "api/src");
|
|
27
|
+
if (!fs.existsSync(apiSrc))
|
|
28
|
+
return false;
|
|
29
|
+
|
|
30
|
+
const oj = resolveOjBin(projectRoot);
|
|
31
|
+
if (!fs.existsSync(oj)) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`[ojm] 找到 ${path.relative(projectRoot, apiSrc) || "."} 但缺少 ${oj}。\n`
|
|
34
|
+
+ "请重跑 ojm init 幂等补缺(不会覆盖 config 与用户代码)。",
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
// 能力闸门先于任何写入:失败时磁盘上不留半成品
|
|
38
|
+
if (moduleRoot)
|
|
39
|
+
requireModuleRootCapability(projectRoot);
|
|
40
|
+
|
|
41
|
+
// 别名投影:模块增删后要让 TS 的 paths 跟上(否则 `#/<模块名>/…` 在编辑器/typecheck
|
|
42
|
+
// 里解析不到,退化成 any)。内容未变不写盘。
|
|
43
|
+
const syncedPaths = syncAliasPaths(projectRoot);
|
|
44
|
+
if (syncedPaths === "created" || syncedPaths === "updated")
|
|
45
|
+
console.log(`[ojm] 已${syncedPaths === "created" ? "生成" : "更新"} ${ALIAS_PATHS_FILE}(\`#/<模块名>/…\` 的 TS paths 投影)`);
|
|
46
|
+
|
|
47
|
+
const apiDist = layout?.apiDist ?? path.join(projectRoot, "dist", "api");
|
|
48
|
+
console.log("[ojm] oj build(后端)…");
|
|
49
|
+
// 必须显式给 -c:oj build 的 -c 缺省是 ./config.yaml(相对 CWD),而 config 在
|
|
50
|
+
// <project>/config/ 下——漏传会静默读不到配置,连带 tasks 镜像目录回落默认 "tasks"、
|
|
51
|
+
// sql_guard 声明校验不生效(oj 只 warn 两行,极易被忽略)。
|
|
52
|
+
const configPath = resolveConfigPath(projectRoot);
|
|
53
|
+
const configArgs = fs.existsSync(configPath) ? ["-c", configPath] : [];
|
|
54
|
+
// --no-minify:oj 的 swc minify 当前会把产物压坏,必须绕开。实测两种坏法:
|
|
55
|
+
// 1) `const rows = await db.query(...); rows[0]` → 压成 `await db.query(...)[0]`,
|
|
56
|
+
// 下标先于 await 求值,Promise 上取 [0] 得 undefined(登录恒 401);
|
|
57
|
+
// 2) `if (cond) x = y` → 压成 `cond && x = y`,赋值作 && 右项缺括号 →
|
|
58
|
+
// SyntaxError: Invalid left-hand side in assignment(端点 500)。
|
|
59
|
+
// 0.1.32 / 0.1.50 均复现。产物体积换正确性,等上游修好再撤这个旗标。
|
|
60
|
+
// v0.1.85:`--module-root <dir>` 与 `-d` 互斥(都是「源码树根」)。新布局给 src,
|
|
61
|
+
// oj 在子树内递归找 manifest.yaml(命中 src/<name>/api/manifest.yaml 即停)。
|
|
62
|
+
const sourceArgs = layout?.moduleRoot ? ["--module-root", layout.moduleRoot] : ["-d", apiSrc];
|
|
63
|
+
// 出包前先自己确认 config 能读:读不动时 oj build 会静默关掉 sql_guard(见函数注释)
|
|
64
|
+
if (configArgs.length)
|
|
65
|
+
assertSealedValuesOpenable(oj, configPath);
|
|
66
|
+
execFileSync(oj, [
|
|
67
|
+
"build",
|
|
68
|
+
...configArgs,
|
|
69
|
+
...sourceArgs,
|
|
70
|
+
"-o",
|
|
71
|
+
apiDist,
|
|
72
|
+
"--no-minify",
|
|
73
|
+
], { stdio: "inherit" });
|
|
74
|
+
// 宿主 versions.json 双落点:站点壳从 shell-dist 拷进 dist/web(mergeShellSite),
|
|
75
|
+
// 而方案安装器读的是 `dist/api/versions.json`(B-11:与 manifests.yaml 同层,用于
|
|
76
|
+
// peerRuntime 强制比对)。两处必须同源,否则新工程 `ojm build` 完仍装不上方案——
|
|
77
|
+
// preflight 直接 C2-09 fail-closed「无法读取宿主 runtime 版本」。
|
|
78
|
+
const versionsSrc = path.join(resolveShellDist(), "versions.json");
|
|
79
|
+
if (fs.existsSync(versionsSrc)) {
|
|
80
|
+
fs.mkdirSync(apiDist, { recursive: true });
|
|
81
|
+
fs.copyFileSync(versionsSrc, path.join(apiDist, "versions.json"));
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* config 里的密封值(`oj secret seal` 产物)在 build 前试解一把——fail-closed。
|
|
88
|
+
*
|
|
89
|
+
* **为什么不能只信 oj 的 warn**:实测(oj 0.1.115)config 读不动时 `oj build` **退出码仍是 0**,
|
|
90
|
+
* 只往 stderr 甩两行「读配置失败 → 镜像目录回落默认 tasks / 本次 build 按 sql_guard=off 处理」,
|
|
91
|
+
* 然后照常出包。那两行夹在模块清单里极易被忽略,而关掉的 `sql_guard` 正是 tenant 声明校验
|
|
92
|
+
* (跨模块 SQL 的唯一守卫)——包看起来完好,检查其实没跑。
|
|
93
|
+
*
|
|
94
|
+
* 探法就是 oj 自己的排障命令 `oj secret open`:它和 `oj serve` / `oj build` 用同一套密钥解析
|
|
95
|
+
* (`OJ_SECRET_KEY` → `OJ_SECRET_KEY_FILE` → config 的 `secrets.private_key_path`),
|
|
96
|
+
* 解不开就非零退出。明文只可能出现在它的 stdout,这里直接丢弃,不落终端也不落日志。
|
|
97
|
+
*/
|
|
98
|
+
function assertSealedValuesOpenable(oj: string, configPath: string): void {
|
|
99
|
+
// 注释行不算:config 里留着注释掉的旧密文很常见,naive 匹配会让 build 因为一行 `#` 而失败
|
|
100
|
+
const live = fs.readFileSync(configPath, "utf-8")
|
|
101
|
+
.split("\n")
|
|
102
|
+
.filter(line => !line.trimStart().startsWith("#"))
|
|
103
|
+
.join("\n");
|
|
104
|
+
const sealed = [...new Set(live.match(/ENC\[[^\]\n]+\]/g) ?? [])];
|
|
105
|
+
for (const value of sealed) {
|
|
106
|
+
try {
|
|
107
|
+
execFileSync(oj, ["secret", "open", "-c", configPath, value], { stdio: ["ignore", "ignore", "pipe"] });
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
const stderr = String((e as { stderr?: unknown }).stderr ?? "").trim();
|
|
111
|
+
const detail = stderr || (e as Error).message;
|
|
112
|
+
throw new Error(
|
|
113
|
+
`[ojm] config 里的密封值解不开,已停在出包前:${configPath}\n`
|
|
114
|
+
+ ` oj 说:${detail}\n`
|
|
115
|
+
+ "\n"
|
|
116
|
+
+ "不能放行:oj build 遇到读不动的 config 不会失败(退出码 0),只 warn 两行就继续——\n"
|
|
117
|
+
+ "tasks 镜像目录回落默认值,sql_guard(tenant 声明校验)直接关掉。那种包看着正常,\n"
|
|
118
|
+
+ "跨模块 SQL 的守卫却没跑过。\n"
|
|
119
|
+
+ "\n"
|
|
120
|
+
+ "两类原因,都修得了:\n"
|
|
121
|
+
+ " 1) 本机加载的私钥与密封时用的公钥不是一对——最常见是换机器/换部署目录时只带了\n"
|
|
122
|
+
+ " config.yaml,没带 config/secrets-private.pem;\n"
|
|
123
|
+
+ " 2) 密文被改过——手工编辑、复制被截断、编辑器把长行折了。\n"
|
|
124
|
+
+ "\n"
|
|
125
|
+
+ "单独验一条:oj secret open -c <config> '<那个 ENC[...] 值>'(成功=这条没问题,失败=就是它)\n"
|
|
126
|
+
+ "修法:把配对私钥带到本机(OJ_SECRET_KEY / OJ_SECRET_KEY_FILE / config 的 secrets.private_key_path\n"
|
|
127
|
+
+ " 三选一);密钥确实换了,就在本机重新 oj secret seal 覆写该值(明文走 stdin,别上命令行)。",
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|