hatchkit 0.2.1 → 0.2.3
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.js +1 -1
- 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.js +1 -1
- package/dist/completion.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +39 -7
- package/dist/config.js.map +1 -1
- package/dist/deploy/terraform.d.ts +18 -0
- package/dist/deploy/terraform.d.ts.map +1 -1
- package/dist/deploy/terraform.js +52 -5
- package/dist/deploy/terraform.js.map +1 -1
- package/dist/dev-setup.d.ts +3 -1
- package/dist/dev-setup.d.ts.map +1 -1
- package/dist/dev-setup.js +104 -4
- package/dist/dev-setup.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +4 -3
- package/dist/doctor.js.map +1 -1
- package/dist/explain.js +1 -1
- package/dist/explain.js.map +1 -1
- package/dist/index.js +265 -42
- package/dist/index.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 +6 -0
- package/dist/provision/index.d.ts.map +1 -1
- package/dist/provision/index.js +114 -9
- 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 +27 -4
- package/dist/provision/openpanel.js.map +1 -1
- package/dist/provision/plausible.d.ts +10 -0
- package/dist/provision/plausible.d.ts.map +1 -1
- package/dist/provision/plausible.js +78 -17
- package/dist/provision/plausible.js.map +1 -1
- 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/scaffold/app.js +2 -2
- package/dist/scaffold/app.js.map +1 -1
- package/dist/scaffold/manifest.d.ts +14 -0
- package/dist/scaffold/manifest.d.ts.map +1 -1
- package/dist/scaffold/manifest.js.map +1 -1
- package/dist/scaffold/server-add.js +3 -1
- package/dist/scaffold/server-add.js.map +1 -1
- package/dist/scaffold/starter-files.d.ts +3 -3
- package/dist/scaffold/starter-files.js +3 -3
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { confirm } from "@inquirer/prompts";
|
|
5
5
|
import chalk from "chalk";
|
|
6
|
-
import { ensureCoolify, ensureGitHub, ensureHetzner, ensureS3, getConfig, getConfigPath, getCoolifyConfig, getGhcrConfig, getMlServices, isFirstRun, reconfigureProvider, resetConfig, runOnboarding, } from "./config.js";
|
|
6
|
+
import { ensureCoolify, ensureDns, ensureGitHub, ensureHetzner, ensureS3, getConfig, getConfigPath, getCoolifyConfig, getGhcrConfig, getMlServices, isFirstRun, reconfigureProvider, resetConfig, runOnboarding, } from "./config.js";
|
|
7
7
|
import { runCoolifySetup } from "./deploy/coolify.js";
|
|
8
8
|
import { setupGitHub } from "./deploy/github.js";
|
|
9
9
|
import { deployMlServices } from "./deploy/gpu.js";
|
|
10
10
|
import { pushProjectKeyToCoolify, pushProjectKeyToGh, rotateProjectKey, setProjectKey, showProjectKey, } from "./deploy/keys.js";
|
|
11
11
|
import { handleCreateFailure, runRollback } from "./deploy/rollback.js";
|
|
12
|
-
import { runTerraform } from "./deploy/terraform.js";
|
|
12
|
+
import { requireCloudflareZoneForTerraform, runTerraform } from "./deploy/terraform.js";
|
|
13
13
|
import { collectProjectConfig } from "./prompts.js";
|
|
14
|
-
import { runProvision, runUnprovision } from "./provision/index.js";
|
|
14
|
+
import { runProvision, runUnprovision, } from "./provision/index.js";
|
|
15
15
|
import { scaffoldApp } from "./scaffold/app.js";
|
|
16
16
|
import { scaffoldInfra } from "./scaffold/infra.js";
|
|
17
|
+
import { readManifest } from "./scaffold/manifest.js";
|
|
17
18
|
import { mlEnvVarName, printMlSummary, resolveMlServices } from "./scaffold/ml-client.js";
|
|
18
19
|
import { runUpdate } from "./scaffold/update.js";
|
|
19
20
|
import { installCancelHandler, isCancelInProgress, uninstallCancelHandler, } from "./utils/cancel-handler.js";
|
|
@@ -438,10 +439,10 @@ function opts(result) {
|
|
|
438
439
|
* dir already lives inside the project (`packages/server`,
|
|
439
440
|
* `apps/web`, etc.). Returns undefined when no manifest is found —
|
|
440
441
|
* callers fall back to "skip s3" with a hint. */
|
|
441
|
-
function inferProjectDir(
|
|
442
|
-
if (!
|
|
442
|
+
function inferProjectDir(startDir) {
|
|
443
|
+
if (!startDir)
|
|
443
444
|
return undefined;
|
|
444
|
-
let cur =
|
|
445
|
+
let cur = startDir;
|
|
445
446
|
for (let i = 0; i < 4; i++) {
|
|
446
447
|
if (existsSync(join(cur, ".hatchkit.json")))
|
|
447
448
|
return cur;
|
|
@@ -452,15 +453,165 @@ function inferProjectDir(serverEnvDir) {
|
|
|
452
453
|
}
|
|
453
454
|
return undefined;
|
|
454
455
|
}
|
|
456
|
+
function manifestBucketEntries(manifest) {
|
|
457
|
+
const buckets = manifest?.s3Buckets;
|
|
458
|
+
if (!buckets)
|
|
459
|
+
return [];
|
|
460
|
+
const out = [];
|
|
461
|
+
for (const [key, value] of Object.entries(buckets)) {
|
|
462
|
+
if (key === "tokenId" || key === "accountId")
|
|
463
|
+
continue;
|
|
464
|
+
if (value && typeof value === "object" && "name" in value) {
|
|
465
|
+
out.push(value);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return out;
|
|
469
|
+
}
|
|
470
|
+
function readIfExists(path) {
|
|
471
|
+
if (!existsSync(path))
|
|
472
|
+
return "";
|
|
473
|
+
try {
|
|
474
|
+
return readFileSync(path, "utf-8");
|
|
475
|
+
}
|
|
476
|
+
catch {
|
|
477
|
+
return "";
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
function readProjectEnvText(projectDir, baseName) {
|
|
481
|
+
const chunks = [];
|
|
482
|
+
if (projectDir) {
|
|
483
|
+
for (const dir of [
|
|
484
|
+
".",
|
|
485
|
+
"packages/server",
|
|
486
|
+
"packages/client",
|
|
487
|
+
"packages/web",
|
|
488
|
+
"apps/server",
|
|
489
|
+
"apps/api",
|
|
490
|
+
"apps/web",
|
|
491
|
+
"apps/client",
|
|
492
|
+
"server",
|
|
493
|
+
"client",
|
|
494
|
+
"web",
|
|
495
|
+
]) {
|
|
496
|
+
const abs = resolve(projectDir, dir);
|
|
497
|
+
chunks.push(readIfExists(join(abs, ".env.production")));
|
|
498
|
+
chunks.push(readIfExists(join(abs, ".env.development")));
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (baseName) {
|
|
502
|
+
const provisionedDir = join(dirname(getConfigPath()), "provisioned");
|
|
503
|
+
if (existsSync(provisionedDir)) {
|
|
504
|
+
for (const file of readdirSync(provisionedDir)) {
|
|
505
|
+
if (file.startsWith(`${baseName}.`) && file.endsWith(".env")) {
|
|
506
|
+
chunks.push(readIfExists(join(provisionedDir, file)));
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return chunks.join("\n");
|
|
512
|
+
}
|
|
513
|
+
function servicesAlreadyAdded(args) {
|
|
514
|
+
const text = readProjectEnvText(args.projectDir, args.baseName);
|
|
515
|
+
const added = new Set();
|
|
516
|
+
if (/(^|\n)(PUBLIC_)?GLITCHTIP_DSN=/m.test(text))
|
|
517
|
+
added.add("glitchtip");
|
|
518
|
+
if (/(^|\n)(PUBLIC_)?OPENPANEL_CLIENT_ID=/m.test(text))
|
|
519
|
+
added.add("openpanel");
|
|
520
|
+
if (/(^|\n)(NEXT_PUBLIC_|PUBLIC_)?PLAUSIBLE_DOMAIN=/m.test(text))
|
|
521
|
+
added.add("plausible");
|
|
522
|
+
if (/(^|\n)RESEND_API_KEY=/m.test(text))
|
|
523
|
+
added.add("resend");
|
|
524
|
+
if (/(^|\n)R2(_[A-Z0-9]+)?_ACCESS_KEY_ID=/m.test(text))
|
|
525
|
+
added.add("s3");
|
|
526
|
+
if (manifestBucketEntries(args.manifest).some((bucket) => bucket.tokenId))
|
|
527
|
+
added.add("s3");
|
|
528
|
+
if (args.manifest?.integrations?.email)
|
|
529
|
+
added.add("email");
|
|
530
|
+
if (args.manifest?.integrations?.searchConsole)
|
|
531
|
+
added.add("search-console");
|
|
532
|
+
return added;
|
|
533
|
+
}
|
|
534
|
+
function servicesImpossibleForProject(manifest) {
|
|
535
|
+
const blocked = new Set();
|
|
536
|
+
if (!manifest)
|
|
537
|
+
return blocked;
|
|
538
|
+
if (!manifest.domain) {
|
|
539
|
+
blocked.add("email");
|
|
540
|
+
blocked.add("search-console");
|
|
541
|
+
}
|
|
542
|
+
if (manifest.surfaces === "server-only")
|
|
543
|
+
blocked.add("plausible");
|
|
544
|
+
if (manifest.surfaces === "client-only") {
|
|
545
|
+
blocked.add("resend");
|
|
546
|
+
blocked.add("s3");
|
|
547
|
+
}
|
|
548
|
+
if (manifestBucketEntries(manifest).length === 0)
|
|
549
|
+
blocked.add("s3");
|
|
550
|
+
return blocked;
|
|
551
|
+
}
|
|
552
|
+
function recordProvisionedEvent(ledger, event) {
|
|
553
|
+
if (event.service === "glitchtip")
|
|
554
|
+
ledger.record({ kind: "glitchtip", project: event.project });
|
|
555
|
+
if (event.service === "openpanel")
|
|
556
|
+
ledger.record({ kind: "openpanel", project: event.project });
|
|
557
|
+
if (event.service === "plausible" && event.created) {
|
|
558
|
+
ledger.record({ kind: "plausible", project: event.project });
|
|
559
|
+
}
|
|
560
|
+
if (event.service === "resend")
|
|
561
|
+
ledger.record({ kind: "resend", client: event.client });
|
|
562
|
+
if (event.service === "s3" && event.minted) {
|
|
563
|
+
ledger.record({
|
|
564
|
+
kind: "r2Token",
|
|
565
|
+
tokenId: event.tokenId,
|
|
566
|
+
accountId: event.accountId,
|
|
567
|
+
audience: "account",
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
if (event.service === "search-console" && event.dnsRecord?.created) {
|
|
571
|
+
ledger.record({
|
|
572
|
+
kind: "cloudflareDnsRecord",
|
|
573
|
+
zoneId: event.dnsRecord.zoneId,
|
|
574
|
+
recordId: event.dnsRecord.id,
|
|
575
|
+
name: event.dnsRecord.name,
|
|
576
|
+
type: event.dnsRecord.type,
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
if (event.service === "email") {
|
|
580
|
+
if (event.destinationCreatedThisRun) {
|
|
581
|
+
ledger.record({
|
|
582
|
+
kind: "cloudflareEmailDestination",
|
|
583
|
+
accountId: event.accountId,
|
|
584
|
+
destinationId: event.destinationId,
|
|
585
|
+
email: event.destinationEmail,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
for (const dns of event.dnsRecords) {
|
|
589
|
+
ledger.record({
|
|
590
|
+
kind: "cloudflareDnsRecord",
|
|
591
|
+
zoneId: event.zoneId,
|
|
592
|
+
recordId: dns.id,
|
|
593
|
+
name: dns.name,
|
|
594
|
+
type: dns.type,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
for (const rule of event.rules) {
|
|
598
|
+
if (!rule.created)
|
|
599
|
+
continue;
|
|
600
|
+
ledger.record({
|
|
601
|
+
kind: "cloudflareEmailRoutingRule",
|
|
602
|
+
zoneId: event.zoneId,
|
|
603
|
+
ruleId: rule.id,
|
|
604
|
+
address: rule.address,
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
455
609
|
async function handleAdd() {
|
|
456
610
|
// Positional args are optional — anything missing is prompted for.
|
|
457
611
|
// hatchkit add (fully interactive)
|
|
458
612
|
// hatchkit add raptor-runner (prompts for services)
|
|
459
613
|
// hatchkit add raptor-runner all
|
|
460
614
|
// hatchkit add raptor-runner glitchtip,resend
|
|
461
|
-
const positional = args.slice(1).filter((a) => !a.startsWith("--"));
|
|
462
|
-
let baseName = positional[0];
|
|
463
|
-
const rawService = positional[1];
|
|
464
615
|
const allServices = [
|
|
465
616
|
"glitchtip",
|
|
466
617
|
"openpanel",
|
|
@@ -470,6 +621,24 @@ async function handleAdd() {
|
|
|
470
621
|
"email",
|
|
471
622
|
"search-console",
|
|
472
623
|
];
|
|
624
|
+
const isServiceExpr = (value) => {
|
|
625
|
+
if (!value)
|
|
626
|
+
return false;
|
|
627
|
+
if (value === "all")
|
|
628
|
+
return true;
|
|
629
|
+
return value
|
|
630
|
+
.split(",")
|
|
631
|
+
.map((s) => s.trim().toLowerCase())
|
|
632
|
+
.every((s) => allServices.includes(s));
|
|
633
|
+
};
|
|
634
|
+
const positional = args.slice(1).filter((a) => !a.startsWith("--"));
|
|
635
|
+
const inferredProjectDir = inferProjectDir(process.cwd());
|
|
636
|
+
const inferredManifest = inferredProjectDir ? readManifest(inferredProjectDir) : null;
|
|
637
|
+
const firstArgIsService = isServiceExpr(positional[0]);
|
|
638
|
+
let baseName = firstArgIsService
|
|
639
|
+
? inferredManifest?.name
|
|
640
|
+
: (positional[0] ?? inferredManifest?.name);
|
|
641
|
+
const rawService = firstArgIsService ? positional[0] : positional[1];
|
|
473
642
|
if (!baseName) {
|
|
474
643
|
const { input } = await import("@inquirer/prompts");
|
|
475
644
|
const { validateProjectName } = await import("./utils/validate.js");
|
|
@@ -478,37 +647,54 @@ async function handleAdd() {
|
|
|
478
647
|
validate: validateProjectName,
|
|
479
648
|
});
|
|
480
649
|
}
|
|
650
|
+
const alreadyAdded = servicesAlreadyAdded({
|
|
651
|
+
baseName,
|
|
652
|
+
projectDir: inferredProjectDir,
|
|
653
|
+
manifest: inferredManifest,
|
|
654
|
+
});
|
|
655
|
+
const impossible = servicesImpossibleForProject(inferredManifest);
|
|
656
|
+
const hiddenServices = new Set([...alreadyAdded, ...impossible]);
|
|
657
|
+
const addableServices = allServices.filter((service) => !hiddenServices.has(service));
|
|
481
658
|
let services;
|
|
482
659
|
if (!rawService) {
|
|
660
|
+
if (addableServices.length === 0) {
|
|
661
|
+
console.log(chalk.green(` Nothing to add — ${baseName} already has every supported service.`));
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
483
664
|
const { multiselect } = await import("./utils/multiselect.js");
|
|
665
|
+
const serviceChoices = [
|
|
666
|
+
{ name: "GlitchTip (error tracking)", value: "glitchtip", checked: false },
|
|
667
|
+
{ name: "OpenPanel (product analytics)", value: "openpanel", checked: false },
|
|
668
|
+
{ name: "Plausible (web analytics)", value: "plausible", checked: false },
|
|
669
|
+
{ name: "Resend (transactional email)", value: "resend", checked: false },
|
|
670
|
+
{
|
|
671
|
+
name: "S3 / R2 (per-bucket scoped credentials from .hatchkit.json)",
|
|
672
|
+
value: "s3",
|
|
673
|
+
checked: false,
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
name: "Email forwarding (Cloudflare Email Routing — MX/SPF/DMARC + rules)",
|
|
677
|
+
value: "email",
|
|
678
|
+
checked: false,
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
name: "Google Search Console (DNS verification + domain property)",
|
|
682
|
+
value: "search-console",
|
|
683
|
+
checked: false,
|
|
684
|
+
},
|
|
685
|
+
];
|
|
484
686
|
services = await multiselect({
|
|
485
687
|
message: "Which services to add?",
|
|
486
|
-
choices:
|
|
487
|
-
{ name: "GlitchTip (error tracking)", value: "glitchtip", checked: true },
|
|
488
|
-
{ name: "OpenPanel (product analytics)", value: "openpanel", checked: true },
|
|
489
|
-
{ name: "Plausible (web analytics)", value: "plausible", checked: false },
|
|
490
|
-
{ name: "Resend (transactional email)", value: "resend", checked: true },
|
|
491
|
-
{
|
|
492
|
-
name: "S3 / R2 (per-bucket scoped credentials from .hatchkit.json)",
|
|
493
|
-
value: "s3",
|
|
494
|
-
checked: false,
|
|
495
|
-
},
|
|
496
|
-
{
|
|
497
|
-
name: "Email forwarding (Cloudflare Email Routing — MX/SPF/DMARC + rules)",
|
|
498
|
-
value: "email",
|
|
499
|
-
checked: false,
|
|
500
|
-
},
|
|
501
|
-
{
|
|
502
|
-
name: "Google Search Console (DNS verification + domain property)",
|
|
503
|
-
value: "search-console",
|
|
504
|
-
checked: false,
|
|
505
|
-
},
|
|
506
|
-
],
|
|
688
|
+
choices: serviceChoices.filter((choice) => addableServices.includes(choice.value)),
|
|
507
689
|
required: true,
|
|
508
690
|
});
|
|
509
691
|
}
|
|
510
692
|
else if (rawService === "all") {
|
|
511
|
-
services =
|
|
693
|
+
services = addableServices;
|
|
694
|
+
if (services.length === 0) {
|
|
695
|
+
console.log(chalk.green(` Nothing to add — ${baseName} already has every supported service.`));
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
512
698
|
}
|
|
513
699
|
else {
|
|
514
700
|
const requested = rawService.split(",").map((s) => s.trim().toLowerCase());
|
|
@@ -518,7 +704,17 @@ async function handleAdd() {
|
|
|
518
704
|
console.log(chalk.dim(` Valid: ${allServices.join(", ")}, or 'all'`));
|
|
519
705
|
process.exit(1);
|
|
520
706
|
}
|
|
521
|
-
|
|
707
|
+
const skipped = requested.filter((service) => hiddenServices.has(service));
|
|
708
|
+
if (skipped.length > 0) {
|
|
709
|
+
console.log(chalk.red(` Refusing to add already-present/unavailable service(s): ${skipped.join(", ")}`));
|
|
710
|
+
console.log(chalk.dim(" Run `hatchkit remove` first if you want Hatchkit to recreate them."));
|
|
711
|
+
process.exit(1);
|
|
712
|
+
}
|
|
713
|
+
services = requested.filter((service) => !hiddenServices.has(service));
|
|
714
|
+
if (services.length === 0) {
|
|
715
|
+
console.log(chalk.green(` Nothing to add — requested service(s) are already present or unavailable.`));
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
522
718
|
}
|
|
523
719
|
// Flag parsing:
|
|
524
720
|
// --no-write → never write; print a cache summary only
|
|
@@ -573,7 +769,16 @@ async function handleAdd() {
|
|
|
573
769
|
: inferProjectDir(needsServer ? resolvePath(serverDirFlag) : undefined),
|
|
574
770
|
};
|
|
575
771
|
}
|
|
576
|
-
|
|
772
|
+
const ledger = RunLedger.resumeOrStart(baseName);
|
|
773
|
+
await runProvision({
|
|
774
|
+
baseName,
|
|
775
|
+
services,
|
|
776
|
+
surfaces,
|
|
777
|
+
enableDevObs,
|
|
778
|
+
failIfExists: true,
|
|
779
|
+
onProvisioned: (event) => recordProvisionedEvent(ledger, event),
|
|
780
|
+
});
|
|
781
|
+
ledger.complete();
|
|
577
782
|
}
|
|
578
783
|
async function handleProvisionS3() {
|
|
579
784
|
// `hatchkit provision s3` — create the public+private bucket pair
|
|
@@ -970,6 +1175,13 @@ async function handleCreate() {
|
|
|
970
1175
|
config.installDeps = false;
|
|
971
1176
|
if (forceNoLocalDev)
|
|
972
1177
|
config.localDev = undefined;
|
|
1178
|
+
// Terraform's Cloudflare stacks can only write records into an
|
|
1179
|
+
// existing zone. Check that before project-specific mutations such as
|
|
1180
|
+
// scaffolding, provider clients, GitHub repos, or infra files.
|
|
1181
|
+
if (config.deploymentMode === "coolify" && config.runDeployment && !config.dryRun) {
|
|
1182
|
+
const dns = await ensureDns();
|
|
1183
|
+
await requireCloudflareZoneForTerraform(config.baseDomain, dns);
|
|
1184
|
+
}
|
|
973
1185
|
// Ensure needed providers are configured (lazy prompting).
|
|
974
1186
|
// Coolify + Hetzner only matter for the coolify deployment mode.
|
|
975
1187
|
// gh-pages skips them entirely (no server, no Docker registry).
|
|
@@ -1114,7 +1326,7 @@ async function handleCreate() {
|
|
|
1114
1326
|
else if (event.service === "openpanel") {
|
|
1115
1327
|
ledger?.record({ kind: "openpanel", project: event.project });
|
|
1116
1328
|
}
|
|
1117
|
-
else if (event.service === "plausible") {
|
|
1329
|
+
else if (event.service === "plausible" && event.created) {
|
|
1118
1330
|
ledger?.record({ kind: "plausible", project: event.project });
|
|
1119
1331
|
}
|
|
1120
1332
|
},
|
|
@@ -2021,9 +2233,13 @@ function printHelp(topic) {
|
|
|
2021
2233
|
without the fragment).
|
|
2022
2234
|
|
|
2023
2235
|
${chalk.bold("DNS:")}
|
|
2024
|
-
${chalk.cyan("dev-setup init")} auto-upserts a DNS-only A record
|
|
2236
|
+
${chalk.cyan("dev-setup init")} auto-upserts a DNS-only A record on a
|
|
2237
|
+
dedicated ${chalk.cyan("local.")} subdomain:
|
|
2025
2238
|
*.local.<your-domain> A <your-tailnet-ip> (DNS-only)
|
|
2026
2239
|
|
|
2240
|
+
Custom ${chalk.cyan("--domain")} values must use that ${chalk.cyan("local.")} prefix so
|
|
2241
|
+
Hatchkit never overwrites a production wildcard such as *.example.com.
|
|
2242
|
+
|
|
2027
2243
|
If Cloudflare credentials are unavailable, add that record manually.
|
|
2028
2244
|
|
|
2029
2245
|
This feature is fully optional: until you run ${chalk.cyan("dev-setup init")},
|
|
@@ -2138,6 +2354,7 @@ function printHelp(topic) {
|
|
|
2138
2354
|
|
|
2139
2355
|
${chalk.bold("Usage:")}
|
|
2140
2356
|
hatchkit add [<project-name>] [<services>] [flags]
|
|
2357
|
+
hatchkit add [<services>] [flags] ${chalk.dim("(inside a project with .hatchkit.json)")}
|
|
2141
2358
|
|
|
2142
2359
|
${chalk.bold("What it does:")}
|
|
2143
2360
|
· GlitchTip / OpenPanel: ${chalk.bold("one project per product")}, events tagged by
|
|
@@ -2155,6 +2372,11 @@ function printHelp(topic) {
|
|
|
2155
2372
|
· A 0600 cache of every value is saved under
|
|
2156
2373
|
${chalk.dim("<config-dir>/provisioned/<project>.*.env")} for recoverability.
|
|
2157
2374
|
${chalk.dim("Secret values never hit stdout.")}
|
|
2375
|
+
· The interactive menu only shows services not already present for the
|
|
2376
|
+
current project, starts with nothing selected, and refuses explicit
|
|
2377
|
+
requests that would recreate known resources.
|
|
2378
|
+
· Before creating provider resources, add runs read-only existence probes
|
|
2379
|
+
for the selected services and stops on conflicts so cleanup stays safe.
|
|
2158
2380
|
|
|
2159
2381
|
${chalk.bold("Surfaces:")}
|
|
2160
2382
|
hatchkit asks which surfaces your project has. Options:
|
|
@@ -2190,6 +2412,7 @@ function printHelp(topic) {
|
|
|
2190
2412
|
|
|
2191
2413
|
${chalk.bold("Examples:")}
|
|
2192
2414
|
hatchkit add
|
|
2415
|
+
hatchkit add search-console
|
|
2193
2416
|
hatchkit add raptor-runner
|
|
2194
2417
|
hatchkit add raptor-runner all --enable-dev-obs
|
|
2195
2418
|
hatchkit add raptor-runner glitchtip,resend --no-write
|
|
@@ -2513,13 +2736,13 @@ function printHelp(topic) {
|
|
|
2513
2736
|
}
|
|
2514
2737
|
if (topic === "assets") {
|
|
2515
2738
|
console.log(`
|
|
2516
|
-
${chalk.bold("hatchkit assets")} — move bytes between local
|
|
2739
|
+
${chalk.bold("hatchkit assets")} — move bytes between local S3 and prod buckets
|
|
2517
2740
|
|
|
2518
2741
|
${chalk.bold("Subcommands:")}
|
|
2519
|
-
assets seed [--from <dir>] Local dir → local
|
|
2742
|
+
assets seed [--from <dir>] Local dir → local S3 bucket.
|
|
2520
2743
|
Defaults to ./seed/assets.
|
|
2521
|
-
assets push [--bucket assets|state] Local
|
|
2522
|
-
assets pull [--bucket assets|state] Prod bucket → local
|
|
2744
|
+
assets push [--bucket assets|state] Local S3 → prod bucket.
|
|
2745
|
+
assets pull [--bucket assets|state] Prod bucket → local S3.
|
|
2523
2746
|
Caution: prod data may include PII.
|
|
2524
2747
|
assets migrate --from-endpoint=URL External S3 → prod bucket.
|
|
2525
2748
|
--from-bucket=NAME The adoption escape hatch — copy
|
|
@@ -2546,7 +2769,7 @@ function printHelp(topic) {
|
|
|
2546
2769
|
the env doesn't carry them (R2's URL-driven assets bucket).
|
|
2547
2770
|
|
|
2548
2771
|
${chalk.bold("Examples:")}
|
|
2549
|
-
hatchkit assets seed # ./seed/assets/ → local
|
|
2772
|
+
hatchkit assets seed # ./seed/assets/ → local S3
|
|
2550
2773
|
hatchkit assets push --dry-run # see what would ship to prod
|
|
2551
2774
|
hatchkit assets push # actually ship it
|
|
2552
2775
|
hatchkit assets migrate --from-endpoint https://nyc3.digitaloceanspaces.com \\
|
|
@@ -2587,7 +2810,7 @@ function printHelp(topic) {
|
|
|
2587
2810
|
update Add features to an already-scaffolded project (run in project dir)
|
|
2588
2811
|
server add Retrofit a server into a client-only project
|
|
2589
2812
|
add Create GlitchTip / OpenPanel / Plausible / Resend clients for an existing project
|
|
2590
|
-
assets Move bytes between local
|
|
2813
|
+
assets Move bytes between local S3 and prod buckets (seed/push/pull/migrate)
|
|
2591
2814
|
remove Delete the -dev/-prod clients created by 'add' (inverse of add)
|
|
2592
2815
|
destroy Roll back everything ${chalk.cyan("hatchkit create")} did for a project
|
|
2593
2816
|
rename-domain Move a scaffolded project to a new domain (rewrites tfvars/env/manifest)
|