affect-cli 0.0.3 → 0.0.5

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/README.md +24 -11
  2. package/dist/index.js +671 -85
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # affect-cli
2
2
 
3
- Shared environment and deployment commands for [Affect](https://github.com/ludicroushq/affect). Requires Bun 1.4.2 or later and a project-local Convex CLI.
3
+ Shared environment and deployment commands for Affect projects. Requires Bun 1.4.2 or later and a project-local Convex CLI.
4
4
 
5
5
  ```sh
6
6
  bun add --dev --exact affect-cli
@@ -10,23 +10,36 @@ bunx --no-install affect --help
10
10
  Run commands from the application repository root:
11
11
 
12
12
  - `affect env` prints shell exports from `env/{development,preview,production,ci}/.env`. It requires the matching `DOTENV_PRIVATE_KEY` when that file exists. Treat its output as secrets.
13
+ - `affect env sync-dev [--force]` reads only `env/development/.env` with its matching `DOTENV_PRIVATE_KEY` and synchronizes declared server settings to its verified hosted development backend. The profile must provide the canonical `VITE_CONVEX_URL` and `VITE_CONVEX_SITE_URL` pair and a development-scoped deploy key. It never deploys code or builds the frontend. It ignores inherited CI, production, account-token and provider-host selection. Missing required settings fail; absent optional settings preserve remote values; explicit empty strings remain values. Existing differing values refuse the entire batch unless `--force` is supplied. Omission never deletes a remote setting. Output contains only the verified target and setting counts.
13
14
  - `affect run -- <command> [args...]` loads that environment and runs a command. Existing environment values take precedence; arguments, standard I/O, and exit codes are preserved.
14
15
  - `affect dotenvx -- …` forwards arguments to the bundled native dotenvx CLI, for example `affect dotenvx -- encrypt -f env/development/.env --no-armor --no-native`.
15
16
  - `affect setup` initializes an anonymous local Convex backend only for a fresh project, or retries its environment import. It preserves existing linked deployments and credentials.
16
- - `affect deploy` uses Convex's native deploy/build sequence, imports the selected backend environment, and builds the frontend. Vercel supplies `VERCEL_ENV=preview|production` and the preview branch name automatically; put the matching deploy key in the encrypted file. `--build` is its internal Convex callback.
17
+ - `affect deploy` invokes Convex's native deploy sequence for the selected Vercel-scoped profile. Native `convex deploy` pushes Convex backend code and runs the root build callback; Vercel supplies `VERCEL_ENV=preview|production` and the preview branch name automatically, and the matching deploy key remains encrypted in the profile. The callback requires the verified `VITE_CONVEX_URL` and `VITE_CONVEX_SITE_URL` pair and runs the root TanStack build. Frontend build output is not published by `affect deploy`; publication belongs to the configured frontend host. `--build` is its internal Convex callback.
17
18
 
18
- Selection uses `VERCEL_ENV`, then `ci` for GitHub Actions/CI, otherwise `development`. Files stay in the application; cryptography stays in dotenvx. Deployment follows Affect's `apps/web` layout.
19
+ Except for the explicitly development-only `env sync-dev`, selection uses `VERCEL_ENV`, then `ci` for GitHub Actions/CI, otherwise `development`. Files stay in the application; cryptography stays in dotenvx. Deployment follows Affect's `apps/web` layout.
19
20
 
20
- For example, an application's package scripts can load their environment automatically:
21
+ `env sync-dev` requires a declaration-only `convex/env.config.ts` with a default Convex `EnvDefinition` export. Share that same object with `defineApp({ env })`; keep it independent of generated files, app/component initialization and runtime environment reads. The Bun-only CLI loads this trusted module directly. It does not execute `convex.config.ts` or generate an allowlist from source text. For example:
21
22
 
22
- ```json
23
- {
24
- "scripts": {
25
- "dev": "affect run -- convex dev --start \"bun run dev:web\""
26
- }
27
- }
23
+ ```ts
24
+ import type { EnvDefinition } from "convex/server";
25
+ import { v } from "convex/values";
26
+
27
+ export default {
28
+ API_TOKEN: v.string(),
29
+ OPTIONAL_SETTING: v.optional(v.string()),
30
+ } satisfies EnvDefinition;
28
31
  ```
29
32
 
33
+ Use the profile's development-scoped deploy key and canonical public Convex cloud/site origins (`VITE_CONVEX_URL` and `VITE_CONVEX_SITE_URL`). Before any update the command verifies the provider's cloud/development identity, owning team/project and both canonical URLs. Reserved credential/control/client-public names are rejected even if declared. Only present declared server settings are sent, using the native Convex JSON environment API without temporary plaintext files; native Convex remains responsible for provider limits and application value validation. The command needs metadata and environment read/write permissions; it does not create credentials or fall back to a broader login. Profile values use the same dotenvx decryption and interpolation as `affect env`. The profile remains encrypted with dotenvx; only settings declared by `convex/env.config.ts` cross into the backend.
34
+
35
+ From the application root, run the TanStack development script with the selected profile:
36
+
37
+ ```sh
38
+ bunx --no-install affect run -- bun run dev
39
+ ```
40
+
41
+ `affect run` supplies the selected profile to the application process; it does not start a Convex watcher. The canonical Convex URLs are Vite build-time values. A host may separately provide runtime-only listener settings for its server, but `affect deploy` does not create a runtime env file or copy the deploy key into the frontend. Vercel supplies `VERCEL_ENV` and, for previews, `VERCEL_GIT_COMMIT_REF`; the CLI uses those host values to select the target and branch, then reads the matching deploy key from the encrypted profile.
42
+
30
43
  ## Development and releases
31
44
 
32
45
  ```sh
@@ -37,4 +50,4 @@ bun run changeset version
37
50
  bun run release
38
51
  ```
39
52
 
40
- For automated releases, commit a changeset with each releasable change. The release workflow opens a version PR; merging it publishes to npm. Configure npm's GitHub trusted publisher for `ludicroushq/affect-cli`, workflow `release.yml`, with direct publishing enabled. No npm token is needed. `bun run release` remains available for an intentional manual release.
53
+ For automated releases, commit a changeset with each releasable change. The release workflow opens a version PR; merging it publishes to npm. Configure npm's GitHub trusted publisher for `nahtnam/affect-cli`, workflow `release.yml`, with direct publishing enabled. No npm token is needed. `bun run release` remains available for an intentional manual release.
package/dist/index.js CHANGED
@@ -1684,6 +1684,7 @@ var normalize = (n) => n > 0 ? Math.floor(n) : 0;
1684
1684
  // node_modules/effect/dist/Result.js
1685
1685
  var succeed2 = succeed;
1686
1686
  var fail2 = fail;
1687
+ var failVoid = /* @__PURE__ */ fail2(undefined);
1687
1688
  var try_ = (evaluate) => {
1688
1689
  if (isFunction(evaluate)) {
1689
1690
  try {
@@ -1841,6 +1842,17 @@ var contains = /* @__PURE__ */ containsWith(/* @__PURE__ */ asEquivalence());
1841
1842
  var empty2 = () => [];
1842
1843
  var of = (a) => [a];
1843
1844
  var map3 = /* @__PURE__ */ dual(2, (self, f) => self.map(f));
1845
+ var filterMap = /* @__PURE__ */ dual(2, (self, f) => {
1846
+ const as = fromIterable(self);
1847
+ const out = [];
1848
+ for (let i = 0;i < as.length; i++) {
1849
+ const result = f(as[i], i);
1850
+ if (isSuccess2(result)) {
1851
+ out.push(result.success);
1852
+ }
1853
+ }
1854
+ return out;
1855
+ });
1844
1856
  var filter = /* @__PURE__ */ dual(2, (self, predicate) => {
1845
1857
  const as = fromIterable(self);
1846
1858
  const out = [];
@@ -3475,6 +3487,7 @@ var flatMapEager = /* @__PURE__ */ dual(2, (self, f) => {
3475
3487
  }
3476
3488
  return flatMap2(self, f);
3477
3489
  });
3490
+ var flatten2 = (self) => flatMap2(self, identity);
3478
3491
  var map4 = /* @__PURE__ */ dual(2, (self, f) => new ContImpl(self, mapCont, f));
3479
3492
  var mapEager = /* @__PURE__ */ dual(2, (self, f) => effectIsExit(self) ? exitMap(self, f) : map4(self, f));
3480
3493
  var mapErrorEager = /* @__PURE__ */ dual(2, (self, f) => effectIsExit(self) ? exitMapError(self, f) : mapError(self, f));
@@ -5059,6 +5072,7 @@ var try_3 = try_2;
5059
5072
  var withFiber2 = withFiber;
5060
5073
  var fromResult2 = fromResult;
5061
5074
  var flatMap3 = flatMap2;
5075
+ var flatten3 = flatten2;
5062
5076
  var andThen2 = andThen;
5063
5077
  var tap2 = tap;
5064
5078
  var exit2 = exit;
@@ -7558,6 +7572,28 @@ function String3() {
7558
7572
  function Number4() {
7559
7573
  return transform(globalThis.Number);
7560
7574
  }
7575
+ function parseJson(options) {
7576
+ return transformEffect((input, parseOptions) => try_3({
7577
+ try: () => JSON.parse(input, options?.reviver),
7578
+ catch: () => new InvalidValue({
7579
+ expected: "a valid JSON string"
7580
+ }, input, parseOptions)
7581
+ }));
7582
+ }
7583
+ function stringifyJson(options) {
7584
+ return transformEffect((input, parseOptions) => try_3({
7585
+ try: () => {
7586
+ const output = JSON.stringify(input, options?.replacer, options?.space);
7587
+ if (output === undefined) {
7588
+ throw new TypeError("Value cannot be represented as JSON");
7589
+ }
7590
+ return output;
7591
+ },
7592
+ catch: () => new InvalidValue({
7593
+ expected: "a JSON-serializable value"
7594
+ }, input, parseOptions)
7595
+ }));
7596
+ }
7561
7597
  function encodeBase64() {
7562
7598
  return transform(encode);
7563
7599
  }
@@ -7567,6 +7603,15 @@ function decodeBase64() {
7567
7603
  }, input, options)));
7568
7604
  }
7569
7605
 
7606
+ // node_modules/effect/dist/internal/schema/content.js
7607
+ var ContentTypeId = /* @__PURE__ */ Symbol.for("~effect/Schema/content");
7608
+ function setContent(operation, content) {
7609
+ Object.defineProperty(operation, ContentTypeId, {
7610
+ value: content
7611
+ });
7612
+ return operation;
7613
+ }
7614
+
7570
7615
  // node_modules/effect/dist/SchemaTransformation.js
7571
7616
  var TypeId18 = "~effect/SchemaTransformation/Transformation";
7572
7617
  var Transformation = class extends Class {
@@ -7664,6 +7709,12 @@ var urlFromString = /* @__PURE__ */ transformEffect2({
7664
7709
  encode: (url) => succeed6(url.href)
7665
7710
  });
7666
7711
  var uint8ArrayFromBase64String = /* @__PURE__ */ new Transformation(/* @__PURE__ */ decodeBase64(), /* @__PURE__ */ encodeBase64());
7712
+ function fromJsonString(options) {
7713
+ return new Transformation(parseJson(options ?? {}), setContent(stringifyJson(options), {
7714
+ mediaType: "application/json",
7715
+ preserveSchema: options?.reviver === undefined && options?.replacer === undefined
7716
+ }));
7717
+ }
7667
7718
 
7668
7719
  // node_modules/effect/dist/SchemaAST.js
7669
7720
  function makeGuard(tag) {
@@ -12355,6 +12406,7 @@ function Literal2(literal) {
12355
12406
  return out;
12356
12407
  }
12357
12408
  var Unknown2 = /* @__PURE__ */ make25(unknown);
12409
+ var Null2 = /* @__PURE__ */ make25(null_);
12358
12410
  var Undefined2 = /* @__PURE__ */ make25(undefined_2);
12359
12411
  var String5 = /* @__PURE__ */ make25(string2);
12360
12412
  var Number6 = /* @__PURE__ */ make25(number2);
@@ -12408,6 +12460,7 @@ function Literals(literals) {
12408
12460
  }
12409
12461
  });
12410
12462
  }
12463
+ var NullOr = /* @__PURE__ */ lambda((self) => Union2([self, Null2]));
12411
12464
  var UndefinedOr = /* @__PURE__ */ lambda((self) => Union2([self, Undefined2]));
12412
12465
  function decodeTo2(to, transformation) {
12413
12466
  return (from) => {
@@ -12485,6 +12538,45 @@ function isBase64(annotations) {
12485
12538
  ...annotations
12486
12539
  });
12487
12540
  }
12541
+ function makeIsGreaterThan(options) {
12542
+ const gt = isGreaterThan(options.order);
12543
+ const formatter = options.formatter ?? format;
12544
+ return (exclusiveMinimum, annotations) => {
12545
+ return makeFilter2((input) => gt(input, exclusiveMinimum), {
12546
+ expected: `a value greater than ${formatter(exclusiveMinimum)}`,
12547
+ arbitraryConstraint: {
12548
+ order: options.order,
12549
+ minimum: exclusiveMinimum,
12550
+ exclusiveMinimum: true
12551
+ },
12552
+ ...options.annotate?.(exclusiveMinimum),
12553
+ ...annotations
12554
+ });
12555
+ };
12556
+ }
12557
+ function encodeNumberPayload(number) {
12558
+ if (!globalThis.Number.isFinite(number)) {
12559
+ throw new globalThis.RangeError(`Expected a finite number, got ${format(number)}`);
12560
+ }
12561
+ return number;
12562
+ }
12563
+ var isGreaterThan2 = /* @__PURE__ */ makeIsGreaterThan({
12564
+ order: Number2,
12565
+ annotate: (exclusiveMinimum) => ({
12566
+ representation: {
12567
+ id: "effect/schema/isGreaterThan",
12568
+ payload: {
12569
+ exclusiveMinimum: encodeNumberPayload(exclusiveMinimum)
12570
+ }
12571
+ },
12572
+ toJsonSchema: () => ({
12573
+ exclusiveMinimum
12574
+ }),
12575
+ toCode: () => ({
12576
+ runtime: `Schema.isGreaterThan(${format(exclusiveMinimum)})`
12577
+ })
12578
+ })
12579
+ });
12488
12580
  function isInt(annotations) {
12489
12581
  return makeFilter2((n) => globalThis.Number.isSafeInteger(n), {
12490
12582
  expected: "an integer",
@@ -12614,6 +12706,13 @@ var URL2 = /* @__PURE__ */ instanceOf(globalThis.URL, {
12614
12706
  expected: "URL",
12615
12707
  toCodecJson: () => link3()(URLString, urlFromString)
12616
12708
  });
12709
+ var URLFromString = /* @__PURE__ */ URLString.pipe(/* @__PURE__ */ decodeTo2(URL2, urlFromString));
12710
+ var JsonString = /* @__PURE__ */ String5.annotate({
12711
+ expected: "a string that will be decoded as JSON"
12712
+ });
12713
+ function fromJsonString2(schema, options) {
12714
+ return JsonString.pipe(decodeTo2(schema, fromJsonString(options)));
12715
+ }
12617
12716
  var File = /* @__PURE__ */ instanceOf(globalThis.File, {
12618
12717
  representation: {
12619
12718
  id: "effect/schema/File",
@@ -13649,7 +13748,7 @@ var Custom = (initialState, ...args) => {
13649
13748
  op.events = events;
13650
13749
  return op;
13651
13750
  };
13652
- var Date4 = (options) => {
13751
+ var Date5 = (options) => {
13653
13752
  const opts = {
13654
13753
  initial: new globalThis.Date,
13655
13754
  dateMask: "YYYY-MM-DD HH:mm:ss",
@@ -18141,7 +18240,7 @@ var promptSingle = (single) => {
18141
18240
  })), commandLineArg);
18142
18241
  }
18143
18242
  case "Date":
18144
- return map6(run3(Date4({
18243
+ return map6(run3(Date5({
18145
18244
  message
18146
18245
  })), (date) => commandLineArg(date.toISOString()));
18147
18246
  case "Finite":
@@ -18519,7 +18618,7 @@ var runWith2 = (command, config) => {
18519
18618
  }) => andThen2(showUserError(error, userMessage), fail7(error))), catchFilter2((e) => isQuitError(e) ? succeed2(e) : fail2(e), (_) => interrupt2));
18520
18619
  };
18521
18620
  // package.json
18522
- var version = "0.0.3";
18621
+ var version = "0.0.5";
18523
18622
  // node_modules/effect/dist/http/HttpBody.js
18524
18623
  var TypeId35 = "~effect/http/HttpBody";
18525
18624
  var HttpBodyErrorTypeId = "~effect/http/HttpBody/HttpBodyError";
@@ -18804,6 +18903,7 @@ var make32 = (method) => (url, options) => modify(empty8, {
18804
18903
  url,
18805
18904
  ...options ?? undefined
18806
18905
  });
18906
+ var get4 = /* @__PURE__ */ make32("GET");
18807
18907
  var post = /* @__PURE__ */ make32("POST");
18808
18908
  var modify = /* @__PURE__ */ dual(2, (self, options) => {
18809
18909
  let result = self;
@@ -18880,14 +18980,6 @@ var getWebSource = (self) => self[WebSourceSymbol]?.();
18880
18980
  // node_modules/effect/dist/http/HttpClientResponse.js
18881
18981
  var TypeId39 = "~effect/http/HttpClientResponse";
18882
18982
  var fromWeb = (request, source) => new WebHttpClientResponse(request, source);
18883
- var filterStatusOk = (self) => self.status >= 200 && self.status < 300 ? succeed6(self) : fail7(new HttpClientError({
18884
- reason: new StatusCodeError({
18885
- response: self,
18886
- request: self.request,
18887
- description: "non 2xx status code"
18888
- })
18889
- }));
18890
-
18891
18983
  class WebHttpClientResponse extends Class2 {
18892
18984
  [TypeId38];
18893
18985
  [TypeId39];
@@ -19073,7 +19165,6 @@ var addHeaderAttributes = (span, phase, headers, filter, redactedNames) => {
19073
19165
  var TypeId40 = "~effect/http/HttpClient";
19074
19166
  var HttpClient = /* @__PURE__ */ Service("effect/HttpClient");
19075
19167
  var transformResponse = /* @__PURE__ */ dual(2, (self, f) => makeWith2((request) => f(self.postprocess(request)), self.preprocess));
19076
- var filterStatusOk2 = /* @__PURE__ */ transformResponse(/* @__PURE__ */ flatMap3(filterStatusOk));
19077
19168
  var makeWith2 = (postprocess, preprocess) => {
19078
19169
  const self = Object.create(Proto14);
19079
19170
  self.preprocess = preprocess;
@@ -19478,6 +19569,469 @@ var exports = fn2("Environment.exports")(function* exports() {
19478
19569
  `);
19479
19570
  });
19480
19571
 
19572
+ // src/deploy.ts
19573
+ var controlPlane = "https://api.convex.dev";
19574
+ var callbackMarker = "AFFECT_CONVEX_DEPLOY_CALLBACK";
19575
+ var callbackCommand = "bunx --no-install affect deploy --build";
19576
+ var deploymentNames = [
19577
+ "CONVEX_DEPLOY_KEY",
19578
+ "CONVEX_DEPLOYMENT_TOKEN",
19579
+ "CONVEX_DEPLOYMENT",
19580
+ "CONVEX_OVERRIDE_ACCESS_TOKEN",
19581
+ "CONVEX_SELF_HOSTED_URL",
19582
+ "CONVEX_SELF_HOSTED_ADMIN_KEY",
19583
+ "CONVEX_AGENT_MODE",
19584
+ "CONVEX_PROVISION_HOST"
19585
+ ];
19586
+ var canonicalNames = ["VITE_CONVEX_URL", "VITE_CONVEX_SITE_URL"];
19587
+ var publicBuildNames = ["VITE_SITE_URL", "VITE_POSTHOG_HOST", "VITE_POSTHOG_KEY"];
19588
+ var hostNames = [
19589
+ "CI",
19590
+ "GITHUB_ACTIONS",
19591
+ "VERCEL",
19592
+ "VERCEL_TARGET_ENV",
19593
+ "NOW_BUILDER",
19594
+ "VERCEL_GIT_COMMIT_SHA",
19595
+ "VERCEL_URL",
19596
+ "VERCEL_BRANCH_URL",
19597
+ "VERCEL_PROJECT_PRODUCTION_URL"
19598
+ ];
19599
+ var optionalSystemNames = ["TMPDIR", "TMP", "TEMP", "LANG", "LC_ALL"];
19600
+ var proxyNames = [
19601
+ "NODE_USE_ENV_PROXY",
19602
+ "HTTP_PROXY",
19603
+ "HTTPS_PROXY",
19604
+ "ALL_PROXY",
19605
+ "NO_PROXY",
19606
+ "http_proxy",
19607
+ "https_proxy",
19608
+ "all_proxy",
19609
+ "no_proxy"
19610
+ ];
19611
+ var reservedSetting = /^(?:CONVEX_|DOTENV_|(?:[A-Z0-9]+_)*PUBLIC_|VITE_|REACT_APP_|EXPO_PUBLIC_|VERCEL_|GITHUB_|NODE_|BUN_|AFFECT_|CI$|PATH$|HOME$|USERPROFILE$|PWD$|SHELL$|ENV$|TMPDIR$|(?:HTTPS?|ALL|NO)_PROXY$)/u;
19612
+ var failure = (cause) => new UserError({ cause });
19613
+ var empty9 = "";
19614
+ var Origin = URLFromString.check(makeFilter2((url) => url.protocol === "https:" && url.username === "" && url.password === "" && url.pathname === "/" && url.search === "" && url.hash === ""));
19615
+ var Id = Int.check(isGreaterThan2(0));
19616
+ var Slug = String5.check(isPattern2(/^[a-z0-9][a-z0-9_-]*$/u));
19617
+ var Identity = Struct({
19618
+ project: Slug,
19619
+ projectId: Id,
19620
+ team: Slug,
19621
+ teamId: Id
19622
+ });
19623
+ var Deployment = Struct({
19624
+ deploymentType: Literals(["dev", "preview", "prod"]),
19625
+ id: Id,
19626
+ kind: Literal2("cloud"),
19627
+ projectId: Id,
19628
+ teamId: Id
19629
+ });
19630
+ var CanonicalUrls = Struct({
19631
+ convexCloudUrl: Origin,
19632
+ convexSiteUrl: Origin
19633
+ });
19634
+ var PreviewAuthorization = Struct({
19635
+ adminKey: NonEmptyString,
19636
+ deploymentName: NonEmptyString,
19637
+ deploymentType: Literal2("preview"),
19638
+ isDefault: Boolean3,
19639
+ reference: NullOr(String5),
19640
+ url: Origin
19641
+ });
19642
+ var Declaration2 = Record(String5, Struct({
19643
+ isConvexValidator: Literal2(true),
19644
+ isOptional: Literals(["required", "optional"])
19645
+ }));
19646
+ var RootManifest = Struct({
19647
+ dependencies: optional2(Record(String5, String5)),
19648
+ devDependencies: optional2(Record(String5, String5)),
19649
+ scripts: Record(String5, String5),
19650
+ workspaces: ArraySchema(String5)
19651
+ });
19652
+ var WebManifest = Struct({
19653
+ dependencies: optional2(Record(String5, String5)),
19654
+ devDependencies: optional2(Record(String5, String5)),
19655
+ name: Literal2("@affect/web"),
19656
+ scripts: Record(String5, String5)
19657
+ });
19658
+ var inheritedNames = [
19659
+ "PATH",
19660
+ "CI",
19661
+ "GITHUB_ACTIONS",
19662
+ "VERCEL_ENV",
19663
+ "VERCEL_GIT_COMMIT_REF",
19664
+ "NODE_OPTIONS",
19665
+ "BUN_OPTIONS",
19666
+ ...deploymentNames,
19667
+ ...canonicalNames,
19668
+ ...publicBuildNames,
19669
+ ...hostNames,
19670
+ ...optionalSystemNames,
19671
+ ...proxyNames
19672
+ ];
19673
+ var readInheritedEnvironment = fnUntraced2(function* readInheritedEnvironment() {
19674
+ const inherited = {};
19675
+ for (const name of inheritedNames) {
19676
+ inherited[name] = yield* String6(name).pipe(withDefault3(empty9));
19677
+ }
19678
+ return inherited;
19679
+ });
19680
+ var httpRequest = fnUntraced2(function* httpRequest(url, authorization, body) {
19681
+ const client = yield* HttpClient;
19682
+ const request = setHeader(body === undefined ? get4(url) : post(url), "Authorization", authorization);
19683
+ const outgoing = body === undefined ? request : yield* bodyJson(request, body);
19684
+ const response = yield* client.execute(outgoing);
19685
+ if (response.status !== 200) {
19686
+ return yield* failure("Convex deployment verification failed.");
19687
+ }
19688
+ return response;
19689
+ }, mapError2(() => failure("Convex deployment verification failed.")));
19690
+ var json2 = (schema, response) => response.json.pipe(flatMap3(decodeEffect2(schema)), mapError2(() => failure("Convex deployment returned invalid metadata.")));
19691
+ var validateDeploymentControls = fnUntraced2(function* validateDeploymentControls(values, inherited) {
19692
+ for (const name of deploymentNames) {
19693
+ const configured = values[name] ?? empty9;
19694
+ const ambient = inherited[name] ?? empty9;
19695
+ if (name === "CONVEX_DEPLOY_KEY") {
19696
+ continue;
19697
+ }
19698
+ if (name === "CONVEX_PROVISION_HOST") {
19699
+ if (configured !== empty9 && configured !== controlPlane || ambient !== empty9 && ambient !== controlPlane) {
19700
+ return yield* failure("Convex provider host must be the standard Convex control plane.");
19701
+ }
19702
+ continue;
19703
+ }
19704
+ if (configured !== empty9 || ambient !== empty9) {
19705
+ return yield* failure("Conflicting Convex deployment authentication or target settings are not allowed.");
19706
+ }
19707
+ }
19708
+ return yield* void_3;
19709
+ });
19710
+ var validateProfileKey = fnUntraced2(function* validateProfileKey(target, branch, deployKey, values, inherited) {
19711
+ const matchingPrefix = target === "preview" ? "preview:" : "prod:";
19712
+ if (!deployKey.startsWith(matchingPrefix)) {
19713
+ return yield* failure("Encrypted project file must contain a Convex deploy key matching VERCEL_ENV.");
19714
+ }
19715
+ if (target === "preview") {
19716
+ if (!/^[A-Za-z0-9][A-Za-z0-9_./-]*$/u.test(branch)) {
19717
+ return yield* failure("Preview deployment requires a valid branch name.");
19718
+ }
19719
+ const parts = /^preview:(?<team>[a-z0-9][a-z0-9_-]*):(?<project>[a-z0-9][a-z0-9_-]*)\|(?<secret>.+)$/u.exec(deployKey);
19720
+ const groups = parts?.groups;
19721
+ if (groups?.["team"] === undefined || groups["project"] === undefined || groups["secret"] === undefined) {
19722
+ return yield* failure("Encrypted project file must contain a valid preview-scoped Convex deploy key.");
19723
+ }
19724
+ return {
19725
+ branch,
19726
+ deployKey,
19727
+ inherited,
19728
+ projectSlug: groups["project"],
19729
+ target,
19730
+ teamSlug: groups["team"],
19731
+ values
19732
+ };
19733
+ }
19734
+ if (!/^prod:[a-z0-9][a-z0-9-]*\|.+$/u.test(deployKey)) {
19735
+ return yield* failure("Encrypted project file must contain a valid production-scoped Convex deploy key.");
19736
+ }
19737
+ return { branch, deployKey, inherited, target, values };
19738
+ });
19739
+ var profile = fn2("Deploy.profile")(function* profile() {
19740
+ const { target, values } = yield* load();
19741
+ if (target !== "preview" && target !== "production") {
19742
+ return yield* failure("Deploy requires VERCEL_ENV=preview or production.");
19743
+ }
19744
+ const inherited = yield* readInheritedEnvironment();
19745
+ const inheritedKey = inherited["CONVEX_DEPLOY_KEY"] ?? empty9;
19746
+ const deployKey = values["CONVEX_DEPLOY_KEY"] ?? empty9;
19747
+ if (inheritedKey !== empty9 && inheritedKey !== deployKey) {
19748
+ return yield* failure("CONVEX_DEPLOY_KEY conflicts with the selected encrypted project file.");
19749
+ }
19750
+ yield* validateDeploymentControls(values, inherited);
19751
+ return yield* validateProfileKey(target, inherited["VERCEL_GIT_COMMIT_REF"] ?? empty9, deployKey, values, inherited);
19752
+ });
19753
+ var readDeclaration = fn2("Deploy.readDeclaration")(function* readDeclaration(root) {
19754
+ const path = yield* Path;
19755
+ const declarationPath = path.join(root, "convex", "env.config.ts");
19756
+ const imported = yield* try_3({
19757
+ catch: () => failure("Cannot load the declaration-only convex/env.config.ts."),
19758
+ try: () => {
19759
+ const module = import.meta.require(declarationPath);
19760
+ return module;
19761
+ }
19762
+ });
19763
+ const decoded = yield* decodeUnknownEffect2(Struct({ default: Declaration2 }))(imported).pipe(mapError2(() => failure("Cannot load a valid declaration-only Convex environment definition.")));
19764
+ const keys2 = keys(decoded.default);
19765
+ yield* decodeEffect2(ArraySchema(String5.check(makeFilter2((name) => !reservedSetting.test(name)))))(keys2).pipe(mapError2(() => failure("Server declaration contains a credential, control or client-public name.")));
19766
+ return decoded.default;
19767
+ });
19768
+ var readManifest = fn2("Deploy.readManifest")(function* readManifest(root, relativePath, schema) {
19769
+ const fs = yield* FileSystem;
19770
+ const path = yield* Path;
19771
+ const filePath = path.join(root, relativePath);
19772
+ const content = yield* fs.readFileString(filePath).pipe(mapError2(() => failure("The Affect TanStack workspace build contract is unavailable.")));
19773
+ const value = yield* decodeEffect2(fromJsonString2(schema))(content).pipe(mapError2(() => failure("The Affect TanStack workspace build contract is invalid.")));
19774
+ return value;
19775
+ });
19776
+ var preflight = fn2("Deploy.preflight")(function* preflight() {
19777
+ const path = yield* Path;
19778
+ const root = path.resolve(".");
19779
+ const [rootManifest, webManifest] = yield* all2([
19780
+ readManifest(root, "package.json", RootManifest),
19781
+ readManifest(root, "apps/web/package.json", WebManifest)
19782
+ ]);
19783
+ const dependencies = { ...rootManifest.dependencies, ...rootManifest.devDependencies };
19784
+ const webDependencies = { ...webManifest.dependencies, ...webManifest.devDependencies };
19785
+ if (!contains("apps/*")(rootManifest.workspaces) || rootManifest.scripts["build"] !== "bun run --filter @affect/web build" || dependencies["vite"] === undefined || dependencies["convex"] === undefined || webManifest.scripts["build"] !== "vite build" || webDependencies["@tanstack/react-start"] === undefined || webDependencies["vite"] === undefined) {
19786
+ return yield* failure("This CLI requires the Affect TanStack workspace build contract.");
19787
+ }
19788
+ const declaration = yield* readDeclaration(root);
19789
+ return { declaration, root };
19790
+ });
19791
+ var selectedBackendSettings = (values, declaration) => map3(filter(toEntries(declaration), ([name]) => has(values, name)), ([name]) => ({ name, value: values[name] ?? empty9 }));
19792
+ var validateRequiredSettings = (values, declaration) => {
19793
+ const required = filter(toEntries(declaration), ([name, validator]) => validator.isOptional === "required" && !has(values, name));
19794
+ if (required.length === 0) {
19795
+ return void_3;
19796
+ }
19797
+ const missing = [];
19798
+ for (const [name] of required) {
19799
+ missing.push(name);
19800
+ }
19801
+ return failure(`Missing required server settings: ${join(missing, ", ")}.`);
19802
+ };
19803
+ var childProfileCollision = (values, required) => {
19804
+ for (const name of required) {
19805
+ if (has(values, name)) {
19806
+ return true;
19807
+ }
19808
+ }
19809
+ return false;
19810
+ };
19811
+ var copyDefined = (source, names, values, into) => {
19812
+ for (const name of names) {
19813
+ if (!has(values, name) && source[name] !== empty9) {
19814
+ into[name] = source[name] ?? empty9;
19815
+ }
19816
+ }
19817
+ };
19818
+ var temporaryHome = fnUntraced2(function* temporaryHome(prepareMessage, cleanupMessage) {
19819
+ const fileSystem = yield* FileSystem;
19820
+ return yield* acquireRelease2(fileSystem.makeTempDirectory({ prefix: "affect-cli-home-" }).pipe(mapError2(() => failure(prepareMessage))), (directory) => fileSystem.remove(directory, { force: true, recursive: true }).pipe(mapError2(() => failure(cleanupMessage)), ignore2));
19821
+ });
19822
+ var verifiedDeployment = fn2("Deploy.verifyDeployment")(function* verifiedDeployment(selected, callbackCloud, callbackSite) {
19823
+ const key = selected.deployKey;
19824
+ if (selected.target === "production") {
19825
+ const endpoint = yield* json2(Origin, yield* httpRequest(`${controlPlane}/api/deployment/url_for_key`, `Bearer ${key}`, {
19826
+ deployKey: key
19827
+ }));
19828
+ const deploymentAuthorization = `Convex ${key}`;
19829
+ const deploymentName = key.slice("prod:".length).split("|", 1)[0] ?? empty9;
19830
+ const identity = yield* json2(Identity, yield* httpRequest(`${controlPlane}/api/deployment/team_and_project_for_key`, `Bearer ${key}`, { deployKey: key }));
19831
+ const info = yield* json2(Deployment, yield* httpRequest(`${endpoint.origin}/api/v1/deployment_info`, deploymentAuthorization));
19832
+ if (info.deploymentType !== "prod" || info.teamId !== identity.teamId || info.projectId !== identity.projectId) {
19833
+ return yield* failure("Production deploy key and Convex deployment metadata do not match.");
19834
+ }
19835
+ const canonical = yield* json2(CanonicalUrls, yield* httpRequest(`${endpoint.origin}/api/v1/get_canonical_urls`, deploymentAuthorization));
19836
+ if (canonical.convexCloudUrl.origin !== endpoint.origin || canonical.convexCloudUrl.origin !== callbackCloud.origin || canonical.convexSiteUrl.origin !== callbackSite.origin) {
19837
+ return yield* failure("Native Convex URLs do not match the selected production deployment.");
19838
+ }
19839
+ return {
19840
+ adminKey: key,
19841
+ cloud: canonical.convexCloudUrl.origin,
19842
+ deploymentName,
19843
+ deploymentType: "prod",
19844
+ identity,
19845
+ site: canonical.convexSiteUrl.origin,
19846
+ url: endpoint.origin
19847
+ };
19848
+ }
19849
+ const teamSlugFromKey = selected.teamSlug ?? empty9;
19850
+ const projectSlugFromKey = selected.projectSlug ?? empty9;
19851
+ const authorization = yield* json2(PreviewAuthorization, yield* httpRequest(`${controlPlane}/api/deployment/authorize_preview`, `Bearer ${key}`, {
19852
+ previewName: selected.branch,
19853
+ projectSelection: {
19854
+ kind: "teamAndProjectSlugs",
19855
+ projectSlug: projectSlugFromKey,
19856
+ teamSlug: teamSlugFromKey
19857
+ }
19858
+ }));
19859
+ const endpoint = authorization.url;
19860
+ const deploymentAuthorization = `Convex ${authorization.adminKey}`;
19861
+ const { deploymentName } = authorization;
19862
+ const info = yield* json2(Deployment, yield* httpRequest(`${endpoint.origin}/api/v1/deployment_info`, deploymentAuthorization));
19863
+ if (authorization.deploymentType !== "preview" || info.deploymentType !== "preview") {
19864
+ return yield* failure("Preview authorization and Convex deployment metadata do not match.");
19865
+ }
19866
+ const canonical = yield* json2(CanonicalUrls, yield* httpRequest(`${endpoint.origin}/api/v1/get_canonical_urls`, deploymentAuthorization));
19867
+ if (canonical.convexCloudUrl.origin !== endpoint.origin || canonical.convexCloudUrl.origin !== callbackCloud.origin || canonical.convexSiteUrl.origin !== callbackSite.origin) {
19868
+ return yield* failure("Native Convex URLs do not match the selected preview deployment.");
19869
+ }
19870
+ return {
19871
+ adminKey: authorization.adminKey,
19872
+ cloud: canonical.convexCloudUrl.origin,
19873
+ deploymentName,
19874
+ deploymentType: "preview",
19875
+ identity: {
19876
+ project: projectSlugFromKey,
19877
+ projectId: info.projectId,
19878
+ team: teamSlugFromKey,
19879
+ teamId: info.teamId
19880
+ },
19881
+ site: canonical.convexSiteUrl.origin,
19882
+ url: endpoint.origin
19883
+ };
19884
+ });
19885
+ var buildEnvironment = (selected, values, verified, scratchHome) => {
19886
+ const child = {};
19887
+ copyDefined(selected.inherited, ["PATH", ...optionalSystemNames], values, child);
19888
+ child[process.platform === "win32" ? "USERPROFILE" : "HOME"] = scratchHome;
19889
+ child["VERCEL_ENV"] = selected.target;
19890
+ if (selected.target === "preview") {
19891
+ child["VERCEL_GIT_COMMIT_REF"] = selected.branch;
19892
+ }
19893
+ copyDefined(selected.inherited, hostNames, values, child);
19894
+ for (const name of canonicalNames) {
19895
+ child[name] = name === "VITE_CONVEX_URL" ? verified.cloud : verified.site;
19896
+ }
19897
+ for (const name of publicBuildNames) {
19898
+ const profileValue = values[name];
19899
+ const inheritedValue = selected.inherited[name] ?? empty9;
19900
+ if (profileValue !== undefined) {
19901
+ child[name] = profileValue;
19902
+ } else if (inheritedValue !== empty9) {
19903
+ child[name] = inheritedValue;
19904
+ }
19905
+ }
19906
+ return child;
19907
+ };
19908
+ var syncSettings = fn2("Deploy.syncSettings")(function* syncSettings(verified, settings) {
19909
+ if (settings.length === 0) {
19910
+ return yield* void_3;
19911
+ }
19912
+ const client = yield* HttpClient;
19913
+ const request = yield* bodyJson(setHeader(post(`${verified.url}/api/v1/update_environment_variables`), "Authorization", `Convex ${verified.adminKey}`), { changes: settings });
19914
+ const response = yield* client.execute(request);
19915
+ if (response.status !== 200) {
19916
+ return yield* failure("Could not synchronize Convex environment variables.");
19917
+ }
19918
+ return yield* void_3;
19919
+ }, mapError2(() => failure("Could not synchronize Convex environment variables.")));
19920
+ var runBuildCallback = fn2("Deploy.runBuildCallback")(function* runBuildCallback() {
19921
+ const callbackMode = yield* String6(callbackMarker).pipe(withDefault3(empty9));
19922
+ if (callbackMode !== "1") {
19923
+ return yield* failure("The Convex build callback can only run through affect deploy.");
19924
+ }
19925
+ const selected = yield* profile();
19926
+ const { declaration, root } = yield* preflight();
19927
+ yield* validateRequiredSettings(selected.values, declaration);
19928
+ const cloudText = yield* NonEmptyString2("VITE_CONVEX_URL").pipe(mapError2(() => failure("Native Convex did not inject the canonical cloud URL.")));
19929
+ const siteText = yield* NonEmptyString2("VITE_CONVEX_SITE_URL").pipe(mapError2(() => failure("Native Convex did not inject the canonical site URL.")));
19930
+ const cloud = yield* decodeEffect2(Origin)(cloudText).pipe(mapError2(() => failure("Native Convex injected an invalid cloud URL.")));
19931
+ const site = yield* decodeEffect2(Origin)(siteText).pipe(mapError2(() => failure("Native Convex injected an invalid site URL.")));
19932
+ const verified = yield* verifiedDeployment(selected, cloud, site).pipe(provide3(layer11), provideService2(RequestInit, { redirect: "error" }));
19933
+ const settings = selectedBackendSettings(selected.values, declaration);
19934
+ yield* syncSettings(verified, settings).pipe(provide3(layer11), provideService2(RequestInit, { redirect: "error" }));
19935
+ yield* error(`Convex target: ${verified.identity.team}/${verified.identity.project}; ${verified.deploymentType} ${verified.deploymentName}.`);
19936
+ const home = yield* temporaryHome("Could not prepare an isolated build environment.", "Could not clean the isolated build environment.");
19937
+ const env = buildEnvironment(selected, selected.values, verified, home);
19938
+ const child = yield* make18("bun", ["run", "build"], {
19939
+ cwd: root,
19940
+ env,
19941
+ extendEnv: false,
19942
+ stderr: "inherit",
19943
+ stdin: "inherit",
19944
+ stdout: "inherit"
19945
+ });
19946
+ const status = yield* child.exitCode;
19947
+ if (status !== 0) {
19948
+ return yield* failure(`bun run build exited with status ${status}.`);
19949
+ }
19950
+ return yield* void_3;
19951
+ });
19952
+ var nativeEnvironment = (selected, values, privateKey, home) => {
19953
+ const env = {};
19954
+ for (const name of [
19955
+ ...hostNames,
19956
+ ...optionalSystemNames,
19957
+ ...proxyNames,
19958
+ "NODE_OPTIONS",
19959
+ "BUN_OPTIONS"
19960
+ ]) {
19961
+ env[name] = empty9;
19962
+ }
19963
+ copyDefined(selected.inherited, ["PATH", ...optionalSystemNames, ...hostNames, ...proxyNames], values, env);
19964
+ env["VERCEL_ENV"] = selected.target;
19965
+ if (selected.target === "preview") {
19966
+ env["VERCEL_GIT_COMMIT_REF"] = selected.branch;
19967
+ }
19968
+ env[process.platform === "win32" ? "USERPROFILE" : "HOME"] = home;
19969
+ env["CONVEX_PROVISION_HOST"] = controlPlane;
19970
+ env["CONVEX_DEPLOY_KEY"] = selected.deployKey;
19971
+ env["DOTENV_PRIVATE_KEY"] = privateKey;
19972
+ env[callbackMarker] = "1";
19973
+ for (const name of deploymentNames) {
19974
+ if (name !== "CONVEX_DEPLOY_KEY" && name !== "CONVEX_PROVISION_HOST") {
19975
+ env[name] = empty9;
19976
+ }
19977
+ }
19978
+ for (const name of canonicalNames) {
19979
+ env[name] = empty9;
19980
+ }
19981
+ for (const name of publicBuildNames) {
19982
+ env[name] = values[name] ?? selected.inherited[name] ?? empty9;
19983
+ }
19984
+ return env;
19985
+ };
19986
+ var deployNative = fn2("Deploy.native")(function* deployNative(selected, values, root) {
19987
+ const privateKey = yield* NonEmptyString2("DOTENV_PRIVATE_KEY").pipe(mapError2(() => failure("Set the matching DOTENV_PRIVATE_KEY for the selected profile.")));
19988
+ const home = yield* temporaryHome("Could not prepare an isolated Convex configuration home.", "Could not clean the isolated Convex configuration home.");
19989
+ const env = nativeEnvironment(selected, values, privateKey, home);
19990
+ const args = [
19991
+ "--no-install",
19992
+ "convex",
19993
+ "deploy",
19994
+ "--cmd-url-env-var-name",
19995
+ "VITE_CONVEX_URL",
19996
+ ...selected.target === "preview" ? ["--preview-name", selected.branch] : [],
19997
+ "--cmd",
19998
+ callbackCommand
19999
+ ];
20000
+ const child = yield* make18("bunx", args, {
20001
+ cwd: root,
20002
+ env,
20003
+ extendEnv: false,
20004
+ stderr: "inherit",
20005
+ stdin: "inherit",
20006
+ stdout: "inherit"
20007
+ });
20008
+ const status = yield* child.exitCode;
20009
+ if (status !== 0) {
20010
+ return yield* failure(`Convex deploy exited with status ${status}.`);
20011
+ }
20012
+ return yield* void_3;
20013
+ });
20014
+ var deploy = fn2("Deploy.run")(function* deploy(build) {
20015
+ if (build) {
20016
+ return yield* runBuildCallback();
20017
+ }
20018
+ const selected = yield* profile();
20019
+ const { declaration } = yield* preflight();
20020
+ yield* validateRequiredSettings(selected.values, declaration);
20021
+ const requiredCollisions = [
20022
+ "PATH",
20023
+ "VERCEL_ENV",
20024
+ ...selected.target === "preview" ? ["VERCEL_GIT_COMMIT_REF"] : []
20025
+ ];
20026
+ if (childProfileCollision(selected.values, requiredCollisions)) {
20027
+ return yield* failure("Encrypted profile conflicts with required deployment environment settings.");
20028
+ }
20029
+ const path = yield* Path;
20030
+ const root = path.resolve(".");
20031
+ yield* error(`Convex target: ${selected.target}${selected.target === "preview" ? ` (${selected.branch})` : ""}; selected by env/${selected.target}/.env.`);
20032
+ return yield* deployNative(selected, selected.values, root);
20033
+ }, scoped2);
20034
+
19481
20035
  // src/process.ts
19482
20036
  var run6 = fn2("Process.run")(function* run(command) {
19483
20037
  const child = yield* command;
@@ -19498,76 +20052,6 @@ var convex = (args, env, input) => run6(make18("bunx", ["--no-install", "convex"
19498
20052
  stdout: "inherit"
19499
20053
  }));
19500
20054
 
19501
- // src/deploy.ts
19502
- var convexManagedEnvironmentNames = [
19503
- "CONVEX_DEPLOY_KEY",
19504
- "CONVEX_DEPLOYMENT_TOKEN",
19505
- "CONVEX_DEPLOYMENT",
19506
- "CONVEX_SELF_HOSTED_URL",
19507
- "CONVEX_SELF_HOSTED_ADMIN_KEY",
19508
- "CONVEX_URL",
19509
- "PUBLIC_CONVEX_URL",
19510
- "NEXT_PUBLIC_CONVEX_URL",
19511
- "VITE_CONVEX_URL",
19512
- "REACT_APP_CONVEX_URL",
19513
- "EXPO_PUBLIC_CONVEX_URL",
19514
- "CONVEX_SITE_URL",
19515
- "PUBLIC_CONVEX_SITE_URL",
19516
- "NEXT_PUBLIC_CONVEX_SITE_URL",
19517
- "VITE_CONVEX_SITE_URL",
19518
- "REACT_APP_CONVEX_SITE_URL",
19519
- "EXPO_PUBLIC_CONVEX_SITE_URL"
19520
- ];
19521
- var deploy = fn2("Deploy.run")(function* deploy(build) {
19522
- const environment = yield* load();
19523
- const { target, values, file } = environment;
19524
- if (target !== "preview" && target !== "production") {
19525
- return yield* new UserError({
19526
- cause: "Deploy requires VERCEL_ENV=preview or production."
19527
- });
19528
- }
19529
- const branch = yield* String6("VERCEL_GIT_COMMIT_REF").pipe(withDefault3(""));
19530
- if (target === "preview" && !branch) {
19531
- return yield* new UserError({ cause: "Preview deployment requires a branch name." });
19532
- }
19533
- const prefix = target === "preview" ? "preview:" : "prod:";
19534
- const deployKey = values["CONVEX_DEPLOY_KEY"] ?? "";
19535
- if (!deployKey.startsWith(prefix)) {
19536
- return yield* new UserError({
19537
- cause: "Encrypted project file must contain a Convex deploy key matching VERCEL_ENV."
19538
- });
19539
- }
19540
- yield* error(`Convex target: ${target}${target === "preview" ? ` (${branch})` : ""}; selected by env/${target}/.env.`);
19541
- if (build) {
19542
- const cloud = yield* NonEmptyString2("NEXT_PUBLIC_CONVEX_URL");
19543
- const site = yield* NonEmptyString2("NEXT_PUBLIC_CONVEX_SITE_URL");
19544
- const changes = map3(filter(toEntries(values), ([name]) => !contains(name)(convexManagedEnvironmentNames)), ([name, value]) => ({ name, value }));
19545
- if (changes.length > 0) {
19546
- yield* gen2(function* syncEnvironmentVariables() {
19547
- const client = yield* HttpClient;
19548
- const request = yield* bodyJson(setHeader(post(`${cloud}/api/v1/update_environment_variables`), "Authorization", `Convex ${deployKey}`), { changes });
19549
- yield* filterStatusOk2(client).execute(request).pipe(asVoid2);
19550
- }).pipe(provide3(layer11), mapError2(() => new UserError({
19551
- cause: "Could not synchronize Convex environment variables."
19552
- })));
19553
- }
19554
- return yield* run6(make18("bun", ["run", "build"], {
19555
- env: { ...values, NEXT_PUBLIC_CONVEX_SITE_URL: site, NEXT_PUBLIC_CONVEX_URL: cloud },
19556
- extendEnv: true,
19557
- stderr: "inherit",
19558
- stdout: "inherit"
19559
- })).pipe(asVoid2);
19560
- }
19561
- const fs = yield* FileSystem;
19562
- yield* fs.copyFile(file, "apps/web/.env.production");
19563
- return yield* convex([
19564
- "deploy",
19565
- ...target === "preview" ? ["--preview-name", branch] : [],
19566
- "--cmd",
19567
- "bunx --no-install affect deploy --build"
19568
- ], values).pipe(asVoid2);
19569
- });
19570
-
19571
20055
  // src/dotenv.ts
19572
20056
  var dotenvx = fn2("Dotenvx.run")(function* dotenvx(args, capture) {
19573
20057
  const path = yield* Path;
@@ -19645,15 +20129,117 @@ var setup = fn2("Setup.run")(function* setup() {
19645
20129
  return yield* log("Local Convex setup complete.");
19646
20130
  });
19647
20131
 
20132
+ // src/sync-dev.ts
20133
+ var failure2 = (cause) => new UserError({ cause });
20134
+ var reserved = /^(?:CONVEX_|DOTENV_|(?:[A-Z0-9]+_)*PUBLIC_|VITE_|REACT_APP_|EXPO_PUBLIC_|VERCEL_|GITHUB_|NODE_|BUN_|AFFECT_|CI$|PATH$|HOME$|PWD$|SHELL$|ENV$|TMPDIR$|(?:HTTPS?|ALL|NO)_PROXY$)/u;
20135
+ var SettingName = String5.check(makeFilter2((name) => !reserved.test(name)));
20136
+ var Declaration3 = Record(String5, Struct({
20137
+ isConvexValidator: Literal2(true),
20138
+ isOptional: Literals(["required", "optional"])
20139
+ }));
20140
+ var Origin2 = URLFromString.check(makeFilter2((url) => url.protocol === "https:" && url.username === "" && url.password === "" && url.pathname === "/" && url.search === "" && url.hash === ""));
20141
+ var Profile = Struct({
20142
+ CONVEX_DEPLOY_KEY: String5.check(isPattern2(/^dev:[a-z0-9-]+\|.+$/u)),
20143
+ VITE_CONVEX_SITE_URL: Origin2,
20144
+ VITE_CONVEX_URL: Origin2
20145
+ });
20146
+ var Slug2 = String5.check(isPattern2(/^[a-z0-9][a-z0-9_-]*$/u));
20147
+ var Id2 = Int.check(isGreaterThan2(0));
20148
+ var Identity2 = Struct({ project: Slug2, projectId: Id2, team: Slug2, teamId: Id2 });
20149
+ var Deployment2 = Struct({
20150
+ deploymentType: Literal2("dev"),
20151
+ id: Id2,
20152
+ kind: Literal2("cloud"),
20153
+ projectId: Id2,
20154
+ teamId: Id2
20155
+ });
20156
+ var CanonicalUrls2 = Struct({ convexCloudUrl: Origin2, convexSiteUrl: Origin2 });
20157
+ var Existing = Struct({
20158
+ environmentVariables: Record(String5, String5)
20159
+ });
20160
+ var request = fn2("DevelopmentEnv.request")(function* request(url, authorization, body) {
20161
+ const client = yield* HttpClient;
20162
+ const base = setHeader(body === undefined ? get4(url) : post(url), "Authorization", authorization);
20163
+ const outgoing = body === undefined ? base : yield* bodyJson(base, body);
20164
+ const response = yield* client.execute(outgoing);
20165
+ if (response.status !== 200) {
20166
+ return yield* failure2("Development environment request failed.");
20167
+ }
20168
+ return response;
20169
+ }, mapError2(() => failure2("Development environment request failed.")));
20170
+ var json3 = (schema, response) => response.json.pipe(flatMap3(decodeUnknownEffect2(schema)), mapError2(() => failure2("Invalid development deployment metadata.")));
20171
+ var syncDev = fn2("DevelopmentEnv.sync")(function* syncDev(force) {
20172
+ const path = yield* Path;
20173
+ const fs = yield* FileSystem;
20174
+ const file = path.resolve("env", "development", ".env");
20175
+ if (!(yield* fs.exists(file))) {
20176
+ return yield* failure2("Missing env/development/.env.");
20177
+ }
20178
+ yield* NonEmptyString2("DOTENV_PRIVATE_KEY").pipe(mapError2(() => failure2("Set the matching development DOTENV_PRIVATE_KEY.")));
20179
+ const values = yield* read2(file);
20180
+ const profile = yield* decodeUnknownEffect2(Profile)(values).pipe(mapError2(() => failure2("Development profile requires a dev-scoped key and HTTPS Convex origins.")));
20181
+ const name = profile.CONVEX_DEPLOY_KEY.split("|")[0]?.slice(4);
20182
+ if ((values["CONVEX_DEPLOYMENT"] ?? "") !== "" && values["CONVEX_DEPLOYMENT"] !== `dev:${name}`) {
20183
+ return yield* failure2("CONVEX_DEPLOYMENT does not match the development key.");
20184
+ }
20185
+ const declarationModule = yield* try_3({
20186
+ catch: () => failure2("Cannot load the declaration-only convex/env.config.ts."),
20187
+ try: () => decodeUnknownEffect2(Struct({ default: Declaration3 }))(import.meta.require(path.resolve("convex", "env.config.ts")))
20188
+ }).pipe(flatten3, mapError2(() => failure2("Cannot load a valid declaration-only convex/env.config.ts with Convex validators.")));
20189
+ const declaration = declarationModule.default;
20190
+ yield* decodeEffect2(ArraySchema(SettingName))(keys(declaration)).pipe(mapError2(() => failure2("Server declaration contains a credential, control or client-public name.")));
20191
+ const entries = toEntries(declaration);
20192
+ const missing = filter(entries, ([key, validator]) => validator.isOptional === "required" && !has(values, key));
20193
+ if (missing.length > 0) {
20194
+ return yield* failure2(`Missing required server settings: ${join(map3(missing, ([key]) => key), ", ")}.`);
20195
+ }
20196
+ const selected = filterMap(entries, ([key]) => {
20197
+ const value = has(values, key) ? values[key] : undefined;
20198
+ return value === undefined ? failVoid : succeed2({ name: key, value });
20199
+ });
20200
+ const key = profile.CONVEX_DEPLOY_KEY;
20201
+ const bearer = `Bearer ${key}`;
20202
+ const lookup = { deployKey: key };
20203
+ const cloud = yield* json3(Origin2, yield* request("https://api.convex.dev/api/deployment/url_for_key", bearer, lookup));
20204
+ if (cloud.origin !== profile.VITE_CONVEX_URL.origin) {
20205
+ return yield* failure2("Development key and public Convex URL do not match.");
20206
+ }
20207
+ const identity = yield* json3(Identity2, yield* request("https://api.convex.dev/api/deployment/team_and_project_for_key", bearer, lookup));
20208
+ const authorization = `Convex ${key}`;
20209
+ const deployment = yield* json3(Deployment2, yield* request(`${cloud.origin}/api/v1/deployment_info`, authorization));
20210
+ if (deployment.teamId !== identity.teamId || deployment.projectId !== identity.projectId) {
20211
+ return yield* failure2("Development deployment identity does not match the key.");
20212
+ }
20213
+ const urls = yield* json3(CanonicalUrls2, yield* request(`${cloud.origin}/api/v1/get_canonical_urls`, authorization));
20214
+ if (urls.convexCloudUrl.origin !== cloud.origin || urls.convexSiteUrl.origin !== profile.VITE_CONVEX_SITE_URL.origin) {
20215
+ return yield* failure2("Development canonical Convex URLs do not match the profile.");
20216
+ }
20217
+ yield* error(`Convex target: ${identity.team}/${identity.project}; development ${name} (ID ${deployment.id}).`);
20218
+ const existing = yield* json3(Existing, yield* request(`${cloud.origin}/api/v1/list_environment_variables`, authorization));
20219
+ const changed = filter(selected, (entry) => !has(existing.environmentVariables, entry.name) || existing.environmentVariables[entry.name] !== entry.value);
20220
+ const conflicts = filter(changed, (entry) => has(existing.environmentVariables, entry.name));
20221
+ if (!force && conflicts.length > 0) {
20222
+ return yield* failure2(`Existing settings differ: ${join(map3(conflicts, (entry) => entry.name), ", ")}. Use --force to overwrite them.`);
20223
+ }
20224
+ if (changed.length > 0) {
20225
+ yield* request(`${cloud.origin}/api/v1/update_environment_variables`, authorization, {
20226
+ changes: changed
20227
+ });
20228
+ }
20229
+ return yield* log(`Development settings: ${changed.length - conflicts.length} added, ${conflicts.length} updated, ${selected.length - changed.length} unchanged.`);
20230
+ }, provide3(layer11), provideService2(RequestInit, { redirect: "error" }));
20231
+
19648
20232
  // src/index.ts
19649
- var program = make30("affect").pipe(withDescription3("Environment and deployment commands for Affect applications."), withSubcommands([
19650
- make30("env", {}, () => exports().pipe(flatMap3(log))).pipe(withDescription3("Print shell exports for the selected encrypted environment.")),
20233
+ var program = make30("affect").pipe(withDescription3("Environment and native deployment commands for Affect applications."), withSubcommands([
20234
+ make30("env", {}, () => exports().pipe(flatMap3(log))).pipe(withDescription3("Print shell exports for the selected encrypted environment."), withSubcommands([
20235
+ make30("sync-dev", { force: Boolean6("force").pipe(withDefault5(false)) }, ({ force }) => syncDev(force)).pipe(withDescription3("Sync declared server settings to the verified hosted development backend; --force overwrites conflicts. No deployment."))
20236
+ ])),
19651
20237
  make30("run", {
19652
20238
  command: String10("command"),
19653
20239
  params: String10("args").pipe(variadic2())
19654
20240
  }, ({ command, params }) => run7(command, params)).pipe(withDescription3("Run a command with the selected encrypted environment.")),
19655
20241
  make30("dotenvx", { args: String10("args").pipe(variadic2()) }, ({ args }) => dotenvx(args, false)).pipe(withDescription3("Run the bundled dotenvx CLI; pass its flags after --.")),
19656
20242
  make30("setup", {}, setup).pipe(withDescription3("Initialize or retry an anonymous local Convex backend.")),
19657
- make30("deploy", { build: Boolean6("build").pipe(withDefault5(false)) }, ({ build }) => deploy(build)).pipe(withDescription3("Deploy the selected Vercel preview or production environment."))
20243
+ make30("deploy", { build: Boolean6("build").pipe(withDefault5(false)) }, ({ build }) => deploy(build)).pipe(withDescription3("Run native Convex deploy and the TanStack build for the selected Vercel target; does not publish frontend output. --build is the internal callback."))
19658
20244
  ]), run5({ version }), provide3(layer10), provideService2(LogToStderr2, true));
19659
20245
  runMain2(program);
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "affect-cli",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Affect command-line interface",
5
- "homepage": "https://github.com/ludicroushq/affect-cli#readme",
5
+ "homepage": "https://github.com/nahtnam/affect-cli#readme",
6
6
  "bugs": {
7
- "url": "https://github.com/ludicroushq/affect-cli/issues"
7
+ "url": "https://github.com/nahtnam/affect-cli/issues"
8
8
  },
9
9
  "license": "MIT",
10
10
  "repository": {
11
11
  "type": "git",
12
- "url": "git+https://github.com/ludicroushq/affect-cli.git"
12
+ "url": "git+https://github.com/nahtnam/affect-cli.git"
13
13
  },
14
14
  "bin": {
15
15
  "affect": "dist/index.js"