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.
- package/README.md +118 -0
- package/dist/__tests__/generator/replace-tokens.spec.d.ts +1 -0
- package/dist/__tests__/generator/replace-tokens.spec.js +281 -0
- package/dist/__tests__/generator.spec.d.ts +1 -0
- package/dist/__tests__/generator.spec.js +162 -0
- package/dist/__tests__/validation/project-name.spec.d.ts +1 -0
- package/dist/__tests__/validation/project-name.spec.js +57 -0
- package/dist/__tests__/wizard.spec.d.ts +1 -0
- package/dist/__tests__/wizard.spec.js +232 -0
- package/dist/aws/iam.d.ts +75 -0
- package/dist/aws/iam.js +264 -0
- package/dist/aws/organizations.d.ts +79 -0
- package/dist/aws/organizations.js +168 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +206 -0
- package/dist/commands/setup-github.d.ts +4 -0
- package/dist/commands/setup-github.js +185 -0
- package/dist/generator/copy-file.d.ts +15 -0
- package/dist/generator/copy-file.js +56 -0
- package/dist/generator/generate-project.d.ts +14 -0
- package/dist/generator/generate-project.js +81 -0
- package/dist/generator/index.d.ts +4 -0
- package/dist/generator/index.js +3 -0
- package/dist/generator/replace-tokens.d.ts +29 -0
- package/dist/generator/replace-tokens.js +68 -0
- package/dist/github/secrets.d.ts +109 -0
- package/dist/github/secrets.js +275 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/prompts/auth.d.ts +3 -0
- package/dist/prompts/auth.js +23 -0
- package/dist/prompts/aws-config.d.ts +2 -0
- package/dist/prompts/aws-config.js +14 -0
- package/dist/prompts/features.d.ts +2 -0
- package/dist/prompts/features.js +10 -0
- package/dist/prompts/github-setup.d.ts +53 -0
- package/dist/prompts/github-setup.js +208 -0
- package/dist/prompts/org-structure.d.ts +9 -0
- package/dist/prompts/org-structure.js +93 -0
- package/dist/prompts/platforms.d.ts +2 -0
- package/dist/prompts/platforms.js +12 -0
- package/dist/prompts/project-name.d.ts +2 -0
- package/dist/prompts/project-name.js +8 -0
- package/dist/prompts/theme.d.ts +2 -0
- package/dist/prompts/theme.js +14 -0
- package/dist/templates/index.d.ts +4 -0
- package/dist/templates/index.js +2 -0
- package/dist/templates/manifest.d.ts +11 -0
- package/dist/templates/manifest.js +99 -0
- package/dist/templates/tokens.d.ts +39 -0
- package/dist/templates/tokens.js +37 -0
- package/dist/templates/types.d.ts +52 -0
- package/dist/templates/types.js +1 -0
- package/dist/types.d.ts +27 -0
- package/dist/types.js +1 -0
- package/dist/validation/project-name.d.ts +1 -0
- package/dist/validation/project-name.js +12 -0
- package/dist/wizard.d.ts +2 -0
- package/dist/wizard.js +81 -0
- package/package.json +68 -0
- package/templates/.github/actions/build-and-test/action.yml +24 -0
- package/templates/.github/actions/deploy-cdk/action.yml +46 -0
- package/templates/.github/actions/deploy-web/action.yml +72 -0
- package/templates/.github/actions/setup/action.yml +29 -0
- package/templates/.github/pull_request_template.md +15 -0
- package/templates/.github/workflows/deploy-dev.yml +80 -0
- package/templates/.github/workflows/deploy-prod.yml +67 -0
- package/templates/.github/workflows/deploy-stage.yml +77 -0
- package/templates/.github/workflows/pull-request.yml +72 -0
- package/templates/.vscode/extensions.json +7 -0
- package/templates/.vscode/settings.json +67 -0
- package/templates/apps/api/.eslintrc.json +18 -0
- package/templates/apps/api/cdk/app.ts +93 -0
- package/templates/apps/api/cdk/auth/cognito-stack.ts +164 -0
- package/templates/apps/api/cdk/cdk.json +73 -0
- package/templates/apps/api/cdk/deployment-user-stack.ts +187 -0
- package/templates/apps/api/cdk/org-stack.ts +67 -0
- package/templates/apps/api/cdk/static-stack.ts +361 -0
- package/templates/apps/api/cdk/tsconfig.json +39 -0
- package/templates/apps/api/cdk/user-stack.ts +255 -0
- package/templates/apps/api/jest.config.ts +38 -0
- package/templates/apps/api/lambdas.yml +84 -0
- package/templates/apps/api/project.json.template +58 -0
- package/templates/apps/api/src/__tests__/setup.ts +10 -0
- package/templates/apps/api/src/handlers/users/create-user.ts +52 -0
- package/templates/apps/api/src/handlers/users/delete-user.ts +45 -0
- package/templates/apps/api/src/handlers/users/get-me.ts +72 -0
- package/templates/apps/api/src/handlers/users/get-user.ts +45 -0
- package/templates/apps/api/src/handlers/users/get-users.ts +23 -0
- package/templates/apps/api/src/handlers/users/index.ts +17 -0
- package/templates/apps/api/src/handlers/users/update-user.ts +72 -0
- package/templates/apps/api/src/lib/dynamo/dynamo-model.ts +504 -0
- package/templates/apps/api/src/lib/dynamo/index.ts +12 -0
- package/templates/apps/api/src/lib/dynamo/utils.ts +39 -0
- package/templates/apps/api/src/middleware/auth0-auth.ts +97 -0
- package/templates/apps/api/src/middleware/cognito-auth.ts +90 -0
- package/templates/apps/api/src/models/UserModel.ts +109 -0
- package/templates/apps/api/src/schemas/user.schema.ts +44 -0
- package/templates/apps/api/src/services/user-service.ts +108 -0
- package/templates/apps/api/src/utils/auth-context.ts +60 -0
- package/templates/apps/api/src/utils/common/helpers.ts +26 -0
- package/templates/apps/api/src/utils/lambda-handler.ts +148 -0
- package/templates/apps/api/src/utils/response.ts +52 -0
- package/templates/apps/api/src/utils/validator.ts +75 -0
- package/templates/apps/api/tsconfig.app.json +15 -0
- package/templates/apps/api/tsconfig.json +19 -0
- package/templates/apps/api/tsconfig.spec.json +17 -0
- package/templates/apps/mobile/.env.example +5 -0
- package/templates/apps/mobile/.eslintrc.json +33 -0
- package/templates/apps/mobile/app.json +33 -0
- package/templates/apps/mobile/assets/.gitkeep +0 -0
- package/templates/apps/mobile/babel.config.js +19 -0
- package/templates/apps/mobile/index.js +7 -0
- package/templates/apps/mobile/jest.config.ts +22 -0
- package/templates/apps/mobile/metro.config.js +35 -0
- package/templates/apps/mobile/package.json +22 -0
- package/templates/apps/mobile/project.json.template +64 -0
- package/templates/apps/mobile/src/App.tsx +367 -0
- package/templates/apps/mobile/src/__tests__/App.spec.tsx +46 -0
- package/templates/apps/mobile/src/__tests__/store/user-store.spec.ts +156 -0
- package/templates/apps/mobile/src/config/api.ts +16 -0
- package/templates/apps/mobile/src/store/user-store.ts +56 -0
- package/templates/apps/mobile/src/test-setup.ts +10 -0
- package/templates/apps/mobile/tsconfig.json +22 -0
- package/templates/apps/web/.env.example +13 -0
- package/templates/apps/web/.eslintrc.json +26 -0
- package/templates/apps/web/index.html +13 -0
- package/templates/apps/web/jest.config.ts +24 -0
- package/templates/apps/web/package.json +15 -0
- package/templates/apps/web/project.json.template +66 -0
- package/templates/apps/web/src/App.tsx +352 -0
- package/templates/apps/web/src/__mocks__/config/api.ts +41 -0
- package/templates/apps/web/src/__tests__/App.spec.tsx +240 -0
- package/templates/apps/web/src/__tests__/store/user-store.spec.ts +185 -0
- package/templates/apps/web/src/auth/auth0-provider.tsx +103 -0
- package/templates/apps/web/src/auth/cognito-provider.tsx +143 -0
- package/templates/apps/web/src/auth/index.ts +7 -0
- package/templates/apps/web/src/auth/use-auth.ts +16 -0
- package/templates/apps/web/src/config/amplify-config.ts +31 -0
- package/templates/apps/web/src/config/api.ts +38 -0
- package/templates/apps/web/src/config/auth0-config.ts +17 -0
- package/templates/apps/web/src/main.tsx +41 -0
- package/templates/apps/web/src/store/user-store.ts +56 -0
- package/templates/apps/web/src/styles.css +165 -0
- package/templates/apps/web/src/test-setup.ts +1 -0
- package/templates/apps/web/src/theme/index.ts +30 -0
- package/templates/apps/web/src/vite-env.d.ts +19 -0
- package/templates/apps/web/tsconfig.app.json +24 -0
- package/templates/apps/web/tsconfig.json +22 -0
- package/templates/apps/web/tsconfig.spec.json +28 -0
- package/templates/apps/web/vite.config.ts +87 -0
- package/templates/manifest.json +28 -0
- package/templates/packages/api-client/.eslintrc.json +18 -0
- package/templates/packages/api-client/jest.config.ts +13 -0
- package/templates/packages/api-client/package.json +8 -0
- package/templates/packages/api-client/project.json.template +34 -0
- package/templates/packages/api-client/src/__tests__/api-client.spec.ts +408 -0
- package/templates/packages/api-client/src/api-client.ts +201 -0
- package/templates/packages/api-client/src/config.ts +193 -0
- package/templates/packages/api-client/src/index.ts +9 -0
- package/templates/packages/api-client/tsconfig.json +22 -0
- package/templates/packages/api-client/tsconfig.lib.json +11 -0
- package/templates/packages/api-client/tsconfig.spec.json +14 -0
- package/templates/packages/common-types/.eslintrc.json +18 -0
- package/templates/packages/common-types/package.json +6 -0
- package/templates/packages/common-types/project.json.template +26 -0
- package/templates/packages/common-types/src/api.types.ts +24 -0
- package/templates/packages/common-types/src/auth.types.ts +36 -0
- package/templates/packages/common-types/src/common.types.ts +46 -0
- package/templates/packages/common-types/src/index.ts +19 -0
- package/templates/packages/common-types/src/lambda.types.ts +39 -0
- package/templates/packages/common-types/src/user.types.ts +31 -0
- package/templates/packages/common-types/tsconfig.json +19 -0
- package/templates/packages/common-types/tsconfig.lib.json +11 -0
- package/templates/root/.editorconfig +23 -0
- package/templates/root/.nvmrc +1 -0
- package/templates/root/eslint.config.js +61 -0
- package/templates/root/jest.preset.js +16 -0
- package/templates/root/nx.json +29 -0
- package/templates/root/package.json +131 -0
- 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>;
|
package/dist/aws/iam.js
ADDED
|
@@ -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[]>;
|