@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/src/builtin.ts
CHANGED
|
@@ -64,9 +64,16 @@ export function readBuiltinModules(projectRoot: string): Set<string> {
|
|
|
64
64
|
export function deriveBuiltinModules(include: Iterable<string>): Set<string> {
|
|
65
65
|
const mods = new Set<string>();
|
|
66
66
|
for (const p of include) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
// 聚合容器下的后端模块:src/app/api/<name>(模块名取 api 的下一段,不是容器名)
|
|
68
|
+
const nested = /^src\/[^/]+\/api\/([^/]+)(?:\/|$)/.exec(p)?.[1];
|
|
69
|
+
if (nested) {
|
|
70
|
+
mods.add(nested);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
// 平铺后端 src/<name>/api 与前端 src/<name>/web/...
|
|
74
|
+
const flat = /^src\/([^/]+)\/(?:api|web)(?:\/|$)/.exec(p)?.[1];
|
|
75
|
+
if (flat)
|
|
76
|
+
mods.add(flat);
|
|
70
77
|
}
|
|
71
78
|
return mods;
|
|
72
79
|
}
|
package/src/bundle.ts
CHANGED
|
@@ -308,6 +308,16 @@ async function assembleBundle(projectRoot: string, layout: ProjectLayout, plan:
|
|
|
308
308
|
}
|
|
309
309
|
fs.mkdirSync(path.join(root, "web", "modules"), { recursive: true });
|
|
310
310
|
fs.cpSync(ownWeb, path.join(root, "web", "modules", name), { recursive: true });
|
|
311
|
+
// R1 数据源随包(计划 docs/plans/20261103-0948):安装期角色 title 解析读
|
|
312
|
+
// <包>/web/modules/<name>/locales/<lang>.json——构建产物不含 locales(源码 JSON
|
|
313
|
+
// 被 vite 内联进 JS chunk),故从源码树复制双语;目录不存在(纯后端段缺 locales)静默跳过。
|
|
314
|
+
for (const lang of ["zh-CN", "en-US"]) {
|
|
315
|
+
const src = path.join(webSrcOf(layout, name), "locales", `${lang}.json`);
|
|
316
|
+
if (fs.existsSync(src)) {
|
|
317
|
+
fs.mkdirSync(path.join(root, "web", "modules", name, "locales"), { recursive: true });
|
|
318
|
+
fs.copyFileSync(src, path.join(root, "web", "modules", name, "locales", `${lang}.json`));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
311
321
|
const web = m.web as Record<string, unknown>;
|
|
312
322
|
for (const rel of [web.entry, ...(Array.isArray(web.css) ? web.css : [])]) {
|
|
313
323
|
if (typeof rel === "string" && !fs.existsSync(path.join(root, "web", rel))) {
|
|
@@ -365,6 +375,57 @@ export async function buildBundle(projectRoot: string, opts: { out?: string, nam
|
|
|
365
375
|
return results;
|
|
366
376
|
}
|
|
367
377
|
|
|
378
|
+
/**
|
|
379
|
+
* `ojm build` 末尾的用户方案摘要(2026-11-03,计划 docs/plans/20261103-0925):
|
|
380
|
+
* 明确回答「我的方案构建进了吗、能不能打包」。判定口径与 resolveContractTargets 同源
|
|
381
|
+
* (模块级契约 ∪ 根契约,减内置名单)——摘要里列出的就是 --bundle 将要打包的那批。
|
|
382
|
+
* 只报告不裁决:不调 resolveLayout(构建末尾不因摘要崩溃),log 可注入供测试断言。
|
|
383
|
+
*/
|
|
384
|
+
export function summarizeBuiltSolutions(projectRoot: string, built: readonly { name: string, version: string }[], log: (line: string) => void = console.log): void {
|
|
385
|
+
const builtin = readBuiltinModules(projectRoot);
|
|
386
|
+
const rootSolution = path.join(projectRoot, "solution.yaml");
|
|
387
|
+
const rootManifest = path.join(projectRoot, "manifest.yaml");
|
|
388
|
+
const rootContract = fs.existsSync(rootSolution) ? rootSolution : fs.existsSync(rootManifest) ? rootManifest : undefined;
|
|
389
|
+
const rootName = rootContract ? peekContractName(rootContract) : undefined;
|
|
390
|
+
|
|
391
|
+
// 用户方案候选:模块级契约(非内置)∪ 根契约 name(按名去重——「根压 src」只是打包取哪份契约,摘要里是同一个方案)
|
|
392
|
+
const names = new Set<string>();
|
|
393
|
+
for (const c of collectModuleContracts(projectRoot)) {
|
|
394
|
+
if (!builtin.has(c.moduleDir!))
|
|
395
|
+
names.add(c.moduleDir!);
|
|
396
|
+
}
|
|
397
|
+
if (rootName !== undefined && !builtin.has(rootName))
|
|
398
|
+
names.add(rootName);
|
|
399
|
+
|
|
400
|
+
const builtByName = new Map(built.map(b => [b.name, b]));
|
|
401
|
+
if (names.size === 0) {
|
|
402
|
+
log("[ojm] 用户方案 0 份(ojm new <name> 创建后即可 ojm build --bundle 打包)");
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
const all = [...names].sort();
|
|
406
|
+
const ok = all.filter(n => builtByName.has(n));
|
|
407
|
+
const missing = all.filter(n => !builtByName.has(n));
|
|
408
|
+
const builtList = ok.map(n => `${n}@${builtByName.get(n)!.version}`).join("、") || "无";
|
|
409
|
+
if (missing.length === 0)
|
|
410
|
+
log(`[ojm] 用户方案 ${all.length} 份已构建:${builtList}(ojm build --bundle 打包)`);
|
|
411
|
+
else
|
|
412
|
+
log(`[ojm] 用户方案 ${all.length} 份中 ${ok.length} 份已构建:${builtList};未构建:${missing.join("、")}(查 web.config.ts 登记/enabled)`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const builtinBuilt = built.filter(b => builtin.has(b.name));
|
|
416
|
+
if (builtinBuilt.length > 0)
|
|
417
|
+
log(`[ojm] 框架自带模块 ${builtinBuilt.length} 份已构建(不参与用户打包)`);
|
|
418
|
+
|
|
419
|
+
// module-root 口径(src/ 树在即适用;flat/new 工程模块不在 src/):非内置且无契约的模块不会被打包——提前点名,别等 --bundle 才发现
|
|
420
|
+
if (fs.existsSync(path.join(projectRoot, "src"))) {
|
|
421
|
+
for (const b of built) {
|
|
422
|
+
if (builtin.has(b.name) || names.has(b.name))
|
|
423
|
+
continue;
|
|
424
|
+
log(`[ojm] ⚠️ 模块 ${b.name}@${b.version} 缺方案契约(src/${b.name}/solution.yaml),不会被打包`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
368
429
|
/**
|
|
369
430
|
* C2-31 分层核对:模块级 manifest 仅 oj 原生键,且 name/version 与 bundle 级逐一相等
|
|
370
431
|
* (oj 启动强校验同源)。
|
package/src/cli.ts
CHANGED
|
@@ -135,7 +135,10 @@ function makeAction(def: HelpEntry, projectRoot: string, lang: Lang, exit: (code
|
|
|
135
135
|
}
|
|
136
136
|
const { buildBackend, buildModules } = await import("./build");
|
|
137
137
|
await buildBackend(projectRoot);
|
|
138
|
-
await buildModules(projectRoot, { mergeSite: true });
|
|
138
|
+
const built = await buildModules(projectRoot, { mergeSite: true });
|
|
139
|
+
// 用户方案摘要(计划 20261103-0925):构建末尾点名「我的方案」是否进构建、能否打包
|
|
140
|
+
const { summarizeBuiltSolutions } = await import("./bundle");
|
|
141
|
+
summarizeBuiltSolutions(projectRoot, built);
|
|
139
142
|
};
|
|
140
143
|
case "new":
|
|
141
144
|
return async (name: string | undefined, extra: string[] = [], opts: { layout?: string, api?: boolean } = {}) => {
|
|
@@ -72,7 +72,7 @@ function emit() {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
describe("emitClient (module target)", () => {
|
|
75
|
-
const { "
|
|
75
|
+
const { "index.ts": apiTs, "api.schemas.ts": schemasTs } = emit();
|
|
76
76
|
|
|
77
77
|
it("aPI_PREFIX / MODULE_PREFIX 用 JSON 字符串字面量(注入安全)", () => {
|
|
78
78
|
expect(apiTs).toContain("export const API_PREFIX = \"/wiki\";");
|
|
@@ -114,7 +114,7 @@ describe("emitClient (module target)", () => {
|
|
|
114
114
|
describe("emitClient 注入安全:模块名含特殊字符不破坏源码", () => {
|
|
115
115
|
it("module 名含 -/$ 时 MODULE_PREFIX/工厂名仍走字符串字面量(API_PREFIX 取 IR wirePrefix)", () => {
|
|
116
116
|
const ir = irFromOpenapi(DOC, { base: "/v1/api" });
|
|
117
|
-
const { "
|
|
117
|
+
const { "index.ts": apiTs } = emitClient(ir, { target: "module", module: "weird-name" });
|
|
118
118
|
// module 选项只影响 MODULE_PREFIX 与 create<Module>Client 名
|
|
119
119
|
expect(apiTs).toContain("export const MODULE_PREFIX = \"/weird-name\";");
|
|
120
120
|
expect(apiTs).toContain("export function createWeirdNameClient(");
|
|
@@ -128,7 +128,7 @@ describe("emitClient 注入安全:模块名含特殊字符不破坏源码", ()
|
|
|
128
128
|
describe("emitClient 多根聚合归属(owner)", () => {
|
|
129
129
|
it("owner 与 module 不同 → 落 MODULE_OWNER,护栏对比归属模块;MODULE_PREFIX 仍是 api 前缀", () => {
|
|
130
130
|
const ir = irFromOpenapi(DOC, { base: "/v1/api" });
|
|
131
|
-
const { "
|
|
131
|
+
const { "index.ts": apiTs } = emitClient(ir, { target: "module", module: "system", owner: "app" });
|
|
132
132
|
expect(apiTs).toContain("export const MODULE_PREFIX = \"/system\";");
|
|
133
133
|
expect(apiTs).toContain("export const MODULE_OWNER = \"/app\";");
|
|
134
134
|
expect(apiTs).toContain("ctx.module.name !== MODULE_OWNER.slice(1)");
|
|
@@ -137,7 +137,7 @@ describe("emitClient 多根聚合归属(owner)", () => {
|
|
|
137
137
|
|
|
138
138
|
it("owner 缺省 = module → 生成物与历史逐字一致(不出现 MODULE_OWNER)", () => {
|
|
139
139
|
const ir = irFromOpenapi(DOC, { base: "/v1/api" });
|
|
140
|
-
const { "
|
|
140
|
+
const { "index.ts": apiTs } = emitClient(ir, { target: "module", module: "system", owner: "system" });
|
|
141
141
|
expect(apiTs).not.toContain("MODULE_OWNER");
|
|
142
142
|
expect(apiTs).toContain("ctx.module.name !== MODULE_PREFIX.slice(1)");
|
|
143
143
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// ScopedRequestLike 真源在 runtime contract(生成物 __client/
|
|
1
|
+
// ScopedRequestLike 真源在 runtime contract(生成物 __client/index.ts 也从这里 import,
|
|
2
2
|
// 自身不再 re-export——旧写法 type import 自 __client 会在再生后 TS2305)。
|
|
3
3
|
import type { ScopedRequestLike } from "@oj-module/runtime/contract/errors";
|
|
4
4
|
|
|
@@ -9,7 +9,7 @@ import type { ScopedRequestLike } from "@oj-module/runtime/contract/errors";
|
|
|
9
9
|
// 不加载 @oj-module/runtime 重依赖(antd / pro-components 在 node 环境无法 import),仅桩 request、
|
|
10
10
|
// 透传真实 zod(供生成 client 的 DEV 响应校验走真实路径)。ContractApiError 由轻量子路径提供。
|
|
11
11
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
12
|
-
import { bindRequest } from "../../../templates/src/
|
|
12
|
+
import { bindRequest } from "../../../templates/src/app/web/_platform/__client";
|
|
13
13
|
import { httpMarketClient } from "../../../templates/src/oj-market/web/api/defaultClient";
|
|
14
14
|
|
|
15
15
|
// index() 直连 request(不走生成 client),故把桩 request 做成可变实现,测试内统一指向同一 mock。
|
|
@@ -46,9 +46,9 @@ describe("runApi 消费 openapi.json(新口径 Task #4)", () => {
|
|
|
46
46
|
const result = await runApi({ cwd: dir, openapi: "openapi.json" });
|
|
47
47
|
expect(result.contracts).toBe(2);
|
|
48
48
|
|
|
49
|
-
const wikiApi = join(dir, "web/src/wiki/__client/
|
|
49
|
+
const wikiApi = join(dir, "web/src/wiki/__client/index.ts");
|
|
50
50
|
const wikiSchemas = join(dir, "web/src/wiki/__client/api.schemas.ts");
|
|
51
|
-
const userApi = join(dir, "web/src/user/__client/
|
|
51
|
+
const userApi = join(dir, "web/src/user/__client/index.ts");
|
|
52
52
|
expect(existsSync(wikiApi)).toBe(true);
|
|
53
53
|
expect(existsSync(wikiSchemas)).toBe(true);
|
|
54
54
|
expect(existsSync(userApi)).toBe(true);
|
package/src/contract/check.ts
CHANGED
|
@@ -307,17 +307,21 @@ async function checkFromOpenapi(openapiPath: string, cwd: string, _exemption: Ap
|
|
|
307
307
|
const violations: CheckViolation[] = [];
|
|
308
308
|
const layout = tryResolveLayout(cwd);
|
|
309
309
|
// 契约工具的扫描单元是「模块 api 根」而不是整棵源码树:module-root 布局里
|
|
310
|
-
// `src/<name>/api` 的 `api` 段不属于路由,且 `web/__client/
|
|
310
|
+
// `src/<name>/api` 的 `api` 段不属于路由,且 `web/__client/index.ts` 不能被当 handler。
|
|
311
311
|
const roots = layout ? apiModuleRoots(layout) : legacyApiModuleRoots(cwd);
|
|
312
312
|
const owners = await loadClientOwners(layout?.projectRoot ?? cwd);
|
|
313
313
|
for (const [module, eps] of groupIrByModule(ir)) {
|
|
314
314
|
const client = emitClient(eps, { target: "module", module, owner: owners[module] });
|
|
315
315
|
const clientDir = clientDirOf(layout, cwd, module, owners);
|
|
316
|
-
for (const [file, content] of [["
|
|
316
|
+
for (const [file, content] of [["index.ts", client["index.ts"]], ["api.schemas.ts", client["api.schemas.ts"]]] as const) {
|
|
317
317
|
const p = join(clientDir, file);
|
|
318
318
|
if (!existsSync(p) || readFileSync(p, "utf8") !== content)
|
|
319
319
|
violations.push({ level: "error", kind: "artifact-stale", message: `[ojm-api] 生成物过期或缺失:${relative(cwd, p)}——请重跑 ojm api 使其与 openapi.json 同步。`, filePath: p });
|
|
320
320
|
}
|
|
321
|
+
// 旧生成物(0.2.54 前叫 api.ts):不删会被 oj dev 当路由扫描并因 import 逃逸报错
|
|
322
|
+
const legacy = join(clientDir, "api.ts");
|
|
323
|
+
if (existsSync(legacy))
|
|
324
|
+
violations.push({ level: "error", kind: "artifact-stale", message: `[ojm-api] 旧生成物仍在:${relative(cwd, legacy)}——入口已改为 index.ts,请重跑 ojm api(会删除旧文件)。`, filePath: legacy });
|
|
321
325
|
}
|
|
322
326
|
const apiSrcDir = layout ? apiModulesSrc(layout) : join(cwd, "api/src");
|
|
323
327
|
if (existsSync(apiSrcDir)) {
|
|
@@ -357,8 +357,15 @@ ${entries.join("\n")}
|
|
|
357
357
|
`;
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
-
/**
|
|
361
|
-
|
|
360
|
+
/**
|
|
361
|
+
* 发射双产物:index.ts(类型 + 请求函数 + 工厂)与 api.schemas.ts(DEV 校验 schema)。
|
|
362
|
+
*
|
|
363
|
+
* 入口文件名是 `index.ts` 而非 `api.ts`:oj dev 的 `--module-root` 扫描会把模块目录下
|
|
364
|
+
* **任何 `api.ts`** 当路由文件导入,聚合模块(src/app 同时有 api/ 与 web/)里的
|
|
365
|
+
* `web/<root>/__client/index.ts` 会被误扫(import 逃逸 project root 直接报错)。
|
|
366
|
+
* `index.ts` 不在扫描名单内,且 `from "../__client"` 的导入更短。
|
|
367
|
+
*/
|
|
368
|
+
export function emitClient(ir: IrEndpoint[], opts: { target: "module" | "internal", module?: string, /** client 归属模块(多根聚合);缺省 = module */ owner?: string }): { "index.ts": string, "api.schemas.ts": string } {
|
|
362
369
|
if (ir.length === 0)
|
|
363
370
|
throw new Error("[ojm-api] client 发射失败:IR 为空——openapi.json 里没有任何端点(operationId + tags),无需生成;请检查 \`oj openapi\` 产物。");
|
|
364
371
|
if (opts.target === "module" && !opts.module) {
|
|
@@ -386,7 +393,7 @@ export function emitClient(ir: IrEndpoint[], opts: { target: "module" | "interna
|
|
|
386
393
|
sections.push(emitEndpoint(ep));
|
|
387
394
|
}
|
|
388
395
|
return {
|
|
389
|
-
"
|
|
396
|
+
"index.ts": `${sections.join("\n\n")}\n`,
|
|
390
397
|
"api.schemas.ts": emitSchemas(ir, opts.target),
|
|
391
398
|
};
|
|
392
399
|
}
|
package/src/contract/run.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { OjSchemaDiag } from "./emit-oj-schema";
|
|
|
2
2
|
import type { IrEndpoint } from "./ir";
|
|
3
3
|
import type { OpenApiDoc } from "./openapi-ir";
|
|
4
4
|
import { execFile as execFileCb } from "node:child_process";
|
|
5
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { basename, dirname, join } from "node:path";
|
|
8
8
|
import process from "node:process";
|
|
@@ -89,8 +89,16 @@ async function runFromOpenapi(openapiPath: string, cwd: string, result: RunResul
|
|
|
89
89
|
for (const [module, eps] of byModule) {
|
|
90
90
|
const client = emitClient(eps, { target: "module", module, owner: owners[module] });
|
|
91
91
|
const clientDir = clientDirOf(layout, cwd, module, owners);
|
|
92
|
-
writeIfChanged(join(clientDir, "
|
|
92
|
+
writeIfChanged(join(clientDir, "index.ts"), client["index.ts"], result);
|
|
93
93
|
writeIfChanged(join(clientDir, "api.schemas.ts"), client["api.schemas.ts"], result);
|
|
94
|
+
// 迁移:0.2.54 起入口文件从 api.ts 改为 index.ts。旧文件必须删——oj dev 的
|
|
95
|
+
// --module-root 扫描会把模块目录下任何 api.ts 当路由导入(聚合模块的
|
|
96
|
+
// web/<root>/__client/index.ts 会直接报 import 逃逸)。
|
|
97
|
+
const legacy = join(clientDir, "api.ts");
|
|
98
|
+
if (existsSync(legacy)) {
|
|
99
|
+
rmSync(legacy);
|
|
100
|
+
result.written.push(legacy);
|
|
101
|
+
}
|
|
94
102
|
}
|
|
95
103
|
// stub:oj 目录镜像树(人碰过的文件永不写——plan 阶段已按指纹判好)。
|
|
96
104
|
// 已实现键全量扫一遍传入:嵌套组(模块根单文件)实现的端点不再生成镜像 stub。
|
package/src/init.ts
CHANGED
|
@@ -21,7 +21,7 @@ import { BUILTIN_LIST_FILE, writeBuiltinList } from "./builtin";
|
|
|
21
21
|
import { resolveCertDir, resolveConfigPath } from "./layout";
|
|
22
22
|
import { probeOjCaps } from "./oj-caps";
|
|
23
23
|
import { mintDevCert } from "./oj-cert";
|
|
24
|
-
import { isSupportedOjVersion, MIN_OJ_VERSION, resolveOjBin, vendorCommand } from "./vendor";
|
|
24
|
+
import { isSupportedOjVersion, MIN_OJ_VERSION, resolveOjBin, syncOjDevkit, vendorCommand } from "./vendor";
|
|
25
25
|
import { readHostVersions } from "./versions";
|
|
26
26
|
|
|
27
27
|
export interface InitOptions {
|
|
@@ -167,25 +167,16 @@ export async function initProject(destDir: string, opts: InitOptions = {}): Prom
|
|
|
167
167
|
// oj 不可用/不支持 secret → 保留明文并告警(init 不因加密失败而失败)
|
|
168
168
|
await sealJwtSecret(destDir, report, opts.seal);
|
|
169
169
|
|
|
170
|
-
// 4) devkit 分发:agent skill
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
// v0.1.82 起 devkit 新增两份专题指南(api-manual/SKILL 均引用它们):
|
|
178
|
-
// SQL 构造器能力矩阵/方言差异、oj test L1 测试指南。它们是**增量文档**、
|
|
179
|
-
// 不影响 ojm 运行,故「有则拷、无则记跳过」——bin/devkit 来自用户本地
|
|
180
|
-
// oj 版本(MIN_OJ_VERSION 只钉到 0.1.73),旧版 devkit 没有这两个文件,
|
|
181
|
-
// 为两份文档把整个 init 打挂不值当(用户升 oj 后重跑 init 即补上)。
|
|
182
|
-
for (const name of ["sql-builder-guide.md", "oj-test-guide.md"])
|
|
183
|
-
copyIfPresent(path.join(devkit, name), path.join(skillDir, name), report);
|
|
184
|
-
copyIfAbsent(path.join(devkit, "global.d.ts"), path.join(destDir, "global.d.ts"), report);
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
170
|
+
// 4) devkit 分发:agent skill(与 vendor 同一同步出口 syncOjDevkit:bin/devkit 全量
|
|
171
|
+
// .md **覆盖**进 .claude/skills/oj-api-dev——文档随 oj 版本同步,见计划 20261103-1020)
|
|
172
|
+
// + 全局类型(模板已落则 copyIfAbsent 跳过,模板变体优先、用户可编辑)
|
|
173
|
+
const devkitStatus = syncOjDevkit(destDir, binDir);
|
|
174
|
+
if (devkitStatus.ok)
|
|
175
|
+
report.created.push(`.claude/skills/oj-api-dev(oj devkit 文档已同步 ${devkitStatus.files.length} 个文件)`);
|
|
176
|
+
else
|
|
187
177
|
report.skipped.push(".claude/skills/oj-api-dev(bin/devkit 缺失,跳过)");
|
|
188
|
-
|
|
178
|
+
if (fs.existsSync(path.join(binDir, "devkit", "global.d.ts")))
|
|
179
|
+
copyIfAbsent(path.join(binDir, "devkit", "global.d.ts"), path.join(destDir, "global.d.ts"), report);
|
|
189
180
|
|
|
190
181
|
// 4b) ojm 模块开发 skill 分发:从 cli 包内置 devkit(prepack 同步的
|
|
191
182
|
// docs/devkit)拷到 .claude/skills/ojm-module-dev/;已存在则幂等跳过。
|
|
@@ -197,7 +188,8 @@ export async function initProject(destDir: string, opts: InitOptions = {}): Prom
|
|
|
197
188
|
// 业务工程拿到模板后自助完成对接要用,不能只留本仓:
|
|
198
189
|
// · `wecom-daikaifa.md` 企业微信代开发(服务商模板):凭据清单 + 用户/开发/部署三视角;
|
|
199
190
|
// · `wecom-internal-app.md` 企业内部应用(自建):五项 corp 凭据 + 验证并接入 + 共存。
|
|
200
|
-
|
|
191
|
+
// · `publish-walkthrough.md` 方案包发布全流程(新手照做版):打包/签名/CDN/安装一条链。
|
|
192
|
+
for (const name of ["SKILL.md", "manual.md", "README.md", "wecom-daikaifa.md", "wecom-internal-app.md", "publish-walkthrough.md"])
|
|
201
193
|
copyIfAbsent(path.join(cliDevkit, name), path.join(ojmSkillDir, name), report);
|
|
202
194
|
}
|
|
203
195
|
else {
|
|
@@ -240,15 +232,22 @@ export async function initProject(destDir: string, opts: InitOptions = {}): Prom
|
|
|
240
232
|
// 与 pnpm-workspace.yaml 同型,改由 init 现场生成
|
|
241
233
|
ensureGitignore(destDir, report);
|
|
242
234
|
|
|
243
|
-
// 5d) 旧布局冲突提示:0.2.54
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
235
|
+
// 5d) 旧布局冲突提示:0.2.54 起因/app 同时承载前端聚合模块与后端模块。
|
|
236
|
+
// init 只补缺不删除——老工程重跑 init 会同时留下旧目录:
|
|
237
|
+
// · src/<name>/web 与 src/app/web 同名路由注册两遍(菜单重复);
|
|
238
|
+
// · src/<name>/api 与 src/app/api/<name> 模块名重复(ojm 扫描直接报错)。
|
|
239
|
+
const mergedWeb = path.join(destDir, "src", "app", "web", "entry.ts");
|
|
240
|
+
const mergedApi = path.join(destDir, "src", "app", "api");
|
|
241
|
+
if (fs.existsSync(mergedWeb) || fs.existsSync(mergedApi)) {
|
|
242
|
+
const appModules = ["home", "login", "personal-center", "notification", "system", "_platform", "auth", "web"];
|
|
243
|
+
const oldWeb = appModules.filter(name => fs.existsSync(path.join(destDir, "src", name, "web", "entry.ts")));
|
|
244
|
+
if (oldWeb.length > 0)
|
|
245
|
+
report.warnings.push(`检测到旧布局的前端模块目录(${oldWeb.map(n => `src/${n}/web`).join("、")})与新聚合模块 src/app/web 并存——路由会重复注册。请手工删除这些旧 web 目录。`);
|
|
246
|
+
const oldApi = appModules.filter(name =>
|
|
247
|
+
fs.existsSync(path.join(destDir, "src", name, "api", "manifest.yaml"))
|
|
248
|
+
&& fs.existsSync(path.join(mergedApi, name, "manifest.yaml")));
|
|
249
|
+
if (oldApi.length > 0)
|
|
250
|
+
report.warnings.push(`检测到旧布局的后端模块目录(${oldApi.map(n => `src/${n}/api`).join("、")})与新位置 src/app/api/<name> 同名——模块名重复会让 ojm 扫描报错。请手工删除这些旧 api 目录。`);
|
|
252
251
|
}
|
|
253
252
|
|
|
254
253
|
console.log(`[ojm] init 完成:${destDir}`);
|
|
@@ -411,9 +410,10 @@ function pruneUnincludedApiPaths(destDir: string, include: Set<string>, report:
|
|
|
411
410
|
// 只裁本次新建的 openapi.json:init 是幂等补缺,不改动既有工程文件
|
|
412
411
|
if (!report.created.includes("openapi.json"))
|
|
413
412
|
return;
|
|
414
|
-
//
|
|
413
|
+
// 白名单里的 api 侧条目决定「脚手架有哪些后端模块」:
|
|
414
|
+
// 平铺 `src/<name>/api` 与聚合容器 `src/app/api/<name>` 都要认。
|
|
415
415
|
const mods = new Set([...include]
|
|
416
|
-
.map(p => /^src\/([^/]+)\/api$/.exec(p)?.[1])
|
|
416
|
+
.map(p => /^src\/([^/]+)\/api$/.exec(p)?.[1] ?? /^src\/[^/]+\/api\/([^/]+)$/.exec(p)?.[1])
|
|
417
417
|
.filter((m): m is string => !!m));
|
|
418
418
|
if (mods.size === 0)
|
|
419
419
|
return;
|
|
@@ -475,21 +475,6 @@ function copyIfAbsent(src: string, dest: string, report: InitReport): void {
|
|
|
475
475
|
report.created.push(path.relative(process.cwd(), dest) || path.basename(dest));
|
|
476
476
|
}
|
|
477
477
|
|
|
478
|
-
/**
|
|
479
|
-
* 同 copyIfAbsent,但**源缺失**时记一条跳过而非抛错。
|
|
480
|
-
*
|
|
481
|
-
* 用于从 bin/devkit 拷第三方版本化内容(两份专题指南):devkit 随用户本地 oj
|
|
482
|
-
* 版本而变,旧版没有新文件属正常版本 skew,不是安装包损坏——缺了只少份文档,
|
|
483
|
-
* 不该让 init 失败。源在而目标已存在仍走 copyIfAbsent 的幂等跳过。
|
|
484
|
-
*/
|
|
485
|
-
function copyIfPresent(src: string, dest: string, report: InitReport): void {
|
|
486
|
-
if (!fs.existsSync(src)) {
|
|
487
|
-
report.skipped.push(`${path.basename(src)}(bin/devkit 无此文件,跳过)`);
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
copyIfAbsent(src, dest, report);
|
|
491
|
-
}
|
|
492
|
-
|
|
493
478
|
/**
|
|
494
479
|
* 新工程 .gitignore:不存在则生成;已存在则幂等跳过(用户规则永不覆盖)。
|
|
495
480
|
*
|
package/src/layout.ts
CHANGED
|
@@ -6,13 +6,16 @@
|
|
|
6
6
|
*
|
|
7
7
|
* 三种布局(互斥):
|
|
8
8
|
* module-root <project>/src/<name>/{api,web} 新布局:一目录一模块(设计 v2)
|
|
9
|
+
* 也支持聚合容器:src/app/{api/<模块>,web}——app 的 web 侧是多根前端模块,
|
|
10
|
+
* api 侧每个子目录各自是独立后端模块(模块名 = 子目录名 = URL 前缀)
|
|
9
11
|
* new <project>/web/src/<name> 历史 uni-dev 布局
|
|
10
12
|
* flat <project>/web/<name> 框架根仓 dogfooding(产物落 build/)
|
|
11
13
|
*
|
|
12
14
|
* module-root 的判定只认「模块根标记文件」而不是单纯的 src/ 存在性:工程里
|
|
13
15
|
* 常有不属于模块的 src/(脚本、共享库),凭目录名猜会误判。
|
|
14
16
|
* `src/<x>/api/manifest.yaml` 与 `src/<x>/web/entry.ts` 是唯一真源(设计 C-6:
|
|
15
|
-
*
|
|
17
|
+
* 一侧可缺省,两侧皆无则不是模块)。聚合容器下后端模块根是 `src/app/api/<x>/manifest.yaml`
|
|
18
|
+
* (见 collectApiRoots)。
|
|
16
19
|
*
|
|
17
20
|
* 产物根(2026-10-22 起收进工程根单一 dist/,原 {web,api}/dist 与源码混树):
|
|
18
21
|
* dist/api oj 扁平 release 根(<name>-<version>/ 同层兄弟 + manifests.yaml),三种布局一致
|
|
@@ -31,7 +34,7 @@ export type LayoutKind = "module-root" | "new" | "flat";
|
|
|
31
34
|
/** 模块一侧的可缺省组合:两侧皆无不是模块(设计 C-6) */
|
|
32
35
|
export interface ModuleSides {
|
|
33
36
|
name: string
|
|
34
|
-
/**
|
|
37
|
+
/** 后端源码目录(`src/<name>/api` 或聚合容器 `src/app/api/<name>`;无此侧为 undefined) */
|
|
35
38
|
apiSrc?: string
|
|
36
39
|
/** 前端源码目录 `<project>/src/<name>/web`(无此侧为 undefined) */
|
|
37
40
|
webSrc?: string
|
|
@@ -82,13 +85,44 @@ function isFile(p: string): boolean {
|
|
|
82
85
|
* 坏目录只在 module-root 被判定为当前布局后才报错(见 resolveLayout),
|
|
83
86
|
* 否则一个恰好带 `src/utils/api/` 的历史工程会被误伤。
|
|
84
87
|
*/
|
|
88
|
+
/** 递归扫描时跳过的目录名(产物 / 依赖 / 生成物,均不可能承载模块根) */
|
|
89
|
+
const SCAN_SKIP_DIRS = new Set(["node_modules", "dist", "build", "__client", ".cache", ".git"]);
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 收集一个容器目录下的后端模块根。
|
|
93
|
+
*
|
|
94
|
+
* 两种形态并存(oj 的 `--module-root` 支持任意深度):
|
|
95
|
+
* · `<container>/api/manifest.yaml` —— 模块根在 api 自身(src/<name>/api)
|
|
96
|
+
* · `<container>/api/<name>/manifest.yaml` —— 聚合容器下的子模块(src/app/api/system)
|
|
97
|
+
*
|
|
98
|
+
* 后者用于「一个应用目录承载多个业务域」:src/app 的 web 侧是多根聚合模块,
|
|
99
|
+
* api 侧按业务域分成 system / notification / … 各自仍是独立模块(模块名 = 目录名
|
|
100
|
+
* = URL 前缀,故不能并成一个 app 模块)。命中模块根即停,不再往 api 内部递归。
|
|
101
|
+
*/
|
|
102
|
+
function collectApiRoots(container: string, out: string[]): void {
|
|
103
|
+
const apiDir = path.join(container, "api");
|
|
104
|
+
if (isFile(path.join(apiDir, "manifest.yaml"))) {
|
|
105
|
+
out.push(apiDir);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (!isDir(apiDir))
|
|
109
|
+
return;
|
|
110
|
+
for (const ent of fs.readdirSync(apiDir, { withFileTypes: true })
|
|
111
|
+
.filter(e => e.isDirectory() && !e.name.startsWith(".") && !SCAN_SKIP_DIRS.has(e.name))
|
|
112
|
+
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))) {
|
|
113
|
+
if (isFile(path.join(apiDir, ent.name, "manifest.yaml")))
|
|
114
|
+
out.push(path.join(apiDir, ent.name));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
85
118
|
function scanModuleRoot(projectRoot: string): { modules: ModuleSides[], broken: string[] } | null {
|
|
86
119
|
const srcDir = path.join(projectRoot, "src");
|
|
87
120
|
if (!isDir(srcDir))
|
|
88
121
|
return null;
|
|
89
122
|
|
|
90
|
-
const
|
|
123
|
+
const byName = new Map<string, ModuleSides>();
|
|
91
124
|
const broken: string[] = [];
|
|
125
|
+
const apiOwners = new Map<string, string>();
|
|
92
126
|
// 按名字排序:readdir 的顺序取决于文件系统与创建历史(同一份源码在不同机器上可能不同),
|
|
93
127
|
// 而枚举顺序会进入构建顺序与产物内容——不排序则「同布局两次构建 sha256 相等」无法保证。
|
|
94
128
|
const entries = fs.readdirSync(srcDir, { withFileTypes: true })
|
|
@@ -96,22 +130,41 @@ function scanModuleRoot(projectRoot: string): { modules: ModuleSides[], broken:
|
|
|
96
130
|
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
97
131
|
for (const ent of entries) {
|
|
98
132
|
const dir = path.join(srcDir, ent.name);
|
|
99
|
-
const apiDir = path.join(dir, "api");
|
|
100
133
|
const webDir = path.join(dir, "web");
|
|
101
|
-
const hasApi = isFile(path.join(apiDir, "manifest.yaml"));
|
|
102
134
|
const hasWeb = isFile(path.join(webDir, "entry.ts"));
|
|
103
|
-
|
|
135
|
+
const apiRoots: string[] = [];
|
|
136
|
+
collectApiRoots(dir, apiRoots);
|
|
137
|
+
if (!hasWeb && apiRoots.length === 0) {
|
|
104
138
|
// 只把「有 api/ 或 web/ 子目录但缺标记」算坏目录;纯共享库目录(无两者)静默跳过
|
|
105
|
-
if (isDir(
|
|
139
|
+
if (isDir(path.join(dir, "api")) || isDir(webDir))
|
|
106
140
|
broken.push(ent.name);
|
|
107
141
|
continue;
|
|
108
142
|
}
|
|
109
|
-
|
|
110
|
-
name: ent.name,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
143
|
+
if (hasWeb)
|
|
144
|
+
byName.set(ent.name, { name: ent.name, webSrc: webDir });
|
|
145
|
+
for (const apiDir of apiRoots) {
|
|
146
|
+
// 模块名:平铺 src/system/api → system(api 的父目录);
|
|
147
|
+
// 聚合 src/app/api/system → system(api 下方那层目录本身)。
|
|
148
|
+
const name = path.basename(apiDir) === "api"
|
|
149
|
+
? path.basename(path.dirname(apiDir))
|
|
150
|
+
: path.basename(apiDir);
|
|
151
|
+
const prev = apiOwners.get(name);
|
|
152
|
+
if (prev) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`[ojm] 后端模块名重复:${name} 同时出现在 ${path.relative(projectRoot, prev)} 与 `
|
|
155
|
+
+ `${path.relative(projectRoot, apiDir)}。模块名即 URL 前缀,必须唯一——`
|
|
156
|
+
+ "删掉旧布局目录(或改名)后重试。",
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
apiOwners.set(name, apiDir);
|
|
160
|
+
const hit = byName.get(name);
|
|
161
|
+
if (hit)
|
|
162
|
+
hit.apiSrc = apiDir;
|
|
163
|
+
else
|
|
164
|
+
byName.set(name, { name, apiSrc: apiDir });
|
|
165
|
+
}
|
|
114
166
|
}
|
|
167
|
+
const modules = [...byName.values()].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
115
168
|
return { modules, broken };
|
|
116
169
|
}
|
|
117
170
|
|
|
@@ -244,7 +297,7 @@ export function legacyApiModuleRoots(projectRoot: string): ApiModuleRoot[] {
|
|
|
244
297
|
* 为什么不直接 walk `apiModulesSrc(layout)`:module-root 布局的树根是 `src`,模块的
|
|
245
298
|
* api 侧在 `<src>/<name>/api`,中间那层 `api` 是**布局分隔符、不是路由段**。拿 `src`
|
|
246
299
|
* 当扫描根会双重出错——路由镜像多出 `api` 段(`system/api/users` ≠ 真实 `system/users`),
|
|
247
|
-
* 且 `src/<name>/web/__client/
|
|
300
|
+
* 且 `src/<name>/web/__client/index.ts` 这种前端生成物也会被当 handler 扫进来。
|
|
248
301
|
*
|
|
249
302
|
* module-root → 按 `<name>/api/manifest.yaml` 判模块(与 build 同源,name 取 manifest);
|
|
250
303
|
* 历史布局 → `<project>/api/src` 的一级目录即模块。
|
|
@@ -267,9 +320,12 @@ export function moduleDir(layout: ProjectLayout, name: string): string {
|
|
|
267
320
|
* 历史布局为 `<project>/api/src/<name>`——不在 modulesSrc 之下,必须走 projectRoot。
|
|
268
321
|
*/
|
|
269
322
|
export function apiSrcOf(layout: ProjectLayout, name: string): string {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
323
|
+
if (layout.kind !== "module-root")
|
|
324
|
+
return path.join(layout.projectRoot, "api", "src", name);
|
|
325
|
+
// 聚合容器(src/app/api/<name>)与平铺(src/<name>/api)并存,以扫描结果为准;
|
|
326
|
+
// 找不到时回落旧口径,保证报错信息里的路径仍指向「本该在的位置」。
|
|
327
|
+
const hit = scanModules(layout).find(m => m.name === name)?.apiSrc;
|
|
328
|
+
return hit ?? path.join(layout.modulesSrc, name, "api");
|
|
273
329
|
}
|
|
274
330
|
|
|
275
331
|
/**
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// BDD:`ojm build` 末尾用户方案摘要(计划 docs/plans/20261103-0925-ojm-build-solution-summary.md)。
|
|
2
|
+
// 摘要只报告不裁决:判定口径与 --bundle 打包对象同源(模块级契约 ∪ 根契约,减内置名单);
|
|
3
|
+
// log 注入断言,夹具只摆契约/名单/构建结果三样,不需要真实工程结构。
|
|
4
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
9
|
+
import { summarizeBuiltSolutions } from "../../bundle";
|
|
10
|
+
|
|
11
|
+
let project: string;
|
|
12
|
+
let lines: string[];
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
project = mkdtempSync(join(tmpdir(), "ojm-build-summary-"));
|
|
16
|
+
lines = [];
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
rmSync(project, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/** 模块级契约:src/<name>/solution.yaml(+ web/entry.ts 保持真实模块目录形态) */
|
|
24
|
+
function moduleContract(name: string): void {
|
|
25
|
+
const dir = join(project, "src", name);
|
|
26
|
+
mkdirSync(join(dir, "web"), { recursive: true });
|
|
27
|
+
writeFileSync(join(dir, "web", "entry.ts"), "export default {};\n");
|
|
28
|
+
writeFileSync(join(dir, "solution.yaml"), `name: ${name}\nversion: 1.0.0\nsummary: ${name}\n`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function builtinList(names: string[]): void {
|
|
32
|
+
mkdirSync(join(project, "src"), { recursive: true });
|
|
33
|
+
writeFileSync(join(project, "src", ".ojm-builtin.json"), JSON.stringify({ modules: names }));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function summaryOf(built: { name: string, version: string }[]): string[] {
|
|
37
|
+
summarizeBuiltSolutions(project, built, l => lines.push(l));
|
|
38
|
+
return lines;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe("summarizeBuiltSolutions", () => {
|
|
42
|
+
it("场景:全部构建成功 → 一行列出可打包方案 + 一行列出内置模块", () => {
|
|
43
|
+
moduleContract("crm");
|
|
44
|
+
moduleContract("erp");
|
|
45
|
+
builtinList(["home", "login"]);
|
|
46
|
+
const out = summaryOf([
|
|
47
|
+
{ name: "crm", version: "1.0.0" },
|
|
48
|
+
{ name: "erp", version: "0.2.0" },
|
|
49
|
+
{ name: "home", version: "9.9.9" },
|
|
50
|
+
{ name: "login", version: "9.9.9" },
|
|
51
|
+
]);
|
|
52
|
+
expect(out.some(l => l.includes("用户方案 2 份已构建") && l.includes("crm@1.0.0") && l.includes("erp@0.2.0"))).toBe(true);
|
|
53
|
+
expect(out.some(l => l.includes("框架自带模块 2 份已构建"))).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("场景:方案没进构建 → 点名未构建方案与排查方向", () => {
|
|
57
|
+
moduleContract("crm");
|
|
58
|
+
moduleContract("erp");
|
|
59
|
+
builtinList(["home"]);
|
|
60
|
+
const out = summaryOf([
|
|
61
|
+
{ name: "crm", version: "1.0.0" },
|
|
62
|
+
{ name: "home", version: "9.9.9" },
|
|
63
|
+
]);
|
|
64
|
+
expect(out.some(l => l.includes("crm@1.0.0"))).toBe(true);
|
|
65
|
+
expect(out.filter(l => l.includes("erp")).join("\n")).toContain("未构建");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("场景:零用户方案 → 提示 ojm new", () => {
|
|
69
|
+
moduleContract("home");
|
|
70
|
+
builtinList(["home"]);
|
|
71
|
+
const out = summaryOf([{ name: "home", version: "9.9.9" }]);
|
|
72
|
+
expect(out.some(l => l.includes("用户方案 0 份") && l.includes("ojm new"))).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("场景:非内置模块缺契约 → 警告不会被打包", () => {
|
|
76
|
+
builtinList(["home"]);
|
|
77
|
+
const out = summaryOf([
|
|
78
|
+
{ name: "foo", version: "0.1.0" },
|
|
79
|
+
{ name: "home", version: "9.9.9" },
|
|
80
|
+
]);
|
|
81
|
+
expect(out.some(l => l.includes("缺方案契约") && l.includes("foo@0.1.0"))).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("场景:旧单方案工程(根契约、无 src/ 树)→ 列出根契约方案,不误报缺契约", () => {
|
|
85
|
+
writeFileSync(join(project, "manifest.yaml"), "name: demo-solution\nversion: 0.1.0\nsummary: 旧写法\n");
|
|
86
|
+
const out = summaryOf([{ name: "demo-solution", version: "0.1.0" }]);
|
|
87
|
+
expect(out.some(l => l.includes("用户方案 1 份已构建") && l.includes("demo-solution@0.1.0"))).toBe(true);
|
|
88
|
+
expect(out.every(l => !l.includes("缺方案契约"))).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("场景:新脚手架形态(内置集中 src/app,无用户方案)→ 0 份提示,src/app 不被误判", () => {
|
|
92
|
+
// 脚手架 init 后:src/ 下只有 app(平台聚合树,无契约)与 oj-market,均在内置名单
|
|
93
|
+
mkdirSync(join(project, "src", "app", "web"), { recursive: true });
|
|
94
|
+
writeFileSync(join(project, "src", "app", "web", "entry.ts"), "export default {};\n");
|
|
95
|
+
mkdirSync(join(project, "src", "oj-market", "web"), { recursive: true });
|
|
96
|
+
writeFileSync(join(project, "src", "oj-market", "web", "entry.ts"), "export default {};\n");
|
|
97
|
+
builtinList(["app", "oj-market", "_platform", "auth", "notification", "personal-center", "system", "web"]);
|
|
98
|
+
const out = summaryOf([
|
|
99
|
+
{ name: "app", version: "0.2.56" },
|
|
100
|
+
{ name: "oj-market", version: "0.2.56" },
|
|
101
|
+
]);
|
|
102
|
+
expect(out.some(l => l.includes("用户方案 0 份") && l.includes("ojm new"))).toBe(true);
|
|
103
|
+
expect(out.some(l => l.includes("框架自带模块 2 份已构建"))).toBe(true);
|
|
104
|
+
expect(out.every(l => !l.includes("缺方案契约"))).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
});
|