Merge pull request #291 from microsoft/octogonz/rush-5.44.0
Upgrade to Rush 5.44.0
This commit is contained in:
Коммит
d3c3b64b53
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@microsoft/tsdoc-config",
|
||||
"comment": "",
|
||||
"type": "none"
|
||||
}
|
||||
],
|
||||
"packageName": "@microsoft/tsdoc-config",
|
||||
"email": "4673363+octogonz@users.noreply.github.com"
|
||||
}
|
|
@ -6,10 +6,10 @@ jobs:
|
|||
strategy:
|
||||
maxParallel: 2
|
||||
matrix:
|
||||
'NodeJs 10':
|
||||
NodeVersion: 10
|
||||
'NodeJs 12':
|
||||
NodeVersion: 12
|
||||
'NodeJs 14':
|
||||
NodeVersion: 14
|
||||
steps:
|
||||
- checkout: self
|
||||
- template: templates/build.yaml
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
variables:
|
||||
NodeVersion: 12
|
||||
NodeVersion: 14
|
||||
FORCE_COLOR: 1
|
||||
steps:
|
||||
- checkout: self
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
pool:
|
||||
vmImage: 'windows-latest'
|
||||
vmImage: 'ubuntu-latest'
|
||||
variables:
|
||||
NodeVersion: 12
|
||||
NodeVersion: 14
|
||||
FORCE_COLOR: 1
|
||||
steps:
|
||||
- checkout: self
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
resources:
|
||||
- repo: self
|
||||
variables:
|
||||
NodeVersion: 12
|
||||
NodeVersion: 14
|
||||
FORCE_COLOR: 1
|
||||
steps:
|
||||
- checkout: self
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* This configuration file manages Rush integration with JFrog Artifactory services.
|
||||
* More documentation is available on the Rush website: https://rushjs.io
|
||||
*/
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/artifactory.schema.json",
|
||||
|
||||
"packageRegistry": {
|
||||
/**
|
||||
* (Required) Set this to "true" to enable Rush to manage tokens for an Artifactory NPM registry.
|
||||
* When enabled, "rush install" will automatically detect when the user's ~/.npmrc
|
||||
* authentication token is missing or expired. And "rush setup" will prompt the user to
|
||||
* renew their token.
|
||||
*
|
||||
* The default value is false.
|
||||
*/
|
||||
"enabled": false,
|
||||
|
||||
/**
|
||||
* (Required) Specify the URL of your NPM registry. This is the same URL that appears in
|
||||
* your .npmrc file. It should look something like this example:
|
||||
*
|
||||
* https://your-company.jfrog.io/your-project/api/npm/npm-private/
|
||||
*/
|
||||
"registryUrl": "",
|
||||
|
||||
/**
|
||||
* A list of custom strings that "rush setup" should add to the user's ~/.npmrc file at the time
|
||||
* when the token is updated. This could be used for example to configure the company registry
|
||||
* to be used whenever NPM is invoked as a standalone command (but it's not needed for Rush
|
||||
* operations like "rush add" and "rush install", which get their mappings from the monorepo's
|
||||
* common/config/rush/.npmrc file).
|
||||
*
|
||||
* NOTE: The ~/.npmrc settings are global for the user account on a given machine, so be careful
|
||||
* about adding settings that may interfere with other work outside the monorepo.
|
||||
*/
|
||||
"userNpmrcLinesToAdd": [
|
||||
// "@example:registry=https://your-company.jfrog.io/your-project/api/npm/npm-private/"
|
||||
],
|
||||
|
||||
/**
|
||||
* (Required) Specifies the URL of the Artifactory control panel where the user can generate
|
||||
* an API key. This URL is printed after the "visitWebsite" message.
|
||||
* It should look something like this example: https://your-company.jfrog.io/
|
||||
* Specify an empty string to suppress this line entirely.
|
||||
*/
|
||||
"artifactoryWebsiteUrl": "",
|
||||
|
||||
/**
|
||||
* These settings allow the "rush setup" interactive prompts to be customized, for
|
||||
* example with messages specific to your team or configuration. Specify an empty string
|
||||
* to suppress that message entirely.
|
||||
*/
|
||||
"messageOverrides": {
|
||||
/**
|
||||
* Overrides the message that normally says:
|
||||
* "This monorepo consumes packages from an Artifactory private NPM registry."
|
||||
*/
|
||||
// "introduction": "",
|
||||
/**
|
||||
* Overrides the message that normally says:
|
||||
* "Please contact the repository maintainers for help with setting up an Artifactory user account."
|
||||
*/
|
||||
// "obtainAnAccount": "",
|
||||
/**
|
||||
* Overrides the message that normally says:
|
||||
* "Please open this URL in your web browser:"
|
||||
*
|
||||
* The "artifactoryWebsiteUrl" string is printed after this message.
|
||||
*/
|
||||
// "visitWebsite": "",
|
||||
/**
|
||||
* Overrides the message that normally says:
|
||||
* "Your user name appears in the upper-right corner of the JFrog website."
|
||||
*/
|
||||
// "locateUserName": "",
|
||||
/**
|
||||
* Overrides the message that normally says:
|
||||
* "Click 'Edit Profile' on the JFrog website. Click the 'Generate API Key'
|
||||
* button if you haven't already done so previously."
|
||||
*/
|
||||
// "locateApiKey": ""
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* This configuration file defines custom commands for the "rush" command-line.
|
||||
* For full documentation, please see https://rushjs.io
|
||||
* More documentation is available on the Rush website: https://rushjs.io
|
||||
*/
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/command-line.schema.json",
|
||||
|
@ -107,7 +107,23 @@
|
|||
// /**
|
||||
// * If true then this command will be incremental like the built-in "build" command
|
||||
// */
|
||||
// "incremental": false
|
||||
// "incremental": false,
|
||||
//
|
||||
// /**
|
||||
// * (EXPERIMENTAL) Normally Rush terminates after the command finishes. If this option is set to "true" Rush
|
||||
// * will instead enter a loop where it watches the file system for changes to the selected projects. Whenever a
|
||||
// * change is detected, the command will be invoked again for the changed project and any selected projects that
|
||||
// * directly or indirectly depend on it.
|
||||
// *
|
||||
// * For details, refer to the website article "Using watch mode".
|
||||
// */
|
||||
// "watchForChanges": false,
|
||||
//
|
||||
// /**
|
||||
// * (EXPERIMENTAL) Disable cache for this action. This may be useful if this command affects state outside of
|
||||
// * projects' own folders.
|
||||
// */
|
||||
// "disableBuildCache ": false
|
||||
// },
|
||||
//
|
||||
// {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* This configuration file specifies NPM dependency version selections that affect all projects
|
||||
* in a Rush repo. For full documentation, please see https://rushjs.io
|
||||
* in a Rush repo. More documentation is available on the Rush website: https://rushjs.io
|
||||
*/
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/common-versions.schema.json",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* This configuration file allows repo maintainers to enable and disable experimental
|
||||
* Rush features. For full documentation, please see https://rushjs.io
|
||||
* Rush features. More documentation is available on the Rush website: https://rushjs.io
|
||||
*/
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/experiments.schema.json"
|
||||
|
@ -15,14 +15,35 @@
|
|||
// "legacyIncrementalBuildDependencyDetection": true,
|
||||
|
||||
/**
|
||||
* By default, rush passes --no-prefer-frozen-lockfile to 'pnpm install'.
|
||||
* Set this option to true to pass '--frozen-lockfile' instead.
|
||||
* By default, 'rush install' passes --no-prefer-frozen-lockfile to 'pnpm install'.
|
||||
* Set this option to true to pass '--frozen-lockfile' instead for faster installs.
|
||||
*/
|
||||
// "usePnpmFrozenLockfileForRushInstall": true,
|
||||
|
||||
/**
|
||||
* By default, 'rush update' passes --no-prefer-frozen-lockfile to 'pnpm install'.
|
||||
* Set this option to true to pass '--prefer-frozen-lockfile' instead to minimize shrinkwrap changes.
|
||||
*/
|
||||
// "usePnpmPreferFrozenLockfileForRushUpdate": true,
|
||||
|
||||
/**
|
||||
* If using the 'preventManualShrinkwrapChanges' option, restricts the hash to only include the layout of external dependencies.
|
||||
* Used to allow links between workspace projects or the addition/removal of references to existing dependency versions to not
|
||||
* cause hash changes.
|
||||
*/
|
||||
// "omitImportersFromPreventManualShrinkwrapChanges": true,
|
||||
|
||||
/**
|
||||
* If true, the chmod field in temporary project tar headers will not be normalized.
|
||||
* This normalization can help ensure consistent tarball integrity across platforms.
|
||||
*/
|
||||
// "noChmodFieldInTarHeaderNormalization": true
|
||||
// "noChmodFieldInTarHeaderNormalization": true,
|
||||
|
||||
/**
|
||||
* If true, the build cache feature is enabled. To use this feature, a common/config/rush/build-cache.json
|
||||
* file must be created with configuration options.
|
||||
*
|
||||
* See https://github.com/microsoft/rushstack/issues/2393 for details about this experimental feature.
|
||||
*/
|
||||
// "buildCache": true
|
||||
}
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* This is configuration file is used for advanced publishing configurations with Rush.
|
||||
* For full documentation, please see https://rushjs.io
|
||||
* More documentation is available on the Rush website: https://rushjs.io
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,7 +16,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
|
|
|
@ -16,7 +16,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
|
|
37
rush.json
37
rush.json
|
@ -16,7 +16,7 @@
|
|||
* path segment in the "$schema" field for all your Rush config files. This will ensure
|
||||
* correct error-underlining and tab-completion for editors such as VS Code.
|
||||
*/
|
||||
"rushVersion": "5.35.2",
|
||||
"rushVersion": "5.44.0",
|
||||
|
||||
/**
|
||||
* The next field selects which package manager should be installed and determines its version.
|
||||
|
@ -26,7 +26,7 @@
|
|||
* Specify one of: "pnpmVersion", "npmVersion", or "yarnVersion". See the Rush documentation
|
||||
* for details about these alternatives.
|
||||
*/
|
||||
"pnpmVersion": "5.13.4",
|
||||
"pnpmVersion": "5.15.2",
|
||||
|
||||
// "npmVersion": "4.5.0",
|
||||
// "yarnVersion": "1.9.4",
|
||||
|
@ -100,7 +100,7 @@
|
|||
* If true, then `rush install` will use the PNPM workspaces feature to perform the
|
||||
* install.
|
||||
*
|
||||
* This feature uses PNPM to peform the entire monorepo install. When using workspaces, Rush will
|
||||
* This feature uses PNPM to perform the entire monorepo install. When using workspaces, Rush will
|
||||
* generate a "pnpm-workspace.yaml" file referencing all local projects to install. Rush will
|
||||
* also generate a "pnpmfile.js" which is used to provide preferred versions support. When install
|
||||
* is run, this pnpmfile will be used to replace dependency version ranges with a smaller subset
|
||||
|
@ -120,9 +120,11 @@
|
|||
*
|
||||
* Specify a SemVer range to ensure developers use a Node.js version that is appropriate
|
||||
* for your repo.
|
||||
*
|
||||
* LTS schedule: https://nodejs.org/en/about/releases/
|
||||
* LTS versions: https://nodejs.org/en/download/releases/
|
||||
*/
|
||||
// "nodeSupportedVersionRange": ">=12.13.0 <13.0.0",
|
||||
"nodeSupportedVersionRange": ">=10.13.0 <11.0.0 || >=12.13.0 <13.0.0 || >=14.0.0 <15.0.0",
|
||||
"nodeSupportedVersionRange": ">=12.13.0 <13.0.0 || >=14.15.0 <15.0.0",
|
||||
|
||||
/**
|
||||
* Odd-numbered major versions of Node.js are experimental. Even-numbered releases
|
||||
|
@ -158,7 +160,7 @@
|
|||
*
|
||||
* The Rush developers recommend a "category folder" model, where buildable project folders
|
||||
* must always be exactly two levels below the repo root. The parent folder acts as the category.
|
||||
* This provides a basic facility for grouping related projects (e.g. "apps", "libaries",
|
||||
* This provides a basic facility for grouping related projects (e.g. "apps", "libraries",
|
||||
* "tools", "prototypes") while still encouraging teams to organize their projects into
|
||||
* a unified taxonomy. Limiting to 2 levels seems very restrictive at first, but if you have
|
||||
* 20 categories and 20 projects in each category, this scheme can easily accommodate hundreds
|
||||
|
@ -239,7 +241,7 @@
|
|||
* They are case-insensitive anchored JavaScript RegExps. Example: ".*@example\.com"
|
||||
*
|
||||
* IMPORTANT: Because these are regular expressions encoded as JSON string literals,
|
||||
* RegExp escapes need two backspashes, and ordinary periods should be "\\.".
|
||||
* RegExp escapes need two backslashes, and ordinary periods should be "\\.".
|
||||
*/
|
||||
"allowedEmailRegExps": ["[^@]+@users\\.noreply\\.github\\.com"],
|
||||
|
||||
|
@ -257,7 +259,15 @@
|
|||
* you might configure your system's trigger to look for a special string such as "[skip-ci]"
|
||||
* in the commit message, and then customize Rush's message to contain that string.
|
||||
*/
|
||||
// "versionBumpCommitMessage": "Applying package updates. [skip-ci]"
|
||||
// "versionBumpCommitMessage": "Applying package updates. [skip-ci]",
|
||||
/**
|
||||
* The commit message to use when committing changes during 'rush version'.
|
||||
*
|
||||
* For example, if you want to prevent these commits from triggering a CI build,
|
||||
* you might configure your system's trigger to look for a special string such as "[skip-ci]"
|
||||
* in the commit message, and then customize Rush's message to contain that string.
|
||||
*/
|
||||
// "changeLogUpdateCommitMessage": "Deleting change files and updating change logs for package updates. [skip-ci]"
|
||||
},
|
||||
|
||||
"repository": {
|
||||
|
@ -322,7 +332,7 @@
|
|||
* Installation variants allow you to maintain a parallel set of configuration files that can be
|
||||
* used to build the entire monorepo with an alternate set of dependencies. For example, suppose
|
||||
* you upgrade all your projects to use a new release of an important framework, but during a transition period
|
||||
* you intend to maintain compability with the old release. In this situation, you probably want your
|
||||
* you intend to maintain compatibility with the old release. In this situation, you probably want your
|
||||
* CI validation to build the entire repo twice: once with the old release, and once with the new release.
|
||||
*
|
||||
* Rush "installation variants" correspond to sets of config files located under this folder:
|
||||
|
@ -416,6 +426,15 @@
|
|||
// // "shouldPublish": false,
|
||||
//
|
||||
// /**
|
||||
// * Facilitates postprocessing of a project's files prior to publishing.
|
||||
// *
|
||||
// * If specified, the "publishFolder" is the relative path to a subfolder of the project folder.
|
||||
// * The "rush publish" command will publish the subfolder instead of the project folder. The subfolder
|
||||
// * must contain its own package.json file, which is typically a build output.
|
||||
// */
|
||||
// // "publishFolder": "temp/publish",
|
||||
//
|
||||
// /**
|
||||
// * An optional version policy associated with the project. Version policies are defined
|
||||
// * in "version-policies.json" file. See the "rush publish" documentation for more info.
|
||||
// * NOTE: "versionPolicyName" and "shouldPublish" are alternatives; you cannot specify them both.
|
||||
|
|
|
@ -12,13 +12,19 @@ interface ISnapshot {
|
|||
s5_extends: ISnapshot[];
|
||||
}
|
||||
|
||||
function replaceAll(text: string, search: string, replace: string): string {
|
||||
return text.split(search).join(replace);
|
||||
}
|
||||
|
||||
// To make the unit tests deterministic, we need to replace all OS-dependent absolute paths
|
||||
// with OS-independent paths that are relative to the unit test folder.
|
||||
function makeStablePath(testPath: string): string {
|
||||
if (testPath.length === 0) {
|
||||
return '';
|
||||
}
|
||||
return '.../' + path.relative(__dirname, testPath).split('\\').join('/');
|
||||
console.log('IN: ' + testPath);
|
||||
console.log('OUT: ' + replaceAll(path.relative(__dirname, testPath), '\\', '/'));
|
||||
return '.../' + replaceAll(path.relative(__dirname, testPath), '\\', '/');
|
||||
}
|
||||
|
||||
// Build a map from absolute path --> stable path, for each TSDocConfigFile.filePath value
|
||||
|
@ -37,9 +43,14 @@ function buildStablePathMap(stablePathMap: Map<string, string>, configFile: TSDo
|
|||
// Search and replace all absolute paths with the corresponding stable path.
|
||||
// For example, "Found C:\A\B\C.txt in C:\A\D\E.txt" becomes "Found .../B/C.txt in .../D/E.txt".
|
||||
function convertToStablePaths(text: string, stablePathMap: Map<string, string>): string {
|
||||
for (const pair of Array.from(stablePathMap.entries())) {
|
||||
text = text.split(pair[0]).join(pair[1]);
|
||||
// Sort the [key,value] pairs by key length from longest to shortest.
|
||||
// This ensures that a shorter path does not replace a subpath of a longer path.
|
||||
const pairs: [string, string][] = Array.from(stablePathMap.entries()).sort((x, y) => y[0].length - x[0].length);
|
||||
for (const pair of pairs) {
|
||||
text = replaceAll(text, pair[0], pair[1]);
|
||||
}
|
||||
// Also convert any loose backslashes to slashes
|
||||
//text = replaceAll(text, '\\', '/');
|
||||
return text;
|
||||
}
|
||||
|
||||
|
@ -141,54 +152,54 @@ test('Load e5', () => {
|
|||
"s0_filePath": ".../assets/e5/tsdoc.json",
|
||||
"s1_fileNotFound": false,
|
||||
"s2_hasErrors": true,
|
||||
"s3_errorSummary": "Error encountered for .../assets/e5\\\\tsdoc-a.json:
|
||||
Circular reference encountered for \\"extends\\" field of \\".../assets/e5\\\\tsdoc-b.json\\"
|
||||
"s3_errorSummary": "Error encountered for .../assets/e5/tsdoc-a.json:
|
||||
Circular reference encountered for \\"extends\\" field of \\".../assets/e5/tsdoc-b.json\\"
|
||||
|
||||
Error encountered for .../assets/e5\\\\tsdoc-c.json:
|
||||
Error encountered for .../assets/e5/tsdoc-c.json:
|
||||
Error loading config file: data should NOT have additional properties
|
||||
",
|
||||
"s4_log": Array [],
|
||||
"s5_extends": Array [
|
||||
Object {
|
||||
"s0_filePath": ".../assets/e5\\\\tsdoc-a.json",
|
||||
"s0_filePath": ".../assets/e5/tsdoc-a.json",
|
||||
"s1_fileNotFound": false,
|
||||
"s2_hasErrors": true,
|
||||
"s3_errorSummary": "Error encountered for .../assets/e5\\\\tsdoc-a.json:
|
||||
Circular reference encountered for \\"extends\\" field of \\".../assets/e5\\\\tsdoc-b.json\\"
|
||||
"s3_errorSummary": "Error encountered for .../assets/e5/tsdoc-a.json:
|
||||
Circular reference encountered for \\"extends\\" field of \\".../assets/e5/tsdoc-b.json\\"
|
||||
|
||||
Error encountered for .../assets/e5\\\\tsdoc-c.json:
|
||||
Error encountered for .../assets/e5/tsdoc-c.json:
|
||||
Error loading config file: data should NOT have additional properties
|
||||
",
|
||||
"s4_log": Array [],
|
||||
"s5_extends": Array [
|
||||
Object {
|
||||
"s0_filePath": ".../assets/e5\\\\tsdoc-b.json",
|
||||
"s0_filePath": ".../assets/e5/tsdoc-b.json",
|
||||
"s1_fileNotFound": false,
|
||||
"s2_hasErrors": true,
|
||||
"s3_errorSummary": "Error encountered for .../assets/e5\\\\tsdoc-a.json:
|
||||
Circular reference encountered for \\"extends\\" field of \\".../assets/e5\\\\tsdoc-b.json\\"
|
||||
"s3_errorSummary": "Error encountered for .../assets/e5/tsdoc-a.json:
|
||||
Circular reference encountered for \\"extends\\" field of \\".../assets/e5/tsdoc-b.json\\"
|
||||
",
|
||||
"s4_log": Array [],
|
||||
"s5_extends": Array [
|
||||
Object {
|
||||
"s0_filePath": ".../assets/e5\\\\tsdoc-a.json",
|
||||
"s0_filePath": ".../assets/e5/tsdoc-a.json",
|
||||
"s1_fileNotFound": false,
|
||||
"s2_hasErrors": true,
|
||||
"s3_errorSummary": "Error encountered for .../assets/e5\\\\tsdoc-a.json:
|
||||
Circular reference encountered for \\"extends\\" field of \\".../assets/e5\\\\tsdoc-b.json\\"
|
||||
"s3_errorSummary": "Error encountered for .../assets/e5/tsdoc-a.json:
|
||||
Circular reference encountered for \\"extends\\" field of \\".../assets/e5/tsdoc-b.json\\"
|
||||
",
|
||||
"s4_log": Array [
|
||||
"[tsdoc-config-cyclic-extends] Circular reference encountered for \\"extends\\" field of \\".../assets/e5\\\\tsdoc-b.json\\"",
|
||||
"[tsdoc-config-cyclic-extends] Circular reference encountered for \\"extends\\" field of \\".../assets/e5/tsdoc-b.json\\"",
|
||||
],
|
||||
"s5_extends": Array [],
|
||||
},
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"s0_filePath": ".../assets/e5\\\\tsdoc-c.json",
|
||||
"s0_filePath": ".../assets/e5/tsdoc-c.json",
|
||||
"s1_fileNotFound": false,
|
||||
"s2_hasErrors": true,
|
||||
"s3_errorSummary": "Error encountered for .../assets/e5\\\\tsdoc-c.json:
|
||||
"s3_errorSummary": "Error encountered for .../assets/e5/tsdoc-c.json:
|
||||
Error loading config file: data should NOT have additional properties
|
||||
",
|
||||
"s4_log": Array [
|
||||
|
|
Загрузка…
Ссылка в новой задаче