@stacksjs/buddy 0.70.88 → 0.70.90

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 (143) hide show
  1. package/dist/cli.d.ts +1 -0
  2. package/dist/cli.js +165 -0
  3. package/dist/commands/about.d.ts +2 -0
  4. package/dist/commands/about.js +35 -0
  5. package/dist/commands/add.d.ts +2 -0
  6. package/dist/commands/add.js +33 -0
  7. package/dist/commands/auth.d.ts +2 -0
  8. package/dist/commands/auth.js +68 -0
  9. package/dist/commands/build.d.ts +2 -0
  10. package/dist/commands/build.js +153 -0
  11. package/dist/commands/cd.d.ts +20 -0
  12. package/dist/commands/cd.js +39 -0
  13. package/dist/commands/changelog.d.ts +2 -0
  14. package/dist/commands/changelog.js +32 -0
  15. package/dist/commands/clean.d.ts +2 -0
  16. package/dist/commands/clean.js +37 -0
  17. package/dist/commands/cloud.d.ts +2 -0
  18. package/dist/commands/cloud.js +503 -0
  19. package/dist/commands/commit.d.ts +2 -0
  20. package/dist/commands/commit.js +16 -0
  21. package/dist/commands/completion.d.ts +2 -0
  22. package/dist/commands/completion.js +143 -0
  23. package/dist/commands/config-migrate.d.ts +26 -0
  24. package/dist/commands/config-migrate.js +94 -0
  25. package/dist/commands/configure.d.ts +2 -0
  26. package/dist/commands/configure.js +48 -0
  27. package/dist/commands/create.d.ts +2 -0
  28. package/dist/commands/create.js +141 -0
  29. package/dist/commands/deploy.d.ts +107 -0
  30. package/dist/commands/deploy.js +1439 -0
  31. package/dist/commands/dev.d.ts +10 -0
  32. package/dist/commands/dev.js +907 -0
  33. package/dist/commands/dns.d.ts +2 -0
  34. package/dist/commands/dns.js +102 -0
  35. package/dist/commands/doctor.d.ts +2 -0
  36. package/dist/commands/doctor.js +285 -0
  37. package/dist/commands/domains.d.ts +2 -0
  38. package/dist/commands/domains.js +159 -0
  39. package/dist/commands/email.d.ts +2 -0
  40. package/dist/commands/email.js +524 -0
  41. package/dist/commands/env.d.ts +2 -0
  42. package/dist/commands/env.js +246 -0
  43. package/dist/commands/extension.d.ts +8 -0
  44. package/dist/commands/extension.js +78 -0
  45. package/dist/commands/features.d.ts +180 -0
  46. package/dist/commands/features.js +422 -0
  47. package/dist/commands/fresh.d.ts +2 -0
  48. package/dist/commands/fresh.js +39 -0
  49. package/dist/commands/generate.d.ts +2 -0
  50. package/dist/commands/generate.js +104 -0
  51. package/dist/commands/http.d.ts +2 -0
  52. package/dist/commands/http.js +33 -0
  53. package/dist/commands/index.d.ts +53 -0
  54. package/dist/commands/index.js +53 -0
  55. package/dist/commands/install.d.ts +2 -0
  56. package/dist/commands/install.js +22 -0
  57. package/dist/commands/key.d.ts +2 -0
  58. package/dist/commands/key.js +23 -0
  59. package/dist/commands/lint.d.ts +2 -0
  60. package/dist/commands/lint.js +46 -0
  61. package/dist/commands/list.d.ts +2 -0
  62. package/dist/commands/list.js +108 -0
  63. package/dist/commands/mail.d.ts +2 -0
  64. package/dist/commands/mail.js +693 -0
  65. package/dist/commands/maintenance.d.ts +2 -0
  66. package/dist/commands/maintenance.js +141 -0
  67. package/dist/commands/make.d.ts +2 -0
  68. package/dist/commands/make.js +375 -0
  69. package/dist/commands/migrate-project.d.ts +2 -0
  70. package/dist/commands/migrate-project.js +49 -0
  71. package/dist/commands/migrate.d.ts +4 -0
  72. package/dist/commands/migrate.js +455 -0
  73. package/dist/commands/outdated.d.ts +2 -0
  74. package/dist/commands/outdated.js +19 -0
  75. package/dist/commands/package.d.ts +2 -0
  76. package/dist/commands/package.js +17 -0
  77. package/dist/commands/phone.d.ts +2 -0
  78. package/dist/commands/phone.js +188 -0
  79. package/dist/commands/ports.d.ts +2 -0
  80. package/dist/commands/ports.js +118 -0
  81. package/dist/commands/prepublish.d.ts +2 -0
  82. package/dist/commands/prepublish.js +17 -0
  83. package/dist/commands/projects.d.ts +2 -0
  84. package/dist/commands/projects.js +29 -0
  85. package/dist/commands/publish.d.ts +2 -0
  86. package/dist/commands/publish.js +172 -0
  87. package/dist/commands/queue.d.ts +2 -0
  88. package/dist/commands/queue.js +248 -0
  89. package/dist/commands/release.d.ts +2 -0
  90. package/dist/commands/release.js +30 -0
  91. package/dist/commands/route.d.ts +2 -0
  92. package/dist/commands/route.js +21 -0
  93. package/dist/commands/saas.d.ts +2 -0
  94. package/dist/commands/saas.js +25 -0
  95. package/dist/commands/schedule.d.ts +2 -0
  96. package/dist/commands/schedule.js +61 -0
  97. package/dist/commands/search.d.ts +2 -0
  98. package/dist/commands/search.js +84 -0
  99. package/dist/commands/seed.d.ts +2 -0
  100. package/dist/commands/seed.js +71 -0
  101. package/dist/commands/serve.d.ts +25 -0
  102. package/dist/commands/serve.js +182 -0
  103. package/dist/commands/setup.d.ts +12 -0
  104. package/dist/commands/setup.js +215 -0
  105. package/dist/commands/share.d.ts +2 -0
  106. package/dist/commands/share.js +214 -0
  107. package/dist/commands/sms.d.ts +2 -0
  108. package/dist/commands/sms.js +328 -0
  109. package/dist/commands/stacks.d.ts +2 -0
  110. package/dist/commands/stacks.js +69 -0
  111. package/dist/commands/telemetry.d.ts +2 -0
  112. package/dist/commands/telemetry.js +74 -0
  113. package/dist/commands/test.d.ts +2 -0
  114. package/dist/commands/test.js +130 -0
  115. package/dist/commands/tinker.d.ts +2 -0
  116. package/dist/commands/tinker.js +37 -0
  117. package/dist/commands/types.d.ts +2 -0
  118. package/dist/commands/types.js +18 -0
  119. package/dist/commands/upgrade.d.ts +2 -0
  120. package/dist/commands/upgrade.js +98 -0
  121. package/dist/commands/version.d.ts +2 -0
  122. package/dist/commands/version.js +16 -0
  123. package/dist/config.d.ts +43 -0
  124. package/dist/config.js +223 -0
  125. package/dist/custom-cli.d.ts +1 -0
  126. package/dist/custom-cli.js +23 -0
  127. package/dist/index.d.ts +1 -0
  128. package/dist/index.js +1 -0
  129. package/dist/lazy-commands.d.ts +61 -0
  130. package/dist/lazy-commands.js +188 -0
  131. package/dist/migrators/index.d.ts +9 -0
  132. package/dist/migrators/index.js +62 -0
  133. package/dist/migrators/laravel/index.d.ts +2 -0
  134. package/dist/migrators/laravel/index.js +148 -0
  135. package/dist/migrators/laravel/migrations.d.ts +52 -0
  136. package/dist/migrators/laravel/migrations.js +232 -0
  137. package/dist/migrators/laravel/models.d.ts +34 -0
  138. package/dist/migrators/laravel/models.js +134 -0
  139. package/dist/migrators/rails/index.d.ts +2 -0
  140. package/dist/migrators/rails/index.js +11 -0
  141. package/dist/migrators/types.d.ts +44 -0
  142. package/dist/migrators/types.js +0 -0
  143. package/package.json +2 -2
@@ -0,0 +1,26 @@
1
+ import type { CLI } from '@stacksjs/types';
2
+ /*.ts` files.
3
+ * Honors `--dry-run` (prints diffs without writing) and `--only=<id>`
4
+ * (restrict to a single codemod).
5
+ */
6
+ export declare function runConfigMigrations(opts: {
7
+ dryRun?: boolean
8
+ only?: string
9
+ }): Promise<{ changed: string[], skipped: string[] }>;
10
+ export declare function configMigrate(buddy: CLI): void;
11
+ /**
12
+ * A single config codemod — describes a search-and-replace operation
13
+ * against a user config file under `~/config/<file>.ts`. Each codemod
14
+ * is targeted at one specific Stacks-version transition (e.g. "0.65 →
15
+ * 0.66 renamed `cache.driver` to `cache.default`"). The replacement is
16
+ * a string transform — we deliberately avoid AST manipulation here
17
+ * because the input is user TS, comments matter, and we'd rather
18
+ * produce a textual diff than gamble on `ts-morph` reformatting code.
19
+ */
20
+ export declare interface ConfigCodemod {
21
+ id: string
22
+ description: string
23
+ files: string[]
24
+ shouldApply(content: string): boolean
25
+ apply(content: string): string
26
+ }
@@ -0,0 +1,94 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import process from "node:process";
3
+ import { italic, log } from "@stacksjs/cli";
4
+ import { projectPath } from "@stacksjs/path";
5
+ const CODEMODS = [
6
+ {
7
+ id: "0.65-cache-driver-rename",
8
+ description: "Rename `cache.driver` to `cache.default` (introduced in 0.65).",
9
+ files: ["cache.ts"],
10
+ shouldApply: (c) => /\bdriver\s*:/.test(c) && !c.includes("default:"),
11
+ apply: (c) => c.replace(/(\bcache\b[\s\S]*?)\bdriver(\s*:)/m, "$1default$2")
12
+ },
13
+ {
14
+ id: "0.68-search-engine-name",
15
+ description: "Rename `searchEngine.driver` to `searchEngine.default` (matches other configs).",
16
+ files: ["search-engine.ts"],
17
+ shouldApply: (c) => /\bdriver\s*:/.test(c),
18
+ apply: (c) => c.replace(/\bdriver(\s*:)/g, "default$1")
19
+ },
20
+ {
21
+ id: "0.70-ports-string-to-number",
22
+ description: 'Convert string port values like `"3000"` to numbers (TypeScript types now require number).',
23
+ files: ["ports.ts"],
24
+ shouldApply: (c) => /:\s*['"]\d+['"]/.test(c),
25
+ apply: (c) => c.replace(/:\s*['"](\d+)['"]/g, ": $1")
26
+ }
27
+ ];
28
+ export async function runConfigMigrations(opts) {
29
+ const changed = [], skipped = [], codemods = opts.only ? CODEMODS.filter((m) => m.id === opts.only) : CODEMODS;
30
+ if (codemods.length === 0) {
31
+ log.warn(`No matching codemods. Available: ${CODEMODS.map((m) => m.id).join(", ")}`);
32
+ return { changed, skipped };
33
+ }
34
+ for (const cm of codemods) {
35
+ log.info(`Codemod ${italic(cm.id)} \u2014 ${cm.description}`);
36
+ for (const rel of cm.files) {
37
+ const full = projectPath(`config/${rel}`);
38
+ if (!existsSync(full)) {
39
+ skipped.push(`${full} (missing)`);
40
+ continue;
41
+ }
42
+ const before = readFileSync(full, "utf-8");
43
+ if (!cm.shouldApply(before)) {
44
+ skipped.push(`${full} (no changes)`);
45
+ continue;
46
+ }
47
+ const after = cm.apply(before);
48
+ if (after === before) {
49
+ skipped.push(`${full} (no changes)`);
50
+ continue;
51
+ }
52
+ if (opts.dryRun) {
53
+ log.info(`[dry-run] would update ${full}`);
54
+ printDiff(before, after);
55
+ } else {
56
+ writeFileSync(full, after);
57
+ log.success(`updated ${full}`);
58
+ }
59
+ changed.push(full);
60
+ }
61
+ }
62
+ return { changed, skipped };
63
+ }
64
+ function printDiff(before, after) {
65
+ const beforeLines = before.split(`
66
+ `), afterLines = after.split(`
67
+ `), max = Math.max(beforeLines.length, afterLines.length);
68
+ for (let i = 0;i < max; i++) {
69
+ const b = beforeLines[i], a = afterLines[i];
70
+ if (b === a)
71
+ continue;
72
+ if (b !== void 0)
73
+ process.stdout.write(` - ${b}
74
+ `);
75
+ if (a !== void 0)
76
+ process.stdout.write(` + ${a}
77
+ `);
78
+ }
79
+ process.stdout.write(`
80
+ `);
81
+ }
82
+ export function configMigrate(buddy) {
83
+ buddy.command("config:migrate", "Apply config-shape codemods for the latest Stacks version").option("--dry-run", "Preview changes without writing", { default: !1 }).option("--only [id]", "Run a single codemod by id", { default: "" }).action(async (options) => {
84
+ log.info("Running `buddy config:migrate` ...");
85
+ const { changed, skipped } = await runConfigMigrations({
86
+ dryRun: Boolean(options.dryRun),
87
+ only: options.only?.length ? options.only : void 0
88
+ });
89
+ log.info(`Changed: ${changed.length}, Skipped: ${skipped.length}`);
90
+ if (skipped.length > 0)
91
+ for (const s of skipped)
92
+ log.debug(` skipped: ${s}`);
93
+ });
94
+ }
@@ -0,0 +1,2 @@
1
+ import type { CLI } from '@stacksjs/types';
2
+ export declare function configure(buddy: CLI): void;
@@ -0,0 +1,48 @@
1
+ import process from "node:process";
2
+ import { log, onUnknownSubcommand, outro, runCommand } from "@stacksjs/cli";
3
+ import { path as p } from "@stacksjs/path";
4
+ import { ExitCode } from "@stacksjs/types";
5
+ export function configure(buddy) {
6
+ const descriptions = {
7
+ configure: "Configure options",
8
+ aws: "Configure the AWS connection",
9
+ project: "Target a specific project",
10
+ profile: "The AWS profile to use",
11
+ verbose: "Enable verbose output"
12
+ };
13
+ buddy.command("configure", descriptions.configure).option("--aws", descriptions.aws, { default: !1 }).option("-p, --project [project]", descriptions.project, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (options) => {
14
+ log.debug("Running `buddy configure` ...", options);
15
+ if (options?.aws) {
16
+ await configureAws(options);
17
+ process.exit(ExitCode.Success);
18
+ }
19
+ log.info("Not implemented yet. Please use the --aws flag to configure AWS.");
20
+ process.exit(ExitCode.Success);
21
+ });
22
+ buddy.command("configure:aws", descriptions.aws).option("-p, --project [project]", descriptions.project, { default: !1 }).option("--profile", descriptions.profile, {
23
+ default: process.env.AWS_PROFILE
24
+ }).option("--verbose", descriptions.verbose, { default: !1 }).option("--access-key-id", "The AWS access key").option("--secret-access-key", "The AWS secret access key").option("--region", "The AWS region").option("--output", "The AWS output format").option("--quiet", "Suppress output").action(async (options) => {
25
+ log.debug("Running `buddy configure:aws` ...", options);
26
+ await configureAws(options);
27
+ process.exit(ExitCode.Success);
28
+ });
29
+ onUnknownSubcommand(buddy, "configure");
30
+ }
31
+ async function configureAws(options) {
32
+ const startTime = performance.now(), awsAccessKeyId = options?.accessKeyId ?? process.env.AWS_ACCESS_KEY_ID, awsSecretAccessKey = options?.secretAccessKey ?? process.env.AWS_SECRET_ACCESS_KEY, defaultRegion = "us-east-1", defaultOutputFormat = options?.output ?? "json", profile = process.env.AWS_PROFILE ?? options?.profile, command = profile ? `aws configure --profile ${profile}` : "aws configure", input = `${awsAccessKeyId}
33
+ ${awsSecretAccessKey}
34
+ ${defaultRegion}
35
+ ${defaultOutputFormat}
36
+ `, result = await runCommand(command, {
37
+ cwd: p.projectPath(),
38
+ stdin: "pipe",
39
+ input
40
+ });
41
+ if (result.isErr) {
42
+ await outro("While running the cloud command, there was an issue", { startTime, useSeconds: !0 }, result.error);
43
+ process.exit(ExitCode.FatalError);
44
+ }
45
+ if (options?.quiet)
46
+ return;
47
+ await outro("Exited", { startTime, useSeconds: !0 });
48
+ }
@@ -0,0 +1,2 @@
1
+ import type { CLI } from '@stacksjs/types';
2
+ export declare function create(buddy: CLI): void;
@@ -0,0 +1,141 @@
1
+ import { chmodSync } from "node:fs";
2
+ import process from "node:process";
3
+ import { runAction } from "@stacksjs/actions";
4
+ import { bold, cyan, dim, intro, log, onUnknownSubcommand, runCommand } from "@stacksjs/cli";
5
+ import { Action } from "@stacksjs/enums";
6
+ import { resolve } from "@stacksjs/path";
7
+ import { isFolder } from "@stacksjs/storage";
8
+ import { ExitCode } from "@stacksjs/types";
9
+ import { useOnline } from "@stacksjs/utils";
10
+ import { uninstallAllFeatures } from "./features";
11
+ import { ensurePantryDependencies, ensurePantryInstalled } from "./setup";
12
+ export function create(buddy) {
13
+ const descriptions = {
14
+ name: "The name of the project",
15
+ command: "Create a new Stacks project",
16
+ ui: "Are you building a UI?",
17
+ components: "Are you building UI components?",
18
+ webComponents: "Automagically built optimized custom elements/web components?",
19
+ vue: "Automagically built a Vue component library?",
20
+ views: "How about views?",
21
+ functions: "Are you developing functions/composables?",
22
+ api: "Are you building an API?",
23
+ database: "Do you need a database?",
24
+ notifications: "Do you need notifications? e.g. email, SMS, push or chat notifications",
25
+ cache: "Do you need caching?",
26
+ email: "Do you need email?",
27
+ project: "Target a specific project",
28
+ minimal: "Skip optional feature bundles (cms, commerce, dashboard, marketing, monitoring, realtime, queue) \u2014 bare-bones API/SPA starter that can re-add them later via `./buddy <feature>:install`.",
29
+ verbose: "Enable verbose output"
30
+ };
31
+ buddy.command("new [name]", descriptions.command).alias("create [name]").option("-n, --name [name]", descriptions.name, { default: !1 }).option("-u, --ui", descriptions.ui, { default: !0 }).option("-c, --components", descriptions.components, { default: !0 }).option("-w, --web-components", descriptions.webComponents, { default: !0 }).option("-v, --vue", descriptions.vue, { default: !0 }).option("-p, --views", descriptions.views, { default: !0 }).option("-f, --functions", descriptions.functions, { default: !0 }).option("-a, --api", descriptions.api, { default: !0 }).option("-d, --database", descriptions.database, { default: !0 }).option("-ca, --cache", descriptions.cache, { default: !1 }).option("-e, --email", descriptions.email, { default: !1 }).option("-p, --project [project]", descriptions.project, { default: !1 }).option("-m, --minimal", descriptions.minimal, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (name, options) => {
32
+ log.debug("Running `buddy new <name>` ...", options);
33
+ const startTime = await intro("buddy new");
34
+ name = name ?? options.name;
35
+ const path = resolve(process.cwd(), name);
36
+ isFolderCheck(path);
37
+ onlineCheck();
38
+ const result = await download(name, path, options);
39
+ if (result.isErr) {
40
+ log.error(result.error);
41
+ process.exit(ExitCode.FatalError);
42
+ }
43
+ await ensureEnv(path, options);
44
+ ensureExecutableScripts(path);
45
+ await install(path, options);
46
+ if (options.minimal)
47
+ await stripFeatures(path);
48
+ if (startTime) {
49
+ const time = performance.now() - startTime;
50
+ log.success(dim(`[${time.toFixed(2)}ms] Completed`));
51
+ }
52
+ log.info(bold("Welcome to the Stacks Framework! \u269B\uFE0F"));
53
+ log.info("To learn more, visit https://stacksjs.com");
54
+ process.exit(ExitCode.Success);
55
+ });
56
+ onUnknownSubcommand(buddy, "new");
57
+ }
58
+ function isFolderCheck(path) {
59
+ if (isFolder(path)) {
60
+ log.error(`Path ${path} already exists`);
61
+ process.exit(ExitCode.FatalError);
62
+ }
63
+ }
64
+ function onlineCheck() {
65
+ if (!useOnline()) {
66
+ log.info("It appears you are disconnected from the internet.");
67
+ log.info("The Stacks setup requires a brief internet connection for setup.");
68
+ log.info("For instance, it installs your dependencies from.");
69
+ process.exit(ExitCode.FatalError);
70
+ }
71
+ }
72
+ async function download(name, path, _options) {
73
+ log.info("Setting up your stack.");
74
+ try {
75
+ const { downloadTemplate } = await import("@stacksjs/gitit");
76
+ await downloadTemplate("stacks", { dir: name });
77
+ log.success(`Successfully scaffolded your project at ${cyan(path)}`);
78
+ return { isErr: !1 };
79
+ } catch (error) {
80
+ return { isErr: !0, error: error instanceof Error ? error.message : String(error) };
81
+ }
82
+ }
83
+ function ensureExecutableScripts(path) {
84
+ for (const script of ["buddy", "bootstrap"])
85
+ try {
86
+ chmodSync(resolve(path, script), 493);
87
+ } catch {}
88
+ }
89
+ async function ensureEnv(path, _options) {
90
+ log.info("Ensuring your environment is ready...");
91
+ await ensurePantryInstalled();
92
+ await ensurePantryDependencies(path);
93
+ log.success("Environment is ready");
94
+ }
95
+ async function install(path, options) {
96
+ log.info("Installing & setting up Stacks");
97
+ log.info("Running bun install...");
98
+ let result = await runCommand("bun install", { ...options, cwd: path });
99
+ if (result?.isErr) {
100
+ log.error(result.error);
101
+ process.exit(ExitCode.FatalError);
102
+ }
103
+ log.info("Copying .env.example \u2192 .env");
104
+ result = await runCommand("cp .env.example .env", { ...options, cwd: path });
105
+ if (result?.isErr) {
106
+ log.error(result.error);
107
+ process.exit(ExitCode.FatalError);
108
+ }
109
+ log.info("Removing template-encrypted env files...");
110
+ const { rm } = await import("node:fs/promises");
111
+ for (const stale of [".env.development", ".env.staging", ".env.production", ".env.keys"])
112
+ await rm(`${path}/${stale}`, { force: !0 });
113
+ log.info("Generating application key...");
114
+ const keyResult = await runAction(Action.KeyGenerate, { ...options, cwd: path });
115
+ if (keyResult.isErr) {
116
+ log.error(keyResult.error);
117
+ process.exit(ExitCode.FatalError);
118
+ }
119
+ log.info("Initializing git repository...");
120
+ result = await runCommand("git init", { ...options, cwd: path });
121
+ if (result.isErr) {
122
+ log.error(result.error);
123
+ process.exit(ExitCode.FatalError);
124
+ }
125
+ log.success("Installed & set-up \uD83D\uDE80");
126
+ }
127
+ async function stripFeatures(path) {
128
+ log.info("Stripping optional feature bundles (--minimal)...");
129
+ const results = await uninstallAllFeatures({ root: path });
130
+ let strippedAny = !1;
131
+ for (const { feature, configOutcome, filesRemoved } of results)
132
+ if (configOutcome === "flipped" || filesRemoved.length > 0) {
133
+ strippedAny = !0;
134
+ const fileSummary = filesRemoved.length > 0 ? ` (${filesRemoved.length} path${filesRemoved.length === 1 ? "" : "s"})` : "";
135
+ log.info(` - ${feature}${fileSummary}`);
136
+ }
137
+ if (!strippedAny)
138
+ log.info(" \u2192 no feature scaffolding present; nothing to strip.");
139
+ else
140
+ log.success("Minimal skeleton ready \u2014 run `./buddy <feature>:install` to add features back.");
141
+ }
@@ -0,0 +1,107 @@
1
+ import type { CLI } from '@stacksjs/types';
2
+ /**
3
+ * Resolve (and decrypt) the deploy-target's environment file into a flat
4
+ * key/value map, so its values can be shipped to the server as each site's
5
+ * systemd `.env` content.
6
+ *
7
+ * ts-cloud's `buildSiteDeployScript` treats `site.env` as the COMPLETE
8
+ * content of the deployed `.env` — it doesn't read or merge in anything
9
+ * from the packaged release tarball (ts-cloud is a generic deploy tool; it
10
+ * has no idea `.env.production`/dotenvx encryption exist, that's entirely a
11
+ * Stacks convention). Left unaddressed, every Hetzner site deploys with
12
+ * ONLY whatever's in that site's own `env` override (often nothing at all)
13
+ * — confirmed against a real deploy (stacksjs/status#1 Phase 9): the `main`
14
+ * site (no `env` override) came up logging "loaded 0 variables from .env",
15
+ * and `api` (which only declares `{ HOST, APP_ENV }` to force the loopback
16
+ * bind) came up with just those 2 keys and none of its real production
17
+ * config, failing config validation on the still-`encrypted:...` APP_ENV
18
+ * ciphertext it never had a chance to decrypt (no DOTENV_PRIVATE_KEY_* in
19
+ * that 2-key set).
20
+ *
21
+ * Returns `{}` (not an error) when the file doesn't exist or fails to
22
+ * parse — an app with no `.env.production` yet shouldn't block deploying
23
+ * with whatever `site.env` overrides it does have.
24
+ */
25
+ export declare function resolveDeployEnvValues(environment: 'production' | 'staging' | 'development'): Promise<Record<string, string>>;
26
+ /**
27
+ * Merge the deploy-target's resolved env values underneath each site's own
28
+ * explicit `env` overrides, stripping a general `PORT` when the site
29
+ * declares its own `port` (the generated systemd unit already sets
30
+ * `Environment=PORT=${site.port}` — see buildSiteDeployScript in ts-cloud —
31
+ * so a leftover PORT in the shipped `.env` would otherwise silently win
32
+ * over it once the app's own dotenv loading applies file values on top of
33
+ * the process env).
34
+ */
35
+ export declare function mergeSiteDeployEnv(sites: Record<string, any>, resolvedDeployEnv: Record<string, string>): Record<string, any>;
36
+ /**
37
+ * Make the site model environment-aware. For a non-production environment that
38
+ * declares a `domainPrefix` (staging → `staging`, development → `dev`), every
39
+ * site's public domain becomes `<prefix>.<domain>`, and URL values that point at
40
+ * those hosts (APP_URL, OAuth redirect URLs, redirect targets, …) are rewritten
41
+ * to match — so one config drives prod + staging + dev from their own branches
42
+ * without duplicating site blocks. Only `//<host>` URL occurrences are rewritten;
43
+ * bare `user@host` (e.g. mail identities) is left alone. Production is untouched.
44
+ */
45
+ export declare function applyEnvironmentToSites(sites: Record<string, any>, environment: string, config: any): Record<string, any>;
46
+ /**
47
+ * Loopback-bound server-app sites (e.g. the `api` site: env.HOST=127.0.0.1,
48
+ * reached only through `buddy serve`'s same-origin /api proxy on :3000 —
49
+ * stacksjs/stacks#1950) must NOT have their port opened to the internet.
50
+ * ts-cloud's Hetzner provisioning opens EVERY numeric `site.port` to
51
+ * 0.0.0.0/0 + ::/0 (collectUpstreamPorts → buildHetznerFirewallRules), which
52
+ * would leave only the process bind between the public internet and the full
53
+ * bun-router API. Hand the provision step a copy of the config with those
54
+ * ports stripped — the unmodified config still drives deployAllComputeSites,
55
+ * so the systemd unit (ExecStart, Environment=PORT) is unaffected.
56
+ *
57
+ * Domain-less sites only: a loopback site WITH a domain feeds the rpx
58
+ * gateway's route table (which proxies to 127.0.0.1:port on-box), so its
59
+ * port declaration is left alone.
60
+ */
61
+ export declare function scrubLoopbackSitePortsForFirewall(tsCloudConfig: any): any;
62
+ /**
63
+ * Everything is MERGE-based so a shared mail server keeps every other tenant's
64
+ * domains, keys, users, and forward rules untouched. Best-effort — a hiccup is
65
+ * logged, never fails the release. Returns what the DNS step needs (mail host +
66
+ * DKIM public key), or null when there is nothing to reconcile / it failed.
67
+ */
68
+ export declare function provisionMailTenant(ip: string, logger: typeof log): Promise<MailTenantResult | null>;
69
+ /**
70
+ * Publish the mail DNS for a hosted domain via Porkbun (idempotent delete+create
71
+ * per record): MX → the mail host, SPF authorizing the box IP, the domain's DKIM
72
+ * public key at `mail._domainkey`, and a DMARC policy. Best-effort — logged, not
73
+ * thrown. No-op without Porkbun credentials (the records are printed to add by
74
+ * hand). MX targets the tenant's own `mail.<domain>` when that name already
75
+ * resolves to the box (own-brand mail host; requires the mail cert to cover it
76
+ * as a SAN), and falls back to the shared mail host (`mail.stacksjs.com`)
77
+ * otherwise, where no per-domain mail A record or extra TLS SAN is needed.
78
+ */
79
+ export declare function reconcileMailDns(res: MailTenantResult, ip: string, logger: typeof log): Promise<void>;
80
+ export declare function deploy(buddy: CLI): void;
81
+ /**
82
+ * Use console.log for clean output without timestamps
83
+ * @defaultValue
84
+ * ```ts
85
+ * {
86
+ * info: (...args: any[]) => unknown,
87
+ * success: (...args: any[]) => unknown,
88
+ * warn: (...args: any[]) => unknown,
89
+ * error: (...args: any[]) => unknown,
90
+ * debug: (...args: any[]) => void
91
+ * }
92
+ * ```
93
+ */
94
+ declare const log: {
95
+ info: (...args: any[]) => unknown;
96
+ success: (...args: any[]) => unknown;
97
+ warn: (...args: any[]) => unknown;
98
+ error: (...args: any[]) => unknown;
99
+ debug: (...args: any[]) => void
100
+ };
101
+ /** What a mail-tenant reconcile resolved + provisioned, for the DNS step. */
102
+ export declare interface MailTenantResult {
103
+ domain: string
104
+ mailHost: string
105
+ dkimPubB64?: string
106
+ created: Array<{ address: string, password: string }>
107
+ }