@samebase/alchemy-convex 0.7.0 → 0.8.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 +39 -13
- package/lib/index.d.ts +49 -11
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +103 -53
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/Deployment.ts +97 -18
- package/src/Providers.ts +2 -2
- package/src/Push.ts +58 -48
- package/src/State.ts +1 -0
- package/src/index.ts +2 -0
package/package.json
CHANGED
package/src/Deployment.ts
CHANGED
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
// variables, and its functions, with the URLs that a frontend or a Worker
|
|
3
3
|
// needs. This is the Convex plane of a stack: no deploy key leaves it.
|
|
4
4
|
//
|
|
5
|
-
// - `
|
|
6
|
-
//
|
|
5
|
+
// - `rootDir` is the app directory, relative to the directory where the
|
|
6
|
+
// deploy runs (the directory of the stack file), such as `apps/web`, and
|
|
7
|
+
// "." by default. The push runs in it, and `source` is relative to it.
|
|
8
|
+
// The constructor writes it into the props, "." included, so that a reader
|
|
9
|
+
// of the state row finds the app directory of every deployment.
|
|
10
|
+
// - `source` is the Convex functions directory relative to `rootDir`, such
|
|
11
|
+
// as `./convex`. The update pushes it with the Convex CLI that the app
|
|
12
|
+
// installed (Push.ts).
|
|
7
13
|
// The content identity of the push is a hash of the source files, the
|
|
8
14
|
// app's package.json, convex.json, and lockfile, the deployment name, and
|
|
9
15
|
// `env`. The same hash is a no-op. The hash is taken again after the push,
|
|
@@ -45,9 +51,11 @@
|
|
|
45
51
|
//
|
|
46
52
|
// Patterns from Confect `packages/alchemy/src/Deployment.ts`
|
|
47
53
|
// (https://github.com/rjdellecese/confect, ISC license, see NOTICE).
|
|
54
|
+
import { posix, win32 } from "node:path";
|
|
48
55
|
import { Resource } from "alchemy";
|
|
49
56
|
import { OwnedBySomeoneElse, Unowned } from "alchemy/AdoptPolicy";
|
|
50
57
|
import { isResolved } from "alchemy/Diff";
|
|
58
|
+
import type { InputProps } from "alchemy/Input";
|
|
51
59
|
import * as Provider from "alchemy/Provider";
|
|
52
60
|
import * as Effect from "effect/Effect";
|
|
53
61
|
import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
|
|
@@ -79,10 +87,16 @@ interface DeploymentInputs {
|
|
|
79
87
|
/** The project: a Convex.Project resource, or `{ projectId }`. Cannot change. */
|
|
80
88
|
readonly project: { readonly projectId: number };
|
|
81
89
|
/**
|
|
82
|
-
* The
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
90
|
+
* The app directory, relative to the directory where the deploy runs (the
|
|
91
|
+
* directory of the stack file), such as `apps/web`. Default ".". It must
|
|
92
|
+
* have a package.json. The push runs in it, and `source` is relative to
|
|
93
|
+
* it. State from 0.7 has no `rootDir`.
|
|
94
|
+
*/
|
|
95
|
+
readonly rootDir?: string;
|
|
96
|
+
/**
|
|
97
|
+
* The Convex functions directory relative to `rootDir`, such as
|
|
98
|
+
* `./convex`, or that directory with a push command and more hashed
|
|
99
|
+
* files. Without `source`, the resource pushes nothing.
|
|
86
100
|
*/
|
|
87
101
|
readonly source?: Source;
|
|
88
102
|
/** Environment variables to set on the deployment before the push. */
|
|
@@ -134,10 +148,61 @@ export type Deployment = Resource<
|
|
|
134
148
|
never,
|
|
135
149
|
Providers
|
|
136
150
|
>;
|
|
137
|
-
|
|
151
|
+
/** The resource class. Stacks call {@link Deployment}, which writes `rootDir` into the props. */
|
|
152
|
+
export const DeploymentResource = Resource<Deployment>("Convex.Deployment", {
|
|
138
153
|
defaultRemovalPolicy: "retain",
|
|
139
154
|
});
|
|
140
155
|
|
|
156
|
+
/** The props of a `Convex.Deployment` call: Alchemy inputs, with `rootDir` a plain string. */
|
|
157
|
+
export type DeploymentInput = DeploymentProps extends infer P
|
|
158
|
+
? P extends DeploymentProps
|
|
159
|
+
? InputProps<P, "rootDir">
|
|
160
|
+
: never
|
|
161
|
+
: never;
|
|
162
|
+
|
|
163
|
+
/** `rootDir` in POSIX form, without "./" and a trailing slash, such as `apps/web` or ".". */
|
|
164
|
+
const normalizeRootDir = (rootDir: string) => {
|
|
165
|
+
const normalized = posix.normalize(rootDir.replaceAll("\\", "/"));
|
|
166
|
+
return normalized.length > 1 && normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/** `rootDir` is an absolute path. The declaration stops with it as a defect, before the plan. */
|
|
170
|
+
export class AbsoluteRootDir extends Schema.TaggedError<AbsoluteRootDir>()("AbsoluteRootDir", {
|
|
171
|
+
rootDir: Schema.String,
|
|
172
|
+
}) {
|
|
173
|
+
override get message() {
|
|
174
|
+
return `Convex.Deployment rootDir ${this.rootDir} is an absolute path. State keeps rootDir for every machine that runs the stack, so set it relative to the directory where the deploy runs, such as "apps/web".`;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* `Convex.Deployment("id", props)`. The props go to state with `rootDir` in
|
|
180
|
+
* POSIX form, and with "." when it is not set, so that every row names the
|
|
181
|
+
* app directory of its deployment. An absolute `rootDir` stops the
|
|
182
|
+
* declaration, so it never reaches the plan or state. The check dies,
|
|
183
|
+
* because the program of `Alchemy.Stack` can fail only with a ConfigError.
|
|
184
|
+
* `win32.isAbsolute` also answers true for a POSIX absolute path. The other
|
|
185
|
+
* fields keep it usable where Alchemy takes a resource class, such as
|
|
186
|
+
* `Provider.collection`.
|
|
187
|
+
*/
|
|
188
|
+
export const Deployment = Object.assign(
|
|
189
|
+
(id: string, props: DeploymentInput) => {
|
|
190
|
+
const rootDir = props.rootDir ?? ".";
|
|
191
|
+
return win32.isAbsolute(rootDir)
|
|
192
|
+
? Effect.die(new AbsoluteRootDir({ rootDir }))
|
|
193
|
+
: DeploymentResource(id, { ...props, rootDir: normalizeRootDir(rootDir) });
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
Type: DeploymentResource.Type,
|
|
197
|
+
Props: DeploymentResource.Props,
|
|
198
|
+
Self: DeploymentResource.Self,
|
|
199
|
+
Provider: DeploymentResource.Provider,
|
|
200
|
+
Aliases: DeploymentResource.Aliases,
|
|
201
|
+
ref: (id: string, options?: { stage?: string; stack?: string }) =>
|
|
202
|
+
DeploymentResource.ref(id, options),
|
|
203
|
+
},
|
|
204
|
+
);
|
|
205
|
+
|
|
141
206
|
/** The props name another deployment than the one in state. */
|
|
142
207
|
export class DeploymentIdentityChange extends Schema.TaggedError<DeploymentIdentityChange>()(
|
|
143
208
|
"DeploymentIdentityChange",
|
|
@@ -336,7 +401,7 @@ const isRefused = (error: ConvexApiError) =>
|
|
|
336
401
|
|
|
337
402
|
export const DeploymentProvider = () =>
|
|
338
403
|
Provider.effect(
|
|
339
|
-
|
|
404
|
+
DeploymentResource,
|
|
340
405
|
Effect.gen(function* () {
|
|
341
406
|
const api = yield* ManagementApi;
|
|
342
407
|
const credentials = yield* Credentials;
|
|
@@ -517,10 +582,10 @@ export const DeploymentProvider = () =>
|
|
|
517
582
|
});
|
|
518
583
|
|
|
519
584
|
/** The content identity of `source` and `env` on a deployment, or undefined when there is nothing to push. */
|
|
520
|
-
const wantedHash = (news: DeploymentProps, deployment: string) =>
|
|
585
|
+
const wantedHash = (news: DeploymentProps, rootDir: string, deployment: string) =>
|
|
521
586
|
news.source === undefined && Object.keys(news.env ?? {}).length === 0
|
|
522
587
|
? Effect.succeed(undefined)
|
|
523
|
-
: pushHash({ source: news.source, deployment, env: news.env ?? {} });
|
|
588
|
+
: pushHash({ rootDir, source: news.source, deployment, env: news.env ?? {} });
|
|
524
589
|
|
|
525
590
|
return {
|
|
526
591
|
// `name`, `reference`, and the URLs change when an expired preview is
|
|
@@ -529,10 +594,21 @@ export const DeploymentProvider = () =>
|
|
|
529
594
|
stables: ["projectId", "type", "previewName"],
|
|
530
595
|
|
|
531
596
|
// Compares with the attributes: after a refused change, `olds` are
|
|
532
|
-
// the refused props.
|
|
533
|
-
|
|
597
|
+
// the refused props. Only `rootDir` compares with `olds`, because it
|
|
598
|
+
// is in the props only. A refused update leaves the row `updating`,
|
|
599
|
+
// and the next plan updates it again anyway.
|
|
600
|
+
diff: Effect.fn(function* ({ news, olds, output }) {
|
|
534
601
|
if (!isResolved(news) || output === undefined) return undefined;
|
|
535
602
|
yield* checkIdentity(output, news);
|
|
603
|
+
// State from 0.7 has no rootDir; the constructor always writes it.
|
|
604
|
+
const rootDir = news.rootDir ?? ".";
|
|
605
|
+
// Also here, so that a wrong rootDir stops the plan before Apply
|
|
606
|
+
// writes the new props to the row.
|
|
607
|
+
if (news.source !== undefined) yield* checkSource(rootDir, news.source);
|
|
608
|
+
// State from 0.7 has no rootDir, and another rootDir is another
|
|
609
|
+
// app directory. The update writes the props to state; it pushes
|
|
610
|
+
// only when the hash changed.
|
|
611
|
+
if (olds.rootDir !== rootDir) return { action: "update" } as const;
|
|
536
612
|
const access = yield* accessFor(addressOf(news), changesEnv(news, output));
|
|
537
613
|
// A preview expires, anyone can delete a deployment, and another
|
|
538
614
|
// production deployment can become the default. Reconcile then
|
|
@@ -540,7 +616,7 @@ export const DeploymentProvider = () =>
|
|
|
540
616
|
// URL of a deployment that is gone.
|
|
541
617
|
const current = yield* live(access, output);
|
|
542
618
|
if (current?.name !== output.name) return { action: "update" } as const;
|
|
543
|
-
const hash = yield* wantedHash(news, output.name);
|
|
619
|
+
const hash = yield* wantedHash(news, rootDir, output.name);
|
|
544
620
|
return hash === output.hash
|
|
545
621
|
? ({ action: "noop" } as const)
|
|
546
622
|
: ({ action: "update" } as const);
|
|
@@ -581,8 +657,10 @@ export const DeploymentProvider = () =>
|
|
|
581
657
|
reconcile: Effect.fn(function* ({ fqn, news, output }) {
|
|
582
658
|
// Also here: with an unresolved prop in plan, diff could not check.
|
|
583
659
|
if (output !== undefined) yield* checkIdentity(output, news);
|
|
584
|
-
// The functions directory and the program,
|
|
585
|
-
|
|
660
|
+
// The app directory, the functions directory, and the program,
|
|
661
|
+
// before any change.
|
|
662
|
+
const rootDir = news.rootDir ?? ".";
|
|
663
|
+
if (news.source !== undefined) yield* checkSource(rootDir, news.source);
|
|
586
664
|
const address = addressOf(news);
|
|
587
665
|
const access = yield* accessFor(address, changesEnv(news, output));
|
|
588
666
|
const env = news.env ?? {};
|
|
@@ -597,7 +675,7 @@ export const DeploymentProvider = () =>
|
|
|
597
675
|
const refuse = (name: string) =>
|
|
598
676
|
new OwnedBySomeoneElse({
|
|
599
677
|
message: `Project ${address.projectId} already has ${news.type} deployment ${name}${news.type === "prod" ? " with code" : ` named "${news.name}"`}. Re-run with --adopt to take it over${news.type === "prod" ? "" : ", or use another name"}.`,
|
|
600
|
-
resourceType:
|
|
678
|
+
resourceType: DeploymentResource.Type,
|
|
601
679
|
physicalName: name,
|
|
602
680
|
});
|
|
603
681
|
|
|
@@ -662,7 +740,7 @@ export const DeploymentProvider = () =>
|
|
|
662
740
|
// Variables set on another deployment stay there: they are not ours
|
|
663
741
|
// on this one.
|
|
664
742
|
const previousEnvNames = output?.name === target.name ? envNamesOf(output) : [];
|
|
665
|
-
const before = yield* wantedHash(news, target.name);
|
|
743
|
+
const before = yield* wantedHash(news, rootDir, target.name);
|
|
666
744
|
if (before === undefined && previousEnvNames.length === 0) {
|
|
667
745
|
return { ...target, hash: undefined, envNames: [] };
|
|
668
746
|
}
|
|
@@ -672,6 +750,7 @@ export const DeploymentProvider = () =>
|
|
|
672
750
|
const options = {
|
|
673
751
|
deployment: target.name,
|
|
674
752
|
url: target.url,
|
|
753
|
+
rootDir,
|
|
675
754
|
source: news.source,
|
|
676
755
|
env,
|
|
677
756
|
previousEnvNames,
|
|
@@ -719,7 +798,7 @@ export const DeploymentProvider = () =>
|
|
|
719
798
|
// such as convex/_generated, are part of the identity.
|
|
720
799
|
return {
|
|
721
800
|
...target,
|
|
722
|
-
hash: yield* wantedHash(news, target.name),
|
|
801
|
+
hash: yield* wantedHash(news, rootDir, target.name),
|
|
723
802
|
envNames: Object.keys(env).sort(),
|
|
724
803
|
};
|
|
725
804
|
}),
|
package/src/Providers.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import * as Provider from "alchemy/Provider";
|
|
4
4
|
import * as Layer from "effect/Layer";
|
|
5
5
|
import { type Credentials, fromConfig } from "./Credentials.ts";
|
|
6
|
-
import {
|
|
6
|
+
import { DeploymentProvider, DeploymentResource } from "./Deployment.ts";
|
|
7
7
|
import { ManagementApiLive } from "./ManagementApi.ts";
|
|
8
8
|
import { Project, ProjectProvider } from "./Project.ts";
|
|
9
9
|
|
|
@@ -15,7 +15,7 @@ export class Providers extends Provider.ProviderCollection<Providers>()("Convex"
|
|
|
15
15
|
* `CONVEX_DEPLOY_KEY`).
|
|
16
16
|
*/
|
|
17
17
|
export const providers = (credentials: Layer.Layer<Credentials> = fromConfig()) =>
|
|
18
|
-
Layer.effect(Providers, Provider.collection([Project,
|
|
18
|
+
Layer.effect(Providers, Provider.collection([Project, DeploymentResource])).pipe(
|
|
19
19
|
Layer.provide(Layer.mergeAll(ProjectProvider(), DeploymentProvider())),
|
|
20
20
|
Layer.provide(ManagementApiLive()),
|
|
21
21
|
Layer.provide(credentials),
|
package/src/Push.ts
CHANGED
|
@@ -48,13 +48,14 @@ import {
|
|
|
48
48
|
export type EnvValue = string | Redacted.Redacted<string>;
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
* The `source` prop: the Convex functions directory
|
|
52
|
-
*
|
|
51
|
+
* The `source` prop: the Convex functions directory relative to the app
|
|
52
|
+
* directory (`rootDir`), such as `./convex`, or that directory with a push
|
|
53
|
+
* command and more files for the content identity.
|
|
53
54
|
*/
|
|
54
55
|
export type Source =
|
|
55
56
|
| string
|
|
56
57
|
| {
|
|
57
|
-
/** The Convex functions directory, such as `./convex`. */
|
|
58
|
+
/** The Convex functions directory relative to the app directory, such as `./convex`. */
|
|
58
59
|
readonly path: string;
|
|
59
60
|
/**
|
|
60
61
|
* Replaces `convex deploy`: a program and its arguments, run in the app
|
|
@@ -109,17 +110,20 @@ export class ConvexCliNotFound extends Schema.TaggedError<ConvexCliNotFound>()(
|
|
|
109
110
|
{ cwd: Schema.String },
|
|
110
111
|
) {
|
|
111
112
|
override get message() {
|
|
112
|
-
return `Convex.Deployment found no installed convex package from ${this.cwd}, and it does not download one. Install convex in the app (npm install convex), or set
|
|
113
|
+
return `Convex.Deployment found no installed convex package from ${this.cwd}, and it does not download one. Install convex in the app (npm install convex), or set rootDir to the directory of the app.`;
|
|
113
114
|
}
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
/**
|
|
117
|
+
/** The app directory (`rootDir`) has no package.json. */
|
|
117
118
|
export class ConvexAppNotFound extends Schema.TaggedError<ConvexAppNotFound>()(
|
|
118
119
|
"ConvexAppNotFound",
|
|
119
|
-
{
|
|
120
|
+
{
|
|
121
|
+
/** The app directory, resolved against the working directory of the deploy. */
|
|
122
|
+
rootDir: Schema.String,
|
|
123
|
+
},
|
|
120
124
|
) {
|
|
121
125
|
override get message() {
|
|
122
|
-
return `Convex.Deployment found no package.json
|
|
126
|
+
return `Convex.Deployment found no package.json in ${this.rootDir}. Set rootDir to the directory of the Convex app, relative to the directory where the deploy runs, such as "apps/web".`;
|
|
123
127
|
}
|
|
124
128
|
}
|
|
125
129
|
|
|
@@ -227,32 +231,27 @@ const outputTail = (text: string, secrets: readonly string[]) =>
|
|
|
227
231
|
|
|
228
232
|
const valueOf = (value: EnvValue) => (Redacted.isRedacted(value) ? Redacted.value(value) : value);
|
|
229
233
|
|
|
230
|
-
/**
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
} catch {
|
|
240
|
-
const parent = dirname(directory);
|
|
241
|
-
if (parent === directory) throw new Error("no package.json");
|
|
242
|
-
directory = parent;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
},
|
|
246
|
-
catch: () => new ConvexAppNotFound({ source }),
|
|
234
|
+
/**
|
|
235
|
+
* The app directory: `rootDir` resolved against the working directory of the
|
|
236
|
+
* deploy. It must have a package.json. There is no search above it.
|
|
237
|
+
*/
|
|
238
|
+
const appDirectoryOf = (rootDir: string) => {
|
|
239
|
+
const directory = resolve(rootDir);
|
|
240
|
+
return Effect.tryPromise({
|
|
241
|
+
try: () => access(join(directory, "package.json")).then(() => directory),
|
|
242
|
+
catch: () => new ConvexAppNotFound({ rootDir: directory }),
|
|
247
243
|
});
|
|
244
|
+
};
|
|
248
245
|
|
|
249
|
-
/** `source` is not the functions directory that the Convex CLI pushes from
|
|
246
|
+
/** `source` is not the functions directory that the Convex CLI pushes from the app directory. */
|
|
250
247
|
export class SourceMismatch extends Schema.TaggedError<SourceMismatch>()("SourceMismatch", {
|
|
248
|
+
/** The app directory, resolved. */
|
|
249
|
+
rootDir: Schema.String,
|
|
251
250
|
source: Schema.String,
|
|
252
251
|
functions: Schema.String,
|
|
253
252
|
}) {
|
|
254
253
|
override get message() {
|
|
255
|
-
return `Convex.Deployment source is ${this.source}, but the Convex CLI pushes ${this.functions}
|
|
254
|
+
return `Convex.Deployment source is ${this.source}, but the Convex CLI pushes ${this.functions} from the app directory ${this.rootDir} (the "functions" field of convex.json, or convex/ by default). Set rootDir to the directory of the Convex app, and source to its functions directory relative to rootDir, such as "./convex".`;
|
|
256
255
|
}
|
|
257
256
|
}
|
|
258
257
|
|
|
@@ -269,8 +268,8 @@ const checkFunctionsDirectory = (cwd: string, path: string) =>
|
|
|
269
268
|
? undefined
|
|
270
269
|
: Schema.decodeUnknownSync(Schema.fromJsonString(ConvexJson))(text).functions;
|
|
271
270
|
const functions = resolve(cwd, configured ?? "convex");
|
|
272
|
-
if (functions !==
|
|
273
|
-
return yield* new SourceMismatch({ source:
|
|
271
|
+
if (functions !== path) {
|
|
272
|
+
return yield* new SourceMismatch({ rootDir: cwd, source: path, functions });
|
|
274
273
|
}
|
|
275
274
|
});
|
|
276
275
|
|
|
@@ -300,6 +299,8 @@ export const installedCli = (cwd: string) =>
|
|
|
300
299
|
* A change in a workspace package outside the app directory is not seen.
|
|
301
300
|
*/
|
|
302
301
|
export const pushHash = (options: {
|
|
302
|
+
/** The app directory, relative to the working directory of the deploy or absolute. */
|
|
303
|
+
readonly rootDir: string;
|
|
303
304
|
readonly source: Source | undefined;
|
|
304
305
|
readonly deployment: string;
|
|
305
306
|
readonly env: Readonly<Record<string, EnvValue>>;
|
|
@@ -309,28 +310,32 @@ export const pushHash = (options: {
|
|
|
309
310
|
const files =
|
|
310
311
|
source === undefined
|
|
311
312
|
? undefined
|
|
312
|
-
: yield* appDirectoryOf(
|
|
313
|
-
Effect.flatMap((cwd) =>
|
|
314
|
-
|
|
313
|
+
: yield* appDirectoryOf(options.rootDir).pipe(
|
|
314
|
+
Effect.flatMap((cwd) => {
|
|
315
|
+
const functions = relative(cwd, resolve(cwd, source.path)).split("\\").join("/");
|
|
316
|
+
return hashDirectory({
|
|
315
317
|
cwd,
|
|
316
318
|
// By default every file of the app that git does not ignore,
|
|
317
|
-
// such as a module outside `source` that a function imports
|
|
318
|
-
//
|
|
319
|
-
// the
|
|
319
|
+
// such as a module outside `source` that a function imports,
|
|
320
|
+
// and the functions when convex.json puts them outside
|
|
321
|
+
// rootDir. `include` narrows it to the functions, the
|
|
322
|
+
// manifests, and the listed globs.
|
|
320
323
|
memo:
|
|
321
|
-
source.include
|
|
322
|
-
? {
|
|
323
|
-
: {
|
|
324
|
+
source.include !== undefined
|
|
325
|
+
? {
|
|
324
326
|
include: [
|
|
325
|
-
`${
|
|
327
|
+
`${functions}/**`,
|
|
326
328
|
"package.json",
|
|
327
329
|
"convex.json",
|
|
328
330
|
...source.include,
|
|
329
331
|
],
|
|
330
332
|
lockfile: true,
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
|
|
333
|
+
}
|
|
334
|
+
: functions === ".." || functions.startsWith("../")
|
|
335
|
+
? { include: ["**/*", `${functions}/**`], lockfile: true }
|
|
336
|
+
: {},
|
|
337
|
+
});
|
|
338
|
+
}),
|
|
334
339
|
);
|
|
335
340
|
const env = Object.entries(options.env)
|
|
336
341
|
.map(([name, value]): readonly [string, string] => [name, valueOf(value)])
|
|
@@ -469,7 +474,9 @@ export interface PushOptions {
|
|
|
469
474
|
readonly deployment: string;
|
|
470
475
|
/** Client URL of the deployment. */
|
|
471
476
|
readonly url: string;
|
|
472
|
-
/** The
|
|
477
|
+
/** The app directory, relative to the working directory of the deploy or absolute. The push runs in it. */
|
|
478
|
+
readonly rootDir: string;
|
|
479
|
+
/** The functions directory relative to `rootDir`, or it with a push command. Without it, only the variables change. */
|
|
473
480
|
readonly source: Source | undefined;
|
|
474
481
|
readonly env: Readonly<Record<string, EnvValue>>;
|
|
475
482
|
/** Names that an earlier push set on this deployment. Names that left `env` are removed. */
|
|
@@ -485,14 +492,14 @@ export interface PushOptions {
|
|
|
485
492
|
|
|
486
493
|
/**
|
|
487
494
|
* The app directory and the program of a push. Fails before any change when
|
|
488
|
-
* `source` is not the functions directory
|
|
489
|
-
* installed Convex CLI and no push command.
|
|
495
|
+
* `rootDir` has no package.json, when `source` is not the functions directory
|
|
496
|
+
* of the app, or when the app has no installed Convex CLI and no push command.
|
|
490
497
|
*/
|
|
491
|
-
export const checkSource = (input: Source) =>
|
|
498
|
+
export const checkSource = (rootDir: string, input: Source) =>
|
|
492
499
|
Effect.gen(function* () {
|
|
493
500
|
const source = sourceOf(input);
|
|
494
|
-
const cwd = yield* appDirectoryOf(
|
|
495
|
-
yield* checkFunctionsDirectory(cwd, source.path);
|
|
501
|
+
const cwd = yield* appDirectoryOf(rootDir);
|
|
502
|
+
yield* checkFunctionsDirectory(cwd, resolve(cwd, source.path));
|
|
496
503
|
const program = source.command ?? (yield* installedCli(cwd));
|
|
497
504
|
return { cwd, program };
|
|
498
505
|
});
|
|
@@ -510,7 +517,10 @@ export const push = (
|
|
|
510
517
|
) =>
|
|
511
518
|
Effect.gen(function* () {
|
|
512
519
|
const source = options.source === undefined ? undefined : sourceOf(options.source);
|
|
513
|
-
const resolved =
|
|
520
|
+
const resolved =
|
|
521
|
+
options.source === undefined
|
|
522
|
+
? undefined
|
|
523
|
+
: yield* checkSource(options.rootDir, options.source);
|
|
514
524
|
const cwd = resolved?.cwd;
|
|
515
525
|
const program = resolved?.program;
|
|
516
526
|
const env = Object.entries(options.env).sort(([a], [b]) => (a < b ? -1 : 1));
|
package/src/State.ts
CHANGED
|
@@ -270,6 +270,7 @@ const pushStore = (
|
|
|
270
270
|
push(options.spawner, key, {
|
|
271
271
|
deployment: deployment.name,
|
|
272
272
|
url: deployment.deploymentUrl,
|
|
273
|
+
rootDir: join(options.store, ".."),
|
|
273
274
|
source: {
|
|
274
275
|
path: options.store,
|
|
275
276
|
command: [...cli, "deploy", "--typecheck", "disable", "--codegen", "disable"],
|
package/src/index.ts
CHANGED
|
@@ -8,10 +8,12 @@ export {
|
|
|
8
8
|
fromValues,
|
|
9
9
|
} from "./Credentials.ts";
|
|
10
10
|
export {
|
|
11
|
+
AbsoluteRootDir,
|
|
11
12
|
AmbiguousDeployment,
|
|
12
13
|
Deployment,
|
|
13
14
|
type DeploymentAttributes,
|
|
14
15
|
DeploymentIdentityChange,
|
|
16
|
+
type DeploymentInput,
|
|
15
17
|
type DeploymentProps,
|
|
16
18
|
ProductionDeploymentNotFound,
|
|
17
19
|
} from "./Deployment.ts";
|