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
|
@@ -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-
|
|
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-
|
|
59
|
-
create-aws-
|
|
60
|
-
create-aws-
|
|
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-
|
|
64
|
-
create-aws-
|
|
65
|
-
create-aws-
|
|
66
|
-
create-aws-
|
|
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-
|
|
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
|
|
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
|
-
|
|
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(
|
|
108
|
+
async function runCreate(args) {
|
|
117
109
|
printWelcome();
|
|
118
110
|
console.log(''); // blank line after banner
|
|
119
|
-
|
|
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
|
|
142
|
+
printNextSteps(config.projectName);
|
|
144
143
|
process.exit(0);
|
|
145
144
|
}
|
|
146
145
|
/**
|