@prisma/composer 0.2.0-dev.12 → 0.2.0-dev.14

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/bin.mjs CHANGED
@@ -2,50 +2,60 @@
2
2
  import { Cli, Command, Option, UsageError } from "clipanion";
3
3
  import * as fs from "node:fs";
4
4
  import * as path from "node:path";
5
+ import { join, relative, resolve, sep } from "node:path";
6
+ import "alchemy";
7
+ import "effect/Effect";
8
+ import "effect/Layer";
5
9
  import * as c12 from "c12";
6
10
  import { pathToFileURL } from "node:url";
7
11
  import { spawnSync } from "node:child_process";
8
- //#region ../../0-framework/3-tooling/assemble/dist/index.mjs
9
- /**
10
- * A user-facing assembly failure with a message that already names the fix
11
- * (mirrors @internal/cli's CliError contract). This package must not import
12
- * CliError its second consumer is the future programmatic deploy API, not
13
- * just the CLI so it throws its own typed error; the CLI maps it (or lets
14
- * it propagate, since bin.ts already treats every Error uniformly: print the
15
- * message, exit nonzero).
16
- */
17
- var AssembleError = class extends Error {
18
- constructor(message) {
19
- super(message);
20
- this.name = "AssembleError";
12
+ import { stat, unwatchFile, watch, watchFile } from "fs";
13
+ import { lstat, open, readdir, realpath, stat as stat$1 } from "fs/promises";
14
+ import { EventEmitter } from "events";
15
+ import * as sysPath from "path";
16
+ import { lstat as lstat$1, readdir as readdir$1, realpath as realpath$1, stat as stat$2 } from "node:fs/promises";
17
+ import { Readable } from "node:stream";
18
+ import { type } from "os";
19
+ //#region ../../0-framework/1-core/core/dist/container-transport-DKmKg5JQ.mjs
20
+ /** '@prisma/composer-prisma-cloud' → 'PRISMA_COMPOSER_CONTAINER_PRISMA_COMPOSER_PRISMA_CLOUD' */
21
+ function containerEnvVarName(extensionId) {
22
+ return `PRISMA_COMPOSER_CONTAINER_${extensionId.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "")}`;
23
+ }
24
+ function collisionError(a, b, varName) {
25
+ return /* @__PURE__ */ new Error(`Extension ids "${a}" and "${b}" both mangle to the container transport variable "${varName}" — rename one of the extensions.`);
26
+ }
27
+ function emptySerializeError(extensionId) {
28
+ return /* @__PURE__ */ new Error(`Extension "${extensionId}"'s container instance serialized to an empty string — ContainerInstance.serialize() must return a non-empty string.`);
29
+ }
30
+ /** The env entries the CLI sets on the alchemy process: `{ [containerEnvVarName(id)]: instance.serialize() }` for every resolved instance. */
31
+ function containerEnv(instances) {
32
+ const env = {};
33
+ const ownerByVarName = /* @__PURE__ */ new Map();
34
+ for (const [extensionId, instance] of instances) {
35
+ const varName = containerEnvVarName(extensionId);
36
+ const owner = ownerByVarName.get(varName);
37
+ if (owner !== void 0) throw collisionError(owner, extensionId, varName);
38
+ ownerByVarName.set(varName, extensionId);
39
+ const serialized = instance.serialize();
40
+ if (serialized.length === 0) throw emptySerializeError(extensionId);
41
+ env[varName] = serialized;
21
42
  }
22
- };
43
+ return env;
44
+ }
45
+ //#endregion
46
+ //#region ../../0-framework/1-core/core/dist/app-config-joXc-BKm.mjs
47
+ /** `<cwd>/.prisma-composer/dev` — the dev instance's app-scoped state directory (ADR-0041, ADR-0004's tool-state rule). "dev" names the user-facing feature/dir (naming, operator 2026-07-23) — this constant's name and value are unchanged by the localTarget rename. */
48
+ const DEV_DIR = ".prisma-composer/dev";
23
49
  /**
24
- * The registry route for one service's build: extension by
25
- * `build.extension`, node descriptor by `build.type`, kind must be "build".
26
- * The CLI's coverage validation reports the same misses earlier with the
27
- * config fix; these errors are the backstop for programmatic callers.
50
+ * True when an extension only participates in assembly (every `nodes` entry
51
+ * is `kind: 'build'`, and it declares none of `providers`/`application`/
52
+ * `provisions`/`container`) it owns no resources or services, so it has
53
+ * nothing to emulate and is exempt from local-target-capability requirements
54
+ * (ADR-0041). Shared by `localTargetProviders` and every local-target hook
55
+ * iteration.
28
56
  */
29
- function buildDescriptorAssemble(config, node, address, cwd) {
30
- const { extension, type } = node.build;
31
- const extensionDescriptor = config.extensions.find((candidate) => candidate.id === extension);
32
- if (extensionDescriptor === void 0) throw new AssembleError(`No extension "${extension}" is configured (needed by service "${node.name}"'s build) — add it to prisma-composer.config.ts's \`extensions\`.`);
33
- const nodeDescriptor = extensionDescriptor.nodes[type];
34
- if (nodeDescriptor === void 0) throw new AssembleError(`Extension "${extension}" has no descriptor for build type "${type}" (known: ${Object.keys(extensionDescriptor.nodes).join(", ")}).`);
35
- if (nodeDescriptor.kind !== "build") throw new AssembleError(`Extension "${extension}"'s descriptor for type "${type}" is a "${nodeDescriptor.kind}" descriptor — assembling a service build needs a "build" descriptor.`);
36
- return nodeDescriptor.assemble({
37
- build: node.build,
38
- address,
39
- cwd
40
- });
41
- }
42
- async function assembleServices(graph, config, cwd, run) {
43
- const runAssembler = run ?? ((node, address, nodeCwd) => buildDescriptorAssemble(config, node, address, nodeCwd));
44
- const serviceNodes = graph.nodes.filter((n) => n.node.kind === "service");
45
- if (serviceNodes.length === 0) throw new AssembleError("The loaded graph has no service to assemble.");
46
- const bundles = {};
47
- for (const { id, node } of serviceNodes) bundles[id] = await runAssembler(node, id, cwd);
48
- return { bundles };
57
+ function isBuildOnlyExtension(extension) {
58
+ return Object.values(extension.nodes).every((node) => node.kind === "build") && extension.providers === void 0 && extension.application === void 0 && extension.provisions === void 0 && extension.container === void 0;
49
59
  }
50
60
  //#endregion
51
61
  //#region ../../0-framework/0-foundation/foundation/dist/casts.mjs
@@ -584,133 +594,1679 @@ function Load(root, opts) {
584
594
  throw new LoadError("Load expects a service or module root (received another node kind).");
585
595
  }
586
596
  //#endregion
587
- //#region ../../0-framework/1-core/core/dist/container-transport-DKmKg5JQ.mjs
588
- /** '@prisma/composer-prisma-cloud' 'PRISMA_COMPOSER_CONTAINER_PRISMA_COMPOSER_PRISMA_CLOUD' */
589
- function containerEnvVarName(extensionId) {
590
- return `PRISMA_COMPOSER_CONTAINER_${extensionId.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "")}`;
591
- }
592
- function collisionError(a, b, varName) {
593
- return /* @__PURE__ */ new Error(`Extension ids "${a}" and "${b}" both mangle to the container transport variable "${varName}" — rename one of the extensions.`);
594
- }
595
- function emptySerializeError(extensionId) {
596
- return /* @__PURE__ */ new Error(`Extension "${extensionId}"'s container instance serialized to an empty string — ContainerInstance.serialize() must return a non-empty string.`);
597
- }
598
- /** The env entries the CLI sets on the alchemy process: `{ [containerEnvVarName(id)]: instance.serialize() }` for every resolved instance. */
599
- function containerEnv(instances) {
600
- const env = {};
601
- const ownerByVarName = /* @__PURE__ */ new Map();
602
- for (const [extensionId, instance] of instances) {
603
- const varName = containerEnvVarName(extensionId);
604
- const owner = ownerByVarName.get(varName);
605
- if (owner !== void 0) throw collisionError(owner, extensionId, varName);
606
- ownerByVarName.set(varName, extensionId);
607
- const serialized = instance.serialize();
608
- if (serialized.length === 0) throw emptySerializeError(extensionId);
609
- env[varName] = serialized;
597
+ //#region ../../0-framework/1-core/core/dist/deploy-H3PKi0ZR.mjs
598
+ var LowerError = class extends Error {
599
+ constructor(message) {
600
+ super(message);
601
+ this.name = "LowerError";
610
602
  }
611
- return env;
603
+ };
604
+ //#endregion
605
+ //#region ../../0-framework/1-core/core/dist/local-target.mjs
606
+ /** The local-target stack's own provider aggregation (ADR-0041; naming, operator 2026-07-23 — the seam is `localTarget`, "dev" names the user-facing feature only) — the local-target counterpart of `deploy.ts`'s `mergedProviders`, kept in its own module so `lower()` learns nothing about it (deploy.ts's REVISED — operator review of #162). */
607
+ function noLocalTargetSupportError(id) {
608
+ return new LowerError(`extension "${id}" has no dev support — it declares no \`localTarget\` descriptor (ADR-0041).`);
609
+ }
610
+ /**
611
+ * Resolves every non-build-only configured extension's lazy `localTarget`
612
+ * thunk, once (ADR-0041's lazy local-target reference — operator directive:
613
+ * the production control entry carries only the thunk, never the
614
+ * descriptor, so resolving it is this module's job, not something a deploy
615
+ * path ever does). A build-only extension (`isBuildOnlyExtension`) owns no
616
+ * resources or services and is skipped entirely — never even checked for a
617
+ * `localTarget` thunk. Every other configured extension must be
618
+ * local-target-capable, or the dev command cannot bring the app up at all,
619
+ * so a missing thunk throws naming the extension. The generated dev stack
620
+ * module calls this once and threads the resolved map through every
621
+ * subsequent hook, including `localTargetProviders`.
622
+ */
623
+ async function resolveLocalTargets(config) {
624
+ const entries = await Promise.all(config.extensions.flatMap((extension) => {
625
+ if (isBuildOnlyExtension(extension)) return [];
626
+ if (extension.localTarget === void 0) throw noLocalTargetSupportError(extension.id);
627
+ return [extension.localTarget().then((descriptor) => [extension.id, descriptor])];
628
+ }));
629
+ return new Map(entries);
612
630
  }
613
631
  //#endregion
614
- //#region ../../0-framework/3-tooling/cli/dist/cli-riJGhp7G.mjs
632
+ //#region ../../0-framework/3-tooling/assemble/dist/index.mjs
615
633
  /**
616
- * A user-facing failure with a message that already names the fix (deploy-cli.md
617
- * § Error surface). `bin.ts` catches this and any other Error, including
618
- * core's LoadError/LowerError uniformly: print the message, exit nonzero.
634
+ * A user-facing assembly failure with a message that already names the fix
635
+ * (mirrors @internal/cli's CliError contract). This package must not import
636
+ * CliErrorits second consumer is the future programmatic deploy API, not
637
+ * just the CLI — so it throws its own typed error; the CLI maps it (or lets
638
+ * it propagate, since bin.ts already treats every Error uniformly: print the
639
+ * message, exit nonzero).
619
640
  */
620
- var CliError = class extends Error {
641
+ var AssembleError = class extends Error {
621
642
  constructor(message) {
622
643
  super(message);
623
- this.name = "CliError";
644
+ this.name = "AssembleError";
624
645
  }
625
646
  };
626
- /** Pipeline step 6: writes a regenerated-every-run, independently runnable stack module at `.prisma-composer/alchemy.run.ts`. */
627
- const GENERATED_DIR = ".prisma-composer";
628
- const GENERATED_FILE = "alchemy.run.ts";
629
- /** A relative import specifier from `.prisma-composer/alchemy.run.ts` to `target` (posix separators). */
630
- function relativeImportSpecifier(generatedDir, target) {
631
- const rel = path.relative(generatedDir, target).split(path.sep).join("/");
632
- return rel.startsWith(".") ? rel : `./${rel}`;
633
- }
634
- function quote(value) {
635
- return JSON.stringify(value);
636
- }
637
- function renderBundle(bundle) {
638
- return `{ dir: ${quote(bundle.dir)}, entry: ${quote(bundle.entry)} }`;
639
- }
640
- function renderOptions(input) {
641
- const lines = [];
642
- lines.push(` name: ${quote(input.name)},`);
643
- lines.push(" bundles: {");
644
- for (const [id, bundle] of Object.entries(input.assembled.bundles)) lines.push(` ${quote(id)}: ${renderBundle(bundle)},`);
645
- lines.push(" },");
646
- lines.push(" report: deploymentReport,");
647
- return lines.join("\n");
647
+ /**
648
+ * The registry route for one service's build: extension by
649
+ * `build.extension`, node descriptor by `build.type`, kind must be "build".
650
+ * The CLI's coverage validation reports the same misses earlier with the
651
+ * config fix; these errors are the backstop for programmatic callers.
652
+ */
653
+ function buildDescriptorAssemble(config, node, address, cwd) {
654
+ const { extension, type } = node.build;
655
+ const extensionDescriptor = config.extensions.find((candidate) => candidate.id === extension);
656
+ if (extensionDescriptor === void 0) throw new AssembleError(`No extension "${extension}" is configured (needed by service "${node.name}"'s build) — add it to prisma-composer.config.ts's \`extensions\`.`);
657
+ const nodeDescriptor = extensionDescriptor.nodes[type];
658
+ if (nodeDescriptor === void 0) throw new AssembleError(`Extension "${extension}" has no descriptor for build type "${type}" (known: ${Object.keys(extensionDescriptor.nodes).join(", ")}).`);
659
+ if (nodeDescriptor.kind !== "build") throw new AssembleError(`Extension "${extension}"'s descriptor for type "${type}" is a "${nodeDescriptor.kind}" descriptor — assembling a service build needs a "build" descriptor.`);
660
+ return nodeDescriptor.assemble({
661
+ build: node.build,
662
+ address,
663
+ cwd
664
+ });
648
665
  }
649
- /** Renders the stack module's source (tests assert on it without touching disk) — uses `//` headers, not a block comment, since a cwd path with a star-slash could close one early. */
650
- function renderStackFile(input) {
651
- const generatedDir = path.join(input.cwd, GENERATED_DIR);
652
- const appImport = relativeImportSpecifier(generatedDir, input.entryPath);
653
- const configImport = relativeImportSpecifier(generatedDir, input.configPath);
654
- return `// Generated by \`prisma-composer deploy\`/\`prisma-composer destroy\` overwritten on every
655
- // run; do not edit by hand. Independently runnable from ${quote(input.cwd)}:
656
- //
657
- // alchemy deploy ${GENERATED_DIR}/${GENERATED_FILE}
658
- //
659
- // bisects a CLI bug from an Alchemy bug (deploy-cli.md § Implementation decisions).
660
- import { lower } from '@prisma/composer/deploy';
661
- import { deploymentReport } from '@prisma/composer/report';
662
- import config from ${quote(configImport)};
663
- import app from ${quote(appImport)};
664
-
665
- export default lower(app, config, {
666
- ${renderOptions(input)}
667
- });
668
- `;
666
+ async function assembleServices(graph, config, cwd, run) {
667
+ const runAssembler = run ?? ((node, address, nodeCwd) => buildDescriptorAssemble(config, node, address, nodeCwd));
668
+ const serviceNodes = graph.nodes.filter((n) => n.node.kind === "service");
669
+ if (serviceNodes.length === 0) throw new AssembleError("The loaded graph has no service to assemble.");
670
+ const bundles = {};
671
+ for (const { id, node } of serviceNodes) bundles[id] = await runAssembler(node, id, cwd);
672
+ return { bundles };
669
673
  }
670
- /** Writes the stack file, returning its absolute path. */
671
- function writeStackFile(input) {
672
- const generatedDir = path.join(input.cwd, GENERATED_DIR);
673
- fs.mkdirSync(generatedDir, { recursive: true });
674
- const filePath = path.join(generatedDir, GENERATED_FILE);
675
- fs.writeFileSync(filePath, renderStackFile(input));
676
- return filePath;
674
+ //#endregion
675
+ //#region ../../0-framework/3-tooling/cli/node_modules/readdirp/esm/index.js
676
+ const EntryTypes = {
677
+ FILE_TYPE: "files",
678
+ DIR_TYPE: "directories",
679
+ FILE_DIR_TYPE: "files_directories",
680
+ EVERYTHING_TYPE: "all"
681
+ };
682
+ const defaultOptions = {
683
+ root: ".",
684
+ fileFilter: (_entryInfo) => true,
685
+ directoryFilter: (_entryInfo) => true,
686
+ type: EntryTypes.FILE_TYPE,
687
+ lstat: false,
688
+ depth: 2147483648,
689
+ alwaysStat: false,
690
+ highWaterMark: 4096
691
+ };
692
+ Object.freeze(defaultOptions);
693
+ const RECURSIVE_ERROR_CODE = "READDIRP_RECURSIVE_ERROR";
694
+ const NORMAL_FLOW_ERRORS = /* @__PURE__ */ new Set([
695
+ "ENOENT",
696
+ "EPERM",
697
+ "EACCES",
698
+ "ELOOP",
699
+ RECURSIVE_ERROR_CODE
700
+ ]);
701
+ const ALL_TYPES = [
702
+ EntryTypes.DIR_TYPE,
703
+ EntryTypes.EVERYTHING_TYPE,
704
+ EntryTypes.FILE_DIR_TYPE,
705
+ EntryTypes.FILE_TYPE
706
+ ];
707
+ const DIR_TYPES = /* @__PURE__ */ new Set([
708
+ EntryTypes.DIR_TYPE,
709
+ EntryTypes.EVERYTHING_TYPE,
710
+ EntryTypes.FILE_DIR_TYPE
711
+ ]);
712
+ const FILE_TYPES = /* @__PURE__ */ new Set([
713
+ EntryTypes.EVERYTHING_TYPE,
714
+ EntryTypes.FILE_DIR_TYPE,
715
+ EntryTypes.FILE_TYPE
716
+ ]);
717
+ const isNormalFlowError = (error) => NORMAL_FLOW_ERRORS.has(error.code);
718
+ const wantBigintFsStats = process.platform === "win32";
719
+ const emptyFn = (_entryInfo) => true;
720
+ const normalizeFilter = (filter) => {
721
+ if (filter === void 0) return emptyFn;
722
+ if (typeof filter === "function") return filter;
723
+ if (typeof filter === "string") {
724
+ const fl = filter.trim();
725
+ return (entry) => entry.basename === fl;
726
+ }
727
+ if (Array.isArray(filter)) {
728
+ const trItems = filter.map((item) => item.trim());
729
+ return (entry) => trItems.some((f) => entry.basename === f);
730
+ }
731
+ return emptyFn;
732
+ };
733
+ /** Readable readdir stream, emitting new files as they're being listed. */
734
+ var ReaddirpStream = class extends Readable {
735
+ constructor(options = {}) {
736
+ super({
737
+ objectMode: true,
738
+ autoDestroy: true,
739
+ highWaterMark: options.highWaterMark
740
+ });
741
+ const opts = {
742
+ ...defaultOptions,
743
+ ...options
744
+ };
745
+ const { root, type } = opts;
746
+ this._fileFilter = normalizeFilter(opts.fileFilter);
747
+ this._directoryFilter = normalizeFilter(opts.directoryFilter);
748
+ const statMethod = opts.lstat ? lstat$1 : stat$2;
749
+ if (wantBigintFsStats) this._stat = (path) => statMethod(path, { bigint: true });
750
+ else this._stat = statMethod;
751
+ this._maxDepth = opts.depth ?? defaultOptions.depth;
752
+ this._wantsDir = type ? DIR_TYPES.has(type) : false;
753
+ this._wantsFile = type ? FILE_TYPES.has(type) : false;
754
+ this._wantsEverything = type === EntryTypes.EVERYTHING_TYPE;
755
+ this._root = resolve(root);
756
+ this._isDirent = !opts.alwaysStat;
757
+ this._statsProp = this._isDirent ? "dirent" : "stats";
758
+ this._rdOptions = {
759
+ encoding: "utf8",
760
+ withFileTypes: this._isDirent
761
+ };
762
+ this.parents = [this._exploreDir(root, 1)];
763
+ this.reading = false;
764
+ this.parent = void 0;
765
+ }
766
+ async _read(batch) {
767
+ if (this.reading) return;
768
+ this.reading = true;
769
+ try {
770
+ while (!this.destroyed && batch > 0) {
771
+ const par = this.parent;
772
+ const fil = par && par.files;
773
+ if (fil && fil.length > 0) {
774
+ const { path, depth } = par;
775
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path));
776
+ const awaited = await Promise.all(slice);
777
+ for (const entry of awaited) {
778
+ if (!entry) continue;
779
+ if (this.destroyed) return;
780
+ const entryType = await this._getEntryType(entry);
781
+ if (entryType === "directory" && this._directoryFilter(entry)) {
782
+ if (depth <= this._maxDepth) this.parents.push(this._exploreDir(entry.fullPath, depth + 1));
783
+ if (this._wantsDir) {
784
+ this.push(entry);
785
+ batch--;
786
+ }
787
+ } else if ((entryType === "file" || this._includeAsFile(entry)) && this._fileFilter(entry)) {
788
+ if (this._wantsFile) {
789
+ this.push(entry);
790
+ batch--;
791
+ }
792
+ }
793
+ }
794
+ } else {
795
+ const parent = this.parents.pop();
796
+ if (!parent) {
797
+ this.push(null);
798
+ break;
799
+ }
800
+ this.parent = await parent;
801
+ if (this.destroyed) return;
802
+ }
803
+ }
804
+ } catch (error) {
805
+ this.destroy(error);
806
+ } finally {
807
+ this.reading = false;
808
+ }
809
+ }
810
+ async _exploreDir(path, depth) {
811
+ let files;
812
+ try {
813
+ files = await readdir$1(path, this._rdOptions);
814
+ } catch (error) {
815
+ this._onError(error);
816
+ }
817
+ return {
818
+ files,
819
+ depth,
820
+ path
821
+ };
822
+ }
823
+ async _formatEntry(dirent, path) {
824
+ let entry;
825
+ const basename = this._isDirent ? dirent.name : dirent;
826
+ try {
827
+ const fullPath = resolve(join(path, basename));
828
+ entry = {
829
+ path: relative(this._root, fullPath),
830
+ fullPath,
831
+ basename
832
+ };
833
+ entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
834
+ } catch (err) {
835
+ this._onError(err);
836
+ return;
837
+ }
838
+ return entry;
839
+ }
840
+ _onError(err) {
841
+ if (isNormalFlowError(err) && !this.destroyed) this.emit("warn", err);
842
+ else this.destroy(err);
843
+ }
844
+ async _getEntryType(entry) {
845
+ if (!entry && this._statsProp in entry) return "";
846
+ const stats = entry[this._statsProp];
847
+ if (stats.isFile()) return "file";
848
+ if (stats.isDirectory()) return "directory";
849
+ if (stats && stats.isSymbolicLink()) {
850
+ const full = entry.fullPath;
851
+ try {
852
+ const entryRealPath = await realpath$1(full);
853
+ const entryRealPathStats = await lstat$1(entryRealPath);
854
+ if (entryRealPathStats.isFile()) return "file";
855
+ if (entryRealPathStats.isDirectory()) {
856
+ const len = entryRealPath.length;
857
+ if (full.startsWith(entryRealPath) && full.substr(len, 1) === sep) {
858
+ const recursiveError = /* @__PURE__ */ new Error(`Circular symlink detected: "${full}" points to "${entryRealPath}"`);
859
+ recursiveError.code = RECURSIVE_ERROR_CODE;
860
+ return this._onError(recursiveError);
861
+ }
862
+ return "directory";
863
+ }
864
+ } catch (error) {
865
+ this._onError(error);
866
+ return "";
867
+ }
868
+ }
869
+ }
870
+ _includeAsFile(entry) {
871
+ const stats = entry && entry[this._statsProp];
872
+ return stats && this._wantsEverything && !stats.isDirectory();
873
+ }
874
+ };
875
+ /**
876
+ * Streaming version: Reads all files and directories in given root recursively.
877
+ * Consumes ~constant small amount of RAM.
878
+ * @param root Root directory
879
+ * @param options Options to specify root (start directory), filters and recursion depth
880
+ */
881
+ function readdirp(root, options = {}) {
882
+ let type = options.entryType || options.type;
883
+ if (type === "both") type = EntryTypes.FILE_DIR_TYPE;
884
+ if (type) options.type = type;
885
+ if (!root) throw new Error("readdirp: root argument is required. Usage: readdirp(root, options)");
886
+ else if (typeof root !== "string") throw new TypeError("readdirp: root argument must be a string. Usage: readdirp(root, options)");
887
+ else if (type && !ALL_TYPES.includes(type)) throw new Error(`readdirp: Invalid type passed. Use one of ${ALL_TYPES.join(", ")}`);
888
+ options.root = root;
889
+ return new ReaddirpStream(options);
677
890
  }
678
- const GENERATED_STACK_RELATIVE_PATH = path.join(GENERATED_DIR, GENERATED_FILE);
891
+ //#endregion
892
+ //#region ../../0-framework/3-tooling/cli/node_modules/chokidar/esm/handler.js
893
+ const STR_DATA = "data";
894
+ const STR_CLOSE = "close";
895
+ const EMPTY_FN = () => {};
896
+ const pl = process.platform;
897
+ const isWindows = pl === "win32";
898
+ const isMacos = pl === "darwin";
899
+ const isLinux = pl === "linux";
900
+ const isFreeBSD = pl === "freebsd";
901
+ const isIBMi = type() === "OS400";
902
+ const EVENTS = {
903
+ ALL: "all",
904
+ READY: "ready",
905
+ ADD: "add",
906
+ CHANGE: "change",
907
+ ADD_DIR: "addDir",
908
+ UNLINK: "unlink",
909
+ UNLINK_DIR: "unlinkDir",
910
+ RAW: "raw",
911
+ ERROR: "error"
912
+ };
913
+ const EV = EVENTS;
914
+ const THROTTLE_MODE_WATCH = "watch";
915
+ const statMethods = {
916
+ lstat,
917
+ stat: stat$1
918
+ };
919
+ const KEY_LISTENERS = "listeners";
920
+ const KEY_ERR = "errHandlers";
921
+ const KEY_RAW = "rawEmitters";
922
+ const HANDLER_KEYS = [
923
+ KEY_LISTENERS,
924
+ KEY_ERR,
925
+ KEY_RAW
926
+ ];
927
+ const binaryExtensions = /* @__PURE__ */ new Set([
928
+ "3dm",
929
+ "3ds",
930
+ "3g2",
931
+ "3gp",
932
+ "7z",
933
+ "a",
934
+ "aac",
935
+ "adp",
936
+ "afdesign",
937
+ "afphoto",
938
+ "afpub",
939
+ "ai",
940
+ "aif",
941
+ "aiff",
942
+ "alz",
943
+ "ape",
944
+ "apk",
945
+ "appimage",
946
+ "ar",
947
+ "arj",
948
+ "asf",
949
+ "au",
950
+ "avi",
951
+ "bak",
952
+ "baml",
953
+ "bh",
954
+ "bin",
955
+ "bk",
956
+ "bmp",
957
+ "btif",
958
+ "bz2",
959
+ "bzip2",
960
+ "cab",
961
+ "caf",
962
+ "cgm",
963
+ "class",
964
+ "cmx",
965
+ "cpio",
966
+ "cr2",
967
+ "cur",
968
+ "dat",
969
+ "dcm",
970
+ "deb",
971
+ "dex",
972
+ "djvu",
973
+ "dll",
974
+ "dmg",
975
+ "dng",
976
+ "doc",
977
+ "docm",
978
+ "docx",
979
+ "dot",
980
+ "dotm",
981
+ "dra",
982
+ "DS_Store",
983
+ "dsk",
984
+ "dts",
985
+ "dtshd",
986
+ "dvb",
987
+ "dwg",
988
+ "dxf",
989
+ "ecelp4800",
990
+ "ecelp7470",
991
+ "ecelp9600",
992
+ "egg",
993
+ "eol",
994
+ "eot",
995
+ "epub",
996
+ "exe",
997
+ "f4v",
998
+ "fbs",
999
+ "fh",
1000
+ "fla",
1001
+ "flac",
1002
+ "flatpak",
1003
+ "fli",
1004
+ "flv",
1005
+ "fpx",
1006
+ "fst",
1007
+ "fvt",
1008
+ "g3",
1009
+ "gh",
1010
+ "gif",
1011
+ "graffle",
1012
+ "gz",
1013
+ "gzip",
1014
+ "h261",
1015
+ "h263",
1016
+ "h264",
1017
+ "icns",
1018
+ "ico",
1019
+ "ief",
1020
+ "img",
1021
+ "ipa",
1022
+ "iso",
1023
+ "jar",
1024
+ "jpeg",
1025
+ "jpg",
1026
+ "jpgv",
1027
+ "jpm",
1028
+ "jxr",
1029
+ "key",
1030
+ "ktx",
1031
+ "lha",
1032
+ "lib",
1033
+ "lvp",
1034
+ "lz",
1035
+ "lzh",
1036
+ "lzma",
1037
+ "lzo",
1038
+ "m3u",
1039
+ "m4a",
1040
+ "m4v",
1041
+ "mar",
1042
+ "mdi",
1043
+ "mht",
1044
+ "mid",
1045
+ "midi",
1046
+ "mj2",
1047
+ "mka",
1048
+ "mkv",
1049
+ "mmr",
1050
+ "mng",
1051
+ "mobi",
1052
+ "mov",
1053
+ "movie",
1054
+ "mp3",
1055
+ "mp4",
1056
+ "mp4a",
1057
+ "mpeg",
1058
+ "mpg",
1059
+ "mpga",
1060
+ "mxu",
1061
+ "nef",
1062
+ "npx",
1063
+ "numbers",
1064
+ "nupkg",
1065
+ "o",
1066
+ "odp",
1067
+ "ods",
1068
+ "odt",
1069
+ "oga",
1070
+ "ogg",
1071
+ "ogv",
1072
+ "otf",
1073
+ "ott",
1074
+ "pages",
1075
+ "pbm",
1076
+ "pcx",
1077
+ "pdb",
1078
+ "pdf",
1079
+ "pea",
1080
+ "pgm",
1081
+ "pic",
1082
+ "png",
1083
+ "pnm",
1084
+ "pot",
1085
+ "potm",
1086
+ "potx",
1087
+ "ppa",
1088
+ "ppam",
1089
+ "ppm",
1090
+ "pps",
1091
+ "ppsm",
1092
+ "ppsx",
1093
+ "ppt",
1094
+ "pptm",
1095
+ "pptx",
1096
+ "psd",
1097
+ "pya",
1098
+ "pyc",
1099
+ "pyo",
1100
+ "pyv",
1101
+ "qt",
1102
+ "rar",
1103
+ "ras",
1104
+ "raw",
1105
+ "resources",
1106
+ "rgb",
1107
+ "rip",
1108
+ "rlc",
1109
+ "rmf",
1110
+ "rmvb",
1111
+ "rpm",
1112
+ "rtf",
1113
+ "rz",
1114
+ "s3m",
1115
+ "s7z",
1116
+ "scpt",
1117
+ "sgi",
1118
+ "shar",
1119
+ "snap",
1120
+ "sil",
1121
+ "sketch",
1122
+ "slk",
1123
+ "smv",
1124
+ "snk",
1125
+ "so",
1126
+ "stl",
1127
+ "suo",
1128
+ "sub",
1129
+ "swf",
1130
+ "tar",
1131
+ "tbz",
1132
+ "tbz2",
1133
+ "tga",
1134
+ "tgz",
1135
+ "thmx",
1136
+ "tif",
1137
+ "tiff",
1138
+ "tlz",
1139
+ "ttc",
1140
+ "ttf",
1141
+ "txz",
1142
+ "udf",
1143
+ "uvh",
1144
+ "uvi",
1145
+ "uvm",
1146
+ "uvp",
1147
+ "uvs",
1148
+ "uvu",
1149
+ "viv",
1150
+ "vob",
1151
+ "war",
1152
+ "wav",
1153
+ "wax",
1154
+ "wbmp",
1155
+ "wdp",
1156
+ "weba",
1157
+ "webm",
1158
+ "webp",
1159
+ "whl",
1160
+ "wim",
1161
+ "wm",
1162
+ "wma",
1163
+ "wmv",
1164
+ "wmx",
1165
+ "woff",
1166
+ "woff2",
1167
+ "wrm",
1168
+ "wvx",
1169
+ "xbm",
1170
+ "xif",
1171
+ "xla",
1172
+ "xlam",
1173
+ "xls",
1174
+ "xlsb",
1175
+ "xlsm",
1176
+ "xlsx",
1177
+ "xlt",
1178
+ "xltm",
1179
+ "xltx",
1180
+ "xm",
1181
+ "xmind",
1182
+ "xpi",
1183
+ "xpm",
1184
+ "xwd",
1185
+ "xz",
1186
+ "z",
1187
+ "zip",
1188
+ "zipx"
1189
+ ]);
1190
+ const isBinaryPath = (filePath) => binaryExtensions.has(sysPath.extname(filePath).slice(1).toLowerCase());
1191
+ const foreach = (val, fn) => {
1192
+ if (val instanceof Set) val.forEach(fn);
1193
+ else fn(val);
1194
+ };
1195
+ const addAndConvert = (main, prop, item) => {
1196
+ let container = main[prop];
1197
+ if (!(container instanceof Set)) main[prop] = container = /* @__PURE__ */ new Set([container]);
1198
+ container.add(item);
1199
+ };
1200
+ const clearItem = (cont) => (key) => {
1201
+ const set = cont[key];
1202
+ if (set instanceof Set) set.clear();
1203
+ else delete cont[key];
1204
+ };
1205
+ const delFromSet = (main, prop, item) => {
1206
+ const container = main[prop];
1207
+ if (container instanceof Set) container.delete(item);
1208
+ else if (container === item) delete main[prop];
1209
+ };
1210
+ const isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
1211
+ const FsWatchInstances = /* @__PURE__ */ new Map();
679
1212
  /**
680
- * Pipeline step: find and load `prisma-composer.config.ts` (ADR-0017) — the ONE
681
- * file that imports control-plane code. Discovery is the standard walk-up
682
- * from the deploy entry's directory (mirrors prisma-next's config-loader);
683
- * loading is c12 with that explicit path (rc/global/package.json lookups
684
- * disabled), so the config file's own static imports resolve from the app
685
- * root by whatever package manager runs — no specifier construction, no
686
- * anchoring. The loaded shape is validated field-by-field with CliErrors
687
- * naming the field.
1213
+ * Instantiates the fs_watch interface
1214
+ * @param path to be watched
1215
+ * @param options to be passed to fs_watch
1216
+ * @param listener main event handler
1217
+ * @param errHandler emits info about errors
1218
+ * @param emitRaw emits raw event data
1219
+ * @returns {NativeFsWatcher}
688
1220
  */
689
- const CONFIG_FILENAME = "prisma-composer.config.ts";
690
- /** Walks UP from the entry file's directory looking for the literal CONFIG_FILENAME; undefined when the walk hits the filesystem root. */
691
- function findConfigPathForEntry(entryPath) {
692
- let current = path.dirname(path.resolve(entryPath));
693
- while (true) {
694
- const candidate = path.join(current, CONFIG_FILENAME);
695
- if (fs.existsSync(candidate)) return candidate;
696
- const parent = path.dirname(current);
697
- if (parent === current) return void 0;
698
- current = parent;
1221
+ function createFsWatchInstance(path, options, listener, errHandler, emitRaw) {
1222
+ const handleEvent = (rawEvent, evPath) => {
1223
+ listener(path);
1224
+ emitRaw(rawEvent, evPath, { watchedPath: path });
1225
+ if (evPath && path !== evPath) fsWatchBroadcast(sysPath.resolve(path, evPath), KEY_LISTENERS, sysPath.join(path, evPath));
1226
+ };
1227
+ try {
1228
+ return watch(path, { persistent: options.persistent }, handleEvent);
1229
+ } catch (error) {
1230
+ errHandler(error);
1231
+ return;
699
1232
  }
700
1233
  }
701
- function missingConfigError(entryPath) {
702
- return new CliError(`No ${CONFIG_FILENAME} found walking up from "${path.dirname(path.resolve(entryPath))}" — the deploy needs the app's config file. Create one next to (or above) the entry, default-exporting defineConfig({ extensions: [...], state: ... }) from '@prisma/composer/config'.`);
703
- }
704
- function fieldError(field, requirement) {
705
- return new CliError(`${CONFIG_FILENAME}: \`${field}\` ${requirement} — see defineConfig() in '@prisma/composer/config'.`);
706
- }
707
- function isRecord(value) {
708
- return typeof value === "object" && value !== null;
709
- }
710
1234
  /**
711
- * Field-by-field validation of the loaded default export deliberately no
712
- * schema library: each check is a CliError naming the offending field.
713
- * Returns the same object, typed.
1235
+ * Helper for passing fs_watch event data to a collection of listeners
1236
+ * @param fullPath absolute path bound to fs_watch instance
1237
+ */
1238
+ const fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
1239
+ const cont = FsWatchInstances.get(fullPath);
1240
+ if (!cont) return;
1241
+ foreach(cont[listenerType], (listener) => {
1242
+ listener(val1, val2, val3);
1243
+ });
1244
+ };
1245
+ /**
1246
+ * Instantiates the fs_watch interface or binds listeners
1247
+ * to an existing one covering the same file system entry
1248
+ * @param path
1249
+ * @param fullPath absolute path
1250
+ * @param options to be passed to fs_watch
1251
+ * @param handlers container for event listener functions
1252
+ */
1253
+ const setFsWatchListener = (path, fullPath, options, handlers) => {
1254
+ const { listener, errHandler, rawEmitter } = handlers;
1255
+ let cont = FsWatchInstances.get(fullPath);
1256
+ let watcher;
1257
+ if (!options.persistent) {
1258
+ watcher = createFsWatchInstance(path, options, listener, errHandler, rawEmitter);
1259
+ if (!watcher) return;
1260
+ return watcher.close.bind(watcher);
1261
+ }
1262
+ if (cont) {
1263
+ addAndConvert(cont, KEY_LISTENERS, listener);
1264
+ addAndConvert(cont, KEY_ERR, errHandler);
1265
+ addAndConvert(cont, KEY_RAW, rawEmitter);
1266
+ } else {
1267
+ watcher = createFsWatchInstance(path, options, fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS), errHandler, fsWatchBroadcast.bind(null, fullPath, KEY_RAW));
1268
+ if (!watcher) return;
1269
+ watcher.on(EV.ERROR, async (error) => {
1270
+ const broadcastErr = fsWatchBroadcast.bind(null, fullPath, KEY_ERR);
1271
+ if (cont) cont.watcherUnusable = true;
1272
+ if (isWindows && error.code === "EPERM") try {
1273
+ await (await open(path, "r")).close();
1274
+ broadcastErr(error);
1275
+ } catch (err) {}
1276
+ else broadcastErr(error);
1277
+ });
1278
+ cont = {
1279
+ listeners: listener,
1280
+ errHandlers: errHandler,
1281
+ rawEmitters: rawEmitter,
1282
+ watcher
1283
+ };
1284
+ FsWatchInstances.set(fullPath, cont);
1285
+ }
1286
+ return () => {
1287
+ delFromSet(cont, KEY_LISTENERS, listener);
1288
+ delFromSet(cont, KEY_ERR, errHandler);
1289
+ delFromSet(cont, KEY_RAW, rawEmitter);
1290
+ if (isEmptySet(cont.listeners)) {
1291
+ cont.watcher.close();
1292
+ FsWatchInstances.delete(fullPath);
1293
+ HANDLER_KEYS.forEach(clearItem(cont));
1294
+ cont.watcher = void 0;
1295
+ Object.freeze(cont);
1296
+ }
1297
+ };
1298
+ };
1299
+ const FsWatchFileInstances = /* @__PURE__ */ new Map();
1300
+ /**
1301
+ * Instantiates the fs_watchFile interface or binds listeners
1302
+ * to an existing one covering the same file system entry
1303
+ * @param path to be watched
1304
+ * @param fullPath absolute path
1305
+ * @param options options to be passed to fs_watchFile
1306
+ * @param handlers container for event listener functions
1307
+ * @returns closer
1308
+ */
1309
+ const setFsWatchFileListener = (path, fullPath, options, handlers) => {
1310
+ const { listener, rawEmitter } = handlers;
1311
+ let cont = FsWatchFileInstances.get(fullPath);
1312
+ const copts = cont && cont.options;
1313
+ if (copts && (copts.persistent < options.persistent || copts.interval > options.interval)) {
1314
+ unwatchFile(fullPath);
1315
+ cont = void 0;
1316
+ }
1317
+ if (cont) {
1318
+ addAndConvert(cont, KEY_LISTENERS, listener);
1319
+ addAndConvert(cont, KEY_RAW, rawEmitter);
1320
+ } else {
1321
+ cont = {
1322
+ listeners: listener,
1323
+ rawEmitters: rawEmitter,
1324
+ options,
1325
+ watcher: watchFile(fullPath, options, (curr, prev) => {
1326
+ foreach(cont.rawEmitters, (rawEmitter) => {
1327
+ rawEmitter(EV.CHANGE, fullPath, {
1328
+ curr,
1329
+ prev
1330
+ });
1331
+ });
1332
+ const currmtime = curr.mtimeMs;
1333
+ if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) foreach(cont.listeners, (listener) => listener(path, curr));
1334
+ })
1335
+ };
1336
+ FsWatchFileInstances.set(fullPath, cont);
1337
+ }
1338
+ return () => {
1339
+ delFromSet(cont, KEY_LISTENERS, listener);
1340
+ delFromSet(cont, KEY_RAW, rawEmitter);
1341
+ if (isEmptySet(cont.listeners)) {
1342
+ FsWatchFileInstances.delete(fullPath);
1343
+ unwatchFile(fullPath);
1344
+ cont.options = cont.watcher = void 0;
1345
+ Object.freeze(cont);
1346
+ }
1347
+ };
1348
+ };
1349
+ /**
1350
+ * @mixin
1351
+ */
1352
+ var NodeFsHandler = class {
1353
+ constructor(fsW) {
1354
+ this.fsw = fsW;
1355
+ this._boundHandleError = (error) => fsW._handleError(error);
1356
+ }
1357
+ /**
1358
+ * Watch file for changes with fs_watchFile or fs_watch.
1359
+ * @param path to file or dir
1360
+ * @param listener on fs change
1361
+ * @returns closer for the watcher instance
1362
+ */
1363
+ _watchWithNodeFs(path, listener) {
1364
+ const opts = this.fsw.options;
1365
+ const directory = sysPath.dirname(path);
1366
+ const basename = sysPath.basename(path);
1367
+ this.fsw._getWatchedDir(directory).add(basename);
1368
+ const absolutePath = sysPath.resolve(path);
1369
+ const options = { persistent: opts.persistent };
1370
+ if (!listener) listener = EMPTY_FN;
1371
+ let closer;
1372
+ if (opts.usePolling) {
1373
+ options.interval = opts.interval !== opts.binaryInterval && isBinaryPath(basename) ? opts.binaryInterval : opts.interval;
1374
+ closer = setFsWatchFileListener(path, absolutePath, options, {
1375
+ listener,
1376
+ rawEmitter: this.fsw._emitRaw
1377
+ });
1378
+ } else closer = setFsWatchListener(path, absolutePath, options, {
1379
+ listener,
1380
+ errHandler: this._boundHandleError,
1381
+ rawEmitter: this.fsw._emitRaw
1382
+ });
1383
+ return closer;
1384
+ }
1385
+ /**
1386
+ * Watch a file and emit add event if warranted.
1387
+ * @returns closer for the watcher instance
1388
+ */
1389
+ _handleFile(file, stats, initialAdd) {
1390
+ if (this.fsw.closed) return;
1391
+ const dirname = sysPath.dirname(file);
1392
+ const basename = sysPath.basename(file);
1393
+ const parent = this.fsw._getWatchedDir(dirname);
1394
+ let prevStats = stats;
1395
+ if (parent.has(basename)) return;
1396
+ const listener = async (path, newStats) => {
1397
+ if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5)) return;
1398
+ if (!newStats || newStats.mtimeMs === 0) try {
1399
+ const newStats = await stat$1(file);
1400
+ if (this.fsw.closed) return;
1401
+ const at = newStats.atimeMs;
1402
+ const mt = newStats.mtimeMs;
1403
+ if (!at || at <= mt || mt !== prevStats.mtimeMs) this.fsw._emit(EV.CHANGE, file, newStats);
1404
+ if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats.ino) {
1405
+ this.fsw._closeFile(path);
1406
+ prevStats = newStats;
1407
+ const closer = this._watchWithNodeFs(file, listener);
1408
+ if (closer) this.fsw._addPathCloser(path, closer);
1409
+ } else prevStats = newStats;
1410
+ } catch (error) {
1411
+ this.fsw._remove(dirname, basename);
1412
+ }
1413
+ else if (parent.has(basename)) {
1414
+ const at = newStats.atimeMs;
1415
+ const mt = newStats.mtimeMs;
1416
+ if (!at || at <= mt || mt !== prevStats.mtimeMs) this.fsw._emit(EV.CHANGE, file, newStats);
1417
+ prevStats = newStats;
1418
+ }
1419
+ };
1420
+ const closer = this._watchWithNodeFs(file, listener);
1421
+ if (!(initialAdd && this.fsw.options.ignoreInitial) && this.fsw._isntIgnored(file)) {
1422
+ if (!this.fsw._throttle(EV.ADD, file, 0)) return;
1423
+ this.fsw._emit(EV.ADD, file, stats);
1424
+ }
1425
+ return closer;
1426
+ }
1427
+ /**
1428
+ * Handle symlinks encountered while reading a dir.
1429
+ * @param entry returned by readdirp
1430
+ * @param directory path of dir being read
1431
+ * @param path of this item
1432
+ * @param item basename of this item
1433
+ * @returns true if no more processing is needed for this entry.
1434
+ */
1435
+ async _handleSymlink(entry, directory, path, item) {
1436
+ if (this.fsw.closed) return;
1437
+ const full = entry.fullPath;
1438
+ const dir = this.fsw._getWatchedDir(directory);
1439
+ if (!this.fsw.options.followSymlinks) {
1440
+ this.fsw._incrReadyCount();
1441
+ let linkPath;
1442
+ try {
1443
+ linkPath = await realpath(path);
1444
+ } catch (e) {
1445
+ this.fsw._emitReady();
1446
+ return true;
1447
+ }
1448
+ if (this.fsw.closed) return;
1449
+ if (dir.has(item)) {
1450
+ if (this.fsw._symlinkPaths.get(full) !== linkPath) {
1451
+ this.fsw._symlinkPaths.set(full, linkPath);
1452
+ this.fsw._emit(EV.CHANGE, path, entry.stats);
1453
+ }
1454
+ } else {
1455
+ dir.add(item);
1456
+ this.fsw._symlinkPaths.set(full, linkPath);
1457
+ this.fsw._emit(EV.ADD, path, entry.stats);
1458
+ }
1459
+ this.fsw._emitReady();
1460
+ return true;
1461
+ }
1462
+ if (this.fsw._symlinkPaths.has(full)) return true;
1463
+ this.fsw._symlinkPaths.set(full, true);
1464
+ }
1465
+ _handleRead(directory, initialAdd, wh, target, dir, depth, throttler) {
1466
+ directory = sysPath.join(directory, "");
1467
+ throttler = this.fsw._throttle("readdir", directory, 1e3);
1468
+ if (!throttler) return;
1469
+ const previous = this.fsw._getWatchedDir(wh.path);
1470
+ const current = /* @__PURE__ */ new Set();
1471
+ let stream = this.fsw._readdirp(directory, {
1472
+ fileFilter: (entry) => wh.filterPath(entry),
1473
+ directoryFilter: (entry) => wh.filterDir(entry)
1474
+ });
1475
+ if (!stream) return;
1476
+ stream.on(STR_DATA, async (entry) => {
1477
+ if (this.fsw.closed) {
1478
+ stream = void 0;
1479
+ return;
1480
+ }
1481
+ const item = entry.path;
1482
+ let path = sysPath.join(directory, item);
1483
+ current.add(item);
1484
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path, item)) return;
1485
+ if (this.fsw.closed) {
1486
+ stream = void 0;
1487
+ return;
1488
+ }
1489
+ if (item === target || !target && !previous.has(item)) {
1490
+ this.fsw._incrReadyCount();
1491
+ path = sysPath.join(dir, sysPath.relative(dir, path));
1492
+ this._addToNodeFs(path, initialAdd, wh, depth + 1);
1493
+ }
1494
+ }).on(EV.ERROR, this._boundHandleError);
1495
+ return new Promise((resolve, reject) => {
1496
+ if (!stream) return reject();
1497
+ stream.once("end", () => {
1498
+ if (this.fsw.closed) {
1499
+ stream = void 0;
1500
+ return;
1501
+ }
1502
+ const wasThrottled = throttler ? throttler.clear() : false;
1503
+ resolve(void 0);
1504
+ previous.getChildren().filter((item) => {
1505
+ return item !== directory && !current.has(item);
1506
+ }).forEach((item) => {
1507
+ this.fsw._remove(directory, item);
1508
+ });
1509
+ stream = void 0;
1510
+ if (wasThrottled) this._handleRead(directory, false, wh, target, dir, depth, throttler);
1511
+ });
1512
+ });
1513
+ }
1514
+ /**
1515
+ * Read directory to add / remove files from `@watched` list and re-read it on change.
1516
+ * @param dir fs path
1517
+ * @param stats
1518
+ * @param initialAdd
1519
+ * @param depth relative to user-supplied path
1520
+ * @param target child path targeted for watch
1521
+ * @param wh Common watch helpers for this path
1522
+ * @param realpath
1523
+ * @returns closer for the watcher instance.
1524
+ */
1525
+ async _handleDir(dir, stats, initialAdd, depth, target, wh, realpath) {
1526
+ const parentDir = this.fsw._getWatchedDir(sysPath.dirname(dir));
1527
+ const tracked = parentDir.has(sysPath.basename(dir));
1528
+ if (!(initialAdd && this.fsw.options.ignoreInitial) && !target && !tracked) this.fsw._emit(EV.ADD_DIR, dir, stats);
1529
+ parentDir.add(sysPath.basename(dir));
1530
+ this.fsw._getWatchedDir(dir);
1531
+ let throttler;
1532
+ let closer;
1533
+ const oDepth = this.fsw.options.depth;
1534
+ if ((oDepth == null || depth <= oDepth) && !this.fsw._symlinkPaths.has(realpath)) {
1535
+ if (!target) {
1536
+ await this._handleRead(dir, initialAdd, wh, target, dir, depth, throttler);
1537
+ if (this.fsw.closed) return;
1538
+ }
1539
+ closer = this._watchWithNodeFs(dir, (dirPath, stats) => {
1540
+ if (stats && stats.mtimeMs === 0) return;
1541
+ this._handleRead(dirPath, false, wh, target, dir, depth, throttler);
1542
+ });
1543
+ }
1544
+ return closer;
1545
+ }
1546
+ /**
1547
+ * Handle added file, directory, or glob pattern.
1548
+ * Delegates call to _handleFile / _handleDir after checks.
1549
+ * @param path to file or ir
1550
+ * @param initialAdd was the file added at watch instantiation?
1551
+ * @param priorWh depth relative to user-supplied path
1552
+ * @param depth Child path actually targeted for watch
1553
+ * @param target Child path actually targeted for watch
1554
+ */
1555
+ async _addToNodeFs(path, initialAdd, priorWh, depth, target) {
1556
+ const ready = this.fsw._emitReady;
1557
+ if (this.fsw._isIgnored(path) || this.fsw.closed) {
1558
+ ready();
1559
+ return false;
1560
+ }
1561
+ const wh = this.fsw._getWatchHelpers(path);
1562
+ if (priorWh) {
1563
+ wh.filterPath = (entry) => priorWh.filterPath(entry);
1564
+ wh.filterDir = (entry) => priorWh.filterDir(entry);
1565
+ }
1566
+ try {
1567
+ const stats = await statMethods[wh.statMethod](wh.watchPath);
1568
+ if (this.fsw.closed) return;
1569
+ if (this.fsw._isIgnored(wh.watchPath, stats)) {
1570
+ ready();
1571
+ return false;
1572
+ }
1573
+ const follow = this.fsw.options.followSymlinks;
1574
+ let closer;
1575
+ if (stats.isDirectory()) {
1576
+ const absPath = sysPath.resolve(path);
1577
+ const targetPath = follow ? await realpath(path) : path;
1578
+ if (this.fsw.closed) return;
1579
+ closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
1580
+ if (this.fsw.closed) return;
1581
+ if (absPath !== targetPath && targetPath !== void 0) this.fsw._symlinkPaths.set(absPath, targetPath);
1582
+ } else if (stats.isSymbolicLink()) {
1583
+ const targetPath = follow ? await realpath(path) : path;
1584
+ if (this.fsw.closed) return;
1585
+ const parent = sysPath.dirname(wh.watchPath);
1586
+ this.fsw._getWatchedDir(parent).add(wh.watchPath);
1587
+ this.fsw._emit(EV.ADD, wh.watchPath, stats);
1588
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path, wh, targetPath);
1589
+ if (this.fsw.closed) return;
1590
+ if (targetPath !== void 0) this.fsw._symlinkPaths.set(sysPath.resolve(path), targetPath);
1591
+ } else closer = this._handleFile(wh.watchPath, stats, initialAdd);
1592
+ ready();
1593
+ if (closer) this.fsw._addPathCloser(path, closer);
1594
+ return false;
1595
+ } catch (error) {
1596
+ if (this.fsw._handleError(error)) {
1597
+ ready();
1598
+ return path;
1599
+ }
1600
+ }
1601
+ }
1602
+ };
1603
+ //#endregion
1604
+ //#region ../../0-framework/3-tooling/cli/node_modules/chokidar/esm/index.js
1605
+ /*! chokidar - MIT License (c) 2012 Paul Miller (paulmillr.com) */
1606
+ const SLASH = "/";
1607
+ const SLASH_SLASH = "//";
1608
+ const ONE_DOT = ".";
1609
+ const TWO_DOTS = "..";
1610
+ const STRING_TYPE = "string";
1611
+ const BACK_SLASH_RE = /\\/g;
1612
+ const DOUBLE_SLASH_RE = /\/\//;
1613
+ const DOT_RE = /\..*\.(sw[px])$|~$|\.subl.*\.tmp/;
1614
+ const REPLACER_RE = /^\.[/\\]/;
1615
+ function arrify(item) {
1616
+ return Array.isArray(item) ? item : [item];
1617
+ }
1618
+ const isMatcherObject = (matcher) => typeof matcher === "object" && matcher !== null && !(matcher instanceof RegExp);
1619
+ function createPattern(matcher) {
1620
+ if (typeof matcher === "function") return matcher;
1621
+ if (typeof matcher === "string") return (string) => matcher === string;
1622
+ if (matcher instanceof RegExp) return (string) => matcher.test(string);
1623
+ if (typeof matcher === "object" && matcher !== null) return (string) => {
1624
+ if (matcher.path === string) return true;
1625
+ if (matcher.recursive) {
1626
+ const relative = sysPath.relative(matcher.path, string);
1627
+ if (!relative) return false;
1628
+ return !relative.startsWith("..") && !sysPath.isAbsolute(relative);
1629
+ }
1630
+ return false;
1631
+ };
1632
+ return () => false;
1633
+ }
1634
+ function normalizePath(path) {
1635
+ if (typeof path !== "string") throw new Error("string expected");
1636
+ path = sysPath.normalize(path);
1637
+ path = path.replace(/\\/g, "/");
1638
+ let prepend = false;
1639
+ if (path.startsWith("//")) prepend = true;
1640
+ const DOUBLE_SLASH_RE = /\/\//;
1641
+ while (path.match(DOUBLE_SLASH_RE)) path = path.replace(DOUBLE_SLASH_RE, "/");
1642
+ if (prepend) path = "/" + path;
1643
+ return path;
1644
+ }
1645
+ function matchPatterns(patterns, testString, stats) {
1646
+ const path = normalizePath(testString);
1647
+ for (let index = 0; index < patterns.length; index++) {
1648
+ const pattern = patterns[index];
1649
+ if (pattern(path, stats)) return true;
1650
+ }
1651
+ return false;
1652
+ }
1653
+ function anymatch(matchers, testString) {
1654
+ if (matchers == null) throw new TypeError("anymatch: specify first argument");
1655
+ const patterns = arrify(matchers).map((matcher) => createPattern(matcher));
1656
+ if (testString == null) return (testString, stats) => {
1657
+ return matchPatterns(patterns, testString, stats);
1658
+ };
1659
+ return matchPatterns(patterns, testString);
1660
+ }
1661
+ const unifyPaths = (paths_) => {
1662
+ const paths = arrify(paths_).flat();
1663
+ if (!paths.every((p) => typeof p === STRING_TYPE)) throw new TypeError(`Non-string provided as watch path: ${paths}`);
1664
+ return paths.map(normalizePathToUnix);
1665
+ };
1666
+ const toUnix = (string) => {
1667
+ let str = string.replace(BACK_SLASH_RE, SLASH);
1668
+ let prepend = false;
1669
+ if (str.startsWith(SLASH_SLASH)) prepend = true;
1670
+ while (str.match(DOUBLE_SLASH_RE)) str = str.replace(DOUBLE_SLASH_RE, SLASH);
1671
+ if (prepend) str = SLASH + str;
1672
+ return str;
1673
+ };
1674
+ const normalizePathToUnix = (path) => toUnix(sysPath.normalize(toUnix(path)));
1675
+ const normalizeIgnored = (cwd = "") => (path) => {
1676
+ if (typeof path === "string") return normalizePathToUnix(sysPath.isAbsolute(path) ? path : sysPath.join(cwd, path));
1677
+ else return path;
1678
+ };
1679
+ const getAbsolutePath = (path, cwd) => {
1680
+ if (sysPath.isAbsolute(path)) return path;
1681
+ return sysPath.join(cwd, path);
1682
+ };
1683
+ const EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
1684
+ /**
1685
+ * Directory entry.
1686
+ */
1687
+ var DirEntry = class {
1688
+ constructor(dir, removeWatcher) {
1689
+ this.path = dir;
1690
+ this._removeWatcher = removeWatcher;
1691
+ this.items = /* @__PURE__ */ new Set();
1692
+ }
1693
+ add(item) {
1694
+ const { items } = this;
1695
+ if (!items) return;
1696
+ if (item !== ONE_DOT && item !== TWO_DOTS) items.add(item);
1697
+ }
1698
+ async remove(item) {
1699
+ const { items } = this;
1700
+ if (!items) return;
1701
+ items.delete(item);
1702
+ if (items.size > 0) return;
1703
+ const dir = this.path;
1704
+ try {
1705
+ await readdir(dir);
1706
+ } catch (err) {
1707
+ if (this._removeWatcher) this._removeWatcher(sysPath.dirname(dir), sysPath.basename(dir));
1708
+ }
1709
+ }
1710
+ has(item) {
1711
+ const { items } = this;
1712
+ if (!items) return;
1713
+ return items.has(item);
1714
+ }
1715
+ getChildren() {
1716
+ const { items } = this;
1717
+ if (!items) return [];
1718
+ return [...items.values()];
1719
+ }
1720
+ dispose() {
1721
+ this.items.clear();
1722
+ this.path = "";
1723
+ this._removeWatcher = EMPTY_FN;
1724
+ this.items = EMPTY_SET;
1725
+ Object.freeze(this);
1726
+ }
1727
+ };
1728
+ const STAT_METHOD_F = "stat";
1729
+ const STAT_METHOD_L = "lstat";
1730
+ var WatchHelper = class {
1731
+ constructor(path, follow, fsw) {
1732
+ this.fsw = fsw;
1733
+ const watchPath = path;
1734
+ this.path = path = path.replace(REPLACER_RE, "");
1735
+ this.watchPath = watchPath;
1736
+ this.fullWatchPath = sysPath.resolve(watchPath);
1737
+ this.dirParts = [];
1738
+ this.dirParts.forEach((parts) => {
1739
+ if (parts.length > 1) parts.pop();
1740
+ });
1741
+ this.followSymlinks = follow;
1742
+ this.statMethod = follow ? STAT_METHOD_F : STAT_METHOD_L;
1743
+ }
1744
+ entryPath(entry) {
1745
+ return sysPath.join(this.watchPath, sysPath.relative(this.watchPath, entry.fullPath));
1746
+ }
1747
+ filterPath(entry) {
1748
+ const { stats } = entry;
1749
+ if (stats && stats.isSymbolicLink()) return this.filterDir(entry);
1750
+ const resolvedPath = this.entryPath(entry);
1751
+ return this.fsw._isntIgnored(resolvedPath, stats) && this.fsw._hasReadPermissions(stats);
1752
+ }
1753
+ filterDir(entry) {
1754
+ return this.fsw._isntIgnored(this.entryPath(entry), entry.stats);
1755
+ }
1756
+ };
1757
+ /**
1758
+ * Watches files & directories for changes. Emitted events:
1759
+ * `add`, `addDir`, `change`, `unlink`, `unlinkDir`, `all`, `error`
1760
+ *
1761
+ * new FSWatcher()
1762
+ * .add(directories)
1763
+ * .on('add', path => log('File', path, 'was added'))
1764
+ */
1765
+ var FSWatcher = class extends EventEmitter {
1766
+ constructor(_opts = {}) {
1767
+ super();
1768
+ this.closed = false;
1769
+ this._closers = /* @__PURE__ */ new Map();
1770
+ this._ignoredPaths = /* @__PURE__ */ new Set();
1771
+ this._throttled = /* @__PURE__ */ new Map();
1772
+ this._streams = /* @__PURE__ */ new Set();
1773
+ this._symlinkPaths = /* @__PURE__ */ new Map();
1774
+ this._watched = /* @__PURE__ */ new Map();
1775
+ this._pendingWrites = /* @__PURE__ */ new Map();
1776
+ this._pendingUnlinks = /* @__PURE__ */ new Map();
1777
+ this._readyCount = 0;
1778
+ this._readyEmitted = false;
1779
+ const awf = _opts.awaitWriteFinish;
1780
+ const DEF_AWF = {
1781
+ stabilityThreshold: 2e3,
1782
+ pollInterval: 100
1783
+ };
1784
+ const opts = {
1785
+ persistent: true,
1786
+ ignoreInitial: false,
1787
+ ignorePermissionErrors: false,
1788
+ interval: 100,
1789
+ binaryInterval: 300,
1790
+ followSymlinks: true,
1791
+ usePolling: false,
1792
+ atomic: true,
1793
+ ..._opts,
1794
+ ignored: _opts.ignored ? arrify(_opts.ignored) : arrify([]),
1795
+ awaitWriteFinish: awf === true ? DEF_AWF : typeof awf === "object" ? {
1796
+ ...DEF_AWF,
1797
+ ...awf
1798
+ } : false
1799
+ };
1800
+ if (isIBMi) opts.usePolling = true;
1801
+ if (opts.atomic === void 0) opts.atomic = !opts.usePolling;
1802
+ const envPoll = process.env.CHOKIDAR_USEPOLLING;
1803
+ if (envPoll !== void 0) {
1804
+ const envLower = envPoll.toLowerCase();
1805
+ if (envLower === "false" || envLower === "0") opts.usePolling = false;
1806
+ else if (envLower === "true" || envLower === "1") opts.usePolling = true;
1807
+ else opts.usePolling = !!envLower;
1808
+ }
1809
+ const envInterval = process.env.CHOKIDAR_INTERVAL;
1810
+ if (envInterval) opts.interval = Number.parseInt(envInterval, 10);
1811
+ let readyCalls = 0;
1812
+ this._emitReady = () => {
1813
+ readyCalls++;
1814
+ if (readyCalls >= this._readyCount) {
1815
+ this._emitReady = EMPTY_FN;
1816
+ this._readyEmitted = true;
1817
+ process.nextTick(() => this.emit(EVENTS.READY));
1818
+ }
1819
+ };
1820
+ this._emitRaw = (...args) => this.emit(EVENTS.RAW, ...args);
1821
+ this._boundRemove = this._remove.bind(this);
1822
+ this.options = opts;
1823
+ this._nodeFsHandler = new NodeFsHandler(this);
1824
+ Object.freeze(opts);
1825
+ }
1826
+ _addIgnoredPath(matcher) {
1827
+ if (isMatcherObject(matcher)) {
1828
+ for (const ignored of this._ignoredPaths) if (isMatcherObject(ignored) && ignored.path === matcher.path && ignored.recursive === matcher.recursive) return;
1829
+ }
1830
+ this._ignoredPaths.add(matcher);
1831
+ }
1832
+ _removeIgnoredPath(matcher) {
1833
+ this._ignoredPaths.delete(matcher);
1834
+ if (typeof matcher === "string") {
1835
+ for (const ignored of this._ignoredPaths) if (isMatcherObject(ignored) && ignored.path === matcher) this._ignoredPaths.delete(ignored);
1836
+ }
1837
+ }
1838
+ /**
1839
+ * Adds paths to be watched on an existing FSWatcher instance.
1840
+ * @param paths_ file or file list. Other arguments are unused
1841
+ */
1842
+ add(paths_, _origAdd, _internal) {
1843
+ const { cwd } = this.options;
1844
+ this.closed = false;
1845
+ this._closePromise = void 0;
1846
+ let paths = unifyPaths(paths_);
1847
+ if (cwd) paths = paths.map((path) => {
1848
+ return getAbsolutePath(path, cwd);
1849
+ });
1850
+ paths.forEach((path) => {
1851
+ this._removeIgnoredPath(path);
1852
+ });
1853
+ this._userIgnored = void 0;
1854
+ if (!this._readyCount) this._readyCount = 0;
1855
+ this._readyCount += paths.length;
1856
+ Promise.all(paths.map(async (path) => {
1857
+ const res = await this._nodeFsHandler._addToNodeFs(path, !_internal, void 0, 0, _origAdd);
1858
+ if (res) this._emitReady();
1859
+ return res;
1860
+ })).then((results) => {
1861
+ if (this.closed) return;
1862
+ results.forEach((item) => {
1863
+ if (item) this.add(sysPath.dirname(item), sysPath.basename(_origAdd || item));
1864
+ });
1865
+ });
1866
+ return this;
1867
+ }
1868
+ /**
1869
+ * Close watchers or start ignoring events from specified paths.
1870
+ */
1871
+ unwatch(paths_) {
1872
+ if (this.closed) return this;
1873
+ const paths = unifyPaths(paths_);
1874
+ const { cwd } = this.options;
1875
+ paths.forEach((path) => {
1876
+ if (!sysPath.isAbsolute(path) && !this._closers.has(path)) {
1877
+ if (cwd) path = sysPath.join(cwd, path);
1878
+ path = sysPath.resolve(path);
1879
+ }
1880
+ this._closePath(path);
1881
+ this._addIgnoredPath(path);
1882
+ if (this._watched.has(path)) this._addIgnoredPath({
1883
+ path,
1884
+ recursive: true
1885
+ });
1886
+ this._userIgnored = void 0;
1887
+ });
1888
+ return this;
1889
+ }
1890
+ /**
1891
+ * Close watchers and remove all listeners from watched paths.
1892
+ */
1893
+ close() {
1894
+ if (this._closePromise) return this._closePromise;
1895
+ this.closed = true;
1896
+ this.removeAllListeners();
1897
+ const closers = [];
1898
+ this._closers.forEach((closerList) => closerList.forEach((closer) => {
1899
+ const promise = closer();
1900
+ if (promise instanceof Promise) closers.push(promise);
1901
+ }));
1902
+ this._streams.forEach((stream) => stream.destroy());
1903
+ this._userIgnored = void 0;
1904
+ this._readyCount = 0;
1905
+ this._readyEmitted = false;
1906
+ this._watched.forEach((dirent) => dirent.dispose());
1907
+ this._closers.clear();
1908
+ this._watched.clear();
1909
+ this._streams.clear();
1910
+ this._symlinkPaths.clear();
1911
+ this._throttled.clear();
1912
+ this._closePromise = closers.length ? Promise.all(closers).then(() => void 0) : Promise.resolve();
1913
+ return this._closePromise;
1914
+ }
1915
+ /**
1916
+ * Expose list of watched paths
1917
+ * @returns for chaining
1918
+ */
1919
+ getWatched() {
1920
+ const watchList = {};
1921
+ this._watched.forEach((entry, dir) => {
1922
+ const index = (this.options.cwd ? sysPath.relative(this.options.cwd, dir) : dir) || ONE_DOT;
1923
+ watchList[index] = entry.getChildren().sort();
1924
+ });
1925
+ return watchList;
1926
+ }
1927
+ emitWithAll(event, args) {
1928
+ this.emit(event, ...args);
1929
+ if (event !== EVENTS.ERROR) this.emit(EVENTS.ALL, event, ...args);
1930
+ }
1931
+ /**
1932
+ * Normalize and emit events.
1933
+ * Calling _emit DOES NOT MEAN emit() would be called!
1934
+ * @param event Type of event
1935
+ * @param path File or directory path
1936
+ * @param stats arguments to be passed with event
1937
+ * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
1938
+ */
1939
+ async _emit(event, path, stats) {
1940
+ if (this.closed) return;
1941
+ const opts = this.options;
1942
+ if (isWindows) path = sysPath.normalize(path);
1943
+ if (opts.cwd) path = sysPath.relative(opts.cwd, path);
1944
+ const args = [path];
1945
+ if (stats != null) args.push(stats);
1946
+ const awf = opts.awaitWriteFinish;
1947
+ let pw;
1948
+ if (awf && (pw = this._pendingWrites.get(path))) {
1949
+ pw.lastChange = /* @__PURE__ */ new Date();
1950
+ return this;
1951
+ }
1952
+ if (opts.atomic) {
1953
+ if (event === EVENTS.UNLINK) {
1954
+ this._pendingUnlinks.set(path, [event, ...args]);
1955
+ setTimeout(() => {
1956
+ this._pendingUnlinks.forEach((entry, path) => {
1957
+ this.emit(...entry);
1958
+ this.emit(EVENTS.ALL, ...entry);
1959
+ this._pendingUnlinks.delete(path);
1960
+ });
1961
+ }, typeof opts.atomic === "number" ? opts.atomic : 100);
1962
+ return this;
1963
+ }
1964
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path)) {
1965
+ event = EVENTS.CHANGE;
1966
+ this._pendingUnlinks.delete(path);
1967
+ }
1968
+ }
1969
+ if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
1970
+ const awfEmit = (err, stats) => {
1971
+ if (err) {
1972
+ event = EVENTS.ERROR;
1973
+ args[0] = err;
1974
+ this.emitWithAll(event, args);
1975
+ } else if (stats) {
1976
+ if (args.length > 1) args[1] = stats;
1977
+ else args.push(stats);
1978
+ this.emitWithAll(event, args);
1979
+ }
1980
+ };
1981
+ this._awaitWriteFinish(path, awf.stabilityThreshold, event, awfEmit);
1982
+ return this;
1983
+ }
1984
+ if (event === EVENTS.CHANGE) {
1985
+ if (!this._throttle(EVENTS.CHANGE, path, 50)) return this;
1986
+ }
1987
+ if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
1988
+ const fullPath = opts.cwd ? sysPath.join(opts.cwd, path) : path;
1989
+ let stats;
1990
+ try {
1991
+ stats = await stat$1(fullPath);
1992
+ } catch (err) {}
1993
+ if (!stats || this.closed) return;
1994
+ args.push(stats);
1995
+ }
1996
+ this.emitWithAll(event, args);
1997
+ return this;
1998
+ }
1999
+ /**
2000
+ * Common handler for errors
2001
+ * @returns The error if defined, otherwise the value of the FSWatcher instance's `closed` flag
2002
+ */
2003
+ _handleError(error) {
2004
+ const code = error && error.code;
2005
+ if (error && code !== "ENOENT" && code !== "ENOTDIR" && (!this.options.ignorePermissionErrors || code !== "EPERM" && code !== "EACCES")) this.emit(EVENTS.ERROR, error);
2006
+ return error || this.closed;
2007
+ }
2008
+ /**
2009
+ * Helper utility for throttling
2010
+ * @param actionType type being throttled
2011
+ * @param path being acted upon
2012
+ * @param timeout duration of time to suppress duplicate actions
2013
+ * @returns tracking object or false if action should be suppressed
2014
+ */
2015
+ _throttle(actionType, path, timeout) {
2016
+ if (!this._throttled.has(actionType)) this._throttled.set(actionType, /* @__PURE__ */ new Map());
2017
+ const action = this._throttled.get(actionType);
2018
+ if (!action) throw new Error("invalid throttle");
2019
+ const actionPath = action.get(path);
2020
+ if (actionPath) {
2021
+ actionPath.count++;
2022
+ return false;
2023
+ }
2024
+ let timeoutObject;
2025
+ const clear = () => {
2026
+ const item = action.get(path);
2027
+ const count = item ? item.count : 0;
2028
+ action.delete(path);
2029
+ clearTimeout(timeoutObject);
2030
+ if (item) clearTimeout(item.timeoutObject);
2031
+ return count;
2032
+ };
2033
+ timeoutObject = setTimeout(clear, timeout);
2034
+ const thr = {
2035
+ timeoutObject,
2036
+ clear,
2037
+ count: 0
2038
+ };
2039
+ action.set(path, thr);
2040
+ return thr;
2041
+ }
2042
+ _incrReadyCount() {
2043
+ return this._readyCount++;
2044
+ }
2045
+ /**
2046
+ * Awaits write operation to finish.
2047
+ * Polls a newly created file for size variations. When files size does not change for 'threshold' milliseconds calls callback.
2048
+ * @param path being acted upon
2049
+ * @param threshold Time in milliseconds a file size must be fixed before acknowledging write OP is finished
2050
+ * @param event
2051
+ * @param awfEmit Callback to be called when ready for event to be emitted.
2052
+ */
2053
+ _awaitWriteFinish(path, threshold, event, awfEmit) {
2054
+ const awf = this.options.awaitWriteFinish;
2055
+ if (typeof awf !== "object") return;
2056
+ const pollInterval = awf.pollInterval;
2057
+ let timeoutHandler;
2058
+ let fullPath = path;
2059
+ if (this.options.cwd && !sysPath.isAbsolute(path)) fullPath = sysPath.join(this.options.cwd, path);
2060
+ const now = /* @__PURE__ */ new Date();
2061
+ const writes = this._pendingWrites;
2062
+ function awaitWriteFinishFn(prevStat) {
2063
+ stat(fullPath, (err, curStat) => {
2064
+ if (err || !writes.has(path)) {
2065
+ if (err && err.code !== "ENOENT") awfEmit(err);
2066
+ return;
2067
+ }
2068
+ const now = Number(/* @__PURE__ */ new Date());
2069
+ if (prevStat && curStat.size !== prevStat.size) writes.get(path).lastChange = now;
2070
+ if (now - writes.get(path).lastChange >= threshold) {
2071
+ writes.delete(path);
2072
+ awfEmit(void 0, curStat);
2073
+ } else timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
2074
+ });
2075
+ }
2076
+ if (!writes.has(path)) {
2077
+ writes.set(path, {
2078
+ lastChange: now,
2079
+ cancelWait: () => {
2080
+ writes.delete(path);
2081
+ clearTimeout(timeoutHandler);
2082
+ return event;
2083
+ }
2084
+ });
2085
+ timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval);
2086
+ }
2087
+ }
2088
+ /**
2089
+ * Determines whether user has asked to ignore this path.
2090
+ */
2091
+ _isIgnored(path, stats) {
2092
+ if (this.options.atomic && DOT_RE.test(path)) return true;
2093
+ if (!this._userIgnored) {
2094
+ const { cwd } = this.options;
2095
+ const ignored = (this.options.ignored || []).map(normalizeIgnored(cwd));
2096
+ const list = [...[...this._ignoredPaths].map(normalizeIgnored(cwd)), ...ignored];
2097
+ this._userIgnored = anymatch(list, void 0);
2098
+ }
2099
+ return this._userIgnored(path, stats);
2100
+ }
2101
+ _isntIgnored(path, stat) {
2102
+ return !this._isIgnored(path, stat);
2103
+ }
2104
+ /**
2105
+ * Provides a set of common helpers and properties relating to symlink handling.
2106
+ * @param path file or directory pattern being watched
2107
+ */
2108
+ _getWatchHelpers(path) {
2109
+ return new WatchHelper(path, this.options.followSymlinks, this);
2110
+ }
2111
+ /**
2112
+ * Provides directory tracking objects
2113
+ * @param directory path of the directory
2114
+ */
2115
+ _getWatchedDir(directory) {
2116
+ const dir = sysPath.resolve(directory);
2117
+ if (!this._watched.has(dir)) this._watched.set(dir, new DirEntry(dir, this._boundRemove));
2118
+ return this._watched.get(dir);
2119
+ }
2120
+ /**
2121
+ * Check for read permissions: https://stackoverflow.com/a/11781404/1358405
2122
+ */
2123
+ _hasReadPermissions(stats) {
2124
+ if (this.options.ignorePermissionErrors) return true;
2125
+ return Boolean(Number(stats.mode) & 256);
2126
+ }
2127
+ /**
2128
+ * Handles emitting unlink events for
2129
+ * files and directories, and via recursion, for
2130
+ * files and directories within directories that are unlinked
2131
+ * @param directory within which the following item is located
2132
+ * @param item base path of item/directory
2133
+ */
2134
+ _remove(directory, item, isDirectory) {
2135
+ const path = sysPath.join(directory, item);
2136
+ const fullPath = sysPath.resolve(path);
2137
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path) || this._watched.has(fullPath);
2138
+ if (!this._throttle("remove", path, 100)) return;
2139
+ if (!isDirectory && this._watched.size === 1) this.add(directory, item, true);
2140
+ this._getWatchedDir(path).getChildren().forEach((nested) => this._remove(path, nested));
2141
+ const parent = this._getWatchedDir(directory);
2142
+ const wasTracked = parent.has(item);
2143
+ parent.remove(item);
2144
+ if (this._symlinkPaths.has(fullPath)) this._symlinkPaths.delete(fullPath);
2145
+ let relPath = path;
2146
+ if (this.options.cwd) relPath = sysPath.relative(this.options.cwd, path);
2147
+ if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
2148
+ if (this._pendingWrites.get(relPath).cancelWait() === EVENTS.ADD) return;
2149
+ }
2150
+ this._watched.delete(path);
2151
+ this._watched.delete(fullPath);
2152
+ const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
2153
+ if (wasTracked && !this._isIgnored(path)) this._emit(eventName, path);
2154
+ this._closePath(path);
2155
+ }
2156
+ /**
2157
+ * Closes all watchers for a path
2158
+ */
2159
+ _closePath(path) {
2160
+ this._closeFile(path);
2161
+ const dir = sysPath.dirname(path);
2162
+ this._getWatchedDir(dir).remove(sysPath.basename(path));
2163
+ }
2164
+ /**
2165
+ * Closes only file-specific watchers
2166
+ */
2167
+ _closeFile(path) {
2168
+ const closers = this._closers.get(path);
2169
+ if (!closers) return;
2170
+ closers.forEach((closer) => closer());
2171
+ this._closers.delete(path);
2172
+ }
2173
+ _addPathCloser(path, closer) {
2174
+ if (!closer) return;
2175
+ let list = this._closers.get(path);
2176
+ if (!list) {
2177
+ list = [];
2178
+ this._closers.set(path, list);
2179
+ }
2180
+ list.push(closer);
2181
+ }
2182
+ _readdirp(root, opts) {
2183
+ if (this.closed) return;
2184
+ let stream = readdirp(root, {
2185
+ type: EVENTS.ALL,
2186
+ alwaysStat: true,
2187
+ lstat: true,
2188
+ ...opts,
2189
+ depth: 0
2190
+ });
2191
+ this._streams.add(stream);
2192
+ stream.once(STR_CLOSE, () => {
2193
+ stream = void 0;
2194
+ });
2195
+ stream.once("end", () => {
2196
+ if (stream) {
2197
+ this._streams.delete(stream);
2198
+ stream = void 0;
2199
+ }
2200
+ });
2201
+ return stream;
2202
+ }
2203
+ };
2204
+ /**
2205
+ * Instantiates watcher with paths to be tracked.
2206
+ * @param paths file / directory paths
2207
+ * @param options opts, such as `atomic`, `awaitWriteFinish`, `ignored`, and others
2208
+ * @returns an instance of FSWatcher for chaining.
2209
+ * @example
2210
+ * const watcher = watch('.').on('all', (event, path) => { console.log(event, path); });
2211
+ * watch('.', { atomic: true, awaitWriteFinish: true, ignored: (f, stats) => stats?.isFile() && !f.endsWith('.js') })
2212
+ */
2213
+ function watch$1(paths, options = {}) {
2214
+ const watcher = new FSWatcher(options);
2215
+ watcher.add(paths);
2216
+ return watcher;
2217
+ }
2218
+ var esm_default = {
2219
+ watch: watch$1,
2220
+ FSWatcher
2221
+ };
2222
+ //#endregion
2223
+ //#region ../../0-framework/3-tooling/cli/dist/cli-B3LcFQLH.mjs
2224
+ /**
2225
+ * A user-facing failure with a message that already names the fix (deploy-cli.md
2226
+ * § Error surface). `bin.ts` catches this — and any other Error, including
2227
+ * core's LoadError/LowerError — uniformly: print the message, exit nonzero.
2228
+ */
2229
+ var CliError = class extends Error {
2230
+ constructor(message) {
2231
+ super(message);
2232
+ this.name = "CliError";
2233
+ }
2234
+ };
2235
+ /**
2236
+ * Pipeline step: find and load `prisma-composer.config.ts` (ADR-0017) — the ONE
2237
+ * file that imports control-plane code. Discovery is the standard walk-up
2238
+ * from the deploy entry's directory (mirrors prisma-next's config-loader);
2239
+ * loading is c12 with that explicit path (rc/global/package.json lookups
2240
+ * disabled), so the config file's own static imports resolve from the app
2241
+ * root by whatever package manager runs — no specifier construction, no
2242
+ * anchoring. The loaded shape is validated field-by-field with CliErrors
2243
+ * naming the field.
2244
+ */
2245
+ const CONFIG_FILENAME = "prisma-composer.config.ts";
2246
+ /** Walks UP from the entry file's directory looking for the literal CONFIG_FILENAME; undefined when the walk hits the filesystem root. */
2247
+ function findConfigPathForEntry(entryPath) {
2248
+ let current = path.dirname(path.resolve(entryPath));
2249
+ while (true) {
2250
+ const candidate = path.join(current, CONFIG_FILENAME);
2251
+ if (fs.existsSync(candidate)) return candidate;
2252
+ const parent = path.dirname(current);
2253
+ if (parent === current) return void 0;
2254
+ current = parent;
2255
+ }
2256
+ }
2257
+ function missingConfigError(entryPath) {
2258
+ return new CliError(`No ${CONFIG_FILENAME} found walking up from "${path.dirname(path.resolve(entryPath))}" — the deploy needs the app's config file. Create one next to (or above) the entry, default-exporting defineConfig({ extensions: [...], state: ... }) from '@prisma/composer/config'.`);
2259
+ }
2260
+ function fieldError(field, requirement) {
2261
+ return new CliError(`${CONFIG_FILENAME}: \`${field}\` ${requirement} — see defineConfig() in '@prisma/composer/config'.`);
2262
+ }
2263
+ function isRecord(value) {
2264
+ return typeof value === "object" && value !== null;
2265
+ }
2266
+ /**
2267
+ * Field-by-field validation of the loaded default export — deliberately no
2268
+ * schema library: each check is a CliError naming the offending field.
2269
+ * Returns the same object, typed.
714
2270
  */
715
2271
  function validateConfigShape(loaded, configPath) {
716
2272
  if (!isRecord(loaded) || Object.keys(loaded).length === 0) throw new CliError(`"${configPath}" exported no config — it must default-export defineConfig({ extensions: [...], state: ... }) from '@prisma/composer/config'.`);
@@ -808,6 +2364,70 @@ async function loadEntry(entryArg, cwd) {
808
2364
  root: blindCast(root)
809
2365
  };
810
2366
  }
2367
+ function lookup(extensions, extension, type, expectedKind, what) {
2368
+ const ext = extensions.get(extension);
2369
+ if (ext === void 0) throw new CliError(`No extension "${extension}" is configured (needed by ${what}) — add it to ${CONFIG_FILENAME}'s \`extensions\` (import its /control entry and list its descriptor).`);
2370
+ const descriptor = ext.nodes[type];
2371
+ if (descriptor === void 0) throw new CliError(`Extension "${extension}" has no descriptor for node type "${type}" (needed by ${what}; known: ${Object.keys(ext.nodes).join(", ")}).`);
2372
+ if (descriptor.kind !== expectedKind) throw new CliError(`Extension "${extension}"'s descriptor for node type "${type}" is a "${descriptor.kind}" descriptor — ${what} needs a "${expectedKind}" descriptor.`);
2373
+ }
2374
+ /** Throws a CliError on the first uncovered `(extension, type)`; silent when the config covers the whole graph. */
2375
+ function validateRegistryCoverage(graph, config) {
2376
+ const extensions = new Map(config.extensions.map((descriptor) => [descriptor.id, descriptor]));
2377
+ for (const { id, node } of graph.nodes) {
2378
+ if (node.kind === "resource") {
2379
+ lookup(extensions, node.extension, node.type, "resource", `resource node "${id}"`);
2380
+ continue;
2381
+ }
2382
+ if (node.kind !== "service") continue;
2383
+ lookup(extensions, node.extension, node.type, "service", `service node "${id}"`);
2384
+ lookup(extensions, node.build.extension, node.build.type, "build", `service node "${id}"'s build descriptor`);
2385
+ }
2386
+ }
2387
+ /**
2388
+ * The shared prefix of `deploy`/`destroy`/`dev` (deploy-cli.md § The
2389
+ * pipeline; local-dev spec § 6): config discovery/load, entry load, Load,
2390
+ * registry coverage validation, name resolution, assemble. Deploy and dev
2391
+ * diverge after this — deploy resolves containers/preflight/stack file
2392
+ * against the hosted providers, dev against the local ones — so everything
2393
+ * up to and including assemble lives here once, consumed verbatim by both
2394
+ * `run()` (main.ts) and `runDev()` (dev/run-dev.ts), so the two pipelines
2395
+ * cannot drift.
2396
+ */
2397
+ /**
2398
+ * Runs config discovery/load, entry load, Load, registry coverage, name
2399
+ * resolution, and assemble — steps 1–6 of `run()`. `onAssembleError`, when
2400
+ * given, lets a caller decorate an assemble failure with command-specific
2401
+ * guidance (destroy's "build first" hint) without this shared step knowing
2402
+ * about any one command.
2403
+ */
2404
+ async function runPipeline(entry, overrideName, cwd, deps = {}, onAssembleError) {
2405
+ const resolvedEntryPath = path.resolve(cwd, entry);
2406
+ const configPath = findConfigPathForEntry(resolvedEntryPath);
2407
+ if (configPath === void 0) throw missingConfigError(resolvedEntryPath);
2408
+ const config = deps.config ?? (await loadAppConfig(configPath)).config;
2409
+ const entryModule = await loadEntry(entry, cwd);
2410
+ const graph = Load(entryModule.root);
2411
+ if (graph.root.node.kind !== "module") throw new CliError("The deploy root must be a module — wrap your service, e.g. export default module('name', ({ provision }) => { provision(service); }).");
2412
+ validateRegistryCoverage(graph, config);
2413
+ const name = overrideName ?? entryModule.root.name;
2414
+ if (name.length === 0) throw new CliError("The root node has no name — name it at authoring, or pass --name.");
2415
+ let assembled;
2416
+ try {
2417
+ assembled = await assembleServices(graph, config, cwd, deps.runAssembler);
2418
+ } catch (error) {
2419
+ if (onAssembleError !== void 0 && error instanceof Error) throw onAssembleError(error);
2420
+ throw error;
2421
+ }
2422
+ return {
2423
+ configPath,
2424
+ config,
2425
+ entryModule,
2426
+ graph,
2427
+ name,
2428
+ assembled
2429
+ };
2430
+ }
811
2431
  /**
812
2432
  * Pipeline step 7 (deploy-cli.md § The pipeline; design-notes.md's "Driving
813
2433
  * Alchemy" call): shell out to the generated stack file. Resolves the
@@ -848,26 +2468,441 @@ function runAlchemy(input) {
848
2468
  if (result.error !== void 0) throw result.error;
849
2469
  return result.status ?? 1;
850
2470
  }
851
- function lookup(extensions, extension, type, expectedKind, what) {
852
- const ext = extensions.get(extension);
853
- if (ext === void 0) throw new CliError(`No extension "${extension}" is configured (needed by ${what}) — add it to ${CONFIG_FILENAME}'s \`extensions\` (import its /control entry and list its descriptor).`);
854
- const descriptor = ext.nodes[type];
855
- if (descriptor === void 0) throw new CliError(`Extension "${extension}" has no descriptor for node type "${type}" (needed by ${what}; known: ${Object.keys(ext.nodes).join(", ")}).`);
856
- if (descriptor.kind !== expectedKind) throw new CliError(`Extension "${extension}"'s descriptor for node type "${type}" is a "${descriptor.kind}" descriptor — ${what} needs a "${expectedKind}" descriptor.`);
2471
+ /**
2472
+ * Local-dev spec § 6 `generate-dev-stack.ts`: like generate-stack.ts but at
2473
+ * `.prisma-composer/dev/alchemy.run.ts`, always Alchemy stage `dev`, using
2474
+ * Alchemy's own `localState()` and no `report` — dev prints its own front
2475
+ * door (run-dev.ts), so core's presentation-free lower() gets nothing to
2476
+ * call back into. The generated module IS the one orchestration point where
2477
+ * dev provenance is resolved (spec § 3 REVISED): it deserializes the
2478
+ * containers, top-level-awaits `resolveLocalTargets(config)` (the lazy
2479
+ * `localTarget` thunks, ADR-0041), and passes
2480
+ * `providers: localTargetProviders(...)` + `state: localState()` explicitly
2481
+ * — `lower()` learns nothing about dev.
2482
+ */
2483
+ const DEV_GENERATED_DIR = ".prisma-composer/dev";
2484
+ const DEV_GENERATED_FILE = "alchemy.run.ts";
2485
+ /** A relative import specifier from `.prisma-composer/dev/alchemy.run.ts` to `target` (posix separators). */
2486
+ function relativeImportSpecifier$1(generatedDir, target) {
2487
+ const rel = path.relative(generatedDir, target).split(path.sep).join("/");
2488
+ return rel.startsWith(".") ? rel : `./${rel}`;
857
2489
  }
858
- /** Throws a CliError on the first uncovered `(extension, type)`; silent when the config covers the whole graph. */
859
- function validateRegistryCoverage(graph, config) {
860
- const extensions = new Map(config.extensions.map((descriptor) => [descriptor.id, descriptor]));
861
- for (const { id, node } of graph.nodes) {
862
- if (node.kind === "resource") {
863
- lookup(extensions, node.extension, node.type, "resource", `resource node "${id}"`);
2490
+ function quote$1(value) {
2491
+ return JSON.stringify(value);
2492
+ }
2493
+ function renderBundle$1(bundle) {
2494
+ return `{ dir: ${quote$1(bundle.dir)}, entry: ${quote$1(bundle.entry)} }`;
2495
+ }
2496
+ function renderOptions$1(input) {
2497
+ const lines = [];
2498
+ lines.push(` name: ${quote$1(input.name)},`);
2499
+ lines.push(" bundles: {");
2500
+ for (const [id, bundle] of Object.entries(input.assembled.bundles)) lines.push(` ${quote$1(id)}: ${renderBundle$1(bundle)},`);
2501
+ lines.push(" },");
2502
+ lines.push(" providers: localTargetProviders(resolved, containers, devDir),");
2503
+ lines.push(" state: localState(),");
2504
+ return lines.join("\n");
2505
+ }
2506
+ /** Renders the dev stack module's source (tests assert on it without touching disk). */
2507
+ function renderDevStackFile(input) {
2508
+ const generatedDir = path.join(input.cwd, DEV_GENERATED_DIR);
2509
+ const appImport = relativeImportSpecifier$1(generatedDir, input.entryPath);
2510
+ const configImport = relativeImportSpecifier$1(generatedDir, input.configPath);
2511
+ return `// Generated by \`prisma-composer dev\` — overwritten on every run; do not
2512
+ // edit by hand. Independently runnable from ${quote$1(input.cwd)}:
2513
+ //
2514
+ // alchemy deploy ${DEV_GENERATED_DIR}/${DEV_GENERATED_FILE} --stage dev
2515
+ //
2516
+ // bisects a CLI bug from an Alchemy bug (deploy-cli.md § Implementation decisions).
2517
+ import * as path from 'node:path';
2518
+ import { deserializeContainers } from '@prisma/composer/config';
2519
+ import { lower } from '@prisma/composer/deploy';
2520
+ import { DEV_DIR, localTargetProviders, resolveLocalTargets } from '@prisma/composer/local-target';
2521
+ import { localState } from 'alchemy/State/LocalState';
2522
+ import config from ${quote$1(configImport)};
2523
+ import app from ${quote$1(appImport)};
2524
+
2525
+ const containers = deserializeContainers(config.extensions, process.env);
2526
+ const resolved = await resolveLocalTargets(config);
2527
+ const devDir = path.join(process.cwd(), DEV_DIR);
2528
+
2529
+ export default lower(app, config, {
2530
+ ${renderOptions$1(input)}
2531
+ });
2532
+ `;
2533
+ }
2534
+ /** Writes the dev stack file, returning its absolute path. */
2535
+ function writeDevStackFile(input) {
2536
+ const generatedDir = path.join(input.cwd, DEV_GENERATED_DIR);
2537
+ fs.mkdirSync(generatedDir, { recursive: true });
2538
+ const filePath = path.join(generatedDir, DEV_GENERATED_FILE);
2539
+ fs.writeFileSync(filePath, renderDevStackFile(input));
2540
+ return filePath;
2541
+ }
2542
+ const DEV_STACK_RELATIVE_PATH = path.join(DEV_GENERATED_DIR, DEV_GENERATED_FILE);
2543
+ /**
2544
+ * Local-dev spec § 6 `watch.ts`: watches each assembled bundle's declared
2545
+ * `watch` paths (a directory watched recursively, a file watched through
2546
+ * its parent — see `startWatch`), debounced 300 ms and coalesced across
2547
+ * every service — one edit across several files/services still fires
2548
+ * exactly one rebuild.
2549
+ *
2550
+ * The watch ENGINE is `chokidar` v4 (operator decision, design tip 74272d8:
2551
+ * don't-reinvent-the-wheel beats the no-new-deps contract here — it absorbs
2552
+ * the atomic-rename class and the cross-platform recursive-watch
2553
+ * differences; v4 is pure JS, no native code, no glob surface — irrelevant
2554
+ * here anyway, since every target is a literal file or directory path,
2555
+ * never a pattern). The parent-directory indirection for FILE targets stays
2556
+ * OURS (`startWatch` explains why), as does the debounce: chokidar's own
2557
+ * `awaitWriteFinish` is a per-file "has this file's size stopped changing"
2558
+ * poll, a different semantic from "coalesce a burst across many files into
2559
+ * one callback," which is what the dev loop actually needs.
2560
+ */
2561
+ const DEBOUNCE_MS = 300;
2562
+ /** Bundles → watch targets, plus the addresses with nothing watchable (the pinned one-line startup note). */
2563
+ function watchTargetsFrom(bundles) {
2564
+ const targets = [];
2565
+ const unwatchable = [];
2566
+ for (const [address, bundle] of Object.entries(bundles)) {
2567
+ const paths = bundle.watch;
2568
+ if (paths === void 0 || paths.length === 0) {
2569
+ unwatchable.push(address);
864
2570
  continue;
865
2571
  }
866
- if (node.kind !== "service") continue;
867
- lookup(extensions, node.extension, node.type, "service", `service node "${id}"`);
868
- lookup(extensions, node.build.extension, node.build.type, "build", `service node "${id}"'s build descriptor`);
2572
+ targets.push({
2573
+ address,
2574
+ paths
2575
+ });
869
2576
  }
2577
+ return {
2578
+ targets,
2579
+ unwatchable
2580
+ };
870
2581
  }
2582
+ /**
2583
+ * Watches every target's paths via chokidar, debounced 300 ms and coalesced
2584
+ * across every service, invoking `onChange` once per burst.
2585
+ *
2586
+ * File targets are watched THROUGH their parent directory, filtered to the
2587
+ * exact path: a watch bound directly to a file dies with the file's inode
2588
+ * on Linux, so `rm -rf dist && bun build --outfile dist/x.mjs` — every
2589
+ * rebuild's shape — would go silently unobserved after the first delete
2590
+ * (chokidar absorbs atomic renames, not unlink+recreate of a directly
2591
+ * watched file; proven by the delete-recreate test failing on Linux CI
2592
+ * only). Directory targets are watched recursively as themselves; a
2593
+ * nonexistent path is treated as a file target, so it starts reporting the
2594
+ * moment something creates it.
2595
+ */
2596
+ function startWatch(targets, onChange) {
2597
+ let timer;
2598
+ const trigger = () => {
2599
+ if (timer !== void 0) clearTimeout(timer);
2600
+ timer = setTimeout(() => {
2601
+ timer = void 0;
2602
+ onChange();
2603
+ }, DEBOUNCE_MS);
2604
+ };
2605
+ const fileTargets = /* @__PURE__ */ new Set();
2606
+ const directoryRoots = /* @__PURE__ */ new Set();
2607
+ const parentRoots = /* @__PURE__ */ new Set();
2608
+ for (const target of targets) for (const p of target.paths) {
2609
+ const abs = path.resolve(p);
2610
+ let isDirectory = false;
2611
+ try {
2612
+ isDirectory = fs.statSync(abs).isDirectory();
2613
+ } catch {}
2614
+ if (isDirectory) directoryRoots.add(abs);
2615
+ else {
2616
+ fileTargets.add(abs);
2617
+ parentRoots.add(path.dirname(abs));
2618
+ }
2619
+ }
2620
+ const reportError = (error) => {
2621
+ console.error(`[dev] watch error: ${error instanceof Error ? error.message : String(error)}`);
2622
+ };
2623
+ const watchers = [];
2624
+ if (directoryRoots.size > 0) {
2625
+ const directoryWatcher = esm_default.watch([...directoryRoots], { ignoreInitial: true });
2626
+ directoryWatcher.on("all", () => trigger());
2627
+ directoryWatcher.on("error", reportError);
2628
+ watchers.push(directoryWatcher);
2629
+ }
2630
+ if (parentRoots.size > 0) {
2631
+ const parentWatcher = esm_default.watch([...parentRoots], {
2632
+ ignoreInitial: true,
2633
+ depth: 0
2634
+ });
2635
+ parentWatcher.on("all", (_event, eventPath) => {
2636
+ if (fileTargets.has(path.resolve(eventPath))) trigger();
2637
+ });
2638
+ parentWatcher.on("error", reportError);
2639
+ watchers.push(parentWatcher);
2640
+ }
2641
+ let markReady = () => {};
2642
+ const allReady = Promise.all(watchers.map((watcher) => new Promise((resolve) => watcher.on("ready", () => resolve())))).then(() => {});
2643
+ return {
2644
+ ready: Promise.race([allReady, new Promise((resolve) => markReady = resolve)]),
2645
+ stop: () => {
2646
+ if (timer !== void 0) clearTimeout(timer);
2647
+ markReady();
2648
+ for (const watcher of watchers) watcher.close();
2649
+ }
2650
+ };
2651
+ }
2652
+ /**
2653
+ * Local-dev spec § 6 `run-dev.ts`: `prisma-composer dev <entry>` — steps 1–6
2654
+ * of `run()` reused via pipeline.ts, then the dev-only pipeline: capability
2655
+ * check, containers, `--fresh` teardown, preflight, emulators, converge
2656
+ * against a generated dev stack file, attach (front door + merged logs),
2657
+ * watch loop until interrupted.
2658
+ */
2659
+ function toCliError(error) {
2660
+ return error instanceof CliError ? error : new CliError(error instanceof Error ? error.message : String(error));
2661
+ }
2662
+ /** `[dev] ready:` then one line per endpoint, ordered by address depth (fewest dots first) then lexicographic. Exported for tests. */
2663
+ function renderFrontDoor(endpoints) {
2664
+ return ["[dev] ready:", ...[...endpoints].sort((a, b) => {
2665
+ const depthA = a.address.split(".").length;
2666
+ const depthB = b.address.split(".").length;
2667
+ if (depthA !== depthB) return depthA - depthB;
2668
+ return a.address < b.address ? -1 : a.address > b.address ? 1 : 0;
2669
+ }).map((e) => `[dev] ${e.address} ${e.url}`)];
2670
+ }
2671
+ function printFrontDoor(endpoints) {
2672
+ for (const line of renderFrontDoor(endpoints)) console.log(line);
2673
+ }
2674
+ const EMULATOR_RETRY_ATTEMPTS = 5;
2675
+ const EMULATOR_RETRY_DELAY_MS = 500;
2676
+ /** An emulator admin call right after a converge that just PUT dozens of resources through the same daemon can hit a transient refused/reset connection — a brief loopback hiccup under load, not a real failure. Retried before giving up. Applies to every attach admin call the dev session makes (`startServices`, `endpoints`). */
2677
+ async function withEmulatorRetry(call) {
2678
+ let lastError;
2679
+ for (let attempt = 1; attempt <= EMULATOR_RETRY_ATTEMPTS; attempt += 1) try {
2680
+ return await call();
2681
+ } catch (error) {
2682
+ lastError = error;
2683
+ if (attempt < EMULATOR_RETRY_ATTEMPTS) await new Promise((resolve) => setTimeout(resolve, EMULATOR_RETRY_DELAY_MS));
2684
+ }
2685
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
2686
+ }
2687
+ async function mergedEndpoints(attachments) {
2688
+ return (await Promise.all(attachments.map((a) => withEmulatorRetry(() => a.endpoints())))).flat();
2689
+ }
2690
+ /** Pumps every attachment's merged log stream to stdout, each line prefixed `[<service>] `, until `signal` aborts. */
2691
+ function pumpLogs(attachments, signal) {
2692
+ for (const attachment of attachments) (async () => {
2693
+ try {
2694
+ for await (const { service, line } of attachment.logs(signal)) {
2695
+ if (signal.aborted) return;
2696
+ console.log(`[${service}] ${line}`);
2697
+ }
2698
+ } catch (error) {
2699
+ if (!signal.aborted) console.error(`[dev] log stream failed: ${error instanceof Error ? error.message : String(error)}`);
2700
+ }
2701
+ })();
2702
+ }
2703
+ /** Runs the full dev pipeline; returns the process exit code. */
2704
+ async function runDev(args, deps = {}) {
2705
+ if (process.platform === "win32") throw new CliError("local dev is not supported on Windows yet.");
2706
+ const cwd = process.cwd();
2707
+ const devDir = path.join(cwd, DEV_DIR);
2708
+ const pipelineDeps = {
2709
+ runAssembler: deps.runAssembler,
2710
+ config: deps.config
2711
+ };
2712
+ const { configPath, config, entryModule, graph, name, assembled } = await runPipeline(args.entry, args.name, cwd, pipelineDeps);
2713
+ let resolved;
2714
+ try {
2715
+ resolved = await resolveLocalTargets(config);
2716
+ } catch (error) {
2717
+ throw toCliError(error);
2718
+ }
2719
+ const containers = /* @__PURE__ */ new Map();
2720
+ for (const [id, dev] of resolved) try {
2721
+ containers.set(id, await dev.container.ensure({
2722
+ appName: name,
2723
+ stage: void 0
2724
+ }));
2725
+ } catch (error) {
2726
+ throw toCliError(error);
2727
+ }
2728
+ if (args.fresh) for (const [id, dev] of resolved) {
2729
+ if (dev.teardown === void 0) continue;
2730
+ try {
2731
+ await dev.teardown({
2732
+ container: containers.get(id),
2733
+ stage: void 0
2734
+ });
2735
+ } catch (error) {
2736
+ throw toCliError(error);
2737
+ }
2738
+ }
2739
+ for (const [id, dev] of resolved) {
2740
+ if (dev.preflight === void 0) continue;
2741
+ try {
2742
+ await dev.preflight({
2743
+ graph,
2744
+ container: containers.get(id),
2745
+ stage: void 0
2746
+ });
2747
+ } catch (error) {
2748
+ throw toCliError(error);
2749
+ }
2750
+ }
2751
+ for (const [id, dev] of resolved) {
2752
+ if (dev.emulators === void 0) continue;
2753
+ try {
2754
+ await dev.emulators({
2755
+ graph,
2756
+ container: containers.get(id),
2757
+ devDir
2758
+ });
2759
+ } catch (error) {
2760
+ throw toCliError(error);
2761
+ }
2762
+ }
2763
+ const converge = () => {
2764
+ const stackPath = writeDevStackFile({
2765
+ entryPath: entryModule.path,
2766
+ cwd,
2767
+ configPath,
2768
+ name,
2769
+ assembled
2770
+ });
2771
+ const status = (deps.alchemy ?? runAlchemy)({
2772
+ command: "deploy",
2773
+ stackFileRelativePath: DEV_STACK_RELATIVE_PATH,
2774
+ cwd,
2775
+ stage: "dev",
2776
+ containerEnv: containerEnv(containers)
2777
+ });
2778
+ if (status !== 0) {
2779
+ console.error(`\nGenerated stack file: ${stackPath}`);
2780
+ console.error(`Run \`alchemy deploy ${DEV_STACK_RELATIVE_PATH} --yes --stage dev\` from ${cwd} to reproduce this directly.`);
2781
+ }
2782
+ return status;
2783
+ };
2784
+ const firstStatus = converge();
2785
+ if (firstStatus !== 0) return firstStatus;
2786
+ const attachments = [];
2787
+ for (const [id, dev] of resolved) attachments.push(await dev.attach({
2788
+ container: containers.get(id),
2789
+ devDir
2790
+ }));
2791
+ const started = [];
2792
+ for (const attachment of attachments) try {
2793
+ await withEmulatorRetry(() => attachment.startServices());
2794
+ started.push(attachment);
2795
+ } catch (error) {
2796
+ await Promise.all(started.map((a) => a.stopServices().catch(() => void 0)));
2797
+ throw toCliError(error);
2798
+ }
2799
+ printFrontDoor(await mergedEndpoints(attachments));
2800
+ const logsController = new AbortController();
2801
+ pumpLogs(attachments, logsController.signal);
2802
+ const { targets, unwatchable } = watchTargetsFrom(assembled.bundles);
2803
+ for (const address of unwatchable) console.log(`[dev] ${address} has no watchable inputs`);
2804
+ const watch = startWatch(targets, () => {
2805
+ (async () => {
2806
+ try {
2807
+ const rePipeline = await runPipeline(args.entry, args.name, cwd, pipelineDeps);
2808
+ writeDevStackFile({
2809
+ entryPath: rePipeline.entryModule.path,
2810
+ cwd,
2811
+ configPath: rePipeline.configPath,
2812
+ name: rePipeline.name,
2813
+ assembled: rePipeline.assembled
2814
+ });
2815
+ if ((deps.alchemy ?? runAlchemy)({
2816
+ command: "deploy",
2817
+ stackFileRelativePath: DEV_STACK_RELATIVE_PATH,
2818
+ cwd,
2819
+ stage: "dev",
2820
+ containerEnv: containerEnv(containers)
2821
+ }) !== 0) {
2822
+ console.error("[dev] converge failed — the running app is untouched; still watching.");
2823
+ return;
2824
+ }
2825
+ printFrontDoor(await mergedEndpoints(attachments));
2826
+ } catch (error) {
2827
+ console.error(`[dev] rebuild failed: ${error instanceof Error ? error.message : String(error)}`);
2828
+ }
2829
+ })();
2830
+ });
2831
+ await watch.ready;
2832
+ await new Promise((resolve) => {
2833
+ let stopping = false;
2834
+ const finish = () => {
2835
+ if (stopping) return;
2836
+ stopping = true;
2837
+ console.log("[dev] stopping — the app's services are stopping; emulators and data stay up.");
2838
+ watch.stop();
2839
+ (async () => {
2840
+ logsController.abort();
2841
+ for (const attachment of attachments) await attachment.stopServices().catch(() => void 0);
2842
+ console.log("[dev] stopped.");
2843
+ resolve();
2844
+ })();
2845
+ };
2846
+ process.removeAllListeners("SIGINT");
2847
+ process.removeAllListeners("SIGTERM");
2848
+ process.on("SIGINT", finish);
2849
+ process.on("SIGTERM", finish);
2850
+ });
2851
+ return 0;
2852
+ }
2853
+ /** Pipeline step 6: writes a regenerated-every-run, independently runnable stack module at `.prisma-composer/alchemy.run.ts`. */
2854
+ const GENERATED_DIR = ".prisma-composer";
2855
+ const GENERATED_FILE = "alchemy.run.ts";
2856
+ /** A relative import specifier from `.prisma-composer/alchemy.run.ts` to `target` (posix separators). */
2857
+ function relativeImportSpecifier(generatedDir, target) {
2858
+ const rel = path.relative(generatedDir, target).split(path.sep).join("/");
2859
+ return rel.startsWith(".") ? rel : `./${rel}`;
2860
+ }
2861
+ function quote(value) {
2862
+ return JSON.stringify(value);
2863
+ }
2864
+ function renderBundle(bundle) {
2865
+ return `{ dir: ${quote(bundle.dir)}, entry: ${quote(bundle.entry)} }`;
2866
+ }
2867
+ function renderOptions(input) {
2868
+ const lines = [];
2869
+ lines.push(` name: ${quote(input.name)},`);
2870
+ lines.push(" bundles: {");
2871
+ for (const [id, bundle] of Object.entries(input.assembled.bundles)) lines.push(` ${quote(id)}: ${renderBundle(bundle)},`);
2872
+ lines.push(" },");
2873
+ lines.push(" report: deploymentReport,");
2874
+ return lines.join("\n");
2875
+ }
2876
+ /** Renders the stack module's source (tests assert on it without touching disk) — uses `//` headers, not a block comment, since a cwd path with a star-slash could close one early. */
2877
+ function renderStackFile(input) {
2878
+ const generatedDir = path.join(input.cwd, GENERATED_DIR);
2879
+ const appImport = relativeImportSpecifier(generatedDir, input.entryPath);
2880
+ const configImport = relativeImportSpecifier(generatedDir, input.configPath);
2881
+ return `// Generated by \`prisma-composer deploy\`/\`prisma-composer destroy\` — overwritten on every
2882
+ // run; do not edit by hand. Independently runnable from ${quote(input.cwd)}:
2883
+ //
2884
+ // alchemy deploy ${GENERATED_DIR}/${GENERATED_FILE}
2885
+ //
2886
+ // bisects a CLI bug from an Alchemy bug (deploy-cli.md § Implementation decisions).
2887
+ import { lower } from '@prisma/composer/deploy';
2888
+ import { deploymentReport } from '@prisma/composer/report';
2889
+ import config from ${quote(configImport)};
2890
+ import app from ${quote(appImport)};
2891
+
2892
+ export default lower(app, config, {
2893
+ ${renderOptions(input)}
2894
+ });
2895
+ `;
2896
+ }
2897
+ /** Writes the stack file, returning its absolute path. */
2898
+ function writeStackFile(input) {
2899
+ const generatedDir = path.join(input.cwd, GENERATED_DIR);
2900
+ fs.mkdirSync(generatedDir, { recursive: true });
2901
+ const filePath = path.join(generatedDir, GENERATED_FILE);
2902
+ fs.writeFileSync(filePath, renderStackFile(input));
2903
+ return filePath;
2904
+ }
2905
+ const GENERATED_STACK_RELATIVE_PATH = path.join(GENERATED_DIR, GENERATED_FILE);
871
2906
  /** A stage name must be a valid git ref (deploy-cli.md) — checked via `git check-ref-format`, never silently normalized. Runs before anything platform-specific. */
872
2907
  function validateStageName(stage) {
873
2908
  const result = spawnSync("git", ["check-ref-format", `refs/heads/${stage}`], { stdio: "ignore" });
@@ -906,8 +2941,26 @@ var DestroyCommand = class extends DeployCliCommand {
906
2941
  });
907
2942
  action = "destroy";
908
2943
  };
2944
+ /** `<entry>`/`--name`/`--fresh` only — no `--stage`/`--production` (local-dev spec § 6: a working directory has exactly one dev instance, no stages). */
2945
+ var DevCommand = class extends Command {
2946
+ static paths = [["dev"]];
2947
+ static usage = Command.Usage({
2948
+ description: "Bring up the application whose root node is <entry>'s default export, entirely on this machine, credential-free.",
2949
+ examples: [["Run an app locally", "$0 dev src/service.ts"]]
2950
+ });
2951
+ entry = Option.String({ name: "entry" });
2952
+ name = Option.String("--name", { description: "Override the root node's name — the dev instance's application name." });
2953
+ fresh = Option.Boolean("--fresh", false, { description: "Destroy the dev stack and wipe the dev state directory before starting." });
2954
+ async execute() {
2955
+ return 0;
2956
+ }
2957
+ };
909
2958
  function buildCli() {
910
- return Cli.from([DeployCommand, DestroyCommand], {
2959
+ return Cli.from([
2960
+ DeployCommand,
2961
+ DestroyCommand,
2962
+ DevCommand
2963
+ ], {
911
2964
  binaryName: BINARY_NAME,
912
2965
  binaryLabel: "The prisma-composer deploy CLI"
913
2966
  });
@@ -935,7 +2988,16 @@ function parseArgs(argv) {
935
2988
  entry: command.entry,
936
2989
  name: command.name,
937
2990
  stage: command.stage,
938
- production: command.production
2991
+ production: command.production,
2992
+ fresh: false
2993
+ };
2994
+ if (command instanceof DevCommand) return {
2995
+ command: "dev",
2996
+ entry: command.entry,
2997
+ name: command.name,
2998
+ stage: void 0,
2999
+ production: false,
3000
+ fresh: command.fresh
939
3001
  };
940
3002
  if (argv.includes("--help") || argv.includes("-h")) throw new HelpRequested(cli.usage(null, { detailed: true }));
941
3003
  throw new UsageError(cli.usage(null, { detailed: true }));
@@ -968,27 +3030,17 @@ async function run(argv, deps = {}) {
968
3030
  }
969
3031
  throw error;
970
3032
  }
3033
+ if (args.command === "dev") return runDev(args, deps);
971
3034
  const stage = effectiveStage(args);
972
3035
  if (stage !== void 0) validateStageName(stage);
973
3036
  const cwd = process.cwd();
974
3037
  if (args.command === "destroy") warnIfNoLocalDeployState(cwd);
975
- const resolvedEntryPath = path.resolve(cwd, args.entry);
976
- const configPath = findConfigPathForEntry(resolvedEntryPath);
977
- if (configPath === void 0) throw missingConfigError(resolvedEntryPath);
978
- const config = deps.config ?? (await loadAppConfig(configPath)).config;
979
- const entryModule = await loadEntry(args.entry, cwd);
980
- const graph = Load(entryModule.root);
981
- if (graph.root.node.kind !== "module") throw new CliError("The deploy root must be a module — wrap your service, e.g. export default module('name', ({ provision }) => { provision(service); }).");
982
- validateRegistryCoverage(graph, config);
983
- const name = args.name ?? entryModule.root.name;
984
- if (name.length === 0) throw new CliError("The root node has no name — name it at authoring, or pass --name.");
985
- let assembled;
986
- try {
987
- assembled = await assembleServices(graph, config, cwd, deps.runAssembler);
988
- } catch (error) {
989
- if (args.command === "destroy" && error instanceof Error) throw new CliError(`${error.message}\n\ndestroy evaluates the same stack program as deploy, which packages the built artifacts — so the app must be built first. Run the build, then retry the destroy.`);
990
- throw error;
991
- }
3038
+ const pipelineDeps = {
3039
+ runAssembler: deps.runAssembler,
3040
+ config: deps.config
3041
+ };
3042
+ const onAssembleError = args.command === "destroy" ? (error) => new CliError(`${error.message}\n\ndestroy evaluates the same stack program as deploy, which packages the built artifacts — so the app must be built first. Run the build, then retry the destroy.`) : void 0;
3043
+ const { configPath, config, entryModule, graph, name, assembled } = await runPipeline(args.entry, args.name, cwd, pipelineDeps, onAssembleError);
992
3044
  const containers = /* @__PURE__ */ new Map();
993
3045
  for (const extension of config.extensions) {
994
3046
  if (extension.container === void 0) continue;