@ebowwa/seedinstallation 0.3.0 → 0.4.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 +1 -1
- package/README.md +36 -144
- package/dist/bootstrap.d.ts +5 -6
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +294 -215
- package/dist/clone.d.ts +1 -0
- package/dist/clone.d.ts.map +1 -0
- package/dist/clone.js +54 -68
- package/dist/device-auth.d.ts +4 -5
- package/dist/device-auth.d.ts.map +1 -0
- package/dist/device-auth.js +252 -164
- package/dist/index.d.ts +13 -12
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +941 -10
- package/dist/runtime.d.ts +9 -14
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +211 -114
- package/dist/sudo.d.ts +1 -0
- package/dist/sudo.d.ts.map +1 -0
- package/dist/sudo.js +123 -222
- package/dist/systemd.d.ts +207 -3
- package/dist/systemd.d.ts.map +1 -0
- package/dist/systemd.js +477 -177
- package/package.json +40 -40
package/dist/bootstrap.js
CHANGED
|
@@ -1,225 +1,304 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, {
|
|
5
|
+
get: all[name],
|
|
6
|
+
enumerable: true,
|
|
7
|
+
configurable: true,
|
|
8
|
+
set: (newValue) => all[name] = () => newValue
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
12
|
+
|
|
13
|
+
// sudo.ts
|
|
14
|
+
var exports_sudo = {};
|
|
15
|
+
__export(exports_sudo, {
|
|
16
|
+
writeFile: () => writeFile,
|
|
17
|
+
sudo: () => sudo,
|
|
18
|
+
serviceEnable: () => serviceEnable,
|
|
19
|
+
service: () => service,
|
|
20
|
+
pkgInstall: () => pkgInstall,
|
|
21
|
+
exec: () => exec
|
|
22
|
+
});
|
|
23
|
+
async function sudo(cmd, opts) {
|
|
24
|
+
const parts = Array.isArray(cmd) ? cmd : cmd.split(/\s+/);
|
|
25
|
+
const envPrefix = opts.env ? Object.entries(opts.env).map(([k, v]) => `${k}=${shellEscape(v)}`) : [];
|
|
26
|
+
const sudoCmd = ["sudo", ...envPrefix, ...parts];
|
|
27
|
+
return exec(sudoCmd, opts);
|
|
28
|
+
}
|
|
29
|
+
async function pkgInstall(packages, opts) {
|
|
30
|
+
const pm = opts.pm ?? "apt";
|
|
31
|
+
const envOverride = {
|
|
32
|
+
...opts.env,
|
|
33
|
+
...opts.nonInteractive !== false ? { DEBIAN_FRONTEND: "noninteractive" } : {}
|
|
34
|
+
};
|
|
35
|
+
await sudo(updateCmd[pm], { ...opts, env: envOverride, quiet: true });
|
|
36
|
+
return sudo([...installCmd[pm], ...packages], {
|
|
37
|
+
...opts,
|
|
38
|
+
env: envOverride
|
|
39
|
+
});
|
|
10
40
|
}
|
|
11
41
|
async function writeFile(path, content, opts) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
42
|
+
const op = opts.append ? "-a" : "";
|
|
43
|
+
const teeCmd = `tee ${op} ${shellEscape(path)}`.trim();
|
|
44
|
+
const result = await execPipe(content, ["sudo", teeCmd], opts);
|
|
45
|
+
if (result.ok && opts.mode) {
|
|
46
|
+
await sudo(["chmod", opts.mode, path], opts);
|
|
47
|
+
}
|
|
48
|
+
if (result.ok && opts.owner) {
|
|
49
|
+
await sudo(["chown", opts.owner, path], opts);
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
async function service(name, action, opts) {
|
|
54
|
+
return sudo(["systemctl", action, name], opts);
|
|
55
|
+
}
|
|
56
|
+
async function serviceEnable(name, opts) {
|
|
57
|
+
return sudo(["systemctl", "enable", "--now", name], opts);
|
|
58
|
+
}
|
|
59
|
+
function buildSshPrefix(ctx) {
|
|
60
|
+
const parts = ["ssh", "-F", "/dev/null", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null"];
|
|
61
|
+
if (ctx.keyPath)
|
|
62
|
+
parts.push("-i", ctx.keyPath);
|
|
63
|
+
else if (ctx.key)
|
|
64
|
+
parts.push("-i", ctx.key);
|
|
65
|
+
if (ctx.port)
|
|
66
|
+
parts.push("-p", String(ctx.port));
|
|
67
|
+
parts.push(`${ctx.user ?? "root"}@${ctx.host}`);
|
|
68
|
+
return parts;
|
|
69
|
+
}
|
|
70
|
+
async function exec(args, opts) {
|
|
71
|
+
const finalArgs = opts.context.type === "ssh" ? [...buildSshPrefix(opts.context), args.map(shellEscape).join(" ")] : args;
|
|
72
|
+
const proc = Bun.spawn(finalArgs, {
|
|
73
|
+
stdout: "pipe",
|
|
74
|
+
stderr: "pipe",
|
|
75
|
+
timeout: opts.timeout ?? 30000
|
|
76
|
+
});
|
|
77
|
+
const exitCode = await proc.exited;
|
|
78
|
+
const stdout = opts.quiet ? "" : await new Response(proc.stdout).text();
|
|
79
|
+
const stderr = await new Response(proc.stderr).text();
|
|
80
|
+
return { stdout, stderr, exitCode, ok: exitCode === 0 };
|
|
81
|
+
}
|
|
82
|
+
async function execPipe(input, args, opts) {
|
|
83
|
+
if (opts.context.type === "ssh") {
|
|
84
|
+
const sshPrefix = buildSshPrefix(opts.context);
|
|
85
|
+
const remoteCmd = args.join(" ");
|
|
86
|
+
const fullArgs = [...sshPrefix, remoteCmd];
|
|
87
|
+
const proc2 = Bun.spawn(fullArgs, {
|
|
88
|
+
stdin: new TextEncoder().encode(input),
|
|
89
|
+
stdout: "pipe",
|
|
90
|
+
stderr: "pipe",
|
|
91
|
+
timeout: opts.timeout ?? 30000
|
|
92
|
+
});
|
|
93
|
+
const exitCode2 = await proc2.exited;
|
|
94
|
+
const stdout2 = opts.quiet ? "" : await new Response(proc2.stdout).text();
|
|
95
|
+
const stderr2 = await new Response(proc2.stderr).text();
|
|
96
|
+
return { stdout: stdout2, stderr: stderr2, exitCode: exitCode2, ok: exitCode2 === 0 };
|
|
97
|
+
}
|
|
98
|
+
const proc = Bun.spawn(["sh", "-c", args.join(" ")], {
|
|
99
|
+
stdin: new TextEncoder().encode(input),
|
|
100
|
+
stdout: "pipe",
|
|
101
|
+
stderr: "pipe",
|
|
102
|
+
timeout: opts.timeout ?? 30000
|
|
103
|
+
});
|
|
104
|
+
const exitCode = await proc.exited;
|
|
105
|
+
const stdout = opts.quiet ? "" : await new Response(proc.stdout).text();
|
|
106
|
+
const stderr = await new Response(proc.stderr).text();
|
|
107
|
+
return { stdout, stderr, exitCode, ok: exitCode === 0 };
|
|
108
|
+
}
|
|
109
|
+
function shellEscape(s) {
|
|
110
|
+
if (/^[a-zA-Z0-9._\-\/=:@]+$/.test(s))
|
|
111
|
+
return s;
|
|
112
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
113
|
+
}
|
|
114
|
+
var installCmd, updateCmd;
|
|
115
|
+
var init_sudo = __esm(() => {
|
|
116
|
+
installCmd = {
|
|
117
|
+
apt: ["apt-get", "install", "-y"],
|
|
118
|
+
dnf: ["dnf", "install", "-y"],
|
|
119
|
+
apk: ["apk", "add", "--no-cache"]
|
|
120
|
+
};
|
|
121
|
+
updateCmd = {
|
|
122
|
+
apt: ["apt-get", "update", "-qq"],
|
|
123
|
+
dnf: ["dnf", "check-update"],
|
|
124
|
+
apk: ["apk", "update"]
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// bootstrap.ts
|
|
129
|
+
async function exec2(args, opts) {
|
|
130
|
+
const { sudo: sudo2 } = await Promise.resolve().then(() => (init_sudo(), exports_sudo));
|
|
131
|
+
return sudo2(args, opts);
|
|
132
|
+
}
|
|
133
|
+
async function writeFile2(path, content, opts) {
|
|
134
|
+
const { writeFile: wf } = await Promise.resolve().then(() => (init_sudo(), exports_sudo));
|
|
135
|
+
return wf(path, content, opts);
|
|
136
|
+
}
|
|
137
|
+
async function getBootstrapStatus(statusFile, opts) {
|
|
138
|
+
const result = await exec2(["cat", statusFile], { ...opts, quiet: true });
|
|
139
|
+
if (!result.ok) {
|
|
90
140
|
return {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
source,
|
|
95
|
-
phases,
|
|
96
|
-
raw: content,
|
|
141
|
+
status: "pending",
|
|
142
|
+
phases: {},
|
|
143
|
+
raw: ""
|
|
97
144
|
};
|
|
145
|
+
}
|
|
146
|
+
return parseBootstrapStatus(result.stdout);
|
|
98
147
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
148
|
+
function parseBootstrapStatus(content) {
|
|
149
|
+
const phases = {};
|
|
150
|
+
const data = {};
|
|
151
|
+
for (const line of content.trim().split(`
|
|
152
|
+
`)) {
|
|
153
|
+
if (!line || !line.includes("="))
|
|
154
|
+
continue;
|
|
155
|
+
const [key, ...valueParts] = line.split("=");
|
|
156
|
+
const value = valueParts.join("=").trim();
|
|
157
|
+
data[key] = value;
|
|
158
|
+
}
|
|
159
|
+
const status = data.status || "started";
|
|
160
|
+
const startedAt = data.started_at;
|
|
161
|
+
const completedAt = data.completed_at;
|
|
162
|
+
const source = data.source;
|
|
163
|
+
for (const [key, value] of Object.entries(data)) {
|
|
164
|
+
if (!key.startsWith("phase."))
|
|
165
|
+
continue;
|
|
166
|
+
const parts = key.split(".");
|
|
167
|
+
if (parts.length < 3)
|
|
168
|
+
continue;
|
|
169
|
+
const [, phaseName, field] = parts;
|
|
170
|
+
if (!phases[phaseName]) {
|
|
171
|
+
phases[phaseName] = { name: phaseName, status: "pending" };
|
|
172
|
+
}
|
|
173
|
+
switch (field) {
|
|
174
|
+
case "status":
|
|
175
|
+
phases[phaseName].status = value;
|
|
176
|
+
break;
|
|
177
|
+
case "started_at":
|
|
178
|
+
phases[phaseName].startedAt = value;
|
|
179
|
+
break;
|
|
180
|
+
case "completed_at":
|
|
181
|
+
phases[phaseName].completedAt = value;
|
|
182
|
+
break;
|
|
183
|
+
case "error":
|
|
184
|
+
phases[phaseName].error = value;
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
status,
|
|
190
|
+
startedAt,
|
|
191
|
+
completedAt,
|
|
192
|
+
source,
|
|
193
|
+
phases,
|
|
194
|
+
raw: content
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
async function initBootstrap(statusFile, source, opts) {
|
|
198
|
+
const now = new Date().toISOString();
|
|
199
|
+
const content = `status=started
|
|
200
|
+
started_at=${now}
|
|
201
|
+
source=${source}
|
|
202
|
+
`;
|
|
203
|
+
return writeFile2(statusFile, content, opts);
|
|
204
|
+
}
|
|
205
|
+
async function startPhase(statusFile, phase, opts) {
|
|
206
|
+
const now = new Date().toISOString();
|
|
207
|
+
const line = `phase.${phase}.status=running
|
|
208
|
+
phase.${phase}.started_at=${now}
|
|
209
|
+
`;
|
|
210
|
+
return writeFile2(statusFile, line, { ...opts, append: true });
|
|
211
|
+
}
|
|
212
|
+
async function completePhase(statusFile, phase, opts) {
|
|
213
|
+
const now = new Date().toISOString();
|
|
214
|
+
const line = `phase.${phase}.status=complete
|
|
215
|
+
phase.${phase}.completed_at=${now}
|
|
216
|
+
`;
|
|
217
|
+
return writeFile2(statusFile, line, { ...opts, append: true });
|
|
218
|
+
}
|
|
219
|
+
async function failPhase(statusFile, phase, error, opts) {
|
|
220
|
+
const now = new Date().toISOString();
|
|
221
|
+
const safeError = error.replace(/\n/g, " ");
|
|
222
|
+
const line = `phase.${phase}.status=failed
|
|
223
|
+
phase.${phase}.completed_at=${now}
|
|
224
|
+
phase.${phase}.error=${safeError}
|
|
225
|
+
`;
|
|
226
|
+
return writeFile2(statusFile, line, { ...opts, append: true });
|
|
227
|
+
}
|
|
228
|
+
async function completeBootstrap(statusFile, opts) {
|
|
229
|
+
const now = new Date().toISOString();
|
|
230
|
+
const line = `status=complete
|
|
231
|
+
completed_at=${now}
|
|
232
|
+
`;
|
|
233
|
+
return writeFile2(statusFile, line, { ...opts, append: true });
|
|
234
|
+
}
|
|
235
|
+
async function failBootstrap(statusFile, error, opts) {
|
|
236
|
+
const now = new Date().toISOString();
|
|
237
|
+
const safeError = error.replace(/\n/g, " ");
|
|
238
|
+
const line = `status=failed
|
|
239
|
+
completed_at=${now}
|
|
240
|
+
error=${safeError}
|
|
241
|
+
`;
|
|
242
|
+
return writeFile2(statusFile, line, { ...opts, append: true });
|
|
243
|
+
}
|
|
244
|
+
async function checkMarker(markerPath, opts) {
|
|
245
|
+
const result = await exec2(["test", "-f", markerPath, "&&", "echo", "exists"], {
|
|
246
|
+
...opts,
|
|
247
|
+
quiet: true
|
|
248
|
+
});
|
|
249
|
+
return result.ok && result.stdout.trim() === "exists";
|
|
250
|
+
}
|
|
251
|
+
async function setMarker(markerPath, opts) {
|
|
252
|
+
return exec2(["touch", markerPath], opts);
|
|
253
|
+
}
|
|
254
|
+
async function removeMarker(markerPath, opts) {
|
|
255
|
+
return exec2(["rm", "-f", markerPath], opts);
|
|
256
|
+
}
|
|
257
|
+
async function waitForBootstrap(statusFile, opts = {}) {
|
|
258
|
+
const maxAttempts = opts.maxAttempts ?? 30;
|
|
259
|
+
const intervalMs = opts.intervalMs ?? 2000;
|
|
260
|
+
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
261
|
+
const status = await getBootstrapStatus(statusFile, opts);
|
|
262
|
+
if (status.status === "complete") {
|
|
263
|
+
return { completed: true, status };
|
|
199
264
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
await sleep(intervalMs);
|
|
265
|
+
if (status.status === "failed") {
|
|
266
|
+
return { completed: false, status };
|
|
267
|
+
}
|
|
268
|
+
opts.onProgress?.(attempt, status);
|
|
269
|
+
await sleep(intervalMs);
|
|
270
|
+
}
|
|
271
|
+
const finalStatus = await getBootstrapStatus(statusFile, opts);
|
|
272
|
+
return { completed: false, status: finalStatus };
|
|
273
|
+
}
|
|
274
|
+
async function waitForMarker(markerPath, opts = {}) {
|
|
275
|
+
const maxAttempts = opts.maxAttempts ?? 30;
|
|
276
|
+
const intervalMs = opts.intervalMs ?? 2000;
|
|
277
|
+
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
278
|
+
const exists = await checkMarker(markerPath, opts);
|
|
279
|
+
if (exists) {
|
|
280
|
+
return { exists: true, timedOut: false };
|
|
217
281
|
}
|
|
218
|
-
|
|
282
|
+
opts.onProgress?.(attempt, { status: "running" });
|
|
283
|
+
await sleep(intervalMs);
|
|
284
|
+
}
|
|
285
|
+
return { exists: false, timedOut: true };
|
|
219
286
|
}
|
|
220
|
-
// ---------------------------------------------------------------------------
|
|
221
|
-
// Utilities
|
|
222
|
-
// ---------------------------------------------------------------------------
|
|
223
287
|
function sleep(ms) {
|
|
224
|
-
|
|
288
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
225
289
|
}
|
|
290
|
+
export {
|
|
291
|
+
waitForMarker,
|
|
292
|
+
waitForBootstrap,
|
|
293
|
+
startPhase,
|
|
294
|
+
setMarker,
|
|
295
|
+
removeMarker,
|
|
296
|
+
parseBootstrapStatus,
|
|
297
|
+
initBootstrap,
|
|
298
|
+
getBootstrapStatus,
|
|
299
|
+
failPhase,
|
|
300
|
+
failBootstrap,
|
|
301
|
+
completePhase,
|
|
302
|
+
completeBootstrap,
|
|
303
|
+
checkMarker
|
|
304
|
+
};
|
package/dist/clone.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clone.d.ts","sourceRoot":"","sources":["../clone.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,wDAAwD;IACxD,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA+BpE"}
|