hatchkit 0.1.47 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adopt.d.ts +61 -1
- package/dist/adopt.d.ts.map +1 -1
- package/dist/adopt.js +90 -86
- package/dist/adopt.js.map +1 -1
- package/dist/assets/env.d.ts +2 -2
- package/dist/assets/env.d.ts.map +1 -1
- package/dist/assets/index.js +11 -11
- package/dist/assets/index.js.map +1 -1
- package/dist/assets/mirror.js +1 -1
- package/dist/completion.d.ts.map +1 -1
- package/dist/completion.js +20 -2
- package/dist/completion.js.map +1 -1
- package/dist/config.d.ts +32 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +364 -1
- package/dist/config.js.map +1 -1
- package/dist/deploy/coolify.d.ts +5 -0
- package/dist/deploy/coolify.d.ts.map +1 -1
- package/dist/deploy/coolify.js +67 -4
- package/dist/deploy/coolify.js.map +1 -1
- package/dist/deploy/ghcr.d.ts +1 -0
- package/dist/deploy/ghcr.d.ts.map +1 -1
- package/dist/deploy/ghcr.js +2 -2
- package/dist/deploy/ghcr.js.map +1 -1
- package/dist/deploy/github.d.ts.map +1 -1
- package/dist/deploy/github.js +3 -2
- package/dist/deploy/github.js.map +1 -1
- package/dist/deploy/rollback.d.ts.map +1 -1
- package/dist/deploy/rollback.js +9 -0
- package/dist/deploy/rollback.js.map +1 -1
- package/dist/dev-setup.d.ts +13 -5
- package/dist/dev-setup.d.ts.map +1 -1
- package/dist/dev-setup.js +268 -59
- package/dist/dev-setup.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +65 -1
- package/dist/doctor.js.map +1 -1
- package/dist/email/index.js +5 -5
- package/dist/email/index.js.map +1 -1
- package/dist/email/setup.d.ts +1 -1
- package/dist/email/setup.d.ts.map +1 -1
- package/dist/email/setup.js +3 -3
- package/dist/email/setup.js.map +1 -1
- package/dist/explain.d.ts.map +1 -1
- package/dist/explain.js +9 -8
- package/dist/explain.js.map +1 -1
- package/dist/index.js +523 -91
- package/dist/index.js.map +1 -1
- package/dist/inventory.d.ts +1 -0
- package/dist/inventory.d.ts.map +1 -1
- package/dist/inventory.js +2 -0
- package/dist/inventory.js.map +1 -1
- package/dist/onboarding/plan.d.ts +54 -0
- package/dist/onboarding/plan.d.ts.map +1 -0
- package/dist/onboarding/plan.js +143 -0
- package/dist/onboarding/plan.js.map +1 -0
- package/dist/onboarding/review.d.ts +27 -0
- package/dist/onboarding/review.d.ts.map +1 -0
- package/dist/onboarding/review.js +55 -0
- package/dist/onboarding/review.js.map +1 -0
- package/dist/prompts.d.ts +13 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +107 -89
- package/dist/prompts.js.map +1 -1
- package/dist/provision/glitchtip.d.ts +1 -0
- package/dist/provision/glitchtip.d.ts.map +1 -1
- package/dist/provision/glitchtip.js +16 -0
- package/dist/provision/glitchtip.js.map +1 -1
- package/dist/provision/index.d.ts +26 -3
- package/dist/provision/index.d.ts.map +1 -1
- package/dist/provision/index.js +215 -11
- package/dist/provision/index.js.map +1 -1
- package/dist/provision/openpanel.d.ts +1 -0
- package/dist/provision/openpanel.d.ts.map +1 -1
- package/dist/provision/openpanel.js +21 -0
- package/dist/provision/openpanel.js.map +1 -1
- package/dist/provision/plausible.d.ts +11 -0
- package/dist/provision/plausible.d.ts.map +1 -0
- package/dist/provision/plausible.js +108 -0
- package/dist/provision/plausible.js.map +1 -0
- package/dist/provision/resend.d.ts +4 -0
- package/dist/provision/resend.d.ts.map +1 -1
- package/dist/provision/resend.js +11 -6
- package/dist/provision/resend.js.map +1 -1
- package/dist/provision/search-console.d.ts +17 -0
- package/dist/provision/search-console.d.ts.map +1 -0
- package/dist/provision/search-console.js +142 -0
- package/dist/provision/search-console.js.map +1 -0
- package/dist/scaffold/app.d.ts +1 -0
- package/dist/scaffold/app.d.ts.map +1 -1
- package/dist/scaffold/app.js +6 -3
- package/dist/scaffold/app.js.map +1 -1
- package/dist/scaffold/infra.js +2 -0
- package/dist/scaffold/infra.js.map +1 -1
- package/dist/scaffold/manifest.d.ts +18 -2
- package/dist/scaffold/manifest.d.ts.map +1 -1
- package/dist/scaffold/manifest.js +7 -1
- package/dist/scaffold/manifest.js.map +1 -1
- package/dist/scaffold/server-add.d.ts +21 -0
- package/dist/scaffold/server-add.d.ts.map +1 -0
- package/dist/scaffold/server-add.js +275 -0
- package/dist/scaffold/server-add.js.map +1 -0
- package/dist/scaffold/starter-files.d.ts +3 -3
- package/dist/scaffold/starter-files.js +3 -3
- package/dist/scaffold/update.d.ts +1 -0
- package/dist/scaffold/update.d.ts.map +1 -1
- package/dist/scaffold/update.js +8 -5
- package/dist/scaffold/update.js.map +1 -1
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +27 -1
- package/dist/status.js.map +1 -1
- package/dist/templates/base/env.example.hbs +3 -0
- package/dist/utils/cloudflare-api.d.ts +5 -0
- package/dist/utils/cloudflare-api.d.ts.map +1 -1
- package/dist/utils/cloudflare-api.js +19 -0
- package/dist/utils/cloudflare-api.js.map +1 -1
- package/dist/utils/coolify-api.d.ts +3 -2
- package/dist/utils/coolify-api.d.ts.map +1 -1
- package/dist/utils/coolify-api.js +19 -5
- package/dist/utils/coolify-api.js.map +1 -1
- package/dist/utils/flags.d.ts.map +1 -1
- package/dist/utils/flags.js +16 -0
- package/dist/utils/flags.js.map +1 -1
- package/dist/utils/run-ledger.d.ts +3 -0
- package/dist/utils/run-ledger.d.ts.map +1 -1
- package/dist/utils/run-ledger.js.map +1 -1
- package/dist/utils/secrets.d.ts +5 -0
- package/dist/utils/secrets.d.ts.map +1 -1
- package/dist/utils/secrets.js +5 -0
- package/dist/utils/secrets.js.map +1 -1
- package/package.json +24 -3
package/dist/dev-setup.js
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
* `hatchkit dev-setup` — opt-in Tailscale-served dev URLs.
|
|
3
3
|
*
|
|
4
4
|
* Goal: every scaffolded project reachable from any Tailscale peer at
|
|
5
|
-
* https://<slug>.local
|
|
5
|
+
* https://<slug>.local.<project-domain>/ with no per-project DNS work,
|
|
6
6
|
* no port juggling, no app-side base/basePath config, and zero
|
|
7
7
|
* collisions between projects.
|
|
8
8
|
*
|
|
9
9
|
* Architecture (host-wide one-time setup):
|
|
10
10
|
*
|
|
11
|
-
* phone ──HTTPS──▶ <slug>.local
|
|
12
|
-
* │ DNS
|
|
11
|
+
* phone ──HTTPS──▶ <slug>.local.<project-domain>:443
|
|
12
|
+
* │ DNS A → laptop's 100.x tailnet IP
|
|
13
13
|
* ▼
|
|
14
14
|
* tailscale serve --tcp=443 (raw TCP passthrough)
|
|
15
15
|
* ▼
|
|
@@ -29,9 +29,9 @@ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node
|
|
|
29
29
|
import { createServer } from "node:net";
|
|
30
30
|
import { homedir } from "node:os";
|
|
31
31
|
import { join } from "node:path";
|
|
32
|
-
import { CADDYFILE_PATH, DEV_CONFIG_DIR,
|
|
32
|
+
import { CADDYFILE_PATH, DEV_CONFIG_DIR, LOCAL_DEV_DOMAIN, LOCAL_DEV_DOMAIN_WILDCARD, MANAGED_MARKER, PROJECTS_DIR, isLocalDevActive, localDevDomainFromProjectDomain, localDevUrl, normaliseLocalDevDomain, projectFragmentPath, readCaddyLocalDevDomain, readCaddyPort, removeProjectFragment, tailscaleIdentity, tailscaleServeTcpTarget, writeProjectFragment, } from "@hatchkit/dev-shared";
|
|
33
33
|
import { exec, execOk } from "./utils/exec.js";
|
|
34
|
-
export { CADDYFILE_PATH, DEV_CONFIG_DIR, LOCAL_DEV_DOMAIN, LOCAL_DEV_DOMAIN_WILDCARD, MANAGED_MARKER, PROJECTS_DIR, isLocalDevActive, readCaddyPort, tailscaleIdentity, tailscaleServeTcpTarget, };
|
|
34
|
+
export { CADDYFILE_PATH, DEV_CONFIG_DIR, LOCAL_DEV_DOMAIN, LOCAL_DEV_DOMAIN_WILDCARD, MANAGED_MARKER, PROJECTS_DIR, isLocalDevActive, readCaddyLocalDevDomain, readCaddyPort, tailscaleIdentity, tailscaleServeTcpTarget, };
|
|
35
35
|
export const CADDY_LOG_PATH = join(DEV_CONFIG_DIR, "caddy.log");
|
|
36
36
|
export const CADDY_ERR_LOG_PATH = join(DEV_CONFIG_DIR, "caddy.err.log");
|
|
37
37
|
export const CADDY_WRAPPER_PATH = join(DEV_CONFIG_DIR, "caddy-wrapper.sh");
|
|
@@ -50,7 +50,9 @@ const CADDY_PORT_BUMP_LIMIT = 50;
|
|
|
50
50
|
// ---------------------------------------------------------------------------
|
|
51
51
|
// Caddyfile + launchd plist contents
|
|
52
52
|
// ---------------------------------------------------------------------------
|
|
53
|
-
export function caddyfileContents(caddyPort) {
|
|
53
|
+
export function caddyfileContents(caddyPort, localDevDomain = LOCAL_DEV_DOMAIN) {
|
|
54
|
+
const domain = normaliseLocalDevDomain(localDevDomain) ?? LOCAL_DEV_DOMAIN;
|
|
55
|
+
const wildcard = `*.${domain}`;
|
|
54
56
|
return `${MANAGED_MARKER}. Edit at your own risk — re-running
|
|
55
57
|
# \`hatchkit dev-setup init\` will overwrite this file (delete the marker
|
|
56
58
|
# line above to keep your edits across re-runs; doctor will then skip
|
|
@@ -66,11 +68,11 @@ export function caddyfileContents(caddyPort) {
|
|
|
66
68
|
auto_https disable_redirects
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
https://${
|
|
71
|
+
https://${wildcard}:${caddyPort} {
|
|
70
72
|
bind 127.0.0.1
|
|
71
73
|
# No explicit \`tls\` directive: the site address already names the
|
|
72
74
|
# wildcard subject, and the global \`acme_dns cloudflare\` block
|
|
73
|
-
# drives DNS-01 issuance. Adding \`tls
|
|
75
|
+
# drives DNS-01 issuance. Adding an explicit wildcard \`tls\` arg here
|
|
74
76
|
# would be parsed as the email-or-keyword form and Caddy 2 rejects
|
|
75
77
|
# it ("single argument must either be 'internal', 'force_automate',
|
|
76
78
|
# or an email address").
|
|
@@ -413,9 +415,36 @@ export async function checkLocalDevHost() {
|
|
|
413
415
|
}
|
|
414
416
|
return out;
|
|
415
417
|
}
|
|
418
|
+
export function localDevDomainSafetyIssue(input) {
|
|
419
|
+
const domain = normaliseLocalDevDomain(input);
|
|
420
|
+
if (!domain)
|
|
421
|
+
return "Local-dev domain is invalid. Use a domain like local.example.com.";
|
|
422
|
+
const labels = domain.split(".");
|
|
423
|
+
if (labels.length < 3 || labels[0] !== "local") {
|
|
424
|
+
const suggested = domain.startsWith("local.") ? domain : `local.${domain}`;
|
|
425
|
+
return (`Unsafe local-dev domain "${domain}". Use a dedicated local-dev subdomain, ` +
|
|
426
|
+
`for example "${suggested}", so Hatchkit manages "*.local..." DNS instead ` +
|
|
427
|
+
"of a production wildcard.");
|
|
428
|
+
}
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
export function isTailscaleIpv4(ip) {
|
|
432
|
+
const parts = ip.split(".").map((p) => Number(p));
|
|
433
|
+
if (parts.length !== 4 || parts.some((p) => !Number.isInteger(p) || p < 0 || p > 255)) {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
const n = ((parts[0] << 24) >>> 0) + (parts[1] << 16) + (parts[2] << 8) + parts[3];
|
|
437
|
+
// Tailscale IPv4s live in CGNAT space 100.64.0.0/10.
|
|
438
|
+
return n >= 0x64400000 && n <= 0x647fffff;
|
|
439
|
+
}
|
|
416
440
|
export async function runDevSetupInit(opts = {}) {
|
|
417
441
|
if (!existsSync(PROJECTS_DIR))
|
|
418
442
|
mkdirSync(PROJECTS_DIR, { recursive: true });
|
|
443
|
+
const localDevDomain = normaliseLocalDevDomain(opts.localDevDomain) ??
|
|
444
|
+
(isLocalDevActive() ? readCaddyLocalDevDomain() : LOCAL_DEV_DOMAIN);
|
|
445
|
+
const domainSafetyIssue = localDevDomainSafetyIssue(localDevDomain);
|
|
446
|
+
if (domainSafetyIssue)
|
|
447
|
+
throw new Error(domainSafetyIssue);
|
|
419
448
|
let caddyPort = opts.force ? null : readCaddyPort();
|
|
420
449
|
if (caddyPort === null) {
|
|
421
450
|
caddyPort = await pickFreeCaddyPort();
|
|
@@ -428,7 +457,7 @@ export async function runDevSetupInit(opts = {}) {
|
|
|
428
457
|
// unless --force is set; the user may be running their own dev domain
|
|
429
458
|
// setup we shouldn't trample on first encounter.
|
|
430
459
|
let wroteCaddyfile = false;
|
|
431
|
-
const nextCaddyfile = caddyfileContents(caddyPort);
|
|
460
|
+
const nextCaddyfile = caddyfileContents(caddyPort, localDevDomain);
|
|
432
461
|
if (existsSync(CADDYFILE_PATH)) {
|
|
433
462
|
const existing = readFileSync(CADDYFILE_PATH, "utf-8");
|
|
434
463
|
if (!existing.includes(MANAGED_MARKER) && !opts.force) {
|
|
@@ -483,7 +512,8 @@ export async function runDevSetupInit(opts = {}) {
|
|
|
483
512
|
wroteWrapper = true;
|
|
484
513
|
}
|
|
485
514
|
const nextPlist = launchdPlistContentsWrapped(CADDY_WRAPPER_PATH);
|
|
486
|
-
if (!existsSync(LAUNCHD_PLIST_PATH) ||
|
|
515
|
+
if (!existsSync(LAUNCHD_PLIST_PATH) ||
|
|
516
|
+
readFileSync(LAUNCHD_PLIST_PATH, "utf-8") !== nextPlist) {
|
|
487
517
|
writeFileSync(LAUNCHD_PLIST_PATH, nextPlist);
|
|
488
518
|
wrotePlist = true;
|
|
489
519
|
}
|
|
@@ -501,7 +531,8 @@ export async function runDevSetupInit(opts = {}) {
|
|
|
501
531
|
notes.push(`For keychain-backed storage: \`security add-generic-password -s ${DEFAULT_CADDY_KEYCHAIN_SERVICE} -a ${DEFAULT_CADDY_KEYCHAIN_ACCOUNT} -w '<token>' -U\` then re-run \`dev-setup init\`.`);
|
|
502
532
|
}
|
|
503
533
|
const nextPlist = launchdPlistContents(caddyBinPath, token ?? null);
|
|
504
|
-
if (!existsSync(LAUNCHD_PLIST_PATH) ||
|
|
534
|
+
if (!existsSync(LAUNCHD_PLIST_PATH) ||
|
|
535
|
+
readFileSync(LAUNCHD_PLIST_PATH, "utf-8") !== nextPlist) {
|
|
505
536
|
writeFileSync(LAUNCHD_PLIST_PATH, nextPlist);
|
|
506
537
|
wrotePlist = true;
|
|
507
538
|
}
|
|
@@ -528,7 +559,7 @@ export async function runDevSetupInit(opts = {}) {
|
|
|
528
559
|
registeredServe = true;
|
|
529
560
|
}
|
|
530
561
|
}
|
|
531
|
-
// DNS: ensure
|
|
562
|
+
// DNS: ensure the local-dev wildcard → laptop's tailnet IP (A record,
|
|
532
563
|
// DNS-only). Without this every project URL hits whatever the parent
|
|
533
564
|
// zone's wildcard says (typically the Coolify host IP, proxied — which
|
|
534
565
|
// doesn't reach the tailnet) and phone requests die at the TLS
|
|
@@ -536,8 +567,9 @@ export async function runDevSetupInit(opts = {}) {
|
|
|
536
567
|
// but only when each peer has Tailscale's resolver in front of its
|
|
537
568
|
// public DNS — fragile on iOS, where stub resolvers cache the
|
|
538
569
|
// intermediate NXDOMAIN. Direct A record is the bulletproof shape.
|
|
539
|
-
const dnsRecord = await ensureLocalDevDnsRecord(opts, notes);
|
|
570
|
+
const dnsRecord = await ensureLocalDevDnsRecord({ ...opts, localDevDomain }, notes);
|
|
540
571
|
return {
|
|
572
|
+
localDevDomain,
|
|
541
573
|
caddyPort,
|
|
542
574
|
wroteCaddyfile,
|
|
543
575
|
wrotePlist,
|
|
@@ -548,11 +580,13 @@ export async function runDevSetupInit(opts = {}) {
|
|
|
548
580
|
notes,
|
|
549
581
|
};
|
|
550
582
|
}
|
|
551
|
-
/** Upsert the
|
|
583
|
+
/** Upsert the local-dev wildcard A record to the laptop's current
|
|
552
584
|
* tailnet IP. Idempotent. Returns the action taken (or a reason it
|
|
553
585
|
* was skipped). All failures are non-fatal — they only nudge the
|
|
554
586
|
* user toward the manual command. */
|
|
555
587
|
async function ensureLocalDevDnsRecord(opts, notes) {
|
|
588
|
+
const localDevDomain = normaliseLocalDevDomain(opts.localDevDomain) ?? LOCAL_DEV_DOMAIN;
|
|
589
|
+
const wildcard = `*.${localDevDomain}`;
|
|
556
590
|
const identity = await tailscaleIdentity();
|
|
557
591
|
if (!identity) {
|
|
558
592
|
notes.push("DNS record skipped: tailscale daemon offline (no IP to point the record at).");
|
|
@@ -577,14 +611,27 @@ async function ensureLocalDevDnsRecord(opts, notes) {
|
|
|
577
611
|
try {
|
|
578
612
|
const { CloudflareApi } = await import("./utils/cloudflare-api.js");
|
|
579
613
|
const cf = new CloudflareApi({ token });
|
|
580
|
-
const zone = await resolveZoneForName(cf,
|
|
614
|
+
const zone = await resolveZoneForName(cf, wildcard);
|
|
581
615
|
if (!zone) {
|
|
582
|
-
notes.push(`DNS record skipped: no Cloudflare zone found for ${
|
|
616
|
+
notes.push(`DNS record skipped: no Cloudflare zone found for ${localDevDomain}. The token may lack Zone:Zone:Read scope.`);
|
|
583
617
|
return "failed";
|
|
584
618
|
}
|
|
619
|
+
const existing = await findLocalDevWildcardRecords(cf, zone.id, wildcard);
|
|
620
|
+
const conflict = existing.find((r) => r.type !== "A" || !isTailscaleIpv4(r.content));
|
|
621
|
+
if (conflict) {
|
|
622
|
+
notes.push(`DNS record skipped: ${formatRecord(conflict)} already exists. Hatchkit will not overwrite non-Tailscale wildcard DNS; choose --domain local.<your-domain> or change the record manually.`);
|
|
623
|
+
return "skipped-existing";
|
|
624
|
+
}
|
|
625
|
+
if (existing.length > 1) {
|
|
626
|
+
notes.push(`DNS record skipped: ${wildcard} has multiple local-dev-looking records. Clean them up manually before re-running.`);
|
|
627
|
+
return "skipped-existing";
|
|
628
|
+
}
|
|
629
|
+
const current = existing[0];
|
|
630
|
+
if (current && current.content === identity.ip && !current.proxied)
|
|
631
|
+
return "unchanged";
|
|
585
632
|
const upsert = await cf.upsertRecord(zone.id, {
|
|
586
633
|
type: "A",
|
|
587
|
-
name:
|
|
634
|
+
name: wildcard,
|
|
588
635
|
content: identity.ip,
|
|
589
636
|
proxied: false,
|
|
590
637
|
ttl: 60,
|
|
@@ -600,10 +647,21 @@ async function ensureLocalDevDnsRecord(opts, notes) {
|
|
|
600
647
|
return "failed";
|
|
601
648
|
}
|
|
602
649
|
}
|
|
650
|
+
async function findLocalDevWildcardRecords(cf, zoneId, wildcard) {
|
|
651
|
+
const [a, aaaa, cname] = await Promise.all([
|
|
652
|
+
cf.findRecordsByName(zoneId, wildcard, "A"),
|
|
653
|
+
cf.findRecordsByName(zoneId, wildcard, "AAAA"),
|
|
654
|
+
cf.findRecordsByName(zoneId, wildcard, "CNAME"),
|
|
655
|
+
]);
|
|
656
|
+
return [...a, ...aaaa, ...cname];
|
|
657
|
+
}
|
|
658
|
+
function formatRecord(record) {
|
|
659
|
+
return `${record.type} ${record.name} -> ${record.content}${record.proxied ? " (proxied)" : ""}`;
|
|
660
|
+
}
|
|
603
661
|
/** Walk the candidate label chain looking for a Cloudflare zone we can
|
|
604
|
-
* manage. For `*.local.
|
|
662
|
+
* manage. For `*.local.example.com` this tries `local.example.com`
|
|
605
663
|
* first (in case the user has it delegated as its own zone), then
|
|
606
|
-
* `
|
|
664
|
+
* `example.com`, then `com` (which won't be ours but completes the
|
|
607
665
|
* chain symmetrically). Returns the first hit. */
|
|
608
666
|
async function resolveZoneForName(cf, name) {
|
|
609
667
|
const labels = name.replace(/^\*\./, "").split(".");
|
|
@@ -615,41 +673,76 @@ async function resolveZoneForName(cf, name) {
|
|
|
615
673
|
}
|
|
616
674
|
return null;
|
|
617
675
|
}
|
|
618
|
-
/** Probe the live
|
|
676
|
+
/** Probe the live local-dev wildcard Cloudflare record. Returns
|
|
619
677
|
* null when there's no token to check with — that's a configuration
|
|
620
678
|
* state, not a failure. Otherwise reports drift between the record
|
|
621
679
|
* and the laptop's current tailnet IP. */
|
|
622
680
|
async function checkLocalDevDnsRecord(currentIp) {
|
|
681
|
+
const localDevDomain = readCaddyLocalDevDomain();
|
|
682
|
+
const domainSafetyIssue = localDevDomainSafetyIssue(localDevDomain);
|
|
683
|
+
if (domainSafetyIssue) {
|
|
684
|
+
return {
|
|
685
|
+
name: "Local-dev / DNS domain",
|
|
686
|
+
status: "fail",
|
|
687
|
+
detail: domainSafetyIssue,
|
|
688
|
+
hint: [
|
|
689
|
+
"Re-run `hatchkit dev-setup init --domain local.<your-domain>`.",
|
|
690
|
+
"This prevents Hatchkit from managing a production wildcard such as *.example.com.",
|
|
691
|
+
],
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
const wildcard = `*.${localDevDomain}`;
|
|
623
695
|
const token = (await readCloudflareTokenFromConfig()) ?? (await readCloudflareTokenFromKeychain());
|
|
624
696
|
if (!token)
|
|
625
697
|
return null;
|
|
626
698
|
try {
|
|
627
699
|
const { CloudflareApi } = await import("./utils/cloudflare-api.js");
|
|
628
700
|
const cf = new CloudflareApi({ token });
|
|
629
|
-
const zone = await resolveZoneForName(cf,
|
|
701
|
+
const zone = await resolveZoneForName(cf, wildcard);
|
|
630
702
|
if (!zone) {
|
|
631
703
|
return {
|
|
632
704
|
name: `Local-dev / DNS A record`,
|
|
633
705
|
status: "fail",
|
|
634
|
-
detail: `no Cloudflare zone found for ${
|
|
706
|
+
detail: `no Cloudflare zone found for ${localDevDomain}`,
|
|
635
707
|
hint: [
|
|
636
708
|
"Token may lack Zone:Zone:Read on the parent zone, or the zone isn't on Cloudflare.",
|
|
637
709
|
"Add the record manually if you're using a different DNS provider:",
|
|
638
|
-
`
|
|
710
|
+
` ${wildcard} A ${currentIp} (DNS-only / unproxied, TTL 60)`,
|
|
639
711
|
],
|
|
640
712
|
};
|
|
641
713
|
}
|
|
642
|
-
const
|
|
643
|
-
|
|
714
|
+
const existing = await findLocalDevWildcardRecords(cf, zone.id, wildcard);
|
|
715
|
+
const conflict = existing.find((r) => r.type !== "A" || !isTailscaleIpv4(r.content));
|
|
716
|
+
if (conflict) {
|
|
717
|
+
return {
|
|
718
|
+
name: "Local-dev / DNS A record",
|
|
719
|
+
status: "fail",
|
|
720
|
+
detail: `existing non-local-dev wildcard record: ${formatRecord(conflict)}`,
|
|
721
|
+
hint: [
|
|
722
|
+
"Hatchkit will not overwrite this record automatically.",
|
|
723
|
+
"Use a dedicated local-dev suffix such as local.<your-domain>, or change DNS manually.",
|
|
724
|
+
],
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
if (existing.length > 1) {
|
|
644
728
|
return {
|
|
645
729
|
name: "Local-dev / DNS A record",
|
|
646
730
|
status: "fail",
|
|
647
|
-
detail:
|
|
731
|
+
detail: `${wildcard} has multiple A/AAAA/CNAME records`,
|
|
648
732
|
hint: [
|
|
649
|
-
"
|
|
733
|
+
"Clean up duplicate wildcard records manually, then re-run `hatchkit dev-setup init`.",
|
|
650
734
|
],
|
|
651
735
|
};
|
|
652
736
|
}
|
|
737
|
+
const record = existing[0];
|
|
738
|
+
if (!record) {
|
|
739
|
+
return {
|
|
740
|
+
name: "Local-dev / DNS A record",
|
|
741
|
+
status: "fail",
|
|
742
|
+
detail: `no A record for ${wildcard} in zone ${zone.name}`,
|
|
743
|
+
hint: ["Run `hatchkit dev-setup init` to create it automatically."],
|
|
744
|
+
};
|
|
745
|
+
}
|
|
653
746
|
if (record.content !== currentIp) {
|
|
654
747
|
return {
|
|
655
748
|
name: "Local-dev / DNS A record",
|
|
@@ -674,7 +767,7 @@ async function checkLocalDevDnsRecord(currentIp) {
|
|
|
674
767
|
return {
|
|
675
768
|
name: "Local-dev / DNS A record",
|
|
676
769
|
status: "ok",
|
|
677
|
-
detail: `${
|
|
770
|
+
detail: `${wildcard} → ${currentIp} (DNS-only)`,
|
|
678
771
|
};
|
|
679
772
|
}
|
|
680
773
|
catch (err) {
|
|
@@ -747,10 +840,27 @@ async function pluginVersionRange() {
|
|
|
747
840
|
* safe to call from scaffold (first run) AND from `dev-setup enable`
|
|
748
841
|
* on an already-wired project. */
|
|
749
842
|
export async function enableProjectLocalDev(input) {
|
|
750
|
-
const
|
|
843
|
+
const manifest = await (async () => {
|
|
844
|
+
try {
|
|
845
|
+
const { readManifest } = await import("./scaffold/manifest.js");
|
|
846
|
+
return readManifest(input.projectDir);
|
|
847
|
+
}
|
|
848
|
+
catch {
|
|
849
|
+
return null;
|
|
850
|
+
}
|
|
851
|
+
})();
|
|
852
|
+
const localDevDomain = normaliseLocalDevDomain(input.localDevDomain) ??
|
|
853
|
+
normaliseLocalDevDomain(manifest?.localDev?.domain) ??
|
|
854
|
+
localDevDomainFromProjectDomain(manifest?.domain) ??
|
|
855
|
+
LOCAL_DEV_DOMAIN;
|
|
856
|
+
const domainSafetyIssue = localDevDomainSafetyIssue(localDevDomain);
|
|
857
|
+
if (domainSafetyIssue)
|
|
858
|
+
throw new Error(domainSafetyIssue);
|
|
859
|
+
const wroteFragment = writeProjectFragment(input.slug, input.devPort, localDevDomain);
|
|
751
860
|
const docsPath = join(input.projectDir, "docs", "dev-setup.md");
|
|
752
861
|
const docsContent = renderDevSetupDocs({
|
|
753
862
|
slug: input.slug,
|
|
863
|
+
localDevDomain,
|
|
754
864
|
tailscale: await tailscaleIdentity(),
|
|
755
865
|
});
|
|
756
866
|
let wroteDocs = false;
|
|
@@ -954,8 +1064,10 @@ function patchPluginPackageJsonDep(projectDir, versionRange, framework) {
|
|
|
954
1064
|
return "added";
|
|
955
1065
|
}
|
|
956
1066
|
export function renderDevSetupDocs(input) {
|
|
1067
|
+
const localDevDomain = normaliseLocalDevDomain(input.localDevDomain) ?? LOCAL_DEV_DOMAIN;
|
|
1068
|
+
const wildcard = `*.${localDevDomain}`;
|
|
957
1069
|
const tailnetHostname = input.tailscale?.fullName ?? "<your-machine>.<tailnet>.ts.net";
|
|
958
|
-
const url =
|
|
1070
|
+
const url = localDevUrl(input.slug, localDevDomain);
|
|
959
1071
|
return `# Dev URL setup (\`${url}\`)
|
|
960
1072
|
|
|
961
1073
|
This project ships with the **hatchkit local-dev** integration: when you run
|
|
@@ -973,20 +1085,25 @@ framework \`base\` / \`basePath\` config.
|
|
|
973
1085
|
|
|
974
1086
|
## One-time host setup
|
|
975
1087
|
|
|
976
|
-
Do this **once per machine**, not per project.
|
|
977
|
-
|
|
1088
|
+
Do this **once per machine**, not per project. New hatchkit projects opt in by
|
|
1089
|
+
default, so after the host is wired they just work.
|
|
1090
|
+
|
|
1091
|
+
\`\`\`
|
|
1092
|
+
hatchkit dev-setup init --domain ${localDevDomain}
|
|
1093
|
+
\`\`\`
|
|
978
1094
|
|
|
979
1095
|
### 1. Cloudflare DNS — auto-managed
|
|
980
1096
|
|
|
981
1097
|
\`hatchkit dev-setup init\` creates a DNS-only A record:
|
|
982
1098
|
|
|
983
1099
|
\`\`\`
|
|
984
|
-
|
|
1100
|
+
${wildcard} A <your-tailnet-ip> (DNS-only, TTL 60)
|
|
985
1101
|
\`\`\`
|
|
986
1102
|
|
|
987
1103
|
It uses your hatchkit DNS token (or the \`caddy-dev/cloudflare-acme\`
|
|
988
1104
|
keychain entry as a fallback) — the same token Caddy already needs for
|
|
989
|
-
DNS-01 ACME. \`Zone:DNS:Edit\` + \`Zone:Zone:Read\`
|
|
1105
|
+
DNS-01 ACME. Required permissions: \`Zone:DNS:Edit\` + \`Zone:Zone:Read\`
|
|
1106
|
+
on the parent zone.
|
|
990
1107
|
|
|
991
1108
|
**Why a direct A record instead of a CNAME to ${tailnetHostname}?**
|
|
992
1109
|
A CNAME to a \`.ts.net\` name only resolves when each peer has
|
|
@@ -999,7 +1116,7 @@ else gets a useless 100.x address (intended).
|
|
|
999
1116
|
If you're using a non-Cloudflare DNS provider, add the record yourself:
|
|
1000
1117
|
|
|
1001
1118
|
\`\`\`
|
|
1002
|
-
|
|
1119
|
+
${wildcard} A <your-tailnet-ip> (DNS-only)
|
|
1003
1120
|
\`\`\`
|
|
1004
1121
|
|
|
1005
1122
|
### 2. Cloudflare API token
|
|
@@ -1013,8 +1130,16 @@ hatchkit config add dns
|
|
|
1013
1130
|
\`\`\`
|
|
1014
1131
|
|
|
1015
1132
|
Permissions: \`Zone:DNS:Edit\` + \`Zone:Zone:Read\` scoped to
|
|
1016
|
-
|
|
1017
|
-
|
|
1133
|
+
\`${localDevDomain}\`. For the cleanest setup, keep the token in Keychain:
|
|
1134
|
+
|
|
1135
|
+
\`\`\`
|
|
1136
|
+
security add-generic-password -s caddy-dev -a cloudflare-acme -w '<token>' -U
|
|
1137
|
+
\`\`\`
|
|
1138
|
+
|
|
1139
|
+
When that keychain entry exists, \`dev-setup init\` writes a tiny Caddy
|
|
1140
|
+
wrapper that reads the token at startup, so the launchd plist never stores
|
|
1141
|
+
the token in plaintext. Without the keychain entry, hatchkit falls back to
|
|
1142
|
+
embedding the DNS token from \`hatchkit config add dns\` in the plist.
|
|
1018
1143
|
|
|
1019
1144
|
### 3. Caddy with the Cloudflare DNS plugin
|
|
1020
1145
|
|
|
@@ -1037,10 +1162,11 @@ xcaddy build --with github.com/caddy-dns/cloudflare
|
|
|
1037
1162
|
hatchkit dev-setup init
|
|
1038
1163
|
\`\`\`
|
|
1039
1164
|
|
|
1040
|
-
This writes \`~/.config/dev/Caddyfile\`, a launchd
|
|
1041
|
-
on a free port (default 9443, auto-bumps if taken),
|
|
1042
|
-
|
|
1043
|
-
|
|
1165
|
+
This writes \`~/.config/dev/Caddyfile\`, writes/loads a launchd job that runs
|
|
1166
|
+
Caddy on a free port (default 9443, auto-bumps if taken), registers
|
|
1167
|
+
\`tailscale serve --tcp=443 → localhost:<caddyPort>\`, and upserts the
|
|
1168
|
+
\`${wildcard}\` DNS-only A record when Cloudflare credentials are
|
|
1169
|
+
available. Idempotent — safe to re-run.
|
|
1044
1170
|
|
|
1045
1171
|
### 5. Verify
|
|
1046
1172
|
|
|
@@ -1048,13 +1174,14 @@ Idempotent — safe to re-run.
|
|
|
1048
1174
|
hatchkit doctor
|
|
1049
1175
|
\`\`\`
|
|
1050
1176
|
|
|
1051
|
-
Look for the **Local-dev** rows.
|
|
1177
|
+
Look for the **Local-dev** rows. They should be green:
|
|
1052
1178
|
|
|
1053
1179
|
- Tailscale daemon
|
|
1054
1180
|
- Caddy installed
|
|
1055
1181
|
- Caddy cloudflare plugin
|
|
1056
|
-
- Cloudflare API token in plist
|
|
1182
|
+
- Cloudflare ACME token in keychain, or Cloudflare API token in plist
|
|
1057
1183
|
- Caddy launchd job
|
|
1184
|
+
- DNS A record
|
|
1058
1185
|
- Tailscale serve bridge
|
|
1059
1186
|
|
|
1060
1187
|
## Per-project bits
|
|
@@ -1077,6 +1204,35 @@ hatchkit dev plugin:
|
|
|
1077
1204
|
\`HATCHKIT_LOCAL_DEV=0\` in the environment disables the plugin entirely;
|
|
1078
1205
|
the dev server falls back to its default banner.
|
|
1079
1206
|
|
|
1207
|
+
## Mobile/devices
|
|
1208
|
+
|
|
1209
|
+
For browser testing on a phone or tablet, install Tailscale on the device,
|
|
1210
|
+
sign in to the same tailnet, and open:
|
|
1211
|
+
|
|
1212
|
+
\`\`\`
|
|
1213
|
+
${url}
|
|
1214
|
+
\`\`\`
|
|
1215
|
+
|
|
1216
|
+
For the native Capacitor loop, the scaffolded \`pnpm dev:ios\` and
|
|
1217
|
+
\`pnpm dev:android\` scripts run the WebView against \`CAP_DEV_URL\`.
|
|
1218
|
+
The iOS script targets the Simulator; the Android script targets an emulator
|
|
1219
|
+
or attached device. Simulators/emulators use local host routes automatically.
|
|
1220
|
+
Android physical devices auto-pick this Tailscale URL when \`.hatchkit.json\`
|
|
1221
|
+
has \`localDev.slug\`; otherwise Android devices can use either:
|
|
1222
|
+
|
|
1223
|
+
\`\`\`
|
|
1224
|
+
LAN_IP=<your-lan-ip> pnpm dev:android
|
|
1225
|
+
\`\`\`
|
|
1226
|
+
|
|
1227
|
+
or, when the device is on Tailscale and this host setup is green:
|
|
1228
|
+
|
|
1229
|
+
\`\`\`
|
|
1230
|
+
CAP_DEV_URL=${url} pnpm dev:android
|
|
1231
|
+
\`\`\`
|
|
1232
|
+
|
|
1233
|
+
For a real iPhone, set \`CAP_DEV_URL=${url}\`, run \`npx cap sync ios\`,
|
|
1234
|
+
then launch from Xcode via \`npx cap open ios\`.
|
|
1235
|
+
|
|
1080
1236
|
## Cleanup
|
|
1081
1237
|
|
|
1082
1238
|
If you tear down this project:
|
|
@@ -1104,6 +1260,32 @@ export async function runDevSetupCli(args) {
|
|
|
1104
1260
|
}
|
|
1105
1261
|
if (sub === "init") {
|
|
1106
1262
|
const force = args.includes("--force");
|
|
1263
|
+
const domainFlagIdx = args.findIndex((a) => a === "--domain" || a.startsWith("--domain="));
|
|
1264
|
+
const rawDomain = domainFlagIdx === -1
|
|
1265
|
+
? undefined
|
|
1266
|
+
: args[domainFlagIdx].includes("=")
|
|
1267
|
+
? args[domainFlagIdx].slice("--domain=".length)
|
|
1268
|
+
: args[domainFlagIdx + 1];
|
|
1269
|
+
let localDevDomain = rawDomain ? normaliseLocalDevDomain(rawDomain) : undefined;
|
|
1270
|
+
if (rawDomain && !localDevDomain) {
|
|
1271
|
+
console.log("Usage: --domain <local-dev-domain> (for example: local.example.com)");
|
|
1272
|
+
process.exit(1);
|
|
1273
|
+
}
|
|
1274
|
+
if (!localDevDomain) {
|
|
1275
|
+
const { resolve } = await import("node:path");
|
|
1276
|
+
const { readManifest } = await import("./scaffold/manifest.js");
|
|
1277
|
+
const manifest = readManifest(resolve("."));
|
|
1278
|
+
localDevDomain =
|
|
1279
|
+
normaliseLocalDevDomain(manifest?.localDev?.domain) ??
|
|
1280
|
+
localDevDomainFromProjectDomain(manifest?.domain) ??
|
|
1281
|
+
undefined;
|
|
1282
|
+
}
|
|
1283
|
+
const domainSafetyIssue = localDevDomainSafetyIssue(localDevDomain ?? LOCAL_DEV_DOMAIN);
|
|
1284
|
+
if (domainSafetyIssue) {
|
|
1285
|
+
const chalk = (await import("chalk")).default;
|
|
1286
|
+
console.log(chalk.red(` ${domainSafetyIssue}`));
|
|
1287
|
+
process.exit(1);
|
|
1288
|
+
}
|
|
1107
1289
|
// --caddy-token-keychain <service>:<account> → wrapper mode with custom pair
|
|
1108
1290
|
// --no-caddy-token-keychain → force inline-env mode
|
|
1109
1291
|
// (default) → auto-detect default pair
|
|
@@ -1125,9 +1307,10 @@ export async function runDevSetupCli(args) {
|
|
|
1125
1307
|
caddyTokenKeychain = { service, account };
|
|
1126
1308
|
}
|
|
1127
1309
|
}
|
|
1128
|
-
const result = await runDevSetupInit({ force, caddyTokenKeychain });
|
|
1310
|
+
const result = await runDevSetupInit({ force, caddyTokenKeychain, localDevDomain });
|
|
1129
1311
|
const chalk = (await import("chalk")).default;
|
|
1130
1312
|
console.log(chalk.bold("\n hatchkit dev-setup init\n"));
|
|
1313
|
+
console.log(` Local-dev domain: ${chalk.cyan(result.localDevDomain)}`);
|
|
1131
1314
|
console.log(` Caddy port: ${chalk.cyan(result.caddyPort)}`);
|
|
1132
1315
|
console.log(` Caddyfile: ${result.wroteCaddyfile ? chalk.green("wrote") : chalk.dim("unchanged")} ${chalk.dim(CADDYFILE_PATH)}`);
|
|
1133
1316
|
if (result.wroteWrapper) {
|
|
@@ -1141,10 +1324,12 @@ export async function runDevSetupCli(args) {
|
|
|
1141
1324
|
? chalk.green(result.dnsRecord)
|
|
1142
1325
|
: result.dnsRecord === "unchanged"
|
|
1143
1326
|
? chalk.dim("unchanged")
|
|
1144
|
-
: result.dnsRecord === "
|
|
1145
|
-
? chalk.
|
|
1146
|
-
:
|
|
1147
|
-
|
|
1327
|
+
: result.dnsRecord === "skipped-existing"
|
|
1328
|
+
? chalk.yellow("skipped (existing wildcard)")
|
|
1329
|
+
: result.dnsRecord === "failed"
|
|
1330
|
+
? chalk.red("failed")
|
|
1331
|
+
: chalk.dim("skipped (no CF token)");
|
|
1332
|
+
console.log(` DNS A record: ${dnsLabel} ${chalk.dim(`*.${result.localDevDomain}`)}`);
|
|
1148
1333
|
}
|
|
1149
1334
|
if (result.notes.length > 0) {
|
|
1150
1335
|
console.log(chalk.bold("\n Notes:"));
|
|
@@ -1162,13 +1347,17 @@ export async function runDevSetupCli(args) {
|
|
|
1162
1347
|
}
|
|
1163
1348
|
const chalk = (await import("chalk")).default;
|
|
1164
1349
|
for (const r of checks) {
|
|
1165
|
-
const icon = r.status === "ok"
|
|
1350
|
+
const icon = r.status === "ok"
|
|
1351
|
+
? chalk.green("✓")
|
|
1352
|
+
: r.status === "fail"
|
|
1353
|
+
? chalk.red("✗")
|
|
1354
|
+
: chalk.dim("·");
|
|
1166
1355
|
console.log(` ${icon} ${r.name}${r.detail ? chalk.dim(` — ${r.detail}`) : ""}`);
|
|
1167
1356
|
}
|
|
1168
1357
|
return;
|
|
1169
1358
|
}
|
|
1170
1359
|
console.log("Usage: hatchkit dev-setup <init|status|enable|disable> [flags]");
|
|
1171
|
-
console.log("\n init
|
|
1360
|
+
console.log("\n init [--domain] Auto-write ~/.config/dev/Caddyfile, launchd plist, register tailscale TCP bridge.");
|
|
1172
1361
|
console.log(" status Run the same checks doctor runs, but only the Local-dev rows.");
|
|
1173
1362
|
console.log(" enable [--slug] Wire the project in cwd for Tailscale dev URLs (writes Caddy fragment,");
|
|
1174
1363
|
console.log(" docs/dev-setup.md, patches next.config, adds plugin dep).");
|
|
@@ -1176,7 +1365,7 @@ export async function runDevSetupCli(args) {
|
|
|
1176
1365
|
}
|
|
1177
1366
|
async function runDevSetupEnableCli(args) {
|
|
1178
1367
|
const chalk = (await import("chalk")).default;
|
|
1179
|
-
const { resolve
|
|
1368
|
+
const { resolve } = await import("node:path");
|
|
1180
1369
|
const { readManifest, writeManifest } = await import("./scaffold/manifest.js");
|
|
1181
1370
|
const { sanitiseSlug } = await import("@hatchkit/dev-shared");
|
|
1182
1371
|
const { input, confirm: askConfirm } = await import("@inquirer/prompts");
|
|
@@ -1198,6 +1387,12 @@ async function runDevSetupEnableCli(args) {
|
|
|
1198
1387
|
: args[projectDirFlagIdx].includes("=")
|
|
1199
1388
|
? args[projectDirFlagIdx].slice("--project-dir=".length)
|
|
1200
1389
|
: args[projectDirFlagIdx + 1];
|
|
1390
|
+
const domainFlagIdx = args.findIndex((a) => a === "--domain" || a.startsWith("--domain="));
|
|
1391
|
+
const rawLocalDevDomain = domainFlagIdx === -1
|
|
1392
|
+
? undefined
|
|
1393
|
+
: args[domainFlagIdx].includes("=")
|
|
1394
|
+
? args[domainFlagIdx].slice("--domain=".length)
|
|
1395
|
+
: args[domainFlagIdx + 1];
|
|
1201
1396
|
const projectDir = projectDirFlag ? resolve(projectDirFlag) : resolve(".");
|
|
1202
1397
|
const manifest = readManifest(projectDir);
|
|
1203
1398
|
if (!manifest) {
|
|
@@ -1205,12 +1400,25 @@ async function runDevSetupEnableCli(args) {
|
|
|
1205
1400
|
console.log(chalk.dim(` Run from a hatchkit-managed project root, or pass --project-dir <path>.`));
|
|
1206
1401
|
process.exit(1);
|
|
1207
1402
|
}
|
|
1403
|
+
const localDevDomain = normaliseLocalDevDomain(rawLocalDevDomain) ??
|
|
1404
|
+
normaliseLocalDevDomain(manifest.localDev?.domain) ??
|
|
1405
|
+
localDevDomainFromProjectDomain(manifest.domain) ??
|
|
1406
|
+
LOCAL_DEV_DOMAIN;
|
|
1407
|
+
if (rawLocalDevDomain && !normaliseLocalDevDomain(rawLocalDevDomain)) {
|
|
1408
|
+
console.log(chalk.red(` --domain ${rawLocalDevDomain} is not a valid local-dev domain.`));
|
|
1409
|
+
process.exit(1);
|
|
1410
|
+
}
|
|
1411
|
+
const domainSafetyIssue = localDevDomainSafetyIssue(localDevDomain);
|
|
1412
|
+
if (domainSafetyIssue) {
|
|
1413
|
+
console.log(chalk.red(` ${domainSafetyIssue}`));
|
|
1414
|
+
process.exit(1);
|
|
1415
|
+
}
|
|
1208
1416
|
// Slug: flag → manifest.localDev → manifest.name → prompt.
|
|
1209
1417
|
let slug = slugFlag ? sanitiseSlug(slugFlag) : manifest.localDev?.slug;
|
|
1210
1418
|
if (!slug) {
|
|
1211
1419
|
const defaultSlug = sanitiseSlug(manifest.name);
|
|
1212
1420
|
slug = await input({
|
|
1213
|
-
message:
|
|
1421
|
+
message: `Slug for this project (${localDevUrl("<slug>", localDevDomain)}):`,
|
|
1214
1422
|
default: defaultSlug,
|
|
1215
1423
|
validate: (v) => {
|
|
1216
1424
|
const s = sanitiseSlug(v);
|
|
@@ -1237,8 +1445,9 @@ async function runDevSetupEnableCli(args) {
|
|
|
1237
1445
|
}
|
|
1238
1446
|
console.log(chalk.bold(`\n Enabling local-dev for ${chalk.cyan(manifest.name)}\n`));
|
|
1239
1447
|
console.log(` Slug: ${chalk.cyan(slug)}`);
|
|
1448
|
+
console.log(` Domain: ${chalk.cyan(localDevDomain)}`);
|
|
1240
1449
|
console.log(` Dev port: ${chalk.cyan(devPort)}`);
|
|
1241
|
-
console.log(` URL: ${chalk.cyan(
|
|
1450
|
+
console.log(` URL: ${chalk.cyan(localDevUrl(slug, localDevDomain))}`);
|
|
1242
1451
|
const skipConfirm = args.includes("--yes") || args.includes("-y");
|
|
1243
1452
|
if (!skipConfirm) {
|
|
1244
1453
|
const ok = await askConfirm({ message: "Proceed?", default: true });
|
|
@@ -1247,11 +1456,11 @@ async function runDevSetupEnableCli(args) {
|
|
|
1247
1456
|
return;
|
|
1248
1457
|
}
|
|
1249
1458
|
}
|
|
1250
|
-
const result = await enableProjectLocalDev({ projectDir, slug, devPort });
|
|
1459
|
+
const result = await enableProjectLocalDev({ projectDir, slug, localDevDomain, devPort });
|
|
1251
1460
|
// Persist the slug in the manifest so subsequent runs (plugin, doctor,
|
|
1252
1461
|
// future `dev-setup disable`) all converge on the same identity.
|
|
1253
|
-
if (manifest.localDev?.slug !== slug) {
|
|
1254
|
-
const updated = { ...manifest, localDev: { slug } };
|
|
1462
|
+
if (manifest.localDev?.slug !== slug || manifest.localDev?.domain !== localDevDomain) {
|
|
1463
|
+
const updated = { ...manifest, localDev: { slug, domain: localDevDomain } };
|
|
1255
1464
|
writeManifest(projectDir, updated);
|
|
1256
1465
|
}
|
|
1257
1466
|
console.log(`\n Framework: ${chalk.cyan(result.framework)}`);
|
|
@@ -1272,14 +1481,14 @@ async function runDevSetupEnableCli(args) {
|
|
|
1272
1481
|
console.log(chalk.dim(" // …"));
|
|
1273
1482
|
console.log(chalk.dim(" plugins: ["));
|
|
1274
1483
|
console.log(chalk.dim(" // …your existing plugins"));
|
|
1275
|
-
console.log(chalk.dim(` localDev({ slug: "${slug}" }),`));
|
|
1484
|
+
console.log(chalk.dim(` localDev({ slug: "${slug}", localDevDomain: "${localDevDomain}" }),`));
|
|
1276
1485
|
console.log(chalk.dim(" ],"));
|
|
1277
|
-
console.log(chalk.dim(
|
|
1486
|
+
console.log(chalk.dim(` server: { allowedHosts: [".${localDevDomain}", ".ts.net", ".local"] },`));
|
|
1278
1487
|
}
|
|
1279
1488
|
if (result.patchedConfig === "unsupported-shape") {
|
|
1280
1489
|
console.log(chalk.bold("\n Next config wiring (CJS / non-standard shape):"));
|
|
1281
1490
|
console.log(chalk.dim(' const { withLocalDev } = require("@hatchkit/dev-plugin-next");'));
|
|
1282
|
-
console.log(chalk.dim(` module.exports = withLocalDev(nextConfig, { slug: "${slug}" });`));
|
|
1491
|
+
console.log(chalk.dim(` module.exports = withLocalDev(nextConfig, { slug: "${slug}", localDevDomain: "${localDevDomain}" });`));
|
|
1283
1492
|
}
|
|
1284
1493
|
console.log(chalk.dim(`\n Verify with: \`hatchkit doctor\` (or \`hatchkit dev-setup status\`).`));
|
|
1285
1494
|
}
|