create-aws-project 1.5.0 → 1.6.0

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.
@@ -10,7 +10,6 @@ import prompts from 'prompts';
10
10
  import pc from 'picocolors';
11
11
  import { execSync } from 'node:child_process';
12
12
  import { requireProjectContext } from '../utils/project-context.js';
13
- import { createCrossAccountIAMClient, createDeploymentUserWithCredentials, createAccessKey, } from '../aws/iam.js';
14
13
  import { createGitHubClient, setEnvironmentCredentials, parseGitHubUrl, } from '../github/secrets.js';
15
14
  const VALID_ENVIRONMENTS = ['dev', 'stage', 'prod'];
16
15
  /**
@@ -151,49 +150,55 @@ async function promptForRepoInfo() {
151
150
  return parseGitHubUrl(response.repo);
152
151
  }
153
152
  /**
154
- * Handles AWS and GitHub errors with actionable messages
153
+ * Determines batch environments from CLI args
154
+ * @param args Command arguments
155
+ * @param config Project config with deployment credentials
156
+ * @returns Array of environments to configure
157
+ */
158
+ function determineBatchEnvironments(args, config) {
159
+ if (args.includes('--all')) {
160
+ const envs = VALID_ENVIRONMENTS.filter(env => config.deploymentCredentials?.[env]);
161
+ if (envs.length === 0) {
162
+ console.error(pc.red('Error:') + ' No environments have credentials configured.');
163
+ console.error('Run setup-aws-envs first to create deployment credentials:');
164
+ console.error(` ${pc.cyan('npx create-aws-project setup-aws-envs')}`);
165
+ process.exit(1);
166
+ }
167
+ return [...envs];
168
+ }
169
+ // Multiple positional arguments (filter out flags)
170
+ const envArgs = args.filter(arg => !arg.startsWith('--'));
171
+ for (const envArg of envArgs) {
172
+ if (envArg !== envArg.toLowerCase()) {
173
+ console.error(pc.red('Error:') + ` Environment must be lowercase: ${pc.bold(envArg)}`);
174
+ console.error(`Did you mean: ${pc.cyan(envArg.toLowerCase())}?`);
175
+ process.exit(1);
176
+ }
177
+ if (!isValidEnvironment(envArg)) {
178
+ console.error(pc.red('Error:') + ` Invalid environment: ${pc.bold(envArg)}`);
179
+ console.error('Valid environments: ' + VALID_ENVIRONMENTS.join(', '));
180
+ process.exit(1);
181
+ }
182
+ if (!config.deploymentCredentials?.[envArg]) {
183
+ console.error(pc.red('Error:') + ` No credentials for ${pc.bold(envArg)}.`);
184
+ console.error('Run setup-aws-envs first to create deployment credentials:');
185
+ console.error(` ${pc.cyan('npx create-aws-project setup-aws-envs')}`);
186
+ process.exit(1);
187
+ }
188
+ }
189
+ return envArgs;
190
+ }
191
+ /**
192
+ * Handles GitHub errors with actionable messages
155
193
  * @param error The error to handle
156
- * @param env The environment being configured
157
194
  * @returns never - always exits
158
195
  */
159
- function handleError(error, env) {
196
+ function handleError(error) {
160
197
  console.error('');
161
198
  if (!(error instanceof Error)) {
162
199
  console.error(pc.red('Error:') + ' Unknown error occurred');
163
200
  process.exit(1);
164
201
  }
165
- // AWS AssumeRole errors
166
- if (error.name === 'AccessDenied' || error.message.includes('AssumeRole')) {
167
- console.error(pc.red('Error: Cannot assume role in target account'));
168
- console.error('');
169
- console.error('This command requires cross-account access via:');
170
- console.error(` arn:aws:iam::<${env}-account-id>:role/OrganizationAccountAccessRole`);
171
- console.error('');
172
- console.error('Ensure:');
173
- console.error(` 1. The ${env} account was created via setup-aws-envs`);
174
- console.error(' 2. Your credentials are from the management account');
175
- console.error(' 3. OrganizationAccountAccessRole exists in target account');
176
- process.exit(1);
177
- }
178
- // IAM user already exists
179
- if (error.message.includes('already exists')) {
180
- console.error(pc.red('Error:') + ` ${error.message}`);
181
- console.error('');
182
- console.error('To retry, delete the IAM user first:');
183
- console.error(' 1. Go to AWS Console > IAM > Users');
184
- console.error(` 2. Find and delete the existing deployment user`);
185
- console.error(' 3. Run this command again');
186
- process.exit(1);
187
- }
188
- // Access key limit exceeded
189
- if (error.name === 'LimitExceeded' || error.message.includes('LimitExceeded')) {
190
- console.error(pc.red('Error: Access key limit exceeded'));
191
- console.error('');
192
- console.error('IAM users can only have 2 active access keys.');
193
- console.error('Delete an existing key before creating a new one:');
194
- console.error(' AWS Console > IAM > Users > Security credentials > Access keys');
195
- process.exit(1);
196
- }
197
202
  // GitHub authentication errors
198
203
  if (error.message.includes('authentication failed') || error.name === 'HttpError') {
199
204
  console.error(pc.red('Error: GitHub authentication failed'));
@@ -222,7 +227,84 @@ export async function runInitializeGitHub(args) {
222
227
  // 1. Validate we're in a project directory
223
228
  const context = await requireProjectContext();
224
229
  const { config } = context;
225
- const { projectName, awsRegion } = config;
230
+ // 2. Detect batch mode
231
+ const nonFlagArgs = args.filter(a => !a.startsWith('--'));
232
+ const isBatchMode = args.includes('--all') || nonFlagArgs.length > 1;
233
+ if (isBatchMode) {
234
+ // BATCH MODE: Configure multiple environments
235
+ const environments = determineBatchEnvironments(args, config);
236
+ // Get repository info once (try git remote, fallback to prompt)
237
+ let repoInfo = getGitRemoteOrigin();
238
+ if (!repoInfo) {
239
+ console.log('');
240
+ console.log(pc.yellow('Note:') + ' Could not detect GitHub repository from git remote.');
241
+ repoInfo = await promptForRepoInfo();
242
+ }
243
+ else {
244
+ console.log('');
245
+ console.log(`Detected repository: ${pc.cyan(`${repoInfo.owner}/${repoInfo.repo}`)}`);
246
+ }
247
+ // Prompt for GitHub PAT once
248
+ const pat = await promptForGitHubPAT();
249
+ // Create GitHub client once
250
+ const githubClient = createGitHubClient(pat);
251
+ // Track results for each environment
252
+ const results = [];
253
+ // Process each environment
254
+ for (const env of environments) {
255
+ const spinner = ora(`Configuring ${env} environment...`).start();
256
+ try {
257
+ const credentials = config.deploymentCredentials?.[env];
258
+ if (!credentials) {
259
+ throw new Error(`No deployment credentials found for ${env}`);
260
+ }
261
+ const githubEnvName = GITHUB_ENV_NAMES[env];
262
+ spinner.text = `Configuring GitHub Environment "${githubEnvName}"...`;
263
+ await setEnvironmentCredentials(githubClient, repoInfo.owner, repoInfo.repo, githubEnvName, credentials.accessKeyId, credentials.secretAccessKey);
264
+ spinner.succeed(`Configured ${githubEnvName} environment`);
265
+ results.push({ env, success: true });
266
+ }
267
+ catch (error) {
268
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
269
+ spinner.fail(`Failed ${env}`);
270
+ results.push({ env, success: false, error: errorMessage });
271
+ }
272
+ }
273
+ // Show batch summary
274
+ console.log('');
275
+ console.log(pc.bold('Batch Configuration Summary'));
276
+ console.log('');
277
+ for (const result of results) {
278
+ if (result.success) {
279
+ console.log(` ${pc.green('✓')} Configured ${GITHUB_ENV_NAMES[result.env]} environment`);
280
+ }
281
+ else {
282
+ console.log(` ${pc.red('✗')} Failed ${result.env}: ${result.error}`);
283
+ }
284
+ }
285
+ const successCount = results.filter(r => r.success).length;
286
+ console.log('');
287
+ console.log(`Successfully configured ${successCount} of ${results.length} environments`);
288
+ if (successCount < results.length) {
289
+ console.log('');
290
+ console.log('You can retry failed environments individually:');
291
+ for (const result of results) {
292
+ if (!result.success) {
293
+ console.log(` ${pc.cyan(`npx create-aws-project initialize-github ${result.env}`)}`);
294
+ }
295
+ }
296
+ }
297
+ console.log('');
298
+ console.log('View secrets at:');
299
+ console.log(` ${pc.cyan(`https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings/environments`)}`);
300
+ console.log('');
301
+ // Exit with error code if any failed
302
+ if (successCount < results.length) {
303
+ process.exit(1);
304
+ }
305
+ return;
306
+ }
307
+ // SINGLE MODE (existing behavior, unchanged)
226
308
  // 2. Determine environment
227
309
  let env;
228
310
  if (args[0]) {
@@ -245,26 +327,17 @@ export async function runInitializeGitHub(args) {
245
327
  }
246
328
  else {
247
329
  // Interactive environment selection
248
- const configuredEnvs = VALID_ENVIRONMENTS.filter((e) => config.accounts?.[e]);
330
+ const configuredEnvs = VALID_ENVIRONMENTS.filter((e) => config.deploymentCredentials?.[e]);
249
331
  if (configuredEnvs.length === 0) {
250
332
  console.error(pc.red('Error:') + ' No environments configured.');
251
333
  console.error('');
252
- console.error('Run setup-aws-envs first to create environment accounts:');
334
+ console.error('Run setup-aws-envs first to create deployment credentials:');
253
335
  console.error(` ${pc.cyan('npx create-aws-project setup-aws-envs')}`);
254
336
  process.exit(1);
255
337
  }
256
338
  env = await promptForEnvironment(configuredEnvs);
257
339
  }
258
- // 3. Validate account ID exists for environment
259
- const accountId = config.accounts?.[env];
260
- if (!accountId) {
261
- console.error(pc.red('Error:') + ` No account ID for ${env}.`);
262
- console.error('');
263
- console.error('Run setup-aws-envs first to create environment accounts:');
264
- console.error(` ${pc.cyan('npx create-aws-project setup-aws-envs')}`);
265
- process.exit(1);
266
- }
267
- // 4. Get repository info (try git remote, fallback to prompt)
340
+ // 3. Get repository info (try git remote, fallback to prompt)
268
341
  let repoInfo = getGitRemoteOrigin();
269
342
  if (!repoInfo) {
270
343
  console.log('');
@@ -275,33 +348,27 @@ export async function runInitializeGitHub(args) {
275
348
  console.log('');
276
349
  console.log(`Detected repository: ${pc.cyan(`${repoInfo.owner}/${repoInfo.repo}`)}`);
277
350
  }
278
- // 5. Prompt for GitHub PAT (always interactive per CONTEXT.md)
351
+ // 4. Prompt for GitHub PAT (always interactive per CONTEXT.md)
279
352
  const pat = await promptForGitHubPAT();
280
- // 6. Execute AWS and GitHub setup
353
+ // 5. Execute GitHub setup
281
354
  const spinner = ora(`Initializing ${env} environment...`).start();
282
355
  try {
283
- // Step 1: Create cross-account IAM client
284
- spinner.text = `Assuming role in ${env} account (${accountId})...`;
285
- const iamClient = createCrossAccountIAMClient(awsRegion, accountId);
286
- // Step 2: Get deployment user credentials
287
- // If setup-aws-envs already created the user, just create an access key.
288
- // Otherwise fall back to full user creation (backward compat with older projects).
289
- const existingUserName = config.deploymentUsers?.[env];
290
- let userName;
291
- let credentials;
292
- if (existingUserName) {
293
- spinner.text = `Using existing deployment user: ${existingUserName}`;
294
- userName = existingUserName;
295
- credentials = await createAccessKey(iamClient, userName);
296
- spinner.succeed(`Created access key for ${userName}`);
297
- }
298
- else {
299
- spinner.text = `Creating IAM deployment user...`;
300
- const fullCredentials = await createDeploymentUserWithCredentials(iamClient, projectName, env, accountId);
301
- userName = fullCredentials.userName;
302
- credentials = fullCredentials;
356
+ // Step 1: Validate deployment credentials exist in config
357
+ const credentials = config.deploymentCredentials?.[env];
358
+ if (!credentials) {
359
+ spinner.fail(`No deployment credentials found for ${env}`);
360
+ console.error('');
361
+ // Check if user exists but credentials don't (migration case)
362
+ if (config.deploymentUsers?.[env]) {
363
+ console.error(pc.yellow('Note:') + ' Deployment user exists but credentials were not found.');
364
+ console.error('Your project may have been set up with an older version.');
365
+ console.error('');
366
+ }
367
+ console.error('Run setup-aws-envs first to create deployment credentials:');
368
+ console.error(` ${pc.cyan('npx create-aws-project setup-aws-envs')}`);
369
+ process.exit(1);
303
370
  }
304
- // Step 3: Configure GitHub
371
+ // Step 2: Configure GitHub environment
305
372
  const githubEnvName = GITHUB_ENV_NAMES[env];
306
373
  spinner.text = `Configuring GitHub Environment "${githubEnvName}"...`;
307
374
  const githubClient = createGitHubClient(pat);
@@ -311,20 +378,16 @@ export async function runInitializeGitHub(args) {
311
378
  console.log('');
312
379
  console.log(pc.green(`${env} environment setup complete!`));
313
380
  console.log('');
314
- console.log('Resources created:');
315
- console.log(` Deployment User: ${pc.cyan(userName)}`);
381
+ console.log('Credentials pushed to GitHub:');
382
+ console.log(` Deployment User: ${pc.cyan(credentials.userName)}`);
383
+ console.log(` Access Key ID: ${pc.cyan(credentials.accessKeyId)}`);
316
384
  console.log(` GitHub Environment: ${pc.cyan(githubEnvName)}`);
317
- // Add note if using existing user from setup-aws-envs
318
- if (existingUserName) {
319
- console.log('');
320
- console.log(pc.dim('Note: Deployment user was created by setup-aws-envs. Access key created for GitHub.'));
321
- }
322
385
  console.log('');
323
386
  console.log('View secrets at:');
324
387
  console.log(` ${pc.cyan(`https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings/environments`)}`);
325
388
  console.log('');
326
389
  // Show next steps (other environments if any remain)
327
- const remainingEnvs = VALID_ENVIRONMENTS.filter((e) => e !== env && config.accounts?.[e]);
390
+ const remainingEnvs = VALID_ENVIRONMENTS.filter((e) => e !== env && config.deploymentCredentials?.[e]);
328
391
  if (remainingEnvs.length > 0) {
329
392
  console.log(pc.bold('Next steps:'));
330
393
  console.log('');
@@ -337,13 +400,29 @@ export async function runInitializeGitHub(args) {
337
400
  else {
338
401
  console.log(pc.bold('All environments configured!'));
339
402
  console.log('');
340
- console.log('Deploy your application by pushing to the main branch:');
341
- console.log(` ${pc.cyan('git push origin main')}`);
403
+ console.log(pc.bold('Get started:'));
342
404
  console.log('');
405
+ console.log(` ${pc.cyan('npm install')}`);
406
+ console.log('');
407
+ if (config.platforms?.includes('web')) {
408
+ console.log(` ${pc.gray('# Start web app')}`);
409
+ console.log(` ${pc.cyan('npm run web')}`);
410
+ console.log('');
411
+ }
412
+ if (config.platforms?.includes('mobile')) {
413
+ console.log(` ${pc.gray('# Start mobile app')}`);
414
+ console.log(` ${pc.cyan('npm run mobile')}`);
415
+ console.log('');
416
+ }
417
+ if (config.platforms?.includes('api')) {
418
+ console.log(` ${pc.gray('# Deploy API')}`);
419
+ console.log(` ${pc.cyan('npm run cdk:deploy')}`);
420
+ console.log('');
421
+ }
343
422
  }
344
423
  }
345
424
  catch (error) {
346
425
  spinner.fail(`Failed to configure ${env} environment`);
347
- handleError(error, env);
426
+ handleError(error);
348
427
  }
349
428
  }