@samva/cli 0.20.0 → 0.22.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 (3) hide show
  1. package/AGENTS.md +11 -3
  2. package/dist/bin.mjs +775 -234
  3. package/package.json +8 -8
package/dist/bin.mjs CHANGED
@@ -14278,7 +14278,7 @@ var layer12 = /* @__PURE__ */ layer.pipe(/* @__PURE__ */ provideMerge(/* @__PURE
14278
14278
  // package.json
14279
14279
  var package_default = {
14280
14280
  name: "@samva/cli",
14281
- version: "0.20.0",
14281
+ version: "0.22.0",
14282
14282
  description: "Samva CLI \u2014 email and platform operations from your terminal",
14283
14283
  keywords: [
14284
14284
  "cli",
@@ -14321,7 +14321,7 @@ var package_default = {
14321
14321
  "build:release-artifacts": "bun run scripts/build-release-artifacts.ts",
14322
14322
  test: "vitest run --root ../.. --project cli",
14323
14323
  "test:package": "vitest run --root ../.. --project cli packages/cli/tests/package-install.e2e.test.ts",
14324
- typecheck: "tsc --noEmit && tsc --noEmit -p tests/consumer/tsconfig.json"
14324
+ typecheck: "tsc --noEmit && tsc --noEmit -p tests/consumer/tsconfig.json && tsc --noEmit -p tests/consumer/tsconfig.default.json"
14325
14325
  },
14326
14326
  devDependencies: {
14327
14327
  "@effect/platform-bun": "catalog:effect",
@@ -14334,16 +14334,16 @@ var package_default = {
14334
14334
  "@types/node": "catalog:",
14335
14335
  "better-auth": "catalog:",
14336
14336
  effect: "catalog:effect",
14337
- samva: "workspace:0.22.1",
14337
+ samva: "workspace:0.23.0",
14338
14338
  typescript: "catalog:",
14339
14339
  vite: "~8.3.0",
14340
14340
  vitest: "catalog:"
14341
14341
  },
14342
14342
  optionalDependencies: {
14343
- "@samva/cli-darwin-arm64": "workspace:0.20.0",
14344
- "@samva/cli-darwin-x64": "workspace:0.20.0",
14345
- "@samva/cli-linux-arm64": "workspace:0.20.0",
14346
- "@samva/cli-linux-x64": "workspace:0.20.0"
14343
+ "@samva/cli-darwin-arm64": "workspace:0.22.0",
14344
+ "@samva/cli-darwin-x64": "workspace:0.22.0",
14345
+ "@samva/cli-linux-arm64": "workspace:0.22.0",
14346
+ "@samva/cli-linux-x64": "workspace:0.22.0"
14347
14347
  },
14348
14348
  engines: {
14349
14349
  node: ">=18"
@@ -25566,7 +25566,7 @@ var __exportAll = (all, no_symbols) => {
25566
25566
  };
25567
25567
 
25568
25568
  // ../sdk/dist/package.js
25569
- var version = "0.22.1";
25569
+ var version = "0.23.0";
25570
25570
 
25571
25571
  // ../sdk/dist/client-attribution.js
25572
25572
  var SDK_VERSION = version;
@@ -25909,7 +25909,7 @@ var commandTree = {
25909
25909
  update: {},
25910
25910
  remove: {}
25911
25911
  },
25912
- brands: { list: {}, get: {}, pull: {}, push: {} },
25912
+ brands: { list: {}, get: {}, pull: {}, push: {}, default: {}, remove: {} },
25913
25913
  mailboxes: {
25914
25914
  events: {},
25915
25915
  list: {},
@@ -54599,6 +54599,8 @@ var make41 = /* @__PURE__ */ forGroup(BrandsGroup);
54599
54599
  var list3 = /* @__PURE__ */ make41("brandsList");
54600
54600
  var get9 = /* @__PURE__ */ make41("brandsGet");
54601
54601
  var update4 = /* @__PURE__ */ make41("brandsUpdate");
54602
+ var remove4 = /* @__PURE__ */ make41("brandsRemove");
54603
+ var setDefault = /* @__PURE__ */ make41("brandsSetDefault");
54602
54604
 
54603
54605
  // src/commands/brands.ts
54604
54606
  var SNAPSHOT_DIR = [".samva", "brands"];
@@ -54755,11 +54757,62 @@ var push = make29("push", {
54755
54757
  yield* log(`Updated brand ${updated.slug} from ${file}.`);
54756
54758
  yield* logInfo("Published templates keep the brand they were built against until they are republished.");
54757
54759
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
54758
- var brandsCommand = make29("brands").pipe(withDescription3("List, snapshot, and update the organization's brands (what samva:brand resolves to)"), withExamples([
54760
+ var requiredSlugArgument = String10("slug").pipe(withDescription4("Brand slug"));
54761
+ var dryRunFlag2 = Boolean6("dry-run").pipe(withDescription2("Print the request without sending it"), withDefault5(false));
54762
+ var yesFlag2 = Boolean6("yes").pipe(withDescription2("Confirm without prompting (required outside a terminal)"), withDefault5(false));
54763
+ var makeDefault = make29("default", {
54764
+ slug: requiredSlugArgument,
54765
+ dryRun: dryRunFlag2,
54766
+ json: jsonFlag
54767
+ }).pipe(withDescription3("Make a brand the organization's default: bare samva:brand and domains without a brand resolve to it"), withExamples([
54768
+ { command: "samva brands default acme" },
54769
+ { command: "samva brands default acme --dry-run --json" }
54770
+ ]), withHandler(({ slug, dryRun, json }) => gen2(function* () {
54771
+ if (dryRun)
54772
+ return yield* json ? printJson({ dryRun: true, action: "brands.default", slug }) : log(`[dry-run] Would make brand ${slug} the default.`);
54773
+ const client = yield* getClientProvider;
54774
+ const brand = yield* setDefault({ slug }).pipe(client.provide, mapError2(apiFailure(`Could not make brand ${slug} the default`)));
54775
+ const result = {
54776
+ action: "brands.default",
54777
+ slug: brand.slug,
54778
+ name: brand.name,
54779
+ isDefault: brand.isDefault
54780
+ };
54781
+ if (json)
54782
+ return yield* printJson(result);
54783
+ yield* log(`Brand ${brand.slug} (${brand.name}) is now the default.`);
54784
+ }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
54785
+ var remove5 = make29("remove", {
54786
+ slug: requiredSlugArgument,
54787
+ dryRun: dryRunFlag2,
54788
+ yes: yesFlag2,
54789
+ json: jsonFlag
54790
+ }).pipe(withDescription3("Permanently delete a brand. The default brand cannot be deleted; make another brand the default first. Domains that used it fall back to the default."), withExamples([
54791
+ { command: "samva brands remove promo --dry-run" },
54792
+ { command: "samva brands remove promo --yes" }
54793
+ ]), withHandler(({ slug, dryRun, yes, json }) => gen2(function* () {
54794
+ if (dryRun)
54795
+ return yield* json ? printJson({ dryRun: true, action: "brands.remove", slug }) : log(`[dry-run] Would permanently remove brand ${slug}.`);
54796
+ yield* confirmDestructive({
54797
+ yes,
54798
+ json,
54799
+ message: `Permanently remove brand ${slug}?`,
54800
+ nonInteractiveMessage: "Permanent brand deletion requires --yes in non-interactive mode. Preview with --dry-run."
54801
+ });
54802
+ const client = yield* getClientProvider;
54803
+ yield* remove4({ slug }).pipe(client.provide, mapError2(apiFailure(`Could not remove brand ${slug}`)));
54804
+ const result = { action: "brands.remove", slug, removed: true };
54805
+ if (json)
54806
+ return yield* printJson(result);
54807
+ yield* log(`Removed brand ${slug}.`);
54808
+ }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
54809
+ var brandsCommand = make29("brands").pipe(withDescription3("List, snapshot, update, and delete the organization's brands (what samva:brand resolves to)"), withExamples([
54759
54810
  { command: "samva brands list" },
54760
54811
  { command: "samva brands pull", description: "Snapshot the default brand for offline builds" },
54761
- { command: "samva brands push brand.css --brand acme" }
54762
- ]), withSubcommands([list4, get10, pull, push]));
54812
+ { command: "samva brands push brand.css --brand acme" },
54813
+ { command: "samva brands default acme", description: "Make acme the default brand" },
54814
+ { command: "samva brands remove promo --yes" }
54815
+ ]), withSubcommands([list4, get10, pull, push, makeDefault, remove5]));
54763
54816
 
54764
54817
  // ../sdk/dist/effect/generated/campaigns.js
54765
54818
  var CampaignId2 = /* @__PURE__ */ String4.check(isPattern2(/* @__PURE__ */ new RegExp("^camp_[0-9a-hjkmnp-tv-z]{16,24}$", "u")).annotate({
@@ -55387,7 +55440,7 @@ var excludeTagsFlag = String9("exclude-tags").pipe(withDescription2("Contact tag
55387
55440
  var contactIdsFlag = String9("contact-ids").pipe(withDescription2("Explicit contact ids to target (comma-separated or repeated)"), atLeast2(0));
55388
55441
  var unsubscribeGroupIdFlag = String9("unsubscribe-group-id").pipe(withDescription2("Unsubscribe group id applied to every send; overrides the organization's default group"), optional4);
55389
55442
  var pageSizeFlag = Int5("page-size").pipe(withDescription2("Recipients dispatched per batch"), optional4);
55390
- var dryRunFlag2 = Boolean6("dry-run").pipe(withDescription2("Validate inputs and print the request payload without calling the API"), withDefault5(false));
55443
+ var dryRunFlag3 = Boolean6("dry-run").pipe(withDescription2("Validate inputs and print the request payload without calling the API"), withDefault5(false));
55391
55444
  var trackOpensFlag = Literals3("track-opens", ["on", "off"]).pipe(withDescription2("Override open tracking for campaign sends; recipient opt-out wins"), optional4);
55392
55445
  var trackClicksFlag = Literals3("track-clicks", ["on", "off"]).pipe(withDescription2("Override click tracking for campaign sends; recipient opt-out wins"), optional4);
55393
55446
  var atFlag = String9("at").pipe(withDescription2("Absolute run start instant as ISO 8601 (defaults to now)"), optional4);
@@ -55415,7 +55468,7 @@ var create4 = make29("create", {
55415
55468
  pageSize: pageSizeFlag,
55416
55469
  trackOpens: trackOpensFlag,
55417
55470
  trackClicks: trackClicksFlag,
55418
- dryRun: dryRunFlag2,
55471
+ dryRun: dryRunFlag3,
55419
55472
  json: jsonFlag
55420
55473
  }).pipe(withDescription3("Create an email campaign targeting tags or explicit contacts"), withExamples([
55421
55474
  {
@@ -55794,7 +55847,7 @@ var runCreate = make29("create", {
55794
55847
  campaignId: String10("campaignId"),
55795
55848
  at: atFlag,
55796
55849
  idempotencyKey: idempotencyKeyFlag,
55797
- dryRun: dryRunFlag2,
55850
+ dryRun: dryRunFlag3,
55798
55851
  json: jsonFlag
55799
55852
  }).pipe(withDescription3("Create a run for a campaign (starts immediately without --at)"), withExamples([
55800
55853
  { command: "samva campaigns runs create camp_abc123", description: "Start now" },
@@ -55907,7 +55960,7 @@ var runGet = make29("get", {
55907
55960
  var runPause = make29("pause", {
55908
55961
  campaignId: String10("campaignId"),
55909
55962
  runId: String10("runId"),
55910
- dryRun: dryRunFlag2,
55963
+ dryRun: dryRunFlag3,
55911
55964
  json: jsonFlag
55912
55965
  }).pipe(withDescription3("Pause a running campaign run"), withExamples([{ command: "samva campaigns runs pause camp_abc123 crun_def456" }]), withHandler(({ campaignId, runId, dryRun, json }) => gen2(function* () {
55913
55966
  if (dryRun)
@@ -55923,7 +55976,7 @@ var runPause = make29("pause", {
55923
55976
  var runResume = make29("resume", {
55924
55977
  campaignId: String10("campaignId"),
55925
55978
  runId: String10("runId"),
55926
- dryRun: dryRunFlag2,
55979
+ dryRun: dryRunFlag3,
55927
55980
  json: jsonFlag
55928
55981
  }).pipe(withDescription3("Resume a paused campaign run"), withExamples([{ command: "samva campaigns runs resume camp_abc123 crun_def456" }]), withHandler(({ campaignId, runId, dryRun, json }) => gen2(function* () {
55929
55982
  if (dryRun)
@@ -61003,7 +61056,7 @@ var rotateDkim = make29("rotate-dkim", {
61003
61056
  yield* log("The minimum retention date does not authorize deletion. Retirement requires separate approval and verification that at least two actual former-record TTLs have elapsed.");
61004
61057
  yield* log(`After DNS publication, advance verification: samva email domains check ${id}`);
61005
61058
  })));
61006
- var remove4 = make29("remove", {
61059
+ var remove6 = make29("remove", {
61007
61060
  id: String10("id").pipe(withDescription4("Domain id to permanently remove")),
61008
61061
  dryRun: Boolean6("dry-run").pipe(withDescription2("Print the deletion plan without calling the API"), withDefault5(false)),
61009
61062
  yes: Boolean6("yes").pipe(withDescription2("Confirm permanent deletion (required for non-interactive use)"), withDefault5(false)),
@@ -61026,7 +61079,7 @@ var remove4 = make29("remove", {
61026
61079
  return yield* printJson(data);
61027
61080
  yield* log(`Removed domain ${id}.`);
61028
61081
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
61029
- var setDefault = make29("set-default", {
61082
+ var setDefault2 = make29("set-default", {
61030
61083
  id: String10("id").pipe(withDescription4("Domain id to make the default")),
61031
61084
  dryRun: Boolean6("dry-run").pipe(withDescription2("Print the planned change without calling the API"), withDefault5(false)),
61032
61085
  json: jsonFlag
@@ -61144,7 +61197,7 @@ var customDisable = make29("disable", {
61144
61197
  var customTracking = make29("custom-tracking").pipe(withDescription3("Manage a domain's custom tracking (custom open/click subdomain)"), withExamples([{ command: "samva email domains custom-tracking enable dom_abc123" }]), withSubcommands([customEnable, customStatus, customRecheck, customDisable]));
61145
61198
  var domainsCommand = make29("domains").pipe(withDescription3("Manage email sending domains"), withExamples([
61146
61199
  { command: "samva email domains list", description: "List configured domains" }
61147
- ]), withSubcommands([list9, add2, check2, rotateDkim, setDefault, remove4, customTracking]));
61200
+ ]), withSubcommands([list9, add2, check2, rotateDkim, setDefault2, remove6, customTracking]));
61148
61201
 
61149
61202
  // src/commands/email/receiving.ts
61150
61203
  var destinationLabel = (destination) => {
@@ -61683,7 +61736,7 @@ var idempotencyKeyFlag2 = String9("idempotency-key").pipe(withDescription2("Key
61683
61736
  var trackOpensFlag2 = Literals3("track-opens", ["on", "off"]).pipe(withDescription2("Override open tracking for this send; recipient opt-out still wins"), optional4);
61684
61737
  var trackClicksFlag2 = Literals3("track-clicks", ["on", "off"]).pipe(withDescription2("Override click tracking for this send; recipient opt-out still wins"), optional4);
61685
61738
  var attachFlag = String9("attach").pipe(withDescription2("File to attach (repeatable); small files travel inline, larger ones upload through media first"), atLeast2(0));
61686
- var dryRunFlag3 = Boolean6("dry-run").pipe(withDescription2("Validate inputs and print the request payload without calling the API"), withDefault5(false));
61739
+ var dryRunFlag4 = Boolean6("dry-run").pipe(withDescription2("Validate inputs and print the request payload without calling the API"), withDefault5(false));
61687
61740
  var bodySourceName = (input) => {
61688
61741
  if (input.templateRefValue)
61689
61742
  return "template";
@@ -61720,7 +61773,7 @@ var send3 = make29("send", {
61720
61773
  trackOpens: trackOpensFlag2,
61721
61774
  trackClicks: trackClicksFlag2,
61722
61775
  attach: attachFlag,
61723
- dryRun: dryRunFlag3,
61776
+ dryRun: dryRunFlag4,
61724
61777
  wait: Boolean6("wait").pipe(withDescription2("Wait for delivered/read or a terminal delivery failure"), withDefault5(false)),
61725
61778
  timeout: timeoutFlag,
61726
61779
  json: jsonFlag
@@ -62130,7 +62183,7 @@ var check3 = make29("check", {
62130
62183
  ["Verified at", data.verifiedAt ?? null]
62131
62184
  ]);
62132
62185
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
62133
- var setDefault2 = make29("set-default", {
62186
+ var setDefault3 = make29("set-default", {
62134
62187
  id: String10("id").pipe(withDescription4("Sender id to make the default")),
62135
62188
  dryRun: Boolean6("dry-run").pipe(withDescription2("Print the planned change without calling the API"), withDefault5(false)),
62136
62189
  json: jsonFlag
@@ -62146,7 +62199,7 @@ var setDefault2 = make29("set-default", {
62146
62199
  return yield* printJson(data);
62147
62200
  yield* log(`Sender ${id} is now the default sender.`);
62148
62201
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
62149
- var remove5 = make29("remove", {
62202
+ var remove7 = make29("remove", {
62150
62203
  id: String10("id").pipe(withDescription4("Sender id to permanently remove")),
62151
62204
  dryRun: Boolean6("dry-run").pipe(withDescription2("Print the deletion plan without calling the API"), withDefault5(false)),
62152
62205
  yes: Boolean6("yes").pipe(withDescription2("Confirm permanent deletion (required for non-interactive use)"), withDefault5(false)),
@@ -62171,7 +62224,7 @@ var remove5 = make29("remove", {
62171
62224
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
62172
62225
  var sendersCommand = make29("senders").pipe(withDescription3("Manage sender addresses"), withExamples([
62173
62226
  { command: "samva email senders list", description: "List sender addresses" }
62174
- ]), withSubcommands([list12, add3, check3, setDefault2, remove5]));
62227
+ ]), withSubcommands([list12, add3, check3, setDefault3, remove7]));
62175
62228
 
62176
62229
  // src/commands/email/tracking.ts
62177
62230
  var toggle = (name, description) => Literals3(name, ["on", "off"]).pipe(withDescription2(description), optional4);
@@ -64012,7 +64065,7 @@ __export(exports_mailboxes, {
64012
64065
  mailboxes_exports: () => mailboxes_exports2,
64013
64066
  pause: () => pause,
64014
64067
  releaseMessage: () => releaseMessage,
64015
- remove: () => remove6,
64068
+ remove: () => remove8,
64016
64069
  removeEndpoint: () => removeEndpoint,
64017
64070
  removeThreadLabels: () => removeThreadLabels,
64018
64071
  reply: () => reply,
@@ -67061,7 +67114,7 @@ var mailboxes_exports2 = /* @__PURE__ */ __exportAll({
67061
67114
  listThreads: () => listThreads,
67062
67115
  pause: () => pause,
67063
67116
  releaseMessage: () => releaseMessage,
67064
- remove: () => remove6,
67117
+ remove: () => remove8,
67065
67118
  removeEndpoint: () => removeEndpoint,
67066
67119
  removeThreadLabels: () => removeThreadLabels,
67067
67120
  reply: () => reply,
@@ -67085,7 +67138,7 @@ var list16 = /* @__PURE__ */ make50("mailboxesList");
67085
67138
  var create6 = /* @__PURE__ */ make50("mailboxesCreate");
67086
67139
  var get19 = /* @__PURE__ */ make50("mailboxesGet");
67087
67140
  var update8 = /* @__PURE__ */ make50("mailboxesUpdate");
67088
- var remove6 = /* @__PURE__ */ make50("mailboxesRemove");
67141
+ var remove8 = /* @__PURE__ */ make50("mailboxesRemove");
67089
67142
  var createEndpoint = /* @__PURE__ */ make50("mailboxesCreateEndpoint");
67090
67143
  var updateEndpoint = /* @__PURE__ */ make50("mailboxesUpdateEndpoint");
67091
67144
  var removeEndpoint = /* @__PURE__ */ make50("mailboxesRemoveEndpoint");
@@ -68747,7 +68800,7 @@ var list17 = /* @__PURE__ */ make51("journeysList");
68747
68800
  var create7 = /* @__PURE__ */ make51("journeysCreate");
68748
68801
  var get20 = /* @__PURE__ */ make51("journeysGet");
68749
68802
  var update9 = /* @__PURE__ */ make51("journeysUpdate");
68750
- var remove7 = /* @__PURE__ */ make51("journeysRemove");
68803
+ var remove9 = /* @__PURE__ */ make51("journeysRemove");
68751
68804
  var replaceDraft = /* @__PURE__ */ make51("journeysReplaceDraft");
68752
68805
  var publish3 = /* @__PURE__ */ make51("journeysPublish");
68753
68806
  var pause2 = /* @__PURE__ */ make51("journeysPause");
@@ -68974,7 +69027,7 @@ var destructive = (name, description, warning, run, failure) => make29(name, {
68974
69027
  ]);
68975
69028
  }).pipe(quietWhenJson(json))));
68976
69029
  var archive4 = destructive("archive", "Archive a journey and exit its active runs", "Archive and exit every active run of", (id) => archive3({ id }), "Could not archive journey");
68977
- var remove8 = destructive("delete", "Delete a never-published or archived journey with its versions and runs", "Permanently delete", (id) => remove7({ id }), "Could not delete journey");
69030
+ var remove10 = destructive("delete", "Delete a never-published or archived journey with its versions and runs", "Permanently delete", (id) => remove9({ id }), "Could not delete journey");
68978
69031
  var getVersion3 = make29("version", {
68979
69032
  id: idArgument2,
68980
69033
  version: Int6("version").pipe(withDescription4("Published version number")),
@@ -69101,7 +69154,7 @@ var journeysCommand = make29("journeys").pipe(withDescription3("Create, publish,
69101
69154
  pause3,
69102
69155
  resume3,
69103
69156
  archive4,
69104
- remove8,
69157
+ remove10,
69105
69158
  getVersion3,
69106
69159
  stats2,
69107
69160
  runs2
@@ -72743,7 +72796,7 @@ var actionDecision = (verb) => make29(verb, {
72743
72796
  })).pipe(client.provide, mapError2((e) => mailboxApiError(`Could not ${verb} action`, e, { personOnly: verb })));
72744
72797
  yield* json ? printJson(result) : printAction(result);
72745
72798
  })));
72746
- var remove9 = make29("remove", {
72799
+ var remove11 = make29("remove", {
72747
72800
  id: String10("mailbox-id").pipe(withSchema3(MailboxId)),
72748
72801
  dryRun: Boolean6("dry-run").pipe(withDefault5(false)),
72749
72802
  yes: Boolean6("yes").pipe(withDefault5(false)),
@@ -72761,7 +72814,7 @@ var remove9 = make29("remove", {
72761
72814
  nonInteractiveMessage: "Permanent mailbox deletion requires --yes in non-interactive mode. Preview with --dry-run."
72762
72815
  });
72763
72816
  const client = yield* getClientProvider;
72764
- const result = yield* remove6({ id }).pipe(client.provide, mapError2((e) => mailboxApiError("Could not remove mailbox", e)));
72817
+ const result = yield* remove8({ id }).pipe(client.provide, mapError2((e) => mailboxApiError("Could not remove mailbox", e)));
72765
72818
  if (json)
72766
72819
  return yield* printJson(result);
72767
72820
  yield* log(`Removed mailbox ${id}. The messages it received stay with the organization.`);
@@ -73052,7 +73105,7 @@ var mailboxesCommand = make29("mailboxes").pipe(withDescription3("Mailbox readin
73052
73105
  actionCancel,
73053
73106
  actionRetry
73054
73107
  ])),
73055
- remove9,
73108
+ remove11,
73056
73109
  threads,
73057
73110
  read2,
73058
73111
  send7,
@@ -73283,7 +73336,7 @@ var list20 = /* @__PURE__ */ make52("namespacesList");
73283
73336
  var create10 = /* @__PURE__ */ make52("namespacesCreate");
73284
73337
  var get23 = /* @__PURE__ */ make52("namespacesGet");
73285
73338
  var update11 = /* @__PURE__ */ make52("namespacesUpdate");
73286
- var remove10 = /* @__PURE__ */ make52("namespacesRemove");
73339
+ var remove12 = /* @__PURE__ */ make52("namespacesRemove");
73287
73340
  var assignResource = /* @__PURE__ */ make52("namespacesAssignResource");
73288
73341
  var removeResource = /* @__PURE__ */ make52("namespacesRemoveResource");
73289
73342
  var getUsage2 = /* @__PURE__ */ make52("namespacesGetUsage");
@@ -73291,7 +73344,7 @@ var getUsage2 = /* @__PURE__ */ make52("namespacesGetUsage");
73291
73344
  // src/commands/namespaces.ts
73292
73345
  var idArgument3 = String10("id").pipe(withDescription4("Namespace id (ns_...)"));
73293
73346
  var optionalString2 = (name, description) => String9(name).pipe(withDescription2(description), optional4);
73294
- var dryRunFlag4 = Boolean6("dry-run").pipe(withDescription2("Print the planned change without calling the API"), withDefault5(false));
73347
+ var dryRunFlag5 = Boolean6("dry-run").pipe(withDescription2("Print the planned change without calling the API"), withDefault5(false));
73295
73348
  var RESOURCE_FLAGS = ["domain", "sender", "contact", "template"];
73296
73349
  var resourceFlags = {
73297
73350
  domain: optionalString2("domain", "Domain id (dom_...)"),
@@ -73381,7 +73434,7 @@ var list21 = make29("list", {
73381
73434
  var create11 = make29("create", {
73382
73435
  name: String10("name").pipe(withDescription4("Namespace name")),
73383
73436
  metadata: String9("metadata").pipe(withDescription2("Your own label, key=value (repeatable); Samva stores and never reads it"), atLeast2(0)),
73384
- dryRun: dryRunFlag4,
73437
+ dryRun: dryRunFlag5,
73385
73438
  json: jsonFlag
73386
73439
  }).pipe(withDescription3("Create a namespace, a sub-tenant for one of your customers"), withExamples([
73387
73440
  { command: "samva namespaces create Acme --metadata customerId=cus_123" },
@@ -73420,7 +73473,7 @@ var update12 = make29("update", {
73420
73473
  name: optionalString2("name", "New name"),
73421
73474
  metadata: String9("metadata").pipe(withDescription2("Set a metadata label, key=value (repeatable); other keys are kept"), atLeast2(0)),
73422
73475
  unsetMetadata: String9("unset-metadata").pipe(withDescription2("Delete a metadata key (repeatable)"), atLeast2(0)),
73423
- dryRun: dryRunFlag4,
73476
+ dryRun: dryRunFlag5,
73424
73477
  json: jsonFlag
73425
73478
  }).pipe(withDescription3("Rename a namespace or change its metadata"), withExamples([
73426
73479
  { command: "samva namespaces update ns_7q2xk9mvt4znw8rh --name Acme --metadata plan=pro" },
@@ -73467,9 +73520,9 @@ var update12 = make29("update", {
73467
73520
  ["Updated", updated.updatedAt]
73468
73521
  ]);
73469
73522
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
73470
- var remove11 = make29("delete", {
73523
+ var remove13 = make29("delete", {
73471
73524
  id: idArgument3,
73472
- dryRun: dryRunFlag4,
73525
+ dryRun: dryRunFlag5,
73473
73526
  yes: Boolean6("yes").pipe(withDefault5(false)),
73474
73527
  json: jsonFlag
73475
73528
  }).pipe(withDescription3("Delete an empty namespace; one that still holds anything is refused with a count of what remains"), withExamples([
@@ -73485,7 +73538,7 @@ var remove11 = make29("delete", {
73485
73538
  nonInteractiveMessage: "Namespace deletion requires --yes in non-interactive mode. Preview with --dry-run."
73486
73539
  });
73487
73540
  const client = yield* getClientProvider;
73488
- const result = yield* remove10({ id }).pipe(client.provide, mapError2((error) => toCliApiError("Could not delete namespace", error)));
73541
+ const result = yield* remove12({ id }).pipe(client.provide, mapError2((error) => toCliApiError("Could not delete namespace", error)));
73489
73542
  if (json)
73490
73543
  return yield* printJson(result);
73491
73544
  yield* log(`Deleted namespace ${id}.`);
@@ -73493,7 +73546,7 @@ var remove11 = make29("delete", {
73493
73546
  var assign2 = make29("assign", {
73494
73547
  id: idArgument3,
73495
73548
  ...resourceFlags,
73496
- dryRun: dryRunFlag4,
73549
+ dryRun: dryRunFlag5,
73497
73550
  json: jsonFlag
73498
73551
  }).pipe(withDescription3("Move a domain, sender, contact, or template into a namespace; pass exactly one resource flag"), withExamples([
73499
73552
  { command: "samva namespaces assign ns_7q2xk9mvt4znw8rh --domain dom_xyz789" },
@@ -73523,7 +73576,7 @@ var assign2 = make29("assign", {
73523
73576
  var unassign = make29("unassign", {
73524
73577
  id: idArgument3,
73525
73578
  ...resourceFlags,
73526
- dryRun: dryRunFlag4,
73579
+ dryRun: dryRunFlag5,
73527
73580
  json: jsonFlag
73528
73581
  }).pipe(withDescription3("Return a domain, sender, contact, or template to organization level; pass exactly one resource flag"), withExamples([
73529
73582
  { command: "samva namespaces unassign ns_7q2xk9mvt4znw8rh --domain dom_xyz789" }
@@ -73571,7 +73624,7 @@ var usage3 = make29("usage", {
73571
73624
  ["Total", data.totalMessages]
73572
73625
  ]);
73573
73626
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
73574
- var namespacesCommand = make29("namespaces").pipe(withDescription3("Manage namespaces, the sub-tenants of your organization"), withExamples([{ command: "samva namespaces list --json" }]), withSubcommands([list21, create11, get24, update12, remove11, assign2, unassign, usage3]));
73627
+ var namespacesCommand = make29("namespaces").pipe(withDescription3("Manage namespaces, the sub-tenants of your organization"), withExamples([{ command: "samva namespaces list --json" }]), withSubcommands([list21, create11, get24, update12, remove13, assign2, unassign, usage3]));
73575
73628
 
73576
73629
  // ../sdk/dist/effect/generated/whoami.js
73577
73630
  var WhoamiUser = /* @__PURE__ */ Struct({
@@ -73912,7 +73965,7 @@ var use2 = make29("use", { name: String10("name") }).pipe(withDescription3("Sele
73912
73965
  yield* writeConfig({ ...config, activeProfile: name });
73913
73966
  yield* printResult({ action: "profile.use", name }, `Active profile set to ${name}.`);
73914
73967
  })));
73915
- var remove12 = make29("delete", {
73968
+ var remove14 = make29("delete", {
73916
73969
  name: String10("name"),
73917
73970
  yes: Boolean6("yes").pipe(withDefault5(false))
73918
73971
  }).pipe(withDescription3("Delete a profile and its OS-keyring credential"), withHandler(fnUntraced2(function* ({ name, yes }) {
@@ -73937,7 +73990,7 @@ var remove12 = make29("delete", {
73937
73990
  yield* (yield* Service4).clear(name);
73938
73991
  yield* printResult({ action: "profile.delete", name }, `Deleted profile ${name}.`);
73939
73992
  })));
73940
- var profileCommand = make29("profile").pipe(withDescription3("Manage user-level credential identity and organization contexts"), withSubcommands([list23, show, create12, use2, remove12]));
73993
+ var profileCommand = make29("profile").pipe(withDescription3("Manage user-level credential identity and organization contexts"), withSubcommands([list23, show, create12, use2, remove14]));
73941
73994
 
73942
73995
  // ../sdk/dist/effect/generated/scheduled-messages.js
73943
73996
  var ScheduledMessageId2 = /* @__PURE__ */ String4.check(isPattern2(/* @__PURE__ */ new RegExp("^sched_[0-9a-hjkmnp-tv-z]{16,24}$", "u")).annotate({
@@ -74194,7 +74247,7 @@ var atFlag2 = String9("at").pipe(withDescription2("Absolute send instant as an I
74194
74247
  var timezoneFlag = String9("timezone").pipe(withDescription2("IANA timezone for the schedule, e.g. America/New_York"), optional4);
74195
74248
  var idempotencyKeyFlag3 = String9("idempotency-key").pipe(withDescription2("Idempotency key so retries do not schedule duplicates"), optional4);
74196
74249
  var referenceIdFlag = String9("reference-id").pipe(withDescription2("Your own reference id to correlate this schedule"), optional4);
74197
- var dryRunFlag5 = Boolean6("dry-run").pipe(withDescription2("Validate inputs and print the request payload without calling the API"), withDefault5(false));
74250
+ var dryRunFlag6 = Boolean6("dry-run").pipe(withDescription2("Validate inputs and print the request payload without calling the API"), withDefault5(false));
74198
74251
  var trackOpensFlag3 = Literals3("track-opens", ["on", "off"]).pipe(withDescription2("Override open tracking for this scheduled send; recipient opt-out wins"), optional4);
74199
74252
  var trackClicksFlag3 = Literals3("track-clicks", ["on", "off"]).pipe(withDescription2("Override click tracking for this scheduled send; recipient opt-out wins"), optional4);
74200
74253
  var statusFlag2 = Literals3("status", SCHEDULED_STATUSES).pipe(withDescription2("Filter by status"), optional4);
@@ -74219,7 +74272,7 @@ var create14 = make29("create", {
74219
74272
  referenceId: referenceIdFlag,
74220
74273
  trackOpens: trackOpensFlag3,
74221
74274
  trackClicks: trackClicksFlag3,
74222
- dryRun: dryRunFlag5,
74275
+ dryRun: dryRunFlag6,
74223
74276
  json: jsonFlag
74224
74277
  }).pipe(withDescription3("Schedule an email to send at a future time"), withExamples([
74225
74278
  {
@@ -74598,7 +74651,7 @@ var subscriptionsCommand = make29("subscriptions").pipe(withDescription3("Reques
74598
74651
  // src/commands/templates.ts
74599
74652
  import { constants as constants2 } from "fs";
74600
74653
  import { access as access3, stat as stat5 } from "fs/promises";
74601
- import { dirname as dirname5, resolve as resolve16 } from "path";
74654
+ import { basename as basename3, dirname as dirname5, resolve as resolve17 } from "path";
74602
74655
  import { pathToFileURL as pathToFileURL5 } from "url";
74603
74656
 
74604
74657
  // ../sdk/dist/effect/generated/templates.js
@@ -76862,7 +76915,7 @@ var create15 = /* @__PURE__ */ make54("templatesCreate");
76862
76915
  var createFromWebsite = /* @__PURE__ */ make54("templatesCreateFromWebsite");
76863
76916
  var addEntry = /* @__PURE__ */ make54("templatesAddEntry");
76864
76917
  var getById = /* @__PURE__ */ make54("templatesGetById");
76865
- var remove13 = /* @__PURE__ */ make54("templatesRemove");
76918
+ var remove15 = /* @__PURE__ */ make54("templatesRemove");
76866
76919
  var update13 = /* @__PURE__ */ make54("templatesUpdate");
76867
76920
  var openWorkspace = /* @__PURE__ */ make54("templatesOpenWorkspace");
76868
76921
  var getGitRepository = /* @__PURE__ */ make54("templatesGetGitRepository");
@@ -76886,6 +76939,146 @@ var offlineFlag = Boolean6("offline").pipe(withDescription2("Use the cached bran
76886
76939
 
76887
76940
  // src/commands/templates/toolchain.ts
76888
76941
  import { pathToFileURL as pathToFileURL3 } from "url";
76942
+ // ../../node_modules/.bun/@samva+markup@0.17.3+a19bcd7db259aaf7/node_modules/@samva/markup/package.json
76943
+ var package_default2 = {
76944
+ name: "@samva/markup",
76945
+ version: "0.17.3",
76946
+ description: "Type-safe authoring and compilation primitives for Samva templates",
76947
+ keywords: [
76948
+ "email",
76949
+ "jsx",
76950
+ "samva",
76951
+ "sml",
76952
+ "sms",
76953
+ "templates",
76954
+ "tsx",
76955
+ "whatsapp"
76956
+ ],
76957
+ homepage: "https://samva.dev",
76958
+ bugs: {
76959
+ url: "https://github.com/SamvaHQ/SML/issues"
76960
+ },
76961
+ license: "MIT",
76962
+ author: "Arya Labs, Inc.",
76963
+ repository: {
76964
+ type: "git",
76965
+ url: "https://github.com/SamvaHQ/SML.git",
76966
+ directory: "packages/markup"
76967
+ },
76968
+ files: [
76969
+ "dist",
76970
+ "samva-brand.d.ts",
76971
+ "generated",
76972
+ "skills",
76973
+ "package.json",
76974
+ "README.md",
76975
+ "CHANGELOG.md",
76976
+ "AGENTS.md",
76977
+ "LICENSE"
76978
+ ],
76979
+ type: "module",
76980
+ sideEffects: false,
76981
+ exports: {
76982
+ ".": {
76983
+ types: "./dist/index.d.ts",
76984
+ import: "./dist/index.js"
76985
+ },
76986
+ "./email": {
76987
+ types: "./dist/email.d.ts",
76988
+ import: "./dist/email.js"
76989
+ },
76990
+ "./email/jsx-runtime": {
76991
+ types: "./dist/email/jsx-runtime.d.ts",
76992
+ import: "./dist/email/jsx-runtime.js"
76993
+ },
76994
+ "./email/jsx-dev-runtime": {
76995
+ types: "./dist/email/jsx-dev-runtime.d.ts",
76996
+ import: "./dist/email/jsx-dev-runtime.js"
76997
+ },
76998
+ "./fmt": {
76999
+ types: "./dist/fmt.d.ts",
77000
+ import: "./dist/fmt.js"
77001
+ },
77002
+ "./sms": {
77003
+ types: "./dist/sms.d.ts",
77004
+ import: "./dist/sms.js"
77005
+ },
77006
+ "./whatsapp": {
77007
+ types: "./dist/whatsapp.d.ts",
77008
+ import: "./dist/whatsapp.js"
77009
+ },
77010
+ "./input-schema": {
77011
+ types: "./dist/input-schema.d.ts",
77012
+ import: "./dist/input-schema.js"
77013
+ },
77014
+ "./compiler": {
77015
+ types: "./dist/compiler.d.ts",
77016
+ import: "./dist/compiler.js"
77017
+ },
77018
+ "./compatibility": {
77019
+ types: "./dist/compatibility.d.ts",
77020
+ import: "./dist/compatibility.js"
77021
+ },
77022
+ "./diagnostics": {
77023
+ types: "./dist/diagnostics.d.ts",
77024
+ import: "./dist/diagnostics.js"
77025
+ },
77026
+ "./render": {
77027
+ types: "./dist/render.d.ts",
77028
+ import: "./dist/render.js"
77029
+ },
77030
+ "./edit": {
77031
+ types: "./dist/edit.d.ts",
77032
+ import: "./dist/edit.js"
77033
+ },
77034
+ "./brand": {
77035
+ types: "./dist/brand.d.ts",
77036
+ import: "./dist/brand.js"
77037
+ },
77038
+ "./samva-brand": {
77039
+ types: "./samva-brand.d.ts"
77040
+ },
77041
+ "./contract.json": "./generated/contract.json",
77042
+ "./reference.md": "./generated/reference.md",
77043
+ "./package.json": "./package.json"
77044
+ },
77045
+ publishConfig: {
77046
+ access: "public"
77047
+ },
77048
+ scripts: {
77049
+ build: "tsdown",
77050
+ prepack: "bun run build",
77051
+ "corpus:render": "bun run scripts/render-compatibility-corpus.ts",
77052
+ "codegen:tailwind": "bun run scripts/codegen-tailwind.ts && oxfmt src/email/tailwind-stylesheets.gen.ts",
77053
+ "codegen:contract": "bun run scripts/generate-contract.ts",
77054
+ test: "vitest run",
77055
+ typecheck: "tsc --noEmit && tsc --noEmit -p tests/consumer/tsconfig.json"
77056
+ },
77057
+ dependencies: {
77058
+ "@babel/parser": "^8.0.4",
77059
+ "@cfworker/json-schema": "4.1.1",
77060
+ "@standard-schema/spec": "1.1.0",
77061
+ "better-result": "3.0.1",
77062
+ caniemail: "2.0.2",
77063
+ "css-tree": "3.2.1",
77064
+ tailwindcss: "^4.3.3"
77065
+ },
77066
+ devDependencies: {
77067
+ "@css-inline/css-inline-wasm": "0.15.0",
77068
+ "@effect/vitest": "4.0.0",
77069
+ "@types/css-tree": "3.2.0",
77070
+ "@types/node": "^26.6.2",
77071
+ effect: "4.0.0",
77072
+ tsdown: "^0.22.13",
77073
+ typescript: "^7.0.2",
77074
+ vitest: "^5.0.3",
77075
+ zod: "4.6.5"
77076
+ }
77077
+ };
77078
+
77079
+ // src/lib/authoring-release.ts
77080
+ var minorOf = (version) => version.split(".").slice(0, 2).join(".");
77081
+ var AUTHORING_MINOR = minorOf(package_default2.version);
76889
77082
 
76890
77083
  // src/commands/templates/brand-resolver.ts
76891
77084
  var brand_resolver_default = `import { execFile } from "node:child_process";
@@ -77316,6 +77509,30 @@ export const createBrandResolver = (options: {
77316
77509
 
77317
77510
  // src/commands/templates/toolchain.ts
77318
77511
  var TOOLCHAIN_INSTALL = "npm install --save-dev @samva/markup @samva/vite vite";
77512
+ var AUTHORING_PACKAGES = ["@samva/markup", "@samva/vite"];
77513
+ var toolchainSkew = (installed, action) => {
77514
+ for (const name of AUTHORING_PACKAGES) {
77515
+ const version = installed[name];
77516
+ if (version === undefined || minorOf(version) === AUTHORING_MINOR)
77517
+ continue;
77518
+ const behind = Number(minorOf(version).split(".")[1]) < Number(AUTHORING_MINOR.split(".")[1]);
77519
+ return behind ? `${action}: this project has ${name} ${version}, but this CLI drives ${AUTHORING_MINOR}.x. Update the project with \`npm install --save-dev @samva/markup@~${AUTHORING_MINOR}.0 @samva/vite@~${AUTHORING_MINOR}.0\`.` : `${action}: this project has ${name} ${version}, which is newer than the ${AUTHORING_MINOR}.x this CLI drives. Update the CLI with \`npm install -g @samva/cli@latest\`.`;
77520
+ }
77521
+ return;
77522
+ };
77523
+ var installedAuthoring = async (root) => Object.fromEntries(await Promise.all(AUTHORING_PACKAGES.map(async (name) => {
77524
+ const manifest = await Promise.resolve().then(async () => await Bun.file(Bun.resolveSync(`${name}/package.json`, root)).json()).catch(() => {
77525
+ return;
77526
+ });
77527
+ return [name, manifest?.version];
77528
+ })));
77529
+ var requireCompatibleToolchain = (action) => tryPromise2({
77530
+ try: () => installedAuthoring(process.cwd()),
77531
+ catch: () => new SamvaCliError({ message: `${action}: could not read the project's installed packages.` })
77532
+ }).pipe(flatMap3((installed) => {
77533
+ const skew = toolchainSkew(installed, action);
77534
+ return skew === undefined ? void_3 : fail6(new SamvaCliError({ message: skew }));
77535
+ }));
77319
77536
  var MISSING_MODULE = /Cannot find (?:package|module) '([^']+)'/;
77320
77537
  var toolchainFailure = (stderr, action) => {
77321
77538
  const missing = MISSING_MODULE.exec(stderr)?.[1];
@@ -77327,28 +77544,42 @@ var toolchainFailure = (stderr, action) => {
77327
77544
  }
77328
77545
  return `${action}: ${stderr.trim() || "the toolchain process failed."}`;
77329
77546
  };
77547
+ var invocationEnv = gen2(function* () {
77548
+ const profile = getOrUndefined(yield* Profile);
77549
+ const org = getOrUndefined(yield* Org);
77550
+ const apiUrl = getOrUndefined(yield* ApiUrl);
77551
+ return {
77552
+ ...profile === undefined ? {} : { SAMVA_PROFILE: profile },
77553
+ ...org === undefined ? {} : { SAMVA_ORG: org },
77554
+ ...apiUrl === undefined ? {} : { SAMVA_API_URL: apiUrl }
77555
+ };
77556
+ });
77330
77557
  var NODE_MISSING = "Install Node.js 22+ to run template authoring commands.";
77331
- var runToolchain = (input) => tryPromise2({
77332
- try: async () => {
77333
- const child = Bun.spawn(["node", "--input-type=module", "--eval", input.script], {
77334
- cwd: process.cwd(),
77335
- env: { ...Bun.env, SAMVA_TOOLCHAIN_INPUT: JSON.stringify(input.options) },
77336
- stdout: "pipe",
77337
- stderr: "pipe"
77338
- });
77339
- const [stdout, stderr, exitCode] = await Promise.all([
77340
- new Response(child.stdout).text(),
77341
- new Response(child.stderr).text(),
77342
- child.exited
77343
- ]);
77344
- if (exitCode !== 0) {
77345
- return Promise.reject(new SamvaCliError({ message: toolchainFailure(stderr, input.action) }));
77346
- }
77347
- return JSON.parse(stdout);
77348
- },
77349
- catch: (cause) => cause instanceof SamvaCliError ? cause : new SamvaCliError({
77350
- message: `${input.action}: ${NODE_MISSING} ${String(cause)}`
77351
- })
77558
+ var runToolchain = (input) => gen2(function* () {
77559
+ yield* requireCompatibleToolchain(input.action);
77560
+ const selection = yield* invocationEnv;
77561
+ return yield* tryPromise2({
77562
+ try: async () => {
77563
+ const child = Bun.spawn(["node", "--input-type=module", "--eval", input.script], {
77564
+ cwd: process.cwd(),
77565
+ env: { ...Bun.env, ...selection, SAMVA_TOOLCHAIN_INPUT: JSON.stringify(input.options) },
77566
+ stdout: "pipe",
77567
+ stderr: "pipe"
77568
+ });
77569
+ const [stdout, stderr, exitCode] = await Promise.all([
77570
+ new Response(child.stdout).text(),
77571
+ new Response(child.stderr).text(),
77572
+ child.exited
77573
+ ]);
77574
+ if (exitCode !== 0) {
77575
+ return Promise.reject(new SamvaCliError({ message: toolchainFailure(stderr, input.action) }));
77576
+ }
77577
+ return JSON.parse(stdout);
77578
+ },
77579
+ catch: (cause) => cause instanceof SamvaCliError ? cause : new SamvaCliError({
77580
+ message: `${input.action}: ${NODE_MISSING} ${String(cause)}`
77581
+ })
77582
+ });
77352
77583
  });
77353
77584
  var OPTIONS_PRELUDE = "const options = JSON.parse(process.env.SAMVA_TOOLCHAIN_INPUT);";
77354
77585
  var brandResolverPrelude = () => `${new Bun.Transpiler({ loader: "ts" }).transformSync(brand_resolver_default)}
@@ -77539,8 +77770,24 @@ var buildDir = (dir, assetBase) => runToolchain({
77539
77770
  ${brandResolverPrelude()} process.stdout.write(JSON.stringify(await buildTemplates(options)));`
77540
77771
  });
77541
77772
 
77542
- // src/commands/templates/project.ts
77773
+ // src/commands/templates/local-export.ts
77543
77774
  import { resolve as resolve10 } from "path";
77775
+ var exportProject = (input) => runToolchain({
77776
+ action: `Could not export templates${input.dir === undefined ? "" : ` in ${input.dir}`}`,
77777
+ options: {
77778
+ root: process.cwd(),
77779
+ out: resolve10(input.out),
77780
+ ...input.dir === undefined ? {} : { dir: resolve10(input.dir) },
77781
+ ...input.assetBase === undefined ? {} : { assetBase: input.assetBase },
77782
+ ...input.offline === true ? { offline: true } : {}
77783
+ },
77784
+ script: `import { exportProject } from "@samva/vite/project"; ${OPTIONS_PRELUDE}
77785
+ ${brandResolverPrelude()}
77786
+ process.stdout.write(JSON.stringify(await exportProject(options)));`
77787
+ });
77788
+
77789
+ // src/commands/templates/project.ts
77790
+ import { resolve as resolve11 } from "path";
77544
77791
  var templateId = String10("id").pipe(withDescription4("Template id"));
77545
77792
  var projectOrTemplateId = String10("id").pipe(withDescription4("Template project id (tproj_...) or the id of any template in it"));
77546
77793
  var runGit = (args, options = {}) => tryPromise2({
@@ -77627,7 +77874,7 @@ var cloneProject = fn2("Templates.cloneProject")(function* (input) {
77627
77874
  getById({ id: cloneTemplateId }).pipe(client.provide),
77628
77875
  getGitRepository({ id: cloneTemplateId }).pipe(client.provide)
77629
77876
  ], { concurrency: 2 }).pipe(mapError2((error) => toCliApiError("Could not resolve template project", error)));
77630
- const root = resolve10(input.dir);
77877
+ const root = resolve11(input.dir);
77631
77878
  const helper = yield* helperCommand(template.id, "read");
77632
77879
  yield* runGit([
77633
77880
  "-c",
@@ -77725,7 +77972,7 @@ var init2 = make29("init", {
77725
77972
  description: "Create deterministically for CI or a coding agent"
77726
77973
  }
77727
77974
  ]), withHandler(({ name, projectName, slug, dir, starter, idempotencyKey, dryRun, json }) => gen2(function* () {
77728
- const root = resolve10(getOrElse(dir, () => projectDirectory(name)));
77975
+ const root = resolve11(getOrElse(dir, () => projectDirectory(name)));
77729
77976
  const key = getOrUndefined(idempotencyKey);
77730
77977
  const request = {
77731
77978
  name,
@@ -78080,10 +78327,12 @@ ${lines.join(`
78080
78327
 
78081
78328
  // src/commands/templates/render-command.ts
78082
78329
  import { mkdir as mkdir3, writeFile as writeFile4 } from "fs/promises";
78083
- import { dirname as dirname3, resolve as resolve12 } from "path";
78330
+ import { dirname as dirname3, resolve as resolve13 } from "path";
78084
78331
 
78085
78332
  // src/commands/templates/local-render.ts
78086
- import { resolve as resolve11 } from "path";
78333
+ import { mkdtemp as mkdtemp2, readFile as readFile5, rm as rm2 } from "fs/promises";
78334
+ import { tmpdir as tmpdir2 } from "os";
78335
+ import { join as join4, resolve as resolve12 } from "path";
78087
78336
  var renderLocal = (input) => runToolchain({
78088
78337
  action: `Could not render ${input.template}`,
78089
78338
  options: {
@@ -78091,7 +78340,7 @@ var renderLocal = (input) => runToolchain({
78091
78340
  template: input.template,
78092
78341
  fixture: input.fixture,
78093
78342
  channel: input.channel,
78094
- ...input.dir === undefined ? {} : { dir: resolve11(input.dir) },
78343
+ ...input.dir === undefined ? {} : { dir: resolve12(input.dir) },
78095
78344
  ...input.locale === undefined ? {} : { locale: input.locale },
78096
78345
  ...input.timeZone === undefined ? {} : { timeZone: input.timeZone },
78097
78346
  ...input.assetBase === undefined ? {} : { assetBase: input.assetBase },
@@ -78100,6 +78349,55 @@ var renderLocal = (input) => runToolchain({
78100
78349
  script: `import { renderChannel, renderTemplate } from "@samva/vite/render"; ${OPTIONS_PRELUDE}
78101
78350
  ${brandResolverPrelude()} const { channel, ...rest } = options; const result = channel === "email" ? await renderTemplate(rest) : await renderChannel({ ...rest, channel }); process.stdout.write(JSON.stringify(result));`
78102
78351
  });
78352
+ var finding = (code, message) => ({
78353
+ code,
78354
+ severity: "error",
78355
+ message,
78356
+ origins: []
78357
+ });
78358
+ var renderWithProjectAssets = (input) => gen2(function* () {
78359
+ const scratch = yield* acquireRelease2(tryPromise2({
78360
+ try: () => mkdtemp2(join4(tmpdir2(), "samva-snapshot-export-")),
78361
+ catch: (cause) => new SamvaCliError({ message: `Could not create a scratch directory: ${String(cause)}` })
78362
+ }), (path) => promise2(() => rm2(path, { recursive: true, force: true })));
78363
+ const receipt = yield* exportProject({
78364
+ dir: input.dir,
78365
+ out: scratch,
78366
+ assetBase: join4(scratch, "assets"),
78367
+ offline: input.offline
78368
+ });
78369
+ const entry = input.template.replace(/^\.\//, "");
78370
+ const template = receipt.templates.find((candidate) => candidate.id === entry || candidate.entryPath === entry);
78371
+ const others = receipt.templates.filter((candidate) => candidate !== template).map((candidate) => candidate.entryPath);
78372
+ const diagnostics = receipt.diagnostics.filter((diagnostic) => {
78373
+ const file = diagnostic.origins[0]?.fileName ?? "";
78374
+ return !others.some((path) => file === path || file.endsWith(`/${path}`));
78375
+ });
78376
+ if (template === undefined)
78377
+ return {
78378
+ ok: false,
78379
+ diagnostics: [
78380
+ ...diagnostics,
78381
+ finding("template-not-found", `No template ${JSON.stringify(input.template)} in the project. Known templates: ${receipt.templates.map((candidate) => candidate.id).join(", ") || "none"}.`)
78382
+ ]
78383
+ };
78384
+ if (diagnostics.some((diagnostic) => diagnostic.severity === "error"))
78385
+ return { ok: false, diagnostics };
78386
+ const page = template.fixtures.find((candidate) => candidate.fixture === input.fixture)?.html;
78387
+ if (page === undefined)
78388
+ return {
78389
+ ok: false,
78390
+ diagnostics: [
78391
+ ...diagnostics,
78392
+ finding("unknown-fixture", `Template ${template.id} has no email fixture ${JSON.stringify(input.fixture)}. Known fixtures: ${template.fixtures.map((candidate) => candidate.fixture).join(", ") || "none"}.`)
78393
+ ]
78394
+ };
78395
+ const html = yield* tryPromise2({
78396
+ try: () => readFile5(join4(scratch, page), "utf8"),
78397
+ catch: (cause) => new SamvaCliError({ message: `Could not read the exported page: ${String(cause)}` })
78398
+ });
78399
+ return { ok: true, template: template.id, html, diagnostics };
78400
+ });
78103
78401
 
78104
78402
  // src/commands/templates/render-command.ts
78105
78403
  var renderPayload = (result, out) => ({
@@ -78175,7 +78473,7 @@ var renderCommand = make29("render", {
78175
78473
  json
78176
78474
  }) => gen2(function* () {
78177
78475
  const machine = json || format === "json";
78178
- const outPath = map(out, (value) => resolve12(value)).pipe(getOrUndefined);
78476
+ const outPath = map(out, (value) => resolve13(value)).pipe(getOrUndefined);
78179
78477
  const result = yield* renderLocal({
78180
78478
  template,
78181
78479
  fixture,
@@ -78225,13 +78523,13 @@ var renderCommand = make29("render", {
78225
78523
  }).pipe(json || format === "json" ? provideService2(MinimumLogLevel2, "None") : identity)));
78226
78524
 
78227
78525
  // src/commands/templates/snapshot-command.ts
78228
- import { resolve as resolve13 } from "path";
78526
+ import { resolve as resolve14 } from "path";
78229
78527
 
78230
78528
  // src/commands/templates/snapshot-browser.ts
78231
78529
  import { existsSync } from "fs";
78232
- import { copyFile as copyFile3, mkdir as mkdir4, mkdtemp as mkdtemp2, readFile as readFile5, rm as rm2, stat as stat4, writeFile as writeFile5 } from "fs/promises";
78233
- import { tmpdir as tmpdir2 } from "os";
78234
- import { join as join4 } from "path";
78530
+ import { copyFile as copyFile3, mkdir as mkdir4, mkdtemp as mkdtemp3, readFile as readFile6, rm as rm3, stat as stat4, writeFile as writeFile5 } from "fs/promises";
78531
+ import { tmpdir as tmpdir3 } from "os";
78532
+ import { join as join5 } from "path";
78235
78533
  import { pathToFileURL as pathToFileURL4 } from "url";
78236
78534
  var VIEWPORTS = {
78237
78535
  desktop: { width: 1280, height: 800 },
@@ -78242,8 +78540,8 @@ var BROWSER_REMEDY = "Install a browser to screenshot with: add `playwright` to
78242
78540
  var snapshotFileName = (template, fixture, viewport, scheme) => `${template}-${fixture}-${viewport}-${scheme}.png`;
78243
78541
  var takeSnapshots = async (input) => {
78244
78542
  await mkdir4(input.outDir, { recursive: true });
78245
- const scratch = await mkdtemp2(join4(tmpdir2(), "samva-snapshot-"));
78246
- const htmlPath = join4(scratch, "page.html");
78543
+ const scratch = await mkdtemp3(join5(tmpdir3(), "samva-snapshot-"));
78544
+ const htmlPath = join5(scratch, "page.html");
78247
78545
  await writeFile5(htmlPath, input.html);
78248
78546
  const requests = input.viewports.flatMap((viewport) => input.schemes.map((scheme) => ({
78249
78547
  htmlPath,
@@ -78251,7 +78549,7 @@ var takeSnapshots = async (input) => {
78251
78549
  width: VIEWPORTS[viewport].width,
78252
78550
  height: VIEWPORTS[viewport].height,
78253
78551
  scheme,
78254
- outFile: join4(input.outDir, snapshotFileName(input.template, input.fixture, viewport, scheme))
78552
+ outFile: join5(input.outDir, snapshotFileName(input.template, input.fixture, viewport, scheme))
78255
78553
  })));
78256
78554
  const reasons = [];
78257
78555
  try {
@@ -78272,7 +78570,7 @@ var takeSnapshots = async (input) => {
78272
78570
  }
78273
78571
  return { ok: false, reasons };
78274
78572
  } finally {
78275
- await rm2(scratch, { recursive: true, force: true });
78573
+ await rm3(scratch, { recursive: true, force: true });
78276
78574
  }
78277
78575
  };
78278
78576
  var CHROME_PATHS = [
@@ -78366,10 +78664,10 @@ var chromeLauncher = (binary, run = runProcess) => ({
78366
78664
  status: "unavailable",
78367
78665
  reason: "no Chrome or Chromium found in the usual locations and SAMVA_CHROME is not set"
78368
78666
  };
78369
- const profile = await mkdtemp2(join4(tmpdir2(), "samva-chrome-"));
78667
+ const profile = await mkdtemp3(join5(tmpdir3(), "samva-chrome-"));
78370
78668
  try {
78371
78669
  for (const request of requests) {
78372
- const source = await readFile5(request.htmlPath, "utf8");
78670
+ const source = await readFile6(request.htmlPath, "utf8");
78373
78671
  const measured = request.htmlPath.replace(/\.html$/, `.${request.viewport}.${request.scheme}.html`);
78374
78672
  await writeFile5(measured, withMeasurement(source));
78375
78673
  const measure = await run(binary, chromeMeasureArgs({
@@ -78380,7 +78678,7 @@ var chromeLauncher = (binary, run = runProcess) => ({
78380
78678
  url: pathToFileURL4(measured).href
78381
78679
  }), { stdoutIncludes: "</html>" });
78382
78680
  const height = Math.max(request.height, parseMeasuredHeight(measure.stdout) ?? request.height);
78383
- const staged = join4(profile, `shot-${request.viewport}-${request.scheme}.png`);
78681
+ const staged = join5(profile, `shot-${request.viewport}-${request.scheme}.png`);
78384
78682
  const shot = await run(binary, chromeScreenshotArgs({
78385
78683
  profile,
78386
78684
  width: request.width,
@@ -78399,7 +78697,7 @@ var chromeLauncher = (binary, run = runProcess) => ({
78399
78697
  }
78400
78698
  return { status: "done" };
78401
78699
  } finally {
78402
- await rm2(profile, { recursive: true, force: true });
78700
+ await rm3(profile, { recursive: true, force: true });
78403
78701
  }
78404
78702
  }
78405
78703
  });
@@ -78491,7 +78789,7 @@ var snapshotCommand = make29("snapshot", {
78491
78789
  out: Directory2("out").pipe(withDescription2("Directory to write the PNGs into"), withDefault5(".samva/snapshots")),
78492
78790
  viewport: String9("viewport").pipe(withDescription2(`Comma-separated viewports: ${Object.entries(VIEWPORTS).map(([name, size]) => `${name} (${size.width}px)`).join(", ")}`), withDefault5("desktop,mobile")),
78493
78791
  scheme: String9("scheme").pipe(withDescription2("Comma-separated color schemes: light, dark"), withDefault5("light,dark")),
78494
- assetBase: assetBaseFlag,
78792
+ assetBase: String9("asset-base").pipe(withDescription2("HTTPS origin the template's imported assets are served from; omit to use the project's own asset files"), optional4),
78495
78793
  offline: offlineFlag,
78496
78794
  json: jsonFlag
78497
78795
  }).pipe(withDescription3("Screenshot one fixture in a headless browser at desktop and mobile widths, light and dark"), withExamples([
@@ -78503,17 +78801,18 @@ var snapshotCommand = make29("snapshot", {
78503
78801
  command: "samva templates snapshot welcome --fixture default --viewport mobile --scheme dark --out shots",
78504
78802
  description: "One screenshot: mobile, dark"
78505
78803
  }
78506
- ]), withHandler(({ template, fixture, dir, out, viewport, scheme, assetBase, offline, json }) => gen2(function* () {
78804
+ ]), withHandler(({ template, fixture, dir, out, viewport, scheme, assetBase, offline, json }) => scoped2(gen2(function* () {
78507
78805
  const viewports = yield* parseChoices("viewport", viewport, Object.keys(VIEWPORTS));
78508
78806
  const schemes = yield* parseChoices("scheme", scheme, SCHEMES);
78509
- const rendered = yield* renderLocal({
78510
- template,
78511
- fixture,
78512
- channel: "email",
78513
- dir: getOrUndefined(dir),
78514
- assetBase: getOrUndefined(assetBase),
78515
- offline
78516
- });
78807
+ const base = getOrUndefined(assetBase);
78808
+ if (base !== undefined && !/^https:\/\//i.test(base)) {
78809
+ return yield* new CliUsageError({
78810
+ code: "invalid_asset_base",
78811
+ message: `--asset-base must be an HTTPS origin; got ${base}. Omit it to screenshot with the project's own asset files.`
78812
+ });
78813
+ }
78814
+ const input = { template, fixture, dir: getOrUndefined(dir) };
78815
+ const rendered = base === undefined ? yield* renderWithProjectAssets({ ...input, channel: "email", offline }) : yield* renderLocal({ ...input, channel: "email", assetBase: base, offline });
78517
78816
  if (!rendered.ok || !("html" in rendered)) {
78518
78817
  const message = `Could not render ${template} (${fixture}) to screenshot it.`;
78519
78818
  const diagnostics = rendered.diagnostics.map((diagnostic) => reportDiagnostic(diagnostic, { file: "." }));
@@ -78530,7 +78829,7 @@ var snapshotCommand = make29("snapshot", {
78530
78829
  }
78531
78830
  return yield* new SamvaCliError({ message });
78532
78831
  }
78533
- const outDir = resolve13(out);
78832
+ const outDir = resolve14(out);
78534
78833
  const shots = yield* promise2(() => takeSnapshots({
78535
78834
  html: rendered.html,
78536
78835
  template: rendered.template,
@@ -78558,10 +78857,10 @@ var snapshotCommand = make29("snapshot", {
78558
78857
  return yield* printJson(result);
78559
78858
  for (const file of shots.files)
78560
78859
  yield* log(file.path);
78561
- }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
78860
+ })).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
78562
78861
 
78563
78862
  // src/commands/templates/types-command.ts
78564
- import { dirname as dirname4, resolve as resolve14 } from "path";
78863
+ import { dirname as dirname4, resolve as resolve15 } from "path";
78565
78864
 
78566
78865
  // src/commands/templates/schema-types.ts
78567
78866
  var RESERVED = new Set([
@@ -78609,6 +78908,93 @@ var pascalCase = (value, fallback) => {
78609
78908
  const joined = parts.map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("").replace(/^[0-9]+/, "");
78610
78909
  return joined.length === 0 || RESERVED.has(joined.toLowerCase()) ? fallback : joined;
78611
78910
  };
78911
+ var text2 = (value) => ({ kind: "text", text: value });
78912
+ var NEVER = text2("never");
78913
+ var union2 = (members) => members.length === 1 ? members[0] ?? NEVER : { kind: "union", members: members.flatMap((m) => m.kind === "union" ? m.members : [m]) };
78914
+ var PRINT_WIDTH = 100;
78915
+ var pad2 = (indent) => " ".repeat(indent);
78916
+ var isFlat = (doc) => {
78917
+ switch (doc.kind) {
78918
+ case "text":
78919
+ return true;
78920
+ case "object":
78921
+ return false;
78922
+ case "union":
78923
+ case "intersection":
78924
+ return doc.members.every(isFlat);
78925
+ case "array":
78926
+ return isFlat(doc.element);
78927
+ case "tuple":
78928
+ return doc.members.every(isFlat) && (doc.rest === undefined || isFlat(doc.rest));
78929
+ case "record":
78930
+ return isFlat(doc.value);
78931
+ }
78932
+ };
78933
+ var flat = (doc) => {
78934
+ switch (doc.kind) {
78935
+ case "text":
78936
+ return doc.text;
78937
+ case "object":
78938
+ return `{ ${doc.members.map(({ name, optional, type }) => `readonly ${name}${optional ? "?" : ""}: ${flat(type)}`).join("; ")} }`;
78939
+ case "union":
78940
+ return doc.members.map(flat).join(" | ");
78941
+ case "intersection":
78942
+ return doc.members.map(operand).join(" & ");
78943
+ case "array":
78944
+ return `readonly ${wrapped(doc.element)}[]`;
78945
+ case "tuple":
78946
+ return `readonly [${[...doc.members.map(flat), ...doc.rest === undefined ? [] : [`...${flat(doc.rest)}[]`]].join(", ")}]`;
78947
+ case "record":
78948
+ return `Record<string, ${flat(doc.value)}>`;
78949
+ }
78950
+ };
78951
+ var needsParentheses = (doc) => doc.kind === "union" || doc.kind === "intersection" || doc.kind === "array" || doc.kind === "tuple";
78952
+ var operand = (doc) => doc.kind === "union" ? `(${flat(doc)})` : flat(doc);
78953
+ var wrapped = (doc) => needsParentheses(doc) ? `(${flat(doc)})` : flat(doc);
78954
+ var printType = (doc, indent, column, suffix = 1) => {
78955
+ switch (doc.kind) {
78956
+ case "object":
78957
+ return `{
78958
+ ${doc.members.map(({ name, optional, type }) => {
78959
+ const head = `${pad2(indent + 2)}readonly ${name}${optional ? "?" : ""}:`;
78960
+ const body = printType(type, indent + 2, head.length + 1);
78961
+ return `${head}${body.startsWith(`
78962
+ `) ? "" : " "}${body};`;
78963
+ }).join(`
78964
+ `)}
78965
+ ${pad2(indent)}}`;
78966
+ case "union": {
78967
+ if (isFlat(doc) && column + flat(doc).length + suffix <= PRINT_WIDTH)
78968
+ return flat(doc);
78969
+ return doc.members.map((member) => `
78970
+ ${pad2(indent + 2)}| ${printType(member, indent + 4, indent + 4, 0)}`).join("");
78971
+ }
78972
+ case "intersection":
78973
+ return doc.members.map((member) => {
78974
+ const body = printType(member, indent, column, suffix);
78975
+ if (member.kind !== "union")
78976
+ return body;
78977
+ return body.startsWith(`
78978
+ `) ? `(${body}
78979
+ ${pad2(indent)})` : `(${body})`;
78980
+ }).join(" & ");
78981
+ case "array": {
78982
+ const element = doc.element;
78983
+ if (isFlat(doc) && column + flat(doc).length + suffix <= PRINT_WIDTH)
78984
+ return flat(doc);
78985
+ if (element.kind === "object")
78986
+ return `readonly ${printType(element, indent, column + 9, 0)}[]`;
78987
+ if (!needsParentheses(element))
78988
+ return flat(doc);
78989
+ const body = printType(element, indent, column + 10, 0);
78990
+ return body.startsWith(`
78991
+ `) ? `readonly (${body}
78992
+ ${pad2(indent)})[]` : `readonly (${body})[]`;
78993
+ }
78994
+ default:
78995
+ return flat(doc);
78996
+ }
78997
+ };
78612
78998
  var UNSUPPORTED_KEYWORDS = [
78613
78999
  "not",
78614
79000
  "if",
@@ -78625,31 +79011,31 @@ var UNSUPPORTED_KEYWORDS = [
78625
79011
  ];
78626
79012
  var literal = (value, pointer, state) => {
78627
79013
  if (value === null)
78628
- return "null";
79014
+ return text2("null");
78629
79015
  if (typeof value === "string")
78630
- return JSON.stringify(value);
79016
+ return text2(JSON.stringify(value));
78631
79017
  if (typeof value === "number" || typeof value === "boolean")
78632
- return String(value);
79018
+ return text2(String(value));
78633
79019
  state.problems.push({
78634
79020
  pointer,
78635
79021
  message: "Only string, number, boolean and null literals become generated types."
78636
79022
  });
78637
- return "never";
79023
+ return NEVER;
78638
79024
  };
78639
79025
  var scalar = (type, pointer, state) => {
78640
79026
  switch (type) {
78641
79027
  case "string":
78642
- return "string";
79028
+ return text2("string");
78643
79029
  case "number":
78644
79030
  case "integer":
78645
- return "number";
79031
+ return text2("number");
78646
79032
  case "boolean":
78647
- return "boolean";
79033
+ return text2("boolean");
78648
79034
  case "null":
78649
- return "null";
79035
+ return text2("null");
78650
79036
  default:
78651
79037
  state.problems.push({ pointer, message: `Unsupported JSON Schema type ${type}.` });
78652
- return "never";
79038
+ return NEVER;
78653
79039
  }
78654
79040
  };
78655
79041
  var definitionName = (state, key, index, pointer) => {
@@ -78671,13 +79057,13 @@ var emit = (schema, pointer, root, state) => {
78671
79057
  pointer,
78672
79058
  message: "An unconstrained schema cannot produce a type that checks anything."
78673
79059
  });
78674
- return "never";
79060
+ return NEVER;
78675
79061
  }
78676
79062
  if (schema === false)
78677
- return "never";
79063
+ return NEVER;
78678
79064
  if (!isObject2(schema)) {
78679
79065
  state.problems.push({ pointer, message: "Expected a JSON Schema object." });
78680
- return "never";
79066
+ return NEVER;
78681
79067
  }
78682
79068
  for (const keyword of UNSUPPORTED_KEYWORDS) {
78683
79069
  if (schema[keyword] !== undefined) {
@@ -78685,7 +79071,7 @@ var emit = (schema, pointer, root, state) => {
78685
79071
  pointer,
78686
79072
  message: `Generated types do not support the ${keyword} keyword.`
78687
79073
  });
78688
- return "never";
79074
+ return NEVER;
78689
79075
  }
78690
79076
  }
78691
79077
  const reference = schema.$ref;
@@ -78697,48 +79083,51 @@ var emit = (schema, pointer, root, state) => {
78697
79083
  pointer,
78698
79084
  message: `Only local #/$defs references are generated; found ${reference}.`
78699
79085
  });
78700
- return "never";
79086
+ return NEVER;
78701
79087
  }
78702
79088
  const name = definitionName(state, key, Object.keys(defs).indexOf(key), pointer);
78703
79089
  if (name === undefined)
78704
- return "never";
79090
+ return NEVER;
78705
79091
  if (!state.defs.has(name) && !state.emitting.has(name)) {
78706
79092
  state.emitting.add(name);
78707
79093
  state.defs.set(name, emit(defs[key], `#/$defs/${key}`, root, state));
78708
79094
  }
78709
- return name;
79095
+ return text2(name);
78710
79096
  }
78711
79097
  if (schema.const !== undefined)
78712
79098
  return literal(schema.const, pointer, state);
78713
79099
  if (Array.isArray(schema.enum)) {
78714
- return schema.enum.length === 0 ? "never" : schema.enum.map((value) => literal(value, pointer, state)).join(" | ");
79100
+ return schema.enum.length === 0 ? NEVER : union2(schema.enum.map((value) => literal(value, pointer, state)));
78715
79101
  }
78716
79102
  if (Array.isArray(schema.allOf)) {
78717
- return schema.allOf.map((member, index) => emit(member, `${pointer}/allOf/${index}`, root, state)).join(" & ");
79103
+ return {
79104
+ kind: "intersection",
79105
+ members: schema.allOf.map((member, index) => emit(member, `${pointer}/allOf/${index}`, root, state))
79106
+ };
78718
79107
  }
78719
79108
  for (const key of ["anyOf", "oneOf"]) {
78720
79109
  const members = schema[key];
78721
79110
  if (!Array.isArray(members))
78722
79111
  continue;
78723
- return members.length === 0 ? "never" : members.map((member, index) => emit(member, `${pointer}/${key}/${index}`, root, state)).join(" | ");
79112
+ return members.length === 0 ? NEVER : union2(members.map((member, index) => emit(member, `${pointer}/${key}/${index}`, root, state)));
78724
79113
  }
78725
79114
  const type = schema.type;
78726
79115
  if (Array.isArray(type)) {
78727
- return type.map((member) => scalarOrStructured(schema, member, pointer, root, state)).join(" | ");
79116
+ return union2(type.map((member) => scalarOrStructured(schema, member, pointer, root, state)));
78728
79117
  }
78729
79118
  if (typeof type !== "string") {
78730
79119
  state.problems.push({
78731
79120
  pointer,
78732
79121
  message: "A generated type needs an explicit type, enum, const, $ref or composition."
78733
79122
  });
78734
- return "never";
79123
+ return NEVER;
78735
79124
  }
78736
79125
  return scalarOrStructured(schema, type, pointer, root, state);
78737
79126
  };
78738
79127
  var scalarOrStructured = (schema, type, pointer, root, state) => {
78739
79128
  if (typeof type !== "string") {
78740
79129
  state.problems.push({ pointer, message: "Expected a JSON Schema type name." });
78741
- return "never";
79130
+ return NEVER;
78742
79131
  }
78743
79132
  if (type === "object") {
78744
79133
  const properties = isObject2(schema.properties) ? schema.properties : {};
@@ -78751,40 +79140,52 @@ var scalarOrStructured = (schema, type, pointer, root, state) => {
78751
79140
  pointer,
78752
79141
  message: "An object with no declared properties cannot produce a type that checks input."
78753
79142
  });
78754
- return "never";
79143
+ return NEVER;
78755
79144
  }
78756
- return additional === false ? "Record<string, never>" : `Record<string, ${emit(additional, `${pointer}/additionalProperties`, root, state)}>`;
79145
+ return additional === false ? { kind: "record", value: NEVER } : {
79146
+ kind: "record",
79147
+ value: emit(additional, `${pointer}/additionalProperties`, root, state)
79148
+ };
78757
79149
  }
78758
79150
  const declared = keys.map((key) => ({
78759
79151
  name: SAFE_PROPERTY.test(key) ? key : JSON.stringify(key),
78760
- optional: required.has(key) ? "" : "?",
79152
+ optional: !required.has(key),
78761
79153
  type: emit(properties[key], `${pointer}/properties/${key}`, root, state)
78762
79154
  }));
78763
- const shape = `{
78764
- ${declared.map(({ name, optional, type: member }) => ` readonly ${name}${optional}: ${member};`).join(`
78765
- `)}
78766
- }`;
79155
+ const shape = { kind: "object", members: declared };
78767
79156
  if (additional === undefined || additional === false || additional === true)
78768
79157
  return shape;
78769
79158
  const extra = emit(additional, `${pointer}/additionalProperties`, root, state);
78770
- const open = [...new Set([...declared.map(({ type: member }) => member), extra])].filter((member) => member !== "never").join(" | ");
78771
- return `${shape} & Record<string, ${open.length === 0 ? "never" : open}>`;
79159
+ const seen = new Set;
79160
+ const open = [...declared.map((member) => member.type), extra].filter((member) => {
79161
+ const key = flat(member);
79162
+ if (key === "never" || seen.has(key))
79163
+ return false;
79164
+ seen.add(key);
79165
+ return true;
79166
+ });
79167
+ return {
79168
+ kind: "intersection",
79169
+ members: [shape, { kind: "record", value: open.length === 0 ? NEVER : union2(open) }]
79170
+ };
78772
79171
  }
78773
79172
  if (type === "array") {
78774
79173
  if (Array.isArray(schema.prefixItems)) {
78775
79174
  const tuple = schema.prefixItems.map((member, index) => emit(member, `${pointer}/prefixItems/${index}`, root, state));
78776
- const rest = schema.items === undefined || schema.items === false ? "" : `, ...${emit(schema.items, `${pointer}/items`, root, state)}[]`;
78777
- return `readonly [${tuple.join(", ")}${rest}]`;
79175
+ return {
79176
+ kind: "tuple",
79177
+ members: tuple,
79178
+ ...schema.items === undefined || schema.items === false ? {} : { rest: emit(schema.items, `${pointer}/items`, root, state) }
79179
+ };
78778
79180
  }
78779
79181
  if (schema.items === undefined) {
78780
79182
  state.problems.push({
78781
79183
  pointer,
78782
79184
  message: "An array without declared items cannot produce a type that checks input."
78783
79185
  });
78784
- return "never";
79186
+ return NEVER;
78785
79187
  }
78786
- const member = emit(schema.items, `${pointer}/items`, root, state);
78787
- return SAFE_PROPERTY.test(member) ? `readonly ${member}[]` : `readonly (${member})[]`;
79188
+ return { kind: "array", element: emit(schema.items, `${pointer}/items`, root, state) };
78788
79189
  }
78789
79190
  return scalar(type, pointer, state);
78790
79191
  };
@@ -78808,31 +79209,54 @@ var schemaToTypeScript = (schema, prefix) => {
78808
79209
  var TEMPLATE_TYPES_MANIFEST_PREFIX = "// samva:catalog ";
78809
79210
  var ManifestSchema = Struct({
78810
79211
  version: Literal2(1),
78811
- reference: Literals(["exact", "contract-latest"]),
79212
+ by: optionalKey2(Literals(["id", "slug"])),
79213
+ reference: optionalKey2(Literals(["exact", "contract-latest"])),
78812
79214
  templates: ArraySchema(Struct({
78813
79215
  key: String4,
78814
79216
  templateId: String4,
78815
- reference: String4
79217
+ reference: optionalKey2(String4),
79218
+ inputContractId: optionalKey2(String4)
78816
79219
  }))
78817
79220
  });
78818
79221
  var decodeManifest = decodeUnknownOption2(fromJsonString2(ManifestSchema));
79222
+ var withKeying = (manifest) => ({ ...manifest, by: manifest.by ?? "id" });
78819
79223
  var readTemplateTypesManifest = (source) => {
78820
79224
  const line = source.split(`
78821
79225
  `).find((candidate) => candidate.startsWith(TEMPLATE_TYPES_MANIFEST_PREFIX));
78822
- return line === undefined ? undefined : getOrUndefined(decodeManifest(line.slice(TEMPLATE_TYPES_MANIFEST_PREFIX.length)));
79226
+ return line === undefined ? undefined : getOrUndefined(map(decodeManifest(line.slice(TEMPLATE_TYPES_MANIFEST_PREFIX.length)), withKeying));
79227
+ };
79228
+ var MODULE_TYPE_NAMES = [
79229
+ "SamvaTemplateInputs",
79230
+ "TemplateContent",
79231
+ "TemplateSlug",
79232
+ "TemplateValue",
79233
+ "TemplateValues"
79234
+ ];
79235
+ var memberKey = (value) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value) ? value : JSON.stringify(value);
79236
+ var declaration = (head, type) => {
79237
+ const body = printType(type, 0, head.length + 1);
79238
+ return `${head}${body.startsWith(`
79239
+ `) ? "" : " "}${body};`;
79240
+ };
79241
+ var mapEntry = (key, fields) => {
79242
+ const flat = ` ${key}: { ${fields.map(([name, value]) => `${name}: ${value}`).join(", ")} },`;
79243
+ return flat.length <= PRINT_WIDTH ? flat : [` ${key}: {`, ...fields.map(([name, value]) => ` ${name}: ${value},`), " },"].join(`
79244
+ `);
78823
79245
  };
78824
79246
  var renderTemplateTypes = (input) => {
79247
+ const bySlug = input.by === "slug";
79248
+ const referenceKind = bySlug ? "contract-latest" : input.reference;
78825
79249
  const skipped = [];
78826
79250
  const problems = [];
78827
79251
  const entries = [...input.entries].sort((left, right) => left.key < right.key ? -1 : left.key > right.key ? 1 : 0);
78828
79252
  const generated = [];
78829
- const used = new Set;
79253
+ const declared = new Set(MODULE_TYPE_NAMES);
78830
79254
  for (const [index, entry] of entries.entries()) {
78831
- const reference = input.reference === "exact" ? entry.publicationId : entry.inputContractId;
79255
+ const reference = referenceKind === "exact" ? entry.publicationId : entry.inputContractId;
78832
79256
  if (reference === undefined) {
78833
79257
  skipped.push({
78834
79258
  key: entry.key,
78835
- reason: input.reference === "exact" ? "no publication yet; publish the template before generating an exact reference" : "no input contract yet; publish the template before adopting its contract"
79259
+ reason: referenceKind === "exact" ? "no publication yet; publish the template before generating an exact reference" : bySlug ? "no input contract yet; publish the template before generating types by slug" : "no input contract yet; publish the template before adopting its contract"
78836
79260
  });
78837
79261
  continue;
78838
79262
  }
@@ -78841,10 +79265,17 @@ var renderTemplateTypes = (input) => {
78841
79265
  continue;
78842
79266
  }
78843
79267
  let name = pascalCase(entry.key, `Template${index + 1}`);
78844
- while (used.has(name))
79268
+ let rendered = schemaToTypeScript(entry.inputSchema, name);
79269
+ const typeNames = () => [
79270
+ `${name}Input`,
79271
+ ...rendered.definitions.map((definition) => definition.name)
79272
+ ];
79273
+ while (typeNames().some((typeName) => declared.has(typeName))) {
78845
79274
  name = `${name}_`;
78846
- used.add(name);
78847
- const rendered = schemaToTypeScript(entry.inputSchema, name);
79275
+ rendered = schemaToTypeScript(entry.inputSchema, name);
79276
+ }
79277
+ for (const typeName of typeNames())
79278
+ declared.add(typeName);
78848
79279
  for (const problem of rendered.problems)
78849
79280
  problems.push({ ...problem, key: entry.key });
78850
79281
  generated.push({
@@ -78857,19 +79288,60 @@ var renderTemplateTypes = (input) => {
78857
79288
  }
78858
79289
  const manifest = {
78859
79290
  version: 1,
78860
- reference: input.reference,
79291
+ by: input.by,
79292
+ ...bySlug ? {} : { reference: referenceKind },
78861
79293
  templates: generated.map(({ entry, reference }) => ({
78862
79294
  key: entry.key,
78863
79295
  templateId: entry.templateId,
78864
- reference
79296
+ ...bySlug ? { inputContractId: reference } : { reference }
78865
79297
  }))
78866
79298
  };
78867
- const referenceField = input.reference === "exact" ? "publicationId" : "inputContractId";
78868
- const referenceNote = input.reference === "exact" ? "Each reference pins one exact publication, so a send from this file renders the same message until you regenerate." : "Each reference names an input contract, so a send from this file follows the newest publication that still validates input identically.";
78869
- const definitions = generated.flatMap(({ definitions: members }) => members.map(({ name, type }) => `type ${name} = ${type};`));
78870
- const inputs = generated.map(({ entry, name, type }) => [`/** Input for the \`${entry.key}\` template. */`, `export type ${name}Input = ${type};`].join(`
79299
+ const referenceField = referenceKind === "exact" ? "publicationId" : "inputContractId";
79300
+ const referenceNote = bySlug ? "Each helper names a template by slug, so a send from this file renders the template's current publication; its input is typed from the input contract current when you generated." : referenceKind === "exact" ? "Each reference pins one exact publication, so a send from this file renders the same message until you regenerate." : "Each reference names an input contract, so a send from this file follows the newest publication that still validates input identically.";
79301
+ const definitions = generated.flatMap(({ definitions: members }) => members.map(({ name, type }) => declaration(`type ${name} =`, type)));
79302
+ const inputs = generated.map(({ entry, name, type }) => [
79303
+ `/** Input for the \`${entry.key}\` template. */`,
79304
+ declaration(`export type ${name}Input =`, type)
79305
+ ].join(`
78871
79306
  `));
78872
- const key = (value) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value) ? value : JSON.stringify(value);
79307
+ const keying = bySlug ? [
79308
+ "/**",
79309
+ " * Email content for one template, named by slug. TypeScript rejects input the template's",
79310
+ " * input contract would reject; spread the result into `email.send`.",
79311
+ " */",
79312
+ "export const templateContent = <Slug extends TemplateSlug>(",
79313
+ " slug: Slug,",
79314
+ " templateData: SamvaTemplateInputs[Slug],",
79315
+ ") => ({ templateSlug: slug, templateData: asTemplateData(templateData) });"
79316
+ ] : [
79317
+ `/** ${referenceNote} */`,
79318
+ generated.length === 0 ? "export const samvaTemplates = {} as const;" : `export const samvaTemplates = {
79319
+ ${generated.map(({ entry, reference }) => mapEntry(memberKey(entry.key), [
79320
+ ["templateId", JSON.stringify(entry.templateId)],
79321
+ [referenceField, JSON.stringify(reference)]
79322
+ ])).join(`
79323
+ `)}
79324
+ } as const;`,
79325
+ "",
79326
+ "/** What a send spreads in: the template's reference and its input as JSON. */",
79327
+ "export type TemplateContent = {",
79328
+ " readonly templateId: string;",
79329
+ ` readonly ${referenceField}: string;`,
79330
+ " readonly templateData: TemplateValues;",
79331
+ "};",
79332
+ "",
79333
+ "/**",
79334
+ " * Email content for one template. TypeScript rejects input the published",
79335
+ " * schema would reject, and the reference travels with it. Spread it into `email.send`.",
79336
+ " */",
79337
+ "export const templateContent = <Slug extends TemplateSlug>(",
79338
+ " slug: Slug,",
79339
+ " templateData: SamvaTemplateInputs[Slug],",
79340
+ "): TemplateContent => ({",
79341
+ " ...samvaTemplates[slug],",
79342
+ " templateData: asTemplateData(templateData),",
79343
+ "});"
79344
+ ];
78873
79345
  const source = `${[
78874
79346
  "// Generated by `samva templates types`. Do not edit.",
78875
79347
  `//`,
@@ -78886,24 +79358,43 @@ var renderTemplateTypes = (input) => {
78886
79358
  `), ""],
78887
79359
  "/** What each published template accepts, keyed by slug. */",
78888
79360
  generated.length === 0 ? "export interface SamvaTemplateInputs {}" : `export interface SamvaTemplateInputs {
78889
- ${generated.map(({ entry, name }) => ` readonly ${key(entry.key)}: ${name}Input;`).join(`
79361
+ ${generated.map(({ entry, name }) => ` readonly ${memberKey(entry.key)}: ${name}Input;`).join(`
78890
79362
  `)}
78891
79363
  }`,
78892
79364
  "",
78893
- `/** ${referenceNote} */`,
78894
- generated.length === 0 ? "export const samvaTemplates = {} as const;" : `export const samvaTemplates = {
78895
- ${generated.map(({ entry, reference }) => ` ${key(entry.key)}: { templateId: ${JSON.stringify(entry.templateId)}, ${referenceField}: ${JSON.stringify(reference)} },`).join(`
78896
- `)}
78897
- } as const;`,
79365
+ "/** The slug of every template in this file. */",
79366
+ "export type TemplateSlug = keyof SamvaTemplateInputs;",
78898
79367
  "",
78899
- "/**",
78900
- " * Email content for one template. TypeScript rejects input the published",
78901
- " * schema would reject, and the reference travels with it.",
78902
- " */",
78903
- "export const templateContent = <Name extends keyof SamvaTemplateInputs>(",
78904
- " name: Name,",
78905
- " templateData: SamvaTemplateInputs[Name],",
78906
- ") => ({ ...samvaTemplates[name], templateData });"
79368
+ "// What the API accepts as template data. A generated input is made of these values, but its",
79369
+ "// readonly lists and optional properties are not always assignable to them, so the helper",
79370
+ "// rebuilds the input as JSON values, leaving out a property that is undefined.",
79371
+ "type TemplateValue =",
79372
+ " | string",
79373
+ " | number",
79374
+ " | boolean",
79375
+ " | null",
79376
+ " | TemplateValue[]",
79377
+ " | { [key: string]: TemplateValue };",
79378
+ "type TemplateValues = { [key: string]: TemplateValue };",
79379
+ "const toTemplateValue = (value: unknown): TemplateValue => {",
79380
+ ' if (value === null || typeof value === "string") return value;',
79381
+ ' if (typeof value === "number" || typeof value === "boolean") return value;',
79382
+ " if (Array.isArray(value)) return value.map(toTemplateValue);",
79383
+ ' if (typeof value !== "object") return null;',
79384
+ " return Object.fromEntries(",
79385
+ " Object.entries(value)",
79386
+ " .filter(([, member]) => member !== undefined)",
79387
+ " .map(([key, member]) => [key, toTemplateValue(member)]),",
79388
+ " );",
79389
+ "};",
79390
+ "const asTemplateData = (input: SamvaTemplateInputs[TemplateSlug]): TemplateValues =>",
79391
+ " Object.fromEntries(",
79392
+ " Object.entries(input)",
79393
+ " .filter(([, member]) => member !== undefined)",
79394
+ " .map(([key, member]) => [key, toTemplateValue(member)]),",
79395
+ " );",
79396
+ "",
79397
+ ...keying
78907
79398
  ].join(`
78908
79399
  `)}
78909
79400
  `;
@@ -78920,6 +79411,13 @@ var templateTypesDrift = (existing, generated) => {
78920
79411
  detail: "The file carries no samva:catalog manifest; it was not written by this command."
78921
79412
  }
78922
79413
  ];
79414
+ if (manifest.by !== generated.manifest.by)
79415
+ return [
79416
+ {
79417
+ kind: "keying",
79418
+ detail: `Generated by ${manifest.by}; asked for ${generated.manifest.by}.`
79419
+ }
79420
+ ];
78923
79421
  if (manifest.reference !== generated.manifest.reference)
78924
79422
  return [
78925
79423
  {
@@ -78936,7 +79434,14 @@ var templateTypesDrift = (existing, generated) => {
78936
79434
  drift.push({ kind: "added", key, detail: `${key} is published and not in the file.` });
78937
79435
  continue;
78938
79436
  }
78939
- if (previous.reference !== entry.reference)
79437
+ if (generated.manifest.by === "slug") {
79438
+ if (previous.inputContractId !== entry.inputContractId)
79439
+ drift.push({
79440
+ kind: "contract",
79441
+ key,
79442
+ detail: `${key} was typed from input contract ${previous.inputContractId}; the catalog now has ${entry.inputContractId}.`
79443
+ });
79444
+ } else if (previous.reference !== entry.reference)
78940
79445
  drift.push({
78941
79446
  kind: "reference",
78942
79447
  key,
@@ -78982,13 +79487,22 @@ var readCatalog = fn2("templates.types.readCatalog")(function* () {
78982
79487
  ...item.inputSchema === null ? {} : { inputSchema: item.inputSchema }
78983
79488
  }))), { concurrency: CATALOG_READ_CONCURRENCY });
78984
79489
  });
79490
+ var keyingRows = (by, reference) => by === "slug" ? [
79491
+ ["By", "slug"],
79492
+ ["Publication", "current"],
79493
+ ["Drift", "input contract"]
79494
+ ] : [
79495
+ ["By", "id"],
79496
+ ["Reference", reference ?? "exact"]
79497
+ ];
78985
79498
  var readExisting = (path) => gen2(function* () {
78986
79499
  const fs = yield* FileSystem;
78987
79500
  return (yield* fs.exists(path)) ? yield* fs.readFileString(path) : undefined;
78988
79501
  }).pipe(catch_2(() => succeed6(undefined)));
78989
79502
  var typesCommand = make29("types", {
78990
79503
  out: String9("out").pipe(withDescription2("TypeScript file to write the generated template inputs into"), withDefault5("samva-templates.ts")),
78991
- reference: String9("reference").pipe(withDescription2("exact pins one publication per template; contract-latest follows the newest publication that validates input identically"), withDefault5("exact")),
79504
+ by: Literals3("by", ["id", "slug"]).pipe(withDescription2("id keys each send by template id with a reference; slug keys it by template slug and follows the template's current publication"), withDefault5("id")),
79505
+ reference: String9("reference").pipe(withDescription2("With --by id (default exact): exact pins one publication per template; contract-latest follows the newest publication that validates input identically"), optional4),
78992
79506
  check: Boolean6("check").pipe(withDescription2("Report drift without writing; exits 1 when the file is out of date"), withDefault5(false)),
78993
79507
  json: jsonFlag
78994
79508
  }).pipe(withDescription3("Generate typed template inputs from the authenticated catalog"), withExamples([
@@ -78996,21 +79510,32 @@ var typesCommand = make29("types", {
78996
79510
  command: "samva templates types --out src/samva-templates.ts",
78997
79511
  description: "Pin each template's current publication and type its input"
78998
79512
  },
79513
+ {
79514
+ command: "samva templates types --by slug --out src/samva-templates.ts",
79515
+ description: "Type a send that names the template by slug: { templateSlug, templateData }"
79516
+ },
78999
79517
  {
79000
79518
  command: "samva templates types --check",
79001
79519
  description: "Fail CI when the generated file no longer matches the catalog"
79002
79520
  }
79003
- ]), withHandler(fnUntraced2(function* ({ out, reference, check, json }) {
79521
+ ]), withHandler(fnUntraced2(function* ({ out, by, reference: requested, check, json }) {
79522
+ const reference = getOrElse(requested, () => "exact");
79004
79523
  if (reference !== "exact" && reference !== "contract-latest") {
79005
79524
  return yield* new SamvaCliError({
79006
79525
  message: `Unknown --reference ${reference}. Use exact or contract-latest.`
79007
79526
  });
79008
79527
  }
79528
+ if (by === "slug" && isSome2(requested)) {
79529
+ return yield* new SamvaCliError({
79530
+ message: "--reference does not apply with --by slug: a send by slug renders the template's current publication, and the types follow its input contract."
79531
+ });
79532
+ }
79009
79533
  const context = yield* resolveExecutionContext;
79010
- const path = resolve14(out);
79534
+ const path = resolve15(out);
79011
79535
  const entries = yield* readCatalog();
79012
79536
  const generated = renderTemplateTypes({
79013
79537
  entries,
79538
+ by,
79014
79539
  reference,
79015
79540
  origin: context.apiUrl
79016
79541
  });
@@ -79023,7 +79548,8 @@ var typesCommand = make29("types", {
79023
79548
  action: "templates.types",
79024
79549
  ok: false,
79025
79550
  out: path,
79026
- reference,
79551
+ by,
79552
+ ...generated.manifest.reference === undefined ? {} : { reference: generated.manifest.reference },
79027
79553
  problems: generated.problems
79028
79554
  },
79029
79555
  human: [
@@ -79039,7 +79565,8 @@ var typesCommand = make29("types", {
79039
79565
  action: "templates.types",
79040
79566
  ok: check ? drift.length === 0 : true,
79041
79567
  out: path,
79042
- reference,
79568
+ by,
79569
+ ...generated.manifest.reference === undefined ? {} : { reference: generated.manifest.reference },
79043
79570
  templates: generated.manifest.templates,
79044
79571
  skipped: generated.skipped,
79045
79572
  drift,
@@ -79051,7 +79578,7 @@ var typesCommand = make29("types", {
79051
79578
  return yield* printJson(result);
79052
79579
  return yield* printFields([
79053
79580
  ["File", path],
79054
- ["Reference", reference],
79581
+ ...keyingRows(by, generated.manifest.reference),
79055
79582
  ["Templates", generated.manifest.templates.length],
79056
79583
  ["Status", "up to date"]
79057
79584
  ]);
@@ -79078,7 +79605,7 @@ var typesCommand = make29("types", {
79078
79605
  return yield* printJson(written);
79079
79606
  yield* printFields([
79080
79607
  ["File", path],
79081
- ["Reference", reference],
79608
+ ...keyingRows(by, generated.manifest.reference),
79082
79609
  ["Templates", generated.manifest.templates.length],
79083
79610
  ["Skipped", generated.skipped.length]
79084
79611
  ]);
@@ -79087,9 +79614,22 @@ var typesCommand = make29("types", {
79087
79614
  })));
79088
79615
 
79089
79616
  // src/commands/templates/workspace.ts
79090
- import { basename as basename2, resolve as resolve15 } from "path";
79617
+ import { basename as basename2, resolve as resolve16 } from "path";
79091
79618
  var templateIdArgument = String10("template-id").pipe(withDescription4("Template whose project workspace to act on"));
79092
79619
  var revisionFlag2 = String9("revision").pipe(withDescription2("Workspace revision this call is fenced to; a workspace that moved refuses the call"));
79620
+ var readRevisionFlag = String9("revision").pipe(withDescription2("Workspace revision this call is fenced to; a workspace that moved refuses the call (default: the workspace's current revision)"), optional4);
79621
+ var currentRevision = (id, revision) => isSome2(revision) ? succeed6(revision.value) : gen2(function* () {
79622
+ const client = yield* getClientProvider;
79623
+ const view = yield* openWorkspace({ id }).pipe(client.provide, mapError2((error) => {
79624
+ const failure = toCliApiError("Could not read the template workspace revision", error);
79625
+ return new SamvaCliError({
79626
+ message: `${failure.message} The current revision is read by opening the workspace, which needs templates:create; pass --revision to skip that.`,
79627
+ api: failure.api,
79628
+ details: failure.details
79629
+ });
79630
+ }));
79631
+ return view.workspace.revision;
79632
+ });
79093
79633
  var idempotencyKeyFlag4 = String9("idempotency-key").pipe(withDescription2("Key so an identical retry returns the original result rather than repeating it"));
79094
79634
  var apiFailure2 = (what) => (error) => toCliApiError(`Could not ${what}`, error);
79095
79635
  var emit2 = (value) => printJsonDocument(value);
@@ -79105,11 +79645,12 @@ var open3 = make29("open", { id: templateIdArgument, json: jsonFlag }).pipe(with
79105
79645
  })));
79106
79646
  var diff = make29("diff", {
79107
79647
  id: templateIdArgument,
79108
- revision: revisionFlag2,
79648
+ revision: readRevisionFlag,
79109
79649
  json: jsonFlag
79110
79650
  }).pipe(withDescription3("Show every unsaved change against the workspace Git base"), withHandler(fnUntraced2(function* ({ id, revision, json }) {
79651
+ const expectedRevision = yield* currentRevision(id, revision);
79111
79652
  const client = yield* getClientProvider;
79112
- const result = yield* diffWorkspace({ id, expectedRevision: revision }).pipe(client.provide, mapError2(apiFailure2("diff the template workspace")));
79653
+ const result = yield* diffWorkspace({ id, expectedRevision }).pipe(client.provide, mapError2(apiFailure2("diff the template workspace")));
79113
79654
  yield* emit2(result);
79114
79655
  })));
79115
79656
  var patch2 = make29("patch", {
@@ -79140,7 +79681,7 @@ var patch2 = make29("patch", {
79140
79681
  message: `--set must be <project-path>=<local-file>; got ${entry}.`
79141
79682
  });
79142
79683
  const path = entry.slice(0, separator);
79143
- const source = resolve15(entry.slice(separator + 1));
79684
+ const source = resolve16(entry.slice(separator + 1));
79144
79685
  const content = yield* fs.readFileString(source).pipe(mapError2((cause) => new SamvaCliError({ message: `Could not read ${source}: ${String(cause)}` })));
79145
79686
  written.push({ path, content });
79146
79687
  }
@@ -79174,7 +79715,7 @@ var addAsset = make29("add-asset", {
79174
79715
  }
79175
79716
  ]), withHandler(fnUntraced2(function* ({ id, file, revision, path, replace, idempotencyKey, json }) {
79176
79717
  const fs = yield* FileSystem;
79177
- const source = resolve15(file);
79718
+ const source = resolve16(file);
79178
79719
  const bytes = yield* fs.readFile(source).pipe(mapError2((cause) => new SamvaCliError({ message: `Could not read ${source}: ${String(cause)}` })));
79179
79720
  const client = yield* getClientProvider;
79180
79721
  const view = yield* putWorkspaceAsset({
@@ -79189,28 +79730,30 @@ var addAsset = make29("add-asset", {
79189
79730
  })));
79190
79731
  var check4 = make29("check", {
79191
79732
  id: templateIdArgument,
79192
- revision: revisionFlag2,
79733
+ revision: readRevisionFlag,
79193
79734
  json: jsonFlag
79194
79735
  }).pipe(withDescription3("Build the effective workspace tree and report what it declares plus every finding"), withExamples([
79195
79736
  {
79196
- command: "samva templates workspace check tmpl_abc123 --revision 5",
79197
- description: "Read the template id, input schema, fixture names, and located diagnostics without rendering or writing"
79737
+ command: "samva templates workspace check tmpl_abc123",
79738
+ description: "Read the template id, input schema, fixture names, and located diagnostics without rendering or writing; --revision 5 fences the call to a revision you inspected"
79198
79739
  }
79199
79740
  ]), withHandler(fnUntraced2(function* ({ id, revision, json }) {
79741
+ const expectedRevision = yield* currentRevision(id, revision);
79200
79742
  const client = yield* getClientProvider;
79201
- const result = yield* checkWorkspace({ id, expectedRevision: revision }).pipe(client.provide, mapError2(apiFailure2("check the template workspace")));
79743
+ const result = yield* checkWorkspace({ id, expectedRevision }).pipe(client.provide, mapError2(apiFailure2("check the template workspace")));
79202
79744
  yield* emit2(result);
79203
79745
  })));
79204
79746
  var fixture = make29("fixture", {
79205
79747
  id: templateIdArgument,
79206
- revision: revisionFlag2,
79748
+ revision: readRevisionFlag,
79207
79749
  name: String9("name").pipe(withDescription2("One of the fixture names `workspace check` reported")),
79208
79750
  json: jsonFlag
79209
79751
  }).pipe(withDescription3("Render one input the template declares, as the recipient receives it"), withHandler(fnUntraced2(function* ({ id, revision, name, json }) {
79752
+ const expectedRevision = yield* currentRevision(id, revision);
79210
79753
  const client = yield* getClientProvider;
79211
79754
  const result = yield* renderWorkspaceFixture({
79212
79755
  id,
79213
- expectedRevision: revision,
79756
+ expectedRevision,
79214
79757
  fixture: name
79215
79758
  }).pipe(client.provide, mapError2(apiFailure2("render the template fixture")));
79216
79759
  yield* emit2(result);
@@ -79287,42 +79830,52 @@ var workspaceCommand = make29("workspace").pipe(withDescription3("Author a templ
79287
79830
 
79288
79831
  // src/commands/templates.ts
79289
79832
  var tsconfigFlag = String9("tsconfig").pipe(withDescription2("TypeScript project used to check authored templates"), withDefault5("tsconfig.json"));
79290
- var dryRunFlag6 = Boolean6("dry-run").pipe(withDescription2("Show the request without calling the API"), withDefault5(false));
79833
+ var dryRunFlag7 = Boolean6("dry-run").pipe(withDescription2("Show the request without calling the API"), withDefault5(false));
79291
79834
  var templateIdArgument2 = String10("id").pipe(withDescription4("Remote template id"));
79292
79835
  var parseObject = (raw, flag) => try_3({
79293
79836
  try: () => JSON.parse(raw),
79294
79837
  catch: () => new SamvaCliError({ message: `${flag} must be a valid JSON object.` })
79295
79838
  }).pipe(filterOrFail2((value) => isObject(value) && !Array.isArray(value), () => new SamvaCliError({ message: `${flag} must be a non-null JSON object.` })));
79296
79839
  var decodePayload = (raw, flag, schema) => parseObject(raw, flag).pipe(flatMap3(decodeUnknownEffect2(schema)), mapError2((error) => error instanceof SamvaCliError ? error : new SamvaCliError({ message: `${flag} does not match the API schema: ${String(error)}` })));
79297
- var createProjectBuildSession = (dir, assetBase) => tryPromise2({
79298
- try: async () => {
79299
- const entry = Bun.resolveSync("@samva/vite/build", process.cwd());
79300
- const projectBuild = await import(pathToFileURL5(entry).href);
79301
- const { createBrandResolver, loadBrandEnv } = await loadSamvaBrandResolver(process.cwd());
79302
- const projectBrand = await import(pathToFileURL5(Bun.resolveSync("@samva/markup/brand", process.cwd())).href);
79303
- return projectBuild.createTemplateBuildSession({
79304
- brandResolver: createBrandResolver({
79840
+ var createProjectBuildSession = (dir, assetBase) => gen2(function* () {
79841
+ yield* requireCompatibleToolchain(`Could not start template watch${dir === undefined ? "" : ` in ${dir}`}`);
79842
+ const selection = yield* invocationEnv;
79843
+ return yield* tryPromise2({
79844
+ try: async () => {
79845
+ const entry = Bun.resolveSync("@samva/vite/build", process.cwd());
79846
+ const projectBuild = await import(pathToFileURL5(entry).href);
79847
+ const { createBrandResolver, loadBrandEnv } = await loadSamvaBrandResolver(process.cwd());
79848
+ const projectBrand = await import(pathToFileURL5(Bun.resolveSync("@samva/markup/brand", process.cwd())).href);
79849
+ return projectBuild.createTemplateBuildSession({
79850
+ brandResolver: createBrandResolver({
79851
+ root: process.cwd(),
79852
+ env: { ...await loadBrandEnv(process.cwd()), ...selection }
79853
+ }),
79854
+ brandPlugin: projectBrand.samvaBrandPlugin,
79305
79855
  root: process.cwd(),
79306
- env: await loadBrandEnv(process.cwd())
79307
- }),
79308
- brandPlugin: projectBrand.samvaBrandPlugin,
79309
- root: process.cwd(),
79310
- ...dir === undefined ? {} : { dir: resolve16(dir) },
79311
- ...assetBase === undefined ? {} : { assetBase }
79312
- });
79313
- },
79314
- catch: (cause) => new SamvaCliError({
79315
- message: toolchainFailure(String(cause), `Could not start template watch${dir === undefined ? "" : ` in ${dir}`}`)
79316
- })
79856
+ ...dir === undefined ? {} : { dir: resolve17(dir) },
79857
+ ...assetBase === undefined ? {} : { assetBase }
79858
+ });
79859
+ },
79860
+ catch: (cause) => new SamvaCliError({
79861
+ message: toolchainFailure(String(cause), `Could not start template watch${dir === undefined ? "" : ` in ${dir}`}`)
79862
+ })
79863
+ });
79317
79864
  });
79318
79865
  var TSC_DIAGNOSTIC_LINE = /^(?:(.*?)(?:\((\d+),(\d+)\))?: )?(?:error )?(TS\d+): (.*)$/;
79866
+ var BRAND_MODULE_NOT_FOUND = /^Cannot find module 'samva:brand(?:\/[^']*)?'/;
79867
+ var ASSET_MODULE_NOT_FOUND = /^Cannot find module '[^']+\.(?:png|jpe?g|gif|webp|woff2)'/;
79868
+ var withTypesHint = (message, tsconfig) => {
79869
+ const types = BRAND_MODULE_NOT_FOUND.test(message) ? "@samva/markup/samva-brand" : ASSET_MODULE_NOT_FOUND.test(message) ? "vite/client" : undefined;
79870
+ return types === undefined ? message : `${message} Add "${types}" to compilerOptions.types in ${basename3(tsconfig)}.`;
79871
+ };
79319
79872
  var parseTypeScriptDiagnostics = (output, tsconfig) => output.split(`
79320
79873
  `).filter((line) => line.length > 0).map((line) => {
79321
79874
  const match = TSC_DIAGNOSTIC_LINE.exec(line);
79322
79875
  return match === null ? { file: tsconfig, code: "TSC", message: line } : {
79323
79876
  file: match[1] ?? tsconfig,
79324
79877
  code: match[4] ?? "TSC",
79325
- message: match[5] ?? line,
79878
+ message: withTypesHint(match[5] ?? line, tsconfig),
79326
79879
  ...match[2] === undefined ? {} : { line: Number(match[2]) },
79327
79880
  ...match[3] === undefined ? {} : { column: Number(match[3]) }
79328
79881
  };
@@ -79330,16 +79883,16 @@ var parseTypeScriptDiagnostics = (output, tsconfig) => output.split(`
79330
79883
  var typecheckProject = (tsconfig) => tryPromise2({
79331
79884
  try: async () => {
79332
79885
  const typescriptPackage = Bun.resolveSync("typescript/package.json", process.cwd());
79333
- const tsc = resolve16(dirname5(typescriptPackage), "bin/tsc");
79886
+ const tsc = resolve17(dirname5(typescriptPackage), "bin/tsc");
79334
79887
  const typescriptManifest = await Bun.file(typescriptPackage).json();
79335
- const child = Bun.spawn(["node", tsc, "--noEmit", "--pretty", "false", "--project", resolve16(tsconfig)], { cwd: process.cwd(), stdout: "pipe", stderr: "pipe" });
79888
+ const child = Bun.spawn(["node", tsc, "--noEmit", "--pretty", "false", "--project", resolve17(tsconfig)], { cwd: process.cwd(), stdout: "pipe", stderr: "pipe" });
79336
79889
  const [stdout, stderr, exitCode] = await Promise.all([
79337
79890
  new Response(child.stdout).text(),
79338
79891
  new Response(child.stderr).text(),
79339
79892
  child.exited
79340
79893
  ]);
79341
79894
  const output = `${stdout}${stderr}`.trim();
79342
- const diagnostics = parseTypeScriptDiagnostics(output, resolve16(tsconfig));
79895
+ const diagnostics = parseTypeScriptDiagnostics(output, resolve17(tsconfig));
79343
79896
  if (exitCode !== 0 && diagnostics.length === 0) {
79344
79897
  return Promise.reject(new SamvaCliError({
79345
79898
  message: output || `Could not run TypeScript from this project. Install typescript and check ${tsconfig}.`
@@ -79634,7 +80187,7 @@ var build3 = make29("build", {
79634
80187
  }
79635
80188
  return yield* new SamvaCliError({ message });
79636
80189
  }
79637
- const target = resolve16(out);
80190
+ const target = resolve17(out);
79638
80191
  const artifact = toTemplateBuildArtifact(built);
79639
80192
  yield* writeJsonFile(target, artifact);
79640
80193
  const result = {
@@ -79718,7 +80271,7 @@ var createRemote = make29("create", {
79718
80271
  data: String9("data").pipe(withDescription2("Complete template JSON payload")),
79719
80272
  namespace: namespaceFlag("create"),
79720
80273
  shared: Boolean6("shared").pipe(withDescription2("Share the new organization-level project with every namespace; namespace-scoped keys read and send it but cannot change it"), withDefault5(false)),
79721
- dryRun: dryRunFlag6,
80274
+ dryRun: dryRunFlag7,
79722
80275
  json: jsonFlag
79723
80276
  }).pipe(withDescription3("Create a remote template from JSON"), withExamples([
79724
80277
  {
@@ -79761,7 +80314,7 @@ var updateRemote = make29("update", {
79761
80314
  id: templateIdArgument2,
79762
80315
  data: String9("data").pipe(withDescription2("JSON object of template fields to update")),
79763
80316
  shared: Literals3("shared", ["true", "false"]).pipe(withDescription2("Share the template's organization-level project with every namespace (true) or stop sharing it (false); applies to every entry in the project"), optional4),
79764
- dryRun: dryRunFlag6,
80317
+ dryRun: dryRunFlag7,
79765
80318
  json: jsonFlag
79766
80319
  }).pipe(withDescription3("Update a remote template from JSON"), withExamples([
79767
80320
  {
@@ -79791,7 +80344,7 @@ var updateRemote = make29("update", {
79791
80344
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
79792
80345
  var removeRemote = make29("remove", {
79793
80346
  id: templateIdArgument2,
79794
- dryRun: dryRunFlag6,
80347
+ dryRun: dryRunFlag7,
79795
80348
  yes: Boolean6("yes").pipe(withDescription2("Confirm destructive state change"), withDefault5(false)),
79796
80349
  json: jsonFlag
79797
80350
  }).pipe(withDescription3("Deactivate a remote template; its project file and publications remain"), withExamples([{ command: "samva templates remove tmpl_abc123 --dry-run" }]), withHandler(({ id, dryRun, yes, json }) => gen2(function* () {
@@ -79804,7 +80357,7 @@ var removeRemote = make29("remove", {
79804
80357
  nonInteractiveMessage: "remove requires --yes in non-interactive mode. Preview with --dry-run."
79805
80358
  });
79806
80359
  const client = yield* getClientProvider;
79807
- const result = yield* remove13({ id }).pipe(client.provide, mapError2((error) => toCliApiError("Could not remove template", error)));
80360
+ const result = yield* remove15({ id }).pipe(client.provide, mapError2((error) => toCliApiError("Could not remove template", error)));
79808
80361
  if (json)
79809
80362
  return yield* printJson(result);
79810
80363
  yield* printFields([
@@ -79812,18 +80365,6 @@ var removeRemote = make29("remove", {
79812
80365
  ["Removed", result.success]
79813
80366
  ]);
79814
80367
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
79815
- var exportProject = (input) => runToolchain({
79816
- action: `Could not export templates${input.dir === undefined ? "" : ` in ${input.dir}`}`,
79817
- options: {
79818
- root: process.cwd(),
79819
- out: resolve16(input.out),
79820
- ...input.dir === undefined ? {} : { dir: resolve16(input.dir) },
79821
- ...input.assetBase === undefined ? {} : { assetBase: input.assetBase }
79822
- },
79823
- script: `import { exportProject } from "@samva/vite/project"; ${OPTIONS_PRELUDE}
79824
- ${brandResolverPrelude()}
79825
- process.stdout.write(JSON.stringify(await exportProject(options)));`
79826
- });
79827
80368
  var exportCommand = make29("export", {
79828
80369
  dir: dirFlag,
79829
80370
  out: Directory2("out").pipe(withDescription2("Directory to write rendered fixtures, assets, and the catalog into")),
@@ -79844,7 +80385,7 @@ var exportCommand = make29("export", {
79844
80385
  const result = {
79845
80386
  action: "templates.export",
79846
80387
  ok: errors.length === 0,
79847
- out: resolve16(out),
80388
+ out: resolve17(out),
79848
80389
  assetBase: receipt.assets.base,
79849
80390
  templates: receipt.templates.map((template) => ({
79850
80391
  id: template.id,
@@ -80486,7 +81027,7 @@ var list27 = /* @__PURE__ */ make55("webhooksList");
80486
81027
  var create16 = /* @__PURE__ */ make55("webhooksCreate");
80487
81028
  var getSecret = /* @__PURE__ */ make55("webhooksGetSecret");
80488
81029
  var get28 = /* @__PURE__ */ make55("webhooksGet");
80489
- var remove14 = /* @__PURE__ */ make55("webhooksRemove");
81030
+ var remove16 = /* @__PURE__ */ make55("webhooksRemove");
80490
81031
  var update14 = /* @__PURE__ */ make55("webhooksUpdate");
80491
81032
  var test = /* @__PURE__ */ make55("webhooksTest");
80492
81033
  var getLogs = /* @__PURE__ */ make55("webhooksGetLogs");
@@ -80808,7 +81349,7 @@ var update15 = make29("update", {
80808
81349
  return yield* printJson(result);
80809
81350
  yield* printFields(objectRows(result));
80810
81351
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
80811
- var remove15 = make29("remove", {
81352
+ var remove17 = make29("remove", {
80812
81353
  id: idArgument4,
80813
81354
  dryRun: Boolean6("dry-run").pipe(withDefault5(false)),
80814
81355
  yes: Boolean6("yes").pipe(withDefault5(false)),
@@ -80826,7 +81367,7 @@ var remove15 = make29("remove", {
80826
81367
  nonInteractiveMessage: "Permanent webhook deletion requires --yes in non-interactive mode. Preview with --dry-run."
80827
81368
  });
80828
81369
  const client = yield* getClientProvider;
80829
- const result = yield* remove14({ id }).pipe(client.provide, mapError2((error) => toCliApiError("Could not remove webhook", error)));
81370
+ const result = yield* remove16({ id }).pipe(client.provide, mapError2((error) => toCliApiError("Could not remove webhook", error)));
80830
81371
  if (json)
80831
81372
  return yield* printJson(result);
80832
81373
  yield* log(`Removed webhook ${id}.`);
@@ -80842,7 +81383,7 @@ var webhooksCommand = make29("webhooks").pipe(withDescription3("Inspect, test, a
80842
81383
  stats3,
80843
81384
  retry4,
80844
81385
  rotate3,
80845
- remove15
81386
+ remove17
80846
81387
  ]));
80847
81388
 
80848
81389
  // src/lib/root-commands.ts