litmus-cli 1.4.7 → 1.4.9
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/commands/connect.d.ts +16 -0
- package/dist/commands/connect.d.ts.map +1 -1
- package/dist/commands/connect.js +75 -11
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +113 -4
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/push.js +2 -2
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/submit.d.ts.map +1 -1
- package/dist/commands/submit.js +15 -0
- package/dist/commands/submit.js.map +1 -1
- package/dist/lib/api.d.ts +91 -0
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +146 -2
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/detect-project.d.ts +46 -1
- package/dist/lib/detect-project.d.ts.map +1 -1
- package/dist/lib/detect-project.js +40 -19
- package/dist/lib/detect-project.js.map +1 -1
- package/dist/lib/extract.d.ts.map +1 -1
- package/dist/lib/extract.js +28 -1
- package/dist/lib/extract.js.map +1 -1
- package/dist/lib/python-env.d.ts +157 -0
- package/dist/lib/python-env.d.ts.map +1 -0
- package/dist/lib/python-env.js +477 -0
- package/dist/lib/python-env.js.map +1 -0
- package/package.json +1 -1
|
@@ -101,7 +101,7 @@ export function resolvePythonInterpreter() {
|
|
|
101
101
|
* which is the case a warning most needs to catch. `shell: true` on Windows so
|
|
102
102
|
* `.cmd`/`.ps1` shims resolve, matching the Python probe above.
|
|
103
103
|
*/
|
|
104
|
-
function isOnPath(bin) {
|
|
104
|
+
export function isOnPath(bin) {
|
|
105
105
|
try {
|
|
106
106
|
return (spawnSync(bin, ["--version"], {
|
|
107
107
|
encoding: "utf8",
|
|
@@ -116,7 +116,8 @@ function isOnPath(bin) {
|
|
|
116
116
|
/**
|
|
117
117
|
* Detect project type and suggest an install command.
|
|
118
118
|
*/
|
|
119
|
-
export function detectProject(dir) {
|
|
119
|
+
export function detectProject(dir, deps = {}) {
|
|
120
|
+
const onPath = deps.isOnPath ?? isOnPath;
|
|
120
121
|
if (existsSync(path.join(dir, "package.json"))) {
|
|
121
122
|
const hasYarnLock = existsSync(path.join(dir, "yarn.lock"));
|
|
122
123
|
const hasPnpmLock = existsSync(path.join(dir, "pnpm-lock.yaml"));
|
|
@@ -133,24 +134,13 @@ export function detectProject(dir) {
|
|
|
133
134
|
: hasBunLock
|
|
134
135
|
? "bun install"
|
|
135
136
|
: "npm install";
|
|
136
|
-
// Same disclosure contract as the Python skew below: a missing toolchain is
|
|
137
|
-
// ours to state here, not theirs to infer. Without this, `init`'s install
|
|
138
|
-
// failure prints "run `bun install` manually", which is the command that just
|
|
139
|
-
// failed — and the candidate is left guessing that a runtime they may never
|
|
140
|
-
// have heard of is the missing piece.
|
|
141
|
-
//
|
|
142
137
|
// Gated on the SELECTED command, not on `hasBunLock`. A repo carrying both a
|
|
143
|
-
// bun lockfile and a pnpm/yarn one resolves to pnpm/yarn above, and
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
"your PATH. Install it from https://bun.sh, then re-run `bun install` in " +
|
|
150
|
-
"this folder. npm can install the dependencies but cannot run the " +
|
|
151
|
-
"scaffold's server — it uses Bun-only APIs."
|
|
152
|
-
: undefined;
|
|
153
|
-
return { type: "node", installCommand, toolchainWarning };
|
|
138
|
+
// bun lockfile and a pnpm/yarn one resolves to pnpm/yarn above, and reporting
|
|
139
|
+
// bun as missing there would send the candidate to install a second,
|
|
140
|
+
// competing dependency tree over the one we just built. Also means
|
|
141
|
+
// npm/yarn/pnpm repos never pay for the PATH probe.
|
|
142
|
+
const missingRuntime = installCommand === "bun install" && !onPath("bun") ? "bun" : undefined;
|
|
143
|
+
return { type: "node", installCommand, missingRuntime };
|
|
154
144
|
}
|
|
155
145
|
if (existsSync(path.join(dir, "requirements.txt")) ||
|
|
156
146
|
existsSync(path.join(dir, "pyproject.toml")) ||
|
|
@@ -203,4 +193,35 @@ export function detectProject(dir) {
|
|
|
203
193
|
}
|
|
204
194
|
return { type: "unknown", installCommand: null };
|
|
205
195
|
}
|
|
196
|
+
/** How each supported runtime is obtained. npm ships per-platform bun binaries,
|
|
197
|
+
* and npm is guaranteed present because this CLI is an npm package. */
|
|
198
|
+
export const RUNTIME_INSTALL = {
|
|
199
|
+
bun: "npm install -g bun",
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* Install a runtime the project needs and the machine lacks.
|
|
203
|
+
*
|
|
204
|
+
* Separated from `init`'s reporting so all three outcomes are assertable: the
|
|
205
|
+
* install failing, and the install "succeeding" while leaving nothing on PATH.
|
|
206
|
+
* That second case is the one worth a unit test — a global install can land
|
|
207
|
+
* somewhere this process cannot reach, and treating it as success means the
|
|
208
|
+
* dependency install below dies on a command-not-found we already knew about.
|
|
209
|
+
*/
|
|
210
|
+
export function installRuntime(runtime, deps = {}) {
|
|
211
|
+
const exec = deps.exec ?? ((command) => {
|
|
212
|
+
// spawnSync reports a failure in `status` rather than throwing, so surface
|
|
213
|
+
// it as one — the catch below is what turns it into "unavailable".
|
|
214
|
+
const r = spawnSync(command, { shell: true, stdio: "pipe" });
|
|
215
|
+
if (r.status !== 0)
|
|
216
|
+
throw new Error(`\`${command}\` exited ${r.status}`);
|
|
217
|
+
});
|
|
218
|
+
const onPath = deps.isOnPath ?? isOnPath;
|
|
219
|
+
try {
|
|
220
|
+
exec(RUNTIME_INSTALL[runtime]);
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return "unavailable";
|
|
224
|
+
}
|
|
225
|
+
return onPath(runtime) ? "installed" : "unavailable";
|
|
226
|
+
}
|
|
206
227
|
//# sourceMappingURL=detect-project.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect-project.js","sourceRoot":"","sources":["../../src/lib/detect-project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AACzC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,IAAI,CAAA;AAC5C,OAAO,IAAI,MAAM,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"detect-project.js","sourceRoot":"","sources":["../../src/lib/detect-project.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/lib/extract.ts"],"names":[],"mappings":"AAYA;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/lib/extract.ts"],"names":[],"mappings":"AAYA;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAmEf"}
|
package/dist/lib/extract.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createWriteStream } from "fs";
|
|
2
|
-
import { mkdir } from "fs/promises";
|
|
2
|
+
import { chmod, mkdir } from "fs/promises";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { pipeline } from "stream/promises";
|
|
5
5
|
import unzipper from "unzipper";
|
|
@@ -42,6 +42,33 @@ export async function extractZip(zipPath, targetDir) {
|
|
|
42
42
|
}
|
|
43
43
|
await mkdir(path.dirname(resolvedPath), { recursive: true });
|
|
44
44
|
await pipeline(entry.stream(), createWriteStream(resolvedPath));
|
|
45
|
+
// RESTORE THE EXECUTABLE BIT, which `createWriteStream` drops.
|
|
46
|
+
//
|
|
47
|
+
// It writes with the default mode and ignores whatever the archive says, so
|
|
48
|
+
// a package whose brief instructs
|
|
49
|
+
//
|
|
50
|
+
// ./bin/train --config config/run.json --out runs/mine
|
|
51
|
+
//
|
|
52
|
+
// extracted to a non-executable file and the candidate hit "Permission
|
|
53
|
+
// denied" on the first thing they were told to do. Manual `unzip` honours
|
|
54
|
+
// the mode, so this only ever broke for people using the CLI, which is
|
|
55
|
+
// everyone.
|
|
56
|
+
//
|
|
57
|
+
// GRANTED, NOT COPIED. We add +x when the archive marks the entry
|
|
58
|
+
// executable and clamp to 0755; we never apply the archive's mode verbatim.
|
|
59
|
+
// An archive can carry setuid, setgid and sticky bits, and nothing arriving
|
|
60
|
+
// over the wire should be able to set those on a candidate's machine.
|
|
61
|
+
// Anything not marked executable keeps the default write mode.
|
|
62
|
+
//
|
|
63
|
+
// The unix mode lives in the high half of the ZIP's external attributes.
|
|
64
|
+
// Absent on archives written by tools that store no mode, which reads as 0
|
|
65
|
+
// and correctly grants nothing.
|
|
66
|
+
const mode = (entry.externalFileAttributes ?? 0) >>> 16;
|
|
67
|
+
if (mode & 0o111) {
|
|
68
|
+
// Best-effort: on Windows chmod only toggles the read-only flag, and a
|
|
69
|
+
// filesystem that refuses it should not fail the whole extraction.
|
|
70
|
+
await chmod(resolvedPath, 0o755).catch(() => { });
|
|
71
|
+
}
|
|
45
72
|
}
|
|
46
73
|
}
|
|
47
74
|
//# sourceMappingURL=extract.js.map
|
package/dist/lib/extract.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extract.js","sourceRoot":"","sources":["../../src/lib/extract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,IAAI,CAAA;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"extract.js","sourceRoot":"","sources":["../../src/lib/extract.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bind a Python assessment's notebook to the environment `init` just built.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS AT ALL (ENG-1824). `litmus init` has always created `venv/`
|
|
5
|
+
* and installed into it, and ENG-1417 made it prefer the grading interpreter
|
|
6
|
+
* and warn when it could not get one. None of that reaches a notebook. For an
|
|
7
|
+
* `.ipynb` deliverable the graded artifact is the SAVED CELL OUTPUT — the
|
|
8
|
+
* extractor parses the notebook as JSON and never re-executes it — so the
|
|
9
|
+
* interpreter that decides the result is the one Jupyter picked, not the one
|
|
10
|
+
* in `venv/`. A perfect venv the kernel never uses buys exactly zero, and the
|
|
11
|
+
* shipped notebooks carry the generic kernelspec `{"name": "python3"}`, which
|
|
12
|
+
* resolves to whatever `python3` kernel the machine happens to have. A
|
|
13
|
+
* candidate with their own JupyterLab and their own pandas therefore runs
|
|
14
|
+
* every cell on a foreign interpreter, and nothing anywhere notices.
|
|
15
|
+
*
|
|
16
|
+
* EVERYTHING HERE IS BEST-EFFORT AND NEVER FATAL. It runs on the path that
|
|
17
|
+
* decides whether a candidate can start working, against tooling we do not
|
|
18
|
+
* control, on three platforms. A kernel we failed to register is a candidate
|
|
19
|
+
* who picks one manually — today's behaviour. An exception thrown here would
|
|
20
|
+
* be a candidate who cannot start at all, which is far worse than the skew it
|
|
21
|
+
* was trying to prevent. Every entry point returns a result and swallows.
|
|
22
|
+
*/
|
|
23
|
+
/** Where the venv's interpreter lives, per platform. */
|
|
24
|
+
export declare function venvPython(dir: string): string;
|
|
25
|
+
/** The `jupyter` the candidate should be told to run: the venv's own. */
|
|
26
|
+
export declare function venvJupyterCommand(): string;
|
|
27
|
+
/**
|
|
28
|
+
* Is `jupyter lab` actually runnable in this venv?
|
|
29
|
+
*
|
|
30
|
+
* `ipykernel` alone is not enough to answer that, and `ipykernel` is what the
|
|
31
|
+
* kernel registration proves. It brings in `jupyter-core`, so `venv/bin/jupyter`
|
|
32
|
+
* EXISTS while the `lab` subcommand — a separate `jupyterlab` distribution —
|
|
33
|
+
* does not. Telling a candidate to run the first and most prominent command we
|
|
34
|
+
* give them, and having it answer `'lab' is not a Jupyter command`, is a worse
|
|
35
|
+
* first thirty seconds than saying nothing.
|
|
36
|
+
*/
|
|
37
|
+
export declare function hasJupyterLab(dir: string): boolean;
|
|
38
|
+
export declare function uvVersion(): string | null;
|
|
39
|
+
/**
|
|
40
|
+
* Build `venv/` with uv, pinned to the grading interpreter.
|
|
41
|
+
*
|
|
42
|
+
* The directory name is passed EXPLICITLY. uv's default is `.venv`, and every
|
|
43
|
+
* line of instruction we ship — the package README's `source venv/bin/activate`
|
|
44
|
+
* included — names `venv`. Taking uv's default here would leave the candidate
|
|
45
|
+
* reading true-looking instructions about a directory that does not exist.
|
|
46
|
+
*/
|
|
47
|
+
export declare function buildVenvWithUv(dir: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* The distributions `requirements.txt` declares, as installable names.
|
|
50
|
+
*
|
|
51
|
+
* Deliberately conservative: anything that is not a plain pinned-or-floored
|
|
52
|
+
* requirement (a flag, an `-r` include, a URL, a path) is dropped rather than
|
|
53
|
+
* guessed at, because the only consumer is a probe whose false alarms would be
|
|
54
|
+
* worse than its silence.
|
|
55
|
+
*/
|
|
56
|
+
export declare function declaredRequirements(dir: string): string[];
|
|
57
|
+
export interface EnvProbe {
|
|
58
|
+
version: string | null;
|
|
59
|
+
/** Declared distributions that are not installed in the venv. */
|
|
60
|
+
missing: string[];
|
|
61
|
+
/** Top-level modules that are installed but fail to import (broken wheel/ABI). */
|
|
62
|
+
broken: string[];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Ask the venv's own interpreter what it has.
|
|
66
|
+
*
|
|
67
|
+
* PRESENCE IS CHECKED THROUGH `importlib.metadata`, NOT BY IMPORTING THE
|
|
68
|
+
* REQUIREMENT NAME. Distribution names and module names disagree often enough
|
|
69
|
+
* (`beautifulsoup4`/`bs4`, `scikit-learn`/`sklearn`, `pillow`/`PIL`) that a
|
|
70
|
+
* hand-maintained alias table would eventually tell a candidate their working
|
|
71
|
+
* environment is broken. The metadata lookup is exact. The import check is
|
|
72
|
+
* then driven off the metadata's own top-level modules, so it stays exact too
|
|
73
|
+
* while still catching the case presence cannot — a wheel that installs and
|
|
74
|
+
* then fails to load.
|
|
75
|
+
*/
|
|
76
|
+
export declare function probeEnvironment(dir: string): EnvProbe;
|
|
77
|
+
/**
|
|
78
|
+
* A kernelspec name identifying ONE CHECKOUT, not one assessment.
|
|
79
|
+
*
|
|
80
|
+
* Jupyter lowercases kernelspec names on install and matches them literally
|
|
81
|
+
* afterwards, so the name is normalised HERE rather than being compared
|
|
82
|
+
* case-insensitively later — otherwise the notebook we rewrite points at
|
|
83
|
+
* `litmus-Foo` while the registered kernel is `litmus-foo`, and the candidate
|
|
84
|
+
* gets a picker prompt about a missing kernel, which is worse than the generic
|
|
85
|
+
* name we started with.
|
|
86
|
+
*
|
|
87
|
+
* THE PATH FINGERPRINT IS WHAT KEEPS TWO CHECKOUTS APART, and the name alone
|
|
88
|
+
* cannot: kernelspecs are user-scope and `ipykernel install` OVERWRITES a spec
|
|
89
|
+
* of the same name. Two folders that normalise alike — a practice run beside a
|
|
90
|
+
* real attempt, the same assessment re-initialised under a different parent,
|
|
91
|
+
* or two companies who both called theirs "Take Home" — would otherwise share
|
|
92
|
+
* one spec. The second init would silently redirect the first notebook at the
|
|
93
|
+
* second checkout's interpreter, and submitting either would delete the spec
|
|
94
|
+
* the other one is bound to. The fingerprint is not shown to anyone: the
|
|
95
|
+
* picker displays `display_name`, and the notebook is bound by name so the
|
|
96
|
+
* candidate never has to read either.
|
|
97
|
+
*/
|
|
98
|
+
export declare function kernelNameFor(folderName: string, projectRoot: string): string;
|
|
99
|
+
/**
|
|
100
|
+
* Register the venv's interpreter as a Jupyter kernel.
|
|
101
|
+
*
|
|
102
|
+
* `--user`, NOT `--sys-prefix`. A sys-prefix kernel is visible only to a
|
|
103
|
+
* JupyterLab launched from inside that venv, which excludes exactly the
|
|
104
|
+
* candidate this whole change is for: the one who already has a global
|
|
105
|
+
* JupyterLab, opens the notebook with it, and cannot even SEE the correct
|
|
106
|
+
* kernel. A user-scope kernelspec is discoverable by any Jupyter on the
|
|
107
|
+
* machine, which is the property that makes the notebook rewrite below mean
|
|
108
|
+
* something.
|
|
109
|
+
*
|
|
110
|
+
* Requires `ipykernel` in the venv. Assessments that ship jupyterlab get it
|
|
111
|
+
* transitively; ones that do not simply return false and the flow degrades to
|
|
112
|
+
* today's behaviour.
|
|
113
|
+
*/
|
|
114
|
+
export declare function registerKernel(dir: string, name: string, displayName: string): boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Remove the kernel we registered, before the assessment folder goes away.
|
|
117
|
+
*
|
|
118
|
+
* A user-scope kernelspec OUTLIVES THE FOLDER, and `litmus submit` deletes the
|
|
119
|
+
* folder. Left behind, it is a permanent entry in the candidate's own Jupyter
|
|
120
|
+
* pointing at an interpreter that no longer exists — our litter on their
|
|
121
|
+
* machine, and a broken kernel in every notebook they open afterwards. We made
|
|
122
|
+
* the global change, so we undo it.
|
|
123
|
+
*
|
|
124
|
+
* Called while the venv still exists, because that is what provides
|
|
125
|
+
* `jupyter_client` — asking it to remove the spec is how this stays correct on
|
|
126
|
+
* all three platforms without hardcoding three kernel directories.
|
|
127
|
+
*/
|
|
128
|
+
export declare function unregisterKernel(dir: string, name: string): boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Rewrite a notebook's kernelspec name/display_name, changing nothing else.
|
|
131
|
+
*
|
|
132
|
+
* A SURGICAL STRING EDIT, NOT A PARSE-AND-SERIALISE. The obvious
|
|
133
|
+
* implementation — `JSON.parse`, mutate, `JSON.stringify` — reformats the
|
|
134
|
+
* whole file: a 900-line notebook collapses to one line with the default
|
|
135
|
+
* serialiser and still differs byte-for-byte under any indent setting. That
|
|
136
|
+
* matters here more than it usually would, because `init` git-commits the
|
|
137
|
+
* extracted package as the candidate's baseline and the notebook diff against
|
|
138
|
+
* that baseline is grading evidence. Reflowing the deliverable before the
|
|
139
|
+
* candidate has opened it turns every later diff into one line of churn on the
|
|
140
|
+
* one file that carries their work.
|
|
141
|
+
*
|
|
142
|
+
* So: find the `kernelspec` object, replace the two string values inside it,
|
|
143
|
+
* leave every other byte — including `language`, the trailing whitespace and
|
|
144
|
+
* the file's own indentation — exactly as shipped. Returns null when there is
|
|
145
|
+
* nothing to edit, which is a report, not a failure: a notebook without a
|
|
146
|
+
* kernelspec is one we should leave alone rather than restructure.
|
|
147
|
+
*/
|
|
148
|
+
export declare function rewriteKernelspec(source: string, name: string, displayName: string): string | null;
|
|
149
|
+
/**
|
|
150
|
+
* Point every notebook the package shipped at the registered kernel.
|
|
151
|
+
*
|
|
152
|
+
* Scoped to notebooks that exist at init time, i.e. ones we shipped. The
|
|
153
|
+
* candidate's own later notebooks are theirs; the deliverable is the one that
|
|
154
|
+
* has to execute on the environment we built.
|
|
155
|
+
*/
|
|
156
|
+
export declare function bindNotebooks(dir: string, name: string, displayName: string): string[];
|
|
157
|
+
//# sourceMappingURL=python-env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"python-env.d.ts","sourceRoot":"","sources":["../../src/lib/python-env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAUH,wDAAwD;AACxD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI9C;AAED,yEAAyE;AACzE,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAKlD;AAwDD,wBAAgB,SAAS,IAAI,MAAM,GAAG,IAAI,CAOzC;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CA0CpD;AAID;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAmB1D;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,iEAAiE;IACjE,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,kFAAkF;IAClF,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CAgDtD;AAID;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAW7E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAOtF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAUnE;AAID;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,MAAM,GAAG,IAAI,CA6Df;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAoCtF"}
|