Skip to content

Commit

Permalink
Implement JavaScript-based constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
Mrtenz committed Sep 30, 2023
1 parent e03d033 commit d59c486
Show file tree
Hide file tree
Showing 12 changed files with 2,999 additions and 2,810 deletions.
1 change: 1 addition & 0 deletions .depcheckrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"@lavamoat/preinstall-always-fail",
"@metamask/auto-changelog",
"@types/*",
"@yarnpkg/types",
"prettier-plugin-packagejson",
"ts-node",
"typedoc"
Expand Down
8 changes: 7 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,15 @@ module.exports = {
},

{
files: ['*.js'],
files: ['*.js', '*.cjs'],
parserOptions: {
sourceType: 'script',
ecmaVersion: 2020,
},
settings: {
jsdoc: {
mode: 'typescript',
},
},
extends: ['@metamask/eslint-config-nodejs'],
},
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v16
v18
52 changes: 0 additions & 52 deletions .yarn/plugins/@yarnpkg/plugin-constraints.cjs

This file was deleted.

786 changes: 0 additions & 786 deletions .yarn/releases/yarn-3.2.1.cjs

This file was deleted.

893 changes: 893 additions & 0 deletions .yarn/releases/yarn-4.0.0-rc.52.cjs

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
compressionLevel: mixed

enableGlobalCache: false

enableScripts: false

enableTelemetry: 0
Expand All @@ -11,7 +15,5 @@ nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-allow-scripts.cjs
spec: "https://raw.githubusercontent.com/LavaMoat/LavaMoat/main/packages/yarn-plugin-allow-scripts/bundles/@yarnpkg/plugin-allow-scripts.js"
- path: .yarn/plugins/@yarnpkg/plugin-constraints.cjs
spec: "@yarnpkg/plugin-constraints"

yarnPath: .yarn/releases/yarn-3.2.1.cjs
yarnPath: .yarn/releases/yarn-4.0.0-rc.52.cjs
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ See our documentation:

### Setup

- Install [Node.js](https://nodejs.org) version 16
- Install [Node.js](https://nodejs.org) version 18
- If you are using [nvm](https://github.com/creationix/nvm#installation) (recommended) running `nvm use` will automatically choose the right node version for you.
- Install [Yarn v3](https://yarnpkg.com/getting-started/install)
- Run `yarn install` to install dependencies and run any required post-install scripts
Expand Down
99 changes: 0 additions & 99 deletions constraints.pro

This file was deleted.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@metamask/module-template",
"name": "@metamask/metamask-module-template",
"version": "0.0.0",
"description": "The MetaMask Node module template",
"homepage": "https://github.com/MetaMask/metamask-module-template#readme",
Expand Down Expand Up @@ -40,7 +40,7 @@
"lint:changelog": "auto-changelog validate",
"lint:constraints": "yarn constraints",
"lint:dependencies": "depcheck && yarn dedupe",
"lint:eslint": "eslint . --cache --ext js,ts",
"lint:eslint": "eslint . --cache --ext js,cjs,ts",
"lint:fix": "yarn lint:eslint --fix && yarn lint:constraints --fix && yarn lint:misc --write && yarn lint:dependencies && yarn lint:changelog",
"lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' '**/*.yml' '!.yarnrc.yml' --ignore-path .gitignore --no-error-on-unmatched-pattern",
"prepack": "./scripts/prepack.sh",
Expand All @@ -61,6 +61,7 @@
"@types/node": "^16",
"@typescript-eslint/eslint-plugin": "^5.43.0",
"@typescript-eslint/parser": "^5.43.0",
"@yarnpkg/types": "^4.0.0-rc.52",
"depcheck": "^1.4.3",
"eslint": "^8.44.0",
"eslint-config-prettier": "^8.8.0",
Expand All @@ -80,7 +81,7 @@
"typedoc": "^0.23.15",
"typescript": "~4.8.4"
},
"packageManager": "yarn@3.2.1",
"packageManager": "yarn@4.0.0-rc.52",
"engines": {
"node": ">=16.0.0"
},
Expand Down
206 changes: 206 additions & 0 deletions yarn.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/** @type {import('@yarnpkg/types')} */
const { defineConfig } = require('@yarnpkg/types');
const { readFile } = require('fs/promises');
const { basename, resolve } = require('path');

/**
* Aliases for the Yarn type definitions, to make the code more readable.
*
* @typedef {import('@yarnpkg/types').Yarn.Constraints.Workspace} Workspace
* @typedef {import('@yarnpkg/types').Yarn.Constraints.Dependency} Dependency
*/

/**
* The base URL for the GitHub repository.
*
* @type {string}
*/
const BASE_URL = 'https://github.com/MetaMask/';

/**
* Get the absolute path to a file within the workspace.
*
* @param {Workspace} workspace - The workspace.
* @param {string} path - The path to the file, relative to the workspace root.
* @returns {string} The absolute path to the file.
*/
function getWorkspacePath(workspace, path) {
return resolve(__dirname, workspace.cwd, path);
}

/**
* Get the contents of a file within the workspace. The file is expected to be
* encoded as UTF-8.
*
* @param {Workspace} workspace - The workspace.
* @param {string} path - The path to the file, relative to the workspace root.
* @returns {Promise<string>} The contents of the file.
*/
async function getWorkspaceFile(workspace, path) {
return await readFile(getWorkspacePath(workspace, path), 'utf8');
}

/**
* Expect that the workspace has the given field, and that it is a non-null
* value. If the field is not present, or is null, this will log an error, and
* cause the constraint to fail.
*
* If a value is provided, this will also verify that the field is equal to the
* given value.
*
* @param {Workspace} workspace - The workspace to check.
* @param {string} field - The field to check.
* @param {any} [value] - The value to check.
*/
function expectWorkspaceField(workspace, field, value) {
const fieldValue = workspace.manifest[field];
if (fieldValue === null) {
workspace.error(`Missing required field "${field}".`);
return;
}

if (value) {
workspace.set(field, value);
}
}

/**
* Expect that the workspace has a description, and that it is a non-null
* string. If the description is not present, or is null, this will log an
* error, and cause the constraint to fail.
*
* This will also verify that the description does not end with a period.
*
* @param {Workspace} workspace - The workspace to check.
*/
function expectWorkspaceDescription(workspace) {
expectWorkspaceField(workspace, 'description');

const { description } = workspace.manifest;
if (typeof description !== 'string') {
workspace.error(
`Expected description to be a string, but got ${typeof description}.`,
);
return;
}

if (description.endsWith('.')) {
workspace.set('description', description.slice(0, -1));
}
}

/**
* Expect that if a dependency is listed under "dependencies", it is not also
* listed under "devDependencies". If it is, this will log an error, and cause
* the constraint to fail.
*
* @param {Workspace} workspace - The workspace to check.
*/
function expectWorkspaceDependencies(workspace) {
workspace.pkg.dependencies.forEach((dependency) => {
// `workspace.pkg` does not have a `devDependencies` field, so we need to
// check the `manifest` instead.
const isDependency = Boolean(
workspace.manifest.dependencies?.[dependency.ident],
);
const isDevDependency = Boolean(
workspace.manifest.devDependencies?.[dependency.ident],
);

if (isDependency && isDevDependency) {
workspace.unset(`devDependencies.${dependency.ident}`);
}
});
}

/**
* Expect that the workspace has a README.md file, and that it is a non-empty
* string. The README.md is expected to:
*
* - Not contain template instructions (unless the workspace is the module
* template itself).
* - Match the version of Node.js specified in the `.nvmrc` file.
*
* @param {Workspace} workspace - The workspace to check.
* @param {string} workspaceName - The name of the workspace.
*/
async function expectReadme(workspace, workspaceName) {
const readme = await getWorkspaceFile(workspace, 'README.md');
if (workspaceName !== 'metamask-module-template') {
if (readme.includes('## Template Instructions')) {
workspace.error(
'The README.md contains template instructions. These should be removed.',
);
}
}

const nvmrc = await getWorkspaceFile(workspace, '.nvmrc');
const nodeVersion = nvmrc.trim().replace(/^v/u, '');
if (
!readme.includes(`[Node.js](https://nodejs.org) version ${nodeVersion}`)
) {
workspace.error(
`The README.md does not match the Node.js version specified in the .nvmrc file. Please update the README.md to include "version ${nodeVersion}".`,
);
}
}

module.exports = defineConfig({
async constraints({ Yarn }) {
const workspace = Yarn.workspace();
const workspaceName = basename(__dirname);
const workspaceRepository = `${BASE_URL}${workspaceName}`;

// The package must have a name, version, description, and license.
expectWorkspaceField(workspace, 'name', `@metamask/${workspaceName}`);
expectWorkspaceField(workspace, 'version');
expectWorkspaceField(workspace, 'license');
expectWorkspaceDescription(workspace);

// The package must have a valid README.md file.
await expectReadme(workspace, workspaceName);

expectWorkspaceDependencies(workspace);

// The homepage of the package must match its name.
workspace.set('homepage', `${workspaceRepository}#readme`);

// The bugs URL of the package must point to the Issues page for the
// repository.
workspace.set('bugs.url', `${workspaceRepository}/issues`);

// The package must specify Git as the repository type, and match the URL of
// a repository within the MetaMask organization.
workspace.set('repository.type', 'git');
workspace.set('repository.url', `${workspaceRepository}.git`);

// The package must specify a minimum Node.js version of 16.
workspace.set('engines.node', '>=16.0.0');

// The package must specify a `types` entrypoint, and an `import`
// entrypoint.
workspace.set('types', './dist/types/index.d.ts');
workspace.set('exports["."].types', './dist/types/index.d.ts');

// The package must specify a `main` and `module` entrypoint, and a
// `require` and `import` entrypoint.
workspace.set('main', './dist/cjs/index.js');
workspace.set('exports["."].require', './dist/cjs/index.js');
workspace.set('module', './dist/esm/index.js');
workspace.set('exports["."].import', './dist/esm/index.js');

// The package must export a `package.json` file.
workspace.set('exports["./package.json"]', './package.json');

// The list of files included in the package must only include files
// generated during the build process.
workspace.set('files[0]', 'dist/cjs/**');
workspace.set('files[1]', 'dist/esm/**');
workspace.set('files[2]', 'dist/types/**');

// The package is public, and should be published to the npm registry.
workspace.unset('private');
workspace.set('publishConfig.access', 'public');
workspace.set('publishConfig.registry', 'https://registry.npmjs.org/');
},
});
Loading

0 comments on commit d59c486

Please sign in to comment.