@vxil/config 0.2.0 → 0.3.1
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 +72 -0
- package/dist/index.d.ts +50 -11
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/src/index.test.ts +1 -1
- package/src/index.ts +54 -13
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# @vxil/config
|
|
2
|
+
|
|
3
|
+
`defineConfig()` — one typed source of truth for a whole Vxil backend.
|
|
4
|
+
|
|
5
|
+
A `vxil.config.ts` in your repo declares every feature you have enabled, your
|
|
6
|
+
CMS content model, your deployed functions, your secret *references* and your
|
|
7
|
+
seed data. `vxil push` compiles it to a manifest and applies it. Nothing in this
|
|
8
|
+
file is ever executed by Vxil — config is data.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm i -D @vxil/config
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
> The CLI that reads this file is **`@vxil/cli`** (it installs the `vxil`
|
|
15
|
+
> command). The bare `vxil` package name is blocked on npm — never `npm i vxil`.
|
|
16
|
+
|
|
17
|
+
## Use it
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
// vxil.config.ts
|
|
21
|
+
import { defineConfig } from '@vxil/config';
|
|
22
|
+
|
|
23
|
+
export default defineConfig({
|
|
24
|
+
features: {
|
|
25
|
+
cms: { enabled: true },
|
|
26
|
+
files: { enabled: true },
|
|
27
|
+
notifications: { enabled: true, fromEmail: 'noreply@acme.com' },
|
|
28
|
+
},
|
|
29
|
+
cms: {
|
|
30
|
+
collections: {
|
|
31
|
+
posts: {
|
|
32
|
+
fields: {
|
|
33
|
+
title: { type: 'string', required: true, indexSlot: 's1' },
|
|
34
|
+
body: { type: 'text' },
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx @vxil/cli plan # dry-run diff against the live tenant
|
|
44
|
+
npx @vxil/cli push # apply, idempotent
|
|
45
|
+
npx @vxil/cli gen # emit the per-tenant typed client
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## What it gives you
|
|
49
|
+
|
|
50
|
+
- **Real autocomplete.** `defineConfig` is a typed identity function: it returns
|
|
51
|
+
its argument verbatim, and the types come from
|
|
52
|
+
[`@vxil/feature-configs`](https://www.npmjs.com/package/@vxil/feature-configs),
|
|
53
|
+
the same schemas the platform validates against. A knob that does not exist is
|
|
54
|
+
a compile error in your editor, not a 422 at push time.
|
|
55
|
+
- **The whole backend in one file.** Features, the CMS content model (collections,
|
|
56
|
+
field types, index slots, uniqueness, lifecycle hook expressions), functions,
|
|
57
|
+
secret references and seed data — one declarative object.
|
|
58
|
+
- **Secrets by reference, never by value.** A config carries `secret:<name>`; the
|
|
59
|
+
value is set once with `vxil secrets set` and never lands in your repo.
|
|
60
|
+
- **Versioned, concurrent-safe applies.** Every push is an If-Match write against
|
|
61
|
+
an immutable config version, so two people (or a person and CI) cannot silently
|
|
62
|
+
clobber each other — and `vxil versions` / `vxil rollback` undo one.
|
|
63
|
+
|
|
64
|
+
## Related
|
|
65
|
+
|
|
66
|
+
- [`@vxil/cli`](https://www.npmjs.com/package/@vxil/cli) — the `vxil` command.
|
|
67
|
+
- [`@vxil/feature-configs`](https://www.npmjs.com/package/@vxil/feature-configs) — the feature schemas and their `Static<>` types.
|
|
68
|
+
- [`@vxil/sdk`](https://www.npmjs.com/package/@vxil/sdk) — the typed runtime client.
|
|
69
|
+
|
|
70
|
+
Docs: [vxil.com](https://vxil.com) · Dashboard: [vxil.com/dashboard](https://vxil.com/dashboard)
|
|
71
|
+
|
|
72
|
+
MIT © techmaker.io
|
package/dist/index.d.ts
CHANGED
|
@@ -23,9 +23,34 @@ export interface FieldDef {
|
|
|
23
23
|
* cannot reconcile surface as a visible plan warning, never a silent no-op. */
|
|
24
24
|
unique?: boolean;
|
|
25
25
|
/** Per-relation-field delete behavior (cms.md §11): bounded, atomic fan-out
|
|
26
|
-
* when the referenced item is deleted
|
|
27
|
-
*
|
|
28
|
-
|
|
26
|
+
* when the referenced item is deleted — or `restrict` (§11.1), which REFUSES
|
|
27
|
+
* the delete with 409 `referenced` while a live reference exists. Same
|
|
28
|
+
* carriage + reconcile behavior as `unique`. */
|
|
29
|
+
onDelete?: 'cascade' | 'set_null' | 'restrict';
|
|
30
|
+
/** FIELD-LEVEL READ SECURITY (cms.md §18): the end-user ORG ROLE slugs allowed
|
|
31
|
+
* to READ this field. Omitted/`[]` = ungated (every caller sees it — today's
|
|
32
|
+
* behavior). A non-empty list is FAIL-SAFE: in VERIFIED end-user mode the
|
|
33
|
+
* field is OMITTED from every read (get / list / query / `$expand` at any
|
|
34
|
+
* depth / the write-response echo / read-hook payloads) unless the session's
|
|
35
|
+
* verified `roles` claim intersects the list, it is UNQUERYABLE (a filter /
|
|
36
|
+
* sort / group-by naming it is a 422, so it cannot be a value oracle), and it
|
|
37
|
+
* is NEVER served on the anonymous public lane. A trusted SERVER caller (an
|
|
38
|
+
* API key with no end-user session) still sees every field — this gates END
|
|
39
|
+
* USERS, not the tenant. ≤16 entries, each `^[a-z0-9][a-z0-9_-]{0,31}$` (the
|
|
40
|
+
* `orgs` role alphabet). The gate
|
|
41
|
+
* never affects WRITES. Carried by BOTH push paths and alterable in place. */
|
|
42
|
+
readRoles?: string[];
|
|
43
|
+
}
|
|
44
|
+
/** One config-declared per-record ACTION button (cms.md §17): exactly ONE
|
|
45
|
+
* human-initiated step — pressing it invokes the deployed tenant function `fn`
|
|
46
|
+
* with `{ collection, item_id, action, actor, item }` and returns its result.
|
|
47
|
+
* No conditions, no chaining, no scheduling. `key` matches
|
|
48
|
+
* /^[a-z][a-z0-9_]{0,31}$/, `fn` is a function name (/^[a-z][a-z0-9-]{0,47}$/),
|
|
49
|
+
* ≤8 per collection. */
|
|
50
|
+
export interface CmsActionDef {
|
|
51
|
+
key: string;
|
|
52
|
+
label: string;
|
|
53
|
+
fn: string;
|
|
29
54
|
}
|
|
30
55
|
/** The CANONICAL field-attribute name set (config-side spelling), DERIVED from
|
|
31
56
|
* `FieldDef` itself so it can never drift from the authoring type: it is every
|
|
@@ -51,7 +76,7 @@ export type CoversFieldAttrs<Covered extends FieldAttrName> = [
|
|
|
51
76
|
export interface CollectionDef {
|
|
52
77
|
singular?: string;
|
|
53
78
|
fields: Record<string, FieldDef>;
|
|
54
|
-
/** End-user owner-scoping (docs/
|
|
79
|
+
/** End-user owner-scoping (https://vxil.com/docs/guide/09-security-and-multitenancy): names an
|
|
55
80
|
* existing `string` field on this collection that holds the owner (end-user)
|
|
56
81
|
* id. When set, the cms worker auto-scopes owned reads/writes to the VERIFIED
|
|
57
82
|
* end-user principal in end-user mode (default-deny) — and is a no-op in
|
|
@@ -66,10 +91,24 @@ export interface CollectionDef {
|
|
|
66
91
|
* with a regression test on each — an attribute read by no enforcing path ships
|
|
67
92
|
* inert (the validation.unique / owner_field precedent). */
|
|
68
93
|
public?: boolean;
|
|
94
|
+
/** Per-record action buttons (cms.md §17): `[{ key, label, fn }]`, stored on
|
|
95
|
+
* the collection (model data, not a config leaf). The dashboard renders one
|
|
96
|
+
* button per action on each record row; `POST /v1/cms/items/:coll/:id/actions/
|
|
97
|
+
* :key` invokes `fn` once. Carried by BOTH push paths (create + reconcile on
|
|
98
|
+
* existing collections — config is the source of truth; `[]`/absent clears). */
|
|
99
|
+
actions?: CmsActionDef[];
|
|
69
100
|
}
|
|
101
|
+
/** The auth lifecycle events an `authHook` binding may subscribe to (F4-30,
|
|
102
|
+
* auth wave 2026-09-10) — a CLOSED union; ONE event per binding, one audit
|
|
103
|
+
* event → one function invocation, no branching inside vxil:
|
|
104
|
+
* user.created — auth.user.created { user_id, method, is_anonymous }
|
|
105
|
+
* session.created — auth.session.created { user_id, session_id }
|
|
106
|
+
* session.revoked — auth.session.revoked { user_id, session_id, reason }
|
|
107
|
+
* signin.failure — auth.signin.failure { email_hash | user_id, reason } */
|
|
108
|
+
export type AuthHookEvent = 'user.created' | 'session.created' | 'session.revoked' | 'signin.failure';
|
|
70
109
|
/** A function trigger (the §7.3 crossing). cmsHook fires on a CMS write;
|
|
71
|
-
* authHook
|
|
72
|
-
* at-least-once, ~1min fanout latency). */
|
|
110
|
+
* authHook fires on ONE auth lifecycle event (default `user.created` — the
|
|
111
|
+
* post-signup hook; at-least-once, ~1min fanout latency). */
|
|
73
112
|
export type FunctionTrigger = {
|
|
74
113
|
kind: 'http';
|
|
75
114
|
path?: string;
|
|
@@ -88,7 +127,7 @@ export type FunctionTrigger = {
|
|
|
88
127
|
event: 'beforeCreate' | 'beforeUpdate' | 'beforeWrite';
|
|
89
128
|
} | {
|
|
90
129
|
kind: 'authHook';
|
|
91
|
-
event?:
|
|
130
|
+
event?: AuthHookEvent;
|
|
92
131
|
};
|
|
93
132
|
/** A deployed tenant function — source in functions/, deployed on `vxil push`. */
|
|
94
133
|
export interface FunctionDef {
|
|
@@ -99,9 +138,9 @@ export interface FunctionDef {
|
|
|
99
138
|
* functions:write, secrets:write rejected; payments:write / notifications:send /
|
|
100
139
|
* users:* allowed as owner-granted business scopes). */
|
|
101
140
|
scopes?: string[];
|
|
102
|
-
/**
|
|
141
|
+
/** Names of tenant secrets injected into the function at invoke time. */
|
|
103
142
|
secrets?: string[];
|
|
104
|
-
/** Outbound
|
|
143
|
+
/** Outbound host allowlist (deny-by-default egress guard). */
|
|
105
144
|
egressAllow?: string[];
|
|
106
145
|
limits?: {
|
|
107
146
|
cpuMs?: number;
|
|
@@ -132,7 +171,7 @@ export interface SeedSpec {
|
|
|
132
171
|
email?: string;
|
|
133
172
|
}[];
|
|
134
173
|
}
|
|
135
|
-
/** The released API majors (docs/
|
|
174
|
+
/** The released API majors (see https://vxil.com/docs/guide/12-going-to-production), a CLOSED union —
|
|
136
175
|
* `'v1'` is the only released major, so `apiVersion: 'v2'` is a COMPILE error
|
|
137
176
|
* until a v2 GAs. `defineConfig` also validates it at runtime against
|
|
138
177
|
* `RELEASED_API_VERSIONS`. */
|
|
@@ -144,7 +183,7 @@ export declare const RELEASED_API_VERSIONS: readonly ApiVersion[];
|
|
|
144
183
|
export interface VxilConfig {
|
|
145
184
|
/** target environment label; resolved to a baseUrl by `vxil link` / `--env`. */
|
|
146
185
|
env?: string;
|
|
147
|
-
/** Pin the API major the CLI targets (default `'v1'
|
|
186
|
+
/** Pin the API major the CLI targets (default `'v1'`; see https://vxil.com/docs/guide/12-going-to-production).
|
|
148
187
|
* Path-major, per-platform: a breaking change ships as a new major on a new
|
|
149
188
|
* path with a ≥6-month deprecation window. `VXIL_API_VERSION` overrides this
|
|
150
189
|
* for a one-off invocation. */
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ export function defineConfig(c) {
|
|
|
9
9
|
if (c.apiVersion !== undefined && !RELEASED_API_VERSIONS.includes(c.apiVersion)) {
|
|
10
10
|
throw new Error(`vxil.config: apiVersion '${String(c.apiVersion)}' is not a released API major ` +
|
|
11
11
|
`(released: ${RELEASED_API_VERSIONS.map((v) => `'${v}'`).join(', ')}). ` +
|
|
12
|
-
'A new major ships as a new path with its own deprecation window — see docs/
|
|
12
|
+
'A new major ships as a new path with its own deprecation window — see https://vxil.com/docs/guide/12-going-to-production.');
|
|
13
13
|
}
|
|
14
14
|
return c;
|
|
15
15
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vxil/config",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "Typed vxil.config.ts authoring for the vxil backend platform — defineConfig, feature and cms field definitions (published for @vxil/cli).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://vxil.com",
|
|
7
7
|
"repository": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"src"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@vxil/feature-configs": "0.
|
|
25
|
+
"@vxil/feature-configs": "0.3.1"
|
|
26
26
|
},
|
|
27
27
|
"publishConfig": {
|
|
28
28
|
"access": "public"
|
package/src/index.test.ts
CHANGED
|
@@ -23,6 +23,6 @@ describe('defineConfig', () => {
|
|
|
23
23
|
expect(() => defineConfig({ apiVersion: 'v2' as 'v1', features: {} }))
|
|
24
24
|
.toThrow(/apiVersion 'v2' is not a released API major/);
|
|
25
25
|
expect(() => defineConfig({ apiVersion: 'latest' as 'v1' }))
|
|
26
|
-
.toThrow(/
|
|
26
|
+
.toThrow(/docs\/guide\/12-going-to-production/);
|
|
27
27
|
});
|
|
28
28
|
});
|
package/src/index.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// §7.3-consistent — config is DATA; the only tenant CODE that crosses into vxil
|
|
8
8
|
// is (a) the Lane-A hook expressions (a closed sandbox) and (b) the functions/
|
|
9
9
|
// sources (the paid, opt-in, egress-guarded crossing). Everything here is
|
|
10
|
-
// declarative. See docs/
|
|
10
|
+
// declarative. See https://vxil.com/docs/guide/05-typed-sdk-and-cli.
|
|
11
11
|
import type {
|
|
12
12
|
NotificationsConfig, JobsConfig, AuthConfig, RateLimitsConfig, FilesConfig,
|
|
13
13
|
WebhooksConfig, CommentsConfig, CmsConfig, McpConfig, RealtimeConfig,
|
|
@@ -42,9 +42,35 @@ export interface FieldDef {
|
|
|
42
42
|
* cannot reconcile surface as a visible plan warning, never a silent no-op. */
|
|
43
43
|
unique?: boolean;
|
|
44
44
|
/** Per-relation-field delete behavior (cms.md §11): bounded, atomic fan-out
|
|
45
|
-
* when the referenced item is deleted
|
|
46
|
-
*
|
|
47
|
-
|
|
45
|
+
* when the referenced item is deleted — or `restrict` (§11.1), which REFUSES
|
|
46
|
+
* the delete with 409 `referenced` while a live reference exists. Same
|
|
47
|
+
* carriage + reconcile behavior as `unique`. */
|
|
48
|
+
onDelete?: 'cascade' | 'set_null' | 'restrict';
|
|
49
|
+
/** FIELD-LEVEL READ SECURITY (cms.md §18): the end-user ORG ROLE slugs allowed
|
|
50
|
+
* to READ this field. Omitted/`[]` = ungated (every caller sees it — today's
|
|
51
|
+
* behavior). A non-empty list is FAIL-SAFE: in VERIFIED end-user mode the
|
|
52
|
+
* field is OMITTED from every read (get / list / query / `$expand` at any
|
|
53
|
+
* depth / the write-response echo / read-hook payloads) unless the session's
|
|
54
|
+
* verified `roles` claim intersects the list, it is UNQUERYABLE (a filter /
|
|
55
|
+
* sort / group-by naming it is a 422, so it cannot be a value oracle), and it
|
|
56
|
+
* is NEVER served on the anonymous public lane. A trusted SERVER caller (an
|
|
57
|
+
* API key with no end-user session) still sees every field — this gates END
|
|
58
|
+
* USERS, not the tenant. ≤16 entries, each `^[a-z0-9][a-z0-9_-]{0,31}$` (the
|
|
59
|
+
* `orgs` role alphabet). The gate
|
|
60
|
+
* never affects WRITES. Carried by BOTH push paths and alterable in place. */
|
|
61
|
+
readRoles?: string[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** One config-declared per-record ACTION button (cms.md §17): exactly ONE
|
|
65
|
+
* human-initiated step — pressing it invokes the deployed tenant function `fn`
|
|
66
|
+
* with `{ collection, item_id, action, actor, item }` and returns its result.
|
|
67
|
+
* No conditions, no chaining, no scheduling. `key` matches
|
|
68
|
+
* /^[a-z][a-z0-9_]{0,31}$/, `fn` is a function name (/^[a-z][a-z0-9-]{0,47}$/),
|
|
69
|
+
* ≤8 per collection. */
|
|
70
|
+
export interface CmsActionDef {
|
|
71
|
+
key: string;
|
|
72
|
+
label: string;
|
|
73
|
+
fn: string;
|
|
48
74
|
}
|
|
49
75
|
|
|
50
76
|
/** The CANONICAL field-attribute name set (config-side spelling), DERIVED from
|
|
@@ -72,7 +98,7 @@ export type CoversFieldAttrs<Covered extends FieldAttrName> =
|
|
|
72
98
|
export interface CollectionDef {
|
|
73
99
|
singular?: string;
|
|
74
100
|
fields: Record<string, FieldDef>;
|
|
75
|
-
/** End-user owner-scoping (docs/
|
|
101
|
+
/** End-user owner-scoping (https://vxil.com/docs/guide/09-security-and-multitenancy): names an
|
|
76
102
|
* existing `string` field on this collection that holds the owner (end-user)
|
|
77
103
|
* id. When set, the cms worker auto-scopes owned reads/writes to the VERIFIED
|
|
78
104
|
* end-user principal in end-user mode (default-deny) — and is a no-op in
|
|
@@ -87,18 +113,33 @@ export interface CollectionDef {
|
|
|
87
113
|
* with a regression test on each — an attribute read by no enforcing path ships
|
|
88
114
|
* inert (the validation.unique / owner_field precedent). */
|
|
89
115
|
public?: boolean;
|
|
116
|
+
/** Per-record action buttons (cms.md §17): `[{ key, label, fn }]`, stored on
|
|
117
|
+
* the collection (model data, not a config leaf). The dashboard renders one
|
|
118
|
+
* button per action on each record row; `POST /v1/cms/items/:coll/:id/actions/
|
|
119
|
+
* :key` invokes `fn` once. Carried by BOTH push paths (create + reconcile on
|
|
120
|
+
* existing collections — config is the source of truth; `[]`/absent clears). */
|
|
121
|
+
actions?: CmsActionDef[];
|
|
90
122
|
}
|
|
91
123
|
|
|
124
|
+
/** The auth lifecycle events an `authHook` binding may subscribe to (F4-30,
|
|
125
|
+
* auth wave 2026-09-10) — a CLOSED union; ONE event per binding, one audit
|
|
126
|
+
* event → one function invocation, no branching inside vxil:
|
|
127
|
+
* user.created — auth.user.created { user_id, method, is_anonymous }
|
|
128
|
+
* session.created — auth.session.created { user_id, session_id }
|
|
129
|
+
* session.revoked — auth.session.revoked { user_id, session_id, reason }
|
|
130
|
+
* signin.failure — auth.signin.failure { email_hash | user_id, reason } */
|
|
131
|
+
export type AuthHookEvent = 'user.created' | 'session.created' | 'session.revoked' | 'signin.failure';
|
|
132
|
+
|
|
92
133
|
/** A function trigger (the §7.3 crossing). cmsHook fires on a CMS write;
|
|
93
|
-
* authHook
|
|
94
|
-
* at-least-once, ~1min fanout latency). */
|
|
134
|
+
* authHook fires on ONE auth lifecycle event (default `user.created` — the
|
|
135
|
+
* post-signup hook; at-least-once, ~1min fanout latency). */
|
|
95
136
|
export type FunctionTrigger =
|
|
96
137
|
| { kind: 'http'; path?: string }
|
|
97
138
|
| { kind: 'cron'; schedule: string }
|
|
98
139
|
| { kind: 'queue'; source: string }
|
|
99
140
|
| { kind: 'webhook'; source: string }
|
|
100
141
|
| { kind: 'cmsHook'; collection: string; event: 'beforeCreate' | 'beforeUpdate' | 'beforeWrite' }
|
|
101
|
-
| { kind: 'authHook'; event?:
|
|
142
|
+
| { kind: 'authHook'; event?: AuthHookEvent };
|
|
102
143
|
|
|
103
144
|
/** A deployed tenant function — source in functions/, deployed on `vxil push`. */
|
|
104
145
|
export interface FunctionDef {
|
|
@@ -109,9 +150,9 @@ export interface FunctionDef {
|
|
|
109
150
|
* functions:write, secrets:write rejected; payments:write / notifications:send /
|
|
110
151
|
* users:* allowed as owner-granted business scopes). */
|
|
111
152
|
scopes?: string[];
|
|
112
|
-
/**
|
|
153
|
+
/** Names of tenant secrets injected into the function at invoke time. */
|
|
113
154
|
secrets?: string[];
|
|
114
|
-
/** Outbound
|
|
155
|
+
/** Outbound host allowlist (deny-by-default egress guard). */
|
|
115
156
|
egressAllow?: string[];
|
|
116
157
|
limits?: { cpuMs?: number; timeoutMs?: number; memoryMb?: number };
|
|
117
158
|
enabled?: boolean;
|
|
@@ -132,7 +173,7 @@ export interface SeedSpec {
|
|
|
132
173
|
users?: { id: string; email?: string }[];
|
|
133
174
|
}
|
|
134
175
|
|
|
135
|
-
/** The released API majors (docs/
|
|
176
|
+
/** The released API majors (see https://vxil.com/docs/guide/12-going-to-production), a CLOSED union —
|
|
136
177
|
* `'v1'` is the only released major, so `apiVersion: 'v2'` is a COMPILE error
|
|
137
178
|
* until a v2 GAs. `defineConfig` also validates it at runtime against
|
|
138
179
|
* `RELEASED_API_VERSIONS`. */
|
|
@@ -147,7 +188,7 @@ export interface VxilConfig {
|
|
|
147
188
|
/** target environment label; resolved to a baseUrl by `vxil link` / `--env`. */
|
|
148
189
|
env?: string;
|
|
149
190
|
|
|
150
|
-
/** Pin the API major the CLI targets (default `'v1'
|
|
191
|
+
/** Pin the API major the CLI targets (default `'v1'`; see https://vxil.com/docs/guide/12-going-to-production).
|
|
151
192
|
* Path-major, per-platform: a breaking change ships as a new major on a new
|
|
152
193
|
* path with a ≥6-month deprecation window. `VXIL_API_VERSION` overrides this
|
|
153
194
|
* for a one-off invocation. */
|
|
@@ -204,7 +245,7 @@ export function defineConfig(c: VxilConfig): VxilConfig {
|
|
|
204
245
|
throw new Error(
|
|
205
246
|
`vxil.config: apiVersion '${String(c.apiVersion)}' is not a released API major ` +
|
|
206
247
|
`(released: ${RELEASED_API_VERSIONS.map((v) => `'${v}'`).join(', ')}). ` +
|
|
207
|
-
'A new major ships as a new path with its own deprecation window — see docs/
|
|
248
|
+
'A new major ships as a new path with its own deprecation window — see https://vxil.com/docs/guide/12-going-to-production.',
|
|
208
249
|
);
|
|
209
250
|
}
|
|
210
251
|
return c;
|