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.
@@ -0,0 +1,329 @@
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
+ import ora from 'ora';
9
+ import prompts from 'prompts';
10
+ import pc from 'picocolors';
11
+ import { execSync } from 'node:child_process';
12
+ import { requireProjectContext } from '../utils/project-context.js';
13
+ import { createCrossAccountIAMClient, createDeploymentUserWithCredentials, } from '../aws/iam.js';
14
+ import { createGitHubClient, setEnvironmentCredentials, parseGitHubUrl, } from '../github/secrets.js';
15
+ const VALID_ENVIRONMENTS = ['dev', 'stage', 'prod'];
16
+ /**
17
+ * GitHub environment display names
18
+ */
19
+ const GITHUB_ENV_NAMES = {
20
+ dev: 'Development',
21
+ stage: 'Staging',
22
+ prod: 'Production',
23
+ };
24
+ /**
25
+ * Validates environment argument
26
+ * @param env Environment name to validate
27
+ * @returns true if valid, false otherwise
28
+ */
29
+ function isValidEnvironment(env) {
30
+ return VALID_ENVIRONMENTS.includes(env);
31
+ }
32
+ /**
33
+ * Attempts to extract GitHub repo owner/name from git remote
34
+ * @returns GitHubRepoInfo if successful, null if fails or not GitHub
35
+ */
36
+ function getGitRemoteOrigin() {
37
+ try {
38
+ const remote = execSync('git remote get-url origin', {
39
+ encoding: 'utf-8',
40
+ stdio: ['pipe', 'pipe', 'pipe'],
41
+ }).trim();
42
+ return parseGitHubUrl(remote);
43
+ }
44
+ catch {
45
+ // Git not initialized, no remote, or parse failure
46
+ return null;
47
+ }
48
+ }
49
+ /**
50
+ * Prompts user for GitHub Personal Access Token
51
+ * @returns GitHub PAT string
52
+ */
53
+ async function promptForGitHubPAT() {
54
+ console.log('');
55
+ console.log(pc.bold('GitHub Authentication'));
56
+ console.log('');
57
+ console.log('A Personal Access Token is required to configure GitHub secrets.');
58
+ console.log(pc.dim('Token must have "repo" scope for environment secrets access.'));
59
+ console.log('');
60
+ console.log('Create a token at: https://github.com/settings/tokens/new');
61
+ console.log('');
62
+ const response = await prompts({
63
+ type: 'password',
64
+ name: 'token',
65
+ message: 'GitHub Personal Access Token:',
66
+ validate: (value) => {
67
+ if (!value.trim()) {
68
+ return 'Token is required';
69
+ }
70
+ // GitHub tokens start with ghp_ (classic) or github_pat_ (fine-grained)
71
+ if (!value.startsWith('ghp_') && !value.startsWith('github_pat_')) {
72
+ return 'Invalid token format. Expected ghp_ or github_pat_ prefix.';
73
+ }
74
+ return true;
75
+ },
76
+ }, {
77
+ onCancel: () => {
78
+ console.log(`\n${pc.red('x')} Setup cancelled`);
79
+ process.exit(1);
80
+ },
81
+ });
82
+ if (!response.token) {
83
+ console.error(pc.red('Error:') + ' GitHub token is required.');
84
+ process.exit(1);
85
+ }
86
+ return response.token;
87
+ }
88
+ /**
89
+ * Prompts user to select environment from configured ones
90
+ * @param configuredEnvs List of environments with account IDs
91
+ * @returns Selected environment name
92
+ */
93
+ async function promptForEnvironment(configuredEnvs) {
94
+ console.log('');
95
+ console.log(pc.bold('Environment Selection'));
96
+ console.log('');
97
+ const response = await prompts({
98
+ type: 'select',
99
+ name: 'env',
100
+ message: 'Select environment to configure:',
101
+ choices: configuredEnvs.map((env) => ({
102
+ title: `${env} (${GITHUB_ENV_NAMES[env]})`,
103
+ value: env,
104
+ })),
105
+ }, {
106
+ onCancel: () => {
107
+ console.log(`\n${pc.red('x')} Setup cancelled`);
108
+ process.exit(1);
109
+ },
110
+ });
111
+ if (!response.env) {
112
+ console.error(pc.red('Error:') + ' Environment selection is required.');
113
+ process.exit(1);
114
+ }
115
+ return response.env;
116
+ }
117
+ /**
118
+ * Prompts user for GitHub repository owner/name
119
+ * @returns GitHubRepoInfo with owner and repo
120
+ */
121
+ async function promptForRepoInfo() {
122
+ console.log('');
123
+ console.log(pc.bold('GitHub Repository'));
124
+ console.log('');
125
+ console.log('Enter your GitHub repository in owner/repo format.');
126
+ console.log(pc.dim('Example: myusername/my-project'));
127
+ console.log('');
128
+ const response = await prompts({
129
+ type: 'text',
130
+ name: 'repo',
131
+ message: 'GitHub repository (owner/repo):',
132
+ validate: (value) => {
133
+ if (!value.trim()) {
134
+ return 'Repository is required';
135
+ }
136
+ if (!/^[^/]+\/[^/]+$/.test(value.trim())) {
137
+ return 'Format must be owner/repo (e.g., username/project-name)';
138
+ }
139
+ return true;
140
+ },
141
+ }, {
142
+ onCancel: () => {
143
+ console.log(`\n${pc.red('x')} Setup cancelled`);
144
+ process.exit(1);
145
+ },
146
+ });
147
+ if (!response.repo) {
148
+ console.error(pc.red('Error:') + ' Repository is required.');
149
+ process.exit(1);
150
+ }
151
+ return parseGitHubUrl(response.repo);
152
+ }
153
+ /**
154
+ * Handles AWS and GitHub errors with actionable messages
155
+ * @param error The error to handle
156
+ * @param env The environment being configured
157
+ * @returns never - always exits
158
+ */
159
+ function handleError(error, env) {
160
+ console.error('');
161
+ if (!(error instanceof Error)) {
162
+ console.error(pc.red('Error:') + ' Unknown error occurred');
163
+ process.exit(1);
164
+ }
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
+ // GitHub authentication errors
198
+ if (error.message.includes('authentication failed') || error.name === 'HttpError') {
199
+ console.error(pc.red('Error: GitHub authentication failed'));
200
+ console.error('');
201
+ console.error('Ensure your Personal Access Token:');
202
+ console.error(' 1. Has "repo" scope enabled');
203
+ console.error(' 2. Belongs to the repository owner or has access');
204
+ console.error(' 3. Is not expired');
205
+ console.error('');
206
+ console.error('Create a new token at: https://github.com/settings/tokens/new');
207
+ process.exit(1);
208
+ }
209
+ // Default error handling
210
+ console.error(pc.red('Error:') + ` ${error.message}`);
211
+ if (error.name) {
212
+ console.error(pc.dim(`Error type: ${error.name}`));
213
+ }
214
+ process.exit(1);
215
+ }
216
+ /**
217
+ * Runs the initialize-github command
218
+ *
219
+ * @param args Command arguments - expects environment name as first arg (optional)
220
+ */
221
+ export async function runInitializeGitHub(args) {
222
+ // 1. Validate we're in a project directory
223
+ const context = await requireProjectContext();
224
+ const { config } = context;
225
+ const { projectName, awsRegion } = config;
226
+ // 2. Determine environment
227
+ let env;
228
+ if (args[0]) {
229
+ // Environment provided as argument
230
+ const envArg = args[0];
231
+ // Validate it's lowercase (enforce consistency)
232
+ if (envArg !== envArg.toLowerCase()) {
233
+ console.error(pc.red('Error:') + ` Environment must be lowercase: ${pc.bold(envArg)}`);
234
+ console.error('');
235
+ console.error(`Did you mean: ${pc.cyan(envArg.toLowerCase())}?`);
236
+ process.exit(1);
237
+ }
238
+ if (!isValidEnvironment(envArg)) {
239
+ console.error(pc.red('Error:') + ` Invalid environment: ${pc.bold(envArg)}`);
240
+ console.error('');
241
+ console.error('Valid environments: ' + VALID_ENVIRONMENTS.join(', '));
242
+ process.exit(1);
243
+ }
244
+ env = envArg;
245
+ }
246
+ else {
247
+ // Interactive environment selection
248
+ const configuredEnvs = VALID_ENVIRONMENTS.filter((e) => config.accounts?.[e]);
249
+ if (configuredEnvs.length === 0) {
250
+ console.error(pc.red('Error:') + ' No environments configured.');
251
+ console.error('');
252
+ console.error('Run setup-aws-envs first to create environment accounts:');
253
+ console.error(` ${pc.cyan('npx create-aws-project setup-aws-envs')}`);
254
+ process.exit(1);
255
+ }
256
+ env = await promptForEnvironment(configuredEnvs);
257
+ }
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)
268
+ let repoInfo = getGitRemoteOrigin();
269
+ if (!repoInfo) {
270
+ console.log('');
271
+ console.log(pc.yellow('Note:') + ' Could not detect GitHub repository from git remote.');
272
+ repoInfo = await promptForRepoInfo();
273
+ }
274
+ else {
275
+ console.log('');
276
+ console.log(`Detected repository: ${pc.cyan(`${repoInfo.owner}/${repoInfo.repo}`)}`);
277
+ }
278
+ // 5. Prompt for GitHub PAT (always interactive per CONTEXT.md)
279
+ const pat = await promptForGitHubPAT();
280
+ // 6. Execute AWS and GitHub setup
281
+ const spinner = ora(`Initializing ${env} environment...`).start();
282
+ 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: Create deployment user and credentials
287
+ spinner.text = `Creating IAM deployment user...`;
288
+ const credentials = await createDeploymentUserWithCredentials(iamClient, projectName, env, accountId);
289
+ // Step 3: Configure GitHub
290
+ const githubEnvName = GITHUB_ENV_NAMES[env];
291
+ spinner.text = `Configuring GitHub Environment "${githubEnvName}"...`;
292
+ const githubClient = createGitHubClient(pat);
293
+ await setEnvironmentCredentials(githubClient, repoInfo.owner, repoInfo.repo, githubEnvName, credentials.accessKeyId, credentials.secretAccessKey);
294
+ spinner.succeed(`Configured ${githubEnvName} environment`);
295
+ // Success summary
296
+ console.log('');
297
+ console.log(pc.green(`${env} environment setup complete!`));
298
+ console.log('');
299
+ console.log('Resources created:');
300
+ console.log(` IAM User: ${pc.cyan(`arn:aws:iam::${accountId}:user/deployment/${credentials.userName}`)}`);
301
+ console.log(` GitHub Environment: ${pc.cyan(githubEnvName)}`);
302
+ console.log('');
303
+ console.log('View secrets at:');
304
+ console.log(` ${pc.cyan(`https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings/environments`)}`);
305
+ console.log('');
306
+ // Show next steps (other environments if any remain)
307
+ const remainingEnvs = VALID_ENVIRONMENTS.filter((e) => e !== env && config.accounts?.[e]);
308
+ if (remainingEnvs.length > 0) {
309
+ console.log(pc.bold('Next steps:'));
310
+ console.log('');
311
+ console.log('Configure remaining environments:');
312
+ for (const remainingEnv of remainingEnvs) {
313
+ console.log(` ${pc.cyan(`npx create-aws-project initialize-github ${remainingEnv}`)}`);
314
+ }
315
+ console.log('');
316
+ }
317
+ else {
318
+ console.log(pc.bold('All environments configured!'));
319
+ console.log('');
320
+ console.log('Deploy your application by pushing to the main branch:');
321
+ console.log(` ${pc.cyan('git push origin main')}`);
322
+ console.log('');
323
+ }
324
+ }
325
+ catch (error) {
326
+ spinner.fail(`Failed to configure ${env} environment`);
327
+ handleError(error, env);
328
+ }
329
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * setup-aws-envs command
3
+ *
4
+ * Sets up AWS Organizations and environment accounts (dev, stage, prod)
5
+ * Must be run from inside a project directory
6
+ */
7
+ /**
8
+ * Runs the setup-aws-envs command
9
+ *
10
+ * Creates AWS Organizations and environment accounts (dev, stage, prod)
11
+ *
12
+ * @param _args Command arguments (unused)
13
+ */
14
+ export declare function runSetupAwsEnvs(_args: string[]): Promise<void>;
@@ -0,0 +1,233 @@
1
+ /**
2
+ * setup-aws-envs command
3
+ *
4
+ * Sets up AWS Organizations and environment accounts (dev, stage, prod)
5
+ * Must be run from inside a project directory
6
+ */
7
+ import ora from 'ora';
8
+ import prompts from 'prompts';
9
+ import pc from 'picocolors';
10
+ import { readFileSync, writeFileSync } from 'node:fs';
11
+ import { requireProjectContext } from '../utils/project-context.js';
12
+ import { createOrganizationsClient, checkExistingOrganization, createOrganization, createAccount, waitForAccountCreation, } from '../aws/organizations.js';
13
+ /**
14
+ * Environment names for account creation
15
+ */
16
+ const ENVIRONMENTS = ['dev', 'stage', 'prod'];
17
+ /**
18
+ * Validates an email address format
19
+ * @param value Email string to validate
20
+ * @returns true if valid, error message string if invalid
21
+ */
22
+ function validateEmail(value) {
23
+ if (!value.trim()) {
24
+ return 'Email is required';
25
+ }
26
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
27
+ return 'Invalid email format';
28
+ }
29
+ return true;
30
+ }
31
+ /**
32
+ * Validates email format and uniqueness
33
+ * @param value Email string to validate
34
+ * @param existing Array of already collected emails
35
+ * @returns true if valid and unique, error message string if invalid
36
+ */
37
+ function validateUniqueEmail(value, existing) {
38
+ const emailValid = validateEmail(value);
39
+ if (emailValid !== true) {
40
+ return emailValid;
41
+ }
42
+ const lower = value.toLowerCase();
43
+ if (existing.some((e) => e?.toLowerCase() === lower)) {
44
+ return 'Each account requires a unique email address';
45
+ }
46
+ return true;
47
+ }
48
+ /**
49
+ * Collects unique email addresses for each environment account
50
+ * @param projectName Project name for display
51
+ * @returns EnvironmentEmails object with dev, stage, prod emails
52
+ */
53
+ async function collectEmails(projectName) {
54
+ console.log('');
55
+ console.log(pc.bold('AWS Account Configuration'));
56
+ console.log('');
57
+ console.log(`Setting up environment accounts for ${pc.cyan(projectName)}`);
58
+ console.log('');
59
+ console.log('Each AWS environment account requires a unique root email address.');
60
+ console.log(pc.dim('Tip: Use email aliases like yourname+dev@company.com'));
61
+ console.log('');
62
+ const onCancel = () => {
63
+ console.log(`\n${pc.red('x')} Setup cancelled`);
64
+ process.exit(1);
65
+ };
66
+ // Collect emails sequentially to enable uniqueness validation
67
+ const devResponse = await prompts({
68
+ type: 'text',
69
+ name: 'dev',
70
+ message: 'Dev account root email:',
71
+ validate: validateEmail,
72
+ }, { onCancel });
73
+ if (!devResponse.dev) {
74
+ console.log(pc.red('Error:') + ' Dev email is required.');
75
+ process.exit(1);
76
+ }
77
+ const stageResponse = await prompts({
78
+ type: 'text',
79
+ name: 'stage',
80
+ message: 'Stage account root email:',
81
+ validate: (v) => validateUniqueEmail(v, [devResponse.dev]),
82
+ }, { onCancel });
83
+ if (!stageResponse.stage) {
84
+ console.log(pc.red('Error:') + ' Stage email is required.');
85
+ process.exit(1);
86
+ }
87
+ const prodResponse = await prompts({
88
+ type: 'text',
89
+ name: 'prod',
90
+ message: 'Prod account root email:',
91
+ validate: (v) => validateUniqueEmail(v, [devResponse.dev, stageResponse.stage]),
92
+ }, { onCancel });
93
+ if (!prodResponse.prod) {
94
+ console.log(pc.red('Error:') + ' Prod email is required.');
95
+ process.exit(1);
96
+ }
97
+ return {
98
+ dev: devResponse.dev,
99
+ stage: stageResponse.stage,
100
+ prod: prodResponse.prod,
101
+ };
102
+ }
103
+ /**
104
+ * Updates the config file with account IDs
105
+ * @param configPath Path to the config file
106
+ * @param accounts Record of environment to account ID
107
+ */
108
+ function updateConfigAccounts(configPath, accounts) {
109
+ const content = readFileSync(configPath, 'utf-8');
110
+ const config = JSON.parse(content);
111
+ config.accounts = { ...config.accounts, ...accounts };
112
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
113
+ }
114
+ /**
115
+ * Handles AWS-specific errors with actionable messages
116
+ * @param error The error to handle
117
+ * @returns never - always exits
118
+ */
119
+ function handleAwsError(error) {
120
+ console.error('');
121
+ if (!(error instanceof Error)) {
122
+ console.error(pc.red('Error:') + ' Unknown error occurred');
123
+ process.exit(1);
124
+ }
125
+ switch (error.name) {
126
+ case 'AccessDeniedException':
127
+ console.error(pc.red('Error: Insufficient AWS permissions'));
128
+ console.error('');
129
+ console.error('Your AWS credentials need the following permissions:');
130
+ console.error(' - organizations:DescribeOrganization');
131
+ console.error(' - organizations:CreateOrganization');
132
+ console.error(' - organizations:CreateAccount');
133
+ console.error(' - organizations:DescribeCreateAccountStatus');
134
+ console.error('');
135
+ console.error('Ensure you are using credentials from the management account.');
136
+ break;
137
+ case 'AWSOrganizationsNotInUseException':
138
+ console.error(pc.red('Error: Unexpected state - no organization exists after creation attempt'));
139
+ console.error('This may be a temporary AWS issue. Please try again in a few minutes.');
140
+ break;
141
+ case 'ConstraintViolationException':
142
+ console.error(pc.red('Error: AWS Organizations limit reached'));
143
+ console.error('');
144
+ console.error(error.message);
145
+ console.error('');
146
+ console.error('You may have hit the account creation limit. Contact AWS Support.');
147
+ break;
148
+ case 'FinalizingOrganizationException':
149
+ console.error(pc.red('Error: AWS Organization is still initializing'));
150
+ console.error('Please wait about an hour and try again.');
151
+ break;
152
+ case 'TooManyRequestsException':
153
+ console.error(pc.red('Error: AWS rate limit exceeded'));
154
+ console.error('Please wait a few minutes and try again.');
155
+ break;
156
+ default:
157
+ console.error(pc.red('Error:') + ` ${error.message}`);
158
+ if (error.name) {
159
+ console.error(pc.dim(`Error type: ${error.name}`));
160
+ }
161
+ }
162
+ process.exit(1);
163
+ }
164
+ /**
165
+ * Runs the setup-aws-envs command
166
+ *
167
+ * Creates AWS Organizations and environment accounts (dev, stage, prod)
168
+ *
169
+ * @param _args Command arguments (unused)
170
+ */
171
+ export async function runSetupAwsEnvs(_args) {
172
+ // 1. Validate we're in a project directory
173
+ const context = await requireProjectContext();
174
+ const { config, configPath } = context;
175
+ // 2. Check if already configured (warn but don't abort - allows retry after partial failure)
176
+ const existingAccounts = config.accounts ?? {};
177
+ if (Object.keys(existingAccounts).length > 0) {
178
+ console.log('');
179
+ console.log(pc.yellow('Warning:') + ' AWS accounts already configured in this project:');
180
+ for (const [env, id] of Object.entries(existingAccounts)) {
181
+ console.log(` ${env}: ${id}`);
182
+ }
183
+ console.log('');
184
+ console.log(pc.dim('Continuing will create additional accounts...'));
185
+ }
186
+ // 3. Collect emails (all input before any AWS calls - per research pitfall #6)
187
+ const emails = await collectEmails(config.projectName);
188
+ // 4. Execute AWS operations with progress spinner
189
+ const spinner = ora('Starting AWS Organizations setup...').start();
190
+ try {
191
+ // Organizations API requires us-east-1 (region-locked per research pitfall #1)
192
+ const client = createOrganizationsClient('us-east-1');
193
+ // Check/create organization
194
+ spinner.text = 'Checking for existing AWS Organization...';
195
+ let orgId = await checkExistingOrganization(client);
196
+ if (!orgId) {
197
+ spinner.text = 'Creating AWS Organization...';
198
+ orgId = await createOrganization(client);
199
+ spinner.succeed(`Created AWS Organization: ${orgId}`);
200
+ }
201
+ else {
202
+ spinner.succeed(`Using existing AWS Organization: ${orgId}`);
203
+ }
204
+ // Create accounts sequentially (per research - AWS rate limits require sequential)
205
+ const accounts = {};
206
+ for (const env of ENVIRONMENTS) {
207
+ spinner.start(`Creating ${env} account (this may take several minutes)...`);
208
+ const accountName = `${config.projectName}-${env}`;
209
+ const { requestId } = await createAccount(client, emails[env], accountName);
210
+ spinner.text = `Waiting for ${env} account creation...`;
211
+ const result = await waitForAccountCreation(client, requestId);
212
+ accounts[env] = result.accountId;
213
+ // Save after EACH successful account (per research pitfall #3 - handle partial success)
214
+ updateConfigAccounts(configPath, accounts);
215
+ spinner.succeed(`Created ${env} account: ${result.accountId}`);
216
+ }
217
+ // Final success
218
+ console.log('');
219
+ console.log(pc.green('AWS environment setup complete!'));
220
+ console.log('');
221
+ console.log('Account IDs saved to config:');
222
+ for (const [env, id] of Object.entries(accounts)) {
223
+ console.log(` ${env}: ${id}`);
224
+ }
225
+ console.log('');
226
+ console.log(pc.bold('Next step:'));
227
+ console.log(` ${pc.cyan('npx create-aws-project initialize-github dev')}`);
228
+ }
229
+ catch (error) {
230
+ spinner.fail('AWS setup failed');
231
+ handleAwsError(error);
232
+ }
233
+ }
@@ -1,3 +1,14 @@
1
+ /**
2
+ * setup-github command module
3
+ *
4
+ * Orchestrates the complete flow of creating IAM deployment users
5
+ * and configuring GitHub repository secrets for each environment.
6
+ */
7
+ /**
8
+ * Shows deprecation notice for the old setup-github command
9
+ * Exits with code 1 to indicate user should use new command
10
+ */
11
+ export declare function showDeprecationNotice(): never;
1
12
  /**
2
13
  * Runs the setup-github command
3
14
  */
@@ -10,6 +10,28 @@ import { createGitHubClient, setEnvironmentCredentials } from '../github/secrets
10
10
  * Orchestrates the complete flow of creating IAM deployment users
11
11
  * and configuring GitHub repository secrets for each environment.
12
12
  */
13
+ /**
14
+ * Shows deprecation notice for the old setup-github command
15
+ * Exits with code 1 to indicate user should use new command
16
+ */
17
+ export function showDeprecationNotice() {
18
+ console.log('');
19
+ console.log(pc.yellow('DEPRECATED:') + ' The setup-github command has been replaced.');
20
+ console.log('');
21
+ console.log('Use the new per-environment command instead:');
22
+ console.log('');
23
+ console.log(` ${pc.cyan('npx create-aws-project initialize-github <env>')}`);
24
+ console.log('');
25
+ console.log('Where <env> is one of: dev, stage, prod');
26
+ console.log('');
27
+ console.log('Example workflow:');
28
+ console.log(` 1. ${pc.cyan('npx create-aws-project setup-aws-envs')} # Create AWS accounts`);
29
+ console.log(` 2. ${pc.cyan('npx create-aws-project initialize-github dev')} # Configure dev`);
30
+ console.log(` 3. ${pc.cyan('npx create-aws-project initialize-github prod')} # Configure prod`);
31
+ console.log('');
32
+ console.log(pc.dim('The new approach provides better error isolation per environment.'));
33
+ process.exit(1);
34
+ }
13
35
  /**
14
36
  * Prints the setup-github command banner
15
37
  */
@@ -45,6 +45,10 @@ export function deriveTokenValues(config) {
45
45
  AUTH_AUTH0: config.auth.provider === 'auth0' ? 'true' : 'false',
46
46
  AUTH_SOCIAL_LOGIN: config.auth.features.includes('social-login') ? 'true' : 'false',
47
47
  AUTH_MFA: config.auth.features.includes('mfa') ? 'true' : 'false',
48
+ // Platform tokens for conditional documentation
49
+ WEB: config.platforms.includes('web') ? 'true' : 'false',
50
+ MOBILE: config.platforms.includes('mobile') ? 'true' : 'false',
51
+ API: config.platforms.includes('api') ? 'true' : 'false',
48
52
  // Organization tokens
49
53
  ORG_ENABLED: config.org?.enabled ? 'true' : 'false',
50
54
  ORG_NAME: config.org?.organizationName ?? '',
@@ -61,6 +65,7 @@ export function deriveTokenValues(config) {
61
65
  */
62
66
  export const templateManifest = {
63
67
  shared: [
68
+ { src: 'root/README.md', dest: 'README.md' },
64
69
  { src: 'root/package.json', dest: 'package.json' },
65
70
  { src: 'root/tsconfig.base.json', dest: 'tsconfig.base.json' },
66
71
  { src: 'root/nx.json', dest: 'nx.json' },
@@ -11,6 +11,10 @@ export interface TokenValues {
11
11
  AUTH_AUTH0: string;
12
12
  AUTH_SOCIAL_LOGIN: string;
13
13
  AUTH_MFA: string;
14
+ /** Platform selection flags ('true' or 'false') */
15
+ WEB: string;
16
+ MOBILE: string;
17
+ API: string;
14
18
  /** Organization enabled flag ('true' or 'false') */
15
19
  ORG_ENABLED?: string;
16
20
  /** Organization name */