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.
@@ -0,0 +1,139 @@
1
+ import { STSClient, AssumeRoleCommand } from '@aws-sdk/client-sts';
2
+ import { execa } from 'execa';
3
+ import pc from 'picocolors';
4
+ /**
5
+ * AWS CDK Bootstrap module
6
+ *
7
+ * Provides functions to bootstrap AWS CDK in environment accounts,
8
+ * preparing them for CDK deployments with proper trust policies.
9
+ */
10
+ const ENVIRONMENTS = ['dev', 'stage', 'prod'];
11
+ /**
12
+ * Bootstraps AWS CDK in a single environment account
13
+ *
14
+ * Runs `cdk bootstrap` via npx to prepare the account for CDK deployments.
15
+ * Bootstrap creates the necessary CloudFormation stack with S3 bucket and ECR
16
+ * repository for CDK deployment assets.
17
+ *
18
+ * @param options - Bootstrap options including account ID, region, and credentials
19
+ * @returns Promise resolving to bootstrap result with success status and output
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const result = await bootstrapCDKEnvironment({
24
+ * accountId: '123456789012',
25
+ * region: 'us-west-2',
26
+ * credentials: { accessKeyId: 'AKIA...', secretAccessKey: 'secret...', sessionToken: 'token...' }
27
+ * });
28
+ * if (!result.success) {
29
+ * console.error('Bootstrap failed:', result.output);
30
+ * }
31
+ * ```
32
+ */
33
+ export async function bootstrapCDKEnvironment(options) {
34
+ const { accountId, region, credentials } = options;
35
+ try {
36
+ const args = [
37
+ 'cdk',
38
+ 'bootstrap',
39
+ `aws://${accountId}/${region}`,
40
+ '--trust',
41
+ accountId,
42
+ '--cloudformation-execution-policies',
43
+ 'arn:aws:iam::aws:policy/AdministratorAccess',
44
+ '--require-approval',
45
+ 'never',
46
+ ];
47
+ const env = {
48
+ ...process.env,
49
+ AWS_ACCESS_KEY_ID: credentials.accessKeyId,
50
+ AWS_SECRET_ACCESS_KEY: credentials.secretAccessKey,
51
+ AWS_REGION: region,
52
+ };
53
+ // Add session token if present (for temporary credentials)
54
+ if (credentials.sessionToken) {
55
+ env.AWS_SESSION_TOKEN = credentials.sessionToken;
56
+ }
57
+ const result = await execa('npx', args, {
58
+ all: true,
59
+ env,
60
+ });
61
+ return {
62
+ success: true,
63
+ output: result.all || '',
64
+ };
65
+ }
66
+ catch (error) {
67
+ const execaError = error;
68
+ return {
69
+ success: false,
70
+ output: execaError.all || execaError.message,
71
+ };
72
+ }
73
+ }
74
+ /**
75
+ * Bootstraps AWS CDK in all environment accounts (dev, stage, prod)
76
+ *
77
+ * Iterates through all environments, assumes the OrganizationAccountAccessRole
78
+ * in each account, and runs CDK bootstrap. This prepares all environments for
79
+ * CDK deployments with proper trust relationships and execution policies.
80
+ *
81
+ * @param options - Bootstrap options including account map, region, admin credentials, and spinner
82
+ * @throws Error if bootstrap fails in any environment
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * await bootstrapAllEnvironments({
87
+ * accounts: { dev: '111111111111', stage: '222222222222', prod: '333333333333' },
88
+ * region: 'us-west-2',
89
+ * adminCredentials: { accessKeyId: 'AKIA...', secretAccessKey: 'secret...' },
90
+ * spinner: ora()
91
+ * });
92
+ * ```
93
+ */
94
+ export async function bootstrapAllEnvironments(options) {
95
+ const { accounts, region, adminCredentials, spinner } = options;
96
+ for (const env of ENVIRONMENTS) {
97
+ const accountId = accounts[env];
98
+ if (!accountId) {
99
+ continue;
100
+ }
101
+ spinner.text = `Bootstrapping CDK in ${env} account (${accountId})...`;
102
+ // Get cross-account credentials via STS AssumeRole
103
+ const stsClient = new STSClient({
104
+ region,
105
+ ...(adminCredentials && { credentials: adminCredentials }),
106
+ });
107
+ const assumeRoleCommand = new AssumeRoleCommand({
108
+ RoleArn: `arn:aws:iam::${accountId}:role/OrganizationAccountAccessRole`,
109
+ RoleSessionName: `create-aws-project-bootstrap-${Date.now()}`,
110
+ DurationSeconds: 900,
111
+ });
112
+ const assumeRoleResponse = await stsClient.send(assumeRoleCommand);
113
+ if (!assumeRoleResponse.Credentials?.AccessKeyId ||
114
+ !assumeRoleResponse.Credentials?.SecretAccessKey ||
115
+ !assumeRoleResponse.Credentials?.SessionToken) {
116
+ spinner.fail(`Failed to assume role in ${env} account`);
117
+ throw new Error(`Failed to get temporary credentials for ${env} account`);
118
+ }
119
+ const credentials = {
120
+ accessKeyId: assumeRoleResponse.Credentials.AccessKeyId,
121
+ secretAccessKey: assumeRoleResponse.Credentials.SecretAccessKey,
122
+ sessionToken: assumeRoleResponse.Credentials.SessionToken,
123
+ };
124
+ // Bootstrap the environment
125
+ const result = await bootstrapCDKEnvironment({
126
+ accountId,
127
+ region,
128
+ credentials,
129
+ });
130
+ if (!result.success) {
131
+ spinner.fail(`CDK bootstrap failed in ${env} account`);
132
+ console.error(pc.red('Bootstrap error:'));
133
+ console.error(result.output);
134
+ throw new Error(`CDK bootstrap failed in ${env} account`);
135
+ }
136
+ spinner.succeed(`CDK bootstrapped in ${env} account (${accountId})`);
137
+ }
138
+ console.log(pc.green('All environments bootstrapped for CDK deployments!'));
139
+ }
package/dist/aws/iam.js CHANGED
@@ -210,6 +210,23 @@ function getCDKDeploymentPolicyDocument(accountId) {
210
210
  ],
211
211
  Resource: '*',
212
212
  },
213
+ {
214
+ Sid: 'S3ListBuckets',
215
+ Effect: 'Allow',
216
+ Action: 's3:ListAllMyBuckets',
217
+ Resource: '*',
218
+ },
219
+ {
220
+ Sid: 'AccessWebBucket',
221
+ Effect: 'Allow',
222
+ Action: [
223
+ 's3:*'
224
+ ],
225
+ Resource: [
226
+ `arn:aws:s3:::*-development-web-${accountId}`,
227
+ `arn:aws:s3:::*-development-web-${accountId}/*`,
228
+ ],
229
+ },
213
230
  ],
214
231
  };
215
232
  return JSON.stringify(policy);
@@ -1,4 +1,4 @@
1
- import { OrganizationsClient } from '@aws-sdk/client-organizations';
1
+ import { OrganizationsClient, Account } from '@aws-sdk/client-organizations';
2
2
  /**
3
3
  * AWS Organizations service module
4
4
  *
@@ -17,6 +17,12 @@ export declare function createOrganizationsClient(region?: string): Organization
17
17
  * @returns Organization ID if exists, null if not in an organization
18
18
  */
19
19
  export declare function checkExistingOrganization(client: OrganizationsClient): Promise<string | null>;
20
+ /**
21
+ * Lists all accounts in the AWS Organization with automatic pagination handling
22
+ * @param client - OrganizationsClient instance
23
+ * @returns Array of Account objects from the organization
24
+ */
25
+ export declare function listOrganizationAccounts(client: OrganizationsClient): Promise<Account[]>;
20
26
  /**
21
27
  * Creates a new AWS Organization with all features enabled
22
28
  * @param client - OrganizationsClient instance
@@ -1,4 +1,4 @@
1
- import { OrganizationsClient, CreateOrganizationCommand, CreateAccountCommand, DescribeCreateAccountStatusCommand, DescribeOrganizationCommand, AlreadyInOrganizationException, } from '@aws-sdk/client-organizations';
1
+ import { OrganizationsClient, CreateOrganizationCommand, CreateAccountCommand, DescribeCreateAccountStatusCommand, DescribeOrganizationCommand, AlreadyInOrganizationException, ListAccountsCommand, } from '@aws-sdk/client-organizations';
2
2
  import pc from 'picocolors';
3
3
  /**
4
4
  * AWS Organizations service module
@@ -33,6 +33,24 @@ export async function checkExistingOrganization(client) {
33
33
  throw error;
34
34
  }
35
35
  }
36
+ /**
37
+ * Lists all accounts in the AWS Organization with automatic pagination handling
38
+ * @param client - OrganizationsClient instance
39
+ * @returns Array of Account objects from the organization
40
+ */
41
+ export async function listOrganizationAccounts(client) {
42
+ const accounts = [];
43
+ let nextToken;
44
+ do {
45
+ const command = new ListAccountsCommand({ NextToken: nextToken });
46
+ const response = await client.send(command);
47
+ if (response.Accounts) {
48
+ accounts.push(...response.Accounts);
49
+ }
50
+ nextToken = response.NextToken;
51
+ } while (nextToken);
52
+ return accounts;
53
+ }
36
54
  /**
37
55
  * Creates a new AWS Organization with all features enabled
38
56
  * @param client - OrganizationsClient instance
@@ -0,0 +1,68 @@
1
+ import { IAMClient } from '@aws-sdk/client-iam';
2
+ /**
3
+ * AWS Root Credential Detection and Admin User Management
4
+ *
5
+ * Provides functions to detect root credentials and create/adopt admin IAM users
6
+ * in the management account for cross-account operations.
7
+ */
8
+ /**
9
+ * Caller identity information from STS GetCallerIdentity
10
+ */
11
+ export interface CallerIdentity {
12
+ arn: string;
13
+ accountId: string;
14
+ userId: string;
15
+ isRoot: boolean;
16
+ }
17
+ /**
18
+ * Admin user creation result
19
+ */
20
+ export interface AdminUserResult {
21
+ userName: string;
22
+ accessKeyId: string;
23
+ secretAccessKey: string;
24
+ adopted: boolean;
25
+ }
26
+ /**
27
+ * Checks if an ARN represents a root user
28
+ * @param arn - AWS ARN to check
29
+ * @returns true if ARN ends with :root
30
+ */
31
+ export declare function isRootUser(arn: string): boolean;
32
+ /**
33
+ * Detects if the current AWS credentials are for a root user
34
+ * @param region - AWS region for STS client
35
+ * @returns Caller identity with root status flag
36
+ */
37
+ export declare function detectRootCredentials(region: string): Promise<CallerIdentity>;
38
+ /**
39
+ * Retries a function with exponential backoff
40
+ * @param fn - Async function to retry
41
+ * @param options - Retry configuration
42
+ * @returns Result of the function
43
+ * @throws Last error if all retries fail
44
+ */
45
+ export declare function retryWithBackoff<T>(fn: () => Promise<T>, options?: {
46
+ maxRetries?: number;
47
+ baseDelayMs?: number;
48
+ description?: string;
49
+ }): Promise<T>;
50
+ /**
51
+ * Creates access key for admin user
52
+ * @param client - IAMClient instance
53
+ * @param userName - Admin user name
54
+ * @returns Access key credentials
55
+ * @throws Error if user already has 2 access keys
56
+ */
57
+ export declare function createAccessKeyForAdmin(client: IAMClient, userName: string): Promise<{
58
+ accessKeyId: string;
59
+ secretAccessKey: string;
60
+ }>;
61
+ /**
62
+ * Creates or adopts an admin user in the management account
63
+ * @param client - IAMClient instance
64
+ * @param projectName - Project name for user naming
65
+ * @returns Admin user credentials
66
+ * @throws Error if user exists but not managed by this tool or has existing keys
67
+ */
68
+ export declare function createOrAdoptAdminUser(client: IAMClient, projectName: string): Promise<AdminUserResult>;
@@ -0,0 +1,157 @@
1
+ import { CreateUserCommand, GetUserCommand, AttachUserPolicyCommand, CreateAccessKeyCommand, ListUserTagsCommand, NoSuchEntityException, } from '@aws-sdk/client-iam';
2
+ import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts';
3
+ import pc from 'picocolors';
4
+ import { getAccessKeyCount } from './iam.js';
5
+ /**
6
+ * Checks if an ARN represents a root user
7
+ * @param arn - AWS ARN to check
8
+ * @returns true if ARN ends with :root
9
+ */
10
+ export function isRootUser(arn) {
11
+ return arn.endsWith(':root');
12
+ }
13
+ /**
14
+ * Detects if the current AWS credentials are for a root user
15
+ * @param region - AWS region for STS client
16
+ * @returns Caller identity with root status flag
17
+ */
18
+ export async function detectRootCredentials(region) {
19
+ const client = new STSClient({ region });
20
+ const command = new GetCallerIdentityCommand({});
21
+ const response = await client.send(command);
22
+ if (!response.Arn || !response.Account || !response.UserId) {
23
+ throw new Error('GetCallerIdentity returned incomplete response');
24
+ }
25
+ return {
26
+ arn: response.Arn,
27
+ accountId: response.Account,
28
+ userId: response.UserId,
29
+ isRoot: isRootUser(response.Arn),
30
+ };
31
+ }
32
+ /**
33
+ * Helper function to sleep for retry backoff
34
+ */
35
+ function sleep(ms) {
36
+ return new Promise((resolve) => setTimeout(resolve, ms));
37
+ }
38
+ /**
39
+ * Retries a function with exponential backoff
40
+ * @param fn - Async function to retry
41
+ * @param options - Retry configuration
42
+ * @returns Result of the function
43
+ * @throws Last error if all retries fail
44
+ */
45
+ export async function retryWithBackoff(fn, options) {
46
+ const maxRetries = options?.maxRetries ?? 5;
47
+ const baseDelayMs = options?.baseDelayMs ?? 1000;
48
+ const description = options?.description ?? 'operation';
49
+ let lastError;
50
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
51
+ try {
52
+ return await fn();
53
+ }
54
+ catch (error) {
55
+ lastError = error instanceof Error ? error : new Error(String(error));
56
+ if (attempt < maxRetries) {
57
+ const delay = baseDelayMs * Math.pow(2, attempt);
58
+ console.log(pc.dim(` Retrying ${description} (attempt ${attempt + 1}/${maxRetries})...`));
59
+ await sleep(delay);
60
+ }
61
+ }
62
+ }
63
+ throw lastError;
64
+ }
65
+ /**
66
+ * Creates access key for admin user
67
+ * @param client - IAMClient instance
68
+ * @param userName - Admin user name
69
+ * @returns Access key credentials
70
+ * @throws Error if user already has 2 access keys
71
+ */
72
+ export async function createAccessKeyForAdmin(client, userName) {
73
+ // Check access key limit before attempting creation
74
+ const keyCount = await getAccessKeyCount(client, userName);
75
+ if (keyCount >= 2) {
76
+ throw new Error(`IAM user ${userName} already has 2 access keys (AWS maximum). Delete an existing key in AWS Console > IAM > Users > ${userName} > Security credentials before retrying.`);
77
+ }
78
+ const command = new CreateAccessKeyCommand({
79
+ UserName: userName,
80
+ });
81
+ const response = await client.send(command);
82
+ if (!response.AccessKey?.AccessKeyId || !response.AccessKey?.SecretAccessKey) {
83
+ throw new Error('Access key created but credentials not returned');
84
+ }
85
+ console.log(pc.green(` Created access key for ${userName}`));
86
+ return {
87
+ accessKeyId: response.AccessKey.AccessKeyId,
88
+ secretAccessKey: response.AccessKey.SecretAccessKey,
89
+ };
90
+ }
91
+ /**
92
+ * Creates or adopts an admin user in the management account
93
+ * @param client - IAMClient instance
94
+ * @param projectName - Project name for user naming
95
+ * @returns Admin user credentials
96
+ * @throws Error if user exists but not managed by this tool or has existing keys
97
+ */
98
+ export async function createOrAdoptAdminUser(client, projectName) {
99
+ const userName = `${projectName}-admin`;
100
+ // Check if user already exists
101
+ try {
102
+ await client.send(new GetUserCommand({ UserName: userName }));
103
+ // User exists - check if managed by us
104
+ const tagsCommand = new ListUserTagsCommand({ UserName: userName });
105
+ const tagsResponse = await client.send(tagsCommand);
106
+ const isManagedByUs = tagsResponse.Tags?.some((tag) => tag.Key === 'ManagedBy' && tag.Value === 'create-aws-starter-kit') ?? false;
107
+ if (!isManagedByUs) {
108
+ throw new Error(`IAM user "${userName}" exists but was not created by this tool. Delete it or use a different project name.`);
109
+ }
110
+ // Managed by us - check key count
111
+ const keyCount = await getAccessKeyCount(client, userName);
112
+ if (keyCount >= 1) {
113
+ throw new Error(`IAM user "${userName}" already exists with ${keyCount} access key(s). This tool cannot retrieve existing secret keys. Please delete all access keys for this user in AWS Console > IAM > Users > ${userName} > Security credentials, or provide IAM credentials directly instead of using root credentials.`);
114
+ }
115
+ // No keys - create new key and adopt
116
+ console.log(pc.yellow(` Adopting existing admin user: ${userName}`));
117
+ const credentials = await retryWithBackoff(() => createAccessKeyForAdmin(client, userName), { description: 'access key creation' });
118
+ return {
119
+ userName,
120
+ accessKeyId: credentials.accessKeyId,
121
+ secretAccessKey: credentials.secretAccessKey,
122
+ adopted: true,
123
+ };
124
+ }
125
+ catch (error) {
126
+ // User doesn't exist - create new
127
+ if (error instanceof NoSuchEntityException) {
128
+ console.log(pc.cyan(` Creating admin user: ${userName}`));
129
+ // Create user
130
+ await client.send(new CreateUserCommand({
131
+ UserName: userName,
132
+ Path: '/admin/',
133
+ Tags: [
134
+ { Key: 'Purpose', Value: 'CLI Admin' },
135
+ { Key: 'ManagedBy', Value: 'create-aws-starter-kit' },
136
+ ],
137
+ }));
138
+ console.log(pc.green(` Created IAM user: ${userName}`));
139
+ // Attach AdministratorAccess policy
140
+ await client.send(new AttachUserPolicyCommand({
141
+ UserName: userName,
142
+ PolicyArn: 'arn:aws:iam::aws:policy/AdministratorAccess',
143
+ }));
144
+ console.log(pc.green(` Attached AdministratorAccess policy`));
145
+ // Create access key with retry for IAM eventual consistency
146
+ const credentials = await retryWithBackoff(() => createAccessKeyForAdmin(client, userName), { description: 'access key creation after user creation' });
147
+ return {
148
+ userName,
149
+ accessKeyId: credentials.accessKeyId,
150
+ secretAccessKey: credentials.secretAccessKey,
151
+ adopted: false,
152
+ };
153
+ }
154
+ // Other error - propagate
155
+ throw error;
156
+ }
157
+ }
package/dist/cli.js CHANGED
@@ -7,6 +7,7 @@ import { generateProject } from './generator/index.js';
7
7
  import { showDeprecationNotice } from './commands/setup-github.js';
8
8
  import { runSetupAwsEnvs } from './commands/setup-aws-envs.js';
9
9
  import { runInitializeGitHub } from './commands/initialize-github.js';
10
+ import { promptGitSetup, setupGitRepository } from './git/setup.js';
10
11
  /**
11
12
  * Write project configuration file for downstream commands
12
13
  */
@@ -40,14 +41,14 @@ function getVersion() {
40
41
  */
41
42
  function printHelp() {
42
43
  console.log(`
43
- create-aws-starter-kit [command] [options]
44
+ create-aws-project [command] [options]
44
45
 
45
46
  Scaffold a new AWS Starter Kit project with React, Lambda, and CDK infrastructure.
46
47
 
47
48
  Commands:
48
49
  (default) Create a new project (interactive wizard)
49
50
  setup-aws-envs Set up AWS Organizations and environment accounts
50
- initialize-github Configure GitHub Environment for deployment
51
+ initialize-github Configure GitHub Environment for deployment (supports batch mode)
51
52
  setup-github ${pc.dim('[DEPRECATED]')} Use initialize-github instead
52
53
 
53
54
  Options:
@@ -55,15 +56,19 @@ Options:
55
56
  --version, -v Show version number
56
57
 
57
58
  Usage:
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
59
+ create-aws-project Run interactive wizard
60
+ create-aws-project setup-aws-envs Create AWS accounts
61
+ create-aws-project initialize-github dev Configure dev environment
62
+ create-aws-project initialize-github --all Configure all environments
63
+ create-aws-project initialize-github dev stage Configure specific environments
61
64
 
62
65
  Examples:
63
- create-aws-starter-kit my-app
64
- create-aws-starter-kit setup-aws-envs
65
- create-aws-starter-kit initialize-github dev
66
- create-aws-starter-kit --help
66
+ create-aws-project my-app
67
+ create-aws-project setup-aws-envs
68
+ create-aws-project initialize-github dev
69
+ create-aws-project initialize-github --all
70
+ create-aws-project initialize-github dev stage prod
71
+ create-aws-project --help
67
72
  `.trim());
68
73
  }
69
74
  /**
@@ -73,7 +78,7 @@ function printWelcome() {
73
78
  console.log(`
74
79
  ╔═══════════════════════════════════════════════════════╗
75
80
  ║ ║
76
- ║ create-aws-starter-kit
81
+ ║ create-aws-project
77
82
  ║ AWS Starter Kit Project Generator ║
78
83
  ║ ║
79
84
  ╚═══════════════════════════════════════════════════════╝
@@ -82,41 +87,30 @@ function printWelcome() {
82
87
  /**
83
88
  * Print post-generation instructions
84
89
  */
85
- function printNextSteps(projectName, platforms) {
90
+ function printNextSteps(projectName) {
86
91
  console.log('');
87
92
  console.log(pc.bold('Next steps:'));
88
93
  console.log('');
89
94
  console.log(` ${pc.cyan('cd')} ${projectName}`);
90
- console.log(` ${pc.cyan('npm install')}`);
91
95
  console.log('');
92
- if (platforms.includes('web')) {
93
- console.log(` ${pc.gray('# Start web app')}`);
94
- console.log(` ${pc.cyan('npm run web')}`);
95
- console.log('');
96
- }
97
- if (platforms.includes('mobile')) {
98
- console.log(` ${pc.gray('# Start mobile app')}`);
99
- console.log(` ${pc.cyan('npm run mobile')}`);
100
- console.log('');
101
- }
102
- if (platforms.includes('api')) {
103
- console.log(` ${pc.gray('# Deploy API')}`);
104
- console.log(` ${pc.cyan('npm run cdk:deploy')}`);
105
- console.log('');
106
- }
107
- console.log(` ${pc.gray('# Configure AWS environments')}`);
96
+ console.log(` ${pc.gray('# Set up AWS accounts and credentials')}`);
108
97
  console.log(` ${pc.cyan('npx create-aws-project setup-aws-envs')}`);
109
98
  console.log('');
99
+ console.log(` ${pc.gray('# Push credentials to GitHub secrets')}`);
100
+ console.log(` ${pc.cyan('npx create-aws-project initialize-github')}`);
101
+ console.log('');
110
102
  console.log(pc.gray('Happy coding!'));
111
103
  }
112
104
  /**
113
105
  * Run the create project wizard flow
114
106
  * This is the default command when no subcommand is specified
115
107
  */
116
- async function runCreate(_args) {
108
+ async function runCreate(args) {
117
109
  printWelcome();
118
110
  console.log(''); // blank line after banner
119
- const config = await runWizard();
111
+ // Extract project name from CLI args (first non-flag argument)
112
+ const nameArg = args.find(arg => !arg.startsWith('-'));
113
+ const config = await runWizard(nameArg ? { defaultName: nameArg } : undefined);
120
114
  if (!config) {
121
115
  console.log('\nProject creation cancelled.');
122
116
  process.exit(1);
@@ -137,10 +131,15 @@ async function runCreate(_args) {
137
131
  await generateProject(config, outputDir);
138
132
  // Write config file for downstream commands
139
133
  writeConfigFile(outputDir, config);
134
+ // Optional: GitHub repository setup
135
+ const gitResult = await promptGitSetup();
136
+ if (gitResult) {
137
+ await setupGitRepository(outputDir, gitResult.repoUrl, gitResult.pat);
138
+ }
140
139
  // Success message and next steps
141
140
  console.log('');
142
141
  console.log(pc.green('✔') + ` Created ${pc.bold(config.projectName)} successfully!`);
143
- printNextSteps(config.projectName, config.platforms);
142
+ printNextSteps(config.projectName);
144
143
  process.exit(0);
145
144
  }
146
145
  /**