hatchkit 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/dist/config.d.ts +131 -0
  2. package/dist/config.d.ts.map +1 -0
  3. package/dist/config.js +629 -0
  4. package/dist/config.js.map +1 -0
  5. package/dist/deploy/coolify.d.ts +4 -0
  6. package/dist/deploy/coolify.d.ts.map +1 -0
  7. package/dist/deploy/coolify.js +20 -0
  8. package/dist/deploy/coolify.js.map +1 -0
  9. package/dist/deploy/github.d.ts +4 -0
  10. package/dist/deploy/github.d.ts.map +1 -0
  11. package/dist/deploy/github.js +39 -0
  12. package/dist/deploy/github.js.map +1 -0
  13. package/dist/deploy/gpu.d.ts +4 -0
  14. package/dist/deploy/gpu.d.ts.map +1 -0
  15. package/dist/deploy/gpu.js +97 -0
  16. package/dist/deploy/gpu.js.map +1 -0
  17. package/dist/deploy/keys.d.ts +9 -0
  18. package/dist/deploy/keys.d.ts.map +1 -0
  19. package/dist/deploy/keys.js +73 -0
  20. package/dist/deploy/keys.js.map +1 -0
  21. package/dist/deploy/terraform.d.ts +4 -0
  22. package/dist/deploy/terraform.d.ts.map +1 -0
  23. package/dist/deploy/terraform.js +55 -0
  24. package/dist/deploy/terraform.js.map +1 -0
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +599 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/prompts.d.ts +52 -0
  30. package/dist/prompts.d.ts.map +1 -0
  31. package/dist/prompts.js +313 -0
  32. package/dist/prompts.js.map +1 -0
  33. package/dist/provision/glitchtip.d.ts +6 -0
  34. package/dist/provision/glitchtip.d.ts.map +1 -0
  35. package/dist/provision/glitchtip.js +46 -0
  36. package/dist/provision/glitchtip.js.map +1 -0
  37. package/dist/provision/index.d.ts +9 -0
  38. package/dist/provision/index.d.ts.map +1 -0
  39. package/dist/provision/index.js +108 -0
  40. package/dist/provision/index.js.map +1 -0
  41. package/dist/provision/openpanel.d.ts +8 -0
  42. package/dist/provision/openpanel.d.ts.map +1 -0
  43. package/dist/provision/openpanel.js +66 -0
  44. package/dist/provision/openpanel.js.map +1 -0
  45. package/dist/provision/resend.d.ts +16 -0
  46. package/dist/provision/resend.d.ts.map +1 -0
  47. package/dist/provision/resend.js +43 -0
  48. package/dist/provision/resend.js.map +1 -0
  49. package/dist/scaffold/app.d.ts +13 -0
  50. package/dist/scaffold/app.d.ts.map +1 -0
  51. package/dist/scaffold/app.js +340 -0
  52. package/dist/scaffold/app.js.map +1 -0
  53. package/dist/scaffold/dotenvx.d.ts +30 -0
  54. package/dist/scaffold/dotenvx.d.ts.map +1 -0
  55. package/dist/scaffold/dotenvx.js +142 -0
  56. package/dist/scaffold/dotenvx.js.map +1 -0
  57. package/dist/scaffold/infra.d.ts +17 -0
  58. package/dist/scaffold/infra.d.ts.map +1 -0
  59. package/dist/scaffold/infra.js +200 -0
  60. package/dist/scaffold/infra.js.map +1 -0
  61. package/dist/scaffold/manifest.d.ts +50 -0
  62. package/dist/scaffold/manifest.d.ts.map +1 -0
  63. package/dist/scaffold/manifest.js +83 -0
  64. package/dist/scaffold/manifest.js.map +1 -0
  65. package/dist/scaffold/ml-client.d.ts +20 -0
  66. package/dist/scaffold/ml-client.d.ts.map +1 -0
  67. package/dist/scaffold/ml-client.js +38 -0
  68. package/dist/scaffold/ml-client.js.map +1 -0
  69. package/dist/scaffold/pkg-json.d.ts +20 -0
  70. package/dist/scaffold/pkg-json.d.ts.map +1 -0
  71. package/dist/scaffold/pkg-json.js +113 -0
  72. package/dist/scaffold/pkg-json.js.map +1 -0
  73. package/dist/scaffold/starter-files.d.ts +40 -0
  74. package/dist/scaffold/starter-files.d.ts.map +1 -0
  75. package/dist/scaffold/starter-files.js +197 -0
  76. package/dist/scaffold/starter-files.js.map +1 -0
  77. package/dist/scaffold/update.d.ts +8 -0
  78. package/dist/scaffold/update.d.ts.map +1 -0
  79. package/dist/scaffold/update.js +255 -0
  80. package/dist/scaffold/update.js.map +1 -0
  81. package/dist/templates/addons/analytics/middleware.ts.hbs +13 -0
  82. package/dist/templates/addons/analytics/sentry.ts.hbs +16 -0
  83. package/dist/templates/addons/storage/s3.ts.hbs +40 -0
  84. package/dist/templates/addons/storage/upload.ts.hbs +23 -0
  85. package/dist/templates/addons/stripe/checkout.ts.hbs +27 -0
  86. package/dist/templates/addons/stripe/client.ts.hbs +6 -0
  87. package/dist/templates/addons/stripe/webhook.ts.hbs +39 -0
  88. package/dist/templates/addons/websocket/redis.ts.hbs +25 -0
  89. package/dist/templates/addons/websocket/ws.ts.hbs +32 -0
  90. package/dist/templates/base/.dockerignore.hbs +7 -0
  91. package/dist/templates/base/Dockerfile.hbs +18 -0
  92. package/dist/templates/base/env.example.hbs +60 -0
  93. package/dist/templates/base/github-actions.yml.hbs +35 -0
  94. package/dist/templates/base/gitignore.hbs +5 -0
  95. package/dist/templates/base/package.json.hbs +36 -0
  96. package/dist/templates/base/src/auth/auth.ts.hbs +13 -0
  97. package/dist/templates/base/src/auth/routes.ts.hbs +19 -0
  98. package/dist/templates/base/src/config.ts.hbs +36 -0
  99. package/dist/templates/base/src/db.ts.hbs +12 -0
  100. package/dist/templates/base/src/index.ts.hbs +80 -0
  101. package/dist/templates/base/src/routes/health.ts.hbs +12 -0
  102. package/dist/templates/base/tsconfig.json.hbs +18 -0
  103. package/dist/templates/ml-clients/3d-extraction.ts.hbs +20 -0
  104. package/dist/templates/ml-clients/background-removal.ts.hbs +17 -0
  105. package/dist/templates/ml-clients/custom-hf.ts.hbs +20 -0
  106. package/dist/templates/ml-clients/image-recognition.ts.hbs +22 -0
  107. package/dist/templates/ml-clients/subtitles.ts.hbs +26 -0
  108. package/dist/utils/coolify-api.d.ts +45 -0
  109. package/dist/utils/coolify-api.d.ts.map +1 -0
  110. package/dist/utils/coolify-api.js +72 -0
  111. package/dist/utils/coolify-api.js.map +1 -0
  112. package/dist/utils/errors.d.ts +2 -0
  113. package/dist/utils/errors.d.ts.map +1 -0
  114. package/dist/utils/errors.js +31 -0
  115. package/dist/utils/errors.js.map +1 -0
  116. package/dist/utils/exec.d.ts +23 -0
  117. package/dist/utils/exec.d.ts.map +1 -0
  118. package/dist/utils/exec.js +47 -0
  119. package/dist/utils/exec.js.map +1 -0
  120. package/dist/utils/flags.d.ts +17 -0
  121. package/dist/utils/flags.d.ts.map +1 -0
  122. package/dist/utils/flags.js +116 -0
  123. package/dist/utils/flags.js.map +1 -0
  124. package/dist/utils/hf-api.d.ts +13 -0
  125. package/dist/utils/hf-api.d.ts.map +1 -0
  126. package/dist/utils/hf-api.js +30 -0
  127. package/dist/utils/hf-api.js.map +1 -0
  128. package/dist/utils/ports.d.ts +29 -0
  129. package/dist/utils/ports.d.ts.map +1 -0
  130. package/dist/utils/ports.js +86 -0
  131. package/dist/utils/ports.js.map +1 -0
  132. package/dist/utils/secrets.d.ts +25 -0
  133. package/dist/utils/secrets.d.ts.map +1 -0
  134. package/dist/utils/secrets.js +51 -0
  135. package/dist/utils/secrets.js.map +1 -0
  136. package/dist/utils/template.d.ts +7 -0
  137. package/dist/utils/template.d.ts.map +1 -0
  138. package/dist/utils/template.js +35 -0
  139. package/dist/utils/template.js.map +1 -0
  140. package/dist/utils/validate.d.ts +16 -0
  141. package/dist/utils/validate.d.ts.map +1 -0
  142. package/dist/utils/validate.js +60 -0
  143. package/dist/utils/validate.js.map +1 -0
  144. package/dist/utils/version.d.ts +2 -0
  145. package/dist/utils/version.d.ts.map +1 -0
  146. package/dist/utils/version.js +21 -0
  147. package/dist/utils/version.js.map +1 -0
  148. package/package.json +48 -0
  149. package/scripts/copy-templates.mjs +20 -0
@@ -0,0 +1,142 @@
1
+ /*
2
+ * dotenvx integration for scaffolded projects.
3
+ *
4
+ * Flow at scaffold time:
5
+ * 1. For each key → value mapping the user supplied (prompts OR
6
+ * presets), call `dotenvx.set(key, value, { encrypt: true, path })`.
7
+ * On first call, dotenvx generates a fresh ECIES keypair and
8
+ * writes the public key into `.env.production` + the private
9
+ * key into `.env.keys`.
10
+ * 2. Read `.env.keys` to pull out DOTENV_PRIVATE_KEY_PRODUCTION and
11
+ * mirror it into the OS keychain under
12
+ * `dotenvx:<project-name>:production-private-key`.
13
+ * The on-disk .env.keys stays (gitignored) for local dev
14
+ * ergonomics; keychain is the canonical backup + source for
15
+ * re-deploy.
16
+ * 3. Values the user DIDN'T supply land as plaintext "CHANGE_ME_*"
17
+ * in .env.production — the user can encrypt them later with
18
+ * `dotenvx set KEY value -f .env.production`.
19
+ *
20
+ * None of the values (including the generated BETTER_AUTH_SECRET)
21
+ * are ever logged. The private key is printed once via a deferred
22
+ * hint pointing at `hatchkit keys show <project>`.
23
+ */
24
+ import { randomBytes } from "node:crypto";
25
+ import { existsSync, readFileSync } from "node:fs";
26
+ import { join } from "node:path";
27
+ import { set as dotenvxSet } from "@dotenvx/dotenvx";
28
+ import chalk from "chalk";
29
+ import ora from "ora";
30
+ import { SECRET_KEYS, setSecret } from "../utils/secrets.js";
31
+ /** Set of keys that are always safe to auto-generate if the user
32
+ * doesn't supply them (cryptographically random, no external
33
+ * coordination needed). */
34
+ function autoGeneratedDefaults() {
35
+ return {
36
+ BETTER_AUTH_SECRET: randomBytes(32).toString("hex"),
37
+ };
38
+ }
39
+ /** Populate the starter's `packages/server/.env.production` with
40
+ * encrypted values + a plaintext CHANGE_ME_<KEY> for anything the
41
+ * user didn't supply. Mirrors the generated private key into the
42
+ * macOS Keychain (or equivalent) and returns it so callers can push
43
+ * it to Coolify / print a retrieval hint. */
44
+ export async function seedDotenvxProduction(outputDir, config, userValues) {
45
+ const envPath = join(outputDir, "packages/server/.env.production");
46
+ const envKeysPath = join(outputDir, "packages/server/.env.keys");
47
+ // Determine the full key → value map.
48
+ const defaults = autoGeneratedDefaults();
49
+ const merged = { ...defaults, ...userValues };
50
+ // Every key we want present in .env.production. Includes ones the
51
+ // user didn't supply — those get plaintext CHANGE_ME_<KEY> so
52
+ // misconfigured deploys fail loudly rather than silently running
53
+ // with empty secrets.
54
+ const allKeys = candidateKeys(config);
55
+ const encryptedKeys = [];
56
+ const placeholderKeys = [];
57
+ const spinner = ora("Seeding encrypted .env.production").start();
58
+ try {
59
+ for (const key of allKeys) {
60
+ const raw = merged[key];
61
+ const hasValue = raw !== undefined && raw.trim() !== "";
62
+ if (hasValue) {
63
+ dotenvxSet(key, raw, { path: envPath, encrypt: true });
64
+ encryptedKeys.push(key);
65
+ }
66
+ else {
67
+ // CHANGE_ME_<KEY> is intentionally noisy — anything that tries
68
+ // to use it at runtime will either fail validation (zod /
69
+ // `getRequired`) or point clearly at its own source.
70
+ dotenvxSet(key, `CHANGE_ME_${key}`, { path: envPath, encrypt: false });
71
+ placeholderKeys.push(key);
72
+ }
73
+ }
74
+ spinner.succeed(`Seeded .env.production (${encryptedKeys.length} encrypted, ${placeholderKeys.length} placeholders)`);
75
+ }
76
+ catch (err) {
77
+ spinner.fail("Failed to seed .env.production");
78
+ throw err;
79
+ }
80
+ // Pull the freshly-generated private key out of .env.keys.
81
+ const { privateKey, publicKey } = readKeypair(envKeysPath, envPath);
82
+ // Mirror the private key into the OS keychain. Keep .env.keys on
83
+ // disk too (gitignored) so local `pnpm start` works with no extra
84
+ // steps — the keychain copy is for re-deploy + recovery.
85
+ await setSecret(SECRET_KEYS.dotenvxPrivateKey(config.name), privateKey);
86
+ return { privateKey, publicKey, encryptedKeys, placeholderKeys };
87
+ }
88
+ /** Keys the scaffolder seeds into .env.production. Extend here when
89
+ * the starter gains new required env entries. */
90
+ function candidateKeys(config) {
91
+ const base = ["MONGODB_URI", "BETTER_AUTH_SECRET", "BETTER_AUTH_URL", "FRONTEND_URL"];
92
+ if (config.features.includes("stripe")) {
93
+ base.push("STRIPE_SECRET_KEY", "STRIPE_WEBHOOK_SECRET");
94
+ }
95
+ if (config.features.includes("analytics")) {
96
+ base.push("SENTRY_DSN");
97
+ }
98
+ if (config.features.includes("s3")) {
99
+ base.push("S3_ENDPOINT", "S3_BUCKET_NAME", "S3_PUBLIC_URL", "AWS_REGION", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY");
100
+ }
101
+ if (config.features.includes("websocket")) {
102
+ base.push("REDIS_URL");
103
+ }
104
+ return base;
105
+ }
106
+ /** Parse the generated .env.keys + the header of .env.production to
107
+ * recover the hex-encoded production keypair. */
108
+ function readKeypair(envKeysPath, envPath) {
109
+ if (!existsSync(envKeysPath)) {
110
+ throw new Error(`dotenvx was expected to create ${envKeysPath}, but it's missing. Nothing was encrypted.`);
111
+ }
112
+ const keysContent = readFileSync(envKeysPath, "utf-8");
113
+ const privateMatch = keysContent.match(/^DOTENV_PRIVATE_KEY_PRODUCTION="?([0-9a-fA-F]+)"?/m);
114
+ if (!privateMatch) {
115
+ throw new Error(`Could not find DOTENV_PRIVATE_KEY_PRODUCTION in ${envKeysPath}.`);
116
+ }
117
+ const envContent = existsSync(envPath) ? readFileSync(envPath, "utf-8") : "";
118
+ const publicMatch = envContent.match(/^DOTENV_PUBLIC_KEY_PRODUCTION="?([0-9a-fA-F]+)"?/m);
119
+ return {
120
+ privateKey: privateMatch[1],
121
+ publicKey: publicMatch ? publicMatch[1] : "",
122
+ };
123
+ }
124
+ /** Prompt the user for the subset of candidate keys that are
125
+ * sensitive + likely already known at scaffold time. Anything they
126
+ * leave blank falls through to CHANGE_ME. Skipped entirely in
127
+ * non-interactive mode — presets should carry the values instead. */
128
+ export function printDotenvxSummary(result, projectName) {
129
+ if (result.encryptedKeys.length > 0) {
130
+ console.log(chalk.green(` ✓ Encrypted ${result.encryptedKeys.length} values into .env.production`));
131
+ }
132
+ if (result.placeholderKeys.length > 0) {
133
+ console.log(chalk.dim(` Placeholders (edit later with \`dotenvx set KEY value -f .env.production\`):`));
134
+ for (const k of result.placeholderKeys) {
135
+ console.log(chalk.dim(` ${k}`));
136
+ }
137
+ }
138
+ console.log(chalk.yellow(`\n dotenvx private key stored in macOS Keychain (hatchkit:${projectName}).`));
139
+ console.log(chalk.dim(` Retrieve anytime: hatchkit keys show ${projectName}`));
140
+ console.log(chalk.dim(` Push to Coolify: hatchkit keys push ${projectName}`));
141
+ }
142
+ //# sourceMappingURL=dotenvx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dotenvx.js","sourceRoot":"","sources":["../../src/scaffold/dotenvx.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,GAAG,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AAEtB,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAqB7D;;4BAE4B;AAC5B,SAAS,qBAAqB;IAC5B,OAAO;QACL,kBAAkB,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;KACpD,CAAC;AACJ,CAAC;AAED;;;;8CAI8C;AAC9C,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,SAAiB,EACjB,MAAqB,EACrB,UAAyB;IAEzB,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,EAAE,iCAAiC,CAAC,CAAC;IACnE,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,2BAA2B,CAAC,CAAC;IAEjE,sCAAsC;IACtC,MAAM,QAAQ,GAAG,qBAAqB,EAAE,CAAC;IACzC,MAAM,MAAM,GAAuC,EAAE,GAAG,QAAQ,EAAE,GAAG,UAAU,EAAE,CAAC;IAElF,kEAAkE;IAClE,8DAA8D;IAC9D,iEAAiE;IACjE,sBAAsB;IACtB,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,aAAa,GAAa,EAAE,CAAC;IACnC,MAAM,eAAe,GAAa,EAAE,CAAC;IAErC,MAAM,OAAO,GAAG,GAAG,CAAC,mCAAmC,CAAC,CAAC,KAAK,EAAE,CAAC;IACjE,IAAI,CAAC;QACH,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;YACxB,MAAM,QAAQ,GAAG,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;YACxD,IAAI,QAAQ,EAAE,CAAC;gBACb,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;gBACvD,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC1B,CAAC;iBAAM,CAAC;gBACN,+DAA+D;gBAC/D,0DAA0D;gBAC1D,qDAAqD;gBACrD,UAAU,CAAC,GAAG,EAAE,aAAa,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;gBACvE,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;QACD,OAAO,CAAC,OAAO,CACb,2BAA2B,aAAa,CAAC,MAAM,eAAe,eAAe,CAAC,MAAM,gBAAgB,CACrG,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;QAC/C,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,2DAA2D;IAC3D,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,WAAW,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAEpE,iEAAiE;IACjE,kEAAkE;IAClE,yDAAyD;IACzD,MAAM,SAAS,CAAC,WAAW,CAAC,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC,CAAC;IAExE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,eAAe,EAAE,CAAC;AACnE,CAAC;AAED;kDACkD;AAClD,SAAS,aAAa,CAAC,MAAqB;IAC1C,MAAM,IAAI,GAAG,CAAC,aAAa,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,cAAc,CAAC,CAAC;IACtF,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,uBAAuB,CAAC,CAAC;IAC1D,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QAC1C,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC1B,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,IAAI,CAAC,IAAI,CACP,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,uBAAuB,CACxB,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QAC1C,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAOD;kDACkD;AAClD,SAAS,WAAW,CAAC,WAAmB,EAAE,OAAe;IACvD,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CACb,kCAAkC,WAAW,4CAA4C,CAC1F,CAAC;IACJ,CAAC;IACD,MAAM,WAAW,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IACvD,MAAM,YAAY,GAAG,WAAW,CAAC,KAAK,CAAC,oDAAoD,CAAC,CAAC;IAC7F,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,mDAAmD,WAAW,GAAG,CAAC,CAAC;IACrF,CAAC;IAED,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7E,MAAM,WAAW,GAAG,UAAU,CAAC,KAAK,CAAC,mDAAmD,CAAC,CAAC;IAE1F,OAAO;QACL,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC;QAC3B,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;KAC7C,CAAC;AACJ,CAAC;AAED;;;sEAGsE;AACtE,MAAM,UAAU,mBAAmB,CAAC,MAAyB,EAAE,WAAmB;IAChF,IAAI,MAAM,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpC,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,KAAK,CAAC,iBAAiB,MAAM,CAAC,aAAa,CAAC,MAAM,8BAA8B,CAAC,CACxF,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtC,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,GAAG,CAAC,kFAAkF,CAAC,CAC9F,CAAC;QACF,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,eAAe,EAAE,CAAC;YACvC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,MAAM,CAAC,8DAA8D,WAAW,IAAI,CAAC,CAC5F,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,4CAA4C,WAAW,EAAE,CAAC,CAAC,CAAC;IAClF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,4CAA4C,WAAW,EAAE,CAAC,CAAC,CAAC;AACpF,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { ProjectConfig } from "../prompts.js";
2
+ /** Generate Terraform tfvars for the project. */
3
+ export declare function generateTfvars(config: ProjectConfig): string;
4
+ /** Generate Coolify stack .env for the project. */
5
+ export declare function generateCoolifyEnv(config: ProjectConfig, extras?: {
6
+ repoUrl?: string;
7
+ serverPort?: number;
8
+ clientPort?: number;
9
+ }): string;
10
+ export interface ScaffoldInfraOptions {
11
+ repoUrl?: string;
12
+ serverPort?: number;
13
+ clientPort?: number;
14
+ }
15
+ /** Write infra config files. */
16
+ export declare function scaffoldInfra(config: ProjectConfig, repoRoot: string, options?: ScaffoldInfraOptions): void;
17
+ //# sourceMappingURL=infra.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"infra.d.ts","sourceRoot":"","sources":["../../src/scaffold/infra.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAGnD,iDAAiD;AACjD,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAkC5D;AAED,mDAAmD;AACnD,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,aAAa,EACrB,MAAM,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GAC1E,MAAM,CAqBR;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,gCAAgC;AAChC,wBAAgB,aAAa,CAC3B,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,oBAAyB,GACjC,IAAI,CA0CN"}
@@ -0,0 +1,200 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import chalk from "chalk";
4
+ import { getConfig } from "../config.js";
5
+ import { renderString } from "../utils/template.js";
6
+ /** Generate Terraform tfvars for the project. */
7
+ export function generateTfvars(config) {
8
+ // Avoid silent duplicate-key drop when the user's chosen subdomain
9
+ // collides with a reserved name ("admin", "api.<sub>"). Build the map
10
+ // iteratively and skip duplicates.
11
+ const subdomains = {};
12
+ const addSubdomain = (key, description) => {
13
+ if (!key)
14
+ return;
15
+ if (subdomains[key])
16
+ return;
17
+ subdomains[key] = description;
18
+ };
19
+ addSubdomain(config.subdomain, "Web app + API paths");
20
+ addSubdomain(`api.${config.subdomain}`, "REST API");
21
+ addSubdomain("admin", "Coolify dashboard");
22
+ if (config.deployTarget === "new") {
23
+ return renderString(TFVARS_TEMPLATE, {
24
+ name: config.name,
25
+ serverType: config.serverSize || "cpx21",
26
+ serverLocation: config.serverLocation || "nbg1",
27
+ domain: config.baseDomain,
28
+ subdomains,
29
+ s3Enabled: config.features.includes("s3") && config.s3Provider !== "existing",
30
+ s3BucketName: `${config.name}-assets`,
31
+ s3Location: config.serverLocation || "nbg1",
32
+ });
33
+ }
34
+ // For existing server: DNS-only tfvars
35
+ return renderString(DNS_ONLY_TFVARS_TEMPLATE, {
36
+ domain: config.baseDomain,
37
+ subdomains,
38
+ targetIpv4: config.serverIp || "",
39
+ targetIpv6: "",
40
+ });
41
+ }
42
+ /** Generate Coolify stack .env for the project. */
43
+ export function generateCoolifyEnv(config, extras = {}) {
44
+ const coolifyConfig = getConfig().providers.coolify;
45
+ const s3Config = getS3Config(config);
46
+ return renderString(COOLIFY_ENV_TEMPLATE, {
47
+ coolifyUrl: coolifyConfig?.url || "https://admin.example.com",
48
+ name: config.name,
49
+ domain: config.domain,
50
+ repoUrl: extras.repoUrl ?? "",
51
+ serverPort: extras.serverPort ?? 3000,
52
+ clientPort: extras.clientPort ?? 3000,
53
+ mongoEnabled: true,
54
+ redisEnabled: config.features.includes("websocket"),
55
+ s3Provider: config.s3Provider === "existing" ? "custom" : config.s3Provider,
56
+ s3Bucket: s3Config?.bucket || "",
57
+ s3Endpoint: s3Config?.endpoint || "",
58
+ s3Region: s3Config?.region || "",
59
+ stripe: config.features.includes("stripe"),
60
+ analytics: config.features.includes("analytics"),
61
+ mlServices: config.mlServices,
62
+ });
63
+ }
64
+ /** Write infra config files. */
65
+ export function scaffoldInfra(config, repoRoot, options = {}) {
66
+ // Fail early with a clear message if the infra submodule isn't
67
+ // populated — otherwise Terraform writes are silently skipped and the
68
+ // later terraform/coolify exec steps crash cryptically.
69
+ if (!config.dryRun && !existsSync(join(repoRoot, "terraform"))) {
70
+ throw new Error(`Infra submodule is empty at ${repoRoot}. Run 'git submodule update --init' in the monorepo root before deploying.`);
71
+ }
72
+ const stacksDir = join(repoRoot, "stacks");
73
+ if (!existsSync(stacksDir))
74
+ mkdirSync(stacksDir, { recursive: true });
75
+ const tfvars = generateTfvars(config);
76
+ const coolifyEnv = generateCoolifyEnv(config, {
77
+ repoUrl: options.repoUrl,
78
+ serverPort: options.serverPort,
79
+ clientPort: options.clientPort,
80
+ });
81
+ if (config.dryRun) {
82
+ console.log(chalk.bold("\n [dry-run] Infra config:\n"));
83
+ console.log(chalk.dim(" --- terraform.tfvars ---"));
84
+ console.log(chalk.dim(tfvars));
85
+ console.log(chalk.dim(" --- coolify .env ---"));
86
+ console.log(chalk.dim(coolifyEnv));
87
+ return;
88
+ }
89
+ // Write Terraform tfvars
90
+ const tfDir = config.deployTarget === "new"
91
+ ? join(repoRoot, "terraform", "stacks", "node-realtime")
92
+ : join(repoRoot, "terraform", "stacks", "dns-only");
93
+ if (existsSync(tfDir)) {
94
+ writeFileSync(join(tfDir, `${config.name}.tfvars`), tfvars, "utf-8");
95
+ console.log(chalk.green(` ✓ Terraform config: ${tfDir}/${config.name}.tfvars`));
96
+ }
97
+ // Write Coolify .env
98
+ writeFileSync(join(stacksDir, `${config.name}.env`), coolifyEnv, "utf-8");
99
+ console.log(chalk.green(` ✓ Coolify config: stacks/${config.name}.env`));
100
+ }
101
+ // ---------------------------------------------------------------------------
102
+ // Helpers
103
+ // ---------------------------------------------------------------------------
104
+ function getS3Config(config) {
105
+ if (!config.features.includes("s3"))
106
+ return null;
107
+ if (config.s3Provider === "existing") {
108
+ return {
109
+ bucket: config.s3ExistingBucket || "",
110
+ endpoint: config.s3ExistingEndpoint || "",
111
+ region: config.s3ExistingRegion || "",
112
+ };
113
+ }
114
+ const providerConfig = getConfig().providers.s3[config.s3Provider];
115
+ return {
116
+ bucket: `${config.name}-assets`,
117
+ endpoint: providerConfig?.endpoint || "",
118
+ region: providerConfig?.region || "",
119
+ };
120
+ }
121
+ // ---------------------------------------------------------------------------
122
+ // Templates (inline — small enough to not warrant separate files)
123
+ // ---------------------------------------------------------------------------
124
+ const TFVARS_TEMPLATE = `server_name = "{{name}}-prod"
125
+ server_type = "{{serverType}}"
126
+ server_location = "{{serverLocation}}"
127
+ ssh_key_name = "deploy-key"
128
+ ssh_public_key = "ssh-ed25519 CHANGE_ME"
129
+
130
+ domain = "{{domain}}"
131
+ subdomains = {
132
+ {{#each subdomains}}
133
+ "{{@key}}" = "{{this}}"
134
+ {{/each}}
135
+ }
136
+ dns_ttl = 300
137
+
138
+ firewall_enabled = true
139
+
140
+ {{#if s3Enabled}}
141
+ s3_enabled = true
142
+ s3_bucket_name = "{{s3BucketName}}"
143
+ s3_bucket_acl = "private"
144
+ s3_location = "{{s3Location}}"
145
+ {{else}}
146
+ s3_enabled = false
147
+ {{/if}}
148
+ `;
149
+ const DNS_ONLY_TFVARS_TEMPLATE = `domain = "{{domain}}"
150
+ subdomains = {
151
+ {{#each subdomains}}
152
+ "{{@key}}" = "{{this}}"
153
+ {{/each}}
154
+ }
155
+ target_ipv4 = "{{targetIpv4}}"
156
+ target_ipv6 = "{{targetIpv6}}"
157
+ dns_ttl = 300
158
+ `;
159
+ const COOLIFY_ENV_TEMPLATE = `COOLIFY_URL="{{coolifyUrl}}"
160
+
161
+ PROJECT_NAME="{{name}}"
162
+ ENVIRONMENT_NAME="production"
163
+
164
+ APP_NAME="{{name}}-web"
165
+ GITHUB_REPO_URL="{{repoUrl}}"
166
+ APP_PORT="{{clientPort}}"
167
+ SERVER_PORT="{{serverPort}}"
168
+
169
+ APP_DOMAIN="{{domain}}"
170
+
171
+ MONGO_ENABLED="yes"
172
+ REDIS_ENABLED="{{#if redisEnabled}}yes{{else}}no{{/if}}"
173
+
174
+ S3_PROVIDER="{{s3Provider}}"
175
+ S3_BUCKET="{{s3Bucket}}"
176
+ S3_ENDPOINT="{{s3Endpoint}}"
177
+ S3_ACCESS_KEY=""
178
+ S3_SECRET_KEY=""
179
+ S3_REGION="{{s3Region}}"
180
+
181
+ {{#if stripe}}
182
+ STRIPE_SECRET_KEY=""
183
+ STRIPE_PUBLISHABLE_KEY=""
184
+ STRIPE_WEBHOOK_SECRET=""
185
+ {{/if}}
186
+
187
+ {{#if analytics}}
188
+ GLITCHTIP_DSN=""
189
+ OPENPANEL_API_URL=""
190
+ OPENPANEL_CLIENT_ID=""
191
+ OPENPANEL_CLIENT_SECRET=""
192
+ {{/if}}
193
+
194
+ {{#each mlServices}}
195
+ ML_{{this}}_ENDPOINT=""
196
+ {{/each}}
197
+
198
+ TOKEN_SECRET=""
199
+ `;
200
+ //# sourceMappingURL=infra.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"infra.js","sourceRoot":"","sources":["../../src/scaffold/infra.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAuB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,iDAAiD;AACjD,MAAM,UAAU,cAAc,CAAC,MAAqB;IAClD,mEAAmE;IACnE,sEAAsE;IACtE,mCAAmC;IACnC,MAAM,UAAU,GAA2B,EAAE,CAAC;IAC9C,MAAM,YAAY,GAAG,CAAC,GAAW,EAAE,WAAmB,EAAE,EAAE;QACxD,IAAI,CAAC,GAAG;YAAE,OAAO;QACjB,IAAI,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO;QAC5B,UAAU,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC;IAChC,CAAC,CAAC;IACF,YAAY,CAAC,MAAM,CAAC,SAAS,EAAE,qBAAqB,CAAC,CAAC;IACtD,YAAY,CAAC,OAAO,MAAM,CAAC,SAAS,EAAE,EAAE,UAAU,CAAC,CAAC;IACpD,YAAY,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC;IAE3C,IAAI,MAAM,CAAC,YAAY,KAAK,KAAK,EAAE,CAAC;QAClC,OAAO,YAAY,CAAC,eAAe,EAAE;YACnC,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,OAAO;YACxC,cAAc,EAAE,MAAM,CAAC,cAAc,IAAI,MAAM;YAC/C,MAAM,EAAE,MAAM,CAAC,UAAU;YACzB,UAAU;YACV,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,UAAU,KAAK,UAAU;YAC7E,YAAY,EAAE,GAAG,MAAM,CAAC,IAAI,SAAS;YACrC,UAAU,EAAE,MAAM,CAAC,cAAc,IAAI,MAAM;SAC5C,CAAC,CAAC;IACL,CAAC;IAED,uCAAuC;IACvC,OAAO,YAAY,CAAC,wBAAwB,EAAE;QAC5C,MAAM,EAAE,MAAM,CAAC,UAAU;QACzB,UAAU;QACV,UAAU,EAAE,MAAM,CAAC,QAAQ,IAAI,EAAE;QACjC,UAAU,EAAE,EAAE;KACf,CAAC,CAAC;AACL,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,kBAAkB,CAChC,MAAqB,EACrB,SAAyE,EAAE;IAE3E,MAAM,aAAa,GAAG,SAAS,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC;IACpD,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IAErC,OAAO,YAAY,CAAC,oBAAoB,EAAE;QACxC,UAAU,EAAE,aAAa,EAAE,GAAG,IAAI,2BAA2B;QAC7D,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,EAAE;QAC7B,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,IAAI;QACrC,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,IAAI;QACrC,YAAY,EAAE,IAAI;QAClB,YAAY,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC;QACnD,UAAU,EAAE,MAAM,CAAC,UAAU,KAAK,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU;QAC3E,QAAQ,EAAE,QAAQ,EAAE,MAAM,IAAI,EAAE;QAChC,UAAU,EAAE,QAAQ,EAAE,QAAQ,IAAI,EAAE;QACpC,QAAQ,EAAE,QAAQ,EAAE,MAAM,IAAI,EAAE;QAChC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC1C,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC;QAChD,UAAU,EAAE,MAAM,CAAC,UAAU;KAC9B,CAAC,CAAC;AACL,CAAC;AAQD,gCAAgC;AAChC,MAAM,UAAU,aAAa,CAC3B,MAAqB,EACrB,QAAgB,EAChB,UAAgC,EAAE;IAElC,+DAA+D;IAC/D,sEAAsE;IACtE,wDAAwD;IACxD,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC;QAC/D,MAAM,IAAI,KAAK,CACb,+BAA+B,QAAQ,4EAA4E,CACpH,CAAC;IACJ,CAAC;IACD,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC3C,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEtE,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,UAAU,GAAG,kBAAkB,CAAC,MAAM,EAAE;QAC5C,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,UAAU,EAAE,OAAO,CAAC,UAAU;KAC/B,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC,CAAC;QACzD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC,CAAC;QACrD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,CAAC;QACjD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;QACnC,OAAO;IACT,CAAC;IAED,yBAAyB;IACzB,MAAM,KAAK,GACT,MAAM,CAAC,YAAY,KAAK,KAAK;QAC3B,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,eAAe,CAAC;QACxD,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;IAExD,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QACtB,aAAa,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,MAAM,CAAC,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;QACrE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,yBAAyB,KAAK,IAAI,MAAM,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC;IACnF,CAAC;IAED,qBAAqB;IACrB,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,MAAM,CAAC,IAAI,MAAM,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;IAC1E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,8BAA8B,MAAM,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,SAAS,WAAW,CAClB,MAAqB;IAErB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAEjD,IAAI,MAAM,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;QACrC,OAAO;YACL,MAAM,EAAE,MAAM,CAAC,gBAAgB,IAAI,EAAE;YACrC,QAAQ,EAAE,MAAM,CAAC,kBAAkB,IAAI,EAAE;YACzC,MAAM,EAAE,MAAM,CAAC,gBAAgB,IAAI,EAAE;SACtC,CAAC;IACJ,CAAC;IAED,MAAM,cAAc,GAAG,SAAS,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,MAAM,CAAC,UAAU,CAA+B,CAAC;IACjG,OAAO;QACL,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,SAAS;QAC/B,QAAQ,EAAE,cAAc,EAAE,QAAQ,IAAI,EAAE;QACxC,MAAM,EAAE,cAAc,EAAE,MAAM,IAAI,EAAE;KACrC,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,kEAAkE;AAClE,8EAA8E;AAE9E,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;CAwBvB,CAAC;AAEF,MAAM,wBAAwB,GAAG;;;;;;;;;CAShC,CAAC;AAEF,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwC5B,CAAC"}
@@ -0,0 +1,50 @@
1
+ import type { Feature, GpuPlatform, MlService, ProjectConfig, S3Provider } from "../prompts.js";
2
+ import type { ProjectPorts } from "../utils/ports.js";
3
+ export declare const MANIFEST_FILENAME = ".hatchkit.json";
4
+ export declare const MANIFEST_VERSION = 1;
5
+ export interface ProjectManifest {
6
+ /** Schema version. Increment when the shape changes incompatibly. */
7
+ version: typeof MANIFEST_VERSION;
8
+ /** CLI version that produced this manifest (diagnostic only). */
9
+ cliVersion: string;
10
+ /** ISO timestamp of the scaffold. */
11
+ scaffoldedAt: string;
12
+ /** Project name — duplicated from package.json for convenience. */
13
+ name: string;
14
+ /** Production domain — already public in DNS + .env.example. */
15
+ domain: string;
16
+ /** Feature flags selected at scaffold. */
17
+ features: Feature[];
18
+ /** ML services wired into the backend. */
19
+ mlServices: MlService[];
20
+ /** S3 provider name (`hetzner` / `aws` / `r2` / `existing` / `none`).
21
+ * Credentials are NOT stored here — only the choice. */
22
+ s3Provider: S3Provider;
23
+ /** Where the app deploys. `existing` vs `new` is public-safe; the
24
+ * actual serverId/IP is not in the manifest. */
25
+ deployTarget: "existing" | "new";
26
+ /** GPU platform choice, if any ML services need deployment. */
27
+ gpuPlatform?: GpuPlatform;
28
+ /** HF model ID for custom-hf, if selected. HF models are public. */
29
+ customHfModelId?: string;
30
+ /** GPU tier (T4/A10G/A100/H100) — public product names. */
31
+ customHfGpuType?: string;
32
+ /** Ports assigned to this project. They're already public in the
33
+ * scaffolded .env.development files and docker-compose.yml. */
34
+ ports: {
35
+ server: number;
36
+ client: number;
37
+ nativeHmr?: number;
38
+ };
39
+ }
40
+ /** Build a manifest from the internal ProjectConfig, explicitly
41
+ * whitelisting only the safe fields. Any new field on ProjectConfig
42
+ * will NOT leak into the manifest unless added here on purpose. */
43
+ export declare function toManifest(config: ProjectConfig, ports: ProjectPorts, cliVersion: string): ProjectManifest;
44
+ export declare function writeManifest(outputDir: string, manifest: ProjectManifest): void;
45
+ /** Read + validate a manifest from a scaffolded project directory.
46
+ * Returns null if the file doesn't exist. Throws on malformed JSON
47
+ * or an unknown schema version so downstream code doesn't silently
48
+ * operate on a wrong shape. */
49
+ export declare function readManifest(projectDir: string): ProjectManifest | null;
50
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/scaffold/manifest.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,eAAO,MAAM,iBAAiB,mBAAmB,CAAC;AAClD,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,OAAO,EAAE,OAAO,gBAAgB,CAAC;IACjC,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,0CAA0C;IAC1C,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB;6DACyD;IACzD,UAAU,EAAE,UAAU,CAAC;IACvB;qDACiD;IACjD,YAAY,EAAE,UAAU,GAAG,KAAK,CAAC;IACjC,+DAA+D;IAC/D,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,oEAAoE;IACpE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2DAA2D;IAC3D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;oEACgE;IAChE,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/D;AAED;;oEAEoE;AACpE,wBAAgB,UAAU,CACxB,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,YAAY,EACnB,UAAU,EAAE,MAAM,GACjB,eAAe,CAoBjB;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAGhF;AAED;;;gCAGgC;AAChC,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAiBvE"}
@@ -0,0 +1,83 @@
1
+ /*
2
+ * Project manifest — .hatchkit.json at the root of a scaffolded project.
3
+ *
4
+ * Purpose: capture just enough about how this project was scaffolded
5
+ * that `hatchkit update` can diff the current feature set against a
6
+ * desired new one and apply only the delta.
7
+ *
8
+ * ============================================================
9
+ * SECURITY: WHAT GOES IN — AND WHAT ABSOLUTELY DOES NOT
10
+ * ============================================================
11
+ *
12
+ * The manifest file gets committed to the project's git repo. Treat
13
+ * every field as eventually public. The included fields below are all
14
+ * already public in one way or another (package.json `name`, the
15
+ * domain is in DNS + .env.example, feature flags are inferable from
16
+ * dependency lists, ports are in .env.* and docker-compose.yml).
17
+ *
18
+ * Fields that MUST NEVER be written here:
19
+ * - tokens, passwords, API keys (any credential)
20
+ * - serverIp, serverId (Coolify server coordinates)
21
+ * - s3ExistingAccessKey / SecretKey / Endpoint / Bucket (user creds)
22
+ * - serverSize / serverLocation (infrastructure cost signal)
23
+ *
24
+ * ProjectConfig has those fields; the `toManifest` function below is
25
+ * the single choke point that picks out the safe subset. Any time a
26
+ * new field is added to ProjectConfig, it must be triaged here — the
27
+ * default is to NOT include it.
28
+ */
29
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
30
+ import { join } from "node:path";
31
+ export const MANIFEST_FILENAME = ".hatchkit.json";
32
+ export const MANIFEST_VERSION = 1;
33
+ /** Build a manifest from the internal ProjectConfig, explicitly
34
+ * whitelisting only the safe fields. Any new field on ProjectConfig
35
+ * will NOT leak into the manifest unless added here on purpose. */
36
+ export function toManifest(config, ports, cliVersion) {
37
+ return {
38
+ version: MANIFEST_VERSION,
39
+ cliVersion,
40
+ scaffoldedAt: new Date().toISOString(),
41
+ name: config.name,
42
+ domain: config.domain,
43
+ features: [...config.features],
44
+ mlServices: [...config.mlServices],
45
+ s3Provider: config.s3Provider,
46
+ deployTarget: config.deployTarget,
47
+ gpuPlatform: config.gpuPlatform,
48
+ customHfModelId: config.customHfModelId,
49
+ customHfGpuType: config.customHfGpuType,
50
+ ports: {
51
+ server: ports.server,
52
+ client: ports.client,
53
+ nativeHmr: ports.nativeHmr,
54
+ },
55
+ };
56
+ }
57
+ export function writeManifest(outputDir, manifest) {
58
+ const path = join(outputDir, MANIFEST_FILENAME);
59
+ writeFileSync(path, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
60
+ }
61
+ /** Read + validate a manifest from a scaffolded project directory.
62
+ * Returns null if the file doesn't exist. Throws on malformed JSON
63
+ * or an unknown schema version so downstream code doesn't silently
64
+ * operate on a wrong shape. */
65
+ export function readManifest(projectDir) {
66
+ const path = join(projectDir, MANIFEST_FILENAME);
67
+ if (!existsSync(path))
68
+ return null;
69
+ let parsed;
70
+ try {
71
+ parsed = JSON.parse(readFileSync(path, "utf-8"));
72
+ }
73
+ catch (err) {
74
+ throw new Error(`Manifest at ${path} is not valid JSON: ${err.message}`);
75
+ }
76
+ if (!parsed ||
77
+ typeof parsed !== "object" ||
78
+ parsed.version !== MANIFEST_VERSION) {
79
+ throw new Error(`Manifest at ${path} has unknown version. Expected ${MANIFEST_VERSION}.`);
80
+ }
81
+ return parsed;
82
+ }
83
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/scaffold/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAIjC,MAAM,CAAC,MAAM,iBAAiB,GAAG,gBAAgB,CAAC;AAClD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAkClC;;oEAEoE;AACpE,MAAM,UAAU,UAAU,CACxB,MAAqB,EACrB,KAAmB,EACnB,UAAkB;IAElB,OAAO;QACL,OAAO,EAAE,gBAAgB;QACzB,UAAU;QACV,YAAY,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACtC,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,QAAQ,EAAE,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC;QAC9B,UAAU,EAAE,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC;QAClC,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,KAAK,EAAE;YACL,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,SAAS,EAAE,KAAK,CAAC,SAAS;SAC3B;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,SAAiB,EAAE,QAAyB;IACxE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;IAChD,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AACzE,CAAC;AAED;;;gCAGgC;AAChC,MAAM,UAAU,YAAY,CAAC,UAAkB;IAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC;IACjD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,eAAe,IAAI,uBAAwB,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IACtF,CAAC;IACD,IACE,CAAC,MAAM;QACP,OAAO,MAAM,KAAK,QAAQ;QACzB,MAAgC,CAAC,OAAO,KAAK,gBAAgB,EAC9D,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,eAAe,IAAI,kCAAkC,gBAAgB,GAAG,CAAC,CAAC;IAC5F,CAAC;IACD,OAAO,MAAyB,CAAC;AACnC,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { type MlServiceEntry } from "../config.js";
2
+ import type { MlService, ProjectConfig } from "../prompts.js";
3
+ /** Resolve ML services — reuse existing or mark for deployment.
4
+ * Services in config.forceRedeployMl bypass the registry and always
5
+ * go to the deploy list, which recovers from stale registry entries. */
6
+ export declare function resolveMlServices(config: ProjectConfig): Promise<{
7
+ reuse: Array<{
8
+ service: MlService;
9
+ entry: MlServiceEntry;
10
+ }>;
11
+ deploy: MlService[];
12
+ }>;
13
+ /** Get the env var name for an ML service endpoint. */
14
+ export declare function mlEnvVarName(service: MlService): string;
15
+ /** Print ML service resolution summary. */
16
+ export declare function printMlSummary(reuse: Array<{
17
+ service: MlService;
18
+ entry: MlServiceEntry;
19
+ }>, deploy: MlService[]): void;
20
+ //# sourceMappingURL=ml-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ml-client.d.ts","sourceRoot":"","sources":["../../src/scaffold/ml-client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,cAAc,EAAiB,MAAM,cAAc,CAAC;AAClE,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9D;;yEAEyE;AACzE,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC;IACtE,KAAK,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,cAAc,CAAA;KAAE,CAAC,CAAC;IAC5D,MAAM,EAAE,SAAS,EAAE,CAAC;CACrB,CAAC,CAeD;AAED,uDAAuD;AACvD,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,CAGvD;AAED,2CAA2C;AAC3C,wBAAgB,cAAc,CAC5B,KAAK,EAAE,KAAK,CAAC;IAAE,OAAO,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,cAAc,CAAA;CAAE,CAAC,EAC3D,MAAM,EAAE,SAAS,EAAE,GAClB,IAAI,CAUN"}
@@ -0,0 +1,38 @@
1
+ import chalk from "chalk";
2
+ import { getMlServices } from "../config.js";
3
+ /** Resolve ML services — reuse existing or mark for deployment.
4
+ * Services in config.forceRedeployMl bypass the registry and always
5
+ * go to the deploy list, which recovers from stale registry entries. */
6
+ export async function resolveMlServices(config) {
7
+ const registry = getMlServices();
8
+ const forceSet = new Set(config.forceRedeployMl ?? []);
9
+ const reuse = [];
10
+ const deploy = [];
11
+ for (const service of config.mlServices) {
12
+ if (registry[service] && !forceSet.has(service)) {
13
+ reuse.push({ service, entry: registry[service] });
14
+ }
15
+ else {
16
+ deploy.push(service);
17
+ }
18
+ }
19
+ return { reuse, deploy };
20
+ }
21
+ /** Get the env var name for an ML service endpoint. */
22
+ export function mlEnvVarName(service) {
23
+ const name = service.replace(/-/g, "_").toUpperCase();
24
+ return `ML_${name}_ENDPOINT`;
25
+ }
26
+ /** Print ML service resolution summary. */
27
+ export function printMlSummary(reuse, deploy) {
28
+ if (reuse.length > 0) {
29
+ console.log(chalk.dim("\n Reusing existing ML services:"));
30
+ for (const { service, entry } of reuse) {
31
+ console.log(chalk.dim(` ${service} → ${entry.endpoint}`));
32
+ }
33
+ }
34
+ if (deploy.length > 0) {
35
+ console.log(chalk.yellow(`\n New ML services to deploy: ${deploy.join(", ")}`));
36
+ }
37
+ }
38
+ //# sourceMappingURL=ml-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ml-client.js","sourceRoot":"","sources":["../../src/scaffold/ml-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAuB,aAAa,EAAE,MAAM,cAAc,CAAC;AAGlE;;yEAEyE;AACzE,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,MAAqB;IAI3D,MAAM,QAAQ,GAAG,aAAa,EAAE,CAAC;IACjC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC;IACvD,MAAM,KAAK,GAAyD,EAAE,CAAC;IACvE,MAAM,MAAM,GAAgB,EAAE,CAAC;IAE/B,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACxC,IAAI,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAChD,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACpD,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAC3B,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,YAAY,CAAC,OAAkB;IAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IACtD,OAAO,MAAM,IAAI,WAAW,CAAC;AAC/B,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,cAAc,CAC5B,KAA2D,EAC3D,MAAmB;IAEnB,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC,CAAC;QAC5D,KAAK,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,KAAK,EAAE,CAAC;YACvC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,OAAO,MAAM,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,kCAAkC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IACnF,CAAC;AACH,CAAC"}
@@ -0,0 +1,20 @@
1
+ export declare function stripPackageJsonScripts(outputDir: string, names: string[]): void;
2
+ export declare function stripPackageJsonDeps(outputDir: string, names: string[]): void;
3
+ export declare function stripPackageJsonBuildBlock(outputDir: string): void;
4
+ /** Drop the `pnpm typecheck:electron` segment from the root `typecheck`
5
+ * script regardless of where it sits in the `&&` chain. Handles:
6
+ * "A && pnpm typecheck:electron" → "A"
7
+ * "pnpm typecheck:electron && A" → "A"
8
+ * "pnpm typecheck:electron" → <script deleted entirely>
9
+ */
10
+ export declare function unchainTypecheckScript(outputDir: string): void;
11
+ /** Set a specific script entry in the root `package.json`. Creates
12
+ * `scripts` if missing. */
13
+ export declare function setPackageJsonScript(outputDir: string, name: string, value: string): void;
14
+ /** Read the `name` field from a workspace package's package.json. */
15
+ export declare function readPackageName(pkgDir: string): string | undefined;
16
+ /** Return every workspace package's `name` field from its package.json.
17
+ * Used to populate Next's `transpilePackages` dynamically instead of
18
+ * hardcoding `@starter/*` names. */
19
+ export declare function readWorkspacePackageNames(outputDir: string): string[];
20
+ //# sourceMappingURL=pkg-json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkg-json.d.ts","sourceRoot":"","sources":["../../src/scaffold/pkg-json.ts"],"names":[],"mappings":"AAaA,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAOhF;AAED,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAU7E;AAED,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAMlE;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAkB9D;AAED;4BAC4B;AAC5B,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAOzF;AAED,qEAAqE;AACrE,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASlE;AAED;;qCAEqC;AACrC,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CASrE"}