@prisma/composer-prisma-cloud 0.35.0-dev.3 → 0.35.0-dev.4

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.
@@ -1,5 +1,5 @@
1
1
  import { j as blindCast$1, k as stashRows } from "./serializer-A2fOAuiz-BHwRF-Cz.mjs";
2
- import { D as isWithin, S as Providers, T as ServiceKeyProvider, c as PgWarmProvider, d as S3CredentialsProvider, f as collectPreflightNames, g as prismaCloudContainerOf, l as PrismaCloudContainer, m as deserialize, n as GeneratedParamProvider, r as LocalOrmMigrationProvider, x as mintKeyPair, y as resourceId } from "./s3-credentials-resource-C-8pDHBj-Ygt1f2bm.mjs";
2
+ import { D as isWithin, S as Providers, T as ServiceKeyProvider, c as PgWarmProvider, d as S3CredentialsProvider, f as collectPreflightNames, g as prismaCloudContainerOf, l as PrismaCloudContainer, m as deserialize, n as GeneratedParamProvider, r as LocalOrmMigrationProvider, x as mintKeyPair, y as resourceId } from "./s3-credentials-resource-C-8pDHBj-iR0k6man.mjs";
3
3
  import { createRequire } from "node:module";
4
4
  import { blindCast } from "@prisma/composer/casts";
5
5
  import * as Effect from "effect/Effect";
@@ -19,7 +19,7 @@ import { App, Bucket, BucketAccessKey, Connection, Database, Deployment, Environ
19
19
  import * as fs$1$1 from "node:fs/promises";
20
20
  import ro from "node:fs/promises";
21
21
  import { spawn } from "node:child_process";
22
- import { DEV_DIR } from "@prisma/composer/config";
22
+ import { DEV_DIR, DEV_ENVIRONMENT_NAME_FILE, devEnvironmentDir } from "@prisma/composer/config";
23
23
  import { fileURLToPath } from "node:url";
24
24
  import net from "node:net";
25
25
  import ar, { dirname, parse } from "path";
@@ -5691,18 +5691,14 @@ var To = (s) => {
5691
5691
  function containerNotResolvedError() {
5692
5692
  return /* @__PURE__ */ new Error("local dev: no container was resolved for this provider — the extension's `localTarget.container` descriptor did not run before converge.");
5693
5693
  }
5694
- /**
5695
- * Every local provider's emulator app namespace is its resolved local
5696
- * container's `input.appName` — a field already on core's generic
5697
- * `ContainerInstance` (no narrowing to a target-specific container class
5698
- * needed for it). Narrowing to e.g. the prisma-cloud target's
5699
- * `PrismaCloudContainer` is not available here: `@internal/local-target`
5700
- * sits below the extensions layer and cannot import it (ADR-0028's layer
5701
- * order).
5702
- */
5703
- function appNameOf(container) {
5704
- if (container === void 0) throw containerNotResolvedError();
5705
- return container.input.appName;
5694
+ /** The app and environment local dev runs, from the extension's resolved container. */
5695
+ function appScopeOf(input) {
5696
+ if (input.container === void 0) throw containerNotResolvedError();
5697
+ return {
5698
+ devDir: input.devDir,
5699
+ app: input.container.input.appName,
5700
+ environment: input.container.input.environment
5701
+ };
5706
5702
  }
5707
5703
  /**
5708
5704
  * The shared dev-instance store (local-dev spec § 4): the JSON files under
@@ -5774,11 +5770,7 @@ function withAppPrefix(app, nodes) {
5774
5770
  }
5775
5771
  /** A record written before ADR-0057, with every node keyed by its full address. */
5776
5772
  function withFullAddressKeys(record) {
5777
- const apps = Object.fromEntries(Object.entries(record.apps).map(([app, { nodes }]) => [app, { nodes: withAppPrefix(app, nodes) }]));
5778
- return {
5779
- current: record.current,
5780
- apps
5781
- };
5773
+ return { apps: Object.fromEntries(Object.entries(record.apps).map(([app, { nodes }]) => [app, { nodes: withAppPrefix(app, nodes) }])) };
5782
5774
  }
5783
5775
  /** Where local dev loads `@prisma/dev` from, for the Postgres emulator. */
5784
5776
  function noPrismaDevError(cause) {
@@ -5801,8 +5793,9 @@ function resolvePrismaDevModulePath() {
5801
5793
  }
5802
5794
  }
5803
5795
  /**
5804
- * Which emulator instance backs each node of each app run from a directory.
5805
- * `prisma dev` records it in `<directory>/.prisma-composer/dev/`, and every
5796
+ * Which emulator instance backs each node of each app run in one environment
5797
+ * from a directory. `prisma dev` records it in the environment's folder,
5798
+ * `<directory>/.prisma-composer/dev/<environment>/`, and every
5806
5799
  * Composer operation on an emulator instance goes through it. The emulators
5807
5800
  * know nothing about Composer: the labels written on create are for people
5808
5801
  * reading an emulator listing, and nothing here reads them back.
@@ -5821,7 +5814,7 @@ function isAppInstances(value) {
5821
5814
  return typeof value === "object" && value !== null && "nodes" in value && isNodes(value.nodes);
5822
5815
  }
5823
5816
  function isEmulatorInstances(value) {
5824
- return typeof value === "object" && value !== null && "current" in value && typeof value.current === "string" && "apps" in value && typeof value.apps === "object" && value.apps !== null && Object.values(value.apps).every(isAppInstances);
5817
+ return typeof value === "object" && value !== null && "apps" in value && typeof value.apps === "object" && value.apps !== null && Object.values(value.apps).every(isAppInstances);
5825
5818
  }
5826
5819
  function isEarlierServiceNode(value) {
5827
5820
  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";
@@ -5832,6 +5825,8 @@ function isSingleAppRecord(value) {
5832
5825
  function isStoredRecord(value) {
5833
5826
  return isEmulatorInstances(value) || isSingleAppRecord(value);
5834
5827
  }
5828
+ /** Format 2 differs from the current one only by its `current` app, which is dropped. */
5829
+ const FULL_ADDRESS_FORMAT_VERSIONS = /* @__PURE__ */ new Set([2, 3]);
5835
5830
  /**
5836
5831
  * A record without `formatVersion` was written before node keys were full addresses, so each of
5837
5832
  * its keys reads with the app's name in front; a single-app record also reads as a record holding
@@ -5840,26 +5835,20 @@ function isStoredRecord(value) {
5840
5835
  function normalized(stored) {
5841
5836
  if (stored === void 0) return void 0;
5842
5837
  if (isEmulatorInstances(stored)) {
5843
- if (stored.formatVersion === 2) return {
5844
- current: stored.current,
5845
- apps: stored.apps
5846
- };
5847
- return withFullAddressKeys(stored);
5838
+ if (stored.formatVersion !== void 0 && FULL_ADDRESS_FORMAT_VERSIONS.has(stored.formatVersion)) return { apps: stored.apps };
5839
+ return withFullAddressKeys({ apps: stored.apps });
5848
5840
  }
5849
5841
  const nodes = Object.fromEntries(Object.entries(stored.nodes).map(([address, { id, port }]) => [address, {
5850
5842
  kind: "service",
5851
5843
  id,
5852
5844
  port
5853
5845
  }]));
5854
- return withFullAddressKeys({
5855
- current: stored.app,
5856
- apps: { [stored.app]: { nodes } }
5857
- });
5846
+ return withFullAddressKeys({ apps: { [stored.app]: { nodes } } });
5858
5847
  }
5859
- /** The project directory that holds `devDir` (`<directory>/.prisma-composer/dev`). */
5848
+ /** The project directory that holds an environment's folder (`<directory>/.prisma-composer/dev/<environment>`). */
5860
5849
  function directoryOf(devDir) {
5861
- const normalizedDir = path$1.normalize(devDir);
5862
- return normalizedDir.endsWith(DEV_DIR) ? normalizedDir.slice(0, -DEV_DIR.length - 1) : normalizedDir;
5850
+ const devRoot = path$1.dirname(path$1.normalize(devDir));
5851
+ return devRoot.endsWith(DEV_DIR) ? devRoot.slice(0, -DEV_DIR.length - 1) : devRoot;
5863
5852
  }
5864
5853
  function store(devDir) {
5865
5854
  return createStore(path$1.join(devDir, EMULATOR_INSTANCES_FILE), isStoredRecord, void 0);
@@ -5869,13 +5858,13 @@ async function readRecord(devDir) {
5869
5858
  }
5870
5859
  async function updateRecord(devDir, mutate) {
5871
5860
  await store(devDir).update((stored) => ({
5872
- formatVersion: 2,
5861
+ formatVersion: 3,
5873
5862
  ...mutate(normalized(stored))
5874
5863
  }));
5875
5864
  }
5876
- /** The record `prisma dev` left in `directory`; undefined when it has not run there. */
5877
- function readEmulatorInstances(directory) {
5878
- return readRecord(path$1.join(directory, DEV_DIR));
5865
+ /** The record `prisma dev` left in an environment's folder; undefined when it has not run there. */
5866
+ function readEmulatorInstances(devDir) {
5867
+ return readRecord(devDir);
5879
5868
  }
5880
5869
  async function appNodes$1(scope) {
5881
5870
  return (await readRecord(scope.devDir))?.apps[scope.app]?.nodes ?? {};
@@ -5884,19 +5873,17 @@ async function updateNodes(scope, mutate) {
5884
5873
  await updateRecord(scope.devDir, (record) => {
5885
5874
  const nodes = { ...record?.apps[scope.app]?.nodes ?? {} };
5886
5875
  mutate(nodes);
5887
- return {
5888
- current: record?.current ?? scope.app,
5889
- apps: {
5890
- ...record?.apps,
5891
- [scope.app]: { nodes }
5892
- }
5893
- };
5876
+ return { apps: {
5877
+ ...record?.apps,
5878
+ [scope.app]: { nodes }
5879
+ } };
5894
5880
  });
5895
5881
  }
5896
5882
  function labelsFor(scope, address) {
5897
5883
  return {
5898
5884
  "composer.directory": directoryOf(scope.devDir),
5899
5885
  "composer.app": scope.app,
5886
+ "composer.environment": scope.environment,
5900
5887
  "composer.address": address
5901
5888
  };
5902
5889
  }
@@ -5942,25 +5929,6 @@ async function stopRunning(kind, ids) {
5942
5929
  await client.stopInstances(ids);
5943
5930
  return listed.filter((i) => i.status === "running").map((i) => i.id);
5944
5931
  }
5945
- /**
5946
- * Makes `scope.app` the directory's current app. When another app ran here
5947
- * last, its service processes and database servers are stopped and its
5948
- * instances stay recorded, so running it again resumes them with their data
5949
- * and ports. Returns the previous app's name when it changed.
5950
- */
5951
- async function selectApp(scope) {
5952
- const record = await readRecord(scope.devDir);
5953
- const previous = record?.current;
5954
- if (previous !== void 0 && previous !== scope.app) await stopNodes(record?.apps[previous]?.nodes ?? {});
5955
- await updateRecord(scope.devDir, (latest) => ({
5956
- current: scope.app,
5957
- apps: {
5958
- ...latest?.apps,
5959
- [scope.app]: latest?.apps[scope.app] ?? { nodes: {} }
5960
- }
5961
- }));
5962
- return previous !== void 0 && previous !== scope.app ? previous : void 0;
5963
- }
5964
5932
  /** Each node of the app of `kind`, with the instance that backs it. */
5965
5933
  async function nodeInstancesOf(scope, kind) {
5966
5934
  return Object.entries(await appNodes$1(scope)).flatMap(([address, instance]) => isOfKind(instance, kind) ? [{
@@ -6043,14 +6011,16 @@ async function ensureDatabaseInstance(scope, address, prismaDevModulePath) {
6043
6011
  }
6044
6012
  /**
6045
6013
  * The bucket for `address`. The recorded bucket is reused while the bucket
6046
- * emulator still has it. Otherwise a new one is created over `dir` and
6047
- * recorded; the objects already in `dir` are its objects.
6014
+ * emulator still has it over `dir`. Otherwise a new one is created over `dir`
6015
+ * and recorded, replacing a recorded one over another directory; the objects
6016
+ * already in `dir` are its objects.
6048
6017
  */
6049
6018
  async function ensureBucketInstance(scope, address, dir) {
6050
6019
  const client = bucketsClient();
6051
6020
  const recorded = await recordedInstance(scope, address, "bucket");
6052
6021
  const existing = recorded ? await client.getInstance(recorded.id) : void 0;
6053
- if (existing) return existing;
6022
+ if (existing?.dir === dir) return existing;
6023
+ if (existing) await client.deleteInstance(existing.id);
6054
6024
  const created = await client.createInstance({
6055
6025
  labels: labelsFor(scope, address),
6056
6026
  dir
@@ -6092,18 +6062,16 @@ async function recordedKinds(scope) {
6092
6062
  }
6093
6063
  /**
6094
6064
  * `--fresh`: deletes the app's recorded instances of every kind and its
6095
- * entry. Other apps' entries are left alone. A bucket's objects live in the
6096
- * dev directory, which `--fresh` removes separately.
6065
+ * entry. Other apps' entries and other environments are left alone. A
6066
+ * bucket's objects live in the environment's folder, which `--fresh` removes
6067
+ * separately.
6097
6068
  */
6098
6069
  async function deleteAppInstances(scope) {
6099
6070
  for (const instance of Object.values(await appNodes$1(scope))) await deleteInstance(instance);
6100
6071
  await updateRecord(scope.devDir, (record) => {
6101
6072
  const apps = { ...record?.apps };
6102
6073
  delete apps[scope.app];
6103
- return {
6104
- current: record?.current ?? scope.app,
6105
- apps
6106
- };
6074
+ return { apps };
6107
6075
  });
6108
6076
  }
6109
6077
  /** When `prisma dev` ends: stops the app's service processes and database servers, keeping their data and ports. */
@@ -6148,21 +6116,15 @@ function bucketIdOfInput(value) {
6148
6116
  }
6149
6117
  const BUCKETS_DIR = "buckets";
6150
6118
  /**
6151
- * Where a bucket's objects live: in the project's dev directory, under its app, so `--fresh`
6119
+ * Where a bucket's objects live: in the environment's folder, under its app, so `--fresh`
6152
6120
  * removes them with the app. The folder is named by the address below the app, which is the
6153
- * name earlier releases gave it, so an upgrade finds the objects where they are.
6121
+ * name earlier releases gave it.
6154
6122
  */
6155
6123
  function bucketDataDir(scope, address) {
6156
6124
  const prefix = `${scope.app}.`;
6157
6125
  if (!address.startsWith(prefix)) throw new Error(`local dev: bucket address "${address}" is not inside the app "${scope.app}".`);
6158
6126
  return path$1.join(scope.devDir, BUCKETS_DIR, scope.app, address.slice(prefix.length));
6159
6127
  }
6160
- function appScopeOf$2(input) {
6161
- return {
6162
- devDir: input.devDir,
6163
- app: appNameOf(input.container)
6164
- };
6165
- }
6166
6128
  /**
6167
6129
  * `Bucket` → the bucket instance the directory's record names for the
6168
6130
  * resource, created when there is none, over an in-project data root. Its
@@ -6177,7 +6139,7 @@ function LocalBucketProvider(input) {
6177
6139
  try: async () => {
6178
6140
  const name = id;
6179
6141
  return {
6180
- bucketId: (await ensureBucketInstance(appScopeOf$2(input), name, bucketDataDir(appScopeOf$2(input), name))).id,
6142
+ bucketId: (await ensureBucketInstance(appScopeOf(input), name, bucketDataDir(appScopeOf(input), name))).id,
6181
6143
  name,
6182
6144
  projectId: projectIdOfInput(news.project),
6183
6145
  createdAt: DEV_TIMESTAMP,
@@ -6189,7 +6151,7 @@ function LocalBucketProvider(input) {
6189
6151
  diff: ({ output }) => Effect.tryPromise({
6190
6152
  try: async () => {
6191
6153
  if (output === void 0) return void 0;
6192
- return await recordedInstanceId(appScopeOf$2(input), output.name) === output.bucketId ? void 0 : { action: "update" };
6154
+ return await recordedInstanceId(appScopeOf(input), output.name) === output.bucketId ? void 0 : { action: "update" };
6193
6155
  },
6194
6156
  catch: (cause) => cause
6195
6157
  }),
@@ -6449,11 +6411,8 @@ async function materializeEnv(devDir, address, port) {
6449
6411
  if (process.env["HOME"] !== void 0) env["HOME"] = process.env["HOME"];
6450
6412
  return env;
6451
6413
  }
6452
- function appScopeOf$1(input) {
6453
- return {
6454
- devDir: input.devDir,
6455
- app: appNameOf(input.container)
6456
- };
6414
+ function artifactDirOf(devDir, artifactHash) {
6415
+ return path$1.join(devDir, "artifacts", artifactHash);
6457
6416
  }
6458
6417
  function localUrl(port) {
6459
6418
  return `http://localhost:${String(port)}`;
@@ -6471,7 +6430,7 @@ function LocalAppProvider(input) {
6471
6430
  reconcile: ({ id, news }) => Effect.tryPromise({
6472
6431
  try: async () => {
6473
6432
  const name = id;
6474
- const { instance } = await ensureServiceInstance(appScopeOf$1(input), name);
6433
+ const { instance } = await ensureServiceInstance(appScopeOf(input), name);
6475
6434
  const url = localUrl(instance.port);
6476
6435
  return {
6477
6436
  appId: name,
@@ -6490,13 +6449,13 @@ function LocalAppProvider(input) {
6490
6449
  diff: ({ output }) => Effect.tryPromise({
6491
6450
  try: async () => {
6492
6451
  if (output === void 0) return void 0;
6493
- if ((await liveServiceInstance(appScopeOf$1(input), output.name))?.url === output.appEndpointDomain) return void 0;
6452
+ if ((await liveServiceInstance(appScopeOf(input), output.name))?.url === output.appEndpointDomain) return void 0;
6494
6453
  return { action: "update" };
6495
6454
  },
6496
6455
  catch: (cause) => cause
6497
6456
  }),
6498
6457
  delete: ({ output }) => Effect.tryPromise({
6499
- try: () => deleteNodeInstance(appScopeOf$1(input), output.name),
6458
+ try: () => deleteNodeInstance(appScopeOf(input), output.name),
6500
6459
  catch: (cause) => cause
6501
6460
  }),
6502
6461
  read: ({ output }) => Effect.succeed(output)
@@ -6556,10 +6515,10 @@ function LocalDeploymentProvider(input) {
6556
6515
  const appId = appIdOfInput(news.app);
6557
6516
  if (news.artifactPath === void 0) throw new Error("local Deployment requires an artifactPath — nothing to run.");
6558
6517
  const artifactHash = await artifactSha256(news.artifactPath);
6559
- const artifactDir = path$1.join(input.devDir, "artifacts", artifactHash);
6518
+ const artifactDir = artifactDirOf(input.devDir, artifactHash);
6560
6519
  if (!fs$2.existsSync(artifactDir)) extractComputeArtifact(news.artifactPath, artifactDir);
6561
6520
  const { address, listens } = readComputeManifest(artifactDir);
6562
- const { instance } = await ensureServiceInstance(appScopeOf$1(input), appId);
6521
+ const { instance } = await ensureServiceInstance(appScopeOf(input), appId);
6563
6522
  const env = await materializeEnv(input.devDir, address, instance.port);
6564
6523
  try {
6565
6524
  await computeClient().putDeployment(instance.id, {
@@ -6590,8 +6549,10 @@ function LocalDeploymentProvider(input) {
6590
6549
  diff: ({ output }) => Effect.tryPromise({
6591
6550
  try: async () => {
6592
6551
  if (output === void 0) return void 0;
6593
- if ((await liveServiceInstance(appScopeOf$1(input), output.appId))?.artifactHash === output.artifactHash) return void 0;
6594
- return { action: "update" };
6552
+ const live = await liveServiceInstance(appScopeOf(input), output.appId);
6553
+ const { artifactHash } = output;
6554
+ if (live?.artifactHash !== artifactHash) return { action: "update" };
6555
+ return artifactHash === void 0 || fs$2.existsSync(artifactDirOf(input.devDir, artifactHash)) ? void 0 : { action: "update" };
6595
6556
  },
6596
6557
  catch: (cause) => cause
6597
6558
  }),
@@ -6624,6 +6585,45 @@ function LocalProjectProvider(_input) {
6624
6585
  delete: () => Effect.void
6625
6586
  }));
6626
6587
  }
6588
+ /**
6589
+ * Releases before each environment had its own folder kept local dev state
6590
+ * directly in `.prisma-composer/dev/`. That state is the environment `dev`'s:
6591
+ * the first run after an upgrade moves the record, the environment variable
6592
+ * values, the placeholder secrets and the bucket objects into `dev`'s folder,
6593
+ * so its databases and objects are kept. Unpacked artifacts and the generated
6594
+ * stack file are regenerated, so they are removed instead. Delete this file
6595
+ * once no such folder remains.
6596
+ */
6597
+ const PRE_ENVIRONMENT_ENVIRONMENT = "dev";
6598
+ const MOVED = [
6599
+ EMULATOR_INSTANCES_FILE,
6600
+ "env.json",
6601
+ "secrets.json",
6602
+ BUCKETS_DIR
6603
+ ];
6604
+ const REMOVED = ["artifacts", "alchemy.run.ts"];
6605
+ function isFile(file) {
6606
+ return fs$2.statSync(file, { throwIfNoEntry: false })?.isFile() === true;
6607
+ }
6608
+ /** Moves a pre-environment `.prisma-composer/dev/` in `directory` into the environment `dev`; does nothing for any other folder. */
6609
+ function movePreEnvironmentDevState(directory) {
6610
+ const root = path$1.join(directory, DEV_DIR);
6611
+ const target = path$1.join(directory, devEnvironmentDir(PRE_ENVIRONMENT_ENVIRONMENT));
6612
+ if (!isFile(path$1.join(root, "emulator-instances.json"))) return;
6613
+ if (fs$2.existsSync(path$1.join(target, "emulator-instances.json"))) return;
6614
+ fs$2.mkdirSync(target, { recursive: true });
6615
+ for (const entry of MOVED) if (fs$2.existsSync(path$1.join(root, entry))) fs$2.renameSync(path$1.join(root, entry), path$1.join(target, entry));
6616
+ fs$2.writeFileSync(path$1.join(target, DEV_ENVIRONMENT_NAME_FILE), `${PRE_ENVIRONMENT_ENVIRONMENT}\n`);
6617
+ for (const entry of REMOVED) fs$2.rmSync(path$1.join(root, entry), {
6618
+ recursive: true,
6619
+ force: true
6620
+ });
6621
+ }
6622
+ /**
6623
+ * An app's local copy in one environment, read from the environment's
6624
+ * record alone: where each of its nodes runs, and stopping it. Behind
6625
+ * `prisma dev url` and `prisma dev stop`.
6626
+ */
6627
6627
  const CONDITION_OF = {
6628
6628
  running: "running",
6629
6629
  stopped: "stopped",
@@ -6684,12 +6684,16 @@ async function locate(address, node) {
6684
6684
  condition: "running"
6685
6685
  };
6686
6686
  }
6687
- /** The directory's current app; undefined when `prisma dev` never ran there. */
6688
- async function currentApp(devDir) {
6689
- const record = await readEmulatorInstances(directoryOf(devDir));
6690
- if (record === void 0) return void 0;
6691
- const app = record.current;
6692
- const nodes = record.apps[app]?.nodes ?? {};
6687
+ /** The directory `prisma dev` ran in, from the environment's folder in it. */
6688
+ function directoryOf$1(devDir, environment) {
6689
+ return path$1.resolve(devDir, path$1.relative(devEnvironmentDir(environment), "."));
6690
+ }
6691
+ /** The app's local copy in the environment; undefined when `prisma dev` never ran it there. */
6692
+ async function currentApp(input) {
6693
+ const { devDir, app, environment } = input;
6694
+ movePreEnvironmentDevState(directoryOf$1(devDir, environment));
6695
+ const nodes = (await readEmulatorInstances(devDir))?.apps[app]?.nodes;
6696
+ if (nodes === void 0) return void 0;
6693
6697
  return {
6694
6698
  app,
6695
6699
  addresses: Object.keys(nodes),
@@ -6699,7 +6703,8 @@ async function currentApp(devDir) {
6699
6703
  },
6700
6704
  stop: () => stopAppInstances({
6701
6705
  devDir,
6702
- app
6706
+ app,
6707
+ environment
6703
6708
  })
6704
6709
  };
6705
6710
  }
@@ -6754,12 +6759,6 @@ function LocalDatabaseProvider(input) {
6754
6759
  delete: () => Effect.void
6755
6760
  }));
6756
6761
  }
6757
- function appScopeOf(input) {
6758
- return {
6759
- devDir: input.devDir,
6760
- app: appNameOf(input.container)
6761
- };
6762
- }
6763
6762
  function noRecordedInstanceError(databaseId) {
6764
6763
  return /* @__PURE__ */ new Error(`no local Postgres instance recorded for databaseId "${databaseId}" — the Database provider did not run; converge is corrupt (try --fresh).`);
6765
6764
  }
@@ -6834,9 +6833,25 @@ function removeLocalPaths(paths) {
6834
6833
  });
6835
6834
  }
6836
6835
  /**
6837
- * Removes the dev state directory's contents for `--fresh`, except what
6838
- * other apps run from the directory still use: the record of their emulator
6839
- * instances and their buckets' objects. This app's objects go.
6836
+ * Removes one Alchemy stage's local state: the files directly in its folder,
6837
+ * then the folder once it is empty. Alchemy keeps the stage `feature/x` in
6838
+ * `feature/`'s folder, so a subfolder is another stage's and stays.
6839
+ */
6840
+ function removeAlchemyStage(stageDir) {
6841
+ let entries;
6842
+ try {
6843
+ entries = fs$2.readdirSync(stageDir, { withFileTypes: true });
6844
+ } catch {
6845
+ return;
6846
+ }
6847
+ for (const entry of entries) if (!entry.isDirectory()) fs$2.rmSync(path$1.join(stageDir, entry.name), { force: true });
6848
+ if (entries.every((entry) => !entry.isDirectory())) fs$2.rmdirSync(stageDir);
6849
+ }
6850
+ /**
6851
+ * Removes an environment folder's contents for `--fresh`, except the
6852
+ * environment's recorded name and what other apps run in the environment
6853
+ * still use: the record of their emulator instances and their buckets'
6854
+ * objects. This app's objects go.
6840
6855
  */
6841
6856
  function removeAppDevState(devDir, app) {
6842
6857
  let entries;
@@ -6845,7 +6860,11 @@ function removeAppDevState(devDir, app) {
6845
6860
  } catch {
6846
6861
  return;
6847
6862
  }
6848
- const kept = /* @__PURE__ */ new Set([EMULATOR_INSTANCES_FILE, BUCKETS_DIR]);
6863
+ const kept = /* @__PURE__ */ new Set([
6864
+ DEV_ENVIRONMENT_NAME_FILE,
6865
+ EMULATOR_INSTANCES_FILE,
6866
+ BUCKETS_DIR
6867
+ ]);
6849
6868
  removeLocalPaths([...entries.filter((entry) => !kept.has(entry)).map((entry) => path$1.join(devDir, entry)), path$1.join(devDir, BUCKETS_DIR, app)]);
6850
6869
  }
6851
6870
  //#endregion
@@ -6944,9 +6963,12 @@ async function* mergedLogs(scope, signal, tail, addresses) {
6944
6963
  }
6945
6964
  }
6946
6965
  async function devAttach(input) {
6966
+ const { appName: app, environment } = prismaCloudContainerOf(input.container).input;
6967
+ movePreEnvironmentDevState(directoryOf(input.devDir));
6947
6968
  const scope = {
6948
6969
  devDir: input.devDir,
6949
- app: prismaCloudContainerOf(input.container).input.appName
6970
+ app,
6971
+ environment
6950
6972
  };
6951
6973
  const client = computeClient();
6952
6974
  return {
@@ -7051,12 +7073,7 @@ async function runDevEmulators(input) {
7051
7073
  const { url: postgresUrl } = await ensureDaemon("postgres", daemonEntry("postgres"));
7052
7074
  console.log(`[dev] postgres emulator ready at ${postgresUrl}`);
7053
7075
  }
7054
- const scope = {
7055
- devDir: input.devDir,
7056
- app: appNameOf(input.container)
7057
- };
7058
- const previous = await selectApp(scope);
7059
- if (previous !== void 0) console.log(`[dev] stopped the local services of "${previous}", the app run here before; its data is kept`);
7076
+ const scope = appScopeOf(input);
7060
7077
  const portChanges = await ensureAppInstances(scope, nodes).catch(async (error) => {
7061
7078
  await stopAppInstances(scope).catch(() => void 0);
7062
7079
  throw error;
@@ -7073,7 +7090,8 @@ function missingEnvParamsError(missing) {
7073
7090
  return /* @__PURE__ */ new Error(`local dev preflight failed — ${missing.length} env-sourced param(s) are not set in this shell:\n${lines.join("\n")}\n\nSet each in the shell you run \`prisma dev\` from.`);
7074
7091
  }
7075
7092
  async function runDevPreflight(input) {
7076
- const store = secretsStore(`${process.cwd()}/${DEV_DIR}`);
7093
+ movePreEnvironmentDevState(process.cwd());
7094
+ const store = secretsStore(`${process.cwd()}/${devEnvironmentDir(input.environment)}`);
7077
7095
  const { secrets, envParams } = collectPreflightNames(input.graph);
7078
7096
  await store.update((current) => {
7079
7097
  const next = { ...current };
@@ -7109,18 +7127,21 @@ const DAEMON_OF = {
7109
7127
  };
7110
7128
  async function runDevTeardown(input) {
7111
7129
  const app = prismaCloudContainerOf(input.container).input.appName;
7130
+ const { environment } = input;
7112
7131
  const cwd = process.cwd();
7113
- const devDir = `${cwd}/${DEV_DIR}`;
7132
+ movePreEnvironmentDevState(cwd);
7133
+ const devDir = `${cwd}/${devEnvironmentDir(environment)}`;
7114
7134
  const scope = {
7115
7135
  devDir,
7116
- app
7136
+ app,
7137
+ environment
7117
7138
  };
7118
7139
  try {
7119
7140
  for (const kind of await recordedKinds(scope)) await ensureDaemon(DAEMON_OF[kind], daemonEntry(DAEMON_OF[kind]));
7120
7141
  await deleteAppInstances(scope);
7121
7142
  } finally {
7122
7143
  removeAppDevState(devDir, app);
7123
- removeLocalPaths([`${cwd}/.alchemy/state/${app}/dev`]);
7144
+ removeAlchemyStage(`${cwd}/.alchemy/state/${app}/${environment}`);
7124
7145
  }
7125
7146
  }
7126
7147
  /** `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`. */
@@ -7140,7 +7161,7 @@ function localTargetDescriptor() {
7140
7161
  emulators: (input) => runDevEmulators(input),
7141
7162
  attach: (input) => devAttach(input),
7142
7163
  teardown: (input) => runDevTeardown(input),
7143
- current: (input) => currentApp(input.devDir)
7164
+ current: (input) => currentApp(input)
7144
7165
  };
7145
7166
  }
7146
7167
  //#endregion