@highstate/common 0.9.16 → 0.9.18
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/{chunk-HZBJ6LLS.js → chunk-YYNV3MVT.js} +211 -127
- package/dist/chunk-YYNV3MVT.js.map +1 -0
- package/dist/highstate.manifest.json +8 -8
- package/dist/index.js +1 -1
- package/dist/units/dns/record-set/index.js +1 -1
- package/dist/units/dns/record-set/index.js.map +1 -1
- package/dist/units/existing-server/index.js +11 -11
- package/dist/units/existing-server/index.js.map +1 -1
- package/dist/units/network/l3-endpoint/index.js +1 -1
- package/dist/units/network/l3-endpoint/index.js.map +1 -1
- package/dist/units/network/l4-endpoint/index.js +1 -1
- package/dist/units/network/l4-endpoint/index.js.map +1 -1
- package/dist/units/script/index.js +1 -1
- package/dist/units/script/index.js.map +1 -1
- package/dist/units/server-dns/index.js +1 -1
- package/dist/units/server-dns/index.js.map +1 -1
- package/dist/units/server-patch/index.js +1 -1
- package/dist/units/server-patch/index.js.map +1 -1
- package/dist/units/ssh/key-pair/index.js +4 -5
- package/dist/units/ssh/key-pair/index.js.map +1 -1
- package/package.json +6 -6
- package/src/shared/command.ts +245 -71
- package/src/shared/files.ts +11 -16
- package/src/shared/network.ts +2 -2
- package/src/shared/passwords.ts +38 -2
- package/src/shared/ssh.ts +215 -65
- package/src/units/existing-server/index.ts +11 -10
- package/src/units/ssh/key-pair/index.ts +4 -5
- package/dist/chunk-HZBJ6LLS.js.map +0 -1
@@ -1,12 +1,12 @@
|
|
1
|
-
import { toPromise, output, ComponentResource, interpolate, normalize, secret,
|
2
|
-
import { uniqueBy, capitalize, groupBy } from 'remeda';
|
1
|
+
import { toPromise, output, ComponentResource, interpolate, normalize, secret, ensureSecretValue, asset } from '@highstate/pulumi';
|
2
|
+
import { uniqueBy, flat, capitalize, groupBy } from 'remeda';
|
3
|
+
import { homedir, tmpdir } from 'node:os';
|
3
4
|
import { local, remote } from '@pulumi/command';
|
4
5
|
import '@highstate/library';
|
5
|
-
import { randomBytes } from '@noble/hashes/utils';
|
6
|
+
import { randomBytes, bytesToHex } from '@noble/hashes/utils';
|
6
7
|
import { secureMask } from 'micro-key-producer/password.js';
|
7
8
|
import getKeys, { PrivateExport } from 'micro-key-producer/ssh.js';
|
8
9
|
import { randomBytes as randomBytes$1 } from 'micro-key-producer/utils.js';
|
9
|
-
import { tmpdir } from 'node:os';
|
10
10
|
import { mkdtemp, writeFile, cp, rm, stat, rename, mkdir } from 'node:fs/promises';
|
11
11
|
import { join, dirname, basename, extname } from 'node:path';
|
12
12
|
import { createReadStream } from 'node:fs';
|
@@ -123,7 +123,7 @@ async function requireInputL4Endpoint(rawEndpoint, inputEndpoint) {
|
|
123
123
|
}
|
124
124
|
throw new Error("No endpoint provided");
|
125
125
|
}
|
126
|
-
function
|
126
|
+
function l3EndpointToL4(l3Endpoint, port, protocol = "tcp") {
|
127
127
|
return {
|
128
128
|
...parseL3Endpoint(l3Endpoint),
|
129
129
|
port,
|
@@ -216,74 +216,124 @@ function createCommand(command) {
|
|
216
216
|
}
|
217
217
|
return command;
|
218
218
|
}
|
219
|
+
function wrapWithWorkDir(dir) {
|
220
|
+
if (!dir) {
|
221
|
+
return (command) => output(command);
|
222
|
+
}
|
223
|
+
return (command) => interpolate`cd "${dir}" && ${command}`;
|
224
|
+
}
|
225
|
+
function wrapWithWaitFor(timeout = 300, interval = 5) {
|
226
|
+
return (command) => (
|
227
|
+
// TOD: escape the command
|
228
|
+
interpolate`timeout ${timeout} bash -c 'while ! ${createCommand(command)}; do sleep ${interval}; done'`
|
229
|
+
);
|
230
|
+
}
|
219
231
|
var Command = class _Command extends ComponentResource {
|
220
|
-
command;
|
221
232
|
stdout;
|
222
233
|
stderr;
|
223
234
|
constructor(name, args, opts) {
|
224
235
|
super("highstate:common:Command", name, args, opts);
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
236
|
+
const command = args.host === "local" ? new local.Command(
|
237
|
+
name,
|
238
|
+
{
|
239
|
+
create: output(args.create).apply(createCommand),
|
240
|
+
update: args.update ? output(args.update).apply(createCommand) : void 0,
|
241
|
+
delete: args.delete ? output(args.delete).apply(createCommand) : void 0,
|
242
|
+
logging: args.logging,
|
243
|
+
triggers: args.triggers ? output(args.triggers).apply(flat) : void 0,
|
244
|
+
dir: args.cwd ?? homedir(),
|
245
|
+
environment: args.environment,
|
246
|
+
stdin: args.stdin
|
247
|
+
},
|
248
|
+
{ ...opts, parent: this }
|
249
|
+
) : new remote.Command(
|
250
|
+
name,
|
251
|
+
{
|
252
|
+
connection: output(args.host).apply((server) => {
|
253
|
+
if ("host" in server) {
|
254
|
+
return output(server);
|
255
|
+
}
|
256
|
+
if (!server.ssh) {
|
257
|
+
throw new Error(`The server "${server.hostname}" has no SSH credentials`);
|
258
|
+
}
|
259
|
+
return getServerConnection(server.ssh);
|
260
|
+
}),
|
261
|
+
create: output(args.create).apply(createCommand).apply(wrapWithWorkDir(args.cwd)),
|
262
|
+
update: args.update ? output(args.update).apply(createCommand).apply(wrapWithWorkDir(args.cwd)) : void 0,
|
263
|
+
delete: args.delete ? output(args.delete).apply(createCommand).apply(wrapWithWorkDir(args.cwd)) : void 0,
|
264
|
+
logging: args.logging,
|
265
|
+
triggers: args.triggers ? output(args.triggers).apply(flat) : void 0,
|
266
|
+
stdin: args.stdin,
|
267
|
+
environment: args.environment
|
268
|
+
},
|
269
|
+
{ ...opts, parent: this }
|
270
|
+
);
|
271
|
+
this.stdout = command.stdout;
|
272
|
+
this.stderr = command.stderr;
|
258
273
|
}
|
274
|
+
/**
|
275
|
+
* Waits for the command to complete and returns its output.
|
276
|
+
* The standard output will be returned.
|
277
|
+
*/
|
278
|
+
async wait() {
|
279
|
+
return await toPromise(this.stdout);
|
280
|
+
}
|
281
|
+
/**
|
282
|
+
* Creates a command that writes the given content to a file on the host.
|
283
|
+
* The file will be created if it does not exist, and overwritten if it does.
|
284
|
+
*
|
285
|
+
* Use for small text files like configuration files.
|
286
|
+
*/
|
259
287
|
static createTextFile(name, options, opts) {
|
260
|
-
return
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
{
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
);
|
271
|
-
return command;
|
272
|
-
});
|
288
|
+
return new _Command(
|
289
|
+
name,
|
290
|
+
{
|
291
|
+
host: options.host,
|
292
|
+
create: interpolate`mkdir -p $(dirname "${options.path}") && cat > ${options.path}`,
|
293
|
+
delete: interpolate`rm -rf ${options.path}`,
|
294
|
+
stdin: options.content
|
295
|
+
},
|
296
|
+
opts
|
297
|
+
);
|
273
298
|
}
|
299
|
+
/**
|
300
|
+
* Creates a command that waits for a file to be created and then reads its content.
|
301
|
+
* This is useful for waiting for a file to be generated by another process.
|
302
|
+
*
|
303
|
+
* Use for small text files like configuration files.
|
304
|
+
*/
|
274
305
|
static receiveTextFile(name, options, opts) {
|
275
|
-
return
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
306
|
+
return new _Command(
|
307
|
+
name,
|
308
|
+
{
|
309
|
+
host: options.host,
|
310
|
+
create: interpolate`while ! test -f "${options.path}"; do sleep 1; done; cat "${options.path}"`,
|
311
|
+
logging: "stderr"
|
312
|
+
},
|
313
|
+
opts
|
314
|
+
);
|
315
|
+
}
|
316
|
+
/**
|
317
|
+
* Creates a command that waits for a condition to be met.
|
318
|
+
* The command will run until the condition is met or the timeout is reached.
|
319
|
+
*
|
320
|
+
* The condition is considered met if the command returns a zero exit code.
|
321
|
+
*
|
322
|
+
* @param name The name of the command resource.
|
323
|
+
* @param args The arguments for the command, including the condition to check.
|
324
|
+
* @param opts Optional resource options.
|
325
|
+
*/
|
326
|
+
static waitFor(name, args, opts) {
|
327
|
+
return new _Command(
|
328
|
+
name,
|
329
|
+
{
|
330
|
+
...args,
|
331
|
+
create: output(args.create).apply(wrapWithWaitFor(args.timeout, args.interval)),
|
332
|
+
update: args.update ? output(args.update).apply(wrapWithWaitFor(args.timeout, args.interval)) : void 0,
|
333
|
+
delete: args.delete ? output(args.delete).apply(wrapWithWaitFor(args.timeout, args.interval)) : void 0
|
334
|
+
},
|
335
|
+
opts
|
336
|
+
);
|
287
337
|
}
|
288
338
|
};
|
289
339
|
function getTypeByEndpoint(endpoint) {
|
@@ -435,6 +485,16 @@ async function updateEndpointsWithFqdn(endpoints, fqdn, fqdnEndpointFilter, patc
|
|
435
485
|
function generatePassword() {
|
436
486
|
return secureMask.apply(randomBytes(32)).password;
|
437
487
|
}
|
488
|
+
function generateKey(format = "hex") {
|
489
|
+
const bytes = randomBytes(32);
|
490
|
+
if (format === "raw") {
|
491
|
+
return bytes;
|
492
|
+
}
|
493
|
+
if (format === "base64") {
|
494
|
+
return Buffer.from(bytes).toString("base64");
|
495
|
+
}
|
496
|
+
return bytesToHex(bytes);
|
497
|
+
}
|
438
498
|
|
439
499
|
// assets/images.json
|
440
500
|
var terminal_ssh = {
|
@@ -494,11 +554,11 @@ function createSshTerminal(credentials) {
|
|
494
554
|
};
|
495
555
|
});
|
496
556
|
}
|
497
|
-
function
|
557
|
+
function generateSshPrivateKey() {
|
498
558
|
const seed = randomBytes$1(32);
|
499
559
|
return getKeys(seed).privateKey;
|
500
560
|
}
|
501
|
-
function
|
561
|
+
function sshPrivateKeyToKeyPair(privateKeyString) {
|
502
562
|
return output(privateKeyString).apply((privateKeyString2) => {
|
503
563
|
const privateKeyStruct = PrivateExport.decode(privateKeyString2);
|
504
564
|
const privKey = privateKeyStruct.keys[0].privKey.privKey;
|
@@ -511,60 +571,88 @@ function privateKeyToKeyPair(privateKeyString) {
|
|
511
571
|
});
|
512
572
|
});
|
513
573
|
}
|
514
|
-
function
|
515
|
-
if (
|
516
|
-
return output(
|
574
|
+
function ensureSshKeyPair(privateKey, existingKeyPair) {
|
575
|
+
if (existingKeyPair) {
|
576
|
+
return output(existingKeyPair);
|
517
577
|
}
|
518
|
-
|
519
|
-
return privateKey.apply(privateKeyToKeyPair);
|
578
|
+
return ensureSecretValue(privateKey, generateSshPrivateKey).value.apply(sshPrivateKeyToKeyPair);
|
520
579
|
}
|
521
|
-
function createServerEntity(
|
580
|
+
async function createServerEntity({
|
581
|
+
name,
|
582
|
+
fallbackHostname,
|
583
|
+
endpoints,
|
584
|
+
sshEndpoint,
|
585
|
+
sshPort = 22,
|
586
|
+
sshUser = "root",
|
587
|
+
sshPassword,
|
588
|
+
sshPrivateKey,
|
589
|
+
hasSsh = true,
|
590
|
+
pingInterval,
|
591
|
+
pingTimeout,
|
592
|
+
waitForPing,
|
593
|
+
waitForSsh,
|
594
|
+
sshCheckInterval,
|
595
|
+
sshCheckTimeout
|
596
|
+
}) {
|
597
|
+
if (endpoints.length === 0) {
|
598
|
+
throw new Error("At least one L3 endpoint is required to create a server entity");
|
599
|
+
}
|
600
|
+
fallbackHostname ??= name;
|
601
|
+
waitForSsh ??= hasSsh;
|
602
|
+
waitForPing ??= !waitForSsh;
|
603
|
+
if (waitForPing) {
|
604
|
+
await Command.waitFor(`${name}.ping`, {
|
605
|
+
host: "local",
|
606
|
+
create: `ping -c 1 ${l3EndpointToString(endpoints[0])}`,
|
607
|
+
timeout: pingTimeout ?? 300,
|
608
|
+
interval: pingInterval ?? 5,
|
609
|
+
triggers: [Date.now()]
|
610
|
+
}).wait();
|
611
|
+
}
|
612
|
+
if (!hasSsh) {
|
613
|
+
return {
|
614
|
+
hostname: name,
|
615
|
+
endpoints
|
616
|
+
};
|
617
|
+
}
|
618
|
+
sshEndpoint ??= l3EndpointToL4(endpoints[0], sshPort);
|
619
|
+
if (waitForSsh) {
|
620
|
+
await Command.waitFor(`${name}.ssh`, {
|
621
|
+
host: "local",
|
622
|
+
create: `nc -zv ${l3EndpointToString(sshEndpoint)} ${sshPort}`,
|
623
|
+
timeout: sshCheckTimeout ?? 300,
|
624
|
+
interval: sshCheckInterval ?? 5,
|
625
|
+
triggers: [Date.now()]
|
626
|
+
}).wait();
|
627
|
+
}
|
522
628
|
const connection = output({
|
523
|
-
host: l3EndpointToString(
|
524
|
-
port:
|
629
|
+
host: l3EndpointToString(sshEndpoint),
|
630
|
+
port: sshEndpoint.port,
|
525
631
|
user: sshUser,
|
526
632
|
password: sshPassword,
|
527
633
|
privateKey: sshPrivateKey,
|
528
634
|
dialErrorLimit: 3
|
529
635
|
});
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
endpoints: [endpoint]
|
534
|
-
});
|
535
|
-
}
|
536
|
-
const command = new local.Command("check-ssh", {
|
537
|
-
create: `nc -zv ${l3EndpointToString(endpoint)} ${sshPort} && echo "up" || echo "down"`,
|
636
|
+
const hostnameResult = new remote.Command("hostname", {
|
637
|
+
connection,
|
638
|
+
create: "hostname",
|
538
639
|
triggers: [Date.now()]
|
539
640
|
});
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
641
|
+
const hostKeyResult = new remote.Command("host-key", {
|
642
|
+
connection,
|
643
|
+
create: "cat /etc/ssh/ssh_host_ed25519_key.pub",
|
644
|
+
triggers: [Date.now()]
|
645
|
+
});
|
646
|
+
return await toPromise({
|
647
|
+
endpoints,
|
648
|
+
hostname: hostnameResult.stdout.apply((x) => x.trim()),
|
649
|
+
ssh: {
|
650
|
+
endpoints: [sshEndpoint],
|
651
|
+
user: sshUser,
|
652
|
+
hostKey: hostKeyResult.stdout.apply((x) => x.trim()),
|
653
|
+
password: sshPassword,
|
654
|
+
keyPair: sshPrivateKey ? sshPrivateKeyToKeyPair(sshPrivateKey) : void 0
|
546
655
|
}
|
547
|
-
const hostnameResult = new remote.Command("hostname", {
|
548
|
-
connection,
|
549
|
-
create: "hostname",
|
550
|
-
triggers: [Date.now()]
|
551
|
-
});
|
552
|
-
const hostKeyResult = new remote.Command("host-key", {
|
553
|
-
connection,
|
554
|
-
create: "cat /etc/ssh/ssh_host_ed25519_key.pub",
|
555
|
-
triggers: [Date.now()]
|
556
|
-
});
|
557
|
-
return output({
|
558
|
-
endpoints: [endpoint],
|
559
|
-
hostname: hostnameResult.stdout.apply((x) => x.trim()),
|
560
|
-
ssh: {
|
561
|
-
endpoints: [l3ToL4Endpoint(endpoint, sshPort)],
|
562
|
-
user: sshUser,
|
563
|
-
hostKey: hostKeyResult.stdout.apply((x) => x.trim()),
|
564
|
-
password: sshPassword,
|
565
|
-
keyPair: sshPrivateKey ? privateKeyToKeyPair(sshPrivateKey) : void 0
|
566
|
-
}
|
567
|
-
});
|
568
656
|
});
|
569
657
|
}
|
570
658
|
function assetFromFile(file) {
|
@@ -673,21 +761,20 @@ var MaterializedFile = class _MaterializedFile {
|
|
673
761
|
break;
|
674
762
|
}
|
675
763
|
case "remote": {
|
676
|
-
const response = await
|
764
|
+
const response = await fetch(l7EndpointToString(this.entity.content.endpoint));
|
677
765
|
if (!response.ok) throw new Error(`Failed to fetch: ${response.statusText}`);
|
678
766
|
const arrayBuffer = await response.arrayBuffer();
|
679
767
|
await writeFile(this._path, Buffer.from(arrayBuffer), { mode: this.entity.meta.mode });
|
680
768
|
break;
|
681
769
|
}
|
682
770
|
case "artifact": {
|
683
|
-
const artifactData = this.entity.content[HighstateSignature.Artifact];
|
684
771
|
const artifactPath = process.env.HIGHSTATE_ARTIFACT_READ_PATH;
|
685
772
|
if (!artifactPath) {
|
686
773
|
throw new Error(
|
687
774
|
"HIGHSTATE_ARTIFACT_READ_PATH environment variable is not set but required for artifact content"
|
688
775
|
);
|
689
776
|
}
|
690
|
-
const tgzPath = join(artifactPath, `${
|
777
|
+
const tgzPath = join(artifactPath, `${this.entity.content.hash}.tgz`);
|
691
778
|
const readStream = createReadStream(tgzPath);
|
692
779
|
await unarchiveFromStream(readStream, dirname(this._path), "tar");
|
693
780
|
break;
|
@@ -751,10 +838,9 @@ var MaterializedFile = class _MaterializedFile {
|
|
751
838
|
meta: newMeta,
|
752
839
|
content: {
|
753
840
|
type: "artifact",
|
754
|
-
[HighstateSignature.Artifact]:
|
755
|
-
|
756
|
-
|
757
|
-
}
|
841
|
+
[HighstateSignature.Artifact]: true,
|
842
|
+
hash: hashValue,
|
843
|
+
meta: await toPromise(this.artifactMeta)
|
758
844
|
}
|
759
845
|
};
|
760
846
|
} finally {
|
@@ -853,7 +939,7 @@ var MaterializedFolder = class _MaterializedFolder {
|
|
853
939
|
break;
|
854
940
|
}
|
855
941
|
case "remote": {
|
856
|
-
const response = await
|
942
|
+
const response = await fetch(l7EndpointToString(this.entity.content.endpoint));
|
857
943
|
if (!response.ok) throw new Error(`Failed to fetch: ${response.statusText}`);
|
858
944
|
if (!response.body) throw new Error("Response body is empty");
|
859
945
|
const url = new URL(l7EndpointToString(this.entity.content.endpoint));
|
@@ -886,14 +972,13 @@ var MaterializedFolder = class _MaterializedFolder {
|
|
886
972
|
break;
|
887
973
|
}
|
888
974
|
case "artifact": {
|
889
|
-
const artifactData = this.entity.content[HighstateSignature.Artifact];
|
890
975
|
const artifactPath = process.env.HIGHSTATE_ARTIFACT_READ_PATH;
|
891
976
|
if (!artifactPath) {
|
892
977
|
throw new Error(
|
893
978
|
"HIGHSTATE_ARTIFACT_READ_PATH environment variable is not set but required for artifact content"
|
894
979
|
);
|
895
980
|
}
|
896
|
-
const tgzPath = join(artifactPath, `${
|
981
|
+
const tgzPath = join(artifactPath, `${this.entity.content.hash}.tgz`);
|
897
982
|
const readStream = createReadStream(tgzPath);
|
898
983
|
await unarchiveFromStream(readStream, dirname(this._path), "tar");
|
899
984
|
break;
|
@@ -972,11 +1057,10 @@ var MaterializedFolder = class _MaterializedFolder {
|
|
972
1057
|
return {
|
973
1058
|
meta: newMeta,
|
974
1059
|
content: {
|
1060
|
+
[HighstateSignature.Artifact]: true,
|
975
1061
|
type: "artifact",
|
976
|
-
|
977
|
-
|
978
|
-
meta: await toPromise(this.artifactMeta)
|
979
|
-
}
|
1062
|
+
hash: hashValue,
|
1063
|
+
meta: await toPromise(this.artifactMeta)
|
980
1064
|
}
|
981
1065
|
};
|
982
1066
|
} finally {
|
@@ -1033,7 +1117,7 @@ async function fetchFileSize(endpoint) {
|
|
1033
1117
|
);
|
1034
1118
|
}
|
1035
1119
|
const url = l7EndpointToString(endpoint);
|
1036
|
-
const response = await
|
1120
|
+
const response = await fetch(url, { method: "HEAD" });
|
1037
1121
|
if (!response.ok) {
|
1038
1122
|
throw new Error(`Failed to fetch file size: ${response.statusText}`);
|
1039
1123
|
}
|
@@ -1052,6 +1136,6 @@ function getNameByEndpoint(endpoint) {
|
|
1052
1136
|
return parsedEndpoint.resource ? basename(parsedEndpoint.resource) : "";
|
1053
1137
|
}
|
1054
1138
|
|
1055
|
-
export { Command, DnsRecord, DnsRecordSet, MaterializedFile, MaterializedFolder, archiveFromFolder, assetFromFile, createServerEntity, createSshTerminal, fetchFileSize, filterEndpoints, generatePassword,
|
1056
|
-
//# sourceMappingURL=chunk-
|
1057
|
-
//# sourceMappingURL=chunk-
|
1139
|
+
export { Command, DnsRecord, DnsRecordSet, MaterializedFile, MaterializedFolder, archiveFromFolder, assetFromFile, createServerEntity, createSshTerminal, ensureSshKeyPair, fetchFileSize, filterEndpoints, generateKey, generatePassword, generateSshPrivateKey, getNameByEndpoint, getServerConnection, l34EndpointToString, l3EndpointToCidr, l3EndpointToL4, l3EndpointToString, l4EndpointToString, l4EndpointWithProtocolToString, l7EndpointToString, parseL34Endpoint, parseL3Endpoint, parseL4Endpoint, parseL7Endpoint, requireInputL3Endpoint, requireInputL4Endpoint, sshPrivateKeyToKeyPair, updateEndpoints, updateEndpointsWithFqdn };
|
1140
|
+
//# sourceMappingURL=chunk-YYNV3MVT.js.map
|
1141
|
+
//# sourceMappingURL=chunk-YYNV3MVT.js.map
|