2
0
mirror of https://github.com/iconify/iconify.git synced 2024-11-09 14:50:56 +00:00

More options for monorepo script, build and test all packages

This commit is contained in:
Vjacheslav Trushkin 2022-01-29 13:49:16 +02:00
parent 93ccc6c439
commit a50939f438
12 changed files with 183 additions and 36 deletions

View File

@ -69,7 +69,7 @@ Other packages:
## Installation
This monorepo used Lerna to manage packages, but due to few bugs in Lerna, it was replaced with custom manager.
This monorepo used Lerna to manage packages, but due to several bugs in Lerna and Lerna development being abandoned, it was replaced with custom manager.
To install dependencies in all packages, run
@ -85,6 +85,26 @@ If you want to remove `node_modules` for all packages, run `npm run clean`.
If you want to re-install dependencies, run `npm run reinstall`.
To build everything, run `npm run build` (this excludes demo packages).
To run tests, run `npm run test` (this excludes demo packages).
### Other commands
You can run any commands on any package from that package's directory.
Commands that modify `node_modules` might break symlinks. To fix it, run `npm run link` from monorepo directory.
### Commands for all packages
If you want to run a command on all packages, run `node monorepo run your_command --if-present`.
There are several options to filter packages:
- `--if-present` will check if command is present before running it.
- `--public` will execute command only for public packages (everything except demo).
- `--private` will execute command only for private packages (only demo packages).
### Monorepo on Windows
This monorepo uses symbolic links to create links between packages. This allows development of multiple packages at the same time.

View File

@ -1,5 +1,6 @@
import { consoleLog } from './log';
import { PackageInfo } from './types';
import { findWorkspaces } from './workspaces';
import { filterWorkspaces } from './workspaces';
/**
* Run callback for all workspaces
@ -8,10 +9,10 @@ export function runAction(
log: string,
callback: (workspace: PackageInfo) => void
): void {
const workspaces = findWorkspaces();
const workspaces = filterWorkspaces();
if (!workspaces.length) {
throw new Error('No packages found');
}
console.log(`${log}...`);
consoleLog(`${log}...`);
workspaces.forEach(callback);
}

View File

@ -1,5 +1,6 @@
import fs from 'fs';
import { addToPath, pathToString, relativePath } from './dirs';
import { consoleLog } from './log';
import { PackageInfo } from './types';
/**
@ -17,7 +18,7 @@ export function cleanWorkspace(workspace: PackageInfo) {
return;
}
console.log('Removing:', relativePath(dir));
consoleLog('Removing:', relativePath(dir));
try {
fs.rmSync(dir, {
recursive: true,

View File

@ -1,5 +1,7 @@
import { spawnSync } from 'child_process';
import { pathToString, relativePath } from './dirs';
import { consoleError, consoleLog } from './log';
import { actionOptions } from './options';
import { PackageInfo } from './types';
let npm: string;
@ -7,7 +9,7 @@ let npm: string;
/**
* Get NPM command
*/
function getNPMCommand(): string {
function getNPMCommand(): string {
const clients = ['npm', 'npm.cmd'];
for (let i = 0; i < clients.length; i++) {
const cmd = clients[i];
@ -16,10 +18,10 @@ let npm: string;
return cmd;
}
}
throw new Error('Cannot execute NPM commands')
consoleError('Cannot detect NPM client');
process.exit(5);
}
/**
* Run NPM command
*/
@ -29,14 +31,15 @@ export function runNPMCommand(workspace: PackageInfo, params: string[]): void {
}
const cwd = pathToString(workspace.path);
console.log('\n' + relativePath(cwd) + ':', npm, params.join(' '));
consoleLog('\n' + relativePath(cwd) + ':', npm, params.join(' '));
const result = spawnSync(npm, params, {
cwd,
stdio: 'inherit',
stdio: actionOptions.silent ? 'pipe' : 'inherit',
});
if (result.status !== 0) {
throw new Error(
consoleError(
`Failed to run "${npm} ${params.join(' ')}" at ${relativePath(cwd)}`
);
process.exit(result.status);
}
}

View File

@ -1,6 +1,7 @@
import fs from 'fs';
import { dirname } from 'path';
import { addToPath, pathToString, relativePath } from './dirs';
import { consoleLog } from './log';
import type { PathList } from './types';
/**
@ -24,7 +25,7 @@ export function createLink(from: PathList, to: PathList, unlink = true): void {
}
// Create link
console.log(
consoleLog(
'Creating link:',
relativePath(fromDir),
'->',
@ -38,7 +39,7 @@ export function createLink(from: PathList, to: PathList, unlink = true): void {
*/
export function removeLink(path: PathList, packageName: string): void {
const dir = pathToString(addToPath(path, packageName));
console.log('Removing link:', relativePath(dir));
consoleLog('Removing link:', relativePath(dir));
try {
fs.unlinkSync(dir);
} catch (err) {
@ -63,7 +64,7 @@ function rmdir(dir: string) {
try {
const stat = fs.lstatSync(dir);
if (stat.isDirectory() || stat.isSymbolicLink()) {
console.log('Removing', relativePath(dir));
consoleLog('Removing', relativePath(dir));
fs.rmSync(dir, {
recursive: true,
});

View File

@ -0,0 +1,13 @@
import { actionOptions } from './options';
export function consoleLog(...args: unknown[]) {
if (!actionOptions.silent) {
console.log(...args);
}
}
export function consoleError(...args: unknown[]) {
if (!actionOptions.silent) {
console.error(...args);
}
}

View File

@ -0,0 +1,41 @@
type PackageTypeFilter = 'private' | 'public' | 'all';
/**
* Interface for options
*/
export interface ActionOptions {
// Run command if present, applies to 'run' and 'run-script'
ifPresent: boolean;
// Filter packages by `private` property, undefined if not set
private?: PackageTypeFilter;
// Silent
silent: boolean;
}
/**
* Options
*/
export const actionOptions: ActionOptions = {
ifPresent: false,
silent: false,
};
/**
* Toggle private filter
*/
export function enablePrivateFilter(value: boolean) {
const newValue: PackageTypeFilter = value ? 'private' : 'public';
if (!actionOptions.private) {
// Not set: set to new value
actionOptions.private = newValue;
return;
}
if (actionOptions.private !== newValue) {
// Enable all
actionOptions.private = 'all';
}
}

View File

@ -10,27 +10,33 @@ import { pathToString } from './dirs';
export function getPackageInfo(path: PathList): PackageInfo | null {
const packageFilename = pathToString(addToPath(path, 'package.json'));
let name: unknown;
let version: string;
let isPrivate: boolean;
let data: Record<string, unknown>;
let name: string;
try {
const data = JSON.parse(
fs.readFileSync(packageFilename, 'utf8')
) as Record<string, unknown>;
data = JSON.parse(fs.readFileSync(packageFilename, 'utf8'));
if (
typeof data !== 'object' ||
!data ||
typeof data.name !== 'string'
) {
return null;
}
name = data.name;
version = typeof data.version === 'string' ? data.version : '';
isPrivate = version ? !!data.private : true;
} catch (err) {
return null;
}
return typeof name === 'string'
? {
name,
private: isPrivate,
version,
path,
}
: null;
const version = typeof data.version === 'string' ? data.version : '';
const isPrivate = version ? !!data.private : true;
const scripts =
typeof data.scripts === 'object' ? Object.keys(data.scripts) : [];
return {
name,
private: isPrivate,
version,
scripts,
path,
};
}
/**

View File

@ -15,5 +15,6 @@ export interface PackageInfo {
name: PackageName;
private: boolean;
version: string;
scripts: string[];
path: PathList;
}

View File

@ -2,6 +2,7 @@ import type { PathList, PackageInfo } from './types';
import { rootDir } from './dirs';
import { addToPath, findSubdirs } from './dirs';
import { getFixPackageName, getPackageInfo } from './package';
import { actionOptions } from './options';
/**
* Workspaces cache
@ -60,3 +61,31 @@ export function findWorkspaces(): PackageInfo[] {
// Cache and return result
return workspaces;
}
// Cache for filterWorkspaces() result
let filteredWorkspaces: PackageInfo[];
/**
* Get only workspaces that match options
*/
export function filterWorkspaces(): PackageInfo[] {
if (!filteredWorkspaces) {
filteredWorkspaces = findWorkspaces().filter((item) => {
// Filter by `private` property
if (
actionOptions.private !== void 0 &&
actionOptions.private !== 'all'
) {
if (item.private !== (actionOptions.private === 'private')) {
return false;
}
}
// TODO: match name
// Match
return true;
});
}
return filteredWorkspaces;
}

View File

@ -2,6 +2,7 @@ import { runAction } from './helpers/action';
import { addLinksToWorkspace } from './helpers/add-links';
import { cleanWorkspace } from './helpers/clean';
import { runNPMCommand } from './helpers/exec';
import { actionOptions, enablePrivateFilter } from './helpers/options';
import { removeLinksFromWorkspace } from './helpers/remove-links';
/**
@ -35,11 +36,19 @@ interface ActionWithParam {
const actionWithParamsFunctions: Record<string, (param: string) => void> = {
run: (param: string) => {
runAction(`Running "npm run ${param}"`, (workspace) => {
runNPMCommand(workspace, ['run', param]);
if (
!actionOptions.ifPresent ||
workspace.scripts.indexOf(param) !== -1
) {
runNPMCommand(workspace, ['run', param]);
}
});
},
};
// Aliases
actionWithParamsFunctions['run-script'] = actionWithParamsFunctions['run'];
/**
* Run code
*/
@ -60,6 +69,25 @@ export function run() {
return;
}
// Options
switch (arg) {
case '--if-present':
actionOptions.ifPresent = true;
return;
case '--silent':
actionOptions.silent = true;
return;
case '--public':
enablePrivateFilter(false);
return;
case '--private':
enablePrivateFilter(true);
return;
}
// Action
if (actionFunctions[arg] !== void 0) {
actions.push(arg);

View File

@ -4,6 +4,7 @@
"description": "The most versatile icon framework",
"author": "Vjacheslav Trushkin <cyberalien@gmail.com> (https://iconify.design)",
"license": "(Apache-2.0 OR GPL-2.0)",
"main": "./monorepo/index.js",
"bugs": "https://github.com/iconify/iconify/issues",
"homepage": "https://iconify.design/",
"repository": {
@ -11,11 +12,13 @@
"url": "https://github.com/iconify/iconify.git"
},
"scripts": {
"install": "node monorepo/index install",
"clean": "node monorepo/index clean",
"link": "node monorepo/index link",
"unlink": "node monorepo/index unlink",
"reinstall": "node monorepo/index clean install"
"install": "node monorepo install",
"clean": "node monorepo clean",
"link": "node monorepo link",
"unlink": "node monorepo unlink",
"reinstall": "node monorepo clean install",
"build": "node monorepo run build --if-present --public",
"test": "node monorepo run build --if-present --public"
},
"devDependencies": {}
}