feat(js): generate package.json with overrides and resolutions (#27601)
This PR ensures that `overrides` and `resolutions` are in the generated package.json file as well. If they are missing, then using `--frozen-lockfile` will fail due to mismatched overrides in the lockfile. Also adds a `skipOverrides` flag to the affected executors and plugins -- same as `skipPackageManger` that was added previously. Affected executors/plugins: - `@nx/vite:build` - `@nx/webpack:webpack` - `@nx/remix:build` - `@nx/next:build` - `NxAppWebpackPlugin` <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #26884
This commit is contained in:
parent
35899e3a25
commit
042049c785
@ -61,6 +61,10 @@
|
|||||||
"default": false,
|
"default": false,
|
||||||
"x-priority": "internal"
|
"x-priority": "internal"
|
||||||
},
|
},
|
||||||
|
"skipOverrides": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do not add a `overrides` and `resolutions` entries to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
||||||
|
},
|
||||||
"skipPackageManager": {
|
"skipPackageManager": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Do not add a `packageManager` entry to the generated package.json file."
|
"description": "Do not add a `packageManager` entry to the generated package.json file."
|
||||||
|
|||||||
@ -31,6 +31,10 @@
|
|||||||
"description": "Generate a lockfile (e.g. package-lock.json) that matches the workspace lockfile to ensure package versions match.",
|
"description": "Generate a lockfile (e.g. package-lock.json) that matches the workspace lockfile to ensure package versions match.",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
|
"skipOverrides": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do not add a `overrides` and `resolutions` entries to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
||||||
|
},
|
||||||
"skipPackageManager": {
|
"skipPackageManager": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Do not add a `packageManager` entry to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
"description": "Do not add a `packageManager` entry to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
||||||
|
|||||||
@ -51,6 +51,10 @@
|
|||||||
"description": "Include devDependencies in the generated package.json.",
|
"description": "Include devDependencies in the generated package.json.",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"skipOverrides": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do not add a `overrides` and `resolutions` entries to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
||||||
|
},
|
||||||
"skipPackageManager": {
|
"skipPackageManager": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Do not add a `packageManager` entry to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
"description": "Do not add a `packageManager` entry to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
||||||
|
|||||||
@ -239,6 +239,10 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Generates a `package.json` and pruned lock file with the project's `node_module` dependencies populated for installing in a container. If a `package.json` exists in the project's directory, it will be reused with dependencies populated."
|
"description": "Generates a `package.json` and pruned lock file with the project's `node_module` dependencies populated for installing in a container. If a `package.json` exists in the project's directory, it will be reused with dependencies populated."
|
||||||
},
|
},
|
||||||
|
"skipOverrides": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do not add a `overrides` and `resolutions` entries to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
||||||
|
},
|
||||||
"skipPackageManager": {
|
"skipPackageManager": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Do not add a `packageManager` entry to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
"description": "Do not add a `packageManager` entry to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
||||||
|
|||||||
@ -215,6 +215,18 @@ Type: `string[]`
|
|||||||
|
|
||||||
External scripts that will be included before the main application entry.
|
External scripts that will be included before the main application entry.
|
||||||
|
|
||||||
|
##### skipOverrides
|
||||||
|
|
||||||
|
Type: `boolean`
|
||||||
|
|
||||||
|
Do not add a `overrides` and `resolutions` entries to the generated package.json file. Only works in conjunction with `generatePackageJson` option.
|
||||||
|
|
||||||
|
##### skipPackageManager
|
||||||
|
|
||||||
|
Type: `boolean`
|
||||||
|
|
||||||
|
Do not add a `packageManager` entry to the generated package.json file. Only works in conjunction with `generatePackageJson` option.
|
||||||
|
|
||||||
##### skipTypeChecking
|
##### skipTypeChecking
|
||||||
|
|
||||||
Type: `boolean`
|
Type: `boolean`
|
||||||
|
|||||||
@ -81,6 +81,7 @@ export default async function buildExecutor(
|
|||||||
target: context.targetName,
|
target: context.targetName,
|
||||||
root: context.root,
|
root: context.root,
|
||||||
isProduction: !options.includeDevDependenciesInPackageJson, // By default we remove devDependencies since this is a production build.
|
isProduction: !options.includeDevDependenciesInPackageJson, // By default we remove devDependencies since this is a production build.
|
||||||
|
skipOverrides: options.skipOverrides,
|
||||||
skipPackageManager: options.skipPackageManager,
|
skipPackageManager: options.skipPackageManager,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@ -58,6 +58,10 @@
|
|||||||
"default": false,
|
"default": false,
|
||||||
"x-priority": "internal"
|
"x-priority": "internal"
|
||||||
},
|
},
|
||||||
|
"skipOverrides": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do not add a `overrides` and `resolutions` entries to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
||||||
|
},
|
||||||
"skipPackageManager": {
|
"skipPackageManager": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Do not add a `packageManager` entry to the generated package.json file."
|
"description": "Do not add a `packageManager` entry to the generated package.json file."
|
||||||
|
|||||||
@ -39,6 +39,7 @@ export interface NextBuildBuilderOptions {
|
|||||||
nextConfig?: string;
|
nextConfig?: string;
|
||||||
outputPath: string;
|
outputPath: string;
|
||||||
profile?: boolean;
|
profile?: boolean;
|
||||||
|
skipOverrides?: boolean;
|
||||||
skipPackageManager?: boolean;
|
skipPackageManager?: boolean;
|
||||||
watch?: boolean;
|
watch?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -779,6 +779,227 @@ describe('createPackageJson', () => {
|
|||||||
/Package Manager Mismatch/
|
/Package Manager Mismatch/
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add overrides (pnpm)', () => {
|
||||||
|
spies.push(
|
||||||
|
jest
|
||||||
|
.spyOn(fs, 'existsSync')
|
||||||
|
.mockImplementation(
|
||||||
|
(path) =>
|
||||||
|
path === 'libs/lib1/package.json' ||
|
||||||
|
path === 'apps/app1/package.json' ||
|
||||||
|
path === 'package.json'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
spies.push(
|
||||||
|
jest
|
||||||
|
.spyOn(fileutilsModule, 'readJsonFile')
|
||||||
|
.mockImplementation((path) => {
|
||||||
|
if (path === 'package.json') {
|
||||||
|
return {
|
||||||
|
...rootPackageJson(),
|
||||||
|
pnpm: {
|
||||||
|
overrides: {
|
||||||
|
foo: '1.0.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (path === 'libs/lib1/package.json') {
|
||||||
|
return projectPackageJson();
|
||||||
|
}
|
||||||
|
if (path === 'apps/app1/package.json') {
|
||||||
|
return {
|
||||||
|
...projectPackageJson(),
|
||||||
|
pnpm: {
|
||||||
|
overrides: {
|
||||||
|
foo: '2.0.0',
|
||||||
|
bar: '1.0.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
createPackageJson('lib1', graph, {
|
||||||
|
root: '',
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
dependencies: {
|
||||||
|
random: '1.0.0',
|
||||||
|
typescript: '^4.8.4',
|
||||||
|
},
|
||||||
|
name: 'other-name',
|
||||||
|
version: '1.2.3',
|
||||||
|
pnpm: {
|
||||||
|
overrides: {
|
||||||
|
foo: '1.0.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
createPackageJson('app1', graph, {
|
||||||
|
root: '',
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
dependencies: {
|
||||||
|
random: '1.0.0',
|
||||||
|
typescript: '^4.8.4',
|
||||||
|
},
|
||||||
|
name: 'other-name',
|
||||||
|
version: '1.2.3',
|
||||||
|
pnpm: {
|
||||||
|
overrides: {
|
||||||
|
foo: '2.0.0',
|
||||||
|
bar: '1.0.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add overrides (npm)', () => {
|
||||||
|
spies.push(
|
||||||
|
jest
|
||||||
|
.spyOn(fs, 'existsSync')
|
||||||
|
.mockImplementation(
|
||||||
|
(path) =>
|
||||||
|
path === 'libs/lib1/package.json' ||
|
||||||
|
path === 'apps/app1/package.json' ||
|
||||||
|
path === 'package.json'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
spies.push(
|
||||||
|
jest
|
||||||
|
.spyOn(fileutilsModule, 'readJsonFile')
|
||||||
|
.mockImplementation((path) => {
|
||||||
|
if (path === 'package.json') {
|
||||||
|
return {
|
||||||
|
...rootPackageJson(),
|
||||||
|
overrides: {
|
||||||
|
foo: '1.0.0',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (path === 'libs/lib1/package.json') {
|
||||||
|
return projectPackageJson();
|
||||||
|
}
|
||||||
|
if (path === 'apps/app1/package.json') {
|
||||||
|
return {
|
||||||
|
...projectPackageJson(),
|
||||||
|
overrides: {
|
||||||
|
foo: '2.0.0',
|
||||||
|
bar: '1.0.0',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
createPackageJson('lib1', graph, {
|
||||||
|
root: '',
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
dependencies: {
|
||||||
|
random: '1.0.0',
|
||||||
|
typescript: '^4.8.4',
|
||||||
|
},
|
||||||
|
name: 'other-name',
|
||||||
|
version: '1.2.3',
|
||||||
|
overrides: {
|
||||||
|
foo: '1.0.0',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
createPackageJson('app1', graph, {
|
||||||
|
root: '',
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
dependencies: {
|
||||||
|
random: '1.0.0',
|
||||||
|
typescript: '^4.8.4',
|
||||||
|
},
|
||||||
|
name: 'other-name',
|
||||||
|
version: '1.2.3',
|
||||||
|
overrides: {
|
||||||
|
foo: '2.0.0',
|
||||||
|
bar: '1.0.0',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add resolutions (yarn)', () => {
|
||||||
|
spies.push(
|
||||||
|
jest
|
||||||
|
.spyOn(fs, 'existsSync')
|
||||||
|
.mockImplementation(
|
||||||
|
(path) =>
|
||||||
|
path === 'libs/lib1/package.json' ||
|
||||||
|
path === 'apps/app1/package.json' ||
|
||||||
|
path === 'package.json'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
spies.push(
|
||||||
|
jest
|
||||||
|
.spyOn(fileutilsModule, 'readJsonFile')
|
||||||
|
.mockImplementation((path) => {
|
||||||
|
if (path === 'package.json') {
|
||||||
|
return {
|
||||||
|
...rootPackageJson(),
|
||||||
|
resolutions: {
|
||||||
|
foo: '1.0.0',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (path === 'libs/lib1/package.json') {
|
||||||
|
return projectPackageJson();
|
||||||
|
}
|
||||||
|
if (path === 'apps/app1/package.json') {
|
||||||
|
return {
|
||||||
|
...projectPackageJson(),
|
||||||
|
resolutions: {
|
||||||
|
foo: '2.0.0',
|
||||||
|
bar: '1.0.0',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
createPackageJson('lib1', graph, {
|
||||||
|
root: '',
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
dependencies: {
|
||||||
|
random: '1.0.0',
|
||||||
|
typescript: '^4.8.4',
|
||||||
|
},
|
||||||
|
name: 'other-name',
|
||||||
|
version: '1.2.3',
|
||||||
|
resolutions: {
|
||||||
|
foo: '1.0.0',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
createPackageJson('app1', graph, {
|
||||||
|
root: '',
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
dependencies: {
|
||||||
|
random: '1.0.0',
|
||||||
|
typescript: '^4.8.4',
|
||||||
|
},
|
||||||
|
name: 'other-name',
|
||||||
|
version: '1.2.3',
|
||||||
|
resolutions: {
|
||||||
|
foo: '2.0.0',
|
||||||
|
bar: '1.0.0',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -39,6 +39,7 @@ export function createPackageJson(
|
|||||||
isProduction?: boolean;
|
isProduction?: boolean;
|
||||||
helperDependencies?: string[];
|
helperDependencies?: string[];
|
||||||
skipPackageManager?: boolean;
|
skipPackageManager?: boolean;
|
||||||
|
skipOverrides?: boolean;
|
||||||
} = {},
|
} = {},
|
||||||
fileMap: ProjectFileMap = null
|
fileMap: ProjectFileMap = null
|
||||||
): PackageJson {
|
): PackageJson {
|
||||||
@ -198,6 +199,34 @@ export function createPackageJson(
|
|||||||
packageJson.packageManager = rootPackageJson.packageManager;
|
packageJson.packageManager = rootPackageJson.packageManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// region Overrides/Resolutions
|
||||||
|
|
||||||
|
// npm
|
||||||
|
if (rootPackageJson.overrides && !options.skipOverrides) {
|
||||||
|
packageJson.overrides = {
|
||||||
|
...rootPackageJson.overrides,
|
||||||
|
...packageJson.overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// pnpm
|
||||||
|
if (rootPackageJson.pnpm?.overrides && !options.skipOverrides) {
|
||||||
|
packageJson.pnpm ??= {};
|
||||||
|
packageJson.pnpm.overrides = {
|
||||||
|
...rootPackageJson.pnpm.overrides,
|
||||||
|
...packageJson.pnpm.overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// yarn
|
||||||
|
if (rootPackageJson.resolutions && !options.skipOverrides) {
|
||||||
|
packageJson.resolutions = {
|
||||||
|
...rootPackageJson.resolutions,
|
||||||
|
...packageJson.resolutions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// endregion Overrides/Resolutions
|
||||||
|
|
||||||
return packageJson;
|
return packageJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -54,6 +54,9 @@ export interface PackageJson {
|
|||||||
peerDependencies?: Record<string, string>;
|
peerDependencies?: Record<string, string>;
|
||||||
peerDependenciesMeta?: Record<string, { optional: boolean }>;
|
peerDependenciesMeta?: Record<string, { optional: boolean }>;
|
||||||
resolutions?: Record<string, string>;
|
resolutions?: Record<string, string>;
|
||||||
|
pnpm?: {
|
||||||
|
overrides?: PackageOverride;
|
||||||
|
};
|
||||||
overrides?: PackageOverride;
|
overrides?: PackageOverride;
|
||||||
bin?: Record<string, string> | string;
|
bin?: Record<string, string> | string;
|
||||||
workspaces?:
|
workspaces?:
|
||||||
|
|||||||
@ -28,6 +28,10 @@
|
|||||||
"description": "Generate a lockfile (e.g. package-lock.json) that matches the workspace lockfile to ensure package versions match.",
|
"description": "Generate a lockfile (e.g. package-lock.json) that matches the workspace lockfile to ensure package versions match.",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
|
"skipOverrides": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do not add a `overrides` and `resolutions` entries to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
||||||
|
},
|
||||||
"skipPackageManager": {
|
"skipPackageManager": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Do not add a `packageManager` entry to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
"description": "Do not add a `packageManager` entry to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
||||||
|
|||||||
@ -126,6 +126,7 @@ export async function* viteBuildExecutor(
|
|||||||
target: context.targetName,
|
target: context.targetName,
|
||||||
root: context.root,
|
root: context.root,
|
||||||
isProduction: !options.includeDevDependenciesInPackageJson, // By default we remove devDependencies since this is a production build.
|
isProduction: !options.includeDevDependenciesInPackageJson, // By default we remove devDependencies since this is a production build.
|
||||||
|
skipOverrides: options.skipOverrides,
|
||||||
skipPackageManager: options.skipPackageManager,
|
skipPackageManager: options.skipPackageManager,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@ -4,6 +4,7 @@ export interface ViteBuildExecutorOptions {
|
|||||||
generatePackageJson?: boolean;
|
generatePackageJson?: boolean;
|
||||||
includeDevDependenciesInPackageJson?: boolean;
|
includeDevDependenciesInPackageJson?: boolean;
|
||||||
outputPath?: string;
|
outputPath?: string;
|
||||||
|
skipOverrides?: boolean;
|
||||||
skipPackageManager?: boolean;
|
skipPackageManager?: boolean;
|
||||||
skipTypeCheck?: boolean;
|
skipTypeCheck?: boolean;
|
||||||
tsConfig?: string;
|
tsConfig?: string;
|
||||||
|
|||||||
@ -60,6 +60,10 @@
|
|||||||
"description": "Include devDependencies in the generated package.json.",
|
"description": "Include devDependencies in the generated package.json.",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"skipOverrides": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do not add a `overrides` and `resolutions` entries to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
||||||
|
},
|
||||||
"skipPackageManager": {
|
"skipPackageManager": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Do not add a `packageManager` entry to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
"description": "Do not add a `packageManager` entry to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
||||||
|
|||||||
@ -163,6 +163,10 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Generates a `package.json` and pruned lock file with the project's `node_module` dependencies populated for installing in a container. If a `package.json` exists in the project's directory, it will be reused with dependencies populated."
|
"description": "Generates a `package.json` and pruned lock file with the project's `node_module` dependencies populated for installing in a container. If a `package.json` exists in the project's directory, it will be reused with dependencies populated."
|
||||||
},
|
},
|
||||||
|
"skipOverrides": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do not add a `overrides` and `resolutions` entries to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
||||||
|
},
|
||||||
"skipPackageManager": {
|
"skipPackageManager": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Do not add a `packageManager` entry to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
"description": "Do not add a `packageManager` entry to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
||||||
|
|||||||
@ -167,6 +167,10 @@ export interface NxAppWebpackPluginOptions {
|
|||||||
* External scripts that will be included before the main application entry.
|
* External scripts that will be included before the main application entry.
|
||||||
*/
|
*/
|
||||||
scripts?: Array<ExtraEntryPointClass | string>;
|
scripts?: Array<ExtraEntryPointClass | string>;
|
||||||
|
/**
|
||||||
|
* Do not add a `overrides` and `resolutions` entries to the generated package.json file. Only works in conjunction with `generatePackageJson` option.
|
||||||
|
*/
|
||||||
|
skipOverrides?: boolean;
|
||||||
/**
|
/**
|
||||||
* Do not add a `packageManager` entry to the generated package.json file. Only works in conjunction with `generatePackageJson` option.
|
* Do not add a `packageManager` entry to the generated package.json file. Only works in conjunction with `generatePackageJson` option.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user