@treeseed/sdk 0.10.21 → 0.10.23
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/config-runtime.d.ts +1 -1
- package/dist/operations/services/deploy.d.ts +20 -3
- package/dist/operations/services/deploy.js +228 -102
- 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.js +16 -0
- package/dist/operations/services/railway-api.js +56 -8
- package/dist/operations/services/railway-deploy.d.ts +2 -1
- package/dist/operations/services/railway-deploy.js +15 -18
- 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/units.js +1 -0
- package/dist/sdk-types.d.ts +1 -1
- package/dist/sdk-types.js +2 -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
|
@@ -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,
|
|
@@ -1083,6 +1200,7 @@ function shouldManageCloudflareWebCacheRules(deployConfig, target) {
|
|
|
1083
1200
|
}
|
|
1084
1201
|
function cloudflareApiRequest(path, { method = "GET", body, env, allowFailure = false } = {}) {
|
|
1085
1202
|
const requestScript = `import { readFileSync } from 'node:fs';
|
|
1203
|
+
import { request } from 'node:https';
|
|
1086
1204
|
const input = JSON.parse(readFileSync(0, 'utf8') || '{}');
|
|
1087
1205
|
function errorMessage(error) {
|
|
1088
1206
|
const parts = [];
|
|
@@ -1099,15 +1217,32 @@ function errorMessage(error) {
|
|
|
1099
1217
|
return [...new Set(parts.filter(Boolean))].join('; ') || String(error);
|
|
1100
1218
|
}
|
|
1101
1219
|
try {
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1220
|
+
const body = input.body ? JSON.stringify(input.body) : undefined;
|
|
1221
|
+
const response = await new Promise((resolve, reject) => {
|
|
1222
|
+
const req = request(input.url, {
|
|
1223
|
+
method: input.method,
|
|
1224
|
+
headers: {
|
|
1225
|
+
authorization: 'Bearer ' + input.token,
|
|
1226
|
+
'content-type': 'application/json',
|
|
1227
|
+
},
|
|
1228
|
+
timeout: input.timeoutMs ?? 12000,
|
|
1229
|
+
}, (res) => {
|
|
1230
|
+
const chunks = [];
|
|
1231
|
+
res.setEncoding('utf8');
|
|
1232
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
1233
|
+
res.on('end', () => resolve({
|
|
1234
|
+
ok: typeof res.statusCode === 'number' && res.statusCode >= 200 && res.statusCode < 300,
|
|
1235
|
+
text: chunks.join(''),
|
|
1236
|
+
}));
|
|
1237
|
+
});
|
|
1238
|
+
req.on('timeout', () => {
|
|
1239
|
+
req.destroy(new Error('Cloudflare API request timed out'));
|
|
1240
|
+
});
|
|
1241
|
+
req.on('error', reject);
|
|
1242
|
+
if (body) req.write(body);
|
|
1243
|
+
req.end();
|
|
1109
1244
|
});
|
|
1110
|
-
const rawBody =
|
|
1245
|
+
const rawBody = response.text;
|
|
1111
1246
|
let payload;
|
|
1112
1247
|
try {
|
|
1113
1248
|
payload = rawBody ? JSON.parse(rawBody) : {};
|
|
@@ -1126,6 +1261,7 @@ try {
|
|
|
1126
1261
|
url: `https://api.cloudflare.com/client/v4${path}`,
|
|
1127
1262
|
method,
|
|
1128
1263
|
body,
|
|
1264
|
+
timeoutMs: 12e3,
|
|
1129
1265
|
token: env?.CLOUDFLARE_API_TOKEN ?? process.env.CLOUDFLARE_API_TOKEN ?? ""
|
|
1130
1266
|
});
|
|
1131
1267
|
const isTransient = (text) => /fetch failed|timed out|etimedout|econnreset|enetunreach|temporarily unavailable|aborted/iu.test(text || "");
|
|
@@ -1514,14 +1650,7 @@ function resolveConfiguredContentPublicBaseUrl(deployConfig) {
|
|
|
1514
1650
|
return envOrNull("TREESEED_CONTENT_PUBLIC_BASE_URL") ?? deployConfig.cloudflare.r2?.publicBaseUrl ?? "";
|
|
1515
1651
|
}
|
|
1516
1652
|
function missingTurnstileRequirements() {
|
|
1517
|
-
|
|
1518
|
-
if (!envOrNull("TREESEED_PUBLIC_TURNSTILE_SITE_KEY")) {
|
|
1519
|
-
issues.push("Set TREESEED_PUBLIC_TURNSTILE_SITE_KEY before deploying.");
|
|
1520
|
-
}
|
|
1521
|
-
if (!envOrNull("TREESEED_TURNSTILE_SECRET_KEY")) {
|
|
1522
|
-
issues.push("Set TREESEED_TURNSTILE_SECRET_KEY before deploying.");
|
|
1523
|
-
}
|
|
1524
|
-
return issues;
|
|
1653
|
+
return [];
|
|
1525
1654
|
}
|
|
1526
1655
|
function missingContentRuntimeRequirements(deployConfig) {
|
|
1527
1656
|
const issues = [];
|
|
@@ -1545,20 +1674,6 @@ function collectMissingDeployInputs(tenantRoot) {
|
|
|
1545
1674
|
message: "Cloudflare account ID is missing. Set CLOUDFLARE_ACCOUNT_ID with treeseed config or provide it now."
|
|
1546
1675
|
});
|
|
1547
1676
|
}
|
|
1548
|
-
if (!envOrNull("TREESEED_PUBLIC_TURNSTILE_SITE_KEY")) {
|
|
1549
|
-
missing.push({
|
|
1550
|
-
key: "TREESEED_PUBLIC_TURNSTILE_SITE_KEY",
|
|
1551
|
-
label: "Turnstile public site key",
|
|
1552
|
-
message: "Turnstile public site key is missing for deploy."
|
|
1553
|
-
});
|
|
1554
|
-
}
|
|
1555
|
-
if (!envOrNull("TREESEED_TURNSTILE_SECRET_KEY")) {
|
|
1556
|
-
missing.push({
|
|
1557
|
-
key: "TREESEED_TURNSTILE_SECRET_KEY",
|
|
1558
|
-
label: "Turnstile secret key",
|
|
1559
|
-
message: "Turnstile secret key is missing for deploy."
|
|
1560
|
-
});
|
|
1561
|
-
}
|
|
1562
1677
|
if (deployConfig.providers?.content?.runtime === "team_scoped_r2_overlay" && !envOrNull("TREESEED_EDITORIAL_PREVIEW_SECRET")) {
|
|
1563
1678
|
missing.push({
|
|
1564
1679
|
key: "TREESEED_EDITORIAL_PREVIEW_SECRET",
|
|
@@ -1655,6 +1770,24 @@ function resolveExistingKvIdByName(kvNamespaces, expectedName, fallbackId) {
|
|
|
1655
1770
|
}
|
|
1656
1771
|
return kvNamespaces.find((entry) => entry?.title === expectedName)?.id ?? null;
|
|
1657
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
|
+
}
|
|
1658
1791
|
function resolveExistingD1ByName(d1Databases, expectedName, current) {
|
|
1659
1792
|
if (current?.databaseId && !isPlaceholderResourceId(current.databaseId)) {
|
|
1660
1793
|
return current;
|
|
@@ -1679,22 +1812,28 @@ function deleteKvNamespace(tenantRoot, namespaceId, { env, dryRun, preview = fal
|
|
|
1679
1812
|
if (dryRun) {
|
|
1680
1813
|
return { status: "planned", id: namespaceId, preview };
|
|
1681
1814
|
}
|
|
1682
|
-
const
|
|
1683
|
-
|
|
1684
|
-
|
|
1815
|
+
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
1816
|
+
const path = accountId ? `/accounts/${encodeURIComponent(accountId)}/storage/kv/namespaces/${encodeURIComponent(namespaceId)}` : null;
|
|
1817
|
+
const deleted = deleteCloudflareApiResource(path, { env, dryRun: false, name: namespaceId, type: "kv-namespace" });
|
|
1818
|
+
return { status: deleted.status, id: namespaceId, preview };
|
|
1819
|
+
}
|
|
1820
|
+
function deleteTurnstileWidget(sitekey, { env, dryRun, name = null }) {
|
|
1821
|
+
if (!sitekey || isPlaceholderResourceId(sitekey)) {
|
|
1822
|
+
return { status: "missing", sitekey, name };
|
|
1685
1823
|
}
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
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}`);
|
|
1696
1835
|
}
|
|
1697
|
-
return { status:
|
|
1836
|
+
return { status: deleted.status, sitekey, name };
|
|
1698
1837
|
}
|
|
1699
1838
|
function deleteD1Database(tenantRoot, databaseName, { env, dryRun }) {
|
|
1700
1839
|
if (!databaseName) {
|
|
@@ -1703,18 +1842,12 @@ function deleteD1Database(tenantRoot, databaseName, { env, dryRun }) {
|
|
|
1703
1842
|
if (dryRun) {
|
|
1704
1843
|
return { status: "planned", name: databaseName };
|
|
1705
1844
|
}
|
|
1706
|
-
const
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
}
|
|
1712
|
-
const output = `${result.stdout ?? ""}
|
|
1713
|
-
${result.stderr ?? ""}`;
|
|
1714
|
-
if (result.status !== 0 && !looksLikeMissingResource(output)) {
|
|
1715
|
-
throw new Error(output.trim() || `Failed to delete D1 database ${databaseName}.`);
|
|
1716
|
-
}
|
|
1717
|
-
return { status: result.status === 0 ? "deleted" : "missing", name: databaseName };
|
|
1845
|
+
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
1846
|
+
const database = accountId ? listD1Databases(tenantRoot, env).find((entry) => entry?.name === databaseName) : null;
|
|
1847
|
+
const databaseId = database?.uuid ?? database?.id ?? null;
|
|
1848
|
+
const path = accountId && databaseId ? `/accounts/${encodeURIComponent(accountId)}/d1/database/${encodeURIComponent(databaseId)}` : null;
|
|
1849
|
+
const deleted = deleteCloudflareApiResource(path, { env, dryRun: false, name: databaseName, type: "d1-database" });
|
|
1850
|
+
return { status: deleted.status, name: databaseName, id: databaseId };
|
|
1718
1851
|
}
|
|
1719
1852
|
function deleteWorker(tenantRoot, workerName, { env, dryRun, force = false }) {
|
|
1720
1853
|
if (!workerName) {
|
|
@@ -1723,23 +1856,10 @@ function deleteWorker(tenantRoot, workerName, { env, dryRun, force = false }) {
|
|
|
1723
1856
|
if (dryRun) {
|
|
1724
1857
|
return { status: "planned", name: workerName };
|
|
1725
1858
|
}
|
|
1726
|
-
const
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
}
|
|
1730
|
-
const result = runWrangler(args, {
|
|
1731
|
-
cwd: tenantRoot,
|
|
1732
|
-
allowFailure: true,
|
|
1733
|
-
capture: true,
|
|
1734
|
-
env,
|
|
1735
|
-
input: "y\n"
|
|
1736
|
-
});
|
|
1737
|
-
const output = `${result.stdout ?? ""}
|
|
1738
|
-
${result.stderr ?? ""}`;
|
|
1739
|
-
if (result.status !== 0 && !looksLikeMissingResource(output)) {
|
|
1740
|
-
throw new Error(output.trim() || `Failed to delete Worker ${workerName}.`);
|
|
1741
|
-
}
|
|
1742
|
-
return { status: result.status === 0 ? "deleted" : "missing", name: workerName };
|
|
1859
|
+
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
1860
|
+
const path = accountId ? `/accounts/${encodeURIComponent(accountId)}/workers/services/${encodeURIComponent(workerName)}` : null;
|
|
1861
|
+
const deleted = deleteCloudflareApiResource(path, { env, dryRun: false, name: workerName, type: "worker" });
|
|
1862
|
+
return { status: deleted.status, name: workerName };
|
|
1743
1863
|
}
|
|
1744
1864
|
function resourceOperation(provider, type, name, status, extra = {}) {
|
|
1745
1865
|
return {
|
|
@@ -1768,7 +1888,7 @@ function formatCloudflareErrors(payload) {
|
|
|
1768
1888
|
}
|
|
1769
1889
|
function deleteQueueByName(tenantRoot, queue, { env, dryRun }) {
|
|
1770
1890
|
const name = queueName(queue) ?? queue?.name ?? null;
|
|
1771
|
-
|
|
1891
|
+
let id = queueId(queue);
|
|
1772
1892
|
if (!name) {
|
|
1773
1893
|
return resourceOperation("cloudflare", "queue", name, "missing");
|
|
1774
1894
|
}
|
|
@@ -1776,6 +1896,10 @@ function deleteQueueByName(tenantRoot, queue, { env, dryRun }) {
|
|
|
1776
1896
|
return resourceOperation("cloudflare", "queue", name, "planned", { id });
|
|
1777
1897
|
}
|
|
1778
1898
|
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
1899
|
+
if (!id && accountId) {
|
|
1900
|
+
const live = listQueues(tenantRoot, env).find((entry) => queueName(entry) === name);
|
|
1901
|
+
id = queueId(live);
|
|
1902
|
+
}
|
|
1779
1903
|
const path = id ? `/accounts/${encodeURIComponent(accountId)}/queues/${encodeURIComponent(id)}` : null;
|
|
1780
1904
|
if (path) {
|
|
1781
1905
|
const deleted = deleteCloudflareApiResource(path, { env, dryRun: false, name, type: "queue" });
|
|
@@ -1783,19 +1907,10 @@ function deleteQueueByName(tenantRoot, queue, { env, dryRun }) {
|
|
|
1783
1907
|
return { ...deleted, id };
|
|
1784
1908
|
}
|
|
1785
1909
|
}
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
allowFailure: true,
|
|
1789
|
-
capture: true,
|
|
1790
|
-
env,
|
|
1791
|
-
input: "y\n"
|
|
1792
|
-
});
|
|
1793
|
-
const output = `${result.stdout ?? ""}
|
|
1794
|
-
${result.stderr ?? ""}`;
|
|
1795
|
-
if (result.status !== 0 && !looksLikeMissingResource(output)) {
|
|
1796
|
-
throw new Error(output.trim() || `Failed to delete queue ${name}.`);
|
|
1910
|
+
if (accountId) {
|
|
1911
|
+
return resourceOperation("cloudflare", "queue", name, "missing", { id });
|
|
1797
1912
|
}
|
|
1798
|
-
|
|
1913
|
+
throw new Error(`Failed to delete queue ${name}: CLOUDFLARE_ACCOUNT_ID is not configured.`);
|
|
1799
1914
|
}
|
|
1800
1915
|
function isLegacyTreeseedQueueName(name, scope) {
|
|
1801
1916
|
if (!name || !scope) {
|
|
@@ -1827,19 +1942,10 @@ function deleteR2Bucket(tenantRoot, bucketName, { env, dryRun, deleteData }) {
|
|
|
1827
1942
|
return resourceOperation("cloudflare", "r2-bucket", bucketName, "planned");
|
|
1828
1943
|
}
|
|
1829
1944
|
const drained = drainR2Bucket(bucketName, { env });
|
|
1830
|
-
const
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
env,
|
|
1835
|
-
input: "y\n"
|
|
1836
|
-
});
|
|
1837
|
-
const output = `${result.stdout ?? ""}
|
|
1838
|
-
${result.stderr ?? ""}`;
|
|
1839
|
-
if (result.status !== 0 && !looksLikeMissingResource(output)) {
|
|
1840
|
-
throw new Error(output.trim() || `Failed to delete R2 bucket ${bucketName}.`);
|
|
1841
|
-
}
|
|
1842
|
-
return resourceOperation("cloudflare", "r2-bucket", bucketName, result.status === 0 ? "deleted" : "missing", drained);
|
|
1945
|
+
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
1946
|
+
const path = accountId ? `/accounts/${encodeURIComponent(accountId)}/r2/buckets/${encodeURIComponent(bucketName)}` : null;
|
|
1947
|
+
const deleted = deleteCloudflareApiResource(path, { env, dryRun: false, name: bucketName, type: "r2-bucket" });
|
|
1948
|
+
return resourceOperation("cloudflare", "r2-bucket", bucketName, deleted.status, drained);
|
|
1843
1949
|
}
|
|
1844
1950
|
function r2ObjectKey(entry) {
|
|
1845
1951
|
return typeof entry?.key === "string" ? entry.key : typeof entry?.name === "string" ? entry.name : "";
|
|
@@ -2408,9 +2514,10 @@ async function destroyTreeseedEnvironmentResources(tenantRoot, options = {}) {
|
|
|
2408
2514
|
const force = options.force ?? false;
|
|
2409
2515
|
const kvNamespaces = dryRun ? [] : listKvNamespaces(tenantRoot, env);
|
|
2410
2516
|
const d1Databases = dryRun ? [] : listD1Databases(tenantRoot, env);
|
|
2411
|
-
const queues = listQueues(tenantRoot, env);
|
|
2517
|
+
const queues = dryRun ? [] : listQueues(tenantRoot, env);
|
|
2412
2518
|
const buckets = dryRun ? [] : listR2Buckets(tenantRoot, env);
|
|
2413
2519
|
const pagesProjects = dryRun ? [] : listPagesProjects(tenantRoot, env);
|
|
2520
|
+
const turnstileWidgets = dryRun ? [] : listTurnstileWidgets(tenantRoot, env);
|
|
2414
2521
|
state.kvNamespaces.FORM_GUARD_KV.id = resolveExistingKvIdByName(
|
|
2415
2522
|
kvNamespaces,
|
|
2416
2523
|
state.kvNamespaces.FORM_GUARD_KV.name,
|
|
@@ -2428,11 +2535,17 @@ async function destroyTreeseedEnvironmentResources(tenantRoot, options = {}) {
|
|
|
2428
2535
|
state.d1Databases.SITE_DATA_DB.databaseName,
|
|
2429
2536
|
state.d1Databases.SITE_DATA_DB
|
|
2430
2537
|
);
|
|
2538
|
+
state.turnstileWidgets.formGuard = resolveExistingTurnstileWidget(turnstileWidgets, state.turnstileWidgets?.formGuard);
|
|
2431
2539
|
const pagesProject = pagesProjects.find((entry) => entry?.name === state.pages?.projectName);
|
|
2432
2540
|
const bucket = buckets.find((entry) => entry?.name === state.content?.bucketName);
|
|
2433
2541
|
const queue = queues.find((entry) => queueName(entry) === state.queues?.agentWork?.name);
|
|
2434
2542
|
const dlq = queues.find((entry) => queueName(entry) === state.queues?.agentWork?.dlqName);
|
|
2435
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
|
+
});
|
|
2436
2549
|
const formGuard = deleteKvNamespace(tenantRoot, state.kvNamespaces.FORM_GUARD_KV.id, { env, dryRun });
|
|
2437
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;
|
|
2438
2551
|
const session = state.kvNamespaces.SESSION?.id ? deleteKvNamespace(tenantRoot, state.kvNamespaces.SESSION.id, { env, dryRun }) : null;
|
|
@@ -2480,6 +2593,7 @@ async function destroyTreeseedEnvironmentResources(tenantRoot, options = {}) {
|
|
|
2480
2593
|
const operations = {
|
|
2481
2594
|
cloudflare: [
|
|
2482
2595
|
resourceOperation("cloudflare", "worker", state.workerName, workerResult.status, workerResult),
|
|
2596
|
+
resourceOperation("cloudflare", "turnstile-widget", state.turnstileWidgets?.formGuard?.name, turnstileWidget.status, turnstileWidget),
|
|
2483
2597
|
resourceOperation("cloudflare", "kv-namespace", state.kvNamespaces.FORM_GUARD_KV.name, formGuard.status, formGuard),
|
|
2484
2598
|
...formGuardPreview ? [resourceOperation("cloudflare", "kv-namespace-preview", state.kvNamespaces.FORM_GUARD_KV.name, formGuardPreview.status, formGuardPreview)] : [],
|
|
2485
2599
|
...session ? [resourceOperation("cloudflare", "kv-namespace", state.kvNamespaces.SESSION.name, session.status, session)] : [],
|
|
@@ -2522,6 +2636,7 @@ function destroyCloudflareResources(tenantRoot, options = {}) {
|
|
|
2522
2636
|
const queues = listQueues(tenantRoot, env);
|
|
2523
2637
|
const buckets = dryRun ? [] : listR2Buckets(tenantRoot, env);
|
|
2524
2638
|
const pagesProjects = dryRun ? [] : listPagesProjects(tenantRoot, env);
|
|
2639
|
+
const turnstileWidgets = dryRun ? [] : listTurnstileWidgets(tenantRoot, env);
|
|
2525
2640
|
state.kvNamespaces.FORM_GUARD_KV.id = resolveExistingKvIdByName(
|
|
2526
2641
|
kvNamespaces,
|
|
2527
2642
|
state.kvNamespaces.FORM_GUARD_KV.name,
|
|
@@ -2532,11 +2647,17 @@ function destroyCloudflareResources(tenantRoot, options = {}) {
|
|
|
2532
2647
|
state.d1Databases.SITE_DATA_DB.databaseName,
|
|
2533
2648
|
state.d1Databases.SITE_DATA_DB
|
|
2534
2649
|
);
|
|
2650
|
+
state.turnstileWidgets.formGuard = resolveExistingTurnstileWidget(turnstileWidgets, state.turnstileWidgets?.formGuard);
|
|
2535
2651
|
const queue = queues.find((entry) => queueName(entry) === state.queues?.agentWork?.name);
|
|
2536
2652
|
const dlq = queues.find((entry) => queueName(entry) === state.queues?.agentWork?.dlqName);
|
|
2537
2653
|
const bucket = buckets.find((entry) => entry?.name === state.content?.bucketName);
|
|
2538
2654
|
const pagesProject = pagesProjects.find((entry) => entry?.name === state.pages?.projectName);
|
|
2539
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
|
+
});
|
|
2540
2661
|
const formGuard = deleteKvNamespace(tenantRoot, state.kvNamespaces.FORM_GUARD_KV.id, { env, dryRun });
|
|
2541
2662
|
const database = deleteD1DatabaseForDestroy(tenantRoot, state.d1Databases.SITE_DATA_DB.databaseName, { env, dryRun, deleteData });
|
|
2542
2663
|
const deletedQueue = deleteQueueByName(tenantRoot, queue ?? { name: state.queues?.agentWork?.name }, { env, dryRun });
|
|
@@ -2550,6 +2671,7 @@ function destroyCloudflareResources(tenantRoot, options = {}) {
|
|
|
2550
2671
|
const pages = pagesProject || dryRun ? deletePagesProject(state.pages?.projectName, { env, dryRun }) : resourceOperation("cloudflare", "pages-project", state.pages?.projectName, "missing");
|
|
2551
2672
|
const operations = {
|
|
2552
2673
|
worker,
|
|
2674
|
+
turnstileWidget,
|
|
2553
2675
|
formGuard,
|
|
2554
2676
|
database,
|
|
2555
2677
|
queue: deletedQueue,
|
|
@@ -3119,12 +3241,14 @@ export {
|
|
|
3119
3241
|
collectMissingDeployInputs,
|
|
3120
3242
|
createBranchPreviewDeployTarget,
|
|
3121
3243
|
createPersistentDeployTarget,
|
|
3244
|
+
createTurnstileWidget,
|
|
3122
3245
|
deployTargetLabel,
|
|
3123
3246
|
deriveTreeseedStagingSurfaceDomain,
|
|
3124
3247
|
destroyCloudflareResources,
|
|
3125
3248
|
destroyTreeseedEnvironmentResources,
|
|
3126
3249
|
ensureGeneratedWranglerConfig,
|
|
3127
3250
|
finalizeDeploymentState,
|
|
3251
|
+
getTurnstileWidget,
|
|
3128
3252
|
hasProvisionedCloudflareResources,
|
|
3129
3253
|
isWranglerAlreadyExistsError,
|
|
3130
3254
|
listD1Databases,
|
|
@@ -3132,6 +3256,7 @@ export {
|
|
|
3132
3256
|
listPagesProjects,
|
|
3133
3257
|
listQueues,
|
|
3134
3258
|
listR2Buckets,
|
|
3259
|
+
listTurnstileWidgets,
|
|
3135
3260
|
loadDeployState,
|
|
3136
3261
|
markDeploymentInitialized,
|
|
3137
3262
|
markManagedServicesInitialized,
|
|
@@ -3158,6 +3283,7 @@ export {
|
|
|
3158
3283
|
scopeFromTarget,
|
|
3159
3284
|
shouldDeleteRailwayProjectAfterEnvironmentDestroy,
|
|
3160
3285
|
syncCloudflareSecrets,
|
|
3286
|
+
updateTurnstileWidget,
|
|
3161
3287
|
validateDeployPrerequisites,
|
|
3162
3288
|
validateDestroyPrerequisites,
|
|
3163
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,
|