sst 2.5.4 → 2.5.6
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/cli/commands/bind.js +89 -64
- package/cli/commands/version.js +1 -0
- package/config.js +10 -10
- package/constructs/Api.d.ts +1 -1
- package/constructs/Api.js +1 -1
- package/constructs/ApiGatewayV1Api.d.ts +1 -1
- package/constructs/ApiGatewayV1Api.js +1 -1
- package/constructs/AppSyncApi.d.ts +1 -1
- package/constructs/AppSyncApi.js +1 -1
- package/constructs/Auth.d.ts +1 -1
- package/constructs/Auth.js +1 -1
- package/constructs/Bucket.d.ts +1 -1
- package/constructs/Bucket.js +1 -1
- package/constructs/Cognito.d.ts +1 -1
- package/constructs/Cognito.js +1 -1
- package/constructs/Cron.d.ts +1 -1
- package/constructs/Cron.js +1 -1
- package/constructs/EventBus.d.ts +1 -1
- package/constructs/EventBus.js +1 -1
- package/constructs/Function.d.ts +1 -1
- package/constructs/Function.js +1 -1
- package/constructs/Job.d.ts +38 -2
- package/constructs/Job.js +4 -2
- package/constructs/KinesisStream.d.ts +1 -1
- package/constructs/KinesisStream.js +1 -1
- package/constructs/Parameter.d.ts +1 -1
- package/constructs/Parameter.js +1 -1
- package/constructs/Queue.d.ts +1 -1
- package/constructs/Queue.js +1 -1
- package/constructs/RDS.d.ts +2 -2
- package/constructs/RDS.js +1 -1
- package/constructs/Script.d.ts +2 -2
- package/constructs/Script.js +1 -1
- package/constructs/Secret.d.ts +1 -1
- package/constructs/Secret.js +1 -1
- package/constructs/SsrSite.js +5 -1
- package/constructs/Stack.d.ts +1 -1
- package/constructs/Stack.js +1 -1
- package/constructs/StaticSite.d.ts +1 -1
- package/constructs/StaticSite.js +1 -1
- package/constructs/Table.d.ts +1 -1
- package/constructs/Table.js +1 -1
- package/constructs/Topic.d.ts +1 -1
- package/constructs/Topic.js +1 -1
- package/constructs/WebSocketApi.d.ts +1 -1
- package/constructs/WebSocketApi.js +1 -1
- package/constructs/future/Auth.d.ts +1 -1
- package/constructs/future/Auth.js +1 -1
- package/package.json +1 -1
- package/project.d.ts +7 -0
- package/project.js +35 -8
- package/sst.mjs +126 -82
- package/support/bootstrap-metadata-function/index.mjs +238 -238
- package/support/custom-resources/index.mjs +238 -238
package/sst.mjs
CHANGED
|
@@ -317,6 +317,7 @@ var init_build = __esm({
|
|
|
317
317
|
var project_exports = {};
|
|
318
318
|
__export(project_exports, {
|
|
319
319
|
ProjectContext: () => ProjectContext,
|
|
320
|
+
exportedForTesting: () => exportedForTesting,
|
|
320
321
|
initProject: () => initProject,
|
|
321
322
|
useProject: () => useProject
|
|
322
323
|
});
|
|
@@ -353,14 +354,18 @@ async function initProject(globals) {
|
|
|
353
354
|
}();
|
|
354
355
|
const config = await Promise.resolve(sstConfig.config(globals));
|
|
355
356
|
const stage = globals.stage || config.stage || await usePersonalStage(out) || await promptPersonalStage(out);
|
|
356
|
-
const [version2, cdkVersion] = await (async () => {
|
|
357
|
+
const [version2, cdkVersion, constructsVersion] = await (async () => {
|
|
357
358
|
try {
|
|
358
359
|
const packageJson = JSON.parse(
|
|
359
360
|
await fs4.readFile(
|
|
360
361
|
url2.fileURLToPath(new URL("./package.json", import.meta.url))
|
|
361
362
|
).then((x) => x.toString())
|
|
362
363
|
);
|
|
363
|
-
return [
|
|
364
|
+
return [
|
|
365
|
+
packageJson.version,
|
|
366
|
+
packageJson.dependencies["aws-cdk-lib"],
|
|
367
|
+
packageJson.dependencies["constructs"]
|
|
368
|
+
];
|
|
364
369
|
} catch {
|
|
365
370
|
return ["unknown", "unknown"];
|
|
366
371
|
}
|
|
@@ -368,6 +373,7 @@ async function initProject(globals) {
|
|
|
368
373
|
const project = {
|
|
369
374
|
version: version2,
|
|
370
375
|
cdkVersion,
|
|
376
|
+
constructsVersion,
|
|
371
377
|
config: {
|
|
372
378
|
...config,
|
|
373
379
|
stage,
|
|
@@ -415,26 +421,29 @@ async function usePersonalStage(out) {
|
|
|
415
421
|
return;
|
|
416
422
|
}
|
|
417
423
|
}
|
|
418
|
-
async function promptPersonalStage(out) {
|
|
424
|
+
async function promptPersonalStage(out, isRetry) {
|
|
419
425
|
const readline = await import("readline");
|
|
420
426
|
const rl = readline.createInterface({
|
|
421
427
|
input: process.stdin,
|
|
422
428
|
output: process.stdout
|
|
423
429
|
});
|
|
424
|
-
|
|
425
|
-
const suggested = os.userInfo().username;
|
|
430
|
+
const stage = await new Promise((resolve) => {
|
|
431
|
+
const suggested = sanitizeStageName(os.userInfo().username) || "local";
|
|
432
|
+
const instruction = !isRetry ? `Please enter a name you\u2019d like to use for your personal stage.` : `Please enter a name that starts with a letter, followed by letters, numbers, or hyphens.`;
|
|
426
433
|
rl.question(
|
|
427
|
-
|
|
428
|
-
suggested
|
|
429
|
-
)}: `,
|
|
434
|
+
`${instruction} Or hit enter to use ${blue(suggested)}: `,
|
|
430
435
|
async (input) => {
|
|
431
436
|
rl.close();
|
|
432
|
-
const result = input
|
|
433
|
-
await fs4.writeFile(path4.join(out, "stage"), result);
|
|
437
|
+
const result = input === "" ? suggested : input;
|
|
434
438
|
resolve(result);
|
|
435
439
|
}
|
|
436
440
|
);
|
|
437
441
|
});
|
|
442
|
+
if (isValidStageName(stage)) {
|
|
443
|
+
await fs4.writeFile(path4.join(out, "stage"), stage);
|
|
444
|
+
return stage;
|
|
445
|
+
}
|
|
446
|
+
return await promptPersonalStage(out, true);
|
|
438
447
|
}
|
|
439
448
|
async function findRoot() {
|
|
440
449
|
async function find2(dir) {
|
|
@@ -455,7 +464,13 @@ async function findRoot() {
|
|
|
455
464
|
const result = await find2(process.cwd());
|
|
456
465
|
return result;
|
|
457
466
|
}
|
|
458
|
-
|
|
467
|
+
function sanitizeStageName(stage) {
|
|
468
|
+
return stage.replace(/[^A-Za-z0-9-]/g, "-").replace(/--+/g, "-").replace(/^[^A-Za-z]/, "").replace(/-$/, "") || "local";
|
|
469
|
+
}
|
|
470
|
+
function isValidStageName(stage) {
|
|
471
|
+
return Boolean(stage.match(/^[A-Za-z][A-Za-z0-9-]*$/));
|
|
472
|
+
}
|
|
473
|
+
var ProjectContext, CONFIG_EXTENSIONS, exportedForTesting;
|
|
459
474
|
var init_project = __esm({
|
|
460
475
|
"src/project.ts"() {
|
|
461
476
|
"use strict";
|
|
@@ -472,6 +487,10 @@ var init_project = __esm({
|
|
|
472
487
|
".config.mjs",
|
|
473
488
|
".config.js"
|
|
474
489
|
];
|
|
490
|
+
exportedForTesting = {
|
|
491
|
+
sanitizeStageName,
|
|
492
|
+
isValidStageName
|
|
493
|
+
};
|
|
475
494
|
}
|
|
476
495
|
});
|
|
477
496
|
|
|
@@ -6518,12 +6537,12 @@ async function* scan(prefix) {
|
|
|
6518
6537
|
token = results.NextToken;
|
|
6519
6538
|
}
|
|
6520
6539
|
}
|
|
6521
|
-
function parse(ssmName) {
|
|
6522
|
-
const parts = ssmName.split("/");
|
|
6540
|
+
function parse(ssmName, prefix) {
|
|
6541
|
+
const parts = ssmName.substring(prefix.length).split("/");
|
|
6523
6542
|
return {
|
|
6524
|
-
type: parts[
|
|
6525
|
-
id: parts[
|
|
6526
|
-
prop: parts.slice(
|
|
6543
|
+
type: parts[0],
|
|
6544
|
+
id: parts[1],
|
|
6545
|
+
prop: parts.slice(2).join("/")
|
|
6527
6546
|
};
|
|
6528
6547
|
}
|
|
6529
6548
|
async function restartFunction(arn) {
|
|
@@ -6564,7 +6583,7 @@ var init_config = __esm({
|
|
|
6564
6583
|
async function parameters() {
|
|
6565
6584
|
const result = [];
|
|
6566
6585
|
for await (const p of scan(PREFIX.FALLBACK)) {
|
|
6567
|
-
const parsed = parse(p.Name);
|
|
6586
|
+
const parsed = parse(p.Name, PREFIX.FALLBACK);
|
|
6568
6587
|
if (parsed.type === "secrets")
|
|
6569
6588
|
continue;
|
|
6570
6589
|
result.push({
|
|
@@ -6573,7 +6592,7 @@ var init_config = __esm({
|
|
|
6573
6592
|
});
|
|
6574
6593
|
}
|
|
6575
6594
|
for await (const p of scan(PREFIX.STAGE)) {
|
|
6576
|
-
const parsed = parse(p.Name);
|
|
6595
|
+
const parsed = parse(p.Name, PREFIX.STAGE);
|
|
6577
6596
|
if (parsed.type === "secrets")
|
|
6578
6597
|
continue;
|
|
6579
6598
|
result.push({
|
|
@@ -6599,13 +6618,13 @@ var init_config = __esm({
|
|
|
6599
6618
|
async function secrets2() {
|
|
6600
6619
|
const result = {};
|
|
6601
6620
|
for await (const p of scan(PREFIX.STAGE + "Secret")) {
|
|
6602
|
-
const parsed = parse(p.Name);
|
|
6621
|
+
const parsed = parse(p.Name, PREFIX.STAGE);
|
|
6603
6622
|
if (!result[parsed.id])
|
|
6604
6623
|
result[parsed.id] = {};
|
|
6605
6624
|
result[parsed.id].value = p.Value;
|
|
6606
6625
|
}
|
|
6607
6626
|
for await (const p of scan(PREFIX.FALLBACK + "Secret")) {
|
|
6608
|
-
const parsed = parse(p.Name);
|
|
6627
|
+
const parsed = parse(p.Name, PREFIX.FALLBACK);
|
|
6609
6628
|
if (!result[parsed.id])
|
|
6610
6629
|
result[parsed.id] = {};
|
|
6611
6630
|
result[parsed.id].fallback = p.Value;
|
|
@@ -6716,7 +6735,7 @@ var init_config = __esm({
|
|
|
6716
6735
|
PREFIX = {
|
|
6717
6736
|
get STAGE() {
|
|
6718
6737
|
const project = useProject();
|
|
6719
|
-
return
|
|
6738
|
+
return project.config.ssmPrefix;
|
|
6720
6739
|
},
|
|
6721
6740
|
get FALLBACK() {
|
|
6722
6741
|
const project = useProject();
|
|
@@ -7113,12 +7132,7 @@ Are you sure you want to run this stage in dev mode? [y/N] `,
|
|
|
7113
7132
|
// src/cli/commands/bind.ts
|
|
7114
7133
|
init_error();
|
|
7115
7134
|
import path18 from "path";
|
|
7116
|
-
var
|
|
7117
|
-
NextjsSite: "next.config",
|
|
7118
|
-
AstroSite: "astro.config",
|
|
7119
|
-
RemixSite: "remix.config",
|
|
7120
|
-
SolidStartSite: "vite.config",
|
|
7121
|
-
SlsNextjsSite: "next.config"
|
|
7135
|
+
var OutdatedMetadataError = class extends Error {
|
|
7122
7136
|
};
|
|
7123
7137
|
var bind = (program2) => program2.command(
|
|
7124
7138
|
["bind <command..>", "env <command..>"],
|
|
@@ -7147,21 +7161,32 @@ var bind = (program2) => program2.command(
|
|
|
7147
7161
|
const bus = useBus2();
|
|
7148
7162
|
const project = useProject2();
|
|
7149
7163
|
const command = args.command?.join(" ");
|
|
7150
|
-
const
|
|
7164
|
+
const isSite = await isRunningInSite();
|
|
7151
7165
|
let p;
|
|
7152
7166
|
let timer;
|
|
7153
7167
|
let siteConfigCache;
|
|
7154
7168
|
if (!command) {
|
|
7155
7169
|
throw new VisibleError(
|
|
7156
|
-
`Command is required, e.g. sst bind ${
|
|
7170
|
+
`Command is required, e.g. sst bind ${isSite ? "next dev" : "vitest run"}`
|
|
7157
7171
|
);
|
|
7158
7172
|
}
|
|
7159
|
-
|
|
7160
|
-
if (!initialMetadata && !isSsrSite) {
|
|
7173
|
+
if (!isSite) {
|
|
7161
7174
|
Logger2.debug("Running in script mode.");
|
|
7162
7175
|
return await bindScript();
|
|
7163
7176
|
}
|
|
7164
|
-
|
|
7177
|
+
try {
|
|
7178
|
+
await bindSite("init");
|
|
7179
|
+
} catch (e) {
|
|
7180
|
+
if (e instanceof OutdatedMetadataError) {
|
|
7181
|
+
Colors2.line(
|
|
7182
|
+
Colors2.warning(
|
|
7183
|
+
"Warning: This was deployed with an old version of SST. Run `sst dev` or `sst deploy` to update."
|
|
7184
|
+
)
|
|
7185
|
+
);
|
|
7186
|
+
return await bindScript();
|
|
7187
|
+
}
|
|
7188
|
+
throw e;
|
|
7189
|
+
}
|
|
7165
7190
|
bus.subscribe(
|
|
7166
7191
|
"stacks.metadata.updated",
|
|
7167
7192
|
() => bindSite("metadata_updated")
|
|
@@ -7172,9 +7197,7 @@ var bind = (program2) => program2.command(
|
|
|
7172
7197
|
);
|
|
7173
7198
|
bus.subscribe("config.secret.updated", (payload) => {
|
|
7174
7199
|
const secretName = payload.properties.name;
|
|
7175
|
-
if (siteConfigCache?.secrets
|
|
7176
|
-
return;
|
|
7177
|
-
if (!siteConfigCache.secrets.includes(secretName))
|
|
7200
|
+
if (!(siteConfigCache?.secrets || []).includes(secretName))
|
|
7178
7201
|
return;
|
|
7179
7202
|
Colors2.line(
|
|
7180
7203
|
`
|
|
@@ -7183,26 +7206,47 @@ var bind = (program2) => program2.command(
|
|
|
7183
7206
|
);
|
|
7184
7207
|
bindSite("secrets_updated");
|
|
7185
7208
|
});
|
|
7186
|
-
async function
|
|
7209
|
+
async function isRunningInSite() {
|
|
7187
7210
|
const { existsAsync: existsAsync3 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
7188
7211
|
const { readFile } = await import("fs/promises");
|
|
7212
|
+
const SITE_CONFIGS = [
|
|
7213
|
+
{ file: "next.config", multiExtension: true },
|
|
7214
|
+
{ file: "astro.config", multiExtension: true },
|
|
7215
|
+
{ file: "remix.config", multiExtension: true },
|
|
7216
|
+
{ file: "svelte.config", multiExtension: true },
|
|
7217
|
+
{ file: "gatsby-config", multiExtension: true },
|
|
7218
|
+
{ file: "angular.json" },
|
|
7219
|
+
{ file: "ember-cli-build.js" },
|
|
7220
|
+
{
|
|
7221
|
+
file: "vite.config",
|
|
7222
|
+
multiExtension: true,
|
|
7223
|
+
match: /solid-start|plugin-vue|plugin-react|@preact\/preset-vite/
|
|
7224
|
+
},
|
|
7225
|
+
{ file: "package.json", match: /react-scripts/ },
|
|
7226
|
+
{ file: "index.html" }
|
|
7227
|
+
];
|
|
7189
7228
|
const results = await Promise.all(
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7229
|
+
SITE_CONFIGS.map((site) => {
|
|
7230
|
+
const files = site.multiExtension ? [".js", ".cjs", ".mjs", ".ts"].map(
|
|
7231
|
+
(ext) => `${site.file}${ext}`
|
|
7232
|
+
) : [site.file];
|
|
7233
|
+
return files.map(async (file) => {
|
|
7234
|
+
const exists = await existsAsync3(file);
|
|
7235
|
+
if (!exists)
|
|
7236
|
+
return false;
|
|
7237
|
+
if (site.match) {
|
|
7238
|
+
const content = await readFile(file);
|
|
7239
|
+
return content.toString().match(site.match);
|
|
7196
7240
|
}
|
|
7197
|
-
return
|
|
7198
|
-
})
|
|
7199
|
-
).flat()
|
|
7241
|
+
return true;
|
|
7242
|
+
});
|
|
7243
|
+
}).flat()
|
|
7200
7244
|
);
|
|
7201
7245
|
return results.some(Boolean);
|
|
7202
7246
|
}
|
|
7203
7247
|
async function bindSite(reason) {
|
|
7204
|
-
const siteMetadata =
|
|
7205
|
-
const siteConfig = await
|
|
7248
|
+
const siteMetadata = await getSiteMetadataUntilAvailable();
|
|
7249
|
+
const siteConfig = await parseSiteMetadata(siteMetadata);
|
|
7206
7250
|
if (reason === "metadata_updated") {
|
|
7207
7251
|
if (areEnvsSame(siteConfig.envs, siteConfigCache?.envs || {}))
|
|
7208
7252
|
return;
|
|
@@ -7247,42 +7291,13 @@ var bind = (program2) => program2.command(
|
|
|
7247
7291
|
...await localIamCredentials()
|
|
7248
7292
|
});
|
|
7249
7293
|
}
|
|
7250
|
-
async function parseSiteConfig(metadata3) {
|
|
7251
|
-
const { LambdaClient: LambdaClient2, GetFunctionCommand } = await import("@aws-sdk/client-lambda");
|
|
7252
|
-
const { useAWSClient: useAWSClient2 } = await Promise.resolve().then(() => (init_credentials(), credentials_exports));
|
|
7253
|
-
const isBindSupported = metadata3.type !== "StaticSite" && metadata3.type !== "SlsNextjsSite";
|
|
7254
|
-
if (!isBindSupported) {
|
|
7255
|
-
return { envs: metadata3.data.environment };
|
|
7256
|
-
}
|
|
7257
|
-
const lambda = useAWSClient2(LambdaClient2);
|
|
7258
|
-
const { Configuration: functionConfig } = await lambda.send(
|
|
7259
|
-
new GetFunctionCommand({
|
|
7260
|
-
FunctionName: metadata3.data.server
|
|
7261
|
-
})
|
|
7262
|
-
);
|
|
7263
|
-
return {
|
|
7264
|
-
role: functionConfig?.Role,
|
|
7265
|
-
envs: functionConfig?.Environment?.Variables || {},
|
|
7266
|
-
secrets: metadata3.data.secrets
|
|
7267
|
-
};
|
|
7268
|
-
}
|
|
7269
7294
|
async function getSiteMetadataUntilAvailable() {
|
|
7270
7295
|
const { createSpinner: createSpinner2 } = await Promise.resolve().then(() => (init_spinner(), spinner_exports));
|
|
7271
7296
|
const spinner = createSpinner2({});
|
|
7272
7297
|
while (true) {
|
|
7273
7298
|
const data2 = await getSiteMetadata();
|
|
7274
7299
|
if (!data2) {
|
|
7275
|
-
spinner.start(
|
|
7276
|
-
"Make sure `sst dev` is running..."
|
|
7277
|
-
);
|
|
7278
|
-
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
7279
|
-
continue;
|
|
7280
|
-
}
|
|
7281
|
-
const isBindSupported = data2.type !== "StaticSite" && data2.type !== "SlsNextjsSite";
|
|
7282
|
-
if (isBindSupported && !data2.data.server || !isBindSupported && !data2.data.environment) {
|
|
7283
|
-
spinner.start(
|
|
7284
|
-
"This was deployed with an old version of SST. Make sure to restart `sst dev`..."
|
|
7285
|
-
);
|
|
7300
|
+
spinner.start("Make sure `sst dev` is running...");
|
|
7286
7301
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
7287
7302
|
continue;
|
|
7288
7303
|
}
|
|
@@ -7294,12 +7309,40 @@ var bind = (program2) => program2.command(
|
|
|
7294
7309
|
const { metadata: metadata3 } = await Promise.resolve().then(() => (init_metadata(), metadata_exports));
|
|
7295
7310
|
const metadataData = await metadata3();
|
|
7296
7311
|
return Object.values(metadataData).flat().filter(
|
|
7297
|
-
(c) =>
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
7301
|
-
|
|
7312
|
+
(c) => [
|
|
7313
|
+
"StaticSite",
|
|
7314
|
+
"NextjsSite",
|
|
7315
|
+
"AstroSite",
|
|
7316
|
+
"RemixSite",
|
|
7317
|
+
"SolidStartSite",
|
|
7318
|
+
"SlsNextjsSite"
|
|
7319
|
+
].includes(c.type)
|
|
7320
|
+
).find((c) => {
|
|
7321
|
+
const isSsr = c.type !== "StaticSite" && c.type !== "SlsNextjsSite";
|
|
7322
|
+
if (!c.data.path || isSsr && !c.data.server || !isSsr && !c.data.environment) {
|
|
7323
|
+
throw new OutdatedMetadataError();
|
|
7324
|
+
}
|
|
7325
|
+
return path18.resolve(project.paths.root, c.data.path) === process.cwd();
|
|
7326
|
+
});
|
|
7327
|
+
}
|
|
7328
|
+
async function parseSiteMetadata(metadata3) {
|
|
7329
|
+
const { LambdaClient: LambdaClient2, GetFunctionCommand } = await import("@aws-sdk/client-lambda");
|
|
7330
|
+
const { useAWSClient: useAWSClient2 } = await Promise.resolve().then(() => (init_credentials(), credentials_exports));
|
|
7331
|
+
const isBindSupported = metadata3.type !== "StaticSite" && metadata3.type !== "SlsNextjsSite";
|
|
7332
|
+
if (!isBindSupported) {
|
|
7333
|
+
return { envs: metadata3.data.environment };
|
|
7334
|
+
}
|
|
7335
|
+
const lambda = useAWSClient2(LambdaClient2);
|
|
7336
|
+
const { Configuration: functionConfig } = await lambda.send(
|
|
7337
|
+
new GetFunctionCommand({
|
|
7338
|
+
FunctionName: metadata3.data.server
|
|
7339
|
+
})
|
|
7302
7340
|
);
|
|
7341
|
+
return {
|
|
7342
|
+
role: functionConfig?.Role,
|
|
7343
|
+
envs: functionConfig?.Environment?.Variables || {},
|
|
7344
|
+
secrets: metadata3.data.secrets
|
|
7345
|
+
};
|
|
7303
7346
|
}
|
|
7304
7347
|
async function assumeSsrRole(roleArn) {
|
|
7305
7348
|
const { STSClient: STSClient2, AssumeRoleCommand } = await import("@aws-sdk/client-sts");
|
|
@@ -8034,6 +8077,7 @@ var version = (program2) => program2.command(
|
|
|
8034
8077
|
const project = useProject2();
|
|
8035
8078
|
Colors2.line(Colors2.bold(`SST:`), `v${project.version}`);
|
|
8036
8079
|
Colors2.line(Colors2.bold(`CDK:`), `v${project.cdkVersion}`);
|
|
8080
|
+
Colors2.line(Colors2.bold(`Constructs:`), `v${project.constructsVersion}`);
|
|
8037
8081
|
}
|
|
8038
8082
|
);
|
|
8039
8083
|
|