@prisma/composer-prisma-cloud 0.30.0-dev.2 → 0.30.0-dev.3
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/dist/auth/pack.mjs +1 -1
- package/dist/auth/pack.mjs.map +1 -1
- package/dist/auth/testing.mjs +1 -1
- package/dist/auth/testing.mjs.map +1 -1
- package/dist/buckets-main.mjs +2 -1
- package/dist/buckets-main.mjs.map +1 -1
- package/dist/compute-main.mjs +243 -231
- package/dist/compute-main.mjs.map +1 -1
- package/dist/control.mjs +1 -1
- package/dist/control.mjs.map +1 -1
- package/dist/{segments-BW2SYdBf-CtmcRzwy.mjs → daemon-D9BV062Q-ZTmWho_N.mjs} +2 -11
- package/dist/daemon-D9BV062Q-ZTmWho_N.mjs.map +1 -0
- package/dist/local-target.mjs +296 -89
- package/dist/local-target.mjs.map +1 -1
- package/dist/postgres-main.mjs +2 -1
- package/dist/postgres-main.mjs.map +1 -1
- package/dist/{s3-credentials-resource-BZjtuopJ-D0fKtjEH.mjs → s3-credentials-resource-BZjtuopJ-azxxffwQ.mjs} +4 -4
- package/dist/{s3-credentials-resource-BZjtuopJ-D0fKtjEH.mjs.map → s3-credentials-resource-BZjtuopJ-azxxffwQ.mjs.map} +1 -1
- package/dist/segments-CA7TOJ1U-CoICbb95.mjs +14 -0
- package/dist/segments-CA7TOJ1U-CoICbb95.mjs.map +1 -0
- package/package.json +18 -18
- package/dist/segments-BW2SYdBf-CtmcRzwy.mjs.map +0 -1
package/dist/local-target.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as blindCast$1 } from "./casts-DpaahrlC-Ykus3kCE.mjs";
|
|
2
|
-
import { E as isWithin, b as mintKeyPair, c as PrismaCloudContainer, d as collectPreflightNames, h as prismaCloudContainerOf, i as OrmMigrationProvider, n as GeneratedParamProvider, p as deserialize, s as PgWarmProvider, u as S3CredentialsProvider, v as serviceAppName, w as ServiceKeyProvider, x as Providers } from "./s3-credentials-resource-BZjtuopJ-
|
|
2
|
+
import { E as isWithin, b as mintKeyPair, c as PrismaCloudContainer, d as collectPreflightNames, h as prismaCloudContainerOf, i as OrmMigrationProvider, n as GeneratedParamProvider, p as deserialize, s as PgWarmProvider, u as S3CredentialsProvider, v as serviceAppName, w as ServiceKeyProvider, x as Providers } from "./s3-credentials-resource-BZjtuopJ-azxxffwQ.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { blindCast } from "@prisma/composer/casts";
|
|
5
5
|
import * as Effect from "effect/Effect";
|
|
@@ -1564,7 +1564,7 @@ var require_adapter = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
1564
1564
|
};
|
|
1565
1565
|
}));
|
|
1566
1566
|
//#endregion
|
|
1567
|
-
//#region ../../1-prisma-cloud/0-lowering/dev-emulators/dist/
|
|
1567
|
+
//#region ../../1-prisma-cloud/0-lowering/dev-emulators/dist/daemon-D9BV062Q.mjs
|
|
1568
1568
|
var import_proper_lockfile = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
1569
1569
|
const lockfile = require_lockfile();
|
|
1570
1570
|
const { toPromise, toSync, toSyncOptions } = require_adapter();
|
|
@@ -2050,6 +2050,8 @@ async function ensureDaemon(name, entry, opts = {}) {
|
|
|
2050
2050
|
await release();
|
|
2051
2051
|
}
|
|
2052
2052
|
}
|
|
2053
|
+
//#endregion
|
|
2054
|
+
//#region ../../1-prisma-cloud/0-lowering/dev-emulators/dist/segments-CA7TOJ1U.mjs
|
|
2053
2055
|
/**
|
|
2054
2056
|
* API hygiene shared by both daemons (local-dev spec § 2): every `<app>`,
|
|
2055
2057
|
* `<id>`, and `<name>` path segment must match this shape or the request is
|
|
@@ -2134,19 +2136,22 @@ const ADMIN_FETCH_RETRY_DELAY_MS = 150;
|
|
|
2134
2136
|
* `fetch` for the loopback admin calls, retried on transient socket errors:
|
|
2135
2137
|
* a keep-alive reuse race (the runtime re-uses a pooled connection the
|
|
2136
2138
|
* daemon's HTTP server just closed idle — surfaces as "fetch failed" /
|
|
2137
|
-
* "other side closed") or a briefly overloaded daemon.
|
|
2138
|
-
*
|
|
2139
|
-
*
|
|
2140
|
-
*
|
|
2139
|
+
* "other side closed") or a briefly overloaded daemon. A short blind retry is
|
|
2140
|
+
* safe only for an idempotent call; a call that is not, such as creating an
|
|
2141
|
+
* instance, passes `retry: false`, because the emulator may have handled the
|
|
2142
|
+
* first attempt. A caller-driven abort is never retried. When every attempt
|
|
2143
|
+
* fails to connect, it throws `DEV.EMULATOR_UNREACHABLE`; callers retry that
|
|
2144
|
+
* code and nothing else.
|
|
2141
2145
|
*/
|
|
2142
|
-
async function adminFetch(url, init) {
|
|
2146
|
+
async function adminFetch(url, init, opts = {}) {
|
|
2147
|
+
const attempts = opts.retry === false ? 1 : ADMIN_FETCH_ATTEMPTS;
|
|
2143
2148
|
let lastError;
|
|
2144
|
-
for (let attempt = 1; attempt <=
|
|
2149
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) try {
|
|
2145
2150
|
return await fetch(url, init);
|
|
2146
2151
|
} catch (error) {
|
|
2147
2152
|
if (init?.signal?.aborted === true) throw error;
|
|
2148
2153
|
lastError = error;
|
|
2149
|
-
if (attempt <
|
|
2154
|
+
if (attempt < attempts) await new Promise((resolve) => setTimeout(resolve, ADMIN_FETCH_RETRY_DELAY_MS));
|
|
2150
2155
|
}
|
|
2151
2156
|
throw new CliStructuredError("DEV.EMULATOR_UNREACHABLE", `Could not connect to the local dev emulator at ${new URL(url).origin}: ${lastError instanceof Error ? lastError.message : String(lastError)}`, {
|
|
2152
2157
|
why: "The emulator is registered and its process is alive, but it did not accept the connection.",
|
|
@@ -2162,23 +2167,33 @@ async function expectOk(res) {
|
|
|
2162
2167
|
}
|
|
2163
2168
|
return res;
|
|
2164
2169
|
}
|
|
2165
|
-
const
|
|
2170
|
+
const INSTANCE_STATUSES = /* @__PURE__ */ new Set([
|
|
2166
2171
|
"running",
|
|
2167
2172
|
"backoff",
|
|
2168
2173
|
"held",
|
|
2169
2174
|
"stopped"
|
|
2170
2175
|
]);
|
|
2171
|
-
function
|
|
2172
|
-
return typeof value === "object" && value !== null &&
|
|
2176
|
+
function isStringRecord$1(value) {
|
|
2177
|
+
return typeof value === "object" && value !== null && Object.values(value).every((v) => typeof v === "string");
|
|
2178
|
+
}
|
|
2179
|
+
function isInstanceInfo(value) {
|
|
2180
|
+
return typeof value === "object" && value !== null && "id" in value && typeof value.id === "string" && "labels" in value && isStringRecord$1(value.labels) && "port" in value && typeof value.port === "number" && "url" in value && typeof value.url === "string" && "status" in value && typeof value.status === "string" && INSTANCE_STATUSES.has(value.status) && "logPath" in value && typeof value.logPath === "string";
|
|
2173
2181
|
}
|
|
2174
|
-
function
|
|
2175
|
-
return Array.isArray(value) && value.every(
|
|
2182
|
+
function isInstanceInfoArray(value) {
|
|
2183
|
+
return Array.isArray(value) && value.every(isInstanceInfo);
|
|
2176
2184
|
}
|
|
2177
2185
|
function isHealthBody(value) {
|
|
2178
2186
|
return typeof value === "object" && value !== null && "version" in value && typeof value.version === "string";
|
|
2179
2187
|
}
|
|
2180
|
-
function
|
|
2181
|
-
return
|
|
2188
|
+
function instancePath(baseUrl, id) {
|
|
2189
|
+
return `${baseUrl}/instances/${encodeURIComponent(id)}`;
|
|
2190
|
+
}
|
|
2191
|
+
function postJson(body) {
|
|
2192
|
+
return {
|
|
2193
|
+
method: "POST",
|
|
2194
|
+
headers: { "content-type": "application/json" },
|
|
2195
|
+
body: JSON.stringify(body)
|
|
2196
|
+
};
|
|
2182
2197
|
}
|
|
2183
2198
|
function computeClient(opts = {}) {
|
|
2184
2199
|
const baseUrl = resolveBaseUrl("compute", opts);
|
|
@@ -2189,26 +2204,33 @@ function computeClient(opts = {}) {
|
|
|
2189
2204
|
if (!isHealthBody(body)) throw new Error("malformed /health response from the compute emulator");
|
|
2190
2205
|
return body;
|
|
2191
2206
|
},
|
|
2192
|
-
async
|
|
2193
|
-
const body = await (await expectOk(await adminFetch(`${baseUrl}/
|
|
2194
|
-
if (!
|
|
2207
|
+
async createInstance(request) {
|
|
2208
|
+
const body = await (await expectOk(await adminFetch(`${baseUrl}/instances`, postJson(request), { retry: false }))).json();
|
|
2209
|
+
if (!isInstanceInfo(body)) throw new Error("malformed instance-create response from the compute emulator");
|
|
2195
2210
|
return body;
|
|
2196
2211
|
},
|
|
2197
|
-
async
|
|
2198
|
-
await expectOk(await adminFetch(`${baseUrl}/
|
|
2212
|
+
async listInstances() {
|
|
2213
|
+
const body = await (await expectOk(await adminFetch(`${baseUrl}/instances`))).json();
|
|
2214
|
+
if (!isInstanceInfoArray(body)) throw new Error("malformed instances listing from the compute emulator");
|
|
2215
|
+
return body;
|
|
2216
|
+
},
|
|
2217
|
+
async getInstance(id) {
|
|
2218
|
+
const res = await adminFetch(instancePath(baseUrl, id));
|
|
2219
|
+
if (res.status === 404) return void 0;
|
|
2220
|
+
const body = await (await expectOk(res)).json();
|
|
2221
|
+
if (!isInstanceInfo(body)) throw new Error("malformed instance response from the compute emulator");
|
|
2222
|
+
return body;
|
|
2223
|
+
},
|
|
2224
|
+
async putDeployment(id, deployment) {
|
|
2225
|
+
await expectOk(await adminFetch(`${instancePath(baseUrl, id)}/deployment`, {
|
|
2199
2226
|
method: "PUT",
|
|
2200
2227
|
headers: { "content-type": "application/json" },
|
|
2201
2228
|
body: JSON.stringify(deployment)
|
|
2202
2229
|
}));
|
|
2203
2230
|
},
|
|
2204
|
-
async
|
|
2205
|
-
const body = await (await expectOk(await adminFetch(`${baseUrl}/apps/${encodeSegment(app)}/services`))).json();
|
|
2206
|
-
if (!isServiceInfoArray(body)) throw new Error("malformed services listing from the compute emulator");
|
|
2207
|
-
return body;
|
|
2208
|
-
},
|
|
2209
|
-
async *followLogs(app, id, signal, opts) {
|
|
2231
|
+
async *followLogs(id, signal, opts) {
|
|
2210
2232
|
const tail = opts?.tail !== void 0 ? `&tail=${String(Math.max(0, Math.trunc(opts.tail)))}` : "";
|
|
2211
|
-
const body = (await expectOk(await adminFetch(`${baseUrl
|
|
2233
|
+
const body = (await expectOk(await adminFetch(`${instancePath(baseUrl, id)}/logs?follow=1${tail}`, signal ? { signal } : void 0))).body;
|
|
2212
2234
|
if (!body) return;
|
|
2213
2235
|
const reader = body.getReader();
|
|
2214
2236
|
const decoder = new TextDecoder();
|
|
@@ -2222,14 +2244,14 @@ function computeClient(opts = {}) {
|
|
|
2222
2244
|
await reader.cancel().catch(() => void 0);
|
|
2223
2245
|
}
|
|
2224
2246
|
},
|
|
2225
|
-
async
|
|
2226
|
-
await expectOk(await adminFetch(`${baseUrl}/
|
|
2247
|
+
async startInstances(ids) {
|
|
2248
|
+
await expectOk(await adminFetch(`${baseUrl}/instances/start`, postJson({ ids })));
|
|
2227
2249
|
},
|
|
2228
|
-
async
|
|
2229
|
-
await expectOk(await adminFetch(`${baseUrl}/
|
|
2250
|
+
async stopInstances(ids) {
|
|
2251
|
+
await expectOk(await adminFetch(`${baseUrl}/instances/stop`, postJson({ ids })));
|
|
2230
2252
|
},
|
|
2231
|
-
async
|
|
2232
|
-
await expectOk(await adminFetch(
|
|
2253
|
+
async deleteInstance(id) {
|
|
2254
|
+
await expectOk(await adminFetch(instancePath(baseUrl, id), { method: "DELETE" }));
|
|
2233
2255
|
}
|
|
2234
2256
|
};
|
|
2235
2257
|
}
|
|
@@ -5833,6 +5855,144 @@ function secretsStore(devDir) {
|
|
|
5833
5855
|
return createStore(path$1.join(devDir, "secrets.json"), isStringRecord, {}, 384);
|
|
5834
5856
|
}
|
|
5835
5857
|
/**
|
|
5858
|
+
* Which emulator instance backs each node of the app run from a directory.
|
|
5859
|
+
* `prisma dev` records it in `<directory>/.prisma-composer/dev/`, and every
|
|
5860
|
+
* Composer operation on an emulator instance goes through it. The emulators
|
|
5861
|
+
* know nothing about Composer: the labels written on create are for people
|
|
5862
|
+
* reading an emulator listing, and nothing here reads them back.
|
|
5863
|
+
*/
|
|
5864
|
+
const EMULATOR_INSTANCES_FILE = "emulator-instances.json";
|
|
5865
|
+
function isNodeInstance(value) {
|
|
5866
|
+
return typeof value === "object" && value !== null && "kind" in value && value.kind === "compute" && "id" in value && typeof value.id === "string" && "port" in value && typeof value.port === "number";
|
|
5867
|
+
}
|
|
5868
|
+
function isEmulatorInstances(value) {
|
|
5869
|
+
return typeof value === "object" && value !== null && "app" in value && typeof value.app === "string" && "nodes" in value && typeof value.nodes === "object" && value.nodes !== null && Object.values(value.nodes).every(isNodeInstance);
|
|
5870
|
+
}
|
|
5871
|
+
function directoryOf(devDir) {
|
|
5872
|
+
const normalized = path$1.normalize(devDir);
|
|
5873
|
+
return normalized.endsWith(DEV_DIR) ? normalized.slice(0, -DEV_DIR.length - 1) : normalized;
|
|
5874
|
+
}
|
|
5875
|
+
function store(devDir) {
|
|
5876
|
+
return createStore(path$1.join(devDir, EMULATOR_INSTANCES_FILE), isEmulatorInstances, void 0);
|
|
5877
|
+
}
|
|
5878
|
+
/** This app's nodes. A record left by another app run from the same directory counts as empty. */
|
|
5879
|
+
async function appNodes(scope) {
|
|
5880
|
+
const current = await store(scope.devDir).read();
|
|
5881
|
+
return current?.app === scope.app ? current.nodes : {};
|
|
5882
|
+
}
|
|
5883
|
+
async function updateNodes(scope, mutate) {
|
|
5884
|
+
await store(scope.devDir).update((current) => {
|
|
5885
|
+
const nodes = { ...current?.app === scope.app ? current.nodes : {} };
|
|
5886
|
+
mutate(nodes);
|
|
5887
|
+
return {
|
|
5888
|
+
app: scope.app,
|
|
5889
|
+
nodes
|
|
5890
|
+
};
|
|
5891
|
+
});
|
|
5892
|
+
}
|
|
5893
|
+
/**
|
|
5894
|
+
* A directory has one dev instance. When the record names another app, that
|
|
5895
|
+
* app's compute instances are deleted and the record starts over for this
|
|
5896
|
+
* one. Returns the previous app's name when it was retired.
|
|
5897
|
+
*/
|
|
5898
|
+
async function retirePreviousApp(scope) {
|
|
5899
|
+
const current = await store(scope.devDir).read();
|
|
5900
|
+
if (current === void 0 || current.app === scope.app) return void 0;
|
|
5901
|
+
const client = computeClient();
|
|
5902
|
+
for (const instance of Object.values(current.nodes)) await client.deleteInstance(instance.id);
|
|
5903
|
+
await store(scope.devDir).update(() => ({
|
|
5904
|
+
app: scope.app,
|
|
5905
|
+
nodes: {}
|
|
5906
|
+
}));
|
|
5907
|
+
return current.app;
|
|
5908
|
+
}
|
|
5909
|
+
/** Each compute node of the app, with the instance that backs it. */
|
|
5910
|
+
async function computeNodeInstances(scope) {
|
|
5911
|
+
return Object.entries(await appNodes(scope)).map(([address, instance]) => ({
|
|
5912
|
+
address,
|
|
5913
|
+
instance
|
|
5914
|
+
}));
|
|
5915
|
+
}
|
|
5916
|
+
/** The compute instance recorded for `address`; undefined when there is none. */
|
|
5917
|
+
async function mappedComputeInstance(scope, address) {
|
|
5918
|
+
return (await appNodes(scope))[address];
|
|
5919
|
+
}
|
|
5920
|
+
/**
|
|
5921
|
+
* The instance that runs the service at `address`. The recorded instance is
|
|
5922
|
+
* reused while the Compute emulator still has it. Otherwise a new one is
|
|
5923
|
+
* created, requesting the recorded port, and recorded.
|
|
5924
|
+
*/
|
|
5925
|
+
async function ensureComputeInstance(scope, address) {
|
|
5926
|
+
const client = computeClient();
|
|
5927
|
+
const recorded = await mappedComputeInstance(scope, address);
|
|
5928
|
+
if (recorded && await client.getInstance(recorded.id)) return { instance: recorded };
|
|
5929
|
+
const created = await client.createInstance({
|
|
5930
|
+
labels: {
|
|
5931
|
+
"composer.directory": directoryOf(scope.devDir),
|
|
5932
|
+
"composer.app": scope.app,
|
|
5933
|
+
"composer.address": address
|
|
5934
|
+
},
|
|
5935
|
+
...recorded ? { port: recorded.port } : {}
|
|
5936
|
+
});
|
|
5937
|
+
const instance = {
|
|
5938
|
+
kind: "compute",
|
|
5939
|
+
id: created.id,
|
|
5940
|
+
port: created.port
|
|
5941
|
+
};
|
|
5942
|
+
await updateNodes(scope, (nodes) => {
|
|
5943
|
+
nodes[address] = instance;
|
|
5944
|
+
});
|
|
5945
|
+
if (recorded && recorded.port !== created.port) return {
|
|
5946
|
+
instance,
|
|
5947
|
+
portChange: {
|
|
5948
|
+
address,
|
|
5949
|
+
previous: recorded.port,
|
|
5950
|
+
given: created.port
|
|
5951
|
+
}
|
|
5952
|
+
};
|
|
5953
|
+
return { instance };
|
|
5954
|
+
}
|
|
5955
|
+
/** The emulator's view of the instance recorded for `address`; undefined when either is missing. */
|
|
5956
|
+
async function liveComputeInstance(scope, address) {
|
|
5957
|
+
const recorded = await mappedComputeInstance(scope, address);
|
|
5958
|
+
return recorded ? computeClient().getInstance(recorded.id) : void 0;
|
|
5959
|
+
}
|
|
5960
|
+
/** Deletes the instance recorded for `address` and forgets it. */
|
|
5961
|
+
async function deleteComputeInstance(scope, address) {
|
|
5962
|
+
const recorded = await mappedComputeInstance(scope, address);
|
|
5963
|
+
if (!recorded) return;
|
|
5964
|
+
await computeClient().deleteInstance(recorded.id);
|
|
5965
|
+
await updateNodes(scope, (nodes) => {
|
|
5966
|
+
delete nodes[address];
|
|
5967
|
+
});
|
|
5968
|
+
}
|
|
5969
|
+
/**
|
|
5970
|
+
* `--fresh`: deletes every instance the directory's record names, whichever
|
|
5971
|
+
* app wrote it, then empties the record. Teardown runs before the emulators
|
|
5972
|
+
* hook, so the record may still name the app run here before.
|
|
5973
|
+
*/
|
|
5974
|
+
async function deleteRecordedInstances(devDir) {
|
|
5975
|
+
const current = await store(devDir).read();
|
|
5976
|
+
if (current === void 0) return;
|
|
5977
|
+
const client = computeClient();
|
|
5978
|
+
for (const instance of Object.values(current.nodes)) await client.deleteInstance(instance.id);
|
|
5979
|
+
await store(devDir).update(() => ({
|
|
5980
|
+
app: current.app,
|
|
5981
|
+
nodes: {}
|
|
5982
|
+
}));
|
|
5983
|
+
}
|
|
5984
|
+
/**
|
|
5985
|
+
* The Compute emulator's failure messages name instances by id; `prisma dev`
|
|
5986
|
+
* names services by address. Rewrites a plain `Error`'s message and leaves
|
|
5987
|
+
* any other error, such as a structured one, untouched.
|
|
5988
|
+
*/
|
|
5989
|
+
function withServiceAddresses(error, nodes) {
|
|
5990
|
+
if (!(error instanceof Error) || error.constructor !== Error) return error;
|
|
5991
|
+
let message = error.message.replace("deploy it with listens: false", "declare it with compute({ listens: false })");
|
|
5992
|
+
for (const { address, instance } of nodes) message = message.replaceAll(`instance "${instance.id}"`, `service "${address}"`);
|
|
5993
|
+
return message === error.message ? error : new Error(message, { cause: error });
|
|
5994
|
+
}
|
|
5995
|
+
/**
|
|
5836
5996
|
* Local compute-cluster providers: upstream alchemy's `Prisma.App` and
|
|
5837
5997
|
* `Prisma.Deployment` become clients of the machine-scoped Compute emulator;
|
|
5838
5998
|
* `Prisma.EnvironmentVariable` becomes a row in the dev env store;
|
|
@@ -5937,16 +6097,6 @@ function readComputeManifest(artifactDir) {
|
|
|
5937
6097
|
listens: parsed.listens !== false
|
|
5938
6098
|
};
|
|
5939
6099
|
}
|
|
5940
|
-
/**
|
|
5941
|
-
* A service address is dot-separated (`"orders.service"`) but the emulator's
|
|
5942
|
-
* `<id>` segment must match `/^[a-z0-9][a-z0-9-]*$/`: disallowed characters
|
|
5943
|
-
* become dashes, runs collapse. The real address still rides the deployment
|
|
5944
|
-
* body's `address` field untouched, so listings show it verbatim.
|
|
5945
|
-
*/
|
|
5946
|
-
function slugServiceId(address) {
|
|
5947
|
-
const slug = address.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
5948
|
-
return slug.length > 0 ? slug : "svc";
|
|
5949
|
-
}
|
|
5950
6100
|
async function materializeEnv(devDir, address, port) {
|
|
5951
6101
|
const env = scopedEnvRows(await envStore(devDir).read(), address);
|
|
5952
6102
|
env[servicePortEnvKey(address)] = JSON.stringify(port);
|
|
@@ -5956,21 +6106,30 @@ async function materializeEnv(devDir, address, port) {
|
|
|
5956
6106
|
if (process.env["HOME"] !== void 0) env["HOME"] = process.env["HOME"];
|
|
5957
6107
|
return env;
|
|
5958
6108
|
}
|
|
6109
|
+
function appScopeOf(input) {
|
|
6110
|
+
return {
|
|
6111
|
+
devDir: input.devDir,
|
|
6112
|
+
app: appNameOf(input.container)
|
|
6113
|
+
};
|
|
6114
|
+
}
|
|
6115
|
+
function localUrl(port) {
|
|
6116
|
+
return `http://localhost:${String(port)}`;
|
|
6117
|
+
}
|
|
5959
6118
|
/**
|
|
5960
|
-
* `Prisma.App` → the Compute emulator:
|
|
5961
|
-
*
|
|
5962
|
-
*
|
|
5963
|
-
*
|
|
5964
|
-
*
|
|
6119
|
+
* `Prisma.App` → the Compute emulator: finds or creates the instance that
|
|
6120
|
+
* runs the service, which holds the service's port. The app id here IS the
|
|
6121
|
+
* service's own address. An App whose instance was re-created on another
|
|
6122
|
+
* port is updated, so its dependents get the new URL. Deleting an App (its
|
|
6123
|
+
* service was removed) deletes its instance.
|
|
5965
6124
|
*/
|
|
5966
6125
|
function LocalAppProvider(input) {
|
|
5967
6126
|
return Provider.effect(App, Effect.succeed({
|
|
5968
6127
|
list: () => Effect.succeed([]),
|
|
5969
6128
|
reconcile: ({ id, news }) => Effect.tryPromise({
|
|
5970
6129
|
try: async () => {
|
|
5971
|
-
const appName = appNameOf(input.container);
|
|
5972
6130
|
const name = news.displayName ?? id;
|
|
5973
|
-
const {
|
|
6131
|
+
const { instance } = await ensureComputeInstance(appScopeOf(input), name);
|
|
6132
|
+
const url = localUrl(instance.port);
|
|
5974
6133
|
return {
|
|
5975
6134
|
appId: name,
|
|
5976
6135
|
name,
|
|
@@ -5985,25 +6144,43 @@ function LocalAppProvider(input) {
|
|
|
5985
6144
|
},
|
|
5986
6145
|
catch: (cause) => cause
|
|
5987
6146
|
}),
|
|
5988
|
-
|
|
6147
|
+
diff: ({ output }) => Effect.tryPromise({
|
|
6148
|
+
try: async () => {
|
|
6149
|
+
if (output === void 0) return void 0;
|
|
6150
|
+
if ((await liveComputeInstance(appScopeOf(input), output.name))?.url === output.appEndpointDomain) return void 0;
|
|
6151
|
+
return { action: "update" };
|
|
6152
|
+
},
|
|
6153
|
+
catch: (cause) => cause
|
|
6154
|
+
}),
|
|
6155
|
+
delete: ({ output }) => Effect.tryPromise({
|
|
6156
|
+
try: () => deleteComputeInstance(appScopeOf(input), output.name),
|
|
6157
|
+
catch: (cause) => cause
|
|
6158
|
+
}),
|
|
5989
6159
|
read: ({ output }) => Effect.succeed(output)
|
|
5990
6160
|
}));
|
|
5991
6161
|
}
|
|
5992
6162
|
/**
|
|
5993
|
-
*
|
|
5994
|
-
* before Alchemy applies the `App` resources concurrently. A new
|
|
6163
|
+
* Ensures each service's emulator instance one at a time, in the order given,
|
|
6164
|
+
* before Alchemy applies the `App` resources concurrently. A new instance gets
|
|
5995
6165
|
* the smallest free port, so on a fresh start the ports follow this order; a
|
|
5996
|
-
* service that already has
|
|
5997
|
-
* `App` resources' `displayName`s.
|
|
6166
|
+
* service that already has an instance keeps its port. `serviceAppNames` are
|
|
6167
|
+
* the `App` resources' `displayName`s. Returns the services whose instance had
|
|
6168
|
+
* to be re-created and could not get its remembered port back.
|
|
5998
6169
|
*/
|
|
5999
|
-
async function
|
|
6000
|
-
const
|
|
6170
|
+
async function ensureServiceInstances(container, devDir, serviceAppNames) {
|
|
6171
|
+
const scope = {
|
|
6172
|
+
devDir,
|
|
6173
|
+
app: appNameOf(container)
|
|
6174
|
+
};
|
|
6175
|
+
const changes = [];
|
|
6001
6176
|
for (const service of serviceAppNames) try {
|
|
6002
|
-
await
|
|
6177
|
+
const { portChange } = await ensureComputeInstance(scope, service);
|
|
6178
|
+
if (portChange) changes.push(portChange);
|
|
6003
6179
|
} catch (cause) {
|
|
6004
6180
|
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
6005
|
-
throw new Error(`
|
|
6181
|
+
throw new Error(`ensuring the local service for service "${service}" failed: ${reason}`, { cause });
|
|
6006
6182
|
}
|
|
6183
|
+
return changes;
|
|
6007
6184
|
}
|
|
6008
6185
|
/** `Prisma.EnvironmentVariable` → a key/value row in `<devDir>/env.json`. Upstream's value is `Redacted`; env.json holds the plain string the child process is given. */
|
|
6009
6186
|
function LocalEnvironmentVariableProvider(input) {
|
|
@@ -6043,8 +6220,8 @@ function LocalEnvironmentVariableProvider(input) {
|
|
|
6043
6220
|
}));
|
|
6044
6221
|
}
|
|
6045
6222
|
/**
|
|
6046
|
-
* `Prisma.Deployment` → unpacks the artifact once per hash,
|
|
6047
|
-
*
|
|
6223
|
+
* `Prisma.Deployment` → unpacks the artifact once per hash, finds the
|
|
6224
|
+
* service's emulator instance and its port, materializes the child's full env (env store +
|
|
6048
6225
|
* secrets + the port override + `PATH`/`HOME`), and puts the deployment — the
|
|
6049
6226
|
* emulator (re)starts the child only when the hash or env actually changed.
|
|
6050
6227
|
* `portMapping` is ignored on purpose: the emulator owns port allocation, and
|
|
@@ -6056,24 +6233,27 @@ function LocalDeploymentProvider(input) {
|
|
|
6056
6233
|
list: () => Effect.succeed([]),
|
|
6057
6234
|
reconcile: ({ news }) => Effect.tryPromise({
|
|
6058
6235
|
try: async () => {
|
|
6059
|
-
const app = appNameOf(input.container);
|
|
6060
6236
|
const appId = appIdOfInput(news.app);
|
|
6061
|
-
const emulatorId = slugServiceId(appId);
|
|
6062
6237
|
if (news.artifactPath === void 0) throw new Error("local Deployment requires an artifactPath — nothing to run.");
|
|
6063
6238
|
const artifactHash = await artifactSha256(news.artifactPath);
|
|
6064
6239
|
const artifactDir = path$1.join(input.devDir, "artifacts", artifactHash);
|
|
6065
6240
|
if (!fs$2.existsSync(artifactDir)) extractComputeArtifact(news.artifactPath, artifactDir);
|
|
6066
6241
|
const { address, listens } = readComputeManifest(artifactDir);
|
|
6067
|
-
const {
|
|
6068
|
-
const env = await materializeEnv(input.devDir, address, port);
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
})
|
|
6242
|
+
const { instance } = await ensureComputeInstance(appScopeOf(input), appId);
|
|
6243
|
+
const env = await materializeEnv(input.devDir, address, instance.port);
|
|
6244
|
+
try {
|
|
6245
|
+
await computeClient().putDeployment(instance.id, {
|
|
6246
|
+
artifactDir,
|
|
6247
|
+
artifactHash,
|
|
6248
|
+
env,
|
|
6249
|
+
listens
|
|
6250
|
+
});
|
|
6251
|
+
} catch (error) {
|
|
6252
|
+
throw withServiceAddresses(error, [{
|
|
6253
|
+
address,
|
|
6254
|
+
instance
|
|
6255
|
+
}]);
|
|
6256
|
+
}
|
|
6077
6257
|
return {
|
|
6078
6258
|
deploymentId: artifactHash,
|
|
6079
6259
|
appId,
|
|
@@ -6081,12 +6261,20 @@ function LocalDeploymentProvider(input) {
|
|
|
6081
6261
|
status: "running",
|
|
6082
6262
|
previewDomain: null,
|
|
6083
6263
|
artifactHash,
|
|
6084
|
-
appEndpointDomain:
|
|
6264
|
+
appEndpointDomain: localUrl(instance.port),
|
|
6085
6265
|
createdAt: DEV_TIMESTAMP
|
|
6086
6266
|
};
|
|
6087
6267
|
},
|
|
6088
6268
|
catch: (cause) => cause
|
|
6089
6269
|
}),
|
|
6270
|
+
diff: ({ output }) => Effect.tryPromise({
|
|
6271
|
+
try: async () => {
|
|
6272
|
+
if (output === void 0) return void 0;
|
|
6273
|
+
if ((await liveComputeInstance(appScopeOf(input), output.appId))?.artifactHash === output.artifactHash) return void 0;
|
|
6274
|
+
return { action: "update" };
|
|
6275
|
+
},
|
|
6276
|
+
catch: (cause) => cause
|
|
6277
|
+
}),
|
|
6090
6278
|
delete: () => Effect.void
|
|
6091
6279
|
}));
|
|
6092
6280
|
}
|
|
@@ -6275,7 +6463,7 @@ const RELIST_INTERVAL_MS = 2e3;
|
|
|
6275
6463
|
* dropped (an emulator restart shows a gap in that service's stream, never a
|
|
6276
6464
|
* dead session).
|
|
6277
6465
|
*/
|
|
6278
|
-
async function* mergedLogs(
|
|
6466
|
+
async function* mergedLogs(scope, signal, tail) {
|
|
6279
6467
|
const client = computeClient();
|
|
6280
6468
|
const followed = /* @__PURE__ */ new Set();
|
|
6281
6469
|
const backlogged = /* @__PURE__ */ new Set();
|
|
@@ -6294,7 +6482,7 @@ async function* mergedLogs(app, signal, tail) {
|
|
|
6294
6482
|
(async () => {
|
|
6295
6483
|
try {
|
|
6296
6484
|
let buffer = "";
|
|
6297
|
-
for await (const chunk of client.followLogs(
|
|
6485
|
+
for await (const chunk of client.followLogs(id, signal, { tail: followTail })) {
|
|
6298
6486
|
buffer += chunk;
|
|
6299
6487
|
let newlineAt = buffer.indexOf("\n");
|
|
6300
6488
|
while (newlineAt !== -1) {
|
|
@@ -6312,8 +6500,7 @@ async function* mergedLogs(app, signal, tail) {
|
|
|
6312
6500
|
})();
|
|
6313
6501
|
};
|
|
6314
6502
|
const relist = async () => {
|
|
6315
|
-
const
|
|
6316
|
-
for (const svc of services) follow(svc.id, svc.address);
|
|
6503
|
+
for (const { address, instance } of await computeNodeInstances(scope)) follow(instance.id, address);
|
|
6317
6504
|
};
|
|
6318
6505
|
await relist();
|
|
6319
6506
|
const timer = setInterval(() => void relist(), RELIST_INTERVAL_MS);
|
|
@@ -6338,18 +6525,31 @@ async function* mergedLogs(app, signal, tail) {
|
|
|
6338
6525
|
}
|
|
6339
6526
|
}
|
|
6340
6527
|
async function devAttach(input) {
|
|
6341
|
-
const
|
|
6528
|
+
const scope = {
|
|
6529
|
+
devDir: input.devDir,
|
|
6530
|
+
app: prismaCloudContainerOf(input.container).input.appName
|
|
6531
|
+
};
|
|
6342
6532
|
const client = computeClient();
|
|
6343
6533
|
return {
|
|
6344
|
-
startServices: () =>
|
|
6534
|
+
startServices: async () => {
|
|
6535
|
+
const nodes = await computeNodeInstances(scope);
|
|
6536
|
+
try {
|
|
6537
|
+
await client.startInstances(nodes.map(({ instance }) => instance.id));
|
|
6538
|
+
} catch (error) {
|
|
6539
|
+
throw withServiceAddresses(error, nodes);
|
|
6540
|
+
}
|
|
6541
|
+
},
|
|
6345
6542
|
endpoints: async () => {
|
|
6346
|
-
return (await
|
|
6347
|
-
address
|
|
6348
|
-
url:
|
|
6543
|
+
return (await computeNodeInstances(scope)).map(({ address, instance }) => ({
|
|
6544
|
+
address,
|
|
6545
|
+
url: `http://localhost:${String(instance.port)}`
|
|
6349
6546
|
}));
|
|
6350
6547
|
},
|
|
6351
|
-
logs: (signal, opts) => mergedLogs(
|
|
6352
|
-
stopServices: () =>
|
|
6548
|
+
logs: (signal, opts) => mergedLogs(scope, signal, opts?.tail ?? 0),
|
|
6549
|
+
stopServices: async () => {
|
|
6550
|
+
const nodes = await computeNodeInstances(scope);
|
|
6551
|
+
await client.stopInstances(nodes.map(({ instance }) => instance.id));
|
|
6552
|
+
}
|
|
6353
6553
|
};
|
|
6354
6554
|
}
|
|
6355
6555
|
const LOCAL_PROJECT_ID = "local";
|
|
@@ -6384,7 +6584,13 @@ function serviceAppNames(input) {
|
|
|
6384
6584
|
async function runDevEmulators(input) {
|
|
6385
6585
|
const { url: computeUrl } = await ensureDaemon("compute", daemonEntry("compute"));
|
|
6386
6586
|
console.log(`[dev] compute emulator ready at ${computeUrl}`);
|
|
6387
|
-
await
|
|
6587
|
+
const retired = await retirePreviousApp({
|
|
6588
|
+
devDir: input.devDir,
|
|
6589
|
+
app: appNameOf(input.container)
|
|
6590
|
+
});
|
|
6591
|
+
if (retired !== void 0) console.log(`[dev] removed the local services of "${retired}", the app run here before`);
|
|
6592
|
+
const portChanges = await ensureServiceInstances(input.container, input.devDir, serviceAppNames(input));
|
|
6593
|
+
for (const { address, previous, given } of portChanges) console.log(`[dev] service "${address}" now uses port ${String(given)}: its previous port ${String(previous)} is in use`);
|
|
6388
6594
|
if (usesBuckets(input)) {
|
|
6389
6595
|
const { url: bucketsUrl } = await ensureDaemon("buckets", daemonEntry("buckets"));
|
|
6390
6596
|
console.log(`[dev] buckets emulator ready at ${bucketsUrl}`);
|
|
@@ -6441,13 +6647,14 @@ async function tolerateUnreachable(action) {
|
|
|
6441
6647
|
async function runDevTeardown(input) {
|
|
6442
6648
|
const app = prismaCloudContainerOf(input.container).input.appName;
|
|
6443
6649
|
const cwd = process.cwd();
|
|
6650
|
+
const devDir = `${cwd}/${DEV_DIR}`;
|
|
6444
6651
|
await tolerateUnreachable(() => postgresClient().deleteApp(app));
|
|
6445
6652
|
try {
|
|
6446
6653
|
await ensureDaemon("compute", daemonEntry("compute"));
|
|
6447
|
-
await
|
|
6654
|
+
await deleteRecordedInstances(devDir);
|
|
6448
6655
|
} finally {
|
|
6449
6656
|
await tolerateUnreachable(() => bucketsClient().deleteApp(app));
|
|
6450
|
-
removeLocalPaths([
|
|
6657
|
+
removeLocalPaths([devDir, `${cwd}/.alchemy/state/${app}/dev`]);
|
|
6451
6658
|
}
|
|
6452
6659
|
}
|
|
6453
6660
|
/** `localTargetProviders()`'s `ProviderCollection` doesn't structurally unify with Alchemy's inferred providers Layer (a @internal/lowering typings gap); it satisfies it at runtime — mirrors `control/extension.ts`'s own `asProvidersLayer`, duplicated rather than shared so this module imports nothing from `./control`. */
|