@xano/cli 1.3.3 → 1.3.5-beta.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/README.md +137 -3
- package/dist/base-command.d.ts +12 -1
- package/dist/base-command.js +36 -22
- package/dist/commands/branch/set_live/index.d.ts +14 -0
- package/dist/commands/branch/set_live/index.js +38 -0
- package/dist/commands/ephemeral/push/index.js +6 -1
- package/dist/commands/function/create/index.d.ts +15 -0
- package/dist/commands/function/create/index.js +39 -0
- package/dist/commands/function/edit/index.d.ts +15 -0
- package/dist/commands/function/edit/index.js +39 -0
- package/dist/commands/function/run/index.js +5 -11
- package/dist/commands/policy/catalogue/index.d.ts +16 -0
- package/dist/commands/policy/catalogue/index.js +60 -0
- package/dist/commands/policy/coverage/index.d.ts +17 -0
- package/dist/commands/policy/coverage/index.js +33 -0
- package/dist/commands/policy/create/index.d.ts +18 -0
- package/dist/commands/policy/create/index.js +44 -0
- package/dist/commands/policy/delete/index.d.ts +19 -0
- package/dist/commands/policy/delete/index.js +61 -0
- package/dist/commands/policy/evaluate/index.d.ts +32 -0
- package/dist/commands/policy/evaluate/index.js +71 -0
- package/dist/commands/policy/list/index.d.ts +14 -0
- package/dist/commands/policy/list/index.js +21 -0
- package/dist/commands/policy/parse/index.d.ts +19 -0
- package/dist/commands/policy/parse/index.js +18 -0
- package/dist/commands/policy/publish/index.d.ts +20 -0
- package/dist/commands/policy/publish/index.js +38 -0
- package/dist/commands/policy/runs/index.d.ts +54 -0
- package/dist/commands/policy/runs/index.js +273 -0
- package/dist/commands/policy/status/index.d.ts +21 -0
- package/dist/commands/policy/status/index.js +54 -0
- package/dist/commands/release/create/index.js +4 -0
- package/dist/commands/release/deploy/index.d.ts +19 -0
- package/dist/commands/release/deploy/index.js +68 -6
- package/dist/commands/release/import/index.js +5 -1
- package/dist/commands/release/list/index.d.ts +7 -0
- package/dist/commands/release/list/index.js +46 -9
- package/dist/commands/release/push/index.js +19 -1
- package/dist/commands/sandbox/delete/index.js +8 -0
- package/dist/commands/sandbox/pull/index.d.ts +7 -0
- package/dist/commands/sandbox/pull/index.js +49 -0
- package/dist/commands/sandbox/push/index.d.ts +4 -0
- package/dist/commands/sandbox/push/index.js +59 -2
- package/dist/commands/sandbox/reset/index.js +8 -0
- package/dist/commands/skills/pull/index.d.ts +30 -0
- package/dist/commands/skills/pull/index.js +118 -0
- package/dist/commands/tenant/create/index.d.ts +3 -0
- package/dist/commands/tenant/create/index.js +21 -0
- package/dist/commands/tenant/deploy_release/index.d.ts +40 -0
- package/dist/commands/tenant/deploy_release/index.js +182 -4
- package/dist/commands/tenant/edit/index.d.ts +2 -0
- package/dist/commands/tenant/edit/index.js +39 -4
- package/dist/commands/tenant_deploy_request/bypass/index.d.ts +31 -0
- package/dist/commands/tenant_deploy_request/bypass/index.js +109 -0
- package/dist/commands/tenant_deploy_request/create/index.d.ts +21 -0
- package/dist/commands/tenant_deploy_request/create/index.js +108 -0
- package/dist/commands/tenant_deploy_request/edit/index.d.ts +22 -0
- package/dist/commands/tenant_deploy_request/edit/index.js +103 -0
- package/dist/commands/tenant_deploy_request/get/index.d.ts +25 -0
- package/dist/commands/tenant_deploy_request/get/index.js +113 -0
- package/dist/commands/tenant_deploy_request/list/index.d.ts +20 -0
- package/dist/commands/tenant_deploy_request/list/index.js +122 -0
- package/dist/commands/tenant_deploy_request/revision/index.d.ts +21 -0
- package/dist/commands/tenant_deploy_request/revision/index.js +83 -0
- package/dist/commands/tenant_deploy_request/set_status/index.d.ts +38 -0
- package/dist/commands/tenant_deploy_request/set_status/index.js +159 -0
- package/dist/commands/workspace/pull/index.d.ts +17 -0
- package/dist/commands/workspace/pull/index.js +124 -7
- package/dist/commands/workspace/push/index.d.ts +23 -0
- package/dist/commands/workspace/push/index.js +188 -6
- package/dist/policy-command.d.ts +91 -0
- package/dist/policy-command.js +95 -0
- package/dist/utils/document-parser.d.ts +10 -0
- package/dist/utils/document-parser.js +21 -0
- package/dist/utils/export-compare.d.ts +52 -0
- package/dist/utils/export-compare.js +134 -0
- package/dist/utils/local-config.d.ts +23 -0
- package/dist/utils/local-config.js +62 -2
- package/dist/utils/multidoc-push.d.ts +124 -4
- package/dist/utils/multidoc-push.js +670 -49
- package/dist/utils/object-fetch.d.ts +86 -0
- package/dist/utils/object-fetch.js +265 -0
- package/dist/utils/policy/catalogue.d.ts +85 -0
- package/dist/utils/policy/catalogue.js +196 -0
- package/dist/utils/policy/coverage.d.ts +19 -0
- package/dist/utils/policy/coverage.js +93 -0
- package/dist/utils/policy/errors.d.ts +20 -0
- package/dist/utils/policy/errors.js +93 -0
- package/dist/utils/policy/feedback.d.ts +90 -0
- package/dist/utils/policy/feedback.js +210 -0
- package/dist/utils/policy/findings.d.ts +29 -0
- package/dist/utils/policy/findings.js +82 -0
- package/dist/utils/policy/gate.d.ts +138 -0
- package/dist/utils/policy/gate.js +193 -0
- package/dist/utils/policy/permission.d.ts +7 -0
- package/dist/utils/policy/permission.js +55 -0
- package/dist/utils/policy/push-gate.d.ts +5 -0
- package/dist/utils/policy/push-gate.js +43 -0
- package/dist/utils/policy/release.d.ts +85 -0
- package/dist/utils/policy/release.js +117 -0
- package/dist/utils/policy/request.d.ts +55 -0
- package/dist/utils/policy/request.js +119 -0
- package/dist/utils/policy/runs.d.ts +30 -0
- package/dist/utils/policy/runs.js +101 -0
- package/dist/utils/policy/status.d.ts +55 -0
- package/dist/utils/policy/status.js +127 -0
- package/dist/utils/policy/template.d.ts +66 -0
- package/dist/utils/policy/template.js +152 -0
- package/dist/utils/policy/types.d.ts +295 -0
- package/dist/utils/policy/types.js +1 -0
- package/dist/utils/reference-checker.d.ts +3 -0
- package/dist/utils/reference-checker.js +3 -2
- package/dist/utils/sandbox-marker.d.ts +9 -0
- package/dist/utils/sandbox-marker.js +119 -0
- package/dist/utils/sync-state.d.ts +71 -0
- package/dist/utils/sync-state.js +353 -0
- package/oclif.manifest.json +6472 -3181
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -197,13 +197,16 @@ xano workspace delete -w <workspace_id> --force
|
|
|
197
197
|
xano workspace pull
|
|
198
198
|
xano workspace pull -d ./my-workspace # Specify output directory
|
|
199
199
|
xano workspace pull -b dev # Specific branch
|
|
200
|
+
xano workspace pull -b '' # The live branch, whatever the profile's branch
|
|
200
201
|
xano workspace pull --env --records # Include env vars and table records
|
|
201
202
|
xano workspace pull --draft # Include draft changes
|
|
203
|
+
xano workspace pull --sync-state # Write .xano/sync_state.json; later pulls keep it current
|
|
202
204
|
|
|
203
205
|
# Push local files to workspace (defaults to current directory, only changed files)
|
|
204
206
|
xano workspace push
|
|
205
207
|
xano workspace push -d ./my-workspace # Push from a specific directory
|
|
206
208
|
xano workspace push -b dev
|
|
209
|
+
xano workspace push -b '' # The live branch, whatever the profile's branch
|
|
207
210
|
xano workspace push --sync # Full push — send all files, not just changed ones
|
|
208
211
|
xano workspace push --sync --delete # Full push + delete remote objects not included
|
|
209
212
|
xano workspace push --dry-run # Preview changes without pushing
|
|
@@ -212,10 +215,16 @@ xano workspace push --env # Include environment v
|
|
|
212
215
|
xano workspace push --truncate # Truncate tables before import
|
|
213
216
|
xano workspace push --no-transaction # Disable database transaction wrapping
|
|
214
217
|
xano workspace push --no-guids # Skip writing GUIDs back to local files
|
|
218
|
+
xano workspace push --fast # Push only files that changed since last sync
|
|
219
|
+
xano workspace push --fast --refresh # Re-compare against the server export, then push
|
|
220
|
+
xano workspace push --no-fast # Skip --fast even when profile.yaml sets fast: true
|
|
221
|
+
xano workspace push --writeback # Write server-formatted XanoScript back to pushed files
|
|
222
|
+
xano workspace push --writeback --writeback-diff ./diffs # Also save local vs server copies for linter work
|
|
215
223
|
xano workspace push --force # Skip preview and confirmation (for CI/CD)
|
|
216
224
|
xano workspace push -i "function/*" # Push only matching files
|
|
217
225
|
xano workspace push -e "table/*" # Push all files except tables
|
|
218
226
|
xano workspace push -i "function/*" -e "**/test*" # Include functions, exclude tests
|
|
227
|
+
xano workspace push -o json # One JSON document: the preview (with --dry-run) or the import result
|
|
219
228
|
|
|
220
229
|
# Pull from a git repository to local files (defaults to current directory)
|
|
221
230
|
xano workspace git pull -r https://github.com/owner/repo
|
|
@@ -244,6 +253,44 @@ directory *adds* a file rather than overwriting, and renaming a workspace leaves
|
|
|
244
253
|
the old-name file behind. Delete the stale ones, keeping the single document that
|
|
245
254
|
matches your target workspace.
|
|
246
255
|
|
|
256
|
+
### `.xano/sync_state.json`
|
|
257
|
+
|
|
258
|
+
`--fast` skips files whose contents still match a local sync
|
|
259
|
+
record at `<push dir>/.xano/sync_state.json`. The file is per-working-copy
|
|
260
|
+
state: it should not be committed. Ignoring it is up to you (the CLI never
|
|
261
|
+
edits `.gitignore` or any other VCS ignore file).
|
|
262
|
+
|
|
263
|
+
The record is created by `workspace pull --sync-state`, `sandbox pull --sync-state`,
|
|
264
|
+
`workspace push --fast`, or `sandbox push --fast`.
|
|
265
|
+
Once it exists, every successful workspace or sandbox push or pull updates it. A successful
|
|
266
|
+
`workspace push --sync --delete` or `sandbox push --sync --delete` also removes entries for
|
|
267
|
+
files deleted locally, which clears the "deleted locally" note. A default push or pull does
|
|
268
|
+
not create it. `--dry-run` never writes it, and neither does `pull --draft`, because
|
|
269
|
+
`--fast` compares against published versions. Push records the exact bytes it sent, so a
|
|
270
|
+
file edited while a push runs is sent again by the next `--fast` push. Delete the file to turn this off; the next
|
|
271
|
+
`--fast` push compares against the server export once (~99 seconds on a
|
|
272
|
+
~1,100-document workspace) and rebuilds it. If that export fails, the CLI falls
|
|
273
|
+
back to a full dry-run.
|
|
274
|
+
|
|
275
|
+
Sandbox `--fast` also keeps a per-user marker at `sandbox_sync.json` next to your
|
|
276
|
+
credentials file (normally `~/.xano/sandbox_sync.json`). A successful sandbox push
|
|
277
|
+
or `sandbox pull --sync-state` writes a new token there and into the directory's
|
|
278
|
+
record. `sandbox reset` and `sandbox delete` clear the host's entries, as does a
|
|
279
|
+
sandbox push from another directory. If the tokens do not match, `--fast` compares
|
|
280
|
+
against the sandbox export once instead of trusting the record. After a reset or
|
|
281
|
+
push done outside this CLI or on another machine, use `--refresh`.
|
|
282
|
+
|
|
283
|
+
`--refresh` (requires `--fast`) runs that export comparison even when a record
|
|
284
|
+
already exists. Use it after edits in the Xano UI, a teammate's push, or when
|
|
285
|
+
the record looks wrong. `--fast` will not overwrite remote-only changes; a push
|
|
286
|
+
without `--fast` still will.
|
|
287
|
+
|
|
288
|
+
`--fast` cannot be combined with `--sync`. It works with `--force` and still
|
|
289
|
+
sends only the files the record (or export comparison) marked as changed.
|
|
290
|
+
With `--records`, tables that have `items` are always sent, as they are without
|
|
291
|
+
`--fast`. With `--env`, `--fast` compares every file with the dry-run, because the
|
|
292
|
+
record does not track environment variables.
|
|
293
|
+
|
|
247
294
|
### Flatten
|
|
248
295
|
|
|
249
296
|
A hand-authored `.xs` file can hold several documents joined by `---`. That is
|
|
@@ -387,8 +434,8 @@ Input flexibility for `function run` (assembled into one JSON `input` object):
|
|
|
387
434
|
to run against `live`; an unknown label is rejected by the server with
|
|
388
435
|
`Invalid data source.`
|
|
389
436
|
|
|
390
|
-
`-t/--tenant <name>` runs the function on that tenant
|
|
391
|
-
|
|
437
|
+
`-t/--tenant <name>` runs the function on that tenant
|
|
438
|
+
(`/workspace/{id}/tenant/{name}/function/run`) instead of the workspace.
|
|
392
439
|
|
|
393
440
|
Merge order is JSON base first, then `--data` overrides. Missing required inputs are
|
|
394
441
|
prompted for on an interactive terminal; in a non-TTY (CI) context the command fails
|
|
@@ -610,9 +657,14 @@ xano tenant get <tenant_name>
|
|
|
610
657
|
xano tenant create "My Tenant"
|
|
611
658
|
xano tenant create "My Tenant" -d "Description" --type tier2 --cluster_id 1 --platform_id 5
|
|
612
659
|
xano tenant create "My Tenant" --type tier2 --cluster_id 1 --license ./license.yaml
|
|
660
|
+
xano tenant create "Production" --required_reviewers 1 --allow_deploy_bypass
|
|
661
|
+
xano tenant create "Staging" --required_reviewers 0 --allow_quick_deploy
|
|
613
662
|
|
|
614
663
|
# Edit a tenant
|
|
615
664
|
xano tenant edit <tenant_name> --display "New Name" -d "New description"
|
|
665
|
+
xano tenant edit <tenant_name> --required_reviewers 1
|
|
666
|
+
xano tenant edit <tenant_name> --required_reviewers 0
|
|
667
|
+
xano tenant edit <tenant_name> --allow_quick_deploy
|
|
616
668
|
|
|
617
669
|
# Delete a tenant (confirmation required)
|
|
618
670
|
xano tenant delete <tenant_name>
|
|
@@ -663,6 +715,65 @@ xano tenant deploy_release <tenant_name> --release v1.0
|
|
|
663
715
|
xano tenant deploy_platform <tenant_name> --platform_id 5 --license ./license.yaml
|
|
664
716
|
```
|
|
665
717
|
|
|
718
|
+
If a tenant has `required_reviewers` set above 0, `tenant deploy_release` fails with an
|
|
719
|
+
error naming the `tenant_deploy_request` command to run instead — see below. A tenant
|
|
720
|
+
with `allow_quick_deploy` enabled skips the gate entirely, regardless of
|
|
721
|
+
`required_reviewers`.
|
|
722
|
+
|
|
723
|
+
#### Tenant Deploy Requests
|
|
724
|
+
|
|
725
|
+
When a tenant has `required_reviewers` set above 0 and `allow_quick_deploy` is off (see
|
|
726
|
+
`tenant create`/`tenant edit` above), a release may only be deployed to it through an
|
|
727
|
+
**approved deploy request**, requiring that many distinct reviewer approvals. An author
|
|
728
|
+
with no deploy permission opens a request; a named reviewer who does hold deploy
|
|
729
|
+
permission on the tenant votes to approve it, and once enough reviewers have approved,
|
|
730
|
+
it deploys as part of that action. `allow_deploy_bypass` (set at `tenant create` time
|
|
731
|
+
only) additionally allows an authorized user to skip a specific request's gate via
|
|
732
|
+
`tenant_deploy_request bypass`, audited with a required reason.
|
|
733
|
+
|
|
734
|
+
```bash
|
|
735
|
+
# List deploy requests in a workspace (results show the target tenant and release)
|
|
736
|
+
xano tenant_deploy_request list
|
|
737
|
+
xano tenant_deploy_request list --tenant prod --status pending
|
|
738
|
+
xano tenant_deploy_request list --tenant prod --release v1.2
|
|
739
|
+
xano tenant_deploy_request list --to-review
|
|
740
|
+
|
|
741
|
+
# Get details of a specific deploy request
|
|
742
|
+
xano tenant_deploy_request get <id>
|
|
743
|
+
|
|
744
|
+
# Open a deploy request (submits for review immediately unless --draft is passed)
|
|
745
|
+
xano tenant_deploy_request create "Deploy v1.2 to prod" --tenant prod --release v1.2 --reviewers 12,45
|
|
746
|
+
xano tenant_deploy_request create "Deploy v1.2 to prod" --tenant prod --release v1.2 --draft
|
|
747
|
+
|
|
748
|
+
# Edit a deploy request's title, description or reviewers
|
|
749
|
+
xano tenant_deploy_request edit <id> --title "Deploy v1.2.1 to prod"
|
|
750
|
+
xano tenant_deploy_request edit <id> --reviewers 12,45,67
|
|
751
|
+
|
|
752
|
+
# Change status: draft, submit, approve, request_changes, close, or reopen
|
|
753
|
+
# (approve records one reviewer's vote; once enough votes are in it deploys the
|
|
754
|
+
# release as part of that same call)
|
|
755
|
+
xano tenant_deploy_request set_status <id> --status submit
|
|
756
|
+
xano tenant_deploy_request set_status <id> --status approve
|
|
757
|
+
xano tenant_deploy_request set_status <id> --status request_changes --reason "Needs a migration note"
|
|
758
|
+
xano tenant_deploy_request set_status <id> --status close --reason "Superseded by a newer request"
|
|
759
|
+
xano tenant_deploy_request set_status <id> --status reopen
|
|
760
|
+
|
|
761
|
+
# Link a newer release to a request after changes were requested (author only)
|
|
762
|
+
xano tenant_deploy_request revision <id> --release v1.2.1 --note "Added the missing migration"
|
|
763
|
+
|
|
764
|
+
# Deploy past the approval gate under the deploy-bypass permission (requires the
|
|
765
|
+
# tenant's allow_deploy_bypass to be enabled, and a separately-grantable RBAC
|
|
766
|
+
# permission). Every use is audited.
|
|
767
|
+
xano tenant_deploy_request bypass <id> --reason "Prod incident, reviewer unavailable"
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
CI example (e.g. a GitHub Action step):
|
|
771
|
+
|
|
772
|
+
```bash
|
|
773
|
+
xano tenant_deploy_request create "$PR_TITLE" --tenant prod --release v1.2 \
|
|
774
|
+
--description "$PR_BODY" --reviewers 12,45 -o json
|
|
775
|
+
```
|
|
776
|
+
|
|
666
777
|
#### Tenant License
|
|
667
778
|
|
|
668
779
|
```bash
|
|
@@ -820,6 +931,7 @@ xano sandbox get -o json
|
|
|
820
931
|
xano sandbox pull
|
|
821
932
|
xano sandbox pull -d ./my-sandbox
|
|
822
933
|
xano sandbox pull --env --records
|
|
934
|
+
xano sandbox pull --sync-state # Write .xano/sync_state.json; later pulls keep it current
|
|
823
935
|
|
|
824
936
|
# Push local files to sandbox (defaults to current directory, only changed files)
|
|
825
937
|
xano sandbox push
|
|
@@ -827,6 +939,11 @@ xano sandbox push -d ./my-workspace # Push from a specific
|
|
|
827
939
|
xano sandbox push --sync # Full push — send all files
|
|
828
940
|
xano sandbox push --sync --delete # Full push + delete remote objects not included
|
|
829
941
|
xano sandbox push --dry-run # Preview changes without pushing
|
|
942
|
+
xano sandbox push --fast # Skip unchanged files using .xano/sync_state.json
|
|
943
|
+
xano sandbox push --fast --refresh # Re-compare against the sandbox export, then push
|
|
944
|
+
xano sandbox push --no-fast # Push without --fast even when profile.yaml sets fast: true
|
|
945
|
+
xano sandbox push --writeback # Write server-formatted XanoScript back after push
|
|
946
|
+
xano sandbox push --writeback --writeback-diff ./diffs # Also save local vs server copies
|
|
830
947
|
xano sandbox push --records --env # Include records and environment variables
|
|
831
948
|
xano sandbox push --truncate # Truncate tables before import
|
|
832
949
|
xano sandbox push --no-guids # Skip writing GUIDs back to local files
|
|
@@ -972,11 +1089,28 @@ workspace: 110 # optional override
|
|
|
972
1089
|
instance_origin: https://your-instance.xano.io # optional override
|
|
973
1090
|
account_origin: https://app.xano.com # optional override
|
|
974
1091
|
branch: main # optional override
|
|
1092
|
+
fast: true # optional: default workspace/sandbox push --fast
|
|
1093
|
+
writeback: true # optional: default workspace/sandbox push --writeback
|
|
1094
|
+
writeback_object_limit: 50 # optional: workspace push --writeback per-object fetch limit (default 25)
|
|
975
1095
|
```
|
|
976
1096
|
|
|
977
1097
|
When a `profile.yaml` is in effect, every command prints the active target,
|
|
978
1098
|
e.g. `Using profile 'staging' (workspace 110) · profile.yaml` (suppressed for
|
|
979
|
-
`--output json`).
|
|
1099
|
+
`--output json`). `workspace push` and `sandbox push` also log
|
|
1100
|
+
`Using --fast --writeback · profile.yaml` when those keys turn the flags on.
|
|
1101
|
+
`--no-fast` and `--no-writeback` override them for one run. `--sync` ignores a
|
|
1102
|
+
`fast: true` rather than erroring.
|
|
1103
|
+
|
|
1104
|
+
`workspace push --writeback` fetches each pushed object individually when the push
|
|
1105
|
+
has at most `writeback_object_limit` documents (default 25), and otherwise downloads
|
|
1106
|
+
the full workspace export. Per-object requests grow with the number of documents;
|
|
1107
|
+
the export costs about the same no matter how many you pushed. Raise the limit for a
|
|
1108
|
+
large workspace where the export is slow; `0` always uses the export. If a per-object
|
|
1109
|
+
request fails, writeback falls back to the export. Sandbox push always uses the export.
|
|
1110
|
+
Writeback skips, with a warning, any file that changed on disk after the push read it.
|
|
1111
|
+
|
|
1112
|
+
An explicit `-p/--profile` or `XANO_PROFILE` ignores `profile.yaml` entirely,
|
|
1113
|
+
including `fast`, `writeback`, and `writeback_object_limit`.
|
|
980
1114
|
|
|
981
1115
|
Generate one with `xano profile use`. It writes a self-documenting
|
|
982
1116
|
`profile.yaml` (every overridable field is included as a commented example, so
|
package/dist/base-command.d.ts
CHANGED
|
@@ -63,6 +63,16 @@ export default abstract class BaseCommand extends Command {
|
|
|
63
63
|
* Sets NODE_TLS_REJECT_UNAUTHORIZED=0 so all fetch() calls skip cert verification.
|
|
64
64
|
*/
|
|
65
65
|
protected applyInsecureFromProfile(): void;
|
|
66
|
+
/**
|
|
67
|
+
* Report a failure as exit 1 for commands that reserve exit 2 for findings: operational errors,
|
|
68
|
+
* flag errors and oclif's own default exit 2 all become 1, and under `-o json` the failure is also
|
|
69
|
+
* written to stdout as `{"error": {"exit": 1, "message"}}`. A deliberate exit 0 passes through.
|
|
70
|
+
*/
|
|
71
|
+
protected catchAsOperational(error: Error & {
|
|
72
|
+
oclif?: {
|
|
73
|
+
exit?: number;
|
|
74
|
+
};
|
|
75
|
+
}): Promise<void>;
|
|
66
76
|
finally(_: Error | undefined): Promise<void>;
|
|
67
77
|
/**
|
|
68
78
|
* Get the resolved credentials file path, respecting --config flag and XANO_CONFIG env var.
|
|
@@ -78,6 +88,8 @@ export default abstract class BaseCommand extends Command {
|
|
|
78
88
|
protected getOrCreateSandbox(profile: ProfileConfig, verbose: boolean): Promise<SandboxTenant>;
|
|
79
89
|
protected getProfile(): string | undefined;
|
|
80
90
|
init(): Promise<void>;
|
|
91
|
+
/** Raw-argv check: the parsed flags are not available yet when the banner prints. */
|
|
92
|
+
protected isJsonOutput(): boolean;
|
|
81
93
|
protected loadCredentialsFile(): CredentialsFile | null;
|
|
82
94
|
/**
|
|
83
95
|
* Poll a static-host build until it reaches a terminal status (ok | error),
|
|
@@ -129,7 +141,6 @@ export default abstract class BaseCommand extends Command {
|
|
|
129
141
|
verbose: boolean;
|
|
130
142
|
workspaceId: string;
|
|
131
143
|
}): Promise<string>;
|
|
132
|
-
private isJsonOutput;
|
|
133
144
|
/**
|
|
134
145
|
* Find and parse the nearest project-local profile.yaml, unless an explicit
|
|
135
146
|
* -p/XANO_PROFILE was given (in which case the local file is ignored).
|
package/dist/base-command.js
CHANGED
|
@@ -156,6 +156,18 @@ export default class BaseCommand extends Command {
|
|
|
156
156
|
// Don't fail the command if we can't read the profile for insecure check
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Report a failure as exit 1 for commands that reserve exit 2 for findings: operational errors,
|
|
161
|
+
* flag errors and oclif's own default exit 2 all become 1, and under `-o json` the failure is also
|
|
162
|
+
* written to stdout as `{"error": {"exit": 1, "message"}}`. A deliberate exit 0 passes through.
|
|
163
|
+
*/
|
|
164
|
+
async catchAsOperational(error) {
|
|
165
|
+
if (error.oclif?.exit === 0)
|
|
166
|
+
return super.catch(error);
|
|
167
|
+
if (this.isJsonOutput())
|
|
168
|
+
this.log(JSON.stringify({ error: { exit: 1, message: error.message } }, null, 2));
|
|
169
|
+
this.error(error, { exit: 1 });
|
|
170
|
+
}
|
|
159
171
|
async finally(_) {
|
|
160
172
|
if (this.updateNotice && !this.isJsonOutput()) {
|
|
161
173
|
this.log(this.updateNotice);
|
|
@@ -221,6 +233,19 @@ export default class BaseCommand extends Command {
|
|
|
221
233
|
const forceUpdateCheck = process.env.XANO_FORCE_UPDATE_CHECK === '1';
|
|
222
234
|
this.updateNotice = checkForUpdate(this.config.version, forceUpdateCheck);
|
|
223
235
|
}
|
|
236
|
+
/** Raw-argv check: the parsed flags are not available yet when the banner prints. */
|
|
237
|
+
isJsonOutput() {
|
|
238
|
+
const args = this.argv;
|
|
239
|
+
for (let i = 0; i < args.length; i++) {
|
|
240
|
+
if (args[i] === '--output' && args[i + 1] === 'json')
|
|
241
|
+
return true;
|
|
242
|
+
if (args[i] === '-o' && args[i + 1] === 'json')
|
|
243
|
+
return true;
|
|
244
|
+
if (args[i] === '--output=json' || args[i] === '-o=json')
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
224
249
|
loadCredentialsFile() {
|
|
225
250
|
const credentialsPath = this.getCredentialsPath();
|
|
226
251
|
if (!fs.existsSync(credentialsPath)) {
|
|
@@ -354,18 +379,19 @@ export default class BaseCommand extends Command {
|
|
|
354
379
|
...(timeoutMs > 0 && !options.signal ? { signal: AbortSignal.timeout(timeoutMs) } : {}),
|
|
355
380
|
});
|
|
356
381
|
const contentType = headers['Content-Type'] || 'application/json';
|
|
382
|
+
const logDiagnostic = this.isJsonOutput() ? this.logToStderr.bind(this) : this.log.bind(this);
|
|
357
383
|
if (verbose) {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
384
|
+
logDiagnostic('');
|
|
385
|
+
logDiagnostic('─'.repeat(60));
|
|
386
|
+
logDiagnostic(`→ ${method} ${url}`);
|
|
387
|
+
logDiagnostic(` Content-Type: ${contentType}`);
|
|
362
388
|
if (authToken) {
|
|
363
|
-
|
|
389
|
+
logDiagnostic(` Authorization: Bearer ${authToken.slice(0, 8)}...${authToken.slice(-4)}`);
|
|
364
390
|
}
|
|
365
391
|
if (options.body) {
|
|
366
392
|
const bodyStr = typeof options.body === 'string' ? options.body : String(options.body);
|
|
367
393
|
const bodyPreview = bodyStr.length > 500 ? bodyStr.slice(0, 500) + '...' : bodyStr;
|
|
368
|
-
|
|
394
|
+
logDiagnostic(` Body: ${bodyPreview}`);
|
|
369
395
|
}
|
|
370
396
|
}
|
|
371
397
|
const startTime = Date.now();
|
|
@@ -381,7 +407,7 @@ export default class BaseCommand extends Command {
|
|
|
381
407
|
if (dispatcherSupported && isInvalidDispatcherError(error)) {
|
|
382
408
|
dispatcherSupported = false;
|
|
383
409
|
if (verbose) {
|
|
384
|
-
|
|
410
|
+
logDiagnostic(' (this Node runtime rejected undici dispatcher; retrying without it — see DEV-7773)');
|
|
385
411
|
}
|
|
386
412
|
response = await fetch(url, buildFetchOptions(false));
|
|
387
413
|
}
|
|
@@ -391,9 +417,9 @@ export default class BaseCommand extends Command {
|
|
|
391
417
|
}
|
|
392
418
|
const elapsed = Date.now() - startTime;
|
|
393
419
|
if (verbose) {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
420
|
+
logDiagnostic(`← ${response.status} ${response.statusText} (${elapsed}ms)`);
|
|
421
|
+
logDiagnostic('─'.repeat(60));
|
|
422
|
+
logDiagnostic('');
|
|
397
423
|
}
|
|
398
424
|
return response;
|
|
399
425
|
}
|
|
@@ -468,18 +494,6 @@ export default class BaseCommand extends Command {
|
|
|
468
494
|
conclude(`stopped waiting after ${Math.round(timeoutMs / 1000)}s (last status: ${stage || 'unknown'})`);
|
|
469
495
|
return stage;
|
|
470
496
|
}
|
|
471
|
-
isJsonOutput() {
|
|
472
|
-
const args = process.argv;
|
|
473
|
-
for (let i = 0; i < args.length; i++) {
|
|
474
|
-
if (args[i] === '--output' && args[i + 1] === 'json')
|
|
475
|
-
return true;
|
|
476
|
-
if (args[i] === '-o' && args[i + 1] === 'json')
|
|
477
|
-
return true;
|
|
478
|
-
if (args[i] === '--output=json' || args[i] === '-o=json')
|
|
479
|
-
return true;
|
|
480
|
-
}
|
|
481
|
-
return false;
|
|
482
|
-
}
|
|
483
497
|
/**
|
|
484
498
|
* Find and parse the nearest project-local profile.yaml, unless an explicit
|
|
485
499
|
* -p/XANO_PROFILE was given (in which case the local file is ignored).
|
|
@@ -8,11 +8,25 @@ export default class BranchSetLive extends BaseCommand {
|
|
|
8
8
|
static flags: {
|
|
9
9
|
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
10
|
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
'policy-override': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
12
|
workspace: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
13
|
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
14
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
15
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
16
|
};
|
|
17
|
+
/** Set once the set-live policy gate refuses, the one failure that exits 2. */
|
|
18
|
+
private refusedByPolicyGate;
|
|
19
|
+
/** Exit 2 is the set-live gate's refusal; every other failure, flag errors included, exits 1. */
|
|
20
|
+
protected catch(error: Error & {
|
|
21
|
+
oclif?: {
|
|
22
|
+
exit?: number;
|
|
23
|
+
};
|
|
24
|
+
}): Promise<void>;
|
|
16
25
|
run(): Promise<void>;
|
|
17
26
|
private confirm;
|
|
27
|
+
/**
|
|
28
|
+
* The set-live gate refused: the verdict on stdout (under `-o json`, `{set_live: false, message,
|
|
29
|
+
* policy_gate}`), then the platform's message and how to proceed, exiting 2.
|
|
30
|
+
*/
|
|
31
|
+
private refuseSetLive;
|
|
18
32
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import BaseCommand from '../../../base-command.js';
|
|
3
|
+
import { BLANK_POLICY_OVERRIDE, blankPolicyOverride, gateOverrideHint, gateRefusal, liveGateLines, quoted, } from '../../../utils/policy/gate.js';
|
|
3
4
|
export default class BranchSetLive extends BaseCommand {
|
|
4
5
|
static args = {
|
|
5
6
|
branch_label: Args.string({
|
|
@@ -40,14 +41,25 @@ Branch 'v1' is now live
|
|
|
40
41
|
options: ['summary', 'json'],
|
|
41
42
|
required: false,
|
|
42
43
|
}),
|
|
44
|
+
'policy-override': Flags.string({ description: 'Set the branch live past a blocking set-live policy gate with an audited reason (requires workspace:policy update)', hidden: true }),
|
|
43
45
|
workspace: Flags.integer({
|
|
44
46
|
char: 'w',
|
|
45
47
|
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
46
48
|
required: false,
|
|
47
49
|
}),
|
|
48
50
|
};
|
|
51
|
+
/** Set once the set-live policy gate refuses, the one failure that exits 2. */
|
|
52
|
+
refusedByPolicyGate = false;
|
|
53
|
+
/** Exit 2 is the set-live gate's refusal; every other failure, flag errors included, exits 1. */
|
|
54
|
+
async catch(error) {
|
|
55
|
+
if (this.refusedByPolicyGate)
|
|
56
|
+
return super.catch(error);
|
|
57
|
+
return this.catchAsOperational(error);
|
|
58
|
+
}
|
|
49
59
|
async run() {
|
|
50
60
|
const { args, flags } = await this.parse(BranchSetLive);
|
|
61
|
+
if (blankPolicyOverride(flags['policy-override']))
|
|
62
|
+
this.error(BLANK_POLICY_OVERRIDE);
|
|
51
63
|
const { profile } = this.resolveProfile(flags);
|
|
52
64
|
// Get workspace ID from flag or profile
|
|
53
65
|
const workspaceId = flags.workspace || profile.workspace;
|
|
@@ -66,16 +78,23 @@ Branch 'v1' is now live
|
|
|
66
78
|
}
|
|
67
79
|
// Construct the API URL
|
|
68
80
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/branch/${encodeURIComponent(branchLabel)}/live`;
|
|
81
|
+
// A reason from someone with workspace:policy update sets the branch live past a blocking gate.
|
|
82
|
+
const overrideReason = flags['policy-override']?.trim();
|
|
69
83
|
// Set branch as live via the API
|
|
70
84
|
try {
|
|
71
85
|
const response = await this.verboseFetch(apiUrl, {
|
|
86
|
+
...(overrideReason ? { body: JSON.stringify({ override_reason: overrideReason }) } : {}),
|
|
72
87
|
headers: {
|
|
73
88
|
'accept': 'application/json',
|
|
74
89
|
'Authorization': `Bearer ${profile.access_token}`,
|
|
90
|
+
...(overrideReason ? { 'Content-Type': 'application/json' } : {}),
|
|
75
91
|
},
|
|
76
92
|
method: 'POST',
|
|
77
93
|
}, flags.verbose, profile.access_token);
|
|
78
94
|
if (!response.ok) {
|
|
95
|
+
const refused = await gateRefusal(response);
|
|
96
|
+
if (refused)
|
|
97
|
+
this.refuseSetLive(refused, flags, branchLabel);
|
|
79
98
|
const errorText = await response.text();
|
|
80
99
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
81
100
|
}
|
|
@@ -89,6 +108,8 @@ Branch 'v1' is now live
|
|
|
89
108
|
}
|
|
90
109
|
}
|
|
91
110
|
catch (error) {
|
|
111
|
+
if (this.refusedByPolicyGate)
|
|
112
|
+
throw error;
|
|
92
113
|
if (error instanceof Error) {
|
|
93
114
|
this.error(`Failed to set branch as live: ${error.message}`);
|
|
94
115
|
}
|
|
@@ -111,4 +132,21 @@ Branch 'v1' is now live
|
|
|
111
132
|
});
|
|
112
133
|
});
|
|
113
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* The set-live gate refused: the verdict on stdout (under `-o json`, `{set_live: false, message,
|
|
137
|
+
* policy_gate}`), then the platform's message and how to proceed, exiting 2.
|
|
138
|
+
*/
|
|
139
|
+
refuseSetLive(refused, flags, branchLabel) {
|
|
140
|
+
if (flags.output === 'json') {
|
|
141
|
+
this.log(JSON.stringify({ message: refused.message, policy_gate: refused.answer, set_live: false }, null, 2));
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
for (const line of liveGateLines(refused.answer, 'branch'))
|
|
145
|
+
this.log(line);
|
|
146
|
+
}
|
|
147
|
+
const workspace = flags.workspace ? ` -w ${flags.workspace}` : '';
|
|
148
|
+
const hint = gateOverrideHint(refused.answer, `xano branch set_live ${quoted(branchLabel)}${workspace} --policy-override "<why>"`);
|
|
149
|
+
this.refusedByPolicyGate = true;
|
|
150
|
+
this.error(`${refused.message}${hint}`, { exit: 2 });
|
|
151
|
+
}
|
|
114
152
|
}
|
|
@@ -4,6 +4,7 @@ import { resolve } from 'node:path';
|
|
|
4
4
|
import BaseCommand from '../../../base-command.js';
|
|
5
5
|
import { isAwaited, waitForMicroservices, } from '../../../utils/microservice-wait.js';
|
|
6
6
|
import { executePush } from '../../../utils/multidoc-push.js';
|
|
7
|
+
import { policiesSkippedNotice } from '../../../utils/policy/feedback.js';
|
|
7
8
|
/** Status glyph for one microservice row in the `--wait` progress block. */
|
|
8
9
|
const icon = (e) => {
|
|
9
10
|
if (!isAwaited(e))
|
|
@@ -168,13 +169,17 @@ Skip preview and push immediately
|
|
|
168
169
|
truncate: flags.truncate,
|
|
169
170
|
verbose: flags.verbose,
|
|
170
171
|
};
|
|
171
|
-
await executePush({
|
|
172
|
+
const result = await executePush({
|
|
172
173
|
accessToken: profile.access_token,
|
|
173
174
|
branch: '',
|
|
174
175
|
command: this,
|
|
175
176
|
inputDir,
|
|
176
177
|
verboseFetch: this.verboseFetch.bind(this),
|
|
177
178
|
}, target, pushFlags);
|
|
179
|
+
// When the platform leaves policy files out, it says so once.
|
|
180
|
+
const skipped = policiesSkippedNotice(result.response ?? result.preview);
|
|
181
|
+
if (skipped)
|
|
182
|
+
this.warn(skipped);
|
|
178
183
|
// --wait: after a real push, poll the tenant's live microservice status until
|
|
179
184
|
// every auto-deployed microservice (tenant_deploy="auto") is ready or the
|
|
180
185
|
// wait times out. Skipped on --dry-run (nothing was deployed).
|
|
@@ -7,13 +7,28 @@ export default class FunctionCreate extends BaseCommand {
|
|
|
7
7
|
edit: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
8
|
file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
9
|
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
'policy-override': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
11
|
stdin: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
12
|
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
13
|
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
14
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
15
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
16
|
};
|
|
17
|
+
/** Set once the publish policy gate refuses, the one failure that exits 2. */
|
|
18
|
+
private refusedByPolicyGate;
|
|
19
|
+
/** Exit 2 is the publish gate's refusal; every other failure, flag errors included, exits 1. */
|
|
20
|
+
protected catch(error: Error & {
|
|
21
|
+
oclif?: {
|
|
22
|
+
exit?: number;
|
|
23
|
+
};
|
|
24
|
+
}): Promise<void>;
|
|
16
25
|
run(): Promise<void>;
|
|
17
26
|
private editFile;
|
|
18
27
|
private readStdin;
|
|
28
|
+
/**
|
|
29
|
+
* The live-branch publish gate refused the new function: the verdict on stdout (under `-o json`,
|
|
30
|
+
* `{created: false, message, policy_gate}`), then the platform's message and how to proceed,
|
|
31
|
+
* exiting 2.
|
|
32
|
+
*/
|
|
33
|
+
private refusePublish;
|
|
19
34
|
}
|
|
@@ -4,6 +4,7 @@ import * as fs from 'node:fs';
|
|
|
4
4
|
import * as os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import BaseCommand from '../../../base-command.js';
|
|
7
|
+
import { BLANK_POLICY_OVERRIDE, blankPolicyOverride, gateRefusal, publishRefusal, quoted } from '../../../utils/policy/gate.js';
|
|
7
8
|
export default class FunctionCreate extends BaseCommand {
|
|
8
9
|
static args = {};
|
|
9
10
|
static description = 'Create a new function in a workspace';
|
|
@@ -59,6 +60,7 @@ Name: my_function
|
|
|
59
60
|
options: ['summary', 'json'],
|
|
60
61
|
required: false,
|
|
61
62
|
}),
|
|
63
|
+
'policy-override': Flags.string({ description: 'Create the function past a blocking live-branch publish policy gate with an audited reason (requires workspace:policy update)', hidden: true }),
|
|
62
64
|
stdin: Flags.boolean({
|
|
63
65
|
char: 's',
|
|
64
66
|
default: false,
|
|
@@ -72,8 +74,18 @@ Name: my_function
|
|
|
72
74
|
required: false,
|
|
73
75
|
}),
|
|
74
76
|
};
|
|
77
|
+
/** Set once the publish policy gate refuses, the one failure that exits 2. */
|
|
78
|
+
refusedByPolicyGate = false;
|
|
79
|
+
/** Exit 2 is the publish gate's refusal; every other failure, flag errors included, exits 1. */
|
|
80
|
+
async catch(error) {
|
|
81
|
+
if (this.refusedByPolicyGate)
|
|
82
|
+
return super.catch(error);
|
|
83
|
+
return this.catchAsOperational(error);
|
|
84
|
+
}
|
|
75
85
|
async run() {
|
|
76
86
|
const { flags } = await this.parse(FunctionCreate);
|
|
87
|
+
if (blankPolicyOverride(flags['policy-override']))
|
|
88
|
+
this.error(BLANK_POLICY_OVERRIDE);
|
|
77
89
|
const { profile, profileName } = this.resolveProfile(flags);
|
|
78
90
|
// Determine workspace_id from flag or profile
|
|
79
91
|
let workspaceId;
|
|
@@ -133,6 +145,10 @@ Name: my_function
|
|
|
133
145
|
const queryParams = new URLSearchParams({
|
|
134
146
|
include_xanoscript: 'false',
|
|
135
147
|
});
|
|
148
|
+
// A reason from someone with workspace:policy update saves past a blocking publish gate.
|
|
149
|
+
const overrideReason = flags['policy-override']?.trim();
|
|
150
|
+
if (overrideReason)
|
|
151
|
+
queryParams.set('override_reason', overrideReason);
|
|
136
152
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/function?${queryParams.toString()}`;
|
|
137
153
|
// Create function via API
|
|
138
154
|
try {
|
|
@@ -146,6 +162,9 @@ Name: my_function
|
|
|
146
162
|
method: 'POST',
|
|
147
163
|
}, flags.verbose, profile.access_token);
|
|
148
164
|
if (!response.ok) {
|
|
165
|
+
const refused = await gateRefusal(response);
|
|
166
|
+
if (refused)
|
|
167
|
+
this.refusePublish(refused, flags);
|
|
149
168
|
const errorText = await response.text();
|
|
150
169
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
151
170
|
}
|
|
@@ -166,6 +185,8 @@ Name: my_function
|
|
|
166
185
|
}
|
|
167
186
|
}
|
|
168
187
|
catch (error) {
|
|
188
|
+
if (this.refusedByPolicyGate)
|
|
189
|
+
throw error;
|
|
169
190
|
if (error instanceof Error) {
|
|
170
191
|
this.error(`Failed to create function: ${error.message}`);
|
|
171
192
|
}
|
|
@@ -239,4 +260,22 @@ Name: my_function
|
|
|
239
260
|
process.stdin.resume();
|
|
240
261
|
});
|
|
241
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* The live-branch publish gate refused the new function: the verdict on stdout (under `-o json`,
|
|
265
|
+
* `{created: false, message, policy_gate}`), then the platform's message and how to proceed,
|
|
266
|
+
* exiting 2.
|
|
267
|
+
*/
|
|
268
|
+
refusePublish(refused, flags) {
|
|
269
|
+
const workspace = flags.workspace ? ` -w ${quoted(flags.workspace)}` : '';
|
|
270
|
+
const source = flags.file ? ` -f ${quoted(flags.file)}` : ' --stdin';
|
|
271
|
+
const { lines, message } = publishRefusal(refused, {
|
|
272
|
+
json: flags.output === 'json',
|
|
273
|
+
outcome: 'created',
|
|
274
|
+
rerun: `xano function create${workspace}${source} --policy-override "<why>"`,
|
|
275
|
+
});
|
|
276
|
+
for (const line of lines)
|
|
277
|
+
this.log(line);
|
|
278
|
+
this.refusedByPolicyGate = true;
|
|
279
|
+
this.error(message, { exit: 2 });
|
|
280
|
+
}
|
|
242
281
|
}
|
|
@@ -9,6 +9,7 @@ export default class FunctionEdit extends BaseCommand {
|
|
|
9
9
|
edit: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
10
|
file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
11
|
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
'policy-override': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
13
|
publish: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
14
|
stdin: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
15
|
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -16,10 +17,24 @@ export default class FunctionEdit extends BaseCommand {
|
|
|
16
17
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
18
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
19
|
};
|
|
20
|
+
/** Set once the publish policy gate refuses, the one failure that exits 2. */
|
|
21
|
+
private refusedByPolicyGate;
|
|
22
|
+
/** Exit 2 is the publish gate's refusal; every other failure, flag errors included, exits 1. */
|
|
23
|
+
protected catch(error: Error & {
|
|
24
|
+
oclif?: {
|
|
25
|
+
exit?: number;
|
|
26
|
+
};
|
|
27
|
+
}): Promise<void>;
|
|
19
28
|
run(): Promise<void>;
|
|
20
29
|
private editFile;
|
|
21
30
|
private editFunctionContent;
|
|
22
31
|
private fetchFunctionCode;
|
|
23
32
|
private promptForFunctionId;
|
|
24
33
|
private readStdin;
|
|
34
|
+
/**
|
|
35
|
+
* The live-branch publish gate refused the update: the verdict on stdout (under `-o json`,
|
|
36
|
+
* `{updated: false, message, policy_gate}`), then the platform's message and how to proceed,
|
|
37
|
+
* exiting 2.
|
|
38
|
+
*/
|
|
39
|
+
private refusePublish;
|
|
25
40
|
}
|