create-aws-project 1.2.1

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.
Files changed (181) hide show
  1. package/README.md +118 -0
  2. package/dist/__tests__/generator/replace-tokens.spec.d.ts +1 -0
  3. package/dist/__tests__/generator/replace-tokens.spec.js +281 -0
  4. package/dist/__tests__/generator.spec.d.ts +1 -0
  5. package/dist/__tests__/generator.spec.js +162 -0
  6. package/dist/__tests__/validation/project-name.spec.d.ts +1 -0
  7. package/dist/__tests__/validation/project-name.spec.js +57 -0
  8. package/dist/__tests__/wizard.spec.d.ts +1 -0
  9. package/dist/__tests__/wizard.spec.js +232 -0
  10. package/dist/aws/iam.d.ts +75 -0
  11. package/dist/aws/iam.js +264 -0
  12. package/dist/aws/organizations.d.ts +79 -0
  13. package/dist/aws/organizations.js +168 -0
  14. package/dist/cli.d.ts +4 -0
  15. package/dist/cli.js +206 -0
  16. package/dist/commands/setup-github.d.ts +4 -0
  17. package/dist/commands/setup-github.js +185 -0
  18. package/dist/generator/copy-file.d.ts +15 -0
  19. package/dist/generator/copy-file.js +56 -0
  20. package/dist/generator/generate-project.d.ts +14 -0
  21. package/dist/generator/generate-project.js +81 -0
  22. package/dist/generator/index.d.ts +4 -0
  23. package/dist/generator/index.js +3 -0
  24. package/dist/generator/replace-tokens.d.ts +29 -0
  25. package/dist/generator/replace-tokens.js +68 -0
  26. package/dist/github/secrets.d.ts +109 -0
  27. package/dist/github/secrets.js +275 -0
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.js +6 -0
  30. package/dist/prompts/auth.d.ts +3 -0
  31. package/dist/prompts/auth.js +23 -0
  32. package/dist/prompts/aws-config.d.ts +2 -0
  33. package/dist/prompts/aws-config.js +14 -0
  34. package/dist/prompts/features.d.ts +2 -0
  35. package/dist/prompts/features.js +10 -0
  36. package/dist/prompts/github-setup.d.ts +53 -0
  37. package/dist/prompts/github-setup.js +208 -0
  38. package/dist/prompts/org-structure.d.ts +9 -0
  39. package/dist/prompts/org-structure.js +93 -0
  40. package/dist/prompts/platforms.d.ts +2 -0
  41. package/dist/prompts/platforms.js +12 -0
  42. package/dist/prompts/project-name.d.ts +2 -0
  43. package/dist/prompts/project-name.js +8 -0
  44. package/dist/prompts/theme.d.ts +2 -0
  45. package/dist/prompts/theme.js +14 -0
  46. package/dist/templates/index.d.ts +4 -0
  47. package/dist/templates/index.js +2 -0
  48. package/dist/templates/manifest.d.ts +11 -0
  49. package/dist/templates/manifest.js +99 -0
  50. package/dist/templates/tokens.d.ts +39 -0
  51. package/dist/templates/tokens.js +37 -0
  52. package/dist/templates/types.d.ts +52 -0
  53. package/dist/templates/types.js +1 -0
  54. package/dist/types.d.ts +27 -0
  55. package/dist/types.js +1 -0
  56. package/dist/validation/project-name.d.ts +1 -0
  57. package/dist/validation/project-name.js +12 -0
  58. package/dist/wizard.d.ts +2 -0
  59. package/dist/wizard.js +81 -0
  60. package/package.json +68 -0
  61. package/templates/.github/actions/build-and-test/action.yml +24 -0
  62. package/templates/.github/actions/deploy-cdk/action.yml +46 -0
  63. package/templates/.github/actions/deploy-web/action.yml +72 -0
  64. package/templates/.github/actions/setup/action.yml +29 -0
  65. package/templates/.github/pull_request_template.md +15 -0
  66. package/templates/.github/workflows/deploy-dev.yml +80 -0
  67. package/templates/.github/workflows/deploy-prod.yml +67 -0
  68. package/templates/.github/workflows/deploy-stage.yml +77 -0
  69. package/templates/.github/workflows/pull-request.yml +72 -0
  70. package/templates/.vscode/extensions.json +7 -0
  71. package/templates/.vscode/settings.json +67 -0
  72. package/templates/apps/api/.eslintrc.json +18 -0
  73. package/templates/apps/api/cdk/app.ts +93 -0
  74. package/templates/apps/api/cdk/auth/cognito-stack.ts +164 -0
  75. package/templates/apps/api/cdk/cdk.json +73 -0
  76. package/templates/apps/api/cdk/deployment-user-stack.ts +187 -0
  77. package/templates/apps/api/cdk/org-stack.ts +67 -0
  78. package/templates/apps/api/cdk/static-stack.ts +361 -0
  79. package/templates/apps/api/cdk/tsconfig.json +39 -0
  80. package/templates/apps/api/cdk/user-stack.ts +255 -0
  81. package/templates/apps/api/jest.config.ts +38 -0
  82. package/templates/apps/api/lambdas.yml +84 -0
  83. package/templates/apps/api/project.json.template +58 -0
  84. package/templates/apps/api/src/__tests__/setup.ts +10 -0
  85. package/templates/apps/api/src/handlers/users/create-user.ts +52 -0
  86. package/templates/apps/api/src/handlers/users/delete-user.ts +45 -0
  87. package/templates/apps/api/src/handlers/users/get-me.ts +72 -0
  88. package/templates/apps/api/src/handlers/users/get-user.ts +45 -0
  89. package/templates/apps/api/src/handlers/users/get-users.ts +23 -0
  90. package/templates/apps/api/src/handlers/users/index.ts +17 -0
  91. package/templates/apps/api/src/handlers/users/update-user.ts +72 -0
  92. package/templates/apps/api/src/lib/dynamo/dynamo-model.ts +504 -0
  93. package/templates/apps/api/src/lib/dynamo/index.ts +12 -0
  94. package/templates/apps/api/src/lib/dynamo/utils.ts +39 -0
  95. package/templates/apps/api/src/middleware/auth0-auth.ts +97 -0
  96. package/templates/apps/api/src/middleware/cognito-auth.ts +90 -0
  97. package/templates/apps/api/src/models/UserModel.ts +109 -0
  98. package/templates/apps/api/src/schemas/user.schema.ts +44 -0
  99. package/templates/apps/api/src/services/user-service.ts +108 -0
  100. package/templates/apps/api/src/utils/auth-context.ts +60 -0
  101. package/templates/apps/api/src/utils/common/helpers.ts +26 -0
  102. package/templates/apps/api/src/utils/lambda-handler.ts +148 -0
  103. package/templates/apps/api/src/utils/response.ts +52 -0
  104. package/templates/apps/api/src/utils/validator.ts +75 -0
  105. package/templates/apps/api/tsconfig.app.json +15 -0
  106. package/templates/apps/api/tsconfig.json +19 -0
  107. package/templates/apps/api/tsconfig.spec.json +17 -0
  108. package/templates/apps/mobile/.env.example +5 -0
  109. package/templates/apps/mobile/.eslintrc.json +33 -0
  110. package/templates/apps/mobile/app.json +33 -0
  111. package/templates/apps/mobile/assets/.gitkeep +0 -0
  112. package/templates/apps/mobile/babel.config.js +19 -0
  113. package/templates/apps/mobile/index.js +7 -0
  114. package/templates/apps/mobile/jest.config.ts +22 -0
  115. package/templates/apps/mobile/metro.config.js +35 -0
  116. package/templates/apps/mobile/package.json +22 -0
  117. package/templates/apps/mobile/project.json.template +64 -0
  118. package/templates/apps/mobile/src/App.tsx +367 -0
  119. package/templates/apps/mobile/src/__tests__/App.spec.tsx +46 -0
  120. package/templates/apps/mobile/src/__tests__/store/user-store.spec.ts +156 -0
  121. package/templates/apps/mobile/src/config/api.ts +16 -0
  122. package/templates/apps/mobile/src/store/user-store.ts +56 -0
  123. package/templates/apps/mobile/src/test-setup.ts +10 -0
  124. package/templates/apps/mobile/tsconfig.json +22 -0
  125. package/templates/apps/web/.env.example +13 -0
  126. package/templates/apps/web/.eslintrc.json +26 -0
  127. package/templates/apps/web/index.html +13 -0
  128. package/templates/apps/web/jest.config.ts +24 -0
  129. package/templates/apps/web/package.json +15 -0
  130. package/templates/apps/web/project.json.template +66 -0
  131. package/templates/apps/web/src/App.tsx +352 -0
  132. package/templates/apps/web/src/__mocks__/config/api.ts +41 -0
  133. package/templates/apps/web/src/__tests__/App.spec.tsx +240 -0
  134. package/templates/apps/web/src/__tests__/store/user-store.spec.ts +185 -0
  135. package/templates/apps/web/src/auth/auth0-provider.tsx +103 -0
  136. package/templates/apps/web/src/auth/cognito-provider.tsx +143 -0
  137. package/templates/apps/web/src/auth/index.ts +7 -0
  138. package/templates/apps/web/src/auth/use-auth.ts +16 -0
  139. package/templates/apps/web/src/config/amplify-config.ts +31 -0
  140. package/templates/apps/web/src/config/api.ts +38 -0
  141. package/templates/apps/web/src/config/auth0-config.ts +17 -0
  142. package/templates/apps/web/src/main.tsx +41 -0
  143. package/templates/apps/web/src/store/user-store.ts +56 -0
  144. package/templates/apps/web/src/styles.css +165 -0
  145. package/templates/apps/web/src/test-setup.ts +1 -0
  146. package/templates/apps/web/src/theme/index.ts +30 -0
  147. package/templates/apps/web/src/vite-env.d.ts +19 -0
  148. package/templates/apps/web/tsconfig.app.json +24 -0
  149. package/templates/apps/web/tsconfig.json +22 -0
  150. package/templates/apps/web/tsconfig.spec.json +28 -0
  151. package/templates/apps/web/vite.config.ts +87 -0
  152. package/templates/manifest.json +28 -0
  153. package/templates/packages/api-client/.eslintrc.json +18 -0
  154. package/templates/packages/api-client/jest.config.ts +13 -0
  155. package/templates/packages/api-client/package.json +8 -0
  156. package/templates/packages/api-client/project.json.template +34 -0
  157. package/templates/packages/api-client/src/__tests__/api-client.spec.ts +408 -0
  158. package/templates/packages/api-client/src/api-client.ts +201 -0
  159. package/templates/packages/api-client/src/config.ts +193 -0
  160. package/templates/packages/api-client/src/index.ts +9 -0
  161. package/templates/packages/api-client/tsconfig.json +22 -0
  162. package/templates/packages/api-client/tsconfig.lib.json +11 -0
  163. package/templates/packages/api-client/tsconfig.spec.json +14 -0
  164. package/templates/packages/common-types/.eslintrc.json +18 -0
  165. package/templates/packages/common-types/package.json +6 -0
  166. package/templates/packages/common-types/project.json.template +26 -0
  167. package/templates/packages/common-types/src/api.types.ts +24 -0
  168. package/templates/packages/common-types/src/auth.types.ts +36 -0
  169. package/templates/packages/common-types/src/common.types.ts +46 -0
  170. package/templates/packages/common-types/src/index.ts +19 -0
  171. package/templates/packages/common-types/src/lambda.types.ts +39 -0
  172. package/templates/packages/common-types/src/user.types.ts +31 -0
  173. package/templates/packages/common-types/tsconfig.json +19 -0
  174. package/templates/packages/common-types/tsconfig.lib.json +11 -0
  175. package/templates/root/.editorconfig +23 -0
  176. package/templates/root/.nvmrc +1 -0
  177. package/templates/root/eslint.config.js +61 -0
  178. package/templates/root/jest.preset.js +16 -0
  179. package/templates/root/nx.json +29 -0
  180. package/templates/root/package.json +131 -0
  181. package/templates/root/tsconfig.base.json +29 -0
@@ -0,0 +1,232 @@
1
+ import { describe, it, expect, jest, beforeEach } from '@jest/globals';
2
+ // Create a mock function for prompts
3
+ const mockPrompts = jest.fn();
4
+ // Mock prompts module
5
+ jest.unstable_mockModule('prompts', () => ({
6
+ __esModule: true,
7
+ default: mockPrompts,
8
+ }));
9
+ // Mock picocolors to avoid console output issues
10
+ jest.unstable_mockModule('picocolors', () => ({
11
+ __esModule: true,
12
+ default: {
13
+ red: (s) => s,
14
+ green: (s) => s,
15
+ blue: (s) => s,
16
+ yellow: (s) => s,
17
+ cyan: (s) => s,
18
+ magenta: (s) => s,
19
+ bold: (s) => s,
20
+ dim: (s) => s,
21
+ },
22
+ }));
23
+ // Dynamic import after mocking
24
+ const { runWizard } = await import('../wizard.js');
25
+ describe('runWizard', () => {
26
+ beforeEach(() => {
27
+ jest.clearAllMocks();
28
+ // Mock process.exit to prevent test from exiting
29
+ jest.spyOn(process, 'exit').mockImplementation((() => {
30
+ throw new Error('process.exit called');
31
+ }));
32
+ });
33
+ describe('successful completion', () => {
34
+ it('should return complete ProjectConfig when all prompts answered', async () => {
35
+ mockPrompts.mockResolvedValueOnce({
36
+ projectName: 'my-test-app',
37
+ platforms: ['web', 'api'],
38
+ authProvider: 'none',
39
+ authFeatures: [],
40
+ features: ['github-actions'],
41
+ awsRegion: 'us-east-1',
42
+ brandColor: 'blue',
43
+ });
44
+ const result = await runWizard();
45
+ expect(result).not.toBeNull();
46
+ expect(result).toEqual({
47
+ projectName: 'my-test-app',
48
+ platforms: ['web', 'api'],
49
+ features: ['github-actions'],
50
+ awsRegion: 'us-east-1',
51
+ brandColor: 'blue',
52
+ auth: { provider: 'none', features: [] },
53
+ });
54
+ });
55
+ it('should return config with all platforms selected', async () => {
56
+ mockPrompts.mockResolvedValueOnce({
57
+ projectName: 'full-stack-app',
58
+ platforms: ['web', 'mobile', 'api'],
59
+ authProvider: 'none',
60
+ authFeatures: [],
61
+ features: ['github-actions', 'vscode-config'],
62
+ awsRegion: 'eu-west-1',
63
+ brandColor: 'purple',
64
+ });
65
+ const result = await runWizard();
66
+ expect(result).not.toBeNull();
67
+ expect(result?.platforms).toEqual(['web', 'mobile', 'api']);
68
+ expect(result?.features).toEqual(['github-actions', 'vscode-config']);
69
+ });
70
+ it('should default features to empty array when none selected', async () => {
71
+ mockPrompts.mockResolvedValueOnce({
72
+ projectName: 'minimal-app',
73
+ platforms: ['api'],
74
+ authProvider: 'none',
75
+ authFeatures: [],
76
+ features: undefined, // No features selected
77
+ awsRegion: 'ap-southeast-1',
78
+ brandColor: 'teal',
79
+ });
80
+ const result = await runWizard();
81
+ expect(result).not.toBeNull();
82
+ expect(result?.features).toEqual([]);
83
+ });
84
+ });
85
+ describe('incomplete responses', () => {
86
+ it('should return null when projectName is missing', async () => {
87
+ mockPrompts.mockResolvedValueOnce({
88
+ projectName: undefined,
89
+ platforms: ['web'],
90
+ awsRegion: 'us-east-1',
91
+ brandColor: 'blue',
92
+ });
93
+ const result = await runWizard();
94
+ expect(result).toBeNull();
95
+ });
96
+ it('should return null when platforms is empty', async () => {
97
+ mockPrompts.mockResolvedValueOnce({
98
+ projectName: 'my-app',
99
+ platforms: [],
100
+ awsRegion: 'us-east-1',
101
+ brandColor: 'blue',
102
+ });
103
+ const result = await runWizard();
104
+ expect(result).toBeNull();
105
+ });
106
+ it('should return null when awsRegion is missing', async () => {
107
+ mockPrompts.mockResolvedValueOnce({
108
+ projectName: 'my-app',
109
+ platforms: ['web'],
110
+ awsRegion: undefined,
111
+ brandColor: 'blue',
112
+ });
113
+ const result = await runWizard();
114
+ expect(result).toBeNull();
115
+ });
116
+ it('should return null when brandColor is missing', async () => {
117
+ mockPrompts.mockResolvedValueOnce({
118
+ projectName: 'my-app',
119
+ platforms: ['web'],
120
+ awsRegion: 'us-east-1',
121
+ brandColor: undefined,
122
+ });
123
+ const result = await runWizard();
124
+ expect(result).toBeNull();
125
+ });
126
+ });
127
+ describe('cancellation', () => {
128
+ it('should exit when user cancels via onCancel', async () => {
129
+ // Simulate prompts calling onCancel by storing the callback and calling it
130
+ let storedOnCancel;
131
+ mockPrompts.mockImplementationOnce(((_questions, options) => {
132
+ storedOnCancel = options?.onCancel;
133
+ // Trigger onCancel before returning
134
+ if (storedOnCancel) {
135
+ storedOnCancel({}, {});
136
+ }
137
+ return Promise.resolve({});
138
+ }));
139
+ await expect(runWizard()).rejects.toThrow('process.exit called');
140
+ expect(process.exit).toHaveBeenCalledWith(0);
141
+ });
142
+ });
143
+ describe('config structure verification', () => {
144
+ it('should return config with all expected fields', async () => {
145
+ mockPrompts.mockResolvedValueOnce({
146
+ projectName: 'test-app',
147
+ platforms: ['web'],
148
+ authProvider: 'none',
149
+ authFeatures: [],
150
+ features: [],
151
+ awsRegion: 'us-east-1',
152
+ brandColor: 'green',
153
+ });
154
+ const result = await runWizard();
155
+ expect(result).not.toBeNull();
156
+ expect(result).toHaveProperty('projectName');
157
+ expect(result).toHaveProperty('platforms');
158
+ expect(result).toHaveProperty('features');
159
+ expect(result).toHaveProperty('awsRegion');
160
+ expect(result).toHaveProperty('brandColor');
161
+ expect(result).toHaveProperty('auth');
162
+ });
163
+ it('should pass correct prompts to prompts library', async () => {
164
+ mockPrompts.mockResolvedValueOnce({
165
+ projectName: 'my-app',
166
+ platforms: ['web'],
167
+ authProvider: 'none',
168
+ authFeatures: [],
169
+ features: [],
170
+ awsRegion: 'us-east-1',
171
+ brandColor: 'orange',
172
+ });
173
+ await runWizard();
174
+ expect(mockPrompts).toHaveBeenCalledTimes(1);
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)
178
+ expect(Array.isArray(promptsArg)).toBe(true);
179
+ expect(promptsArg.length).toBe(15);
180
+ });
181
+ });
182
+ describe('auth configuration', () => {
183
+ it('should return ProjectConfig with Cognito auth and features', async () => {
184
+ mockPrompts.mockResolvedValueOnce({
185
+ projectName: 'auth-test-app',
186
+ platforms: ['web', 'api'],
187
+ authProvider: 'cognito',
188
+ authFeatures: ['social-login', 'mfa'],
189
+ features: [],
190
+ awsRegion: 'us-east-1',
191
+ brandColor: 'blue',
192
+ });
193
+ const result = await runWizard();
194
+ expect(result?.auth).toEqual({
195
+ provider: 'cognito',
196
+ features: ['social-login', 'mfa'],
197
+ });
198
+ });
199
+ it('should return ProjectConfig with Auth0 auth and no features', async () => {
200
+ mockPrompts.mockResolvedValueOnce({
201
+ projectName: 'auth0-app',
202
+ platforms: ['web'],
203
+ authProvider: 'auth0',
204
+ authFeatures: [],
205
+ features: ['github-actions'],
206
+ awsRegion: 'eu-west-1',
207
+ brandColor: 'purple',
208
+ });
209
+ const result = await runWizard();
210
+ expect(result?.auth).toEqual({
211
+ provider: 'auth0',
212
+ features: [],
213
+ });
214
+ });
215
+ it('should default auth to none when not selected', async () => {
216
+ mockPrompts.mockResolvedValueOnce({
217
+ projectName: 'no-auth-app',
218
+ platforms: ['api'],
219
+ authProvider: 'none',
220
+ // authFeatures not returned when provider is 'none'
221
+ features: [],
222
+ awsRegion: 'us-west-2',
223
+ brandColor: 'teal',
224
+ });
225
+ const result = await runWizard();
226
+ expect(result?.auth).toEqual({
227
+ provider: 'none',
228
+ features: [],
229
+ });
230
+ });
231
+ });
232
+ });
@@ -0,0 +1,75 @@
1
+ import { IAMClient } from '@aws-sdk/client-iam';
2
+ /**
3
+ * AWS IAM service module
4
+ *
5
+ * Provides functions to create IAM deployment users with least-privilege
6
+ * policies for CDK deployment to specific environment accounts.
7
+ */
8
+ /**
9
+ * Creates a configured IAMClient
10
+ * @param region - AWS region (defaults to us-east-1)
11
+ * @returns Configured IAMClient instance
12
+ */
13
+ export declare function createIAMClient(region?: string): IAMClient;
14
+ /**
15
+ * Creates an IAM deployment user with path /deployment/
16
+ * @param client - IAMClient instance
17
+ * @param userName - User name (format: {project}-{environment}-deploy)
18
+ * @throws Error if user creation fails (unless user already exists)
19
+ */
20
+ export declare function createDeploymentUser(client: IAMClient, userName: string): Promise<void>;
21
+ /**
22
+ * Creates a customer managed policy with minimal CDK deployment permissions
23
+ * @param client - IAMClient instance
24
+ * @param policyName - Policy name (format: {project}-{environment}-cdk-deploy)
25
+ * @param accountId - AWS account ID for resource ARNs
26
+ * @returns Policy ARN
27
+ * @throws Error if policy creation fails (unless policy already exists)
28
+ */
29
+ export declare function createCDKDeploymentPolicy(client: IAMClient, policyName: string, accountId: string): Promise<string>;
30
+ /**
31
+ * Attaches an IAM policy to a user
32
+ * @param client - IAMClient instance
33
+ * @param userName - IAM user name
34
+ * @param policyArn - Policy ARN to attach
35
+ */
36
+ export declare function attachPolicyToUser(client: IAMClient, userName: string, policyArn: string): Promise<void>;
37
+ /**
38
+ * Access key credentials for a deployment user
39
+ */
40
+ export interface AccessKeyCredentials {
41
+ accessKeyId: string;
42
+ secretAccessKey: string;
43
+ }
44
+ /**
45
+ * Creates access key credentials for an IAM user
46
+ * @param client - IAMClient instance
47
+ * @param userName - IAM user name
48
+ * @returns Access key ID and secret access key
49
+ * @note The secret access key is ONLY available at creation time
50
+ */
51
+ export declare function createAccessKey(client: IAMClient, userName: string): Promise<AccessKeyCredentials>;
52
+ /**
53
+ * Complete deployment user credentials
54
+ */
55
+ export interface DeploymentUserCredentials {
56
+ userName: string;
57
+ accessKeyId: string;
58
+ secretAccessKey: string;
59
+ }
60
+ /**
61
+ * Creates a deployment user with all required resources and returns credentials
62
+ *
63
+ * Orchestrates the full workflow:
64
+ * 1. Create IAM user with path /deployment/
65
+ * 2. Create CDK deployment policy with least-privilege permissions
66
+ * 3. Attach policy to user
67
+ * 4. Create and return access key credentials
68
+ *
69
+ * @param client - IAMClient instance
70
+ * @param projectName - Project name for resource naming
71
+ * @param environment - Environment name (dev, stage, prod)
72
+ * @param accountId - AWS account ID for policy resource ARNs
73
+ * @returns User name and access credentials for GitHub secrets
74
+ */
75
+ export declare function createDeploymentUserWithCredentials(client: IAMClient, projectName: string, environment: string, accountId: string): Promise<DeploymentUserCredentials>;
@@ -0,0 +1,264 @@
1
+ import { IAMClient, CreateUserCommand, GetUserCommand, CreatePolicyCommand, GetPolicyCommand, AttachUserPolicyCommand, CreateAccessKeyCommand, NoSuchEntityException, } from '@aws-sdk/client-iam';
2
+ import pc from 'picocolors';
3
+ /**
4
+ * AWS IAM service module
5
+ *
6
+ * Provides functions to create IAM deployment users with least-privilege
7
+ * policies for CDK deployment to specific environment accounts.
8
+ */
9
+ /**
10
+ * Creates a configured IAMClient
11
+ * @param region - AWS region (defaults to us-east-1)
12
+ * @returns Configured IAMClient instance
13
+ */
14
+ export function createIAMClient(region = 'us-east-1') {
15
+ return new IAMClient({ region });
16
+ }
17
+ /**
18
+ * Checks if an IAM user exists
19
+ * @param client - IAMClient instance
20
+ * @param userName - User name to check
21
+ * @returns true if user exists, false otherwise
22
+ */
23
+ async function userExists(client, userName) {
24
+ try {
25
+ const command = new GetUserCommand({ UserName: userName });
26
+ await client.send(command);
27
+ return true;
28
+ }
29
+ catch (error) {
30
+ if (error instanceof NoSuchEntityException) {
31
+ return false;
32
+ }
33
+ throw error;
34
+ }
35
+ }
36
+ /**
37
+ * Checks if an IAM policy exists
38
+ * @param client - IAMClient instance
39
+ * @param policyArn - Policy ARN to check
40
+ * @returns true if policy exists, false otherwise
41
+ */
42
+ async function policyExists(client, policyArn) {
43
+ try {
44
+ const command = new GetPolicyCommand({ PolicyArn: policyArn });
45
+ await client.send(command);
46
+ return true;
47
+ }
48
+ catch (error) {
49
+ if (error instanceof NoSuchEntityException) {
50
+ return false;
51
+ }
52
+ throw error;
53
+ }
54
+ }
55
+ /**
56
+ * Creates an IAM deployment user with path /deployment/
57
+ * @param client - IAMClient instance
58
+ * @param userName - User name (format: {project}-{environment}-deploy)
59
+ * @throws Error if user creation fails (unless user already exists)
60
+ */
61
+ export async function createDeploymentUser(client, userName) {
62
+ // Check if user already exists
63
+ if (await userExists(client, userName)) {
64
+ console.log(pc.yellow(` User ${userName} already exists, reusing`));
65
+ return;
66
+ }
67
+ const command = new CreateUserCommand({
68
+ UserName: userName,
69
+ Path: '/deployment/',
70
+ Tags: [
71
+ { Key: 'Purpose', Value: 'CDK Deployment' },
72
+ { Key: 'ManagedBy', Value: 'create-aws-starter-kit' },
73
+ ],
74
+ });
75
+ await client.send(command);
76
+ console.log(pc.green(` Created IAM user: ${userName}`));
77
+ }
78
+ /**
79
+ * CDK deployment policy document with minimal permissions
80
+ * @param accountId - AWS account ID for resource ARNs
81
+ * @returns Policy document JSON string
82
+ */
83
+ function getCDKDeploymentPolicyDocument(accountId) {
84
+ const policy = {
85
+ Version: '2012-10-17',
86
+ Statement: [
87
+ {
88
+ Sid: 'CloudFormationFullAccess',
89
+ Effect: 'Allow',
90
+ Action: 'cloudformation:*',
91
+ Resource: '*',
92
+ },
93
+ {
94
+ Sid: 'CDKBootstrapBucket',
95
+ Effect: 'Allow',
96
+ Action: 's3:*',
97
+ Resource: [
98
+ `arn:aws:s3:::cdk-*-assets-${accountId}-*`,
99
+ `arn:aws:s3:::cdk-*-assets-${accountId}-*/*`,
100
+ ],
101
+ },
102
+ {
103
+ Sid: 'CDKRoleAssumption',
104
+ Effect: 'Allow',
105
+ Action: [
106
+ 'iam:PassRole',
107
+ 'sts:AssumeRole',
108
+ ],
109
+ Resource: [
110
+ `arn:aws:iam::${accountId}:role/cdk-*`,
111
+ ],
112
+ },
113
+ {
114
+ Sid: 'SSMContextLookup',
115
+ Effect: 'Allow',
116
+ Action: 'ssm:GetParameter',
117
+ Resource: `arn:aws:ssm:*:${accountId}:parameter/cdk-bootstrap/*`,
118
+ },
119
+ {
120
+ Sid: 'LambdaDeployment',
121
+ Effect: 'Allow',
122
+ Action: 'lambda:*',
123
+ Resource: '*',
124
+ },
125
+ {
126
+ Sid: 'APIGatewayDeployment',
127
+ Effect: 'Allow',
128
+ Action: 'apigateway:*',
129
+ Resource: '*',
130
+ },
131
+ {
132
+ Sid: 'DynamoDBDeployment',
133
+ Effect: 'Allow',
134
+ Action: 'dynamodb:*',
135
+ Resource: '*',
136
+ },
137
+ {
138
+ Sid: 'CloudFrontDeployment',
139
+ Effect: 'Allow',
140
+ Action: 'cloudfront:*',
141
+ Resource: '*',
142
+ },
143
+ {
144
+ Sid: 'CognitoDeployment',
145
+ Effect: 'Allow',
146
+ Action: 'cognito-idp:*',
147
+ Resource: '*',
148
+ },
149
+ {
150
+ Sid: 'ECRAccess',
151
+ Effect: 'Allow',
152
+ Action: [
153
+ 'ecr:GetAuthorizationToken',
154
+ 'ecr:BatchCheckLayerAvailability',
155
+ 'ecr:GetDownloadUrlForLayer',
156
+ 'ecr:BatchGetImage',
157
+ ],
158
+ Resource: '*',
159
+ },
160
+ ],
161
+ };
162
+ return JSON.stringify(policy);
163
+ }
164
+ /**
165
+ * Creates a customer managed policy with minimal CDK deployment permissions
166
+ * @param client - IAMClient instance
167
+ * @param policyName - Policy name (format: {project}-{environment}-cdk-deploy)
168
+ * @param accountId - AWS account ID for resource ARNs
169
+ * @returns Policy ARN
170
+ * @throws Error if policy creation fails (unless policy already exists)
171
+ */
172
+ export async function createCDKDeploymentPolicy(client, policyName, accountId) {
173
+ // Construct the expected policy ARN
174
+ const expectedPolicyArn = `arn:aws:iam::${accountId}:policy/${policyName}`;
175
+ // Check if policy already exists
176
+ if (await policyExists(client, expectedPolicyArn)) {
177
+ console.log(pc.yellow(` Policy ${policyName} already exists, reusing`));
178
+ return expectedPolicyArn;
179
+ }
180
+ const command = new CreatePolicyCommand({
181
+ PolicyName: policyName,
182
+ PolicyDocument: getCDKDeploymentPolicyDocument(accountId),
183
+ Description: `CDK deployment policy for ${policyName.replace('-cdk-deploy', '')}`,
184
+ Tags: [
185
+ { Key: 'Purpose', Value: 'CDK Deployment' },
186
+ { Key: 'ManagedBy', Value: 'create-aws-starter-kit' },
187
+ ],
188
+ });
189
+ const response = await client.send(command);
190
+ if (!response.Policy?.Arn) {
191
+ throw new Error('Policy created but no ARN returned');
192
+ }
193
+ console.log(pc.green(` Created policy: ${policyName}`));
194
+ return response.Policy.Arn;
195
+ }
196
+ /**
197
+ * Attaches an IAM policy to a user
198
+ * @param client - IAMClient instance
199
+ * @param userName - IAM user name
200
+ * @param policyArn - Policy ARN to attach
201
+ */
202
+ export async function attachPolicyToUser(client, userName, policyArn) {
203
+ const command = new AttachUserPolicyCommand({
204
+ UserName: userName,
205
+ PolicyArn: policyArn,
206
+ });
207
+ await client.send(command);
208
+ console.log(pc.green(` Attached policy to user ${userName}`));
209
+ }
210
+ /**
211
+ * Creates access key credentials for an IAM user
212
+ * @param client - IAMClient instance
213
+ * @param userName - IAM user name
214
+ * @returns Access key ID and secret access key
215
+ * @note The secret access key is ONLY available at creation time
216
+ */
217
+ export async function createAccessKey(client, userName) {
218
+ const command = new CreateAccessKeyCommand({
219
+ UserName: userName,
220
+ });
221
+ const response = await client.send(command);
222
+ if (!response.AccessKey?.AccessKeyId || !response.AccessKey?.SecretAccessKey) {
223
+ throw new Error('Access key created but credentials not returned');
224
+ }
225
+ console.log(pc.green(` Created access key for ${userName}`));
226
+ return {
227
+ accessKeyId: response.AccessKey.AccessKeyId,
228
+ secretAccessKey: response.AccessKey.SecretAccessKey,
229
+ };
230
+ }
231
+ /**
232
+ * Creates a deployment user with all required resources and returns credentials
233
+ *
234
+ * Orchestrates the full workflow:
235
+ * 1. Create IAM user with path /deployment/
236
+ * 2. Create CDK deployment policy with least-privilege permissions
237
+ * 3. Attach policy to user
238
+ * 4. Create and return access key credentials
239
+ *
240
+ * @param client - IAMClient instance
241
+ * @param projectName - Project name for resource naming
242
+ * @param environment - Environment name (dev, stage, prod)
243
+ * @param accountId - AWS account ID for policy resource ARNs
244
+ * @returns User name and access credentials for GitHub secrets
245
+ */
246
+ export async function createDeploymentUserWithCredentials(client, projectName, environment, accountId) {
247
+ const userName = `${projectName}-${environment}-deploy`;
248
+ const policyName = `${projectName}-${environment}-cdk-deploy`;
249
+ console.log(pc.cyan(`\nCreating deployment user for ${environment}...`));
250
+ // Step 1: Create the deployment user
251
+ await createDeploymentUser(client, userName);
252
+ // Step 2: Create the CDK deployment policy
253
+ const policyArn = await createCDKDeploymentPolicy(client, policyName, accountId);
254
+ // Step 3: Attach policy to user
255
+ await attachPolicyToUser(client, userName, policyArn);
256
+ // Step 4: Create and return access credentials
257
+ const credentials = await createAccessKey(client, userName);
258
+ console.log(pc.green(`\nDeployment user ${userName} ready with credentials`));
259
+ return {
260
+ userName,
261
+ accessKeyId: credentials.accessKeyId,
262
+ secretAccessKey: credentials.secretAccessKey,
263
+ };
264
+ }
@@ -0,0 +1,79 @@
1
+ import { OrganizationsClient } from '@aws-sdk/client-organizations';
2
+ /**
3
+ * AWS Organizations service module
4
+ *
5
+ * Provides functions to create AWS Organizations and member accounts
6
+ * programmatically during project generation.
7
+ */
8
+ /**
9
+ * Creates a configured OrganizationsClient
10
+ * @param region - AWS region (defaults to us-east-1 as Organizations API is global)
11
+ * @returns Configured OrganizationsClient instance
12
+ */
13
+ export declare function createOrganizationsClient(region?: string): OrganizationsClient;
14
+ /**
15
+ * Checks if the current AWS account is already part of an organization
16
+ * @param client - OrganizationsClient instance
17
+ * @returns Organization ID if exists, null if not in an organization
18
+ */
19
+ export declare function checkExistingOrganization(client: OrganizationsClient): Promise<string | null>;
20
+ /**
21
+ * Creates a new AWS Organization with all features enabled
22
+ * @param client - OrganizationsClient instance
23
+ * @returns Organization ID of the newly created organization
24
+ * @throws Error if organization creation fails (except for already existing)
25
+ */
26
+ export declare function createOrganization(client: OrganizationsClient): Promise<string>;
27
+ /**
28
+ * Result of an account creation request
29
+ */
30
+ export interface CreateAccountResult {
31
+ requestId: string;
32
+ }
33
+ /**
34
+ * Creates a new AWS account within the organization
35
+ * @param client - OrganizationsClient instance
36
+ * @param email - Email address for the new account's root user
37
+ * @param accountName - Display name for the account
38
+ * @returns CreateAccountStatus ID for polling
39
+ */
40
+ export declare function createAccount(client: OrganizationsClient, email: string, accountName: string): Promise<CreateAccountResult>;
41
+ /**
42
+ * Result of a completed account creation
43
+ */
44
+ export interface AccountCreationResult {
45
+ accountId: string;
46
+ accountName: string;
47
+ }
48
+ /**
49
+ * Polls the account creation status until completion or timeout
50
+ * @param client - OrganizationsClient instance
51
+ * @param createAccountRequestId - The request ID from createAccount
52
+ * @param timeoutMs - Maximum time to wait in milliseconds (default: 5 minutes)
53
+ * @returns Account ID when creation succeeds
54
+ * @throws Error if creation fails or times out
55
+ */
56
+ export declare function waitForAccountCreation(client: OrganizationsClient, createAccountRequestId: string, timeoutMs?: number): Promise<AccountCreationResult>;
57
+ /**
58
+ * Account configuration for environment creation
59
+ */
60
+ export interface EnvironmentAccountConfig {
61
+ environment: string;
62
+ email: string;
63
+ }
64
+ /**
65
+ * Result of environment account creation
66
+ */
67
+ export interface EnvironmentAccountResult {
68
+ environment: string;
69
+ email: string;
70
+ accountId: string;
71
+ }
72
+ /**
73
+ * Creates multiple environment accounts sequentially
74
+ * @param client - OrganizationsClient instance
75
+ * @param orgName - Organization name prefix for account names
76
+ * @param accounts - Array of environment and email configurations
77
+ * @returns Array of created account results
78
+ */
79
+ export declare function createEnvironmentAccounts(client: OrganizationsClient, orgName: string, accounts: EnvironmentAccountConfig[]): Promise<EnvironmentAccountResult[]>;