hatchkit 0.1.4 → 0.1.6

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 (71) hide show
  1. package/dist/adopt.d.ts +2 -0
  2. package/dist/adopt.d.ts.map +1 -0
  3. package/dist/adopt.js +819 -0
  4. package/dist/adopt.js.map +1 -0
  5. package/dist/completion.d.ts.map +1 -1
  6. package/dist/completion.js +3 -0
  7. package/dist/completion.js.map +1 -1
  8. package/dist/config.d.ts +30 -1
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/config.js +108 -0
  11. package/dist/config.js.map +1 -1
  12. package/dist/deploy/coolify-app.d.ts +35 -0
  13. package/dist/deploy/coolify-app.d.ts.map +1 -0
  14. package/dist/deploy/coolify-app.js +238 -0
  15. package/dist/deploy/coolify-app.js.map +1 -0
  16. package/dist/deploy/pages.js +50 -9
  17. package/dist/deploy/pages.js.map +1 -1
  18. package/dist/deploy/rename-domain.d.ts.map +1 -1
  19. package/dist/deploy/rename-domain.js +26 -6
  20. package/dist/deploy/rename-domain.js.map +1 -1
  21. package/dist/deploy/rollback.d.ts +10 -0
  22. package/dist/deploy/rollback.d.ts.map +1 -0
  23. package/dist/deploy/rollback.js +295 -0
  24. package/dist/deploy/rollback.js.map +1 -0
  25. package/dist/deploy/terraform.d.ts +10 -1
  26. package/dist/deploy/terraform.d.ts.map +1 -1
  27. package/dist/deploy/terraform.js +177 -42
  28. package/dist/deploy/terraform.js.map +1 -1
  29. package/dist/doctor.d.ts.map +1 -1
  30. package/dist/doctor.js +25 -0
  31. package/dist/doctor.js.map +1 -1
  32. package/dist/explain.d.ts.map +1 -1
  33. package/dist/explain.js +5 -0
  34. package/dist/explain.js.map +1 -1
  35. package/dist/index.js +377 -122
  36. package/dist/index.js.map +1 -1
  37. package/dist/prompts.d.ts.map +1 -1
  38. package/dist/prompts.js +283 -11
  39. package/dist/prompts.js.map +1 -1
  40. package/dist/provision/stripe.d.ts +19 -0
  41. package/dist/provision/stripe.d.ts.map +1 -0
  42. package/dist/provision/stripe.js +58 -0
  43. package/dist/provision/stripe.js.map +1 -0
  44. package/dist/scaffold/dotenvx.d.ts.map +1 -1
  45. package/dist/scaffold/dotenvx.js +35 -11
  46. package/dist/scaffold/dotenvx.js.map +1 -1
  47. package/dist/scaffold/infra.d.ts +21 -1
  48. package/dist/scaffold/infra.d.ts.map +1 -1
  49. package/dist/scaffold/infra.js +66 -20
  50. package/dist/scaffold/infra.js.map +1 -1
  51. package/dist/status.d.ts.map +1 -1
  52. package/dist/status.js +7 -0
  53. package/dist/status.js.map +1 -1
  54. package/dist/utils/cloudflare-api.d.ts +23 -0
  55. package/dist/utils/cloudflare-api.d.ts.map +1 -1
  56. package/dist/utils/cloudflare-api.js +31 -0
  57. package/dist/utils/cloudflare-api.js.map +1 -1
  58. package/dist/utils/coolify-api.d.ts +64 -3
  59. package/dist/utils/coolify-api.d.ts.map +1 -1
  60. package/dist/utils/coolify-api.js +99 -3
  61. package/dist/utils/coolify-api.js.map +1 -1
  62. package/dist/utils/run-ledger.d.ts +68 -0
  63. package/dist/utils/run-ledger.d.ts.map +1 -0
  64. package/dist/utils/run-ledger.js +99 -0
  65. package/dist/utils/run-ledger.js.map +1 -0
  66. package/dist/utils/secrets.d.ts +2 -0
  67. package/dist/utils/secrets.d.ts.map +1 -1
  68. package/dist/utils/secrets.js +2 -0
  69. package/dist/utils/secrets.js.map +1 -1
  70. package/package.json +2 -2
  71. package/scripts/release-prep.mjs +130 -95
@@ -1,30 +1,29 @@
1
1
  #!/usr/bin/env node
2
2
  /*
3
- * release-prep — run before `npm version` so any dangling working-tree
4
- * changes get committed and shipped with the release instead of hanging
5
- * around on main afterwards.
3
+ * release-prep — strict pre-release verification.
6
4
  *
7
- * Runs from cli/ (where the release scripts live). Finds the repo root
8
- * via `git rev-parse --show-toplevel` and operates on the whole repo,
9
- * not just cli/ if you've left changes under infra/ or docs/ they get
10
- * picked up too.
5
+ * Refuses to release if any of the three repos (hatchkit main,
6
+ * infra submodule, starter submodule) has uncommitted or untracked
7
+ * changes. Exits with a clear list of which repos need handling and
8
+ * what's dirty in each — release scripts assume a clean baseline so
9
+ * the version commit + tag, the npm publish, and the cross-repo push
10
+ * all line up against the same set of trees.
11
11
  *
12
- * Safety rails:
13
- * - Refuses to run if any staged/untracked path looks like a secret
14
- * (.env*, *.pem, *.key, *credentials*, *secret*).
15
- * - Submodule pointer changes (git shows as ` M infra` vs `M infra`)
16
- * are fine, but untracked content INSIDE a submodule is NOT auto-
17
- * committed from the parent repo — you'd want to commit that inside
18
- * the submodule yourself first. We warn + bail in that case.
19
- * - Interactive: prompts for a commit message (default:
20
- * "chore: pre-release changes").
21
- * - Non-interactive (no TTY): uses the default message and proceeds.
22
- * Skip the whole thing with RELEASE_SKIP_PREP=1.
12
+ * ALSO refuses when the local cli/package.json version is at-or-
13
+ * behind the version published to npm. `npm version patch` increments
14
+ * from the LOCAL version, not the registry's, so drift between the
15
+ * two leads to the script generating a number that's already taken
16
+ * and the publish step crashing after build/typecheck. Catch it up
17
+ * front instead.
18
+ *
19
+ * Skip with RELEASE_SKIP_PREP=1 (e.g. a CI-driven release that's
20
+ * already vouched for cleanliness). RELEASE_SKIP_NPM_CHECK=1 only
21
+ * skips the npm-version comparison, useful offline.
23
22
  */
24
23
 
25
- import { execSync, spawnSync } from "node:child_process";
26
- import { createInterface } from "node:readline";
27
- import { stdin as input, stdout as output } from "node:process";
24
+ import { execSync } from "node:child_process";
25
+ import { existsSync, readFileSync } from "node:fs";
26
+ import { join, relative } from "node:path";
28
27
 
29
28
  if (process.env.RELEASE_SKIP_PREP === "1") {
30
29
  console.log(" release-prep: RELEASE_SKIP_PREP=1 — skipping.");
@@ -35,96 +34,132 @@ function sh(cmd, opts = {}) {
35
34
  return execSync(cmd, { encoding: "utf8", ...opts }).trim();
36
35
  }
37
36
 
38
- // Find the repo root so git commands work no matter where the script was
39
- // launched from (pnpm invokes scripts with cwd=cli/).
40
37
  let repoRoot;
41
38
  try {
42
39
  repoRoot = sh("git rev-parse --show-toplevel");
43
- } catch (err) {
40
+ } catch {
44
41
  console.error(" release-prep: not inside a git repo. Aborting.");
45
42
  process.exit(1);
46
43
  }
47
44
 
48
- const porcelain = sh("git status --porcelain", { cwd: repoRoot });
49
- if (!porcelain) {
50
- console.log(" release-prep: working tree clean. Nothing to commit.");
51
- process.exit(0);
52
- }
45
+ const repos = [
46
+ { label: "hatchkit (main)", path: repoRoot, hint: `cd ${repoRoot}` },
47
+ {
48
+ label: "infra (submodule)",
49
+ path: join(repoRoot, "infra"),
50
+ hint: `cd ${join(repoRoot, "infra")}`,
51
+ },
52
+ {
53
+ label: "starter (submodule)",
54
+ path: join(repoRoot, "starter"),
55
+ hint: `cd ${join(repoRoot, "starter")}`,
56
+ },
57
+ ];
53
58
 
54
- const entries = porcelain.split("\n").map((l) => {
55
- // Porcelain format: "XY path". X = index, Y = worktree. Submodules
56
- // show up with `m`/`M` in position Y + 1 for content changes.
57
- const code = l.slice(0, 2);
58
- const path = l.slice(3);
59
- return { code, path };
60
- });
59
+ const dirty = [];
60
+ for (const r of repos) {
61
+ // Submodule may not be initialized skip silently rather than fail.
62
+ // (`.git` is a file inside an initialized submodule, a dir at the root.)
63
+ if (!existsSync(join(r.path, ".git"))) continue;
61
64
 
62
- console.log("\n release-prep: working tree isn't clean.\n");
63
- console.log(sh("git status --short", { cwd: repoRoot }));
64
-
65
- // Refuse secret-looking paths. Keep the pattern conservative — better a
66
- // false positive that requires manual commit than a leaked token.
67
- const SECRET_RE = /(^|\/)(\.env(\.|$)|[^/]*credentials[^/]*|[^/]*secret[^/]*|[^/]*\.(pem|key|pfx|p12)$)/i;
68
- const secretsFound = entries.filter((e) => SECRET_RE.test(e.path));
69
- if (secretsFound.length > 0) {
70
- console.error("\n release-prep: refusing these paths look like secrets:\n");
71
- for (const e of secretsFound) console.error(` ${e.path}`);
72
- console.error(
73
- "\n Resolve manually: gitignore, delete, or commit yourself and re-run the release.",
74
- );
75
- process.exit(1);
65
+ let status;
66
+ try {
67
+ status = sh("git status --porcelain", { cwd: r.path });
68
+ } catch (err) {
69
+ console.error(` release-prep: couldn't read ${r.label}: ${err.message}`);
70
+ process.exit(1);
71
+ }
72
+ if (status) {
73
+ dirty.push({ ...r, status });
74
+ }
76
75
  }
77
76
 
78
- // Untracked content inside a submodule shows as e.g. ' m infra' with
79
- // only the second column set to lowercase 'm'. Parent-repo `git add`
80
- // can't reach inside; bail so the user commits in the submodule first.
81
- const untrackedInSubmodule = entries.filter((e) => e.code === " m" || e.code === "?m");
82
- if (untrackedInSubmodule.length > 0) {
83
- console.error(
84
- "\n release-prep: submodule has untracked changes inside it:\n",
85
- );
86
- for (const e of untrackedInSubmodule) {
87
- console.error(` ${e.path} — commit inside the submodule first`);
77
+ if (dirty.length === 0) {
78
+ // Tree is clean also check that the local version isn't lagging
79
+ // behind what's on npm. `npm version patch` would silently bump
80
+ // into a taken number otherwise.
81
+ if (process.env.RELEASE_SKIP_NPM_CHECK !== "1") {
82
+ const driftError = checkNpmDrift(repoRoot);
83
+ if (driftError) {
84
+ console.error(`\n ✗ release-prep: ${driftError}\n`);
85
+ process.exit(1);
86
+ }
88
87
  }
89
- console.error(
90
- "\n cd into each submodule, commit, then `git add <submodule>` + re-run the release.",
91
- );
92
- process.exit(1);
88
+ console.log(" ✓ release-prep: all trees clean. Continuing release.");
89
+ process.exit(0);
93
90
  }
94
91
 
95
- // Prompt for a commit message unless we're non-interactive.
96
- const DEFAULT_MSG = "chore: pre-release changes";
97
- let message = DEFAULT_MSG;
98
- if (input.isTTY && output.isTTY) {
99
- const rl = createInterface({ input, output });
100
- message = await new Promise((resolve) => {
101
- rl.question(` Commit message [${DEFAULT_MSG}]: `, (answer) => {
102
- rl.close();
103
- resolve(answer.trim() || DEFAULT_MSG);
104
- });
105
- });
106
- } else {
107
- console.log(` release-prep: non-interactive — using default message "${DEFAULT_MSG}".`);
92
+ console.error("\n ✗ release-prep: cannot release dangling changes.\n");
93
+ for (const r of dirty) {
94
+ const rel = r.path === repoRoot ? "." : relative(repoRoot, r.path);
95
+ console.error(` ── ${r.label} (${rel}/)`);
96
+ for (const line of r.status.split("\n")) {
97
+ console.error(` ${line}`);
98
+ }
99
+ console.error();
108
100
  }
109
-
110
- // Stage + commit at the repo root. Using `git add -A` here is intentional
111
- // (the whole point of this helper is to sweep everything into the release
112
- // commit); the secret-path guard above is what keeps it safe.
113
- const add = spawnSync("git", ["add", "-A"], { cwd: repoRoot, stdio: "inherit" });
114
- if (add.status !== 0) {
115
- console.error(" release-prep: `git add -A` failed.");
116
- process.exit(add.status ?? 1);
101
+ console.error(" Handle each tree before releasing:");
102
+ for (const r of dirty) {
103
+ console.error(` ${r.hint} && git status # commit / stash / discard`);
117
104
  }
105
+ console.error("\n Then re-run the release.\n");
106
+ process.exit(1);
118
107
 
119
- const commit = spawnSync("git", ["commit", "-m", message], {
120
- cwd: repoRoot,
121
- stdio: "inherit",
122
- });
123
- if (commit.status !== 0) {
124
- console.error(
125
- " release-prep: `git commit` failed (pre-commit hook? empty diff after filters?).",
126
- );
127
- process.exit(commit.status ?? 1);
108
+ /** Returns null on success, an error message string when the local
109
+ * cli/package.json is at-or-behind what's published on npm. We only
110
+ * read the registry — never write — so it's safe to run blind. */
111
+ function checkNpmDrift(repoRoot) {
112
+ const pkgPath = join(repoRoot, "cli", "package.json");
113
+ if (!existsSync(pkgPath)) return null; // not the hatchkit monorepo layout
114
+ let pkg;
115
+ try {
116
+ pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
117
+ } catch (err) {
118
+ return `couldn't parse cli/package.json — ${err.message}`;
119
+ }
120
+ const local = pkg.version;
121
+ const name = pkg.name;
122
+ if (!local || !name) return null;
123
+
124
+ let registry;
125
+ try {
126
+ registry = sh(`npm view ${name} version`, { stdio: ["ignore", "pipe", "ignore"] });
127
+ } catch {
128
+ // 404 (package doesn't exist yet on npm) is fine — first publish.
129
+ return null;
130
+ }
131
+ if (!registry) return null;
132
+
133
+ // Only refuse when local is BEHIND registry. Equal is the post-sync
134
+ // state — `npm version patch` increments past it cleanly.
135
+ if (compareSemver(local, registry) < 0) {
136
+ return [
137
+ `local ${name}@${local} is behind the registry's ${registry}.`,
138
+ "",
139
+ " `npm version patch` increments from the LOCAL version, so a release now",
140
+ " would try to publish a version that's already taken. Sync first:",
141
+ "",
142
+ ` cd ${join(repoRoot, "cli")}`,
143
+ ` npm version ${registry} --no-git-tag-version # match the registry`,
144
+ ` cd ${repoRoot}`,
145
+ ` git add cli/package.json cli/package-lock.json`,
146
+ ` git commit -m "chore: release v${registry}"`,
147
+ ` git tag v${registry}`,
148
+ "",
149
+ " Then re-run the release; it'll bump cleanly past that.",
150
+ ].join("\n");
151
+ }
152
+ return null;
128
153
  }
129
154
 
130
- console.log("\n release-prep: committed. Continuing release.\n");
155
+ /** Tiny semver compare — returns -1 / 0 / 1 for a vs b. We only feed
156
+ * it values that come straight out of package.json + npm view, so
157
+ * pre-release / build-metadata strings are out of scope. */
158
+ function compareSemver(a, b) {
159
+ const [aa, bb] = [a, b].map((v) => v.split(".").map((n) => Number(n) || 0));
160
+ for (let i = 0; i < 3; i++) {
161
+ if ((aa[i] ?? 0) > (bb[i] ?? 0)) return 1;
162
+ if ((aa[i] ?? 0) < (bb[i] ?? 0)) return -1;
163
+ }
164
+ return 0;
165
+ }