create-aws-project 1.3.0 → 1.5.0

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 (45) hide show
  1. package/README.md +56 -32
  2. package/dist/__tests__/harness/fixtures/config-factories.d.ts +15 -0
  3. package/dist/__tests__/harness/fixtures/config-factories.js +71 -0
  4. package/dist/__tests__/harness/fixtures/fixtures.spec.d.ts +1 -0
  5. package/dist/__tests__/harness/fixtures/fixtures.spec.js +89 -0
  6. package/dist/__tests__/harness/fixtures/index.d.ts +2 -0
  7. package/dist/__tests__/harness/fixtures/index.js +2 -0
  8. package/dist/__tests__/harness/fixtures/matrix.d.ts +28 -0
  9. package/dist/__tests__/harness/fixtures/matrix.js +58 -0
  10. package/dist/__tests__/harness/local-runner.d.ts +2 -0
  11. package/dist/__tests__/harness/local-runner.js +184 -0
  12. package/dist/__tests__/harness/run-command.d.ts +7 -0
  13. package/dist/__tests__/harness/run-command.js +26 -0
  14. package/dist/__tests__/harness/run-command.spec.d.ts +1 -0
  15. package/dist/__tests__/harness/run-command.spec.js +67 -0
  16. package/dist/__tests__/harness/temp-dir.d.ts +1 -0
  17. package/dist/__tests__/harness/temp-dir.js +14 -0
  18. package/dist/__tests__/harness/temp-dir.spec.d.ts +1 -0
  19. package/dist/__tests__/harness/temp-dir.spec.js +40 -0
  20. package/dist/__tests__/harness/validate-project.d.ts +16 -0
  21. package/dist/__tests__/harness/validate-project.js +78 -0
  22. package/dist/__tests__/harness/validate-project.spec.d.ts +1 -0
  23. package/dist/__tests__/harness/validate-project.spec.js +189 -0
  24. package/dist/aws/iam.d.ts +16 -3
  25. package/dist/aws/iam.js +61 -5
  26. package/dist/commands/initialize-github.js +25 -5
  27. package/dist/commands/setup-aws-envs.js +46 -11
  28. package/dist/github/secrets.js +10 -42
  29. package/dist/utils/project-context.d.ts +1 -0
  30. package/package.json +8 -3
  31. package/templates/apps/api/cdk/static-stack.ts +0 -108
  32. package/templates/apps/api/src/handlers/users/get-me.ts +2 -2
  33. package/templates/apps/api/src/middleware/auth0-auth.ts +4 -4
  34. package/templates/apps/api/src/middleware/cognito-auth.ts +3 -3
  35. package/templates/apps/api/src/utils/auth-context.ts +1 -1
  36. package/templates/apps/mobile/jest.config.ts +1 -0
  37. package/templates/apps/mobile/src/test-setup.ts +1 -1
  38. package/templates/apps/web/jest.config.ts +1 -0
  39. package/templates/apps/web/src/__mocks__/config/auth0-config.ts +16 -0
  40. package/templates/apps/web/src/__tests__/App.spec.tsx +12 -0
  41. package/templates/apps/web/src/config/amplify-config.ts +23 -25
  42. package/templates/apps/web/src/config/auth0-config.ts +3 -3
  43. package/templates/apps/web/src/test-setup.ts +4 -0
  44. package/templates/apps/web/tsconfig.spec.json +1 -0
  45. package/templates/root/package.json +12 -9
package/README.md CHANGED
@@ -2,7 +2,9 @@
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
- [![npm version](https://img.shields.io/npm/v/create-aws-starter-kit.svg)](https://www.npmjs.com/package/create-aws-starter-kit)
5
+ [![npm version](https://img.shields.io/npm/v/create-aws-project.svg)](https://www.npmjs.com/package/create-aws-project)
6
+
7
+ ![AWS Architecture Diagram](./aws-architecture-diagram.svg)
6
8
 
7
9
  ## Quick Start
8
10
 
@@ -33,7 +35,7 @@ The generated project is a full-stack Nx monorepo with:
33
35
  ## CLI Options
34
36
 
35
37
  ```
36
- create-aws-starter-kit [command] [options]
38
+ create-aws-project [command] [options]
37
39
 
38
40
  Commands:
39
41
  (default) Create a new project (interactive wizard)
@@ -45,10 +47,10 @@ Options:
45
47
  --version, -v Show version number
46
48
 
47
49
  Examples:
48
- npx create-aws-starter-kit my-app
49
- npx create-aws-starter-kit setup-aws-envs
50
- npx create-aws-starter-kit initialize-github dev
51
- npx create-aws-starter-kit --help
50
+ npx create-aws-project my-app
51
+ npx create-aws-project setup-aws-envs
52
+ npx create-aws-project initialize-github dev
53
+ npx create-aws-project --help
52
54
  ```
53
55
 
54
56
  ## Wizard Prompts
@@ -74,30 +76,6 @@ The interactive wizard will ask you about:
74
76
  - Note: Node 25+ has Jest compatibility issues - use 22.x or 24.x
75
77
  - **npm** - Included with Node.js
76
78
 
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
79
  ## Post-Install Setup
102
80
 
103
81
  After creating your project, you'll set up AWS environments and GitHub deployment. This is a one-time setup.
@@ -109,7 +87,29 @@ Before you begin:
109
87
  - GitHub repository created for your project
110
88
  - GitHub Personal Access Token with "repo" scope ([create one here](https://github.com/settings/tokens/new))
111
89
 
112
- ### Step 1: Set Up AWS Environments
90
+ ### Step 1: connect to your .git project
91
+
92
+ * Initialize the repository
93
+ ```
94
+ git init
95
+ ```
96
+
97
+ * Add the remote repository using the git remote add <name> <url> command. A common practice is to name it origin.
98
+ ```bash
99
+ git remote add origin <REMOTE_URL>
100
+ ```
101
+
102
+ * Verify the connection by listing your remotes. The -v flag shows the URLs.
103
+ ```bash
104
+ git remote -v
105
+ ```
106
+
107
+ * Push your local commits to the remote repository for the first time.
108
+ ```bash
109
+ git push -u origin main
110
+ ```
111
+
112
+ ### Step 2: Set Up AWS Environments
113
113
 
114
114
  From your project directory, run:
115
115
 
@@ -136,7 +136,7 @@ AWS environment setup complete!
136
136
 
137
137
  Account IDs are saved to `.aws-starter-config.json` for the next step.
138
138
 
139
- ### Step 2: Configure GitHub Environments
139
+ ### Step 3: Configure GitHub Environments
140
140
 
141
141
  For each environment, run:
142
142
 
@@ -215,6 +215,30 @@ Your Personal Access Token may be invalid or missing permissions. Ensure:
215
215
 
216
216
  Create a new token at: https://github.com/settings/tokens/new
217
217
 
218
+ ## After Setup
219
+
220
+ Once your project is set up:
221
+
222
+ ```bash
223
+ cd my-project
224
+ npm install
225
+ ```
226
+
227
+ Then start developing:
228
+
229
+ ```bash
230
+ # Start web app
231
+ npm run web
232
+
233
+ # Start mobile app
234
+ npm run mobile
235
+
236
+ # Deploy API to AWS
237
+ npm run cdk:deploy
238
+ ```
239
+
240
+ See the generated project's README for detailed documentation.
241
+
218
242
  ## License
219
243
 
220
244
  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,2 @@
1
+ import { type TestTier } from './fixtures/index.js';
2
+ export declare function runValidationSuite(tier?: TestTier): Promise<void>;
@@ -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,7 @@
1
+ export interface CommandResult {
2
+ success: boolean;
3
+ exitCode: number;
4
+ output: string;
5
+ timedOut: boolean;
6
+ }
7
+ export declare function runCommand(command: string, args: string[], cwd: string, timeout?: number): Promise<CommandResult>;
@@ -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 {};