thevoidforge 21.0.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/dist/scripts/vault-read.d.ts +11 -0
- package/dist/scripts/vault-read.js +89 -0
- package/dist/scripts/voidforge.d.ts +20 -0
- package/dist/scripts/voidforge.js +404 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/wizard/api/auth.d.ts +5 -0
- package/dist/wizard/api/auth.js +133 -0
- package/dist/wizard/api/blueprint.d.ts +45 -0
- package/dist/wizard/api/blueprint.js +184 -0
- package/dist/wizard/api/cloud-providers.d.ts +16 -0
- package/dist/wizard/api/cloud-providers.js +363 -0
- package/dist/wizard/api/credentials.d.ts +1 -0
- package/dist/wizard/api/credentials.js +258 -0
- package/dist/wizard/api/danger-room.d.ts +18 -0
- package/dist/wizard/api/danger-room.js +401 -0
- package/dist/wizard/api/deploy.d.ts +4 -0
- package/dist/wizard/api/deploy.js +164 -0
- package/dist/wizard/api/prd.d.ts +1 -0
- package/dist/wizard/api/prd.js +363 -0
- package/dist/wizard/api/project.d.ts +1 -0
- package/dist/wizard/api/project.js +239 -0
- package/dist/wizard/api/projects.d.ts +6 -0
- package/dist/wizard/api/projects.js +648 -0
- package/dist/wizard/api/provision.d.ts +4 -0
- package/dist/wizard/api/provision.js +535 -0
- package/dist/wizard/api/terminal.d.ts +25 -0
- package/dist/wizard/api/terminal.js +241 -0
- package/dist/wizard/api/users.d.ts +6 -0
- package/dist/wizard/api/users.js +244 -0
- package/dist/wizard/api/war-room.d.ts +14 -0
- package/dist/wizard/api/war-room.js +45 -0
- package/dist/wizard/lib/ad-platform-core.d.ts +6 -0
- package/dist/wizard/lib/ad-platform-core.js +1 -0
- package/dist/wizard/lib/adapters/index.d.ts +52 -0
- package/dist/wizard/lib/adapters/index.js +38 -0
- package/dist/wizard/lib/adapters/sandbox-bank.d.ts +17 -0
- package/dist/wizard/lib/adapters/sandbox-bank.js +77 -0
- package/dist/wizard/lib/adapters/sandbox.d.ts +39 -0
- package/dist/wizard/lib/adapters/sandbox.js +174 -0
- package/dist/wizard/lib/adapters/stripe.d.ts +19 -0
- package/dist/wizard/lib/adapters/stripe.js +143 -0
- package/dist/wizard/lib/adapters/types.d.ts +9 -0
- package/dist/wizard/lib/adapters/types.js +10 -0
- package/dist/wizard/lib/agent-memory.d.ts +36 -0
- package/dist/wizard/lib/agent-memory.js +114 -0
- package/dist/wizard/lib/anomaly-detection.d.ts +59 -0
- package/dist/wizard/lib/anomaly-detection.js +122 -0
- package/dist/wizard/lib/anthropic.d.ts +21 -0
- package/dist/wizard/lib/anthropic.js +105 -0
- package/dist/wizard/lib/asset-scanner.d.ts +23 -0
- package/dist/wizard/lib/asset-scanner.js +107 -0
- package/dist/wizard/lib/audit-log.d.ts +23 -0
- package/dist/wizard/lib/audit-log.js +70 -0
- package/dist/wizard/lib/autonomy-controller.d.ts +76 -0
- package/dist/wizard/lib/autonomy-controller.js +183 -0
- package/dist/wizard/lib/body-parser.d.ts +2 -0
- package/dist/wizard/lib/body-parser.js +36 -0
- package/dist/wizard/lib/build-analytics.d.ts +39 -0
- package/dist/wizard/lib/build-analytics.js +91 -0
- package/dist/wizard/lib/build-step.d.ts +21 -0
- package/dist/wizard/lib/build-step.js +104 -0
- package/dist/wizard/lib/campaign-proposer.d.ts +39 -0
- package/dist/wizard/lib/campaign-proposer.js +180 -0
- package/dist/wizard/lib/campaign-state-machine.d.ts +63 -0
- package/dist/wizard/lib/campaign-state-machine.js +114 -0
- package/dist/wizard/lib/ci-generator.d.ts +14 -0
- package/dist/wizard/lib/ci-generator.js +187 -0
- package/dist/wizard/lib/claude-merge.d.ts +38 -0
- package/dist/wizard/lib/claude-merge.js +115 -0
- package/dist/wizard/lib/codegen/erd-gen.d.ts +16 -0
- package/dist/wizard/lib/codegen/erd-gen.js +98 -0
- package/dist/wizard/lib/codegen/integrations.d.ts +18 -0
- package/dist/wizard/lib/codegen/integrations.js +189 -0
- package/dist/wizard/lib/codegen/openapi-gen.d.ts +15 -0
- package/dist/wizard/lib/codegen/openapi-gen.js +79 -0
- package/dist/wizard/lib/codegen/prisma-types.d.ts +15 -0
- package/dist/wizard/lib/codegen/prisma-types.js +44 -0
- package/dist/wizard/lib/codegen/seed-gen.d.ts +16 -0
- package/dist/wizard/lib/codegen/seed-gen.js +128 -0
- package/dist/wizard/lib/compliance.d.ts +51 -0
- package/dist/wizard/lib/compliance.js +112 -0
- package/dist/wizard/lib/correlation-engine.d.ts +59 -0
- package/dist/wizard/lib/correlation-engine.js +151 -0
- package/dist/wizard/lib/cost-estimator.d.ts +22 -0
- package/dist/wizard/lib/cost-estimator.js +72 -0
- package/dist/wizard/lib/cost-tracker.d.ts +27 -0
- package/dist/wizard/lib/cost-tracker.js +37 -0
- package/dist/wizard/lib/daemon-aggregator.d.ts +71 -0
- package/dist/wizard/lib/daemon-aggregator.js +204 -0
- package/dist/wizard/lib/daemon-core.d.ts +6 -0
- package/dist/wizard/lib/daemon-core.js +5 -0
- package/dist/wizard/lib/dashboard-data.d.ts +132 -0
- package/dist/wizard/lib/dashboard-data.js +336 -0
- package/dist/wizard/lib/dashboard-ws.d.ts +25 -0
- package/dist/wizard/lib/dashboard-ws.js +91 -0
- package/dist/wizard/lib/deep-current.d.ts +77 -0
- package/dist/wizard/lib/deep-current.js +234 -0
- package/dist/wizard/lib/deploy-coordinator.d.ts +40 -0
- package/dist/wizard/lib/deploy-coordinator.js +86 -0
- package/dist/wizard/lib/deploy-log.d.ts +28 -0
- package/dist/wizard/lib/deploy-log.js +52 -0
- package/dist/wizard/lib/desktop-notify.d.ts +27 -0
- package/dist/wizard/lib/desktop-notify.js +98 -0
- package/dist/wizard/lib/dns/cloudflare-dns.d.ts +35 -0
- package/dist/wizard/lib/dns/cloudflare-dns.js +216 -0
- package/dist/wizard/lib/dns/cloudflare-registrar.d.ts +31 -0
- package/dist/wizard/lib/dns/cloudflare-registrar.js +148 -0
- package/dist/wizard/lib/dns/types.d.ts +22 -0
- package/dist/wizard/lib/dns/types.js +4 -0
- package/dist/wizard/lib/document-discovery.d.ts +33 -0
- package/dist/wizard/lib/document-discovery.js +145 -0
- package/dist/wizard/lib/env-validator.d.ts +14 -0
- package/dist/wizard/lib/env-validator.js +205 -0
- package/dist/wizard/lib/env-writer.d.ts +13 -0
- package/dist/wizard/lib/env-writer.js +26 -0
- package/dist/wizard/lib/exec.d.ts +30 -0
- package/dist/wizard/lib/exec.js +52 -0
- package/dist/wizard/lib/experiment.d.ts +70 -0
- package/dist/wizard/lib/experiment.js +169 -0
- package/dist/wizard/lib/extensions.d.ts +20 -0
- package/dist/wizard/lib/extensions.js +183 -0
- package/dist/wizard/lib/financial/adapter-factory.d.ts +47 -0
- package/dist/wizard/lib/financial/adapter-factory.js +225 -0
- package/dist/wizard/lib/financial/billing/base.d.ts +6 -0
- package/dist/wizard/lib/financial/billing/base.js +1 -0
- package/dist/wizard/lib/financial/billing/google-billing.d.ts +56 -0
- package/dist/wizard/lib/financial/billing/google-billing.js +298 -0
- package/dist/wizard/lib/financial/billing/meta-billing.d.ts +54 -0
- package/dist/wizard/lib/financial/billing/meta-billing.js +243 -0
- package/dist/wizard/lib/financial/billing/tiktok-billing.d.ts +54 -0
- package/dist/wizard/lib/financial/billing/tiktok-billing.js +260 -0
- package/dist/wizard/lib/financial/campaign/base.d.ts +13 -0
- package/dist/wizard/lib/financial/campaign/base.js +1 -0
- package/dist/wizard/lib/financial/campaign/google-campaign.d.ts +42 -0
- package/dist/wizard/lib/financial/campaign/google-campaign.js +388 -0
- package/dist/wizard/lib/financial/campaign/meta-campaign.d.ts +41 -0
- package/dist/wizard/lib/financial/campaign/meta-campaign.js +311 -0
- package/dist/wizard/lib/financial/campaign/sandbox-campaign.d.ts +45 -0
- package/dist/wizard/lib/financial/campaign/sandbox-campaign.js +261 -0
- package/dist/wizard/lib/financial/campaign/tiktok-campaign.d.ts +40 -0
- package/dist/wizard/lib/financial/campaign/tiktok-campaign.js +350 -0
- package/dist/wizard/lib/financial/funding-auto.d.ts +44 -0
- package/dist/wizard/lib/financial/funding-auto.js +52 -0
- package/dist/wizard/lib/financial/funding-policy.d.ts +60 -0
- package/dist/wizard/lib/financial/funding-policy.js +179 -0
- package/dist/wizard/lib/financial/platform-planner.d.ts +47 -0
- package/dist/wizard/lib/financial/platform-planner.js +134 -0
- package/dist/wizard/lib/financial/reconciliation-engine.d.ts +78 -0
- package/dist/wizard/lib/financial/reconciliation-engine.js +193 -0
- package/dist/wizard/lib/financial/registry.d.ts +22 -0
- package/dist/wizard/lib/financial/registry.js +26 -0
- package/dist/wizard/lib/financial/reporting.d.ts +96 -0
- package/dist/wizard/lib/financial/reporting.js +198 -0
- package/dist/wizard/lib/financial/stablecoin/base.d.ts +6 -0
- package/dist/wizard/lib/financial/stablecoin/base.js +1 -0
- package/dist/wizard/lib/financial/stablecoin/circle.d.ts +54 -0
- package/dist/wizard/lib/financial/stablecoin/circle.js +367 -0
- package/dist/wizard/lib/financial/stablecoin/mercury.d.ts +24 -0
- package/dist/wizard/lib/financial/stablecoin/mercury.js +171 -0
- package/dist/wizard/lib/financial/stablecoin/sandbox-stablecoin.d.ts +47 -0
- package/dist/wizard/lib/financial/stablecoin/sandbox-stablecoin.js +202 -0
- package/dist/wizard/lib/financial/treasury-planner.d.ts +52 -0
- package/dist/wizard/lib/financial/treasury-planner.js +128 -0
- package/dist/wizard/lib/financial-core.d.ts +6 -0
- package/dist/wizard/lib/financial-core.js +5 -0
- package/dist/wizard/lib/financial-vault.d.ts +34 -0
- package/dist/wizard/lib/financial-vault.js +199 -0
- package/dist/wizard/lib/frontmatter.d.ts +30 -0
- package/dist/wizard/lib/frontmatter.js +96 -0
- package/dist/wizard/lib/gap-analysis.d.ts +37 -0
- package/dist/wizard/lib/gap-analysis.js +218 -0
- package/dist/wizard/lib/github.d.ts +22 -0
- package/dist/wizard/lib/github.js +261 -0
- package/dist/wizard/lib/headless-deploy.d.ts +14 -0
- package/dist/wizard/lib/headless-deploy.js +452 -0
- package/dist/wizard/lib/health-monitor.d.ts +15 -0
- package/dist/wizard/lib/health-monitor.js +91 -0
- package/dist/wizard/lib/health-poller.d.ts +9 -0
- package/dist/wizard/lib/health-poller.js +123 -0
- package/dist/wizard/lib/heartbeat.d.ts +15 -0
- package/dist/wizard/lib/heartbeat.js +827 -0
- package/dist/wizard/lib/http-helpers.d.ts +9 -0
- package/dist/wizard/lib/http-helpers.js +24 -0
- package/dist/wizard/lib/image-gen.d.ts +56 -0
- package/dist/wizard/lib/image-gen.js +159 -0
- package/dist/wizard/lib/instance-sizing.d.ts +26 -0
- package/dist/wizard/lib/instance-sizing.js +51 -0
- package/dist/wizard/lib/kongo/analytics.d.ts +29 -0
- package/dist/wizard/lib/kongo/analytics.js +179 -0
- package/dist/wizard/lib/kongo/campaigns.d.ts +52 -0
- package/dist/wizard/lib/kongo/campaigns.js +91 -0
- package/dist/wizard/lib/kongo/client.d.ts +58 -0
- package/dist/wizard/lib/kongo/client.js +221 -0
- package/dist/wizard/lib/kongo/jobs.d.ts +57 -0
- package/dist/wizard/lib/kongo/jobs.js +122 -0
- package/dist/wizard/lib/kongo/pages.d.ts +60 -0
- package/dist/wizard/lib/kongo/pages.js +150 -0
- package/dist/wizard/lib/kongo/provisioner.d.ts +64 -0
- package/dist/wizard/lib/kongo/provisioner.js +116 -0
- package/dist/wizard/lib/kongo/seed.d.ts +49 -0
- package/dist/wizard/lib/kongo/seed.js +237 -0
- package/dist/wizard/lib/kongo/types.d.ts +323 -0
- package/dist/wizard/lib/kongo/types.js +11 -0
- package/dist/wizard/lib/kongo/variants.d.ts +57 -0
- package/dist/wizard/lib/kongo/variants.js +88 -0
- package/dist/wizard/lib/kongo/webhooks.d.ts +41 -0
- package/dist/wizard/lib/kongo/webhooks.js +112 -0
- package/dist/wizard/lib/marker.d.ts +28 -0
- package/dist/wizard/lib/marker.js +79 -0
- package/dist/wizard/lib/migrator.d.ts +35 -0
- package/dist/wizard/lib/migrator.js +190 -0
- package/dist/wizard/lib/natural-language-deploy.d.ts +30 -0
- package/dist/wizard/lib/natural-language-deploy.js +186 -0
- package/dist/wizard/lib/network.d.ts +22 -0
- package/dist/wizard/lib/network.js +72 -0
- package/dist/wizard/lib/oauth-core.d.ts +6 -0
- package/dist/wizard/lib/oauth-core.js +5 -0
- package/dist/wizard/lib/open-browser.d.ts +1 -0
- package/dist/wizard/lib/open-browser.js +26 -0
- package/dist/wizard/lib/patterns/ad-billing-adapter.d.ts +209 -0
- package/dist/wizard/lib/patterns/ad-billing-adapter.js +269 -0
- package/dist/wizard/lib/patterns/ad-platform-adapter.d.ts +200 -0
- package/dist/wizard/lib/patterns/ad-platform-adapter.js +212 -0
- package/dist/wizard/lib/patterns/daemon-process.d.ts +88 -0
- package/dist/wizard/lib/patterns/daemon-process.js +271 -0
- package/dist/wizard/lib/patterns/financial-transaction.d.ts +161 -0
- package/dist/wizard/lib/patterns/financial-transaction.js +132 -0
- package/dist/wizard/lib/patterns/funding-plan.d.ts +136 -0
- package/dist/wizard/lib/patterns/funding-plan.js +200 -0
- package/dist/wizard/lib/patterns/oauth-token-lifecycle.d.ts +94 -0
- package/dist/wizard/lib/patterns/oauth-token-lifecycle.js +139 -0
- package/dist/wizard/lib/patterns/outbound-rate-limiter.d.ts +67 -0
- package/dist/wizard/lib/patterns/outbound-rate-limiter.js +216 -0
- package/dist/wizard/lib/patterns/revenue-source-adapter.d.ts +96 -0
- package/dist/wizard/lib/patterns/revenue-source-adapter.js +182 -0
- package/dist/wizard/lib/patterns/stablecoin-adapter.d.ts +218 -0
- package/dist/wizard/lib/patterns/stablecoin-adapter.js +264 -0
- package/dist/wizard/lib/prd-validator.d.ts +39 -0
- package/dist/wizard/lib/prd-validator.js +137 -0
- package/dist/wizard/lib/project-init.d.ts +24 -0
- package/dist/wizard/lib/project-init.js +193 -0
- package/dist/wizard/lib/project-registry.d.ts +86 -0
- package/dist/wizard/lib/project-registry.js +359 -0
- package/dist/wizard/lib/provision-manifest.d.ts +44 -0
- package/dist/wizard/lib/provision-manifest.js +164 -0
- package/dist/wizard/lib/provisioner-registry.d.ts +15 -0
- package/dist/wizard/lib/provisioner-registry.js +34 -0
- package/dist/wizard/lib/provisioners/aws-vps.d.ts +6 -0
- package/dist/wizard/lib/provisioners/aws-vps.js +643 -0
- package/dist/wizard/lib/provisioners/cloudflare.d.ts +6 -0
- package/dist/wizard/lib/provisioners/cloudflare.js +300 -0
- package/dist/wizard/lib/provisioners/docker.d.ts +6 -0
- package/dist/wizard/lib/provisioners/docker.js +75 -0
- package/dist/wizard/lib/provisioners/http-client.d.ts +20 -0
- package/dist/wizard/lib/provisioners/http-client.js +79 -0
- package/dist/wizard/lib/provisioners/railway.d.ts +6 -0
- package/dist/wizard/lib/provisioners/railway.js +413 -0
- package/dist/wizard/lib/provisioners/scripts/caddyfile.d.ts +10 -0
- package/dist/wizard/lib/provisioners/scripts/caddyfile.js +54 -0
- package/dist/wizard/lib/provisioners/scripts/deploy-vps.d.ts +10 -0
- package/dist/wizard/lib/provisioners/scripts/deploy-vps.js +112 -0
- package/dist/wizard/lib/provisioners/scripts/docker-compose.d.ts +11 -0
- package/dist/wizard/lib/provisioners/scripts/docker-compose.js +91 -0
- package/dist/wizard/lib/provisioners/scripts/dockerfile.d.ts +5 -0
- package/dist/wizard/lib/provisioners/scripts/dockerfile.js +185 -0
- package/dist/wizard/lib/provisioners/scripts/ecosystem-config.d.ts +10 -0
- package/dist/wizard/lib/provisioners/scripts/ecosystem-config.js +36 -0
- package/dist/wizard/lib/provisioners/scripts/provision-vps.d.ts +14 -0
- package/dist/wizard/lib/provisioners/scripts/provision-vps.js +202 -0
- package/dist/wizard/lib/provisioners/scripts/rollback-vps.d.ts +10 -0
- package/dist/wizard/lib/provisioners/scripts/rollback-vps.js +67 -0
- package/dist/wizard/lib/provisioners/self-deploy.d.ts +41 -0
- package/dist/wizard/lib/provisioners/self-deploy.js +185 -0
- package/dist/wizard/lib/provisioners/static-s3.d.ts +6 -0
- package/dist/wizard/lib/provisioners/static-s3.js +235 -0
- package/dist/wizard/lib/provisioners/types.d.ts +40 -0
- package/dist/wizard/lib/provisioners/types.js +4 -0
- package/dist/wizard/lib/provisioners/vercel.d.ts +6 -0
- package/dist/wizard/lib/provisioners/vercel.js +287 -0
- package/dist/wizard/lib/pty-manager.d.ts +42 -0
- package/dist/wizard/lib/pty-manager.js +231 -0
- package/dist/wizard/lib/rate-limiter-core.d.ts +5 -0
- package/dist/wizard/lib/rate-limiter-core.js +5 -0
- package/dist/wizard/lib/reconciliation.d.ts +43 -0
- package/dist/wizard/lib/reconciliation.js +173 -0
- package/dist/wizard/lib/revenue-types.d.ts +5 -0
- package/dist/wizard/lib/revenue-types.js +1 -0
- package/dist/wizard/lib/route-optimizer.d.ts +28 -0
- package/dist/wizard/lib/route-optimizer.js +93 -0
- package/dist/wizard/lib/s3-deploy.d.ts +19 -0
- package/dist/wizard/lib/s3-deploy.js +156 -0
- package/dist/wizard/lib/safety-tiers.d.ts +76 -0
- package/dist/wizard/lib/safety-tiers.js +134 -0
- package/dist/wizard/lib/sentry-generator.d.ts +15 -0
- package/dist/wizard/lib/sentry-generator.js +116 -0
- package/dist/wizard/lib/server-config.d.ts +13 -0
- package/dist/wizard/lib/server-config.js +23 -0
- package/dist/wizard/lib/service-install.d.ts +18 -0
- package/dist/wizard/lib/service-install.js +182 -0
- package/dist/wizard/lib/site-scanner.d.ts +80 -0
- package/dist/wizard/lib/site-scanner.js +262 -0
- package/dist/wizard/lib/ssh-deploy.d.ts +25 -0
- package/dist/wizard/lib/ssh-deploy.js +225 -0
- package/dist/wizard/lib/templates.d.ts +24 -0
- package/dist/wizard/lib/templates.js +219 -0
- package/dist/wizard/lib/totp.d.ts +35 -0
- package/dist/wizard/lib/totp.js +276 -0
- package/dist/wizard/lib/tower-auth.d.ts +43 -0
- package/dist/wizard/lib/tower-auth.js +352 -0
- package/dist/wizard/lib/tower-rate-limit.d.ts +14 -0
- package/dist/wizard/lib/tower-rate-limit.js +61 -0
- package/dist/wizard/lib/tower-session.d.ts +28 -0
- package/dist/wizard/lib/tower-session.js +119 -0
- package/dist/wizard/lib/treasury-backup.d.ts +23 -0
- package/dist/wizard/lib/treasury-backup.js +126 -0
- package/dist/wizard/lib/treasury-heartbeat.d.ts +82 -0
- package/dist/wizard/lib/treasury-heartbeat.js +1104 -0
- package/dist/wizard/lib/updater.d.ts +29 -0
- package/dist/wizard/lib/updater.js +190 -0
- package/dist/wizard/lib/user-manager.d.ts +39 -0
- package/dist/wizard/lib/user-manager.js +182 -0
- package/dist/wizard/lib/vault.d.ts +26 -0
- package/dist/wizard/lib/vault.js +161 -0
- package/dist/wizard/router.d.ts +5 -0
- package/dist/wizard/router.js +15 -0
- package/dist/wizard/server.d.ts +18 -0
- package/dist/wizard/server.js +436 -0
- package/package.json +59 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare provisioner — creates a real Workers/Pages project via API + generates wrangler.toml.
|
|
3
|
+
* v3.8.0: Includes GitHub source at creation time for auto-deploy (ADR-011, ADR-015).
|
|
4
|
+
*/
|
|
5
|
+
import { writeFile } from 'node:fs/promises';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { httpsPost, httpsGet, httpsDelete, safeJsonParse, slugify } from './http-client.js';
|
|
8
|
+
import { recordResourcePending, recordResourceCreated } from '../provision-manifest.js';
|
|
9
|
+
import { appendEnvSection } from '../env-writer.js';
|
|
10
|
+
const DEPLOY_POLL_INTERVAL_MS = 5000;
|
|
11
|
+
const DEPLOY_POLL_TIMEOUT_MS = 300_000;
|
|
12
|
+
export const cloudflareProvisioner = {
|
|
13
|
+
async validate(ctx) {
|
|
14
|
+
const errors = [];
|
|
15
|
+
if (!ctx.projectDir)
|
|
16
|
+
errors.push('Project directory is required');
|
|
17
|
+
if (!ctx.credentials['cloudflare-api-token'])
|
|
18
|
+
errors.push('Cloudflare API token is required');
|
|
19
|
+
return errors;
|
|
20
|
+
},
|
|
21
|
+
async provision(ctx, emit) {
|
|
22
|
+
const files = [];
|
|
23
|
+
const resources = [];
|
|
24
|
+
const outputs = {};
|
|
25
|
+
const token = ctx.credentials['cloudflare-api-token'];
|
|
26
|
+
const slug = slugify(ctx.projectName);
|
|
27
|
+
const headers = {
|
|
28
|
+
'Authorization': `Bearer ${token}`,
|
|
29
|
+
'Content-Type': 'application/json',
|
|
30
|
+
};
|
|
31
|
+
// Step 1: Get account ID
|
|
32
|
+
emit({ step: 'cf-account', status: 'started', message: 'Fetching Cloudflare account' });
|
|
33
|
+
let accountId = '';
|
|
34
|
+
try {
|
|
35
|
+
const res = await httpsGet('api.cloudflare.com', '/client/v4/accounts?page=1&per_page=1', {
|
|
36
|
+
'Authorization': `Bearer ${token}`,
|
|
37
|
+
});
|
|
38
|
+
if (res.status !== 200) {
|
|
39
|
+
throw new Error(`Cloudflare API returned ${res.status}`);
|
|
40
|
+
}
|
|
41
|
+
const data = safeJsonParse(res.body);
|
|
42
|
+
if (!data?.success || !data?.result || data.result.length === 0) {
|
|
43
|
+
throw new Error('No Cloudflare account found for this token');
|
|
44
|
+
}
|
|
45
|
+
accountId = data.result[0].id;
|
|
46
|
+
outputs['CF_ACCOUNT_ID'] = accountId;
|
|
47
|
+
emit({ step: 'cf-account', status: 'done', message: `Account: ${data.result[0].name}` });
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
emit({ step: 'cf-account', status: 'error', message: 'Failed to fetch Cloudflare account', detail: err.message });
|
|
51
|
+
return { success: false, resources, outputs, files, error: err.message };
|
|
52
|
+
}
|
|
53
|
+
// GitHub source info (used in project creation and deploy polling)
|
|
54
|
+
const ghOwner = ctx.credentials['_github-owner'];
|
|
55
|
+
const ghRepo = ctx.credentials['_github-repo-name'];
|
|
56
|
+
// Step 2: Create Pages project
|
|
57
|
+
emit({ step: 'cf-project', status: 'started', message: 'Creating Cloudflare Pages project' });
|
|
58
|
+
try {
|
|
59
|
+
await recordResourcePending(ctx.runId, 'cf-pages-project', slug, 'global');
|
|
60
|
+
const projectPayload = {
|
|
61
|
+
name: slug,
|
|
62
|
+
production_branch: 'main',
|
|
63
|
+
};
|
|
64
|
+
if (ghOwner && ghRepo) {
|
|
65
|
+
projectPayload.source = {
|
|
66
|
+
type: 'github',
|
|
67
|
+
config: {
|
|
68
|
+
owner: ghOwner,
|
|
69
|
+
repo_name: ghRepo,
|
|
70
|
+
production_branch: 'main',
|
|
71
|
+
pr_comments_enabled: true,
|
|
72
|
+
deployments_enabled: true,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
projectPayload.build_config = {
|
|
76
|
+
build_command: ctx.framework === 'django' ? '' : 'npm run build',
|
|
77
|
+
destination_dir: ctx.framework === 'next.js' ? 'out' : 'dist',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const body = JSON.stringify(projectPayload);
|
|
81
|
+
const res = await httpsPost('api.cloudflare.com', `/client/v4/accounts/${accountId}/pages/projects`, headers, body);
|
|
82
|
+
if (res.status === 200 || res.status === 201) {
|
|
83
|
+
const data = safeJsonParse(res.body);
|
|
84
|
+
const projectName = data?.result?.name ?? slug;
|
|
85
|
+
const subdomain = data?.result?.subdomain ?? `${slug}.pages.dev`;
|
|
86
|
+
resources.push({ type: 'cf-pages-project', id: projectName, region: 'global' });
|
|
87
|
+
await recordResourceCreated(ctx.runId, 'cf-pages-project', projectName, 'global');
|
|
88
|
+
outputs['CF_PROJECT_NAME'] = projectName;
|
|
89
|
+
outputs['CF_PROJECT_URL'] = `https://${subdomain}`;
|
|
90
|
+
emit({ step: 'cf-project', status: 'done', message: `Pages project "${projectName}" created — ${subdomain}` });
|
|
91
|
+
}
|
|
92
|
+
else if (res.status === 409) {
|
|
93
|
+
// Project exists — track it so cleanup knows about it
|
|
94
|
+
resources.push({ type: 'cf-pages-project', id: slug, region: 'global' });
|
|
95
|
+
await recordResourceCreated(ctx.runId, 'cf-pages-project', slug, 'global');
|
|
96
|
+
emit({ step: 'cf-project', status: 'done', message: `Project "${slug}" already exists on Cloudflare — will use existing` });
|
|
97
|
+
outputs['CF_PROJECT_NAME'] = slug;
|
|
98
|
+
outputs['CF_PROJECT_URL'] = `https://${slug}.pages.dev`;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
const data = safeJsonParse(res.body);
|
|
102
|
+
const errMsg = data?.errors?.[0]?.message || `Cloudflare API returned ${res.status}`;
|
|
103
|
+
throw new Error(errMsg);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
emit({ step: 'cf-project', status: 'error', message: 'Failed to create Pages project', detail: err.message });
|
|
108
|
+
return { success: false, resources, outputs, files, error: err.message };
|
|
109
|
+
}
|
|
110
|
+
// Step 3: Add custom domain if hostname provided
|
|
111
|
+
const projectName = outputs['CF_PROJECT_NAME'] || slug;
|
|
112
|
+
if (ctx.hostname && accountId) {
|
|
113
|
+
emit({ step: 'cf-domain', status: 'started', message: `Adding domain ${ctx.hostname} to Pages project` });
|
|
114
|
+
try {
|
|
115
|
+
const domainBody = JSON.stringify({ name: ctx.hostname });
|
|
116
|
+
const domainRes = await httpsPost('api.cloudflare.com', `/client/v4/accounts/${accountId}/pages/projects/${projectName}/domains`, headers, domainBody);
|
|
117
|
+
if (domainRes.status === 200 || domainRes.status === 201) {
|
|
118
|
+
outputs['CF_CUSTOM_DOMAIN'] = ctx.hostname;
|
|
119
|
+
emit({ step: 'cf-domain', status: 'done', message: `Domain "${ctx.hostname}" added to Pages project` });
|
|
120
|
+
}
|
|
121
|
+
else if (domainRes.status === 409) {
|
|
122
|
+
emit({ step: 'cf-domain', status: 'done', message: `Domain "${ctx.hostname}" already configured on Pages project` });
|
|
123
|
+
outputs['CF_CUSTOM_DOMAIN'] = ctx.hostname;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
const errData = safeJsonParse(domainRes.body);
|
|
127
|
+
throw new Error(errData?.errors?.[0]?.message || `Pages domains API returned ${domainRes.status}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
emit({ step: 'cf-domain', status: 'error', message: 'Failed to add domain to Pages project', detail: err.message });
|
|
132
|
+
// Non-fatal
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Step 4: Create D1 database if requested
|
|
136
|
+
if (ctx.database === 'sqlite') {
|
|
137
|
+
emit({ step: 'cf-d1', status: 'started', message: 'Creating D1 database' });
|
|
138
|
+
try {
|
|
139
|
+
await recordResourcePending(ctx.runId, 'cf-d1-database', `${slug}-db`, 'global');
|
|
140
|
+
const body = JSON.stringify({ name: `${slug}-db` });
|
|
141
|
+
const res = await httpsPost('api.cloudflare.com', `/client/v4/accounts/${accountId}/d1/database`, headers, body);
|
|
142
|
+
if (res.status === 200 || res.status === 201) {
|
|
143
|
+
const data = safeJsonParse(res.body);
|
|
144
|
+
const dbId = data?.result?.uuid ?? '';
|
|
145
|
+
if (dbId) {
|
|
146
|
+
resources.push({ type: 'cf-d1-database', id: dbId, region: 'global' });
|
|
147
|
+
await recordResourceCreated(ctx.runId, 'cf-d1-database', dbId, 'global');
|
|
148
|
+
outputs['CF_D1_DATABASE_ID'] = dbId;
|
|
149
|
+
outputs['CF_D1_DATABASE_NAME'] = `${slug}-db`;
|
|
150
|
+
}
|
|
151
|
+
emit({ step: 'cf-d1', status: 'done', message: `D1 database "${slug}-db" created` });
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
const data = safeJsonParse(res.body);
|
|
155
|
+
throw new Error(data?.errors?.[0]?.message || `D1 creation returned ${res.status}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
emit({ step: 'cf-d1', status: 'error', message: 'Failed to create D1 database', detail: err.message });
|
|
160
|
+
// Non-fatal
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
emit({ step: 'cf-d1', status: 'skipped', message: 'No database requested' });
|
|
165
|
+
}
|
|
166
|
+
// Step 5: Generate wrangler.toml
|
|
167
|
+
emit({ step: 'cf-config', status: 'started', message: 'Generating wrangler.toml' });
|
|
168
|
+
try {
|
|
169
|
+
let config = `# wrangler.toml — Cloudflare Workers/Pages configuration
|
|
170
|
+
# Generated by VoidForge
|
|
171
|
+
# Deploy with: npx wrangler pages deploy ./dist
|
|
172
|
+
|
|
173
|
+
name = "${slug}"
|
|
174
|
+
compatibility_date = "${new Date().toISOString().slice(0, 10)}"
|
|
175
|
+
pages_build_output_dir = "./dist"
|
|
176
|
+
|
|
177
|
+
[vars]
|
|
178
|
+
ENVIRONMENT = "production"
|
|
179
|
+
`;
|
|
180
|
+
if (outputs['CF_D1_DATABASE_ID']) {
|
|
181
|
+
config += `
|
|
182
|
+
[[d1_databases]]
|
|
183
|
+
binding = "DB"
|
|
184
|
+
database_name = "${slug}-db"
|
|
185
|
+
database_id = "${outputs['CF_D1_DATABASE_ID']}"
|
|
186
|
+
`;
|
|
187
|
+
}
|
|
188
|
+
await writeFile(join(ctx.projectDir, 'wrangler.toml'), config, 'utf-8');
|
|
189
|
+
files.push('wrangler.toml');
|
|
190
|
+
emit({ step: 'cf-config', status: 'done', message: 'Generated wrangler.toml' });
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
emit({ step: 'cf-config', status: 'error', message: 'Failed to write wrangler.toml', detail: err.message });
|
|
194
|
+
}
|
|
195
|
+
// Step 6: Poll for deployment (if GitHub-linked, deploy triggered by push)
|
|
196
|
+
if (ghOwner && ghRepo && accountId) {
|
|
197
|
+
emit({ step: 'cf-deploy', status: 'started', message: 'Waiting for Cloudflare Pages deployment...' });
|
|
198
|
+
try {
|
|
199
|
+
const start = Date.now();
|
|
200
|
+
let deployUrl = '';
|
|
201
|
+
while (Date.now() - start < DEPLOY_POLL_TIMEOUT_MS) {
|
|
202
|
+
await new Promise(r => setTimeout(r, DEPLOY_POLL_INTERVAL_MS));
|
|
203
|
+
if (ctx.abortSignal?.aborted)
|
|
204
|
+
break;
|
|
205
|
+
const depRes = await httpsGet('api.cloudflare.com', `/client/v4/accounts/${accountId}/pages/projects/${projectName}/deployments?sort_by=created_on&sort_order=desc&per_page=1`, { 'Authorization': `Bearer ${token}` });
|
|
206
|
+
if (depRes.status !== 200)
|
|
207
|
+
continue;
|
|
208
|
+
const depData = safeJsonParse(depRes.body);
|
|
209
|
+
const latest = depData?.result?.[0];
|
|
210
|
+
if (!latest)
|
|
211
|
+
continue;
|
|
212
|
+
const stage = latest.latest_stage;
|
|
213
|
+
if (stage?.name === 'deploy' && stage?.status === 'success') {
|
|
214
|
+
deployUrl = latest.url ? `https://${latest.url}` : outputs['CF_PROJECT_URL'] || '';
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
if (stage?.status === 'failure') {
|
|
218
|
+
emit({ step: 'cf-deploy', status: 'error', message: 'Cloudflare Pages deployment failed', detail: 'Check the Cloudflare dashboard for build logs' });
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
const elapsed = Math.round((Date.now() - start) / 1000);
|
|
222
|
+
if (elapsed % 15 === 0) {
|
|
223
|
+
emit({ step: 'cf-deploy', status: 'started', message: `Deploy status: ${stage?.name || 'queued'} / ${stage?.status || 'waiting'}... (${elapsed}s)` });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (deployUrl) {
|
|
227
|
+
outputs['DEPLOY_URL'] = deployUrl;
|
|
228
|
+
emit({ step: 'cf-deploy', status: 'done', message: `Live at ${deployUrl}` });
|
|
229
|
+
}
|
|
230
|
+
else if (!ctx.abortSignal?.aborted) {
|
|
231
|
+
emit({ step: 'cf-deploy', status: 'error', message: 'Deployment polling timed out — check Cloudflare dashboard' });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
emit({ step: 'cf-deploy', status: 'error', message: 'Failed to poll deployment', detail: err.message });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else if (!ghOwner || !ghRepo) {
|
|
239
|
+
emit({ step: 'cf-deploy', status: 'skipped', message: 'No GitHub repo linked — deploy manually with: npx wrangler pages deploy ./dist' });
|
|
240
|
+
}
|
|
241
|
+
// Step 7: Write .env
|
|
242
|
+
emit({ step: 'cf-env', status: 'started', message: 'Writing Cloudflare config to .env' });
|
|
243
|
+
try {
|
|
244
|
+
const envLines = [
|
|
245
|
+
`# VoidForge Cloudflare — generated ${new Date().toISOString()}`,
|
|
246
|
+
`CF_ACCOUNT_ID=${accountId}`,
|
|
247
|
+
`CF_PROJECT_NAME=${outputs['CF_PROJECT_NAME'] || slug}`,
|
|
248
|
+
];
|
|
249
|
+
if (outputs['CF_PROJECT_URL'])
|
|
250
|
+
envLines.push(`CF_PROJECT_URL=${outputs['CF_PROJECT_URL']}`);
|
|
251
|
+
if (outputs['CF_D1_DATABASE_ID'])
|
|
252
|
+
envLines.push(`CF_D1_DATABASE_ID=${outputs['CF_D1_DATABASE_ID']}`);
|
|
253
|
+
if (outputs['DEPLOY_URL'])
|
|
254
|
+
envLines.push(`DEPLOY_URL=${outputs['DEPLOY_URL']}`);
|
|
255
|
+
envLines.push(ghOwner ? '# Auto-deploys on push to main' : '# Deploy with: npx wrangler pages deploy ./dist');
|
|
256
|
+
await appendEnvSection(ctx.projectDir, envLines);
|
|
257
|
+
emit({ step: 'cf-env', status: 'done', message: 'Cloudflare config written to .env' });
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
emit({ step: 'cf-env', status: 'error', message: 'Failed to write .env', detail: err.message });
|
|
261
|
+
}
|
|
262
|
+
return { success: true, resources, outputs, files };
|
|
263
|
+
},
|
|
264
|
+
async cleanup(resources, credentials) {
|
|
265
|
+
const token = credentials['cloudflare-api-token'];
|
|
266
|
+
if (!token)
|
|
267
|
+
return;
|
|
268
|
+
// Need account ID for API calls — fetch it
|
|
269
|
+
let accountId = '';
|
|
270
|
+
try {
|
|
271
|
+
const res = await httpsGet('api.cloudflare.com', '/client/v4/accounts?page=1&per_page=1', {
|
|
272
|
+
'Authorization': `Bearer ${token}`,
|
|
273
|
+
});
|
|
274
|
+
const data = safeJsonParse(res.body);
|
|
275
|
+
accountId = data?.result?.[0]?.id ?? '';
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (!accountId)
|
|
281
|
+
return;
|
|
282
|
+
for (const resource of [...resources].reverse()) {
|
|
283
|
+
try {
|
|
284
|
+
switch (resource.type) {
|
|
285
|
+
case 'cf-d1-database': {
|
|
286
|
+
await httpsDelete('api.cloudflare.com', `/client/v4/accounts/${accountId}/d1/database/${resource.id}`, { 'Authorization': `Bearer ${token}` });
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
case 'cf-pages-project': {
|
|
290
|
+
await httpsDelete('api.cloudflare.com', `/client/v4/accounts/${accountId}/pages/projects/${resource.id}`, { 'Authorization': `Bearer ${token}` });
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
console.error(`Failed to cleanup ${resource.type} ${resource.id}:`, err.message);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker provisioner — generates Dockerfile + docker-compose.yml locally.
|
|
3
|
+
* No cloud API calls. Files only.
|
|
4
|
+
*/
|
|
5
|
+
import { writeFile } from 'node:fs/promises';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { generateDockerfile, generateDockerignore } from './scripts/dockerfile.js';
|
|
8
|
+
import { generateDockerCompose } from './scripts/docker-compose.js';
|
|
9
|
+
export const dockerProvisioner = {
|
|
10
|
+
async validate(ctx) {
|
|
11
|
+
const errors = [];
|
|
12
|
+
if (!ctx.projectDir)
|
|
13
|
+
errors.push('Project directory is required');
|
|
14
|
+
if (!ctx.projectName)
|
|
15
|
+
errors.push('Project name is required');
|
|
16
|
+
return errors;
|
|
17
|
+
},
|
|
18
|
+
async provision(ctx, emit) {
|
|
19
|
+
const files = [];
|
|
20
|
+
const framework = ctx.framework || 'express';
|
|
21
|
+
// Step 1: Generate Dockerfile
|
|
22
|
+
emit({ step: 'dockerfile', status: 'started', message: 'Generating Dockerfile' });
|
|
23
|
+
try {
|
|
24
|
+
const dockerfile = generateDockerfile(framework);
|
|
25
|
+
const dockerfilePath = join(ctx.projectDir, 'Dockerfile');
|
|
26
|
+
await writeFile(dockerfilePath, dockerfile, 'utf-8');
|
|
27
|
+
files.push('Dockerfile');
|
|
28
|
+
emit({ step: 'dockerfile', status: 'done', message: `Dockerfile generated (${framework})` });
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
emit({ step: 'dockerfile', status: 'error', message: 'Failed to write Dockerfile', detail: err.message });
|
|
32
|
+
return { success: false, resources: [], outputs: {}, files, error: err.message };
|
|
33
|
+
}
|
|
34
|
+
// Step 2: Generate docker-compose.yml
|
|
35
|
+
emit({ step: 'docker-compose', status: 'started', message: 'Generating docker-compose.yml' });
|
|
36
|
+
try {
|
|
37
|
+
const compose = generateDockerCompose({
|
|
38
|
+
projectName: ctx.projectName,
|
|
39
|
+
framework,
|
|
40
|
+
database: ctx.database || 'none',
|
|
41
|
+
cache: ctx.cache || 'none',
|
|
42
|
+
});
|
|
43
|
+
const composePath = join(ctx.projectDir, 'docker-compose.yml');
|
|
44
|
+
await writeFile(composePath, compose, 'utf-8');
|
|
45
|
+
files.push('docker-compose.yml');
|
|
46
|
+
emit({ step: 'docker-compose', status: 'done', message: 'docker-compose.yml generated' });
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
emit({ step: 'docker-compose', status: 'error', message: 'Failed to write docker-compose.yml', detail: err.message });
|
|
50
|
+
return { success: false, resources: [], outputs: {}, files, error: err.message };
|
|
51
|
+
}
|
|
52
|
+
// Step 3: Generate .dockerignore
|
|
53
|
+
emit({ step: 'dockerignore', status: 'started', message: 'Generating .dockerignore' });
|
|
54
|
+
try {
|
|
55
|
+
const ignore = generateDockerignore();
|
|
56
|
+
const ignorePath = join(ctx.projectDir, '.dockerignore');
|
|
57
|
+
await writeFile(ignorePath, ignore, 'utf-8');
|
|
58
|
+
files.push('.dockerignore');
|
|
59
|
+
emit({ step: 'dockerignore', status: 'done', message: '.dockerignore generated' });
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
emit({ step: 'dockerignore', status: 'error', message: 'Failed to write .dockerignore', detail: err.message });
|
|
63
|
+
return { success: false, resources: [], outputs: {}, files, error: err.message };
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
success: true,
|
|
67
|
+
resources: [],
|
|
68
|
+
outputs: {},
|
|
69
|
+
files,
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
async cleanup(_resources, _credentials) {
|
|
73
|
+
// Docker provisioner creates local files only — nothing to clean up
|
|
74
|
+
},
|
|
75
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTTPS client for provisioner API calls.
|
|
3
|
+
* Uses raw node:https — no dependencies.
|
|
4
|
+
*
|
|
5
|
+
* Security: All callers hardcode hostnames (api.cloudflare.com, etc.).
|
|
6
|
+
* Never pass user-controlled input as the hostname parameter.
|
|
7
|
+
*/
|
|
8
|
+
interface HttpResponse {
|
|
9
|
+
status: number;
|
|
10
|
+
body: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function httpsGet(hostname: string, path: string, headers: Record<string, string>, timeout?: number): Promise<HttpResponse>;
|
|
13
|
+
export declare function httpsPost(hostname: string, path: string, headers: Record<string, string>, body?: string, timeout?: number): Promise<HttpResponse>;
|
|
14
|
+
export declare function httpsPut(hostname: string, path: string, headers: Record<string, string>, body?: string, timeout?: number): Promise<HttpResponse>;
|
|
15
|
+
export declare function httpsDelete(hostname: string, path: string, headers: Record<string, string>, timeout?: number): Promise<HttpResponse>;
|
|
16
|
+
/** Slugify a name for use as a cloud resource identifier. Strips non-alphanumeric, trims hyphens. */
|
|
17
|
+
export declare function slugify(name: string): string;
|
|
18
|
+
/** Safely parse JSON — returns null on invalid input instead of throwing. */
|
|
19
|
+
export declare function safeJsonParse(body: string): unknown;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTTPS client for provisioner API calls.
|
|
3
|
+
* Uses raw node:https — no dependencies.
|
|
4
|
+
*
|
|
5
|
+
* Security: All callers hardcode hostnames (api.cloudflare.com, etc.).
|
|
6
|
+
* Never pass user-controlled input as the hostname parameter.
|
|
7
|
+
*/
|
|
8
|
+
import { request as httpsRequest } from 'node:https';
|
|
9
|
+
export function httpsGet(hostname, path, headers, timeout) {
|
|
10
|
+
return httpsCallWithRetry('GET', hostname, path, headers, undefined, timeout);
|
|
11
|
+
}
|
|
12
|
+
export function httpsPost(hostname, path, headers, body, timeout) {
|
|
13
|
+
return httpsCallWithRetry('POST', hostname, path, headers, body, timeout);
|
|
14
|
+
}
|
|
15
|
+
export function httpsPut(hostname, path, headers, body, timeout) {
|
|
16
|
+
return httpsCallWithRetry('PUT', hostname, path, headers, body, timeout);
|
|
17
|
+
}
|
|
18
|
+
export function httpsDelete(hostname, path, headers, timeout) {
|
|
19
|
+
return httpsCallWithRetry('DELETE', hostname, path, headers, undefined, timeout);
|
|
20
|
+
}
|
|
21
|
+
/** Slugify a name for use as a cloud resource identifier. Strips non-alphanumeric, trims hyphens. */
|
|
22
|
+
export function slugify(name) {
|
|
23
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '').slice(0, 40);
|
|
24
|
+
return slug || 'voidforge-project';
|
|
25
|
+
}
|
|
26
|
+
/** Safely parse JSON — returns null on invalid input instead of throwing. */
|
|
27
|
+
export function safeJsonParse(body) {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(body);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const DEFAULT_TIMEOUT = 30000;
|
|
36
|
+
const MAX_RETRIES = 1;
|
|
37
|
+
const RETRY_DELAY_MS = 2000;
|
|
38
|
+
const TRANSIENT_CODES = ['ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND', 'EPIPE'];
|
|
39
|
+
/**
|
|
40
|
+
* Wrapper around httpsCall that retries once on transient network errors.
|
|
41
|
+
* Transient errors: ECONNRESET, ETIMEDOUT, ENOTFOUND, EPIPE, socket hang up.
|
|
42
|
+
*/
|
|
43
|
+
async function httpsCallWithRetry(method, hostname, path, headers, body, timeout) {
|
|
44
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
45
|
+
try {
|
|
46
|
+
return await httpsCall(method, hostname, path, headers, body, timeout);
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
const code = err.code;
|
|
50
|
+
const isTransient = TRANSIENT_CODES.includes(code || '') ||
|
|
51
|
+
err.message.includes('socket hang up');
|
|
52
|
+
if (attempt < MAX_RETRIES && isTransient) {
|
|
53
|
+
await new Promise(r => setTimeout(r, RETRY_DELAY_MS));
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Unreachable — the loop always returns or throws — but TypeScript needs this
|
|
60
|
+
throw new Error('Retry loop exited unexpectedly');
|
|
61
|
+
}
|
|
62
|
+
function httpsCall(method, hostname, path, headers, body, timeout) {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const opts = { hostname, path, method, headers, timeout: timeout ?? DEFAULT_TIMEOUT };
|
|
65
|
+
if (body) {
|
|
66
|
+
headers['Content-Length'] = String(Buffer.byteLength(body));
|
|
67
|
+
}
|
|
68
|
+
const req = httpsRequest(opts, (res) => {
|
|
69
|
+
let data = '';
|
|
70
|
+
res.on('data', (chunk) => { data += chunk.toString(); });
|
|
71
|
+
res.on('end', () => resolve({ status: res.statusCode ?? 0, body: data }));
|
|
72
|
+
});
|
|
73
|
+
req.on('error', reject);
|
|
74
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Request timed out')); });
|
|
75
|
+
if (body)
|
|
76
|
+
req.write(body);
|
|
77
|
+
req.end();
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Railway provisioner — creates a real Railway project via GraphQL API + generates railway.toml.
|
|
3
|
+
* v3.8.0: Creates service with GitHub source, sets env vars, polls deploy (ADR-015).
|
|
4
|
+
*/
|
|
5
|
+
import type { Provisioner } from './types.js';
|
|
6
|
+
export declare const railwayProvisioner: Provisioner;
|