@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.
Files changed (305) hide show
  1. package/LICENSE.md +11 -0
  2. package/bin/cli.js +4 -0
  3. package/dist/api/control-plane.d.ts +124 -0
  4. package/dist/api/control-plane.d.ts.map +1 -0
  5. package/dist/api/control-plane.js +245 -0
  6. package/dist/api/control-plane.js.map +1 -0
  7. package/dist/api/control-plane.spec.d.ts +2 -0
  8. package/dist/api/control-plane.spec.d.ts.map +1 -0
  9. package/dist/api/control-plane.spec.js +296 -0
  10. package/dist/api/control-plane.spec.js.map +1 -0
  11. package/dist/api/figma-api.d.ts +31 -0
  12. package/dist/api/figma-api.d.ts.map +1 -0
  13. package/dist/api/figma-api.js +169 -0
  14. package/dist/api/figma-api.js.map +1 -0
  15. package/dist/api/figma-api.spec.d.ts +14 -0
  16. package/dist/api/figma-api.spec.d.ts.map +1 -0
  17. package/dist/api/figma-api.spec.js +201 -0
  18. package/dist/api/figma-api.spec.js.map +1 -0
  19. package/dist/auth/__tests__/fixtures.d.ts +26 -0
  20. package/dist/auth/__tests__/fixtures.d.ts.map +1 -0
  21. package/dist/auth/__tests__/fixtures.js +67 -0
  22. package/dist/auth/__tests__/fixtures.js.map +1 -0
  23. package/dist/auth/__tests__/mocks.d.ts +33 -0
  24. package/dist/auth/__tests__/mocks.d.ts.map +1 -0
  25. package/dist/auth/__tests__/mocks.js +63 -0
  26. package/dist/auth/__tests__/mocks.js.map +1 -0
  27. package/dist/auth/browser.d.ts +31 -0
  28. package/dist/auth/browser.d.ts.map +1 -0
  29. package/dist/auth/browser.js +41 -0
  30. package/dist/auth/browser.js.map +1 -0
  31. package/dist/auth/callback-server.d.ts +61 -0
  32. package/dist/auth/callback-server.d.ts.map +1 -0
  33. package/dist/auth/callback-server.js +168 -0
  34. package/dist/auth/callback-server.js.map +1 -0
  35. package/dist/auth/callback-server.spec.d.ts +2 -0
  36. package/dist/auth/callback-server.spec.d.ts.map +1 -0
  37. package/dist/auth/callback-server.spec.js +85 -0
  38. package/dist/auth/callback-server.spec.js.map +1 -0
  39. package/dist/auth/crypto-utils.d.ts +33 -0
  40. package/dist/auth/crypto-utils.d.ts.map +1 -0
  41. package/dist/auth/crypto-utils.js +96 -0
  42. package/dist/auth/crypto-utils.js.map +1 -0
  43. package/dist/auth/crypto-utils.spec.d.ts +2 -0
  44. package/dist/auth/crypto-utils.spec.d.ts.map +1 -0
  45. package/dist/auth/crypto-utils.spec.js +36 -0
  46. package/dist/auth/crypto-utils.spec.js.map +1 -0
  47. package/dist/auth/jwt-validator.d.ts +41 -0
  48. package/dist/auth/jwt-validator.d.ts.map +1 -0
  49. package/dist/auth/jwt-validator.js +113 -0
  50. package/dist/auth/jwt-validator.js.map +1 -0
  51. package/dist/auth/jwt-validator.spec.d.ts +10 -0
  52. package/dist/auth/jwt-validator.spec.d.ts.map +1 -0
  53. package/dist/auth/jwt-validator.spec.js +236 -0
  54. package/dist/auth/jwt-validator.spec.js.map +1 -0
  55. package/dist/auth/oauth-flow.d.ts +62 -0
  56. package/dist/auth/oauth-flow.d.ts.map +1 -0
  57. package/dist/auth/oauth-flow.js +172 -0
  58. package/dist/auth/oauth-flow.js.map +1 -0
  59. package/dist/auth/oauth-flow.spec.d.ts +2 -0
  60. package/dist/auth/oauth-flow.spec.d.ts.map +1 -0
  61. package/dist/auth/oauth-flow.spec.js +134 -0
  62. package/dist/auth/oauth-flow.spec.js.map +1 -0
  63. package/dist/auth/pages/error.html +85 -0
  64. package/dist/auth/pages/success.html +74 -0
  65. package/dist/auth/pkce.d.ts +51 -0
  66. package/dist/auth/pkce.d.ts.map +1 -0
  67. package/dist/auth/pkce.js +71 -0
  68. package/dist/auth/pkce.js.map +1 -0
  69. package/dist/auth/pkce.spec.d.ts +2 -0
  70. package/dist/auth/pkce.spec.d.ts.map +1 -0
  71. package/dist/auth/pkce.spec.js +32 -0
  72. package/dist/auth/pkce.spec.js.map +1 -0
  73. package/dist/auth/project-token.d.ts +47 -0
  74. package/dist/auth/project-token.d.ts.map +1 -0
  75. package/dist/auth/project-token.js +134 -0
  76. package/dist/auth/project-token.js.map +1 -0
  77. package/dist/auth/project-token.spec.d.ts +2 -0
  78. package/dist/auth/project-token.spec.d.ts.map +1 -0
  79. package/dist/auth/project-token.spec.js +332 -0
  80. package/dist/auth/project-token.spec.js.map +1 -0
  81. package/dist/auth/refresh.d.ts +35 -0
  82. package/dist/auth/refresh.d.ts.map +1 -0
  83. package/dist/auth/refresh.js +149 -0
  84. package/dist/auth/refresh.js.map +1 -0
  85. package/dist/auth/refresh.spec.d.ts +2 -0
  86. package/dist/auth/refresh.spec.d.ts.map +1 -0
  87. package/dist/auth/refresh.spec.js +105 -0
  88. package/dist/auth/refresh.spec.js.map +1 -0
  89. package/dist/auth/token-store.d.ts +110 -0
  90. package/dist/auth/token-store.d.ts.map +1 -0
  91. package/dist/auth/token-store.js +188 -0
  92. package/dist/auth/token-store.js.map +1 -0
  93. package/dist/auth/token-store.spec.d.ts +2 -0
  94. package/dist/auth/token-store.spec.d.ts.map +1 -0
  95. package/dist/auth/token-store.spec.js +276 -0
  96. package/dist/auth/token-store.spec.js.map +1 -0
  97. package/dist/auth/types.d.ts +197 -0
  98. package/dist/auth/types.d.ts.map +1 -0
  99. package/dist/auth/types.js +6 -0
  100. package/dist/auth/types.js.map +1 -0
  101. package/dist/cli.d.ts +5 -0
  102. package/dist/cli.d.ts.map +1 -0
  103. package/dist/cli.js +108 -0
  104. package/dist/cli.js.map +1 -0
  105. package/dist/commands/__tests__/test-utils.d.ts +28 -0
  106. package/dist/commands/__tests__/test-utils.d.ts.map +1 -0
  107. package/dist/commands/__tests__/test-utils.js +34 -0
  108. package/dist/commands/__tests__/test-utils.js.map +1 -0
  109. package/dist/commands/deploy.d.ts +16 -0
  110. package/dist/commands/deploy.d.ts.map +1 -0
  111. package/dist/commands/deploy.js +297 -0
  112. package/dist/commands/deploy.js.map +1 -0
  113. package/dist/commands/deploy.spec.d.ts +2 -0
  114. package/dist/commands/deploy.spec.d.ts.map +1 -0
  115. package/dist/commands/deploy.spec.js +18 -0
  116. package/dist/commands/deploy.spec.js.map +1 -0
  117. package/dist/commands/init.d.ts +30 -0
  118. package/dist/commands/init.d.ts.map +1 -0
  119. package/dist/commands/init.js +410 -0
  120. package/dist/commands/init.js.map +1 -0
  121. package/dist/commands/init.spec.d.ts +2 -0
  122. package/dist/commands/init.spec.d.ts.map +1 -0
  123. package/dist/commands/init.spec.js +283 -0
  124. package/dist/commands/init.spec.js.map +1 -0
  125. package/dist/commands/list-tokens.d.ts +16 -0
  126. package/dist/commands/list-tokens.d.ts.map +1 -0
  127. package/dist/commands/list-tokens.js +44 -0
  128. package/dist/commands/list-tokens.js.map +1 -0
  129. package/dist/commands/login.d.ts +17 -0
  130. package/dist/commands/login.d.ts.map +1 -0
  131. package/dist/commands/login.js +69 -0
  132. package/dist/commands/login.js.map +1 -0
  133. package/dist/commands/logout.d.ts +16 -0
  134. package/dist/commands/logout.d.ts.map +1 -0
  135. package/dist/commands/logout.js +43 -0
  136. package/dist/commands/logout.js.map +1 -0
  137. package/dist/config/oauth.d.ts +19 -0
  138. package/dist/config/oauth.d.ts.map +1 -0
  139. package/dist/config/oauth.js +43 -0
  140. package/dist/config/oauth.js.map +1 -0
  141. package/dist/constants.d.ts +27 -0
  142. package/dist/constants.d.ts.map +1 -0
  143. package/dist/constants.js +27 -0
  144. package/dist/constants.js.map +1 -0
  145. package/dist/index.d.ts +5 -0
  146. package/dist/index.d.ts.map +1 -0
  147. package/dist/index.js +6 -0
  148. package/dist/index.js.map +1 -0
  149. package/dist/plugin/adapter.d.ts +5 -0
  150. package/dist/plugin/adapter.d.ts.map +1 -0
  151. package/dist/plugin/adapter.js +139 -0
  152. package/dist/plugin/adapter.js.map +1 -0
  153. package/dist/plugin/build-config.d.ts +37 -0
  154. package/dist/plugin/build-config.d.ts.map +1 -0
  155. package/dist/plugin/build-config.js +25 -0
  156. package/dist/plugin/build-config.js.map +1 -0
  157. package/dist/plugin/build-config.spec.d.ts +2 -0
  158. package/dist/plugin/build-config.spec.d.ts.map +1 -0
  159. package/dist/plugin/build-config.spec.js +72 -0
  160. package/dist/plugin/build-config.spec.js.map +1 -0
  161. package/dist/types/config.d.ts +45 -0
  162. package/dist/types/config.d.ts.map +1 -0
  163. package/dist/types/config.js +9 -0
  164. package/dist/types/config.js.map +1 -0
  165. package/dist/types.d.ts +60 -0
  166. package/dist/types.d.ts.map +1 -0
  167. package/dist/types.js +3 -0
  168. package/dist/types.js.map +1 -0
  169. package/dist/utils/asset-collection.d.ts +30 -0
  170. package/dist/utils/asset-collection.d.ts.map +1 -0
  171. package/dist/utils/asset-collection.js +117 -0
  172. package/dist/utils/asset-collection.js.map +1 -0
  173. package/dist/utils/asset-collection.spec.d.ts +2 -0
  174. package/dist/utils/asset-collection.spec.d.ts.map +1 -0
  175. package/dist/utils/asset-collection.spec.js +155 -0
  176. package/dist/utils/asset-collection.spec.js.map +1 -0
  177. package/dist/utils/build-detection.d.ts +32 -0
  178. package/dist/utils/build-detection.d.ts.map +1 -0
  179. package/dist/utils/build-detection.js +55 -0
  180. package/dist/utils/build-detection.js.map +1 -0
  181. package/dist/utils/build-detection.spec.d.ts +2 -0
  182. package/dist/utils/build-detection.spec.d.ts.map +1 -0
  183. package/dist/utils/build-detection.spec.js +110 -0
  184. package/dist/utils/build-detection.spec.js.map +1 -0
  185. package/dist/utils/config.d.ts +33 -0
  186. package/dist/utils/config.d.ts.map +1 -0
  187. package/dist/utils/config.js +78 -0
  188. package/dist/utils/config.js.map +1 -0
  189. package/dist/utils/config.spec.d.ts +2 -0
  190. package/dist/utils/config.spec.d.ts.map +1 -0
  191. package/dist/utils/config.spec.js +167 -0
  192. package/dist/utils/config.spec.js.map +1 -0
  193. package/dist/utils/download-template.d.ts +15 -0
  194. package/dist/utils/download-template.d.ts.map +1 -0
  195. package/dist/utils/download-template.js +74 -0
  196. package/dist/utils/download-template.js.map +1 -0
  197. package/dist/utils/download-template.spec.d.ts +2 -0
  198. package/dist/utils/download-template.spec.d.ts.map +1 -0
  199. package/dist/utils/download-template.spec.js +76 -0
  200. package/dist/utils/download-template.spec.js.map +1 -0
  201. package/dist/utils/env-management.d.ts +16 -0
  202. package/dist/utils/env-management.d.ts.map +1 -0
  203. package/dist/utils/env-management.js +98 -0
  204. package/dist/utils/env-management.js.map +1 -0
  205. package/dist/utils/env-management.spec.d.ts +2 -0
  206. package/dist/utils/env-management.spec.d.ts.map +1 -0
  207. package/dist/utils/env-management.spec.js +123 -0
  208. package/dist/utils/env-management.spec.js.map +1 -0
  209. package/dist/utils/format.d.ts +23 -0
  210. package/dist/utils/format.d.ts.map +1 -0
  211. package/dist/utils/format.js +49 -0
  212. package/dist/utils/format.js.map +1 -0
  213. package/dist/utils/formatter.d.ts +11 -0
  214. package/dist/utils/formatter.d.ts.map +1 -0
  215. package/dist/utils/formatter.js +47 -0
  216. package/dist/utils/formatter.js.map +1 -0
  217. package/dist/utils/git.d.ts +15 -0
  218. package/dist/utils/git.d.ts.map +1 -0
  219. package/dist/utils/git.js +92 -0
  220. package/dist/utils/git.js.map +1 -0
  221. package/dist/utils/git.spec.d.ts +2 -0
  222. package/dist/utils/git.spec.d.ts.map +1 -0
  223. package/dist/utils/git.spec.js +99 -0
  224. package/dist/utils/git.spec.js.map +1 -0
  225. package/dist/utils/is-debug.d.ts +6 -0
  226. package/dist/utils/is-debug.d.ts.map +1 -0
  227. package/dist/utils/is-debug.js +8 -0
  228. package/dist/utils/is-debug.js.map +1 -0
  229. package/dist/utils/lambda-config.d.ts +7 -0
  230. package/dist/utils/lambda-config.d.ts.map +1 -0
  231. package/dist/utils/lambda-config.js +144 -0
  232. package/dist/utils/lambda-config.js.map +1 -0
  233. package/dist/utils/lambda-config.spec.d.ts +2 -0
  234. package/dist/utils/lambda-config.spec.d.ts.map +1 -0
  235. package/dist/utils/lambda-config.spec.js +141 -0
  236. package/dist/utils/lambda-config.spec.js.map +1 -0
  237. package/dist/utils/log.d.ts +6 -0
  238. package/dist/utils/log.d.ts.map +1 -0
  239. package/dist/utils/log.js +22 -0
  240. package/dist/utils/log.js.map +1 -0
  241. package/dist/utils/messages.d.ts +10 -0
  242. package/dist/utils/messages.d.ts.map +1 -0
  243. package/dist/utils/messages.js +96 -0
  244. package/dist/utils/messages.js.map +1 -0
  245. package/dist/utils/package-manager.d.ts +12 -0
  246. package/dist/utils/package-manager.d.ts.map +1 -0
  247. package/dist/utils/package-manager.js +37 -0
  248. package/dist/utils/package-manager.js.map +1 -0
  249. package/dist/utils/payload-config-ast.d.ts +30 -0
  250. package/dist/utils/payload-config-ast.d.ts.map +1 -0
  251. package/dist/utils/payload-config-ast.js +259 -0
  252. package/dist/utils/payload-config-ast.js.map +1 -0
  253. package/dist/utils/payload-config-ast.spec.d.ts +2 -0
  254. package/dist/utils/payload-config-ast.spec.d.ts.map +1 -0
  255. package/dist/utils/payload-config-ast.spec.js +303 -0
  256. package/dist/utils/payload-config-ast.spec.js.map +1 -0
  257. package/dist/utils/payload-config-finder.d.ts +9 -0
  258. package/dist/utils/payload-config-finder.d.ts.map +1 -0
  259. package/dist/utils/payload-config-finder.js +26 -0
  260. package/dist/utils/payload-config-finder.js.map +1 -0
  261. package/dist/utils/payload-config-modifier.d.ts +18 -0
  262. package/dist/utils/payload-config-modifier.d.ts.map +1 -0
  263. package/dist/utils/payload-config-modifier.int.spec.d.ts +2 -0
  264. package/dist/utils/payload-config-modifier.int.spec.d.ts.map +1 -0
  265. package/dist/utils/payload-config-modifier.int.spec.js +116 -0
  266. package/dist/utils/payload-config-modifier.int.spec.js.map +1 -0
  267. package/dist/utils/payload-config-modifier.js +232 -0
  268. package/dist/utils/payload-config-modifier.js.map +1 -0
  269. package/dist/utils/payload-config-modifier.spec.d.ts +2 -0
  270. package/dist/utils/payload-config-modifier.spec.d.ts.map +1 -0
  271. package/dist/utils/payload-config-modifier.spec.js +162 -0
  272. package/dist/utils/payload-config-modifier.spec.js.map +1 -0
  273. package/dist/utils/payload-package-check.d.ts +15 -0
  274. package/dist/utils/payload-package-check.d.ts.map +1 -0
  275. package/dist/utils/payload-package-check.js +116 -0
  276. package/dist/utils/payload-package-check.js.map +1 -0
  277. package/dist/utils/payload-package-check.spec.d.ts +2 -0
  278. package/dist/utils/payload-package-check.spec.d.ts.map +1 -0
  279. package/dist/utils/payload-package-check.spec.js +148 -0
  280. package/dist/utils/payload-package-check.spec.js.map +1 -0
  281. package/dist/utils/project.d.ts +58 -0
  282. package/dist/utils/project.d.ts.map +1 -0
  283. package/dist/utils/project.js +252 -0
  284. package/dist/utils/project.js.map +1 -0
  285. package/dist/utils/project.spec.d.ts +2 -0
  286. package/dist/utils/project.spec.d.ts.map +1 -0
  287. package/dist/utils/project.spec.js +222 -0
  288. package/dist/utils/project.spec.js.map +1 -0
  289. package/dist/utils/s3-upload.d.ts +43 -0
  290. package/dist/utils/s3-upload.d.ts.map +1 -0
  291. package/dist/utils/s3-upload.js +128 -0
  292. package/dist/utils/s3-upload.js.map +1 -0
  293. package/dist/utils/s3-upload.spec.d.ts +2 -0
  294. package/dist/utils/s3-upload.spec.d.ts.map +1 -0
  295. package/dist/utils/s3-upload.spec.js +140 -0
  296. package/dist/utils/s3-upload.spec.js.map +1 -0
  297. package/dist/utils/token-display.d.ts +14 -0
  298. package/dist/utils/token-display.d.ts.map +1 -0
  299. package/dist/utils/token-display.js +62 -0
  300. package/dist/utils/token-display.js.map +1 -0
  301. package/dist/utils/typescript-validator.d.ts +11 -0
  302. package/dist/utils/typescript-validator.d.ts.map +1 -0
  303. package/dist/utils/typescript-validator.js +58 -0
  304. package/dist/utils/typescript-validator.js.map +1 -0
  305. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config.spec.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=download-template.spec.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=env-management.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-management.spec.d.ts","sourceRoot":"","sources":["../../src/utils/env-management.spec.ts"],"names":[],"mappings":""}