anhedral 0.10.0 → 0.11.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 +78 -39
- package/assets/{anhedral-cli-init-technical.svg → anhedral-stack-technical.svg} +6 -6
- package/assets/{anhedral-cli-init.svg → anhedral-stack.svg} +7 -7
- package/assets/security/audit-deps.mjs +6 -1
- package/dist/bin.js +271 -154
- package/dist/bounded-file.d.ts +1 -0
- package/dist/bounded-file.js +1 -0
- package/dist/capabilities.d.ts +4 -4
- package/dist/capabilities.js +1 -1
- package/dist/capability-contract.d.ts +47 -0
- package/dist/capability-contract.js +136 -0
- package/dist/cli-arguments.d.ts +16 -0
- package/dist/cli-arguments.js +115 -0
- package/dist/cli-output.d.ts +8 -0
- package/dist/cli-output.js +211 -0
- package/dist/cloudflare.d.ts +7 -2
- package/dist/cloudflare.js +166 -36
- package/dist/command-schema.d.ts +21 -0
- package/dist/command-schema.js +374 -0
- package/dist/delivery.d.ts +2 -2
- package/dist/delivery.js +21 -4
- package/dist/dependencies.d.ts +32 -0
- package/dist/dependencies.js +47 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.js +15 -1
- package/dist/inspector-model.d.ts +115 -0
- package/dist/inspector-model.js +1 -0
- package/dist/lifecycle-model.d.ts +15 -0
- package/dist/lifecycle-model.js +20 -0
- package/dist/lifecycle.d.ts +12 -0
- package/dist/lifecycle.js +92 -0
- package/dist/mobile-delivery.d.ts +100 -0
- package/dist/mobile-delivery.js +319 -0
- package/dist/platforms/desktop.js +4 -5
- package/dist/platforms/extension.js +9 -73
- package/dist/platforms/mobile.js +14 -52
- package/dist/project-inspector.d.ts +4 -0
- package/dist/project-inspector.js +646 -0
- package/dist/project-state.d.ts +17 -0
- package/dist/project-state.js +239 -11
- package/dist/provider-auth.d.ts +41 -0
- package/dist/provider-auth.js +210 -0
- package/dist/providers/adapters.d.ts +17 -0
- package/dist/providers/adapters.js +269 -0
- package/dist/providers/api.d.ts +28 -0
- package/dist/providers/api.js +252 -0
- package/dist/providers/config.d.ts +15 -0
- package/dist/providers/config.js +184 -0
- package/dist/providers/definitions.d.ts +318 -0
- package/dist/providers/definitions.js +127 -0
- package/dist/providers/provision.d.ts +24 -0
- package/dist/providers/provision.js +76 -0
- package/dist/providers/receipts.d.ts +20 -0
- package/dist/providers/receipts.js +66 -0
- package/dist/providers/recovery-verification.d.ts +7 -0
- package/dist/providers/recovery-verification.js +112 -0
- package/dist/providers/recovery.d.ts +30 -0
- package/dist/providers/recovery.js +34 -0
- package/dist/providers/runtime.d.ts +10 -0
- package/dist/providers/runtime.js +231 -0
- package/dist/providers/schema.d.ts +28 -0
- package/dist/providers/schema.js +49 -0
- package/dist/providers/state.d.ts +56 -0
- package/dist/providers/state.js +329 -0
- package/dist/provision.d.ts +1 -1
- package/dist/provision.js +61 -3
- package/dist/readiness.js +11 -3
- package/dist/registry/add.d.ts +6 -0
- package/dist/registry/add.js +35 -4
- package/dist/registry/commands.d.ts +3 -2
- package/dist/registry/commands.js +53 -14
- package/dist/registry/options.js +3 -2
- package/dist/registry/preview.d.ts +11 -0
- package/dist/registry/preview.js +91 -0
- package/dist/registry/receipt.js +1 -0
- package/dist/registry/resolve.js +19 -13
- package/dist/registry/types.d.ts +1 -0
- package/dist/registry/usage.d.ts +10 -0
- package/dist/registry/usage.js +22 -37
- package/dist/render.d.ts +2 -0
- package/dist/render.js +10 -0
- package/dist/scaffold/api.js +20 -3
- package/dist/scaffold/auth.js +68 -28
- package/dist/scaffold/bootstrap.d.ts +2 -0
- package/dist/scaffold/bootstrap.js +9 -0
- package/dist/scaffold/database.js +12 -6
- package/dist/scaffold/documentation.js +11 -8
- package/dist/scaffold/environment.d.ts +2 -0
- package/dist/scaffold/environment.js +18 -1
- package/dist/scaffold/files.d.ts +1 -0
- package/dist/scaffold/files.js +15 -4
- package/dist/scaffold/services.js +37 -13
- package/dist/scaffold/stack-documentation.d.ts +3 -0
- package/dist/scaffold/stack-documentation.js +52 -0
- package/dist/scaffold/styling.d.ts +3 -0
- package/dist/scaffold/styling.js +84 -0
- package/dist/scaffold/web.js +31 -11
- package/dist/scaffold/workspace.js +49 -22
- package/dist/ship.d.ts +320 -0
- package/dist/ship.js +211 -0
- package/dist/stack-registry.d.ts +2 -2
- package/dist/stack-registry.js +44 -11
- package/dist/standard.d.ts +2 -1
- package/dist/standard.js +13 -12
- package/dist/vercel.d.ts +3 -3
- package/dist/vercel.js +27 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/vinext.js +9 -18
- package/dist/workspace-graph.js +3 -2
- package/docs/application-stack-standard.md +51 -80
- package/docs/cli-design.md +67 -0
- package/docs/delivery-workflow.md +29 -15
- package/docs/plugin-ui.md +61 -0
- package/docs/provider-configuration.md +117 -0
- package/package.json +16 -12
- package/dist/managed-hosting.d.ts +0 -49
- package/dist/managed-hosting.js +0 -373
- package/docs/managed-hosting-architecture.md +0 -87
- package/docs/sponsorships.md +0 -25
package/dist/cloudflare.js
CHANGED
|
@@ -1,64 +1,194 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const ORIGIN = 'https://api.cloudflare.com';
|
|
2
|
+
const MAX_RESPONSE_BYTES = 2 * 1024 * 1024;
|
|
3
|
+
function object(value) {
|
|
4
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
function cancelBody(response) {
|
|
7
|
+
try {
|
|
8
|
+
void response.body?.cancel().catch(() => undefined);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
/* Best effort cancellation never exposes provider diagnostics. */
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
async function boundedJson(response, signal) {
|
|
15
|
+
if (signal.aborted) {
|
|
16
|
+
cancelBody(response);
|
|
17
|
+
throw new Error();
|
|
18
|
+
}
|
|
19
|
+
const reader = response.body?.getReader();
|
|
20
|
+
if (!reader)
|
|
21
|
+
throw new Error();
|
|
22
|
+
const cancel = () => {
|
|
23
|
+
void reader.cancel().catch(() => undefined);
|
|
24
|
+
};
|
|
25
|
+
signal.addEventListener('abort', cancel, { once: true });
|
|
26
|
+
const chunks = [];
|
|
27
|
+
let bytes = 0;
|
|
28
|
+
try {
|
|
29
|
+
for (;;) {
|
|
30
|
+
const chunk = await reader.read();
|
|
31
|
+
if (chunk.done)
|
|
32
|
+
break;
|
|
33
|
+
bytes += chunk.value.length;
|
|
34
|
+
if (bytes > MAX_RESPONSE_BYTES || signal.aborted)
|
|
35
|
+
throw new Error();
|
|
36
|
+
chunks.push(chunk.value);
|
|
37
|
+
}
|
|
38
|
+
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
cancel();
|
|
42
|
+
throw new Error();
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
signal.removeEventListener('abort', cancel);
|
|
46
|
+
reader.releaseLock();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function responseInfo(value) {
|
|
50
|
+
if (value === undefined || value === null)
|
|
51
|
+
return undefined;
|
|
52
|
+
if (!object(value))
|
|
53
|
+
throw new Error('Cloudflare returned unsupported pagination.');
|
|
54
|
+
const info = {};
|
|
55
|
+
for (const field of ['page', 'total_pages', 'count', 'per_page']) {
|
|
56
|
+
const entry = value[field];
|
|
57
|
+
if (entry !== undefined) {
|
|
58
|
+
if (typeof entry !== 'number' || !Number.isSafeInteger(entry) || entry < 0)
|
|
59
|
+
throw new Error('Cloudflare returned unsupported pagination.');
|
|
60
|
+
info[field] = entry;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return info;
|
|
64
|
+
}
|
|
65
|
+
/** Credentials stay in this closure. Fixed account paths, bounded response reads and
|
|
66
|
+
* a whole-request deadline apply to reads and mutations; mutations never retry. */
|
|
67
|
+
export async function cloudflareRequest(project, directory, run, env, options = {}) {
|
|
68
|
+
const account = project.accountId;
|
|
69
|
+
if (typeof account !== 'string' || !/^[a-f0-9]{32}$/.test(account))
|
|
70
|
+
throw new Error('Select a valid Cloudflare account before provider requests.');
|
|
71
|
+
const timeoutMs = options.timeoutMs ?? 30_000;
|
|
72
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 30_000)
|
|
73
|
+
throw new Error('Invalid bounded Cloudflare request deadline.');
|
|
74
|
+
const send = options.send ?? fetch;
|
|
3
75
|
let credentials;
|
|
4
76
|
try {
|
|
5
|
-
|
|
77
|
+
const raw = await run('pnpm', ['exec', 'wrangler', 'auth', 'token', '--json'], directory, env);
|
|
78
|
+
if (Buffer.byteLength(raw) > 64 * 1024)
|
|
79
|
+
throw new Error();
|
|
80
|
+
credentials = JSON.parse(raw);
|
|
6
81
|
}
|
|
7
82
|
catch {
|
|
8
83
|
throw new Error('Cannot retrieve structured Wrangler credentials; update Wrangler and check authentication.');
|
|
9
84
|
}
|
|
10
|
-
if (!
|
|
85
|
+
if (!object(credentials) ||
|
|
86
|
+
typeof credentials.type !== 'string' ||
|
|
87
|
+
!['oauth', 'api_token'].includes(credentials.type) ||
|
|
11
88
|
typeof credentials.token !== 'string' ||
|
|
12
|
-
|
|
89
|
+
!/^[\x21-\x7e]{1,16384}$/.test(credentials.token))
|
|
13
90
|
throw new Error('Cloudflare operations require scoped API-token or Wrangler OAuth access.');
|
|
91
|
+
const token = credentials.token;
|
|
92
|
+
const prefix = `/client/v4/accounts/${account}`;
|
|
14
93
|
return async (method, endpoint, body) => {
|
|
15
|
-
|
|
16
|
-
!endpoint.startsWith('/') ||
|
|
17
|
-
endpoint.includes('..') ||
|
|
18
|
-
endpoint.includes('#'))
|
|
19
|
-
throw new Error('Invalid account-scoped provider request.');
|
|
20
|
-
let response;
|
|
94
|
+
let url;
|
|
21
95
|
try {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
96
|
+
if (typeof endpoint !== 'string')
|
|
97
|
+
throw new Error();
|
|
98
|
+
const decodedPath = decodeURIComponent(endpoint.split('?')[0]);
|
|
99
|
+
if (!['GET', 'POST'].includes(method) ||
|
|
100
|
+
endpoint.length > 4096 ||
|
|
101
|
+
!endpoint.startsWith('/') ||
|
|
102
|
+
endpoint.startsWith('//') ||
|
|
103
|
+
/[\x00-\x20\x7f#\\]/.test(endpoint) ||
|
|
104
|
+
decodedPath.includes('..') ||
|
|
105
|
+
/[\x00-\x20\x7f#\\%]/.test(decodedPath))
|
|
106
|
+
throw new Error();
|
|
107
|
+
url = new URL(`${prefix}${endpoint}`, ORIGIN);
|
|
108
|
+
if (url.origin !== ORIGIN || !url.pathname.startsWith(`${prefix}/`))
|
|
109
|
+
throw new Error();
|
|
32
110
|
}
|
|
33
111
|
catch {
|
|
34
|
-
throw new Error('
|
|
112
|
+
throw new Error('Invalid account-scoped provider request.');
|
|
35
113
|
}
|
|
36
|
-
|
|
114
|
+
const controller = new AbortController();
|
|
115
|
+
let timer;
|
|
116
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
117
|
+
timer = setTimeout(() => {
|
|
118
|
+
controller.abort();
|
|
119
|
+
reject(new Error('Cloudflare request deadline exceeded; inspect remote status before retrying.'));
|
|
120
|
+
}, timeoutMs);
|
|
121
|
+
});
|
|
37
122
|
try {
|
|
38
|
-
|
|
123
|
+
return await Promise.race([
|
|
124
|
+
(async () => {
|
|
125
|
+
let response;
|
|
126
|
+
try {
|
|
127
|
+
response = await send(url.href, {
|
|
128
|
+
method,
|
|
129
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
130
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
131
|
+
redirect: 'error',
|
|
132
|
+
signal: controller.signal,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
throw new Error('Cloudflare request failed; check connectivity and account permissions.');
|
|
137
|
+
}
|
|
138
|
+
let data;
|
|
139
|
+
try {
|
|
140
|
+
data = await boundedJson(response, controller.signal);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
throw new Error(`Cloudflare returned unsupported output (HTTP ${response.status}).`);
|
|
144
|
+
}
|
|
145
|
+
if (!object(data))
|
|
146
|
+
throw new Error(`Cloudflare returned unsupported output (HTTP ${response.status}).`);
|
|
147
|
+
if (!response.ok || data.success !== true) {
|
|
148
|
+
const codes = Array.isArray(data.errors)
|
|
149
|
+
? data.errors
|
|
150
|
+
.slice(0, 20)
|
|
151
|
+
.filter(object)
|
|
152
|
+
.map((item) => item.code)
|
|
153
|
+
.filter((code) => typeof code === 'number' &&
|
|
154
|
+
Number.isSafeInteger(code) &&
|
|
155
|
+
code >= 0 &&
|
|
156
|
+
code <= 1_000_000)
|
|
157
|
+
.join(',')
|
|
158
|
+
: '';
|
|
159
|
+
throw new Error(`Cloudflare operation failed (HTTP ${response.status}${codes ? `; codes ${codes}` : ''}). Check scoped permissions and service eligibility; no paid plan is activated automatically.`);
|
|
160
|
+
}
|
|
161
|
+
if (!Object.hasOwn(data, 'result'))
|
|
162
|
+
throw new Error('Cloudflare returned no operation result.');
|
|
163
|
+
return { result: data.result, result_info: responseInfo(data.result_info) };
|
|
164
|
+
})(),
|
|
165
|
+
timeout,
|
|
166
|
+
]);
|
|
39
167
|
}
|
|
40
|
-
|
|
41
|
-
|
|
168
|
+
finally {
|
|
169
|
+
clearTimeout(timer);
|
|
42
170
|
}
|
|
43
|
-
if (!response.ok || data.success !== true) {
|
|
44
|
-
const codes = (data.errors ?? [])
|
|
45
|
-
.map((item) => item.code)
|
|
46
|
-
.filter((code) => typeof code === 'number')
|
|
47
|
-
.join(',');
|
|
48
|
-
throw new Error(`Cloudflare operation failed (HTTP ${response.status}${codes ? `; codes ${codes}` : ''}). Check scoped permissions and service eligibility; no paid plan is activated automatically.`);
|
|
49
|
-
}
|
|
50
|
-
return { result: data.result, result_info: data.result_info };
|
|
51
171
|
};
|
|
52
172
|
}
|
|
53
173
|
export async function listCloudflare(request, endpoint) {
|
|
54
174
|
const result = [];
|
|
175
|
+
let bytes = 0;
|
|
55
176
|
for (let page = 1; page <= 1000; page++) {
|
|
56
177
|
const response = await request('GET', `${endpoint}${endpoint.includes('?') ? '&' : '?'}page=${page}&per_page=100`);
|
|
57
178
|
if (!Array.isArray(response.result) ||
|
|
58
|
-
response.result.
|
|
179
|
+
response.result.length > 100 ||
|
|
180
|
+
response.result.some((item) => !object(item)))
|
|
181
|
+
throw new Error('Unsupported provider resource list.');
|
|
182
|
+
try {
|
|
183
|
+
bytes += Buffer.byteLength(JSON.stringify(response.result));
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
59
186
|
throw new Error('Unsupported provider resource list.');
|
|
187
|
+
}
|
|
188
|
+
if (result.length + response.result.length > 10_000 || bytes > 10 * 1024 * 1024)
|
|
189
|
+
throw new Error('Provider resource listing exceeded its collection limit.');
|
|
60
190
|
result.push(...response.result);
|
|
61
|
-
const info = response.result_info;
|
|
191
|
+
const info = responseInfo(response.result_info);
|
|
62
192
|
if (info?.total_pages !== undefined ? page >= info.total_pages : response.result.length < 100)
|
|
63
193
|
return result;
|
|
64
194
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Machine-readable option semantics; help is presentation, never parser input. */
|
|
2
|
+
export interface CommandOption {
|
|
3
|
+
flag: string;
|
|
4
|
+
aliases?: readonly string[];
|
|
5
|
+
description: string;
|
|
6
|
+
value?: string;
|
|
7
|
+
arity?: 'required' | 'optional';
|
|
8
|
+
repeatable?: boolean;
|
|
9
|
+
hidden?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface CommandDefinition {
|
|
12
|
+
description: string;
|
|
13
|
+
usage: string;
|
|
14
|
+
options: readonly CommandOption[];
|
|
15
|
+
examples: readonly string[];
|
|
16
|
+
note?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare const GLOBAL_OPTIONS: readonly CommandOption[];
|
|
19
|
+
export declare function optionHelp(option: CommandOption): string;
|
|
20
|
+
export declare function commandDefinition(command: string | undefined): CommandDefinition | undefined;
|
|
21
|
+
export declare const COMMANDS: Readonly<Record<string, CommandDefinition>>;
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { STANDARD_PRODUCTS } from './stack-registry.js';
|
|
2
|
+
export const GLOBAL_OPTIONS = [
|
|
3
|
+
{
|
|
4
|
+
flag: '--cwd',
|
|
5
|
+
aliases: ['-c'],
|
|
6
|
+
value: 'directory',
|
|
7
|
+
arity: 'required',
|
|
8
|
+
description: 'Run from a selected working directory.',
|
|
9
|
+
},
|
|
10
|
+
{ flag: '--json', description: 'Return machine-readable output.' },
|
|
11
|
+
{
|
|
12
|
+
flag: '--quiet',
|
|
13
|
+
aliases: ['-q'],
|
|
14
|
+
description: 'Suppress human output; retain JSON and errors.',
|
|
15
|
+
},
|
|
16
|
+
{ flag: '--verbose', description: 'Show local scaffolding tool output (human mode only).' },
|
|
17
|
+
{ flag: '--help', aliases: ['-h'], description: 'Show command help.' },
|
|
18
|
+
];
|
|
19
|
+
export function optionHelp(option) {
|
|
20
|
+
const flags = [...(option.aliases ?? []), option.flag].join(', ');
|
|
21
|
+
const argument = option.arity === 'required'
|
|
22
|
+
? ` <${option.value}>`
|
|
23
|
+
: option.arity === 'optional'
|
|
24
|
+
? ` [${option.value}]`
|
|
25
|
+
: '';
|
|
26
|
+
return `${flags}${argument} ${option.description}`;
|
|
27
|
+
}
|
|
28
|
+
export function commandDefinition(command) {
|
|
29
|
+
return COMMANDS[command === 'new' ? 'init' : (command ?? '')];
|
|
30
|
+
}
|
|
31
|
+
export const COMMANDS = {
|
|
32
|
+
inspect: {
|
|
33
|
+
description: 'Inspect local stack, setup requirements and recorded delivery evidence.',
|
|
34
|
+
usage: 'inspect [directory] [options]',
|
|
35
|
+
options: [],
|
|
36
|
+
examples: ['inspect', 'inspect --json'],
|
|
37
|
+
note: 'Read-only and offline. Recorded results do not establish live provider status or permissions.',
|
|
38
|
+
},
|
|
39
|
+
integrations: {
|
|
40
|
+
description: 'Inspect private integration receipts or reconcile a verified pending resource.',
|
|
41
|
+
usage: 'integrations status | recover [directory] [options]',
|
|
42
|
+
options: [
|
|
43
|
+
{
|
|
44
|
+
flag: '--provider',
|
|
45
|
+
value: 'name',
|
|
46
|
+
arity: 'required',
|
|
47
|
+
description: 'Select a bundled integration provider for recovery.',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
flag: '--operation',
|
|
51
|
+
value: 'hash',
|
|
52
|
+
arity: 'required',
|
|
53
|
+
description: 'Select the exact pending operation from status.',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
flag: '--resource',
|
|
57
|
+
value: 'id',
|
|
58
|
+
arity: 'required',
|
|
59
|
+
description: 'Verify and adopt this existing resource; never recreate it.',
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
examples: [
|
|
63
|
+
'integrations status --json',
|
|
64
|
+
'integrations recover --provider neon --operation recorded-hash --resource existing-id',
|
|
65
|
+
],
|
|
66
|
+
note: 'Status is read-only. Recovery verifies provider identity before updating a local receipt. Legacy unknown operations and unrecoverable one-time credentials remain blocked.',
|
|
67
|
+
},
|
|
68
|
+
init: {
|
|
69
|
+
description: 'Scaffold a project from the bundled stack registry.',
|
|
70
|
+
usage: 'init [directory] [products...] [options]',
|
|
71
|
+
options: [
|
|
72
|
+
...STANDARD_PRODUCTS.map((name) => ({
|
|
73
|
+
flag: `--${name}`,
|
|
74
|
+
description: `Include ${name}.`,
|
|
75
|
+
hidden: true,
|
|
76
|
+
})),
|
|
77
|
+
{
|
|
78
|
+
flag: '--hosting',
|
|
79
|
+
description: 'Select web hosting.',
|
|
80
|
+
value: 'cloudflare|vercel',
|
|
81
|
+
arity: 'required',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
flag: '--skip-install',
|
|
85
|
+
description: 'Skip the final install (bootstrap still needs network).',
|
|
86
|
+
},
|
|
87
|
+
{ flag: '--git', description: 'Initialize a project repository or skip it.' },
|
|
88
|
+
{ flag: '--no-git', description: 'Initialize a project repository or skip it.' },
|
|
89
|
+
{ flag: '--dry-run', description: 'Preview the selected stack without writes or network.' },
|
|
90
|
+
],
|
|
91
|
+
examples: [
|
|
92
|
+
'init client-app',
|
|
93
|
+
'init mobile-app --expo',
|
|
94
|
+
'init product --next --hono --neon --clerk --dry-run',
|
|
95
|
+
],
|
|
96
|
+
note: `Default: Vinext on Cloudflare Workers, using the official shadcn pnpm/Turborepo monorepo.\nProducts: ${STANDARD_PRODUCTS.join(', ')}\nOnly bundled options are supported. --all is intentionally unsupported.`,
|
|
97
|
+
},
|
|
98
|
+
auth: {
|
|
99
|
+
description: 'Check or guide authentication for the selected providers.',
|
|
100
|
+
usage: 'auth [directory] [options]',
|
|
101
|
+
options: [
|
|
102
|
+
{
|
|
103
|
+
flag: '--provider',
|
|
104
|
+
description: 'Check one provider required by this project.',
|
|
105
|
+
value: 'name',
|
|
106
|
+
arity: 'required',
|
|
107
|
+
},
|
|
108
|
+
{ flag: '--status', description: 'Check existing access without opening login.' },
|
|
109
|
+
{ flag: '--non-interactive', description: 'Never open an interactive login.' },
|
|
110
|
+
],
|
|
111
|
+
examples: ['auth', 'auth --status --json', 'auth --provider neon'],
|
|
112
|
+
note: 'Management keys come from the environment. Login does not establish all write permissions or complete provider setup. Complete anhedral.providers.json before shipping.',
|
|
113
|
+
},
|
|
114
|
+
ship: {
|
|
115
|
+
description: 'Provision, configure and deploy the supported selected stack.',
|
|
116
|
+
usage: 'ship [directory] [options]',
|
|
117
|
+
options: [
|
|
118
|
+
{ flag: '--dry-run', description: 'Preview provisioning and deployment without mutations.' },
|
|
119
|
+
{
|
|
120
|
+
flag: '--provider',
|
|
121
|
+
description: 'Select hosting in a mixed project.',
|
|
122
|
+
value: 'cloudflare|vercel',
|
|
123
|
+
arity: 'required',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
flag: '--platform',
|
|
127
|
+
description: 'Build a selected mobile platform.',
|
|
128
|
+
value: 'ios|android',
|
|
129
|
+
arity: 'required',
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
flag: '--profile',
|
|
133
|
+
description: 'Select the configured EAS build profile.',
|
|
134
|
+
value: 'name',
|
|
135
|
+
arity: 'required',
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
examples: ['ship --dry-run', 'ship --json', 'ship --platform android --profile preview'],
|
|
139
|
+
note: 'Connect the intended hosting account and audience first. Commit source before shipping; resolved tracked bindings require review and another commit. Mobile builds return links and never submit to stores.',
|
|
140
|
+
},
|
|
141
|
+
add: {
|
|
142
|
+
description: 'Add bundled stack parts while preserving edited source.',
|
|
143
|
+
usage: 'add [directory] <items...> [options]',
|
|
144
|
+
options: [
|
|
145
|
+
{ flag: '--dry-run', description: 'Preview changed paths without installing them.' },
|
|
146
|
+
{
|
|
147
|
+
flag: '--diff',
|
|
148
|
+
description: 'Preview source changes; implies --dry-run.',
|
|
149
|
+
value: 'path',
|
|
150
|
+
arity: 'optional',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
flag: '--view',
|
|
154
|
+
description: 'Preview generated source; implies --dry-run.',
|
|
155
|
+
value: 'path',
|
|
156
|
+
arity: 'optional',
|
|
157
|
+
},
|
|
158
|
+
{ flag: '--skip-install', description: 'Skip the final dependency install.' },
|
|
159
|
+
],
|
|
160
|
+
examples: [
|
|
161
|
+
'add resend',
|
|
162
|
+
'add client-app resend --dry-run',
|
|
163
|
+
'add resend --diff --cwd client-app',
|
|
164
|
+
],
|
|
165
|
+
note: 'Source previews are local and bounded. A new web bootstrap cannot be previewed offline; the plan reports that limitation. Edited-source conflicts require review; there is no force overwrite.',
|
|
166
|
+
},
|
|
167
|
+
registry: {
|
|
168
|
+
description: 'Inspect the bundled stack catalog and setup requirements.',
|
|
169
|
+
usage: 'registry list | view <item> | capabilities [options]',
|
|
170
|
+
options: [],
|
|
171
|
+
examples: [
|
|
172
|
+
'registry list',
|
|
173
|
+
'registry view neon',
|
|
174
|
+
'registry capabilities',
|
|
175
|
+
'registry capabilities --json',
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
doctor: {
|
|
179
|
+
description: 'Check project provenance and local readiness without writes.',
|
|
180
|
+
usage: 'doctor [directory] [options]',
|
|
181
|
+
options: [
|
|
182
|
+
{ flag: '--providers', description: 'Also check tools and existing provider context.' },
|
|
183
|
+
],
|
|
184
|
+
examples: ['doctor', 'doctor client-app --providers --json'],
|
|
185
|
+
},
|
|
186
|
+
connect: {
|
|
187
|
+
description: 'Select the intended hosting account, project and audience.',
|
|
188
|
+
usage: 'connect [directory] [options]',
|
|
189
|
+
options: [
|
|
190
|
+
{
|
|
191
|
+
flag: '--provider',
|
|
192
|
+
description: 'Select the hosting provider.',
|
|
193
|
+
value: 'cloudflare|vercel',
|
|
194
|
+
arity: 'required',
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
flag: '--account',
|
|
198
|
+
description: 'Select the Cloudflare account.',
|
|
199
|
+
value: 'id',
|
|
200
|
+
arity: 'required',
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
flag: '--audience',
|
|
204
|
+
description: 'Set the explicit hosting audience.',
|
|
205
|
+
value: 'public|private',
|
|
206
|
+
arity: 'required',
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
flag: '--allow-email',
|
|
210
|
+
description: 'Allow a private Cloudflare member (repeatable).',
|
|
211
|
+
value: 'email',
|
|
212
|
+
arity: 'required',
|
|
213
|
+
repeatable: true,
|
|
214
|
+
},
|
|
215
|
+
{ flag: '--scope', description: 'Select the Vercel team.', value: 'team', arity: 'required' },
|
|
216
|
+
{
|
|
217
|
+
flag: '--use',
|
|
218
|
+
description: 'Declare Vercel usage.',
|
|
219
|
+
value: 'personal|commercial',
|
|
220
|
+
arity: 'required',
|
|
221
|
+
},
|
|
222
|
+
{ flag: '--create', description: 'Create the selected Vercel project.' },
|
|
223
|
+
{ flag: '--git', description: 'Connect the verified GitHub repository.' },
|
|
224
|
+
],
|
|
225
|
+
examples: [
|
|
226
|
+
'connect --provider cloudflare --audience public',
|
|
227
|
+
'connect --scope customer --audience private --use commercial --create',
|
|
228
|
+
],
|
|
229
|
+
note: 'Vercel Hobby is personal/non-commercial. Commercial applications need an eligible existing plan; no paid plan is activated automatically.',
|
|
230
|
+
},
|
|
231
|
+
repository: {
|
|
232
|
+
description: 'Connect or create the application owner’s GitHub repository.',
|
|
233
|
+
usage: 'repository [directory] [--owner <account>] [options]',
|
|
234
|
+
options: [
|
|
235
|
+
{
|
|
236
|
+
flag: '--owner',
|
|
237
|
+
description: 'Select the GitHub owner.',
|
|
238
|
+
value: 'account',
|
|
239
|
+
arity: 'required',
|
|
240
|
+
},
|
|
241
|
+
{ flag: '--name', description: 'Set the repository name.', value: 'name', arity: 'required' },
|
|
242
|
+
{
|
|
243
|
+
flag: '--visibility',
|
|
244
|
+
description: 'Set visibility (default: private).',
|
|
245
|
+
value: 'private|public',
|
|
246
|
+
arity: 'required',
|
|
247
|
+
},
|
|
248
|
+
{ flag: '--create', description: 'Create the repository when needed.' },
|
|
249
|
+
],
|
|
250
|
+
examples: ['repository --owner customer --create'],
|
|
251
|
+
},
|
|
252
|
+
register: {
|
|
253
|
+
description: 'Record application identity and delivery targets.',
|
|
254
|
+
usage: 'register [directory] [--provider cloudflare|vercel]',
|
|
255
|
+
options: [
|
|
256
|
+
{
|
|
257
|
+
flag: '--provider',
|
|
258
|
+
description: 'Select the provider.',
|
|
259
|
+
value: 'cloudflare|vercel',
|
|
260
|
+
arity: 'required',
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
examples: ['register'],
|
|
264
|
+
},
|
|
265
|
+
provision: {
|
|
266
|
+
description: 'Reconcile Cloudflare resources without deploying code.',
|
|
267
|
+
usage: 'provision [directory] [options]',
|
|
268
|
+
options: [
|
|
269
|
+
{
|
|
270
|
+
flag: '--provider',
|
|
271
|
+
description: 'Select Cloudflare in a mixed project.',
|
|
272
|
+
value: 'cloudflare',
|
|
273
|
+
arity: 'required',
|
|
274
|
+
},
|
|
275
|
+
{ flag: '--dry-run', description: 'Preview resources without mutations.' },
|
|
276
|
+
],
|
|
277
|
+
examples: ['provision --dry-run'],
|
|
278
|
+
note: 'Use ship for the full selected-integration and hosting flow.',
|
|
279
|
+
},
|
|
280
|
+
status: {
|
|
281
|
+
description: 'Inspect recorded release and provider evidence.',
|
|
282
|
+
usage: 'status [directory] [options]',
|
|
283
|
+
options: [
|
|
284
|
+
{
|
|
285
|
+
flag: '--provider',
|
|
286
|
+
description: 'Select hosting.',
|
|
287
|
+
value: 'cloudflare|vercel',
|
|
288
|
+
arity: 'required',
|
|
289
|
+
},
|
|
290
|
+
{ flag: '--refresh', description: 'Refresh provider evidence.' },
|
|
291
|
+
],
|
|
292
|
+
examples: ['status --refresh'],
|
|
293
|
+
},
|
|
294
|
+
manage: {
|
|
295
|
+
description: 'Write a local overview of recorded delivery evidence.',
|
|
296
|
+
usage: 'manage [directory] [options]',
|
|
297
|
+
options: [
|
|
298
|
+
{
|
|
299
|
+
flag: '--provider',
|
|
300
|
+
description: 'Select hosting.',
|
|
301
|
+
value: 'cloudflare|vercel',
|
|
302
|
+
arity: 'required',
|
|
303
|
+
},
|
|
304
|
+
{ flag: '--local', description: 'Use local evidence without provider refresh.' },
|
|
305
|
+
{ flag: '--refresh', description: 'Refresh provider evidence.' },
|
|
306
|
+
],
|
|
307
|
+
examples: ['manage --local'],
|
|
308
|
+
},
|
|
309
|
+
verify: {
|
|
310
|
+
description: 'Exercise the application’s configured live acceptance checks.',
|
|
311
|
+
usage: 'verify [directory] [options]',
|
|
312
|
+
options: [
|
|
313
|
+
{
|
|
314
|
+
flag: '--provider',
|
|
315
|
+
description: 'Select hosting.',
|
|
316
|
+
value: 'cloudflare|vercel',
|
|
317
|
+
arity: 'required',
|
|
318
|
+
},
|
|
319
|
+
{ flag: '--refresh', description: 'Refresh provider evidence.' },
|
|
320
|
+
],
|
|
321
|
+
examples: ['verify'],
|
|
322
|
+
},
|
|
323
|
+
adopt: {
|
|
324
|
+
description: 'Record a verified existing Vercel Git deployment.',
|
|
325
|
+
usage: 'adopt [directory] [--provider vercel]',
|
|
326
|
+
options: [
|
|
327
|
+
{ flag: '--provider', description: 'Select Vercel.', value: 'vercel', arity: 'required' },
|
|
328
|
+
{ flag: '--refresh', description: 'Refresh provider evidence.' },
|
|
329
|
+
],
|
|
330
|
+
examples: ['adopt --provider vercel'],
|
|
331
|
+
note: 'Does not push or deploy.',
|
|
332
|
+
},
|
|
333
|
+
recover: {
|
|
334
|
+
description: 'Recover verified hosting state or a proven interrupted local lock.',
|
|
335
|
+
usage: 'recover [directory] [options]',
|
|
336
|
+
options: [
|
|
337
|
+
{
|
|
338
|
+
flag: '--lock',
|
|
339
|
+
description: 'Release only a proven dead local delivery lock; never replay provider operations.',
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
flag: '--provider',
|
|
343
|
+
description: 'Select hosting.',
|
|
344
|
+
value: 'cloudflare|vercel',
|
|
345
|
+
arity: 'required',
|
|
346
|
+
},
|
|
347
|
+
{ flag: '--refresh', description: 'Refresh provider evidence.' },
|
|
348
|
+
],
|
|
349
|
+
examples: ['recover'],
|
|
350
|
+
note: 'Does not recreate missing integration credentials or historical product checks.',
|
|
351
|
+
},
|
|
352
|
+
rollback: {
|
|
353
|
+
description: 'Restore a recorded code release after compatibility review.',
|
|
354
|
+
usage: 'rollback [directory] --release <id> --data-compatible [options]',
|
|
355
|
+
options: [
|
|
356
|
+
{
|
|
357
|
+
flag: '--provider',
|
|
358
|
+
description: 'Select hosting.',
|
|
359
|
+
value: 'cloudflare|vercel',
|
|
360
|
+
arity: 'required',
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
flag: '--release',
|
|
364
|
+
description: 'Select a recorded release.',
|
|
365
|
+
value: 'id',
|
|
366
|
+
arity: 'required',
|
|
367
|
+
},
|
|
368
|
+
{ flag: '--data-compatible', description: 'Confirm data/schema compatibility.' },
|
|
369
|
+
{ flag: '--refresh', description: 'Refresh provider evidence.' },
|
|
370
|
+
],
|
|
371
|
+
examples: ['rollback --release recorded-id --data-compatible'],
|
|
372
|
+
note: 'Code rollback does not restore data.',
|
|
373
|
+
},
|
|
374
|
+
};
|
package/dist/delivery.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ interface TargetRelease {
|
|
|
9
9
|
urls: string[];
|
|
10
10
|
providerConfirmed: boolean;
|
|
11
11
|
}
|
|
12
|
-
interface Release {
|
|
12
|
+
export interface Release {
|
|
13
13
|
id: string;
|
|
14
14
|
commit: string;
|
|
15
15
|
startedAt: string;
|
|
@@ -26,7 +26,7 @@ interface Release {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
export declare function connectApplication(root: string, accountId: string | undefined, audience: 'public' | 'private' | undefined, run?: import("./project-state.js").DeliveryRunner, emails?: string[]): Promise<ApplicationProject>;
|
|
29
|
-
export declare function planDelivery(root: string): {
|
|
29
|
+
export declare function planDelivery(root: string, allowResourcePlaceholders?: boolean): {
|
|
30
30
|
project: ApplicationProject;
|
|
31
31
|
checks: string[][];
|
|
32
32
|
deployments: {
|