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
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
|
+
[](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 {};
|