chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
/* eslint-disable */
|
||||
module.exports = {
|
||||
transform: {
|
||||
'^.+\\.[tj]sx?$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'],
|
||||
maxWorkers: 1,
|
||||
globals: {},
|
||||
globalSetup: '../utils/global-setup.ts',
|
||||
globalTeardown: '../utils/global-teardown.ts',
|
||||
displayName: 'e2e-plugin',
|
||||
preset: '../jest.preset.e2e.js',
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "@nx/e2e-plugin",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"nx": "workspace:*",
|
||||
"@nx/e2e-utils": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "e2e-plugin",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "e2e/plugin",
|
||||
"projectType": "application",
|
||||
"implicitDependencies": ["create-nx-plugin"],
|
||||
"// targets": "to see all targets run: nx show project e2e-plugin --web",
|
||||
"targets": {}
|
||||
}
|
||||
@@ -0,0 +1,532 @@
|
||||
import {
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
createFile,
|
||||
newProject,
|
||||
readJson,
|
||||
renameFile,
|
||||
runCLI,
|
||||
runCommand,
|
||||
uniq,
|
||||
updateFile,
|
||||
updateJson,
|
||||
} from '@nx/e2e-utils';
|
||||
import {
|
||||
ASYNC_GENERATOR_EXECUTOR_CONTENTS,
|
||||
NX_PLUGIN_V2_CONTENTS,
|
||||
} from './nx-plugin.fixtures';
|
||||
|
||||
describe('Nx Plugin (TS solution)', () => {
|
||||
let workspaceName: string;
|
||||
|
||||
beforeAll(() => {
|
||||
workspaceName = newProject({
|
||||
preset: 'ts',
|
||||
packages: ['@nx/eslint', '@nx/jest', '@nx/plugin'],
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => cleanupProject());
|
||||
|
||||
it('should be able to generate a Nx Plugin with generators, executors and migrations', async () => {
|
||||
const plugin = uniq('plugin');
|
||||
const generator = uniq('generator');
|
||||
const executor = uniq('executor');
|
||||
const migrationVersion = '1.0.0';
|
||||
|
||||
runCLI(
|
||||
`generate @nx/plugin:plugin packages/${plugin} --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest --publishable`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/plugin:generator packages/${plugin}/src/generators/${generator}/generator --name ${generator}`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/plugin:executor packages/${plugin}/src/executors/${executor}/executor --name ${executor} --includeHasher`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/plugin:migration packages/${plugin}/src/migrations/update-${migrationVersion}/update-${migrationVersion} --packageVersion=${migrationVersion} --packageJsonUpdates=false`
|
||||
);
|
||||
|
||||
expect(runCLI(`lint @proj/${plugin}`)).toContain(
|
||||
`Successfully ran target lint for project @proj/${plugin}`
|
||||
);
|
||||
expect(runCLI(`typecheck @proj/${plugin}`)).toContain(
|
||||
`Successfully ran target typecheck for project @proj/${plugin}`
|
||||
);
|
||||
expect(runCLI(`build @proj/${plugin}`)).toContain(
|
||||
`Successfully ran target build for project @proj/${plugin}`
|
||||
);
|
||||
checkFilesExist(
|
||||
// entry point
|
||||
`packages/${plugin}/dist/index.js`,
|
||||
`packages/${plugin}/dist/index.d.ts`,
|
||||
// generator
|
||||
`packages/${plugin}/dist/generators/${generator}/schema.json`,
|
||||
`packages/${plugin}/dist/generators/${generator}/schema.d.ts`,
|
||||
`packages/${plugin}/dist/generators/${generator}/generator.js`,
|
||||
`packages/${plugin}/dist/generators/${generator}/generator.d.ts`,
|
||||
// executor
|
||||
`packages/${plugin}/dist/executors/${executor}/schema.json`,
|
||||
`packages/${plugin}/dist/executors/${executor}/schema.d.ts`,
|
||||
`packages/${plugin}/dist/executors/${executor}/executor.js`,
|
||||
`packages/${plugin}/dist/executors/${executor}/executor.d.ts`,
|
||||
`packages/${plugin}/dist/executors/${executor}/hasher.js`,
|
||||
`packages/${plugin}/dist/executors/${executor}/hasher.d.ts`,
|
||||
// migration
|
||||
`packages/${plugin}/dist/migrations/update-${migrationVersion}/update-${migrationVersion}.js`,
|
||||
`packages/${plugin}/dist/migrations/update-${migrationVersion}/update-${migrationVersion}.d.ts`
|
||||
);
|
||||
expect(runCLI(`test @proj/${plugin}`)).toContain(
|
||||
`Successfully ran target test for project @proj/${plugin}`
|
||||
);
|
||||
expect(runCLI(`e2e @proj/${plugin}-e2e`)).toContain(
|
||||
`Successfully ran target e2e for project @proj/${plugin}-e2e`
|
||||
);
|
||||
|
||||
// Check that inferred targets also work
|
||||
updateJson('nx.json', (json) => {
|
||||
json.plugins.push({
|
||||
plugin: '@nx/jest/plugin',
|
||||
include: ['packages/*-e2e/**/*'],
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'e2e-ci',
|
||||
},
|
||||
});
|
||||
return json;
|
||||
});
|
||||
updateJson(`packages/${plugin}-e2e/package.json`, (json) => {
|
||||
delete json.targets;
|
||||
return json;
|
||||
});
|
||||
expect(() => runCLI(`e2e @proj/${plugin}-e2e`)).not.toThrow();
|
||||
}, 90000);
|
||||
|
||||
it('should be able to infer projects and targets', async () => {
|
||||
const plugin = uniq('plugin');
|
||||
runCLI(`generate @nx/plugin:plugin packages/${plugin}`);
|
||||
|
||||
// Setup project inference + target inference
|
||||
updateFile(`packages/${plugin}/src/index.ts`, NX_PLUGIN_V2_CONTENTS);
|
||||
|
||||
// Register plugin in nx.json (required for inference)
|
||||
updateJson(`nx.json`, (nxJson) => {
|
||||
nxJson.plugins.push({
|
||||
plugin: `@${workspaceName}/${plugin}`,
|
||||
options: { inferredTags: ['my-tag'] },
|
||||
});
|
||||
return nxJson;
|
||||
});
|
||||
|
||||
// Create project that should be inferred by Nx
|
||||
const inferredProject = uniq('inferred');
|
||||
createFile(
|
||||
`packages/${inferredProject}/package.json`,
|
||||
JSON.stringify({
|
||||
name: inferredProject,
|
||||
version: '0.0.1',
|
||||
})
|
||||
);
|
||||
createFile(`packages/${inferredProject}/my-project-file`);
|
||||
|
||||
// Attempt to use inferred project w/ Nx
|
||||
expect(runCLI(`build ${inferredProject}`)).toContain(
|
||||
'custom registered target'
|
||||
);
|
||||
const configuration = JSON.parse(
|
||||
runCLI(`show project ${inferredProject} --json`)
|
||||
);
|
||||
expect(configuration.tags).toContain('my-tag');
|
||||
expect(configuration.metadata.technologies).toEqual(['my-plugin']);
|
||||
});
|
||||
|
||||
it('should be able to use local generators and executors', async () => {
|
||||
const plugin = uniq('plugin');
|
||||
const generator = uniq('generator');
|
||||
const executor = uniq('executor');
|
||||
const generatedProject = uniq('project');
|
||||
|
||||
runCLI(`generate @nx/plugin:plugin packages/${plugin} --linter eslint`);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/plugin:generator --name ${generator} --path packages/${plugin}/src/generators/${generator}/generator`
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/plugin:executor --name ${executor} --path packages/${plugin}/src/executors/${executor}/executor`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`packages/${plugin}/src/executors/${executor}/executor.ts`,
|
||||
ASYNC_GENERATOR_EXECUTOR_CONTENTS
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @${workspaceName}/${plugin}:${generator} --name ${generatedProject}`
|
||||
);
|
||||
|
||||
updateJson(`libs/${generatedProject}/project.json`, (project) => {
|
||||
project.targets['execute'] = {
|
||||
executor: `@${workspaceName}/${plugin}:${executor}`,
|
||||
};
|
||||
return project;
|
||||
});
|
||||
|
||||
expect(() => checkFilesExist(`libs/${generatedProject}`)).not.toThrow();
|
||||
expect(() => runCLI(`execute ${generatedProject}`)).not.toThrow();
|
||||
expect(() => runCLI(`lint ${generatedProject}`)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should be able to resolve local generators and executors using package.json development condition export', async () => {
|
||||
const plugin = uniq('plugin');
|
||||
const generator = uniq('generator');
|
||||
const executor = uniq('executor');
|
||||
const generatedProject = uniq('project');
|
||||
|
||||
runCLI(`generate @nx/plugin:plugin packages/${plugin}`);
|
||||
|
||||
// move/generate everything in the "code" folder, which is not a standard location and wouldn't
|
||||
// be considered by the fall back resolution logic, so the only way it could be resolved is if
|
||||
// the development condition export is used
|
||||
renameFile(
|
||||
`packages/${plugin}/src/index.ts`,
|
||||
`packages/${plugin}/code/index.ts`
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/plugin:generator --name ${generator} --path packages/${plugin}/code/generators/${generator}/generator`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/plugin:executor --name ${executor} --path packages/${plugin}/code/executors/${executor}/executor`
|
||||
);
|
||||
|
||||
updateJson(`packages/${plugin}/package.json`, (pkg) => {
|
||||
pkg.nx.sourceRoot = `packages/${plugin}/code`;
|
||||
pkg.nx.targets.build.options.main = `packages/${plugin}/code/index.ts`;
|
||||
pkg.nx.targets.build.options.rootDir = `packages/${plugin}/code`;
|
||||
pkg.nx.targets.build.options.assets.forEach(
|
||||
(asset: { input: string }) => {
|
||||
asset.input = `./packages/${plugin}/code`;
|
||||
}
|
||||
);
|
||||
pkg.exports = {
|
||||
'.': {
|
||||
types: './dist/index.d.ts',
|
||||
development: './code/index.ts',
|
||||
default: './dist/index.js',
|
||||
},
|
||||
'./package.json': './package.json',
|
||||
'./generators.json': {
|
||||
development: './generators.json',
|
||||
default: './generators.json',
|
||||
},
|
||||
'./executors.json': './executors.json',
|
||||
'./dist/generators/*/schema.json': {
|
||||
development: './code/generators/*/schema.json',
|
||||
default: './dist/generators/*/schema.json',
|
||||
},
|
||||
'./dist/generators/*/generator': {
|
||||
types: './dist/generators/*/generator.d.ts',
|
||||
development: './code/generators/*/generator.ts',
|
||||
default: './dist/generators/*/generator.js',
|
||||
},
|
||||
'./dist/executors/*/schema.json': {
|
||||
development: './code/executors/*/schema.json',
|
||||
default: './dist/executors/*/schema.json',
|
||||
},
|
||||
'./dist/executors/*/executor': {
|
||||
types: './dist/executors/*/executor.d.ts',
|
||||
development: './code/executors/*/executor.ts',
|
||||
default: './dist/executors/*/executor.js',
|
||||
},
|
||||
};
|
||||
return pkg;
|
||||
});
|
||||
|
||||
updateJson(`packages/${plugin}/tsconfig.lib.json`, (tsconfig) => {
|
||||
tsconfig.compilerOptions.rootDir = 'code';
|
||||
tsconfig.include = ['code/**/*.ts'];
|
||||
return tsconfig;
|
||||
});
|
||||
|
||||
updateFile(
|
||||
`packages/${plugin}/code/executors/${executor}/executor.ts`,
|
||||
ASYNC_GENERATOR_EXECUTOR_CONTENTS
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @${workspaceName}/${plugin}:${generator} --name ${generatedProject}`
|
||||
);
|
||||
|
||||
updateJson(`libs/${generatedProject}/project.json`, (project) => {
|
||||
project.targets['execute'] = {
|
||||
executor: `@${workspaceName}/${plugin}:${executor}`,
|
||||
};
|
||||
return project;
|
||||
});
|
||||
|
||||
expect(() => checkFilesExist(`libs/${generatedProject}`)).not.toThrow();
|
||||
expect(() => runCLI(`execute ${generatedProject}`)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should load local plugins registered via subpath imports', async () => {
|
||||
const plugin = uniq('plugin');
|
||||
runCLI(`generate @nx/plugin:plugin packages/${plugin}`);
|
||||
const sourceCondition =
|
||||
readJson('tsconfig.base.json').compilerOptions.customConditions[0];
|
||||
expect(sourceCondition).not.toBe('development');
|
||||
|
||||
// expose a subpath plugin from a non-default location to ensure resolution
|
||||
// goes through the workspace custom condition rather than any heuristic
|
||||
updateFile(
|
||||
`packages/${plugin}/src/plugins/cypress/plugin.ts`,
|
||||
NX_PLUGIN_V2_CONTENTS
|
||||
);
|
||||
|
||||
updateJson(`packages/${plugin}/package.json`, (pkg) => {
|
||||
pkg.exports = {
|
||||
...pkg.exports,
|
||||
'./cypress': {
|
||||
[sourceCondition]: './src/plugins/cypress/plugin.ts',
|
||||
default: './dist/plugins/cypress/plugin.js',
|
||||
},
|
||||
};
|
||||
return pkg;
|
||||
});
|
||||
|
||||
updateJson(`nx.json`, (nxJson) => {
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push({
|
||||
plugin: `@${workspaceName}/${plugin}/cypress`,
|
||||
options: { inferredTags: ['cypress-tag'] },
|
||||
});
|
||||
return nxJson;
|
||||
});
|
||||
|
||||
const inferredProject = uniq('subpath-inferred');
|
||||
createFile(
|
||||
`packages/${inferredProject}/package.json`,
|
||||
JSON.stringify({ name: inferredProject, version: '0.0.1' })
|
||||
);
|
||||
createFile(`packages/${inferredProject}/my-project-file`);
|
||||
|
||||
expect(runCLI(`build ${inferredProject}`)).toContain(
|
||||
'custom registered target'
|
||||
);
|
||||
const configuration = JSON.parse(
|
||||
runCLI(`show project ${inferredProject} --json`)
|
||||
);
|
||||
expect(configuration.tags).toContain('cypress-tag');
|
||||
});
|
||||
|
||||
// Regression: a subpath plugin whose TS source uses NodeNext `.js` relative
|
||||
// imports (TS resolves them to the sibling `.ts` at compile time) needs
|
||||
// `.js -> .ts` rewriting on whichever resolver layer applies:
|
||||
// - type: module -> loads as ESM; native type stripping loads the `.ts`
|
||||
// and Nx's self-contained ESM resolve hook rewrites the specifier (no
|
||||
// ts-node/swc-node required).
|
||||
// - type: commonjs -> native strip can't run ESM `import` syntax in a CJS
|
||||
// module, so Nx falls back to swc/ts-node to transpile, then the
|
||||
// `Module._resolveFilename` patch rewrites the emitted `require('./x.js')`.
|
||||
// Both shapes must load correctly.
|
||||
for (const moduleType of ['module', 'commonjs'] as const) {
|
||||
it(`should load local plugin subpath imports whose TS sources use NodeNext-style .js import specifiers (type: ${moduleType})`, async () => {
|
||||
const plugin = uniq('plugin');
|
||||
runCLI(`generate @nx/plugin:plugin packages/${plugin}`);
|
||||
const sourceCondition =
|
||||
readJson('tsconfig.base.json').compilerOptions.customConditions[0];
|
||||
expect(sourceCondition).not.toBe('development');
|
||||
|
||||
// Plugin entry imports a sibling helper via NodeNext `.js` specifier
|
||||
// (which TS resolves to the sibling `.ts` file at compile time).
|
||||
updateFile(
|
||||
`packages/${plugin}/src/plugins/docker/index.ts`,
|
||||
`import { dockerCreateNodes, dockerCreateMetadata } from './nodes.js';
|
||||
import type { CreateNodesV2, CreateMetadata } from '@nx/devkit';
|
||||
type PluginOptions = { inferredTags: string[] };
|
||||
export const createNodesV2: CreateNodesV2<PluginOptions> = [
|
||||
'**/my-project-file',
|
||||
dockerCreateNodes,
|
||||
];
|
||||
export const createMetadata: CreateMetadata = dockerCreateMetadata;
|
||||
`
|
||||
);
|
||||
updateFile(
|
||||
`packages/${plugin}/src/plugins/docker/nodes.ts`,
|
||||
`import { basename, dirname } from 'path';
|
||||
import type { CreateMetadata, ProjectsMetadata } from '@nx/devkit';
|
||||
|
||||
type PluginOptions = { inferredTags: string[] };
|
||||
|
||||
export const dockerCreateMetadata: CreateMetadata = (graph) => {
|
||||
const metadata: ProjectsMetadata = {};
|
||||
for (const projectNode of Object.values(graph.nodes)) {
|
||||
metadata[projectNode.name] = {
|
||||
metadata: { technologies: ['my-plugin'] },
|
||||
};
|
||||
}
|
||||
return metadata;
|
||||
};
|
||||
|
||||
export const dockerCreateNodes = (files: string[], options: PluginOptions) => {
|
||||
const results: any[] = [];
|
||||
for (const f of files) {
|
||||
const root = dirname(f);
|
||||
const name = basename(root);
|
||||
results.push([
|
||||
f,
|
||||
{
|
||||
projects: {
|
||||
[root]: {
|
||||
root,
|
||||
name,
|
||||
targets: {
|
||||
build: {
|
||||
executor: 'nx:run-commands',
|
||||
options: { command: "echo 'custom registered target'" },
|
||||
},
|
||||
},
|
||||
tags: options.inferredTags,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
return results;
|
||||
};
|
||||
`
|
||||
);
|
||||
|
||||
updateJson(`packages/${plugin}/package.json`, (pkg) => {
|
||||
pkg.type = moduleType;
|
||||
pkg.exports = {
|
||||
...pkg.exports,
|
||||
'./docker': {
|
||||
[sourceCondition]: './src/plugins/docker/index.ts',
|
||||
types: './dist/plugins/docker/index.d.ts',
|
||||
import: './dist/plugins/docker/index.js',
|
||||
default: './dist/plugins/docker/index.js',
|
||||
},
|
||||
};
|
||||
return pkg;
|
||||
});
|
||||
|
||||
updateJson(`nx.json`, (nxJson) => {
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push({
|
||||
plugin: `@${workspaceName}/${plugin}/docker`,
|
||||
options: { inferredTags: ['docker-tag'] },
|
||||
});
|
||||
return nxJson;
|
||||
});
|
||||
|
||||
const inferredProject = uniq('docker-inferred');
|
||||
createFile(
|
||||
`packages/${inferredProject}/package.json`,
|
||||
JSON.stringify({ name: inferredProject, version: '0.0.1' })
|
||||
);
|
||||
createFile(`packages/${inferredProject}/my-project-file`);
|
||||
|
||||
expect(runCLI(`build ${inferredProject}`)).toContain(
|
||||
'custom registered target'
|
||||
);
|
||||
const configuration = JSON.parse(
|
||||
runCLI(`show project ${inferredProject} --json`)
|
||||
);
|
||||
expect(configuration.tags).toContain('docker-tag');
|
||||
});
|
||||
}
|
||||
|
||||
it('should load local plugin subpath imports from dist when no source condition is declared', async () => {
|
||||
const plugin = uniq('plugin');
|
||||
runCLI(`generate @nx/plugin:plugin packages/${plugin}`);
|
||||
|
||||
// Expose a subpath plugin via the dist artifact only (no custom source condition).
|
||||
// Nx should fall through to whatever resolve.exports returns — the dist file —
|
||||
// rather than hard-failing.
|
||||
createFile(
|
||||
`packages/${plugin}/dist/plugins/cypress/plugin.js`,
|
||||
`const { basename, dirname } = require("path");
|
||||
|
||||
exports.createNodesV2 = [
|
||||
"**/my-project-file",
|
||||
(files, options) =>
|
||||
files.map((f) => {
|
||||
const root = dirname(f);
|
||||
const name = basename(root);
|
||||
return [
|
||||
f,
|
||||
{
|
||||
projects: {
|
||||
[root]: {
|
||||
root,
|
||||
name,
|
||||
targets: {
|
||||
build: {
|
||||
executor: "nx:run-commands",
|
||||
options: {
|
||||
command: "echo 'dist registered target'",
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: options.inferredTags,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}),
|
||||
];
|
||||
`
|
||||
);
|
||||
|
||||
updateJson(`packages/${plugin}/package.json`, (pkg) => {
|
||||
pkg.exports = {
|
||||
...pkg.exports,
|
||||
'./cypress': {
|
||||
default: './dist/plugins/cypress/plugin.js',
|
||||
},
|
||||
};
|
||||
return pkg;
|
||||
});
|
||||
|
||||
updateJson(`nx.json`, (nxJson) => {
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push({
|
||||
plugin: `@${workspaceName}/${plugin}/cypress`,
|
||||
options: { inferredTags: ['cypress-tag'] },
|
||||
});
|
||||
return nxJson;
|
||||
});
|
||||
|
||||
const inferredProject = uniq('subpath-inferred');
|
||||
createFile(
|
||||
`packages/${inferredProject}/package.json`,
|
||||
JSON.stringify({ name: inferredProject, version: '0.0.1' })
|
||||
);
|
||||
createFile(`packages/${inferredProject}/my-project-file`);
|
||||
|
||||
// With no source condition, Nx resolves to the dist artifact — loading
|
||||
// should succeed (not hard-fail with a "custom condition" error).
|
||||
expect(runCLI(`build ${inferredProject}`)).toContain(
|
||||
'dist registered target'
|
||||
);
|
||||
});
|
||||
|
||||
it('should respect and support generating plugins with a name different than the import path', async () => {
|
||||
const plugin = uniq('plugin');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/plugin:plugin packages/${plugin} --name=${plugin} --linter=eslint --publishable`
|
||||
);
|
||||
|
||||
const packageJson = readJson(`packages/${plugin}/package.json`);
|
||||
expect(packageJson.nx.name).toBe(plugin);
|
||||
|
||||
expect(runCLI(`build ${plugin}`)).toContain(
|
||||
`Successfully ran target build for project ${plugin}`
|
||||
);
|
||||
expect(runCLI(`lint ${plugin}`)).toContain(
|
||||
`Successfully ran target lint for project ${plugin}`
|
||||
);
|
||||
}, 90000);
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
export const ASYNC_GENERATOR_EXECUTOR_CONTENTS = `import type { ExecutorContext } from '@nx/devkit';
|
||||
|
||||
async function* asyncGenerator(
|
||||
) {
|
||||
for (let i = 5; i < 10; i++) {
|
||||
yield new Promise((res) => setTimeout(() => res({ success: true }), 5));
|
||||
}
|
||||
yield { success: true };
|
||||
}
|
||||
|
||||
export default async function* execute(
|
||||
options: unknown,
|
||||
context: ExecutorContext
|
||||
) {
|
||||
for (let i = 5; i < 10; i++) {
|
||||
yield new Promise((res) => setTimeout(() => res({ success: true }), 5));
|
||||
}
|
||||
yield* asyncGenerator();
|
||||
}
|
||||
`;
|
||||
|
||||
export const NX_PLUGIN_V2_CONTENTS = `import { basename, dirname } from "path";
|
||||
import type { CreateNodesV2, CreateMetadata, ProjectsMetadata } from "@nx/devkit";
|
||||
|
||||
type PluginOptions = {
|
||||
inferredTags: string[]
|
||||
}
|
||||
|
||||
export const createMetadata: CreateMetadata = (graph) => {
|
||||
const metadata: ProjectsMetadata = {};
|
||||
for (const projectNode of Object.values(graph.nodes)) {
|
||||
metadata[projectNode.name] = {
|
||||
metadata: {
|
||||
technologies: ["my-plugin"]
|
||||
}
|
||||
}
|
||||
}
|
||||
return metadata;
|
||||
}
|
||||
|
||||
export const createNodesV2: CreateNodesV2<PluginOptions> = [
|
||||
"**/my-project-file",
|
||||
(files, options, ctx) => {
|
||||
const results = [];
|
||||
for (const f of files) {
|
||||
// f = path/to/my/file/my-project-file
|
||||
const root = dirname(f);
|
||||
// root = path/to/my/file
|
||||
const name = basename(root);
|
||||
// name = file
|
||||
|
||||
results.push([
|
||||
f,
|
||||
{
|
||||
projects: {
|
||||
[root]: {
|
||||
root,
|
||||
name,
|
||||
targets: {
|
||||
build: {
|
||||
executor: "nx:run-commands",
|
||||
options: {
|
||||
command: "echo 'custom registered target'",
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: options.inferredTags,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
return results;
|
||||
},
|
||||
];
|
||||
|
||||
`;
|
||||
@@ -0,0 +1,513 @@
|
||||
import { ProjectConfiguration } from '@nx/devkit';
|
||||
import {
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
createFile,
|
||||
expectTestsPass,
|
||||
getPackageManagerCommand,
|
||||
newProject,
|
||||
readJson,
|
||||
runCLI,
|
||||
runCLIAsync,
|
||||
runCommand,
|
||||
tmpProjPath,
|
||||
trimDaemonLog,
|
||||
uniq,
|
||||
updateFile,
|
||||
updateJson,
|
||||
} from '@nx/e2e-utils';
|
||||
import type { PackageJson } from 'nx/src/utils/package-json';
|
||||
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'path';
|
||||
import {
|
||||
ASYNC_GENERATOR_EXECUTOR_CONTENTS,
|
||||
NX_PLUGIN_V2_CONTENTS,
|
||||
} from './nx-plugin.fixtures';
|
||||
|
||||
describe('Nx Plugin', () => {
|
||||
let workspaceName: string;
|
||||
|
||||
beforeAll(() => {
|
||||
workspaceName = newProject({
|
||||
packages: ['@nx/eslint', '@nx/jest', '@nx/plugin'],
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// The suite shares one long-lived daemon (no `reset`), so dump its log
|
||||
// once before teardown — CI shows it for a daemon crash on plugin load.
|
||||
try {
|
||||
const daemonLog = join(
|
||||
tmpProjPath(),
|
||||
'.nx',
|
||||
'workspace-data',
|
||||
'd',
|
||||
'daemon.log'
|
||||
);
|
||||
if (existsSync(daemonLog)) {
|
||||
// Trimmed — see trimDaemonLog; the raw log is thousands of lines.
|
||||
console.log(
|
||||
`\n========== daemon.log (trimmed) ==========\n${trimDaemonLog(
|
||||
readFileSync(daemonLog, 'utf-8')
|
||||
)}\n========== end daemon.log ==========\n`
|
||||
);
|
||||
} else {
|
||||
console.log(`[plugin-debug] no daemon log at ${daemonLog}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`[plugin-debug] failed to read daemon log: ${e}`);
|
||||
}
|
||||
|
||||
cleanupProject();
|
||||
});
|
||||
|
||||
it('should be able to generate a Nx Plugin ', async () => {
|
||||
const plugin = uniq('plugin');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/plugin:plugin ${plugin} --linter=eslint --e2eTestRunner=jest --publishable`
|
||||
);
|
||||
const lintResults = runCLI(`lint ${plugin}`);
|
||||
expect(lintResults).toContain('All files pass linting');
|
||||
|
||||
const buildResults = runCLI(`build ${plugin}`);
|
||||
expect(buildResults).toContain('Done compiling TypeScript files');
|
||||
checkFilesExist(
|
||||
`dist/${plugin}/package.json`,
|
||||
`dist/${plugin}/src/index.js`
|
||||
);
|
||||
const project = readJson(`${plugin}/project.json`);
|
||||
expect(project).toMatchObject({
|
||||
tags: [],
|
||||
});
|
||||
|
||||
runCLI(`e2e ${plugin}-e2e`);
|
||||
}, 90000);
|
||||
|
||||
it('should be able to generate a migration', async () => {
|
||||
const plugin = uniq('plugin');
|
||||
const version = '1.0.0';
|
||||
|
||||
runCLI(`generate @nx/plugin:plugin ${plugin} --linter=eslint`);
|
||||
runCLI(
|
||||
`generate @nx/plugin:migration --path=${plugin}/src/migrations/update-${version}/update-${version} --packageVersion=${version} --packageJsonUpdates=false`
|
||||
);
|
||||
|
||||
const lintResults = runCLI(`lint ${plugin}`);
|
||||
expect(lintResults).toContain('All files pass linting');
|
||||
|
||||
expectTestsPass(await runCLIAsync(`test ${plugin}`));
|
||||
|
||||
const buildResults = runCLI(`build ${plugin}`);
|
||||
expect(buildResults).toContain('Done compiling TypeScript files');
|
||||
checkFilesExist(
|
||||
`dist/${plugin}/src/migrations/update-${version}/update-${version}.js`,
|
||||
`${plugin}/src/migrations/update-${version}/update-${version}.ts`
|
||||
);
|
||||
const migrationsJson = readJson(`${plugin}/migrations.json`);
|
||||
expect(migrationsJson).toMatchObject({
|
||||
generators: expect.objectContaining({
|
||||
[`update-${version}`]: {
|
||||
version,
|
||||
description: `Migration for v1.0.0`,
|
||||
implementation: `./src/migrations/update-${version}/update-${version}`,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}, 90000);
|
||||
|
||||
it('should be able to generate a generator', async () => {
|
||||
const plugin = uniq('plugin');
|
||||
const generator = uniq('generator');
|
||||
|
||||
runCLI(`generate @nx/plugin:plugin ${plugin} --linter=eslint`);
|
||||
runCLI(
|
||||
`generate @nx/plugin:generator ${plugin}/src/generators/${generator}/generator --name ${generator}`
|
||||
);
|
||||
|
||||
const lintResults = runCLI(`lint ${plugin}`);
|
||||
expect(lintResults).toContain('All files pass linting');
|
||||
|
||||
expectTestsPass(await runCLIAsync(`test ${plugin}`));
|
||||
|
||||
const buildResults = runCLI(`build ${plugin}`);
|
||||
expect(buildResults).toContain('Done compiling TypeScript files');
|
||||
checkFilesExist(
|
||||
`${plugin}/src/generators/${generator}/schema.d.ts`,
|
||||
`${plugin}/src/generators/${generator}/schema.json`,
|
||||
`${plugin}/src/generators/${generator}/generator.ts`,
|
||||
`${plugin}/src/generators/${generator}/generator.spec.ts`,
|
||||
`dist/${plugin}/src/generators/${generator}/schema.d.ts`,
|
||||
`dist/${plugin}/src/generators/${generator}/schema.json`,
|
||||
`dist/${plugin}/src/generators/${generator}/generator.js`
|
||||
);
|
||||
const generatorJson = readJson(`${plugin}/generators.json`);
|
||||
expect(generatorJson).toMatchObject({
|
||||
generators: expect.objectContaining({
|
||||
[generator]: {
|
||||
factory: `./src/generators/${generator}/generator`,
|
||||
schema: `./src/generators/${generator}/schema.json`,
|
||||
description: `${generator} generator`,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}, 90000);
|
||||
|
||||
it('should be able to generate an executor', async () => {
|
||||
const plugin = uniq('plugin');
|
||||
const executor = uniq('executor');
|
||||
|
||||
runCLI(`generate @nx/plugin:plugin ${plugin} --linter=eslint`);
|
||||
runCLI(
|
||||
`generate @nx/plugin:executor --name ${executor} --path=${plugin}/src/executors/${executor}/executor --includeHasher`
|
||||
);
|
||||
|
||||
const lintResults = runCLI(`lint ${plugin}`);
|
||||
expect(lintResults).toContain('All files pass linting');
|
||||
|
||||
expectTestsPass(await runCLIAsync(`test ${plugin}`));
|
||||
|
||||
const buildResults = runCLI(`build ${plugin}`);
|
||||
expect(buildResults).toContain('Done compiling TypeScript files');
|
||||
checkFilesExist(
|
||||
`${plugin}/src/executors/${executor}/schema.d.ts`,
|
||||
`${plugin}/src/executors/${executor}/schema.json`,
|
||||
`${plugin}/src/executors/${executor}/executor.ts`,
|
||||
`${plugin}/src/executors/${executor}/hasher.ts`,
|
||||
`${plugin}/src/executors/${executor}/executor.spec.ts`,
|
||||
`dist/${plugin}/src/executors/${executor}/schema.d.ts`,
|
||||
`dist/${plugin}/src/executors/${executor}/schema.json`,
|
||||
`dist/${plugin}/src/executors/${executor}/executor.js`,
|
||||
`dist/${plugin}/src/executors/${executor}/hasher.js`
|
||||
);
|
||||
const executorsJson = readJson(`${plugin}/executors.json`);
|
||||
expect(executorsJson).toMatchObject({
|
||||
executors: expect.objectContaining({
|
||||
[executor]: {
|
||||
implementation: `./src/executors/${executor}/executor`,
|
||||
hasher: `./src/executors/${executor}/hasher`,
|
||||
schema: `./src/executors/${executor}/schema.json`,
|
||||
description: `${executor} executor`,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}, 90000);
|
||||
|
||||
it('should catch invalid implementations, schemas, and version in lint', async () => {
|
||||
const plugin = uniq('plugin');
|
||||
const goodGenerator = uniq('good-generator');
|
||||
const goodExecutor = uniq('good-executor');
|
||||
const badExecutorBadImplPath = uniq('bad-executor');
|
||||
const goodMigration = uniq('good-migration');
|
||||
const badFactoryPath = uniq('bad-generator');
|
||||
const badMigrationVersion = uniq('bad-version');
|
||||
const missingMigrationVersion = uniq('missing-version');
|
||||
|
||||
// Generating the plugin results in a generator also called {plugin},
|
||||
// as well as an executor called "build"
|
||||
runCLI(`generate @nx/plugin:plugin ${plugin} --linter=eslint`);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/plugin:generator --name=${goodGenerator} --path=${plugin}/src/generators/${goodGenerator}/generator`
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/plugin:generator --name=${badFactoryPath} --path=${plugin}/src/generators/${badFactoryPath}/generator`
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/plugin:executor --name=${goodExecutor} --path=${plugin}/src/executors/${goodExecutor}/executor`
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/plugin:executor --name=${badExecutorBadImplPath} --path=${plugin}/src/executors/${badExecutorBadImplPath}/executor`
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/plugin:migration --name=${badMigrationVersion} --path=${plugin}/src/migrations --packageVersion="invalid"`
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/plugin:migration --name=${missingMigrationVersion} --path=${plugin}/migrations/0.1.0 --packageVersion="0.1.0"`
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/plugin:migration --name=${goodMigration} --path=${plugin}/migrations/0.1.0 --packageVersion="0.1.0"`
|
||||
);
|
||||
|
||||
updateFile(`${plugin}/generators.json`, (f) => {
|
||||
const json = JSON.parse(f);
|
||||
// @proj/plugin:plugin has an invalid implementation path
|
||||
json.generators[badFactoryPath].factory =
|
||||
`./generators/${plugin}/bad-path`;
|
||||
// @proj/plugin:non-existant has a missing implementation path amd schema
|
||||
json.generators['non-existant-generator'] = {};
|
||||
return JSON.stringify(json);
|
||||
});
|
||||
|
||||
updateFile(`${plugin}/executors.json`, (f) => {
|
||||
const json = JSON.parse(f);
|
||||
// @proj/plugin:badExecutorBadImplPath has an invalid implementation path
|
||||
json.executors[badExecutorBadImplPath].implementation =
|
||||
'./executors/bad-path';
|
||||
// @proj/plugin:non-existant has a missing implementation path amd schema
|
||||
json.executors['non-existant-executor'] = {};
|
||||
return JSON.stringify(json);
|
||||
});
|
||||
|
||||
updateFile(`${plugin}/migrations.json`, (f) => {
|
||||
const json = JSON.parse(f);
|
||||
delete json.generators[missingMigrationVersion].version;
|
||||
return JSON.stringify(json);
|
||||
});
|
||||
|
||||
const results = runCLI(`lint ${plugin}`, { silenceError: true });
|
||||
expect(results).toContain(
|
||||
`${badFactoryPath}: Implementation path should point to a valid file`
|
||||
);
|
||||
expect(results).toContain(
|
||||
`non-existant-generator: Missing required property - \`schema\``
|
||||
);
|
||||
expect(results).toContain(
|
||||
`non-existant-generator: Missing required property - \`implementation\``
|
||||
);
|
||||
expect(results).not.toContain(goodGenerator);
|
||||
|
||||
expect(results).toContain(
|
||||
`${badExecutorBadImplPath}: Implementation path should point to a valid file`
|
||||
);
|
||||
expect(results).toContain(
|
||||
`non-existant-executor: Missing required property - \`schema\``
|
||||
);
|
||||
expect(results).toContain(
|
||||
`non-existant-executor: Missing required property - \`implementation\``
|
||||
);
|
||||
expect(results).not.toContain(goodExecutor);
|
||||
|
||||
expect(results).toContain(
|
||||
`${missingMigrationVersion}: Missing required property - \`version\``
|
||||
);
|
||||
expect(results).toContain(
|
||||
`${badMigrationVersion}: Version should be a valid semver`
|
||||
);
|
||||
expect(results).not.toContain(goodMigration);
|
||||
});
|
||||
|
||||
describe('local plugins', () => {
|
||||
let plugin: string;
|
||||
beforeEach(() => {
|
||||
plugin = uniq('plugin');
|
||||
runCLI(`generate @nx/plugin:plugin ${plugin} --linter=eslint`);
|
||||
});
|
||||
|
||||
it('should be able to infer projects and targets', async () => {
|
||||
// Setup project inference + target inference
|
||||
updateFile(`${plugin}/src/index.ts`, NX_PLUGIN_V2_CONTENTS);
|
||||
|
||||
// Register plugin in nx.json (required for inference)
|
||||
updateFile(`nx.json`, (nxJson) => {
|
||||
const nx = JSON.parse(nxJson);
|
||||
nx.plugins = [
|
||||
{
|
||||
plugin: `@${workspaceName}/${plugin}`,
|
||||
options: { inferredTags: ['my-tag'] },
|
||||
},
|
||||
];
|
||||
return JSON.stringify(nx, null, 2);
|
||||
});
|
||||
|
||||
// Create project that should be inferred by Nx
|
||||
const inferredProject = uniq('inferred');
|
||||
createFile(`${inferredProject}/my-project-file`);
|
||||
|
||||
// Attempt to use inferred project w/ Nx
|
||||
expect(runCLI(`build ${inferredProject}`)).toContain(
|
||||
'custom registered target'
|
||||
);
|
||||
const configuration = JSON.parse(
|
||||
runCLI(`show project ${inferredProject} --json`)
|
||||
);
|
||||
expect(configuration.tags).toEqual(['my-tag']);
|
||||
expect(configuration.metadata.technologies).toEqual(['my-plugin']);
|
||||
});
|
||||
|
||||
it('should be able to use local generators and executors', async () => {
|
||||
const generator = uniq('generator');
|
||||
const executor = uniq('executor');
|
||||
const generatedProject = uniq('project');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/plugin:generator --name ${generator} --path ${plugin}/src/generators/${generator}/generator`
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/plugin:executor --name ${executor} --path ${plugin}/src/executors/${executor}/executor`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`${plugin}/src/executors/${executor}/executor.ts`,
|
||||
ASYNC_GENERATOR_EXECUTOR_CONTENTS
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @${workspaceName}/${plugin}:${generator} --name ${generatedProject}`
|
||||
);
|
||||
|
||||
updateFile(`libs/${generatedProject}/project.json`, (f) => {
|
||||
const project: ProjectConfiguration = JSON.parse(f);
|
||||
project.targets['execute'] = {
|
||||
executor: `@${workspaceName}/${plugin}:${executor}`,
|
||||
};
|
||||
return JSON.stringify(project, null, 2);
|
||||
});
|
||||
|
||||
expect(() => checkFilesExist(`libs/${generatedProject}`)).not.toThrow();
|
||||
expect(() => runCLI(`execute ${generatedProject}`)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should work with ts-node only', async () => {
|
||||
const oldPackageJson: PackageJson = readJson('package.json');
|
||||
updateJson<PackageJson>('package.json', (j) => {
|
||||
delete j.dependencies['@swc-node/register'];
|
||||
delete j.devDependencies['@swc-node/register'];
|
||||
return j;
|
||||
});
|
||||
runCommand(getPackageManagerCommand().install);
|
||||
|
||||
const generator = uniq('generator');
|
||||
|
||||
expect(() => {
|
||||
runCLI(
|
||||
`generate @nx/plugin:generator ${plugin}/src/generators/${generator}/generator --name ${generator}`
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @${workspaceName}/${plugin}:${generator} --name ${uniq(
|
||||
'test'
|
||||
)}`
|
||||
);
|
||||
}).not.toThrow();
|
||||
updateFile('package.json', JSON.stringify(oldPackageJson, null, 2));
|
||||
runCommand(getPackageManagerCommand().install);
|
||||
});
|
||||
});
|
||||
|
||||
describe('--directory', () => {
|
||||
it('should create a plugin in the specified directory', async () => {
|
||||
const plugin = uniq('plugin');
|
||||
runCLI(
|
||||
`generate @nx/plugin:plugin libs/subdir/${plugin} --linter=eslint --e2eTestRunner=jest`
|
||||
);
|
||||
checkFilesExist(`libs/subdir/${plugin}/package.json`);
|
||||
const pluginProject = readJson(
|
||||
join('libs', 'subdir', plugin, 'project.json')
|
||||
);
|
||||
const pluginE2EProject = readJson(
|
||||
join('libs', 'subdir', `${plugin}-e2e`, 'project.json')
|
||||
);
|
||||
expect(pluginProject.targets).toBeDefined();
|
||||
expect(pluginE2EProject).toBeTruthy();
|
||||
}, 90000);
|
||||
});
|
||||
describe('--tags', () => {
|
||||
it('should add tags to project configuration', () => {
|
||||
const plugin = uniq('plugin');
|
||||
runCLI(
|
||||
`generate @nx/plugin:plugin ${plugin} --linter=eslint --tags=e2etag,e2ePackage `
|
||||
);
|
||||
const pluginProject = readJson(join(plugin, 'project.json'));
|
||||
expect(pluginProject.tags).toEqual(['e2etag', 'e2ePackage']);
|
||||
}, 90000);
|
||||
});
|
||||
|
||||
it('should be able to generate a create-package plugin without e2e tests', async () => {
|
||||
const plugin = uniq('plugin');
|
||||
const createAppName = `create-${plugin}-app`;
|
||||
runCLI(
|
||||
`generate @nx/plugin:plugin ${plugin} --e2eTestRunner jest --publishable`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/plugin:create-package ${createAppName} --name=${createAppName} --project=${plugin} --verbose`
|
||||
);
|
||||
|
||||
const buildResults = runCLI(`build ${createAppName}`);
|
||||
expect(buildResults).toContain('Done compiling TypeScript files');
|
||||
|
||||
checkFilesExist(
|
||||
`${plugin}/src/generators/preset`,
|
||||
`${createAppName}`,
|
||||
`dist/${createAppName}/bin/index.js`
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to generate a create-package plugin ', async () => {
|
||||
const plugin = uniq('plugin');
|
||||
const createAppName = `create-${plugin}-app`;
|
||||
runCLI(
|
||||
`generate @nx/plugin:plugin ${plugin} --e2eTestRunner jest --publishable`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/plugin:create-package ${createAppName} --name=${createAppName} --project=${plugin} --e2eProject=${plugin}-e2e --verbose`
|
||||
);
|
||||
|
||||
const buildResults = runCLI(`build ${createAppName}`);
|
||||
expect(buildResults).toContain('Done compiling TypeScript files');
|
||||
|
||||
checkFilesExist(
|
||||
`${plugin}/src/generators/preset`,
|
||||
`${createAppName}`,
|
||||
`dist/${createAppName}/bin/index.js`
|
||||
);
|
||||
|
||||
runCLI(`e2e ${plugin}-e2e`);
|
||||
});
|
||||
|
||||
it('should throw an error when run create-package for an invalid plugin ', async () => {
|
||||
const plugin = uniq('plugin');
|
||||
expect(() =>
|
||||
runCLI(
|
||||
`generate @nx/plugin:create-package create-${plugin} --name=create-${plugin} --project=invalid-plugin`
|
||||
)
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it('should support the new name and root format', async () => {
|
||||
const plugin = uniq('plugin');
|
||||
const createAppName = `create-${plugin}-app`;
|
||||
|
||||
runCLI(
|
||||
`generate @nx/plugin:plugin ${plugin} --e2eTestRunner jest --publishable`
|
||||
);
|
||||
|
||||
// check files are generated without the layout directory ("libs/") and
|
||||
// using the project name as the directory when no directory is provided
|
||||
checkFilesExist(`${plugin}/src/index.ts`);
|
||||
// check build works
|
||||
expect(runCLI(`build ${plugin}`)).toContain(
|
||||
`Successfully ran target build for project ${plugin}`
|
||||
);
|
||||
// check tests pass
|
||||
const appTestResult = runCLI(`test ${plugin}`);
|
||||
expect(appTestResult).toContain(
|
||||
`Successfully ran target test for project ${plugin}`
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/plugin:create-package ${createAppName} --name=${createAppName} --project=${plugin} --e2eProject=${plugin}-e2e`
|
||||
);
|
||||
|
||||
// check files are generated without the layout directory ("libs/") and
|
||||
// using the project name as the directory when no directory is provided
|
||||
checkFilesExist(`${plugin}/src/generators/preset`, `${createAppName}`);
|
||||
// check build works
|
||||
expect(runCLI(`build ${createAppName}`)).toContain(
|
||||
`Successfully ran target build for project ${createAppName}`
|
||||
);
|
||||
// check tests pass
|
||||
const libTestResult = runCLI(`test ${createAppName}`);
|
||||
expect(libTestResult).toContain(
|
||||
`Successfully ran target test for project ${createAppName}`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"types": ["node", "jest"]
|
||||
},
|
||||
"include": [],
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "../utils"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/nx"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "out-tsc/spec",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"exclude": ["out-tsc"],
|
||||
"include": [
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.js",
|
||||
"**/*.test.js",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.test.jsx",
|
||||
"src/**/*.ts",
|
||||
"**/*.d.ts",
|
||||
"jest.config.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user