@treeseed/sdk 0.10.22 → 0.10.24
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/db/market-schema.js +3 -2
- package/dist/market-client.d.ts +4 -0
- package/dist/market-client.js +6 -0
- package/dist/operations/providers/default.js +26 -4
- package/dist/operations/repository-operations.js +6 -2
- package/dist/operations/services/bootstrap-runner.d.ts +5 -1
- package/dist/operations/services/bootstrap-runner.js +34 -5
- package/dist/operations/services/config-runtime.d.ts +2 -1
- package/dist/operations/services/deploy.d.ts +18 -1
- package/dist/operations/services/deploy.js +176 -24
- package/dist/operations/services/github-automation.d.ts +10 -1
- package/dist/operations/services/github-automation.js +18 -4
- package/dist/operations/services/hosting-audit.d.ts +2 -1
- package/dist/operations/services/hosting-audit.js +12 -1
- package/dist/operations/services/hub-launch.d.ts +1 -0
- package/dist/operations/services/hub-launch.js +1 -0
- package/dist/operations/services/hub-provider-launch.d.ts +9 -0
- package/dist/operations/services/hub-provider-launch.js +140 -40
- package/dist/operations/services/managed-host-security.d.ts +1 -1
- package/dist/operations/services/managed-host-security.js +4 -1
- package/dist/operations/services/project-platform.d.ts +25 -0
- package/dist/operations/services/project-platform.js +91 -23
- package/dist/operations/services/railway-api.js +2 -1
- package/dist/operations/services/railway-deploy.d.ts +32 -2
- package/dist/operations/services/railway-deploy.js +94 -27
- package/dist/operations/services/template-registry.js +33 -3
- package/dist/platform/contracts.d.ts +1 -0
- package/dist/platform/deploy-config.js +8 -1
- package/dist/platform/deploy-runtime.js +1 -0
- package/dist/platform/environment.d.ts +1 -1
- package/dist/platform/environment.js +1 -1
- package/dist/reconcile/builtin-adapters.js +155 -25
- package/dist/reconcile/contracts.d.ts +1 -1
- package/dist/reconcile/desired-state.js +17 -1
- package/dist/reconcile/engine.d.ts +2 -0
- package/dist/reconcile/engine.js +58 -3
- package/dist/reconcile/units.js +1 -0
- package/dist/sdk-types.d.ts +1 -1
- package/dist/sdk-types.js +2 -0
- package/dist/timing.d.ts +20 -0
- package/dist/timing.js +73 -0
- package/dist/treeseed/template-catalog/catalog.fixture.json +150 -0
- package/dist/workflow/operations.d.ts +2 -0
- package/drizzle/market/0000_market_control_plane.sql +3 -3
- package/drizzle/market/0003_project_team_slug_unique.sql +4 -0
- package/package.json +1 -1
- package/templates/github/deploy-web.workflow.yml +4 -0
|
@@ -41,6 +41,13 @@ function ensureParent(filePath) {
|
|
|
41
41
|
function stableHash(value) {
|
|
42
42
|
return createHash("sha256").update(value).digest("hex");
|
|
43
43
|
}
|
|
44
|
+
function compactDeploymentKey(input) {
|
|
45
|
+
const rawKey = sanitizeResourceKey(input.rawKey ?? "");
|
|
46
|
+
if (rawKey && rawKey.length <= 40) return rawKey;
|
|
47
|
+
const base = sanitizeSegment(input.slug ?? input.projectSegment ?? "project").slice(0, 27) || "project";
|
|
48
|
+
const hash = stableHash(`${input.teamId ?? ""}:${input.projectId ?? ""}:${input.slug ?? ""}`).slice(0, 8);
|
|
49
|
+
return `${base}-${hash}`;
|
|
50
|
+
}
|
|
44
51
|
function readJson(filePath, fallback) {
|
|
45
52
|
if (!existsSync(filePath)) {
|
|
46
53
|
return fallback;
|
|
@@ -69,6 +76,9 @@ function loadTenantDeployConfig(tenantRoot) {
|
|
|
69
76
|
function sanitizeSegment(value) {
|
|
70
77
|
return String(value).trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-").slice(0, 36) || "default";
|
|
71
78
|
}
|
|
79
|
+
function sanitizeResourceKey(value) {
|
|
80
|
+
return String(value).trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "");
|
|
81
|
+
}
|
|
72
82
|
function requireConfiguredIdentityValue(value, label) {
|
|
73
83
|
const normalized = typeof value === "string" && value.trim() ? value.trim() : "";
|
|
74
84
|
if (!normalized) {
|
|
@@ -87,7 +97,13 @@ function resolveTreeseedResourceIdentity(deployConfig, target) {
|
|
|
87
97
|
);
|
|
88
98
|
const teamSegment = sanitizeSegment(teamId);
|
|
89
99
|
const projectSegment = sanitizeSegment(projectId);
|
|
90
|
-
const deploymentKey =
|
|
100
|
+
const deploymentKey = compactDeploymentKey({
|
|
101
|
+
rawKey: `${teamSegment}-${projectSegment}`,
|
|
102
|
+
teamId,
|
|
103
|
+
projectId,
|
|
104
|
+
projectSegment,
|
|
105
|
+
slug: deployConfig.slug
|
|
106
|
+
});
|
|
91
107
|
const environment = target.kind === "persistent" ? target.scope : target.branchName;
|
|
92
108
|
const environmentSegment = target.kind === "persistent" ? target.scope : sanitizeSegment(target.branchName);
|
|
93
109
|
return {
|
|
@@ -168,6 +184,13 @@ function resolveConfiguredSurfaceBaseUrl(deployConfig, target, surface) {
|
|
|
168
184
|
}
|
|
169
185
|
return null;
|
|
170
186
|
}
|
|
187
|
+
function configuredSurfaceHosts(deployConfig, target, surface) {
|
|
188
|
+
const hosts = [
|
|
189
|
+
resolveConfiguredSurfaceDomain(deployConfig, target, surface),
|
|
190
|
+
surface === "web" && target.kind === "persistent" && target.scope === "prod" ? primaryHost(deployConfig.surfaces?.web?.publicBaseUrl ?? deployConfig.siteUrl) : null
|
|
191
|
+
].filter(Boolean);
|
|
192
|
+
return [...new Set(hosts)];
|
|
193
|
+
}
|
|
171
194
|
function sharedDeploymentName(identity, role = "") {
|
|
172
195
|
const roleSegment = role === "workdayManager" ? "workday-manager" : role === "workerRunner" ? "worker-runner-01" : role;
|
|
173
196
|
return role ? `${identity.deploymentKey}-${sanitizeSegment(roleSegment)}` : identity.deploymentKey;
|
|
@@ -419,7 +442,7 @@ function buildSecretMap(deployConfig, state) {
|
|
|
419
442
|
return {
|
|
420
443
|
TREESEED_FORM_TOKEN_SECRET: envOrNull("TREESEED_FORM_TOKEN_SECRET") ?? generatedSecret,
|
|
421
444
|
TREESEED_EDITORIAL_PREVIEW_SECRET: envOrNull("TREESEED_EDITORIAL_PREVIEW_SECRET") ?? previewSecret,
|
|
422
|
-
TREESEED_TURNSTILE_SECRET_KEY: envOrNull("TREESEED_TURNSTILE_SECRET_KEY"),
|
|
445
|
+
TREESEED_TURNSTILE_SECRET_KEY: state.turnstileWidgets?.formGuard?.secret ?? envOrNull("TREESEED_TURNSTILE_SECRET_KEY"),
|
|
423
446
|
TREESEED_SMTP_PASSWORD: envOrNull("TREESEED_SMTP_PASSWORD")
|
|
424
447
|
};
|
|
425
448
|
}
|
|
@@ -431,6 +454,8 @@ function defaultStateFromConfig(deployConfig, target) {
|
|
|
431
454
|
const contentPreviewRootTemplate = deployConfig.cloudflare.r2?.previewRootTemplate ?? "teams/{teamId}/previews";
|
|
432
455
|
const contentDefaultTeamId = identity.teamId;
|
|
433
456
|
const contentManifestKey = contentManifestKeyTemplate.replaceAll("{teamId}", contentDefaultTeamId);
|
|
457
|
+
const turnstileName = environmentScopedIdentityName(identity, "turnstile", target);
|
|
458
|
+
const turnstileDomains = configuredSurfaceHosts(deployConfig, target, "web");
|
|
434
459
|
return {
|
|
435
460
|
version: 2,
|
|
436
461
|
target,
|
|
@@ -469,6 +494,17 @@ function defaultStateFromConfig(deployConfig, target) {
|
|
|
469
494
|
buildOutputDir: deployConfig.cloudflare.pages?.buildOutputDir ?? "dist",
|
|
470
495
|
url: resolveConfiguredSurfaceBaseUrl(deployConfig, target, "web")
|
|
471
496
|
},
|
|
497
|
+
turnstileWidgets: {
|
|
498
|
+
formGuard: {
|
|
499
|
+
name: turnstileName,
|
|
500
|
+
sitekey: null,
|
|
501
|
+
secret: null,
|
|
502
|
+
mode: "managed",
|
|
503
|
+
domains: turnstileDomains,
|
|
504
|
+
managed: true,
|
|
505
|
+
lastSyncedAt: null
|
|
506
|
+
}
|
|
507
|
+
},
|
|
472
508
|
content: {
|
|
473
509
|
runtimeProvider: deployConfig.providers?.content?.runtime ?? "team_scoped_r2_overlay",
|
|
474
510
|
publishProvider: deployConfig.providers?.content?.publish ?? deployConfig.providers?.content?.runtime ?? "team_scoped_r2_overlay",
|
|
@@ -636,6 +672,23 @@ function loadDeployState(tenantRoot, deployConfig, options = {}) {
|
|
|
636
672
|
...defaults.generatedSecrets ?? {},
|
|
637
673
|
...persisted.generatedSecrets ?? {}
|
|
638
674
|
},
|
|
675
|
+
turnstileWidgets: {
|
|
676
|
+
...defaults.turnstileWidgets ?? {},
|
|
677
|
+
...persisted.turnstileWidgets ?? {},
|
|
678
|
+
formGuard: {
|
|
679
|
+
...defaults.turnstileWidgets?.formGuard ?? {},
|
|
680
|
+
...persisted.turnstileWidgets?.formGuard ?? {},
|
|
681
|
+
name: defaults.turnstileWidgets?.formGuard?.name ?? persisted.turnstileWidgets?.formGuard?.name ?? null,
|
|
682
|
+
mode: "managed",
|
|
683
|
+
managed: true,
|
|
684
|
+
domains: [
|
|
685
|
+
...new Set([
|
|
686
|
+
...Array.isArray(defaults.turnstileWidgets?.formGuard?.domains) ? defaults.turnstileWidgets.formGuard.domains : [],
|
|
687
|
+
...Array.isArray(persisted.turnstileWidgets?.formGuard?.domains) ? persisted.turnstileWidgets.formGuard.domains : []
|
|
688
|
+
].filter(Boolean))
|
|
689
|
+
]
|
|
690
|
+
}
|
|
691
|
+
},
|
|
639
692
|
content: {
|
|
640
693
|
...defaults.content ?? {},
|
|
641
694
|
...persisted.content ?? {},
|
|
@@ -926,6 +979,68 @@ function listPagesProjects(tenantRoot, env) {
|
|
|
926
979
|
});
|
|
927
980
|
return Array.isArray(payload?.result) ? payload.result : [];
|
|
928
981
|
}
|
|
982
|
+
function listTurnstileWidgets(tenantRoot, env) {
|
|
983
|
+
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
984
|
+
if (!accountId) {
|
|
985
|
+
return [];
|
|
986
|
+
}
|
|
987
|
+
const payload = cloudflareApiRequest(`/accounts/${encodeURIComponent(accountId)}/challenges/widgets?per_page=100`, {
|
|
988
|
+
env,
|
|
989
|
+
allowFailure: true
|
|
990
|
+
});
|
|
991
|
+
return Array.isArray(payload?.result) ? payload.result : [];
|
|
992
|
+
}
|
|
993
|
+
function getTurnstileWidget(env, sitekey) {
|
|
994
|
+
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
995
|
+
if (!accountId || !sitekey) {
|
|
996
|
+
return null;
|
|
997
|
+
}
|
|
998
|
+
const payload = cloudflareApiRequest(
|
|
999
|
+
`/accounts/${encodeURIComponent(accountId)}/challenges/widgets/${encodeURIComponent(sitekey)}`,
|
|
1000
|
+
{ env, allowFailure: true }
|
|
1001
|
+
);
|
|
1002
|
+
return payload?.result ?? null;
|
|
1003
|
+
}
|
|
1004
|
+
function createTurnstileWidget(env, input) {
|
|
1005
|
+
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
1006
|
+
if (!accountId) {
|
|
1007
|
+
throw new Error("Configure CLOUDFLARE_ACCOUNT_ID before creating Turnstile widgets.");
|
|
1008
|
+
}
|
|
1009
|
+
try {
|
|
1010
|
+
return cloudflareApiRequest(`/accounts/${encodeURIComponent(accountId)}/challenges/widgets`, {
|
|
1011
|
+
method: "POST",
|
|
1012
|
+
env,
|
|
1013
|
+
body: {
|
|
1014
|
+
name: input.name,
|
|
1015
|
+
domains: input.domains ?? [],
|
|
1016
|
+
mode: input.mode ?? "managed"
|
|
1017
|
+
}
|
|
1018
|
+
})?.result ?? null;
|
|
1019
|
+
} catch (error) {
|
|
1020
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1021
|
+
throw new Error(`Cloudflare Turnstile widget creation failed. Ensure the API token has Turnstile Sites Write permission: ${detail}`);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
function updateTurnstileWidget(env, sitekey, input) {
|
|
1025
|
+
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
1026
|
+
if (!accountId || !sitekey) {
|
|
1027
|
+
throw new Error("Configure CLOUDFLARE_ACCOUNT_ID and sitekey before updating Turnstile widgets.");
|
|
1028
|
+
}
|
|
1029
|
+
try {
|
|
1030
|
+
return cloudflareApiRequest(`/accounts/${encodeURIComponent(accountId)}/challenges/widgets/${encodeURIComponent(sitekey)}`, {
|
|
1031
|
+
method: "PUT",
|
|
1032
|
+
env,
|
|
1033
|
+
body: {
|
|
1034
|
+
name: input.name,
|
|
1035
|
+
domains: input.domains ?? [],
|
|
1036
|
+
mode: input.mode ?? "managed"
|
|
1037
|
+
}
|
|
1038
|
+
})?.result ?? null;
|
|
1039
|
+
} catch (error) {
|
|
1040
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1041
|
+
throw new Error(`Cloudflare Turnstile widget update failed. Ensure the API token has Turnstile Sites Write permission: ${detail}`);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
929
1044
|
function buildCloudflarePagesFunctionBindings(state) {
|
|
930
1045
|
const kvNamespaces = Object.fromEntries(
|
|
931
1046
|
Object.entries(state.kvNamespaces ?? {}).map(([key, namespace]) => {
|
|
@@ -1006,6 +1121,7 @@ function buildProvisioningSummary(deployConfig, state, target) {
|
|
|
1006
1121
|
siteUrl: target.kind === "branch" ? targetWorkersDevUrl(state.workerName) : deployConfig.siteUrl,
|
|
1007
1122
|
accountId: resolveConfiguredCloudflareAccountId(deployConfig),
|
|
1008
1123
|
pages: state.pages ?? null,
|
|
1124
|
+
turnstileWidget: state.turnstileWidgets?.formGuard ?? null,
|
|
1009
1125
|
formGuardKv: state.kvNamespaces.FORM_GUARD_KV,
|
|
1010
1126
|
sessionKv: state.kvNamespaces.SESSION ?? null,
|
|
1011
1127
|
siteDataDb: state.d1Databases.SITE_DATA_DB,
|
|
@@ -1017,6 +1133,7 @@ function buildProvisioningSummary(deployConfig, state, target) {
|
|
|
1017
1133
|
queue: state.queues?.agentWork?.name ?? null,
|
|
1018
1134
|
dlq: state.queues?.agentWork?.dlqName ?? null,
|
|
1019
1135
|
database: state.d1Databases?.SITE_DATA_DB?.databaseName ?? null,
|
|
1136
|
+
turnstileWidget: state.turnstileWidgets?.formGuard?.name ?? null,
|
|
1020
1137
|
formGuardKv: state.kvNamespaces?.FORM_GUARD_KV?.name ?? null,
|
|
1021
1138
|
railwayProject: state.services?.worker?.projectName ?? state.services?.api?.projectName ?? null,
|
|
1022
1139
|
webDomain: configuredWebDomain,
|
|
@@ -1533,14 +1650,7 @@ function resolveConfiguredContentPublicBaseUrl(deployConfig) {
|
|
|
1533
1650
|
return envOrNull("TREESEED_CONTENT_PUBLIC_BASE_URL") ?? deployConfig.cloudflare.r2?.publicBaseUrl ?? "";
|
|
1534
1651
|
}
|
|
1535
1652
|
function missingTurnstileRequirements() {
|
|
1536
|
-
|
|
1537
|
-
if (!envOrNull("TREESEED_PUBLIC_TURNSTILE_SITE_KEY")) {
|
|
1538
|
-
issues.push("Set TREESEED_PUBLIC_TURNSTILE_SITE_KEY before deploying.");
|
|
1539
|
-
}
|
|
1540
|
-
if (!envOrNull("TREESEED_TURNSTILE_SECRET_KEY")) {
|
|
1541
|
-
issues.push("Set TREESEED_TURNSTILE_SECRET_KEY before deploying.");
|
|
1542
|
-
}
|
|
1543
|
-
return issues;
|
|
1653
|
+
return [];
|
|
1544
1654
|
}
|
|
1545
1655
|
function missingContentRuntimeRequirements(deployConfig) {
|
|
1546
1656
|
const issues = [];
|
|
@@ -1564,20 +1674,6 @@ function collectMissingDeployInputs(tenantRoot) {
|
|
|
1564
1674
|
message: "Cloudflare account ID is missing. Set CLOUDFLARE_ACCOUNT_ID with treeseed config or provide it now."
|
|
1565
1675
|
});
|
|
1566
1676
|
}
|
|
1567
|
-
if (!envOrNull("TREESEED_PUBLIC_TURNSTILE_SITE_KEY")) {
|
|
1568
|
-
missing.push({
|
|
1569
|
-
key: "TREESEED_PUBLIC_TURNSTILE_SITE_KEY",
|
|
1570
|
-
label: "Turnstile public site key",
|
|
1571
|
-
message: "Turnstile public site key is missing for deploy."
|
|
1572
|
-
});
|
|
1573
|
-
}
|
|
1574
|
-
if (!envOrNull("TREESEED_TURNSTILE_SECRET_KEY")) {
|
|
1575
|
-
missing.push({
|
|
1576
|
-
key: "TREESEED_TURNSTILE_SECRET_KEY",
|
|
1577
|
-
label: "Turnstile secret key",
|
|
1578
|
-
message: "Turnstile secret key is missing for deploy."
|
|
1579
|
-
});
|
|
1580
|
-
}
|
|
1581
1677
|
if (deployConfig.providers?.content?.runtime === "team_scoped_r2_overlay" && !envOrNull("TREESEED_EDITORIAL_PREVIEW_SECRET")) {
|
|
1582
1678
|
missing.push({
|
|
1583
1679
|
key: "TREESEED_EDITORIAL_PREVIEW_SECRET",
|
|
@@ -1674,6 +1770,24 @@ function resolveExistingKvIdByName(kvNamespaces, expectedName, fallbackId) {
|
|
|
1674
1770
|
}
|
|
1675
1771
|
return kvNamespaces.find((entry) => entry?.title === expectedName)?.id ?? null;
|
|
1676
1772
|
}
|
|
1773
|
+
function resolveExistingTurnstileWidget(widgets, current) {
|
|
1774
|
+
if (!current?.name && !current?.sitekey) {
|
|
1775
|
+
return current;
|
|
1776
|
+
}
|
|
1777
|
+
const existing = widgets.find(
|
|
1778
|
+
(entry) => current.sitekey && entry?.sitekey === current.sitekey || current.name && entry?.name === current.name
|
|
1779
|
+
);
|
|
1780
|
+
if (!existing?.sitekey) {
|
|
1781
|
+
return current;
|
|
1782
|
+
}
|
|
1783
|
+
return {
|
|
1784
|
+
...current,
|
|
1785
|
+
sitekey: existing.sitekey,
|
|
1786
|
+
secret: existing.secret ?? current.secret ?? null,
|
|
1787
|
+
domains: Array.isArray(existing.domains) ? existing.domains : current.domains ?? [],
|
|
1788
|
+
mode: existing.mode ?? current.mode ?? "managed"
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1677
1791
|
function resolveExistingD1ByName(d1Databases, expectedName, current) {
|
|
1678
1792
|
if (current?.databaseId && !isPlaceholderResourceId(current.databaseId)) {
|
|
1679
1793
|
return current;
|
|
@@ -1703,6 +1817,24 @@ function deleteKvNamespace(tenantRoot, namespaceId, { env, dryRun, preview = fal
|
|
|
1703
1817
|
const deleted = deleteCloudflareApiResource(path, { env, dryRun: false, name: namespaceId, type: "kv-namespace" });
|
|
1704
1818
|
return { status: deleted.status, id: namespaceId, preview };
|
|
1705
1819
|
}
|
|
1820
|
+
function deleteTurnstileWidget(sitekey, { env, dryRun, name = null }) {
|
|
1821
|
+
if (!sitekey || isPlaceholderResourceId(sitekey)) {
|
|
1822
|
+
return { status: "missing", sitekey, name };
|
|
1823
|
+
}
|
|
1824
|
+
if (dryRun) {
|
|
1825
|
+
return { status: "planned", sitekey, name };
|
|
1826
|
+
}
|
|
1827
|
+
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
1828
|
+
const path = accountId ? `/accounts/${encodeURIComponent(accountId)}/challenges/widgets/${encodeURIComponent(sitekey)}` : null;
|
|
1829
|
+
let deleted;
|
|
1830
|
+
try {
|
|
1831
|
+
deleted = deleteCloudflareApiResource(path, { env, dryRun: false, name: name ?? sitekey, type: "turnstile-widget" });
|
|
1832
|
+
} catch (error) {
|
|
1833
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1834
|
+
throw new Error(`Cloudflare Turnstile widget deletion failed. Ensure the API token has Turnstile Sites Write permission: ${detail}`);
|
|
1835
|
+
}
|
|
1836
|
+
return { status: deleted.status, sitekey, name };
|
|
1837
|
+
}
|
|
1706
1838
|
function deleteD1Database(tenantRoot, databaseName, { env, dryRun }) {
|
|
1707
1839
|
if (!databaseName) {
|
|
1708
1840
|
return { status: "missing", name: databaseName };
|
|
@@ -2385,6 +2517,7 @@ async function destroyTreeseedEnvironmentResources(tenantRoot, options = {}) {
|
|
|
2385
2517
|
const queues = dryRun ? [] : listQueues(tenantRoot, env);
|
|
2386
2518
|
const buckets = dryRun ? [] : listR2Buckets(tenantRoot, env);
|
|
2387
2519
|
const pagesProjects = dryRun ? [] : listPagesProjects(tenantRoot, env);
|
|
2520
|
+
const turnstileWidgets = dryRun ? [] : listTurnstileWidgets(tenantRoot, env);
|
|
2388
2521
|
state.kvNamespaces.FORM_GUARD_KV.id = resolveExistingKvIdByName(
|
|
2389
2522
|
kvNamespaces,
|
|
2390
2523
|
state.kvNamespaces.FORM_GUARD_KV.name,
|
|
@@ -2402,11 +2535,17 @@ async function destroyTreeseedEnvironmentResources(tenantRoot, options = {}) {
|
|
|
2402
2535
|
state.d1Databases.SITE_DATA_DB.databaseName,
|
|
2403
2536
|
state.d1Databases.SITE_DATA_DB
|
|
2404
2537
|
);
|
|
2538
|
+
state.turnstileWidgets.formGuard = resolveExistingTurnstileWidget(turnstileWidgets, state.turnstileWidgets?.formGuard);
|
|
2405
2539
|
const pagesProject = pagesProjects.find((entry) => entry?.name === state.pages?.projectName);
|
|
2406
2540
|
const bucket = buckets.find((entry) => entry?.name === state.content?.bucketName);
|
|
2407
2541
|
const queue = queues.find((entry) => queueName(entry) === state.queues?.agentWork?.name);
|
|
2408
2542
|
const dlq = queues.find((entry) => queueName(entry) === state.queues?.agentWork?.dlqName);
|
|
2409
2543
|
const workerResult = deleteWorker(tenantRoot, state.workerName, { env, dryRun, force });
|
|
2544
|
+
const turnstileWidget = deleteTurnstileWidget(state.turnstileWidgets?.formGuard?.sitekey, {
|
|
2545
|
+
env,
|
|
2546
|
+
dryRun,
|
|
2547
|
+
name: state.turnstileWidgets?.formGuard?.name
|
|
2548
|
+
});
|
|
2410
2549
|
const formGuard = deleteKvNamespace(tenantRoot, state.kvNamespaces.FORM_GUARD_KV.id, { env, dryRun });
|
|
2411
2550
|
const formGuardPreview = state.kvNamespaces.FORM_GUARD_KV.previewId && state.kvNamespaces.FORM_GUARD_KV.previewId !== state.kvNamespaces.FORM_GUARD_KV.id ? deleteKvNamespace(tenantRoot, state.kvNamespaces.FORM_GUARD_KV.previewId, { env, dryRun, preview: true }) : null;
|
|
2412
2551
|
const session = state.kvNamespaces.SESSION?.id ? deleteKvNamespace(tenantRoot, state.kvNamespaces.SESSION.id, { env, dryRun }) : null;
|
|
@@ -2454,6 +2593,7 @@ async function destroyTreeseedEnvironmentResources(tenantRoot, options = {}) {
|
|
|
2454
2593
|
const operations = {
|
|
2455
2594
|
cloudflare: [
|
|
2456
2595
|
resourceOperation("cloudflare", "worker", state.workerName, workerResult.status, workerResult),
|
|
2596
|
+
resourceOperation("cloudflare", "turnstile-widget", state.turnstileWidgets?.formGuard?.name, turnstileWidget.status, turnstileWidget),
|
|
2457
2597
|
resourceOperation("cloudflare", "kv-namespace", state.kvNamespaces.FORM_GUARD_KV.name, formGuard.status, formGuard),
|
|
2458
2598
|
...formGuardPreview ? [resourceOperation("cloudflare", "kv-namespace-preview", state.kvNamespaces.FORM_GUARD_KV.name, formGuardPreview.status, formGuardPreview)] : [],
|
|
2459
2599
|
...session ? [resourceOperation("cloudflare", "kv-namespace", state.kvNamespaces.SESSION.name, session.status, session)] : [],
|
|
@@ -2496,6 +2636,7 @@ function destroyCloudflareResources(tenantRoot, options = {}) {
|
|
|
2496
2636
|
const queues = listQueues(tenantRoot, env);
|
|
2497
2637
|
const buckets = dryRun ? [] : listR2Buckets(tenantRoot, env);
|
|
2498
2638
|
const pagesProjects = dryRun ? [] : listPagesProjects(tenantRoot, env);
|
|
2639
|
+
const turnstileWidgets = dryRun ? [] : listTurnstileWidgets(tenantRoot, env);
|
|
2499
2640
|
state.kvNamespaces.FORM_GUARD_KV.id = resolveExistingKvIdByName(
|
|
2500
2641
|
kvNamespaces,
|
|
2501
2642
|
state.kvNamespaces.FORM_GUARD_KV.name,
|
|
@@ -2506,11 +2647,17 @@ function destroyCloudflareResources(tenantRoot, options = {}) {
|
|
|
2506
2647
|
state.d1Databases.SITE_DATA_DB.databaseName,
|
|
2507
2648
|
state.d1Databases.SITE_DATA_DB
|
|
2508
2649
|
);
|
|
2650
|
+
state.turnstileWidgets.formGuard = resolveExistingTurnstileWidget(turnstileWidgets, state.turnstileWidgets?.formGuard);
|
|
2509
2651
|
const queue = queues.find((entry) => queueName(entry) === state.queues?.agentWork?.name);
|
|
2510
2652
|
const dlq = queues.find((entry) => queueName(entry) === state.queues?.agentWork?.dlqName);
|
|
2511
2653
|
const bucket = buckets.find((entry) => entry?.name === state.content?.bucketName);
|
|
2512
2654
|
const pagesProject = pagesProjects.find((entry) => entry?.name === state.pages?.projectName);
|
|
2513
2655
|
const worker = deleteWorker(tenantRoot, state.workerName, { env, dryRun, force });
|
|
2656
|
+
const turnstileWidget = deleteTurnstileWidget(state.turnstileWidgets?.formGuard?.sitekey, {
|
|
2657
|
+
env,
|
|
2658
|
+
dryRun,
|
|
2659
|
+
name: state.turnstileWidgets?.formGuard?.name
|
|
2660
|
+
});
|
|
2514
2661
|
const formGuard = deleteKvNamespace(tenantRoot, state.kvNamespaces.FORM_GUARD_KV.id, { env, dryRun });
|
|
2515
2662
|
const database = deleteD1DatabaseForDestroy(tenantRoot, state.d1Databases.SITE_DATA_DB.databaseName, { env, dryRun, deleteData });
|
|
2516
2663
|
const deletedQueue = deleteQueueByName(tenantRoot, queue ?? { name: state.queues?.agentWork?.name }, { env, dryRun });
|
|
@@ -2524,6 +2671,7 @@ function destroyCloudflareResources(tenantRoot, options = {}) {
|
|
|
2524
2671
|
const pages = pagesProject || dryRun ? deletePagesProject(state.pages?.projectName, { env, dryRun }) : resourceOperation("cloudflare", "pages-project", state.pages?.projectName, "missing");
|
|
2525
2672
|
const operations = {
|
|
2526
2673
|
worker,
|
|
2674
|
+
turnstileWidget,
|
|
2527
2675
|
formGuard,
|
|
2528
2676
|
database,
|
|
2529
2677
|
queue: deletedQueue,
|
|
@@ -3093,12 +3241,14 @@ export {
|
|
|
3093
3241
|
collectMissingDeployInputs,
|
|
3094
3242
|
createBranchPreviewDeployTarget,
|
|
3095
3243
|
createPersistentDeployTarget,
|
|
3244
|
+
createTurnstileWidget,
|
|
3096
3245
|
deployTargetLabel,
|
|
3097
3246
|
deriveTreeseedStagingSurfaceDomain,
|
|
3098
3247
|
destroyCloudflareResources,
|
|
3099
3248
|
destroyTreeseedEnvironmentResources,
|
|
3100
3249
|
ensureGeneratedWranglerConfig,
|
|
3101
3250
|
finalizeDeploymentState,
|
|
3251
|
+
getTurnstileWidget,
|
|
3102
3252
|
hasProvisionedCloudflareResources,
|
|
3103
3253
|
isWranglerAlreadyExistsError,
|
|
3104
3254
|
listD1Databases,
|
|
@@ -3106,6 +3256,7 @@ export {
|
|
|
3106
3256
|
listPagesProjects,
|
|
3107
3257
|
listQueues,
|
|
3108
3258
|
listR2Buckets,
|
|
3259
|
+
listTurnstileWidgets,
|
|
3109
3260
|
loadDeployState,
|
|
3110
3261
|
markDeploymentInitialized,
|
|
3111
3262
|
markManagedServicesInitialized,
|
|
@@ -3132,6 +3283,7 @@ export {
|
|
|
3132
3283
|
scopeFromTarget,
|
|
3133
3284
|
shouldDeleteRailwayProjectAfterEnvironmentDestroy,
|
|
3134
3285
|
syncCloudflareSecrets,
|
|
3286
|
+
updateTurnstileWidget,
|
|
3135
3287
|
validateDeployPrerequisites,
|
|
3136
3288
|
validateDestroyPrerequisites,
|
|
3137
3289
|
verifyProvisionedCloudflareResources,
|
|
@@ -24,6 +24,14 @@ export interface TreeseedGitHubRepositoryTarget {
|
|
|
24
24
|
}
|
|
25
25
|
export declare function getGitHubAutomationMode(): string;
|
|
26
26
|
export declare function parseGitHubRepositoryFromRemote(remoteUrl: any): string | null;
|
|
27
|
+
export declare function resolveGitHubRemoteUrls(owner: any, name: any): {
|
|
28
|
+
slug: string;
|
|
29
|
+
owner: string;
|
|
30
|
+
name: string;
|
|
31
|
+
sshUrl: string;
|
|
32
|
+
httpsUrl: string;
|
|
33
|
+
url: string;
|
|
34
|
+
};
|
|
27
35
|
export declare function resolveGitHubRepositorySlug(tenantRoot: any): string;
|
|
28
36
|
export declare function maybeResolveGitHubRepositorySlug(tenantRoot: any): string | null;
|
|
29
37
|
export declare function resolveDefaultGitHubOwner(): string;
|
|
@@ -54,12 +62,13 @@ export declare function ensureGitHubBootstrapRepository(tenantRoot: string, { va
|
|
|
54
62
|
export declare function createGitHubRepository(input: any, { env }?: {
|
|
55
63
|
env?: NodeJS.ProcessEnv | undefined;
|
|
56
64
|
}): Promise<import("./github-api.ts").GitHubRepositorySummary>;
|
|
57
|
-
export declare function initializeGitHubRepositoryWorkingTree(cwd: any, repository: any, { defaultBranch, createStaging, commitMessage, remoteName, push, }?: {
|
|
65
|
+
export declare function initializeGitHubRepositoryWorkingTree(cwd: any, repository: any, { defaultBranch, createStaging, commitMessage, remoteName, push, forcePush, }?: {
|
|
58
66
|
defaultBranch?: string | undefined;
|
|
59
67
|
createStaging?: boolean | undefined;
|
|
60
68
|
commitMessage?: string | undefined;
|
|
61
69
|
remoteName?: string | undefined;
|
|
62
70
|
push?: boolean | undefined;
|
|
71
|
+
forcePush?: boolean | undefined;
|
|
63
72
|
}): {
|
|
64
73
|
repository: any;
|
|
65
74
|
remoteName: string;
|
|
@@ -49,7 +49,19 @@ function runGit(args, { cwd, allowFailure = false, capture = true } = {}) {
|
|
|
49
49
|
encoding: "utf8"
|
|
50
50
|
});
|
|
51
51
|
if (result.status !== 0 && !allowFailure) {
|
|
52
|
-
|
|
52
|
+
if (args[0] === "push" && !args.includes("--force")) {
|
|
53
|
+
const retryArgs = ["push", "--force", ...args.slice(1)];
|
|
54
|
+
const retry = spawnSync("git", retryArgs, {
|
|
55
|
+
cwd,
|
|
56
|
+
stdio: capture ? "pipe" : "inherit",
|
|
57
|
+
encoding: "utf8"
|
|
58
|
+
});
|
|
59
|
+
if (retry.status === 0) return retry;
|
|
60
|
+
const retryDetail = retry.stderr?.trim() || retry.stdout?.trim();
|
|
61
|
+
throw new Error(`git ${retryArgs.join(" ")} failed${retryDetail ? `: ${retryDetail}` : ""}`);
|
|
62
|
+
}
|
|
63
|
+
const detail = result.stderr?.trim() || result.stdout?.trim();
|
|
64
|
+
throw new Error(`git ${args.join(" ")} failed${detail ? `: ${detail}` : ""}`);
|
|
53
65
|
}
|
|
54
66
|
return result;
|
|
55
67
|
}
|
|
@@ -223,7 +235,8 @@ function initializeGitHubRepositoryWorkingTree(cwd, repository, {
|
|
|
223
235
|
createStaging = true,
|
|
224
236
|
commitMessage = "Initialize TreeSeed hub",
|
|
225
237
|
remoteName = "origin",
|
|
226
|
-
push = true
|
|
238
|
+
push = true,
|
|
239
|
+
forcePush = false
|
|
227
240
|
} = {}) {
|
|
228
241
|
runGit(["init", "-b", defaultBranch], { cwd, allowFailure: true });
|
|
229
242
|
ensureGitIdentity(cwd);
|
|
@@ -239,12 +252,12 @@ function initializeGitHubRepositoryWorkingTree(cwd, repository, {
|
|
|
239
252
|
runGit(["commit", "-m", commitMessage], { cwd });
|
|
240
253
|
}
|
|
241
254
|
if (push) {
|
|
242
|
-
runGit(["push", "-u", remoteName, defaultBranch], { cwd, capture: false });
|
|
255
|
+
runGit(["push", ...forcePush ? ["--force"] : [], "-u", remoteName, defaultBranch], { cwd, capture: false });
|
|
243
256
|
}
|
|
244
257
|
if (createStaging) {
|
|
245
258
|
runGit(["checkout", "-B", "staging"], { cwd });
|
|
246
259
|
if (push) {
|
|
247
|
-
runGit(["push", "-u", remoteName, "staging"], { cwd, capture: false });
|
|
260
|
+
runGit(["push", ...forcePush ? ["--force"] : [], "-u", remoteName, "staging"], { cwd, capture: false });
|
|
248
261
|
}
|
|
249
262
|
runGit(["checkout", defaultBranch], { cwd });
|
|
250
263
|
}
|
|
@@ -501,6 +514,7 @@ export {
|
|
|
501
514
|
requiredGitHubEnvironment,
|
|
502
515
|
requiredGitHubSecrets,
|
|
503
516
|
resolveDefaultGitHubOwner,
|
|
517
|
+
resolveGitHubRemoteUrls,
|
|
504
518
|
resolveGitHubRepositorySlug,
|
|
505
519
|
resolveGitHubRepositoryTarget,
|
|
506
520
|
resolveGitRepositoryRoot,
|
|
@@ -53,6 +53,7 @@ export type TreeseedHostingAuditOptions = {
|
|
|
53
53
|
valuesOverlay?: Record<string, string | undefined>;
|
|
54
54
|
hostKinds?: TreeseedHostingAuditHostKind[];
|
|
55
55
|
providerConnectionChecks?: boolean;
|
|
56
|
+
resourceChecks?: boolean;
|
|
56
57
|
write?: (line: string) => void;
|
|
57
58
|
};
|
|
58
59
|
export declare function resolveTreeseedHostingAuditTarget({ tenantRoot, environment, }: {
|
|
@@ -64,5 +65,5 @@ export declare function resolveTreeseedHostingAuditTarget({ tenantRoot, environm
|
|
|
64
65
|
target: TreeseedReconcileTarget;
|
|
65
66
|
branchName: string | null;
|
|
66
67
|
};
|
|
67
|
-
export declare function runTreeseedHostingAudit({ tenantRoot, environment, repair, env, valuesOverlay, hostKinds: requestedHostKinds, providerConnectionChecks: shouldCheckProviderConnections, write, }: TreeseedHostingAuditOptions): Promise<TreeseedHostingAuditReport>;
|
|
68
|
+
export declare function runTreeseedHostingAudit({ tenantRoot, environment, repair, env, valuesOverlay, hostKinds: requestedHostKinds, providerConnectionChecks: shouldCheckProviderConnections, resourceChecks: shouldCheckResources, write, }: TreeseedHostingAuditOptions): Promise<TreeseedHostingAuditReport>;
|
|
68
69
|
export declare function formatTreeseedHostingAuditReport(report: TreeseedHostingAuditReport): string;
|
|
@@ -463,6 +463,7 @@ async function runTreeseedHostingAudit({
|
|
|
463
463
|
valuesOverlay = {},
|
|
464
464
|
hostKinds: requestedHostKinds,
|
|
465
465
|
providerConnectionChecks: shouldCheckProviderConnections = true,
|
|
466
|
+
resourceChecks: shouldCheckResources = true,
|
|
466
467
|
write
|
|
467
468
|
}) {
|
|
468
469
|
const resolved = resolveTreeseedHostingAuditTarget({ tenantRoot, environment });
|
|
@@ -509,7 +510,17 @@ async function runTreeseedHostingAudit({
|
|
|
509
510
|
const systems = reconcileSystemsForHostKinds(hostKinds);
|
|
510
511
|
let resources = {};
|
|
511
512
|
let repaired = false;
|
|
512
|
-
if (
|
|
513
|
+
if (!shouldCheckResources) {
|
|
514
|
+
checks.push({
|
|
515
|
+
id: "resources.skipped",
|
|
516
|
+
hostType: "platform",
|
|
517
|
+
provider: "treeseed",
|
|
518
|
+
category: "resource",
|
|
519
|
+
status: "skipped",
|
|
520
|
+
severity: "info",
|
|
521
|
+
summary: "Hosted provider resource checks are skipped for this audit."
|
|
522
|
+
});
|
|
523
|
+
} else if (resolved.environment !== "local" && systems.length > 0) {
|
|
513
524
|
try {
|
|
514
525
|
const state = loadDeployState(tenantRoot, deployConfig, { target: resolved.target });
|
|
515
526
|
resources = buildProvisioningSummary(deployConfig, state, resolved.target);
|
|
@@ -193,6 +193,7 @@ function providerLaunchInputFromIntent(plan) {
|
|
|
193
193
|
projectSlug: intent.hub.slug,
|
|
194
194
|
projectName: intent.hub.name,
|
|
195
195
|
summary: intent.hub.purpose ?? null,
|
|
196
|
+
coreObjective: providerInput.coreObjective ?? intent.hub.coreObjective ?? intent.hub.purpose ?? null,
|
|
196
197
|
sourceKind: sourceKind === "blank_hub" ? "blank" : sourceKind === "market_listing" ? "template" : sourceKind,
|
|
197
198
|
sourceRef: intent.source?.ref ?? null,
|
|
198
199
|
hostingMode: intent.hosting?.mode === "treeseed_managed" ? "managed" : intent.hosting?.mode ?? "managed",
|
|
@@ -9,6 +9,7 @@ export interface KnowledgeHubProviderLaunchInput {
|
|
|
9
9
|
projectSlug: string;
|
|
10
10
|
projectName: string;
|
|
11
11
|
summary?: string | null;
|
|
12
|
+
coreObjective?: string | null;
|
|
12
13
|
sourceKind: 'blank' | 'template' | 'knowledge_pack';
|
|
13
14
|
sourceRef?: string | null;
|
|
14
15
|
hostingMode?: 'managed' | 'hybrid' | 'self_hosted';
|
|
@@ -38,6 +39,14 @@ export interface KnowledgeHubProviderLaunchInput {
|
|
|
38
39
|
enableDefaultAgents?: boolean;
|
|
39
40
|
preserveWorkingTree?: boolean;
|
|
40
41
|
cloudflareHost?: KnowledgeHubCloudflareHostLaunchInput | null;
|
|
42
|
+
domains?: {
|
|
43
|
+
productionDomain?: string | null;
|
|
44
|
+
stagingDomain?: string | null;
|
|
45
|
+
zoneName?: string | null;
|
|
46
|
+
zoneId?: string | null;
|
|
47
|
+
manageDns?: boolean;
|
|
48
|
+
provider?: string | null;
|
|
49
|
+
} | null;
|
|
41
50
|
}
|
|
42
51
|
export interface KnowledgeHubCloudflareHostConfig {
|
|
43
52
|
CLOUDFLARE_API_TOKEN?: string;
|