embark/scripts/nightly-release.js
Michael Bradley, Jr 3693ebd90d fix: ensure that packages properly specify their dependencies
Many packages in the monorepo did not specify all of their dependencies; they
were effectively relying on resolution in the monorepo's root
`node_modules`. In a production release of `embark` and `embark[js]-*` packages
this can lead to broken packages.

To fix the problem currently and to help prevent it from happening again, make
use of the `eslint-plugin-import` package's `import/no-extraneous-dependencies`
and `import/no-unresolved` rules. In the root `tslint.json` set
`"no-implicit-dependencies": true`, wich is the tslint equivalent of
`import/no-extraneous-dependencies`; there is no tslint equivalent for
`import/no-unresolved`, but we will eventually replace tslint with an eslint
configuration that checks both `.js` and `.ts` files.

For `import/no-unresolved` to work in our monorepo setup, in most packages add
an `index.js` that has:

```js
module.exports = require('./dist'); // or './dist/lib' in some cases
```

And point `"main"` in `package.json` to `"./index.js"`. Despite what's
indicated in npm's documentation for `package.json`, it's also necessary to add
`"index.js"` to the `"files"` array.

Make sure that all `.js` files that can and should be linted are in fact
linted. For example, files in `packages/embark/src/cmd/` weren't being linted
and many test suites weren't being linted.

Bump all relevant packages to `eslint@6.8.0`.

Fix all linter errors that arose after these changes.

Implement a `check-yarn-lock` script that's run as part of `"ci:full"` and
`"qa:full"`, and can manually be invoked via `yarn cylock` in the root of the
monorepo. The script exits with error if any specifiers are found in
`yarn.lock` for `embark[js][-*]` and/or `@embarklabs/*` (with a few exceptions,
cf. `scripts/check-yarn-lock.js`).
2020-02-25 14:52:10 -06:00

185 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const chalk = require('chalk');
const {execSync} = require('child_process');
const path = require('path');
const {readJsonSync} = require('fs-extra');
const branch = `master`;
const commitMsg = `chore(prerelease): %v`;
const distTag = `nightly`;
const preId = `nightly`;
const remote = `origin`;
const cyan = (str) => chalk.cyan(str);
const execSyncInherit = (cmd) => execSync(cmd, {stdio: 'inherit'});
const log = (mark, str, which = 'log') => console[which](
mark, str.filter(s => !!s).join(` `)
);
const logError = (...str) => log(chalk.red(``), str, 'error');
const logInfo = (...str) => log(chalk.blue(``), str);
const logSuccess = (...str) => log(chalk.green(``), str);
const logWarning = (...str) => log(chalk.yellow('‼︎'), str);
const failMsg = `${chalk.red(`NIGHTLY RELEASE FAILED!`)} Stopping right here.`;
const runCommand = (cmd, inherit = true, display) => {
logInfo(`Running command ${cyan(display || cmd)}.`);
let out;
if (inherit) {
execSyncInherit(cmd);
} else {
out = execSync(cmd);
}
return out;
};
(async () => {
if (!process.env.GH_TOKEN) {
logError(
`Environment variable ${cyan('GH_TOKEN')} was not defined or falsy. It`,
`must be defined with a properly scoped GitHub personal access token.`
);
logError(failMsg);
process.exit(1);
}
if (!process.env.IS_GITHUB_ACTIONS_WORKFLOW) {
logError(
`This script should only be run in the context of a GitHub Actions`,
`workflow (that workflow should set the environment variable`,
`${cyan('IS_GITHUB_ACTIONS_WORKFLOW')} to a truthy value).`
);
logError(failMsg);
process.exit(1);
}
const lernaJsonPath = path.join(__dirname, '../lerna.json');
logInfo(`Reading ${cyan(lernaJsonPath)}...`);
let lernaJson, registry;
try {
lernaJson = readJsonSync(lernaJsonPath);
registry = lernaJson.command.publish.registry;
if (!registry) throw new Error('missing registry in lerna.json');
} catch (e) {
console.error(e.stack || e);
logError(
`Could not read values from ${cyan(lernaJsonPath)}. Please check the`,
`error above.`
);
logError(failMsg);
process.exit(1);
}
logInfo(`Determining the current branch...`);
let currentBranch;
try {
currentBranch = runCommand(`git rev-parse --abbrev-ref HEAD`, false)
.toString()
.trim();
} catch (e) {
logError(`Could not determine the branch. Please check the error above.`);
logError(failMsg);
process.exit(1);
}
if (currentBranch === branch) {
logSuccess(`Current branch and release branch are the same.`);
} else {
logError(
`Current branch ${cyan(currentBranch)} is not the same as release branch`,
`${cyan(branch)}.`
);
logError(failMsg);
process.exit(1);
}
logInfo(
`Fetching commits from ${cyan(remote)} to compare local and remote`,
`branches...`
);
try {
runCommand(`git fetch ${remote}`, false);
} catch (e) {
logError(`Could not fetch latest commits. Please check the error above.`);
logError(failMsg);
process.exit(1);
}
let localRef, remoteRef;
try {
localRef = runCommand(`git rev-parse ${branch}`, false).toString().trim();
remoteRef = runCommand(`git rev-parse ${remote}/${branch}`, false)
.toString()
.trim();
} catch (e) {
logError(`A problem occured. Please check the error above.`);
logError(failMsg);
process.exit(1);
}
if (localRef === remoteRef) {
logSuccess(`Local branch is in sync with remote branch.`);
} else {
logError(
`Local branch ${cyan(branch)} is not in sync with`,
`${cyan(`${remote}/${branch}`)}.`
);
logError(failMsg);
process.exit(1);
}
const lernaPublish = [
`lerna`,
`publish`,
`--conventional-commits`,
`--conventional-prerelease`,
`--create-release github`,
`--dist-tag ${distTag}`,
`--git-remote ${remote}`,
`--message "${commitMsg}"`,
`--preid ${preId}`,
`--registry ${registry}`,
`--yes`
].filter(str => !!str).join(` `);
try {
runCommand(lernaPublish);
if (localRef ===
runCommand(`git rev-parse ${branch}`, false).toString().trim()) {
logWarning(
chalk.yellow(`NIGHTLY RELEASE STOPPED!`),
`No commit or tag was created. No packages were published. This is`,
`most likely due to no qualifying changes having been made to the`,
`${cyan(branch)} branch since the previous release. Please check the`,
`output above.`
);
process.exit(0);
}
} catch (e) {
logError(`A problem occured. Please check the error above.`);
const lernaDocsUrl = 'https://github.com/lerna/lerna/tree/master/commands/publish#bump-from-package';
logError(
failMsg,
`Make sure to clean up commits, tags, and releases as necessary. Check`,
`the package registry to verify if any packages were published. If so`,
`they may need to be flagged as deprecated since the`,
`${cyan('lerna publish')} command exited with error. In some cases it`,
`may be possible to salvage an imcomplete release by using the`,
`${cyan('from-package')} keyword with the ${cyan('lerna publish')}`,
`command. See: ${lernaDocsUrl}`
);
process.exit(1);
}
logSuccess(`${chalk.green(`NIGHTLY RELEASE SUCCEEDED!`)} Woohoo! Done.`);
})().then(() => {
process.exit(0);
}).catch(e => {
console.error(e.stack || e);
logError(`A problem occured. Please check the error above.`);
logError(failMsg);
process.exit(1);
});