anhedral 0.8.6 → 0.10.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 +137 -38
- package/assets/anhedral-cli-init-technical.svg +15 -15
- package/assets/anhedral-cli-init.svg +16 -16
- package/dist/access.d.ts +10 -0
- package/dist/access.js +93 -0
- package/dist/auth-schema-snapshot.d.ts +346 -0
- package/dist/auth-schema-snapshot.js +347 -0
- package/dist/auth-schema.d.ts +1 -0
- package/dist/auth-schema.js +57 -0
- package/dist/bin.js +349 -39
- package/dist/bounded-file.d.ts +2 -0
- package/dist/bounded-file.js +30 -0
- package/dist/capabilities.d.ts +24 -42
- package/dist/capabilities.js +73 -75
- package/dist/cloudflare.d.ts +14 -0
- package/dist/cloudflare.js +66 -0
- package/dist/command.js +12 -3
- package/dist/delivery.d.ts +118 -0
- package/dist/delivery.js +611 -0
- package/dist/dependencies.d.ts +24 -3
- package/dist/dependencies.js +112 -74
- package/dist/dependency-order.d.ts +2 -0
- package/dist/dependency-order.js +24 -0
- package/dist/index.d.ts +16 -3
- package/dist/index.js +11 -2
- package/dist/managed-hosting.d.ts +49 -0
- package/dist/managed-hosting.js +373 -0
- package/dist/management.d.ts +3 -0
- package/dist/management.js +47 -0
- package/dist/platforms/desktop.d.ts +2 -2
- package/dist/platforms/desktop.js +25 -15
- package/dist/platforms/extension.d.ts +2 -2
- package/dist/platforms/extension.js +104 -66
- package/dist/platforms/mobile.d.ts +2 -2
- package/dist/platforms/mobile.js +13 -7
- package/dist/pnpm-policy.d.ts +0 -2
- package/dist/pnpm-policy.js +18 -13
- package/dist/project-state.d.ts +28 -0
- package/dist/project-state.js +226 -0
- package/dist/provider-readiness.d.ts +30 -0
- package/dist/provider-readiness.js +87 -0
- package/dist/provision.d.ts +46 -0
- package/dist/provision.js +197 -0
- package/dist/readiness.d.ts +4 -2
- package/dist/readiness.js +64 -14
- package/dist/registry/add.d.ts +18 -0
- package/dist/registry/add.js +163 -0
- package/dist/registry/bootstrap.d.ts +2 -0
- package/dist/registry/bootstrap.js +14 -0
- package/dist/registry/commands.d.ts +2 -0
- package/dist/registry/commands.js +48 -0
- package/dist/registry/context.d.ts +3 -0
- package/dist/registry/context.js +21 -0
- package/dist/registry/options.d.ts +2 -0
- package/dist/registry/options.js +65 -0
- package/dist/registry/receipt.d.ts +2 -0
- package/dist/registry/receipt.js +52 -0
- package/dist/registry/resolve.d.ts +10 -0
- package/dist/registry/resolve.js +98 -0
- package/dist/registry/types.d.ts +64 -0
- package/dist/registry/usage.d.ts +1 -0
- package/dist/registry/usage.js +37 -0
- package/dist/render.d.ts +0 -4
- package/dist/render.js +3 -16
- package/dist/repository.d.ts +24 -0
- package/dist/repository.js +96 -0
- package/dist/scaffold/api.d.ts +2 -0
- package/dist/scaffold/api.js +13 -0
- package/dist/scaffold/auth.d.ts +3 -0
- package/dist/scaffold/auth.js +95 -0
- package/dist/scaffold/bootstrap.d.ts +3 -0
- package/dist/scaffold/bootstrap.js +39 -0
- package/dist/scaffold/compose.d.ts +4 -0
- package/dist/scaffold/compose.js +34 -0
- package/dist/scaffold/database.d.ts +2 -0
- package/dist/scaffold/database.js +43 -0
- package/dist/scaffold/documentation.d.ts +3 -0
- package/dist/scaffold/documentation.js +91 -0
- package/dist/scaffold/environment.d.ts +2 -0
- package/dist/scaffold/environment.js +5 -0
- package/dist/scaffold/files.d.ts +25 -0
- package/dist/scaffold/files.js +108 -0
- package/dist/scaffold/integrations.d.ts +3 -0
- package/dist/scaffold/integrations.js +7 -0
- package/dist/scaffold/services.d.ts +5 -0
- package/dist/scaffold/services.js +84 -0
- package/dist/scaffold/web.d.ts +2 -0
- package/dist/scaffold/web.js +102 -0
- package/dist/scaffold/workspace.d.ts +3 -0
- package/dist/scaffold/workspace.js +150 -0
- package/dist/security-backports.js +7 -2
- package/dist/stack-registry.d.ts +80 -0
- package/dist/stack-registry.js +424 -0
- package/dist/standard.d.ts +6 -19
- package/dist/standard.js +45 -658
- package/dist/transaction.js +47 -56
- package/dist/util.js +12 -2
- package/dist/vercel.d.ts +98 -0
- package/dist/vercel.js +796 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/vinext.d.ts +1 -0
- package/dist/vinext.js +83 -0
- package/dist/workspace-graph.d.ts +7 -0
- package/dist/workspace-graph.js +73 -0
- package/docs/application-stack-standard.md +76 -91
- package/docs/delivery-workflow.md +83 -0
- package/docs/managed-hosting-architecture.md +87 -0
- package/docs/sponsorships.md +25 -0
- package/package.json +23 -29
- package/dist/evidence-validation.d.ts +0 -2
- package/dist/evidence-validation.js +0 -18
- package/dist/foundation.d.ts +0 -6
- package/dist/foundation.js +0 -49
- package/dist/progress-view.d.ts +0 -57
- package/dist/progress-view.js +0 -69
- package/dist/progress.d.ts +0 -95
- package/dist/progress.js +0 -327
- package/dist/project.d.ts +0 -5
- package/dist/standard-products.d.ts +0 -2
- package/dist/standard-products.js +0 -6
- /package/assets/{images/svg/logo-white-subtract.svg → anhedral-mark.svg} +0 -0
- /package/dist/{project.js → registry/types.js} +0 -0
package/dist/capabilities.js
CHANGED
|
@@ -1,93 +1,91 @@
|
|
|
1
|
+
import { resolveStackRegistry } from './registry/resolve.js';
|
|
2
|
+
import { BOOTSTRAP_COMMAND } from './registry/bootstrap.js';
|
|
1
3
|
import { GENERATOR_VERSION } from './version.js';
|
|
2
4
|
import { PACKAGE_MANAGER } from './dependencies.js';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
wxt: capability('WXT Chrome extension starter; select sidepanel or popup for the task', ['Chrome', 'WXT'], ['Extension store publisher access for distribution'], ['Store publishing credentials when required'], ['Extension app; minimal browser permissions and store package'], ['Installed selected surface, relevant messaging and minimal permission behavior'], { app: true }),
|
|
13
|
-
hono: capability('Shared OpenAPI backend', ['Wrangler'], ['Cloudflare Workers deployment access'], [], ['API Worker; application routes and authorization'], ['Contract validation, unauthenticated rejection and resource ownership'], { app: true }),
|
|
14
|
-
neon: capability('PostgreSQL persistence through Drizzle and Hyperdrive', ['Neon integration or API', 'Wrangler'], ['Neon project/branch access; Cloudflare Hyperdrive configuration access'], ['DATABASE_URL for migrations; separate least-privilege Hyperdrive origin credentials'], ['Generated Drizzle + Hyperdrive recipe: schema/client factories, migration config and HYPERDRIVE binding; direct Neon serverless access is an existing-project adaptation'], ['Migration and runtime connectivity; disable query caching for correctness-sensitive reads'], { conflicts: ['d1'], workerBinding: true }),
|
|
15
|
-
d1: capability('SQLite-compatible hosted persistence', ['Wrangler'], ['Cloudflare D1 access'], [], ['D1 database, DB binding and reviewed migrations'], ['Migration, constraints, transactional behavior and runtime queries'], { conflicts: ['neon'], workerBinding: true }),
|
|
16
|
-
'local-data': capability('Device-local SQLite and files', ['Selected platform database/filesystem tooling'], [], [], ['Integration boundary; implement platform adapters and explicit synchronization if needed'], ['Persistence, offline writes and recovery on the selected device'], {}, 'integration-boundary'),
|
|
17
|
-
clerk: capability('Managed identity', ['Clerk integration or dashboard'], ['Client-owned Clerk project access'], ['Clerk publishable key; server secret key; webhook secret when used'], ['Identity project, allowed URLs, SDK setup and protected routes'], ['Sign-in/out, session expiry, server-side authorization and selected native flows; route-group names do not authorize requests'], { conflicts: ['better-auth'], requiresAny: ['next', 'hono'] }),
|
|
18
|
-
'better-auth': capability('Application-managed identity', ['Selected database tooling'], ['Selected database migration/runtime access'], ['BETTER_AUTH_SECRET; BETTER_AUTH_URL'], ['Selected Neon or D1 database, auth schema, trusted origins and application routes'], ['Sign-in/out, session expiry, sign-up policy and resource authorization'], { conflicts: ['clerk'], requiresAny: ['neon', 'd1'], workerBinding: true }),
|
|
19
|
-
r2: capability('Private remote files', ['Wrangler'], ['Cloudflare R2 bucket/binding access'], [], ['Private R2 bucket and FILES binding; authorized upload/download endpoints'], ['Public bucket access disabled; file ownership enforced; clients receive no storage credentials'], { workerBinding: true }),
|
|
20
|
-
kv: capability('Eventually consistent cache/configuration', ['Wrangler'], ['Cloudflare KV namespace access'], [], ['KV namespace and CONFIG binding'], ['TTL and propagation behavior; never use KV as transactional authority'], { workerBinding: true }),
|
|
21
|
-
realtime: capability('Rooms and collaboration', ['Wrangler', 'Browser control'], ['Cloudflare Durable Objects deployment access'], [], ['Realtime Worker, Durable Object namespace/migration and WebSocket authorization'], ['Authorized room isolation, disconnect/reconnect and deployed WebSockets']),
|
|
22
|
-
queues: capability('Deferred jobs', ['Wrangler'], ['Cloudflare Queues configuration access'], [], ['Queue, producer/consumer bindings and failure handling'], ['Retries, idempotency, concurrency and dead-letter recovery']),
|
|
23
|
-
cron: capability('Scheduled work', ['Wrangler'], ['Cloudflare Worker deployment access'], [], ['Scheduled Worker and environment-specific Cron Triggers'], ['Schedule, idempotency, failure visibility and overlapping runs']),
|
|
24
|
-
workflows: capability('Durable multi-step processing', ['Wrangler'], ['Cloudflare Workflows deployment access'], [], ['Workflow Worker/binding and resumable steps'], ['Retry/resume, idempotency, cancellation and failure recovery']),
|
|
25
|
-
openai: capability('Direct OpenAI integration', ['OpenAI integration or API'], ['Client-owned OpenAI project and model access'], ['OPENAI_API_KEY'], ['Server SDK factory; implement application behavior and spending limits'], ['Selected model, failure handling, private inputs and cost controls'], { conflicts: ['ai-sdk'] }, 'sdk-factory'),
|
|
26
|
-
'ai-sdk': capability('Streaming AI and provider abstraction', ['Selected AI provider integration or API'], ['Selected provider project/model access'], ['OPENAI_API_KEY for the generated default; selected provider credentials otherwise'], ['Server SDK factory; implement streaming, tools and application behavior'], ['Streaming, tool authorization, interruption and spending limits'], { conflicts: ['openai'] }, 'sdk-factory'),
|
|
27
|
-
'workers-ai': capability('Cloudflare-hosted inference', ['Wrangler'], ['Cloudflare Workers AI model access'], [], ['AI binding and selected model invocation'], ['Supported model behavior, authorization and usage limits'], { workerBinding: true }),
|
|
28
|
-
resend: capability('Outbound application email', ['Resend integration or API'], ['Client-owned Resend project; sending-domain DNS access'], ['RESEND_API_KEY; webhook secret when used'], ['Verified sending domain, sender and server integration'], ['Delivery, suppression/bounce handling and webhook verification; inbound routing is separate'], {}, 'sdk-factory'),
|
|
29
|
-
stripe: capability('Web payments and subscriptions', ['Stripe integration or CLI'], ['Client-owned Stripe account; test/live access as required'], ['STRIPE_SECRET_KEY; STRIPE_WEBHOOK_SECRET'], ['Server SDK factory; implement products/prices, signed webhooks and entitlements'], ['Test purchase/cancellation, webhook replay and transactional idempotency'], {}, 'sdk-factory'),
|
|
30
|
-
revenuecat: capability('Cross-store subscription entitlements alongside Stripe', ['RevenueCat integration or API', 'Selected mobile store tooling'], ['RevenueCat project; App Store/Play Store product access'], ['Platform public SDK keys; server API/webhook credentials as required'], ['Integration guidance only; install platform SDKs and configure offerings/store products'], ['Sandbox purchase/restore, verified webhooks and entitlement reconciliation'], { requiresAll: ['stripe'] }, 'integration-boundary'),
|
|
31
|
-
sentry: capability('Additional runtime/crash diagnostics', ['Sentry integration or API'], ['Client-owned Sentry project access'], ['SENTRY_DSN; source-map upload token when required'], ['Integration boundary; install/init runtime SDKs and configure private source-map uploads'], ['Captured runtime errors, source maps, privacy filtering and alert delivery'], {}, 'integration-boundary'),
|
|
32
|
-
basin: capability('Basin ingestion and analytical SQL; product analytics needs its own event model and reports', ['Wrangler', 'Cloudflare integration or API'], ['Cloudflare analytics/Pipelines access; plan supporting selected services; Pipelines Setup grant for managed R2 sink setup'], [], ['EventStream interface and ANALYTICS binding; provision Basin stream, pipeline/table, retention and reports separately; optional private R2 sink'], ['Private ingestion, event delivery, query results and archive retrieval when selected'], { workerBinding: true }, 'integration-boundary'),
|
|
33
|
-
posthog: capability('Specialized product analytics', ['PostHog integration or API'], ['Client-owned PostHog project access'], ['POSTHOG_API_KEY; POSTHOG_HOST; separate privileged key if needed'], ['Analytics client factory; implement consent, event schema and privacy filtering'], ['Event receipt, consent behavior and redaction of private data'], {}, 'sdk-factory'),
|
|
34
|
-
styling: capability('Shared styling definitions', [], [], [], ['Styling package; implement actual shared colors, typography, spacing and themes'], ['Consistency across selected interfaces; preserve platform-specific components'], {}, 'integration-boundary'),
|
|
5
|
+
import { STANDARD_PRODUCTS, getStackDefinition } from './stack-registry.js';
|
|
6
|
+
export const CAPABILITIES = Object.fromEntries(STANDARD_PRODUCTS.map((id) => {
|
|
7
|
+
const definition = getStackDefinition(id);
|
|
8
|
+
return [id, { ...definition.setup, selection: definition.selection }];
|
|
9
|
+
}));
|
|
10
|
+
export const CAPABILITY_REGISTRY = {
|
|
11
|
+
schemaVersion: 1,
|
|
12
|
+
cliVersion: GENERATOR_VERSION,
|
|
13
|
+
capabilities: CAPABILITIES,
|
|
35
14
|
};
|
|
36
|
-
export function resolveStandardProducts(selected, hosting, hostingSpecified) {
|
|
37
|
-
if (![...selected].some((id) => CAPABILITIES[id].selection.app))
|
|
38
|
-
selected.add('next');
|
|
39
|
-
for (const id of selected)
|
|
40
|
-
validateSelection(id, selected);
|
|
41
|
-
const needsWorker = [...selected].some((id) => CAPABILITIES[id].selection.workerBinding);
|
|
42
|
-
if (needsWorker && !selected.has('hono') && (!selected.has('next') || hosting !== 'cloudflare')) {
|
|
43
|
-
throw new Error('Selected server bindings need a Cloudflare Next.js app or an explicitly selected Hono Worker.');
|
|
44
|
-
}
|
|
45
|
-
if (hostingSpecified && !selected.has('next'))
|
|
46
|
-
throw new Error('--hosting applies to the Next.js app.');
|
|
47
|
-
}
|
|
48
|
-
function validateSelection(id, selected) {
|
|
49
|
-
const { conflicts = [], requiresAny = [], requiresAll = [] } = CAPABILITIES[id].selection;
|
|
50
|
-
const conflict = conflicts.find((other) => selected.has(other));
|
|
51
|
-
if (conflict)
|
|
52
|
-
throw new Error(`Choose one of ${[id, conflict].join(' / ')}.`);
|
|
53
|
-
if (requiresAny.length && !requiresAny.some((other) => selected.has(other))) {
|
|
54
|
-
throw new Error(`${id} requires an explicitly selected ${requiresAny.join(' or ')} capability.`);
|
|
55
|
-
}
|
|
56
|
-
const missing = requiresAll.filter((other) => !selected.has(other));
|
|
57
|
-
if (missing.length)
|
|
58
|
-
throw new Error(`${id} requires ${missing.join(', ')}; select it explicitly.`);
|
|
59
|
-
}
|
|
60
|
-
export const CAPABILITY_REGISTRY = { schemaVersion: 1, cliVersion: GENERATOR_VERSION, capabilities: CAPABILITIES };
|
|
61
15
|
const HOSTING_SETUP = {
|
|
62
16
|
cloudflare: {
|
|
63
17
|
tools: ['Cloudflare integration or API', 'Wrangler'],
|
|
64
|
-
access: [
|
|
18
|
+
access: [
|
|
19
|
+
'Client-owned Cloudflare account; account ID; scoped OAuth/API access; service-compatible plan; DNS access only when needed',
|
|
20
|
+
],
|
|
65
21
|
},
|
|
66
22
|
vercel: {
|
|
67
23
|
tools: ['Vercel integration or CLI'],
|
|
68
|
-
access: [
|
|
24
|
+
access: [
|
|
25
|
+
'Client-owned Vercel team/project access; Hobby only for personal non-commercial use; suitable authorized plan for commercial apps',
|
|
26
|
+
],
|
|
69
27
|
},
|
|
70
28
|
};
|
|
71
|
-
export function createSetupPlan(options) {
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
29
|
+
export function createSetupPlan(options, registry = resolveStackRegistry(options)) {
|
|
30
|
+
const items = new Map(registry.items.map((item) => [item.name, item]));
|
|
31
|
+
const capabilities = options.products.map((id) => {
|
|
32
|
+
const item = items.get(id);
|
|
33
|
+
return {
|
|
34
|
+
id,
|
|
35
|
+
...item.setup,
|
|
36
|
+
selection: item.selection,
|
|
37
|
+
status: options.dryRun ? 'planned' : 'starter',
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
const providers = [
|
|
41
|
+
...new Set([
|
|
42
|
+
...(options.products.includes('next') || options.products.includes('vinext')
|
|
43
|
+
? [options.hosting]
|
|
44
|
+
: []),
|
|
75
45
|
...capabilities.flatMap(({ tools }) => tools.includes('Wrangler') ? ['cloudflare'] : []),
|
|
76
|
-
])
|
|
46
|
+
]),
|
|
47
|
+
].map((provider) => HOSTING_SETUP[provider]);
|
|
77
48
|
return {
|
|
78
|
-
schemaVersion: 1,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
49
|
+
schemaVersion: 1,
|
|
50
|
+
cliVersion: GENERATOR_VERSION,
|
|
51
|
+
registryVersion: CAPABILITY_REGISTRY.schemaVersion,
|
|
52
|
+
standard: 'anhedral-application-stack',
|
|
53
|
+
root: options.root,
|
|
54
|
+
name: options.name,
|
|
55
|
+
products: options.products,
|
|
56
|
+
hosting: options.hosting,
|
|
57
|
+
registry,
|
|
58
|
+
bootstrap: [...BOOTSTRAP_COMMAND],
|
|
59
|
+
dryRun: options.dryRun,
|
|
83
60
|
setup: {
|
|
84
|
-
tools: [
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
61
|
+
tools: [
|
|
62
|
+
...new Set([
|
|
63
|
+
'Git',
|
|
64
|
+
'Node.js 22.13+ for generated workspaces',
|
|
65
|
+
PACKAGE_MANAGER.replace('@', ' '),
|
|
66
|
+
'GitHub integration or gh',
|
|
67
|
+
'Fallow',
|
|
68
|
+
'Cloudflare security audit skill',
|
|
69
|
+
...providers.flatMap((provider) => provider.tools),
|
|
70
|
+
...capabilities.flatMap((item) => item.tools),
|
|
71
|
+
]),
|
|
72
|
+
],
|
|
73
|
+
access: [
|
|
74
|
+
'User-owned GitHub repository and CI access',
|
|
75
|
+
...(options.hosting === 'vercel' && options.products.includes('next')
|
|
76
|
+
? ['Vercel GitHub installation access for the selected web repository']
|
|
77
|
+
: []),
|
|
78
|
+
...providers.flatMap((provider) => provider.access),
|
|
79
|
+
],
|
|
88
80
|
capabilities,
|
|
89
|
-
completion: [
|
|
90
|
-
'
|
|
81
|
+
completion: [
|
|
82
|
+
'Guide GitHub authentication, own local Git checkout, reviewed initial commit and private user-owned remote; verify the selected deployment source connection',
|
|
83
|
+
'Discover available plugins and fallbacks; verify the selected account and scope',
|
|
84
|
+
'Configure separate environments, bindings and server-side secrets',
|
|
85
|
+
'Implement starter behavior and meaningful tests',
|
|
86
|
+
'Run pnpm check plus platform/provider verification',
|
|
87
|
+
'Verify deployed preview; obtain required production authorization; record rollback and operating ownership',
|
|
88
|
+
],
|
|
91
89
|
resourcesProvisioned: false,
|
|
92
90
|
},
|
|
93
91
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ApplicationProject, DeliveryRunner } from './project-state.js';
|
|
2
|
+
export interface CloudflareResponse {
|
|
3
|
+
result: unknown;
|
|
4
|
+
result_info?: {
|
|
5
|
+
page?: number;
|
|
6
|
+
total_pages?: number;
|
|
7
|
+
count?: number;
|
|
8
|
+
per_page?: number;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export type CloudflareRequest = (method: 'GET' | 'POST', endpoint: string, body?: unknown) => Promise<CloudflareResponse>;
|
|
12
|
+
/** Credentials stay in this closure, never in returned receipts, arguments or files. */
|
|
13
|
+
export declare function cloudflareRequest(project: ApplicationProject, directory: string, run: DeliveryRunner, env: NodeJS.ProcessEnv): Promise<CloudflareRequest>;
|
|
14
|
+
export declare function listCloudflare(request: CloudflareRequest, endpoint: string): Promise<Record<string, unknown>[]>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** Credentials stay in this closure, never in returned receipts, arguments or files. */
|
|
2
|
+
export async function cloudflareRequest(project, directory, run, env) {
|
|
3
|
+
let credentials;
|
|
4
|
+
try {
|
|
5
|
+
credentials = JSON.parse(await run('pnpm', ['exec', 'wrangler', 'auth', 'token', '--json'], directory, env));
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
throw new Error('Cannot retrieve structured Wrangler credentials; update Wrangler and check authentication.');
|
|
9
|
+
}
|
|
10
|
+
if (!['oauth', 'api_token'].includes(credentials.type) ||
|
|
11
|
+
typeof credentials.token !== 'string' ||
|
|
12
|
+
!credentials.token)
|
|
13
|
+
throw new Error('Cloudflare operations require scoped API-token or Wrangler OAuth access.');
|
|
14
|
+
return async (method, endpoint, body) => {
|
|
15
|
+
if (!project.accountId ||
|
|
16
|
+
!endpoint.startsWith('/') ||
|
|
17
|
+
endpoint.includes('..') ||
|
|
18
|
+
endpoint.includes('#'))
|
|
19
|
+
throw new Error('Invalid account-scoped provider request.');
|
|
20
|
+
let response;
|
|
21
|
+
try {
|
|
22
|
+
response = await fetch(`https://api.cloudflare.com/client/v4/accounts/${project.accountId}${endpoint}`, {
|
|
23
|
+
method,
|
|
24
|
+
headers: {
|
|
25
|
+
Authorization: `Bearer ${credentials.token}`,
|
|
26
|
+
'Content-Type': 'application/json',
|
|
27
|
+
},
|
|
28
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
29
|
+
redirect: 'error',
|
|
30
|
+
signal: AbortSignal.timeout(30_000),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw new Error('Cloudflare request failed; check connectivity and account permissions.');
|
|
35
|
+
}
|
|
36
|
+
let data;
|
|
37
|
+
try {
|
|
38
|
+
data = (await response.json());
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
throw new Error(`Cloudflare returned unsupported output (HTTP ${response.status}).`);
|
|
42
|
+
}
|
|
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
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export async function listCloudflare(request, endpoint) {
|
|
54
|
+
const result = [];
|
|
55
|
+
for (let page = 1; page <= 1000; page++) {
|
|
56
|
+
const response = await request('GET', `${endpoint}${endpoint.includes('?') ? '&' : '?'}page=${page}&per_page=100`);
|
|
57
|
+
if (!Array.isArray(response.result) ||
|
|
58
|
+
response.result.some((item) => !item || typeof item !== 'object'))
|
|
59
|
+
throw new Error('Unsupported provider resource list.');
|
|
60
|
+
result.push(...response.result);
|
|
61
|
+
const info = response.result_info;
|
|
62
|
+
if (info?.total_pages !== undefined ? page >= info.total_pages : response.result.length < 100)
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
throw new Error('Provider resource listing exceeded pagination limit.');
|
|
66
|
+
}
|
package/dist/command.js
CHANGED
|
@@ -5,11 +5,20 @@ export function resolveCommand(command, args, platform = process.platform, searc
|
|
|
5
5
|
if (platform !== 'win32' || !['pnpm', 'pnpm.cmd'].includes(command))
|
|
6
6
|
return { command, args: [...args] };
|
|
7
7
|
const directories = searchPath.split(';').filter(Boolean);
|
|
8
|
-
const executable = directories
|
|
8
|
+
const executable = directories
|
|
9
|
+
.map((directory) => path.join(directory, 'pnpm.exe'))
|
|
10
|
+
.find(existsSync);
|
|
9
11
|
if (executable)
|
|
10
12
|
return { command: executable, args: [...args] };
|
|
11
|
-
const entries = [
|
|
12
|
-
|
|
13
|
+
const entries = [
|
|
14
|
+
'node_modules/pnpm/bin/pnpm.cjs',
|
|
15
|
+
'node_modules/corepack/dist/pnpm.js',
|
|
16
|
+
'pnpm.cjs',
|
|
17
|
+
'../pnpm/bin/pnpm.cjs',
|
|
18
|
+
];
|
|
19
|
+
const entry = directories
|
|
20
|
+
.flatMap((directory) => entries.map((file) => path.join(directory, file)))
|
|
21
|
+
.find(existsSync);
|
|
13
22
|
if (!entry)
|
|
14
23
|
throw new Error('Cannot locate a direct pnpm entry point. Install pnpm or enable Corepack.');
|
|
15
24
|
return { command: process.execPath, args: [entry, ...args] };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { type CloudflareRequest } from './cloudflare.js';
|
|
2
|
+
import { type ApplicationProject } from './project-state.js';
|
|
3
|
+
export { registerApplication, readApplication, defaultRunner, environment, targetDirectory, } from './project-state.js';
|
|
4
|
+
export type { ApplicationProject, DeploymentTarget, DeliveryRunner } from './project-state.js';
|
|
5
|
+
import type { DeploymentTarget } from './project-state.js';
|
|
6
|
+
interface TargetRelease {
|
|
7
|
+
target: string;
|
|
8
|
+
versionId: string;
|
|
9
|
+
urls: string[];
|
|
10
|
+
providerConfirmed: boolean;
|
|
11
|
+
}
|
|
12
|
+
interface Release {
|
|
13
|
+
id: string;
|
|
14
|
+
commit: string;
|
|
15
|
+
startedAt: string;
|
|
16
|
+
finishedAt?: string;
|
|
17
|
+
state: 'running' | 'checked' | 'deployed' | 'failed';
|
|
18
|
+
targets: TargetRelease[];
|
|
19
|
+
failure?: string;
|
|
20
|
+
operation?: 'ship' | 'rollback';
|
|
21
|
+
restoredReleaseId?: string;
|
|
22
|
+
verification?: {
|
|
23
|
+
state: 'passed' | 'failed';
|
|
24
|
+
checkedAt: string;
|
|
25
|
+
command: 'pnpm run test:live';
|
|
26
|
+
};
|
|
27
|
+
}
|
|
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): {
|
|
30
|
+
project: ApplicationProject;
|
|
31
|
+
checks: string[][];
|
|
32
|
+
deployments: {
|
|
33
|
+
target: string;
|
|
34
|
+
directory: string;
|
|
35
|
+
command: string[];
|
|
36
|
+
}[];
|
|
37
|
+
resourcesProvisioned: boolean;
|
|
38
|
+
audience: "unconfigured" | "public" | "private";
|
|
39
|
+
};
|
|
40
|
+
export declare function parseDeploymentOutput(source: string, target: DeploymentTarget): TargetRelease;
|
|
41
|
+
interface ActiveVersion {
|
|
42
|
+
versionId: string;
|
|
43
|
+
percentage: number;
|
|
44
|
+
}
|
|
45
|
+
export declare function shipApplication(root: string, run?: import("./project-state.js").DeliveryRunner, suppliedRequest?: CloudflareRequest): Promise<{
|
|
46
|
+
projectId: string;
|
|
47
|
+
release: Release;
|
|
48
|
+
managementFile: string;
|
|
49
|
+
productVerification: string;
|
|
50
|
+
management: string;
|
|
51
|
+
}>;
|
|
52
|
+
export declare function applicationStatus(root: string, refresh?: boolean, run?: import("./project-state.js").DeliveryRunner): Promise<{
|
|
53
|
+
project: ApplicationProject;
|
|
54
|
+
access: import("./access.js").AccessReceipt[] | undefined;
|
|
55
|
+
architecture: {
|
|
56
|
+
target: string;
|
|
57
|
+
workerName: string;
|
|
58
|
+
directory: string;
|
|
59
|
+
bindings: {
|
|
60
|
+
kind: string;
|
|
61
|
+
binding: string;
|
|
62
|
+
}[];
|
|
63
|
+
scheduled: string[];
|
|
64
|
+
}[];
|
|
65
|
+
releases: Release[];
|
|
66
|
+
provider: {
|
|
67
|
+
target: string;
|
|
68
|
+
activeRecordedVersion: boolean;
|
|
69
|
+
versions: ActiveVersion[];
|
|
70
|
+
}[];
|
|
71
|
+
evidence: string;
|
|
72
|
+
productVerification: string;
|
|
73
|
+
management: string | null;
|
|
74
|
+
}>;
|
|
75
|
+
export declare function verifyApplication(root: string, run?: import("./project-state.js").DeliveryRunner): Promise<{
|
|
76
|
+
projectId: string;
|
|
77
|
+
releaseId: string;
|
|
78
|
+
verification: {
|
|
79
|
+
state: "passed" | "failed";
|
|
80
|
+
checkedAt: string;
|
|
81
|
+
command: "pnpm run test:live";
|
|
82
|
+
};
|
|
83
|
+
managementFile: string;
|
|
84
|
+
}>;
|
|
85
|
+
export declare function rollbackApplication(root: string, releaseId: string, dataCompatible: boolean, run?: import("./project-state.js").DeliveryRunner, suppliedRequest?: CloudflareRequest): Promise<{
|
|
86
|
+
projectId: string;
|
|
87
|
+
release: Release;
|
|
88
|
+
managementFile: string;
|
|
89
|
+
productVerification: string;
|
|
90
|
+
dataRestored: boolean;
|
|
91
|
+
}>;
|
|
92
|
+
/** Recover current provider evidence, never fabricate a historical release or source-to-version mapping. */
|
|
93
|
+
export declare function recoverApplication(root: string, run?: import("./project-state.js").DeliveryRunner, suppliedRequest?: CloudflareRequest): Promise<{
|
|
94
|
+
schemaVersion: number;
|
|
95
|
+
projectId: string;
|
|
96
|
+
accountId: string | null;
|
|
97
|
+
checkedAt: string;
|
|
98
|
+
evidence: string;
|
|
99
|
+
localCommit: string;
|
|
100
|
+
sourceProvenance: string;
|
|
101
|
+
replayableReleaseId: string | null;
|
|
102
|
+
productVerification: string;
|
|
103
|
+
provider: {
|
|
104
|
+
target: string;
|
|
105
|
+
versions: ActiveVersion[];
|
|
106
|
+
bindingsConfirmed: boolean;
|
|
107
|
+
}[];
|
|
108
|
+
resources: {
|
|
109
|
+
kind: string;
|
|
110
|
+
name: string;
|
|
111
|
+
id: string;
|
|
112
|
+
target: string;
|
|
113
|
+
}[];
|
|
114
|
+
access: import("./access.js").AccessReceipt[] | undefined;
|
|
115
|
+
audience: "public" | "private";
|
|
116
|
+
audienceEvidence: string;
|
|
117
|
+
limitations: string[];
|
|
118
|
+
}>;
|