hatchkit 0.1.35 → 0.1.38

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 (102) hide show
  1. package/dist/adopt.d.ts.map +1 -1
  2. package/dist/adopt.js +167 -1
  3. package/dist/adopt.js.map +1 -1
  4. package/dist/assets/env.d.ts +40 -0
  5. package/dist/assets/env.d.ts.map +1 -0
  6. package/dist/assets/env.js +150 -0
  7. package/dist/assets/env.js.map +1 -0
  8. package/dist/assets/index.d.ts +2 -0
  9. package/dist/assets/index.d.ts.map +1 -0
  10. package/dist/assets/index.js +328 -0
  11. package/dist/assets/index.js.map +1 -0
  12. package/dist/assets/mirror.d.ts +59 -0
  13. package/dist/assets/mirror.d.ts.map +1 -0
  14. package/dist/assets/mirror.js +251 -0
  15. package/dist/assets/mirror.js.map +1 -0
  16. package/dist/completion.d.ts.map +1 -1
  17. package/dist/completion.js +16 -0
  18. package/dist/completion.js.map +1 -1
  19. package/dist/config.d.ts +18 -8
  20. package/dist/config.d.ts.map +1 -1
  21. package/dist/config.js +157 -50
  22. package/dist/config.js.map +1 -1
  23. package/dist/deploy/coolify-app.d.ts +6 -0
  24. package/dist/deploy/coolify-app.d.ts.map +1 -1
  25. package/dist/deploy/coolify-app.js +58 -15
  26. package/dist/deploy/coolify-app.js.map +1 -1
  27. package/dist/deploy/coolify.d.ts.map +1 -1
  28. package/dist/deploy/coolify.js +50 -25
  29. package/dist/deploy/coolify.js.map +1 -1
  30. package/dist/deploy/rename-domain.d.ts.map +1 -1
  31. package/dist/deploy/rename-domain.js +4 -2
  32. package/dist/deploy/rename-domain.js.map +1 -1
  33. package/dist/deploy/rollback.d.ts +11 -0
  34. package/dist/deploy/rollback.d.ts.map +1 -1
  35. package/dist/deploy/rollback.js +46 -9
  36. package/dist/deploy/rollback.js.map +1 -1
  37. package/dist/deploy/sync.d.ts +98 -0
  38. package/dist/deploy/sync.d.ts.map +1 -0
  39. package/dist/deploy/sync.js +354 -0
  40. package/dist/deploy/sync.js.map +1 -0
  41. package/dist/doctor.d.ts.map +1 -1
  42. package/dist/doctor.js +29 -11
  43. package/dist/doctor.js.map +1 -1
  44. package/dist/explain.d.ts.map +1 -1
  45. package/dist/explain.js +5 -0
  46. package/dist/explain.js.map +1 -1
  47. package/dist/index.js +190 -32
  48. package/dist/index.js.map +1 -1
  49. package/dist/prompts.d.ts +19 -0
  50. package/dist/prompts.d.ts.map +1 -1
  51. package/dist/prompts.js +81 -20
  52. package/dist/prompts.js.map +1 -1
  53. package/dist/provision/s3-buckets.d.ts +12 -5
  54. package/dist/provision/s3-buckets.d.ts.map +1 -1
  55. package/dist/provision/s3-buckets.js +14 -7
  56. package/dist/provision/s3-buckets.js.map +1 -1
  57. package/dist/provision/stripe.d.ts +85 -16
  58. package/dist/provision/stripe.d.ts.map +1 -1
  59. package/dist/provision/stripe.js +334 -28
  60. package/dist/provision/stripe.js.map +1 -1
  61. package/dist/provision/write-env.d.ts +7 -0
  62. package/dist/provision/write-env.d.ts.map +1 -1
  63. package/dist/provision/write-env.js +18 -0
  64. package/dist/provision/write-env.js.map +1 -1
  65. package/dist/scaffold/app.d.ts.map +1 -1
  66. package/dist/scaffold/app.js +33 -3
  67. package/dist/scaffold/app.js.map +1 -1
  68. package/dist/scaffold/infra.js +2 -2
  69. package/dist/scaffold/infra.js.map +1 -1
  70. package/dist/scaffold/manifest.d.ts +5 -0
  71. package/dist/scaffold/manifest.d.ts.map +1 -1
  72. package/dist/scaffold/manifest.js +1 -0
  73. package/dist/scaffold/manifest.js.map +1 -1
  74. package/dist/scaffold/starter-files.d.ts +22 -0
  75. package/dist/scaffold/starter-files.d.ts.map +1 -1
  76. package/dist/scaffold/starter-files.js +44 -0
  77. package/dist/scaffold/starter-files.js.map +1 -1
  78. package/dist/scaffold/surfaces.d.ts +11 -0
  79. package/dist/scaffold/surfaces.d.ts.map +1 -0
  80. package/dist/scaffold/surfaces.js +331 -0
  81. package/dist/scaffold/surfaces.js.map +1 -0
  82. package/dist/status.d.ts.map +1 -1
  83. package/dist/status.js +11 -1
  84. package/dist/status.js.map +1 -1
  85. package/dist/templates/build-pipeline/docker-compose.yml.hbs +17 -0
  86. package/dist/utils/cancel-handler.d.ts +17 -0
  87. package/dist/utils/cancel-handler.d.ts.map +1 -0
  88. package/dist/utils/cancel-handler.js +97 -0
  89. package/dist/utils/cancel-handler.js.map +1 -0
  90. package/dist/utils/coolify-api.d.ts +60 -2
  91. package/dist/utils/coolify-api.d.ts.map +1 -1
  92. package/dist/utils/coolify-api.js +85 -2
  93. package/dist/utils/coolify-api.js.map +1 -1
  94. package/dist/utils/secrets.d.ts +29 -0
  95. package/dist/utils/secrets.d.ts.map +1 -1
  96. package/dist/utils/secrets.js +29 -0
  97. package/dist/utils/secrets.js.map +1 -1
  98. package/dist/utils/validate.d.ts +6 -0
  99. package/dist/utils/validate.d.ts.map +1 -1
  100. package/dist/utils/validate.js +17 -0
  101. package/dist/utils/validate.js.map +1 -1
  102. package/package.json +3 -2
@@ -0,0 +1,98 @@
1
+ import { type ProjectManifest } from "../scaffold/manifest.js";
2
+ import { type CoolifyApplication } from "../utils/coolify-api.js";
3
+ export interface SyncOptions {
4
+ /** Project root containing `.hatchkit.json`. */
5
+ projectDir: string;
6
+ /** Print the desired changes without PATCHing Coolify. */
7
+ dryRun?: boolean;
8
+ /** Emit `{ ok, apps: [...] }` JSON to stdout. Suppresses the human
9
+ * rendering for scripts. */
10
+ json?: boolean;
11
+ }
12
+ /** What sync intends to do for one Coolify application — surfaces both
13
+ * the desired payload and a diff against what Coolify currently reports.
14
+ * Renderable in either human-readable or JSON form. */
15
+ export interface AppSyncPlan {
16
+ /** Coolify uuid. */
17
+ uuid: string;
18
+ /** Coolify app name (used to locate the resource). */
19
+ name: string;
20
+ /** Build pack reported by Coolify — drives which API field carries
21
+ * the domain payload. */
22
+ buildPack?: CoolifyApplication["buildPack"];
23
+ /** Per-service domains for dockercompose apps. Always populated when
24
+ * the build pack is dockercompose; undefined otherwise. */
25
+ desiredDockerComposeDomains?: Array<{
26
+ name: string;
27
+ domain: string;
28
+ }>;
29
+ /** Comma-joined FQDN list for non-dockercompose apps. Always
30
+ * populated when the build pack is nixpacks / dockerfile / static;
31
+ * undefined for dockercompose. */
32
+ desiredDomains?: string[];
33
+ /** ports_exposes the manifest expects on this app. Always set —
34
+ * Coolify keeps it as a non-empty string. */
35
+ desiredPortsExposes: string;
36
+ /** Snapshot of the same fields as Coolify currently reports them.
37
+ * Used by the renderer to decide "already correct" vs. "will
38
+ * change", and by the JSON output as the before-state. */
39
+ current: {
40
+ fqdn: string | null;
41
+ dockerComposeDomains?: Array<{
42
+ name: string;
43
+ domain: string;
44
+ }>;
45
+ portsExposes?: string;
46
+ };
47
+ /** Whether a PATCH is needed to converge — false means everything
48
+ * already matches, sync skips the API call. */
49
+ changed: boolean;
50
+ }
51
+ export interface SyncResult {
52
+ ok: boolean;
53
+ /** Set when sync couldn't run at all (e.g. no manifest, no Coolify
54
+ * config, no matching apps). Either `apps` or `error` will be
55
+ * meaningful — never both. */
56
+ error?: string;
57
+ apps: AppSyncPlan[];
58
+ /** When dryRun, no PATCH was made even if `changed` was true. */
59
+ dryRun: boolean;
60
+ }
61
+ /** Top-level entrypoint. Reads the project manifest, finds the Coolify
62
+ * app(s) hatchkit knows about by name, and pushes the desired domain
63
+ * + ports payload — or just prints what it would push when `dryRun`. */
64
+ export declare function runSync(opts: SyncOptions): Promise<SyncResult>;
65
+ /** Desired state for one Coolify application, derived from the manifest.
66
+ * Computed before any API calls so `--dry-run` never hits the network
67
+ * for plan generation. */
68
+ interface DesiredApp {
69
+ /** Name to look up in Coolify. */
70
+ appName: string;
71
+ /** Build-pack-aware payload — only one of these is set per app. The
72
+ * CoolifyApi.updateApplication shape needs the right field for the
73
+ * build pack reported by Coolify; we resolve that at apply time, not
74
+ * here, since the manifest doesn't carry build pack. */
75
+ domains: Array<{
76
+ name: string;
77
+ domain: string;
78
+ }>;
79
+ /** ports_exposes for this app. Comma-separated string Coolify
80
+ * stores verbatim. */
81
+ portsExposes: string;
82
+ }
83
+ /** Map a manifest to the set of Coolify apps hatchkit owns for it. The
84
+ * shapes we cover (matching the layouts `findCoolifyAppsForProject`
85
+ * understands):
86
+ *
87
+ * 1. Adopted single-app: `<name>`
88
+ * 2. Starter split (legacy): `<name>-server` + `<name>-client`
89
+ * 3. Old single-app fallbacks: `<name>-web` / `<name>-app` / `<name>-api`
90
+ *
91
+ * We synthesize a candidate list per layout. The actual lookup happens
92
+ * per-name; misses are skipped. This means a project that scaffolded as
93
+ * starter-split AND was later adopted as single-app would push twice —
94
+ * not a problem because each PATCH is independent and idempotent. */
95
+ export declare function computeDesiredAppStates(manifest: ProjectManifest): DesiredApp[];
96
+ export declare function runSyncCli(args: string[]): Promise<void>;
97
+ export {};
98
+ //# sourceMappingURL=sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/deploy/sync.ts"],"names":[],"mappings":"AAoCA,OAAO,EAAE,KAAK,eAAe,EAAgB,MAAM,yBAAyB,CAAC;AAC7E,OAAO,EAAc,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE9E,MAAM,WAAW,WAAW;IAC1B,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;iCAC6B;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;wDAEwD;AACxD,MAAM,WAAW,WAAW;IAC1B,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb;8BAC0B;IAC1B,SAAS,CAAC,EAAE,kBAAkB,CAAC,WAAW,CAAC,CAAC;IAC5C;gEAC4D;IAC5D,2BAA2B,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtE;;uCAEmC;IACnC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B;kDAC8C;IAC9C,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;+DAE2D;IAC3D,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,oBAAoB,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAC/D,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IACF;oDACgD;IAChD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,OAAO,CAAC;IACZ;;mCAE+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,WAAW,EAAE,CAAC;IACpB,iEAAiE;IACjE,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;yEAEyE;AACzE,wBAAsB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAsHpE;AAMD;;2BAE2B;AAC3B,UAAU,UAAU;IAClB,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB;;;6DAGyD;IACzD,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD;2BACuB;IACvB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;sEAWsE;AACtE,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,eAAe,GAAG,UAAU,EAAE,CA6E/E;AA2HD,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAe9D"}
@@ -0,0 +1,354 @@
1
+ /*
2
+ * sync — push the .hatchkit.json manifest's view of the project onto
3
+ * the Coolify resource(s) hatchkit created (or adopted) for it.
4
+ *
5
+ * Why this exists: Coolify's auto-generated Traefik labels are derived
6
+ * from the application's Domain field (`docker_compose_domains` for
7
+ * dockercompose build packs, `fqdn` / `domains` otherwise). When a
8
+ * scaffold or adopt run created the app without that field populated —
9
+ * either because of the pre-fix bug where `updateApplication` couldn't
10
+ * push domains, or because the user changed the manifest after scaffold —
11
+ * the container ends up with zero traefik labels and Traefik silently
12
+ * drops the route. `hatchkit sync` reads the manifest, finds the matching
13
+ * Coolify app(s), and PATCHes them so Coolify regenerates the labels on
14
+ * the next deploy.
15
+ *
16
+ * Scope is deliberately narrow. Sync only pushes fields that are safe to
17
+ * blast over the wire idempotently:
18
+ * · domain (`docker_compose_domains` for compose apps; `domains` for
19
+ * nixpacks / dockerfile / static)
20
+ * · ports_exposes (so the multi-host routing for the starter's split
21
+ * compose stays consistent with what runCoolifySetup creates)
22
+ *
23
+ * Out of scope (handled by other commands):
24
+ * · env vars → `hatchkit keys push` + adopt's setAppEnv
25
+ * · DNS records → adopt's wireDns + `rename-domain`
26
+ * · ML services / GPU → `hatchkit add gpu`
27
+ * · S3 buckets / tokens → `hatchkit provision s3`
28
+ *
29
+ * Idempotent by design: reads current state first, only PATCHes when the
30
+ * desired domain set differs from what Coolify reports. `--dry-run`
31
+ * shows the diff without touching anything.
32
+ */
33
+ import chalk from "chalk";
34
+ import ora from "ora";
35
+ import { getCoolifyConfig } from "../config.js";
36
+ import { readManifest } from "../scaffold/manifest.js";
37
+ import { CoolifyApi } from "../utils/coolify-api.js";
38
+ /** Top-level entrypoint. Reads the project manifest, finds the Coolify
39
+ * app(s) hatchkit knows about by name, and pushes the desired domain
40
+ * + ports payload — or just prints what it would push when `dryRun`. */
41
+ export async function runSync(opts) {
42
+ const manifest = readManifest(opts.projectDir);
43
+ if (!manifest) {
44
+ const err = `No .hatchkit.json found in ${opts.projectDir}.`;
45
+ if (!opts.json) {
46
+ console.log(chalk.red(` ${err}`));
47
+ console.log(chalk.dim(" Run `hatchkit sync` from a hatchkit-scaffolded project root, or `hatchkit adopt` to onboard an existing project first."));
48
+ }
49
+ return { ok: false, error: err, apps: [], dryRun: !!opts.dryRun };
50
+ }
51
+ const cfg = await getCoolifyConfig();
52
+ if (!cfg) {
53
+ const err = "Coolify is not configured. Run `hatchkit config add coolify` first.";
54
+ if (!opts.json)
55
+ console.log(chalk.red(` ${err}`));
56
+ return { ok: false, error: err, apps: [], dryRun: !!opts.dryRun };
57
+ }
58
+ const api = new CoolifyApi({ url: cfg.url, token: cfg.token });
59
+ const desiredAll = computeDesiredAppStates(manifest);
60
+ const apps = [];
61
+ const errors = [];
62
+ // Match every desired-app entry against Coolify by name. We don't
63
+ // pre-list /applications and intersect because sync should still work
64
+ // when the user has hundreds of apps; a per-name lookup is cheaper.
65
+ // Apps the manifest expects but that don't exist in Coolify are
66
+ // logged as a hint (the user probably needs `hatchkit adopt` first)
67
+ // but don't fail the whole run — partial sync of the apps that DO
68
+ // exist is the most useful behavior.
69
+ for (const desired of desiredAll) {
70
+ const matchSpinner = opts.json ? null : ora(`Coolify: locating "${desired.appName}"`).start();
71
+ const found = await api.findApplicationByName(desired.appName);
72
+ if (!found) {
73
+ matchSpinner?.warn(`Coolify: no app named "${desired.appName}" — skipping`);
74
+ continue;
75
+ }
76
+ matchSpinner?.succeed(`Coolify: found "${desired.appName}" (${found.uuid})`);
77
+ let current;
78
+ try {
79
+ current = await api.getApplication(found.uuid);
80
+ }
81
+ catch (err) {
82
+ errors.push(`Failed to read Coolify app "${desired.appName}" (${found.uuid}): ${err.message}`);
83
+ continue;
84
+ }
85
+ const plan = buildPlan(found.uuid, desired, current);
86
+ apps.push(plan);
87
+ if (!opts.json)
88
+ renderPlan(plan);
89
+ if (!plan.changed)
90
+ continue;
91
+ if (opts.dryRun)
92
+ continue;
93
+ const patch = ora(`Coolify: updating "${desired.appName}"`).start();
94
+ try {
95
+ await api.updateApplication(plan.uuid, {
96
+ portsExposes: plan.desiredPortsExposes,
97
+ ...(plan.desiredDockerComposeDomains
98
+ ? { dockerComposeDomains: plan.desiredDockerComposeDomains }
99
+ : {}),
100
+ ...(plan.desiredDomains ? { domains: plan.desiredDomains } : {}),
101
+ });
102
+ patch.succeed(`Coolify: updated "${desired.appName}"`);
103
+ }
104
+ catch (err) {
105
+ patch.fail(`Coolify: PATCH failed: ${err.message}`);
106
+ errors.push(`PATCH ${desired.appName}: ${err.message}`);
107
+ }
108
+ }
109
+ if (apps.length === 0 && errors.length === 0) {
110
+ const err = `No Coolify apps matched manifest project "${manifest.name}".`;
111
+ if (!opts.json) {
112
+ console.log(chalk.yellow(` ${err}`));
113
+ console.log(chalk.dim(` Looked for: ${desiredAll.map((d) => `"${d.appName}"`).join(", ")}.\n` +
114
+ ` Run \`hatchkit adopt\` to create them, or rename the existing app(s) to match.`));
115
+ }
116
+ return { ok: false, error: err, apps, dryRun: !!opts.dryRun };
117
+ }
118
+ if (!opts.json) {
119
+ if (opts.dryRun) {
120
+ console.log(chalk.dim("\n --dry-run: no changes pushed."));
121
+ }
122
+ else {
123
+ const changed = apps.filter((a) => a.changed);
124
+ if (changed.length === 0) {
125
+ console.log(chalk.green("\n ✓ Coolify already in sync with manifest."));
126
+ }
127
+ else {
128
+ console.log(chalk.green(`\n ✓ Synced ${changed.length} app(s) to manifest state.`) +
129
+ chalk.dim("\n Trigger a redeploy in Coolify (or push a commit) for Traefik to pick up the new labels."));
130
+ }
131
+ }
132
+ if (errors.length > 0) {
133
+ console.log(chalk.yellow("\n Errors:"));
134
+ for (const e of errors)
135
+ console.log(chalk.yellow(` · ${e}`));
136
+ }
137
+ }
138
+ return {
139
+ ok: errors.length === 0,
140
+ apps,
141
+ dryRun: !!opts.dryRun,
142
+ ...(errors.length > 0 ? { error: errors.join("; ") } : {}),
143
+ };
144
+ }
145
+ /** Map a manifest to the set of Coolify apps hatchkit owns for it. The
146
+ * shapes we cover (matching the layouts `findCoolifyAppsForProject`
147
+ * understands):
148
+ *
149
+ * 1. Adopted single-app: `<name>`
150
+ * 2. Starter split (legacy): `<name>-server` + `<name>-client`
151
+ * 3. Old single-app fallbacks: `<name>-web` / `<name>-app` / `<name>-api`
152
+ *
153
+ * We synthesize a candidate list per layout. The actual lookup happens
154
+ * per-name; misses are skipped. This means a project that scaffolded as
155
+ * starter-split AND was later adopted as single-app would push twice —
156
+ * not a problem because each PATCH is independent and idempotent. */
157
+ export function computeDesiredAppStates(manifest) {
158
+ const { name, domain, surfaces, ports } = manifest;
159
+ const portServer = String(ports?.server ?? 3000);
160
+ const portClient = String(ports?.client ?? 3001);
161
+ // Routing recipes — see `runCoolifySetup` (cli/src/deploy/coolify.ts)
162
+ // for the create-time source of truth. Sync mirrors that exactly so
163
+ // re-running sync converges to the same labels Coolify generated at
164
+ // create time.
165
+ const apiDomain = `api.${domain}`;
166
+ const frontendDomain = `https://${domain}`;
167
+ const backendDomains = [
168
+ `https://${apiDomain}`,
169
+ `https://${domain}/api`,
170
+ `https://${domain}/api/ws`,
171
+ `https://${apiDomain}/ws`,
172
+ ];
173
+ const splitClientDomains = [{ name: "client", domain: frontendDomain }];
174
+ const splitServerDomains = backendDomains.map((d) => ({ name: "server", domain: d }));
175
+ // Single-app layout: one Coolify app named `<name>` with one compose
176
+ // service `app`. ports_exposes is surface-aware:
177
+ // server-only / both → server port (the public listener)
178
+ // client-only → 80 (matches adopt.ts's static-site default)
179
+ const singleAppPort = surfaces === "client-only" ? "80" : portServer;
180
+ const singleAppDomain = surfaces === "client-only" && (singleAppPort === "80" || singleAppPort === "443")
181
+ ? `https://${domain}`
182
+ : `https://${domain}:${singleAppPort}`;
183
+ // Use bare `https://<domain>` when the listener is on the conventional
184
+ // 80/443 — Coolify's Traefik handles the HTTPS termination and the
185
+ // explicit port suffix would push the route through Traefik on a
186
+ // non-standard port (which won't match the Coolify ingress). The
187
+ // formatDockerComposeDomain helper in coolify-app.ts uses the same
188
+ // rule; mirror it here so sync output matches what adopt creates.
189
+ const singleAppCanonicalDomain = singleAppPort === "80" || singleAppPort === "443" ? `https://${domain}` : singleAppDomain;
190
+ const singleApp = {
191
+ appName: name,
192
+ domains: [{ name: "app", domain: singleAppCanonicalDomain }],
193
+ portsExposes: singleAppPort,
194
+ };
195
+ // Starter-split layout: two apps. Each app's compose has its own
196
+ // service named `client` or `server` respectively; routing splits
197
+ // along the same lines as runCoolifySetup creates.
198
+ const splitClient = {
199
+ appName: `${name}-client`,
200
+ domains: splitClientDomains,
201
+ portsExposes: portClient,
202
+ };
203
+ const splitServer = {
204
+ appName: `${name}-server`,
205
+ domains: splitServerDomains,
206
+ portsExposes: portServer,
207
+ };
208
+ // Old single-app fallbacks. `runCoolifySetup` creates `<name>-web`
209
+ // for the very-old starter shape; the others are speculative for
210
+ // hand-written compose layouts that adopt previously matched.
211
+ const fallbackWeb = {
212
+ ...singleApp,
213
+ appName: `${name}-web`,
214
+ };
215
+ // Filter by surfaces so we don't ship a non-existent split shape
216
+ // in JSON output. The actual Coolify lookup will skip non-existent
217
+ // names anyway, but keeping the candidate list tight reduces noise.
218
+ if (surfaces === "client-only") {
219
+ return [singleApp, fallbackWeb, splitClient];
220
+ }
221
+ if (surfaces === "server-only") {
222
+ return [singleApp, fallbackWeb, splitServer];
223
+ }
224
+ // both / undefined → server-of-truth is the split layout, but adopt
225
+ // collapses to single-app for projects without a separate frontend.
226
+ return [singleApp, fallbackWeb, splitServer, splitClient];
227
+ }
228
+ // ---------------------------------------------------------------------------
229
+ // Plan rendering
230
+ // ---------------------------------------------------------------------------
231
+ function buildPlan(uuid, desired, current) {
232
+ const isCompose = current.buildPack === "dockercompose";
233
+ // dockercompose apps use docker_compose_domains; everything else uses
234
+ // the flat `domains` field. Coolify rejects a domain payload that
235
+ // doesn't match the build pack with a 422.
236
+ const desiredDockerComposeDomains = isCompose ? desired.domains : undefined;
237
+ const desiredDomains = isCompose ? undefined : desired.domains.map((d) => d.domain);
238
+ const portsChanged = current.portsExposes !== undefined && current.portsExposes !== desired.portsExposes;
239
+ const domainsChanged = isCompose
240
+ ? !sameDockerComposeDomains(current.dockerComposeDomains, desired.domains)
241
+ : !sameStringList(splitFqdn(current.fqdn), desired.domains.map((d) => d.domain));
242
+ return {
243
+ uuid,
244
+ name: current.name || desired.appName,
245
+ buildPack: current.buildPack,
246
+ ...(desiredDockerComposeDomains ? { desiredDockerComposeDomains } : {}),
247
+ ...(desiredDomains ? { desiredDomains } : {}),
248
+ desiredPortsExposes: desired.portsExposes,
249
+ current: {
250
+ fqdn: current.fqdn,
251
+ ...(current.dockerComposeDomains
252
+ ? { dockerComposeDomains: current.dockerComposeDomains }
253
+ : {}),
254
+ ...(current.portsExposes !== undefined ? { portsExposes: current.portsExposes } : {}),
255
+ },
256
+ changed: portsChanged || domainsChanged,
257
+ };
258
+ }
259
+ function renderPlan(plan) {
260
+ console.log(chalk.bold(`\n ${plan.name}`) + chalk.dim(` (${plan.uuid.slice(0, 8)}…)`));
261
+ if (plan.buildPack) {
262
+ console.log(chalk.dim(` build pack: ${plan.buildPack}`));
263
+ }
264
+ if (plan.desiredDockerComposeDomains) {
265
+ const before = plan.current.dockerComposeDomains ?? [];
266
+ const after = plan.desiredDockerComposeDomains;
267
+ const same = sameDockerComposeDomains(before, after);
268
+ if (same) {
269
+ console.log(chalk.green(` ✓ docker_compose_domains: in sync`));
270
+ console.log(chalk.dim(` ${formatDockerComposeDomains(after)}`));
271
+ }
272
+ else {
273
+ console.log(chalk.yellow(` · docker_compose_domains:`));
274
+ console.log(chalk.dim(` before: ${formatDockerComposeDomains(before)}`));
275
+ console.log(chalk.dim(` after: ${formatDockerComposeDomains(after)}`));
276
+ }
277
+ }
278
+ else if (plan.desiredDomains) {
279
+ const before = splitFqdn(plan.current.fqdn);
280
+ const after = plan.desiredDomains;
281
+ const same = sameStringList(before, after);
282
+ if (same) {
283
+ console.log(chalk.green(` ✓ domains: in sync (${after.join(", ")})`));
284
+ }
285
+ else {
286
+ console.log(chalk.yellow(` · domains:`));
287
+ console.log(chalk.dim(` before: ${before.join(", ") || "(empty)"}`));
288
+ console.log(chalk.dim(` after: ${after.join(", ")}`));
289
+ }
290
+ }
291
+ if (plan.current.portsExposes !== undefined &&
292
+ plan.current.portsExposes !== plan.desiredPortsExposes) {
293
+ console.log(chalk.yellow(` · ports_exposes:`));
294
+ console.log(chalk.dim(` before: ${plan.current.portsExposes}`));
295
+ console.log(chalk.dim(` after: ${plan.desiredPortsExposes}`));
296
+ }
297
+ else if (plan.current.portsExposes === plan.desiredPortsExposes) {
298
+ console.log(chalk.green(` ✓ ports_exposes: ${plan.desiredPortsExposes}`));
299
+ }
300
+ }
301
+ // ---------------------------------------------------------------------------
302
+ // Helpers
303
+ // ---------------------------------------------------------------------------
304
+ function splitFqdn(fqdn) {
305
+ if (!fqdn)
306
+ return [];
307
+ return fqdn
308
+ .split(",")
309
+ .map((s) => s.trim())
310
+ .filter(Boolean);
311
+ }
312
+ function sameStringList(a, b) {
313
+ if (a.length !== b.length)
314
+ return false;
315
+ const sa = [...a].sort();
316
+ const sb = [...b].sort();
317
+ return sa.every((v, i) => v === sb[i]);
318
+ }
319
+ function sameDockerComposeDomains(a, b) {
320
+ const left = a ?? [];
321
+ if (left.length !== b.length)
322
+ return false;
323
+ // Order-insensitive comparison — Coolify doesn't promise to round-trip
324
+ // the array in the same order it was sent.
325
+ const key = (e) => `${e.name}::${e.domain}`;
326
+ const setA = new Set(left.map(key));
327
+ return b.every((e) => setA.has(key(e)));
328
+ }
329
+ function formatDockerComposeDomains(entries) {
330
+ if (entries.length === 0)
331
+ return "(empty)";
332
+ return entries.map((e) => `${e.name}=${e.domain}`).join(", ");
333
+ }
334
+ // ---------------------------------------------------------------------------
335
+ // CLI glue — thin wrapper the dispatcher calls.
336
+ // ---------------------------------------------------------------------------
337
+ export async function runSyncCli(args) {
338
+ const dryRun = args.includes("--dry-run");
339
+ const json = args.includes("--json");
340
+ const dirArg = (() => {
341
+ const i = args.findIndex((a) => a === "--dir");
342
+ if (i >= 0 && args[i + 1])
343
+ return args[i + 1];
344
+ return undefined;
345
+ })();
346
+ const projectDir = dirArg ? dirArg : process.cwd();
347
+ const result = await runSync({ projectDir, dryRun, json });
348
+ if (json) {
349
+ console.log(JSON.stringify(result, null, 2));
350
+ }
351
+ if (!result.ok)
352
+ process.exit(1);
353
+ }
354
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/deploy/sync.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAuBA,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAwfD,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAoBnE;AAED;;;;;;;;;;;0CAW0C;AAC1C,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CA6FrF;AAED;;;;;;;;;;;;;+CAa+C;AAC/C,wBAAsB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CA6HxF;AAED,wBAAsB,SAAS,CAAC,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA8C5E"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAuBA,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAugBD,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAoBnE;AAED;;;;;;;;;;;0CAW0C;AAC1C,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CA6FrF;AAED;;;;;;;;;;;;;+CAa+C;AAC/C,wBAAsB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CA6HxF;AAED,wBAAsB,SAAS,CAAC,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA8C5E"}
package/dist/doctor.js CHANGED
@@ -423,30 +423,47 @@ async function checkResend() {
423
423
  return undefined;
424
424
  });
425
425
  }
426
- async function checkStripe() {
427
- const { getStripeConfig } = await import("./config.js");
428
- const cfg = await getStripeConfig();
429
- if (!cfg)
430
- return { name: "Stripe", status: "skip" };
431
- return check(`Stripe (${cfg.mode})`, async () => {
426
+ async function checkStripeMode(mode, secretKey) {
427
+ return check(`Stripe (${mode} master)`, async () => {
432
428
  const res = await fetch("https://api.stripe.com/v1/balance", {
433
- headers: { Authorization: `Bearer ${cfg.secretKey}` },
429
+ headers: { Authorization: `Bearer ${secretKey}` },
434
430
  });
435
431
  if (!res.ok)
436
432
  throw new Error(`HTTP ${res.status}`);
437
- return "secret key valid";
433
+ // The webhook_endpoints:write scope can't be cheaply tested with
434
+ // a GET, so /balance is the proxy: it proves the key is live and
435
+ // the account is reachable. A scope-mismatched key still passes
436
+ // /balance — at provision time, POST /v1/webhook_endpoints will
437
+ // surface the scope error inline (and `hatchkit create` already
438
+ // soft-fails to a manual fallback when that happens).
439
+ return "master key valid";
438
440
  }, (detail) => {
439
441
  const code = httpCode(detail);
440
442
  if (code === 401) {
441
443
  return [
442
- "Stripe secret key is invalid or was rotated.",
443
- "Find the current pair at https://dashboard.stripe.com/apikeys",
444
+ `Stripe ${mode} master key is invalid or was rotated.`,
445
+ `Create a new restricted key (${mode} mode) at https://dashboard.stripe.com/apikeys`,
446
+ "Required scope: Webhook Endpoints — Write",
444
447
  "Then re-run: `hatchkit config add stripe`",
445
448
  ];
446
449
  }
447
450
  return undefined;
448
451
  });
449
452
  }
453
+ async function checkStripe() {
454
+ const { getStripeConfig } = await import("./config.js");
455
+ const cfg = await getStripeConfig();
456
+ if (!cfg)
457
+ return [{ name: "Stripe", status: "skip" }];
458
+ const out = [];
459
+ if (cfg.testSecretKey)
460
+ out.push(await checkStripeMode("test", cfg.testSecretKey));
461
+ if (cfg.liveSecretKey)
462
+ out.push(await checkStripeMode("live", cfg.liveSecretKey));
463
+ if (out.length === 0)
464
+ return [{ name: "Stripe", status: "skip" }];
465
+ return out;
466
+ }
450
467
  export async function collectDoctorResults() {
451
468
  const results = [];
452
469
  results.push(await checkGitHub());
@@ -460,7 +477,8 @@ export async function collectDoctorResults() {
460
477
  results.push(await checkGlitchtip());
461
478
  results.push(await checkOpenpanel());
462
479
  results.push(await checkResend());
463
- results.push(await checkStripe());
480
+ for (const r of await checkStripe())
481
+ results.push(r);
464
482
  // Project-local checks — only run when doctor was invoked inside a
465
483
  // hatchkit-managed project (manifest at cwd). Globally they're a
466
484
  // no-op, so `hatchkit doctor` from $HOME stays clean.