@oj-module/cli 0.2.64 → 0.2.67
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/bin/.oj-version +1 -0
- package/bin/oj +4 -0
- package/devkit/README.md +18 -4
- package/devkit/SKILL.md +35 -3
- package/devkit/framework-dev.md +22 -11
- package/devkit/manual.md +161 -68
- package/devkit/menu-rules.md +4 -1
- package/devkit/publish-walkthrough.md +110 -37
- package/devkit/wecom-internal-app.md +7 -2
- package/devkit/wecom-multi-corp-links.md +163 -0
- package/package.json +3 -2
- package/shell-dist/assets/ahooks.2l850ymmh0.js +18 -0
- package/shell-dist/assets/antd-es-locale-zh_CN.1gjjsvxbf0.js +1 -0
- package/shell-dist/assets/antd-img-crop.196rkty480.js +99 -0
- package/shell-dist/assets/antd-locale-en_US.561tozx860.js +1 -0
- package/shell-dist/assets/antd-locale-zh_CN.1cmpmgzh70.js +1 -0
- package/shell-dist/assets/antd.uw1coq15l0.js +348 -0
- package/shell-dist/assets/clsx.jotilrt2e0.js +1 -0
- package/shell-dist/assets/contract-errors.2o17vz9u50.js +1 -0
- package/shell-dist/assets/contract.3h327o3mj0.js +57 -0
- package/shell-dist/assets/cssinjs.atgmhan7m0.js +3 -0
- package/shell-dist/assets/dayjs-plugin-advancedFormat.3vsyd2eis0.js +1 -0
- package/shell-dist/assets/dayjs-plugin-customParseFormat.13xhghv1d0.js +1 -0
- package/shell-dist/assets/dayjs-plugin-isoWeek.1fn1tw82p0.js +1 -0
- package/shell-dist/assets/dayjs-plugin-localeData.2rwbjp9nk0.js +1 -0
- package/shell-dist/assets/dayjs-plugin-quarterOfYear.2oqo1lcx80.js +1 -0
- package/shell-dist/assets/dayjs-plugin-relativeTime.2pqs3et4y0.js +1 -0
- package/shell-dist/assets/dayjs-plugin-weekOfYear.gvn9iph080.js +1 -0
- package/shell-dist/assets/dayjs-plugin-weekYear.2u78l3ex10.js +1 -0
- package/shell-dist/assets/dayjs-plugin-weekday.2d56a4tml0.js +1 -0
- package/shell-dist/assets/dayjs.pa60l6wjo0.js +1 -0
- package/shell-dist/assets/dnd-kit-core.1xyreig6r0.js +5 -0
- package/shell-dist/assets/dnd-kit-sortable.s94qciuft0.js +1 -0
- package/shell-dist/assets/dnd-kit-utilities.1umw4xphb0.js +1 -0
- package/shell-dist/assets/i18next.33zqofapj0.js +1 -0
- package/shell-dist/assets/icons-es-components-Context.365qmflwg0.js +1 -0
- package/shell-dist/assets/icons.159wjwals0.js +57 -0
- package/shell-dist/assets/{index-xZhmujjM.js → index.6alqyx6ae0.js} +21 -1
- package/shell-dist/assets/jsx-dev-runtime.3uibuc82f0.js +14 -0
- package/shell-dist/assets/jsx-runtime.h40xu4o5b0.js +14 -0
- package/shell-dist/assets/keepalive-for-react.xenu5vam70.js +1 -0
- package/shell-dist/assets/kit.37kr8ui390.js +1 -0
- package/shell-dist/assets/ky.1cgch7gu60.js +6 -0
- package/shell-dist/assets/motion-react.pjsxytvjs0.js +12 -0
- package/shell-dist/assets/motion.21tciqutg0.js +4 -0
- package/shell-dist/assets/nprogress.2nz76n9ny0.js +7 -0
- package/shell-dist/assets/ojm-dayjs-locale-zh-cn.3vr3beiyp0.js +9 -0
- package/shell-dist/assets/pinyin-pro.3v0mnxczn0.js +1 -0
- package/shell-dist/assets/pro-components.2kyot1vcl0.js +83 -0
- package/shell-dist/assets/react-countup.2x8idnr8y0.js +10 -0
- package/shell-dist/assets/react-dom-client.ick0v05ob0.js +43 -0
- package/shell-dist/assets/react-dom.2so9uxg3f0.js +22 -0
- package/shell-dist/assets/react-error-boundary.2dncdfj3b0.js +1 -0
- package/shell-dist/assets/react-i18next.auhvjhqv00.js +1 -0
- package/shell-dist/assets/react-jss.1ei6dr9e00.js +29 -0
- package/shell-dist/assets/react-query.3rln45ow00.js +1 -0
- package/shell-dist/assets/react-router-dom.3c6zld8h40.js +44 -0
- package/shell-dist/assets/react-router.2h9rebcv50.js +56 -0
- package/shell-dist/assets/react.1ew63mwts0.js +14 -0
- package/shell-dist/assets/runtime.2dgstpjsu0.js +21702 -0
- package/shell-dist/assets/simplebar-react.2g739uvai0.js +1 -0
- package/shell-dist/assets/spin-delay.yjeyj6iwp0.js +9 -0
- package/shell-dist/assets/tailwind-merge.125ehiqjw0.js +1 -0
- package/shell-dist/assets/use-sync-external-store-shim.1nz1pi9bb0.js +22 -0
- package/shell-dist/assets/zustand-middleware.1xaxjueme0.js +6 -0
- package/shell-dist/assets/zustand-react-shallow.pybayv1hx0.js +1 -0
- package/shell-dist/assets/zustand-react.9fsl9brxy0.js +1 -0
- package/shell-dist/assets/zustand-shallow.6cz0yyv9x0.js +1 -0
- package/shell-dist/assets/zustand-vanilla-shallow.3424monv50.js +1 -0
- package/shell-dist/assets/zustand-vanilla.1mo3ka3g30.js +1 -0
- package/shell-dist/assets/zustand.3gjgzlnqo0.js +1 -0
- package/shell-dist/index.html +67 -118
- package/shell-dist/versions.json +3 -3
- package/src/__tests__/publish-keygen.test.ts +157 -0
- package/src/__tests__/templates-migration-schema-parity.test.ts +540 -0
- package/src/__tests__/templates-schema-columns.test.ts +15 -2
- package/src/__tests__/templates-tenant-scope.test.ts +6 -2
- package/src/bundle.ts +8 -3
- package/src/cli.ts +17 -5
- package/src/dev.ts +15 -10
- package/src/esm-exports.ts +152 -70
- package/src/install.ts +11 -4
- package/src/keygen.ts +54 -0
- package/src/lifecycle/__tests__/bundle.test.ts +21 -3
- package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/manifest.yaml +1 -1
- package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/migrations/0001__init.sql +2 -2
- package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/schema.yaml +5 -2
- package/src/lifecycle/__tests__/fixtures/fixture-valid/manifest.yaml +1 -1
- package/src/lifecycle/__tests__/manifest-contract.test.ts +75 -3
- package/src/lifecycle/__tests__/smoke.test.ts +2 -2
- package/src/manifest-contract.ts +91 -8
- package/src/market/bootstrap.ts +26 -2
- package/src/market/command.ts +1 -0
- package/src/market/publish.ts +5 -1
- package/src/market/walkthrough.ts +24 -8
- package/src/shared-deps.ts +69 -19
- package/src/site-base.ts +4 -0
- package/src/site.ts +2 -0
- package/src/usage.ts +8 -4
- package/src/vendor.ts +5 -1
- package/templates/config.yaml +4 -2
- package/templates/init.include.yaml +8 -3
- package/templates/openapi.json +460 -1
- package/templates/src/app/api/_platform/_lifecycle/compensate.ts +136 -0
- package/templates/src/app/api/_platform/_lifecycle/installer.ts +71 -22
- package/templates/src/app/api/_platform/_lifecycle/lock.ts +23 -0
- package/templates/src/app/api/_platform/_lifecycle/steps/deploy-web.ts +28 -0
- package/templates/src/app/api/_platform/_lifecycle/steps/preflight.ts +46 -0
- package/templates/src/app/api/_platform/_lifecycle/steps/reload.ts +100 -36
- package/templates/src/app/api/_platform/_lifecycle/steps/smoke.ts +7 -15
- package/templates/src/app/api/_platform/_lifecycle/text-edit.ts +71 -19
- package/templates/src/app/api/_platform/_lifecycle/types.ts +26 -2
- package/templates/src/app/api/_platform/_lifecycle/uninstall.ts +93 -14
- package/templates/src/app/api/_platform/_shared/service/domain/settings.ts +8 -4
- package/templates/src/app/api/_platform/manifest.yaml +1 -0
- package/templates/src/app/api/_platform/market/api.ts +235 -10
- package/templates/src/app/api/_platform/migrations/0001__baseline.mysql.sql +178 -0
- package/templates/src/app/api/_platform/migrations/0001__baseline.pg.sql +173 -0
- package/templates/src/app/api/_platform/migrations/0001__baseline.sql +188 -0
- package/templates/src/app/api/_platform/migrations/0002__market_release.mysql.sql +17 -0
- package/templates/src/app/api/_platform/migrations/0002__market_release.pg.sql +16 -0
- package/templates/src/app/api/_platform/migrations/0002__market_release.sql +33 -0
- package/templates/src/app/api/_platform/schema.yaml +18 -0
- package/templates/src/app/api/_platform/seed.mysql.sql +10 -0
- package/templates/src/app/api/_platform/seed.pg.sql +10 -0
- package/templates/src/app/api/auth/_shared/wecom/client.ts +24 -4
- package/templates/src/app/api/home/migrations/0001__create_home_tables.mysql.sql +14 -0
- package/templates/src/app/api/home/migrations/0001__create_home_tables.pg.sql +14 -0
- package/templates/src/app/api/home/migrations/0002__tenant.pg.sql +4 -0
- package/templates/src/app/api/home/seed.mysql.sql +71 -0
- package/templates/src/app/api/home/seed.pg.sql +71 -0
- package/templates/src/app/api/notification/_shared/notify.ts +6 -6
- package/templates/src/app/api/notification/migrations/0001__create_notifications.mysql.sql +11 -0
- package/templates/src/app/api/notification/migrations/0001__create_notifications.pg.sql +11 -0
- package/templates/src/app/api/notification/migrations/0002__notifications_user.mysql.sql +8 -0
- package/templates/src/app/api/notification/migrations/0003__notifications_broadcast.mysql.sql +21 -0
- package/templates/src/app/api/notification/migrations/0003__notifications_broadcast.pg.sql +22 -0
- package/templates/src/app/api/notification/migrations/0004__notifications_broadcast_hidden.mysql.sql +10 -0
- package/templates/src/app/api/notification/migrations/0004__notifications_broadcast_hidden.pg.sql +11 -0
- package/templates/src/app/api/notification/migrations/0005__tenant.pg.sql +6 -0
- package/templates/src/app/api/notification/seed.mysql.sql +8 -0
- package/templates/src/app/api/notification/seed.pg.sql +11 -0
- package/templates/src/app/api/publish/_shared/identity.ts +69 -0
- package/templates/src/app/api/publish/api.ts +349 -0
- package/templates/src/app/api/publish/manifest.yaml +8 -0
- package/templates/src/app/api/publish/migrations/0001__publish_credential.mysql.sql +23 -0
- package/templates/src/app/api/publish/migrations/0001__publish_credential.pg.sql +23 -0
- package/templates/src/app/api/publish/migrations/0001__publish_credential.sql +39 -0
- package/templates/src/app/api/publish/schema.yaml +33 -0
- package/templates/src/app/api/system/login-methods/api.ts +2 -1
- package/templates/src/app/web/_platform/__client/api.schemas.ts +15 -0
- package/templates/src/app/web/_platform/__client/index.ts +25 -1
- package/templates/src/app/web/entry.ts +9 -5
- package/templates/src/app/web/home/pages/components/bar-chart.tsx +5 -3
- package/templates/src/app/web/home/pages/components/echarts-setup.ts +30 -0
- package/templates/src/app/web/home/pages/components/line-chart.tsx +5 -3
- package/templates/src/app/web/home/pages/components/pie-chart.tsx +6 -3
- package/templates/src/app/web/home/pages/index.tsx +3 -2
- package/templates/src/app/web/home/routes.ts +8 -2
- package/templates/src/app/web/login/locales/en-US.json +7 -1
- package/templates/src/app/web/login/locales/zh-CN.json +7 -1
- package/templates/src/app/web/login/pages/login.tsx +79 -86
- package/templates/src/app/web/login/routes.ts +54 -1
- package/templates/src/app/web/personal-center/locales/en-US.json +1 -0
- package/templates/src/app/web/personal-center/locales/zh-CN.json +1 -0
- package/templates/src/app/web/personal-center/pages/my-profile/index.tsx +5 -0
- package/templates/src/app/web/system/__client/api.schemas.ts +2 -1
- package/templates/src/app/web/system/locales/en-US.json +1 -8
- package/templates/src/app/web/system/locales/zh-CN.json +1 -8
- package/templates/src/app/web/system/pages/settings/index.tsx +17 -4
- package/templates/src/app/web/system/routes.ts +0 -6
- package/templates/src/oj-market/web/__tests__/CardGrid.test.tsx +33 -0
- package/templates/src/oj-market/web/__tests__/DetailDrawer.test.tsx +69 -0
- package/templates/src/oj-market/web/__tests__/marketClient.fake.ts +7 -1
- package/templates/src/oj-market/web/api/client.ts +3 -0
- package/templates/src/oj-market/web/api/defaultClient.ts +5 -0
- package/templates/src/oj-market/web/components/CardItem.tsx +44 -1
- package/templates/src/oj-market/web/components/DetailDrawer.tsx +17 -1
- package/templates/src/oj-market/web/entry.ts +31 -2
- package/templates/src/oj-market/web/locales/en-US.json +5 -1
- package/templates/src/oj-market/web/locales/zh-CN.json +5 -1
- package/templates/src/oj-market/web/publish/__client/api.schemas.ts +61 -0
- package/templates/src/oj-market/web/publish/__client/index.ts +164 -0
- package/templates/src/oj-market/web/publish/locales/en-US.json +80 -0
- package/templates/src/oj-market/web/publish/locales/zh-CN.json +80 -0
- package/templates/src/oj-market/web/publish/pages/history.tsx +112 -0
- package/templates/src/oj-market/web/publish/pages/index.tsx +196 -0
- package/templates/src/oj-market/web/publish/pages/model.ts +52 -0
- package/templates/src/oj-market/web/publish/pages/token-modal.tsx +82 -0
- package/templates/src/oj-market/web/publish/routes.ts +41 -0
- package/templates/src/oj-market/web/types.ts +30 -0
- package/templates/tests/lifecycle/compensate.spec.ts +207 -0
- package/templates/tests/lifecycle/deploy-smoke.spec.ts +38 -14
- package/templates/tests/lifecycle/installer.spec.ts +28 -9
- package/templates/tests/lifecycle/market-api.spec.ts +164 -12
- package/templates/tests/lifecycle/preflight.spec.ts +68 -0
- package/templates/tests/lifecycle/role-title-translate.spec.ts +2 -32
- package/templates/tests/lifecycle/text-edit.spec.ts +45 -27
- package/templates/tests/lifecycle/uninstall.spec.ts +36 -0
- package/templates/tests/publish-credential.spec.ts +354 -0
- package/templates/tests/publish-identity.spec.ts +44 -0
- package/templates/tests/wecom-client-timeout.spec.ts +33 -3
- package/templates/tests/wecom-landing.spec.ts +63 -1
- package/templates/tsconfig.paths.json +1 -0
- package/templates/web.config.ts +6 -4
- package/vendor/host-versions.json +3 -3
- package/shell-dist/assets/ahooks.js +0 -6909
- package/shell-dist/assets/antd-es-locale-zh_CN.js +0 -257
- package/shell-dist/assets/antd-img-crop.js +0 -1495
- package/shell-dist/assets/antd-locale-en_US.js +0 -374
- package/shell-dist/assets/antd-locale-zh_CN.js +0 -379
- package/shell-dist/assets/antd.js +0 -106874
- package/shell-dist/assets/clsx.js +0 -33
- package/shell-dist/assets/contract-errors.js +0 -30
- package/shell-dist/assets/contract.js +0 -18900
- package/shell-dist/assets/cssinjs.js +0 -2595
- package/shell-dist/assets/dayjs-plugin-advancedFormat.js +0 -98
- package/shell-dist/assets/dayjs-plugin-customParseFormat.js +0 -173
- package/shell-dist/assets/dayjs-plugin-isoWeek.js +0 -77
- package/shell-dist/assets/dayjs-plugin-localeData.js +0 -126
- package/shell-dist/assets/dayjs-plugin-quarterOfYear.js +0 -77
- package/shell-dist/assets/dayjs-plugin-relativeTime.js +0 -92
- package/shell-dist/assets/dayjs-plugin-weekOfYear.js +0 -73
- package/shell-dist/assets/dayjs-plugin-weekYear.js +0 -63
- package/shell-dist/assets/dayjs-plugin-weekday.js +0 -63
- package/shell-dist/assets/dayjs.js +0 -326
- package/shell-dist/assets/dnd-kit-core.js +0 -3601
- package/shell-dist/assets/dnd-kit-sortable.js +0 -717
- package/shell-dist/assets/dnd-kit-utilities.js +0 -340
- package/shell-dist/assets/i18next.js +0 -2321
- package/shell-dist/assets/icons-es-components-Context.js +0 -20
- package/shell-dist/assets/icons.js +0 -34961
- package/shell-dist/assets/jsx-dev-runtime.js +0 -75
- package/shell-dist/assets/jsx-runtime.js +0 -97
- package/shell-dist/assets/keepalive-for-react.js +0 -283
- package/shell-dist/assets/kit.js +0 -151
- package/shell-dist/assets/ky.js +0 -1427
- package/shell-dist/assets/motion-react.js +0 -13589
- package/shell-dist/assets/motion.js +0 -9959
- package/shell-dist/assets/nprogress.js +0 -314
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-BarsOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-CalendarOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-CaretDownFilled.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-CaretDownOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-CaretUpOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-CheckCircleFilled.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-CheckOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-ClockCircleOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-CloseCircleFilled.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-CloseOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-CopyOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-DeleteOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-DoubleLeftOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-DoubleRightOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-DownOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-DownloadOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-EditOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-EllipsisOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-EnterOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-ExclamationCircleFilled.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-EyeInvisibleOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-EyeOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-FileOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-FileTextOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-FilterFilled.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-FolderOpenOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-FolderOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-HolderOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-InfoCircleFilled.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-LeftOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-LoadingOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-MinusOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-MinusSquareOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-PaperClipOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-PictureOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-PlusOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-PlusSquareOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-QuestionCircleOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-ReloadOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-RightOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-RotateLeftOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-RotateRightOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-SearchOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-StarFilled.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-SwapOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-SwapRightOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-UpOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-VerticalAlignTopOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-WarningFilled.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-ZoomInOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-ZoomOutOutlined.js +0 -7
- package/shell-dist/assets/ojm-dayjs-locale-zh-cn.js +0 -81
- package/shell-dist/assets/pinyin-pro.js +0 -25175
- package/shell-dist/assets/pro-components.js +0 -44627
- package/shell-dist/assets/react-countup.js +0 -525
- package/shell-dist/assets/react-dom-client.js +0 -12134
- package/shell-dist/assets/react-dom.js +0 -266
- package/shell-dist/assets/react-error-boundary.js +0 -141
- package/shell-dist/assets/react-i18next.js +0 -1773
- package/shell-dist/assets/react-jss.js +0 -4089
- package/shell-dist/assets/react-query.js +0 -3072
- package/shell-dist/assets/react-router-dom.js +0 -9927
- package/shell-dist/assets/react-router.js +0 -14584
- package/shell-dist/assets/react.js +0 -590
- package/shell-dist/assets/runtime.js +0 -28311
- package/shell-dist/assets/simplebar-react.js +0 -1119
- package/shell-dist/assets/spin-delay.js +0 -111
- package/shell-dist/assets/tailwind-merge.js +0 -3364
- package/shell-dist/assets/use-sync-external-store-shim.js +0 -130
- package/shell-dist/assets/zustand-middleware.js +0 -513
- package/shell-dist/assets/zustand-react-shallow.js +0 -27
- package/shell-dist/assets/zustand-react.js +0 -39
- package/shell-dist/assets/zustand-shallow.js +0 -22
- package/shell-dist/assets/zustand-vanilla-shallow.js +0 -71
- package/shell-dist/assets/zustand-vanilla.js +0 -40
- package/shell-dist/assets/zustand.js +0 -32
- package/templates/seed.mysql.sql +0 -6
- package/templates/seed.pg.sql +0 -6
- package/templates/src/app/api/_platform/migrations/0001__create_users.mysql.sql +0 -9
- package/templates/src/app/api/_platform/migrations/0001__create_users.pg.sql +0 -9
- package/templates/src/app/api/_platform/migrations/0001__create_users.sql +0 -7
- package/templates/src/app/api/_platform/migrations/0002__add_users_avatar_base64.mysql.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0002__add_users_avatar_base64.sql +0 -2
- package/templates/src/app/api/_platform/migrations/0003__users_source_display_name.mysql.sql +0 -5
- package/templates/src/app/api/_platform/migrations/0003__users_source_display_name.sql +0 -5
- package/templates/src/app/api/_platform/migrations/0004__sys_rbac.mysql.sql +0 -70
- package/templates/src/app/api/_platform/migrations/0004__sys_rbac.pg.sql +0 -67
- package/templates/src/app/api/_platform/migrations/0004__sys_rbac.sql +0 -67
- package/templates/src/app/api/_platform/migrations/0005__users_must_change.mysql.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0005__users_must_change.sql +0 -2
- package/templates/src/app/api/_platform/migrations/0006__users_is_forbidden.mysql.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0006__users_is_forbidden.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0007__users_email.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0008__sys_refresh_token.mysql.sql +0 -10
- package/templates/src/app/api/_platform/migrations/0008__sys_refresh_token.pg.sql +0 -10
- package/templates/src/app/api/_platform/migrations/0008__sys_refresh_token.sql +0 -11
- package/templates/src/app/api/_platform/migrations/0009__sys_throttle.mysql.sql +0 -12
- package/templates/src/app/api/_platform/migrations/0009__sys_throttle.pg.sql +0 -12
- package/templates/src/app/api/_platform/migrations/0009__sys_throttle.sql +0 -13
- package/templates/src/app/api/_platform/migrations/0010__sys_solution_route.mysql.sql +0 -17
- package/templates/src/app/api/_platform/migrations/0010__sys_solution_route.pg.sql +0 -16
- package/templates/src/app/api/_platform/migrations/0010__sys_solution_route.sql +0 -18
- package/templates/src/app/api/_platform/migrations/0011__sys_setting_group_label.mysql.sql +0 -2
- package/templates/src/app/api/_platform/migrations/0011__sys_setting_group_label.sql +0 -4
- package/templates/src/app/api/_platform/migrations/0012__market_history.mysql.sql +0 -13
- package/templates/src/app/api/_platform/migrations/0012__market_history.pg.sql +0 -12
- package/templates/src/app/api/_platform/migrations/0012__market_history.sql +0 -14
- package/templates/src/app/api/_platform/migrations/0013__users_profile.sql +0 -4
- package/templates/src/app/api/_platform/migrations/0014__reg_source_seq.mysql.sql +0 -9
- package/templates/src/app/api/_platform/migrations/0014__reg_source_seq.pg.sql +0 -7
- package/templates/src/app/api/_platform/migrations/0014__reg_source_seq.sql +0 -8
- package/templates/src/app/api/_platform/migrations/0015__tenant.mysql.sql +0 -12
- package/templates/src/app/api/_platform/migrations/0015__tenant.pg.sql +0 -11
- package/templates/src/app/api/_platform/migrations/0015__tenant.sql +0 -44
- package/templates/src/app/api/_platform/migrations/0016__org.mysql.sql +0 -41
- package/templates/src/app/api/_platform/migrations/0016__org.pg.sql +0 -40
- package/templates/src/app/api/_platform/migrations/0016__org.sql +0 -62
- package/templates/src/app/api/_platform/migrations/0017__users_employee_no.mysql.sql +0 -7
- package/templates/src/app/api/_platform/migrations/0017__users_employee_no.pg.sql +0 -4
- package/templates/src/app/api/_platform/migrations/0017__users_employee_no.sql +0 -8
- package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.mysql.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.pg.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.sql +0 -8
- package/templates/src/app/web/system/pages/login-methods/index.tsx +0 -30
- /package/shell-dist/assets/{nprogress-css.js → nprogress-css.1cq2pzkre0.js} +0 -0
- /package/shell-dist/assets/{ojm-antd-es-button.js → ojm-antd-es-button.2uriphio50.js} +0 -0
- /package/shell-dist/assets/{ojm-antd-es-modal.js → ojm-antd-es-modal.bf3tgdh8v0.js} +0 -0
- /package/shell-dist/assets/{ojm-antd-es-slider.js → ojm-antd-es-slider.xyvptuhid0.js} +0 -0
- /package/shell-dist/assets/{ojm-antd-es-upload.js → ojm-antd-es-upload.13gx45e0g0.js} +0 -0
|
@@ -88,12 +88,25 @@ describe("templates 后端 schema.yaml 列白名单", () => {
|
|
|
88
88
|
expect(violations).toEqual([]);
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
-
it("s-2 回归锚点:
|
|
91
|
+
it("s-2 回归锚点:_platform 基线为 0001、增量 0002(发布历史)三方言齐备,且基线里确有 users.employee_no", () => {
|
|
92
92
|
const migDir = join(srcRoot, "app", "api", "_platform", "migrations");
|
|
93
|
-
for (const file of [
|
|
93
|
+
for (const file of [
|
|
94
|
+
"0001__baseline.sql",
|
|
95
|
+
"0001__baseline.mysql.sql",
|
|
96
|
+
"0001__baseline.pg.sql",
|
|
97
|
+
"0002__market_release.sql",
|
|
98
|
+
"0002__market_release.mysql.sql",
|
|
99
|
+
"0002__market_release.pg.sql",
|
|
100
|
+
])
|
|
94
101
|
expect(existsSync(join(migDir, file)), `缺迁移 ${file}`).toBe(true);
|
|
95
102
|
|
|
103
|
+
// 0001 是压平后的结构基线,已应用过的库不会重跑——新增结构只能追加新序号,
|
|
104
|
+
// 不得回改基线(否则老库拿不到)。序号必须从 0001 起连续、无空洞。
|
|
105
|
+
const seqs = [...new Set(readdirSync(migDir).map(f => f.slice(0, 4)))].sort();
|
|
106
|
+
expect(seqs).toEqual(["0001", "0002"]);
|
|
107
|
+
|
|
96
108
|
const schema = parse(readFileSync(join(srcRoot, "app", "api", "_platform", "schema.yaml"), "utf-8")) as SchemaDoc;
|
|
97
109
|
expect(schema.tables?.users?.columns?.employee_no).toMatchObject({ type: "text" });
|
|
110
|
+
expect(readFileSync(join(migDir, "0001__baseline.sql"), "utf-8")).toContain("employee_no");
|
|
98
111
|
});
|
|
99
112
|
});
|
|
@@ -115,7 +115,7 @@ describe("templates 租户定性(A 租户表 / B 共享表)", () => {
|
|
|
115
115
|
expect(unknown).toEqual([]);
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
-
it("t-4
|
|
118
|
+
it("t-4 29 张表的 A/B 分组与方案 v3 §5.2 一致(新增表须先定性)", () => {
|
|
119
119
|
const { tables } = loadSchemaTables();
|
|
120
120
|
const shared = [...tables].filter(([, d]) => d.tenant === false).map(([n]) => n).sort();
|
|
121
121
|
// Phase 0 落地 21 张(11 A + 10 B);Phase 1 加企微三表,全部 B 组(D9:查它们时必然还没有租户上下文)
|
|
@@ -125,10 +125,14 @@ describe("templates 租户定性(A 租户表 / B 共享表)", () => {
|
|
|
125
125
|
// (D7 不接受 corpId 参数),故不进 shared_allow——跨租户读元数据那条另走显式 tenant_id 的裸 SQL。
|
|
126
126
|
// 改这张表清单 = 改租户隔离边界,必须同步 config.yaml 的 tenant.shared_allow(t-2/t-3 会把关)。
|
|
127
127
|
// 2026-11-02 demo 模块下线(ecc7b35e)删掉 demo/api/schema.yaml 的 todos 表,28 → 27。
|
|
128
|
-
|
|
128
|
+
// 2026-11 发布凭证加 publish_credential:共享表(CLI 兑换无租户语义,归属走 owner_tenant),27 → 28。
|
|
129
|
+
// 2026-11 发布历史加 market_release:共享表(发布物落在站点级 CDN,历史按站点归口),28 → 29。
|
|
130
|
+
expect(tables.size).toBe(29);
|
|
129
131
|
expect(shared).toEqual([
|
|
130
132
|
"_oj_migrations",
|
|
131
133
|
"market_history",
|
|
134
|
+
"market_release",
|
|
135
|
+
"publish_credential",
|
|
132
136
|
"reg_source_seq",
|
|
133
137
|
"sys_login_throttle",
|
|
134
138
|
"sys_permission",
|
package/src/bundle.ts
CHANGED
|
@@ -456,14 +456,19 @@ function checkModuleManifest(built: string, name: string, version: string, _summ
|
|
|
456
456
|
return out;
|
|
457
457
|
}
|
|
458
458
|
|
|
459
|
-
/**
|
|
459
|
+
/**
|
|
460
|
+
* api.tables ↔ schema.yaml 表集对照(C2-12a)+ 表前缀(C2-12c)。
|
|
461
|
+
* 引用式方案(tables 缺省)只要 schema.yaml 存在也做前缀检查——表名冲突与打包形态无关。
|
|
462
|
+
*/
|
|
460
463
|
function checkTables(m: Record<string, unknown>, layout: ProjectLayout, name: string): string[] {
|
|
461
464
|
const api = m.api as Record<string, unknown> | undefined;
|
|
462
|
-
if (api === undefined
|
|
465
|
+
if (api === undefined)
|
|
463
466
|
return [];
|
|
464
467
|
const schemaPath = path.join(apiSrcOf(layout, name), "schema.yaml");
|
|
465
468
|
if (!fs.existsSync(schemaPath)) {
|
|
466
|
-
return
|
|
469
|
+
return api.tables === undefined
|
|
470
|
+
? []
|
|
471
|
+
: [`【C2-12a】api.tables 已声明但缺少 ${path.relative(layout.projectRoot, schemaPath)},无法对照表集(oj 冗余校验同源)`];
|
|
467
472
|
}
|
|
468
473
|
return checkTablesAgainstSchema(m, fs.readFileSync(schemaPath, "utf8"));
|
|
469
474
|
}
|
package/src/cli.ts
CHANGED
|
@@ -223,9 +223,21 @@ function makeAction(def: HelpEntry, projectRoot: string, lang: Lang, exit: (code
|
|
|
223
223
|
await mergeManifests(out ?? "", inputs);
|
|
224
224
|
};
|
|
225
225
|
case "keygen":
|
|
226
|
-
return async (opts: { id?: string, out?: string, yes?: boolean } = {}) => {
|
|
227
|
-
const { keygenCommand } = await import("./keygen");
|
|
228
|
-
|
|
226
|
+
return async (opts: { id?: string, out?: string, yes?: boolean, credential?: string, siteUrl?: string } = {}) => {
|
|
227
|
+
const { exchangePublishCredential, keygenCommand } = await import("./keygen");
|
|
228
|
+
// 令牌解析:--credential > OJM_PUBLISH_CREDENTIAL;都没有则走原离线路径(逐字节不变)
|
|
229
|
+
const credential = (opts.credential ?? process.env.OJM_PUBLISH_CREDENTIAL ?? "").trim();
|
|
230
|
+
if (credential === "") {
|
|
231
|
+
keygenCommand({ id: opts.id, out: opts.out, yes: opts.yes });
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// 身份只来自凭证:--id 是另一条(离线)路径的入口,同时给必然有一方被静默忽略
|
|
235
|
+
if (opts.id !== undefined)
|
|
236
|
+
throw new UsageError("[ojm] --credential 与 --id 不能同时使用:凭证已绑定发布身份(要自定义身份请走离线 ojm keygen --id)");
|
|
237
|
+
const identity = await exchangePublishCredential(credential, { siteUrl: opts.siteUrl });
|
|
238
|
+
const who = identity.display_name === null ? identity.username : `${identity.display_name}(${identity.username})`;
|
|
239
|
+
console.log(`[ojm] 凭证已兑换:产品 ${identity.product} / 申请人 ${who}`);
|
|
240
|
+
keygenCommand({ id: identity.key_id, out: opts.out, yes: opts.yes });
|
|
229
241
|
};
|
|
230
242
|
case "sign":
|
|
231
243
|
return async (file: string | undefined, opts: { id?: string, key?: string, check?: boolean, registryId?: string } = {}) => {
|
|
@@ -283,9 +295,9 @@ function makeAction(def: HelpEntry, projectRoot: string, lang: Lang, exit: (code
|
|
|
283
295
|
exit(EXIT_FAILURE);
|
|
284
296
|
};
|
|
285
297
|
case "install":
|
|
286
|
-
return async (target: string | undefined, opts: SiteOpts & { locale?: string, name?: string, version?: string, registry?: string } = {}) => {
|
|
298
|
+
return async (target: string | undefined, opts: SiteOpts & { locale?: string, name?: string, version?: string, registry?: string, force?: boolean } = {}) => {
|
|
287
299
|
const { installCommand } = await import("./install");
|
|
288
|
-
await installCommand(target ?? "", { siteUrl: opts.siteUrl, user: opts.user, pass: opts.pass, locale: opts.locale, name: opts.name, version: opts.version, registry: opts.registry });
|
|
300
|
+
await installCommand(target ?? "", { siteUrl: opts.siteUrl, user: opts.user, pass: opts.pass, locale: opts.locale, name: opts.name, version: opts.version, registry: opts.registry, force: opts.force });
|
|
289
301
|
};
|
|
290
302
|
case "list":
|
|
291
303
|
return async (opts: SiteOpts & { all?: boolean, json?: boolean } = {}) => {
|
package/src/dev.ts
CHANGED
|
@@ -230,12 +230,6 @@ export async function devServer(projectRoot: string, opts: DevOptions = {}): Pro
|
|
|
230
230
|
proxyWsUpgrade(ojTarget)(req, socket, head);
|
|
231
231
|
});
|
|
232
232
|
|
|
233
|
-
// 退出回收:server 关闭 → SSE 通道 + oj 子进程一并回收
|
|
234
|
-
server.on("close", () => {
|
|
235
|
-
hub.close();
|
|
236
|
-
void oj?.stop();
|
|
237
|
-
});
|
|
238
|
-
|
|
239
233
|
// 终端 Ctrl+C / `kill`(SIGINT / SIGTERM)终止 dev 进程时,后端 oj 必须随之消亡:
|
|
240
234
|
// 起一个统一优雅停机,先停 oj 再退出;并打印后端终止信息。
|
|
241
235
|
// 终端 Ctrl-C 是进程组信号:外层 bin 转发一份 + 组送达一份,内层会收到
|
|
@@ -249,13 +243,24 @@ export async function devServer(projectRoot: string, opts: DevOptions = {}): Pro
|
|
|
249
243
|
shuttingDown = true;
|
|
250
244
|
void shutdownDev(server, oj, hub);
|
|
251
245
|
};
|
|
252
|
-
process.on("SIGINT", shutdown);
|
|
253
|
-
process.on("SIGTERM", shutdown);
|
|
254
|
-
|
|
255
246
|
// 兜底:dev 进程以任何非优雅路径退出(未走 SIGINT/SIGTERM,如未捕获异常)时,
|
|
256
247
|
// 同步强杀 oj,确保它不孤儿化继续占端口(shutdownDev 已停则 kill 为 no-op)。
|
|
257
|
-
|
|
248
|
+
const killOjOnExit = () => {
|
|
258
249
|
oj?.kill?.();
|
|
250
|
+
};
|
|
251
|
+
process.on("SIGINT", shutdown);
|
|
252
|
+
process.on("SIGTERM", shutdown);
|
|
253
|
+
process.once("exit", killOjOnExit);
|
|
254
|
+
|
|
255
|
+
// 退出回收:server 关闭 → SSE 通道 + oj 子进程一并回收,并摘除本实例注册的
|
|
256
|
+
// 进程级信号/exit handler。devServer 可在同一进程被多次调用(编程式 / 测试),
|
|
257
|
+
// 不摘除会累积闭包、触发 MaxListenersExceededWarning(与 preview 的 stop 同款)。
|
|
258
|
+
server.on("close", () => {
|
|
259
|
+
hub.close();
|
|
260
|
+
void oj?.stop();
|
|
261
|
+
process.removeListener("SIGINT", shutdown);
|
|
262
|
+
process.removeListener("SIGTERM", shutdown);
|
|
263
|
+
process.removeListener("exit", killOjOnExit);
|
|
259
264
|
});
|
|
260
265
|
|
|
261
266
|
// 端口被占用时自动顺延,避免 `EADDRINUSE` 直接让 `pnpm dev` 以非零码退出
|
package/src/esm-exports.ts
CHANGED
|
@@ -14,14 +14,19 @@
|
|
|
14
14
|
* 本模块只做「扫描产物文本、回答谁导出/导入了什么」,不含修复逻辑,
|
|
15
15
|
* 供 shell 构建(门禁,失败即中断)与 tests/ 共用,避免两套正则漂移。
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
* `minify: false`
|
|
19
|
-
*
|
|
17
|
+
* 解析口径:`es-module-lexer` 真词法扫描,而非行首正则。
|
|
18
|
+
* 早先的实现假设「esbuild/vite 在 `minify: false` 下顶层语句一律顶格」,一旦
|
|
19
|
+
* 共享资产开启压缩(`minifyWhitespace` 会把 `export{a as b}` / `}from"react"`
|
|
20
|
+
* 挤成一行且行内任意位置),行首锚定的正则全部失配 → 门禁集体误报「零具名导出」
|
|
21
|
+
* 和「裸说明符未登记」。词法扫描只依赖语法、与压缩/换行无关,故换掉。
|
|
22
|
+
* 顺带消灭了「字符串字面量里恰好写了 import/from」的误报(原先靠行首锚定压制)。
|
|
20
23
|
*/
|
|
21
24
|
|
|
22
25
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
23
26
|
import { resolve } from "node:path";
|
|
24
27
|
|
|
28
|
+
import { ImportType, initSync, parse as lexModule } from "es-module-lexer";
|
|
29
|
+
|
|
25
30
|
/** 一个 ESM 文件的导出面 */
|
|
26
31
|
export interface EsmExports {
|
|
27
32
|
/** 静态具名导出(不含 `default`、不含 `export *` 透传的部分) */
|
|
@@ -56,22 +61,6 @@ export interface EsmImport {
|
|
|
56
61
|
needsDefault: boolean
|
|
57
62
|
}
|
|
58
63
|
|
|
59
|
-
/**
|
|
60
|
-
* `export { a, b as c }`(本地导出块)与 `export { a, b as c } from "x"`(再导出)
|
|
61
|
-
* 的公共部分:取出**对外暴露**的名字(`b as c` 取 `c`)。
|
|
62
|
-
*/
|
|
63
|
-
function exportedNamesFromClause(clause: string): string[] {
|
|
64
|
-
return clause
|
|
65
|
-
.split(",")
|
|
66
|
-
.map(part => part.trim())
|
|
67
|
-
.filter(Boolean)
|
|
68
|
-
.map((part) => {
|
|
69
|
-
const as = part.split(/\s+as\s+/);
|
|
70
|
-
return (as[as.length - 1] ?? part).trim();
|
|
71
|
-
})
|
|
72
|
-
.filter(name => name !== "default");
|
|
73
|
-
}
|
|
74
|
-
|
|
75
64
|
/**
|
|
76
65
|
* `import { a, b as c }` 中被**请求**的名字。
|
|
77
66
|
*
|
|
@@ -88,51 +77,75 @@ function importedNamesFromClause(clause: string): string[] {
|
|
|
88
77
|
.filter(name => name !== "default");
|
|
89
78
|
}
|
|
90
79
|
|
|
80
|
+
/** wasm 实例化只需一次;按需初始化,避免「只是 import 一下」的消费方付启动开销 */
|
|
81
|
+
let lexReady = false;
|
|
82
|
+
function lex(source: string): ReturnType<typeof lexModule> {
|
|
83
|
+
if (!lexReady) {
|
|
84
|
+
initSync();
|
|
85
|
+
lexReady = true;
|
|
86
|
+
}
|
|
87
|
+
return lexModule(source);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** 语句文本;`ss`/`se` 是 lexer 给出的整条语句区间(含分号) */
|
|
91
|
+
function statementAt(source: string, ss: number, se: number): string {
|
|
92
|
+
return source.slice(ss, se);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 从一条 `import … from "x"` / `export … from "x"` 语句里分出关键字与子句。
|
|
97
|
+
*
|
|
98
|
+
* 语句边界已由 lexer 确定,这里只处理语句内部的切片,因此不再需要行首锚定,
|
|
99
|
+
* 压缩后(`import{a as b}from"react"`)同样成立。
|
|
100
|
+
*/
|
|
101
|
+
function clauseOf(statement: string): { keyword: "import" | "export", clause: string } | undefined {
|
|
102
|
+
const head = /^\s*(import|export)\b/.exec(statement);
|
|
103
|
+
if (!head)
|
|
104
|
+
return undefined;
|
|
105
|
+
const rest = statement.slice(head[0].length);
|
|
106
|
+
// `from` 后必须紧跟说明符字符串,才不会误取子句里的同名标识符
|
|
107
|
+
const fromIdx = rest.search(/\bfrom\s*["']/);
|
|
108
|
+
if (fromIdx < 0)
|
|
109
|
+
return undefined;
|
|
110
|
+
return { keyword: head[1] as "import" | "export", clause: rest.slice(0, fromIdx) };
|
|
111
|
+
}
|
|
112
|
+
|
|
91
113
|
/** 解析产物的静态导出面 */
|
|
92
114
|
export function parseEsmExports(source: string): EsmExports {
|
|
115
|
+
const [imports, exports] = lex(source);
|
|
93
116
|
const names = new Set<string>();
|
|
117
|
+
let hasDefault = false;
|
|
94
118
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
for (const clause of clauses) {
|
|
102
|
-
for (const name of exportedNamesFromClause(clause))
|
|
103
|
-
names.add(name);
|
|
119
|
+
for (const exp of exports) {
|
|
120
|
+
if (exp.n === "default")
|
|
121
|
+
hasDefault = true;
|
|
122
|
+
else
|
|
123
|
+
names.add(exp.n);
|
|
104
124
|
}
|
|
105
125
|
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
126
|
+
// `export * from "x"` 不产生具名导出(静态不可展开),lexer 也不把它列进 exports,
|
|
127
|
+
// 只能从「语句以 export 开头、星号之后不带 as」反推。
|
|
128
|
+
// `export * as ns from "x"` 是有名字的导出(ns),导出面仍可静态确定,不算在内。
|
|
129
|
+
const hasStarReexport = imports.some((imp) => {
|
|
130
|
+
if (imp.t !== ImportType.Static || imp.ss < 0)
|
|
131
|
+
return false;
|
|
132
|
+
return /^export\s*\*(?!\s*as\b)/.test(statementAt(source, imp.ss, imp.se));
|
|
133
|
+
});
|
|
110
134
|
|
|
111
|
-
|
|
112
|
-
// (esbuild 产出的正是后者,形如 `rad_shim_xxx_default as default`)
|
|
113
|
-
const hasDefaultClause = clauses.some(clause => clause
|
|
114
|
-
.split(",")
|
|
115
|
-
.map(part => part.trim())
|
|
116
|
-
.some(part => /\bas\s+default$/.test(part)));
|
|
117
|
-
|
|
118
|
-
return {
|
|
119
|
-
names,
|
|
120
|
-
hasDefault: /^export\s+default\b/m.test(source) || hasDefaultClause,
|
|
121
|
-
hasStarReexport: /^export\s*\*/m.test(source),
|
|
122
|
-
};
|
|
135
|
+
return { names, hasDefault, hasStarReexport };
|
|
123
136
|
}
|
|
124
137
|
|
|
125
138
|
/**
|
|
126
139
|
* 解析产物消费了哪些说明符的哪些具名导出。
|
|
127
140
|
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* `export { a } from "x"`(再导出同样是消费)。
|
|
141
|
+
* 覆盖:`import { a, b as c } from "x"`、`import X, { a } from "x"`、
|
|
142
|
+
* `export { a } from "x"`(再导出同样是消费)。
|
|
131
143
|
*
|
|
132
144
|
* 不覆盖:`import * as ns from "x"`、动态 `import("x")` 后取属性——
|
|
133
145
|
* 这些都拿不到具名绑定,无从校验。
|
|
134
146
|
*/
|
|
135
147
|
export function parseEsmImports(source: string): EsmImport[] {
|
|
148
|
+
const [imports] = lex(source);
|
|
136
149
|
const found = new Map<string, { names: Set<string>, needsDefault: boolean }>();
|
|
137
150
|
|
|
138
151
|
const slot = (specifier: string) => {
|
|
@@ -144,25 +157,30 @@ export function parseEsmImports(source: string): EsmImport[] {
|
|
|
144
157
|
return entry;
|
|
145
158
|
};
|
|
146
159
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
160
|
+
for (const imp of imports) {
|
|
161
|
+
// 动态 import / import.meta 不带静态具名绑定
|
|
162
|
+
if (imp.t !== ImportType.Static || imp.ss < 0 || !imp.n)
|
|
163
|
+
continue;
|
|
164
|
+
const parsed = clauseOf(statementAt(source, imp.ss, imp.se));
|
|
165
|
+
if (!parsed)
|
|
166
|
+
continue;
|
|
153
167
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
168
|
+
const entry = slot(imp.n);
|
|
169
|
+
const brace = /\{([^}]*)\}/.exec(parsed.clause);
|
|
170
|
+
if (brace) {
|
|
171
|
+
for (const name of importedNamesFromClause(brace[1] ?? ""))
|
|
172
|
+
entry.names.add(name);
|
|
173
|
+
}
|
|
160
174
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
175
|
+
// default 导入:子句去掉 `{…}` 后仍剩标识符(`X` / `X,`)。
|
|
176
|
+
// `* as ns` 是命名空间导入,不消费 default。
|
|
177
|
+
// 不能假定它一定可用——只有 default 的包若 shim 没兜住,浏览器同样是
|
|
178
|
+
// "does not provide an export named 'default'"。
|
|
179
|
+
if (parsed.keyword === "import") {
|
|
180
|
+
const head = parsed.clause.replace(/\{[^}]*\}/g, "").trim();
|
|
181
|
+
if (head && !head.startsWith("*"))
|
|
182
|
+
entry.needsDefault = true;
|
|
183
|
+
}
|
|
166
184
|
}
|
|
167
185
|
|
|
168
186
|
return [...found].map(([specifier, entry]) => ({
|
|
@@ -233,24 +251,88 @@ export function collectDynamicRequires(distDir: string): DynamicRequireHit[] {
|
|
|
233
251
|
}
|
|
234
252
|
|
|
235
253
|
/**
|
|
236
|
-
*
|
|
254
|
+
* 产物引用的全部裸说明符(静态/副作用 import、`import("x")`、再导出)。
|
|
237
255
|
*
|
|
238
256
|
* 浏览器只能解析 importmap 里登记过的裸说明符,漏一个就是
|
|
239
257
|
* `Failed to resolve module specifier` 白屏。
|
|
240
258
|
*/
|
|
241
259
|
export function parseBareSpecifiers(source: string): string[] {
|
|
260
|
+
const [imports] = lex(source);
|
|
242
261
|
const found = new Set<string>();
|
|
243
|
-
//
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
262
|
+
// lexer 已给出每条语句的说明符原文;非字符串字面量(`import(x)`)为 undefined,跳过
|
|
263
|
+
for (const imp of imports) {
|
|
264
|
+
const spec = imp.n;
|
|
265
|
+
if (!spec)
|
|
266
|
+
continue;
|
|
267
|
+
if (spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("data:") || spec.startsWith("http"))
|
|
248
268
|
continue;
|
|
249
269
|
found.add(spec);
|
|
250
270
|
}
|
|
251
271
|
return [...found];
|
|
252
272
|
}
|
|
253
273
|
|
|
274
|
+
/**
|
|
275
|
+
* 产物的**静态**依赖说明符:`import … from "x"`、副作用 `import "x"`、再导出。
|
|
276
|
+
*
|
|
277
|
+
* 与 {@link parseBareSpecifiers} 的差别有两点,都是为了算「首屏静态闭包」:
|
|
278
|
+
* - **排除动态 `import("x")`**:那是懒加载,不在首屏关键路径上,预取它会白拉;
|
|
279
|
+
* - **保留相对/绝对路径**:闭包要顺着 `/assets/x.hash.js` 继续走,不能只看裸名。
|
|
280
|
+
*
|
|
281
|
+
* 静态图的切分口径与浏览器一致(缓存模块记录前先取完整静态图),所以这里能算出
|
|
282
|
+
* 「浏览器一定会请求的最小集合」。
|
|
283
|
+
*/
|
|
284
|
+
export function parseStaticSpecifiers(source: string): string[] {
|
|
285
|
+
const [imports] = lex(source);
|
|
286
|
+
const found = new Set<string>();
|
|
287
|
+
for (const imp of imports) {
|
|
288
|
+
// d === -1 才是静态导入;`import("x")` 的 d 是 `(` 的偏移
|
|
289
|
+
if (imp.d !== -1 || !imp.n)
|
|
290
|
+
continue;
|
|
291
|
+
found.add(imp.n);
|
|
292
|
+
}
|
|
293
|
+
return [...found];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* 首屏静态依赖闭包:从入口出发,顺着**静态** import 一直走到没得走。
|
|
298
|
+
*
|
|
299
|
+
* 这就是浏览器一定会请求的集合(ESM 语义:先取完整静态图再执行),所以可以放心
|
|
300
|
+
* 全量预取;动态 `import()` 不在其中。裸说明符经 importmap 解析,相对路径按当前
|
|
301
|
+
* 文件所在目录拼。
|
|
302
|
+
*
|
|
303
|
+
* `readAsset` 返回 undefined 表示读不到(跨源 CDN 资产、或缺失文件):该 URL 仍算
|
|
304
|
+
* 闭包成员(预取它对浏览器有效),但不再往下展开。
|
|
305
|
+
*
|
|
306
|
+
* 与构建脚本共用同一份实现,是为了让测试能重算一遍对撞产物——两套实现必然漂移。
|
|
307
|
+
*/
|
|
308
|
+
export function collectStaticClosure(
|
|
309
|
+
entryUrl: string,
|
|
310
|
+
importmap: Record<string, string>,
|
|
311
|
+
readAsset: (url: string) => string | undefined,
|
|
312
|
+
): string[] {
|
|
313
|
+
const seen = new Set<string>();
|
|
314
|
+
const queue = [entryUrl];
|
|
315
|
+
while (queue.length) {
|
|
316
|
+
const url = queue.shift()!;
|
|
317
|
+
if (seen.has(url))
|
|
318
|
+
continue;
|
|
319
|
+
seen.add(url);
|
|
320
|
+
const source = readAsset(url);
|
|
321
|
+
if (source === undefined)
|
|
322
|
+
continue;
|
|
323
|
+
for (const spec of parseStaticSpecifiers(source)) {
|
|
324
|
+
const target = spec.startsWith("/")
|
|
325
|
+
? spec
|
|
326
|
+
: spec.startsWith(".")
|
|
327
|
+
? resolve("/", url.slice(0, url.lastIndexOf("/") + 1), spec)
|
|
328
|
+
: importmap[spec];
|
|
329
|
+
if (target)
|
|
330
|
+
queue.push(target);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return [...seen];
|
|
334
|
+
}
|
|
335
|
+
|
|
254
336
|
/** 一处「资产引用了 importmap 未登记的裸说明符」 */
|
|
255
337
|
export interface UnresolvedSpecifier {
|
|
256
338
|
file: string
|
package/src/install.ts
CHANGED
|
@@ -254,6 +254,11 @@ export interface InstallOptions {
|
|
|
254
254
|
locale?: string
|
|
255
255
|
/** REG-R9②:从指定 registry 源安装(白名单在服务端;旧站点 API < 0.2.0 不支持 → exit 1) */
|
|
256
256
|
registry?: string
|
|
257
|
+
/**
|
|
258
|
+
* 强制重装配(oj 0.1.123+ reloadConfig(force)):旁路「unchanged=已是最新装配」幂等短路,
|
|
259
|
+
* 强制宿主重做一次装配。运维显式重装配用;缺省 false。
|
|
260
|
+
*/
|
|
261
|
+
force?: boolean
|
|
257
262
|
/** 测试注入;缺省 globalThis.fetch */
|
|
258
263
|
fetchFn?: typeof fetch
|
|
259
264
|
/** 测试注入;缺省真睡 */
|
|
@@ -269,6 +274,8 @@ export async function installCommand(target: string, opts: InstallOptions = {}):
|
|
|
269
274
|
const fetchFn = opts.fetchFn ?? globalThis.fetch;
|
|
270
275
|
const { base, user, pass } = resolveSite(opts);
|
|
271
276
|
const token = await login(fetchFn, base, user, pass);
|
|
277
|
+
// force 透传(oj 0.1.123+ reloadConfig(force) 逃逸舱):仅运维显式重装配时用;常规安装走锁 diff
|
|
278
|
+
const forceQ = opts.force === true ? "?force=1" : "";
|
|
272
279
|
|
|
273
280
|
let response: { task_id: string };
|
|
274
281
|
if (opts.registry !== undefined) {
|
|
@@ -282,13 +289,13 @@ export async function installCommand(target: string, opts: InstallOptions = {}):
|
|
|
282
289
|
if (!isSiteApiAtLeast(meta.apiVersion, "0.2.0"))
|
|
283
290
|
throw new Error(`[ojm] 目标站点平台 API 版本过低(需 ≥ 0.2.0 才支持 --registry 源安装,实为 ${meta.apiVersion ?? "未知"})——请升级目标站点的 oj-module,或改用 CDN/file 源`);
|
|
284
291
|
const registryOrigin = originOf(src.url) ?? src.url;
|
|
285
|
-
response = await callApi<{ task_id: string }>(fetchFn, base, token, "POST",
|
|
292
|
+
response = await callApi<{ task_id: string }>(fetchFn, base, token, "POST", `/api/_platform/market/install${forceQ}`, {
|
|
286
293
|
name: slug,
|
|
287
294
|
version: opts.version ?? version,
|
|
288
295
|
source: "registry",
|
|
289
296
|
registry: registryOrigin,
|
|
290
297
|
});
|
|
291
|
-
console.log(`[ojm] 已从 registry 源提交安装任务(源 ${registryOrigin},物理名 ${slug})`);
|
|
298
|
+
console.log(`[ojm] 已从 registry 源提交安装任务(源 ${registryOrigin},物理名 ${slug}${opts.force === true ? ",强制重装配" : ""})`);
|
|
292
299
|
}
|
|
293
300
|
else {
|
|
294
301
|
const parsed = parseTarget(target);
|
|
@@ -314,7 +321,7 @@ export async function installCommand(target: string, opts: InstallOptions = {}):
|
|
|
314
321
|
else {
|
|
315
322
|
console.log(`[ojm] 未发现签名侧车(${path.basename(sigPath)})——宿主验签硬 fail-closed,未签名包将被拒(sig_missing);签名方法见 devkit「签名与信任」`);
|
|
316
323
|
}
|
|
317
|
-
const res = await fetchFn(`${base}/api/_platform/market/install`, {
|
|
324
|
+
const res = await fetchFn(`${base}/api/_platform/market/install${forceQ}`, {
|
|
318
325
|
method: "POST",
|
|
319
326
|
headers: { authorization: `Bearer ${token}`, ...tenantHeaders() },
|
|
320
327
|
body: form,
|
|
@@ -324,7 +331,7 @@ export async function installCommand(target: string, opts: InstallOptions = {}):
|
|
|
324
331
|
else {
|
|
325
332
|
if (opts.name !== undefined && opts.name !== parsed.name)
|
|
326
333
|
throw new Error(`[ojm] --name「${opts.name}」与目标「${parsed.name}」不一致——请二选一`);
|
|
327
|
-
response = await callApi<{ task_id: string }>(fetchFn, base, token, "POST",
|
|
334
|
+
response = await callApi<{ task_id: string }>(fetchFn, base, token, "POST", `/api/_platform/market/install${forceQ}`, {
|
|
328
335
|
name: parsed.name,
|
|
329
336
|
version: opts.version ?? parsed.version,
|
|
330
337
|
source: "cdn",
|
package/src/keygen.ts
CHANGED
|
@@ -10,6 +10,7 @@ import path from "node:path";
|
|
|
10
10
|
import process from "node:process";
|
|
11
11
|
import { generateKeyPair } from "./lifecycle/keygen";
|
|
12
12
|
import { fingerprintOfPublicKey } from "./lifecycle/sign";
|
|
13
|
+
import { DEFAULT_SITE_URL, tenantHeaders, unwrapEnvelope } from "./site";
|
|
13
14
|
|
|
14
15
|
export interface KeygenOptions {
|
|
15
16
|
/** 发布方 key_id(C2-37:<publisher>.<id> 全小写,禁裸通用名) */
|
|
@@ -67,3 +68,56 @@ export function keygenCommand(opts: KeygenOptions = {}): KeygenResult {
|
|
|
67
68
|
console.log(`[ojm] 下一步:ojm sign <pkg>.tgz --id ${keyId}(或 --key ${privateKeyPath} --id ${keyId})`);
|
|
68
69
|
return { keyId, publicKeyHex: kp.publicKeyHex, fingerprint, dir, privateKeyPath, publicKeyPath };
|
|
69
70
|
}
|
|
71
|
+
|
|
72
|
+
/** 兑换结果:服务端下发身份(CLI 零推导——编码规则只在 templates 侧一份) */
|
|
73
|
+
export interface ExchangedIdentity {
|
|
74
|
+
key_id: string
|
|
75
|
+
product: string
|
|
76
|
+
username: string
|
|
77
|
+
display_name: string | null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface ExchangeOptions {
|
|
81
|
+
/** 站点地址;flag > OJM_SITE_URL > DEFAULT_SITE_URL */
|
|
82
|
+
siteUrl?: string
|
|
83
|
+
/** 测试注入 */
|
|
84
|
+
fetchFn?: typeof fetch
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 用一次性发布凭证换身份(匿名端点,不需要账号密码)。
|
|
89
|
+
*
|
|
90
|
+
* 只做「取回服务端算好的 key_id」——不做任何编码推导(规则漂移比多一次网络请求贵得多),
|
|
91
|
+
* 但**必须**校验拿回的 key_id 形状:它马上会被拼进 `~/.ojm/keys/<key_id>`,
|
|
92
|
+
* 一个被中间人改成 `../../x` 的响应就是任意路径写入。失败一律不落盘(调用方先兑换后生成)。
|
|
93
|
+
*/
|
|
94
|
+
export async function exchangePublishCredential(token: string, opts: ExchangeOptions = {}): Promise<ExchangedIdentity> {
|
|
95
|
+
const t = token.trim();
|
|
96
|
+
// 形状先挡:不是 64 位 hex 的输入连请求都不必发(与页面生成规则一致)
|
|
97
|
+
if (!/^[0-9a-f]{64}$/i.test(t))
|
|
98
|
+
throw new Error("[ojm] 发布凭证格式不对(应为 64 位十六进制):请在站点「发布管理」页复制完整凭证");
|
|
99
|
+
|
|
100
|
+
const base = (opts.siteUrl ?? process.env.OJM_SITE_URL ?? DEFAULT_SITE_URL).replace(/\/+$/, "");
|
|
101
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
102
|
+
let res: Response;
|
|
103
|
+
try {
|
|
104
|
+
res = await fetchFn(`${base}/api/publish/credential/exchange`, {
|
|
105
|
+
method: "POST",
|
|
106
|
+
headers: { "content-type": "application/json", ...tenantHeaders() },
|
|
107
|
+
body: JSON.stringify({ token: t }),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
throw new Error(`[ojm] 无法连接站点 ${base}:${e instanceof Error ? e.message : String(e)}——请确认站点地址(--site-url / OJM_SITE_URL)与网络`);
|
|
112
|
+
}
|
|
113
|
+
const data = await unwrapEnvelope(res) as Partial<ExchangedIdentity> | null;
|
|
114
|
+
const keyId = String(data?.key_id ?? "");
|
|
115
|
+
if (!isValidKeyId(keyId))
|
|
116
|
+
throw new Error(`[ojm] 站点返回的 key_id 非法(C2-37):「${keyId}」——已中止,未写入任何密钥(请核对站点版本)`);
|
|
117
|
+
return {
|
|
118
|
+
key_id: keyId,
|
|
119
|
+
product: String(data?.product ?? ""),
|
|
120
|
+
username: String(data?.username ?? ""),
|
|
121
|
+
display_name: data?.display_name == null ? null : String(data.display_name),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -53,8 +53,8 @@ function makePrebuiltArtifacts(): void {
|
|
|
53
53
|
const apiDist = join(project, "dist/api/demo-solution-0.1.0");
|
|
54
54
|
mkdirSync(join(apiDist, "ping"), { recursive: true });
|
|
55
55
|
mkdirSync(join(apiDist, "migrations"), { recursive: true });
|
|
56
|
-
writeFileSync(join(apiDist, "manifest.yaml"), "name: demo-solution\ndesc: manifest.yaml 契约全字段合法夹具\nversion: 0.1.0\ntables:\n -
|
|
57
|
-
writeFileSync(join(apiDist, "schema.yaml"), "
|
|
56
|
+
writeFileSync(join(apiDist, "manifest.yaml"), "name: demo-solution\ndesc: manifest.yaml 契约全字段合法夹具\nversion: 0.1.0\ntables:\n - demo_solution_note\n");
|
|
57
|
+
writeFileSync(join(apiDist, "schema.yaml"), "prefix: demo_solution_\ntables:\n demo_solution_note: {}\n");
|
|
58
58
|
writeFileSync(join(apiDist, "routes.js"), "export {};\n");
|
|
59
59
|
writeFileSync(join(apiDist, "ping/api.js"), "export async function ping() {}\n");
|
|
60
60
|
// 站点构建产物(dist/web/modules/<name>/<version>/entry.js + css,对应 web.entry/css 声明)
|
|
@@ -317,11 +317,29 @@ describe("buildBundle · 校验先于打包(fail-fast)", () => {
|
|
|
317
317
|
|
|
318
318
|
it("c2-12a:api.tables 与 schema.yaml 表集不一致 → 构建期拒绝", async () => {
|
|
319
319
|
const m = readManifest();
|
|
320
|
-
m.api.tables = ["
|
|
320
|
+
m.api.tables = ["demo_solution_note", "ghost"];
|
|
321
321
|
writeManifest(m);
|
|
322
322
|
await expect(pack()).rejects.toThrow(/C2-12a/);
|
|
323
323
|
});
|
|
324
324
|
|
|
325
|
+
it("c2-12c:schema.yaml 缺 prefix / 表名未带方案前缀 → 构建期拒绝", async () => {
|
|
326
|
+
const schemaPath = join(project, "api/src/demo-solution/schema.yaml");
|
|
327
|
+
writeFileSync(schemaPath, "tables:\n demo_solution_note: {}\n");
|
|
328
|
+
await expect(pack()).rejects.toThrow(/C2-12c.*prefix/);
|
|
329
|
+
|
|
330
|
+
writeFileSync(schemaPath, "prefix: demo_solution_\ntables:\n note: {}\n");
|
|
331
|
+
await expect(pack()).rejects.toThrow(/C2-12c.*demo_solution_note/);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("c2-12c:prefix 落在第一方保留段 → 构建期拒绝", async () => {
|
|
335
|
+
const m = readManifest();
|
|
336
|
+
m.api.tables = ["oj_note"];
|
|
337
|
+
writeManifest(m);
|
|
338
|
+
const schemaPath = join(project, "api/src/demo-solution/schema.yaml");
|
|
339
|
+
writeFileSync(schemaPath, "prefix: oj_\ntables:\n oj_note: {}\n");
|
|
340
|
+
await expect(pack()).rejects.toThrow(/C2-12c.*保留段/);
|
|
341
|
+
});
|
|
342
|
+
|
|
325
343
|
it("c2-25:locales 缺引用 key → 构建期 fail-fast", async () => {
|
|
326
344
|
const m = readManifest();
|
|
327
345
|
m.roles[0].title_key = "demo-solution:role.absent";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
-- 0001__init:建
|
|
2
|
-
CREATE TABLE IF NOT EXISTS
|
|
1
|
+
-- 0001__init:建 demo_solution_note 表(与 schema.yaml 列集一致;oj migrate 按 seq 升序执行)
|
|
2
|
+
CREATE TABLE IF NOT EXISTS demo_solution_note (
|
|
3
3
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
4
4
|
title TEXT NOT NULL,
|
|
5
5
|
create_time INTEGER NOT NULL
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# 声明式表结构(形态对齐 apps/playground-oj/api/src/demo/schema.yaml)。
|
|
2
|
-
#
|
|
2
|
+
# C2-12c:prefix 必须 = 模块名 kebab→snake 再补尾部 `_`(demo-solution → demo_solution_),
|
|
3
|
+
# 且每张表名以它开头——表名全库共享,前缀把归属编码进名字,避免方案之间撞表。
|
|
4
|
+
prefix: demo_solution_
|
|
5
|
+
# 契约 C2-12a:bundle manifest 的 api.tables 必须与本表 keys 一致(oj 冗余校验 fail-fast)。
|
|
3
6
|
tables:
|
|
4
|
-
|
|
7
|
+
demo_solution_note:
|
|
5
8
|
pk: id
|
|
6
9
|
columns:
|
|
7
10
|
id: {type: integer, autoincrement: true}
|