@bagdock/cli 0.6.1 → 0.7.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.
Files changed (2) hide show
  1. package/dist/bagdock.js +153 -39
  2. package/package.json +1 -1
package/dist/bagdock.js CHANGED
@@ -4638,16 +4638,40 @@ function requireConfig() {
4638
4638
  }
4639
4639
  return config;
4640
4640
  }
4641
- async function envList() {
4641
+ function parseTarget(target) {
4642
+ if (!target)
4643
+ return;
4644
+ const t = target.toLowerCase();
4645
+ if (t === "staging")
4646
+ return ["staging"];
4647
+ if (t === "production")
4648
+ return ["production"];
4649
+ if (t === "both")
4650
+ return ["staging", "production"];
4651
+ console.error(source_default.red(`Invalid --target: ${target}. Use staging, production, or both.`));
4652
+ process.exit(1);
4653
+ }
4654
+ async function envList(opts) {
4642
4655
  const config = requireConfig();
4643
4656
  try {
4657
+ if (opts?.reconcile) {
4658
+ status("Reconciling with Cloudflare...");
4659
+ const reconcileRes = await apiFetchJson(`/api/v1/developer/apps/${config.slug}/env/reconcile`, "POST", {});
4660
+ if (!reconcileRes.ok) {
4661
+ console.error(source_default.yellow(`Reconcile failed (${reconcileRes.status}) — showing cached data`));
4662
+ }
4663
+ }
4644
4664
  const res = await apiFetch(`/api/v1/developer/apps/${config.slug}/env`);
4645
4665
  if (!res.ok) {
4646
4666
  console.error(source_default.red(`Failed to list env vars (${res.status})`));
4647
4667
  process.exit(1);
4648
4668
  }
4649
- const { data } = await res.json();
4650
- if (!data.length) {
4669
+ const body = await res.json();
4670
+ if (isJsonMode()) {
4671
+ outputSuccess(body);
4672
+ return;
4673
+ }
4674
+ if (!body.data.length) {
4651
4675
  console.log(source_default.yellow("No environment variables set."));
4652
4676
  console.log("Use", source_default.cyan("bagdock env set <KEY> <VALUE>"), "to add one.");
4653
4677
  return;
@@ -4655,8 +4679,13 @@ async function envList() {
4655
4679
  console.log(source_default.bold(`
4656
4680
  Environment variables for ${config.slug}:
4657
4681
  `));
4658
- for (const v of data) {
4659
- console.log(` ${source_default.cyan(v.key)} ${source_default.dim(`(updated ${v.updatedAt})`)}`);
4682
+ for (const v of body.data) {
4683
+ const envLabel = v.environments?.length ? source_default.dim(`[${v.environments.join(", ")}]`) : source_default.dim("[no target]");
4684
+ console.log(` ${source_default.cyan(v.key)} ${envLabel} ${source_default.dim(`updated ${v.updatedAt}`)}`);
4685
+ }
4686
+ if (body.last_reconciled_at) {
4687
+ console.log(source_default.dim(`
4688
+ Last synced with Cloudflare: ${body.last_reconciled_at}`));
4660
4689
  }
4661
4690
  console.log();
4662
4691
  } catch (err) {
@@ -4664,22 +4693,40 @@ Environment variables for ${config.slug}:
4664
4693
  process.exit(1);
4665
4694
  }
4666
4695
  }
4667
- async function envSet(key, value) {
4696
+ async function envSet(key, value, opts) {
4668
4697
  const config = requireConfig();
4698
+ const environments = parseTarget(opts?.target);
4669
4699
  try {
4670
- const res = await apiFetchJson(`/api/v1/developer/apps/${config.slug}/env`, "PUT", { key, value });
4700
+ const payload = { key, value };
4701
+ if (environments)
4702
+ payload.environments = environments;
4703
+ const res = await apiFetchJson(`/api/v1/developer/apps/${config.slug}/env`, "PUT", payload);
4671
4704
  if (!res.ok) {
4672
4705
  const body = await res.text();
4673
4706
  console.error(source_default.red(`Failed to set ${key} (${res.status}):`), body.slice(0, 200));
4674
4707
  process.exit(1);
4675
4708
  }
4676
- console.log(source_default.green(`Set ${key}`), source_default.dim("— will take effect on next deploy"));
4709
+ const result = await res.json();
4710
+ if (isJsonMode()) {
4711
+ outputSuccess(result);
4712
+ return;
4713
+ }
4714
+ if (result.status === "partial") {
4715
+ console.log(source_default.yellow(`Partially set ${key}:`));
4716
+ for (const [env2, status2] of Object.entries(result.environments ?? {})) {
4717
+ const icon = status2 === "ok" ? source_default.green("✓") : source_default.red("✗");
4718
+ console.log(` ${icon} ${env2}: ${status2}`);
4719
+ }
4720
+ } else {
4721
+ const targetLabel = environments ? ` (${environments.join(", ")})` : "";
4722
+ console.log(source_default.green(`Set ${key}${targetLabel}`));
4723
+ }
4677
4724
  } catch (err) {
4678
4725
  console.error(source_default.red("Failed:"), err.message);
4679
4726
  process.exit(1);
4680
4727
  }
4681
4728
  }
4682
- async function envRemove(key) {
4729
+ async function envRemove(key, opts) {
4683
4730
  const config = requireConfig();
4684
4731
  try {
4685
4732
  const res = await apiFetch(`/api/v1/developer/apps/${config.slug}/env/${key}`, { method: "DELETE" });
@@ -4687,7 +4734,12 @@ async function envRemove(key) {
4687
4734
  console.error(source_default.red(`Failed to remove ${key} (${res.status})`));
4688
4735
  process.exit(1);
4689
4736
  }
4690
- console.log(source_default.green(`Removed ${key}`), source_default.dim("— will take effect on next deploy"));
4737
+ if (isJsonMode()) {
4738
+ const result = await res.json();
4739
+ outputSuccess(result);
4740
+ return;
4741
+ }
4742
+ console.log(source_default.green(`Removed ${key}`));
4691
4743
  } catch (err) {
4692
4744
  console.error(source_default.red("Failed:"), err.message);
4693
4745
  process.exit(1);
@@ -5081,8 +5133,19 @@ async function init(dir, opts) {
5081
5133
  const template = selectTemplate(type, kind, slug);
5082
5134
  writeFileSync2(entryFile, template);
5083
5135
  }
5136
+ if (type === "edge" && kind === "comms") {
5137
+ const typesFile = join2(srcDir, "types.ts");
5138
+ if (!existsSync2(typesFile)) {
5139
+ writeFileSync2(typesFile, COMMS_TYPES_TEMPLATE());
5140
+ }
5141
+ const verifyFile = join2(srcDir, "verify.ts");
5142
+ if (!existsSync2(verifyFile)) {
5143
+ writeFileSync2(verifyFile, COMMS_VERIFY_TEMPLATE());
5144
+ }
5145
+ }
5084
5146
  const pkgFile = join2(projectDir, "package.json");
5085
5147
  if (!existsSync2(pkgFile)) {
5148
+ const deps = {};
5086
5149
  const devDeps = {
5087
5150
  "@cloudflare/workers-types": "^4.20240909.0",
5088
5151
  typescript: "^5.3.3"
@@ -5090,11 +5153,15 @@ async function init(dir, opts) {
5090
5153
  if (type === "edge" && kind === "adapter") {
5091
5154
  devDeps["@bagdock/adapter-worker-template"] = "workspace:*";
5092
5155
  }
5156
+ if (type === "edge" && kind === "comms") {
5157
+ deps["@bagdock/worker-sdk"] = "^0.1.0";
5158
+ }
5093
5159
  writeFileSync2(pkgFile, JSON.stringify({
5094
5160
  name: slug,
5095
5161
  version: "0.1.0",
5096
5162
  private: true,
5097
5163
  main: "src/index.ts",
5164
+ ...Object.keys(deps).length ? { dependencies: deps } : {},
5098
5165
  devDependencies: devDeps
5099
5166
  }, null, 2));
5100
5167
  }
@@ -5103,12 +5170,17 @@ async function init(dir, opts) {
5103
5170
  Initialised Bagdock ${label} project!
5104
5171
  `));
5105
5172
  console.log("Created:");
5106
- console.log(` ${source_default.cyan("bagdock.json")} — project config`);
5107
- console.log(` ${source_default.cyan("src/index.ts")} — worker entry point`);
5173
+ console.log(` ${source_default.cyan("bagdock.json")} — project config`);
5174
+ console.log(` ${source_default.cyan("src/index.ts")} — worker entry point`);
5175
+ if (type === "edge" && kind === "comms") {
5176
+ console.log(` ${source_default.cyan("src/types.ts")} — environment bindings`);
5177
+ console.log(` ${source_default.cyan("src/verify.ts")} — webhook verification (customise for your vendor)`);
5178
+ }
5108
5179
  console.log();
5109
5180
  console.log("Next steps:");
5110
- console.log(` 1. ${source_default.cyan("bagdock dev")} start local dev server`);
5111
- console.log(` 2. ${source_default.cyan("bagdock deploy")} deploy to Bagdock platform`);
5181
+ console.log(` 1. ${source_default.cyan("bun install")} install dependencies`);
5182
+ console.log(` 2. ${source_default.cyan("bagdock dev")} start local dev server`);
5183
+ console.log(` 3. ${source_default.cyan("bagdock deploy")} — deploy to Bagdock platform`);
5112
5184
  }
5113
5185
  function resolveKind(type, kindOpt) {
5114
5186
  if (kindOpt)
@@ -5154,33 +5226,75 @@ export default createAdapterWorker(new ${toPascalCase(slug)}Adapter())
5154
5226
  `;
5155
5227
  }
5156
5228
  function COMMS_TEMPLATE(slug) {
5157
- return `/**
5158
- * ${toPascalCase(slug)} Communications edge worker.
5159
- *
5160
- * Handles SMS, Voice, or Telephony actions dispatched by the workflow engine.
5161
- */
5229
+ return `import { createCommsWorker } from '@bagdock/worker-sdk'
5230
+ import type { HandlerContext } from '@bagdock/worker-sdk'
5231
+ import type { Env } from './types'
5232
+ import { vendorWebhookVerify } from './verify'
5162
5233
 
5163
- interface Env {
5164
- API_KEY: string
5234
+ async function handleSmsSend(ctx: HandlerContext<Env>): Promise<Response> {
5235
+ const { to, body } = await ctx.request.json() as { to: string; body: string }
5236
+ // TODO: call your vendor's SMS API using ctx.env for secrets
5237
+ return Response.json({ id: crypto.randomUUID(), status: 'queued', provider: '${slug}', from: '', to })
5165
5238
  }
5166
5239
 
5167
- export default {
5168
- async fetch(request: Request, env: Env): Promise<Response> {
5169
- const url = new URL(request.url)
5170
- const action = url.pathname.replace(/^\\//, '')
5240
+ async function handleSmsWebhook(ctx: HandlerContext<Env>): Promise<Response> {
5241
+ const payload = await ctx.request.json()
5242
+ ctx.logger.info('webhook.sms', { payload })
5243
+ return Response.json({ received: true })
5244
+ }
5171
5245
 
5172
- if (action === 'send-sms') {
5173
- const body = await request.json() as { to: string; text: string; from?: string }
5174
- return Response.json({ id: crypto.randomUUID(), status: 'queued' })
5175
- }
5246
+ export default createCommsWorker<Env, readonly ['sms']>({
5247
+ version: '0.1.0',
5248
+ capabilities: ['sms'],
5176
5249
 
5177
- if (action === 'health') {
5178
- return Response.json({ status: 'healthy', version: '0.1.0' })
5179
- }
5250
+ async onInstall(ctx) {
5251
+ // TODO: provision vendor resources
5252
+ ctx.logger.info('lifecycle.install', { operatorId: ctx.operatorId })
5253
+ return { installation_state: { provisioned: true } }
5254
+ },
5255
+
5256
+ async onUninstall(ctx) {
5257
+ // TODO: clean up vendor resources
5258
+ ctx.logger.info('lifecycle.uninstall', { operatorId: ctx.operatorId })
5259
+ },
5180
5260
 
5181
- return Response.json({ error: 'Unknown action', action }, { status: 404 })
5261
+ routes: {
5262
+ 'sms/send': handleSmsSend,
5263
+ 'webhooks/sms': { handler: handleSmsWebhook, verify: vendorWebhookVerify },
5182
5264
  },
5265
+ })
5266
+ `;
5183
5267
  }
5268
+ function COMMS_TYPES_TEMPLATE() {
5269
+ return `import type { BaseEnv } from '@bagdock/worker-sdk'
5270
+
5271
+ export interface Env extends BaseEnv {
5272
+ ADAPTER_NAME: string
5273
+ PROVIDER_SLUG: string
5274
+ API_KEY: string
5275
+ WEBHOOK_SECRET: string
5276
+ OPERATOR_CONFIG?: KVNamespace
5277
+ }
5278
+ `;
5279
+ }
5280
+ function COMMS_VERIFY_TEMPLATE() {
5281
+ return `import { hmacSha256Verify } from '@bagdock/worker-sdk'
5282
+ import type { VerifyFunction } from '@bagdock/worker-sdk'
5283
+ import type { Env } from './types'
5284
+
5285
+ /**
5286
+ * Adapter-local webhook verification.
5287
+ *
5288
+ * Replace with your vendor's signing method. See the @bagdock/worker-sdk
5289
+ * README for examples using vendor SDKs or the ed25519Verify primitive.
5290
+ */
5291
+ export const vendorWebhookVerify: VerifyFunction<Env> = (request, env, rawBody) =>
5292
+ hmacSha256Verify({
5293
+ signature: request.headers.get('x-webhook-signature'),
5294
+ secret: env.WEBHOOK_SECRET,
5295
+ signingString: rawBody,
5296
+ timestamp: request.headers.get('x-webhook-timestamp'),
5297
+ })
5184
5298
  `;
5185
5299
  }
5186
5300
  function WEBHOOK_TEMPLATE(slug) {
@@ -5358,17 +5472,17 @@ program2.command("link").description("Link current directory to a Bagdock app or
5358
5472
  await link2(opts);
5359
5473
  });
5360
5474
  var envCmd = program2.command("env").description("Manage app environment variables");
5361
- envCmd.command("list").description("List environment variables for this app").action(async () => {
5475
+ envCmd.command("list").description("List environment variables for this app").option("--reconcile", "Force sync with Cloudflare before listing").action(async (opts) => {
5362
5476
  const { envList: envList2 } = await Promise.resolve().then(() => (init_env_cmd(), exports_env_cmd));
5363
- await envList2();
5477
+ await envList2({ reconcile: opts.reconcile });
5364
5478
  });
5365
- envCmd.command("set <key> <value>").description("Set an environment variable").action(async (key, value) => {
5479
+ 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
5480
  const { envSet: envSet2 } = await Promise.resolve().then(() => (init_env_cmd(), exports_env_cmd));
5367
- await envSet2(key, value);
5481
+ await envSet2(key, value, { target: opts.target });
5368
5482
  });
5369
- envCmd.command("remove <key>").description("Remove an environment variable").action(async (key) => {
5483
+ 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
5484
  const { envRemove: envRemove2 } = await Promise.resolve().then(() => (init_env_cmd(), exports_env_cmd));
5371
- await envRemove2(key);
5485
+ await envRemove2(key, { target: opts.target });
5372
5486
  });
5373
5487
  envCmd.command("pull [file]").description("Pull remote env var keys to a local .env file").action(async (file) => {
5374
5488
  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.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Bagdock developer CLI — build, test, and deploy apps and edges on the Bagdock platform",
5
5
  "keywords": [
6
6
  "bagdock",