@lwrjs/shared-utils 0.9.0-alpha.3 → 0.9.0-alpha.30
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/build/cjs/compiler.cjs +80 -0
- package/build/cjs/env.cjs +4 -8
- package/build/cjs/fs-watch.cjs +43 -0
- package/build/cjs/fs.cjs +25 -15
- package/build/cjs/graph.cjs +4 -3
- package/build/cjs/identity.cjs +14 -70
- package/build/cjs/index.cjs +1 -1
- package/build/cjs/interchangeable-modules.cjs +9 -2
- package/build/cjs/logger.cjs +33 -11
- package/build/cjs/object.cjs +2 -1
- package/build/cjs/serialize.cjs +31 -2
- package/build/cjs/site-metadata.cjs +121 -0
- package/build/es/compiler.d.ts +15 -0
- package/build/es/compiler.js +65 -0
- package/build/es/env.d.ts +1 -1
- package/build/es/env.js +19 -11
- package/build/es/fs-watch.d.ts +17 -0
- package/build/es/fs-watch.js +27 -0
- package/build/es/fs.d.ts +5 -10
- package/build/es/fs.js +33 -23
- package/build/es/graph.d.ts +2 -1
- package/build/es/graph.js +3 -3
- package/build/es/identity.d.ts +9 -39
- package/build/es/identity.js +12 -87
- package/build/es/index.d.ts +1 -1
- package/build/es/index.js +1 -1
- package/build/es/interchangeable-modules.d.ts +1 -1
- package/build/es/interchangeable-modules.js +9 -2
- package/build/es/logger.js +36 -10
- package/build/es/mappings.js +2 -0
- package/build/es/object.js +2 -1
- package/build/es/serialize.d.ts +8 -1
- package/build/es/serialize.js +19 -1
- package/build/es/site-metadata.d.ts +27 -0
- package/build/es/site-metadata.js +111 -0
- package/build/es/typescript.js +2 -1
- package/package.json +27 -11
package/build/es/logger.js
CHANGED
|
@@ -43,21 +43,33 @@ function log(level, message, additionalInfo) {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
if (shouldLog) {
|
|
46
|
-
|
|
46
|
+
const logMessage = `[${level}]${gap(message)}${message}`;
|
|
47
|
+
const additionalMessage = additionalInfo
|
|
48
|
+
? `Additional Info: ${JSON.stringify(additionalInfo)}`
|
|
49
|
+
: undefined;
|
|
47
50
|
if (level == ERROR) {
|
|
48
|
-
|
|
51
|
+
console.error('\x1b[31m%s\x1b[0m', logMessage); // red
|
|
52
|
+
if (additionalInfo) {
|
|
53
|
+
console.error('\n\x1b[31m%s\x1b[0m', additionalMessage); // red
|
|
54
|
+
}
|
|
49
55
|
}
|
|
50
56
|
else if (level == WARN) {
|
|
51
|
-
|
|
57
|
+
console.warn('\x1b[33m%s\x1b[0m', logMessage); // yellow
|
|
58
|
+
if (additionalInfo) {
|
|
59
|
+
console.warn('\n\x1b[33m%s\x1b[0m', additionalMessage); // yellow
|
|
60
|
+
}
|
|
52
61
|
}
|
|
53
|
-
else {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
else if (level == DEBUG || level == VERBOSE) {
|
|
63
|
+
console.log('\x1b[2m%s\x1b[0m', logMessage); // dim
|
|
64
|
+
if (additionalInfo) {
|
|
65
|
+
console.log('\n\x1b[2m%s\x1b[0m', additionalMessage); // dim
|
|
66
|
+
}
|
|
58
67
|
}
|
|
59
68
|
else {
|
|
60
|
-
|
|
69
|
+
console.log(logMessage);
|
|
70
|
+
if (additionalInfo) {
|
|
71
|
+
console.log(`\n${additionalMessage}`);
|
|
72
|
+
}
|
|
61
73
|
}
|
|
62
74
|
}
|
|
63
75
|
}
|
|
@@ -83,12 +95,26 @@ export const stringifyError = (error) => {
|
|
|
83
95
|
return JSON.stringify(retObj);
|
|
84
96
|
}
|
|
85
97
|
};
|
|
98
|
+
// Return a space if there should be a space between the message and the level
|
|
99
|
+
function gap(message) {
|
|
100
|
+
return String(message)?.indexOf('[') === 0 ? '' : ' ';
|
|
101
|
+
}
|
|
102
|
+
function logError(err, additionalInfo) {
|
|
103
|
+
if (err instanceof DiagnosticsError || !err || !err.message) {
|
|
104
|
+
log(ERROR, stringifyError(err), additionalInfo);
|
|
105
|
+
}
|
|
106
|
+
// If this is an error with a message update the message header and log as is...
|
|
107
|
+
if (err && err.message) {
|
|
108
|
+
err.message = `[${ERROR}]${gap(err.message)}${err.message}`;
|
|
109
|
+
console.error('\x1b[31m%s\x1b[0m', err);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
86
112
|
export const logger = {
|
|
87
113
|
verbose: (message, additionalInfo) => log(VERBOSE, message, additionalInfo),
|
|
88
114
|
debug: (message, additionalInfo) => log(DEBUG, message, additionalInfo),
|
|
89
115
|
info: (message, additionalInfo) => log(INFO, message, additionalInfo),
|
|
90
116
|
warn: (message, additionalInfo) => log(WARN, message, additionalInfo),
|
|
91
|
-
error: (error, additionalInfo) =>
|
|
117
|
+
error: (error, additionalInfo) => logError(error, additionalInfo),
|
|
92
118
|
log,
|
|
93
119
|
setOptions: (opts) => {
|
|
94
120
|
options = opts;
|
package/build/es/mappings.js
CHANGED
|
@@ -141,6 +141,8 @@ async function createIndex(specifiers, moduleRegistry, runtimeEnvironment, runti
|
|
|
141
141
|
if (!moduleId.version) {
|
|
142
142
|
throw new Error('Module specifier must include a version: ' + specifier);
|
|
143
143
|
}
|
|
144
|
+
// Do not use replace with the moduleBundler. Otherwize we will create a bundle from every
|
|
145
|
+
// root just to get the URL.
|
|
144
146
|
index[specifier] = await moduleRegistry.resolveModuleUri({ ...moduleId, version: moduleId.version }, moduleRuntimeEnvironment, runtimeParams);
|
|
145
147
|
}
|
|
146
148
|
// Queue up uri requests
|
package/build/es/object.js
CHANGED
|
@@ -18,7 +18,8 @@ function _deepFreeze(obj, maxDepth, depth) {
|
|
|
18
18
|
*/
|
|
19
19
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
|
20
20
|
export function deepFreeze(obj, maxDepth = 5) {
|
|
21
|
-
|
|
21
|
+
const objClone = JSON.parse(JSON.stringify(obj));
|
|
22
|
+
return _deepFreeze(objClone, maxDepth, 0);
|
|
22
23
|
}
|
|
23
24
|
/**
|
|
24
25
|
* Returns a function which debounces when the input function is called with the same first argument
|
package/build/es/serialize.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LinkedModuleDefinition, ModuleJsonDefinition, ModuleRegistry, RuntimeParams } from '@lwrjs/types';
|
|
1
|
+
import type { LinkedModuleDefinition, ModuleJsonDefinition, ModuleRegistry, RuntimeParams } from '@lwrjs/types';
|
|
2
2
|
/**
|
|
3
3
|
* Take a Module Definition and return its JSON serialization
|
|
4
4
|
*
|
|
@@ -19,4 +19,11 @@ export declare function replaceStringFromLocation(src: string, { startOffset, en
|
|
|
19
19
|
startOffset: number;
|
|
20
20
|
endOffset: number;
|
|
21
21
|
}, replaceValue: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Given two TTLs, return the shortest one
|
|
24
|
+
* @param newTtl - time-to-live: time string or number of seconds
|
|
25
|
+
* @param oldTtl - the current shortest TTL (if it exists)
|
|
26
|
+
* @returns - the shorter of the two TTLs IN SECONDS, undefined if both TTLs are missing
|
|
27
|
+
*/
|
|
28
|
+
export declare function shortestTtl(newTtl?: string | number, oldTtl?: string | number): number | undefined;
|
|
22
29
|
//# sourceMappingURL=serialize.d.ts.map
|
package/build/es/serialize.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import ms from 'ms';
|
|
1
2
|
// Given a Module Identifier, return a JSON entry
|
|
2
3
|
async function createJsonModule(moduleId, moduleRegistry, runtimeEnvironment, runtimeParams) {
|
|
3
4
|
const { ownHash, moduleEntry: { version }, } = await moduleRegistry.getModule(moduleId, runtimeParams);
|
|
@@ -6,7 +7,7 @@ async function createJsonModule(moduleId, moduleRegistry, runtimeEnvironment, ru
|
|
|
6
7
|
version,
|
|
7
8
|
ownHash,
|
|
8
9
|
links: {
|
|
9
|
-
self: moduleRegistry.resolveModuleUri(moduleId, runtimeEnvironment, runtimeParams, ownHash),
|
|
10
|
+
self: await moduleRegistry.resolveModuleUri(moduleId, runtimeEnvironment, runtimeParams, ownHash),
|
|
10
11
|
},
|
|
11
12
|
};
|
|
12
13
|
}
|
|
@@ -42,4 +43,21 @@ export async function serializeModuleToJson(code = '', { specifier, version, own
|
|
|
42
43
|
export function replaceStringFromLocation(src, { startOffset, endOffset }, replaceValue) {
|
|
43
44
|
return src.substr(0, startOffset) + replaceValue + src.substr(endOffset, src.length);
|
|
44
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Given two TTLs, return the shortest one
|
|
48
|
+
* @param newTtl - time-to-live: time string or number of seconds
|
|
49
|
+
* @param oldTtl - the current shortest TTL (if it exists)
|
|
50
|
+
* @returns - the shorter of the two TTLs IN SECONDS, undefined if both TTLs are missing
|
|
51
|
+
*/
|
|
52
|
+
export function shortestTtl(newTtl, oldTtl) {
|
|
53
|
+
if (!newTtl && !oldTtl)
|
|
54
|
+
return undefined;
|
|
55
|
+
const newSeconds = typeof newTtl === 'string' ? ms(newTtl) / 1000 : newTtl;
|
|
56
|
+
const oldSeconds = typeof oldTtl === 'string' ? ms(oldTtl) / 1000 : oldTtl;
|
|
57
|
+
if (!newSeconds)
|
|
58
|
+
return oldSeconds;
|
|
59
|
+
if (!oldSeconds)
|
|
60
|
+
return newSeconds;
|
|
61
|
+
return newSeconds < oldSeconds ? newSeconds : oldSeconds;
|
|
62
|
+
}
|
|
45
63
|
//# sourceMappingURL=serialize.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { SiteAssets, SiteBundles, SiteMetadata, SiteResources } from '@lwrjs/types';
|
|
2
|
+
declare type Options = {
|
|
3
|
+
rootDir: string;
|
|
4
|
+
};
|
|
5
|
+
export declare class SiteMetadataImpl implements SiteMetadata {
|
|
6
|
+
private options;
|
|
7
|
+
private siteBundles;
|
|
8
|
+
private siteResources;
|
|
9
|
+
private siteAssets;
|
|
10
|
+
constructor(options: Options);
|
|
11
|
+
getSiteRootDir(): string;
|
|
12
|
+
getSiteBundles(): SiteBundles;
|
|
13
|
+
getSiteResources(): SiteResources;
|
|
14
|
+
getSiteAssets(): SiteAssets;
|
|
15
|
+
persistSiteMetadata(): Promise<void>;
|
|
16
|
+
private readStaticBundleMetadata;
|
|
17
|
+
/**
|
|
18
|
+
* Read the metadata about the pre-built resources of the current site.
|
|
19
|
+
*/
|
|
20
|
+
private readStaticResourceMetadata;
|
|
21
|
+
/**
|
|
22
|
+
* Read the metadata about the pre-built assets of the current site.
|
|
23
|
+
*/
|
|
24
|
+
private readStaticAssetsMetadata;
|
|
25
|
+
}
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=site-metadata.d.ts.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import { logger } from './logger.js';
|
|
4
|
+
const SITE_METADATA_PATH = '.metadata';
|
|
5
|
+
const STATIC_BUNDLE_METADATA_PATH = path.join(SITE_METADATA_PATH, '/bundle-metadata.json');
|
|
6
|
+
const STATIC_RESOURCE_METADATA_PATH = path.join(SITE_METADATA_PATH, '/resource-metadata.json');
|
|
7
|
+
const STATIC_ASSET_METADATA_PATH = path.join(SITE_METADATA_PATH, '/asset-metadata.json');
|
|
8
|
+
export class SiteMetadataImpl {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.options = options;
|
|
11
|
+
this.siteBundles = this.readStaticBundleMetadata(options.rootDir);
|
|
12
|
+
this.siteResources = this.readStaticResourceMetadata(options.rootDir);
|
|
13
|
+
this.siteAssets = this.readStaticAssetsMetadata(options.rootDir);
|
|
14
|
+
}
|
|
15
|
+
getSiteRootDir() {
|
|
16
|
+
return this.options.rootDir;
|
|
17
|
+
}
|
|
18
|
+
getSiteBundles() {
|
|
19
|
+
return this.siteBundles;
|
|
20
|
+
}
|
|
21
|
+
getSiteResources() {
|
|
22
|
+
return this.siteResources;
|
|
23
|
+
}
|
|
24
|
+
getSiteAssets() {
|
|
25
|
+
return this.siteAssets;
|
|
26
|
+
}
|
|
27
|
+
async persistSiteMetadata() {
|
|
28
|
+
// Create the metadata directory if if does not exist
|
|
29
|
+
const siteMetadataPath = path.join(this.options.rootDir, SITE_METADATA_PATH);
|
|
30
|
+
try {
|
|
31
|
+
if (!(await fs.pathExists(siteMetadataPath))) {
|
|
32
|
+
await fs.mkdir(siteMetadataPath, { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
// Save Bundle Metadata
|
|
35
|
+
const bundleMetadataPath = path.join(this.options.rootDir, STATIC_BUNDLE_METADATA_PATH);
|
|
36
|
+
await fs.writeJSON(bundleMetadataPath, this.siteBundles, { spaces: 2 });
|
|
37
|
+
// Save Resource Metadata
|
|
38
|
+
const resourceMetadataPath = path.join(this.options.rootDir, STATIC_RESOURCE_METADATA_PATH);
|
|
39
|
+
await fs.writeJSON(resourceMetadataPath, this.siteResources, { spaces: 2 });
|
|
40
|
+
// Save Resource Metadata
|
|
41
|
+
const assetMetadataPath = path.join(this.options.rootDir, STATIC_ASSET_METADATA_PATH);
|
|
42
|
+
return fs.writeJSON(assetMetadataPath, this.siteAssets, { spaces: 2 });
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
console.error(`[SiteMetadata] Failed to save site metadata ${siteMetadataPath}`);
|
|
46
|
+
console.error(err);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
readStaticBundleMetadata(staticRoot) {
|
|
50
|
+
let bundleMetadataPath;
|
|
51
|
+
let siteBundles = { bundles: {} };
|
|
52
|
+
try {
|
|
53
|
+
bundleMetadataPath = path.join(staticRoot, STATIC_BUNDLE_METADATA_PATH);
|
|
54
|
+
const savedMetadata = fs.readJSONSync(bundleMetadataPath);
|
|
55
|
+
siteBundles = savedMetadata;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (error.code === 'ENOENT') {
|
|
59
|
+
logger.debug(`[SiteMetadata] Failed to load Static Bundle Metadata: ${bundleMetadataPath}`);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return siteBundles;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Read the metadata about the pre-built resources of the current site.
|
|
69
|
+
*/
|
|
70
|
+
readStaticResourceMetadata(staticRoot) {
|
|
71
|
+
let resourceMetadataPath;
|
|
72
|
+
let siteResources = { resources: {} };
|
|
73
|
+
try {
|
|
74
|
+
resourceMetadataPath = path.join(staticRoot, STATIC_RESOURCE_METADATA_PATH);
|
|
75
|
+
const savedMetadata = fs.readJSONSync(resourceMetadataPath);
|
|
76
|
+
siteResources = savedMetadata;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (error.code === 'ENOENT') {
|
|
80
|
+
logger.debug(`[SiteMetadata] Failed to load Static Resource Metadata: ${resourceMetadataPath}`);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return siteResources;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Read the metadata about the pre-built assets of the current site.
|
|
90
|
+
*/
|
|
91
|
+
readStaticAssetsMetadata(staticRoot) {
|
|
92
|
+
let assetMetadataPath;
|
|
93
|
+
let siteAssets = {
|
|
94
|
+
assets: {},
|
|
95
|
+
};
|
|
96
|
+
try {
|
|
97
|
+
assetMetadataPath = path.join(staticRoot, STATIC_ASSET_METADATA_PATH);
|
|
98
|
+
siteAssets = fs.readJSONSync(assetMetadataPath);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (error.code === 'ENOENT') {
|
|
102
|
+
logger.debug(`[SiteMetadata] Failed to load Static Resource Metadata: ${assetMetadataPath}`);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return siteAssets;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=site-metadata.js.map
|
package/build/es/typescript.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import { join, dirname, extname } from 'path';
|
|
3
3
|
import esbuildEsm from 'esbuild';
|
|
4
|
-
// Workaround until this is fixed:
|
|
5
4
|
// https://github.com/evanw/esbuild/issues/706
|
|
5
|
+
// Fixed in 0.11.0 but upgrading past 0.9.7 has caused breaking changes for consumers...
|
|
6
|
+
// https://github.com/salesforce-experience-platform-emu/lwr/issues/1014
|
|
6
7
|
let esbuild = esbuildEsm;
|
|
7
8
|
if (!esbuildEsm) {
|
|
8
9
|
try {
|
package/package.json
CHANGED
|
@@ -4,18 +4,18 @@
|
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"version": "0.9.0-alpha.
|
|
7
|
+
"version": "0.9.0-alpha.30",
|
|
8
8
|
"homepage": "https://developer.salesforce.com/docs/platform/lwr/overview",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "https://github.com/salesforce/lwr.git",
|
|
11
|
+
"url": "https://github.com/salesforce-experience-platform-emu/lwr.git",
|
|
12
12
|
"directory": "packages/@lwrjs/shared-utils"
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "tsc -b"
|
|
16
16
|
},
|
|
17
17
|
"bugs": {
|
|
18
|
-
"url": "https://github.com/salesforce/lwr/issues"
|
|
18
|
+
"url": "https://github.com/salesforce-experience-platform-emu/lwr/issues"
|
|
19
19
|
},
|
|
20
20
|
"type": "module",
|
|
21
21
|
"types": "build/es/index.d.ts",
|
|
@@ -25,6 +25,18 @@
|
|
|
25
25
|
".": {
|
|
26
26
|
"import": "./build/es/index.js",
|
|
27
27
|
"require": "./build/cjs/index.cjs"
|
|
28
|
+
},
|
|
29
|
+
"./fs-watch": {
|
|
30
|
+
"import": "./build/es/fs-watch.js",
|
|
31
|
+
"require": "./build/cjs/fs-watch.cjs"
|
|
32
|
+
},
|
|
33
|
+
"./typescript": {
|
|
34
|
+
"import": "./build/es/typescript.js",
|
|
35
|
+
"require": "./build/cjs/typescript.cjs"
|
|
36
|
+
},
|
|
37
|
+
"./compiler": {
|
|
38
|
+
"import": "./build/es/compiler.js",
|
|
39
|
+
"require": "./build/cjs/compiler.cjs"
|
|
28
40
|
}
|
|
29
41
|
},
|
|
30
42
|
"files": [
|
|
@@ -33,25 +45,29 @@
|
|
|
33
45
|
"build/**/*.d.ts"
|
|
34
46
|
],
|
|
35
47
|
"dependencies": {
|
|
36
|
-
"chokidar": "^3.4.0",
|
|
37
48
|
"es-module-lexer": "^0.3.18",
|
|
38
|
-
"esbuild": "^0.9.7",
|
|
39
49
|
"fast-json-stable-stringify": "^2.1.0",
|
|
40
50
|
"magic-string": "^0.25.7",
|
|
41
51
|
"mime-types": "^2.1.33",
|
|
52
|
+
"ms": "^2.1.3",
|
|
42
53
|
"parse5-sax-parser": "^6.0.1",
|
|
43
54
|
"path-to-regexp": "^6.2.0",
|
|
44
|
-
"slugify": "^1.4.5"
|
|
45
|
-
|
|
55
|
+
"slugify": "^1.4.5"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"@locker/compiler": "0.18.14",
|
|
59
|
+
"chokidar": "^3.5.3",
|
|
60
|
+
"esbuild": "^0.9.7",
|
|
61
|
+
"rollup": "~2.45.2"
|
|
46
62
|
},
|
|
47
63
|
"devDependencies": {
|
|
48
|
-
"@lwrjs/diagnostics": "0.9.0-alpha.
|
|
49
|
-
"@lwrjs/types": "0.9.0-alpha.
|
|
64
|
+
"@lwrjs/diagnostics": "0.9.0-alpha.30",
|
|
65
|
+
"@lwrjs/types": "0.9.0-alpha.30",
|
|
50
66
|
"@types/mime-types": "2.1.1",
|
|
51
67
|
"@types/path-to-regexp": "^1.7.0"
|
|
52
68
|
},
|
|
53
69
|
"engines": {
|
|
54
|
-
"node": ">=
|
|
70
|
+
"node": ">=16.0.0 <20"
|
|
55
71
|
},
|
|
56
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "6e18768b8e47066c41ae0b792bf4aa766b0b784b"
|
|
57
73
|
}
|