Skip to content

Commit

Permalink
feat(misc): nx init should work on non-monorepo projects
Browse files Browse the repository at this point in the history
  • Loading branch information
vsavkin committed Nov 22, 2022
1 parent 90f2791 commit 661bea4
Show file tree
Hide file tree
Showing 7 changed files with 357 additions and 165 deletions.
37 changes: 34 additions & 3 deletions e2e/nx-init/src/nx-init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
cleanupProject,
createNonNxProjectDirectory,
getPackageManagerCommand,
getPublishedVersion,
getSelectedPackageManager,
renameFile,
runCLI,
Expand All @@ -16,8 +17,8 @@ describe('nx init', () => {

afterEach(() => cleanupProject());

it('should work', () => {
createNonNxProjectDirectory();
it('should work in a monorepo', () => {
createNonNxProjectDirectory('monorepo', true);
updateFile(
'packages/package/package.json',
JSON.stringify({
Expand All @@ -30,12 +31,42 @@ describe('nx init', () => {

runCommand(pmc.install);

const output = runCommand(`${pmc.runUninstalledPackage} nx init -y`);
const output = runCommand(
`${pmc.runUninstalledPackage} nx@${getPublishedVersion()} init -y`
);
expect(output).toContain('Enabled computation caching');

expect(runCLI('run package:echo')).toContain('123');
renameFile('nx.json', 'nx.json.old');

expect(runCLI('run package:echo')).toContain('123');
});

it('should work in a regular npm repo ttt', () => {
createNonNxProjectDirectory('regular-repo', false);
updateFile(
'package.json',
JSON.stringify({
name: 'package',
scripts: {
echo: 'echo 123',
},
})
);

runCommand(pmc.install);

const output = runCommand(
`${
pmc.runUninstalledPackage
} nx@${getPublishedVersion()} init -y --cacheable=echo`
);
console.log(output);
expect(output).toContain('Enabled computation caching');

expect(runCLI('echo')).toContain('123');
renameFile('nx.json', 'nx.json.old');

expect(runCLI('echo')).toContain('123');
});
});
7 changes: 5 additions & 2 deletions e2e/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,17 @@ export function readProjectConfig(projectName: string): ProjectConfiguration {
return readJson(path);
}

export function createNonNxProjectDirectory(name = uniq('proj')) {
export function createNonNxProjectDirectory(
name = uniq('proj'),
addWorkspaces = true
) {
projName = name;
ensureDirSync(tmpProjPath());
createFile(
'package.json',
JSON.stringify({
name,
workspaces: ['packages/*'],
workspaces: addWorkspaces ? ['packages/*'] : undefined,
})
);
}
Expand Down
169 changes: 28 additions & 141 deletions packages/add-nx-to-monorepo/src/add-nx-to-monorepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@

import * as path from 'path';
import * as fs from 'fs';
import { execSync } from 'child_process';
import * as enquirer from 'enquirer';
import { joinPathFragments } from 'nx/src/utils/path';
import {
getPackageManagerCommand,
PackageManagerCommands,
} from 'nx/src/utils/package-manager';
import { getPackageManagerCommand } from 'nx/src/utils/package-manager';
import { output } from 'nx/src/utils/output';
import { readJsonFile, writeJsonFile } from 'nx/src/utils/fileutils';
import { readJsonFile } from 'nx/src/utils/fileutils';
import ignore from 'ignore';
import * as yargsParser from 'yargs-parser';
import {
askAboutNxCloud,
createNxJsonFile,
initCloud,
runInstall,
addDepsToPackageJson,
} from 'nx/src/nx-init/utils';

const parsedArgs = yargsParser(process.argv, {
boolean: ['yes'],
Expand All @@ -38,13 +41,12 @@ async function addNxToMonorepo() {

output.log({ title: `🐳 Nx initialization` });

const pmc = getPackageManagerCommand();
const packageJsonFiles = allProjectPackageJsonFiles(repoRoot);
const scripts = combineAllScriptNames(repoRoot, packageJsonFiles);

let targetDefaults: string[];
let cacheableOperations: string[];
let scriptOutputs = {};
let scriptOutputs = {} as { [script: string]: string };
let useCloud: boolean;

if (parsedArgs.yes !== true) {
Expand All @@ -57,8 +59,7 @@ async function addNxToMonorepo() {
{
type: 'multiselect',
name: 'targetDefaults',
message:
'Which of the following scripts need to be run in deterministic/topological order?',
message: `Which scripts need to be run in order? (e.g. before building a project, dependent projects must be built.)`,
choices: scripts,
},
])) as any
Expand All @@ -70,21 +71,23 @@ async function addNxToMonorepo() {
type: 'multiselect',
name: 'cacheableOperations',
message:
'Which of the following scripts are cacheable? (Produce the same output given the same input, e.g. build, test and lint usually are, serve and start are not)',
'Which scripts are cacheable? (Produce the same output given the same input, e.g. build, test and lint usually are, serve and start are not)',
choices: scripts,
},
])) as any
).cacheableOperations;

for (const scriptName of cacheableOperations) {
// eslint-disable-next-line no-await-in-loop
scriptOutputs[scriptName] = await enquirer.prompt([
{
type: 'input',
name: scriptName,
message: `Does the "${scriptName}" script create any outputs? If not, leave blank, otherwise provide a path relative to a project root (e.g. dist, lib, build, coverage)`,
},
]);
scriptOutputs[scriptName] = (
await enquirer.prompt([
{
type: 'input',
name: scriptName,
message: `Does the "${scriptName}" script create any outputs? If not, leave blank, otherwise provide a path relative to a project root (e.g. dist, lib, build, coverage)`,
},
])
)[scriptName];
}

useCloud = await askAboutNxCloud();
Expand All @@ -98,42 +101,20 @@ async function addNxToMonorepo() {
repoRoot,
targetDefaults,
cacheableOperations,
scriptOutputs
scriptOutputs,
undefined
);

addDepsToPackageJson(repoRoot, useCloud);

output.log({ title: `📦 Installing dependencies` });
runInstall(repoRoot, pmc);
runInstall(repoRoot);

if (useCloud) {
initCloud(repoRoot, pmc);
initCloud(repoRoot);
}

printFinalMessage(pmc);
}

function askAboutNxCloud() {
return enquirer
.prompt([
{
name: 'NxCloud',
message: `Enable distributed caching to make your CI faster`,
type: 'autocomplete',
choices: [
{
name: 'Yes',
hint: 'I want faster builds',
},

{
name: 'No',
},
],
initial: 'Yes' as any,
},
])
.then((a: { NxCloud: 'Yes' | 'No' }) => a.NxCloud === 'Yes');
printFinalMessage();
}

// scanning package.json files
Expand Down Expand Up @@ -198,102 +179,8 @@ function combineAllScriptNames(
return [...res];
}

function createNxJsonFile(
repoRoot: string,
targetDefaults: string[],
cacheableOperations: string[],
scriptOutputs: { [name: string]: string }
) {
const nxJsonPath = joinPathFragments(repoRoot, 'nx.json');
let nxJson = {} as any;
try {
nxJson = readJsonFile(nxJsonPath);
// eslint-disable-next-line no-empty
} catch {}

nxJson.tasksRunnerOptions ||= {};
nxJson.tasksRunnerOptions.default ||= {};
nxJson.tasksRunnerOptions.default.runner ||= 'nx/tasks-runners/default';
nxJson.tasksRunnerOptions.default.options ||= {};
nxJson.tasksRunnerOptions.default.options.cacheableOperations =
cacheableOperations;
nxJson.targetDefaults ||= {};
for (const scriptName of targetDefaults) {
nxJson.targetDefaults[scriptName] ||= {};
nxJson.targetDefaults[scriptName] = { dependsOn: [`^${scriptName}`] };
}
for (const [scriptName, scriptAnswerData] of Object.entries(scriptOutputs)) {
if (!scriptAnswerData[scriptName]) {
// eslint-disable-next-line no-continue
continue;
}
nxJson.targetDefaults[scriptName] ||= {};
nxJson.targetDefaults[scriptName].outputs = [
`{projectRoot}/${scriptAnswerData[scriptName]}`,
];
}
nxJson.defaultBase = deduceDefaultBase();
writeJsonFile(nxJsonPath, nxJson);
}

function deduceDefaultBase() {
try {
execSync(`git rev-parse --verify main`, {
stdio: ['ignore', 'ignore', 'ignore'],
});
return 'main';
} catch {
try {
execSync(`git rev-parse --verify dev`, {
stdio: ['ignore', 'ignore', 'ignore'],
});
return 'dev';
} catch {
try {
execSync(`git rev-parse --verify develop`, {
stdio: ['ignore', 'ignore', 'ignore'],
});
return 'develop';
} catch {
try {
execSync(`git rev-parse --verify next`, {
stdio: ['ignore', 'ignore', 'ignore'],
});
return 'next';
} catch {
return 'master';
}
}
}
}
}

// add dependencies
function addDepsToPackageJson(repoRoot: string, useCloud: boolean) {
const json = readJsonFile(joinPathFragments(repoRoot, `package.json`));
if (!json.devDependencies) json.devDependencies = {};
json.devDependencies['nx'] = require('../package.json').version;
if (useCloud) {
json.devDependencies['@nrwl/nx-cloud'] = 'latest';
}
writeJsonFile(`package.json`, json);
}

function runInstall(repoRoot: string, pmc: PackageManagerCommands) {
execSync(pmc.install, { stdio: [0, 1, 2], cwd: repoRoot });
}

function initCloud(repoRoot: string, pmc: PackageManagerCommands) {
execSync(
`${pmc.exec} nx g @nrwl/nx-cloud:init --installationSource=add-nx-to-monorepo`,
{
stdio: [0, 1, 2],
cwd: repoRoot,
}
);
}

function printFinalMessage(pmc: PackageManagerCommands) {
function printFinalMessage() {
const pmc = getPackageManagerCommand();
output.success({
title: `🎉 Done!`,
bodyLines: [
Expand Down
27 changes: 23 additions & 4 deletions packages/nx/src/command-line/init.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
import { execSync } from 'child_process';
import { existsSync } from 'fs';
import { readJsonFile } from '../utils/fileutils';
import { addNxToNpmRepo } from '../nx-init/add-nx-to-npm-repo';

export function initHandler() {
export async function initHandler() {
const args = process.argv.slice(2).join(' ');
if (existsSync('package.json')) {
execSync(`npx --yes add-nx-to-monorepo@latest ${args}`, {
stdio: [0, 1, 2],
});
if (isMonorepo()) {
// TODO: vsavkin remove add-nx-to-monorepo
execSync(`npx --yes add-nx-to-monorepo@latest ${args}`, {
stdio: [0, 1, 2],
});
} else {
await addNxToNpmRepo();
}
} else {
execSync(`npx --yes create-nx-workspace@latest ${args}`, {
stdio: [0, 1, 2],
});
}
}

function isMonorepo() {
const packageJson = readJsonFile('package.json');
if (!!packageJson.workspaces) return true;

if (existsSync('pnpm-workspace.yaml') || existsSync('pnpm-workspace.yml'))
return true;

if (existsSync('lerna.json')) return true;

return false;
}
30 changes: 15 additions & 15 deletions packages/nx/src/command-line/run-one.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,21 +161,21 @@ export function calculateDefaultProjectName(
workspaceConfiguration: ProjectsConfigurations & NxJsonConfiguration
) {
let relativeCwd = cwd.replace(/\\/g, '/').split(root.replace(/\\/g, '/'))[1];
if (relativeCwd) {
relativeCwd = relativeCwd.startsWith('/')
? relativeCwd.substring(1)
: relativeCwd;
const matchingProject = Object.keys(workspaceConfiguration.projects).find(
(p) => {
const projectRoot = workspaceConfiguration.projects[p].root;
return (
relativeCwd == projectRoot ||
relativeCwd.startsWith(`${projectRoot}/`)
);
}
);
if (matchingProject) return matchingProject;
}

relativeCwd = relativeCwd.startsWith('/')
? relativeCwd.substring(1)
: relativeCwd;
const matchingProject = Object.keys(workspaceConfiguration.projects).find(
(p) => {
const projectRoot = workspaceConfiguration.projects[p].root;
return (
relativeCwd == projectRoot ||
(relativeCwd == '' && projectRoot == '.') ||
relativeCwd.startsWith(`${projectRoot}/`)
);
}
);
if (matchingProject) return matchingProject;
return (
(workspaceConfiguration.cli as { defaultProjectName: string })
?.defaultProjectName || workspaceConfiguration.defaultProject
Expand Down
Loading

0 comments on commit 661bea4

Please sign in to comment.