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.
Files changed (39) hide show
  1. package/README.md +54 -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/github/secrets.js +10 -42
  25. package/package.json +8 -3
  26. package/templates/apps/api/src/handlers/users/get-me.ts +2 -2
  27. package/templates/apps/api/src/middleware/auth0-auth.ts +4 -4
  28. package/templates/apps/api/src/middleware/cognito-auth.ts +3 -3
  29. package/templates/apps/api/src/utils/auth-context.ts +1 -1
  30. package/templates/apps/mobile/jest.config.ts +1 -0
  31. package/templates/apps/mobile/src/test-setup.ts +1 -1
  32. package/templates/apps/web/jest.config.ts +1 -0
  33. package/templates/apps/web/src/__mocks__/config/auth0-config.ts +16 -0
  34. package/templates/apps/web/src/__tests__/App.spec.tsx +12 -0
  35. package/templates/apps/web/src/config/amplify-config.ts +23 -25
  36. package/templates/apps/web/src/config/auth0-config.ts +3 -3
  37. package/templates/apps/web/src/test-setup.ts +4 -0
  38. package/templates/apps/web/tsconfig.spec.json +1 -0
  39. 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
- [![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
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-starter-kit [command] [options]
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-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
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: Set Up AWS Environments
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 2: Configure GitHub Environments
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,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 {};