@samebase/alchemy-convex 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -0
- package/lib/index.d.ts +49 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +159 -11
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/RunRecord.ts +208 -0
- package/src/State.ts +49 -6
- package/src/index.ts +7 -0
- package/src/store/convex/state.ts +48 -2
- package/src/store/convex/version.ts +8 -4
package/README.md
CHANGED
|
@@ -119,6 +119,39 @@ the end. Rows are in the tables `resources` and `outputs` in the dashboard, unde
|
|
|
119
119
|
The store has no lock: run one deploy of a stage at a time, such as with a CI concurrency group.
|
|
120
120
|
The names of the store functions are a wire contract: never renamed or removed.
|
|
121
121
|
|
|
122
|
+
### What the output records about a run
|
|
123
|
+
|
|
124
|
+
Alchemy state holds no time, commit, branch, or run. `Convex.state()` adds them to the stage output:
|
|
125
|
+
the document that `setOutput` stores is the stack's output plus the key `samebase.run`. On GitHub
|
|
126
|
+
Actions the record comes from the run's variables and the event payload; on a laptop from git, which
|
|
127
|
+
needs a branch and an `origin` remote. It holds no secret.
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"convexUrl": "https://modest-guanaco-252.convex.cloud",
|
|
132
|
+
"samebase.run": {
|
|
133
|
+
"version": 1,
|
|
134
|
+
"repository": "samebase/base-alchemy",
|
|
135
|
+
"stackFile": "alchemy.run.ts",
|
|
136
|
+
"branch": "feature",
|
|
137
|
+
"pullRequest": 42,
|
|
138
|
+
"headCommit": "8af3c5b7932c528939cbc7aa0597b1fc36c8dc83",
|
|
139
|
+
"runUrl": "https://github.com/samebase/base-alchemy/actions/runs/12345678901/attempts/1",
|
|
140
|
+
"source": "github-actions",
|
|
141
|
+
"recordedAt": "2026-10-21T10:00:00.000Z"
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`stackFile` is the working directory of the run below the repository root plus `alchemy.run.ts`.
|
|
147
|
+
`pullRequest` and `runUrl` are `null` for a push and for a laptop, where `source` is `laptop`. A
|
|
148
|
+
value that the stack itself returns under `samebase.run` stays. A checkout that gives no record,
|
|
149
|
+
such as a tarball without git or a detached HEAD, writes its outputs without one, and the log says
|
|
150
|
+
why. Alchemy writes the output once after a full successful deploy, so a failed deploy leaves the
|
|
151
|
+
previous record, and a deploy of selected resources writes none. A reader gets every stage of a
|
|
152
|
+
stack, with its resources and output, from the store function `state:readStack({ stack })` in one
|
|
153
|
+
call.
|
|
154
|
+
|
|
122
155
|
## Resources
|
|
123
156
|
|
|
124
157
|
- `Convex.Project`: `{ team, name, deploymentRegion?, deploymentType? }` creates a project with its
|
package/lib/index.d.ts
CHANGED
|
@@ -347,6 +347,54 @@ export declare class ProjectNotFound extends ProjectNotFound_base {
|
|
|
347
347
|
get message(): string;
|
|
348
348
|
}
|
|
349
349
|
//#endregion
|
|
350
|
+
//#region src/RunRecord.d.ts
|
|
351
|
+
/** The key of the record in the stage output document. */
|
|
352
|
+
export declare const RUN_RECORD_KEY = "samebase.run";
|
|
353
|
+
/** Version 1 of the record. A reader validates `version` first. */
|
|
354
|
+
interface RunRecord {
|
|
355
|
+
readonly version: 1;
|
|
356
|
+
/** GitHub `owner/name`. */
|
|
357
|
+
readonly repository: string;
|
|
358
|
+
/** Path of the stack file from the repository root, POSIX separators, such as `apps/samebase/alchemy.run.ts`. */
|
|
359
|
+
readonly stackFile: string;
|
|
360
|
+
/** The branch that was deployed: the head branch of a pull request, else the pushed branch. */
|
|
361
|
+
readonly branch: string;
|
|
362
|
+
/** The pull request whose head was deployed, or null for a push and for a laptop. */
|
|
363
|
+
readonly pullRequest: number | null;
|
|
364
|
+
/** The commit that was deployed: the head of the pull request, else GITHUB_SHA, else git HEAD. */
|
|
365
|
+
readonly headCommit: string;
|
|
366
|
+
/** The GitHub Actions run and attempt, or null for a laptop. */
|
|
367
|
+
readonly runUrl: string | null;
|
|
368
|
+
readonly source: "github-actions" | "laptop";
|
|
369
|
+
/** ISO 8601, when the record was written. */
|
|
370
|
+
readonly recordedAt: string;
|
|
371
|
+
}
|
|
372
|
+
declare const RunRecordError_base: Schema.Class<RunRecordError, Schema.TaggedStruct<"RunRecordError", {
|
|
373
|
+
readonly reason: Schema.String;
|
|
374
|
+
}>, import("effect/Cause").YieldableError>;
|
|
375
|
+
export declare class RunRecordError extends RunRecordError_base {
|
|
376
|
+
get message(): string;
|
|
377
|
+
}
|
|
378
|
+
/** Where the run happens: the working directory of the stack and the process environment. */
|
|
379
|
+
interface RunEnvironment {
|
|
380
|
+
readonly cwd: string;
|
|
381
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* The record of this run: from the GitHub Actions variables and the event
|
|
385
|
+
* payload when GITHUB_ACTIONS is `true`, else from git in `cwd`, which needs
|
|
386
|
+
* a branch and an `origin` remote with `owner/name`. Fails when a value is
|
|
387
|
+
* missing; nothing is guessed.
|
|
388
|
+
*/
|
|
389
|
+
export declare const runRecord: (where: RunEnvironment) => Effect.Effect<RunRecord, RunRecordError>;
|
|
390
|
+
/**
|
|
391
|
+
* The output document of a stage: the stack's output plus the record under
|
|
392
|
+
* {@link RUN_RECORD_KEY}, or undefined for an output with no place for the
|
|
393
|
+
* key: a string, an array, null, or an object that is not plain, such as a
|
|
394
|
+
* Date. A value that the stack itself put under the key stays.
|
|
395
|
+
*/
|
|
396
|
+
export declare const withRunRecord: (output: unknown, record: RunRecord) => object | undefined;
|
|
397
|
+
//#endregion
|
|
350
398
|
//#region src/State.d.ts
|
|
351
399
|
/** The name of the state project in each team. */
|
|
352
400
|
export declare const STORE_PROJECT_NAME = "alchemy-state";
|
|
@@ -362,5 +410,5 @@ export declare const state: (options?: {
|
|
|
362
410
|
readonly team?: string | number;
|
|
363
411
|
}) => Layer.Layer<State, never, ChildProcessSpawner.ChildProcessSpawner>;
|
|
364
412
|
//#endregion
|
|
365
|
-
export type { DeploymentAttributes, DeploymentProps, DeploymentRegion, EnvValue, ProjectAttributes, ProjectProps, Source };
|
|
413
|
+
export type { DeploymentAttributes, DeploymentProps, DeploymentRegion, EnvValue, ProjectAttributes, ProjectProps, RunRecord, Source };
|
|
366
414
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/Credentials.ts","../src/ManagementApi.ts","../src/Providers.ts","../src/Push.ts","../src/Deployment.ts","../src/Project.ts","../src/State.ts"],"mappings":";;;;;;;;;;;;;UA6BiB;;WAEN,aAAa,SAAS;;WAEtB;;UAGM;;WAEN,aAAa,OAAO,OAAO;;WAE3B,gBAAgB,OAAO;;WAEvB,WAAW,OAAO,OAAO,SAAS;;;qBAGhC,oBAAoB;;qBAKpB;;qBAGA;;;;qBAOA,yBAAyB;;;;;;;;;qBAiCzB,kBAAiB,MAAM,MAAM;;qBA0B7B,aAAc;WAChB,cAAc,SAAS;WACvB,YAAY,SAAS;MAC5B,MAAM,MAAM;;;KC3GJ,mBAAmB,kBAAkB;KACrC,mBAAmB,kBAAkB;;UAGhC,YAAY;WAClB,OAAO;WACP;WACA,UAAU;;;;;;;;;;;;;qBAQR,uBAAuB;UAuBnB;;WAEN,UAAU,GACjB,mBACA,MAAM,QAAQ,qBAAqB,QAAQ,YAAY,QACpD,OAAO,OAAO,GAAG;;WAEb,cACP,mBACA,MAAM,QAAQ,qBAAqB,QAAQ,0BACxC,OAAO,aAAa;;WAEhB,oBAAoB,GAC3B,mBACA,6BACA,WAAW,SAAS,kBACpB,MAAM,QAAQ,qBAAqB,QAAQ,YAAY,QACpD,OAAO,OAAO,GAAG;;;;;;;;WAQb,gBACP,sCACG,OAAO,eAAe;;;;;;;;WAQlB,oBAAoB,mBAAmB,OAAO;;;;;WAK9C,YAAY,OAAO,SAAS,qBAAqB;;;qBAM/C,sBAAsB;;;;qBC1FtB,kBAAkB;;;;;;qBAOlB,YAAa,cAAa,MAAM,MAAM,iBAA2B,MAAA,MAAA,8CAAA,mCAAA,oDAAA;;;;KC+BlE,oBAAoB,SAAS;;;;;KAM7B;;WAIG;;;;;;;;WAQA;;;;;;;WAOA;;;;;;qBAkCF,0BAA0B;MAIxB;;;;;;qBAMF,0BAA0B;MAIxB;;;;;;;;;qBAMF,uBAAuB;MAMrB;;;;;;;qBAQF,2BAA2B;MAIzB;;;;;;;qBAMF,oCAAoC;MAIlC;;;;;;qBAMF,kCAAkC;MAIhC;;;qBAuCF,oBACX;EACG;EAAa;;;;UChIR;;WAEC;aAAoB;;;;;;;;WAOpB,SAAS;;WAET,MAAM,SAAS,eAAe;;KAG7B,mBACP;;WAEU;MAEV;;WAEU;;;;;WAKA;;;UAIL;WACC;WACA,MAAM;;WAEN;;WAEA;;WAEA;;WAEA;;WAEA;;UAGM,6BAA6B;;WAEnC;;WAEA;;YAGC,aAAa,8BAEvB,iBACA,6BAEA;qBAEW,8BAAU,cAAA;;;;;;;;;qBAKV,iCAAiC;MAU/B;;;;;;qBAMF,qCAAqC;MAInC;;;;;;;;;qBAMF,4BAA4B;MAS1B;;;;;KC5IH,mBAAmB;KAEnB;;WAGG;;WAEA;;WAEA,mBAAmB;;WAEnB;;;WAIA;;WAEA;;UAGE;WACN;WACA;WACA;WACA;WACA;WACA;;YAGC,UAAU,2BAA2B,cAAc,0BAA0B;qBAC5E,2BAAO,cAAA;;;;;;;qBAGP,0BAA0B;MAIxB;;;;;;;;;;;;qBASF,gCAAgC;MAS9B;;;;;;;;qBAOF,yBAAyB;MAKvB;;;;;;qBAMF,+BAA+B;MAI7B;;;;;;;qBAMF,wBAAwB;MAItB;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/Credentials.ts","../src/ManagementApi.ts","../src/Providers.ts","../src/Push.ts","../src/Deployment.ts","../src/Project.ts","../src/RunRecord.ts","../src/State.ts"],"mappings":";;;;;;;;;;;;;UA6BiB;;WAEN,aAAa,SAAS;;WAEtB;;UAGM;;WAEN,aAAa,OAAO,OAAO;;WAE3B,gBAAgB,OAAO;;WAEvB,WAAW,OAAO,OAAO,SAAS;;;qBAGhC,oBAAoB;;qBAKpB;;qBAGA;;;;qBAOA,yBAAyB;;;;;;;;;qBAiCzB,kBAAiB,MAAM,MAAM;;qBA0B7B,aAAc;WAChB,cAAc,SAAS;WACvB,YAAY,SAAS;MAC5B,MAAM,MAAM;;;KC3GJ,mBAAmB,kBAAkB;KACrC,mBAAmB,kBAAkB;;UAGhC,YAAY;WAClB,OAAO;WACP;WACA,UAAU;;;;;;;;;;;;;qBAQR,uBAAuB;UAuBnB;;WAEN,UAAU,GACjB,mBACA,MAAM,QAAQ,qBAAqB,QAAQ,YAAY,QACpD,OAAO,OAAO,GAAG;;WAEb,cACP,mBACA,MAAM,QAAQ,qBAAqB,QAAQ,0BACxC,OAAO,aAAa;;WAEhB,oBAAoB,GAC3B,mBACA,6BACA,WAAW,SAAS,kBACpB,MAAM,QAAQ,qBAAqB,QAAQ,YAAY,QACpD,OAAO,OAAO,GAAG;;;;;;;;WAQb,gBACP,sCACG,OAAO,eAAe;;;;;;;;WAQlB,oBAAoB,mBAAmB,OAAO;;;;;WAK9C,YAAY,OAAO,SAAS,qBAAqB;;;qBAM/C,sBAAsB;;;;qBC1FtB,kBAAkB;;;;;;qBAOlB,YAAa,cAAa,MAAM,MAAM,iBAA2B,MAAA,MAAA,8CAAA,mCAAA,oDAAA;;;;KC+BlE,oBAAoB,SAAS;;;;;KAM7B;;WAIG;;;;;;;;WAQA;;;;;;;WAOA;;;;;;qBAkCF,0BAA0B;MAIxB;;;;;;qBAMF,0BAA0B;MAIxB;;;;;;;;;qBAMF,uBAAuB;MAMrB;;;;;;;qBAQF,2BAA2B;MAIzB;;;;;;;qBAMF,oCAAoC;MAIlC;;;;;;qBAMF,kCAAkC;MAIhC;;;qBAuCF,oBACX;EACG;EAAa;;;;UChIR;;WAEC;aAAoB;;;;;;;;WAOpB,SAAS;;WAET,MAAM,SAAS,eAAe;;KAG7B,mBACP;;WAEU;MAEV;;WAEU;;;;;WAKA;;;UAIL;WACC;WACA,MAAM;;WAEN;;WAEA;;WAEA;;WAEA;;WAEA;;UAGM,6BAA6B;;WAEnC;;WAEA;;YAGC,aAAa,8BAEvB,iBACA,6BAEA;qBAEW,8BAAU,cAAA;;;;;;;;;qBAKV,iCAAiC;MAU/B;;;;;;qBAMF,qCAAqC;MAInC;;;;;;;;;qBAMF,4BAA4B;MAS1B;;;;;KC5IH,mBAAmB;KAEnB;;WAGG;;WAEA;;WAEA,mBAAmB;;WAEnB;;;WAIA;;WAEA;;UAGE;WACN;WACA;WACA;WACA;WACA;WACA;;YAGC,UAAU,2BAA2B,cAAc,0BAA0B;qBAC5E,2BAAO,cAAA;;;;;;;qBAGP,0BAA0B;MAIxB;;;;;;;;;;;;qBASF,gCAAgC;MAS9B;;;;;;;;qBAOF,yBAAyB;MAKvB;;;;;;qBAMF,+BAA+B;MAI7B;;;;;;;qBAMF,wBAAwB;MAItB;;;;;qBC5GF;;UAMI;WACN;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;WACA;;WAEA;;;;;qBAGE,uBAAuB;MAGrB;;;UAME;WACN;WACA,KAAK,SAAS;;;;;;;;qBAuIZ,YAAa,OAAO,mBAAiB,OAAO,OAAO,WAAW;;;;;;;qBAgB9D,gBAAiB,iBAAiB,QAAQ;;;;qBCxH1C;;;;;;;;;qBAwWA,QACX;WAAoB;MACnB,MAAM,MAAM,cAAc,oBAAoB"}
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { access, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { access, mkdtemp, readFile, realpath, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { homedir, tmpdir } from "node:os";
|
|
4
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
4
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import * as Config from "effect/Config";
|
|
6
6
|
import * as Context from "effect/Context";
|
|
7
7
|
import * as Effect from "effect/Effect";
|
|
@@ -20,11 +20,12 @@ import { Stack } from "alchemy/Stack";
|
|
|
20
20
|
import { createDeploymentClient, createManagementClient } from "@convex-dev/platform";
|
|
21
21
|
import * as Schedule from "effect/Schedule";
|
|
22
22
|
import { createHash } from "node:crypto";
|
|
23
|
-
import { stripVTControlCharacters } from "node:util";
|
|
23
|
+
import { promisify, stripVTControlCharacters } from "node:util";
|
|
24
24
|
import { hashDirectory } from "alchemy/Command/Memo";
|
|
25
25
|
import * as Clock from "effect/Clock";
|
|
26
26
|
import * as ChildProcess from "effect/process/ChildProcess";
|
|
27
27
|
import * as Stream from "effect/Stream";
|
|
28
|
+
import { execFile } from "node:child_process";
|
|
28
29
|
import { fileURLToPath } from "node:url";
|
|
29
30
|
import * as Scope from "effect/Scope";
|
|
30
31
|
import { STATE_STORE_VERSION, State, StateStoreError, encodeState, reviveState } from "alchemy/State";
|
|
@@ -1329,6 +1330,125 @@ var Providers = class extends Provider.ProviderCollection()("Convex") {};
|
|
|
1329
1330
|
*/
|
|
1330
1331
|
const providers = (credentials = fromConfig()) => Layer.effect(Providers, Provider.collection([Project, Deployment])).pipe(Layer.provide(Layer.mergeAll(ProjectProvider(), DeploymentProvider())), Layer.provide(ManagementApiLive()), Layer.provide(credentials), Layer.orDie);
|
|
1331
1332
|
//#endregion
|
|
1333
|
+
//#region src/RunRecord.ts
|
|
1334
|
+
/** The key of the record in the stage output document. */
|
|
1335
|
+
const RUN_RECORD_KEY = "samebase.run";
|
|
1336
|
+
/** The name of the stack file. Alchemy resolves `--config` against the working directory and does not expose it to a state layer, so the record assumes the default name. */
|
|
1337
|
+
const STACK_FILE_NAME = "alchemy.run.ts";
|
|
1338
|
+
var RunRecordError = class extends Schema.TaggedError()("RunRecordError", { reason: Schema.String }) {
|
|
1339
|
+
get message() {
|
|
1340
|
+
return `The run record of the stage output cannot be made: ${this.reason}`;
|
|
1341
|
+
}
|
|
1342
|
+
};
|
|
1343
|
+
/** The part of the GitHub Actions event payload that the record reads. A push event has no `pull_request`. */
|
|
1344
|
+
const EventPayload = Schema.Struct({ pull_request: Schema.optionalKey(Schema.Struct({
|
|
1345
|
+
number: Schema.Number,
|
|
1346
|
+
head: Schema.Struct({ sha: Schema.String })
|
|
1347
|
+
})) });
|
|
1348
|
+
/** `owner/name` of an https, ssh, or scp-like git URL with a host. A local path is not one. */
|
|
1349
|
+
const REMOTE_URL = /^(?:[a-z+]+:\/\/[^/]+\/|[^/@:]+@[^:/]+:)([^/]+)\/([^/]+?)(?:\.git)?\/?$/;
|
|
1350
|
+
const execFileAsync = promisify(execFile);
|
|
1351
|
+
const now = Clock.currentTimeMillis.pipe(Effect.map((millis) => new Date(millis).toISOString()));
|
|
1352
|
+
const git = (cwd, args) => Effect.tryPromise({
|
|
1353
|
+
try: async () => (await execFileAsync("git", [
|
|
1354
|
+
"-C",
|
|
1355
|
+
cwd,
|
|
1356
|
+
...args
|
|
1357
|
+
], { encoding: "utf8" })).stdout.trim(),
|
|
1358
|
+
catch: (error) => new RunRecordError({ reason: `git ${args.join(" ")} failed in ${cwd} (${error instanceof Error ? error.message.split("\n")[0] : String(error)})` })
|
|
1359
|
+
});
|
|
1360
|
+
/** The stack file from the repository root: the working directory below the git top level, plus {@link STACK_FILE_NAME}. */
|
|
1361
|
+
const stackFileOf = (cwd) => Effect.gen(function* () {
|
|
1362
|
+
const top = yield* git(cwd, ["rev-parse", "--show-toplevel"]);
|
|
1363
|
+
const [realTop, realCwd] = yield* Effect.tryPromise({
|
|
1364
|
+
try: () => Promise.all([realpath(top), realpath(cwd)]),
|
|
1365
|
+
catch: () => new RunRecordError({ reason: `${cwd} is gone` })
|
|
1366
|
+
});
|
|
1367
|
+
return [...relative(realTop, realCwd).split(sep).filter((part) => part !== ""), STACK_FILE_NAME].join("/");
|
|
1368
|
+
});
|
|
1369
|
+
const variable = (env, name) => Effect.suspend(() => {
|
|
1370
|
+
const value = env[name];
|
|
1371
|
+
return value === void 0 || value === "" ? Effect.fail(new RunRecordError({ reason: `GITHUB_ACTIONS is true, and ${name} is not set` })) : Effect.succeed(value);
|
|
1372
|
+
});
|
|
1373
|
+
const fromGitHubActions = (where) => Effect.gen(function* () {
|
|
1374
|
+
const { env } = where;
|
|
1375
|
+
const repository = yield* variable(env, "GITHUB_REPOSITORY");
|
|
1376
|
+
const refName = yield* variable(env, "GITHUB_REF_NAME");
|
|
1377
|
+
const sha = yield* variable(env, "GITHUB_SHA");
|
|
1378
|
+
const serverUrl = yield* variable(env, "GITHUB_SERVER_URL");
|
|
1379
|
+
const runId = yield* variable(env, "GITHUB_RUN_ID");
|
|
1380
|
+
const runAttempt = yield* variable(env, "GITHUB_RUN_ATTEMPT");
|
|
1381
|
+
const eventPath = env["GITHUB_EVENT_PATH"];
|
|
1382
|
+
const pullRequest = (eventPath === void 0 || eventPath === "" ? void 0 : yield* Effect.tryPromise({
|
|
1383
|
+
try: () => readFile(eventPath, "utf8"),
|
|
1384
|
+
catch: () => new RunRecordError({ reason: `GITHUB_EVENT_PATH ${eventPath} cannot be read` })
|
|
1385
|
+
}).pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(EventPayload))), Effect.mapError((error) => error instanceof RunRecordError ? error : new RunRecordError({ reason: `GITHUB_EVENT_PATH ${eventPath} is not an event payload` }))))?.pull_request;
|
|
1386
|
+
const headCommit = pullRequest?.head.sha ?? sha;
|
|
1387
|
+
const headRef = env["GITHUB_HEAD_REF"];
|
|
1388
|
+
const mergeRef = /^refs\/pull\/(\d+)\/merge$/.exec(env["GITHUB_REF"] ?? "");
|
|
1389
|
+
return {
|
|
1390
|
+
version: 1,
|
|
1391
|
+
repository,
|
|
1392
|
+
stackFile: yield* stackFileOf(where.cwd),
|
|
1393
|
+
branch: headRef === void 0 || headRef === "" ? refName : headRef,
|
|
1394
|
+
pullRequest: pullRequest?.number ?? (mergeRef === null ? null : Number(mergeRef[1])),
|
|
1395
|
+
headCommit,
|
|
1396
|
+
runUrl: `${serverUrl}/${repository}/actions/runs/${runId}/attempts/${runAttempt}`,
|
|
1397
|
+
source: "github-actions",
|
|
1398
|
+
recordedAt: yield* now
|
|
1399
|
+
};
|
|
1400
|
+
});
|
|
1401
|
+
const fromGit = (where) => Effect.gen(function* () {
|
|
1402
|
+
const { cwd } = where;
|
|
1403
|
+
const origin = yield* git(cwd, [
|
|
1404
|
+
"remote",
|
|
1405
|
+
"get-url",
|
|
1406
|
+
"origin"
|
|
1407
|
+
]);
|
|
1408
|
+
const match = REMOTE_URL.exec(origin);
|
|
1409
|
+
if (match === null) return yield* new RunRecordError({ reason: `the origin remote of ${cwd} is not a git URL with a host and owner/name: ${origin}` });
|
|
1410
|
+
const branch = yield* git(cwd, [
|
|
1411
|
+
"rev-parse",
|
|
1412
|
+
"--abbrev-ref",
|
|
1413
|
+
"HEAD"
|
|
1414
|
+
]);
|
|
1415
|
+
if (branch === "HEAD") return yield* new RunRecordError({ reason: `the checkout at ${cwd} is on no branch (detached HEAD)` });
|
|
1416
|
+
return {
|
|
1417
|
+
version: 1,
|
|
1418
|
+
repository: `${match[1]}/${match[2]}`,
|
|
1419
|
+
stackFile: yield* stackFileOf(cwd),
|
|
1420
|
+
branch,
|
|
1421
|
+
pullRequest: null,
|
|
1422
|
+
headCommit: yield* git(cwd, ["rev-parse", "HEAD"]),
|
|
1423
|
+
runUrl: null,
|
|
1424
|
+
source: "laptop",
|
|
1425
|
+
recordedAt: yield* now
|
|
1426
|
+
};
|
|
1427
|
+
});
|
|
1428
|
+
/**
|
|
1429
|
+
* The record of this run: from the GitHub Actions variables and the event
|
|
1430
|
+
* payload when GITHUB_ACTIONS is `true`, else from git in `cwd`, which needs
|
|
1431
|
+
* a branch and an `origin` remote with `owner/name`. Fails when a value is
|
|
1432
|
+
* missing; nothing is guessed.
|
|
1433
|
+
*/
|
|
1434
|
+
const runRecord = (where) => where.env["GITHUB_ACTIONS"] === "true" ? fromGitHubActions(where) : fromGit(where);
|
|
1435
|
+
/** A plain object, which a spread keeps whole. A Date, a Duration, or a Redacted value is an object too, and a spread loses it. */
|
|
1436
|
+
const isPlainObject = (value) => {
|
|
1437
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1438
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1439
|
+
return prototype === Object.prototype || prototype === null;
|
|
1440
|
+
};
|
|
1441
|
+
/**
|
|
1442
|
+
* The output document of a stage: the stack's output plus the record under
|
|
1443
|
+
* {@link RUN_RECORD_KEY}, or undefined for an output with no place for the
|
|
1444
|
+
* key: a string, an array, null, or an object that is not plain, such as a
|
|
1445
|
+
* Date. A value that the stack itself put under the key stays.
|
|
1446
|
+
*/
|
|
1447
|
+
const withRunRecord = (output, record) => isPlainObject(output) ? "samebase.run" in output ? output : {
|
|
1448
|
+
...output,
|
|
1449
|
+
[RUN_RECORD_KEY]: record
|
|
1450
|
+
} : void 0;
|
|
1451
|
+
//#endregion
|
|
1332
1452
|
//#region src/State.ts
|
|
1333
1453
|
/** The name of the state project in each team. */
|
|
1334
1454
|
const STORE_PROJECT_NAME = "alchemy-state";
|
|
@@ -1449,7 +1569,7 @@ const storeDirectory = () => join(dirname(fileURLToPath(import.meta.url)), "..",
|
|
|
1449
1569
|
*/
|
|
1450
1570
|
const pushStore = (options, deployment) => Effect.gen(function* () {
|
|
1451
1571
|
const cli = yield* installedCli(join(options.store, ".."));
|
|
1452
|
-
yield* Effect.logInfo(`Convex state: pushing the store version
|
|
1572
|
+
yield* Effect.logInfo(`Convex state: pushing the store version 2 to ${deployment.name}`);
|
|
1453
1573
|
yield* withPushKey(options.api, deployment.name, PUSH_KEY_ACTIONS, (key) => push(options.spawner, key, {
|
|
1454
1574
|
deployment: deployment.name,
|
|
1455
1575
|
url: deployment.deploymentUrl,
|
|
@@ -1512,16 +1632,31 @@ const makeConvexState = (options) => Effect.gen(function* () {
|
|
|
1512
1632
|
return next.call(path, args);
|
|
1513
1633
|
})))) };
|
|
1514
1634
|
const version = yield* storeVersion(functions);
|
|
1515
|
-
if (version === void 0 || version <
|
|
1635
|
+
if (version === void 0 || version < 2) {
|
|
1516
1636
|
if (version === void 0 && deployment.lastDeployTime) return yield* failure(`project ${STORE_PROJECT_NAME} (${project.id}) of team ${team} has code that is not the state store, and Convex.state does not push over it. Rename that project in the Convex dashboard.`);
|
|
1517
1637
|
const now = yield* storeVersion(functions);
|
|
1518
|
-
if (now === void 0 || now <
|
|
1638
|
+
if (now === void 0 || now < 2) {
|
|
1519
1639
|
yield* pushStore(options, deployment);
|
|
1520
1640
|
const pushed = yield* storeVersion(functions);
|
|
1521
|
-
if (pushed === void 0 || pushed <
|
|
1641
|
+
if (pushed === void 0 || pushed < 2) return yield* failure(`${deployment.name} answers store version ${pushed ?? "none"} after the push of version 2.`);
|
|
1522
1642
|
}
|
|
1523
|
-
} else if (version >
|
|
1643
|
+
} else if (version > 2) yield* Effect.logInfo(`Convex state: the store of team ${team} is version ${version}, newer than version 2 of this package, and it stays.`);
|
|
1524
1644
|
const remote = remoteOf(functions);
|
|
1645
|
+
/** The stack's output plus the record, when the run has one and the output has a place for it. */
|
|
1646
|
+
const documentOf = (request) => Effect.gen(function* () {
|
|
1647
|
+
const { run } = options;
|
|
1648
|
+
if (run === void 0) return request.value;
|
|
1649
|
+
const recordedAt = new Date(yield* Clock.currentTimeMillis).toISOString();
|
|
1650
|
+
const document = withRunRecord(request.value, {
|
|
1651
|
+
...run,
|
|
1652
|
+
recordedAt
|
|
1653
|
+
});
|
|
1654
|
+
if (document === void 0) {
|
|
1655
|
+
yield* Effect.logWarning(`Convex state: the output of ${request.stack}/${request.stage} is not a plain object, so it carries no run record.`);
|
|
1656
|
+
return request.value;
|
|
1657
|
+
}
|
|
1658
|
+
return document;
|
|
1659
|
+
});
|
|
1525
1660
|
return {
|
|
1526
1661
|
id: "convex",
|
|
1527
1662
|
getVersion: () => Effect.succeed(STATE_STORE_VERSION),
|
|
@@ -1534,7 +1669,15 @@ const makeConvexState = (options) => Effect.gen(function* () {
|
|
|
1534
1669
|
set: (request) => remote.set(request).pipe(Effect.as(request.value)),
|
|
1535
1670
|
delete: (request) => remote.delete(request).pipe(Effect.asVoid),
|
|
1536
1671
|
deleteStack: (request) => remote.deleteStack(request).pipe(Effect.asVoid),
|
|
1537
|
-
setOutput: (request) =>
|
|
1672
|
+
setOutput: (request) => Effect.gen(function* () {
|
|
1673
|
+
const value = yield* documentOf(request);
|
|
1674
|
+
yield* remote.setOutput({
|
|
1675
|
+
stack: request.stack,
|
|
1676
|
+
stage: request.stage,
|
|
1677
|
+
value
|
|
1678
|
+
});
|
|
1679
|
+
return value;
|
|
1680
|
+
})
|
|
1538
1681
|
};
|
|
1539
1682
|
});
|
|
1540
1683
|
/**
|
|
@@ -1551,16 +1694,21 @@ const state = (options = {}) => Layer.effect(State, Effect.gen(function* () {
|
|
|
1551
1694
|
const context = yield* Effect.context();
|
|
1552
1695
|
return yield* Effect.cached(Effect.gen(function* () {
|
|
1553
1696
|
const api = yield* ManagementApi.pipe(Effect.provide(ManagementApiLive().pipe(Layer.provide(fromConfig()))));
|
|
1697
|
+
const run = yield* runRecord({
|
|
1698
|
+
cwd: process.cwd(),
|
|
1699
|
+
env: process.env
|
|
1700
|
+
}).pipe(Effect.catch((error) => Effect.logWarning(`Convex state: the stage outputs of this run carry no record. ${error.message}`).pipe(Effect.as(void 0))));
|
|
1554
1701
|
return yield* makeConvexState({
|
|
1555
1702
|
team: options.team,
|
|
1556
1703
|
api,
|
|
1557
1704
|
spawner,
|
|
1558
1705
|
scope,
|
|
1559
|
-
store: storeDirectory()
|
|
1706
|
+
store: storeDirectory(),
|
|
1707
|
+
run
|
|
1560
1708
|
});
|
|
1561
1709
|
}).pipe(Effect.provideContext(context), Effect.orDie));
|
|
1562
1710
|
}));
|
|
1563
1711
|
//#endregion
|
|
1564
|
-
export { ACCESS_TOKEN_ENV, AmbiguousDeployment, AmbiguousProject, CodePushFailed, ConvexApiError, ConvexAppNotFound, ConvexCliNotFound, CreatedProjectNotFound, Credentials, CredentialsError, DEPLOY_KEY_ENV, Deployment, DeploymentIdentityChange, EnvironmentNeedsDeployKey, ManagementApi, ProductionDeploymentNotFound, Project, ProjectDeploymentChange, ProjectNotFound, ProjectTeamChange, Providers, PushTargetMismatch, STORE_PROJECT_NAME, VariablesOwnedBySomeoneElse, fromConfig, fromValues, parseDeployOutput, providers, state };
|
|
1712
|
+
export { ACCESS_TOKEN_ENV, AmbiguousDeployment, AmbiguousProject, CodePushFailed, ConvexApiError, ConvexAppNotFound, ConvexCliNotFound, CreatedProjectNotFound, Credentials, CredentialsError, DEPLOY_KEY_ENV, Deployment, DeploymentIdentityChange, EnvironmentNeedsDeployKey, ManagementApi, ProductionDeploymentNotFound, Project, ProjectDeploymentChange, ProjectNotFound, ProjectTeamChange, Providers, PushTargetMismatch, RUN_RECORD_KEY, RunRecordError, STORE_PROJECT_NAME, VariablesOwnedBySomeoneElse, fromConfig, fromValues, parseDeployOutput, providers, runRecord, state, withRunRecord };
|
|
1565
1713
|
|
|
1566
1714
|
//# sourceMappingURL=index.js.map
|