Skip to content

Commit

Permalink
feat(roc-plugin-repo): Made it possible to define custom scripts
Browse files Browse the repository at this point in the history
This can be done using npm scripts in the package.json for a project. Additionally it's possible to add actions that interact with how projects are built.
  • Loading branch information
dlmr committed Dec 14, 2017
1 parent f7afc22 commit 86b5b55
Show file tree
Hide file tree
Showing 16 changed files with 486 additions and 233 deletions.
2 changes: 2 additions & 0 deletions extensions/roc-plugin-repo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"micromatch": "^3.0.3",
"ora": "^1.3.0",
"output-file-sync": "^2.0.0",
"p-all": "^1.0.0",
"p-series": "^1.0.0",
"path-exists": "^3.0.0",
"pify": "^3.0.0",
Expand All @@ -66,6 +67,7 @@
"semver": "^5.3.0",
"signal-exit": "^3.0.2",
"slash": "^1.0.0",
"strong-log-transformer": "^1.0.6",
"temp": "^0.8.3",
"tempfile": "^2.0.0",
"write-pkg": "^3.1.0"
Expand Down
73 changes: 73 additions & 0 deletions extensions/roc-plugin-repo/src/actions/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import babel from '../commands/utils/babel';
import { invokeHook } from '../util';

function buildWithBabel(config, watch, createLogger) {
const settings = config.settings.repo;

return (projects, mode, multipleTargets) =>
Promise.all(
projects.map(project => {
const logger = createLogger(project.name).logger;
const babelConfig = invokeHook('babel-config', mode, project);
try {
logger(`Building for ${mode} with Babel`);
return babel(
{
mode,
path: project.path,
src: `${project.path}/${settings.input}`,
out: multipleTargets
? `${project.path}/${settings.output}/${mode}`
: `${project.path}/${settings.output}`,
// We want to ignore potential __snapshots__ and __mocks__ directories
ignore: settings.test.concat([
'**/__snapshots__/**',
'**/__mocks__/**',
]),
copyFiles: true,
sourceMaps: true,
babelrc: false,
watch,
log: logger,
},
babelConfig,
);
} catch (err) {
err.projectName = project.name;
if (err._babel && err instanceof SyntaxError) {
// Display codeFrame if it is an Babel Error
err.message = `${err.message}\n${err.codeFrame}`;
}
throw err;
}
}),
);
}

export default (context, projects, { options: { watch }, createLogger }) => {
const esmJavaScriptBuild = projects.filter(
({ packageJSON }) =>
packageJSON.module && packageJSON.module.endsWith('.js'),
);
const cjsJavaScriptBuild = projects.filter(
({ packageJSON }) => packageJSON.main && packageJSON.main.endsWith('.js'),
);

return async () => {
if (cjsJavaScriptBuild.length > 0) {
await buildWithBabel(context.config, watch, createLogger)(
cjsJavaScriptBuild,
'cjs',
esmJavaScriptBuild.length > 0,
);
}

if (esmJavaScriptBuild.length > 0) {
await buildWithBabel(context.config, watch, createLogger)(
esmJavaScriptBuild,
'esm',
cjsJavaScriptBuild.length > 0,
);
}
};
};
17 changes: 17 additions & 0 deletions extensions/roc-plugin-repo/src/actions/clean.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import execa from 'execa';
import Listr from 'listr';
import isCI from 'is-ci';

const rimraf = require.resolve('rimraf/bin');

const clean = (project, output) => `${rimraf} ${project.path}/${output}`;

export default (context, projects) => () =>
new Listr(
projects.map(project => ({
title: `Cleaning ${project.name}`,
task: () =>
execa.shell(clean(project, context.config.settings.repo.output)),
})),
{ concurrent: true, renderer: isCI ? 'verbose' : 'default' },
).run();
61 changes: 61 additions & 0 deletions extensions/roc-plugin-repo/src/actions/lint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import path from 'path';

import execa from 'execa';
import { fileExists } from 'roc';
import log from 'roc/log/default/small';

const eslint = require.resolve('eslint/bin/eslint');

const eslintCommand = (project, fix, config, ignorePattern, ignoreFile) =>
`${eslint} ${config}${project.path} --ignore-path '${ignoreFile}' --ignore-pattern '${ignorePattern}' ${fix
? '--fix'
: ''}`;

export default (context, projects, { options: { fix, forceDefault } }) => {
let config;
if (fileExists('.eslintrc', context.directory) && !forceDefault) {
log.info(
'A local ESLint configuration was detected and will be used over the default.\n',
);
config = '';
} else {
config = `--config ${require.resolve('../configuration/eslintrc.js')} `;
}

let ignoreFile;
if (fileExists('.eslintignore', context.directory) && !forceDefault) {
log.info(
'A local ESLint ignore file was detected and will be used over the default.\n',
);
ignoreFile = path.join(context.directory, '.eslintignore');
} else {
ignoreFile = require.resolve('../configuration/eslintignore');
}

return () =>
Promise.all(
projects
.map(project => {
const ignorePattern = path.join(
path.relative(
context.directory,
path.resolve(project.path, context.config.settings.repo.output),
),
'*',
);

return execa.shell(
eslintCommand(project, fix, config, ignorePattern, ignoreFile),
{ stdio: 'inherit' },
);
})
.map(promise => promise.then(() => 0, () => 1)),
).then(results => {
const status = results.reduce((previous, current) =>
Math.max(previous, current),
);
if (status === 1) {
process.exitCode = 1;
}
});
};
24 changes: 24 additions & 0 deletions extensions/roc-plugin-repo/src/actions/publish.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import execa from 'execa';

/**
* By default all projects will be published using npm something that we might want to go away from.
* The question is if there is anything that can be used to detect if a project should be published
* using npm.
*
* One alternative would have been to use "private: true" but that is already used for other
* purposes, mainly making sure we don't do anything in terms of releasing for them.
*/
export default (context, projects, { options: { distTag } }) => () =>
Promise.all(
projects.map(project => {
let registry = '';
const publishConfig = project.packageJSON.publishConfig;
if (publishConfig && publishConfig.registry) {
registry = `--registry='${publishConfig.registry}'`;
}

return execa.shell(`npm publish ${registry} --tag ${distTag}`, {
cwd: project.path,
});
}),
);
19 changes: 19 additions & 0 deletions extensions/roc-plugin-repo/src/actions/runScript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { lazyFunctionRequire } from 'roc';

const lazyRequire = lazyFunctionRequire(require);

export default ({ context }) => (script, projects, extra) => () => {
if (script === 'build') {
return lazyRequire('./build')(context, projects, extra);
} else if (script === 'clean') {
return lazyRequire('./clean')(context, projects, extra);
} else if (script === 'lint') {
return lazyRequire('./lint')(context, projects, extra);
} else if (script === 'publish') {
return lazyRequire('./publish')(context, projects, extra);
} else if (script === 'test') {
return lazyRequire('./test')(context, projects, extra);
}

return [];
};
39 changes: 39 additions & 0 deletions extensions/roc-plugin-repo/src/actions/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import jest from 'jest'; // eslint-disable-line import/no-extraneous-dependencies

export default (context, projects, { options, extraArguments }) => {
// Enforce test
process.env.NODE_ENV = 'test';

process.env.ROC_INITAL_ARGV = JSON.stringify(process.argv);

let argv = [...extraArguments];

const jestConfig = {
resolver: require.resolve('../commands/utils/jest/roc-resolver.js'),
testPathIgnorePatterns: projects.map(
({ path }) =>
`${path}/(${context.config.settings.repo.output}|node_modules)/`,
),
transform: {
'^.+\\.js$': require.resolve(
'../commands/utils/jest/babel-jest-transformer.js',
),
},
testMatch: [].concat(
...projects.map(({ path }) =>
context.config.settings.repo.test.map(pattern => `${path}/${pattern}`),
),
),
};

argv.push('--config', JSON.stringify(jestConfig));
argv = argv
.concat(
Object.keys(options).map(
key => options[key] !== undefined && `--${key}=${options[key]}`,
),
)
.filter(Boolean);

return () => jest.run(argv);
};
72 changes: 3 additions & 69 deletions extensions/roc-plugin-repo/src/commands/build.js
Original file line number Diff line number Diff line change
@@ -1,80 +1,14 @@
import log from 'roc/log/default';

import { invokeHook } from '../util';
import babel from './utils/babel';
import scriptRunner from './utils/scriptRunner';

export default projects => ({
arguments: { managed: { projects: selectedProjects } },
options: { managed: { watch } },
options: { managed: { watch, concurrent } },
context,
}) => {
const settings = context.config.settings.repo;
const selected = projects.filter(
({ name }) => !selectedProjects || selectedProjects.includes(name),
);

if (selected.length === 0) {
return log.small.warn('No projects were found');
}

return Promise.all(
settings.targets.map(mode =>
Promise.all(
selected
.map(
project =>
new Promise((resolve, reject) => {
const config = invokeHook('babel-config', mode, project);
try {
babel(
{
identifier: project.name,
mode,
path: project.path,
src: `${project.path}/${settings.input}`,
out:
settings.targets.length === 1
? `${project.path}/${settings.output}`
: `${project.path}/${settings.output}/${mode}`,
// We want to ignore potential __snapshots__ and __mocks__ directories
ignore: settings.test.concat([
'**/__snapshots__/**',
'**/__mocks__/**',
]),
copyFiles: true,
sourceMaps: true,
babelrc: false,
watch,
},
config,
);
} catch (err) {
if (err._babel && err instanceof SyntaxError) {
// Display codeFrame if it is an Babel Error
err.message = `${err.message}\n${err.codeFrame}`;
log.large.warn(project.name, 'Build Error', err);
}
reject(err);
}
resolve();
}),
)
.map(promise => promise.then(() => 0, () => 1)),
).then(results =>
results.reduce((previous, current) => Math.max(previous, current)),
),
),
).then(results => {
const status = results.reduce((previous, current) =>
Math.max(previous, current),
);
if (status === 1) {
process.exitCode = 1;
log.small.log(); // Create a new line for padding purposes
log.small.error('Build failed.');
} else if (!watch) {
log.small.log(); // Create a new line for padding purposes
log.small.success('Build succeeded.');
}
});
return scriptRunner('build')(selected, settings, concurrent, { watch });
};
23 changes: 4 additions & 19 deletions extensions/roc-plugin-repo/src/commands/clean.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,14 @@
import { execute } from 'roc';
import log from 'roc/log/default/small';
import Listr from 'listr';
import isCI from 'is-ci';

const rimraf = require.resolve('rimraf/bin');

const clean = (project, output) => `${rimraf} ${project.path}/${output}`;
import scriptRunner from './utils/scriptRunner';

export default projects => ({
arguments: { managed: { projects: selectedProjects } },
options: { managed: { concurrent } },
context,
}) => {
const settings = context.config.settings.repo;
const selected = projects.filter(
({ name }) => !selectedProjects || selectedProjects.includes(name),
);

if (selected.length === 0) {
return log.warn('No projects were found');
}

return new Listr(
selected.map(project => ({
title: `Cleaning ${project.name}`,
task: () => execute(clean(project, context.config.settings.repo.output)),
})),
{ concurrent: true, renderer: isCI ? 'verbose' : 'default' },
).run();
return scriptRunner('clean')(selected, settings, concurrent);
};
Loading

0 comments on commit 86b5b55

Please sign in to comment.