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.
- package/README.md +24 -11
- package/dist/index.js +671 -85
- 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
|
|
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`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
23
|
-
{
|
|
24
|
-
|
|
25
|
-
|
|
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 `
|
|
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
|
|
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(
|
|
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.
|
|
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("
|
|
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
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Affect command-line interface",
|
|
5
|
-
"homepage": "https://github.com/
|
|
5
|
+
"homepage": "https://github.com/nahtnam/affect-cli#readme",
|
|
6
6
|
"bugs": {
|
|
7
|
-
"url": "https://github.com/
|
|
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/
|
|
12
|
+
"url": "git+https://github.com/nahtnam/affect-cli.git"
|
|
13
13
|
},
|
|
14
14
|
"bin": {
|
|
15
15
|
"affect": "dist/index.js"
|