@xano/cli 1.2.0 → 1.3.1-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 +381 -4
- package/dist/base-command.d.ts +54 -36
- package/dist/base-command.js +246 -169
- package/dist/commands/auth/index.d.ts +1 -1
- package/dist/commands/auth/index.js +7 -7
- package/dist/commands/branch/create/index.d.ts +1 -1
- package/dist/commands/branch/create/index.js +1 -1
- package/dist/commands/branch/set_live/index.d.ts +22 -0
- package/dist/commands/branch/set_live/index.js +60 -1
- package/dist/commands/ephemeral/create/index.d.ts +18 -0
- package/dist/commands/ephemeral/create/index.js +103 -0
- package/dist/commands/ephemeral/delete/index.d.ts +18 -0
- package/dist/commands/ephemeral/delete/index.js +99 -0
- package/dist/commands/ephemeral/edit/index.d.ts +18 -0
- package/dist/commands/ephemeral/edit/index.js +109 -0
- package/dist/commands/ephemeral/get/index.d.ts +21 -0
- package/dist/commands/ephemeral/get/index.js +125 -0
- package/dist/commands/ephemeral/impersonate/index.d.ts +20 -0
- package/dist/commands/ephemeral/impersonate/index.js +120 -0
- package/dist/commands/ephemeral/list/index.d.ts +19 -0
- package/dist/commands/ephemeral/list/index.js +123 -0
- package/dist/commands/ephemeral/pull/index.d.ts +19 -0
- package/dist/commands/ephemeral/pull/index.js +163 -0
- package/dist/commands/ephemeral/push/index.d.ts +35 -0
- package/dist/commands/ephemeral/push/index.js +228 -0
- package/dist/commands/ephemeral/static_host/build/delete/index.d.ts +20 -0
- package/dist/commands/ephemeral/static_host/build/delete/index.js +121 -0
- package/dist/commands/ephemeral/static_host/build/get/index.d.ts +18 -0
- package/dist/commands/ephemeral/static_host/build/get/index.js +137 -0
- package/dist/commands/ephemeral/static_host/build/list/index.d.ts +18 -0
- package/dist/commands/ephemeral/static_host/build/list/index.js +137 -0
- package/dist/commands/ephemeral/static_host/build/pull/index.d.ts +53 -0
- package/dist/commands/ephemeral/static_host/build/pull/index.js +308 -0
- package/dist/commands/ephemeral/static_host/build/push/index.d.ts +24 -0
- package/dist/commands/ephemeral/static_host/build/push/index.js +250 -0
- package/dist/commands/ephemeral/static_host/create/index.d.ts +18 -0
- package/dist/commands/ephemeral/static_host/create/index.js +92 -0
- package/dist/commands/ephemeral/static_host/deploy/index.d.ts +19 -0
- package/dist/commands/ephemeral/static_host/deploy/index.js +112 -0
- package/dist/commands/ephemeral/static_host/edit/index.d.ts +24 -0
- package/dist/commands/ephemeral/static_host/edit/index.js +158 -0
- package/dist/commands/ephemeral/static_host/get/index.d.ts +19 -0
- package/dist/commands/ephemeral/static_host/get/index.js +101 -0
- package/dist/commands/ephemeral/static_host/list/index.d.ts +17 -0
- package/dist/commands/ephemeral/static_host/list/index.js +132 -0
- package/dist/commands/flatten/index.d.ts +34 -0
- package/dist/commands/flatten/index.js +140 -0
- package/dist/commands/function/create/index.d.ts +15 -0
- package/dist/commands/function/create/index.js +41 -1
- package/dist/commands/function/edit/index.d.ts +15 -0
- package/dist/commands/function/edit/index.js +66 -32
- package/dist/commands/function/get/index.js +2 -3
- package/dist/commands/function/run/index.d.ts +1 -0
- package/dist/commands/function/run/index.js +19 -6
- package/dist/commands/policy/catalogue/index.d.ts +15 -0
- package/dist/commands/policy/catalogue/index.js +29 -0
- package/dist/commands/policy/create/index.d.ts +18 -0
- package/dist/commands/policy/create/index.js +39 -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/profile/create/index.d.ts +1 -1
- package/dist/commands/profile/create/index.js +7 -7
- package/dist/commands/profile/me/index.js +1 -1
- package/dist/commands/profile/use/index.js +0 -4
- package/dist/commands/profile/wizard/index.d.ts +1 -1
- package/dist/commands/profile/wizard/index.js +12 -10
- package/dist/commands/profile/workspace/set/index.js +1 -1
- package/dist/commands/release/create/index.js +7 -0
- package/dist/commands/release/deploy/index.d.ts +28 -0
- package/dist/commands/release/deploy/index.js +90 -6
- package/dist/commands/release/edit/index.js +2 -2
- package/dist/commands/release/export/index.js +1 -2
- package/dist/commands/release/import/index.js +6 -2
- package/dist/commands/release/list/index.d.ts +7 -0
- package/dist/commands/release/list/index.js +54 -10
- package/dist/commands/release/pull/index.d.ts +0 -6
- package/dist/commands/release/pull/index.js +13 -99
- package/dist/commands/release/push/index.js +21 -3
- package/dist/commands/sandbox/env/get_all/index.js +1 -1
- package/dist/commands/sandbox/env/set_all/index.js +1 -1
- package/dist/commands/sandbox/get/index.js +1 -1
- package/dist/commands/sandbox/license/get/index.js +1 -1
- package/dist/commands/sandbox/license/set/index.js +1 -1
- package/dist/commands/sandbox/pull/index.d.ts +0 -1
- package/dist/commands/sandbox/pull/index.js +13 -81
- package/dist/commands/sandbox/push/index.d.ts +1 -1
- package/dist/commands/sandbox/push/index.js +11 -6
- package/dist/commands/skills/pull/index.d.ts +30 -0
- package/dist/commands/skills/pull/index.js +118 -0
- package/dist/commands/static_host/build/create/index.d.ts +1 -1
- package/dist/commands/static_host/build/create/index.js +2 -2
- package/dist/commands/static_host/build/pull/index.js +1 -1
- package/dist/commands/static_host/build/push/index.js +1 -1
- package/dist/commands/static_host/edit/index.js +1 -1
- package/dist/commands/tenant/backup/export/index.js +1 -2
- package/dist/commands/tenant/backup/import/index.js +1 -1
- package/dist/commands/tenant/cluster/create/index.js +1 -1
- package/dist/commands/tenant/cluster/license/get/index.js +1 -1
- package/dist/commands/tenant/cluster/license/set/index.js +1 -1
- package/dist/commands/tenant/create/index.d.ts +4 -1
- package/dist/commands/tenant/create/index.js +26 -5
- package/dist/commands/tenant/deploy_platform/index.js +1 -1
- package/dist/commands/tenant/deploy_release/index.d.ts +40 -0
- package/dist/commands/tenant/deploy_release/index.js +186 -4
- package/dist/commands/tenant/edit/index.d.ts +2 -0
- package/dist/commands/tenant/edit/index.js +46 -11
- package/dist/commands/tenant/env/get_all/index.js +1 -1
- package/dist/commands/tenant/env/set_all/index.js +1 -1
- package/dist/commands/tenant/impersonate/index.d.ts +1 -1
- package/dist/commands/tenant/impersonate/index.js +14 -14
- package/dist/commands/tenant/license/get/index.js +1 -1
- package/dist/commands/tenant/license/set/index.js +1 -1
- package/dist/commands/tenant/pull/index.d.ts +0 -6
- package/dist/commands/tenant/pull/index.js +14 -100
- 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 +116 -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/git/pull/index.d.ts +4 -4
- package/dist/commands/workspace/git/pull/index.js +158 -97
- package/dist/commands/workspace/pull/index.d.ts +9 -5
- package/dist/commands/workspace/pull/index.js +83 -106
- package/dist/commands/workspace/push/index.d.ts +19 -0
- package/dist/commands/workspace/push/index.js +124 -5
- package/dist/help.js +4 -2
- package/dist/policy-command.d.ts +90 -0
- package/dist/policy-command.js +94 -0
- package/dist/update-check.d.ts +1 -1
- package/dist/update-check.js +1 -1
- package/dist/utils/document-parser.d.ts +126 -0
- package/dist/utils/document-parser.js +332 -4
- package/dist/utils/flatten.d.ts +36 -0
- package/dist/utils/flatten.js +68 -0
- package/dist/utils/knowledge-sync.d.ts +1 -0
- package/dist/utils/knowledge-sync.js +11 -6
- package/dist/utils/microservice-wait.d.ts +53 -0
- package/dist/utils/microservice-wait.js +84 -0
- package/dist/utils/multidoc-push.d.ts +143 -2
- package/dist/utils/multidoc-push.js +479 -40
- package/dist/utils/paging.d.ts +2 -1
- package/dist/utils/paging.js +0 -1
- package/dist/utils/policy/catalogue.d.ts +7 -0
- package/dist/utils/policy/catalogue.js +72 -0
- package/dist/utils/policy/errors.d.ts +12 -0
- package/dist/utils/policy/errors.js +61 -0
- package/dist/utils/policy/feedback.d.ts +68 -0
- package/dist/utils/policy/feedback.js +165 -0
- package/dist/utils/policy/findings.d.ts +23 -0
- package/dist/utils/policy/findings.js +65 -0
- package/dist/utils/policy/gate.d.ts +161 -0
- package/dist/utils/policy/gate.js +201 -0
- package/dist/utils/policy/goal.d.ts +46 -0
- package/dist/utils/policy/goal.js +95 -0
- package/dist/utils/policy/permission.d.ts +23 -0
- package/dist/utils/policy/permission.js +84 -0
- package/dist/utils/policy/push-gate.d.ts +5 -0
- package/dist/utils/policy/push-gate.js +29 -0
- package/dist/utils/policy/release.d.ts +85 -0
- package/dist/utils/policy/release.js +115 -0
- package/dist/utils/policy/request.d.ts +55 -0
- package/dist/utils/policy/request.js +115 -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 +47 -0
- package/dist/utils/policy/status.js +117 -0
- package/dist/utils/policy/types.d.ts +185 -0
- package/dist/utils/policy/types.js +1 -0
- package/dist/utils/reference-checker.js +14 -3
- package/oclif.manifest.json +8538 -3445
- package/package.json +16 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import snakeCase from 'lodash.snakecase';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
4
|
+
import { placeDocuments, splitMultidoc } from './document-parser.js';
|
|
5
|
+
/**
|
|
6
|
+
* Split ONE multi-document `.xs` bundle (documents joined by `---`) into the
|
|
7
|
+
* standard per-document tree — the exact layout `pull` produces — using the
|
|
8
|
+
* shared `placeDocuments` placement so the two never drift.
|
|
9
|
+
*
|
|
10
|
+
* Filesystem-touching, but self-contained: it reads the bundle, resolves every
|
|
11
|
+
* document's destination, checks for collisions, writes, and (unless
|
|
12
|
+
* `keepSource`) removes the original. Throws on a destination collision when
|
|
13
|
+
* `force` is not set, and never deletes the source when a written document
|
|
14
|
+
* landed on the source's own path.
|
|
15
|
+
*
|
|
16
|
+
* Shared by the `flatten` command and the push-time prompt, so a user who
|
|
17
|
+
* confirms "flatten now?" gets byte-identical output to running `xano flatten`.
|
|
18
|
+
*/
|
|
19
|
+
export function flattenBundleFile(bundleFile, opts = {}) {
|
|
20
|
+
const { dryRun = false, force = false, keepSource = false, log } = opts;
|
|
21
|
+
const content = fs.readFileSync(bundleFile, 'utf8').trim();
|
|
22
|
+
const documents = splitMultidoc(content);
|
|
23
|
+
if (documents.length === 0)
|
|
24
|
+
return { removedSource: false, skipped: 'empty', written: [] };
|
|
25
|
+
if (documents.length === 1)
|
|
26
|
+
return { removedSource: false, skipped: 'single', written: [] };
|
|
27
|
+
const outputDir = opts.outputDir ? resolve(opts.outputDir) : dirname(bundleFile);
|
|
28
|
+
const placed = placeDocuments(documents, { join, relative, snakeCase });
|
|
29
|
+
// Pre-check destination collisions (unless --force / dry-run). A written file
|
|
30
|
+
// landing on the source's own path is not a clash — it replaces the bundle.
|
|
31
|
+
if (!dryRun && !force) {
|
|
32
|
+
const clashes = placed
|
|
33
|
+
.map((p) => join(outputDir, p.relPath))
|
|
34
|
+
.filter((abs) => fs.existsSync(abs) && resolve(abs) !== resolve(bundleFile));
|
|
35
|
+
if (clashes.length > 0) {
|
|
36
|
+
throw new Error(`Destination file(s) already exist (use --force to overwrite):\n` +
|
|
37
|
+
clashes.map((c) => ` ${relative(process.cwd(), c) || c}`).join('\n'));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const written = [];
|
|
41
|
+
for (const p of placed) {
|
|
42
|
+
const abs = join(outputDir, p.relPath);
|
|
43
|
+
if (log)
|
|
44
|
+
log(` WRITE ${relative(process.cwd(), abs) || p.relPath}`);
|
|
45
|
+
if (!dryRun) {
|
|
46
|
+
fs.mkdirSync(dirname(abs), { recursive: true });
|
|
47
|
+
fs.writeFileSync(abs, p.content.endsWith('\n') ? p.content : `${p.content}\n`, 'utf8');
|
|
48
|
+
}
|
|
49
|
+
written.push(abs);
|
|
50
|
+
}
|
|
51
|
+
// Remove the original bundle unless asked to keep it, and never when a split
|
|
52
|
+
// document was written over the source's own path.
|
|
53
|
+
let removedSource = false;
|
|
54
|
+
if (!dryRun && !keepSource) {
|
|
55
|
+
const wroteOverSource = placed.some((p) => resolve(join(outputDir, p.relPath)) === resolve(bundleFile));
|
|
56
|
+
if (wroteOverSource) {
|
|
57
|
+
if (log)
|
|
58
|
+
log(` (source ${basename(bundleFile)} was overwritten by a split document; not deleting)`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
fs.rmSync(bundleFile);
|
|
62
|
+
removedSource = true;
|
|
63
|
+
if (log)
|
|
64
|
+
log(` removed ${relative(process.cwd(), bundleFile) || bundleFile}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { removedSource, written };
|
|
68
|
+
}
|
|
@@ -17,6 +17,7 @@ export interface KnowledgeObject {
|
|
|
17
17
|
mode?: string;
|
|
18
18
|
name: string;
|
|
19
19
|
scope?: string;
|
|
20
|
+
tag?: string[];
|
|
20
21
|
}
|
|
21
22
|
/** A locally-collected object that also remembers its primary file on disk (for GUID writeback). */
|
|
22
23
|
export interface LocalKnowledgeObject extends KnowledgeObject {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable camelcase -- knowledge_type / dry_run / guid_map are external Metadata API field names */
|
|
2
1
|
import * as yaml from 'js-yaml';
|
|
3
2
|
import snakeCase from 'lodash.snakecase';
|
|
4
3
|
import { minimatch } from 'minimatch';
|
|
@@ -12,7 +11,7 @@ import { dirname, join, relative, sep } from 'node:path';
|
|
|
12
11
|
// let the server dictate file paths.
|
|
13
12
|
export const KNOWLEDGE_DIR = 'knowledge';
|
|
14
13
|
// ── Frontmatter ──────────────────────────────────────────────────────────────
|
|
15
|
-
const FRONTMATTER_ORDER = ['name', 'description', 'knowledge_type', '
|
|
14
|
+
const FRONTMATTER_ORDER = ['name', 'description', 'knowledge_type', 'inclusion', 'tags', 'guid'];
|
|
16
15
|
/**
|
|
17
16
|
* Build a primary `.md` file body: YAML frontmatter (built from the object's
|
|
18
17
|
* structured fields) followed by the markdown content.
|
|
@@ -23,12 +22,10 @@ export function buildPrimaryContent(obj) {
|
|
|
23
22
|
if (obj.description !== undefined)
|
|
24
23
|
meta.description = obj.description;
|
|
25
24
|
meta.knowledge_type = obj.knowledge_type;
|
|
26
|
-
if (obj.scope !== undefined)
|
|
27
|
-
meta.scope = obj.scope;
|
|
28
25
|
if (obj.mode !== undefined)
|
|
29
26
|
meta.inclusion = modeToInclusion(obj.mode);
|
|
30
|
-
if (obj.
|
|
31
|
-
meta.
|
|
27
|
+
if (obj.tag !== undefined && obj.tag.length > 0)
|
|
28
|
+
meta.tags = obj.tag;
|
|
32
29
|
if (obj.guid !== undefined)
|
|
33
30
|
meta.guid = obj.guid;
|
|
34
31
|
// Emit keys in a stable, human-friendly order.
|
|
@@ -107,6 +104,10 @@ const MODE_TO_INCLUSION = {
|
|
|
107
104
|
function modeToInclusion(mode) {
|
|
108
105
|
return MODE_TO_INCLUSION[mode] ?? mode;
|
|
109
106
|
}
|
|
107
|
+
/** Parse the on-disk `tags:` frontmatter value into a string array, or `undefined` if absent/malformed. */
|
|
108
|
+
function parseTags(tags) {
|
|
109
|
+
return Array.isArray(tags) && tags.every((t) => typeof t === 'string') ? tags : undefined;
|
|
110
|
+
}
|
|
110
111
|
// ── Path mapping (CLI-owned layout) ────────────────────────────────────────────
|
|
111
112
|
/**
|
|
112
113
|
* Allocate a unique snake_case folder/file segment per call, suffixing
|
|
@@ -246,6 +247,9 @@ export function collectKnowledgeObjects(inputDir, include, exclude) {
|
|
|
246
247
|
};
|
|
247
248
|
if (typeof meta.guid === 'string')
|
|
248
249
|
obj.guid = meta.guid;
|
|
250
|
+
const tags = parseTags(meta.tags);
|
|
251
|
+
if (tags)
|
|
252
|
+
obj.tag = tags;
|
|
249
253
|
if (knowledgeType === 'skill') {
|
|
250
254
|
const skillFolder = posixPath.slice('skills/'.length, posixPath.indexOf('/SKILL.md'));
|
|
251
255
|
const refPrefix = `skills/${skillFolder}/references/`;
|
|
@@ -345,6 +349,7 @@ function canonical(obj) {
|
|
|
345
349
|
files,
|
|
346
350
|
mode: obj.mode ?? '',
|
|
347
351
|
scope: obj.scope ?? '',
|
|
352
|
+
tag: [...(obj.tag ?? [])].sort(),
|
|
348
353
|
});
|
|
349
354
|
}
|
|
350
355
|
/**
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Poll a tenant's microservice deployment status until every auto-deployed
|
|
3
|
+
* microservice settles (ok/error) or a timeout elapses. Backs `ephemeral push
|
|
4
|
+
* --wait`: after a multidoc push auto-deploys microservices (tenant_deploy=
|
|
5
|
+
* "auto"), the CLI waits here for them to become Ready and reports the outcome.
|
|
6
|
+
*
|
|
7
|
+
* The backend route (GET .../tenant/{name}/microservice, statement
|
|
8
|
+
* mvp:microservice_status_all) returns one entry per microservice with a live
|
|
9
|
+
* k8s readiness read. `manual`/`disabled` microservices are reported but not
|
|
10
|
+
* waited on (they were never deployed by the push).
|
|
11
|
+
*/
|
|
12
|
+
/** One microservice's status as returned by the tenant status route. */
|
|
13
|
+
export interface MicroserviceStatusEntry {
|
|
14
|
+
desired?: number;
|
|
15
|
+
detail?: string;
|
|
16
|
+
microservice_id: number;
|
|
17
|
+
name: string;
|
|
18
|
+
ready?: number;
|
|
19
|
+
status: string;
|
|
20
|
+
tenant_deploy?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface WaitOptions {
|
|
23
|
+
/** Bearer token for the authed status GET. */
|
|
24
|
+
accessToken: string;
|
|
25
|
+
/** Called once per poll with the latest entries, for progress rendering. */
|
|
26
|
+
onPoll?: (entries: MicroserviceStatusEntry[], elapsedMs: number) => void;
|
|
27
|
+
/** Poll cadence in ms (default 2000). */
|
|
28
|
+
pollIntervalMs?: number;
|
|
29
|
+
/** Absolute URL of the tenant microservice status route. */
|
|
30
|
+
statusUrl: string;
|
|
31
|
+
/** Overall wait budget in ms (default 300000 = 300s). */
|
|
32
|
+
timeoutMs?: number;
|
|
33
|
+
/** Verbose request logging passthrough. */
|
|
34
|
+
verbose?: boolean;
|
|
35
|
+
/** Authed fetch (command.verboseFetch bound), so TLS/dispatcher settings apply. */
|
|
36
|
+
verboseFetch: (url: string, options: RequestInit, verbose: boolean, authToken?: string) => Promise<Response>;
|
|
37
|
+
}
|
|
38
|
+
export interface WaitResult {
|
|
39
|
+
/** The final status entries observed. */
|
|
40
|
+
entries: MicroserviceStatusEntry[];
|
|
41
|
+
/** True if any awaited (auto) microservice ended in "error". */
|
|
42
|
+
hadError: boolean;
|
|
43
|
+
/** True if the wait budget elapsed before all awaited microservices settled. */
|
|
44
|
+
timedOut: boolean;
|
|
45
|
+
}
|
|
46
|
+
/** An awaited microservice is one that was auto-deployed and is not skipped/disabled. */
|
|
47
|
+
export declare function isAwaited(entry: MicroserviceStatusEntry): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Poll until all awaited microservices settle or the timeout elapses. Transient
|
|
50
|
+
* poll failures (network blips, a brief 5xx while a pod restarts) are tolerated —
|
|
51
|
+
* they don't abort the wait; only the overall timeout does.
|
|
52
|
+
*/
|
|
53
|
+
export declare function waitForMicroservices(opts: WaitOptions): Promise<WaitResult>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Poll a tenant's microservice deployment status until every auto-deployed
|
|
3
|
+
* microservice settles (ok/error) or a timeout elapses. Backs `ephemeral push
|
|
4
|
+
* --wait`: after a multidoc push auto-deploys microservices (tenant_deploy=
|
|
5
|
+
* "auto"), the CLI waits here for them to become Ready and reports the outcome.
|
|
6
|
+
*
|
|
7
|
+
* The backend route (GET .../tenant/{name}/microservice, statement
|
|
8
|
+
* mvp:microservice_status_all) returns one entry per microservice with a live
|
|
9
|
+
* k8s readiness read. `manual`/`disabled` microservices are reported but not
|
|
10
|
+
* waited on (they were never deployed by the push).
|
|
11
|
+
*/
|
|
12
|
+
/** Statuses that mean an awaited microservice has settled (no longer in flight). */
|
|
13
|
+
const SETTLED = new Set(['error', 'ok']);
|
|
14
|
+
/** Statuses that mean we don't wait on this microservice at all. */
|
|
15
|
+
const NOT_AWAITED = new Set(['disabled', 'skipped']);
|
|
16
|
+
const sleep = (ms) => new Promise((r) => {
|
|
17
|
+
setTimeout(r, ms);
|
|
18
|
+
});
|
|
19
|
+
/** An awaited microservice is one that was auto-deployed and is not skipped/disabled. */
|
|
20
|
+
export function isAwaited(entry) {
|
|
21
|
+
return !NOT_AWAITED.has(entry.status) && (entry.tenant_deploy ?? 'auto') !== 'manual';
|
|
22
|
+
}
|
|
23
|
+
/** True once every awaited microservice has settled (ok/error). */
|
|
24
|
+
function allSettled(entries) {
|
|
25
|
+
const awaited = entries.filter((e) => isAwaited(e));
|
|
26
|
+
// No auto microservices at all → nothing to wait for.
|
|
27
|
+
if (awaited.length === 0)
|
|
28
|
+
return true;
|
|
29
|
+
return awaited.every((e) => SETTLED.has(e.status));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Fetch the status route once. Throws on a non-2xx so the caller can decide
|
|
33
|
+
* whether a transient failure is fatal (it isn't — we keep polling).
|
|
34
|
+
*/
|
|
35
|
+
async function fetchStatus(opts) {
|
|
36
|
+
const res = await opts.verboseFetch(opts.statusUrl, { headers: { Authorization: `Bearer ${opts.accessToken}` }, method: 'GET' }, opts.verbose ?? false, opts.accessToken);
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
throw new Error(`status ${res.status} ${res.statusText}`);
|
|
39
|
+
}
|
|
40
|
+
const body = (await res.json());
|
|
41
|
+
if (!Array.isArray(body)) {
|
|
42
|
+
throw new TypeError('unexpected status response shape');
|
|
43
|
+
}
|
|
44
|
+
return body;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Poll until all awaited microservices settle or the timeout elapses. Transient
|
|
48
|
+
* poll failures (network blips, a brief 5xx while a pod restarts) are tolerated —
|
|
49
|
+
* they don't abort the wait; only the overall timeout does.
|
|
50
|
+
*/
|
|
51
|
+
export async function waitForMicroservices(opts) {
|
|
52
|
+
const pollIntervalMs = opts.pollIntervalMs ?? 2000;
|
|
53
|
+
const timeoutMs = opts.timeoutMs ?? 300_000;
|
|
54
|
+
const start = Date.now();
|
|
55
|
+
let entries = [];
|
|
56
|
+
// Poll loop. We always do at least one fetch so `--wait` reports even when the
|
|
57
|
+
// deployment already settled between the push returning and the first poll.
|
|
58
|
+
for (;;) {
|
|
59
|
+
const elapsed = Date.now() - start;
|
|
60
|
+
try {
|
|
61
|
+
// eslint-disable-next-line no-await-in-loop -- polling is sequential by design
|
|
62
|
+
entries = await fetchStatus(opts);
|
|
63
|
+
opts.onPoll?.(entries, elapsed);
|
|
64
|
+
if (allSettled(entries)) {
|
|
65
|
+
return { entries, hadError: entries.some((e) => isAwaited(e) && e.status === 'error'), timedOut: false };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// A transient status read failure is non-fatal — keep polling until the
|
|
70
|
+
// overall timeout. There is deliberately no consecutive-failure cap: we
|
|
71
|
+
// don't surface each blip, and the timeout is the only failure signal.
|
|
72
|
+
}
|
|
73
|
+
if (Date.now() - start + pollIntervalMs > timeoutMs) {
|
|
74
|
+
// Out of budget. Report whatever we last saw as timed out.
|
|
75
|
+
return {
|
|
76
|
+
entries,
|
|
77
|
+
hadError: entries.some((e) => isAwaited(e) && e.status === 'error'),
|
|
78
|
+
timedOut: true,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// eslint-disable-next-line no-await-in-loop -- pacing the poll loop is the point
|
|
82
|
+
await sleep(pollIntervalMs);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Command } from '@oclif/core';
|
|
1
|
+
import { Command, Errors } from '@oclif/core';
|
|
2
2
|
import { type BadIndex, type BadReference } from './reference-checker.js';
|
|
3
3
|
export interface PushFlags {
|
|
4
4
|
delete: boolean;
|
|
@@ -21,6 +21,11 @@ export interface PushTarget {
|
|
|
21
21
|
buildPushUrl: (queryParams: URLSearchParams) => string;
|
|
22
22
|
/** CLI version string */
|
|
23
23
|
cliVersion: string;
|
|
24
|
+
/**
|
|
25
|
+
* Guidance for a refusal this target recognises, given the HTTP status and the server's payload.
|
|
26
|
+
* A preview or import refused that way stops with the message and this guidance appended.
|
|
27
|
+
*/
|
|
28
|
+
explainRefusal?: (status: number, payload: unknown) => string | undefined;
|
|
24
29
|
/** Instance origin URL (e.g., "https://x123-abcd-1234.xano.io") */
|
|
25
30
|
instanceOrigin: string;
|
|
26
31
|
/** Human-readable label for log messages (e.g., "sandbox environment", "workspace 40") */
|
|
@@ -54,8 +59,56 @@ export interface PushContext {
|
|
|
54
59
|
inputDir: string;
|
|
55
60
|
/** Optional knowledge sync config. Only workspace push sets this. */
|
|
56
61
|
knowledge?: KnowledgeConfig;
|
|
62
|
+
/** Where progress and diagnostics go; defaults to the command's stdout. */
|
|
63
|
+
log?: (message: string) => void;
|
|
57
64
|
verboseFetch: (url: string, options: RequestInit, verbose: boolean, authToken?: string) => Promise<Response>;
|
|
58
65
|
}
|
|
66
|
+
export interface DryRunSummary {
|
|
67
|
+
created: number;
|
|
68
|
+
deleted: number;
|
|
69
|
+
truncated: number;
|
|
70
|
+
unchanged: number;
|
|
71
|
+
updated: number;
|
|
72
|
+
}
|
|
73
|
+
export interface DryRunOperation {
|
|
74
|
+
action: string;
|
|
75
|
+
details: string;
|
|
76
|
+
name: string;
|
|
77
|
+
reason?: string;
|
|
78
|
+
type: string;
|
|
79
|
+
}
|
|
80
|
+
export interface DryRunResult {
|
|
81
|
+
operations: DryRunOperation[];
|
|
82
|
+
/** True when the sandbox currently holds a different source workspace than the one being pushed. */
|
|
83
|
+
source_workspace_mismatch?: boolean;
|
|
84
|
+
summary: Record<string, DryRunSummary>;
|
|
85
|
+
workspace_name?: string;
|
|
86
|
+
}
|
|
87
|
+
/** What a push did, for the calling command to report. */
|
|
88
|
+
export interface PushResult {
|
|
89
|
+
knowledge: {
|
|
90
|
+
deleted: number;
|
|
91
|
+
imported: number;
|
|
92
|
+
};
|
|
93
|
+
preview: DryRunResult | null;
|
|
94
|
+
/** The multidoc import's response body; absent when no multidoc was imported. */
|
|
95
|
+
response?: Record<string, unknown>;
|
|
96
|
+
/** The documents the multidoc import carried. */
|
|
97
|
+
sent: Array<{
|
|
98
|
+
content: string;
|
|
99
|
+
filePath: string;
|
|
100
|
+
}>;
|
|
101
|
+
/** Why the push stopped before writing anything, when it did. */
|
|
102
|
+
stopped?: 'blocked' | 'cancelled' | 'dry-run' | 'no-changes';
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* A push that failed after its multidoc import landed. The import is not rolled back, so the error
|
|
106
|
+
* carries what it did for the calling command to report beside the failure.
|
|
107
|
+
*/
|
|
108
|
+
export declare class FailedAfterImportError extends Errors.CLIError {
|
|
109
|
+
readonly imported: PushResult;
|
|
110
|
+
constructor(message: string, imported: PushResult);
|
|
111
|
+
}
|
|
59
112
|
/**
|
|
60
113
|
* Filter document entries down to the ones the dry-run preview reports as changed,
|
|
61
114
|
* used to send only the changed documents during a partial (non-`--sync`) push.
|
|
@@ -89,7 +142,61 @@ export declare function collectFiles(dir: string): string[];
|
|
|
89
142
|
* Apply include/exclude glob filters to a file list. Logs filter results.
|
|
90
143
|
* Returns the filtered file list.
|
|
91
144
|
*/
|
|
145
|
+
/**
|
|
146
|
+
* Normalize a filter pattern into one minimatch can actually match against a
|
|
147
|
+
* repo-relative FILE path.
|
|
148
|
+
*
|
|
149
|
+
* minimatch has no notion of "this pattern names a directory", and the two
|
|
150
|
+
* spellings a person reaches for first both silently match NOTHING:
|
|
151
|
+
*
|
|
152
|
+
* "table/" a trailing slash is not directory syntax; it matches nothing
|
|
153
|
+
* "table" no slash, so `matchBase` compares it to the file's BASENAME,
|
|
154
|
+
* i.e. it asks "is this file called `table`", not "is it under
|
|
155
|
+
* `table/`"
|
|
156
|
+
*
|
|
157
|
+
* Silently matching nothing is the dangerous half of this: combined with
|
|
158
|
+
* `--delete`, a filter that fails open pushes more than intended, and a filter
|
|
159
|
+
* that succeeds deletes what it was meant to protect. So a pattern that names a
|
|
160
|
+
* real directory is expanded to `<dir>/**` rather than left to match nothing.
|
|
161
|
+
*/
|
|
162
|
+
export declare function normalizeFilterPattern(pattern: string, inputDir: string): string;
|
|
92
163
|
export declare function applyFilters(files: string[], inputDir: string, include: string[] | undefined, exclude: string[] | undefined, log: (msg: string) => void): string[];
|
|
164
|
+
/** A document the filter removed from the push, and therefore must protect. */
|
|
165
|
+
export interface FilteredOutDocument {
|
|
166
|
+
guid?: string;
|
|
167
|
+
key: string;
|
|
168
|
+
name: string;
|
|
169
|
+
relPath: string;
|
|
170
|
+
type: string;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Describe the documents a --include/--exclude filter removed.
|
|
174
|
+
*
|
|
175
|
+
* These are the objects the user asked NOT to touch, and they are exactly the
|
|
176
|
+
* ones a `--delete` sweep would remove: a filtered-out document never reaches
|
|
177
|
+
* the payload, so the server reads it as deleted from the tree. Their GUIDs are
|
|
178
|
+
* sent as `protect_guids` so the server spares them, and their keys are used to
|
|
179
|
+
* VERIFY the server actually did.
|
|
180
|
+
*/
|
|
181
|
+
export declare function describeFilteredOut(allFiles: string[], keptFiles: string[], inputDir: string): FilteredOutDocument[];
|
|
182
|
+
/**
|
|
183
|
+
* Delete operations that would remove something the filter excluded.
|
|
184
|
+
*
|
|
185
|
+
* This is the guard's actual test, and it is deliberately performed against the
|
|
186
|
+
* dry-run the SERVER returned rather than trusting that `protect_guids` was
|
|
187
|
+
* honoured: an older instance ignores the parameter entirely, and a document
|
|
188
|
+
* with no GUID cannot be protected at all. Either way the answer here is the
|
|
189
|
+
* truth about what the push is about to do.
|
|
190
|
+
*/
|
|
191
|
+
export declare function deletesHittingFilteredOut(operations: Array<{
|
|
192
|
+
action: string;
|
|
193
|
+
name: string;
|
|
194
|
+
type: string;
|
|
195
|
+
}>, filteredOut: FilteredOutDocument[]): Array<{
|
|
196
|
+
name: string;
|
|
197
|
+
relPath: string;
|
|
198
|
+
type: string;
|
|
199
|
+
}>;
|
|
93
200
|
/**
|
|
94
201
|
* Read .xs files into document entries, skipping empty files.
|
|
95
202
|
*/
|
|
@@ -97,6 +204,40 @@ export declare function readDocuments(files: string[]): Array<{
|
|
|
97
204
|
content: string;
|
|
98
205
|
filePath: string;
|
|
99
206
|
}>;
|
|
207
|
+
/**
|
|
208
|
+
* Return the entries whose file holds more than one document (a `---`
|
|
209
|
+
* separator). Used to refuse multi-doc files before the push pipeline's
|
|
210
|
+
* single-doc-per-file assumptions can drop or corrupt them.
|
|
211
|
+
*/
|
|
212
|
+
export declare function findMultiDocEntries(entries: Array<{
|
|
213
|
+
content: string;
|
|
214
|
+
filePath: string;
|
|
215
|
+
}>): Array<{
|
|
216
|
+
count: number;
|
|
217
|
+
filePath: string;
|
|
218
|
+
}>;
|
|
219
|
+
/**
|
|
220
|
+
* Return every `workspace` document in the push set, with the name it declares.
|
|
221
|
+
*
|
|
222
|
+
* A tree must carry at most ONE. The server applies the first workspace
|
|
223
|
+
* document it encounters to the workspace being pushed into and silently
|
|
224
|
+
* discards the rest, with no check that the document actually describes that
|
|
225
|
+
* workspace — so a second one renames the destination to a foreign name while
|
|
226
|
+
* leaving its content untouched, and which name wins flips with the contents of
|
|
227
|
+
* the pushed set.
|
|
228
|
+
*
|
|
229
|
+
* Trees accumulate a second one easily, because `pull` names the file after the
|
|
230
|
+
* workspace's own name (`workspace/{name}.xs`): pulling a different workspace
|
|
231
|
+
* into the same directory ADDS a file rather than overwriting, and renaming a
|
|
232
|
+
* workspace leaves the old-name file behind.
|
|
233
|
+
*/
|
|
234
|
+
export declare function findWorkspaceEntries(entries: Array<{
|
|
235
|
+
content: string;
|
|
236
|
+
filePath: string;
|
|
237
|
+
}>): Array<{
|
|
238
|
+
filePath: string;
|
|
239
|
+
name: string;
|
|
240
|
+
}>;
|
|
100
241
|
export declare function renderBadReferences(badRefs: BadReference[], log: (msg: string) => void): void;
|
|
101
242
|
export declare function renderBadIndexes(badIndexes: BadIndex[], log: (msg: string) => void): void;
|
|
102
243
|
export declare function confirm(message: string): Promise<boolean>;
|
|
@@ -104,7 +245,7 @@ export declare function confirm(message: string): Promise<boolean>;
|
|
|
104
245
|
* Execute a multidoc push with preview, validation, partial mode, and GUID sync.
|
|
105
246
|
* Shared by both sandbox:push and workspace:push commands.
|
|
106
247
|
*/
|
|
107
|
-
export declare function executePush(ctx: PushContext, target: PushTarget, flags: PushFlags): Promise<
|
|
248
|
+
export declare function executePush(ctx: PushContext, target: PushTarget, flags: PushFlags): Promise<PushResult>;
|
|
108
249
|
/**
|
|
109
250
|
* Turn a thrown fetch/network error into an actionable message.
|
|
110
251
|
*
|