@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,1192 @@
|
|
|
1
|
+
import {
|
|
2
|
+
registerSecret,
|
|
3
|
+
tokenTextProblem
|
|
4
|
+
} from "./chunk-AWQ7BQTQ.js";
|
|
5
|
+
import {
|
|
6
|
+
expandHome,
|
|
7
|
+
readPathEnvVar
|
|
8
|
+
} from "./chunk-EUUUMY7J.js";
|
|
9
|
+
import {
|
|
10
|
+
debugEnabled,
|
|
11
|
+
describeTransportFailure,
|
|
12
|
+
httpFailure,
|
|
13
|
+
isTimeoutError,
|
|
14
|
+
statusLabel
|
|
15
|
+
} from "./chunk-YLDALTWZ.js";
|
|
16
|
+
import {
|
|
17
|
+
pointerRootFor,
|
|
18
|
+
resolveProjectProfile
|
|
19
|
+
} from "./chunk-5FBYXK5P.js";
|
|
20
|
+
import {
|
|
21
|
+
atomicWrite,
|
|
22
|
+
linkTarget
|
|
23
|
+
} from "./chunk-LLNZGEFK.js";
|
|
24
|
+
import {
|
|
25
|
+
DEFAULT_PROFILE,
|
|
26
|
+
resolveActiveProfile
|
|
27
|
+
} from "./chunk-QXTKSOFI.js";
|
|
28
|
+
import {
|
|
29
|
+
readEnvVar
|
|
30
|
+
} from "./chunk-X2NL4KUO.js";
|
|
31
|
+
import {
|
|
32
|
+
UsageError
|
|
33
|
+
} from "./chunk-PTHVMXZI.js";
|
|
34
|
+
import {
|
|
35
|
+
shellQuote
|
|
36
|
+
} from "./chunk-5ZSO4T2G.js";
|
|
37
|
+
|
|
38
|
+
// src/auth/store.ts
|
|
39
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, rmSync, rmdirSync, statSync, lstatSync, realpathSync, accessSync, constants as fsConstants } from "fs";
|
|
40
|
+
import { homedir as homedir2 } from "os";
|
|
41
|
+
import { execFileSync } from "child_process";
|
|
42
|
+
import { basename, dirname as dirname2, join as join2, relative, resolve } from "path";
|
|
43
|
+
import lockfile from "proper-lockfile";
|
|
44
|
+
|
|
45
|
+
// src/auth/oauth.ts
|
|
46
|
+
import * as client from "openid-client";
|
|
47
|
+
|
|
48
|
+
// src/auth/client-store.ts
|
|
49
|
+
import { existsSync, readFileSync, mkdirSync } from "fs";
|
|
50
|
+
import { homedir } from "os";
|
|
51
|
+
import { dirname, join } from "path";
|
|
52
|
+
function clientStorePath() {
|
|
53
|
+
return readPathEnvVar("XANO_CLIENT_FILE") ?? join(homedir(), ".xano", "xanosdk-clients.json");
|
|
54
|
+
}
|
|
55
|
+
function key(authHost, redirectUri) {
|
|
56
|
+
return `${authHost}|${redirectUri}`;
|
|
57
|
+
}
|
|
58
|
+
function readAll(path) {
|
|
59
|
+
if (!existsSync(path)) return {};
|
|
60
|
+
try {
|
|
61
|
+
const parsed = JSON.parse(readFileSync(path, "utf8").replace(/^\uFEFF/, ""));
|
|
62
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
63
|
+
} catch {
|
|
64
|
+
return {};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function writeAll(path, all) {
|
|
68
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
69
|
+
atomicWrite(path, JSON.stringify(all, null, 2) + "\n", { mode: 384 });
|
|
70
|
+
}
|
|
71
|
+
async function getOrRegisterClient(params) {
|
|
72
|
+
const path = clientStorePath();
|
|
73
|
+
const all = readAll(path);
|
|
74
|
+
const k = key(params.authHost, params.redirectUri);
|
|
75
|
+
const cached = all[k];
|
|
76
|
+
if (cached?.client_id) return cached.client_id;
|
|
77
|
+
const { client_id } = await registerClient({
|
|
78
|
+
registrationEndpoint: params.registrationEndpoint,
|
|
79
|
+
redirectUri: params.redirectUri,
|
|
80
|
+
scope: params.scope
|
|
81
|
+
});
|
|
82
|
+
all[k] = { client_id };
|
|
83
|
+
writeAll(path, all);
|
|
84
|
+
return client_id;
|
|
85
|
+
}
|
|
86
|
+
function clearClient(params) {
|
|
87
|
+
const path = clientStorePath();
|
|
88
|
+
const all = readAll(path);
|
|
89
|
+
const k = key(params.authHost, params.redirectUri);
|
|
90
|
+
if (!(k in all)) return;
|
|
91
|
+
delete all[k];
|
|
92
|
+
writeAll(path, all);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/auth/oauth.ts
|
|
96
|
+
var CALLBACK_PATH = "/oauth/callback";
|
|
97
|
+
var DEFAULT_PORT = 47100;
|
|
98
|
+
var SOFTWARE_ID = "xanosdk";
|
|
99
|
+
var DEFAULT_AUTH_HOST = "https://app.xano.com";
|
|
100
|
+
var DEFAULT_SCOPE = "offline_access workspace:read workspace:write xano:dev";
|
|
101
|
+
var DISCOVERY_PATH = "/.well-known/oauth-authorization-server";
|
|
102
|
+
var NETWORK_TIMEOUT_MS = 3e4;
|
|
103
|
+
function unreachableAuthHost(authHost, err) {
|
|
104
|
+
const why = isTimeoutError(err) ? `no answer within ${NETWORK_TIMEOUT_MS / 1e3}s` : describeTransportFailure(err);
|
|
105
|
+
return new Error(
|
|
106
|
+
`Could not reach the sign-in server at ${authHost}: ${why}. Check the network, and --origin or XANO_ORIGIN if either is set.`,
|
|
107
|
+
{ cause: err }
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
async function discover(authHost) {
|
|
111
|
+
const url = new URL(DISCOVERY_PATH, authHost).href;
|
|
112
|
+
let res;
|
|
113
|
+
try {
|
|
114
|
+
res = await fetch(url, { signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS) });
|
|
115
|
+
} catch (err) {
|
|
116
|
+
throw unreachableAuthHost(authHost, err);
|
|
117
|
+
}
|
|
118
|
+
if (!res.ok) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`OAuth discovery failed (${statusLabel(res)}) at ${url}. Check --origin (currently ${authHost}).`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
const doc = await res.json();
|
|
124
|
+
if (!doc.authorization_endpoint || !doc.token_endpoint) {
|
|
125
|
+
throw new Error(`OAuth discovery doc at ${url} is missing authorization/token endpoints.`);
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
authorization_endpoint: doc.authorization_endpoint,
|
|
129
|
+
token_endpoint: doc.token_endpoint,
|
|
130
|
+
registration_endpoint: doc.registration_endpoint
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
async function registerClient(params) {
|
|
134
|
+
const res = await fetch(params.registrationEndpoint, {
|
|
135
|
+
method: "POST",
|
|
136
|
+
headers: { "Content-Type": "application/json" },
|
|
137
|
+
body: JSON.stringify({
|
|
138
|
+
client_name: "xanosdk CLI",
|
|
139
|
+
software_id: SOFTWARE_ID,
|
|
140
|
+
redirect_uris: [params.redirectUri],
|
|
141
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
142
|
+
response_types: ["code"],
|
|
143
|
+
token_endpoint_auth_method: "none",
|
|
144
|
+
scopes: params.scope.split(/\s+/).filter(Boolean)
|
|
145
|
+
}),
|
|
146
|
+
signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS)
|
|
147
|
+
});
|
|
148
|
+
const text = await res.text();
|
|
149
|
+
if (res.status !== 200 && res.status !== 201) {
|
|
150
|
+
throw new Error(httpFailure("Client registration", res, text));
|
|
151
|
+
}
|
|
152
|
+
const doc = JSON.parse(text);
|
|
153
|
+
if (!doc.client_id) {
|
|
154
|
+
throw new Error(`Client registration response had no client_id.${debugEnabled() ? `
|
|
155
|
+
${text}` : ""}`);
|
|
156
|
+
}
|
|
157
|
+
return { client_id: doc.client_id };
|
|
158
|
+
}
|
|
159
|
+
function decodeAudience(accessToken) {
|
|
160
|
+
const parts = accessToken.split(".");
|
|
161
|
+
if (parts.length < 2) return void 0;
|
|
162
|
+
try {
|
|
163
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
164
|
+
return Array.isArray(payload.aud) ? payload.aud[0] : payload.aud;
|
|
165
|
+
} catch {
|
|
166
|
+
return void 0;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function oauthErrorCode(err) {
|
|
170
|
+
if (err && typeof err === "object" && "error" in err) {
|
|
171
|
+
const code = err.error;
|
|
172
|
+
if (typeof code === "string") return code;
|
|
173
|
+
}
|
|
174
|
+
return void 0;
|
|
175
|
+
}
|
|
176
|
+
function oauthErrorResponse(err) {
|
|
177
|
+
const cause = err?.cause;
|
|
178
|
+
return typeof Response !== "undefined" && cause instanceof Response ? cause : void 0;
|
|
179
|
+
}
|
|
180
|
+
var OAUTH_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
181
|
+
"invalid_request",
|
|
182
|
+
"invalid_client",
|
|
183
|
+
"invalid_grant",
|
|
184
|
+
"unauthorized_client",
|
|
185
|
+
"unsupported_grant_type",
|
|
186
|
+
"invalid_scope"
|
|
187
|
+
]);
|
|
188
|
+
async function resolveOAuthErrorCode(err) {
|
|
189
|
+
const rfc = oauthErrorCode(err);
|
|
190
|
+
if (rfc !== void 0) return rfc;
|
|
191
|
+
const res = oauthErrorResponse(err);
|
|
192
|
+
if (res === void 0 || res.bodyUsed) return void 0;
|
|
193
|
+
try {
|
|
194
|
+
const body = await res.clone().json();
|
|
195
|
+
if (!body || typeof body !== "object") return void 0;
|
|
196
|
+
const fields = body;
|
|
197
|
+
for (const candidate of [fields.error, fields.message, fields.code]) {
|
|
198
|
+
const code = typeof candidate === "string" ? leadingOAuthCode(candidate) : void 0;
|
|
199
|
+
if (code !== void 0) return code;
|
|
200
|
+
}
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
return void 0;
|
|
204
|
+
}
|
|
205
|
+
function leadingOAuthCode(value) {
|
|
206
|
+
const colon = value.indexOf(":");
|
|
207
|
+
const head = (colon === -1 ? value : value.slice(0, colon)).trim();
|
|
208
|
+
if (colon === -1 && head !== value) return void 0;
|
|
209
|
+
return OAUTH_ERROR_CODES.has(head) ? head : void 0;
|
|
210
|
+
}
|
|
211
|
+
var OpenIdProvider = class {
|
|
212
|
+
constructor(opts) {
|
|
213
|
+
this.opts = opts;
|
|
214
|
+
}
|
|
215
|
+
opts;
|
|
216
|
+
configPromise;
|
|
217
|
+
/** The client_id the resolved config uses — set once `build()` completes. */
|
|
218
|
+
resolvedClientId;
|
|
219
|
+
/** The issuer the resolved config discovered — set once `build()` completes. */
|
|
220
|
+
resolvedIssuer;
|
|
221
|
+
redirectUri() {
|
|
222
|
+
if (!this.opts.redirectUri) {
|
|
223
|
+
throw new Error("OpenIdProvider: redirectUri is required for the authorize/exchange flow.");
|
|
224
|
+
}
|
|
225
|
+
return this.opts.redirectUri;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* The client_id this provider registered or reused. Only valid after the
|
|
229
|
+
* config has resolved (e.g. after `buildAuthUrl`/`exchange`); throws otherwise
|
|
230
|
+
* so a caller never records an empty client_id.
|
|
231
|
+
*/
|
|
232
|
+
clientId() {
|
|
233
|
+
if (!this.resolvedClientId) {
|
|
234
|
+
throw new Error("OpenIdProvider.clientId() called before the configuration was resolved.");
|
|
235
|
+
}
|
|
236
|
+
return this.resolvedClientId;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* The authorization server's issuer, as resolved by discovery. Only the paste
|
|
240
|
+
* login mode needs it: a pasted BARE code carries no `iss`, and the AS
|
|
241
|
+
* advertises issuer support, so one has to be supplied for the response to
|
|
242
|
+
* validate at all. Same lifecycle guard as `clientId()` — valid once the
|
|
243
|
+
* configuration has resolved.
|
|
244
|
+
*/
|
|
245
|
+
issuer() {
|
|
246
|
+
if (!this.resolvedIssuer) {
|
|
247
|
+
throw new Error("OpenIdProvider.issuer() called before the configuration was resolved.");
|
|
248
|
+
}
|
|
249
|
+
return this.resolvedIssuer;
|
|
250
|
+
}
|
|
251
|
+
async build() {
|
|
252
|
+
const server = new URL(this.opts.authHost);
|
|
253
|
+
const options = server.protocol === "http:" ? { execute: [client.allowInsecureRequests] } : void 0;
|
|
254
|
+
let clientId = this.opts.clientId;
|
|
255
|
+
if (!clientId) {
|
|
256
|
+
const { registration_endpoint } = await discover(this.opts.authHost);
|
|
257
|
+
if (!registration_endpoint) {
|
|
258
|
+
throw new Error(
|
|
259
|
+
`The OAuth server at ${this.opts.authHost} does not advertise a registration endpoint, so xanosdk can't register its loopback client. Check --origin.`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
clientId = await getOrRegisterClient({
|
|
263
|
+
authHost: this.opts.authHost,
|
|
264
|
+
redirectUri: this.redirectUri(),
|
|
265
|
+
registrationEndpoint: registration_endpoint,
|
|
266
|
+
scope: this.opts.scope
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
this.resolvedClientId = clientId;
|
|
270
|
+
const config = await client.discovery(
|
|
271
|
+
server,
|
|
272
|
+
clientId,
|
|
273
|
+
{ token_endpoint_auth_method: "none" },
|
|
274
|
+
client.None(),
|
|
275
|
+
options
|
|
276
|
+
);
|
|
277
|
+
this.resolvedIssuer = config.serverMetadata().issuer;
|
|
278
|
+
return config;
|
|
279
|
+
}
|
|
280
|
+
config() {
|
|
281
|
+
return this.configPromise ??= this.build();
|
|
282
|
+
}
|
|
283
|
+
async reset() {
|
|
284
|
+
this.configPromise = void 0;
|
|
285
|
+
this.resolvedClientId = void 0;
|
|
286
|
+
this.resolvedIssuer = void 0;
|
|
287
|
+
if (this.opts.redirectUri) {
|
|
288
|
+
clearClient({ authHost: this.opts.authHost, redirectUri: this.opts.redirectUri });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
async buildAuthUrl({ verifier, state }) {
|
|
292
|
+
const config = await this.config();
|
|
293
|
+
const code_challenge = await client.calculatePKCECodeChallenge(verifier);
|
|
294
|
+
return client.buildAuthorizationUrl(config, {
|
|
295
|
+
redirect_uri: this.redirectUri(),
|
|
296
|
+
scope: this.opts.scope,
|
|
297
|
+
code_challenge,
|
|
298
|
+
code_challenge_method: "S256",
|
|
299
|
+
state
|
|
300
|
+
}).href;
|
|
301
|
+
}
|
|
302
|
+
async exchange(callbackUrl, { verifier, state }) {
|
|
303
|
+
const config = await this.config();
|
|
304
|
+
const tokens = await client.authorizationCodeGrant(config, new URL(callbackUrl), {
|
|
305
|
+
// We don't request `openid`, so no ID Token is expected.
|
|
306
|
+
pkceCodeVerifier: verifier,
|
|
307
|
+
expectedState: state,
|
|
308
|
+
idTokenExpected: false
|
|
309
|
+
});
|
|
310
|
+
return pick(tokens);
|
|
311
|
+
}
|
|
312
|
+
async refresh(refreshToken, opts) {
|
|
313
|
+
const config = await this.config();
|
|
314
|
+
const params = {};
|
|
315
|
+
if (opts?.scope) params.scope = opts.scope;
|
|
316
|
+
return pick(await client.refreshTokenGrant(config, refreshToken, params));
|
|
317
|
+
}
|
|
318
|
+
async revoke(refreshToken) {
|
|
319
|
+
const config = await this.config();
|
|
320
|
+
await client.tokenRevocation(config, refreshToken, { token_type_hint: "refresh_token" });
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
function pick(t) {
|
|
324
|
+
registerSecret(t.access_token);
|
|
325
|
+
registerSecret(t.refresh_token);
|
|
326
|
+
return {
|
|
327
|
+
access_token: t.access_token,
|
|
328
|
+
refresh_token: t.refresh_token,
|
|
329
|
+
expires_in: t.expires_in,
|
|
330
|
+
scope: t.scope
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// src/auth/config.ts
|
|
335
|
+
function resolveAuthHost(args, saved) {
|
|
336
|
+
return args.authHost ?? envOrigin() ?? saved ?? DEFAULT_AUTH_HOST;
|
|
337
|
+
}
|
|
338
|
+
function envOrigin() {
|
|
339
|
+
return readEnvVar("XANO_ORIGIN");
|
|
340
|
+
}
|
|
341
|
+
function resolveScope(args) {
|
|
342
|
+
if (args.scope !== void 0 && args.scope.trim() === "") {
|
|
343
|
+
throw new UsageError(
|
|
344
|
+
'--scope is empty \u2014 usually an unset shell variable. Pass the space-separated OAuth scopes to request (e.g. --scope "offline_access workspace:read"). Drop --scope to request the default set.',
|
|
345
|
+
{ hintFor: { command: "login" } }
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
return args.scope ?? DEFAULT_SCOPE;
|
|
349
|
+
}
|
|
350
|
+
function assertHttpsOrigin(origin, label) {
|
|
351
|
+
refuseMistypedScheme(origin, label);
|
|
352
|
+
let url;
|
|
353
|
+
try {
|
|
354
|
+
url = new URL(origin);
|
|
355
|
+
} catch {
|
|
356
|
+
throw new Error(`${label} is not a valid URL: "${origin}" (expected e.g. https://your-instance.xano.io).`);
|
|
357
|
+
}
|
|
358
|
+
const host = url.hostname.replace(/^\[|\]$/g, "");
|
|
359
|
+
const isLoopback = host === "localhost" || host === "127.0.0.1" || host === "::1";
|
|
360
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
361
|
+
throw new Error(
|
|
362
|
+
`${label} must use https://, or http:// for localhost (got "${origin}", whose scheme is ${url.protocol}).`
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopback)) {
|
|
366
|
+
throw new Error(`${label} must use https:// (got "${origin}"). Plain http is allowed only for localhost.`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
function assertSignInOrigin(origin, label) {
|
|
370
|
+
assertNoUserinfo(origin, label);
|
|
371
|
+
assertHttpsOrigin(origin, label);
|
|
372
|
+
const url = new URL(origin);
|
|
373
|
+
if ((url.pathname === "" || url.pathname === "/") && url.search === "" && url.hash === "") return;
|
|
374
|
+
throw new Error(
|
|
375
|
+
`${label} must be an origin \u2014 a scheme and host, with no path, query or fragment (got "${origin}"). Use ${url.origin}.`
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
function assertInstanceOrigin(value, label) {
|
|
379
|
+
refuseMistypedScheme(value, label);
|
|
380
|
+
let url;
|
|
381
|
+
try {
|
|
382
|
+
url = new URL(value);
|
|
383
|
+
} catch {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (url.username !== "" || url.password !== "") {
|
|
387
|
+
throw new Error(
|
|
388
|
+
`${label} must not carry a user name or password (the \`user:password@\` part) \u2014 an instance is addressed by its origin, and the meta API token is what authenticates. Use ${url.origin}${url.password === "" ? "" : ", and treat that password as exposed"}.`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
if ((url.pathname === "" || url.pathname === "/") && url.search === "" && url.hash === "") return;
|
|
392
|
+
throw new Error(
|
|
393
|
+
`${label} must be the instance's origin \u2014 a scheme and host, with no path, query or fragment (got "${value}"). Use ${url.origin}.`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
function refuseMistypedScheme(value, label) {
|
|
397
|
+
const doubled = /^([a-z][a-z0-9+.-]*):\/\/+([a-z][a-z0-9+.-]*):\/\/(.*)$/i.exec(value.trim());
|
|
398
|
+
const bare = value.trim();
|
|
399
|
+
const hostLike = !bare.includes("://") && /^(?:[^\s/:@]+@)?(?:localhost|[a-z0-9-]+(?:\.[a-z0-9-]+)+)(?::\d+)?\/?$/i.test(bare);
|
|
400
|
+
const loopback = doubled !== null && /^(?:[^/@]*@)?(?:localhost|127\.0\.0\.1|\[::1\])(?::\d+)?(?:[/?#]|$)/i.test(doubled[3]);
|
|
401
|
+
const scheme = loopback && [doubled[1], doubled[2]].every((x) => x.toLowerCase() === "http") ? "http" : "https";
|
|
402
|
+
const meant = doubled !== null ? `${scheme}://${doubled[3]}` : hostLike ? `https://${bare}` : void 0;
|
|
403
|
+
if (meant === void 0) return;
|
|
404
|
+
let origin;
|
|
405
|
+
try {
|
|
406
|
+
origin = new URL(meant).origin;
|
|
407
|
+
} catch {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (origin === "null") return;
|
|
411
|
+
throw new Error(
|
|
412
|
+
doubled !== null ? `${label} has its scheme twice. Use ${origin}.` : `${label} needs a scheme (https://). Use ${origin}.`
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
function assertNoUserinfo(origin, label) {
|
|
416
|
+
let url;
|
|
417
|
+
try {
|
|
418
|
+
url = new URL(origin);
|
|
419
|
+
} catch {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (url.username === "" && url.password === "") return;
|
|
423
|
+
throw new Error(
|
|
424
|
+
`${label} must not carry a user name or password (the \`user:password@\` part). A sign-in server is an origin, and the sign-in itself happens in the browser. Use ${url.origin}${url.password === "" ? "" : ", and treat that password as exposed"}.`
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// src/auth/store.ts
|
|
429
|
+
var XANO_DIR = "xano";
|
|
430
|
+
var DEDICATED_DIR = ".xano";
|
|
431
|
+
var CREDENTIAL_FILE_VERSION = 2;
|
|
432
|
+
function emptyCredentialFile() {
|
|
433
|
+
return { version: CREDENTIAL_FILE_VERSION, profiles: {} };
|
|
434
|
+
}
|
|
435
|
+
var DEFAULT_AUTH_FILE = join2(".xano", "auth.json");
|
|
436
|
+
function globalAuthFilePath() {
|
|
437
|
+
return readPathEnvVar("XANO_GLOBAL_CONFIG") ?? join2(homedir2(), ".xanosdk", "auth.json");
|
|
438
|
+
}
|
|
439
|
+
function localAuthFilePath() {
|
|
440
|
+
return join2(pointerRootFor(process.cwd()), DEFAULT_AUTH_FILE);
|
|
441
|
+
}
|
|
442
|
+
function resolveAuthFilePath(args, mode = "read") {
|
|
443
|
+
const explicit = expandHome(args.authFile) ?? readPathEnvVar("XANO_CONFIG");
|
|
444
|
+
if (explicit !== void 0 && explicit.trim() === "") {
|
|
445
|
+
const named = args.authFile !== void 0 ? "`--config`" : "XANO_CONFIG";
|
|
446
|
+
throw new UsageError(
|
|
447
|
+
`${named} is empty. It takes the path of a credential file \u2014 the JSON file \`xanosdk login\` or \`xanosdk profile add\` writes, e.g. \`.xano/auth.json\`` + (args.authFile !== void 0 ? ` \u2014 or drop it to use the default one.` : ` \u2014 or unset XANO_CONFIG.`)
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
if (explicit !== void 0 && args.local && resolve(explicit) !== localAuthFilePath()) {
|
|
451
|
+
const named = args.authFile !== void 0 ? `\`--config ${explicit}\`` : `XANO_CONFIG=${explicit}`;
|
|
452
|
+
throw new UsageError(
|
|
453
|
+
`\`--local\` selects the project-local .xano/auth.json and ${named} names another credential file \u2014 they cannot both be the one this command uses. Drop \`--local\`` + (args.authFile !== void 0 ? `, or drop \`--config\`.` : `, or unset XANO_CONFIG.`)
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
if (explicit !== void 0) {
|
|
457
|
+
const path = resolve(explicit);
|
|
458
|
+
assertCredentialPathUsable(path, args.authFile !== void 0 ? `--config ${explicit}` : `XANO_CONFIG=${explicit}`);
|
|
459
|
+
return path;
|
|
460
|
+
}
|
|
461
|
+
const local = localAuthFilePath();
|
|
462
|
+
if (args.local) return local;
|
|
463
|
+
const global = globalAuthFilePath();
|
|
464
|
+
if (mode === "write") return global;
|
|
465
|
+
if (existsSync2(local) && !holdsNoProfiles(local)) return local;
|
|
466
|
+
return global;
|
|
467
|
+
}
|
|
468
|
+
function loopError(path) {
|
|
469
|
+
return new UsageError(
|
|
470
|
+
`The credential file ${path} is a symbolic link that loops back on itself (ELOOP). Point it at a real file, or pass \`--config <file>\`.`
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
function credentialWriteFailure(err, path) {
|
|
474
|
+
const code = err?.code;
|
|
475
|
+
const head = `No credential can be written to ${path}:`;
|
|
476
|
+
const elsewhere = "or pass `--config <file>` somewhere you can write.";
|
|
477
|
+
switch (code) {
|
|
478
|
+
case "EACCES":
|
|
479
|
+
case "EPERM":
|
|
480
|
+
return new UsageError(`${head} ${dirname2(path)} is not writable by this user (${code}). Make it writable, ${elsewhere}`);
|
|
481
|
+
case "EROFS":
|
|
482
|
+
return new UsageError(`${head} ${dirname2(path)} is on a read-only file system (EROFS). Pass \`--config <file>\` somewhere you can write.`);
|
|
483
|
+
case "ELOOP":
|
|
484
|
+
return loopError(path);
|
|
485
|
+
case "EISDIR":
|
|
486
|
+
return new UsageError(`${head} it is a directory. Remove or rename it, ${elsewhere}`);
|
|
487
|
+
default:
|
|
488
|
+
return err;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
function credentialFileExists(path) {
|
|
492
|
+
try {
|
|
493
|
+
statSync(path);
|
|
494
|
+
return true;
|
|
495
|
+
} catch (err) {
|
|
496
|
+
const code = err?.code;
|
|
497
|
+
if (code === "ELOOP") throw loopError(path);
|
|
498
|
+
if (code !== "EACCES" && code !== "EPERM") return false;
|
|
499
|
+
const dir = dirname2(path);
|
|
500
|
+
const quoted = shellQuote(dir);
|
|
501
|
+
throw new Error(
|
|
502
|
+
`Cannot read the credential directory ${dir}: permission denied (${code}). Nothing is lost \u2014 this user cannot open it, usually because a command run with sudo created it as root. Restore access with \`sudo chown -R "$(whoami)" ${quoted}\` and \`chmod 700 ${quoted}\`, then retry.`,
|
|
503
|
+
{ cause: err }
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
function readCredentialText(path) {
|
|
508
|
+
try {
|
|
509
|
+
return readFileSync2(path, "utf8").replace(/^\uFEFF/, "");
|
|
510
|
+
} catch (err) {
|
|
511
|
+
const code = err?.code;
|
|
512
|
+
if (code === "ELOOP") throw loopError(path);
|
|
513
|
+
if (code === "EISDIR") throw new UsageError(`The credential file ${path} is a directory. Remove or rename it, or pass \`--config <file>\`.`);
|
|
514
|
+
if (code !== "EACCES" && code !== "EPERM") throw err;
|
|
515
|
+
const quoted = shellQuote(path);
|
|
516
|
+
throw new Error(
|
|
517
|
+
`Cannot read the credential file ${path}: permission denied (${code}). The file is not corrupt \u2014 this user cannot open it. Restore access with \`chmod u+rw ${quoted}\` (if another user owns it: \`sudo chown "$(whoami)" ${quoted}\`), then retry.`,
|
|
518
|
+
{ cause: err }
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
function assertCredentialPathUsable(path, named) {
|
|
523
|
+
const blocker = fileInTheWay(path);
|
|
524
|
+
if (blocker !== void 0) {
|
|
525
|
+
throw new UsageError(
|
|
526
|
+
`\`${named}\` names ${path}, but ${blocker} is a file, not a directory \u2014 no credential file can exist below it. Choose a path whose directories are directories, e.g. \`.xano/auth.json\`.`
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
if (isDirectory(path)) {
|
|
530
|
+
throw new UsageError(
|
|
531
|
+
`\`${named}\` names a directory (${path}). It must name a credential file \u2014 the JSON file \`xanosdk login\` or \`xanosdk profile add\` writes, e.g. \`.xano/auth.json\`.`
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
const existing = readExistingRaw(path);
|
|
535
|
+
if (existing.absent) return;
|
|
536
|
+
if (!existing.unparseable && isCredentialRecord(existing.value)) return;
|
|
537
|
+
throw new UsageError(
|
|
538
|
+
`\`${named}\` names ${path}, which exists but is not a Xano SDK credential file. Choose a different path \u2014 a new one is created on the first \`xanosdk login\` or \`xanosdk profile add\`. If it really is a credential file that has been corrupted beyond recognition, delete it yourself and sign in again.`
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
function fileInTheWay(path) {
|
|
542
|
+
let dir = dirname2(resolve(path));
|
|
543
|
+
for (; ; ) {
|
|
544
|
+
if (existsSync2(dir)) return isDirectory(dir) ? void 0 : dir;
|
|
545
|
+
const parent = dirname2(dir);
|
|
546
|
+
if (parent === dir) return void 0;
|
|
547
|
+
dir = parent;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
function isDirectory(path) {
|
|
551
|
+
try {
|
|
552
|
+
return statSync(path).isDirectory();
|
|
553
|
+
} catch {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
function holdsNoProfiles(path) {
|
|
558
|
+
try {
|
|
559
|
+
const file = readCredentialFile(path);
|
|
560
|
+
return file !== null && profileNames(file).length === 0;
|
|
561
|
+
} catch {
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
function projectAuthFilePath() {
|
|
566
|
+
return resolveAuthFilePath({ authFile: void 0, local: false }, "read");
|
|
567
|
+
}
|
|
568
|
+
function readCredentialFile(path) {
|
|
569
|
+
if (!credentialFileExists(path)) return null;
|
|
570
|
+
if (isDirectory(path)) {
|
|
571
|
+
throw new UsageError(`Credential file at ${path} is a directory, not a file. Remove or rename it, then sign in again.`);
|
|
572
|
+
}
|
|
573
|
+
const text = readCredentialText(path);
|
|
574
|
+
let parsed;
|
|
575
|
+
try {
|
|
576
|
+
parsed = JSON.parse(text);
|
|
577
|
+
} catch {
|
|
578
|
+
throw new Error(
|
|
579
|
+
`Credential file at ${path} is corrupt (invalid JSON). ${reauthHint(path)}`
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
583
|
+
throw new Error(`Credential file at ${path} is not a JSON object. ${reauthHint(path)}`);
|
|
584
|
+
}
|
|
585
|
+
const record = parsed;
|
|
586
|
+
if (isEnvelope(record)) {
|
|
587
|
+
return {
|
|
588
|
+
version: record.version,
|
|
589
|
+
default: typeof record.default === "string" ? record.default : void 0,
|
|
590
|
+
profiles: { ...record.profiles }
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
if (!isCredentialRecord(record) && typeof record.type !== "string") {
|
|
594
|
+
throw new Error(
|
|
595
|
+
`${path} is not a Xano SDK credential file. If it should be one, delete it and run \`${loginCommand(DEFAULT_PROFILE, { path })}\` again.`
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
return { version: CREDENTIAL_FILE_VERSION, profiles: { [DEFAULT_PROFILE]: record } };
|
|
599
|
+
}
|
|
600
|
+
function isEnvelope(record) {
|
|
601
|
+
return typeof record.version === "number" && typeof record.profiles === "object" && record.profiles !== null && !Array.isArray(record.profiles);
|
|
602
|
+
}
|
|
603
|
+
function readProfile(file, name, path) {
|
|
604
|
+
if (!Object.prototype.hasOwnProperty.call(file.profiles, name)) return null;
|
|
605
|
+
try {
|
|
606
|
+
return parseCredential(file.profiles[name], path, name);
|
|
607
|
+
} catch (err) {
|
|
608
|
+
if (file.version > CREDENTIAL_FILE_VERSION) {
|
|
609
|
+
throw new Error(
|
|
610
|
+
`${name === DEFAULT_PROFILE ? "The default profile" : `Profile "${name}"`} in the credential file at ${path} was written by a newer xanosdk (file version ${file.version}; this CLI understands ${CREDENTIAL_FILE_VERSION}) \u2014 ${UPGRADE_REMEDY}`
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
throw err;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
var UPGRADE_REMEDY = "upgrade with `npm install -g @xano/sdk@latest` (or `xanosdk upgrade`) and try again.";
|
|
617
|
+
function readCredential(path, profile = DEFAULT_PROFILE) {
|
|
618
|
+
const file = readCredentialFile(path);
|
|
619
|
+
return file === null ? null : readProfile(file, profile, path);
|
|
620
|
+
}
|
|
621
|
+
function profileNames(file) {
|
|
622
|
+
return Object.keys(file.profiles);
|
|
623
|
+
}
|
|
624
|
+
function recordedCredentialPath(recorded) {
|
|
625
|
+
if (recorded === void 0) return null;
|
|
626
|
+
if (recorded === "shared") return globalAuthFilePath();
|
|
627
|
+
if (recorded === "local") return localAuthFilePath();
|
|
628
|
+
return recorded;
|
|
629
|
+
}
|
|
630
|
+
function recordedCredentialFile(path) {
|
|
631
|
+
const abs = resolve(path);
|
|
632
|
+
if (abs === resolve(globalAuthFilePath())) return "shared";
|
|
633
|
+
if (abs === localAuthFilePath()) return "local";
|
|
634
|
+
return abs;
|
|
635
|
+
}
|
|
636
|
+
function recordedProfileStored(profile, recorded) {
|
|
637
|
+
const file = recordedCredentialPath(recorded);
|
|
638
|
+
for (const path of file === null ? [globalAuthFilePath(), localAuthFilePath()] : [file]) {
|
|
639
|
+
try {
|
|
640
|
+
const read = readCredentialFile(path);
|
|
641
|
+
if (read !== null && profileNames(read).includes(profile)) return true;
|
|
642
|
+
} catch {
|
|
643
|
+
return true;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
function describeProfile(file, name, path) {
|
|
649
|
+
try {
|
|
650
|
+
return { name, record: readProfile(file, name, path) };
|
|
651
|
+
} catch (err) {
|
|
652
|
+
return { name, error: err instanceof Error ? err.message : String(err) };
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function isCredentialRecord(v) {
|
|
656
|
+
if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
|
|
657
|
+
const record = v;
|
|
658
|
+
if (isEnvelope(record)) return true;
|
|
659
|
+
if (record.type === "oauth" || record.type === "token") return true;
|
|
660
|
+
return typeof record.access_token === "string" && typeof record.instance === "string";
|
|
661
|
+
}
|
|
662
|
+
function parseCredential(v, path, profile = DEFAULT_PROFILE) {
|
|
663
|
+
const where = `${profile === DEFAULT_PROFILE ? "" : `Profile "${profile}" in the `}credential file at ${path}`;
|
|
664
|
+
if (typeof v !== "object" || v === null || Array.isArray(v)) {
|
|
665
|
+
throw new Error(`${where} is not a JSON object. ${replaceHint(path, profile)}`);
|
|
666
|
+
}
|
|
667
|
+
const record = v;
|
|
668
|
+
const type = record.type;
|
|
669
|
+
if (type === void 0) {
|
|
670
|
+
throw new Error(
|
|
671
|
+
`${where} has no \`type\` field \u2014 it predates the typed credential format. Run ${loginHint(profile, { path })} again to replace it.`
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
if (type === "oauth") return parseOAuth(record, path, profile);
|
|
675
|
+
if (type === "token") return parseToken(record, path, profile);
|
|
676
|
+
throw new Error(
|
|
677
|
+
`${where} has an unrecognized \`type\` (${JSON.stringify(type)}). Expected "oauth" or "token".`
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
function whereIn(kind, path, profile) {
|
|
681
|
+
const suffix = profile === DEFAULT_PROFILE ? "" : ` (profile "${profile}")`;
|
|
682
|
+
return `in the "${kind}" credential at ${path}${suffix}`;
|
|
683
|
+
}
|
|
684
|
+
function reauthHint(path, profile = DEFAULT_PROFILE) {
|
|
685
|
+
return `Delete it and run \`${loginCommand(profile, { path })}\` again.`;
|
|
686
|
+
}
|
|
687
|
+
function replaceHint(path, profile) {
|
|
688
|
+
return `Run \`${loginCommand(profile, { path })}\` to replace it.`;
|
|
689
|
+
}
|
|
690
|
+
function loginHint(profile, target = {}) {
|
|
691
|
+
return `\`${loginCommand(profile, target)}\``;
|
|
692
|
+
}
|
|
693
|
+
function credentialFileFlag(path) {
|
|
694
|
+
if (path === void 0) return "";
|
|
695
|
+
const abs = resolve(path);
|
|
696
|
+
const byEnv = readPathEnvVar("XANO_CONFIG");
|
|
697
|
+
if (byEnv !== void 0 && byEnv.trim() !== "") {
|
|
698
|
+
return abs === resolve(byEnv) ? "" : ` --config ${shellQuote(abs)}`;
|
|
699
|
+
}
|
|
700
|
+
if (abs === resolve(globalAuthFilePath())) return "";
|
|
701
|
+
if (abs === localAuthFilePath()) return " --local";
|
|
702
|
+
return ` --config ${shellQuote(abs)}`;
|
|
703
|
+
}
|
|
704
|
+
function loginCommand(profile, target = {}, extra = []) {
|
|
705
|
+
const name = profile === DEFAULT_PROFILE && bareLoginReaches(target.path) === DEFAULT_PROFILE ? "" : ` --profile ${shellQuote(profile)}`;
|
|
706
|
+
const origin = target.origin === void 0 || sameOriginAs(target.origin, DEFAULT_AUTH_HOST) ? "" : ` --origin ${shellQuote(originOnly(target.origin))}`;
|
|
707
|
+
return `xanosdk login${name}${credentialFileFlag(target.path)}${origin}${extra.map((f) => ` ${f}`).join("")}`;
|
|
708
|
+
}
|
|
709
|
+
function bareLoginReaches(path) {
|
|
710
|
+
let fileDefault;
|
|
711
|
+
try {
|
|
712
|
+
fileDefault = readCredentialFile(path ?? globalAuthFilePath())?.default;
|
|
713
|
+
} catch {
|
|
714
|
+
}
|
|
715
|
+
try {
|
|
716
|
+
return resolveActiveProfile({ fileDefault, readPointer: () => resolveProjectProfile(process.cwd()) }).name;
|
|
717
|
+
} catch {
|
|
718
|
+
return void 0;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
function originOnly(url) {
|
|
722
|
+
try {
|
|
723
|
+
return new URL(url).origin;
|
|
724
|
+
} catch {
|
|
725
|
+
return url;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
function sameOriginAs(a, b) {
|
|
729
|
+
return originOnly(a) === originOnly(b);
|
|
730
|
+
}
|
|
731
|
+
function addProfileHint(profile, target = {}) {
|
|
732
|
+
return `${loginHint(profile, target)} (OAuth), or \`${profileAddCommand(profile, target)}\` (meta API token)`;
|
|
733
|
+
}
|
|
734
|
+
function profileAddCommand(profile, target = {}) {
|
|
735
|
+
const instance = target.instance !== void 0 ? shellQuote(originOnly(target.instance)) : "<url>";
|
|
736
|
+
const workspace = target.workspaceId !== void 0 ? String(target.workspaceId) : "<id>";
|
|
737
|
+
return `xanosdk profile add ${shellQuote(profile)}${credentialFileFlag(target.path)} --instance ${instance} --workspace-id ${workspace}`;
|
|
738
|
+
}
|
|
739
|
+
function parseOAuth(record, path, profile) {
|
|
740
|
+
const at = whereIn("oauth", path, profile);
|
|
741
|
+
if (typeof record.access_token !== "string" || record.access_token === "") {
|
|
742
|
+
throw new Error(`Missing \`access_token\` ${at}. ${replaceHint(path, profile)}`);
|
|
743
|
+
}
|
|
744
|
+
if (typeof record.instance !== "string" || record.instance === "") {
|
|
745
|
+
throw new Error(`Missing \`instance\` ${at}. ${replaceHint(path, profile)}`);
|
|
746
|
+
}
|
|
747
|
+
if (record.workspace_id === void 0) {
|
|
748
|
+
throw new Error(
|
|
749
|
+
`Missing \`workspace_id\` ${at} \u2014 it predates workspace pinning at login. ${replaceHint(path, profile)}`
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
if (typeof record.auth_host !== "string" || record.auth_host === "") {
|
|
753
|
+
throw new Error(`Missing \`auth_host\` ${at}. ${replaceHint(path, profile)}`);
|
|
754
|
+
}
|
|
755
|
+
if (typeof record.client_id !== "string" || record.client_id === "") {
|
|
756
|
+
throw new Error(`Missing \`client_id\` ${at}. ${replaceHint(path, profile)}`);
|
|
757
|
+
}
|
|
758
|
+
registerSecret(record.access_token);
|
|
759
|
+
registerSecret(record.refresh_token);
|
|
760
|
+
for (const field of ["access_token", "refresh_token"]) {
|
|
761
|
+
const value = record[field];
|
|
762
|
+
const problem = typeof value === "string" ? tokenTextProblem(value) : void 0;
|
|
763
|
+
if (problem !== void 0) {
|
|
764
|
+
throw new UsageError(`\`${field}\` ${at} ${problem}, which no request can carry. ${replaceHint(path, profile)}`);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return {
|
|
768
|
+
type: "oauth",
|
|
769
|
+
access_token: record.access_token,
|
|
770
|
+
refresh_token: typeof record.refresh_token === "string" ? record.refresh_token : void 0,
|
|
771
|
+
// A missing expiry reads as "already expired", which triggers a refresh
|
|
772
|
+
// rather than handing out a token we cannot vouch for.
|
|
773
|
+
expires_at: typeof record.expires_at === "number" ? record.expires_at : 0,
|
|
774
|
+
scope: typeof record.scope === "string" ? record.scope : void 0,
|
|
775
|
+
instance: record.instance,
|
|
776
|
+
workspace_id: requireWorkspaceId(record.workspace_id, at),
|
|
777
|
+
auth_host: record.auth_host,
|
|
778
|
+
client_id: record.client_id
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
function parseToken(record, path, profile) {
|
|
782
|
+
return buildTokenCredential(
|
|
783
|
+
{
|
|
784
|
+
instance: record.instance_base_url,
|
|
785
|
+
workspaceId: record.workspace_id,
|
|
786
|
+
token: record.meta_api_token
|
|
787
|
+
},
|
|
788
|
+
{
|
|
789
|
+
at: whereIn("token", path, profile),
|
|
790
|
+
labels: {
|
|
791
|
+
instance: "instance_base_url",
|
|
792
|
+
workspace: "workspace_id",
|
|
793
|
+
token: "meta_api_token"
|
|
794
|
+
},
|
|
795
|
+
fix: `Edit \`meta_api_token\` in ${path} to hold the token alone, on one line.`
|
|
796
|
+
}
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
function buildTokenCredential(values, source) {
|
|
800
|
+
const { at, labels } = source;
|
|
801
|
+
const raw = values.instance;
|
|
802
|
+
if (typeof raw !== "string" || raw.trim() === "") {
|
|
803
|
+
throw new Error(
|
|
804
|
+
`Missing \`${labels.instance}\` ${at}. It must be the instance URL, e.g. "https://your-instance.xano.io".`
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
let origin;
|
|
808
|
+
try {
|
|
809
|
+
origin = new URL(raw.trim()).origin;
|
|
810
|
+
} catch {
|
|
811
|
+
throw new Error(
|
|
812
|
+
`\`${labels.instance}\` ${at} is not a valid URL ("${raw}"), e.g. "https://your-instance.xano.io".`
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
assertHttpsOrigin(raw.trim(), `\`${labels.instance}\` ${at}`);
|
|
816
|
+
const token = values.token;
|
|
817
|
+
if (typeof token !== "string" || token.trim() === "") {
|
|
818
|
+
throw new Error(`Missing \`${labels.token}\` ${at}. It must be a meta API bearer token.`);
|
|
819
|
+
}
|
|
820
|
+
registerSecret(token);
|
|
821
|
+
const problem = tokenTextProblem(token.trim());
|
|
822
|
+
if (problem !== void 0) {
|
|
823
|
+
throw new UsageError(
|
|
824
|
+
`${labels.token.includes(" ") ? labels.token[0].toUpperCase() + labels.token.slice(1) : `\`${labels.token}\``} ${at} ${problem}, which no request can carry. ` + (source.fix ?? `Set it to the meta API token alone \u2014 one line, nothing else.`)
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
if (values.workspaceId === void 0) {
|
|
828
|
+
throw new Error(`Missing \`${labels.workspace}\` ${at}. It must be the numeric workspace id.`);
|
|
829
|
+
}
|
|
830
|
+
return {
|
|
831
|
+
type: "token",
|
|
832
|
+
instance_base_url: origin,
|
|
833
|
+
workspace_id: requireWorkspaceId(values.workspaceId, at, labels.workspace),
|
|
834
|
+
// A pasted token routinely carries stray whitespace or a newline.
|
|
835
|
+
meta_api_token: token.trim()
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
function isSafeIdText(text) {
|
|
839
|
+
return /^\d+$/.test(text.trim()) && Number.isSafeInteger(Number(text.trim()));
|
|
840
|
+
}
|
|
841
|
+
function requireWorkspaceId(value, at, label = "workspace_id") {
|
|
842
|
+
if (typeof value === "string" && /^\d+$/.test(value.trim()) || typeof value === "number" && Number.isInteger(value) && !Number.isSafeInteger(value)) {
|
|
843
|
+
throw new UsageError(
|
|
844
|
+
`\`${label}\` ${at} is out of range (got ${typeof value === "string" ? value.trim() : JSON.stringify(value)}) \u2014 a workspace id is a positive integer no larger than ${Number.MAX_SAFE_INTEGER}.`
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
|
|
848
|
+
throw new UsageError(`\`${label}\` ${at} must be a positive integer (got ${JSON.stringify(value)}).`);
|
|
849
|
+
}
|
|
850
|
+
return value;
|
|
851
|
+
}
|
|
852
|
+
function writeCredentialFile(path, file) {
|
|
853
|
+
const existing = readExistingRaw(path);
|
|
854
|
+
assertOursIfPresent(existing, path, "overwrite");
|
|
855
|
+
assertWritableVersion(existing, path);
|
|
856
|
+
const body = {
|
|
857
|
+
version: CREDENTIAL_FILE_VERSION,
|
|
858
|
+
...file.default === void 0 ? {} : { default: file.default },
|
|
859
|
+
profiles: file.profiles,
|
|
860
|
+
...unknownTopLevelKeys(existing)
|
|
861
|
+
};
|
|
862
|
+
try {
|
|
863
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
864
|
+
atomicWrite(path, JSON.stringify(body, null, 2) + "\n", { mode: 384 });
|
|
865
|
+
} catch (err) {
|
|
866
|
+
throw credentialWriteFailure(err, path);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
function assertCredentialFileWritable(path) {
|
|
870
|
+
if (isDirectory(path)) {
|
|
871
|
+
throw new UsageError(`${path} is a directory, so no credential can be written there. Remove or rename it, or pass \`--config <file>\`.`);
|
|
872
|
+
}
|
|
873
|
+
const existing = readExistingRaw(path);
|
|
874
|
+
assertOursIfPresent(existing, path, "overwrite");
|
|
875
|
+
assertWritableVersion(existing, path);
|
|
876
|
+
const target = linkTarget(path);
|
|
877
|
+
assertDirectoryWritable(target);
|
|
878
|
+
assertLockPathUsable(target);
|
|
879
|
+
}
|
|
880
|
+
function assertDirectoryWritable(path) {
|
|
881
|
+
const blocker = fileInTheWay(path);
|
|
882
|
+
if (blocker !== void 0) {
|
|
883
|
+
throw new UsageError(
|
|
884
|
+
`No credential can be written to ${path}: ${blocker} is a file, not a directory. Pass \`--config <file>\` with a path whose directories are directories.`
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
let dir = dirname2(path);
|
|
888
|
+
while (!existsSync2(dir)) {
|
|
889
|
+
const parent = dirname2(dir);
|
|
890
|
+
if (parent === dir) return;
|
|
891
|
+
dir = parent;
|
|
892
|
+
}
|
|
893
|
+
try {
|
|
894
|
+
accessSync(dir, fsConstants.W_OK | fsConstants.X_OK);
|
|
895
|
+
} catch (err) {
|
|
896
|
+
if (err?.code === "EROFS") {
|
|
897
|
+
throw new UsageError(`No credential can be written to ${path}: ${dir} is on a read-only file system (EROFS). Pass \`--config <file>\` somewhere you can write.`);
|
|
898
|
+
}
|
|
899
|
+
throw new UsageError(
|
|
900
|
+
`No credential can be written to ${path}: ${dir} is not writable by this user. Make it writable, or pass \`--config <file>\` somewhere you can write.`
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
function assertWritableVersion(existing, path, verb = "overwrite") {
|
|
905
|
+
if (existing.absent || existing.unparseable) return;
|
|
906
|
+
const version = existing.value?.version;
|
|
907
|
+
if (typeof version === "number" && version > CREDENTIAL_FILE_VERSION) {
|
|
908
|
+
throw new Error(
|
|
909
|
+
`Credential file at ${path} is version ${version}, newer than this CLI understands (${CREDENTIAL_FILE_VERSION}). Refusing to ${verb} it \u2014 ${UPGRADE_REMEDY}`
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
function assertCredentialFileRemovable(path) {
|
|
914
|
+
const existing = readExistingRaw(path);
|
|
915
|
+
assertOursIfPresent(existing, path, "delete");
|
|
916
|
+
assertWritableVersion(existing, path, "delete");
|
|
917
|
+
}
|
|
918
|
+
function unknownTopLevelKeys(existing) {
|
|
919
|
+
const value = existing.value;
|
|
920
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return {};
|
|
921
|
+
const record = value;
|
|
922
|
+
if (!isEnvelope(record)) return {};
|
|
923
|
+
return Object.fromEntries(Object.entries(record).filter(([key2]) => key2 !== "version" && key2 !== "default" && key2 !== "profiles"));
|
|
924
|
+
}
|
|
925
|
+
function readExistingRaw(path) {
|
|
926
|
+
if (!credentialFileExists(path)) return { absent: true, unparseable: false, value: void 0 };
|
|
927
|
+
const text = readCredentialText(path);
|
|
928
|
+
try {
|
|
929
|
+
return { absent: false, unparseable: false, value: JSON.parse(text) };
|
|
930
|
+
} catch {
|
|
931
|
+
return { absent: false, unparseable: true, value: void 0 };
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
function assertOursIfPresent(existing, path, verb) {
|
|
935
|
+
if (existing.absent) return;
|
|
936
|
+
if (!existing.unparseable && isCredentialRecord(existing.value)) return;
|
|
937
|
+
{
|
|
938
|
+
throw new Error(
|
|
939
|
+
`Refusing to ${verb} ${path}: it exists but is not a Xano SDK credential file. Choose a different --config/$XANO_CONFIG path \u2014 or, if this really is a credential file that has been corrupted beyond recognition, delete it yourself and run \`${loginCommand(DEFAULT_PROFILE, { path })}\` again.`
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
var CREDENTIAL_LOCK_OPTS = {
|
|
944
|
+
// The retry budget deliberately EXCEEDS `stale`: a writer killed mid-write
|
|
945
|
+
// leaves a lock that only goes stale at 20s, and a budget shorter than that
|
|
946
|
+
// would fail the next command with a raw ELOCKED for a process that no longer
|
|
947
|
+
// exists. ~60s of retries against a 20s stale window means the common crash
|
|
948
|
+
// recovers by waiting rather than by the user deleting a lock directory.
|
|
949
|
+
retries: { retries: 60, factor: 1.3, minTimeout: 50, maxTimeout: 1e3 },
|
|
950
|
+
stale: 2e4
|
|
951
|
+
};
|
|
952
|
+
async function withCredentialLock(path, fn) {
|
|
953
|
+
const target = linkTarget(path);
|
|
954
|
+
assertDirectoryWritable(target);
|
|
955
|
+
let created;
|
|
956
|
+
let release;
|
|
957
|
+
try {
|
|
958
|
+
mkdirSync2(dirname2(target), { recursive: true });
|
|
959
|
+
created = bootstrapEnvelope(target);
|
|
960
|
+
} catch (err) {
|
|
961
|
+
throw credentialWriteFailure(err, path);
|
|
962
|
+
}
|
|
963
|
+
try {
|
|
964
|
+
release = await acquireCredentialLock(target);
|
|
965
|
+
} catch (err) {
|
|
966
|
+
if (created && holdsNoProfiles(target)) rmSync(target, { force: true });
|
|
967
|
+
throw credentialWriteFailure(err, path);
|
|
968
|
+
}
|
|
969
|
+
try {
|
|
970
|
+
assertWritableVersion(readExistingRaw(path), path, "change");
|
|
971
|
+
return await fn(readCredentialFile(path) ?? emptyCredentialFile());
|
|
972
|
+
} catch (err) {
|
|
973
|
+
if (created && holdsNoProfiles(target)) rmSync(target, { force: true });
|
|
974
|
+
throw err;
|
|
975
|
+
} finally {
|
|
976
|
+
await release();
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
function bootstrapEnvelope(path) {
|
|
980
|
+
try {
|
|
981
|
+
writeFileSync(path, JSON.stringify(emptyCredentialFile(), null, 2) + "\n", {
|
|
982
|
+
encoding: "utf8",
|
|
983
|
+
mode: 384,
|
|
984
|
+
flag: "wx"
|
|
985
|
+
});
|
|
986
|
+
return true;
|
|
987
|
+
} catch (err) {
|
|
988
|
+
if (err.code === "EEXIST") return false;
|
|
989
|
+
throw err;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
async function acquireCredentialLock(path) {
|
|
993
|
+
assertLockPathUsable(path);
|
|
994
|
+
try {
|
|
995
|
+
return await lockfile.lock(path, CREDENTIAL_LOCK_OPTS);
|
|
996
|
+
} catch (err) {
|
|
997
|
+
if (err.code !== "ELOCKED") throw err;
|
|
998
|
+
throw new Error(
|
|
999
|
+
`Timed out waiting for the credential file lock on ${path}. Another \`xanosdk\` command is writing it \u2014 wait for that one to finish and re-run. If no other run is in progress, a previous one was killed mid-write: remove ${path}.lock and try again.`
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
function assertLockPathUsable(path) {
|
|
1004
|
+
let real = path;
|
|
1005
|
+
try {
|
|
1006
|
+
real = realpathSync(path);
|
|
1007
|
+
} catch {
|
|
1008
|
+
}
|
|
1009
|
+
const lock = `${real}.lock`;
|
|
1010
|
+
let isDir;
|
|
1011
|
+
try {
|
|
1012
|
+
isDir = lstatSync(lock).isDirectory();
|
|
1013
|
+
} catch {
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
if (!isDir) {
|
|
1017
|
+
throw new UsageError(
|
|
1018
|
+
`The credential file lock ${lock} is not a directory, so no \`xanosdk\` run can take it. If no other \`xanosdk\` command is running, remove it (\`rm ${shellQuote(lock)}\`) and try again.`
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
function clearCredential(path) {
|
|
1023
|
+
if (!credentialFileExists(path)) return false;
|
|
1024
|
+
assertCredentialFileRemovable(path);
|
|
1025
|
+
removeCredentialFile(path);
|
|
1026
|
+
return true;
|
|
1027
|
+
}
|
|
1028
|
+
function removeCredentialFile(path) {
|
|
1029
|
+
try {
|
|
1030
|
+
rmSync(path, { force: true });
|
|
1031
|
+
} catch (err) {
|
|
1032
|
+
throw credentialWriteFailure(err, path);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
async function clearCredentialUnderLock(path, fn) {
|
|
1036
|
+
if (!credentialFileExists(path)) return false;
|
|
1037
|
+
return withCredentialLock(path, async (file) => {
|
|
1038
|
+
assertCredentialFileRemovable(path);
|
|
1039
|
+
await fn?.(file);
|
|
1040
|
+
removeCredentialFile(path);
|
|
1041
|
+
return true;
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
function writeOrRemoveCredentialFile(path, file) {
|
|
1045
|
+
if (profileNames(file).length > 0) {
|
|
1046
|
+
writeCredentialFile(path, file);
|
|
1047
|
+
return false;
|
|
1048
|
+
}
|
|
1049
|
+
assertCredentialFileRemovable(path);
|
|
1050
|
+
removeCredentialFile(path);
|
|
1051
|
+
return true;
|
|
1052
|
+
}
|
|
1053
|
+
function removeEmptyProjectCacheDir(path) {
|
|
1054
|
+
if (resolve(path) !== localAuthFilePath() || existsSync2(path)) return false;
|
|
1055
|
+
try {
|
|
1056
|
+
rmdirSync(dirname2(localAuthFilePath()));
|
|
1057
|
+
return true;
|
|
1058
|
+
} catch {
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
function dropProfile(file, name) {
|
|
1063
|
+
delete file.profiles[name];
|
|
1064
|
+
if (file.default === name) delete file.default;
|
|
1065
|
+
return file;
|
|
1066
|
+
}
|
|
1067
|
+
function insideGitRepo(path) {
|
|
1068
|
+
return findGitRoot(dirname2(resolve(path))) !== void 0;
|
|
1069
|
+
}
|
|
1070
|
+
function findGitRoot(startDir) {
|
|
1071
|
+
let dir = resolve(startDir);
|
|
1072
|
+
for (; ; ) {
|
|
1073
|
+
if (existsSync2(join2(dir, ".git"))) return dir;
|
|
1074
|
+
const parent = dirname2(dir);
|
|
1075
|
+
if (parent === dir) return void 0;
|
|
1076
|
+
dir = parent;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
function ensureGitignored(authFilePath, opts = {}) {
|
|
1080
|
+
return addGitignoreRule(authFilePath, opts) !== void 0;
|
|
1081
|
+
}
|
|
1082
|
+
function gitignoreRootFor(path, opts = {}) {
|
|
1083
|
+
return findGitRoot(dirname2(resolve(path))) ?? opts.root ?? process.cwd();
|
|
1084
|
+
}
|
|
1085
|
+
function addGitignoreRule(authFilePath, opts = {}) {
|
|
1086
|
+
const abs = resolve(authFilePath);
|
|
1087
|
+
const root = gitignoreRootFor(abs, opts);
|
|
1088
|
+
const rel = relative(root, abs);
|
|
1089
|
+
if (rel.startsWith("..")) return void 0;
|
|
1090
|
+
const containingDir = dirname2(abs);
|
|
1091
|
+
const insideBackend = relForwardSlash(root, containingDir).split("/").includes(XANO_DIR);
|
|
1092
|
+
const dedicatedDir = basename(containingDir) === DEDICATED_DIR;
|
|
1093
|
+
const fileEntry = relForwardSlash(root, abs);
|
|
1094
|
+
const entry = opts.fileOnly === true || insideBackend || containingDir === root || !dedicatedDir ? (
|
|
1095
|
+
// This file, and nothing around it. A root-level name is anchored with a
|
|
1096
|
+
// leading slash: a pattern with no slash in it matches at ANY depth, so
|
|
1097
|
+
// `out.json` also ignored every `out.json` in every subdirectory (E2E
|
|
1098
|
+
// pass 26). One with a slash inside is anchored already.
|
|
1099
|
+
fileEntry.includes("/") ? fileEntry : `/${fileEntry}`
|
|
1100
|
+
) : relForwardSlash(root, containingDir) + "/";
|
|
1101
|
+
const gitignorePath = join2(root, ".gitignore");
|
|
1102
|
+
const existing = existsSync2(gitignorePath) ? readFileSync2(gitignorePath, "utf8") : "";
|
|
1103
|
+
if (ignoreCovers(existing, entry) || ignoreCovers(existing, fileEntry) || gitIgnores(abs)) return void 0;
|
|
1104
|
+
const prefix = existing.length === 0 || existing.endsWith("\n") ? existing : existing + "\n";
|
|
1105
|
+
writeFileSync(gitignorePath, `${prefix}${entry}
|
|
1106
|
+
`, "utf8");
|
|
1107
|
+
return entry;
|
|
1108
|
+
}
|
|
1109
|
+
function gitIgnores(abs) {
|
|
1110
|
+
return gitSaysIgnored(abs) === true;
|
|
1111
|
+
}
|
|
1112
|
+
function gitSaysIgnored(abs) {
|
|
1113
|
+
const root = findGitRoot(dirname2(resolve(abs)));
|
|
1114
|
+
if (root === void 0) return void 0;
|
|
1115
|
+
try {
|
|
1116
|
+
execFileSync("git", ["check-ignore", "-q", "--", abs], {
|
|
1117
|
+
cwd: root,
|
|
1118
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
1119
|
+
// Bounded: `execFileSync` with no timeout blocks forever on a git that
|
|
1120
|
+
// hangs or stops to prompt. A timeout reads as "cannot tell", which falls
|
|
1121
|
+
// through to the line comparison — the conservative direction.
|
|
1122
|
+
timeout: 5e3
|
|
1123
|
+
});
|
|
1124
|
+
return true;
|
|
1125
|
+
} catch (err) {
|
|
1126
|
+
const status = err.status;
|
|
1127
|
+
return status === 1 ? false : void 0;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
function relForwardSlash(root, target) {
|
|
1131
|
+
return relative(root, target).split(/[\\/]/).join("/");
|
|
1132
|
+
}
|
|
1133
|
+
function ignoreCovers(gitignore, entry) {
|
|
1134
|
+
const want = entry.replace(/\/$/, "");
|
|
1135
|
+
return gitignore.split(/\r?\n/).map((line) => line.trim().replace(/\/$/, "")).some((line) => line.length > 0 && !line.startsWith("#") && line === want);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
export {
|
|
1139
|
+
CALLBACK_PATH,
|
|
1140
|
+
DEFAULT_PORT,
|
|
1141
|
+
decodeAudience,
|
|
1142
|
+
oauthErrorCode,
|
|
1143
|
+
oauthErrorResponse,
|
|
1144
|
+
resolveOAuthErrorCode,
|
|
1145
|
+
OpenIdProvider,
|
|
1146
|
+
resolveAuthHost,
|
|
1147
|
+
envOrigin,
|
|
1148
|
+
resolveScope,
|
|
1149
|
+
assertHttpsOrigin,
|
|
1150
|
+
assertSignInOrigin,
|
|
1151
|
+
assertInstanceOrigin,
|
|
1152
|
+
assertNoUserinfo,
|
|
1153
|
+
CREDENTIAL_FILE_VERSION,
|
|
1154
|
+
emptyCredentialFile,
|
|
1155
|
+
globalAuthFilePath,
|
|
1156
|
+
localAuthFilePath,
|
|
1157
|
+
resolveAuthFilePath,
|
|
1158
|
+
assertCredentialPathUsable,
|
|
1159
|
+
projectAuthFilePath,
|
|
1160
|
+
readCredentialFile,
|
|
1161
|
+
readProfile,
|
|
1162
|
+
UPGRADE_REMEDY,
|
|
1163
|
+
readCredential,
|
|
1164
|
+
profileNames,
|
|
1165
|
+
recordedCredentialPath,
|
|
1166
|
+
recordedCredentialFile,
|
|
1167
|
+
recordedProfileStored,
|
|
1168
|
+
describeProfile,
|
|
1169
|
+
loginHint,
|
|
1170
|
+
credentialFileFlag,
|
|
1171
|
+
loginCommand,
|
|
1172
|
+
sameOriginAs,
|
|
1173
|
+
addProfileHint,
|
|
1174
|
+
profileAddCommand,
|
|
1175
|
+
buildTokenCredential,
|
|
1176
|
+
isSafeIdText,
|
|
1177
|
+
writeCredentialFile,
|
|
1178
|
+
assertCredentialFileWritable,
|
|
1179
|
+
assertCredentialFileRemovable,
|
|
1180
|
+
withCredentialLock,
|
|
1181
|
+
clearCredential,
|
|
1182
|
+
clearCredentialUnderLock,
|
|
1183
|
+
writeOrRemoveCredentialFile,
|
|
1184
|
+
removeEmptyProjectCacheDir,
|
|
1185
|
+
dropProfile,
|
|
1186
|
+
insideGitRepo,
|
|
1187
|
+
ensureGitignored,
|
|
1188
|
+
gitignoreRootFor,
|
|
1189
|
+
addGitignoreRule,
|
|
1190
|
+
gitSaysIgnored
|
|
1191
|
+
};
|
|
1192
|
+
//# sourceMappingURL=chunk-XW6KISBB.js.map
|