nx 20.5.0-beta.3 → 20.5.0-beta.5
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/package.json +11 -11
- package/schemas/nx-schema.json +36 -2
- package/src/command-line/add/add.js +6 -16
- package/src/command-line/init/command-object.js +5 -0
- package/src/command-line/init/configure-plugins.d.ts +5 -6
- package/src/command-line/init/configure-plugins.js +13 -22
- package/src/command-line/init/implementation/react/add-vite-commands-to-package-scripts.js +6 -4
- package/src/command-line/init/implementation/react/index.d.ts +1 -1
- package/src/command-line/init/implementation/react/index.js +32 -185
- package/src/command-line/init/implementation/react/write-vite-config.js +19 -3
- package/src/command-line/init/implementation/utils.d.ts +1 -0
- package/src/command-line/init/implementation/utils.js +14 -0
- package/src/command-line/init/init-v2.d.ts +1 -0
- package/src/command-line/init/init-v2.js +25 -3
- package/src/command-line/release/changelog.js +3 -5
- package/src/command-line/release/command-object.d.ts +1 -0
- package/src/command-line/release/command-object.js +5 -0
- package/src/command-line/release/config/config.js +7 -0
- package/src/command-line/release/utils/git.d.ts +1 -1
- package/src/command-line/release/utils/git.js +55 -7
- package/src/config/nx-json.d.ts +19 -1
- package/src/core/graph/main.js +1 -1
- package/src/core/graph/styles.js +1 -1
- package/src/daemon/server/server.js +1 -0
- package/src/native/index.d.ts +1 -1
- package/src/native/nx.wasm32-wasi.wasm +0 -0
- package/src/plugins/js/lock-file/lock-file.js +28 -13
- package/src/plugins/js/lock-file/yarn-parser.js +10 -10
- package/src/plugins/js/project-graph/affected/lock-file-changes.js +1 -0
- package/src/plugins/js/project-graph/build-dependencies/explicit-project-dependencies.js +1 -1
- package/src/plugins/js/utils/typescript.js +3 -3
- package/src/project-graph/plugins/isolation/plugin-worker.js +8 -6
- package/src/tasks-runner/run-command.js +2 -3
- package/src/utils/command-line-utils.d.ts +1 -1
- package/src/utils/find-matching-projects.js +2 -2
- package/src/utils/package-manager.js +2 -2
- package/src/command-line/init/implementation/react/write-craco-config.d.ts +0 -1
- package/src/command-line/init/implementation/react/write-craco-config.js +0 -61
package/src/core/graph/styles.js
CHANGED
@@ -1 +1 @@
|
|
1
|
-
"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[532],{
|
1
|
+
"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[532],{2558:()=>{}},s=>{var e;e=2558,s(s.s=e)}]);
|
@@ -255,6 +255,7 @@ function lockFileHashChanged() {
|
|
255
255
|
(0, path_1.join)(workspace_root_1.workspaceRoot, 'yarn.lock'),
|
256
256
|
(0, path_1.join)(workspace_root_1.workspaceRoot, 'pnpm-lock.yaml'),
|
257
257
|
(0, path_1.join)(workspace_root_1.workspaceRoot, 'bun.lockb'),
|
258
|
+
(0, path_1.join)(workspace_root_1.workspaceRoot, 'bun.lock'),
|
258
259
|
]
|
259
260
|
.filter((file) => (0, fs_1.existsSync)(file))
|
260
261
|
.map((file) => (0, native_1.hashFile)(file));
|
package/src/native/index.d.ts
CHANGED
@@ -173,7 +173,7 @@ export declare export function getFilesForOutputs(directory: string, entries: Ar
|
|
173
173
|
|
174
174
|
export declare export function getTransformableOutputs(outputs: Array<string>): Array<string>
|
175
175
|
|
176
|
-
export declare export function hashArray(input: Array<string>): string
|
176
|
+
export declare export function hashArray(input: Array<string | undefined | null>): string
|
177
177
|
|
178
178
|
export interface HashDetails {
|
179
179
|
value: string
|
Binary file
|
@@ -10,8 +10,10 @@ exports.getLockFileDependencies = getLockFileDependencies;
|
|
10
10
|
exports.lockFileExists = lockFileExists;
|
11
11
|
exports.getLockFileName = getLockFileName;
|
12
12
|
exports.createLockFile = createLockFile;
|
13
|
-
const
|
14
|
-
const
|
13
|
+
const node_child_process_1 = require("node:child_process");
|
14
|
+
const node_fs_1 = require("node:fs");
|
15
|
+
const node_path_1 = require("node:path");
|
16
|
+
const semver_1 = require("semver");
|
15
17
|
const package_manager_1 = require("../../../utils/package-manager");
|
16
18
|
const workspace_root_1 = require("../../../utils/workspace-root");
|
17
19
|
const output_1 = require("../../../utils/output");
|
@@ -25,23 +27,26 @@ const YARN_LOCK_FILE = 'yarn.lock';
|
|
25
27
|
const NPM_LOCK_FILE = 'package-lock.json';
|
26
28
|
const PNPM_LOCK_FILE = 'pnpm-lock.yaml';
|
27
29
|
const BUN_LOCK_FILE = 'bun.lockb';
|
30
|
+
const BUN_TEXT_LOCK_FILE = 'bun.lock';
|
28
31
|
exports.LOCKFILES = [
|
29
32
|
YARN_LOCK_FILE,
|
30
33
|
NPM_LOCK_FILE,
|
31
34
|
PNPM_LOCK_FILE,
|
32
35
|
BUN_LOCK_FILE,
|
36
|
+
BUN_TEXT_LOCK_FILE,
|
33
37
|
];
|
34
|
-
const YARN_LOCK_PATH = (0,
|
35
|
-
const NPM_LOCK_PATH = (0,
|
36
|
-
const PNPM_LOCK_PATH = (0,
|
37
|
-
const BUN_LOCK_PATH = (0,
|
38
|
+
const YARN_LOCK_PATH = (0, node_path_1.join)(workspace_root_1.workspaceRoot, YARN_LOCK_FILE);
|
39
|
+
const NPM_LOCK_PATH = (0, node_path_1.join)(workspace_root_1.workspaceRoot, NPM_LOCK_FILE);
|
40
|
+
const PNPM_LOCK_PATH = (0, node_path_1.join)(workspace_root_1.workspaceRoot, PNPM_LOCK_FILE);
|
41
|
+
const BUN_LOCK_PATH = (0, node_path_1.join)(workspace_root_1.workspaceRoot, BUN_LOCK_FILE);
|
42
|
+
const BUN_TEXT_LOCK_PATH = (0, node_path_1.join)(workspace_root_1.workspaceRoot, BUN_TEXT_LOCK_FILE);
|
38
43
|
/**
|
39
44
|
* Parses lock file and maps dependencies and metadata to {@link LockFileGraph}
|
40
45
|
*/
|
41
46
|
function getLockFileNodes(packageManager, contents, lockFileHash, context) {
|
42
47
|
try {
|
43
48
|
if (packageManager === 'yarn') {
|
44
|
-
const packageJson = (0, fileutils_1.readJsonFile)((0,
|
49
|
+
const packageJson = (0, fileutils_1.readJsonFile)((0, node_path_1.join)(context.workspaceRoot, 'package.json'));
|
45
50
|
return (0, yarn_parser_1.getYarnLockfileNodes)(contents, lockFileHash, packageJson);
|
46
51
|
}
|
47
52
|
if (packageManager === 'pnpm') {
|
@@ -99,16 +104,16 @@ function getLockFileDependencies(packageManager, contents, lockFileHash, context
|
|
99
104
|
}
|
100
105
|
function lockFileExists(packageManager) {
|
101
106
|
if (packageManager === 'yarn') {
|
102
|
-
return (0,
|
107
|
+
return (0, node_fs_1.existsSync)(YARN_LOCK_PATH);
|
103
108
|
}
|
104
109
|
if (packageManager === 'pnpm') {
|
105
|
-
return (0,
|
110
|
+
return (0, node_fs_1.existsSync)(PNPM_LOCK_PATH);
|
106
111
|
}
|
107
112
|
if (packageManager === 'npm') {
|
108
|
-
return (0,
|
113
|
+
return (0, node_fs_1.existsSync)(NPM_LOCK_PATH);
|
109
114
|
}
|
110
115
|
if (packageManager === 'bun') {
|
111
|
-
return (0,
|
116
|
+
return (0, node_fs_1.existsSync)(BUN_LOCK_PATH) || (0, node_fs_1.existsSync)(BUN_TEXT_LOCK_PATH);
|
112
117
|
}
|
113
118
|
throw new Error(`Unknown package manager ${packageManager} or lock file missing`);
|
114
119
|
}
|
@@ -143,7 +148,17 @@ function getLockFilePath(packageManager) {
|
|
143
148
|
return NPM_LOCK_PATH;
|
144
149
|
}
|
145
150
|
if (packageManager === 'bun') {
|
146
|
-
|
151
|
+
try {
|
152
|
+
const bunVersion = (0, node_child_process_1.execSync)('bun --version').toString().trim();
|
153
|
+
// In version 1.2.0, bun switched to a text based lockfile format by default
|
154
|
+
if ((0, semver_1.gte)(bunVersion, '1.2.0')) {
|
155
|
+
return BUN_TEXT_LOCK_FILE;
|
156
|
+
}
|
157
|
+
return BUN_LOCK_PATH;
|
158
|
+
}
|
159
|
+
catch {
|
160
|
+
return BUN_LOCK_PATH;
|
161
|
+
}
|
147
162
|
}
|
148
163
|
throw new Error(`Unknown package manager: ${packageManager}`);
|
149
164
|
}
|
@@ -157,7 +172,7 @@ function getLockFilePath(packageManager) {
|
|
157
172
|
*/
|
158
173
|
function createLockFile(packageJson, graph, packageManager = (0, package_manager_1.detectPackageManager)(workspace_root_1.workspaceRoot)) {
|
159
174
|
const normalizedPackageJson = (0, package_json_1.normalizePackageJson)(packageJson);
|
160
|
-
const content = (0,
|
175
|
+
const content = (0, node_fs_1.readFileSync)(getLockFilePath(packageManager), 'utf8');
|
161
176
|
try {
|
162
177
|
if (packageManager === 'yarn') {
|
163
178
|
const prunedGraph = (0, project_graph_pruning_1.pruneProjectGraph)(graph, packageJson);
|
@@ -29,14 +29,14 @@ function getYarnLockfileNodes(lockFileContent, lockFileHash, packageJson) {
|
|
29
29
|
const isBerry = !!__metadata;
|
30
30
|
// yarn classic splits keys when parsing so we need to stich them back together
|
31
31
|
const groupedDependencies = groupDependencies(dependencies, isBerry);
|
32
|
-
return getNodes(groupedDependencies, packageJson,
|
32
|
+
return getNodes(groupedDependencies, packageJson, isBerry);
|
33
33
|
}
|
34
34
|
function getYarnLockfileDependencies(lockFileContent, lockFileHash, ctx) {
|
35
35
|
const { __metadata, ...dependencies } = parseLockFile(lockFileContent, lockFileHash);
|
36
36
|
const isBerry = !!__metadata;
|
37
37
|
// yarn classic splits keys when parsing so we need to stich them back together
|
38
38
|
const groupedDependencies = groupDependencies(dependencies, isBerry);
|
39
|
-
return getDependencies(groupedDependencies,
|
39
|
+
return getDependencies(groupedDependencies, ctx);
|
40
40
|
}
|
41
41
|
function getPackageNameKeyPairs(keys) {
|
42
42
|
const result = new Map();
|
@@ -51,7 +51,7 @@ function getPackageNameKeyPairs(keys) {
|
|
51
51
|
});
|
52
52
|
return result;
|
53
53
|
}
|
54
|
-
function getNodes(dependencies, packageJson,
|
54
|
+
function getNodes(dependencies, packageJson, isBerry) {
|
55
55
|
const nodes = new Map();
|
56
56
|
const combinedDeps = {
|
57
57
|
...packageJson.dependencies,
|
@@ -68,7 +68,7 @@ function getNodes(dependencies, packageJson, keyMap, isBerry) {
|
|
68
68
|
nameKeyPairs.forEach((keySet, packageName) => {
|
69
69
|
const keysArray = Array.from(keySet);
|
70
70
|
// use key relevant to the package name
|
71
|
-
const version = findVersion(packageName, keysArray[0], snapshot, isBerry);
|
71
|
+
const [version, isAlias] = findVersion(packageName, keysArray[0], snapshot, isBerry);
|
72
72
|
// use keys linked to the extracted package name
|
73
73
|
keysArray.forEach((key) => {
|
74
74
|
// we don't need to keep duplicates, we can just track the keys
|
@@ -79,7 +79,7 @@ function getNodes(dependencies, packageJson, keyMap, isBerry) {
|
|
79
79
|
}
|
80
80
|
const node = {
|
81
81
|
type: 'npm',
|
82
|
-
name: version
|
82
|
+
name: version && !isAlias
|
83
83
|
? `npm:${packageName}@${version}`
|
84
84
|
: `npm:${packageName}`,
|
85
85
|
data: {
|
@@ -151,7 +151,7 @@ function findVersion(packageName, key, snapshot, isBerry) {
|
|
151
151
|
? snapshot.resolution && !snapshot.resolution.startsWith(`${packageName}@`)
|
152
152
|
: versionRange.startsWith('npm:');
|
153
153
|
if (isAlias) {
|
154
|
-
return versionRange;
|
154
|
+
return [versionRange, true];
|
155
155
|
}
|
156
156
|
// check for berry tarball packages
|
157
157
|
if (isBerry &&
|
@@ -159,13 +159,13 @@ function findVersion(packageName, key, snapshot, isBerry) {
|
|
159
159
|
// different registry would yield suffix following '::' which we don't need
|
160
160
|
snapshot.resolution.split('::')[0] !==
|
161
161
|
`${packageName}@npm:${snapshot.version}`) {
|
162
|
-
return snapshot.resolution.slice(packageName.length + 1);
|
162
|
+
return [snapshot.resolution.slice(packageName.length + 1)];
|
163
163
|
}
|
164
164
|
if (!isBerry && isTarballPackage(versionRange, snapshot)) {
|
165
|
-
return snapshot.resolved;
|
165
|
+
return [snapshot.resolved];
|
166
166
|
}
|
167
167
|
// otherwise it's a standard version
|
168
|
-
return snapshot.version;
|
168
|
+
return [snapshot.version];
|
169
169
|
}
|
170
170
|
// check if snapshot represents tarball package
|
171
171
|
function isTarballPackage(versionRange, snapshot) {
|
@@ -193,7 +193,7 @@ function getHoistedVersion(packageName) {
|
|
193
193
|
return version;
|
194
194
|
}
|
195
195
|
}
|
196
|
-
function getDependencies(dependencies,
|
196
|
+
function getDependencies(dependencies, ctx) {
|
197
197
|
const projectGraphDependencies = [];
|
198
198
|
Object.keys(dependencies).forEach((keys) => {
|
199
199
|
const snapshot = dependencies[keys];
|
@@ -19,6 +19,7 @@ const getTouchedProjectsFromLockFile = (fileChanges, projectGraphNodes) => {
|
|
19
19
|
'pnpm-lock.yaml',
|
20
20
|
'pnpm-lock.yml',
|
21
21
|
'bun.lockb',
|
22
|
+
'bun.lock',
|
22
23
|
];
|
23
24
|
if (fileChanges.some((f) => lockFiles.includes(f.file))) {
|
24
25
|
return Object.values(projectGraphNodes).map((p) => p.name);
|
@@ -38,7 +38,7 @@ function buildExplicitTypeScriptDependencies(ctx, targetProjectLocator) {
|
|
38
38
|
if (isVuePluginInstalled()) {
|
39
39
|
moduleExtensions.push('.vue');
|
40
40
|
}
|
41
|
-
for (const [project, fileData] of Object.entries(ctx.
|
41
|
+
for (const [project, fileData] of Object.entries(ctx.filesToProcess.projectFileMap)) {
|
42
42
|
filesToProcess[project] ??= [];
|
43
43
|
for (const { file } of fileData) {
|
44
44
|
if (moduleExtensions.some((ext) => file.endsWith(ext))) {
|
@@ -23,11 +23,11 @@ function readTsConfigOptions(tsConfigPath) {
|
|
23
23
|
tsModule = require('typescript');
|
24
24
|
}
|
25
25
|
const readResult = tsModule.readConfigFile(tsConfigPath, tsModule.sys.readFile);
|
26
|
-
// we don't need to scan
|
26
|
+
// We only care about options, so we don't need to scan source files, and thus
|
27
|
+
// `readDirectory` is stubbed for performance.
|
27
28
|
const host = {
|
29
|
+
...tsModule.sys,
|
28
30
|
readDirectory: () => [],
|
29
|
-
readFile: () => '',
|
30
|
-
fileExists: tsModule.sys.fileExists,
|
31
31
|
};
|
32
32
|
return tsModule.parseJsonConfigFileContent(readResult.config, host, (0, path_1.dirname)(tsConfigPath)).options;
|
33
33
|
}
|
@@ -186,12 +186,14 @@ const server = (0, net_1.createServer)((socket) => {
|
|
186
186
|
});
|
187
187
|
});
|
188
188
|
server.listen(socketPath);
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
}
|
189
|
+
if (process.env.NX_PLUGIN_NO_TIMEOUTS !== 'true') {
|
190
|
+
setTimeout(() => {
|
191
|
+
if (!connected) {
|
192
|
+
console.error('The plugin worker is exiting as it was not connected to within 5 seconds.');
|
193
|
+
process.exit(1);
|
194
|
+
}
|
195
|
+
}, 5000).unref();
|
196
|
+
}
|
195
197
|
const exitHandler = (exitCode) => () => {
|
196
198
|
server.close();
|
197
199
|
try {
|
@@ -535,10 +535,9 @@ function getRunner(nxArgs, nxJson) {
|
|
535
535
|
try {
|
536
536
|
if (isCustomRunnerPath(modulePath)) {
|
537
537
|
output_1.output.warn({
|
538
|
-
title: `Custom task runners will
|
538
|
+
title: `Custom task runners will be replaced by a new API starting with Nx 21.`,
|
539
539
|
bodyLines: [
|
540
|
-
`
|
541
|
-
`For more information, see https://nx.dev/nx-enterprise/powerpack/custom-caching`,
|
540
|
+
`For more information, see https://nx.dev/deprecated/custom-tasks-runner`,
|
542
541
|
],
|
543
542
|
});
|
544
543
|
}
|
@@ -8,7 +8,7 @@ export interface NxArgs {
|
|
8
8
|
targets?: string[];
|
9
9
|
configuration?: string;
|
10
10
|
/**
|
11
|
-
* @deprecated Custom task runners will
|
11
|
+
* @deprecated Custom task runners will be replaced by a new API starting with Nx 21. More info: https://nx.dev/deprecated/custom-tasks-runner
|
12
12
|
*/
|
13
13
|
runner?: string;
|
14
14
|
parallel?: number;
|
@@ -112,8 +112,8 @@ function addMatchingProjectsByName(projectNames, projects, pattern, matchedProje
|
|
112
112
|
return;
|
113
113
|
}
|
114
114
|
if (!(0, globs_1.isGlobPattern)(pattern.value)) {
|
115
|
-
// Custom regex that is basically \b
|
116
|
-
const regex = new RegExp(`(?<![a-zA-Z0-9])${pattern.value}(?![a-zA-Z0-9])`, 'i');
|
115
|
+
// Custom regex that is basically \b but includes hyphens (-) and excludes underscores (_), so "foo" pattern matches "foo_bar" but not "foo-e2e".
|
116
|
+
const regex = new RegExp(`(?<![a-zA-Z0-9-])${pattern.value}(?![a-zA-Z0-9-])`, 'i');
|
117
117
|
const matchingProjects = Object.keys(projects).filter((name) => regex.test(name));
|
118
118
|
for (const projectName of matchingProjects) {
|
119
119
|
if (pattern.exclude) {
|
@@ -35,7 +35,7 @@ const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
35
35
|
function detectPackageManager(dir = '') {
|
36
36
|
const nxJson = (0, configuration_1.readNxJson)();
|
37
37
|
return (nxJson.cli?.packageManager ??
|
38
|
-
((0, fs_1.existsSync)((0, path_1.join)(dir, 'bun.lockb'))
|
38
|
+
((0, fs_1.existsSync)((0, path_1.join)(dir, 'bun.lockb')) || (0, fs_1.existsSync)((0, path_1.join)(dir, 'bun.lock'))
|
39
39
|
? 'bun'
|
40
40
|
: (0, fs_1.existsSync)((0, path_1.join)(dir, 'yarn.lock'))
|
41
41
|
? 'yarn'
|
@@ -151,7 +151,7 @@ function getPackageManagerCommand(packageManager = detectPackageManager(), root
|
|
151
151
|
};
|
152
152
|
},
|
153
153
|
bun: () => {
|
154
|
-
// bun doesn't current support
|
154
|
+
// bun doesn't current support programmatically reading config https://github.com/oven-sh/bun/issues/7140
|
155
155
|
return {
|
156
156
|
install: 'bun install',
|
157
157
|
ciInstall: 'bun install --no-cache',
|
@@ -1 +0,0 @@
|
|
1
|
-
export declare function writeCracoConfig(appName: string, isCRA5: boolean, isStandalone: boolean): void;
|
@@ -1,61 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.writeCracoConfig = writeCracoConfig;
|
4
|
-
const fs_1 = require("fs");
|
5
|
-
function writeCracoConfig(appName, isCRA5, isStandalone) {
|
6
|
-
const configOverride = `
|
7
|
-
const path = require('path');
|
8
|
-
const TsConfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
9
|
-
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
|
10
|
-
module.exports = {
|
11
|
-
webpack: {
|
12
|
-
configure: (config) => {
|
13
|
-
// Remove guard against importing modules outside of \`src\`.
|
14
|
-
// Needed for workspace projects.
|
15
|
-
config.resolve.plugins = config.resolve.plugins.filter(
|
16
|
-
(plugin) => !(plugin instanceof ModuleScopePlugin)
|
17
|
-
);
|
18
|
-
// Add support for importing workspace projects.
|
19
|
-
config.resolve.plugins.push(
|
20
|
-
new TsConfigPathsPlugin({
|
21
|
-
configFile: path.resolve(__dirname, 'tsconfig.json'),
|
22
|
-
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
23
|
-
mainFields: ['browser', 'module', 'main'],
|
24
|
-
})
|
25
|
-
);
|
26
|
-
${isCRA5
|
27
|
-
? `
|
28
|
-
// Replace include option for babel loader with exclude
|
29
|
-
// so babel will handle workspace projects as well.
|
30
|
-
config.module.rules[1].oneOf.forEach((r) => {
|
31
|
-
if (r.loader && r.loader.indexOf('babel') !== -1) {
|
32
|
-
r.exclude = /node_modules/;
|
33
|
-
delete r.include;
|
34
|
-
}
|
35
|
-
});`
|
36
|
-
: `
|
37
|
-
// Replace include option for babel loader with exclude
|
38
|
-
// so babel will handle workspace projects as well.
|
39
|
-
config.module.rules.forEach((r) => {
|
40
|
-
if (r.oneOf) {
|
41
|
-
const babelLoader = r.oneOf.find(
|
42
|
-
(rr) => rr.loader.indexOf('babel-loader') !== -1
|
43
|
-
);
|
44
|
-
babelLoader.exclude = /node_modules/;
|
45
|
-
delete babelLoader.include;
|
46
|
-
}
|
47
|
-
});
|
48
|
-
`}
|
49
|
-
return config;
|
50
|
-
},
|
51
|
-
},
|
52
|
-
jest: {
|
53
|
-
configure: (config) => {
|
54
|
-
config.resolver = '@nx/jest/plugins/resolver';
|
55
|
-
return config;
|
56
|
-
},
|
57
|
-
},
|
58
|
-
};
|
59
|
-
`;
|
60
|
-
(0, fs_1.writeFileSync)(isStandalone ? 'craco.config.js' : `apps/${appName}/craco.config.js`, configOverride);
|
61
|
-
}
|