Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add flow for monorepo w/ independent versions #6

Merged
merged 42 commits into from
Aug 2, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
3d5b014
Use Babel for Jest coverage provider
mcmire Jul 11, 2022
6a42f71
Add flow for monorepo w/ independent versions
mcmire Jul 11, 2022
10d62bc
Fix lint issues / roll back some changes
mcmire Jul 19, 2022
cc69588
Fix test failure on Node 16
mcmire Jul 19, 2022
557b5c1
Re-enable this test
mcmire Jul 19, 2022
9b2d02f
Clean up a couple of things
mcmire Jul 19, 2022
c622b3a
Remove TODOs
mcmire Jul 21, 2022
f7c1538
Fix name of test in `git-utils`
mcmire Jul 27, 2022
3dfa8b0
Merge branch 'main' into add-first-flow
mcmire Jul 27, 2022
137c0d9
Respond to feedback
mcmire Jul 27, 2022
29545e5
Fix docs for resolveExecutable
mcmire Jul 27, 2022
7a9af7e
Remove deepmerge
mcmire Jul 27, 2022
c7d33f5
Improve test for `getEnvironmentVariables`
mcmire Jul 27, 2022
8f4864d
Clean up assertions in tests for ensureDirectoryPathExists
mcmire Jul 27, 2022
7ad7e75
Remove knownKeysOf
mcmire Jul 27, 2022
a9b3d8c
Add a polyrepo test for readProject
mcmire Jul 27, 2022
8ceda73
Add 'bin' entry
mcmire Jul 27, 2022
b8521a1
Update yarn.lock
mcmire Jul 27, 2022
3ab9e27
Use "initial parameters" instead of "information we need to proceed"
mcmire Jul 28, 2022
9fbf13c
Bump dev version of Node to v16
mcmire Jul 28, 2022
0931959
Simplify how lines are indented in validateReleaseSpecification
mcmire Jul 28, 2022
05f66e0
indentationLength -> indentedLineLength
mcmire Jul 29, 2022
20bca54
Drop -utils suffix from most files
mcmire Jul 29, 2022
006fe54
Skip command-line-arguments from tests
mcmire Jul 29, 2022
cbdf61a
Make args to readManifestDependencyFields consistent with readManifes…
mcmire Jul 29, 2022
6a3887a
wip
mcmire Jul 29, 2022
ecbba2b
Simplify UnvalidatedManifest and add more docs to package manifest stuff
mcmire Jul 29, 2022
fec220a
Rename 'Manifest' types to 'PackageManifest'
mcmire Jul 29, 2022
ad02957
Use .strict() when parsing command-line args
mcmire Aug 1, 2022
eb391cf
Update withSandbox to throw if entry exists, not just directory
mcmire Aug 1, 2022
58dcdf1
Extract directory check in withSandbox
mcmire Aug 1, 2022
6c52cc7
Fix typo in package-manifest
mcmire Aug 1, 2022
7565f34
Fix another typo
mcmire Aug 1, 2022
fd2c322
Remove generics from package-manifest
mcmire Aug 2, 2022
d8f9c78
Remove dependencies fields
mcmire Aug 2, 2022
fa6b380
Remove extra 'describe' within 'when a release spec file already exists'
mcmire Aug 2, 2022
1e5a8d6
Promisify rimraf
mcmire Aug 2, 2022
fc30d38
Throw if the new version of a package is less than its current version
mcmire Aug 2, 2022
e367ac1
Stop ignoring errors
mcmire Aug 2, 2022
761377f
Use error causes to wrap errors
mcmire Aug 2, 2022
35f5085
Clarify the version comparison check
mcmire Aug 2, 2022
b4d820d
Rename coverError to wrapError
mcmire Aug 2, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@ module.exports = {
next: 'multiline-block-like',
},
],
// This prevents using bulleted/numbered lists in JSDoc blocks.
// See: </~https://github.com/gajus/eslint-plugin-jsdoc/issues/541>
'jsdoc/check-indentation': 'off',
// It's common for scripts to access `process.env`
'node/no-process-env': 'off',
// It's common for scripts to exit explicitly
Expand All @@ -42,6 +39,11 @@ module.exports = {
{
files: ['*.ts'],
extends: ['@metamask/eslint-config-typescript'],
rules: {
// This prevents using bulleted/numbered lists in JSDoc blocks.
// See: </~https://github.com/gajus/eslint-plugin-jsdoc/issues/541>
'jsdoc/check-indentation': 'off',
},
},

{
Expand Down
10 changes: 6 additions & 4 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ module.exports = {
coverageDirectory: 'coverage',

// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
coveragePathIgnorePatterns: [
'/node_modules/',
'/src/index.ts',
'/src/inputs-utils.ts',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file uses yargs to parse command-line arguments. It's pretty hard to mock yargs given its flexible API, and this is the only thing this file does, so I opted not to write tests for it. If there's a way to do this however then I'd love to know!

],

// Indicates which provider should be used to instrument code for coverage
coverageProvider: 'v8',
coverageProvider: 'babel',

// A list of reporter names that Jest uses when writing coverage reports
coverageReporters: ['html', 'json-summary', 'text'],
Expand Down
20 changes: 20 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,34 @@
"test": "jest && jest-it-up",
"test:watch": "jest --watch"
},
"dependencies": {
"@metamask/action-utils": "^0.0.2",
"@metamask/utils": "^2.0.0",
"debug": "^4.3.4",
"execa": "^5.0.0",
"glob": "^8.0.3",
"semver": "^7.3.7",
"which": "^2.0.2",
"yaml": "^2.1.1",
"yargs": "^17.5.1"
},
"devDependencies": {
"@lavamoat/allow-scripts": "^2.0.3",
"@metamask/auto-changelog": "^2.3.0",
"@metamask/eslint-config": "^9.0.0",
"@metamask/eslint-config-jest": "^9.0.0",
"@metamask/eslint-config-nodejs": "^9.0.0",
"@metamask/eslint-config-typescript": "^9.0.1",
"@types/debug": "^4.1.7",
"@types/jest": "^28.1.4",
"@types/jest-when": "^3.5.2",
"@types/node": "^17.0.23",
"@types/rimraf": "^3.0.2",
"@types/which": "^2.0.1",
"@types/yargs": "^17.0.10",
"@typescript-eslint/eslint-plugin": "^4.21.0",
"@typescript-eslint/parser": "^4.21.0",
"deepmerge": "^4.2.2",
"eslint": "^7.23.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-import": "^2.22.1",
Expand All @@ -42,9 +59,12 @@
"eslint-plugin-prettier": "^3.3.1",
"jest": "^28.0.0",
"jest-it-up": "^2.0.2",
"jest-when": "^3.5.1",
"nanoid": "^3.3.4",
"prettier": "^2.2.1",
"prettier-plugin-packagejson": "^2.2.17",
"rimraf": "^3.0.2",
"stdio-mock": "^1.2.0",
"ts-jest": "^28.0.0",
"ts-node": "^10.7.0",
"typescript": "^4.2.4"
Expand Down
115 changes: 115 additions & 0 deletions src/editor-utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { when } from 'jest-when';
import { determineEditor } from './editor-utils';
import * as envUtils from './env-utils';
import * as miscUtils from './misc-utils';

jest.mock('./env-utils');
jest.mock('./misc-utils');

describe('editor-utils', () => {
describe('determineEditor', () => {
it('returns information about the editor from EDITOR if it resolves to an executable', async () => {
jest
.spyOn(envUtils, 'getEnvironmentVariables')
.mockReturnValue({ EDITOR: 'editor', TODAY: undefined });
when(jest.spyOn(miscUtils, 'resolveExecutable'))
.calledWith('editor')
.mockResolvedValue('/path/to/resolved-editor');

expect(await determineEditor()).toStrictEqual({
path: '/path/to/resolved-editor',
args: [],
});
});

it('falls back to VSCode if it exists and if EDITOR does not point to an executable', async () => {
jest
.spyOn(envUtils, 'getEnvironmentVariables')
.mockReturnValue({ EDITOR: 'editor', TODAY: undefined });
when(jest.spyOn(miscUtils, 'resolveExecutable'))
.calledWith('editor')
.mockResolvedValue(null)
.calledWith('code')
.mockResolvedValue('/path/to/code');

expect(await determineEditor()).toStrictEqual({
path: '/path/to/code',
args: ['--wait'],
});
});

it('returns null if resolving EDITOR returns null and resolving VSCode returns null', async () => {
jest
.spyOn(envUtils, 'getEnvironmentVariables')
.mockReturnValue({ EDITOR: 'editor', TODAY: undefined });
when(jest.spyOn(miscUtils, 'resolveExecutable'))
.calledWith('editor')
.mockResolvedValue(null)
.calledWith('code')
.mockResolvedValue(null);

expect(await determineEditor()).toBeNull();
});

it('returns null if resolving EDITOR returns null and resolving VSCode throws', async () => {
jest
.spyOn(envUtils, 'getEnvironmentVariables')
.mockReturnValue({ EDITOR: 'editor', TODAY: undefined });
when(jest.spyOn(miscUtils, 'resolveExecutable'))
.calledWith('editor')
.mockResolvedValue(null)
.calledWith('code')
.mockRejectedValue(new Error('some error'));

expect(await determineEditor()).toBeNull();
});

it('returns null if resolving EDITOR throws and resolving VSCode returns null', async () => {
jest
.spyOn(envUtils, 'getEnvironmentVariables')
.mockReturnValue({ EDITOR: 'editor', TODAY: undefined });
when(jest.spyOn(miscUtils, 'resolveExecutable'))
.calledWith('editor')
.mockRejectedValue(new Error('some error'))
.calledWith('code')
.mockResolvedValue(null);

expect(await determineEditor()).toBeNull();
});

it('returns null if resolving EDITOR throws and resolving VSCode throws', async () => {
jest
.spyOn(envUtils, 'getEnvironmentVariables')
.mockReturnValue({ EDITOR: 'editor', TODAY: undefined });
when(jest.spyOn(miscUtils, 'resolveExecutable'))
.calledWith('editor')
.mockRejectedValue(new Error('some error'))
.calledWith('code')
.mockRejectedValue(new Error('some error'));

expect(await determineEditor()).toBeNull();
});

it('returns null if EDITOR is unset and resolving VSCode returns null', async () => {
jest
.spyOn(envUtils, 'getEnvironmentVariables')
.mockReturnValue({ EDITOR: undefined, TODAY: undefined });
when(jest.spyOn(miscUtils, 'resolveExecutable'))
.calledWith('code')
.mockResolvedValue(null);

expect(await determineEditor()).toBeNull();
});

it('returns null if EDITOR is unset and resolving VSCode throws', async () => {
jest
.spyOn(envUtils, 'getEnvironmentVariables')
.mockReturnValue({ EDITOR: undefined, TODAY: undefined });
when(jest.spyOn(miscUtils, 'resolveExecutable'))
.calledWith('code')
.mockRejectedValue(new Error('some error'));

expect(await determineEditor()).toBeNull();
});
});
});
56 changes: 56 additions & 0 deletions src/editor-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { getEnvironmentVariables } from './env-utils';
import { debug, resolveExecutable } from './misc-utils';

/**
* Information about the editor present on the user's computer.
*
* @property path - The path to the executable representing the editor.
* @property args - Command-line arguments to pass to the executable when
* calling it.
*/
export interface Editor {
path: string;
args: string[];
}

/**
* Looks for an executable that represents a code editor on your computer. Tries
* the `EDITOR` environment variable first, falling back to the executable that
* represents VSCode (`code`).
*
* @returns A promise that contains information about the found editor (path and
* arguments), or null otherwise.
*/
export async function determineEditor(): Promise<Editor | null> {
let executablePath: string | null = null;
const executableArgs: string[] = [];
const { EDITOR } = getEnvironmentVariables();

if (EDITOR !== undefined) {
try {
executablePath = await resolveExecutable(EDITOR);
} catch (error) {
debug(
`Could not resolve executable ${EDITOR} (${error}), falling back to VSCode`,
);
}
}

if (executablePath === null) {
try {
executablePath = await resolveExecutable('code');
// Waits until the file is closed before returning
executableArgs.push('--wait');
} catch (error) {
debug(
`Could not resolve path to VSCode: ${error}, continuing regardless`,
);
}
}

if (executablePath !== null) {
return { path: executablePath, args: executableArgs };
}

return null;
}
27 changes: 27 additions & 0 deletions src/env-utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { getEnvironmentVariables } from './env-utils';

describe('env-utils', () => {
describe('getEnvironmentVariables', () => {
let existingProcessEnv: NodeJS.ProcessEnv;

beforeEach(() => {
existingProcessEnv = { ...process.env };
});

afterEach(() => {
Object.keys(existingProcessEnv).forEach((key) => {
process.env[key] = existingProcessEnv[key];
});
});

it('returns only the environment variables from process.env that we use in this tool', () => {
process.env.EDITOR = 'editor';
process.env.TODAY = 'today';

expect(getEnvironmentVariables()).toStrictEqual({
EDITOR: 'editor',
TODAY: 'today',
});
});
});
});
16 changes: 16 additions & 0 deletions src/env-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
interface Env {
EDITOR: string | undefined;
TODAY: string | undefined;
}

/**
* Returns all of the environment variables that this tool uses.
*
* @returns An object with a selection of properties from `process.env` that
* this tool needs to access, whether their values are defined or not.
*/
export function getEnvironmentVariables(): Env {
return ['EDITOR', 'TODAY'].reduce((object, key) => {
return { ...object, [key]: process.env[key] };
}, {} as Env);
}
Loading