@impetik/xeer-mcp 0.2.30 → 0.2.32
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 +4 -2
- package/package.json +2 -2
- package/vendor/spec/actions.d.ts +41 -4
- package/vendor/spec/actions.js +41 -9
- package/vendor/spec/admin-sql.js +7 -2
- package/vendor/spec/diagnostics.js +6 -0
- package/vendor/spec/template-distribution.d.ts +24 -0
- package/vendor/spec/template-distribution.js +41 -3
- package/vendor/spec/template.d.ts +13 -0
- package/vendor/spec/template.js +36 -1
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ The generated registry is the complete MCP tool and exclusion surface:
|
|
|
76
76
|
| `xeer_dev_stop` | `dev.stop` | `author` / `operator` | Stop a local development session and release its lease. | `sessionId?`: `string`<br>`cursor?`: `integer` [0..9007199254740991]; default `0` | `run-local` | writes; idempotent; reversible; non-destructive | `none` | `xeer.dev.v0` | none |
|
|
77
77
|
| `xeer_diagnostics` | `diagnostics` | `author` / `operator` | Explain one emitted diagnostic code. | `code`: `string` | none | read-only; idempotent; reversible; non-destructive | `none` | `xeer.command.v0` | none |
|
|
78
78
|
|
|
79
|
-
**CLI actions intentionally excluded from MCP (
|
|
79
|
+
**CLI actions intentionally excluded from MCP (39).**
|
|
80
80
|
|
|
81
81
|
| CLI action | Action | Summary | Effects | Safety | Path policy | Output | Why no MCP tool |
|
|
82
82
|
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
@@ -116,7 +116,9 @@ The generated registry is the complete MCP tool and exclusion surface:
|
|
|
116
116
|
| `xeer actions` | `actions` | Print the machine-readable action and safety catalogue. | none | read-only; idempotent; reversible; non-destructive | `none` | `xeer.command.v0` | Not exposed through MCP v0; use the CLI deliberately. |
|
|
117
117
|
| `xeer db tables` | `db.tables` | List the tables of a running application database. | `read-state` | read-only; idempotent; reversible; non-destructive | `project-relative` | `xeer.command.v0` | Not exposed through MCP v0; use the CLI deliberately. |
|
|
118
118
|
| `xeer db schema` | `db.schema` | Print the declared schema of a running application database. | `read-state` | read-only; idempotent; reversible; non-destructive | `project-relative` | `xeer.command.v0` | Not exposed through MCP v0; use the CLI deliberately. |
|
|
119
|
-
| `xeer db exec` | `db.exec` | Run
|
|
119
|
+
| `xeer db exec` | `db.exec` | Run SQL against a running or deployed application database. | `read-state`<br>`write-state`<br>`network-read`<br>`network-write` | writes; non-idempotent; irreversible; destructive | `project-relative` | `xeer.command.v0` | Destructive state replacement requires an explicit CLI invocation. |
|
|
120
|
+
| `xeer db bookmark` | `db.bookmark` | Print the current Time Travel bookmark of a deployed application database. | `network-read` | read-only; idempotent; reversible; non-destructive | `project-relative` | `xeer.command.v0` | Not exposed through MCP v0; use the CLI deliberately. |
|
|
121
|
+
| `xeer db restore` | `db.restore` | Restore a deployed application database to a bookmark or a time. | `network-write`<br>`write-state` | writes; non-idempotent; reversible; destructive | `project-relative` | `xeer.command.v0` | Destructive state replacement requires an explicit CLI invocation. |
|
|
120
122
|
<!-- xeer-action-reference:end -->
|
|
121
123
|
|
|
122
124
|
Every tool returns `{ protocol: "xeer.mcp-result.v0", action, ok, result?, error? }` both as
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@impetik/xeer-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.32",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Model Context Protocol server for Xeer project context, diagnostics, scaffold, check, test, dev, build, and preview deployment.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
49
49
|
"zod": "^4.0.10",
|
|
50
|
-
"@impetik/xeer": "0.2.
|
|
50
|
+
"@impetik/xeer": "0.2.32"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/node": "^24.1.0"
|
package/vendor/spec/actions.d.ts
CHANGED
|
@@ -1265,15 +1265,52 @@ declare const actions: readonly [{
|
|
|
1265
1265
|
}, {
|
|
1266
1266
|
readonly id: "db.exec";
|
|
1267
1267
|
readonly command: readonly ["db", "exec"];
|
|
1268
|
-
readonly summary: "Run
|
|
1269
|
-
readonly
|
|
1268
|
+
readonly summary: "Run SQL against a running or deployed application database.";
|
|
1269
|
+
readonly description: string;
|
|
1270
|
+
readonly usage: readonly ["db exec [directory] <sql> [--state <dev|preview>] [--write] [--json]", "db exec [directory] <sql> --remote --environment <prod|preview> [--param <value>]... [--control-url <url>] [--json]", "db exec [directory] --remote --environment <prod|preview> --file <batch.json> [--control-url <url>] [--json]"];
|
|
1270
1271
|
readonly helpOrder: 340;
|
|
1271
1272
|
readonly outputProtocol: "xeer.command.v0";
|
|
1272
|
-
readonly effects: readonly ["read-state", "write-state"];
|
|
1273
|
+
readonly effects: readonly ["read-state", "write-state", "network-read", "network-write"];
|
|
1273
1274
|
readonly idempotent: false;
|
|
1274
1275
|
readonly reversible: false;
|
|
1276
|
+
readonly destructive: true;
|
|
1277
|
+
readonly humanPrerequisites: readonly ["Locally, a human must pass --write before any statement that modifies rows.", string];
|
|
1278
|
+
readonly pathPolicy: "project-relative";
|
|
1279
|
+
readonly surfaces: {
|
|
1280
|
+
readonly cli: true;
|
|
1281
|
+
readonly mcpExclusion: "Destructive state replacement requires an explicit CLI invocation.";
|
|
1282
|
+
};
|
|
1283
|
+
}, {
|
|
1284
|
+
readonly id: "db.bookmark";
|
|
1285
|
+
readonly command: readonly ["db", "bookmark"];
|
|
1286
|
+
readonly summary: "Print the current Time Travel bookmark of a deployed application database.";
|
|
1287
|
+
readonly description: string;
|
|
1288
|
+
readonly usage: readonly ["db bookmark [directory] --remote --environment <prod|preview> [--control-url <url>] [--json]"];
|
|
1289
|
+
readonly helpOrder: 345;
|
|
1290
|
+
readonly outputProtocol: "xeer.command.v0";
|
|
1291
|
+
readonly effects: readonly ["network-read"];
|
|
1292
|
+
readonly idempotent: true;
|
|
1293
|
+
readonly reversible: true;
|
|
1275
1294
|
readonly destructive: false;
|
|
1276
|
-
readonly humanPrerequisites: readonly [
|
|
1295
|
+
readonly humanPrerequisites: readonly [string];
|
|
1296
|
+
readonly pathPolicy: "project-relative";
|
|
1297
|
+
readonly surfaces: {
|
|
1298
|
+
readonly cli: true;
|
|
1299
|
+
readonly mcpExclusion: "Not exposed through MCP v0; use the CLI deliberately.";
|
|
1300
|
+
};
|
|
1301
|
+
}, {
|
|
1302
|
+
readonly id: "db.restore";
|
|
1303
|
+
readonly command: readonly ["db", "restore"];
|
|
1304
|
+
readonly summary: "Restore a deployed application database to a bookmark or a time.";
|
|
1305
|
+
readonly description: string;
|
|
1306
|
+
readonly usage: readonly ["db restore [directory] --remote --environment <prod|preview> --to <bookmark|time> [--control-url <url>] [--json]"];
|
|
1307
|
+
readonly helpOrder: 346;
|
|
1308
|
+
readonly outputProtocol: "xeer.command.v0";
|
|
1309
|
+
readonly effects: readonly ["network-write", "write-state"];
|
|
1310
|
+
readonly idempotent: false;
|
|
1311
|
+
readonly reversible: true;
|
|
1312
|
+
readonly destructive: true;
|
|
1313
|
+
readonly humanPrerequisites: readonly [string];
|
|
1277
1314
|
readonly pathPolicy: "project-relative";
|
|
1278
1315
|
readonly surfaces: {
|
|
1279
1316
|
readonly cli: true;
|
package/vendor/spec/actions.js
CHANGED
|
@@ -17,7 +17,7 @@ export const XEER_CLI_COMMAND_GROUPS = Object.freeze([
|
|
|
17
17
|
{ command: 'state', summary: 'Inspect running application state or reset local state.' },
|
|
18
18
|
{ command: 'env', summary: 'Manage application environment values and secrets.' },
|
|
19
19
|
{ command: 'token', summary: 'Manage service builder tokens.' },
|
|
20
|
-
{ command: 'db', summary: 'Inspect and
|
|
20
|
+
{ command: 'db', summary: 'Inspect, query and restore a running or deployed application database.' },
|
|
21
21
|
]);
|
|
22
22
|
const NOT_EXPOSED_V0 = 'Not exposed through MCP v0; use the CLI deliberately.';
|
|
23
23
|
const HUMAN_IDENTITY = 'Human identity and project ownership decisions are not delegated through MCP.';
|
|
@@ -771,16 +771,48 @@ const actions = [
|
|
|
771
771
|
{
|
|
772
772
|
// Not idempotent and not reversible, because `--write` admits UPDATE and DELETE and the action
|
|
773
773
|
// catalogue describes what an invocation may do rather than what a particular statement does.
|
|
774
|
-
//
|
|
775
|
-
//
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
774
|
+
// Destructive because `--remote` runs any SQL the owner sends, DDL included: on a deployed
|
|
775
|
+
// database the owner may do anything to their own data, and `db restore` is the undo.
|
|
776
|
+
id: 'db.exec', command: ['db', 'exec'], summary: 'Run SQL against a running or deployed application database.',
|
|
777
|
+
description: 'Locally (`--state`), runs one statement against a running `xeer dev` or `xeer preview`: '
|
|
778
|
+
+ 'reads by default, row writes with --write, and never DDL. With --remote it runs as the owner '
|
|
779
|
+
+ 'against the D1 database of a deployed framework project, in the --environment named (there is no '
|
|
780
|
+
+ 'default), through the control plane: any SQL, one statement or a --file batch (a JSON array of '
|
|
781
|
+
+ '{"sql","params"}) that runs as one transaction, so a failing statement rolls every statement '
|
|
782
|
+
+ 'back. Undo with `xeer db restore --remote`.',
|
|
783
|
+
usage: ['db exec [directory] <sql> [--state <dev|preview>] [--write] [--json]',
|
|
784
|
+
'db exec [directory] <sql> --remote --environment <prod|preview> [--param <value>]... [--control-url <url>] [--json]',
|
|
785
|
+
'db exec [directory] --remote --environment <prod|preview> --file <batch.json> [--control-url <url>] [--json]'],
|
|
786
|
+
helpOrder: 340,
|
|
787
|
+
outputProtocol: 'xeer.command.v0', effects: ['read-state', 'write-state', 'network-read', 'network-write'],
|
|
788
|
+
idempotent: false, reversible: false, destructive: true,
|
|
789
|
+
humanPrerequisites: ['Locally, a human must pass --write before any statement that modifies rows.',
|
|
790
|
+
BUILDER_CREDENTIAL],
|
|
782
791
|
pathPolicy: 'project-relative', surfaces: { cli: true, mcpExclusion: DESTRUCTIVE_STATE },
|
|
783
792
|
},
|
|
793
|
+
{
|
|
794
|
+
id: 'db.bookmark', command: ['db', 'bookmark'],
|
|
795
|
+
summary: 'Print the current Time Travel bookmark of a deployed application database.',
|
|
796
|
+
description: 'The bookmark names the database as it is now; `xeer db restore --remote --to <bookmark>` '
|
|
797
|
+
+ 'returns to it for 30 days (7 on the Workers Free plan).',
|
|
798
|
+
usage: ['db bookmark [directory] --remote --environment <prod|preview> [--control-url <url>] [--json]'], helpOrder: 345,
|
|
799
|
+
outputProtocol: 'xeer.command.v0', effects: ['network-read'], idempotent: true, reversible: true,
|
|
800
|
+
destructive: false, humanPrerequisites: [BUILDER_CREDENTIAL], pathPolicy: 'project-relative',
|
|
801
|
+
surfaces: { cli: true, mcpExclusion: NOT_EXPOSED_V0 },
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
// Reversible although destructive: the answer carries previousBookmark, and restoring to it
|
|
805
|
+
// brings back everything this restore discarded.
|
|
806
|
+
id: 'db.restore', command: ['db', 'restore'],
|
|
807
|
+
summary: 'Restore a deployed application database to a bookmark or a time.',
|
|
808
|
+
description: 'Replaces the whole database in --environment with its state at the bookmark or ISO time '
|
|
809
|
+
+ '(with a zone) given to --to. Every later write is discarded, including the owner writes; uploaded '
|
|
810
|
+
+ 'media, code and sessions are not touched. The previousBookmark in the result undoes the restore.',
|
|
811
|
+
usage: ['db restore [directory] --remote --environment <prod|preview> --to <bookmark|time> [--control-url <url>] [--json]'],
|
|
812
|
+
helpOrder: 346, outputProtocol: 'xeer.command.v0', effects: ['network-write', 'write-state'], idempotent: false,
|
|
813
|
+
reversible: true, destructive: true, humanPrerequisites: [BUILDER_CREDENTIAL], pathPolicy: 'project-relative',
|
|
814
|
+
surfaces: { cli: true, mcpExclusion: DESTRUCTIVE_STATE },
|
|
815
|
+
},
|
|
784
816
|
];
|
|
785
817
|
export const XEER_ACTIONS = Object.freeze(actions);
|
|
786
818
|
export function actionDefinition(id) {
|
package/vendor/spec/admin-sql.js
CHANGED
|
@@ -24,8 +24,13 @@
|
|
|
24
24
|
* change enforcement at all.
|
|
25
25
|
*/
|
|
26
26
|
import { ADMIN_PROTOCOL } from './admin.js';
|
|
27
|
-
/**
|
|
28
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Statements that only read. A strict allowlist of leading verbs: `WITH` and `EXPLAIN` are not here
|
|
29
|
+
* because the verb after them decides what they do — `WITH x AS (SELECT 1) UPDATE posts …` writes
|
|
30
|
+
* without `--write`, and `EXPLAIN PRAGMA ignore_check_constraints=ON` sets the pragma while the
|
|
31
|
+
* statement is prepared.
|
|
32
|
+
*/
|
|
33
|
+
const READ_VERBS = new Set(['SELECT', 'VALUES']);
|
|
29
34
|
const WRITE_VERBS = new Set(['INSERT', 'UPDATE', 'DELETE', 'REPLACE']);
|
|
30
35
|
/**
|
|
31
36
|
* Pragmas that only report. An allowlist rather than a denylist, because the case that must never
|
|
@@ -357,6 +357,12 @@ export const DIAGNOSTIC_DEFINITIONS = [
|
|
|
357
357
|
define('XE1841', 'The running server published no admin session secret, so its database surface is '
|
|
358
358
|
+ 'not reachable. The secret is minted per run and shared through the state lease.', 'Restart the server with a build that publishes one; a server older than this CLI does not.', ['db']),
|
|
359
359
|
define('XE1842', 'The server answered the database request with a protocol this CLI does not know.', 'The CLI and the running server are different versions. Restart the server from this checkout.', ['db']),
|
|
360
|
+
define('XE1843', 'The control plane refused, or did not complete, a `xeer db --remote` request.', 'Read `message` and `hint`: they carry the refusal verbatim. `sql_error` means the whole batch was '
|
|
361
|
+
+ 'rolled back; an unknown outcome means it may have been applied, so read before running writes again.', ['db']),
|
|
362
|
+
define('XE1844', 'The control plane returned a database response the CLI will not trust.', 'Retry the read; if it persists the control plane and CLI versions are incompatible.', ['db']),
|
|
363
|
+
define('XE1845', 'A `xeer db --remote` invocation is wrong: no --remote, no --environment, a --state '
|
|
364
|
+
+ 'alongside it, an unreadable --file, a bad --to, or no linked application.', 'Pass --remote --environment prod|preview in the project directory. A --file batch is a JSON array '
|
|
365
|
+
+ 'of {"sql","params"}; --to takes a bookmark or an ISO time with a zone.', ['db']),
|
|
360
366
|
define('XE1821', 'The export file could not be read or written.', 'Read `message`: usually a missing path or permissions. `--out` creates parent directories.', ['export', 'import']),
|
|
361
367
|
define('XE1822', 'The document is not a usable xeer.state-export.v0 export: wrong protocol, or it '
|
|
362
368
|
+ 'contradicts itself (counts, schema, or an encoded value).', 'Import the unmodified file `xeer export --out` produced. `detail.code` names the exact defect.', ['export', 'import']),
|
|
@@ -34,6 +34,15 @@ export declare const templateDistributionEntrySchema: z.ZodObject<{
|
|
|
34
34
|
url: z.ZodString;
|
|
35
35
|
sha256: z.ZodString;
|
|
36
36
|
}, z.core.$strict>;
|
|
37
|
+
admin: z.ZodOptional<z.ZodObject<{
|
|
38
|
+
path: z.ZodString;
|
|
39
|
+
signIn: z.ZodEnum<{
|
|
40
|
+
code: "code";
|
|
41
|
+
owner: "owner";
|
|
42
|
+
}>;
|
|
43
|
+
codeSecret: z.ZodOptional<z.ZodString>;
|
|
44
|
+
tasks: z.ZodString;
|
|
45
|
+
}, z.core.$strict>>;
|
|
37
46
|
author: z.ZodObject<{
|
|
38
47
|
name: z.ZodString;
|
|
39
48
|
url: z.ZodOptional<z.ZodString>;
|
|
@@ -66,6 +75,9 @@ export declare const templateDistributionEntrySchema: z.ZodObject<{
|
|
|
66
75
|
astro: "astro";
|
|
67
76
|
sveltekit: "sveltekit";
|
|
68
77
|
}>>;
|
|
78
|
+
integration: z.ZodOptional<z.ZodEnum<{
|
|
79
|
+
emdash: "emdash";
|
|
80
|
+
}>>;
|
|
69
81
|
}, z.core.$strict>>;
|
|
70
82
|
}, z.core.$strict>;
|
|
71
83
|
export type TemplateDistributionEntryV0 = z.infer<typeof templateDistributionEntrySchema>;
|
|
@@ -87,6 +99,15 @@ export declare const templateDistributionCatalogSchema: z.ZodObject<{
|
|
|
87
99
|
url: z.ZodString;
|
|
88
100
|
sha256: z.ZodString;
|
|
89
101
|
}, z.core.$strict>;
|
|
102
|
+
admin: z.ZodOptional<z.ZodObject<{
|
|
103
|
+
path: z.ZodString;
|
|
104
|
+
signIn: z.ZodEnum<{
|
|
105
|
+
code: "code";
|
|
106
|
+
owner: "owner";
|
|
107
|
+
}>;
|
|
108
|
+
codeSecret: z.ZodOptional<z.ZodString>;
|
|
109
|
+
tasks: z.ZodString;
|
|
110
|
+
}, z.core.$strict>>;
|
|
90
111
|
author: z.ZodObject<{
|
|
91
112
|
name: z.ZodString;
|
|
92
113
|
url: z.ZodOptional<z.ZodString>;
|
|
@@ -119,6 +140,9 @@ export declare const templateDistributionCatalogSchema: z.ZodObject<{
|
|
|
119
140
|
astro: "astro";
|
|
120
141
|
sveltekit: "sveltekit";
|
|
121
142
|
}>>;
|
|
143
|
+
integration: z.ZodOptional<z.ZodEnum<{
|
|
144
|
+
emdash: "emdash";
|
|
145
|
+
}>>;
|
|
122
146
|
}, z.core.$strict>>;
|
|
123
147
|
}, z.core.$strict>>;
|
|
124
148
|
}, z.core.$strict>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { FRAMEWORK_NAMES } from './scaffold-names.js';
|
|
3
|
-
import { checkTemplateMetadataUniqueness, templateAltTextSchema, templateIdentityShape, templateMetadataShape, templateSlugSchema, } from './template.js';
|
|
2
|
+
import { FRAMEWORK_NAMES, INTEGRATION_NAMES } from './scaffold-names.js';
|
|
3
|
+
import { checkTemplateMetadataUniqueness, templateAdminPathSchema, templateAdminTasksSchema, templateAltTextSchema, templateIdentityShape, templateMetadataShape, templateSlugSchema, } from './template.js';
|
|
4
4
|
export const TEMPLATE_DISTRIBUTION_CATALOG_FORMAT = 'xeer.template-distribution-catalog.v0';
|
|
5
5
|
export const TEMPLATE_DISTRIBUTION_CATALOG_SCHEMA_URL = 'https://docs.xeer.run/template-distribution-catalog-v0.schema.json';
|
|
6
6
|
/** Exact semantic version: no range operator, no `v` prefix, no wildcard. */
|
|
@@ -40,7 +40,12 @@ export const TEMPLATE_PROJECT_KINDS = ['xeer-application', 'framework'];
|
|
|
40
40
|
const templateProjectSchema = z.strictObject({
|
|
41
41
|
kind: z.enum(TEMPLATE_PROJECT_KINDS),
|
|
42
42
|
framework: z.enum(FRAMEWORK_NAMES).optional(),
|
|
43
|
+
/** Copied from the framework configuration; EmDash is the only integration, and it runs on Astro. */
|
|
44
|
+
integration: z.enum(INTEGRATION_NAMES).optional(),
|
|
43
45
|
}).superRefine((project, ctx) => {
|
|
46
|
+
if (project.integration === 'emdash' && project.framework !== 'astro') {
|
|
47
|
+
ctx.addIssue({ code: 'custom', path: ['integration'], message: 'the emdash integration runs on the astro framework' });
|
|
48
|
+
}
|
|
44
49
|
if ((project.framework !== undefined) === (project.kind === 'framework'))
|
|
45
50
|
return;
|
|
46
51
|
ctx.addIssue({
|
|
@@ -51,6 +56,17 @@ const templateProjectSchema = z.strictObject({
|
|
|
51
56
|
: `only a framework project names a framework; a ${project.kind} project does not`,
|
|
52
57
|
});
|
|
53
58
|
});
|
|
59
|
+
/**
|
|
60
|
+
* The Site admin an entry resolves to: an owner dashboard unlocked by one of the entry's invite codes
|
|
61
|
+
* (`code`), or EmDash's admin, which the owner opens through the platform's owner sign-in (`owner`).
|
|
62
|
+
* Authored in `template.json` for the first, derived from the EmDash integration for the second.
|
|
63
|
+
*/
|
|
64
|
+
const distributedAdminSchema = z.strictObject({
|
|
65
|
+
path: templateAdminPathSchema,
|
|
66
|
+
signIn: z.enum(['code', 'owner']),
|
|
67
|
+
codeSecret: z.string().optional(),
|
|
68
|
+
tasks: templateAdminTasksSchema,
|
|
69
|
+
});
|
|
54
70
|
/**
|
|
55
71
|
* One published template. The authored metadata is copied verbatim from `template.json`; everything
|
|
56
72
|
* else is produced by the release job that packed, deployed, and verified this exact artifact.
|
|
@@ -77,7 +93,29 @@ export const templateDistributionEntrySchema = z.strictObject({
|
|
|
77
93
|
/** Release version of the template artifact itself, independent of the platform version. */
|
|
78
94
|
artifactVersion: exactVersion,
|
|
79
95
|
sourceArchive: sourceArchiveSchema,
|
|
80
|
-
|
|
96
|
+
/** Absent for a template without an admin, and on every entry published before the field existed. */
|
|
97
|
+
admin: distributedAdminSchema.optional(),
|
|
98
|
+
}).superRefine((entry, ctx) => {
|
|
99
|
+
checkTemplateMetadataUniqueness(entry, ctx);
|
|
100
|
+
const { admin } = entry;
|
|
101
|
+
const emdash = entry.project?.integration === 'emdash';
|
|
102
|
+
if (emdash && admin?.signIn !== 'owner') {
|
|
103
|
+
ctx.addIssue({ code: 'custom', path: ['admin'], message: 'an emdash project carries the owner admin' });
|
|
104
|
+
}
|
|
105
|
+
if (!admin)
|
|
106
|
+
return;
|
|
107
|
+
if (admin.signIn === 'owner' && !emdash) {
|
|
108
|
+
ctx.addIssue({ code: 'custom', path: ['admin', 'signIn'], message: 'the owner sign-in belongs to an emdash project' });
|
|
109
|
+
}
|
|
110
|
+
if ((admin.codeSecret !== undefined) !== (admin.signIn === 'code')) {
|
|
111
|
+
ctx.addIssue({ code: 'custom', path: ['admin', 'codeSecret'],
|
|
112
|
+
message: 'a code admin names its codeSecret, and only a code admin does' });
|
|
113
|
+
}
|
|
114
|
+
else if (admin.codeSecret !== undefined && !entry.requiredSecrets.some((secret) => secret.name === admin.codeSecret)) {
|
|
115
|
+
ctx.addIssue({ code: 'custom', path: ['admin', 'codeSecret'],
|
|
116
|
+
message: 'admin codeSecret must name one of the entry requiredSecrets' });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
81
119
|
/**
|
|
82
120
|
* Versioned distribution catalog published atomically after a platform release. Entries are keyed by
|
|
83
121
|
* template id, sorted by codepoint so a regenerated catalog is byte-stable, and a template with no
|
|
@@ -3,6 +3,13 @@ export declare const TEMPLATE_FORMAT: "xeer.template.v0";
|
|
|
3
3
|
export declare const TEMPLATE_MANIFEST_SCHEMA_URL: "https://docs.xeer.run/template-v0.schema.json";
|
|
4
4
|
/** Catalog identifier shape shared by template directory names and categories. */
|
|
5
5
|
export declare const templateSlugSchema: z.ZodString;
|
|
6
|
+
/**
|
|
7
|
+
* Where a template's own admin lives, relative to the site's origin: one absolute path, nothing that
|
|
8
|
+
* could leave the origin (`//host`) or carry a query or fragment.
|
|
9
|
+
*/
|
|
10
|
+
export declare const templateAdminPathSchema: z.ZodString;
|
|
11
|
+
/** What the owner does in the admin, shown next to its button: one short sentence. */
|
|
12
|
+
export declare const templateAdminTasksSchema: z.ZodString;
|
|
6
13
|
/** Alt text carried by a gallery image in either the source or the distribution catalog. */
|
|
7
14
|
export declare const templateAltTextSchema: z.ZodString;
|
|
8
15
|
/**
|
|
@@ -50,6 +57,12 @@ export declare function checkTemplateMetadataUniqueness(metadata: {
|
|
|
50
57
|
}[];
|
|
51
58
|
}, ctx: z.RefinementCtx): void;
|
|
52
59
|
export declare const templateManifestSchema: z.ZodObject<{
|
|
60
|
+
admin: z.ZodOptional<z.ZodObject<{
|
|
61
|
+
path: z.ZodOptional<z.ZodString>;
|
|
62
|
+
signIn: z.ZodOptional<z.ZodLiteral<"code">>;
|
|
63
|
+
codeSecret: z.ZodOptional<z.ZodString>;
|
|
64
|
+
tasks: z.ZodOptional<z.ZodString>;
|
|
65
|
+
}, z.core.$strict>>;
|
|
53
66
|
author: z.ZodObject<{
|
|
54
67
|
name: z.ZodString;
|
|
55
68
|
url: z.ZodOptional<z.ZodString>;
|
package/vendor/spec/template.js
CHANGED
|
@@ -18,6 +18,27 @@ const requiredSecretSchema = z.strictObject({
|
|
|
18
18
|
name: environmentName,
|
|
19
19
|
description: trimmed(200),
|
|
20
20
|
});
|
|
21
|
+
/**
|
|
22
|
+
* Where a template's own admin lives, relative to the site's origin: one absolute path, nothing that
|
|
23
|
+
* could leave the origin (`//host`) or carry a query or fragment.
|
|
24
|
+
*/
|
|
25
|
+
export const templateAdminPathSchema = z.string().max(200)
|
|
26
|
+
.regex(/^\/(?!\/)[^\s?#\\]*$/u, 'admin path must be a plain site path starting with one "/"');
|
|
27
|
+
/** What the owner does in the admin, shown next to its button: one short sentence. */
|
|
28
|
+
export const templateAdminTasksSchema = trimmed(140)
|
|
29
|
+
.regex(/^[^.!?]*(?:[.!?](?! )[^.!?]*)*\.$/u, 'admin tasks must be one short sentence ending in a full stop');
|
|
30
|
+
/**
|
|
31
|
+
* The Site admin a template offers. An application template authors all of it: the path of its owner
|
|
32
|
+
* dashboard, and the invite code (one of its `requiredSecrets`) that unlocks it. An EmDash template's
|
|
33
|
+
* admin is derived from its integration by the catalog, so there only `tasks` may be authored; which
|
|
34
|
+
* of the two applies is known only to the catalog generator, which reads the root contract.
|
|
35
|
+
*/
|
|
36
|
+
const templateAdminSchema = z.strictObject({
|
|
37
|
+
path: templateAdminPathSchema.optional(),
|
|
38
|
+
signIn: z.literal('code').optional(),
|
|
39
|
+
codeSecret: environmentName.optional(),
|
|
40
|
+
tasks: templateAdminTasksSchema.optional(),
|
|
41
|
+
});
|
|
21
42
|
/** Alt text carried by a gallery image in either the source or the distribution catalog. */
|
|
22
43
|
export const templateAltTextSchema = trimmed(200);
|
|
23
44
|
const screenshotSchema = z.strictObject({
|
|
@@ -70,7 +91,21 @@ export const templateManifestSchema = z.strictObject({
|
|
|
70
91
|
...templateMetadataShape,
|
|
71
92
|
screenshots: z.array(screenshotSchema).min(1).max(8),
|
|
72
93
|
...templateIdentityShape,
|
|
73
|
-
|
|
94
|
+
admin: templateAdminSchema.optional(),
|
|
95
|
+
}).superRefine((manifest, ctx) => {
|
|
96
|
+
checkTemplateMetadataUniqueness(manifest, ctx);
|
|
97
|
+
const { admin } = manifest;
|
|
98
|
+
if (!admin)
|
|
99
|
+
return;
|
|
100
|
+
const code = [admin.path, admin.signIn, admin.codeSecret];
|
|
101
|
+
if (code.some((value) => value !== undefined) && code.some((value) => value === undefined)) {
|
|
102
|
+
ctx.addIssue({ code: 'custom', path: ['admin'], message: 'admin needs path, signIn, and codeSecret together' });
|
|
103
|
+
}
|
|
104
|
+
if (admin.codeSecret !== undefined && !manifest.requiredSecrets.some((secret) => secret.name === admin.codeSecret)) {
|
|
105
|
+
ctx.addIssue({ code: 'custom', path: ['admin', 'codeSecret'],
|
|
106
|
+
message: 'admin codeSecret must name one of the template requiredSecrets' });
|
|
107
|
+
}
|
|
108
|
+
});
|
|
74
109
|
const generatedTemplateManifestJsonSchema = z.toJSONSchema(templateManifestSchema);
|
|
75
110
|
const generatedProperties = generatedTemplateManifestJsonSchema.properties;
|
|
76
111
|
export const templateManifestJsonSchema = Object.freeze({
|