@biffo/cli 0.41.9 → 0.41.11
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/core.version +1 -1
- package/dist/index.js +384 -215
- package/package.json +1 -1
package/core.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.41.
|
|
1
|
+
0.41.11
|
package/dist/index.js
CHANGED
|
@@ -51,12 +51,13 @@ function findTemplateRoot(startDir) {
|
|
|
51
51
|
dir = parent;
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
function resolveTemplateRoot(
|
|
55
|
-
const start = fromDir ?? dirname(fileURLToPath(import.meta.url));
|
|
54
|
+
function resolveTemplateRoot(options = {}) {
|
|
55
|
+
const start = options.fromDir ?? dirname(fileURLToPath(import.meta.url));
|
|
56
56
|
const root = findTemplateRoot(start);
|
|
57
57
|
if (!root) {
|
|
58
|
+
const guidance = options.guidance ?? "Point this command at a biffo-template checkout at the target version.";
|
|
58
59
|
throw new Error(
|
|
59
|
-
`Could not locate a Biffo template root (a directory with ${CORE_MANIFEST_FILE} and core.version) above ${start}.
|
|
60
|
+
`Could not locate a Biffo template root (a directory with ${CORE_MANIFEST_FILE} and core.version) above ${start}. ` + guidance
|
|
60
61
|
);
|
|
61
62
|
}
|
|
62
63
|
return root;
|
|
@@ -236,6 +237,7 @@ var log = {
|
|
|
236
237
|
};
|
|
237
238
|
|
|
238
239
|
// src/commands/core-diff.ts
|
|
240
|
+
var MISSING_TEMPLATE_ROOT_GUIDANCE = "Pass --template <path> to a biffo-template checkout, e.g. `biffo core diff --template /path/to/biffo-template`.";
|
|
239
241
|
var coreDiffCommand = new Command("diff").description(
|
|
240
242
|
"Show which template-owned files an upgrade would change in this instance (read-only)"
|
|
241
243
|
).option("--cwd <path>", "Instance repo root to inspect (defaults to the current directory)").option(
|
|
@@ -253,7 +255,7 @@ var coreDiffCommand = new Command("diff").description(
|
|
|
253
255
|
}
|
|
254
256
|
});
|
|
255
257
|
async function runCoreDiff(options) {
|
|
256
|
-
const templateRoot = options.templateRoot ?? resolveTemplateRoot();
|
|
258
|
+
const templateRoot = options.templateRoot ?? resolveTemplateRoot({ guidance: MISSING_TEMPLATE_ROOT_GUIDANCE });
|
|
257
259
|
const manifest = readCoreManifest(templateRoot);
|
|
258
260
|
const templateVersion = readCoreVersionFile(join3(templateRoot, "core.version"));
|
|
259
261
|
const instanceVersion = readInstanceCoreVersion(options.cwd);
|
|
@@ -833,6 +835,39 @@ var GitHubAdapter = class {
|
|
|
833
835
|
log.info(`Committed ${files.map((f) => f.path).join(", ")} to ${branch}`);
|
|
834
836
|
return commit.sha;
|
|
835
837
|
}
|
|
838
|
+
/** The commit SHA at the head of `branch`. */
|
|
839
|
+
async getBranchSha(org, repo, branch) {
|
|
840
|
+
const ref = await this.waitForRef(org, repo, `heads/${branch}`, 12e4, 3e3);
|
|
841
|
+
return ref.object.sha;
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Point `branch` at `sha` via a fast-forward-only ref update (issue #329).
|
|
845
|
+
*
|
|
846
|
+
* `sha` must be an ancestor-descendant of the branch's current head:
|
|
847
|
+
* GitHub's `updateRef` rejects a non-fast-forward move unless `force` is set,
|
|
848
|
+
* and it is deliberately left unset here. The only caller is `writeInstanceFiles`,
|
|
849
|
+
* moving a freshly-created branch onto the instance commit built on that
|
|
850
|
+
* branch's own shared base — always a fast-forward. If it ever isn't, failing
|
|
851
|
+
* loudly is the correct outcome, not clobbering history.
|
|
852
|
+
*
|
|
853
|
+
* Idempotent: a no-op (no API write) when `branch` is already at `sha`, so a
|
|
854
|
+
* resumed init neither errors nor rewrites anything.
|
|
855
|
+
*/
|
|
856
|
+
async fastForwardBranch(org, repo, branch, sha) {
|
|
857
|
+
const current = await this.getBranchSha(org, repo, branch);
|
|
858
|
+
if (current === sha) {
|
|
859
|
+
log.info(`${branch} already at ${sha.slice(0, 7)} \u2014 skipping`);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
await this.octokit.git.updateRef({
|
|
863
|
+
owner: org,
|
|
864
|
+
repo,
|
|
865
|
+
ref: `heads/${branch}`,
|
|
866
|
+
sha,
|
|
867
|
+
force: false
|
|
868
|
+
});
|
|
869
|
+
log.info(`Fast-forwarded ${branch} to ${sha.slice(0, 7)}`);
|
|
870
|
+
}
|
|
836
871
|
async setDefaultBranch(org, repo, branch) {
|
|
837
872
|
await this.octokit.repos.update({ owner: org, repo, default_branch: branch });
|
|
838
873
|
log.info(`Default branch set to ${branch}`);
|
|
@@ -1508,6 +1543,7 @@ function materializeTemplateAtTag(repo, version, git = defaultGit) {
|
|
|
1508
1543
|
}
|
|
1509
1544
|
|
|
1510
1545
|
// src/commands/core-upgrade.ts
|
|
1546
|
+
var MISSING_TEMPLATE_ROOT_GUIDANCE2 = "Pass --template-repo <path> to a biffo-template git checkout, e.g. `biffo core upgrade --template-repo /path/to/biffo-template`.";
|
|
1511
1547
|
var coreUpgradeCommand = new Command3("upgrade").description("Three-way-merge template-owned files for a core upgrade; preview it or open a PR").option("--cwd <path>", "Instance repo root to upgrade (defaults to the current directory)").option(
|
|
1512
1548
|
"--template-repo <path>",
|
|
1513
1549
|
"Path to a biffo-template git checkout whose core-v* tags supply the base/target trees (defaults to the template this CLI ships with)"
|
|
@@ -1570,7 +1606,7 @@ async function runCoreUpgrade(options, deps = defaultDeps()) {
|
|
|
1570
1606
|
}
|
|
1571
1607
|
async function runCoreUpgradeResolved(options, deps, cleanups) {
|
|
1572
1608
|
const materialize = deps.materialize ?? materializeTemplateAtTag;
|
|
1573
|
-
const templateRepo = options.templateRepo ? resolve3(options.templateRepo) : resolveTemplateRoot();
|
|
1609
|
+
const templateRepo = options.templateRepo ? resolve3(options.templateRepo) : resolveTemplateRoot({ guidance: MISSING_TEMPLATE_ROOT_GUIDANCE2 });
|
|
1574
1610
|
const instanceVersion = readInstanceCoreVersion(options.cwd);
|
|
1575
1611
|
let theirsDir;
|
|
1576
1612
|
let toVersion;
|
|
@@ -2701,6 +2737,287 @@ function registerNonInteractive(command) {
|
|
|
2701
2737
|
return command;
|
|
2702
2738
|
}
|
|
2703
2739
|
|
|
2740
|
+
// src/lib/root-sibling.ts
|
|
2741
|
+
var ROOT_SIBLING_NAME = "app";
|
|
2742
|
+
var RESERVED_SIBLING_NAMES = ["admin", "login", ROOT_SIBLING_NAME];
|
|
2743
|
+
function isRootPathPrefix(pathPrefix) {
|
|
2744
|
+
return pathPrefix === "";
|
|
2745
|
+
}
|
|
2746
|
+
function registryNameFor(pathPrefix) {
|
|
2747
|
+
return isRootPathPrefix(pathPrefix) ? ROOT_SIBLING_NAME : pathPrefix;
|
|
2748
|
+
}
|
|
2749
|
+
function displayPath(pathPrefix) {
|
|
2750
|
+
return isRootPathPrefix(pathPrefix) ? "/" : `/${pathPrefix}`;
|
|
2751
|
+
}
|
|
2752
|
+
function basePathFor(pathPrefix) {
|
|
2753
|
+
return isRootPathPrefix(pathPrefix) ? "" : `/${pathPrefix}`;
|
|
2754
|
+
}
|
|
2755
|
+
function rootSiblingProjectName(coreProjectName) {
|
|
2756
|
+
return `${coreProjectName}-app`;
|
|
2757
|
+
}
|
|
2758
|
+
function bucketRegionalDomain(bucketName, region) {
|
|
2759
|
+
return region === "us-east-1" ? `${bucketName}.s3.amazonaws.com` : `${bucketName}.s3.${region}.amazonaws.com`;
|
|
2760
|
+
}
|
|
2761
|
+
function siteBucketName(projectName, environment, accountId) {
|
|
2762
|
+
return `${projectName}-${environment}-site-${accountId}`;
|
|
2763
|
+
}
|
|
2764
|
+
function upsertSiblingOrigin(existing, entry) {
|
|
2765
|
+
return [...existing.filter((s) => s.name !== entry.name), entry];
|
|
2766
|
+
}
|
|
2767
|
+
function serializeRegistry(origins) {
|
|
2768
|
+
return JSON.stringify({ sibling_origins: origins }, null, 2) + "\n";
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
// src/lib/sibling-teardown.ts
|
|
2772
|
+
var SiblingResolutionError = class extends Error {
|
|
2773
|
+
constructor(message) {
|
|
2774
|
+
super(message);
|
|
2775
|
+
this.name = "SiblingResolutionError";
|
|
2776
|
+
}
|
|
2777
|
+
};
|
|
2778
|
+
var REGISTRY_ENVIRONMENTS = ["dev", "staging", "prod"];
|
|
2779
|
+
function registryPath(environment) {
|
|
2780
|
+
return `infra/environments/${environment}/siblings.auto.tfvars.json`;
|
|
2781
|
+
}
|
|
2782
|
+
var REGISTRATION_BRANCH_PREFIX = "biffo/register-sibling-";
|
|
2783
|
+
function parseSiblingBucketDomain(domain) {
|
|
2784
|
+
const host = /^(?<bucket>[a-z0-9][a-z0-9.-]*)\.s3(?:\.[a-z0-9-]+)?\.amazonaws\.com$/.exec(domain);
|
|
2785
|
+
const bucket = host?.groups?.["bucket"];
|
|
2786
|
+
if (!bucket) return null;
|
|
2787
|
+
const parts = /^(?<project>.+)-(?<env>dev|staging|prod)-site-(?<account>\d{12})$/.exec(bucket);
|
|
2788
|
+
const project = parts?.groups?.["project"];
|
|
2789
|
+
const env = parts?.groups?.["env"];
|
|
2790
|
+
const account = parts?.groups?.["account"];
|
|
2791
|
+
if (!project || !env || !account) return null;
|
|
2792
|
+
return { projectName: project, environment: env, accountId: account };
|
|
2793
|
+
}
|
|
2794
|
+
function parseRegistry(contents) {
|
|
2795
|
+
if (contents === void 0 || contents.trim() === "") return [];
|
|
2796
|
+
let parsed;
|
|
2797
|
+
try {
|
|
2798
|
+
parsed = JSON.parse(contents);
|
|
2799
|
+
} catch {
|
|
2800
|
+
throw new SiblingResolutionError(
|
|
2801
|
+
"A siblings.auto.tfvars.json in the core repo is not valid JSON. Refusing to tear down while the sibling registry cannot be read \u2014 fix or remove the file and re-run."
|
|
2802
|
+
);
|
|
2803
|
+
}
|
|
2804
|
+
const origins = parsed.sibling_origins;
|
|
2805
|
+
if (origins === void 0) return [];
|
|
2806
|
+
if (!Array.isArray(origins)) {
|
|
2807
|
+
throw new SiblingResolutionError(
|
|
2808
|
+
"sibling_origins in the core repo is not a list. Refusing to tear down while the sibling registry cannot be read."
|
|
2809
|
+
);
|
|
2810
|
+
}
|
|
2811
|
+
return origins;
|
|
2812
|
+
}
|
|
2813
|
+
function collectSiblings(sources) {
|
|
2814
|
+
const byPrefix = /* @__PURE__ */ new Map();
|
|
2815
|
+
for (const source of sources) {
|
|
2816
|
+
for (const entry of source.entries) {
|
|
2817
|
+
if (!entry?.name || !entry.bucket_regional_domain) {
|
|
2818
|
+
throw new SiblingResolutionError(
|
|
2819
|
+
`A sibling entry in ${registryPath(source.environment)} is missing name or bucket_regional_domain. Refusing to tear down while a registered sibling cannot be identified \u2014 it would be left behind, still billing.`
|
|
2820
|
+
);
|
|
2821
|
+
}
|
|
2822
|
+
const parsed = parseSiblingBucketDomain(entry.bucket_regional_domain);
|
|
2823
|
+
if (!parsed) {
|
|
2824
|
+
throw new SiblingResolutionError(
|
|
2825
|
+
`Could not work out which project owns the sibling "${entry.name}" from its bucket "${entry.bucket_regional_domain}" (in ${registryPath(source.environment)}).
|
|
2826
|
+
Refusing to guess \u2014 tear that sibling down by hand, remove its entry, then re-run.`
|
|
2827
|
+
);
|
|
2828
|
+
}
|
|
2829
|
+
const existing = byPrefix.get(entry.name);
|
|
2830
|
+
if (existing) {
|
|
2831
|
+
if (existing.projectName !== parsed.projectName) {
|
|
2832
|
+
throw new SiblingResolutionError(
|
|
2833
|
+
`The sibling "${entry.name}" maps to two different projects across environments ("${existing.projectName}" and "${parsed.projectName}"). Refusing to guess which repo to delete \u2014 resolve the registry by hand and re-run.`
|
|
2834
|
+
);
|
|
2835
|
+
}
|
|
2836
|
+
if (!existing.environments.includes(parsed.environment)) {
|
|
2837
|
+
existing.environments.push(parsed.environment);
|
|
2838
|
+
}
|
|
2839
|
+
if (source.pendingRegistrationPr === void 0) {
|
|
2840
|
+
existing.registered = true;
|
|
2841
|
+
delete existing.pendingRegistrationPr;
|
|
2842
|
+
}
|
|
2843
|
+
continue;
|
|
2844
|
+
}
|
|
2845
|
+
byPrefix.set(entry.name, {
|
|
2846
|
+
pathPrefix: entry.name,
|
|
2847
|
+
projectName: parsed.projectName,
|
|
2848
|
+
environments: [parsed.environment],
|
|
2849
|
+
accountId: parsed.accountId,
|
|
2850
|
+
registered: source.pendingRegistrationPr === void 0,
|
|
2851
|
+
...source.pendingRegistrationPr !== void 0 ? { pendingRegistrationPr: source.pendingRegistrationPr } : {}
|
|
2852
|
+
});
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
return [...byPrefix.values()].sort((a, b) => a.pathPrefix.localeCompare(b.pathPrefix));
|
|
2856
|
+
}
|
|
2857
|
+
var SIBLING_MARKER_FILE = "biffo.sibling.json";
|
|
2858
|
+
function markerMatches(marker, coreProjectName, sibling) {
|
|
2859
|
+
if (!marker) return false;
|
|
2860
|
+
if (marker.core_project !== coreProjectName) return false;
|
|
2861
|
+
if (marker.path_prefix === sibling.pathPrefix) return true;
|
|
2862
|
+
if (marker.path_prefix === "" && sibling.pathPrefix === ROOT_SIBLING_NAME) return true;
|
|
2863
|
+
return marker.name === sibling.projectName;
|
|
2864
|
+
}
|
|
2865
|
+
async function resolveSiblingRepos(github, coreOrg, coreProjectName, siblings) {
|
|
2866
|
+
const resolved = [];
|
|
2867
|
+
for (const sibling of siblings) {
|
|
2868
|
+
const repo = sibling.projectName;
|
|
2869
|
+
if (!await github.repoExists(coreOrg, repo)) {
|
|
2870
|
+
resolved.push({ ...sibling, org: coreOrg, repo, repoState: "gone" });
|
|
2871
|
+
continue;
|
|
2872
|
+
}
|
|
2873
|
+
const raw = await github.getFileContent(coreOrg, repo, SIBLING_MARKER_FILE);
|
|
2874
|
+
let marker = null;
|
|
2875
|
+
if (raw !== void 0) {
|
|
2876
|
+
try {
|
|
2877
|
+
marker = JSON.parse(raw);
|
|
2878
|
+
} catch {
|
|
2879
|
+
marker = null;
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
if (!markerMatches(marker, coreProjectName, sibling)) {
|
|
2883
|
+
throw new SiblingResolutionError(
|
|
2884
|
+
`Refusing to tear down: ${coreOrg}/${repo} is registered as the sibling "${sibling.pathPrefix}" of "${coreProjectName}", but that repo does not carry a matching ${SIBLING_MARKER_FILE}.
|
|
2885
|
+
It may be an unrelated repo that happens to share the name. Deleting a repo cannot be undone, so nothing has been deleted.
|
|
2886
|
+
Delete ${coreOrg}/${repo} by hand if it really is the sibling, remove its entry from the core repo's siblings.auto.tfvars.json, then re-run biffo teardown.`
|
|
2887
|
+
);
|
|
2888
|
+
}
|
|
2889
|
+
resolved.push({ ...sibling, org: coreOrg, repo, repoState: "present" });
|
|
2890
|
+
}
|
|
2891
|
+
return resolved;
|
|
2892
|
+
}
|
|
2893
|
+
async function discoverSiblings(github, coreOrg, coreRepo, coreProjectName) {
|
|
2894
|
+
const sources = [];
|
|
2895
|
+
for (const env of REGISTRY_ENVIRONMENTS) {
|
|
2896
|
+
const contents = await github.getFileContent(coreOrg, coreRepo, registryPath(env));
|
|
2897
|
+
sources.push({ environment: env, entries: parseRegistry(contents) });
|
|
2898
|
+
}
|
|
2899
|
+
const openPrs = await github.listOpenPullRequests(coreOrg, coreRepo);
|
|
2900
|
+
for (const pr of openPrs) {
|
|
2901
|
+
if (!pr.headRef.startsWith(REGISTRATION_BRANCH_PREFIX)) continue;
|
|
2902
|
+
for (const env of REGISTRY_ENVIRONMENTS) {
|
|
2903
|
+
const contents = await github.getFileContent(coreOrg, coreRepo, registryPath(env), pr.headRef);
|
|
2904
|
+
sources.push({
|
|
2905
|
+
environment: env,
|
|
2906
|
+
entries: parseRegistry(contents),
|
|
2907
|
+
pendingRegistrationPr: pr.number
|
|
2908
|
+
});
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
const discovered = collectSiblings(sources);
|
|
2912
|
+
return resolveSiblingRepos(github, coreOrg, coreProjectName, discovered);
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
// src/lib/sibling-wiring.ts
|
|
2916
|
+
function cloudfrontDistributionArn(accountId, distributionId) {
|
|
2917
|
+
return `arn:aws:cloudfront::${accountId}:distribution/${distributionId}`;
|
|
2918
|
+
}
|
|
2919
|
+
var REQUIRED_CORE_OUTPUTS = [
|
|
2920
|
+
"cognito_user_pool_id",
|
|
2921
|
+
"cognito_client_id",
|
|
2922
|
+
"api_gateway_url",
|
|
2923
|
+
"portal_url",
|
|
2924
|
+
"cloudfront_distribution_id"
|
|
2925
|
+
];
|
|
2926
|
+
function coreWiringFromOutputs(outputs, coreAccountId, environment) {
|
|
2927
|
+
const missing = REQUIRED_CORE_OUTPUTS.filter((k) => !outputs[k]?.trim());
|
|
2928
|
+
if (missing.length > 0) {
|
|
2929
|
+
throw new Error(
|
|
2930
|
+
`Cannot wire siblings for ${environment}: the core deploy did not expose ${missing.join(", ")} in its Terraform outputs. A sibling wired without these would build a frontend pointing at no API/Cognito and skip its S3 bucket policy (so / returns 403). Confirm the core actually deployed ${environment} before wiring.`
|
|
2931
|
+
);
|
|
2932
|
+
}
|
|
2933
|
+
const distributionId = outputs["cloudfront_distribution_id"];
|
|
2934
|
+
return {
|
|
2935
|
+
identity: {
|
|
2936
|
+
cognitoUserPoolId: outputs["cognito_user_pool_id"],
|
|
2937
|
+
cognitoClientId: outputs["cognito_client_id"],
|
|
2938
|
+
apiUrl: outputs["api_gateway_url"],
|
|
2939
|
+
portalUrl: outputs["portal_url"]
|
|
2940
|
+
},
|
|
2941
|
+
cdn: {
|
|
2942
|
+
distributionId,
|
|
2943
|
+
distributionArn: cloudfrontDistributionArn(coreAccountId, distributionId)
|
|
2944
|
+
}
|
|
2945
|
+
};
|
|
2946
|
+
}
|
|
2947
|
+
async function setSiblingCoreIdentity(github, org, repo, env, identity) {
|
|
2948
|
+
await github.setEnvVariable(
|
|
2949
|
+
org,
|
|
2950
|
+
repo,
|
|
2951
|
+
env,
|
|
2952
|
+
"CORE_COGNITO_USER_POOL_ID",
|
|
2953
|
+
identity.cognitoUserPoolId
|
|
2954
|
+
);
|
|
2955
|
+
await github.setEnvVariable(org, repo, env, "CORE_COGNITO_CLIENT_ID", identity.cognitoClientId);
|
|
2956
|
+
await github.setEnvVariable(org, repo, env, "CORE_API_URL", identity.apiUrl);
|
|
2957
|
+
await github.setEnvVariable(org, repo, env, "CORE_PORTAL_URL", identity.portalUrl);
|
|
2958
|
+
await github.setEnvVariable(
|
|
2959
|
+
org,
|
|
2960
|
+
repo,
|
|
2961
|
+
env,
|
|
2962
|
+
"CORS_ORIGINS_JSON",
|
|
2963
|
+
JSON.stringify([identity.portalUrl])
|
|
2964
|
+
);
|
|
2965
|
+
}
|
|
2966
|
+
async function wireSiblingEnvironment(github, org, repo, env, wiring) {
|
|
2967
|
+
await setSiblingCoreIdentity(github, org, repo, env, wiring.identity);
|
|
2968
|
+
await github.setEnvVariable(
|
|
2969
|
+
org,
|
|
2970
|
+
repo,
|
|
2971
|
+
env,
|
|
2972
|
+
"PARENT_CLOUDFRONT_DISTRIBUTION_ARN",
|
|
2973
|
+
wiring.cdn.distributionArn
|
|
2974
|
+
);
|
|
2975
|
+
await github.setEnvVariable(
|
|
2976
|
+
org,
|
|
2977
|
+
repo,
|
|
2978
|
+
env,
|
|
2979
|
+
"PARENT_CLOUDFRONT_DISTRIBUTION_ID",
|
|
2980
|
+
wiring.cdn.distributionId
|
|
2981
|
+
);
|
|
2982
|
+
}
|
|
2983
|
+
async function wireSiblingsAfterCoreDeploy(github, coreOrg, coreRepo, coreProjectName, environment, wiring, siblingGithubToken) {
|
|
2984
|
+
const siblings = await discoverSiblings(github, coreOrg, coreRepo, coreProjectName);
|
|
2985
|
+
const result = { wired: [], gone: [], skippedEnv: [] };
|
|
2986
|
+
for (const sibling of siblings) {
|
|
2987
|
+
const slug = `${sibling.org}/${sibling.repo}`;
|
|
2988
|
+
if (sibling.repoState === "gone") {
|
|
2989
|
+
result.gone.push(slug);
|
|
2990
|
+
continue;
|
|
2991
|
+
}
|
|
2992
|
+
await github.setRepoSecret(
|
|
2993
|
+
sibling.org,
|
|
2994
|
+
sibling.repo,
|
|
2995
|
+
"SIBLING_GITHUB_TOKEN",
|
|
2996
|
+
siblingGithubToken
|
|
2997
|
+
);
|
|
2998
|
+
if (!sibling.environments.includes(environment)) {
|
|
2999
|
+
result.skippedEnv.push(slug);
|
|
3000
|
+
continue;
|
|
3001
|
+
}
|
|
3002
|
+
await wireSiblingEnvironment(github, sibling.org, sibling.repo, environment, wiring);
|
|
3003
|
+
result.wired.push(slug);
|
|
3004
|
+
}
|
|
3005
|
+
return result;
|
|
3006
|
+
}
|
|
3007
|
+
function formatWiringResult(environment, result) {
|
|
3008
|
+
const lines = [];
|
|
3009
|
+
for (const slug of result.wired) {
|
|
3010
|
+
lines.push(` Wired sibling ${slug} (${environment}): CORE_*, CORS, PARENT_CLOUDFRONT_*, token`);
|
|
3011
|
+
}
|
|
3012
|
+
for (const slug of result.skippedEnv) {
|
|
3013
|
+
lines.push(` Sibling ${slug}: token set; not registered for ${environment}, env vars skipped`);
|
|
3014
|
+
}
|
|
3015
|
+
for (const slug of result.gone) {
|
|
3016
|
+
lines.push(` Sibling ${slug}: registered but repo not found \u2014 skipped`);
|
|
3017
|
+
}
|
|
3018
|
+
return lines;
|
|
3019
|
+
}
|
|
3020
|
+
|
|
2704
3021
|
// src/commands/deploy.ts
|
|
2705
3022
|
var deployCommand = new Command9("deploy").description("Deploy infrastructure and application to an environment").argument("<environment>", "Target environment: dev | staging | prod").option("--infra-only", "Deploy infrastructure only, skip application build").option("--app-only", "Deploy application only, skip Terraform").option("-p, --project <name>", "Project name (overrides biffo.config.json in current directory)").option("-c, --config <path>", "Path to biffo.config.json").option("-y, --yes", "Skip deployment target confirmation").action(
|
|
2706
3023
|
async (environment, options) => {
|
|
@@ -2968,8 +3285,9 @@ async function runDeploy(github, aws, config, environment, options = {}) {
|
|
|
2968
3285
|
}
|
|
2969
3286
|
const reportStep = totalSteps;
|
|
2970
3287
|
log.step(reportStep, totalSteps, "Reading deployment outputs...");
|
|
3288
|
+
let outputs;
|
|
2971
3289
|
try {
|
|
2972
|
-
|
|
3290
|
+
outputs = await aws.readTerraformOutputs(stateBucket, stateKey);
|
|
2973
3291
|
console.log(chalk8.bold("\n Deploy complete!\n"));
|
|
2974
3292
|
if (outputs.portal_url) console.log(` Portal: ${chalk8.cyan(outputs.portal_url)}`);
|
|
2975
3293
|
if (outputs.api_gateway_url)
|
|
@@ -3006,6 +3324,50 @@ async function runDeploy(github, aws, config, environment, options = {}) {
|
|
|
3006
3324
|
console.log(` Actions: ${actionsUrl}
|
|
3007
3325
|
`);
|
|
3008
3326
|
}
|
|
3327
|
+
await wireSiblingsIfAny(github, config, environment, awsConfig2.account_id, outputs, options.token);
|
|
3328
|
+
}
|
|
3329
|
+
async function wireSiblingsIfAny(github, config, environment, coreAccountId, outputs, token) {
|
|
3330
|
+
const { org, repo } = config.source_control.config;
|
|
3331
|
+
if (!outputs) {
|
|
3332
|
+
log.warn(
|
|
3333
|
+
`Skipped wiring siblings: this run produced no readable Terraform outputs (e.g. --app-only without a prior infra apply). Re-run \`biffo deploy ${environment}\` once the core infrastructure is applied so its siblings can be wired.`
|
|
3334
|
+
);
|
|
3335
|
+
return;
|
|
3336
|
+
}
|
|
3337
|
+
if (!token) {
|
|
3338
|
+
log.warn(
|
|
3339
|
+
"Skipped wiring siblings: no GitHub token available to set the SIBLING_GITHUB_TOKEN secret."
|
|
3340
|
+
);
|
|
3341
|
+
return;
|
|
3342
|
+
}
|
|
3343
|
+
try {
|
|
3344
|
+
const wiring = coreWiringFromOutputs(outputs, coreAccountId, environment);
|
|
3345
|
+
const result = await wireSiblingsAfterCoreDeploy(
|
|
3346
|
+
github,
|
|
3347
|
+
org,
|
|
3348
|
+
repo,
|
|
3349
|
+
config.project.name,
|
|
3350
|
+
environment,
|
|
3351
|
+
wiring,
|
|
3352
|
+
token
|
|
3353
|
+
);
|
|
3354
|
+
const lines = formatWiringResult(environment, result);
|
|
3355
|
+
if (lines.length > 0) {
|
|
3356
|
+
console.log(chalk8.dim("\n Siblings:"));
|
|
3357
|
+
for (const line of lines) console.log(chalk8.dim(line));
|
|
3358
|
+
console.log();
|
|
3359
|
+
}
|
|
3360
|
+
} catch (err) {
|
|
3361
|
+
if (err instanceof SiblingResolutionError) {
|
|
3362
|
+
log.error(`Could not wire siblings: ${err.message}`);
|
|
3363
|
+
} else {
|
|
3364
|
+
log.error(`Failed to wire siblings to the deployed core: ${err.message}`);
|
|
3365
|
+
}
|
|
3366
|
+
log.error(
|
|
3367
|
+
` The core deployed, but at least one sibling was not wired. Fix the cause and re-run \`biffo deploy ${environment}\` \u2014 wiring is idempotent.`
|
|
3368
|
+
);
|
|
3369
|
+
process.exit(1);
|
|
3370
|
+
}
|
|
3009
3371
|
}
|
|
3010
3372
|
function resolveGithubToken() {
|
|
3011
3373
|
if (process.env["GITHUB_TOKEN"]) return process.env["GITHUB_TOKEN"];
|
|
@@ -3507,37 +3869,6 @@ async function resolveRepoIds(github, config) {
|
|
|
3507
3869
|
}
|
|
3508
3870
|
}
|
|
3509
3871
|
|
|
3510
|
-
// src/lib/root-sibling.ts
|
|
3511
|
-
var ROOT_SIBLING_NAME = "app";
|
|
3512
|
-
var RESERVED_SIBLING_NAMES = ["admin", "login", ROOT_SIBLING_NAME];
|
|
3513
|
-
function isRootPathPrefix(pathPrefix) {
|
|
3514
|
-
return pathPrefix === "";
|
|
3515
|
-
}
|
|
3516
|
-
function registryNameFor(pathPrefix) {
|
|
3517
|
-
return isRootPathPrefix(pathPrefix) ? ROOT_SIBLING_NAME : pathPrefix;
|
|
3518
|
-
}
|
|
3519
|
-
function displayPath(pathPrefix) {
|
|
3520
|
-
return isRootPathPrefix(pathPrefix) ? "/" : `/${pathPrefix}`;
|
|
3521
|
-
}
|
|
3522
|
-
function basePathFor(pathPrefix) {
|
|
3523
|
-
return isRootPathPrefix(pathPrefix) ? "" : `/${pathPrefix}`;
|
|
3524
|
-
}
|
|
3525
|
-
function rootSiblingProjectName(coreProjectName) {
|
|
3526
|
-
return `${coreProjectName}-app`;
|
|
3527
|
-
}
|
|
3528
|
-
function bucketRegionalDomain(bucketName, region) {
|
|
3529
|
-
return region === "us-east-1" ? `${bucketName}.s3.amazonaws.com` : `${bucketName}.s3.${region}.amazonaws.com`;
|
|
3530
|
-
}
|
|
3531
|
-
function siteBucketName(projectName, environment, accountId) {
|
|
3532
|
-
return `${projectName}-${environment}-site-${accountId}`;
|
|
3533
|
-
}
|
|
3534
|
-
function upsertSiblingOrigin(existing, entry) {
|
|
3535
|
-
return [...existing.filter((s) => s.name !== entry.name), entry];
|
|
3536
|
-
}
|
|
3537
|
-
function serializeRegistry(origins) {
|
|
3538
|
-
return JSON.stringify({ sibling_origins: origins }, null, 2) + "\n";
|
|
3539
|
-
}
|
|
3540
|
-
|
|
3541
3872
|
// src/config/sibling-schema.ts
|
|
3542
3873
|
import { z as z4 } from "zod";
|
|
3543
3874
|
var SiblingConfigSchema = z4.object({
|
|
@@ -3774,12 +4105,11 @@ async function runSiblingCreateCommand(name, options) {
|
|
|
3774
4105
|
`
|
|
3775
4106
|
Next steps:
|
|
3776
4107
|
1. Merge the registration PR above \u2014 until it merges, baseurl.com${displayPath(pathPrefix)} won't route anywhere.
|
|
3777
|
-
2.
|
|
3778
|
-
|
|
4108
|
+
2. Run \`biffo deploy <env>\` on the CORE project (or re-run it if already deployed). That wires
|
|
4109
|
+
this sibling automatically: the SIBLING_GITHUB_TOKEN secret, the CORE_* identity variables,
|
|
4110
|
+
and \u2014 once the registration PR has merged and the core has redeployed \u2014
|
|
4111
|
+
PARENT_CLOUDFRONT_DISTRIBUTION_ARN. No manual variable/secret setup is needed (issue #337).
|
|
3779
4112
|
3. Push to \`dev\` (or run the Deploy workflow manually) to provision this sibling's own AWS resources.
|
|
3780
|
-
4. Once the registration PR has ALSO merged and the core project has redeployed, set
|
|
3781
|
-
PARENT_CLOUDFRONT_DISTRIBUTION_ARN on this repo and re-run its Deploy workflow \u2014 see this
|
|
3782
|
-
repo's README, "The two-phase CDN registration".
|
|
3783
4113
|
`
|
|
3784
4114
|
);
|
|
3785
4115
|
}
|
|
@@ -3801,7 +4131,6 @@ async function runSiblingCreate(github, aws, coreAws, git, config, session, opti
|
|
|
3801
4131
|
"Core project isn't deployed yet \u2014 deferring its identity (CORE_COGNITO_*, CORE_API_URL)"
|
|
3802
4132
|
);
|
|
3803
4133
|
session.outputs.coreIdentity = {};
|
|
3804
|
-
markSiblingStepComplete(session, "resolve_core_identity");
|
|
3805
4134
|
} else if (!session.completedSteps.includes("resolve_core_identity")) {
|
|
3806
4135
|
log.step(2, totalSteps, "Resolving core project's identity...");
|
|
3807
4136
|
session.outputs.coreIdentity = await resolveCoreIdentity(
|
|
@@ -4060,23 +4389,7 @@ async function configureSiblingGithub(github, config, coreConfig, session, coreI
|
|
|
4060
4389
|
for (const env of config.environments) {
|
|
4061
4390
|
const identity = coreIdentity[env];
|
|
4062
4391
|
if (!identity) continue;
|
|
4063
|
-
await github
|
|
4064
|
-
org,
|
|
4065
|
-
repo,
|
|
4066
|
-
env,
|
|
4067
|
-
"CORE_COGNITO_USER_POOL_ID",
|
|
4068
|
-
identity.cognitoUserPoolId
|
|
4069
|
-
);
|
|
4070
|
-
await github.setEnvVariable(org, repo, env, "CORE_COGNITO_CLIENT_ID", identity.cognitoClientId);
|
|
4071
|
-
await github.setEnvVariable(org, repo, env, "CORE_API_URL", identity.apiUrl);
|
|
4072
|
-
await github.setEnvVariable(org, repo, env, "CORE_PORTAL_URL", identity.portalUrl);
|
|
4073
|
-
await github.setEnvVariable(
|
|
4074
|
-
org,
|
|
4075
|
-
repo,
|
|
4076
|
-
env,
|
|
4077
|
-
"CORS_ORIGINS_JSON",
|
|
4078
|
-
JSON.stringify([identity.portalUrl])
|
|
4079
|
-
);
|
|
4392
|
+
await setSiblingCoreIdentity(github, org, repo, env, identity);
|
|
4080
4393
|
}
|
|
4081
4394
|
if (session.outputs.oidcRoleArn) {
|
|
4082
4395
|
await github.setRepoSecret(org, repo, "SIBLING_OIDC_ROLE_ARN", session.outputs.oidcRoleArn);
|
|
@@ -4320,12 +4633,7 @@ var initCommand = new Command12("init").description("Scaffold a new project from
|
|
|
4320
4633
|
Platform: https://github.com/${org}/${repo}`);
|
|
4321
4634
|
console.log(` Application: https://github.com/${org}/${appRepo} (serves /)`);
|
|
4322
4635
|
console.log(
|
|
4323
|
-
`
|
|
4324
|
-
Next:
|
|
4325
|
-
1. Clone the platform repo and run its first deploy \u2014 /admin and /login come up with it.
|
|
4326
|
-
2. Then deploy the application repo. Until it deploys, / has no content and 404s;
|
|
4327
|
-
that window is expected.
|
|
4328
|
-
`
|
|
4636
|
+
"\n Next:\n 1. Clone the platform repo and run `biffo deploy dev` \u2014 /admin and /login come up, and\n the same deploy wires the application repo (its CORE_* identity, the parent CloudFront\n ARN, and the SIBLING_GITHUB_TOKEN secret) automatically.\n 2. Then run the application repo's Deploy workflow \u2014 no manual variable/secret setup is\n needed. Until it deploys, / has no content and 404s; that window is expected.\n"
|
|
4329
4637
|
);
|
|
4330
4638
|
}
|
|
4331
4639
|
);
|
|
@@ -4539,7 +4847,12 @@ function appSiblingRegistryFiles(config) {
|
|
|
4539
4847
|
}));
|
|
4540
4848
|
}
|
|
4541
4849
|
var INSTANCE_CONFIG_FILE = "biffo.config.json";
|
|
4542
|
-
var
|
|
4850
|
+
var INSTANCE_FILE_BASE_BRANCH = "main";
|
|
4851
|
+
var INSTANCE_FILE_FOLLOWER_BRANCHES = ["dev", "staging"];
|
|
4852
|
+
var INSTANCE_FILE_BRANCHES = [
|
|
4853
|
+
INSTANCE_FILE_BASE_BRANCH,
|
|
4854
|
+
...INSTANCE_FILE_FOLLOWER_BRANCHES
|
|
4855
|
+
];
|
|
4543
4856
|
async function writeInstanceFiles(github, org, repo, config) {
|
|
4544
4857
|
const files = [
|
|
4545
4858
|
{ path: INSTANCE_CORE_FILE, content: serializeInstanceCoreVersion(getLatestCoreVersion()) },
|
|
@@ -4547,8 +4860,10 @@ async function writeInstanceFiles(github, org, repo, config) {
|
|
|
4547
4860
|
...config ? appSiblingRegistryFiles(config) : []
|
|
4548
4861
|
];
|
|
4549
4862
|
const message = config ? `chore: record core version, register the ${ROOT_SIBLING_NAME} sibling, and drop the template ${INSTANCE_CONFIG_FILE}` : `chore: record core version and drop the template ${INSTANCE_CONFIG_FILE}`;
|
|
4550
|
-
|
|
4551
|
-
|
|
4863
|
+
const committed = await github.commitFiles(org, repo, INSTANCE_FILE_BASE_BRANCH, files, message);
|
|
4864
|
+
const sharedSha = committed ?? await github.getBranchSha(org, repo, INSTANCE_FILE_BASE_BRANCH);
|
|
4865
|
+
for (const branch of INSTANCE_FILE_FOLLOWER_BRANCHES) {
|
|
4866
|
+
await github.fastForwardBranch(org, repo, branch, sharedSha);
|
|
4552
4867
|
}
|
|
4553
4868
|
}
|
|
4554
4869
|
function printPlan2(config) {
|
|
@@ -6160,131 +6475,6 @@ import { GetCallerIdentityCommand as GetCallerIdentityCommand3, STSClient as STS
|
|
|
6160
6475
|
import chalk20 from "chalk";
|
|
6161
6476
|
import { Command as Command22 } from "commander";
|
|
6162
6477
|
import inquirer8 from "inquirer";
|
|
6163
|
-
|
|
6164
|
-
// src/lib/sibling-teardown.ts
|
|
6165
|
-
var SiblingResolutionError = class extends Error {
|
|
6166
|
-
constructor(message) {
|
|
6167
|
-
super(message);
|
|
6168
|
-
this.name = "SiblingResolutionError";
|
|
6169
|
-
}
|
|
6170
|
-
};
|
|
6171
|
-
var REGISTRY_ENVIRONMENTS = ["dev", "staging", "prod"];
|
|
6172
|
-
function registryPath(environment) {
|
|
6173
|
-
return `infra/environments/${environment}/siblings.auto.tfvars.json`;
|
|
6174
|
-
}
|
|
6175
|
-
var REGISTRATION_BRANCH_PREFIX = "biffo/register-sibling-";
|
|
6176
|
-
function parseSiblingBucketDomain(domain) {
|
|
6177
|
-
const host = /^(?<bucket>[a-z0-9][a-z0-9.-]*)\.s3(?:\.[a-z0-9-]+)?\.amazonaws\.com$/.exec(domain);
|
|
6178
|
-
const bucket = host?.groups?.["bucket"];
|
|
6179
|
-
if (!bucket) return null;
|
|
6180
|
-
const parts = /^(?<project>.+)-(?<env>dev|staging|prod)-site-(?<account>\d{12})$/.exec(bucket);
|
|
6181
|
-
const project = parts?.groups?.["project"];
|
|
6182
|
-
const env = parts?.groups?.["env"];
|
|
6183
|
-
const account = parts?.groups?.["account"];
|
|
6184
|
-
if (!project || !env || !account) return null;
|
|
6185
|
-
return { projectName: project, environment: env, accountId: account };
|
|
6186
|
-
}
|
|
6187
|
-
function parseRegistry(contents) {
|
|
6188
|
-
if (contents === void 0 || contents.trim() === "") return [];
|
|
6189
|
-
let parsed;
|
|
6190
|
-
try {
|
|
6191
|
-
parsed = JSON.parse(contents);
|
|
6192
|
-
} catch {
|
|
6193
|
-
throw new SiblingResolutionError(
|
|
6194
|
-
"A siblings.auto.tfvars.json in the core repo is not valid JSON. Refusing to tear down while the sibling registry cannot be read \u2014 fix or remove the file and re-run."
|
|
6195
|
-
);
|
|
6196
|
-
}
|
|
6197
|
-
const origins = parsed.sibling_origins;
|
|
6198
|
-
if (origins === void 0) return [];
|
|
6199
|
-
if (!Array.isArray(origins)) {
|
|
6200
|
-
throw new SiblingResolutionError(
|
|
6201
|
-
"sibling_origins in the core repo is not a list. Refusing to tear down while the sibling registry cannot be read."
|
|
6202
|
-
);
|
|
6203
|
-
}
|
|
6204
|
-
return origins;
|
|
6205
|
-
}
|
|
6206
|
-
function collectSiblings(sources) {
|
|
6207
|
-
const byPrefix = /* @__PURE__ */ new Map();
|
|
6208
|
-
for (const source of sources) {
|
|
6209
|
-
for (const entry of source.entries) {
|
|
6210
|
-
if (!entry?.name || !entry.bucket_regional_domain) {
|
|
6211
|
-
throw new SiblingResolutionError(
|
|
6212
|
-
`A sibling entry in ${registryPath(source.environment)} is missing name or bucket_regional_domain. Refusing to tear down while a registered sibling cannot be identified \u2014 it would be left behind, still billing.`
|
|
6213
|
-
);
|
|
6214
|
-
}
|
|
6215
|
-
const parsed = parseSiblingBucketDomain(entry.bucket_regional_domain);
|
|
6216
|
-
if (!parsed) {
|
|
6217
|
-
throw new SiblingResolutionError(
|
|
6218
|
-
`Could not work out which project owns the sibling "${entry.name}" from its bucket "${entry.bucket_regional_domain}" (in ${registryPath(source.environment)}).
|
|
6219
|
-
Refusing to guess \u2014 tear that sibling down by hand, remove its entry, then re-run.`
|
|
6220
|
-
);
|
|
6221
|
-
}
|
|
6222
|
-
const existing = byPrefix.get(entry.name);
|
|
6223
|
-
if (existing) {
|
|
6224
|
-
if (existing.projectName !== parsed.projectName) {
|
|
6225
|
-
throw new SiblingResolutionError(
|
|
6226
|
-
`The sibling "${entry.name}" maps to two different projects across environments ("${existing.projectName}" and "${parsed.projectName}"). Refusing to guess which repo to delete \u2014 resolve the registry by hand and re-run.`
|
|
6227
|
-
);
|
|
6228
|
-
}
|
|
6229
|
-
if (!existing.environments.includes(parsed.environment)) {
|
|
6230
|
-
existing.environments.push(parsed.environment);
|
|
6231
|
-
}
|
|
6232
|
-
if (source.pendingRegistrationPr === void 0) {
|
|
6233
|
-
existing.registered = true;
|
|
6234
|
-
delete existing.pendingRegistrationPr;
|
|
6235
|
-
}
|
|
6236
|
-
continue;
|
|
6237
|
-
}
|
|
6238
|
-
byPrefix.set(entry.name, {
|
|
6239
|
-
pathPrefix: entry.name,
|
|
6240
|
-
projectName: parsed.projectName,
|
|
6241
|
-
environments: [parsed.environment],
|
|
6242
|
-
accountId: parsed.accountId,
|
|
6243
|
-
registered: source.pendingRegistrationPr === void 0,
|
|
6244
|
-
...source.pendingRegistrationPr !== void 0 ? { pendingRegistrationPr: source.pendingRegistrationPr } : {}
|
|
6245
|
-
});
|
|
6246
|
-
}
|
|
6247
|
-
}
|
|
6248
|
-
return [...byPrefix.values()].sort((a, b) => a.pathPrefix.localeCompare(b.pathPrefix));
|
|
6249
|
-
}
|
|
6250
|
-
var SIBLING_MARKER_FILE = "biffo.sibling.json";
|
|
6251
|
-
function markerMatches(marker, coreProjectName, sibling) {
|
|
6252
|
-
if (!marker) return false;
|
|
6253
|
-
if (marker.core_project !== coreProjectName) return false;
|
|
6254
|
-
if (marker.path_prefix === sibling.pathPrefix) return true;
|
|
6255
|
-
if (marker.path_prefix === "" && sibling.pathPrefix === ROOT_SIBLING_NAME) return true;
|
|
6256
|
-
return marker.name === sibling.projectName;
|
|
6257
|
-
}
|
|
6258
|
-
async function resolveSiblingRepos(github, coreOrg, coreProjectName, siblings) {
|
|
6259
|
-
const resolved = [];
|
|
6260
|
-
for (const sibling of siblings) {
|
|
6261
|
-
const repo = sibling.projectName;
|
|
6262
|
-
if (!await github.repoExists(coreOrg, repo)) {
|
|
6263
|
-
resolved.push({ ...sibling, org: coreOrg, repo, repoState: "gone" });
|
|
6264
|
-
continue;
|
|
6265
|
-
}
|
|
6266
|
-
const raw = await github.getFileContent(coreOrg, repo, SIBLING_MARKER_FILE);
|
|
6267
|
-
let marker = null;
|
|
6268
|
-
if (raw !== void 0) {
|
|
6269
|
-
try {
|
|
6270
|
-
marker = JSON.parse(raw);
|
|
6271
|
-
} catch {
|
|
6272
|
-
marker = null;
|
|
6273
|
-
}
|
|
6274
|
-
}
|
|
6275
|
-
if (!markerMatches(marker, coreProjectName, sibling)) {
|
|
6276
|
-
throw new SiblingResolutionError(
|
|
6277
|
-
`Refusing to tear down: ${coreOrg}/${repo} is registered as the sibling "${sibling.pathPrefix}" of "${coreProjectName}", but that repo does not carry a matching ${SIBLING_MARKER_FILE}.
|
|
6278
|
-
It may be an unrelated repo that happens to share the name. Deleting a repo cannot be undone, so nothing has been deleted.
|
|
6279
|
-
Delete ${coreOrg}/${repo} by hand if it really is the sibling, remove its entry from the core repo's siblings.auto.tfvars.json, then re-run biffo teardown.`
|
|
6280
|
-
);
|
|
6281
|
-
}
|
|
6282
|
-
resolved.push({ ...sibling, org: coreOrg, repo, repoState: "present" });
|
|
6283
|
-
}
|
|
6284
|
-
return resolved;
|
|
6285
|
-
}
|
|
6286
|
-
|
|
6287
|
-
// src/commands/teardown.ts
|
|
6288
6478
|
var teardownCommand = new Command22("teardown").description(
|
|
6289
6479
|
"Destroy all infrastructure then remove the repo, IAM role, and state bucket \u2014 single command"
|
|
6290
6480
|
).option("--project <name>", "Project name to tear down (reads session if omitted)").option("--skip-destroy", "Skip terraform destroy (only use if infrastructure is already gone)").option(
|
|
@@ -6594,27 +6784,6 @@ Destroying sibling ${sibling.org}/${sibling.repo} (${env})...`);
|
|
|
6594
6784
|
}
|
|
6595
6785
|
var SIBLING_DESTROY_WORKFLOW = "destroy-infra.yml";
|
|
6596
6786
|
var SIBLING_DESTROY_WORKFLOW_PATH = `.github/workflows/${SIBLING_DESTROY_WORKFLOW}`;
|
|
6597
|
-
async function discoverSiblings(github, coreOrg, coreRepo, coreProjectName) {
|
|
6598
|
-
const sources = [];
|
|
6599
|
-
for (const env of REGISTRY_ENVIRONMENTS) {
|
|
6600
|
-
const contents = await github.getFileContent(coreOrg, coreRepo, registryPath(env));
|
|
6601
|
-
sources.push({ environment: env, entries: parseRegistry(contents) });
|
|
6602
|
-
}
|
|
6603
|
-
const openPrs = await github.listOpenPullRequests(coreOrg, coreRepo);
|
|
6604
|
-
for (const pr of openPrs) {
|
|
6605
|
-
if (!pr.headRef.startsWith(REGISTRATION_BRANCH_PREFIX)) continue;
|
|
6606
|
-
for (const env of REGISTRY_ENVIRONMENTS) {
|
|
6607
|
-
const contents = await github.getFileContent(coreOrg, coreRepo, registryPath(env), pr.headRef);
|
|
6608
|
-
sources.push({
|
|
6609
|
-
environment: env,
|
|
6610
|
-
entries: parseRegistry(contents),
|
|
6611
|
-
pendingRegistrationPr: pr.number
|
|
6612
|
-
});
|
|
6613
|
-
}
|
|
6614
|
-
}
|
|
6615
|
-
const discovered = collectSiblings(sources);
|
|
6616
|
-
return resolveSiblingRepos(github, coreOrg, coreProjectName, discovered);
|
|
6617
|
-
}
|
|
6618
6787
|
async function assertSiblingsAreDestroyable(github, siblings) {
|
|
6619
6788
|
const missing = [];
|
|
6620
6789
|
for (const sibling of siblings) {
|