@devramps/cli 0.1.11 → 0.1.13

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.
Files changed (2) hide show
  1. package/dist/index.js +107 -55
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -933,6 +933,11 @@ async function authenticateViaBrowser(options = {}) {
933
933
  const state = generateState();
934
934
  verbose("Generated PKCE code_challenge and state");
935
935
  const { server, port, callbackPromise } = await startCallbackServer(state);
936
+ const sigintHandler = () => {
937
+ server.close();
938
+ process.exit(130);
939
+ };
940
+ process.on("SIGINT", sigintHandler);
936
941
  try {
937
942
  const redirectUri = `http://localhost:${port}`;
938
943
  const authParams = new URLSearchParams({
@@ -1005,6 +1010,7 @@ async function authenticateViaBrowser(options = {}) {
1005
1010
  cicdRegion: awsConfig.defaultRegion
1006
1011
  };
1007
1012
  } finally {
1013
+ process.removeListener("SIGINT", sigintHandler);
1008
1014
  await closeServer(server);
1009
1015
  }
1010
1016
  }
@@ -1296,8 +1302,11 @@ async function parseAdditionalPolicies(pipelineDir) {
1296
1302
  if (!policy || typeof policy !== "object") {
1297
1303
  throw new Error(`Policy at index ${i} is not an object`);
1298
1304
  }
1299
- if (!("Statement" in policy) || !Array.isArray(policy.Statement)) {
1300
- throw new Error(`Policy at index ${i} is missing Statement array`);
1305
+ if (!("Statement" in policy) || !policy.Statement || typeof policy.Statement !== "object") {
1306
+ throw new Error(`Policy at index ${i} is missing Statement`);
1307
+ }
1308
+ if (!Array.isArray(policy.Statement)) {
1309
+ policy.Statement = [policy.Statement];
1301
1310
  }
1302
1311
  validatedPolicies.push(policy);
1303
1312
  }
@@ -1501,7 +1510,8 @@ function createBaseTemplate(description) {
1501
1510
  function sanitizeResourceId(name) {
1502
1511
  return name.replace(/[^a-zA-Z0-9]/g, "").substring(0, 64);
1503
1512
  }
1504
- function addOidcProviderResource(template, conditional = true) {
1513
+ function addOidcProviderResource(template, conditional = true, oidcProviderUrl) {
1514
+ const providerUrl = oidcProviderUrl || OIDC_PROVIDER_URL;
1505
1515
  if (conditional) {
1506
1516
  template.Parameters.OIDCProviderExists = {
1507
1517
  Type: "String",
@@ -1517,27 +1527,28 @@ function addOidcProviderResource(template, conditional = true) {
1517
1527
  Type: "AWS::IAM::OIDCProvider",
1518
1528
  ...conditional ? { Condition: "CreateOIDCProvider" } : {},
1519
1529
  Properties: {
1520
- Url: `https://${OIDC_PROVIDER_URL}`,
1521
- ClientIdList: [OIDC_PROVIDER_URL],
1530
+ Url: `https://${providerUrl}`,
1531
+ ClientIdList: [providerUrl],
1522
1532
  ThumbprintList: [getOidcThumbprint()],
1523
1533
  Tags: STANDARD_TAGS
1524
1534
  }
1525
1535
  };
1526
1536
  }
1527
- function buildOidcTrustPolicy(accountId, subject) {
1537
+ function buildOidcTrustPolicy(accountId, subject, oidcProviderUrl) {
1538
+ const providerUrl = oidcProviderUrl || OIDC_PROVIDER_URL;
1528
1539
  return {
1529
1540
  Version: "2012-10-17",
1530
1541
  Statement: [
1531
1542
  {
1532
1543
  Effect: "Allow",
1533
1544
  Principal: {
1534
- Federated: `arn:aws:iam::${accountId}:oidc-provider/${OIDC_PROVIDER_URL}`
1545
+ Federated: `arn:aws:iam::${accountId}:oidc-provider/${providerUrl}`
1535
1546
  },
1536
1547
  Action: "sts:AssumeRoleWithWebIdentity",
1537
1548
  Condition: {
1538
1549
  StringEquals: {
1539
- [`${OIDC_PROVIDER_URL}:sub`]: subject,
1540
- [`${OIDC_PROVIDER_URL}:aud`]: OIDC_PROVIDER_URL
1550
+ [`${providerUrl}:sub`]: subject,
1551
+ [`${providerUrl}:aud`]: providerUrl
1541
1552
  }
1542
1553
  }
1543
1554
  }
@@ -1899,7 +1910,7 @@ function createTerraformStateBucketPolicy(bucketName, cicdAccountId, allowedAcco
1899
1910
 
1900
1911
  // src/templates/org-stack.ts
1901
1912
  function generateOrgStackTemplate(options) {
1902
- const { orgSlug, cicdAccountId, targetAccountIds } = options;
1913
+ const { orgSlug, cicdAccountId, targetAccountIds, oidcProviderUrl } = options;
1903
1914
  const template = createBaseTemplate(`DevRamps Org Stack for ${orgSlug}`);
1904
1915
  const kmsKeyPolicy = buildKmsKeyPolicy(cicdAccountId, targetAccountIds);
1905
1916
  template.Resources.DevRampsKMSKey = createKmsKeyResource(
@@ -1929,7 +1940,7 @@ function generateOrgStackTemplate(options) {
1929
1940
  PolicyDocument: bucketPolicy
1930
1941
  }
1931
1942
  };
1932
- const trustPolicy = buildOidcTrustPolicy(cicdAccountId, `org:${orgSlug}`);
1943
+ const trustPolicy = buildOidcTrustPolicy(cicdAccountId, `org:${orgSlug}/cicd`, oidcProviderUrl);
1933
1944
  const orgRolePolicies = buildOrgRolePolicies(orgSlug);
1934
1945
  template.Resources.DevRampsCICDDeploymentRole = createIamRoleResource(
1935
1946
  getOrgRoleName(),
@@ -2137,11 +2148,11 @@ function generatePipelineStackTemplate(options) {
2137
2148
  }
2138
2149
 
2139
2150
  // src/templates/account-stack.ts
2140
- function generateAccountStackTemplate() {
2151
+ function generateAccountStackTemplate(options) {
2141
2152
  const template = createBaseTemplate(
2142
2153
  "DevRamps Account Bootstrap Stack - Creates OIDC provider for the account"
2143
2154
  );
2144
- addOidcProviderResource(template, false);
2155
+ addOidcProviderResource(template, false, options?.oidcProviderUrl);
2145
2156
  template.Outputs = {
2146
2157
  OIDCProviderArn: {
2147
2158
  Description: "ARN of the OIDC provider",
@@ -2333,13 +2344,14 @@ function generateStageStackTemplate(options) {
2333
2344
  steps,
2334
2345
  additionalPolicies,
2335
2346
  dockerArtifacts,
2336
- bundleArtifacts
2347
+ bundleArtifacts,
2348
+ oidcProviderUrl
2337
2349
  } = options;
2338
2350
  const template = createBaseTemplate(
2339
2351
  `DevRamps Stage Stack for ${pipelineSlug}/${stageName}`
2340
2352
  );
2341
2353
  const roleName = generateStageRoleName(pipelineSlug, stageName);
2342
- const trustPolicy = buildStageTrustPolicy(accountId, orgSlug, pipelineSlug, stageName);
2354
+ const trustPolicy = buildStageTrustPolicy(accountId, orgSlug, pipelineSlug, oidcProviderUrl);
2343
2355
  const policies = buildStagePolicies(steps, additionalPolicies);
2344
2356
  template.Resources.StageDeploymentRole = createIamRoleResource(
2345
2357
  roleName,
@@ -2383,7 +2395,8 @@ function generateStageStackTemplate(options) {
2383
2395
  );
2384
2396
  s3Outputs[artifact.name] = { resourceId };
2385
2397
  }
2386
- const oidcProviderArn = `arn:aws:iam::${accountId}:oidc-provider/${OIDC_PROVIDER_URL}`;
2398
+ const providerUrl = oidcProviderUrl || OIDC_PROVIDER_URL;
2399
+ const oidcProviderArn = `arn:aws:iam::${accountId}:oidc-provider/${providerUrl}`;
2387
2400
  template.Outputs = {
2388
2401
  StageRoleArn: {
2389
2402
  Description: "ARN of the stage deployment role",
@@ -2423,9 +2436,9 @@ function generateStageStackTemplate(options) {
2423
2436
  }
2424
2437
  return template;
2425
2438
  }
2426
- function buildStageTrustPolicy(accountId, orgSlug, pipelineSlug, stageName) {
2427
- const subject = `org:${orgSlug}/pipeline:${pipelineSlug}/stage:${stageName}`;
2428
- return buildOidcTrustPolicy(accountId, subject);
2439
+ function buildStageTrustPolicy(accountId, orgSlug, pipelineSlug, oidcProviderUrl) {
2440
+ const subject = `org:${orgSlug}/pipeline:${pipelineSlug}`;
2441
+ return buildOidcTrustPolicy(accountId, subject, oidcProviderUrl);
2429
2442
  }
2430
2443
  function buildStagePolicies(steps, additionalPolicies) {
2431
2444
  const policies = [];
@@ -2531,6 +2544,15 @@ async function confirmDeployment(plan) {
2531
2544
  }
2532
2545
 
2533
2546
  // src/commands/bootstrap.ts
2547
+ function getOidcProviderUrlFromEndpoint(endpointOverride) {
2548
+ if (!endpointOverride) return void 0;
2549
+ try {
2550
+ const url = new URL(endpointOverride);
2551
+ return url.hostname;
2552
+ } catch {
2553
+ return void 0;
2554
+ }
2555
+ }
2534
2556
  async function bootstrapCommand(options) {
2535
2557
  try {
2536
2558
  if (options.verbose) {
@@ -2580,7 +2602,8 @@ async function bootstrapCommand(options) {
2580
2602
  info("Deployment cancelled by user.");
2581
2603
  return;
2582
2604
  }
2583
- await executeDeployment(plan, pipelines, pipelineArtifacts, authData, identity.accountId, options);
2605
+ const oidcProviderUrl = getOidcProviderUrlFromEndpoint(options.endpointOverride);
2606
+ await executeDeployment(plan, pipelines, pipelineArtifacts, authData, identity.accountId, options, oidcProviderUrl);
2584
2607
  } catch (error2) {
2585
2608
  if (error2 instanceof DevRampsError) {
2586
2609
  error(error2.message);
@@ -2771,7 +2794,9 @@ async function showDryRunPlan(plan) {
2771
2794
  }
2772
2795
  const totalStacks = 1 + plan.pipelineStacks.length + plan.accountStacks.length + plan.stageStacks.length;
2773
2796
  newline();
2774
- info(`Total stacks to deploy (in parallel): ${totalStacks}`);
2797
+ info(`Total stacks to deploy: ${totalStacks}`);
2798
+ info(` Phase 1: ${plan.accountStacks.length} Account stack(s) (deployed first)`);
2799
+ info(` Phase 2: ${1 + plan.pipelineStacks.length + plan.stageStacks.length} Org/Pipeline/Stage stack(s) (deployed in parallel after Phase 1)`);
2775
2800
  }
2776
2801
  async function confirmDeploymentPlan(plan) {
2777
2802
  const totalStacks = 1 + plan.pipelineStacks.length + plan.accountStacks.length + plan.stageStacks.length;
@@ -2791,30 +2816,68 @@ async function confirmDeploymentPlan(plan) {
2791
2816
  ]
2792
2817
  });
2793
2818
  }
2794
- async function executeDeployment(plan, pipelines, pipelineArtifacts, authData, currentAccountId, options) {
2819
+ async function executeDeployment(plan, pipelines, pipelineArtifacts, authData, currentAccountId, options, oidcProviderUrl) {
2795
2820
  const results = { success: 0, failed: 0 };
2796
2821
  const totalStacks = 1 + plan.pipelineStacks.length + plan.accountStacks.length + plan.stageStacks.length;
2822
+ const remainingStacks = 1 + plan.pipelineStacks.length + plan.stageStacks.length;
2797
2823
  newline();
2798
- header("Deploying All Stacks");
2799
- info(`Deploying ${totalStacks} stack(s) in parallel...`);
2824
+ header("Phase 1: Deploying Account Bootstrap Stacks");
2825
+ info(`Deploying ${plan.accountStacks.length} account stack(s) in parallel...`);
2800
2826
  newline();
2801
- const progress = getMultiStackProgress();
2802
- progress.addStack(plan.orgStack.stackName, "org", plan.orgStack.accountId, plan.orgStack.region, 5);
2827
+ const accountProgress = getMultiStackProgress();
2828
+ for (const stack of plan.accountStacks) {
2829
+ accountProgress.addStack(stack.stackName, "account", stack.accountId, stack.region, 1);
2830
+ }
2831
+ accountProgress.start();
2832
+ const accountResults = await Promise.all(
2833
+ plan.accountStacks.map(async (stack) => {
2834
+ try {
2835
+ await deployAccountStack(stack, currentAccountId, options, oidcProviderUrl);
2836
+ return { stack: `${stack.stackName} (${stack.accountId})`, success: true };
2837
+ } catch (error2) {
2838
+ return {
2839
+ stack: `${stack.stackName} (${stack.accountId})`,
2840
+ success: false,
2841
+ error: error2 instanceof Error ? error2.message : String(error2)
2842
+ };
2843
+ }
2844
+ })
2845
+ );
2846
+ clearMultiStackProgress();
2847
+ newline();
2848
+ for (const result of accountResults) {
2849
+ if (result.success) {
2850
+ success(`${result.stack} deployed`);
2851
+ results.success++;
2852
+ } else {
2853
+ error(`${result.stack} failed: ${result.error}`);
2854
+ results.failed++;
2855
+ }
2856
+ }
2857
+ if (results.failed > 0) {
2858
+ newline();
2859
+ header("Deployment Summary");
2860
+ error(`${results.failed} account stack(s) failed. Skipping remaining ${remainingStacks} stack(s).`);
2861
+ process.exit(1);
2862
+ }
2863
+ newline();
2864
+ header("Phase 2: Deploying Org, Pipeline, and Stage Stacks");
2865
+ info(`Deploying ${remainingStacks} stack(s) in parallel...`);
2866
+ newline();
2867
+ const mainProgress = getMultiStackProgress();
2868
+ mainProgress.addStack(plan.orgStack.stackName, "org", plan.orgStack.accountId, plan.orgStack.region, 5);
2803
2869
  for (const stack of plan.pipelineStacks) {
2804
2870
  const resourceCount = stack.dockerArtifacts.length + stack.bundleArtifacts.length;
2805
- progress.addStack(stack.stackName, "pipeline", stack.accountId, stack.region, Math.max(resourceCount, 1));
2806
- }
2807
- for (const stack of plan.accountStacks) {
2808
- progress.addStack(stack.stackName, "account", stack.accountId, stack.region, 1);
2871
+ mainProgress.addStack(stack.stackName, "pipeline", stack.accountId, stack.region, Math.max(resourceCount, 1));
2809
2872
  }
2810
2873
  for (const stack of plan.stageStacks) {
2811
2874
  const resourceCount = stack.dockerArtifacts.length + stack.bundleArtifacts.length + 2;
2812
- progress.addStack(stack.stackName, "stage", stack.accountId, stack.region, resourceCount);
2875
+ mainProgress.addStack(stack.stackName, "stage", stack.accountId, stack.region, resourceCount);
2813
2876
  }
2814
- progress.start();
2877
+ mainProgress.start();
2815
2878
  const orgPromise = (async () => {
2816
2879
  try {
2817
- await deployOrgStack(plan, pipelines, authData, currentAccountId, options);
2880
+ await deployOrgStack(plan, pipelines, authData, currentAccountId, options, oidcProviderUrl);
2818
2881
  return { stack: plan.orgStack.stackName, success: true };
2819
2882
  } catch (error2) {
2820
2883
  return {
@@ -2836,21 +2899,9 @@ async function executeDeployment(plan, pipelines, pipelineArtifacts, authData, c
2836
2899
  };
2837
2900
  }
2838
2901
  });
2839
- const accountPromises = plan.accountStacks.map(async (stack) => {
2840
- try {
2841
- await deployAccountStack(stack, currentAccountId, options);
2842
- return { stack: stack.stackName, success: true };
2843
- } catch (error2) {
2844
- return {
2845
- stack: stack.stackName,
2846
- success: false,
2847
- error: error2 instanceof Error ? error2.message : String(error2)
2848
- };
2849
- }
2850
- });
2851
2902
  const stagePromises = plan.stageStacks.map(async (stack) => {
2852
2903
  try {
2853
- await deployStageStack(stack, authData, currentAccountId, options);
2904
+ await deployStageStack(stack, authData, currentAccountId, options, oidcProviderUrl);
2854
2905
  return { stack: stack.stackName, success: true };
2855
2906
  } catch (error2) {
2856
2907
  return {
@@ -2860,15 +2911,14 @@ async function executeDeployment(plan, pipelines, pipelineArtifacts, authData, c
2860
2911
  };
2861
2912
  }
2862
2913
  });
2863
- const allResults = await Promise.all([
2914
+ const mainResults = await Promise.all([
2864
2915
  orgPromise,
2865
2916
  ...pipelinePromises,
2866
- ...accountPromises,
2867
2917
  ...stagePromises
2868
2918
  ]);
2869
2919
  clearMultiStackProgress();
2870
2920
  newline();
2871
- for (const result of allResults) {
2921
+ for (const result of mainResults) {
2872
2922
  if (result.success) {
2873
2923
  success(`${result.stack} deployed`);
2874
2924
  results.success++;
@@ -2887,7 +2937,7 @@ async function executeDeployment(plan, pipelines, pipelineArtifacts, authData, c
2887
2937
  process.exit(1);
2888
2938
  }
2889
2939
  }
2890
- async function deployOrgStack(plan, pipelines, authData, currentAccountId, options) {
2940
+ async function deployOrgStack(plan, pipelines, authData, currentAccountId, options, oidcProviderUrl) {
2891
2941
  const { orgSlug, cicdAccountId, cicdRegion } = authData;
2892
2942
  const credentials = cicdAccountId !== currentAccountId ? (await assumeRoleForAccount({
2893
2943
  targetAccountId: cicdAccountId,
@@ -2917,7 +2967,8 @@ async function deployOrgStack(plan, pipelines, authData, currentAccountId, optio
2917
2967
  const template = generateOrgStackTemplate({
2918
2968
  orgSlug,
2919
2969
  cicdAccountId,
2920
- targetAccountIds
2970
+ targetAccountIds,
2971
+ oidcProviderUrl
2921
2972
  });
2922
2973
  const deployOptions = {
2923
2974
  stackName: plan.orgStack.stackName,
@@ -2952,13 +3003,13 @@ async function deployPipelineStack(stack, authData, currentAccountId, options) {
2952
3003
  await previewStackChanges(deployOptions);
2953
3004
  await deployStack(deployOptions);
2954
3005
  }
2955
- async function deployAccountStack(stack, currentAccountId, options) {
3006
+ async function deployAccountStack(stack, currentAccountId, options, oidcProviderUrl) {
2956
3007
  const credentials = stack.accountId !== currentAccountId ? (await assumeRoleForAccount({
2957
3008
  targetAccountId: stack.accountId,
2958
3009
  currentAccountId,
2959
3010
  targetRoleName: options.targetAccountRoleName
2960
3011
  }))?.credentials : void 0;
2961
- const template = generateAccountStackTemplate();
3012
+ const template = generateAccountStackTemplate({ oidcProviderUrl });
2962
3013
  const deployOptions = {
2963
3014
  stackName: stack.stackName,
2964
3015
  template,
@@ -2969,7 +3020,7 @@ async function deployAccountStack(stack, currentAccountId, options) {
2969
3020
  await previewStackChanges(deployOptions);
2970
3021
  await deployStack(deployOptions);
2971
3022
  }
2972
- async function deployStageStack(stack, authData, currentAccountId, options) {
3023
+ async function deployStageStack(stack, authData, currentAccountId, options, oidcProviderUrl) {
2973
3024
  const credentials = stack.accountId !== currentAccountId ? (await assumeRoleForAccount({
2974
3025
  targetAccountId: stack.accountId,
2975
3026
  currentAccountId,
@@ -2983,7 +3034,8 @@ async function deployStageStack(stack, authData, currentAccountId, options) {
2983
3034
  steps: stack.steps,
2984
3035
  additionalPolicies: stack.additionalPolicies,
2985
3036
  dockerArtifacts: stack.dockerArtifacts,
2986
- bundleArtifacts: stack.bundleArtifacts
3037
+ bundleArtifacts: stack.bundleArtifacts,
3038
+ oidcProviderUrl
2987
3039
  });
2988
3040
  const deployOptions = {
2989
3041
  stackName: stack.stackName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devramps/cli",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "DevRamps CLI - Bootstrap AWS infrastructure for CI/CD pipelines",
5
5
  "main": "dist/index.js",
6
6
  "bin": {