@webboxes/plugin-hub 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/README.md +8 -2
- package/dist/contract.d.ts +63 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +310 -0
- package/dist/contract.js.map +1 -0
- package/dist/folders.d.ts +28 -0
- package/dist/folders.d.ts.map +1 -0
- package/dist/folders.js +88 -0
- package/dist/folders.js.map +1 -0
- package/dist/github-cli.d.ts +18 -0
- package/dist/github-cli.d.ts.map +1 -0
- package/dist/github-cli.js +140 -0
- package/dist/github-cli.js.map +1 -0
- package/dist/http.d.ts +17 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +59 -0
- package/dist/http.js.map +1 -0
- package/dist/hub.d.ts +129 -0
- package/dist/hub.d.ts.map +1 -0
- package/dist/hub.js +148 -0
- package/dist/hub.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/name.d.ts +31 -0
- package/dist/name.d.ts.map +1 -0
- package/dist/name.js +57 -0
- package/dist/name.js.map +1 -0
- package/dist/npm-cli.d.ts +11 -0
- package/dist/npm-cli.d.ts.map +1 -0
- package/dist/npm-cli.js +102 -0
- package/dist/npm-cli.js.map +1 -0
- package/dist/npm-publish.d.ts +36 -0
- package/dist/npm-publish.d.ts.map +1 -0
- package/dist/npm-publish.js +63 -0
- package/dist/npm-publish.js.map +1 -0
- package/dist/pack.d.ts +32 -0
- package/dist/pack.d.ts.map +1 -0
- package/dist/pack.js +137 -0
- package/dist/pack.js.map +1 -0
- package/dist/pull.d.ts +55 -0
- package/dist/pull.d.ts.map +1 -0
- package/dist/pull.js +127 -0
- package/dist/pull.js.map +1 -0
- package/dist/save.d.ts +10 -0
- package/dist/save.d.ts.map +1 -0
- package/dist/save.js +15 -0
- package/dist/save.js.map +1 -0
- package/dist/share.d.ts +72 -0
- package/dist/share.d.ts.map +1 -0
- package/dist/share.js +136 -0
- package/dist/share.js.map +1 -0
- package/dist/source.d.ts +38 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +122 -0
- package/dist/source.js.map +1 -0
- package/dist/store.d.ts +43 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +112 -0
- package/dist/store.js.map +1 -0
- package/dist/tarball.d.ts +16 -0
- package/dist/tarball.d.ts.map +1 -0
- package/dist/tarball.js +138 -0
- package/dist/tarball.js.map +1 -0
- package/dist/version.d.ts +16 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +69 -0
- package/dist/version.js.map +1 -0
- package/package.json +44 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 WebBoxes s.r.o.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @webboxes/plugin-hub
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Saving, sharing and pulling Cordis plugins, with no Cordis, DSH or surface dependency (spec 044, `specs/044-extension-persistence-and-registries/lifecycle.md`).
|
|
4
|
+
|
|
5
|
+
- **Names** (`parsePluginName`, `pluginNameOf`): `@owner/accounting-app-aurora.sidebar.date-filter`; the dots are the levels of the author's tree.
|
|
6
|
+
- **Sources** (`parsePluginSource`): `store:`, `github:owner/repo[@version]`, `npm:`, a `.tgz` file or https URL, a folder, or a bare name. A raw git address is refused.
|
|
7
|
+
- **Tarballs** (`packFolder`, `writeTarball`, `readTarball`, `integrityOf`): npm's file rules and layout, written deterministically (the same files give the same sha512 on every system), with no npm needed.
|
|
8
|
+
- **Store** (`PluginStore`, `savePlugin`): every saved or pulled version as a tarball plus a record; a version is kept once.
|
|
9
|
+
- **Pull** (`pullPlugin`): resolve, fetch, check the sha512, read the manifest, store. Installing is the app's job.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export type Side = 'host' | 'client';
|
|
2
|
+
export type SideLists = Partial<Record<Side, readonly string[]>>;
|
|
3
|
+
export interface ServiceContract {
|
|
4
|
+
readonly requires: SideLists;
|
|
5
|
+
readonly provides: SideLists;
|
|
6
|
+
readonly slots: {
|
|
7
|
+
readonly offers: readonly string[];
|
|
8
|
+
readonly fills: readonly string[];
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/** The host entry the Loader imports: `exports["."]`, then `main`, then `./index.js`. */
|
|
12
|
+
export declare function hostEntryOf(pkg: Readonly<Record<string, unknown>>): string;
|
|
13
|
+
/** Services a source registers: a Service class's `super(ctx, 'name')`, `ctx.provide('name')` and `reflect.provide('name')`. */
|
|
14
|
+
export declare function providedServices(text: string): string[];
|
|
15
|
+
/** UI slots a source declares (`children: { 'name': { kind: ... } }`) and fills (`slots.register({ name: 'x' })`, `slots.inject('x', ...)`). */
|
|
16
|
+
export declare function slotsInSource(text: string): {
|
|
17
|
+
readonly offers: string[];
|
|
18
|
+
readonly fills: string[];
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* A client bundle's `inject`, read from its source, not run: client bundles are written for the browser's module loader and cannot be imported
|
|
22
|
+
* here. Only the literal list form is readable (`exports.inject = ['slots', 'connection']`); any other form is reported, so a contract is never
|
|
23
|
+
* silently empty.
|
|
24
|
+
*/
|
|
25
|
+
export declare function clientInjectFromSource(text: string): {
|
|
26
|
+
readonly names: readonly string[];
|
|
27
|
+
} | {
|
|
28
|
+
readonly error: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* A host entry's `inject`, read from its source when it is written literally (`export const inject = ['tools']`, `export const inject =
|
|
32
|
+
* { tools: null }`, `static inject = [...]` in a Service class). Reading the source, not importing it, matters: a plugin imports packages
|
|
33
|
+
* the app provides (`@deepseek-ai/dsh-tools`), which do not resolve from its source folder. Undefined when there is no literal `inject`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function hostInjectFromSource(text: string): {
|
|
36
|
+
readonly names: readonly string[];
|
|
37
|
+
} | {
|
|
38
|
+
readonly error: string;
|
|
39
|
+
} | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Read a plugin's contract from its code. The host entry is imported (its `inject` is what the Loader sees, including a Service class's static
|
|
42
|
+
* or decorated `inject`); the client bundle is read as source. Host `provides` scans every source file except the client bundle; slots scan all.
|
|
43
|
+
*/
|
|
44
|
+
export declare function readServiceContract(dir: string): Promise<{
|
|
45
|
+
readonly errors: readonly string[];
|
|
46
|
+
readonly contract?: ServiceContract;
|
|
47
|
+
}>;
|
|
48
|
+
/** Shape errors for `acryl.requires`, `acryl.provides` and `acryl.slots` as a catalog reads them. */
|
|
49
|
+
export declare function checkContractShape(block: Readonly<Record<string, unknown>> | undefined): string[];
|
|
50
|
+
/**
|
|
51
|
+
* Bring package.json's `acryl.requires`, `acryl.provides` and `acryl.slots` in line with the code. The rest of the file keeps its content and key
|
|
52
|
+
* order; it is rewritten with two-space indentation, and only when something changed. A package without an `acryl` block gets the documented
|
|
53
|
+
* minimal one around the contract.
|
|
54
|
+
*/
|
|
55
|
+
export declare function syncServiceContract(dir: string): Promise<{
|
|
56
|
+
readonly ok: true;
|
|
57
|
+
readonly contract: ServiceContract;
|
|
58
|
+
readonly written: boolean;
|
|
59
|
+
} | {
|
|
60
|
+
readonly ok: false;
|
|
61
|
+
readonly errors: readonly string[];
|
|
62
|
+
}>;
|
|
63
|
+
//# sourceMappingURL=contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAgBA,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG,QAAQ,CAAA;AACpC,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAA;AAEhE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;IAC5B,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,CAAA;CAC1F;AAOD,yFAAyF;AACzF,wBAAgB,WAAW,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,CAI1E;AAED,gIAAgI;AAChI,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAMvD;AAED,gJAAgJ;AAChJ,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAOnG;AAWD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAYvH;AAwBD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAoCjI;AAkBD;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAA;CAAE,CAAC,CAoD3I;AAED,qGAAqG;AACrG,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,GAAG,MAAM,EAAE,CAoBjG;AAID;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC,CAYjN"}
|
package/dist/contract.js
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
// A plugin's service contract and UI slots, read from its code (spec 044, spec.md "The service contract" and lifecycle.md D9 option D).
|
|
2
|
+
//
|
|
3
|
+
// "acryl": {
|
|
4
|
+
// "requires": { "host": ["connection", "webServer"], "client": ["slots"] }, the plugin's `inject`: what the app must provide
|
|
5
|
+
// "provides": { "host": [], "client": [] }, services it registers: `super(ctx, 'x')`, `ctx.provide('x')`
|
|
6
|
+
// "slots": { "offers": ["notes.panel"], "fills": ["conversation.session.header.actions"] } UI places it declares and fills
|
|
7
|
+
// }
|
|
8
|
+
//
|
|
9
|
+
// Generated, never hand-written, so a catalog, the Market and a Blend's builder can tell which app runs a plugin, which plugin supplies a missing
|
|
10
|
+
// service, and where in which kind of app it shows, without loading its code. In the pinned Cordis every `inject` entry is required; an
|
|
11
|
+
// optional dependency is a `ctx.get()` and is not part of the contract. Slots are read only in their literal form: a slot named by a variable
|
|
12
|
+
// is not listed (they describe, they do not gate).
|
|
13
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
14
|
+
import { extname, join } from 'node:path';
|
|
15
|
+
import { pathToFileURL } from 'node:url';
|
|
16
|
+
const SOURCE_EXTENSIONS = new Set(['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts', '.tsx', '.jsx']);
|
|
17
|
+
const SERVICE_NAME = /^[A-Za-z][A-Za-z0-9.\-_]*$/u;
|
|
18
|
+
const SIDES = ['host', 'client'];
|
|
19
|
+
let importCounter = 0;
|
|
20
|
+
/** The host entry the Loader imports: `exports["."]`, then `main`, then `./index.js`. */
|
|
21
|
+
export function hostEntryOf(pkg) {
|
|
22
|
+
const exportsField = pkg['exports'];
|
|
23
|
+
const dot = exportsField?.['.'];
|
|
24
|
+
return typeof dot === 'string' ? dot : typeof pkg['main'] === 'string' ? pkg['main'] : './index.js';
|
|
25
|
+
}
|
|
26
|
+
/** Services a source registers: a Service class's `super(ctx, 'name')`, `ctx.provide('name')` and `reflect.provide('name')`. */
|
|
27
|
+
export function providedServices(text) {
|
|
28
|
+
const out = new Set();
|
|
29
|
+
for (const pattern of [/super\(\s*ctx\s*,\s*'([A-Za-z0-9.\-_]+)'/gu, /ctx\.provide\(\s*'([A-Za-z0-9.\-_]+)'/gu, /reflect\.provide\(\s*'([A-Za-z0-9.\-_]+)'/gu]) {
|
|
30
|
+
for (const match of text.matchAll(pattern))
|
|
31
|
+
if (match[1] !== undefined)
|
|
32
|
+
out.add(match[1]);
|
|
33
|
+
}
|
|
34
|
+
return [...out];
|
|
35
|
+
}
|
|
36
|
+
/** UI slots a source declares (`children: { 'name': { kind: ... } }`) and fills (`slots.register({ name: 'x' })`, `slots.inject('x', ...)`). */
|
|
37
|
+
export function slotsInSource(text) {
|
|
38
|
+
const offers = new Set();
|
|
39
|
+
const fills = new Set();
|
|
40
|
+
for (const match of text.matchAll(/['"]([A-Za-z][\w.-]*)['"]\s*:\s*\{\s*kind\s*:/gu))
|
|
41
|
+
if (match[1] !== undefined)
|
|
42
|
+
offers.add(match[1]);
|
|
43
|
+
for (const match of text.matchAll(/slots\.(?:register|registerFactory)\(\s*\{\s*name\s*:\s*['"]([A-Za-z][\w.-]*)['"]/gu))
|
|
44
|
+
if (match[1] !== undefined)
|
|
45
|
+
fills.add(match[1]);
|
|
46
|
+
for (const match of text.matchAll(/slots\.inject\(\s*['"]([A-Za-z][\w.-]*)['"]/gu))
|
|
47
|
+
if (match[1] !== undefined)
|
|
48
|
+
fills.add(match[1]);
|
|
49
|
+
return { offers: [...offers], fills: [...fills] };
|
|
50
|
+
}
|
|
51
|
+
/** Service names of a Cordis `inject` value: an array, or a name -> config map (inherited keys included, as `Inject.resolve` does). */
|
|
52
|
+
function injectNames(inject) {
|
|
53
|
+
if (inject === undefined || inject === null)
|
|
54
|
+
return [];
|
|
55
|
+
if (Array.isArray(inject))
|
|
56
|
+
return inject.filter((name) => typeof name === 'string');
|
|
57
|
+
const names = [];
|
|
58
|
+
for (const name in inject)
|
|
59
|
+
names.push(name);
|
|
60
|
+
return names;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A client bundle's `inject`, read from its source, not run: client bundles are written for the browser's module loader and cannot be imported
|
|
64
|
+
* here. Only the literal list form is readable (`exports.inject = ['slots', 'connection']`); any other form is reported, so a contract is never
|
|
65
|
+
* silently empty.
|
|
66
|
+
*/
|
|
67
|
+
export function clientInjectFromSource(text) {
|
|
68
|
+
const at = /(?:\bexport\s+const\s+inject|\b(?:module\.)?exports\.inject)\s*=\s*/u.exec(text);
|
|
69
|
+
if (at === null)
|
|
70
|
+
return { names: [] };
|
|
71
|
+
const list = /^\[([^\]]*)\]/u.exec(text.slice(at.index + at[0].length));
|
|
72
|
+
if (list === null)
|
|
73
|
+
return { error: 'the client bundle\'s "inject" is not a literal list; write it as exports.inject = [\'slots\', ...] so its contract can be read' };
|
|
74
|
+
const names = [];
|
|
75
|
+
for (const item of (list[1] ?? '').split(',').map(entry => entry.trim()).filter(entry => entry !== '')) {
|
|
76
|
+
const name = /^(['"])([^'"]+)\1$/u.exec(item)?.[2];
|
|
77
|
+
if (name === undefined)
|
|
78
|
+
return { error: `the client bundle's "inject" has a non-literal entry ${JSON.stringify(item)}; list the service names as strings` };
|
|
79
|
+
names.push(name);
|
|
80
|
+
}
|
|
81
|
+
return { names };
|
|
82
|
+
}
|
|
83
|
+
/** The text of the balanced `[...]` or `{...}` starting at `open`, or undefined when it does not close. */
|
|
84
|
+
function balanced(text, open) {
|
|
85
|
+
const pairs = { '[': ']', '{': '}', '(': ')' };
|
|
86
|
+
const stack = [];
|
|
87
|
+
let quote;
|
|
88
|
+
for (let index = open; index < text.length; index += 1) {
|
|
89
|
+
const char = text[index];
|
|
90
|
+
if (quote !== undefined) {
|
|
91
|
+
if (char === '\\')
|
|
92
|
+
index += 1;
|
|
93
|
+
else if (char === quote)
|
|
94
|
+
quote = undefined;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (char === '"' || char === "'" || char === '`')
|
|
98
|
+
quote = char;
|
|
99
|
+
else if (pairs[char] !== undefined)
|
|
100
|
+
stack.push(pairs[char]);
|
|
101
|
+
else if (char === stack.at(-1)) {
|
|
102
|
+
stack.pop();
|
|
103
|
+
if (stack.length === 0)
|
|
104
|
+
return text.slice(open, index + 1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* A host entry's `inject`, read from its source when it is written literally (`export const inject = ['tools']`, `export const inject =
|
|
111
|
+
* { tools: null }`, `static inject = [...]` in a Service class). Reading the source, not importing it, matters: a plugin imports packages
|
|
112
|
+
* the app provides (`@deepseek-ai/dsh-tools`), which do not resolve from its source folder. Undefined when there is no literal `inject`.
|
|
113
|
+
*/
|
|
114
|
+
export function hostInjectFromSource(text) {
|
|
115
|
+
const at = /(?:\bexport\s+const\s+inject|\bstatic\s+inject|\b(?:module\.)?exports\.inject)\s*=\s*/u.exec(text);
|
|
116
|
+
if (at === null)
|
|
117
|
+
return undefined;
|
|
118
|
+
const start = at.index + at[0].length;
|
|
119
|
+
const literal = balanced(text, start);
|
|
120
|
+
if (literal === undefined || (literal[0] !== '[' && literal[0] !== '{'))
|
|
121
|
+
return { error: 'the host entry\'s "inject" is not a literal list or object; write it as export const inject = [\'tools\', ...] so its contract can be read' };
|
|
122
|
+
const body = literal.slice(1, -1);
|
|
123
|
+
if (literal[0] === '[') {
|
|
124
|
+
const names = [];
|
|
125
|
+
for (const item of body.split(',').map(entry => entry.trim()).filter(entry => entry !== '')) {
|
|
126
|
+
const name = /^(['"])([^'"]+)\1$/u.exec(item)?.[2];
|
|
127
|
+
if (name === undefined)
|
|
128
|
+
return { error: `the host entry's "inject" has a non-literal entry ${JSON.stringify(item)}; list the service names as strings` };
|
|
129
|
+
names.push(name);
|
|
130
|
+
}
|
|
131
|
+
return { names };
|
|
132
|
+
}
|
|
133
|
+
// An object: its top-level keys are the names (the values are per-service config).
|
|
134
|
+
const names = [];
|
|
135
|
+
let depth = 0;
|
|
136
|
+
let token = '';
|
|
137
|
+
let quote;
|
|
138
|
+
for (const char of body) {
|
|
139
|
+
if (quote !== undefined) {
|
|
140
|
+
if (char === quote)
|
|
141
|
+
quote = undefined;
|
|
142
|
+
if (depth === 0)
|
|
143
|
+
token += char;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (char === '"' || char === "'") {
|
|
147
|
+
quote = char;
|
|
148
|
+
if (depth === 0)
|
|
149
|
+
token += char;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if ('[{('.includes(char))
|
|
153
|
+
depth += 1;
|
|
154
|
+
else if (']})'.includes(char))
|
|
155
|
+
depth -= 1;
|
|
156
|
+
else if (char === ':' && depth === 0) {
|
|
157
|
+
const key = /^\s*(?:(['"])([^'"]+)\1|([A-Za-z_$][\w$.-]*))\s*$/u.exec(token.split(',').at(-1) ?? '');
|
|
158
|
+
if (key === null)
|
|
159
|
+
return { error: 'the host entry\'s "inject" object has a computed key; write its keys literally' };
|
|
160
|
+
names.push(key[2] ?? key[3] ?? '');
|
|
161
|
+
token = '';
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
else if (char === ',' && depth === 0) {
|
|
165
|
+
token = '';
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (depth === 0)
|
|
169
|
+
token += char;
|
|
170
|
+
}
|
|
171
|
+
return { names };
|
|
172
|
+
}
|
|
173
|
+
function sourceFiles(dir, skip) {
|
|
174
|
+
const out = [];
|
|
175
|
+
const walk = (current) => {
|
|
176
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
177
|
+
if (entry.name === 'node_modules' || entry.name.startsWith('.'))
|
|
178
|
+
continue;
|
|
179
|
+
const path = join(current, entry.name);
|
|
180
|
+
if (entry.isDirectory())
|
|
181
|
+
walk(path);
|
|
182
|
+
else if (SOURCE_EXTENSIONS.has(extname(entry.name)) && !skip.has(path))
|
|
183
|
+
out.push(path);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
walk(dir);
|
|
187
|
+
return out.sort();
|
|
188
|
+
}
|
|
189
|
+
const sorted = (names) => [...new Set(names)].sort();
|
|
190
|
+
/**
|
|
191
|
+
* Read a plugin's contract from its code. The host entry is imported (its `inject` is what the Loader sees, including a Service class's static
|
|
192
|
+
* or decorated `inject`); the client bundle is read as source. Host `provides` scans every source file except the client bundle; slots scan all.
|
|
193
|
+
*/
|
|
194
|
+
export async function readServiceContract(dir) {
|
|
195
|
+
let pkg;
|
|
196
|
+
try {
|
|
197
|
+
pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'));
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return { errors: ['package.json is missing or invalid'] };
|
|
201
|
+
}
|
|
202
|
+
const errors = [];
|
|
203
|
+
const hostRel = hostEntryOf(pkg);
|
|
204
|
+
const hostEntry = join(dir, hostRel);
|
|
205
|
+
const exportsField = pkg['exports'];
|
|
206
|
+
const dsh = pkg['dsh'];
|
|
207
|
+
const clientRel = dsh?.['client'] !== undefined && typeof exportsField?.['./client'] === 'string' ? exportsField['./client'] : undefined;
|
|
208
|
+
const clientEntry = clientRel === undefined ? undefined : join(dir, clientRel);
|
|
209
|
+
const requires = {};
|
|
210
|
+
const provides = {};
|
|
211
|
+
if (!existsSync(hostEntry))
|
|
212
|
+
errors.push(`the host entry ${hostRel} does not exist`);
|
|
213
|
+
else {
|
|
214
|
+
const literal = hostInjectFromSource(readFileSync(hostEntry, 'utf8'));
|
|
215
|
+
if (literal !== undefined && 'names' in literal)
|
|
216
|
+
requires.host = sorted(literal.names);
|
|
217
|
+
else {
|
|
218
|
+
// No literal (a computed or decorated `inject`): read it the way the Loader does, by importing the entry.
|
|
219
|
+
try {
|
|
220
|
+
const mod = await import(`${pathToFileURL(hostEntry).href}?contract=${++importCounter}-${Date.now()}`);
|
|
221
|
+
const plugin = ('default' in mod ? mod['default'] : mod);
|
|
222
|
+
requires.host = sorted(injectNames(plugin?.inject));
|
|
223
|
+
}
|
|
224
|
+
catch (cause) {
|
|
225
|
+
errors.push(literal !== undefined && 'error' in literal
|
|
226
|
+
? literal.error
|
|
227
|
+
: `importing ${hostRel} failed, so its "inject" cannot be read: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const skip = new Set(clientEntry === undefined ? [] : [clientEntry]);
|
|
231
|
+
provides.host = sorted(sourceFiles(dir, skip).flatMap(file => providedServices(readFileSync(file, 'utf8'))));
|
|
232
|
+
}
|
|
233
|
+
if (clientEntry !== undefined) {
|
|
234
|
+
if (!existsSync(clientEntry))
|
|
235
|
+
errors.push(`the client bundle ${clientRel ?? ''} does not exist`);
|
|
236
|
+
else {
|
|
237
|
+
const source = readFileSync(clientEntry, 'utf8');
|
|
238
|
+
const read = clientInjectFromSource(source);
|
|
239
|
+
if ('error' in read)
|
|
240
|
+
errors.push(read.error);
|
|
241
|
+
else
|
|
242
|
+
requires.client = sorted(read.names);
|
|
243
|
+
provides.client = sorted(providedServices(source));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const offers = [];
|
|
247
|
+
const fills = [];
|
|
248
|
+
for (const file of sourceFiles(dir, new Set())) {
|
|
249
|
+
const found = slotsInSource(readFileSync(file, 'utf8'));
|
|
250
|
+
offers.push(...found.offers);
|
|
251
|
+
fills.push(...found.fills);
|
|
252
|
+
}
|
|
253
|
+
if (errors.length > 0)
|
|
254
|
+
return { errors };
|
|
255
|
+
return { errors, contract: { requires, provides, slots: { offers: sorted(offers), fills: sorted(fills) } } };
|
|
256
|
+
}
|
|
257
|
+
/** Shape errors for `acryl.requires`, `acryl.provides` and `acryl.slots` as a catalog reads them. */
|
|
258
|
+
export function checkContractShape(block) {
|
|
259
|
+
const errors = [];
|
|
260
|
+
for (const field of ['requires', 'provides']) {
|
|
261
|
+
const value = block?.[field];
|
|
262
|
+
if (value === undefined)
|
|
263
|
+
continue;
|
|
264
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
265
|
+
errors.push(`"acryl.${field}" must be an object with "host" and/or "client" lists`);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
for (const [side, names] of Object.entries(value)) {
|
|
269
|
+
if (!SIDES.includes(side))
|
|
270
|
+
errors.push(`"acryl.${field}.${side}" is not a side; use "host" or "client"`);
|
|
271
|
+
else if (!Array.isArray(names) || !names.every(name => typeof name === 'string' && SERVICE_NAME.test(name)))
|
|
272
|
+
errors.push(`"acryl.${field}.${side}" must be a list of service names`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const slots = block?.['slots'];
|
|
276
|
+
if (slots !== undefined) {
|
|
277
|
+
if (typeof slots !== 'object' || slots === null || Array.isArray(slots))
|
|
278
|
+
errors.push('"acryl.slots" must be an object with "offers" and "fills" lists');
|
|
279
|
+
else
|
|
280
|
+
for (const [key, names] of Object.entries(slots)) {
|
|
281
|
+
if (key !== 'offers' && key !== 'fills')
|
|
282
|
+
errors.push(`"acryl.slots.${key}" is not "offers" or "fills"`);
|
|
283
|
+
else if (!Array.isArray(names) || !names.every(name => typeof name === 'string' && SERVICE_NAME.test(name)))
|
|
284
|
+
errors.push(`"acryl.slots.${key}" must be a list of slot names`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return errors;
|
|
288
|
+
}
|
|
289
|
+
const same = (a, b) => JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
|
|
290
|
+
/**
|
|
291
|
+
* Bring package.json's `acryl.requires`, `acryl.provides` and `acryl.slots` in line with the code. The rest of the file keeps its content and key
|
|
292
|
+
* order; it is rewritten with two-space indentation, and only when something changed. A package without an `acryl` block gets the documented
|
|
293
|
+
* minimal one around the contract.
|
|
294
|
+
*/
|
|
295
|
+
export async function syncServiceContract(dir) {
|
|
296
|
+
const read = await readServiceContract(dir);
|
|
297
|
+
if (read.contract === undefined)
|
|
298
|
+
return { ok: false, errors: read.errors };
|
|
299
|
+
const file = join(dir, 'package.json');
|
|
300
|
+
const pkg = JSON.parse(readFileSync(file, 'utf8'));
|
|
301
|
+
const existing = pkg['acryl'];
|
|
302
|
+
const block = typeof existing === 'object' && existing !== null ? existing : { schemaVersion: 1, artifacts: { plugins: [hostEntryOf(pkg)] } };
|
|
303
|
+
const { contract } = read;
|
|
304
|
+
if (same(block['requires'], contract.requires) && same(block['provides'], contract.provides) && same(block['slots'], contract.slots))
|
|
305
|
+
return { ok: true, contract, written: false };
|
|
306
|
+
pkg['acryl'] = { ...block, requires: contract.requires, provides: contract.provides, slots: contract.slots };
|
|
307
|
+
writeFileSync(file, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
308
|
+
return { ok: true, contract, written: true };
|
|
309
|
+
}
|
|
310
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const PROJECT_PLUGINS_FOLDER = ".acryl-plugins";
|
|
2
|
+
export declare const HOME_PLUGINS_FOLDER = "plugins";
|
|
3
|
+
export declare const STORE_FOLDER = ".store";
|
|
4
|
+
/** What a move did: entries left in the old folder because the new one already had that name. */
|
|
5
|
+
export interface FolderMove {
|
|
6
|
+
readonly from: string;
|
|
7
|
+
readonly to: string;
|
|
8
|
+
readonly kept: readonly string[];
|
|
9
|
+
}
|
|
10
|
+
/** Reports a move that could not take everything (a person decides what to do with both copies). */
|
|
11
|
+
export type MoveReporter = (move: FolderMove) => void;
|
|
12
|
+
/** Move `from` to `to` as described above; a no-op when `from` does not exist. */
|
|
13
|
+
export declare function moveFolder(from: string, to: string, report?: MoveReporter): FolderMove | undefined;
|
|
14
|
+
/** A project's source plugins: `<project>/.acryl-plugins/`. */
|
|
15
|
+
export declare function projectPluginsDir(projectDir: string, report?: MoveReporter): string;
|
|
16
|
+
/** An app home's own source plugins: `<home>/plugins/`. The store's old place inside it moves first, so it is never taken for a plugin. */
|
|
17
|
+
export declare function homePluginsDir(home: string, report?: MoveReporter): string;
|
|
18
|
+
/**
|
|
19
|
+
* Where an app instance keeps its plugin store, under that instance's home: per instance, never machine-wide, so a development or test run never
|
|
20
|
+
* writes into the user's real home. The home itself comes from the app (ACRYL's AppInstance family).
|
|
21
|
+
*/
|
|
22
|
+
export declare function storeRootOf(home: string, report?: MoveReporter): string;
|
|
23
|
+
/**
|
|
24
|
+
* A source folder an install recorded before the rename, where it is now: the same path with the old folder name replaced, when the recorded one is
|
|
25
|
+
* gone and the renamed one exists. Any other path is returned as it is.
|
|
26
|
+
*/
|
|
27
|
+
export declare function currentSourcePath(recorded: string): string;
|
|
28
|
+
//# sourceMappingURL=folders.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"folders.d.ts","sourceRoot":"","sources":["../src/folders.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,sBAAsB,mBAAmB,CAAA;AACtD,eAAO,MAAM,mBAAmB,YAAY,CAAA;AAC5C,eAAO,MAAM,YAAY,WAAW,CAAA;AAMpC,iGAAiG;AACjG,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CACjC;AAED,oGAAoG;AACpG,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAA;AAUrD,kFAAkF;AAClF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,GAAE,YAA8B,GAAG,UAAU,GAAG,SAAS,CAenH;AAED,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,MAAM,CAInF;AAED,2IAA2I;AAC3I,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,MAAM,CAK1E;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,MAAM,CAQvE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAM1D"}
|
package/dist/folders.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Where source plugins and the plugin store live (spec 044, "One term: plugin", build step 1b), and the one-time move from the folders they had
|
|
2
|
+
// before the rename. Every reader of these folders asks here, so the names are written once:
|
|
3
|
+
//
|
|
4
|
+
// a project's source plugins <project>/.acryl-plugins/<name>/ (was .acryl-extensions/)
|
|
5
|
+
// an app instance's, inside a project <project>/.acryl/instances/<scope>/plugins/<name>/ (was .../extensions/)
|
|
6
|
+
// an app home's own source plugins <home>/plugins/<name>/ (was <home>/extensions/)
|
|
7
|
+
// an app home's plugin store <home>/plugins/.store/ (was <home>/plugins/store/)
|
|
8
|
+
//
|
|
9
|
+
// A dot name is never a plugin name, so the store and a plugin named `store` cannot collide. An old folder is moved the first time its new place is
|
|
10
|
+
// asked for: renamed whole when the new one does not exist, otherwise entry by entry, and an entry whose name is already taken in the new folder stays
|
|
11
|
+
// where it is and is reported. Nothing is deleted except an old folder left empty by the move.
|
|
12
|
+
import { existsSync, mkdirSync, readdirSync, renameSync, rmdirSync } from 'node:fs';
|
|
13
|
+
import { dirname, join, sep } from 'node:path';
|
|
14
|
+
export const PROJECT_PLUGINS_FOLDER = '.acryl-plugins';
|
|
15
|
+
export const HOME_PLUGINS_FOLDER = 'plugins';
|
|
16
|
+
export const STORE_FOLDER = '.store';
|
|
17
|
+
const LEGACY_PROJECT_PLUGINS_FOLDER = '.acryl-extensions';
|
|
18
|
+
const LEGACY_HOME_PLUGINS_FOLDER = 'extensions';
|
|
19
|
+
const LEGACY_STORE_FOLDER = 'store';
|
|
20
|
+
const warnOnce = new Set();
|
|
21
|
+
const defaultReporter = move => {
|
|
22
|
+
const key = `${move.from}\0${move.to}`;
|
|
23
|
+
if (warnOnce.has(key))
|
|
24
|
+
return;
|
|
25
|
+
warnOnce.add(key);
|
|
26
|
+
process.emitWarning(`${move.from} still holds ${move.kept.join(', ')}: ${move.to} already has folders with those names, so they were not moved`);
|
|
27
|
+
};
|
|
28
|
+
/** Move `from` to `to` as described above; a no-op when `from` does not exist. */
|
|
29
|
+
export function moveFolder(from, to, report = defaultReporter) {
|
|
30
|
+
if (!existsSync(from))
|
|
31
|
+
return undefined;
|
|
32
|
+
if (!existsSync(to)) {
|
|
33
|
+
mkdirSync(dirname(to), { recursive: true });
|
|
34
|
+
renameSync(from, to);
|
|
35
|
+
return { from, to, kept: [] };
|
|
36
|
+
}
|
|
37
|
+
const kept = [];
|
|
38
|
+
for (const entry of readdirSync(from)) {
|
|
39
|
+
if (existsSync(join(to, entry)))
|
|
40
|
+
kept.push(entry);
|
|
41
|
+
else
|
|
42
|
+
renameSync(join(from, entry), join(to, entry));
|
|
43
|
+
}
|
|
44
|
+
if (kept.length === 0)
|
|
45
|
+
rmdirSync(from);
|
|
46
|
+
else
|
|
47
|
+
report({ from, to, kept });
|
|
48
|
+
return { from, to, kept };
|
|
49
|
+
}
|
|
50
|
+
/** A project's source plugins: `<project>/.acryl-plugins/`. */
|
|
51
|
+
export function projectPluginsDir(projectDir, report) {
|
|
52
|
+
const dir = join(projectDir, PROJECT_PLUGINS_FOLDER);
|
|
53
|
+
moveFolder(join(projectDir, LEGACY_PROJECT_PLUGINS_FOLDER), dir, report);
|
|
54
|
+
return dir;
|
|
55
|
+
}
|
|
56
|
+
/** An app home's own source plugins: `<home>/plugins/`. The store's old place inside it moves first, so it is never taken for a plugin. */
|
|
57
|
+
export function homePluginsDir(home, report) {
|
|
58
|
+
const dir = join(home, HOME_PLUGINS_FOLDER);
|
|
59
|
+
moveFolder(join(dir, LEGACY_STORE_FOLDER), join(dir, STORE_FOLDER), report);
|
|
60
|
+
moveFolder(join(home, LEGACY_HOME_PLUGINS_FOLDER), dir, report);
|
|
61
|
+
return dir;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Where an app instance keeps its plugin store, under that instance's home: per instance, never machine-wide, so a development or test run never
|
|
65
|
+
* writes into the user's real home. The home itself comes from the app (ACRYL's AppInstance family).
|
|
66
|
+
*/
|
|
67
|
+
export function storeRootOf(home, report) {
|
|
68
|
+
const plugins = join(home, HOME_PLUGINS_FOLDER);
|
|
69
|
+
const dir = join(plugins, STORE_FOLDER);
|
|
70
|
+
// Only the store's index marks the old folder as the store: a source plugin named `store` has a package.json instead.
|
|
71
|
+
if (existsSync(join(plugins, LEGACY_STORE_FOLDER, 'index.json')) && !existsSync(join(plugins, LEGACY_STORE_FOLDER, 'package.json'))) {
|
|
72
|
+
moveFolder(join(plugins, LEGACY_STORE_FOLDER), dir, report);
|
|
73
|
+
}
|
|
74
|
+
return dir;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* A source folder an install recorded before the rename, where it is now: the same path with the old folder name replaced, when the recorded one is
|
|
78
|
+
* gone and the renamed one exists. Any other path is returned as it is.
|
|
79
|
+
*/
|
|
80
|
+
export function currentSourcePath(recorded) {
|
|
81
|
+
if (existsSync(recorded))
|
|
82
|
+
return recorded;
|
|
83
|
+
const renamed = recorded
|
|
84
|
+
.replace(`${sep}${LEGACY_PROJECT_PLUGINS_FOLDER}${sep}`, `${sep}${PROJECT_PLUGINS_FOLDER}${sep}`)
|
|
85
|
+
.replace(new RegExp(`\\${sep}${LEGACY_HOME_PLUGINS_FOLDER}\\${sep}(?=[^\\${sep}]+\\${sep}?$)`, 'u'), `${sep}${HOME_PLUGINS_FOLDER}${sep}`);
|
|
86
|
+
return renamed !== recorded && existsSync(renamed) ? renamed : recorded;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=folders.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"folders.js","sourceRoot":"","sources":["../src/folders.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ReleasePort } from './pull.js';
|
|
2
|
+
import type { PublishGitPort, RepositoryHostPort } from './share.js';
|
|
3
|
+
export declare class ToolError extends Error {
|
|
4
|
+
constructor(message: string);
|
|
5
|
+
}
|
|
6
|
+
export declare function githubCli(): RepositoryHostPort;
|
|
7
|
+
export declare function gitCli(): PublishGitPort;
|
|
8
|
+
/**
|
|
9
|
+
* GitHub releases through the user's own `gh` login: what a pull of a private repository needs (an anonymous request cannot see it). Assets
|
|
10
|
+
* are fetched by their API address with `Accept: application/octet-stream`, which works for private and public repositories alike.
|
|
11
|
+
*/
|
|
12
|
+
export declare function githubReleasesViaGh(): ReleasePort;
|
|
13
|
+
/**
|
|
14
|
+
* Releases from the first port that sees the repository: anonymous HTTPS for a public one, then the user's `gh` for a private one. Downloads
|
|
15
|
+
* go to the port that answered for the release, since only it can fetch its asset addresses.
|
|
16
|
+
*/
|
|
17
|
+
export declare function firstReleasePort(...ports: readonly ReleasePort[]): ReleasePort;
|
|
18
|
+
//# sourceMappingURL=github-cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-cli.d.ts","sourceRoot":"","sources":["../src/github-cli.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAc,MAAM,YAAY,CAAA;AAEhF,qBAAa,SAAU,SAAQ,KAAK;gBACtB,OAAO,EAAE,MAAM;CAI5B;AA2BD,wBAAgB,SAAS,IAAI,kBAAkB,CA2B9C;AAED,wBAAgB,MAAM,IAAI,cAAc,CAgBvC;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,WAAW,CAoBjD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,KAAK,EAAE,SAAS,WAAW,EAAE,GAAG,WAAW,CAqB9E"}
|