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.
- package/README.md +3 -25
- package/dist/__tests__/aws/cdk-bootstrap.spec.d.ts +1 -0
- package/dist/__tests__/aws/cdk-bootstrap.spec.js +266 -0
- package/dist/__tests__/aws/root-credentials.spec.d.ts +1 -0
- package/dist/__tests__/aws/root-credentials.spec.js +230 -0
- package/dist/aws/cdk-bootstrap.d.ts +77 -0
- package/dist/aws/cdk-bootstrap.js +139 -0
- package/dist/aws/iam.js +17 -0
- package/dist/aws/organizations.d.ts +7 -1
- package/dist/aws/organizations.js +19 -1
- package/dist/aws/root-credentials.d.ts +68 -0
- package/dist/aws/root-credentials.js +157 -0
- package/dist/cli.js +30 -31
- package/dist/commands/initialize-github.js +162 -83
- package/dist/commands/setup-aws-envs.js +282 -50
- package/dist/git/setup.d.ts +28 -0
- package/dist/git/setup.js +169 -0
- package/dist/utils/project-context.d.ts +14 -0
- package/dist/wizard.d.ts +4 -1
- package/dist/wizard.js +6 -2
- package/package.json +1 -1
- package/templates/apps/web/src/App.tsx +4 -2
- package/templates/apps/web/src/__tests__/App.spec.tsx +27 -9
- package/templates/apps/web/vite.config.ts +3 -0
- package/templates/root/README.md +1 -1
|
@@ -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
|
-
*
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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
|
-
//
|
|
351
|
+
// 4. Prompt for GitHub PAT (always interactive per CONTEXT.md)
|
|
279
352
|
const pat = await promptForGitHubPAT();
|
|
280
|
-
//
|
|
353
|
+
// 5. Execute GitHub setup
|
|
281
354
|
const spinner = ora(`Initializing ${env} environment...`).start();
|
|
282
355
|
try {
|
|
283
|
-
// Step 1:
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
|
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('
|
|
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.
|
|
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('
|
|
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
|
|
426
|
+
handleError(error);
|
|
348
427
|
}
|
|
349
428
|
}
|