@samebase/alchemy-convex 0.3.0 → 0.4.1

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/src/index.ts CHANGED
@@ -1,45 +1,12 @@
1
- // @samebase/alchemy-convex: Convex resources for Alchemy v2.
2
- export {
3
- Code,
4
- type CodeAttributes,
5
- type CodeDeployment,
6
- type CodeProps,
7
- CodePushFailed,
8
- ConvexCliNotFound,
9
- DeployKeyMismatch,
10
- EnvironmentNeedsDeployKey,
11
- parseDeployOutput,
12
- PushTargetMismatch,
13
- } from "./Code.ts";
1
+ // @samebase/alchemy-convex: the Convex plane for Alchemy v2.
14
2
  export {
15
3
  ACCESS_TOKEN_ENV,
16
4
  Credentials,
17
5
  CredentialsError,
6
+ DEPLOY_KEY_ENV,
18
7
  fromConfig,
19
- fromToken,
8
+ fromValues,
20
9
  } from "./Credentials.ts";
21
- export {
22
- DefaultEnvironmentVariable,
23
- type DefaultEnvironmentVariableAttributes,
24
- type DefaultEnvironmentVariableProps,
25
- type DeploymentType,
26
- } from "./DefaultEnvironmentVariable.ts";
27
- export {
28
- Deploy,
29
- DeployArgumentError,
30
- type DeployAttributes,
31
- deployArgs,
32
- DeployOutputError,
33
- type DeployProps,
34
- } from "./Deploy.ts";
35
- export {
36
- DeployKey,
37
- type DeployKeyAction,
38
- type DeployKeyAttributes,
39
- DeployKeyIsCredential,
40
- type DeployKeyProps,
41
- DeployKeyRecoveryRequired,
42
- } from "./DeployKey.ts";
43
10
  export {
44
11
  AmbiguousDeployment,
45
12
  Deployment,
@@ -48,18 +15,7 @@ export {
48
15
  type DeploymentProps,
49
16
  ProductionDeploymentNotFound,
50
17
  } from "./Deployment.ts";
51
- export {
52
- EnvironmentVariable,
53
- type EnvironmentVariableAttributes,
54
- type EnvironmentVariableProps,
55
- VariableIdentityChange,
56
- } from "./EnvironmentVariable.ts";
57
18
  export { ConvexApiError, ManagementApi } from "./ManagementApi.ts";
58
- export {
59
- PreviewDeployKey,
60
- type PreviewDeployKeyAttributes,
61
- type PreviewDeployKeyProps,
62
- } from "./PreviewDeployKey.ts";
63
19
  export {
64
20
  AmbiguousProject,
65
21
  CreatedProjectNotFound,
@@ -67,7 +23,19 @@ export {
67
23
  Project,
68
24
  ProjectDeploymentChange,
69
25
  type ProjectAttributes,
26
+ ProjectNotFound,
70
27
  type ProjectProps,
71
28
  ProjectTeamChange,
72
29
  } from "./Project.ts";
73
30
  export { Providers, providers } from "./Providers.ts";
31
+ export {
32
+ CodePushFailed,
33
+ ConvexAppNotFound,
34
+ ConvexCliNotFound,
35
+ EnvironmentNeedsDeployKey,
36
+ type EnvValue,
37
+ parseDeployOutput,
38
+ PushTargetMismatch,
39
+ type Source,
40
+ VariablesOwnedBySomeoneElse,
41
+ } from "./Push.ts";
package/src/Code.ts DELETED
@@ -1,463 +0,0 @@
1
- // Convex.Code: pushes the functions of a Convex app to one deployment with
2
- // the Convex CLI that the app has installed, and exposes the URLs that a
3
- // frontend or a Worker needs.
4
- //
5
- // - Every apply pushes. The resource does not hash the sources: a preview
6
- // deployment can expire, a push from outside Alchemy can change the
7
- // deployment, and a hash of the app directory misses changes in workspace
8
- // packages. The Convex CLI uploads only the modules that changed.
9
- // - The CLI is the `convex` package that `cwd` resolves, run with the Node.js
10
- // that runs Alchemy. Nothing is downloaded. The `command` prop replaces it.
11
- // - The deploy key reaches the CLI only through an env file (`--env-file`)
12
- // with mode 0600 in a new temporary directory. The directory is removed
13
- // after the run, on success, on failure, and on interruption. It is also the
14
- // home directory of the CLI, so the CLI cannot read the Convex login of this
15
- // machine and pushes with the deploy key only. Variables named `CONVEX_*`
16
- // are not passed on to the CLI.
17
- // - The CLI output stays out of the logs. A failure shows its last lines with
18
- // the deploy key removed.
19
- // - Before the push, the key must fit the deployment: a deploy key of that
20
- // deployment, or a preview deploy key for a Convex.Deployment preview. After
21
- // the push, the deployment that the CLI names must be that deployment.
22
- // - `env` variables are set on the deployment before the push with the deploy
23
- // key, through the deployment API like Convex.EnvironmentVariable. A
24
- // variable that exists with another value and that this resource did not
25
- // set is someone else's: it is overwritten only with --adopt. A name that
26
- // leaves `env` is removed from the deployment.
27
- // - Delete does nothing. The functions and the variables stay on the
28
- // deployment: the deployed code can need them.
29
- //
30
- // Patterns from Confect `packages/alchemy/src/Code.ts`,
31
- // `internal/CodeDeployment.ts`, and `internal/CommandRunner.ts`
32
- // (https://github.com/rjdellecese/confect, ISC license).
33
- import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
34
- import { createRequire } from "node:module";
35
- import { tmpdir } from "node:os";
36
- import { dirname, join, resolve } from "node:path";
37
- import { stripVTControlCharacters } from "node:util";
38
- import { Resource } from "alchemy";
39
- import { OwnedBySomeoneElse } from "alchemy/AdoptPolicy";
40
- import * as Provider from "alchemy/Provider";
41
- import * as Clock from "effect/Clock";
42
- import * as Effect from "effect/Effect";
43
- import * as ChildProcess from "effect/process/ChildProcess";
44
- import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
45
- import * as Redacted from "effect/Redacted";
46
- import * as Schema from "effect/Schema";
47
- import * as Stream from "effect/Stream";
48
- import { shouldAdopt } from "./Adoption.ts";
49
- import { type DeploymentAttributes, siteUrlOf } from "./Deployment.ts";
50
- import { listVariables, writeVariables } from "./EnvironmentVariable.ts";
51
- import { retryIdempotentWrite } from "./ManagementApi.ts";
52
- import type { Providers } from "./Providers.ts";
53
-
54
- /** The attributes of a Convex.Deployment that a push needs. Pass the resource itself. */
55
- export type CodeDeployment = Pick<DeploymentAttributes, "name" | "type" | "url" | "previewName">;
56
-
57
- export interface CodeProps {
58
- /**
59
- * The deployment that receives the push: a Convex.Deployment resource, or a
60
- * deployment name such as `happy-animal-123`. A preview deploy key needs the
61
- * resource. For `env` on a deployment outside US East, pass the resource.
62
- */
63
- readonly deployment: string | CodeDeployment;
64
- /**
65
- * A deploy key of `deployment` (Convex.DeployKey), or, for a preview
66
- * deployment, a preview deploy key of its project (Convex.PreviewDeployKey).
67
- */
68
- readonly deployKey: Redacted.Redacted<string>;
69
- /** The Convex app directory: the one with `convex/` and the `convex` package installed. */
70
- readonly cwd: string;
71
- /**
72
- * The program and first arguments that run the Convex CLI, such as
73
- * `["pnpm", "exec", "convex"]`. Code adds `deploy` and its flags. By
74
- * default, the `convex` package that `cwd` resolves, run with this Node.js.
75
- */
76
- readonly command?: readonly [string, ...string[]];
77
- /** Environment variables to set on the deployment before the push. Needs a deploy key of the deployment. */
78
- readonly env?: Readonly<Record<string, string | Redacted.Redacted<string>>>;
79
- /** `convex deploy --typecheck`. The CLI default is `try`. */
80
- readonly typecheck?: "enable" | "try" | "disable";
81
- /** `convex deploy --codegen`. The CLI default is `enable`. */
82
- readonly codegen?: "enable" | "disable";
83
- }
84
-
85
- export interface CodeAttributes {
86
- /** The deployment that received the push, such as `happy-animal-123`. */
87
- readonly deploymentName: string;
88
- /** Client URL, such as `https://happy-animal-123.convex.cloud`. */
89
- readonly url: string;
90
- /** HTTP actions URL, such as `https://happy-animal-123.convex.site`. */
91
- readonly siteUrl: string;
92
- /** When the push finished, in milliseconds since the Unix epoch. */
93
- readonly deployedAt: number;
94
- /** The names of the `env` variables that this resource set on the deployment, sorted. */
95
- readonly envNames: readonly string[];
96
- }
97
-
98
- export type Code = Resource<"Convex.Code", CodeProps, CodeAttributes, never, Providers>;
99
- export const Code = Resource<Code>("Convex.Code");
100
-
101
- /**
102
- * The CLI prints this line with `logFinishedStep` after the push succeeds, to
103
- * stderr (convex 1.46 `cli/lib/deploy2.js` for existing deployments,
104
- * `cli/deploy.js` for preview deployments). The host is
105
- * `<deployment>.convex.cloud` or `<deployment>.<region>.convex.cloud`.
106
- */
107
- const DEPLOYED_LINE =
108
- /Deployed Convex functions to (https:\/\/([a-z0-9]+(?:-[a-z0-9]+)*)(?:\.[a-z0-9]+(?:-[a-z0-9]+)*)?\.convex\.cloud)\/?(?=\s|$)/m;
109
-
110
- /** Reads the deployment URL from `convex deploy` output. ANSI escape codes are removed first. */
111
- export const parseDeployOutput = (
112
- text: string,
113
- ): { url: string; deploymentName: string } | undefined => {
114
- const match = DEPLOYED_LINE.exec(stripVTControlCharacters(text));
115
- const url = match?.[1];
116
- const deploymentName = match?.[2];
117
- return url === undefined || deploymentName === undefined ? undefined : { url, deploymentName };
118
- };
119
-
120
- /**
121
- * What a deploy key targets, from its prefix: `<type>:<deployment>|<secret>`
122
- * (or `<deployment>|<secret>` in old keys) for one deployment,
123
- * `preview:<team>:<project>|<secret>` for preview deployments of a project,
124
- * `project:<team>:<project>|<secret>` for the project's production
125
- * deployment. Same rules as convex 1.46 `cli/lib/deployment.js`.
126
- */
127
- export const keyTargetOf = (
128
- key: string,
129
- ):
130
- | { readonly kind: "deployment"; readonly deployment: string }
131
- | { readonly kind: "preview" | "project" | "unknown" } => {
132
- const bar = key.indexOf("|");
133
- // A key in the env file must be one unquoted dotenv value.
134
- if (bar <= 0 || /[\s#'"`]/.test(key)) return { kind: "unknown" };
135
- const parts = key.slice(0, bar).split(":");
136
- const [first, second] = parts;
137
- if (parts.length === 3 && first === "preview") return { kind: "preview" };
138
- if (parts.length === 3 && first === "project") return { kind: "project" };
139
- if (parts.length === 2 && second) return { kind: "deployment", deployment: second };
140
- if (parts.length === 1 && first) return { kind: "deployment", deployment: first };
141
- return { kind: "unknown" };
142
- };
143
-
144
- /** The deploy key does not fit the deployment. Checked before the push. */
145
- export class DeployKeyMismatch extends Schema.TaggedError<DeployKeyMismatch>()(
146
- "DeployKeyMismatch",
147
- {
148
- /** The deployment that should receive the push. */
149
- deployment: Schema.String,
150
- /** What the key targets. */
151
- key: Schema.Literals(["deployment", "preview", "project", "unknown"]),
152
- /** For a deployment key: the deployment in the key prefix. Never the secret. */
153
- keyDeployment: Schema.UndefinedOr(Schema.String),
154
- },
155
- ) {
156
- override get message() {
157
- switch (this.key) {
158
- case "deployment":
159
- return `The deploy key is for deployment ${this.keyDeployment}, not for ${this.deployment}. Pass a deploy key of ${this.deployment}, such as Convex.DeployKey with deployment ${this.deployment}.`;
160
- case "preview":
161
- return `A preview deploy key pushes to a preview deployment by its preview name, and ${this.deployment} is not a Convex.Deployment of type "preview". Pass the Convex.Deployment preview resource as deployment, or a deploy key of ${this.deployment}.`;
162
- case "project":
163
- return `Convex.Code does not take a project deploy key: it cannot check before the push that the key targets ${this.deployment}. Pass a deploy key of ${this.deployment} (Convex.DeployKey).`;
164
- case "unknown":
165
- return `deployKey is not a Convex deploy key ("<type>:<deployment>|<secret>" or "preview:<team>:<project>|<secret>"). Pass Convex.DeployKey(...).deployKey or Convex.PreviewDeployKey(...).previewDeployKey.`;
166
- }
167
- }
168
- }
169
-
170
- /** `env` needs the deployment API, which takes only a deploy key of the deployment. */
171
- export class EnvironmentNeedsDeployKey extends Schema.TaggedError<EnvironmentNeedsDeployKey>()(
172
- "EnvironmentNeedsDeployKey",
173
- { deployment: Schema.String },
174
- ) {
175
- override get message() {
176
- return `Convex.Code cannot set or remove env variables on ${this.deployment} with a preview deploy key: the deployment API takes only a deploy key of the deployment. Pass a Convex.DeployKey of ${this.deployment}, or use Convex.DefaultEnvironmentVariable with deploymentType "preview" for values that every new preview deployment gets.`;
177
- }
178
- }
179
-
180
- /** No `convex` package resolves from `cwd`. */
181
- export class ConvexCliNotFound extends Schema.TaggedError<ConvexCliNotFound>()(
182
- "ConvexCliNotFound",
183
- { cwd: Schema.String },
184
- ) {
185
- override get message() {
186
- return `Convex.Code found no installed convex package from ${this.cwd}, and it does not download one. Install convex in the app (npm install convex), set cwd to the app directory, or set the command prop.`;
187
- }
188
- }
189
-
190
- /** The Convex CLI failed, or exited 0 without the deployment URL. The output tail never contains the key. */
191
- export class CodePushFailed extends Schema.TaggedError<CodePushFailed>()("CodePushFailed", {
192
- /** Exit code of the CLI. Undefined when it did not start or a signal stopped it. */
193
- exitCode: Schema.UndefinedOr(Schema.Number),
194
- /** The last lines of the CLI output, with the deploy key removed. */
195
- outputTail: Schema.String,
196
- }) {
197
- override get message() {
198
- return this.exitCode === 0
199
- ? `convex deploy exited 0 but printed no "Deployed Convex functions to <url>" line, so Convex.Code has no deployment URL. Last output, with the deploy key removed:\n${this.outputTail}`
200
- : `convex deploy failed${this.exitCode === undefined ? "" : ` with exit code ${this.exitCode}`}. Last output, with the deploy key removed:\n${this.outputTail}`;
201
- }
202
- }
203
-
204
- /** The CLI pushed to another deployment than the one in the props. Checked after the push. */
205
- export class PushTargetMismatch extends Schema.TaggedError<PushTargetMismatch>()(
206
- "PushTargetMismatch",
207
- { expected: Schema.String, actual: Schema.String },
208
- ) {
209
- override get message() {
210
- return `The Convex CLI pushed the functions to deployment ${this.actual}, but Convex.Code expected ${this.expected}. With a preview deploy key, this happens when the key belongs to another project, or when the preview deployment ${this.expected} expired during the deploy and the CLI created ${this.actual} for the same preview name. Check the key. After an expiry, run the deploy again with --adopt, so that Convex.Deployment takes over ${this.actual}.`;
211
- }
212
- }
213
-
214
- /**
215
- * Checks that the key fits the deployment, and returns the preview name for
216
- * `--preview-name` when the key is a preview deploy key.
217
- */
218
- const previewNameFor = (
219
- target: {
220
- readonly name: string;
221
- readonly type: CodeDeployment["type"] | undefined;
222
- readonly previewName: string | undefined;
223
- },
224
- keyTarget: ReturnType<typeof keyTargetOf>,
225
- changesEnvironment: boolean,
226
- ): Effect.Effect<string | undefined, DeployKeyMismatch | EnvironmentNeedsDeployKey> => {
227
- const mismatch = new DeployKeyMismatch({
228
- deployment: target.name,
229
- key: keyTarget.kind,
230
- keyDeployment: keyTarget.kind === "deployment" ? keyTarget.deployment : undefined,
231
- });
232
- switch (keyTarget.kind) {
233
- case "deployment":
234
- return keyTarget.deployment === target.name
235
- ? Effect.succeed(undefined)
236
- : Effect.fail(mismatch);
237
- case "preview":
238
- if (target.type !== "preview" || target.previewName === undefined) {
239
- return Effect.fail(mismatch);
240
- }
241
- return changesEnvironment
242
- ? Effect.fail(new EnvironmentNeedsDeployKey({ deployment: target.name }))
243
- : Effect.succeed(target.previewName);
244
- case "project":
245
- case "unknown":
246
- return Effect.fail(mismatch);
247
- default:
248
- return keyTarget satisfies never;
249
- }
250
- };
251
-
252
- /** Number of output lines that a failure quotes. */
253
- const OUTPUT_TAIL_LINES = 20;
254
-
255
- /** The last non-empty lines of `text`, without escape codes and with every secret replaced. */
256
- const outputTail = (text: string, secrets: readonly string[]) =>
257
- secrets
258
- .reduce(
259
- (redacted, secret) => redacted.split(secret).join("[REDACTED]"),
260
- stripVTControlCharacters(text),
261
- )
262
- .split(/\r?\n|\r/)
263
- .filter((line) => line.trim() !== "")
264
- .slice(-OUTPUT_TAIL_LINES)
265
- .join("\n");
266
-
267
- const CliManifest = Schema.Struct({
268
- bin: Schema.Union([Schema.String, Schema.Record(Schema.String, Schema.String)]),
269
- });
270
-
271
- /** `[node, <convex bin>]` for the `convex` package that `cwd` resolves. Never downloads. */
272
- const installedCli = (cwd: string) =>
273
- Effect.tryPromise({
274
- try: async (): Promise<readonly [string, ...string[]]> => {
275
- const manifestPath = createRequire(join(cwd, "package.json")).resolve("convex/package.json");
276
- const { bin } = Schema.decodeUnknownSync(Schema.fromJsonString(CliManifest))(
277
- await readFile(manifestPath, "utf8"),
278
- );
279
- const entry = typeof bin === "string" ? bin : bin["convex"];
280
- if (entry === undefined) throw new Error("the convex package has no convex bin");
281
- return [process.execPath, resolve(dirname(manifestPath), entry)];
282
- },
283
- catch: () => new ConvexCliNotFound({ cwd }),
284
- });
285
-
286
- /**
287
- * Runs `use` with an env file that holds the key, in a new temporary
288
- * directory, and removes the directory afterwards: on success, on failure,
289
- * and on interruption.
290
- */
291
- const withKeyFile = <A, E, R>(
292
- key: string,
293
- use: (paths: { readonly envFile: string; readonly home: string }) => Effect.Effect<A, E, R>,
294
- ) =>
295
- Effect.acquireUseRelease(
296
- Effect.promise(() => mkdtemp(join(tmpdir(), "alchemy-convex-"))),
297
- (home) =>
298
- Effect.promise(() =>
299
- writeFile(join(home, "deploy.env"), `CONVEX_DEPLOY_KEY=${key}\n`, { mode: 0o600 }),
300
- ).pipe(Effect.andThen(use({ envFile: join(home, "deploy.env"), home }))),
301
- (home) => Effect.promise(() => rm(home, { recursive: true, force: true })),
302
- );
303
-
304
- /**
305
- * The environment of the CLI: this process's variables without `CONVEX_*`,
306
- * with `home` as the home directory.
307
- */
308
- const cliEnvironment = (home: string): Record<string, string | undefined> => ({
309
- ...Object.fromEntries(
310
- Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("CONVEX_")),
311
- ),
312
- HOME: home,
313
- USERPROFILE: home,
314
- XDG_CONFIG_HOME: home,
315
- });
316
-
317
- export const CodeProvider = () =>
318
- Provider.effect(
319
- Code,
320
- Effect.gen(function* () {
321
- const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
322
-
323
- /** Runs the CLI with piped output. Interruption stops its process group. */
324
- const run = (argv: readonly [string, ...string[]], cwd: string, home: string) =>
325
- Effect.gen(function* () {
326
- const [command, ...args] = argv;
327
- const child = yield* spawner.spawn(
328
- ChildProcess.make(command, args, {
329
- cwd,
330
- env: cliEnvironment(home),
331
- extendEnv: false,
332
- stdin: "ignore",
333
- stdout: "pipe",
334
- stderr: "pipe",
335
- }),
336
- );
337
- const [exitCode, stdout, stderr] = yield* Effect.all(
338
- [
339
- child.exitCode,
340
- Stream.mkString(Stream.decodeText(child.stdout)),
341
- Stream.mkString(Stream.decodeText(child.stderr)),
342
- ],
343
- { concurrency: "unbounded" },
344
- );
345
- return { exitCode: Number(exitCode), output: `${stdout}\n${stderr}` };
346
- }).pipe(Effect.scoped);
347
-
348
- return {
349
- // Every apply pushes: see the top of this file.
350
- diff: () => Effect.succeed({ action: "update" } as const),
351
-
352
- reconcile: Effect.fn(function* ({ fqn, news, output }) {
353
- const target =
354
- typeof news.deployment === "string"
355
- ? {
356
- name: news.deployment,
357
- address: news.deployment,
358
- type: undefined,
359
- previewName: undefined,
360
- }
361
- : { ...news.deployment, address: news.deployment.url };
362
- const key = Redacted.value(news.deployKey);
363
- const secrets = [key, key.slice(key.indexOf("|") + 1)].filter(
364
- (secret) => secret.length >= 8,
365
- );
366
- const env = Object.entries(news.env ?? {}).sort(([a], [b]) => (a < b ? -1 : 1));
367
- // Names set on another deployment stay there: this push does not own them.
368
- const previous =
369
- output !== undefined && output.deploymentName === target.name ? output.envNames : [];
370
- const removed = previous.filter((name) => !env.some(([envName]) => envName === name));
371
-
372
- // 1. The key must fit the deployment. No side effect before this.
373
- const previewName = yield* previewNameFor(
374
- target,
375
- keyTargetOf(key),
376
- env.length > 0 || removed.length > 0,
377
- );
378
-
379
- // 2. The CLI must exist before any variable changes.
380
- const cwd = resolve(news.cwd);
381
- const program = news.command ?? (yield* installedCli(cwd));
382
-
383
- // 3. Variables before the push, so the push sees them. Each
384
- // attempt checks first, so a retry after a write conflict never
385
- // overwrites a value that another writer set in the meantime.
386
- if (env.length > 0 || removed.length > 0) {
387
- const adopt = yield* shouldAdopt(fqn);
388
- const fresh = env.filter(([name]) => !previous.includes(name));
389
- yield* retryIdempotentWrite(
390
- Effect.gen(function* () {
391
- if (fresh.length > 0 && !adopt) {
392
- const current = yield* listVariables(target.address, news.deployKey);
393
- // The same value loses nothing, such as a value that an
394
- // earlier run set before its push failed.
395
- const taken = fresh
396
- .filter(
397
- ([name, value]) =>
398
- Object.hasOwn(current, name) &&
399
- current[name] !==
400
- (Redacted.isRedacted(value) ? Redacted.value(value) : value),
401
- )
402
- .map(([name]) => name);
403
- if (taken.length > 0) {
404
- return yield* new OwnedBySomeoneElse({
405
- message: `Environment variables ${taken.join(", ")} already exist on deployment ${target.name} with other values. Re-run with --adopt to take them over and overwrite them, or remove them from env.`,
406
- resourceType: Code.Type,
407
- physicalName: target.name,
408
- });
409
- }
410
- }
411
- yield* writeVariables(target.address, news.deployKey, [
412
- ...env.map(([name, value]) => ({ name, value })),
413
- ...removed.map((name) => ({ name, value: null })),
414
- ]);
415
- }),
416
- );
417
- }
418
-
419
- // 4. The push.
420
- const args = [
421
- "deploy",
422
- ...(previewName === undefined ? [] : ["--preview-name", previewName]),
423
- ...(news.typecheck === undefined ? [] : [`--typecheck=${news.typecheck}`]),
424
- ...(news.codegen === undefined ? [] : [`--codegen=${news.codegen}`]),
425
- ];
426
- const result = yield* withKeyFile(key, ({ envFile, home }) =>
427
- run([...program, ...args, "--env-file", envFile], cwd, home),
428
- ).pipe(
429
- Effect.mapError(
430
- (error) =>
431
- new CodePushFailed({
432
- exitCode: undefined,
433
- outputTail: outputTail(error.message, secrets),
434
- }),
435
- ),
436
- );
437
- const deployed = parseDeployOutput(result.output);
438
- if (result.exitCode !== 0 || deployed === undefined) {
439
- return yield* new CodePushFailed({
440
- exitCode: result.exitCode,
441
- outputTail: outputTail(result.output, secrets),
442
- });
443
- }
444
- if (deployed.deploymentName !== target.name) {
445
- return yield* new PushTargetMismatch({
446
- expected: target.name,
447
- actual: deployed.deploymentName,
448
- });
449
- }
450
- return {
451
- deploymentName: deployed.deploymentName,
452
- url: deployed.url,
453
- siteUrl: siteUrlOf(deployed.url),
454
- deployedAt: yield* Clock.currentTimeMillis,
455
- envNames: env.map(([name]) => name),
456
- };
457
- }),
458
-
459
- // The functions and the variables stay: the deployment runs them.
460
- delete: () => Effect.void,
461
- };
462
- }),
463
- );