Skip to content

Commit

Permalink
build(preview): rewrite prepare-preview-buidlds in ts + support works…
Browse files Browse the repository at this point in the history
…pace packages
  • Loading branch information
ccharly committed Sep 11, 2024
1 parent 55f15b4 commit 672cc7b
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 49 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"lint:eslint": "eslint . --cache --ext js,cjs,mjs,ts",
"lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write && yarn constraints --fix && yarn lint:dependencies:fix",
"lint:misc": "prettier '**/*.json' '**/*.md' '!**/CHANGELOG.old.md' '**/*.yml' '!.yarnrc.yml' '!merged-packages/**' --ignore-path .gitignore",
"prepare:preview": "./scripts/prepare-preview-builds.sh",
"prepare:preview": "ts-node scripts/prepare-preview-builds.ts",
"prepare:preview:local": "yarn prepare:preview @metamask-previews $(git rev-parse --short HEAD)",
"publish:preview": "yarn workspaces foreach --all --no-private --parallel --verbose run publish:preview",
"readme:update": "ts-node scripts/update-readme-content.ts",
Expand All @@ -48,6 +48,7 @@
"@metamask/eslint-config-jest": "^12.0.0",
"@metamask/eslint-config-nodejs": "^12.0.0",
"@metamask/eslint-config-typescript": "^12.0.0",
"@npmcli/package-json": "^5.0.0",
"@types/jest": "^28.1.6",
"@types/node": "^16",
"@typescript-eslint/eslint-plugin": "^5.43.0",
Expand Down
9 changes: 0 additions & 9 deletions scripts/prepare-preview-builds.jq

This file was deleted.

39 changes: 0 additions & 39 deletions scripts/prepare-preview-builds.sh

This file was deleted.

225 changes: 225 additions & 0 deletions scripts/prepare-preview-builds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
#!yarn ts-node

import PackageJson from '@npmcli/package-json';
// import { spawn } from 'node:child_process/promises';
import { spawn } from 'child_process';
import execa from 'execa';

// Previews object displayed by the CI when you ask for preview builds.
type Arguments = {
// NPM scope (@metamask-previews)
npmScope: string;
// Commit ID
commitId: string;
};

type WorkspacePackage = {
location: string;
name: string;
};

type WorkspacePreviewPackage = WorkspacePackage & {
previewName: string;
previewVersion: string;
version: string;
};

class UsageError extends Error {
constructor(message: string) {
// 1 because `ts-node` is being used as a launcher, so argv[0] is ts-node "bin.js"
const bin: string = process.argv[1];

super(
`usage: ${bin} <npm-scope> <commit-id>\n${
message ? `\nerror: ${message}\n` : ''
}`,
);
}
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

/**
* Parse and verifies that each arguments is well-formatted.
*
* @returns Parsed arguments as an `Arguments` object.
*/
async function parseAndVerifyArguments(): Promise<Arguments> {
if (process.argv.length !== 4) {
throw new UsageError('not enough arguments');
}
// 1: ts-node (bin.js), 2: This script, 3: NPM scope, 4: Commit ID
const [, , npmScope, commitId] = process.argv as [
string,
string,
string,
string,
];

return { npmScope, commitId };
}

/**
* Gets a preview package from a workspace package.
*
* @param pkg - Workspace package.
* @param npmScope - NPM scope used for the preview.
* @param commitId - Commit ID used in the preview version.
* @returns The preview package.
*/
async function getPkgPreview(
pkg: WorkspacePackage,
npmScope: string,
commitId: string,
): Promise<WorkspacePreviewPackage> {
const pkgJson = await PackageJson.load(pkg.location);

// Assuming we always have a version in our package.json
const pkgVersion: string = pkgJson.content.version;

return {
...pkg,
version: pkgVersion,
previewName: pkg.name.replace('@metamask/', `${npmScope}/`),
previewVersion: `${pkgVersion}-${commitId}`,
};
}

/**
* Gets all workspace packages.
*
* @returns The list of workspace packages.
*/
async function getWorkspacePackages(): Promise<WorkspacePackage[]> {
const { stdout } = await execa('yarn', [
'workspaces',
'list',
'--no-private',
'--json',
]);

// Stops early, to avoid having JSON parsing error on empty lines
if (stdout.trim() === '') {
return [];
}

// Each `yarn why --json` lines is a JSON object, so parse it and "type" it
return stdout.split('\n').map((line) => JSON.parse(line) as WorkspacePackage);
}

/**
* Gets all workspace packages as preview packages.
*
* @param npmScope - NPM scope used for the preview.
* @param commitId - Commit ID used in the preview version.
* @returns The list of preview packages.
*/
async function getWorkspacePreviewPackages(
npmScope: string,
commitId: string,
): Promise<WorkspacePreviewPackage[]> {
const pkgs = await getWorkspacePackages();
return await Promise.all(
pkgs.map(async (pkg) => await getPkgPreview(pkg, npmScope, commitId)),
);
}

/**
* Updates all workspace packages with their preview name and version.
*
* @param previewPkgs - Preview package list.
*/
async function updateWorkspacePackagesWithPreviewInfo(
previewPkgs: WorkspacePreviewPackage[],
): Promise<void> {
for (const pkg of previewPkgs) {
const pkgJson = await PackageJson.load(pkg.location);

// Update package.json with preview ingo
pkgJson.update({
name: pkg.previewName,
version: pkg.previewVersion,
});

// Update dependencies that refers to a workspace package. We pin the current version
// of that package instead, and `yarn` will resolve this using the global resolutions
// (see `updateWorkspaceResolutions`)
for (const depKey of ['dependencies', 'devDependencies']) {
const deps = pkgJson.content[depKey];

for (const { name, version } of previewPkgs) {
// Only consider dependenc that refers to a local workspace package
if (name in deps && deps[name] === 'workspace:^') {
// Override this dependency with a "fixed" version,
// `yarn` will resolve this using the global resolutions being injected by
// `updateWorkspaceResolutions`
deps[name] = version;
}
}

// Finally override the dependencies
pkgJson.update({
[depKey]: deps,
});
}

await pkgJson.save();
}
}

/**
* Updates workspace resolutions with preview packages.
*
* @param previewPkgs - Preview package list.
*/
async function updateWorkspaceResolutions(
previewPkgs: WorkspacePreviewPackage[],
): Promise<void> {
const workspacePkgJson = await PackageJson.load('.');

// Compute resolutions to map currently versionned packages to their preview
// counterpart
const resolutions = {};
for (const pkg of previewPkgs) {
resolutions[`${pkg.name}@${pkg.version}`] = `workspace:${pkg.location}`;
}

// Update workspace resolutions to use preview packages
workspacePkgJson.update({
resolutions: {
...workspacePkgJson.content.resolutions,
// This comes after so we can override any "conflicting" resolutions (that would share
// the same name)
...resolutions,
},
});

await workspacePkgJson.save();
}

/**
* Yarn install.
*/
function yarnInstall() {
spawn('yarn', ['install', '--no-immutable'], { stdio: 'inherit' });
}

/**
* The entrypoint to this script.
*/
async function main() {
const { npmScope, commitId } = await parseAndVerifyArguments();
const previewPkgs = await getWorkspacePreviewPackages(npmScope, commitId);

console.log(':: preparing manifests...');
await updateWorkspacePackagesWithPreviewInfo(previewPkgs);

console.log(':: updating global resolutions...');
await updateWorkspaceResolutions(previewPkgs);

console.log(':: installing dependencies...');
yarnInstall();
}
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1683,6 +1683,7 @@ __metadata:
"@metamask/eslint-config-jest": "npm:^12.0.0"
"@metamask/eslint-config-nodejs": "npm:^12.0.0"
"@metamask/eslint-config-typescript": "npm:^12.0.0"
"@npmcli/package-json": "npm:^5.0.0"
"@types/jest": "npm:^28.1.6"
"@types/node": "npm:^16"
"@typescript-eslint/eslint-plugin": "npm:^5.43.0"
Expand Down

0 comments on commit 672cc7b

Please sign in to comment.