create-aws-project 1.5.1 → 1.7.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.
- package/dist/__tests__/aws/cdk-bootstrap.spec.d.ts +1 -0
- package/dist/__tests__/aws/cdk-bootstrap.spec.js +266 -0
- package/dist/__tests__/aws/root-credentials.spec.d.ts +1 -0
- package/dist/__tests__/aws/root-credentials.spec.js +230 -0
- package/dist/__tests__/config/non-interactive-aws.spec.d.ts +1 -0
- package/dist/__tests__/config/non-interactive-aws.spec.js +100 -0
- package/dist/__tests__/config/non-interactive.spec.d.ts +1 -0
- package/dist/__tests__/config/non-interactive.spec.js +152 -0
- package/dist/aws/cdk-bootstrap.d.ts +77 -0
- package/dist/aws/cdk-bootstrap.js +139 -0
- package/dist/aws/iam.js +17 -0
- package/dist/aws/organizations.d.ts +7 -1
- package/dist/aws/organizations.js +19 -1
- package/dist/aws/root-credentials.d.ts +68 -0
- package/dist/aws/root-credentials.js +157 -0
- package/dist/cli.js +58 -23
- package/dist/commands/initialize-github.js +162 -83
- package/dist/commands/setup-aws-envs.d.ts +2 -2
- package/dist/commands/setup-aws-envs.js +590 -52
- package/dist/config/non-interactive-aws.d.ts +27 -0
- package/dist/config/non-interactive-aws.js +80 -0
- package/dist/config/non-interactive.d.ts +48 -0
- package/dist/config/non-interactive.js +92 -0
- package/dist/git/setup.d.ts +3 -3
- package/dist/git/setup.js +28 -24
- package/dist/utils/project-context.d.ts +14 -0
- package/package.json +4 -3
- package/templates/apps/web/src/App.tsx +4 -2
- package/templates/apps/web/src/__tests__/App.spec.tsx +27 -9
- package/templates/apps/web/vite.config.ts +3 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as z from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Zod schema for the non-interactive JSON config file for setup-aws-envs.
|
|
4
|
+
* Only `email` is required — all other AWS setup config lives in .aws-starter-config.json.
|
|
5
|
+
*/
|
|
6
|
+
export declare const SetupAwsEnvsConfigSchema: z.ZodObject<{
|
|
7
|
+
email: z.ZodString;
|
|
8
|
+
}, z.core.$strip>;
|
|
9
|
+
export type SetupAwsEnvsConfig = z.infer<typeof SetupAwsEnvsConfigSchema>;
|
|
10
|
+
/**
|
|
11
|
+
* Load, validate, and return a SetupAwsEnvsConfig from a JSON config file path.
|
|
12
|
+
* Exits with code 1 and prints all errors if validation fails.
|
|
13
|
+
* Also exits with code 1 if the email does not contain an '@' sign.
|
|
14
|
+
*/
|
|
15
|
+
export declare function loadSetupAwsEnvsConfig(configPath: string): SetupAwsEnvsConfig;
|
|
16
|
+
/**
|
|
17
|
+
* Derive per-environment email addresses from a root email.
|
|
18
|
+
* Inserts -{env} between the local part and the domain.
|
|
19
|
+
*
|
|
20
|
+
* Example:
|
|
21
|
+
* deriveEnvironmentEmails('owner@example.com', ['dev', 'stage', 'prod'])
|
|
22
|
+
* → { dev: 'owner-dev@example.com', stage: 'owner-stage@example.com', prod: 'owner-prod@example.com' }
|
|
23
|
+
*
|
|
24
|
+
* Handles plus aliases: 'user+tag@company.com' → 'user+tag-dev@company.com'
|
|
25
|
+
* Handles subdomains: 'admin@sub.example.com' → 'admin-dev@sub.example.com'
|
|
26
|
+
*/
|
|
27
|
+
export declare function deriveEnvironmentEmails(rootEmail: string, environments: readonly string[]): Record<string, string>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as z from 'zod';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import pc from 'picocolors';
|
|
5
|
+
/**
|
|
6
|
+
* Zod schema for the non-interactive JSON config file for setup-aws-envs.
|
|
7
|
+
* Only `email` is required — all other AWS setup config lives in .aws-starter-config.json.
|
|
8
|
+
*/
|
|
9
|
+
export const SetupAwsEnvsConfigSchema = z.object({
|
|
10
|
+
email: z.string().min(1, { message: 'email is required' }),
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* Load, validate, and return a SetupAwsEnvsConfig from a JSON config file path.
|
|
14
|
+
* Exits with code 1 and prints all errors if validation fails.
|
|
15
|
+
* Also exits with code 1 if the email does not contain an '@' sign.
|
|
16
|
+
*/
|
|
17
|
+
export function loadSetupAwsEnvsConfig(configPath) {
|
|
18
|
+
// 1. Resolve path relative to cwd
|
|
19
|
+
const absolutePath = resolve(process.cwd(), configPath);
|
|
20
|
+
// 2. Read file — fail fast if not found or unreadable
|
|
21
|
+
let rawContent;
|
|
22
|
+
try {
|
|
23
|
+
rawContent = readFileSync(absolutePath, 'utf-8');
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
console.error(pc.red('Error:') + ` Cannot read config file: ${absolutePath}`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
// 3. Parse JSON — fail fast if invalid
|
|
30
|
+
let rawData;
|
|
31
|
+
try {
|
|
32
|
+
rawData = JSON.parse(rawContent);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
console.error(pc.red('Error:') + ` Config file is not valid JSON: ${absolutePath}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
// 4. Validate with Zod — collect ALL errors in one pass
|
|
39
|
+
const result = SetupAwsEnvsConfigSchema.safeParse(rawData);
|
|
40
|
+
if (!result.success) {
|
|
41
|
+
console.error(pc.red('Error:') + ' Invalid config file:');
|
|
42
|
+
console.error('');
|
|
43
|
+
for (const issue of result.error.issues) {
|
|
44
|
+
const fieldPath = issue.path.length > 0 ? issue.path.join('.') : '(root)';
|
|
45
|
+
console.error(` ${pc.red('x')} ${fieldPath}: ${issue.message}`);
|
|
46
|
+
}
|
|
47
|
+
console.error('');
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
// 5. Additional email format check — must contain '@' for derivation to work correctly
|
|
51
|
+
if (!result.data.email.includes('@')) {
|
|
52
|
+
console.error(pc.red('Error:') + ' Invalid config file:');
|
|
53
|
+
console.error('');
|
|
54
|
+
console.error(` ${pc.red('x')} email: must be a valid email address`);
|
|
55
|
+
console.error('');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
return result.data;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Derive per-environment email addresses from a root email.
|
|
62
|
+
* Inserts -{env} between the local part and the domain.
|
|
63
|
+
*
|
|
64
|
+
* Example:
|
|
65
|
+
* deriveEnvironmentEmails('owner@example.com', ['dev', 'stage', 'prod'])
|
|
66
|
+
* → { dev: 'owner-dev@example.com', stage: 'owner-stage@example.com', prod: 'owner-prod@example.com' }
|
|
67
|
+
*
|
|
68
|
+
* Handles plus aliases: 'user+tag@company.com' → 'user+tag-dev@company.com'
|
|
69
|
+
* Handles subdomains: 'admin@sub.example.com' → 'admin-dev@sub.example.com'
|
|
70
|
+
*/
|
|
71
|
+
export function deriveEnvironmentEmails(rootEmail, environments) {
|
|
72
|
+
const atIndex = rootEmail.lastIndexOf('@');
|
|
73
|
+
const localPart = rootEmail.slice(0, atIndex);
|
|
74
|
+
const domain = rootEmail.slice(atIndex); // includes '@'
|
|
75
|
+
const derived = {};
|
|
76
|
+
for (const env of environments) {
|
|
77
|
+
derived[env] = `${localPart}-${env}${domain}`;
|
|
78
|
+
}
|
|
79
|
+
return derived;
|
|
80
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as z from 'zod';
|
|
2
|
+
import type { ProjectConfig } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Zod schema for the non-interactive JSON config file.
|
|
5
|
+
* Only `name` is required; all other fields have defaults matching NI-04 spec.
|
|
6
|
+
*/
|
|
7
|
+
export declare const NonInteractiveConfigSchema: z.ZodObject<{
|
|
8
|
+
name: z.ZodString;
|
|
9
|
+
platforms: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
10
|
+
web: "web";
|
|
11
|
+
mobile: "mobile";
|
|
12
|
+
api: "api";
|
|
13
|
+
}>>>;
|
|
14
|
+
auth: z.ZodDefault<z.ZodEnum<{
|
|
15
|
+
cognito: "cognito";
|
|
16
|
+
auth0: "auth0";
|
|
17
|
+
none: "none";
|
|
18
|
+
}>>;
|
|
19
|
+
authFeatures: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
20
|
+
"social-login": "social-login";
|
|
21
|
+
mfa: "mfa";
|
|
22
|
+
}>>>;
|
|
23
|
+
features: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
24
|
+
"github-actions": "github-actions";
|
|
25
|
+
"vscode-config": "vscode-config";
|
|
26
|
+
}>>>;
|
|
27
|
+
region: z.ZodDefault<z.ZodEnum<{
|
|
28
|
+
"us-east-1": "us-east-1";
|
|
29
|
+
"us-west-2": "us-west-2";
|
|
30
|
+
"eu-west-1": "eu-west-1";
|
|
31
|
+
"eu-central-1": "eu-central-1";
|
|
32
|
+
"ap-northeast-1": "ap-northeast-1";
|
|
33
|
+
"ap-southeast-2": "ap-southeast-2";
|
|
34
|
+
}>>;
|
|
35
|
+
brandColor: z.ZodDefault<z.ZodEnum<{
|
|
36
|
+
blue: "blue";
|
|
37
|
+
purple: "purple";
|
|
38
|
+
teal: "teal";
|
|
39
|
+
green: "green";
|
|
40
|
+
orange: "orange";
|
|
41
|
+
}>>;
|
|
42
|
+
}, z.core.$strip>;
|
|
43
|
+
export type NonInteractiveConfig = z.infer<typeof NonInteractiveConfigSchema>;
|
|
44
|
+
/**
|
|
45
|
+
* Load, validate, and return a ProjectConfig from a JSON config file path.
|
|
46
|
+
* Exits with code 1 and prints all errors if validation fails.
|
|
47
|
+
*/
|
|
48
|
+
export declare function loadNonInteractiveConfig(configPath: string): ProjectConfig;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as z from 'zod';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import pc from 'picocolors';
|
|
5
|
+
import { validateProjectName } from '../validation/project-name.js';
|
|
6
|
+
// Valid value constants (as const tuples for Zod enum compatibility)
|
|
7
|
+
const VALID_PLATFORMS = ['web', 'mobile', 'api'];
|
|
8
|
+
const VALID_AUTH_PROVIDERS = ['none', 'cognito', 'auth0'];
|
|
9
|
+
const VALID_AUTH_FEATURES = ['social-login', 'mfa'];
|
|
10
|
+
const VALID_FEATURES = ['github-actions', 'vscode-config'];
|
|
11
|
+
const VALID_REGIONS = [
|
|
12
|
+
'us-east-1',
|
|
13
|
+
'us-west-2',
|
|
14
|
+
'eu-west-1',
|
|
15
|
+
'eu-central-1',
|
|
16
|
+
'ap-northeast-1',
|
|
17
|
+
'ap-southeast-2',
|
|
18
|
+
];
|
|
19
|
+
const VALID_BRAND_COLORS = ['blue', 'purple', 'teal', 'green', 'orange'];
|
|
20
|
+
/**
|
|
21
|
+
* Zod schema for the non-interactive JSON config file.
|
|
22
|
+
* Only `name` is required; all other fields have defaults matching NI-04 spec.
|
|
23
|
+
*/
|
|
24
|
+
export const NonInteractiveConfigSchema = z.object({
|
|
25
|
+
name: z.string().min(1, { message: 'name is required' }),
|
|
26
|
+
platforms: z.array(z.enum(VALID_PLATFORMS)).min(1).default(['web', 'api']),
|
|
27
|
+
auth: z.enum(VALID_AUTH_PROVIDERS).default('none'),
|
|
28
|
+
authFeatures: z.array(z.enum(VALID_AUTH_FEATURES)).default([]),
|
|
29
|
+
features: z.array(z.enum(VALID_FEATURES)).default(['github-actions', 'vscode-config']),
|
|
30
|
+
region: z.enum(VALID_REGIONS).default('us-east-1'),
|
|
31
|
+
brandColor: z.enum(VALID_BRAND_COLORS).default('blue'),
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* Load, validate, and return a ProjectConfig from a JSON config file path.
|
|
35
|
+
* Exits with code 1 and prints all errors if validation fails.
|
|
36
|
+
*/
|
|
37
|
+
export function loadNonInteractiveConfig(configPath) {
|
|
38
|
+
// 1. Resolve path relative to cwd
|
|
39
|
+
const absolutePath = resolve(process.cwd(), configPath);
|
|
40
|
+
// 2. Read file — fail fast if not found or unreadable
|
|
41
|
+
let rawContent;
|
|
42
|
+
try {
|
|
43
|
+
rawContent = readFileSync(absolutePath, 'utf-8');
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
console.error(pc.red('Error:') + ` Cannot read config file: ${absolutePath}`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
// 3. Parse JSON — fail fast if invalid
|
|
50
|
+
let rawData;
|
|
51
|
+
try {
|
|
52
|
+
rawData = JSON.parse(rawContent);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
console.error(pc.red('Error:') + ` Config file is not valid JSON: ${absolutePath}`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
// 4. Validate with Zod — collect ALL errors in one pass
|
|
59
|
+
const result = NonInteractiveConfigSchema.safeParse(rawData);
|
|
60
|
+
if (!result.success) {
|
|
61
|
+
console.error(pc.red('Error:') + ' Invalid config file:');
|
|
62
|
+
console.error('');
|
|
63
|
+
for (const issue of result.error.issues) {
|
|
64
|
+
const fieldPath = issue.path.length > 0 ? issue.path.join('.') : '(root)';
|
|
65
|
+
console.error(` ${pc.red('x')} ${fieldPath}: ${issue.message}`);
|
|
66
|
+
}
|
|
67
|
+
console.error('');
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
const cfg = result.data;
|
|
71
|
+
// 5. Additional project name validation via existing npm package name validator
|
|
72
|
+
const nameValidation = validateProjectName(cfg.name);
|
|
73
|
+
if (nameValidation !== true) {
|
|
74
|
+
console.error(pc.red('Error:') + ' Invalid config file:');
|
|
75
|
+
console.error('');
|
|
76
|
+
console.error(` ${pc.red('x')} name: ${nameValidation}`);
|
|
77
|
+
console.error('');
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
// 6. Map to ProjectConfig — silently drop authFeatures when auth is 'none'
|
|
81
|
+
return {
|
|
82
|
+
projectName: cfg.name,
|
|
83
|
+
platforms: cfg.platforms,
|
|
84
|
+
awsRegion: cfg.region,
|
|
85
|
+
features: cfg.features,
|
|
86
|
+
brandColor: cfg.brandColor,
|
|
87
|
+
auth: {
|
|
88
|
+
provider: cfg.auth,
|
|
89
|
+
features: cfg.auth === 'none' ? [] : cfg.authFeatures,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
package/dist/git/setup.d.ts
CHANGED
|
@@ -19,10 +19,10 @@ export declare function promptGitSetup(): Promise<{
|
|
|
19
19
|
pat: string;
|
|
20
20
|
} | null>;
|
|
21
21
|
/**
|
|
22
|
-
* Sets up git repository
|
|
23
|
-
*
|
|
22
|
+
* Sets up local git repository and ensures GitHub remote exists
|
|
23
|
+
* Does NOT commit or push — code is pushed after AWS accounts are configured
|
|
24
24
|
* @param projectDir - Path to the project directory
|
|
25
25
|
* @param repoUrl - GitHub repository URL
|
|
26
|
-
* @param pat - GitHub Personal Access Token
|
|
26
|
+
* @param pat - GitHub Personal Access Token (used to create repo if needed)
|
|
27
27
|
*/
|
|
28
28
|
export declare function setupGitRepository(projectDir: string, repoUrl: string, pat: string): Promise<void>;
|
package/dist/git/setup.js
CHANGED
|
@@ -83,11 +83,11 @@ export async function promptGitSetup() {
|
|
|
83
83
|
return { repoUrl, pat: patResponse.pat };
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
|
-
* Sets up git repository
|
|
87
|
-
*
|
|
86
|
+
* Sets up local git repository and ensures GitHub remote exists
|
|
87
|
+
* Does NOT commit or push — code is pushed after AWS accounts are configured
|
|
88
88
|
* @param projectDir - Path to the project directory
|
|
89
89
|
* @param repoUrl - GitHub repository URL
|
|
90
|
-
* @param pat - GitHub Personal Access Token
|
|
90
|
+
* @param pat - GitHub Personal Access Token (used to create repo if needed)
|
|
91
91
|
*/
|
|
92
92
|
export async function setupGitRepository(projectDir, repoUrl, pat) {
|
|
93
93
|
const spinner = ora();
|
|
@@ -99,19 +99,8 @@ export async function setupGitRepository(projectDir, repoUrl, pat) {
|
|
|
99
99
|
// Git init
|
|
100
100
|
spinner.start('Initializing git repository...');
|
|
101
101
|
execSync('git init -b main', { cwd: projectDir, stdio: 'pipe' });
|
|
102
|
-
// Check for git user config, set if not configured
|
|
103
|
-
try {
|
|
104
|
-
execSync('git config user.name', { cwd: projectDir, stdio: 'pipe' });
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
// User config not set, use defaults
|
|
108
|
-
execSync('git config user.name "create-aws-project"', { cwd: projectDir, stdio: 'pipe' });
|
|
109
|
-
execSync('git config user.email "noreply@create-aws-project"', { cwd: projectDir, stdio: 'pipe' });
|
|
110
|
-
}
|
|
111
|
-
execSync('git add .', { cwd: projectDir, stdio: 'pipe' });
|
|
112
|
-
execSync('git commit -m "Initial commit from create-aws-project"', { cwd: projectDir, stdio: 'pipe' });
|
|
113
102
|
spinner.succeed('Git repository initialized');
|
|
114
|
-
// Ensure remote repo exists
|
|
103
|
+
// Ensure remote repo exists on GitHub
|
|
115
104
|
spinner.start('Checking GitHub repository...');
|
|
116
105
|
try {
|
|
117
106
|
await octokit.rest.repos.get({ owner, repo });
|
|
@@ -144,22 +133,37 @@ export async function setupGitRepository(projectDir, repoUrl, pat) {
|
|
|
144
133
|
throw error;
|
|
145
134
|
}
|
|
146
135
|
}
|
|
147
|
-
//
|
|
148
|
-
spinner.start('
|
|
149
|
-
const authUrl = `https://${pat}@github.com/${owner}/${repo}.git`;
|
|
150
|
-
execSync(`git remote add origin ${authUrl}`, { cwd: projectDir, stdio: 'pipe' });
|
|
151
|
-
execSync('git push -u origin main', { cwd: projectDir, stdio: 'pipe' });
|
|
152
|
-
// CRITICAL: Remove PAT from .git/config
|
|
136
|
+
// Set origin remote (clean URL, no PAT embedded)
|
|
137
|
+
spinner.start('Setting remote origin...');
|
|
153
138
|
const cleanUrl = `https://github.com/${owner}/${repo}.git`;
|
|
154
|
-
execSync(`git remote
|
|
155
|
-
spinner.succeed(`
|
|
139
|
+
execSync(`git remote add origin ${cleanUrl}`, { cwd: projectDir, stdio: 'pipe' });
|
|
140
|
+
spinner.succeed(`Origin set to ${owner}/${repo}`);
|
|
141
|
+
// Guide user on next steps
|
|
142
|
+
console.log('');
|
|
143
|
+
console.log(pc.dim('Code will be committed and pushed after AWS setup is complete.'));
|
|
144
|
+
console.log(pc.dim('Next: run setup-aws-envs to configure AWS accounts.'));
|
|
156
145
|
}
|
|
157
146
|
catch (error) {
|
|
158
147
|
// Git setup failure should not prevent the user from using their project
|
|
159
148
|
if (spinner.isSpinning) {
|
|
160
149
|
spinner.fail();
|
|
161
150
|
}
|
|
162
|
-
|
|
151
|
+
// Detect GitHub auth/permission errors and give actionable guidance
|
|
152
|
+
if (error?.status === 401 || error?.status === 403 || error?.message?.includes('Bad credentials')) {
|
|
153
|
+
console.log('');
|
|
154
|
+
console.log(pc.red('GitHub authentication failed.'));
|
|
155
|
+
console.log('');
|
|
156
|
+
console.log('Create a Personal Access Token at:');
|
|
157
|
+
console.log(` ${pc.cyan('https://github.com/settings/tokens/new')}`);
|
|
158
|
+
console.log('');
|
|
159
|
+
console.log('Required scopes:');
|
|
160
|
+
console.log(` ${pc.bold('repo')} — full repository access`);
|
|
161
|
+
console.log('');
|
|
162
|
+
console.log('Then re-run project creation and enter the new token.');
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
console.log(pc.yellow('Warning:') + ' Git setup failed: ' + (error instanceof Error ? error.message : 'Unknown error'));
|
|
166
|
+
}
|
|
163
167
|
console.log(pc.dim('Your project was created successfully. You can set up git manually.'));
|
|
164
168
|
}
|
|
165
169
|
}
|
|
@@ -9,6 +9,15 @@
|
|
|
9
9
|
* This file is generated by the wizard and contains project settings
|
|
10
10
|
*/
|
|
11
11
|
export declare const CONFIG_FILE = ".aws-starter-config.json";
|
|
12
|
+
/**
|
|
13
|
+
* Deployment credentials for a single environment
|
|
14
|
+
* Created by setup-aws-envs, consumed by initialize-github
|
|
15
|
+
*/
|
|
16
|
+
export interface DeploymentCredentials {
|
|
17
|
+
userName: string;
|
|
18
|
+
accessKeyId: string;
|
|
19
|
+
secretAccessKey: string;
|
|
20
|
+
}
|
|
12
21
|
/**
|
|
13
22
|
* Minimal project config structure for context detection
|
|
14
23
|
* Full config defined in types.ts - this is subset needed for context
|
|
@@ -20,6 +29,11 @@ export interface ProjectConfigMinimal {
|
|
|
20
29
|
configVersion?: string;
|
|
21
30
|
accounts?: Record<string, string>;
|
|
22
31
|
deploymentUsers?: Record<string, string>;
|
|
32
|
+
deploymentCredentials?: Record<string, DeploymentCredentials>;
|
|
33
|
+
adminUser?: {
|
|
34
|
+
userName: string;
|
|
35
|
+
accessKeyId: string;
|
|
36
|
+
};
|
|
23
37
|
}
|
|
24
38
|
/**
|
|
25
39
|
* Project context containing config path, project root, and parsed config
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-aws-project",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "CLI tool to scaffold AWS projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -52,11 +52,12 @@
|
|
|
52
52
|
"@aws-sdk/credential-providers": "^3.971.0",
|
|
53
53
|
"@octokit/rest": "^22.0.1",
|
|
54
54
|
"find-up": "^8.0.0",
|
|
55
|
+
"libsodium-wrappers": "^0.7.15",
|
|
55
56
|
"ora": "^9.1.0",
|
|
56
57
|
"picocolors": "^1.1.1",
|
|
57
58
|
"prompts": "^2.4.2",
|
|
58
|
-
"
|
|
59
|
-
"
|
|
59
|
+
"validate-npm-package-name": "^7.0.2",
|
|
60
|
+
"zod": "^4.3.6"
|
|
60
61
|
},
|
|
61
62
|
"devDependencies": {
|
|
62
63
|
"@types/jest": "^30.0.0",
|
|
@@ -15,15 +15,9 @@ jest.mock('../config/api', () => ({
|
|
|
15
15
|
}));
|
|
16
16
|
|
|
17
17
|
// Mock useAuth hook to provide auth context for tests
|
|
18
|
+
const mockUseAuth = jest.fn();
|
|
18
19
|
jest.mock('../auth/use-auth', () => ({
|
|
19
|
-
useAuth: () => (
|
|
20
|
-
user: null,
|
|
21
|
-
isAuthenticated: false,
|
|
22
|
-
isLoading: false,
|
|
23
|
-
login: jest.fn(),
|
|
24
|
-
logout: jest.fn(),
|
|
25
|
-
getAccessToken: jest.fn().mockResolvedValue(null),
|
|
26
|
-
}),
|
|
20
|
+
useAuth: (...args: unknown[]) => mockUseAuth(...args),
|
|
27
21
|
}));
|
|
28
22
|
|
|
29
23
|
const mockApiClient = apiClient as jest.Mocked<typeof apiClient>;
|
|
@@ -51,6 +45,15 @@ describe('App', () => {
|
|
|
51
45
|
});
|
|
52
46
|
// Reset mocks
|
|
53
47
|
jest.clearAllMocks();
|
|
48
|
+
// Default: not authenticated
|
|
49
|
+
mockUseAuth.mockReturnValue({
|
|
50
|
+
user: null,
|
|
51
|
+
isAuthenticated: false,
|
|
52
|
+
isLoading: false,
|
|
53
|
+
signIn: jest.fn(),
|
|
54
|
+
signOut: jest.fn(),
|
|
55
|
+
getAccessToken: jest.fn().mockResolvedValue(null),
|
|
56
|
+
});
|
|
54
57
|
// Default mock implementations
|
|
55
58
|
mockApiClient.getUsers.mockResolvedValue([]);
|
|
56
59
|
});
|
|
@@ -217,7 +220,16 @@ describe('App', () => {
|
|
|
217
220
|
});
|
|
218
221
|
});
|
|
219
222
|
|
|
220
|
-
it('should fetch users on mount
|
|
223
|
+
it('should fetch users on mount when authenticated', async () => {
|
|
224
|
+
mockUseAuth.mockReturnValue({
|
|
225
|
+
user: { email: 'test@example.com' },
|
|
226
|
+
isAuthenticated: true,
|
|
227
|
+
isLoading: false,
|
|
228
|
+
signIn: jest.fn(),
|
|
229
|
+
signOut: jest.fn(),
|
|
230
|
+
getAccessToken: jest.fn().mockResolvedValue('token'),
|
|
231
|
+
});
|
|
232
|
+
|
|
221
233
|
const mockUsers = [
|
|
222
234
|
{ id: '1', email: 'user1@example.com', name: 'User 1', createdAt: '2024-01-01' },
|
|
223
235
|
{ id: '2', email: 'user2@example.com', name: 'User 2', createdAt: '2024-01-02' },
|
|
@@ -236,6 +248,12 @@ describe('App', () => {
|
|
|
236
248
|
});
|
|
237
249
|
});
|
|
238
250
|
|
|
251
|
+
it('should not fetch users on mount when not authenticated', async () => {
|
|
252
|
+
await renderWithChakra(<App />);
|
|
253
|
+
|
|
254
|
+
expect(mockApiClient.getUsers).not.toHaveBeenCalled();
|
|
255
|
+
});
|
|
256
|
+
|
|
239
257
|
it('should display error message when error state is set', async () => {
|
|
240
258
|
// Set error state before rendering
|
|
241
259
|
useUserStore.setState({
|
|
@@ -15,6 +15,9 @@ export default defineConfig(({ mode }) => {
|
|
|
15
15
|
strictPort: false, // Try next port if 3000 is taken
|
|
16
16
|
open: false, // Don't auto-open browser
|
|
17
17
|
cors: true,
|
|
18
|
+
fs: {
|
|
19
|
+
allow: [path.resolve(__dirname, '../..')],
|
|
20
|
+
},
|
|
18
21
|
// Proxy API requests to backend during development
|
|
19
22
|
// Note: Vite automatically loads VITE_ prefixed env vars
|
|
20
23
|
proxy: mode === 'development' ? {
|