create-aws-project 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -0
- package/dist/__tests__/generator/replace-tokens.spec.d.ts +1 -0
- package/dist/__tests__/generator/replace-tokens.spec.js +281 -0
- package/dist/__tests__/generator.spec.d.ts +1 -0
- package/dist/__tests__/generator.spec.js +162 -0
- package/dist/__tests__/validation/project-name.spec.d.ts +1 -0
- package/dist/__tests__/validation/project-name.spec.js +57 -0
- package/dist/__tests__/wizard.spec.d.ts +1 -0
- package/dist/__tests__/wizard.spec.js +232 -0
- package/dist/aws/iam.d.ts +75 -0
- package/dist/aws/iam.js +264 -0
- package/dist/aws/organizations.d.ts +79 -0
- package/dist/aws/organizations.js +168 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +206 -0
- package/dist/commands/setup-github.d.ts +4 -0
- package/dist/commands/setup-github.js +185 -0
- package/dist/generator/copy-file.d.ts +15 -0
- package/dist/generator/copy-file.js +56 -0
- package/dist/generator/generate-project.d.ts +14 -0
- package/dist/generator/generate-project.js +81 -0
- package/dist/generator/index.d.ts +4 -0
- package/dist/generator/index.js +3 -0
- package/dist/generator/replace-tokens.d.ts +29 -0
- package/dist/generator/replace-tokens.js +68 -0
- package/dist/github/secrets.d.ts +109 -0
- package/dist/github/secrets.js +275 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/prompts/auth.d.ts +3 -0
- package/dist/prompts/auth.js +23 -0
- package/dist/prompts/aws-config.d.ts +2 -0
- package/dist/prompts/aws-config.js +14 -0
- package/dist/prompts/features.d.ts +2 -0
- package/dist/prompts/features.js +10 -0
- package/dist/prompts/github-setup.d.ts +53 -0
- package/dist/prompts/github-setup.js +208 -0
- package/dist/prompts/org-structure.d.ts +9 -0
- package/dist/prompts/org-structure.js +93 -0
- package/dist/prompts/platforms.d.ts +2 -0
- package/dist/prompts/platforms.js +12 -0
- package/dist/prompts/project-name.d.ts +2 -0
- package/dist/prompts/project-name.js +8 -0
- package/dist/prompts/theme.d.ts +2 -0
- package/dist/prompts/theme.js +14 -0
- package/dist/templates/index.d.ts +4 -0
- package/dist/templates/index.js +2 -0
- package/dist/templates/manifest.d.ts +11 -0
- package/dist/templates/manifest.js +99 -0
- package/dist/templates/tokens.d.ts +39 -0
- package/dist/templates/tokens.js +37 -0
- package/dist/templates/types.d.ts +52 -0
- package/dist/templates/types.js +1 -0
- package/dist/types.d.ts +27 -0
- package/dist/types.js +1 -0
- package/dist/validation/project-name.d.ts +1 -0
- package/dist/validation/project-name.js +12 -0
- package/dist/wizard.d.ts +2 -0
- package/dist/wizard.js +81 -0
- package/package.json +68 -0
- package/templates/.github/actions/build-and-test/action.yml +24 -0
- package/templates/.github/actions/deploy-cdk/action.yml +46 -0
- package/templates/.github/actions/deploy-web/action.yml +72 -0
- package/templates/.github/actions/setup/action.yml +29 -0
- package/templates/.github/pull_request_template.md +15 -0
- package/templates/.github/workflows/deploy-dev.yml +80 -0
- package/templates/.github/workflows/deploy-prod.yml +67 -0
- package/templates/.github/workflows/deploy-stage.yml +77 -0
- package/templates/.github/workflows/pull-request.yml +72 -0
- package/templates/.vscode/extensions.json +7 -0
- package/templates/.vscode/settings.json +67 -0
- package/templates/apps/api/.eslintrc.json +18 -0
- package/templates/apps/api/cdk/app.ts +93 -0
- package/templates/apps/api/cdk/auth/cognito-stack.ts +164 -0
- package/templates/apps/api/cdk/cdk.json +73 -0
- package/templates/apps/api/cdk/deployment-user-stack.ts +187 -0
- package/templates/apps/api/cdk/org-stack.ts +67 -0
- package/templates/apps/api/cdk/static-stack.ts +361 -0
- package/templates/apps/api/cdk/tsconfig.json +39 -0
- package/templates/apps/api/cdk/user-stack.ts +255 -0
- package/templates/apps/api/jest.config.ts +38 -0
- package/templates/apps/api/lambdas.yml +84 -0
- package/templates/apps/api/project.json.template +58 -0
- package/templates/apps/api/src/__tests__/setup.ts +10 -0
- package/templates/apps/api/src/handlers/users/create-user.ts +52 -0
- package/templates/apps/api/src/handlers/users/delete-user.ts +45 -0
- package/templates/apps/api/src/handlers/users/get-me.ts +72 -0
- package/templates/apps/api/src/handlers/users/get-user.ts +45 -0
- package/templates/apps/api/src/handlers/users/get-users.ts +23 -0
- package/templates/apps/api/src/handlers/users/index.ts +17 -0
- package/templates/apps/api/src/handlers/users/update-user.ts +72 -0
- package/templates/apps/api/src/lib/dynamo/dynamo-model.ts +504 -0
- package/templates/apps/api/src/lib/dynamo/index.ts +12 -0
- package/templates/apps/api/src/lib/dynamo/utils.ts +39 -0
- package/templates/apps/api/src/middleware/auth0-auth.ts +97 -0
- package/templates/apps/api/src/middleware/cognito-auth.ts +90 -0
- package/templates/apps/api/src/models/UserModel.ts +109 -0
- package/templates/apps/api/src/schemas/user.schema.ts +44 -0
- package/templates/apps/api/src/services/user-service.ts +108 -0
- package/templates/apps/api/src/utils/auth-context.ts +60 -0
- package/templates/apps/api/src/utils/common/helpers.ts +26 -0
- package/templates/apps/api/src/utils/lambda-handler.ts +148 -0
- package/templates/apps/api/src/utils/response.ts +52 -0
- package/templates/apps/api/src/utils/validator.ts +75 -0
- package/templates/apps/api/tsconfig.app.json +15 -0
- package/templates/apps/api/tsconfig.json +19 -0
- package/templates/apps/api/tsconfig.spec.json +17 -0
- package/templates/apps/mobile/.env.example +5 -0
- package/templates/apps/mobile/.eslintrc.json +33 -0
- package/templates/apps/mobile/app.json +33 -0
- package/templates/apps/mobile/assets/.gitkeep +0 -0
- package/templates/apps/mobile/babel.config.js +19 -0
- package/templates/apps/mobile/index.js +7 -0
- package/templates/apps/mobile/jest.config.ts +22 -0
- package/templates/apps/mobile/metro.config.js +35 -0
- package/templates/apps/mobile/package.json +22 -0
- package/templates/apps/mobile/project.json.template +64 -0
- package/templates/apps/mobile/src/App.tsx +367 -0
- package/templates/apps/mobile/src/__tests__/App.spec.tsx +46 -0
- package/templates/apps/mobile/src/__tests__/store/user-store.spec.ts +156 -0
- package/templates/apps/mobile/src/config/api.ts +16 -0
- package/templates/apps/mobile/src/store/user-store.ts +56 -0
- package/templates/apps/mobile/src/test-setup.ts +10 -0
- package/templates/apps/mobile/tsconfig.json +22 -0
- package/templates/apps/web/.env.example +13 -0
- package/templates/apps/web/.eslintrc.json +26 -0
- package/templates/apps/web/index.html +13 -0
- package/templates/apps/web/jest.config.ts +24 -0
- package/templates/apps/web/package.json +15 -0
- package/templates/apps/web/project.json.template +66 -0
- package/templates/apps/web/src/App.tsx +352 -0
- package/templates/apps/web/src/__mocks__/config/api.ts +41 -0
- package/templates/apps/web/src/__tests__/App.spec.tsx +240 -0
- package/templates/apps/web/src/__tests__/store/user-store.spec.ts +185 -0
- package/templates/apps/web/src/auth/auth0-provider.tsx +103 -0
- package/templates/apps/web/src/auth/cognito-provider.tsx +143 -0
- package/templates/apps/web/src/auth/index.ts +7 -0
- package/templates/apps/web/src/auth/use-auth.ts +16 -0
- package/templates/apps/web/src/config/amplify-config.ts +31 -0
- package/templates/apps/web/src/config/api.ts +38 -0
- package/templates/apps/web/src/config/auth0-config.ts +17 -0
- package/templates/apps/web/src/main.tsx +41 -0
- package/templates/apps/web/src/store/user-store.ts +56 -0
- package/templates/apps/web/src/styles.css +165 -0
- package/templates/apps/web/src/test-setup.ts +1 -0
- package/templates/apps/web/src/theme/index.ts +30 -0
- package/templates/apps/web/src/vite-env.d.ts +19 -0
- package/templates/apps/web/tsconfig.app.json +24 -0
- package/templates/apps/web/tsconfig.json +22 -0
- package/templates/apps/web/tsconfig.spec.json +28 -0
- package/templates/apps/web/vite.config.ts +87 -0
- package/templates/manifest.json +28 -0
- package/templates/packages/api-client/.eslintrc.json +18 -0
- package/templates/packages/api-client/jest.config.ts +13 -0
- package/templates/packages/api-client/package.json +8 -0
- package/templates/packages/api-client/project.json.template +34 -0
- package/templates/packages/api-client/src/__tests__/api-client.spec.ts +408 -0
- package/templates/packages/api-client/src/api-client.ts +201 -0
- package/templates/packages/api-client/src/config.ts +193 -0
- package/templates/packages/api-client/src/index.ts +9 -0
- package/templates/packages/api-client/tsconfig.json +22 -0
- package/templates/packages/api-client/tsconfig.lib.json +11 -0
- package/templates/packages/api-client/tsconfig.spec.json +14 -0
- package/templates/packages/common-types/.eslintrc.json +18 -0
- package/templates/packages/common-types/package.json +6 -0
- package/templates/packages/common-types/project.json.template +26 -0
- package/templates/packages/common-types/src/api.types.ts +24 -0
- package/templates/packages/common-types/src/auth.types.ts +36 -0
- package/templates/packages/common-types/src/common.types.ts +46 -0
- package/templates/packages/common-types/src/index.ts +19 -0
- package/templates/packages/common-types/src/lambda.types.ts +39 -0
- package/templates/packages/common-types/src/user.types.ts +31 -0
- package/templates/packages/common-types/tsconfig.json +19 -0
- package/templates/packages/common-types/tsconfig.lib.json +11 -0
- package/templates/root/.editorconfig +23 -0
- package/templates/root/.nvmrc +1 -0
- package/templates/root/eslint.config.js +61 -0
- package/templates/root/jest.preset.js +16 -0
- package/templates/root/nx.json +29 -0
- package/templates/root/package.json +131 -0
- package/templates/root/tsconfig.base.json +29 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { existsSync, statSync } from 'node:fs';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import pc from 'picocolors';
|
|
5
|
+
import { copyFileWithTokens, copyDirectoryWithTokens } from './copy-file.js';
|
|
6
|
+
import { deriveTokenValues, templateManifest } from '../templates/index.js';
|
|
7
|
+
/**
|
|
8
|
+
* Get the templates directory path
|
|
9
|
+
*/
|
|
10
|
+
function getTemplatesDir() {
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
// Navigate from dist/src/generator to templates/
|
|
14
|
+
return join(__dirname, '..', '..', 'templates');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Copy a template entry (file or directory) to output
|
|
18
|
+
*/
|
|
19
|
+
function copyTemplateEntry(entry, templatesDir, outputDir, tokens) {
|
|
20
|
+
const srcPath = join(templatesDir, entry.src);
|
|
21
|
+
const destPath = join(outputDir, entry.dest);
|
|
22
|
+
if (!existsSync(srcPath)) {
|
|
23
|
+
console.warn(`Warning: Template not found: ${entry.src}`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const stats = statSync(srcPath);
|
|
27
|
+
if (stats.isDirectory()) {
|
|
28
|
+
copyDirectoryWithTokens(srcPath, destPath, tokens);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
copyFileWithTokens(srcPath, destPath, tokens);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Generate a project from templates based on user configuration
|
|
36
|
+
* @param config - User's project configuration from wizard
|
|
37
|
+
* @param outputDir - Directory where project will be created
|
|
38
|
+
* @param options - Optional generation options including progress callback
|
|
39
|
+
*/
|
|
40
|
+
export async function generateProject(config, outputDir, options = {}) {
|
|
41
|
+
const { onProgress = (msg) => console.log(msg) } = options;
|
|
42
|
+
const templatesDir = getTemplatesDir();
|
|
43
|
+
const tokens = deriveTokenValues(config);
|
|
44
|
+
onProgress(pc.cyan('Creating project structure...'));
|
|
45
|
+
// 1. Copy all shared files
|
|
46
|
+
onProgress(' Copying shared configuration...');
|
|
47
|
+
for (const entry of templateManifest.shared) {
|
|
48
|
+
copyTemplateEntry(entry, templatesDir, outputDir, tokens);
|
|
49
|
+
}
|
|
50
|
+
// 2. Copy platform-specific files based on user selection
|
|
51
|
+
for (const platform of config.platforms) {
|
|
52
|
+
onProgress(` Copying ${platform} app...`);
|
|
53
|
+
const platformEntries = templateManifest.byPlatform[platform];
|
|
54
|
+
if (platformEntries) {
|
|
55
|
+
for (const entry of platformEntries) {
|
|
56
|
+
copyTemplateEntry(entry, templatesDir, outputDir, tokens);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// 3. Copy feature-specific files based on user selection
|
|
61
|
+
for (const feature of config.features) {
|
|
62
|
+
onProgress(` Adding ${feature}...`);
|
|
63
|
+
const featureEntries = templateManifest.byFeature[feature];
|
|
64
|
+
if (featureEntries) {
|
|
65
|
+
for (const entry of featureEntries) {
|
|
66
|
+
copyTemplateEntry(entry, templatesDir, outputDir, tokens);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// 4. Copy auth provider-specific files based on user selection
|
|
71
|
+
if (config.auth.provider !== 'none') {
|
|
72
|
+
onProgress(` Adding ${config.auth.provider} authentication...`);
|
|
73
|
+
const authEntries = templateManifest.byAuthProvider[config.auth.provider];
|
|
74
|
+
if (authEntries) {
|
|
75
|
+
for (const entry of authEntries) {
|
|
76
|
+
copyTemplateEntry(entry, templatesDir, outputDir, tokens);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
onProgress(pc.green('✔') + ' Project structure created');
|
|
81
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { TokenValues } from '../templates/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Process conditional blocks in content based on token values.
|
|
4
|
+
*
|
|
5
|
+
* Supports two formats:
|
|
6
|
+
* 1. Comment-wrapped (for TypeScript/JavaScript files):
|
|
7
|
+
* // {{#if TOKEN}}
|
|
8
|
+
* import { Something } from './something';
|
|
9
|
+
* // {{/if TOKEN}}
|
|
10
|
+
*
|
|
11
|
+
* 2. Plain (for other file types):
|
|
12
|
+
* {{#if TOKEN}}
|
|
13
|
+
* Some content
|
|
14
|
+
* {{/if TOKEN}}
|
|
15
|
+
*
|
|
16
|
+
* @param content - File content with conditional blocks
|
|
17
|
+
* @param tokens - Token values (value of 'true' keeps content, anything else removes it)
|
|
18
|
+
* @returns Content with conditional blocks processed
|
|
19
|
+
*/
|
|
20
|
+
export declare function processConditionalBlocks(content: string, tokens: TokenValues): string;
|
|
21
|
+
/**
|
|
22
|
+
* Replace all {{TOKEN}} placeholders in content with actual values.
|
|
23
|
+
* Processes conditional blocks first, then replaces remaining tokens.
|
|
24
|
+
*
|
|
25
|
+
* @param content - File content with {{TOKEN}} placeholders and conditional blocks
|
|
26
|
+
* @param tokens - Token values to substitute
|
|
27
|
+
* @returns Content with conditionals processed and all tokens replaced
|
|
28
|
+
*/
|
|
29
|
+
export declare function replaceTokens(content: string, tokens: TokenValues): string;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { TOKEN_PATTERN } from '../templates/tokens.js';
|
|
2
|
+
/**
|
|
3
|
+
* Pattern to match comment-wrapped conditional blocks:
|
|
4
|
+
* // {{#if TOKEN}}
|
|
5
|
+
* ...content...
|
|
6
|
+
* // {{/if TOKEN}}
|
|
7
|
+
*
|
|
8
|
+
* Uses non-greedy matching and backreference to ensure opening/closing tokens match.
|
|
9
|
+
*/
|
|
10
|
+
const COMMENT_CONDITIONAL_PATTERN = /\/\/\s*\{\{#if\s+(\w+)\}\}\r?\n([\s\S]*?)\/\/\s*\{\{\/if\s+\1\}\}\r?\n?/g;
|
|
11
|
+
/**
|
|
12
|
+
* Pattern to match plain conditional blocks (without comment prefix):
|
|
13
|
+
* {{#if TOKEN}}
|
|
14
|
+
* ...content...
|
|
15
|
+
* {{/if TOKEN}}
|
|
16
|
+
*/
|
|
17
|
+
const PLAIN_CONDITIONAL_PATTERN = /\{\{#if\s+(\w+)\}\}\r?\n?([\s\S]*?)\{\{\/if\s+\1\}\}\r?\n?/g;
|
|
18
|
+
/**
|
|
19
|
+
* Process conditional blocks in content based on token values.
|
|
20
|
+
*
|
|
21
|
+
* Supports two formats:
|
|
22
|
+
* 1. Comment-wrapped (for TypeScript/JavaScript files):
|
|
23
|
+
* // {{#if TOKEN}}
|
|
24
|
+
* import { Something } from './something';
|
|
25
|
+
* // {{/if TOKEN}}
|
|
26
|
+
*
|
|
27
|
+
* 2. Plain (for other file types):
|
|
28
|
+
* {{#if TOKEN}}
|
|
29
|
+
* Some content
|
|
30
|
+
* {{/if TOKEN}}
|
|
31
|
+
*
|
|
32
|
+
* @param content - File content with conditional blocks
|
|
33
|
+
* @param tokens - Token values (value of 'true' keeps content, anything else removes it)
|
|
34
|
+
* @returns Content with conditional blocks processed
|
|
35
|
+
*/
|
|
36
|
+
export function processConditionalBlocks(content, tokens) {
|
|
37
|
+
let result = content;
|
|
38
|
+
// Process comment-wrapped conditionals first (more specific pattern)
|
|
39
|
+
result = result.replace(COMMENT_CONDITIONAL_PATTERN, (_match, tokenName, blockContent) => {
|
|
40
|
+
const tokenValue = tokens[tokenName];
|
|
41
|
+
// Keep content if token value is exactly 'true', otherwise remove entire block
|
|
42
|
+
return tokenValue === 'true' ? blockContent : '';
|
|
43
|
+
});
|
|
44
|
+
// Process plain conditionals
|
|
45
|
+
result = result.replace(PLAIN_CONDITIONAL_PATTERN, (_match, tokenName, blockContent) => {
|
|
46
|
+
const tokenValue = tokens[tokenName];
|
|
47
|
+
// Keep content if token value is exactly 'true', otherwise remove entire block
|
|
48
|
+
return tokenValue === 'true' ? blockContent : '';
|
|
49
|
+
});
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Replace all {{TOKEN}} placeholders in content with actual values.
|
|
54
|
+
* Processes conditional blocks first, then replaces remaining tokens.
|
|
55
|
+
*
|
|
56
|
+
* @param content - File content with {{TOKEN}} placeholders and conditional blocks
|
|
57
|
+
* @param tokens - Token values to substitute
|
|
58
|
+
* @returns Content with conditionals processed and all tokens replaced
|
|
59
|
+
*/
|
|
60
|
+
export function replaceTokens(content, tokens) {
|
|
61
|
+
// Process conditional blocks BEFORE token replacement
|
|
62
|
+
const processedContent = processConditionalBlocks(content, tokens);
|
|
63
|
+
// Then replace individual tokens
|
|
64
|
+
return processedContent.replace(TOKEN_PATTERN, (match, tokenName) => {
|
|
65
|
+
const value = tokens[tokenName];
|
|
66
|
+
return value !== undefined ? value : match; // Keep original if unknown token
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Octokit } from '@octokit/rest';
|
|
2
|
+
/**
|
|
3
|
+
* GitHub secrets service module
|
|
4
|
+
*
|
|
5
|
+
* Provides functions to manage GitHub environment secrets
|
|
6
|
+
* for AWS deployment credentials via the GitHub REST API.
|
|
7
|
+
*
|
|
8
|
+
* Uses GitHub Environments to organize secrets by environment (dev, stage, prod),
|
|
9
|
+
* with the same secret names in each environment (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY).
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Creates a configured Octokit client
|
|
13
|
+
* @param token - GitHub Personal Access Token (requires `repo` scope)
|
|
14
|
+
* @returns Configured Octokit instance
|
|
15
|
+
*/
|
|
16
|
+
export declare function createGitHubClient(token: string): Octokit;
|
|
17
|
+
/**
|
|
18
|
+
* Public key for encrypting secrets
|
|
19
|
+
*/
|
|
20
|
+
export interface PublicKey {
|
|
21
|
+
keyId: string;
|
|
22
|
+
key: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Fetches the repository's public key for encrypting secrets
|
|
26
|
+
* @param client - Octokit instance
|
|
27
|
+
* @param owner - Repository owner (username or organization)
|
|
28
|
+
* @param repo - Repository name
|
|
29
|
+
* @returns Public key ID and base64-encoded key
|
|
30
|
+
*/
|
|
31
|
+
export declare function getRepositoryPublicKey(client: Octokit, owner: string, repo: string): Promise<PublicKey>;
|
|
32
|
+
/**
|
|
33
|
+
* Encrypts a secret value using the repository's public key
|
|
34
|
+
* @param publicKey - Base64-encoded repository public key
|
|
35
|
+
* @param secretValue - Plain text secret value to encrypt
|
|
36
|
+
* @returns Base64-encoded encrypted value
|
|
37
|
+
*/
|
|
38
|
+
export declare function encryptSecret(publicKey: string, secretValue: string): Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Creates or ensures a GitHub environment exists
|
|
41
|
+
* @param client - Octokit instance
|
|
42
|
+
* @param owner - Repository owner
|
|
43
|
+
* @param repo - Repository name
|
|
44
|
+
* @param environmentName - Name of the environment (e.g., dev, stage, prod)
|
|
45
|
+
*/
|
|
46
|
+
export declare function ensureEnvironmentExists(client: Octokit, owner: string, repo: string, environmentName: string): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Fetches the environment's public key for encrypting secrets
|
|
49
|
+
* @param client - Octokit instance
|
|
50
|
+
* @param owner - Repository owner
|
|
51
|
+
* @param repo - Repository name
|
|
52
|
+
* @param environmentName - Name of the environment
|
|
53
|
+
* @returns Public key ID and base64-encoded key
|
|
54
|
+
*/
|
|
55
|
+
export declare function getEnvironmentPublicKey(client: Octokit, owner: string, repo: string, environmentName: string): Promise<PublicKey>;
|
|
56
|
+
/**
|
|
57
|
+
* Sets (creates or updates) an environment secret
|
|
58
|
+
* @param client - Octokit instance
|
|
59
|
+
* @param owner - Repository owner
|
|
60
|
+
* @param repo - Repository name
|
|
61
|
+
* @param environmentName - Name of the environment
|
|
62
|
+
* @param secretName - Name of the secret (e.g., AWS_ACCESS_KEY_ID)
|
|
63
|
+
* @param secretValue - Plain text secret value
|
|
64
|
+
*/
|
|
65
|
+
export declare function setEnvironmentSecret(client: Octokit, owner: string, repo: string, environmentName: string, secretName: string, secretValue: string): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Sets (creates or updates) a repository secret
|
|
68
|
+
* @param client - Octokit instance
|
|
69
|
+
* @param owner - Repository owner
|
|
70
|
+
* @param repo - Repository name
|
|
71
|
+
* @param secretName - Name of the secret (e.g., AWS_ACCESS_KEY_ID_DEV)
|
|
72
|
+
* @param secretValue - Plain text secret value
|
|
73
|
+
*/
|
|
74
|
+
export declare function setRepositorySecret(client: Octokit, owner: string, repo: string, secretName: string, secretValue: string): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Sets AWS credentials as environment secrets for a specific GitHub environment
|
|
77
|
+
*
|
|
78
|
+
* Creates the GitHub environment if it doesn't exist, then sets AWS_ACCESS_KEY_ID
|
|
79
|
+
* and AWS_SECRET_ACCESS_KEY as secrets within that environment.
|
|
80
|
+
*
|
|
81
|
+
* @param client - Octokit instance
|
|
82
|
+
* @param owner - Repository owner
|
|
83
|
+
* @param repo - Repository name
|
|
84
|
+
* @param environment - Environment name (e.g., dev, stage, prod)
|
|
85
|
+
* @param accessKeyId - AWS Access Key ID
|
|
86
|
+
* @param secretAccessKey - AWS Secret Access Key
|
|
87
|
+
*/
|
|
88
|
+
export declare function setEnvironmentCredentials(client: Octokit, owner: string, repo: string, environment: string, accessKeyId: string, secretAccessKey: string): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Parsed GitHub repository URL
|
|
91
|
+
*/
|
|
92
|
+
export interface GitHubRepoInfo {
|
|
93
|
+
owner: string;
|
|
94
|
+
repo: string;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Parses a GitHub repository URL to extract owner and repo
|
|
98
|
+
* @param url - GitHub repository URL in various formats
|
|
99
|
+
* @returns Parsed owner and repo
|
|
100
|
+
* @throws Error if URL format is not recognized
|
|
101
|
+
*
|
|
102
|
+
* Supported formats:
|
|
103
|
+
* - https://github.com/owner/repo
|
|
104
|
+
* - https://github.com/owner/repo.git
|
|
105
|
+
* - git@github.com:owner/repo.git
|
|
106
|
+
* - git@github.com:owner/repo
|
|
107
|
+
* - owner/repo
|
|
108
|
+
*/
|
|
109
|
+
export declare function parseGitHubUrl(url: string): GitHubRepoInfo;
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { Octokit } from '@octokit/rest';
|
|
2
|
+
import nacl from 'tweetnacl';
|
|
3
|
+
import naclUtil from 'tweetnacl-util';
|
|
4
|
+
import pc from 'picocolors';
|
|
5
|
+
const { decodeUTF8, encodeBase64, decodeBase64 } = naclUtil;
|
|
6
|
+
/**
|
|
7
|
+
* GitHub secrets service module
|
|
8
|
+
*
|
|
9
|
+
* Provides functions to manage GitHub environment secrets
|
|
10
|
+
* for AWS deployment credentials via the GitHub REST API.
|
|
11
|
+
*
|
|
12
|
+
* Uses GitHub Environments to organize secrets by environment (dev, stage, prod),
|
|
13
|
+
* with the same secret names in each environment (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY).
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Creates a configured Octokit client
|
|
17
|
+
* @param token - GitHub Personal Access Token (requires `repo` scope)
|
|
18
|
+
* @returns Configured Octokit instance
|
|
19
|
+
*/
|
|
20
|
+
export function createGitHubClient(token) {
|
|
21
|
+
return new Octokit({
|
|
22
|
+
auth: token,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Fetches the repository's public key for encrypting secrets
|
|
27
|
+
* @param client - Octokit instance
|
|
28
|
+
* @param owner - Repository owner (username or organization)
|
|
29
|
+
* @param repo - Repository name
|
|
30
|
+
* @returns Public key ID and base64-encoded key
|
|
31
|
+
*/
|
|
32
|
+
export async function getRepositoryPublicKey(client, owner, repo) {
|
|
33
|
+
try {
|
|
34
|
+
const response = await client.actions.getRepoPublicKey({
|
|
35
|
+
owner,
|
|
36
|
+
repo,
|
|
37
|
+
});
|
|
38
|
+
return {
|
|
39
|
+
keyId: response.data.key_id,
|
|
40
|
+
key: response.data.key,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (error instanceof Error && 'status' in error && error.status === 401) {
|
|
45
|
+
throw new Error('GitHub authentication failed. Ensure your token has the "repo" scope for repository secrets access.');
|
|
46
|
+
}
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Encrypts a secret value using the repository's public key
|
|
52
|
+
* @param publicKey - Base64-encoded repository public key
|
|
53
|
+
* @param secretValue - Plain text secret value to encrypt
|
|
54
|
+
* @returns Base64-encoded encrypted value
|
|
55
|
+
*/
|
|
56
|
+
export async function encryptSecret(publicKey, secretValue) {
|
|
57
|
+
// Decode the public key from base64
|
|
58
|
+
const publicKeyBytes = decodeBase64(publicKey);
|
|
59
|
+
// Convert the secret value to UTF-8 bytes
|
|
60
|
+
const secretBytes = decodeUTF8(secretValue);
|
|
61
|
+
// Encrypt using libsodium sealed box (via tweetnacl)
|
|
62
|
+
const encryptedBytes = naclSealedBox(secretBytes, publicKeyBytes);
|
|
63
|
+
// Return base64-encoded encrypted value
|
|
64
|
+
return encodeBase64(encryptedBytes);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Sealed box encryption compatible with GitHub's libsodium implementation
|
|
68
|
+
*/
|
|
69
|
+
function naclSealedBox(message, publicKey) {
|
|
70
|
+
// Generate ephemeral key pair
|
|
71
|
+
const ephemeralKeyPair = nacl.box.keyPair();
|
|
72
|
+
// Compute nonce from ephemeral public key and recipient public key
|
|
73
|
+
const nonce = new Uint8Array(nacl.box.nonceLength);
|
|
74
|
+
const nonceInput = new Uint8Array(ephemeralKeyPair.publicKey.length + publicKey.length);
|
|
75
|
+
nonceInput.set(ephemeralKeyPair.publicKey);
|
|
76
|
+
nonceInput.set(publicKey, ephemeralKeyPair.publicKey.length);
|
|
77
|
+
// Use first 24 bytes of blake2b hash as nonce (simplified: use crypto hash)
|
|
78
|
+
const hashBytes = naclHash(nonceInput).slice(0, nacl.box.nonceLength);
|
|
79
|
+
nonce.set(hashBytes);
|
|
80
|
+
// Encrypt the message
|
|
81
|
+
const ciphertext = nacl.box(message, nonce, publicKey, ephemeralKeyPair.secretKey);
|
|
82
|
+
if (!ciphertext) {
|
|
83
|
+
throw new Error('Encryption failed');
|
|
84
|
+
}
|
|
85
|
+
// Concatenate ephemeral public key and ciphertext (sealed box format)
|
|
86
|
+
const sealedBox = new Uint8Array(ephemeralKeyPair.publicKey.length + ciphertext.length);
|
|
87
|
+
sealedBox.set(ephemeralKeyPair.publicKey);
|
|
88
|
+
sealedBox.set(ciphertext, ephemeralKeyPair.publicKey.length);
|
|
89
|
+
return sealedBox;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Simple hash function for nonce generation
|
|
93
|
+
*/
|
|
94
|
+
function naclHash(input) {
|
|
95
|
+
return nacl.hash(input);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Creates or ensures a GitHub environment exists
|
|
99
|
+
* @param client - Octokit instance
|
|
100
|
+
* @param owner - Repository owner
|
|
101
|
+
* @param repo - Repository name
|
|
102
|
+
* @param environmentName - Name of the environment (e.g., dev, stage, prod)
|
|
103
|
+
*/
|
|
104
|
+
export async function ensureEnvironmentExists(client, owner, repo, environmentName) {
|
|
105
|
+
try {
|
|
106
|
+
// PUT request creates or updates the environment
|
|
107
|
+
await client.request('PUT /repos/{owner}/{repo}/environments/{environment_name}', {
|
|
108
|
+
owner,
|
|
109
|
+
repo,
|
|
110
|
+
environment_name: environmentName,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (error instanceof Error && 'status' in error && error.status === 401) {
|
|
115
|
+
throw new Error('GitHub authentication failed. Ensure your token has the "repo" scope for environment access.');
|
|
116
|
+
}
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Fetches the environment's public key for encrypting secrets
|
|
122
|
+
* @param client - Octokit instance
|
|
123
|
+
* @param owner - Repository owner
|
|
124
|
+
* @param repo - Repository name
|
|
125
|
+
* @param environmentName - Name of the environment
|
|
126
|
+
* @returns Public key ID and base64-encoded key
|
|
127
|
+
*/
|
|
128
|
+
export async function getEnvironmentPublicKey(client, owner, repo, environmentName) {
|
|
129
|
+
try {
|
|
130
|
+
const response = await client.request('GET /repos/{owner}/{repo}/environments/{environment_name}/secrets/public-key', {
|
|
131
|
+
owner,
|
|
132
|
+
repo,
|
|
133
|
+
environment_name: environmentName,
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
keyId: response.data.key_id,
|
|
137
|
+
key: response.data.key,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
if (error instanceof Error && 'status' in error && error.status === 401) {
|
|
142
|
+
throw new Error('GitHub authentication failed. Ensure your token has the "repo" scope for environment secrets access.');
|
|
143
|
+
}
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Sets (creates or updates) an environment secret
|
|
149
|
+
* @param client - Octokit instance
|
|
150
|
+
* @param owner - Repository owner
|
|
151
|
+
* @param repo - Repository name
|
|
152
|
+
* @param environmentName - Name of the environment
|
|
153
|
+
* @param secretName - Name of the secret (e.g., AWS_ACCESS_KEY_ID)
|
|
154
|
+
* @param secretValue - Plain text secret value
|
|
155
|
+
*/
|
|
156
|
+
export async function setEnvironmentSecret(client, owner, repo, environmentName, secretName, secretValue) {
|
|
157
|
+
try {
|
|
158
|
+
// Get the environment's public key
|
|
159
|
+
const publicKey = await getEnvironmentPublicKey(client, owner, repo, environmentName);
|
|
160
|
+
// Encrypt the secret value
|
|
161
|
+
const encryptedValue = await encryptSecret(publicKey.key, secretValue);
|
|
162
|
+
// Set the environment secret
|
|
163
|
+
await client.request('PUT /repos/{owner}/{repo}/environments/{environment_name}/secrets/{secret_name}', {
|
|
164
|
+
owner,
|
|
165
|
+
repo,
|
|
166
|
+
environment_name: environmentName,
|
|
167
|
+
secret_name: secretName,
|
|
168
|
+
encrypted_value: encryptedValue,
|
|
169
|
+
key_id: publicKey.keyId,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
if (error instanceof Error && 'status' in error && error.status === 401) {
|
|
174
|
+
throw new Error('GitHub authentication failed. Ensure your token has the "repo" scope for environment secrets access.');
|
|
175
|
+
}
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Sets (creates or updates) a repository secret
|
|
181
|
+
* @param client - Octokit instance
|
|
182
|
+
* @param owner - Repository owner
|
|
183
|
+
* @param repo - Repository name
|
|
184
|
+
* @param secretName - Name of the secret (e.g., AWS_ACCESS_KEY_ID_DEV)
|
|
185
|
+
* @param secretValue - Plain text secret value
|
|
186
|
+
*/
|
|
187
|
+
export async function setRepositorySecret(client, owner, repo, secretName, secretValue) {
|
|
188
|
+
try {
|
|
189
|
+
// Get the repository's public key
|
|
190
|
+
const publicKey = await getRepositoryPublicKey(client, owner, repo);
|
|
191
|
+
// Encrypt the secret value
|
|
192
|
+
const encryptedValue = await encryptSecret(publicKey.key, secretValue);
|
|
193
|
+
// Set the secret
|
|
194
|
+
await client.actions.createOrUpdateRepoSecret({
|
|
195
|
+
owner,
|
|
196
|
+
repo,
|
|
197
|
+
secret_name: secretName,
|
|
198
|
+
encrypted_value: encryptedValue,
|
|
199
|
+
key_id: publicKey.keyId,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
if (error instanceof Error && 'status' in error && error.status === 401) {
|
|
204
|
+
throw new Error('GitHub authentication failed. Ensure your token has the "repo" scope for repository secrets access.');
|
|
205
|
+
}
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Sets AWS credentials as environment secrets for a specific GitHub environment
|
|
211
|
+
*
|
|
212
|
+
* Creates the GitHub environment if it doesn't exist, then sets AWS_ACCESS_KEY_ID
|
|
213
|
+
* and AWS_SECRET_ACCESS_KEY as secrets within that environment.
|
|
214
|
+
*
|
|
215
|
+
* @param client - Octokit instance
|
|
216
|
+
* @param owner - Repository owner
|
|
217
|
+
* @param repo - Repository name
|
|
218
|
+
* @param environment - Environment name (e.g., dev, stage, prod)
|
|
219
|
+
* @param accessKeyId - AWS Access Key ID
|
|
220
|
+
* @param secretAccessKey - AWS Secret Access Key
|
|
221
|
+
*/
|
|
222
|
+
export async function setEnvironmentCredentials(client, owner, repo, environment, accessKeyId, secretAccessKey) {
|
|
223
|
+
// Ensure the GitHub environment exists
|
|
224
|
+
console.log(pc.dim(` Creating/verifying "${environment}" environment...`));
|
|
225
|
+
await ensureEnvironmentExists(client, owner, repo, environment);
|
|
226
|
+
// Set AWS credentials as environment secrets (same names in each environment)
|
|
227
|
+
console.log(pc.dim(` Setting AWS_ACCESS_KEY_ID in "${environment}" environment...`));
|
|
228
|
+
await setEnvironmentSecret(client, owner, repo, environment, 'AWS_ACCESS_KEY_ID', accessKeyId);
|
|
229
|
+
console.log(pc.dim(` Setting AWS_SECRET_ACCESS_KEY in "${environment}" environment...`));
|
|
230
|
+
await setEnvironmentSecret(client, owner, repo, environment, 'AWS_SECRET_ACCESS_KEY', secretAccessKey);
|
|
231
|
+
console.log(pc.green(` Credentials set for "${environment}" environment`));
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Parses a GitHub repository URL to extract owner and repo
|
|
235
|
+
* @param url - GitHub repository URL in various formats
|
|
236
|
+
* @returns Parsed owner and repo
|
|
237
|
+
* @throws Error if URL format is not recognized
|
|
238
|
+
*
|
|
239
|
+
* Supported formats:
|
|
240
|
+
* - https://github.com/owner/repo
|
|
241
|
+
* - https://github.com/owner/repo.git
|
|
242
|
+
* - git@github.com:owner/repo.git
|
|
243
|
+
* - git@github.com:owner/repo
|
|
244
|
+
* - owner/repo
|
|
245
|
+
*/
|
|
246
|
+
export function parseGitHubUrl(url) {
|
|
247
|
+
// Remove trailing slashes and .git suffix
|
|
248
|
+
let cleanUrl = url.trim().replace(/\/$/, '').replace(/\.git$/, '');
|
|
249
|
+
// Format: https://github.com/owner/repo
|
|
250
|
+
const httpsMatch = cleanUrl.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)$/);
|
|
251
|
+
if (httpsMatch) {
|
|
252
|
+
return {
|
|
253
|
+
owner: httpsMatch[1],
|
|
254
|
+
repo: httpsMatch[2],
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
// Format: git@github.com:owner/repo
|
|
258
|
+
const sshMatch = cleanUrl.match(/^git@github\.com:([^/]+)\/([^/]+)$/);
|
|
259
|
+
if (sshMatch) {
|
|
260
|
+
return {
|
|
261
|
+
owner: sshMatch[1],
|
|
262
|
+
repo: sshMatch[2],
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
// Format: owner/repo
|
|
266
|
+
const shortMatch = cleanUrl.match(/^([^/]+)\/([^/]+)$/);
|
|
267
|
+
if (shortMatch) {
|
|
268
|
+
return {
|
|
269
|
+
owner: shortMatch[1],
|
|
270
|
+
repo: shortMatch[2],
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
throw new Error(`Unable to parse GitHub URL: ${url}. ` +
|
|
274
|
+
'Expected formats: https://github.com/owner/repo, git@github.com:owner/repo, or owner/repo');
|
|
275
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
export const authProviderPrompt = {
|
|
3
|
+
type: 'select',
|
|
4
|
+
name: 'authProvider',
|
|
5
|
+
message: 'Authentication provider:',
|
|
6
|
+
choices: [
|
|
7
|
+
{ title: `${pc.yellow('○')} None (add later)`, value: 'none' },
|
|
8
|
+
{ title: `${pc.cyan('◆')} AWS Cognito`, value: 'cognito' },
|
|
9
|
+
{ title: `${pc.magenta('◆')} Auth0`, value: 'auth0' },
|
|
10
|
+
],
|
|
11
|
+
initial: 0,
|
|
12
|
+
hint: '- Cognito for AWS-native, Auth0 for flexibility'
|
|
13
|
+
};
|
|
14
|
+
export const authFeaturesPrompt = {
|
|
15
|
+
type: (prev) => prev !== 'none' ? 'multiselect' : null,
|
|
16
|
+
name: 'authFeatures',
|
|
17
|
+
message: 'Auth features (space to toggle):',
|
|
18
|
+
choices: [
|
|
19
|
+
{ title: 'Social Login (Google, GitHub)', value: 'social-login', selected: false },
|
|
20
|
+
{ title: 'Multi-Factor Authentication (MFA)', value: 'mfa', selected: false },
|
|
21
|
+
],
|
|
22
|
+
hint: '- Email/password always included'
|
|
23
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const awsRegionPrompt = {
|
|
2
|
+
type: 'select',
|
|
3
|
+
name: 'awsRegion',
|
|
4
|
+
message: 'AWS Region:',
|
|
5
|
+
choices: [
|
|
6
|
+
{ title: 'US East (N. Virginia) - us-east-1', value: 'us-east-1' },
|
|
7
|
+
{ title: 'US West (Oregon) - us-west-2', value: 'us-west-2' },
|
|
8
|
+
{ title: 'EU (Ireland) - eu-west-1', value: 'eu-west-1' },
|
|
9
|
+
{ title: 'EU (Frankfurt) - eu-central-1', value: 'eu-central-1' },
|
|
10
|
+
{ title: 'Asia Pacific (Tokyo) - ap-northeast-1', value: 'ap-northeast-1' },
|
|
11
|
+
{ title: 'Asia Pacific (Sydney) - ap-southeast-2', value: 'ap-southeast-2' }
|
|
12
|
+
],
|
|
13
|
+
initial: 0
|
|
14
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const featuresPrompt = {
|
|
2
|
+
type: 'multiselect',
|
|
3
|
+
name: 'features',
|
|
4
|
+
message: 'Include optional features (space to toggle):',
|
|
5
|
+
choices: [
|
|
6
|
+
{ title: 'GitHub Actions (CI/CD workflows)', value: 'github-actions', selected: true },
|
|
7
|
+
{ title: 'VSCode Config (settings, extensions)', value: 'vscode-config', selected: true }
|
|
8
|
+
],
|
|
9
|
+
hint: '- All optional'
|
|
10
|
+
};
|