create-aws-project 1.2.1 → 1.3.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 CHANGED
@@ -7,7 +7,7 @@ Create a new AWS project from scratch including CloudFront, API Gateway, Lambdas
7
7
  ## Quick Start
8
8
 
9
9
  ```bash
10
- npx create-aws-starter-kit my-project
10
+ npx create-aws-project my-project
11
11
  ```
12
12
 
13
13
  **Requirements:** Node.js 22.16.0+ (npm included)
@@ -33,35 +33,24 @@ The generated project is a full-stack Nx monorepo with:
33
33
  ## CLI Options
34
34
 
35
35
  ```
36
- create-aws-starter-kit [options] [project-name]
36
+ create-aws-starter-kit [command] [options]
37
+
38
+ Commands:
39
+ (default) Create a new project (interactive wizard)
40
+ setup-aws-envs Set up AWS Organizations and environment accounts
41
+ initialize-github Configure GitHub Environment for deployment
37
42
 
38
43
  Options:
39
- --help, -h Show help message
40
- --version, -v Show version number
44
+ --help, -h Show help message
45
+ --version, -v Show version number
41
46
 
42
47
  Examples:
43
48
  npx create-aws-starter-kit my-app
49
+ npx create-aws-starter-kit setup-aws-envs
50
+ npx create-aws-starter-kit initialize-github dev
44
51
  npx create-aws-starter-kit --help
45
- npx create-aws-starter-kit --version
46
- ```
47
-
48
- ### Setup GitHub Command
49
-
50
- After generating a project with AWS Organizations enabled, configure GitHub Actions deployment:
51
-
52
- ```bash
53
- npx create-aws-starter-kit setup-github
54
52
  ```
55
53
 
56
- This command:
57
- - Creates IAM deployment users per environment (dev, stage, prod)
58
- - Configures GitHub Environments with AWS credentials
59
- - Sets up least-privilege CDK deployment permissions
60
-
61
- **Requirements:**
62
- - GitHub Personal Access Token with `repo` and `admin:org` scopes
63
- - AWS credentials with IAM permissions
64
-
65
54
  ## Wizard Prompts
66
55
 
67
56
  The interactive wizard will ask you about:
@@ -72,11 +61,7 @@ The interactive wizard will ask you about:
72
61
  - None (add later)
73
62
  - AWS Cognito
74
63
  - Auth0
75
- - Optional: Social login, MFA
76
- 4. **AWS Organizations** - Multi-account setup (optional):
77
- - Creates separate AWS accounts for each environment
78
- - Environments: dev, stage, prod (plus optional qa, sandbox)
79
- - Requires root email per account
64
+ 4. **Auth features** - Social login, MFA (conditional on auth provider)
80
65
  5. **Features** - Optional extras:
81
66
  - GitHub Actions workflows for CI/CD
82
67
  - VS Code workspace configuration
@@ -113,6 +98,123 @@ npm run cdk:deploy
113
98
 
114
99
  See the generated project's README for detailed documentation.
115
100
 
101
+ ## Post-Install Setup
102
+
103
+ After creating your project, you'll set up AWS environments and GitHub deployment. This is a one-time setup.
104
+
105
+ ### Prerequisites
106
+
107
+ Before you begin:
108
+ - AWS CLI configured with credentials from your AWS management account
109
+ - GitHub repository created for your project
110
+ - GitHub Personal Access Token with "repo" scope ([create one here](https://github.com/settings/tokens/new))
111
+
112
+ ### Step 1: Set Up AWS Environments
113
+
114
+ From your project directory, run:
115
+
116
+ ```bash
117
+ npx create-aws-project setup-aws-envs
118
+ ```
119
+
120
+ This command:
121
+ - Creates an AWS Organization (if you don't have one)
122
+ - Creates three environment accounts: dev, stage, prod
123
+ - Prompts for a unique root email for each account (tip: use aliases like you+dev@email.com)
124
+
125
+ **What's happening:** AWS Organizations lets you isolate each environment in its own AWS account. This is a security best practice - your production data is completely separate from development.
126
+
127
+ Expected output:
128
+ ```
129
+ ✔ Created AWS Organization: o-xxxxxxxxxx
130
+ ✔ Created dev account: 123456789012
131
+ ✔ Created stage account: 234567890123
132
+ ✔ Created prod account: 345678901234
133
+
134
+ AWS environment setup complete!
135
+ ```
136
+
137
+ Account IDs are saved to `.aws-starter-config.json` for the next step.
138
+
139
+ ### Step 2: Configure GitHub Environments
140
+
141
+ For each environment, run:
142
+
143
+ ```bash
144
+ npx create-aws-project initialize-github dev
145
+ ```
146
+
147
+ This command:
148
+ - Creates an IAM deployment user in the target AWS account
149
+ - Configures GitHub Environment secrets with AWS credentials
150
+ - Sets up least-privilege permissions for CDK deployments
151
+
152
+ **What's happening:** Each GitHub Environment (Development, Staging, Production) gets its own AWS credentials. When GitHub Actions runs, it uses the right credentials for the target environment.
153
+
154
+ Repeat for each environment:
155
+ ```bash
156
+ npx create-aws-project initialize-github stage
157
+ npx create-aws-project initialize-github prod
158
+ ```
159
+
160
+ You'll be prompted for your GitHub PAT each time (it's not stored).
161
+
162
+ ### You're Done!
163
+
164
+ Push to main to trigger your first deployment:
165
+ ```bash
166
+ git push origin main
167
+ ```
168
+
169
+ GitHub Actions will deploy to your dev environment automatically.
170
+
171
+ ## Troubleshooting
172
+
173
+ ### setup-aws-envs errors
174
+
175
+ **"Insufficient AWS permissions"**
176
+
177
+ Your AWS credentials need Organizations permissions. Ensure you're using credentials from the management account (not a member account).
178
+
179
+ Required permissions:
180
+ - organizations:DescribeOrganization
181
+ - organizations:CreateOrganization
182
+ - organizations:CreateAccount
183
+ - organizations:DescribeCreateAccountStatus
184
+
185
+ **"AWS Organizations limit reached"**
186
+
187
+ AWS limits how many accounts you can create. Contact AWS Support to request a limit increase.
188
+
189
+ **"AWS Organization is still initializing"**
190
+
191
+ New organizations take up to an hour to fully initialize. Wait and try again.
192
+
193
+ ### initialize-github errors
194
+
195
+ **"Cannot assume role in target account"**
196
+
197
+ The command needs to access the target AWS account via `OrganizationAccountAccessRole`. This role is created automatically when you create accounts via `setup-aws-envs`. Ensure:
198
+ 1. You ran `setup-aws-envs` first
199
+ 2. Your credentials are from the management account
200
+ 3. The account ID in `.aws-starter-config.json` is correct
201
+
202
+ **"IAM user already exists"**
203
+
204
+ The deployment user already exists in the target account. To retry:
205
+ 1. Go to AWS Console > IAM > Users
206
+ 2. Delete the existing `<project>-<env>-deploy` user
207
+ 3. Run the command again
208
+
209
+ **"GitHub authentication failed"**
210
+
211
+ Your Personal Access Token may be invalid or missing permissions. Ensure:
212
+ 1. Token has "repo" scope enabled
213
+ 2. Token belongs to the repository owner (or has collaborator access)
214
+ 3. Token is not expired
215
+
216
+ Create a new token at: https://github.com/settings/tokens/new
217
+
116
218
  ## License
117
219
 
118
220
  ISC
@@ -12,6 +12,9 @@ describe('replaceTokens', () => {
12
12
  AUTH_AUTH0: 'false',
13
13
  AUTH_SOCIAL_LOGIN: 'false',
14
14
  AUTH_MFA: 'false',
15
+ WEB: 'false',
16
+ MOBILE: 'false',
17
+ API: 'false',
15
18
  };
16
19
  describe('single token replacement', () => {
17
20
  it('should replace PROJECT_NAME token', () => {
@@ -107,6 +110,9 @@ describe('processConditionalBlocks', () => {
107
110
  AUTH_AUTH0: 'false',
108
111
  AUTH_SOCIAL_LOGIN: 'false',
109
112
  AUTH_MFA: 'false',
113
+ WEB: 'false',
114
+ MOBILE: 'false',
115
+ API: 'false',
110
116
  };
111
117
  describe('comment-wrapped conditionals', () => {
112
118
  it('should keep content when token is true (removes markers)', () => {
@@ -228,6 +234,9 @@ describe('replaceTokens integration with conditionals', () => {
228
234
  AUTH_AUTH0: 'false',
229
235
  AUTH_SOCIAL_LOGIN: 'false',
230
236
  AUTH_MFA: 'false',
237
+ WEB: 'false',
238
+ MOBILE: 'false',
239
+ API: 'false',
231
240
  };
232
241
  it('should process conditionals before token replacement', () => {
233
242
  const tokens = { ...baseTokens, AUTH_COGNITO: 'true' };
@@ -173,10 +173,9 @@ describe('runWizard', () => {
173
173
  await runWizard();
174
174
  expect(mockPrompts).toHaveBeenCalledTimes(1);
175
175
  const [promptsArg] = mockPrompts.mock.calls[0];
176
- // Verify 15 prompts are passed (projectName, platforms, authProvider, authFeatures, features, awsRegion,
177
- // enableOrg, orgName, orgEnvironments, devEmail, stageEmail, prodEmail, qaEmail, sandboxEmail, brandColor)
176
+ // Verify 7 prompts are passed (projectName, platforms, authProvider, authFeatures, features, awsRegion, brandColor)
178
177
  expect(Array.isArray(promptsArg)).toBe(true);
179
- expect(promptsArg.length).toBe(15);
178
+ expect(promptsArg.length).toBe(7);
180
179
  });
181
180
  });
182
181
  describe('auth configuration', () => {
package/dist/aws/iam.d.ts CHANGED
@@ -11,11 +11,18 @@ import { IAMClient } from '@aws-sdk/client-iam';
11
11
  * @returns Configured IAMClient instance
12
12
  */
13
13
  export declare function createIAMClient(region?: string): IAMClient;
14
+ /**
15
+ * Creates an IAMClient configured to access a target account via cross-account role assumption
16
+ * @param region - AWS region
17
+ * @param targetAccountId - Target AWS account ID to access
18
+ * @returns IAMClient configured with cross-account credentials
19
+ */
20
+ export declare function createCrossAccountIAMClient(region: string, targetAccountId: string): IAMClient;
14
21
  /**
15
22
  * Creates an IAM deployment user with path /deployment/
16
23
  * @param client - IAMClient instance
17
24
  * @param userName - User name (format: {project}-{environment}-deploy)
18
- * @throws Error if user creation fails (unless user already exists)
25
+ * @throws Error if user already exists or creation fails
19
26
  */
20
27
  export declare function createDeploymentUser(client: IAMClient, userName: string): Promise<void>;
21
28
  /**
package/dist/aws/iam.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { IAMClient, CreateUserCommand, GetUserCommand, CreatePolicyCommand, GetPolicyCommand, AttachUserPolicyCommand, CreateAccessKeyCommand, NoSuchEntityException, } from '@aws-sdk/client-iam';
2
+ import { fromTemporaryCredentials } from '@aws-sdk/credential-providers';
2
3
  import pc from 'picocolors';
3
4
  /**
4
5
  * AWS IAM service module
@@ -14,6 +15,25 @@ import pc from 'picocolors';
14
15
  export function createIAMClient(region = 'us-east-1') {
15
16
  return new IAMClient({ region });
16
17
  }
18
+ /**
19
+ * Creates an IAMClient configured to access a target account via cross-account role assumption
20
+ * @param region - AWS region
21
+ * @param targetAccountId - Target AWS account ID to access
22
+ * @returns IAMClient configured with cross-account credentials
23
+ */
24
+ export function createCrossAccountIAMClient(region, targetAccountId) {
25
+ const roleArn = `arn:aws:iam::${targetAccountId}:role/OrganizationAccountAccessRole`;
26
+ return new IAMClient({
27
+ region,
28
+ credentials: fromTemporaryCredentials({
29
+ params: {
30
+ RoleArn: roleArn,
31
+ RoleSessionName: `create-aws-project-${Date.now()}`,
32
+ DurationSeconds: 900,
33
+ },
34
+ }),
35
+ });
36
+ }
17
37
  /**
18
38
  * Checks if an IAM user exists
19
39
  * @param client - IAMClient instance
@@ -56,13 +76,12 @@ async function policyExists(client, policyArn) {
56
76
  * Creates an IAM deployment user with path /deployment/
57
77
  * @param client - IAMClient instance
58
78
  * @param userName - User name (format: {project}-{environment}-deploy)
59
- * @throws Error if user creation fails (unless user already exists)
79
+ * @throws Error if user already exists or creation fails
60
80
  */
61
81
  export async function createDeploymentUser(client, userName) {
62
82
  // Check if user already exists
63
83
  if (await userExists(client, userName)) {
64
- console.log(pc.yellow(` User ${userName} already exists, reusing`));
65
- return;
84
+ throw new Error(`IAM user "${userName}" already exists. Delete manually before retrying.`);
66
85
  }
67
86
  const command = new CreateUserCommand({
68
87
  UserName: userName,
package/dist/cli.js CHANGED
@@ -1,11 +1,30 @@
1
- import { readFileSync, existsSync, mkdirSync } from 'node:fs';
1
+ import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import { dirname, join, resolve } from 'node:path';
4
4
  import pc from 'picocolors';
5
5
  import { runWizard } from './wizard.js';
6
6
  import { generateProject } from './generator/index.js';
7
- import { createOrganizationsClient, checkExistingOrganization, createOrganization, createEnvironmentAccounts, } from './aws/organizations.js';
8
- import { runSetupGitHub } from './commands/setup-github.js';
7
+ import { showDeprecationNotice } from './commands/setup-github.js';
8
+ import { runSetupAwsEnvs } from './commands/setup-aws-envs.js';
9
+ import { runInitializeGitHub } from './commands/initialize-github.js';
10
+ /**
11
+ * Write project configuration file for downstream commands
12
+ */
13
+ function writeConfigFile(outputDir, config) {
14
+ const configContent = {
15
+ configVersion: '1.0',
16
+ projectName: config.projectName,
17
+ platforms: config.platforms,
18
+ authProvider: config.auth.provider,
19
+ features: config.features,
20
+ awsRegion: config.awsRegion,
21
+ theme: config.brandColor,
22
+ createdAt: new Date().toISOString(),
23
+ accounts: {},
24
+ };
25
+ const configPath = join(outputDir, '.aws-starter-config.json');
26
+ writeFileSync(configPath, JSON.stringify(configContent, null, 2), 'utf-8');
27
+ }
9
28
  /**
10
29
  * Get the version from package.json
11
30
  */
@@ -26,20 +45,24 @@ create-aws-starter-kit [command] [options]
26
45
  Scaffold a new AWS Starter Kit project with React, Lambda, and CDK infrastructure.
27
46
 
28
47
  Commands:
29
- (default) Create a new project (interactive wizard)
30
- setup-github Configure GitHub Actions deployment credentials
48
+ (default) Create a new project (interactive wizard)
49
+ setup-aws-envs Set up AWS Organizations and environment accounts
50
+ initialize-github Configure GitHub Environment for deployment
51
+ setup-github ${pc.dim('[DEPRECATED]')} Use initialize-github instead
31
52
 
32
53
  Options:
33
- --help, -h Show this help message
34
- --version, -v Show version number
54
+ --help, -h Show this help message
55
+ --version, -v Show version number
35
56
 
36
57
  Usage:
37
- create-aws-starter-kit [project-name] Create a new project
38
- create-aws-starter-kit setup-github Configure GitHub deployment
58
+ create-aws-starter-kit Run interactive wizard
59
+ create-aws-starter-kit setup-aws-envs Create AWS accounts
60
+ create-aws-starter-kit initialize-github dev Configure dev environment
39
61
 
40
62
  Examples:
41
63
  create-aws-starter-kit my-app
42
- create-aws-starter-kit setup-github
64
+ create-aws-starter-kit setup-aws-envs
65
+ create-aws-starter-kit initialize-github dev
43
66
  create-aws-starter-kit --help
44
67
  `.trim());
45
68
  }
@@ -81,31 +104,16 @@ function printNextSteps(projectName, platforms) {
81
104
  console.log(` ${pc.cyan('npm run cdk:deploy')}`);
82
105
  console.log('');
83
106
  }
107
+ console.log(` ${pc.gray('# Configure AWS environments')}`);
108
+ console.log(` ${pc.cyan('npx create-aws-project setup-aws-envs')}`);
109
+ console.log('');
84
110
  console.log(pc.gray('Happy coding!'));
85
111
  }
86
112
  /**
87
- * Parse command line arguments and run the CLI
113
+ * Run the create project wizard flow
114
+ * This is the default command when no subcommand is specified
88
115
  */
89
- export async function run() {
90
- const args = process.argv.slice(2);
91
- // Check for --help or -h
92
- if (args.includes('--help') || args.includes('-h')) {
93
- printHelp();
94
- process.exit(0);
95
- }
96
- // Check for --version or -v
97
- if (args.includes('--version') || args.includes('-v')) {
98
- console.log(getVersion());
99
- process.exit(0);
100
- }
101
- // Check for subcommands (first non-flag argument)
102
- const command = args.find((arg) => !arg.startsWith('-'));
103
- // Handle setup-github command
104
- if (command === 'setup-github') {
105
- await runSetupGitHub();
106
- process.exit(0);
107
- }
108
- // Default: run interactive wizard
116
+ async function runCreate(_args) {
109
117
  printWelcome();
110
118
  console.log(''); // blank line after banner
111
119
  const config = await runWizard();
@@ -113,77 +121,6 @@ export async function run() {
113
121
  console.log('\nProject creation cancelled.');
114
122
  process.exit(1);
115
123
  }
116
- // Set up AWS Organizations if enabled
117
- if (config.org?.enabled) {
118
- console.log('');
119
- console.log(pc.cyan('Setting up AWS Organizations...'));
120
- try {
121
- const orgClient = createOrganizationsClient(config.awsRegion);
122
- // Check if already in an organization
123
- const existingOrgId = await checkExistingOrganization(orgClient);
124
- if (existingOrgId) {
125
- console.log(pc.yellow(`Already in organization: ${existingOrgId}`));
126
- console.log(pc.dim('Proceeding with existing organization...'));
127
- }
128
- else {
129
- // Create new organization
130
- console.log(pc.dim('Creating new AWS Organization...'));
131
- const orgId = await createOrganization(orgClient);
132
- console.log(pc.green('✔') + ` Organization created: ${orgId}`);
133
- }
134
- // Create environment accounts
135
- const accountResults = await createEnvironmentAccounts(orgClient, config.org.organizationName, config.org.accounts);
136
- // Update config.org.accounts with returned accountIds
137
- for (const result of accountResults) {
138
- const account = config.org.accounts.find((a) => a.environment === result.environment);
139
- if (account) {
140
- account.accountId = result.accountId;
141
- }
142
- }
143
- console.log('');
144
- console.log(pc.green('✔') + ' AWS Organizations setup complete');
145
- console.log(pc.dim('Account IDs:'));
146
- for (const account of config.org.accounts) {
147
- console.log(pc.dim(` ${account.environment}: ${account.accountId}`));
148
- }
149
- }
150
- catch (error) {
151
- console.log('');
152
- // Handle specific AWS errors
153
- if (error instanceof Error) {
154
- if (error.name === 'CredentialsProviderError' ||
155
- error.message.includes('Could not load credentials')) {
156
- console.log(pc.red('Error:') + ' AWS credentials not configured.');
157
- console.log('');
158
- console.log('Please configure AWS credentials using one of these methods:');
159
- console.log(' 1. Run: ' + pc.cyan('aws configure'));
160
- console.log(' 2. Set environment variables: ' + pc.cyan('AWS_ACCESS_KEY_ID') + ' and ' + pc.cyan('AWS_SECRET_ACCESS_KEY'));
161
- console.log(' 3. Use an AWS profile: ' + pc.cyan('export AWS_PROFILE=your-profile'));
162
- process.exit(1);
163
- }
164
- if (error.name === 'AccessDeniedException' ||
165
- error.message.includes('not authorized')) {
166
- console.log(pc.red('Error:') + ' Insufficient AWS permissions.');
167
- console.log('');
168
- console.log('Required IAM permissions for AWS Organizations:');
169
- console.log(' - ' + pc.cyan('organizations:CreateOrganization'));
170
- console.log(' - ' + pc.cyan('organizations:DescribeOrganization'));
171
- console.log(' - ' + pc.cyan('organizations:CreateAccount'));
172
- console.log(' - ' + pc.cyan('organizations:DescribeCreateAccountStatus'));
173
- process.exit(1);
174
- }
175
- // Log which account failed if it's an account creation error
176
- if (error.message.includes('Failed to create')) {
177
- console.log(pc.red('Error:') + ` ${error.message}`);
178
- process.exit(1);
179
- }
180
- }
181
- // Generic error
182
- const message = error instanceof Error ? error.message : 'Unknown error';
183
- console.log(pc.red('Error:') + ` AWS Organizations setup failed: ${message}`);
184
- process.exit(1);
185
- }
186
- }
187
124
  // Determine output directory
188
125
  const outputDir = resolve(process.cwd(), config.projectName);
189
126
  // Check if directory already exists
@@ -198,9 +135,49 @@ export async function run() {
198
135
  // Generate project
199
136
  console.log('');
200
137
  await generateProject(config, outputDir);
138
+ // Write config file for downstream commands
139
+ writeConfigFile(outputDir, config);
201
140
  // Success message and next steps
202
141
  console.log('');
203
142
  console.log(pc.green('✔') + ` Created ${pc.bold(config.projectName)} successfully!`);
204
143
  printNextSteps(config.projectName, config.platforms);
205
144
  process.exit(0);
206
145
  }
146
+ /**
147
+ * Parse command line arguments and run the CLI
148
+ */
149
+ export async function run() {
150
+ const args = process.argv.slice(2);
151
+ // Check for --help or -h
152
+ if (args.includes('--help') || args.includes('-h')) {
153
+ printHelp();
154
+ process.exit(0);
155
+ }
156
+ // Check for --version or -v
157
+ if (args.includes('--version') || args.includes('-v')) {
158
+ console.log(getVersion());
159
+ process.exit(0);
160
+ }
161
+ // Find the command (first non-flag argument)
162
+ const commandIndex = args.findIndex((arg) => !arg.startsWith('-'));
163
+ const command = commandIndex !== -1 ? args[commandIndex] : undefined;
164
+ const commandArgs = commandIndex !== -1 ? args.slice(commandIndex + 1) : [];
165
+ // Route to appropriate command handler
166
+ switch (command) {
167
+ case 'setup-aws-envs':
168
+ await runSetupAwsEnvs(commandArgs);
169
+ break;
170
+ case 'initialize-github':
171
+ await runInitializeGitHub(commandArgs);
172
+ break;
173
+ case 'setup-github':
174
+ // Deprecated command - show notice and exit
175
+ showDeprecationNotice();
176
+ break;
177
+ default:
178
+ // Default: run interactive create wizard
179
+ // This handles both no command and unknown commands
180
+ await runCreate(args);
181
+ break;
182
+ }
183
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * initialize-github command
3
+ *
4
+ * Configures GitHub Environment with AWS credentials for a single environment
5
+ * Must be run from inside a project directory
6
+ * Requires environment name as argument (dev, stage, prod)
7
+ */
8
+ /**
9
+ * Runs the initialize-github command
10
+ *
11
+ * @param args Command arguments - expects environment name as first arg (optional)
12
+ */
13
+ export declare function runInitializeGitHub(args: string[]): Promise<void>;