@bagdock/cli 0.6.1 → 0.8.0
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/bagdock.js +186 -41
- package/package.json +2 -2
package/dist/bagdock.js
CHANGED
|
@@ -993,7 +993,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
993
993
|
this._exitCallback = (err) => {
|
|
994
994
|
if (err.code !== "commander.executeSubCommandAsync") {
|
|
995
995
|
throw err;
|
|
996
|
-
}
|
|
996
|
+
}
|
|
997
997
|
};
|
|
998
998
|
}
|
|
999
999
|
return this;
|
|
@@ -4105,7 +4105,9 @@ Deploying ${config.slug}@${config.version} → ${envLabel}
|
|
|
4105
4105
|
formData.append("metadata", JSON.stringify({
|
|
4106
4106
|
version: config.version,
|
|
4107
4107
|
environment,
|
|
4108
|
-
compatibilityDate: config.compatibilityDate ?? "2024-09-23"
|
|
4108
|
+
compatibilityDate: config.compatibilityDate ?? "2024-09-23",
|
|
4109
|
+
...config.kv ? { kv: config.kv } : {},
|
|
4110
|
+
...config.webhooks ? { webhooks: config.webhooks } : {}
|
|
4109
4111
|
}));
|
|
4110
4112
|
try {
|
|
4111
4113
|
const res = await fetch(`${getApiBase()}/api/v1/developer/apps/${config.slug}/deploy`, {
|
|
@@ -4376,6 +4378,35 @@ async function validate() {
|
|
|
4376
4378
|
checks.push({ name: "Bundle size", status: "pass", message: `${(size / 1024).toFixed(1)} KB` });
|
|
4377
4379
|
}
|
|
4378
4380
|
}
|
|
4381
|
+
if (config.webhooks !== undefined) {
|
|
4382
|
+
if (!Array.isArray(config.webhooks)) {
|
|
4383
|
+
checks.push({ name: "Webhooks", status: "fail", message: '"webhooks" must be an array of { name, path, description? }' });
|
|
4384
|
+
} else {
|
|
4385
|
+
const problems = [];
|
|
4386
|
+
const seen = new Set;
|
|
4387
|
+
config.webhooks.forEach((wh, i) => {
|
|
4388
|
+
if (!wh || typeof wh !== "object") {
|
|
4389
|
+
problems.push(`#${i} is not an object`);
|
|
4390
|
+
return;
|
|
4391
|
+
}
|
|
4392
|
+
if (!wh.name)
|
|
4393
|
+
problems.push(`#${i} missing "name"`);
|
|
4394
|
+
if (!wh.path)
|
|
4395
|
+
problems.push(`#${i} missing "path"`);
|
|
4396
|
+
else if (!wh.path.startsWith("/"))
|
|
4397
|
+
problems.push(`"${wh.name ?? i}" path must start with "/" (got "${wh.path}")`);
|
|
4398
|
+
if (wh.name && seen.has(wh.name))
|
|
4399
|
+
problems.push(`duplicate name "${wh.name}"`);
|
|
4400
|
+
if (wh.name)
|
|
4401
|
+
seen.add(wh.name);
|
|
4402
|
+
});
|
|
4403
|
+
if (problems.length) {
|
|
4404
|
+
checks.push({ name: "Webhooks", status: "fail", message: problems.join("; ") });
|
|
4405
|
+
} else {
|
|
4406
|
+
checks.push({ name: "Webhooks", status: "pass", message: `${config.webhooks.length} declared` });
|
|
4407
|
+
}
|
|
4408
|
+
}
|
|
4409
|
+
}
|
|
4379
4410
|
const linked = resolveSlug();
|
|
4380
4411
|
if (linked && linked !== config.slug) {
|
|
4381
4412
|
checks.push({ name: "Project link", status: "warn", message: `bagdock.json slug "${config.slug}" differs from linked project "${linked}"` });
|
|
@@ -4638,16 +4669,40 @@ function requireConfig() {
|
|
|
4638
4669
|
}
|
|
4639
4670
|
return config;
|
|
4640
4671
|
}
|
|
4641
|
-
|
|
4672
|
+
function parseTarget(target) {
|
|
4673
|
+
if (!target)
|
|
4674
|
+
return;
|
|
4675
|
+
const t = target.toLowerCase();
|
|
4676
|
+
if (t === "staging")
|
|
4677
|
+
return ["staging"];
|
|
4678
|
+
if (t === "production")
|
|
4679
|
+
return ["production"];
|
|
4680
|
+
if (t === "both")
|
|
4681
|
+
return ["staging", "production"];
|
|
4682
|
+
console.error(source_default.red(`Invalid --target: ${target}. Use staging, production, or both.`));
|
|
4683
|
+
process.exit(1);
|
|
4684
|
+
}
|
|
4685
|
+
async function envList(opts) {
|
|
4642
4686
|
const config = requireConfig();
|
|
4643
4687
|
try {
|
|
4688
|
+
if (opts?.reconcile) {
|
|
4689
|
+
status("Reconciling with Cloudflare...");
|
|
4690
|
+
const reconcileRes = await apiFetchJson(`/api/v1/developer/apps/${config.slug}/env/reconcile`, "POST", {});
|
|
4691
|
+
if (!reconcileRes.ok) {
|
|
4692
|
+
console.error(source_default.yellow(`Reconcile failed (${reconcileRes.status}) — showing cached data`));
|
|
4693
|
+
}
|
|
4694
|
+
}
|
|
4644
4695
|
const res = await apiFetch(`/api/v1/developer/apps/${config.slug}/env`);
|
|
4645
4696
|
if (!res.ok) {
|
|
4646
4697
|
console.error(source_default.red(`Failed to list env vars (${res.status})`));
|
|
4647
4698
|
process.exit(1);
|
|
4648
4699
|
}
|
|
4649
|
-
const
|
|
4650
|
-
if (
|
|
4700
|
+
const body = await res.json();
|
|
4701
|
+
if (isJsonMode()) {
|
|
4702
|
+
outputSuccess(body);
|
|
4703
|
+
return;
|
|
4704
|
+
}
|
|
4705
|
+
if (!body.data.length) {
|
|
4651
4706
|
console.log(source_default.yellow("No environment variables set."));
|
|
4652
4707
|
console.log("Use", source_default.cyan("bagdock env set <KEY> <VALUE>"), "to add one.");
|
|
4653
4708
|
return;
|
|
@@ -4655,8 +4710,13 @@ async function envList() {
|
|
|
4655
4710
|
console.log(source_default.bold(`
|
|
4656
4711
|
Environment variables for ${config.slug}:
|
|
4657
4712
|
`));
|
|
4658
|
-
for (const v of data) {
|
|
4659
|
-
|
|
4713
|
+
for (const v of body.data) {
|
|
4714
|
+
const envLabel = v.environments?.length ? source_default.dim(`[${v.environments.join(", ")}]`) : source_default.dim("[no target]");
|
|
4715
|
+
console.log(` ${source_default.cyan(v.key)} ${envLabel} ${source_default.dim(`updated ${v.updatedAt}`)}`);
|
|
4716
|
+
}
|
|
4717
|
+
if (body.last_reconciled_at) {
|
|
4718
|
+
console.log(source_default.dim(`
|
|
4719
|
+
Last synced with Cloudflare: ${body.last_reconciled_at}`));
|
|
4660
4720
|
}
|
|
4661
4721
|
console.log();
|
|
4662
4722
|
} catch (err) {
|
|
@@ -4664,22 +4724,40 @@ Environment variables for ${config.slug}:
|
|
|
4664
4724
|
process.exit(1);
|
|
4665
4725
|
}
|
|
4666
4726
|
}
|
|
4667
|
-
async function envSet(key, value) {
|
|
4727
|
+
async function envSet(key, value, opts) {
|
|
4668
4728
|
const config = requireConfig();
|
|
4729
|
+
const environments = parseTarget(opts?.target);
|
|
4669
4730
|
try {
|
|
4670
|
-
const
|
|
4731
|
+
const payload = { key, value };
|
|
4732
|
+
if (environments)
|
|
4733
|
+
payload.environments = environments;
|
|
4734
|
+
const res = await apiFetchJson(`/api/v1/developer/apps/${config.slug}/env`, "PUT", payload);
|
|
4671
4735
|
if (!res.ok) {
|
|
4672
4736
|
const body = await res.text();
|
|
4673
4737
|
console.error(source_default.red(`Failed to set ${key} (${res.status}):`), body.slice(0, 200));
|
|
4674
4738
|
process.exit(1);
|
|
4675
4739
|
}
|
|
4676
|
-
|
|
4740
|
+
const result = await res.json();
|
|
4741
|
+
if (isJsonMode()) {
|
|
4742
|
+
outputSuccess(result);
|
|
4743
|
+
return;
|
|
4744
|
+
}
|
|
4745
|
+
if (result.status === "partial") {
|
|
4746
|
+
console.log(source_default.yellow(`Partially set ${key}:`));
|
|
4747
|
+
for (const [env2, status2] of Object.entries(result.environments ?? {})) {
|
|
4748
|
+
const icon = status2 === "ok" ? source_default.green("✓") : source_default.red("✗");
|
|
4749
|
+
console.log(` ${icon} ${env2}: ${status2}`);
|
|
4750
|
+
}
|
|
4751
|
+
} else {
|
|
4752
|
+
const targetLabel = environments ? ` (${environments.join(", ")})` : "";
|
|
4753
|
+
console.log(source_default.green(`Set ${key}${targetLabel}`));
|
|
4754
|
+
}
|
|
4677
4755
|
} catch (err) {
|
|
4678
4756
|
console.error(source_default.red("Failed:"), err.message);
|
|
4679
4757
|
process.exit(1);
|
|
4680
4758
|
}
|
|
4681
4759
|
}
|
|
4682
|
-
async function envRemove(key) {
|
|
4760
|
+
async function envRemove(key, opts) {
|
|
4683
4761
|
const config = requireConfig();
|
|
4684
4762
|
try {
|
|
4685
4763
|
const res = await apiFetch(`/api/v1/developer/apps/${config.slug}/env/${key}`, { method: "DELETE" });
|
|
@@ -4687,7 +4765,12 @@ async function envRemove(key) {
|
|
|
4687
4765
|
console.error(source_default.red(`Failed to remove ${key} (${res.status})`));
|
|
4688
4766
|
process.exit(1);
|
|
4689
4767
|
}
|
|
4690
|
-
|
|
4768
|
+
if (isJsonMode()) {
|
|
4769
|
+
const result = await res.json();
|
|
4770
|
+
outputSuccess(result);
|
|
4771
|
+
return;
|
|
4772
|
+
}
|
|
4773
|
+
console.log(source_default.green(`Removed ${key}`));
|
|
4691
4774
|
} catch (err) {
|
|
4692
4775
|
console.error(source_default.red("Failed:"), err.message);
|
|
4693
4776
|
process.exit(1);
|
|
@@ -5081,8 +5164,19 @@ async function init(dir, opts) {
|
|
|
5081
5164
|
const template = selectTemplate(type, kind, slug);
|
|
5082
5165
|
writeFileSync2(entryFile, template);
|
|
5083
5166
|
}
|
|
5167
|
+
if (type === "edge" && kind === "comms") {
|
|
5168
|
+
const typesFile = join2(srcDir, "types.ts");
|
|
5169
|
+
if (!existsSync2(typesFile)) {
|
|
5170
|
+
writeFileSync2(typesFile, COMMS_TYPES_TEMPLATE());
|
|
5171
|
+
}
|
|
5172
|
+
const verifyFile = join2(srcDir, "verify.ts");
|
|
5173
|
+
if (!existsSync2(verifyFile)) {
|
|
5174
|
+
writeFileSync2(verifyFile, COMMS_VERIFY_TEMPLATE());
|
|
5175
|
+
}
|
|
5176
|
+
}
|
|
5084
5177
|
const pkgFile = join2(projectDir, "package.json");
|
|
5085
5178
|
if (!existsSync2(pkgFile)) {
|
|
5179
|
+
const deps = {};
|
|
5086
5180
|
const devDeps = {
|
|
5087
5181
|
"@cloudflare/workers-types": "^4.20240909.0",
|
|
5088
5182
|
typescript: "^5.3.3"
|
|
@@ -5090,11 +5184,15 @@ async function init(dir, opts) {
|
|
|
5090
5184
|
if (type === "edge" && kind === "adapter") {
|
|
5091
5185
|
devDeps["@bagdock/adapter-worker-template"] = "workspace:*";
|
|
5092
5186
|
}
|
|
5187
|
+
if (type === "edge" && kind === "comms") {
|
|
5188
|
+
deps["@bagdock/worker-sdk"] = "^0.1.0";
|
|
5189
|
+
}
|
|
5093
5190
|
writeFileSync2(pkgFile, JSON.stringify({
|
|
5094
5191
|
name: slug,
|
|
5095
5192
|
version: "0.1.0",
|
|
5096
5193
|
private: true,
|
|
5097
5194
|
main: "src/index.ts",
|
|
5195
|
+
...Object.keys(deps).length ? { dependencies: deps } : {},
|
|
5098
5196
|
devDependencies: devDeps
|
|
5099
5197
|
}, null, 2));
|
|
5100
5198
|
}
|
|
@@ -5103,12 +5201,17 @@ async function init(dir, opts) {
|
|
|
5103
5201
|
Initialised Bagdock ${label} project!
|
|
5104
5202
|
`));
|
|
5105
5203
|
console.log("Created:");
|
|
5106
|
-
console.log(` ${source_default.cyan("bagdock.json")}
|
|
5107
|
-
console.log(` ${source_default.cyan("src/index.ts")}
|
|
5204
|
+
console.log(` ${source_default.cyan("bagdock.json")} — project config`);
|
|
5205
|
+
console.log(` ${source_default.cyan("src/index.ts")} — worker entry point`);
|
|
5206
|
+
if (type === "edge" && kind === "comms") {
|
|
5207
|
+
console.log(` ${source_default.cyan("src/types.ts")} — environment bindings`);
|
|
5208
|
+
console.log(` ${source_default.cyan("src/verify.ts")} — webhook verification (customise for your vendor)`);
|
|
5209
|
+
}
|
|
5108
5210
|
console.log();
|
|
5109
5211
|
console.log("Next steps:");
|
|
5110
|
-
console.log(` 1. ${source_default.cyan("
|
|
5111
|
-
console.log(` 2. ${source_default.cyan("bagdock
|
|
5212
|
+
console.log(` 1. ${source_default.cyan("bun install")} — install dependencies`);
|
|
5213
|
+
console.log(` 2. ${source_default.cyan("bagdock dev")} — start local dev server`);
|
|
5214
|
+
console.log(` 3. ${source_default.cyan("bagdock deploy")} — deploy to Bagdock platform`);
|
|
5112
5215
|
}
|
|
5113
5216
|
function resolveKind(type, kindOpt) {
|
|
5114
5217
|
if (kindOpt)
|
|
@@ -5154,33 +5257,75 @@ export default createAdapterWorker(new ${toPascalCase(slug)}Adapter())
|
|
|
5154
5257
|
`;
|
|
5155
5258
|
}
|
|
5156
5259
|
function COMMS_TEMPLATE(slug) {
|
|
5157
|
-
return
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
*/
|
|
5260
|
+
return `import { createCommsWorker } from '@bagdock/worker-sdk'
|
|
5261
|
+
import type { HandlerContext } from '@bagdock/worker-sdk'
|
|
5262
|
+
import type { Env } from './types'
|
|
5263
|
+
import { vendorWebhookVerify } from './verify'
|
|
5162
5264
|
|
|
5163
|
-
|
|
5164
|
-
|
|
5265
|
+
async function handleSmsSend(ctx: HandlerContext<Env>): Promise<Response> {
|
|
5266
|
+
const { to, body } = await ctx.request.json() as { to: string; body: string }
|
|
5267
|
+
// TODO: call your vendor's SMS API using ctx.env for secrets
|
|
5268
|
+
return Response.json({ id: crypto.randomUUID(), status: 'queued', provider: '${slug}', from: '', to })
|
|
5165
5269
|
}
|
|
5166
5270
|
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5271
|
+
async function handleSmsWebhook(ctx: HandlerContext<Env>): Promise<Response> {
|
|
5272
|
+
const payload = await ctx.request.json()
|
|
5273
|
+
ctx.logger.info('webhook.sms', { payload })
|
|
5274
|
+
return Response.json({ received: true })
|
|
5275
|
+
}
|
|
5171
5276
|
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
}
|
|
5277
|
+
export default createCommsWorker<Env, readonly ['sms']>({
|
|
5278
|
+
version: '0.1.0',
|
|
5279
|
+
capabilities: ['sms'],
|
|
5176
5280
|
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
}
|
|
5281
|
+
async onInstall(ctx) {
|
|
5282
|
+
// TODO: provision vendor resources
|
|
5283
|
+
ctx.logger.info('lifecycle.install', { operatorId: ctx.operatorId })
|
|
5284
|
+
return { installation_state: { provisioned: true } }
|
|
5285
|
+
},
|
|
5286
|
+
|
|
5287
|
+
async onUninstall(ctx) {
|
|
5288
|
+
// TODO: clean up vendor resources
|
|
5289
|
+
ctx.logger.info('lifecycle.uninstall', { operatorId: ctx.operatorId })
|
|
5290
|
+
},
|
|
5180
5291
|
|
|
5181
|
-
|
|
5292
|
+
routes: {
|
|
5293
|
+
'sms/send': handleSmsSend,
|
|
5294
|
+
'webhooks/sms': { handler: handleSmsWebhook, verify: vendorWebhookVerify },
|
|
5182
5295
|
},
|
|
5296
|
+
})
|
|
5297
|
+
`;
|
|
5183
5298
|
}
|
|
5299
|
+
function COMMS_TYPES_TEMPLATE() {
|
|
5300
|
+
return `import type { BaseEnv } from '@bagdock/worker-sdk'
|
|
5301
|
+
|
|
5302
|
+
export interface Env extends BaseEnv {
|
|
5303
|
+
ADAPTER_NAME: string
|
|
5304
|
+
PROVIDER_SLUG: string
|
|
5305
|
+
API_KEY: string
|
|
5306
|
+
WEBHOOK_SECRET: string
|
|
5307
|
+
OPERATOR_CONFIG?: KVNamespace
|
|
5308
|
+
}
|
|
5309
|
+
`;
|
|
5310
|
+
}
|
|
5311
|
+
function COMMS_VERIFY_TEMPLATE() {
|
|
5312
|
+
return `import { hmacSha256Verify } from '@bagdock/worker-sdk'
|
|
5313
|
+
import type { VerifyFunction } from '@bagdock/worker-sdk'
|
|
5314
|
+
import type { Env } from './types'
|
|
5315
|
+
|
|
5316
|
+
/**
|
|
5317
|
+
* Adapter-local webhook verification.
|
|
5318
|
+
*
|
|
5319
|
+
* Replace with your vendor's signing method. See the @bagdock/worker-sdk
|
|
5320
|
+
* README for examples using vendor SDKs or the ed25519Verify primitive.
|
|
5321
|
+
*/
|
|
5322
|
+
export const vendorWebhookVerify: VerifyFunction<Env> = (request, env, rawBody) =>
|
|
5323
|
+
hmacSha256Verify({
|
|
5324
|
+
signature: request.headers.get('x-webhook-signature'),
|
|
5325
|
+
secret: env.WEBHOOK_SECRET,
|
|
5326
|
+
signingString: rawBody,
|
|
5327
|
+
timestamp: request.headers.get('x-webhook-timestamp'),
|
|
5328
|
+
})
|
|
5184
5329
|
`;
|
|
5185
5330
|
}
|
|
5186
5331
|
function WEBHOOK_TEMPLATE(slug) {
|
|
@@ -5358,17 +5503,17 @@ program2.command("link").description("Link current directory to a Bagdock app or
|
|
|
5358
5503
|
await link2(opts);
|
|
5359
5504
|
});
|
|
5360
5505
|
var envCmd = program2.command("env").description("Manage app environment variables");
|
|
5361
|
-
envCmd.command("list").description("List environment variables for this app").action(async () => {
|
|
5506
|
+
envCmd.command("list").description("List environment variables for this app").option("--reconcile", "Force sync with Cloudflare before listing").action(async (opts) => {
|
|
5362
5507
|
const { envList: envList2 } = await Promise.resolve().then(() => (init_env_cmd(), exports_env_cmd));
|
|
5363
|
-
await envList2();
|
|
5508
|
+
await envList2({ reconcile: opts.reconcile });
|
|
5364
5509
|
});
|
|
5365
|
-
envCmd.command("set <key> <value>").description("Set an environment variable").action(async (key, value) => {
|
|
5510
|
+
envCmd.command("set <key> <value>").description("Set an environment variable").option("--target <target>", "Target namespace: staging, production, or both (default: both)").action(async (key, value, opts) => {
|
|
5366
5511
|
const { envSet: envSet2 } = await Promise.resolve().then(() => (init_env_cmd(), exports_env_cmd));
|
|
5367
|
-
await envSet2(key, value);
|
|
5512
|
+
await envSet2(key, value, { target: opts.target });
|
|
5368
5513
|
});
|
|
5369
|
-
envCmd.command("remove <key>").description("Remove an environment variable").action(async (key) => {
|
|
5514
|
+
envCmd.command("remove <key>").description("Remove an environment variable").option("--target <target>", "Target namespace: staging, production, or both (default: both)").action(async (key, opts) => {
|
|
5370
5515
|
const { envRemove: envRemove2 } = await Promise.resolve().then(() => (init_env_cmd(), exports_env_cmd));
|
|
5371
|
-
await envRemove2(key);
|
|
5516
|
+
await envRemove2(key, { target: opts.target });
|
|
5372
5517
|
});
|
|
5373
5518
|
envCmd.command("pull [file]").description("Pull remote env var keys to a local .env file").action(async (file) => {
|
|
5374
5519
|
const { envPull: envPull2 } = await Promise.resolve().then(() => (init_env_cmd(), exports_env_cmd));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bagdock/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Bagdock developer CLI — build, test, and deploy apps and edges on the Bagdock platform",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bagdock",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"type": "git",
|
|
18
18
|
"url": "git+https://github.com/bagdock/bagdock-cli.git"
|
|
19
19
|
},
|
|
20
|
-
"homepage": "https://
|
|
20
|
+
"homepage": "https://bagdock.com/docs/cli",
|
|
21
21
|
"bugs": {
|
|
22
22
|
"url": "https://github.com/bagdock/bagdock-cli/issues"
|
|
23
23
|
},
|