@treeseed/sdk 0.12.23 → 0.12.25
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/github-api.d.ts +6 -0
- package/dist/operations/services/github-api.js +14 -0
- package/dist/reconcile/builtin-adapters.js +16 -4
- package/dist/reconcile/providers/github-private.d.ts +4 -0
- package/dist/reconcile/providers/github-private.js +8 -3
- package/dist/workflow/operations.d.ts +3 -3
- package/dist/workflow/operations.js +35 -2
- package/package.json +1 -1
|
@@ -170,6 +170,12 @@ export declare function listGitHubEnvironmentVariableNames(repository: string |
|
|
|
170
170
|
}, environmentName: string, { client }?: {
|
|
171
171
|
client?: GitHubApiClient;
|
|
172
172
|
}): Promise<Set<string>>;
|
|
173
|
+
export declare function listGitHubEnvironmentVariables(repository: string | {
|
|
174
|
+
owner: string;
|
|
175
|
+
name: string;
|
|
176
|
+
}, environmentName: string, { client }?: {
|
|
177
|
+
client?: GitHubApiClient;
|
|
178
|
+
}): Promise<Map<string, string>>;
|
|
173
179
|
export declare function upsertGitHubRepositorySecret(repository: string | {
|
|
174
180
|
owner: string;
|
|
175
181
|
name: string;
|
|
@@ -344,6 +344,19 @@ async function listGitHubEnvironmentVariableNames(repository, environmentName, {
|
|
|
344
344
|
throw normalizeGitHubApiError(error, `Unable to list GitHub environment variables for ${owner}/${name}:${environmentName}`);
|
|
345
345
|
}
|
|
346
346
|
}
|
|
347
|
+
async function listGitHubEnvironmentVariables(repository, environmentName, { client = createGitHubApiClient() } = {}) {
|
|
348
|
+
const { owner, name } = typeof repository === "string" ? parseGitHubRepositorySlug(repository) : repository;
|
|
349
|
+
try {
|
|
350
|
+
const paginate = client.paginate;
|
|
351
|
+
const variables = await withGitHubApiRetries(() => paginate(
|
|
352
|
+
"GET /repos/{owner}/{repo}/environments/{environment_name}/variables",
|
|
353
|
+
{ owner, repo: name, environment_name: environmentName, per_page: 100 }
|
|
354
|
+
));
|
|
355
|
+
return new Map(variables.map((entry) => [String(entry.name ?? "").trim(), String(entry.value ?? "")]).filter(([variableName]) => variableName.length > 0));
|
|
356
|
+
} catch (error) {
|
|
357
|
+
throw normalizeGitHubApiError(error, `Unable to list GitHub environment variables for ${owner}/${name}:${environmentName}`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
347
360
|
async function encryptGitHubSecret(secret, key) {
|
|
348
361
|
await sodium.ready;
|
|
349
362
|
const messageBytes = Buffer.from(secret);
|
|
@@ -927,6 +940,7 @@ export {
|
|
|
927
940
|
getLatestGitHubWorkflowRun,
|
|
928
941
|
listGitHubEnvironmentSecretNames,
|
|
929
942
|
listGitHubEnvironmentVariableNames,
|
|
943
|
+
listGitHubEnvironmentVariables,
|
|
930
944
|
listGitHubRepositorySecretNames,
|
|
931
945
|
listGitHubRepositoryVariableNames,
|
|
932
946
|
maybeGetGitHubRepository,
|
|
@@ -446,11 +446,12 @@ function buildGitHubBindingAdapter(unitType) {
|
|
|
446
446
|
const observed = await observeGitHubEnvironment(repository, environment, buildGitHubEnv(input));
|
|
447
447
|
const names = unitType === "github-secret-binding" ? observed.secretNames : observed.variableNames;
|
|
448
448
|
const exists = observed.exists && names.includes(name);
|
|
449
|
+
const value = unitType === "github-variable-binding" ? String(observed.variableValues?.[name] ?? "") : null;
|
|
449
450
|
const warnings = observed.authAvailable === false ? [String(observed.error ?? "GitHub authentication is unavailable")] : observed.exists ? [] : [`GitHub environment ${environment} is missing`];
|
|
450
451
|
return {
|
|
451
452
|
...genericObservedState(input, exists, warnings),
|
|
452
453
|
status: exists ? "ready" : "pending",
|
|
453
|
-
live: { repository, environment, name, exists, observed }
|
|
454
|
+
live: { repository, environment, name, exists, value, observed }
|
|
454
455
|
};
|
|
455
456
|
},
|
|
456
457
|
diff(input) {
|
|
@@ -458,13 +459,24 @@ function buildGitHubBindingAdapter(unitType) {
|
|
|
458
459
|
if (input.observed.live?.observed?.authAvailable === false) {
|
|
459
460
|
return { action: "blocked", reasons: input.observed.warnings, before: input.observed.live, after: input.unit.spec };
|
|
460
461
|
}
|
|
461
|
-
if (input.observed.exists) {
|
|
462
|
-
return noopDiff();
|
|
463
|
-
}
|
|
464
462
|
const value = buildGitHubEnv(input)[name];
|
|
465
463
|
if (!value) {
|
|
466
464
|
return { action: "blocked", reasons: [`Missing local value for ${name}`], before: input.observed.live, after: input.unit.spec };
|
|
467
465
|
}
|
|
466
|
+
if (input.observed.exists) {
|
|
467
|
+
if (unitType === "github-variable-binding") {
|
|
468
|
+
const observedValue = String(input.observed.live.value ?? "");
|
|
469
|
+
if (observedValue !== value) {
|
|
470
|
+
return {
|
|
471
|
+
action: "update",
|
|
472
|
+
reasons: [`GitHub variable ${name} value drifted`],
|
|
473
|
+
before: input.observed.live,
|
|
474
|
+
after: input.unit.spec
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return noopDiff();
|
|
479
|
+
}
|
|
468
480
|
return { action: "update", reasons: [`GitHub ${unitType === "github-secret-binding" ? "secret" : "variable"} ${name} is missing`], before: input.observed.live, after: input.unit.spec };
|
|
469
481
|
},
|
|
470
482
|
async apply(input) {
|
|
@@ -11,6 +11,9 @@ export declare function observeGitHubEnvironment(repository: string, environment
|
|
|
11
11
|
environment: string;
|
|
12
12
|
secretNames: string[];
|
|
13
13
|
variableNames: string[];
|
|
14
|
+
variableValues: {
|
|
15
|
+
[k: string]: string;
|
|
16
|
+
};
|
|
14
17
|
authAvailable?: undefined;
|
|
15
18
|
error?: undefined;
|
|
16
19
|
} | {
|
|
@@ -20,6 +23,7 @@ export declare function observeGitHubEnvironment(repository: string, environment
|
|
|
20
23
|
environment: string;
|
|
21
24
|
secretNames: never[];
|
|
22
25
|
variableNames: never[];
|
|
26
|
+
variableValues: {};
|
|
23
27
|
error: string;
|
|
24
28
|
}>;
|
|
25
29
|
export declare function ensureReconcileGitHubEnvironment(repository: string, environment: string, branchName: string | null, env: NodeJS.ProcessEnv | Record<string, string | undefined>): Promise<{
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
getLatestGitHubWorkflowRun,
|
|
7
7
|
listGitHubEnvironmentSecretNames,
|
|
8
8
|
listGitHubEnvironmentVariableNames,
|
|
9
|
+
listGitHubEnvironmentVariables,
|
|
9
10
|
upsertGitHubEnvironmentSecret,
|
|
10
11
|
upsertGitHubEnvironmentVariable,
|
|
11
12
|
waitForGitHubWorkflowRunCompletion
|
|
@@ -19,16 +20,18 @@ function isGitHubAuthError(message) {
|
|
|
19
20
|
async function observeGitHubEnvironment(repository, environment, env) {
|
|
20
21
|
const client = createReconcileGitHubClient(env);
|
|
21
22
|
try {
|
|
22
|
-
const [secretNames, variableNames] = await Promise.all([
|
|
23
|
+
const [secretNames, variableNames, variableValues] = await Promise.all([
|
|
23
24
|
listGitHubEnvironmentSecretNames(repository, environment, { client }),
|
|
24
|
-
listGitHubEnvironmentVariableNames(repository, environment, { client })
|
|
25
|
+
listGitHubEnvironmentVariableNames(repository, environment, { client }),
|
|
26
|
+
listGitHubEnvironmentVariables(repository, environment, { client })
|
|
25
27
|
]);
|
|
26
28
|
return {
|
|
27
29
|
exists: true,
|
|
28
30
|
repository,
|
|
29
31
|
environment,
|
|
30
32
|
secretNames: [...secretNames].sort(),
|
|
31
|
-
variableNames: [...variableNames].sort()
|
|
33
|
+
variableNames: [...variableNames].sort(),
|
|
34
|
+
variableValues: Object.fromEntries([...variableValues.entries()].sort(([left], [right]) => left.localeCompare(right)))
|
|
32
35
|
};
|
|
33
36
|
} catch (error) {
|
|
34
37
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -40,6 +43,7 @@ async function observeGitHubEnvironment(repository, environment, env) {
|
|
|
40
43
|
environment,
|
|
41
44
|
secretNames: [],
|
|
42
45
|
variableNames: [],
|
|
46
|
+
variableValues: {},
|
|
43
47
|
error: message
|
|
44
48
|
};
|
|
45
49
|
}
|
|
@@ -51,6 +55,7 @@ async function observeGitHubEnvironment(repository, environment, env) {
|
|
|
51
55
|
environment,
|
|
52
56
|
secretNames: [],
|
|
53
57
|
variableNames: [],
|
|
58
|
+
variableValues: {},
|
|
54
59
|
error: message
|
|
55
60
|
};
|
|
56
61
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { planTreeseedReconciliation, reconcileTreeseedTarget, type TreeseedReconcileTarget } from '../reconcile/index.js';
|
|
1
|
+
import { planTreeseedReconciliation, reconcileTreeseedTarget, type TreeseedDesiredUnit, type TreeseedReconcileTarget } from '../reconcile/index.js';
|
|
2
2
|
import { STAGING_BRANCH } from '../operations/services/git-workflow.js';
|
|
3
3
|
import type { TreeseedProofDriver } from '../operations/services/release-proof.js';
|
|
4
4
|
import { type TreeseedWorkflowTiming } from '../operations/services/workflow-timing.js';
|
|
@@ -927,7 +927,7 @@ export declare function workflowRelease(helpers: WorkflowOperationHelpers, input
|
|
|
927
927
|
}[];
|
|
928
928
|
reconcile: {
|
|
929
929
|
target: TreeseedReconcileTarget;
|
|
930
|
-
units:
|
|
930
|
+
units: TreeseedDesiredUnit[];
|
|
931
931
|
plans: import("../reconcile/contracts.js").TreeseedReconcilePlan[];
|
|
932
932
|
results: TreeseedReconcileResult[];
|
|
933
933
|
state: import("../reconcile/contracts.js").TreeseedReconcileStateRecord;
|
|
@@ -1149,7 +1149,7 @@ export declare function workflowRelease(helpers: WorkflowOperationHelpers, input
|
|
|
1149
1149
|
}[];
|
|
1150
1150
|
reconcile: {
|
|
1151
1151
|
target: TreeseedReconcileTarget;
|
|
1152
|
-
units:
|
|
1152
|
+
units: TreeseedDesiredUnit[];
|
|
1153
1153
|
plans: import("../reconcile/contracts.js").TreeseedReconcilePlan[];
|
|
1154
1154
|
results: TreeseedReconcileResult[];
|
|
1155
1155
|
state: import("../reconcile/contracts.js").TreeseedReconcileStateRecord;
|
|
@@ -5387,8 +5387,9 @@ async function runReleaseGateReconcileFacade(operation, helpers, root, target, i
|
|
|
5387
5387
|
};
|
|
5388
5388
|
const desiredGraph = compileTreeseedDesiredResourceGraph({ tenantRoot: root, target });
|
|
5389
5389
|
const rawUnits = compileTreeseedDesiredUnitsFromGraph(desiredGraph).filter((unit) => unit.provider === "treeseed" && (unit.unitType === "package-manifest" || unit.unitType.startsWith("release-gate:")) && unit.unitType !== "release-gate:npm-publish" && unit.unitType !== "release-gate:image-publish" && (includeHostedReleaseGates || unit.unitType !== "release-gate:hosted-reconcile" && unit.unitType !== "release-gate:live-verify") || unit.provider === "github" && (unit.unitType === "github-environment" || unit.unitType === "github-secret-binding" || unit.unitType === "github-variable-binding"));
|
|
5390
|
-
const
|
|
5391
|
-
const
|
|
5390
|
+
const unitsWithReleaseImageRefs = appendReleaseImageRefGitHubVariableBindings(rawUnits, input.releaseImageRefs ?? {});
|
|
5391
|
+
const rawUnitIds = new Set(unitsWithReleaseImageRefs.map((unit) => unit.unitId));
|
|
5392
|
+
const units = unitsWithReleaseImageRefs.map((unit) => ({
|
|
5392
5393
|
...unit,
|
|
5393
5394
|
dependencies: unit.dependencies.filter((dependency) => rawUnitIds.has(dependency))
|
|
5394
5395
|
}));
|
|
@@ -5461,6 +5462,38 @@ ${blockers.join("\n")}`, {
|
|
|
5461
5462
|
])
|
|
5462
5463
|
});
|
|
5463
5464
|
}
|
|
5465
|
+
function appendReleaseImageRefGitHubVariableBindings(units, releaseImageRefs) {
|
|
5466
|
+
const entries = Object.entries(releaseImageRefs).map(([name, value]) => [name.trim(), value.trim()]).filter(([name, value]) => name.length > 0 && value.length > 0);
|
|
5467
|
+
if (entries.length === 0) return units;
|
|
5468
|
+
const apiProductionEnvironment = units.find((unit) => unit.provider === "github" && unit.unitType === "github-environment" && unit.unitId === "github-environment:@treeseed/api:production");
|
|
5469
|
+
if (!apiProductionEnvironment) return units;
|
|
5470
|
+
const existingUnitIds = new Set(units.map((unit) => unit.unitId));
|
|
5471
|
+
const additions = entries.map(([variableName]) => {
|
|
5472
|
+
const unitId = `github-variable-binding:@treeseed/api:production:${variableName}`;
|
|
5473
|
+
if (existingUnitIds.has(unitId)) return null;
|
|
5474
|
+
return {
|
|
5475
|
+
...apiProductionEnvironment,
|
|
5476
|
+
unitId,
|
|
5477
|
+
unitType: "github-variable-binding",
|
|
5478
|
+
logicalName: `@treeseed/api production ${variableName}`,
|
|
5479
|
+
dependencies: [apiProductionEnvironment.unitId],
|
|
5480
|
+
spec: {
|
|
5481
|
+
packageId: "@treeseed/api",
|
|
5482
|
+
packageRoot: apiProductionEnvironment.spec.packageRoot,
|
|
5483
|
+
repository: apiProductionEnvironment.spec.repository,
|
|
5484
|
+
environment: "production",
|
|
5485
|
+
variableName,
|
|
5486
|
+
envName: variableName
|
|
5487
|
+
},
|
|
5488
|
+
secrets: {},
|
|
5489
|
+
metadata: {
|
|
5490
|
+
...apiProductionEnvironment.metadata,
|
|
5491
|
+
releaseImageRef: true
|
|
5492
|
+
}
|
|
5493
|
+
};
|
|
5494
|
+
}).filter((unit) => Boolean(unit));
|
|
5495
|
+
return additions.length > 0 ? [...units, ...additions] : units;
|
|
5496
|
+
}
|
|
5464
5497
|
async function workflowRelease(helpers, input) {
|
|
5465
5498
|
try {
|
|
5466
5499
|
return await withContextEnv(helpers.context.env, async () => {
|