affect-cli 0.0.4 → 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 +10 -12
- package/dist/index.js +548 -119
- 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,11 +10,11 @@ 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. 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
|
+
- `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.
|
|
14
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.
|
|
15
15
|
- `affect dotenvx -- …` forwards arguments to the bundled native dotenvx CLI, for example `affect dotenvx -- encrypt -f env/development/.env --no-armor --no-native`.
|
|
16
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.
|
|
17
|
-
- `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.
|
|
18
18
|
|
|
19
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.
|
|
20
20
|
|
|
@@ -30,18 +30,16 @@ export default {
|
|
|
30
30
|
} satisfies EnvDefinition;
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
Use the profile's development-scoped deploy key and canonical public Convex cloud/site origins. 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`.
|
|
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
34
|
|
|
35
|
-
|
|
35
|
+
From the application root, run the TanStack development script with the selected profile:
|
|
36
36
|
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
"scripts": {
|
|
40
|
-
"dev": "affect run -- convex dev --start \"bun run dev:web\""
|
|
41
|
-
}
|
|
42
|
-
}
|
|
37
|
+
```sh
|
|
38
|
+
bunx --no-install affect run -- bun run dev
|
|
43
39
|
```
|
|
44
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
|
+
|
|
45
43
|
## Development and releases
|
|
46
44
|
|
|
47
45
|
```sh
|
|
@@ -52,4 +50,4 @@ bun run changeset version
|
|
|
52
50
|
bun run release
|
|
53
51
|
```
|
|
54
52
|
|
|
55
|
-
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
|
@@ -7572,6 +7572,28 @@ function String3() {
|
|
|
7572
7572
|
function Number4() {
|
|
7573
7573
|
return transform(globalThis.Number);
|
|
7574
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
|
+
}
|
|
7575
7597
|
function encodeBase64() {
|
|
7576
7598
|
return transform(encode);
|
|
7577
7599
|
}
|
|
@@ -7581,6 +7603,15 @@ function decodeBase64() {
|
|
|
7581
7603
|
}, input, options)));
|
|
7582
7604
|
}
|
|
7583
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
|
+
|
|
7584
7615
|
// node_modules/effect/dist/SchemaTransformation.js
|
|
7585
7616
|
var TypeId18 = "~effect/SchemaTransformation/Transformation";
|
|
7586
7617
|
var Transformation = class extends Class {
|
|
@@ -7678,6 +7709,12 @@ var urlFromString = /* @__PURE__ */ transformEffect2({
|
|
|
7678
7709
|
encode: (url) => succeed6(url.href)
|
|
7679
7710
|
});
|
|
7680
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
|
+
}
|
|
7681
7718
|
|
|
7682
7719
|
// node_modules/effect/dist/SchemaAST.js
|
|
7683
7720
|
function makeGuard(tag) {
|
|
@@ -12369,6 +12406,7 @@ function Literal2(literal) {
|
|
|
12369
12406
|
return out;
|
|
12370
12407
|
}
|
|
12371
12408
|
var Unknown2 = /* @__PURE__ */ make25(unknown);
|
|
12409
|
+
var Null2 = /* @__PURE__ */ make25(null_);
|
|
12372
12410
|
var Undefined2 = /* @__PURE__ */ make25(undefined_2);
|
|
12373
12411
|
var String5 = /* @__PURE__ */ make25(string2);
|
|
12374
12412
|
var Number6 = /* @__PURE__ */ make25(number2);
|
|
@@ -12422,6 +12460,7 @@ function Literals(literals) {
|
|
|
12422
12460
|
}
|
|
12423
12461
|
});
|
|
12424
12462
|
}
|
|
12463
|
+
var NullOr = /* @__PURE__ */ lambda((self) => Union2([self, Null2]));
|
|
12425
12464
|
var UndefinedOr = /* @__PURE__ */ lambda((self) => Union2([self, Undefined2]));
|
|
12426
12465
|
function decodeTo2(to, transformation) {
|
|
12427
12466
|
return (from) => {
|
|
@@ -12668,6 +12707,12 @@ var URL2 = /* @__PURE__ */ instanceOf(globalThis.URL, {
|
|
|
12668
12707
|
toCodecJson: () => link3()(URLString, urlFromString)
|
|
12669
12708
|
});
|
|
12670
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
|
+
}
|
|
12671
12716
|
var File = /* @__PURE__ */ instanceOf(globalThis.File, {
|
|
12672
12717
|
representation: {
|
|
12673
12718
|
id: "effect/schema/File",
|
|
@@ -18573,7 +18618,7 @@ var runWith2 = (command, config) => {
|
|
|
18573
18618
|
}) => andThen2(showUserError(error, userMessage), fail7(error))), catchFilter2((e) => isQuitError(e) ? succeed2(e) : fail2(e), (_) => interrupt2));
|
|
18574
18619
|
};
|
|
18575
18620
|
// package.json
|
|
18576
|
-
var version = "0.0.
|
|
18621
|
+
var version = "0.0.5";
|
|
18577
18622
|
// node_modules/effect/dist/http/HttpBody.js
|
|
18578
18623
|
var TypeId35 = "~effect/http/HttpBody";
|
|
18579
18624
|
var HttpBodyErrorTypeId = "~effect/http/HttpBody/HttpBodyError";
|
|
@@ -18935,14 +18980,6 @@ var getWebSource = (self) => self[WebSourceSymbol]?.();
|
|
|
18935
18980
|
// node_modules/effect/dist/http/HttpClientResponse.js
|
|
18936
18981
|
var TypeId39 = "~effect/http/HttpClientResponse";
|
|
18937
18982
|
var fromWeb = (request, source) => new WebHttpClientResponse(request, source);
|
|
18938
|
-
var filterStatusOk = (self) => self.status >= 200 && self.status < 300 ? succeed6(self) : fail7(new HttpClientError({
|
|
18939
|
-
reason: new StatusCodeError({
|
|
18940
|
-
response: self,
|
|
18941
|
-
request: self.request,
|
|
18942
|
-
description: "non 2xx status code"
|
|
18943
|
-
})
|
|
18944
|
-
}));
|
|
18945
|
-
|
|
18946
18983
|
class WebHttpClientResponse extends Class2 {
|
|
18947
18984
|
[TypeId38];
|
|
18948
18985
|
[TypeId39];
|
|
@@ -19128,7 +19165,6 @@ var addHeaderAttributes = (span, phase, headers, filter, redactedNames) => {
|
|
|
19128
19165
|
var TypeId40 = "~effect/http/HttpClient";
|
|
19129
19166
|
var HttpClient = /* @__PURE__ */ Service("effect/HttpClient");
|
|
19130
19167
|
var transformResponse = /* @__PURE__ */ dual(2, (self, f) => makeWith2((request) => f(self.postprocess(request)), self.preprocess));
|
|
19131
|
-
var filterStatusOk2 = /* @__PURE__ */ transformResponse(/* @__PURE__ */ flatMap3(filterStatusOk));
|
|
19132
19168
|
var makeWith2 = (postprocess, preprocess) => {
|
|
19133
19169
|
const self = Object.create(Proto14);
|
|
19134
19170
|
self.preprocess = preprocess;
|
|
@@ -19533,6 +19569,469 @@ var exports = fn2("Environment.exports")(function* exports() {
|
|
|
19533
19569
|
`);
|
|
19534
19570
|
});
|
|
19535
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
|
+
|
|
19536
20035
|
// src/process.ts
|
|
19537
20036
|
var run6 = fn2("Process.run")(function* run(command) {
|
|
19538
20037
|
const child = yield* command;
|
|
@@ -19553,76 +20052,6 @@ var convex = (args, env, input) => run6(make18("bunx", ["--no-install", "convex"
|
|
|
19553
20052
|
stdout: "inherit"
|
|
19554
20053
|
}));
|
|
19555
20054
|
|
|
19556
|
-
// src/deploy.ts
|
|
19557
|
-
var convexManagedEnvironmentNames = [
|
|
19558
|
-
"CONVEX_DEPLOY_KEY",
|
|
19559
|
-
"CONVEX_DEPLOYMENT_TOKEN",
|
|
19560
|
-
"CONVEX_DEPLOYMENT",
|
|
19561
|
-
"CONVEX_SELF_HOSTED_URL",
|
|
19562
|
-
"CONVEX_SELF_HOSTED_ADMIN_KEY",
|
|
19563
|
-
"CONVEX_URL",
|
|
19564
|
-
"PUBLIC_CONVEX_URL",
|
|
19565
|
-
"NEXT_PUBLIC_CONVEX_URL",
|
|
19566
|
-
"VITE_CONVEX_URL",
|
|
19567
|
-
"REACT_APP_CONVEX_URL",
|
|
19568
|
-
"EXPO_PUBLIC_CONVEX_URL",
|
|
19569
|
-
"CONVEX_SITE_URL",
|
|
19570
|
-
"PUBLIC_CONVEX_SITE_URL",
|
|
19571
|
-
"NEXT_PUBLIC_CONVEX_SITE_URL",
|
|
19572
|
-
"VITE_CONVEX_SITE_URL",
|
|
19573
|
-
"REACT_APP_CONVEX_SITE_URL",
|
|
19574
|
-
"EXPO_PUBLIC_CONVEX_SITE_URL"
|
|
19575
|
-
];
|
|
19576
|
-
var deploy = fn2("Deploy.run")(function* deploy(build) {
|
|
19577
|
-
const environment = yield* load();
|
|
19578
|
-
const { target, values, file } = environment;
|
|
19579
|
-
if (target !== "preview" && target !== "production") {
|
|
19580
|
-
return yield* new UserError({
|
|
19581
|
-
cause: "Deploy requires VERCEL_ENV=preview or production."
|
|
19582
|
-
});
|
|
19583
|
-
}
|
|
19584
|
-
const branch = yield* String6("VERCEL_GIT_COMMIT_REF").pipe(withDefault3(""));
|
|
19585
|
-
if (target === "preview" && !branch) {
|
|
19586
|
-
return yield* new UserError({ cause: "Preview deployment requires a branch name." });
|
|
19587
|
-
}
|
|
19588
|
-
const prefix = target === "preview" ? "preview:" : "prod:";
|
|
19589
|
-
const deployKey = values["CONVEX_DEPLOY_KEY"] ?? "";
|
|
19590
|
-
if (!deployKey.startsWith(prefix)) {
|
|
19591
|
-
return yield* new UserError({
|
|
19592
|
-
cause: "Encrypted project file must contain a Convex deploy key matching VERCEL_ENV."
|
|
19593
|
-
});
|
|
19594
|
-
}
|
|
19595
|
-
yield* error(`Convex target: ${target}${target === "preview" ? ` (${branch})` : ""}; selected by env/${target}/.env.`);
|
|
19596
|
-
if (build) {
|
|
19597
|
-
const cloud = yield* NonEmptyString2("NEXT_PUBLIC_CONVEX_URL");
|
|
19598
|
-
const site = yield* NonEmptyString2("NEXT_PUBLIC_CONVEX_SITE_URL");
|
|
19599
|
-
const changes = map3(filter(toEntries(values), ([name]) => !contains(name)(convexManagedEnvironmentNames)), ([name, value]) => ({ name, value }));
|
|
19600
|
-
if (changes.length > 0) {
|
|
19601
|
-
yield* gen2(function* syncEnvironmentVariables() {
|
|
19602
|
-
const client = yield* HttpClient;
|
|
19603
|
-
const request = yield* bodyJson(setHeader(post(`${cloud}/api/v1/update_environment_variables`), "Authorization", `Convex ${deployKey}`), { changes });
|
|
19604
|
-
yield* filterStatusOk2(client).execute(request).pipe(asVoid2);
|
|
19605
|
-
}).pipe(provide3(layer11), mapError2(() => new UserError({
|
|
19606
|
-
cause: "Could not synchronize Convex environment variables."
|
|
19607
|
-
})));
|
|
19608
|
-
}
|
|
19609
|
-
return yield* run6(make18("bun", ["run", "build"], {
|
|
19610
|
-
env: { ...values, NEXT_PUBLIC_CONVEX_SITE_URL: site, NEXT_PUBLIC_CONVEX_URL: cloud },
|
|
19611
|
-
extendEnv: true,
|
|
19612
|
-
stderr: "inherit",
|
|
19613
|
-
stdout: "inherit"
|
|
19614
|
-
})).pipe(asVoid2);
|
|
19615
|
-
}
|
|
19616
|
-
const fs = yield* FileSystem;
|
|
19617
|
-
yield* fs.copyFile(file, "apps/web/.env.production");
|
|
19618
|
-
return yield* convex([
|
|
19619
|
-
"deploy",
|
|
19620
|
-
...target === "preview" ? ["--preview-name", branch] : [],
|
|
19621
|
-
"--cmd",
|
|
19622
|
-
"bunx --no-install affect deploy --build"
|
|
19623
|
-
], values).pipe(asVoid2);
|
|
19624
|
-
});
|
|
19625
|
-
|
|
19626
20055
|
// src/dotenv.ts
|
|
19627
20056
|
var dotenvx = fn2("Dotenvx.run")(function* dotenvx(args, capture) {
|
|
19628
20057
|
const path = yield* Path;
|
|
@@ -19701,30 +20130,30 @@ var setup = fn2("Setup.run")(function* setup() {
|
|
|
19701
20130
|
});
|
|
19702
20131
|
|
|
19703
20132
|
// src/sync-dev.ts
|
|
19704
|
-
var
|
|
19705
|
-
var reserved = /^(?:CONVEX_|DOTENV_|
|
|
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;
|
|
19706
20135
|
var SettingName = String5.check(makeFilter2((name) => !reserved.test(name)));
|
|
19707
|
-
var
|
|
20136
|
+
var Declaration3 = Record(String5, Struct({
|
|
19708
20137
|
isConvexValidator: Literal2(true),
|
|
19709
20138
|
isOptional: Literals(["required", "optional"])
|
|
19710
20139
|
}));
|
|
19711
|
-
var
|
|
20140
|
+
var Origin2 = URLFromString.check(makeFilter2((url) => url.protocol === "https:" && url.username === "" && url.password === "" && url.pathname === "/" && url.search === "" && url.hash === ""));
|
|
19712
20141
|
var Profile = Struct({
|
|
19713
20142
|
CONVEX_DEPLOY_KEY: String5.check(isPattern2(/^dev:[a-z0-9-]+\|.+$/u)),
|
|
19714
|
-
|
|
19715
|
-
|
|
20143
|
+
VITE_CONVEX_SITE_URL: Origin2,
|
|
20144
|
+
VITE_CONVEX_URL: Origin2
|
|
19716
20145
|
});
|
|
19717
|
-
var
|
|
19718
|
-
var
|
|
19719
|
-
var
|
|
19720
|
-
var
|
|
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({
|
|
19721
20150
|
deploymentType: Literal2("dev"),
|
|
19722
|
-
id:
|
|
20151
|
+
id: Id2,
|
|
19723
20152
|
kind: Literal2("cloud"),
|
|
19724
|
-
projectId:
|
|
19725
|
-
teamId:
|
|
20153
|
+
projectId: Id2,
|
|
20154
|
+
teamId: Id2
|
|
19726
20155
|
});
|
|
19727
|
-
var
|
|
20156
|
+
var CanonicalUrls2 = Struct({ convexCloudUrl: Origin2, convexSiteUrl: Origin2 });
|
|
19728
20157
|
var Existing = Struct({
|
|
19729
20158
|
environmentVariables: Record(String5, String5)
|
|
19730
20159
|
});
|
|
@@ -19734,35 +20163,35 @@ var request = fn2("DevelopmentEnv.request")(function* request(url, authorization
|
|
|
19734
20163
|
const outgoing = body === undefined ? base : yield* bodyJson(base, body);
|
|
19735
20164
|
const response = yield* client.execute(outgoing);
|
|
19736
20165
|
if (response.status !== 200) {
|
|
19737
|
-
return yield*
|
|
20166
|
+
return yield* failure2("Development environment request failed.");
|
|
19738
20167
|
}
|
|
19739
20168
|
return response;
|
|
19740
|
-
}, mapError2(() =>
|
|
19741
|
-
var
|
|
20169
|
+
}, mapError2(() => failure2("Development environment request failed.")));
|
|
20170
|
+
var json3 = (schema, response) => response.json.pipe(flatMap3(decodeUnknownEffect2(schema)), mapError2(() => failure2("Invalid development deployment metadata.")));
|
|
19742
20171
|
var syncDev = fn2("DevelopmentEnv.sync")(function* syncDev(force) {
|
|
19743
20172
|
const path = yield* Path;
|
|
19744
20173
|
const fs = yield* FileSystem;
|
|
19745
20174
|
const file = path.resolve("env", "development", ".env");
|
|
19746
20175
|
if (!(yield* fs.exists(file))) {
|
|
19747
|
-
return yield*
|
|
20176
|
+
return yield* failure2("Missing env/development/.env.");
|
|
19748
20177
|
}
|
|
19749
|
-
yield* NonEmptyString2("DOTENV_PRIVATE_KEY").pipe(mapError2(() =>
|
|
20178
|
+
yield* NonEmptyString2("DOTENV_PRIVATE_KEY").pipe(mapError2(() => failure2("Set the matching development DOTENV_PRIVATE_KEY.")));
|
|
19750
20179
|
const values = yield* read2(file);
|
|
19751
|
-
const profile = yield* decodeUnknownEffect2(Profile)(values).pipe(mapError2(() =>
|
|
20180
|
+
const profile = yield* decodeUnknownEffect2(Profile)(values).pipe(mapError2(() => failure2("Development profile requires a dev-scoped key and HTTPS Convex origins.")));
|
|
19752
20181
|
const name = profile.CONVEX_DEPLOY_KEY.split("|")[0]?.slice(4);
|
|
19753
20182
|
if ((values["CONVEX_DEPLOYMENT"] ?? "") !== "" && values["CONVEX_DEPLOYMENT"] !== `dev:${name}`) {
|
|
19754
|
-
return yield*
|
|
20183
|
+
return yield* failure2("CONVEX_DEPLOYMENT does not match the development key.");
|
|
19755
20184
|
}
|
|
19756
20185
|
const declarationModule = yield* try_3({
|
|
19757
|
-
catch: () =>
|
|
19758
|
-
try: () => decodeUnknownEffect2(Struct({ default:
|
|
19759
|
-
}).pipe(flatten3, mapError2(() =>
|
|
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.")));
|
|
19760
20189
|
const declaration = declarationModule.default;
|
|
19761
|
-
yield* decodeEffect2(ArraySchema(SettingName))(keys(declaration)).pipe(mapError2(() =>
|
|
20190
|
+
yield* decodeEffect2(ArraySchema(SettingName))(keys(declaration)).pipe(mapError2(() => failure2("Server declaration contains a credential, control or client-public name.")));
|
|
19762
20191
|
const entries = toEntries(declaration);
|
|
19763
20192
|
const missing = filter(entries, ([key, validator]) => validator.isOptional === "required" && !has(values, key));
|
|
19764
20193
|
if (missing.length > 0) {
|
|
19765
|
-
return yield*
|
|
20194
|
+
return yield* failure2(`Missing required server settings: ${join(map3(missing, ([key]) => key), ", ")}.`);
|
|
19766
20195
|
}
|
|
19767
20196
|
const selected = filterMap(entries, ([key]) => {
|
|
19768
20197
|
const value = has(values, key) ? values[key] : undefined;
|
|
@@ -19771,26 +20200,26 @@ var syncDev = fn2("DevelopmentEnv.sync")(function* syncDev(force) {
|
|
|
19771
20200
|
const key = profile.CONVEX_DEPLOY_KEY;
|
|
19772
20201
|
const bearer = `Bearer ${key}`;
|
|
19773
20202
|
const lookup = { deployKey: key };
|
|
19774
|
-
const cloud = yield*
|
|
19775
|
-
if (cloud.origin !== profile.
|
|
19776
|
-
return yield*
|
|
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.");
|
|
19777
20206
|
}
|
|
19778
|
-
const identity = yield*
|
|
20207
|
+
const identity = yield* json3(Identity2, yield* request("https://api.convex.dev/api/deployment/team_and_project_for_key", bearer, lookup));
|
|
19779
20208
|
const authorization = `Convex ${key}`;
|
|
19780
|
-
const deployment = yield*
|
|
20209
|
+
const deployment = yield* json3(Deployment2, yield* request(`${cloud.origin}/api/v1/deployment_info`, authorization));
|
|
19781
20210
|
if (deployment.teamId !== identity.teamId || deployment.projectId !== identity.projectId) {
|
|
19782
|
-
return yield*
|
|
20211
|
+
return yield* failure2("Development deployment identity does not match the key.");
|
|
19783
20212
|
}
|
|
19784
|
-
const urls = yield*
|
|
19785
|
-
if (urls.convexCloudUrl.origin !== cloud.origin || urls.convexSiteUrl.origin !== profile.
|
|
19786
|
-
return yield*
|
|
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.");
|
|
19787
20216
|
}
|
|
19788
20217
|
yield* error(`Convex target: ${identity.team}/${identity.project}; development ${name} (ID ${deployment.id}).`);
|
|
19789
|
-
const existing = yield*
|
|
20218
|
+
const existing = yield* json3(Existing, yield* request(`${cloud.origin}/api/v1/list_environment_variables`, authorization));
|
|
19790
20219
|
const changed = filter(selected, (entry) => !has(existing.environmentVariables, entry.name) || existing.environmentVariables[entry.name] !== entry.value);
|
|
19791
20220
|
const conflicts = filter(changed, (entry) => has(existing.environmentVariables, entry.name));
|
|
19792
20221
|
if (!force && conflicts.length > 0) {
|
|
19793
|
-
return yield*
|
|
20222
|
+
return yield* failure2(`Existing settings differ: ${join(map3(conflicts, (entry) => entry.name), ", ")}. Use --force to overwrite them.`);
|
|
19794
20223
|
}
|
|
19795
20224
|
if (changed.length > 0) {
|
|
19796
20225
|
yield* request(`${cloud.origin}/api/v1/update_environment_variables`, authorization, {
|
|
@@ -19801,7 +20230,7 @@ var syncDev = fn2("DevelopmentEnv.sync")(function* syncDev(force) {
|
|
|
19801
20230
|
}, provide3(layer11), provideService2(RequestInit, { redirect: "error" }));
|
|
19802
20231
|
|
|
19803
20232
|
// src/index.ts
|
|
19804
|
-
var program = make30("affect").pipe(withDescription3("Environment and deployment commands for Affect applications."), withSubcommands([
|
|
20233
|
+
var program = make30("affect").pipe(withDescription3("Environment and native deployment commands for Affect applications."), withSubcommands([
|
|
19805
20234
|
make30("env", {}, () => exports().pipe(flatMap3(log))).pipe(withDescription3("Print shell exports for the selected encrypted environment."), withSubcommands([
|
|
19806
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."))
|
|
19807
20236
|
])),
|
|
@@ -19811,6 +20240,6 @@ var program = make30("affect").pipe(withDescription3("Environment and deployment
|
|
|
19811
20240
|
}, ({ command, params }) => run7(command, params)).pipe(withDescription3("Run a command with the selected encrypted environment.")),
|
|
19812
20241
|
make30("dotenvx", { args: String10("args").pipe(variadic2()) }, ({ args }) => dotenvx(args, false)).pipe(withDescription3("Run the bundled dotenvx CLI; pass its flags after --.")),
|
|
19813
20242
|
make30("setup", {}, setup).pipe(withDescription3("Initialize or retry an anonymous local Convex backend.")),
|
|
19814
|
-
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."))
|
|
19815
20244
|
]), run5({ version }), provide3(layer10), provideService2(LogToStderr2, true));
|
|
19816
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"
|