@payloadcms/figma 0.0.1-alpha.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/LICENSE.md +11 -0
- package/bin/cli.js +4 -0
- package/dist/api/control-plane.d.ts +124 -0
- package/dist/api/control-plane.d.ts.map +1 -0
- package/dist/api/control-plane.js +245 -0
- package/dist/api/control-plane.js.map +1 -0
- package/dist/api/control-plane.spec.d.ts +2 -0
- package/dist/api/control-plane.spec.d.ts.map +1 -0
- package/dist/api/control-plane.spec.js +296 -0
- package/dist/api/control-plane.spec.js.map +1 -0
- package/dist/api/figma-api.d.ts +31 -0
- package/dist/api/figma-api.d.ts.map +1 -0
- package/dist/api/figma-api.js +169 -0
- package/dist/api/figma-api.js.map +1 -0
- package/dist/api/figma-api.spec.d.ts +14 -0
- package/dist/api/figma-api.spec.d.ts.map +1 -0
- package/dist/api/figma-api.spec.js +201 -0
- package/dist/api/figma-api.spec.js.map +1 -0
- package/dist/auth/__tests__/fixtures.d.ts +26 -0
- package/dist/auth/__tests__/fixtures.d.ts.map +1 -0
- package/dist/auth/__tests__/fixtures.js +67 -0
- package/dist/auth/__tests__/fixtures.js.map +1 -0
- package/dist/auth/__tests__/mocks.d.ts +33 -0
- package/dist/auth/__tests__/mocks.d.ts.map +1 -0
- package/dist/auth/__tests__/mocks.js +63 -0
- package/dist/auth/__tests__/mocks.js.map +1 -0
- package/dist/auth/browser.d.ts +31 -0
- package/dist/auth/browser.d.ts.map +1 -0
- package/dist/auth/browser.js +41 -0
- package/dist/auth/browser.js.map +1 -0
- package/dist/auth/callback-server.d.ts +61 -0
- package/dist/auth/callback-server.d.ts.map +1 -0
- package/dist/auth/callback-server.js +168 -0
- package/dist/auth/callback-server.js.map +1 -0
- package/dist/auth/callback-server.spec.d.ts +2 -0
- package/dist/auth/callback-server.spec.d.ts.map +1 -0
- package/dist/auth/callback-server.spec.js +85 -0
- package/dist/auth/callback-server.spec.js.map +1 -0
- package/dist/auth/crypto-utils.d.ts +33 -0
- package/dist/auth/crypto-utils.d.ts.map +1 -0
- package/dist/auth/crypto-utils.js +96 -0
- package/dist/auth/crypto-utils.js.map +1 -0
- package/dist/auth/crypto-utils.spec.d.ts +2 -0
- package/dist/auth/crypto-utils.spec.d.ts.map +1 -0
- package/dist/auth/crypto-utils.spec.js +36 -0
- package/dist/auth/crypto-utils.spec.js.map +1 -0
- package/dist/auth/jwt-validator.d.ts +41 -0
- package/dist/auth/jwt-validator.d.ts.map +1 -0
- package/dist/auth/jwt-validator.js +113 -0
- package/dist/auth/jwt-validator.js.map +1 -0
- package/dist/auth/jwt-validator.spec.d.ts +10 -0
- package/dist/auth/jwt-validator.spec.d.ts.map +1 -0
- package/dist/auth/jwt-validator.spec.js +236 -0
- package/dist/auth/jwt-validator.spec.js.map +1 -0
- package/dist/auth/oauth-flow.d.ts +62 -0
- package/dist/auth/oauth-flow.d.ts.map +1 -0
- package/dist/auth/oauth-flow.js +172 -0
- package/dist/auth/oauth-flow.js.map +1 -0
- package/dist/auth/oauth-flow.spec.d.ts +2 -0
- package/dist/auth/oauth-flow.spec.d.ts.map +1 -0
- package/dist/auth/oauth-flow.spec.js +134 -0
- package/dist/auth/oauth-flow.spec.js.map +1 -0
- package/dist/auth/pages/error.html +85 -0
- package/dist/auth/pages/success.html +74 -0
- package/dist/auth/pkce.d.ts +51 -0
- package/dist/auth/pkce.d.ts.map +1 -0
- package/dist/auth/pkce.js +71 -0
- package/dist/auth/pkce.js.map +1 -0
- package/dist/auth/pkce.spec.d.ts +2 -0
- package/dist/auth/pkce.spec.d.ts.map +1 -0
- package/dist/auth/pkce.spec.js +32 -0
- package/dist/auth/pkce.spec.js.map +1 -0
- package/dist/auth/project-token.d.ts +47 -0
- package/dist/auth/project-token.d.ts.map +1 -0
- package/dist/auth/project-token.js +134 -0
- package/dist/auth/project-token.js.map +1 -0
- package/dist/auth/project-token.spec.d.ts +2 -0
- package/dist/auth/project-token.spec.d.ts.map +1 -0
- package/dist/auth/project-token.spec.js +332 -0
- package/dist/auth/project-token.spec.js.map +1 -0
- package/dist/auth/refresh.d.ts +35 -0
- package/dist/auth/refresh.d.ts.map +1 -0
- package/dist/auth/refresh.js +149 -0
- package/dist/auth/refresh.js.map +1 -0
- package/dist/auth/refresh.spec.d.ts +2 -0
- package/dist/auth/refresh.spec.d.ts.map +1 -0
- package/dist/auth/refresh.spec.js +105 -0
- package/dist/auth/refresh.spec.js.map +1 -0
- package/dist/auth/token-store.d.ts +110 -0
- package/dist/auth/token-store.d.ts.map +1 -0
- package/dist/auth/token-store.js +188 -0
- package/dist/auth/token-store.js.map +1 -0
- package/dist/auth/token-store.spec.d.ts +2 -0
- package/dist/auth/token-store.spec.d.ts.map +1 -0
- package/dist/auth/token-store.spec.js +276 -0
- package/dist/auth/token-store.spec.js.map +1 -0
- package/dist/auth/types.d.ts +197 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +6 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +108 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/__tests__/test-utils.d.ts +28 -0
- package/dist/commands/__tests__/test-utils.d.ts.map +1 -0
- package/dist/commands/__tests__/test-utils.js +34 -0
- package/dist/commands/__tests__/test-utils.js.map +1 -0
- package/dist/commands/deploy.d.ts +16 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +297 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/deploy.spec.d.ts +2 -0
- package/dist/commands/deploy.spec.d.ts.map +1 -0
- package/dist/commands/deploy.spec.js +18 -0
- package/dist/commands/deploy.spec.js.map +1 -0
- package/dist/commands/init.d.ts +30 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +410 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/init.spec.d.ts +2 -0
- package/dist/commands/init.spec.d.ts.map +1 -0
- package/dist/commands/init.spec.js +283 -0
- package/dist/commands/init.spec.js.map +1 -0
- package/dist/commands/list-tokens.d.ts +16 -0
- package/dist/commands/list-tokens.d.ts.map +1 -0
- package/dist/commands/list-tokens.js +44 -0
- package/dist/commands/list-tokens.js.map +1 -0
- package/dist/commands/login.d.ts +17 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +69 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +16 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +43 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/config/oauth.d.ts +19 -0
- package/dist/config/oauth.d.ts.map +1 -0
- package/dist/config/oauth.js +43 -0
- package/dist/config/oauth.js.map +1 -0
- package/dist/constants.d.ts +27 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +27 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin/adapter.d.ts +5 -0
- package/dist/plugin/adapter.d.ts.map +1 -0
- package/dist/plugin/adapter.js +139 -0
- package/dist/plugin/adapter.js.map +1 -0
- package/dist/plugin/build-config.d.ts +37 -0
- package/dist/plugin/build-config.d.ts.map +1 -0
- package/dist/plugin/build-config.js +25 -0
- package/dist/plugin/build-config.js.map +1 -0
- package/dist/plugin/build-config.spec.d.ts +2 -0
- package/dist/plugin/build-config.spec.d.ts.map +1 -0
- package/dist/plugin/build-config.spec.js +72 -0
- package/dist/plugin/build-config.spec.js.map +1 -0
- package/dist/types/config.d.ts +45 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +9 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types.d.ts +60 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/asset-collection.d.ts +30 -0
- package/dist/utils/asset-collection.d.ts.map +1 -0
- package/dist/utils/asset-collection.js +117 -0
- package/dist/utils/asset-collection.js.map +1 -0
- package/dist/utils/asset-collection.spec.d.ts +2 -0
- package/dist/utils/asset-collection.spec.d.ts.map +1 -0
- package/dist/utils/asset-collection.spec.js +155 -0
- package/dist/utils/asset-collection.spec.js.map +1 -0
- package/dist/utils/build-detection.d.ts +32 -0
- package/dist/utils/build-detection.d.ts.map +1 -0
- package/dist/utils/build-detection.js +55 -0
- package/dist/utils/build-detection.js.map +1 -0
- package/dist/utils/build-detection.spec.d.ts +2 -0
- package/dist/utils/build-detection.spec.d.ts.map +1 -0
- package/dist/utils/build-detection.spec.js +110 -0
- package/dist/utils/build-detection.spec.js.map +1 -0
- package/dist/utils/config.d.ts +33 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +78 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/config.spec.d.ts +2 -0
- package/dist/utils/config.spec.d.ts.map +1 -0
- package/dist/utils/config.spec.js +167 -0
- package/dist/utils/config.spec.js.map +1 -0
- package/dist/utils/download-template.d.ts +15 -0
- package/dist/utils/download-template.d.ts.map +1 -0
- package/dist/utils/download-template.js +74 -0
- package/dist/utils/download-template.js.map +1 -0
- package/dist/utils/download-template.spec.d.ts +2 -0
- package/dist/utils/download-template.spec.d.ts.map +1 -0
- package/dist/utils/download-template.spec.js +76 -0
- package/dist/utils/download-template.spec.js.map +1 -0
- package/dist/utils/env-management.d.ts +16 -0
- package/dist/utils/env-management.d.ts.map +1 -0
- package/dist/utils/env-management.js +98 -0
- package/dist/utils/env-management.js.map +1 -0
- package/dist/utils/env-management.spec.d.ts +2 -0
- package/dist/utils/env-management.spec.d.ts.map +1 -0
- package/dist/utils/env-management.spec.js +123 -0
- package/dist/utils/env-management.spec.js.map +1 -0
- package/dist/utils/format.d.ts +23 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +49 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/formatter.d.ts +11 -0
- package/dist/utils/formatter.d.ts.map +1 -0
- package/dist/utils/formatter.js +47 -0
- package/dist/utils/formatter.js.map +1 -0
- package/dist/utils/git.d.ts +15 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +92 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/git.spec.d.ts +2 -0
- package/dist/utils/git.spec.d.ts.map +1 -0
- package/dist/utils/git.spec.js +99 -0
- package/dist/utils/git.spec.js.map +1 -0
- package/dist/utils/is-debug.d.ts +6 -0
- package/dist/utils/is-debug.d.ts.map +1 -0
- package/dist/utils/is-debug.js +8 -0
- package/dist/utils/is-debug.js.map +1 -0
- package/dist/utils/lambda-config.d.ts +7 -0
- package/dist/utils/lambda-config.d.ts.map +1 -0
- package/dist/utils/lambda-config.js +144 -0
- package/dist/utils/lambda-config.js.map +1 -0
- package/dist/utils/lambda-config.spec.d.ts +2 -0
- package/dist/utils/lambda-config.spec.d.ts.map +1 -0
- package/dist/utils/lambda-config.spec.js +141 -0
- package/dist/utils/lambda-config.spec.js.map +1 -0
- package/dist/utils/log.d.ts +6 -0
- package/dist/utils/log.d.ts.map +1 -0
- package/dist/utils/log.js +22 -0
- package/dist/utils/log.js.map +1 -0
- package/dist/utils/messages.d.ts +10 -0
- package/dist/utils/messages.d.ts.map +1 -0
- package/dist/utils/messages.js +96 -0
- package/dist/utils/messages.js.map +1 -0
- package/dist/utils/package-manager.d.ts +12 -0
- package/dist/utils/package-manager.d.ts.map +1 -0
- package/dist/utils/package-manager.js +37 -0
- package/dist/utils/package-manager.js.map +1 -0
- package/dist/utils/payload-config-ast.d.ts +30 -0
- package/dist/utils/payload-config-ast.d.ts.map +1 -0
- package/dist/utils/payload-config-ast.js +259 -0
- package/dist/utils/payload-config-ast.js.map +1 -0
- package/dist/utils/payload-config-ast.spec.d.ts +2 -0
- package/dist/utils/payload-config-ast.spec.d.ts.map +1 -0
- package/dist/utils/payload-config-ast.spec.js +303 -0
- package/dist/utils/payload-config-ast.spec.js.map +1 -0
- package/dist/utils/payload-config-finder.d.ts +9 -0
- package/dist/utils/payload-config-finder.d.ts.map +1 -0
- package/dist/utils/payload-config-finder.js +26 -0
- package/dist/utils/payload-config-finder.js.map +1 -0
- package/dist/utils/payload-config-modifier.d.ts +18 -0
- package/dist/utils/payload-config-modifier.d.ts.map +1 -0
- package/dist/utils/payload-config-modifier.int.spec.d.ts +2 -0
- package/dist/utils/payload-config-modifier.int.spec.d.ts.map +1 -0
- package/dist/utils/payload-config-modifier.int.spec.js +116 -0
- package/dist/utils/payload-config-modifier.int.spec.js.map +1 -0
- package/dist/utils/payload-config-modifier.js +232 -0
- package/dist/utils/payload-config-modifier.js.map +1 -0
- package/dist/utils/payload-config-modifier.spec.d.ts +2 -0
- package/dist/utils/payload-config-modifier.spec.d.ts.map +1 -0
- package/dist/utils/payload-config-modifier.spec.js +162 -0
- package/dist/utils/payload-config-modifier.spec.js.map +1 -0
- package/dist/utils/payload-package-check.d.ts +15 -0
- package/dist/utils/payload-package-check.d.ts.map +1 -0
- package/dist/utils/payload-package-check.js +116 -0
- package/dist/utils/payload-package-check.js.map +1 -0
- package/dist/utils/payload-package-check.spec.d.ts +2 -0
- package/dist/utils/payload-package-check.spec.d.ts.map +1 -0
- package/dist/utils/payload-package-check.spec.js +148 -0
- package/dist/utils/payload-package-check.spec.js.map +1 -0
- package/dist/utils/project.d.ts +58 -0
- package/dist/utils/project.d.ts.map +1 -0
- package/dist/utils/project.js +252 -0
- package/dist/utils/project.js.map +1 -0
- package/dist/utils/project.spec.d.ts +2 -0
- package/dist/utils/project.spec.d.ts.map +1 -0
- package/dist/utils/project.spec.js +222 -0
- package/dist/utils/project.spec.js.map +1 -0
- package/dist/utils/s3-upload.d.ts +43 -0
- package/dist/utils/s3-upload.d.ts.map +1 -0
- package/dist/utils/s3-upload.js +128 -0
- package/dist/utils/s3-upload.js.map +1 -0
- package/dist/utils/s3-upload.spec.d.ts +2 -0
- package/dist/utils/s3-upload.spec.d.ts.map +1 -0
- package/dist/utils/s3-upload.spec.js +140 -0
- package/dist/utils/s3-upload.spec.js.map +1 -0
- package/dist/utils/token-display.d.ts +14 -0
- package/dist/utils/token-display.d.ts.map +1 -0
- package/dist/utils/token-display.js +62 -0
- package/dist/utils/token-display.js.map +1 -0
- package/dist/utils/typescript-validator.d.ts +11 -0
- package/dist/utils/typescript-validator.d.ts.map +1 -0
- package/dist/utils/typescript-validator.js +58 -0
- package/dist/utils/typescript-validator.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration file management utilities
|
|
3
|
+
*/ import fs from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
const CONFIG_FILENAME = 'figma.config.json';
|
|
6
|
+
const GITIGNORE_FILENAME = '.gitignore';
|
|
7
|
+
/**
|
|
8
|
+
* Read figma.config.json from the current directory
|
|
9
|
+
*
|
|
10
|
+
* @param projectPath - Path to project directory (defaults to cwd)
|
|
11
|
+
* @returns Parsed configuration or null if not found
|
|
12
|
+
*/ export async function readFigmaConfig(projectPath = process.cwd()) {
|
|
13
|
+
const configPath = path.join(projectPath, CONFIG_FILENAME);
|
|
14
|
+
try {
|
|
15
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
16
|
+
return JSON.parse(content);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error.code === 'ENOENT') {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
throw new Error(`Failed to read ${CONFIG_FILENAME}: ${error.message}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Write figma.config.json to the current directory
|
|
26
|
+
*
|
|
27
|
+
* @param config - Configuration to write
|
|
28
|
+
* @param projectPath - Path to project directory (defaults to cwd)
|
|
29
|
+
*/ export async function writeFigmaConfig(config, projectPath = process.cwd()) {
|
|
30
|
+
const configPath = path.join(projectPath, CONFIG_FILENAME);
|
|
31
|
+
try {
|
|
32
|
+
const content = JSON.stringify(config, null, 2);
|
|
33
|
+
await fs.writeFile(configPath, content + '\n', 'utf-8');
|
|
34
|
+
} catch (error) {
|
|
35
|
+
throw new Error(`Failed to write ${CONFIG_FILENAME}: ${error.message}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Check if figma.config.json exists
|
|
40
|
+
*
|
|
41
|
+
* @param projectPath - Path to project directory (defaults to cwd)
|
|
42
|
+
* @returns True if config file exists
|
|
43
|
+
*/ export async function hasFigmaConfig(projectPath = process.cwd()) {
|
|
44
|
+
const configPath = path.join(projectPath, CONFIG_FILENAME);
|
|
45
|
+
try {
|
|
46
|
+
await fs.access(configPath);
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Ensure .gitignore exists and optionally add entries
|
|
54
|
+
*
|
|
55
|
+
* @param projectPath - Path to project directory
|
|
56
|
+
* @param entries - Optional entries to add to .gitignore
|
|
57
|
+
*/ export async function ensureGitignore(projectPath, entries = []) {
|
|
58
|
+
const gitignorePath = path.join(projectPath, GITIGNORE_FILENAME);
|
|
59
|
+
let existingContent = '';
|
|
60
|
+
try {
|
|
61
|
+
existingContent = await fs.readFile(gitignorePath, 'utf-8');
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (error.code !== 'ENOENT') {
|
|
64
|
+
throw new Error(`Failed to read ${GITIGNORE_FILENAME}: ${error.message}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Parse existing entries
|
|
68
|
+
const existingLines = existingContent.split('\n').filter((line)=>line.trim());
|
|
69
|
+
const existingSet = new Set(existingLines);
|
|
70
|
+
// Add new entries that don't exist
|
|
71
|
+
const newEntries = entries.filter((entry)=>!existingSet.has(entry));
|
|
72
|
+
if (newEntries.length > 0) {
|
|
73
|
+
const newContent = existingContent ? `${existingContent.trimEnd()}\n\n# Figma CMS\n${newEntries.join('\n')}\n` : `# Figma CMS\n${newEntries.join('\n')}\n`;
|
|
74
|
+
await fs.writeFile(gitignorePath, newContent, 'utf-8');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/config.ts"],"sourcesContent":["/**\n * Configuration file management utilities\n */\n\nimport fs from 'fs/promises'\nimport path from 'path'\n\nimport type { FigmaConfig } from '../types/config.js'\n\nconst CONFIG_FILENAME = 'figma.config.json'\nconst GITIGNORE_FILENAME = '.gitignore'\n\n/**\n * Read figma.config.json from the current directory\n *\n * @param projectPath - Path to project directory (defaults to cwd)\n * @returns Parsed configuration or null if not found\n */\nexport async function readFigmaConfig(\n projectPath: string = process.cwd(),\n): Promise<FigmaConfig | null> {\n const configPath = path.join(projectPath, CONFIG_FILENAME)\n\n try {\n const content = await fs.readFile(configPath, 'utf-8')\n return JSON.parse(content) as FigmaConfig\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return null\n }\n throw new Error(`Failed to read ${CONFIG_FILENAME}: ${(error as Error).message}`)\n }\n}\n\n/**\n * Write figma.config.json to the current directory\n *\n * @param config - Configuration to write\n * @param projectPath - Path to project directory (defaults to cwd)\n */\nexport async function writeFigmaConfig(\n config: FigmaConfig,\n projectPath: string = process.cwd(),\n): Promise<void> {\n const configPath = path.join(projectPath, CONFIG_FILENAME)\n\n try {\n const content = JSON.stringify(config, null, 2)\n await fs.writeFile(configPath, content + '\\n', 'utf-8')\n } catch (error) {\n throw new Error(`Failed to write ${CONFIG_FILENAME}: ${(error as Error).message}`)\n }\n}\n\n/**\n * Check if figma.config.json exists\n *\n * @param projectPath - Path to project directory (defaults to cwd)\n * @returns True if config file exists\n */\nexport async function hasFigmaConfig(projectPath: string = process.cwd()): Promise<boolean> {\n const configPath = path.join(projectPath, CONFIG_FILENAME)\n\n try {\n await fs.access(configPath)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Ensure .gitignore exists and optionally add entries\n *\n * @param projectPath - Path to project directory\n * @param entries - Optional entries to add to .gitignore\n */\nexport async function ensureGitignore(projectPath: string, entries: string[] = []): Promise<void> {\n const gitignorePath = path.join(projectPath, GITIGNORE_FILENAME)\n\n let existingContent = ''\n try {\n existingContent = await fs.readFile(gitignorePath, 'utf-8')\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw new Error(`Failed to read ${GITIGNORE_FILENAME}: ${(error as Error).message}`)\n }\n }\n\n // Parse existing entries\n const existingLines = existingContent.split('\\n').filter((line) => line.trim())\n const existingSet = new Set(existingLines)\n\n // Add new entries that don't exist\n const newEntries = entries.filter((entry) => !existingSet.has(entry))\n\n if (newEntries.length > 0) {\n const newContent = existingContent\n ? `${existingContent.trimEnd()}\\n\\n# Figma CMS\\n${newEntries.join('\\n')}\\n`\n : `# Figma CMS\\n${newEntries.join('\\n')}\\n`\n\n await fs.writeFile(gitignorePath, newContent, 'utf-8')\n }\n}\n"],"names":["fs","path","CONFIG_FILENAME","GITIGNORE_FILENAME","readFigmaConfig","projectPath","process","cwd","configPath","join","content","readFile","JSON","parse","error","code","Error","message","writeFigmaConfig","config","stringify","writeFile","hasFigmaConfig","access","ensureGitignore","entries","gitignorePath","existingContent","existingLines","split","filter","line","trim","existingSet","Set","newEntries","entry","has","length","newContent","trimEnd"],"mappings":"AAAA;;CAEC,GAED,OAAOA,QAAQ,cAAa;AAC5B,OAAOC,UAAU,OAAM;AAIvB,MAAMC,kBAAkB;AACxB,MAAMC,qBAAqB;AAE3B;;;;;CAKC,GACD,OAAO,eAAeC,gBACpBC,cAAsBC,QAAQC,GAAG,EAAE;IAEnC,MAAMC,aAAaP,KAAKQ,IAAI,CAACJ,aAAaH;IAE1C,IAAI;QACF,MAAMQ,UAAU,MAAMV,GAAGW,QAAQ,CAACH,YAAY;QAC9C,OAAOI,KAAKC,KAAK,CAACH;IACpB,EAAE,OAAOI,OAAO;QACd,IAAI,AAACA,MAAgCC,IAAI,KAAK,UAAU;YACtD,OAAO;QACT;QACA,MAAM,IAAIC,MAAM,CAAC,eAAe,EAAEd,gBAAgB,EAAE,EAAE,AAACY,MAAgBG,OAAO,EAAE;IAClF;AACF;AAEA;;;;;CAKC,GACD,OAAO,eAAeC,iBACpBC,MAAmB,EACnBd,cAAsBC,QAAQC,GAAG,EAAE;IAEnC,MAAMC,aAAaP,KAAKQ,IAAI,CAACJ,aAAaH;IAE1C,IAAI;QACF,MAAMQ,UAAUE,KAAKQ,SAAS,CAACD,QAAQ,MAAM;QAC7C,MAAMnB,GAAGqB,SAAS,CAACb,YAAYE,UAAU,MAAM;IACjD,EAAE,OAAOI,OAAO;QACd,MAAM,IAAIE,MAAM,CAAC,gBAAgB,EAAEd,gBAAgB,EAAE,EAAE,AAACY,MAAgBG,OAAO,EAAE;IACnF;AACF;AAEA;;;;;CAKC,GACD,OAAO,eAAeK,eAAejB,cAAsBC,QAAQC,GAAG,EAAE;IACtE,MAAMC,aAAaP,KAAKQ,IAAI,CAACJ,aAAaH;IAE1C,IAAI;QACF,MAAMF,GAAGuB,MAAM,CAACf;QAChB,OAAO;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA;;;;;CAKC,GACD,OAAO,eAAegB,gBAAgBnB,WAAmB,EAAEoB,UAAoB,EAAE;IAC/E,MAAMC,gBAAgBzB,KAAKQ,IAAI,CAACJ,aAAaF;IAE7C,IAAIwB,kBAAkB;IACtB,IAAI;QACFA,kBAAkB,MAAM3B,GAAGW,QAAQ,CAACe,eAAe;IACrD,EAAE,OAAOZ,OAAO;QACd,IAAI,AAACA,MAAgCC,IAAI,KAAK,UAAU;YACtD,MAAM,IAAIC,MAAM,CAAC,eAAe,EAAEb,mBAAmB,EAAE,EAAE,AAACW,MAAgBG,OAAO,EAAE;QACrF;IACF;IAEA,yBAAyB;IACzB,MAAMW,gBAAgBD,gBAAgBE,KAAK,CAAC,MAAMC,MAAM,CAAC,CAACC,OAASA,KAAKC,IAAI;IAC5E,MAAMC,cAAc,IAAIC,IAAIN;IAE5B,mCAAmC;IACnC,MAAMO,aAAaV,QAAQK,MAAM,CAAC,CAACM,QAAU,CAACH,YAAYI,GAAG,CAACD;IAE9D,IAAID,WAAWG,MAAM,GAAG,GAAG;QACzB,MAAMC,aAAaZ,kBACf,GAAGA,gBAAgBa,OAAO,GAAG,iBAAiB,EAAEL,WAAW1B,IAAI,CAAC,MAAM,EAAE,CAAC,GACzE,CAAC,aAAa,EAAE0B,WAAW1B,IAAI,CAAC,MAAM,EAAE,CAAC;QAE7C,MAAMT,GAAGqB,SAAS,CAACK,eAAea,YAAY;IAChD;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.spec.d.ts","sourceRoot":"","sources":["../../src/utils/config.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
4
|
+
import { ensureGitignore, hasFigmaConfig, readFigmaConfig, writeFigmaConfig } from './config.js';
|
|
5
|
+
describe('config utilities', ()=>{
|
|
6
|
+
let testDir;
|
|
7
|
+
beforeEach(async ()=>{
|
|
8
|
+
// Create unique temp directory for each test
|
|
9
|
+
testDir = path.join(process.cwd(), '.test-tmp', `config-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`);
|
|
10
|
+
await fs.mkdir(testDir, {
|
|
11
|
+
recursive: true
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
afterEach(async ()=>{
|
|
15
|
+
// Clean up test directory
|
|
16
|
+
try {
|
|
17
|
+
await fs.rm(testDir, {
|
|
18
|
+
force: true,
|
|
19
|
+
recursive: true
|
|
20
|
+
});
|
|
21
|
+
} catch {
|
|
22
|
+
// Ignore cleanup errors
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
describe('writeFigmaConfig', ()=>{
|
|
26
|
+
it('should write config to figma.config.json', async ()=>{
|
|
27
|
+
const config = {
|
|
28
|
+
domain: 'test-cms.figmacms.com',
|
|
29
|
+
tenantId: 'tenant_test123'
|
|
30
|
+
};
|
|
31
|
+
await writeFigmaConfig(config, testDir);
|
|
32
|
+
const filePath = path.join(testDir, 'figma.config.json');
|
|
33
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
34
|
+
const parsed = JSON.parse(content);
|
|
35
|
+
expect(parsed).toEqual(config);
|
|
36
|
+
});
|
|
37
|
+
it('should format JSON with 2 space indentation', async ()=>{
|
|
38
|
+
const config = {
|
|
39
|
+
domain: 'format.figmacms.com',
|
|
40
|
+
tenantId: 'tenant_format'
|
|
41
|
+
};
|
|
42
|
+
await writeFigmaConfig(config, testDir);
|
|
43
|
+
const filePath = path.join(testDir, 'figma.config.json');
|
|
44
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
45
|
+
// Check indentation
|
|
46
|
+
expect(content).toContain(' "tenantId"');
|
|
47
|
+
expect(content).toContain(' "domain"');
|
|
48
|
+
});
|
|
49
|
+
it('should include subdomain when provided', async ()=>{
|
|
50
|
+
const config = {
|
|
51
|
+
domain: 'sub.figmacms.com',
|
|
52
|
+
subdomain: 'custom',
|
|
53
|
+
tenantId: 'tenant_sub'
|
|
54
|
+
};
|
|
55
|
+
await writeFigmaConfig(config, testDir);
|
|
56
|
+
const read = await readFigmaConfig(testDir);
|
|
57
|
+
expect(read?.subdomain).toBe('custom');
|
|
58
|
+
});
|
|
59
|
+
it('should throw error if directory does not exist', async ()=>{
|
|
60
|
+
const config = {
|
|
61
|
+
domain: 'fail.figmacms.com',
|
|
62
|
+
tenantId: 'tenant_fail'
|
|
63
|
+
};
|
|
64
|
+
const nonExistentDir = path.join(testDir, 'does-not-exist');
|
|
65
|
+
await expect(writeFigmaConfig(config, nonExistentDir)).rejects.toThrow();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
describe('readFigmaConfig', ()=>{
|
|
69
|
+
it('should read existing config', async ()=>{
|
|
70
|
+
const config = {
|
|
71
|
+
domain: 'read.figmacms.com',
|
|
72
|
+
tenantId: 'tenant_read'
|
|
73
|
+
};
|
|
74
|
+
await writeFigmaConfig(config, testDir);
|
|
75
|
+
const read = await readFigmaConfig(testDir);
|
|
76
|
+
expect(read).toEqual(config);
|
|
77
|
+
});
|
|
78
|
+
it('should return null if config does not exist', async ()=>{
|
|
79
|
+
const read = await readFigmaConfig(testDir);
|
|
80
|
+
expect(read).toBeNull();
|
|
81
|
+
});
|
|
82
|
+
it('should throw error if config is invalid JSON', async ()=>{
|
|
83
|
+
const filePath = path.join(testDir, 'figma.config.json');
|
|
84
|
+
await fs.writeFile(filePath, 'not valid json{', 'utf-8');
|
|
85
|
+
await expect(readFigmaConfig(testDir)).rejects.toThrow('Failed to read figma.config.json');
|
|
86
|
+
});
|
|
87
|
+
it('should handle config with all optional fields', async ()=>{
|
|
88
|
+
const config = {
|
|
89
|
+
domain: 'full.figmacms.com',
|
|
90
|
+
subdomain: 'custom-sub',
|
|
91
|
+
tenantId: 'tenant_full'
|
|
92
|
+
};
|
|
93
|
+
await writeFigmaConfig(config, testDir);
|
|
94
|
+
const read = await readFigmaConfig(testDir);
|
|
95
|
+
expect(read).toEqual(config);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
describe('hasFigmaConfig', ()=>{
|
|
99
|
+
it('should return true if config exists', async ()=>{
|
|
100
|
+
const config = {
|
|
101
|
+
domain: 'has.figmacms.com',
|
|
102
|
+
tenantId: 'tenant_has'
|
|
103
|
+
};
|
|
104
|
+
await writeFigmaConfig(config, testDir);
|
|
105
|
+
const has = await hasFigmaConfig(testDir);
|
|
106
|
+
expect(has).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
it('should return false if config does not exist', async ()=>{
|
|
109
|
+
const has = await hasFigmaConfig(testDir);
|
|
110
|
+
expect(has).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
it('should handle directory that does not exist', async ()=>{
|
|
113
|
+
const nonExistentDir = path.join(testDir, 'does-not-exist');
|
|
114
|
+
const has = await hasFigmaConfig(nonExistentDir);
|
|
115
|
+
expect(has).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
describe('ensureGitignore', ()=>{
|
|
119
|
+
it('should create .gitignore if it does not exist', async ()=>{
|
|
120
|
+
await ensureGitignore(testDir, [
|
|
121
|
+
'node_modules',
|
|
122
|
+
'.env'
|
|
123
|
+
]);
|
|
124
|
+
const filePath = path.join(testDir, '.gitignore');
|
|
125
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
126
|
+
expect(content).toContain('node_modules');
|
|
127
|
+
expect(content).toContain('.env');
|
|
128
|
+
expect(content).toContain('# Figma CMS');
|
|
129
|
+
});
|
|
130
|
+
it('should append to existing .gitignore', async ()=>{
|
|
131
|
+
const filePath = path.join(testDir, '.gitignore');
|
|
132
|
+
await fs.writeFile(filePath, '# Existing content\ndist\n', 'utf-8');
|
|
133
|
+
await ensureGitignore(testDir, [
|
|
134
|
+
'node_modules'
|
|
135
|
+
]);
|
|
136
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
137
|
+
expect(content).toContain('# Existing content');
|
|
138
|
+
expect(content).toContain('dist');
|
|
139
|
+
expect(content).toContain('node_modules');
|
|
140
|
+
expect(content).toContain('# Figma CMS');
|
|
141
|
+
});
|
|
142
|
+
it('should not add duplicate entries', async ()=>{
|
|
143
|
+
await ensureGitignore(testDir, [
|
|
144
|
+
'node_modules',
|
|
145
|
+
'.env'
|
|
146
|
+
]);
|
|
147
|
+
await ensureGitignore(testDir, [
|
|
148
|
+
'node_modules',
|
|
149
|
+
'dist'
|
|
150
|
+
]);
|
|
151
|
+
const filePath = path.join(testDir, '.gitignore');
|
|
152
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
153
|
+
// Count occurrences of node_modules
|
|
154
|
+
const matches = content.match(/node_modules/g);
|
|
155
|
+
expect(matches?.length).toBe(1);
|
|
156
|
+
});
|
|
157
|
+
it('should handle empty entries array', async ()=>{
|
|
158
|
+
await ensureGitignore(testDir, []);
|
|
159
|
+
const filePath = path.join(testDir, '.gitignore');
|
|
160
|
+
const exists = await fs.access(filePath).then(()=>true).catch(()=>false);
|
|
161
|
+
// Should not create file if no entries to add
|
|
162
|
+
expect(exists).toBe(false);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
//# sourceMappingURL=config.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/config.spec.ts"],"sourcesContent":["import fs from 'fs/promises'\nimport path from 'path'\nimport { afterEach, beforeEach, describe, expect, it } from 'vitest'\n\nimport type { FigmaConfig } from '../types/config.js'\n\nimport { ensureGitignore, hasFigmaConfig, readFigmaConfig, writeFigmaConfig } from './config.js'\n\ndescribe('config utilities', () => {\n let testDir: string\n\n beforeEach(async () => {\n // Create unique temp directory for each test\n testDir = path.join(\n process.cwd(),\n '.test-tmp',\n `config-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,\n )\n await fs.mkdir(testDir, { recursive: true })\n })\n\n afterEach(async () => {\n // Clean up test directory\n try {\n await fs.rm(testDir, { force: true, recursive: true })\n } catch {\n // Ignore cleanup errors\n }\n })\n\n describe('writeFigmaConfig', () => {\n it('should write config to figma.config.json', async () => {\n const config: FigmaConfig = {\n domain: 'test-cms.figmacms.com',\n tenantId: 'tenant_test123',\n }\n\n await writeFigmaConfig(config, testDir)\n\n const filePath = path.join(testDir, 'figma.config.json')\n const content = await fs.readFile(filePath, 'utf-8')\n const parsed = JSON.parse(content)\n\n expect(parsed).toEqual(config)\n })\n\n it('should format JSON with 2 space indentation', async () => {\n const config: FigmaConfig = {\n domain: 'format.figmacms.com',\n tenantId: 'tenant_format',\n }\n\n await writeFigmaConfig(config, testDir)\n\n const filePath = path.join(testDir, 'figma.config.json')\n const content = await fs.readFile(filePath, 'utf-8')\n\n // Check indentation\n expect(content).toContain(' \"tenantId\"')\n expect(content).toContain(' \"domain\"')\n })\n\n it('should include subdomain when provided', async () => {\n const config: FigmaConfig = {\n domain: 'sub.figmacms.com',\n subdomain: 'custom',\n tenantId: 'tenant_sub',\n }\n\n await writeFigmaConfig(config, testDir)\n\n const read = await readFigmaConfig(testDir)\n expect(read?.subdomain).toBe('custom')\n })\n\n it('should throw error if directory does not exist', async () => {\n const config: FigmaConfig = {\n domain: 'fail.figmacms.com',\n tenantId: 'tenant_fail',\n }\n\n const nonExistentDir = path.join(testDir, 'does-not-exist')\n\n await expect(writeFigmaConfig(config, nonExistentDir)).rejects.toThrow()\n })\n })\n\n describe('readFigmaConfig', () => {\n it('should read existing config', async () => {\n const config: FigmaConfig = {\n domain: 'read.figmacms.com',\n tenantId: 'tenant_read',\n }\n\n await writeFigmaConfig(config, testDir)\n\n const read = await readFigmaConfig(testDir)\n\n expect(read).toEqual(config)\n })\n\n it('should return null if config does not exist', async () => {\n const read = await readFigmaConfig(testDir)\n\n expect(read).toBeNull()\n })\n\n it('should throw error if config is invalid JSON', async () => {\n const filePath = path.join(testDir, 'figma.config.json')\n await fs.writeFile(filePath, 'not valid json{', 'utf-8')\n\n await expect(readFigmaConfig(testDir)).rejects.toThrow('Failed to read figma.config.json')\n })\n\n it('should handle config with all optional fields', async () => {\n const config: FigmaConfig = {\n domain: 'full.figmacms.com',\n subdomain: 'custom-sub',\n tenantId: 'tenant_full',\n }\n\n await writeFigmaConfig(config, testDir)\n const read = await readFigmaConfig(testDir)\n\n expect(read).toEqual(config)\n })\n })\n\n describe('hasFigmaConfig', () => {\n it('should return true if config exists', async () => {\n const config: FigmaConfig = {\n domain: 'has.figmacms.com',\n tenantId: 'tenant_has',\n }\n\n await writeFigmaConfig(config, testDir)\n\n const has = await hasFigmaConfig(testDir)\n\n expect(has).toBe(true)\n })\n\n it('should return false if config does not exist', async () => {\n const has = await hasFigmaConfig(testDir)\n\n expect(has).toBe(false)\n })\n\n it('should handle directory that does not exist', async () => {\n const nonExistentDir = path.join(testDir, 'does-not-exist')\n\n const has = await hasFigmaConfig(nonExistentDir)\n\n expect(has).toBe(false)\n })\n })\n\n describe('ensureGitignore', () => {\n it('should create .gitignore if it does not exist', async () => {\n await ensureGitignore(testDir, ['node_modules', '.env'])\n\n const filePath = path.join(testDir, '.gitignore')\n const content = await fs.readFile(filePath, 'utf-8')\n\n expect(content).toContain('node_modules')\n expect(content).toContain('.env')\n expect(content).toContain('# Figma CMS')\n })\n\n it('should append to existing .gitignore', async () => {\n const filePath = path.join(testDir, '.gitignore')\n await fs.writeFile(filePath, '# Existing content\\ndist\\n', 'utf-8')\n\n await ensureGitignore(testDir, ['node_modules'])\n\n const content = await fs.readFile(filePath, 'utf-8')\n\n expect(content).toContain('# Existing content')\n expect(content).toContain('dist')\n expect(content).toContain('node_modules')\n expect(content).toContain('# Figma CMS')\n })\n\n it('should not add duplicate entries', async () => {\n await ensureGitignore(testDir, ['node_modules', '.env'])\n await ensureGitignore(testDir, ['node_modules', 'dist'])\n\n const filePath = path.join(testDir, '.gitignore')\n const content = await fs.readFile(filePath, 'utf-8')\n\n // Count occurrences of node_modules\n const matches = content.match(/node_modules/g)\n expect(matches?.length).toBe(1)\n })\n\n it('should handle empty entries array', async () => {\n await ensureGitignore(testDir, [])\n\n const filePath = path.join(testDir, '.gitignore')\n const exists = await fs\n .access(filePath)\n .then(() => true)\n .catch(() => false)\n\n // Should not create file if no entries to add\n expect(exists).toBe(false)\n })\n })\n})\n"],"names":["fs","path","afterEach","beforeEach","describe","expect","it","ensureGitignore","hasFigmaConfig","readFigmaConfig","writeFigmaConfig","testDir","join","process","cwd","Date","now","Math","random","toString","substring","mkdir","recursive","rm","force","config","domain","tenantId","filePath","content","readFile","parsed","JSON","parse","toEqual","toContain","subdomain","read","toBe","nonExistentDir","rejects","toThrow","toBeNull","writeFile","has","matches","match","length","exists","access","then","catch"],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error thrown when template download fails
|
|
3
|
+
*/
|
|
4
|
+
export declare class TemplateDownloadError extends Error {
|
|
5
|
+
cause?: Error | undefined;
|
|
6
|
+
constructor(message: string, cause?: Error | undefined);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Download Payload template from GitHub with retry logic
|
|
10
|
+
*
|
|
11
|
+
* @param projectDir - Directory to extract template into
|
|
12
|
+
* @throws TemplateDownloadError if download fails
|
|
13
|
+
*/
|
|
14
|
+
export declare function downloadTemplateFromGitHub(projectDir: string): Promise<void>;
|
|
15
|
+
//# sourceMappingURL=download-template.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"download-template.d.ts","sourceRoot":"","sources":["../../src/utils/download-template.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAGrC,KAAK,CAAC,EAAE,KAAK;gBADpB,OAAO,EAAE,MAAM,EACR,KAAK,CAAC,EAAE,KAAK,YAAA;CAKvB;AAOD;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBlF"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { Readable } from 'node:stream';
|
|
3
|
+
import { pipeline } from 'node:stream/promises';
|
|
4
|
+
import { x } from 'tar';
|
|
5
|
+
/**
|
|
6
|
+
* Error thrown when template download fails
|
|
7
|
+
*/ export class TemplateDownloadError extends Error {
|
|
8
|
+
cause;
|
|
9
|
+
constructor(message, cause){
|
|
10
|
+
super(message), this.cause = cause;
|
|
11
|
+
this.name = 'TemplateDownloadError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const BRANCH_OR_TAG = 'main';
|
|
15
|
+
const TEMPLATE_NAME = 'blank';
|
|
16
|
+
const MAX_RETRIES = 3;
|
|
17
|
+
const INITIAL_RETRY_DELAY = 1000 // 1 second
|
|
18
|
+
;
|
|
19
|
+
/**
|
|
20
|
+
* Download Payload template from GitHub with retry logic
|
|
21
|
+
*
|
|
22
|
+
* @param projectDir - Directory to extract template into
|
|
23
|
+
* @throws TemplateDownloadError if download fails
|
|
24
|
+
*/ export async function downloadTemplateFromGitHub(projectDir) {
|
|
25
|
+
let lastError;
|
|
26
|
+
for(let attempt = 1; attempt <= MAX_RETRIES; attempt++){
|
|
27
|
+
try {
|
|
28
|
+
await downloadTemplateAttempt(projectDir);
|
|
29
|
+
return; // Success
|
|
30
|
+
} catch (error) {
|
|
31
|
+
lastError = error instanceof Error ? error : new Error('Unknown error');
|
|
32
|
+
if (attempt < MAX_RETRIES) {
|
|
33
|
+
// Exponential backoff: 1s, 2s, 4s
|
|
34
|
+
const delay = INITIAL_RETRY_DELAY * Math.pow(2, attempt - 1);
|
|
35
|
+
await new Promise((resolve)=>setTimeout(resolve, delay));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// All retries failed
|
|
40
|
+
throw new TemplateDownloadError(`Failed to download template after ${MAX_RETRIES} attempts`, lastError);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Single attempt to download template (extracted for retry logic)
|
|
44
|
+
*/ async function downloadTemplateAttempt(projectDir) {
|
|
45
|
+
const url = `https://codeload.github.com/payloadcms/payload/tar.gz/${BRANCH_OR_TAG}`;
|
|
46
|
+
const filter = `payload-${BRANCH_OR_TAG}/templates/${TEMPLATE_NAME}/`;
|
|
47
|
+
try {
|
|
48
|
+
// Ensure target directory exists
|
|
49
|
+
await fs.mkdir(projectDir, {
|
|
50
|
+
recursive: true
|
|
51
|
+
});
|
|
52
|
+
await pipeline(await downloadTarStream(url), x({
|
|
53
|
+
cwd: projectDir,
|
|
54
|
+
filter: (p)=>p.includes(filter),
|
|
55
|
+
strip: 2 + TEMPLATE_NAME.split('/').length
|
|
56
|
+
}));
|
|
57
|
+
} catch (error) {
|
|
58
|
+
throw new TemplateDownloadError('Failed to download template from GitHub', error instanceof Error ? error : undefined);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Download tar stream from URL
|
|
63
|
+
*/ async function downloadTarStream(url) {
|
|
64
|
+
const res = await fetch(url);
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
67
|
+
}
|
|
68
|
+
if (!res.body) {
|
|
69
|
+
throw new Error(`Failed to download: ${url}`);
|
|
70
|
+
}
|
|
71
|
+
return Readable.from(res.body);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
//# sourceMappingURL=download-template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/download-template.ts"],"sourcesContent":["import fs from 'node:fs/promises'\nimport { Readable } from 'node:stream'\nimport { pipeline } from 'node:stream/promises'\nimport { x } from 'tar'\n\n/**\n * Error thrown when template download fails\n */\nexport class TemplateDownloadError extends Error {\n constructor(\n message: string,\n public cause?: Error,\n ) {\n super(message)\n this.name = 'TemplateDownloadError'\n }\n}\n\nconst BRANCH_OR_TAG = 'main'\nconst TEMPLATE_NAME = 'blank'\nconst MAX_RETRIES = 3\nconst INITIAL_RETRY_DELAY = 1000 // 1 second\n\n/**\n * Download Payload template from GitHub with retry logic\n *\n * @param projectDir - Directory to extract template into\n * @throws TemplateDownloadError if download fails\n */\nexport async function downloadTemplateFromGitHub(projectDir: string): Promise<void> {\n let lastError: Error | undefined\n\n for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {\n try {\n await downloadTemplateAttempt(projectDir)\n return // Success\n } catch (error) {\n lastError = error instanceof Error ? error : new Error('Unknown error')\n\n if (attempt < MAX_RETRIES) {\n // Exponential backoff: 1s, 2s, 4s\n const delay = INITIAL_RETRY_DELAY * Math.pow(2, attempt - 1)\n await new Promise((resolve) => setTimeout(resolve, delay))\n }\n }\n }\n\n // All retries failed\n throw new TemplateDownloadError(\n `Failed to download template after ${MAX_RETRIES} attempts`,\n lastError,\n )\n}\n\n/**\n * Single attempt to download template (extracted for retry logic)\n */\nasync function downloadTemplateAttempt(projectDir: string): Promise<void> {\n const url = `https://codeload.github.com/payloadcms/payload/tar.gz/${BRANCH_OR_TAG}`\n const filter = `payload-${BRANCH_OR_TAG}/templates/${TEMPLATE_NAME}/`\n\n try {\n // Ensure target directory exists\n await fs.mkdir(projectDir, { recursive: true })\n\n await pipeline(\n await downloadTarStream(url),\n x({\n cwd: projectDir,\n filter: (p) => p.includes(filter),\n strip: 2 + TEMPLATE_NAME.split('/').length,\n }),\n )\n } catch (error) {\n throw new TemplateDownloadError(\n 'Failed to download template from GitHub',\n error instanceof Error ? error : undefined,\n )\n }\n}\n\n/**\n * Download tar stream from URL\n */\nasync function downloadTarStream(url: string): Promise<Readable> {\n const res = await fetch(url)\n\n if (!res.ok) {\n throw new Error(`HTTP ${res.status}: ${res.statusText}`)\n }\n\n if (!res.body) {\n throw new Error(`Failed to download: ${url}`)\n }\n\n return Readable.from(res.body as unknown as NodeJS.ReadableStream)\n}\n"],"names":["fs","Readable","pipeline","x","TemplateDownloadError","Error","message","cause","name","BRANCH_OR_TAG","TEMPLATE_NAME","MAX_RETRIES","INITIAL_RETRY_DELAY","downloadTemplateFromGitHub","projectDir","lastError","attempt","downloadTemplateAttempt","error","delay","Math","pow","Promise","resolve","setTimeout","url","filter","mkdir","recursive","downloadTarStream","cwd","p","includes","strip","split","length","undefined","res","fetch","ok","status","statusText","body","from"],"mappings":"AAAA,OAAOA,QAAQ,mBAAkB;AACjC,SAASC,QAAQ,QAAQ,cAAa;AACtC,SAASC,QAAQ,QAAQ,uBAAsB;AAC/C,SAASC,CAAC,QAAQ,MAAK;AAEvB;;CAEC,GACD,OAAO,MAAMC,8BAA8BC;;IACzC,YACEC,OAAe,EACf,AAAOC,KAAa,CACpB;QACA,KAAK,CAACD,eAFCC,QAAAA;QAGP,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,MAAMC,gBAAgB;AACtB,MAAMC,gBAAgB;AACtB,MAAMC,cAAc;AACpB,MAAMC,sBAAsB,KAAK,WAAW;;AAE5C;;;;;CAKC,GACD,OAAO,eAAeC,2BAA2BC,UAAkB;IACjE,IAAIC;IAEJ,IAAK,IAAIC,UAAU,GAAGA,WAAWL,aAAaK,UAAW;QACvD,IAAI;YACF,MAAMC,wBAAwBH;YAC9B,QAAO,UAAU;QACnB,EAAE,OAAOI,OAAO;YACdH,YAAYG,iBAAiBb,QAAQa,QAAQ,IAAIb,MAAM;YAEvD,IAAIW,UAAUL,aAAa;gBACzB,kCAAkC;gBAClC,MAAMQ,QAAQP,sBAAsBQ,KAAKC,GAAG,CAAC,GAAGL,UAAU;gBAC1D,MAAM,IAAIM,QAAQ,CAACC,UAAYC,WAAWD,SAASJ;YACrD;QACF;IACF;IAEA,qBAAqB;IACrB,MAAM,IAAIf,sBACR,CAAC,kCAAkC,EAAEO,YAAY,SAAS,CAAC,EAC3DI;AAEJ;AAEA;;CAEC,GACD,eAAeE,wBAAwBH,UAAkB;IACvD,MAAMW,MAAM,CAAC,sDAAsD,EAAEhB,eAAe;IACpF,MAAMiB,SAAS,CAAC,QAAQ,EAAEjB,cAAc,WAAW,EAAEC,cAAc,CAAC,CAAC;IAErE,IAAI;QACF,iCAAiC;QACjC,MAAMV,GAAG2B,KAAK,CAACb,YAAY;YAAEc,WAAW;QAAK;QAE7C,MAAM1B,SACJ,MAAM2B,kBAAkBJ,MACxBtB,EAAE;YACA2B,KAAKhB;YACLY,QAAQ,CAACK,IAAMA,EAAEC,QAAQ,CAACN;YAC1BO,OAAO,IAAIvB,cAAcwB,KAAK,CAAC,KAAKC,MAAM;QAC5C;IAEJ,EAAE,OAAOjB,OAAO;QACd,MAAM,IAAId,sBACR,2CACAc,iBAAiBb,QAAQa,QAAQkB;IAErC;AACF;AAEA;;CAEC,GACD,eAAeP,kBAAkBJ,GAAW;IAC1C,MAAMY,MAAM,MAAMC,MAAMb;IAExB,IAAI,CAACY,IAAIE,EAAE,EAAE;QACX,MAAM,IAAIlC,MAAM,CAAC,KAAK,EAAEgC,IAAIG,MAAM,CAAC,EAAE,EAAEH,IAAII,UAAU,EAAE;IACzD;IAEA,IAAI,CAACJ,IAAIK,IAAI,EAAE;QACb,MAAM,IAAIrC,MAAM,CAAC,oBAAoB,EAAEoB,KAAK;IAC9C;IAEA,OAAOxB,SAAS0C,IAAI,CAACN,IAAIK,IAAI;AAC/B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"download-template.spec.d.ts","sourceRoot":"","sources":["../../src/utils/download-template.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { downloadTemplateFromGitHub, TemplateDownloadError } from './download-template.js';
|
|
5
|
+
describe('downloadTemplateFromGitHub', ()=>{
|
|
6
|
+
const testTempDir = path.join(process.cwd(), '.test-temp-download');
|
|
7
|
+
beforeEach(async ()=>{
|
|
8
|
+
await fs.mkdir(testTempDir, {
|
|
9
|
+
recursive: true
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
afterEach(async ()=>{
|
|
13
|
+
await fs.rm(testTempDir, {
|
|
14
|
+
force: true,
|
|
15
|
+
recursive: true
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
it('should download and extract template to target directory', async ()=>{
|
|
19
|
+
const targetDir = path.join(testTempDir, 'test-project');
|
|
20
|
+
await downloadTemplateFromGitHub(targetDir);
|
|
21
|
+
// Verify essential files exist
|
|
22
|
+
const packageJson = await fs.readFile(path.join(targetDir, 'package.json'), 'utf-8');
|
|
23
|
+
expect(packageJson).toContain('payload');
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe('downloadTemplateFromGitHub - retry logic', ()=>{
|
|
27
|
+
const testTempDir = path.join(process.cwd(), '.test-temp-download');
|
|
28
|
+
beforeEach(async ()=>{
|
|
29
|
+
await fs.mkdir(testTempDir, {
|
|
30
|
+
recursive: true
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
afterEach(async ()=>{
|
|
34
|
+
await fs.rm(testTempDir, {
|
|
35
|
+
force: true,
|
|
36
|
+
recursive: true
|
|
37
|
+
});
|
|
38
|
+
vi.restoreAllMocks();
|
|
39
|
+
});
|
|
40
|
+
it('should retry on network failure up to 3 times', async ()=>{
|
|
41
|
+
const targetDir = path.join(testTempDir, 'retry-test');
|
|
42
|
+
// Mock fetch to fail twice then succeed
|
|
43
|
+
let attemptCount = 0;
|
|
44
|
+
const originalFetch = global.fetch;
|
|
45
|
+
global.fetch = vi.fn(async (...args)=>{
|
|
46
|
+
attemptCount++;
|
|
47
|
+
if (attemptCount < 3) {
|
|
48
|
+
// Simulate network error
|
|
49
|
+
return {
|
|
50
|
+
ok: false,
|
|
51
|
+
status: 500,
|
|
52
|
+
statusText: 'Server Error'
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// Third attempt succeeds
|
|
56
|
+
return await originalFetch(...args);
|
|
57
|
+
});
|
|
58
|
+
await downloadTemplateFromGitHub(targetDir);
|
|
59
|
+
// Expect at least 3 calls (2 failures + 1 success)
|
|
60
|
+
expect(attemptCount).toBeGreaterThanOrEqual(3);
|
|
61
|
+
});
|
|
62
|
+
it('should throw error after 3 failed attempts', async ()=>{
|
|
63
|
+
const targetDir = path.join(testTempDir, 'fail-test');
|
|
64
|
+
// Mock fetch to always fail
|
|
65
|
+
global.fetch = vi.fn(()=>{
|
|
66
|
+
return Promise.resolve({
|
|
67
|
+
ok: false,
|
|
68
|
+
status: 500,
|
|
69
|
+
statusText: 'Server Error'
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
await expect(downloadTemplateFromGitHub(targetDir)).rejects.toThrow(TemplateDownloadError);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
//# sourceMappingURL=download-template.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/download-template.spec.ts"],"sourcesContent":["import fs from 'fs/promises'\nimport path from 'path'\nimport { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'\n\nimport { downloadTemplateFromGitHub, TemplateDownloadError } from './download-template.js'\n\ndescribe('downloadTemplateFromGitHub', () => {\n const testTempDir = path.join(process.cwd(), '.test-temp-download')\n\n beforeEach(async () => {\n await fs.mkdir(testTempDir, { recursive: true })\n })\n\n afterEach(async () => {\n await fs.rm(testTempDir, { force: true, recursive: true })\n })\n\n it('should download and extract template to target directory', async () => {\n const targetDir = path.join(testTempDir, 'test-project')\n\n await downloadTemplateFromGitHub(targetDir)\n\n // Verify essential files exist\n const packageJson = await fs.readFile(path.join(targetDir, 'package.json'), 'utf-8')\n expect(packageJson).toContain('payload')\n })\n})\n\ndescribe('downloadTemplateFromGitHub - retry logic', () => {\n const testTempDir = path.join(process.cwd(), '.test-temp-download')\n\n beforeEach(async () => {\n await fs.mkdir(testTempDir, { recursive: true })\n })\n\n afterEach(async () => {\n await fs.rm(testTempDir, { force: true, recursive: true })\n vi.restoreAllMocks()\n })\n\n it('should retry on network failure up to 3 times', async () => {\n const targetDir = path.join(testTempDir, 'retry-test')\n\n // Mock fetch to fail twice then succeed\n let attemptCount = 0\n const originalFetch = global.fetch\n global.fetch = vi.fn(async (...args: Parameters<typeof fetch>) => {\n attemptCount++\n if (attemptCount < 3) {\n // Simulate network error\n return {\n ok: false,\n status: 500,\n statusText: 'Server Error',\n } as Response\n }\n // Third attempt succeeds\n return await originalFetch(...args)\n })\n\n await downloadTemplateFromGitHub(targetDir)\n\n // Expect at least 3 calls (2 failures + 1 success)\n expect(attemptCount).toBeGreaterThanOrEqual(3)\n })\n\n it('should throw error after 3 failed attempts', async () => {\n const targetDir = path.join(testTempDir, 'fail-test')\n\n // Mock fetch to always fail\n global.fetch = vi.fn(() => {\n return Promise.resolve({\n ok: false,\n status: 500,\n statusText: 'Server Error',\n } as Response)\n })\n\n await expect(downloadTemplateFromGitHub(targetDir)).rejects.toThrow(TemplateDownloadError)\n })\n})\n"],"names":["fs","path","afterEach","beforeEach","describe","expect","it","vi","downloadTemplateFromGitHub","TemplateDownloadError","testTempDir","join","process","cwd","mkdir","recursive","rm","force","targetDir","packageJson","readFile","toContain","restoreAllMocks","attemptCount","originalFetch","global","fetch","fn","args","ok","status","statusText","toBeGreaterThanOrEqual","Promise","resolve","rejects","toThrow"],"mappings":"AAAA,OAAOA,QAAQ,cAAa;AAC5B,OAAOC,UAAU,OAAM;AACvB,SAASC,SAAS,EAAEC,UAAU,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAQ;AAExE,SAASC,0BAA0B,EAAEC,qBAAqB,QAAQ,yBAAwB;AAE1FL,SAAS,8BAA8B;IACrC,MAAMM,cAAcT,KAAKU,IAAI,CAACC,QAAQC,GAAG,IAAI;IAE7CV,WAAW;QACT,MAAMH,GAAGc,KAAK,CAACJ,aAAa;YAAEK,WAAW;QAAK;IAChD;IAEAb,UAAU;QACR,MAAMF,GAAGgB,EAAE,CAACN,aAAa;YAAEO,OAAO;YAAMF,WAAW;QAAK;IAC1D;IAEAT,GAAG,4DAA4D;QAC7D,MAAMY,YAAYjB,KAAKU,IAAI,CAACD,aAAa;QAEzC,MAAMF,2BAA2BU;QAEjC,+BAA+B;QAC/B,MAAMC,cAAc,MAAMnB,GAAGoB,QAAQ,CAACnB,KAAKU,IAAI,CAACO,WAAW,iBAAiB;QAC5Eb,OAAOc,aAAaE,SAAS,CAAC;IAChC;AACF;AAEAjB,SAAS,4CAA4C;IACnD,MAAMM,cAAcT,KAAKU,IAAI,CAACC,QAAQC,GAAG,IAAI;IAE7CV,WAAW;QACT,MAAMH,GAAGc,KAAK,CAACJ,aAAa;YAAEK,WAAW;QAAK;IAChD;IAEAb,UAAU;QACR,MAAMF,GAAGgB,EAAE,CAACN,aAAa;YAAEO,OAAO;YAAMF,WAAW;QAAK;QACxDR,GAAGe,eAAe;IACpB;IAEAhB,GAAG,iDAAiD;QAClD,MAAMY,YAAYjB,KAAKU,IAAI,CAACD,aAAa;QAEzC,wCAAwC;QACxC,IAAIa,eAAe;QACnB,MAAMC,gBAAgBC,OAAOC,KAAK;QAClCD,OAAOC,KAAK,GAAGnB,GAAGoB,EAAE,CAAC,OAAO,GAAGC;YAC7BL;YACA,IAAIA,eAAe,GAAG;gBACpB,yBAAyB;gBACzB,OAAO;oBACLM,IAAI;oBACJC,QAAQ;oBACRC,YAAY;gBACd;YACF;YACA,yBAAyB;YACzB,OAAO,MAAMP,iBAAiBI;QAChC;QAEA,MAAMpB,2BAA2BU;QAEjC,mDAAmD;QACnDb,OAAOkB,cAAcS,sBAAsB,CAAC;IAC9C;IAEA1B,GAAG,8CAA8C;QAC/C,MAAMY,YAAYjB,KAAKU,IAAI,CAACD,aAAa;QAEzC,4BAA4B;QAC5Be,OAAOC,KAAK,GAAGnB,GAAGoB,EAAE,CAAC;YACnB,OAAOM,QAAQC,OAAO,CAAC;gBACrBL,IAAI;gBACJC,QAAQ;gBACRC,YAAY;YACd;QACF;QAEA,MAAM1B,OAAOG,2BAA2BU,YAAYiB,OAAO,CAACC,OAAO,CAAC3B;IACtE;AACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a cryptographically secure secret for PAYLOAD_SECRET
|
|
3
|
+
*
|
|
4
|
+
* @returns 24-character hexadecimal string
|
|
5
|
+
*/
|
|
6
|
+
export declare function generateSecret(): string;
|
|
7
|
+
/**
|
|
8
|
+
* Manage environment files for a new project
|
|
9
|
+
*
|
|
10
|
+
* Creates .env from .env.example (if exists) and populates PAYLOAD_SECRET
|
|
11
|
+
*
|
|
12
|
+
* @param projectPath - Path to project directory
|
|
13
|
+
* @returns The generated payload secret
|
|
14
|
+
*/
|
|
15
|
+
export declare function manageEnvFiles(projectPath: string): Promise<string>;
|
|
16
|
+
//# sourceMappingURL=env-management.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-management.d.ts","sourceRoot":"","sources":["../../src/utils/env-management.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAoDD;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA8CzE"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
/**
|
|
5
|
+
* Generate a cryptographically secure secret for PAYLOAD_SECRET
|
|
6
|
+
*
|
|
7
|
+
* @returns 24-character hexadecimal string
|
|
8
|
+
*/ export function generateSecret() {
|
|
9
|
+
return randomBytes(32).toString('hex').slice(0, 24);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Sanitize and process environment file contents
|
|
13
|
+
*
|
|
14
|
+
* @param contents - Raw .env file contents
|
|
15
|
+
* @param payloadSecret - Secret to use for PAYLOAD_SECRET
|
|
16
|
+
* @returns Processed .env contents
|
|
17
|
+
*/ function sanitizeEnv(contents, payloadSecret) {
|
|
18
|
+
const lines = contents.split('\n');
|
|
19
|
+
const seenKeys = new Set();
|
|
20
|
+
const processedLines = [];
|
|
21
|
+
for (const line of lines){
|
|
22
|
+
// Skip empty lines and comments
|
|
23
|
+
if (!line.trim() || line.trim().startsWith('#')) {
|
|
24
|
+
processedLines.push(line);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
// Skip lines without =
|
|
28
|
+
if (!line.includes('=')) {
|
|
29
|
+
processedLines.push(line);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const [key] = line.split('=');
|
|
33
|
+
const trimmedKey = key.trim();
|
|
34
|
+
// Skip duplicate keys
|
|
35
|
+
if (seenKeys.has(trimmedKey)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
seenKeys.add(trimmedKey);
|
|
39
|
+
// Replace PAYLOAD_SECRET with actual value
|
|
40
|
+
if (trimmedKey === 'PAYLOAD_SECRET' || trimmedKey === 'PAYLOAD_SECRET_KEY') {
|
|
41
|
+
processedLines.push(`PAYLOAD_SECRET=${payloadSecret}`);
|
|
42
|
+
} else {
|
|
43
|
+
processedLines.push(line);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Add PAYLOAD_SECRET if not present
|
|
47
|
+
if (!seenKeys.has('PAYLOAD_SECRET') && !seenKeys.has('PAYLOAD_SECRET_KEY')) {
|
|
48
|
+
processedLines.push(`PAYLOAD_SECRET=${payloadSecret}`);
|
|
49
|
+
}
|
|
50
|
+
return processedLines.join('\n');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Manage environment files for a new project
|
|
54
|
+
*
|
|
55
|
+
* Creates .env from .env.example (if exists) and populates PAYLOAD_SECRET
|
|
56
|
+
*
|
|
57
|
+
* @param projectPath - Path to project directory
|
|
58
|
+
* @returns The generated payload secret
|
|
59
|
+
*/ export async function manageEnvFiles(projectPath) {
|
|
60
|
+
const payloadSecret = generateSecret();
|
|
61
|
+
const envExamplePath = path.join(projectPath, '.env.example');
|
|
62
|
+
const envPath = path.join(projectPath, '.env');
|
|
63
|
+
try {
|
|
64
|
+
// Check if .env.example exists
|
|
65
|
+
let envExampleContents = '';
|
|
66
|
+
try {
|
|
67
|
+
envExampleContents = await fs.readFile(envExamplePath, 'utf-8');
|
|
68
|
+
} catch {
|
|
69
|
+
// No .env.example, create minimal .env
|
|
70
|
+
envExampleContents = '';
|
|
71
|
+
}
|
|
72
|
+
// Check if .env already exists
|
|
73
|
+
let envExists = false;
|
|
74
|
+
try {
|
|
75
|
+
await fs.access(envPath);
|
|
76
|
+
envExists = true;
|
|
77
|
+
} catch {
|
|
78
|
+
// .env doesn't exist
|
|
79
|
+
}
|
|
80
|
+
if (!envExists) {
|
|
81
|
+
// Create .env from .env.example
|
|
82
|
+
const sanitized = sanitizeEnv(envExampleContents, payloadSecret);
|
|
83
|
+
await fs.writeFile(envPath, sanitized, 'utf-8');
|
|
84
|
+
} else {
|
|
85
|
+
// Update existing .env if it doesn't have PAYLOAD_SECRET
|
|
86
|
+
const existingContents = await fs.readFile(envPath, 'utf-8');
|
|
87
|
+
if (!existingContents.includes('PAYLOAD_SECRET=') && !existingContents.includes('PAYLOAD_SECRET_KEY=')) {
|
|
88
|
+
const sanitized = sanitizeEnv(existingContents, payloadSecret);
|
|
89
|
+
await fs.writeFile(envPath, sanitized, 'utf-8');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return payloadSecret;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
throw new Error(`Failed to manage environment files: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
//# sourceMappingURL=env-management.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/env-management.ts"],"sourcesContent":["import { randomBytes } from 'crypto'\nimport fs from 'fs/promises'\nimport path from 'path'\n\n/**\n * Generate a cryptographically secure secret for PAYLOAD_SECRET\n *\n * @returns 24-character hexadecimal string\n */\nexport function generateSecret(): string {\n return randomBytes(32).toString('hex').slice(0, 24)\n}\n\n/**\n * Sanitize and process environment file contents\n *\n * @param contents - Raw .env file contents\n * @param payloadSecret - Secret to use for PAYLOAD_SECRET\n * @returns Processed .env contents\n */\nfunction sanitizeEnv(contents: string, payloadSecret: string): string {\n const lines = contents.split('\\n')\n const seenKeys = new Set<string>()\n const processedLines: string[] = []\n\n for (const line of lines) {\n // Skip empty lines and comments\n if (!line.trim() || line.trim().startsWith('#')) {\n processedLines.push(line)\n continue\n }\n\n // Skip lines without =\n if (!line.includes('=')) {\n processedLines.push(line)\n continue\n }\n\n const [key] = line.split('=')\n const trimmedKey = key.trim()\n\n // Skip duplicate keys\n if (seenKeys.has(trimmedKey)) {\n continue\n }\n seenKeys.add(trimmedKey)\n\n // Replace PAYLOAD_SECRET with actual value\n if (trimmedKey === 'PAYLOAD_SECRET' || trimmedKey === 'PAYLOAD_SECRET_KEY') {\n processedLines.push(`PAYLOAD_SECRET=${payloadSecret}`)\n } else {\n processedLines.push(line)\n }\n }\n\n // Add PAYLOAD_SECRET if not present\n if (!seenKeys.has('PAYLOAD_SECRET') && !seenKeys.has('PAYLOAD_SECRET_KEY')) {\n processedLines.push(`PAYLOAD_SECRET=${payloadSecret}`)\n }\n\n return processedLines.join('\\n')\n}\n\n/**\n * Manage environment files for a new project\n *\n * Creates .env from .env.example (if exists) and populates PAYLOAD_SECRET\n *\n * @param projectPath - Path to project directory\n * @returns The generated payload secret\n */\nexport async function manageEnvFiles(projectPath: string): Promise<string> {\n const payloadSecret = generateSecret()\n const envExamplePath = path.join(projectPath, '.env.example')\n const envPath = path.join(projectPath, '.env')\n\n try {\n // Check if .env.example exists\n let envExampleContents = ''\n try {\n envExampleContents = await fs.readFile(envExamplePath, 'utf-8')\n } catch {\n // No .env.example, create minimal .env\n envExampleContents = ''\n }\n\n // Check if .env already exists\n let envExists = false\n try {\n await fs.access(envPath)\n envExists = true\n } catch {\n // .env doesn't exist\n }\n\n if (!envExists) {\n // Create .env from .env.example\n const sanitized = sanitizeEnv(envExampleContents, payloadSecret)\n await fs.writeFile(envPath, sanitized, 'utf-8')\n } else {\n // Update existing .env if it doesn't have PAYLOAD_SECRET\n const existingContents = await fs.readFile(envPath, 'utf-8')\n if (\n !existingContents.includes('PAYLOAD_SECRET=') &&\n !existingContents.includes('PAYLOAD_SECRET_KEY=')\n ) {\n const sanitized = sanitizeEnv(existingContents, payloadSecret)\n await fs.writeFile(envPath, sanitized, 'utf-8')\n }\n }\n\n return payloadSecret\n } catch (error) {\n throw new Error(\n `Failed to manage environment files: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n }\n}\n"],"names":["randomBytes","fs","path","generateSecret","toString","slice","sanitizeEnv","contents","payloadSecret","lines","split","seenKeys","Set","processedLines","line","trim","startsWith","push","includes","key","trimmedKey","has","add","join","manageEnvFiles","projectPath","envExamplePath","envPath","envExampleContents","readFile","envExists","access","sanitized","writeFile","existingContents","error","Error","message"],"mappings":"AAAA,SAASA,WAAW,QAAQ,SAAQ;AACpC,OAAOC,QAAQ,cAAa;AAC5B,OAAOC,UAAU,OAAM;AAEvB;;;;CAIC,GACD,OAAO,SAASC;IACd,OAAOH,YAAY,IAAII,QAAQ,CAAC,OAAOC,KAAK,CAAC,GAAG;AAClD;AAEA;;;;;;CAMC,GACD,SAASC,YAAYC,QAAgB,EAAEC,aAAqB;IAC1D,MAAMC,QAAQF,SAASG,KAAK,CAAC;IAC7B,MAAMC,WAAW,IAAIC;IACrB,MAAMC,iBAA2B,EAAE;IAEnC,KAAK,MAAMC,QAAQL,MAAO;QACxB,gCAAgC;QAChC,IAAI,CAACK,KAAKC,IAAI,MAAMD,KAAKC,IAAI,GAAGC,UAAU,CAAC,MAAM;YAC/CH,eAAeI,IAAI,CAACH;YACpB;QACF;QAEA,uBAAuB;QACvB,IAAI,CAACA,KAAKI,QAAQ,CAAC,MAAM;YACvBL,eAAeI,IAAI,CAACH;YACpB;QACF;QAEA,MAAM,CAACK,IAAI,GAAGL,KAAKJ,KAAK,CAAC;QACzB,MAAMU,aAAaD,IAAIJ,IAAI;QAE3B,sBAAsB;QACtB,IAAIJ,SAASU,GAAG,CAACD,aAAa;YAC5B;QACF;QACAT,SAASW,GAAG,CAACF;QAEb,2CAA2C;QAC3C,IAAIA,eAAe,oBAAoBA,eAAe,sBAAsB;YAC1EP,eAAeI,IAAI,CAAC,CAAC,eAAe,EAAET,eAAe;QACvD,OAAO;YACLK,eAAeI,IAAI,CAACH;QACtB;IACF;IAEA,oCAAoC;IACpC,IAAI,CAACH,SAASU,GAAG,CAAC,qBAAqB,CAACV,SAASU,GAAG,CAAC,uBAAuB;QAC1ER,eAAeI,IAAI,CAAC,CAAC,eAAe,EAAET,eAAe;IACvD;IAEA,OAAOK,eAAeU,IAAI,CAAC;AAC7B;AAEA;;;;;;;CAOC,GACD,OAAO,eAAeC,eAAeC,WAAmB;IACtD,MAAMjB,gBAAgBL;IACtB,MAAMuB,iBAAiBxB,KAAKqB,IAAI,CAACE,aAAa;IAC9C,MAAME,UAAUzB,KAAKqB,IAAI,CAACE,aAAa;IAEvC,IAAI;QACF,+BAA+B;QAC/B,IAAIG,qBAAqB;QACzB,IAAI;YACFA,qBAAqB,MAAM3B,GAAG4B,QAAQ,CAACH,gBAAgB;QACzD,EAAE,OAAM;YACN,uCAAuC;YACvCE,qBAAqB;QACvB;QAEA,+BAA+B;QAC/B,IAAIE,YAAY;QAChB,IAAI;YACF,MAAM7B,GAAG8B,MAAM,CAACJ;YAChBG,YAAY;QACd,EAAE,OAAM;QACN,qBAAqB;QACvB;QAEA,IAAI,CAACA,WAAW;YACd,gCAAgC;YAChC,MAAME,YAAY1B,YAAYsB,oBAAoBpB;YAClD,MAAMP,GAAGgC,SAAS,CAACN,SAASK,WAAW;QACzC,OAAO;YACL,yDAAyD;YACzD,MAAME,mBAAmB,MAAMjC,GAAG4B,QAAQ,CAACF,SAAS;YACpD,IACE,CAACO,iBAAiBhB,QAAQ,CAAC,sBAC3B,CAACgB,iBAAiBhB,QAAQ,CAAC,wBAC3B;gBACA,MAAMc,YAAY1B,YAAY4B,kBAAkB1B;gBAChD,MAAMP,GAAGgC,SAAS,CAACN,SAASK,WAAW;YACzC;QACF;QAEA,OAAOxB;IACT,EAAE,OAAO2B,OAAO;QACd,MAAM,IAAIC,MACR,CAAC,oCAAoC,EAAED,iBAAiBC,QAAQD,MAAME,OAAO,GAAG,iBAAiB;IAErG;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-management.spec.d.ts","sourceRoot":"","sources":["../../src/utils/env-management.spec.ts"],"names":[],"mappings":""}
|