@chatlychat/cli 0.1.3 → 0.1.5
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/dist/commands/ai.d.ts +4 -0
- package/dist/commands/ai.d.ts.map +1 -0
- package/dist/commands/ai.js +149 -0
- package/dist/commands/ai.js.map +1 -0
- package/dist/commands/common.d.ts +79 -0
- package/dist/commands/common.d.ts.map +1 -0
- package/dist/commands/common.js +194 -0
- package/dist/commands/common.js.map +1 -0
- package/dist/commands/config.d.ts +40 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +336 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/insights.d.ts +5 -0
- package/dist/commands/insights.d.ts.map +1 -0
- package/dist/commands/insights.js +277 -0
- package/dist/commands/insights.js.map +1 -0
- package/dist/commands/integrations.d.ts +15 -0
- package/dist/commands/integrations.d.ts.map +1 -0
- package/dist/commands/integrations.js +256 -0
- package/dist/commands/integrations.js.map +1 -0
- package/dist/commands/resources.d.ts +19 -7
- package/dist/commands/resources.d.ts.map +1 -1
- package/dist/commands/resources.js +100 -191
- package/dist/commands/resources.js.map +1 -1
- package/dist/commands/workspace-as-code.d.ts +27 -0
- package/dist/commands/workspace-as-code.d.ts.map +1 -0
- package/dist/commands/workspace-as-code.js +377 -0
- package/dist/commands/workspace-as-code.js.map +1 -0
- package/dist/help.d.ts +25 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +104 -8
- package/dist/help.js.map +1 -1
- package/dist/run.d.ts +17 -0
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +88 -41
- package/dist/run.js.map +1 -1
- package/dist/wac/__tests__/wac-fixtures.d.ts +20 -0
- package/dist/wac/__tests__/wac-fixtures.d.ts.map +1 -0
- package/dist/wac/__tests__/wac-fixtures.js +38 -0
- package/dist/wac/__tests__/wac-fixtures.js.map +1 -0
- package/dist/wac/apply.d.ts +113 -0
- package/dist/wac/apply.d.ts.map +1 -0
- package/dist/wac/apply.js +265 -0
- package/dist/wac/apply.js.map +1 -0
- package/dist/wac/diff.d.ts +137 -0
- package/dist/wac/diff.d.ts.map +1 -0
- package/dist/wac/diff.js +413 -0
- package/dist/wac/diff.js.map +1 -0
- package/dist/wac/document.d.ts +110 -0
- package/dist/wac/document.d.ts.map +1 -0
- package/dist/wac/document.js +429 -0
- package/dist/wac/document.js.map +1 -0
- package/dist/wac/render.d.ts +52 -0
- package/dist/wac/render.d.ts.map +1 -0
- package/dist/wac/render.js +156 -0
- package/dist/wac/render.js.map +1 -0
- package/dist/wac/resource-def.d.ts +116 -0
- package/dist/wac/resource-def.d.ts.map +1 -0
- package/dist/wac/resource-def.js +44 -0
- package/dist/wac/resource-def.js.map +1 -0
- package/dist/wac/resources.d.ts +104 -0
- package/dist/wac/resources.d.ts.map +1 -0
- package/dist/wac/resources.js +541 -0
- package/dist/wac/resources.js.map +1 -0
- package/dist/wac/values.d.ts +16 -0
- package/dist/wac/values.d.ts.map +1 -0
- package/dist/wac/values.js +53 -0
- package/dist/wac/values.js.map +1 -0
- package/package.json +3 -2
- package/dist/cli.spec.d.ts +0 -2
- package/dist/cli.spec.d.ts.map +0 -1
- package/dist/cli.spec.js +0 -226
- package/dist/cli.spec.js.map +0 -1
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chatly.json` — the document, and the only thing in this feature allowed to
|
|
3
|
+
* refuse you.
|
|
4
|
+
*
|
|
5
|
+
* ═══ WHY THERE IS NO zod HERE, AND WHAT TAKES ITS PLACE ══════════════════
|
|
6
|
+
*
|
|
7
|
+
* This repo validates with zod everywhere and this schema wants it. It is not
|
|
8
|
+
* here for one reason: `@chatlychat/cli` is a PUBLISHED package whose
|
|
9
|
+
* dependency list is exactly `@chatlychat/sdk-node`, zod is not reachable
|
|
10
|
+
* from `apps/cli` (it is in the workspace catalog, but no package here
|
|
11
|
+
* declares it, so it is not installed under `apps/cli/node_modules`), and
|
|
12
|
+
* CLAUDE.md §2 forbids adding a dependency without explicit approval. Adding
|
|
13
|
+
* one to a published CLI is a product decision about what customers install,
|
|
14
|
+
* not a tidying-up decision — the same reason `args.ts` is hand-written
|
|
15
|
+
* rather than `commander`.
|
|
16
|
+
*
|
|
17
|
+
* So the checks below are hand-written, and deliberately shaped so the swap
|
|
18
|
+
* is mechanical if that approval comes: one `parseDocument` entry point,
|
|
19
|
+
* issues collected as `{ path, message }` with a dotted path, and all of them
|
|
20
|
+
* reported at once rather than the first. A zod `.strict()` object and
|
|
21
|
+
* `ZodError.issues` is a drop-in for the body of this file.
|
|
22
|
+
*
|
|
23
|
+
* ═══ WHERE STRICTNESS APPLIES, AND WHERE IT DELIBERATELY DOES NOT ════════
|
|
24
|
+
*
|
|
25
|
+
* CLAUDE.md records the bug twice over: a key the reader has never heard of
|
|
26
|
+
* is STRIPPED, so the write answers 200 and did nothing, and `helpProfile` vs
|
|
27
|
+
* `profile` cost a complete feature that way. The cure is to refuse the key
|
|
28
|
+
* by name and say what the valid ones are. That is done for every part of the
|
|
29
|
+
* document whose vocabulary the CLI OWNS:
|
|
30
|
+
*
|
|
31
|
+
* - the top level (`version`, `workspace`, `resources`)
|
|
32
|
+
* - the resource-type keys inside `resources`
|
|
33
|
+
* - the per-row control keys the CLI invents (`bot:`, which is not an API
|
|
34
|
+
* field at all)
|
|
35
|
+
* - the natural key, which must be present and a non-empty string
|
|
36
|
+
* - any field the API RETURNS and no write path accepts (`unwritable` in
|
|
37
|
+
* `resources.ts`) — `triggers.brandId`, `brands.usage`,
|
|
38
|
+
* `macros.usageCount` and twenty more. These are the dangerous ones:
|
|
39
|
+
* they are spelled correctly, they come back on every read, and zod
|
|
40
|
+
* strips them on the way in.
|
|
41
|
+
*
|
|
42
|
+
* It is NOT done for the rest of a row's fields, and that is a decision
|
|
43
|
+
* rather than an omission. `common.ts`'s `bodyFrom` already states the
|
|
44
|
+
* position for every write on this CLI: "the body goes to the API exactly as
|
|
45
|
+
* written, so the schema in `apps/api` is the only spelling that matters…
|
|
46
|
+
* the CLI simply refuses to add a second renaming layer on top." A per-row
|
|
47
|
+
* allow-list here would be a second COPY of nine API schemas, in a package
|
|
48
|
+
* published on a different cadence, and it would reject a field the API
|
|
49
|
+
* shipped yesterday — a gate that fails on correct input is a gate somebody
|
|
50
|
+
* switches off.
|
|
51
|
+
*
|
|
52
|
+
* What replaces it is better evidence than a copied list: `plan` compares the
|
|
53
|
+
* fields a row declares against the fields the SERVER returns for rows of
|
|
54
|
+
* that type, and warns about one no live row has ever carried. That catches
|
|
55
|
+
* `helpProfle` from the server's own vocabulary, with nothing to keep in
|
|
56
|
+
* step. Its blind spot — a workspace with no rows of that type yet — is
|
|
57
|
+
* reported as such rather than passed off as a clean bill. See `diff.ts`.
|
|
58
|
+
*/
|
|
59
|
+
import { type ResourceDef } from './resources.js';
|
|
60
|
+
/** The file format's version. Bumped only for a change that breaks a reader. */
|
|
61
|
+
export declare const DOCUMENT_VERSION = 1;
|
|
62
|
+
export interface Issue {
|
|
63
|
+
/** Dotted, and it is the path IN THE FILE: `resources.bots[2].slug`. */
|
|
64
|
+
readonly path: string;
|
|
65
|
+
readonly message: string;
|
|
66
|
+
}
|
|
67
|
+
export interface DocumentRow {
|
|
68
|
+
/** The natural key's value, as written. */
|
|
69
|
+
readonly key: string;
|
|
70
|
+
/** Where in the file this row came from, for an error message. */
|
|
71
|
+
readonly path: string;
|
|
72
|
+
/** The row's API fields, with the CLI's own control keys removed. */
|
|
73
|
+
readonly body: Readonly<Record<string, unknown>>;
|
|
74
|
+
/** `{ botId: { type: 'bots', key: 'support' } }` — resolved by `diff.ts`. */
|
|
75
|
+
readonly refs: Readonly<Record<string, {
|
|
76
|
+
readonly type: string;
|
|
77
|
+
readonly key: string;
|
|
78
|
+
}>>;
|
|
79
|
+
/**
|
|
80
|
+
* Top-level fields whose value came, wholly or partly, from `${env:…}`.
|
|
81
|
+
* Rendered as the variable's name rather than its value, so a plan can be
|
|
82
|
+
* pasted into a ticket.
|
|
83
|
+
*/
|
|
84
|
+
readonly fromEnv: ReadonlyArray<string>;
|
|
85
|
+
}
|
|
86
|
+
export interface DocumentSection {
|
|
87
|
+
readonly def: ResourceDef;
|
|
88
|
+
readonly rows: ReadonlyArray<DocumentRow>;
|
|
89
|
+
}
|
|
90
|
+
export interface WacDocument {
|
|
91
|
+
readonly version: number;
|
|
92
|
+
/** Optional guard: `apply` refuses a workspace that is not this one. */
|
|
93
|
+
readonly workspace: string | undefined;
|
|
94
|
+
/** In `RESOURCES` order — which is dependency order. */
|
|
95
|
+
readonly sections: ReadonlyArray<DocumentSection>;
|
|
96
|
+
}
|
|
97
|
+
/** Thrown with every issue at once, not the first one found. */
|
|
98
|
+
export declare class DocumentError extends Error {
|
|
99
|
+
readonly issues: ReadonlyArray<Issue>;
|
|
100
|
+
constructor(issues: ReadonlyArray<Issue>);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Parse and validate. Throws `DocumentError` with every problem it found.
|
|
104
|
+
*
|
|
105
|
+
* Nothing here touches the network. `env` is the only ambient input and it is
|
|
106
|
+
* injected, which is what lets the spec drive this with a literal object and
|
|
107
|
+
* no transport at all.
|
|
108
|
+
*/
|
|
109
|
+
export declare function parseDocument(raw: unknown, env?: Readonly<Record<string, string | undefined>>): WacDocument;
|
|
110
|
+
//# sourceMappingURL=document.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../src/wac/document.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,OAAO,EAA+B,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE/E,gFAAgF;AAChF,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAyBlC,MAAM,WAAW,KAAK;IACpB,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,2CAA2C;IAC3C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACjD,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IACzF;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;CACnD;AAED,gEAAgE;AAChE,qBAAa,aAAc,SAAQ,KAAK;IACtC,SAAgB,MAAM,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC;gBAE1B,MAAM,EAAE,aAAa,CAAC,KAAK,CAAC;CAQhD;AAwGD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,OAAO,EACZ,GAAG,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAM,GACrD,WAAW,CAiFb"}
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chatly.json` — the document, and the only thing in this feature allowed to
|
|
3
|
+
* refuse you.
|
|
4
|
+
*
|
|
5
|
+
* ═══ WHY THERE IS NO zod HERE, AND WHAT TAKES ITS PLACE ══════════════════
|
|
6
|
+
*
|
|
7
|
+
* This repo validates with zod everywhere and this schema wants it. It is not
|
|
8
|
+
* here for one reason: `@chatlychat/cli` is a PUBLISHED package whose
|
|
9
|
+
* dependency list is exactly `@chatlychat/sdk-node`, zod is not reachable
|
|
10
|
+
* from `apps/cli` (it is in the workspace catalog, but no package here
|
|
11
|
+
* declares it, so it is not installed under `apps/cli/node_modules`), and
|
|
12
|
+
* CLAUDE.md §2 forbids adding a dependency without explicit approval. Adding
|
|
13
|
+
* one to a published CLI is a product decision about what customers install,
|
|
14
|
+
* not a tidying-up decision — the same reason `args.ts` is hand-written
|
|
15
|
+
* rather than `commander`.
|
|
16
|
+
*
|
|
17
|
+
* So the checks below are hand-written, and deliberately shaped so the swap
|
|
18
|
+
* is mechanical if that approval comes: one `parseDocument` entry point,
|
|
19
|
+
* issues collected as `{ path, message }` with a dotted path, and all of them
|
|
20
|
+
* reported at once rather than the first. A zod `.strict()` object and
|
|
21
|
+
* `ZodError.issues` is a drop-in for the body of this file.
|
|
22
|
+
*
|
|
23
|
+
* ═══ WHERE STRICTNESS APPLIES, AND WHERE IT DELIBERATELY DOES NOT ════════
|
|
24
|
+
*
|
|
25
|
+
* CLAUDE.md records the bug twice over: a key the reader has never heard of
|
|
26
|
+
* is STRIPPED, so the write answers 200 and did nothing, and `helpProfile` vs
|
|
27
|
+
* `profile` cost a complete feature that way. The cure is to refuse the key
|
|
28
|
+
* by name and say what the valid ones are. That is done for every part of the
|
|
29
|
+
* document whose vocabulary the CLI OWNS:
|
|
30
|
+
*
|
|
31
|
+
* - the top level (`version`, `workspace`, `resources`)
|
|
32
|
+
* - the resource-type keys inside `resources`
|
|
33
|
+
* - the per-row control keys the CLI invents (`bot:`, which is not an API
|
|
34
|
+
* field at all)
|
|
35
|
+
* - the natural key, which must be present and a non-empty string
|
|
36
|
+
* - any field the API RETURNS and no write path accepts (`unwritable` in
|
|
37
|
+
* `resources.ts`) — `triggers.brandId`, `brands.usage`,
|
|
38
|
+
* `macros.usageCount` and twenty more. These are the dangerous ones:
|
|
39
|
+
* they are spelled correctly, they come back on every read, and zod
|
|
40
|
+
* strips them on the way in.
|
|
41
|
+
*
|
|
42
|
+
* It is NOT done for the rest of a row's fields, and that is a decision
|
|
43
|
+
* rather than an omission. `common.ts`'s `bodyFrom` already states the
|
|
44
|
+
* position for every write on this CLI: "the body goes to the API exactly as
|
|
45
|
+
* written, so the schema in `apps/api` is the only spelling that matters…
|
|
46
|
+
* the CLI simply refuses to add a second renaming layer on top." A per-row
|
|
47
|
+
* allow-list here would be a second COPY of nine API schemas, in a package
|
|
48
|
+
* published on a different cadence, and it would reject a field the API
|
|
49
|
+
* shipped yesterday — a gate that fails on correct input is a gate somebody
|
|
50
|
+
* switches off.
|
|
51
|
+
*
|
|
52
|
+
* What replaces it is better evidence than a copied list: `plan` compares the
|
|
53
|
+
* fields a row declares against the fields the SERVER returns for rows of
|
|
54
|
+
* that type, and warns about one no live row has ever carried. That catches
|
|
55
|
+
* `helpProfle` from the server's own vocabulary, with nothing to keep in
|
|
56
|
+
* step. Its blind spot — a workspace with no rows of that type yet — is
|
|
57
|
+
* reported as such rather than passed off as a clean bill. See `diff.ts`.
|
|
58
|
+
*/
|
|
59
|
+
import { resourceDef, RESOURCE_TYPES } from './resources.js';
|
|
60
|
+
/** The file format's version. Bumped only for a change that breaks a reader. */
|
|
61
|
+
export const DOCUMENT_VERSION = 1;
|
|
62
|
+
const TOP_LEVEL_KEYS = ['version', 'workspace', 'resources'];
|
|
63
|
+
/**
|
|
64
|
+
* `${env:NAME}` inside any string value.
|
|
65
|
+
*
|
|
66
|
+
* Two of these resources cannot be described at all without a secret — a
|
|
67
|
+
* documentation source needs its provider token on create, a customer card
|
|
68
|
+
* needs an `Authorization` header — and a config file in git is the one place
|
|
69
|
+
* those must not be. An interpolation is the smallest thing that makes the
|
|
70
|
+
* file usable, and it is narrow on purpose: this exact form, in a string, and
|
|
71
|
+
* nothing else. No defaults, no shell, no file reads, no nesting.
|
|
72
|
+
*
|
|
73
|
+
* An UNSET variable is refused by name. It is not substituted with an empty
|
|
74
|
+
* string, and that is the whole point: CLAUDE.md records both halves of this
|
|
75
|
+
* failure — "a shell `read` cannot fail", where empty vars beat the sentinel
|
|
76
|
+
* and a health check invented findings, and "env fallback reaches
|
|
77
|
+
* production", where an unset variable became a localhost canonical URL on
|
|
78
|
+
* every page. An empty token here would be POSTed, verified against the
|
|
79
|
+
* provider, and fail with a message about the provider rather than about the
|
|
80
|
+
* variable nobody exported.
|
|
81
|
+
*/
|
|
82
|
+
const ENV_REF = /\$\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
83
|
+
/** Thrown with every issue at once, not the first one found. */
|
|
84
|
+
export class DocumentError extends Error {
|
|
85
|
+
issues;
|
|
86
|
+
constructor(issues) {
|
|
87
|
+
super(`${String(issues.length)} problem(s) in the document:\n` +
|
|
88
|
+
issues.map((i) => ` ${i.path}\n ${i.message}`).join('\n'));
|
|
89
|
+
this.name = 'DocumentError';
|
|
90
|
+
this.issues = issues;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function isPlainObject(value) {
|
|
94
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
95
|
+
}
|
|
96
|
+
/** `a`, `b` and `c` — these go in error messages, so they read as English. */
|
|
97
|
+
function list(names) {
|
|
98
|
+
if (names.length <= 1) {
|
|
99
|
+
return names[0] ?? '(none)';
|
|
100
|
+
}
|
|
101
|
+
return `${names.slice(0, -1).join(', ')} and ${String(names[names.length - 1])}`;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Did the author mean one of the valid keys?
|
|
105
|
+
*
|
|
106
|
+
* Levenshtein ≤ 2, which covers the real cases: a dropped letter, a doubled
|
|
107
|
+
* letter, a transposition, `botid` for `botId`, `eventTypes` for `events`.
|
|
108
|
+
* Anything further is not a typo and guessing would be noise.
|
|
109
|
+
*/
|
|
110
|
+
function nearest(key, valid) {
|
|
111
|
+
let best;
|
|
112
|
+
for (const candidate of valid) {
|
|
113
|
+
const d = distance(key.toLowerCase(), candidate.toLowerCase());
|
|
114
|
+
if (d <= 2 && (best === undefined || d < best.d)) {
|
|
115
|
+
best = { name: candidate, d };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return best?.name;
|
|
119
|
+
}
|
|
120
|
+
function distance(a, b) {
|
|
121
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
122
|
+
for (let i = 1; i <= a.length; i += 1) {
|
|
123
|
+
const row = [i];
|
|
124
|
+
for (let j = 1; j <= b.length; j += 1) {
|
|
125
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
126
|
+
row.push(Math.min((row[j - 1] ?? 0) + 1, (prev[j] ?? 0) + 1, (prev[j - 1] ?? 0) + cost));
|
|
127
|
+
}
|
|
128
|
+
prev = row;
|
|
129
|
+
}
|
|
130
|
+
return prev[b.length] ?? Math.max(a.length, b.length);
|
|
131
|
+
}
|
|
132
|
+
function refuseUnknown(issues, path, actual, valid, what) {
|
|
133
|
+
for (const key of actual) {
|
|
134
|
+
if (valid.includes(key)) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const guess = nearest(key, valid);
|
|
138
|
+
issues.push({
|
|
139
|
+
path: `${path}.${key}`,
|
|
140
|
+
message: `unknown ${what} "${key}".` +
|
|
141
|
+
(guess === undefined ? '' : ` Did you mean "${guess}"?`) +
|
|
142
|
+
` Valid: ${list(valid)}.`,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Substitute `${env:NAME}` throughout a value. Collects unset names.
|
|
148
|
+
*
|
|
149
|
+
* `env` is a parameter rather than a read of `process.env` so the spec can
|
|
150
|
+
* drive it: a test that monkey-patches the process environment is a test that
|
|
151
|
+
* leaks into its neighbours.
|
|
152
|
+
*/
|
|
153
|
+
function interpolate(value, env, missing, used) {
|
|
154
|
+
if (typeof value === 'string') {
|
|
155
|
+
return value.replace(ENV_REF, (_match, name) => {
|
|
156
|
+
const found = env[name];
|
|
157
|
+
if (found === undefined || found === '') {
|
|
158
|
+
missing.add(name);
|
|
159
|
+
return '';
|
|
160
|
+
}
|
|
161
|
+
used.add(name);
|
|
162
|
+
return found;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
if (Array.isArray(value)) {
|
|
166
|
+
return value.map((v) => interpolate(v, env, missing, used));
|
|
167
|
+
}
|
|
168
|
+
if (isPlainObject(value)) {
|
|
169
|
+
const out = {};
|
|
170
|
+
for (const [k, v] of Object.entries(value)) {
|
|
171
|
+
out[k] = interpolate(v, env, missing, used);
|
|
172
|
+
}
|
|
173
|
+
return out;
|
|
174
|
+
}
|
|
175
|
+
return value;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Parse and validate. Throws `DocumentError` with every problem it found.
|
|
179
|
+
*
|
|
180
|
+
* Nothing here touches the network. `env` is the only ambient input and it is
|
|
181
|
+
* injected, which is what lets the spec drive this with a literal object and
|
|
182
|
+
* no transport at all.
|
|
183
|
+
*/
|
|
184
|
+
export function parseDocument(raw, env = {}) {
|
|
185
|
+
const issues = [];
|
|
186
|
+
if (!isPlainObject(raw)) {
|
|
187
|
+
throw new DocumentError([
|
|
188
|
+
{
|
|
189
|
+
path: '(root)',
|
|
190
|
+
message: 'the document must be an object with `version` and `resources`, not ' +
|
|
191
|
+
(Array.isArray(raw) ? 'an array' : typeof raw),
|
|
192
|
+
},
|
|
193
|
+
]);
|
|
194
|
+
}
|
|
195
|
+
refuseUnknown(issues, '(root)', Object.keys(raw), [...TOP_LEVEL_KEYS], 'top-level key');
|
|
196
|
+
const version = raw['version'];
|
|
197
|
+
if (version === undefined) {
|
|
198
|
+
issues.push({
|
|
199
|
+
path: 'version',
|
|
200
|
+
message: `required. Write \`"version": ${String(DOCUMENT_VERSION)}\`.`,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
else if (version !== DOCUMENT_VERSION) {
|
|
204
|
+
issues.push({
|
|
205
|
+
path: 'version',
|
|
206
|
+
message: `this CLI reads version ${String(DOCUMENT_VERSION)}; the document says ` +
|
|
207
|
+
`${JSON.stringify(version)}. Upgrade \`@chatlychat/cli\` rather than editing the number.`,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
const workspaceRaw = raw['workspace'];
|
|
211
|
+
let workspace;
|
|
212
|
+
if (workspaceRaw !== undefined) {
|
|
213
|
+
if (typeof workspaceRaw !== 'string' || workspaceRaw.length === 0) {
|
|
214
|
+
issues.push({
|
|
215
|
+
path: 'workspace',
|
|
216
|
+
message: 'must be a workspace id string, or left out. It is a guard: `apply` refuses to run ' +
|
|
217
|
+
'against any other workspace, which is what stops a staging file reaching production.',
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
workspace = workspaceRaw;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const resourcesRaw = raw['resources'];
|
|
225
|
+
const sections = [];
|
|
226
|
+
if (resourcesRaw === undefined) {
|
|
227
|
+
issues.push({
|
|
228
|
+
path: 'resources',
|
|
229
|
+
message: `required. An object keyed by resource type: ${list(RESOURCE_TYPES)}.`,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
else if (!isPlainObject(resourcesRaw)) {
|
|
233
|
+
issues.push({ path: 'resources', message: 'must be an object keyed by resource type.' });
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
refuseUnknown(issues, 'resources', Object.keys(resourcesRaw), RESOURCE_TYPES, 'resource type');
|
|
237
|
+
/*
|
|
238
|
+
* Iterated in RESOURCES order, not in the file's key order.
|
|
239
|
+
*
|
|
240
|
+
* The order matters at apply time — a routing rule names a bot that this
|
|
241
|
+
* run may be about to create — and taking it from the file would make the
|
|
242
|
+
* result depend on how somebody chose to lay out their document.
|
|
243
|
+
*/
|
|
244
|
+
for (const type of RESOURCE_TYPES) {
|
|
245
|
+
const value = resourcesRaw[type];
|
|
246
|
+
if (value === undefined) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const def = resourceDef(type);
|
|
250
|
+
if (def === undefined) {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
sections.push({ def, rows: parseSection(issues, def, value, env) });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (issues.length > 0) {
|
|
257
|
+
throw new DocumentError(issues);
|
|
258
|
+
}
|
|
259
|
+
return { version: DOCUMENT_VERSION, workspace, sections };
|
|
260
|
+
}
|
|
261
|
+
function parseSection(issues, def, value, env) {
|
|
262
|
+
const at = `resources.${def.type}`;
|
|
263
|
+
/*
|
|
264
|
+
* The resource with no usable natural key is refused here, by name, with
|
|
265
|
+
* the reason.
|
|
266
|
+
*
|
|
267
|
+
* `channels` stays a VALID key under `resources` — dropping it would make
|
|
268
|
+
* the unknown-key message say "channels is not a resource type", which is
|
|
269
|
+
* wrong and sends the reader hunting for a spelling mistake. What it cannot
|
|
270
|
+
* be is non-empty. `publicId` is minted as a GLOBALLY unique reservation
|
|
271
|
+
* (another tenant may hold `web`), `type` and `publicId` are immutable
|
|
272
|
+
* afterwards, `config` reads back redacted, and the create response carries
|
|
273
|
+
* per-provider webhook registration results a diff cannot model.
|
|
274
|
+
*
|
|
275
|
+
* Half-supporting it — "we will update the ones we can match and ignore the
|
|
276
|
+
* rest" — is worse than refusing, because the thing it silently cannot do
|
|
277
|
+
* is the thing somebody put it in the file for.
|
|
278
|
+
*/
|
|
279
|
+
if (def.keySource === 'server') {
|
|
280
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
281
|
+
return [];
|
|
282
|
+
}
|
|
283
|
+
issues.push({
|
|
284
|
+
path: at,
|
|
285
|
+
message: `${def.label}s cannot be managed from a document. \`${def.naturalKey}\` is minted on ` +
|
|
286
|
+
'create and is unique across every workspace, not just yours, so a row here has no key ' +
|
|
287
|
+
`it can safely claim; \`type\` and \`${def.naturalKey}\` are then immutable, and ` +
|
|
288
|
+
'`config` reads back with `__redacted__` in every credential key, so even an update ' +
|
|
289
|
+
`diff is mostly unknowable. Use \`chatly ${def.type} create\` and ` +
|
|
290
|
+
`\`chatly ${def.type} update <id>\`, and remove this section.`,
|
|
291
|
+
});
|
|
292
|
+
return [];
|
|
293
|
+
}
|
|
294
|
+
if (!Array.isArray(value)) {
|
|
295
|
+
issues.push({
|
|
296
|
+
path: at,
|
|
297
|
+
message: `must be an array of ${def.label} objects (it is ${typeof value}).`,
|
|
298
|
+
});
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
const rows = [];
|
|
302
|
+
const seen = new Map();
|
|
303
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
304
|
+
const path = `${at}[${String(i)}]`;
|
|
305
|
+
const row = value[i];
|
|
306
|
+
if (!isPlainObject(row)) {
|
|
307
|
+
issues.push({ path, message: `must be an object, not ${typeof row}.` });
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const keyValue = row[def.naturalKey];
|
|
311
|
+
if (typeof keyValue !== 'string' || keyValue.length === 0) {
|
|
312
|
+
/*
|
|
313
|
+
* The one field this file insists on, and the message says why rather
|
|
314
|
+
* than just "required": somebody reading it on a fresh checkout needs
|
|
315
|
+
* to know that the uuid they were about to paste is the wrong answer.
|
|
316
|
+
*/
|
|
317
|
+
issues.push({
|
|
318
|
+
path: `${path}.${def.naturalKey}`,
|
|
319
|
+
message: `every ${def.label} needs \`${def.naturalKey}\`: a non-empty string. It is the ` +
|
|
320
|
+
'natural key this file matches on, so `plan` can tell a create from an update ' +
|
|
321
|
+
'without a server uuid — which does not exist yet in a new environment.' +
|
|
322
|
+
(def.keyEnforced
|
|
323
|
+
? ''
|
|
324
|
+
: ` Note the database does NOT make \`${def.naturalKey}\` unique for this ` +
|
|
325
|
+
'resource, so this file is the only thing keeping it unique.'),
|
|
326
|
+
});
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
const duplicate = seen.get(keyValue);
|
|
330
|
+
if (duplicate !== undefined) {
|
|
331
|
+
issues.push({
|
|
332
|
+
path: `${path}.${def.naturalKey}`,
|
|
333
|
+
message: `duplicate ${def.naturalKey} "${keyValue}" — also at ${duplicate}. Two rows with one ` +
|
|
334
|
+
'key cannot both be applied, and guessing which was meant is how the wrong resource ' +
|
|
335
|
+
'gets overwritten.',
|
|
336
|
+
});
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
seen.set(keyValue, path);
|
|
340
|
+
/*
|
|
341
|
+
* Fields the API RETURNS and no write path accepts.
|
|
342
|
+
*
|
|
343
|
+
* The dangerous class, and the reason `unwritable` exists in
|
|
344
|
+
* `resources.ts`. These are spelled correctly, they come back on every
|
|
345
|
+
* read, and copying a `chatly bots list --json` row into the document is
|
|
346
|
+
* the obvious way to start one — so `brands.usage`, `macros.usageCount`,
|
|
347
|
+
* `triggers.brandId` and twenty more arrive looking exactly like
|
|
348
|
+
* settings. zod strips every one of them and the write answers 200.
|
|
349
|
+
*/
|
|
350
|
+
for (const field of Object.keys(row)) {
|
|
351
|
+
if (!def.unwritable.includes(field)) {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
issues.push({
|
|
355
|
+
path: `${path}.${field}`,
|
|
356
|
+
message: `\`${field}\` is returned on a read and NO write path accepts it, so zod would strip ` +
|
|
357
|
+
'it and the API would answer 200 having ignored you. Remove it. (A document is not a ' +
|
|
358
|
+
'copy of a list row: it holds the settings you choose, not the state the server ' +
|
|
359
|
+
'reports.)',
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
const missing = new Set();
|
|
363
|
+
const usedEnv = new Set();
|
|
364
|
+
const body = {};
|
|
365
|
+
const refs = {};
|
|
366
|
+
const fromEnv = [];
|
|
367
|
+
for (const [field, rawValue] of Object.entries(row)) {
|
|
368
|
+
if (def.unwritable.includes(field)) {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
const ref = def.references?.[field];
|
|
372
|
+
if (ref === undefined) {
|
|
373
|
+
/*
|
|
374
|
+
* A FRESH set per field, not a shared one.
|
|
375
|
+
*
|
|
376
|
+
* This was one `usedEnv` for the whole row and the membership test
|
|
377
|
+
* was `size > before`, which is correct exactly once: two fields
|
|
378
|
+
* referencing the SAME variable left the second one unmarked, and an
|
|
379
|
+
* unmarked field is printed by value. The spec caught it with a
|
|
380
|
+
* document whose `token` and `baseUrl` both read `${env:X}` — the
|
|
381
|
+
* token was masked and the live credential went out beside it.
|
|
382
|
+
*/
|
|
383
|
+
const perField = new Set();
|
|
384
|
+
body[field] = interpolate(rawValue, env, missing, perField);
|
|
385
|
+
if (perField.size > 0) {
|
|
386
|
+
fromEnv.push(field);
|
|
387
|
+
for (const name of perField) {
|
|
388
|
+
usedEnv.add(name);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
/*
|
|
394
|
+
* A reference key. `bot: "support"` is not an API field — it resolves
|
|
395
|
+
* into `botId` — so it must never reach the body, and the wire field
|
|
396
|
+
* must not be written by hand beside it.
|
|
397
|
+
*/
|
|
398
|
+
if (typeof rawValue !== 'string' || rawValue.length === 0) {
|
|
399
|
+
issues.push({
|
|
400
|
+
path: `${path}.${field}`,
|
|
401
|
+
message: `must be the \`${ref.type}\` natural key as a string.`,
|
|
402
|
+
});
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
if (row[ref.wire] !== undefined) {
|
|
406
|
+
issues.push({
|
|
407
|
+
path: `${path}.${ref.wire}`,
|
|
408
|
+
message: `remove it: this row has both \`${field}: "${rawValue}"\` and \`${ref.wire}\`. ` +
|
|
409
|
+
`\`${ref.wire}\` is a server uuid, which pins the document to one environment — ` +
|
|
410
|
+
`\`${field}\` is the portable form and is what this CLI resolves.`,
|
|
411
|
+
});
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
refs[ref.wire] = { type: ref.type, key: rawValue };
|
|
415
|
+
}
|
|
416
|
+
for (const name of missing) {
|
|
417
|
+
issues.push({
|
|
418
|
+
path,
|
|
419
|
+
message: `\${env:${name}} is used here and ${name} is unset or empty. It is NOT substituted ` +
|
|
420
|
+
'with an empty string: an empty token would be sent, verified against the provider, ' +
|
|
421
|
+
'and fail with a message about the provider rather than about the variable nobody ' +
|
|
422
|
+
`exported. Export ${name} and run again.`,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
rows.push({ key: keyValue, path, body, refs, fromEnv });
|
|
426
|
+
}
|
|
427
|
+
return rows;
|
|
428
|
+
}
|
|
429
|
+
//# sourceMappingURL=document.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.js","sourceRoot":"","sources":["../../src/wac/document.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Printing a plan, and one rule behind every choice here: a diff nobody reads
|
|
3
|
+
* is worse than no diff, because it gets approved.
|
|
4
|
+
*
|
|
5
|
+
* So the shape is terraform's, because that is the shape people already know
|
|
6
|
+
* how to read — a sigil per action, the resource addressed as `type/key`, and
|
|
7
|
+
* the changed fields indented under it with old on the left. What is NOT
|
|
8
|
+
* terraform's: unchanged resources are one line each rather than a collapsed
|
|
9
|
+
* count, because on this surface a workspace has tens of resources and not
|
|
10
|
+
* thousands, and "the thing I expected to be unchanged is listed as
|
|
11
|
+
* unchanged" is the second most useful sentence a plan can say.
|
|
12
|
+
*
|
|
13
|
+
* Values are JSON, truncated. A macro body is 20,000 characters and a
|
|
14
|
+
* trigger's `actions` array is a nested object; neither belongs on a terminal
|
|
15
|
+
* line in full, and `--json` exists for the whole thing.
|
|
16
|
+
*/
|
|
17
|
+
import { type Plan } from './diff.js';
|
|
18
|
+
/** One value, as JSON, clipped. `undefined` is `(unset)`, not `undefined`. */
|
|
19
|
+
export declare function show(value: unknown): string;
|
|
20
|
+
export interface RenderOptions {
|
|
21
|
+
/** `--prune` was passed, so orphan lines say "delete" rather than "kept". */
|
|
22
|
+
readonly prune: boolean;
|
|
23
|
+
/** Types whose live list may be short, where prune is refused regardless. */
|
|
24
|
+
readonly pruneUnsafe: ReadonlyArray<string>;
|
|
25
|
+
}
|
|
26
|
+
/** The whole plan, as `chatly plan` prints it. */
|
|
27
|
+
export declare function renderPlan(plan: Plan, opts: RenderOptions): string;
|
|
28
|
+
/**
|
|
29
|
+
* The line that follows a plan, and it is load-bearing.
|
|
30
|
+
*
|
|
31
|
+
* `plan` is the half of this feature that is safe, and saying so explicitly
|
|
32
|
+
* is what makes somebody willing to run it against production. "Nothing was
|
|
33
|
+
* written" is a claim the spec holds this CLI to: a recording transport
|
|
34
|
+
* asserts zero non-GET requests over the whole plan path.
|
|
35
|
+
*/
|
|
36
|
+
export declare function planFooter(plan: Plan, opts: RenderOptions): string;
|
|
37
|
+
/**
|
|
38
|
+
* The plan, for `--json`, with every env-sourced value taken back out.
|
|
39
|
+
*
|
|
40
|
+
* `plan --json | tee plan.json` is a thing somebody does on the first day,
|
|
41
|
+
* and a plan item carries `row.body` — which holds the RESOLVED value of
|
|
42
|
+
* every `${env:…}` reference, including a docs source's provider token. The
|
|
43
|
+
* human renderer already masks those (`fromEnv` on each row); the JSON
|
|
44
|
+
* renderer printed them, which would have written a live credential into a
|
|
45
|
+
* file somebody then commits.
|
|
46
|
+
*
|
|
47
|
+
* So `row` does not go out at all — a consumer wants the diff, not a copy of
|
|
48
|
+
* its own input — and any change whose field came from the environment is
|
|
49
|
+
* reported as having come from the environment rather than by value.
|
|
50
|
+
*/
|
|
51
|
+
export declare function planForJson(plan: Plan): unknown;
|
|
52
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/wac/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAA2B,KAAK,IAAI,EAAiB,MAAM,WAAW,CAAC;AAY9E,8EAA8E;AAC9E,wBAAgB,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAM3C;AAWD,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC7C;AAED,kDAAkD;AAClD,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,aAAa,GAAG,MAAM,CAgElE;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,aAAa,GAAG,MAAM,CAkBlE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAmB/C"}
|