@treeseed/sdk 0.6.1 → 0.6.3
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/operations/services/bootstrap-runner.d.ts +33 -0
- package/dist/operations/services/bootstrap-runner.js +136 -0
- package/dist/operations/services/config-runtime.d.ts +27 -8
- package/dist/operations/services/config-runtime.js +303 -127
- package/dist/operations/services/github-api.d.ts +34 -0
- package/dist/operations/services/github-api.js +187 -4
- package/dist/operations/services/github-automation.d.ts +30 -0
- package/dist/operations/services/github-automation.js +107 -1
- package/dist/operations/services/project-platform.d.ts +38 -2
- package/dist/operations/services/project-platform.js +319 -15
- package/dist/operations/services/railway-deploy.d.ts +6 -2
- package/dist/operations/services/railway-deploy.js +26 -18
- package/dist/operations/services/runtime-tools.d.ts +0 -2
- package/dist/operations/services/runtime-tools.js +0 -2
- package/dist/platform/env.yaml +71 -96
- package/dist/platform/environment.d.ts +4 -0
- package/dist/platform/environment.js +54 -0
- package/dist/reconcile/bootstrap-systems.d.ts +32 -0
- package/dist/reconcile/bootstrap-systems.js +175 -0
- package/dist/reconcile/builtin-adapters.js +1 -9
- package/dist/reconcile/desired-state.js +16 -14
- package/dist/reconcile/engine.d.ts +9 -4
- package/dist/reconcile/engine.js +57 -14
- package/dist/reconcile/index.d.ts +1 -0
- package/dist/reconcile/index.js +1 -0
- package/dist/scripts/config-treeseed.js +30 -0
- package/dist/scripts/package-tools.js +0 -2
- package/dist/scripts/tenant-deploy.js +16 -36
- package/dist/scripts/test-cloudflare-local.js +0 -2
- package/dist/workflow/operations.js +23 -4
- package/dist/workflow.d.ts +5 -0
- package/package.json +1 -1
- package/templates/github/deploy.workflow.yml +15 -15
|
@@ -27,11 +27,23 @@ function parseGitHubRepositorySlug(value) {
|
|
|
27
27
|
name: rest.join("/")
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
-
function
|
|
30
|
+
function createGitHubRequestSignal(timeoutMs = DEFAULT_GITHUB_API_TIMEOUT_MS, upstreamSignal) {
|
|
31
31
|
if (typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function") {
|
|
32
|
-
|
|
32
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
33
|
+
if (upstreamSignal) {
|
|
34
|
+
const abortSignalAny = AbortSignal.any;
|
|
35
|
+
return typeof abortSignalAny === "function" ? abortSignalAny([upstreamSignal, timeoutSignal]) : timeoutSignal;
|
|
36
|
+
}
|
|
37
|
+
return timeoutSignal;
|
|
33
38
|
}
|
|
34
|
-
return void 0;
|
|
39
|
+
return upstreamSignal ?? void 0;
|
|
40
|
+
}
|
|
41
|
+
function createGitHubTimeoutFetch(timeoutMs = DEFAULT_GITHUB_API_TIMEOUT_MS) {
|
|
42
|
+
const baseFetch = globalThis.fetch.bind(globalThis);
|
|
43
|
+
return ((input, init) => {
|
|
44
|
+
const signal = createGitHubRequestSignal(timeoutMs, init?.signal ?? null);
|
|
45
|
+
return baseFetch(input, signal ? { ...init, signal } : init);
|
|
46
|
+
});
|
|
35
47
|
}
|
|
36
48
|
function normalizeGitHubApiError(error, context) {
|
|
37
49
|
if (error && typeof error === "object") {
|
|
@@ -100,7 +112,7 @@ function createGitHubApiClient({
|
|
|
100
112
|
return new Octokit({
|
|
101
113
|
auth: token,
|
|
102
114
|
request: {
|
|
103
|
-
|
|
115
|
+
fetch: createGitHubTimeoutFetch(timeoutMs)
|
|
104
116
|
}
|
|
105
117
|
});
|
|
106
118
|
}
|
|
@@ -208,6 +220,119 @@ async function listGitHubRepositoryVariableNames(repository, { client = createGi
|
|
|
208
220
|
throw normalizeGitHubApiError(error, `Unable to list GitHub variables for ${owner}/${name}`);
|
|
209
221
|
}
|
|
210
222
|
}
|
|
223
|
+
async function ensureGitHubActionsEnvironment(repository, environmentName, {
|
|
224
|
+
client = createGitHubApiClient(),
|
|
225
|
+
branchName
|
|
226
|
+
} = {}) {
|
|
227
|
+
const { owner, name: repo } = typeof repository === "string" ? parseGitHubRepositorySlug(repository) : repository;
|
|
228
|
+
try {
|
|
229
|
+
await withGitHubApiRetries(() => client.request("PUT /repos/{owner}/{repo}/environments/{environment_name}", {
|
|
230
|
+
owner,
|
|
231
|
+
repo,
|
|
232
|
+
environment_name: environmentName,
|
|
233
|
+
...branchName ? {
|
|
234
|
+
deployment_branch_policy: {
|
|
235
|
+
protected_branches: false,
|
|
236
|
+
custom_branch_policies: true
|
|
237
|
+
}
|
|
238
|
+
} : {}
|
|
239
|
+
}));
|
|
240
|
+
if (branchName) {
|
|
241
|
+
await ensureGitHubEnvironmentBranchPolicy(client, {
|
|
242
|
+
owner,
|
|
243
|
+
repo,
|
|
244
|
+
environmentName,
|
|
245
|
+
branchName
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return { repository: `${owner}/${repo}`, environment: environmentName };
|
|
249
|
+
} catch (error) {
|
|
250
|
+
throw normalizeGitHubApiError(error, `Unable to ensure GitHub environment ${environmentName} for ${owner}/${repo}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async function ensureGitHubEnvironmentBranchPolicy(client, {
|
|
254
|
+
owner,
|
|
255
|
+
repo,
|
|
256
|
+
environmentName,
|
|
257
|
+
branchName
|
|
258
|
+
}) {
|
|
259
|
+
const response = await withGitHubApiRetries(() => client.request(
|
|
260
|
+
"GET /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies",
|
|
261
|
+
{
|
|
262
|
+
owner,
|
|
263
|
+
repo,
|
|
264
|
+
environment_name: environmentName,
|
|
265
|
+
per_page: 100
|
|
266
|
+
}
|
|
267
|
+
));
|
|
268
|
+
const policies = Array.isArray(response.data?.branch_policies) ? response.data.branch_policies : [];
|
|
269
|
+
const desired = policies.find((policy) => policy.name === branchName && (policy.type ?? "branch") === "branch");
|
|
270
|
+
for (const policy of policies) {
|
|
271
|
+
if (!policy.id || policy.name === branchName && (policy.type ?? "branch") === "branch") {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
await withGitHubApiRetries(() => client.request(
|
|
275
|
+
"DELETE /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies/{branch_policy_id}",
|
|
276
|
+
{
|
|
277
|
+
owner,
|
|
278
|
+
repo,
|
|
279
|
+
environment_name: environmentName,
|
|
280
|
+
branch_policy_id: policy.id
|
|
281
|
+
}
|
|
282
|
+
));
|
|
283
|
+
}
|
|
284
|
+
if (desired) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
await withGitHubApiRetries(() => client.request(
|
|
289
|
+
"POST /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies",
|
|
290
|
+
{
|
|
291
|
+
owner,
|
|
292
|
+
repo,
|
|
293
|
+
environment_name: environmentName,
|
|
294
|
+
name: branchName,
|
|
295
|
+
type: "branch"
|
|
296
|
+
}
|
|
297
|
+
));
|
|
298
|
+
} catch (error) {
|
|
299
|
+
if (error && typeof error === "object" && error.status === 303) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
throw error;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
async function paginateGitHubEnvironmentNames(client, route, params) {
|
|
306
|
+
const paginate = client.paginate;
|
|
307
|
+
return await paginateNames(() => paginate(route, {
|
|
308
|
+
...params,
|
|
309
|
+
per_page: 100
|
|
310
|
+
}));
|
|
311
|
+
}
|
|
312
|
+
async function listGitHubEnvironmentSecretNames(repository, environmentName, { client = createGitHubApiClient() } = {}) {
|
|
313
|
+
const { owner, name } = typeof repository === "string" ? parseGitHubRepositorySlug(repository) : repository;
|
|
314
|
+
try {
|
|
315
|
+
return await paginateGitHubEnvironmentNames(
|
|
316
|
+
client,
|
|
317
|
+
"GET /repos/{owner}/{repo}/environments/{environment_name}/secrets",
|
|
318
|
+
{ owner, repo: name, environment_name: environmentName }
|
|
319
|
+
);
|
|
320
|
+
} catch (error) {
|
|
321
|
+
throw normalizeGitHubApiError(error, `Unable to list GitHub environment secrets for ${owner}/${name}:${environmentName}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async function listGitHubEnvironmentVariableNames(repository, environmentName, { client = createGitHubApiClient() } = {}) {
|
|
325
|
+
const { owner, name } = typeof repository === "string" ? parseGitHubRepositorySlug(repository) : repository;
|
|
326
|
+
try {
|
|
327
|
+
return await paginateGitHubEnvironmentNames(
|
|
328
|
+
client,
|
|
329
|
+
"GET /repos/{owner}/{repo}/environments/{environment_name}/variables",
|
|
330
|
+
{ owner, repo: name, environment_name: environmentName }
|
|
331
|
+
);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
throw normalizeGitHubApiError(error, `Unable to list GitHub environment variables for ${owner}/${name}:${environmentName}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
211
336
|
async function encryptGitHubSecret(secret, key) {
|
|
212
337
|
await sodium.ready;
|
|
213
338
|
const messageBytes = Buffer.from(secret);
|
|
@@ -233,6 +358,27 @@ async function upsertGitHubRepositorySecret(repository, name, value, { client =
|
|
|
233
358
|
throw normalizeGitHubApiError(error, `Unable to upsert GitHub secret ${name} for ${owner}/${repo}`);
|
|
234
359
|
}
|
|
235
360
|
}
|
|
361
|
+
async function upsertGitHubEnvironmentSecret(repository, environmentName, name, value, { client = createGitHubApiClient() } = {}) {
|
|
362
|
+
const { owner, name: repo } = typeof repository === "string" ? parseGitHubRepositorySlug(repository) : repository;
|
|
363
|
+
try {
|
|
364
|
+
const key = await client.request("GET /repos/{owner}/{repo}/environments/{environment_name}/secrets/public-key", {
|
|
365
|
+
owner,
|
|
366
|
+
repo,
|
|
367
|
+
environment_name: environmentName
|
|
368
|
+
});
|
|
369
|
+
const encryptedValue = await encryptGitHubSecret(value, String(key.data.key ?? ""));
|
|
370
|
+
await withGitHubApiRetries(() => client.request("PUT /repos/{owner}/{repo}/environments/{environment_name}/secrets/{secret_name}", {
|
|
371
|
+
owner,
|
|
372
|
+
repo,
|
|
373
|
+
environment_name: environmentName,
|
|
374
|
+
secret_name: name,
|
|
375
|
+
encrypted_value: encryptedValue,
|
|
376
|
+
key_id: String(key.data.key_id ?? "")
|
|
377
|
+
}));
|
|
378
|
+
} catch (error) {
|
|
379
|
+
throw normalizeGitHubApiError(error, `Unable to upsert GitHub environment secret ${name} for ${owner}/${repo}:${environmentName}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
236
382
|
async function upsertGitHubRepositoryVariable(repository, name, value, { client = createGitHubApiClient() } = {}) {
|
|
237
383
|
const { owner, name: repo } = typeof repository === "string" ? parseGitHubRepositorySlug(repository) : repository;
|
|
238
384
|
try {
|
|
@@ -273,6 +419,38 @@ async function upsertGitHubRepositoryVariable(repository, name, value, { client
|
|
|
273
419
|
throw normalizeGitHubApiError(error, `Unable to upsert GitHub variable ${name} for ${owner}/${repo}`);
|
|
274
420
|
}
|
|
275
421
|
}
|
|
422
|
+
async function upsertGitHubEnvironmentVariable(repository, environmentName, name, value, { client = createGitHubApiClient() } = {}) {
|
|
423
|
+
const { owner, name: repo } = typeof repository === "string" ? parseGitHubRepositorySlug(repository) : repository;
|
|
424
|
+
try {
|
|
425
|
+
await withGitHubApiRetries(async () => {
|
|
426
|
+
try {
|
|
427
|
+
await client.request("POST /repos/{owner}/{repo}/environments/{environment_name}/variables", {
|
|
428
|
+
owner,
|
|
429
|
+
repo,
|
|
430
|
+
environment_name: environmentName,
|
|
431
|
+
name,
|
|
432
|
+
value
|
|
433
|
+
});
|
|
434
|
+
return;
|
|
435
|
+
} catch (error) {
|
|
436
|
+
const status = typeof error?.status === "number" ? Number(error.status) : null;
|
|
437
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
438
|
+
if (status !== 409 && status !== 422 && !/already exists/iu.test(message)) {
|
|
439
|
+
throw error;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
await client.request("PATCH /repos/{owner}/{repo}/environments/{environment_name}/variables/{name}", {
|
|
443
|
+
owner,
|
|
444
|
+
repo,
|
|
445
|
+
environment_name: environmentName,
|
|
446
|
+
name,
|
|
447
|
+
value
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
} catch (error) {
|
|
451
|
+
throw normalizeGitHubApiError(error, `Unable to upsert GitHub environment variable ${name} for ${owner}/${repo}:${environmentName}`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
276
454
|
function upsertGitHubRepositoryVariableWithGhCli(repository, name, value, {
|
|
277
455
|
env = process.env
|
|
278
456
|
} = {}) {
|
|
@@ -440,14 +618,19 @@ async function ensureGitHubBranchFromBase(repository, branch, {
|
|
|
440
618
|
}
|
|
441
619
|
export {
|
|
442
620
|
createGitHubApiClient,
|
|
621
|
+
ensureGitHubActionsEnvironment,
|
|
443
622
|
ensureGitHubBranchFromBase,
|
|
444
623
|
ensureGitHubRepository,
|
|
445
624
|
getGitHubRepository,
|
|
625
|
+
listGitHubEnvironmentSecretNames,
|
|
626
|
+
listGitHubEnvironmentVariableNames,
|
|
446
627
|
listGitHubRepositorySecretNames,
|
|
447
628
|
listGitHubRepositoryVariableNames,
|
|
448
629
|
maybeGetGitHubRepository,
|
|
449
630
|
parseGitHubRepositorySlug,
|
|
450
631
|
resolveGitHubApiToken,
|
|
632
|
+
upsertGitHubEnvironmentSecret,
|
|
633
|
+
upsertGitHubEnvironmentVariable,
|
|
451
634
|
upsertGitHubRepositorySecret,
|
|
452
635
|
upsertGitHubRepositoryVariable,
|
|
453
636
|
upsertGitHubRepositoryVariableWithGhCli,
|
|
@@ -16,11 +16,41 @@ export interface GitHubProvisionedRepository {
|
|
|
16
16
|
visibility: 'private' | 'public' | 'internal';
|
|
17
17
|
defaultBranch: string;
|
|
18
18
|
}
|
|
19
|
+
export interface TreeseedGitHubRepositoryTarget {
|
|
20
|
+
owner: string;
|
|
21
|
+
name: string;
|
|
22
|
+
visibility: 'private' | 'public' | 'internal';
|
|
23
|
+
source: 'config' | 'origin' | 'default';
|
|
24
|
+
}
|
|
19
25
|
export declare function getGitHubAutomationMode(): "stub" | "real";
|
|
20
26
|
export declare function parseGitHubRepositoryFromRemote(remoteUrl: any): string | null;
|
|
21
27
|
export declare function resolveGitHubRepositorySlug(tenantRoot: any): string;
|
|
22
28
|
export declare function maybeResolveGitHubRepositorySlug(tenantRoot: any): string | null;
|
|
23
29
|
export declare function resolveDefaultGitHubOwner(): string;
|
|
30
|
+
export declare function resolveGitHubRepositoryTarget(tenantRoot: string, { values, defaultName, }?: {
|
|
31
|
+
values?: Record<string, string | undefined>;
|
|
32
|
+
defaultName?: string;
|
|
33
|
+
}): TreeseedGitHubRepositoryTarget;
|
|
34
|
+
export declare function ensureGitHubBootstrapRepository(tenantRoot: string, { values, defaultName, onProgress, }?: {
|
|
35
|
+
values?: Record<string, string | undefined>;
|
|
36
|
+
defaultName?: string;
|
|
37
|
+
onProgress?: (message: string) => void;
|
|
38
|
+
}): Promise<{
|
|
39
|
+
repository: string;
|
|
40
|
+
target: TreeseedGitHubRepositoryTarget;
|
|
41
|
+
created: boolean;
|
|
42
|
+
remote: {
|
|
43
|
+
changed: boolean;
|
|
44
|
+
previous: null;
|
|
45
|
+
next: string;
|
|
46
|
+
} | {
|
|
47
|
+
changed: boolean;
|
|
48
|
+
previous: string;
|
|
49
|
+
next: string;
|
|
50
|
+
};
|
|
51
|
+
pushed: boolean;
|
|
52
|
+
mode: string;
|
|
53
|
+
}>;
|
|
24
54
|
export declare function createGitHubRepository(input: any): Promise<import("./github-api.ts").GitHubRepositorySummary | {
|
|
25
55
|
visibility: any;
|
|
26
56
|
defaultBranch: string;
|
|
@@ -6,6 +6,8 @@ import { packageRoot, loadCliDeployConfig } from "./runtime-tools.js";
|
|
|
6
6
|
import {
|
|
7
7
|
createGitHubApiClient,
|
|
8
8
|
ensureGitHubRepository,
|
|
9
|
+
maybeGetGitHubRepository,
|
|
10
|
+
parseGitHubRepositorySlug,
|
|
9
11
|
listGitHubRepositorySecretNames,
|
|
10
12
|
listGitHubRepositoryVariableNames,
|
|
11
13
|
upsertGitHubRepositorySecret,
|
|
@@ -97,7 +99,7 @@ function maybeResolveGitHubRepositorySlug(tenantRoot) {
|
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
101
|
function resolveDefaultGitHubOwner() {
|
|
100
|
-
const explicit = envOrNull("
|
|
102
|
+
const explicit = envOrNull("TREESEED_GITHUB_OWNER");
|
|
101
103
|
if (explicit) {
|
|
102
104
|
return explicit;
|
|
103
105
|
}
|
|
@@ -110,6 +112,108 @@ function resolveDefaultGitHubOwner() {
|
|
|
110
112
|
}
|
|
111
113
|
return "treeseed-ai";
|
|
112
114
|
}
|
|
115
|
+
function normalizeGitHubVisibility(value) {
|
|
116
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
117
|
+
return normalized === "public" || normalized === "internal" || normalized === "private" ? normalized : "private";
|
|
118
|
+
}
|
|
119
|
+
function configuredValue(values, key) {
|
|
120
|
+
const value = values?.[key] ?? process.env[key];
|
|
121
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : "";
|
|
122
|
+
}
|
|
123
|
+
function resolveGitHubRepositoryTarget(tenantRoot, {
|
|
124
|
+
values = {},
|
|
125
|
+
defaultName
|
|
126
|
+
} = {}) {
|
|
127
|
+
const origin = maybeResolveGitHubRepositorySlug(tenantRoot);
|
|
128
|
+
const parsedOrigin = origin ? parseGitHubRepositorySlug(origin) : null;
|
|
129
|
+
const owner = configuredValue(values, "TREESEED_GITHUB_OWNER") || parsedOrigin?.owner || "";
|
|
130
|
+
const name = configuredValue(values, "TREESEED_GITHUB_REPOSITORY_NAME") || parsedOrigin?.name || defaultName || "project";
|
|
131
|
+
if (!owner) {
|
|
132
|
+
throw new Error("Configure TREESEED_GITHUB_OWNER before GitHub repository bootstrap.");
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
owner: slugifySegment(owner, "owner"),
|
|
136
|
+
name: slugifySegment(name, "project"),
|
|
137
|
+
visibility: normalizeGitHubVisibility(configuredValue(values, "TREESEED_GITHUB_REPOSITORY_VISIBILITY")),
|
|
138
|
+
source: configuredValue(values, "TREESEED_GITHUB_OWNER") || configuredValue(values, "TREESEED_GITHUB_REPOSITORY_NAME") ? "config" : parsedOrigin ? "origin" : "default"
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function ensureGitRepositoryInitialized(cwd, defaultBranch) {
|
|
142
|
+
const insideWorkTree = runGit(["rev-parse", "--is-inside-work-tree"], { cwd, allowFailure: true }).stdout?.trim() === "true";
|
|
143
|
+
if (!insideWorkTree) {
|
|
144
|
+
runGit(["init", "-b", defaultBranch], { cwd });
|
|
145
|
+
}
|
|
146
|
+
ensureGitIdentity(cwd);
|
|
147
|
+
}
|
|
148
|
+
function ensureOriginRemote(cwd, repository, remoteName = "origin") {
|
|
149
|
+
const currentRemote = runGit(["remote", "get-url", remoteName], { cwd, allowFailure: true }).stdout?.trim() ?? "";
|
|
150
|
+
if (!currentRemote) {
|
|
151
|
+
runGit(["remote", "add", remoteName, repository.sshUrl], { cwd });
|
|
152
|
+
return { changed: true, previous: null, next: repository.sshUrl };
|
|
153
|
+
}
|
|
154
|
+
if (currentRemote !== repository.sshUrl && currentRemote !== repository.httpsUrl) {
|
|
155
|
+
runGit(["remote", "set-url", remoteName, repository.sshUrl], { cwd });
|
|
156
|
+
return { changed: true, previous: currentRemote, next: repository.sshUrl };
|
|
157
|
+
}
|
|
158
|
+
return { changed: false, previous: currentRemote, next: currentRemote };
|
|
159
|
+
}
|
|
160
|
+
function pushAllGitHubRefs(cwd, remoteName = "origin") {
|
|
161
|
+
runGit(["push", "-u", remoteName, "--all"], { cwd, capture: false });
|
|
162
|
+
runGit(["push", remoteName, "--tags"], { cwd, capture: false });
|
|
163
|
+
}
|
|
164
|
+
async function ensureGitHubBootstrapRepository(tenantRoot, {
|
|
165
|
+
values = {},
|
|
166
|
+
defaultName,
|
|
167
|
+
onProgress
|
|
168
|
+
} = {}) {
|
|
169
|
+
const target = resolveGitHubRepositoryTarget(tenantRoot, { values, defaultName });
|
|
170
|
+
const remotes = resolveGitHubRemoteUrls(target.owner, target.name);
|
|
171
|
+
const slug = remotes.slug;
|
|
172
|
+
onProgress?.(`[local][github][repo] Preparing ${slug} from ${target.source}...`);
|
|
173
|
+
if (isGitHubAutomationStubbed()) {
|
|
174
|
+
onProgress?.(`[local][github][repo] Stubbed GitHub automation; repository ${slug} not changed.`);
|
|
175
|
+
return {
|
|
176
|
+
repository: slug,
|
|
177
|
+
target,
|
|
178
|
+
created: false,
|
|
179
|
+
remote: { changed: false, previous: null, next: remotes.sshUrl },
|
|
180
|
+
pushed: false,
|
|
181
|
+
mode: "stub"
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const client = createGitHubApiClient({
|
|
185
|
+
env: {
|
|
186
|
+
GH_TOKEN: configuredValue(values, "GH_TOKEN") || configuredValue(values, "GITHUB_TOKEN"),
|
|
187
|
+
GITHUB_TOKEN: configuredValue(values, "GH_TOKEN") || configuredValue(values, "GITHUB_TOKEN")
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
const existing = await maybeGetGitHubRepository({ owner: target.owner, name: target.name }, { client });
|
|
191
|
+
const repository = existing ?? await ensureGitHubRepository({
|
|
192
|
+
owner: target.owner,
|
|
193
|
+
name: target.name,
|
|
194
|
+
visibility: target.visibility
|
|
195
|
+
}, { client });
|
|
196
|
+
const created = !existing;
|
|
197
|
+
onProgress?.(`[local][github][repo] ${created ? "Created" : "Verified"} ${repository.slug}.`);
|
|
198
|
+
ensureGitRepositoryInitialized(tenantRoot, repository.defaultBranch || "main");
|
|
199
|
+
const remote = ensureOriginRemote(tenantRoot, repository);
|
|
200
|
+
if (remote.changed) {
|
|
201
|
+
onProgress?.(`[local][github][repo] Updated origin remote to ${repository.slug}.`);
|
|
202
|
+
}
|
|
203
|
+
if (created) {
|
|
204
|
+
onProgress?.(`[local][github][repo] Pushing all local branches and tags to ${repository.slug}...`);
|
|
205
|
+
pushAllGitHubRefs(tenantRoot);
|
|
206
|
+
onProgress?.(`[local][github][repo] Pushed all local branches and tags to ${repository.slug}.`);
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
repository: repository.slug,
|
|
210
|
+
target,
|
|
211
|
+
created,
|
|
212
|
+
remote,
|
|
213
|
+
pushed: created,
|
|
214
|
+
mode: "real"
|
|
215
|
+
};
|
|
216
|
+
}
|
|
113
217
|
async function createGitHubRepository(input) {
|
|
114
218
|
const visibility = input.visibility ?? "private";
|
|
115
219
|
const remotes = resolveGitHubRemoteUrls(input.owner, input.name);
|
|
@@ -422,6 +526,7 @@ async function waitForGitHubWorkflowCompletion(tenantRoot, {
|
|
|
422
526
|
export {
|
|
423
527
|
createGitHubRepository,
|
|
424
528
|
ensureDeployWorkflow,
|
|
529
|
+
ensureGitHubBootstrapRepository,
|
|
425
530
|
ensureGitHubDeployAutomation,
|
|
426
531
|
ensureGitHubEnvironment,
|
|
427
532
|
ensureGitHubSecrets,
|
|
@@ -440,6 +545,7 @@ export {
|
|
|
440
545
|
requiredGitHubSecrets,
|
|
441
546
|
resolveDefaultGitHubOwner,
|
|
442
547
|
resolveGitHubRepositorySlug,
|
|
548
|
+
resolveGitHubRepositoryTarget,
|
|
443
549
|
resolveGitRepositoryRoot,
|
|
444
550
|
waitForGitHubWorkflowCompletion
|
|
445
551
|
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type ControlPlaneReporter } from '../../control-plane.ts';
|
|
2
|
+
import type { TreeseedRunnableBootstrapSystem } from '../../reconcile/index.ts';
|
|
3
|
+
import { type TreeseedBootstrapExecution, type TreeseedBootstrapWriter } from './bootstrap-runner.ts';
|
|
2
4
|
export type ProjectPlatformScope = 'local' | 'staging' | 'prod';
|
|
3
5
|
export type ProjectPlatformAction = 'provision' | 'deploy_code' | 'publish_content' | 'monitor';
|
|
4
6
|
export interface ProjectPlatformActionOptions {
|
|
@@ -9,9 +11,43 @@ export interface ProjectPlatformActionOptions {
|
|
|
9
11
|
dryRun?: boolean;
|
|
10
12
|
reporter?: ControlPlaneReporter;
|
|
11
13
|
skipProvision?: boolean;
|
|
14
|
+
bootstrapSystems?: TreeseedRunnableBootstrapSystem[];
|
|
15
|
+
bootstrapExecution?: TreeseedBootstrapExecution;
|
|
16
|
+
write?: TreeseedBootstrapWriter;
|
|
17
|
+
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
12
18
|
}
|
|
13
19
|
export declare function inferEnvironmentFromBranch(tenantRoot: string): "staging" | "prod";
|
|
14
20
|
export declare function resolveScope(environment: string | null): ProjectPlatformScope;
|
|
21
|
+
export type TenantCloudflareDeployContext = {
|
|
22
|
+
tenantRoot: string;
|
|
23
|
+
scope: ProjectPlatformScope;
|
|
24
|
+
target: any;
|
|
25
|
+
dryRun?: boolean;
|
|
26
|
+
wranglerPath: string;
|
|
27
|
+
databaseName: string;
|
|
28
|
+
pagesProjectName: string | null;
|
|
29
|
+
pagesBranchName: string | null;
|
|
30
|
+
env: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
31
|
+
write?: TreeseedBootstrapWriter;
|
|
32
|
+
};
|
|
33
|
+
export declare function prepareTenantCloudflareDeploy({ tenantRoot, scope, target: explicitTarget, dryRun, write, env, }: {
|
|
34
|
+
tenantRoot: string;
|
|
35
|
+
scope: ProjectPlatformScope;
|
|
36
|
+
target?: any;
|
|
37
|
+
dryRun?: boolean;
|
|
38
|
+
write?: TreeseedBootstrapWriter;
|
|
39
|
+
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
40
|
+
}): TenantCloudflareDeployContext;
|
|
41
|
+
export declare function runTenantDataMigration(context: TenantCloudflareDeployContext): Promise<{
|
|
42
|
+
databaseName: string;
|
|
43
|
+
dryRun: boolean;
|
|
44
|
+
}>;
|
|
45
|
+
export declare function runTenantWebBuild(context: Pick<TenantCloudflareDeployContext, 'tenantRoot' | 'scope' | 'dryRun' | 'env' | 'write'>): Promise<{
|
|
46
|
+
dryRun: boolean;
|
|
47
|
+
}>;
|
|
48
|
+
export declare function runTenantWebPublish(context: TenantCloudflareDeployContext): Promise<{
|
|
49
|
+
dryRun: boolean;
|
|
50
|
+
}>;
|
|
15
51
|
export declare function provisionProjectPlatform(options: ProjectPlatformActionOptions): Promise<{
|
|
16
52
|
ok: boolean;
|
|
17
53
|
scope: ProjectPlatformScope;
|
|
@@ -209,7 +245,7 @@ export declare function deployProjectPlatform(options: ProjectPlatformActionOpti
|
|
|
209
245
|
healthcheckTimeoutSeconds: number | null;
|
|
210
246
|
runtimeMode: string | null;
|
|
211
247
|
} | null;
|
|
212
|
-
})[];
|
|
248
|
+
} | undefined)[];
|
|
213
249
|
}>;
|
|
214
250
|
export declare function publishProjectContent(options: ProjectPlatformActionOptions): Promise<{
|
|
215
251
|
ok: boolean;
|
|
@@ -698,5 +734,5 @@ export declare function runProjectPlatformAction(action: ProjectPlatformAction,
|
|
|
698
734
|
healthcheckTimeoutSeconds: number | null;
|
|
699
735
|
runtimeMode: string | null;
|
|
700
736
|
} | null;
|
|
701
|
-
})[];
|
|
737
|
+
} | undefined)[];
|
|
702
738
|
}>;
|