@xano/sdk 1.0.0
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/CHANGELOG.md +788 -0
- package/LICENSE +21 -0
- package/README.md +541 -0
- package/dist/.build-fingerprint +1 -0
- package/dist/agent-file-refresh-ZEMK33AX.js +38 -0
- package/dist/answer-shape-K67TAAPY.js +33 -0
- package/dist/backend-dir-2U53CIAL.js +39 -0
- package/dist/backend-slot-7QXW2QYC.js +28 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +255 -0
- package/dist/branch-NCRNWQBQ.js +61 -0
- package/dist/branch-commands-ACIRCEQJ.js +79 -0
- package/dist/bundle.d.ts +223 -0
- package/dist/bundle.js +153 -0
- package/dist/capture-WAXPD7DR.js +41 -0
- package/dist/chunk-27GWNP2I.js +23 -0
- package/dist/chunk-2CMJZDCL.js +177 -0
- package/dist/chunk-2EZ3ZJZZ.js +302 -0
- package/dist/chunk-2GNFDZA2.js +390 -0
- package/dist/chunk-2MNLRIOX.js +11 -0
- package/dist/chunk-2TFRR6JV.js +104 -0
- package/dist/chunk-2ZUEE4EK.js +408 -0
- package/dist/chunk-36JUQZVQ.js +22 -0
- package/dist/chunk-3DRXWLJ5.js +83 -0
- package/dist/chunk-3JDU4VRL.js +137 -0
- package/dist/chunk-3KZCLN4Z.js +241 -0
- package/dist/chunk-3LDULPRE.js +168 -0
- package/dist/chunk-3M4LFYNQ.js +111 -0
- package/dist/chunk-3XI5CTUE.js +22 -0
- package/dist/chunk-3YW23UFH.js +50 -0
- package/dist/chunk-45UIT5T2.js +862 -0
- package/dist/chunk-4BW2SG2N.js +46 -0
- package/dist/chunk-4KHEMC32.js +79 -0
- package/dist/chunk-4POWHT2K.js +321 -0
- package/dist/chunk-4Q2GN7NW.js +192 -0
- package/dist/chunk-5A2PLKZB.js +70 -0
- package/dist/chunk-5FBYXK5P.js +159 -0
- package/dist/chunk-5HVXPTU4.js +323 -0
- package/dist/chunk-5QU2P5CA.js +304 -0
- package/dist/chunk-5V5XU32D.js +191 -0
- package/dist/chunk-5ZSO4T2G.js +10 -0
- package/dist/chunk-622HUCZT.js +10 -0
- package/dist/chunk-65HWOMGV.js +177 -0
- package/dist/chunk-6ELFRN3P.js +365 -0
- package/dist/chunk-6HO3NCSJ.js +1055 -0
- package/dist/chunk-6IUMKYWI.js +457 -0
- package/dist/chunk-6KEB3C3U.js +65 -0
- package/dist/chunk-6MR5TI5V.js +987 -0
- package/dist/chunk-6NNTNXN7.js +157 -0
- package/dist/chunk-6PBR37LN.js +97 -0
- package/dist/chunk-6QXLMMT3.js +155 -0
- package/dist/chunk-6ZGJCBZS.js +224 -0
- package/dist/chunk-73IVBKMA.js +250 -0
- package/dist/chunk-7G3CTU7I.js +286 -0
- package/dist/chunk-A3UDOZB6.js +222 -0
- package/dist/chunk-A6E77GDW.js +85 -0
- package/dist/chunk-AHJWCYQO.js +660 -0
- package/dist/chunk-AKNCFVKZ.js +36 -0
- package/dist/chunk-AM4FKL4J.js +237 -0
- package/dist/chunk-AN6BL47T.js +26 -0
- package/dist/chunk-AOSYVMUS.js +147 -0
- package/dist/chunk-APWXY5SL.js +182 -0
- package/dist/chunk-AWEIIR2U.js +218 -0
- package/dist/chunk-AWQ7BQTQ.js +73 -0
- package/dist/chunk-BAUZOWGX.js +393 -0
- package/dist/chunk-BHACRS6R.js +940 -0
- package/dist/chunk-BWPNT55T.js +680 -0
- package/dist/chunk-C6RIMRU4.js +187 -0
- package/dist/chunk-CGVQZXYG.js +263 -0
- package/dist/chunk-CIMQZRRE.js +162 -0
- package/dist/chunk-CJHQKJU4.js +113 -0
- package/dist/chunk-CTMO46PK.js +1897 -0
- package/dist/chunk-D53J55C5.js +106 -0
- package/dist/chunk-D7VGG3WS.js +90 -0
- package/dist/chunk-DAD3ONIS.js +245 -0
- package/dist/chunk-DGWHAVRG.js +162 -0
- package/dist/chunk-DX4QR4B2.js +483 -0
- package/dist/chunk-EHP3WPEG.js +21 -0
- package/dist/chunk-ELP2HMXC.js +432 -0
- package/dist/chunk-ERVIASGL.js +21 -0
- package/dist/chunk-ET4Y4XCB.js +865 -0
- package/dist/chunk-ETO6LGLD.js +4750 -0
- package/dist/chunk-EUUUMY7J.js +24 -0
- package/dist/chunk-FQJ2TTHP.js +218 -0
- package/dist/chunk-FQOF7E2E.js +133 -0
- package/dist/chunk-FWD7AL6N.js +21 -0
- package/dist/chunk-FXFV3KU6.js +138 -0
- package/dist/chunk-FXRQOWTE.js +171 -0
- package/dist/chunk-G554NZNI.js +210 -0
- package/dist/chunk-GSGDWJB5.js +13 -0
- package/dist/chunk-H2V3N23D.js +66 -0
- package/dist/chunk-H5C7MBCR.js +159 -0
- package/dist/chunk-HHN734BU.js +371 -0
- package/dist/chunk-HRVBLCVJ.js +243 -0
- package/dist/chunk-HVRUQXGD.js +1264 -0
- package/dist/chunk-I4F5TCJH.js +130 -0
- package/dist/chunk-IDOTCMDV.js +57 -0
- package/dist/chunk-IGOLHKWF.js +427 -0
- package/dist/chunk-JGE7CAWB.js +148 -0
- package/dist/chunk-JTUKLCXS.js +92 -0
- package/dist/chunk-JZGVVJSI.js +66 -0
- package/dist/chunk-K4WX6UUU.js +923 -0
- package/dist/chunk-K5BBGTYZ.js +217 -0
- package/dist/chunk-K6RXBGUB.js +427 -0
- package/dist/chunk-KTSDSD36.js +165 -0
- package/dist/chunk-L5JYN3NM.js +1038 -0
- package/dist/chunk-LIEOKXKD.js +354 -0
- package/dist/chunk-LLNZGEFK.js +51 -0
- package/dist/chunk-LPECSJ2J.js +55 -0
- package/dist/chunk-LQCMKAJ7.js +413 -0
- package/dist/chunk-LRN4ZJCQ.js +1145 -0
- package/dist/chunk-MJ4ZVHKN.js +17 -0
- package/dist/chunk-MXHSQOC4.js +3061 -0
- package/dist/chunk-N5GKCWE5.js +367 -0
- package/dist/chunk-NA3PXMEF.js +10 -0
- package/dist/chunk-NAQDMEHV.js +76 -0
- package/dist/chunk-NWKXM5RD.js +109 -0
- package/dist/chunk-NZOWWHU5.js +196 -0
- package/dist/chunk-OHX6MIUZ.js +184 -0
- package/dist/chunk-OOXWYAA4.js +15 -0
- package/dist/chunk-OPHB5ORL.js +1990 -0
- package/dist/chunk-OXA6PSZT.js +1725 -0
- package/dist/chunk-P2HAGF6H.js +73 -0
- package/dist/chunk-PTHVMXZI.js +3036 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-Q325JKYB.js +189 -0
- package/dist/chunk-Q7DGTQ5G.js +255 -0
- package/dist/chunk-QCNYHBL7.js +601 -0
- package/dist/chunk-QO6BVGS5.js +145 -0
- package/dist/chunk-QXTKSOFI.js +76 -0
- package/dist/chunk-ROY4YAEQ.js +66 -0
- package/dist/chunk-RTJRZFFY.js +447 -0
- package/dist/chunk-SAVJUS2X.js +128 -0
- package/dist/chunk-SFLHVCKX.js +173 -0
- package/dist/chunk-STSFNCY4.js +127 -0
- package/dist/chunk-SWBF2LTK.js +170 -0
- package/dist/chunk-T5KELPAR.js +109 -0
- package/dist/chunk-TFFGOFVZ.js +1481 -0
- package/dist/chunk-TLHIFK4M.js +256 -0
- package/dist/chunk-TLQE7ZN3.js +156 -0
- package/dist/chunk-TPHWJ7UG.js +1981 -0
- package/dist/chunk-TRJCALW3.js +291 -0
- package/dist/chunk-TTEIYD5P.js +133 -0
- package/dist/chunk-U64O2MXY.js +407 -0
- package/dist/chunk-UCQ2VUTG.js +630 -0
- package/dist/chunk-UTI5G6OJ.js +44 -0
- package/dist/chunk-UVHDMMIP.js +188 -0
- package/dist/chunk-UZ6QQWJT.js +120 -0
- package/dist/chunk-VAX3UZOY.js +686 -0
- package/dist/chunk-W3HBUKFO.js +57 -0
- package/dist/chunk-WKQLGKCL.js +2823 -0
- package/dist/chunk-WQFNASK4.js +560 -0
- package/dist/chunk-WRBKLYSJ.js +224 -0
- package/dist/chunk-WTPZRR4I.js +17 -0
- package/dist/chunk-WZDJ2LLX.js +2385 -0
- package/dist/chunk-X2NL4KUO.js +14 -0
- package/dist/chunk-X4DVXBFY.js +38 -0
- package/dist/chunk-X677DMZM.js +7050 -0
- package/dist/chunk-XBKMWY7F.js +612 -0
- package/dist/chunk-XNCP4KYO.js +78 -0
- package/dist/chunk-XVBGLISP.js +35 -0
- package/dist/chunk-XW6KISBB.js +1192 -0
- package/dist/chunk-YGZHHAAB.js +8156 -0
- package/dist/chunk-YHEFQUQB.js +186 -0
- package/dist/chunk-YLDALTWZ.js +653 -0
- package/dist/chunk-YU4HSQW7.js +229 -0
- package/dist/chunk-ZDPP6RKB.js +27 -0
- package/dist/chunk-ZFDZKNGG.js +194 -0
- package/dist/chunk-ZIY7RNUT.js +49 -0
- package/dist/chunk-ZYJP6BOO.js +30 -0
- package/dist/cli.d.ts +1111 -0
- package/dist/cli.js +165 -0
- package/dist/codegen-command-VR2GWR6C.js +173 -0
- package/dist/codegen.d.ts +115 -0
- package/dist/codegen.js +19 -0
- package/dist/commands-GJMJ4XYZ.js +76 -0
- package/dist/completion-ZQLO6PB6.js +738 -0
- package/dist/config-MBYGCAFK.js +136 -0
- package/dist/deploy-command-JVZPWHMK.js +180 -0
- package/dist/deploy-source-3W45CXMP.js +81 -0
- package/dist/dev-env-sync-HN44K4D5.js +188 -0
- package/dist/diagnostics-UEAAHWVF.js +30 -0
- package/dist/display-name-hint-ABBPVQIR.js +46 -0
- package/dist/emit-CyhBDxli.d.ts +7007 -0
- package/dist/env-example-refresh-ET5HMMDW.js +126 -0
- package/dist/env-pull-command-TNMNB3CV.js +323 -0
- package/dist/env-target-O4HRKTGZ.js +40 -0
- package/dist/env-var-command-ZIRDCAZA.js +427 -0
- package/dist/ephemeral-P7F3PMAD.js +54 -0
- package/dist/ephemeral-command-CLJJKOFT.js +592 -0
- package/dist/ephemeral-state-PYCBSOGX.js +61 -0
- package/dist/file-lock-PT74GWYW.js +10 -0
- package/dist/generate-command-AYEOLA26.js +510 -0
- package/dist/gitignore-3SJ3WNRG.js +51 -0
- package/dist/guards-4EBNCZDC.js +162 -0
- package/dist/hosted-file-PSHJB3HX.js +28 -0
- package/dist/impersonate-command-5JAS3WSV.js +152 -0
- package/dist/import-EQSFU6JC.js +19 -0
- package/dist/index.d.ts +5470 -0
- package/dist/index.js +371 -0
- package/dist/init-command-X2FR4UAK.js +125 -0
- package/dist/init-modules-RY7UHAOL.js +50 -0
- package/dist/init-web-M4YZSZK6.js +42 -0
- package/dist/internal.d.ts +179 -0
- package/dist/internal.js +2634 -0
- package/dist/io-RC56QSN5.js +26 -0
- package/dist/keep-data-merge-7MPLVAVI.js +1119 -0
- package/dist/lambda-config-reconcile-OUZ4OYAB.js +116 -0
- package/dist/landed-753YLJ5H.js +40 -0
- package/dist/landing-record-SU52SKXU.js +61 -0
- package/dist/live-diff-YONW2VI3.js +65 -0
- package/dist/local-engine-choice-CH5X3W7E.js +79 -0
- package/dist/local-engine-command-Z3OL7VOD.js +766 -0
- package/dist/local-engine-config-5U3SC434.js +39 -0
- package/dist/local-engine-deploy-PCQ4SNPB.js +39 -0
- package/dist/local-engine-process-EHTE5LLU.js +63 -0
- package/dist/local-engine-state-A4FTRFAF.js +32 -0
- package/dist/local-engine-update-check-6L645VBL.js +162 -0
- package/dist/lock-MRRHW5GS.js +87 -0
- package/dist/lock-commands-2QG25HL7.js +963 -0
- package/dist/login-command-QPYRWOXZ.js +538 -0
- package/dist/logout-command-WOIQLDP6.js +56 -0
- package/dist/loop-ALIZLOSX.js +191 -0
- package/dist/marketplace-command-K5YL42NO.js +977 -0
- package/dist/marketplace-resolve-LY32JES5.js +40 -0
- package/dist/meta-client-YXGPW53C.js +244 -0
- package/dist/module-manifest-JFGHU2TC.js +38 -0
- package/dist/near-key.d.ts +13 -0
- package/dist/near-key.js +8 -0
- package/dist/node.d.ts +95 -0
- package/dist/node.js +969 -0
- package/dist/operation-outcome-TZSESNC7.js +51 -0
- package/dist/output-target-AL3I44GG.js +47 -0
- package/dist/package-manager-PRAHUSCN.js +34 -0
- package/dist/plan-disclosure-6G4MLZKD.js +114 -0
- package/dist/plugin-contributions-WRNY5ABG.js +28 -0
- package/dist/plugin-prompt-4VA3B4FF.js +20 -0
- package/dist/plugin-questions-JTSYA74Q.js +24 -0
- package/dist/plugin.d.ts +396 -0
- package/dist/plugin.js +1 -0
- package/dist/preflight-command-2T5Z36MI.js +321 -0
- package/dist/profile-command-AH3PIM4X.js +800 -0
- package/dist/profile-pointer-GRWJU3YD.js +31 -0
- package/dist/profile-select-74TRDEPD.js +23 -0
- package/dist/project-dependency-KSH63ODT.js +35 -0
- package/dist/project-sdk-REPPOIBD.js +24 -0
- package/dist/promote-command-PCWT5N6S.js +140 -0
- package/dist/prompt-II3F3LND.js +27 -0
- package/dist/publish-command-TTDUUJI5.js +414 -0
- package/dist/pull-command-VBO7C4S3.js +161 -0
- package/dist/pull-superseded-GJWLG7XO.js +20 -0
- package/dist/release-command-N4ZPKMHR.js +146 -0
- package/dist/release-ns-command-3KFDMZ6G.js +1745 -0
- package/dist/response-HlXahv4K.d.ts +925 -0
- package/dist/retry-command-AS5EVREX.js +34 -0
- package/dist/routes-manifest-SNWUVBT6.js +43 -0
- package/dist/runtime-XETHVF33.js +14 -0
- package/dist/scaffold.d.ts +1911 -0
- package/dist/scaffold.js +107 -0
- package/dist/secrets-file-LVZD6IIM.js +56 -0
- package/dist/secrets-fill-command-TTCPSVXN.js +218 -0
- package/dist/seed-VU7C3QYS.js +46 -0
- package/dist/sent-writes-N7X4OVXT.js +22 -0
- package/dist/source-resolve-S2XA3XZF.js +83 -0
- package/dist/static-host-B2Y6AG3W.js +352 -0
- package/dist/status-command-RUDXTF7I.js +554 -0
- package/dist/store-6VNCD4D7.js +20 -0
- package/dist/store-ARM6VMO5.js +98 -0
- package/dist/store-DuYfzKlW.d.ts +3144 -0
- package/dist/suggest-6FOOYKHG.js +12 -0
- package/dist/sveltekit.d.ts +154 -0
- package/dist/sveltekit.js +109 -0
- package/dist/sync-contributions-UCEROO26.js +415 -0
- package/dist/table-ENAVMITR.js +42 -0
- package/dist/tables-command-GUPXXSB5.js +156 -0
- package/dist/tenant-IIHPOHGP.js +42 -0
- package/dist/tenant-command-D5HGI2X4.js +965 -0
- package/dist/test-command-TIKZMDPZ.js +476 -0
- package/dist/tests-BGGIOK3L.js +26 -0
- package/dist/token-NICJUTMT.js +72 -0
- package/dist/toolchain-hooks-GHKLUJOH.js +96 -0
- package/dist/toolchain-modules-5EAZUONB.js +24 -0
- package/dist/tracked-backend-EIBUMDZA.js +80 -0
- package/dist/update-check-2W2NLDTC.js +136 -0
- package/dist/upgrade-command-QHNFWM7O.js +531 -0
- package/dist/verify-rollout-DRCK5TDD.js +61 -0
- package/dist/whoami-command-STGINTXH.js +51 -0
- package/dist/workspace-2CLHU2LK.js +93 -0
- package/dist/workspace-binding-56PIIPCP.js +54 -0
- package/dist/workspace-command-JEXQETJD.js +535 -0
- package/dist/workspace-env-F2RFMSC5.js +70 -0
- package/dist/workspace-export-OJSH2HIW.js +21 -0
- package/dist/writable-VA7OISSK.js +25 -0
- package/dist/xano-ICFUSPFR.js +42 -0
- package/dist/xanosdk-import-GN42TO2B.js +56 -0
- package/dist/xanosdk-project-S6DXJONQ.js +41 -0
- package/dist/xdo-CxDr2Q1C.d.ts +1371 -0
- package/guides/README.md +33 -0
- package/guides/authoring.md +921 -0
- package/guides/cli.md +401 -0
- package/guides/codegen.md +177 -0
- package/guides/codes.md +404 -0
- package/guides/coverage.md +74 -0
- package/guides/deploying.md +1525 -0
- package/guides/environment.md +400 -0
- package/guides/marketplace.md +415 -0
- package/guides/module-contract.md +558 -0
- package/guides/object-kinds.md +433 -0
- package/guides/project-structure.md +43 -0
- package/guides/scaffold.md +351 -0
- package/guides/typed-frontend.md +242 -0
- package/llms/client.md +63 -0
- package/llms/errors.md +56 -0
- package/llms/fields.md +176 -0
- package/llms/filters.md +183 -0
- package/llms/kinds-agent-mcp.md +30 -0
- package/llms/kinds-core.md +100 -0
- package/llms/kinds-knowledge.md +20 -0
- package/llms/kinds-realtime.md +76 -0
- package/llms/lambda.md +94 -0
- package/llms/legacy.md +44 -0
- package/llms/lock.md +40 -0
- package/llms/object-kinds.md +34 -0
- package/llms/statements-calls.md +62 -0
- package/llms/statements-catalog.md +320 -0
- package/llms/statements-data.md +66 -0
- package/llms/statements-runtime.md +68 -0
- package/llms/tests.md +59 -0
- package/llms/toolchain.md +16 -0
- package/llms/triggers.md +36 -0
- package/llms/values.md +29 -0
- package/llms-full.txt +2085 -0
- package/llms.txt +493 -0
- package/manifest.json +11224 -0
- package/package.json +162 -0
- package/types/lambda-globals.d.ts +129 -0
|
@@ -0,0 +1,2823 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EXIT_STATIC_FAILED,
|
|
3
|
+
assertStaticHostExists,
|
|
4
|
+
noteStaticTakenDown,
|
|
5
|
+
readStaticTeardown,
|
|
6
|
+
refuseStaticHostAReplaceClears,
|
|
7
|
+
staticRemovedField,
|
|
8
|
+
staticRetryCommand,
|
|
9
|
+
staticRetryHint,
|
|
10
|
+
unknownStaticOutcome,
|
|
11
|
+
warnStaticTeardown
|
|
12
|
+
} from "./chunk-FQJ2TTHP.js";
|
|
13
|
+
import {
|
|
14
|
+
shownLabel
|
|
15
|
+
} from "./chunk-K6RXBGUB.js";
|
|
16
|
+
import {
|
|
17
|
+
confirm,
|
|
18
|
+
needsConfirmation
|
|
19
|
+
} from "./chunk-65HWOMGV.js";
|
|
20
|
+
import {
|
|
21
|
+
landedRecordFor,
|
|
22
|
+
landingLockPath,
|
|
23
|
+
recordForeignLanding,
|
|
24
|
+
recordLanding
|
|
25
|
+
} from "./chunk-CGVQZXYG.js";
|
|
26
|
+
import {
|
|
27
|
+
assertStorageModesKept,
|
|
28
|
+
discloseCanonicalMoves,
|
|
29
|
+
movesClause,
|
|
30
|
+
storageChangesPayload
|
|
31
|
+
} from "./chunk-BWPNT55T.js";
|
|
32
|
+
import {
|
|
33
|
+
listTables,
|
|
34
|
+
tableRowCounter,
|
|
35
|
+
tableRowIds
|
|
36
|
+
} from "./chunk-C6RIMRU4.js";
|
|
37
|
+
import {
|
|
38
|
+
XANOSDK_IMPORT_CONFLICT_CODE,
|
|
39
|
+
XANOSDK_IMPORT_UNIQUENESS_CODE,
|
|
40
|
+
XanoSdkImportRefusal,
|
|
41
|
+
xanosdkImport
|
|
42
|
+
} from "./chunk-WQFNASK4.js";
|
|
43
|
+
import {
|
|
44
|
+
archiveSeedContent
|
|
45
|
+
} from "./chunk-AOSYVMUS.js";
|
|
46
|
+
import {
|
|
47
|
+
encodeWorkspaceArchive
|
|
48
|
+
} from "./chunk-3DRXWLJ5.js";
|
|
49
|
+
import {
|
|
50
|
+
DEFAULT_BRANCH_LABEL,
|
|
51
|
+
assertBranchAbsent,
|
|
52
|
+
assertUsableBranchLabel,
|
|
53
|
+
createBranch,
|
|
54
|
+
derivedLabelTail,
|
|
55
|
+
listBranchListing,
|
|
56
|
+
liveBranchLabel,
|
|
57
|
+
setLiveBranch
|
|
58
|
+
} from "./chunk-TRJCALW3.js";
|
|
59
|
+
import {
|
|
60
|
+
compareToLive,
|
|
61
|
+
envValuesOf,
|
|
62
|
+
payloadOf,
|
|
63
|
+
planTypeSection,
|
|
64
|
+
rowLabeler,
|
|
65
|
+
sectionKind,
|
|
66
|
+
sharedSchemaChanges,
|
|
67
|
+
storedColumnsAfter,
|
|
68
|
+
uniqueIndexLabels
|
|
69
|
+
} from "./chunk-45UIT5T2.js";
|
|
70
|
+
import {
|
|
71
|
+
assertBundleFile,
|
|
72
|
+
assertBundleInput,
|
|
73
|
+
loadBundleText,
|
|
74
|
+
resolveLockPath
|
|
75
|
+
} from "./chunk-ETO6LGLD.js";
|
|
76
|
+
import {
|
|
77
|
+
LookupFailedError,
|
|
78
|
+
isUnansweredLookup,
|
|
79
|
+
unansweredCause
|
|
80
|
+
} from "./chunk-QCNYHBL7.js";
|
|
81
|
+
import {
|
|
82
|
+
isSignatureRefusal
|
|
83
|
+
} from "./chunk-AN6BL47T.js";
|
|
84
|
+
import {
|
|
85
|
+
getAccessToken
|
|
86
|
+
} from "./chunk-ET4Y4XCB.js";
|
|
87
|
+
import {
|
|
88
|
+
ephemeralLandedOn
|
|
89
|
+
} from "./chunk-5QU2P5CA.js";
|
|
90
|
+
import {
|
|
91
|
+
pipedYes,
|
|
92
|
+
retryCommand,
|
|
93
|
+
withheldNote
|
|
94
|
+
} from "./chunk-T5KELPAR.js";
|
|
95
|
+
import {
|
|
96
|
+
contextFlags
|
|
97
|
+
} from "./chunk-NAQDMEHV.js";
|
|
98
|
+
import {
|
|
99
|
+
backendDirIn
|
|
100
|
+
} from "./chunk-5A2PLKZB.js";
|
|
101
|
+
import {
|
|
102
|
+
shellWord
|
|
103
|
+
} from "./chunk-FWD7AL6N.js";
|
|
104
|
+
import {
|
|
105
|
+
describeWrite
|
|
106
|
+
} from "./chunk-4KHEMC32.js";
|
|
107
|
+
import {
|
|
108
|
+
seedRowsByTableGuid
|
|
109
|
+
} from "./chunk-U64O2MXY.js";
|
|
110
|
+
import {
|
|
111
|
+
readLockFile
|
|
112
|
+
} from "./chunk-P2HAGF6H.js";
|
|
113
|
+
import {
|
|
114
|
+
destinationKey,
|
|
115
|
+
groupNames,
|
|
116
|
+
isEphemeralDestinationKey,
|
|
117
|
+
landedIdentities,
|
|
118
|
+
landedKeyForRow,
|
|
119
|
+
storedColumnsOf,
|
|
120
|
+
withStoredColumns
|
|
121
|
+
} from "./chunk-TLQE7ZN3.js";
|
|
122
|
+
import {
|
|
123
|
+
TransportError,
|
|
124
|
+
isTimeoutError
|
|
125
|
+
} from "./chunk-YLDALTWZ.js";
|
|
126
|
+
import {
|
|
127
|
+
CliError,
|
|
128
|
+
EXIT_OUTCOME_UNKNOWN,
|
|
129
|
+
UsageError,
|
|
130
|
+
backendDestinationPayload,
|
|
131
|
+
blank,
|
|
132
|
+
describeWriteTarget,
|
|
133
|
+
detail,
|
|
134
|
+
displayPath,
|
|
135
|
+
info,
|
|
136
|
+
isMachineOutput,
|
|
137
|
+
link,
|
|
138
|
+
pastePath,
|
|
139
|
+
statesOutcomeUnknown,
|
|
140
|
+
step,
|
|
141
|
+
success,
|
|
142
|
+
warn,
|
|
143
|
+
writeJson,
|
|
144
|
+
writeTargetPayload
|
|
145
|
+
} from "./chunk-PTHVMXZI.js";
|
|
146
|
+
import {
|
|
147
|
+
calcSignatureJson,
|
|
148
|
+
signatureHolds
|
|
149
|
+
} from "./chunk-LQCMKAJ7.js";
|
|
150
|
+
import {
|
|
151
|
+
safeNames
|
|
152
|
+
} from "./chunk-GSGDWJB5.js";
|
|
153
|
+
import {
|
|
154
|
+
adoptFromBundle,
|
|
155
|
+
codePinnedTables,
|
|
156
|
+
identityNamesByGuid,
|
|
157
|
+
lockKey,
|
|
158
|
+
lockNameForObject,
|
|
159
|
+
sdkKindName2 as sdkKindName
|
|
160
|
+
} from "./chunk-6MR5TI5V.js";
|
|
161
|
+
import {
|
|
162
|
+
shellQuote
|
|
163
|
+
} from "./chunk-5ZSO4T2G.js";
|
|
164
|
+
import {
|
|
165
|
+
REFERENCEABLE_KIND_PAYLOAD_KEYS,
|
|
166
|
+
rawDeriveGuid
|
|
167
|
+
} from "./chunk-NZOWWHU5.js";
|
|
168
|
+
import {
|
|
169
|
+
withArticle
|
|
170
|
+
} from "./chunk-36JUQZVQ.js";
|
|
171
|
+
|
|
172
|
+
// src/emit/release-command.ts
|
|
173
|
+
import { existsSync, readFileSync } from "fs";
|
|
174
|
+
import { dirname, join, relative, resolve, sep } from "path";
|
|
175
|
+
|
|
176
|
+
// src/emit/plan-presentation.ts
|
|
177
|
+
function nounOf(ctx) {
|
|
178
|
+
return ctx.noun ?? (ctx.tenant ? "tenant" : "workspace");
|
|
179
|
+
}
|
|
180
|
+
function rowsOf(payload, section) {
|
|
181
|
+
const rows = payload[section];
|
|
182
|
+
return Array.isArray(rows) ? rows.filter((r) => r !== null && typeof r === "object") : [];
|
|
183
|
+
}
|
|
184
|
+
function sdkKindForPlanType(type, row) {
|
|
185
|
+
return sectionKind(planTypeSection(type), row);
|
|
186
|
+
}
|
|
187
|
+
function guidOf(row) {
|
|
188
|
+
return typeof row.guid === "string" && row.guid !== "" ? row.guid : void 0;
|
|
189
|
+
}
|
|
190
|
+
function deletedRows(ops, ctx) {
|
|
191
|
+
const ours = payloadOf(ctx.bundle);
|
|
192
|
+
const theirs = payloadOf(ctx.live);
|
|
193
|
+
const used = /* @__PURE__ */ new Set();
|
|
194
|
+
const carried = /* @__PURE__ */ new Map();
|
|
195
|
+
const carriedIn = (section) => {
|
|
196
|
+
let set = carried.get(section);
|
|
197
|
+
if (set === void 0) {
|
|
198
|
+
set = new Set(rowsOf(ours, section).map(guidOf).filter((g) => g !== void 0));
|
|
199
|
+
carried.set(section, set);
|
|
200
|
+
}
|
|
201
|
+
return set;
|
|
202
|
+
};
|
|
203
|
+
return ops.map((op) => {
|
|
204
|
+
if (op.action !== "delete") return void 0;
|
|
205
|
+
const section = planTypeSection(op.type);
|
|
206
|
+
const kept = carriedIn(section);
|
|
207
|
+
const named = rowsOf(theirs, section).filter((r) => !used.has(r) && r.name === op.name);
|
|
208
|
+
const row = named.find((r) => {
|
|
209
|
+
const guid = guidOf(r);
|
|
210
|
+
return guid === void 0 || !kept.has(guid);
|
|
211
|
+
}) ?? named[0];
|
|
212
|
+
if (row !== void 0) used.add(row);
|
|
213
|
+
return row;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
function presentOperations(ops, ctx) {
|
|
217
|
+
const ours = payloadOf(ctx.bundle);
|
|
218
|
+
const theirs = payloadOf(ctx.live);
|
|
219
|
+
const ourLabel = rowLabeler(ours);
|
|
220
|
+
const theirLabel = rowLabeler(theirs);
|
|
221
|
+
const cursor = /* @__PURE__ */ new Map();
|
|
222
|
+
const deleted = deletedRows(ops, ctx);
|
|
223
|
+
const created = new Set(ops.filter((o) => o.action === "create").map((o) => `${o.type}\0${o.name}`));
|
|
224
|
+
return ops.map((op, index) => {
|
|
225
|
+
const section = planTypeSection(op.type);
|
|
226
|
+
let row;
|
|
227
|
+
let labeler = ourLabel;
|
|
228
|
+
if (op.action === "delete") {
|
|
229
|
+
labeler = theirLabel;
|
|
230
|
+
row = deleted[index];
|
|
231
|
+
} else if (op.action === "create" || op.action === "update") {
|
|
232
|
+
const rows = rowsOf(ours, section);
|
|
233
|
+
const at = cursor.get(section) ?? 0;
|
|
234
|
+
row = rows[at]?.name === op.name ? rows[at] : rows.find((r) => r.name === op.name);
|
|
235
|
+
cursor.set(section, at + 1);
|
|
236
|
+
} else {
|
|
237
|
+
row = rowsOf(ours, section).find((r) => r.name === op.name);
|
|
238
|
+
}
|
|
239
|
+
const type = sdkKindForPlanType(op.type, row);
|
|
240
|
+
const query = section === "query" && row !== void 0 ? queryParts(row, labeler(section, row)) : {};
|
|
241
|
+
const label = row !== void 0 && section !== "workspace" ? labeler(section, row) : `${type}:${op.name}`;
|
|
242
|
+
const provenance = op.action === "delete" ? ctx.provenance?.(op, row) : void 0;
|
|
243
|
+
const worded = op.action === "delete" && created.has(`${op.type}\0${op.name}`) ? "recreate" : op.action;
|
|
244
|
+
const details = sdkWords(op.details, nounOf(ctx), provenance, ctx.rows, worded);
|
|
245
|
+
const reason = sdkWords(op.reason, nounOf(ctx), provenance, ctx.rows, worded);
|
|
246
|
+
return {
|
|
247
|
+
type,
|
|
248
|
+
name: op.name,
|
|
249
|
+
...query,
|
|
250
|
+
label,
|
|
251
|
+
action: op.action,
|
|
252
|
+
...details === void 0 ? {} : { details },
|
|
253
|
+
...reason === void 0 ? {} : { reason }
|
|
254
|
+
};
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function tableRenames(bundle, live) {
|
|
258
|
+
const liveNames = /* @__PURE__ */ new Map();
|
|
259
|
+
for (const row of rowsOf(payloadOf(live), "dbo")) {
|
|
260
|
+
const guid = guidOf(row);
|
|
261
|
+
if (guid !== void 0 && typeof row.name === "string") liveNames.set(guid, row.name);
|
|
262
|
+
}
|
|
263
|
+
const out = [];
|
|
264
|
+
for (const row of rowsOf(payloadOf(bundle), "dbo")) {
|
|
265
|
+
const guid = guidOf(row);
|
|
266
|
+
if (guid === void 0 || typeof row.name !== "string") continue;
|
|
267
|
+
const from = liveNames.get(guid);
|
|
268
|
+
if (from !== void 0 && from !== row.name) out.push({ from, to: row.name });
|
|
269
|
+
}
|
|
270
|
+
return out;
|
|
271
|
+
}
|
|
272
|
+
function queryParts(row, label) {
|
|
273
|
+
const verb = typeof row.verb === "string" && row.verb !== "" ? row.verb : void 0;
|
|
274
|
+
const group = / \(apiGroup (.+)\)$/.exec(label)?.[1];
|
|
275
|
+
return { ...verb === void 0 ? {} : { verb }, ...group === void 0 ? {} : { apiGroup: group } };
|
|
276
|
+
}
|
|
277
|
+
function countActions(ops) {
|
|
278
|
+
const out = { create: 0, update: 0, delete: 0, truncate: 0 };
|
|
279
|
+
for (const op of ops) out[op.action] = (out[op.action] ?? 0) + 1;
|
|
280
|
+
return out;
|
|
281
|
+
}
|
|
282
|
+
function countTypes(ops) {
|
|
283
|
+
const out = {};
|
|
284
|
+
for (const op of ops) {
|
|
285
|
+
const counts = out[op.type] ??= { create: 0, update: 0, delete: 0, truncate: 0 };
|
|
286
|
+
counts[op.action] = (counts[op.action] ?? 0) + 1;
|
|
287
|
+
}
|
|
288
|
+
return out;
|
|
289
|
+
}
|
|
290
|
+
function presentRows(rows) {
|
|
291
|
+
if (rows === null || typeof rows !== "object" || Array.isArray(rows)) return void 0;
|
|
292
|
+
const out = {};
|
|
293
|
+
for (const [key, value] of Object.entries(rows)) {
|
|
294
|
+
out[key.replace(/_([a-z])/g, (_, c) => c.toUpperCase())] = value;
|
|
295
|
+
}
|
|
296
|
+
return out;
|
|
297
|
+
}
|
|
298
|
+
function sdkWords(text, target, provenance, rows, action) {
|
|
299
|
+
if (text === void 0 || text === "") return void 0;
|
|
300
|
+
const known = KNOWN_SENTENCES.find(([match]) => match.test(text));
|
|
301
|
+
if (known !== void 0) return known[1](target, provenance, rows, action);
|
|
302
|
+
return ENGINE_VOCABULARY.test(text) ? void 0 : text;
|
|
303
|
+
}
|
|
304
|
+
function recreated(rows) {
|
|
305
|
+
const source = rows?.source ?? "project";
|
|
306
|
+
const carried = rows?.carried ?? true;
|
|
307
|
+
if (source === "bundle") {
|
|
308
|
+
return carried ? "The bundle's tables are re-created with the rows it carries; anything it does not carry is gone." : "The bundle carries no rows \u2014 every table comes back empty.";
|
|
309
|
+
}
|
|
310
|
+
return carried ? "This project's own tables are re-created with its seed rows; anything it does not declare is gone." : "This project's own tables are re-created empty \u2014 this deploy carries no seed rows; anything it does not declare is gone.";
|
|
311
|
+
}
|
|
312
|
+
function notRecreated(rows) {
|
|
313
|
+
return (rows?.source ?? "project") === "bundle" ? "The bundle does not carry this table, so it is dropped, not re-created." : "This project does not declare this table, so it is dropped, not re-created.";
|
|
314
|
+
}
|
|
315
|
+
var ENGINE_VOCABULARY = /\b(?:mode|delete|records|truncate)=|\bobj_type\b|\b[a-z]+:[a-z_]+\b/;
|
|
316
|
+
var KNOWN_SENTENCES = [
|
|
317
|
+
[
|
|
318
|
+
/^Workspace settings and environment variables will be updated from the archive, and the workspace will be CLEARED/,
|
|
319
|
+
(t) => `Settings are updated from the archive, after the ${t} is CLEARED`
|
|
320
|
+
],
|
|
321
|
+
[/^Workspace settings and environment variables will be updated from the archive/, () => "Settings are updated from the archive"],
|
|
322
|
+
[/^mode=replace wipes the destination workspace/, (t) => `\`--replace\` clears the ${t} before importing`],
|
|
323
|
+
[
|
|
324
|
+
/^Will be created: no object in this workspace carries the archive's guid/,
|
|
325
|
+
(t) => `Created: nothing in the ${t} carries this object's identity`
|
|
326
|
+
],
|
|
327
|
+
[/^Will be deleted by the workspace clear that mode=replace performs/, () => "Deleted by the clear `--replace` runs before importing"],
|
|
328
|
+
[
|
|
329
|
+
/^Will be deleted: the archive does not carry this object and delete=true/,
|
|
330
|
+
(_t, provenance) => `Deleted: ${whyPruned(provenance)}, and \`--prune\` removes what the project omits`
|
|
331
|
+
],
|
|
332
|
+
[
|
|
333
|
+
/^ROW LOSS: mode=replace drops every table/,
|
|
334
|
+
(t, _provenance, rows, action) => `ROW LOSS: \`--replace\` drops every table in the ${t} before importing, destroying its rows. ` + // On a table being DELETED — one the archive does not carry — nothing
|
|
335
|
+
// brings it back; "every table comes back empty" described a table that
|
|
336
|
+
// does not come back at all.
|
|
337
|
+
(action === "delete" ? notRecreated(rows) : recreated(rows))
|
|
338
|
+
],
|
|
339
|
+
[
|
|
340
|
+
/^ROW LOSS: this table is deleted because the archive does not carry it/,
|
|
341
|
+
(_t, provenance) => `ROW LOSS: \`--prune\` deletes this table because ${whyPruned(provenance)}, and its rows go with it. Leaving out \`--seed\` does NOT protect them. Nothing in this deploy restores them.`
|
|
342
|
+
],
|
|
343
|
+
[
|
|
344
|
+
/^truncate=true empties the tables the archive carries, and records=true/,
|
|
345
|
+
() => "`--reset-data` empties the tables this project declares, and `--seed` then loads its seed rows into them"
|
|
346
|
+
],
|
|
347
|
+
[
|
|
348
|
+
/^truncate=true empties the tables the archive carries/,
|
|
349
|
+
() => "`--reset-data` empties the tables this project declares, and without `--seed` nothing is loaded back"
|
|
350
|
+
],
|
|
351
|
+
[
|
|
352
|
+
/^Matched by obj_type, not by guid/,
|
|
353
|
+
(t) => `Matched as the ${t}'s one error trigger rather than by identity, which the import replaces with the ${t}'s own. It is updated in place, not replaced.`
|
|
354
|
+
]
|
|
355
|
+
];
|
|
356
|
+
function whyPruned(provenance) {
|
|
357
|
+
switch (provenance) {
|
|
358
|
+
case "landed":
|
|
359
|
+
return "this project no longer declares it";
|
|
360
|
+
case "adopted":
|
|
361
|
+
return "it was adopted by `lock import`, not declared by this project";
|
|
362
|
+
case "locked":
|
|
363
|
+
return "this project no longer declares it, though it never landed it here \u2014 it may be another project's of the same name";
|
|
364
|
+
case "never":
|
|
365
|
+
return "this project never declared it \u2014 it is another project's or was made by hand";
|
|
366
|
+
default:
|
|
367
|
+
return "this project does not declare it";
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/emit/release-command.ts
|
|
372
|
+
var DESTRUCTIVE_ACTIONS = /* @__PURE__ */ new Set(["delete", "truncate", "drop"]);
|
|
373
|
+
var IDENTITY_EXEMPT_TYPES = /* @__PURE__ */ new Set(["workspace"]);
|
|
374
|
+
var BRANCH_INCOMPATIBLE = [
|
|
375
|
+
{
|
|
376
|
+
flag: "--replace",
|
|
377
|
+
why: "a replace wipes the whole workspace, which is not something a branch can scope"
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
flag: "--backup-branch",
|
|
381
|
+
why: "a deploy to a branch does not overwrite the live branch, so there is nothing for a backup to protect"
|
|
382
|
+
}
|
|
383
|
+
];
|
|
384
|
+
function resolveRequest(args) {
|
|
385
|
+
let branch;
|
|
386
|
+
if (args.branch !== void 0) {
|
|
387
|
+
branch = assertUsableBranchLabel(args.branch, "--branch");
|
|
388
|
+
const conflicting = BRANCH_INCOMPATIBLE.filter(
|
|
389
|
+
({ flag }) => flag === "--replace" ? args.replace : args.backupBranch !== void 0
|
|
390
|
+
);
|
|
391
|
+
if (conflicting.length > 0) {
|
|
392
|
+
const { flag, why } = conflicting[0];
|
|
393
|
+
throw new UsageError(
|
|
394
|
+
`\`--branch\` cannot be combined with \`${flag}\`: ${why}.
|
|
395
|
+
Drop \`${flag}\` to stage this deploy on "${branch}", or drop \`--branch\` to deploy to the live branch.`,
|
|
396
|
+
{ helpFor: { command: "deploy" } }
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
} else if (args.setLive) {
|
|
400
|
+
throw new UsageError(
|
|
401
|
+
`\`--set-live\` promotes the branch a deploy just landed on, and this deploy names no branch.
|
|
402
|
+
Add \`--branch <label>\` to stage the deploy and promote it, or use \`xanosdk workspace branch set-live <label>${contextFlags()}\` to promote a branch that already exists.`,
|
|
403
|
+
{ helpFor: { command: "deploy" } }
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
if (typeof args.backupBranch === "string") {
|
|
407
|
+
assertUsableBranchLabel(args.backupBranch, "--backup-branch", "drop the flag to deploy without a snapshot");
|
|
408
|
+
}
|
|
409
|
+
if (args.replace) {
|
|
410
|
+
if (args.backupBranch !== void 0) {
|
|
411
|
+
throw new UsageError(
|
|
412
|
+
`\`--replace\` cannot be combined with \`--backup-branch\`: a replace deletes the workspace's non-live branches, so it would destroy the snapshot along with everything else and leave you with a rollback target that does not exist.
|
|
413
|
+
Drop \`--replace\` to merge (which a backup CAN protect), or take a workspace export first with \`xanosdk workspace export${contextFlags()}\`.`,
|
|
414
|
+
{ helpFor: { command: "deploy" } }
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
const clearedByReplace = [args.prune ? "--prune" : "", args.resetData ? "--reset-data" : ""].filter(Boolean);
|
|
418
|
+
if (clearedByReplace.length > 0) {
|
|
419
|
+
throw new UsageError(
|
|
420
|
+
`\`--replace\` cannot be combined with ${clearedByReplace.join(" or ")}. A replace already clears every object and every table row, so there is nothing left for those flags to describe. Drop \`--replace\` to merge, or drop ${clearedByReplace.join("/")}.`,
|
|
421
|
+
{ helpFor: { command: "deploy" } }
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
if (args.seed) {
|
|
425
|
+
throw new UsageError(
|
|
426
|
+
"`--replace` cannot be combined with `--seed`, because a replace already writes this project's seed rows \u2014 the archive carries them and the replaced workspace comes back with them. The flag is redundant here, not refused because seeding is impossible.\nDrop `--seed` to keep the replace (your seed rows still land), or drop `--replace` to merge and seed an existing workspace.",
|
|
427
|
+
{ helpFor: { command: "deploy" } }
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
return { mode: "replace", preserveGuids: true };
|
|
431
|
+
}
|
|
432
|
+
return {
|
|
433
|
+
mode: "merge",
|
|
434
|
+
prune: args.prune,
|
|
435
|
+
records: args.seed,
|
|
436
|
+
truncate: args.resetData,
|
|
437
|
+
...branch !== void 0 ? { branch } : {}
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
function derivedBackupLabel(now) {
|
|
441
|
+
return `backup-${derivedLabelTail(now)}`;
|
|
442
|
+
}
|
|
443
|
+
function plannedBackupLabel(flag, branches) {
|
|
444
|
+
const label = assertUsableBranchLabel(
|
|
445
|
+
typeof flag === "string" ? flag : derivedBackupLabel(/* @__PURE__ */ new Date()),
|
|
446
|
+
"--backup-branch",
|
|
447
|
+
"drop the flag to deploy without a snapshot"
|
|
448
|
+
);
|
|
449
|
+
assertBranchAbsent(branches, label, "backup");
|
|
450
|
+
return label;
|
|
451
|
+
}
|
|
452
|
+
function isDestructive(req) {
|
|
453
|
+
return req.mode === "replace" || req.prune === true || req.truncate === true || req.records === true;
|
|
454
|
+
}
|
|
455
|
+
function countByAction(operations) {
|
|
456
|
+
const counts = /* @__PURE__ */ new Map();
|
|
457
|
+
for (const op of operations) {
|
|
458
|
+
counts.set(op.action, (counts.get(op.action) ?? 0) + 1);
|
|
459
|
+
}
|
|
460
|
+
return counts;
|
|
461
|
+
}
|
|
462
|
+
function correspondence(plan) {
|
|
463
|
+
let matched = 0;
|
|
464
|
+
let created = 0;
|
|
465
|
+
let orphaned = 0;
|
|
466
|
+
for (const op of plan.operations) {
|
|
467
|
+
if (IDENTITY_EXEMPT_TYPES.has(op.type)) continue;
|
|
468
|
+
if (op.action === "create") created++;
|
|
469
|
+
else if (op.action === "delete") orphaned++;
|
|
470
|
+
else matched++;
|
|
471
|
+
}
|
|
472
|
+
return { matched, created, orphaned };
|
|
473
|
+
}
|
|
474
|
+
function warnOnZeroCorrespondence(plan, req, targetIsEmpty2, noun = "workspace", sameNamed, dryRun = true, presented = plan.operations) {
|
|
475
|
+
if (req.mode !== "merge") return;
|
|
476
|
+
const { matched, created, orphaned } = correspondence(plan);
|
|
477
|
+
if (matched > 0 || created === 0) return;
|
|
478
|
+
if (targetIsEmpty2) return;
|
|
479
|
+
const objects = (n) => `${n} object${n === 1 ? "" : "s"}`;
|
|
480
|
+
const would = dryRun ? "would" : "will";
|
|
481
|
+
blank();
|
|
482
|
+
if (orphaned > 0) {
|
|
483
|
+
warn(
|
|
484
|
+
`NOTHING MATCHED. ${created === 1 ? "The 1 object" : `All ${objects(created)}`} ${would} be created and ${objects(orphaned)} already in this ${noun} deleted \u2014 not one of them shares an identity with this project.`,
|
|
485
|
+
"plan.nothing-matched",
|
|
486
|
+
[
|
|
487
|
+
`This is not an update. It empties the ${noun} and rebuilds it from scratch.`,
|
|
488
|
+
// "Never released to" was false beside a stale record: the question is what it holds NOW.
|
|
489
|
+
`That is what landing on ${withArticle(noun)} with no current landing of this project looks like.`,
|
|
490
|
+
"To adopt what is already there, pin those objects' guids on your defs first. A prune here is not recoverable."
|
|
491
|
+
]
|
|
492
|
+
);
|
|
493
|
+
} else {
|
|
494
|
+
const settings = presented.some(
|
|
495
|
+
(op) => IDENTITY_EXEMPT_TYPES.has(op.type) && !["create", "delete", "unchanged"].includes(op.action)
|
|
496
|
+
);
|
|
497
|
+
const landing = `Only ${withArticle(noun)} holding a current landing of this project will match.`;
|
|
498
|
+
let line;
|
|
499
|
+
if (sameNamed === void 0) {
|
|
500
|
+
line = `If this ${noun} is already populated, its objects carry different identities and you will end up with two of everything. ${landing}`;
|
|
501
|
+
} else if (sameNamed.length === 0) {
|
|
502
|
+
line = `None of them shares a name with anything already in this ${noun}: they are all new objects, added beside what is there.`;
|
|
503
|
+
} else {
|
|
504
|
+
const shown = sameNamed.slice(0, 5).join(", ") + (sameNamed.length > 5 ? `, \u2026 ${sameNamed.length - 5} more` : "");
|
|
505
|
+
line = `${sameNamed.length === 1 ? "1 of them shares its name" : `${sameNamed.length} of them share a name`} with an object already in this ${noun} under a different identity (${shown}) \u2014 you will end up with two of ${sameNamed.length === 1 ? "it" : "each"}. ${landing}`;
|
|
506
|
+
}
|
|
507
|
+
warn(
|
|
508
|
+
`Nothing matched: ${created === 1 ? "the 1 object" : `all ${objects(created)}`} ${would} be created, ${settings ? `none updated but the ${noun}'s own settings` : "none updated"}.`,
|
|
509
|
+
"plan.nothing-matched",
|
|
510
|
+
[line]
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function sameNamedCreates(plan, presented, live) {
|
|
515
|
+
if (live === void 0) return void 0;
|
|
516
|
+
const payload = payloadOf(live);
|
|
517
|
+
const label = rowLabeler(payload);
|
|
518
|
+
const out = [];
|
|
519
|
+
plan.operations.forEach((op, i) => {
|
|
520
|
+
if (op.action !== "create" || IDENTITY_EXEMPT_TYPES.has(op.type)) return;
|
|
521
|
+
const section = planTypeSection(op.type);
|
|
522
|
+
const rows = payload[section];
|
|
523
|
+
const shown = presented[i]?.label;
|
|
524
|
+
const composed = COMPOSED_IDENTITY_SECTIONS.has(section) && shown !== void 0;
|
|
525
|
+
const held = Array.isArray(rows) && rows.some(
|
|
526
|
+
(r) => r !== null && typeof r === "object" && r.name === op.name && (!composed || label(section, r) === shown)
|
|
527
|
+
);
|
|
528
|
+
if (held) out.push(shown ?? `${op.type}:${op.name}`);
|
|
529
|
+
});
|
|
530
|
+
return out;
|
|
531
|
+
}
|
|
532
|
+
function envCarriedBy(bundle, live) {
|
|
533
|
+
if (live === void 0) return void 0;
|
|
534
|
+
const ours = envValuesOf(bundle);
|
|
535
|
+
const theirs = envValuesOf(live);
|
|
536
|
+
return { names: ours.size, created: [...ours.keys()].filter((name) => !theirs.has(name)) };
|
|
537
|
+
}
|
|
538
|
+
var COMPOSED_IDENTITY_SECTIONS = /* @__PURE__ */ new Set(["query", "channel", "message"]);
|
|
539
|
+
function payloadKeyFor(type) {
|
|
540
|
+
return (Object.hasOwn(REFERENCEABLE_KIND_PAYLOAD_KEYS, type) ? REFERENCEABLE_KIND_PAYLOAD_KEYS[type] : void 0) ?? type;
|
|
541
|
+
}
|
|
542
|
+
function splitPruneDeletes(plan, record, ctx) {
|
|
543
|
+
const rows = deletedRows(plan.operations, ctx);
|
|
544
|
+
const ownedIdentities = {};
|
|
545
|
+
const ownedAs = landedOwnerKey(record, ctx.live);
|
|
546
|
+
const collateral = [];
|
|
547
|
+
const owned = [];
|
|
548
|
+
plan.operations.forEach((op, index) => {
|
|
549
|
+
if (op.action !== "delete") return;
|
|
550
|
+
if (IDENTITY_EXEMPT_TYPES.has(op.type)) return;
|
|
551
|
+
const key = ownedAs(op, rows[index]);
|
|
552
|
+
if (key === void 0) {
|
|
553
|
+
collateral.push(op);
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
owned.push(op);
|
|
557
|
+
ownedIdentities[key] = record[key];
|
|
558
|
+
});
|
|
559
|
+
return { collateral, owned, ownedIdentities };
|
|
560
|
+
}
|
|
561
|
+
function landedOwnerKey(record, live) {
|
|
562
|
+
const liveGroups = groupNamesByRef(live);
|
|
563
|
+
return (op, row) => {
|
|
564
|
+
if (record === void 0) return void 0;
|
|
565
|
+
const payloadKey = payloadKeyFor(op.type);
|
|
566
|
+
const key = row !== void 0 ? landedKeyForRow(payloadKey, { ...row, name: op.name }, liveGroups) : payloadKey === "query" ? void 0 : lockKey(payloadKey, op.name);
|
|
567
|
+
const entry = key === void 0 ? void 0 : record[key];
|
|
568
|
+
if (key === void 0 || entry === void 0) return void 0;
|
|
569
|
+
const guid = typeof row?.guid === "string" && row.guid !== "" ? row.guid : void 0;
|
|
570
|
+
if (guid !== void 0 && entry.guid !== guid) return void 0;
|
|
571
|
+
if (payloadKey === "toolset") {
|
|
572
|
+
const isAgent = row !== void 0 ? row.type === "agent" : op.type === "agent";
|
|
573
|
+
if (entry.type === "agent" !== isAgent) return void 0;
|
|
574
|
+
}
|
|
575
|
+
return key;
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
function pruneProvenance(args, landingKey, live) {
|
|
579
|
+
let record;
|
|
580
|
+
let objects;
|
|
581
|
+
try {
|
|
582
|
+
const lockPath = resolveLockPath(args, args.file ?? ".");
|
|
583
|
+
const lockExists = existsSync(lockPath);
|
|
584
|
+
if (landingKey !== void 0 && (lockExists || isEphemeralDestinationKey(landingKey))) {
|
|
585
|
+
record = landedRecordFor(lockPath, landingKey).record;
|
|
586
|
+
}
|
|
587
|
+
if (lockExists) objects = readLockFile(lockPath).objects;
|
|
588
|
+
} catch {
|
|
589
|
+
}
|
|
590
|
+
const landed = landedOwnerKey(record, live);
|
|
591
|
+
const declared = landedOwnerKey(objects, live);
|
|
592
|
+
return (op, row) => {
|
|
593
|
+
if (record === void 0 && objects === void 0) return void 0;
|
|
594
|
+
const landedKey = landed(op, row);
|
|
595
|
+
if (landedKey !== void 0) return objects?.[landedKey]?.adopted === true ? "adopted" : "landed";
|
|
596
|
+
return declared(op, row) !== void 0 ? "locked" : "never";
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
function groupNamesByRef(bundle) {
|
|
600
|
+
const payload = bundle?.payload;
|
|
601
|
+
return payload !== null && typeof payload === "object" ? groupNames(payload) : /* @__PURE__ */ new Map();
|
|
602
|
+
}
|
|
603
|
+
function assertLiveWorkspaceWasRead(live, destNoun, branch, failure, kind = "workspace") {
|
|
604
|
+
if (live !== void 0) return;
|
|
605
|
+
const scope = branch === void 0 ? "" : ` on branch "${branch}"`;
|
|
606
|
+
const reason = liveReadReason(failure);
|
|
607
|
+
const because = reason === void 0 ? "" : ` (${reason})`;
|
|
608
|
+
if (failure !== void 0 && isUnansweredLookup(failure)) {
|
|
609
|
+
const cause = unansweredCause(failure);
|
|
610
|
+
throw new LookupFailedError(
|
|
611
|
+
`Refusing to deploy into ${destNoun}${scope}: it could not be read${because}, so whether this deploy drops a column or renames the workspace is unknown. ${cause} \u2014 nothing was written`,
|
|
612
|
+
"unreachable",
|
|
613
|
+
kind
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
throw new UsageError(
|
|
617
|
+
`Refusing to deploy into ${destNoun}${scope}: it could not be read${because}, so whether this deploy drops a column or renames the workspace is unknown.
|
|
618
|
+
The import reports a dropped column as a routine table update, and the data in it is not recoverable \u2014 so the one check that would have caught it has to have run.
|
|
619
|
+
Retry when the instance is reachable. \`--dry-run\` still prints the plan and says which checks could not be made.`,
|
|
620
|
+
{ hintFor: { command: "deploy" } }
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
async function readLiveWorkspace(auth, dest, branch) {
|
|
624
|
+
return (await readLive(auth, dest, branch)).live;
|
|
625
|
+
}
|
|
626
|
+
var LIVE_READ_RETRY_PAUSES_MS = [250, 750];
|
|
627
|
+
function isTransientReadFailure(err) {
|
|
628
|
+
const cause = err?.cause;
|
|
629
|
+
if (isTimeoutError(err) || isTimeoutError(cause) || err instanceof TransportError && err.timeout) return false;
|
|
630
|
+
return isUnansweredLookup(err);
|
|
631
|
+
}
|
|
632
|
+
async function readLive(auth, dest, branch) {
|
|
633
|
+
const { exportWorkspaceBundle } = await import("./workspace-export-OJSH2HIW.js");
|
|
634
|
+
let failure;
|
|
635
|
+
for (let attempt = 0; attempt <= LIVE_READ_RETRY_PAUSES_MS.length; attempt++) {
|
|
636
|
+
try {
|
|
637
|
+
const live = await exportWorkspaceBundle(auth, {
|
|
638
|
+
base: dest.base,
|
|
639
|
+
workspaceId: dest.workspaceId,
|
|
640
|
+
label: "reading the target workspace",
|
|
641
|
+
...branch !== void 0 ? { branch } : {}
|
|
642
|
+
});
|
|
643
|
+
return { live };
|
|
644
|
+
} catch (err) {
|
|
645
|
+
failure = err;
|
|
646
|
+
const pause = LIVE_READ_RETRY_PAUSES_MS[attempt];
|
|
647
|
+
if (pause === void 0 || !isTransientReadFailure(err)) break;
|
|
648
|
+
await new Promise((r) => setTimeout(r, pause));
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return { live: void 0, failure };
|
|
652
|
+
}
|
|
653
|
+
function liveReadReason(failure) {
|
|
654
|
+
if (failure === void 0) return void 0;
|
|
655
|
+
const first = ((failure instanceof Error ? failure.message : String(failure)).split("\n")[0] ?? "").trim();
|
|
656
|
+
return first === "" ? void 0 : first.replace(/[.:]$/, "");
|
|
657
|
+
}
|
|
658
|
+
function keepWorkspaceName(bundle, live) {
|
|
659
|
+
const liveWs = live?.payload?.workspace;
|
|
660
|
+
const liveName = typeof liveWs?.name === "string" && liveWs.name !== "" ? liveWs.name : void 0;
|
|
661
|
+
if (liveName === void 0) return bundle;
|
|
662
|
+
let parsed;
|
|
663
|
+
try {
|
|
664
|
+
parsed = JSON.parse(bundle);
|
|
665
|
+
} catch {
|
|
666
|
+
return bundle;
|
|
667
|
+
}
|
|
668
|
+
const ws = parsed?.payload?.workspace;
|
|
669
|
+
if (ws === null || typeof ws !== "object" || Array.isArray(ws) || ws.name === liveName) return bundle;
|
|
670
|
+
const held = signatureHolds(bundle) !== false;
|
|
671
|
+
ws.name = liveName;
|
|
672
|
+
return resigned(parsed, held);
|
|
673
|
+
}
|
|
674
|
+
function resigned(parsed, held) {
|
|
675
|
+
const { sig, ...unsigned } = parsed;
|
|
676
|
+
if (sig === void 0) return JSON.stringify(unsigned);
|
|
677
|
+
return JSON.stringify({ ...unsigned, sig: held ? calcSignatureJson(unsigned) : sig });
|
|
678
|
+
}
|
|
679
|
+
var WORKSPACE_LEVEL_SETTINGS = /* @__PURE__ */ new Set(["name", "description", "preferences", "realtime", "history", "middleware"]);
|
|
680
|
+
var OMITTABLE_WORKSPACE_SETTINGS = ["preferences", "realtime", "history", "middleware"];
|
|
681
|
+
function withholdWorkspaceSettings(bundle, live) {
|
|
682
|
+
let parsed;
|
|
683
|
+
try {
|
|
684
|
+
parsed = JSON.parse(bundle);
|
|
685
|
+
} catch {
|
|
686
|
+
return { bundle, withheld: [] };
|
|
687
|
+
}
|
|
688
|
+
const ws = parsed?.payload?.workspace;
|
|
689
|
+
if (ws === null || ws === void 0 || typeof ws !== "object" || Array.isArray(ws)) return { bundle, withheld: [] };
|
|
690
|
+
const held = signatureHolds(bundle) !== false;
|
|
691
|
+
let withheld = [];
|
|
692
|
+
if (live !== void 0) {
|
|
693
|
+
try {
|
|
694
|
+
const order = [...WORKSPACE_LEVEL_SETTINGS];
|
|
695
|
+
const rank = (path) => order.indexOf(path.split(".")[0]);
|
|
696
|
+
withheld = compareToLive(parsed, live, void 0, { documentation: "skip", sample: "all" }).settingsFields.filter((path) => rank(path) !== -1).sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
|
|
697
|
+
} catch {
|
|
698
|
+
withheld = [];
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
const liveWs = live?.payload?.workspace;
|
|
702
|
+
let changed = false;
|
|
703
|
+
for (const key of ["name", "description"]) {
|
|
704
|
+
const value = liveWs?.[key];
|
|
705
|
+
if (typeof value === "string") {
|
|
706
|
+
if (ws[key] !== value) {
|
|
707
|
+
ws[key] = value;
|
|
708
|
+
changed = true;
|
|
709
|
+
}
|
|
710
|
+
} else if (key === "description" && live !== void 0 && Object.hasOwn(ws, key)) {
|
|
711
|
+
delete ws[key];
|
|
712
|
+
changed = true;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
for (const key of OMITTABLE_WORKSPACE_SETTINGS) {
|
|
716
|
+
if (Object.hasOwn(ws, key)) {
|
|
717
|
+
delete ws[key];
|
|
718
|
+
changed = true;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
if (!changed) return { bundle, withheld };
|
|
722
|
+
return { bundle: resigned(parsed, held), withheld };
|
|
723
|
+
}
|
|
724
|
+
function collisionBranch(branches, target) {
|
|
725
|
+
if (target === void 0) return void 0;
|
|
726
|
+
return branches.some((b) => b.label === target) ? target : void 0;
|
|
727
|
+
}
|
|
728
|
+
function targetIsEmpty(live) {
|
|
729
|
+
if (live === void 0) return false;
|
|
730
|
+
const payload = live.payload;
|
|
731
|
+
if (payload === null || typeof payload !== "object") return false;
|
|
732
|
+
return !Object.entries(payload).some(
|
|
733
|
+
// The workspace settings row is present in every workspace, empty or not.
|
|
734
|
+
([key, value]) => key !== "workspace" && Array.isArray(value) && value.length > 0
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
async function convergedWithLive(live, req, outgoingBundle, pinned) {
|
|
738
|
+
if (live === void 0 || req.mode !== "merge") return false;
|
|
739
|
+
if (req.records === true || req.truncate === true) return false;
|
|
740
|
+
const { compareToLive: compareToLive2 } = await import("./live-diff-YONW2VI3.js");
|
|
741
|
+
let result;
|
|
742
|
+
try {
|
|
743
|
+
result = compareToLive2(
|
|
744
|
+
JSON.parse(outgoingBundle),
|
|
745
|
+
live,
|
|
746
|
+
new Set(pinned.map((p) => `${p.payloadKey}:${p.name}`)),
|
|
747
|
+
// A merge does not write the `documentation` block, so it can never be a
|
|
748
|
+
// reason to send; `warnAboutLiveLosses` says what stays behind instead.
|
|
749
|
+
{ documentation: "skip" }
|
|
750
|
+
);
|
|
751
|
+
} catch {
|
|
752
|
+
return false;
|
|
753
|
+
}
|
|
754
|
+
if (!result.converged) return false;
|
|
755
|
+
if (req.prune === true && result.liveOnly.length > 0) return false;
|
|
756
|
+
return true;
|
|
757
|
+
}
|
|
758
|
+
async function unchangedWithLive(live, req, outgoingBundle, pinned) {
|
|
759
|
+
if (live === void 0 || req.mode !== "merge") return void 0;
|
|
760
|
+
if (req.records === true || req.truncate === true) return void 0;
|
|
761
|
+
const { compareToLive: compareToLive2, declaredLabels, payloadOf: payloadOf2, SETTINGS_LABEL } = await import("./live-diff-YONW2VI3.js");
|
|
762
|
+
try {
|
|
763
|
+
const outgoing = JSON.parse(outgoingBundle);
|
|
764
|
+
const result = compareToLive2(outgoing, live, new Set(pinned.map((p) => `${p.payloadKey}:${p.name}`)), {
|
|
765
|
+
documentation: "skip",
|
|
766
|
+
sample: "all"
|
|
767
|
+
});
|
|
768
|
+
const changed = /* @__PURE__ */ new Set([...result.differing, ...result.missing]);
|
|
769
|
+
const declared = declaredLabels(outgoing);
|
|
770
|
+
const once = declared.filter((l, i) => declared.indexOf(l) === i && declared.lastIndexOf(l) === i);
|
|
771
|
+
const settings = "workspace" in payloadOf2(outgoing) && !changed.has(SETTINGS_LABEL);
|
|
772
|
+
return { labels: new Set(once.filter((l) => !changed.has(l))), settings };
|
|
773
|
+
} catch {
|
|
774
|
+
return void 0;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
function markUnchanged(presented, unchanged, noun) {
|
|
778
|
+
if (unchanged === void 0) return [...presented];
|
|
779
|
+
return presented.map((op) => {
|
|
780
|
+
if (op.action !== "update") return op;
|
|
781
|
+
const same = op.type === "workspace" ? unchanged.settings : unchanged.labels.has(op.label);
|
|
782
|
+
if (!same) return op;
|
|
783
|
+
const { details: _d, reason: _r, ...rest } = op;
|
|
784
|
+
return { ...rest, action: "unchanged", details: `Already matches the ${noun}; nothing about it changes` };
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
var REPLACE_DROPS = "which deletes every table's rows there (only seed rows come back)";
|
|
788
|
+
async function replaceCommandHint(args) {
|
|
789
|
+
const { retryCommand: retryCommand2, withheldNote: withheldNote2 } = await import("./deploy-command-JVZPWHMK.js");
|
|
790
|
+
const valued = /* @__PURE__ */ new Set(["--branch", "--backup-branch", "--expect-live"]);
|
|
791
|
+
const dropped = /* @__PURE__ */ new Set(["--prune", "--reset-data", "--seed", "--set-live", "--dry-run", "--replace", "--yes", "-y"]);
|
|
792
|
+
const argv = [];
|
|
793
|
+
const typed = args.argv ?? [];
|
|
794
|
+
for (let i = 0; i < typed.length; i++) {
|
|
795
|
+
const spelling = typed[i].split("=")[0];
|
|
796
|
+
if (valued.has(spelling)) {
|
|
797
|
+
if (spelling === typed[i]) i += 1;
|
|
798
|
+
} else if (!dropped.has(spelling)) argv.push(typed[i]);
|
|
799
|
+
}
|
|
800
|
+
const { command, withheld } = retryCommand2(args.argv === void 0 ? args : { ...args, argv }, { add: ["--replace"] });
|
|
801
|
+
return `\`${command}\`.${withheldNote2(withheld)}`;
|
|
802
|
+
}
|
|
803
|
+
function landingTableFields(losses) {
|
|
804
|
+
const { droppedColumns, retypedColumns, narrowedEnums, retargetedRefs, indexChanges, notNullKept, notNullTightened, renamedTables, pairedColumns } = losses;
|
|
805
|
+
return {
|
|
806
|
+
renamedTables,
|
|
807
|
+
droppedColumns,
|
|
808
|
+
// The dropped-and-added pairs the column-drop warning names, as `--keep-data` carries them.
|
|
809
|
+
...pairedColumns.length > 0 ? { pairedColumns } : {},
|
|
810
|
+
notNullKept,
|
|
811
|
+
retypedColumns,
|
|
812
|
+
narrowedEnums,
|
|
813
|
+
retargetedRefs,
|
|
814
|
+
indexChanges,
|
|
815
|
+
notNullTightened,
|
|
816
|
+
storageChanges: storageChangesPayload(losses.storageChanges)
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
async function warnAboutLiveLosses(live, req, outgoingBundle, setWhere = "workspace", fromBundle = false, replaceHint = "`xanosdk deploy <source> --to <destination> --replace`.", noun = "workspace", failure, stored) {
|
|
820
|
+
const { NO_TABLE_EFFECTS, tableEffectsOf, discloseTableEffects, tableEffectFields, alteringChanges, droppedAndAdded } = await import("./plan-disclosure-6G4MLZKD.js");
|
|
821
|
+
const none = {
|
|
822
|
+
...tableEffectFields(NO_TABLE_EFFECTS),
|
|
823
|
+
renamedTables: [],
|
|
824
|
+
pairedColumns: [],
|
|
825
|
+
unchangedEnv: [],
|
|
826
|
+
droppedEnv: [],
|
|
827
|
+
unappliedDocumentation: [],
|
|
828
|
+
storageChanges: []
|
|
829
|
+
};
|
|
830
|
+
if (live === void 0) {
|
|
831
|
+
const reason = liveReadReason(failure);
|
|
832
|
+
warn(
|
|
833
|
+
`Could not read the target ${noun}${reason === void 0 ? "" : ` (${reason})`}, so this plan does not account for dropped columns or env that will not update.`,
|
|
834
|
+
"plan.target-unreadable"
|
|
835
|
+
);
|
|
836
|
+
blank();
|
|
837
|
+
return none;
|
|
838
|
+
}
|
|
839
|
+
const { diffAgainstLive, tableAdditions } = await import("./live-diff-YONW2VI3.js");
|
|
840
|
+
let diff;
|
|
841
|
+
let additions;
|
|
842
|
+
try {
|
|
843
|
+
const outgoing = JSON.parse(outgoingBundle);
|
|
844
|
+
diff = diffAgainstLive(outgoing, live, { mode: req.mode, ...stored === void 0 ? {} : { stored } });
|
|
845
|
+
additions = tableAdditions(outgoing, live);
|
|
846
|
+
} catch {
|
|
847
|
+
return none;
|
|
848
|
+
}
|
|
849
|
+
const effects = tableEffectsOf(diff, additions, { mode: req.mode === "replace" ? "rebuild" : "merge" });
|
|
850
|
+
discloseTableEffects(effects, {
|
|
851
|
+
subject: "the deploy",
|
|
852
|
+
stages: "deploys",
|
|
853
|
+
remedies: {
|
|
854
|
+
"plan.column-drop": "The plan reports these as a routine in-place table update; they are not recoverable.",
|
|
855
|
+
"plan.not-null-kept": `Later plans cannot see it. A replace recreates the table, which relaxes it \u2014 and replaces its rows: ${replaceHint}`,
|
|
856
|
+
"plan.not-null-tightened": `A replace recreates the table \u2014 and replaces its rows: ${replaceHint}`
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
if (alteringChanges(effects).length > 0) blank();
|
|
860
|
+
if (diff.unchangedEnv.length > 0) {
|
|
861
|
+
warn(
|
|
862
|
+
`${diff.unchangedEnv.length} environment variable${diff.unchangedEnv.length === 1 ? "" : "s"} will NOT be updated: ${diff.unchangedEnv.map((e) => e.name).join(", ")}`,
|
|
863
|
+
"plan.env-kept",
|
|
864
|
+
[
|
|
865
|
+
"A merge is add-only for env: it creates keys that do not exist and leaves existing ones as they are.",
|
|
866
|
+
// The command, not "set them in tenant … directly" (E2E pass 23).
|
|
867
|
+
// `--yes`: a workspace or tenant write asks first, and off a terminal
|
|
868
|
+
// refuses without it — the printed command has to run as printed.
|
|
869
|
+
`To change these values, run \`printf %s "$VALUE" | xanosdk env set NAME --to ${shellQuote(setWhere)}${noun === "ephemeral" ? "" : " --yes"}${contextFlags()}\` for each, or rebuild the ${noun} with a replace, ${REPLACE_DROPS}: ${replaceHint}`
|
|
870
|
+
]
|
|
871
|
+
);
|
|
872
|
+
blank();
|
|
873
|
+
}
|
|
874
|
+
if (diff.droppedEnv.length > 0) {
|
|
875
|
+
const one = diff.droppedEnv.length === 1;
|
|
876
|
+
warn(
|
|
877
|
+
`${diff.droppedEnv.length} environment variable${one ? "" : "s"} will be DROPPED: ${safeNames(diff.droppedEnv.map((e) => e.name))}`,
|
|
878
|
+
"plan.env-drop",
|
|
879
|
+
// A `--bundle` sends that file's env set, not this project's: worded by
|
|
880
|
+
// what the BUNDLE carries (E2E pass 13 L3) — "not declared in this
|
|
881
|
+
// project" was false for a name the project declares and the bundle lacks.
|
|
882
|
+
fromBundle ? [
|
|
883
|
+
`A replace rebuilds the ${noun} from the archive, so its env set becomes exactly what the bundle carries.`,
|
|
884
|
+
`${one ? "This one is" : "These are"} set on the target and not carried by the bundle.`,
|
|
885
|
+
`Send a bundle that carries ${one ? "it" : "them"} to keep ${one ? "it" : "them"}, or deploy without \`--replace\` (a merge leaves existing values alone).`
|
|
886
|
+
] : [
|
|
887
|
+
`A replace rebuilds the ${noun} from the archive, so its env set becomes exactly what \`workspaceConfig({ env })\` declares.`,
|
|
888
|
+
`${one ? "This one is" : "These are"} set on the target and not declared in this project \u2014 added through the UI, or by an earlier deploy \u2014 so nothing else here mentions ${one ? "it" : "them"}.`,
|
|
889
|
+
`Declare ${one ? "the name" : "the names"} to keep ${one ? "it" : "them"}, or deploy without \`--replace\` (a merge leaves existing values alone).`
|
|
890
|
+
]
|
|
891
|
+
);
|
|
892
|
+
blank();
|
|
893
|
+
}
|
|
894
|
+
if (diff.unappliedDocumentation.length > 0) {
|
|
895
|
+
const what = diff.unappliedDocumentation.includes("require_token") ? diff.unappliedDocumentation.includes("token") ? "documentation gate and token" : "documentation gate" : "documentation token";
|
|
896
|
+
warn(
|
|
897
|
+
`The workspace ${what} will NOT be updated.`,
|
|
898
|
+
"plan.doc-not-updated",
|
|
899
|
+
[
|
|
900
|
+
"A merge does not write the workspace's `documentation` block; the live one stays as it is.",
|
|
901
|
+
`To apply it, rebuild the ${noun} with a replace, ${REPLACE_DROPS}: ${replaceHint}`
|
|
902
|
+
]
|
|
903
|
+
);
|
|
904
|
+
blank();
|
|
905
|
+
}
|
|
906
|
+
if (diff.workspaceRename !== void 0) {
|
|
907
|
+
warn(
|
|
908
|
+
`This workspace will be RENAMED: "${diff.workspaceRename.from}" \u2192 "${diff.workspaceRename.to}"`,
|
|
909
|
+
"plan.workspace-rename",
|
|
910
|
+
[
|
|
911
|
+
"An import carries the workspace's own settings, so deploying a project renames the workspace it lands in.",
|
|
912
|
+
// A `--bundle` has no project source to edit (E2E pass 20): its name is what lands.
|
|
913
|
+
fromBundle ? `The bundle's name replaces the workspace's. To keep "${diff.workspaceRename.from}", send a bundle exported under that name.` : `To keep the current name, change the name in \`workspace("\u2026")\` to "${diff.workspaceRename.from}".`
|
|
914
|
+
]
|
|
915
|
+
);
|
|
916
|
+
blank();
|
|
917
|
+
}
|
|
918
|
+
return {
|
|
919
|
+
...tableEffectFields(effects),
|
|
920
|
+
// The plan's "rename table … (rows kept)" lines, for the `--json` reader.
|
|
921
|
+
renamedTables: req.mode === "merge" ? tableRenames(JSON.parse(outgoingBundle), live) : [],
|
|
922
|
+
pairedColumns: droppedAndAdded(effects),
|
|
923
|
+
unchangedEnv: diff.unchangedEnv.map((e) => e.name),
|
|
924
|
+
droppedEnv: diff.droppedEnv.map((e) => e.name),
|
|
925
|
+
unappliedDocumentation: [...diff.unappliedDocumentation],
|
|
926
|
+
...diff.workspaceRename !== void 0 ? { workspaceRename: diff.workspaceRename } : {},
|
|
927
|
+
storageChanges: effects.storageChanges ?? []
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
function assertPruneStaysInScope(args, plan, where, ctx = { bundle: void 0, live: void 0, tenant: false }, presented = presentOperations(plan.operations, ctx), refusal) {
|
|
931
|
+
const ephemeral = where.key !== void 0 && isEphemeralDestinationKey(where.key);
|
|
932
|
+
if (!ephemeral && args.lockPath === void 0 && args.file === void 0) {
|
|
933
|
+
throw new UsageError(
|
|
934
|
+
"`--prune` needs a lock file to know which objects this project landed here, and `--bundle` carries none. Pass `--lock=<path>` to name the project's lock, release from the entry file instead, or drop `--prune`.",
|
|
935
|
+
{ helpFor: { command: "deploy" } }
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
const lockPath = resolveLockPath(args, args.file ?? ".");
|
|
939
|
+
const noLock = !ephemeral && !existsSync(lockPath);
|
|
940
|
+
const kept = where.key === void 0 ? void 0 : noLock ? { record: void 0, file: displayPath(lockPath), local: false } : landedRecordFor(lockPath, where.key);
|
|
941
|
+
const record = kept?.record;
|
|
942
|
+
const { collateral, owned, ownedIdentities } = splitPruneDeletes(plan, record, ctx);
|
|
943
|
+
if (collateral.length === 0 && noLock) {
|
|
944
|
+
throw refusalError(
|
|
945
|
+
refusal?.frame,
|
|
946
|
+
"pruneOutOfScope",
|
|
947
|
+
`\`--prune\` is unavailable here: there is no lock at ${displayPath(lockPath)}, so no landing record for ${where.noun}${where.key === void 0 ? "" : ` (${where.key})`} to say what this project owns there. Nothing was written. Drop \`--prune\`${args.noLock ? " and keep `--no-lock`" : ""}, or deploy here once without \`--prune\`${args.noLock ? " or `--no-lock`" : ""} \u2014 that writes the lock and records what it lands.`,
|
|
948
|
+
{ branch: refusal?.branch, outOfScope: [] }
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
if (collateral.length === 0) return ownedIdentities;
|
|
952
|
+
const lines = collateral.map((op) => {
|
|
953
|
+
const shown = presented[plan.operations.indexOf(op)];
|
|
954
|
+
const reason = shown?.reason;
|
|
955
|
+
return ` ${shown?.label ?? `${op.type}:${op.name}`}${reason !== void 0 ? ` \u2014 ${reason}` : ""}`;
|
|
956
|
+
});
|
|
957
|
+
const n = collateral.length;
|
|
958
|
+
const outOfScope = collateral.map((op) => {
|
|
959
|
+
const shown = presented[plan.operations.indexOf(op)];
|
|
960
|
+
return {
|
|
961
|
+
type: shown?.type ?? op.type,
|
|
962
|
+
name: op.name,
|
|
963
|
+
label: shown?.label ?? `${op.type}:${op.name}`,
|
|
964
|
+
...shown?.reason === void 0 ? {} : { reason: shown.reason }
|
|
965
|
+
};
|
|
966
|
+
});
|
|
967
|
+
const lastLine = ephemeral ? `To keep the objects above, drop \`--prune\`. If one of them is this project's, it was landed from another checkout of it (${kept?.file ?? ".xano/ephemeral.json"} is kept per project, on this machine) or before the record existed: delete it by hand, or define it again, deploy here, and remove it once the landing is recorded.` : `To keep the objects above, drop \`--prune\`. If one of them is this project's, it was landed before the record existed or from a lock that was not committed: delete it by hand, or define it again, deploy, and remove it once the landing is recorded.`;
|
|
968
|
+
throw refusalError(
|
|
969
|
+
refusal?.frame,
|
|
970
|
+
"pruneOutOfScope",
|
|
971
|
+
`\`--prune\` would delete ${n} object${n === 1 ? "" : "s"} this project never landed on ${where.noun}:
|
|
972
|
+
${lines.join("\n")}
|
|
973
|
+
|
|
974
|
+
\`--prune\` deletes only what this project put here, as ${kept?.file ?? displayPath(lockPath)} records it per destination \u2014 but the endpoint deletes everything absent from the bundle, including objects another project or a person put there. Sharing a name with an object in this project's lock does not make it this project's. Dropping a populated table is not recoverable.
|
|
975
|
+
` + (record === void 0 ? noLock ? `There is no lock at ${displayPath(lockPath)}, so no landing record for ${where.noun}${where.key === void 0 ? "" : ` (${where.key})`}. Deploy here once without \`--prune\`${args.noLock ? " or `--no-lock`" : ""} \u2014 that writes the lock and records what it lands \u2014 and commit the lock. A lock written by \`xanosdk export\` records no landing, so a prune would still be refused.
|
|
976
|
+
` : kept?.local === true ? `${kept.file} has no landing record for ${where.noun} (${where.key}): this project has not deployed to it (not from this checkout), or its record was cleared when a release or another backend's copy that is not this project's replaced what it serves. Deploy here once without \`--prune\` \u2014 that records what it lands.
|
|
977
|
+
` : `The lock has no landing record for ${where.noun} (${where.key ?? "unknown destination"}): this project has not landed here since the lock started recording landings, this is a fresh clone of a lock whose record was never committed, or the record was cleared when ${clearedBy(where.key)}. Deploy here once without \`--prune\` \u2014 that records what it lands \u2014 and commit the lock.
|
|
978
|
+
` : owned.length > 0 ? `The ${owned.length} deletion${owned.length === 1 ? "" : "s"} this project did land here would have been correct.
|
|
979
|
+
` : "") + lastLine,
|
|
980
|
+
// No `--help` pointer: the flags are fine, and the help cannot say which
|
|
981
|
+
// objects this project landed. The message above is the whole answer.
|
|
982
|
+
{ branch: refusal?.branch, outOfScope }
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
function clearedBy(key) {
|
|
986
|
+
if (key !== void 0 && /\/workspace\/[^/]+$/.test(key)) {
|
|
987
|
+
return "a `deploy --to workspace --replace` of a bundle that is not this project's replaced what it serves";
|
|
988
|
+
}
|
|
989
|
+
if (key !== void 0 && /\/tenant\/[^/]+$/.test(key)) {
|
|
990
|
+
return "a release or a bundle that is not this project's replaced what it serves (`tenant deploy`, or `deploy --to tenant:<name> --replace --bundle`)";
|
|
991
|
+
}
|
|
992
|
+
return "a release or another backend's copy that is not this project's replaced what it serves";
|
|
993
|
+
}
|
|
994
|
+
function seedWritePhrase(t) {
|
|
995
|
+
const rows = `${t.rows} ${t.rows === 1 ? "row" : "rows"}`;
|
|
996
|
+
if (t.overwrites === null) return `${rows} written; how many overwrite a live row could not be read`;
|
|
997
|
+
return `${rows} written, ${t.overwrites} of them overwriting ${t.overwrites === 1 ? "a live row" : "live rows"}`;
|
|
998
|
+
}
|
|
999
|
+
function seedOverwriteClause(seeded) {
|
|
1000
|
+
const hit = seeded.filter((t) => t.overwrites !== null && t.overwrites > 0);
|
|
1001
|
+
const total = hit.reduce((n, t) => n + t.overwrites, 0);
|
|
1002
|
+
const unread = seeded.filter((t) => t.overwrites === null).map((t) => t.table);
|
|
1003
|
+
const parts = [
|
|
1004
|
+
total > 0 ? ` It overwrites ${total} live ${total === 1 ? "row" : "rows"} by id (${hit.map((t) => `${t.table}: ${t.overwrites}`).join(", ")}).` : "",
|
|
1005
|
+
unread.length > 0 ? ` The live rows of ${unread.join(", ")} could not be read, so what it overwrites there is unknown.` : ""
|
|
1006
|
+
];
|
|
1007
|
+
return parts.join("");
|
|
1008
|
+
}
|
|
1009
|
+
async function seedWrites(auth, archive, bundle, dest) {
|
|
1010
|
+
const byGuid = seedRowsByTableGuid(archiveSeedContent(archive));
|
|
1011
|
+
if (byGuid.size === 0) return [];
|
|
1012
|
+
const tables = bundle?.payload?.dbo;
|
|
1013
|
+
const names = /* @__PURE__ */ new Map();
|
|
1014
|
+
for (const t of Array.isArray(tables) ? tables : []) {
|
|
1015
|
+
if (typeof t.guid === "string" && typeof t.name === "string") names.set(t.guid, t.name);
|
|
1016
|
+
}
|
|
1017
|
+
const listed = await listTables(auth, { workspaceId: dest.workspaceId, base: dest.base }).catch(() => void 0);
|
|
1018
|
+
const out = [];
|
|
1019
|
+
for (const [guid, rows] of byGuid) {
|
|
1020
|
+
if (rows.length === 0) continue;
|
|
1021
|
+
const table = names.get(guid) ?? guid;
|
|
1022
|
+
const here = listed?.find((l) => l.guid === guid) ?? listed?.find((l) => l.name === table);
|
|
1023
|
+
const overwrites = listed === void 0 ? null : here === void 0 ? 0 : await tableRowIds(auth, { workspaceId: dest.workspaceId, tableId: here.id, base: dest.base }).then(
|
|
1024
|
+
(ids) => rows.filter((r) => r.id !== void 0 && r.id !== null && ids.has(String(r.id))).length,
|
|
1025
|
+
() => null
|
|
1026
|
+
);
|
|
1027
|
+
out.push({ table, rows: rows.length, overwrites });
|
|
1028
|
+
}
|
|
1029
|
+
return out;
|
|
1030
|
+
}
|
|
1031
|
+
function renderPlan(plan, req, workspaceId, targetIsEmpty2, converged = false, listing = { branches: [], unaddressable: [] }, branchReadFailed = false, tenantLabel, presented = presentOperations(plan.operations, {
|
|
1032
|
+
bundle: void 0,
|
|
1033
|
+
live: void 0,
|
|
1034
|
+
tenant: tenantLabel !== void 0
|
|
1035
|
+
}), noun = tenantLabel === void 0 ? "workspace" : "tenant", sameNamed, envCarried, dryRun = true, renamed = [], seeded = []) {
|
|
1036
|
+
const counts = countByAction(presented);
|
|
1037
|
+
const label = plan.workspaceName !== void 0 ? `${plan.workspaceName} (#${workspaceId})` : `#${workspaceId}`;
|
|
1038
|
+
blank();
|
|
1039
|
+
const onBranch = tenantLabel === void 0 && req.branch !== void 0 ? ` branch ${JSON.stringify(req.branch)}` : "";
|
|
1040
|
+
step(tenantLabel !== void 0 ? `Plan for ${tenantLabel}` : `Plan for workspace ${label}${onBranch}`);
|
|
1041
|
+
if (converged) {
|
|
1042
|
+
detail(
|
|
1043
|
+
`no changes \u2014 every object this project defines already matches the ${noun}`
|
|
1044
|
+
);
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
const order = ["create", "update", "delete", "truncate", "drop"];
|
|
1048
|
+
const seen = [...order.filter((a) => counts.has(a)), ...[...counts.keys()].filter((a) => !order.includes(a))];
|
|
1049
|
+
if (seen.length === 0) {
|
|
1050
|
+
detail("no changes");
|
|
1051
|
+
} else {
|
|
1052
|
+
for (const action of seen) {
|
|
1053
|
+
detail(`${action}: ${counts.get(action)}`);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
if (req.mode === "merge") {
|
|
1057
|
+
for (const { from, to } of renamed) detail(`The merge ${dryRun ? "would" : "will"} rename table ${from} \u2192 ${to} (rows kept).`);
|
|
1058
|
+
}
|
|
1059
|
+
const destructive = presented.filter((op) => DESTRUCTIVE_ACTIONS.has(op.action));
|
|
1060
|
+
if (destructive.length > 0) {
|
|
1061
|
+
blank();
|
|
1062
|
+
warn(
|
|
1063
|
+
`${destructive.length} destructive operation${destructive.length === 1 ? "" : "s"}:`,
|
|
1064
|
+
"deploy.destructive",
|
|
1065
|
+
destructive.map((op) => `${op.action} ${op.label}${op.reason !== void 0 ? ` \u2014 ${op.reason}` : ""}`)
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
if (plan.hasRecords) {
|
|
1069
|
+
blank();
|
|
1070
|
+
if (seeded.length === 0) {
|
|
1071
|
+
warn(`Table rows from the bundle ${dryRun ? "would be" : "WILL be"} written.`, "deploy.rows-written");
|
|
1072
|
+
} else {
|
|
1073
|
+
warn(
|
|
1074
|
+
`Table rows from the bundle ${dryRun ? "would be" : "WILL be"} written \u2014 each by its id, overwriting the row the ${noun} holds at that id:`,
|
|
1075
|
+
"deploy.rows-written",
|
|
1076
|
+
[
|
|
1077
|
+
...seeded.map((t) => `table ${t.table}: ${seedWritePhrase(t)}`),
|
|
1078
|
+
"A seed row authored without an `id` is numbered by its position (1, 2, \u2026), so it lands on that id too."
|
|
1079
|
+
]
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
blank();
|
|
1084
|
+
if (plan.hasEnv && req.mode === "merge" && envCarried !== void 0 && envCarried.names > 0 && envCarried.created.length === 0) {
|
|
1085
|
+
detail(
|
|
1086
|
+
`Every environment variable the bundle carries already exists on the ${noun} \u2014 a merge is add-only, so none is written and existing keys are NOT updated.`
|
|
1087
|
+
);
|
|
1088
|
+
} else if (plan.hasEnv) {
|
|
1089
|
+
warn(
|
|
1090
|
+
`Environment variables from the bundle ${dryRun ? "would be" : "WILL be"} written.`,
|
|
1091
|
+
"deploy.env-written",
|
|
1092
|
+
req.mode === "merge" ? ["Merge is add-only: keys that do not exist are created; existing keys are NOT updated."] : []
|
|
1093
|
+
);
|
|
1094
|
+
} else if (req.mode === "replace") {
|
|
1095
|
+
warn(
|
|
1096
|
+
`The archive carries no environment variables, and \`--replace\` clears the ${noun}'s env \u2014 it comes back with none.`,
|
|
1097
|
+
"release.no-env"
|
|
1098
|
+
);
|
|
1099
|
+
} else {
|
|
1100
|
+
detail("The archive carries no environment variables \u2014 existing env is left untouched.");
|
|
1101
|
+
}
|
|
1102
|
+
const notes = (Array.isArray(plan.summary.notes) ? plan.summary.notes : []).filter(
|
|
1103
|
+
(note) => typeof note === "string"
|
|
1104
|
+
);
|
|
1105
|
+
if (notes.length > 0) {
|
|
1106
|
+
blank();
|
|
1107
|
+
for (const note of notes) warn(note, "plan.server-note");
|
|
1108
|
+
}
|
|
1109
|
+
if (req.mode === "replace") {
|
|
1110
|
+
blank();
|
|
1111
|
+
warn(
|
|
1112
|
+
tenantLabel !== void 0 ? `\`--replace\` wipes ${tenantLabel} before importing \u2014 including table data and history.` : "`--replace` wipes this workspace before importing \u2014 including table data, history, and every one of its branches.",
|
|
1113
|
+
"deploy.replace-wipe"
|
|
1114
|
+
);
|
|
1115
|
+
if (branchReadFailed) {
|
|
1116
|
+
blank();
|
|
1117
|
+
warn(
|
|
1118
|
+
"The branch inventory could not be read, so what this wipe destroys is UNKNOWN.",
|
|
1119
|
+
"branch.inventory-unknown",
|
|
1120
|
+
["`--allow-branch-deletion` accepted the deletion; it did not establish what is lost."]
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
const doomed = branchReadFailed ? { named: [], unnamed: 0, total: 0, backups: 0, authored: 0 } : doomedBranches(listing);
|
|
1124
|
+
if (doomed.total > 0) {
|
|
1125
|
+
blank();
|
|
1126
|
+
warn(`${doomedHeadline(doomed)} will be permanently deleted and CANNOT be recovered:`, "branch.deletion", branchDisclosureLines(doomed));
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
warnOnZeroCorrespondence(plan, req, targetIsEmpty2, noun, sameNamed, dryRun, presented);
|
|
1130
|
+
blank();
|
|
1131
|
+
}
|
|
1132
|
+
var BRANCH_DISCLOSURE_LIMIT = 10;
|
|
1133
|
+
function doomedBranches(listing) {
|
|
1134
|
+
const named = listing.branches.filter((b) => b.label !== DEFAULT_BRANCH_LABEL);
|
|
1135
|
+
const unnamedRows = listing.unaddressable;
|
|
1136
|
+
const backups = named.filter((b) => b.backup).length + unnamedRows.filter((b) => b.backup).length;
|
|
1137
|
+
const total = named.length + unnamedRows.length;
|
|
1138
|
+
return { named, unnamed: unnamedRows.length, total, backups, authored: total - backups };
|
|
1139
|
+
}
|
|
1140
|
+
function doomedHeadline(doomed) {
|
|
1141
|
+
const noun = `branch${doomed.total === 1 ? "" : "es"}`;
|
|
1142
|
+
const counted = `${doomed.total} ${noun} besides ${DEFAULT_BRANCH_LABEL}`;
|
|
1143
|
+
if (doomed.backups === 0 || doomed.authored === 0) return counted;
|
|
1144
|
+
return `${counted} \u2014 ${doomed.authored} yours, ${doomed.backups} platform backup${doomed.backups === 1 ? "" : "s"}`;
|
|
1145
|
+
}
|
|
1146
|
+
function branchDisclosureLines(doomed) {
|
|
1147
|
+
const shown = doomed.named.slice(0, BRANCH_DISCLOSURE_LIMIT);
|
|
1148
|
+
const lines = shown.map(
|
|
1149
|
+
(b) => `${shownLabel(b.label)}${b.live ? " (live \u2014 the deploy lands on " + DEFAULT_BRANCH_LABEL + " instead)" : ""}${b.backup ? " (platform backup)" : ""}`
|
|
1150
|
+
);
|
|
1151
|
+
const rest = doomed.named.length - shown.length;
|
|
1152
|
+
if (rest > 0) {
|
|
1153
|
+
lines.push(`\u2026 and ${rest} more \u2014 \`xanosdk workspace branch list${contextFlags()}\` prints the full inventory`);
|
|
1154
|
+
}
|
|
1155
|
+
if (doomed.unnamed > 0) {
|
|
1156
|
+
lines.push(
|
|
1157
|
+
doomed.unnamed === 1 ? `1 branch with no label, which cannot be named here or exported \u2014 it is deleted all the same` : `${doomed.unnamed} branches with no label, which cannot be named here or exported \u2014 they are deleted all the same`
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
return lines;
|
|
1161
|
+
}
|
|
1162
|
+
function branchDeletionReport(req, listing, readFailed, destHasBranches) {
|
|
1163
|
+
if (req.mode !== "replace" || !destHasBranches) return void 0;
|
|
1164
|
+
if (readFailed) return { unknown: true };
|
|
1165
|
+
const doomed = doomedBranches(listing);
|
|
1166
|
+
return {
|
|
1167
|
+
unknown: false,
|
|
1168
|
+
total: doomed.total,
|
|
1169
|
+
authored: doomed.authored,
|
|
1170
|
+
platformBackups: doomed.backups,
|
|
1171
|
+
unlabeled: doomed.unnamed,
|
|
1172
|
+
labels: doomed.named.map((b) => ({ label: b.label, backup: b.backup }))
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
function assertBranchDeletionAcknowledged(args, req, listing, readError, workspaceId) {
|
|
1176
|
+
if (req.mode !== "replace") return;
|
|
1177
|
+
if (args.allowBranchDeletion) return;
|
|
1178
|
+
if (readError !== void 0) {
|
|
1179
|
+
throw new UsageError(
|
|
1180
|
+
`Refusing to \`--replace\` workspace #${workspaceId}: its branch list could not be read, so whether this wipe destroys other branches is unknown.
|
|
1181
|
+
A replace deletes every branch in the workspace and none of them can be recovered. A failed read is not evidence that there are none.
|
|
1182
|
+
Retry when the instance is reachable, or pass \`--allow-branch-deletion\` to proceed anyway.`,
|
|
1183
|
+
{ hintFor: { command: "deploy" } }
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
const doomed = doomedBranches(listing);
|
|
1187
|
+
if (doomed.total === 0) return;
|
|
1188
|
+
const listed = branchDisclosureLines(doomed).map((l) => ` - ${l}`).join("\n");
|
|
1189
|
+
const liveDoomed = doomed.named.find((b) => b.live);
|
|
1190
|
+
const copy = doomed.named.length === 0 ? `A label-less branch cannot be exported, because every branch route addresses a branch by label` : liveDoomed !== void 0 ? `Take a copy of each one first \u2014 \`xanosdk workspace export --branch <label>${contextFlags()}\`, once per branch. The live branch, ${JSON.stringify(liveDoomed.label)}, is deleted too: back it up with \`xanosdk workspace export --branch ${shellWord(liveDoomed.label)}${contextFlags()}\`` : `Take a copy of each one first \u2014 \`xanosdk workspace export --branch <label>${contextFlags()}\`, once per branch (a bare \`workspace export\` reads only the live branch, ${DEFAULT_BRANCH_LABEL}, which the replace keeps but overwrites)`;
|
|
1191
|
+
throw new UsageError(
|
|
1192
|
+
`Refusing to \`--replace\` workspace #${workspaceId}: it has ${doomedHeadline(doomed)}, and the clear deletes branches by WORKSPACE.
|
|
1193
|
+
These will be permanently deleted and CANNOT be recovered:
|
|
1194
|
+
${listed}
|
|
1195
|
+
|
|
1196
|
+
\`--yes\` does not cover this \u2014 it waives the confirmation, not the loss.
|
|
1197
|
+
${copy}.
|
|
1198
|
+
Or drop \`--replace\` to merge (which leaves other branches untouched), or pass \`--allow-branch-deletion\` to accept the deletion.`,
|
|
1199
|
+
// A one-line pointer, as every refusal of what the TARGET holds gets here:
|
|
1200
|
+
// the flags parsed fine, and a usage block under this list would bury it.
|
|
1201
|
+
{ hintFor: { command: "deploy" } }
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
function assertSharedSchemaAcknowledged(args, live, bundle, branch) {
|
|
1205
|
+
const changes = gatedSchemaChanges(sharedSchemaChanges(JSON.parse(bundle), live, { prune: args.prune }));
|
|
1206
|
+
const waive = retryCommand(args, { add: ["--allow-shared-schema-changes"] });
|
|
1207
|
+
const waived = `\`${waive.command}\`${withheldNote(waive.withheld)}`;
|
|
1208
|
+
if (changes === void 0) {
|
|
1209
|
+
if (args.allowSharedSchemaChanges) return void 0;
|
|
1210
|
+
return refuseOrReport(
|
|
1211
|
+
args,
|
|
1212
|
+
`Refusing to deploy to branch "${branch}": the live workspace could not be read, so whether this deploy changes shared schema is unknown.
|
|
1213
|
+
Tables and microservices are shared by every branch, so a schema change reaches production even on a deploy to a branch. A failed read is not evidence that there is none.
|
|
1214
|
+
Retry when the instance is reachable. \`--allow-shared-schema-changes\` waives THIS check, but a deploy that cannot read its target is refused either way \u2014 use \`--dry-run\` to see the plan without writing.`,
|
|
1215
|
+
void 0,
|
|
1216
|
+
{ reason: "tables-unreadable", changes: null }
|
|
1217
|
+
);
|
|
1218
|
+
}
|
|
1219
|
+
if (changes.length === 0 || args.allowSharedSchemaChanges) return changes;
|
|
1220
|
+
const lines = changes.map((c) => {
|
|
1221
|
+
const detailText = c.details === void 0 || c.details.length === 0 ? "" : ` \u2014 ${c.details.join(", ")}`;
|
|
1222
|
+
return ` ${c.action} ${c.kind === "dbo" ? "table" : "microservice"} "${c.name}"${detailText}`;
|
|
1223
|
+
});
|
|
1224
|
+
return refuseOrReport(
|
|
1225
|
+
args,
|
|
1226
|
+
`\`--branch ${branch}\` cannot stage ${changes.length} change${changes.length === 1 ? "" : "s"} to this workspace's SHARED schema:
|
|
1227
|
+
${lines.join("\n")}
|
|
1228
|
+
|
|
1229
|
+
Tables and microservices carry no branch in Xano \u2014 one set is shared by every branch. These would be applied to the LIVE workspace by the same import that stages your logic on "${branch}", and the plan would report them as routine.
|
|
1230
|
+
Deploy without \`--branch\` if applying them is what you want, take them out of this deploy, or stage the logic and apply the schema knowing both happen: ${waived}.`,
|
|
1231
|
+
changes,
|
|
1232
|
+
{ reason: "shared-schema-changes", changes: lines.map((l) => l.trim()) }
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
function ungatedSchemaChanges(changes) {
|
|
1236
|
+
const gated = gatedSchemaChanges(changes) ?? [];
|
|
1237
|
+
return (changes ?? []).flatMap((c) => {
|
|
1238
|
+
if (c.action !== "alter" || c.details === void 0) return [];
|
|
1239
|
+
const kept = gated.find((g) => g.kind === c.kind && g.name === c.name)?.details ?? [];
|
|
1240
|
+
return c.details.filter((d) => !kept.includes(d)).map((d) => `table "${c.name}": ${d}`);
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
function gatedSchemaChanges(changes) {
|
|
1244
|
+
if (changes === void 0) return void 0;
|
|
1245
|
+
const gated = (d) => !/^drop index /.test(d) && !/^add index (?!unique\()/.test(d);
|
|
1246
|
+
return changes.flatMap((c) => {
|
|
1247
|
+
if (c.action !== "alter" || c.details === void 0) return [c];
|
|
1248
|
+
const details = c.details.filter(gated);
|
|
1249
|
+
return details.length === 0 ? [] : [{ ...c, details }];
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
function refuseOrReport(args, message, result, details) {
|
|
1253
|
+
if (!args.dryRun) {
|
|
1254
|
+
throw new CliError("SDK_SHARED_SCHEMA_CHANGE", message, {
|
|
1255
|
+
exitCode: 2,
|
|
1256
|
+
details: { landed: false, declined: false, ...details }
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
const [head, ...rest] = message.split("\n");
|
|
1260
|
+
warn(/^Refusing/.test(head) ? head.replace(/^Refusing/, "A real run would refuse") : `A real run would refuse: ${head}`, "deploy.dry-run-refusal");
|
|
1261
|
+
for (const line of rest) {
|
|
1262
|
+
if (line.trim() === "") continue;
|
|
1263
|
+
detail(line.trim());
|
|
1264
|
+
}
|
|
1265
|
+
blank();
|
|
1266
|
+
return result;
|
|
1267
|
+
}
|
|
1268
|
+
function releaseCanonicalLock(args, classified) {
|
|
1269
|
+
if (classified !== void 0) return classified;
|
|
1270
|
+
const lockPath = args.lockPath ?? (args.file !== void 0 ? resolveLockPath(args, args.file) : void 0);
|
|
1271
|
+
if (lockPath === void 0 || !existsSync(lockPath)) return void 0;
|
|
1272
|
+
return readLockFile(lockPath);
|
|
1273
|
+
}
|
|
1274
|
+
function codePinnedCanonicals(bundle, lock, compiledFromSource = false) {
|
|
1275
|
+
if (lock === void 0 && !compiledFromSource) return [];
|
|
1276
|
+
let payload;
|
|
1277
|
+
try {
|
|
1278
|
+
const parsed = JSON.parse(bundle)?.payload;
|
|
1279
|
+
if (parsed === null || typeof parsed !== "object") return [];
|
|
1280
|
+
payload = parsed;
|
|
1281
|
+
} catch {
|
|
1282
|
+
return [];
|
|
1283
|
+
}
|
|
1284
|
+
const pinned = [];
|
|
1285
|
+
for (const payloadKey of ["app", "toolset", "realtime_server"]) {
|
|
1286
|
+
const section = payload[payloadKey];
|
|
1287
|
+
if (!Array.isArray(section)) continue;
|
|
1288
|
+
for (const row of section) {
|
|
1289
|
+
if (row === null || typeof row !== "object") continue;
|
|
1290
|
+
const { name, canonical, guid } = row;
|
|
1291
|
+
if (typeof name !== "string" || name === "") continue;
|
|
1292
|
+
if (typeof canonical !== "string" || canonical === "") continue;
|
|
1293
|
+
if (lock !== void 0 && lock.objects[lockKey(payloadKey, name)]?.canonical_source !== "code") {
|
|
1294
|
+
continue;
|
|
1295
|
+
}
|
|
1296
|
+
pinned.push({
|
|
1297
|
+
payloadKey,
|
|
1298
|
+
name,
|
|
1299
|
+
canonical,
|
|
1300
|
+
...typeof guid === "string" && guid !== "" ? { guid } : {}
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
return pinned;
|
|
1305
|
+
}
|
|
1306
|
+
function pinnedCanonicalGuids(bundle, lock, compiledFromSource = false) {
|
|
1307
|
+
const guids = [];
|
|
1308
|
+
for (const entry of codePinnedCanonicals(bundle, lock, compiledFromSource)) {
|
|
1309
|
+
if (entry.guid !== void 0 && !guids.includes(entry.guid)) guids.push(entry.guid);
|
|
1310
|
+
}
|
|
1311
|
+
return guids;
|
|
1312
|
+
}
|
|
1313
|
+
var EXIT_IMPORT_CONFLICT = 2;
|
|
1314
|
+
function parsedOrUndefined(bundle) {
|
|
1315
|
+
try {
|
|
1316
|
+
return JSON.parse(bundle);
|
|
1317
|
+
} catch {
|
|
1318
|
+
return void 0;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
function namedRow(ctx, kind, name) {
|
|
1322
|
+
const section = planTypeSection(kind);
|
|
1323
|
+
for (const side of [ctx?.bundle, ctx?.live]) {
|
|
1324
|
+
const rows = payloadOf(side)[section];
|
|
1325
|
+
if (!Array.isArray(rows)) continue;
|
|
1326
|
+
const row = rows.find((r) => r !== null && typeof r === "object" && r.name === name);
|
|
1327
|
+
if (row !== void 0) return row;
|
|
1328
|
+
}
|
|
1329
|
+
return void 0;
|
|
1330
|
+
}
|
|
1331
|
+
function canonicalsForOutput(canonicals, ctx) {
|
|
1332
|
+
return canonicals.map((c) => ({ ...c, kind: sdkKindForPlanType(c.kind, namedRow(ctx, c.kind, c.name)) }));
|
|
1333
|
+
}
|
|
1334
|
+
function plannedCanonicalsForOutput(canonicals, ctx) {
|
|
1335
|
+
return canonicalsForOutput(canonicals, ctx).map((c, i) => ({ ...c, live: liveSlug(ctx.live, canonicals[i]) ?? null }));
|
|
1336
|
+
}
|
|
1337
|
+
function liveSlug(live, c) {
|
|
1338
|
+
const rows = payloadOf(live)[planTypeSection(c.kind)];
|
|
1339
|
+
if (!Array.isArray(rows)) return void 0;
|
|
1340
|
+
const records = rows.filter((r) => r !== null && typeof r === "object");
|
|
1341
|
+
const row = (c.guid === void 0 ? void 0 : records.find((r) => r.guid === c.guid)) ?? records.find((r) => r.name === c.name && (typeof r.guid !== "string" || r.guid === ""));
|
|
1342
|
+
return typeof row?.canonical === "string" && row.canonical !== "" ? row.canonical : void 0;
|
|
1343
|
+
}
|
|
1344
|
+
function plannedCanonicalMoves(canonicals, ctx) {
|
|
1345
|
+
return plannedCanonicalsForOutput(canonicals, ctx).filter((c) => c.live !== null && c.served !== void 0 && c.served !== c.live).map((c) => ({ kind: c.kind, name: c.name, from: c.live, to: c.served }));
|
|
1346
|
+
}
|
|
1347
|
+
function reportPlannedCanonicalMoves(moves, will, when) {
|
|
1348
|
+
if (moves.length === 0) return;
|
|
1349
|
+
discloseCanonicalMoves(moves, { subject: "the deploy", will, ...when === void 0 ? {} : { when } });
|
|
1350
|
+
blank();
|
|
1351
|
+
}
|
|
1352
|
+
function conflictsForOutput(conflicts, ctx) {
|
|
1353
|
+
return conflicts.map((c) => ({ ...c, kind: sdkKindForPlanType(c.kind, namedRow(ctx, c.kind, c.identity)) }));
|
|
1354
|
+
}
|
|
1355
|
+
function planForOutput(plan, presented, tenant, converged = false, noun = tenant ? "tenant" : "workspace") {
|
|
1356
|
+
const { branch, operations: _operations, summary, ...rest } = plan;
|
|
1357
|
+
const {
|
|
1358
|
+
types: _types,
|
|
1359
|
+
actions: _actions,
|
|
1360
|
+
delete: _delete,
|
|
1361
|
+
records: _records,
|
|
1362
|
+
truncate: _truncate,
|
|
1363
|
+
rows,
|
|
1364
|
+
...kept
|
|
1365
|
+
} = summary;
|
|
1366
|
+
const shownRows = presentRows(rows);
|
|
1367
|
+
const shown = converged ? presented.map(({ details: _d, reason: _r, ...op }) => ({
|
|
1368
|
+
...op,
|
|
1369
|
+
action: "unchanged",
|
|
1370
|
+
details: `Already matches the ${noun}; nothing is sent for it`
|
|
1371
|
+
})) : presented;
|
|
1372
|
+
return {
|
|
1373
|
+
...rest,
|
|
1374
|
+
summary: {
|
|
1375
|
+
...kept,
|
|
1376
|
+
actions: countActions(shown),
|
|
1377
|
+
types: countTypes(shown),
|
|
1378
|
+
...shownRows === void 0 ? {} : { rows: shownRows }
|
|
1379
|
+
},
|
|
1380
|
+
operations: shown,
|
|
1381
|
+
...tenant ? {} : { branch: branch ?? null }
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
function destinationPayload(resolved, dest, auth) {
|
|
1385
|
+
if (dest?.kind !== "tenant") return writeTargetPayload({ base: resolved.base, workspaceId: resolved.workspaceId, kind: "workspace" });
|
|
1386
|
+
return backendDestinationPayload(auth, {
|
|
1387
|
+
kind: dest.type ?? "tenant",
|
|
1388
|
+
name: dest.name ?? resolved.label ?? "",
|
|
1389
|
+
url: resolved.base,
|
|
1390
|
+
...dest.display === void 0 ? {} : { display: dest.display }
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
function planFromRoute(res, tenant = false, otherBranches) {
|
|
1394
|
+
const { notes, ...summary } = res.plan.summary;
|
|
1395
|
+
return {
|
|
1396
|
+
workspaceName: res.workspace.name,
|
|
1397
|
+
summary: notes === void 0 ? summary : { ...summary, notes: presentableNotes(notes, tenant, otherBranches) },
|
|
1398
|
+
hasRecords: res.plan.hasRecords,
|
|
1399
|
+
hasEnv: res.plan.hasEnv,
|
|
1400
|
+
operations: res.plan.operations,
|
|
1401
|
+
branch: res.branch
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
var INTERNAL_IDENTIFIER = /\b[a-z]+:[a-z_]+\b/;
|
|
1405
|
+
var REPLACE_CLEAR_NOTE = /other branch/i;
|
|
1406
|
+
var BRANCHES_IN_LIST = /\bbranches, /;
|
|
1407
|
+
var REPLACE_CLEAR_TEXT = "A replace clears the whole workspace, not one branch: every object on every other branch of this workspace is destroyed too, and none of them is listed in this plan.";
|
|
1408
|
+
function presentableNotes(notes, tenant = false, otherBranches) {
|
|
1409
|
+
const all = typeof notes === "string" ? [notes] : Array.isArray(notes) ? notes : [];
|
|
1410
|
+
const shown = [];
|
|
1411
|
+
let withheld = 0;
|
|
1412
|
+
for (const note of all) {
|
|
1413
|
+
if (typeof note !== "string" || note === "") continue;
|
|
1414
|
+
if (tenant && REPLACE_CLEAR_NOTE.test(note)) continue;
|
|
1415
|
+
if (otherBranches === 0 && REPLACE_CLEAR_NOTE.test(note)) continue;
|
|
1416
|
+
if (tenant && BRANCHES_IN_LIST.test(note)) {
|
|
1417
|
+
shown.push(note.replace(BRANCHES_IN_LIST, ""));
|
|
1418
|
+
continue;
|
|
1419
|
+
}
|
|
1420
|
+
if (!INTERNAL_IDENTIFIER.test(note)) shown.push(note);
|
|
1421
|
+
else if (REPLACE_CLEAR_NOTE.test(note)) {
|
|
1422
|
+
shown.push(REPLACE_CLEAR_TEXT);
|
|
1423
|
+
} else withheld += 1;
|
|
1424
|
+
}
|
|
1425
|
+
if (withheld > 0) {
|
|
1426
|
+
shown.push(
|
|
1427
|
+
`The instance attached ${withheld} more note${withheld === 1 ? "" : "s"} about this preview that the CLI cannot show \u2014 treat the plan as incomplete.`
|
|
1428
|
+
);
|
|
1429
|
+
}
|
|
1430
|
+
return shown;
|
|
1431
|
+
}
|
|
1432
|
+
function codePinnedGuid(args) {
|
|
1433
|
+
let lock;
|
|
1434
|
+
try {
|
|
1435
|
+
lock = readLockFile(resolveLockPath(args, args.file ?? "."));
|
|
1436
|
+
} catch {
|
|
1437
|
+
lock = void 0;
|
|
1438
|
+
}
|
|
1439
|
+
return (guid, sending) => {
|
|
1440
|
+
const key = lockKey(sending.payloadKey, sending.lockName);
|
|
1441
|
+
const entry = lock?.objects[key];
|
|
1442
|
+
if (entry?.guid === guid && entry.guid_source === "code") return true;
|
|
1443
|
+
if (guid === rawDeriveGuid(key)) return false;
|
|
1444
|
+
const legacy = sending.payloadKey === "query" ? lock?.objects[lockKey("query", sending.name)]?.guid : void 0;
|
|
1445
|
+
return lock?.objects[key]?.guid !== guid && legacy !== guid;
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
function lockRenameRemedy(args) {
|
|
1449
|
+
const lockPath = resolveLockPath(args, args.file ?? ".");
|
|
1450
|
+
let lock;
|
|
1451
|
+
try {
|
|
1452
|
+
lock = readLockFile(lockPath);
|
|
1453
|
+
} catch {
|
|
1454
|
+
return void 0;
|
|
1455
|
+
}
|
|
1456
|
+
return (ownerGuid, sending) => {
|
|
1457
|
+
const prefix = `${sending.payloadKey}:`;
|
|
1458
|
+
const key = Object.keys(lock.objects).find(
|
|
1459
|
+
(k) => k.startsWith(prefix) && lock.objects[k]?.guid === ownerGuid && k !== `${prefix}${sending.lockName}`
|
|
1460
|
+
);
|
|
1461
|
+
if (key === void 0) return void 0;
|
|
1462
|
+
let [kind, oldName, newName] = [sending.kind, key.slice(prefix.length), sending.lockName];
|
|
1463
|
+
const [og, ...orest] = oldName.split("|");
|
|
1464
|
+
const [ng, ...nrest] = newName.split("|");
|
|
1465
|
+
if (sending.payloadKey === "query" && og !== ng && orest.join("|") === nrest.join("|") && `app:${og}` in lock.objects) {
|
|
1466
|
+
[kind, oldName, newName] = ["apiGroup", og, ng];
|
|
1467
|
+
}
|
|
1468
|
+
const [o, n] = [oldName.split("|"), newName.split("|")];
|
|
1469
|
+
if (sending.payloadKey === "channel" || sending.payloadKey === "message") {
|
|
1470
|
+
if (og !== ng && orest.join("|") === nrest.join("|") && `realtime_server:${og}` in lock.objects) {
|
|
1471
|
+
[kind, oldName, newName] = ["realtimeServer", og, ng];
|
|
1472
|
+
} else if (sending.payloadKey === "message" && o[1] !== n[1] && o[0] === n[0] && o[2] === n[2] && `channel:${o[0]}|${o[1]}` in lock.objects) {
|
|
1473
|
+
[kind, oldName, newName] = ["realtimeChannel", `${o[0]}|${o[1]}`, `${n[0]}|${n[1]}`];
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
const entry = args.file === void 0 ? "" : ` --entry=${shellWord(displayPath(args.file))}`;
|
|
1477
|
+
return `This is a rename of ${kind} "${oldName}" to "${newName}": xano.lock still pins that guid under the old name. Run \`xanosdk lock rename ${kind} ${shellWord(oldName)} ${shellWord(newName)} --lock=${shellWord(displayPath(lockPath))}${entry}\`, then retry \u2014 the object already there is updated in place, keeping its guid and canonical.`;
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
function adoptRemedy(args, dest) {
|
|
1481
|
+
const lockPath = resolveLockPath(args, args.file ?? ".");
|
|
1482
|
+
const lockFlag = lockPath === resolve("xano.lock") ? "" : ` --lock=${shellWord(displayPath(lockPath))}`;
|
|
1483
|
+
const creds = contextFlags(args);
|
|
1484
|
+
const importIt = `\`xanosdk lock import live.json${lockFlag} --yes\``;
|
|
1485
|
+
const scope = " \u2014 every identity in that file becomes this project's, and a later `--prune` from this project can delete any of those objects once they have landed";
|
|
1486
|
+
if (dest?.kind !== "tenant") {
|
|
1487
|
+
return `export it with \`xanosdk workspace export --path live.json${creds}\`, then ${importIt}${scope}`;
|
|
1488
|
+
}
|
|
1489
|
+
if (dest.name === void 0 || dest.name === "") return void 0;
|
|
1490
|
+
if (dest.type !== "ephemeral") return pullRemedy(dest.name, lockPath, creds);
|
|
1491
|
+
return `export it with \`xanosdk ephemeral export ${shellWord(dest.name)} --format json --path live.json${creds}\`, then ${importIt}${scope}`;
|
|
1492
|
+
}
|
|
1493
|
+
function pullRemedy(tenant, lockPath, creds) {
|
|
1494
|
+
const backend = dirname(lockPath);
|
|
1495
|
+
if (!existsSync(join(backend, "index.ts"))) return void 0;
|
|
1496
|
+
const cwd = process.cwd();
|
|
1497
|
+
const rel = relative(cwd, backend).split(sep).join("/");
|
|
1498
|
+
if (rel === "" || rel === ".." || rel.startsWith("../") || rel.startsWith("/")) return void 0;
|
|
1499
|
+
const dirFlag = resolve(backendDirIn(cwd)) === resolve(backend) ? "" : ` --backend-dir ${shellWord(pastePath(rel))}`;
|
|
1500
|
+
const yes = process.stdin.isTTY === true ? "" : " --yes";
|
|
1501
|
+
return `pull it into this project with \`xanosdk pull tenant:${shellWord(tenant)}${dirFlag}${creds}${yes}\` (commit first) \u2014 it rewrites the files a decode wrote in ${rel}/, your edits to them included, and keeps the files you added; every identity the tenant holds becomes this project's, and a later \`--prune\` from this project can delete any of those objects once they have landed`;
|
|
1502
|
+
}
|
|
1503
|
+
function adoptionAddsNothing(args, live) {
|
|
1504
|
+
if (live === void 0 || kindsByGuid(live).size === 0) return false;
|
|
1505
|
+
const lockPath = resolveLockPath(args, args.file ?? ".");
|
|
1506
|
+
if (!existsSync(lockPath)) return false;
|
|
1507
|
+
try {
|
|
1508
|
+
const { added, changed } = adoptFromBundle(readLockFile(lockPath), live, "the destination");
|
|
1509
|
+
return added.length === 0 && changed.length === 0;
|
|
1510
|
+
} catch {
|
|
1511
|
+
return false;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
var REFUSAL_CODES = {
|
|
1515
|
+
identityConflict: "SDK_IDENTITY_CONFLICT",
|
|
1516
|
+
renamePending: "SDK_IDENTITY_CONFLICT",
|
|
1517
|
+
kindConflict: "SDK_KIND_CONFLICT",
|
|
1518
|
+
importRefused: "SDK_IMPORT_REFUSED",
|
|
1519
|
+
uniqueViolation: "SDK_IMPORT_REFUSED",
|
|
1520
|
+
pruneOutOfScope: "SDK_PRUNE_OUT_OF_SCOPE"
|
|
1521
|
+
};
|
|
1522
|
+
async function unrecordedTableRenames(args, bundle, live, plan, classified) {
|
|
1523
|
+
const entry = args.file;
|
|
1524
|
+
if (entry === void 0) return [];
|
|
1525
|
+
const created = plan.operations.filter((o) => o.action === "create" && (o.type === "table" || o.type === "dbo")).map((o) => o.name);
|
|
1526
|
+
if (created.length === 0) return [];
|
|
1527
|
+
const lockPath = resolveLockPath(args, entry);
|
|
1528
|
+
let lock;
|
|
1529
|
+
try {
|
|
1530
|
+
lock = readLockFile(lockPath);
|
|
1531
|
+
} catch {
|
|
1532
|
+
return [];
|
|
1533
|
+
}
|
|
1534
|
+
const guidsOf = (doc) => {
|
|
1535
|
+
const rows = payloadOf(doc).dbo;
|
|
1536
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
1537
|
+
for (const r of Array.isArray(rows) ? rows : []) {
|
|
1538
|
+
const row = r;
|
|
1539
|
+
if (typeof row?.guid === "string" && row.guid !== "") out2.set(row.guid, typeof row.name === "string" ? row.name : row.guid);
|
|
1540
|
+
}
|
|
1541
|
+
return out2;
|
|
1542
|
+
};
|
|
1543
|
+
let outgoingDoc;
|
|
1544
|
+
try {
|
|
1545
|
+
outgoingDoc = JSON.parse(bundle);
|
|
1546
|
+
} catch {
|
|
1547
|
+
return [];
|
|
1548
|
+
}
|
|
1549
|
+
const outgoing = guidsOf(outgoingDoc);
|
|
1550
|
+
const outgoingNames = new Set(outgoing.values());
|
|
1551
|
+
const held = guidsOf(live);
|
|
1552
|
+
const codePinned = codePinnedTables(outgoingDoc, lock, classified);
|
|
1553
|
+
const { orphanFixUps } = await import("./cli.js");
|
|
1554
|
+
const { setGuidRemedy, clearReplacedCommand } = await import("./keep-data-merge-7MPLVAVI.js");
|
|
1555
|
+
const run = (line) => (line ?? "").replace(/^(renamed|deleted)\? run: /, "");
|
|
1556
|
+
const out = [];
|
|
1557
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1558
|
+
for (const [key, e] of Object.entries((classified ?? lock).objects)) {
|
|
1559
|
+
if (!key.startsWith("dbo:")) continue;
|
|
1560
|
+
const orphan = !outgoingNames.has(key.slice("dbo:".length));
|
|
1561
|
+
const guids = [...typeof e.guid === "string" ? [e.guid] : [], ...e.replaced ?? []];
|
|
1562
|
+
for (const guid of guids) {
|
|
1563
|
+
if (outgoing.has(guid) || seen.has(guid)) continue;
|
|
1564
|
+
const table = held.get(guid);
|
|
1565
|
+
if (table === void 0) continue;
|
|
1566
|
+
seen.add(guid);
|
|
1567
|
+
const [rename, prune] = orphanFixUps(key, entry, lockPath, void 0, [], created.length === 1 ? created[0] : void 0);
|
|
1568
|
+
const setGuid = setGuidRemedy(guid, created, codePinned) ?? (orphan ? void 0 : { ...created.length === 1 ? { table: created[0] } : {}, guid, pins: [] });
|
|
1569
|
+
out.push({
|
|
1570
|
+
table,
|
|
1571
|
+
guid,
|
|
1572
|
+
candidates: created,
|
|
1573
|
+
...setGuid === void 0 ? { rename: run(rename) } : { setGuid },
|
|
1574
|
+
prune: orphan ? run(prune) : clearReplacedCommand(key.slice("dbo:".length), lockPath)
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
for (const [name, t] of codePinned) {
|
|
1579
|
+
for (const guid of t.replaces ?? []) {
|
|
1580
|
+
if (outgoing.has(guid) || seen.has(guid)) continue;
|
|
1581
|
+
const table = held.get(guid);
|
|
1582
|
+
if (table === void 0) continue;
|
|
1583
|
+
seen.add(guid);
|
|
1584
|
+
const setGuid = setGuidRemedy(guid, created, codePinned);
|
|
1585
|
+
const [rename] = orphanFixUps(`dbo:${name}`, entry, lockPath, void 0, [], created.length === 1 ? created[0] : void 0);
|
|
1586
|
+
out.push({
|
|
1587
|
+
table,
|
|
1588
|
+
guid,
|
|
1589
|
+
candidates: created,
|
|
1590
|
+
...setGuid === void 0 ? { rename: run(rename) } : { setGuid },
|
|
1591
|
+
prune: clearReplacedCommand(name, lockPath)
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
return out;
|
|
1596
|
+
}
|
|
1597
|
+
async function pendingRenameRefusal(frame, pending, prune, branch) {
|
|
1598
|
+
const { pendingRenameLines, pendingRenameFollowUp, pendingRenameDetail, sameNameRepin } = await import("./keep-data-merge-7MPLVAVI.js");
|
|
1599
|
+
const one = pending.length === 1;
|
|
1600
|
+
const lines = pending.flatMap(
|
|
1601
|
+
(p) => pendingRenameLines(
|
|
1602
|
+
p,
|
|
1603
|
+
prune ? "the old table would still be dropped" : "the new table would still be created empty beside the old one",
|
|
1604
|
+
prune ? {} : { addFlag: "--prune" }
|
|
1605
|
+
)
|
|
1606
|
+
);
|
|
1607
|
+
const err = refusalError(
|
|
1608
|
+
frame,
|
|
1609
|
+
"renamePending",
|
|
1610
|
+
(pending.every(sameNameRepin) ? `Refusing to deploy into ${frame.noun}: ${one ? "a table's def pins" : "table defs pin"} a new guid under the name the table already has. A merge matches on guid, so it would create the new table empty ` + (prune ? `and DROP the old one with every row.` : `beside the old one, which the target refuses by name.`) : `Refusing to deploy into ${frame.noun}: the project adds a table while xano.lock still pins ${one ? "a table" : "tables"} the build no longer declares \u2014 a rename the lock was not told about. A merge matches on guid, so it would create the new table empty ` + (prune ? `and DROP the old one with every row.` : `beside the old one, which keeps the rows the app no longer reads.`)) + `
|
|
1611
|
+
${lines.join("\n")}
|
|
1612
|
+
Nothing was written. ` + pendingRenameFollowUp(pending, `it is no longer this project's.`),
|
|
1613
|
+
{ branch }
|
|
1614
|
+
);
|
|
1615
|
+
err.details.renames = pending.map(pendingRenameDetail);
|
|
1616
|
+
return err;
|
|
1617
|
+
}
|
|
1618
|
+
function refusalError(frame, refused, message, lists) {
|
|
1619
|
+
const details = {
|
|
1620
|
+
refused,
|
|
1621
|
+
...lists.serverCode === void 0 ? {} : { serverCode: lists.serverCode },
|
|
1622
|
+
landed: false,
|
|
1623
|
+
...frame === void 0 ? {} : {
|
|
1624
|
+
dryRun: frame.dryRun,
|
|
1625
|
+
declined: false,
|
|
1626
|
+
workspaceId: frame.workspaceId,
|
|
1627
|
+
destination: frame.destination,
|
|
1628
|
+
mode: frame.mode,
|
|
1629
|
+
prune: frame.prune,
|
|
1630
|
+
// A tenant has no branches: the route's echo of its internal default
|
|
1631
|
+
// describes nothing the reader chose.
|
|
1632
|
+
branch: frame.tenant ? null : lists.branch ?? null
|
|
1633
|
+
},
|
|
1634
|
+
conflicts: frame === void 0 ? [...lists.conflicts ?? []] : conflictsForRefusal(lists.conflicts ?? [], frame),
|
|
1635
|
+
kindConflicts: lists.kindConflicts ?? [],
|
|
1636
|
+
canonicals: lists.canonicals ?? [],
|
|
1637
|
+
outOfScope: lists.outOfScope ?? [],
|
|
1638
|
+
...lists.indexes === void 0 ? {} : { uniqueViolation: true, indexes: lists.indexes.map((i) => ({ ...i })) },
|
|
1639
|
+
...lists.seededIndexes === void 0 ? {} : { seededIndexes: lists.seededIndexes.map((i) => ({ ...i })) },
|
|
1640
|
+
// Nothing was written, so nothing was landed.
|
|
1641
|
+
landingRecord: null
|
|
1642
|
+
};
|
|
1643
|
+
return new CliError(lists.code ?? REFUSAL_CODES[refused], message, {
|
|
1644
|
+
details,
|
|
1645
|
+
exitCode: lists.exitCode ?? EXIT_IMPORT_CONFLICT
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
function ownerIsDestination(owner, frame) {
|
|
1649
|
+
return owner.workspaceId === frame.workspaceId;
|
|
1650
|
+
}
|
|
1651
|
+
function conflictsForRefusal(conflicts, frame) {
|
|
1652
|
+
if (!frame.tenant) return [...conflicts];
|
|
1653
|
+
return conflicts.map((c) => {
|
|
1654
|
+
if (!ownerIsDestination(c.owner, frame)) return c;
|
|
1655
|
+
return { ...c, owner: { ...c.owner, workspaceName: void 0, branch: void 0 } };
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1658
|
+
function describeOwner(owner, frame) {
|
|
1659
|
+
const guid = owner.guid !== void 0 ? ` (${owner.guid})` : "";
|
|
1660
|
+
const deleted = owner.deleted ? ", deleted but still holding the name" : "";
|
|
1661
|
+
if (frame.tenant && ownerIsDestination(owner, frame)) return `${frame.noun}${guid}${deleted}`;
|
|
1662
|
+
const where = ownerIsDestination(owner, frame) ? "this workspace" : `workspace #${owner.workspaceId}`;
|
|
1663
|
+
const named = owner.workspaceName !== void 0 ? ` "${owner.workspaceName}"` : "";
|
|
1664
|
+
const branch = owner.branch !== void 0 && !frame.tenant ? ` on branch "${owner.branch}"` : "";
|
|
1665
|
+
return `${where}${named}${branch}${guid}${deleted}`;
|
|
1666
|
+
}
|
|
1667
|
+
function conflictRemedy(c, frame, bundle, all = []) {
|
|
1668
|
+
const purge = c.owner.deleted ? " A deleted object still holds its identity, so it has to be purged before the name is free." : "";
|
|
1669
|
+
const sibling = siblingHolder(c, frame, bundle);
|
|
1670
|
+
if (sibling !== void 0) return siblingRemedy(c, sibling, all);
|
|
1671
|
+
if (!ownerIsDestination(c.owner, frame)) {
|
|
1672
|
+
const named = c.owner.workspaceName !== void 0 ? ` ("${c.owner.workspaceName}")` : "";
|
|
1673
|
+
return `Nothing in this project can free it: the identity is held by workspace #${c.owner.workspaceId}${named}, not by the one being released into. ${c.kind === "canonical" ? "Change the `canonical` on yours (or drop it to have one minted)" : "Rename yours"}, or free it in workspace #${c.owner.workspaceId}.${purge}`;
|
|
1674
|
+
}
|
|
1675
|
+
if (c.owner.guid !== void 0 && c.archiveGuid !== void 0 && frame.lockRename !== void 0) {
|
|
1676
|
+
const sending = kindsByGuid(bundle).get(c.archiveGuid);
|
|
1677
|
+
const rename = sending === void 0 ? void 0 : frame.lockRename(c.owner.guid, sending);
|
|
1678
|
+
if (rename !== void 0) return `${rename}${purge}`;
|
|
1679
|
+
}
|
|
1680
|
+
const holder = frame.tenant ? frame.noun : "the workspace";
|
|
1681
|
+
const pin = c.owner.guid !== void 0 ? (
|
|
1682
|
+
// A def's `guid:` wins over the lock's entry for it (the next build
|
|
1683
|
+
// re-pins the lock and says so), so this is the whole step.
|
|
1684
|
+
`pin it on the def (\`guid: "${c.owner.guid}"\`) so this ${frame.verb ?? "deploy"} UPDATES the object that is already there`
|
|
1685
|
+
) : void 0;
|
|
1686
|
+
const fix = c.kind === "canonical" ? `change the \`canonical\` on this project's def to a slug nothing else serves, or drop it to have the instance mint one` : "rename it in this project";
|
|
1687
|
+
const Fix = `${fix.charAt(0).toUpperCase()}${fix.slice(1)}`;
|
|
1688
|
+
const sent = c.archiveGuid === void 0 ? void 0 : kindsByGuid(bundle).get(c.archiveGuid);
|
|
1689
|
+
if (c.kind !== "canonical" && sent !== void 0 && frame.pinnedInCode?.(c.archiveGuid, sent) === true) {
|
|
1690
|
+
const setIt = c.owner.guid !== void 0 ? `change it to \`guid: "${c.owner.guid}"\` so this ${frame.verb ?? "deploy"} UPDATES the object that is already there, or remove it` : "change or remove it";
|
|
1691
|
+
return `${Fix}, or ${setIt}: the def pins \`guid: "${c.archiveGuid}"\` in code, and a def's \`guid:\` wins over xano.lock, so adopting into the lock cannot change what this ${frame.verb ?? "deploy"} sends.${purge}`;
|
|
1692
|
+
}
|
|
1693
|
+
if (frame.adopt === void 0) {
|
|
1694
|
+
return `${Fix}${pin === void 0 ? "" : `, or ${pin}`}.${purge}`;
|
|
1695
|
+
}
|
|
1696
|
+
if (frame.adoptAddsNothing === true) {
|
|
1697
|
+
const why = c.kind === "canonical" ? `the slug is held by an object under a different name (a differently named group, say), which adopting cannot make this one` : `adopting cannot free it`;
|
|
1698
|
+
return `${pin === void 0 ? Fix : `${pin.charAt(0).toUpperCase()}${pin.slice(1)}, or ${fix}`}. Adopting again changes nothing: xano.lock already holds every identity ${holder} holds, and ${why}.${purge}`;
|
|
1699
|
+
}
|
|
1700
|
+
if (pin !== void 0) {
|
|
1701
|
+
return `${pin.charAt(0).toUpperCase()}${pin.slice(1)}, or ${fix}. To adopt everything ${holder} holds instead: ${frame.adopt}.${purge}`;
|
|
1702
|
+
}
|
|
1703
|
+
return `${Fix}, or adopt everything ${holder} holds: ${frame.adopt}.${purge}`;
|
|
1704
|
+
}
|
|
1705
|
+
function withSignatureHint(err, bundlePath) {
|
|
1706
|
+
if (!isSignatureRefusal(err)) return err;
|
|
1707
|
+
err.message += `
|
|
1708
|
+
The archive's signature does not match its content` + (bundlePath === void 0 ? "." : `: ${bundlePath} was changed after it was exported. ${exportAgain(bundleOrigin(bundlePath))} rather than editing the bundle, or deploy from the entry file.`);
|
|
1709
|
+
return err;
|
|
1710
|
+
}
|
|
1711
|
+
function bundleOrigin(path) {
|
|
1712
|
+
try {
|
|
1713
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
1714
|
+
const ws = parsed.payload?.workspace;
|
|
1715
|
+
if (ws === void 0 || ws === null || typeof ws !== "object") return void 0;
|
|
1716
|
+
return "guid" in ws || "iv" in ws ? "live" : "compiled";
|
|
1717
|
+
} catch {
|
|
1718
|
+
return void 0;
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
function exportAgain(origin) {
|
|
1722
|
+
if (origin === "compiled") return "Export it again (`xanosdk export <entry>`)";
|
|
1723
|
+
if (origin === "live") {
|
|
1724
|
+
return "Export it again from the backend it came from (`xanosdk ephemeral export <name>` or `xanosdk workspace export`)";
|
|
1725
|
+
}
|
|
1726
|
+
return "Export it again with the command that wrote it (`xanosdk export <entry>`, `xanosdk ephemeral export <name>` or `xanosdk workspace export`)";
|
|
1727
|
+
}
|
|
1728
|
+
function conflictLine(c, frame, ctx, all = []) {
|
|
1729
|
+
const other = otherKindHolder(c, frame, ctx?.live);
|
|
1730
|
+
return ` ${c.kind} "${c.identity}" \u2014 held by ` + (other === void 0 ? "" : `${other.kind} "${other.name}" in `) + describeOwner(c.owner, frame) + (c.archiveGuid !== void 0 ? `; this ${frame.verb ?? "deploy"} carries ${c.archiveGuid}` : "") + `
|
|
1731
|
+
${other === void 0 ? conflictRemedy(c, frame, ctx?.bundle, all) : otherKindRemedy(c, other, frame)}`;
|
|
1732
|
+
}
|
|
1733
|
+
function otherKindHolder(c, frame, live) {
|
|
1734
|
+
if (live === void 0 || c.kind === "canonical" || c.owner.guid === void 0) return void 0;
|
|
1735
|
+
if (!ownerIsDestination(c.owner, frame)) return void 0;
|
|
1736
|
+
const held = kindsByGuid(live).get(c.owner.guid);
|
|
1737
|
+
return held === void 0 || held.kind === c.kind ? void 0 : held;
|
|
1738
|
+
}
|
|
1739
|
+
function otherKindRemedy(c, holder, frame) {
|
|
1740
|
+
const purge = c.owner.deleted ? " A deleted object still holds its identity, so it has to be purged before the name is free." : "";
|
|
1741
|
+
const where = frame.tenant ? frame.noun : "the workspace";
|
|
1742
|
+
return `${c.kind.charAt(0).toUpperCase()}${c.kind.slice(1)} and ${holder.kind} names share one name space, and pinning that guid would turn the ${holder.kind} into this ${c.kind}. Rename one of them: this project's ${c.kind}, or the ${holder.kind} on ${where} if it is yours to rename.${purge}`;
|
|
1743
|
+
}
|
|
1744
|
+
function siblingHolder(c, frame, bundle) {
|
|
1745
|
+
if (bundle === void 0 || c.owner.guid === void 0 || !ownerIsDestination(c.owner, frame)) return void 0;
|
|
1746
|
+
if (c.owner.guid === c.archiveGuid) return void 0;
|
|
1747
|
+
const defs = kindsByGuid(bundle);
|
|
1748
|
+
const holder = defs.get(c.owner.guid);
|
|
1749
|
+
if (holder === void 0) return void 0;
|
|
1750
|
+
return { holder, sending: c.archiveGuid === void 0 ? void 0 : defs.get(c.archiveGuid) };
|
|
1751
|
+
}
|
|
1752
|
+
function siblingRemedy(c, { holder, sending }, all = []) {
|
|
1753
|
+
const what = c.kind === "canonical" ? "canonical" : "name";
|
|
1754
|
+
const held = `${holder.kind} "${holder.name}"`;
|
|
1755
|
+
const mine = sending === void 0 ? "the def that asks for it" : `${sending.kind} "${sending.name}"`;
|
|
1756
|
+
const swap = all.some(
|
|
1757
|
+
(o) => o !== c && o.kind === c.kind && o.archiveGuid === c.owner.guid && o.owner.guid === c.archiveGuid
|
|
1758
|
+
);
|
|
1759
|
+
const move = swap ? `They trade ${what}s, and one deploy checks each against what the destination holds when it starts, so it takes three: deploy with ${mine}'s ${what} set to one nobody holds and ${held}'s left as it is now, then with ${held}'s as declared, then with ${mine}'s as declared.` : `To move it from one to the other, deploy once with ${mine}'s ${what} set to something else (that lands ${held}'s change and frees it), then claim it in a second deploy.`;
|
|
1760
|
+
return `It is held by ${held}, which this project also declares \u2014 so pinning its guid would give two defs one identity, and adopting cannot free it. Change the ${what} on one of the two: keep it on ${held}, or give ${mine} another. ${move}`;
|
|
1761
|
+
}
|
|
1762
|
+
function assertNoImportConflicts(res, frame, ctx = { bundle: void 0, live: void 0, tenant: false }) {
|
|
1763
|
+
if (res.conflicts.length === 0) return;
|
|
1764
|
+
const conflicts = conflictsForOutput(res.conflicts, ctx);
|
|
1765
|
+
const lines = conflicts.map((c) => conflictLine(c, frame, ctx, res.conflicts));
|
|
1766
|
+
throw refusalError(
|
|
1767
|
+
frame,
|
|
1768
|
+
"identityConflict",
|
|
1769
|
+
`Refusing to deploy into ${frame.noun}: ${res.conflicts.length} identit${res.conflicts.length === 1 ? "y is" : "ies are"} already taken by something else.
|
|
1770
|
+
${lines.join("\n")}
|
|
1771
|
+
|
|
1772
|
+
Nothing was written. The instance refuses these rather than inventing a name or a URL for them \u2014 a suffixed second object reports success and then splits your workspace in half, and a substituted public URL answers 404 on every route built from the one you pinned.`,
|
|
1773
|
+
{ branch: res.branch, conflicts, canonicals: canonicalsForOutput(res.canonicals, ctx) }
|
|
1774
|
+
);
|
|
1775
|
+
}
|
|
1776
|
+
function kindsByGuid(bundle) {
|
|
1777
|
+
const out = /* @__PURE__ */ new Map();
|
|
1778
|
+
const payload = bundle?.payload;
|
|
1779
|
+
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) return out;
|
|
1780
|
+
const apps = identityNamesByGuid(payload);
|
|
1781
|
+
for (const payloadKey of Object.values(REFERENCEABLE_KIND_PAYLOAD_KEYS)) {
|
|
1782
|
+
const rows = payload[payloadKey];
|
|
1783
|
+
if (!Array.isArray(rows)) continue;
|
|
1784
|
+
for (const row of rows) {
|
|
1785
|
+
if (row === null || typeof row !== "object") continue;
|
|
1786
|
+
const { guid, name, type } = row;
|
|
1787
|
+
if (typeof guid !== "string" || guid === "" || typeof name !== "string") continue;
|
|
1788
|
+
const kind = payloadKey === "toolset" ? type === "agent" ? "agent" : "mcpServer" : sdkKindName(payloadKey);
|
|
1789
|
+
out.set(guid, { kind, name, payloadKey, lockName: lockNameForObject(payloadKey, row, apps) });
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
return out;
|
|
1793
|
+
}
|
|
1794
|
+
function assertNoKindSwap(bundle, live, frame, branch, lock) {
|
|
1795
|
+
if (live === void 0 || bundle === void 0) return;
|
|
1796
|
+
const destNoun = frame.noun;
|
|
1797
|
+
const held = kindsByGuid(live);
|
|
1798
|
+
const swaps = [];
|
|
1799
|
+
const pinnedDefs = [];
|
|
1800
|
+
const pinnedOthers = [];
|
|
1801
|
+
const lockPinned = [];
|
|
1802
|
+
let shared = 0;
|
|
1803
|
+
for (const [guid, sent] of kindsByGuid(bundle)) {
|
|
1804
|
+
const there = held.get(guid);
|
|
1805
|
+
if (there === void 0 || there.kind === sent.kind) continue;
|
|
1806
|
+
swaps.push({ guid, name: sent.name, sending: sent.kind, holds: there.kind, holdsName: there.name });
|
|
1807
|
+
const key = lockKey(sent.payloadKey, sent.name);
|
|
1808
|
+
const pinned = sent.payloadKey !== "query" && guid !== rawDeriveGuid(key);
|
|
1809
|
+
if (pinned && there.name !== sent.name && lock?.file.objects[key]?.guid === guid) lockPinned.push(`${sent.kind}:${sent.name}`);
|
|
1810
|
+
else if (pinned && there.name !== sent.name) pinnedOthers.push(`${sent.kind} "${sent.name}"`);
|
|
1811
|
+
else {
|
|
1812
|
+
shared++;
|
|
1813
|
+
if (pinned) pinnedDefs.push(`${sent.kind} "${sent.name}"`);
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
if (swaps.length === 0) return;
|
|
1817
|
+
const lines = swaps.map(
|
|
1818
|
+
(s) => ` ${s.sending} "${s.name}" \u2014 ${destNoun} holds ${s.holds} "${s.holdsName}" under the same identity (${s.guid})`
|
|
1819
|
+
);
|
|
1820
|
+
const one = swaps.length === 1;
|
|
1821
|
+
const oneShared = shared === 1;
|
|
1822
|
+
throw refusalError(
|
|
1823
|
+
frame,
|
|
1824
|
+
"kindConflict",
|
|
1825
|
+
`Refusing to deploy into ${destNoun}: ${swaps.length} object${one ? "" : "s"} would replace a different kind of object in place.
|
|
1826
|
+
${lines.join("\n")}
|
|
1827
|
+
|
|
1828
|
+
A merge matches objects by identity, so this would turn the ${swaps[0].holds} there into this project's ${swaps[0].sending}, and the plan would call it an update. Nothing was written.` + (shared === 0 ? "" : `
|
|
1829
|
+
An agent and an MCP server of the same name share one identity. Rename ${oneShared ? "the object" : "these objects"} in this project, or remove the ${oneShared ? "one" : "ones"} on ${destNoun} first if ${oneShared ? "it is" : "they are"} yours to remove.` + (pinnedDefs.length === 0 ? "" : `
|
|
1830
|
+
A rename alone keeps the guid ${pinnedDefs.join(", ")} ${pinnedDefs.length === 1 ? "pins" : "pin"} in code (\`guid:\` on the def): remove that \`guid:\`, or change it, when you rename.`)) + (pinnedOthers.length === 0 ? "" : `
|
|
1831
|
+
${pinnedOthers.join(", ")} ${pinnedOthers.length === 1 ? "pins" : "pin"} another object's guid in code (\`guid:\` on the def). Removing that \`guid:\` alone resolves it \u2014 no rename is needed.`) + (lockPinned.length === 0 || lock === void 0 ? "" : `
|
|
1832
|
+
${displayPath(lock.path)} pins another object's guid to ${lockPinned.join(", ")} \u2014 the lock, not code. Drop ${lockPinned.length === 1 ? "that entry" : "those entries"} so the next compile derives ${lockPinned.length === 1 ? "its" : "their"} own (and remove any \`guid:\` on the def): \`xanosdk lock prune --identity-only ${lockPinned.map(shellWord).join(" ")} --yes --lock=${shellWord(displayPath(lock.path))}\`.`),
|
|
1833
|
+
{ branch, kindConflicts: swaps }
|
|
1834
|
+
);
|
|
1835
|
+
}
|
|
1836
|
+
function canonicalDifferences(canonicals) {
|
|
1837
|
+
return canonicals.filter(
|
|
1838
|
+
(c) => c.requested !== void 0 && c.requested !== "" && c.served !== c.requested
|
|
1839
|
+
);
|
|
1840
|
+
}
|
|
1841
|
+
function warnAboutUnnamedPublicUrls(bundle, hasLock) {
|
|
1842
|
+
if (hasLock) return [];
|
|
1843
|
+
let payload;
|
|
1844
|
+
try {
|
|
1845
|
+
const parsed = JSON.parse(bundle)?.payload;
|
|
1846
|
+
if (parsed === null || typeof parsed !== "object") return [];
|
|
1847
|
+
payload = parsed;
|
|
1848
|
+
} catch {
|
|
1849
|
+
return [];
|
|
1850
|
+
}
|
|
1851
|
+
const unnamed = [];
|
|
1852
|
+
for (const payloadKey of ["app", "toolset", "realtime_server"]) {
|
|
1853
|
+
const section = payload[payloadKey];
|
|
1854
|
+
if (!Array.isArray(section)) continue;
|
|
1855
|
+
for (const row of section) {
|
|
1856
|
+
if (row === null || typeof row !== "object") continue;
|
|
1857
|
+
const { name, canonical } = row;
|
|
1858
|
+
if (typeof name !== "string" || name === "") continue;
|
|
1859
|
+
if (typeof canonical === "string" && canonical !== "") continue;
|
|
1860
|
+
unnamed.push(`${sectionKind(payloadKey, row)}:${name}`);
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
if (unnamed.length === 0) return [];
|
|
1864
|
+
warn(
|
|
1865
|
+
`${unnamed.length} public URL${unnamed.length === 1 ? "" : "s"} will be invented by the instance, and this project will not know ${unnamed.length === 1 ? "it" : "them"}:`,
|
|
1866
|
+
"canonical.invented",
|
|
1867
|
+
[
|
|
1868
|
+
...unnamed,
|
|
1869
|
+
"A workspace has to serve some URL, so the instance mints one \u2014 a different one in every workspace you deploy to.",
|
|
1870
|
+
"Set `canonical` on the def to choose it, or build with a lock \u2014 an ordinary `xanosdk export <entry>` writes one \u2014 which records the minted value and sends it back on every later release.",
|
|
1871
|
+
"A deploy from `--no-lock` or from a pre-exported `--bundle` has no lock to record it in, which is why this is the only case left where the instance still decides."
|
|
1872
|
+
]
|
|
1873
|
+
);
|
|
1874
|
+
blank();
|
|
1875
|
+
return unnamed;
|
|
1876
|
+
}
|
|
1877
|
+
function reportCanonicalDifferences(differences, ctx, planned = false) {
|
|
1878
|
+
if (differences.length === 0) return;
|
|
1879
|
+
differences = canonicalsForOutput(differences, ctx);
|
|
1880
|
+
if (!planned) blank();
|
|
1881
|
+
const one = differences.length === 1;
|
|
1882
|
+
warn(
|
|
1883
|
+
`${differences.length} public URL${one ? "" : "s"} ${planned ? "would not be" : one ? "is not" : "are not"} the value this project compiled:`,
|
|
1884
|
+
"canonical.differs",
|
|
1885
|
+
[
|
|
1886
|
+
...differences.map(
|
|
1887
|
+
(c) => `${c.kind} "${c.name}" \u2014 asked for "${c.requested ?? ""}", ${planned ? "would serve" : "serves"} "${c.served ?? ""}" (${c.outcome})`
|
|
1888
|
+
),
|
|
1889
|
+
"A public URL is unique across the whole instance, not per workspace, so an import keeps the one an object already had and substitutes a token when another workspace owns the one asked for.",
|
|
1890
|
+
"Anything built from the compiled value answers 404. Pin these in code to make the instance serve them or refuse."
|
|
1891
|
+
]
|
|
1892
|
+
);
|
|
1893
|
+
if (planned) blank();
|
|
1894
|
+
}
|
|
1895
|
+
function assertStaticMatchesServedUrls(differences, ctx) {
|
|
1896
|
+
if (differences.length === 0) return;
|
|
1897
|
+
differences = canonicalsForOutput(differences, ctx);
|
|
1898
|
+
const lines = differences.map(
|
|
1899
|
+
(c) => ` ${c.kind} "${c.name}" \u2014 built against "${c.requested ?? ""}", the workspace serves "${c.served ?? ""}"`
|
|
1900
|
+
);
|
|
1901
|
+
throw new Error(
|
|
1902
|
+
`The workspace was updated, but \`--static\` was not published: the frontend was built against ${differences.length} public URL${differences.length === 1 ? "" : "s"} the workspace does not serve:
|
|
1903
|
+
${lines.join("\n")}
|
|
1904
|
+
|
|
1905
|
+
Every route derived from ${differences.length === 1 ? "that value" : "those values"} would answer 404, and a published build that 404s is harder to diagnose than one that was never published.
|
|
1906
|
+
Pin ${differences.length === 1 ? "the slug" : "the slugs"} in code so the instance must serve ${differences.length === 1 ? "it" : "them"} or refuse the release, or free ${differences.length === 1 ? "it" : "them"} on whichever workspace holds ${differences.length === 1 ? "it" : "them"} and deploy again.`
|
|
1907
|
+
);
|
|
1908
|
+
}
|
|
1909
|
+
function seededUniqueIndexes(bundle, content) {
|
|
1910
|
+
const seeded = new Set(seedRowsByTableGuid(content).keys());
|
|
1911
|
+
if (seeded.size === 0) return [];
|
|
1912
|
+
const dbo = JSON.parse(bundle).payload?.dbo;
|
|
1913
|
+
if (!Array.isArray(dbo)) return [];
|
|
1914
|
+
return dbo.flatMap((t) => {
|
|
1915
|
+
const table = t ?? {};
|
|
1916
|
+
if (typeof table.guid !== "string" || typeof table.name !== "string" || !seeded.has(table.guid)) return [];
|
|
1917
|
+
const name = table.name;
|
|
1918
|
+
return uniqueIndexLabels(table).map((index) => ({ table: name, index }));
|
|
1919
|
+
});
|
|
1920
|
+
}
|
|
1921
|
+
function reportApplyRefusal(err, frame, branch, backupLabel, rerun, indexChanges = [], seededUnique = []) {
|
|
1922
|
+
if (backupLabel !== void 0) {
|
|
1923
|
+
blank();
|
|
1924
|
+
detail(`The pre-deploy snapshot on branch "${backupLabel}" is redundant \u2014 nothing was written for it to protect.`);
|
|
1925
|
+
detail(`Delete it with \`xanosdk workspace branch delete ${backupLabel}${pipedYes()}${contextFlags()}\`.`);
|
|
1926
|
+
}
|
|
1927
|
+
const conflicts = err.conflicts.length > 0 ? `
|
|
1928
|
+
${err.conflicts.map((c) => conflictLine(c, frame)).join("\n")}
|
|
1929
|
+
` : "";
|
|
1930
|
+
if (err.code === XANOSDK_IMPORT_UNIQUENESS_CODE) {
|
|
1931
|
+
const uniqueAdds = indexChanges.filter((c) => c.action === "add" && c.unique);
|
|
1932
|
+
const added = new Set(uniqueAdds.map((c) => `${c.table}\0${c.index}`));
|
|
1933
|
+
const seeded = seededUnique.filter((c) => !added.has(`${c.table}\0${c.index}`));
|
|
1934
|
+
const lines = (list) => list.length === 0 ? "" : `${list.map((c) => ` table ${c.table}: ${c.index}`).join("\n")}
|
|
1935
|
+
`;
|
|
1936
|
+
const why = uniqueAdds.length > 0 && seeded.length === 0 ? `the rows already there break a unique index this deploy adds` : uniqueAdds.length === 0 && seeded.length > 0 ? `the seed rows this deploy writes collide with rows already there on a unique index` : uniqueAdds.length > 0 ? `the rows already there break a unique index this deploy adds, or the seed rows it writes collide with them` : `rows break a unique index`;
|
|
1937
|
+
const remedy = uniqueAdds.length > 0 && seeded.length === 0 ? `Remove the duplicate rows or the index, then deploy again.` : seeded.length > 0 ? (uniqueAdds.length > 0 ? `Remove the duplicate rows or the index you add, or ` : ``) + `${uniqueAdds.length > 0 ? "change" : "Change"} the colliding seed values, or delete or merge the live rows they collide with, then deploy again.` : `Remove the duplicate rows, then deploy again.`;
|
|
1938
|
+
throw refusalError(
|
|
1939
|
+
frame,
|
|
1940
|
+
"uniqueViolation",
|
|
1941
|
+
`${frame.noun.charAt(0).toUpperCase()}${frame.noun.slice(1)} was NOT changed: ${why}, so the instance refused the import. Nothing was written.
|
|
1942
|
+
` + lines(uniqueAdds) + (seeded.length > 0 ? `${uniqueAdds.length > 0 ? "Seeded tables' unique indexes:\n" : ""}${lines(seeded)}` : "") + remedy,
|
|
1943
|
+
{
|
|
1944
|
+
branch,
|
|
1945
|
+
serverCode: err.code,
|
|
1946
|
+
indexes: uniqueAdds.map((c) => ({ table: c.table, index: c.index })),
|
|
1947
|
+
...seeded.length > 0 ? { seededIndexes: seeded.map((c) => ({ table: c.table, index: c.index })) } : {}
|
|
1948
|
+
}
|
|
1949
|
+
);
|
|
1950
|
+
}
|
|
1951
|
+
const conflict = err.code === XANOSDK_IMPORT_CONFLICT_CODE;
|
|
1952
|
+
throw refusalError(
|
|
1953
|
+
frame,
|
|
1954
|
+
"importRefused",
|
|
1955
|
+
`${frame.noun.charAt(0).toUpperCase()}${frame.noun.slice(1)} was NOT changed: the instance refused the import when it ran it.
|
|
1956
|
+
${err.reason}
|
|
1957
|
+
${conflicts}` + (err.retryable ? `
|
|
1958
|
+
It was busy with another import. Nothing here has to change \u2014 ` + (rerun === void 0 ? "deploy again in a moment." : `run \`${rerun.command}\` again in a moment.${rerun.note}`) : `
|
|
1959
|
+
The preview passed and this did not, so something changed on ${frame.noun} in between. Deploy again once the identities above are free.`),
|
|
1960
|
+
{
|
|
1961
|
+
branch,
|
|
1962
|
+
serverCode: err.code,
|
|
1963
|
+
conflicts: conflictsForOutput(err.conflicts),
|
|
1964
|
+
canonicals: canonicalsForOutput(err.canonicals),
|
|
1965
|
+
// In progress is transient: exit 8, as a failure to reach the instance is.
|
|
1966
|
+
...conflict ? { code: REFUSAL_CODES.identityConflict } : { exitCode: err.retryable ? 8 : 1 }
|
|
1967
|
+
}
|
|
1968
|
+
);
|
|
1969
|
+
}
|
|
1970
|
+
function reportWithheldSettings(branch, withheld) {
|
|
1971
|
+
if (withheld.length === 0) return;
|
|
1972
|
+
warn(
|
|
1973
|
+
`${withheld.length} workspace setting${withheld.length === 1 ? "" : "s"} will NOT be applied from branch "${branch}": ${withheld.join(", ")}`,
|
|
1974
|
+
"branch.settings-withheld",
|
|
1975
|
+
[
|
|
1976
|
+
"Workspace settings are shared by every branch, so a branch landing leaves them as the workspace has them.",
|
|
1977
|
+
"To apply them, deploy without `--branch` \u2014 onto the live branch, which after `--set-live` is the one you staged."
|
|
1978
|
+
]
|
|
1979
|
+
);
|
|
1980
|
+
blank();
|
|
1981
|
+
}
|
|
1982
|
+
async function restoreLiveAfterReplace(auth, target, landed) {
|
|
1983
|
+
const repair = () => {
|
|
1984
|
+
warn(
|
|
1985
|
+
`No branch reports as live after the replace, so the workspace serves nothing.`,
|
|
1986
|
+
"branch.none-live",
|
|
1987
|
+
[`Set it with \`xanosdk workspace branch set-live ${landed} --yes${contextFlags()}\`.`]
|
|
1988
|
+
);
|
|
1989
|
+
};
|
|
1990
|
+
let listing;
|
|
1991
|
+
try {
|
|
1992
|
+
listing = await listBranchListing(auth, target);
|
|
1993
|
+
} catch {
|
|
1994
|
+
warn(
|
|
1995
|
+
`Could not verify whether a branch is live after the replace.`,
|
|
1996
|
+
"branch.live-unverified",
|
|
1997
|
+
[
|
|
1998
|
+
`If none is, the workspace serves nothing \u2014 set it with \`xanosdk workspace branch set-live ${landed} --yes${contextFlags()}\`.`
|
|
1999
|
+
]
|
|
2000
|
+
);
|
|
2001
|
+
return void 0;
|
|
2002
|
+
}
|
|
2003
|
+
try {
|
|
2004
|
+
if (liveBranchLabel(listing.branches) !== void 0 || listing.unaddressable.some((b) => b.live)) {
|
|
2005
|
+
return void 0;
|
|
2006
|
+
}
|
|
2007
|
+
if (!listing.branches.some((b) => b.label === landed)) {
|
|
2008
|
+
repair();
|
|
2009
|
+
return void 0;
|
|
2010
|
+
}
|
|
2011
|
+
await setLiveBranch(auth, { ...target, label: landed });
|
|
2012
|
+
} catch {
|
|
2013
|
+
repair();
|
|
2014
|
+
return void 0;
|
|
2015
|
+
}
|
|
2016
|
+
success(`Branch "${landed}" is live again \u2014 a replace clears the live flag.`);
|
|
2017
|
+
return landed;
|
|
2018
|
+
}
|
|
2019
|
+
function reportBranchCaveats(branch, setLive, changed, indexChanges = [], yes = "") {
|
|
2020
|
+
blank();
|
|
2021
|
+
if (changed === void 0) {
|
|
2022
|
+
warn(
|
|
2023
|
+
"The live workspace could not be read, so whether this changed shared schema is UNKNOWN.",
|
|
2024
|
+
"branch.shared-schema-unknown",
|
|
2025
|
+
["Tables and microservices are shared by every branch \u2014 a change there reached production."]
|
|
2026
|
+
);
|
|
2027
|
+
} else if (changed > 0) {
|
|
2028
|
+
warn(
|
|
2029
|
+
`${changed} shared-schema change${changed === 1 ? " was" : "s were"} applied to the LIVE workspace.`,
|
|
2030
|
+
"branch.shared-schema-changed",
|
|
2031
|
+
["Tables and microservices are shared by every branch \u2014 only your logic is staged."]
|
|
2032
|
+
);
|
|
2033
|
+
} else if (indexChanges.length > 0) {
|
|
2034
|
+
warn(
|
|
2035
|
+
`Only logic was staged on "${branch}", but ${indexChanges.length === 1 ? "an index change was" : `${indexChanges.length} index changes were`} applied to the tables every branch shares, live included:`,
|
|
2036
|
+
"branch.shared-schema-changed",
|
|
2037
|
+
[...indexChanges, "A non-unique index changes how rows are found, not what they hold."]
|
|
2038
|
+
);
|
|
2039
|
+
} else {
|
|
2040
|
+
detail(`Only logic was staged on "${branch}" \u2014 this deploy changed no shared schema.`);
|
|
2041
|
+
}
|
|
2042
|
+
detail(`See what staged with \`xanosdk workspace export --branch ${branch}${contextFlags()}\`.`);
|
|
2043
|
+
if (!setLive) {
|
|
2044
|
+
detail(`Nothing serves "${branch}" yet. Promote it with \`xanosdk workspace branch set-live ${branch}${yes}${contextFlags()}\`.`);
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
async function runReleaseCommand(args, dest, opts = {}) {
|
|
2048
|
+
const req = resolveRequest(args);
|
|
2049
|
+
if (req.mode === "merge" && req.branch !== void 0) args = { ...args, branch: req.branch };
|
|
2050
|
+
assertBundleInput(args, { command: "deploy" });
|
|
2051
|
+
assertBundleFile(args, { command: "deploy" });
|
|
2052
|
+
const auth = opts.auth ?? await getAccessToken(args);
|
|
2053
|
+
const withSeed = req.mode === "replace" || req.records === true;
|
|
2054
|
+
const { discoverToolchainPlugins } = await import("./toolchain-modules-5EAZUONB.js");
|
|
2055
|
+
const toolchain = await discoverToolchainPlugins(process.cwd(), { frozen: args.frozenLock === true });
|
|
2056
|
+
const loaded = await loadBundleText(
|
|
2057
|
+
args.dryRun ? { ...args, lockReadOnly: true } : args,
|
|
2058
|
+
{ command: "deploy" },
|
|
2059
|
+
// A `--prune` preview says its orphan warning once the prune is planned,
|
|
2060
|
+
// without the entries that prune deletes — the real run leaves them out at
|
|
2061
|
+
// its lock commit, and the preview told the reader to `lock prune` by hand
|
|
2062
|
+
// what the prune was about to drop (E2E pass 25).
|
|
2063
|
+
{ withSeed, deferLockWrite: !args.dryRun, deferOrphanWarning: args.dryRun === true && args.prune === true }
|
|
2064
|
+
);
|
|
2065
|
+
const { source, content, nonPublicSeedValues } = loaded;
|
|
2066
|
+
let bundle = loaded.bundle;
|
|
2067
|
+
let lockCommitted = false;
|
|
2068
|
+
let resolvedTo;
|
|
2069
|
+
let nothingToAdopt = false;
|
|
2070
|
+
const commitLock = (opts2) => {
|
|
2071
|
+
if (lockCommitted) return;
|
|
2072
|
+
lockCommitted = true;
|
|
2073
|
+
loaded.commitLock?.({
|
|
2074
|
+
...opts2,
|
|
2075
|
+
...resolvedTo === void 0 ? {} : { to: resolvedTo },
|
|
2076
|
+
...nothingToAdopt ? { nothingToAdopt } : {}
|
|
2077
|
+
});
|
|
2078
|
+
};
|
|
2079
|
+
if (!args.dryRun && args.file !== void 0) {
|
|
2080
|
+
const { ignoreBackendSecretFilesRead } = await import("./deploy-command-JVZPWHMK.js");
|
|
2081
|
+
await ignoreBackendSecretFilesRead(args);
|
|
2082
|
+
}
|
|
2083
|
+
if (args.static !== void 0) {
|
|
2084
|
+
const { assertNoSeedLeaks } = await import("./deploy-command-JVZPWHMK.js");
|
|
2085
|
+
await assertNoSeedLeaks(args.static, nonPublicSeedValues);
|
|
2086
|
+
}
|
|
2087
|
+
{
|
|
2088
|
+
const { runBundleHooks } = await import("./toolchain-hooks-GHKLUJOH.js");
|
|
2089
|
+
const { readVersion } = await import("./cli.js");
|
|
2090
|
+
await runBundleHooks(
|
|
2091
|
+
toolchain.loaded,
|
|
2092
|
+
{
|
|
2093
|
+
bundle: loaded.bundleObject ?? JSON.parse(bundle),
|
|
2094
|
+
// Absent on the `--bundle <path>` branch: the project did not compile it.
|
|
2095
|
+
entry: args.file,
|
|
2096
|
+
cwd: process.cwd(),
|
|
2097
|
+
command: "deploy",
|
|
2098
|
+
frozen: args.frozenLock === true || args.dryRun === true,
|
|
2099
|
+
sdkVersion: readVersion()
|
|
2100
|
+
},
|
|
2101
|
+
args.dryRun === true ? "" : "; nothing was deployed"
|
|
2102
|
+
);
|
|
2103
|
+
}
|
|
2104
|
+
const { base: destBase, workspaceId, label: destLabel } = dest ?? {
|
|
2105
|
+
base: auth.instance,
|
|
2106
|
+
workspaceId: auth.workspaceId
|
|
2107
|
+
};
|
|
2108
|
+
const resolvedDest = { base: destBase, workspaceId, label: destLabel };
|
|
2109
|
+
const destTenantLabel = dest?.kind === "tenant" ? destLabel : void 0;
|
|
2110
|
+
const destNoun = destTenantLabel ?? `workspace #${workspaceId}`;
|
|
2111
|
+
const destShownLabel = destLabel !== void 0 && dest?.display !== void 0 && dest.display !== dest.name && !destLabel.includes(`(${JSON.stringify(dest.display)})`) ? `${destLabel} (${JSON.stringify(dest.display)})` : destLabel;
|
|
2112
|
+
const refusalFrame = {
|
|
2113
|
+
workspaceId,
|
|
2114
|
+
noun: destTenantLabel !== void 0 ? destShownLabel ?? destTenantLabel : destNoun,
|
|
2115
|
+
tenant: dest?.kind === "tenant",
|
|
2116
|
+
destination: destinationPayload(resolvedDest, dest, auth),
|
|
2117
|
+
mode: req.mode,
|
|
2118
|
+
prune: req.prune === true,
|
|
2119
|
+
dryRun: args.dryRun === true,
|
|
2120
|
+
adopt: adoptRemedy(args, dest),
|
|
2121
|
+
lockRename: lockRenameRemedy(args),
|
|
2122
|
+
pinnedInCode: codePinnedGuid(args)
|
|
2123
|
+
};
|
|
2124
|
+
const landingDest = dest?.kind === "tenant" ? dest.name !== void 0 && dest.name !== "" ? { kind: dest.type === "ephemeral" ? "ephemeral" : "tenant", name: dest.name } : void 0 : { kind: "workspace", workspaceId };
|
|
2125
|
+
const landingKey = landingDest === void 0 ? void 0 : destinationKey(auth.instance, landingDest);
|
|
2126
|
+
let tenantLive;
|
|
2127
|
+
let tenantRead;
|
|
2128
|
+
if (dest?.kind === "tenant") {
|
|
2129
|
+
tenantRead = await readLive(auth, { base: destBase, workspaceId, label: destLabel });
|
|
2130
|
+
tenantLive = tenantRead.live;
|
|
2131
|
+
bundle = keepWorkspaceName(bundle, tenantLive);
|
|
2132
|
+
}
|
|
2133
|
+
let branchLive;
|
|
2134
|
+
let withheldSettings = [];
|
|
2135
|
+
if (req.branch !== void 0 && dest?.kind !== "tenant") {
|
|
2136
|
+
const branchRead = await readLive(auth, resolvedDest);
|
|
2137
|
+
branchLive = { read: branchRead.live, failure: branchRead.failure };
|
|
2138
|
+
const withheld = withholdWorkspaceSettings(bundle, branchLive.read);
|
|
2139
|
+
bundle = withheld.bundle;
|
|
2140
|
+
withheldSettings = withheld.withheld;
|
|
2141
|
+
}
|
|
2142
|
+
const archive = encodeWorkspaceArchive(bundle, [...content, ...loaded.files]);
|
|
2143
|
+
if (req.records === true && content.length === 0) {
|
|
2144
|
+
warn("`--seed` was passed, but no table in this project declares seed rows \u2014 no rows will be written.", "seed.none-declared");
|
|
2145
|
+
}
|
|
2146
|
+
const target = {
|
|
2147
|
+
baseUrl: destBase,
|
|
2148
|
+
archive,
|
|
2149
|
+
workspaceId,
|
|
2150
|
+
...req,
|
|
2151
|
+
...opts.stateCheck !== void 0 ? { stateCheck: opts.stateCheck } : {}
|
|
2152
|
+
};
|
|
2153
|
+
const destHasBranches = dest?.kind !== "tenant";
|
|
2154
|
+
let branchReadError;
|
|
2155
|
+
let listing = { branches: [], unaddressable: [] };
|
|
2156
|
+
if (req.branch !== void 0 || args.backupBranch !== void 0 || req.mode === "replace" && destHasBranches) {
|
|
2157
|
+
try {
|
|
2158
|
+
listing = await listBranchListing(auth, { baseUrl: destBase, workspaceId });
|
|
2159
|
+
} catch (err) {
|
|
2160
|
+
if (req.mode !== "replace") throw err;
|
|
2161
|
+
branchReadError = err;
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
const branches = listing.branches;
|
|
2165
|
+
if (destHasBranches) {
|
|
2166
|
+
assertBranchDeletionAcknowledged(args, req, listing, branchReadError, workspaceId);
|
|
2167
|
+
}
|
|
2168
|
+
if (req.branch !== void 0) assertBranchAbsent(branches, req.branch);
|
|
2169
|
+
const plannedBackup = args.backupBranch !== void 0 && destHasBranches ? plannedBackupLabel(args.backupBranch, branches) : void 0;
|
|
2170
|
+
if (args.static !== void 0 && !args.dryRun) {
|
|
2171
|
+
refuseStaticHostAReplaceClears(args, req.mode === "replace", dest === void 0 ? "release" : "deploy");
|
|
2172
|
+
await assertStaticHostExists(args, auth, { baseUrl: destBase, workspaceId });
|
|
2173
|
+
}
|
|
2174
|
+
step(
|
|
2175
|
+
`${args.dryRun ? "Planning" : "Deploying"} ${source} \u2192 ${describeWriteTarget({
|
|
2176
|
+
base: destBase,
|
|
2177
|
+
// A tenant by the workspace it lives under, as its `destination` names
|
|
2178
|
+
// it — its own internal workspace is always #1 (E2E pass 36).
|
|
2179
|
+
workspaceId: dest?.kind === "tenant" ? auth.workspaceId : workspaceId,
|
|
2180
|
+
label: destShownLabel
|
|
2181
|
+
})}` + (req.branch !== void 0 ? ` branch "${req.branch}"` : "") + (req.mode === "replace" ? " (full replace)" : " (merge)")
|
|
2182
|
+
);
|
|
2183
|
+
const pinned = codePinnedCanonicals(
|
|
2184
|
+
bundle,
|
|
2185
|
+
releaseCanonicalLock(args, loaded.classifiedLock),
|
|
2186
|
+
args.file !== void 0
|
|
2187
|
+
);
|
|
2188
|
+
const preview = await xanosdkImport(auth, {
|
|
2189
|
+
...target,
|
|
2190
|
+
dryRun: true,
|
|
2191
|
+
pinned: pinned.map((p) => p.guid).filter((g) => g !== void 0)
|
|
2192
|
+
}).catch(async (err) => {
|
|
2193
|
+
if (err instanceof XanoSdkImportRefusal && err.retryable) {
|
|
2194
|
+
const { importInProgressError } = await import("./keep-data-merge-7MPLVAVI.js");
|
|
2195
|
+
const { retryCommand: retryCommand2, withheldNote: withheldNote2 } = await import("./retry-command-AS5EVREX.js");
|
|
2196
|
+
const retry = retryCommand2(args);
|
|
2197
|
+
throw importInProgressError(
|
|
2198
|
+
describeWriteTarget({ base: destBase, workspaceId, label: destShownLabel }),
|
|
2199
|
+
{ command: retry.command, note: withheldNote2(retry.withheld) },
|
|
2200
|
+
err
|
|
2201
|
+
);
|
|
2202
|
+
}
|
|
2203
|
+
if (isUnansweredLookup(err)) {
|
|
2204
|
+
const head = (err instanceof Error ? err.message : String(err)).split("\n")[0].trim().replace(/[.:]$/, "");
|
|
2205
|
+
const failed = new LookupFailedError(
|
|
2206
|
+
`${head}. ${unansweredCause(err)} \u2014 nothing was written`,
|
|
2207
|
+
"unreachable",
|
|
2208
|
+
dest?.kind === "tenant" ? dest.type === "ephemeral" ? "ephemeral" : "tenant" : "workspace"
|
|
2209
|
+
);
|
|
2210
|
+
failed.cause = err;
|
|
2211
|
+
throw failed;
|
|
2212
|
+
}
|
|
2213
|
+
throw withSignatureHint(err, args.bundle);
|
|
2214
|
+
});
|
|
2215
|
+
const destIsTenant = dest?.kind === "tenant";
|
|
2216
|
+
const destKindNoun = destIsTenant ? dest?.type ?? "tenant" : "workspace";
|
|
2217
|
+
if (destKindNoun === "ephemeral" && args.to?.startsWith("tenant:") === true) {
|
|
2218
|
+
resolvedTo = `ephemeral:${args.to.slice("tenant:".length)}`;
|
|
2219
|
+
}
|
|
2220
|
+
const otherBranches = req.mode === "replace" && destHasBranches && branchReadError === void 0 ? doomedBranches(listing).total : void 0;
|
|
2221
|
+
const plan = planFromRoute(preview, destIsTenant, otherBranches);
|
|
2222
|
+
nothingToAdopt = preview.conflicts.length === 0 && plan.operations.every((op) => op.action === "create" || IDENTITY_EXEMPT_TYPES.has(op.type));
|
|
2223
|
+
const heldHere = preview.conflicts.filter((c) => ownerIsDestination(c.owner, refusalFrame));
|
|
2224
|
+
const toolsetHeld = heldHere.some((c) => c.kind === "toolset" || c.kind === "agent" || c.kind === "mcpServer");
|
|
2225
|
+
const conflictLive = heldHere.length === 0 ? void 0 : dest?.kind === "tenant" ? tenantLive : refusalFrame.adopt !== void 0 ? await readLiveWorkspace(auth, resolvedDest) : toolsetHeld ? await readLiveWorkspace(auth, resolvedDest).catch(() => void 0) : void 0;
|
|
2226
|
+
const conflictFrame = refusalFrame.adopt !== void 0 && heldHere.length > 0 ? { ...refusalFrame, adoptAddsNothing: adoptionAddsNothing(args, conflictLive) } : refusalFrame;
|
|
2227
|
+
assertNoImportConflicts(preview, conflictFrame, {
|
|
2228
|
+
bundle: parsedOrUndefined(bundle),
|
|
2229
|
+
live: conflictLive,
|
|
2230
|
+
tenant: destIsTenant,
|
|
2231
|
+
noun: destKindNoun
|
|
2232
|
+
});
|
|
2233
|
+
const scopeBranch = collisionBranch(branches, req.branch);
|
|
2234
|
+
const liveRead = dest?.kind === "tenant" ? tenantRead ?? { live: void 0 } : branchLive !== void 0 && scopeBranch === void 0 ? { live: branchLive.read, failure: branchLive.failure } : await readLive(auth, resolvedDest, scopeBranch);
|
|
2235
|
+
const live = liveRead.live;
|
|
2236
|
+
const liveKind = dest?.kind === "tenant" ? dest.type === "ephemeral" ? "ephemeral" : "tenant" : "workspace";
|
|
2237
|
+
if (req.mode === "merge") {
|
|
2238
|
+
const lockPath = args.lockPath ?? (args.file !== void 0 ? resolveLockPath(args, args.file) : void 0);
|
|
2239
|
+
const lockFile = releaseCanonicalLock(args);
|
|
2240
|
+
assertNoKindSwap(
|
|
2241
|
+
parsedOrUndefined(bundle),
|
|
2242
|
+
live,
|
|
2243
|
+
refusalFrame,
|
|
2244
|
+
preview.branch,
|
|
2245
|
+
lockPath !== void 0 && lockFile !== void 0 ? { file: lockFile, path: lockPath } : void 0
|
|
2246
|
+
);
|
|
2247
|
+
}
|
|
2248
|
+
const converged = await convergedWithLive(live, req, bundle, pinned);
|
|
2249
|
+
const planContext = {
|
|
2250
|
+
bundle: parsedOrUndefined(bundle),
|
|
2251
|
+
live,
|
|
2252
|
+
tenant: destIsTenant,
|
|
2253
|
+
noun: destKindNoun,
|
|
2254
|
+
rows: { source: args.file !== void 0 ? "project" : "bundle", carried: plan.hasRecords },
|
|
2255
|
+
...req.prune === true ? { provenance: pruneProvenance(args, landingKey, live) } : {}
|
|
2256
|
+
};
|
|
2257
|
+
const presented = markUnchanged(
|
|
2258
|
+
presentOperations(plan.operations, planContext),
|
|
2259
|
+
converged ? void 0 : await unchangedWithLive(live, req, bundle, pinned),
|
|
2260
|
+
destKindNoun
|
|
2261
|
+
);
|
|
2262
|
+
const seeded = req.mode === "merge" && req.records === true && req.truncate !== true && plan.hasRecords ? await seedWrites(auth, target.archive, parsedOrUndefined(bundle), { base: destBase, workspaceId }) : [];
|
|
2263
|
+
renderPlan(
|
|
2264
|
+
plan,
|
|
2265
|
+
req,
|
|
2266
|
+
workspaceId,
|
|
2267
|
+
targetIsEmpty(live),
|
|
2268
|
+
converged,
|
|
2269
|
+
listing,
|
|
2270
|
+
branchReadError !== void 0,
|
|
2271
|
+
destTenantLabel,
|
|
2272
|
+
presented,
|
|
2273
|
+
destKindNoun,
|
|
2274
|
+
sameNamedCreates(plan, presented, live),
|
|
2275
|
+
envCarriedBy(parsedOrUndefined(bundle), live),
|
|
2276
|
+
args.dryRun === true,
|
|
2277
|
+
tableRenames(parsedOrUndefined(bundle), live),
|
|
2278
|
+
seeded
|
|
2279
|
+
);
|
|
2280
|
+
const branchDeletion = branchDeletionReport(
|
|
2281
|
+
req,
|
|
2282
|
+
listing,
|
|
2283
|
+
branchReadError !== void 0,
|
|
2284
|
+
destHasBranches
|
|
2285
|
+
);
|
|
2286
|
+
const priorStored = landingDest?.kind === "ephemeral" && landingKey !== void 0 ? storedColumnsOf(ephemeralLandedOn(process.cwd(), landingKey)) : void 0;
|
|
2287
|
+
const losses = await warnAboutLiveLosses(
|
|
2288
|
+
live,
|
|
2289
|
+
req,
|
|
2290
|
+
bundle,
|
|
2291
|
+
dest?.kind === "tenant" ? `tenant:${dest.name ?? "<name>"}` : "workspace",
|
|
2292
|
+
args.bundle !== void 0,
|
|
2293
|
+
await replaceCommandHint(args),
|
|
2294
|
+
destKindNoun,
|
|
2295
|
+
liveRead.failure,
|
|
2296
|
+
priorStored
|
|
2297
|
+
);
|
|
2298
|
+
if (req.branch !== void 0) reportWithheldSettings(req.branch, withheldSettings);
|
|
2299
|
+
const unnamedPublicUrls = warnAboutUnnamedPublicUrls(
|
|
2300
|
+
bundle,
|
|
2301
|
+
releaseCanonicalLock(args, loaded.classifiedLock) !== void 0
|
|
2302
|
+
);
|
|
2303
|
+
const sharedChanges = req.branch === void 0 ? [] : assertSharedSchemaAcknowledged(args, live, bundle, req.branch);
|
|
2304
|
+
const pruned = req.prune === true ? assertPruneStaysInScope(args, plan, { key: landingKey, noun: destNoun }, planContext, presented, {
|
|
2305
|
+
frame: refusalFrame,
|
|
2306
|
+
branch: preview.branch
|
|
2307
|
+
}) : {};
|
|
2308
|
+
loaded.warnLockOrphans?.(new Set(Object.keys(pruned)));
|
|
2309
|
+
if (req.mode === "merge") {
|
|
2310
|
+
const pending = await unrecordedTableRenames(args, bundle, live, plan, loaded.classifiedLock);
|
|
2311
|
+
if (pending.length > 0) throw await pendingRenameRefusal(refusalFrame, pending, req.prune === true, preview.branch);
|
|
2312
|
+
}
|
|
2313
|
+
const dryRunUpToDate = req.branch === void 0 && (plan.operations.length === 0 || converged);
|
|
2314
|
+
if (req.mode === "merge" && destIsTenant && live !== void 0 && !dryRunUpToDate) {
|
|
2315
|
+
const { liveTableTriggers, tableTriggerMergeError } = await import("./keep-data-merge-7MPLVAVI.js");
|
|
2316
|
+
const triggers = liveTableTriggers(live);
|
|
2317
|
+
if (triggers.length > 0) {
|
|
2318
|
+
const { retryCommand: retryCommand2, withheldNote: withheldNote2 } = await import("./retry-command-AS5EVREX.js");
|
|
2319
|
+
const replace = retryCommand2(args, { add: ["--replace"], drop: ["--prune", "--yes", "-y"] });
|
|
2320
|
+
throw tableTriggerMergeError(destNoun, triggers, {
|
|
2321
|
+
command: replace.command,
|
|
2322
|
+
note: withheldNote2(replace.withheld)
|
|
2323
|
+
});
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
if (req.mode === "merge" && live !== void 0 && !dryRunUpToDate && losses.storageChanges.length > 0) {
|
|
2327
|
+
const { retryCommand: retryCommand2, withheldNote: withheldNote2 } = await import("./retry-command-AS5EVREX.js");
|
|
2328
|
+
const replace = retryCommand2(args, { add: ["--replace"], drop: ["--prune", "--yes", "-y"] });
|
|
2329
|
+
await assertStorageModesKept(losses.storageChanges, tableRowCounter(auth, { workspaceId, base: destBase }), {
|
|
2330
|
+
target: destNoun,
|
|
2331
|
+
subject: "the deploy",
|
|
2332
|
+
remedy: `rebuild the ${destKindNoun} with \`${replace.command}\` (that replaces every table's rows).${withheldNote2(replace.withheld)}`
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2335
|
+
const staticRead = req.mode === "replace" && !dryRunUpToDate ? await readStaticTeardown(auth, { baseUrl: destBase, workspaceId }, void 0) : void 0;
|
|
2336
|
+
if (staticRead !== void 0 && warnStaticTeardown(staticRead, destNoun, {
|
|
2337
|
+
publishing: args.static !== void 0,
|
|
2338
|
+
keepHint: "drop `--replace` to merge, which leaves it serving"
|
|
2339
|
+
})) {
|
|
2340
|
+
blank();
|
|
2341
|
+
}
|
|
2342
|
+
const canonicalMoves = plannedCanonicalMoves(preview.canonicals, planContext);
|
|
2343
|
+
const movesWhen = req.branch === void 0 || args.setLive ? void 0 : "once the branch is made live";
|
|
2344
|
+
if (args.dryRun) {
|
|
2345
|
+
reportCanonicalDifferences(canonicalDifferences(preview.canonicals), planContext, true);
|
|
2346
|
+
reportPlannedCanonicalMoves(canonicalMoves, "would", movesWhen);
|
|
2347
|
+
if (plannedBackup !== void 0) {
|
|
2348
|
+
info(`A real run snapshots the live branch as "${plannedBackup}" before importing.`);
|
|
2349
|
+
}
|
|
2350
|
+
info("Dry run \u2014 the archive was sent for planning only, and nothing was written.");
|
|
2351
|
+
if (isMachineOutput(args)) {
|
|
2352
|
+
writeJson({
|
|
2353
|
+
landed: false,
|
|
2354
|
+
dryRun: true,
|
|
2355
|
+
declined: false,
|
|
2356
|
+
// Always present; a dry run records nothing — what it would land is
|
|
2357
|
+
// not landed, and the record is what a prune deletes by.
|
|
2358
|
+
landingRecord: null,
|
|
2359
|
+
workspaceId,
|
|
2360
|
+
destination: destinationPayload(resolvedDest, dest, auth),
|
|
2361
|
+
// The verdict the real run would reach, said up front: a wrapper
|
|
2362
|
+
// deciding whether a deploy is needed reads one key.
|
|
2363
|
+
upToDate: dryRunUpToDate,
|
|
2364
|
+
// The real run's keys, so a wrapper reads one document shape either
|
|
2365
|
+
// way. `records` is whether the real run writes rows — the plan's
|
|
2366
|
+
// answer, which the "rows would be written" warning states too.
|
|
2367
|
+
mode: req.mode,
|
|
2368
|
+
prune: req.prune === true,
|
|
2369
|
+
records: !dryRunUpToDate && plan.hasRecords,
|
|
2370
|
+
...seeded.length > 0 ? { seedRows: seeded } : {},
|
|
2371
|
+
truncate: req.truncate === true,
|
|
2372
|
+
operations: dryRunUpToDate ? 0 : plan.operations.length,
|
|
2373
|
+
plan: planForOutput(plan, presented, destIsTenant, converged, destKindNoun),
|
|
2374
|
+
// A tenant has no branches, so it is planned against none — the route's
|
|
2375
|
+
// echo of its internal default describes nothing the reader chose.
|
|
2376
|
+
branch: destIsTenant ? null : preview.branch ?? null,
|
|
2377
|
+
conflicts: conflictsForOutput(preview.conflicts, planContext),
|
|
2378
|
+
canonicals: plannedCanonicalsForOutput(preview.canonicals, planContext),
|
|
2379
|
+
...branchDeletion !== void 0 ? { branchDeletion } : {},
|
|
2380
|
+
// What the plan cannot say, under the keys the real run's summary uses:
|
|
2381
|
+
// the text dry run warns about each, and this is where a wrapper decides.
|
|
2382
|
+
...landingTableFields(losses),
|
|
2383
|
+
unchangedEnv: losses.unchangedEnv,
|
|
2384
|
+
droppedEnv: losses.droppedEnv,
|
|
2385
|
+
unappliedDocumentation: losses.unappliedDocumentation,
|
|
2386
|
+
workspaceRename: losses.workspaceRename ?? null,
|
|
2387
|
+
workspaceSettingsNotApplied: withheldSettings,
|
|
2388
|
+
// The real run's key: how many shared-schema changes a branch landing
|
|
2389
|
+
// carries (`null` when they could not be read) — above zero without
|
|
2390
|
+
// `--allow-shared-schema-changes`, the real run refuses.
|
|
2391
|
+
...req.branch !== void 0 ? { sharedSchemaChanges: sharedChanges?.length ?? null } : {},
|
|
2392
|
+
...unnamedPublicUrls.length > 0 ? { unnamedPublicUrls: [...unnamedPublicUrls] } : {},
|
|
2393
|
+
...staticRemovedField(staticRead?.teardown, void 0)
|
|
2394
|
+
});
|
|
2395
|
+
}
|
|
2396
|
+
return;
|
|
2397
|
+
}
|
|
2398
|
+
let backupLabel;
|
|
2399
|
+
let applyResponse;
|
|
2400
|
+
let promoted = args.setLive;
|
|
2401
|
+
let liveRestored;
|
|
2402
|
+
const upToDate = dryRunUpToDate;
|
|
2403
|
+
if (upToDate) {
|
|
2404
|
+
commitLock();
|
|
2405
|
+
success(`${destNoun.charAt(0).toUpperCase()}${destNoun.slice(1)} is already up to date \u2014 no import sent.`);
|
|
2406
|
+
} else {
|
|
2407
|
+
assertLiveWorkspaceWasRead(live, destNoun, scopeBranch, liveRead.failure, liveKind);
|
|
2408
|
+
reportPlannedCanonicalMoves(canonicalMoves, "will", movesWhen);
|
|
2409
|
+
if (req.branch !== void 0 && args.setLive && !args.yes && process.stdin.isTTY !== true) {
|
|
2410
|
+
const { yesRerun } = await import("./retry-command-AS5EVREX.js");
|
|
2411
|
+
const { rerun, note } = yesRerun(args, "deploy");
|
|
2412
|
+
throw needsConfirmation(
|
|
2413
|
+
`Make "${req.branch}" the live branch?${movesClause(canonicalMoves)} (or drop --set-live to stage it)`,
|
|
2414
|
+
{ details: { landed: false, setLive: false, branch: req.branch }, rerun, note },
|
|
2415
|
+
`Nothing was written: the \`--set-live\` cutover asks after landing, and stdin is not a terminal.`
|
|
2416
|
+
);
|
|
2417
|
+
}
|
|
2418
|
+
if ((isDestructive(req) || losses.droppedColumns.length > 0 || losses.retypedColumns.length > 0 || losses.narrowedEnums.length > 0 || losses.droppedEnv.length > 0 || // A public URL this run moves — not a staged branch's, which moves
|
|
2419
|
+
// only at its set-live, and that asks then.
|
|
2420
|
+
movesWhen === void 0 && canonicalMoves.length > 0) && !args.yes && destKindNoun !== "ephemeral") {
|
|
2421
|
+
const unsent = () => ({
|
|
2422
|
+
landed: false,
|
|
2423
|
+
dryRun: false,
|
|
2424
|
+
declined: false,
|
|
2425
|
+
workspaceId,
|
|
2426
|
+
destination: destinationPayload(resolvedDest, dest, auth),
|
|
2427
|
+
upToDate: false,
|
|
2428
|
+
mode: req.mode,
|
|
2429
|
+
prune: req.prune === true,
|
|
2430
|
+
records: false,
|
|
2431
|
+
truncate: req.truncate === true,
|
|
2432
|
+
// The PLANNED count, as the dry run's document gives it for the same
|
|
2433
|
+
// plan (E2E pass 29: 0 here, 7 there); `landed: false` says none went.
|
|
2434
|
+
operations: plan.operations.length,
|
|
2435
|
+
plan: planForOutput(plan, presented, destIsTenant, converged, destKindNoun),
|
|
2436
|
+
branch: destIsTenant ? null : preview.branch ?? null,
|
|
2437
|
+
conflicts: conflictsForOutput(preview.conflicts, planContext),
|
|
2438
|
+
canonicals: plannedCanonicalsForOutput(preview.canonicals, planContext),
|
|
2439
|
+
...branchDeletion !== void 0 ? { branchDeletion } : {},
|
|
2440
|
+
...landingTableFields(losses),
|
|
2441
|
+
unchangedEnv: losses.unchangedEnv,
|
|
2442
|
+
droppedEnv: losses.droppedEnv,
|
|
2443
|
+
unappliedDocumentation: losses.unappliedDocumentation,
|
|
2444
|
+
workspaceRename: losses.workspaceRename ?? null,
|
|
2445
|
+
workspaceSettingsNotApplied: withheldSettings,
|
|
2446
|
+
landingRecord: null,
|
|
2447
|
+
...seeded.length > 0 ? { seedRows: seeded } : {}
|
|
2448
|
+
});
|
|
2449
|
+
const { yesRerun } = await import("./retry-command-AS5EVREX.js");
|
|
2450
|
+
const { rerun, note } = yesRerun(args, "deploy");
|
|
2451
|
+
const ok = await confirm(`Apply this plan to ${destNoun}?${seedOverwriteClause(seeded)}${movesClause(canonicalMoves, movesWhen === void 0 ? "It" : "Once live, it")}`, {
|
|
2452
|
+
flag: "--yes",
|
|
2453
|
+
refusal: { details: unsent(), rerun, note }
|
|
2454
|
+
});
|
|
2455
|
+
if (!ok) {
|
|
2456
|
+
info("Deploy cancelled \u2014 nothing was written.");
|
|
2457
|
+
if (isMachineOutput(args)) writeJson({ ...unsent(), declined: true });
|
|
2458
|
+
return;
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
commitLock({ pruned: new Set(Object.keys(pruned)) });
|
|
2462
|
+
if (args.backupBranch !== void 0) {
|
|
2463
|
+
const sourceLabel = liveBranchLabel(branches);
|
|
2464
|
+
const label = plannedBackup ?? plannedBackupLabel(args.backupBranch, branches);
|
|
2465
|
+
step(`Snapshotting the live branch as "${label}" before importing\u2026`);
|
|
2466
|
+
try {
|
|
2467
|
+
await createBranch(auth, {
|
|
2468
|
+
baseUrl: destBase,
|
|
2469
|
+
workspaceId,
|
|
2470
|
+
label,
|
|
2471
|
+
...sourceLabel !== void 0 ? { sourceBranch: sourceLabel } : {},
|
|
2472
|
+
description: `Pre-deploy snapshot taken by xanosdk from ${source}`
|
|
2473
|
+
});
|
|
2474
|
+
} catch (err) {
|
|
2475
|
+
throw new Error(
|
|
2476
|
+
`The pre-deploy backup branch could not be created, so the deploy was not sent.
|
|
2477
|
+
Nothing has changed in workspace #${workspaceId}.
|
|
2478
|
+
Retry, or drop \`--backup-branch\` to deploy without one.`,
|
|
2479
|
+
{ cause: err }
|
|
2480
|
+
);
|
|
2481
|
+
}
|
|
2482
|
+
backupLabel = label;
|
|
2483
|
+
success(`Live branch snapshotted as "${label}".`);
|
|
2484
|
+
}
|
|
2485
|
+
try {
|
|
2486
|
+
applyResponse = await describeWrite(
|
|
2487
|
+
{ what: `the ${req.mode === "replace" ? "replace" : "merge"} into ${destShownLabel ?? `workspace #${workspaceId}`}` },
|
|
2488
|
+
() => xanosdkImport(auth, {
|
|
2489
|
+
...target,
|
|
2490
|
+
dryRun: false,
|
|
2491
|
+
pinned: pinned.map((p) => p.guid).filter((g) => g !== void 0)
|
|
2492
|
+
})
|
|
2493
|
+
);
|
|
2494
|
+
} catch (err) {
|
|
2495
|
+
if (err instanceof XanoSdkImportRefusal) {
|
|
2496
|
+
const { retryCommand: retryCommand2, withheldNote: withheldNote2 } = await import("./retry-command-AS5EVREX.js");
|
|
2497
|
+
const retry = retryCommand2(args);
|
|
2498
|
+
reportApplyRefusal(
|
|
2499
|
+
err,
|
|
2500
|
+
refusalFrame,
|
|
2501
|
+
preview.branch,
|
|
2502
|
+
backupLabel,
|
|
2503
|
+
{ command: retry.command, note: withheldNote2(retry.withheld) },
|
|
2504
|
+
losses.indexChanges,
|
|
2505
|
+
req.records === true ? seededUniqueIndexes(bundle, content) : []
|
|
2506
|
+
);
|
|
2507
|
+
}
|
|
2508
|
+
if (backupLabel !== void 0) {
|
|
2509
|
+
blank();
|
|
2510
|
+
warn(
|
|
2511
|
+
`The import failed. Your pre-deploy snapshot is on branch "${backupLabel}".`,
|
|
2512
|
+
"deploy.import-failed-backup",
|
|
2513
|
+
[
|
|
2514
|
+
`Restore it with \`xanosdk workspace branch set-live ${backupLabel}${pipedYes(args)}${contextFlags()}\` \u2014 that restores logic, not tables: every branch shares them, so whatever the import did to tables and rows stays.`
|
|
2515
|
+
]
|
|
2516
|
+
);
|
|
2517
|
+
}
|
|
2518
|
+
throw err;
|
|
2519
|
+
}
|
|
2520
|
+
const landed = req.mode === "replace" ? DEFAULT_BRANCH_LABEL : applyResponse?.branch ?? req.branch;
|
|
2521
|
+
success(
|
|
2522
|
+
req.branch !== void 0 ? `Deployed to workspace #${workspaceId} on branch "${landed}"` : destTenantLabel !== void 0 ? `Deployed to ${destTenantLabel}` : (
|
|
2523
|
+
// Named even without `--branch`: a merge lands on whichever branch
|
|
2524
|
+
// is live, and when that is not the default this is where a reader
|
|
2525
|
+
// learns it.
|
|
2526
|
+
`Deployed to instance workspace #${workspaceId}${landed === void 0 ? "" : ` on branch "${landed}"`}`
|
|
2527
|
+
)
|
|
2528
|
+
);
|
|
2529
|
+
if (req.branch !== void 0) {
|
|
2530
|
+
if (args.setLive) {
|
|
2531
|
+
const previous = liveBranchLabel(branches);
|
|
2532
|
+
blank();
|
|
2533
|
+
if (previous !== void 0) {
|
|
2534
|
+
warn(
|
|
2535
|
+
`Promoting "${req.branch}" is a production cutover for workspace #${workspaceId}.`,
|
|
2536
|
+
"branch.production-cutover",
|
|
2537
|
+
[`The runtime stops serving "${previous}" and starts serving "${req.branch}".`]
|
|
2538
|
+
);
|
|
2539
|
+
} else {
|
|
2540
|
+
warn(
|
|
2541
|
+
`No branch is live in workspace #${workspaceId} \u2014 promoting "${req.branch}" makes it the branch the runtime serves.`,
|
|
2542
|
+
"branch.none-live"
|
|
2543
|
+
);
|
|
2544
|
+
}
|
|
2545
|
+
if (!args.yes && !await confirm(`Make "${req.branch}" the live branch?${movesClause(canonicalMoves)}`, {
|
|
2546
|
+
flag: "--yes",
|
|
2547
|
+
refusal: {
|
|
2548
|
+
details: { landed: true, setLive: false, branch: req.branch },
|
|
2549
|
+
rerun: `xanosdk workspace branch set-live ${shellWord(req.branch)} --yes${contextFlags(args)}`
|
|
2550
|
+
}
|
|
2551
|
+
})) {
|
|
2552
|
+
info(`Set-live cancelled \u2014 nothing was switched: the deploy is staged on "${req.branch}" and nothing serves it yet.`);
|
|
2553
|
+
detail(`Promote it later with \`xanosdk workspace branch set-live ${req.branch}${pipedYes(args)}${contextFlags()}\`.`);
|
|
2554
|
+
promoted = false;
|
|
2555
|
+
} else {
|
|
2556
|
+
await setLiveBranch(auth, { baseUrl: destBase, workspaceId, label: req.branch });
|
|
2557
|
+
success(`Branch "${req.branch}" is now live.`);
|
|
2558
|
+
if (previous !== void 0) {
|
|
2559
|
+
detail(setLiveRollback(previous, args));
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
reportBranchCaveats(
|
|
2564
|
+
req.branch,
|
|
2565
|
+
promoted,
|
|
2566
|
+
sharedChanges?.length,
|
|
2567
|
+
ungatedSchemaChanges(sharedSchemaChanges(JSON.parse(bundle), live, { prune: args.prune })),
|
|
2568
|
+
pipedYes(args)
|
|
2569
|
+
);
|
|
2570
|
+
} else if (backupLabel !== void 0) {
|
|
2571
|
+
blank();
|
|
2572
|
+
detail(
|
|
2573
|
+
`Roll back this deploy's logic with \`xanosdk workspace branch set-live ${backupLabel}${pipedYes(args)}${contextFlags()}\` \u2014 it restores logic, not tables: every branch shares them, so what this deploy did to tables and rows stays.`
|
|
2574
|
+
);
|
|
2575
|
+
}
|
|
2576
|
+
if (req.mode === "replace" && destHasBranches) {
|
|
2577
|
+
liveRestored = await restoreLiveAfterReplace(auth, { baseUrl: destBase, workspaceId }, DEFAULT_BRANCH_LABEL);
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
const landingMode = req.mode === "replace" ? "replace" : "merge";
|
|
2581
|
+
const storedAfter = priorStored === void 0 ? void 0 : storedColumnsAfter(
|
|
2582
|
+
parsedOrUndefined(bundle),
|
|
2583
|
+
req.mode === "replace" ? void 0 : live,
|
|
2584
|
+
priorStored,
|
|
2585
|
+
req.mode === "replace" ? "rebuild" : "merge"
|
|
2586
|
+
);
|
|
2587
|
+
const landingRecord = landingDest === void 0 ? null : args.file === void 0 && args.lockPath === void 0 ? recordForeignLanding({
|
|
2588
|
+
lockPath: landingLockPath(args),
|
|
2589
|
+
instance: auth.instance,
|
|
2590
|
+
dest: landingDest,
|
|
2591
|
+
bundle: parsedOrUndefined(bundle),
|
|
2592
|
+
mode: landingMode,
|
|
2593
|
+
removed: pruned,
|
|
2594
|
+
...storedAfter !== void 0 ? { stored: storedAfter } : {}
|
|
2595
|
+
}) : recordLanding({
|
|
2596
|
+
lockPath: landingLockPath(args, { entryFile: args.file, fromProject: false }),
|
|
2597
|
+
instance: auth.instance,
|
|
2598
|
+
dest: landingDest,
|
|
2599
|
+
update: {
|
|
2600
|
+
mode: landingMode,
|
|
2601
|
+
identities: withStoredColumns(landedIdentities(parsedOrUndefined(bundle)), storedAfter),
|
|
2602
|
+
removed: pruned
|
|
2603
|
+
}
|
|
2604
|
+
});
|
|
2605
|
+
const servedDifferences = canonicalDifferences((applyResponse ?? preview).canonicals);
|
|
2606
|
+
reportCanonicalDifferences(servedDifferences, planContext);
|
|
2607
|
+
if (!args.dryRun) opts.afterLanding?.(parsedOrUndefined(bundle));
|
|
2608
|
+
link(destBase);
|
|
2609
|
+
const summary = {
|
|
2610
|
+
// `landed: false` with `upToDate: true` is a SUCCESS, not a failure — the
|
|
2611
|
+
// workspace already matches. A wrapper gating on `landed` alone would read
|
|
2612
|
+
// a converged release as a broken one, so both fields are always present.
|
|
2613
|
+
landed: !upToDate,
|
|
2614
|
+
// The dry run's keys, so a wrapper reads one document shape either way.
|
|
2615
|
+
dryRun: false,
|
|
2616
|
+
declined: false,
|
|
2617
|
+
landingRecord,
|
|
2618
|
+
workspaceId,
|
|
2619
|
+
plan: planForOutput(plan, presented, destIsTenant, converged),
|
|
2620
|
+
conflicts: conflictsForOutput((applyResponse ?? preview).conflicts, planContext),
|
|
2621
|
+
upToDate,
|
|
2622
|
+
mode: req.mode,
|
|
2623
|
+
prune: req.prune === true,
|
|
2624
|
+
// What HAPPENED, not the flag that was asked for. This is the one field
|
|
2625
|
+
// someone checks to confirm a seed landed, and echoing the input into it
|
|
2626
|
+
// reported success over a write that never occurred. Rows go
|
|
2627
|
+
// out only when the archive actually carried some AND the import ran —
|
|
2628
|
+
// which covers both ways rows get written: `--seed` on a merge, and a
|
|
2629
|
+
// replace, where the rows ride along with no flag at all. `content` is only
|
|
2630
|
+
// ever populated when one of those asked for it (see `withSeed` above), so
|
|
2631
|
+
// a merge without `--seed` still reports false, and so does a replace of a
|
|
2632
|
+
// project that declares no seed rows.
|
|
2633
|
+
records: content.length > 0 && !upToDate,
|
|
2634
|
+
truncate: req.truncate === true,
|
|
2635
|
+
destination: destinationPayload(resolvedDest, dest, auth),
|
|
2636
|
+
// What this release will DO, not what the plan enumerated: a converged
|
|
2637
|
+
// release sends nothing, and reporting the server's matched-identity count
|
|
2638
|
+
// beside `upToDate: true` would contradict it.
|
|
2639
|
+
operations: upToDate ? 0 : plan.operations.length,
|
|
2640
|
+
...landingTableFields(losses),
|
|
2641
|
+
unchangedEnv: losses.unchangedEnv,
|
|
2642
|
+
droppedEnv: losses.droppedEnv,
|
|
2643
|
+
unappliedDocumentation: losses.unappliedDocumentation,
|
|
2644
|
+
// Always present, `null` when nothing is renamed — the dry run's key too,
|
|
2645
|
+
// so a wrapper reads one field on both.
|
|
2646
|
+
workspaceRename: losses.workspaceRename ?? null,
|
|
2647
|
+
workspaceSettingsNotApplied: withheldSettings,
|
|
2648
|
+
...unnamedPublicUrls.length > 0 ? { unnamedPublicUrls: [...unnamedPublicUrls] } : {},
|
|
2649
|
+
// The label the instance says it planned or landed against — the same rule
|
|
2650
|
+
// as the success line above, and the dry run's `branch`, so a converged
|
|
2651
|
+
// run carries it too. `assertXanoSdkBranchHonored` has already refused any
|
|
2652
|
+
// disagreement, so this can only ever restate it.
|
|
2653
|
+
branch: destIsTenant ? null : applyResponse?.branch ?? preview.branch ?? null,
|
|
2654
|
+
// Present only with `--branch`: a plain release promotes nothing.
|
|
2655
|
+
...req.branch !== void 0 ? {
|
|
2656
|
+
setLive: promoted,
|
|
2657
|
+
sharedSchemaChanges: sharedChanges?.length
|
|
2658
|
+
} : {},
|
|
2659
|
+
...backupLabel !== void 0 ? { backupBranch: backupLabel } : {},
|
|
2660
|
+
// Present only when the replace left nothing live and this run set it.
|
|
2661
|
+
...liveRestored !== void 0 ? { liveRestored } : {},
|
|
2662
|
+
// Reported on the APPLY as well as the dry run: a tool reconciling what a
|
|
2663
|
+
// release actually did needs the loss in the same shape it was shown the
|
|
2664
|
+
// loss it authorized.
|
|
2665
|
+
...branchDeletion !== void 0 ? { branchDeletion } : {},
|
|
2666
|
+
// What the instance actually served: the applied response when an import
|
|
2667
|
+
// ran, and the preview's own read when the release was already converged.
|
|
2668
|
+
canonicals: canonicalsForOutput((applyResponse ?? preview).canonicals, planContext)
|
|
2669
|
+
};
|
|
2670
|
+
if (args.static !== void 0) {
|
|
2671
|
+
if (servedDifferences.length > 0) {
|
|
2672
|
+
if (isMachineOutput(args)) writeJson(summary);
|
|
2673
|
+
assertStaticMatchesServedUrls(servedDifferences, planContext);
|
|
2674
|
+
}
|
|
2675
|
+
const { buildStaticEnv, deployStaticTo } = await import("./deploy-command-JVZPWHMK.js");
|
|
2676
|
+
const env = buildStaticEnv(destBase, args.staticEnv);
|
|
2677
|
+
const explicit = Object.keys(args.staticEnv).length > 0;
|
|
2678
|
+
const to = dest?.kind === "tenant" ? `${dest.type === "ephemeral" ? "ephemeral" : "tenant"}:${dest.name ?? ""}` : "workspace";
|
|
2679
|
+
const { noteLanded } = await import("./sent-writes-N7X4OVXT.js");
|
|
2680
|
+
const backendLabel = destNoun;
|
|
2681
|
+
noteLanded({ said: upToDate ? `The backend on ${backendLabel} was already up to date.` : `Deployed to ${backendLabel}.` });
|
|
2682
|
+
try {
|
|
2683
|
+
summary.static = await deployStaticTo(
|
|
2684
|
+
args.static,
|
|
2685
|
+
auth,
|
|
2686
|
+
// Named as the step line named the destination: a tenant's internal
|
|
2687
|
+
// workspace is #1, which names nothing the reader typed.
|
|
2688
|
+
{ baseUrl: destBase, workspaceId, label: destTenantLabel },
|
|
2689
|
+
env,
|
|
2690
|
+
explicit,
|
|
2691
|
+
args.staticHost,
|
|
2692
|
+
args.skipLiveness,
|
|
2693
|
+
args.staticRouting,
|
|
2694
|
+
void 0,
|
|
2695
|
+
{ rerun: staticRetryCommand(args.static, to, args, void 0) }
|
|
2696
|
+
);
|
|
2697
|
+
} catch (err) {
|
|
2698
|
+
const unknown = err instanceof Error && statesOutcomeUnknown(err.message);
|
|
2699
|
+
const message = unknownStaticOutcome(err instanceof Error ? err.message : String(err), unknown);
|
|
2700
|
+
summary.static = {
|
|
2701
|
+
url: void 0,
|
|
2702
|
+
error: message,
|
|
2703
|
+
retry: staticRetryCommand(args.static, to, args, err),
|
|
2704
|
+
completed: unknown ? "unknown" : "no"
|
|
2705
|
+
};
|
|
2706
|
+
warn(
|
|
2707
|
+
`The static-host upload failed \u2014 the backend ${upToDate ? "is unchanged" : "deploy stands"}:`,
|
|
2708
|
+
"static.upload-failed",
|
|
2709
|
+
[message]
|
|
2710
|
+
);
|
|
2711
|
+
if (!upToDate) noteStaticTakenDown(staticRead?.teardown, unknown);
|
|
2712
|
+
detail(staticRetryHint(args.static, to, args, err));
|
|
2713
|
+
process.exitCode = unknown ? EXIT_OUTCOME_UNKNOWN : EXIT_STATIC_FAILED;
|
|
2714
|
+
} finally {
|
|
2715
|
+
noteLanded(void 0);
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
if (!upToDate) Object.assign(summary, staticRemovedField(staticRead?.teardown, summary.static?.url));
|
|
2719
|
+
if (isMachineOutput(args)) writeJson(summary);
|
|
2720
|
+
}
|
|
2721
|
+
function setLiveRollback(previous, args) {
|
|
2722
|
+
return `Roll back with \`xanosdk workspace branch set-live ${shellWord(previous)}${pipedYes(args)}${contextFlags(args)}\`.`;
|
|
2723
|
+
}
|
|
2724
|
+
async function assertReleaseIdentitiesFree(args, auth, opts) {
|
|
2725
|
+
const tenant = opts.tenant;
|
|
2726
|
+
const target = tenant === void 0 ? { baseUrl: auth.instance, workspaceId: auth.workspaceId } : { baseUrl: tenant.base, workspaceId: tenant.workspaceId };
|
|
2727
|
+
const { detectXanoSdkImportRoute } = await import("./xanosdk-import-GN42TO2B.js");
|
|
2728
|
+
let capable;
|
|
2729
|
+
try {
|
|
2730
|
+
capable = await detectXanoSdkImportRoute(auth, target);
|
|
2731
|
+
} catch (err) {
|
|
2732
|
+
if (err?.status === 501) capable = false;
|
|
2733
|
+
else {
|
|
2734
|
+
const { unreachableProbeError } = await import("./keep-data-merge-7MPLVAVI.js");
|
|
2735
|
+
const retry = retryCommand(args, {
|
|
2736
|
+
add: pipedYes(args) === "" ? [] : ["--yes"],
|
|
2737
|
+
command: [opts.verb, ...args.positionals.map(shellWord)].join(" ")
|
|
2738
|
+
});
|
|
2739
|
+
throw await unreachableProbeError(err, {
|
|
2740
|
+
label: tenant === void 0 ? `workspace #${auth.workspaceId}` : `${tenant.type} "${tenant.name}"`,
|
|
2741
|
+
kind: tenant === void 0 ? "workspace" : tenant.type,
|
|
2742
|
+
rerun: { command: retry.command, note: withheldNote(retry.withheld) }
|
|
2743
|
+
});
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
if (!capable) {
|
|
2747
|
+
return { checked: false, reason: "this instance has no route to plan the landing through" };
|
|
2748
|
+
}
|
|
2749
|
+
const lockArgs = { ...args, ...opts.lockPath === void 0 ? {} : { lockPath: opts.lockPath } };
|
|
2750
|
+
const lock = opts.lockPath !== void 0 && existsSync(opts.lockPath) ? readLockFile(opts.lockPath) : void 0;
|
|
2751
|
+
const pinned = pinnedCanonicalGuids(JSON.stringify(opts.bundle), lock, false);
|
|
2752
|
+
const res = await xanosdkImport(auth, {
|
|
2753
|
+
...target,
|
|
2754
|
+
archive: opts.archive,
|
|
2755
|
+
dryRun: true,
|
|
2756
|
+
mode: "merge",
|
|
2757
|
+
records: false,
|
|
2758
|
+
...opts.branch === void 0 ? {} : { branch: opts.branch },
|
|
2759
|
+
pinned
|
|
2760
|
+
});
|
|
2761
|
+
const resolved = { base: target.baseUrl, workspaceId: target.workspaceId };
|
|
2762
|
+
const dest = tenant === void 0 ? void 0 : {
|
|
2763
|
+
...resolved,
|
|
2764
|
+
kind: "tenant",
|
|
2765
|
+
type: tenant.type,
|
|
2766
|
+
name: tenant.name,
|
|
2767
|
+
...tenant.display === void 0 ? {} : { display: tenant.display }
|
|
2768
|
+
};
|
|
2769
|
+
const frame = {
|
|
2770
|
+
workspaceId: target.workspaceId,
|
|
2771
|
+
noun: tenant === void 0 ? `workspace #${auth.workspaceId}` : `${tenant.type} "${tenant.name}"${tenant.display === void 0 ? "" : ` ("${tenant.display}")`}`,
|
|
2772
|
+
tenant: tenant !== void 0,
|
|
2773
|
+
verb: opts.verb,
|
|
2774
|
+
destination: destinationPayload(resolved, dest, auth),
|
|
2775
|
+
mode: "merge",
|
|
2776
|
+
prune: false,
|
|
2777
|
+
dryRun: false,
|
|
2778
|
+
adopt: adoptRemedy(lockArgs, dest),
|
|
2779
|
+
lockRename: lockRenameRemedy(lockArgs),
|
|
2780
|
+
pinnedInCode: codePinnedGuid(lockArgs)
|
|
2781
|
+
};
|
|
2782
|
+
try {
|
|
2783
|
+
const heldHere = res.conflicts.some((c) => ownerIsDestination(c.owner, frame));
|
|
2784
|
+
assertNoImportConflicts(
|
|
2785
|
+
res,
|
|
2786
|
+
heldHere ? { ...frame, adoptAddsNothing: adoptionAddsNothing(lockArgs, opts.live) } : frame,
|
|
2787
|
+
{ bundle: opts.bundle, live: opts.live, tenant: tenant !== void 0, noun: tenant === void 0 ? "workspace" : tenant.type }
|
|
2788
|
+
);
|
|
2789
|
+
assertNoKindSwap(
|
|
2790
|
+
opts.bundle,
|
|
2791
|
+
opts.live,
|
|
2792
|
+
frame,
|
|
2793
|
+
res.branch,
|
|
2794
|
+
lock === void 0 || opts.lockPath === void 0 ? void 0 : { file: lock, path: opts.lockPath }
|
|
2795
|
+
);
|
|
2796
|
+
} catch (err) {
|
|
2797
|
+
if (err instanceof CliError) {
|
|
2798
|
+
err.message = `${err.message.replace(/^Refusing to deploy into /, `Refusing to ${opts.subject} into `)}
|
|
2799
|
+
${opts.remedySuffix}`;
|
|
2800
|
+
}
|
|
2801
|
+
throw err;
|
|
2802
|
+
}
|
|
2803
|
+
return { checked: true, pinned, canonicals: res.canonicals };
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
export {
|
|
2807
|
+
keepWorkspaceName,
|
|
2808
|
+
withholdWorkspaceSettings,
|
|
2809
|
+
collisionBranch,
|
|
2810
|
+
ungatedSchemaChanges,
|
|
2811
|
+
gatedSchemaChanges,
|
|
2812
|
+
releaseCanonicalLock,
|
|
2813
|
+
codePinnedCanonicals,
|
|
2814
|
+
pinnedCanonicalGuids,
|
|
2815
|
+
presentableNotes,
|
|
2816
|
+
REFUSAL_CODES,
|
|
2817
|
+
withSignatureHint,
|
|
2818
|
+
bundleOrigin,
|
|
2819
|
+
runReleaseCommand,
|
|
2820
|
+
setLiveRollback,
|
|
2821
|
+
assertReleaseIdentitiesFree
|
|
2822
|
+
};
|
|
2823
|
+
//# sourceMappingURL=chunk-WKQLGKCL.js.map
|