@devramps/cli 0.1.30 → 0.1.32
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/index.js +104 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { program } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/bootstrap.ts
|
|
7
|
+
import { createHash as createHash2 } from "crypto";
|
|
7
8
|
import ora from "ora";
|
|
8
9
|
|
|
9
10
|
// src/aws/credentials.ts
|
|
@@ -1008,8 +1009,11 @@ async function authenticateViaBrowser(options = {}) {
|
|
|
1008
1009
|
verbose(`CI/CD Account: ${cicdAccountId}, Region: ${awsConfig.defaultRegion}`);
|
|
1009
1010
|
return {
|
|
1010
1011
|
orgSlug: orgResponse.slug,
|
|
1012
|
+
organizationId: tokenResponse.organization_id,
|
|
1011
1013
|
cicdAccountId,
|
|
1012
|
-
cicdRegion: awsConfig.defaultRegion
|
|
1014
|
+
cicdRegion: awsConfig.defaultRegion,
|
|
1015
|
+
accessToken: tokenResponse.access_token,
|
|
1016
|
+
apiBaseUrl: baseUrl
|
|
1013
1017
|
};
|
|
1014
1018
|
} finally {
|
|
1015
1019
|
process.removeListener("SIGINT", sigintHandler);
|
|
@@ -1378,15 +1382,30 @@ async function parsePipeline(basePath, slug) {
|
|
|
1378
1382
|
throw new PipelineParseError(slug, `Stage "${stage.name}" is missing region`);
|
|
1379
1383
|
}
|
|
1380
1384
|
}
|
|
1385
|
+
if (definition.pipeline.ephemeral_environments) {
|
|
1386
|
+
for (const [name, env] of Object.entries(definition.pipeline.ephemeral_environments)) {
|
|
1387
|
+
if (!env.account_id) {
|
|
1388
|
+
throw new PipelineParseError(slug, `Ephemeral environment "${name}" is missing account_id`);
|
|
1389
|
+
}
|
|
1390
|
+
if (!env.region) {
|
|
1391
|
+
throw new PipelineParseError(slug, `Ephemeral environment "${name}" is missing region`);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1381
1395
|
const targetAccountIds = extractTargetAccountIds(definition);
|
|
1382
1396
|
const steps = extractSteps(definition);
|
|
1383
1397
|
const additionalPolicies = await parseAdditionalPoliciesForPipeline(basePath, slug);
|
|
1384
|
-
|
|
1398
|
+
const ephemeralStages = ephemeralEnvironmentsAsStages(definition);
|
|
1399
|
+
const allStages = [...definition.pipeline.stages, ...ephemeralStages];
|
|
1400
|
+
if (ephemeralStages.length > 0) {
|
|
1401
|
+
verbose(`Pipeline ${slug}: ${ephemeralStages.length} ephemeral environment(s) will be bootstrapped as stages`);
|
|
1402
|
+
}
|
|
1403
|
+
verbose(`Pipeline ${slug}: ${targetAccountIds.length} accounts, ${allStages.length} stages, ${steps.length} steps`);
|
|
1385
1404
|
return {
|
|
1386
1405
|
slug,
|
|
1387
1406
|
definition,
|
|
1388
1407
|
targetAccountIds,
|
|
1389
|
-
stages:
|
|
1408
|
+
stages: allStages,
|
|
1390
1409
|
steps,
|
|
1391
1410
|
additionalPolicies
|
|
1392
1411
|
};
|
|
@@ -1398,8 +1417,26 @@ function extractTargetAccountIds(definition) {
|
|
|
1398
1417
|
accountIds.add(stage.account_id);
|
|
1399
1418
|
}
|
|
1400
1419
|
}
|
|
1420
|
+
if (definition.pipeline.ephemeral_environments) {
|
|
1421
|
+
for (const env of Object.values(definition.pipeline.ephemeral_environments)) {
|
|
1422
|
+
if (env.account_id) {
|
|
1423
|
+
accountIds.add(env.account_id);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1401
1427
|
return Array.from(accountIds);
|
|
1402
1428
|
}
|
|
1429
|
+
function ephemeralEnvironmentsAsStages(definition) {
|
|
1430
|
+
const envs = definition.pipeline.ephemeral_environments;
|
|
1431
|
+
if (!envs) return [];
|
|
1432
|
+
return Object.entries(envs).map(([name, env]) => ({
|
|
1433
|
+
name: `ephemeral-${name}`,
|
|
1434
|
+
account_id: env.account_id,
|
|
1435
|
+
region: env.region,
|
|
1436
|
+
skip: env.skip,
|
|
1437
|
+
vars: env.vars
|
|
1438
|
+
}));
|
|
1439
|
+
}
|
|
1403
1440
|
function extractSteps(definition) {
|
|
1404
1441
|
return definition.pipeline.steps || [];
|
|
1405
1442
|
}
|
|
@@ -3365,6 +3402,7 @@ async function executeDeployment(plan, pipelines, pipelineArtifacts, authData, c
|
|
|
3365
3402
|
header("Deployment Summary");
|
|
3366
3403
|
if (results.failed === 0) {
|
|
3367
3404
|
success(`All ${results.success} stack(s) deployed successfully!`);
|
|
3405
|
+
await markPipelinesBootstrapped(pipelines, authData);
|
|
3368
3406
|
process.exit(0);
|
|
3369
3407
|
} else {
|
|
3370
3408
|
warn(`${results.success} stack(s) succeeded, ${results.failed} stack(s) failed.`);
|
|
@@ -3510,6 +3548,69 @@ async function deployImportStack(stack, currentAccountId, options, oidcProviderU
|
|
|
3510
3548
|
await previewStackChanges(deployOptions);
|
|
3511
3549
|
await deployStack(deployOptions);
|
|
3512
3550
|
}
|
|
3551
|
+
async function markPipelinesBootstrapped(pipelines, authData) {
|
|
3552
|
+
newline();
|
|
3553
|
+
const spinner = ora("Registering bootstrap status...").start();
|
|
3554
|
+
let pipelineMap;
|
|
3555
|
+
try {
|
|
3556
|
+
const listUrl = `${authData.apiBaseUrl}/api/v1/organizations/${authData.organizationId}/pipelines?limit=100`;
|
|
3557
|
+
const listRes = await fetch(listUrl, {
|
|
3558
|
+
headers: {
|
|
3559
|
+
Authorization: `Bearer ${authData.accessToken}`,
|
|
3560
|
+
Accept: "application/json"
|
|
3561
|
+
}
|
|
3562
|
+
});
|
|
3563
|
+
if (!listRes.ok) {
|
|
3564
|
+
spinner.warn(`Could not register bootstrap status: failed to list pipelines (${listRes.status})`);
|
|
3565
|
+
return;
|
|
3566
|
+
}
|
|
3567
|
+
const listData = await listRes.json();
|
|
3568
|
+
pipelineMap = new Map(listData.pipelines.map((p) => [p.slug, p.id]));
|
|
3569
|
+
} catch (error2) {
|
|
3570
|
+
spinner.warn(`Could not register bootstrap status: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
3571
|
+
return;
|
|
3572
|
+
}
|
|
3573
|
+
let succeeded = 0;
|
|
3574
|
+
let failed = 0;
|
|
3575
|
+
for (const pipeline of pipelines) {
|
|
3576
|
+
const pipelineId = pipelineMap.get(pipeline.slug);
|
|
3577
|
+
if (!pipelineId) {
|
|
3578
|
+
verbose(`Pipeline ${pipeline.slug} not found in backend \u2014 skipping mark-bootstrapped`);
|
|
3579
|
+
failed++;
|
|
3580
|
+
continue;
|
|
3581
|
+
}
|
|
3582
|
+
const definitionHash = createHash2("sha256").update(JSON.stringify(pipeline.definition)).digest("hex");
|
|
3583
|
+
try {
|
|
3584
|
+
const url = `${authData.apiBaseUrl}/api/v1/organizations/${authData.organizationId}/pipelines/${pipelineId}/mark-bootstrapped`;
|
|
3585
|
+
const res = await fetch(url, {
|
|
3586
|
+
method: "POST",
|
|
3587
|
+
headers: {
|
|
3588
|
+
Authorization: `Bearer ${authData.accessToken}`,
|
|
3589
|
+
"Content-Type": "application/json",
|
|
3590
|
+
Accept: "application/json"
|
|
3591
|
+
},
|
|
3592
|
+
body: JSON.stringify({ definitionHash })
|
|
3593
|
+
});
|
|
3594
|
+
if (res.ok) {
|
|
3595
|
+
verbose(`Marked ${pipeline.slug} as bootstrapped (hash: ${definitionHash.slice(0, 12)}...)`);
|
|
3596
|
+
succeeded++;
|
|
3597
|
+
} else {
|
|
3598
|
+
verbose(`Failed to mark ${pipeline.slug} as bootstrapped: ${res.status}`);
|
|
3599
|
+
failed++;
|
|
3600
|
+
}
|
|
3601
|
+
} catch (error2) {
|
|
3602
|
+
verbose(`Error marking ${pipeline.slug} as bootstrapped: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
3603
|
+
failed++;
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
if (failed === 0) {
|
|
3607
|
+
spinner.succeed(`Bootstrap status registered for ${succeeded} pipeline(s)`);
|
|
3608
|
+
} else if (succeeded > 0) {
|
|
3609
|
+
spinner.warn(`Bootstrap status registered for ${succeeded} pipeline(s), ${failed} failed (non-fatal)`);
|
|
3610
|
+
} else {
|
|
3611
|
+
spinner.warn("Could not register bootstrap status (non-fatal)");
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3513
3614
|
|
|
3514
3615
|
// src/index.ts
|
|
3515
3616
|
program.name("devramps").description("DevRamps CLI - Bootstrap AWS infrastructure for CI/CD pipelines").version("0.1.0");
|