@gaganref/confect-cli 0.0.0-stage → 10.3.1-fork.2
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/CHANGELOG.md +1824 -0
- package/LICENSE +7 -0
- package/README.md +16 -2
- package/bin/confect.mjs +26 -0
- package/dist/Ansi.mjs +18 -0
- package/dist/Ansi.mjs.map +1 -0
- package/dist/BuildError.mjs +30 -0
- package/dist/BuildError.mjs.map +1 -0
- package/dist/Bundler.mjs +426 -0
- package/dist/Bundler.mjs.map +1 -0
- package/dist/CodeBlockWriter.mjs +55 -0
- package/dist/CodeBlockWriter.mjs.map +1 -0
- package/dist/CodegenError.mjs +194 -0
- package/dist/CodegenError.mjs.map +1 -0
- package/dist/ConfectDirectory.mjs +35 -0
- package/dist/ConfectDirectory.mjs.map +1 -0
- package/dist/ConvexConfig.mjs +198 -0
- package/dist/ConvexConfig.mjs.map +1 -0
- package/dist/ConvexDirectory.mjs +54 -0
- package/dist/ConvexDirectory.mjs.map +1 -0
- package/dist/Declaration.mjs +258 -0
- package/dist/Declaration.mjs.map +1 -0
- package/dist/DirectoryOptions.mjs +42 -0
- package/dist/DirectoryOptions.mjs.map +1 -0
- package/dist/DocName.mjs +25 -0
- package/dist/DocName.mjs.map +1 -0
- package/dist/EntryPoint.mjs +111 -0
- package/dist/EntryPoint.mjs.map +1 -0
- package/dist/Evaluation.mjs +117 -0
- package/dist/Evaluation.mjs.map +1 -0
- package/dist/EvaluationWorker.mjs +46 -0
- package/dist/EvaluationWorker.mjs.map +1 -0
- package/dist/FunctionModule.mjs +137 -0
- package/dist/FunctionModule.mjs.map +1 -0
- package/dist/FunctionPath.mjs +18 -0
- package/dist/FunctionPath.mjs.map +1 -0
- package/dist/FunctionPaths.mjs +45 -0
- package/dist/FunctionPaths.mjs.map +1 -0
- package/dist/GenerationSnapshot.mjs +183 -0
- package/dist/GenerationSnapshot.mjs.map +1 -0
- package/dist/GroupPath.mjs +51 -0
- package/dist/GroupPath.mjs.map +1 -0
- package/dist/GroupPaths.mjs +8 -0
- package/dist/GroupPaths.mjs.map +1 -0
- package/dist/ImplModulePath.mjs +8 -0
- package/dist/ImplModulePath.mjs.map +1 -0
- package/dist/LeafModule.mjs +285 -0
- package/dist/LeafModule.mjs.map +1 -0
- package/dist/ProjectRoot.mjs +38 -0
- package/dist/ProjectRoot.mjs.map +1 -0
- package/dist/RelativeModulePath.mjs +13 -0
- package/dist/RelativeModulePath.mjs.map +1 -0
- package/dist/Remove.mjs +24 -0
- package/dist/Remove.mjs.map +1 -0
- package/dist/RestoreWatcher.mjs +31 -0
- package/dist/RestoreWatcher.mjs.map +1 -0
- package/dist/Rollback.mjs +50 -0
- package/dist/Rollback.mjs.map +1 -0
- package/dist/SnapshotError.mjs +21 -0
- package/dist/SnapshotError.mjs.map +1 -0
- package/dist/SpecAssemblyNode.mjs +91 -0
- package/dist/SpecAssemblyNode.mjs.map +1 -0
- package/dist/SpecModulePath.mjs +10 -0
- package/dist/SpecModulePath.mjs.map +1 -0
- package/dist/SyncState.mjs +122 -0
- package/dist/SyncState.mjs.map +1 -0
- package/dist/TableModule.mjs +97 -0
- package/dist/TableModule.mjs.map +1 -0
- package/dist/TableModulePath.mjs +8 -0
- package/dist/TableModulePath.mjs.map +1 -0
- package/dist/WatchBuild.mjs +190 -0
- package/dist/WatchBuild.mjs.map +1 -0
- package/dist/WatchGraph.mjs +35 -0
- package/dist/WatchGraph.mjs.map +1 -0
- package/dist/WatchTopology.mjs +55 -0
- package/dist/WatchTopology.mjs.map +1 -0
- package/dist/Watcher.mjs +378 -0
- package/dist/Watcher.mjs.map +1 -0
- package/dist/cliApp.mjs +9 -0
- package/dist/cliApp.mjs.map +1 -0
- package/dist/confect/codegen.mjs +548 -0
- package/dist/confect/codegen.mjs.map +1 -0
- package/dist/confect/declaration.mjs +218 -0
- package/dist/confect/declaration.mjs.map +1 -0
- package/dist/confect/dev.mjs +182 -0
- package/dist/confect/dev.mjs.map +1 -0
- package/dist/confect.mjs +9 -0
- package/dist/confect.mjs.map +1 -0
- package/dist/index.mjs +19 -0
- package/dist/index.mjs.map +1 -0
- package/dist/log.mjs +107 -0
- package/dist/log.mjs.map +1 -0
- package/dist/package.mjs +6 -0
- package/dist/package.mjs.map +1 -0
- package/dist/templates.mjs +448 -0
- package/dist/templates.mjs.map +1 -0
- package/dist/utils.mjs +105 -0
- package/dist/utils.mjs.map +1 -0
- package/package.json +51 -3
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { affected, make } from "./WatchGraph.mjs";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Array from "effect/Array";
|
|
4
|
+
import * as Result from "effect/Result";
|
|
5
|
+
import * as HashSet from "effect/HashSet";
|
|
6
|
+
import * as Match from "effect/Match";
|
|
7
|
+
import * as Option from "effect/Option";
|
|
8
|
+
import * as HashMap from "effect/HashMap";
|
|
9
|
+
import * as Brand from "effect/Brand";
|
|
10
|
+
import * as Data from "effect/Data";
|
|
11
|
+
import * as Equal from "effect/Equal";
|
|
12
|
+
import * as Hex from "effect/encoding/Hex";
|
|
13
|
+
//#region src/WatchBuild.ts
|
|
14
|
+
const Failure = Data.taggedEnum();
|
|
15
|
+
const emptyCapture = {
|
|
16
|
+
fingerprints: HashMap.empty(),
|
|
17
|
+
failures: HashMap.empty()
|
|
18
|
+
};
|
|
19
|
+
const Outcome = Data.taggedEnum();
|
|
20
|
+
const Generation = Brand.nominal();
|
|
21
|
+
const Barrier = Brand.nominal();
|
|
22
|
+
const RequestId = Brand.nominal();
|
|
23
|
+
const makeRequestId = () => RequestId({});
|
|
24
|
+
const Request = Data.taggedEnum();
|
|
25
|
+
const initialProgress = {
|
|
26
|
+
generation: Generation(0),
|
|
27
|
+
request: Request.Idle(),
|
|
28
|
+
lastRequest: Option.none()
|
|
29
|
+
};
|
|
30
|
+
const start = (progress) => ({
|
|
31
|
+
...progress,
|
|
32
|
+
generation: Generation(progress.generation + 1)
|
|
33
|
+
});
|
|
34
|
+
const captureBarrier = (progress) => Barrier(progress.generation);
|
|
35
|
+
const request = (progress, id) => ({
|
|
36
|
+
...progress,
|
|
37
|
+
request: Option.isSome(progress.lastRequest) && progress.lastRequest.value === id ? Request.Joined() : Request.Pending(),
|
|
38
|
+
lastRequest: Option.some(id)
|
|
39
|
+
});
|
|
40
|
+
const complete = (progress, completion) => ({
|
|
41
|
+
...progress,
|
|
42
|
+
request: Request.$match(progress.request, {
|
|
43
|
+
Idle: (idle) => idle,
|
|
44
|
+
Pending: () => Request.Completed({ completion }),
|
|
45
|
+
Joined: () => Request.Completed({ completion }),
|
|
46
|
+
Completed: (completed) => completed
|
|
47
|
+
})
|
|
48
|
+
});
|
|
49
|
+
const getCompletion = (progress) => Request.$match(progress.request, {
|
|
50
|
+
Idle: () => Option.none(),
|
|
51
|
+
Pending: () => Option.none(),
|
|
52
|
+
Joined: () => Option.none(),
|
|
53
|
+
Completed: ({ completion }) => Option.some(completion)
|
|
54
|
+
});
|
|
55
|
+
const joinedWithoutCompletion = (progress) => Request.$is("Joined")(progress.request);
|
|
56
|
+
const endRequest = (progress) => ({
|
|
57
|
+
...progress,
|
|
58
|
+
request: Request.Idle()
|
|
59
|
+
});
|
|
60
|
+
const isFresh = (barrier, completion) => completion.generation > barrier;
|
|
61
|
+
const verifyInputs = Effect.fn("WatchBuild.verifyInputs")(function* (snapshot, fs, crypto) {
|
|
62
|
+
const matches = yield* Effect.forEach(snapshot.fingerprints, ([file, expected]) => fs.readFile(file).pipe(Effect.flatMap((contents) => crypto.digest("SHA-256", contents)), Effect.map((digest) => Hex.encode(digest) === expected), Effect.mapError((cause) => Failure.InputCaptureFailed({
|
|
63
|
+
file,
|
|
64
|
+
cause
|
|
65
|
+
}))), { concurrency: 8 });
|
|
66
|
+
return Array.every(matches, (matchesInput) => matchesInput);
|
|
67
|
+
});
|
|
68
|
+
const State = Data.taggedEnum();
|
|
69
|
+
const lastSuccessful = (state) => State.$match(state, {
|
|
70
|
+
Initial: () => Option.none(),
|
|
71
|
+
Succeeded: ({ snapshot }) => Option.some(snapshot),
|
|
72
|
+
Failed: ({ lastSuccessful: snapshot }) => snapshot
|
|
73
|
+
});
|
|
74
|
+
const observe = (previous, outcome) => Outcome.$match(outcome, {
|
|
75
|
+
Succeeded: ({ snapshot, warnings }) => State.Succeeded({
|
|
76
|
+
snapshot,
|
|
77
|
+
warnings
|
|
78
|
+
}),
|
|
79
|
+
Failed: (observation) => State.Failed({
|
|
80
|
+
observation,
|
|
81
|
+
lastSuccessful: lastSuccessful(previous),
|
|
82
|
+
discovered: State.$is("Initial")(previous) ? HashSet.empty() : HashSet.union(State.$is("Failed")(previous) ? previous.discovered : HashSet.empty(), HashSet.difference(HashSet.fromIterable(HashMap.keys(observation.fingerprints)), HashSet.fromIterable(HashMap.keys(fingerprints(previous)))))
|
|
83
|
+
})
|
|
84
|
+
});
|
|
85
|
+
const fingerprints = (state) => State.$match(state, {
|
|
86
|
+
Initial: () => HashMap.empty(),
|
|
87
|
+
Succeeded: ({ snapshot }) => snapshot.fingerprints,
|
|
88
|
+
Failed: ({ observation }) => observation.fingerprints
|
|
89
|
+
});
|
|
90
|
+
const outputHash = (state) => State.$match(state, {
|
|
91
|
+
Initial: () => Option.none(),
|
|
92
|
+
Succeeded: ({ snapshot }) => Option.some(snapshot.outputHash),
|
|
93
|
+
Failed: () => Option.none()
|
|
94
|
+
});
|
|
95
|
+
const Change = Data.taggedEnum();
|
|
96
|
+
const diff = (previous, current, generatedDirectoryPrefix) => {
|
|
97
|
+
const previousFingerprints = fingerprints(previous);
|
|
98
|
+
const currentFingerprints = fingerprints(current);
|
|
99
|
+
const files = HashSet.union(HashSet.fromIterable(HashMap.keys(previousFingerprints)), HashSet.fromIterable(HashMap.keys(currentFingerprints)));
|
|
100
|
+
const changed = Array.fromIterable(files).filter((file) => !Equal.equals(HashMap.get(previousFingerprints, file), HashMap.get(currentFingerprints, file)));
|
|
101
|
+
const sources = changed.filter((file) => !file.startsWith(generatedDirectoryPrefix));
|
|
102
|
+
const recovered = State.$is("Failed")(previous) && State.$is("Succeeded")(current);
|
|
103
|
+
const untrackedOutput = recovered ? Option.match(lastSuccessful(previous), {
|
|
104
|
+
onNone: () => false,
|
|
105
|
+
onSome: (baseline) => Equal.equals(baseline.fingerprints, currentFingerprints) && !Equal.equals(Option.some(baseline.outputHash), outputHash(current))
|
|
106
|
+
}) : changed.length === 0 && !Equal.equals(outputHash(previous), outputHash(current));
|
|
107
|
+
if (State.$is("Initial")(previous) || sources.some((file) => HashMap.has(previousFingerprints, file)) && State.$is("Failed")(current) || untrackedOutput) return Change.Full();
|
|
108
|
+
const graph = Option.match(lastSuccessful(current), {
|
|
109
|
+
onNone: () => HashMap.empty(),
|
|
110
|
+
onSome: (snapshot) => snapshot.graph
|
|
111
|
+
});
|
|
112
|
+
const previousGraph = Option.match(lastSuccessful(previous), {
|
|
113
|
+
onNone: () => HashMap.empty(),
|
|
114
|
+
onSome: (snapshot) => snapshot.graph
|
|
115
|
+
});
|
|
116
|
+
const affectedSources = recovered && State.$is("Failed")(previous) ? Array.fromIterable(HashSet.union(HashSet.fromIterable(sources), HashSet.filter(previous.discovered, (file) => !file.startsWith(generatedDirectoryPrefix)))) : sources;
|
|
117
|
+
const paths = HashSet.union(affected(graph, affectedSources), affected(previousGraph, affectedSources));
|
|
118
|
+
return recovered ? Change.Recovered({ paths }) : Change.Affected({ paths });
|
|
119
|
+
};
|
|
120
|
+
const parse = Effect.fn("WatchBuild.parse")(function* (result, capture, resolve, crypto) {
|
|
121
|
+
const failures = Array.fromIterable(HashMap.values(capture.failures));
|
|
122
|
+
if (Array.isReadonlyArrayNonEmpty(result.errors)) failures.push(Failure.BuildFailed({ errors: result.errors }));
|
|
123
|
+
if (Array.isReadonlyArrayNonEmpty(failures)) return Outcome.Failed({
|
|
124
|
+
fingerprints: capture.fingerprints,
|
|
125
|
+
failures,
|
|
126
|
+
warnings: result.warnings
|
|
127
|
+
});
|
|
128
|
+
if (!result.metafile) return Outcome.Failed({
|
|
129
|
+
fingerprints: capture.fingerprints,
|
|
130
|
+
failures: [Failure.InvalidBuildResult({ missing: "metafile" })],
|
|
131
|
+
warnings: result.warnings
|
|
132
|
+
});
|
|
133
|
+
if (!result.outputFiles || !Array.isReadonlyArrayNonEmpty(result.outputFiles)) return Outcome.Failed({
|
|
134
|
+
fingerprints: capture.fingerprints,
|
|
135
|
+
failures: [Failure.InvalidBuildResult({ missing: "outputFiles" })],
|
|
136
|
+
warnings: result.warnings
|
|
137
|
+
});
|
|
138
|
+
const hashes = yield* Effect.forEach(result.outputFiles, (file) => crypto.digest("SHA-256", file.contents).pipe(Effect.map(Hex.encode), Effect.mapError((cause) => Failure.OutputCaptureFailed({
|
|
139
|
+
file: file.path,
|
|
140
|
+
cause
|
|
141
|
+
})))).pipe(Effect.result);
|
|
142
|
+
if (Result.isFailure(hashes)) return Outcome.Failed({
|
|
143
|
+
fingerprints: capture.fingerprints,
|
|
144
|
+
failures: [hashes.failure],
|
|
145
|
+
warnings: result.warnings
|
|
146
|
+
});
|
|
147
|
+
return Outcome.Succeeded({
|
|
148
|
+
snapshot: {
|
|
149
|
+
fingerprints: capture.fingerprints,
|
|
150
|
+
graph: make(result.metafile, resolve),
|
|
151
|
+
outputHash: hashes.success.join(":")
|
|
152
|
+
},
|
|
153
|
+
warnings: result.warnings
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
const failureMessage = (failure) => Failure.$match(failure, {
|
|
157
|
+
BuildFailed: ({ errors }) => errors.map((error) => error.text).join("\n"),
|
|
158
|
+
InputCaptureFailed: ({ file, cause }) => `Could not fingerprint input ${file}: ${cause.message}`,
|
|
159
|
+
OutputCaptureFailed: ({ file, cause }) => `Could not fingerprint output ${file}: ${cause.message}`,
|
|
160
|
+
InvalidBuildResult: ({ missing }) => `esbuild completed without ${missing}`
|
|
161
|
+
});
|
|
162
|
+
const messages = (outcome) => Outcome.$match(outcome, {
|
|
163
|
+
Succeeded: () => [],
|
|
164
|
+
Failed: ({ failures }) => failures.flatMap((failure) => Match.value(failure).pipe(Match.tag("BuildFailed", ({ errors }) => errors), Match.tag("InputCaptureFailed", "OutputCaptureFailed", "InvalidBuildResult", (captureFailure) => [{
|
|
165
|
+
id: "confect-watch",
|
|
166
|
+
pluginName: "confect",
|
|
167
|
+
text: failureMessage(captureFailure),
|
|
168
|
+
location: null,
|
|
169
|
+
notes: [],
|
|
170
|
+
detail: captureFailure
|
|
171
|
+
}]), Match.exhaustive))
|
|
172
|
+
});
|
|
173
|
+
const diagnostics = (state) => State.$match(state, {
|
|
174
|
+
Initial: () => ({
|
|
175
|
+
errors: [],
|
|
176
|
+
warnings: []
|
|
177
|
+
}),
|
|
178
|
+
Succeeded: ({ warnings }) => ({
|
|
179
|
+
errors: [],
|
|
180
|
+
warnings
|
|
181
|
+
}),
|
|
182
|
+
Failed: ({ observation }) => ({
|
|
183
|
+
errors: messages(observation),
|
|
184
|
+
warnings: observation.warnings
|
|
185
|
+
})
|
|
186
|
+
});
|
|
187
|
+
//#endregion
|
|
188
|
+
export { Change, Failure, Outcome, State, captureBarrier, complete, diagnostics, diff, emptyCapture, endRequest, failureMessage, fingerprints, getCompletion, initialProgress, isFresh, joinedWithoutCompletion, lastSuccessful, makeRequestId, messages, observe, outputHash, parse, request, start, verifyInputs };
|
|
189
|
+
|
|
190
|
+
//# sourceMappingURL=WatchBuild.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WatchBuild.mjs","names":["WatchGraph.affected","WatchGraph.make"],"sources":["../src/WatchBuild.ts"],"sourcesContent":["import * as Array from \"effect/Array\";\nimport * as Brand from \"effect/Brand\";\nimport type * as Crypto from \"effect/Crypto\";\nimport * as Data from \"effect/Data\";\nimport * as Effect from \"effect/Effect\";\nimport type * as FileSystem from \"effect/FileSystem\";\nimport * as Equal from \"effect/Equal\";\nimport * as HashMap from \"effect/HashMap\";\nimport * as HashSet from \"effect/HashSet\";\nimport * as Match from \"effect/Match\";\nimport * as Hex from \"effect/encoding/Hex\";\nimport * as Option from \"effect/Option\";\nimport type { PlatformError } from \"effect/PlatformError\";\nimport * as Result from \"effect/Result\";\nimport type * as esbuild from \"esbuild\";\nimport * as WatchGraph from \"./WatchGraph\";\n\nexport type Failure = Data.TaggedEnum<{\n BuildFailed: {\n readonly errors: Array.NonEmptyReadonlyArray<esbuild.Message>;\n };\n InputCaptureFailed: { readonly file: string; readonly cause: PlatformError };\n OutputCaptureFailed: { readonly file: string; readonly cause: PlatformError };\n InvalidBuildResult: { readonly missing: \"metafile\" | \"outputFiles\" };\n}>;\nexport const Failure = Data.taggedEnum<Failure>();\nexport type Fingerprints = HashMap.HashMap<string, string>;\n\nexport interface Capture {\n readonly fingerprints: Fingerprints;\n readonly failures: HashMap.HashMap<string, Failure>;\n}\nexport const emptyCapture: Capture = {\n fingerprints: HashMap.empty(),\n failures: HashMap.empty(),\n};\n\nexport interface Snapshot {\n readonly fingerprints: Fingerprints;\n readonly graph: WatchGraph.WatchGraph;\n readonly outputHash: string;\n}\n\nexport type Outcome = Data.TaggedEnum<{\n Succeeded: {\n readonly snapshot: Snapshot;\n readonly warnings: ReadonlyArray<esbuild.Message>;\n };\n Failed: {\n readonly fingerprints: Fingerprints;\n readonly failures: Array.NonEmptyReadonlyArray<Failure>;\n readonly warnings: ReadonlyArray<esbuild.Message>;\n };\n}>;\nexport const Outcome = Data.taggedEnum<Outcome>();\n\nexport type Generation = number & Brand.Brand<\"WatchBuild.Generation\">;\nconst Generation = Brand.nominal<Generation>();\n\nexport type Barrier = number & Brand.Brand<\"WatchBuild.Barrier\">;\nconst Barrier = Brand.nominal<Barrier>();\n\nexport type RequestId = object & Brand.Brand<\"WatchBuild.RequestId\">;\nconst RequestId = Brand.nominal<RequestId>();\n\nexport const makeRequestId = (): RequestId => RequestId({});\n\nexport interface Completion {\n readonly generation: Generation;\n readonly outcome: Outcome;\n}\n\ntype Request = Data.TaggedEnum<{\n Idle: {};\n Pending: {};\n Joined: {};\n Completed: { readonly completion: Completion };\n}>;\nconst Request = Data.taggedEnum<Request>();\n\nexport interface Progress {\n readonly generation: Generation;\n readonly request: Request;\n readonly lastRequest: Option.Option<RequestId>;\n}\n\nexport const initialProgress: Progress = {\n generation: Generation(0),\n request: Request.Idle(),\n lastRequest: Option.none(),\n};\n\nexport const start = (progress: Progress): Progress => ({\n ...progress,\n generation: Generation(progress.generation + 1),\n});\n\nexport const captureBarrier = (progress: Progress): Barrier =>\n Barrier(progress.generation);\n\nexport const request = (progress: Progress, id: RequestId): Progress => ({\n ...progress,\n request:\n Option.isSome(progress.lastRequest) && progress.lastRequest.value === id\n ? Request.Joined()\n : Request.Pending(),\n lastRequest: Option.some(id),\n});\n\nexport const complete = (\n progress: Progress,\n completion: Completion,\n): Progress => ({\n ...progress,\n request: Request.$match(progress.request, {\n Idle: (idle) => idle,\n Pending: () => Request.Completed({ completion }),\n Joined: () => Request.Completed({ completion }),\n Completed: (completed) => completed,\n }),\n});\n\nexport const getCompletion = (progress: Progress): Option.Option<Completion> =>\n Request.$match(progress.request, {\n Idle: () => Option.none(),\n Pending: () => Option.none(),\n Joined: () => Option.none(),\n Completed: ({ completion }) => Option.some(completion),\n });\n\nexport const joinedWithoutCompletion = (progress: Progress): boolean =>\n Request.$is(\"Joined\")(progress.request);\n\nexport const endRequest = (progress: Progress): Progress => ({\n ...progress,\n request: Request.Idle(),\n});\n\nexport const isFresh = (barrier: Barrier, completion: Completion): boolean =>\n completion.generation > barrier;\n\nexport const verifyInputs = Effect.fn(\"WatchBuild.verifyInputs\")(function* (\n snapshot: Snapshot,\n fs: FileSystem.FileSystem,\n crypto: Crypto.Crypto,\n) {\n const matches = yield* Effect.forEach(\n snapshot.fingerprints,\n ([file, expected]) =>\n fs.readFile(file).pipe(\n Effect.flatMap((contents) => crypto.digest(\"SHA-256\", contents)),\n Effect.map((digest) => Hex.encode(digest) === expected),\n Effect.mapError((cause) => Failure.InputCaptureFailed({ file, cause })),\n ),\n { concurrency: 8 },\n );\n return Array.every(matches, (matchesInput) => matchesInput);\n});\n\nexport type State = Data.TaggedEnum<{\n Initial: {};\n Succeeded: {\n readonly snapshot: Snapshot;\n readonly warnings: ReadonlyArray<esbuild.Message>;\n };\n Failed: {\n readonly observation: Extract<Outcome, { readonly _tag: \"Failed\" }>;\n readonly lastSuccessful: Option.Option<Snapshot>;\n readonly discovered: HashSet.HashSet<string>;\n };\n}>;\nexport const State = Data.taggedEnum<State>();\n\nexport const lastSuccessful = (state: State): Option.Option<Snapshot> =>\n State.$match(state, {\n Initial: () => Option.none(),\n Succeeded: ({ snapshot }) => Option.some(snapshot),\n Failed: ({ lastSuccessful: snapshot }) => snapshot,\n });\n\nexport const observe = (previous: State, outcome: Outcome): State =>\n Outcome.$match(outcome, {\n Succeeded: ({ snapshot, warnings }) =>\n State.Succeeded({ snapshot, warnings }),\n Failed: (observation) =>\n State.Failed({\n observation,\n lastSuccessful: lastSuccessful(previous),\n discovered: State.$is(\"Initial\")(previous)\n ? HashSet.empty()\n : HashSet.union(\n State.$is(\"Failed\")(previous)\n ? previous.discovered\n : HashSet.empty(),\n HashSet.difference(\n HashSet.fromIterable(HashMap.keys(observation.fingerprints)),\n HashSet.fromIterable(HashMap.keys(fingerprints(previous))),\n ),\n ),\n }),\n });\n\nexport const fingerprints = (state: State): Fingerprints =>\n State.$match(state, {\n Initial: () => HashMap.empty(),\n Succeeded: ({ snapshot }) => snapshot.fingerprints,\n Failed: ({ observation }) => observation.fingerprints,\n });\n\nexport const outputHash = (state: State): Option.Option<string> =>\n State.$match(state, {\n Initial: () => Option.none(),\n Succeeded: ({ snapshot }) => Option.some(snapshot.outputHash),\n Failed: () => Option.none(),\n });\n\nexport type Change = Data.TaggedEnum<{\n Full: {};\n Affected: { readonly paths: HashSet.HashSet<string> };\n Recovered: { readonly paths: HashSet.HashSet<string> };\n}>;\nexport const Change = Data.taggedEnum<Change>();\n\nexport const diff = (\n previous: State,\n current: State,\n generatedDirectoryPrefix: string,\n): Change => {\n const previousFingerprints = fingerprints(previous);\n const currentFingerprints = fingerprints(current);\n const files = HashSet.union(\n HashSet.fromIterable(HashMap.keys(previousFingerprints)),\n HashSet.fromIterable(HashMap.keys(currentFingerprints)),\n );\n const changed = Array.fromIterable(files).filter(\n (file) =>\n !Equal.equals(\n HashMap.get(previousFingerprints, file),\n HashMap.get(currentFingerprints, file),\n ),\n );\n const sources = changed.filter(\n (file) => !file.startsWith(generatedDirectoryPrefix),\n );\n const recovered =\n State.$is(\"Failed\")(previous) && State.$is(\"Succeeded\")(current);\n const untrackedOutput = recovered\n ? Option.match(lastSuccessful(previous), {\n onNone: () => false,\n onSome: (baseline) =>\n Equal.equals(baseline.fingerprints, currentFingerprints) &&\n !Equal.equals(Option.some(baseline.outputHash), outputHash(current)),\n })\n : changed.length === 0 &&\n !Equal.equals(outputHash(previous), outputHash(current));\n if (\n State.$is(\"Initial\")(previous) ||\n (sources.some((file) => HashMap.has(previousFingerprints, file)) &&\n State.$is(\"Failed\")(current)) ||\n untrackedOutput\n )\n return Change.Full();\n\n const graph = Option.match(lastSuccessful(current), {\n onNone: () => HashMap.empty<string, HashSet.HashSet<string>>(),\n onSome: (snapshot) => snapshot.graph,\n });\n const previousGraph = Option.match(lastSuccessful(previous), {\n onNone: () => HashMap.empty<string, HashSet.HashSet<string>>(),\n onSome: (snapshot) => snapshot.graph,\n });\n const affectedSources =\n recovered && State.$is(\"Failed\")(previous)\n ? Array.fromIterable(\n HashSet.union(\n HashSet.fromIterable(sources),\n HashSet.filter(\n previous.discovered,\n (file) => !file.startsWith(generatedDirectoryPrefix),\n ),\n ),\n )\n : sources;\n const paths = HashSet.union(\n WatchGraph.affected(graph, affectedSources),\n WatchGraph.affected(previousGraph, affectedSources),\n );\n return recovered ? Change.Recovered({ paths }) : Change.Affected({ paths });\n};\n\nexport const parse = Effect.fn(\"WatchBuild.parse\")(function* (\n result: esbuild.BuildResult,\n capture: Capture,\n resolve: (file: string) => string,\n crypto: Crypto.Crypto,\n) {\n const failures = Array.fromIterable(HashMap.values(capture.failures));\n if (Array.isReadonlyArrayNonEmpty(result.errors))\n failures.push(Failure.BuildFailed({ errors: result.errors }));\n if (Array.isReadonlyArrayNonEmpty(failures))\n return Outcome.Failed({\n fingerprints: capture.fingerprints,\n failures,\n warnings: result.warnings,\n });\n if (!result.metafile)\n return Outcome.Failed({\n fingerprints: capture.fingerprints,\n failures: [Failure.InvalidBuildResult({ missing: \"metafile\" })],\n warnings: result.warnings,\n });\n if (!result.outputFiles || !Array.isReadonlyArrayNonEmpty(result.outputFiles))\n return Outcome.Failed({\n fingerprints: capture.fingerprints,\n failures: [Failure.InvalidBuildResult({ missing: \"outputFiles\" })],\n warnings: result.warnings,\n });\n const hashes = yield* Effect.forEach(result.outputFiles, (file) =>\n crypto.digest(\"SHA-256\", file.contents).pipe(\n Effect.map(Hex.encode),\n Effect.mapError((cause) =>\n Failure.OutputCaptureFailed({ file: file.path, cause }),\n ),\n ),\n ).pipe(Effect.result);\n if (Result.isFailure(hashes))\n return Outcome.Failed({\n fingerprints: capture.fingerprints,\n failures: [hashes.failure],\n warnings: result.warnings,\n });\n return Outcome.Succeeded({\n snapshot: {\n fingerprints: capture.fingerprints,\n graph: WatchGraph.make(result.metafile, resolve),\n outputHash: hashes.success.join(\":\"),\n },\n warnings: result.warnings,\n });\n});\n\nexport const failureMessage = (failure: Failure): string =>\n Failure.$match(failure, {\n BuildFailed: ({ errors }) => errors.map((error) => error.text).join(\"\\n\"),\n InputCaptureFailed: ({ file, cause }) =>\n `Could not fingerprint input ${file}: ${cause.message}`,\n OutputCaptureFailed: ({ file, cause }) =>\n `Could not fingerprint output ${file}: ${cause.message}`,\n InvalidBuildResult: ({ missing }) => `esbuild completed without ${missing}`,\n });\n\nexport const messages = (outcome: Outcome): ReadonlyArray<esbuild.Message> =>\n Outcome.$match(outcome, {\n Succeeded: () => [],\n Failed: ({ failures }) =>\n failures.flatMap((failure) =>\n Match.value(failure).pipe(\n Match.tag(\"BuildFailed\", ({ errors }) => errors),\n Match.tag(\n \"InputCaptureFailed\",\n \"OutputCaptureFailed\",\n \"InvalidBuildResult\",\n (captureFailure) => [\n {\n id: \"confect-watch\",\n pluginName: \"confect\",\n text: failureMessage(captureFailure),\n location: null,\n notes: [],\n detail: captureFailure,\n },\n ],\n ),\n Match.exhaustive,\n ),\n ),\n });\n\nexport const diagnostics = (\n state: State,\n): {\n readonly errors: ReadonlyArray<esbuild.Message>;\n readonly warnings: ReadonlyArray<esbuild.Message>;\n} =>\n State.$match(state, {\n Initial: () => ({ errors: [], warnings: [] }),\n Succeeded: ({ warnings }) => ({ errors: [], warnings }),\n Failed: ({ observation }) => ({\n errors: messages(observation),\n warnings: observation.warnings,\n }),\n });\n"],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as HashSet from "effect/HashSet";
|
|
2
|
+
import * as Option from "effect/Option";
|
|
3
|
+
import * as HashMap from "effect/HashMap";
|
|
4
|
+
//#region src/WatchGraph.ts
|
|
5
|
+
const make = (metafile, resolve) => {
|
|
6
|
+
let importers = HashMap.empty();
|
|
7
|
+
for (const [file, input] of Object.entries(metafile.inputs)) {
|
|
8
|
+
const importer = resolve(file);
|
|
9
|
+
for (const dependency of input.imports) {
|
|
10
|
+
if (dependency.external) continue;
|
|
11
|
+
const imported = resolve(dependency.path);
|
|
12
|
+
const parents = Option.getOrElse(HashMap.get(importers, imported), () => HashSet.empty());
|
|
13
|
+
importers = HashMap.set(importers, imported, HashSet.add(parents, importer));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return importers;
|
|
17
|
+
};
|
|
18
|
+
const affected = (graph, changed) => {
|
|
19
|
+
let visited = HashSet.fromIterable(changed);
|
|
20
|
+
const pending = [...changed];
|
|
21
|
+
while (pending.length > 0) {
|
|
22
|
+
const file = pending.pop();
|
|
23
|
+
if (file === void 0) break;
|
|
24
|
+
for (const importer of Option.getOrElse(HashMap.get(graph, file), () => HashSet.empty())) {
|
|
25
|
+
if (HashSet.has(visited, importer)) continue;
|
|
26
|
+
visited = HashSet.add(visited, importer);
|
|
27
|
+
pending.push(importer);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return visited;
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
export { affected, make };
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=WatchGraph.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WatchGraph.mjs","names":[],"sources":["../src/WatchGraph.ts"],"sourcesContent":["import type * as esbuild from \"esbuild\";\nimport * as HashMap from \"effect/HashMap\";\nimport * as HashSet from \"effect/HashSet\";\nimport * as Option from \"effect/Option\";\n\nexport type WatchGraph = HashMap.HashMap<string, HashSet.HashSet<string>>;\n\nexport const make = (\n metafile: esbuild.Metafile,\n resolve: (file: string) => string,\n): WatchGraph => {\n let importers = HashMap.empty<string, HashSet.HashSet<string>>();\n for (const [file, input] of Object.entries(metafile.inputs)) {\n const importer = resolve(file);\n for (const dependency of input.imports) {\n if (dependency.external) continue;\n const imported = resolve(dependency.path);\n const parents = Option.getOrElse(HashMap.get(importers, imported), () =>\n HashSet.empty<string>(),\n );\n importers = HashMap.set(\n importers,\n imported,\n HashSet.add(parents, importer),\n );\n }\n }\n return importers;\n};\n\nexport const affected = (\n graph: WatchGraph,\n changed: ReadonlyArray<string>,\n): HashSet.HashSet<string> => {\n let visited = HashSet.fromIterable(changed);\n const pending = [...changed];\n while (pending.length > 0) {\n const file = pending.pop();\n if (file === undefined) break;\n for (const importer of Option.getOrElse(HashMap.get(graph, file), () =>\n HashSet.empty<string>(),\n )) {\n if (HashSet.has(visited, importer)) continue;\n visited = HashSet.add(visited, importer);\n pending.push(importer);\n }\n }\n return visited;\n};\n"],"mappings":";;;;AAOA,MAAa,QACX,UACA,YACe;CACf,IAAI,YAAY,QAAQ,MAAuC;CAC/D,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,SAAS,MAAM,GAAG;EAC3D,MAAM,WAAW,QAAQ,IAAI;EAC7B,KAAK,MAAM,cAAc,MAAM,SAAS;GACtC,IAAI,WAAW,UAAU;GACzB,MAAM,WAAW,QAAQ,WAAW,IAAI;GACxC,MAAM,UAAU,OAAO,UAAU,QAAQ,IAAI,WAAW,QAAQ,SAC9D,QAAQ,MAAc,CACxB;GACA,YAAY,QAAQ,IAClB,WACA,UACA,QAAQ,IAAI,SAAS,QAAQ,CAC/B;EACF;CACF;CACA,OAAO;AACT;AAEA,MAAa,YACX,OACA,YAC4B;CAC5B,IAAI,UAAU,QAAQ,aAAa,OAAO;CAC1C,MAAM,UAAU,CAAC,GAAG,OAAO;CAC3B,OAAO,QAAQ,SAAS,GAAG;EACzB,MAAM,OAAO,QAAQ,IAAI;EACzB,IAAI,SAAS,KAAA,GAAW;EACxB,KAAK,MAAM,YAAY,OAAO,UAAU,QAAQ,IAAI,OAAO,IAAI,SAC7D,QAAQ,MAAc,CACxB,GAAG;GACD,IAAI,QAAQ,IAAI,SAAS,QAAQ,GAAG;GACpC,UAAU,QAAQ,IAAI,SAAS,QAAQ;GACvC,QAAQ,KAAK,QAAQ;EACvB;CACF;CACA,OAAO;AACT"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { add, empty, full } from "./SyncState.mjs";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Option from "effect/Option";
|
|
4
|
+
import * as Ref from "effect/Ref";
|
|
5
|
+
import * as Data from "effect/Data";
|
|
6
|
+
import * as Deferred from "effect/Deferred";
|
|
7
|
+
import * as Semaphore from "effect/Semaphore";
|
|
8
|
+
//#region src/WatchTopology.ts
|
|
9
|
+
const State = Data.taggedEnum();
|
|
10
|
+
const Decision = Data.taggedEnum();
|
|
11
|
+
const make = Effect.fn("WatchTopology.make")(function* (pending) {
|
|
12
|
+
const state = yield* Ref.make(State.Reconciling({ revision: 0 }));
|
|
13
|
+
const changed = yield* Ref.make(yield* Deferred.make());
|
|
14
|
+
const lock = yield* Semaphore.make(1);
|
|
15
|
+
const notify = Effect.gen(function* () {
|
|
16
|
+
const previous = yield* Ref.getAndSet(changed, yield* Deferred.make());
|
|
17
|
+
yield* Deferred.succeed(previous, void 0);
|
|
18
|
+
});
|
|
19
|
+
const invalidate = Effect.fn("WatchTopology.invalidate")(function* (update) {
|
|
20
|
+
yield* Ref.update(state, ({ revision }) => State.Reconciling({ revision: revision + 1 }));
|
|
21
|
+
yield* Ref.update(pending, update);
|
|
22
|
+
yield* notify;
|
|
23
|
+
}, (effect) => lock.withPermit(effect));
|
|
24
|
+
return {
|
|
25
|
+
revision: Ref.get(state).pipe(Effect.map((current) => current.revision)),
|
|
26
|
+
invalidate: invalidate(full),
|
|
27
|
+
invalidateEntry: (entry) => invalidate((current) => add(current, [entry])),
|
|
28
|
+
reconcile: Effect.fn("WatchTopology.reconcile")(function* (revision, initial) {
|
|
29
|
+
if ((yield* Ref.get(state)).revision !== revision) return;
|
|
30
|
+
yield* Ref.set(state, State.Observing({
|
|
31
|
+
revision,
|
|
32
|
+
initial
|
|
33
|
+
}));
|
|
34
|
+
yield* notify;
|
|
35
|
+
}, (effect) => lock.withPermit(effect)),
|
|
36
|
+
takePending: lock.withPermit(Effect.gen(function* () {
|
|
37
|
+
const current = yield* Ref.get(state);
|
|
38
|
+
const notification = yield* Ref.get(changed);
|
|
39
|
+
return yield* State.$match(current, {
|
|
40
|
+
Reconciling: () => Effect.succeed(Decision.Wait({ ready: Deferred.await(notification) })),
|
|
41
|
+
Observing: ({ initial }) => Effect.gen(function* () {
|
|
42
|
+
if (yield* Deferred.isDone(initial)) return Decision.Take({ pending: yield* Ref.getAndSet(pending, empty) });
|
|
43
|
+
return Decision.Wait({ ready: Effect.raceFirst(Deferred.await(notification), Deferred.await(initial)) });
|
|
44
|
+
})
|
|
45
|
+
});
|
|
46
|
+
})).pipe(Effect.flatMap(Decision.$match({
|
|
47
|
+
Take: ({ pending: taken }) => Effect.succeedSome(taken),
|
|
48
|
+
Wait: ({ ready }) => ready.pipe(Effect.as(Option.none()))
|
|
49
|
+
})), Effect.repeat({ until: Option.isSome }), Effect.map((result) => result.value))
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
//#endregion
|
|
53
|
+
export { make };
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=WatchTopology.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WatchTopology.mjs","names":["SyncState.full","SyncState.add","SyncState.empty"],"sources":["../src/WatchTopology.ts"],"sourcesContent":["import * as Data from \"effect/Data\";\nimport * as Deferred from \"effect/Deferred\";\nimport * as Effect from \"effect/Effect\";\nimport * as Option from \"effect/Option\";\nimport * as Ref from \"effect/Ref\";\nimport * as Semaphore from \"effect/Semaphore\";\nimport type { Kind } from \"./EntryPoint\";\nimport * as SyncState from \"./SyncState\";\n\ntype State = Data.TaggedEnum<{\n Reconciling: { readonly revision: number };\n Observing: {\n readonly revision: number;\n readonly initial: Deferred.Deferred<void>;\n };\n}>;\nconst State = Data.taggedEnum<State>();\n\ntype Decision = Data.TaggedEnum<{\n Take: { readonly pending: SyncState.Pending };\n Wait: { readonly ready: Effect.Effect<void> };\n}>;\nconst Decision = Data.taggedEnum<Decision>();\n\nexport const make = Effect.fn(\"WatchTopology.make\")(function* (\n pending: Ref.Ref<SyncState.Pending>,\n) {\n const state = yield* Ref.make<State>(State.Reconciling({ revision: 0 }));\n const changed = yield* Ref.make(yield* Deferred.make<void>());\n const lock = yield* Semaphore.make(1);\n const notify = Effect.gen(function* () {\n const previous = yield* Ref.getAndSet(\n changed,\n yield* Deferred.make<void>(),\n );\n yield* Deferred.succeed(previous, undefined);\n });\n\n const invalidate = Effect.fn(\"WatchTopology.invalidate\")(\n function* (update: (pending: SyncState.Pending) => SyncState.Pending) {\n yield* Ref.update(state, ({ revision }) =>\n State.Reconciling({ revision: revision + 1 }),\n );\n yield* Ref.update(pending, update);\n yield* notify;\n },\n (effect) => lock.withPermit(effect),\n );\n\n return {\n revision: Ref.get(state).pipe(Effect.map((current) => current.revision)),\n invalidate: invalidate(SyncState.full),\n invalidateEntry: (entry: Kind) =>\n invalidate((current) => SyncState.add(current, [entry])),\n reconcile: Effect.fn(\"WatchTopology.reconcile\")(\n function* (revision: number, initial: Deferred.Deferred<void>) {\n const current = yield* Ref.get(state);\n if (current.revision !== revision) return;\n yield* Ref.set(state, State.Observing({ revision, initial }));\n yield* notify;\n },\n (effect) => lock.withPermit(effect),\n ),\n takePending: lock\n .withPermit(\n Effect.gen(function* () {\n const current = yield* Ref.get(state);\n const notification = yield* Ref.get(changed);\n return yield* State.$match(current, {\n Reconciling: () =>\n Effect.succeed(\n Decision.Wait({ ready: Deferred.await(notification) }),\n ),\n Observing: ({ initial }) =>\n Effect.gen(function* () {\n if (yield* Deferred.isDone(initial))\n return Decision.Take({\n pending: yield* Ref.getAndSet(pending, SyncState.empty),\n });\n return Decision.Wait({\n ready: Effect.raceFirst(\n Deferred.await(notification),\n Deferred.await(initial),\n ),\n });\n }),\n });\n }),\n )\n .pipe(\n Effect.flatMap(\n Decision.$match({\n Take: ({ pending: taken }) => Effect.succeedSome(taken),\n Wait: ({ ready }) =>\n ready.pipe(Effect.as(Option.none<SyncState.Pending>())),\n }),\n ),\n Effect.repeat({ until: Option.isSome<SyncState.Pending> }),\n Effect.map((result) => result.value),\n ),\n };\n});\n\nexport type WatchTopology = Effect.Success<ReturnType<typeof make>>;\n"],"mappings":";;;;;;;;AAgBA,MAAM,QAAQ,KAAK,WAAkB;AAMrC,MAAM,WAAW,KAAK,WAAqB;AAE3C,MAAa,OAAO,OAAO,GAAG,oBAAoB,CAAC,CAAC,WAClD,SACA;CACA,MAAM,QAAQ,OAAO,IAAI,KAAY,MAAM,YAAY,EAAE,UAAU,EAAE,CAAC,CAAC;CACvE,MAAM,UAAU,OAAO,IAAI,KAAK,OAAO,SAAS,KAAW,CAAC;CAC5D,MAAM,OAAO,OAAO,UAAU,KAAK,CAAC;CACpC,MAAM,SAAS,OAAO,IAAI,aAAa;EACrC,MAAM,WAAW,OAAO,IAAI,UAC1B,SACA,OAAO,SAAS,KAAW,CAC7B;EACA,OAAO,SAAS,QAAQ,UAAU,KAAA,CAAS;CAC7C,CAAC;CAED,MAAM,aAAa,OAAO,GAAG,0BAA0B,CAAC,CACtD,WAAW,QAA2D;EACpE,OAAO,IAAI,OAAO,QAAQ,EAAE,eAC1B,MAAM,YAAY,EAAE,UAAU,WAAW,EAAE,CAAC,CAC9C;EACA,OAAO,IAAI,OAAO,SAAS,MAAM;EACjC,OAAO;CACT,IACC,WAAW,KAAK,WAAW,MAAM,CACpC;CAEA,OAAO;EACL,UAAU,IAAI,IAAI,KAAK,CAAC,CAAC,KAAK,OAAO,KAAK,YAAY,QAAQ,QAAQ,CAAC;EACvE,YAAY,WAAWA,IAAc;EACrC,kBAAkB,UAChB,YAAY,YAAYC,IAAc,SAAS,CAAC,KAAK,CAAC,CAAC;EACzD,WAAW,OAAO,GAAG,yBAAyB,CAAC,CAC7C,WAAW,UAAkB,SAAkC;GAE7D,KAAI,OADmB,IAAI,IAAI,KAAK,EAAA,CACxB,aAAa,UAAU;GACnC,OAAO,IAAI,IAAI,OAAO,MAAM,UAAU;IAAE;IAAU;GAAQ,CAAC,CAAC;GAC5D,OAAO;EACT,IACC,WAAW,KAAK,WAAW,MAAM,CACpC;EACA,aAAa,KACV,WACC,OAAO,IAAI,aAAa;GACtB,MAAM,UAAU,OAAO,IAAI,IAAI,KAAK;GACpC,MAAM,eAAe,OAAO,IAAI,IAAI,OAAO;GAC3C,OAAO,OAAO,MAAM,OAAO,SAAS;IAClC,mBACE,OAAO,QACL,SAAS,KAAK,EAAE,OAAO,SAAS,MAAM,YAAY,EAAE,CAAC,CACvD;IACF,YAAY,EAAE,cACZ,OAAO,IAAI,aAAa;KACtB,IAAI,OAAO,SAAS,OAAO,OAAO,GAChC,OAAO,SAAS,KAAK,EACnB,SAAS,OAAO,IAAI,UAAU,SAASC,KAAe,EACxD,CAAC;KACH,OAAO,SAAS,KAAK,EACnB,OAAO,OAAO,UACZ,SAAS,MAAM,YAAY,GAC3B,SAAS,MAAM,OAAO,CACxB,EACF,CAAC;IACH,CAAC;GACL,CAAC;EACH,CAAC,CACH,CAAC,CACA,KACC,OAAO,QACL,SAAS,OAAO;GACd,OAAO,EAAE,SAAS,YAAY,OAAO,YAAY,KAAK;GACtD,OAAO,EAAE,YACP,MAAM,KAAK,OAAO,GAAG,OAAO,KAAwB,CAAC,CAAC;EAC1D,CAAC,CACH,GACA,OAAO,OAAO,EAAE,OAAO,OAAO,OAA0B,CAAC,GACzD,OAAO,KAAK,WAAW,OAAO,KAAK,CACrC;CACJ;AACF,CAAC"}
|