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
package/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # create-aws-project
2
+
3
+ Create a new AWS project from scratch including CloudFront, API Gateway, Lambdas, Cognito or Auth0, DynamoDB. GitHub pipeline for testing and deploying.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/create-aws-starter-kit.svg)](https://www.npmjs.com/package/create-aws-starter-kit)
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ npx create-aws-starter-kit my-project
11
+ ```
12
+
13
+ **Requirements:** Node.js 22.16.0+ (npm included)
14
+
15
+ ## Features
16
+
17
+ - **Interactive wizard** - Guided setup with smart defaults
18
+ - **Platform selection** - Choose web, mobile, and/or API
19
+ - **Feature toggles** - GitHub Actions CI/CD, VS Code configuration
20
+ - **Theme customization** - Choose a brand color for your UI
21
+ - **AWS region configuration** - Set your deployment region
22
+
23
+ ## What You Get
24
+
25
+ The generated project is a full-stack Nx monorepo with:
26
+
27
+ - **React web app** - Vite + Chakra UI
28
+ - **React Native mobile app** - Expo
29
+ - **AWS Lambda API** - TypeScript handlers
30
+ - **AWS CDK infrastructure** - Infrastructure as code
31
+ - **Shared packages** - Common types and API client
32
+
33
+ ## CLI Options
34
+
35
+ ```
36
+ create-aws-starter-kit [options] [project-name]
37
+
38
+ Options:
39
+ --help, -h Show help message
40
+ --version, -v Show version number
41
+
42
+ Examples:
43
+ npx create-aws-starter-kit my-app
44
+ npx create-aws-starter-kit --help
45
+ npx create-aws-starter-kit --version
46
+ ```
47
+
48
+ ### Setup GitHub Command
49
+
50
+ After generating a project with AWS Organizations enabled, configure GitHub Actions deployment:
51
+
52
+ ```bash
53
+ npx create-aws-starter-kit setup-github
54
+ ```
55
+
56
+ This command:
57
+ - Creates IAM deployment users per environment (dev, stage, prod)
58
+ - Configures GitHub Environments with AWS credentials
59
+ - Sets up least-privilege CDK deployment permissions
60
+
61
+ **Requirements:**
62
+ - GitHub Personal Access Token with `repo` and `admin:org` scopes
63
+ - AWS credentials with IAM permissions
64
+
65
+ ## Wizard Prompts
66
+
67
+ The interactive wizard will ask you about:
68
+
69
+ 1. **Project name** - Must be npm-compatible (lowercase, no spaces)
70
+ 2. **Platforms** - Which platforms to include (web, mobile, api)
71
+ 3. **Authentication** - Choose your auth provider:
72
+ - None (add later)
73
+ - AWS Cognito
74
+ - Auth0
75
+ - Optional: Social login, MFA
76
+ 4. **AWS Organizations** - Multi-account setup (optional):
77
+ - Creates separate AWS accounts for each environment
78
+ - Environments: dev, stage, prod (plus optional qa, sandbox)
79
+ - Requires root email per account
80
+ 5. **Features** - Optional extras:
81
+ - GitHub Actions workflows for CI/CD
82
+ - VS Code workspace configuration
83
+ 6. **AWS region** - Where to deploy your infrastructure
84
+ 7. **Brand color** - Theme color for your UI (blue, purple, teal, green, orange)
85
+
86
+ ## Requirements
87
+
88
+ - **Node.js** - Version 22.16.0 or higher
89
+ - Note: Node 25+ has Jest compatibility issues - use 22.x or 24.x
90
+ - **npm** - Included with Node.js
91
+
92
+ ## After Generation
93
+
94
+ Once your project is created:
95
+
96
+ ```bash
97
+ cd my-project
98
+ npm install
99
+ ```
100
+
101
+ Then start developing:
102
+
103
+ ```bash
104
+ # Start web app
105
+ npm run web
106
+
107
+ # Start mobile app
108
+ npm run mobile
109
+
110
+ # Deploy API to AWS
111
+ npm run cdk:deploy
112
+ ```
113
+
114
+ See the generated project's README for detailed documentation.
115
+
116
+ ## License
117
+
118
+ ISC
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,281 @@
1
+ import { describe, it, expect } from '@jest/globals';
2
+ import { replaceTokens, processConditionalBlocks, } from '../../generator/replace-tokens.js';
3
+ describe('replaceTokens', () => {
4
+ const mockTokens = {
5
+ PROJECT_NAME: 'my-awesome-app',
6
+ PROJECT_NAME_PASCAL: 'MyAwesomeApp',
7
+ PROJECT_NAME_TITLE: 'My Awesome App',
8
+ AWS_REGION: 'us-west-2',
9
+ PACKAGE_SCOPE: '@my-awesome-app',
10
+ BRAND_COLOR: 'blue',
11
+ AUTH_COGNITO: 'false',
12
+ AUTH_AUTH0: 'false',
13
+ AUTH_SOCIAL_LOGIN: 'false',
14
+ AUTH_MFA: 'false',
15
+ };
16
+ describe('single token replacement', () => {
17
+ it('should replace PROJECT_NAME token', () => {
18
+ const content = 'name: {{PROJECT_NAME}}';
19
+ const result = replaceTokens(content, mockTokens);
20
+ expect(result).toBe('name: my-awesome-app');
21
+ });
22
+ it('should replace PROJECT_NAME_PASCAL token', () => {
23
+ const content = 'class {{PROJECT_NAME_PASCAL}}Stack {}';
24
+ const result = replaceTokens(content, mockTokens);
25
+ expect(result).toBe('class MyAwesomeAppStack {}');
26
+ });
27
+ it('should replace AWS_REGION token', () => {
28
+ const content = 'region: {{AWS_REGION}}';
29
+ const result = replaceTokens(content, mockTokens);
30
+ expect(result).toBe('region: us-west-2');
31
+ });
32
+ it('should replace PACKAGE_SCOPE token', () => {
33
+ const content = "import { User } from '{{PACKAGE_SCOPE}}/common-types';";
34
+ const result = replaceTokens(content, mockTokens);
35
+ expect(result).toBe("import { User } from '@my-awesome-app/common-types';");
36
+ });
37
+ it('should replace BRAND_COLOR token', () => {
38
+ const content = 'color: {{BRAND_COLOR}}';
39
+ const result = replaceTokens(content, mockTokens);
40
+ expect(result).toBe('color: blue');
41
+ });
42
+ it('should replace AUTH_COGNITO token', () => {
43
+ const content = 'cognito: {{AUTH_COGNITO}}';
44
+ const result = replaceTokens(content, mockTokens);
45
+ expect(result).toBe('cognito: false');
46
+ });
47
+ });
48
+ describe('multiple token replacement', () => {
49
+ it('should replace multiple different tokens', () => {
50
+ const content = `{
51
+ "name": "{{PROJECT_NAME}}",
52
+ "region": "{{AWS_REGION}}",
53
+ "scope": "{{PACKAGE_SCOPE}}"
54
+ }`;
55
+ const result = replaceTokens(content, mockTokens);
56
+ expect(result).toBe(`{
57
+ "name": "my-awesome-app",
58
+ "region": "us-west-2",
59
+ "scope": "@my-awesome-app"
60
+ }`);
61
+ });
62
+ it('should replace same token multiple times', () => {
63
+ const content = '{{PROJECT_NAME}} and {{PROJECT_NAME}} again';
64
+ const result = replaceTokens(content, mockTokens);
65
+ expect(result).toBe('my-awesome-app and my-awesome-app again');
66
+ });
67
+ });
68
+ describe('unknown token handling', () => {
69
+ it('should preserve unknown tokens', () => {
70
+ const content = '{{UNKNOWN_TOKEN}} stays';
71
+ const result = replaceTokens(content, mockTokens);
72
+ expect(result).toBe('{{UNKNOWN_TOKEN}} stays');
73
+ });
74
+ it('should replace known tokens and preserve unknown ones', () => {
75
+ const content = '{{PROJECT_NAME}} and {{UNKNOWN_TOKEN}}';
76
+ const result = replaceTokens(content, mockTokens);
77
+ expect(result).toBe('my-awesome-app and {{UNKNOWN_TOKEN}}');
78
+ });
79
+ });
80
+ describe('passthrough for content without tokens', () => {
81
+ it('should return unchanged content when no tokens present', () => {
82
+ const content = 'Regular content without any tokens';
83
+ const result = replaceTokens(content, mockTokens);
84
+ expect(result).toBe('Regular content without any tokens');
85
+ });
86
+ it('should return empty string unchanged', () => {
87
+ const content = '';
88
+ const result = replaceTokens(content, mockTokens);
89
+ expect(result).toBe('');
90
+ });
91
+ it('should not match partial token patterns', () => {
92
+ const content = '{ PROJECT_NAME } and {{ broken';
93
+ const result = replaceTokens(content, mockTokens);
94
+ expect(result).toBe('{ PROJECT_NAME } and {{ broken');
95
+ });
96
+ });
97
+ });
98
+ describe('processConditionalBlocks', () => {
99
+ const baseTokens = {
100
+ PROJECT_NAME: 'my-awesome-app',
101
+ PROJECT_NAME_PASCAL: 'MyAwesomeApp',
102
+ PROJECT_NAME_TITLE: 'My Awesome App',
103
+ AWS_REGION: 'us-west-2',
104
+ PACKAGE_SCOPE: '@my-awesome-app',
105
+ BRAND_COLOR: 'blue',
106
+ AUTH_COGNITO: 'false',
107
+ AUTH_AUTH0: 'false',
108
+ AUTH_SOCIAL_LOGIN: 'false',
109
+ AUTH_MFA: 'false',
110
+ };
111
+ describe('comment-wrapped conditionals', () => {
112
+ it('should keep content when token is true (removes markers)', () => {
113
+ const tokens = { ...baseTokens, AUTH_COGNITO: 'true' };
114
+ const content = `before
115
+ // {{#if AUTH_COGNITO}}
116
+ import { CognitoStack } from './cognito';
117
+ // {{/if AUTH_COGNITO}}
118
+ after`;
119
+ const result = processConditionalBlocks(content, tokens);
120
+ expect(result).toBe(`before
121
+ import { CognitoStack } from './cognito';
122
+ after`);
123
+ });
124
+ it('should remove entire block when token is false', () => {
125
+ const tokens = { ...baseTokens, AUTH_COGNITO: 'false' };
126
+ const content = `before
127
+ // {{#if AUTH_COGNITO}}
128
+ import { CognitoStack } from './cognito';
129
+ // {{/if AUTH_COGNITO}}
130
+ after`;
131
+ const result = processConditionalBlocks(content, tokens);
132
+ expect(result).toBe(`before
133
+ after`);
134
+ });
135
+ it('should remove entire block when token is missing', () => {
136
+ const content = `before
137
+ // {{#if UNKNOWN_TOKEN}}
138
+ some content
139
+ // {{/if UNKNOWN_TOKEN}}
140
+ after`;
141
+ const result = processConditionalBlocks(content, baseTokens);
142
+ expect(result).toBe(`before
143
+ after`);
144
+ });
145
+ it('should handle multiple comment-wrapped blocks', () => {
146
+ const tokens = { ...baseTokens, AUTH_COGNITO: 'true', AUTH_AUTH0: 'false' };
147
+ const content = `// {{#if AUTH_COGNITO}}
148
+ import { CognitoStack } from './cognito';
149
+ // {{/if AUTH_COGNITO}}
150
+ // {{#if AUTH_AUTH0}}
151
+ import { Auth0Stack } from './auth0';
152
+ // {{/if AUTH_AUTH0}}
153
+ const app = new App();`;
154
+ const result = processConditionalBlocks(content, tokens);
155
+ expect(result).toBe(`import { CognitoStack } from './cognito';
156
+ const app = new App();`);
157
+ });
158
+ });
159
+ describe('plain conditionals', () => {
160
+ it('should keep content when token is true (removes markers)', () => {
161
+ const tokens = { ...baseTokens, AUTH_COGNITO: 'true' };
162
+ const content = `before
163
+ {{#if AUTH_COGNITO}}
164
+ Cognito content here
165
+ {{/if AUTH_COGNITO}}
166
+ after`;
167
+ const result = processConditionalBlocks(content, tokens);
168
+ expect(result).toBe(`before
169
+ Cognito content here
170
+ after`);
171
+ });
172
+ it('should remove entire block when token is false', () => {
173
+ const tokens = { ...baseTokens, AUTH_COGNITO: 'false' };
174
+ const content = `before
175
+ {{#if AUTH_COGNITO}}
176
+ Cognito content here
177
+ {{/if AUTH_COGNITO}}
178
+ after`;
179
+ const result = processConditionalBlocks(content, tokens);
180
+ expect(result).toBe(`before
181
+ after`);
182
+ });
183
+ });
184
+ describe('nested content with other tokens', () => {
185
+ it('should preserve tokens inside conditional blocks for later replacement', () => {
186
+ const tokens = { ...baseTokens, AUTH_COGNITO: 'true' };
187
+ const content = `// {{#if AUTH_COGNITO}}
188
+ new CognitoStack(app, '{{PROJECT_NAME_PASCAL}}-Cognito');
189
+ // {{/if AUTH_COGNITO}}`;
190
+ const result = processConditionalBlocks(content, tokens);
191
+ expect(result).toBe(`new CognitoStack(app, '{{PROJECT_NAME_PASCAL}}-Cognito');
192
+ `);
193
+ });
194
+ });
195
+ describe('content preservation', () => {
196
+ it('should preserve content outside of conditional blocks', () => {
197
+ const tokens = { ...baseTokens, AUTH_COGNITO: 'false' };
198
+ const content = `const app = new App();
199
+ // {{#if AUTH_COGNITO}}
200
+ new CognitoStack(app);
201
+ // {{/if AUTH_COGNITO}}
202
+ const region = '{{AWS_REGION}}';`;
203
+ const result = processConditionalBlocks(content, tokens);
204
+ expect(result).toBe(`const app = new App();
205
+ const region = '{{AWS_REGION}}';`);
206
+ });
207
+ it('should return unchanged content when no conditionals present', () => {
208
+ const content = `const app = new App();
209
+ const region = '{{AWS_REGION}}';`;
210
+ const result = processConditionalBlocks(content, baseTokens);
211
+ expect(result).toBe(content);
212
+ });
213
+ it('should return empty string unchanged', () => {
214
+ const result = processConditionalBlocks('', baseTokens);
215
+ expect(result).toBe('');
216
+ });
217
+ });
218
+ });
219
+ describe('replaceTokens integration with conditionals', () => {
220
+ const baseTokens = {
221
+ PROJECT_NAME: 'my-awesome-app',
222
+ PROJECT_NAME_PASCAL: 'MyAwesomeApp',
223
+ PROJECT_NAME_TITLE: 'My Awesome App',
224
+ AWS_REGION: 'us-west-2',
225
+ PACKAGE_SCOPE: '@my-awesome-app',
226
+ BRAND_COLOR: 'blue',
227
+ AUTH_COGNITO: 'false',
228
+ AUTH_AUTH0: 'false',
229
+ AUTH_SOCIAL_LOGIN: 'false',
230
+ AUTH_MFA: 'false',
231
+ };
232
+ it('should process conditionals before token replacement', () => {
233
+ const tokens = { ...baseTokens, AUTH_COGNITO: 'true' };
234
+ const content = `const appName = '{{PROJECT_NAME_PASCAL}}';
235
+ // {{#if AUTH_COGNITO}}
236
+ new CognitoStack(app, '{{PROJECT_NAME_PASCAL}}-Cognito');
237
+ // {{/if AUTH_COGNITO}}`;
238
+ const result = replaceTokens(content, tokens);
239
+ expect(result).toBe(`const appName = 'MyAwesomeApp';
240
+ new CognitoStack(app, 'MyAwesomeApp-Cognito');
241
+ `);
242
+ });
243
+ it('should handle AUTH_COGNITO=true keeping Cognito-specific code', () => {
244
+ const tokens = { ...baseTokens, AUTH_COGNITO: 'true' };
245
+ const content = `import * as cdk from 'aws-cdk-lib';
246
+ // {{#if AUTH_COGNITO}}
247
+ import { CognitoStack } from './auth/cognito-stack';
248
+ // {{/if AUTH_COGNITO}}
249
+
250
+ const app = new cdk.App();`;
251
+ const result = replaceTokens(content, tokens);
252
+ expect(result).toBe(`import * as cdk from 'aws-cdk-lib';
253
+ import { CognitoStack } from './auth/cognito-stack';
254
+
255
+ const app = new cdk.App();`);
256
+ });
257
+ it('should handle AUTH_AUTH0=true keeping Auth0-specific code', () => {
258
+ const tokens = { ...baseTokens, AUTH_AUTH0: 'true' };
259
+ const content = `// {{#if AUTH_AUTH0}}
260
+ import { auth0Config } from './auth0-config';
261
+ // {{/if AUTH_AUTH0}}
262
+ export const config = {};`;
263
+ const result = replaceTokens(content, tokens);
264
+ expect(result).toBe(`import { auth0Config } from './auth0-config';
265
+ export const config = {};`);
266
+ });
267
+ it('should remove auth-specific code when provider is false', () => {
268
+ const tokens = { ...baseTokens, AUTH_COGNITO: 'false', AUTH_AUTH0: 'false' };
269
+ const content = `const app = new App();
270
+ // {{#if AUTH_COGNITO}}
271
+ new CognitoStack(app);
272
+ // {{/if AUTH_COGNITO}}
273
+ // {{#if AUTH_AUTH0}}
274
+ new Auth0Integration(app);
275
+ // {{/if AUTH_AUTH0}}
276
+ app.synth();`;
277
+ const result = replaceTokens(content, tokens);
278
+ expect(result).toBe(`const app = new App();
279
+ app.synth();`);
280
+ });
281
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,162 @@
1
+ import { describe, it, expect } from '@jest/globals';
2
+ import { deriveTokenValues, templateManifest } from '../templates/index.js';
3
+ /**
4
+ * Create a minimal valid ProjectConfig for testing
5
+ */
6
+ function createMockConfig(overrides = {}) {
7
+ return {
8
+ projectName: 'test-project',
9
+ platforms: ['web', 'api'],
10
+ awsRegion: 'us-east-1',
11
+ features: [],
12
+ brandColor: 'blue',
13
+ auth: {
14
+ provider: 'none',
15
+ features: [],
16
+ },
17
+ ...overrides,
18
+ };
19
+ }
20
+ describe('Generator', () => {
21
+ describe('templateManifest.byAuthProvider', () => {
22
+ it('should have Cognito auth templates defined', () => {
23
+ expect(templateManifest.byAuthProvider.cognito).toBeDefined();
24
+ expect(Array.isArray(templateManifest.byAuthProvider.cognito)).toBe(true);
25
+ expect(templateManifest.byAuthProvider.cognito.length).toBeGreaterThan(0);
26
+ });
27
+ it('should include cognito auth directory in cognito templates', () => {
28
+ const cognitoTemplates = templateManifest.byAuthProvider.cognito;
29
+ const hasAuthDir = cognitoTemplates.some((entry) => entry.src.includes('auth') || entry.dest.includes('auth'));
30
+ expect(hasAuthDir).toBe(true);
31
+ });
32
+ it('should have empty array for none provider', () => {
33
+ expect(templateManifest.byAuthProvider.none).toBeDefined();
34
+ expect(Array.isArray(templateManifest.byAuthProvider.none)).toBe(true);
35
+ expect(templateManifest.byAuthProvider.none.length).toBe(0);
36
+ });
37
+ it('should have Auth0 auth templates defined', () => {
38
+ expect(templateManifest.byAuthProvider.auth0).toBeDefined();
39
+ expect(Array.isArray(templateManifest.byAuthProvider.auth0)).toBe(true);
40
+ expect(templateManifest.byAuthProvider.auth0.length).toBeGreaterThan(0);
41
+ });
42
+ it('should include Auth0 provider, config, and middleware in auth0 templates', () => {
43
+ const auth0Templates = templateManifest.byAuthProvider.auth0;
44
+ const hasProvider = auth0Templates.some((entry) => entry.src.includes('auth0-provider.tsx'));
45
+ const hasConfig = auth0Templates.some((entry) => entry.src.includes('auth0-config.ts'));
46
+ const hasMiddleware = auth0Templates.some((entry) => entry.src.includes('auth0-auth.ts'));
47
+ expect(hasProvider).toBe(true);
48
+ expect(hasConfig).toBe(true);
49
+ expect(hasMiddleware).toBe(true);
50
+ });
51
+ });
52
+ describe('deriveTokenValues', () => {
53
+ it('should set AUTH_COGNITO to true when cognito provider selected', () => {
54
+ const config = createMockConfig({
55
+ auth: { provider: 'cognito', features: [] },
56
+ });
57
+ const tokens = deriveTokenValues(config);
58
+ expect(tokens.AUTH_COGNITO).toBe('true');
59
+ expect(tokens.AUTH_AUTH0).toBe('false');
60
+ });
61
+ it('should set AUTH_AUTH0 to true when auth0 provider selected', () => {
62
+ const config = createMockConfig({
63
+ auth: { provider: 'auth0', features: [] },
64
+ });
65
+ const tokens = deriveTokenValues(config);
66
+ expect(tokens.AUTH_COGNITO).toBe('false');
67
+ expect(tokens.AUTH_AUTH0).toBe('true');
68
+ });
69
+ it('should set all auth tokens to false when provider is none', () => {
70
+ const config = createMockConfig({
71
+ auth: { provider: 'none', features: [] },
72
+ });
73
+ const tokens = deriveTokenValues(config);
74
+ expect(tokens.AUTH_COGNITO).toBe('false');
75
+ expect(tokens.AUTH_AUTH0).toBe('false');
76
+ expect(tokens.AUTH_MFA).toBe('false');
77
+ expect(tokens.AUTH_SOCIAL_LOGIN).toBe('false');
78
+ });
79
+ it('should set AUTH_MFA to true when mfa feature enabled', () => {
80
+ const config = createMockConfig({
81
+ auth: { provider: 'cognito', features: ['mfa'] },
82
+ });
83
+ const tokens = deriveTokenValues(config);
84
+ expect(tokens.AUTH_MFA).toBe('true');
85
+ });
86
+ it('should set AUTH_SOCIAL_LOGIN to true when social-login feature enabled', () => {
87
+ const config = createMockConfig({
88
+ auth: { provider: 'cognito', features: ['social-login'] },
89
+ });
90
+ const tokens = deriveTokenValues(config);
91
+ expect(tokens.AUTH_SOCIAL_LOGIN).toBe('true');
92
+ });
93
+ it('should set multiple auth feature tokens when multiple features enabled', () => {
94
+ const config = createMockConfig({
95
+ auth: { provider: 'cognito', features: ['mfa', 'social-login'] },
96
+ });
97
+ const tokens = deriveTokenValues(config);
98
+ expect(tokens.AUTH_COGNITO).toBe('true');
99
+ expect(tokens.AUTH_MFA).toBe('true');
100
+ expect(tokens.AUTH_SOCIAL_LOGIN).toBe('true');
101
+ });
102
+ it('should include all auth tokens in the returned object', () => {
103
+ const config = createMockConfig();
104
+ const tokens = deriveTokenValues(config);
105
+ expect('AUTH_COGNITO' in tokens).toBe(true);
106
+ expect('AUTH_AUTH0' in tokens).toBe(true);
107
+ expect('AUTH_MFA' in tokens).toBe(true);
108
+ expect('AUTH_SOCIAL_LOGIN' in tokens).toBe(true);
109
+ });
110
+ });
111
+ describe('auth provider template selection', () => {
112
+ it('should return templates for cognito when cognito provider selected', () => {
113
+ const config = createMockConfig({
114
+ auth: { provider: 'cognito', features: [] },
115
+ });
116
+ const authEntries = templateManifest.byAuthProvider[config.auth.provider];
117
+ expect(authEntries).toBeDefined();
118
+ expect(authEntries.length).toBeGreaterThan(0);
119
+ });
120
+ it('should return empty array when provider is none', () => {
121
+ const config = createMockConfig({
122
+ auth: { provider: 'none', features: [] },
123
+ });
124
+ const authEntries = templateManifest.byAuthProvider[config.auth.provider];
125
+ expect(authEntries).toBeDefined();
126
+ expect(authEntries.length).toBe(0);
127
+ });
128
+ it('should skip auth template processing when provider is none', () => {
129
+ const config = createMockConfig({
130
+ auth: { provider: 'none', features: [] },
131
+ });
132
+ // This tests the logic: if provider !== 'none', we would process templates
133
+ // Since provider === 'none', we would skip the processing
134
+ const shouldProcessAuth = config.auth.provider !== 'none';
135
+ expect(shouldProcessAuth).toBe(false);
136
+ });
137
+ it('should process auth templates when provider is cognito', () => {
138
+ const config = createMockConfig({
139
+ auth: { provider: 'cognito', features: [] },
140
+ });
141
+ // This tests the logic: if provider !== 'none', we should process templates
142
+ const shouldProcessAuth = config.auth.provider !== 'none';
143
+ expect(shouldProcessAuth).toBe(true);
144
+ });
145
+ it('should return templates for auth0 when auth0 provider selected', () => {
146
+ const config = createMockConfig({
147
+ auth: { provider: 'auth0', features: [] },
148
+ });
149
+ const authEntries = templateManifest.byAuthProvider[config.auth.provider];
150
+ expect(authEntries).toBeDefined();
151
+ expect(authEntries.length).toBeGreaterThan(0);
152
+ });
153
+ it('should process auth templates when provider is auth0', () => {
154
+ const config = createMockConfig({
155
+ auth: { provider: 'auth0', features: [] },
156
+ });
157
+ // This tests the logic: if provider !== 'none', we should process templates
158
+ const shouldProcessAuth = config.auth.provider !== 'none';
159
+ expect(shouldProcessAuth).toBe(true);
160
+ });
161
+ });
162
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ import { describe, it, expect } from '@jest/globals';
2
+ import { validateProjectName } from '../../validation/project-name.js';
3
+ describe('validateProjectName', () => {
4
+ describe('valid names', () => {
5
+ it('should accept simple kebab-case names', () => {
6
+ expect(validateProjectName('my-app')).toBe(true);
7
+ });
8
+ it('should accept names with numbers', () => {
9
+ expect(validateProjectName('app123')).toBe(true);
10
+ });
11
+ it('should accept names ending with numbers', () => {
12
+ expect(validateProjectName('my-app-2')).toBe(true);
13
+ });
14
+ it('should accept single-word names', () => {
15
+ expect(validateProjectName('myapp')).toBe(true);
16
+ });
17
+ it('should accept names starting with numbers (npm allows this)', () => {
18
+ expect(validateProjectName('123app')).toBe(true);
19
+ });
20
+ it('should accept scoped packages (npm allows this)', () => {
21
+ expect(validateProjectName('@scope/pkg')).toBe(true);
22
+ });
23
+ it('should accept underscores (npm allows this)', () => {
24
+ expect(validateProjectName('my_app')).toBe(true);
25
+ });
26
+ });
27
+ describe('invalid names', () => {
28
+ it('should reject names with spaces', () => {
29
+ const result = validateProjectName('My App');
30
+ expect(result).not.toBe(true);
31
+ expect(typeof result).toBe('string');
32
+ });
33
+ it('should reject empty string', () => {
34
+ const result = validateProjectName('');
35
+ expect(result).toBe('Project name is required');
36
+ });
37
+ it('should reject whitespace-only string', () => {
38
+ const result = validateProjectName(' ');
39
+ expect(result).toBe('Project name is required');
40
+ });
41
+ it('should reject names with uppercase letters', () => {
42
+ const result = validateProjectName('MyApp');
43
+ expect(result).not.toBe(true);
44
+ expect(typeof result).toBe('string');
45
+ });
46
+ it('should reject names starting with dot', () => {
47
+ const result = validateProjectName('.hidden');
48
+ expect(result).not.toBe(true);
49
+ expect(typeof result).toBe('string');
50
+ });
51
+ it('should reject node_modules as a name', () => {
52
+ const result = validateProjectName('node_modules');
53
+ expect(result).not.toBe(true);
54
+ expect(typeof result).toBe('string');
55
+ });
56
+ });
57
+ });
@@ -0,0 +1 @@
1
+ export {};