@oj-module/cli 0.2.2 → 0.2.5
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 +14 -3
- package/devkit/SKILL.md +16 -4
- package/devkit/framework-dev.md +8 -6
- package/devkit/manual.md +243 -23
- package/package.json +2 -2
- package/shell-dist/assets/runtime.js +143 -8
- package/shell-dist/index.html +2 -2
- package/shell-dist/versions.json +3 -3
- package/src/args.ts +67 -0
- package/src/build.ts +11 -3
- package/src/bundle.ts +197 -0
- package/src/contract/__tests__/handler-scan.test.ts +82 -0
- package/src/contract/check.ts +2 -1
- package/src/contract/handler-scan.ts +32 -0
- package/src/deterministic-tar.ts +120 -0
- package/src/dev.ts +4 -2
- package/src/enable.ts +28 -0
- package/src/index.ts +63 -2
- package/src/info.ts +3 -2
- package/src/init.ts +147 -14
- package/src/install.ts +268 -0
- package/src/keygen.ts +88 -0
- package/src/layout.ts +31 -0
- package/src/lifecycle/__tests__/authoring.test.ts +109 -0
- package/src/lifecycle/__tests__/bundle.test.ts +150 -0
- package/src/lifecycle/__tests__/deterministic-tar.test.ts +145 -0
- package/src/lifecycle/__tests__/fixtures/README.md +59 -0
- package/src/lifecycle/__tests__/fixtures/fixture-invalid/EXPECTED-VIOLATIONS.md +27 -0
- package/src/lifecycle/__tests__/fixtures/fixture-invalid/api/src/Demo/manifest.yaml +6 -0
- package/src/lifecycle/__tests__/fixtures/fixture-invalid/manifest.yaml +40 -0
- package/src/lifecycle/__tests__/fixtures/fixture-invalid/web/src/Demo/entry.ts +14 -0
- package/src/lifecycle/__tests__/fixtures/fixture-invalid/web/src/Demo/locales/en-US.json +12 -0
- package/src/lifecycle/__tests__/fixtures/fixture-invalid/web/src/Demo/locales/zh-CN.json +12 -0
- package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/manifest.yaml +9 -0
- package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/migrations/0001__init.sql +6 -0
- package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/ping/api.ts +9 -0
- package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/schema.yaml +9 -0
- package/src/lifecycle/__tests__/fixtures/fixture-valid/manifest.yaml +96 -0
- package/src/lifecycle/__tests__/fixtures/fixture-valid/web/src/demo-solution/entry.ts +23 -0
- package/src/lifecycle/__tests__/fixtures/fixture-valid/web/src/demo-solution/locales/en-US.json +29 -0
- package/src/lifecycle/__tests__/fixtures/fixture-valid/web/src/demo-solution/locales/zh-CN.json +29 -0
- package/src/lifecycle/__tests__/fixtures/sig/demo-solution-0.2.0.tgz +0 -0
- package/src/lifecycle/__tests__/fixtures/sig/demo-solution-0.2.0.tgz.bad.sig +7 -0
- package/src/lifecycle/__tests__/fixtures/sig/demo-solution-0.2.0.tgz.sig +7 -0
- package/src/lifecycle/__tests__/fixtures/sig/demo-solution-0.2.0.tgz.unknown.sig +7 -0
- package/src/lifecycle/__tests__/fixtures/sig/gen-signed-fixtures.d.mts +21 -0
- package/src/lifecycle/__tests__/fixtures/sig/gen-signed-fixtures.mjs +196 -0
- package/src/lifecycle/__tests__/helpers.ts +78 -0
- package/src/lifecycle/__tests__/install.test.ts +514 -0
- package/src/lifecycle/__tests__/keygen.test.ts +61 -0
- package/src/lifecycle/__tests__/manifest-contract.test.ts +228 -0
- package/src/lifecycle/__tests__/publish.test.ts +80 -0
- package/src/lifecycle/__tests__/sig-fixtures.test.ts +100 -0
- package/src/lifecycle/__tests__/sign.test.ts +131 -0
- package/src/lifecycle/__tests__/smoke.test.ts +61 -0
- package/src/lifecycle/__tests__/trust.test.ts +177 -0
- package/src/lifecycle/__tests__/uninstall.test.ts +87 -0
- package/src/lifecycle/keygen.ts +40 -0
- package/src/lifecycle/sign.ts +74 -0
- package/src/manifest-contract.ts +696 -0
- package/src/oj-cert.ts +19 -0
- package/src/oj-cmd.ts +3 -3
- package/src/preview.ts +2 -2
- package/src/publish.ts +150 -0
- package/src/sign.ts +128 -0
- package/src/site.ts +146 -0
- package/src/trust.ts +116 -0
- package/src/uninstall.ts +29 -0
- package/src/usage.ts +35 -1
- package/templates/README.md +1 -1
- package/templates/api/config.yaml +47 -0
- package/templates/api/seed.mysql.sql +1 -1
- package/templates/api/seed.pg.sql +1 -1
- package/templates/api/src/_platform/_lifecycle/atomic.ts +98 -0
- package/templates/api/src/_platform/_lifecycle/history.ts +31 -0
- package/templates/api/src/_platform/_lifecycle/installer.ts +1080 -0
- package/templates/api/src/_platform/_lifecycle/lock.ts +148 -0
- package/templates/api/src/_platform/_lifecycle/semver.ts +61 -0
- package/templates/api/src/_platform/_lifecycle/sha384.ts +187 -0
- package/templates/api/src/_platform/_lifecycle/state.ts +256 -0
- package/templates/api/src/_platform/_lifecycle/steps/deploy-web.ts +182 -0
- package/templates/api/src/_platform/_lifecycle/steps/download.ts +90 -0
- package/templates/api/src/_platform/_lifecycle/steps/extract.ts +239 -0
- package/templates/api/src/_platform/_lifecycle/steps/migrate.ts +363 -0
- package/templates/api/src/_platform/_lifecycle/steps/preflight.ts +590 -0
- package/templates/api/src/_platform/_lifecycle/steps/register.ts +211 -0
- package/templates/api/src/_platform/_lifecycle/steps/reload.ts +81 -0
- package/templates/api/src/_platform/_lifecycle/steps/smoke.ts +216 -0
- package/templates/api/src/_platform/_lifecycle/steps/verify.ts +207 -0
- package/templates/api/src/_platform/_lifecycle/tasks.ts +119 -0
- package/templates/api/src/_platform/_lifecycle/text-edit.ts +110 -0
- package/templates/api/src/_platform/_lifecycle/tgz-guard.ts +41 -0
- package/templates/api/src/_platform/_lifecycle/trust.ts +229 -0
- package/templates/api/src/_platform/_lifecycle/types.ts +94 -0
- package/templates/api/src/_platform/_lifecycle/uninstall.ts +283 -0
- package/templates/api/src/_platform/_lifecycle/verify-checks.ts +228 -0
- package/templates/api/src/_platform/_lifecycle/verify-report.ts +92 -0
- package/templates/api/src/_platform/_shared/service/access.ts +2 -2
- package/templates/api/src/_platform/_shared/service/settings.ts +10 -1
- package/templates/api/src/_platform/_shared/service/solution-perms.ts +14 -7
- package/templates/api/src/_platform/_shared/site-facade.ts +44 -0
- package/templates/api/src/_platform/manifest.yaml +1 -0
- package/templates/api/src/_platform/market/api.ts +883 -0
- package/templates/api/src/_platform/market/bootstrap.ts +39 -0
- package/templates/api/src/_platform/migrations/0010__sys_solution_route.mysql.sql +17 -0
- package/templates/api/src/_platform/migrations/0010__sys_solution_route.pg.sql +16 -0
- package/templates/api/src/_platform/migrations/0010__sys_solution_route.sql +18 -0
- package/templates/api/src/_platform/migrations/0011__sys_setting_group_label.mysql.sql +2 -0
- package/templates/api/src/_platform/migrations/0011__sys_setting_group_label.sql +4 -0
- package/templates/api/src/_platform/migrations/0012__market_history.mysql.sql +13 -0
- package/templates/api/src/_platform/migrations/0012__market_history.pg.sql +12 -0
- package/templates/api/src/_platform/migrations/0012__market_history.sql +14 -0
- package/templates/api/src/_platform/schema.yaml +21 -4
- package/templates/api/src/_platform/seed.sql +2 -2
- package/templates/api/src/_platform/solutions/api.ts +212 -0
- package/templates/api/src/demo/manifest.yaml +5 -0
- package/templates/api/src/demo/migrations/0001__create_todos.sql +9 -0
- package/templates/api/src/demo/schema.yaml +11 -0
- package/templates/api/src/demo/seed.sql +5 -0
- package/templates/api/src/demo/todos/api.ts +59 -0
- package/templates/api/src/home/line/api.ts +57 -0
- package/templates/api/src/home/manifest.yaml +6 -0
- package/templates/api/src/home/migrations/0001__create_home_tables.sql +13 -0
- package/templates/api/src/home/pie/api.ts +40 -0
- package/templates/api/src/home/schema.yaml +14 -0
- package/templates/api/src/home/seed.sql +70 -0
- package/templates/api/src/system/oplog/api.ts +44 -2
- package/templates/api/tests/fixture.ts +16 -0
- package/templates/api/tests/lifecycle/atomic-state.spec.ts +183 -0
- package/templates/api/tests/lifecycle/bootstrap.spec.ts +116 -0
- package/templates/api/tests/lifecycle/deploy-smoke.spec.ts +492 -0
- package/templates/api/tests/lifecycle/installer.spec.ts +1218 -0
- package/templates/api/tests/lifecycle/market-api.spec.ts +971 -0
- package/templates/api/tests/lifecycle/migrate.spec.ts +575 -0
- package/templates/api/tests/lifecycle/preflight.spec.ts +255 -0
- package/templates/api/tests/lifecycle/register.spec.ts +194 -0
- package/templates/api/tests/lifecycle/semver.spec.ts +43 -0
- package/templates/api/tests/lifecycle/sha384.spec.ts +48 -0
- package/templates/api/tests/lifecycle/sig-fixtures.spec.ts +65 -0
- package/templates/api/tests/lifecycle/solutions-api.spec.ts +98 -0
- package/templates/api/tests/lifecycle/steps-fs.spec.ts +623 -0
- package/templates/api/tests/lifecycle/text-edit.spec.ts +177 -0
- package/templates/api/tests/lifecycle/tgz-guard.spec.ts +103 -0
- package/templates/api/tests/lifecycle/trust.spec.ts +310 -0
- package/templates/api/tests/lifecycle/uninstall.spec.ts +386 -0
- package/templates/api/tests/lifecycle/verify-checks.spec.ts +385 -0
- package/templates/api/tests/lifecycle/verify-report.spec.ts +290 -0
- package/templates/api/tests/rbac-settings.spec.ts +8 -0
- package/templates/init.include.yaml +56 -0
- package/templates/openapi.json +527 -0
- package/templates/package.json +52 -0
- package/templates/web/src/_platform/__client/api.schemas.ts +8 -0
- package/templates/web/src/_platform/__client/api.ts +335 -0
- package/templates/web/src/about/entry.ts +42 -0
- package/templates/web/src/about/locales/en-US.json +16 -0
- package/templates/web/src/about/locales/zh-CN.json +16 -0
- package/templates/web/src/about/pages/constants.ts +20 -0
- package/templates/web/src/about/pages/index.tsx +135 -0
- package/templates/web/src/access/entry.ts +74 -0
- package/templates/web/src/access/locales/en-US.json +46 -0
- package/templates/web/src/access/locales/zh-CN.json +46 -0
- package/templates/web/src/access/pages/access-mode/index.tsx +16 -0
- package/templates/web/src/access/pages/admin-visible/index.tsx +16 -0
- package/templates/web/src/access/pages/button-control/index.tsx +181 -0
- package/templates/web/src/access/pages/common-visible/index.tsx +16 -0
- package/templates/web/src/access/pages/page-control/index.tsx +142 -0
- package/templates/web/src/demo/__client/api.schemas.ts +20 -0
- package/templates/web/src/demo/__client/api.ts +89 -0
- package/templates/web/src/exception/entry.ts +80 -0
- package/templates/web/src/exception/locales/en-US.json +13 -0
- package/templates/web/src/exception/locales/zh-CN.json +13 -0
- package/templates/web/src/exception/pages/403/index.tsx +30 -0
- package/templates/web/src/exception/pages/404/index.tsx +30 -0
- package/templates/web/src/exception/pages/500/index.tsx +30 -0
- package/templates/web/src/exception/pages/unknown-component/index.tsx +38 -0
- package/templates/web/src/home/__client/api.schemas.ts +22 -0
- package/templates/web/src/home/__client/api.ts +113 -0
- package/templates/web/src/login/locales/en-US.json +6 -0
- package/templates/web/src/login/locales/zh-CN.json +6 -0
- package/templates/web/src/login/pages/login.tsx +67 -18
- package/templates/web/src/oj-market/__tests__/CardGrid.test.tsx +93 -0
- package/templates/web/src/oj-market/__tests__/HistoryDrawer.test.tsx +28 -0
- package/templates/web/src/oj-market/__tests__/InstallDialog.test.tsx +69 -0
- package/templates/web/src/oj-market/__tests__/RollbackDialog.test.tsx +43 -0
- package/templates/web/src/oj-market/__tests__/UninstallConfirm.test.tsx +30 -0
- package/templates/web/src/oj-market/__tests__/marketClient.fake.ts +70 -0
- package/templates/web/src/oj-market/__tests__/poll.test.ts +24 -0
- package/templates/web/src/oj-market/__tests__/render.tsx +14 -0
- package/templates/web/src/oj-market/__tests__/runtime-stub.ts +14 -0
- package/templates/web/src/oj-market/__tests__/semver.test.ts +26 -0
- package/templates/web/src/oj-market/api/MarketClientProvider.tsx +12 -0
- package/templates/web/src/oj-market/api/client.ts +135 -0
- package/templates/web/src/oj-market/api/context.ts +11 -0
- package/templates/web/src/oj-market/api/defaultClient.ts +7 -0
- package/templates/web/src/oj-market/components/CardGrid.tsx +76 -0
- package/templates/web/src/oj-market/components/CardItem.tsx +102 -0
- package/templates/web/src/oj-market/components/DetailDrawer.tsx +53 -0
- package/templates/web/src/oj-market/components/HistoryDrawer.tsx +75 -0
- package/templates/web/src/oj-market/components/InstallDialog.tsx +112 -0
- package/templates/web/src/oj-market/components/RollbackDialog.tsx +79 -0
- package/templates/web/src/oj-market/components/UninstallConfirm.tsx +74 -0
- package/templates/web/src/oj-market/entry.ts +27 -0
- package/templates/web/src/oj-market/hooks/useMarket.ts +91 -0
- package/templates/web/src/oj-market/locales/en-US.json +100 -0
- package/templates/web/src/oj-market/locales/zh-CN.json +100 -0
- package/templates/web/src/oj-market/pages/MarketApp.tsx +136 -0
- package/templates/web/src/oj-market/types.ts +84 -0
- package/templates/web/src/oj-market/utils/poll.ts +36 -0
- package/templates/web/src/oj-market/utils/semver.ts +14 -0
- package/templates/web/src/outside/entry.ts +80 -0
- package/templates/web/src/outside/locales/en-US.json +10 -0
- package/templates/web/src/outside/locales/zh-CN.json +10 -0
- package/templates/web/src/personal-center/entry.ts +1 -10
- package/templates/web/src/personal-center/locales/en-US.json +1 -5
- package/templates/web/src/personal-center/locales/zh-CN.json +1 -5
- package/templates/web/src/route-nest/entry.ts +73 -0
- package/templates/web/src/route-nest/locales/en-US.json +9 -0
- package/templates/web/src/route-nest/locales/zh-CN.json +9 -0
- package/templates/web/src/route-nest/pages/menu1/menu1-1/index.tsx +5 -0
- package/templates/web/src/route-nest/pages/menu1/menu1-2/index.tsx +5 -0
- package/templates/web/src/route-nest/pages/menu2/index.tsx +5 -0
- package/templates/web/src/system/__client/api.schemas.ts +12 -0
- package/templates/web/src/system/__client/api.ts +25 -1
- package/templates/web/src/system/pages/oplog/index.tsx +1 -1
- package/templates/web/src/system/pages/roles/index.tsx +1 -0
- package/templates/web/src/system/pages/settings/index.tsx +1 -1
- package/templates/web/src/system/pages/users/index.tsx +1 -1
- package/templates/web.config.ts +32 -40
- package/vendor/host-versions.json +3 -3
- package/templates/pnpm-workspace.yaml +0 -14
- package/templates/web/src/personal-center/pages/settings/index.tsx +0 -5
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
-- home 模块种子(P4-1)。按分号朴素切分(S006)。
|
|
2
|
+
-- home_pie:五分类(code 对齐前端 DATA_KEY),幂等 INSERT OR IGNORE。
|
|
3
|
+
INSERT OR IGNORE INTO home_pie (category, value) VALUES ('electronics', 320);
|
|
4
|
+
INSERT OR IGNORE INTO home_pie (category, value) VALUES ('home_goods', 210);
|
|
5
|
+
INSERT OR IGNORE INTO home_pie (category, value) VALUES ('apparel_accessories', 180);
|
|
6
|
+
INSERT OR IGNORE INTO home_pie (category, value) VALUES ('food_beverages', 150);
|
|
7
|
+
INSERT OR IGNORE INTO home_pie (category, value) VALUES ('beauty_skincare', 95);
|
|
8
|
+
|
|
9
|
+
-- home_line:日窗口序列(day = epoch-day,种子围绕"当前日期"生成,约 60 天窗口)。
|
|
10
|
+
-- 刻意缺 today-30 / today-12 两天,演示"缺哪天补哪天"——handler 在窗口内对缺失日补 0;
|
|
11
|
+
-- 若整个窗口无 seed(seed 随时间变旧),handler 回落确定式趋势兜底(懒补数)。
|
|
12
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20640, 181);
|
|
13
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20641, 190);
|
|
14
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20642, 199);
|
|
15
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20643, 208);
|
|
16
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20644, 217);
|
|
17
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20645, 226);
|
|
18
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20646, 235);
|
|
19
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20647, 244);
|
|
20
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20648, 253);
|
|
21
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20649, 262);
|
|
22
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20650, 271);
|
|
23
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20651, 280);
|
|
24
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20652, 289);
|
|
25
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20653, 298);
|
|
26
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20654, 100);
|
|
27
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20655, 109);
|
|
28
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20656, 118);
|
|
29
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20657, 127);
|
|
30
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20658, 136);
|
|
31
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20659, 145);
|
|
32
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20660, 154);
|
|
33
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20661, 163);
|
|
34
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20662, 172);
|
|
35
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20663, 181);
|
|
36
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20664, 190);
|
|
37
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20665, 199);
|
|
38
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20666, 208);
|
|
39
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20667, 217);
|
|
40
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20668, 226);
|
|
41
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20669, 235);
|
|
42
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20671, 253);
|
|
43
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20672, 262);
|
|
44
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20673, 271);
|
|
45
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20674, 280);
|
|
46
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20675, 289);
|
|
47
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20676, 298);
|
|
48
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20677, 100);
|
|
49
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20678, 109);
|
|
50
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20679, 118);
|
|
51
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20680, 127);
|
|
52
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20681, 136);
|
|
53
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20682, 145);
|
|
54
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20683, 154);
|
|
55
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20684, 163);
|
|
56
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20685, 172);
|
|
57
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20686, 181);
|
|
58
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20687, 190);
|
|
59
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20689, 208);
|
|
60
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20690, 217);
|
|
61
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20691, 226);
|
|
62
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20692, 235);
|
|
63
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20693, 244);
|
|
64
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20694, 253);
|
|
65
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20695, 262);
|
|
66
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20696, 271);
|
|
67
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20697, 280);
|
|
68
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20698, 289);
|
|
69
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20699, 298);
|
|
70
|
+
INSERT OR IGNORE INTO home_line (day, value) VALUES (20700, 100);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { respond } from "../../_platform/_shared/respond";
|
|
2
2
|
// GET /api/system/oplog?action=&username=&module=&limit= —— 操作日志查询(AC1-29;D13 admin 可查)。
|
|
3
|
-
|
|
3
|
+
// POST /api/system/oplog —— 操作日志写入(C3;登录态即可,解决方案模块记录自身操作)。
|
|
4
|
+
import { assertSystemAdmin, currentAccess } from "../../_platform/_shared/service/access";
|
|
5
|
+
import { recordOp } from "../../_platform/_shared/service/audit";
|
|
4
6
|
import { searchOplog } from "../../_platform/_shared/service/oplog";
|
|
5
7
|
|
|
6
8
|
async function get() {
|
|
@@ -50,4 +52,44 @@ get.response = {
|
|
|
50
52
|
},
|
|
51
53
|
};
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
// POST /api/system/oplog —— 写操作日志(C3)。登录态即可(审计写入面向当前用户,非仅管理员);
|
|
56
|
+
// module 由调用方自身模块名传入(脱敏红线:detail 不得含口令/密钥/token)。
|
|
57
|
+
async function post() {
|
|
58
|
+
await respond(async () => {
|
|
59
|
+
await currentAccess();
|
|
60
|
+
const body = (http.body || {}) as Record<string, unknown>;
|
|
61
|
+
const action = String(body.action ?? "");
|
|
62
|
+
const module = String(body.module ?? "");
|
|
63
|
+
if (!action || !module)
|
|
64
|
+
throw Object.assign(new Error("action and module are required"), { status: 400 });
|
|
65
|
+
await recordOp({
|
|
66
|
+
action,
|
|
67
|
+
module,
|
|
68
|
+
version: body.version == null ? undefined : String(body.version),
|
|
69
|
+
target: body.target == null ? undefined : String(body.target),
|
|
70
|
+
detail: body.detail,
|
|
71
|
+
level: typeof body.level === "number" ? body.level : undefined,
|
|
72
|
+
});
|
|
73
|
+
return { ok: true as const };
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
post.schema = {
|
|
77
|
+
body: {
|
|
78
|
+
type: "object",
|
|
79
|
+
required: ["action", "module"],
|
|
80
|
+
properties: {
|
|
81
|
+
action: { type: "string", minLength: 1 },
|
|
82
|
+
module: { type: "string", minLength: 1 },
|
|
83
|
+
version: { type: "string" },
|
|
84
|
+
target: { type: "string" },
|
|
85
|
+
level: { type: "number" },
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
post.response = {
|
|
90
|
+
type: "object",
|
|
91
|
+
required: ["ok"],
|
|
92
|
+
properties: { ok: { type: "boolean" } },
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export default { get, post };
|
|
@@ -13,6 +13,22 @@ const SRC = join(dirname(fileURLToPath(import.meta.url)), "..", "src");
|
|
|
13
13
|
|
|
14
14
|
export const ADMIN = 1;
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* market_history 同构 DDL:表真源 = 市场方案包自带迁移(oj-market 0002__market_history),
|
|
18
|
+
* 不属平台迁移(installDb 不回放)——需要断言留痕的用例显式执行;守卫语义(表不存在静默跳过)
|
|
19
|
+
* 另有专项用例钉住。
|
|
20
|
+
*/
|
|
21
|
+
export const MARKET_HISTORY_DDL = `CREATE TABLE market_history (
|
|
22
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
23
|
+
solution TEXT NOT NULL,
|
|
24
|
+
version TEXT NOT NULL,
|
|
25
|
+
action TEXT NOT NULL,
|
|
26
|
+
status TEXT NOT NULL,
|
|
27
|
+
operator TEXT,
|
|
28
|
+
message TEXT,
|
|
29
|
+
created_at INTEGER NOT NULL
|
|
30
|
+
)`;
|
|
31
|
+
|
|
16
32
|
/** 建内存库 + 注入全局;返回 DatabaseSync 供用例直插数据/断言。 */
|
|
17
33
|
export function installDb(): any {
|
|
18
34
|
const dbo = new DatabaseSync(":memory:");
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// BDD:状态文件原子写 + sidecar 状态存储(D12/D13,P1-4)。
|
|
2
|
+
// atomicWriteText:同目录 tmp + rename 原子替换,一切状态落盘(state.yaml/manifests.yaml/任务文件)的底座。
|
|
3
|
+
// StateStore:.solutions/state.yaml 读写;YAML 为受限子集(两层 map + 标量),
|
|
4
|
+
// 只保证读回自己写出的内容;任何不符格式的字节一律视为损坏 → 空状态,损坏上下文并入
|
|
5
|
+
// 读结果(corruptNote 出口,禁 console——release isolate console 抛 BadResource,Finding A)。
|
|
6
|
+
// fs 依赖经 globalThis.fs 注入(平台 isolate 注入 FsApi;测试注入 node:fs/promises 适配)。
|
|
7
|
+
import type { FsFace } from "../../src/_platform/_lifecycle/atomic";
|
|
8
|
+
import * as fsp from "node:fs/promises";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
13
|
+
import { atomicWriteText } from "../../src/_platform/_lifecycle/atomic";
|
|
14
|
+
import { createStateStore } from "../../src/_platform/_lifecycle/state";
|
|
15
|
+
|
|
16
|
+
const g = globalThis as any;
|
|
17
|
+
|
|
18
|
+
function nodeFsFace(): FsFace {
|
|
19
|
+
return {
|
|
20
|
+
writeFile: (path, data) => fsp.writeFile(path, data),
|
|
21
|
+
readFile: path => fsp.readFile(path),
|
|
22
|
+
mkdir: (path, opts) => fsp.mkdir(path, opts),
|
|
23
|
+
remove: (path, opts) => fsp.rm(path, opts),
|
|
24
|
+
rename: (oldPath, newPath) => fsp.rename(oldPath, newPath),
|
|
25
|
+
copyFile: (src, dest) => fsp.copyFile(src, dest),
|
|
26
|
+
readdir: path => fsp.readdir(path),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let root: string;
|
|
31
|
+
|
|
32
|
+
beforeEach(async () => {
|
|
33
|
+
root = await fsp.mkdtemp(join(tmpdir(), "ojm-atomic-state-"));
|
|
34
|
+
g.fs = nodeFsFace();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(async () => {
|
|
38
|
+
g.fs = undefined;
|
|
39
|
+
await fsp.rm(root, { recursive: true, force: true });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("atomicWriteText(D13)", () => {
|
|
43
|
+
it("写入后读回内容逐字节完整(无半截态)", async () => {
|
|
44
|
+
const p = join(root, "state.yaml");
|
|
45
|
+
await atomicWriteText(p, "solutions:\n demo:\n version: \"1.0.0\"\n");
|
|
46
|
+
expect(await fsp.readFile(p, "utf8")).toBe("solutions:\n demo:\n version: \"1.0.0\"\n");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("并发双写交错完成后内容完整,无交错字节(最终为其中一份完整内容)", async () => {
|
|
50
|
+
const p = join(root, "f.yaml");
|
|
51
|
+
const contentA = `${"A".repeat(5000)}\n`;
|
|
52
|
+
const contentB = `${"B".repeat(5000)}\n`;
|
|
53
|
+
await Promise.all([atomicWriteText(p, contentA), atomicWriteText(p, contentB)]);
|
|
54
|
+
const final = await fsp.readFile(p, "utf8");
|
|
55
|
+
expect(final === contentA || final === contentB).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("rename 失败(模拟 tmp 源缺失)时旧文件原样保留、tmp 清理、错误上抛", async () => {
|
|
59
|
+
const p = join(root, "f.yaml");
|
|
60
|
+
await atomicWriteText(p, "old\n");
|
|
61
|
+
const base = nodeFsFace();
|
|
62
|
+
let renameCalls = 0;
|
|
63
|
+
g.fs = {
|
|
64
|
+
...base,
|
|
65
|
+
rename: (oldPath: string, newPath: string) => {
|
|
66
|
+
renameCalls++;
|
|
67
|
+
if (renameCalls === 1) {
|
|
68
|
+
throw new Error("rename 源不存在(模拟)");
|
|
69
|
+
}
|
|
70
|
+
return base.rename(oldPath, newPath);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
await expect(atomicWriteText(p, "new\n")).rejects.toThrow(/rename 源不存在/);
|
|
74
|
+
expect(await fsp.readFile(p, "utf8")).toBe("old\n");
|
|
75
|
+
expect((await fsp.readdir(root)).sort()).toEqual(["f.yaml"]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("父目录不存在时自动创建(首装 .solutions/ 场景)", async () => {
|
|
79
|
+
const p = join(root, ".solutions", "state.yaml");
|
|
80
|
+
await atomicWriteText(p, "solutions: {}\n");
|
|
81
|
+
expect(await fsp.readFile(p, "utf8")).toBe("solutions: {}\n");
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("stateStore(D12)", () => {
|
|
86
|
+
it("write 后新实例 read 读回一致(落盘格式 round-trip,含 prerelease 版本与 enabled=false)", async () => {
|
|
87
|
+
const p = join(root, ".solutions", "state.yaml");
|
|
88
|
+
const store = await createStateStore(p);
|
|
89
|
+
await store.write({
|
|
90
|
+
solutions: {
|
|
91
|
+
wiki: { version: "1.0.0-beta.1+build.5", enabled: false, installedAt: 1730000000001 },
|
|
92
|
+
demo: { version: "0.2.0", enabled: true, installedAt: 1730000000000 },
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
const again = await createStateStore(p);
|
|
96
|
+
expect(again.read()).toEqual({
|
|
97
|
+
solutions: {
|
|
98
|
+
wiki: { version: "1.0.0-beta.1+build.5", enabled: false, installedAt: 1730000000001 },
|
|
99
|
+
demo: { version: "0.2.0", enabled: true, installedAt: 1730000000000 },
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("patch 合并不丢其他方案键", async () => {
|
|
105
|
+
const p = join(root, ".solutions", "state.yaml");
|
|
106
|
+
const store = await createStateStore(p);
|
|
107
|
+
await store.write({ solutions: { demo: { version: "1.0.0", enabled: true, installedAt: 1 } } });
|
|
108
|
+
await store.write({ solutions: { wiki: { version: "2.0.0", enabled: true, installedAt: 2 } } });
|
|
109
|
+
expect(store.read()).toEqual({
|
|
110
|
+
solutions: {
|
|
111
|
+
demo: { version: "1.0.0", enabled: true, installedAt: 1 },
|
|
112
|
+
wiki: { version: "2.0.0", enabled: true, installedAt: 2 },
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("patch 值为 null 删除该方案键(GC/卸载删除出口)", async () => {
|
|
118
|
+
const p = join(root, ".solutions", "state.yaml");
|
|
119
|
+
const store = await createStateStore(p);
|
|
120
|
+
await store.write({
|
|
121
|
+
solutions: {
|
|
122
|
+
demo: { version: "1.0.0", enabled: true, installedAt: 1 },
|
|
123
|
+
wiki: { version: "2.0.0", enabled: true, installedAt: 2 },
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
await store.write({ solutions: { demo: null as any } });
|
|
127
|
+
expect(store.read()).toEqual({ solutions: { wiki: { version: "2.0.0", enabled: true, installedAt: 2 } } });
|
|
128
|
+
const again = await createStateStore(p);
|
|
129
|
+
expect(again.read()).toEqual({ solutions: { wiki: { version: "2.0.0", enabled: true, installedAt: 2 } } });
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("read 不存在的文件返回空状态不炸", async () => {
|
|
133
|
+
const store = await createStateStore(join(root, "nope", "state.yaml"));
|
|
134
|
+
expect(store.read()).toEqual({ solutions: {} });
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("read 返回快照:调用方改动不影响 store 后续读数", async () => {
|
|
138
|
+
const store = await createStateStore(join(root, "state.yaml"));
|
|
139
|
+
await store.write({ solutions: { demo: { version: "1.0.0", enabled: true, installedAt: 1 } } });
|
|
140
|
+
const snapshot = store.read();
|
|
141
|
+
snapshot.solutions.demo.version = "9.9.9";
|
|
142
|
+
expect(store.read().solutions.demo.version).toBe("1.0.0");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("损坏 yaml 返回空状态,损坏上下文并入读结果 corruptNote(禁 console——release isolate console 抛 BadResource,Finding A)", async () => {
|
|
146
|
+
const p = join(root, "state.yaml");
|
|
147
|
+
await fsp.writeFile(p, "solutions:\n demo:\n version: [broken\n");
|
|
148
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
149
|
+
try {
|
|
150
|
+
const store = await createStateStore(p);
|
|
151
|
+
expect(store.read()).toEqual({ solutions: {} });
|
|
152
|
+
expect(store.corruptNote?.()).toContain("不是本模块写出的合法状态文件");
|
|
153
|
+
expect(store.corruptNote?.()).toContain("条目格式不完整或字段损坏");
|
|
154
|
+
expect(warn).not.toHaveBeenCalled();
|
|
155
|
+
// 自愈重写后 note 清零(写成功 = 盘上真身合法)
|
|
156
|
+
await store.write({ solutions: { demo: { version: "1.0.0", enabled: true, installedAt: 1 } } });
|
|
157
|
+
expect(store.corruptNote?.()).toBeNull();
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
warn.mockRestore();
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("盘上内容损坏时 write 以 patch 为基自愈重写(不因坏文件拒写)", async () => {
|
|
165
|
+
const p = join(root, "state.yaml");
|
|
166
|
+
await fsp.writeFile(p, "这不是状态文件");
|
|
167
|
+
const store = await createStateStore(p);
|
|
168
|
+
await store.write({ solutions: { demo: { version: "1.0.0", enabled: true, installedAt: 1 } } });
|
|
169
|
+
const again = await createStateStore(p);
|
|
170
|
+
expect(again.read()).toEqual({ solutions: { demo: { version: "1.0.0", enabled: true, installedAt: 1 } } });
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("非法条目(version 含换行)拒绝落盘,旧状态原样保留", async () => {
|
|
174
|
+
const p = join(root, "state.yaml");
|
|
175
|
+
const store = await createStateStore(p);
|
|
176
|
+
await store.write({ solutions: { demo: { version: "1.0.0", enabled: true, installedAt: 1 } } });
|
|
177
|
+
await expect(store.write({ solutions: { wiki: { version: "2.0.0\nenabled: true", enabled: true, installedAt: 2 } } }))
|
|
178
|
+
.rejects
|
|
179
|
+
.toThrow(/version/);
|
|
180
|
+
expect(store.read().solutions.demo.version).toBe("1.0.0");
|
|
181
|
+
expect(Object.keys(store.read().solutions)).toEqual(["demo"]);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// BDD:第一方 bootstrap key(P2-12,G3 双轨第一轨)。
|
|
2
|
+
// ① 播种机制:信任库文件尚不存在且 key 在场 → 播种一次(market 首装免 TOFU);
|
|
3
|
+
// key 未回填(null)→ no-op;库文件已存在(含空库 {})→ 不播种——不与运维吊销对抗(G5)。
|
|
4
|
+
// ② 私钥红线钉测:packages/cli 全树(源码 + 模板)零私钥材料——文件名(*.pem/*private*)
|
|
5
|
+
// 与 PEM 私钥块实体(header 后跟 ≥100 字符 base64 体;tasks.ts redact 正则与测试假体
|
|
6
|
+
// 体长 <100 不误伤)双口径扫描。
|
|
7
|
+
// ③ 集成(market-api.spec):信任库为空 + bootstrapKey 注入 → cdn 安装直达 done 不落 needs_trust。
|
|
8
|
+
import type { FsFace } from "../../src/_platform/_lifecycle/atomic";
|
|
9
|
+
import * as fsp from "node:fs/promises";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
14
|
+
import { createTrustStore } from "../../src/_platform/_lifecycle/trust";
|
|
15
|
+
import { BOOTSTRAP_KEY, seedBootstrapKey } from "../../src/_platform/market/bootstrap";
|
|
16
|
+
|
|
17
|
+
const g = globalThis as any;
|
|
18
|
+
|
|
19
|
+
const KEY = { key_id: "test.publisher", public_key: "a".repeat(64) };
|
|
20
|
+
|
|
21
|
+
function nodeFsFace(): FsFace {
|
|
22
|
+
return {
|
|
23
|
+
writeFile: (path, data) => fsp.writeFile(path, data),
|
|
24
|
+
readFile: path => fsp.readFile(path),
|
|
25
|
+
mkdir: (path, opts) => fsp.mkdir(path, opts),
|
|
26
|
+
remove: (path, opts) => fsp.rm(path, opts),
|
|
27
|
+
rename: (oldPath, newPath) => fsp.rename(oldPath, newPath),
|
|
28
|
+
copyFile: (src, dest) => fsp.copyFile(src, dest),
|
|
29
|
+
readdir: path => fsp.readdir(path),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let dir: string;
|
|
34
|
+
|
|
35
|
+
beforeEach(async () => {
|
|
36
|
+
g.fs = nodeFsFace();
|
|
37
|
+
dir = await fsp.mkdtemp(join(tmpdir(), "ojm-bootstrap-"));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
afterEach(async () => {
|
|
41
|
+
g.fs = undefined;
|
|
42
|
+
await fsp.rm(dir, { recursive: true, force: true });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("seedBootstrapKey 播种机制(P2-12:G3 双轨第一轨)", () => {
|
|
46
|
+
it("key 在场 + 信任库文件不存在 → 播种一次(get 读回同公钥)", async () => {
|
|
47
|
+
const trustPath = join(dir, "trusted-keys.yaml");
|
|
48
|
+
await seedBootstrapKey(trustPath, KEY);
|
|
49
|
+
expect(await createTrustStore(trustPath).get(KEY.key_id)).toBe(KEY.public_key);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("key 为 null(发布时回填前的内置口径)→ no-op,不产生库文件", async () => {
|
|
53
|
+
const trustPath = join(dir, "trusted-keys.yaml");
|
|
54
|
+
await seedBootstrapKey(trustPath, null);
|
|
55
|
+
await expect(fsp.access(trustPath)).rejects.toThrow();
|
|
56
|
+
// 内置常量在回填前必须为 null(模板只带公钥机制、不带任何具体钥——发布时回填真钥)
|
|
57
|
+
expect(BOOTSTRAP_KEY).toBeNull();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("信任库文件已存在(含空库 {})→ 不播种:运维 remove 后不复活(G5)", async () => {
|
|
61
|
+
const trustPath = join(dir, "trusted-keys.yaml");
|
|
62
|
+
// 预置空库(已初始化形态)
|
|
63
|
+
await fsp.writeFile(trustPath, "trusted_keys: {}\n");
|
|
64
|
+
await seedBootstrapKey(trustPath, KEY);
|
|
65
|
+
expect(await createTrustStore(trustPath).get(KEY.key_id)).toBeNull();
|
|
66
|
+
// 预置他 key 库:不播种也不破坏既有内容
|
|
67
|
+
await fsp.writeFile(trustPath, `trusted_keys:\n other.pub:\n public_key: "${"b".repeat(64)}"\n added_at: 1\n`);
|
|
68
|
+
await seedBootstrapKey(trustPath, KEY);
|
|
69
|
+
const store = createTrustStore(trustPath);
|
|
70
|
+
expect(await store.get(KEY.key_id)).toBeNull();
|
|
71
|
+
expect(await store.get("other.pub")).toBe("b".repeat(64));
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("私钥红线钉测(P2-12:packages/cli 全树零私钥材料)", () => {
|
|
76
|
+
const CLI_ROOT = join(dirname(fileURLToPath(import.meta.url)), "../../../..");
|
|
77
|
+
|
|
78
|
+
async function* walk(root: string): AsyncGenerator<string> {
|
|
79
|
+
for (const name of await fsp.readdir(root)) {
|
|
80
|
+
if (name === "node_modules" || name === "dist" || name.startsWith(".")) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const p = join(root, name);
|
|
84
|
+
const st = await fsp.stat(p);
|
|
85
|
+
if (st.isDirectory()) {
|
|
86
|
+
yield* walk(p);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
yield p;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
it("无 *.pem / *private* 文件;PEM 私钥块 header 后无真实密钥体(≥100 base64 字符)", async () => {
|
|
95
|
+
const offenders: string[] = [];
|
|
96
|
+
for await (const p of walk(CLI_ROOT)) {
|
|
97
|
+
const base = p.split("/").pop() ?? p;
|
|
98
|
+
if (/\.pem$/i.test(base) || /private/i.test(base)) {
|
|
99
|
+
offenders.push(`${p}(文件名命中私钥形态)`);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
let text: string;
|
|
103
|
+
try {
|
|
104
|
+
text = new TextDecoder().decode(await fsp.readFile(p));
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
continue; // 二进制等不可解码文件跳过(文件名口径已覆盖)
|
|
108
|
+
}
|
|
109
|
+
// 私钥块实体:header 后跟 ≥100 字符连续 base64(redact 正则无体、测试假体 16 字符均不命中)
|
|
110
|
+
if (/-----BEGIN [A-Z ]*PRIVATE KEY-----\r?\n?[A-Za-z0-9+/=]{100,}/.test(text)) {
|
|
111
|
+
offenders.push(`${p}(PEM 私钥块实体命中)`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
expect(offenders).toEqual([]);
|
|
115
|
+
});
|
|
116
|
+
});
|