@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.
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +165 -0
- package/dist/commands/about.d.ts +2 -0
- package/dist/commands/about.js +35 -0
- package/dist/commands/add.d.ts +2 -0
- package/dist/commands/add.js +33 -0
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +68 -0
- package/dist/commands/build.d.ts +2 -0
- package/dist/commands/build.js +153 -0
- package/dist/commands/cd.d.ts +20 -0
- package/dist/commands/cd.js +39 -0
- package/dist/commands/changelog.d.ts +2 -0
- package/dist/commands/changelog.js +32 -0
- package/dist/commands/clean.d.ts +2 -0
- package/dist/commands/clean.js +37 -0
- package/dist/commands/cloud.d.ts +2 -0
- package/dist/commands/cloud.js +503 -0
- package/dist/commands/commit.d.ts +2 -0
- package/dist/commands/commit.js +16 -0
- package/dist/commands/completion.d.ts +2 -0
- package/dist/commands/completion.js +143 -0
- package/dist/commands/config-migrate.d.ts +26 -0
- package/dist/commands/config-migrate.js +94 -0
- package/dist/commands/configure.d.ts +2 -0
- package/dist/commands/configure.js +48 -0
- package/dist/commands/create.d.ts +2 -0
- package/dist/commands/create.js +141 -0
- package/dist/commands/deploy.d.ts +107 -0
- package/dist/commands/deploy.js +1439 -0
- package/dist/commands/dev.d.ts +10 -0
- package/dist/commands/dev.js +907 -0
- package/dist/commands/dns.d.ts +2 -0
- package/dist/commands/dns.js +102 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +285 -0
- package/dist/commands/domains.d.ts +2 -0
- package/dist/commands/domains.js +159 -0
- package/dist/commands/email.d.ts +2 -0
- package/dist/commands/email.js +524 -0
- package/dist/commands/env.d.ts +2 -0
- package/dist/commands/env.js +246 -0
- package/dist/commands/extension.d.ts +8 -0
- package/dist/commands/extension.js +78 -0
- package/dist/commands/features.d.ts +180 -0
- package/dist/commands/features.js +422 -0
- package/dist/commands/fresh.d.ts +2 -0
- package/dist/commands/fresh.js +39 -0
- package/dist/commands/generate.d.ts +2 -0
- package/dist/commands/generate.js +104 -0
- package/dist/commands/http.d.ts +2 -0
- package/dist/commands/http.js +33 -0
- package/dist/commands/index.d.ts +53 -0
- package/dist/commands/index.js +53 -0
- package/dist/commands/install.d.ts +2 -0
- package/dist/commands/install.js +22 -0
- package/dist/commands/key.d.ts +2 -0
- package/dist/commands/key.js +23 -0
- package/dist/commands/lint.d.ts +2 -0
- package/dist/commands/lint.js +46 -0
- package/dist/commands/list.d.ts +2 -0
- package/dist/commands/list.js +108 -0
- package/dist/commands/mail.d.ts +2 -0
- package/dist/commands/mail.js +693 -0
- package/dist/commands/maintenance.d.ts +2 -0
- package/dist/commands/maintenance.js +141 -0
- package/dist/commands/make.d.ts +2 -0
- package/dist/commands/make.js +375 -0
- package/dist/commands/migrate-project.d.ts +2 -0
- package/dist/commands/migrate-project.js +49 -0
- package/dist/commands/migrate.d.ts +4 -0
- package/dist/commands/migrate.js +455 -0
- package/dist/commands/outdated.d.ts +2 -0
- package/dist/commands/outdated.js +19 -0
- package/dist/commands/package.d.ts +2 -0
- package/dist/commands/package.js +17 -0
- package/dist/commands/phone.d.ts +2 -0
- package/dist/commands/phone.js +188 -0
- package/dist/commands/ports.d.ts +2 -0
- package/dist/commands/ports.js +118 -0
- package/dist/commands/prepublish.d.ts +2 -0
- package/dist/commands/prepublish.js +17 -0
- package/dist/commands/projects.d.ts +2 -0
- package/dist/commands/projects.js +29 -0
- package/dist/commands/publish.d.ts +2 -0
- package/dist/commands/publish.js +172 -0
- package/dist/commands/queue.d.ts +2 -0
- package/dist/commands/queue.js +248 -0
- package/dist/commands/release.d.ts +2 -0
- package/dist/commands/release.js +30 -0
- package/dist/commands/route.d.ts +2 -0
- package/dist/commands/route.js +21 -0
- package/dist/commands/saas.d.ts +2 -0
- package/dist/commands/saas.js +25 -0
- package/dist/commands/schedule.d.ts +2 -0
- package/dist/commands/schedule.js +61 -0
- package/dist/commands/search.d.ts +2 -0
- package/dist/commands/search.js +84 -0
- package/dist/commands/seed.d.ts +2 -0
- package/dist/commands/seed.js +71 -0
- package/dist/commands/serve.d.ts +25 -0
- package/dist/commands/serve.js +182 -0
- package/dist/commands/setup.d.ts +12 -0
- package/dist/commands/setup.js +215 -0
- package/dist/commands/share.d.ts +2 -0
- package/dist/commands/share.js +214 -0
- package/dist/commands/sms.d.ts +2 -0
- package/dist/commands/sms.js +328 -0
- package/dist/commands/stacks.d.ts +2 -0
- package/dist/commands/stacks.js +69 -0
- package/dist/commands/telemetry.d.ts +2 -0
- package/dist/commands/telemetry.js +74 -0
- package/dist/commands/test.d.ts +2 -0
- package/dist/commands/test.js +130 -0
- package/dist/commands/tinker.d.ts +2 -0
- package/dist/commands/tinker.js +37 -0
- package/dist/commands/types.d.ts +2 -0
- package/dist/commands/types.js +18 -0
- package/dist/commands/upgrade.d.ts +2 -0
- package/dist/commands/upgrade.js +98 -0
- package/dist/commands/version.d.ts +2 -0
- package/dist/commands/version.js +16 -0
- package/dist/config.d.ts +43 -0
- package/dist/config.js +223 -0
- package/dist/custom-cli.d.ts +1 -0
- package/dist/custom-cli.js +23 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lazy-commands.d.ts +61 -0
- package/dist/lazy-commands.js +188 -0
- package/dist/migrators/index.d.ts +9 -0
- package/dist/migrators/index.js +62 -0
- package/dist/migrators/laravel/index.d.ts +2 -0
- package/dist/migrators/laravel/index.js +148 -0
- package/dist/migrators/laravel/migrations.d.ts +52 -0
- package/dist/migrators/laravel/migrations.js +232 -0
- package/dist/migrators/laravel/models.d.ts +34 -0
- package/dist/migrators/laravel/models.js +134 -0
- package/dist/migrators/rails/index.d.ts +2 -0
- package/dist/migrators/rails/index.js +11 -0
- package/dist/migrators/types.d.ts +44 -0
- package/dist/migrators/types.js +0 -0
- 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';
|