@whyshu/argent-engine 0.0.0-stage → 0.6.1
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/MANIFEST.json +341 -0
- package/engine/VERSION +1 -0
- package/engine/agents/catalog.json +454 -0
- package/engine/configs/_aliases.proposed.json +651 -0
- package/engine/configs/_migration-plan.proposed.json +899 -0
- package/engine/configs/account.json +272 -0
- package/engine/configs/advanced.json +205 -0
- package/engine/configs/host.json +149 -0
- package/engine/configs/hr.json +27 -0
- package/engine/configs/notify.json +264 -0
- package/engine/core/cli/argent.mjs +277 -0
- package/engine/core/contracts/argent.contracts.json +1065 -0
- package/engine/core/contracts/verbs.schema.json +2539 -0
- package/engine/core/lib/argent-datadir.mjs +223 -0
- package/engine/core/lib/audit.mjs +187 -0
- package/engine/core/lib/claim.mjs +258 -0
- package/engine/core/lib/config-catalog.mjs +1172 -0
- package/engine/core/lib/doctor-customer.mjs +577 -0
- package/engine/core/lib/flow-list.mjs +138 -0
- package/engine/core/lib/flow-registry.mjs +48 -0
- package/engine/core/lib/init.mjs +247 -0
- package/engine/core/lib/ledger.mjs +124 -0
- package/engine/core/lib/license-activate.mjs +813 -0
- package/engine/core/lib/license-device.mjs +314 -0
- package/engine/core/lib/license-file.mjs +349 -0
- package/engine/core/lib/license-guard.mjs +123 -0
- package/engine/core/lib/license-ops.mjs +1645 -0
- package/engine/core/lib/license-verdict.mjs +133 -0
- package/engine/core/lib/license.mjs +577 -0
- package/engine/core/lib/notify/dedup.mjs +101 -0
- package/engine/core/lib/notify/feishu.mjs +38 -0
- package/engine/core/lib/notify/index.mjs +95 -0
- package/engine/core/lib/notify/verb.mjs +327 -0
- package/engine/core/lib/notify/webhook.mjs +135 -0
- package/engine/core/lib/notify/wecom.mjs +36 -0
- package/engine/core/lib/python.mjs +145 -0
- package/engine/core/lib/sop.mjs +116 -0
- package/engine/core/lib/versions.mjs +205 -0
- package/engine/core/mcp/client.mjs +67 -0
- package/engine/core/mcp/http.mjs +138 -0
- package/engine/core/mcp/server.mjs +702 -0
- package/engine/scripts/_engine_rules_probe.py +15 -0
- package/engine/scripts/analyze-full-day.py +568 -0
- package/engine/scripts/archive/feishu-bridge.js +246 -0
- package/engine/scripts/argent-agents.mjs +527 -0
- package/engine/scripts/argent-capabilities.mjs +776 -0
- package/engine/scripts/argent-catalog.mjs +186 -0
- package/engine/scripts/argent-datadir.py +62 -0
- package/engine/scripts/argent-doctor.mjs +624 -0
- package/engine/scripts/argent-env-write.mjs +302 -0
- package/engine/scripts/argent-schedule.mjs +620 -0
- package/engine/scripts/argent-setup-check.mjs +369 -0
- package/engine/scripts/argent-upgrade.mjs +1046 -0
- package/engine/scripts/base-bootstrap.sh +576 -0
- package/engine/scripts/billing-mode-daily.sh +13 -0
- package/engine/scripts/build-engine-package.mjs +126 -0
- package/engine/scripts/capability-packs.lock.json +484 -0
- package/engine/scripts/capability_pack.py +1242 -0
- package/engine/scripts/check-1.0-criteria.mjs +355 -0
- package/engine/scripts/check-agent-catalog.mjs +141 -0
- package/engine/scripts/check-billing-mode.sh +378 -0
- package/engine/scripts/check-child-sessions.mjs +83 -0
- package/engine/scripts/check-config-catalog.mjs +677 -0
- package/engine/scripts/check-docs-drift.mjs +522 -0
- package/engine/scripts/check-dsh-web-expose.sh +98 -0
- package/engine/scripts/check-judgment-drift.mjs +403 -0
- package/engine/scripts/check-license.mjs +199 -0
- package/engine/scripts/check-model-auth.py +443 -0
- package/engine/scripts/check-pack-closure.mjs +661 -0
- package/engine/scripts/check-portable-symbols.mjs +116 -0
- package/engine/scripts/check-preset-landing.mjs +124 -0
- package/engine/scripts/check-production-home.mjs +602 -0
- package/engine/scripts/check-role-mapping.mjs +461 -0
- package/engine/scripts/check-sample-redaction.mjs +320 -0
- package/engine/scripts/check-skill-classification.mjs +188 -0
- package/engine/scripts/check-sop-consistency.mjs +431 -0
- package/engine/scripts/check-test-env-safety.mjs +627 -0
- package/engine/scripts/check-version-consistency.mjs +452 -0
- package/engine/scripts/ci-run.sh +558 -0
- package/engine/scripts/collect-evidence.mjs +42 -0
- package/engine/scripts/config-explain.mjs +278 -0
- package/engine/scripts/configure-provider.rb +29 -0
- package/engine/scripts/customer-config-migrate.mjs +179 -0
- package/engine/scripts/customer-preflight.py +892 -0
- package/engine/scripts/customer-readiness.sh +135 -0
- package/engine/scripts/daily-report-inbox.py +242 -0
- package/engine/scripts/daily-report-reconcile.mjs +312 -0
- package/engine/scripts/daily-report.mjs +1310 -0
- package/engine/scripts/daily-report.sh +47 -0
- package/engine/scripts/data/lip-product-dict.json +306 -0
- package/engine/scripts/deliver-customer.mjs +932 -0
- package/engine/scripts/delivery-faces.txt +31 -0
- package/engine/scripts/desktop-trial.sh +146 -0
- package/engine/scripts/dev-window.sh +261 -0
- package/engine/scripts/drill-clean-machine.sh +315 -0
- package/engine/scripts/dsh-api-client.mjs +215 -0
- package/engine/scripts/dsh-backup-scope.sh +83 -0
- package/engine/scripts/dsh-offline-pack.mjs +77 -0
- package/engine/scripts/dsh-overlay-pin.sh +87 -0
- package/engine/scripts/dsh-plane.mjs +153 -0
- package/engine/scripts/dsh-portforward.py +223 -0
- package/engine/scripts/dsh-preflight.mjs +179 -0
- package/engine/scripts/dsh-release-watch-daily.sh +19 -0
- package/engine/scripts/dsh-release-watch.mjs +458 -0
- package/engine/scripts/dsh-remote-check.sh +99 -0
- package/engine/scripts/dsh-restart.sh +179 -0
- package/engine/scripts/dsh-restore.sh +111 -0
- package/engine/scripts/dsh-scope-rewire.mjs +803 -0
- package/engine/scripts/dsh-snapshot.mjs +116 -0
- package/engine/scripts/dsh-upgrade-readiness.mjs +165 -0
- package/engine/scripts/dsh-upgrade.mjs +612 -0
- package/engine/scripts/dsh-url.sh +86 -0
- package/engine/scripts/dsh-web-launch.mjs +155 -0
- package/engine/scripts/ensure-store-windows.py +509 -0
- package/engine/scripts/entitlements.py +458 -0
- package/engine/scripts/env_probe.py +411 -0
- package/engine/scripts/feishu-card.mjs +252 -0
- package/engine/scripts/final-check.mjs +43 -0
- package/engine/scripts/fix-skill-frontmatter.py +209 -0
- package/engine/scripts/flow-auto-retry.py +454 -0
- package/engine/scripts/flow-requirements.json +735 -0
- package/engine/scripts/flow-scaffold.py +275 -0
- package/engine/scripts/flow-timers-plist.sh +87 -0
- package/engine/scripts/flow-timers.sh +184 -0
- package/engine/scripts/flow-trigger-wake.mjs +284 -0
- package/engine/scripts/flow-trigger.sh +571 -0
- package/engine/scripts/flow_billing.py +354 -0
- package/engine/scripts/flow_capability_stats.py +314 -0
- package/engine/scripts/flow_case.py +194 -0
- package/engine/scripts/flow_claim.py +497 -0
- package/engine/scripts/flow_config.py +1025 -0
- package/engine/scripts/flow_failure.py +588 -0
- package/engine/scripts/flow_learn.py +105 -0
- package/engine/scripts/flow_ledger.py +235 -0
- package/engine/scripts/flow_policy.py +238 -0
- package/engine/scripts/flow_preflight.py +339 -0
- package/engine/scripts/flow_registry.py +341 -0
- package/engine/scripts/flow_retry.py +304 -0
- package/engine/scripts/flow_return_to_ops.py +191 -0
- package/engine/scripts/flow_source.py +315 -0
- package/engine/scripts/flow_sources/__init__.py +53 -0
- package/engine/scripts/flow_sources/cases_json.py +106 -0
- package/engine/scripts/flow_sources/common.py +428 -0
- package/engine/scripts/flow_sources/feishu_base.py +273 -0
- package/engine/scripts/flow_stale.py +185 -0
- package/engine/scripts/flow_status_reconcile.py +344 -0
- package/engine/scripts/flow_triage.py +849 -0
- package/engine/scripts/flow_write_verify.py +496 -0
- package/engine/scripts/fulfillment.js +586 -0
- package/engine/scripts/gen-agent-bundles.mjs +276 -0
- package/engine/scripts/gen-preset-rows.mjs +259 -0
- package/engine/scripts/init-ml.sh +2278 -0
- package/engine/scripts/install-internal-role.sh +190 -0
- package/engine/scripts/install-license.mjs +641 -0
- package/engine/scripts/install-one-shot.mjs +665 -0
- package/engine/scripts/integration_contract.py +220 -0
- package/engine/scripts/integrations/wenyao/adapter.py +98 -0
- package/engine/scripts/integrations/xzh/adapter.py +75 -0
- package/engine/scripts/invoice-mapping.example.json +40 -0
- package/engine/scripts/invoice-table.sh +195 -0
- package/engine/scripts/invoice_carrier_notice.py +279 -0
- package/engine/scripts/invoice_fail_closeout.py +297 -0
- package/engine/scripts/invoice_lip_runner.py +1508 -0
- package/engine/scripts/invoice_lip_runner.py.bak-20260922-1710-freready +1090 -0
- package/engine/scripts/invoice_ready_audit.py +191 -0
- package/engine/scripts/invoice_runner.py +675 -0
- package/engine/scripts/invoice_stageb.py +516 -0
- package/engine/scripts/launchd/com.whyshu.argent-bridge.plist.example +42 -0
- package/engine/scripts/launchd/com.whyshu.fulfillment-poll.plist.example +29 -0
- package/engine/scripts/lease.py +377 -0
- package/engine/scripts/lib/agent-install.mjs +649 -0
- package/engine/scripts/lib/argent-datadir.mjs +16 -0
- package/engine/scripts/lib/argent-datadir.sh +18 -0
- package/engine/scripts/lib/argent_datadir.py +285 -0
- package/engine/scripts/lib/argent_filelock.py +507 -0
- package/engine/scripts/lib/bootcheck-wiring.mjs +510 -0
- package/engine/scripts/lib/cdp_ports.py +197 -0
- package/engine/scripts/lib/customer-env.mjs +102 -0
- package/engine/scripts/lib/daily-report-core.mjs +517 -0
- package/engine/scripts/lib/dep-declaration.mjs +81 -0
- package/engine/scripts/lib/deps-migrate.mjs +310 -0
- package/engine/scripts/lib/dsh-host-resolve.mjs +109 -0
- package/engine/scripts/lib/dsh-scope.mjs +500 -0
- package/engine/scripts/lib/dsh-snapshot-path.mjs +35 -0
- package/engine/scripts/lib/dsh-versions.mjs +96 -0
- package/engine/scripts/lib/dsh-web-probe.mjs +214 -0
- package/engine/scripts/lib/is-direct-run.mjs +61 -0
- package/engine/scripts/lib/patch-compose.mjs +241 -0
- package/engine/scripts/lib/patch-target.mjs +35 -0
- package/engine/scripts/lib/plugin-lifecycle.mjs +128 -0
- package/engine/scripts/lib/plugin-packages.mjs +332 -0
- package/engine/scripts/lib/preset-compat.mjs +127 -0
- package/engine/scripts/lib/preset-install.mjs +575 -0
- package/engine/scripts/lib/preset-mechanism.mjs +56 -0
- package/engine/scripts/lib/preset-sync.mjs +59 -0
- package/engine/scripts/lib/profile-retire.mjs +97 -0
- package/engine/scripts/lib/python-runtime.mjs +190 -0
- package/engine/scripts/lib/registry.mjs +72 -0
- package/engine/scripts/lib/repo-root.sh +56 -0
- package/engine/scripts/lib/schedule-mechanism.mjs +659 -0
- package/engine/scripts/lib/schedule-probe.mjs +835 -0
- package/engine/scripts/lib/scope-pointer.mjs +164 -0
- package/engine/scripts/lib/settings-panels.mjs +816 -0
- package/engine/scripts/lib/stdio_utf8.py +86 -0
- package/engine/scripts/lib/wake-auth.mjs +392 -0
- package/engine/scripts/lib/webdriver_ipc.py +264 -0
- package/engine/scripts/lib/zstd-frames.mjs +104 -0
- package/engine/scripts/license-e2e-drill.mjs +426 -0
- package/engine/scripts/license-gate.mjs +245 -0
- package/engine/scripts/lip_backlog.py +171 -0
- package/engine/scripts/lip_table.py +185 -0
- package/engine/scripts/local-usage.mjs +214 -0
- package/engine/scripts/migrate-providers.mjs +127 -0
- package/engine/scripts/migrate-skills.sh +65 -0
- package/engine/scripts/ml-oauth-setup.mjs +550 -0
- package/engine/scripts/ml-store-ids.json +26 -0
- package/engine/scripts/ml_artifact_attribution.py +333 -0
- package/engine/scripts/ml_capacity_probe.py +1158 -0
- package/engine/scripts/ml_hub_status.py +1556 -0
- package/engine/scripts/ml_schema_check.py +115 -0
- package/engine/scripts/ml_store_ids.py +139 -0
- package/engine/scripts/npm-publish-argent.mjs +499 -0
- package/engine/scripts/npm-publish-argent.sh +20 -0
- package/engine/scripts/orchestrator.py +13811 -0
- package/engine/scripts/pack-plugins.mjs +140 -0
- package/engine/scripts/patches/dsh-llm-deepseek--transport-diagnostics.patch +297 -0
- package/engine/scripts/patches/dsh-session--append-message-shape.patch +15 -0
- package/engine/scripts/patches/dsh-session--tool-result-block-017.patch +28 -0
- package/engine/scripts/patches/dsh-session-format-v0-to-v1--permission-preset-origin.patch +11 -0
- package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +20 -0
- package/engine/scripts/patches/dsh-session-persistence-jsonl--worker-bundled-migrators.patch +39 -0
- package/engine/scripts/patches/dsh-tools--render-return-shape.patch +50 -0
- package/engine/scripts/patches/verify-bundled-migrators.mjs +79 -0
- package/engine/scripts/patches/verify-hardening.mjs +167 -0
- package/engine/scripts/patches/verify-session-migration.mjs +149 -0
- package/engine/scripts/patches/verify-transport-diagnostics.mjs +227 -0
- package/engine/scripts/plan-role-rename.mjs +125 -0
- package/engine/scripts/plugin-bootcheck.mjs +451 -0
- package/engine/scripts/plugin-composition.json +15 -0
- package/engine/scripts/poc-mcp-host.mjs +455 -0
- package/engine/scripts/poll-fulfillment.sh +295 -0
- package/engine/scripts/poll-invoice.sh +76 -0
- package/engine/scripts/profile-doctor.mjs +686 -0
- package/engine/scripts/provider-select.sh +71 -0
- package/engine/scripts/provider-switch.sh +146 -0
- package/engine/scripts/provider-sync-target.check.mjs +64 -0
- package/engine/scripts/python-runtime.sh +78 -0
- package/engine/scripts/query_runner.py +310 -0
- package/engine/scripts/release-argent.sh +574 -0
- package/engine/scripts/repair-unresolved-tools.mjs +178 -0
- package/engine/scripts/retire-inventory.mjs +40 -0
- package/engine/scripts/run-tests.sh +257 -0
- package/engine/scripts/s1-audit.mjs +85 -0
- package/engine/scripts/self-sign-license.mjs +179 -0
- package/engine/scripts/session-health-scan.mjs +63 -0
- package/engine/scripts/session-repair-tool-result-content.mjs +181 -0
- package/engine/scripts/setup.sh +22 -0
- package/engine/scripts/skill-install.mjs +629 -0
- package/engine/scripts/sku_translate.py +148 -0
- package/engine/scripts/sync-plugins.sh +90 -0
- package/engine/scripts/sync-presets.sh +27 -0
- package/engine/scripts/sync-scripts.sh +445 -0
- package/engine/scripts/telemetry.py +873 -0
- package/engine/scripts/ui-copy-count.mjs +220 -0
- package/engine/scripts/usage-anchor.sh +137 -0
- package/engine/scripts/usage_report.py +531 -0
- package/engine/scripts/validate-preset-schema.mjs +208 -0
- package/engine/scripts/vertical-init.mjs +323 -0
- package/engine/scripts/vertical-lint.mjs +482 -0
- package/engine/scripts/wenyao-browser.sh +74 -0
- package/engine/scripts/wenyao-fixture-export.py +116 -0
- package/engine/scripts/wenyao-invoice-writeback.mjs +540 -0
- package/engine/scripts/wenyao-login.sh +35 -0
- package/engine/scripts/wenyao-template-map.json +292 -0
- package/engine/scripts/wenyao_attach.py +131 -0
- package/engine/scripts/wenyao_download_template.py +144 -0
- package/engine/scripts/wenyao_fill_template.py +568 -0
- package/engine/scripts/wenyao_login.py +145 -0
- package/engine/scripts/wenyao_portal.py +633 -0
- package/engine/scripts/wenyao_recon.py +230 -0
- package/engine/scripts/wenyao_run.py +503 -0
- package/engine/scripts/wenyao_shadow_diff.py +160 -0
- package/engine/scripts/wenyao_skill_contract.py +156 -0
- package/engine/scripts/wenyao_template_fetch.py +175 -0
- package/engine/scripts/win/dsh-web-autostart.ps1 +372 -0
- package/engine/scripts/ziniao-auth-probe.sh +268 -0
- package/engine/scripts/ziniao-keepalive.sh +217 -0
- package/engine/scripts/ziniao-post-upgrade-check.sh +121 -0
- package/engine/scripts/ziniao-windows.sh +154 -0
- package/engine/skills/README.md +86 -0
- package/engine/skills/argent-dev/SKILL.md +41 -0
- package/engine/skills/internal.json +37 -0
- package/engine/skills/lktop-pm/SKILL.md +35 -0
- package/engine/skills/lktopapp-dev/SKILL.md +34 -0
- package/engine/skills/ml-config-guide/SKILL.md +50 -0
- package/engine/skills/ml-fulfillment-sop/SKILL.md +178 -0
- package/engine/skills/ml-fulfillment-sop/references/andes-components.md +69 -0
- package/engine/skills/ml-fulfillment-sop/references/cdp-port-mapping.md +35 -0
- package/engine/skills/ml-fulfillment-sop/references/feishu-base-fulfillment-schema.md +136 -0
- package/engine/skills/ml-fulfillment-sop/references/multi-window-concurrency.md +69 -0
- package/engine/skills/ml-fulfillment-sop/references/pitfalls.md +77 -0
- package/engine/skills/ml-fulfillment-sop/references/selectors.md +243 -0
- package/engine/skills/ml-fulfillment-sop/scripts/fulfillment.js +586 -0
- package/engine/skills/ml-fulfillment-sop/scripts/poll-and-execute.sh +271 -0
- package/engine/skills/ml-fulfillment-sop/templates/report.md +55 -0
- package/engine/skills/ml-invoice/SKILL.md +506 -0
- package/engine/skills/ml-invoice/references/pitfalls.md +268 -0
- package/engine/skills/ml-invoice/references/portal-flow.md +360 -0
- package/engine/skills/ml-invoice/references/template-fill-contract.md +133 -0
- package/engine/skills/ml-invoice-xzh/SKILL.md +133 -0
- package/engine/skills/ml-invoice-xzh/references/pitfalls.md +230 -0
- package/engine/skills/ml-store-daily-report/SKILL.md +224 -0
- package/engine/skills/ml-store-daily-report/scripts/README.md +25 -0
- package/engine/skills/ml-store-daily-report/scripts/extract.js +691 -0
- package/engine/skills/ml-store-daily-report/scripts/generate.js +299 -0
- package/engine/skills/pet-dev/SKILL.md +42 -0
- package/engine/skills/whyshu-pm/SKILL.md +35 -0
- package/engine/skills/whyshu-web-dev/SKILL.md +50 -0
- package/engine/version.mjs +1 -0
- package/engine/verticals/README.md +103 -0
- package/engine/verticals/_engine-words.baseline.txt +237 -0
- package/engine/verticals/_engine-words.json +19 -0
- package/engine/verticals/mercadolibre/README.md +43 -0
- package/engine/verticals/mercadolibre/config.json +709 -0
- package/engine/verticals/mercadolibre/flows.json +738 -0
- package/engine/verticals/mercadolibre/manifest.json +538 -0
- package/engine/verticals/mercadolibre/migrate-machine-state.mjs +138 -0
- package/engine/verticals/mercadolibre/preflight.py +143 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.failure.md +69 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.md +47 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.scheduled.md +79 -0
- package/engine/verticals/mercadolibre/prompts/full.failure.md +125 -0
- package/engine/verticals/mercadolibre/prompts/full.md +91 -0
- package/engine/verticals/mercadolibre/prompts/wenyao-invoice.failure.md +88 -0
- package/engine/verticals/mercadolibre/prompts/wenyao-invoice.md +64 -0
- package/engine/verticals/mercadolibre/prompts/xzh-invoice.failure.md +91 -0
- package/engine/verticals/mercadolibre/prompts/xzh-invoice.md +46 -0
- package/package.json +22 -4
- package/requirements.txt +5 -0
- package/README.md +0 -3
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* dsh 升级前变更审查(preflight)——2026-08-21 事故固化
|
|
4
|
+
*
|
|
5
|
+
* 用法:
|
|
6
|
+
* node scripts/dsh-preflight.mjs <新版本> [--snapshot <快照文件>] [--from-dir <已装目录>]
|
|
7
|
+
*
|
|
8
|
+
* 做什么:
|
|
9
|
+
* 1. 本地安装目标版本(tarball + 快照 overrides,绝不触碰全局)
|
|
10
|
+
* 2. 依赖结构 diff:与当前全局 @deepseek-ai scope 对比子包清单
|
|
11
|
+
* - 新增子包 → 检查主包 dsh-web-app / dsh 是否声明(漏声明 = rc.8 primitives 断链前科)
|
|
12
|
+
* - 消失子包 → 引用方是否已迁移
|
|
13
|
+
* - 版本变化分布
|
|
14
|
+
* 3. 关键探针包存在性(profiles 断链检查清单)
|
|
15
|
+
* 4. 输出 markdown 报告 → /tmp/dsh-preflight-<版本>.md
|
|
16
|
+
*
|
|
17
|
+
* 注意: 本脚本只做「本地 + 对比」审查;隔离试跑 + CDP boot 验证 + 品牌补丁
|
|
18
|
+
* 特征回归见 docs/dsh-version-management.md「升级前变更审查清单」第 2/3 步
|
|
19
|
+
* (必须带完整品牌补丁在独立 profile 试跑,不能只跑裸版本)。
|
|
20
|
+
*/
|
|
21
|
+
import { execSync } from "node:child_process";
|
|
22
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync, readdirSync } from "node:fs";
|
|
23
|
+
import { join, dirname } from "node:path";
|
|
24
|
+
import { fileURLToPath } from "node:url";
|
|
25
|
+
import { classifySubpackages, readScope } from "./lib/dep-declaration.mjs";
|
|
26
|
+
import { requireDshScope } from "./lib/dsh-scope.mjs";
|
|
27
|
+
|
|
28
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
const REPO = join(__dirname, "..");
|
|
30
|
+
const LOCK_DIR = join(REPO, "dsh-version-lock");
|
|
31
|
+
// 2026-09-28:scope 解析收敛到 scripts/lib/dsh-scope.mjs(客户机 dsh 不在 homebrew 下)。
|
|
32
|
+
// 0.6.0 F1 修复批(2026-10-14):预检**整篇**都在读这棵树的版本/断链 ⇒ 拿不到 scope 必须**响亮失败**
|
|
33
|
+
// (⛔ 不许拿一个可能不存在的路径继续,那会输出一份"树里什么都没有"的假报告)。
|
|
34
|
+
let SCOPE_DIR;
|
|
35
|
+
try { SCOPE_DIR = requireDshScope({ what: "宿主预检(dsh-preflight)" }); }
|
|
36
|
+
catch (e) { console.error(` ❌ ${e.message}`); process.exit(1); }
|
|
37
|
+
// profiles 断链探针(rc.8 事故清单;0.1.1 无 primitives 属正常,见报告)
|
|
38
|
+
const LINK_PROBES = ["dsh-client-ui-primitives", "dsh-client-ui-sidebar", "dsh-client-ui-conversation"];
|
|
39
|
+
// 主包(声明依赖关系的关键包,漏声明检测对象)
|
|
40
|
+
const DECLARER_PKGS = ["dsh", "dsh-web-app", "dsh-client-ui-sidebar", "dsh-client-ui-conversation"];
|
|
41
|
+
|
|
42
|
+
const target = process.argv[2];
|
|
43
|
+
if (!target) {
|
|
44
|
+
console.error("用法: node scripts/dsh-preflight.mjs <新版本> [--snapshot <快照>] [--from-dir <已装目录>]");
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
const snapArgIdx = process.argv.indexOf("--snapshot");
|
|
48
|
+
const snapshotFile = snapArgIdx > -1 ? process.argv[snapArgIdx + 1] : null;
|
|
49
|
+
const fromDirIdx = process.argv.indexOf("--from-dir");
|
|
50
|
+
const fromDir = fromDirIdx > -1 ? process.argv[fromDirIdx + 1] : null;
|
|
51
|
+
|
|
52
|
+
const run = (cmd, opts = {}) => execSync(cmd, { stdio: opts.silent ? "pipe" : "inherit", encoding: "utf8", ...opts });
|
|
53
|
+
const ok = (m) => console.log(" ✅ " + m);
|
|
54
|
+
const warn = (m) => console.log(" ⚠️ " + m);
|
|
55
|
+
const fail = (m) => { console.error(" ❌ " + m); process.exit(1); };
|
|
56
|
+
|
|
57
|
+
function collectVersions(dir) {
|
|
58
|
+
if (!existsSync(dir)) return null;
|
|
59
|
+
const out = {};
|
|
60
|
+
for (const name of readdirSync(dir)) {
|
|
61
|
+
const pj = join(dir, name, "package.json");
|
|
62
|
+
if (existsSync(pj)) {
|
|
63
|
+
try { out[name] = JSON.parse(readFileSync(pj, "utf8")).version; } catch {}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log(`\n═══ dsh 升级前审查: ${target} ═══\n`);
|
|
70
|
+
|
|
71
|
+
// ── 1. 快照 ──────────────────────────────────────────────
|
|
72
|
+
let snapshot = null;
|
|
73
|
+
if (snapshotFile) snapshot = JSON.parse(readFileSync(snapshotFile, "utf8"));
|
|
74
|
+
else {
|
|
75
|
+
const locked = join(LOCK_DIR, `${target}.json`);
|
|
76
|
+
if (existsSync(locked)) snapshot = JSON.parse(readFileSync(locked, "utf8"));
|
|
77
|
+
}
|
|
78
|
+
if (snapshot) ok(`快照: ${snapshotFile || join(LOCK_DIR, target + ".json")}(${Object.keys(snapshot.resolved || {}).length} 个子包)`);
|
|
79
|
+
else warn("无快照——若目标版本无快照文件,先按 docs 生成候选快照再审查");
|
|
80
|
+
|
|
81
|
+
// ── 2. 本地安装 ──────────────────────────────────────────
|
|
82
|
+
console.log("\n[1/4] 本地安装目标版本 ...");
|
|
83
|
+
let work;
|
|
84
|
+
let depRoot;
|
|
85
|
+
if (fromDir) {
|
|
86
|
+
work = fromDir;
|
|
87
|
+
depRoot = join(work, "node_modules", "@deepseek-ai");
|
|
88
|
+
if (!existsSync(depRoot)) fail(`--from-dir 缺少 node_modules/@deepseek-ai: ${depRoot}`);
|
|
89
|
+
ok(`复用已装目录: ${work}`);
|
|
90
|
+
} else {
|
|
91
|
+
const tarball = join("/tmp", `deepseek-ai-dsh-${target}.tgz`);
|
|
92
|
+
if (!existsSync(tarball)) {
|
|
93
|
+
run(`cd /tmp && npm pack @deepseek-ai/dsh@${target} --silent`, { silent: true });
|
|
94
|
+
const found = readdirSync("/tmp").filter((f) => f.startsWith("deepseek-ai-dsh-") && f.endsWith(".tgz"));
|
|
95
|
+
if (!found.length) fail("tarball 下载失败");
|
|
96
|
+
const real = join("/tmp", found[found.length - 1]);
|
|
97
|
+
if (real !== tarball) run(`mv "${real}" "${tarball}"`, { silent: true });
|
|
98
|
+
}
|
|
99
|
+
work = join("/tmp", `dsh-preflight-${target}`);
|
|
100
|
+
rmSync(work, { recursive: true, force: true });
|
|
101
|
+
mkdirSync(work, { recursive: true });
|
|
102
|
+
const pkgJson = { name: "dsh-preflight-work", private: true, dependencies: { "@deepseek-ai/dsh": `file:${tarball}` } };
|
|
103
|
+
if (snapshot) {
|
|
104
|
+
pkgJson.overrides = {};
|
|
105
|
+
for (const [name, ver] of Object.entries(snapshot.resolved)) pkgJson.overrides[name] = ver;
|
|
106
|
+
}
|
|
107
|
+
writeFileSync(join(work, "package.json"), JSON.stringify(pkgJson, null, 2));
|
|
108
|
+
warn("npm install 进行中(file:+overrides 组合可能较慢)...");
|
|
109
|
+
run(`cd "${work}" && npm install --no-audit --no-fund --loglevel=error`);
|
|
110
|
+
ok("依赖已安装(本地目录)");
|
|
111
|
+
depRoot = join(work, "node_modules", "@deepseek-ai");
|
|
112
|
+
}
|
|
113
|
+
const newVers = collectVersions(depRoot);
|
|
114
|
+
if (!newVers) fail("本地安装缺少 @deepseek-ai 目录");
|
|
115
|
+
|
|
116
|
+
// ── 3. 依赖结构 diff ─────────────────────────────────────
|
|
117
|
+
console.log("\n[2/4] 依赖结构 diff(vs 当前全局)...");
|
|
118
|
+
const curVers = collectVersions(SCOPE_DIR);
|
|
119
|
+
if (!curVers) warn("当前全局 scope 不存在,跳过 diff");
|
|
120
|
+
let report = `# dsh 升级前审查报告: ${target}(${new Date().toISOString().slice(0, 16)})\n\n`;
|
|
121
|
+
if (curVers) {
|
|
122
|
+
const added = Object.keys(newVers).filter((n) => !curVers[n]);
|
|
123
|
+
const removed = Object.keys(curVers).filter((n) => !newVers[n]);
|
|
124
|
+
const changed = Object.keys(newVers).filter((n) => curVers[n] && curVers[n] !== newVers[n]);
|
|
125
|
+
report += `## 依赖结构 diff\n\n`;
|
|
126
|
+
if (added.length) report += `**新增子包(${added.length})**:${added.join(", ")}\n\n`;
|
|
127
|
+
if (removed.length) report += `**消失子包(${removed.length})**:${removed.join(", ")}\n\n`;
|
|
128
|
+
if (changed.length) {
|
|
129
|
+
report += `**版本变化(${changed.length})**:\n\n`;
|
|
130
|
+
for (const n of changed) report += `- ${n}: ${curVers[n]} → ${newVers[n]}\n`;
|
|
131
|
+
report += "\n";
|
|
132
|
+
}
|
|
133
|
+
if (!added.length && !removed.length && !changed.length) report += "子包清单无变化。\n\n";
|
|
134
|
+
console.log(` 新增: ${added.length} | 消失: ${removed.length} | 版本变化: ${changed.length}`);
|
|
135
|
+
if (added.length) warn(`⚠️ 新增子包需检查主包是否声明(漏声明 → 断链卡屏,rc.8 primitives 前科)`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── 4. 漏声明检测 + 探针包 ───────────────────────────────
|
|
139
|
+
console.log("\n[3/4] 关键包漏声明检测(断链风险)...");
|
|
140
|
+
// 2026-09-28 修:原实现用**目录名**比**包名**(缺 @deepseek-ai/ scope)⇒ 0.1.7 上把 52 个 UI
|
|
141
|
+
// 子包全报成漏声明。全是噪音的告警等于没有告警(真孤儿会被淹)。分类逻辑见
|
|
142
|
+
// scripts/lib/dep-declaration.mjs:declared(声明方声明)/ transitive(别人依赖)/ orphan(真可疑)。
|
|
143
|
+
const packages = readScope(depRoot);
|
|
144
|
+
const cls = classifySubpackages({
|
|
145
|
+
packages,
|
|
146
|
+
declarers: DECLARER_PKGS.map((p) => (p.includes("/") ? p : `@deepseek-ai/${p}`)),
|
|
147
|
+
interest: (n) => n.startsWith("@deepseek-ai/dsh-client-ui"),
|
|
148
|
+
});
|
|
149
|
+
report += `## 关键包漏声明检测(UI 子包三级分类)\n\n`;
|
|
150
|
+
report += `- 声明方声明:${cls.declared.length} 个\n`;
|
|
151
|
+
report += `- 传递依赖(别的包依赖它,正常):${cls.transitive.length} 个${cls.transitive.length ? ":" + cls.transitive.map((n) => n.replace("@deepseek-ai/", "")).join(", ") : ""}\n`;
|
|
152
|
+
report += `- **既无声明也无人依赖(真可疑)**:${cls.orphan.length} 个${cls.orphan.length ? ":" + cls.orphan.map((n) => n.replace("@deepseek-ai/", "")).join(", ") : ""}\n`;
|
|
153
|
+
if (cls.orphan.length) {
|
|
154
|
+
warn(`⚠️ ${cls.orphan.length} 个 UI 子包既未被声明也无人依赖(磁盘上有、没人引用)—— 逐个确认是否需要增量接线`);
|
|
155
|
+
for (const n of cls.orphan) console.log(` · ${n}`);
|
|
156
|
+
} else {
|
|
157
|
+
ok(`UI 子包全部有归属(声明 ${cls.declared.length} / 传递 ${cls.transitive.length} / 孤儿 0)`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
console.log("\n[4/4] 探针包存在性(profiles 断链清单)...");
|
|
162
|
+
report += `\n## 探针包存在性(profiles 断链清单)\n\n`;
|
|
163
|
+
for (const p of LINK_PROBES) {
|
|
164
|
+
const present = existsSync(join(depRoot, p));
|
|
165
|
+
report += `- ${p}: ${present ? "✅ 存在" : "❌ 不存在"}\n`;
|
|
166
|
+
console.log(` ${present ? "✅" : "❌"} ${p}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const reportPath = `/tmp/dsh-preflight-${target}.md`;
|
|
170
|
+
writeFileSync(reportPath, report);
|
|
171
|
+
console.log(`\n报告已写入: ${reportPath}`);
|
|
172
|
+
console.log(`
|
|
173
|
+
── 剩余人工步骤(docs/dsh-version-management.md「升级前变更审查清单」)──
|
|
174
|
+
2. 隔离试跑 + CDP boot 验证(带完整品牌补丁,独立 profile + 独立端口)
|
|
175
|
+
3. 品牌补丁特征回归(data-slot / wordmark / 类名前缀对照补丁 ①②③④⑤⑥)
|
|
176
|
+
4. CLI 行为 diff(dsh --help;--profile/--port/--no-open 等)
|
|
177
|
+
5. 凭据格式探测(隔离试跑后检查 ~/.dsh/.credentials.yaml 写入格式)
|
|
178
|
+
6. profiles 链接重建行为确认(嵌套 vs 顶层 flat)
|
|
179
|
+
全部通过后再执行 scripts/dsh-upgrade.mjs ${target}`);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# 每日只读巡检:官方 dsh 是否发新版(窗口外 ⇒ 列入待适配)。
|
|
3
|
+
#
|
|
4
|
+
# 为什么:客户会先按 **dsh 官方**方式装宿主(官方 README 首选 `npx @deepseek-ai/dsh web`,
|
|
5
|
+
# npm `latest` 现在就是 0.2.0-rc.2),如果我们的支持窗口跟不上,客户会在门禁处被挡。
|
|
6
|
+
# 本巡检把"官方发版 → 我方待办"的发现动作自动化(发现延迟从"人想起来"降到 ≤1 天)。
|
|
7
|
+
#
|
|
8
|
+
# 判据全部在 `scripts/dsh-release-watch.mjs`(只读、cwd 无关、退出码 0/1/2):
|
|
9
|
+
# 0 = 窗口内;1 = 有更新未适配(需按脚本打印的 7 步链适配);2 = 取不到元数据(⛔ 不等于没更新)
|
|
10
|
+
# ⛔ 本脚本只读:不写仓库、不写 ~/.dsh(日志由 launchd 的 StandardOutPath 落盘)、不装/不升级任何 dsh。
|
|
11
|
+
set -u
|
|
12
|
+
REPO="/Users/chester/Code/argent-dsh"
|
|
13
|
+
NODE="/opt/homebrew/bin/node"
|
|
14
|
+
cd "$REPO" 2>/dev/null || { echo "[$(date '+%F %T')] 仓库不存在:$REPO"; exit 2; }
|
|
15
|
+
echo "──────── $(date '+%F %T') dsh 发版巡检 ────────"
|
|
16
|
+
"$NODE" scripts/dsh-release-watch.mjs 2>&1
|
|
17
|
+
rc=$?
|
|
18
|
+
echo "[$(date '+%F %T')] exit=${rc}(0=窗口内 / 1=有更新未适配 / 2=取不到元数据)"
|
|
19
|
+
exit "${rc}"
|
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* dsh **官方发版监听**(只读)—— 2026-10-11「宿主发版监听批」
|
|
4
|
+
*
|
|
5
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
6
|
+
* 一、为什么需要它(用户提出的现实问题)
|
|
7
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
8
|
+
* dsh 官方 npm 的 `dist-tags.latest` **现在就已经是 `0.2.0-rc.2`**,而 Argent 的支持窗口
|
|
9
|
+
* (`dsh-version-lock/` 里**已晋级**的最高快照)还停在 `0.1.7-rc.2`。客户装机的第一件事是
|
|
10
|
+
* 按官方命令装 dsh ⇒ **装到的就是窗口外版本**。若只在"人发现"之后才去适配,就会长期挡客户。
|
|
11
|
+
*
|
|
12
|
+
* 所以缺的**不是**能力(我们已有 preflight → snapshot → 审计 三段式),缺的是**触发**:
|
|
13
|
+
* 一条只读命令,一跑就知道"官方发了什么、离我们的窗口有多远、下一步抄哪几条命令"。
|
|
14
|
+
* ⛔ 本脚本**只报告**:不写任何文件、不改 registry、不 npm login、不发布、不投递;
|
|
15
|
+
* 除了取元数据本身,没有任何副作用(`--registry` 指本地文件时连网络都不碰 ⇒ 回归可做到零网络)。
|
|
16
|
+
*
|
|
17
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
18
|
+
* 二、判据(**只有一个真源**,⛔ 不新增第二份版本字面量)
|
|
19
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
20
|
+
* · **窗口真源** = `dsh-version-lock/*.json`(顶层 = 已晋级/已验证;`candidate/` **不参与**)
|
|
21
|
+
* + `scripts/lib/dsh-versions.mjs` 的 `listVerifiedVersions()` / `pickLatestVerified()` /
|
|
22
|
+
* `cmpVer()`(**rc 排序也归它**,本文件不自己实现版本比较)。
|
|
23
|
+
* · `verifiedListGaps()` 顺带做一次**基线漂移自检**:内置基线必须覆盖 lock 里已晋级的全集
|
|
24
|
+
* (缺项 = 装机时会把现役版本报成"未验证";与 `scripts/tests/dsh-verified-list.test.mjs` 同源判据)。
|
|
25
|
+
* · 差集:官方 `versions` 里**比窗口最高版本更新**的全部版本 = `outOfWindow`;
|
|
26
|
+
* 其中 `candidate/` 里还没有的 = `pendingCandidate`(= 真正待办的版本),已有的 = `candidateReady`
|
|
27
|
+
* (已审查未验收 ⇒ 下一步是**验收+晋级**,不是重新 preflight)。
|
|
28
|
+
*
|
|
29
|
+
* ## 结论与退出码(⚠️ 的语义是**适配关系**,不是"新版本更好")
|
|
30
|
+
* | 退出码 | 结论行 | 含义 |
|
|
31
|
+
* |---|---|---|
|
|
32
|
+
* | 0 | `状态:✅ 窗口内` | 官方**任何通道**都没有窗口外版本 |
|
|
33
|
+
* | 1 | `状态:⚠️ 有更新未适配` | 官方有窗口外版本(`latest` 出窗 = **直接挡客户**,最高优先) |
|
|
34
|
+
* | 2 | `状态:❌ 取不到元数据` | 拉不到/解析不了官方元数据 —— **⛔ 绝不当成"没有更新"** |
|
|
35
|
+
*
|
|
36
|
+
* 退出码 2 刻意与"没更新"分开:把网络失败读成"今天没发版",正是这类监听器最危险的假绿。
|
|
37
|
+
*
|
|
38
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
39
|
+
* 三、用法
|
|
40
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
41
|
+
* ```bash
|
|
42
|
+
* node scripts/dsh-release-watch.mjs # 人读(默认 registry = npmmirror 镜像)
|
|
43
|
+
* node scripts/dsh-release-watch.mjs --json # 机器读(字段形状稳定)
|
|
44
|
+
* node scripts/dsh-release-watch.mjs --registry https://registry.npmjs.org/@deepseek-ai/dsh
|
|
45
|
+
* node scripts/dsh-release-watch.mjs --registry /tmp/meta.json # 本地元数据(离线/夹具;也认 file://)
|
|
46
|
+
* node scripts/dsh-release-watch.mjs --lock-dir <目录> # 换窗口真源(回归夹具用)
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* ## 环境开关
|
|
50
|
+
* · `ARGENT_TESTS_OFFLINE=1` —— 由 `scripts/run-tests.sh` 导出:**拒绝真发网络请求**(直接抛错 ⇒
|
|
51
|
+
* 走"取不到元数据"分支)。目的是让"夹具回归零网络"这条性质**可执行**,而不是靠自觉
|
|
52
|
+
* (同类先例:`ARGENT_TELEMETRY_OFF`)。⛔ 只影响本脚本的 http(s) 取数,不影响本地文件模式;
|
|
53
|
+
* 且只拦**默认**取数路径(`globalThis.fetch`)—— 显式注入 `fetchImpl` 的调用方走的是**测试 seam**,
|
|
54
|
+
* 由它自己保证零网络(见 `loadMetadata` 里的说明)。
|
|
55
|
+
*
|
|
56
|
+
* ## 纯度
|
|
57
|
+
* 只 import `node:` 内建 + 同层 `scripts/lib/*`(`is-direct-run.mjs` / `dsh-versions.mjs`),
|
|
58
|
+
* **零宿主模块**(不 import `@deepseek-ai/*` / `cordis` / `plugins/*`)——
|
|
59
|
+
* 虽然 `kernel-purity` 的层标注把 `scripts/dsh-*.mjs` 归在 L2(宿主运维档、不扫内容),
|
|
60
|
+
* 本文件仍按 L1 的纯度自律;回归里有一条**静态断言**钉住这件事。
|
|
61
|
+
*
|
|
62
|
+
* 回归:`node scripts/tests/dsh-release-watch.test.mjs`(纯夹具、零网络、含只读性指纹)
|
|
63
|
+
*/
|
|
64
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
65
|
+
import { dirname, join, resolve } from 'node:path'
|
|
66
|
+
import { fileURLToPath } from 'node:url'
|
|
67
|
+
import { isDirectRun } from './lib/is-direct-run.mjs'
|
|
68
|
+
import {
|
|
69
|
+
DSH_VERIFIED_FALLBACK,
|
|
70
|
+
cmpVer,
|
|
71
|
+
listCandidateVersions,
|
|
72
|
+
listVerifiedVersions,
|
|
73
|
+
pickLatestVerified,
|
|
74
|
+
verifiedListGaps,
|
|
75
|
+
} from './lib/dsh-versions.mjs'
|
|
76
|
+
|
|
77
|
+
export const REPO = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
78
|
+
/** 官方 registry(默认走镜像:本机/客户机都在国内网络;`--registry` 可覆盖成 npmjs 官方源)。 */
|
|
79
|
+
export const DEFAULT_REGISTRY = 'https://registry.npmmirror.com/@deepseek-ai/dsh'
|
|
80
|
+
export const DEFAULT_LOCK_DIR = join(REPO, 'dsh-version-lock')
|
|
81
|
+
/** 0.2.x 适配审计清单(另一个只读审计批在写;**不存在时**只提示、不编造)。 */
|
|
82
|
+
export const AUDIT_DOC = 'docs/dsh-0.2.x-适配审计.md'
|
|
83
|
+
/** 结论三态(JSON 的 `status` 用机器值,人读行恒为 `状态:<emoji> <文案>`)。 */
|
|
84
|
+
export const STATUS = {
|
|
85
|
+
ok: '✅ 窗口内',
|
|
86
|
+
stale: '⚠️ 有更新未适配',
|
|
87
|
+
unavailable: '❌ 取不到元数据',
|
|
88
|
+
}
|
|
89
|
+
/** 默认 IO(可注入:回归用临时目录/文件夹具,⛔ 不碰真仓库)。 */
|
|
90
|
+
export const DEFAULT_IO = {
|
|
91
|
+
exists: existsSync,
|
|
92
|
+
readdir: readdirSync,
|
|
93
|
+
read: (p) => readFileSync(p, 'utf8'),
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── 版本比较:**一律**走 lib 的 cmpVer(rc 排序归它,⛔ 本文件不自己实现)──────────
|
|
97
|
+
const sortAsc = (list) => [...list].sort(cmpVer)
|
|
98
|
+
const sortDesc = (list) => [...list].sort((a, b) => cmpVer(b, a))
|
|
99
|
+
const uniq = (list) => [...new Set(list.filter(Boolean))]
|
|
100
|
+
const fmtList = (list, sep = '、') => (list.length ? list.join(sep) : '(无)')
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 差距的**人读描述**(只描述,不参与任何排序/判据 —— 排序恒用 `cmpVer`)。
|
|
104
|
+
* @returns {string|null} `to` 不比 `from` 新 ⇒ null
|
|
105
|
+
*/
|
|
106
|
+
export function describeGap(from, to) {
|
|
107
|
+
if (!from || !to || cmpVer(to, from) <= 0) return null
|
|
108
|
+
const core = (v) => String(v).split('-')[0].split('.').map((n) => Number(n) || 0)
|
|
109
|
+
const [a1, a2, a3] = core(from)
|
|
110
|
+
const [b1, b2, b3] = core(to)
|
|
111
|
+
if (b1 !== a1) return `大版本 +${b1 - a1}(${from} → ${to})`
|
|
112
|
+
if (b2 !== a2) return `小版本 +${b2 - a2}(${from} → ${to})`
|
|
113
|
+
if (b3 !== a3) return `补丁 +${b3 - a3}(${from} → ${to})`
|
|
114
|
+
return `同为 ${b1}.${b2}.${b3},预发布号更靠后(${from} → ${to})`
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** 把 registry 文档(或本地同一形状的 JSON)收敛成我们唯一用得到的两个字段。 */
|
|
118
|
+
export function normalizeMetadata(raw) {
|
|
119
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
120
|
+
throw new Error('元数据不是对象(不是 npm registry 的包文档)')
|
|
121
|
+
}
|
|
122
|
+
const distTags = (raw['dist-tags'] && typeof raw['dist-tags'] === 'object' && !Array.isArray(raw['dist-tags']))
|
|
123
|
+
? raw['dist-tags'] : {}
|
|
124
|
+
const versions = (raw.versions && typeof raw.versions === 'object' && !Array.isArray(raw.versions))
|
|
125
|
+
? Object.keys(raw.versions) : []
|
|
126
|
+
if (!versions.length) {
|
|
127
|
+
throw new Error('元数据里没有 versions(拿到的不是包文档 ⇒ 不能据此判"没更新")')
|
|
128
|
+
}
|
|
129
|
+
// ⚠️ `latest` 缺失同样按"取不到"处理:客户装的正是 `latest`,没有它就无法回答
|
|
130
|
+
// "官方命令会装到哪一版" ⇒ 此时报"窗口内"就是假绿(宁可响亮地失败)。
|
|
131
|
+
if (!distTags.latest) {
|
|
132
|
+
throw new Error('元数据里没有 dist-tags.latest(无法判定"客户按官方命令会装到哪一版" ⇒ 不能判"窗口内")')
|
|
133
|
+
}
|
|
134
|
+
return { distTags, versions }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 取元数据。`registry` 是 http(s) ⇒ 真拉;`file://` 或本地路径 ⇒ 读文件(离线/夹具)。
|
|
139
|
+
* 三种失败(网络 / HTTP 状态 / JSON 形状)**一律抛错**,由调用方转成 exit 2。
|
|
140
|
+
*/
|
|
141
|
+
export async function loadMetadata({ registry = DEFAULT_REGISTRY, fetchImpl = globalThis.fetch, io = DEFAULT_IO, timeoutMs = 20000 } = {}) {
|
|
142
|
+
const src = String(registry || DEFAULT_REGISTRY)
|
|
143
|
+
if (/^https?:\/\//i.test(src)) {
|
|
144
|
+
// ⚠️ 离线开关只拦**默认**取数路径(`globalThis.fetch` = 真发请求);显式注入 `fetchImpl` 的调用方
|
|
145
|
+
// 是在走**测试 seam**(注入的假 fetch 自己保证零网络),不能被一刀切拦掉 ——
|
|
146
|
+
// 2026-10-11 实测踩到:一刀切时 run-tests.sh 导出的 ARGENT_TESTS_OFFLINE=1 会把回归里
|
|
147
|
+
// "假 fetch 抛/假 404" 两条用例变成"离线模式",等于把 seam 本身变得不可测。
|
|
148
|
+
if (fetchImpl === globalThis.fetch && String(process.env.ARGENT_TESTS_OFFLINE || '') === '1') {
|
|
149
|
+
throw new Error('离线模式(ARGENT_TESTS_OFFLINE=1):拒绝真发网络请求(测试期的零网络声明)')
|
|
150
|
+
}
|
|
151
|
+
if (typeof fetchImpl !== 'function') throw new Error('本 Node 没有全局 fetch(需要 Node ≥ 18)')
|
|
152
|
+
let res
|
|
153
|
+
try {
|
|
154
|
+
const signal = (typeof AbortSignal !== 'undefined' && AbortSignal.timeout) ? AbortSignal.timeout(timeoutMs) : undefined
|
|
155
|
+
res = await fetchImpl(src, { headers: { accept: 'application/json' }, signal })
|
|
156
|
+
} catch (e) {
|
|
157
|
+
throw new Error(`请求失败:${e && e.message ? e.message : e}`)
|
|
158
|
+
}
|
|
159
|
+
if (!res || typeof res.ok !== 'boolean') throw new Error('请求返回了非法响应对象')
|
|
160
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}${res.statusText ? ` ${res.statusText}` : ''}`)
|
|
161
|
+
const text = await res.text()
|
|
162
|
+
try {
|
|
163
|
+
return JSON.parse(text)
|
|
164
|
+
} catch (e) {
|
|
165
|
+
throw new Error(`响应不是 JSON:${e && e.message ? e.message : e}`)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const file = src.startsWith('file:') ? fileURLToPath(src) : src
|
|
169
|
+
let text
|
|
170
|
+
try {
|
|
171
|
+
text = io.read(file)
|
|
172
|
+
} catch (e) {
|
|
173
|
+
throw new Error(`读不到本地元数据 ${file}:${e && e.message ? e.message : e}`)
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
return JSON.parse(text)
|
|
177
|
+
} catch (e) {
|
|
178
|
+
throw new Error(`本地元数据不是 JSON(${file}):${e && e.message ? e.message : e}`)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* **窗口真源**(纯读):lock 顶层晋级快照 + 基线漂移自检 + 候选清单。
|
|
184
|
+
* 抽成独立函数是为了让"取不到元数据"时**照样**能把窗口打出来(那只读本机文件,与网络无关)。
|
|
185
|
+
*/
|
|
186
|
+
export function readWindow({ lockDir = DEFAULT_LOCK_DIR, io = DEFAULT_IO } = {}) {
|
|
187
|
+
const promoted = sortAsc(uniq(listVerifiedVersions(lockDir, io)))
|
|
188
|
+
const lockReadable = promoted.length > 0
|
|
189
|
+
const windowVersions = lockReadable ? promoted : sortAsc(uniq(DSH_VERIFIED_FALLBACK))
|
|
190
|
+
const highestVerified = pickLatestVerified(lockDir, io)
|
|
191
|
+
const windowHighest = lockReadable ? highestVerified : windowVersions[windowVersions.length - 1]
|
|
192
|
+
const baseline = verifiedListGaps(DSH_VERIFIED_FALLBACK, lockDir, io)
|
|
193
|
+
return {
|
|
194
|
+
verifiedWindow: windowVersions,
|
|
195
|
+
verifiedHighest: windowHighest,
|
|
196
|
+
windowReadable: lockReadable,
|
|
197
|
+
windowSource: lockReadable
|
|
198
|
+
? `dsh-version-lock/ 顶层晋级快照(${promoted.length} 个)`
|
|
199
|
+
: '⚠️ lock 目录读不到 ⇒ 退回 scripts/lib/dsh-versions.mjs 的内置基线',
|
|
200
|
+
baselineDrift: { ok: baseline.ok, missing: baseline.missing },
|
|
201
|
+
candidateVersions: sortAsc(uniq(listCandidateVersions(lockDir, io))),
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* **纯判据**:元数据 + 窗口真源 ⇒ 结论对象(无网络;IO 只读窗口真源;回归直接调它)。
|
|
207
|
+
*
|
|
208
|
+
* @param {{metadata: object, lockDir?: string, io?: object, repoDocExists?: (rel: string) => boolean}} args
|
|
209
|
+
*/
|
|
210
|
+
export function evaluate({ metadata, lockDir = DEFAULT_LOCK_DIR, io = DEFAULT_IO, repoDocExists } = {}) {
|
|
211
|
+
const { distTags, versions } = normalizeMetadata(metadata)
|
|
212
|
+
const official = uniq(versions)
|
|
213
|
+
const win = readWindow({ lockDir, io })
|
|
214
|
+
const { verifiedHighest: windowHighest, candidateVersions } = win
|
|
215
|
+
|
|
216
|
+
const tagOf = (k) => (distTags[k] ? String(distTags[k]) : null)
|
|
217
|
+
const latest = tagOf('latest')
|
|
218
|
+
const next = tagOf('next')
|
|
219
|
+
const alpha = tagOf('alpha')
|
|
220
|
+
|
|
221
|
+
// ── 差集:官方有、窗口内没有的版本(含各通道)──
|
|
222
|
+
const outOfWindow = sortDesc(official.filter((v) => cmpVer(v, windowHighest) > 0))
|
|
223
|
+
const pendingCandidate = outOfWindow.filter((v) => !candidateVersions.includes(v))
|
|
224
|
+
const candidateReady = outOfWindow.filter((v) => candidateVersions.includes(v))
|
|
225
|
+
const tagVersions = uniq([latest, next, alpha])
|
|
226
|
+
const tagsOutOfWindow = sortDesc(tagVersions.filter((v) => cmpVer(v, windowHighest) > 0))
|
|
227
|
+
const latestInWindow = latest ? cmpVer(latest, windowHighest) <= 0 : null
|
|
228
|
+
// 待办顺序:dist-tags 指向的(latest 最优先)在前,其余按版本倒序 —— 清单要能"从上往下抄"。
|
|
229
|
+
const stepOrder = uniq([...([latest, next, alpha]), ...sortDesc(outOfWindow)])
|
|
230
|
+
.filter((v) => outOfWindow.includes(v))
|
|
231
|
+
|
|
232
|
+
// ── 结论:**任何通道**有窗口外版本 ⇒ ⚠️(宁多报不漏报);拿不到元数据根本走不到这里 ──
|
|
233
|
+
const status = outOfWindow.length === 0 ? 'ok' : 'stale'
|
|
234
|
+
const auditDocExists = typeof repoDocExists === 'function' ? repoDocExists(AUDIT_DOC) : io.exists(join(REPO, AUDIT_DOC))
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
status,
|
|
238
|
+
conclusion: STATUS[status],
|
|
239
|
+
registry: null, // 由 run() 填(evaluate 不关心来源)
|
|
240
|
+
latest,
|
|
241
|
+
next,
|
|
242
|
+
alpha,
|
|
243
|
+
distTags: { ...distTags },
|
|
244
|
+
versionCount: official.length,
|
|
245
|
+
...win,
|
|
246
|
+
inWindow: latestInWindow === null ? false : latestInWindow,
|
|
247
|
+
latestInWindow,
|
|
248
|
+
outOfWindow,
|
|
249
|
+
tagsOutOfWindow,
|
|
250
|
+
pendingCandidate,
|
|
251
|
+
candidateReady,
|
|
252
|
+
gap: latest ? describeGap(windowHighest, latest) : null,
|
|
253
|
+
auditDoc: auditDocExists ? AUDIT_DOC : null,
|
|
254
|
+
nextSteps: buildNextSteps(stepOrder, candidateVersions, auditDocExists),
|
|
255
|
+
error: null,
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** 逐版本给出**照抄可跑**的下一步(已进候选 ⇒ 走验收+晋级;未进候选 ⇒ 走 preflight+快照)。 */
|
|
260
|
+
export function buildNextSteps(stepOrder, candidateVersions, auditDocExists) {
|
|
261
|
+
return stepOrder.map((ver) => {
|
|
262
|
+
const inCandidate = candidateVersions.includes(ver)
|
|
263
|
+
const steps = inCandidate
|
|
264
|
+
? [
|
|
265
|
+
'node scripts/plugin-bootcheck.mjs # 隔离试跑:要求全 preset OK(真实 profile,不重启服务)',
|
|
266
|
+
'node scripts/dsh-upgrade.mjs --patches-only # 五项复核(补丁重打 + 加固 + 旧 session 迁移 + preset 门禁 + 冒烟)',
|
|
267
|
+
`git mv dsh-version-lock/candidate/${ver}.json dsh-version-lock/${ver}.json # 真机重启验收通过后晋级`,
|
|
268
|
+
]
|
|
269
|
+
: [
|
|
270
|
+
`node scripts/dsh-preflight.mjs ${ver}`,
|
|
271
|
+
`node scripts/dsh-snapshot.mjs --from-dir /tmp/dsh-preflight-${ver} --target ${ver} --write`,
|
|
272
|
+
`# ↑ <staged> 缺省 = preflight 建的 /tmp/dsh-preflight-${ver};快照落 dsh-version-lock/candidate/${ver}.json(候选,不参与自动挑选)`,
|
|
273
|
+
]
|
|
274
|
+
if (auditDocExists) steps.push(`# 审计清单:${AUDIT_DOC}`)
|
|
275
|
+
return { version: ver, inCandidate, steps }
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const bar = (s = '') => ` ${s}`
|
|
280
|
+
|
|
281
|
+
/** 人读渲染:结论行恒为**最后一行**,且恒以 `状态:` 开头(可 grep / 可断言)。 */
|
|
282
|
+
export function renderHuman(r) {
|
|
283
|
+
const L = []
|
|
284
|
+
L.push('═══ dsh 官方发版监听(只读)═══')
|
|
285
|
+
L.push(bar(`registry : ${r.registry}`))
|
|
286
|
+
if (r.error) {
|
|
287
|
+
L.push(bar(`取数失败 : ${r.error}`))
|
|
288
|
+
L.push(bar('⛔ 这是"取不到",不是"没有更新" —— 别据此判窗口内;可换 --registry 或稍后重试'))
|
|
289
|
+
}
|
|
290
|
+
const tags = Object.entries(r.distTags || {}).sort(([a], [b]) => (a < b ? -1 : 1)).map(([k, v]) => `${k}=${v}`)
|
|
291
|
+
L.push(bar(`官方 dist-tags: ${tags.length ? tags.join(' ') : '(无)'}`))
|
|
292
|
+
L.push(bar(`版本总数 : ${r.versionCount == null ? '—' : r.versionCount}`))
|
|
293
|
+
L.push(bar(`支持窗口 : ${fmtList(r.verifiedWindow, ' / ')}(最高 ${r.verifiedHighest || '—'};来源 ${r.windowSource})`))
|
|
294
|
+
if (!r.baselineDrift.ok) {
|
|
295
|
+
L.push(bar(`⚠️ 基线漂移 : 内置基线缺 ${fmtList(r.baselineDrift.missing)}`
|
|
296
|
+
+ '(装机时会把已晋级版本报成"未验证";修 scripts/lib/dsh-versions.mjs 的 DSH_VERIFIED_FALLBACK)'))
|
|
297
|
+
}
|
|
298
|
+
L.push(bar(`latest : ${r.latest || '—'}`
|
|
299
|
+
+ (r.gap ? ` —— ⛔ 窗口外:比窗口最高 ${r.verifiedHighest} ${r.gap}` : (r.latestInWindow ? ' —— ✅ 在窗口内' : ''))))
|
|
300
|
+
L.push(bar(`其他通道 : next=${r.next || '—'} alpha=${r.alpha || '—'}`))
|
|
301
|
+
if (r.error) {
|
|
302
|
+
// ⛔ 取不到元数据时**不许**出现"都在窗口内"这类结论(那正是最危险的假绿)。
|
|
303
|
+
L.push(bar('待适配 : 未知 —— 官方侧一个字段都没拿到,⛔ 不得读成"没有更新"'))
|
|
304
|
+
} else if (r.outOfWindow.length) {
|
|
305
|
+
L.push(bar(`待适配(官方已发、窗口内没有,共 ${r.outOfWindow.length} 个): ${fmtList(r.outOfWindow)}`))
|
|
306
|
+
L.push(bar(`dist-tags 指向的窗口外版本: ${fmtList(r.tagsOutOfWindow)}`
|
|
307
|
+
+ (r.latestInWindow === false ? '(latest 出窗 = 直接挡客户,最高优先)' : '(latest 未出窗)')))
|
|
308
|
+
L.push(bar(`候选缺口(官方有、candidate/ 里没有): ${fmtList(r.pendingCandidate)}`))
|
|
309
|
+
L.push(bar(`已在候选(已审查未验收 ⇒ 下一步是验收+晋级): ${fmtList(r.candidateReady)}`))
|
|
310
|
+
L.push('')
|
|
311
|
+
L.push('下一步(按优先级从上往下抄;latest 最先):')
|
|
312
|
+
for (const s of r.nextSteps) {
|
|
313
|
+
L.push(bar(`· ${s.version}${s.inCandidate ? '(已在 candidate/ ⇒ 走验收+晋级)' : '(未进 candidate/ ⇒ 走 preflight+快照)'}`))
|
|
314
|
+
for (const c of s.steps) L.push(bar(` ${c}`))
|
|
315
|
+
}
|
|
316
|
+
} else {
|
|
317
|
+
L.push(bar('待适配 : (无)官方所有已发版本都在窗口内'))
|
|
318
|
+
}
|
|
319
|
+
L.push('')
|
|
320
|
+
L.push(`状态:${r.conclusion}`)
|
|
321
|
+
return L.join('\n')
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** 机器读渲染:字段形状**稳定**(回归 §5 逐字段断言)。 */
|
|
325
|
+
export function renderJson(r) {
|
|
326
|
+
return {
|
|
327
|
+
registry: r.registry,
|
|
328
|
+
latest: r.latest,
|
|
329
|
+
next: r.next,
|
|
330
|
+
alpha: r.alpha,
|
|
331
|
+
versionCount: r.versionCount,
|
|
332
|
+
verifiedWindow: r.verifiedWindow,
|
|
333
|
+
verifiedHighest: r.verifiedHighest,
|
|
334
|
+
inWindow: r.inWindow,
|
|
335
|
+
outOfWindow: r.outOfWindow,
|
|
336
|
+
pendingCandidate: r.pendingCandidate,
|
|
337
|
+
candidateReady: r.candidateReady,
|
|
338
|
+
candidateVersions: r.candidateVersions,
|
|
339
|
+
tagsOutOfWindow: r.tagsOutOfWindow,
|
|
340
|
+
status: r.status,
|
|
341
|
+
conclusion: r.conclusion,
|
|
342
|
+
gap: r.gap,
|
|
343
|
+
windowSource: r.windowSource,
|
|
344
|
+
windowReadable: r.windowReadable,
|
|
345
|
+
baselineDrift: r.baselineDrift,
|
|
346
|
+
distTags: r.distTags,
|
|
347
|
+
auditDoc: r.auditDoc,
|
|
348
|
+
nextSteps: r.nextSteps,
|
|
349
|
+
error: r.error || null,
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export const USAGE = `用法: node scripts/dsh-release-watch.mjs [选项]
|
|
354
|
+
|
|
355
|
+
--registry <url|文件> 官方 registry 元数据来源;http(s) = 真拉,本地路径 / file:// = 读文件(离线夹具)
|
|
356
|
+
默认 ${DEFAULT_REGISTRY}
|
|
357
|
+
--lock-dir <目录> 窗口真源(默认 dsh-version-lock/;回归夹具用临时目录)
|
|
358
|
+
--json 输出机器可读 JSON(字段形状稳定)
|
|
359
|
+
--timeout <毫秒> 单次请求超时(默认 20000)
|
|
360
|
+
-h, --help 本帮助
|
|
361
|
+
|
|
362
|
+
退出码: 0 = 窗口内 / 1 = 有更新未适配 / 2 = 取不到元数据(**不是**"没有更新")
|
|
363
|
+
只读: 不写任何文件、不改 registry、不 npm login;ARGENT_TESTS_OFFLINE=1 时**拒绝**真发网络请求`
|
|
364
|
+
|
|
365
|
+
/** 参数解析(不认识的参数/缺值 ⇒ 收进 errors,由 run() 转成 exit 2)。 */
|
|
366
|
+
export function parseArgs(argv) {
|
|
367
|
+
const out = { registry: DEFAULT_REGISTRY, lockDir: DEFAULT_LOCK_DIR, json: false, help: false, timeoutMs: 20000, errors: [] }
|
|
368
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
369
|
+
const a = argv[i]
|
|
370
|
+
const val = (name) => {
|
|
371
|
+
const v = argv[i + 1]
|
|
372
|
+
if (v === undefined || v.startsWith('--')) { out.errors.push(`${name} 缺参数值`); return null }
|
|
373
|
+
i += 1
|
|
374
|
+
return v
|
|
375
|
+
}
|
|
376
|
+
if (a === '--registry') { const v = val(a); if (v) out.registry = v; continue }
|
|
377
|
+
if (a === '--lock-dir') { const v = val(a); if (v) out.lockDir = v; continue }
|
|
378
|
+
if (a === '--timeout') {
|
|
379
|
+
const v = val(a)
|
|
380
|
+
const n = Number(v)
|
|
381
|
+
if (v && (!Number.isFinite(n) || n <= 0)) out.errors.push(`--timeout 需要正整数毫秒(得到 ${v})`)
|
|
382
|
+
else if (v) out.timeoutMs = n
|
|
383
|
+
continue
|
|
384
|
+
}
|
|
385
|
+
if (a === '--json') { out.json = true; continue }
|
|
386
|
+
if (a === '-h' || a === '--help') { out.help = true; continue }
|
|
387
|
+
out.errors.push(`未知参数 ${a}`)
|
|
388
|
+
}
|
|
389
|
+
return out
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* 主流程(可注入 fetch/io ⇒ 回归能在**零网络**下走同一条代码路径)。
|
|
394
|
+
* @returns {Promise<number>} 退出码(0/1/2)
|
|
395
|
+
*/
|
|
396
|
+
export async function run(argv, {
|
|
397
|
+
fetchImpl = globalThis.fetch,
|
|
398
|
+
io = DEFAULT_IO,
|
|
399
|
+
out = (s) => process.stdout.write(`${s}\n`),
|
|
400
|
+
err = (s) => process.stderr.write(`${s}\n`),
|
|
401
|
+
repoDocExists,
|
|
402
|
+
} = {}) {
|
|
403
|
+
const args = parseArgs(argv)
|
|
404
|
+
if (args.help) { out(USAGE); return 0 }
|
|
405
|
+
if (args.errors.length) {
|
|
406
|
+
for (const e of args.errors) err(`✗ ${e}`)
|
|
407
|
+
err(USAGE)
|
|
408
|
+
return 2
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
let result
|
|
412
|
+
try {
|
|
413
|
+
const raw = await loadMetadata({ registry: args.registry, fetchImpl, io, timeoutMs: args.timeoutMs })
|
|
414
|
+
result = evaluate({ metadata: raw, lockDir: args.lockDir, io, repoDocExists })
|
|
415
|
+
} catch (e) {
|
|
416
|
+
// ★ 关键判据:**取不到元数据 ≠ 没有更新** —— 窗口照常算(那部分只读本机文件),结论恒 ❌ / exit 2。
|
|
417
|
+
result = {
|
|
418
|
+
...evaluateUnavailable({ lockDir: args.lockDir, io }),
|
|
419
|
+
registry: args.registry,
|
|
420
|
+
error: e && e.message ? e.message : String(e),
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
result.registry = args.registry
|
|
424
|
+
if (args.json) out(JSON.stringify(renderJson(result), null, 2))
|
|
425
|
+
else out(renderHuman(result))
|
|
426
|
+
return result.status === 'ok' ? 0 : (result.status === 'stale' ? 1 : 2)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** "取不到元数据"时的结论对象(窗口部分照常给出;官方侧字段一律 null/空 —— ⛔ 不猜)。 */
|
|
430
|
+
export function evaluateUnavailable({ lockDir = DEFAULT_LOCK_DIR, io = DEFAULT_IO } = {}) {
|
|
431
|
+
const win = readWindow({ lockDir, io })
|
|
432
|
+
return {
|
|
433
|
+
status: 'unavailable',
|
|
434
|
+
conclusion: STATUS.unavailable,
|
|
435
|
+
registry: null,
|
|
436
|
+
latest: null,
|
|
437
|
+
next: null,
|
|
438
|
+
alpha: null,
|
|
439
|
+
distTags: {},
|
|
440
|
+
versionCount: null,
|
|
441
|
+
...win,
|
|
442
|
+
inWindow: false,
|
|
443
|
+
latestInWindow: null,
|
|
444
|
+
outOfWindow: [],
|
|
445
|
+
tagsOutOfWindow: [],
|
|
446
|
+
pendingCandidate: [],
|
|
447
|
+
candidateReady: [],
|
|
448
|
+
gap: null,
|
|
449
|
+
auditDoc: null,
|
|
450
|
+
nextSteps: [],
|
|
451
|
+
error: null,
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/** 入口守卫:只在**直接执行**时跑主逻辑(软链安全,判据见 lib/is-direct-run.mjs)。 */
|
|
456
|
+
if (isDirectRun(import.meta.url)) {
|
|
457
|
+
run(process.argv.slice(2)).then((code) => { process.exitCode = code })
|
|
458
|
+
}
|