├── .nvmrc
├── .yarnrc
├── packages
├── lib
│ ├── .gitignore
│ ├── README.md
│ ├── src
│ │ ├── fns.js
│ │ ├── packages
│ │ │ ├── clean-packages.js
│ │ │ ├── index.js
│ │ │ ├── clean-package.js
│ │ │ ├── build-package.js
│ │ │ └── resolve-packages.js
│ │ ├── logic.js
│ │ ├── objects.js
│ │ ├── arrays.js
│ │ ├── index.js
│ │ ├── errors.js
│ │ ├── plugins
│ │ │ ├── develop-build
│ │ │ │ └── index.js
│ │ │ ├── clean-build
│ │ │ │ └── index.js
│ │ │ ├── develop-server
│ │ │ │ ├── index.js
│ │ │ │ └── develop.js
│ │ │ ├── utils.js
│ │ │ ├── resolve-plugin.js
│ │ │ └── script
│ │ │ │ └── index.js
│ │ ├── strings.js
│ │ ├── fs.js
│ │ ├── config
│ │ │ ├── get-package-roots.js
│ │ │ ├── utils.js
│ │ │ └── index.js
│ │ ├── childProcess.js
│ │ ├── colors.js
│ │ ├── types.js
│ │ └── terminal.js
│ └── package.json
├── utils
│ ├── .gitignore
│ ├── README.md
│ ├── src
│ │ ├── index.js
│ │ ├── load-env-vars.js
│ │ ├── get-web-package-manifest.js
│ │ └── configure-graceful-exit.js
│ └── package.json
├── cli
│ ├── README.md
│ ├── src
│ │ ├── lib
│ │ │ ├── handle-error.js
│ │ │ ├── prevent-script-exit.js
│ │ │ └── async-command.js
│ │ ├── commands
│ │ │ ├── deploy.js
│ │ │ ├── clean.js
│ │ │ ├── develop.js
│ │ │ └── build.js
│ │ ├── bin
│ │ │ └── lerna-cola.js
│ │ ├── development-service
│ │ │ ├── create-package-conductor.js
│ │ │ ├── create-package-watcher.js
│ │ │ ├── graceful-shutdown-manager.js
│ │ │ └── index.js
│ │ └── deployment-service
│ │ │ └── index.js
│ └── package.json
├── plugin-build-babel
│ ├── README.md
│ ├── package.json
│ └── src
│ │ └── index.js
├── plugin-build-flow
│ ├── README.md
│ ├── package.json
│ └── src
│ │ └── index.js
├── plugin-deploy-now
│ ├── README.md
│ ├── package.json
│ └── src
│ │ └── index.js
├── babel-plugin-inject-source-map-init
│ ├── README.md
│ ├── package.json
│ └── src
│ │ └── index.js
└── babel-config
│ ├── package.json
│ └── src
│ └── index.js
├── .eslintignore
├── lerna.json
├── flow
└── .flowconfig
├── scripts
├── clean.js
├── build.js
└── utils.js
├── .gitignore
├── LICENSE
├── tasks.todo
├── package.json
├── CODE_OF_CONDUCT.md
└── README.md
/.nvmrc:
--------------------------------------------------------------------------------
1 | 8
2 |
--------------------------------------------------------------------------------
/.yarnrc:
--------------------------------------------------------------------------------
1 | --install.strict-semver true
--------------------------------------------------------------------------------
/packages/lib/.gitignore:
--------------------------------------------------------------------------------
1 | /modules
2 |
--------------------------------------------------------------------------------
/packages/utils/.gitignore:
--------------------------------------------------------------------------------
1 | /modules
2 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | packages/*/build
2 | packages/*/flow-typed
--------------------------------------------------------------------------------
/packages/cli/README.md:
--------------------------------------------------------------------------------
1 | # @lerna-cola/cli
2 |
3 | The [lerna-cola](https://github.com/ctrlplusb/lerna-cola) CLI.
4 |
--------------------------------------------------------------------------------
/packages/lib/README.md:
--------------------------------------------------------------------------------
1 | # @lerna-cola/lib
2 |
3 | The lib for [lerna-cola](https://github.com/ctrlplusb/lerna-cola) packages.
4 |
--------------------------------------------------------------------------------
/packages/plugin-build-babel/README.md:
--------------------------------------------------------------------------------
1 | # @lerna-cola/plugin-build-babel
2 |
3 | See [lerna-cola](https://github.com/ctrlplusb/lerna-cola)
4 |
--------------------------------------------------------------------------------
/packages/plugin-build-flow/README.md:
--------------------------------------------------------------------------------
1 | # @lerna-cola/plugin-build-flow
2 |
3 | See [lerna-cola](https://github.com/ctrlplusb/lerna-cola)
4 |
--------------------------------------------------------------------------------
/packages/plugin-deploy-now/README.md:
--------------------------------------------------------------------------------
1 | # @lerna-cola/plugin-deploy-now
2 |
3 | See [lerna-cola](https://github.com/ctrlplusb/lerna-cola)
4 |
--------------------------------------------------------------------------------
/packages/babel-plugin-inject-source-map-init/README.md:
--------------------------------------------------------------------------------
1 | # babel-plugin-inject-source-map-init
2 |
3 | See [lerna-cola](https://github.com/ctrlplusb/lerna-cola)
4 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "lerna": "2.11.0",
3 | "npmClient": "yarn",
4 | "packages": ["packages/*"],
5 | "registry": "https://registry.npmjs.org/",
6 | "version": "independent"
7 | }
8 |
--------------------------------------------------------------------------------
/flow/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | build/
3 | node_modules/
4 | packages/*/build
5 | flow-typed/npm/execa*
6 |
7 | [include]
8 | flow-typed/npm
9 |
10 | [libs]
11 |
12 | [lints]
13 |
14 | [options]
15 |
--------------------------------------------------------------------------------
/packages/utils/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | # @lerna-cola/utils
8 |
9 | Helpful utilities for [lerna-cola](https://github.com/ctrlplusb/lerna-cola) projects.
10 |
--------------------------------------------------------------------------------
/scripts/clean.js:
--------------------------------------------------------------------------------
1 | const { cleanAsync } = require('./utils')
2 |
3 | cleanAsync().then(() => process.exit(0))
4 |
5 | function preventScriptExit() {
6 | ;(function wait() {
7 | // eslint-disable-next-line no-constant-condition
8 | if (true) setTimeout(wait, 1000)
9 | })()
10 | }
11 |
12 | preventScriptExit()
13 |
--------------------------------------------------------------------------------
/packages/utils/src/index.js:
--------------------------------------------------------------------------------
1 | const configureGracefulExit = require('./configure-graceful-exit')
2 | const getWebPackageManifest = require('./get-web-package-manifest')
3 | const loadEnvVars = require('./load-env-vars')
4 |
5 | module.exports = {
6 | configureGracefulExit,
7 | getWebPackageManifest,
8 | loadEnvVars,
9 | }
10 |
--------------------------------------------------------------------------------
/packages/lib/src/fns.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | function throttle(duration: number, fn: () => mixed) {
4 | let throttler = null
5 | return (...args: Array) => {
6 | if (throttler) {
7 | clearTimeout(throttler)
8 | }
9 | throttler = setTimeout(() => fn(...args), duration)
10 | }
11 | }
12 |
13 | module.exports = {
14 | throttle,
15 | }
16 |
--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------
1 | const { cleanAsync, buildAsync } = require('./utils')
2 |
3 | cleanAsync()
4 | .then(buildAsync)
5 | .then(() => process.exit(0))
6 |
7 | function preventScriptExit() {
8 | ;(function wait() {
9 | // eslint-disable-next-line no-constant-condition
10 | if (true) setTimeout(wait, 1000)
11 | })()
12 | }
13 |
14 | preventScriptExit()
15 |
--------------------------------------------------------------------------------
/packages/babel-plugin-inject-source-map-init/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": false,
3 | "version": "0.26.9",
4 | "name": "babel-plugin-inject-source-map-init",
5 | "description": "Injects a safe initialisation of source-map-support into your transpiled files",
6 | "main": "src/index.js",
7 | "dependencies": {
8 | "babel-template": "6.26.0"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /deprecated
2 |
3 | # Various OS / Runtimes / Node
4 | .DS_Store
5 | .lock-wscript
6 | .simeerc
7 | .vscode
8 | *.log
9 | *.pid
10 | *.seed
11 | flow-typed
12 | lerna-debug.log
13 | logs
14 | node_modules
15 | npm-debug.log
16 | pids
17 | Thumbs.db
18 |
19 | # Transpiled Packages
20 | packages/*/build
21 |
22 | # Generated files
23 | packages/*/.flowconfig
24 |
25 |
--------------------------------------------------------------------------------
/packages/lib/src/packages/clean-packages.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { Package } from '../types'
4 |
5 | const pSeries = require('p-series')
6 | const cleanPackage = require('./clean-package')
7 |
8 | module.exports = async function cleanPackages(
9 | packages: Array,
10 | ): Promise {
11 | await pSeries(packages.map(p => () => cleanPackage(p)))
12 | }
13 |
--------------------------------------------------------------------------------
/packages/lib/src/packages/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | const buildPackage = require('./build-package')
4 | const cleanPackages = require('./clean-packages')
5 | const cleanPackage = require('./clean-package')
6 | const resolvePackages = require('./resolve-packages')
7 |
8 | module.exports = {
9 | buildPackage,
10 | cleanPackages,
11 | cleanPackage,
12 | resolvePackages,
13 | }
14 |
--------------------------------------------------------------------------------
/packages/cli/src/lib/handle-error.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | const { Errors, TerminalUtils } = require('@lerna-cola/lib')
4 |
5 | const handleError = (err: Error) => {
6 | if (err instanceof Errors.PackageError) {
7 | TerminalUtils.errorPkg(err.pkg, err.message, err)
8 | } else {
9 | TerminalUtils.error(err.message, err)
10 | }
11 |
12 | process.exit(1)
13 | }
14 |
15 | module.exports = handleError
16 |
--------------------------------------------------------------------------------
/packages/cli/src/lib/prevent-script-exit.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | // Prevent node process from exiting. (until CTRL + C or process.exit is called)
4 | // We do this to allow our scripts to respont to process exit events and do
5 | // cleaning up etc.
6 | module.exports = function preventScriptExit() {
7 | ;(function wait() {
8 | // eslint-disable-next-line no-constant-condition
9 | if (true) setTimeout(wait, 1000)
10 | })()
11 | }
12 |
--------------------------------------------------------------------------------
/packages/lib/src/logic.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | const R = require('ramda')
4 |
5 | function execIfFunc(x: T | (() => T)): ?T {
6 | return typeof x === 'function' ? x() : x
7 | }
8 |
9 | const onlyIf = R.curry(function onlyIfUncurried(
10 | condition: boolean | (() => boolean),
11 | value: T | (() => T),
12 | ): ?T {
13 | return execIfFunc(condition) ? execIfFunc(value) : undefined
14 | })
15 |
16 | module.exports = {
17 | onlyIf,
18 | }
19 |
--------------------------------------------------------------------------------
/packages/lib/src/objects.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | const deepMerge = require('deepmerge')
4 |
5 | /**
6 | * Deeply merges a given set of objects together.
7 | *
8 | * Objects to the right take priority.
9 | *
10 | * @param {...Object} args - The objects to merge.
11 | *
12 | * @return {Object} - The merged object.
13 | */
14 | function mergeDeep(left: Object, right: Object): Object {
15 | return deepMerge(left, right)
16 | }
17 |
18 | module.exports = {
19 | mergeDeep,
20 | }
21 |
--------------------------------------------------------------------------------
/packages/utils/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": false,
3 | "publishConfig": {
4 | "access": "public"
5 | },
6 | "version": "0.2.3",
7 | "name": "@lerna-cola/utils",
8 | "description": "Helpful utilities for lerna-cola projects.",
9 | "repository": "https://github.com/ctrlplusb/lerna-cola",
10 | "license": "MIT",
11 | "main": "src/index.js",
12 | "files": [
13 | "src"
14 | ],
15 | "dependencies": {
16 | "deepmerge": "3.2.0",
17 | "fs-extra": "7.0.1"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/lib/src/arrays.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | const R = require('ramda')
4 |
5 | /**
6 | * Filters out all null/undefined items from the given array.
7 | *
8 | * @param {Array} as - the target array
9 | *
10 | * @return {Array} The filtered array.
11 | */
12 | function removeNil(as: Array): Array {
13 | return as.filter(a => a != null)
14 | }
15 |
16 | function removeEmpty(as: Array): Array {
17 | return as.filter(a => R.not(R.isEmpty(a)))
18 | }
19 |
20 | module.exports = {
21 | removeNil,
22 | removeEmpty,
23 | }
24 |
--------------------------------------------------------------------------------
/packages/cli/src/lib/async-command.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | const handleError = require('./handle-error')
4 |
5 | module.exports = function asyncCommand(fn: (...any) => mixed) {
6 | return (...args: Array) => {
7 | const [yargv] = args
8 | try {
9 | yargv.promisedResult = Promise.resolve(fn(...args)).catch(handleError)
10 | return yargv.promisedResult
11 | } catch (err) {
12 | handleError(err)
13 | }
14 | throw new Error(
15 | 'Invalid state. Should not have reached this branch of code.',
16 | )
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/plugin-build-babel/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": false,
3 | "publishConfig": {
4 | "access": "public"
5 | },
6 | "version": "0.3.5",
7 | "name": "@lerna-cola/plugin-build-babel",
8 | "description": "Babel plugin for lerna-cola projects.",
9 | "repository": "https://github.com/ctrlplusb/lerna-cola",
10 | "main": "build/index.js",
11 | "files": [
12 | "build"
13 | ],
14 | "dependencies": {
15 | "@lerna-cola/lib": "^0.5.5",
16 | "babel-core": "6.26.3",
17 | "fs-extra": "7.0.1",
18 | "globby": "9.1.0",
19 | "p-limit": "2.2.0",
20 | "pify": "4.0.1"
21 | },
22 | "devDependencies": {
23 | "flow-bin": "^0.94.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/plugin-build-flow/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": false,
3 | "publishConfig": {
4 | "access": "public"
5 | },
6 | "version": "0.3.5",
7 | "name": "@lerna-cola/plugin-build-flow",
8 | "description": "Flow plugin for lerna-cola projects.",
9 | "repository": "https://github.com/ctrlplusb/lerna-cola",
10 | "main": "build/index.js",
11 | "files": [
12 | "build"
13 | ],
14 | "dependencies": {
15 | "@lerna-cola/lib": "^0.5.5",
16 | "flow-remove-types": "1.2.3",
17 | "fs-extra": "7.0.1",
18 | "globby": "9.1.0",
19 | "p-limit": "2.2.0",
20 | "ramda": "0.26.1"
21 | },
22 | "devDependencies": {
23 | "flow-bin": "^0.94.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/deploy.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | const { TerminalUtils } = require('@lerna-cola/lib')
4 | const deploymentService = require('../deployment-service')
5 | const asyncCommand = require('../lib/async-command')
6 |
7 | module.exports = {
8 | command: 'deploy',
9 | desc: 'Executes the deployment process',
10 | handler: asyncCommand(async () => {
11 | try {
12 | if (!process.env.NODE_ENV) {
13 | process.env.NODE_ENV = 'production'
14 | }
15 | TerminalUtils.title('Running deploy...')
16 | await deploymentService()
17 | TerminalUtils.success('Done')
18 | } catch (ex) {
19 | TerminalUtils.error('Deployment failed', ex)
20 | }
21 | }),
22 | }
23 |
--------------------------------------------------------------------------------
/packages/lib/src/packages/clean-package.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { Package } from '../types'
4 |
5 | const TerminalUtils = require('../terminal')
6 | const config = require('../config')
7 |
8 | module.exports = async function cleanPackage(pkg: Package) {
9 | const cleanPlugin = pkg.plugins.cleanPlugin
10 | if (cleanPlugin != null) {
11 | TerminalUtils.verbosePkg(
12 | pkg,
13 | `Running clean plugin: ${cleanPlugin.plugin.name}`,
14 | )
15 | await cleanPlugin.plugin.clean(pkg, cleanPlugin.options, {
16 | config: config(),
17 | })
18 | TerminalUtils.verbosePkg(pkg, `Ran clean`)
19 | } else {
20 | TerminalUtils.verbosePkg(pkg, `No clean plugin to run`)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/lib/src/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | const ArrayUtils = require('./arrays')
4 | const ChildProcessUtils = require('./childProcess')
5 | const config = require('./config')
6 | const Errors = require('./errors')
7 | const FnUtils = require('./fns')
8 | const FsUtils = require('./fs')
9 | const LogicUtils = require('./logic')
10 | const ObjectUtils = require('./objects')
11 | const PackageUtils = require('./packages')
12 | const StringUtils = require('./strings')
13 | const TerminalUtils = require('./terminal')
14 |
15 | module.exports = {
16 | ArrayUtils,
17 | ChildProcessUtils,
18 | config,
19 | Errors,
20 | FnUtils,
21 | FsUtils,
22 | LogicUtils,
23 | ObjectUtils,
24 | PackageUtils,
25 | StringUtils,
26 | TerminalUtils,
27 | }
28 |
--------------------------------------------------------------------------------
/packages/plugin-deploy-now/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": false,
3 | "publishConfig": {
4 | "access": "public"
5 | },
6 | "version": "0.3.5",
7 | "name": "@lerna-cola/plugin-deploy-now",
8 | "description": "Allows deployment of lerna-cola projects to Zeit's now",
9 | "repository": "https://github.com/ctrlplusb/lerna-cola",
10 | "main": "build/index.js",
11 | "files": [
12 | "build"
13 | ],
14 | "dependencies": {
15 | "@lerna-cola/lib": "^0.5.5",
16 | "chalk": "2.4.2",
17 | "dedent": "0.7.0",
18 | "deepmerge": "3.2.0",
19 | "fs-extra": "^7.0.1",
20 | "p-whilst": "1.0.0",
21 | "ramda": "0.26.1",
22 | "temp-write": "3.4.0"
23 | },
24 | "devDependencies": {
25 | "flow-bin": "^0.94.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/lib/src/errors.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { Package } from './types'
4 |
5 | function LernaColaError(message: string) {
6 | this.name = 'LernaColaError'
7 | this.message = message
8 | this.stack = Error().stack
9 | }
10 |
11 | LernaColaError.prototype = Object.create(Error.prototype)
12 | LernaColaError.prototype.name = 'LernaColaError'
13 | LernaColaError.prototype.constructor = LernaColaError
14 |
15 | function PackageError(pkg: Package, message: string, err: ?Error) {
16 | this.name = 'PackageError'
17 | this.message = message
18 | this.stack = err ? err.stack : Error().stack
19 | }
20 |
21 | PackageError.prototype = Object.create(Error.prototype)
22 | PackageError.prototype.name = 'PackageError'
23 | PackageError.prototype.constructor = PackageError
24 |
25 | module.exports = {
26 | LernaColaError,
27 | PackageError,
28 | }
29 |
--------------------------------------------------------------------------------
/packages/utils/src/load-env-vars.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const fs = require('fs-extra')
3 | const dotenv = require('dotenv')
4 |
5 | module.exports = function loadEnvVars() {
6 | // First load anv env specific files
7 | const env = process.env.NODE_ENV || 'production'
8 | const envSpecificPath = path.resolve(process.cwd(), `./.env.${env}`)
9 | if (fs.existsSync(envSpecificPath)) {
10 | // eslint-disable-next-line no-console
11 | console.log(`Loading environment variables from ${envSpecificPath}`)
12 | dotenv.config({ path: envSpecificPath })
13 | }
14 |
15 | // Then load any generic .env "overides"
16 | const envPath = path.resolve(process.cwd(), './.env')
17 | if (fs.existsSync(envPath)) {
18 | // eslint-disable-next-line no-console
19 | console.log(`Loading environment variables from ${envPath}`)
20 | dotenv.config({ path: envPath })
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/clean.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | const { TerminalUtils, PackageUtils } = require('@lerna-cola/lib')
4 | const pSeries = require('p-series')
5 | const asyncCommand = require('../lib/async-command')
6 |
7 | module.exports = {
8 | command: 'clean',
9 | describe: 'Cleans the build output for packages',
10 | // $FlowFixMe
11 | builder: yargs =>
12 | yargs.option('packages', {
13 | alias: 'p',
14 | describe: 'The packages to clean',
15 | type: 'array',
16 | }),
17 | handler: asyncCommand(async argv => {
18 | try {
19 | TerminalUtils.title('Running clean command...')
20 | const packages = PackageUtils.resolvePackages(argv.packages)
21 | const clean = pkg => () => PackageUtils.cleanPackage(pkg)
22 | await pSeries(packages.map(clean))
23 | TerminalUtils.success('Done')
24 | } catch (ex) {
25 | TerminalUtils.error('Clean failed', ex)
26 | }
27 | }),
28 | }
29 |
--------------------------------------------------------------------------------
/packages/babel-plugin-inject-source-map-init/src/index.js:
--------------------------------------------------------------------------------
1 | const { EOL } = require('os')
2 | const template = require('babel-template')
3 |
4 | // We wrap our code with a check for the webpack inject env var to ensure that
5 | // we don't initialise source maps twice.
6 | const sourceMapImportAST = template(`
7 | try {
8 | var sourceMapSupport = require('source-map-support');
9 | sourceMapSupport.install({
10 | environment: 'node'
11 | });
12 | } catch(err) {
13 | console.log('In order to provide a better debugging experience of your lerna-cola packages please ensure that source-map-support is installed.${EOL}${EOL}\tnpm i source-map-support -S -E');
14 | }
15 | `)()
16 |
17 | module.exports = function sourceMapSupportPlugin() {
18 | return {
19 | visitor: {
20 | Program: {
21 | exit(path) {
22 | path.unshiftContainer('body', sourceMapImportAST)
23 | },
24 | },
25 | },
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/cli/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": false,
3 | "publishConfig": {
4 | "access": "public"
5 | },
6 | "version": "0.5.5",
7 | "name": "@lerna-cola/cli",
8 | "description": "The lerna-cola CLI.",
9 | "repository": "https://github.com/ctrlplusb/lerna-cola",
10 | "bin": {
11 | "lerna-cola": "build/bin/lerna-cola.js"
12 | },
13 | "engines": {
14 | "node": ">=8"
15 | },
16 | "files": [
17 | "build"
18 | ],
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/ctrlplusb/lerna-cola/issues"
22 | },
23 | "dependencies": {
24 | "@lerna-cola/lib": "^0.5.5",
25 | "@lerna-cola/utils": "^0.2.3",
26 | "chokidar": "2.1.2",
27 | "dotenv": "7.0.0",
28 | "fs-extra": "7.0.1",
29 | "p-series": "2.0.0",
30 | "ramda": "0.26.1",
31 | "read-pkg": "5.0.0",
32 | "semver": "5.6.0",
33 | "yargs": "13.2.2"
34 | },
35 | "devDependencies": {
36 | "flow-bin": "0.94.0",
37 | "flow-typed": "2.5.1"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/lib/src/plugins/develop-build/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { Package, DevelopPlugin } from '../../types'
4 |
5 | const PackageUtils = require('../../packages')
6 | const { PackageError } = require('../../errors')
7 |
8 | const developBuildPlugin: DevelopPlugin = {
9 | name: 'plugin-develop-build',
10 | clean: (pkg: Package) => {
11 | throw new PackageError(pkg, '"clean" not supported by "build" plugin')
12 | },
13 | build: (pkg: Package) => {
14 | throw new PackageError(pkg, '"build" not supported by "build" plugin')
15 | },
16 | develop: (pkg: Package) =>
17 | PackageUtils.buildPackage(pkg)
18 | // we ensure that nothing is returned as we won't be resolving a
19 | // develop instance with kill cmd etc
20 | .then(() => ({ kill: () => Promise.resolve(undefined) })),
21 | deploy: (pkg: Package) => {
22 | throw new PackageError(pkg, '"deploy" not supported by "build" plugin')
23 | },
24 | }
25 |
26 | module.exports = developBuildPlugin
27 |
--------------------------------------------------------------------------------
/packages/lib/src/plugins/clean-build/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { Package, CleanPlugin } from '../../types'
4 |
5 | const rimraf = require('rimraf')
6 | const pify = require('pify')
7 | const PackageUtils = require('../../packages')
8 | const { PackageError } = require('../../errors')
9 |
10 | const rimrafAsync = pify(rimraf)
11 |
12 | const cleanBuildPlugin: CleanPlugin = {
13 | name: 'plugin-clean-build',
14 | clean: (pkg: Package) => rimrafAsync(pkg.paths.packageBuildOutput),
15 | build: (pkg: Package) => {
16 | throw new PackageError(pkg, '"build" not supported by "build" plugin')
17 | },
18 | develop: (pkg: Package) =>
19 | PackageUtils.buildPackage(pkg)
20 | // we ensure that nothing is returned as we won't be resolving a
21 | // develop instance with kill cmd etc
22 | .then(() => ({ kill: () => Promise.resolve(undefined) })),
23 | deploy: (pkg: Package) => {
24 | throw new PackageError(pkg, '"deploy" not supported by "build" plugin')
25 | },
26 | }
27 |
28 | module.exports = cleanBuildPlugin
29 |
--------------------------------------------------------------------------------
/packages/lib/src/plugins/develop-server/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { Package, DevelopPlugin } from '../../types'
4 |
5 | const develop = require('./develop')
6 | const { PackageError } = require('../../errors')
7 |
8 | const developServerPlugin: DevelopPlugin = {
9 | name: 'plugin-develop-server',
10 | build: (pkg: Package) => {
11 | throw new PackageError(pkg, '"build" not supported by "server" plugin')
12 | },
13 | clean: (pkg: Package) => {
14 | throw new PackageError(pkg, '"clean" not supported by "server" plugin')
15 | },
16 | develop: (pkg: Package) => {
17 | if (!pkg.packageJson.main) {
18 | throw new PackageError(
19 | pkg,
20 | `You must provide a "main" within your package.json when using the "server" develop plugin. See the configuration for ${
21 | pkg.name
22 | }`,
23 | )
24 | }
25 | return develop(pkg)
26 | },
27 | deploy: (pkg: Package) => {
28 | throw new PackageError(pkg, '"deploy" not supported by "server" plugin')
29 | },
30 | }
31 |
32 | module.exports = developServerPlugin
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Sean Matheson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/lib/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": false,
3 | "publishConfig": {
4 | "access": "public"
5 | },
6 | "version": "0.5.5",
7 | "name": "@lerna-cola/lib",
8 | "description": "The lib for lerna-cola packages.",
9 | "repository": "https://github.com/ctrlplusb/lerna-cola",
10 | "license": "MIT",
11 | "bugs": {
12 | "url": "https://github.com/ctrlplusb/lerna-cola/issues"
13 | },
14 | "main": "build/index.js",
15 | "files": [
16 | "build"
17 | ],
18 | "dependencies": {
19 | "chalk": "2.4.2",
20 | "dedent": "0.7.0",
21 | "deepmerge": "3.2.0",
22 | "execa": "1.0.0",
23 | "figures": "2.0.0",
24 | "fs-extra": "7.0.1",
25 | "globby": "9.1.0",
26 | "inquirer": "6.2.2",
27 | "load-json-file": "5.2.0",
28 | "p-map": "^2.0.0",
29 | "p-series": "2.0.0",
30 | "pify": "^4.0.1",
31 | "pretty-format": "^24.5.0",
32 | "ramda": "0.26.1",
33 | "rimraf": "^2.6.3",
34 | "semver": "5.6.0",
35 | "toposort": "2.0.2",
36 | "write-json-file": "3.1.0"
37 | },
38 | "devDependencies": {
39 | "flow-bin": "0.94.0",
40 | "flow-typed": "2.5.1"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/packages/lib/src/strings.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { Chalk } from 'chalk'
4 | import type { Package } from './types'
5 |
6 | const { EOL } = require('os')
7 | const R = require('ramda')
8 | const chalk = require('chalk')
9 | const { removeNil, removeEmpty } = require('./arrays')
10 | const config = require('./config')
11 |
12 | const multiLineStringToArray = (str: string): Array =>
13 | R.pipe(
14 | R.defaultTo(''),
15 | x => x.split(EOL),
16 | removeNil,
17 | removeEmpty,
18 | )(str)
19 |
20 | const prefixedMsg = (color: Chalk, prefix: string, msg: string): string => {
21 | const formattedPrefix = `${color(
22 | `${prefix.padEnd(config().terminalLabelMinLength + 1)}`,
23 | )}|`
24 |
25 | return `${formattedPrefix} ${(msg || '')
26 | .toString()
27 | .replace(/\n/gi, `\n${formattedPrefix} `)}`
28 | }
29 |
30 | const lernaColaMsg = (msg: string): string =>
31 | prefixedMsg(chalk.bgBlack.gray, 'lerna-cola', msg)
32 |
33 | const packageMsg = (pkg: Package, msg: string): string =>
34 | prefixedMsg(pkg.color, pkg.name, msg)
35 |
36 | module.exports = {
37 | lernaColaMsg,
38 | multiLineStringToArray,
39 | packageMsg,
40 | }
41 |
--------------------------------------------------------------------------------
/packages/lib/src/plugins/utils.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { ExecaChildProcess } from 'execa'
4 | import type { Package } from '../types'
5 |
6 | const TerminalUtils = require('../terminal')
7 |
8 | function killChildProcess(
9 | pkg: Package,
10 | childProcess: ExecaChildProcess,
11 | ): Promise {
12 | TerminalUtils.verbosePkg(pkg, `Killing child process...`)
13 |
14 | return new Promise(resolve => {
15 | let killed = false
16 |
17 | childProcess.on('close', () => {
18 | killed = true
19 | })
20 |
21 | childProcess.catch(err => {
22 | TerminalUtils.verbosePkg(pkg, `Process killed with errors`)
23 | TerminalUtils.verbosePkg(pkg, err)
24 | resolve()
25 | })
26 |
27 | const checkInterval = setInterval(() => {
28 | TerminalUtils.verbosePkg(pkg, `Checking if killed`)
29 | if (killed) {
30 | TerminalUtils.verbosePkg(pkg, `Process killed`)
31 | clearInterval(checkInterval)
32 | resolve()
33 | }
34 | }, 1000)
35 |
36 | childProcess.kill('SIGTERM')
37 | }).catch(err => {
38 | TerminalUtils.verbosePkg(pkg, `Fatal error whilst killing process`)
39 | throw err
40 | })
41 | }
42 |
43 | module.exports = { killChildProcess }
44 |
--------------------------------------------------------------------------------
/packages/babel-config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": false,
3 | "publishConfig": {
4 | "access": "public"
5 | },
6 | "version": "0.3.5",
7 | "name": "@lerna-cola/babel-config",
8 | "description": "A Babel config for the Babel build plugin for lerna-cola projects.",
9 | "repository": "https://github.com/ctrlplusb/lerna-cola",
10 | "main": "build/index.js",
11 | "files": [
12 | "build"
13 | ],
14 | "dependencies": {
15 | "@lerna-cola/lib": "^0.5.5",
16 | "babel-plugin-dynamic-import-node": "2.2.0",
17 | "babel-plugin-inject-source-map-init": "^0.26.9",
18 | "babel-plugin-syntax-dynamic-import": "6.18.0",
19 | "babel-plugin-syntax-trailing-function-commas": "6.22.0",
20 | "babel-plugin-transform-class-properties": "6.24.1",
21 | "babel-plugin-transform-object-rest-spread": "6.26.0",
22 | "babel-plugin-transform-react-constant-elements": "6.23.0",
23 | "babel-plugin-transform-react-inline-elements": "6.22.0",
24 | "babel-plugin-transform-react-jsx-self": "6.22.0",
25 | "babel-plugin-transform-react-jsx-source": "6.22.0",
26 | "babel-plugin-transform-react-remove-prop-types": "0.4.24",
27 | "babel-plugin-transform-runtime": "6.23.0",
28 | "babel-preset-env": "1.7.0",
29 | "babel-preset-react": "6.24.1",
30 | "semver": "^5.6.0"
31 | },
32 | "devDependencies": {
33 | "flow-bin": "^0.94.0"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/lib/src/packages/build-package.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { Package } from '../types'
4 |
5 | const TerminalUtils = require('../terminal')
6 | const config = require('../config')
7 |
8 | type Options = {
9 | quiet?: boolean,
10 | }
11 |
12 | const defaultOptions = { quiet: false }
13 |
14 | const executeBuild = pkg => {
15 | if (pkg.plugins.buildPlugin) {
16 | TerminalUtils.verbosePkg(
17 | pkg,
18 | `Building using build plugin: ${pkg.plugins.buildPlugin.plugin.name}`,
19 | )
20 | }
21 | return pkg.plugins.buildPlugin
22 | ? pkg.plugins.buildPlugin.plugin.build(
23 | pkg,
24 | pkg.plugins.buildPlugin.options,
25 | { config: config() },
26 | )
27 | : Promise.resolve()
28 | }
29 |
30 | /**
31 | * Builds the given package.
32 | *
33 | * @param {*} pkg
34 | * The package to be built.
35 | * @param {*} options.quiet
36 | * If set then the logs will only be displayed when the VERBOSE flag is enabled.
37 | */
38 | async function buildPackage(
39 | pkg: Package,
40 | options: ?Options = {},
41 | ): Promise {
42 | const { quiet } = Object.assign({}, defaultOptions, options)
43 | TerminalUtils[quiet ? 'verbosePkg' : 'infoPkg'](pkg, `Building...`)
44 |
45 | try {
46 | await executeBuild(pkg)
47 | TerminalUtils.verbosePkg(pkg, `Built`)
48 | } catch (err) {
49 | TerminalUtils.errorPkg(pkg, `Build failed`)
50 | throw err
51 | }
52 | }
53 |
54 | module.exports = buildPackage
55 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/develop.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | const { TerminalUtils } = require('@lerna-cola/lib')
4 | const developmentService = require('../development-service')
5 | const asyncCommand = require('../lib/async-command')
6 |
7 | module.exports = {
8 | command: 'develop',
9 | desc: 'Starts the coordinated development service',
10 | builder: yargs =>
11 | yargs
12 | .option('packages', {
13 | alias: 'p',
14 | describe: 'The packages to develop',
15 | type: 'array',
16 | })
17 | .option('select', {
18 | alias: 's',
19 | describe: 'Enable selection of packages to develop',
20 | type: 'boolean',
21 | })
22 | .option('exact', {
23 | alias: 'e',
24 | describe:
25 | 'Useful when selecting/filtering packages. When enabled only the selected/filtered packages will be tracked.',
26 | type: 'boolean',
27 | }),
28 | handler: asyncCommand(async argv => {
29 | try {
30 | TerminalUtils.title('Starting development service...')
31 | if (!process.env.NODE_ENV) {
32 | process.env.NODE_ENV = 'development'
33 | }
34 | await developmentService({
35 | packagesFilter: argv.packages,
36 | selectPackages: argv.select,
37 | exact: argv.exact,
38 | })
39 | TerminalUtils.success('Done')
40 | } catch (ex) {
41 | TerminalUtils.error('Unhandled exception in development service', ex)
42 | }
43 | }),
44 | }
45 |
--------------------------------------------------------------------------------
/packages/cli/src/bin/lerna-cola.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | // @flow
4 | /* eslint-disable no-console */
5 |
6 | const yargs = require('yargs')
7 | const { TerminalUtils } = require('@lerna-cola/lib')
8 | const buildCommand = require('../commands/build')
9 | const cleanCommand = require('../commands/clean')
10 | const developCommand = require('../commands/develop')
11 | const deployCommand = require('../commands/deploy')
12 | const preventScriptExit = require('../lib/prevent-script-exit')
13 | const handleError = require('../lib/handle-error')
14 |
15 | const onComplete = output => {
16 | if (output) {
17 | console.log(output)
18 | }
19 | process.exit(0)
20 | }
21 |
22 | const args = process.argv.slice(2)
23 |
24 | yargs
25 | .command(buildCommand)
26 | .command(cleanCommand)
27 | .command(developCommand)
28 | .command(deployCommand)
29 | .demandCommand()
30 | .help('h')
31 | .alias('h', 'help')
32 |
33 | if (args.length > 0) {
34 | yargs.parse(args, (err, argv, output) => {
35 | TerminalUtils.verbose(argv)
36 | if (argv.promisedResult) {
37 | TerminalUtils.verbose('Waiting for async command to complete...')
38 | argv.promisedResult.catch(handleError).then(onComplete)
39 | } else {
40 | if (err) {
41 | handleError(err)
42 | }
43 | onComplete(output)
44 | }
45 | })
46 | } else {
47 | yargs.parse()
48 | }
49 |
50 | process.on('uncaughtException', handleError)
51 | process.on('unhandledRejection', handleError)
52 |
53 | preventScriptExit()
54 |
--------------------------------------------------------------------------------
/tasks.todo:
--------------------------------------------------------------------------------
1 | Bugs:
2 | ☐ If no command/arg is provided to the lerna-cola cli an error occurs. Should print help.
3 |
4 | Docs:
5 | ☐ Intro
6 | ☐ Video overview
7 | ☐ Blog post
8 | ☐ lerna-cola cli
9 | ☐ Tips and Tricks
10 | ☐ SIGTERM/SIGINT effective usage
11 | ☐ Usage of process.stdin.read() to stop process exiting (even though it has child processes)
12 |
13 | Plugins:
14 | ☐ reasonml
15 | ☐ typescript
16 | ☐ rollup
17 | ☐ parceljs
18 |
19 | Features:
20 | ☐ When deploying check that dependencies have previously been published
21 | ☐ Pre/Post command hooks missing on: clean, build, and deploy commands.
22 | ☐ Better help on lerna-cola bin
23 | ☐ Command hooks + error handling of them!
24 | ✔ predevelop @done (2018-3-2 15:15:11)
25 | ✔ postdevelop @done (2018-3-2 15:15:11)
26 | ✔ pretest @done (2018-3-2 15:15:10)
27 | ✔ posttest @done (2018-3-2 15:15:10)
28 | ☐ Finish all inline TODO comments within code.
29 | ☐ Validate the lerna-cola configuration during runtime (tcomb?)
30 | ☐ Chokidar events should result in the absolute minimal amount of work being done. e.g. transpile a single file. remove a dir, etc
31 | ☐ auto-add source-map-support to build dependencies, and then re-enable for production builds.
32 | ☐ plugin interface for compilers - pre/post compile
33 | ☐ plugin interface for compilers - pre/post develop
34 | ☐ Manual reload override for development mode (like nodemon rs).
35 | ☐ Allow for explicit ignoring of a dependency when doing hot development reloads.
36 |
--------------------------------------------------------------------------------
/packages/lib/src/fs.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | const TerminalUtils = require('./terminal')
4 | const path = require('path')
5 | const fs = require('fs-extra')
6 |
7 | /**
8 | * Resolves a package with the given name.
9 | *
10 | * i.e. returns the equivalent of require('foo')
11 | */
12 | function resolvePackage(resolvedPackageName: string): mixed {
13 | const packagePath = require.resolve(resolvedPackageName)
14 | TerminalUtils.verbose(`Trying to resolve package ${packagePath}`)
15 | let resolvedPackage
16 | try {
17 | // eslint-disable-next-line global-require,import/no-dynamic-require
18 | resolvedPackage = require(packagePath)
19 | } catch (err) {
20 | TerminalUtils.verbose(`Failed to resolve package ${packagePath}`)
21 | TerminalUtils.verbose(err)
22 | TerminalUtils.verbose(
23 | `Trying to resolve package ${packagePath} as a symlink`,
24 | )
25 | // EEK! Could be a symlink?
26 | try {
27 | fs.lstatSync(packagePath)
28 | const symLinkPath = fs.readlinkSync(path)
29 | // eslint-disable-next-line global-require,import/no-dynamic-require
30 | resolvedPackage = require(symLinkPath)
31 | } catch (symErr) {
32 | // DO nothing
33 | TerminalUtils.verbose(
34 | `Failed to resolve package ${packagePath} as a symlink`,
35 | )
36 | TerminalUtils.verbose(symErr)
37 | }
38 | }
39 |
40 | TerminalUtils.verbose(`Resolved package ${packagePath}`)
41 |
42 | return resolvedPackage
43 | }
44 |
45 | module.exports = {
46 | resolvePackage,
47 | }
48 |
--------------------------------------------------------------------------------
/packages/cli/src/development-service/create-package-conductor.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type {
4 | Package,
5 | PackageWatcher,
6 | PackageConductor,
7 | } from '@lerna-cola/lib/build/types'
8 |
9 | const { config, TerminalUtils } = require('@lerna-cola/lib')
10 |
11 | const noPluginResult = {
12 | kill: () => Promise.resolve(),
13 | }
14 |
15 | module.exports = function createPackageConductor(
16 | pkg: Package,
17 | watcher: PackageWatcher,
18 | ): PackageConductor {
19 | let runningDevelopInstance
20 |
21 | return {
22 | run: (runType, changedDependency) => {
23 | const developPlugin = pkg.plugins.developPlugin
24 | if (!developPlugin) {
25 | TerminalUtils.verbosePkg(
26 | pkg,
27 | `No develop plugin, skipping develop execution/conductor`,
28 | )
29 | return Promise.resolve(noPluginResult)
30 | }
31 |
32 | TerminalUtils.verbosePkg(
33 | pkg,
34 | `Running develop plugin for change type: ${runType}`,
35 | )
36 |
37 | return developPlugin.plugin
38 | .develop(pkg, developPlugin.options, {
39 | config: config(),
40 | watcher,
41 | runType,
42 | changedDependency,
43 | })
44 | .then(developInstance => {
45 | runningDevelopInstance = developInstance
46 | return developInstance
47 | })
48 | },
49 | stop: () =>
50 | runningDevelopInstance
51 | ? runningDevelopInstance.kill()
52 | : Promise.resolve(),
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/packages/lib/src/config/get-package-roots.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | /**
4 | * Lifted from Lerna ❤️
5 | */
6 |
7 | const globby = require('globby')
8 | const path = require('path')
9 |
10 | module.exports = function resolvePackageRoots(
11 | packageSources: Array = ['packages/*'],
12 | ): Array {
13 | const globOpts = {
14 | cwd: process.cwd(),
15 | absolute: true,
16 | followSymlinkedDirectories: false,
17 | ignore: [],
18 | }
19 |
20 | const hasNodeModules = packageSources.some(
21 | cfg => cfg.indexOf('node_modules') > -1,
22 | )
23 | const hasGlobStar = packageSources.some(cfg => cfg.indexOf('**') > -1)
24 |
25 | if (hasGlobStar) {
26 | if (hasNodeModules) {
27 | throw new Error(
28 | 'An explicit node_modules package path does not allow globstars (**)',
29 | )
30 | }
31 |
32 | globOpts.ignore.push(
33 | // allow globs like "packages/**",
34 | // but avoid picking up node_modules/**/package.json
35 | '**/node_modules/**',
36 | )
37 | }
38 |
39 | return globby
40 | .sync(
41 | packageSources.map(
42 | globPath => path.join(process.cwd(), globPath, 'package.json'),
43 | globOpts,
44 | ),
45 | )
46 | .map(globResult => {
47 | // https://github.com/isaacs/node-glob/blob/master/common.js#L104
48 | // glob always returns "\\" as "/" in windows, so everyone
49 | // gets normalized because we can't have nice things.
50 | const packageConfigPath = path.normalize(globResult)
51 | const packageDir = path.dirname(packageConfigPath)
52 | return packageDir
53 | })
54 | }
55 |
--------------------------------------------------------------------------------
/packages/cli/src/development-service/create-package-watcher.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { Package, PackageWatcher } from '@lerna-cola/lib/build/types'
4 |
5 | const chokidar = require('chokidar')
6 | const { TerminalUtils } = require('@lerna-cola/lib')
7 |
8 | const disabledPackageWatchingResult = {
9 | start: () => undefined,
10 | stop: () => undefined,
11 | }
12 |
13 | module.exports = function createPackageWatcher(
14 | onChange: Function,
15 | pkg: Package,
16 | ): PackageWatcher {
17 | TerminalUtils.verbosePkg(pkg, `Creating development source watcher...`)
18 |
19 | if (pkg.disableSrcWatching) {
20 | TerminalUtils.verbosePkg(
21 | pkg,
22 | `Did not create source watcher as it was disable via package config`,
23 | )
24 | return disabledPackageWatchingResult
25 | }
26 |
27 | const createWatcher = () => {
28 | const watcher = chokidar.watch(
29 | [pkg.paths.packageSrc, pkg.paths.packageJson],
30 | {
31 | ignored: pkg.paths.packageBuildOutput
32 | ? pkg.paths.packageBuildOutput
33 | : undefined,
34 | ignoreInitial: true,
35 | cwd: pkg.paths.packageRoot,
36 | ignorePermissionErrors: true,
37 | },
38 | )
39 | watcher
40 | .on('add', onChange)
41 | .on('change', onChange)
42 | .on('unlink', onChange)
43 | .on('addDir', onChange)
44 | .on('unlinkDir', onChange)
45 | return watcher
46 | }
47 |
48 | let watcher = null
49 |
50 | return {
51 | start: () => {
52 | if (!watcher) {
53 | watcher = createWatcher()
54 | }
55 | },
56 | stop: () => {
57 | if (watcher) {
58 | watcher.close()
59 | watcher = null
60 | }
61 | },
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/packages/utils/src/get-web-package-manifest.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 |
3 | const path = require('path')
4 | const fs = require('fs-extra')
5 |
6 | const requireFn =
7 | typeof __non_webpack_require__ !== 'undefined'
8 | ? // eslint-disable-next-line no-undef
9 | __non_webpack_require__
10 | : require
11 |
12 | const cache = {}
13 |
14 | /**
15 | * Gets the manifest for the lerna-cola web package by the given name.
16 | *
17 | * @param {string} packageName The package's name
18 | * @return {Object} The manifest
19 | */
20 | module.exports = function getWebPackageManifest(
21 | packageName,
22 | basePath = './dist',
23 | ) {
24 | if (cache[packageName]) {
25 | return cache[packageName]
26 | }
27 | let distPath = path.resolve(
28 | process.cwd(),
29 | `./node_modules/${packageName}/${basePath}`,
30 | )
31 | let manifestFile = path.resolve(distPath, `./webpack-manifest.json`)
32 | if (!fs.existsSync(manifestFile)) {
33 | // lerna-cola app packageRoot path
34 | distPath = path.resolve(
35 | process.cwd(),
36 | `../../node_modules/${packageName}/${basePath}`,
37 | )
38 | manifestFile = path.resolve(distPath, `./webpack-manifest.json`)
39 | if (!fs.existsSync(manifestFile)) {
40 | throw new Error(`No manifest found at ${manifestFile}`)
41 | }
42 | }
43 | // eslint-disable-next-line global-require,import/no-dynamic-require
44 | const manifest = requireFn(manifestFile)
45 | if (!manifest.index) {
46 | throw new Error(
47 | `Invalid lerna-cola web package manifest found at ${manifestFile}`,
48 | )
49 | }
50 |
51 | const jsParts = manifest.index.js
52 | .substr(manifest.index.js.indexOf('/lerna-cola/'))
53 | .split('/')
54 | const rootHttpPath = jsParts.slice(0, jsParts.length - 1).join('/')
55 |
56 | cache[packageName] = {
57 | serverPaths: {
58 | packageRoot: distPath,
59 | },
60 | httpPaths: {
61 | packageRoot: rootHttpPath,
62 | js: manifest.index.js,
63 | css: manifest.index.css,
64 | },
65 | manifest,
66 | }
67 |
68 | return cache[packageName]
69 | }
70 |
--------------------------------------------------------------------------------
/packages/utils/src/configure-graceful-exit.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | const deepMerge = require('deepmerge')
4 |
5 | const defaultConfig = {
6 | timeout: 10 * 1000,
7 | messages: {
8 | start: 'Received termination request. Attempting gracefully exit...',
9 | error: 'An error occurred whilst attempting to gracefully exit.',
10 | timeout:
11 | 'Graceful shutdown period timeout lapsed, forcefully shutting down.',
12 | exit: 'Exiting.',
13 | },
14 | catchUnhandled: true,
15 | }
16 |
17 | module.exports = function configureGracefulExit(config = {}) {
18 | const { onExit, messages, timeout, name, catchUnhandled } = deepMerge(
19 | defaultConfig,
20 | config,
21 | )
22 |
23 | if (typeof onExit !== 'function') {
24 | console.error(
25 | 'You must provide an onExit "function" to configureGracefulExit',
26 | )
27 | }
28 |
29 | const frmtMsg = msg => `[${name || process.pid}] ${msg}`
30 |
31 | let exiting = false
32 |
33 | const handleExit = exitCode => {
34 | if (exiting) {
35 | return
36 | }
37 | exiting = true
38 |
39 | const exit = code => {
40 | console.log(frmtMsg(messages.end))
41 | process.exit(code)
42 | }
43 |
44 | try {
45 | console.log(frmtMsg(messages.start))
46 |
47 | Promise.resolve(onExit)
48 | .then(() => process.exit(exitCode))
49 | .catch(err => {
50 | console.error(frmtMsg(messages.error))
51 | console.error(err)
52 | exit(1)
53 | })
54 |
55 | setTimeout(() => {
56 | console.error(frmtMsg(messages.timeout))
57 | exit(1)
58 | }, timeout)
59 | } catch (err) {
60 | console.error(frmtMsg(messages.error))
61 | console.error(err)
62 | exit(1)
63 | }
64 | }
65 |
66 | // Respond to any termination requests
67 | ;['SIGTERM', 'SIGINT'].forEach(signal => {
68 | process.on(signal, () => handleExit(0))
69 | })
70 |
71 | // Catch any unhandled promise rejections
72 | if (catchUnhandled) {
73 | process.on('unhandledRejection', err => {
74 | console.error('An unhandled rejection occurred', err)
75 | handleExit(1)
76 | })
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "workspaces": [
4 | "packages/*"
5 | ],
6 | "scripts": {
7 | "bootstrap": "yarn install && yarn build && yarn flow-types",
8 | "build": "node ./scripts/build.js",
9 | "clean": "node ./scripts/clean.js",
10 | "flow-types": "rimraf projects/*/flow-typed && flow-mono create-symlinks flow/.flowconfig && flow-mono install-types",
11 | "publish": "yarn run build && lerna publish"
12 | },
13 | "devDependencies": {
14 | "babel-core": "^6.26.3",
15 | "babel-eslint": "10.0.1",
16 | "eslint": "5.15.1",
17 | "eslint-config-airbnb": "17.1.0",
18 | "eslint-config-prettier": "4.1.0",
19 | "eslint-plugin-import": "2.16.0",
20 | "eslint-plugin-jsx-a11y": "6.2.1",
21 | "eslint-plugin-react": "7.12.4",
22 | "flow-mono-cli": "1.4.3",
23 | "flow-remove-types": "^1.2.3",
24 | "fs-extra": "^7.0.1",
25 | "globby": "^9.1.0",
26 | "lerna": "^3.13.1",
27 | "p-limit": "^2.2.0",
28 | "ramda": "^0.26.1",
29 | "rimraf": "^2.6.3"
30 | },
31 | "eslintConfig": {
32 | "root": true,
33 | "parser": "babel-eslint",
34 | "extends": [
35 | "eslint-config-airbnb",
36 | "eslint-config-prettier"
37 | ],
38 | "env": {
39 | "browser": true,
40 | "es6": true,
41 | "node": true,
42 | "jest": true
43 | },
44 | "ecmaFeatures": {
45 | "defaultParams": true
46 | },
47 | "rules": {
48 | "array-callback-return": 0,
49 | "arrow-parens": [
50 | "error",
51 | "as-needed"
52 | ],
53 | "camelcase": 0,
54 | "import/no-extraneous-dependencies": 0,
55 | "import/prefer-default-export": 0,
56 | "no-nested-ternary": 0,
57 | "no-underscore-dangle": 0,
58 | "prefer-arrow-callback": 0,
59 | "prefer-destructuring": 0,
60 | "react/forbid-prop-types": 0,
61 | "react/jsx-filename-extension": 0,
62 | "react/no-array-index-key": 0,
63 | "react/no-did-mount-set-state": 0,
64 | "react/no-find-dom-node": 0,
65 | "react/sort-comp": 0
66 | }
67 | },
68 | "prettier": {
69 | "semi": false,
70 | "singleQuote": true,
71 | "trailingComma": "all"
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/build.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | const { TerminalUtils, PackageUtils } = require('@lerna-cola/lib')
4 | const pSeries = require('p-series')
5 | const asyncCommand = require('../lib/async-command')
6 |
7 | module.exports = {
8 | command: 'build',
9 | desc: 'Executes the configured build plugin for each package',
10 | builder: yargs =>
11 | yargs
12 | .option('packages', {
13 | alias: 'p',
14 | describe: 'The packages to build',
15 | type: 'array',
16 | })
17 | .option('select', {
18 | alias: 's',
19 | describe: 'Enable selection of packages to build',
20 | type: 'boolean',
21 | })
22 | .option('exact', {
23 | alias: 'e',
24 | describe:
25 | 'Useful when selecting/filtering packages. When enabled only the specifically selected/filtered packages will be built.',
26 | type: 'boolean',
27 | }),
28 | handler: asyncCommand(async argv => {
29 | try {
30 | if (!process.env.NODE_ENV) {
31 | process.env.NODE_ENV = 'production'
32 | }
33 | TerminalUtils.title('Running build command...')
34 | const queueBuild = pkg => () => PackageUtils.buildPackage(pkg)
35 |
36 | let packages = PackageUtils.resolvePackages(argv.packages || [], {
37 | strict: argv.exact,
38 | })
39 |
40 | if (argv.select) {
41 | // Ask which packages to develop if the select option was enabled
42 | const selectedPackages = await TerminalUtils.multiSelect(
43 | 'Which packages would you like to develop?',
44 | {
45 | choices: packages.map(x => ({
46 | value: x.name,
47 | text: `${x.name} (${x.version})`,
48 | })),
49 | },
50 | )
51 | packages = PackageUtils.resolvePackages(selectedPackages, {
52 | strict: argv.exact,
53 | })
54 | }
55 |
56 | TerminalUtils.verbose(`Building packages:`)
57 | TerminalUtils.verbose(packages.map(x => x.name))
58 |
59 | await pSeries(packages.map(queueBuild))
60 | TerminalUtils.success('Done')
61 | } catch (ex) {
62 | TerminalUtils.error('Build failed', ex)
63 | }
64 | }),
65 | }
66 |
--------------------------------------------------------------------------------
/packages/cli/src/deployment-service/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { Package } from '@lerna-cola/lib/build/types'
4 |
5 | const pSeries = require('p-series')
6 | const { config, TerminalUtils, PackageUtils } = require('@lerna-cola/lib')
7 |
8 | module.exports = async function deploymentService() {
9 | // Determine which packages have a deployment plugin configured
10 | const packagesWithDeployConfig = config().packages.filter(
11 | pkg => !!pkg.plugins.deployPlugin,
12 | )
13 | if (packagesWithDeployConfig.length === 0) {
14 | throw new Error(
15 | 'You do not have any packages with a deploy configuration. Exiting...',
16 | )
17 | }
18 |
19 | // Ask which packages to deploy?
20 | const namesOfPackagesToDeploy = await TerminalUtils.multiSelect(
21 | 'Which packages would you like to deploy?',
22 | {
23 | choices: packagesWithDeployConfig.map(x => ({
24 | value: x.name,
25 | text: `${x.name} (${x.version})`,
26 | })),
27 | },
28 | )
29 |
30 | // Ensure at least one package was selected for deploymnet
31 | if (namesOfPackagesToDeploy.length === 0) {
32 | throw new Error('No packages selected. Exiting...')
33 | }
34 |
35 | // Map the package names to packages
36 | const packagesToDeploy = namesOfPackagesToDeploy.map(
37 | x => config().packageMap[x],
38 | )
39 |
40 | TerminalUtils.info('Building packages...')
41 |
42 | // Get full package tree so we know which related packages need to be built.
43 | const packagesWithDependencies = PackageUtils.resolvePackages(
44 | namesOfPackagesToDeploy,
45 | )
46 |
47 | // First we need to make sure we have built all packages
48 | await pSeries(
49 | packagesWithDependencies.map(pkg => () => PackageUtils.buildPackage(pkg)),
50 | )
51 |
52 | TerminalUtils.info('Deploying packages...')
53 |
54 | // Deploy each of the packages
55 | await pSeries(
56 | packagesToDeploy.map((pkg: Package) => async () => {
57 | const deployPlugin = pkg.plugins.deployPlugin
58 | if (!deployPlugin) {
59 | return
60 | }
61 |
62 | await deployPlugin.plugin.deploy(pkg, deployPlugin.options, {
63 | config: config(),
64 | })
65 | }),
66 | )
67 |
68 | TerminalUtils.success('Deployments complete')
69 | }
70 |
--------------------------------------------------------------------------------
/packages/lib/src/packages/resolve-packages.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { Package } from '../types'
4 |
5 | const R = require('ramda')
6 | const config = require('../config')
7 | const TerminalUtils = require('../terminal')
8 |
9 | type Options = {
10 | strict?: boolean,
11 | }
12 |
13 | type DefaultedOptions = {
14 | strict: boolean,
15 | }
16 |
17 | const defaultOptions = {
18 | strict: false,
19 | }
20 |
21 | /**
22 | * Filters the packages down to the given.
23 | *
24 | * @param packageFilters
25 | * The names of the packages to resolve. If none is specified then
26 | * all of them are resolved.
27 | * @param options.strict
28 | * When enabled then only the packages within packageFilters will
29 | * be returned. Otherwise the packages plus their dependencies will
30 | * be resolved.
31 | *
32 | * @return {Promise>} The resolved packages
33 | */
34 | module.exports = function resolvePackages(
35 | packageFilters: ?Array = [],
36 | options: Options = defaultOptions,
37 | ): Array {
38 | const defaultedOptions: DefaultedOptions = {
39 | ...defaultOptions,
40 | ...options,
41 | }
42 |
43 | const packages = config().packages
44 |
45 | if (!packageFilters || packageFilters.length === 0) {
46 | return packages
47 | }
48 |
49 | TerminalUtils.verbose(
50 | `Resolving packages with filter [${packageFilters.join(', ')}]`,
51 | )
52 |
53 | const packageNames = packages.map(x => x.name)
54 | const invalidFilters = R.without(packageNames, packageFilters)
55 | if (invalidFilters.length > 0) {
56 | throw new Error(
57 | `The following packages could not be resolved:\n[${invalidFilters.join(
58 | ',',
59 | )}]`,
60 | )
61 | }
62 |
63 | const targets = new Set()
64 |
65 | packageFilters.forEach(name => {
66 | targets.add(name)
67 | if (!defaultedOptions.strict) {
68 | config().packageMap[name].allDependencies.forEach(x => {
69 | targets.add(x)
70 | })
71 | }
72 | })
73 |
74 | const filteredPackagesNames = [...targets]
75 |
76 | // Let's get a sorted version by filtering allPackages
77 | // which will already be in a safe build order.
78 | const result = packages.filter(
79 | x => !!filteredPackagesNames.find(name => name === x.name),
80 | )
81 |
82 | TerminalUtils.verbose(`Resolved: [${result.map(R.prop('name')).join(', ')}]`)
83 | return result
84 | }
85 |
--------------------------------------------------------------------------------
/packages/lib/src/plugins/resolve-plugin.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | /* eslint-disable global-require */
4 | /* eslint-disable no-console */
5 |
6 | import type {
7 | CleanPlugin,
8 | BuildPlugin,
9 | DevelopPlugin,
10 | DeployPlugin,
11 | } from '../types'
12 |
13 | const R = require('ramda')
14 | const fs = require('fs-extra')
15 | const path = require('path')
16 |
17 | const pluginCache = {}
18 |
19 | const resolvePackage = (packageName: string): mixed => {
20 | const packagePath = require.resolve(packageName)
21 | console.info(`Trying to resolve package ${packagePath}`)
22 | let resolvedPackage
23 | try {
24 | // eslint-disable-next-line global-require,import/no-dynamic-require
25 | resolvedPackage = require(packagePath)
26 | } catch (err) {
27 | console.info(`Failed to resolve package ${packagePath}`)
28 | console.info(err)
29 | console.info(`Trying to resolve package ${packagePath} as a symlink`)
30 | // EEK! Could be a symlink?
31 | try {
32 | fs.lstatSync(packagePath)
33 | const symLinkPath = fs.readlinkSync(path)
34 | // eslint-disable-next-line global-require,import/no-dynamic-require
35 | resolvedPackage = require(symLinkPath)
36 | } catch (symErr) {
37 | // DO nothing
38 | console.info(`Failed to resolve package ${packagePath} as a symlink`)
39 | console.info(symErr)
40 | }
41 | }
42 |
43 | console.info(`Resolved package ${packagePath}`)
44 |
45 | return resolvedPackage
46 | }
47 |
48 | module.exports = (
49 | pluginName: string,
50 | ): CleanPlugin | BuildPlugin | DevelopPlugin | DeployPlugin => {
51 | if (R.isEmpty(pluginName) || R.isNil(pluginName)) {
52 | throw new Error('No plugin name was given to resolvePlugin')
53 | }
54 |
55 | // Core plugins
56 | switch (pluginName) {
57 | case 'plugin-clean-build':
58 | return require('./clean-build')
59 | case 'plugin-develop-build':
60 | return require('./develop-build')
61 | case 'plugin-develop-server':
62 | return require('./develop-server')
63 | case 'plugin-script':
64 | return require('./script')
65 | default:
66 | // Do nothing, fall through and resolve custom plugin...
67 | }
68 |
69 | if (pluginCache[pluginName]) {
70 | return pluginCache[pluginName]
71 | }
72 |
73 | const packagePlugin = resolvePackage(pluginName)
74 |
75 | if (!packagePlugin) {
76 | throw new Error(
77 | `Could not resolve "${pluginName}" plugin. Make sure you have the plugin installed.`,
78 | )
79 | }
80 |
81 | pluginCache[pluginName] = packagePlugin
82 |
83 | // $FlowFixMe
84 | return packagePlugin
85 | }
86 |
--------------------------------------------------------------------------------
/packages/lib/src/childProcess.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { ExecaChildProcess } from 'execa'
4 | import type { Package } from './types'
5 |
6 | const execa = require('execa')
7 | const TerminalUtils = require('./terminal')
8 | const StringUtils = require('./strings')
9 |
10 | function exec(
11 | command: string,
12 | args?: Array = [],
13 | opts?: Object = {},
14 | ): ExecaChildProcess {
15 | TerminalUtils.verbose(
16 | `exec child process: ${command} ${args.join(' ')}${
17 | opts.cwd ? ` (${opts.cwd})` : ''
18 | }`,
19 | )
20 |
21 | process.env.FORCE_COLOR = 'true'
22 |
23 | return execa(
24 | command,
25 | args,
26 | Object.assign(
27 | {},
28 | {
29 | env: process.env,
30 | stdio: 'pipe',
31 | },
32 | opts,
33 | ),
34 | ).then(result => result.stdout)
35 | }
36 |
37 | function execPkg(
38 | pkg: Package,
39 | command: string,
40 | args?: Array = [],
41 | opts?: Object = {},
42 | ): ExecaChildProcess {
43 | process.env.FORCE_COLOR = 'true'
44 |
45 | const childProcess = execa(
46 | command,
47 | args,
48 | Object.assign({}, opts, {
49 | env: process.env,
50 | }),
51 | )
52 |
53 | childProcess.stdout.on('data', data => {
54 | // eslint-disable-next-line no-console
55 | console.log(StringUtils.packageMsg(pkg, data))
56 | })
57 |
58 | childProcess.stderr.on('data', data => {
59 | // eslint-disable-next-line no-console
60 | console.error(StringUtils.packageMsg(pkg, data))
61 | })
62 |
63 | return childProcess
64 | }
65 |
66 | function execSync(
67 | command: string,
68 | args?: Array = [],
69 | opts?: Object = {},
70 | ): string {
71 | process.env.FORCE_COLOR = 'true'
72 |
73 | TerminalUtils.verbose(
74 | `execSync child process: ${command} ${args.join(' ')}${
75 | opts.cwd ? ` (${opts.cwd})` : ''
76 | }`,
77 | )
78 |
79 | return execa.sync(
80 | command,
81 | args,
82 | Object.assign(
83 | {},
84 | {
85 | env: process.env,
86 | },
87 | opts,
88 | ),
89 | ).stdout
90 | }
91 |
92 | function execSyncPkg(
93 | pkg: Package,
94 | command: string,
95 | args?: Array = [],
96 | opts?: Object = {},
97 | ): string {
98 | const output = execSync(command, args, opts)
99 | // eslint-disable-next-line no-console
100 | console.log(StringUtils.packageMsg(pkg, output))
101 | return output
102 | }
103 |
104 | module.exports = {
105 | exec,
106 | execPkg,
107 | execSync,
108 | execSyncPkg,
109 | }
110 |
--------------------------------------------------------------------------------
/packages/lib/src/plugins/develop-server/develop.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { Package, DevelopInstance } from '../../types'
4 |
5 | const path = require('path')
6 | const TerminalUtils = require('../../terminal')
7 | const ChildProcessUtils = require('../../childProcess')
8 | const PackageUtils = require('../../packages')
9 | const PluginUtils = require('../utils')
10 |
11 | const childProcessMap = {}
12 |
13 | const killChildProcessFor = (pkg: Package): Promise => {
14 | const childProcess = childProcessMap[pkg.name]
15 | if (!childProcess) {
16 | TerminalUtils.verbose(`No running child process for ${pkg.name} to kill`)
17 | return Promise.resolve()
18 | }
19 |
20 | return PluginUtils.killChildProcess(pkg, childProcess).then(() => {
21 | TerminalUtils.verbose(`${pkg.name} killed successfully`)
22 | if (childProcessMap[pkg.name]) {
23 | delete childProcessMap[pkg.name]
24 | }
25 | })
26 | }
27 |
28 | module.exports = function develop(pkg: Package): Promise {
29 | const startServer = (): Promise =>
30 | new Promise((resolve, reject) => {
31 | const childProcess = ChildProcessUtils.execPkg(
32 | pkg,
33 | // Spawn a node process
34 | 'node',
35 | // That runs the main file
36 | [path.resolve(pkg.paths.packageRoot, pkg.packageJson.main)],
37 | {
38 | cwd: pkg.paths.packageRoot,
39 | },
40 | )
41 |
42 | childProcess.catch(err => {
43 | TerminalUtils.verbosePkg(pkg, `Error starting`)
44 | reject(err)
45 | })
46 |
47 | // Give the catch above a tick of space, so that it can resolve any
48 | // error that may have occurred
49 | process.nextTick(() => {
50 | if (!childProcess.stderr) {
51 | TerminalUtils.verbosePkg(
52 | pkg,
53 | 'Not resolving server as childProcess was not created properly. An error probably occurred.',
54 | )
55 | reject(new Error(`${pkg.name} has problems. Please fix`))
56 | } else {
57 | childProcess.on('close', () => {
58 | TerminalUtils.verbosePkg(pkg, `Server process stopped`)
59 | })
60 |
61 | childProcessMap[pkg.name] = childProcess
62 |
63 | resolve()
64 | }
65 | })
66 | })
67 |
68 | return (
69 | PackageUtils.buildPackage(pkg)
70 | // Ensure any existing childProcess is killed
71 | .then(() => killChildProcessFor(pkg))
72 | // Fire up the new childProcess
73 | .then(startServer)
74 | // Return the dev instance API
75 | .then(() => ({
76 | kill: () => killChildProcessFor(pkg),
77 | }))
78 | )
79 | }
80 |
--------------------------------------------------------------------------------
/packages/plugin-build-flow/src/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type {
4 | Package,
5 | CleanPlugin,
6 | BuildPlugin,
7 | } from '@lerna-cola/lib/build/types'
8 |
9 | const os = require('os')
10 | const path = require('path')
11 | const pLimit = require('p-limit')
12 | const R = require('ramda')
13 | const fs = require('fs-extra')
14 | const globby = require('globby')
15 | const flowRemoveTypes = require('flow-remove-types')
16 | const {
17 | Errors: { PackageError },
18 | } = require('@lerna-cola/lib')
19 |
20 | const maxConcurrentTranspiles = os.cpus().length
21 |
22 | // :: string -> void
23 | const ensureParentDirectoryExists = filePath => {
24 | const dir = path.dirname(filePath)
25 | fs.ensureDirSync(dir)
26 | }
27 |
28 | type PluginOptions = {
29 | inputs?: Array,
30 | }
31 |
32 | const flowBuildPlugin: CleanPlugin & BuildPlugin = {
33 | name: '@lerna-cola/plugin-build-flow',
34 | build: (pkg: Package, options: PluginOptions) => {
35 | const patterns = (
36 | options.inputs || ['**/*.js', '!__tests__', '!test.js']
37 | ).concat(['!node_modules/**/*', `!${pkg.paths.packageBuildOutput}/**/*`])
38 |
39 | // :: string -> Array
40 | const getJsFilePaths = () =>
41 | globby(patterns, {
42 | cwd: pkg.paths.packageSrc,
43 | })
44 |
45 | return getJsFilePaths().then(filePaths => {
46 | const transpileFile = filePath =>
47 | new Promise(resolve => {
48 | const module = path.resolve(pkg.paths.packageSrc, filePath)
49 | const input = fs.readFileSync(module, 'utf8')
50 | const output = flowRemoveTypes(input)
51 | const outFile = path.resolve(pkg.paths.packageBuildOutput, filePath)
52 | ensureParentDirectoryExists(outFile)
53 | fs.writeFileSync(outFile, output.toString(), { encoding: 'utf8' })
54 | fs.writeFileSync(`${outFile}.flow`, input, { encoding: 'utf8' })
55 | resolve()
56 | })
57 |
58 | const limit = pLimit(maxConcurrentTranspiles)
59 | const queueTranspile = filePath => limit(() => transpileFile(filePath))
60 | return Promise.all(R.map(queueTranspile, filePaths))
61 | })
62 | },
63 | clean: (pkg: Package) =>
64 | new Promise(resolve => {
65 | if (fs.pathExistsSync(pkg.paths.packageBuildOutput)) {
66 | fs.removeSync(pkg.paths.packageBuildOutput)
67 | }
68 | resolve()
69 | }),
70 | deploy: (pkg: Package) => {
71 | throw new PackageError(pkg, '"deploy" not supported by "flow" plugin')
72 | },
73 | develop: (pkg: Package) => {
74 | throw new PackageError(pkg, '"develop" not supported by "flow" plugin')
75 | },
76 | }
77 |
78 | module.exports = flowBuildPlugin
79 |
--------------------------------------------------------------------------------
/packages/lib/src/colors.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { Chalk } from 'chalk'
4 |
5 | const chalk = require('chalk')
6 |
7 | const background = [
8 | 'bgGreen',
9 | 'bgYellow',
10 | 'bgBlue',
11 | 'bgMagenta',
12 | 'bgCyan',
13 | 'bgWhite',
14 | 'bgBlackBright',
15 | 'bgRedBright',
16 | 'bgGreenBright',
17 | 'bgYellowBright',
18 | 'bgBlueBright',
19 | 'bgMagentaBright',
20 | 'bgCyanBright',
21 | 'bgWhiteBright',
22 | 'bgBlack',
23 | 'bgRed',
24 | ]
25 |
26 | const foreground = [
27 | /* 0 */ 'green',
28 | /* 1 */ 'yellow',
29 | /* 2 */ 'blue',
30 | /* 3 */ 'magenta',
31 | /* 4 */ 'cyan',
32 | /* 5 */ 'white',
33 | /* 6 */ 'gray',
34 | /* 7 */ 'redBright',
35 | /* 8 */ 'greenBright',
36 | /* 9 */ 'yellowBright',
37 | /* 10 */ 'blueBright',
38 | /* 11 */ 'magentaBright',
39 | /* 12 */ 'cyanBright',
40 | /* 13 */ 'whiteBright',
41 | /* 14 */ 'black',
42 | /* 15 */ 'red',
43 | ]
44 |
45 | const colorPairs = [
46 | [0, 2],
47 | [0, 3],
48 | [0, 9],
49 | [0, 13],
50 | [0, 14],
51 | [1, 2],
52 | [1, 3],
53 | [1, 11],
54 | [1, 14],
55 | [1, 15],
56 | [2, 0],
57 | [2, 1],
58 | [2, 8],
59 | [2, 11],
60 | [2, 12],
61 | [2, 13],
62 | [2, 15],
63 | [3, 5],
64 | [3, 8],
65 | [3, 9],
66 | [3, 12],
67 | [3, 13],
68 | [4, 2],
69 | [4, 3],
70 | [4, 6],
71 | [4, 13],
72 | [4, 14],
73 | [4, 15],
74 | [5, 2],
75 | [5, 3],
76 | [5, 6],
77 | [5, 7],
78 | [5, 10],
79 | [5, 14],
80 | [5, 15],
81 | [6, 0],
82 | [6, 1],
83 | [6, 2],
84 | [6, 4],
85 | [6, 8],
86 | [6, 9],
87 | [6, 12],
88 | [6, 13],
89 | [6, 14],
90 | [7, 2],
91 | [7, 9],
92 | [7, 12],
93 | [7, 13],
94 | [7, 14],
95 | [8, 2],
96 | [8, 3],
97 | [8, 6],
98 | [8, 7],
99 | [8, 10],
100 | [8, 14],
101 | [8, 15],
102 | [9, 2],
103 | [9, 3],
104 | [9, 6],
105 | [9, 7],
106 | [9, 10],
107 | [9, 11],
108 | [9, 14],
109 | [9, 15],
110 | [10, 8],
111 | [10, 9],
112 | [10, 12],
113 | [10, 13],
114 | [10, 14],
115 | [11, 2],
116 | [11, 14],
117 | [12, 2],
118 | [12, 3],
119 | [12, 6],
120 | [12, 7],
121 | [12, 10],
122 | [12, 14],
123 | [12, 15],
124 | [13, 0],
125 | [13, 2],
126 | [13, 3],
127 | [13, 4],
128 | [13, 6],
129 | [13, 7],
130 | [13, 10],
131 | [13, 14],
132 | [13, 15],
133 | ]
134 | .reverse()
135 | .map(([b, f]) => [f, b])
136 | .sort(([fx], [fy]) => fy - fx)
137 |
138 | let index = -1
139 |
140 | const nextColorPair = (): Chalk => {
141 | index += 1
142 | if (index >= colorPairs.length) {
143 | index = 0
144 | }
145 | const [fgIndex, bgIndex] = colorPairs[index]
146 | const bg = background[bgIndex]
147 | const fg = foreground[fgIndex]
148 | // $FlowFixMe
149 | return chalk[bg][fg]
150 | }
151 |
152 | module.exports = {
153 | nextColorPair,
154 | }
155 |
--------------------------------------------------------------------------------
/scripts/utils.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | const os = require('os')
4 | const path = require('path')
5 | const pLimit = require('p-limit')
6 | const R = require('ramda')
7 | const fs = require('fs-extra')
8 | const globby = require('globby')
9 | const flowRemoveTypes = require('flow-remove-types')
10 | const rimraf = require('rimraf')
11 | const pify = require('pify')
12 |
13 | const rimrafAsync = pify(rimraf)
14 |
15 | const flowPackages = [
16 | 'babel-config',
17 | 'cli',
18 | 'lib',
19 | 'plugin-build-babel',
20 | 'plugin-build-flow',
21 | 'plugin-deploy-now',
22 | ]
23 |
24 | const maxConcurrentTranspiles = os.cpus().length
25 |
26 | const ensureParentDirectoryExists = filePath => {
27 | const dir = path.dirname(filePath)
28 | fs.ensureDirSync(dir)
29 | }
30 |
31 | async function cleanFlowPackage(packageName) {
32 | const outputDir = path.resolve(
33 | process.cwd(),
34 | `./packages/${packageName}/build`,
35 | )
36 | return rimrafAsync(outputDir)
37 | }
38 |
39 | async function buildFlowPackage(packageName) {
40 | const outputDir = path.resolve(
41 | process.cwd(),
42 | `./packages/${packageName}/build`,
43 | )
44 | const sourceDir = path.resolve(process.cwd(), `./packages/${packageName}/src`)
45 |
46 | const patterns = ['**/*.js', '!__tests__', '!test.js'].concat([
47 | '!node_modules/**/*',
48 | `!${path.basename(outputDir)}/**/*`,
49 | ])
50 |
51 | // :: string -> Array
52 | const getJsFilePaths = () =>
53 | globby(patterns, {
54 | cwd: sourceDir,
55 | })
56 |
57 | const filePaths = await getJsFilePaths()
58 |
59 | const transpileFile = filePath =>
60 | new Promise(resolve => {
61 | const module = path.resolve(sourceDir, filePath)
62 | const input = fs.readFileSync(module, 'utf8')
63 | const output = flowRemoveTypes(input)
64 | const outFile = path.resolve(outputDir, filePath)
65 | ensureParentDirectoryExists(outFile)
66 | fs.writeFileSync(outFile, output.toString(), { encoding: 'utf8' })
67 | fs.writeFileSync(`${outFile}.flow`, input, { encoding: 'utf8' })
68 | resolve()
69 | })
70 |
71 | const limit = pLimit(maxConcurrentTranspiles)
72 | const queueTranspile = filePath => limit(() => transpileFile(filePath))
73 | await Promise.all(R.map(queueTranspile, filePaths))
74 | }
75 |
76 | module.exports = {
77 | buildAsync: () => {
78 | console.log('Building...')
79 | return Promise.all(flowPackages.map(buildFlowPackage)).then(
80 | () => {
81 | console.log('Build complete')
82 | },
83 | err => {
84 | console.error('Build failed')
85 | console.error(err)
86 | process.exit(1)
87 | },
88 | )
89 | },
90 | cleanAsync: () => {
91 | console.log('Cleaning...')
92 | return Promise.all(flowPackages.map(cleanFlowPackage)).then(
93 | () => {
94 | console.log('Clean complete')
95 | },
96 | err => {
97 | console.error('Clean failed')
98 | console.error(err)
99 | process.exit(1)
100 | },
101 | )
102 | },
103 | }
104 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at sean+lerna-cola@ctrlplusb.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/packages/cli/src/development-service/graceful-shutdown-manager.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type {
4 | PackageConductor,
5 | PackageWatcher,
6 | } from '@lerna-cola/lib/build/types'
7 |
8 | const R = require('ramda')
9 | const { config, TerminalUtils } = require('@lerna-cola/lib')
10 |
11 | type PackageWatchers = { [key: string]: PackageWatcher }
12 | type PackageConductors = { [key: string]: PackageConductor }
13 |
14 | module.exports = function gracefulShutdownManager(
15 | packageConductors: PackageConductors,
16 | packageWatchers: PackageWatchers,
17 | ) {
18 | let shuttingDown = false
19 | let postDevelopRun = false
20 |
21 | const postDevelopHook = config().commandHooks.develop.pre
22 |
23 | const ensurePostDevelopHookRun = async () => {
24 | if (postDevelopHook && !postDevelopRun) {
25 | TerminalUtils.info('Running post develop hook')
26 | await postDevelopHook()
27 | }
28 | postDevelopRun = true
29 | }
30 |
31 | async function performGracefulShutdown(exitCode) {
32 | // Avoid multiple calls (e.g. if ctrl+c pressed multiple times)
33 | if (shuttingDown) return
34 | shuttingDown = true
35 | try {
36 | TerminalUtils.info('Shutting down development service...')
37 |
38 | // This will ensure that the process exits after a 10 second grace period.
39 | // Hopefully all the dispose functions below would have completed
40 | setTimeout(async () => {
41 | TerminalUtils.verbose('Forcing shutdown after grace period')
42 | setTimeout(() => {
43 | TerminalUtils.warning(
44 | 'Your post develop hook seems to be taking a long time to complete. 10 seconds have passed so we are now forcing an exit on the develop process.',
45 | )
46 | process.exit(1)
47 | }, 10 * 1000)
48 | // Even if we are forcing an exit we should wait for pross develop
49 | // hook to execute
50 | await ensurePostDevelopHookRun()
51 | process.exit(1)
52 | }, 10 * 1000)
53 |
54 | // Firstly kill all our packageWatchers.
55 | Object.keys(packageWatchers).forEach(packageName =>
56 | packageWatchers[packageName].stop(),
57 | )
58 |
59 | // Then call off the `.stop()` against all our package conductors.
60 | await Promise.all(
61 | R.values(packageConductors).map(packageDevelopConductor =>
62 | packageDevelopConductor.stop(),
63 | ),
64 | )
65 |
66 | // Then call the post develop hook
67 | await ensurePostDevelopHookRun()
68 | } catch (err) {
69 | TerminalUtils.error(
70 | 'An error occurred whilst shutting down the development service',
71 | err,
72 | )
73 | process.exit(1)
74 | }
75 | process.exit(exitCode)
76 | }
77 |
78 | // Ensure that we perform a graceful shutdown when any of the following
79 | // signals are sent to our process.
80 | ;['SIGINT', 'SIGTERM'].forEach(signal => {
81 | process.on(signal, () => {
82 | TerminalUtils.verbose(`Received ${signal} termination signal`)
83 | performGracefulShutdown(0)
84 | })
85 | })
86 |
87 | process.on('unhandledRejection', err => {
88 | TerminalUtils.error('Unhandled error.', err)
89 | performGracefulShutdown(1)
90 | })
91 |
92 | process.on('exit', () => {
93 | performGracefulShutdown(0)
94 | TerminalUtils.info('Till next time. *kiss*')
95 | })
96 | }
97 |
--------------------------------------------------------------------------------
/packages/babel-config/src/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { Package } from '@lerna-cola/lib/build/types'
4 |
5 | const semver = require('semver')
6 | const { ArrayUtils, LogicUtils } = require('@lerna-cola/lib')
7 |
8 | type Options = {
9 | nodeVersion?: string,
10 | }
11 |
12 | module.exports = function generateConfig(pkg: Package, options: Options = {}) {
13 | const env = process.env.BABEL_ENV || process.env.NODE_ENV
14 |
15 | const targetNodeVersion = options.nodeVersion || process.versions.node
16 |
17 | return {
18 | babelrc: false,
19 |
20 | // Handy for sourcemaps generation.
21 | sourceRoot: pkg.paths.packageRoot,
22 |
23 | // Source maps will be useful for debugging errors in our node executions.
24 | sourceMaps: 'both',
25 |
26 | presets: ArrayUtils.removeNil([
27 | [
28 | 'env',
29 | {
30 | targets: {
31 | node: targetNodeVersion,
32 | },
33 | },
34 | ],
35 |
36 | // jsx && flow support
37 | 'react',
38 | ]),
39 |
40 | plugins: ArrayUtils.removeNil([
41 | // const { foo, ...others } = object
42 | // object = { foo, ...others }
43 | // This plugin uses Object.assign directly.
44 | [
45 | 'transform-object-rest-spread',
46 | {
47 | // For node >= 6 we can rely on native Object.assign, else it will
48 | // need to be polyfilled.
49 | useBuiltIns: semver.major(targetNodeVersion) >= 6,
50 | },
51 | ],
52 |
53 | // function (
54 | // arg1,
55 | // arg2,
56 | // ) { }
57 | LogicUtils.onlyIf(
58 | semver.major(targetNodeVersion) < 8,
59 | 'syntax-trailing-function-commas',
60 | ),
61 |
62 | // class { handleThing = () => { } }
63 | 'transform-class-properties',
64 |
65 | // Compiles import() to a deferred require()
66 | 'babel-plugin-dynamic-import-node',
67 |
68 | // Polyfills the runtime needed for async/await and generators.
69 | // async/await exists in Node 7.6.0 upwards
70 | LogicUtils.onlyIf(
71 | semver.lt(targetNodeVersion, '7.6.0'),
72 | 'babel-plugin-transform-runtime',
73 | ),
74 |
75 | // Replaces the React.createElement function with one that is
76 | // more optimized for production.
77 | LogicUtils.onlyIf(
78 | env === 'production',
79 | 'transform-react-inline-elements',
80 | ),
81 |
82 | // Hoists element creation to the top level for subtrees that
83 | // are fully static, which reduces call to React.createElement
84 | // and the resulting allocations. More importantly, it tells
85 | // React that the subtree hasn’t changed so React can completely
86 | // skip it when reconciling.
87 | LogicUtils.onlyIf(
88 | env === 'production',
89 | 'transform-react-constant-elements',
90 | ),
91 |
92 | // Removes PropTypes code as it's just dead weight for a production build.
93 | LogicUtils.onlyIf(
94 | env === 'production',
95 | 'babel-plugin-transform-react-remove-prop-types',
96 | ),
97 |
98 | // The following two plugins are currently necessary to make React warnings
99 | // include more valuable information. They are included here because they are
100 | // currently not enabled in babel-preset-react. See the below threads for more info:
101 | // https://github.com/babel/babel/issues/4702
102 | // https://github.com/babel/babel/pull/3540#issuecomment-228673661
103 | // https://github.com/facebookincubator/create-react-app/issues/989
104 |
105 | // Adds __self attribute to JSX which React will use for some warnings
106 | LogicUtils.onlyIf(
107 | env === 'development' || env === 'test',
108 | 'transform-react-jsx-self',
109 | ),
110 |
111 | // Adds component stack to warning messages
112 | LogicUtils.onlyIf(
113 | env === 'development' || env === 'test',
114 | 'transform-react-jsx-source',
115 | ),
116 |
117 | // If we are transpiling a node package then we inject some code to
118 | // include source maps support on the transpiled code.
119 | LogicUtils.onlyIf(env === 'development', 'inject-source-map-init'),
120 | ]),
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/packages/lib/src/types.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | /* eslint-disable no-use-before-define */
3 |
4 | // eslint-disable-next-line no-unused-vars
5 | import type { ChildProcess } from 'child_process'
6 | import type { Chalk } from 'chalk'
7 |
8 | export type LernaColaPluginConfig =
9 | | string
10 | | {
11 | name: string,
12 | options: Object,
13 | }
14 |
15 | export type CommandHooks = {
16 | pre: () => Promise,
17 | post: () => Promise,
18 | }
19 |
20 | export type LernaColaCommandHooksConfig = {
21 | clean?: CommandHooks,
22 | build?: CommandHooks,
23 | develop?: CommandHooks,
24 | deploy?: CommandHooks,
25 | }
26 |
27 | export type LernaColaPackageConfig = {
28 | srcDir: string,
29 | entryFile: string,
30 | outputDir: string,
31 | disableSrcWatching: boolean,
32 | cleanPlugin?: LernaColaPluginConfig,
33 | buildPlugin?: LernaColaPluginConfig,
34 | developPlugin?: LernaColaPluginConfig,
35 | deployPlugin?: LernaColaPluginConfig,
36 | }
37 |
38 | export type LernaColaConfig = {
39 | commandHooks?: LernaColaCommandHooksConfig,
40 | packages: { [key: string]: LernaColaPackageConfig },
41 | packageSources?: Array,
42 | }
43 |
44 | export type PackageVersions = { [string]: string }
45 |
46 | export type PackageWatcher = {
47 | start: () => void,
48 | stop: () => void,
49 | }
50 |
51 | export type RunType = 'FIRST_RUN' | 'SELF_CHANGED' | 'DEPENDENCY_CHANGED'
52 |
53 | export type PackageConductor = {
54 | run: (type: RunType, changedDependency?: Package) => Promise,
55 | stop: () => Promise,
56 | }
57 |
58 | export type DevelopInstance = {
59 | kill: () => Promise,
60 | }
61 |
62 | /**
63 | * Paths for a Package
64 | */
65 | export type PackagePaths = {
66 | monoRepoRoot: string,
67 | monoRepoRootNodeModules: string,
68 | packageBuildOutput: string,
69 | packageEntryFile: string,
70 | packageJson: string,
71 | packageLockJson: string,
72 | packageNodeModules: string,
73 | packageRoot: string,
74 | packageSrc: string,
75 | packageWebpackCache: string,
76 | }
77 |
78 | export type PluginArgs = {
79 | config: Config,
80 | }
81 |
82 | export type DevelopPluginArgs = PluginArgs & {
83 | runType: RunType,
84 | changedDependency?: Package,
85 | watcher: PackageWatcher,
86 | }
87 |
88 | export type CleanPlugin = {
89 | name: string,
90 | clean: (pkg: Package, options: Object, args: PluginArgs) => Promise,
91 | }
92 |
93 | export type BuildPlugin = {
94 | name: string,
95 | build: (pkg: Package, options: Object, args: PluginArgs) => Promise,
96 | }
97 |
98 | export type DeployPath = string
99 |
100 | export type DeployPlugin = {
101 | name: string,
102 | deploy: (pkg: Package, options: Object, args: PluginArgs) => Promise,
103 | }
104 |
105 | export type DevelopPlugin = {
106 | name: string,
107 | develop: (
108 | pkg: Package,
109 | options: Object,
110 | args: DevelopPluginArgs,
111 | ) => Promise,
112 | }
113 |
114 | export type PackagePlugins = {
115 | cleanPlugin?: {
116 | plugin: CleanPlugin,
117 | options: Object,
118 | },
119 | buildPlugin?: {
120 | plugin: BuildPlugin,
121 | options: Object,
122 | },
123 | deployPlugin?: {
124 | plugin: DeployPlugin,
125 | options: Object,
126 | },
127 | developPlugin?: {
128 | plugin: DevelopPlugin,
129 | options: Object,
130 | },
131 | }
132 |
133 | export type Package = {
134 | name: string,
135 | config: Object,
136 | color: Chalk,
137 | disableSrcWatching: boolean,
138 | allDependants: Array,
139 | allDependencies: Array,
140 | dependants: Array,
141 | dependencies: Array,
142 | devDependencies: Array,
143 | packageJson: Object,
144 | paths: PackagePaths,
145 | plugins: PackagePlugins,
146 | version: string,
147 | }
148 |
149 | export type PackageMap = { [string]: Package }
150 |
151 | export type Config = {
152 | commandHooks: {
153 | clean: CommandHooks,
154 | build: CommandHooks,
155 | develop: CommandHooks,
156 | deploy: CommandHooks,
157 | },
158 | packages: Array,
159 | packageMap: PackageMap,
160 | terminalLabelMinLength: number,
161 | }
162 |
163 | declare module 'execa' {
164 | declare type ExecaChildProcess = ChildProcess & Promise
165 | declare type Execa = (
166 | cmd: string,
167 | args: ?Array,
168 | opts: ?Object,
169 | ) => ExecaChildProcess
170 |
171 | declare type ExecaStatics = {
172 | spawn: Execa,
173 | sync: ChildProcess,
174 | }
175 |
176 | declare type ExecaWithStatics = Execa & ExecaStatics
177 |
178 | declare module.exports: ExecaWithStatics
179 | }
180 |
--------------------------------------------------------------------------------
/packages/lib/src/config/utils.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | /* eslint-disable no-console */
4 | /* eslint-disable global-require */
5 |
6 | import type { LernaColaPluginConfig, Package } from '../types'
7 |
8 | const R = require('ramda')
9 | const toposort = require('toposort')
10 | const resolvePlugin = require('../plugins/resolve-plugin')
11 |
12 | const allDeps = (pkg: Package) =>
13 | (pkg.dependencies || []).concat(pkg.devDependencies || [])
14 |
15 | const getDependencies = (
16 | pkg: Package,
17 | packages: Array,
18 | dependencyType: 'dependencies' | 'devDependencies',
19 | ): Array => {
20 | const targetDependencies = R.path(['packageJson', dependencyType], pkg)
21 | if (!targetDependencies) {
22 | return []
23 | }
24 | return Object.keys(targetDependencies).reduce((acc, cur) => {
25 | const match = packages.find(x => x.name === cur)
26 | return match ? [...acc, match.name] : acc
27 | }, [])
28 | }
29 |
30 | const getDependants = (pkg: Package, packages: Array): Array =>
31 | packages.filter(x => R.contains(pkg.name, allDeps(x))).map(R.prop('name'))
32 |
33 | const orderByDependencies = (packages: Array): Array => {
34 | const packageDependencyGraph = (pkg: Package): Array> =>
35 | R.pipe(
36 | allDeps,
37 | R.map(dependencyName => [dependencyName, pkg.name]),
38 | )(pkg)
39 |
40 | // $FlowFixMe
41 | const dependencyGraph = R.chain(packageDependencyGraph)
42 |
43 | const hasNoDependencies = ({ dependencies }: Package): boolean =>
44 | dependencies.length === 0
45 |
46 | const packagesWithNoDependencies: Array = R.pipe(
47 | R.filter(hasNoDependencies),
48 | R.map(R.prop('name')),
49 | )(packages)
50 |
51 | const findPackageByName = (packageNames: Array): Array =>
52 | packageNames.reduce((acc, name) => {
53 | const found = packages.find(x => x.name === name)
54 | if (found != null) {
55 | return [...acc, found]
56 | }
57 | return acc
58 | }, [])
59 |
60 | return R.pipe(
61 | dependencyGraph,
62 | toposort,
63 | R.without(packagesWithNoDependencies),
64 | R.concat(packagesWithNoDependencies),
65 | findPackageByName,
66 | )(packages)
67 | }
68 |
69 | const getAllDependants = (
70 | pkg: Package,
71 | packages: Array,
72 | ): Array => {
73 | const findPackage = name => packages.find(x => x.name === name)
74 |
75 | const resolveDependants = dependantName => {
76 | const dependant = findPackage(dependantName)
77 | if (!dependant) {
78 | throw new Error(
79 | `Could not find dependant package "${dependantName ||
80 | ''}" for package ${pkg.name}`,
81 | )
82 | }
83 | return [
84 | dependant.name,
85 | ...dependant.dependants,
86 | ...R.map(resolveDependants, dependant.dependants),
87 | ]
88 | }
89 |
90 | // $FlowFixMe
91 | const allDependants = R.chain(resolveDependants, pkg.dependants)
92 |
93 | // Let's get a sorted version of allDependants by filtering allPackages
94 | // which will already be in a safe build order.
95 | return packages
96 | .filter(x => !!R.find(R.equals(x.name), allDependants))
97 | .map(R.prop('name'))
98 | }
99 |
100 | const getAllDependencies = (
101 | pkg: Package,
102 | packages: Array,
103 | ): Array => {
104 | const findPackage = name => R.find(R.propEq('name', name), packages)
105 |
106 | const resolveDependencies = dependencyName => {
107 | const dependency = findPackage(dependencyName)
108 | if (!dependency) {
109 | throw new Error(
110 | `Could not find dependency package "${dependencyName ||
111 | ''}" for package ${pkg.name}`,
112 | )
113 | }
114 | return [
115 | dependency.name,
116 | ...dependency.dependencies,
117 | ...R.map(resolveDependencies, dependency.dependencies),
118 | ]
119 | }
120 |
121 | // $FlowFixMe
122 | const allDependencies = R.chain(resolveDependencies, pkg.dependencies)
123 |
124 | // Let's get a sorted version of allDependencies by filtering allPackages
125 | // which will already be in a safe build order.
126 | return packages
127 | .filter(x => !!R.find(R.equals(x.name), allDependencies))
128 | .map(R.prop('name'))
129 | }
130 |
131 | const getPlugin = (
132 | packageName: string,
133 | pluginConfig: ?LernaColaPluginConfig,
134 | pluginType: string,
135 | ) => {
136 | if (pluginConfig == null) {
137 | return undefined
138 | }
139 | try {
140 | const config =
141 | typeof pluginConfig === 'string'
142 | ? { name: pluginConfig, options: {} }
143 | : pluginConfig
144 | return {
145 | plugin: resolvePlugin(config.name),
146 | options: config.options,
147 | }
148 | } catch (err) {
149 | console.error(`Failed to load "${pluginType}" for ${packageName}`)
150 | throw err
151 | }
152 | }
153 |
154 | module.exports = {
155 | getAllDependants,
156 | getAllDependencies,
157 | getDependants,
158 | getDependencies,
159 | getPlugin,
160 | orderByDependencies,
161 | }
162 |
--------------------------------------------------------------------------------
/packages/lib/src/plugins/script/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { ChildProcess } from 'child_process'
4 | import type {
5 | Package,
6 | CleanPlugin,
7 | BuildPlugin,
8 | DevelopPlugin,
9 | DeployPlugin,
10 | DevelopInstance,
11 | } from '../../types'
12 |
13 | const R = require('ramda')
14 | const TerminalUtils = require('../../terminal')
15 | const ChildProcessUtils = require('../../childProcess')
16 | const PluginUtils = require('../utils')
17 | const { PackageError } = require('../../errors')
18 |
19 | type Options = {|
20 | scriptName: string,
21 | runForEveryChange?: boolean,
22 | runSync?: boolean,
23 | |}
24 |
25 | type TaskName = 'build' | 'develop' | 'deploy' | 'clean'
26 |
27 | type Config = {
28 | managed: boolean,
29 | }
30 |
31 | type ChildProcessMap = {
32 | [key: TaskName]: {
33 | [key: string]: ChildProcess,
34 | },
35 | }
36 |
37 | const childProcessMap: ChildProcessMap = {
38 | build: {},
39 | clean: {},
40 | develop: {},
41 | deploy: {},
42 | }
43 |
44 | const addChildProcess = (
45 | pkg: Package,
46 | task: TaskName,
47 | processInstance: ChildProcess,
48 | ) => {
49 | childProcessMap[task][pkg.name] = processInstance
50 | }
51 |
52 | const removeChildProcess = (pkg: Package, task: TaskName) => {
53 | if (childProcessMap[task]) {
54 | delete childProcessMap[task][pkg.name]
55 | }
56 | }
57 |
58 | const getChildProcess = (pkg: Package, task: TaskName) =>
59 | childProcessMap[task][pkg.name]
60 |
61 | const killChildProcessFor = (pkg: Package, task: TaskName) => {
62 | const childProcess = getChildProcess(pkg, task)
63 | if (!childProcess) {
64 | TerminalUtils.verbosePkg(pkg, `No running "${task}" script process to kill`)
65 | return Promise.resolve()
66 | }
67 | return PluginUtils.killChildProcess(pkg, childProcess).then(() => {
68 | TerminalUtils.verbosePkg(
69 | pkg,
70 | `Killed "${task}" script process successfully`,
71 | )
72 | removeChildProcess(pkg, task)
73 | })
74 | }
75 |
76 | const runScript = (task: TaskName, config: Config) => async (
77 | pkg: Package,
78 | options: Options,
79 | ): Promise => {
80 | if (!options.scriptName || typeof options.scriptName !== 'string') {
81 | throw new Error(
82 | `No scriptName was provided for the develop configuration of ${
83 | pkg.name
84 | }.`,
85 | )
86 | }
87 |
88 | const scriptCmd = R.path(['scripts', options.scriptName], pkg.packageJson)
89 | if (!scriptCmd || R.isEmpty(scriptCmd)) {
90 | throw new Error(
91 | `Could not resolve script named "${options.scriptName}" on ${pkg.name}`,
92 | )
93 | }
94 |
95 | if (config.managed) {
96 | const returnAPI: DevelopInstance = {
97 | kill: () => killChildProcessFor(pkg, task),
98 | }
99 |
100 | const existingProcess = getChildProcess(pkg, task)
101 | if (existingProcess && !options.runForEveryChange) {
102 | return task === 'develop' ? returnAPI : undefined
103 | }
104 |
105 | if (existingProcess) {
106 | await killChildProcessFor(pkg, task)
107 | }
108 |
109 | const execArgs = [
110 | pkg,
111 | 'npm',
112 | ['run', options.scriptName],
113 | {
114 | cwd: pkg.paths.packageRoot,
115 | },
116 | ]
117 |
118 | if (options.runSync) {
119 | ChildProcessUtils.execSyncPkg(...execArgs)
120 | } else {
121 | await new Promise((resolve, reject) => {
122 | TerminalUtils.infoPkg(pkg, `Executing script "${options.scriptName}"`)
123 |
124 | const childProcess = ChildProcessUtils.execPkg(...execArgs)
125 |
126 | childProcess.catch(err => {
127 | TerminalUtils.verbosePkg(
128 | pkg,
129 | `Error executing script "${options.scriptName}"`,
130 | )
131 | reject(err)
132 | })
133 |
134 | // Give the catch above a tick of space, so that it can resolve any
135 | // error that may have occurred
136 | process.nextTick(() => {
137 | childProcess.on('close', () => {
138 | TerminalUtils.verbosePkg(
139 | pkg,
140 | `Stopped script "${options.scriptName}" process`,
141 | )
142 | // ensure that the process is removed
143 | removeChildProcess(pkg, task)
144 | })
145 | addChildProcess(pkg, task, childProcess)
146 | resolve()
147 | })
148 | })
149 | }
150 |
151 | return returnAPI
152 | }
153 |
154 | TerminalUtils.infoPkg(pkg, `Executing script "${options.scriptName}"`)
155 |
156 | try {
157 | await ChildProcessUtils.execPkg(pkg, 'npm', ['run', options.scriptName], {
158 | cwd: pkg.paths.packageRoot,
159 | })
160 | } catch (err) {
161 | throw new PackageError(
162 | pkg,
163 | `Error executing script "${options.scriptName}"`,
164 | err,
165 | )
166 | }
167 |
168 | return undefined
169 | }
170 |
171 | const scriptPlugin: CleanPlugin & BuildPlugin & DevelopPlugin & DeployPlugin = {
172 | name: 'plugin-script',
173 | // $FlowFixMe
174 | build: runScript('build', { managed: false }),
175 | // $FlowFixMe
176 | clean: runScript('clean', { managed: false }),
177 | // $FlowFixMe
178 | develop: runScript('develop', { managed: true }),
179 | // $FlowFixMe
180 | deploy: runScript('deploy', { managed: false }),
181 | }
182 |
183 | module.exports = scriptPlugin
184 |
--------------------------------------------------------------------------------
/packages/lib/src/terminal.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | /* eslint-disable no-console */
4 |
5 | import type { Package } from './types'
6 |
7 | const StringUtils = require('./strings')
8 |
9 | type MultiSelectChoice = {
10 | value: string,
11 | text: string,
12 | }
13 |
14 | type MultiSelectOptions = {
15 | choices: Array,
16 | selected?: Array,
17 | validate?: (Array) => boolean | string,
18 | }
19 |
20 | type SelectOptions = {
21 | choices: Array,
22 | selected?: string,
23 | validate?: string => boolean | string,
24 | }
25 |
26 | type InputOptions = {
27 | validate?: string => boolean | string,
28 | }
29 |
30 | type SingleValueAnswer = {
31 | type: string,
32 | value: string,
33 | }
34 |
35 | type ConfirmAnswer = {
36 | key: string,
37 | value: boolean,
38 | }
39 |
40 | const chalk = require('chalk')
41 | const inquirer = require('inquirer')
42 | const prettyFormat = require('pretty-format')
43 |
44 | const format = data => (typeof data === 'string' ? data : prettyFormat(data))
45 |
46 | const formatVerbose = data => chalk.dim(format(data))
47 | const formatError = data => chalk.red.bold(format(data))
48 | const formatWarning = data => chalk.yellow(format(data))
49 | const formatTitle = data => chalk.bold(format(data))
50 | const formatInfo = data => format(data)
51 | const formatSuccess = data => chalk.green(format(data))
52 | const formatHeader = data => chalk.bold(format(data))
53 |
54 | function verbose(data: any): void {
55 | if (process.env.VERBOSE) {
56 | console.log(StringUtils.lernaColaMsg(formatVerbose(data)))
57 | }
58 | }
59 |
60 | function verbosePkg(pkg: Package, data: any): void {
61 | if (process.env.VERBOSE) {
62 | console.log(StringUtils.packageMsg(pkg, formatVerbose(data)))
63 | }
64 | }
65 |
66 | function error(data: string, err?: Error): void {
67 | console.log(StringUtils.lernaColaMsg(formatError(data)))
68 | if (err && err.stack) {
69 | console.log(StringUtils.lernaColaMsg(err.stack))
70 | }
71 | }
72 |
73 | function errorPkg(pkg: Package, data: any, err?: Error): void {
74 | console.log(StringUtils.packageMsg(pkg, formatError(data)))
75 | if (err && err.stack) {
76 | console.log(StringUtils.packageMsg(pkg, err.stack))
77 | }
78 | }
79 |
80 | function warning(data: any): void {
81 | console.log(StringUtils.lernaColaMsg(formatWarning(data)))
82 | }
83 |
84 | function warningPkg(pkg: Package, data: any): void {
85 | console.log(StringUtils.packageMsg(pkg, formatWarning(data)))
86 | }
87 |
88 | function title(data: any): void {
89 | console.log(StringUtils.lernaColaMsg(formatTitle(data)))
90 | }
91 |
92 | function titlePkg(pkg: Package, data: any): void {
93 | console.log(StringUtils.packageMsg(pkg, formatTitle(data)))
94 | }
95 |
96 | function info(data: any): void {
97 | console.log(StringUtils.lernaColaMsg(formatInfo(data)))
98 | }
99 |
100 | function infoPkg(pkg: Package, data: any): void {
101 | console.log(StringUtils.packageMsg(pkg, formatInfo(data)))
102 | }
103 |
104 | function success(data: any): void {
105 | console.log(StringUtils.lernaColaMsg(formatSuccess(data)))
106 | }
107 |
108 | function successPkg(pkg: Package, data: any): void {
109 | console.log(StringUtils.packageMsg(pkg, formatSuccess(data)))
110 | }
111 |
112 | function header(data: any): void {
113 | console.log(StringUtils.lernaColaMsg(formatHeader(data)))
114 | }
115 |
116 | function headerPkg(pkg: Package, data: any): void {
117 | console.log(StringUtils.packageMsg(pkg, formatHeader(data)))
118 | }
119 |
120 | function multiSelect(
121 | message: string,
122 | options: MultiSelectOptions,
123 | ): Promise> {
124 | const { choices, selected, validate } = options
125 | return inquirer
126 | .prompt([
127 | {
128 | type: 'checkbox',
129 | name: 'prompt',
130 | message,
131 | choices,
132 | pageSize: choices.length,
133 | validate,
134 | default: selected,
135 | },
136 | ])
137 | .then(answers => answers.prompt)
138 | }
139 |
140 | function select(
141 | message: string,
142 | options: SelectOptions,
143 | ): Promise {
144 | const { choices, validate } = options
145 | return inquirer
146 | .prompt([
147 | {
148 | type: 'list',
149 | name: 'prompt',
150 | message,
151 | choices,
152 | pageSize: choices.length,
153 | validate,
154 | },
155 | ])
156 | .then(answers => answers.prompt)
157 | }
158 |
159 | function input(
160 | message: string,
161 | options?: InputOptions = {},
162 | ): Promise {
163 | const { validate } = options
164 | return inquirer
165 | .prompt([
166 | {
167 | type: 'input',
168 | name: 'input',
169 | message,
170 | validate,
171 | },
172 | ])
173 | .then(answers => answers.input)
174 | }
175 |
176 | function confirm(message: string): Promise {
177 | return inquirer
178 | .prompt([
179 | {
180 | type: 'expand',
181 | name: 'confirm',
182 | message,
183 | default: 2, // default to help in order to avoid clicking straight through
184 | choices: [
185 | { key: 'y', name: 'Yes', value: true },
186 | { key: 'n', name: 'No', value: false },
187 | ],
188 | },
189 | ])
190 | .then(answers => answers.confirm)
191 | }
192 |
193 | module.exports = {
194 | confirm,
195 | error,
196 | errorPkg,
197 | header,
198 | headerPkg,
199 | info,
200 | infoPkg,
201 | input,
202 | multiSelect,
203 | select,
204 | success,
205 | successPkg,
206 | title,
207 | titlePkg,
208 | verbose,
209 | verbosePkg,
210 | warning,
211 | warningPkg,
212 | }
213 |
--------------------------------------------------------------------------------
/packages/plugin-deploy-now/src/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { Package, DeployPlugin } from '@lerna-cola/lib/build/types'
4 |
5 | const R = require('ramda')
6 | const pWhilst = require('p-whilst')
7 | const dedent = require('dedent')
8 | const chalk = require('chalk')
9 | const deepMerge = require('deepmerge')
10 | const tempWrite = require('temp-write')
11 | const fs = require('fs-extra')
12 | const {
13 | TerminalUtils,
14 | ChildProcessUtils,
15 | Errors: { PackageError },
16 | } = require('@lerna-cola/lib')
17 |
18 | type NowSettings = {
19 | alias?: string,
20 | forwardNpm?: boolean,
21 | public?: boolean,
22 | }
23 |
24 | type Options = {
25 | disableRemovePrevious?: boolean,
26 | deployTimeoutMins?: number,
27 | passThroughEnvVars?: Array,
28 | settings?: NowSettings,
29 | }
30 |
31 | const nowDeployPlugin: DeployPlugin = {
32 | name: '@lerna-cola/plugin-now',
33 | build: (pkg: Package) => {
34 | throw new PackageError(pkg, '"build" not supported by "now" plugin')
35 | },
36 | clean: (pkg: Package) => {
37 | throw new PackageError(pkg, '"clean" not supported by "now" plugin')
38 | },
39 | develop: (pkg: Package) => {
40 | throw new PackageError(pkg, '"develop" not supported by "now" plugin')
41 | },
42 | deploy: async (pkg: Package, options: Options) => {
43 | try {
44 | try {
45 | ChildProcessUtils.execSync('now', ['-v'])
46 | } catch (err) {
47 | TerminalUtils.errorPkg(
48 | pkg,
49 | 'You need to have the "now" CLI installed on your machine and available on your PATH in order to deploy to now.',
50 | )
51 | throw err
52 | }
53 |
54 | const deploymentName = pkg.name
55 |
56 | const envVars = options.passThroughEnvVars
57 | ? options.passThroughEnvVars.reduce(
58 | (acc, cur) =>
59 | process.env[cur]
60 | ? [...acc, '-e', `${cur}=${process.env[cur]}`]
61 | : acc,
62 | [],
63 | )
64 | : []
65 |
66 | const nowSettingsPath = tempWrite.sync()
67 | const nowSettings = deepMerge(
68 | // Defaults
69 | {
70 | forwardNpm: true,
71 | public: false,
72 | },
73 | // User overrides
74 | options.settings || {},
75 | )
76 | fs.outputJsonSync(nowSettingsPath, nowSettings)
77 | TerminalUtils.verbosePkg(pkg, nowSettings)
78 |
79 | const args = [
80 | 'deploy',
81 | '-n',
82 | deploymentName,
83 | ...envVars,
84 | '-A',
85 | nowSettingsPath,
86 | '-C',
87 | ]
88 |
89 | const deployResponse = await ChildProcessUtils.execPkg(pkg, 'now', args, {
90 | cwd: pkg.paths.packageRoot,
91 | })
92 | const deploymentIdRegex = /(https:\/\/.+\.now\.sh)/g
93 | if (!deploymentIdRegex.test(deployResponse.stdout)) {
94 | throw new PackageError(
95 | pkg,
96 | 'No deployment id could be found, could not complete deployment',
97 | )
98 | }
99 | const deploymentId = deployResponse.stdout.match(deploymentIdRegex)[0]
100 | TerminalUtils.infoPkg(
101 | pkg,
102 | `Waiting for deployment (${deploymentId}) to be ready...`,
103 | )
104 |
105 | // Now we need to wait for the deployment to be ready.
106 |
107 | let ready = false
108 |
109 | setTimeout(() => {
110 | if (ready) {
111 | return
112 | }
113 | throw new PackageError(
114 | pkg,
115 | dedent(`
116 | The deployment process timed out. There may be an issue with your deployment or with "now". You could try a manually deployment using the following commands to gain more insight into the issue:
117 |
118 | ${chalk.blue(`cd ${pkg.paths.packageRoot}`)}
119 | ${chalk.blue(`now ${args.join(' ')}`)}
120 | `),
121 | )
122 | }, (options.deployTimeoutMins || 15) * 60 * 1000)
123 |
124 | await pWhilst(
125 | () => !ready,
126 | async () => {
127 | // we will check the status for the deployment every 5 seconds
128 | await new Promise(resolve => setTimeout(resolve, 5 * 1000))
129 | const status = ChildProcessUtils.execSync('now', ['ls', deploymentId])
130 | if (/READY/.test(status)) {
131 | ready = true
132 | } else {
133 | TerminalUtils.infoPkg(pkg, '...')
134 | }
135 | },
136 | )
137 |
138 | const alias = R.path(['settings', 'alias'], options)
139 |
140 | if (alias != null) {
141 | TerminalUtils.infoPkg(
142 | pkg,
143 | `Setting up alias for new deployment to ${alias}....`,
144 | )
145 | await ChildProcessUtils.execPkg(pkg, 'now', [
146 | 'alias',
147 | 'set',
148 | deploymentId,
149 | alias,
150 | ])
151 |
152 | // We need to do this at this point before attaching the rules as the rules
153 | // seem to indicate the deployment as not being aliased :-/
154 | if (!options.disableRemovePrevious) {
155 | // Removes previous deployments 👍
156 | try {
157 | TerminalUtils.infoPkg(
158 | pkg,
159 | `Checking to see if there are any previous deployments to remove...`,
160 | )
161 | await ChildProcessUtils.execPkg(pkg, 'now', [
162 | 'rm',
163 | deploymentName,
164 | '--safe',
165 | '-y',
166 | ])
167 | } catch (err) {
168 | TerminalUtils.infoPkg(pkg, 'No previous deployments to remove.')
169 | TerminalUtils.verbosePkg(pkg, err.stack)
170 | }
171 | }
172 | }
173 |
174 | TerminalUtils.successPkg(pkg, `Deployment successful`)
175 | } catch (err) {
176 | throw new PackageError(pkg, 'Failed to deploy', err)
177 | }
178 | },
179 | }
180 |
181 | module.exports = nowDeployPlugin
182 |
--------------------------------------------------------------------------------
/packages/plugin-build-babel/src/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type {
4 | Package,
5 | CleanPlugin,
6 | BuildPlugin,
7 | } from '@lerna-cola/lib/build/types'
8 |
9 | const path = require('path')
10 | const babel = require('babel-core')
11 | const pify = require('pify')
12 | const pLimit = require('p-limit')
13 | const R = require('ramda')
14 | const fs = require('fs-extra')
15 | const globby = require('globby')
16 | const { FsUtils, TerminalUtils } = require('@lerna-cola/lib')
17 |
18 | // Having concurrent babel transpilations seems to break the sourcemap output.
19 | // Incorrect sources get mapped - I wonder if there is a shared global state
20 | // that references the "current" file being transpiled for reference in a
21 | // sourcemap.
22 | const maxConcurrentTranspiles = 1
23 |
24 | // :: (..args) => Promise
25 | const transformFile = pify(babel.transformFile)
26 |
27 | const ensureParentDirectoryExists = (filePath: string): void => {
28 | const dir = path.dirname(filePath)
29 | fs.ensureDirSync(dir)
30 | }
31 |
32 | type Options = {
33 | inputs?: Array,
34 | config?: string | Object,
35 | }
36 |
37 | type SanitisedOptions = {
38 | inputs: Array,
39 | config: Object,
40 | }
41 |
42 | const babelBuildPlugin: CleanPlugin & BuildPlugin = {
43 | name: '@lerna-cola/plugin-build-babel',
44 | build: async (pkg: Package, options: Options) => {
45 | try {
46 | const sanitiseOptions = (opts: Options): SanitisedOptions => {
47 | const { config, inputs } = opts
48 |
49 | let resolvedConfig
50 |
51 | const resolveConfig = (packageName: string): Object => {
52 | const module = FsUtils.resolvePackage(packageName)
53 | if (typeof module !== 'function' || typeof module !== 'object') {
54 | TerminalUtils.errorPkg(
55 | pkg,
56 | `The babel config "${packageName}" is an invalid package. Should export an object or a function.`,
57 | )
58 | }
59 | // $FlowFixMe
60 | return typeof module === 'function' ? module(pkg, options) : module
61 | }
62 |
63 | if (config != null) {
64 | if (typeof config !== 'string' || typeof config === 'object') {
65 | TerminalUtils.errorPkg(
66 | pkg,
67 | 'A babel config package name or object must be provided as options',
68 | )
69 | }
70 |
71 | resolvedConfig =
72 | typeof config === 'string' ? resolveConfig(config) : config
73 | } else {
74 | const packageBabelRc = path.resolve(pkg.paths.packageRoot, '.babelrc')
75 | const repoBabelRc = path.resolve(pkg.paths.monoRepoRoot, '.babelrc')
76 | const packageBabelRcJs = path.resolve(
77 | pkg.paths.packageRoot,
78 | '.babelrc.js',
79 | )
80 | const repoBabelRcJs = path.resolve(
81 | pkg.paths.monoRepoRoot,
82 | '.babelrc.js',
83 | )
84 | if (fs.existsSync(packageBabelRcJs)) {
85 | resolvedConfig = fs.readFileSync(packageBabelRc)
86 | } else if (fs.existsSync(repoBabelRcJs)) {
87 | resolvedConfig = fs.readFileSync(repoBabelRc)
88 | } else if (fs.existsSync(packageBabelRc)) {
89 | resolvedConfig = fs.readJsonSync(packageBabelRc)
90 | } else if (fs.existsSync(repoBabelRc)) {
91 | resolvedConfig = fs.readJsonSync(repoBabelRc)
92 | } else {
93 | TerminalUtils.errorPkg(
94 | pkg,
95 | 'No babel config supplied and no .babelrc found in package or root of monorepo.',
96 | )
97 | }
98 | }
99 |
100 | if (!resolvedConfig) {
101 | TerminalUtils.errorPkg(pkg, 'Unexpected state')
102 | }
103 |
104 | return {
105 | // $FlowFixMe
106 | config: resolvedConfig,
107 | inputs: inputs || ['**/*.js', '**/*.jsx', '!__tests__', '!test.js'],
108 | }
109 | }
110 |
111 | const sanitisedOptions = sanitiseOptions(options)
112 |
113 | const patterns = sanitisedOptions.inputs.concat([
114 | '!node_modules/**/*',
115 | `!${path.basename(pkg.paths.packageBuildOutput)}/**/*`,
116 | ])
117 |
118 | // :: string -> Array
119 | const getJsFilePaths = () =>
120 | globby(patterns, {
121 | cwd: pkg.paths.packageSrc,
122 | })
123 |
124 | const transpileFile = filePath => {
125 | const writeTranspiledFile = result => {
126 | const outFile = path.resolve(pkg.paths.packageBuildOutput, filePath)
127 | ensureParentDirectoryExists(outFile)
128 | fs.writeFileSync(outFile, result.code, { encoding: 'utf8' })
129 | fs.writeFileSync(`${outFile}.map`, JSON.stringify(result.map), {
130 | encoding: 'utf8',
131 | })
132 | }
133 | const module = path.resolve(pkg.paths.packageSrc, filePath)
134 | return transformFile(module, sanitisedOptions.config).then(
135 | writeTranspiledFile,
136 | )
137 | }
138 |
139 | const limit = pLimit(maxConcurrentTranspiles)
140 | const queueTranspile = filePath => limit(() => transpileFile(filePath))
141 | const filePaths = await getJsFilePaths()
142 | return Promise.all(R.map(queueTranspile, filePaths))
143 | } catch (err) {
144 | TerminalUtils.errorPkg(pkg, 'Unexpected error', err)
145 | }
146 | },
147 | clean: (pkg: Package) =>
148 | new Promise(resolve => {
149 | if (fs.pathExistsSync(pkg.paths.packageBuildOutput)) {
150 | fs.removeSync(pkg.paths.packageBuildOutput)
151 | }
152 | resolve()
153 | }),
154 | deploy: (pkg: Package) => {
155 | TerminalUtils.errorPkg(pkg, '"deploy" not supported by "babel" plugin')
156 | },
157 | develop: (pkg: Package) => {
158 | TerminalUtils.errorPkg(pkg, '"develop" not supported by "babel" plugin')
159 | },
160 | }
161 |
162 | module.exports = babelBuildPlugin
163 |
--------------------------------------------------------------------------------
/packages/lib/src/config/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | /* eslint-disable global-require */
4 | /* eslint-disable import/no-dynamic-require */
5 | /* eslint-disable no-console */
6 | /* eslint-disable no-param-reassign */
7 |
8 | import type {
9 | Config,
10 | LernaColaConfig,
11 | LernaColaPackageConfig,
12 | Package,
13 | } from '../types'
14 |
15 | const path = require('path')
16 | const fs = require('fs-extra')
17 | const ObjectUtils = require('../objects')
18 | const getPackageRoots = require('./get-package-roots')
19 | const ColorUtils = require('../colors')
20 | const {
21 | getAllDependants,
22 | getAllDependencies,
23 | getDependencies,
24 | getDependants,
25 | getPlugin,
26 | orderByDependencies,
27 | } = require('./utils')
28 |
29 | let cache
30 |
31 | const defaultConfig = {
32 | commandHooks: {
33 | clean: {
34 | pre: () => Promise.resolve(),
35 | post: () => Promise.resolve(),
36 | },
37 | build: {
38 | pre: () => Promise.resolve(),
39 | post: () => Promise.resolve(),
40 | },
41 | develop: {
42 | pre: () => Promise.resolve(),
43 | post: () => Promise.resolve(),
44 | },
45 | deploy: {
46 | pre: () => Promise.resolve(),
47 | post: () => Promise.resolve(),
48 | },
49 | },
50 | packageSources: undefined,
51 | packages: {},
52 | }
53 |
54 | const defaultPackageConfig = {
55 | srcDir: 'src',
56 | entryFile: 'index.js',
57 | outputDir: 'build',
58 | disableSrcWatching: false,
59 | buildPlugin: undefined,
60 | developPlugin: undefined,
61 | deployPlugin: undefined,
62 | }
63 |
64 | const jsConfigPath = path.join(process.cwd(), './lerna-cola.js')
65 | const jsonConfigPath = path.join(process.cwd(), './lerna-cola.json')
66 |
67 | const config = () => {
68 | if (cache) {
69 | return cache
70 | }
71 |
72 | if (!fs.existsSync(jsonConfigPath) && !fs.existsSync(jsConfigPath)) {
73 | throw new Error(
74 | `No lerna-cola config was found. Please create either a lerna-cola.js or a lerna-cola.json configuration file`,
75 | )
76 | }
77 |
78 | const lernaColaConfig: LernaColaConfig = ObjectUtils.mergeDeep(
79 | defaultConfig,
80 | fs.existsSync(jsonConfigPath)
81 | ? // $FlowFixMe
82 | require(jsonConfigPath)
83 | : // $FlowFixMe
84 | require(jsConfigPath),
85 | )
86 |
87 | let packages: Array = getPackageRoots(
88 | lernaColaConfig.packageSources,
89 | ).map(packagePath => {
90 | const packageJsonPath = path.join(packagePath, './package.json')
91 | // $FlowFixMe
92 | const packageJson = require(packageJsonPath)
93 | const packageConfig: LernaColaPackageConfig = ObjectUtils.mergeDeep(
94 | defaultPackageConfig,
95 | lernaColaConfig.packages[packageJson.name] || {},
96 | )
97 | const plugins = {
98 | cleanPlugin: getPlugin(
99 | packageJson.name,
100 | packageConfig.cleanPlugin,
101 | 'cleanPlugin',
102 | ),
103 | buildPlugin: getPlugin(
104 | packageJson.name,
105 | packageConfig.buildPlugin,
106 | 'buildPlugin',
107 | ),
108 | developPlugin: getPlugin(
109 | packageJson.name,
110 | packageConfig.developPlugin,
111 | 'developPlugin',
112 | ),
113 | deployPlugin: getPlugin(
114 | packageJson.name,
115 | packageConfig.deployPlugin,
116 | 'deployPlugin',
117 | ),
118 | }
119 |
120 | // If we have a buildPlugin but no cleanPlugin then we will assign the
121 | // default cleanPlugin that will ensure the build output directory
122 | // gets cleaned between builds.
123 | if (!plugins.cleanPlugin && plugins.buildPlugin) {
124 | plugins.cleanPlugin = getPlugin(
125 | packageJson.name,
126 | 'plugin-clean-build',
127 | 'cleanPlugin',
128 | )
129 | }
130 |
131 | // If we have a build plugin then we should be building our library
132 | // if it changes
133 | if (!plugins.developPlugin && plugins.buildPlugin) {
134 | plugins.developPlugin = getPlugin(
135 | packageJson.name,
136 | 'plugin-develop-build',
137 | 'developPlugin',
138 | )
139 | }
140 |
141 | return {
142 | name: packageJson.name,
143 | color: ColorUtils.nextColorPair(),
144 | config: packageConfig,
145 | disableSrcWatching: packageConfig.disableSrcWatching,
146 | allDependants: [],
147 | allDependencies: [],
148 | dependants: [],
149 | dependencies: [],
150 | devDependencies: [],
151 | packageJson,
152 | paths: {
153 | monoRepoRoot: process.cwd(),
154 | monoRepoRootNodeModules: path.resolve(process.cwd(), './node_modules'),
155 | packageBuildOutput: path.resolve(packagePath, packageConfig.outputDir),
156 | packageSrc: path.resolve(packagePath, packageConfig.srcDir),
157 | packageEntryFile: path.resolve(
158 | packagePath,
159 | packageConfig.srcDir,
160 | packageConfig.entryFile,
161 | ),
162 | packageJson: packageJsonPath,
163 | packageLockJson: path.resolve(packagePath, './package-lock.json'),
164 | packageNodeModules: path.resolve(packagePath, './node_modules'),
165 | packageRoot: packagePath,
166 | packageWebpackCache: path.resolve(packagePath, './.webpackcache'),
167 | },
168 | // $FlowFixMe
169 | plugins,
170 | version: packageJson.version || '0.0.0',
171 | }
172 | })
173 |
174 | packages.forEach(pkg => {
175 | pkg.dependencies = getDependencies(pkg, packages, 'dependencies')
176 | pkg.devDependencies = getDependencies(pkg, packages, 'devDependencies')
177 | pkg.dependants = getDependants(pkg, packages)
178 | })
179 |
180 | // Packages ordered based on their dependencies (via link or soft dep)
181 | // based order, which mean building them in order should be safe.
182 | packages = orderByDependencies(packages)
183 |
184 | packages.forEach(pkg => {
185 | // We get the full dependant list for this package traversing through
186 | // each dependant. This is so we can know which packages will all be
187 | // affect (either directly, or indirectly) when this package changes
188 | pkg.allDependants = getAllDependants(pkg, packages)
189 | pkg.allDependencies = getAllDependencies(pkg, packages)
190 | })
191 |
192 | // Ensure there are no references to unknown packages
193 | Object.keys(lernaColaConfig.packages).forEach(packageName => {
194 | if (packages.find(x => x.name === packageName) == null) {
195 | throw new Error(
196 | `There is a lerna-cola configuration for "${packageName}", however, this package could not be resolved via the packageSources configuration.`,
197 | )
198 | }
199 | })
200 |
201 | const result: Config = {
202 | // $FlowFixMe
203 | commandHooks: lernaColaConfig.commandHooks,
204 | packages,
205 | packageMap: packages.reduce(
206 | (acc, pkg) => Object.assign(acc, { [pkg.name]: pkg }),
207 | {},
208 | ),
209 | terminalLabelMinLength: Math.max(
210 | ...['lerna-cola'.length, ...packages.map(x => x.name.length)],
211 | ),
212 | }
213 |
214 | cache = result
215 |
216 | return result
217 | }
218 |
219 | module.exports = config
220 |
--------------------------------------------------------------------------------
/packages/cli/src/development-service/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | /* eslint-disable no-use-before-define */
4 |
5 | import type {
6 | RunType,
7 | Package,
8 | PackageConductor,
9 | PackageWatcher,
10 | } from '@lerna-cola/lib/build/types'
11 |
12 | const { config, TerminalUtils, PackageUtils } = require('@lerna-cola/lib')
13 | const createPackageConductor = require('./create-package-conductor')
14 | const createPackageWatcher = require('./create-package-watcher')
15 | const gracefulShutdownManager = require('./graceful-shutdown-manager')
16 |
17 | type QueueItem = {
18 | package: Package,
19 | runType: RunType,
20 | changedDependency?: Package,
21 | }
22 |
23 | type Options = {|
24 | packagesFilter?: Array,
25 | selectPackages?: boolean,
26 | exact?: boolean,
27 | |}
28 |
29 | module.exports = async function developmentService({
30 | packagesFilter,
31 | selectPackages,
32 | exact,
33 | }: Options) {
34 | // Keep this message up here so it always comes before any others
35 | TerminalUtils.info('Press CTRL + C to exit')
36 |
37 | const preDevelopHook = config().commandHooks.develop.pre
38 |
39 | TerminalUtils.info('Running the pre develop hook')
40 | await preDevelopHook()
41 |
42 | let packages = PackageUtils.resolvePackages(packagesFilter, {
43 | strict: exact,
44 | })
45 |
46 | if (selectPackages) {
47 | // Ask which packages to develop if the select option was enabled
48 | const selectedPackages = await TerminalUtils.multiSelect(
49 | 'Which packages would you like to develop?',
50 | {
51 | choices: packages.map(x => ({
52 | value: x.name,
53 | text: `${x.name} (${x.version})`,
54 | })),
55 | },
56 | )
57 | packages = PackageUtils.resolvePackages(selectedPackages, { strict: exact })
58 | }
59 |
60 | const packageMap = packages.reduce((acc, cur) =>
61 | Object.assign(
62 | acc,
63 | {
64 | [cur.name]: cur,
65 | },
66 | {},
67 | ),
68 | )
69 |
70 | TerminalUtils.verbose(`Developing packages:`)
71 | TerminalUtils.verbose(packages.map(x => x.name))
72 |
73 | // Firstly clean build for all packages
74 | await PackageUtils.cleanPackages(packages)
75 |
76 | // Represents the current package being built
77 | let currentlyProcessing = null
78 |
79 | // Represents the build backlog queue. FIFO.
80 | // We will queue all the packages for the first run
81 | let toProcessQueue: Array = packages.map(pkg => ({
82 | package: pkg,
83 | runType: 'FIRST_RUN',
84 | }))
85 |
86 | const packageHasDependant = (dependant: Package, pkg: Package): boolean =>
87 | pkg.dependants.includes(dependant.name)
88 |
89 | const getPackageDependants = (pkg: Package): Array =>
90 | pkg.dependants.map(name => packageMap[name]).filter(x => x != null)
91 |
92 | const onChange = (pkg: Package) => (): void => {
93 | queuePackageForProcessing(pkg, 'SELF_CHANGED')
94 | // If no active build running then we will call off to run next item in
95 | // the queue.
96 | if (!currentlyProcessing) {
97 | processNextInTheQueue()
98 | }
99 | }
100 |
101 | /**
102 | * Package watches are responsible for watching the source of the packages
103 | * and then notifying of changes so that packages can be queued to be
104 | * processed.
105 | */
106 | const packageWatchers: {
107 | [key: string]: PackageWatcher,
108 | } = packages.reduce(
109 | (acc, pkg) =>
110 | Object.assign(acc, {
111 | [pkg.name]: createPackageWatcher(onChange(pkg), pkg),
112 | }),
113 | {},
114 | )
115 |
116 | /**
117 | * Package develop conductors are responsible for calling the respective
118 | * develop plugins for packages when changes occur. They manage any
119 | * child processes that are spawned, ensuring there is only a single
120 | * child process per package.
121 | */
122 | const packageDevelopConductors: {
123 | [key: string]: PackageConductor,
124 | } = packages.reduce(
125 | (acc, pkg) =>
126 | Object.assign(acc, {
127 | [pkg.name]: createPackageConductor(pkg, packageWatchers[pkg.name]),
128 | }),
129 | {},
130 | )
131 |
132 | /**
133 | * Queues an item of processing. This will be sensitive to items already
134 | * in the queue.
135 | *
136 | * @param {*} packageToQueue
137 | * The package to queue
138 | * @param {*} runType
139 | * The change that cause the queueing of the item.
140 | * @param {*} changedDependency
141 | * If it was caused by a dependency changing then this is the dependency
142 | * that changed.
143 | */
144 | const queuePackageForProcessing = (
145 | packageToQueue: Package,
146 | runType: RunType,
147 | changedDependency?: Package,
148 | ): void => {
149 | TerminalUtils.verbose(`Attempting to queue ${packageToQueue.name}`)
150 | if (
151 | currentlyProcessing !== null &&
152 | packageHasDependant(packageToQueue, currentlyProcessing)
153 | ) {
154 | // Do nothing as the package currently being built will result in this
155 | // package being built via it's dependancy chain.
156 | TerminalUtils.verbosePkg(
157 | packageToQueue,
158 | `Skipping development service queue as represented by a queued package`,
159 | )
160 | } else if (
161 | toProcessQueue.some(queueItem =>
162 | packageHasDependant(packageToQueue, queueItem.package),
163 | )
164 | ) {
165 | // Do nothing as one of the queued packagesToDevelop will result in this package
166 | // getting built via it's dependancy chain.
167 | TerminalUtils.verbosePkg(
168 | packageToQueue,
169 | `Skipping development service queue as represented by a queued package`,
170 | )
171 | } else {
172 | // Queue the package for building.
173 | TerminalUtils.verbose(`Queuing ${packageToQueue.name}`)
174 | const packageDependants = getPackageDependants(packageToQueue)
175 | // We'll assign the package to the build queue, removing any of the
176 | // package's dependants as they will be represented by the package being
177 | // added.
178 | toProcessQueue = toProcessQueue
179 | .filter(
180 | queueItem =>
181 | !packageDependants.find(pkg => pkg.name === queueItem.package.name),
182 | )
183 | .concat([{ package: packageToQueue, runType, changedDependency }])
184 | TerminalUtils.verbose(
185 | `Queue: [${toProcessQueue.map(x => x.package.name).join(',')}]`,
186 | )
187 | }
188 | }
189 |
190 | /**
191 | * Processes a package in the queue, and if successfully processed it will
192 | * queue the packages dependencies.
193 | */
194 | const processQueueItem = (queueItem: QueueItem): void => {
195 | currentlyProcessing = queueItem.package
196 | const packageDevelopConductor =
197 | packageDevelopConductors[queueItem.package.name]
198 | if (!packageDevelopConductor) {
199 | TerminalUtils.error(
200 | `Did not run develop process for ${
201 | queueItem.package.name
202 | } as there is no package develop conductor registered for it`,
203 | )
204 | return
205 | }
206 | packageDevelopConductor
207 | // Kick off the develop of the package
208 | .run(queueItem.runType, queueItem.changedDependency)
209 | // Develop kickstart succeeded 🎉
210 | .then(() => ({ success: true }))
211 | // Or, failed 😭
212 | .catch(err => {
213 | TerminalUtils.errorPkg(
214 | queueItem.package,
215 | `An error occurred whilst trying to start development instance.`,
216 | err,
217 | )
218 | return { success: false }
219 | })
220 | // Finally...
221 | .then(({ success }) => {
222 | // Ensure any current is removed
223 | currentlyProcessing = null
224 |
225 | // If the build succeeded we will queue dependants
226 | if (success) {
227 | TerminalUtils.verbosePkg(
228 | queueItem.package,
229 | `Develop process ran successfully, queueing dependants...`,
230 | )
231 | const packageDependants = getPackageDependants(
232 | queueItem.package,
233 | ).filter(
234 | dependant =>
235 | !toProcessQueue.some(
236 | queued => queued.package.name === dependant.name,
237 | ),
238 | )
239 | packageDependants.forEach(dep =>
240 | queuePackageForProcessing(
241 | dep,
242 | 'DEPENDENCY_CHANGED',
243 | queueItem.package,
244 | ),
245 | )
246 | }
247 |
248 | // We will call off the next item to be processe even if a failure
249 | // occurred. This is because any items in the queue likely are not
250 | // dependants of this failed package (due to the logic contained within
251 | // the queueing function).
252 | processNextInTheQueue()
253 | })
254 | }
255 |
256 | const processNextInTheQueue = (): void => {
257 | if (currentlyProcessing) {
258 | TerminalUtils.error(
259 | `Tried to process the next Package in the queue even though there is a Package being processed: ${
260 | currentlyProcessing.name
261 | }`,
262 | )
263 | return
264 | }
265 | TerminalUtils.verbose('Popping the queue')
266 | if (toProcessQueue.length > 0) {
267 | // Pop the queue.
268 | const nextToProcess = toProcessQueue[0]
269 | toProcessQueue = toProcessQueue.slice(1)
270 | TerminalUtils.verbose(`Popped ${nextToProcess.package.name}`)
271 | processQueueItem(nextToProcess)
272 | } else {
273 | TerminalUtils.verbose('Nothing to pop')
274 | }
275 | }
276 |
277 | // READY, SET...
278 | Object.keys(packageWatchers).forEach(packageName =>
279 | packageWatchers[packageName].start(),
280 | )
281 |
282 | // GO! 🚀
283 | processNextInTheQueue()
284 |
285 | // Ensure graceful shutting down
286 | gracefulShutdownManager(packageDevelopConductors, packageWatchers)
287 |
288 | return new Promise(() => {
289 | // Never resolve as we are going to wait to CTRL + C to exit.
290 | })
291 | }
292 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | > **Archived / Deprecated**
4 | >
5 | > I no longer need to use this project as the awesome team Zeit give me everything I need with their `now dev` tool and cloud platform. So happy to sunset this project as maintaining this type of project is super hard work. 😀
6 |
7 | ---
8 |
9 | # Lerna Cola 🥤
10 |
11 | Superpowers for your [Lerna](https://lernajs.io/) monorepos.
12 |
13 | Clean, build, develop, and deploy your packages utilising a rich plugin ecosystem.
14 |
15 | ## TOC
16 |
17 | - [Introduction](#introduction)
18 | - [Requirements](#requirements)
19 | - [(Not Really) Requirements](#not-really-requirements)
20 | - [Getting Started](#getting-started)
21 | - [Sample Application](#sample-application)
22 | - [Video Walkthrough](#video-walkthrough)
23 | - [Configuration](#configuration)
24 | - [Example Configuration](#example-configuration)
25 | - [Configuration Schema](#configuration-schema)
26 | - [CLI Commands](#cli-commands)
27 | - [clean](#clean)
28 | - [build](#build)
29 | - [develop](#develop)
30 | - [deploy](#deploy)
31 | - [Plugins](#plugins)
32 | - [Core Plugins](#core-plugins)
33 | - [`plugin-clean-build`](#plugin-clean-build)
34 | - [`plugin-develop-server`](#plugin-develop-server)
35 | - [`plugin-script`](#plugin-script)
36 | - [Official Plugins](#official-plugins)
37 | - [`@lerna-cola/plugin-build-babel`](#lerna-cola-plugin-build-babel)
38 | - [`@lerna-cola/plugin-build-flow`](#lerna-cola-plugin-build-flow)
39 | - [`@lerna-cola/plugin-deploy-now`](#lerna-cola-plugin-deploy-now)
40 | - [3rd Party Plugins](#3rd-party-plugins)
41 | - [Plugin Development](#plugin-development)
42 | - [Clean Plugin](#clean-plugin)
43 | - [Build Plugin](#build-plugin)
44 | - [Develop Plugin](#develop-plugin)
45 | - [Deploy Plugin](#deploy-plugin)
46 | - [Schemas](#schemas)
47 | - [`Package` Schema](#package-schema)
48 | - [`Config` Schema](#package-schema)
49 |
50 | ## Introduction
51 |
52 | [Lerna](https://lernajs.io/) makes it crazy easy to manage cross package dependencies and provides sane methods to version them. It takes away the fear of creating and maintaining a wide set of packages, allowing us to fully embrace the module ethos by creating packages with isolated responsibilities.
53 |
54 | Lerna Cola wants to build on top of these core principles by providing the following additional features:
55 |
56 | - Easily **enrich your packages** with a **compilation/transpilation/bundling** step (babel/flow/typescript/reasonml/webpack/parcel/etc/etc/etc).
57 | - Take away the fear of building a wide set of **microservices/lambda packages** by providing a **rich development service** that handles **hot/auto reloading** of your packages. This allows for a **fluid development experience** reminiscent of old school single package/repository based development.
58 | - **Deploy** your packages with a simple command to a **cloud provider** of your choice.
59 |
60 | You access the features via one of the 4 CLI commands that Lerna Cola provides: [`clean`](#clean), [`build`](#build), [`develop`](#develop), and [`deploy`](#deploy).
61 |
62 | The commands utilise a rich plugin eco-system, allowing for 3rd party contributions.
63 |
64 | Lift your build, development and deployment to the root of your monorepo, keep your packages clean, and utilise the full benefits of a monorepo structure.
65 |
66 | ## Requirements
67 |
68 | - **Node >= 8**
69 |
70 | Version 8 was LTS at the time of writing this so a decision was made to run with it.
71 |
72 | ## (Not Really) Requirements
73 |
74 | - **[Lerna](https://lernajs.io/)**
75 |
76 | To tell you the truth, we don't strictly require that you use Lerna. You could very well use straight up Yarn workspaces or any other monorepo enabling tool, but in our opinion you would be missing out on some very cool features that Lerna provides.
77 |
78 | ## Getting started
79 |
80 | **First, you need to install the cli**
81 |
82 | ```bash
83 | yarn add @lerna-cola/cli -DW
84 | ```
85 |
86 | _or, via NPM_:
87 |
88 | ```bash
89 | npm i @lerna-cola/cli -D
90 | ```
91 |
92 | **Secondly, you'll need a `lerna-cola.js` [configuration](#configuration) file**
93 |
94 | You'll need to reference the [Configuration](#configuration) on how to do this.
95 |
96 | **Lastly, fire up the lern-cola CLI**
97 |
98 | You can run the help to see the help commands
99 |
100 | ```bash
101 | yarn lerna-cola help
102 | ```
103 |
104 | _or, via NPX_
105 |
106 | ```
107 | npx lerna-cola help
108 | ```
109 |
110 | ## Sample Application
111 |
112 | We definitely recommend that you read the all of the documentation first in order to gain a better understanding of Lerna Cola, however, we want to highlight early on that a sample application is maintained here:
113 |
114 | - https://github.com/ctrlplusb/lerna-cola-sample
115 |
116 | This provides a great way for you to quickly clone and run a non-trivial project in order to assess the benefits that Lerna Cola could bring to your monorepos.
117 |
118 | ## Video Walkthrough
119 |
120 | > Coming soon
121 |
122 | ## Configuration
123 |
124 | To use Lerna Cola you will need to create a configuration file named either `lerna-cola.json` or `lerna-cola.js` within the root of your monorepo.
125 |
126 | > Note: When creating a `.js` file ensure that you export the configuration object via `module.exports`.
127 |
128 | ### Example Configuration
129 |
130 | Before we describe the configuration schema, it may be helpful to review a "typical" configuration:
131 |
132 | ```javascript
133 | module.exports = {
134 | packages: {
135 | // The following two packages are microservices where we are using the babel
136 | // plugin to transpile them to support our current node version, and the
137 | // server develop plugin which will take care of executing and restarting
138 | // our microservices for any changes up their dependency trees.
139 |
140 | 'microservice-one': {
141 | buildPlugin: '@lerna-cola/plugin-build-babel',
142 | developPlugin: 'plugin-develop-server',
143 | },
144 |
145 | 'microservice-two': {
146 | buildPlugin: '@lerna-cola/plugin-build-babel',
147 | developPlugin: 'plugin-develop-server',
148 | },
149 |
150 | // The following package is a "Create React App" package which comes with
151 | // it's own build and develop (start) scripts. We will therefore use the
152 | // Lerna Cola script plugin to delegate to the CRA scripts.
153 |
154 | 'my-ui': {
155 | buildPlugin: {
156 | name: 'plugin-script',
157 | options: {
158 | scriptName: 'build',
159 | },
160 | },
161 | developPlugin: {
162 | name: 'plugin-script',
163 | options: {
164 | scriptName: 'start',
165 | },
166 | },
167 | },
168 | },
169 | }
170 | ```
171 |
172 | Within this example we are providing a Lerna Cola configuration for 3 of our packages within our monorepo. We need not provide a configuration for every package within our monorepo; only the ones that we wish to execute Lerna Cola plugins against. Lerna Cola will still be aware of the other packages within our repo, for example providing hot reloading on our servers/apps when a change occurs on our shared utils package.
173 |
174 | In our example we have two microservices that will be Babel transpiled by the [`build`](#build) command, and will be treated as server process (with execution and hot reloading) by the [`develop`](#develop) command. The third package configuration utilities a special [Create React App](https://TODO) plugin that contains configuration for both the [`build`](#build) and [`develop`](#develop) commands.
175 |
176 | This is a very basic example, however, it is illustrative of how quickly you can provide a centralised and coordinated configuration for your monorepo packages. Plugins can of course be customised via options. We recommend you read the [plugin](#plugins) docs for detailed information on them.
177 |
178 | ### Configuration Schema
179 |
180 | The configuration is an Object/JSON structure that supports the following schema:
181 |
182 | - `commandHooks` (_Object_, **optional**)
183 |
184 | A set of hooks to allow you to execute a function pre/post each command. You can specify a set of hooks for each of the commands:
185 |
186 | - clean
187 | - build
188 | - develop
189 | - deploy
190 |
191 | Each of them support the following configuration _Object_:
192 |
193 | - `pre`: (_Function_, **optional**)
194 |
195 | Runs prior to the specifed command. Can return a `Promise`, which will be waited on to resolve before proceeding.
196 |
197 | - `post`: (_Function_, **optional**)
198 |
199 | Runs after to the specifed command completes/fails. Can return a `Promise`, which will be waited on to resolve before proceeding.
200 |
201 | Example:
202 |
203 | ```javascript
204 | // lerna-cola.js
205 | module.exports = {
206 | commandHooks: {
207 | build: {
208 | pre: () => Promise.resolve(),
209 | post: () => Promise.resolve(),
210 | },
211 | },
212 | }
213 | ```
214 |
215 | - `packages` (_Object_, **_optional_**)
216 |
217 | An object where each key is the name of a package within your repository (matching the name of the package within the package's `package.json` file). The value against each key is considered a package configuration object, supporting the following schema:
218 |
219 | - `buildPlugin` (_string_ || _Object_, **optional**)
220 |
221 | The plugin to use when building the package. Please see the [Plugins](#plugins) documentation for more information.
222 |
223 | - `developPlugin` (_string_ || _Object_, **optional**)
224 |
225 | The plugin to use when developing the package. Please see the [Plugins](#plugins) documentation for more information.
226 |
227 | - `deployPlugin` (_string_ || _Object_, **optional**)
228 |
229 | The plugin to use when deploying the package. Please see the [Plugins](#plugins) documentation for more information.
230 |
231 | - `srcDir` (_string_, **optional**, **default**: 'src')
232 |
233 | The directory within your package containing the source.
234 |
235 | - `entryFile` (_string_, **optional**, **default**: 'index.js')
236 |
237 | The file within your `srcDir` that should be considered the "entry" file for your package.
238 |
239 | - `outputDir` (_string_, **optional**, **default**: 'build')
240 |
241 | The directory which should be targetted for build output by the build plugins. Also used by the clean plugin to know which directory should be removed.
242 |
243 | - `disableSrcWatching` (_boolean_, **optional**, **default**: false)
244 |
245 | Use this to disable watching of the source when running the develop command. This can be useful if you don't care about tracking changes for a specific package within your repository, or you have a seperate process that will already track source changes for the respective package (e.g. the `start` script within a Create React App project).
246 |
247 | - `packageSources` (_Array<string>_, **_optional_**, **default**: _see below_)
248 |
249 | An array of globs, paths where your packages are contained. By default it uses the same configuration as Lerna, i.e.:
250 |
251 | ```json
252 | "packageSources": [
253 | "packages/*"
254 | ]
255 | ```
256 |
257 | ## CLI Commands
258 |
259 | Below is an brief description of each command that the CLI provides. You can request for help via the CLI for all of the commands like so:
260 |
261 | ```bash
262 | lerna-cola help
263 | ```
264 |
265 | Or for an individual command like so:
266 |
267 | ```bash
268 | lerna-cola build help
269 | ```
270 |
271 | When executing commands all of the environment variables that are currently available (i.e. `process.env`) will be passed into the respective processes that are spawned for each package.
272 |
273 | ### clean
274 |
275 | Clean the output from the the [`build`](#build) command.
276 |
277 | By default the [`plugin-clean-build`](#plugin-clean-build) plugin is used, however, you can customise this within your [configuration](#configuration).
278 |
279 | ### build
280 |
281 | Take advantage of one of the wide array of plugins to babel/flow/typescript/reasonml/etc/etc/etc transpile/build your packages with the ability to easily share and manage configuration at the root of your monorepo.
282 |
283 | When executed it will run all of the configured plugins for all of your packages that have a `buildPlugin` configured within the `lerna-cola.json` [configuration](#configuration) file. The package build order is based upon on a topological sort against the dependency tree of your packages within the monorepo (i.e. they build in the correct order).
284 |
285 | ### develop
286 |
287 | Run a full auto-reloading development environment in which any changes to one package cascade through your package dependency tree. No more need to manually rebuild/restart servers during development allow for a blazingly hot development experience.
288 |
289 | All of your packages will be watched (even the ones without any explicit configuration within your [configuration](#configuration)), with any changes being propagated through the dependency tree subsequently executing any "develop" plugins that are configured against your projects.
290 |
291 | > Note: If you provide a "build" plugin against on your packages, but no explicit "develop" plugin, then the system will automatically execute the "build" plugin when handling changes.
292 |
293 | All of the logs/output from your packages will be "merged" and printed within the console window where you ran the `develop` command. The console output contains a uniquely colored column to the left allowing you to easily identify the output for each respective package.
294 |
295 | ### deploy
296 |
297 | Deploy your apps with a simple one line command to a cloud provider supported by the plugin system.
298 |
299 | When executing this command your packages will be built in topological sort order (based on their dependency tree between each other) and then will subsequently be deployed via their configured plugins.
300 |
301 | ## Plugins
302 |
303 | Lerna Cola is powered by a powerful plugin system that allows the build, develop, and deploy commands to support a multitude of targets. It is very easy to build your own plugin should you desire to so - please see the ["Plugin Development"](#plugin-development) section for more information.
304 |
305 | Plugins are split by "core" plugins, which are bundled with the main `@lerna-cola/cli` package, and "package" plugins which could either be official Lerna Cola packages, 3rd party packages, or private packages of your own making.
306 |
307 | You define plugins against each of your package configurations within your Lerna Cola [configuration](#configuration).
308 |
309 | Plugins allow two forms of assignment.
310 |
311 | **Form 1 - specify the plugin by name**
312 |
313 | ```javascript
314 | // lerna-cola.js
315 | module.exports = {
316 | packages: {
317 | 'my-package': {
318 | buildPlugin: '@lerna-cola/plugin-build-babel',
319 | },
320 | },
321 | }
322 | ```
323 |
324 | **Form 2 - provide options to the plugin**
325 |
326 | ```javascript
327 | // lerna-cola.js
328 | module.exports = {
329 | packages: {
330 | 'my-package': {
331 | buildPlugin: {
332 | name: '@lerna-cola/plugin-build-babel',
333 | options: {
334 | config: {
335 | presets: ['babel-preset-env'],
336 | },
337 | },
338 | },
339 | },
340 | },
341 | }
342 | ```
343 |
344 | You can pass down any arbitrary set of options, which will be made available to the respective plugin. Please refer to the documentation for each plugin in terms of what options it supports.
345 |
346 | ### Core Plugins
347 |
348 | Below are the "core" plugins which will be immediately available when you add Lerna Cola to your repository.
349 |
350 | #### `plugin-clean-build`
351 |
352 | A [clean](#clean) command plugin.
353 |
354 | This plugin will be automatically assigned to any package with a `buildPlugin` defined.
355 |
356 | When executed it will remove the output directory targetted by your respective build plugin.
357 |
358 | #### `plugin-develop-server`
359 |
360 | A [develop](#develop) command plugin.
361 |
362 | #### `plugin-script`
363 |
364 | This is a special plugin that can be configured against any of your plugins for a package. i.e. `cleanPlugin`, `buildPlugin`, `developPlugin`, and `deployPlugin`.
365 |
366 | It allows you to delegate to a script defined within the `package.json` of your targetted package.
367 |
368 | This can be especially useful for packages that come with their own sets of scripts (e.g. A Create React App package).
369 |
370 | **Options**
371 |
372 | This plugin supports the following options:
373 |
374 | - `scriptName` (_string_, **required**)
375 |
376 | The name of the script to run within your target package.
377 |
378 | - `runForEveryChange` (_boolean_, **default**: false)
379 |
380 | Only used when assigned against the `developPlugin`. Setting this value to `true` will ensure that the your script will be executed any time a change is registered within your packages' source or against one of its dependencies. If your script spawns a long run child process (for example a Create React App server), then an existing running instance will be destroyed before the script is ran again.
381 |
382 | **Example**
383 |
384 | ```javascript
385 | // lerna-cola.js
386 | module.exports = {
387 | packages: {
388 | 'my-create-react-app': {
389 | buildPlugin: {
390 | name: 'script',
391 | options: {
392 | scriptName: 'build',
393 | },
394 | },
395 | },
396 | },
397 | }
398 | ```
399 |
400 | ### Official Plugins
401 |
402 | #### `@lerna-cola/plugin-build-babel`
403 |
404 | A [build](#build) command plugin.
405 |
406 | This plugin will transpile your package's `srcDir` (see the [configuration](#configuration)) using [Babel](https://babeljs.io), outputing the results into your packages `outputDir` (see the [configuration](#configuration)).
407 |
408 | By default it will use a `.babelrc` found within your packages root directory, and if one is not found then it will look for a `.babelrc` within the root of your monorepo. You can alternatively provide a babel configuration via the plugin's options.
409 |
410 | **Options**
411 |
412 | This plugin supports the following options:
413 |
414 | - `config` (_string_ || _Object_, **optional**)
415 |
416 | The [babel configuration](https://babeljs.io/docs/en/api.md) to use for the transpilation.
417 |
418 | This can be one of two things:
419 |
420 | - The name of a package where the main export is a babel configuration object.
421 | - An object containing the babel configuration.
422 |
423 | > Note: Lerna Cola ships a simple babel config package which you could use. It is called `@lerna-cola/babel-config`
424 |
425 | We highly recommend that you enable sourcemaps output within your configuration to aid debugging.
426 |
427 | **Example**
428 |
429 | Specifying an inline config:
430 |
431 | ```javascript
432 | // lerna-cola.js
433 | module.exports = {
434 | packages: {
435 | 'my-lib': {
436 | buildPlugin: {
437 | name: '@lerna-cola/plugin-build-babel',
438 | options: {
439 | config: {
440 | presets: ['babel-preset-env'],
441 | },
442 | },
443 | },
444 | },
445 | },
446 | }
447 | ```
448 |
449 | Specifying a package containing the configuration:
450 |
451 | ```javascript
452 | // lerna-cola.js
453 | module.exports = {
454 | packages: {
455 | 'my-lib': {
456 | buildPlugin: {
457 | name: '@lerna-cola/plugin-build-babel',
458 | options: {
459 | config: 'my-babel-config-package',
460 | },
461 | },
462 | },
463 | },
464 | }
465 | ```
466 |
467 | #### `@lerna-cola/plugin-build-flow`
468 |
469 | A [build](#build) command plugin.
470 |
471 | This plugin will transpile your package's `srcDir` (see the [configuration](#configuration)), stripping your source of any flow annotations, and outputing the result (along with \*.flow) files into your package's `outputDir` (see the [configuration](#configuration)).
472 |
473 | **Options**
474 |
475 | This plugin supports the following options:
476 |
477 | - `inputs` (_Array<string>_ , **optional**, **default**: _see below_)
478 |
479 | An array of glob strings which should be used to match the files that will be processed.
480 |
481 | Defaults to the following:
482 |
483 | ```json
484 | ["**/*.js", "!__tests__", "!test.js"]
485 | ```
486 |
487 | **Example**
488 |
489 | ```javascript
490 | // lerna-cola.js
491 | module.exports = {
492 | packages: {
493 | 'my-lib': {
494 | buildPlugin: '@lerna-cola/plugin-build-flow',
495 | },
496 | },
497 | }
498 | ```
499 |
500 | #### `@lerna-cola/plugin-deploy-now`
501 |
502 | A [deploy](#deploy) command plugin.
503 |
504 | This plugin allows your package to be deployed to Zeit's [now](https://zeit.co/now) cloud service.
505 |
506 | For this plugin to work you need to have installed the `now` CLI, i.e.:
507 |
508 | ```bash
509 | npm i -g now
510 | ```
511 |
512 | You also need to ensure that you have logged into your `now` account:
513 |
514 | ```bash
515 | now login
516 | ```
517 |
518 | **Options**
519 |
520 | This plugin supports the following options:
521 |
522 | - `settings` (_Object_, **optional**)
523 |
524 | Any [settings](https://zeit.co/docs/features/configuration#settings) officially supported by now.
525 |
526 | We highly recommend you set the `alias` setting.
527 |
528 | Some defaults are automatically applied by the plugin, but you can override them:
529 |
530 | ```javascript
531 | {
532 | forwardNpm: true,
533 | public: false
534 | }
535 | ```
536 |
537 | - `disableRemovePrevious` (_boolean_, **optional**, **default**: false)
538 |
539 | If you provide an `alias` within the `settings` option then this plugin will by default remove any previous deployments that were deployed against the target alias. This avoids any unnecessary build up of old deployments, which can become tedious to manage.
540 |
541 | Set this to `true` to prevent the removal of prior deployments of an alias target.
542 |
543 | - `deployTimeoutMins` (_number_, **optional**, **default**: 15)
544 |
545 | The number of minutes to wait before timing out the deployment, subsequently stopping the deployment process with a failure indicated.
546 |
547 | - `passThroughEnvVars` (_Array<string>_ , **optional**)
548 |
549 | An array of strings, describing which environment variables (currently available on your `process.env`) should be passed into the now deployment.
550 |
551 | e.g.
552 |
553 | ```javascript
554 | // lerna-cola.js
555 | module.exports = {
556 | packages: {
557 | 'my-app': {
558 | deployPlugin: {
559 | name: '@lerna-cola/plugin-deploy-now',
560 | options: {
561 | alias: 'my-app.com',
562 | passThroughEnvVars: ['MY_DB_USERNAME', 'MY_DB_PASSWORD'],
563 | },
564 | },
565 | },
566 | },
567 | }
568 | ```
569 |
570 | **Example**
571 |
572 | ```javascript
573 | // lerna-cola.js
574 | module.exports = {
575 | packages: {
576 | 'my-app': {
577 | buildPlugin: {
578 | name: '@lerna-cola/plugin-deploy-now',
579 | options: {
580 | settings: {
581 | alias: 'my-app.com',
582 | sfo1: {
583 | sfo1: {
584 | min: 1,
585 | max: 5,
586 | },
587 | },
588 | },
589 | },
590 | },
591 | },
592 | },
593 | }
594 | ```
595 |
596 | #### `@lerna-cola/plugin-build-typescript`
597 |
598 | > Coming soon
599 |
600 | #### `@lerna-cola/plugin-build-reasonml`
601 |
602 | > Coming soon
603 |
604 | #### `@lerna-cola/plugin-build-parcel`
605 |
606 | > Coming soon
607 |
608 | #### `@lerna-cola/plugin-build-rollup`
609 |
610 | > Coming soon
611 |
612 | ### 3rd Party Plugins
613 |
614 | Hopefully we will have some soon. 🤞
615 |
616 | Please submit a PR to add yours here.
617 |
618 | ## Plugin Development
619 |
620 | Developing plugins is very easy. You can create a plugin to support one or more of the 4 commands that Lerna Cola provides.
621 |
622 | To create a plugin simply create new package where the "main" on its package.json file points to the plugin module.
623 |
624 | Your newly created package can adopt the behaviour of any of the plugin types as long as provides implementation for the interface specified for each plugin type. For example below is a template for a plugin package that supports all the command types (clean/build/develop/deploy):
625 |
626 | ```javascript
627 | // my-plugin.js
628 | module.exports = {
629 | name: 'my-plugin',
630 | clean: (pkg, options, args) => Promise.resolve('todo'),
631 | build: (pkg, options, args) => Promise.resolve('todo'),
632 | develop: (pkg, options, args) => Promise.resolve('todo'),
633 | deploy: (pkg, options, args) => Promise.resolve('todo'),
634 | }
635 | ```
636 |
637 | Looking at the above, you can see that a plugin is really just a simple object, containing functions that map to the command names. Each of the functions will receive a set of useful arguments, which will be described below, and need to return a `Promise` in order to indicate when they have completed.
638 |
639 | When running a command, the associated plugins will get executed for each package that they are configured against. The plugin functions will receive as an argument an object describing the package that it is running against, as well as the specific options that were defined when assigning the plugin to the package within the Lerna Cola [configuration](#configuration) file.
640 |
641 | Lets review the arguments that are provided to each of the plugin functions:
642 |
643 | - `pkg` (_Package_)
644 |
645 | The package for which the plugin command is getting executed against. This contains lots of really useful information about the package, such as it's dependency tree, src and build output paths etc. Please see the [Package Schema](#package-schema) for a full breakdown of what is available.
646 |
647 | - `options` (_Object_)
648 |
649 | Any options that were configured within the `lerna-cola.js` [configuration](#configuration) file will be provided here.
650 |
651 | For example:
652 |
653 | ```javascript
654 | // lerna-cola.js
655 | module.exports = {
656 | packages: {
657 | 'my-lib': {
658 | buildPlugin: {
659 | name: '@lerna-cola/plugin-build-babel',
660 | // 👇 this stuff here
661 | options: {
662 | config: 'my-babel-config-package',
663 | },
664 | },
665 | },
666 | },
667 | }
668 | ```
669 |
670 | - `args` (_Object_)
671 |
672 | This will contain any other useful utils/data. Mostly plugin specific. Please read the docs for each plugin below.
673 |
674 | ### Clean Plugin
675 |
676 | Requires the following interface be satisfied:
677 |
678 | ```javascript
679 | module.exports = {
680 | name: 'my-plugin',
681 | clean: (pkg, options, args) => Promise.resolve('todo'),
682 | }
683 | ```
684 |
685 | ### Build Plugin
686 |
687 | Requires the following interface be satisfied:
688 |
689 | ```javascript
690 | module.exports = {
691 | name: 'my-plugin',
692 | build: (pkg, options, args) => Promise.resolve('todo'),
693 | }
694 | ```
695 |
696 | ### Develop Plugin
697 |
698 | Requires the following interface be satisfied:
699 |
700 | ```javascript
701 | module.exports = {
702 | name: 'my-plugin',
703 | develop: (pkg, options, args) => Promise.resolve('todo'),
704 | }
705 | ```
706 |
707 | A special note on the develop plugin; it gets executed via the development service and will be run multiple times. Specifically the plugin will be run under the following conditions:
708 |
709 | - When the development service first starts
710 | - Any time a change is detected within the src directory of the package it is configured against
711 | - Any time one of the packages within the monorepo that it depends on changes.
712 |
713 | This allows you to know when to do restarting etc, and what type of behaviour you would like to implement for each scenario.
714 |
715 | The `args` parameter is also well used by the develop plugin. It will contain the following items:
716 |
717 | - `runType` (_string_)
718 |
719 | It will contain one of the following values:
720 |
721 | - 'FIRST_RUN'
722 |
723 | Indicates that this is the first time the develop plugin is being executed during by the development service.
724 |
725 | - 'SELF_CHANGED'
726 |
727 | Indicates that a file within the src directory of the package it is configured against changed.
728 |
729 | - 'DEPENDENCY_CHANGED'
730 |
731 | Indicates that a package that it depends upon within the monorepo changed.
732 |
733 | - `changedDependency`
734 |
735 | This will contain the [Package](#package-scheam) meta object describing the dependency that changed when the `runType` is "DEPENDENCY_CHANGED".
736 |
737 | ### Deploy Plugin
738 |
739 | Requires the following interface be satisfied:
740 |
741 | ```javascript
742 | module.exports = {
743 | name: 'my-plugin',
744 | develop: (pkg, options, args) => Promise.resolve('todo'),
745 | }
746 | ```
747 |
748 | ### Schemas
749 |
750 | Below are schemas of the arguments that are provided to your plugins.
751 |
752 | #### `Package` Schema
753 |
754 | The holy grail of information for your plugins. The object contains the following:
755 |
756 | - `name` (_string_)
757 |
758 | The name of the package.
759 |
760 | - `config` (_Object_)
761 |
762 | The configuration that is being used (taken from the `lerna-cola.js` file with defaults applied) for it.
763 |
764 | - `color` (`chalk` package function)
765 |
766 | The `chalk` function representing the color we will use to uniquely identify console output for the package.
767 |
768 | - `dependants` (_Array<string>_)
769 |
770 | The names of the other packages within the monorepo that have a direct dependency on this package.
771 |
772 | - `dependencies` (_Array<string>_)
773 |
774 | The names of the other packages within the monorepo that this package has a direct dependency on.
775 |
776 | - `allDependants` (_Array<string>_)
777 |
778 | The names of the ALL other packages within the monorepo that directly, or indirectly (via dependency tree), depend on this package.
779 |
780 | - `devDependencies` (_Array<string>_)
781 |
782 | The names of the other packages within the monorepo that this package has a direct devDependency on.
783 |
784 | - `packageJson` (_Object_)
785 |
786 | The package.json for this package.
787 |
788 | - `paths` (_Object_)
789 |
790 | A set of paths for this package. Containing the following paths:
791 |
792 | - `monoRepoRoot` (_string_)
793 |
794 | The path to the root of the monorepo that this package belongs to.
795 |
796 | - `monoRepoRootNodeModules` (_string_)
797 |
798 | The path to the node_modules dir contained within the root of the monorepo that this package belongs to.
799 |
800 | - `packageBuildOutput` (_string_)
801 |
802 | The path to which build output should be emitted.
803 |
804 | - `packageEntryFile` (_string_)
805 |
806 | The source entry file for this package.
807 |
808 | - `packageJson` (_string_)
809 |
810 | The path to the package.json file for the package.
811 |
812 | - `packageNodeModules` (_string_)
813 |
814 | The path to the node_modules file for the package.
815 |
816 | - `packageRoot` (_string_)
817 |
818 | The path to the root dir of the package.
819 |
820 | - `packageSrc` (_string_)
821 |
822 | The path to the src dir of the package.
823 |
824 | #### `Config` Schema
825 |
826 | > TODO
827 |
--------------------------------------------------------------------------------