@rubytech/create-maxy-code 0.1.478 → 0.1.480
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/package.json +1 -1
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1789-reseat-channel-row-fork.md +1244 -0
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1818-loop-gate-app-routes.md +462 -0
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1819-top-level-label-allowlist.md +321 -0
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1829-platform-ui-typecheck.md +830 -0
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1831-data-portal-class-derived-allowlist.md +2177 -0
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1789-reseat-channel-row-fork-design.md +201 -0
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1818-loop-gate-app-routes-design.md +217 -0
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1819-top-level-label-allowlist-design.md +80 -0
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1829-platform-ui-typecheck-design.md +163 -0
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1831-data-portal-class-derived-allowlist-design.md +163 -0
- package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-style/dist/index.js +22 -0
- package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-style/src/__tests__/parity.test.ts +6 -0
- package/payload/platform/lib/graph-style/src/index.ts +20 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js +155 -12
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts +3 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.js +68 -28
- package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
- package/payload/platform/lib/storage-broker/src/__tests__/cf-exec.test.ts +194 -16
- package/payload/platform/lib/storage-broker/src/cf-exec.ts +74 -29
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +13 -1
- package/payload/platform/plugins/cloudflare/PLUGIN.md +26 -1
- package/payload/platform/plugins/cloudflare/bin/d1-http.mjs +80 -0
- package/payload/platform/plugins/cloudflare/bin/portal-enrol.mjs +17 -2
- package/payload/platform/plugins/cloudflare/bin/portal-index-push.mjs +357 -0
- package/payload/platform/plugins/cloudflare/bin/schema-exposed-dirs.mjs +103 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/authorize.test.ts +2 -2
- package/payload/platform/plugins/cloudflare/mcp/__tests__/d1-http.test.ts +96 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-directory-listing.test.ts +120 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-enrol.test.ts +59 -4
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-index-push.test.ts +176 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-index-state-contract.test.ts +75 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-indexed-download.test.ts +131 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-sign.test.ts +68 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/schema-exposed-dirs.test.ts +88 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/template-config.test.ts +39 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +68 -1
- package/payload/platform/plugins/cloudflare/skills/data-portal/schema.sql +30 -1
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/portal-sign.mjs +87 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/session.ts +8 -4
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/types.ts +10 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/download.ts +107 -1
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/files.ts +82 -2
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/index.html +9 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.css +56 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.js +98 -2
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/wrangler.toml +12 -0
- package/payload/platform/plugins/docs/references/cloudflare.md +12 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js +41 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js +249 -2
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js +127 -3
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts +17 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js +26 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts +37 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js +126 -5
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts +18 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js +123 -18
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js.map +1 -1
- package/payload/platform/plugins/outlook/references/auth.md +45 -2
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.js +66 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.d.ts +41 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.js +75 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +122 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
- package/payload/server/{chunk-3XLLTG6R.js → chunk-REWXLAPC.js} +30 -10
- package/payload/server/package.json +1 -1
- package/payload/server/public/activity.html +7 -6
- package/payload/server/public/assets/{AdminLoginScreens-DR5eLF-a.js → AdminLoginScreens-DXYJWXaX.js} +1 -1
- package/payload/server/public/assets/AdminShell-UDgNN4CA.js +2 -0
- package/payload/server/public/assets/Checkbox-C08Ulc_a.js +1 -0
- package/payload/server/public/assets/activity-06gsRDVv.js +1 -0
- package/payload/server/public/assets/admin-BhiHyORH.js +1 -0
- package/payload/server/public/assets/{arc-BrHWN2Kr.js → arc-BHXuzTwg.js} +1 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-Ct5q6ks1.js +1 -0
- package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-Clt9NOr0.js → architectureDiagram-Q4EWVU46-DsN_vxau.js} +1 -1
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-BOe_jmXi.js → blockDiagram-DXYQGD6D-CvNat1uB.js} +1 -1
- package/payload/server/public/assets/browser-Cz4IKS47.js +1 -0
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-B64FaKMV.js → c4Diagram-AHTNJAMY-E9yqTPTW.js} +1 -1
- package/payload/server/public/assets/calendar-D0xtNqH8.js +1 -0
- package/payload/server/public/assets/channel-PE9vBKEQ.js +1 -0
- package/payload/server/public/assets/chat-DwLJ830n.js +1 -0
- package/payload/server/public/assets/chevron-left-Cv-TFuEf.js +1 -0
- package/payload/server/public/assets/{chunk-2KRD3SAO-BGAUfWvM.js → chunk-2KRD3SAO-DpzI_0EY.js} +1 -1
- package/payload/server/public/assets/{chunk-336JU56O-PFi6uQZ0.js → chunk-336JU56O-eR_I9Eu4.js} +2 -2
- package/payload/server/public/assets/chunk-426QAEUC-BDNZtJsz.js +1 -0
- package/payload/server/public/assets/{chunk-4BX2VUAB-CkxXXCiF.js → chunk-4BX2VUAB-D-6xk1t8.js} +1 -1
- package/payload/server/public/assets/{chunk-4TB4RGXK-B-7tN4IA.js → chunk-4TB4RGXK-BkqUcFpL.js} +1 -1
- package/payload/server/public/assets/{chunk-55IACEB6-CBkNmqrt.js → chunk-55IACEB6-ChCl2pSP.js} +1 -1
- package/payload/server/public/assets/{chunk-5FUZZQ4R-wa9ujn7i.js → chunk-5FUZZQ4R-DasGsg54.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-DIk-xBaE.js → chunk-5PVQY5BW-DXlHUX1h.js} +1 -1
- package/payload/server/public/assets/{chunk-67CJDMHE-BbMV1lgW.js → chunk-67CJDMHE-BZfVpTsE.js} +1 -1
- package/payload/server/public/assets/{chunk-7N4EOEYR-C49Mv-nQ.js → chunk-7N4EOEYR-C6U5TIgT.js} +1 -1
- package/payload/server/public/assets/{chunk-AA7GKIK3-Dy_pJOp_.js → chunk-AA7GKIK3-CA7cVDmw.js} +1 -1
- package/payload/server/public/assets/{chunk-BSJP7CBP-Ce-OmPqn.js → chunk-BSJP7CBP-DxN2GC3p.js} +1 -1
- package/payload/server/public/assets/{chunk-CIAEETIT-tnjq-APC.js → chunk-CIAEETIT-C0-4wA8y.js} +1 -1
- package/payload/server/public/assets/{chunk-EDXVE4YY-Bvv_deZ2.js → chunk-EDXVE4YY--kyb1rLc.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-CmxCPKyU.js → chunk-ENJZ2VHE-FBN6hR7V.js} +1 -1
- package/payload/server/public/assets/{chunk-FMBD7UC4-Top6y08S.js → chunk-FMBD7UC4-Lpge4M5g.js} +1 -1
- package/payload/server/public/assets/{chunk-FOC6F5B3-B9LPNCUS.js → chunk-FOC6F5B3-CM1rwOou.js} +1 -1
- package/payload/server/public/assets/{chunk-ICPOFSXX-Bi-_c351.js → chunk-ICPOFSXX-JdYLY3yu.js} +2 -2
- package/payload/server/public/assets/{chunk-K5T4RW27-DAnRM04h.js → chunk-K5T4RW27-DCUblGBr.js} +1 -1
- package/payload/server/public/assets/{chunk-KGLVRYIC-C0QYcbLe.js → chunk-KGLVRYIC-BP_cide2.js} +1 -1
- package/payload/server/public/assets/{chunk-LIHQZDEY-BjvA5ykt.js → chunk-LIHQZDEY-DjMa1lOl.js} +1 -1
- package/payload/server/public/assets/{chunk-ORNJ4GCN-Cs46Gj7z.js → chunk-ORNJ4GCN-D51eJAoV.js} +1 -1
- package/payload/server/public/assets/{chunk-OYMX7WX6-BBpimab5.js → chunk-OYMX7WX6-4hWIO544.js} +1 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-B_Hris8x.js +1 -0
- package/payload/server/public/assets/{chunk-U2HBQHQK-CQUSBHoK.js → chunk-U2HBQHQK-BwdQf9cS.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP-Dm8TevG0.js → chunk-X2U36JSP-DmDxwLOg.js} +1 -1
- package/payload/server/public/assets/{chunk-XPW4576I-B10QuxYb.js → chunk-XPW4576I-JQyAB7ND.js} +1 -1
- package/payload/server/public/assets/{chunk-YZCP3GAM-vaGsu4Wk.js → chunk-YZCP3GAM-BonLdCzI.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-D1Qi22bL.js → chunk-ZZ45TVLE-BXKB2wSk.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-D8Q05iMI.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-Dm3hAxfm.js +1 -0
- package/payload/server/public/assets/clone-C-wOwvpY.js +1 -0
- package/payload/server/public/assets/{cose-bilkent-S5V4N54A-CRDvLRXs.js → cose-bilkent-S5V4N54A-BDnd4Blw.js} +1 -1
- package/payload/server/public/assets/{dagre-CEmqJTt3.js → dagre-BmexzuER.js} +1 -1
- package/payload/server/public/assets/{dagre-KV5264BT-LXMTo7Fp.js → dagre-KV5264BT-D4WVuwPj.js} +1 -1
- package/payload/server/public/assets/data-DUDJfnjI.js +1 -0
- package/payload/server/public/assets/{diagram-5BDNPKRD-zyis6nPt.js → diagram-5BDNPKRD-Bbf4mT8a.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-BiU0XAAD.js → diagram-G4DWMVQ6-BpqVTUmx.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-rGFiz1SX.js → diagram-MMDJMWI5-DY-k5k6r.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635-B0wak1ZJ.js → diagram-TYMM5635-CXHxwacS.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA-D8AUxLyX.js → erDiagram-SMLLAGMA-BNKEc3l8.js} +1 -1
- package/payload/server/public/assets/{flatten-naJ5PJ4f.js → flatten-jn8bZgcI.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-BtdUf68b.js → flowDiagram-DWJPFMVM-CbAeuJsi.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-C-lkzDmf.js → ganttDiagram-T4ZO3ILL-CeeNnL4l.js} +1 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-BBHsY16R.js +1 -0
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-DRUKumq2.js → gitGraphDiagram-UUTBAWPF-DG8RCtfg.js} +1 -1
- package/payload/server/public/assets/{graph-DRloNic4.js → graph-BAoktBcx.js} +19 -19
- package/payload/server/public/assets/graph-labels-LeIhl7ZG.js +1 -0
- package/payload/server/public/assets/{graphlib-zRsaoFEC.js → graphlib-BkDExPQi.js} +1 -1
- package/payload/server/public/assets/info-OMHHGYJF-C1j5I_zx.js +1 -0
- package/payload/server/public/assets/infoDiagram-42DDH7IO-ZWkwHWEu.js +2 -0
- package/payload/server/public/assets/{isEmpty-Bnpg7b8f.js → isEmpty-Dkk6_L-B.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-BguMgoR8.js → ishikawaDiagram-UXIWVN3A-D_iMHV64.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-CXhyK1n-.js → journeyDiagram-VCZTEJTY-BvPRpulx.js} +1 -1
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY-C0UCWVx-.js → kanban-definition-6JOO6SKY-CkWaXWo2.js} +1 -1
- package/payload/server/public/assets/{line--uhS9YDE.js → line-BGQT9unx.js} +1 -1
- package/payload/server/public/assets/{linear-CAOWPodQ.js → linear-DzrYI8-8.js} +1 -1
- package/payload/server/public/assets/{maximize-2-CzDaBugs.js → maximize-2-2MgPSwlx.js} +1 -1
- package/payload/server/public/assets/{mermaid-parser.core-D6kPx7vc.js → mermaid-parser.core-DrMY7FMe.js} +2 -2
- package/payload/server/public/assets/{mermaid.core-CTd4DS6O.js → mermaid.core-BWdf6fZ7.js} +3 -3
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-_AyvyG8C.js → mindmap-definition-QFDTVHPH-CgqM9h97.js} +1 -1
- package/payload/server/public/assets/operator-CZifmNhp.js +1 -0
- package/payload/server/public/assets/{ordinal-Wx4m9zHk.js → ordinal-DuJYM7p1.js} +1 -1
- package/payload/server/public/assets/packet-4T2RLAQJ-CGiTbe0N.js +1 -0
- package/payload/server/public/assets/{page-ef-6rRPL.js → page-B4kllGd_.js} +6 -6
- package/payload/server/public/assets/page-DbfE7QwJ.js +1 -0
- package/payload/server/public/assets/{pdf-render-CeX6d2v7.js → pdf-render-riHufgrn.js} +1 -1
- package/payload/server/public/assets/pie-ZZUOXDRM-BrK023dE.js +1 -0
- package/payload/server/public/assets/{pieDiagram-DEJITSTG-6TsUDkkQ.js → pieDiagram-DEJITSTG-BeUGoYS7.js} +1 -1
- package/payload/server/public/assets/public-VtmyYz8E.js +1 -0
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-CWw9gOI4.js → quadrantDiagram-34T5L4WZ-CX82WgKy.js} +1 -1
- package/payload/server/public/assets/radar-PYXPWWZC-p7NRamXX.js +1 -0
- package/payload/server/public/assets/{reduce-BUmc8RDR.js → reduce-COyotL2V.js} +1 -1
- package/payload/server/public/assets/{requirementDiagram-MS252O5E-DCOlxNvx.js → requirementDiagram-MS252O5E-CzqebQXj.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-DXEKUhXP.js → rotate-ccw-DaZdB2cz.js} +1 -1
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-CW4Xbir3.js → sankeyDiagram-XADWPNL6-CaHi1yzU.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-vhb_pBLG.js → sequenceDiagram-FGHM5R23-BF2AeiKW.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-Cb1WExEX.js → stateDiagram-FHFEXIEX-DtUCA8Ih.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-a4FvB3L8.js +1 -0
- package/payload/server/public/assets/tasks-CvqVDbfO.js +1 -0
- package/payload/server/public/assets/{time-entry-format-CsYAjplS.js → time-entry-format-TD-PPEIu.js} +1 -1
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-BAHTf_AF.js → timeline-definition-GMOUNBTQ-D3Q1YrKY.js} +1 -1
- package/payload/server/public/assets/treeView-SZITEDCU-CoWk8VoG.js +1 -0
- package/payload/server/public/assets/treemap-W4RFUUIX-BhWeGj8Y.js +1 -0
- package/payload/server/public/assets/{triangle-alert-0lSBIUg-.js → triangle-alert-BaLhVNnn.js} +1 -1
- package/payload/server/public/assets/useCopyFeedback-BhjPzRa3.js +1 -0
- package/payload/server/public/assets/useSubAccountSwitcher-CWRpVt1-.css +1 -0
- package/payload/server/public/assets/{useSubAccountSwitcher-BZnzc5C3.js → useSubAccountSwitcher-HEVLb6Z9.js} +2 -2
- package/payload/server/public/assets/useVoiceRecorder-CfGgj3vm.js +2 -0
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-DtEttvW4.js → vennDiagram-DHZGUBPP-DnjzQDGf.js} +1 -1
- package/payload/server/public/assets/wardley-RL74JXVD-C0q3Hqng.js +1 -0
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-K06y6CVJ.js → wardleyDiagram-NUSXRM2D-T3WHrUvi.js} +1 -1
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-D66c40lp.js → xychartDiagram-5P7HB3ND-2Efyv4h2.js} +1 -1
- package/payload/server/public/brand-defaults.css +1 -0
- package/payload/server/public/browser.html +4 -5
- package/payload/server/public/calendar.html +5 -6
- package/payload/server/public/chat.html +12 -13
- package/payload/server/public/data.html +11 -12
- package/payload/server/public/graph.html +10 -11
- package/payload/server/public/index.html +14 -15
- package/payload/server/public/operator.html +14 -15
- package/payload/server/public/privacy.html +1 -0
- package/payload/server/public/public.html +12 -13
- package/payload/server/public/tasks.html +7 -5
- package/payload/server/public/vnc-popout.html +1 -0
- package/payload/server/server.js +833 -389
- package/payload/server/{src-XQ63FPRE.js → src-JJ6PHO5I.js} +1 -1
- package/payload/server/public/assets/AdminShell-D9zaUxPd.js +0 -2
- package/payload/server/public/assets/Checkbox-DcbbAIe5.js +0 -1
- package/payload/server/public/assets/activity-C9P5NjEZ.js +0 -1
- package/payload/server/public/assets/admin-D2KLZnQW.js +0 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-CZQzGRSM.js +0 -1
- package/payload/server/public/assets/browser-CDWqEiCD.js +0 -1
- package/payload/server/public/assets/calendar-BG7fOpbB.js +0 -1
- package/payload/server/public/assets/channel-zDUAN9Ks.js +0 -1
- package/payload/server/public/assets/chat-RyH-WYQC.js +0 -1
- package/payload/server/public/assets/chevron-left-DmXCr6nB.js +0 -1
- package/payload/server/public/assets/chunk-426QAEUC-iJnqNRug.js +0 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-Qlc2PFAj.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-CFsKL52p.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BWBUUCiO.js +0 -1
- package/payload/server/public/assets/clone-h5XhkDUL.js +0 -1
- package/payload/server/public/assets/data-BaY_LkLR.js +0 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-CBZPQJLj.js +0 -1
- package/payload/server/public/assets/graph-labels-DD-GqS4v.js +0 -1
- package/payload/server/public/assets/info-OMHHGYJF-1vIoX-kP.js +0 -1
- package/payload/server/public/assets/infoDiagram-42DDH7IO-KHcXRaan.js +0 -2
- package/payload/server/public/assets/operator-BZeeyait.js +0 -1
- package/payload/server/public/assets/packet-4T2RLAQJ-CTU0gCGb.js +0 -1
- package/payload/server/public/assets/page-B0obcg3b.js +0 -1
- package/payload/server/public/assets/pie-ZZUOXDRM-D-EEHxhC.js +0 -1
- package/payload/server/public/assets/public-9D3MMOkC.js +0 -1
- package/payload/server/public/assets/radar-PYXPWWZC-DHXATZGB.js +0 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CxOIKvXh.js +0 -1
- package/payload/server/public/assets/tasks-BB0L7e5a.js +0 -1
- package/payload/server/public/assets/treeView-SZITEDCU-CC7ouMFd.js +0 -1
- package/payload/server/public/assets/treemap-W4RFUUIX-CaCg27im.js +0 -1
- package/payload/server/public/assets/useCopyFeedback-jTBQ2h0V.js +0 -1
- package/payload/server/public/assets/useSelectionMode-BooArySZ.js +0 -1
- package/payload/server/public/assets/useSubAccountSwitcher-D9knHOKq.css +0 -1
- package/payload/server/public/assets/useVoiceRecorder-PUT8q_sm.js +0 -2
- package/payload/server/public/assets/wardley-RL74JXVD-bMPsqQZ7.js +0 -1
- /package/payload/server/public/assets/{_baseFor-k8hYPe-U.js → _baseFor-Bjj9It3Q.js} +0 -0
- /package/payload/server/public/assets/{array-BFDiaBgf.js → array-BrrxEo12.js} +0 -0
- /package/payload/server/public/assets/{cytoscape.esm-Ch0xptA4.js → cytoscape.esm-CTWJxUxo.js} +0 -0
- /package/payload/server/public/assets/{defaultLocale-ZeknFqNe.js → defaultLocale-CFDbRV4k.js} +0 -0
- /package/payload/server/public/assets/{dist-B2N0v8hZ.js → dist-BATfy7Sn.js} +0 -0
- /package/payload/server/public/assets/{init-0VJVrkRJ.js → init-dXCzGgLP.js} +0 -0
- /package/payload/server/public/assets/{katex-Br7bH10N.js → katex-CHA1JFYJ.js} +0 -0
- /package/payload/server/public/assets/{path-INs8XTPH.js → path-CCRpdXhK.js} +0 -0
- /package/payload/server/public/assets/{preload-helper-D_sPnjeL.js → preload-helper-CH6UZRzu.js} +0 -0
- /package/payload/server/public/assets/{rough.esm-DU9bZLvZ.js → rough.esm-Bud4g4fh.js} +0 -0
- /package/payload/server/public/assets/{src-DHSQDYx-.js → src-DNdBRXXx.js} +0 -0
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
mkdtempSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
appendFileSync,
|
|
8
|
+
existsSync,
|
|
9
|
+
readdirSync,
|
|
10
|
+
readFileSync,
|
|
11
|
+
symlinkSync,
|
|
12
|
+
} from "node:fs";
|
|
4
13
|
import { tmpdir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
14
|
+
import { join, dirname } from "node:path";
|
|
6
15
|
import { makeCfExec, makeHouseCfExec, type RunFn, type FetchFn } from "../cf-exec.js";
|
|
7
16
|
|
|
8
17
|
const cred = { apiToken: "cfat_x", accountId: "acc-h" };
|
|
@@ -685,40 +694,208 @@ function projectPage(names: string[], cursor?: string) {
|
|
|
685
694
|
});
|
|
686
695
|
}
|
|
687
696
|
|
|
697
|
+
// A real site tree on disk. pagesDeploy copies the tree before uploading it, so
|
|
698
|
+
// these tests cannot hand it a path that does not exist.
|
|
699
|
+
function siteTree(files: Record<string, string>): string {
|
|
700
|
+
const dir = mkdtempSync(join(tmpdir(), "pages-site-"));
|
|
701
|
+
for (const [rel, body] of Object.entries(files)) {
|
|
702
|
+
const abs = join(dir, rel);
|
|
703
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
704
|
+
writeFileSync(abs, body);
|
|
705
|
+
}
|
|
706
|
+
return dir;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const PORTAL = {
|
|
710
|
+
"index.html": "<!doctype html>",
|
|
711
|
+
"portal.js": "export {}",
|
|
712
|
+
"functions/api/files.js": "export {}",
|
|
713
|
+
"wrangler.toml": 'name = "kit-data"\ndatabase_id = "secret-looking"\n',
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
/** Snapshots the uploaded directory's contents at call time, because
|
|
717
|
+
* pagesDeploy deletes the staging tree before it returns. */
|
|
718
|
+
function snapshottingRun(stdout: string) {
|
|
719
|
+
const calls: Array<{
|
|
720
|
+
args: string[];
|
|
721
|
+
cwd?: string;
|
|
722
|
+
uploadDir: string;
|
|
723
|
+
uploaded: string[];
|
|
724
|
+
}> = [];
|
|
725
|
+
const run: RunFn = async (_cmd, args, _env, opts) => {
|
|
726
|
+
const uploadDir = args[3];
|
|
727
|
+
const uploaded: string[] = [];
|
|
728
|
+
const walk = (d: string, prefix: string) => {
|
|
729
|
+
for (const entry of readdirSync(d, { withFileTypes: true })) {
|
|
730
|
+
if (entry.isDirectory()) walk(join(d, entry.name), `${prefix}${entry.name}/`);
|
|
731
|
+
else uploaded.push(`${prefix}${entry.name}`);
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
walk(uploadDir, "");
|
|
735
|
+
calls.push({ args, cwd: opts?.cwd, uploadDir, uploaded: uploaded.sort() });
|
|
736
|
+
return { stdout };
|
|
737
|
+
};
|
|
738
|
+
return { run, calls };
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const DEPLOYED = "✨ Deployment complete! Take a peek over at https://abc123.glsmithandsons.pages.dev\n";
|
|
742
|
+
|
|
688
743
|
test("pagesDeploy shells npx wrangler (never bare) and returns the deployment url", async () => {
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
);
|
|
744
|
+
const dir = siteTree(PORTAL);
|
|
745
|
+
const { run, calls } = recordingRun(`Uploading... (2/2)\n${DEPLOYED}`);
|
|
692
746
|
const cf = makeCfExec(cred, run);
|
|
693
|
-
const { url } = await cf.pagesDeploy(
|
|
747
|
+
const { url } = await cf.pagesDeploy(dir, "glsmithandsons", "main");
|
|
694
748
|
assert.equal(url, "https://abc123.glsmithandsons.pages.dev");
|
|
695
749
|
assert.equal(calls[0].cmd, "npx");
|
|
696
|
-
assert.deepEqual(calls[0].args, [
|
|
697
|
-
|
|
750
|
+
assert.deepEqual(calls[0].args.slice(0, 3), ["wrangler", "pages", "deploy"]);
|
|
751
|
+
assert.deepEqual(calls[0].args.slice(4), [
|
|
698
752
|
"--project-name", "glsmithandsons", "--branch", "main",
|
|
699
753
|
]);
|
|
700
754
|
assert.equal(calls[0].env.CLOUDFLARE_API_TOKEN, "cfat_x");
|
|
701
755
|
});
|
|
702
756
|
|
|
703
757
|
test("pagesDeploy omits --branch when no branch is given", async () => {
|
|
704
|
-
const
|
|
758
|
+
const dir = siteTree(PORTAL);
|
|
759
|
+
const { run, calls } = recordingRun(DEPLOYED);
|
|
705
760
|
const cf = makeCfExec(cred, run);
|
|
706
|
-
await cf.pagesDeploy(
|
|
707
|
-
assert.deepEqual(calls[0].args, [
|
|
708
|
-
"wrangler", "pages", "deploy", "/acct/sites/gls", "--project-name", "glsmithandsons",
|
|
709
|
-
]);
|
|
761
|
+
await cf.pagesDeploy(dir, "glsmithandsons");
|
|
762
|
+
assert.deepEqual(calls[0].args.slice(4), ["--project-name", "glsmithandsons"]);
|
|
710
763
|
});
|
|
711
764
|
|
|
712
765
|
test("pagesDeploy throws with the body when wrangler prints no deployment url", async () => {
|
|
766
|
+
const dir = siteTree(PORTAL);
|
|
713
767
|
const { run } = recordingRun("Uploading... (0/0)\nsomething went sideways\n");
|
|
714
768
|
const cf = makeCfExec(cred, run);
|
|
715
769
|
await assert.rejects(
|
|
716
|
-
() => cf.pagesDeploy(
|
|
770
|
+
() => cf.pagesDeploy(dir, "glsmithandsons"),
|
|
717
771
|
/something went sideways/,
|
|
718
772
|
"a url-less wrangler run must surface its output, never report a silent success",
|
|
719
773
|
);
|
|
720
774
|
});
|
|
721
775
|
|
|
776
|
+
// --- Task 1782: the uploaded set is the broker's, not whatever the tree holds ---
|
|
777
|
+
|
|
778
|
+
test("pagesDeploy uploads a staged copy with the wrangler config left out", async () => {
|
|
779
|
+
const dir = siteTree(PORTAL);
|
|
780
|
+
const { run, calls } = snapshottingRun(DEPLOYED);
|
|
781
|
+
const cf = makeCfExec(cred, run);
|
|
782
|
+
const { excluded } = await cf.pagesDeploy(dir, "kit-data", "main");
|
|
783
|
+
assert.notEqual(
|
|
784
|
+
calls[0].uploadDir,
|
|
785
|
+
dir,
|
|
786
|
+
"the directory handed to wrangler must not be the tree that holds the config",
|
|
787
|
+
);
|
|
788
|
+
assert.deepEqual(
|
|
789
|
+
calls[0].uploaded,
|
|
790
|
+
["functions/api/files.js", "index.html", "portal.js"],
|
|
791
|
+
"every site file is published and the config is not",
|
|
792
|
+
);
|
|
793
|
+
assert.deepEqual(excluded, ["wrangler.toml"]);
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
test("pagesDeploy still runs wrangler in the tree that holds the config", async () => {
|
|
797
|
+
const dir = siteTree(PORTAL);
|
|
798
|
+
const { run, calls } = snapshottingRun(DEPLOYED);
|
|
799
|
+
const cf = makeCfExec(cred, run);
|
|
800
|
+
await cf.pagesDeploy(dir, "kit-data", "main");
|
|
801
|
+
// wrangler finds its config by walking up from the working directory, so the
|
|
802
|
+
// cwd is what keeps the D1 and R2 bindings applying once the file itself has
|
|
803
|
+
// left the upload. It is also still the writable scratch anchor Task 1770
|
|
804
|
+
// pinned it for.
|
|
805
|
+
assert.equal(calls[0].cwd, dir);
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
test("pagesDeploy excludes every config filename wrangler itself discovers", async () => {
|
|
809
|
+
const dir = siteTree({
|
|
810
|
+
"index.html": "<!doctype html>",
|
|
811
|
+
"wrangler.toml": "x",
|
|
812
|
+
"wrangler.json": "{}",
|
|
813
|
+
"wrangler.jsonc": "{}",
|
|
814
|
+
});
|
|
815
|
+
const { run, calls } = snapshottingRun(DEPLOYED);
|
|
816
|
+
const cf = makeCfExec(cred, run);
|
|
817
|
+
const { excluded } = await cf.pagesDeploy(dir, "kit-data");
|
|
818
|
+
assert.deepEqual(calls[0].uploaded, ["index.html"]);
|
|
819
|
+
assert.deepEqual(excluded, ["wrangler.json", "wrangler.jsonc", "wrangler.toml"]);
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
test("pagesDeploy keeps a nested file that merely shares the config's name", async () => {
|
|
823
|
+
const dir = siteTree({
|
|
824
|
+
"index.html": "<!doctype html>",
|
|
825
|
+
"wrangler.toml": "x",
|
|
826
|
+
"docs/wrangler.toml": "an example the site publishes on purpose",
|
|
827
|
+
});
|
|
828
|
+
const { run, calls } = snapshottingRun(DEPLOYED);
|
|
829
|
+
const cf = makeCfExec(cred, run);
|
|
830
|
+
const { excluded } = await cf.pagesDeploy(dir, "kit-data");
|
|
831
|
+
assert.deepEqual(calls[0].uploaded, ["docs/wrangler.toml", "index.html"]);
|
|
832
|
+
assert.deepEqual(excluded, ["wrangler.toml"], "only the config wrangler reads is dropped");
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
test("pagesDeploy reports nothing excluded for a tree that carries no config", async () => {
|
|
836
|
+
const dir = siteTree({ "index.html": "<!doctype html>" });
|
|
837
|
+
const { run, calls } = snapshottingRun(DEPLOYED);
|
|
838
|
+
const cf = makeCfExec(cred, run);
|
|
839
|
+
const { excluded } = await cf.pagesDeploy(dir, "glsmithandsons");
|
|
840
|
+
assert.deepEqual(calls[0].uploaded, ["index.html"]);
|
|
841
|
+
assert.deepEqual(excluded, []);
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
test("pagesDeploy publishes what a symlink pointed at, including one leaving the tree", async () => {
|
|
845
|
+
// wrangler's own upload walk stats each entry, so deploying in place published
|
|
846
|
+
// the link's target. Copying links verbatim would strand an escaping one.
|
|
847
|
+
// A relative link escaping the tree is the shape that would dangle if the copy
|
|
848
|
+
// reproduced it verbatim somewhere else on disk. It does not: node's fs.cp
|
|
849
|
+
// rewrites every symlink to its resolved absolute target, so the staged link
|
|
850
|
+
// still points at the real file and wrangler's stat-following walk publishes
|
|
851
|
+
// it exactly as an in-place deploy did. This pins that behaviour rather than
|
|
852
|
+
// trusting it — the whole change rests on the staged tree being what wrangler
|
|
853
|
+
// would have uploaded.
|
|
854
|
+
const base = mkdtempSync(join(tmpdir(), "pages-base-"));
|
|
855
|
+
mkdirSync(join(base, "shared"));
|
|
856
|
+
writeFileSync(join(base, "shared", "shared.css"), "body{}");
|
|
857
|
+
const dir = join(base, "site");
|
|
858
|
+
mkdirSync(dir);
|
|
859
|
+
writeFileSync(join(dir, "index.html"), "<!doctype html>");
|
|
860
|
+
writeFileSync(join(dir, "wrangler.toml"), "x");
|
|
861
|
+
symlinkSync(join("..", "shared", "shared.css"), join(dir, "theme.css"));
|
|
862
|
+
// Read during the run: the staging tree is gone by the time pagesDeploy returns.
|
|
863
|
+
let staged = "";
|
|
864
|
+
const run: RunFn = async (_cmd, args) => {
|
|
865
|
+
staged = readFileSync(join(args[3], "theme.css"), "utf8");
|
|
866
|
+
return { stdout: DEPLOYED };
|
|
867
|
+
};
|
|
868
|
+
const cf = makeCfExec(cred, run);
|
|
869
|
+
await cf.pagesDeploy(dir, "kit-data");
|
|
870
|
+
assert.equal(
|
|
871
|
+
staged,
|
|
872
|
+
"body{}",
|
|
873
|
+
"the staged copy carries the target's bytes, not a link that resolves to nothing",
|
|
874
|
+
);
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
test("pagesDeploy removes the staging tree on the success path", async () => {
|
|
878
|
+
const dir = siteTree(PORTAL);
|
|
879
|
+
const { run, calls } = snapshottingRun(DEPLOYED);
|
|
880
|
+
const cf = makeCfExec(cred, run);
|
|
881
|
+
await cf.pagesDeploy(dir, "kit-data");
|
|
882
|
+
assert.equal(existsSync(calls[0].uploadDir), false);
|
|
883
|
+
assert.equal(existsSync(join(dir, "wrangler.toml")), true, "the real tree is untouched");
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
test("pagesDeploy removes the staging tree when wrangler fails", async () => {
|
|
887
|
+
const dir = siteTree(PORTAL);
|
|
888
|
+
let uploadDir = "";
|
|
889
|
+
const run: RunFn = async (_cmd, args) => {
|
|
890
|
+
uploadDir = args[3];
|
|
891
|
+
throw new Error("wrangler exploded");
|
|
892
|
+
};
|
|
893
|
+
const cf = makeCfExec(cred, run);
|
|
894
|
+
await assert.rejects(() => cf.pagesDeploy(dir, "kit-data"), /wrangler exploded/);
|
|
895
|
+
assert.notEqual(uploadDir, "");
|
|
896
|
+
assert.equal(existsSync(uploadDir), false, "a failed deploy must not strand a copy of the site");
|
|
897
|
+
});
|
|
898
|
+
|
|
722
899
|
test("pagesProjectList follows the pagination cursor to exhaustion via the API, never wrangler", async () => {
|
|
723
900
|
const { fetchFn, calls } = sequenceFetch([
|
|
724
901
|
{ ok: true, status: 200, body: projectPage(["glsmithandsons"], "c1") },
|
|
@@ -804,14 +981,15 @@ test("pagesDeploy pins wrangler's working directory to the deploy dir", async ()
|
|
|
804
981
|
const { run, calls } = recordingRun(
|
|
805
982
|
"\u2728 Deployment complete! Take a peek over at https://abc.kit-data.pages.dev\n",
|
|
806
983
|
);
|
|
984
|
+
const dir = siteTree(PORTAL);
|
|
807
985
|
const cf = makeCfExec(cred, run);
|
|
808
|
-
await cf.pagesDeploy(
|
|
986
|
+
await cf.pagesDeploy(dir, "kit-data", "main");
|
|
809
987
|
// Unpinned, wrangler resolves .wrangler/tmp against a directory it infers --
|
|
810
988
|
// on a real install that is the immutable server tree, where the mkdir is
|
|
811
989
|
// EPERM and every deploy dies before uploading.
|
|
812
990
|
assert.equal(
|
|
813
991
|
calls[0].cwd,
|
|
814
|
-
|
|
992
|
+
dir,
|
|
815
993
|
"wrangler must run in the deploy dir so its scratch dir lands beside the tree it uploads",
|
|
816
994
|
);
|
|
817
995
|
});
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
// wrangler. `run` is injectable so the wiring is unit-testable without wrangler.
|
|
4
4
|
|
|
5
5
|
import { execFile } from "node:child_process";
|
|
6
|
-
import {
|
|
6
|
+
import { cp, mkdtemp, rm } from "node:fs/promises";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { join, relative } from "node:path";
|
|
7
9
|
import type { HouseCredential } from "./house-credential.js";
|
|
8
10
|
import { readHouseCredential } from "./house-credential.js";
|
|
9
11
|
import { resolveHouseScopedToken } from "./house-scoped-token.js";
|
|
@@ -60,9 +62,24 @@ export interface CfExec {
|
|
|
60
62
|
r2ObjectDelete(bucket: string, key: string): Promise<void>;
|
|
61
63
|
pagesProjectList(): Promise<{ name: string }[]>;
|
|
62
64
|
pagesProjectCreate(name: string, productionBranch: string): Promise<void>;
|
|
63
|
-
|
|
65
|
+
/** `excluded` names the files dropped from the upload set, so the caller can
|
|
66
|
+
* log what was published rather than assert it (Task 1782). */
|
|
67
|
+
pagesDeploy(
|
|
68
|
+
dir: string,
|
|
69
|
+
project: string,
|
|
70
|
+
branch?: string,
|
|
71
|
+
): Promise<{ url: string; excluded: string[] }>;
|
|
64
72
|
}
|
|
65
73
|
|
|
74
|
+
// The filenames `wrangler pages deploy` discovers as its own configuration
|
|
75
|
+
// (wrangler 4.112.0). Task 1782 — the upload walk has a fixed ignore list with
|
|
76
|
+
// no config hook, so a config sitting in the deployed tree is served publicly at
|
|
77
|
+
// /wrangler.toml, disclosing the D1 database id and R2 bucket name, both of
|
|
78
|
+
// which embed the account id. All three names are dropped rather than only the
|
|
79
|
+
// .toml the data-portal template ships, so a tree that later gains a .json
|
|
80
|
+
// config cannot quietly start republishing it.
|
|
81
|
+
const WRANGLER_CONFIG_NAMES = ["wrangler.json", "wrangler.jsonc", "wrangler.toml"];
|
|
82
|
+
|
|
66
83
|
// An object key carries `/` as a real path separator (the data portal's
|
|
67
84
|
// per-person prefixes), so it is encoded segment-wise rather than whole:
|
|
68
85
|
// separators stay separators, and spaces and specials are escaped. This
|
|
@@ -381,34 +398,62 @@ export function makeCfExec(
|
|
|
381
398
|
}
|
|
382
399
|
},
|
|
383
400
|
async pagesDeploy(dir, project, branch) {
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
//
|
|
391
|
-
//
|
|
392
|
-
//
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
401
|
+
// Task 1782 — publish a directory the broker composes, not whatever the
|
|
402
|
+
// account's tree happens to hold. wrangler takes the upload directory from
|
|
403
|
+
// the positional argument and finds its configuration by walking up from
|
|
404
|
+
// the working directory, which are two independent inputs; pointing them
|
|
405
|
+
// at different places is what removes the config from the published assets
|
|
406
|
+
// while leaving the D1 and R2 bindings it declares fully in force.
|
|
407
|
+
// Measured on the house account 2026-07-20: a brand-new project deployed
|
|
408
|
+
// with the config outside the upload received the same bindings and the
|
|
409
|
+
// same config hash as the control that uploaded it.
|
|
410
|
+
const staged = await mkdtemp(join(tmpdir(), "maxy-pages-upload-"));
|
|
411
|
+
const excluded: string[] = [];
|
|
412
|
+
try {
|
|
413
|
+
await cp(dir, staged, {
|
|
414
|
+
recursive: true,
|
|
415
|
+
// Only a config at the root of the tree is wrangler's own. A nested
|
|
416
|
+
// file of the same name is site content and stays published.
|
|
417
|
+
filter: (src) => {
|
|
418
|
+
const rel = relative(dir, src);
|
|
419
|
+
if (!WRANGLER_CONFIG_NAMES.includes(rel)) return true;
|
|
420
|
+
excluded.push(rel);
|
|
421
|
+
return false;
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
// wrangler is the only supported Pages upload path (it hashes the tree and
|
|
425
|
+
// uploads only what is missing); the API has no single-call equivalent.
|
|
426
|
+
const args = ["wrangler", "pages", "deploy", staged, "--project-name", project];
|
|
427
|
+
if (branch !== undefined) args.push("--branch", branch);
|
|
428
|
+
// Pin the working directory to the tree being published. Left to
|
|
429
|
+
// inference, wrangler anchors `.wrangler/tmp` at the install's `server/`
|
|
430
|
+
// directory (its nearest package.json/node_modules root), which the
|
|
431
|
+
// platform marks immutable — so the scratch mkdir returns EPERM and the
|
|
432
|
+
// deploy never reaches the upload. `dir` is the realpath-confined deploy
|
|
433
|
+
// directory the route resolved, and it is writable. Since 1782 it is also
|
|
434
|
+
// the only place wrangler can still find this site's config, which no
|
|
435
|
+
// longer travels with the assets.
|
|
436
|
+
const { stdout } = await run("npx", args, env, { cwd: dir });
|
|
437
|
+
// The deployment URL is what the caller relays, so parse it strictly.
|
|
438
|
+
// Strip ANSI first: wrangler colours its output when it thinks it has a
|
|
439
|
+
// terminal, and a colour reset adjacent to the URL would otherwise be
|
|
440
|
+
// swallowed by the host character class and returned as part of it.
|
|
441
|
+
const plain = stdout.replace(/\[[0-9;]*m/g, "");
|
|
442
|
+
// Take the LAST match, not the first: wrangler prints the deployment URL
|
|
443
|
+
// on its closing line, and any earlier .pages.dev mention (an alias or a
|
|
444
|
+
// progress line) would otherwise win and hand back the wrong address.
|
|
445
|
+
const urls = plain.match(/https:\/\/[A-Za-z0-9._-]+\.pages\.dev/g);
|
|
446
|
+
const url = urls?.[urls.length - 1];
|
|
447
|
+
// wrangler exits 0 on some partial paths, so a url-less success is an
|
|
448
|
+
// anomaly to surface with its output, never a silent "deployed".
|
|
449
|
+
if (!url) {
|
|
450
|
+
throw new Error(`storage-broker: pages deploy returned no deployment url: ${stdout}`);
|
|
451
|
+
}
|
|
452
|
+
return { url, excluded: excluded.sort() };
|
|
453
|
+
} finally {
|
|
454
|
+
// A failed deploy must not strand a second copy of the site on disk.
|
|
455
|
+
await rm(staged, { recursive: true, force: true });
|
|
410
456
|
}
|
|
411
|
-
return { url };
|
|
412
457
|
},
|
|
413
458
|
|
|
414
459
|
// --- R2 objects (Task 1691) -------------------------------------------
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: platform-architecture
|
|
3
3
|
description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
|
|
4
|
-
content-hash: sha256:
|
|
4
|
+
content-hash: sha256:c7a28352289a7121b08a8d8f2d5bcb1e79497a6acda2e355756cf7b02051c3a5
|
|
5
5
|
brand: maxy-code
|
|
6
6
|
product-name: Maxy
|
|
7
7
|
---
|
|
@@ -1285,6 +1285,18 @@ The most common cause is wrong nameservers on the domain. The domain must use Cl
|
|
|
1285
1285
|
- The `/data` page handles this itself: files over 95 MB are split into ~48 MB parts and sent one at a time under the cap, and the server reassembles them. No plan change or dashboard action is needed for uploads up to the 2 GB app ceiling.
|
|
1286
1286
|
- Raising the proxied body limit is a paid-plan change made on the Cloudflare dashboard, not something the agent does.
|
|
1287
1287
|
|
|
1288
|
+
## The client file portal
|
|
1289
|
+
|
|
1290
|
+
A portal gives named people their own passcode-protected page on a custom domain. Two things sit on it.
|
|
1291
|
+
|
|
1292
|
+
**What they send you.** Anything they upload lands in the account's own storage, one area per person, and nobody sees anyone else's files.
|
|
1293
|
+
|
|
1294
|
+
**What you have prepared for them.** The same page shows the account's deliverable folders — the work produced for that client. Which folders those are is not a setting to maintain: it follows the account's own filing structure, so a business whose work is organised into quotes sees quotes, and a business organised some other way sees that instead. Operator working data, the client's own uploads, and anything internal are never shown.
|
|
1295
|
+
|
|
1296
|
+
Folder names and sizes are published to the portal on a schedule, so the client can browse even while the device is off. The files themselves stay on the device. Opening one checks the device is reachable and then hands the browser a link that expires after a few minutes; if the device is down the client is told exactly that, rather than getting a broken download.
|
|
1297
|
+
|
|
1298
|
+
Each person is bound to one account when they are enrolled. Someone enrolled before this existed has no account bound and sees no folders until they are enrolled again.
|
|
1299
|
+
|
|
1288
1300
|
## What the agent does and does not do
|
|
1289
1301
|
|
|
1290
1302
|
**Does:** invokes `cloudflared` directly via Bash, following `plugins/cloudflare/references/manual-setup.md` step by step; quotes click-paths from the reference files verbatim; verifies external reachability with `curl -I` and surfaces the response.
|
|
@@ -34,7 +34,7 @@ The plugin registers no agent-facing MCP tools. Every Cloudflare operation is th
|
|
|
34
34
|
| [cloudflare/SKILL.md](skills/cloudflare/SKILL.md) | The entry point for every Cloudflare operation. Routes each operation class to its reference, names the outcome contracts (tunnel external HTTP 200; hosting deployed URL; D1 round-trip), and states the tool-discipline + secret-redaction rules that bind the agent. |
|
|
35
35
|
| [site-deploy/SKILL.md](skills/site-deploy/SKILL.md) | Assemble one canonical content tree into a live Pages deployment on its custom domain that survives the install device being offline. Names the single-source-tree convention, the reused per-scope token, the form-to-D1 trigger, the R2 binding trigger, Open Graph correctness, and the cache-busted live-`200` done-gate; defers the command blocks to `hosting-sites.md`, `d1-data-capture.md` and `r2-object-storage.md`. Admin-run; a content specialist hands the deploy off to it. |
|
|
36
36
|
| [calendar-site/SKILL.md](skills/calendar-site/SKILL.md) | Assemble and deploy one account's public booking page — a static page plus edge Functions over a D1 `bookings` table — on its own custom domain, so a submission survives the install device being offline. Admin-run; defers the deploy and the live done-gate to `site-deploy`. |
|
|
37
|
-
| [data-portal/SKILL.md](skills/data-portal/SKILL.md) | Stand up one account's gated file-drop portal on its own custom domain: each enrolled person signs in with their own passcode and can upload, list, download and delete only their own files, into one R2 bucket under a per-person prefix with a D1 manifest row per upload. The bucket is created by the owning account's own session — the broker registers ownership to the creator and never reassigns it — while the admin assembles and deploys. Admin-run; defers the deploy and the live done-gate to `site-deploy`. |
|
|
37
|
+
| [data-portal/SKILL.md](skills/data-portal/SKILL.md) | Stand up one account's gated file-drop portal on its own custom domain: each enrolled person signs in with their own passcode and can upload, list, download and delete only their own files, into one R2 bucket under a per-person prefix with a D1 manifest row per upload. The same portal also shows that sub-account's deliverable folders, published from the device as metadata only and opened through a short-lived signed link. The bucket is created by the owning account's own session — the broker registers ownership to the creator and never reassigns it — while the admin assembles and deploys. Admin-run; defers the deploy and the live done-gate to `site-deploy`. |
|
|
38
38
|
|
|
39
39
|
### References
|
|
40
40
|
|
|
@@ -69,6 +69,31 @@ The setup-done claim only fires when `curl -I https://<hostname>` issued from ou
|
|
|
69
69
|
- **Cloudflare account identity** — `cert.pem` from OAuth. One account per brand per device.
|
|
70
70
|
- **Account binding drift** — `~/{configDir}/cloudflared/account-binding.json` is a historical drift marker. Reset by `rm -rf ~/.${BRAND}/cloudflared/` per `references/reset-guide.md` when switching accounts.
|
|
71
71
|
|
|
72
|
+
## Data-portal folder index
|
|
73
|
+
|
|
74
|
+
The portal shows a sub-account's deliverable folders alongside what the client uploaded. Which folders those are is never a list in the code: it is derived per account from that account's own `SCHEMA.md` — the buckets in its `<!-- ontology-buckets -->` region plus `output`, intersected with its `allowed-top-level` block. A brand on a different vertical exposes different folders with no code change. `bin/schema-exposed-dirs.mjs` is the only place that decision is made.
|
|
75
|
+
|
|
76
|
+
`bin/portal-index-push.mjs` walks those folders and full-replaces the account's rows in the portal's D1 `directory` table each cycle. Metadata only — names, sizes, times — so listing survives the device being offline, and a deleted or renamed file converges on the next cycle without a tombstone. The scheduling heartbeat runs it hourly for every account carrying a `data-portal.json`; it is also runnable standalone, which is how it stays operable while the heartbeat spawn path is unproven on a device.
|
|
77
|
+
|
|
78
|
+
Opening a file reaches the install: the portal probes it, then mints an `/api/portal/fetch` link signed with the account's `PORTAL_FETCH_SECRET` and valid for five minutes. The install verifies the signature **and independently re-derives the exposed folder set**, so a correctly signed link for a non-exposed path is still refused. That second check is the security boundary, not the signature.
|
|
79
|
+
|
|
80
|
+
What to watch, on the device:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
[portal-index] op=resolve account=<id> schemaPresent=<bool> allowed=<n> domainBuckets=<dirs|none> exposed=<dirs|none>
|
|
84
|
+
[portal-index] op=replace account=<id> deleted=all inserted=<n>
|
|
85
|
+
[portal-index] op=verify account=<id> rowsAfter=<n> expected=<n>
|
|
86
|
+
[portal-index] op=collision account=<id> dir=<dir> claimedBy=plugin-owned
|
|
87
|
+
[portal-index] op=exposed-changed account=<id> added=<dirs|none> removed=<dirs|none>
|
|
88
|
+
[portal-index] op=audit accounts=<n> stale=<n> never-ran=<n>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`exposed=none` where deliverables exist means the schema was missing or carried no ontology region — not that the walk found nothing. `rowsAfter` disagreeing with `expected` is a half-applied replace. An `op=collision` line explains a folder that has silently vanished from the client's view, which nothing else would report. `op=exposed-changed` fires only on a delta, so it is rare and always worth reading; a `removed=` naming a populated bucket is the client-visible regression.
|
|
92
|
+
|
|
93
|
+
`op=audit` is the standing check, and it is the only one that speaks when the push does **not**. It runs in-process inside the heartbeat rather than as a spawn, and reads only local state, so it cannot be blinded by the same failure it is watching for. It fires hourly even when everything is healthy — an audit that goes quiet on success is indistinguishable from an audit that stopped running. `result=never-ran` on an account with a `data-portal.json` means no cycle has ever succeeded for it; `result=stale` with `ageMin` means cycles have stopped. Its input is written only after `op=verify`, so a stale entry means the push stopped succeeding rather than merely stopped logging.
|
|
94
|
+
|
|
95
|
+
On the portal side `[data-portal] op=list` carries `account=` and `op=fetch-through` carries the outcome; on the install `[portal-fetch]` logs `op=request` then exactly one of `op=served`, `op=denied`, `op=miss`. A portal `fetch-through` with no matching install `op=request` means the request never arrived, rather than having been refused. `[portal-fetch] op=no-secret account=<id>` is an unconfigured portal, which the `op=denied reason=secret` line alone would not distinguish from a bad signature.
|
|
96
|
+
|
|
72
97
|
## Discipline
|
|
73
98
|
|
|
74
99
|
The agent's permitted surfaces are: direct `cloudflared` and `wrangler` invocations via Bash following the references; Cloudflare API calls authenticated by a reused per-scope narrow token, minted once if absent and persisted to the secrets file (the master token stays in that same file, never on a command line that echoes, never in chat); the reference files; and live verification (`curl -I` for tunnels, the deployed URL for hosting, a `SELECT` for D1). Out of bounds: Playwright or Chrome DevTools driving the dashboard, browser-automating master-token creation, WebSearch-for-CF-recipes, ad-hoc `cloudflared` flag invention not in the runbook, and writing or echoing any token. When a step fails, the agent reports the exact output (secrets redacted), cites the recovery step from `references/reset-guide.md`, and stops.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// D1 over the Cloudflare HTTP API, with bound parameters.
|
|
2
|
+
//
|
|
3
|
+
// Not `wrangler d1 execute`: that spawns a process per statement, which is the
|
|
4
|
+
// CPU-burn defect Task 1805 fixed, and it takes a raw SQL string, which would
|
|
5
|
+
// mean interpolating filenames into SQL. The index push writes one row per file,
|
|
6
|
+
// so both matter here.
|
|
7
|
+
//
|
|
8
|
+
// storage-broker's cf-exec.d1Query still spawns wrangler, and the scheduling
|
|
9
|
+
// plugin has its own HTTP client. Converging the three is a follow-up; this
|
|
10
|
+
// client depends on neither, because the cloudflare plugin is where Cloudflare
|
|
11
|
+
// access belongs and importing the scheduling plugin from here would invert that.
|
|
12
|
+
|
|
13
|
+
const API = 'https://api.cloudflare.com/client/v4'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Always carries the response body into the message. A bare status renders
|
|
17
|
+
* every failure identical and unactionable.
|
|
18
|
+
* @param {unknown} parsed @param {number} status @param {string} label
|
|
19
|
+
* @returns {never}
|
|
20
|
+
*/
|
|
21
|
+
function fail(parsed, status, label) {
|
|
22
|
+
const errs = /** @type {{errors?: {message?: string}[]}} */ (parsed)?.errors
|
|
23
|
+
const detail = Array.isArray(errs) && errs.length ? errs.map((e) => e?.message).join('; ') : ''
|
|
24
|
+
throw new Error(`${label} failed: HTTP ${status}${detail ? ` — ${detail}` : ''}`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {{ accountId: string, token: string, dbName: string,
|
|
29
|
+
* fetchFn?: (url: string, init: object) => Promise<{ ok: boolean, status: number, json: () => Promise<unknown> }> }} opts
|
|
30
|
+
*/
|
|
31
|
+
export function createD1Client(opts) {
|
|
32
|
+
const fetchFn = opts.fetchFn ?? /** @type {never} */ (globalThis.fetch)
|
|
33
|
+
const headers = { Authorization: `Bearer ${opts.token}`, 'Content-Type': 'application/json' }
|
|
34
|
+
/** @type {string | undefined} */ let databaseId
|
|
35
|
+
let statements = 0
|
|
36
|
+
|
|
37
|
+
async function resolveId() {
|
|
38
|
+
if (databaseId !== undefined) return databaseId
|
|
39
|
+
const url = `${API}/accounts/${opts.accountId}/d1/database?name=${encodeURIComponent(opts.dbName)}`
|
|
40
|
+
const res = await fetchFn(url, { method: 'GET', headers })
|
|
41
|
+
const parsed = await res.json()
|
|
42
|
+
if (!res.ok) fail(parsed, res.status, 'd1 database lookup')
|
|
43
|
+
const list = /** @type {{result?: {name?: string, uuid?: string}[]}} */ (parsed).result ?? []
|
|
44
|
+
// Exact name match, never "the first result": `?name=` is a filter whose
|
|
45
|
+
// matching rule is not documented, so taking position on trust would let a
|
|
46
|
+
// near-name run this account's statements against the wrong database.
|
|
47
|
+
const hit = list.find((d) => d.name === opts.dbName)
|
|
48
|
+
if (!hit || typeof hit.uuid !== 'string') {
|
|
49
|
+
throw new Error(`no D1 database named "${opts.dbName}" in account ${opts.accountId}`)
|
|
50
|
+
}
|
|
51
|
+
databaseId = hit.uuid
|
|
52
|
+
return databaseId
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** @param {string} sql @param {unknown[]} [params] */
|
|
56
|
+
async function query(sql, params = []) {
|
|
57
|
+
const id = await resolveId()
|
|
58
|
+
// Counted before the await: `statements` is what reached D1, and a
|
|
59
|
+
// statement that was issued and then errored still went. Counting successes
|
|
60
|
+
// only would report 0 both for "the lookup failed so nothing was sent" and
|
|
61
|
+
// for "one statement was sent and errored".
|
|
62
|
+
statements++
|
|
63
|
+
const res = await fetchFn(`${API}/accounts/${opts.accountId}/d1/database/${id}/query`, {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers,
|
|
66
|
+
body: JSON.stringify({ sql, params }),
|
|
67
|
+
})
|
|
68
|
+
const parsed = await res.json()
|
|
69
|
+
if (!res.ok) fail(parsed, res.status, 'd1 query')
|
|
70
|
+
const first = (/** @type {{result?: {results?: unknown}[]}} */ (parsed).result ?? [])[0]
|
|
71
|
+
return /** @type {Record<string, unknown>[]} */ (first?.results ?? [])
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
query,
|
|
76
|
+
get statements() {
|
|
77
|
+
return statements
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
//
|
|
21
21
|
// Usage:
|
|
22
22
|
// node platform/plugins/cloudflare/bin/portal-enrol.mjs --owner <ownerId> --name <name> \
|
|
23
|
-
// | npx wrangler d1 execute <db> --remote --file=-
|
|
23
|
+
// --account <accountId> | npx wrangler d1 execute <db> --remote --file=-
|
|
24
24
|
|
|
25
25
|
import {
|
|
26
26
|
PASSCODE_LENGTH,
|
|
@@ -60,6 +60,17 @@ if (!OWNER_ID.test(/** @type {string} */ (ownerId))) {
|
|
|
60
60
|
// the literal harmlessly — but it breaks the pipeable contract, so refuse it.
|
|
61
61
|
if (/[\r\n]/.test(/** @type {string} */ (name))) die('--name cannot contain a line break')
|
|
62
62
|
|
|
63
|
+
// The sub-account whose deliverable folders this person may read. Required, and
|
|
64
|
+
// validated to the same charset the account partition uses: a person enrolled
|
|
65
|
+
// with no account resolves to an EMPTY folder list, which is indistinguishable
|
|
66
|
+
// from a sub-account that simply has no deliverables. That is a failure nobody
|
|
67
|
+
// diagnoses, so it fails here instead — loudly, before the row exists.
|
|
68
|
+
const accountId = arg('--account')
|
|
69
|
+
if (!accountId) die('--account <accountId> is required')
|
|
70
|
+
if (!/^[A-Za-z0-9-]+$/.test(/** @type {string} */ (accountId))) {
|
|
71
|
+
die('--account must match ^[A-Za-z0-9-]+$ — an account id, not a path')
|
|
72
|
+
}
|
|
73
|
+
|
|
63
74
|
const saltBytes = new Uint8Array(16)
|
|
64
75
|
crypto.getRandomValues(saltBytes)
|
|
65
76
|
const saltHex = randomSaltHex(saltBytes)
|
|
@@ -84,8 +95,12 @@ const createdAt = new Date().toISOString()
|
|
|
84
95
|
//
|
|
85
96
|
// `createdAt` is deliberately absent from the DO UPDATE: it records when the
|
|
86
97
|
// person was enrolled, and rotating a passcode is not creating a person.
|
|
98
|
+
//
|
|
99
|
+
// `accountId` IS in the DO UPDATE: re-enrolling someone against a different
|
|
100
|
+
// sub-account has to move them, or they keep reading folders they no longer
|
|
101
|
+
// belong to.
|
|
87
102
|
console.log(
|
|
88
|
-
`INSERT INTO people (ownerId, name, salt, hash, createdAt) VALUES ('${ownerId}', '${sqlName}', '${saltHex}', '${hash}', '${createdAt}') ON CONFLICT(ownerId) DO UPDATE SET name=excluded.name, salt=excluded.salt, hash=excluded.hash;`,
|
|
103
|
+
`INSERT INTO people (ownerId, name, salt, hash, createdAt, accountId) VALUES ('${ownerId}', '${sqlName}', '${saltHex}', '${hash}', '${createdAt}', '${accountId}') ON CONFLICT(ownerId) DO UPDATE SET name=excluded.name, salt=excluded.salt, hash=excluded.hash, accountId=excluded.accountId;`,
|
|
89
104
|
)
|
|
90
105
|
|
|
91
106
|
// stderr: everything for the human. Never stdout — see the note at the top.
|