Compare commits
11 Commits
patch-error
...
10.4.6
| Author | SHA1 | Date | |
|---|---|---|---|
| b030a761a8 | |||
| 6b9942f5af | |||
| ceac479da3 | |||
| 5f0bf8a37d | |||
| da05843b84 | |||
| 021c842677 | |||
| cb81c1cd62 | |||
| 344e16005d | |||
| 5023a8bf59 | |||
| cbd404ccd1 | |||
| 95889a2447 |
@@ -54,6 +54,14 @@ Type: `string`
|
||||
|
||||
undefined
|
||||
|
||||
### generatePackageJson
|
||||
|
||||
Default: `false`
|
||||
|
||||
Type: `boolean`
|
||||
|
||||
Generates a package.json 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.
|
||||
|
||||
### main
|
||||
|
||||
Type: `string`
|
||||
|
||||
@@ -55,6 +55,14 @@ Type: `string`
|
||||
|
||||
undefined
|
||||
|
||||
### generatePackageJson
|
||||
|
||||
Default: `false`
|
||||
|
||||
Type: `boolean`
|
||||
|
||||
Generates a package.json 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.
|
||||
|
||||
### main
|
||||
|
||||
Type: `string`
|
||||
|
||||
@@ -55,6 +55,14 @@ Type: `string`
|
||||
|
||||
undefined
|
||||
|
||||
### generatePackageJson
|
||||
|
||||
Default: `false`
|
||||
|
||||
Type: `boolean`
|
||||
|
||||
Generates a package.json 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.
|
||||
|
||||
### main
|
||||
|
||||
Type: `string`
|
||||
|
||||
@@ -246,6 +246,31 @@ forEachCli((currentCLIName) => {
|
||||
}, 120000);
|
||||
});
|
||||
|
||||
describe('Build Node apps', () => {
|
||||
it('should generate a package.json with the `--generatePackageJson` flag', async () => {
|
||||
ensureProject();
|
||||
const nestapp = uniq('nestapp');
|
||||
runCLI(`generate @nrwl/nest:app ${nestapp} --linter=eslint`);
|
||||
|
||||
await runCLIAsync(`build ${nestapp} --generatePackageJson`);
|
||||
|
||||
checkFilesExist(`dist/apps/${nestapp}/package.json`);
|
||||
const packageJson = JSON.parse(
|
||||
readFile(`dist/apps/${nestapp}/package.json`)
|
||||
);
|
||||
expect(packageJson).toEqual(
|
||||
expect.objectContaining({
|
||||
dependencies: {
|
||||
'@nestjs/common': '^7.0.0',
|
||||
'@nestjs/core': '^7.0.0',
|
||||
},
|
||||
main: 'main.js',
|
||||
name: expect.any(String),
|
||||
version: '0.0.1',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('Node Libraries', () => {
|
||||
it('should be able to generate a node library', async () => {
|
||||
ensureProject();
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nrwl/nx-source",
|
||||
"version": "10.4.2",
|
||||
"version": "10.4.6",
|
||||
"description": "Extensible Dev Tools for Monorepos",
|
||||
"homepage": "https://nx.dev",
|
||||
"main": "index.js",
|
||||
|
||||
@@ -9,6 +9,7 @@ import { parseRunOneOptions } from './parse-run-one-options';
|
||||
* @param workspace Relevant local workspace properties
|
||||
*/
|
||||
process.env.NX_CLI_SET = 'true';
|
||||
|
||||
export function initLocal(workspace: Workspace) {
|
||||
require('@nrwl/workspace/' + 'src/utils/perf-logging');
|
||||
const supportedNxCommands = require('@nrwl/workspace/' +
|
||||
@@ -24,7 +25,25 @@ export function initLocal(workspace: Workspace) {
|
||||
.argv;
|
||||
} else {
|
||||
if (runOpts === false || process.env.NX_SKIP_TASKS_RUNNER) {
|
||||
loadCli(workspace);
|
||||
if (workspace.type === 'angular' && process.argv[2] === 'update') {
|
||||
console.log(
|
||||
`Nx provides a much improved version of "ng update". It runs the same migrations, but allows you to:`
|
||||
);
|
||||
console.log(`- rerun the same migration multiple times`);
|
||||
console.log(`- reorder migrations`);
|
||||
console.log(`- skip migrations`);
|
||||
console.log(`- fix migrations that "almost work"`);
|
||||
console.log(`- commit a partially migrated state`);
|
||||
console.log(`- change versions of packages to match org requirements`);
|
||||
console.log(
|
||||
`And, in general, it is lot more reliable for non-trivial workspaces. Read more at: https://nx.dev/latest/angular/workspace/update`
|
||||
);
|
||||
console.log(
|
||||
`Run "nx migrate latest" to update to the latest version of Nx.`
|
||||
);
|
||||
} else {
|
||||
loadCli(workspace);
|
||||
}
|
||||
} else {
|
||||
require('@nrwl/workspace' + '/src/command-line/run-one').runOne(runOpts);
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import { runWebpack, BuildResult } from '@angular-devkit/build-webpack';
|
||||
|
||||
import { Observable, from } from 'rxjs';
|
||||
import { join, resolve } from 'path';
|
||||
import { map, concatMap } from 'rxjs/operators';
|
||||
import { concatMap, map, tap } from 'rxjs/operators';
|
||||
import { getNodeWebpackConfig } from '../../utils/node.config';
|
||||
import { OUT_FILENAME } from '../../utils/config';
|
||||
import { BuildBuilderOptions } from '../../utils/types';
|
||||
import { BuildNodeBuilderOptions } from '../../utils/types';
|
||||
import { normalizeBuildOptions } from '../../utils/normalize';
|
||||
import { NodeJsSyncHost } from '@angular-devkit/core/node';
|
||||
import { createProjectGraph } from '@nrwl/workspace/src/core/project-graph';
|
||||
@@ -15,18 +15,12 @@ import {
|
||||
calculateProjectDependencies,
|
||||
createTmpTsConfig,
|
||||
} from '@nrwl/workspace/src/utils/buildable-libs-utils';
|
||||
import { generatePackageJson } from '../../utils/generate-package-json';
|
||||
|
||||
try {
|
||||
require('dotenv').config();
|
||||
} catch (e) {}
|
||||
|
||||
export interface BuildNodeBuilderOptions extends BuildBuilderOptions {
|
||||
optimization?: boolean;
|
||||
sourceMap?: boolean;
|
||||
externalDependencies: 'all' | 'none' | string[];
|
||||
buildLibsFromSource?: boolean;
|
||||
}
|
||||
|
||||
export type NodeBuildEvent = BuildResult & {
|
||||
outfile: string;
|
||||
};
|
||||
@@ -37,8 +31,8 @@ function run(
|
||||
options: JsonObject & BuildNodeBuilderOptions,
|
||||
context: BuilderContext
|
||||
): Observable<NodeBuildEvent> {
|
||||
const projGraph = createProjectGraph();
|
||||
if (!options.buildLibsFromSource) {
|
||||
const projGraph = createProjectGraph();
|
||||
const { target, dependencies } = calculateProjectDependencies(
|
||||
projGraph,
|
||||
context
|
||||
@@ -51,10 +45,24 @@ function run(
|
||||
);
|
||||
}
|
||||
|
||||
return from(getSourceRoot(context)).pipe(
|
||||
map((sourceRoot) =>
|
||||
normalizeBuildOptions(options, context.workspaceRoot, sourceRoot)
|
||||
return from(getRoots(context)).pipe(
|
||||
map(({ sourceRoot, projectRoot }) =>
|
||||
normalizeBuildOptions(
|
||||
options,
|
||||
context.workspaceRoot,
|
||||
sourceRoot,
|
||||
projectRoot
|
||||
)
|
||||
),
|
||||
tap((normalizedOptions) => {
|
||||
if (normalizedOptions.generatePackageJson) {
|
||||
generatePackageJson(
|
||||
context.target.project,
|
||||
projGraph,
|
||||
normalizedOptions
|
||||
);
|
||||
}
|
||||
}),
|
||||
map((options) => {
|
||||
let config = getNodeWebpackConfig(options);
|
||||
if (options.webpackConfig) {
|
||||
@@ -84,17 +92,20 @@ function run(
|
||||
);
|
||||
}
|
||||
|
||||
async function getSourceRoot(context: BuilderContext) {
|
||||
async function getRoots(
|
||||
context: BuilderContext
|
||||
): Promise<{ sourceRoot: string; projectRoot: string }> {
|
||||
const workspaceHost = workspaces.createWorkspaceHost(new NodeJsSyncHost());
|
||||
const { workspace } = await workspaces.readWorkspace(
|
||||
context.workspaceRoot,
|
||||
workspaceHost
|
||||
);
|
||||
if (workspace.projects.get(context.target.project).sourceRoot) {
|
||||
return workspace.projects.get(context.target.project).sourceRoot;
|
||||
const project = workspace.projects.get(context.target.project);
|
||||
if (project.sourceRoot && project.root) {
|
||||
return { sourceRoot: project.sourceRoot, projectRoot: project.root };
|
||||
} else {
|
||||
context.reportStatus('Error');
|
||||
const message = `${context.target.project} does not have a sourceRoot. Please define one.`;
|
||||
const message = `${context.target.project} does not have a sourceRoot or root. Please define one.`;
|
||||
context.logger.error(message);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
@@ -117,6 +117,11 @@
|
||||
"type": "boolean",
|
||||
"description": "Read buildable libraries from source instead of building them separately.",
|
||||
"default": false
|
||||
},
|
||||
"generatePackageJson": {
|
||||
"type": "boolean",
|
||||
"description": "Generates a package.json 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.",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"required": ["tsConfig", "main"],
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { ProjectGraph, readJsonFile } from '@nrwl/workspace';
|
||||
import { BuildNodeBuilderOptions } from './types';
|
||||
import { writeJsonFile } from '@nrwl/workspace/src/utils/fileutils';
|
||||
import { OUT_FILENAME } from './config';
|
||||
|
||||
/**
|
||||
* Creates a package.json in the output directory for support to install dependencies within containers.
|
||||
*
|
||||
* If a package.json exists in the project, it will reuse that.
|
||||
*
|
||||
* @param projectName
|
||||
* @param graph
|
||||
* @param options
|
||||
* @constructor
|
||||
*/
|
||||
export function generatePackageJson(
|
||||
projectName: string,
|
||||
graph: ProjectGraph,
|
||||
options: BuildNodeBuilderOptions
|
||||
) {
|
||||
const npmDeps = findAllNpmDeps(projectName, graph);
|
||||
// default package.json if one does not exist
|
||||
let packageJson = {
|
||||
name: projectName,
|
||||
version: '0.0.1',
|
||||
main: OUT_FILENAME,
|
||||
dependencies: {},
|
||||
};
|
||||
|
||||
try {
|
||||
packageJson = readJsonFile(`${options.projectRoot}/package.json`);
|
||||
if (!packageJson.dependencies) {
|
||||
packageJson.dependencies = {};
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
const rootPackageJson = readJsonFile(`${options.root}/package.json`);
|
||||
|
||||
Object.entries(npmDeps).forEach(([packageName, version]) => {
|
||||
// don't include devDeps
|
||||
if (rootPackageJson.devDependencies?.[packageName]) {
|
||||
return;
|
||||
}
|
||||
|
||||
packageJson.dependencies[packageName] = version;
|
||||
});
|
||||
|
||||
writeJsonFile(`${options.outputPath}/package.json`, packageJson);
|
||||
}
|
||||
|
||||
function findAllNpmDeps(
|
||||
projectName: string,
|
||||
graph: ProjectGraph,
|
||||
list: { [packageName: string]: string } = {},
|
||||
seen = new Set<string>()
|
||||
) {
|
||||
if (seen.has(projectName)) {
|
||||
return list;
|
||||
}
|
||||
|
||||
seen.add(projectName);
|
||||
|
||||
const node = graph.nodes[projectName];
|
||||
|
||||
if (node.type === 'npm') {
|
||||
list[node.data.packageName] = node.data.version;
|
||||
}
|
||||
graph.dependencies[projectName]?.forEach((dep) => {
|
||||
findAllNpmDeps(dep.target, graph, list, seen);
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
import { getNodeWebpackConfig } from './node.config';
|
||||
import { BannerPlugin } from 'webpack';
|
||||
jest.mock('tsconfig-paths-webpack-plugin');
|
||||
import TsConfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
|
||||
import { BuildNodeBuilderOptions } from '../builders/build/build.impl';
|
||||
import { BuildNodeBuilderOptions } from './types';
|
||||
|
||||
describe('getNodePartial', () => {
|
||||
let input: BuildNodeBuilderOptions;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Configuration, BannerPlugin } from 'webpack';
|
||||
import { Configuration } from 'webpack';
|
||||
import * as mergeWebpack from 'webpack-merge';
|
||||
import * as nodeExternals from 'webpack-node-externals';
|
||||
|
||||
import { BuildNodeBuilderOptions } from '../builders/build/build.impl';
|
||||
import { getBaseWebpackPartial } from './config';
|
||||
import { BuildNodeBuilderOptions } from './types';
|
||||
|
||||
function getNodePartial(options: BuildNodeBuilderOptions) {
|
||||
const webpackConfig: Configuration = {
|
||||
|
||||
@@ -8,6 +8,7 @@ describe('normalizeBuildOptions', () => {
|
||||
let testOptions: BuildBuilderOptions;
|
||||
let root: string;
|
||||
let sourceRoot: Path;
|
||||
let projectRoot: string;
|
||||
|
||||
beforeEach(() => {
|
||||
testOptions = {
|
||||
@@ -29,24 +30,45 @@ describe('normalizeBuildOptions', () => {
|
||||
};
|
||||
root = '/root';
|
||||
sourceRoot = normalize('apps/nodeapp/src');
|
||||
projectRoot = 'apps/nodeapp';
|
||||
});
|
||||
it('should add the root', () => {
|
||||
const result = normalizeBuildOptions(testOptions, root, sourceRoot);
|
||||
const result = normalizeBuildOptions(
|
||||
testOptions,
|
||||
root,
|
||||
sourceRoot,
|
||||
projectRoot
|
||||
);
|
||||
expect(result.root).toEqual('/root');
|
||||
});
|
||||
|
||||
it('should resolve main from root', () => {
|
||||
const result = normalizeBuildOptions(testOptions, root, sourceRoot);
|
||||
const result = normalizeBuildOptions(
|
||||
testOptions,
|
||||
root,
|
||||
sourceRoot,
|
||||
projectRoot
|
||||
);
|
||||
expect(result.main).toEqual('/root/apps/nodeapp/src/main.ts');
|
||||
});
|
||||
|
||||
it('should resolve the output path', () => {
|
||||
const result = normalizeBuildOptions(testOptions, root, sourceRoot);
|
||||
const result = normalizeBuildOptions(
|
||||
testOptions,
|
||||
root,
|
||||
sourceRoot,
|
||||
projectRoot
|
||||
);
|
||||
expect(result.outputPath).toEqual('/root/dist/apps/nodeapp');
|
||||
});
|
||||
|
||||
it('should resolve the tsConfig path', () => {
|
||||
const result = normalizeBuildOptions(testOptions, root, sourceRoot);
|
||||
const result = normalizeBuildOptions(
|
||||
testOptions,
|
||||
root,
|
||||
sourceRoot,
|
||||
projectRoot
|
||||
);
|
||||
expect(result.tsConfig).toEqual('/root/apps/nodeapp/tsconfig.app.json');
|
||||
});
|
||||
|
||||
@@ -69,7 +91,8 @@ describe('normalizeBuildOptions', () => {
|
||||
],
|
||||
},
|
||||
root,
|
||||
sourceRoot
|
||||
sourceRoot,
|
||||
projectRoot
|
||||
);
|
||||
expect(result.assets).toEqual([
|
||||
{
|
||||
@@ -87,7 +110,12 @@ describe('normalizeBuildOptions', () => {
|
||||
});
|
||||
|
||||
it('should resolve the file replacement paths', () => {
|
||||
const result = normalizeBuildOptions(testOptions, root, sourceRoot);
|
||||
const result = normalizeBuildOptions(
|
||||
testOptions,
|
||||
root,
|
||||
sourceRoot,
|
||||
projectRoot
|
||||
);
|
||||
expect(result.fileReplacements).toEqual([
|
||||
{
|
||||
replace: '/root/apps/environment/environment.ts',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Path, normalize } from '@angular-devkit/core';
|
||||
import { normalize } from '@angular-devkit/core';
|
||||
import { resolve, dirname, relative, basename } from 'path';
|
||||
import { BuildBuilderOptions } from './types';
|
||||
import { statSync } from 'fs';
|
||||
@@ -11,12 +11,14 @@ export interface FileReplacement {
|
||||
export function normalizeBuildOptions<T extends BuildBuilderOptions>(
|
||||
options: T,
|
||||
root: string,
|
||||
sourceRoot: string
|
||||
sourceRoot: string,
|
||||
projectRoot: string
|
||||
): T {
|
||||
return {
|
||||
...options,
|
||||
root: root,
|
||||
sourceRoot: sourceRoot,
|
||||
root,
|
||||
sourceRoot,
|
||||
projectRoot,
|
||||
main: resolve(root, options.main),
|
||||
outputPath: resolve(root, options.outputPath),
|
||||
tsConfig: resolve(root, options.tsConfig),
|
||||
|
||||
@@ -41,4 +41,13 @@ export interface BuildBuilderOptions {
|
||||
|
||||
root?: string;
|
||||
sourceRoot?: Path;
|
||||
projectRoot?: string;
|
||||
}
|
||||
|
||||
export interface BuildNodeBuilderOptions extends BuildBuilderOptions {
|
||||
optimization?: boolean;
|
||||
sourceMap?: boolean;
|
||||
externalDependencies: 'all' | 'none' | string[];
|
||||
buildLibsFromSource?: boolean;
|
||||
generatePackageJson?: boolean;
|
||||
}
|
||||
|
||||
@@ -419,8 +419,9 @@ function createFetcher(packageManager: string, logger: logging.Logger) {
|
||||
const dir = dirSync().name;
|
||||
logger.info(`Fetching ${packageName}@${packageVersion}`);
|
||||
const install = getPackageManagerInstallCommand(packageManager);
|
||||
execSync(`${install} ${packageName}@${packageVersion} --prefix=${dir}`, {
|
||||
execSync(`${install} ${packageName}@${packageVersion}`, {
|
||||
stdio: [],
|
||||
cwd: dir,
|
||||
});
|
||||
const packageJsonPath = require.resolve(`${packageName}/package.json`, {
|
||||
paths: [dir],
|
||||
@@ -564,14 +565,11 @@ async function generateMigrationsJsonAndUpdatePackageJson(
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
const startVersion = versions(root, {})('@nrwl/workspace');
|
||||
const installDev = getPackageManagerInstallCommand(packageManager, true);
|
||||
logger.error(
|
||||
`NX The migrate command failed. Try the following to migrate your workspace:`
|
||||
);
|
||||
logger.error(`> ${installDev} @nrwl/workspace@latest`);
|
||||
logger.error(
|
||||
`> nx migrate ${opts.targetPackage}@${opts.targetVersion} --from="@nrwl/workspace@${startVersion}"`
|
||||
`> npx @nrwl/tao@latest migrate ${opts.targetPackage}@${opts.targetVersion}`
|
||||
);
|
||||
logger.error(
|
||||
`This will use the newest version of the migrate functionality, which might have your issue resolved.`
|
||||
|
||||
@@ -134,6 +134,16 @@
|
||||
"version": "10.4.0-beta.5",
|
||||
"description": "Add an explicit dependency on @nrwl/tao",
|
||||
"factory": "./src/migrations/update-10-4-0/add-explicit-dep-on-tao"
|
||||
},
|
||||
"update-script-to-invoke-nx-migrate": {
|
||||
"version": "10.4.5",
|
||||
"description": "Update the 'update' npm script to invoke nx migrate",
|
||||
"factory": "./src/migrations/update-10-4-0/update-script-to-invoke-nx-migrate"
|
||||
},
|
||||
"update-decorate-angular-cli": {
|
||||
"version": "10.4.6",
|
||||
"description": "Update the decoration script when using Angular CLI",
|
||||
"factory": "./src/migrations/update-10-4-0/update-decorate-angular-cli"
|
||||
}
|
||||
},
|
||||
"packageJsonUpdates": {
|
||||
|
||||
@@ -4,7 +4,10 @@ import { nxVersion } from '../../../src/utils/versions';
|
||||
|
||||
export default function update(): Rule {
|
||||
return chain([
|
||||
addDepsToPackageJson({}, { '@nrwl/tao': nxVersion }),
|
||||
addDepsToPackageJson(
|
||||
{},
|
||||
{ '@nrwl/tao': nxVersion, '@nrwl/cli': nxVersion }
|
||||
),
|
||||
formatFiles(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { join as pathJoin } from 'path';
|
||||
import { readFileSync } from 'fs';
|
||||
import { Tree } from '@angular-devkit/schematics';
|
||||
|
||||
export default function update() {
|
||||
return (host: Tree) => {
|
||||
const decorateCli = readFileSync(
|
||||
pathJoin(
|
||||
__dirname as any,
|
||||
'..',
|
||||
'..',
|
||||
'schematics',
|
||||
'utils',
|
||||
'decorate-angular-cli.js__tmpl__'
|
||||
)
|
||||
).toString();
|
||||
if (host.exists('/decorate-angular-cli.js')) {
|
||||
host.overwrite('/decorate-angular-cli.js', decorateCli);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Rule } from '@angular-devkit/schematics';
|
||||
import { updateJsonInTree } from '@nrwl/workspace';
|
||||
|
||||
export default function update(): Rule {
|
||||
return updateJsonInTree('package.json', (json) => {
|
||||
if (json.scripts && json.scripts.update) {
|
||||
json.scripts.update = 'nx migrate latest';
|
||||
}
|
||||
return json;
|
||||
});
|
||||
}
|
||||
@@ -50,6 +50,14 @@ if (!process.env['NX_CLI_SET']) {
|
||||
const { output } = require('@nrwl/workspace');
|
||||
output.warn({ title: 'The Angular CLI was invoked instead of the Nx CLI. Use "npx ng [command]" or "nx [command]" instead.' });
|
||||
}
|
||||
|
||||
if (process.argv[2] === 'update') {
|
||||
const { output } = require('@nrwl/workspace');
|
||||
output.error({
|
||||
title: '"ng update" is deprecated in favor of "nx migrate". Read more: https://nx.dev/latest/angular/workspace/update'
|
||||
});
|
||||
throw new Error();
|
||||
}
|
||||
${angularCLIInit}
|
||||
`);
|
||||
}
|
||||
|
||||
@@ -24,15 +24,7 @@
|
||||
"format": "nx format:write",
|
||||
"format:write": "nx format:write",
|
||||
"format:check": "nx format:check",
|
||||
|
||||
<% if(cli === 'angular') { %>
|
||||
"update": "ng update @nrwl/workspace",
|
||||
<% } %>
|
||||
|
||||
<% if(cli === 'nx') { %>
|
||||
"update": "nx migrate latest",
|
||||
<% } %>
|
||||
|
||||
"workspace-schematic": "nx workspace-schematic",
|
||||
"dep-graph": "nx dep-graph",
|
||||
"help": "nx help"
|
||||
|
||||
Reference in New Issue
Block a user