create-aws-project 1.3.0 → 1.4.3
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 +54 -32
- package/dist/__tests__/harness/fixtures/config-factories.d.ts +15 -0
- package/dist/__tests__/harness/fixtures/config-factories.js +71 -0
- package/dist/__tests__/harness/fixtures/fixtures.spec.d.ts +1 -0
- package/dist/__tests__/harness/fixtures/fixtures.spec.js +89 -0
- package/dist/__tests__/harness/fixtures/index.d.ts +2 -0
- package/dist/__tests__/harness/fixtures/index.js +2 -0
- package/dist/__tests__/harness/fixtures/matrix.d.ts +28 -0
- package/dist/__tests__/harness/fixtures/matrix.js +58 -0
- package/dist/__tests__/harness/local-runner.d.ts +2 -0
- package/dist/__tests__/harness/local-runner.js +184 -0
- package/dist/__tests__/harness/run-command.d.ts +7 -0
- package/dist/__tests__/harness/run-command.js +26 -0
- package/dist/__tests__/harness/run-command.spec.d.ts +1 -0
- package/dist/__tests__/harness/run-command.spec.js +67 -0
- package/dist/__tests__/harness/temp-dir.d.ts +1 -0
- package/dist/__tests__/harness/temp-dir.js +14 -0
- package/dist/__tests__/harness/temp-dir.spec.d.ts +1 -0
- package/dist/__tests__/harness/temp-dir.spec.js +40 -0
- package/dist/__tests__/harness/validate-project.d.ts +16 -0
- package/dist/__tests__/harness/validate-project.js +78 -0
- package/dist/__tests__/harness/validate-project.spec.d.ts +1 -0
- package/dist/__tests__/harness/validate-project.spec.js +189 -0
- package/dist/github/secrets.js +10 -42
- package/package.json +8 -3
- package/templates/apps/api/src/handlers/users/get-me.ts +2 -2
- package/templates/apps/api/src/middleware/auth0-auth.ts +4 -4
- package/templates/apps/api/src/middleware/cognito-auth.ts +3 -3
- package/templates/apps/api/src/utils/auth-context.ts +1 -1
- package/templates/apps/mobile/jest.config.ts +1 -0
- package/templates/apps/mobile/src/test-setup.ts +1 -1
- package/templates/apps/web/jest.config.ts +1 -0
- package/templates/apps/web/src/__mocks__/config/auth0-config.ts +16 -0
- package/templates/apps/web/src/__tests__/App.spec.tsx +12 -0
- package/templates/apps/web/src/config/amplify-config.ts +23 -25
- package/templates/apps/web/src/config/auth0-config.ts +3 -3
- package/templates/apps/web/src/test-setup.ts +4 -0
- package/templates/apps/web/tsconfig.spec.json +1 -0
- package/templates/root/package.json +12 -9
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Create a new AWS project from scratch including CloudFront, API Gateway, Lambdas, Cognito or Auth0, DynamoDB. GitHub pipeline for testing and deploying.
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/create-aws-project)
|
|
6
6
|
|
|
7
7
|
## Quick Start
|
|
8
8
|
|
|
@@ -33,7 +33,7 @@ The generated project is a full-stack Nx monorepo with:
|
|
|
33
33
|
## CLI Options
|
|
34
34
|
|
|
35
35
|
```
|
|
36
|
-
create-aws-
|
|
36
|
+
create-aws-project [command] [options]
|
|
37
37
|
|
|
38
38
|
Commands:
|
|
39
39
|
(default) Create a new project (interactive wizard)
|
|
@@ -45,10 +45,10 @@ Options:
|
|
|
45
45
|
--version, -v Show version number
|
|
46
46
|
|
|
47
47
|
Examples:
|
|
48
|
-
npx create-aws-
|
|
49
|
-
npx create-aws-
|
|
50
|
-
npx create-aws-
|
|
51
|
-
npx create-aws-
|
|
48
|
+
npx create-aws-project my-app
|
|
49
|
+
npx create-aws-project setup-aws-envs
|
|
50
|
+
npx create-aws-project initialize-github dev
|
|
51
|
+
npx create-aws-project --help
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
## Wizard Prompts
|
|
@@ -74,30 +74,6 @@ The interactive wizard will ask you about:
|
|
|
74
74
|
- Note: Node 25+ has Jest compatibility issues - use 22.x or 24.x
|
|
75
75
|
- **npm** - Included with Node.js
|
|
76
76
|
|
|
77
|
-
## After Generation
|
|
78
|
-
|
|
79
|
-
Once your project is created:
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
cd my-project
|
|
83
|
-
npm install
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Then start developing:
|
|
87
|
-
|
|
88
|
-
```bash
|
|
89
|
-
# Start web app
|
|
90
|
-
npm run web
|
|
91
|
-
|
|
92
|
-
# Start mobile app
|
|
93
|
-
npm run mobile
|
|
94
|
-
|
|
95
|
-
# Deploy API to AWS
|
|
96
|
-
npm run cdk:deploy
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
See the generated project's README for detailed documentation.
|
|
100
|
-
|
|
101
77
|
## Post-Install Setup
|
|
102
78
|
|
|
103
79
|
After creating your project, you'll set up AWS environments and GitHub deployment. This is a one-time setup.
|
|
@@ -109,7 +85,29 @@ Before you begin:
|
|
|
109
85
|
- GitHub repository created for your project
|
|
110
86
|
- GitHub Personal Access Token with "repo" scope ([create one here](https://github.com/settings/tokens/new))
|
|
111
87
|
|
|
112
|
-
### Step 1:
|
|
88
|
+
### Step 1: connect to your .git project
|
|
89
|
+
|
|
90
|
+
* Initialize the repository
|
|
91
|
+
```
|
|
92
|
+
git init
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
* Add the remote repository using the git remote add <name> <url> command. A common practice is to name it origin.
|
|
96
|
+
```bash
|
|
97
|
+
git remote add origin <REMOTE_URL>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
* Verify the connection by listing your remotes. The -v flag shows the URLs.
|
|
101
|
+
```bash
|
|
102
|
+
git remote -v
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
* Push your local commits to the remote repository for the first time.
|
|
106
|
+
```bash
|
|
107
|
+
git push -u origin main
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Step 2: Set Up AWS Environments
|
|
113
111
|
|
|
114
112
|
From your project directory, run:
|
|
115
113
|
|
|
@@ -136,7 +134,7 @@ AWS environment setup complete!
|
|
|
136
134
|
|
|
137
135
|
Account IDs are saved to `.aws-starter-config.json` for the next step.
|
|
138
136
|
|
|
139
|
-
### Step
|
|
137
|
+
### Step 3: Configure GitHub Environments
|
|
140
138
|
|
|
141
139
|
For each environment, run:
|
|
142
140
|
|
|
@@ -215,6 +213,30 @@ Your Personal Access Token may be invalid or missing permissions. Ensure:
|
|
|
215
213
|
|
|
216
214
|
Create a new token at: https://github.com/settings/tokens/new
|
|
217
215
|
|
|
216
|
+
## After Setup
|
|
217
|
+
|
|
218
|
+
Once your project is set up:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
cd my-project
|
|
222
|
+
npm install
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Then start developing:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# Start web app
|
|
229
|
+
npm run web
|
|
230
|
+
|
|
231
|
+
# Start mobile app
|
|
232
|
+
npm run mobile
|
|
233
|
+
|
|
234
|
+
# Deploy API to AWS
|
|
235
|
+
npm run cdk:deploy
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
See the generated project's README for detailed documentation.
|
|
239
|
+
|
|
218
240
|
## License
|
|
219
241
|
|
|
220
242
|
ISC
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ProjectConfig } from '../../../types.js';
|
|
2
|
+
export declare const createWebCognitoConfig: () => ProjectConfig;
|
|
3
|
+
export declare const createWebAuth0Config: () => ProjectConfig;
|
|
4
|
+
export declare const createMobileCognitoConfig: () => ProjectConfig;
|
|
5
|
+
export declare const createMobileAuth0Config: () => ProjectConfig;
|
|
6
|
+
export declare const createApiCognitoConfig: () => ProjectConfig;
|
|
7
|
+
export declare const createApiAuth0Config: () => ProjectConfig;
|
|
8
|
+
export declare const createWebMobileCognitoConfig: () => ProjectConfig;
|
|
9
|
+
export declare const createWebMobileAuth0Config: () => ProjectConfig;
|
|
10
|
+
export declare const createWebApiCognitoConfig: () => ProjectConfig;
|
|
11
|
+
export declare const createWebApiAuth0Config: () => ProjectConfig;
|
|
12
|
+
export declare const createMobileApiCognitoConfig: () => ProjectConfig;
|
|
13
|
+
export declare const createMobileApiAuth0Config: () => ProjectConfig;
|
|
14
|
+
export declare const createFullStackCognitoConfig: () => ProjectConfig;
|
|
15
|
+
export declare const createFullStackAuth0Config: () => ProjectConfig;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a base configuration with sensible defaults.
|
|
3
|
+
* All factories should call this to ensure consistent structure.
|
|
4
|
+
*/
|
|
5
|
+
function createBaseConfig(overrides = {}) {
|
|
6
|
+
const authProvider = overrides.authProvider ?? 'cognito';
|
|
7
|
+
return {
|
|
8
|
+
projectName: overrides.projectName ?? 'test-project',
|
|
9
|
+
platforms: overrides.platforms ?? ['web', 'api'],
|
|
10
|
+
awsRegion: overrides.awsRegion ?? 'us-east-1',
|
|
11
|
+
features: overrides.features ?? [],
|
|
12
|
+
brandColor: overrides.brandColor ?? 'blue',
|
|
13
|
+
auth: {
|
|
14
|
+
provider: authProvider,
|
|
15
|
+
features: authProvider === 'none' ? [] : (overrides.authFeatures ?? []),
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
// Single platform factories
|
|
20
|
+
export const createWebCognitoConfig = () => createBaseConfig({ projectName: 'test-web-cognito', platforms: ['web'], authProvider: 'cognito' });
|
|
21
|
+
export const createWebAuth0Config = () => createBaseConfig({ projectName: 'test-web-auth0', platforms: ['web'], authProvider: 'auth0' });
|
|
22
|
+
export const createMobileCognitoConfig = () => createBaseConfig({
|
|
23
|
+
projectName: 'test-mobile-cognito',
|
|
24
|
+
platforms: ['mobile'],
|
|
25
|
+
authProvider: 'cognito',
|
|
26
|
+
});
|
|
27
|
+
export const createMobileAuth0Config = () => createBaseConfig({ projectName: 'test-mobile-auth0', platforms: ['mobile'], authProvider: 'auth0' });
|
|
28
|
+
export const createApiCognitoConfig = () => createBaseConfig({ projectName: 'test-api-cognito', platforms: ['api'], authProvider: 'cognito' });
|
|
29
|
+
export const createApiAuth0Config = () => createBaseConfig({ projectName: 'test-api-auth0', platforms: ['api'], authProvider: 'auth0' });
|
|
30
|
+
// Double platform factories
|
|
31
|
+
export const createWebMobileCognitoConfig = () => createBaseConfig({
|
|
32
|
+
projectName: 'test-web-mobile-cognito',
|
|
33
|
+
platforms: ['web', 'mobile'],
|
|
34
|
+
authProvider: 'cognito',
|
|
35
|
+
});
|
|
36
|
+
export const createWebMobileAuth0Config = () => createBaseConfig({
|
|
37
|
+
projectName: 'test-web-mobile-auth0',
|
|
38
|
+
platforms: ['web', 'mobile'],
|
|
39
|
+
authProvider: 'auth0',
|
|
40
|
+
});
|
|
41
|
+
export const createWebApiCognitoConfig = () => createBaseConfig({
|
|
42
|
+
projectName: 'test-web-api-cognito',
|
|
43
|
+
platforms: ['web', 'api'],
|
|
44
|
+
authProvider: 'cognito',
|
|
45
|
+
});
|
|
46
|
+
export const createWebApiAuth0Config = () => createBaseConfig({
|
|
47
|
+
projectName: 'test-web-api-auth0',
|
|
48
|
+
platforms: ['web', 'api'],
|
|
49
|
+
authProvider: 'auth0',
|
|
50
|
+
});
|
|
51
|
+
export const createMobileApiCognitoConfig = () => createBaseConfig({
|
|
52
|
+
projectName: 'test-mobile-api-cognito',
|
|
53
|
+
platforms: ['mobile', 'api'],
|
|
54
|
+
authProvider: 'cognito',
|
|
55
|
+
});
|
|
56
|
+
export const createMobileApiAuth0Config = () => createBaseConfig({
|
|
57
|
+
projectName: 'test-mobile-api-auth0',
|
|
58
|
+
platforms: ['mobile', 'api'],
|
|
59
|
+
authProvider: 'auth0',
|
|
60
|
+
});
|
|
61
|
+
// Triple platform factories
|
|
62
|
+
export const createFullStackCognitoConfig = () => createBaseConfig({
|
|
63
|
+
projectName: 'test-full-cognito',
|
|
64
|
+
platforms: ['web', 'mobile', 'api'],
|
|
65
|
+
authProvider: 'cognito',
|
|
66
|
+
});
|
|
67
|
+
export const createFullStackAuth0Config = () => createBaseConfig({
|
|
68
|
+
projectName: 'test-full-auth0',
|
|
69
|
+
platforms: ['web', 'mobile', 'api'],
|
|
70
|
+
authProvider: 'auth0',
|
|
71
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals';
|
|
2
|
+
import { TEST_MATRIX, getConfigsByTier, getConfigByName } from './matrix.js';
|
|
3
|
+
describe('Test Fixtures', () => {
|
|
4
|
+
describe('TEST_MATRIX', () => {
|
|
5
|
+
it('should define exactly 14 configurations', () => {
|
|
6
|
+
expect(TEST_MATRIX).toHaveLength(14);
|
|
7
|
+
});
|
|
8
|
+
it('should have unique names for all configurations', () => {
|
|
9
|
+
const names = TEST_MATRIX.map(c => c.name);
|
|
10
|
+
const uniqueNames = new Set(names);
|
|
11
|
+
expect(uniqueNames.size).toBe(names.length);
|
|
12
|
+
});
|
|
13
|
+
it('should have unique project names for all configurations', () => {
|
|
14
|
+
const projectNames = TEST_MATRIX.map(c => c.config.projectName);
|
|
15
|
+
const uniqueNames = new Set(projectNames);
|
|
16
|
+
expect(uniqueNames.size).toBe(projectNames.length);
|
|
17
|
+
});
|
|
18
|
+
it('should cover all 7 platform combinations', () => {
|
|
19
|
+
const platformSets = TEST_MATRIX.map(c => [...c.config.platforms].sort().join('+'));
|
|
20
|
+
const uniquePlatforms = new Set(platformSets);
|
|
21
|
+
expect(uniquePlatforms.size).toBe(7);
|
|
22
|
+
});
|
|
23
|
+
it('should cover both auth providers (cognito, auth0)', () => {
|
|
24
|
+
const authProviders = TEST_MATRIX.map(c => c.config.auth.provider);
|
|
25
|
+
const uniqueProviders = new Set(authProviders);
|
|
26
|
+
expect(uniqueProviders).toContain('cognito');
|
|
27
|
+
expect(uniqueProviders).toContain('auth0');
|
|
28
|
+
});
|
|
29
|
+
it('should not include auth provider none', () => {
|
|
30
|
+
const authProviders = TEST_MATRIX.map(c => c.config.auth.provider);
|
|
31
|
+
const uniqueProviders = new Set(authProviders);
|
|
32
|
+
expect(uniqueProviders).not.toContain('none');
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
describe('getConfigsByTier', () => {
|
|
36
|
+
it('should return 1 config for smoke tier', () => {
|
|
37
|
+
const configs = getConfigsByTier('smoke');
|
|
38
|
+
expect(configs).toHaveLength(1);
|
|
39
|
+
});
|
|
40
|
+
it('should return 5 configs for core tier (smoke + core)', () => {
|
|
41
|
+
const configs = getConfigsByTier('core');
|
|
42
|
+
expect(configs).toHaveLength(5);
|
|
43
|
+
});
|
|
44
|
+
it('should return all 14 configs for full tier', () => {
|
|
45
|
+
const configs = getConfigsByTier('full');
|
|
46
|
+
expect(configs).toHaveLength(14);
|
|
47
|
+
});
|
|
48
|
+
it('core tier should include at least one config per platform', () => {
|
|
49
|
+
const configs = getConfigsByTier('core');
|
|
50
|
+
const platforms = new Set();
|
|
51
|
+
for (const config of configs) {
|
|
52
|
+
for (const platform of config.config.platforms) {
|
|
53
|
+
platforms.add(platform);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
expect(platforms).toContain('web');
|
|
57
|
+
expect(platforms).toContain('mobile');
|
|
58
|
+
expect(platforms).toContain('api');
|
|
59
|
+
});
|
|
60
|
+
it('core tier should include at least one config per auth provider', () => {
|
|
61
|
+
const configs = getConfigsByTier('core');
|
|
62
|
+
const providers = new Set(configs.map(c => c.config.auth.provider));
|
|
63
|
+
expect(providers).toContain('cognito');
|
|
64
|
+
expect(providers).toContain('auth0');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe('getConfigByName', () => {
|
|
68
|
+
it('should return config when name exists', () => {
|
|
69
|
+
const config = getConfigByName('web-api-cognito');
|
|
70
|
+
expect(config.name).toBe('web-api-cognito');
|
|
71
|
+
});
|
|
72
|
+
it('should throw when name does not exist', () => {
|
|
73
|
+
expect(() => getConfigByName('nonexistent')).toThrow('Configuration "nonexistent" not found in TEST_MATRIX');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe('Config validity', () => {
|
|
77
|
+
it.each(TEST_MATRIX)('$name should have valid ProjectConfig structure', ({ config }) => {
|
|
78
|
+
// Required fields
|
|
79
|
+
expect(config.projectName).toBeDefined();
|
|
80
|
+
expect(config.platforms.length).toBeGreaterThan(0);
|
|
81
|
+
expect(config.awsRegion).toBeDefined();
|
|
82
|
+
expect(config.brandColor).toBeDefined();
|
|
83
|
+
expect(config.auth.provider).toBeDefined();
|
|
84
|
+
// Valid values
|
|
85
|
+
expect(['web', 'mobile', 'api']).toEqual(expect.arrayContaining(config.platforms));
|
|
86
|
+
expect(['cognito', 'auth0', 'none']).toContain(config.auth.provider);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { createWebCognitoConfig, createWebAuth0Config, createMobileCognitoConfig, createMobileAuth0Config, createApiCognitoConfig, createApiAuth0Config, createWebMobileCognitoConfig, createWebMobileAuth0Config, createWebApiCognitoConfig, createWebApiAuth0Config, createMobileApiCognitoConfig, createMobileApiAuth0Config, createFullStackCognitoConfig, createFullStackAuth0Config, } from './config-factories.js';
|
|
2
|
+
export { TEST_MATRIX, getConfigsByTier, getConfigByName, type TestTier, type TestConfiguration, } from './matrix.js';
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { createWebCognitoConfig, createWebAuth0Config, createMobileCognitoConfig, createMobileAuth0Config, createApiCognitoConfig, createApiAuth0Config, createWebMobileCognitoConfig, createWebMobileAuth0Config, createWebApiCognitoConfig, createWebApiAuth0Config, createMobileApiCognitoConfig, createMobileApiAuth0Config, createFullStackCognitoConfig, createFullStackAuth0Config, } from './config-factories.js';
|
|
2
|
+
export { TEST_MATRIX, getConfigsByTier, getConfigByName, } from './matrix.js';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ProjectConfig } from '../../../types.js';
|
|
2
|
+
export type TestTier = 'smoke' | 'core' | 'full';
|
|
3
|
+
export interface TestConfiguration {
|
|
4
|
+
name: string;
|
|
5
|
+
config: ProjectConfig;
|
|
6
|
+
tier: TestTier;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Complete test matrix: 14 configurations (7 platforms x 2 auth providers)
|
|
10
|
+
*
|
|
11
|
+
* Tier assignments:
|
|
12
|
+
* - smoke: 1 config (quick sanity check)
|
|
13
|
+
* - core: 4 configs (PR validation - covers all platforms and auth providers)
|
|
14
|
+
* - full: 9 configs (release validation - remaining combinations)
|
|
15
|
+
*/
|
|
16
|
+
export declare const TEST_MATRIX: TestConfiguration[];
|
|
17
|
+
/**
|
|
18
|
+
* Get configurations by tier (cumulative)
|
|
19
|
+
* - 'smoke' returns smoke configs only (1)
|
|
20
|
+
* - 'core' returns smoke + core configs (5)
|
|
21
|
+
* - 'full' returns all configs (14)
|
|
22
|
+
*/
|
|
23
|
+
export declare function getConfigsByTier(tier: TestTier): TestConfiguration[];
|
|
24
|
+
/**
|
|
25
|
+
* Get a single configuration by name.
|
|
26
|
+
* Throws if not found (fail-fast for typos).
|
|
27
|
+
*/
|
|
28
|
+
export declare function getConfigByName(name: string): TestConfiguration;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as factories from './config-factories.js';
|
|
2
|
+
/**
|
|
3
|
+
* Complete test matrix: 14 configurations (7 platforms x 2 auth providers)
|
|
4
|
+
*
|
|
5
|
+
* Tier assignments:
|
|
6
|
+
* - smoke: 1 config (quick sanity check)
|
|
7
|
+
* - core: 4 configs (PR validation - covers all platforms and auth providers)
|
|
8
|
+
* - full: 9 configs (release validation - remaining combinations)
|
|
9
|
+
*/
|
|
10
|
+
export const TEST_MATRIX = [
|
|
11
|
+
// === SMOKE TIER (1 config) ===
|
|
12
|
+
// Most common configuration - web+api with cognito
|
|
13
|
+
{ name: 'web-api-cognito', tier: 'smoke', config: factories.createWebApiCognitoConfig() },
|
|
14
|
+
// === CORE TIER (4 configs) ===
|
|
15
|
+
// Ensures every platform and every auth provider is tested on PRs
|
|
16
|
+
{ name: 'web-cognito', tier: 'core', config: factories.createWebCognitoConfig() },
|
|
17
|
+
{ name: 'mobile-auth0', tier: 'core', config: factories.createMobileAuth0Config() },
|
|
18
|
+
{ name: 'api-cognito', tier: 'core', config: factories.createApiCognitoConfig() },
|
|
19
|
+
{ name: 'full-auth0', tier: 'core', config: factories.createFullStackAuth0Config() },
|
|
20
|
+
// === FULL TIER (9 remaining configs) ===
|
|
21
|
+
// All other combinations for release validation
|
|
22
|
+
{ name: 'web-auth0', tier: 'full', config: factories.createWebAuth0Config() },
|
|
23
|
+
{ name: 'mobile-cognito', tier: 'full', config: factories.createMobileCognitoConfig() },
|
|
24
|
+
{ name: 'api-auth0', tier: 'full', config: factories.createApiAuth0Config() },
|
|
25
|
+
{ name: 'web-mobile-cognito', tier: 'full', config: factories.createWebMobileCognitoConfig() },
|
|
26
|
+
{ name: 'web-mobile-auth0', tier: 'full', config: factories.createWebMobileAuth0Config() },
|
|
27
|
+
{ name: 'web-api-auth0', tier: 'full', config: factories.createWebApiAuth0Config() },
|
|
28
|
+
{ name: 'mobile-api-cognito', tier: 'full', config: factories.createMobileApiCognitoConfig() },
|
|
29
|
+
{ name: 'mobile-api-auth0', tier: 'full', config: factories.createMobileApiAuth0Config() },
|
|
30
|
+
{ name: 'full-cognito', tier: 'full', config: factories.createFullStackCognitoConfig() },
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* Get configurations by tier (cumulative)
|
|
34
|
+
* - 'smoke' returns smoke configs only (1)
|
|
35
|
+
* - 'core' returns smoke + core configs (5)
|
|
36
|
+
* - 'full' returns all configs (14)
|
|
37
|
+
*/
|
|
38
|
+
export function getConfigsByTier(tier) {
|
|
39
|
+
switch (tier) {
|
|
40
|
+
case 'smoke':
|
|
41
|
+
return TEST_MATRIX.filter(c => c.tier === 'smoke');
|
|
42
|
+
case 'core':
|
|
43
|
+
return TEST_MATRIX.filter(c => c.tier === 'smoke' || c.tier === 'core');
|
|
44
|
+
case 'full':
|
|
45
|
+
return TEST_MATRIX;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get a single configuration by name.
|
|
50
|
+
* Throws if not found (fail-fast for typos).
|
|
51
|
+
*/
|
|
52
|
+
export function getConfigByName(name) {
|
|
53
|
+
const config = TEST_MATRIX.find(c => c.name === name);
|
|
54
|
+
if (!config) {
|
|
55
|
+
throw new Error(`Configuration "${name}" not found in TEST_MATRIX`);
|
|
56
|
+
}
|
|
57
|
+
return config;
|
|
58
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import ora from 'ora';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import { getConfigsByTier, getConfigByName, TEST_MATRIX } from './fixtures/index.js';
|
|
4
|
+
import { validateGeneratedProject } from './validate-project.js';
|
|
5
|
+
// CI detection: disable spinner animations in CI environments
|
|
6
|
+
const isCI = process.env.CI === 'true' || !process.stdout.isTTY;
|
|
7
|
+
export async function runValidationSuite(tier = 'core') {
|
|
8
|
+
const configs = getConfigsByTier(tier);
|
|
9
|
+
const results = [];
|
|
10
|
+
const spinner = isCI ? null : ora();
|
|
11
|
+
console.log(pc.bold(`\nRunning ${configs.length} validation tests (tier: ${tier})\n`));
|
|
12
|
+
for (let i = 0; i < configs.length; i++) {
|
|
13
|
+
const config = configs[i];
|
|
14
|
+
const progress = `Testing ${i + 1}/${configs.length}: ${config.name}`;
|
|
15
|
+
// Show progress indicator
|
|
16
|
+
if (spinner) {
|
|
17
|
+
spinner.start(progress);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
console.log(progress);
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const result = await validateGeneratedProject(config.config);
|
|
24
|
+
results.push(result);
|
|
25
|
+
if (result.success) {
|
|
26
|
+
if (spinner) {
|
|
27
|
+
spinner.succeed(pc.green(progress));
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
console.log(pc.green(`✓ PASS: ${progress}`));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
const failMsg = `${progress} (failed at ${result.failedStep})`;
|
|
35
|
+
if (spinner) {
|
|
36
|
+
spinner.fail(pc.red(failMsg));
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
console.error(pc.red(`✗ FAIL: ${failMsg}`));
|
|
40
|
+
}
|
|
41
|
+
// Display error output immediately after failure
|
|
42
|
+
const failedStep = result.steps.find(s => !s.success);
|
|
43
|
+
if (failedStep) {
|
|
44
|
+
console.error(pc.red('\n--- Error Output ---'));
|
|
45
|
+
console.error(failedStep.output);
|
|
46
|
+
console.error(pc.red('--- End Error Output ---\n'));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
const exceptionMsg = `${progress} (exception)`;
|
|
52
|
+
if (spinner) {
|
|
53
|
+
spinner.fail(pc.red(exceptionMsg));
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
console.error(pc.red(`✗ FAIL: ${exceptionMsg}`));
|
|
57
|
+
}
|
|
58
|
+
console.error(error);
|
|
59
|
+
// Push a failed result for the summary
|
|
60
|
+
results.push({
|
|
61
|
+
success: false,
|
|
62
|
+
steps: [],
|
|
63
|
+
totalDuration: 0,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
// Ensure spinner is stopped even on exception
|
|
68
|
+
if (spinner && spinner.isSpinning) {
|
|
69
|
+
spinner.stop();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Display summary table
|
|
74
|
+
displaySummary(results, configs);
|
|
75
|
+
// Exit with appropriate code for CI integration
|
|
76
|
+
const allPassed = results.every(r => r.success);
|
|
77
|
+
process.exit(allPassed ? 0 : 1);
|
|
78
|
+
}
|
|
79
|
+
async function runSingleConfig(configName) {
|
|
80
|
+
const spinner = isCI ? null : ora();
|
|
81
|
+
console.log(pc.bold(`\nRunning single configuration: ${configName}\n`));
|
|
82
|
+
const testConfig = getConfigByName(configName);
|
|
83
|
+
const progress = `Testing: ${testConfig.name}`;
|
|
84
|
+
// Show progress indicator
|
|
85
|
+
if (spinner) {
|
|
86
|
+
spinner.start(progress);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
console.log(progress);
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const result = await validateGeneratedProject(testConfig.config);
|
|
93
|
+
if (result.success) {
|
|
94
|
+
if (spinner) {
|
|
95
|
+
spinner.succeed(pc.green(progress));
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
console.log(pc.green(`✓ PASS: ${progress}`));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
const failMsg = `${progress} (failed at ${result.failedStep})`;
|
|
103
|
+
if (spinner) {
|
|
104
|
+
spinner.fail(pc.red(failMsg));
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
console.error(pc.red(`✗ FAIL: ${failMsg}`));
|
|
108
|
+
}
|
|
109
|
+
// Display error output immediately after failure
|
|
110
|
+
const failedStep = result.steps.find(s => !s.success);
|
|
111
|
+
if (failedStep) {
|
|
112
|
+
console.error(pc.red('\n--- Error Output ---'));
|
|
113
|
+
console.error(failedStep.output);
|
|
114
|
+
console.error(pc.red('--- End Error Output ---\n'));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Display summary table
|
|
118
|
+
displaySummary([result], [testConfig]);
|
|
119
|
+
// Exit with appropriate code for CI integration
|
|
120
|
+
process.exit(result.success ? 0 : 1);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
const exceptionMsg = `${progress} (exception)`;
|
|
124
|
+
if (spinner) {
|
|
125
|
+
spinner.fail(pc.red(exceptionMsg));
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
console.error(pc.red(`✗ FAIL: ${exceptionMsg}`));
|
|
129
|
+
}
|
|
130
|
+
console.error(error);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
// Ensure spinner is stopped even on exception
|
|
135
|
+
if (spinner && spinner.isSpinning) {
|
|
136
|
+
spinner.stop();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function displaySummary(results, configs) {
|
|
141
|
+
const rows = results.map((result, i) => ({
|
|
142
|
+
config: configs[i].name,
|
|
143
|
+
status: result.success ? pc.green('✓ PASS') : pc.red('✗ FAIL'),
|
|
144
|
+
failedStep: result.failedStep ?? '-',
|
|
145
|
+
duration: `${(result.totalDuration / 1000).toFixed(1)}s`,
|
|
146
|
+
}));
|
|
147
|
+
console.log(pc.bold('\n=== Validation Summary ===\n'));
|
|
148
|
+
console.table(rows);
|
|
149
|
+
const passed = results.filter(r => r.success).length;
|
|
150
|
+
const total = results.length;
|
|
151
|
+
const passRate = total > 0 ? ((passed / total) * 100).toFixed(1) : '0.0';
|
|
152
|
+
console.log(pc.bold(`\nResults: ${passed}/${total} passed (${passRate}%)`));
|
|
153
|
+
}
|
|
154
|
+
// CLI entry point - parse tier or config name argument and run
|
|
155
|
+
const arg = process.argv[2] ?? 'core';
|
|
156
|
+
// Validate argument - check if it's a valid tier or config name
|
|
157
|
+
const validTiers = ['smoke', 'core', 'full'];
|
|
158
|
+
const validConfigNames = TEST_MATRIX.map(c => c.name);
|
|
159
|
+
if (validTiers.includes(arg)) {
|
|
160
|
+
// It's a tier - run the validation suite
|
|
161
|
+
runValidationSuite(arg).catch((error) => {
|
|
162
|
+
console.error(pc.red('\nFatal error during validation:'));
|
|
163
|
+
console.error(error);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
else if (validConfigNames.includes(arg)) {
|
|
168
|
+
// It's a specific config name - run single config
|
|
169
|
+
runSingleConfig(arg).catch((error) => {
|
|
170
|
+
console.error(pc.red('\nFatal error during validation:'));
|
|
171
|
+
console.error(error);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
// Invalid argument - show error with all valid options
|
|
177
|
+
console.error(pc.red(`\nError: Invalid argument "${arg}"`));
|
|
178
|
+
console.error(pc.bold('\nValid tiers:'));
|
|
179
|
+
console.error(` ${validTiers.join(', ')}`);
|
|
180
|
+
console.error(pc.bold('\nValid configuration names:'));
|
|
181
|
+
console.error(` ${validConfigNames.join(', ')}`);
|
|
182
|
+
console.error('');
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
export async function runCommand(command, args, cwd, timeout = 600000 // 10 minutes default
|
|
3
|
+
) {
|
|
4
|
+
try {
|
|
5
|
+
const result = await execa(command, args, {
|
|
6
|
+
cwd,
|
|
7
|
+
all: true,
|
|
8
|
+
timeout,
|
|
9
|
+
});
|
|
10
|
+
return {
|
|
11
|
+
success: true,
|
|
12
|
+
exitCode: result.exitCode ?? 0,
|
|
13
|
+
output: result.all ?? '',
|
|
14
|
+
timedOut: false,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
const execaError = error;
|
|
19
|
+
return {
|
|
20
|
+
success: false,
|
|
21
|
+
exitCode: execaError.exitCode ?? 1,
|
|
22
|
+
output: execaError.all ?? execaError.message ?? '',
|
|
23
|
+
timedOut: execaError.timedOut ?? false,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|