@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,246 @@
1
+ import process from "node:process";
2
+ import { log, onUnknownSubcommand } from "@stacksjs/cli";
3
+ import { decryptEnv, encryptEnv, getEnv, getKeypair, rotateKeypair, setEnv } from "@stacksjs/env";
4
+ import { ExitCode } from "@stacksjs/types";
5
+ export function env(buddy) {
6
+ const descriptions = {
7
+ env: "Interact with your environment variables",
8
+ get: "Get an environment variable",
9
+ set: "Set an environment variable",
10
+ encrypt: "Encrypt a value",
11
+ decrypt: "Decrypt a value",
12
+ check: "Check environment configuration and validate setup",
13
+ pretty: "Pretty print the result",
14
+ stdout: "Output the result to stdout",
15
+ keypair: "Generate a keypair",
16
+ fileKeys: "The path to the file containing the keys",
17
+ excludeKey: "The key to exclude from encryption",
18
+ rotate: "Rotate a keypair",
19
+ format: "The format to output the result (json, shell, eval)",
20
+ all: "Get all environment variables",
21
+ file: "The environment file to use",
22
+ project: "Target a specific project",
23
+ verbose: "Enable verbose output"
24
+ };
25
+ buddy.command("env:get [key]", descriptions.get).option("-f, --file [file]", descriptions.file, { default: "" }).option("--format [format]", descriptions.format, { default: "json" }).option("-a, --all", descriptions.all, { default: !1 }).option("-p, --pretty", descriptions.pretty, { default: !1 }).option("-p, --project [project]", descriptions.project, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).example("buddy env:get SECRET").example("buddy env:get SECRET --file .env.production").example("buddy env:get --all --pretty").example("buddy env:get --format shell").example("buddy env:get --format eval").example("buddy env:get --format json").action(async (key, options) => {
26
+ log.debug("Running `buddy env:get` ...", options);
27
+ const result = getEnv(key, {
28
+ file: options.file,
29
+ keysFile: options.fileKeys,
30
+ all: options.all,
31
+ format: options.format,
32
+ prettyPrint: options.pretty
33
+ });
34
+ if (result.success) {
35
+ console.log(result.output);
36
+ process.exit(ExitCode.Success);
37
+ } else {
38
+ console.error(result.error);
39
+ process.exit(ExitCode.FatalError);
40
+ }
41
+ });
42
+ buddy.command("env:set [key] [value]", descriptions.set).option("-f, --file [file]", descriptions.file, { default: "" }).option("-fk, --file-keys [fileKeys]", descriptions.fileKeys, { default: "" }).option("--plain", "Don't encrypt the value", { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).example("buddy env:set SECRET=value").example("buddy env:set SECRET value").example("buddy env:set SECRET value --file .env.production").example("buddy env:set SECRET value --plain").action(async (key, value, options) => {
43
+ log.debug("Running `buddy env:set` ...", options);
44
+ if (!key || !value) {
45
+ console.error("Both key and value are required");
46
+ process.exit(ExitCode.FatalError);
47
+ }
48
+ const result = setEnv(key, value, {
49
+ file: options.file,
50
+ keysFile: options.fileKeys,
51
+ plain: options.plain
52
+ });
53
+ if (result.success) {
54
+ console.log(result.output);
55
+ process.exit(ExitCode.Success);
56
+ } else {
57
+ console.error(result.error);
58
+ process.exit(ExitCode.FatalError);
59
+ }
60
+ });
61
+ buddy.command("env:encrypt [key]", descriptions.encrypt).option("-f, --file [file]", descriptions.file, { default: "" }).option("-fk, --file-keys [fileKeys]", descriptions.fileKeys, { default: "" }).option("-o, --stdout", descriptions.stdout, { default: !1 }).option("-ek, --exclude-key [excludeKey]", descriptions.excludeKey, { default: "" }).example("buddy env:encrypt").example("buddy env:encrypt --file .env.production").example('buddy env:encrypt -k "SECRET_*"').action(async (key, options) => {
62
+ log.debug("Running `buddy env:encrypt` ...", options);
63
+ const result = encryptEnv({
64
+ file: options.file,
65
+ keysFile: options.fileKeys,
66
+ key,
67
+ excludeKey: options.excludeKey,
68
+ stdout: options.stdout
69
+ });
70
+ if (result.success) {
71
+ console.log(result.output);
72
+ process.exit(ExitCode.Success);
73
+ } else {
74
+ console.error(result.error);
75
+ process.exit(ExitCode.FatalError);
76
+ }
77
+ });
78
+ buddy.command("env:decrypt [key]", descriptions.decrypt).option("-f, --file [file]", descriptions.file, { default: "" }).option("-fk, --file-keys [fileKeys]", descriptions.fileKeys, { default: "" }).option("-o, --stdout", descriptions.stdout, { default: !1 }).example("buddy env:decrypt").example("buddy env:decrypt --file .env.production").example('buddy env:decrypt -k "SECRET_*"').action(async (key, options) => {
79
+ log.debug("Running `buddy env:decrypt` ...", options);
80
+ const result = decryptEnv({
81
+ file: options.file,
82
+ keysFile: options.fileKeys,
83
+ key,
84
+ stdout: options.stdout
85
+ });
86
+ if (result.success) {
87
+ console.log(result.output);
88
+ process.exit(ExitCode.Success);
89
+ } else {
90
+ console.error(result.error);
91
+ process.exit(ExitCode.FatalError);
92
+ }
93
+ });
94
+ buddy.command("env:keypair [key]", descriptions.keypair).option("-f, --file [file]", descriptions.file, { default: "" }).option("-fk, --file-keys [fileKeys]", descriptions.fileKeys, { default: "" }).option("--format [format]", descriptions.format, { default: "json" }).example("buddy env:keypair").example("buddy env:keypair --file .env.production").example("buddy env:keypair DOTENV_PRIVATE_KEY").action(async (key, options) => {
95
+ log.debug("Running `buddy env:keypair` ...", options);
96
+ const result = getKeypair(key, {
97
+ file: options.file,
98
+ keysFile: options.fileKeys,
99
+ format: options.format
100
+ });
101
+ if (result.success) {
102
+ console.log(result.output);
103
+ process.exit(ExitCode.Success);
104
+ } else {
105
+ console.error(result.error);
106
+ process.exit(ExitCode.FatalError);
107
+ }
108
+ });
109
+ buddy.command("env:rotate [key]", descriptions.rotate).option("-f, --file [file]", descriptions.file, { default: "" }).option("-fk, --file-keys [fileKeys]", descriptions.fileKeys, { default: "" }).option("-o, --stdout", descriptions.stdout, { default: !1 }).option("-ek, --exclude-key [excludeKey]", descriptions.excludeKey, { default: "" }).example("buddy env:rotate").example("buddy env:rotate --file .env.production").action(async (key, options) => {
110
+ log.debug("Running `buddy env:rotate` ...", options);
111
+ const result = rotateKeypair({
112
+ file: options.file,
113
+ keysFile: options.fileKeys,
114
+ key,
115
+ excludeKey: options.excludeKey,
116
+ stdout: options.stdout
117
+ });
118
+ if (result.success) {
119
+ console.log(result.output);
120
+ process.exit(ExitCode.Success);
121
+ } else {
122
+ console.error(result.error);
123
+ process.exit(ExitCode.FatalError);
124
+ }
125
+ });
126
+ buddy.command("env:check", descriptions.check).option("-f, --file [file]", descriptions.file, { default: "" }).option("--verbose", descriptions.verbose, { default: !1 }).example("buddy env:check").example("buddy env:check --file .env.production").action(async (options) => {
127
+ log.debug("Running `buddy env:check` ...", options);
128
+ const { bold, dim, green, red, yellow, intro } = await import("@stacksjs/cli"), { storage } = await import("@stacksjs/storage"), { existsSync } = await import("node:fs"), { resolve } = await import("node:path");
129
+ await intro("buddy env:check");
130
+ const checks = [], envFile = options.file || ".env", envPath = resolve(process.cwd(), envFile);
131
+ if (existsSync(envPath)) {
132
+ checks.push({
133
+ name: `${envFile} file`,
134
+ status: "pass",
135
+ message: "Found"
136
+ });
137
+ try {
138
+ const envContent = await storage.readTextFile(envPath), lines = String(envContent).split(`
139
+ `).filter((line) => line.trim() && !line.startsWith("#")), varCount = lines.length;
140
+ checks.push({
141
+ name: "Environment variables",
142
+ status: "pass",
143
+ message: `${varCount} variables defined`
144
+ });
145
+ if (lines.some((line) => line.startsWith("APP_KEY="))) {
146
+ const appKeyLine = lines.find((line) => line.startsWith("APP_KEY=")), eq = appKeyLine?.indexOf("=") ?? -1, appKeyValue = (eq >= 0 ? appKeyLine.slice(eq + 1) : "").trim().replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1");
147
+ if (appKeyValue && appKeyValue.length > 0)
148
+ checks.push({
149
+ name: "APP_KEY",
150
+ status: "pass",
151
+ message: "Set"
152
+ });
153
+ else
154
+ checks.push({
155
+ name: "APP_KEY",
156
+ status: "warn",
157
+ message: "Empty (run: buddy key:generate)"
158
+ });
159
+ } else
160
+ checks.push({
161
+ name: "APP_KEY",
162
+ status: "warn",
163
+ message: "Not found (run: buddy key:generate)"
164
+ });
165
+ const hasPublicKey = lines.some((line) => line.startsWith("DOTENV_PUBLIC_KEY=")), hasPrivateKey = lines.some((line) => line.startsWith("DOTENV_PRIVATE_KEY="));
166
+ if (hasPublicKey && hasPrivateKey)
167
+ checks.push({
168
+ name: "Encryption keys",
169
+ status: "pass",
170
+ message: "Public and private keys configured"
171
+ });
172
+ else if (hasPublicKey || hasPrivateKey)
173
+ checks.push({
174
+ name: "Encryption keys",
175
+ status: "warn",
176
+ message: "Incomplete keypair (run: buddy env:keypair)"
177
+ });
178
+ else
179
+ checks.push({
180
+ name: "Encryption keys",
181
+ status: "warn",
182
+ message: "Not configured (optional)"
183
+ });
184
+ } catch (error) {
185
+ checks.push({
186
+ name: `${envFile} content`,
187
+ status: "fail",
188
+ message: `Cannot read file: ${error}`
189
+ });
190
+ }
191
+ } else
192
+ checks.push({
193
+ name: `${envFile} file`,
194
+ status: "fail",
195
+ message: "Not found"
196
+ });
197
+ const keysPath = resolve(process.cwd(), ".env.keys");
198
+ if (existsSync(keysPath))
199
+ checks.push({
200
+ name: ".env.keys file",
201
+ status: "pass",
202
+ message: "Found"
203
+ });
204
+ else
205
+ checks.push({
206
+ name: ".env.keys file",
207
+ status: "warn",
208
+ message: "Not found (optional for encryption)"
209
+ });
210
+ console.log("");
211
+ console.log(bold("Environment Configuration Check:"));
212
+ console.log(dim("\u2500".repeat(60)));
213
+ console.log("");
214
+ let hasFailures = !1, hasWarnings = !1;
215
+ for (const check of checks) {
216
+ let statusIcon = "", statusColor = (text) => text;
217
+ if (check.status === "pass") {
218
+ statusIcon = "\u2713";
219
+ statusColor = green;
220
+ } else if (check.status === "warn") {
221
+ statusIcon = "\u26A0";
222
+ statusColor = yellow;
223
+ hasWarnings = !0;
224
+ } else {
225
+ statusIcon = "\u2717";
226
+ statusColor = red;
227
+ hasFailures = !0;
228
+ }
229
+ console.log(`${statusColor(statusIcon)} ${bold(check.name.padEnd(25))} ${dim(check.message)}`);
230
+ }
231
+ console.log("");
232
+ console.log(dim("\u2500".repeat(60)));
233
+ console.log("");
234
+ if (hasFailures) {
235
+ console.log(red("\u2717 Some critical checks failed. Please address the issues above."));
236
+ process.exit(ExitCode.FatalError);
237
+ } else if (hasWarnings) {
238
+ console.log(yellow("\u26A0 Some checks have warnings. Your environment should work but may have issues."));
239
+ process.exit(ExitCode.Success);
240
+ } else {
241
+ console.log(green("\u2713 All checks passed! Your environment configuration looks healthy."));
242
+ process.exit(ExitCode.Success);
243
+ }
244
+ });
245
+ onUnknownSubcommand(buddy, "env");
246
+ }
@@ -0,0 +1,8 @@
1
+ import type { CLI } from '@stacksjs/types';
2
+ /**
3
+ * `buddy extension:*` — build & package MV3 browser extensions from the
4
+ * project's `config/extension.ts` (see @stacksjs/browser-extension). Zero
5
+ * per-project build scripts: the framework owns the manifest, bundling, and
6
+ * packaging.
7
+ */
8
+ export declare function extension(buddy: CLI): void;
@@ -0,0 +1,78 @@
1
+ import process from "node:process";
2
+ import { log } from "@stacksjs/cli";
3
+ export function extension(buddy) {
4
+ const load = async () => {
5
+ const { loadExtensionConfig } = await import("@stacksjs/browser-extension"), config = await loadExtensionConfig(process.cwd());
6
+ if (!config) {
7
+ log.error("No extension config found. Create `config/extension.ts` exporting `defineExtension({ \u2026 })`.");
8
+ process.exit(1);
9
+ }
10
+ const pkg = await Bun.file(`${process.cwd()}/package.json`).json().catch(() => ({}));
11
+ return { config, version: pkg.version ?? "0.0.0" };
12
+ };
13
+ buddy.command("extension:build", "Build the browser extension (Chrome + Firefox) from config/extension.ts").option("--target <target>", "Build a single target (chrome | firefox); omit to build all").option("--version <version>", "Override the extension version (defaults to package.json)").action(async (options) => {
14
+ const { buildExtension, buildAllTargets } = await import("@stacksjs/browser-extension"), { config, version } = await load(), v = options.version ?? version;
15
+ if (options.target) {
16
+ const { outdir } = await buildExtension(config, { target: options.target, version: v });
17
+ log.success(`Built ${config.name} ${v} (${options.target}) \u2192 ${outdir}`);
18
+ } else {
19
+ await buildAllTargets(config, { version: v });
20
+ log.success(`Built ${config.name} ${v} for ${(config.targets ?? ["chrome", "firefox"]).join(", ")}`);
21
+ }
22
+ });
23
+ buddy.command("extension:init", "Scaffold a browser extension (config/extension.ts + starter background/content/pages)").option("--name <name>", "Extension display name").action(async (options) => {
24
+ const { existsSync } = await import("node:fs"), { mkdir, writeFile } = await import("node:fs/promises"), { dirname, join } = await import("node:path"), cwd = process.cwd(), name = options.name ?? "My Extension", write = async (rel, content) => {
25
+ const abs = join(cwd, rel);
26
+ if (existsSync(abs)) {
27
+ log.info(`skip (exists): ${rel}`);
28
+ return;
29
+ }
30
+ await mkdir(dirname(abs), { recursive: !0 });
31
+ await writeFile(abs, content);
32
+ log.success(`created ${rel}`);
33
+ };
34
+ await write("config/extension.ts", `import { defineExtension } from '@stacksjs/browser-extension'
35
+
36
+ export default defineExtension({
37
+ name: ${JSON.stringify(name)},
38
+ description: 'A browser extension built with Stacks.',
39
+ targets: ['chrome', 'firefox'],
40
+ background: 'src/background/index.ts',
41
+ content: [
42
+ { entry: 'src/content/index.ts', matches: ['<all_urls>'], runAt: 'document_start' },
43
+ ],
44
+ pages: {
45
+ popup: { template: 'pages/popup.stx', script: 'src/ui/popup.ts' },
46
+ },
47
+ icons: { 16: 'icons/icon-16.png', 48: 'icons/icon-48.png', 128: 'icons/icon-128.png' },
48
+ public: 'public',
49
+ manifest: {
50
+ permissions: ['storage', 'tabs'],
51
+ },
52
+ })
53
+ `);
54
+ await write("src/background/index.ts", `// Background service worker (MV3).
55
+ console.log('[${name}] background ready')
56
+ `);
57
+ await write("src/content/index.ts", `// Content script \u2014 runs on matched pages.
58
+ console.log('[${name}] content script loaded')
59
+ `);
60
+ await write("src/ui/popup.ts", `// Popup script.
61
+ console.log('[${name}] popup')
62
+ `);
63
+ await write("pages/popup.stx", `<div class="popup">
64
+ <h1>${name}</h1>
65
+ <p>Edit pages/popup.stx to build your popup.</p>
66
+ <script src="/popup.js"></script>
67
+ </div>
68
+ `);
69
+ log.info("Next: add icons under public/icons, then `buddy extension:build`.");
70
+ });
71
+ buddy.command("extension:package", "Build + zip the browser extension into store-ready archives").option("--target <target>", "Package a single target (chrome | firefox); omit to package all").option("--version <version>", "Override the extension version (defaults to package.json)").action(async (options) => {
72
+ const { packageExtension } = await import("@stacksjs/browser-extension"), { config, version } = await load(), v = options.version ?? version, targets = options.target ? [options.target] : config.targets ?? ["chrome", "firefox"];
73
+ for (const target of targets) {
74
+ const out = await packageExtension(config, { target, version: v });
75
+ log.success(`Packaged ${config.name} (${target}) \u2192 ${out}`);
76
+ }
77
+ });
78
+ }
@@ -0,0 +1,180 @@
1
+ import type { CLI } from '@stacksjs/types';
2
+ /**
3
+ * Parse a migration filename like `0000000045-create-posts-table.sql` or
4
+ * `0000000085-alter-posts-author_id.sql` to extract the table name it
5
+ * acts on. Returns `null` for filenames that don't match the
6
+ * recognised `create-<table>-table` / `alter-<table>-` shapes — those
7
+ * pass through the gate unchanged.
8
+ *
9
+ * Index migrations (`create-<table>_<col>_unique-index-in-<table>.sql`)
10
+ * use the trailing `-in-<table>.sql` segment as the source of truth
11
+ * since the leading segment includes the index name. The other
12
+ * forms read the table from the segment immediately after the verb.
13
+ */
14
+ export declare function migrationTable(filename: string): string | null;
15
+ /**
16
+ * Returns the feature that owns the given migration filename, or `null`
17
+ * if the migration isn't claimed by any feature (in which case it
18
+ * always runs). Used by the migration runner's gating pass.
19
+ */
20
+ export declare function migrationFeature(filename: string): FeatureName | null;
21
+ /**
22
+ * Returns the subset of a feature's manifest paths that currently exist
23
+ * on disk under `root` (defaults to `projectPath()`). Used by both the
24
+ * uninstall delete and the doctor orphan check.
25
+ */
26
+ export declare function featurePathsPresent(feature: FeatureName, root?: string): string[];
27
+ /**
28
+ * Recursively delete every file/dir listed in the feature's manifest under
29
+ * `root` (defaults to `projectPath()`). Missing entries are skipped
30
+ * silently — the operation is safe to re-run. Returns the list of paths
31
+ * actually removed so the caller can print a useful summary.
32
+ */
33
+ export declare function deleteFeatureFiles(feature: FeatureName, root?: string): Promise<string[]>;
34
+ /**
35
+ * Copy every path listed in the feature's manifest from the framework
36
+ * defaults tree (`storage/framework/defaults/<path>`) into the project
37
+ * root (`<project>/<path>`). Missing source entries are skipped — not
38
+ * every feature owns every manifest slot (e.g. `cms` doesn't ship a
39
+ * `resources/components/Dashboard/Commerce/` dir, so that entry is
40
+ * absent from its source). Existing target paths are also skipped by
41
+ * default so re-running install on an existing project is idempotent.
42
+ *
43
+ * Returns the list of paths actually copied. See stacksjs/stacks#1854.
44
+ */
45
+ export declare function copyFeatureFiles(feature: FeatureName, options?: CopyFeatureFilesOptions): Promise<{ copied: string[], skipped: string[] }>;
46
+ /**
47
+ * Flip the top-level `enabled` field in `config/<feature>.ts` to the
48
+ * desired value. Returns:
49
+ *
50
+ * - 'created' — file did not exist and we scaffolded it (install only)
51
+ * - 'flipped' — file existed; `enabled` was on the opposite value
52
+ * - 'unchanged' — file existed; `enabled` already matched
53
+ * - 'missing' — file did not exist and `createIfMissing` was false
54
+ * (uninstall path: nothing to do, feature is already off)
55
+ *
56
+ * Pass `options.root` to target a project directory other than the
57
+ * caller's working tree — `./buddy new --minimal` uses this to disable
58
+ * features in the freshly-cloned project path before the user has cd'd
59
+ * into it.
60
+ */
61
+ export declare function setFeatureEnabled(feature: FeatureName, enabled: boolean, options: { createIfMissing: boolean, root?: string }): Promise<SetFeatureEnabledOutcome>;
62
+ /**
63
+ * Disable every feature in `FEATURE_NAMES` at once — flips
64
+ * `config/<feature>.ts` enabled flags to `false` and removes the
65
+ * stamped scaffolding under the project root.
66
+ *
67
+ * Used by `./buddy new --minimal` to turn the kitchen-sink template
68
+ * `@stacksjs/gitit` clones into a bare-bones starter. Individual
69
+ * `<feature>:install` commands re-enable + re-stamp on demand.
70
+ *
71
+ * Pass `root` to target a project directory other than `projectPath()`;
72
+ * the create command uses this because `./buddy new` runs from the
73
+ * user's cwd while the freshly-cloned project lives at `<cwd>/<name>`.
74
+ *
75
+ * Safe to re-run — both halves of each per-feature step are idempotent.
76
+ */
77
+ export declare function uninstallAllFeatures(options?: { root?: string }): Promise<UninstallAllFeaturesResult[]>;
78
+ export declare function features(buddy: CLI): void;
79
+ /**
80
+ * Feature install / uninstall commands.
81
+ *
82
+ * Each framework feature bundle (dashboard, commerce, cms, marketing,
83
+ * monitoring, realtime, queue) lives in its own `config/<feature>.ts`
84
+ * file. Running `./buddy <feature>:install` flips that file's top-level
85
+ * `enabled` to `true` (scaffolding the file from a starter template if it's
86
+ * missing). `./buddy <feature>:uninstall` flips the flag back to `false`
87
+ * AND removes the feature's stamped action/model/view files from the
88
+ * project (pass `--keep-files` to preserve them); the config file itself
89
+ * is preserved so any custom driver/credential settings survive a future
90
+ * reinstall.
91
+ *
92
+ * The framework loaders (`orm/index.ts` eager-load, `defaults/bootstrap.ts`
93
+ * route registration, action prefetch) consult `feature(name)` at boot and
94
+ * skip anything whose flag is off — so an app with only `auth` activated
95
+ * never pays the cost of importing 70+ Commerce models or registering
96
+ * hundreds of dashboard routes it doesn't use.
97
+ *
98
+ * Auth is intentionally not in this list: it has its own scaffolding
99
+ * pipeline (`buddy auth:setup`) which handles migrations + personal-access
100
+ * client setup beyond a simple `enabled` flip.
101
+ *
102
+ * Mirrors Laravel's `php artisan passport:install` / `horizon:install`
103
+ * pattern: features are inert dead code on disk until installed.
104
+ */
105
+ export declare const FEATURE_NAMES: readonly ['dashboard', 'commerce', 'cms', 'marketing', 'monitoring', 'realtime', 'queue'];
106
+ /**
107
+ * Per-feature stamped file/directory manifest. Paths are relative to the
108
+ * project root and mirror the layout that `./buddy new` lays down. Entries
109
+ * ending in `/` are directory trees (recursive remove on uninstall); bare
110
+ * paths are single files.
111
+ *
112
+ * Manifests intentionally overlap where features share scaffolding —
113
+ * `dashboard` claims the umbrella `app/Actions/Dashboard/` even though
114
+ * `app/Actions/Dashboard/Content/` is also claimed by `cms`. Both the
115
+ * uninstall delete and the doctor orphan check are idempotent
116
+ * (already-gone paths are skipped silently), so the overlap is safe.
117
+ *
118
+ * Adding a new file to one of these directories does **not** require a
119
+ * manifest update — directory entries are recursive. Only add an entry
120
+ * when a feature introduces a new top-level path the framework didn't
121
+ * already claim.
122
+ * @defaultValue
123
+ * ```ts
124
+ * {
125
+ * cms: [ 'app/Actions/Cms/', 'app/Actions/Dashboard/Content/', 'app/Models/Content/', 'app/Models/Tag.ts', 'app/Models/Comment.ts', 'resources/views/dashboard/content/', ],
126
+ * commerce: [ 'app/Actions/Commerce/', 'app/Actions/Dashboard/Commerce/', 'app/Models/commerce/', 'resources/components/Dashboard/Commerce/', 'resources/views/dashboard/commerce/', ],
127
+ * dashboard: [ 'app/Actions/Dashboard/', 'resources/components/Dashboard/', 'resources/views/dashboard/', 'routes/dashboard.ts', 'routes/dashboard-api.ts', ],
128
+ * marketing: [ 'app/Actions/Dashboard/Marketing/', 'app/Models/Campaign.ts', 'app/Models/CampaignSend.ts', 'app/Models/EmailList.ts', 'app/Models/EmailListSubscriber.ts', 'app/Models/SocialPost.ts', 'resources/components/Marketing/', 'resources/views/dashboard/marketing/', ],
129
+ * monitoring: [ 'app/Actions/Monitoring/', 'app/Actions/TestErrorAction.ts', 'app/Models/Error.ts', 'functions/monitoring/', 'resources/views/dashboard/monitoring/', 'resources/views/dashboard/errors/', ],
130
+ * realtime: [ 'app/Actions/Realtime/', 'app/Actions/Dashboard/Realtime/', 'app/Models/realtime/', 'app/Broadcasts/', 'functions/realtime/', 'resources/views/dashboard/realtime/', ],
131
+ * queue: [ 'app/Actions/Queue/', 'app/Actions/Dashboard/Jobs/', 'app/Jobs/', 'app/Models/Job.ts', 'app/Models/FailedJob.ts', 'functions/jobs.ts', 'resources/views/dashboard/queue/', 'resources/views/dashboard/jobs/', ]
132
+ * }
133
+ * ```
134
+ */
135
+ export declare const FEATURE_FILES: Record<FeatureName, readonly string[]>;
136
+ /**
137
+ * Per-feature database table ownership (stacksjs/stacks#1854).
138
+ *
139
+ * Stacks generates SQL migrations from model files, so each feature's
140
+ * tables map 1:1 with the models in its `FEATURE_FILES.app/Models/...`
141
+ * entries. Listed here explicitly rather than derived at runtime so
142
+ * additions are visible in a single grep-able place and the migration
143
+ * gate doesn't depend on filesystem scanning at boot.
144
+ *
145
+ * The migration runner consults this when `config.<feature>.enabled =
146
+ * false`: matching `*-create-<table>-table.sql` (and `*-alter-<table>-*.sql`)
147
+ * files get hidden for the duration of the run, so a project that
148
+ * never installed CMS doesn't materialize `posts`, `pages`,
149
+ * `comments`, etc. on `./buddy migrate`.
150
+ *
151
+ * Tables on this list are scoped to a single feature. Tables shared
152
+ * across features (none today, but `categories` could end up here)
153
+ * should stay out of the manifest until that's resolved — the runner
154
+ * defaults to "run unless owned by a disabled feature".
155
+ * @defaultValue
156
+ * ```ts
157
+ * {
158
+ * cms: ['posts', 'pages', 'comments', 'tags', 'authors', 'categories'],
159
+ * commerce: [ 'products', 'product_variants', 'product_units', 'manufacturers', 'orders', 'order_items', 'carts', 'cart_items', 'payments', 'payment_methods', 'payment_products', 'payment_transactions', 'customers', 'subscribers', 'subscriber_emails', 'subscriptions', 'gift_cards', 'coupons', 'transactions', 'reviews', 'drivers', 'delivery_routes', 'digital_deliveries', 'shipping_methods', 'shipping_rates', 'shipping_zones', 'license_keys', 'loyalty_points', 'loyalty_rewards', 'print_devices', 'receipts', 'tax_rates', 'waitlist_products', 'waitlist_restaurants', ],
160
+ * dashboard: [ 'boards', 'board_columns', 'cards', 'card_labels', 'card_assignees', 'card_comments', 'labels', 'ci_run_states', 'ci_runner_samples', 'ci_runner_alert_states', 'requests', 'logs', ],
161
+ * marketing: [],
162
+ * monitoring: ['errors'],
163
+ * realtime: ['websockets'],
164
+ * queue: ['jobs', 'failed_jobs']
165
+ * }
166
+ * ```
167
+ */
168
+ export declare const FEATURE_TABLES: Record<FeatureName, readonly string[]>;
169
+ export declare interface CopyFeatureFilesOptions {
170
+ force?: boolean
171
+ source?: string
172
+ target?: string
173
+ }
174
+ export declare interface UninstallAllFeaturesResult {
175
+ feature: FeatureName
176
+ configOutcome: SetFeatureEnabledOutcome
177
+ filesRemoved: string[]
178
+ }
179
+ export type FeatureName = (typeof FEATURE_NAMES)[number];
180
+ export type SetFeatureEnabledOutcome = 'created' | 'flipped' | 'unchanged' | 'missing';