@askexenow/exe-os 0.9.348 → 0.9.350
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/deploy/compose/docker-compose.yml +15 -6
- package/deploy/compose/erp-nginx/nginx.conf +17 -10
- package/deploy/stack-manifests/v0.9.30-apps.json +81 -0
- package/deploy/stack-manifests/v0.9.json +111 -2
- package/dist/{active-agent-PX6KAEEQ.js → active-agent-PMCD3LFW.js} +3 -3
- package/dist/{active-agent-QVLZKWR2.js → active-agent-WQYB3JQY.js} +3 -3
- package/dist/{agentic-ontology-6MEXY5L7.js → agentic-ontology-HTU5EMEQ.js} +1 -1
- package/dist/{backfill-metadata-MCFBGWKX.js → backfill-metadata-QUUXE2TL.js} +5 -5
- package/dist/{backup-verify-RGB3KKD4.js → backup-verify-YJGUNZWJ.js} +1 -1
- package/dist/{behaviors-6GI6VND2.js → behaviors-P5FORAYU.js} +6 -6
- package/dist/bin/agentic-ontology-backfill.js +6 -6
- package/dist/bin/agentic-reflection-backfill.js +7 -7
- package/dist/bin/agentic-semantic-label.js +6 -6
- package/dist/bin/backfill-conversations.js +5 -5
- package/dist/bin/backfill-responses.js +5 -5
- package/dist/bin/backfill-vectors.js +6 -6
- package/dist/bin/bulk-sync-postgres.js +11 -11
- package/dist/bin/cc-doctor.js +4 -4
- package/dist/bin/cleanup-stale-review-tasks.js +12 -12
- package/dist/bin/cli.js +18 -18
- package/dist/bin/deferred-daemon-restart.js +68 -68
- package/dist/bin/exe-agent-config.js +2 -2
- package/dist/bin/exe-agent.js +5 -5
- package/dist/bin/exe-assign.js +6 -6
- package/dist/bin/exe-boot.js +26 -26
- package/dist/bin/exe-call.js +5 -5
- package/dist/bin/exe-cloud.js +11 -11
- package/dist/bin/exe-config-dump.js +2 -2
- package/dist/bin/exe-dispatch.js +12 -12
- package/dist/bin/exe-doctor.js +4 -4
- package/dist/bin/exe-export-behaviors.js +8 -8
- package/dist/bin/exe-forget.js +7 -7
- package/dist/bin/exe-gateway.js +7 -7
- package/dist/bin/exe-healthcheck.js +4 -4
- package/dist/bin/exe-heartbeat.js +14 -14
- package/dist/bin/exe-kill.js +16 -16
- package/dist/bin/exe-launch-agent.js +23 -23
- package/dist/bin/exe-memory-reads.js +5 -5
- package/dist/bin/exe-new-employee.js +9 -9
- package/dist/bin/exe-os-mcp-proxy.js +3 -3
- package/dist/bin/exe-pending-messages.js +13 -13
- package/dist/bin/exe-pending-notifications.js +12 -12
- package/dist/bin/exe-pending-reviews.js +12 -12
- package/dist/bin/exe-rename.js +5 -5
- package/dist/bin/exe-review.js +15 -15
- package/dist/bin/exe-search.js +6 -6
- package/dist/bin/exe-session-cleanup.js +16 -16
- package/dist/bin/exe-settings.js +11 -11
- package/dist/bin/exe-start-codex.js +13 -13
- package/dist/bin/exe-start-opencode.js +9 -9
- package/dist/bin/exe-status.js +13 -13
- package/dist/bin/exe-support.js +3 -3
- package/dist/bin/exe-team.js +3 -3
- package/dist/bin/git-sweep.js +13 -13
- package/dist/bin/graph-backfill.js +6 -6
- package/dist/bin/graph-export.js +6 -6
- package/dist/bin/import-history.js +9 -9
- package/dist/bin/install.js +70 -70
- package/dist/bin/intercom-check.js +4 -4
- package/dist/bin/mcp-sessions.js +2 -2
- package/dist/bin/orchestration-metrics.js +4 -4
- package/dist/bin/postgres-agentic-reflection-backfill.js +2 -2
- package/dist/bin/postgres-agentic-semantic-backfill.js +1 -1
- package/dist/bin/pre-publish.js +1 -1
- package/dist/bin/reconcile-backlog.js +2 -2
- package/dist/bin/registry-proxy.js +1 -1
- package/dist/bin/scan-tasks.js +12 -12
- package/dist/bin/setup.js +1 -1
- package/dist/bin/shard-migrate.js +5 -5
- package/dist/bin/stack-update.js +4 -4
- package/dist/bin/vps-health-gate.js +1 -1
- package/dist/{boot-generation-FB2J53IG.js → boot-generation-XNR2CBMX.js} +3 -3
- package/dist/{boot-review-sections-QF5MZGJI.js → boot-review-sections-ZWXRZ6YX.js} +11 -11
- package/dist/{capability-cards-YADEM2KN.js → capability-cards-IOGHQJ6Y.js} +2 -2
- package/dist/{capacity-monitor-CG2IGONS.js → capacity-monitor-3YN75QXX.js} +13 -13
- package/dist/{catchup-brief-QFVNMTU5.js → catchup-brief-HPB5CL7R.js} +16 -16
- package/dist/{chunk-YAIFZXOM.js → chunk-4EEML23J.js} +5 -5
- package/dist/{chunk-UTQWVRU6.js → chunk-4Z74A4A6.js} +5 -5
- package/dist/{chunk-27REQ7KR.js → chunk-53HZ23T5.js} +1 -1
- package/dist/{chunk-KZUU6MNF.js → chunk-55OFK4ZY.js} +1 -1
- package/dist/{chunk-DJYJDK53.js → chunk-5J4GLPTU.js} +7 -7
- package/dist/{chunk-WUMWXU6B.js → chunk-5SJXJSAI.js} +1 -1
- package/dist/{chunk-MH5HWMVH.js → chunk-6ON3JHGE.js} +1 -1
- package/dist/{chunk-A42356G7.js → chunk-6QHQCY3N.js} +1 -1
- package/dist/{chunk-YTPCJLEL.js → chunk-AAVVHD4C.js} +1 -1
- package/dist/{chunk-BUUGFQHL.js → chunk-ABIZWPSK.js} +2 -2
- package/dist/{chunk-AUILJKSM.js → chunk-AGCGXXIB.js} +2 -2
- package/dist/{chunk-6WFXNQDJ.js → chunk-AUEMXOJT.js} +1 -1
- package/dist/{chunk-FENYNW52.js → chunk-BAWZEI77.js} +1 -1
- package/dist/{chunk-MMXIE6HV.js → chunk-BHDOPDPY.js} +7 -1
- package/dist/{chunk-M7PBRIRH.js → chunk-CYBDUQDO.js} +1 -1
- package/dist/{chunk-PBLDPW4U.js → chunk-D2UZ35PQ.js} +2 -2
- package/dist/{chunk-KIMAQNSM.js → chunk-DKOWIBBM.js} +3 -3
- package/dist/{chunk-JORPYG4O.js → chunk-DXYHU5D6.js} +1 -1
- package/dist/{chunk-FPK753IZ.js → chunk-EDEVSNAF.js} +9 -9
- package/dist/{chunk-HL24OHHS.js → chunk-EGYDW57S.js} +9 -9
- package/dist/{chunk-WWT3E7DO.js → chunk-ENIFHOMF.js} +3 -3
- package/dist/{chunk-EFHGK6JP.js → chunk-FGII4IO7.js} +1 -1
- package/dist/{chunk-RBRGSOK5.js → chunk-FHOLN7BD.js} +1 -1
- package/dist/{chunk-7WQ3WHPW.js → chunk-FUOXMZUZ.js} +138 -128
- package/dist/{chunk-X6VEQ7MC.js → chunk-GIEIWDC2.js} +1 -1
- package/dist/{chunk-SLH2743O.js → chunk-GINAJJQR.js} +3 -3
- package/dist/{chunk-5KCC26FB.js → chunk-GIZLVGOM.js} +2 -2
- package/dist/{chunk-GJ6KIHOM.js → chunk-GWO54VKD.js} +4 -4
- package/dist/{chunk-FE6WBI2M.js → chunk-GXMY5ERQ.js} +1 -1
- package/dist/chunk-HCRUMO4B.js +58 -0
- package/dist/{chunk-HYZE3MNS.js → chunk-HUCMSRIF.js} +1 -1
- package/dist/{chunk-23O6LHII.js → chunk-I6UOIBCG.js} +1 -1
- package/dist/{chunk-RFRUDXOI.js → chunk-IC4O376V.js} +2 -2
- package/dist/{chunk-AXNUMBLO.js → chunk-IXDSQ2VO.js} +3 -3
- package/dist/{chunk-H3TA23PH.js → chunk-IXJGEZBS.js} +4 -4
- package/dist/{chunk-SQ4WACH4.js → chunk-JFGX7LHH.js} +1 -1
- package/dist/{chunk-MMOONM6D.js → chunk-JO6RMHMW.js} +1 -1
- package/dist/{chunk-DAODOSH4.js → chunk-KBFHODXV.js} +1 -1
- package/dist/{chunk-5RPCWLXE.js → chunk-KES3SNOH.js} +1 -1
- package/dist/{chunk-Z52QQB4Z.js → chunk-KMWRDB5U.js} +2 -2
- package/dist/{chunk-6JQTSKDR.js → chunk-KZ7BANUM.js} +1 -1
- package/dist/{chunk-WL3IQJKN.js → chunk-L3DKZC4R.js} +3 -3
- package/dist/{chunk-NY6DU5RB.js → chunk-L4SGSV4M.js} +1 -1
- package/dist/{chunk-GA655CUD.js → chunk-LFDW43MP.js} +2 -2
- package/dist/{chunk-ZXSFUR77.js → chunk-LOYK2BRY.js} +15 -15
- package/dist/{chunk-H5QNVWU4.js → chunk-LVG5ATTU.js} +10 -10
- package/dist/{chunk-AQ24P2O7.js → chunk-LVYIM3V4.js} +4 -4
- package/dist/{chunk-PAXAF2UY.js → chunk-LYMEWCFD.js} +24 -8
- package/dist/{chunk-OBWIVRD6.js → chunk-MFRJRJ6U.js} +1 -1
- package/dist/{chunk-Y3JXLIZV.js → chunk-MFX2RWJZ.js} +3 -3
- package/dist/{chunk-AMC2OPYP.js → chunk-MGFG7V3M.js} +1 -1
- package/dist/{chunk-A43SIGAO.js → chunk-NXC3Z7VI.js} +3 -3
- package/dist/{chunk-CCOKYSJA.js → chunk-OPDZ62TW.js} +3 -3
- package/dist/{chunk-5C46Y4AK.js → chunk-PBLFSNIA.js} +1 -1
- package/dist/{chunk-6QRS3IZF.js → chunk-PELEK2IT.js} +1 -1
- package/dist/{chunk-TF5E6O5Q.js → chunk-QABQ7C4C.js} +1 -1
- package/dist/{chunk-MGMXYI7Y.js → chunk-QIALP626.js} +2 -2
- package/dist/{chunk-FFCPSPNK.js → chunk-RYKHPC3Y.js} +1 -1
- package/dist/{chunk-MVZLF27P.js → chunk-SDNIBE22.js} +2 -2
- package/dist/{chunk-AFY4V4N3.js → chunk-SLROFI6M.js} +1 -1
- package/dist/{chunk-2AD2GAPC.js → chunk-SUZOENOA.js} +1 -1
- package/dist/{chunk-O522M4YZ.js → chunk-T346KHGF.js} +1 -1
- package/dist/{chunk-TLWYOMSS.js → chunk-T4VUDUZF.js} +3 -3
- package/dist/{chunk-43RTUMUT.js → chunk-TGGOWOLG.js} +1 -1
- package/dist/{chunk-E3BNUQHW.js → chunk-TGU2IB6P.js} +2 -2
- package/dist/{chunk-WZJ3VYRJ.js → chunk-THDM6HMQ.js} +3 -3
- package/dist/{chunk-YM6GJM2F.js → chunk-TXGOFNXD.js} +4 -4
- package/dist/{chunk-UAKGNQWE.js → chunk-U7HUQPVS.js} +1 -1
- package/dist/{chunk-GLOWJP5H.js → chunk-UCO3XIL5.js} +2 -2
- package/dist/{chunk-JW5XS35A.js → chunk-UXOCABN4.js} +4 -4
- package/dist/{chunk-L36RX54F.js → chunk-UZ6AA52H.js} +6 -6
- package/dist/{chunk-2WJYLXPF.js → chunk-VDEBUOPB.js} +1 -1
- package/dist/{chunk-GKNWKQFY.js → chunk-VDKI6PIZ.js} +1 -1
- package/dist/{chunk-6GSYN262.js → chunk-VF6HK2ID.js} +1 -1
- package/dist/{chunk-Z4UKLRML.js → chunk-VU2MZ7BP.js} +18 -6
- package/dist/{chunk-SMQ642D3.js → chunk-WAVZDYXS.js} +4 -4
- package/dist/{chunk-QNKR7WEL.js → chunk-WOGK3FIE.js} +3 -3
- package/dist/{chunk-DXMTJJP3.js → chunk-YJBBW6OT.js} +4 -4
- package/dist/{chunk-MOZY4IQO.js → chunk-Z4F6ZHDV.js} +1 -1
- package/dist/{chunk-BOQUXG7N.js → chunk-ZMDTRDTD.js} +3 -3
- package/dist/{cleanup-graph-garbage-XTSBQXD6.js → cleanup-graph-garbage-DQDJK6L7.js} +7 -7
- package/dist/{cloud-sync-shutdown-A2QB74YM.js → cloud-sync-shutdown-UHICWZ2B.js} +2 -2
- package/dist/{co-activation-S2FVGMQP.js → co-activation-PIRKOOPD.js} +2 -2
- package/dist/{co-occurrence-VQB6U5ME.js → co-occurrence-PO4EPN5C.js} +5 -5
- package/dist/{conversation-entity-extractor-YF3ZLTEJ.js → conversation-entity-extractor-3UUAYXS6.js} +3 -3
- package/dist/{crdt-sync-2SGCDWC3.js → crdt-sync-ZL3GKAQW.js} +1 -1
- package/dist/{crm-webhook-P64SXEHD.js → crm-webhook-BAF3VM34.js} +2 -2
- package/dist/{cto-delegation-gate-6KLGT2LB.js → cto-delegation-gate-AQE2M4HU.js} +11 -11
- package/dist/{daemon-orchestration-JQGKEWPD.js → daemon-orchestration-WHGMXCZW.js} +16 -16
- package/dist/{doc-graph-extractor-DCJMATVZ.js → doc-graph-extractor-IFLEVHUJ.js} +5 -5
- package/dist/{documents-7EPH3VPE.js → documents-2BXH6YE4.js} +6 -6
- package/dist/{dreaming-O4P2W5E5.js → dreaming-AQI4F7J4.js} +15 -15
- package/dist/{exe-drift-ED7FBS2C.js → exe-drift-OBXBE3BJ.js} +3 -3
- package/dist/{exe-export-YJI4USGZ.js → exe-export-VKRI6DK4.js} +8 -8
- package/dist/{exe-import-SUTAYKUW.js → exe-import-EH74J6HM.js} +8 -8
- package/dist/{exe-key-CRSDAOZD.js → exe-key-GYNPBQDF.js} +68 -68
- package/dist/{exe-snapshot-KB4YYDCA.js → exe-snapshot-CWQBLHZN.js} +16 -16
- package/dist/{fast-db-init-3K2PV6GJ.js → fast-db-init-5HF3TLAF.js} +1 -1
- package/dist/{founder-context-JHHFM4GF.js → founder-context-PWEQQF43.js} +2 -2
- package/dist/gateway/index.js +8 -8
- package/dist/{git-staleness-YPHGWE3B.js → git-staleness-ZQEAOK6X.js} +2 -2
- package/dist/{global-procedures-PEWDBSPJ.js → global-procedures-LBAA7VLL.js} +4 -4
- package/dist/{graph-auto-extract-7OJYYV4G.js → graph-auto-extract-XOJSREN4.js} +5 -5
- package/dist/{graph-rag-YFITUAVO.js → graph-rag-P3PSYW7P.js} +3 -3
- package/dist/{hook-prefetch-O6ZCZCTX.js → hook-prefetch-HGHMON76.js} +12 -12
- package/dist/hooks/bug-report-worker.js +14 -14
- package/dist/hooks/codex-stop-task-finalizer.js +15 -15
- package/dist/hooks/commit-complete.js +14 -14
- package/dist/hooks/error-recall.js +7 -7
- package/dist/hooks/exe-heartbeat-hook.js +3 -3
- package/dist/hooks/ingest.js +6 -6
- package/dist/hooks/instructions-loaded.js +4 -4
- package/dist/hooks/manifest.json +19 -19
- package/dist/hooks/notification.js +4 -4
- package/dist/hooks/post-compact.js +13 -13
- package/dist/hooks/post-tool-combined.js +9 -9
- package/dist/hooks/pre-compact.js +14 -14
- package/dist/hooks/pre-tool-use.js +19 -19
- package/dist/hooks/prompt-submit.js +26 -26
- package/dist/hooks/session-end.js +18 -18
- package/dist/hooks/session-start.js +13 -13
- package/dist/hooks/stop.js +17 -17
- package/dist/hooks/subagent-stop.js +15 -15
- package/dist/hooks/summary-worker.js +18 -18
- package/dist/index.js +21 -21
- package/dist/{installer-CY5PN7TN.js → installer-DWOXEACJ.js} +5 -5
- package/dist/{installer-N4QXGINR.js → installer-LDICNL4N.js} +5 -5
- package/dist/{installer-3ONNP5JV.js → installer-WZLGX2W6.js} +5 -5
- package/dist/{intercom-queue-A52WQOCD.js → intercom-queue-S3XGJ632.js} +11 -11
- package/dist/lib/cloud-sync.js +11 -11
- package/dist/lib/consolidation.js +7 -7
- package/dist/lib/database.js +2 -2
- package/dist/lib/db-backup.js +1 -1
- package/dist/lib/db-daemon-client.js +5 -1
- package/dist/lib/db-write-worker.js +2 -2
- package/dist/lib/db.js +2 -2
- package/dist/lib/device-registry.js +1 -1
- package/dist/lib/employee-templates.js +5 -5
- package/dist/lib/employees.js +2 -2
- package/dist/lib/exe-daemon.js +55 -55
- package/dist/lib/hybrid-search.js +6 -6
- package/dist/lib/identity.js +2 -2
- package/dist/lib/license.js +2 -2
- package/dist/lib/messaging.js +12 -12
- package/dist/lib/registry-proxy.js +1 -1
- package/dist/lib/reminders.js +3 -3
- package/dist/lib/schedules.js +6 -6
- package/dist/lib/session-registry.js +4 -4
- package/dist/lib/skill-learning.js +7 -7
- package/dist/lib/store.js +5 -5
- package/dist/lib/task-router.js +3 -3
- package/dist/lib/tasks.js +13 -13
- package/dist/lib/tmux-routing.js +11 -11
- package/dist/lib/token-spend.js +3 -3
- package/dist/{license-gate-ZRORTAQ2.js → license-gate-H3AEDCEX.js} +3 -3
- package/dist/{license-sync-CQHBDUQE.js → license-sync-FBYT75E3.js} +2 -2
- package/dist/mcp/register-tools.js +67 -67
- package/dist/mcp/server.js +68 -68
- package/dist/mcp/tools/complete-reminder.js +4 -4
- package/dist/mcp/tools/create-reminder.js +4 -4
- package/dist/mcp/tools/create-task.js +15 -15
- package/dist/mcp/tools/deactivate-behavior.js +8 -8
- package/dist/mcp/tools/list-reminders.js +4 -4
- package/dist/mcp/tools/list-tasks.js +15 -15
- package/dist/mcp/tools/send-message.js +14 -14
- package/dist/mcp/tools/update-task.js +14 -14
- package/dist/{mcp-http-config-2A6HKQP5.js → mcp-http-config-XE5KEKOR.js} +3 -3
- package/dist/{mcp-session-identity-OPRX32H5.js → mcp-session-identity-QA4ZXYCY.js} +11 -11
- package/dist/{memory-cards-GFO2CSPV.js → memory-cards-GHAU5A4O.js} +5 -5
- package/dist/{memory-graph-extractor-2Z72HN5P.js → memory-graph-extractor-P7B2V6CN.js} +6 -6
- package/dist/{memory-poisoning-defense-WZJ52KXT.js → memory-poisoning-defense-OY4EJES5.js} +2 -2
- package/dist/{memory-pressure-watchdog-IG7VRFIX.js → memory-pressure-watchdog-ZGA5GAUE.js} +12 -12
- package/dist/{memory-reflection-LTRHDGUL.js → memory-reflection-5NAOS2OJ.js} +2 -2
- package/dist/{notification-ledger-KSCPGLQG.js → notification-ledger-LCG3TRSE.js} +12 -12
- package/dist/{notifications-YKHZPCXU.js → notifications-YX43A36Z.js} +11 -11
- package/dist/{orchestration-events-HAFDPTLV.js → orchestration-events-EXQRGVEJ.js} +3 -3
- package/dist/{orchestrator-4MZEQULR.js → orchestrator-3OO5FV6O.js} +13 -13
- package/dist/{pipeline-router-RSRJPZNI.js → pipeline-router-U6U3M5DB.js} +3 -3
- package/dist/{plan-limits-5QYYPIPT.js → plan-limits-FNYZOE7J.js} +5 -5
- package/dist/{platform-procedures-DZ6YABEK.js → platform-procedures-NBKHO33A.js} +1 -1
- package/dist/{projection-worker-SKAMSZJF.js → projection-worker-HIXCZRPD.js} +2 -2
- package/dist/{prospective-memory-V7LFCPU4.js → prospective-memory-WQJJB2PS.js} +2 -2
- package/dist/{reranker-HXMJ7SKW.js → reranker-GJEC7PVT.js} +1 -1
- package/dist/{retrieval-health-4NCBHUUS.js → retrieval-health-IP74ZJ3D.js} +1 -1
- package/dist/{review-polling-KQUALBEU.js → review-polling-AXQWGH4M.js} +12 -12
- package/dist/runtime/index.js +15 -15
- package/dist/{session-events-3KL735DZ.js → session-events-POBJERCM.js} +12 -12
- package/dist/{session-kill-telemetry-KVRGLYVI.js → session-kill-telemetry-WRT5UFGM.js} +12 -12
- package/dist/{session-scope-LBG6DT5O.js → session-scope-J4JJHTOC.js} +11 -11
- package/dist/{setup-wizard-UL3AKI6E.js → setup-wizard-TEBZRHIB.js} +1 -1
- package/dist/{skill-refinement-6HPISXQC.js → skill-refinement-E2X3FHGX.js} +3 -3
- package/dist/{stack-promote-RSOWO6EY.js → stack-promote-OQWEY3RN.js} +13 -3
- package/dist/{stack-release-HGFFFCGC.js → stack-release-NUITUMB4.js} +464 -169
- package/dist/{stack-update-WBDTJJEF.js → stack-update-5XFOBX3A.js} +3 -3
- package/dist/{steward-gate-CWPD4HEK.js → steward-gate-ODMUO6TD.js} +3 -3
- package/dist/{support-inbox-HRIHHKPU.js → support-inbox-HRU4TVLN.js} +3 -3
- package/dist/{task-enforcement-OAOL7XFL.js → task-enforcement-DTQ5QMDD.js} +12 -12
- package/dist/{task-scope-PSVAE3EM.js → task-scope-3F3MSGSX.js} +11 -11
- package/dist/{tasks-crud-TLELWWUL.js → tasks-crud-DBQFOYOX.js} +11 -11
- package/dist/{tasks-notify-7Q2XTDZ7.js → tasks-notify-X7AJMSX6.js} +12 -12
- package/dist/{tasks-review-S6QB5KGS.js → tasks-review-QQXKYQ52.js} +11 -11
- package/dist/{telemetry-upload-LTL5SW24.js → telemetry-upload-3KLDXO4Z.js} +7 -7
- package/dist/{token-budget-SK32GKFZ.js → token-budget-T3L6OHAN.js} +3 -3
- package/dist/{tool-telemetry-QJGJAHOC.js → tool-telemetry-TKENX37V.js} +1 -1
- package/dist/tui/App.js +20 -20
- package/dist/{tui-data-3JSV56ME.js → tui-data-4ITCYY3G.js} +11 -11
- package/dist/{update-support-LMPGWJVG.js → update-support-TT2ESH4G.js} +9 -9
- package/dist/{work-state-heartbeat-MQQOCU54.js → work-state-heartbeat-UAVYBQOK.js} +1 -1
- package/dist/{worker-gate-P2O64XMH.js → worker-gate-GK46QYHU.js} +1 -1
- package/dist/{workflow-engine-BLLQKA3O.js → workflow-engine-YYRUTNCM.js} +2 -2
- package/dist/{worktree-FJZXHDCJ.js → worktree-5LHP677X.js} +4 -4
- package/dist/{worktree-sweep-SW4AG6JR.js → worktree-sweep-TVQVQTVD.js} +5 -5
- package/package.json +3 -2
- package/release-notes.json +43 -51
- /package/dist/{chunk-GFIWADEH.js → chunk-2BJYY4SK.js} +0 -0
- /package/dist/{chunk-GPZQZTJB.js → chunk-2XQZZAFI.js} +0 -0
- /package/dist/{chunk-HTJKAAHD.js → chunk-4NPP7ASA.js} +0 -0
- /package/dist/{chunk-BTP7BORU.js → chunk-AGV4GKNY.js} +0 -0
- /package/dist/{chunk-XF2D4CDA.js → chunk-IMGD6PW3.js} +0 -0
- /package/dist/{chunk-PIUDQ7P2.js → chunk-JAF64HJT.js} +0 -0
- /package/dist/{chunk-QUOMJKVF.js → chunk-JGTJAA6B.js} +0 -0
- /package/dist/{chunk-H4JKOLXU.js → chunk-PENPH5AG.js} +0 -0
- /package/dist/{chunk-DRHXISWL.js → chunk-PIBEPI5W.js} +0 -0
- /package/dist/{chunk-FXZRJT3H.js → chunk-S6APJBIZ.js} +0 -0
- /package/dist/{chunk-E42D2E5F.js → chunk-TEJOHJZQ.js} +0 -0
- /package/dist/{chunk-AQ6ECPUP.js → chunk-TZMXGMSV.js} +0 -0
- /package/dist/{core-memory-Z7LPR3RX.js → core-memory-F2TJ2VVZ.js} +0 -0
- /package/dist/{oauth-server-FSKABATB.js → oauth-server-DCZN5VOK.js} +0 -0
- /package/dist/{project-boot-FVSC4VQC.js → project-boot-GVT2NKGJ.js} +0 -0
- /package/dist/{wiki-acl-CW2UALTJ.js → wiki-acl-NCAPBNOZ.js} +0 -0
|
@@ -1,49 +1,181 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
writeSignedStackManifest
|
|
4
|
+
} from "./chunk-HCRUMO4B.js";
|
|
5
|
+
import "./chunk-KZ7BANUM.js";
|
|
2
6
|
import {
|
|
3
7
|
assertPreflightGate,
|
|
4
8
|
runPreflightOnManifest
|
|
5
9
|
} from "./chunk-3QWOO74G.js";
|
|
10
|
+
import "./chunk-JO6RMHMW.js";
|
|
11
|
+
import "./chunk-IMGD6PW3.js";
|
|
12
|
+
import "./chunk-BCZF3AGJ.js";
|
|
13
|
+
import "./chunk-HSGBZSCA.js";
|
|
6
14
|
import "./chunk-MLKGABMK.js";
|
|
7
15
|
|
|
8
16
|
// src/bin/stack-release.ts
|
|
9
|
-
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
17
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
|
|
10
18
|
import { execFile as execFileCb } from "child_process";
|
|
11
19
|
import { promisify } from "util";
|
|
12
|
-
import
|
|
20
|
+
import path2 from "path";
|
|
13
21
|
import { createInterface } from "readline";
|
|
22
|
+
|
|
23
|
+
// src/lib/release-lock.ts
|
|
24
|
+
import {
|
|
25
|
+
closeSync,
|
|
26
|
+
existsSync,
|
|
27
|
+
mkdirSync,
|
|
28
|
+
openSync,
|
|
29
|
+
readFileSync,
|
|
30
|
+
unlinkSync,
|
|
31
|
+
writeFileSync
|
|
32
|
+
} from "fs";
|
|
33
|
+
import { homedir, hostname as osHostname } from "os";
|
|
34
|
+
import path from "path";
|
|
35
|
+
import process2 from "process";
|
|
36
|
+
var DEFAULT_RELEASE_LOCK_TTL_MS = 2 * 60 * 60 * 1e3;
|
|
37
|
+
function defaultReleaseLockPath() {
|
|
38
|
+
return path.join(homedir(), ".exe-os", "release.lock");
|
|
39
|
+
}
|
|
40
|
+
function defaultIsPidAlive(pid) {
|
|
41
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
42
|
+
try {
|
|
43
|
+
process2.kill(pid, 0);
|
|
44
|
+
return true;
|
|
45
|
+
} catch (err) {
|
|
46
|
+
const code = err && typeof err === "object" && "code" in err ? String(err.code) : "";
|
|
47
|
+
return code === "EPERM";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function readLock(lockPath) {
|
|
51
|
+
const raw = readFileSync(lockPath, "utf8");
|
|
52
|
+
const parsed = JSON.parse(raw);
|
|
53
|
+
if (typeof parsed.pid !== "number" || typeof parsed.startedAt !== "string" || typeof parsed.targetVersion !== "string" || typeof parsed.branch !== "string" || typeof parsed.hostname !== "string") {
|
|
54
|
+
throw new Error("lock file is missing required fields");
|
|
55
|
+
}
|
|
56
|
+
return parsed;
|
|
57
|
+
}
|
|
58
|
+
function writeLockAtomic(lockPath, lock) {
|
|
59
|
+
mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
60
|
+
const fd = openSync(lockPath, "wx", 384);
|
|
61
|
+
try {
|
|
62
|
+
writeFileSync(fd, JSON.stringify(lock, null, 2) + "\n", "utf8");
|
|
63
|
+
} finally {
|
|
64
|
+
closeSync(fd);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function sameLock(a, b) {
|
|
68
|
+
return a.pid === b.pid && a.startedAt === b.startedAt && a.targetVersion === b.targetVersion && a.branch === b.branch && a.hostname === b.hostname;
|
|
69
|
+
}
|
|
70
|
+
function acquireReleaseLock(opts) {
|
|
71
|
+
const lockPath = opts.lockPath ?? defaultReleaseLockPath();
|
|
72
|
+
const ttlMs = opts.ttlMs ?? DEFAULT_RELEASE_LOCK_TTL_MS;
|
|
73
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
74
|
+
const isPidAlive = opts.isPidAlive ?? defaultIsPidAlive;
|
|
75
|
+
const logNote = opts.log ?? (() => {
|
|
76
|
+
});
|
|
77
|
+
if (existsSync(lockPath)) {
|
|
78
|
+
let existing = null;
|
|
79
|
+
try {
|
|
80
|
+
existing = readLock(lockPath);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
83
|
+
if (!opts.force) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`release lock exists but cannot be read safely (${detail}); re-run with --force to override: ${lockPath}`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
unlinkSync(lockPath);
|
|
89
|
+
logNote(`release lock force-cleared unreadable lock at ${lockPath}`);
|
|
90
|
+
}
|
|
91
|
+
if (existing) {
|
|
92
|
+
const startedMs = Date.parse(existing.startedAt);
|
|
93
|
+
if (!Number.isFinite(startedMs) && !opts.force) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`release lock exists with an invalid timestamp (${existing.startedAt}); re-run with --force to override: ${lockPath}`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
const ageMs = Number.isFinite(startedMs) ? now.getTime() - startedMs : Number.POSITIVE_INFINITY;
|
|
99
|
+
const pidAlive = isPidAlive(existing.pid);
|
|
100
|
+
const fresh = ageMs < ttlMs && pidAlive;
|
|
101
|
+
if (fresh && !opts.force) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`release already in progress: pid ${existing.pid} started ${existing.startedAt}; re-run with --force to override`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
unlinkSync(lockPath);
|
|
107
|
+
if (fresh) {
|
|
108
|
+
logNote(`release lock force-cleared: pid ${existing.pid} started ${existing.startedAt}`);
|
|
109
|
+
} else {
|
|
110
|
+
const reason = !pidAlive ? "pid is not alive" : `older than ${Math.round(ttlMs / 6e4)}m TTL`;
|
|
111
|
+
logNote(`stale release lock cleared: pid ${existing.pid} started ${existing.startedAt} (${reason})`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const lock = {
|
|
116
|
+
pid: opts.pid ?? process2.pid,
|
|
117
|
+
startedAt: now.toISOString(),
|
|
118
|
+
targetVersion: opts.targetVersion,
|
|
119
|
+
branch: opts.branch,
|
|
120
|
+
hostname: opts.hostname ?? osHostname()
|
|
121
|
+
};
|
|
122
|
+
try {
|
|
123
|
+
writeLockAtomic(lockPath, lock);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
const code = err && typeof err === "object" && "code" in err ? String(err.code) : "";
|
|
126
|
+
if (code === "EEXIST") {
|
|
127
|
+
throw new Error(`release lock was acquired by another process; re-run when it completes: ${lockPath}`);
|
|
128
|
+
}
|
|
129
|
+
throw err;
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
lockPath,
|
|
133
|
+
lock,
|
|
134
|
+
release: () => {
|
|
135
|
+
if (!existsSync(lockPath)) return;
|
|
136
|
+
try {
|
|
137
|
+
const current = readLock(lockPath);
|
|
138
|
+
if (sameLock(current, lock)) unlinkSync(lockPath);
|
|
139
|
+
} catch {
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/bin/stack-release.ts
|
|
14
146
|
var execFile = promisify(execFileCb);
|
|
15
147
|
function findExeOsRoot() {
|
|
16
148
|
const envRoot = process.env.EXE_OS_ROOT;
|
|
17
|
-
if (envRoot &&
|
|
18
|
-
let dir =
|
|
149
|
+
if (envRoot && existsSync2(envRoot)) return path2.resolve(envRoot);
|
|
150
|
+
let dir = path2.dirname(new URL(import.meta.url).pathname);
|
|
19
151
|
for (let i = 0; i < 5; i++) {
|
|
20
|
-
const pkgPath =
|
|
21
|
-
if (
|
|
152
|
+
const pkgPath = path2.join(dir, "package.json");
|
|
153
|
+
if (existsSync2(pkgPath)) {
|
|
22
154
|
try {
|
|
23
|
-
const pkg = JSON.parse(
|
|
155
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
24
156
|
if (pkg.name === "@askexenow/exe-os") return dir;
|
|
25
157
|
} catch {
|
|
26
158
|
}
|
|
27
159
|
}
|
|
28
|
-
dir =
|
|
160
|
+
dir = path2.dirname(dir);
|
|
29
161
|
}
|
|
30
|
-
return
|
|
162
|
+
return path2.resolve(path2.dirname(new URL(import.meta.url).pathname), "..", "..");
|
|
31
163
|
}
|
|
32
164
|
var EXE_OS_ROOT = findExeOsRoot();
|
|
33
|
-
var PARENT_DIR =
|
|
34
|
-
var MANIFEST_PATH =
|
|
165
|
+
var PARENT_DIR = path2.dirname(EXE_OS_ROOT);
|
|
166
|
+
var MANIFEST_PATH = path2.join(EXE_OS_ROOT, "deploy", "stack-manifests", "v0.9.json");
|
|
35
167
|
function applyRootOverride(rootPath) {
|
|
36
|
-
const resolved =
|
|
37
|
-
if (!
|
|
168
|
+
const resolved = path2.resolve(rootPath);
|
|
169
|
+
if (!existsSync2(resolved)) {
|
|
38
170
|
throw new Error(`--root path does not exist: ${resolved}`);
|
|
39
171
|
}
|
|
40
172
|
EXE_OS_ROOT = resolved;
|
|
41
|
-
PARENT_DIR =
|
|
42
|
-
MANIFEST_PATH =
|
|
173
|
+
PARENT_DIR = path2.dirname(EXE_OS_ROOT);
|
|
174
|
+
MANIFEST_PATH = path2.join(EXE_OS_ROOT, "deploy", "stack-manifests", "v0.9.json");
|
|
43
175
|
}
|
|
44
176
|
function resolveRepoPath(dirName) {
|
|
45
|
-
const direct =
|
|
46
|
-
if (
|
|
177
|
+
const direct = path2.join(PARENT_DIR, dirName);
|
|
178
|
+
if (existsSync2(direct)) return direct;
|
|
47
179
|
const aliases = {
|
|
48
180
|
"exe-crm": ["openclaw", "exe-crm"],
|
|
49
181
|
"exe-wiki": ["ink", "exe-wiki"]
|
|
@@ -51,8 +183,8 @@ function resolveRepoPath(dirName) {
|
|
|
51
183
|
const candidates = aliases[dirName];
|
|
52
184
|
if (candidates) {
|
|
53
185
|
for (const alias of candidates) {
|
|
54
|
-
const p =
|
|
55
|
-
if (
|
|
186
|
+
const p = path2.join(PARENT_DIR, alias);
|
|
187
|
+
if (existsSync2(p)) return p;
|
|
56
188
|
}
|
|
57
189
|
}
|
|
58
190
|
return direct;
|
|
@@ -146,7 +278,8 @@ function parseReleaseArgs(argv) {
|
|
|
146
278
|
root: null,
|
|
147
279
|
forceBuild: false,
|
|
148
280
|
canary: false,
|
|
149
|
-
allowDirty: false
|
|
281
|
+
allowDirty: false,
|
|
282
|
+
force: false
|
|
150
283
|
};
|
|
151
284
|
for (let i = 0; i < argv.length; i++) {
|
|
152
285
|
const arg = argv[i];
|
|
@@ -159,6 +292,7 @@ function parseReleaseArgs(argv) {
|
|
|
159
292
|
else if (arg === "--allow-red") flags.allowRed = true;
|
|
160
293
|
else if (arg === "--allow-dirty") flags.allowDirty = true;
|
|
161
294
|
else if (arg === "--canary") flags.canary = true;
|
|
295
|
+
else if (arg === "--force") flags.force = true;
|
|
162
296
|
else if (arg === "--repos" && argv[i + 1]) {
|
|
163
297
|
flags.repos = argv[++i].split(",").map((s) => s.trim()).filter(Boolean);
|
|
164
298
|
} else if (arg.startsWith("--repos=")) {
|
|
@@ -167,7 +301,7 @@ function parseReleaseArgs(argv) {
|
|
|
167
301
|
flags.root = argv[++i];
|
|
168
302
|
} else if (arg.startsWith("--root=")) {
|
|
169
303
|
flags.root = arg.slice("--root=".length);
|
|
170
|
-
} else if (arg === "--force
|
|
304
|
+
} else if (arg === "--force-build") {
|
|
171
305
|
flags.forceBuild = true;
|
|
172
306
|
}
|
|
173
307
|
}
|
|
@@ -214,6 +348,61 @@ async function ghRun(args) {
|
|
|
214
348
|
const { stdout } = await execFile("gh", args);
|
|
215
349
|
return stdout.trim();
|
|
216
350
|
}
|
|
351
|
+
async function assertReleaseFreshness(opts) {
|
|
352
|
+
const runGit = opts.gitRunner ?? git;
|
|
353
|
+
const logFn = opts.log ?? log;
|
|
354
|
+
const originRef = `origin/${opts.defaultBranch}`;
|
|
355
|
+
logFn("info", `Freshness gate: fetching origin ${opts.defaultBranch} before publish...`);
|
|
356
|
+
await runGit(opts.repoPath, "fetch", "origin", opts.defaultBranch);
|
|
357
|
+
const missing = (await runGit(opts.repoPath, "log", `${opts.releaseCommit}..${originRef}`, "--oneline")).trim();
|
|
358
|
+
if (missing) {
|
|
359
|
+
throw new Error(
|
|
360
|
+
`Freshness gate failed: ${originRef} contains commits not included in release commit ${opts.releaseCommit}.
|
|
361
|
+
Missing-PR list:
|
|
362
|
+
${missing}
|
|
363
|
+
Abort: rebase/re-cut the release from current ${originRef} before publishing.`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
try {
|
|
367
|
+
await runGit(opts.repoPath, "merge-base", "--is-ancestor", originRef, opts.releaseCommit);
|
|
368
|
+
} catch (err) {
|
|
369
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
370
|
+
throw new Error(
|
|
371
|
+
`Freshness gate failed: release commit ${opts.releaseCommit} is not a descendant of ${originRef}. Abort: rebase/re-cut the release from current ${originRef} before publishing. (${detail})`
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
logFn("ok", `Freshness gate passed: release commit contains current ${originRef}`);
|
|
375
|
+
}
|
|
376
|
+
async function printOpenPrGate(opts) {
|
|
377
|
+
const baseBranch = opts.baseBranch ?? "main";
|
|
378
|
+
const runGh = opts.ghRunner ?? ghRun;
|
|
379
|
+
const logFn = opts.log ?? log;
|
|
380
|
+
try {
|
|
381
|
+
const output = await runGh([
|
|
382
|
+
"pr",
|
|
383
|
+
"list",
|
|
384
|
+
"--repo",
|
|
385
|
+
opts.ghRepo,
|
|
386
|
+
"--base",
|
|
387
|
+
baseBranch,
|
|
388
|
+
"--json",
|
|
389
|
+
"number,title"
|
|
390
|
+
]);
|
|
391
|
+
const prs = JSON.parse(output);
|
|
392
|
+
if (prs.length === 0) {
|
|
393
|
+
logFn("info", `Open PR gate: no open PRs targeting ${baseBranch} in ${opts.ghRepo}`);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
logFn("warn", `Open PR gate: ${prs.length} open PR(s) targeting ${baseBranch} in ${opts.ghRepo}:`);
|
|
397
|
+
for (const pr of prs) {
|
|
398
|
+
const number = typeof pr.number === "number" ? `#${pr.number}` : "#?";
|
|
399
|
+
logFn("warn", ` ${number} ${pr.title ?? "(untitled)"}`);
|
|
400
|
+
}
|
|
401
|
+
} catch (err) {
|
|
402
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
403
|
+
logFn("warn", `Open PR gate: unable to list open PRs for ${opts.ghRepo} targeting ${baseBranch}; continuing (${detail})`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
217
406
|
function bumpVersion(version, mode) {
|
|
218
407
|
const parts = version.split(".").map(Number);
|
|
219
408
|
if (parts.length < 3) throw new Error(`Invalid version: ${version}`);
|
|
@@ -229,16 +418,16 @@ function bumpVersion(version, mode) {
|
|
|
229
418
|
}
|
|
230
419
|
}
|
|
231
420
|
function readPackageVersion(repoPath) {
|
|
232
|
-
const pkgPath =
|
|
233
|
-
if (!
|
|
421
|
+
const pkgPath = path2.join(repoPath, "package.json");
|
|
422
|
+
if (!existsSync2(pkgPath)) {
|
|
234
423
|
throw new Error(`Missing package.json at ${pkgPath}`);
|
|
235
424
|
}
|
|
236
|
-
const pkg = JSON.parse(
|
|
425
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
237
426
|
return pkg.version;
|
|
238
427
|
}
|
|
239
428
|
function readPackageVersionOrNull(repoPath) {
|
|
240
|
-
const pkgPath =
|
|
241
|
-
if (!
|
|
429
|
+
const pkgPath = path2.join(repoPath, "package.json");
|
|
430
|
+
if (!existsSync2(pkgPath)) return null;
|
|
242
431
|
return readPackageVersion(repoPath);
|
|
243
432
|
}
|
|
244
433
|
function repoName(repo) {
|
|
@@ -251,15 +440,15 @@ function warnMissingRepoPackage(repo) {
|
|
|
251
440
|
);
|
|
252
441
|
}
|
|
253
442
|
function hasPackageJson(repo) {
|
|
254
|
-
return
|
|
443
|
+
return existsSync2(path2.join(repo.localPath, "package.json"));
|
|
255
444
|
}
|
|
256
445
|
function deriveCloneUrl(repo) {
|
|
257
446
|
return `https://github.com/${repo.ghRepo}.git`;
|
|
258
447
|
}
|
|
259
448
|
function isCloneTargetSafe(targetPath, parentDir) {
|
|
260
|
-
const resolvedParent =
|
|
261
|
-
const resolvedTarget =
|
|
262
|
-
return resolvedTarget === resolvedParent || resolvedTarget.startsWith(resolvedParent +
|
|
449
|
+
const resolvedParent = path2.resolve(parentDir);
|
|
450
|
+
const resolvedTarget = path2.resolve(targetPath);
|
|
451
|
+
return resolvedTarget === resolvedParent || resolvedTarget.startsWith(resolvedParent + path2.sep);
|
|
263
452
|
}
|
|
264
453
|
var defaultCloneRunner = async (url, targetPath) => {
|
|
265
454
|
await execFile("git", ["clone", url, targetPath]);
|
|
@@ -284,9 +473,9 @@ async function ensureRepoCloned(repo, parentDir = PARENT_DIR, runClone = default
|
|
|
284
473
|
return true;
|
|
285
474
|
}
|
|
286
475
|
function readStackReleaseJson(repoPath) {
|
|
287
|
-
const p =
|
|
288
|
-
if (!
|
|
289
|
-
return JSON.parse(
|
|
476
|
+
const p = path2.join(repoPath, "stack.release.json");
|
|
477
|
+
if (!existsSync2(p)) return null;
|
|
478
|
+
return JSON.parse(readFileSync2(p, "utf8"));
|
|
290
479
|
}
|
|
291
480
|
async function detectChanges(repos) {
|
|
292
481
|
const changes = [];
|
|
@@ -338,10 +527,10 @@ async function bumpVersions(changes, mode, dryRun) {
|
|
|
338
527
|
log("dry", `${change.repo.serviceKey}: ${change.currentVersion} -> ${newVersion}`);
|
|
339
528
|
continue;
|
|
340
529
|
}
|
|
341
|
-
const pkgPath =
|
|
342
|
-
const pkg = JSON.parse(
|
|
530
|
+
const pkgPath = path2.join(change.repo.localPath, "package.json");
|
|
531
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
343
532
|
pkg.version = newVersion;
|
|
344
|
-
|
|
533
|
+
writeFileSync2(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
345
534
|
const srf = readStackReleaseJson(change.repo.localPath);
|
|
346
535
|
if (srf) {
|
|
347
536
|
srf.version = newVersion;
|
|
@@ -363,8 +552,8 @@ async function bumpVersions(changes, mode, dryRun) {
|
|
|
363
552
|
}
|
|
364
553
|
}
|
|
365
554
|
}
|
|
366
|
-
|
|
367
|
-
|
|
555
|
+
writeFileSync2(
|
|
556
|
+
path2.join(change.repo.localPath, "stack.release.json"),
|
|
368
557
|
JSON.stringify(srf, null, 2) + "\n"
|
|
369
558
|
);
|
|
370
559
|
}
|
|
@@ -374,6 +563,7 @@ async function bumpVersions(changes, mode, dryRun) {
|
|
|
374
563
|
}
|
|
375
564
|
}
|
|
376
565
|
async function landViaPR(repoPath, ghRepo, defaultBranch, branch, title) {
|
|
566
|
+
const pushedSha = await git(repoPath, "rev-parse", "HEAD");
|
|
377
567
|
await git(repoPath, "push", "--force-with-lease", "origin", `HEAD:refs/heads/${branch}`);
|
|
378
568
|
try {
|
|
379
569
|
await ghRun([
|
|
@@ -392,13 +582,41 @@ async function landViaPR(repoPath, ghRepo, defaultBranch, branch, title) {
|
|
|
392
582
|
]);
|
|
393
583
|
} catch {
|
|
394
584
|
}
|
|
395
|
-
await ghRun(["pr", "merge", branch, "--repo", ghRepo, "--auto", "--squash"]);
|
|
396
585
|
const deadline = Date.now() + 30 * 60 * 1e3;
|
|
586
|
+
let releasePr;
|
|
587
|
+
for (; ; ) {
|
|
588
|
+
try {
|
|
589
|
+
const output = await ghRun([
|
|
590
|
+
"pr",
|
|
591
|
+
"list",
|
|
592
|
+
"--repo",
|
|
593
|
+
ghRepo,
|
|
594
|
+
"--head",
|
|
595
|
+
branch,
|
|
596
|
+
"--state",
|
|
597
|
+
"all",
|
|
598
|
+
"--json",
|
|
599
|
+
"number,state,headRefOid"
|
|
600
|
+
]);
|
|
601
|
+
releasePr = findPullRequestForHeadSha(JSON.parse(output), pushedSha);
|
|
602
|
+
} catch {
|
|
603
|
+
}
|
|
604
|
+
if (releasePr) break;
|
|
605
|
+
if (Date.now() > deadline) {
|
|
606
|
+
throw new Error(
|
|
607
|
+
`No PR on ${ghRepo} (${branch}) matches released commit ${pushedSha.slice(0, 12)} within 30m \u2014 failing closed rather than attributing another PR's verdict. Aborting release; ${defaultBranch} left untouched.`
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
await sleep(15e3);
|
|
611
|
+
}
|
|
612
|
+
if (releasePr.state !== "MERGED") {
|
|
613
|
+
await ghRun(["pr", "merge", String(releasePr.number), "--repo", ghRepo, "--auto", "--squash"]);
|
|
614
|
+
}
|
|
397
615
|
for (; ; ) {
|
|
398
616
|
const state = (await ghRun([
|
|
399
617
|
"pr",
|
|
400
618
|
"view",
|
|
401
|
-
|
|
619
|
+
String(releasePr.number),
|
|
402
620
|
"--repo",
|
|
403
621
|
ghRepo,
|
|
404
622
|
"--json",
|
|
@@ -409,10 +627,10 @@ async function landViaPR(repoPath, ghRepo, defaultBranch, branch, title) {
|
|
|
409
627
|
if (state === "MERGED") break;
|
|
410
628
|
if (Date.now() > deadline) {
|
|
411
629
|
throw new Error(
|
|
412
|
-
`Release PR ${ghRepo} (${branch}) did not merge within 30m \u2014 required CI likely red. Aborting release; ${defaultBranch} left untouched.`
|
|
630
|
+
`Release PR ${ghRepo}#${releasePr.number} (${branch} @ ${pushedSha.slice(0, 12)}) did not merge within 30m \u2014 required CI likely red. Aborting release; ${defaultBranch} left untouched.`
|
|
413
631
|
);
|
|
414
632
|
}
|
|
415
|
-
await
|
|
633
|
+
await sleep(3e4);
|
|
416
634
|
}
|
|
417
635
|
await git(repoPath, "fetch", "origin", defaultBranch);
|
|
418
636
|
await git(repoPath, "checkout", defaultBranch);
|
|
@@ -432,9 +650,16 @@ async function tagAndTriggerCI(changes, dryRun) {
|
|
|
432
650
|
branch,
|
|
433
651
|
`chore: bump ${change.tag} for stack release`
|
|
434
652
|
);
|
|
653
|
+
change.releaseSha = await git(change.repo.localPath, "rev-parse", "HEAD");
|
|
654
|
+
await assertReleaseFreshness({
|
|
655
|
+
repoPath: change.repo.localPath,
|
|
656
|
+
defaultBranch: change.repo.defaultBranch,
|
|
657
|
+
releaseCommit: change.releaseSha
|
|
658
|
+
});
|
|
659
|
+
await printOpenPrGate({ ghRepo: change.repo.ghRepo, baseBranch: "main" });
|
|
435
660
|
await git(change.repo.localPath, "tag", change.tag);
|
|
436
661
|
await git(change.repo.localPath, "push", "origin", change.tag);
|
|
437
|
-
log("ok", `Landed ${change.repo.serviceKey} ${change.tag} via PR + tagged (green-CI gated)`);
|
|
662
|
+
log("ok", `Landed ${change.repo.serviceKey} ${change.tag} via PR + tagged ${change.releaseSha.slice(0, 12)} (green-CI gated)`);
|
|
438
663
|
}
|
|
439
664
|
}
|
|
440
665
|
async function rollbackFailedReleases(changes, ciStatuses, dryRun) {
|
|
@@ -472,6 +697,26 @@ async function rollbackFailedReleases(changes, ciStatuses, dryRun) {
|
|
|
472
697
|
}
|
|
473
698
|
}
|
|
474
699
|
}
|
|
700
|
+
function workflowRunHeadSha(run) {
|
|
701
|
+
return run.headSha ?? run.head_sha;
|
|
702
|
+
}
|
|
703
|
+
function findPullRequestForHeadSha(prs, pushedSha) {
|
|
704
|
+
const normalizedSha = pushedSha.toLowerCase();
|
|
705
|
+
return prs.find((pr) => pr.headRefOid?.toLowerCase() === normalizedSha);
|
|
706
|
+
}
|
|
707
|
+
function findWorkflowRunForHeadSha(runs, releaseSha) {
|
|
708
|
+
const normalizedReleaseSha = releaseSha.toLowerCase();
|
|
709
|
+
return runs.find((run) => workflowRunHeadSha(run)?.toLowerCase() === normalizedReleaseSha);
|
|
710
|
+
}
|
|
711
|
+
async function resolveReleaseSha(change) {
|
|
712
|
+
if (change.releaseSha) return change.releaseSha;
|
|
713
|
+
try {
|
|
714
|
+
change.releaseSha = await git(change.repo.localPath, "rev-parse", `${change.tag}^{}`);
|
|
715
|
+
return change.releaseSha;
|
|
716
|
+
} catch {
|
|
717
|
+
return void 0;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
475
720
|
async function waitForCI(changes, dryRun) {
|
|
476
721
|
if (dryRun) {
|
|
477
722
|
return changes.map((c) => ({
|
|
@@ -495,6 +740,10 @@ async function waitForCI(changes, dryRun) {
|
|
|
495
740
|
if (pending.length === 0) break;
|
|
496
741
|
for (const st of pending) {
|
|
497
742
|
try {
|
|
743
|
+
const change = changes.find((c) => c.repo.serviceKey === st.repo.serviceKey && c.tag === st.tag);
|
|
744
|
+
if (!change) continue;
|
|
745
|
+
const releaseSha = await resolveReleaseSha(change);
|
|
746
|
+
if (!releaseSha) continue;
|
|
498
747
|
const output = await ghRun([
|
|
499
748
|
"run",
|
|
500
749
|
"list",
|
|
@@ -503,17 +752,17 @@ async function waitForCI(changes, dryRun) {
|
|
|
503
752
|
"--workflow",
|
|
504
753
|
st.repo.releaseWorkflow,
|
|
505
754
|
"--limit",
|
|
506
|
-
"
|
|
755
|
+
"50",
|
|
507
756
|
"--json",
|
|
508
|
-
"status,conclusion,url"
|
|
757
|
+
"status,conclusion,url,headSha"
|
|
509
758
|
]);
|
|
510
759
|
const runs = JSON.parse(output);
|
|
511
|
-
const
|
|
512
|
-
if (!
|
|
513
|
-
if (
|
|
760
|
+
const releaseRun = findWorkflowRunForHeadSha(runs, releaseSha);
|
|
761
|
+
if (!releaseRun) continue;
|
|
762
|
+
if (releaseRun.status === "completed") {
|
|
514
763
|
st.status = "completed";
|
|
515
|
-
st.conclusion =
|
|
516
|
-
st.url =
|
|
764
|
+
st.conclusion = releaseRun.conclusion ?? "unknown";
|
|
765
|
+
st.url = releaseRun.url;
|
|
517
766
|
if (st.conclusion === "success") {
|
|
518
767
|
log("ok", `CI passed: ${st.repo.serviceKey} ${st.tag}`);
|
|
519
768
|
} else {
|
|
@@ -748,7 +997,7 @@ function printImageVerification(results) {
|
|
|
748
997
|
console.log("");
|
|
749
998
|
}
|
|
750
999
|
async function updateStackManifest(changes, ciStatuses, dryRun, imageResults, canary = false) {
|
|
751
|
-
const manifest = JSON.parse(
|
|
1000
|
+
const manifest = JSON.parse(readFileSync2(MANIFEST_PATH, "utf8"));
|
|
752
1001
|
const currentLatest = manifest.latest;
|
|
753
1002
|
const currentParts = currentLatest.split(".").map(Number);
|
|
754
1003
|
const newStackVersion = `${currentParts[0]}.${currentParts[1]}.${(currentParts[2] ?? 0) + 1}`;
|
|
@@ -812,7 +1061,7 @@ async function updateStackManifest(changes, ciStatuses, dryRun, imageResults, ca
|
|
|
812
1061
|
}
|
|
813
1062
|
manifest.stacks[newStackVersion] = newEntry;
|
|
814
1063
|
if (canary) {
|
|
815
|
-
|
|
1064
|
+
writeSignedStackManifest(MANIFEST_PATH, manifest, { warn: (msg) => log("warn", msg) });
|
|
816
1065
|
log("ok", `Stack manifest updated: added CANARY ${newStackVersion} (latest unchanged: ${currentLatest})`);
|
|
817
1066
|
log("warn", `${newStackVersion} is a CANARY release. Deploy to a designated canary host with:`);
|
|
818
1067
|
log("warn", ` exe-os stack-update --target ${newStackVersion} --yes`);
|
|
@@ -820,7 +1069,7 @@ async function updateStackManifest(changes, ciStatuses, dryRun, imageResults, ca
|
|
|
820
1069
|
log("warn", ` exe-os stack-promote --version ${newStackVersion} --yes`);
|
|
821
1070
|
} else {
|
|
822
1071
|
manifest.latest = newStackVersion;
|
|
823
|
-
|
|
1072
|
+
writeSignedStackManifest(MANIFEST_PATH, manifest, { warn: (msg) => log("warn", msg) });
|
|
824
1073
|
log("ok", `Stack manifest updated: ${currentLatest} -> ${newStackVersion}`);
|
|
825
1074
|
}
|
|
826
1075
|
return newStackVersion;
|
|
@@ -828,18 +1077,18 @@ async function updateStackManifest(changes, ciStatuses, dryRun, imageResults, ca
|
|
|
828
1077
|
function gatePublishWithPreflight(newStackVersion, dryRun, allowRed) {
|
|
829
1078
|
let manifest;
|
|
830
1079
|
try {
|
|
831
|
-
manifest = JSON.parse(
|
|
1080
|
+
manifest = JSON.parse(readFileSync2(MANIFEST_PATH, "utf8"));
|
|
832
1081
|
} catch (err) {
|
|
833
1082
|
throw new Error(`Preflight gate: cannot read manifest ${MANIFEST_PATH}: ${err instanceof Error ? err.message : String(err)}`);
|
|
834
1083
|
}
|
|
835
|
-
const composeDir =
|
|
1084
|
+
const composeDir = path2.join(EXE_OS_ROOT, "deploy", "compose");
|
|
836
1085
|
const report = runPreflightOnManifest({
|
|
837
1086
|
manifest,
|
|
838
1087
|
stackVersion: newStackVersion,
|
|
839
1088
|
label: `${MANIFEST_PATH} (stack ${newStackVersion})`,
|
|
840
|
-
envExamplePath:
|
|
841
|
-
composePath:
|
|
842
|
-
monitorComposePath:
|
|
1089
|
+
envExamplePath: path2.join(composeDir, ".env.example"),
|
|
1090
|
+
composePath: path2.join(composeDir, "docker-compose.yml"),
|
|
1091
|
+
monitorComposePath: path2.join(EXE_OS_ROOT, "deploy", "monitor", "docker-compose.hub.yml")
|
|
843
1092
|
});
|
|
844
1093
|
log("info", "Running static preflight gate against the freshly-written manifest...");
|
|
845
1094
|
if (dryRun) {
|
|
@@ -861,7 +1110,7 @@ function gatePublishWithPreflight(newStackVersion, dryRun, allowRed) {
|
|
|
861
1110
|
function gatePreMutationStructure(changes, dryRun, allowRed) {
|
|
862
1111
|
let baseManifest;
|
|
863
1112
|
try {
|
|
864
|
-
baseManifest = JSON.parse(
|
|
1113
|
+
baseManifest = JSON.parse(readFileSync2(MANIFEST_PATH, "utf8"));
|
|
865
1114
|
} catch (err) {
|
|
866
1115
|
throw new Error(`Pre-mutation gate: cannot read manifest ${MANIFEST_PATH}: ${err instanceof Error ? err.message : String(err)}`);
|
|
867
1116
|
}
|
|
@@ -880,14 +1129,14 @@ function gatePreMutationStructure(changes, dryRun, allowRed) {
|
|
|
880
1129
|
latest: simVersion,
|
|
881
1130
|
stacks: { [simVersion]: { version: simVersion, services } }
|
|
882
1131
|
};
|
|
883
|
-
const composeDir =
|
|
1132
|
+
const composeDir = path2.join(EXE_OS_ROOT, "deploy", "compose");
|
|
884
1133
|
const report = runPreflightOnManifest({
|
|
885
1134
|
manifest: simManifest,
|
|
886
1135
|
stackVersion: simVersion,
|
|
887
1136
|
label: `pre-mutation simulated manifest (stack ${simVersion})`,
|
|
888
|
-
envExamplePath:
|
|
889
|
-
composePath:
|
|
890
|
-
monitorComposePath:
|
|
1137
|
+
envExamplePath: path2.join(composeDir, ".env.example"),
|
|
1138
|
+
composePath: path2.join(composeDir, "docker-compose.yml"),
|
|
1139
|
+
monitorComposePath: path2.join(EXE_OS_ROOT, "deploy", "monitor", "docker-compose.hub.yml")
|
|
891
1140
|
});
|
|
892
1141
|
const adjusted = {
|
|
893
1142
|
...report,
|
|
@@ -929,6 +1178,13 @@ async function commitAndTagExeOs(newStackVersion, _exeOsChange, dryRun) {
|
|
|
929
1178
|
log("ok", `Committed stack manifest v${newStackVersion} (via PR)`);
|
|
930
1179
|
}
|
|
931
1180
|
const stackTag = `stack-v${newStackVersion}`;
|
|
1181
|
+
const releaseCommit = await git(EXE_OS_ROOT, "rev-parse", "HEAD");
|
|
1182
|
+
await assertReleaseFreshness({
|
|
1183
|
+
repoPath: EXE_OS_ROOT,
|
|
1184
|
+
defaultBranch: "main",
|
|
1185
|
+
releaseCommit
|
|
1186
|
+
});
|
|
1187
|
+
await printOpenPrGate({ ghRepo: "AskExe/exe-os", baseBranch: "main" });
|
|
932
1188
|
try {
|
|
933
1189
|
await git(EXE_OS_ROOT, "tag", stackTag);
|
|
934
1190
|
await git(EXE_OS_ROOT, "push", "origin", stackTag);
|
|
@@ -997,9 +1253,25 @@ async function confirm(message) {
|
|
|
997
1253
|
});
|
|
998
1254
|
});
|
|
999
1255
|
}
|
|
1256
|
+
function nextStackVersionForLock() {
|
|
1257
|
+
try {
|
|
1258
|
+
const manifest = JSON.parse(readFileSync2(MANIFEST_PATH, "utf8"));
|
|
1259
|
+
const parts = String(manifest.latest ?? "0.9.0").split(".").map(Number);
|
|
1260
|
+
return `stack-v${parts[0] ?? 0}.${parts[1] ?? 9}.${(parts[2] ?? 0) + 1}`;
|
|
1261
|
+
} catch {
|
|
1262
|
+
return "stack-vunknown";
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
async function currentReleaseBranch() {
|
|
1266
|
+
try {
|
|
1267
|
+
return await git(EXE_OS_ROOT, "branch", "--show-current") || "unknown";
|
|
1268
|
+
} catch {
|
|
1269
|
+
return "unknown";
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1000
1272
|
async function runStackRelease(flags) {
|
|
1001
1273
|
if (!flags.stack) {
|
|
1002
|
-
console.error("Usage: exe-os release --stack [--dry-run] [--repos <list>] [--yes] [--skip-image-verify] [--allow-red] [--allow-dirty] [--canary] [--root <path>] [--force]");
|
|
1274
|
+
console.error("Usage: exe-os release --stack [--dry-run] [--repos <list>] [--yes] [--skip-image-verify] [--allow-red] [--allow-dirty] [--canary] [--root <path>] [--force] [--force-build]");
|
|
1003
1275
|
process.exit(1);
|
|
1004
1276
|
}
|
|
1005
1277
|
if (flags.root) {
|
|
@@ -1010,133 +1282,156 @@ async function runStackRelease(flags) {
|
|
|
1010
1282
|
console.warn(" \x1B[33m Docker images will be built WITHOUT requiring green CI.\x1B[0m");
|
|
1011
1283
|
console.warn(" \x1B[33m Use only for break-glass emergencies.\x1B[0m\n");
|
|
1012
1284
|
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1285
|
+
if (flags.force) {
|
|
1286
|
+
console.warn("\n \x1B[33;1m\u26A0 --force: release mutex override is ACTIVE\x1B[0m");
|
|
1287
|
+
console.warn(" \x1B[33m This only overrides ~/.exe-os/release.lock; freshness and publish gates still apply.\x1B[0m");
|
|
1288
|
+
console.warn(" \x1B[33m Use only after verifying no other release is actually running.\x1B[0m\n");
|
|
1289
|
+
}
|
|
1290
|
+
let releaseLock;
|
|
1291
|
+
if (!flags.dryRun) {
|
|
1292
|
+
releaseLock = acquireReleaseLock({
|
|
1293
|
+
targetVersion: nextStackVersionForLock(),
|
|
1294
|
+
branch: await currentReleaseBranch(),
|
|
1295
|
+
force: flags.force,
|
|
1296
|
+
log: (message) => log("warn", message)
|
|
1297
|
+
});
|
|
1298
|
+
log("ok", `Acquired release lock: ${releaseLock.lockPath}`);
|
|
1299
|
+
}
|
|
1300
|
+
try {
|
|
1301
|
+
await assertCleanWorkingTree({ allowDirty: flags.allowDirty || flags.dryRun });
|
|
1302
|
+
console.log(`
|
|
1015
1303
|
\x1B[1mexe-os stack release\x1B[0m${flags.dryRun ? " (dry run)" : ""}
|
|
1016
1304
|
`);
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1305
|
+
const allRepos = buildRepoConfigs();
|
|
1306
|
+
let repos;
|
|
1307
|
+
if (flags.repos) {
|
|
1308
|
+
repos = [];
|
|
1309
|
+
for (const name of flags.repos) {
|
|
1310
|
+
const match = allRepos.find(
|
|
1311
|
+
(r) => r.serviceKey === name || r.ghRepo.endsWith(`/${name}`) || r.ghRepo === name
|
|
1312
|
+
);
|
|
1313
|
+
if (match) {
|
|
1314
|
+
repos.push(match);
|
|
1315
|
+
} else {
|
|
1316
|
+
log("warn", `Unknown repo: ${name} \u2014 skipping`);
|
|
1317
|
+
}
|
|
1029
1318
|
}
|
|
1319
|
+
if (repos.length === 0) {
|
|
1320
|
+
throw new Error("No valid repos specified.");
|
|
1321
|
+
}
|
|
1322
|
+
} else {
|
|
1323
|
+
repos = allRepos;
|
|
1030
1324
|
}
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
warnMissingRepoPackage(repo);
|
|
1041
|
-
return false;
|
|
1042
|
-
});
|
|
1043
|
-
log("info", "Detecting changes across repos...");
|
|
1044
|
-
const changes = await detectChanges(repos);
|
|
1045
|
-
if (changes.length === 0) {
|
|
1046
|
-
console.log("\n No changes detected since last tags. Nothing to release.\n");
|
|
1047
|
-
return;
|
|
1048
|
-
}
|
|
1049
|
-
const bumpMode = flags.major ? "major" : flags.minor ? "minor" : "patch";
|
|
1050
|
-
for (const change of changes) {
|
|
1051
|
-
const newVersion = bumpVersion(change.currentVersion, bumpMode);
|
|
1052
|
-
change.newVersion = newVersion;
|
|
1053
|
-
change.tag = `v${newVersion}`;
|
|
1054
|
-
}
|
|
1055
|
-
console.log("\n Planned releases:");
|
|
1056
|
-
for (const change of changes) {
|
|
1057
|
-
console.log(
|
|
1058
|
-
` ${change.repo.serviceKey.padEnd(14)} ${change.currentVersion} -> ${change.newVersion} (${change.commits.length} commit${change.commits.length === 1 ? "" : "s"})`
|
|
1059
|
-
);
|
|
1060
|
-
}
|
|
1061
|
-
gatePreMutationStructure(changes, flags.dryRun, flags.allowRed);
|
|
1062
|
-
if (!flags.yes && !flags.dryRun) {
|
|
1063
|
-
const ok = await confirm("Proceed with release?");
|
|
1064
|
-
if (!ok) {
|
|
1065
|
-
console.log(" Aborted.\n");
|
|
1325
|
+
repos = repos.filter((repo) => {
|
|
1326
|
+
if (hasPackageJson(repo)) return true;
|
|
1327
|
+
warnMissingRepoPackage(repo);
|
|
1328
|
+
return false;
|
|
1329
|
+
});
|
|
1330
|
+
log("info", "Detecting changes across repos...");
|
|
1331
|
+
const changes = await detectChanges(repos);
|
|
1332
|
+
if (changes.length === 0) {
|
|
1333
|
+
console.log("\n No changes detected since last tags. Nothing to release.\n");
|
|
1066
1334
|
return;
|
|
1067
1335
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
const ciFailures = ciStatuses.filter(
|
|
1074
|
-
(s) => s.status !== "completed" || s.conclusion !== "success"
|
|
1075
|
-
);
|
|
1076
|
-
if (ciFailures.length > 0 && !flags.dryRun) {
|
|
1077
|
-
await rollbackFailedReleases(changes, ciStatuses, flags.dryRun);
|
|
1078
|
-
const ciSuccesses = ciStatuses.filter(
|
|
1079
|
-
(s) => s.status === "completed" && s.conclusion === "success"
|
|
1080
|
-
);
|
|
1081
|
-
if (ciSuccesses.length === 0) {
|
|
1082
|
-
log("fail", "All CI builds failed. Release aborted, all bumps rolled back.");
|
|
1083
|
-
process.exit(1);
|
|
1336
|
+
const bumpMode = flags.major ? "major" : flags.minor ? "minor" : "patch";
|
|
1337
|
+
for (const change of changes) {
|
|
1338
|
+
const newVersion = bumpVersion(change.currentVersion, bumpMode);
|
|
1339
|
+
change.newVersion = newVersion;
|
|
1340
|
+
change.tag = `v${newVersion}`;
|
|
1084
1341
|
}
|
|
1085
|
-
log("
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
} else {
|
|
1091
|
-
imageResults = await verifyImages(changes, ciStatuses, flags.dryRun);
|
|
1092
|
-
const failed = imageResults.filter((r) => (r.ghcr === "missing" || r.ghcr === "error") && r.proxy !== "ok");
|
|
1093
|
-
if (failed.length > 0 && !flags.dryRun) {
|
|
1094
|
-
for (const m of failed) {
|
|
1095
|
-
log("fail", `${shortImageName(m.repo.imageName)}:${m.tag} \u2014 GHCR status: ${m.ghcr}, proxy: ${m.proxy} \u2014 cannot release`);
|
|
1096
|
-
}
|
|
1097
|
-
log("fail", "Aborting: images not verified. Fix GHCR auth/CI or use --skip-image-verify to override.");
|
|
1098
|
-
process.exit(1);
|
|
1342
|
+
console.log("\n Planned releases:");
|
|
1343
|
+
for (const change of changes) {
|
|
1344
|
+
console.log(
|
|
1345
|
+
` ${change.repo.serviceKey.padEnd(14)} ${change.currentVersion} -> ${change.newVersion} (${change.commits.length} commit${change.commits.length === 1 ? "" : "s"})`
|
|
1346
|
+
);
|
|
1099
1347
|
}
|
|
1100
|
-
|
|
1101
|
-
if (
|
|
1102
|
-
|
|
1103
|
-
|
|
1348
|
+
gatePreMutationStructure(changes, flags.dryRun, flags.allowRed);
|
|
1349
|
+
if (!flags.yes && !flags.dryRun) {
|
|
1350
|
+
const ok = await confirm("Proceed with release?");
|
|
1351
|
+
if (!ok) {
|
|
1352
|
+
console.log(" Aborted.\n");
|
|
1353
|
+
return;
|
|
1104
1354
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1355
|
+
}
|
|
1356
|
+
await bumpVersions(changes, bumpMode, flags.dryRun);
|
|
1357
|
+
log("info", "Tagging repos and triggering CI...");
|
|
1358
|
+
await tagAndTriggerCI(changes, flags.dryRun);
|
|
1359
|
+
const ciStatuses = await waitForCI(changes, flags.dryRun);
|
|
1360
|
+
const ciFailures = ciStatuses.filter(
|
|
1361
|
+
(s) => s.status !== "completed" || s.conclusion !== "success"
|
|
1362
|
+
);
|
|
1363
|
+
if (ciFailures.length > 0 && !flags.dryRun) {
|
|
1364
|
+
await rollbackFailedReleases(changes, ciStatuses, flags.dryRun);
|
|
1365
|
+
const ciSuccesses = ciStatuses.filter(
|
|
1366
|
+
(s) => s.status === "completed" && s.conclusion === "success"
|
|
1367
|
+
);
|
|
1368
|
+
if (ciSuccesses.length === 0) {
|
|
1369
|
+
log("fail", "All CI builds failed. Release aborted, all bumps rolled back.");
|
|
1370
|
+
throw new Error("All CI builds failed. Release aborted, all bumps rolled back.");
|
|
1108
1371
|
}
|
|
1372
|
+
log("warn", `${ciFailures.length} CI build(s) failed and were rolled back. Continuing with ${ciSuccesses.length} successful build(s).`);
|
|
1109
1373
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
const
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1374
|
+
let imageResults = [];
|
|
1375
|
+
if (flags.skipImageVerify) {
|
|
1376
|
+
log("warn", "Image verification skipped (--skip-image-verify)");
|
|
1377
|
+
} else {
|
|
1378
|
+
imageResults = await verifyImages(changes, ciStatuses, flags.dryRun);
|
|
1379
|
+
const failed = imageResults.filter((r) => (r.ghcr === "missing" || r.ghcr === "error") && r.proxy !== "ok");
|
|
1380
|
+
if (failed.length > 0 && !flags.dryRun) {
|
|
1381
|
+
for (const m of failed) {
|
|
1382
|
+
log("fail", `${shortImageName(m.repo.imageName)}:${m.tag} \u2014 GHCR status: ${m.ghcr}, proxy: ${m.proxy} \u2014 cannot release`);
|
|
1383
|
+
}
|
|
1384
|
+
log("fail", "Aborting: images not verified. Fix GHCR auth/CI or use --skip-image-verify to override.");
|
|
1385
|
+
throw new Error("Aborting: images not verified. Fix GHCR auth/CI or use --skip-image-verify to override.");
|
|
1386
|
+
}
|
|
1387
|
+
const pending = imageResults.filter((r) => r.proxy === "pending");
|
|
1388
|
+
if (pending.length > 0) {
|
|
1389
|
+
for (const p of pending) {
|
|
1390
|
+
log("warn", `${shortImageName(p.repo.imageName)}:${p.tag} \u2014 proxy cache pending`);
|
|
1391
|
+
}
|
|
1392
|
+
if (!flags.dryRun) {
|
|
1393
|
+
log("fail", "Aborting: proxy cache not ready for all images. Wait for proxy propagation or use --skip-image-verify.");
|
|
1394
|
+
throw new Error("Aborting: proxy cache not ready for all images. Wait for proxy propagation or use --skip-image-verify.");
|
|
1129
1395
|
}
|
|
1130
1396
|
}
|
|
1131
1397
|
printImageVerification(imageResults);
|
|
1132
1398
|
}
|
|
1399
|
+
log("info", "Updating stack manifest...");
|
|
1400
|
+
const newStackVersion = await updateStackManifest(changes, ciStatuses, flags.dryRun, imageResults, flags.canary);
|
|
1401
|
+
gatePublishWithPreflight(newStackVersion, flags.dryRun, flags.allowRed);
|
|
1402
|
+
const exeOsChange = changes.find((c) => c.repo.serviceKey === "exe-os");
|
|
1403
|
+
await commitAndTagExeOs(newStackVersion, exeOsChange, flags.dryRun);
|
|
1404
|
+
printSummary(changes, ciStatuses, newStackVersion, flags.dryRun);
|
|
1405
|
+
if (!flags.skipImageVerify && !flags.dryRun && imageResults.length > 0) {
|
|
1406
|
+
const pendingImages = imageResults.filter((r) => r.proxy === "pending");
|
|
1407
|
+
if (pendingImages.length > 0) {
|
|
1408
|
+
log("info", "Re-checking proxy availability for pending images...");
|
|
1409
|
+
for (const r of pendingImages) {
|
|
1410
|
+
const shortName = shortImageName(r.repo.imageName);
|
|
1411
|
+
const proxyRepo = `askexe/${shortName}`;
|
|
1412
|
+
const proxyToken = process.env.EXE_REGISTRY_PROXY_PULL_TOKEN ?? void 0;
|
|
1413
|
+
const recheck = await checkRegistryManifest(PROXY_REGISTRY, proxyRepo, r.tag, proxyToken);
|
|
1414
|
+
if (recheck.ok) {
|
|
1415
|
+
r.proxy = "ok";
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
printImageVerification(imageResults);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
} finally {
|
|
1422
|
+
releaseLock?.release();
|
|
1423
|
+
if (releaseLock) log("ok", `Released release lock: ${releaseLock.lockPath}`);
|
|
1133
1424
|
}
|
|
1134
1425
|
}
|
|
1135
1426
|
export {
|
|
1136
1427
|
assertCleanWorkingTree,
|
|
1428
|
+
assertReleaseFreshness,
|
|
1137
1429
|
deriveCloneUrl,
|
|
1138
1430
|
ensureRepoCloned,
|
|
1431
|
+
findPullRequestForHeadSha,
|
|
1432
|
+
findWorkflowRunForHeadSha,
|
|
1139
1433
|
isCloneTargetSafe,
|
|
1140
1434
|
parseReleaseArgs,
|
|
1435
|
+
printOpenPrGate,
|
|
1141
1436
|
runStackRelease
|
|
1142
1437
|
};
|