@wootsup/yt-builder-mcp 1.0.0 → 1.1.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 +18 -6
- package/bin/yt-builder-mcp.js +6 -0
- package/dist/auth.d.ts +86 -6
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +141 -8
- package/dist/auth.js.map +1 -1
- package/dist/errors/sanitize.d.ts.map +1 -1
- package/dist/errors/sanitize.js +10 -0
- package/dist/errors/sanitize.js.map +1 -1
- package/dist/gateway/essentials.d.ts +1 -1
- package/dist/gateway/essentials.d.ts.map +1 -1
- package/dist/gateway/essentials.js +15 -2
- package/dist/gateway/essentials.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +52 -16
- package/dist/index.js.map +1 -1
- package/dist/install-skill.d.ts +10 -2
- package/dist/install-skill.d.ts.map +1 -1
- package/dist/install-skill.js +18 -7
- package/dist/install-skill.js.map +1 -1
- package/dist/platform/detect.d.ts +64 -0
- package/dist/platform/detect.d.ts.map +1 -0
- package/dist/platform/detect.js +98 -0
- package/dist/platform/detect.js.map +1 -0
- package/dist/platform/index.d.ts +72 -0
- package/dist/platform/index.d.ts.map +1 -1
- package/dist/platform/index.js +138 -0
- package/dist/platform/index.js.map +1 -1
- package/dist/platform/joomla.d.ts +44 -0
- package/dist/platform/joomla.d.ts.map +1 -0
- package/dist/platform/joomla.js +46 -0
- package/dist/platform/joomla.js.map +1 -0
- package/dist/server.d.ts +61 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +86 -2
- package/dist/server.js.map +1 -1
- package/dist/setup-cli-confirm.d.ts +36 -0
- package/dist/setup-cli-confirm.d.ts.map +1 -0
- package/dist/setup-cli-confirm.js +59 -0
- package/dist/setup-cli-confirm.js.map +1 -0
- package/dist/setup-cli.d.ts +53 -1
- package/dist/setup-cli.d.ts.map +1 -1
- package/dist/setup-cli.js +525 -10
- package/dist/setup-cli.js.map +1 -1
- package/dist/setup-prompts.d.ts.map +1 -1
- package/dist/setup-prompts.js +12 -8
- package/dist/setup-prompts.js.map +1 -1
- package/dist/setup-wizard-types.d.ts +19 -3
- package/dist/setup-wizard-types.d.ts.map +1 -1
- package/dist/setup-wizard.d.ts.map +1 -1
- package/dist/setup-wizard.js +34 -1
- package/dist/setup-wizard.js.map +1 -1
- package/dist/sites/cli/add-site.d.ts +106 -0
- package/dist/sites/cli/add-site.d.ts.map +1 -0
- package/dist/sites/cli/add-site.js +185 -0
- package/dist/sites/cli/add-site.js.map +1 -0
- package/dist/sites/cli/list-sites.d.ts +40 -0
- package/dist/sites/cli/list-sites.d.ts.map +1 -0
- package/dist/sites/cli/list-sites.js +117 -0
- package/dist/sites/cli/list-sites.js.map +1 -0
- package/dist/sites/cli/remove-site.d.ts +60 -0
- package/dist/sites/cli/remove-site.d.ts.map +1 -0
- package/dist/sites/cli/remove-site.js +120 -0
- package/dist/sites/cli/remove-site.js.map +1 -0
- package/dist/sites/cli/set-default.d.ts +47 -0
- package/dist/sites/cli/set-default.d.ts.map +1 -0
- package/dist/sites/cli/set-default.js +69 -0
- package/dist/sites/cli/set-default.js.map +1 -0
- package/dist/sites/cli/test-site.d.ts +77 -0
- package/dist/sites/cli/test-site.d.ts.map +1 -0
- package/dist/sites/cli/test-site.js +146 -0
- package/dist/sites/cli/test-site.js.map +1 -0
- package/dist/sites/client-pool.d.ts +124 -0
- package/dist/sites/client-pool.d.ts.map +1 -0
- package/dist/sites/client-pool.js +172 -0
- package/dist/sites/client-pool.js.map +1 -0
- package/dist/sites/env-bridge.d.ts +55 -0
- package/dist/sites/env-bridge.d.ts.map +1 -0
- package/dist/sites/env-bridge.js +94 -0
- package/dist/sites/env-bridge.js.map +1 -0
- package/dist/sites/paths.d.ts +21 -0
- package/dist/sites/paths.d.ts.map +1 -0
- package/dist/sites/paths.js +37 -0
- package/dist/sites/paths.js.map +1 -0
- package/dist/sites/probe.d.ts +60 -0
- package/dist/sites/probe.d.ts.map +1 -0
- package/dist/sites/probe.js +112 -0
- package/dist/sites/probe.js.map +1 -0
- package/dist/sites/registry.d.ts +142 -0
- package/dist/sites/registry.d.ts.map +1 -0
- package/dist/sites/registry.js +163 -0
- package/dist/sites/registry.js.map +1 -0
- package/dist/sites/schema.d.ts +90 -0
- package/dist/sites/schema.d.ts.map +1 -0
- package/dist/sites/schema.js +71 -0
- package/dist/sites/schema.js.map +1 -0
- package/dist/sites/secret-resolver.d.ts +109 -0
- package/dist/sites/secret-resolver.d.ts.map +1 -0
- package/dist/sites/secret-resolver.js +189 -0
- package/dist/sites/secret-resolver.js.map +1 -0
- package/dist/sites/store.d.ts +82 -0
- package/dist/sites/store.d.ts.map +1 -0
- package/dist/sites/store.js +198 -0
- package/dist/sites/store.js.map +1 -0
- package/dist/sites/tools/index.d.ts +28 -0
- package/dist/sites/tools/index.d.ts.map +1 -0
- package/dist/sites/tools/index.js +30 -0
- package/dist/sites/tools/index.js.map +1 -0
- package/dist/sites/tools/sites-list.d.ts +48 -0
- package/dist/sites/tools/sites-list.d.ts.map +1 -0
- package/dist/sites/tools/sites-list.js +135 -0
- package/dist/sites/tools/sites-list.js.map +1 -0
- package/dist/sites/tools/sites-test.d.ts +47 -0
- package/dist/sites/tools/sites-test.d.ts.map +1 -0
- package/dist/sites/tools/sites-test.js +148 -0
- package/dist/sites/tools/sites-test.js.map +1 -0
- package/dist/skill-loader.d.ts +63 -3
- package/dist/skill-loader.d.ts.map +1 -1
- package/dist/skill-loader.js +123 -10
- package/dist/skill-loader.js.map +1 -1
- package/dist/tools/elements/builders.d.ts +2 -2
- package/dist/tools/elements/builders.d.ts.map +1 -1
- package/dist/tools/elements/builders.js +45 -23
- package/dist/tools/elements/builders.js.map +1 -1
- package/dist/tools/elements/handlers.d.ts +8 -1
- package/dist/tools/elements/handlers.d.ts.map +1 -1
- package/dist/tools/elements/handlers.js +37 -1
- package/dist/tools/elements/handlers.js.map +1 -1
- package/dist/tools/format/health-format.d.ts +12 -6
- package/dist/tools/format/health-format.d.ts.map +1 -1
- package/dist/tools/format/health-format.js +14 -11
- package/dist/tools/format/health-format.js.map +1 -1
- package/dist/tools/format/pages-format.d.ts +7 -1
- package/dist/tools/format/pages-format.d.ts.map +1 -1
- package/dist/tools/format/pages-format.js +25 -1
- package/dist/tools/format/pages-format.js.map +1 -1
- package/dist/tools/health.d.ts +2 -2
- package/dist/tools/health.d.ts.map +1 -1
- package/dist/tools/health.js +120 -47
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/index.d.ts +9 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +21 -7
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/inspection.d.ts +2 -2
- package/dist/tools/inspection.d.ts.map +1 -1
- package/dist/tools/inspection.js +67 -22
- package/dist/tools/inspection.js.map +1 -1
- package/dist/tools/multi-items/builders.d.ts +2 -2
- package/dist/tools/multi-items/builders.d.ts.map +1 -1
- package/dist/tools/multi-items/builders.js +21 -8
- package/dist/tools/multi-items/builders.js.map +1 -1
- package/dist/tools/pages/builders.d.ts +2 -2
- package/dist/tools/pages/builders.d.ts.map +1 -1
- package/dist/tools/pages/builders.js +36 -24
- package/dist/tools/pages/builders.js.map +1 -1
- package/dist/tools/pages/handlers-read.d.ts.map +1 -1
- package/dist/tools/pages/handlers-read.js +10 -0
- package/dist/tools/pages/handlers-read.js.map +1 -1
- package/dist/tools/pages/schemas.d.ts.map +1 -1
- package/dist/tools/pages/schemas.js +18 -0
- package/dist/tools/pages/schemas.js.map +1 -1
- package/dist/tools/pool-resolve-helper.d.ts +83 -0
- package/dist/tools/pool-resolve-helper.d.ts.map +1 -0
- package/dist/tools/pool-resolve-helper.js +148 -0
- package/dist/tools/pool-resolve-helper.js.map +1 -0
- package/dist/tools/shared-schemas.d.ts +10 -0
- package/dist/tools/shared-schemas.d.ts.map +1 -1
- package/dist/tools/shared-schemas.js +20 -2
- package/dist/tools/shared-schemas.js.map +1 -1
- package/dist/tools/sources/builders.d.ts +2 -2
- package/dist/tools/sources/builders.d.ts.map +1 -1
- package/dist/tools/sources/builders.js +27 -15
- package/dist/tools/sources/builders.js.map +1 -1
- package/dist/tools/tool-builder/index.d.ts +1 -1
- package/dist/tools/tool-builder/index.d.ts.map +1 -1
- package/dist/tools/tool-builder/index.js +1 -1
- package/dist/tools/tool-builder/index.js.map +1 -1
- package/dist/tools/tool-builder/results.d.ts +34 -0
- package/dist/tools/tool-builder/results.d.ts.map +1 -1
- package/dist/tools/tool-builder/results.js +86 -0
- package/dist/tools/tool-builder/results.js.map +1 -1
- package/manifest.json +27 -10
- package/package.json +3 -1
- package/skills/yt-builder-mcp/SKILL.md +846 -0
- package/skills/yootheme-builder/SKILL.md +0 -587
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W9 — `list-sites` CLI subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Reads `sites.json`, prints a tabular view matching the structure of
|
|
5
|
+
* the `yootheme_builder_sites_list` MCP tool. NEVER reaches for the
|
|
6
|
+
* secret-resolver — the table only exposes `bearer_source: 'plain' |
|
|
7
|
+
* 'op'` so an in-progress 1Password unlock cannot wedge the CLI.
|
|
8
|
+
*
|
|
9
|
+
* Output contract (verbatim columns, in order):
|
|
10
|
+
* SITE_ID URL PLATFORM DEFAULT LABEL BEARER
|
|
11
|
+
*
|
|
12
|
+
* Empty registry prints "(no sites configured)" + a discovery hint
|
|
13
|
+
* pointing at `add-site`. The exit code is always 0 on a successful
|
|
14
|
+
* read — even an empty registry is a normal state, not an error.
|
|
15
|
+
*
|
|
16
|
+
* @license MIT
|
|
17
|
+
*/
|
|
18
|
+
import type { SitesFileT } from '../schema.js';
|
|
19
|
+
/** Test-injection seam — production callers use the defaults below. */
|
|
20
|
+
export interface ListSitesDeps {
|
|
21
|
+
readonly load: (path: string) => Promise<SitesFileT>;
|
|
22
|
+
readonly log?: (line: string) => void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Render the sites registry to stdout. Returns the rendered lines so
|
|
26
|
+
* tests can assert without parsing captured stdout; the caller is
|
|
27
|
+
* still expected to print them via `deps.log` for the live CLI path.
|
|
28
|
+
*/
|
|
29
|
+
export declare function listSitesCommand(path: string, deps: ListSitesDeps): Promise<readonly string[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Pure renderer — exported for direct test coverage of the layout.
|
|
32
|
+
* Layout: fixed-width left-padded columns, header on row 0, divider on
|
|
33
|
+
* row 1, one row per site. Last line always carries the discovery hint
|
|
34
|
+
* (or empty-registry hint) so a Maria-class user always sees the next
|
|
35
|
+
* step.
|
|
36
|
+
*/
|
|
37
|
+
export declare function renderSitesTable(file: SitesFileT): readonly string[];
|
|
38
|
+
/** Default deps wired to the real store. */
|
|
39
|
+
export declare const DEFAULT_LIST_SITES_DEPS: ListSitesDeps;
|
|
40
|
+
//# sourceMappingURL=list-sites.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-sites.d.ts","sourceRoot":"","sources":["../../../src/sites/cli/list-sites.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,uEAAuE;AACvE,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACrD,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAClC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,aAAa,GACpB,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAO5B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,MAAM,EAAE,CA+EpE;AAED,4CAA4C;AAC5C,eAAO,MAAM,uBAAuB,EAAE,aAErC,CAAC"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W9 — `list-sites` CLI subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Reads `sites.json`, prints a tabular view matching the structure of
|
|
5
|
+
* the `yootheme_builder_sites_list` MCP tool. NEVER reaches for the
|
|
6
|
+
* secret-resolver — the table only exposes `bearer_source: 'plain' |
|
|
7
|
+
* 'op'` so an in-progress 1Password unlock cannot wedge the CLI.
|
|
8
|
+
*
|
|
9
|
+
* Output contract (verbatim columns, in order):
|
|
10
|
+
* SITE_ID URL PLATFORM DEFAULT LABEL BEARER
|
|
11
|
+
*
|
|
12
|
+
* Empty registry prints "(no sites configured)" + a discovery hint
|
|
13
|
+
* pointing at `add-site`. The exit code is always 0 on a successful
|
|
14
|
+
* read — even an empty registry is a normal state, not an error.
|
|
15
|
+
*
|
|
16
|
+
* @license MIT
|
|
17
|
+
*/
|
|
18
|
+
import { SiteRegistry } from '../registry.js';
|
|
19
|
+
import { loadSitesFile } from '../store.js';
|
|
20
|
+
/**
|
|
21
|
+
* Render the sites registry to stdout. Returns the rendered lines so
|
|
22
|
+
* tests can assert without parsing captured stdout; the caller is
|
|
23
|
+
* still expected to print them via `deps.log` for the live CLI path.
|
|
24
|
+
*/
|
|
25
|
+
export async function listSitesCommand(path, deps) {
|
|
26
|
+
const file = await deps.load(path);
|
|
27
|
+
const lines = renderSitesTable(file);
|
|
28
|
+
if (deps.log !== undefined) {
|
|
29
|
+
for (const line of lines)
|
|
30
|
+
deps.log(line);
|
|
31
|
+
}
|
|
32
|
+
return lines;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Pure renderer — exported for direct test coverage of the layout.
|
|
36
|
+
* Layout: fixed-width left-padded columns, header on row 0, divider on
|
|
37
|
+
* row 1, one row per site. Last line always carries the discovery hint
|
|
38
|
+
* (or empty-registry hint) so a Maria-class user always sees the next
|
|
39
|
+
* step.
|
|
40
|
+
*/
|
|
41
|
+
export function renderSitesTable(file) {
|
|
42
|
+
if (file.sites.length === 0) {
|
|
43
|
+
return [
|
|
44
|
+
'(no sites configured)',
|
|
45
|
+
'',
|
|
46
|
+
'Add one with: yt-builder-mcp add-site --url https://example.com --token <bearer>',
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
// Build a registry purely so we can reuse listForDisplay's shape.
|
|
50
|
+
// The platform probe is never triggered because this CLI never
|
|
51
|
+
// calls platformFor().
|
|
52
|
+
const registry = new SiteRegistry(file);
|
|
53
|
+
const rows = registry.listForDisplay();
|
|
54
|
+
const defaultSiteId = registry.defaultSiteId();
|
|
55
|
+
// W12-R1.3 — bearer_source column dropped for consistency with the
|
|
56
|
+
// MCP `sites_list` tool + the `sites://current` resource. The CLI
|
|
57
|
+
// never needed it (the operator already knows which form they
|
|
58
|
+
// chose); keeping it here would be a divergence surface only.
|
|
59
|
+
const COLUMNS = [
|
|
60
|
+
{ key: 'site_id', label: 'SITE_ID', width: 20 },
|
|
61
|
+
{ key: 'url', label: 'URL', width: 36 },
|
|
62
|
+
{ key: 'platform', label: 'PLATFORM', width: 11 },
|
|
63
|
+
{ key: 'default', label: 'DEFAULT', width: 9 },
|
|
64
|
+
{ key: 'label', label: 'LABEL', width: 24 },
|
|
65
|
+
];
|
|
66
|
+
function pad(text, width) {
|
|
67
|
+
if (text.length >= width)
|
|
68
|
+
return text.slice(0, width);
|
|
69
|
+
return text + ' '.repeat(width - text.length);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* W12-R1.3 (A2-L2): strip ANSI escape codes + control characters
|
|
73
|
+
* from operator-provided strings before printing. A malicious or
|
|
74
|
+
* accidentally-pasted label could otherwise repaint the terminal
|
|
75
|
+
* ("\x1b[2J\x1b[H", "\r" cursor reset, etc.) and hide other rows.
|
|
76
|
+
* Replaces all ESC + C0 control bytes with `?` so the cell stays
|
|
77
|
+
* fixed-width.
|
|
78
|
+
*/
|
|
79
|
+
function sanitiseForTerminal(text) {
|
|
80
|
+
// eslint-disable-next-line no-control-regex -- defense-in-depth strip
|
|
81
|
+
return text.replace(/[\x00-\x1f\x7f]/g, '?');
|
|
82
|
+
}
|
|
83
|
+
const header = COLUMNS.map((c) => pad(c.label, c.width)).join(' ');
|
|
84
|
+
const divider = COLUMNS.map((c) => '-'.repeat(c.width)).join(' ');
|
|
85
|
+
const dataLines = rows.map((row) => {
|
|
86
|
+
const platform = row.platform_resolved ?? row.platform_hint;
|
|
87
|
+
const def = row.is_default ? 'yes' : 'no';
|
|
88
|
+
const label = sanitiseForTerminal(row.label ?? '');
|
|
89
|
+
const cells = [
|
|
90
|
+
pad(row.site_id, COLUMNS[0].width),
|
|
91
|
+
pad(row.url, COLUMNS[1].width),
|
|
92
|
+
pad(platform, COLUMNS[2].width),
|
|
93
|
+
pad(def, COLUMNS[3].width),
|
|
94
|
+
pad(label, COLUMNS[4].width),
|
|
95
|
+
];
|
|
96
|
+
return cells.join(' ');
|
|
97
|
+
});
|
|
98
|
+
const total = rows.length;
|
|
99
|
+
const summary = `${String(total)} configured site${total === 1 ? '' : 's'}` +
|
|
100
|
+
(defaultSiteId !== null
|
|
101
|
+
? ` (default: ${defaultSiteId})`
|
|
102
|
+
: ' (no default configured)');
|
|
103
|
+
return [
|
|
104
|
+
summary,
|
|
105
|
+
'',
|
|
106
|
+
header,
|
|
107
|
+
divider,
|
|
108
|
+
...dataLines,
|
|
109
|
+
'',
|
|
110
|
+
'Use `yt-builder-mcp test-site <site_id>` to verify connectivity.',
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
/** Default deps wired to the real store. */
|
|
114
|
+
export const DEFAULT_LIST_SITES_DEPS = {
|
|
115
|
+
load: loadSitesFile,
|
|
116
|
+
};
|
|
117
|
+
//# sourceMappingURL=list-sites.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-sites.js","sourceRoot":"","sources":["../../../src/sites/cli/list-sites.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W9 — `remove-site` CLI subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Removes ONE site entry by `site_id`. Auto-promotion policy (per
|
|
5
|
+
* plan §C.3):
|
|
6
|
+
* - Removing a non-default site → plain delete, default_site_id and
|
|
7
|
+
* the other entries' is_default flags are untouched.
|
|
8
|
+
* - Removing the default AND there is at least one other site →
|
|
9
|
+
* promote the next-in-insertion-order site to default
|
|
10
|
+
* (`is_default:true` + `default_site_id` update).
|
|
11
|
+
* - Removing the only site → `default_site_id: null`, empty array.
|
|
12
|
+
*
|
|
13
|
+
* Returns a structured {@link RemoveSiteResult} so the dispatcher can
|
|
14
|
+
* render the "(default → promoted X)" hint and tests can assert
|
|
15
|
+
* promotion behaviour without parsing stdout.
|
|
16
|
+
*
|
|
17
|
+
* `--yes` skips the confirmation hook supplied by the dispatcher.
|
|
18
|
+
* Production callers always wire a confirm hook; tests pass yes=true
|
|
19
|
+
* to bypass it.
|
|
20
|
+
*
|
|
21
|
+
* @license MIT
|
|
22
|
+
*/
|
|
23
|
+
import type { SitesFileT } from '../schema.js';
|
|
24
|
+
export interface RemoveSiteOptions {
|
|
25
|
+
/** Skip confirmation when true. CLI passes through from --yes. */
|
|
26
|
+
readonly yes?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface RemoveSiteResult {
|
|
29
|
+
readonly siteId: string;
|
|
30
|
+
/** Path of the sites.json that was written. */
|
|
31
|
+
readonly path: string;
|
|
32
|
+
/** site_id that was promoted to default (only when removing default + ≥1 other). */
|
|
33
|
+
readonly promoted?: string;
|
|
34
|
+
/** True when the registry became empty after the remove. */
|
|
35
|
+
readonly nowEmpty: boolean;
|
|
36
|
+
/** True when no write happened because --yes was missing on confirm. */
|
|
37
|
+
readonly cancelled: boolean;
|
|
38
|
+
}
|
|
39
|
+
export declare class RemoveSiteError extends Error {
|
|
40
|
+
readonly code: string;
|
|
41
|
+
constructor(code: string, message: string);
|
|
42
|
+
}
|
|
43
|
+
/** Test/IO injection seam. */
|
|
44
|
+
export interface RemoveSiteDeps {
|
|
45
|
+
readonly load: (path: string) => Promise<SitesFileT>;
|
|
46
|
+
readonly save: (path: string, data: SitesFileT) => Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Confirm hook. Production dispatcher wires this to a clack
|
|
49
|
+
* prompt; tests pass `--yes` so this is never invoked.
|
|
50
|
+
*/
|
|
51
|
+
readonly confirm?: (message: string) => Promise<boolean>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Remove a site by `site_id`. See file header for the auto-promotion
|
|
55
|
+
* policy. Throws {@link RemoveSiteError} with code `'UNKNOWN_SITE'`
|
|
56
|
+
* when `siteId` is not in the registry.
|
|
57
|
+
*/
|
|
58
|
+
export declare function removeSiteCommand(siteId: string, path: string, opts: RemoveSiteOptions, deps: RemoveSiteDeps): Promise<RemoveSiteResult>;
|
|
59
|
+
export declare const DEFAULT_REMOVE_SITE_DEPS: RemoveSiteDeps;
|
|
60
|
+
//# sourceMappingURL=remove-site.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remove-site.d.ts","sourceRoot":"","sources":["../../../src/sites/cli/remove-site.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,KAAK,EAAc,UAAU,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,WAAW,iBAAiB;IAC9B,kEAAkE;IAClE,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,4DAA4D;IAC5D,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC/B;AAED,qBAAa,eAAgB,SAAQ,KAAK;aACV,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAI5D;AAED,8BAA8B;AAC9B,MAAM,WAAW,cAAc;IAC3B,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACrD,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC5D;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACnC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,iBAAiB,EACvB,IAAI,EAAE,cAAc,GACrB,OAAO,CAAC,gBAAgB,CAAC,CA8F3B;AAED,eAAO,MAAM,wBAAwB,EAAE,cAGtC,CAAC"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W9 — `remove-site` CLI subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Removes ONE site entry by `site_id`. Auto-promotion policy (per
|
|
5
|
+
* plan §C.3):
|
|
6
|
+
* - Removing a non-default site → plain delete, default_site_id and
|
|
7
|
+
* the other entries' is_default flags are untouched.
|
|
8
|
+
* - Removing the default AND there is at least one other site →
|
|
9
|
+
* promote the next-in-insertion-order site to default
|
|
10
|
+
* (`is_default:true` + `default_site_id` update).
|
|
11
|
+
* - Removing the only site → `default_site_id: null`, empty array.
|
|
12
|
+
*
|
|
13
|
+
* Returns a structured {@link RemoveSiteResult} so the dispatcher can
|
|
14
|
+
* render the "(default → promoted X)" hint and tests can assert
|
|
15
|
+
* promotion behaviour without parsing stdout.
|
|
16
|
+
*
|
|
17
|
+
* `--yes` skips the confirmation hook supplied by the dispatcher.
|
|
18
|
+
* Production callers always wire a confirm hook; tests pass yes=true
|
|
19
|
+
* to bypass it.
|
|
20
|
+
*
|
|
21
|
+
* @license MIT
|
|
22
|
+
*/
|
|
23
|
+
import { saveSitesFile, loadSitesFile } from '../store.js';
|
|
24
|
+
export class RemoveSiteError extends Error {
|
|
25
|
+
code;
|
|
26
|
+
constructor(code, message) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.code = code;
|
|
29
|
+
this.name = 'RemoveSiteError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Remove a site by `site_id`. See file header for the auto-promotion
|
|
34
|
+
* policy. Throws {@link RemoveSiteError} with code `'UNKNOWN_SITE'`
|
|
35
|
+
* when `siteId` is not in the registry.
|
|
36
|
+
*/
|
|
37
|
+
export async function removeSiteCommand(siteId, path, opts, deps) {
|
|
38
|
+
const file = await deps.load(path);
|
|
39
|
+
const index = file.sites.findIndex((s) => s.site_id === siteId);
|
|
40
|
+
if (index < 0) {
|
|
41
|
+
throw new RemoveSiteError('UNKNOWN_SITE', `Site "${siteId}" not found in ${path}. ` +
|
|
42
|
+
(file.sites.length > 0
|
|
43
|
+
? `Available: ${file.sites.map((s) => s.site_id).join(', ')}.`
|
|
44
|
+
: '(no sites configured)'));
|
|
45
|
+
}
|
|
46
|
+
// W12-R2A (A2-H1) — fail-closed guard. Round 1's check silently
|
|
47
|
+
// proceeded when neither `--yes` NOR a confirm hook was supplied
|
|
48
|
+
// (deps.confirm === undefined). That made the destructive op a
|
|
49
|
+
// no-prompt delete in any non-TTY context (CI, piped stdin, MCP
|
|
50
|
+
// server stdio). Tighten to: if `--yes` is not set, a confirm hook
|
|
51
|
+
// is REQUIRED — otherwise throw a typed error so the dispatcher
|
|
52
|
+
// can surface "pass --yes" with a non-zero exit code.
|
|
53
|
+
if (opts.yes !== true) {
|
|
54
|
+
if (deps.confirm === undefined) {
|
|
55
|
+
throw new RemoveSiteError('CONFIRM_REQUIRED', `Remove of site "${siteId}" requires --yes (no interactive `
|
|
56
|
+
+ `confirm available in this context). Re-run with --yes to skip the prompt.`);
|
|
57
|
+
}
|
|
58
|
+
const cont = await deps.confirm(`Remove site "${siteId}" from ${path}?`);
|
|
59
|
+
if (!cont) {
|
|
60
|
+
return {
|
|
61
|
+
siteId,
|
|
62
|
+
path,
|
|
63
|
+
nowEmpty: false,
|
|
64
|
+
cancelled: true,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const wasDefault = file.sites[index]?.is_default === true
|
|
69
|
+
|| file.default_site_id === siteId;
|
|
70
|
+
// Drop the target entry, preserving insertion order.
|
|
71
|
+
const remaining = file.sites.filter((_, i) => i !== index);
|
|
72
|
+
let promoted;
|
|
73
|
+
let nextSites = remaining;
|
|
74
|
+
let nextDefaultId = file.default_site_id;
|
|
75
|
+
if (wasDefault) {
|
|
76
|
+
if (remaining.length === 0) {
|
|
77
|
+
nextDefaultId = null;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
const promote = remaining[0];
|
|
81
|
+
if (promote === undefined) {
|
|
82
|
+
// unreachable due to length check, but keeps TS strict.
|
|
83
|
+
nextDefaultId = null;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
promoted = promote.site_id;
|
|
87
|
+
nextDefaultId = promote.site_id;
|
|
88
|
+
nextSites = remaining.map((s, i) => ({
|
|
89
|
+
...s,
|
|
90
|
+
is_default: i === 0,
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// Make sure default_site_id still points at a present entry.
|
|
97
|
+
if (nextDefaultId !== null
|
|
98
|
+
&& !remaining.some((s) => s.site_id === nextDefaultId)) {
|
|
99
|
+
nextDefaultId = null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const nextFile = {
|
|
103
|
+
schema_version: 1,
|
|
104
|
+
default_site_id: nextDefaultId,
|
|
105
|
+
sites: nextSites,
|
|
106
|
+
};
|
|
107
|
+
await deps.save(path, nextFile);
|
|
108
|
+
return {
|
|
109
|
+
siteId,
|
|
110
|
+
path,
|
|
111
|
+
...(promoted !== undefined ? { promoted } : {}),
|
|
112
|
+
nowEmpty: nextSites.length === 0,
|
|
113
|
+
cancelled: false,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export const DEFAULT_REMOVE_SITE_DEPS = {
|
|
117
|
+
load: loadSitesFile,
|
|
118
|
+
save: saveSitesFile,
|
|
119
|
+
};
|
|
120
|
+
//# sourceMappingURL=remove-site.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remove-site.js","sourceRoot":"","sources":["../../../src/sites/cli/remove-site.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W9 — `set-default` CLI subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Flips the `is_default` flag on the target entry to `true`, clears
|
|
5
|
+
* it on every other entry, and updates `default_site_id` to the
|
|
6
|
+
* target. Fails with code `'UNKNOWN_SITE'` when the site_id is not
|
|
7
|
+
* present.
|
|
8
|
+
*
|
|
9
|
+
* Idempotent: setting the already-default site as default is a
|
|
10
|
+
* successful no-op (the file is still rewritten so any drift between
|
|
11
|
+
* `default_site_id` and the per-entry `is_default` flag is healed in
|
|
12
|
+
* the same pass).
|
|
13
|
+
*
|
|
14
|
+
* @license MIT
|
|
15
|
+
*/
|
|
16
|
+
import type { SitesFileT } from '../schema.js';
|
|
17
|
+
export interface SetDefaultOptions {
|
|
18
|
+
/** Skip the confirmation prompt when true. CLI passes through from --yes. */
|
|
19
|
+
readonly yes?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface SetDefaultResult {
|
|
22
|
+
readonly siteId: string;
|
|
23
|
+
readonly path: string;
|
|
24
|
+
/** site_id that was the default BEFORE this call (null when none). */
|
|
25
|
+
readonly previousDefault: string | null;
|
|
26
|
+
/** True when no write happened because the confirm hook rejected. */
|
|
27
|
+
readonly cancelled: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare class SetDefaultError extends Error {
|
|
30
|
+
readonly code: string;
|
|
31
|
+
constructor(code: string, message: string);
|
|
32
|
+
}
|
|
33
|
+
export interface SetDefaultDeps {
|
|
34
|
+
readonly load: (path: string) => Promise<SitesFileT>;
|
|
35
|
+
readonly save: (path: string, data: SitesFileT) => Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Confirm hook. Production dispatcher wires this to a y/N readline
|
|
38
|
+
* prompt; tests pass `--yes` so this is never invoked. W12-R2A:
|
|
39
|
+
* `set-default` is destructive in the routing sense — every
|
|
40
|
+
* subsequent tool call routes to the new default — so the guard
|
|
41
|
+
* matches the remove-site shape.
|
|
42
|
+
*/
|
|
43
|
+
readonly confirm?: (message: string) => Promise<boolean>;
|
|
44
|
+
}
|
|
45
|
+
export declare function setDefaultCommand(siteId: string, path: string, opts: SetDefaultOptions, deps: SetDefaultDeps): Promise<SetDefaultResult>;
|
|
46
|
+
export declare const DEFAULT_SET_DEFAULT_DEPS: SetDefaultDeps;
|
|
47
|
+
//# sourceMappingURL=set-default.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"set-default.d.ts","sourceRoot":"","sources":["../../../src/sites/cli/set-default.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAc,UAAU,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,WAAW,iBAAiB;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sEAAsE;IACtE,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,qEAAqE;IACrE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC/B;AAED,qBAAa,eAAgB,SAAQ,KAAK;aACV,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAI5D;AAED,MAAM,WAAW,cAAc;IAC3B,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACrD,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC5D;AAED,wBAAsB,iBAAiB,CACnC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,iBAAiB,EACvB,IAAI,EAAE,cAAc,GACrB,OAAO,CAAC,gBAAgB,CAAC,CAqD3B;AAED,eAAO,MAAM,wBAAwB,EAAE,cAGtC,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W9 — `set-default` CLI subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Flips the `is_default` flag on the target entry to `true`, clears
|
|
5
|
+
* it on every other entry, and updates `default_site_id` to the
|
|
6
|
+
* target. Fails with code `'UNKNOWN_SITE'` when the site_id is not
|
|
7
|
+
* present.
|
|
8
|
+
*
|
|
9
|
+
* Idempotent: setting the already-default site as default is a
|
|
10
|
+
* successful no-op (the file is still rewritten so any drift between
|
|
11
|
+
* `default_site_id` and the per-entry `is_default` flag is healed in
|
|
12
|
+
* the same pass).
|
|
13
|
+
*
|
|
14
|
+
* @license MIT
|
|
15
|
+
*/
|
|
16
|
+
import { loadSitesFile, saveSitesFile } from '../store.js';
|
|
17
|
+
export class SetDefaultError extends Error {
|
|
18
|
+
code;
|
|
19
|
+
constructor(code, message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.name = 'SetDefaultError';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export async function setDefaultCommand(siteId, path, opts, deps) {
|
|
26
|
+
const file = await deps.load(path);
|
|
27
|
+
if (!file.sites.some((s) => s.site_id === siteId)) {
|
|
28
|
+
throw new SetDefaultError('UNKNOWN_SITE', `Site "${siteId}" not found in ${path}. ` +
|
|
29
|
+
(file.sites.length > 0
|
|
30
|
+
? `Available: ${file.sites.map((s) => s.site_id).join(', ')}.`
|
|
31
|
+
: '(no sites configured)'));
|
|
32
|
+
}
|
|
33
|
+
const previousDefault = file.default_site_id;
|
|
34
|
+
// W12-R2A (A2-M1) — destructive-routing-op safeguard. Flipping the
|
|
35
|
+
// default redirects every subsequent tool call that omits `site_id`
|
|
36
|
+
// to a different connected site. That can silently land write-ops
|
|
37
|
+
// on the wrong CMS install. Fail-closed: if `--yes` is not set, a
|
|
38
|
+
// confirm hook is REQUIRED. Idempotent flip (already-default) is
|
|
39
|
+
// still gated — the file is rewritten even on no-op, and an
|
|
40
|
+
// operator confirming "yes" is the only reliable safe-belt.
|
|
41
|
+
if (opts.yes !== true) {
|
|
42
|
+
if (deps.confirm === undefined) {
|
|
43
|
+
throw new SetDefaultError('SET_DEFAULT_CONFIRM_REQUIRED', `Setting default to "${siteId}" requires --yes (no interactive `
|
|
44
|
+
+ `confirm available in this context). Re-run with --yes to skip the prompt.`);
|
|
45
|
+
}
|
|
46
|
+
const priorLabel = previousDefault !== null ? `"${previousDefault}"` : '(none)';
|
|
47
|
+
const cont = await deps.confirm(`Switch default site from ${priorLabel} to "${siteId}" in ${path}? `
|
|
48
|
+
+ `This changes which site every subsequent tool call routes to by default.`);
|
|
49
|
+
if (!cont) {
|
|
50
|
+
return { siteId, path, previousDefault, cancelled: true };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const nextSites = file.sites.map((s) => ({
|
|
54
|
+
...s,
|
|
55
|
+
is_default: s.site_id === siteId,
|
|
56
|
+
}));
|
|
57
|
+
const nextFile = {
|
|
58
|
+
schema_version: 1,
|
|
59
|
+
default_site_id: siteId,
|
|
60
|
+
sites: nextSites,
|
|
61
|
+
};
|
|
62
|
+
await deps.save(path, nextFile);
|
|
63
|
+
return { siteId, path, previousDefault, cancelled: false };
|
|
64
|
+
}
|
|
65
|
+
export const DEFAULT_SET_DEFAULT_DEPS = {
|
|
66
|
+
load: loadSitesFile,
|
|
67
|
+
save: saveSitesFile,
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=set-default.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"set-default.js","sourceRoot":"","sources":["../../../src/sites/cli/set-default.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAiB3D,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACV;IAA5B,YAA4B,IAAY,EAAE,OAAe;QACrD,KAAK,CAAC,OAAO,CAAC,CAAC;QADS,SAAI,GAAJ,IAAI,CAAQ;QAEpC,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAClC,CAAC;CACJ;AAeD,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACnC,MAAc,EACd,IAAY,EACZ,IAAuB,EACvB,IAAoB;IAEpB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,MAAM,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,eAAe,CACrB,cAAc,EACd,SAAS,MAAM,kBAAkB,IAAI,IAAI;YACrC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;gBAClB,CAAC,CAAC,cAAc,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;gBAC9D,CAAC,CAAC,uBAAuB,CAAC,CACrC,CAAC;IACN,CAAC;IAED,MAAM,eAAe,GAAG,IAAI,CAAC,eAAe,CAAC;IAE7C,mEAAmE;IACnE,oEAAoE;IACpE,kEAAkE;IAClE,kEAAkE;IAClE,iEAAiE;IACjE,4DAA4D;IAC5D,4DAA4D;IAC5D,IAAI,IAAI,CAAC,GAAG,KAAK,IAAI,EAAE,CAAC;QACpB,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAC7B,MAAM,IAAI,eAAe,CACrB,8BAA8B,EAC9B,uBAAuB,MAAM,mCAAmC;kBAC1D,2EAA2E,CACpF,CAAC;QACN,CAAC;QACD,MAAM,UAAU,GAAG,eAAe,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,eAAe,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;QAChF,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAC3B,4BAA4B,UAAU,QAAQ,MAAM,QAAQ,IAAI,IAAI;cAC9D,0EAA0E,CACnF,CAAC;QACF,IAAI,CAAC,IAAI,EAAE,CAAC;YACR,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QAC9D,CAAC;IACL,CAAC;IAED,MAAM,SAAS,GAAiB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACnD,GAAG,CAAC;QACJ,UAAU,EAAE,CAAC,CAAC,OAAO,KAAK,MAAM;KACnC,CAAC,CAAC,CAAC;IAEJ,MAAM,QAAQ,GAAe;QACzB,cAAc,EAAE,CAAC;QACjB,eAAe,EAAE,MAAM;QACvB,KAAK,EAAE,SAAS;KACnB,CAAC;IAEF,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAEhC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;AAC/D,CAAC;AAED,MAAM,CAAC,MAAM,wBAAwB,GAAmB;IACpD,IAAI,EAAE,aAAa;IACnB,IAAI,EAAE,aAAa;CACtB,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W9 — `test-site` CLI subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Live connectivity probe for ONE site_id. Mirrors the runtime
|
|
5
|
+
* behaviour of the `yootheme_builder_sites_test` MCP tool, but from
|
|
6
|
+
* the CLI so operators can pre-flight a site without launching the
|
|
7
|
+
* stdio server.
|
|
8
|
+
*
|
|
9
|
+
* Pipeline (per plan §W9):
|
|
10
|
+
* 1. Load `sites.json` via the W1 store.
|
|
11
|
+
* 2. Build a {@link SiteRegistry} + {@link ClientPool} from the
|
|
12
|
+
* entries, wired to the supplied {@link SecretResolver}.
|
|
13
|
+
* 3. Call `pool.resolve(siteId)` → throws structured
|
|
14
|
+
* {@link UnknownSiteError} / {@link NoDefaultSiteError} which we
|
|
15
|
+
* map to a structured CLI result + a non-zero exit code.
|
|
16
|
+
* 4. Probe `/health` (no auth) + `/etag` (auth) in parallel via
|
|
17
|
+
* `Promise.allSettled` so a failure on one path never hides the
|
|
18
|
+
* other's outcome.
|
|
19
|
+
* 5. Print a green/red structured result and return it for the
|
|
20
|
+
* dispatcher / tests.
|
|
21
|
+
*
|
|
22
|
+
* Returns a {@link TestSiteResult} (not exit codes directly) so tests
|
|
23
|
+
* can assert deterministic outputs without re-implementing the
|
|
24
|
+
* probe-routing logic.
|
|
25
|
+
*
|
|
26
|
+
* @license MIT
|
|
27
|
+
*/
|
|
28
|
+
import { type Platform, type PlatformKind } from '../../platform/index.js';
|
|
29
|
+
import { ClientPool } from '../client-pool.js';
|
|
30
|
+
import { SiteRegistry } from '../registry.js';
|
|
31
|
+
import type { SitesFileT } from '../schema.js';
|
|
32
|
+
import { type SecretResolver } from '../secret-resolver.js';
|
|
33
|
+
export interface TestSiteResult {
|
|
34
|
+
readonly siteId: string;
|
|
35
|
+
readonly siteUrl: string;
|
|
36
|
+
readonly platform: PlatformKind | 'unknown';
|
|
37
|
+
readonly pluginReachable: boolean;
|
|
38
|
+
readonly bearerValid: boolean;
|
|
39
|
+
readonly etagReceived: boolean;
|
|
40
|
+
readonly summary: string;
|
|
41
|
+
readonly pluginError?: string;
|
|
42
|
+
readonly bearerError?: string;
|
|
43
|
+
/** True when site_id was not in the registry. */
|
|
44
|
+
readonly unknownSite: boolean;
|
|
45
|
+
/** Available ids when unknownSite is true. */
|
|
46
|
+
readonly available?: readonly string[];
|
|
47
|
+
}
|
|
48
|
+
export interface TestSiteDeps {
|
|
49
|
+
readonly load: (path: string) => Promise<SitesFileT>;
|
|
50
|
+
readonly secretResolver?: SecretResolver;
|
|
51
|
+
/** Inject the platform probe for tests (no network). */
|
|
52
|
+
readonly platformForUrlAsync?: (url: string, hint?: PlatformKind) => Promise<Platform>;
|
|
53
|
+
/**
|
|
54
|
+
* Inject the pool factory. Tests pass a pool that does NOT call
|
|
55
|
+
* the real RestClient (they pre-stub the response shape).
|
|
56
|
+
* Production wires the default ClientPool below.
|
|
57
|
+
*/
|
|
58
|
+
readonly buildPool?: (registry: SiteRegistry, resolver: SecretResolver) => ClientPool;
|
|
59
|
+
readonly log?: (line: string) => void;
|
|
60
|
+
}
|
|
61
|
+
export declare class TestSiteError extends Error {
|
|
62
|
+
readonly code: string;
|
|
63
|
+
constructor(code: string, message: string);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Build the runtime result lines for stdout — keeps the live CLI path
|
|
67
|
+
* + tests in sync without forcing tests to parse text.
|
|
68
|
+
*/
|
|
69
|
+
export declare function renderTestSiteLines(result: TestSiteResult): readonly string[];
|
|
70
|
+
/**
|
|
71
|
+
* Probe one site and return a structured outcome. Never throws on a
|
|
72
|
+
* probe failure (those are surfaced inside the result); throws ONLY
|
|
73
|
+
* on file-IO failures the operator cannot recover from.
|
|
74
|
+
*/
|
|
75
|
+
export declare function testSiteCommand(siteId: string, path: string, deps: TestSiteDeps): Promise<TestSiteResult>;
|
|
76
|
+
export declare const DEFAULT_TEST_SITE_DEPS: TestSiteDeps;
|
|
77
|
+
//# sourceMappingURL=test-site.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-site.d.ts","sourceRoot":"","sources":["../../../src/sites/cli/test-site.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAEH,KAAK,QAAQ,EACb,KAAK,YAAY,EACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAM/C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAEH,KAAK,cAAc,EACtB,MAAM,uBAAuB,CAAC;AAG/B,MAAM,WAAW,cAAc;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,YAAY,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,iDAAiD;IACjD,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,8CAA8C;IAC9C,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,YAAY;IACzB,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACrD,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;IACzC,wDAAwD;IACxD,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAC3B,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,YAAY,KAClB,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CACjB,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,cAAc,KACvB,UAAU,CAAC;IAChB,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED,qBAAa,aAAc,SAAQ,KAAK;aACR,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAI5D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,SAAS,MAAM,EAAE,CA0B7E;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACjC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,YAAY,GACnB,OAAO,CAAC,cAAc,CAAC,CAmEzB;AAED,eAAO,MAAM,sBAAsB,EAAE,YAEpC,CAAC"}
|