@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/devkit/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
| 侧 | 本仓位置 | 用户工程里的落点 | 分发链 |
|
|
11
11
|
|---|---|---|---|
|
|
12
12
|
| ojm 模块开发(前端模块、契约、前后端对接) | 本目录顶层 9 个文件 | `.claude/skills/ojm-module-dev/` | `@oj-module/cli` 包 → `ojm init` 第 4b 步 |
|
|
13
|
-
| oj API 开发(后端 handler、全局对象、配置、测试) | `oj-api-dev/` 子目录(快照) | `.claude/skills/oj-api-dev/` | **oj 二进制自带**(`bin/devkit/`)→ `ojm init` 第 4 步 |
|
|
13
|
+
| oj API 开发(后端 handler、全局对象、配置、测试) | `oj-api-dev/` 子目录(快照) | `.claude/skills/oj-api-dev/` | **oj 二进制自带**(`bin/devkit/`)→ `ojm init` 第 4 步 / `ojm vendor` 装·升后同步(0.2.57 起共用 `syncOjDevkit`,全量 `.md` **覆盖**——文档始终与已装 oj 版本一致) |
|
|
14
14
|
|
|
15
15
|
> **`oj-api-dev/` 子目录是快照,不是本仓维护的文档**:它与本机 oj 的
|
|
16
16
|
> `~/.local/share/oj/devkit/`(oj release 下载)逐字节一致,改它没有意义——
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
|---|---|
|
|
26
26
|
| `SKILL.md` | Claude Code 等 agent 的 skill 入口:工作流、红线、新模块 checklist、陷阱速查,按章节号引用手册 |
|
|
27
27
|
| `manual.md` | 模块开发手册(14 章):工程结构(module-root 单树 + 两处产物根;含 §1.7 handler 四红线——oj 运行时实测钉出,后端必读;§1.8 `#` 别名与 TS 投影)、写模块、契约与代码生成(含 §3.10 旧 contract.ts 工程迁移指南)、provider 接管(覆盖框架内置功能)、布局、i18n、构建发布、排查、CLI 子命令参考(ojm 原生命令 vs oj 透传命令)、方案包打包与分发(含 §11.1.1 引用式方案)、签名与信任、§13.1.1 web.route_prefix 自定义根路径、**§14 组织架构与通讯录**(内置部门/成员底座 + 共享读面 7 端点 + `scope` 两级门禁 + 归属权四条规则 + 源适配器接点,选人与按部门算人都读它) |
|
|
28
|
+
| `publish-walkthrough.md` | 方案包发布全流程(**新手照做版**):构建打包 → keygen 签名 → `market publish` 落 CDN → build-index → verify/trial 验证 → install + TOFU,含多人各自独立发包纪律与失败速查(手册 §11/§12 的过程版) |
|
|
28
29
|
| `framework-dev.md` | 框架贡献者路线(**仅框架仓库**):改 `packages/runtime`、`packages/cli` 时的入口与红线 |
|
|
29
30
|
| `module-dev-and-cdn-playground.md` | 教学文档:开发态 ≠ 分发态、「怎么安装自己的模块 / 会不会重复」、本地 CDN 市场链路五步跑通(setup → publish → build-index → verify → 安装) |
|
|
30
31
|
| `sql-builder-missing-capabilities-pr.md` | 构造器能力缺口 PR 参考(**仅框架仓库**):7 项诉求的闭环记录 |
|
|
@@ -52,6 +53,8 @@
|
|
|
52
53
|
| `api-manual.md` | 后端开发手册(13 章):模块约定、全局对象 API、鉴权租户、测试、配置、构建发布 |
|
|
53
54
|
| `scenarios.md` | 场景速查(照抄就能跑) |
|
|
54
55
|
| `sql-builder-guide.md` | SQL 构造器专题指引(v0.1.82) |
|
|
56
|
+
| `db-guide.md` | db 新人手册:配库 → 建表 → 增删改查 → 事务的上手路径与安全边界(逐 API 穷举仍以 api-manual 为准) |
|
|
57
|
+
| `deploy-tunnel-recipe.md` | 部署级隧道 recipe:跳板机后的库在部署层打通的配方(平台不做进程内 SSH 隧道) |
|
|
55
58
|
| `oj-test-guide.md` | `oj test` L1 测试专题指引 |
|
|
56
59
|
| `global.d.ts` / `oj-modules.d.ts` | handler 全局对象 TS 类型 + `#` 导入别名 ambient 兜底 |
|
|
57
60
|
| `oidc-implementation.md` / `oidc-integration.md` | OIDC 演示走读与集成说明 |
|
|
@@ -92,8 +95,8 @@ cp ~/.local/share/oj/devkit/SKILL.md ~/.local/share/oj/devkit/api-manual.md .cla
|
|
|
92
95
|
`api-manual.md` + 有则拷的 `sql-builder-guide.md` / `oj-test-guide.md` +
|
|
93
96
|
`global.d.ts` → `.claude/skills/oj-api-dev/`;
|
|
94
97
|
- **第 4b 步**:从 cli 包内置 `devkit/`(prepack 由本目录顶层同步)拷
|
|
95
|
-
`SKILL.md` / `manual.md` / `README.md` / `wecom-daikaifa.md` / `wecom-internal-app.md`
|
|
96
|
-
→ `.claude/skills/ojm-module-dev/`。
|
|
98
|
+
`SKILL.md` / `manual.md` / `README.md` / `wecom-daikaifa.md` / `wecom-internal-app.md` /
|
|
99
|
+
`publish-walkthrough.md` → `.claude/skills/ojm-module-dev/`。
|
|
97
100
|
|
|
98
101
|
```mermaid
|
|
99
102
|
flowchart LR
|
|
@@ -110,7 +113,7 @@ flowchart LR
|
|
|
110
113
|
`.claude/skills/ojm-module-dev/` 再 init)。源文件只在仓库 `docs/devkit/` 顶层一处维护。
|
|
111
114
|
- **oj 侧**:手册与 skill 随 oj 版本发布;升级 oj 后用新 `bin/devkit/` 覆盖旧拷贝。
|
|
112
115
|
框架贡献者要把本仓快照同步到新版:从 `~/.local/share/oj/devkit/` 整目录覆盖
|
|
113
|
-
`docs/devkit/oj-api-dev/`(
|
|
116
|
+
`docs/devkit/oj-api-dev/`(oj v0.1.116 起 12 个文件,保持逐字节一致以便对账)。
|
|
114
117
|
|
|
115
118
|
> 本目录顶层 9 个文件是**自包含**的:业务工程拿到即可读懂,不需要工程仓库里的
|
|
116
119
|
> 其它文档。`oj-api-dev/` 子目录是 oj 侧快照,同样自包含(其自身 README 即索引),
|
package/devkit/SKILL.md
CHANGED
|
@@ -18,7 +18,8 @@ description: 用 ojm 开发前后端一体工程时使用——新增/修改前
|
|
|
18
18
|
1. **读章**:建模块 → 手册 §2;加接口(handler .schema → oj openapi → 生成 → 对接)→ §3;
|
|
19
19
|
接管通知/上传/菜单 → §4;换布局或加插槽 → §5;排查 → §8;
|
|
20
20
|
**组织架构 / 通讯录(选人、按部门算收件人、接 LDAP 等新来源)→ §14**;
|
|
21
|
-
**接通企业微信(代开发 / 企业内部应用 / 两套共存)→ `wecom-daikaifa.md` + `wecom-internal-app.md
|
|
21
|
+
**接通企业微信(代开发 / 企业内部应用 / 两套共存)→ `wecom-daikaifa.md` + `wecom-internal-app.md`**;
|
|
22
|
+
**把方案包发出去(打包/签名/CDN/安装一条链,新手照敲)→ `publish-walkthrough.md`**。
|
|
22
23
|
2. **建目录**:一个目录 = 一个模块——前端一侧 `src/<模块名>/web/`(`entry.ts` 必备,
|
|
23
24
|
`pages/`、`locales/` 按需),后端一侧 `src/<模块名>/api/`(目录镜像路由的 `api.ts`,
|
|
24
25
|
动词挂 `.schema`)。两侧可只存在一侧。
|
|
@@ -103,7 +104,7 @@ description: 用 ojm 开发前后端一体工程时使用——新增/修改前
|
|
|
103
104
|
|
|
104
105
|
## 企业微信(接通真实套件 / 自建应用)
|
|
105
106
|
|
|
106
|
-
模板已内置企微登录(`src/
|
|
107
|
+
模板已内置企微登录(`src/app/api/auth/wecom/` + 登录页「企业微信」页签 + `/system/wecom` 后台)。
|
|
107
108
|
两种来源、两本手册,**按客户形态选**:
|
|
108
109
|
|
|
109
110
|
| 客户形态 | 读 | 凭据 |
|
|
@@ -132,7 +133,7 @@ description: 用 ojm 开发前后端一体工程时使用——新增/修改前
|
|
|
132
133
|
- **整页跳转拼 API base**:`window.location.assign(resolveApiBase() + wecomAuthorizePath(corp))`;
|
|
133
134
|
拼站点 base 会被 SPA 兜底页吞掉(页面原地重载、一个请求都不发,控制台无报错)。
|
|
134
135
|
- **通讯录同步是「组织架构底座 + 企微适配器」,不是企微专属功能**:模板的
|
|
135
|
-
`src/
|
|
136
|
+
`src/app/api/auth/wecom/{sync,contacts}.ts` 只负责「取快照 → 喂给领域层」,模型与读面在 `_platform`
|
|
136
137
|
(手册 §14)。要按部门算人、要选人、要给组织架构接 LDAP,一律读 §14,不要在企微代码里加分支。
|
|
137
138
|
同步/事件增量与回调解密是**两条独立纪律**:回调必须回 `success`,而事件处理失败**也照样回 `success`**
|
|
138
139
|
(重推补不回已判死的失败,兜底是下一轮全量同步)。
|
|
@@ -143,6 +144,8 @@ description: 用 ojm 开发前后端一体工程时使用——新增/修改前
|
|
|
143
144
|
- [ ] `web.config.ts` 的 `modules` 里已登记(`enabled` 缺省即启用;module-root 下扫描已能捡到,登记用于排序/覆盖)
|
|
144
145
|
- [ ] 路由 `handle.layout` 选对了(有页面用 `"container"`;无页面模块 `routes: []`)
|
|
145
146
|
- [ ] 菜单标题用命名空间语法 `"<模块名>:menu.<key>"`,且 `locales/<lang>.json` 里有这个 key
|
|
147
|
+
- [ ] manifest 声明了 `roles[]` → 每个 `title_key` 用 `"<模块名>:role.<slug>"` 形状(ns 必须 = 模块名),
|
|
148
|
+
且双语 locale 里都有对应嵌套 key `{"role":{…}}`——安装时解析成 `sys_role.title`,缺失则存裸 key(不失败)
|
|
146
149
|
- [ ] 有接口 → handler 动词挂了 `.schema`(入参)+ 平级 `.response`(出参,oj v0.1.72+)→ `oj openapi` + `ojm api` 跑过 → onInit 里 `create<Module>Client(ctx)`
|
|
147
150
|
- [ ] 接管了某功能 → provider 方法数与类型定义**一个不差**
|
|
148
151
|
- [ ] 有后端 → handler 四红线过一遍(manual §1.7:respond 收口 / import 深度 / 无模块级状态 / 无模块级运行时全局)
|
|
@@ -187,7 +190,7 @@ description: 用 ojm 开发前后端一体工程时使用——新增/修改前
|
|
|
187
190
|
| 登录 401 且 msg 不是 `invalid credentials` | `/auth/*` 不在 `anonymous_paths` | 加进工程根 `config.yaml`,重启 |
|
|
188
191
|
| WS 连接 401(「文件在却连不上」) | 模块写了 `ws.ts`,但其 `/ws` 路由不在 `anonymous_paths`(oj v0.1.30+ WS 握手也过鉴权) | 匿名 WS 加进 `anonymous_paths`;受保护 WS 带 Bearer/Cookie 是正当用法。`ojm api --check` 会对此 warn |
|
|
189
192
|
| 登出/回登录页落空,或登录后落错误边界 | 工程缺 `login` / `home` 模块 | 在 `web.config.ts` 保留两者 |
|
|
190
|
-
| 通知铃 404 `no route matched`(`/api/notifications`) | 前端 `notification` 模块没加载 / 没注册 `notificationsApi`,回落到内置只读兜底;脚手架不再提供 root 级 `/api/notifications` | 保留 `src/app/web/notification/entry.ts` 的登记 + onInit 注册四方法;后端由 `src/
|
|
193
|
+
| 通知铃 404 `no route matched`(`/api/notifications`) | 前端 `notification` 模块没加载 / 没注册 `notificationsApi`,回落到内置只读兜底;脚手架不再提供 root 级 `/api/notifications` | 保留 `src/app/web/notification/entry.ts` 的登记 + onInit 注册四方法;后端由 `src/app/api/notification` 出 `/api/notification/*` |
|
|
191
194
|
| `oj serve` 启动即退,报 `config uses removed server.* site keys (v0.1.58): api_prefix` | 用了 oj v0.1.58 起删除的旧站点键(`api_prefix`/`app_path`/`app_prefix`/`static_sites`/`app_spa_fallback`) | 改 `server.api_prefix: /api` 为顶层 `mounts:`(module-root 写 `{prefix: "/api", api: "api/dist"}`;历史布局写 `api: "src"`、源码交给 `-d`);迁移完整 YAML 由报错给出 |
|
|
192
195
|
| 改完 `mounts:` 仍全 404 | `api:` 指向目录不对(相对 config 所在目录) | module-root 指向产物目录 `"api/dist"`、历史布局指向源码树 `"src"`;`-c` 指向工程根 `config.yaml`(ojm dev/preview 已内置)。module-root 模板**故意不写 api 挂载**(源码交给 `--module-root src`、产物交给 `--api-path api/dist`,同时出现会报 module declared in multiple places) |
|
|
193
196
|
| `oj serve` 启动即退,报 `module '_shared' missing manifest.yaml` | 把共享后端代码放到了扫描根顶层(`src/_shared/`)——oj 把扫描根下的顶层目录都当模块;历史布局是 `api/src/_shared/` | 嵌进所属模块:`src/<模块>/api/_shared/`(历史布局 `api/src/<模块>/_shared/`),用 `#<本模块>/...` 别名 |
|
package/devkit/manual.md
CHANGED
|
@@ -42,22 +42,25 @@
|
|
|
42
42
|
│ ├── tsconfig.json # 后端 typecheck 工程(extends 上两者,exclude */web)
|
|
43
43
|
│ ├── .ojm-builtin.json # 框架自带方案名单(init 派生,可手改/删除;打包排除 + info 折叠,见 §11)
|
|
44
44
|
│ ├── styles.css
|
|
45
|
-
│ ├──
|
|
46
|
-
│ │ ├── api/ #
|
|
47
|
-
│ │
|
|
48
|
-
│ ├──
|
|
49
|
-
│ │ ├──
|
|
50
|
-
│ │ ├──
|
|
51
|
-
│ │ ├──
|
|
52
|
-
│ │
|
|
53
|
-
│ │
|
|
54
|
-
│ │
|
|
45
|
+
│ ├── app/ # 整个应用(后端模块 + 前端聚合模块)
|
|
46
|
+
│ │ ├── api/ # 后端模块(模块名 = URL 前缀,各自仍是独立 oj 模块)
|
|
47
|
+
│ │ │ ├── _platform/ # users 表 / rbac / 市场编排 / 生命周期 + _shared/
|
|
48
|
+
│ │ │ ├── auth/ # login / refresh / logout / ldap-login
|
|
49
|
+
│ │ │ ├── system/ # 用户 / 角色 / 权限 / 设置 / oplog / login-methods
|
|
50
|
+
│ │ │ ├── notification/ # 通知互动(表 + push + del)
|
|
51
|
+
│ │ │ ├── personal-center/ # 头像上传
|
|
52
|
+
│ │ │ └── web/ # root 级 /api/web(hello / user-info / get-async-routes)
|
|
53
|
+
│ │ └── web/ # 前端**多根聚合模块**(一个 entry 多个根)
|
|
54
|
+
│ │ ├── entry.ts # routes 聚合 + i18nNamespaces + 生命周期
|
|
55
|
+
│ │ ├── home/ # /home(routes.ts + pages + locales + __client)
|
|
56
|
+
│ │ ├── login/ # /login、/change-password
|
|
57
|
+
│ │ ├── personal-center/ # /personal-center/my-profile
|
|
58
|
+
│ │ ├── notification/ # /notification-center
|
|
59
|
+
│ │ ├── system/ # /system/*(根上 handle.roles = 系统管理员)
|
|
60
|
+
│ │ ├── auth/ # 仅 __client(无页面)
|
|
61
|
+
│ │ ├── _platform/ # 仅 __client(oj-market 引用)
|
|
62
|
+
│ │ └── web/ # 仅 __client
|
|
55
63
|
│ ├── oj-market/web/ # 解决方案市场(独立模块,删目录即下线)
|
|
56
|
-
│ ├── auth/api/ # login / refresh / logout / ldap-login
|
|
57
|
-
│ ├── system/api/ # 用户 / 角色 / 权限 / 设置 / oplog / login-methods(后端)
|
|
58
|
-
│ ├── notification/api/ # 通知互动(表 + push + del)
|
|
59
|
-
│ ├── personal-center/api/ # 头像上传
|
|
60
|
-
│ └── web/{api,web}/ # root 级 /api/web(hello / user-info / get-async-routes 在 api 侧)
|
|
61
64
|
├── dist/api/ # 后端产物(release 根:<name>-<ver>/ + manifests.yaml + versions.json)—构建后才有
|
|
62
65
|
├── dist/web/ # 站点产物(shell + modules/ + modules.json)—构建后才有
|
|
63
66
|
├── bin/ # oj 二进制 + plugins/ + devkit/(不入库)
|
|
@@ -72,10 +75,13 @@
|
|
|
72
75
|
`resolveCertDir()` 单点派生,你不需要自己拼路径)。
|
|
73
76
|
- **产物有两个根**:后端 `dist/api`(oj 扁平 release,`<name>-<ver>/` 与
|
|
74
77
|
`manifests.yaml` 同层),站点 `dist/web`(`modules/` + `modules.json`)。
|
|
75
|
-
- **`src/`
|
|
76
|
-
|
|
78
|
+
- **`src/` 下的顶层目录必须是一个模块**(`api/manifest.yaml` 或 `web/entry.ts`):
|
|
79
|
+
脚本 / 共享库请放在 `src/` 之外。应用容器 `src/app` 是唯一例外——它的 `api/<name>/`
|
|
80
|
+
子目录各自是独立模块(模块名 = 子目录名 = URL 前缀,oj 的 `--module-root` 递归发现)。
|
|
81
|
+
- **前端 client 入口是 `__client/index.ts`**(0.2.54 起,旧名 `api.ts` 会被 oj dev
|
|
82
|
+
当路由扫描)。页面导入写 `from "../__client"`。
|
|
77
83
|
|
|
78
|
-
> 模板里还有一批 demo-only 模块(`src/
|
|
84
|
+
> 模板里还有一批 demo-only 模块(`src/app/api/home`、`src/{about,access,exception,
|
|
79
85
|
> outside,route-nest}/web`)仅供模板自演示,**不进脚手架**;其 `web.config.ts` 条目带
|
|
80
86
|
> `// #region demo-only` 标记,init 拷贝时整段剥离。新布局的清单是「扫描 + 覆盖」并集,
|
|
81
87
|
> 注释掉不会让模块消失——要「不构建」必须写 `enabled: false`。
|
|
@@ -100,7 +106,7 @@
|
|
|
100
106
|
> **更早建好的 dev 库里仍是旧口令**——拿 `aA123456` 登不上时先怀疑这个,别去查登录链路。
|
|
101
107
|
> 要统一:删 `db.sqlite` 重跑 `oj migrate`(seed 随服务启动重放),或直接
|
|
102
108
|
> `UPDATE users SET password_hash = '<aA123456 的哈希>' WHERE username IN ('superadmin','admin','editor','viewer');`。
|
|
103
|
-
> 哈希现成值从 `src/
|
|
109
|
+
> 哈希现成值从 `src/app/api/_platform/seed.sql` 里 grep `$2y$10$` 即可。
|
|
104
110
|
|
|
105
111
|
端口:前端 devServer **5174**,oj 后端 **9778**
|
|
106
112
|
(以工程根 `config.yaml` 的 `server.port` 为准)。
|
|
@@ -154,9 +160,9 @@
|
|
|
154
160
|
src/<模块>/api/<路径...>/api.ts → /api/<模块>/<路径...>
|
|
155
161
|
```
|
|
156
162
|
|
|
157
|
-
`src/
|
|
163
|
+
`src/app/api/web/hello/api.ts` → `GET|POST /api/web/hello`。
|
|
158
164
|
|
|
159
|
-
注意中间的 `api` 是**布局分隔符,不是路由段**(`src/
|
|
165
|
+
注意中间的 `api` 是**布局分隔符,不是路由段**(`src/app/api/web/hello` ↔ `/api/web/hello`,
|
|
160
166
|
不是 `/api/web/api/hello`)——`ojm` 的契约扫描按「模块的 api 侧目录」逐模块进行,
|
|
161
167
|
不会把 `src` 整棵当扫描根(历史布局同理:`api/src/web/hello/api.ts`)。
|
|
162
168
|
|
|
@@ -217,13 +223,14 @@ src/<模块>/api/<路径...>/api.ts → /api/<模块>/<路径...>
|
|
|
217
223
|
|
|
218
224
|
| 文件位置 | 到 `_platform/_shared` 的写法 |
|
|
219
225
|
|---|---|
|
|
220
|
-
| `src/
|
|
221
|
-
| `src/
|
|
222
|
-
| `src/
|
|
223
|
-
| `src/
|
|
224
|
-
| `src/
|
|
225
|
-
|
|
226
|
-
> 历史布局把 `src/<m>/api` 换成 `api/src/<m>`
|
|
226
|
+
| `src/app/api/system/<x>/api.ts` | `../../_platform/_shared/...` |
|
|
227
|
+
| `src/app/api/system/<x>/<y>/api.ts` | `../../../_platform/_shared/...` |
|
|
228
|
+
| `src/app/api/auth/<z>/api.ts` | `../../_platform/_shared/...` |
|
|
229
|
+
| `src/app/api/_platform/<x>/api.ts` | `../_shared/...` |
|
|
230
|
+
| `src/app/api/_platform/<x>/<y>/api.ts` | `../../_shared/...` |
|
|
231
|
+
|
|
232
|
+
> 历史布局把 `src/<m>/api` 换成 `api/src/<m>` 后同构,深度不变;内置模块收进
|
|
233
|
+
> `src/app/api/<m>` 后比原 `src/<m>/api` **少一层**(上表已按新布局校准)。
|
|
227
234
|
3. **模块级可变状态不跨请求存活。** oj **每请求重建 handler 模块实例**(实测:模块级
|
|
228
235
|
计数器连打三次恒为初值)——`let` / `Map` / `Set` 级状态一律视为**单请求作用域**。
|
|
229
236
|
需要跨请求的状态(限速、节流、去重窗口、判权缓存)**必须落库**或走 oj 持久面。
|
|
@@ -287,7 +294,7 @@ src/<模块名>/web/
|
|
|
287
294
|
> 就是忘了登记,结果模块安静地没加载、按钮安静地灰着,**全程没有一个报错**。
|
|
288
295
|
|
|
289
296
|
**建档顺序很重要**(有接口的模块):先写 handler 的 `.schema` → 跑 `oj openapi` + `ojm api` → 再写 `entry.ts`。
|
|
290
|
-
生成前 `import { createXxxClient } from "./__client
|
|
297
|
+
生成前 `import { createXxxClient } from "./__client"` 必然 ts 报红,
|
|
291
298
|
那不是你写错了,是产物还没生成。完整流水线见 §3.4。
|
|
292
299
|
|
|
293
300
|
```ts
|
|
@@ -316,7 +323,7 @@ import { BookOutlined } from "@ant-design/icons";
|
|
|
316
323
|
import { defineModule } from "@oj-module/runtime";
|
|
317
324
|
import { createElement, lazy } from "react";
|
|
318
325
|
|
|
319
|
-
import { createBooksClient } from "./__client
|
|
326
|
+
import { createBooksClient } from "./__client";
|
|
320
327
|
|
|
321
328
|
const Books = lazy(() => import("./pages/index"));
|
|
322
329
|
|
|
@@ -483,7 +490,7 @@ const roles = useUserStore(s => s.roles);
|
|
|
483
490
|
|
|
484
491
|
**写(改密/改角色/改设置)→ 走契约 API client,不碰框架内部**
|
|
485
492
|
|
|
486
|
-
- 框架已把 `auth/*`、`system/*`、`web/*` 等端点经 OpenAPI 契约定义,并从 `@oj-module/runtime` 导出类型化 fetch(如 `api/rbac` 的 `fetchChangePassword`、`fetchRbacUpdateUserRoles`,`api/user` 的 `fetchUserInfo`)。要调框架内置写接口,直接复用这些导出。**但更推荐模块 own 自己的契约**:在**自己模块的 handler 动词**上写 `.schema`,跑 `oj openapi` + `ojm api` 生成 `src/<模块>/web/__client/
|
|
493
|
+
- 框架已把 `auth/*`、`system/*`、`web/*` 等端点经 OpenAPI 契约定义,并从 `@oj-module/runtime` 导出类型化 fetch(如 `api/rbac` 的 `fetchChangePassword`、`fetchRbacUpdateUserRoles`,`api/user` 的 `fetchUserInfo`)。要调框架内置写接口,直接复用这些导出。**但更推荐模块 own 自己的契约**:在**自己模块的 handler 动词**上写 `.schema`,跑 `oj openapi` + `ojm api` 生成 `src/<模块>/web/__client/index.ts`,模块经自己的 client 调框架端点——接口由契约(versioned OpenAPI)对齐,而非把框架源文件打进来。
|
|
487
494
|
- 底层都走同一个 `request` 单例 + 契约 URL;模块侧请求被 `defineModule` 的 `ctx` 收敛到登记过的 `apiPrefix`,越界 / `../` 穿越 / 逐请求 prefix 覆盖均被拒(请求边界见 §2.6 + `tests/runtime/scoped-request.test.ts`)。
|
|
488
495
|
|
|
489
496
|
**场景:模块记录自己的操作日志(写端)**
|
|
@@ -537,7 +544,7 @@ oj openapi -c config.yaml --module-root src -b /api -o openapi.json → OpenA
|
|
|
537
544
|
ojm api → irFromOpenapi → emitZodFromJsonSchema / emitClient
|
|
538
545
|
│
|
|
539
546
|
▼
|
|
540
|
-
src/<模块>/web/__client/{
|
|
547
|
+
src/<模块>/web/__client/{index.ts, api.schemas.ts}(+ stub / mock)
|
|
541
548
|
```
|
|
542
549
|
|
|
543
550
|
`openapi.json` 即权威文档源,`ojm api --docs` 直接聚合它渲染文档站。`.schema` / `.response`
|
|
@@ -556,7 +563,7 @@ src/<模块>/web/__client/{api.ts, api.schemas.ts}(+ stub / mock)
|
|
|
556
563
|
|
|
557
564
|
| 工程形态 | 权威源 | 产物流向 |
|
|
558
565
|
|---|---|---|
|
|
559
|
-
| **module-root**(`ojm init` 产物,推荐) | 后端 `src/<模块>/api/**/api.ts` 上的 `.schema` | `src/<模块>/web/__client/{
|
|
566
|
+
| **module-root**(`ojm init` 产物,推荐) | 后端 `src/<模块>/api/**/api.ts` 上的 `.schema` | `src/<模块>/web/__client/{index.ts, api.schemas.ts}`(由 `ojm api` 消费 `openapi.json` 生成);stub → `src/<模块>/api/<路径>/api.ts`(未实现 handler 时补骨架) |
|
|
560
567
|
| **uni-dev**(历史布局) | 后端 `api/src/<模块>/**/api.ts` 上的 `.schema` | `web/src/<模块>/__client/…`;stub → `api/src/<模块>/<路径>/api.ts` |
|
|
561
568
|
| 纯前端 | 仅消费他人 `openapi.json` | `__client/` 全部落前端侧,无 stub |
|
|
562
569
|
|
|
@@ -569,7 +576,7 @@ src/<模块>/web/__client/{api.ts, api.schemas.ts}(+ stub / mock)
|
|
|
569
576
|
以目录名作 `MODULE_PREFIX`(跨模块认领护栏的对比基准),只改一边会让 dev 控制台报「正在认领
|
|
570
577
|
别人的 client」。与旧 `apiPrefix` 字面相等约束同源,只是不再手写字段。
|
|
571
578
|
|
|
572
|
-
> 工程里有两个 "web":前端侧 `src/web/web/`,与后端 `src/
|
|
579
|
+
> 工程里有两个 "web":前端侧 `src/app/web/web/`,与后端 `src/app/api/web/`(提供 user-info 等内置接口)。
|
|
573
580
|
> 豁免清单里的 `"modules": ["web"]` 指的是**后者**。
|
|
574
581
|
|
|
575
582
|
### 3.3 handler `.schema`(入参)+ `.response`(出参)写法(JSON Schema 子集)
|
|
@@ -636,7 +643,7 @@ minLength / maxLength / pattern / enum / nullable / minItems / maxItems / defaul
|
|
|
636
643
|
**上传文件(multipart/form-data)**:当前 `ojm api` 的 openapi 流水线**不生成** form 通道的
|
|
637
644
|
前端 client(文件部件是二进制,不进 JSON Schema)。惯例走 antd `Upload` **直连**后端
|
|
638
645
|
(不经 JSON client),handler 用 `http.files` / `http.file(i)` 读字节,仅平级声明
|
|
639
|
-
`.response`(见 `src/personal-center/
|
|
646
|
+
`.response`(见 `src/app/api/personal-center/upload/api.ts` 范本)。
|
|
640
647
|
|
|
641
648
|
**不要手写 `content-type`**:运行时(ky)见到 `FormData` 会自己补 `multipart/form-data;
|
|
642
649
|
boundary=…`,手写反而丢 boundary。
|
|
@@ -646,7 +653,7 @@ boundary=…`,手写反而丢 boundary。
|
|
|
646
653
|
|
|
647
654
|
### 3.4 生成物使用契约
|
|
648
655
|
|
|
649
|
-
生成的 `src/<模块>/web/__client/
|
|
656
|
+
生成的 `src/<模块>/web/__client/index.ts` 导出:
|
|
650
657
|
|
|
651
658
|
- 裸请求函数(`fetchNotifications`、`markRead`…)——**直接 import 调用**;
|
|
652
659
|
- `API_PREFIX` 常量——契约前缀的唯一真源;
|
|
@@ -672,7 +679,7 @@ const booksClient = createBooksClient(ctx);
|
|
|
672
679
|
> 目录却不在」只有一种解释:那个模块的 `__client/` 从没生成过(模板里的
|
|
673
680
|
> personal-center 上传走 antd Upload 直连,不经过 JSON client,故没人 import 它的 `__client`)。
|
|
674
681
|
> 你在自己工程跑一次 `ojm api`,它就会出现。
|
|
675
|
-
> 判断某模块**要不要用** client 的标准只有一个:代码里有没有人 import `./__client
|
|
682
|
+
> 判断某模块**要不要用** client 的标准只有一个:代码里有没有人 import `./__client`。
|
|
676
683
|
> 有就在 onInit 里 `create<Module>Client(ctx)` 一次;没有就什么都不写,
|
|
677
684
|
> 生成了但没人 import 也不报错。
|
|
678
685
|
|
|
@@ -683,7 +690,7 @@ const booksClient = createBooksClient(ctx);
|
|
|
683
690
|
|
|
684
691
|
### 3.5 生成物勿手改
|
|
685
692
|
|
|
686
|
-
`__client/
|
|
693
|
+
`__client/index.ts`、`__client/api.schemas.ts` **重跑即覆盖**(eslint 已忽略这些路径)。
|
|
687
694
|
改需求 = 改 handler 的 `.schema`(或路由),重跑 `oj openapi` + `ojm api`。
|
|
688
695
|
|
|
689
696
|
**唯一例外:stub。** `src/<模块>/api/**/api.ts` 的 stub 文件头带指纹
|
|
@@ -832,7 +839,7 @@ export default { get };
|
|
|
832
839
|
3. **生成规范**:`oj openapi -c config.yaml --module-root src -b /api -o openapi.json`
|
|
833
840
|
(历史布局用 `-d api/src`;项目级单文件、含全部模块;组级 `.schema`/`.response` 已由
|
|
834
841
|
oj 内省期合并)。
|
|
835
|
-
4. **生成 client**:`ojm api` → `src/<模块>/web/__client/{
|
|
842
|
+
4. **生成 client**:`ojm api` → `src/<模块>/web/__client/{index.ts, api.schemas.ts}`
|
|
836
843
|
(历史布局 `web/src/<模块>/__client/`)。
|
|
837
844
|
5. **改前端消费点**:函数名从旧人类命名(`fetchNotifications`)改为
|
|
838
845
|
`camelCase(operationId)`(`notificationNotificationsGet`)——用「逐字节比对」识别,
|
|
@@ -904,9 +911,9 @@ Provider 就是:「这块别用框架自带的了,换成我的实现。」
|
|
|
904
911
|
|
|
905
912
|
### 4.4 案例:接管通知功能(标准四文件)
|
|
906
913
|
|
|
907
|
-
**① handler `.schema`**(`src/
|
|
914
|
+
**① handler `.schema`**(`src/app/api/notification/api.ts` 上各动词挂的 `.schema`)——见 §3.3 与 §3.4。
|
|
908
915
|
|
|
909
|
-
**② handler**(`src/
|
|
916
|
+
**② handler**(`src/app/api/notification/api.ts`,模块根嵌套组——组名 = 路径段,oj v0.1.64):
|
|
910
917
|
|
|
911
918
|
```ts
|
|
912
919
|
const notifications = {
|
|
@@ -939,7 +946,7 @@ export default { notifications, /* push, "push-broadcast" 等同理 */ };
|
|
|
939
946
|
```ts
|
|
940
947
|
import type { NotificationsApiProvider } from "@oj-module/runtime";
|
|
941
948
|
import { defineModule } from "@oj-module/runtime";
|
|
942
|
-
import { createNotificationClient } from "./__client
|
|
949
|
+
import { createNotificationClient } from "./__client";
|
|
943
950
|
|
|
944
951
|
export default defineModule({
|
|
945
952
|
name: "notification",
|
|
@@ -964,7 +971,7 @@ export default defineModule({
|
|
|
964
971
|
**⑤ 跨模块发射原语(任何后端模块都能推通知)**
|
|
965
972
|
|
|
966
973
|
通知不是「只有通知模块自己用」的功能——下单、审核、@我等其它模块也需要给某个人推一条提醒。
|
|
967
|
-
把发射逻辑收敛成 `src/
|
|
974
|
+
把发射逻辑收敛成 `src/app/api/notification/_shared/notify.ts` 里的纯函数原语,其它模块**别名导入
|
|
968
975
|
直接调用,无需走 HTTP**:
|
|
969
976
|
|
|
970
977
|
```ts
|
|
@@ -997,7 +1004,7 @@ export default {
|
|
|
997
1004
|
不带 `node_modules`——dev 能跑、release 或装到站点后必炸。后端共享代码放本模块
|
|
998
1005
|
`api/_shared/`,跨模块走 `#/<模块名>/_shared/...` 别名;类型与 runtime 的
|
|
999
1006
|
`@oj-module/runtime/notify` 保持同形即可(共享契约类型单一真源在 runtime)。
|
|
1000
|
-
- **`_shared` 必须嵌进所属模块的 api 侧**(`src/
|
|
1007
|
+
- **`_shared` 必须嵌进所属模块的 api 侧**(`src/app/api/notification/_shared/`):
|
|
1001
1008
|
`src/` 下每个目录都得是模块(`api/manifest.yaml` 或 `web/entry.ts`),`src/_shared` 会被
|
|
1002
1009
|
`ojm dev/build` 判成坏目录;放进模块内则是「无 `api.ts` 的纯工具目录」,不产生路由。
|
|
1003
1010
|
(历史布局同理:`api/src/notification/_shared/`,而 `api/src/_shared` 会被 oj 当成缺
|
|
@@ -1439,6 +1446,7 @@ pnpm dev # 开发:devServer + oj 后端(src/<模块>/api 保
|
|
|
1439
1446
|
# Ctrl+C(SIGINT)/ kill(SIGTERM)终止 dev 时,前端与后端 oj 一起终止:
|
|
1440
1447
|
# 先关 SSE 通道 → 停 oj 子进程 → 打印「后端 oj 已随前端一起终止」,不留 oj 孤儿占端口
|
|
1441
1448
|
pnpm build # = ojm build:oj build(生成 routes.js)+ 前端全站合并到 web/dist
|
|
1449
|
+
# 末尾打印用户方案摘要:哪些方案已构建、可 --bundle 打包;没进构建的方案点名(查登记/enabled)
|
|
1442
1450
|
pnpm preview # = ojm preview:oj migrate(verify 闸)→ oj serve 单服务(API + 静态 SPA 同进程)
|
|
1443
1451
|
pnpm typecheck # = tsc --noEmit(前端工程)
|
|
1444
1452
|
pnpm typecheck:api # = tsc --noEmit -p src/tsconfig.json(后端工程,见 §6.5)
|
|
@@ -1563,7 +1571,7 @@ export default async (path: string, html: string) => {
|
|
|
1563
1571
|
| 登录 401 且 msg 不是 `invalid credentials` | `/auth/*` 不在 `anonymous_paths` | 加进工程根 `config.yaml`,重启 |
|
|
1564
1572
|
| 登出/回登录页空白或 404 | 工程缺 `login` 模块(shell 无内置登录回落) | 在 `web.config.ts` 保留 `login` |
|
|
1565
1573
|
| 登录后 / 点 logo 落 React Router 错误边界 | 工程缺 `home` 模块 | 在 `web.config.ts` 保留 `home`(尽量靠前) |
|
|
1566
|
-
| 通知铃 404 `no route matched`(`/api/notifications`) | 前端 `notification` 模块没加载 / 没注册 `notificationsApi`,回落到内置只读兜底——而脚手架不再提供 root 级 `/api/notifications` 模块 | 保留 `web.config.ts` 的 `notification` 登记,onInit 里注册四方法(模板 `src/app/web/notification/entry.ts`);后端由 `src/
|
|
1574
|
+
| 通知铃 404 `no route matched`(`/api/notifications`) | 前端 `notification` 模块没加载 / 没注册 `notificationsApi`,回落到内置只读兜底——而脚手架不再提供 root 级 `/api/notifications` 模块 | 保留 `web.config.ts` 的 `notification` 登记,onInit 里注册四方法(模板 `src/app/web/notification/entry.ts`);后端由 `src/app/api/notification` 提供 `/api/notification/*` |
|
|
1567
1575
|
| `@types/react` / `typescript` 装成 `*` | 宿主 versions.json 未列 | 安装后按实际版本钉死(`*` 不可复现) |
|
|
1568
1576
|
| 迁移账本落后、服务拒绝启动 | release `verify` 闸 | 先 `ojm migrate -d api/dist`(透传自动补 `-c`) |
|
|
1569
1577
|
| `seed.sql` 没生效 / 语法错 | 按 `;` 朴素切分,注释里有分号 | 去掉注释里的分号字面量 |
|
|
@@ -1590,11 +1598,11 @@ export default async (path: string, html: string) => {
|
|
|
1590
1598
|
| 接管通知 provider(四方法全量) | `src/app/web/notification/entry.ts` |
|
|
1591
1599
|
| 上传 provider(action + headers 方法) | `src/app/web/personal-center/entry.ts` |
|
|
1592
1600
|
| 无页面模块(`routes: []`) | 同 `src/app/web/notification/entry.ts` |
|
|
1593
|
-
| handler `.schema` 入参 + 平级 `.response` 出参写法(GET + POST、参数段) | `src/
|
|
1594
|
-
| handler 写法(嵌套组列表 / 写入 / 403 归属) | `src/
|
|
1595
|
-
| 统一异常出口 `respond()`(service 抛 `Error & {status}` → 信封 `json.fail`) | 模板 `src/
|
|
1596
|
-
| 生成物长什么样(含工厂) | `src/app/web/notification/__client/
|
|
1597
|
-
| 建表三件套 | `src/
|
|
1601
|
+
| handler `.schema` 入参 + 平级 `.response` 出参写法(GET + POST、参数段) | `src/app/api/notification/api.ts` |
|
|
1602
|
+
| handler 写法(嵌套组列表 / 写入 / 403 归属) | `src/app/api/notification/api.ts`(模块根单文件,`read`/`read-all`/`clear` 子组) |
|
|
1603
|
+
| 统一异常出口 `respond()`(service 抛 `Error & {status}` → 信封 `json.fail`) | 模板 `src/app/api/_platform/_shared/respond.ts` + 各 handler 用法(§1.7-1) |
|
|
1604
|
+
| 生成物长什么样(含工厂) | `src/app/web/notification/__client/index.ts` |
|
|
1605
|
+
| 建表三件套 | `src/app/api/notification/{schema.yaml,manifest.yaml,migrations/}` |
|
|
1598
1606
|
| 全局类型(`window.$message` 等) | 工程根 `env.d.ts` |
|
|
1599
1607
|
|
|
1600
1608
|
另有:
|
|
@@ -1826,8 +1834,9 @@ module-root 下数据源是「扫描 + `web.config.ts` 登记」并集(未登
|
|
|
1826
1834
|
一个脚手架可放多份用户方案(`ojm new` 逐个生成,各自一份 `src/<name>/solution.yaml`),
|
|
1827
1835
|
`ojm build --bundle` 无参时全部打包、逐个校验独立出包;框架自带方案自动排除(名单见
|
|
1828
1836
|
`src/.ojm-builtin.json`,`ojm info` 默认折叠自带模块)。
|
|
1829
|
-
|
|
1830
|
-
[`
|
|
1837
|
+
**从开发到发布的完整过程视角(新手照做版,含签名/私钥纪律与多人各自发包纪律)见本目录**
|
|
1838
|
+
[`publish-walkthrough.md`](./publish-walkthrough.md);仓内深度过程视角另见
|
|
1839
|
+
[`docs/guides/solution-dev-build-publish.md`](../guides/solution-dev-build-publish.md)(不随 cli 包发货)。
|
|
1831
1840
|
|
|
1832
1841
|
### 11.1 打包:`ojm build --bundle`
|
|
1833
1842
|
|
|
@@ -2016,7 +2025,7 @@ ojm trust revoke <key_id> # 吊销:阻断新装/升级,不动已装
|
|
|
2016
2025
|
|
|
2017
2026
|
### 12.3 第一方 bootstrap key(首装免 TOFU)
|
|
2018
2027
|
|
|
2019
|
-
平台模板内置第一方发布方**公钥**(`src/
|
|
2028
|
+
平台模板内置第一方发布方**公钥**(`src/app/api/_platform/market/bootstrap.ts` 的
|
|
2020
2029
|
`BOOTSTRAP_KEY`):信任库文件首次初始化时自动播种一次——**market 方案首装免 TOFU 直接过
|
|
2021
2030
|
验签**。发布时序(平台发行人执行):`ojm keygen` → 签 oj-market 首发包 → 公钥 + key_id
|
|
2022
2031
|
回填 `BOOTSTRAP_KEY` 随模板发布。播种纪律:库已初始化(含空库)不播种——运维 `revoke`
|
|
@@ -2232,7 +2241,7 @@ async function post() {
|
|
|
2232
2241
|
所以前端消费组织架构的唯一正路是「借壳」:
|
|
2233
2242
|
|
|
2234
2243
|
1. 在**自己模块**的 handler 里调 `resolveReadFace` + 需要的那几个读函数(§14.2);
|
|
2235
|
-
2. 跑 `oj openapi` + `ojm api`,生成本模块的 `src/<模块>/web/__client/
|
|
2244
|
+
2. 跑 `oj openapi` + `ojm api`,生成本模块的 `src/<模块>/web/__client/index.ts`;
|
|
2236
2245
|
3. 页面用本模块 client 调。
|
|
2237
2246
|
|
|
2238
2247
|
多一层 handler 换来三件事:接口形状由**你自己的契约**决定(只出页面要的字段)、框架内部实现不进你的
|
|
@@ -50,6 +50,13 @@
|
|
|
50
50
|
|
|
51
51
|
所以「菜单出现两条 A」这种事不会发生——菜单条目按模块名唯一。
|
|
52
52
|
|
|
53
|
+
> **角色名自带翻译(0.2.57 起)**:`roles[].title_key` 落库前按站点语言解析成可读文本存
|
|
54
|
+
> `sys_role.title`,原始 key 另存 `sys_role.title_key`。要求:key 形状 `<模块名>:role.<slug>`
|
|
55
|
+
> (ns 必须等于方案模块名),且两个 locale 文件里都有嵌套 key(`{"role":{"sales":"销售"}}`)。
|
|
56
|
+
> 解析数据源是包内 `web/modules/<name>/locales/`——`build --bundle` 自动从源码复制双语,
|
|
57
|
+
> 作者零动作。key 缺失/文件缺失 → title 存裸 key,**安装永不因此失败**;改 locale 文案后重新
|
|
58
|
+
> 打包升级,角色名即随升级更新。
|
|
59
|
+
|
|
53
60
|
### 3.2 但同名再装会被直接拒
|
|
54
61
|
|
|
55
62
|
安装前置校验有**方案名唯一**约束:同名方案已装 → 拒装并报占用者版本(`方案名已被已装方案「A@x.y.z」占用`)。只有升级 / 重装路径放行。这是第一道防线:从入口就不给你制造重复的机会。
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# 方案包发布全流程——新手照做版
|
|
2
|
+
|
|
3
|
+
适用 ojm ≥ 0.2.22(写作时 0.2.56)。本文自包含:从「代码写完了」到「装进站点」一条命令链照着敲,每步附期望输出与通过判据。
|
|
4
|
+
字段规范与错误码全口径见 `manual.md` §11/§12;仓内深度过程视角另见
|
|
5
|
+
`docs/guides/solution-dev-build-publish.md`(不随 cli 包发货)。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 0. 全景与前提
|
|
10
|
+
|
|
11
|
+
```mermaid
|
|
12
|
+
flowchart LR
|
|
13
|
+
A["① 构建 ojm build"] --> B["② 打包 ojm build --bundle"]
|
|
14
|
+
B --> C["③ 身份 ojm keygen(一次性)"]
|
|
15
|
+
C --> D["④ 签名 ojm sign --check"]
|
|
16
|
+
D --> E["⑤ 发布 ojm market publish"]
|
|
17
|
+
E --> F["⑥ 重建索引 ojm market build-index"]
|
|
18
|
+
F --> G["⑦ 验证 market verify / trial"]
|
|
19
|
+
G --> H["⑧ 安装 ojm install + TOFU"]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
信任链硬 fail-closed:未签、签错、key 不被信任的包一律拒装,没有「先装了再说」的开关。
|
|
23
|
+
**开始前确认三件事**(缺一件打包就过不去):
|
|
24
|
+
|
|
25
|
+
1. 方案契约在位:`src/<模块>/solution.yaml`(`name` / `version` / `summary`;纯前端可省 `api` 段,纯后端可省 `web` 段);
|
|
26
|
+
2. `web.config.ts` 的 `modules` 数组已登记该模块(否则构建不产出站点产物,打包报「缺少本方案的站点产物」);
|
|
27
|
+
3. 双语 locale 在位:`web/src/.../locales/zh-CN.json` 与 `en-US.json`。
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 1. 构建与打包
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
ojm build # ① 站点产物 web/dist/ + 后端产物 api/dist/
|
|
35
|
+
ojm build --bundle # ② 打包全部用户方案 → dist/<name>-<version>.tgz
|
|
36
|
+
ojm build --bundle crm # 只打 src/crm/ 这一份(多方案工程用)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
期望输出(②):
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
[ojm] 方案包已生成 → dist/<name>-<version>.tgz
|
|
43
|
+
[ojm] sha256:…(确定性打包:同源同字节)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
三个要点:
|
|
47
|
+
|
|
48
|
+
- `--bundle` **只组装、不重跑构建**——先 ① 后 ②,顺序反了装的是旧产物。
|
|
49
|
+
- **确定性打包**:同一份源码在任何机器上打出的包字节一致(sha256 相同)——这是多人协作与幂等重发的地基。
|
|
50
|
+
- 打包即校验(fail-fast):契约字段、表集对照、双语齐备、**私钥形态文件禁入包**(`*.pem`/`*.key`/`.env` 系等自动拦截)。两段式出包:校验全绿才开始写盘,失败则 dist/ 零增量。
|
|
51
|
+
|
|
52
|
+
版本号升了吗?`solution.yaml` 的 `version` 与文件名一致,**发布前必须升**(见 §7 版本冲突行)。
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 2. 一次性:生成发布身份(keygen)
|
|
57
|
+
|
|
58
|
+
每个开发者/每个环境**各自生成一把** Ed25519 密钥对,只需做一次:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
ojm keygen --id acme.alice.macbook # 例:<团队/产品>.<人名>.<环境>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- 期望输出里有一行 **fingerprint**(`sha256(公钥)`)——**抄下来发给运维**,TOFU 核对要用(§8)。
|
|
65
|
+
- 落点 `~/.ojm/keys/<id>/`:`private.pem`(权限 0600)+ `public.hex`。
|
|
66
|
+
- key_id 必须**全小写、至少两段**(`<publisher>.<id>`),禁裸通用名如 `mykey`——违约包能签出来但装不进(`sig_key` 拒装)。
|
|
67
|
+
|
|
68
|
+
私钥纪律(逐条照做,违者 = 任何人都能以你的身份发包):
|
|
69
|
+
|
|
70
|
+
| 纪律 | 原因 |
|
|
71
|
+
|---|---|
|
|
72
|
+
| 私钥只活在 `~/.ojm/keys/<id>/`,永不出目录 | 唯一受管位置;换机 = 重新 keygen 新 id 走信任流程 |
|
|
73
|
+
| 绝不提交进任何仓库、绝不写进方案包 | 打包自检会拦私钥文件,但别依赖最后一道闸 |
|
|
74
|
+
| 一台机器可多把 key(按项目/环境分 `<key-name>`),一次发布固定一把 | `.sig` 记 key_id,安装侧按 key_id 查信任库 |
|
|
75
|
+
| CI 签名用临时密钥对,发布后即吊销;或经 secret 注入只在内存停留 | 长期私钥不落 CI 工件/缓存 |
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 3. 每次发布:签名
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
ojm sign dist/<name>-<version>.tgz \
|
|
83
|
+
--id acme.alice.macbook \
|
|
84
|
+
--key ~/.ojm/keys/acme.alice.macbook/private.pem \
|
|
85
|
+
--registry-id oj-registry \
|
|
86
|
+
--check
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- `--registry-id` **必填**:发布仓库的自拟标识(同一市场的所有发布方用同一个值,如 `oj-registry`)。它进签名规范串防「把 A 仓库的包重放到 B 仓库」;站点若配置了自身 registry 身份则必须与之一致(默认模板未配置,不比对)。
|
|
90
|
+
- 产物 = 同目录 `<tgz>.sig` 侧车文件。**签了再改包 = 签名作废**;重打包必须重签。
|
|
91
|
+
- `--check` 离线自检(本地验一遍),不必等安装失败才发现签错。期望输出:`[ojm] 自检通过…`。
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 4. 发布到 CDN
|
|
96
|
+
|
|
97
|
+
CDN 就是**一个装着固定命名文件的目录**:`<name>-<version>.tgz`(+ `.sig`)与索引。发布 = 把文件放进去 + 重建索引。推到远端真 CDN(OSS/S3)只是目录同步(rsync/工具上传),包文件名含版本、内容不可变,多人并发推不冲突。
|
|
98
|
+
|
|
99
|
+
### 4.1 单方案工程(一条命令)
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
ojm market publish --real . \
|
|
103
|
+
--id acme.alice.macbook \
|
|
104
|
+
--key ~/.ojm/keys/acme.alice.macbook/private.pem \
|
|
105
|
+
--registry-id oj-registry \
|
|
106
|
+
--rebuild-index
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
期望输出:`[ojm] 包已落 CDN → cdn/<name>-<version>.tgz(N 字节)` + 发布方指纹。`cdn/` 下落齐六件:tgz / .tgz.sig / 索引两份 yaml(+ 各自 .sig)。
|
|
110
|
+
|
|
111
|
+
> 引用式方案(契约 `api` 段省略 `tables`/`deps`)会打印「引用式方案…非漏配」提示——那是合法形态,不是错误。
|
|
112
|
+
|
|
113
|
+
### 4.2 多方案工程(显式四步)
|
|
114
|
+
|
|
115
|
+
`src/` 下有多份 `solution.yaml` 时,`market publish --real .` 会被拒并提示改走显式链路(一次只发一份,防误发整批):
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
ojm build --bundle crm # ① 打包指定方案
|
|
119
|
+
ojm sign dist/crm-1.0.0.tgz --id acme.alice.macbook \
|
|
120
|
+
--key ~/.ojm/keys/acme.alice.macbook/private.pem \
|
|
121
|
+
--registry-id oj-registry --check # ② 签名
|
|
122
|
+
cp dist/crm-1.0.0.tgz dist/crm-1.0.0.tgz.sig cdn/ # ③ tgz + .sig 放进 cdn/
|
|
123
|
+
ojm market build-index --id <维护者key> --check # ④ 重建索引(见 §4.3)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 4.3 多人协作纪律(同一仓库、不同开发者、不同环境各自发包)
|
|
127
|
+
|
|
128
|
+
`market publish` 的设计就是为多人并发而做的:**每人只写自己的 `<name>-<version>.tgz`**,没有共享可变文件,互不干扰。四条纪律:
|
|
129
|
+
|
|
130
|
+
1. **落包并发安全**:谁都可以随时 `market publish` 落自己的包,不用排队。
|
|
131
|
+
2. **索引重建要中心化**:publish 落包后**索引不自动重建**(日志会提示「多人场景请由维护者跑 `ojm market build-index`」)。包落了但没重建索引 = 站点装不到。约定一个触发点:维护者定期跑,或 CI 钩子。**不要两人同时对同一 cdn/ 跑 build-index**(重建本身设计为单执行者)。
|
|
132
|
+
3. **同版本号字节冲突会被拒**:`crm@1.0.0` 已存在且字节不一致时 publish 直接报错——内容有变就升 version 重发,同一版本号全宇宙只允许一份字节。同源重发字节一致则幂等通过。
|
|
133
|
+
4. **同名方案两把 key 是治理问题**:技术上可行(索引按名字聚合版本、信任按 key),但「这个方案名归谁」机制不裁决——不同人发**不同方案**随便;发**同一方案名**需团队事先约定(推荐:方案名归属唯一发布方,他人在该方案上协作走代码仓库而非另起 key 发布)。
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 5. 验证发布(不起站点)
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
ojm market verify --rebuild-index # 五项自检:完整性/包级签/索引签×2/manifest 结构,全 ✓ 才算过
|
|
141
|
+
ojm market trial --fixture <name>@<version> # 隔离站点跑完整安装,跑完即毁,不污染工程
|
|
142
|
+
ojm market walkthrough --fixture <name>@<version> # 14 项 AC:主链 9 + 负向 5;报告落 logs/walkthrough-<ts>.md
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
walkthrough 期望 `14 PASS / 0 FAIL`。负向 5 项是**预期失败口径**:防线没拦住(恶意包装成了)才算 FAIL。
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 6. 装到站点(运维侧)
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# ① 站点以 release 形态起(dev/preview 完不成安装的热更段)
|
|
153
|
+
oj serve -c config.yaml -b /api --api-path dist/api
|
|
154
|
+
|
|
155
|
+
# ② 市场地址指向站点自供的 /cdn(默认即如此;外部真 CDN 才需要 setup 写绝对地址)
|
|
156
|
+
ojm market setup --site-url http://127.0.0.1:9778 --user superadmin --pass … --new-pass <新口令>
|
|
157
|
+
|
|
158
|
+
# ③ 安装(从 CDN 拉:name[@version];直传文件则给 tgz 路径,.sig 同目录自动随包)
|
|
159
|
+
ojm install crm@1.0.0 --site-url http://127.0.0.1:9778 --user superadmin --pass <新口令>
|
|
160
|
+
|
|
161
|
+
# ④ 首装陌生 key 必停在 needs_trust:响应 trust.fingerprint 与 §2 记下的指纹【逐字】核对
|
|
162
|
+
# 核对通道用当面/电话/既有加密聊天——不要用 CDN 页面上的指纹(那正是攻击者能改的地方)
|
|
163
|
+
ojm trust confirm --task <task-id> --site-url http://127.0.0.1:9778 --user superadmin --pass <新口令>
|
|
164
|
+
ojm install crm@1.0.0 --site-url http://127.0.0.1:9778 --user superadmin --pass <新口令> # 重跑续跑
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
通过判据:`dist/api/manifests.yaml` 出现 `<name>: <version>`;**重启一次 `oj serve`**(新方案路由要重启才注册,热更只覆盖 vars)后 `curl /api/<name>/ping` 返回 `code:0`。
|
|
168
|
+
|
|
169
|
+
第一方方案走模板内置公钥(bootstrap key),首装免 TOFU;第三方走上面的人工核对。key 轮换 = 新 key_id 重发 + 站点 `trust add` 新公钥 + `trust revoke` 旧 key(吊销只阻断新装,不动已装方案)。
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 7. 失败速查(症状 → 病根 → 药方)
|
|
174
|
+
|
|
175
|
+
| 症状(安装拒装码 / 命令报错) | 病根 | 药方 |
|
|
176
|
+
|---|---|---|
|
|
177
|
+
| `sig_missing` | 没签,或 `.sig` 没随包在同目录 | `ojm sign --check` 后确认 `.sig` 与 tgz 同目录再装 |
|
|
178
|
+
| `tampered` | 签完又动过包,或传输损坏 | 重打包重签;核对 sha256 |
|
|
179
|
+
| `sig_key` | key_id 不是 `<publisher>.<id>` 全小写两段式 | 重新 keygen 合规 id,用新 key 重签 |
|
|
180
|
+
| `sig_alg` / `sig_format` | 签名算法/.sig 结构不符 | 用当前 ojm 重签(旧版本工具产物) |
|
|
181
|
+
| `needs_trust`(第五终态,非成功) | 站点第一次见这个 key | 与发布方逐字核对指纹 → `trust confirm --task <id>` → 重跑 install |
|
|
182
|
+
| publish 报「包冲突:…字节不一致」 | 同 name@version 已有字节不同的包 | **升 version 重发**;确需覆盖先删 CDN 旧 tgz |
|
|
183
|
+
| publish 报「一次只发布一份方案」 | 多方案工程走了自动链路 | §4.2 显式四步 |
|
|
184
|
+
| 打包报「缺少本方案的站点产物」 | `web.config.ts` 没登记 / 没先 `ojm build` | 登记后重跑构建再打包 |
|
|
185
|
+
| 打包报「缺少 bundle 级契约」 | 没有 `solution.yaml` | 补契约(§0 前提 1) |
|
|
186
|
+
| 装不到刚发的包 | 索引没重建 | `ojm market build-index`(§4.3 纪律 2),确认 CDN 六文件同步 |
|
|
187
|
+
| 装成后 404 | 方案路由没注册 | 重启 `oj serve`(§6 通过判据) |
|
|
188
|
+
| 私钥误入工程 | 打包自检拦下 / 被外部拷走 | 立即 `trust revoke` 该 key → 换新 key_id 重发 |
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 8. 发布前 checklist
|
|
193
|
+
|
|
194
|
+
- [ ] `solution.yaml` 的 `version` 已升,与文件名一致;
|
|
195
|
+
- [ ] `ojm build` + `ojm build --bundle` 均通过,sha256 记进发布单;
|
|
196
|
+
- [ ] `ojm sign … --check` 通过,`.sig` 与 tgz 同目录;
|
|
197
|
+
- [ ] 新 key 首次发布:fingerprint 已提前交给运维(免得卡在 needs_trust);
|
|
198
|
+
- [ ] `ojm market verify` 五项全 ✓;
|
|
199
|
+
- [ ] 多人团队:索引重建触发点已约定(维护者或 CI)。
|