@prisma/composer 0.6.0-dev.2 → 0.6.0-dev.20
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/{app-config-Cbv_lA38-BHJsd1DF.d.mts → app-config-aIrriqVU-Dpj1RqnY.d.mts} +43 -17
- package/dist/app-config-aIrriqVU-Dpj1RqnY.d.mts.map +1 -0
- package/dist/config-BeBRB-6m.d.mts +1 -0
- package/dist/config.d.mts +3 -3
- package/dist/control.d.mts +219 -0
- package/dist/control.d.mts.map +1 -0
- package/dist/control.mjs +70 -0
- package/dist/control.mjs.map +1 -0
- package/dist/deploy-BeBRB-6m.d.mts +1 -0
- package/dist/deploy.d.mts +2 -2
- package/dist/deploy.mjs +312 -1
- package/dist/deploy.mjs.map +1 -0
- package/dist/deployment-summary-DswOl_9E-C03NJ98H.mjs +70 -0
- package/dist/deployment-summary-DswOl_9E-C03NJ98H.mjs.map +1 -0
- package/dist/{dist-BdvFDCLV.mjs → dist-C5rZnr4n.mjs} +2 -2
- package/dist/{dist-BdvFDCLV.mjs.map → dist-C5rZnr4n.mjs.map} +1 -1
- package/dist/emulator-retry-DGonap4g-S8BNrXFD.mjs +24 -0
- package/dist/emulator-retry-DGonap4g-S8BNrXFD.mjs.map +1 -0
- package/dist/errors-0e8IVwzi.mjs +88 -0
- package/dist/errors-0e8IVwzi.mjs.map +1 -0
- package/dist/execute-deploy-destroy-DfVJUICu-Dbgn1GR_.mjs +275 -0
- package/dist/execute-deploy-destroy-DfVJUICu-Dbgn1GR_.mjs.map +1 -0
- package/dist/execute-dev-BMTFWfFc-CGDFqR4w.mjs +2014 -0
- package/dist/execute-dev-BMTFWfFc-CGDFqR4w.mjs.map +1 -0
- package/dist/execute-log-Cay9hlKW-ChlIv192.mjs +160 -0
- package/dist/execute-log-Cay9hlKW-ChlIv192.mjs.map +1 -0
- package/dist/{graph-DgbALPDo-BEHF94BN.mjs → graph-BJgUfGB0-kvgFzT4q.mjs} +13 -7
- package/dist/graph-BJgUfGB0-kvgFzT4q.mjs.map +1 -0
- package/dist/{graph-DDpXh7S8-b-93BcHn.d.mts → graph-DqklX5Sf-BNS2aO1j.d.mts} +3 -3
- package/dist/graph-DqklX5Sf-BNS2aO1j.d.mts.map +1 -0
- package/dist/{graph-types-C3FOaaXm-C6_rGzfn.d.mts → graph-types-N6brq1zY-1VMNwtkJ.d.mts} +88 -5
- package/dist/graph-types-N6brq1zY-1VMNwtkJ.d.mts.map +1 -0
- package/dist/{index-BQVrMGfe.d.mts → index-DryPyF9U.d.mts} +3 -3
- package/dist/{index-BQVrMGfe.d.mts.map → index-DryPyF9U.d.mts.map} +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +2 -2
- package/dist/internal-error-BIc-ehme-Ck_lgxD_.mjs +18 -0
- package/dist/internal-error-BIc-ehme-Ck_lgxD_.mjs.map +1 -0
- package/dist/local-target-DnA2E8IN.mjs +47 -0
- package/dist/local-target-DnA2E8IN.mjs.map +1 -0
- package/dist/local-target.d.mts +1 -1
- package/dist/local-target.mjs +2 -46
- package/dist/{nextjs-DLyeRR7M-D47aMmfr.d.mts → nextjs-DLyeRR7M-DZODs9tk.d.mts} +3 -3
- package/dist/{nextjs-DLyeRR7M-D47aMmfr.d.mts.map → nextjs-DLyeRR7M-DZODs9tk.d.mts.map} +1 -1
- package/dist/nextjs-control.d.mts +4 -4
- package/dist/nextjs.d.mts +1 -1
- package/dist/node-control.d.mts +3 -3
- package/dist/node.d.mts +2 -2
- package/dist/pipeline-AoW8zq4I-CUon5KZH.mjs +452 -0
- package/dist/pipeline-AoW8zq4I-CUon5KZH.mjs.map +1 -0
- package/dist/report.d.mts +4 -3
- package/dist/report.d.mts.map +1 -1
- package/dist/report.mjs +4 -1
- package/dist/report.mjs.map +1 -1
- package/dist/result-CY_TB7bn.mjs +247 -0
- package/dist/result-CY_TB7bn.mjs.map +1 -0
- package/dist/run-alchemy-D44OZlyB-gVq_0gf4.mjs +93 -0
- package/dist/run-alchemy-D44OZlyB-gVq_0gf4.mjs.map +1 -0
- package/dist/service-rpc.d.mts +2 -2
- package/dist/service-rpc.mjs +2 -2
- package/dist/testing.d.mts +1 -1
- package/package.json +17 -21
- package/src/exports/control.ts +1 -0
- package/src/exports/testing.ts +5 -0
- package/dist/app-config-Cbv_lA38-BHJsd1DF.d.mts.map +0 -1
- package/dist/bin.mjs +0 -3283
- package/dist/bin.mjs.map +0 -1
- package/dist/config-whuqZ5xT.d.mts +0 -1
- package/dist/deploy-C4DxNkJ9-D73lmBqy.mjs +0 -313
- package/dist/deploy-C4DxNkJ9-D73lmBqy.mjs.map +0 -1
- package/dist/deploy-whuqZ5xT.d.mts +0 -1
- package/dist/graph-DDpXh7S8-b-93BcHn.d.mts.map +0 -1
- package/dist/graph-DgbALPDo-BEHF94BN.mjs.map +0 -1
- package/dist/graph-types-C3FOaaXm-C6_rGzfn.d.mts.map +0 -1
- package/dist/local-target.mjs.map +0 -1
package/dist/bin.mjs
DELETED
|
@@ -1,3283 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Cli, Command, Option, UsageError } from "clipanion";
|
|
3
|
-
import * as fs from "node:fs";
|
|
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";
|
|
9
|
-
import * as c12 from "c12";
|
|
10
|
-
import { pathToFileURL } from "node:url";
|
|
11
|
-
import { spawnSync } from "node:child_process";
|
|
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;
|
|
42
|
-
}
|
|
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";
|
|
49
|
-
/**
|
|
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.
|
|
56
|
-
*/
|
|
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;
|
|
59
|
-
}
|
|
60
|
-
//#endregion
|
|
61
|
-
//#region ../../0-framework/0-foundation/foundation/dist/casts-DpaahrlC.mjs
|
|
62
|
-
/**
|
|
63
|
-
* **Last-resort escape hatch for unsafe type assertions. Not a sanctioned tool to reach for.**
|
|
64
|
-
*
|
|
65
|
-
* Before reaching for `blindCast`, **rewrite the surrounding code so the cast becomes
|
|
66
|
-
* unnecessary**: tighten an input type, add a runtime check that narrows via a type
|
|
67
|
-
* predicate, restructure a generic so the compiler can see the relationship you're
|
|
68
|
-
* asserting, or use {@link castAs} when the value already satisfies the target type.
|
|
69
|
-
* Only when no rewrite is feasible does `blindCast` become the right answer — and at
|
|
70
|
-
* that point, the `Reason` literal you supply must articulate the compromise in
|
|
71
|
-
* language a reviewer can evaluate.
|
|
72
|
-
*
|
|
73
|
-
* The reviewer **will** validate the `Reason`. If it doesn't hold up under scrutiny,
|
|
74
|
-
* that is not a signal to soften the reason; it is a signal to go back and solve the
|
|
75
|
-
* underlying type-system problem properly. An unconvincing justification is rework,
|
|
76
|
-
* not a free pass.
|
|
77
|
-
*
|
|
78
|
-
* `blindCast` is the auditable form of `as Foo` / `as unknown as Foo`: it bypasses
|
|
79
|
-
* the compiler's checks (the input type is `unknown`, the output type is whatever the
|
|
80
|
-
* caller asks for), but it forces the unsafety to be named at the call site instead of
|
|
81
|
-
* smuggled in via a bare `as`. The `Reason` type parameter exists only at compile
|
|
82
|
-
* time — it is not present in the emitted JavaScript — but it is grep-able and
|
|
83
|
-
* visible to future readers.
|
|
84
|
-
*
|
|
85
|
-
* @example
|
|
86
|
-
* ```typescript
|
|
87
|
-
* const stringValue = blindCast<
|
|
88
|
-
* string,
|
|
89
|
-
* "JSON.parse returns `unknown`; this field is documented to be a string in the API contract"
|
|
90
|
-
* >(parsed[key]);
|
|
91
|
-
* ```
|
|
92
|
-
*
|
|
93
|
-
* @typeParam TargetType - The type the caller is asserting the input has.
|
|
94
|
-
* @typeParam _Reason - A string literal describing why bypassing the type system is necessary here.
|
|
95
|
-
* Only meaningful at compile time. The reviewer evaluates whether it justifies the unsafety.
|
|
96
|
-
*/
|
|
97
|
-
function blindCast(input) {
|
|
98
|
-
return input;
|
|
99
|
-
}
|
|
100
|
-
//#endregion
|
|
101
|
-
//#region ../../0-framework/1-core/core/dist/graph-DgbALPDo.mjs
|
|
102
|
-
/** Thrown by Load when the graph is malformed. */
|
|
103
|
-
var LoadError = class extends Error {
|
|
104
|
-
constructor(message) {
|
|
105
|
-
super(message);
|
|
106
|
-
this.name = "LoadError";
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
/**
|
|
110
|
-
* Core model: node types and the factories that construct them, plain frozen
|
|
111
|
-
* data objects. A node's `extension` + `type` form its deploy-time registry key (ADR-0017).
|
|
112
|
-
*/
|
|
113
|
-
const NODE = Symbol.for("prisma:node");
|
|
114
|
-
const SECRET_SOURCE = blindCast(Symbol.for("prisma:secret-source"));
|
|
115
|
-
/** True if `value` is a secret source (an `envSecret` result or a forwarded ctx.secrets ref). */
|
|
116
|
-
function isSecretSource(value) {
|
|
117
|
-
return typeof value === "object" && value !== null && blindCast(value)[SECRET_SOURCE] === true;
|
|
118
|
-
}
|
|
119
|
-
const PARAM_SOURCE = blindCast(Symbol.for("prisma:param-source"));
|
|
120
|
-
/** True if `value` is a param source (an `envParam` result or a forwarded ctx.params ref). */
|
|
121
|
-
function isParamSource(value) {
|
|
122
|
-
return typeof value === "object" && value !== null && blindCast(value)[PARAM_SOURCE] === true;
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Core's grammar for every name that becomes a config-key segment —
|
|
126
|
-
* addresses, input/param/secret names: ASCII letters and digits only.
|
|
127
|
-
* Conservative by design so any target medium — POSIX env-var keys
|
|
128
|
-
* included — can uppercase and "_"-join segments without escaping.
|
|
129
|
-
*/
|
|
130
|
-
function isConfigKeySegment(name) {
|
|
131
|
-
return /^[A-Za-z0-9]+$/.test(name);
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* True if `value` was constructed by this module's factories. Checks the
|
|
135
|
-
* brand only, never a prototype — a graph may mix nodes from a different
|
|
136
|
-
* installed copy of core (dual-package hazard).
|
|
137
|
-
*/
|
|
138
|
-
function isNode(value) {
|
|
139
|
-
return typeof value === "object" && value !== null && value[NODE] === true;
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Stable topological sort: every edge's `from` precedes its `to` in the
|
|
143
|
-
* result. Ties (nodes with no ordering constraint between them) keep their
|
|
144
|
-
* relative order from `nodes` — so a graph already authored producer-first
|
|
145
|
-
* comes out byte-identical to its pre-sort layout; only a graph that
|
|
146
|
-
* genuinely needs reordering (e.g. a module wired via a forged ref pointing at a
|
|
147
|
-
* not-yet-provisioned producer) actually moves. A Kahn's-algorithm variant
|
|
148
|
-
* that always picks the ready node with the smallest original index. Edges
|
|
149
|
-
* whose endpoint falls outside `nodes` (e.g. a service-root's input edges
|
|
150
|
-
* targeting the root, which is appended separately) are ignored. Cycles
|
|
151
|
-
* cannot reach here: `assertDependencyDag` already rejects them for
|
|
152
|
-
* dependency edges, and input edges never cycle.
|
|
153
|
-
*/
|
|
154
|
-
function topoSort(nodes, edges) {
|
|
155
|
-
const byId = new Map(nodes.map((n) => [n.id, n]));
|
|
156
|
-
const indexOf = new Map(nodes.map((n, i) => [n.id, i]));
|
|
157
|
-
const indegree = new Map(nodes.map((n) => [n.id, 0]));
|
|
158
|
-
const successors = /* @__PURE__ */ new Map();
|
|
159
|
-
for (const edge of edges) {
|
|
160
|
-
if (!byId.has(edge.from) || !byId.has(edge.to)) continue;
|
|
161
|
-
const targets = successors.get(edge.from) ?? [];
|
|
162
|
-
targets.push(edge.to);
|
|
163
|
-
successors.set(edge.from, targets);
|
|
164
|
-
indegree.set(edge.to, (indegree.get(edge.to) ?? 0) + 1);
|
|
165
|
-
}
|
|
166
|
-
const ready = new Set(nodes.filter((n) => indegree.get(n.id) === 0).map((n) => n.id));
|
|
167
|
-
const order = [];
|
|
168
|
-
while (ready.size > 0) {
|
|
169
|
-
let next;
|
|
170
|
-
let bestIndex = Number.POSITIVE_INFINITY;
|
|
171
|
-
for (const id of ready) {
|
|
172
|
-
const index = indexOf.get(id) ?? Number.POSITIVE_INFINITY;
|
|
173
|
-
if (index < bestIndex) {
|
|
174
|
-
bestIndex = index;
|
|
175
|
-
next = id;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (next === void 0) break;
|
|
179
|
-
ready.delete(next);
|
|
180
|
-
order.push(next);
|
|
181
|
-
for (const target of successors.get(next) ?? []) {
|
|
182
|
-
const remaining = (indegree.get(target) ?? 0) - 1;
|
|
183
|
-
indegree.set(target, remaining);
|
|
184
|
-
if (remaining === 0) ready.add(target);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
if (order.length !== nodes.length) throw new LoadError(`topological sort processed ${order.length} of ${nodes.length} nodes — the graph contains a cycle that slipped past the DAG validation.`);
|
|
188
|
-
return order.map((id) => byId.get(id)).filter((n) => n !== void 0);
|
|
189
|
-
}
|
|
190
|
-
function serviceInputs(service, serviceId) {
|
|
191
|
-
if (typeof service.inputs !== "object" || service.inputs === null) throw new LoadError(`Service "${serviceId}" has no inputs map.`);
|
|
192
|
-
const nodes = [];
|
|
193
|
-
const edges = [];
|
|
194
|
-
for (const [input, value] of Object.entries(service.inputs)) {
|
|
195
|
-
const kind = isNode(value) ? value.kind : void 0;
|
|
196
|
-
if (kind === "resource") throw new LoadError(`Input "${input}" of "${serviceId}" is a resource node — a resource is provisioned by the composing module, never created for a service that mentions it. Declare the input as a dependency (the pack's dependency factory) and wire the module-provisioned resource's ref into it.`);
|
|
197
|
-
if (kind !== "dependency") throw new LoadError(`Input "${input}" of "${serviceId}" is not a branded dependency end (construct it with the dependency() factory).`);
|
|
198
|
-
if (value.type.length === 0) throw new LoadError(`Input "${input}" of "${serviceId}" has an empty node type.`);
|
|
199
|
-
const id = `${serviceId}.${input}`;
|
|
200
|
-
nodes.push({
|
|
201
|
-
id,
|
|
202
|
-
node: value
|
|
203
|
-
});
|
|
204
|
-
edges.push({
|
|
205
|
-
from: id,
|
|
206
|
-
to: serviceId,
|
|
207
|
-
input,
|
|
208
|
-
kind: "input"
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
return {
|
|
212
|
-
nodes,
|
|
213
|
-
edges
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
function loadService(root, rootId) {
|
|
217
|
-
for (const [input, value] of Object.entries(root.inputs)) if (isNode(value) && value.kind === "dependency") throw new LoadError(`Service "${rootId}" has an unwired dependency input "${input}" — this service is composed by a module; deploy the module instead of loading "${rootId}" directly.`);
|
|
218
|
-
if (root.inputSchema !== void 0) throw new LoadError(`Service "${rootId}" declares an input schema but is being loaded directly — a lone service has no enclosing scope to bind its input. Compose it inside a module whose provision() call binds \`input: { … }\` (ADR-0042).`);
|
|
219
|
-
const rootGraphNode = {
|
|
220
|
-
id: rootId,
|
|
221
|
-
node: root
|
|
222
|
-
};
|
|
223
|
-
const { nodes, edges } = serviceInputs(root, rootId);
|
|
224
|
-
return {
|
|
225
|
-
root: rootGraphNode,
|
|
226
|
-
nodes: [...topoSort(nodes, edges), rootGraphNode],
|
|
227
|
-
edges,
|
|
228
|
-
inputBindings: [],
|
|
229
|
-
params: []
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Builds the ref a provision() call hands back: the id (so a producer with no
|
|
234
|
-
* exposed ports — or an untyped slot — can still be wired wholesale) plus one
|
|
235
|
-
* ref-port per exposed contract, each the contract's own runtime value (so
|
|
236
|
-
* its `satisfies()` still works) tagged with the provider's id.
|
|
237
|
-
*/
|
|
238
|
-
function refFor(id, service) {
|
|
239
|
-
const ports = {};
|
|
240
|
-
for (const [port, contract] of Object.entries(service.expose ?? {})) ports[port] = {
|
|
241
|
-
...contract,
|
|
242
|
-
__providerId: id
|
|
243
|
-
};
|
|
244
|
-
return blindCast({
|
|
245
|
-
id,
|
|
246
|
-
...ports
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* The resource variant of refFor: a resource has exactly one port — the
|
|
251
|
-
* contract it provides — flattened onto the ref itself, tagged with the
|
|
252
|
-
* provider id. `id` is written last so a hostile contract value cannot
|
|
253
|
-
* clobber it.
|
|
254
|
-
*/
|
|
255
|
-
function refForResource(id, resource) {
|
|
256
|
-
return blindCast({
|
|
257
|
-
...resource.provides,
|
|
258
|
-
__providerId: id,
|
|
259
|
-
id
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
/** A wired value's producer id: a ref-port's `__providerId`, or a bare ref's `id`. */
|
|
263
|
-
function producerIdOf(ref) {
|
|
264
|
-
if (typeof ref !== "object" || ref === null) return void 0;
|
|
265
|
-
if ("__providerId" in ref && typeof ref.__providerId === "string") return ref.__providerId;
|
|
266
|
-
if ("id" in ref && typeof ref.id === "string") return ref.id;
|
|
267
|
-
}
|
|
268
|
-
/**
|
|
269
|
-
* Brands each `ctx.inputs` entry with the input key it stands for (see
|
|
270
|
-
* flatten): diagnostic only — usage attribution relies on the per-key object
|
|
271
|
-
* identity the branding copy creates, never on reading this back.
|
|
272
|
-
*/
|
|
273
|
-
const MODULE_INPUT_KEY = Symbol("prisma:module-input-key");
|
|
274
|
-
/** Same per-key identity trick as MODULE_INPUT_KEY, for the parallel `ctx.secrets` forwarding channel. */
|
|
275
|
-
const MODULE_SECRET_KEY = Symbol("prisma:module-secret-key");
|
|
276
|
-
/** Same per-key identity trick as MODULE_INPUT_KEY, for the parallel `ctx.params` forwarding channel. */
|
|
277
|
-
const MODULE_PARAM_KEY = Symbol("prisma:module-param-key");
|
|
278
|
-
/** Whether `ref` carries a callable `satisfies` that accepts `required` truthily. */
|
|
279
|
-
function satisfiesRequired(ref, required) {
|
|
280
|
-
return typeof ref === "object" && ref !== null && "satisfies" in ref && typeof ref.satisfies === "function" && ref.satisfies(required);
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Checks one recorded `wiring` object against the Deps it was wired against:
|
|
284
|
-
* every named input exists and is a dependency slot, every referenced
|
|
285
|
-
* producer is a real (by-now-provisioned) address, and a wired ref whose
|
|
286
|
-
* slot declares a required contract must satisfy() it — no producer-kind
|
|
287
|
-
* branching, the contract alone determines validity, whether the producer is
|
|
288
|
-
* a service port or a resource. Shared by both provisioned kinds so a
|
|
289
|
-
* module-as-child gets exactly the checks a service gets, and run once per
|
|
290
|
-
* entry against the one `byId` shared by the whole recursive flatten, so a
|
|
291
|
-
* forwarded ref resolves through to its real producer address regardless of
|
|
292
|
-
* which ancestor scope provisioned it.
|
|
293
|
-
*/
|
|
294
|
-
function validateWiring(pending, byId) {
|
|
295
|
-
const { deps, wiring, targetId, targetKind, enclosingModuleName } = pending;
|
|
296
|
-
for (const [input, ref] of Object.entries(wiring)) {
|
|
297
|
-
const declared = blindCast(deps[input]);
|
|
298
|
-
if (declared === void 0 || !isNode(declared) || declared.kind !== "dependency") throw new LoadError(`The deps for "${targetId}" name "${input}", which is not a dependency slot of that ${targetKind}.`);
|
|
299
|
-
const producerId = producerIdOf(ref);
|
|
300
|
-
const producer = producerId !== void 0 ? byId.get(producerId) : void 0;
|
|
301
|
-
if (producerId === void 0 || producer === void 0) throw new LoadError(`The deps for "${targetId}.${input}" reference "${String(producerId)}", which is not provisioned in module "${enclosingModuleName}".`);
|
|
302
|
-
const required = declared.required;
|
|
303
|
-
if (required !== void 0 && !satisfiesRequired(ref, required)) throw new LoadError(`The deps for "${targetId}.${input}" do not satisfy the slot's required contract.`);
|
|
304
|
-
}
|
|
305
|
-
for (const [input, rawValue] of Object.entries(deps)) {
|
|
306
|
-
const value = blindCast(rawValue);
|
|
307
|
-
if (!isNode(value) || wiring[input] !== void 0) continue;
|
|
308
|
-
if (value.kind === "dependency") throw new LoadError(`Dependency input "${input}" of provisioned ${targetKind} "${targetId}" is not wired to a producer (module "${enclosingModuleName}").`);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Flattens a value into itself plus every nested object/array member value —
|
|
313
|
-
* identity fodder for the ctx.secrets/ctx.params usage marking, which must
|
|
314
|
-
* see a forwarded source wherever it sits inside an input binding (ADR-0042).
|
|
315
|
-
* A secret/param source is an opaque leaf: it is pushed (a forwarded ref IS
|
|
316
|
-
* such a source, so identity-matching still needs it) but never descended
|
|
317
|
-
* into, so a ref buried in an unrelated source's payload is not falsely seen
|
|
318
|
-
* as used. `seen` guards object cycles so a self-referential binding cannot
|
|
319
|
-
* recurse forever.
|
|
320
|
-
*/
|
|
321
|
-
function deepValues(value, out, seen = /* @__PURE__ */ new WeakSet()) {
|
|
322
|
-
out.push(value);
|
|
323
|
-
if (typeof value !== "object" || value === null) return;
|
|
324
|
-
if (isSecretSource(value) || isParamSource(value)) return;
|
|
325
|
-
if (seen.has(value)) return;
|
|
326
|
-
seen.add(value);
|
|
327
|
-
const members = Array.isArray(value) ? value : Object.values(value);
|
|
328
|
-
for (const member of members) deepValues(member, out, seen);
|
|
329
|
-
}
|
|
330
|
-
/** The (unvalidated) edges a `wiring` object implies — one dependency edge per entry; `validateWiring` does the real checking. */
|
|
331
|
-
function wiringEdges(wiring, targetId) {
|
|
332
|
-
return Object.entries(wiring).map(([input, ref]) => ({
|
|
333
|
-
from: producerIdOf(ref) ?? "",
|
|
334
|
-
to: targetId,
|
|
335
|
-
input,
|
|
336
|
-
kind: "dependency"
|
|
337
|
-
}));
|
|
338
|
-
}
|
|
339
|
-
/**
|
|
340
|
-
* Checks the secrets wired into one provisioned child MODULE: every declared
|
|
341
|
-
* forwarding slot is bound to a real secret source (an `envSecret` or a
|
|
342
|
-
* forwarded ctx.secrets ref), and no wired key names a slot the module
|
|
343
|
-
* doesn't declare — the secret analog of `validateWiring`'s per-input checks,
|
|
344
|
-
* but resolved inline (a source carries its own name, so no whole-graph pass
|
|
345
|
-
* is needed).
|
|
346
|
-
*/
|
|
347
|
-
function validateSecretBinding(child, id, secretWiring, enclosingModuleName) {
|
|
348
|
-
const { kind } = child;
|
|
349
|
-
for (const slot of Object.keys(child.secretSlots)) {
|
|
350
|
-
const bound = secretWiring[slot];
|
|
351
|
-
if (bound === void 0) throw new LoadError(`Secret slot "${slot}" of provisioned ${kind} "${id}" is not bound (module "${enclosingModuleName}") — bind it with envSecret('NAME') or forward ctx.secrets.`);
|
|
352
|
-
if (!isSecretSource(bound)) throw new LoadError(`Secret slot "${slot}" of "${id}" (module "${enclosingModuleName}") was wired with a non-secret value — use envSecret('NAME') or a forwarded ctx.secrets ref.`);
|
|
353
|
-
}
|
|
354
|
-
for (const slot of Object.keys(secretWiring)) if (!Object.hasOwn(child.secretSlots, slot)) throw new LoadError(`The secrets for "${id}" name "${slot}", which is not a secret slot of that ${kind} (module "${enclosingModuleName}").`);
|
|
355
|
-
}
|
|
356
|
-
/**
|
|
357
|
-
* Checks the params wired into a provisioned SERVICE: unlike a secret slot, a
|
|
358
|
-
* declared param is never required to be bound here — it may fall back to its
|
|
359
|
-
* own `default` (checked later, at `buildConfig`) — so this only rejects a
|
|
360
|
-
* wired key that names something other than a real declared param.
|
|
361
|
-
*/
|
|
362
|
-
function validateServiceParamBinding(child, id, paramWiring, enclosingModuleName) {
|
|
363
|
-
for (const slot of Object.keys(paramWiring)) if (!Object.hasOwn(child.params, slot)) throw new LoadError(`The params for "${id}" name "${slot}", which is not a param of that service (module "${enclosingModuleName}").`);
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* Checks the params wired into a provisioned MODULE: every declared
|
|
367
|
-
* param-forwarding slot must be bound to a real `ParamSource` — a slot has no
|
|
368
|
-
* schema of its own, so (unlike a service param) it cannot fall back to a
|
|
369
|
-
* literal or a default; the param analog of `validateSecretBinding`.
|
|
370
|
-
*/
|
|
371
|
-
function validateParamNeedBinding(child, id, paramWiring, enclosingModuleName) {
|
|
372
|
-
for (const slot of Object.keys(child.paramSlots)) {
|
|
373
|
-
const bound = paramWiring[slot];
|
|
374
|
-
if (bound === void 0) throw new LoadError(`Param slot "${slot}" of provisioned module "${id}" is not bound (module "${enclosingModuleName}") — bind it with a param source (e.g. envParam('NAME')) or forward ctx.params.`);
|
|
375
|
-
if (!isParamSource(bound)) throw new LoadError(`Param slot "${slot}" of "${id}" (module "${enclosingModuleName}") was wired with a non-source value — a module param-forwarding slot carries no schema to validate a literal against; use envParam('NAME') or a forwarded ctx.params ref.`);
|
|
376
|
-
}
|
|
377
|
-
for (const slot of Object.keys(paramWiring)) if (!Object.hasOwn(child.paramSlots, slot)) throw new LoadError(`The params for "${id}" name "${slot}", which is not a param slot of that module (module "${enclosingModuleName}").`);
|
|
378
|
-
}
|
|
379
|
-
/**
|
|
380
|
-
* Recursively flattens one module's body into the shared graph state and
|
|
381
|
-
* returns its resolved ModuleOutputs (one ref-port per expose key) for the
|
|
382
|
-
* caller (the enclosing provision() call, or Load itself for the root) to
|
|
383
|
-
* use. `address` is this module's OWN full address, or `undefined` for the root
|
|
384
|
-
* scope — its direct children then get bare (unprefixed) addresses, keeping
|
|
385
|
-
* a single-level module identical to before nesting existed. `wiring` supplies a
|
|
386
|
-
* resolved producer ref-port for each of this module's OWN declared deps (empty
|
|
387
|
-
* for the root, which may not declare any — see the root non-empty-deps
|
|
388
|
-
* check in loadModule). `nodes`, `edges`, `pending`, and `byId` are shared
|
|
389
|
-
* across the ENTIRE recursive flatten, not per scope — a nested module may
|
|
390
|
-
* forward in a producer provisioned by an ancestor scope, and it is the
|
|
391
|
-
* shared `byId` (keyed by full address) that lets that resolve.
|
|
392
|
-
*/
|
|
393
|
-
function flatten(moduleNode, address, wiring, secretWiring, paramWiring, nodes, edges, pending, inputBindings, paramBindings, byId) {
|
|
394
|
-
const localIds = /* @__PURE__ */ new Set();
|
|
395
|
-
const used = /* @__PURE__ */ new Set();
|
|
396
|
-
const usedSecrets = /* @__PURE__ */ new Set();
|
|
397
|
-
const usedParams = /* @__PURE__ */ new Set();
|
|
398
|
-
const ctxInputs = {};
|
|
399
|
-
for (const key of Object.keys(moduleNode.deps)) {
|
|
400
|
-
const wired = wiring[key];
|
|
401
|
-
ctxInputs[key] = typeof wired === "object" && wired !== null ? {
|
|
402
|
-
...wired,
|
|
403
|
-
[MODULE_INPUT_KEY]: key
|
|
404
|
-
} : wired;
|
|
405
|
-
}
|
|
406
|
-
const markUsed = (values) => {
|
|
407
|
-
for (const value of values) for (const key of Object.keys(ctxInputs)) if (value === ctxInputs[key]) used.add(key);
|
|
408
|
-
};
|
|
409
|
-
const ctxSecrets = {};
|
|
410
|
-
for (const key of Object.keys(moduleNode.secretSlots)) {
|
|
411
|
-
const bound = secretWiring[key];
|
|
412
|
-
ctxSecrets[key] = typeof bound === "object" && bound !== null ? {
|
|
413
|
-
...bound,
|
|
414
|
-
[MODULE_SECRET_KEY]: key
|
|
415
|
-
} : bound;
|
|
416
|
-
}
|
|
417
|
-
const markSecretsUsed = (values) => {
|
|
418
|
-
for (const value of values) for (const key of Object.keys(ctxSecrets)) if (value === ctxSecrets[key]) usedSecrets.add(key);
|
|
419
|
-
};
|
|
420
|
-
const ctxParams = {};
|
|
421
|
-
for (const key of Object.keys(moduleNode.paramSlots)) {
|
|
422
|
-
const bound = paramWiring[key];
|
|
423
|
-
ctxParams[key] = typeof bound === "object" && bound !== null ? {
|
|
424
|
-
...bound,
|
|
425
|
-
[MODULE_PARAM_KEY]: key
|
|
426
|
-
} : bound;
|
|
427
|
-
}
|
|
428
|
-
const markParamsUsed = (values) => {
|
|
429
|
-
for (const value of values) for (const key of Object.keys(ctxParams)) if (value === ctxParams[key]) usedParams.add(key);
|
|
430
|
-
};
|
|
431
|
-
const provision = (child, opts) => {
|
|
432
|
-
const id = opts?.id ?? child.name;
|
|
433
|
-
const provisionWiring = opts?.deps;
|
|
434
|
-
const provisionSecrets = opts?.secrets;
|
|
435
|
-
const provisionInput = opts?.input;
|
|
436
|
-
const provisionParams = opts?.params;
|
|
437
|
-
if (typeof id !== "string" || id.length === 0) throw new LoadError(`provision() requires a non-empty id (module "${moduleNode.name}").`);
|
|
438
|
-
if (!isConfigKeySegment(id)) throw new LoadError(`provision() id "${id}" (module "${moduleNode.name}") must contain only ASCII letters and digits ([A-Za-z0-9]) — the id becomes an address segment, and addresses derive deterministic config keys (segments uppercased and joined with "_"). "${id}" would put "${id.toUpperCase()}" inside the derived key. ("_" collides with the key separator and "." with the address separator.)`);
|
|
439
|
-
if (localIds.has(id)) throw new LoadError(`Duplicate provision id "${id}" in module "${moduleNode.name}".`);
|
|
440
|
-
const untrusted = child;
|
|
441
|
-
if (!isNode(untrusted) || untrusted.kind !== "service" && untrusted.kind !== "resource" && untrusted.kind !== "module") throw new LoadError(`provision("${id}") expects a branded service, resource, or module node (construct it with the service()/resource()/module() factories or a pack's own).`);
|
|
442
|
-
localIds.add(id);
|
|
443
|
-
const fullAddress = address === void 0 ? id : `${address}.${id}`;
|
|
444
|
-
if (child.kind === "resource") {
|
|
445
|
-
if (provisionWiring !== void 0) throw new LoadError(`provision("${id}") received deps for a resource — a resource has no dependency slots to satisfy.`);
|
|
446
|
-
if (provisionSecrets !== void 0) throw new LoadError(`provision("${id}") received secrets for a resource — a resource has no secret slots to satisfy.`);
|
|
447
|
-
if (provisionInput !== void 0) throw new LoadError(`provision("${id}") received an input binding for a resource — a resource declares no input schema.`);
|
|
448
|
-
if (provisionParams !== void 0) throw new LoadError(`provision("${id}") received params for a resource — a resource has no params to satisfy.`);
|
|
449
|
-
if (child.type.length === 0) throw new LoadError(`provision("${id}") received a resource with an empty node type.`);
|
|
450
|
-
byId.set(fullAddress, child);
|
|
451
|
-
nodes.push({
|
|
452
|
-
id: fullAddress,
|
|
453
|
-
node: child
|
|
454
|
-
});
|
|
455
|
-
return refForResource(fullAddress, child);
|
|
456
|
-
}
|
|
457
|
-
const localWiring = { ...provisionWiring ?? {} };
|
|
458
|
-
markUsed(Object.values(localWiring));
|
|
459
|
-
const localSecrets = { ...provisionSecrets ?? {} };
|
|
460
|
-
markSecretsUsed(Object.values(localSecrets));
|
|
461
|
-
const localParams = { ...provisionParams ?? {} };
|
|
462
|
-
markParamsUsed(Object.values(localParams));
|
|
463
|
-
if (child.kind === "service") {
|
|
464
|
-
if (provisionSecrets !== void 0) throw new LoadError(`provision("${id}") received secrets for a service — a service takes its secrets as envSecret(...) leaves of its \`input\` binding (ADR-0042), not a secrets map.`);
|
|
465
|
-
if (child.inputSchema !== void 0 && provisionInput === void 0) throw new LoadError(`Input of provisioned service "${id}" is not bound (module "${moduleNode.name}") — the service declares an input schema; bind it with \`input: { … }\` on its provision() call.`);
|
|
466
|
-
if (child.inputSchema === void 0 && provisionInput !== void 0) throw new LoadError(`provision("${id}") received an input binding, but the service declares no input schema (module "${moduleNode.name}").`);
|
|
467
|
-
if (provisionInput !== void 0) {
|
|
468
|
-
const leaves = [];
|
|
469
|
-
deepValues(provisionInput, leaves);
|
|
470
|
-
markSecretsUsed(leaves);
|
|
471
|
-
markParamsUsed(leaves);
|
|
472
|
-
inputBindings.push({
|
|
473
|
-
serviceAddress: fullAddress,
|
|
474
|
-
binding: blindCast(provisionInput)
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
validateServiceParamBinding(child, id, localParams, moduleNode.name);
|
|
478
|
-
for (const [slot, binding] of Object.entries(localParams)) paramBindings.push({
|
|
479
|
-
serviceAddress: fullAddress,
|
|
480
|
-
slot,
|
|
481
|
-
binding
|
|
482
|
-
});
|
|
483
|
-
const inputs = serviceInputs(child, fullAddress);
|
|
484
|
-
nodes.push(...inputs.nodes, {
|
|
485
|
-
id: fullAddress,
|
|
486
|
-
node: child
|
|
487
|
-
});
|
|
488
|
-
edges.push(...inputs.edges, ...wiringEdges(localWiring, fullAddress));
|
|
489
|
-
pending.push({
|
|
490
|
-
deps: child.inputs,
|
|
491
|
-
wiring: localWiring,
|
|
492
|
-
targetId: fullAddress,
|
|
493
|
-
targetKind: "service",
|
|
494
|
-
enclosingModuleName: moduleNode.name
|
|
495
|
-
});
|
|
496
|
-
byId.set(fullAddress, child);
|
|
497
|
-
return refFor(fullAddress, child);
|
|
498
|
-
}
|
|
499
|
-
if (provisionInput !== void 0) throw new LoadError(`provision("${id}") received an input binding for a module — a module has no input schema; bind the input on the service that declares it.`);
|
|
500
|
-
validateSecretBinding(child, id, localSecrets, moduleNode.name);
|
|
501
|
-
validateParamNeedBinding(child, id, localParams, moduleNode.name);
|
|
502
|
-
edges.push(...wiringEdges(localWiring, fullAddress));
|
|
503
|
-
pending.push({
|
|
504
|
-
deps: child.deps,
|
|
505
|
-
wiring: localWiring,
|
|
506
|
-
targetId: fullAddress,
|
|
507
|
-
targetKind: "module",
|
|
508
|
-
enclosingModuleName: moduleNode.name
|
|
509
|
-
});
|
|
510
|
-
const childOutputs = flatten(child, fullAddress, localWiring, localSecrets, localParams, nodes, edges, pending, inputBindings, paramBindings, byId);
|
|
511
|
-
nodes.push({
|
|
512
|
-
id: fullAddress,
|
|
513
|
-
node: child
|
|
514
|
-
});
|
|
515
|
-
byId.set(fullAddress, child);
|
|
516
|
-
return blindCast({
|
|
517
|
-
id: fullAddress,
|
|
518
|
-
...childOutputs
|
|
519
|
-
});
|
|
520
|
-
};
|
|
521
|
-
const ctx = blindCast({
|
|
522
|
-
inputs: ctxInputs,
|
|
523
|
-
secrets: ctxSecrets,
|
|
524
|
-
params: ctxParams,
|
|
525
|
-
provision: blindCast(provision)
|
|
526
|
-
});
|
|
527
|
-
const outputs = blindCast(moduleNode.body(ctx) ?? {});
|
|
528
|
-
markUsed(Object.values(outputs));
|
|
529
|
-
for (const key of Object.keys(moduleNode.deps)) if (!used.has(key)) throw new LoadError(`Module "${moduleNode.name}" declares input "${key}" but never forwards it into a provision nor returns it as an output.`);
|
|
530
|
-
for (const key of Object.keys(moduleNode.secretSlots)) if (!usedSecrets.has(key)) throw new LoadError(`Module "${moduleNode.name}" declares secret "${key}" but never forwards it into a provision.`);
|
|
531
|
-
for (const key of Object.keys(moduleNode.paramSlots)) if (!usedParams.has(key)) throw new LoadError(`Module "${moduleNode.name}" declares param "${key}" but never forwards it into a provision.`);
|
|
532
|
-
for (const [key, contract] of Object.entries(moduleNode.expose)) {
|
|
533
|
-
const port = outputs[key];
|
|
534
|
-
if (port === void 0) throw new LoadError(`Module "${moduleNode.name}" declares expose "${key}" but its body did not return a port for it.`);
|
|
535
|
-
if (!satisfiesRequired(port, contract)) throw new LoadError(`Module "${moduleNode.name}"'s returned port for expose "${key}" does not satisfy its declared contract.`);
|
|
536
|
-
}
|
|
537
|
-
return outputs;
|
|
538
|
-
}
|
|
539
|
-
function loadModule(root, opts) {
|
|
540
|
-
const rootId = opts?.id ?? root.name;
|
|
541
|
-
const rootDepKeys = Object.keys(root.deps);
|
|
542
|
-
if (rootDepKeys.length > 0) {
|
|
543
|
-
const names = rootDepKeys.map((k) => `"${k}"`).join(", ");
|
|
544
|
-
throw new LoadError(`Module "${root.name}" declares input${rootDepKeys.length > 1 ? "s" : ""} ${names} but is being deployed as the root — a root has no enclosing scope to wire them; compose "${root.name}" from another module that provisions and wires it instead.`);
|
|
545
|
-
}
|
|
546
|
-
const rootSecretKeys = Object.keys(root.secretSlots);
|
|
547
|
-
if (rootSecretKeys.length > 0) {
|
|
548
|
-
const names = rootSecretKeys.map((k) => `"${k}"`).join(", ");
|
|
549
|
-
throw new LoadError(`Module "${root.name}" declares secret${rootSecretKeys.length > 1 ? "s" : ""} ${names} but is being deployed as the root — a root has no enclosing scope to bind them; the root binds secrets with envSecret('NAME'), it does not declare secret slots of its own.`);
|
|
550
|
-
}
|
|
551
|
-
const rootParamKeys = Object.keys(root.paramSlots);
|
|
552
|
-
if (rootParamKeys.length > 0) {
|
|
553
|
-
const names = rootParamKeys.map((k) => `"${k}"`).join(", ");
|
|
554
|
-
throw new LoadError(`Module "${root.name}" declares param${rootParamKeys.length > 1 ? "s" : ""} ${names} but is being deployed as the root — a root has no enclosing scope to bind them; the root binds params directly on each provision() call, it does not declare param-forwarding slots of its own.`);
|
|
555
|
-
}
|
|
556
|
-
const nodes = [];
|
|
557
|
-
const edges = [];
|
|
558
|
-
const pending = [];
|
|
559
|
-
const inputBindings = [];
|
|
560
|
-
const paramBindings = [];
|
|
561
|
-
const byId = /* @__PURE__ */ new Map();
|
|
562
|
-
flatten(root, void 0, {}, {}, {}, nodes, edges, pending, inputBindings, paramBindings, byId);
|
|
563
|
-
for (const entry of pending) validateWiring(entry, byId);
|
|
564
|
-
assertDependencyDag(edges);
|
|
565
|
-
const rootGraphNode = {
|
|
566
|
-
id: rootId,
|
|
567
|
-
node: root
|
|
568
|
-
};
|
|
569
|
-
return {
|
|
570
|
-
root: rootGraphNode,
|
|
571
|
-
nodes: [...topoSort(nodes, edges), rootGraphNode],
|
|
572
|
-
edges,
|
|
573
|
-
inputBindings,
|
|
574
|
-
params: paramBindings
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
/**
|
|
578
|
-
* The dependency edges must form a DAG — a cycle means neither producer can
|
|
579
|
-
* deploy first. Resources take no wiring, so only service/module-to-service/module
|
|
580
|
-
* edges can ever participate in a cycle; no special-casing needed.
|
|
581
|
-
*/
|
|
582
|
-
function assertDependencyDag(edges) {
|
|
583
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
584
|
-
for (const edge of edges) {
|
|
585
|
-
if (edge.kind !== "dependency") continue;
|
|
586
|
-
const targets = adjacency.get(edge.from) ?? [];
|
|
587
|
-
targets.push(edge.to);
|
|
588
|
-
adjacency.set(edge.from, targets);
|
|
589
|
-
}
|
|
590
|
-
const visiting = /* @__PURE__ */ new Set();
|
|
591
|
-
const done = /* @__PURE__ */ new Set();
|
|
592
|
-
const stack = [];
|
|
593
|
-
const visit = (id) => {
|
|
594
|
-
if (done.has(id)) return;
|
|
595
|
-
if (visiting.has(id)) throw new LoadError(`Dependency cycle: ${[...stack.slice(stack.indexOf(id)), id].join(" → ")} — no deploy order exists.`);
|
|
596
|
-
visiting.add(id);
|
|
597
|
-
stack.push(id);
|
|
598
|
-
for (const next of adjacency.get(id) ?? []) visit(next);
|
|
599
|
-
stack.pop();
|
|
600
|
-
visiting.delete(id);
|
|
601
|
-
done.add(id);
|
|
602
|
-
};
|
|
603
|
-
for (const id of adjacency.keys()) visit(id);
|
|
604
|
-
}
|
|
605
|
-
/**
|
|
606
|
-
* Builds the in-memory graph from a root node. A service root walks its own
|
|
607
|
-
* `inputs`; a module root executes its body (wiring, not user code — the
|
|
608
|
-
* designed exception to imports-run-nothing) and recursively flattens every
|
|
609
|
-
* module it provisions into one graph of hierarchical addresses. A malformed
|
|
610
|
-
* graph is a `LoadError` that names its fix; the individual validation rules
|
|
611
|
-
* live with `loadService` / `loadModule` and are covered by name in the Load
|
|
612
|
-
* tests. Executes nothing of the user's own code beyond module bodies.
|
|
613
|
-
*/
|
|
614
|
-
function Load(root, opts) {
|
|
615
|
-
if (!isNode(root)) throw new LoadError("Load expects a branded service or module node (construct it with the service()/module() factories).");
|
|
616
|
-
if (root.kind === "module") return loadModule(root, opts);
|
|
617
|
-
if (root.kind === "service") return loadService(root, opts?.id ?? "root");
|
|
618
|
-
throw new LoadError("Load expects a service or module root (received another node kind).");
|
|
619
|
-
}
|
|
620
|
-
//#endregion
|
|
621
|
-
//#region ../../0-framework/1-core/core/dist/deploy-C4DxNkJ9.mjs
|
|
622
|
-
var LowerError = class extends Error {
|
|
623
|
-
constructor(message) {
|
|
624
|
-
super(message);
|
|
625
|
-
this.name = "LowerError";
|
|
626
|
-
}
|
|
627
|
-
};
|
|
628
|
-
//#endregion
|
|
629
|
-
//#region ../../0-framework/1-core/core/dist/local-target.mjs
|
|
630
|
-
/** 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). */
|
|
631
|
-
function noLocalTargetSupportError(id) {
|
|
632
|
-
return new LowerError(`extension "${id}" has no dev support — it declares no \`localTarget\` descriptor (ADR-0041).`);
|
|
633
|
-
}
|
|
634
|
-
/**
|
|
635
|
-
* Resolves every non-build-only configured extension's lazy `localTarget`
|
|
636
|
-
* thunk, once (ADR-0041's lazy local-target reference — operator directive:
|
|
637
|
-
* the production control entry carries only the thunk, never the
|
|
638
|
-
* descriptor, so resolving it is this module's job, not something a deploy
|
|
639
|
-
* path ever does). A build-only extension (`isBuildOnlyExtension`) owns no
|
|
640
|
-
* resources or services and is skipped entirely — never even checked for a
|
|
641
|
-
* `localTarget` thunk. Every other configured extension must be
|
|
642
|
-
* local-target-capable, or the dev command cannot bring the app up at all,
|
|
643
|
-
* so a missing thunk throws naming the extension. The generated dev stack
|
|
644
|
-
* module calls this once and threads the resolved map through every
|
|
645
|
-
* subsequent hook, including `localTargetProviders`.
|
|
646
|
-
*/
|
|
647
|
-
async function resolveLocalTargets(config) {
|
|
648
|
-
const entries = await Promise.all(config.extensions.flatMap((extension) => {
|
|
649
|
-
if (isBuildOnlyExtension(extension)) return [];
|
|
650
|
-
if (extension.localTarget === void 0) throw noLocalTargetSupportError(extension.id);
|
|
651
|
-
return [extension.localTarget().then((descriptor) => [extension.id, descriptor])];
|
|
652
|
-
}));
|
|
653
|
-
return new Map(entries);
|
|
654
|
-
}
|
|
655
|
-
//#endregion
|
|
656
|
-
//#region ../../0-framework/3-tooling/assemble/dist/index.mjs
|
|
657
|
-
/**
|
|
658
|
-
* A user-facing assembly failure with a message that already names the fix
|
|
659
|
-
* (mirrors @internal/cli's CliError contract). This package must not import
|
|
660
|
-
* CliError — its second consumer is the future programmatic deploy API, not
|
|
661
|
-
* just the CLI — so it throws its own typed error; the CLI maps it (or lets
|
|
662
|
-
* it propagate, since bin.ts already treats every Error uniformly: print the
|
|
663
|
-
* message, exit nonzero).
|
|
664
|
-
*/
|
|
665
|
-
var AssembleError = class extends Error {
|
|
666
|
-
constructor(message) {
|
|
667
|
-
super(message);
|
|
668
|
-
this.name = "AssembleError";
|
|
669
|
-
}
|
|
670
|
-
};
|
|
671
|
-
/**
|
|
672
|
-
* The registry route for one service's build: extension by
|
|
673
|
-
* `build.extension`, node descriptor by `build.type`, kind must be "build".
|
|
674
|
-
* The CLI's coverage validation reports the same misses earlier with the
|
|
675
|
-
* config fix; these errors are the backstop for programmatic callers.
|
|
676
|
-
*/
|
|
677
|
-
function buildDescriptorAssemble(config, node, address, cwd) {
|
|
678
|
-
const { extension, type } = node.build;
|
|
679
|
-
const extensionDescriptor = config.extensions.find((candidate) => candidate.id === extension);
|
|
680
|
-
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\`.`);
|
|
681
|
-
const nodeDescriptor = extensionDescriptor.nodes[type];
|
|
682
|
-
if (nodeDescriptor === void 0) throw new AssembleError(`Extension "${extension}" has no descriptor for build type "${type}" (known: ${Object.keys(extensionDescriptor.nodes).join(", ")}).`);
|
|
683
|
-
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.`);
|
|
684
|
-
return nodeDescriptor.assemble({
|
|
685
|
-
build: node.build,
|
|
686
|
-
address,
|
|
687
|
-
cwd
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
|
-
async function assembleServices(graph, config, cwd, run) {
|
|
691
|
-
const runAssembler = run ?? ((node, address, nodeCwd) => buildDescriptorAssemble(config, node, address, nodeCwd));
|
|
692
|
-
const serviceNodes = graph.nodes.filter((n) => n.node.kind === "service");
|
|
693
|
-
if (serviceNodes.length === 0) throw new AssembleError("The loaded graph has no service to assemble.");
|
|
694
|
-
const bundles = {};
|
|
695
|
-
for (const { id, node } of serviceNodes) bundles[id] = await runAssembler(node, id, cwd);
|
|
696
|
-
return { bundles };
|
|
697
|
-
}
|
|
698
|
-
//#endregion
|
|
699
|
-
//#region ../../0-framework/3-tooling/cli/node_modules/readdirp/esm/index.js
|
|
700
|
-
const EntryTypes = {
|
|
701
|
-
FILE_TYPE: "files",
|
|
702
|
-
DIR_TYPE: "directories",
|
|
703
|
-
FILE_DIR_TYPE: "files_directories",
|
|
704
|
-
EVERYTHING_TYPE: "all"
|
|
705
|
-
};
|
|
706
|
-
const defaultOptions = {
|
|
707
|
-
root: ".",
|
|
708
|
-
fileFilter: (_entryInfo) => true,
|
|
709
|
-
directoryFilter: (_entryInfo) => true,
|
|
710
|
-
type: EntryTypes.FILE_TYPE,
|
|
711
|
-
lstat: false,
|
|
712
|
-
depth: 2147483648,
|
|
713
|
-
alwaysStat: false,
|
|
714
|
-
highWaterMark: 4096
|
|
715
|
-
};
|
|
716
|
-
Object.freeze(defaultOptions);
|
|
717
|
-
const RECURSIVE_ERROR_CODE = "READDIRP_RECURSIVE_ERROR";
|
|
718
|
-
const NORMAL_FLOW_ERRORS = /* @__PURE__ */ new Set([
|
|
719
|
-
"ENOENT",
|
|
720
|
-
"EPERM",
|
|
721
|
-
"EACCES",
|
|
722
|
-
"ELOOP",
|
|
723
|
-
RECURSIVE_ERROR_CODE
|
|
724
|
-
]);
|
|
725
|
-
const ALL_TYPES = [
|
|
726
|
-
EntryTypes.DIR_TYPE,
|
|
727
|
-
EntryTypes.EVERYTHING_TYPE,
|
|
728
|
-
EntryTypes.FILE_DIR_TYPE,
|
|
729
|
-
EntryTypes.FILE_TYPE
|
|
730
|
-
];
|
|
731
|
-
const DIR_TYPES = /* @__PURE__ */ new Set([
|
|
732
|
-
EntryTypes.DIR_TYPE,
|
|
733
|
-
EntryTypes.EVERYTHING_TYPE,
|
|
734
|
-
EntryTypes.FILE_DIR_TYPE
|
|
735
|
-
]);
|
|
736
|
-
const FILE_TYPES = /* @__PURE__ */ new Set([
|
|
737
|
-
EntryTypes.EVERYTHING_TYPE,
|
|
738
|
-
EntryTypes.FILE_DIR_TYPE,
|
|
739
|
-
EntryTypes.FILE_TYPE
|
|
740
|
-
]);
|
|
741
|
-
const isNormalFlowError = (error) => NORMAL_FLOW_ERRORS.has(error.code);
|
|
742
|
-
const wantBigintFsStats = process.platform === "win32";
|
|
743
|
-
const emptyFn = (_entryInfo) => true;
|
|
744
|
-
const normalizeFilter = (filter) => {
|
|
745
|
-
if (filter === void 0) return emptyFn;
|
|
746
|
-
if (typeof filter === "function") return filter;
|
|
747
|
-
if (typeof filter === "string") {
|
|
748
|
-
const fl = filter.trim();
|
|
749
|
-
return (entry) => entry.basename === fl;
|
|
750
|
-
}
|
|
751
|
-
if (Array.isArray(filter)) {
|
|
752
|
-
const trItems = filter.map((item) => item.trim());
|
|
753
|
-
return (entry) => trItems.some((f) => entry.basename === f);
|
|
754
|
-
}
|
|
755
|
-
return emptyFn;
|
|
756
|
-
};
|
|
757
|
-
/** Readable readdir stream, emitting new files as they're being listed. */
|
|
758
|
-
var ReaddirpStream = class extends Readable {
|
|
759
|
-
constructor(options = {}) {
|
|
760
|
-
super({
|
|
761
|
-
objectMode: true,
|
|
762
|
-
autoDestroy: true,
|
|
763
|
-
highWaterMark: options.highWaterMark
|
|
764
|
-
});
|
|
765
|
-
const opts = {
|
|
766
|
-
...defaultOptions,
|
|
767
|
-
...options
|
|
768
|
-
};
|
|
769
|
-
const { root, type } = opts;
|
|
770
|
-
this._fileFilter = normalizeFilter(opts.fileFilter);
|
|
771
|
-
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
772
|
-
const statMethod = opts.lstat ? lstat$1 : stat$2;
|
|
773
|
-
if (wantBigintFsStats) this._stat = (path) => statMethod(path, { bigint: true });
|
|
774
|
-
else this._stat = statMethod;
|
|
775
|
-
this._maxDepth = opts.depth ?? defaultOptions.depth;
|
|
776
|
-
this._wantsDir = type ? DIR_TYPES.has(type) : false;
|
|
777
|
-
this._wantsFile = type ? FILE_TYPES.has(type) : false;
|
|
778
|
-
this._wantsEverything = type === EntryTypes.EVERYTHING_TYPE;
|
|
779
|
-
this._root = resolve(root);
|
|
780
|
-
this._isDirent = !opts.alwaysStat;
|
|
781
|
-
this._statsProp = this._isDirent ? "dirent" : "stats";
|
|
782
|
-
this._rdOptions = {
|
|
783
|
-
encoding: "utf8",
|
|
784
|
-
withFileTypes: this._isDirent
|
|
785
|
-
};
|
|
786
|
-
this.parents = [this._exploreDir(root, 1)];
|
|
787
|
-
this.reading = false;
|
|
788
|
-
this.parent = void 0;
|
|
789
|
-
}
|
|
790
|
-
async _read(batch) {
|
|
791
|
-
if (this.reading) return;
|
|
792
|
-
this.reading = true;
|
|
793
|
-
try {
|
|
794
|
-
while (!this.destroyed && batch > 0) {
|
|
795
|
-
const par = this.parent;
|
|
796
|
-
const fil = par && par.files;
|
|
797
|
-
if (fil && fil.length > 0) {
|
|
798
|
-
const { path, depth } = par;
|
|
799
|
-
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path));
|
|
800
|
-
const awaited = await Promise.all(slice);
|
|
801
|
-
for (const entry of awaited) {
|
|
802
|
-
if (!entry) continue;
|
|
803
|
-
if (this.destroyed) return;
|
|
804
|
-
const entryType = await this._getEntryType(entry);
|
|
805
|
-
if (entryType === "directory" && this._directoryFilter(entry)) {
|
|
806
|
-
if (depth <= this._maxDepth) this.parents.push(this._exploreDir(entry.fullPath, depth + 1));
|
|
807
|
-
if (this._wantsDir) {
|
|
808
|
-
this.push(entry);
|
|
809
|
-
batch--;
|
|
810
|
-
}
|
|
811
|
-
} else if ((entryType === "file" || this._includeAsFile(entry)) && this._fileFilter(entry)) {
|
|
812
|
-
if (this._wantsFile) {
|
|
813
|
-
this.push(entry);
|
|
814
|
-
batch--;
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
} else {
|
|
819
|
-
const parent = this.parents.pop();
|
|
820
|
-
if (!parent) {
|
|
821
|
-
this.push(null);
|
|
822
|
-
break;
|
|
823
|
-
}
|
|
824
|
-
this.parent = await parent;
|
|
825
|
-
if (this.destroyed) return;
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
} catch (error) {
|
|
829
|
-
this.destroy(error);
|
|
830
|
-
} finally {
|
|
831
|
-
this.reading = false;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
async _exploreDir(path, depth) {
|
|
835
|
-
let files;
|
|
836
|
-
try {
|
|
837
|
-
files = await readdir$1(path, this._rdOptions);
|
|
838
|
-
} catch (error) {
|
|
839
|
-
this._onError(error);
|
|
840
|
-
}
|
|
841
|
-
return {
|
|
842
|
-
files,
|
|
843
|
-
depth,
|
|
844
|
-
path
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
async _formatEntry(dirent, path) {
|
|
848
|
-
let entry;
|
|
849
|
-
const basename = this._isDirent ? dirent.name : dirent;
|
|
850
|
-
try {
|
|
851
|
-
const fullPath = resolve(join(path, basename));
|
|
852
|
-
entry = {
|
|
853
|
-
path: relative(this._root, fullPath),
|
|
854
|
-
fullPath,
|
|
855
|
-
basename
|
|
856
|
-
};
|
|
857
|
-
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
858
|
-
} catch (err) {
|
|
859
|
-
this._onError(err);
|
|
860
|
-
return;
|
|
861
|
-
}
|
|
862
|
-
return entry;
|
|
863
|
-
}
|
|
864
|
-
_onError(err) {
|
|
865
|
-
if (isNormalFlowError(err) && !this.destroyed) this.emit("warn", err);
|
|
866
|
-
else this.destroy(err);
|
|
867
|
-
}
|
|
868
|
-
async _getEntryType(entry) {
|
|
869
|
-
if (!entry && this._statsProp in entry) return "";
|
|
870
|
-
const stats = entry[this._statsProp];
|
|
871
|
-
if (stats.isFile()) return "file";
|
|
872
|
-
if (stats.isDirectory()) return "directory";
|
|
873
|
-
if (stats && stats.isSymbolicLink()) {
|
|
874
|
-
const full = entry.fullPath;
|
|
875
|
-
try {
|
|
876
|
-
const entryRealPath = await realpath$1(full);
|
|
877
|
-
const entryRealPathStats = await lstat$1(entryRealPath);
|
|
878
|
-
if (entryRealPathStats.isFile()) return "file";
|
|
879
|
-
if (entryRealPathStats.isDirectory()) {
|
|
880
|
-
const len = entryRealPath.length;
|
|
881
|
-
if (full.startsWith(entryRealPath) && full.substr(len, 1) === sep) {
|
|
882
|
-
const recursiveError = /* @__PURE__ */ new Error(`Circular symlink detected: "${full}" points to "${entryRealPath}"`);
|
|
883
|
-
recursiveError.code = RECURSIVE_ERROR_CODE;
|
|
884
|
-
return this._onError(recursiveError);
|
|
885
|
-
}
|
|
886
|
-
return "directory";
|
|
887
|
-
}
|
|
888
|
-
} catch (error) {
|
|
889
|
-
this._onError(error);
|
|
890
|
-
return "";
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
_includeAsFile(entry) {
|
|
895
|
-
const stats = entry && entry[this._statsProp];
|
|
896
|
-
return stats && this._wantsEverything && !stats.isDirectory();
|
|
897
|
-
}
|
|
898
|
-
};
|
|
899
|
-
/**
|
|
900
|
-
* Streaming version: Reads all files and directories in given root recursively.
|
|
901
|
-
* Consumes ~constant small amount of RAM.
|
|
902
|
-
* @param root Root directory
|
|
903
|
-
* @param options Options to specify root (start directory), filters and recursion depth
|
|
904
|
-
*/
|
|
905
|
-
function readdirp(root, options = {}) {
|
|
906
|
-
let type = options.entryType || options.type;
|
|
907
|
-
if (type === "both") type = EntryTypes.FILE_DIR_TYPE;
|
|
908
|
-
if (type) options.type = type;
|
|
909
|
-
if (!root) throw new Error("readdirp: root argument is required. Usage: readdirp(root, options)");
|
|
910
|
-
else if (typeof root !== "string") throw new TypeError("readdirp: root argument must be a string. Usage: readdirp(root, options)");
|
|
911
|
-
else if (type && !ALL_TYPES.includes(type)) throw new Error(`readdirp: Invalid type passed. Use one of ${ALL_TYPES.join(", ")}`);
|
|
912
|
-
options.root = root;
|
|
913
|
-
return new ReaddirpStream(options);
|
|
914
|
-
}
|
|
915
|
-
//#endregion
|
|
916
|
-
//#region ../../0-framework/3-tooling/cli/node_modules/chokidar/esm/handler.js
|
|
917
|
-
const STR_DATA = "data";
|
|
918
|
-
const STR_CLOSE = "close";
|
|
919
|
-
const EMPTY_FN = () => {};
|
|
920
|
-
const pl = process.platform;
|
|
921
|
-
const isWindows = pl === "win32";
|
|
922
|
-
const isMacos = pl === "darwin";
|
|
923
|
-
const isLinux = pl === "linux";
|
|
924
|
-
const isFreeBSD = pl === "freebsd";
|
|
925
|
-
const isIBMi = type() === "OS400";
|
|
926
|
-
const EVENTS = {
|
|
927
|
-
ALL: "all",
|
|
928
|
-
READY: "ready",
|
|
929
|
-
ADD: "add",
|
|
930
|
-
CHANGE: "change",
|
|
931
|
-
ADD_DIR: "addDir",
|
|
932
|
-
UNLINK: "unlink",
|
|
933
|
-
UNLINK_DIR: "unlinkDir",
|
|
934
|
-
RAW: "raw",
|
|
935
|
-
ERROR: "error"
|
|
936
|
-
};
|
|
937
|
-
const EV = EVENTS;
|
|
938
|
-
const THROTTLE_MODE_WATCH = "watch";
|
|
939
|
-
const statMethods = {
|
|
940
|
-
lstat,
|
|
941
|
-
stat: stat$1
|
|
942
|
-
};
|
|
943
|
-
const KEY_LISTENERS = "listeners";
|
|
944
|
-
const KEY_ERR = "errHandlers";
|
|
945
|
-
const KEY_RAW = "rawEmitters";
|
|
946
|
-
const HANDLER_KEYS = [
|
|
947
|
-
KEY_LISTENERS,
|
|
948
|
-
KEY_ERR,
|
|
949
|
-
KEY_RAW
|
|
950
|
-
];
|
|
951
|
-
const binaryExtensions = /* @__PURE__ */ new Set([
|
|
952
|
-
"3dm",
|
|
953
|
-
"3ds",
|
|
954
|
-
"3g2",
|
|
955
|
-
"3gp",
|
|
956
|
-
"7z",
|
|
957
|
-
"a",
|
|
958
|
-
"aac",
|
|
959
|
-
"adp",
|
|
960
|
-
"afdesign",
|
|
961
|
-
"afphoto",
|
|
962
|
-
"afpub",
|
|
963
|
-
"ai",
|
|
964
|
-
"aif",
|
|
965
|
-
"aiff",
|
|
966
|
-
"alz",
|
|
967
|
-
"ape",
|
|
968
|
-
"apk",
|
|
969
|
-
"appimage",
|
|
970
|
-
"ar",
|
|
971
|
-
"arj",
|
|
972
|
-
"asf",
|
|
973
|
-
"au",
|
|
974
|
-
"avi",
|
|
975
|
-
"bak",
|
|
976
|
-
"baml",
|
|
977
|
-
"bh",
|
|
978
|
-
"bin",
|
|
979
|
-
"bk",
|
|
980
|
-
"bmp",
|
|
981
|
-
"btif",
|
|
982
|
-
"bz2",
|
|
983
|
-
"bzip2",
|
|
984
|
-
"cab",
|
|
985
|
-
"caf",
|
|
986
|
-
"cgm",
|
|
987
|
-
"class",
|
|
988
|
-
"cmx",
|
|
989
|
-
"cpio",
|
|
990
|
-
"cr2",
|
|
991
|
-
"cur",
|
|
992
|
-
"dat",
|
|
993
|
-
"dcm",
|
|
994
|
-
"deb",
|
|
995
|
-
"dex",
|
|
996
|
-
"djvu",
|
|
997
|
-
"dll",
|
|
998
|
-
"dmg",
|
|
999
|
-
"dng",
|
|
1000
|
-
"doc",
|
|
1001
|
-
"docm",
|
|
1002
|
-
"docx",
|
|
1003
|
-
"dot",
|
|
1004
|
-
"dotm",
|
|
1005
|
-
"dra",
|
|
1006
|
-
"DS_Store",
|
|
1007
|
-
"dsk",
|
|
1008
|
-
"dts",
|
|
1009
|
-
"dtshd",
|
|
1010
|
-
"dvb",
|
|
1011
|
-
"dwg",
|
|
1012
|
-
"dxf",
|
|
1013
|
-
"ecelp4800",
|
|
1014
|
-
"ecelp7470",
|
|
1015
|
-
"ecelp9600",
|
|
1016
|
-
"egg",
|
|
1017
|
-
"eol",
|
|
1018
|
-
"eot",
|
|
1019
|
-
"epub",
|
|
1020
|
-
"exe",
|
|
1021
|
-
"f4v",
|
|
1022
|
-
"fbs",
|
|
1023
|
-
"fh",
|
|
1024
|
-
"fla",
|
|
1025
|
-
"flac",
|
|
1026
|
-
"flatpak",
|
|
1027
|
-
"fli",
|
|
1028
|
-
"flv",
|
|
1029
|
-
"fpx",
|
|
1030
|
-
"fst",
|
|
1031
|
-
"fvt",
|
|
1032
|
-
"g3",
|
|
1033
|
-
"gh",
|
|
1034
|
-
"gif",
|
|
1035
|
-
"graffle",
|
|
1036
|
-
"gz",
|
|
1037
|
-
"gzip",
|
|
1038
|
-
"h261",
|
|
1039
|
-
"h263",
|
|
1040
|
-
"h264",
|
|
1041
|
-
"icns",
|
|
1042
|
-
"ico",
|
|
1043
|
-
"ief",
|
|
1044
|
-
"img",
|
|
1045
|
-
"ipa",
|
|
1046
|
-
"iso",
|
|
1047
|
-
"jar",
|
|
1048
|
-
"jpeg",
|
|
1049
|
-
"jpg",
|
|
1050
|
-
"jpgv",
|
|
1051
|
-
"jpm",
|
|
1052
|
-
"jxr",
|
|
1053
|
-
"key",
|
|
1054
|
-
"ktx",
|
|
1055
|
-
"lha",
|
|
1056
|
-
"lib",
|
|
1057
|
-
"lvp",
|
|
1058
|
-
"lz",
|
|
1059
|
-
"lzh",
|
|
1060
|
-
"lzma",
|
|
1061
|
-
"lzo",
|
|
1062
|
-
"m3u",
|
|
1063
|
-
"m4a",
|
|
1064
|
-
"m4v",
|
|
1065
|
-
"mar",
|
|
1066
|
-
"mdi",
|
|
1067
|
-
"mht",
|
|
1068
|
-
"mid",
|
|
1069
|
-
"midi",
|
|
1070
|
-
"mj2",
|
|
1071
|
-
"mka",
|
|
1072
|
-
"mkv",
|
|
1073
|
-
"mmr",
|
|
1074
|
-
"mng",
|
|
1075
|
-
"mobi",
|
|
1076
|
-
"mov",
|
|
1077
|
-
"movie",
|
|
1078
|
-
"mp3",
|
|
1079
|
-
"mp4",
|
|
1080
|
-
"mp4a",
|
|
1081
|
-
"mpeg",
|
|
1082
|
-
"mpg",
|
|
1083
|
-
"mpga",
|
|
1084
|
-
"mxu",
|
|
1085
|
-
"nef",
|
|
1086
|
-
"npx",
|
|
1087
|
-
"numbers",
|
|
1088
|
-
"nupkg",
|
|
1089
|
-
"o",
|
|
1090
|
-
"odp",
|
|
1091
|
-
"ods",
|
|
1092
|
-
"odt",
|
|
1093
|
-
"oga",
|
|
1094
|
-
"ogg",
|
|
1095
|
-
"ogv",
|
|
1096
|
-
"otf",
|
|
1097
|
-
"ott",
|
|
1098
|
-
"pages",
|
|
1099
|
-
"pbm",
|
|
1100
|
-
"pcx",
|
|
1101
|
-
"pdb",
|
|
1102
|
-
"pdf",
|
|
1103
|
-
"pea",
|
|
1104
|
-
"pgm",
|
|
1105
|
-
"pic",
|
|
1106
|
-
"png",
|
|
1107
|
-
"pnm",
|
|
1108
|
-
"pot",
|
|
1109
|
-
"potm",
|
|
1110
|
-
"potx",
|
|
1111
|
-
"ppa",
|
|
1112
|
-
"ppam",
|
|
1113
|
-
"ppm",
|
|
1114
|
-
"pps",
|
|
1115
|
-
"ppsm",
|
|
1116
|
-
"ppsx",
|
|
1117
|
-
"ppt",
|
|
1118
|
-
"pptm",
|
|
1119
|
-
"pptx",
|
|
1120
|
-
"psd",
|
|
1121
|
-
"pya",
|
|
1122
|
-
"pyc",
|
|
1123
|
-
"pyo",
|
|
1124
|
-
"pyv",
|
|
1125
|
-
"qt",
|
|
1126
|
-
"rar",
|
|
1127
|
-
"ras",
|
|
1128
|
-
"raw",
|
|
1129
|
-
"resources",
|
|
1130
|
-
"rgb",
|
|
1131
|
-
"rip",
|
|
1132
|
-
"rlc",
|
|
1133
|
-
"rmf",
|
|
1134
|
-
"rmvb",
|
|
1135
|
-
"rpm",
|
|
1136
|
-
"rtf",
|
|
1137
|
-
"rz",
|
|
1138
|
-
"s3m",
|
|
1139
|
-
"s7z",
|
|
1140
|
-
"scpt",
|
|
1141
|
-
"sgi",
|
|
1142
|
-
"shar",
|
|
1143
|
-
"snap",
|
|
1144
|
-
"sil",
|
|
1145
|
-
"sketch",
|
|
1146
|
-
"slk",
|
|
1147
|
-
"smv",
|
|
1148
|
-
"snk",
|
|
1149
|
-
"so",
|
|
1150
|
-
"stl",
|
|
1151
|
-
"suo",
|
|
1152
|
-
"sub",
|
|
1153
|
-
"swf",
|
|
1154
|
-
"tar",
|
|
1155
|
-
"tbz",
|
|
1156
|
-
"tbz2",
|
|
1157
|
-
"tga",
|
|
1158
|
-
"tgz",
|
|
1159
|
-
"thmx",
|
|
1160
|
-
"tif",
|
|
1161
|
-
"tiff",
|
|
1162
|
-
"tlz",
|
|
1163
|
-
"ttc",
|
|
1164
|
-
"ttf",
|
|
1165
|
-
"txz",
|
|
1166
|
-
"udf",
|
|
1167
|
-
"uvh",
|
|
1168
|
-
"uvi",
|
|
1169
|
-
"uvm",
|
|
1170
|
-
"uvp",
|
|
1171
|
-
"uvs",
|
|
1172
|
-
"uvu",
|
|
1173
|
-
"viv",
|
|
1174
|
-
"vob",
|
|
1175
|
-
"war",
|
|
1176
|
-
"wav",
|
|
1177
|
-
"wax",
|
|
1178
|
-
"wbmp",
|
|
1179
|
-
"wdp",
|
|
1180
|
-
"weba",
|
|
1181
|
-
"webm",
|
|
1182
|
-
"webp",
|
|
1183
|
-
"whl",
|
|
1184
|
-
"wim",
|
|
1185
|
-
"wm",
|
|
1186
|
-
"wma",
|
|
1187
|
-
"wmv",
|
|
1188
|
-
"wmx",
|
|
1189
|
-
"woff",
|
|
1190
|
-
"woff2",
|
|
1191
|
-
"wrm",
|
|
1192
|
-
"wvx",
|
|
1193
|
-
"xbm",
|
|
1194
|
-
"xif",
|
|
1195
|
-
"xla",
|
|
1196
|
-
"xlam",
|
|
1197
|
-
"xls",
|
|
1198
|
-
"xlsb",
|
|
1199
|
-
"xlsm",
|
|
1200
|
-
"xlsx",
|
|
1201
|
-
"xlt",
|
|
1202
|
-
"xltm",
|
|
1203
|
-
"xltx",
|
|
1204
|
-
"xm",
|
|
1205
|
-
"xmind",
|
|
1206
|
-
"xpi",
|
|
1207
|
-
"xpm",
|
|
1208
|
-
"xwd",
|
|
1209
|
-
"xz",
|
|
1210
|
-
"z",
|
|
1211
|
-
"zip",
|
|
1212
|
-
"zipx"
|
|
1213
|
-
]);
|
|
1214
|
-
const isBinaryPath = (filePath) => binaryExtensions.has(sysPath.extname(filePath).slice(1).toLowerCase());
|
|
1215
|
-
const foreach = (val, fn) => {
|
|
1216
|
-
if (val instanceof Set) val.forEach(fn);
|
|
1217
|
-
else fn(val);
|
|
1218
|
-
};
|
|
1219
|
-
const addAndConvert = (main, prop, item) => {
|
|
1220
|
-
let container = main[prop];
|
|
1221
|
-
if (!(container instanceof Set)) main[prop] = container = /* @__PURE__ */ new Set([container]);
|
|
1222
|
-
container.add(item);
|
|
1223
|
-
};
|
|
1224
|
-
const clearItem = (cont) => (key) => {
|
|
1225
|
-
const set = cont[key];
|
|
1226
|
-
if (set instanceof Set) set.clear();
|
|
1227
|
-
else delete cont[key];
|
|
1228
|
-
};
|
|
1229
|
-
const delFromSet = (main, prop, item) => {
|
|
1230
|
-
const container = main[prop];
|
|
1231
|
-
if (container instanceof Set) container.delete(item);
|
|
1232
|
-
else if (container === item) delete main[prop];
|
|
1233
|
-
};
|
|
1234
|
-
const isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
|
|
1235
|
-
const FsWatchInstances = /* @__PURE__ */ new Map();
|
|
1236
|
-
/**
|
|
1237
|
-
* Instantiates the fs_watch interface
|
|
1238
|
-
* @param path to be watched
|
|
1239
|
-
* @param options to be passed to fs_watch
|
|
1240
|
-
* @param listener main event handler
|
|
1241
|
-
* @param errHandler emits info about errors
|
|
1242
|
-
* @param emitRaw emits raw event data
|
|
1243
|
-
* @returns {NativeFsWatcher}
|
|
1244
|
-
*/
|
|
1245
|
-
function createFsWatchInstance(path, options, listener, errHandler, emitRaw) {
|
|
1246
|
-
const handleEvent = (rawEvent, evPath) => {
|
|
1247
|
-
listener(path);
|
|
1248
|
-
emitRaw(rawEvent, evPath, { watchedPath: path });
|
|
1249
|
-
if (evPath && path !== evPath) fsWatchBroadcast(sysPath.resolve(path, evPath), KEY_LISTENERS, sysPath.join(path, evPath));
|
|
1250
|
-
};
|
|
1251
|
-
try {
|
|
1252
|
-
return watch(path, { persistent: options.persistent }, handleEvent);
|
|
1253
|
-
} catch (error) {
|
|
1254
|
-
errHandler(error);
|
|
1255
|
-
return;
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
/**
|
|
1259
|
-
* Helper for passing fs_watch event data to a collection of listeners
|
|
1260
|
-
* @param fullPath absolute path bound to fs_watch instance
|
|
1261
|
-
*/
|
|
1262
|
-
const fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
|
|
1263
|
-
const cont = FsWatchInstances.get(fullPath);
|
|
1264
|
-
if (!cont) return;
|
|
1265
|
-
foreach(cont[listenerType], (listener) => {
|
|
1266
|
-
listener(val1, val2, val3);
|
|
1267
|
-
});
|
|
1268
|
-
};
|
|
1269
|
-
/**
|
|
1270
|
-
* Instantiates the fs_watch interface or binds listeners
|
|
1271
|
-
* to an existing one covering the same file system entry
|
|
1272
|
-
* @param path
|
|
1273
|
-
* @param fullPath absolute path
|
|
1274
|
-
* @param options to be passed to fs_watch
|
|
1275
|
-
* @param handlers container for event listener functions
|
|
1276
|
-
*/
|
|
1277
|
-
const setFsWatchListener = (path, fullPath, options, handlers) => {
|
|
1278
|
-
const { listener, errHandler, rawEmitter } = handlers;
|
|
1279
|
-
let cont = FsWatchInstances.get(fullPath);
|
|
1280
|
-
let watcher;
|
|
1281
|
-
if (!options.persistent) {
|
|
1282
|
-
watcher = createFsWatchInstance(path, options, listener, errHandler, rawEmitter);
|
|
1283
|
-
if (!watcher) return;
|
|
1284
|
-
return watcher.close.bind(watcher);
|
|
1285
|
-
}
|
|
1286
|
-
if (cont) {
|
|
1287
|
-
addAndConvert(cont, KEY_LISTENERS, listener);
|
|
1288
|
-
addAndConvert(cont, KEY_ERR, errHandler);
|
|
1289
|
-
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
1290
|
-
} else {
|
|
1291
|
-
watcher = createFsWatchInstance(path, options, fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS), errHandler, fsWatchBroadcast.bind(null, fullPath, KEY_RAW));
|
|
1292
|
-
if (!watcher) return;
|
|
1293
|
-
watcher.on(EV.ERROR, async (error) => {
|
|
1294
|
-
const broadcastErr = fsWatchBroadcast.bind(null, fullPath, KEY_ERR);
|
|
1295
|
-
if (cont) cont.watcherUnusable = true;
|
|
1296
|
-
if (isWindows && error.code === "EPERM") try {
|
|
1297
|
-
await (await open(path, "r")).close();
|
|
1298
|
-
broadcastErr(error);
|
|
1299
|
-
} catch (err) {}
|
|
1300
|
-
else broadcastErr(error);
|
|
1301
|
-
});
|
|
1302
|
-
cont = {
|
|
1303
|
-
listeners: listener,
|
|
1304
|
-
errHandlers: errHandler,
|
|
1305
|
-
rawEmitters: rawEmitter,
|
|
1306
|
-
watcher
|
|
1307
|
-
};
|
|
1308
|
-
FsWatchInstances.set(fullPath, cont);
|
|
1309
|
-
}
|
|
1310
|
-
return () => {
|
|
1311
|
-
delFromSet(cont, KEY_LISTENERS, listener);
|
|
1312
|
-
delFromSet(cont, KEY_ERR, errHandler);
|
|
1313
|
-
delFromSet(cont, KEY_RAW, rawEmitter);
|
|
1314
|
-
if (isEmptySet(cont.listeners)) {
|
|
1315
|
-
cont.watcher.close();
|
|
1316
|
-
FsWatchInstances.delete(fullPath);
|
|
1317
|
-
HANDLER_KEYS.forEach(clearItem(cont));
|
|
1318
|
-
cont.watcher = void 0;
|
|
1319
|
-
Object.freeze(cont);
|
|
1320
|
-
}
|
|
1321
|
-
};
|
|
1322
|
-
};
|
|
1323
|
-
const FsWatchFileInstances = /* @__PURE__ */ new Map();
|
|
1324
|
-
/**
|
|
1325
|
-
* Instantiates the fs_watchFile interface or binds listeners
|
|
1326
|
-
* to an existing one covering the same file system entry
|
|
1327
|
-
* @param path to be watched
|
|
1328
|
-
* @param fullPath absolute path
|
|
1329
|
-
* @param options options to be passed to fs_watchFile
|
|
1330
|
-
* @param handlers container for event listener functions
|
|
1331
|
-
* @returns closer
|
|
1332
|
-
*/
|
|
1333
|
-
const setFsWatchFileListener = (path, fullPath, options, handlers) => {
|
|
1334
|
-
const { listener, rawEmitter } = handlers;
|
|
1335
|
-
let cont = FsWatchFileInstances.get(fullPath);
|
|
1336
|
-
const copts = cont && cont.options;
|
|
1337
|
-
if (copts && (copts.persistent < options.persistent || copts.interval > options.interval)) {
|
|
1338
|
-
unwatchFile(fullPath);
|
|
1339
|
-
cont = void 0;
|
|
1340
|
-
}
|
|
1341
|
-
if (cont) {
|
|
1342
|
-
addAndConvert(cont, KEY_LISTENERS, listener);
|
|
1343
|
-
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
1344
|
-
} else {
|
|
1345
|
-
cont = {
|
|
1346
|
-
listeners: listener,
|
|
1347
|
-
rawEmitters: rawEmitter,
|
|
1348
|
-
options,
|
|
1349
|
-
watcher: watchFile(fullPath, options, (curr, prev) => {
|
|
1350
|
-
foreach(cont.rawEmitters, (rawEmitter) => {
|
|
1351
|
-
rawEmitter(EV.CHANGE, fullPath, {
|
|
1352
|
-
curr,
|
|
1353
|
-
prev
|
|
1354
|
-
});
|
|
1355
|
-
});
|
|
1356
|
-
const currmtime = curr.mtimeMs;
|
|
1357
|
-
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) foreach(cont.listeners, (listener) => listener(path, curr));
|
|
1358
|
-
})
|
|
1359
|
-
};
|
|
1360
|
-
FsWatchFileInstances.set(fullPath, cont);
|
|
1361
|
-
}
|
|
1362
|
-
return () => {
|
|
1363
|
-
delFromSet(cont, KEY_LISTENERS, listener);
|
|
1364
|
-
delFromSet(cont, KEY_RAW, rawEmitter);
|
|
1365
|
-
if (isEmptySet(cont.listeners)) {
|
|
1366
|
-
FsWatchFileInstances.delete(fullPath);
|
|
1367
|
-
unwatchFile(fullPath);
|
|
1368
|
-
cont.options = cont.watcher = void 0;
|
|
1369
|
-
Object.freeze(cont);
|
|
1370
|
-
}
|
|
1371
|
-
};
|
|
1372
|
-
};
|
|
1373
|
-
/**
|
|
1374
|
-
* @mixin
|
|
1375
|
-
*/
|
|
1376
|
-
var NodeFsHandler = class {
|
|
1377
|
-
constructor(fsW) {
|
|
1378
|
-
this.fsw = fsW;
|
|
1379
|
-
this._boundHandleError = (error) => fsW._handleError(error);
|
|
1380
|
-
}
|
|
1381
|
-
/**
|
|
1382
|
-
* Watch file for changes with fs_watchFile or fs_watch.
|
|
1383
|
-
* @param path to file or dir
|
|
1384
|
-
* @param listener on fs change
|
|
1385
|
-
* @returns closer for the watcher instance
|
|
1386
|
-
*/
|
|
1387
|
-
_watchWithNodeFs(path, listener) {
|
|
1388
|
-
const opts = this.fsw.options;
|
|
1389
|
-
const directory = sysPath.dirname(path);
|
|
1390
|
-
const basename = sysPath.basename(path);
|
|
1391
|
-
this.fsw._getWatchedDir(directory).add(basename);
|
|
1392
|
-
const absolutePath = sysPath.resolve(path);
|
|
1393
|
-
const options = { persistent: opts.persistent };
|
|
1394
|
-
if (!listener) listener = EMPTY_FN;
|
|
1395
|
-
let closer;
|
|
1396
|
-
if (opts.usePolling) {
|
|
1397
|
-
options.interval = opts.interval !== opts.binaryInterval && isBinaryPath(basename) ? opts.binaryInterval : opts.interval;
|
|
1398
|
-
closer = setFsWatchFileListener(path, absolutePath, options, {
|
|
1399
|
-
listener,
|
|
1400
|
-
rawEmitter: this.fsw._emitRaw
|
|
1401
|
-
});
|
|
1402
|
-
} else closer = setFsWatchListener(path, absolutePath, options, {
|
|
1403
|
-
listener,
|
|
1404
|
-
errHandler: this._boundHandleError,
|
|
1405
|
-
rawEmitter: this.fsw._emitRaw
|
|
1406
|
-
});
|
|
1407
|
-
return closer;
|
|
1408
|
-
}
|
|
1409
|
-
/**
|
|
1410
|
-
* Watch a file and emit add event if warranted.
|
|
1411
|
-
* @returns closer for the watcher instance
|
|
1412
|
-
*/
|
|
1413
|
-
_handleFile(file, stats, initialAdd) {
|
|
1414
|
-
if (this.fsw.closed) return;
|
|
1415
|
-
const dirname = sysPath.dirname(file);
|
|
1416
|
-
const basename = sysPath.basename(file);
|
|
1417
|
-
const parent = this.fsw._getWatchedDir(dirname);
|
|
1418
|
-
let prevStats = stats;
|
|
1419
|
-
if (parent.has(basename)) return;
|
|
1420
|
-
const listener = async (path, newStats) => {
|
|
1421
|
-
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5)) return;
|
|
1422
|
-
if (!newStats || newStats.mtimeMs === 0) try {
|
|
1423
|
-
const newStats = await stat$1(file);
|
|
1424
|
-
if (this.fsw.closed) return;
|
|
1425
|
-
const at = newStats.atimeMs;
|
|
1426
|
-
const mt = newStats.mtimeMs;
|
|
1427
|
-
if (!at || at <= mt || mt !== prevStats.mtimeMs) this.fsw._emit(EV.CHANGE, file, newStats);
|
|
1428
|
-
if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats.ino) {
|
|
1429
|
-
this.fsw._closeFile(path);
|
|
1430
|
-
prevStats = newStats;
|
|
1431
|
-
const closer = this._watchWithNodeFs(file, listener);
|
|
1432
|
-
if (closer) this.fsw._addPathCloser(path, closer);
|
|
1433
|
-
} else prevStats = newStats;
|
|
1434
|
-
} catch (error) {
|
|
1435
|
-
this.fsw._remove(dirname, basename);
|
|
1436
|
-
}
|
|
1437
|
-
else if (parent.has(basename)) {
|
|
1438
|
-
const at = newStats.atimeMs;
|
|
1439
|
-
const mt = newStats.mtimeMs;
|
|
1440
|
-
if (!at || at <= mt || mt !== prevStats.mtimeMs) this.fsw._emit(EV.CHANGE, file, newStats);
|
|
1441
|
-
prevStats = newStats;
|
|
1442
|
-
}
|
|
1443
|
-
};
|
|
1444
|
-
const closer = this._watchWithNodeFs(file, listener);
|
|
1445
|
-
if (!(initialAdd && this.fsw.options.ignoreInitial) && this.fsw._isntIgnored(file)) {
|
|
1446
|
-
if (!this.fsw._throttle(EV.ADD, file, 0)) return;
|
|
1447
|
-
this.fsw._emit(EV.ADD, file, stats);
|
|
1448
|
-
}
|
|
1449
|
-
return closer;
|
|
1450
|
-
}
|
|
1451
|
-
/**
|
|
1452
|
-
* Handle symlinks encountered while reading a dir.
|
|
1453
|
-
* @param entry returned by readdirp
|
|
1454
|
-
* @param directory path of dir being read
|
|
1455
|
-
* @param path of this item
|
|
1456
|
-
* @param item basename of this item
|
|
1457
|
-
* @returns true if no more processing is needed for this entry.
|
|
1458
|
-
*/
|
|
1459
|
-
async _handleSymlink(entry, directory, path, item) {
|
|
1460
|
-
if (this.fsw.closed) return;
|
|
1461
|
-
const full = entry.fullPath;
|
|
1462
|
-
const dir = this.fsw._getWatchedDir(directory);
|
|
1463
|
-
if (!this.fsw.options.followSymlinks) {
|
|
1464
|
-
this.fsw._incrReadyCount();
|
|
1465
|
-
let linkPath;
|
|
1466
|
-
try {
|
|
1467
|
-
linkPath = await realpath(path);
|
|
1468
|
-
} catch (e) {
|
|
1469
|
-
this.fsw._emitReady();
|
|
1470
|
-
return true;
|
|
1471
|
-
}
|
|
1472
|
-
if (this.fsw.closed) return;
|
|
1473
|
-
if (dir.has(item)) {
|
|
1474
|
-
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
1475
|
-
this.fsw._symlinkPaths.set(full, linkPath);
|
|
1476
|
-
this.fsw._emit(EV.CHANGE, path, entry.stats);
|
|
1477
|
-
}
|
|
1478
|
-
} else {
|
|
1479
|
-
dir.add(item);
|
|
1480
|
-
this.fsw._symlinkPaths.set(full, linkPath);
|
|
1481
|
-
this.fsw._emit(EV.ADD, path, entry.stats);
|
|
1482
|
-
}
|
|
1483
|
-
this.fsw._emitReady();
|
|
1484
|
-
return true;
|
|
1485
|
-
}
|
|
1486
|
-
if (this.fsw._symlinkPaths.has(full)) return true;
|
|
1487
|
-
this.fsw._symlinkPaths.set(full, true);
|
|
1488
|
-
}
|
|
1489
|
-
_handleRead(directory, initialAdd, wh, target, dir, depth, throttler) {
|
|
1490
|
-
directory = sysPath.join(directory, "");
|
|
1491
|
-
throttler = this.fsw._throttle("readdir", directory, 1e3);
|
|
1492
|
-
if (!throttler) return;
|
|
1493
|
-
const previous = this.fsw._getWatchedDir(wh.path);
|
|
1494
|
-
const current = /* @__PURE__ */ new Set();
|
|
1495
|
-
let stream = this.fsw._readdirp(directory, {
|
|
1496
|
-
fileFilter: (entry) => wh.filterPath(entry),
|
|
1497
|
-
directoryFilter: (entry) => wh.filterDir(entry)
|
|
1498
|
-
});
|
|
1499
|
-
if (!stream) return;
|
|
1500
|
-
stream.on(STR_DATA, async (entry) => {
|
|
1501
|
-
if (this.fsw.closed) {
|
|
1502
|
-
stream = void 0;
|
|
1503
|
-
return;
|
|
1504
|
-
}
|
|
1505
|
-
const item = entry.path;
|
|
1506
|
-
let path = sysPath.join(directory, item);
|
|
1507
|
-
current.add(item);
|
|
1508
|
-
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path, item)) return;
|
|
1509
|
-
if (this.fsw.closed) {
|
|
1510
|
-
stream = void 0;
|
|
1511
|
-
return;
|
|
1512
|
-
}
|
|
1513
|
-
if (item === target || !target && !previous.has(item)) {
|
|
1514
|
-
this.fsw._incrReadyCount();
|
|
1515
|
-
path = sysPath.join(dir, sysPath.relative(dir, path));
|
|
1516
|
-
this._addToNodeFs(path, initialAdd, wh, depth + 1);
|
|
1517
|
-
}
|
|
1518
|
-
}).on(EV.ERROR, this._boundHandleError);
|
|
1519
|
-
return new Promise((resolve, reject) => {
|
|
1520
|
-
if (!stream) return reject();
|
|
1521
|
-
stream.once("end", () => {
|
|
1522
|
-
if (this.fsw.closed) {
|
|
1523
|
-
stream = void 0;
|
|
1524
|
-
return;
|
|
1525
|
-
}
|
|
1526
|
-
const wasThrottled = throttler ? throttler.clear() : false;
|
|
1527
|
-
resolve(void 0);
|
|
1528
|
-
previous.getChildren().filter((item) => {
|
|
1529
|
-
return item !== directory && !current.has(item);
|
|
1530
|
-
}).forEach((item) => {
|
|
1531
|
-
this.fsw._remove(directory, item);
|
|
1532
|
-
});
|
|
1533
|
-
stream = void 0;
|
|
1534
|
-
if (wasThrottled) this._handleRead(directory, false, wh, target, dir, depth, throttler);
|
|
1535
|
-
});
|
|
1536
|
-
});
|
|
1537
|
-
}
|
|
1538
|
-
/**
|
|
1539
|
-
* Read directory to add / remove files from `@watched` list and re-read it on change.
|
|
1540
|
-
* @param dir fs path
|
|
1541
|
-
* @param stats
|
|
1542
|
-
* @param initialAdd
|
|
1543
|
-
* @param depth relative to user-supplied path
|
|
1544
|
-
* @param target child path targeted for watch
|
|
1545
|
-
* @param wh Common watch helpers for this path
|
|
1546
|
-
* @param realpath
|
|
1547
|
-
* @returns closer for the watcher instance.
|
|
1548
|
-
*/
|
|
1549
|
-
async _handleDir(dir, stats, initialAdd, depth, target, wh, realpath) {
|
|
1550
|
-
const parentDir = this.fsw._getWatchedDir(sysPath.dirname(dir));
|
|
1551
|
-
const tracked = parentDir.has(sysPath.basename(dir));
|
|
1552
|
-
if (!(initialAdd && this.fsw.options.ignoreInitial) && !target && !tracked) this.fsw._emit(EV.ADD_DIR, dir, stats);
|
|
1553
|
-
parentDir.add(sysPath.basename(dir));
|
|
1554
|
-
this.fsw._getWatchedDir(dir);
|
|
1555
|
-
let throttler;
|
|
1556
|
-
let closer;
|
|
1557
|
-
const oDepth = this.fsw.options.depth;
|
|
1558
|
-
if ((oDepth == null || depth <= oDepth) && !this.fsw._symlinkPaths.has(realpath)) {
|
|
1559
|
-
if (!target) {
|
|
1560
|
-
await this._handleRead(dir, initialAdd, wh, target, dir, depth, throttler);
|
|
1561
|
-
if (this.fsw.closed) return;
|
|
1562
|
-
}
|
|
1563
|
-
closer = this._watchWithNodeFs(dir, (dirPath, stats) => {
|
|
1564
|
-
if (stats && stats.mtimeMs === 0) return;
|
|
1565
|
-
this._handleRead(dirPath, false, wh, target, dir, depth, throttler);
|
|
1566
|
-
});
|
|
1567
|
-
}
|
|
1568
|
-
return closer;
|
|
1569
|
-
}
|
|
1570
|
-
/**
|
|
1571
|
-
* Handle added file, directory, or glob pattern.
|
|
1572
|
-
* Delegates call to _handleFile / _handleDir after checks.
|
|
1573
|
-
* @param path to file or ir
|
|
1574
|
-
* @param initialAdd was the file added at watch instantiation?
|
|
1575
|
-
* @param priorWh depth relative to user-supplied path
|
|
1576
|
-
* @param depth Child path actually targeted for watch
|
|
1577
|
-
* @param target Child path actually targeted for watch
|
|
1578
|
-
*/
|
|
1579
|
-
async _addToNodeFs(path, initialAdd, priorWh, depth, target) {
|
|
1580
|
-
const ready = this.fsw._emitReady;
|
|
1581
|
-
if (this.fsw._isIgnored(path) || this.fsw.closed) {
|
|
1582
|
-
ready();
|
|
1583
|
-
return false;
|
|
1584
|
-
}
|
|
1585
|
-
const wh = this.fsw._getWatchHelpers(path);
|
|
1586
|
-
if (priorWh) {
|
|
1587
|
-
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
1588
|
-
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
1589
|
-
}
|
|
1590
|
-
try {
|
|
1591
|
-
const stats = await statMethods[wh.statMethod](wh.watchPath);
|
|
1592
|
-
if (this.fsw.closed) return;
|
|
1593
|
-
if (this.fsw._isIgnored(wh.watchPath, stats)) {
|
|
1594
|
-
ready();
|
|
1595
|
-
return false;
|
|
1596
|
-
}
|
|
1597
|
-
const follow = this.fsw.options.followSymlinks;
|
|
1598
|
-
let closer;
|
|
1599
|
-
if (stats.isDirectory()) {
|
|
1600
|
-
const absPath = sysPath.resolve(path);
|
|
1601
|
-
const targetPath = follow ? await realpath(path) : path;
|
|
1602
|
-
if (this.fsw.closed) return;
|
|
1603
|
-
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
1604
|
-
if (this.fsw.closed) return;
|
|
1605
|
-
if (absPath !== targetPath && targetPath !== void 0) this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
1606
|
-
} else if (stats.isSymbolicLink()) {
|
|
1607
|
-
const targetPath = follow ? await realpath(path) : path;
|
|
1608
|
-
if (this.fsw.closed) return;
|
|
1609
|
-
const parent = sysPath.dirname(wh.watchPath);
|
|
1610
|
-
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
1611
|
-
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
1612
|
-
closer = await this._handleDir(parent, stats, initialAdd, depth, path, wh, targetPath);
|
|
1613
|
-
if (this.fsw.closed) return;
|
|
1614
|
-
if (targetPath !== void 0) this.fsw._symlinkPaths.set(sysPath.resolve(path), targetPath);
|
|
1615
|
-
} else closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
1616
|
-
ready();
|
|
1617
|
-
if (closer) this.fsw._addPathCloser(path, closer);
|
|
1618
|
-
return false;
|
|
1619
|
-
} catch (error) {
|
|
1620
|
-
if (this.fsw._handleError(error)) {
|
|
1621
|
-
ready();
|
|
1622
|
-
return path;
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
};
|
|
1627
|
-
//#endregion
|
|
1628
|
-
//#region ../../0-framework/3-tooling/cli/node_modules/chokidar/esm/index.js
|
|
1629
|
-
/*! chokidar - MIT License (c) 2012 Paul Miller (paulmillr.com) */
|
|
1630
|
-
const SLASH = "/";
|
|
1631
|
-
const SLASH_SLASH = "//";
|
|
1632
|
-
const ONE_DOT = ".";
|
|
1633
|
-
const TWO_DOTS = "..";
|
|
1634
|
-
const STRING_TYPE = "string";
|
|
1635
|
-
const BACK_SLASH_RE = /\\/g;
|
|
1636
|
-
const DOUBLE_SLASH_RE = /\/\//;
|
|
1637
|
-
const DOT_RE = /\..*\.(sw[px])$|~$|\.subl.*\.tmp/;
|
|
1638
|
-
const REPLACER_RE = /^\.[/\\]/;
|
|
1639
|
-
function arrify(item) {
|
|
1640
|
-
return Array.isArray(item) ? item : [item];
|
|
1641
|
-
}
|
|
1642
|
-
const isMatcherObject = (matcher) => typeof matcher === "object" && matcher !== null && !(matcher instanceof RegExp);
|
|
1643
|
-
function createPattern(matcher) {
|
|
1644
|
-
if (typeof matcher === "function") return matcher;
|
|
1645
|
-
if (typeof matcher === "string") return (string) => matcher === string;
|
|
1646
|
-
if (matcher instanceof RegExp) return (string) => matcher.test(string);
|
|
1647
|
-
if (typeof matcher === "object" && matcher !== null) return (string) => {
|
|
1648
|
-
if (matcher.path === string) return true;
|
|
1649
|
-
if (matcher.recursive) {
|
|
1650
|
-
const relative = sysPath.relative(matcher.path, string);
|
|
1651
|
-
if (!relative) return false;
|
|
1652
|
-
return !relative.startsWith("..") && !sysPath.isAbsolute(relative);
|
|
1653
|
-
}
|
|
1654
|
-
return false;
|
|
1655
|
-
};
|
|
1656
|
-
return () => false;
|
|
1657
|
-
}
|
|
1658
|
-
function normalizePath(path) {
|
|
1659
|
-
if (typeof path !== "string") throw new Error("string expected");
|
|
1660
|
-
path = sysPath.normalize(path);
|
|
1661
|
-
path = path.replace(/\\/g, "/");
|
|
1662
|
-
let prepend = false;
|
|
1663
|
-
if (path.startsWith("//")) prepend = true;
|
|
1664
|
-
const DOUBLE_SLASH_RE = /\/\//;
|
|
1665
|
-
while (path.match(DOUBLE_SLASH_RE)) path = path.replace(DOUBLE_SLASH_RE, "/");
|
|
1666
|
-
if (prepend) path = "/" + path;
|
|
1667
|
-
return path;
|
|
1668
|
-
}
|
|
1669
|
-
function matchPatterns(patterns, testString, stats) {
|
|
1670
|
-
const path = normalizePath(testString);
|
|
1671
|
-
for (let index = 0; index < patterns.length; index++) {
|
|
1672
|
-
const pattern = patterns[index];
|
|
1673
|
-
if (pattern(path, stats)) return true;
|
|
1674
|
-
}
|
|
1675
|
-
return false;
|
|
1676
|
-
}
|
|
1677
|
-
function anymatch(matchers, testString) {
|
|
1678
|
-
if (matchers == null) throw new TypeError("anymatch: specify first argument");
|
|
1679
|
-
const patterns = arrify(matchers).map((matcher) => createPattern(matcher));
|
|
1680
|
-
if (testString == null) return (testString, stats) => {
|
|
1681
|
-
return matchPatterns(patterns, testString, stats);
|
|
1682
|
-
};
|
|
1683
|
-
return matchPatterns(patterns, testString);
|
|
1684
|
-
}
|
|
1685
|
-
const unifyPaths = (paths_) => {
|
|
1686
|
-
const paths = arrify(paths_).flat();
|
|
1687
|
-
if (!paths.every((p) => typeof p === STRING_TYPE)) throw new TypeError(`Non-string provided as watch path: ${paths}`);
|
|
1688
|
-
return paths.map(normalizePathToUnix);
|
|
1689
|
-
};
|
|
1690
|
-
const toUnix = (string) => {
|
|
1691
|
-
let str = string.replace(BACK_SLASH_RE, SLASH);
|
|
1692
|
-
let prepend = false;
|
|
1693
|
-
if (str.startsWith(SLASH_SLASH)) prepend = true;
|
|
1694
|
-
while (str.match(DOUBLE_SLASH_RE)) str = str.replace(DOUBLE_SLASH_RE, SLASH);
|
|
1695
|
-
if (prepend) str = SLASH + str;
|
|
1696
|
-
return str;
|
|
1697
|
-
};
|
|
1698
|
-
const normalizePathToUnix = (path) => toUnix(sysPath.normalize(toUnix(path)));
|
|
1699
|
-
const normalizeIgnored = (cwd = "") => (path) => {
|
|
1700
|
-
if (typeof path === "string") return normalizePathToUnix(sysPath.isAbsolute(path) ? path : sysPath.join(cwd, path));
|
|
1701
|
-
else return path;
|
|
1702
|
-
};
|
|
1703
|
-
const getAbsolutePath = (path, cwd) => {
|
|
1704
|
-
if (sysPath.isAbsolute(path)) return path;
|
|
1705
|
-
return sysPath.join(cwd, path);
|
|
1706
|
-
};
|
|
1707
|
-
const EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
|
|
1708
|
-
/**
|
|
1709
|
-
* Directory entry.
|
|
1710
|
-
*/
|
|
1711
|
-
var DirEntry = class {
|
|
1712
|
-
constructor(dir, removeWatcher) {
|
|
1713
|
-
this.path = dir;
|
|
1714
|
-
this._removeWatcher = removeWatcher;
|
|
1715
|
-
this.items = /* @__PURE__ */ new Set();
|
|
1716
|
-
}
|
|
1717
|
-
add(item) {
|
|
1718
|
-
const { items } = this;
|
|
1719
|
-
if (!items) return;
|
|
1720
|
-
if (item !== ONE_DOT && item !== TWO_DOTS) items.add(item);
|
|
1721
|
-
}
|
|
1722
|
-
async remove(item) {
|
|
1723
|
-
const { items } = this;
|
|
1724
|
-
if (!items) return;
|
|
1725
|
-
items.delete(item);
|
|
1726
|
-
if (items.size > 0) return;
|
|
1727
|
-
const dir = this.path;
|
|
1728
|
-
try {
|
|
1729
|
-
await readdir(dir);
|
|
1730
|
-
} catch (err) {
|
|
1731
|
-
if (this._removeWatcher) this._removeWatcher(sysPath.dirname(dir), sysPath.basename(dir));
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1734
|
-
has(item) {
|
|
1735
|
-
const { items } = this;
|
|
1736
|
-
if (!items) return;
|
|
1737
|
-
return items.has(item);
|
|
1738
|
-
}
|
|
1739
|
-
getChildren() {
|
|
1740
|
-
const { items } = this;
|
|
1741
|
-
if (!items) return [];
|
|
1742
|
-
return [...items.values()];
|
|
1743
|
-
}
|
|
1744
|
-
dispose() {
|
|
1745
|
-
this.items.clear();
|
|
1746
|
-
this.path = "";
|
|
1747
|
-
this._removeWatcher = EMPTY_FN;
|
|
1748
|
-
this.items = EMPTY_SET;
|
|
1749
|
-
Object.freeze(this);
|
|
1750
|
-
}
|
|
1751
|
-
};
|
|
1752
|
-
const STAT_METHOD_F = "stat";
|
|
1753
|
-
const STAT_METHOD_L = "lstat";
|
|
1754
|
-
var WatchHelper = class {
|
|
1755
|
-
constructor(path, follow, fsw) {
|
|
1756
|
-
this.fsw = fsw;
|
|
1757
|
-
const watchPath = path;
|
|
1758
|
-
this.path = path = path.replace(REPLACER_RE, "");
|
|
1759
|
-
this.watchPath = watchPath;
|
|
1760
|
-
this.fullWatchPath = sysPath.resolve(watchPath);
|
|
1761
|
-
this.dirParts = [];
|
|
1762
|
-
this.dirParts.forEach((parts) => {
|
|
1763
|
-
if (parts.length > 1) parts.pop();
|
|
1764
|
-
});
|
|
1765
|
-
this.followSymlinks = follow;
|
|
1766
|
-
this.statMethod = follow ? STAT_METHOD_F : STAT_METHOD_L;
|
|
1767
|
-
}
|
|
1768
|
-
entryPath(entry) {
|
|
1769
|
-
return sysPath.join(this.watchPath, sysPath.relative(this.watchPath, entry.fullPath));
|
|
1770
|
-
}
|
|
1771
|
-
filterPath(entry) {
|
|
1772
|
-
const { stats } = entry;
|
|
1773
|
-
if (stats && stats.isSymbolicLink()) return this.filterDir(entry);
|
|
1774
|
-
const resolvedPath = this.entryPath(entry);
|
|
1775
|
-
return this.fsw._isntIgnored(resolvedPath, stats) && this.fsw._hasReadPermissions(stats);
|
|
1776
|
-
}
|
|
1777
|
-
filterDir(entry) {
|
|
1778
|
-
return this.fsw._isntIgnored(this.entryPath(entry), entry.stats);
|
|
1779
|
-
}
|
|
1780
|
-
};
|
|
1781
|
-
/**
|
|
1782
|
-
* Watches files & directories for changes. Emitted events:
|
|
1783
|
-
* `add`, `addDir`, `change`, `unlink`, `unlinkDir`, `all`, `error`
|
|
1784
|
-
*
|
|
1785
|
-
* new FSWatcher()
|
|
1786
|
-
* .add(directories)
|
|
1787
|
-
* .on('add', path => log('File', path, 'was added'))
|
|
1788
|
-
*/
|
|
1789
|
-
var FSWatcher = class extends EventEmitter {
|
|
1790
|
-
constructor(_opts = {}) {
|
|
1791
|
-
super();
|
|
1792
|
-
this.closed = false;
|
|
1793
|
-
this._closers = /* @__PURE__ */ new Map();
|
|
1794
|
-
this._ignoredPaths = /* @__PURE__ */ new Set();
|
|
1795
|
-
this._throttled = /* @__PURE__ */ new Map();
|
|
1796
|
-
this._streams = /* @__PURE__ */ new Set();
|
|
1797
|
-
this._symlinkPaths = /* @__PURE__ */ new Map();
|
|
1798
|
-
this._watched = /* @__PURE__ */ new Map();
|
|
1799
|
-
this._pendingWrites = /* @__PURE__ */ new Map();
|
|
1800
|
-
this._pendingUnlinks = /* @__PURE__ */ new Map();
|
|
1801
|
-
this._readyCount = 0;
|
|
1802
|
-
this._readyEmitted = false;
|
|
1803
|
-
const awf = _opts.awaitWriteFinish;
|
|
1804
|
-
const DEF_AWF = {
|
|
1805
|
-
stabilityThreshold: 2e3,
|
|
1806
|
-
pollInterval: 100
|
|
1807
|
-
};
|
|
1808
|
-
const opts = {
|
|
1809
|
-
persistent: true,
|
|
1810
|
-
ignoreInitial: false,
|
|
1811
|
-
ignorePermissionErrors: false,
|
|
1812
|
-
interval: 100,
|
|
1813
|
-
binaryInterval: 300,
|
|
1814
|
-
followSymlinks: true,
|
|
1815
|
-
usePolling: false,
|
|
1816
|
-
atomic: true,
|
|
1817
|
-
..._opts,
|
|
1818
|
-
ignored: _opts.ignored ? arrify(_opts.ignored) : arrify([]),
|
|
1819
|
-
awaitWriteFinish: awf === true ? DEF_AWF : typeof awf === "object" ? {
|
|
1820
|
-
...DEF_AWF,
|
|
1821
|
-
...awf
|
|
1822
|
-
} : false
|
|
1823
|
-
};
|
|
1824
|
-
if (isIBMi) opts.usePolling = true;
|
|
1825
|
-
if (opts.atomic === void 0) opts.atomic = !opts.usePolling;
|
|
1826
|
-
const envPoll = process.env.CHOKIDAR_USEPOLLING;
|
|
1827
|
-
if (envPoll !== void 0) {
|
|
1828
|
-
const envLower = envPoll.toLowerCase();
|
|
1829
|
-
if (envLower === "false" || envLower === "0") opts.usePolling = false;
|
|
1830
|
-
else if (envLower === "true" || envLower === "1") opts.usePolling = true;
|
|
1831
|
-
else opts.usePolling = !!envLower;
|
|
1832
|
-
}
|
|
1833
|
-
const envInterval = process.env.CHOKIDAR_INTERVAL;
|
|
1834
|
-
if (envInterval) opts.interval = Number.parseInt(envInterval, 10);
|
|
1835
|
-
let readyCalls = 0;
|
|
1836
|
-
this._emitReady = () => {
|
|
1837
|
-
readyCalls++;
|
|
1838
|
-
if (readyCalls >= this._readyCount) {
|
|
1839
|
-
this._emitReady = EMPTY_FN;
|
|
1840
|
-
this._readyEmitted = true;
|
|
1841
|
-
process.nextTick(() => this.emit(EVENTS.READY));
|
|
1842
|
-
}
|
|
1843
|
-
};
|
|
1844
|
-
this._emitRaw = (...args) => this.emit(EVENTS.RAW, ...args);
|
|
1845
|
-
this._boundRemove = this._remove.bind(this);
|
|
1846
|
-
this.options = opts;
|
|
1847
|
-
this._nodeFsHandler = new NodeFsHandler(this);
|
|
1848
|
-
Object.freeze(opts);
|
|
1849
|
-
}
|
|
1850
|
-
_addIgnoredPath(matcher) {
|
|
1851
|
-
if (isMatcherObject(matcher)) {
|
|
1852
|
-
for (const ignored of this._ignoredPaths) if (isMatcherObject(ignored) && ignored.path === matcher.path && ignored.recursive === matcher.recursive) return;
|
|
1853
|
-
}
|
|
1854
|
-
this._ignoredPaths.add(matcher);
|
|
1855
|
-
}
|
|
1856
|
-
_removeIgnoredPath(matcher) {
|
|
1857
|
-
this._ignoredPaths.delete(matcher);
|
|
1858
|
-
if (typeof matcher === "string") {
|
|
1859
|
-
for (const ignored of this._ignoredPaths) if (isMatcherObject(ignored) && ignored.path === matcher) this._ignoredPaths.delete(ignored);
|
|
1860
|
-
}
|
|
1861
|
-
}
|
|
1862
|
-
/**
|
|
1863
|
-
* Adds paths to be watched on an existing FSWatcher instance.
|
|
1864
|
-
* @param paths_ file or file list. Other arguments are unused
|
|
1865
|
-
*/
|
|
1866
|
-
add(paths_, _origAdd, _internal) {
|
|
1867
|
-
const { cwd } = this.options;
|
|
1868
|
-
this.closed = false;
|
|
1869
|
-
this._closePromise = void 0;
|
|
1870
|
-
let paths = unifyPaths(paths_);
|
|
1871
|
-
if (cwd) paths = paths.map((path) => {
|
|
1872
|
-
return getAbsolutePath(path, cwd);
|
|
1873
|
-
});
|
|
1874
|
-
paths.forEach((path) => {
|
|
1875
|
-
this._removeIgnoredPath(path);
|
|
1876
|
-
});
|
|
1877
|
-
this._userIgnored = void 0;
|
|
1878
|
-
if (!this._readyCount) this._readyCount = 0;
|
|
1879
|
-
this._readyCount += paths.length;
|
|
1880
|
-
Promise.all(paths.map(async (path) => {
|
|
1881
|
-
const res = await this._nodeFsHandler._addToNodeFs(path, !_internal, void 0, 0, _origAdd);
|
|
1882
|
-
if (res) this._emitReady();
|
|
1883
|
-
return res;
|
|
1884
|
-
})).then((results) => {
|
|
1885
|
-
if (this.closed) return;
|
|
1886
|
-
results.forEach((item) => {
|
|
1887
|
-
if (item) this.add(sysPath.dirname(item), sysPath.basename(_origAdd || item));
|
|
1888
|
-
});
|
|
1889
|
-
});
|
|
1890
|
-
return this;
|
|
1891
|
-
}
|
|
1892
|
-
/**
|
|
1893
|
-
* Close watchers or start ignoring events from specified paths.
|
|
1894
|
-
*/
|
|
1895
|
-
unwatch(paths_) {
|
|
1896
|
-
if (this.closed) return this;
|
|
1897
|
-
const paths = unifyPaths(paths_);
|
|
1898
|
-
const { cwd } = this.options;
|
|
1899
|
-
paths.forEach((path) => {
|
|
1900
|
-
if (!sysPath.isAbsolute(path) && !this._closers.has(path)) {
|
|
1901
|
-
if (cwd) path = sysPath.join(cwd, path);
|
|
1902
|
-
path = sysPath.resolve(path);
|
|
1903
|
-
}
|
|
1904
|
-
this._closePath(path);
|
|
1905
|
-
this._addIgnoredPath(path);
|
|
1906
|
-
if (this._watched.has(path)) this._addIgnoredPath({
|
|
1907
|
-
path,
|
|
1908
|
-
recursive: true
|
|
1909
|
-
});
|
|
1910
|
-
this._userIgnored = void 0;
|
|
1911
|
-
});
|
|
1912
|
-
return this;
|
|
1913
|
-
}
|
|
1914
|
-
/**
|
|
1915
|
-
* Close watchers and remove all listeners from watched paths.
|
|
1916
|
-
*/
|
|
1917
|
-
close() {
|
|
1918
|
-
if (this._closePromise) return this._closePromise;
|
|
1919
|
-
this.closed = true;
|
|
1920
|
-
this.removeAllListeners();
|
|
1921
|
-
const closers = [];
|
|
1922
|
-
this._closers.forEach((closerList) => closerList.forEach((closer) => {
|
|
1923
|
-
const promise = closer();
|
|
1924
|
-
if (promise instanceof Promise) closers.push(promise);
|
|
1925
|
-
}));
|
|
1926
|
-
this._streams.forEach((stream) => stream.destroy());
|
|
1927
|
-
this._userIgnored = void 0;
|
|
1928
|
-
this._readyCount = 0;
|
|
1929
|
-
this._readyEmitted = false;
|
|
1930
|
-
this._watched.forEach((dirent) => dirent.dispose());
|
|
1931
|
-
this._closers.clear();
|
|
1932
|
-
this._watched.clear();
|
|
1933
|
-
this._streams.clear();
|
|
1934
|
-
this._symlinkPaths.clear();
|
|
1935
|
-
this._throttled.clear();
|
|
1936
|
-
this._closePromise = closers.length ? Promise.all(closers).then(() => void 0) : Promise.resolve();
|
|
1937
|
-
return this._closePromise;
|
|
1938
|
-
}
|
|
1939
|
-
/**
|
|
1940
|
-
* Expose list of watched paths
|
|
1941
|
-
* @returns for chaining
|
|
1942
|
-
*/
|
|
1943
|
-
getWatched() {
|
|
1944
|
-
const watchList = {};
|
|
1945
|
-
this._watched.forEach((entry, dir) => {
|
|
1946
|
-
const index = (this.options.cwd ? sysPath.relative(this.options.cwd, dir) : dir) || ONE_DOT;
|
|
1947
|
-
watchList[index] = entry.getChildren().sort();
|
|
1948
|
-
});
|
|
1949
|
-
return watchList;
|
|
1950
|
-
}
|
|
1951
|
-
emitWithAll(event, args) {
|
|
1952
|
-
this.emit(event, ...args);
|
|
1953
|
-
if (event !== EVENTS.ERROR) this.emit(EVENTS.ALL, event, ...args);
|
|
1954
|
-
}
|
|
1955
|
-
/**
|
|
1956
|
-
* Normalize and emit events.
|
|
1957
|
-
* Calling _emit DOES NOT MEAN emit() would be called!
|
|
1958
|
-
* @param event Type of event
|
|
1959
|
-
* @param path File or directory path
|
|
1960
|
-
* @param stats arguments to be passed with event
|
|
1961
|
-
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
1962
|
-
*/
|
|
1963
|
-
async _emit(event, path, stats) {
|
|
1964
|
-
if (this.closed) return;
|
|
1965
|
-
const opts = this.options;
|
|
1966
|
-
if (isWindows) path = sysPath.normalize(path);
|
|
1967
|
-
if (opts.cwd) path = sysPath.relative(opts.cwd, path);
|
|
1968
|
-
const args = [path];
|
|
1969
|
-
if (stats != null) args.push(stats);
|
|
1970
|
-
const awf = opts.awaitWriteFinish;
|
|
1971
|
-
let pw;
|
|
1972
|
-
if (awf && (pw = this._pendingWrites.get(path))) {
|
|
1973
|
-
pw.lastChange = /* @__PURE__ */ new Date();
|
|
1974
|
-
return this;
|
|
1975
|
-
}
|
|
1976
|
-
if (opts.atomic) {
|
|
1977
|
-
if (event === EVENTS.UNLINK) {
|
|
1978
|
-
this._pendingUnlinks.set(path, [event, ...args]);
|
|
1979
|
-
setTimeout(() => {
|
|
1980
|
-
this._pendingUnlinks.forEach((entry, path) => {
|
|
1981
|
-
this.emit(...entry);
|
|
1982
|
-
this.emit(EVENTS.ALL, ...entry);
|
|
1983
|
-
this._pendingUnlinks.delete(path);
|
|
1984
|
-
});
|
|
1985
|
-
}, typeof opts.atomic === "number" ? opts.atomic : 100);
|
|
1986
|
-
return this;
|
|
1987
|
-
}
|
|
1988
|
-
if (event === EVENTS.ADD && this._pendingUnlinks.has(path)) {
|
|
1989
|
-
event = EVENTS.CHANGE;
|
|
1990
|
-
this._pendingUnlinks.delete(path);
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
|
|
1994
|
-
const awfEmit = (err, stats) => {
|
|
1995
|
-
if (err) {
|
|
1996
|
-
event = EVENTS.ERROR;
|
|
1997
|
-
args[0] = err;
|
|
1998
|
-
this.emitWithAll(event, args);
|
|
1999
|
-
} else if (stats) {
|
|
2000
|
-
if (args.length > 1) args[1] = stats;
|
|
2001
|
-
else args.push(stats);
|
|
2002
|
-
this.emitWithAll(event, args);
|
|
2003
|
-
}
|
|
2004
|
-
};
|
|
2005
|
-
this._awaitWriteFinish(path, awf.stabilityThreshold, event, awfEmit);
|
|
2006
|
-
return this;
|
|
2007
|
-
}
|
|
2008
|
-
if (event === EVENTS.CHANGE) {
|
|
2009
|
-
if (!this._throttle(EVENTS.CHANGE, path, 50)) return this;
|
|
2010
|
-
}
|
|
2011
|
-
if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
|
|
2012
|
-
const fullPath = opts.cwd ? sysPath.join(opts.cwd, path) : path;
|
|
2013
|
-
let stats;
|
|
2014
|
-
try {
|
|
2015
|
-
stats = await stat$1(fullPath);
|
|
2016
|
-
} catch (err) {}
|
|
2017
|
-
if (!stats || this.closed) return;
|
|
2018
|
-
args.push(stats);
|
|
2019
|
-
}
|
|
2020
|
-
this.emitWithAll(event, args);
|
|
2021
|
-
return this;
|
|
2022
|
-
}
|
|
2023
|
-
/**
|
|
2024
|
-
* Common handler for errors
|
|
2025
|
-
* @returns The error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
2026
|
-
*/
|
|
2027
|
-
_handleError(error) {
|
|
2028
|
-
const code = error && error.code;
|
|
2029
|
-
if (error && code !== "ENOENT" && code !== "ENOTDIR" && (!this.options.ignorePermissionErrors || code !== "EPERM" && code !== "EACCES")) this.emit(EVENTS.ERROR, error);
|
|
2030
|
-
return error || this.closed;
|
|
2031
|
-
}
|
|
2032
|
-
/**
|
|
2033
|
-
* Helper utility for throttling
|
|
2034
|
-
* @param actionType type being throttled
|
|
2035
|
-
* @param path being acted upon
|
|
2036
|
-
* @param timeout duration of time to suppress duplicate actions
|
|
2037
|
-
* @returns tracking object or false if action should be suppressed
|
|
2038
|
-
*/
|
|
2039
|
-
_throttle(actionType, path, timeout) {
|
|
2040
|
-
if (!this._throttled.has(actionType)) this._throttled.set(actionType, /* @__PURE__ */ new Map());
|
|
2041
|
-
const action = this._throttled.get(actionType);
|
|
2042
|
-
if (!action) throw new Error("invalid throttle");
|
|
2043
|
-
const actionPath = action.get(path);
|
|
2044
|
-
if (actionPath) {
|
|
2045
|
-
actionPath.count++;
|
|
2046
|
-
return false;
|
|
2047
|
-
}
|
|
2048
|
-
let timeoutObject;
|
|
2049
|
-
const clear = () => {
|
|
2050
|
-
const item = action.get(path);
|
|
2051
|
-
const count = item ? item.count : 0;
|
|
2052
|
-
action.delete(path);
|
|
2053
|
-
clearTimeout(timeoutObject);
|
|
2054
|
-
if (item) clearTimeout(item.timeoutObject);
|
|
2055
|
-
return count;
|
|
2056
|
-
};
|
|
2057
|
-
timeoutObject = setTimeout(clear, timeout);
|
|
2058
|
-
const thr = {
|
|
2059
|
-
timeoutObject,
|
|
2060
|
-
clear,
|
|
2061
|
-
count: 0
|
|
2062
|
-
};
|
|
2063
|
-
action.set(path, thr);
|
|
2064
|
-
return thr;
|
|
2065
|
-
}
|
|
2066
|
-
_incrReadyCount() {
|
|
2067
|
-
return this._readyCount++;
|
|
2068
|
-
}
|
|
2069
|
-
/**
|
|
2070
|
-
* Awaits write operation to finish.
|
|
2071
|
-
* Polls a newly created file for size variations. When files size does not change for 'threshold' milliseconds calls callback.
|
|
2072
|
-
* @param path being acted upon
|
|
2073
|
-
* @param threshold Time in milliseconds a file size must be fixed before acknowledging write OP is finished
|
|
2074
|
-
* @param event
|
|
2075
|
-
* @param awfEmit Callback to be called when ready for event to be emitted.
|
|
2076
|
-
*/
|
|
2077
|
-
_awaitWriteFinish(path, threshold, event, awfEmit) {
|
|
2078
|
-
const awf = this.options.awaitWriteFinish;
|
|
2079
|
-
if (typeof awf !== "object") return;
|
|
2080
|
-
const pollInterval = awf.pollInterval;
|
|
2081
|
-
let timeoutHandler;
|
|
2082
|
-
let fullPath = path;
|
|
2083
|
-
if (this.options.cwd && !sysPath.isAbsolute(path)) fullPath = sysPath.join(this.options.cwd, path);
|
|
2084
|
-
const now = /* @__PURE__ */ new Date();
|
|
2085
|
-
const writes = this._pendingWrites;
|
|
2086
|
-
function awaitWriteFinishFn(prevStat) {
|
|
2087
|
-
stat(fullPath, (err, curStat) => {
|
|
2088
|
-
if (err || !writes.has(path)) {
|
|
2089
|
-
if (err && err.code !== "ENOENT") awfEmit(err);
|
|
2090
|
-
return;
|
|
2091
|
-
}
|
|
2092
|
-
const now = Number(/* @__PURE__ */ new Date());
|
|
2093
|
-
if (prevStat && curStat.size !== prevStat.size) writes.get(path).lastChange = now;
|
|
2094
|
-
if (now - writes.get(path).lastChange >= threshold) {
|
|
2095
|
-
writes.delete(path);
|
|
2096
|
-
awfEmit(void 0, curStat);
|
|
2097
|
-
} else timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
|
|
2098
|
-
});
|
|
2099
|
-
}
|
|
2100
|
-
if (!writes.has(path)) {
|
|
2101
|
-
writes.set(path, {
|
|
2102
|
-
lastChange: now,
|
|
2103
|
-
cancelWait: () => {
|
|
2104
|
-
writes.delete(path);
|
|
2105
|
-
clearTimeout(timeoutHandler);
|
|
2106
|
-
return event;
|
|
2107
|
-
}
|
|
2108
|
-
});
|
|
2109
|
-
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval);
|
|
2110
|
-
}
|
|
2111
|
-
}
|
|
2112
|
-
/**
|
|
2113
|
-
* Determines whether user has asked to ignore this path.
|
|
2114
|
-
*/
|
|
2115
|
-
_isIgnored(path, stats) {
|
|
2116
|
-
if (this.options.atomic && DOT_RE.test(path)) return true;
|
|
2117
|
-
if (!this._userIgnored) {
|
|
2118
|
-
const { cwd } = this.options;
|
|
2119
|
-
const ignored = (this.options.ignored || []).map(normalizeIgnored(cwd));
|
|
2120
|
-
const list = [...[...this._ignoredPaths].map(normalizeIgnored(cwd)), ...ignored];
|
|
2121
|
-
this._userIgnored = anymatch(list, void 0);
|
|
2122
|
-
}
|
|
2123
|
-
return this._userIgnored(path, stats);
|
|
2124
|
-
}
|
|
2125
|
-
_isntIgnored(path, stat) {
|
|
2126
|
-
return !this._isIgnored(path, stat);
|
|
2127
|
-
}
|
|
2128
|
-
/**
|
|
2129
|
-
* Provides a set of common helpers and properties relating to symlink handling.
|
|
2130
|
-
* @param path file or directory pattern being watched
|
|
2131
|
-
*/
|
|
2132
|
-
_getWatchHelpers(path) {
|
|
2133
|
-
return new WatchHelper(path, this.options.followSymlinks, this);
|
|
2134
|
-
}
|
|
2135
|
-
/**
|
|
2136
|
-
* Provides directory tracking objects
|
|
2137
|
-
* @param directory path of the directory
|
|
2138
|
-
*/
|
|
2139
|
-
_getWatchedDir(directory) {
|
|
2140
|
-
const dir = sysPath.resolve(directory);
|
|
2141
|
-
if (!this._watched.has(dir)) this._watched.set(dir, new DirEntry(dir, this._boundRemove));
|
|
2142
|
-
return this._watched.get(dir);
|
|
2143
|
-
}
|
|
2144
|
-
/**
|
|
2145
|
-
* Check for read permissions: https://stackoverflow.com/a/11781404/1358405
|
|
2146
|
-
*/
|
|
2147
|
-
_hasReadPermissions(stats) {
|
|
2148
|
-
if (this.options.ignorePermissionErrors) return true;
|
|
2149
|
-
return Boolean(Number(stats.mode) & 256);
|
|
2150
|
-
}
|
|
2151
|
-
/**
|
|
2152
|
-
* Handles emitting unlink events for
|
|
2153
|
-
* files and directories, and via recursion, for
|
|
2154
|
-
* files and directories within directories that are unlinked
|
|
2155
|
-
* @param directory within which the following item is located
|
|
2156
|
-
* @param item base path of item/directory
|
|
2157
|
-
*/
|
|
2158
|
-
_remove(directory, item, isDirectory) {
|
|
2159
|
-
const path = sysPath.join(directory, item);
|
|
2160
|
-
const fullPath = sysPath.resolve(path);
|
|
2161
|
-
isDirectory = isDirectory != null ? isDirectory : this._watched.has(path) || this._watched.has(fullPath);
|
|
2162
|
-
if (!this._throttle("remove", path, 100)) return;
|
|
2163
|
-
if (!isDirectory && this._watched.size === 1) this.add(directory, item, true);
|
|
2164
|
-
this._getWatchedDir(path).getChildren().forEach((nested) => this._remove(path, nested));
|
|
2165
|
-
const parent = this._getWatchedDir(directory);
|
|
2166
|
-
const wasTracked = parent.has(item);
|
|
2167
|
-
parent.remove(item);
|
|
2168
|
-
if (this._symlinkPaths.has(fullPath)) this._symlinkPaths.delete(fullPath);
|
|
2169
|
-
let relPath = path;
|
|
2170
|
-
if (this.options.cwd) relPath = sysPath.relative(this.options.cwd, path);
|
|
2171
|
-
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
|
2172
|
-
if (this._pendingWrites.get(relPath).cancelWait() === EVENTS.ADD) return;
|
|
2173
|
-
}
|
|
2174
|
-
this._watched.delete(path);
|
|
2175
|
-
this._watched.delete(fullPath);
|
|
2176
|
-
const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
|
|
2177
|
-
if (wasTracked && !this._isIgnored(path)) this._emit(eventName, path);
|
|
2178
|
-
this._closePath(path);
|
|
2179
|
-
}
|
|
2180
|
-
/**
|
|
2181
|
-
* Closes all watchers for a path
|
|
2182
|
-
*/
|
|
2183
|
-
_closePath(path) {
|
|
2184
|
-
this._closeFile(path);
|
|
2185
|
-
const dir = sysPath.dirname(path);
|
|
2186
|
-
this._getWatchedDir(dir).remove(sysPath.basename(path));
|
|
2187
|
-
}
|
|
2188
|
-
/**
|
|
2189
|
-
* Closes only file-specific watchers
|
|
2190
|
-
*/
|
|
2191
|
-
_closeFile(path) {
|
|
2192
|
-
const closers = this._closers.get(path);
|
|
2193
|
-
if (!closers) return;
|
|
2194
|
-
closers.forEach((closer) => closer());
|
|
2195
|
-
this._closers.delete(path);
|
|
2196
|
-
}
|
|
2197
|
-
_addPathCloser(path, closer) {
|
|
2198
|
-
if (!closer) return;
|
|
2199
|
-
let list = this._closers.get(path);
|
|
2200
|
-
if (!list) {
|
|
2201
|
-
list = [];
|
|
2202
|
-
this._closers.set(path, list);
|
|
2203
|
-
}
|
|
2204
|
-
list.push(closer);
|
|
2205
|
-
}
|
|
2206
|
-
_readdirp(root, opts) {
|
|
2207
|
-
if (this.closed) return;
|
|
2208
|
-
let stream = readdirp(root, {
|
|
2209
|
-
type: EVENTS.ALL,
|
|
2210
|
-
alwaysStat: true,
|
|
2211
|
-
lstat: true,
|
|
2212
|
-
...opts,
|
|
2213
|
-
depth: 0
|
|
2214
|
-
});
|
|
2215
|
-
this._streams.add(stream);
|
|
2216
|
-
stream.once(STR_CLOSE, () => {
|
|
2217
|
-
stream = void 0;
|
|
2218
|
-
});
|
|
2219
|
-
stream.once("end", () => {
|
|
2220
|
-
if (stream) {
|
|
2221
|
-
this._streams.delete(stream);
|
|
2222
|
-
stream = void 0;
|
|
2223
|
-
}
|
|
2224
|
-
});
|
|
2225
|
-
return stream;
|
|
2226
|
-
}
|
|
2227
|
-
};
|
|
2228
|
-
/**
|
|
2229
|
-
* Instantiates watcher with paths to be tracked.
|
|
2230
|
-
* @param paths file / directory paths
|
|
2231
|
-
* @param options opts, such as `atomic`, `awaitWriteFinish`, `ignored`, and others
|
|
2232
|
-
* @returns an instance of FSWatcher for chaining.
|
|
2233
|
-
* @example
|
|
2234
|
-
* const watcher = watch('.').on('all', (event, path) => { console.log(event, path); });
|
|
2235
|
-
* watch('.', { atomic: true, awaitWriteFinish: true, ignored: (f, stats) => stats?.isFile() && !f.endsWith('.js') })
|
|
2236
|
-
*/
|
|
2237
|
-
function watch$1(paths, options = {}) {
|
|
2238
|
-
const watcher = new FSWatcher(options);
|
|
2239
|
-
watcher.add(paths);
|
|
2240
|
-
return watcher;
|
|
2241
|
-
}
|
|
2242
|
-
var esm_default = {
|
|
2243
|
-
watch: watch$1,
|
|
2244
|
-
FSWatcher
|
|
2245
|
-
};
|
|
2246
|
-
//#endregion
|
|
2247
|
-
//#region ../../0-framework/3-tooling/cli/dist/cli-DBLZkgTv.mjs
|
|
2248
|
-
/**
|
|
2249
|
-
* A user-facing failure with a message that already names the fix (deploy-cli.md
|
|
2250
|
-
* § Error surface). `bin.ts` catches this — and any other Error, including
|
|
2251
|
-
* core's LoadError/LowerError — uniformly: print the message, exit nonzero.
|
|
2252
|
-
*/
|
|
2253
|
-
var CliError = class extends Error {
|
|
2254
|
-
constructor(message) {
|
|
2255
|
-
super(message);
|
|
2256
|
-
this.name = "CliError";
|
|
2257
|
-
}
|
|
2258
|
-
};
|
|
2259
|
-
/**
|
|
2260
|
-
* Pipeline step: find and load `prisma-composer.config.ts` (ADR-0017) — the ONE
|
|
2261
|
-
* file that imports control-plane code. Discovery is the standard walk-up
|
|
2262
|
-
* from the deploy entry's directory (mirrors prisma-next's config-loader);
|
|
2263
|
-
* loading is c12 with that explicit path (rc/global/package.json lookups
|
|
2264
|
-
* disabled), so the config file's own static imports resolve from the app
|
|
2265
|
-
* root by whatever package manager runs — no specifier construction, no
|
|
2266
|
-
* anchoring. The loaded shape is validated field-by-field with CliErrors
|
|
2267
|
-
* naming the field.
|
|
2268
|
-
*/
|
|
2269
|
-
const CONFIG_FILENAME = "prisma-composer.config.ts";
|
|
2270
|
-
/** Walks UP from the entry file's directory looking for the literal CONFIG_FILENAME; undefined when the walk hits the filesystem root. */
|
|
2271
|
-
function findConfigPathForEntry(entryPath) {
|
|
2272
|
-
let current = path.dirname(path.resolve(entryPath));
|
|
2273
|
-
while (true) {
|
|
2274
|
-
const candidate = path.join(current, CONFIG_FILENAME);
|
|
2275
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
2276
|
-
const parent = path.dirname(current);
|
|
2277
|
-
if (parent === current) return void 0;
|
|
2278
|
-
current = parent;
|
|
2279
|
-
}
|
|
2280
|
-
}
|
|
2281
|
-
function missingConfigError(entryPath) {
|
|
2282
|
-
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'.`);
|
|
2283
|
-
}
|
|
2284
|
-
function fieldError(field, requirement) {
|
|
2285
|
-
return new CliError(`${CONFIG_FILENAME}: \`${field}\` ${requirement} — see defineConfig() in '@prisma/composer/config'.`);
|
|
2286
|
-
}
|
|
2287
|
-
function isRecord(value) {
|
|
2288
|
-
return typeof value === "object" && value !== null;
|
|
2289
|
-
}
|
|
2290
|
-
/**
|
|
2291
|
-
* Field-by-field validation of the loaded default export — deliberately no
|
|
2292
|
-
* schema library: each check is a CliError naming the offending field.
|
|
2293
|
-
* Returns the same object, typed.
|
|
2294
|
-
*/
|
|
2295
|
-
function validateConfigShape(loaded, configPath) {
|
|
2296
|
-
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'.`);
|
|
2297
|
-
const extensions = loaded["extensions"];
|
|
2298
|
-
if (!Array.isArray(extensions)) throw fieldError("extensions", "must be an array");
|
|
2299
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2300
|
-
for (const [index, entry] of extensions.entries()) {
|
|
2301
|
-
if (!isRecord(entry)) throw fieldError(`extensions[${index}]`, "must be an extension descriptor object");
|
|
2302
|
-
const id = entry["id"];
|
|
2303
|
-
if (typeof id !== "string" || id.length === 0) throw fieldError(`extensions[${index}].id`, "must be a non-empty string (the extension package name)");
|
|
2304
|
-
if (!isRecord(entry["nodes"])) throw fieldError(`extensions[${index}].nodes`, "must be an object (the node-ID → control registry)");
|
|
2305
|
-
if (seen.has(id)) throw new CliError(`${CONFIG_FILENAME}: extension "${id}" is listed more than once in \`extensions\`.`);
|
|
2306
|
-
seen.add(id);
|
|
2307
|
-
}
|
|
2308
|
-
const state = loaded["state"];
|
|
2309
|
-
if (!isRecord(state) || typeof state["extension"] !== "string" || typeof state["create"] !== "function") throw fieldError("state", "must be a state descriptor (e.g. prismaState())");
|
|
2310
|
-
return blindCast(loaded);
|
|
2311
|
-
}
|
|
2312
|
-
/**
|
|
2313
|
-
* Loads + validates the config at `configPath` via c12 (explicit file; rc /
|
|
2314
|
-
* global-rc / package.json lookups disabled — discovery already happened in
|
|
2315
|
-
* findConfigPathForEntry).
|
|
2316
|
-
*/
|
|
2317
|
-
async function loadAppConfig(configPath) {
|
|
2318
|
-
const result = await c12.loadConfig({
|
|
2319
|
-
name: "prisma-composer",
|
|
2320
|
-
configFile: configPath,
|
|
2321
|
-
cwd: path.dirname(configPath),
|
|
2322
|
-
rcFile: false,
|
|
2323
|
-
globalRc: false,
|
|
2324
|
-
packageJson: false
|
|
2325
|
-
});
|
|
2326
|
-
const loadedFile = result.configFile;
|
|
2327
|
-
if (typeof loadedFile !== "string" || fs.realpathSync(loadedFile) !== fs.realpathSync(configPath)) throw new CliError(`Config loading resolved "${String(loadedFile)}" instead of the discovered "${configPath}" — refusing to deploy against a different file.`);
|
|
2328
|
-
return {
|
|
2329
|
-
path: configPath,
|
|
2330
|
-
config: validateConfigShape(result.config, configPath)
|
|
2331
|
-
};
|
|
2332
|
-
}
|
|
2333
|
-
/**
|
|
2334
|
-
* `loadEntry` imports a user's topology with the host runtime's own module
|
|
2335
|
-
* loader — under node (the CLI's shebang runtime), that loader strips
|
|
2336
|
-
* TypeScript types but has no JSX transform (ADR-0005: the framework
|
|
2337
|
-
* doesn't bundle or transform the app's code; deploy loading is no
|
|
2338
|
-
* exception). A `.tsx`/`.jsx` file anywhere in the topology's import graph
|
|
2339
|
-
* dies with node's raw `ERR_UNKNOWN_FILE_EXTENSION`, which names the file
|
|
2340
|
-
* but not the cause or the fix. This turns that one case into a CliError
|
|
2341
|
-
* message that does — every other load failure (syntax errors, missing
|
|
2342
|
-
* files, non-JSX unknown extensions) is untouched, re-thrown as-is.
|
|
2343
|
-
*/
|
|
2344
|
-
const UNKNOWN_EXTENSION_CODE = "ERR_UNKNOWN_FILE_EXTENSION";
|
|
2345
|
-
const JSX_EXTENSIONS = [".tsx", ".jsx"];
|
|
2346
|
-
function errorCode(error) {
|
|
2347
|
-
return "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
2348
|
-
}
|
|
2349
|
-
function unknownExtensionPath(error) {
|
|
2350
|
-
if (!(error instanceof Error)) return void 0;
|
|
2351
|
-
if (errorCode(error) !== UNKNOWN_EXTENSION_CODE) return void 0;
|
|
2352
|
-
return /Unknown file extension "\.\w+" for (.+)$/.exec(error.message)?.[1];
|
|
2353
|
-
}
|
|
2354
|
-
/**
|
|
2355
|
-
* Returns a CliError-ready message when `error` is node's
|
|
2356
|
-
* `ERR_UNKNOWN_FILE_EXTENSION` for a `.tsx`/`.jsx` file — `undefined` for
|
|
2357
|
-
* every other error (including unknown-extension failures for some other
|
|
2358
|
-
* extension), so the caller can fall through to its normal rethrow.
|
|
2359
|
-
*/
|
|
2360
|
-
function explainJsxLoadError(error, entryPath) {
|
|
2361
|
-
const offendingPath = unknownExtensionPath(error);
|
|
2362
|
-
if (offendingPath === void 0) return void 0;
|
|
2363
|
-
if (!JSX_EXTENSIONS.some((ext) => offendingPath.endsWith(ext))) return void 0;
|
|
2364
|
-
return `"${offendingPath}" can't be loaded: deploy loads "${entryPath}"'s topology with node's own module loader, which strips TypeScript types but has no JSX transform — so a .tsx/.jsx file can't sit anywhere in that import graph.
|
|
2365
|
-
|
|
2366
|
-
Precompile that file in your own build (transform the JSX away, keep the real npm packages external) and import the compiled output instead of the raw .tsx/.jsx — see examples/email/scripts/build.ts in the prisma/composer repo for a worked example.`;
|
|
2367
|
-
}
|
|
2368
|
-
/**
|
|
2369
|
-
* Pipeline step 1 (deploy-cli.md § The pipeline): import the entry module
|
|
2370
|
-
* (resolved against cwd) and require its default export to be a node — a
|
|
2371
|
-
* service or module, branded by core's factories. Whatever this module exports
|
|
2372
|
-
* IS the application; nothing else marks a root (ADR-0003).
|
|
2373
|
-
*/
|
|
2374
|
-
async function loadEntry(entryArg, cwd) {
|
|
2375
|
-
const resolvedPath = path.resolve(cwd, entryArg);
|
|
2376
|
-
let mod;
|
|
2377
|
-
try {
|
|
2378
|
-
mod = await import(pathToFileURL(resolvedPath).href);
|
|
2379
|
-
} catch (error) {
|
|
2380
|
-
const explained = explainJsxLoadError(error, resolvedPath);
|
|
2381
|
-
if (explained !== void 0) throw new CliError(explained);
|
|
2382
|
-
throw error;
|
|
2383
|
-
}
|
|
2384
|
-
const root = blindCast(mod).default;
|
|
2385
|
-
if (!isNode(root) || root.kind === "dependency" || root.kind === "resource") throw new CliError(`Entry module "${resolvedPath}" must default-export a node (a service or a module) — construct it with service() or module() from @prisma/composer.`);
|
|
2386
|
-
return {
|
|
2387
|
-
path: resolvedPath,
|
|
2388
|
-
root: blindCast(root)
|
|
2389
|
-
};
|
|
2390
|
-
}
|
|
2391
|
-
function lookup(extensions, extension, type, expectedKind, what) {
|
|
2392
|
-
const ext = extensions.get(extension);
|
|
2393
|
-
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).`);
|
|
2394
|
-
const descriptor = ext.nodes[type];
|
|
2395
|
-
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(", ")}).`);
|
|
2396
|
-
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.`);
|
|
2397
|
-
}
|
|
2398
|
-
/** Throws a CliError on the first uncovered `(extension, type)`; silent when the config covers the whole graph. */
|
|
2399
|
-
function validateRegistryCoverage(graph, config) {
|
|
2400
|
-
const extensions = new Map(config.extensions.map((descriptor) => [descriptor.id, descriptor]));
|
|
2401
|
-
for (const { id, node } of graph.nodes) {
|
|
2402
|
-
if (node.kind === "resource") {
|
|
2403
|
-
lookup(extensions, node.extension, node.type, "resource", `resource node "${id}"`);
|
|
2404
|
-
continue;
|
|
2405
|
-
}
|
|
2406
|
-
if (node.kind !== "service") continue;
|
|
2407
|
-
lookup(extensions, node.extension, node.type, "service", `service node "${id}"`);
|
|
2408
|
-
lookup(extensions, node.build.extension, node.build.type, "build", `service node "${id}"'s build descriptor`);
|
|
2409
|
-
}
|
|
2410
|
-
}
|
|
2411
|
-
/**
|
|
2412
|
-
* The shared prefix of `deploy`/`destroy`/`dev` (deploy-cli.md § The
|
|
2413
|
-
* pipeline; local-dev spec § 6): config discovery/load, entry load, Load,
|
|
2414
|
-
* registry coverage validation, name resolution, assemble. Deploy and dev
|
|
2415
|
-
* diverge after this — deploy resolves containers/preflight/stack file
|
|
2416
|
-
* against the hosted providers, dev against the local ones — so everything
|
|
2417
|
-
* up to and including assemble lives here once, consumed verbatim by both
|
|
2418
|
-
* `run()` (main.ts) and `runDev()` (dev/run-dev.ts), so the two pipelines
|
|
2419
|
-
* cannot drift.
|
|
2420
|
-
*/
|
|
2421
|
-
/**
|
|
2422
|
-
* The pipeline's front — config discovery/load, entry load, name resolution —
|
|
2423
|
-
* WITHOUT Load, coverage, or assemble. `log` needs only who the app is (its
|
|
2424
|
-
* config and resolved name) to reach the already-running local instance; it
|
|
2425
|
-
* neither builds nor provisions, so it must not require the user's built
|
|
2426
|
-
* output the way the full pipeline does.
|
|
2427
|
-
*/
|
|
2428
|
-
async function resolveAppIdentity(entry, overrideName, cwd, deps = {}) {
|
|
2429
|
-
const resolvedEntryPath = path.resolve(cwd, entry);
|
|
2430
|
-
const configPath = findConfigPathForEntry(resolvedEntryPath);
|
|
2431
|
-
if (configPath === void 0) throw missingConfigError(resolvedEntryPath);
|
|
2432
|
-
const config = deps.config ?? (await loadAppConfig(configPath)).config;
|
|
2433
|
-
const entryModule = await loadEntry(entry, cwd);
|
|
2434
|
-
const name = overrideName ?? entryModule.root.name;
|
|
2435
|
-
if (name.length === 0) throw new CliError("The root node has no name — name it at authoring, or pass --name.");
|
|
2436
|
-
return {
|
|
2437
|
-
configPath,
|
|
2438
|
-
config,
|
|
2439
|
-
name
|
|
2440
|
-
};
|
|
2441
|
-
}
|
|
2442
|
-
/**
|
|
2443
|
-
* Runs config discovery/load, entry load, Load, registry coverage, name
|
|
2444
|
-
* resolution, and assemble — steps 1–6 of `run()`. `onAssembleError`, when
|
|
2445
|
-
* given, lets a caller decorate an assemble failure with command-specific
|
|
2446
|
-
* guidance (destroy's "build first" hint) without this shared step knowing
|
|
2447
|
-
* about any one command.
|
|
2448
|
-
*/
|
|
2449
|
-
async function runPipeline(entry, overrideName, cwd, deps = {}, onAssembleError) {
|
|
2450
|
-
const resolvedEntryPath = path.resolve(cwd, entry);
|
|
2451
|
-
const configPath = findConfigPathForEntry(resolvedEntryPath);
|
|
2452
|
-
if (configPath === void 0) throw missingConfigError(resolvedEntryPath);
|
|
2453
|
-
const config = deps.config ?? (await loadAppConfig(configPath)).config;
|
|
2454
|
-
const entryModule = await loadEntry(entry, cwd);
|
|
2455
|
-
const graph = Load(entryModule.root);
|
|
2456
|
-
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); }).");
|
|
2457
|
-
validateRegistryCoverage(graph, config);
|
|
2458
|
-
const name = overrideName ?? entryModule.root.name;
|
|
2459
|
-
if (name.length === 0) throw new CliError("The root node has no name — name it at authoring, or pass --name.");
|
|
2460
|
-
let assembled;
|
|
2461
|
-
try {
|
|
2462
|
-
assembled = await assembleServices(graph, config, cwd, deps.runAssembler);
|
|
2463
|
-
} catch (error) {
|
|
2464
|
-
if (onAssembleError !== void 0 && error instanceof Error) throw onAssembleError(error);
|
|
2465
|
-
throw error;
|
|
2466
|
-
}
|
|
2467
|
-
return {
|
|
2468
|
-
configPath,
|
|
2469
|
-
config,
|
|
2470
|
-
entryModule,
|
|
2471
|
-
graph,
|
|
2472
|
-
name,
|
|
2473
|
-
assembled
|
|
2474
|
-
};
|
|
2475
|
-
}
|
|
2476
|
-
/**
|
|
2477
|
-
* Pipeline step 7 (deploy-cli.md § The pipeline; design-notes.md's "Driving
|
|
2478
|
-
* Alchemy" call): shell out to the generated stack file. Resolves the
|
|
2479
|
-
* workspace's own installed `alchemy` bin (walking up `node_modules/.bin`
|
|
2480
|
-
* from the generated file's package dir) rather than going through
|
|
2481
|
-
* `bunx`/`npx`, so this works the same under node and bun — the resolved
|
|
2482
|
-
* bin's own launcher (`alchemy/bin/cli.js`) does its own node/bun dispatch
|
|
2483
|
-
* from there, driven by the env it inherits.
|
|
2484
|
-
*/
|
|
2485
|
-
/** Walks up from `startDir` looking for `node_modules/.bin/alchemy`. */
|
|
2486
|
-
function resolveAlchemyBin(startDir) {
|
|
2487
|
-
let dir = startDir;
|
|
2488
|
-
while (true) {
|
|
2489
|
-
const candidate = path.join(dir, "node_modules", ".bin", "alchemy");
|
|
2490
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
2491
|
-
const parent = path.dirname(dir);
|
|
2492
|
-
if (parent === dir) throw new CliError(`Could not find an installed \`alchemy\` bin above "${startDir}" — add "alchemy" as a dependency of your app.`);
|
|
2493
|
-
dir = parent;
|
|
2494
|
-
}
|
|
2495
|
-
}
|
|
2496
|
-
/** Runs `alchemy deploy|destroy <stack file> --yes --stage <stage>`, inheriting stdio + env, plus every extension's resolved container. */
|
|
2497
|
-
function runAlchemy(input) {
|
|
2498
|
-
const result = spawnSync(resolveAlchemyBin(input.cwd), [
|
|
2499
|
-
input.command,
|
|
2500
|
-
input.stackFileRelativePath,
|
|
2501
|
-
"--yes",
|
|
2502
|
-
"--stage",
|
|
2503
|
-
input.stage
|
|
2504
|
-
], {
|
|
2505
|
-
cwd: input.cwd,
|
|
2506
|
-
stdio: "inherit",
|
|
2507
|
-
env: {
|
|
2508
|
-
...input.env ?? process.env,
|
|
2509
|
-
...input.containerEnv
|
|
2510
|
-
}
|
|
2511
|
-
});
|
|
2512
|
-
if (result.error !== void 0) throw result.error;
|
|
2513
|
-
return result.status ?? 1;
|
|
2514
|
-
}
|
|
2515
|
-
/**
|
|
2516
|
-
* Local-dev spec § 6 `generate-dev-stack.ts`: like generate-stack.ts but at
|
|
2517
|
-
* `.prisma-composer/dev/alchemy.run.ts`, always Alchemy stage `dev`, using
|
|
2518
|
-
* Alchemy's own `localState()` and no `report` — dev prints its own front
|
|
2519
|
-
* door (run-dev.ts), so core's presentation-free lower() gets nothing to
|
|
2520
|
-
* call back into. The generated module IS the one orchestration point where
|
|
2521
|
-
* dev provenance is resolved (spec § 3 REVISED): it deserializes the
|
|
2522
|
-
* containers, top-level-awaits `resolveLocalTargets(config)` (the lazy
|
|
2523
|
-
* `localTarget` thunks, ADR-0041), and passes
|
|
2524
|
-
* `providers: localTargetProviders(...)` + `state: localState()` explicitly
|
|
2525
|
-
* — `lower()` learns nothing about dev.
|
|
2526
|
-
*/
|
|
2527
|
-
const DEV_GENERATED_DIR = ".prisma-composer/dev";
|
|
2528
|
-
const DEV_GENERATED_FILE = "alchemy.run.ts";
|
|
2529
|
-
/** A relative import specifier from `.prisma-composer/dev/alchemy.run.ts` to `target` (posix separators). */
|
|
2530
|
-
function relativeImportSpecifier$1(generatedDir, target) {
|
|
2531
|
-
const rel = path.relative(generatedDir, target).split(path.sep).join("/");
|
|
2532
|
-
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
2533
|
-
}
|
|
2534
|
-
function quote$1(value) {
|
|
2535
|
-
return JSON.stringify(value);
|
|
2536
|
-
}
|
|
2537
|
-
function renderBundle$1(bundle) {
|
|
2538
|
-
return `{ dir: ${quote$1(bundle.dir)}, entry: ${quote$1(bundle.entry)} }`;
|
|
2539
|
-
}
|
|
2540
|
-
function renderOptions$1(input) {
|
|
2541
|
-
const lines = [];
|
|
2542
|
-
lines.push(` name: ${quote$1(input.name)},`);
|
|
2543
|
-
lines.push(" bundles: {");
|
|
2544
|
-
for (const [id, bundle] of Object.entries(input.assembled.bundles)) lines.push(` ${quote$1(id)}: ${renderBundle$1(bundle)},`);
|
|
2545
|
-
lines.push(" },");
|
|
2546
|
-
lines.push(" providers: localTargetProviders(resolved, containers, devDir),");
|
|
2547
|
-
lines.push(" state: localState(),");
|
|
2548
|
-
return lines.join("\n");
|
|
2549
|
-
}
|
|
2550
|
-
/** Renders the dev stack module's source (tests assert on it without touching disk). */
|
|
2551
|
-
function renderDevStackFile(input) {
|
|
2552
|
-
const generatedDir = path.join(input.cwd, DEV_GENERATED_DIR);
|
|
2553
|
-
const appImport = relativeImportSpecifier$1(generatedDir, input.entryPath);
|
|
2554
|
-
const configImport = relativeImportSpecifier$1(generatedDir, input.configPath);
|
|
2555
|
-
return `// Generated by \`prisma-composer dev\` — overwritten on every run; do not
|
|
2556
|
-
// edit by hand. Independently runnable from ${quote$1(input.cwd)}:
|
|
2557
|
-
//
|
|
2558
|
-
// alchemy deploy ${DEV_GENERATED_DIR}/${DEV_GENERATED_FILE} --stage dev
|
|
2559
|
-
//
|
|
2560
|
-
// bisects a CLI bug from an Alchemy bug (deploy-cli.md § Implementation decisions).
|
|
2561
|
-
import * as path from 'node:path';
|
|
2562
|
-
import { deserializeContainers } from '@prisma/composer/config';
|
|
2563
|
-
import { lower } from '@prisma/composer/deploy';
|
|
2564
|
-
import { DEV_DIR, localTargetProviders, resolveLocalTargets } from '@prisma/composer/local-target';
|
|
2565
|
-
import { localState } from 'alchemy/State/LocalState';
|
|
2566
|
-
import config from ${quote$1(configImport)};
|
|
2567
|
-
import app from ${quote$1(appImport)};
|
|
2568
|
-
|
|
2569
|
-
const containers = deserializeContainers(config.extensions, process.env);
|
|
2570
|
-
const resolved = await resolveLocalTargets(config);
|
|
2571
|
-
const devDir = path.join(process.cwd(), DEV_DIR);
|
|
2572
|
-
|
|
2573
|
-
export default lower(app, config, {
|
|
2574
|
-
${renderOptions$1(input)}
|
|
2575
|
-
});
|
|
2576
|
-
`;
|
|
2577
|
-
}
|
|
2578
|
-
/** Writes the dev stack file, returning its absolute path. */
|
|
2579
|
-
function writeDevStackFile(input) {
|
|
2580
|
-
const generatedDir = path.join(input.cwd, DEV_GENERATED_DIR);
|
|
2581
|
-
fs.mkdirSync(generatedDir, { recursive: true });
|
|
2582
|
-
const filePath = path.join(generatedDir, DEV_GENERATED_FILE);
|
|
2583
|
-
fs.writeFileSync(filePath, renderDevStackFile(input));
|
|
2584
|
-
return filePath;
|
|
2585
|
-
}
|
|
2586
|
-
const DEV_STACK_RELATIVE_PATH = path.join(DEV_GENERATED_DIR, DEV_GENERATED_FILE);
|
|
2587
|
-
/**
|
|
2588
|
-
* Local-dev spec § 6 `watch.ts`: watches each assembled bundle's declared
|
|
2589
|
-
* `watch` paths (a directory watched recursively, a file watched through
|
|
2590
|
-
* its parent — see `startWatch`), debounced 300 ms and coalesced across
|
|
2591
|
-
* every service — one edit across several files/services still fires
|
|
2592
|
-
* exactly one rebuild.
|
|
2593
|
-
*
|
|
2594
|
-
* The watch ENGINE is `chokidar` v4 (operator decision, design tip 74272d8:
|
|
2595
|
-
* don't-reinvent-the-wheel beats the no-new-deps contract here — it absorbs
|
|
2596
|
-
* the atomic-rename class and the cross-platform recursive-watch
|
|
2597
|
-
* differences; v4 is pure JS, no native code, no glob surface — irrelevant
|
|
2598
|
-
* here anyway, since every target is a literal file or directory path,
|
|
2599
|
-
* never a pattern). The parent-directory indirection for FILE targets stays
|
|
2600
|
-
* OURS (`startWatch` explains why), as does the debounce: chokidar's own
|
|
2601
|
-
* `awaitWriteFinish` is a per-file "has this file's size stopped changing"
|
|
2602
|
-
* poll, a different semantic from "coalesce a burst across many files into
|
|
2603
|
-
* one callback," which is what the dev loop actually needs.
|
|
2604
|
-
*/
|
|
2605
|
-
const DEBOUNCE_MS = 300;
|
|
2606
|
-
/** Bundles → watch targets, plus the addresses with nothing watchable (the pinned one-line startup note). */
|
|
2607
|
-
function watchTargetsFrom(bundles) {
|
|
2608
|
-
const targets = [];
|
|
2609
|
-
const unwatchable = [];
|
|
2610
|
-
for (const [address, bundle] of Object.entries(bundles)) {
|
|
2611
|
-
const paths = bundle.watch;
|
|
2612
|
-
if (paths === void 0 || paths.length === 0) {
|
|
2613
|
-
unwatchable.push(address);
|
|
2614
|
-
continue;
|
|
2615
|
-
}
|
|
2616
|
-
targets.push({
|
|
2617
|
-
address,
|
|
2618
|
-
paths
|
|
2619
|
-
});
|
|
2620
|
-
}
|
|
2621
|
-
return {
|
|
2622
|
-
targets,
|
|
2623
|
-
unwatchable
|
|
2624
|
-
};
|
|
2625
|
-
}
|
|
2626
|
-
/**
|
|
2627
|
-
* Watches every target's paths via chokidar, debounced 300 ms and coalesced
|
|
2628
|
-
* across every service, invoking `onChange` once per burst.
|
|
2629
|
-
*
|
|
2630
|
-
* File targets are watched THROUGH their parent directory, filtered to the
|
|
2631
|
-
* exact path: a watch bound directly to a file dies with the file's inode
|
|
2632
|
-
* on Linux, so `rm -rf dist && bun build --outfile dist/x.mjs` — every
|
|
2633
|
-
* rebuild's shape — would go silently unobserved after the first delete
|
|
2634
|
-
* (chokidar absorbs atomic renames, not unlink+recreate of a directly
|
|
2635
|
-
* watched file; proven by the delete-recreate test failing on Linux CI
|
|
2636
|
-
* only). Directory targets are watched recursively as themselves; a
|
|
2637
|
-
* nonexistent path is treated as a file target, so it starts reporting the
|
|
2638
|
-
* moment something creates it.
|
|
2639
|
-
*/
|
|
2640
|
-
function startWatch(targets, onChange) {
|
|
2641
|
-
let timer;
|
|
2642
|
-
const trigger = () => {
|
|
2643
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
2644
|
-
timer = setTimeout(() => {
|
|
2645
|
-
timer = void 0;
|
|
2646
|
-
onChange();
|
|
2647
|
-
}, DEBOUNCE_MS);
|
|
2648
|
-
};
|
|
2649
|
-
const fileTargets = /* @__PURE__ */ new Set();
|
|
2650
|
-
const directoryRoots = /* @__PURE__ */ new Set();
|
|
2651
|
-
const parentRoots = /* @__PURE__ */ new Set();
|
|
2652
|
-
for (const target of targets) for (const p of target.paths) {
|
|
2653
|
-
const abs = path.resolve(p);
|
|
2654
|
-
let isDirectory = false;
|
|
2655
|
-
try {
|
|
2656
|
-
isDirectory = fs.statSync(abs).isDirectory();
|
|
2657
|
-
} catch {}
|
|
2658
|
-
if (isDirectory) directoryRoots.add(abs);
|
|
2659
|
-
else {
|
|
2660
|
-
fileTargets.add(abs);
|
|
2661
|
-
parentRoots.add(path.dirname(abs));
|
|
2662
|
-
}
|
|
2663
|
-
}
|
|
2664
|
-
const reportError = (error) => {
|
|
2665
|
-
console.error(`[dev] watch error: ${error instanceof Error ? error.message : String(error)}`);
|
|
2666
|
-
};
|
|
2667
|
-
const watchers = [];
|
|
2668
|
-
if (directoryRoots.size > 0) {
|
|
2669
|
-
const directoryWatcher = esm_default.watch([...directoryRoots], { ignoreInitial: true });
|
|
2670
|
-
directoryWatcher.on("all", () => trigger());
|
|
2671
|
-
directoryWatcher.on("error", reportError);
|
|
2672
|
-
watchers.push(directoryWatcher);
|
|
2673
|
-
}
|
|
2674
|
-
if (parentRoots.size > 0) {
|
|
2675
|
-
const parentWatcher = esm_default.watch([...parentRoots], {
|
|
2676
|
-
ignoreInitial: true,
|
|
2677
|
-
depth: 0
|
|
2678
|
-
});
|
|
2679
|
-
parentWatcher.on("all", (_event, eventPath) => {
|
|
2680
|
-
if (fileTargets.has(path.resolve(eventPath))) trigger();
|
|
2681
|
-
});
|
|
2682
|
-
parentWatcher.on("error", reportError);
|
|
2683
|
-
watchers.push(parentWatcher);
|
|
2684
|
-
}
|
|
2685
|
-
let markReady = () => {};
|
|
2686
|
-
const allReady = Promise.all(watchers.map((watcher) => new Promise((resolve) => watcher.on("ready", () => resolve())))).then(() => {});
|
|
2687
|
-
return {
|
|
2688
|
-
ready: Promise.race([allReady, new Promise((resolve) => markReady = resolve)]),
|
|
2689
|
-
stop: () => {
|
|
2690
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
2691
|
-
markReady();
|
|
2692
|
-
for (const watcher of watchers) watcher.close();
|
|
2693
|
-
}
|
|
2694
|
-
};
|
|
2695
|
-
}
|
|
2696
|
-
/**
|
|
2697
|
-
* Local-dev spec § 6 `run-dev.ts`: `prisma-composer dev <entry>` — steps 1–6
|
|
2698
|
-
* of `run()` reused via pipeline.ts, then the dev-only pipeline: capability
|
|
2699
|
-
* check, containers, `--fresh` teardown, preflight, emulators, converge
|
|
2700
|
-
* against a generated dev stack file, attach (front door + merged logs),
|
|
2701
|
-
* watch loop until interrupted.
|
|
2702
|
-
*/
|
|
2703
|
-
function toCliError$1(error) {
|
|
2704
|
-
return error instanceof CliError ? error : new CliError(error instanceof Error ? error.message : String(error));
|
|
2705
|
-
}
|
|
2706
|
-
/** `[dev] ready:` then one line per endpoint, ordered by address depth (fewest dots first) then lexicographic. Exported for tests. */
|
|
2707
|
-
function renderFrontDoor(endpoints) {
|
|
2708
|
-
return ["[dev] ready:", ...[...endpoints].sort((a, b) => {
|
|
2709
|
-
const depthA = a.address.split(".").length;
|
|
2710
|
-
const depthB = b.address.split(".").length;
|
|
2711
|
-
if (depthA !== depthB) return depthA - depthB;
|
|
2712
|
-
return a.address < b.address ? -1 : a.address > b.address ? 1 : 0;
|
|
2713
|
-
}).map((e) => `[dev] ${e.address} ${e.url}`)];
|
|
2714
|
-
}
|
|
2715
|
-
function printFrontDoor(endpoints) {
|
|
2716
|
-
for (const line of renderFrontDoor(endpoints)) console.log(line);
|
|
2717
|
-
}
|
|
2718
|
-
const EMULATOR_RETRY_ATTEMPTS = 5;
|
|
2719
|
-
const EMULATOR_RETRY_DELAY_MS = 500;
|
|
2720
|
-
/** 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`). */
|
|
2721
|
-
async function withEmulatorRetry(call) {
|
|
2722
|
-
let lastError;
|
|
2723
|
-
for (let attempt = 1; attempt <= EMULATOR_RETRY_ATTEMPTS; attempt += 1) try {
|
|
2724
|
-
return await call();
|
|
2725
|
-
} catch (error) {
|
|
2726
|
-
lastError = error;
|
|
2727
|
-
if (attempt < EMULATOR_RETRY_ATTEMPTS) await new Promise((resolve) => setTimeout(resolve, EMULATOR_RETRY_DELAY_MS));
|
|
2728
|
-
}
|
|
2729
|
-
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
2730
|
-
}
|
|
2731
|
-
async function mergedEndpoints(attachments) {
|
|
2732
|
-
return (await Promise.all(attachments.map((a) => withEmulatorRetry(() => a.endpoints())))).flat();
|
|
2733
|
-
}
|
|
2734
|
-
/** Runs the full dev pipeline; returns the process exit code. */
|
|
2735
|
-
async function runDev(args, deps = {}) {
|
|
2736
|
-
if (process.platform === "win32") throw new CliError("local dev is not supported on Windows yet.");
|
|
2737
|
-
const cwd = process.cwd();
|
|
2738
|
-
const devDir = path.join(cwd, DEV_DIR);
|
|
2739
|
-
const pipelineDeps = {
|
|
2740
|
-
runAssembler: deps.runAssembler,
|
|
2741
|
-
config: deps.config
|
|
2742
|
-
};
|
|
2743
|
-
const { configPath, config, entryModule, graph, name, assembled } = await runPipeline(args.entry, args.name, cwd, pipelineDeps);
|
|
2744
|
-
let resolved;
|
|
2745
|
-
try {
|
|
2746
|
-
resolved = await resolveLocalTargets(config);
|
|
2747
|
-
} catch (error) {
|
|
2748
|
-
throw toCliError$1(error);
|
|
2749
|
-
}
|
|
2750
|
-
const containers = /* @__PURE__ */ new Map();
|
|
2751
|
-
for (const [id, dev] of resolved) try {
|
|
2752
|
-
containers.set(id, await dev.container.ensure({
|
|
2753
|
-
appName: name,
|
|
2754
|
-
stage: void 0
|
|
2755
|
-
}));
|
|
2756
|
-
} catch (error) {
|
|
2757
|
-
throw toCliError$1(error);
|
|
2758
|
-
}
|
|
2759
|
-
if (args.fresh) for (const [id, dev] of resolved) {
|
|
2760
|
-
if (dev.teardown === void 0) continue;
|
|
2761
|
-
try {
|
|
2762
|
-
await dev.teardown({
|
|
2763
|
-
container: containers.get(id),
|
|
2764
|
-
stage: void 0
|
|
2765
|
-
});
|
|
2766
|
-
} catch (error) {
|
|
2767
|
-
throw toCliError$1(error);
|
|
2768
|
-
}
|
|
2769
|
-
}
|
|
2770
|
-
for (const [id, dev] of resolved) {
|
|
2771
|
-
if (dev.preflight === void 0) continue;
|
|
2772
|
-
try {
|
|
2773
|
-
await dev.preflight({
|
|
2774
|
-
graph,
|
|
2775
|
-
container: containers.get(id),
|
|
2776
|
-
stage: void 0
|
|
2777
|
-
});
|
|
2778
|
-
} catch (error) {
|
|
2779
|
-
throw toCliError$1(error);
|
|
2780
|
-
}
|
|
2781
|
-
}
|
|
2782
|
-
for (const [id, dev] of resolved) {
|
|
2783
|
-
if (dev.emulators === void 0) continue;
|
|
2784
|
-
try {
|
|
2785
|
-
await dev.emulators({
|
|
2786
|
-
graph,
|
|
2787
|
-
container: containers.get(id),
|
|
2788
|
-
devDir
|
|
2789
|
-
});
|
|
2790
|
-
} catch (error) {
|
|
2791
|
-
throw toCliError$1(error);
|
|
2792
|
-
}
|
|
2793
|
-
}
|
|
2794
|
-
const converge = () => {
|
|
2795
|
-
const stackPath = writeDevStackFile({
|
|
2796
|
-
entryPath: entryModule.path,
|
|
2797
|
-
cwd,
|
|
2798
|
-
configPath,
|
|
2799
|
-
name,
|
|
2800
|
-
assembled
|
|
2801
|
-
});
|
|
2802
|
-
const status = (deps.alchemy ?? runAlchemy)({
|
|
2803
|
-
command: "deploy",
|
|
2804
|
-
stackFileRelativePath: DEV_STACK_RELATIVE_PATH,
|
|
2805
|
-
cwd,
|
|
2806
|
-
stage: "dev",
|
|
2807
|
-
containerEnv: containerEnv(containers)
|
|
2808
|
-
});
|
|
2809
|
-
if (status !== 0) {
|
|
2810
|
-
console.error(`\nGenerated stack file: ${stackPath}`);
|
|
2811
|
-
console.error(`Run \`alchemy deploy ${DEV_STACK_RELATIVE_PATH} --yes --stage dev\` from ${cwd} to reproduce this directly.`);
|
|
2812
|
-
}
|
|
2813
|
-
return status;
|
|
2814
|
-
};
|
|
2815
|
-
const firstStatus = converge();
|
|
2816
|
-
if (firstStatus !== 0) return firstStatus;
|
|
2817
|
-
const attachments = [];
|
|
2818
|
-
for (const [id, dev] of resolved) attachments.push(await dev.attach({
|
|
2819
|
-
container: containers.get(id),
|
|
2820
|
-
devDir
|
|
2821
|
-
}));
|
|
2822
|
-
const started = [];
|
|
2823
|
-
for (const attachment of attachments) try {
|
|
2824
|
-
await withEmulatorRetry(() => attachment.startServices());
|
|
2825
|
-
started.push(attachment);
|
|
2826
|
-
} catch (error) {
|
|
2827
|
-
await Promise.all(started.map((a) => a.stopServices().catch(() => void 0)));
|
|
2828
|
-
throw toCliError$1(error);
|
|
2829
|
-
}
|
|
2830
|
-
printFrontDoor(await mergedEndpoints(attachments));
|
|
2831
|
-
console.log(`[dev] logs: prisma-composer log ${args.entry}`);
|
|
2832
|
-
const { targets, unwatchable } = watchTargetsFrom(assembled.bundles);
|
|
2833
|
-
for (const address of unwatchable) console.log(`[dev] ${address} has no watchable inputs`);
|
|
2834
|
-
const watch = startWatch(targets, () => {
|
|
2835
|
-
(async () => {
|
|
2836
|
-
try {
|
|
2837
|
-
const rePipeline = await runPipeline(args.entry, args.name, cwd, pipelineDeps);
|
|
2838
|
-
writeDevStackFile({
|
|
2839
|
-
entryPath: rePipeline.entryModule.path,
|
|
2840
|
-
cwd,
|
|
2841
|
-
configPath: rePipeline.configPath,
|
|
2842
|
-
name: rePipeline.name,
|
|
2843
|
-
assembled: rePipeline.assembled
|
|
2844
|
-
});
|
|
2845
|
-
if ((deps.alchemy ?? runAlchemy)({
|
|
2846
|
-
command: "deploy",
|
|
2847
|
-
stackFileRelativePath: DEV_STACK_RELATIVE_PATH,
|
|
2848
|
-
cwd,
|
|
2849
|
-
stage: "dev",
|
|
2850
|
-
containerEnv: containerEnv(containers)
|
|
2851
|
-
}) !== 0) {
|
|
2852
|
-
console.error("[dev] converge failed — the running app is untouched; still watching.");
|
|
2853
|
-
return;
|
|
2854
|
-
}
|
|
2855
|
-
printFrontDoor(await mergedEndpoints(attachments));
|
|
2856
|
-
} catch (error) {
|
|
2857
|
-
console.error(`[dev] rebuild failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2858
|
-
}
|
|
2859
|
-
})();
|
|
2860
|
-
});
|
|
2861
|
-
await watch.ready;
|
|
2862
|
-
await new Promise((resolve) => {
|
|
2863
|
-
let stopping = false;
|
|
2864
|
-
const finish = () => {
|
|
2865
|
-
if (stopping) return;
|
|
2866
|
-
stopping = true;
|
|
2867
|
-
console.log("[dev] stopping — the app's services are stopping; emulators and data stay up.");
|
|
2868
|
-
watch.stop();
|
|
2869
|
-
(async () => {
|
|
2870
|
-
for (const attachment of attachments) await attachment.stopServices().catch(() => void 0);
|
|
2871
|
-
console.log("[dev] stopped.");
|
|
2872
|
-
resolve();
|
|
2873
|
-
})();
|
|
2874
|
-
};
|
|
2875
|
-
process.removeAllListeners("SIGINT");
|
|
2876
|
-
process.removeAllListeners("SIGTERM");
|
|
2877
|
-
process.on("SIGINT", finish);
|
|
2878
|
-
process.on("SIGTERM", finish);
|
|
2879
|
-
});
|
|
2880
|
-
return 0;
|
|
2881
|
-
}
|
|
2882
|
-
/** Pipeline step 6: writes a regenerated-every-run, independently runnable stack module at `.prisma-composer/alchemy.run.ts`. */
|
|
2883
|
-
const GENERATED_DIR = ".prisma-composer";
|
|
2884
|
-
const GENERATED_FILE = "alchemy.run.ts";
|
|
2885
|
-
/** A relative import specifier from `.prisma-composer/alchemy.run.ts` to `target` (posix separators). */
|
|
2886
|
-
function relativeImportSpecifier(generatedDir, target) {
|
|
2887
|
-
const rel = path.relative(generatedDir, target).split(path.sep).join("/");
|
|
2888
|
-
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
2889
|
-
}
|
|
2890
|
-
function quote(value) {
|
|
2891
|
-
return JSON.stringify(value);
|
|
2892
|
-
}
|
|
2893
|
-
function renderBundle(bundle) {
|
|
2894
|
-
return `{ dir: ${quote(bundle.dir)}, entry: ${quote(bundle.entry)} }`;
|
|
2895
|
-
}
|
|
2896
|
-
function renderOptions(input) {
|
|
2897
|
-
const lines = [];
|
|
2898
|
-
lines.push(` name: ${quote(input.name)},`);
|
|
2899
|
-
lines.push(" bundles: {");
|
|
2900
|
-
for (const [id, bundle] of Object.entries(input.assembled.bundles)) lines.push(` ${quote(id)}: ${renderBundle(bundle)},`);
|
|
2901
|
-
lines.push(" },");
|
|
2902
|
-
lines.push(" report: deploymentReport,");
|
|
2903
|
-
return lines.join("\n");
|
|
2904
|
-
}
|
|
2905
|
-
/** 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. */
|
|
2906
|
-
function renderStackFile(input) {
|
|
2907
|
-
const generatedDir = path.join(input.cwd, GENERATED_DIR);
|
|
2908
|
-
const appImport = relativeImportSpecifier(generatedDir, input.entryPath);
|
|
2909
|
-
const configImport = relativeImportSpecifier(generatedDir, input.configPath);
|
|
2910
|
-
return `// Generated by \`prisma-composer deploy\`/\`prisma-composer destroy\` — overwritten on every
|
|
2911
|
-
// run; do not edit by hand. Independently runnable from ${quote(input.cwd)}:
|
|
2912
|
-
//
|
|
2913
|
-
// alchemy deploy ${GENERATED_DIR}/${GENERATED_FILE}
|
|
2914
|
-
//
|
|
2915
|
-
// bisects a CLI bug from an Alchemy bug (deploy-cli.md § Implementation decisions).
|
|
2916
|
-
import { lower } from '@prisma/composer/deploy';
|
|
2917
|
-
import { deploymentReport } from '@prisma/composer/report';
|
|
2918
|
-
import config from ${quote(configImport)};
|
|
2919
|
-
import app from ${quote(appImport)};
|
|
2920
|
-
|
|
2921
|
-
export default lower(app, config, {
|
|
2922
|
-
${renderOptions(input)}
|
|
2923
|
-
});
|
|
2924
|
-
`;
|
|
2925
|
-
}
|
|
2926
|
-
/** Writes the stack file, returning its absolute path. */
|
|
2927
|
-
function writeStackFile(input) {
|
|
2928
|
-
const generatedDir = path.join(input.cwd, GENERATED_DIR);
|
|
2929
|
-
fs.mkdirSync(generatedDir, { recursive: true });
|
|
2930
|
-
const filePath = path.join(generatedDir, GENERATED_FILE);
|
|
2931
|
-
fs.writeFileSync(filePath, renderStackFile(input));
|
|
2932
|
-
return filePath;
|
|
2933
|
-
}
|
|
2934
|
-
const GENERATED_STACK_RELATIVE_PATH = path.join(GENERATED_DIR, GENERATED_FILE);
|
|
2935
|
-
/**
|
|
2936
|
-
* `prisma-composer log <entry> [address]` — tail the merged logs of an
|
|
2937
|
-
* already-running local app. It resolves the app the way `dev` does (config →
|
|
2938
|
-
* localTarget → container → attach) but calls only the attachment's `logs()`
|
|
2939
|
-
* view: it neither builds, provisions, starts, nor stops anything. `dev` no
|
|
2940
|
-
* longer streams logs inline (that drowned the front door once it supervises
|
|
2941
|
-
* more than one service); this is where logs live.
|
|
2942
|
-
*/
|
|
2943
|
-
function toCliError(error) {
|
|
2944
|
-
return error instanceof CliError ? error : new CliError(error instanceof Error ? error.message : String(error));
|
|
2945
|
-
}
|
|
2946
|
-
/** Runs the log tail until interrupted; returns the process exit code. */
|
|
2947
|
-
async function runLog(args, deps = {}) {
|
|
2948
|
-
if (process.platform === "win32") throw new CliError("local dev is not supported on Windows yet.");
|
|
2949
|
-
const cwd = process.cwd();
|
|
2950
|
-
const devDir = path.join(cwd, DEV_DIR);
|
|
2951
|
-
const { config, name } = deps.identity ?? await resolveAppIdentity(args.entry, args.name, cwd, { config: deps.config });
|
|
2952
|
-
let resolved;
|
|
2953
|
-
try {
|
|
2954
|
-
resolved = await resolveLocalTargets(config);
|
|
2955
|
-
} catch (error) {
|
|
2956
|
-
throw toCliError(error);
|
|
2957
|
-
}
|
|
2958
|
-
const attachments = [];
|
|
2959
|
-
for (const target of resolved.values()) try {
|
|
2960
|
-
const container = await target.container.ensure({
|
|
2961
|
-
appName: name,
|
|
2962
|
-
stage: void 0
|
|
2963
|
-
});
|
|
2964
|
-
attachments.push(await target.attach({
|
|
2965
|
-
container,
|
|
2966
|
-
devDir
|
|
2967
|
-
}));
|
|
2968
|
-
} catch (error) {
|
|
2969
|
-
throw toCliError(error);
|
|
2970
|
-
}
|
|
2971
|
-
const services = (await Promise.all(attachments.map((a) => a.endpoints()))).flat();
|
|
2972
|
-
if (services.length === 0) {
|
|
2973
|
-
console.error(`[log] no running services for "${name}" — start it first with \`prisma-composer dev ${args.entry}\`.`);
|
|
2974
|
-
return 0;
|
|
2975
|
-
}
|
|
2976
|
-
if (args.address !== void 0 && !services.some((s) => s.address === args.address)) throw new CliError(`no service "${args.address}" in "${name}" — running services: ${services.map((s) => s.address).join(", ")}.`);
|
|
2977
|
-
const controller = new AbortController();
|
|
2978
|
-
const finish = () => controller.abort();
|
|
2979
|
-
process.on("SIGINT", finish);
|
|
2980
|
-
process.on("SIGTERM", finish);
|
|
2981
|
-
try {
|
|
2982
|
-
await Promise.all(attachments.map(async (attachment) => {
|
|
2983
|
-
try {
|
|
2984
|
-
for await (const { service, line } of attachment.logs(controller.signal, { tail: args.tail })) {
|
|
2985
|
-
if (controller.signal.aborted) return;
|
|
2986
|
-
if (args.address !== void 0 && service !== args.address) continue;
|
|
2987
|
-
console.log(`[${service}] ${line}`);
|
|
2988
|
-
}
|
|
2989
|
-
} catch (error) {
|
|
2990
|
-
if (!controller.signal.aborted) console.error(`[log] stream failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2991
|
-
}
|
|
2992
|
-
}));
|
|
2993
|
-
} finally {
|
|
2994
|
-
process.off("SIGINT", finish);
|
|
2995
|
-
process.off("SIGTERM", finish);
|
|
2996
|
-
}
|
|
2997
|
-
return 0;
|
|
2998
|
-
}
|
|
2999
|
-
/** 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. */
|
|
3000
|
-
function validateStageName(stage) {
|
|
3001
|
-
const result = spawnSync("git", ["check-ref-format", `refs/heads/${stage}`], { stdio: "ignore" });
|
|
3002
|
-
if (result.error) throw new CliError(`git is required to validate --stage "${stage}" (git check-ref-format): ${result.error.message}.`);
|
|
3003
|
-
if (result.status !== 0) throw new CliError(`Invalid --stage "${stage}": must be a valid git ref name (git check-ref-format rejected "refs/heads/${stage}").`);
|
|
3004
|
-
}
|
|
3005
|
-
/**
|
|
3006
|
-
* Argument parsing (clipanion — prisma-next's CLI idiom, see
|
|
3007
|
-
* prisma-next/packages/1-framework/3-tooling/cli/src/migration-cli.ts) +
|
|
3008
|
-
* orchestration of deploy-cli.md § The pipeline.
|
|
3009
|
-
*/
|
|
3010
|
-
const BINARY_NAME = "prisma-composer";
|
|
3011
|
-
/** The <entry>/--name/--stage surface shared by deploy and destroy; execute() is unused — run() drives the pipeline directly so error handling stays under this module's control. */
|
|
3012
|
-
var DeployCliCommand = class extends Command {
|
|
3013
|
-
entry = Option.String({ name: "entry" });
|
|
3014
|
-
name = Option.String("--name", { description: "Override the root node's name — the deploy's application name." });
|
|
3015
|
-
stage = Option.String("--stage", { description: "Alchemy stage to target." });
|
|
3016
|
-
production = Option.Boolean("--production", false, { description: "destroy: tear down the project-level production environment (required to destroy production)." });
|
|
3017
|
-
async execute() {
|
|
3018
|
-
return 0;
|
|
3019
|
-
}
|
|
3020
|
-
};
|
|
3021
|
-
var DeployCommand = class extends DeployCliCommand {
|
|
3022
|
-
static paths = [["deploy"]];
|
|
3023
|
-
static usage = Command.Usage({
|
|
3024
|
-
description: "Deploy the application whose root node is <entry>'s default export.",
|
|
3025
|
-
examples: [["Deploy an app", "$0 deploy src/service.ts"]]
|
|
3026
|
-
});
|
|
3027
|
-
action = "deploy";
|
|
3028
|
-
};
|
|
3029
|
-
var DestroyCommand = class extends DeployCliCommand {
|
|
3030
|
-
static paths = [["destroy"]];
|
|
3031
|
-
static usage = Command.Usage({
|
|
3032
|
-
description: "Tear down the application whose root node is <entry>'s default export — same derivation as deploy, Alchemy destroy.",
|
|
3033
|
-
examples: [["Destroy an app", "$0 destroy src/service.ts"]]
|
|
3034
|
-
});
|
|
3035
|
-
action = "destroy";
|
|
3036
|
-
};
|
|
3037
|
-
/** `<entry>`/`--name`/`--fresh` only — no `--stage`/`--production` (local-dev spec § 6: a working directory has exactly one dev instance, no stages). */
|
|
3038
|
-
var DevCommand = class extends Command {
|
|
3039
|
-
static paths = [["dev"]];
|
|
3040
|
-
static usage = Command.Usage({
|
|
3041
|
-
description: "Bring up the application whose root node is <entry>'s default export, entirely on this machine, credential-free.",
|
|
3042
|
-
examples: [["Run an app locally", "$0 dev src/service.ts"]]
|
|
3043
|
-
});
|
|
3044
|
-
entry = Option.String({ name: "entry" });
|
|
3045
|
-
name = Option.String("--name", { description: "Override the root node's name — the dev instance's application name." });
|
|
3046
|
-
fresh = Option.Boolean("--fresh", false, { description: "Destroy the dev stack and wipe the dev state directory before starting." });
|
|
3047
|
-
async execute() {
|
|
3048
|
-
return 0;
|
|
3049
|
-
}
|
|
3050
|
-
};
|
|
3051
|
-
var LogCommand = class extends Command {
|
|
3052
|
-
static paths = [["log"]];
|
|
3053
|
-
static usage = Command.Usage({
|
|
3054
|
-
description: "Tail the merged logs of the locally-running application whose root node is <entry>'s default export.",
|
|
3055
|
-
examples: [["Tail every service", "$0 log src/service.ts"], ["Tail one service", "$0 log src/service.ts catalog.service"]]
|
|
3056
|
-
});
|
|
3057
|
-
entry = Option.String({ name: "entry" });
|
|
3058
|
-
address = Option.String({
|
|
3059
|
-
name: "address",
|
|
3060
|
-
required: false
|
|
3061
|
-
});
|
|
3062
|
-
name = Option.String("--name", { description: "Override the root node's name — the dev instance's application name." });
|
|
3063
|
-
tail = Option.String("--tail", { description: `How many trailing history lines to show before live output (default ${String(DEFAULT_LOG_TAIL)}).` });
|
|
3064
|
-
async execute() {
|
|
3065
|
-
return 0;
|
|
3066
|
-
}
|
|
3067
|
-
};
|
|
3068
|
-
function buildCli() {
|
|
3069
|
-
return Cli.from([
|
|
3070
|
-
DeployCommand,
|
|
3071
|
-
DestroyCommand,
|
|
3072
|
-
DevCommand,
|
|
3073
|
-
LogCommand
|
|
3074
|
-
], {
|
|
3075
|
-
binaryName: BINARY_NAME,
|
|
3076
|
-
binaryLabel: "The prisma-composer deploy CLI"
|
|
3077
|
-
});
|
|
3078
|
-
}
|
|
3079
|
-
/** Thrown internally when the user explicitly asked for `--help`/`-h` — run() prints it to stdout and exits 0; not a usage error. */
|
|
3080
|
-
var HelpRequested = class extends Error {};
|
|
3081
|
-
/** Duck-typed: clipanion's UnknownSyntaxError isn't re-exported, so match its name + clipanion.type discriminator (mirrors prisma-next's migration-cli.ts). */
|
|
3082
|
-
function isUnknownSyntaxError(error) {
|
|
3083
|
-
if (!(error instanceof Error) || error.name !== "UnknownSyntaxError") return false;
|
|
3084
|
-
const meta = error.clipanion;
|
|
3085
|
-
return typeof meta === "object" && meta !== null && meta.type === "none";
|
|
3086
|
-
}
|
|
3087
|
-
/** `log`'s default backlog: an empty screen reads as broken, so show a little recent history before going live. */
|
|
3088
|
-
const DEFAULT_LOG_TAIL = 20;
|
|
3089
|
-
/** Exported for direct testing (main.test.ts) — not part of the package's public barrel (see index.ts). */
|
|
3090
|
-
function parseArgs(argv) {
|
|
3091
|
-
const cli = buildCli();
|
|
3092
|
-
let command;
|
|
3093
|
-
try {
|
|
3094
|
-
command = cli.process([...argv]);
|
|
3095
|
-
} catch (error) {
|
|
3096
|
-
if (isUnknownSyntaxError(error) || error instanceof UsageError) throw new UsageError(cli.usage(null, { detailed: true }));
|
|
3097
|
-
throw error;
|
|
3098
|
-
}
|
|
3099
|
-
if (command instanceof DeployCommand || command instanceof DestroyCommand) return {
|
|
3100
|
-
command: command.action,
|
|
3101
|
-
entry: command.entry,
|
|
3102
|
-
name: command.name,
|
|
3103
|
-
stage: command.stage,
|
|
3104
|
-
production: command.production,
|
|
3105
|
-
fresh: false
|
|
3106
|
-
};
|
|
3107
|
-
if (command instanceof DevCommand) return {
|
|
3108
|
-
command: "dev",
|
|
3109
|
-
entry: command.entry,
|
|
3110
|
-
name: command.name,
|
|
3111
|
-
stage: void 0,
|
|
3112
|
-
production: false,
|
|
3113
|
-
fresh: command.fresh
|
|
3114
|
-
};
|
|
3115
|
-
if (command instanceof LogCommand) {
|
|
3116
|
-
const tail = command.tail === void 0 ? DEFAULT_LOG_TAIL : Number.parseInt(command.tail, 10);
|
|
3117
|
-
if (Number.isNaN(tail) || tail < 0) throw new UsageError("`--tail` must be a non-negative integer.");
|
|
3118
|
-
return {
|
|
3119
|
-
command: "log",
|
|
3120
|
-
entry: command.entry,
|
|
3121
|
-
name: command.name,
|
|
3122
|
-
stage: void 0,
|
|
3123
|
-
production: false,
|
|
3124
|
-
fresh: false,
|
|
3125
|
-
address: command.address,
|
|
3126
|
-
tail
|
|
3127
|
-
};
|
|
3128
|
-
}
|
|
3129
|
-
if (argv.includes("--help") || argv.includes("-h")) throw new HelpRequested(cli.usage(null, { detailed: true }));
|
|
3130
|
-
throw new UsageError(cli.usage(null, { detailed: true }));
|
|
3131
|
-
}
|
|
3132
|
-
/** Destroy must name its target explicitly — no silent default to production (spec §10). */
|
|
3133
|
-
function effectiveStage(args) {
|
|
3134
|
-
if (args.command === "deploy") {
|
|
3135
|
-
if (args.production) throw new CliError("--production is only valid with `destroy`; `deploy` targets production by default (omit --stage).");
|
|
3136
|
-
return args.stage;
|
|
3137
|
-
}
|
|
3138
|
-
if (args.stage !== void 0 && args.production) throw new CliError("Pass either --stage <name> or --production to `destroy`, not both.");
|
|
3139
|
-
if (args.stage === void 0 && !args.production) throw new CliError("`destroy` requires an explicit target: --stage <name> to tear down a branch environment, or --production to tear down the production environment.");
|
|
3140
|
-
return args.production ? void 0 : args.stage;
|
|
3141
|
-
}
|
|
3142
|
-
const ALCHEMY_STATE_DIR = ".alchemy";
|
|
3143
|
-
/** Warns (doesn't fail) when destroy finds no local deploy state under cwd — likely wrong directory or nothing deployed yet. */
|
|
3144
|
-
function warnIfNoLocalDeployState(cwd) {
|
|
3145
|
-
const stateDir = path.join(cwd, ALCHEMY_STATE_DIR);
|
|
3146
|
-
if (!(fs.existsSync(stateDir) && fs.readdirSync(stateDir).length > 0)) console.warn(`\nNo prior deploy state under ${cwd} — if you deployed from a different directory, run destroy from there; otherwise this is a no-op.`);
|
|
3147
|
-
}
|
|
3148
|
-
/** Runs the full pipeline; returns the process exit code. */
|
|
3149
|
-
async function run(argv, deps = {}) {
|
|
3150
|
-
let args;
|
|
3151
|
-
try {
|
|
3152
|
-
args = parseArgs(argv);
|
|
3153
|
-
} catch (error) {
|
|
3154
|
-
if (error instanceof HelpRequested) {
|
|
3155
|
-
console.log(error.message);
|
|
3156
|
-
return 0;
|
|
3157
|
-
}
|
|
3158
|
-
throw error;
|
|
3159
|
-
}
|
|
3160
|
-
if (args.command === "dev") return runDev(args, deps);
|
|
3161
|
-
if (args.command === "log") return runLog({
|
|
3162
|
-
entry: args.entry,
|
|
3163
|
-
name: args.name,
|
|
3164
|
-
address: args.address,
|
|
3165
|
-
tail: args.tail ?? DEFAULT_LOG_TAIL
|
|
3166
|
-
}, { config: deps.config });
|
|
3167
|
-
const stage = effectiveStage(args);
|
|
3168
|
-
if (stage !== void 0) validateStageName(stage);
|
|
3169
|
-
const cwd = process.cwd();
|
|
3170
|
-
if (args.command === "destroy") warnIfNoLocalDeployState(cwd);
|
|
3171
|
-
const pipelineDeps = {
|
|
3172
|
-
runAssembler: deps.runAssembler,
|
|
3173
|
-
config: deps.config
|
|
3174
|
-
};
|
|
3175
|
-
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;
|
|
3176
|
-
const { configPath, config, entryModule, graph, name, assembled } = await runPipeline(args.entry, args.name, cwd, pipelineDeps, onAssembleError);
|
|
3177
|
-
const containers = /* @__PURE__ */ new Map();
|
|
3178
|
-
for (const extension of config.extensions) {
|
|
3179
|
-
if (extension.container === void 0) continue;
|
|
3180
|
-
try {
|
|
3181
|
-
if (args.command === "deploy") containers.set(extension.id, await extension.container.ensure({
|
|
3182
|
-
appName: name,
|
|
3183
|
-
stage
|
|
3184
|
-
}));
|
|
3185
|
-
else {
|
|
3186
|
-
const instance = await extension.container.locate({
|
|
3187
|
-
appName: name,
|
|
3188
|
-
stage
|
|
3189
|
-
});
|
|
3190
|
-
if (instance === void 0) throw new CliError(`Nothing deployed for ${name}${stage !== void 0 ? `/${stage}` : ""} — deploy it first.`);
|
|
3191
|
-
containers.set(extension.id, instance);
|
|
3192
|
-
}
|
|
3193
|
-
} catch (error) {
|
|
3194
|
-
throw error instanceof CliError ? error : new CliError(error instanceof Error ? error.message : String(error));
|
|
3195
|
-
}
|
|
3196
|
-
}
|
|
3197
|
-
const alchemyStage = containers.get(config.state.extension)?.alchemyStage ?? stage;
|
|
3198
|
-
if (alchemyStage === void 0) throw new CliError("The configured deploy target supplied no deploy scope (its container defines no alchemyStage), so Alchemy has no stage to run under. " + (args.command === "deploy" ? "Pass --stage <name> to choose the deploy scope explicitly." : "destroy --production needs a target whose container supplies the production deploy scope."));
|
|
3199
|
-
if (args.command === "deploy") for (const extension of config.extensions) {
|
|
3200
|
-
if (extension.preflight === void 0) continue;
|
|
3201
|
-
try {
|
|
3202
|
-
await extension.preflight({
|
|
3203
|
-
graph,
|
|
3204
|
-
container: containers.get(extension.id),
|
|
3205
|
-
stage
|
|
3206
|
-
});
|
|
3207
|
-
} catch (error) {
|
|
3208
|
-
throw error instanceof CliError ? error : new CliError(error instanceof Error ? error.message : String(error));
|
|
3209
|
-
}
|
|
3210
|
-
}
|
|
3211
|
-
const stackPath = writeStackFile({
|
|
3212
|
-
entryPath: entryModule.path,
|
|
3213
|
-
cwd,
|
|
3214
|
-
configPath,
|
|
3215
|
-
name,
|
|
3216
|
-
assembled
|
|
3217
|
-
});
|
|
3218
|
-
try {
|
|
3219
|
-
const status = (deps.alchemy ?? runAlchemy)({
|
|
3220
|
-
command: args.command,
|
|
3221
|
-
stackFileRelativePath: GENERATED_STACK_RELATIVE_PATH,
|
|
3222
|
-
cwd,
|
|
3223
|
-
stage: alchemyStage,
|
|
3224
|
-
containerEnv: containerEnv(containers)
|
|
3225
|
-
});
|
|
3226
|
-
if (status !== 0) {
|
|
3227
|
-
console.error(`\nGenerated stack file: ${stackPath}`);
|
|
3228
|
-
console.error(`Run \`alchemy ${args.command} ${GENERATED_STACK_RELATIVE_PATH} --yes --stage ${alchemyStage}\` from ${cwd} to reproduce this directly.`);
|
|
3229
|
-
return status;
|
|
3230
|
-
}
|
|
3231
|
-
if (args.command === "destroy") {
|
|
3232
|
-
for (const extension of config.extensions) {
|
|
3233
|
-
if (extension.teardown === void 0) continue;
|
|
3234
|
-
try {
|
|
3235
|
-
await extension.teardown({
|
|
3236
|
-
container: containers.get(extension.id),
|
|
3237
|
-
stage
|
|
3238
|
-
});
|
|
3239
|
-
} catch (error) {
|
|
3240
|
-
throw error instanceof CliError ? error : new CliError(error instanceof Error ? error.message : String(error));
|
|
3241
|
-
}
|
|
3242
|
-
}
|
|
3243
|
-
for (const extension of config.extensions) {
|
|
3244
|
-
if (extension.container === void 0) continue;
|
|
3245
|
-
const instance = containers.get(extension.id);
|
|
3246
|
-
if (instance === void 0) continue;
|
|
3247
|
-
try {
|
|
3248
|
-
await extension.container.remove(instance);
|
|
3249
|
-
} catch (error) {
|
|
3250
|
-
throw error instanceof CliError ? error : new CliError(error instanceof Error ? error.message : String(error));
|
|
3251
|
-
}
|
|
3252
|
-
}
|
|
3253
|
-
}
|
|
3254
|
-
return status;
|
|
3255
|
-
} catch (error) {
|
|
3256
|
-
console.error(`\nGenerated stack file: ${stackPath}`);
|
|
3257
|
-
throw error;
|
|
3258
|
-
}
|
|
3259
|
-
}
|
|
3260
|
-
/**
|
|
3261
|
-
* Run the `prisma-composer` CLI end to end: dispatch `argv`, map errors to exit
|
|
3262
|
-
* codes. Shared by this package's `bin` and the unscoped `prisma-composer` launcher.
|
|
3263
|
-
*/
|
|
3264
|
-
async function cli(argv = process.argv.slice(2)) {
|
|
3265
|
-
try {
|
|
3266
|
-
process.exitCode = await run(argv);
|
|
3267
|
-
} catch (error) {
|
|
3268
|
-
if (error instanceof UsageError) {
|
|
3269
|
-
console.error(error.message);
|
|
3270
|
-
process.exitCode = 1;
|
|
3271
|
-
return;
|
|
3272
|
-
}
|
|
3273
|
-
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
3274
|
-
process.exitCode = 1;
|
|
3275
|
-
}
|
|
3276
|
-
}
|
|
3277
|
-
//#endregion
|
|
3278
|
-
//#region ../../0-framework/3-tooling/cli/dist/bin.mjs
|
|
3279
|
-
cli();
|
|
3280
|
-
//#endregion
|
|
3281
|
-
export {};
|
|
3282
|
-
|
|
3283
|
-
//# sourceMappingURL=bin.mjs.map
|