157 lines
4.5 KiB
JavaScript
157 lines
4.5 KiB
JavaScript
import { spawnSync } from 'node:child_process';
|
|
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
import { dirname, isAbsolute, relative, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
export const workspaceRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
|
|
export function resolvePackageRoot(packageRootArg = process.argv[2]) {
|
|
const packageRoot = resolve(process.cwd(), packageRootArg ?? '.');
|
|
assertInsideDir(workspaceRoot, packageRoot, 'Package root');
|
|
|
|
const packageJsonPath = resolve(packageRoot, 'package.json');
|
|
|
|
if (!existsSync(packageJsonPath)) {
|
|
throw new Error(`Package root is missing package.json: ${packageRoot}`);
|
|
}
|
|
|
|
return packageRoot;
|
|
}
|
|
|
|
export function readPackageJson(packageRoot) {
|
|
return readJsonFile(resolve(packageRoot, 'package.json'), 'package metadata');
|
|
}
|
|
|
|
export function readJsonFile(filePath, label = 'JSON') {
|
|
try {
|
|
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
} catch (error) {
|
|
throw new Error(`Unable to read ${label} at ${filePath}.`, { cause: error });
|
|
}
|
|
}
|
|
|
|
export function resolvePackagePath(packageRoot, value, label) {
|
|
if (typeof value !== 'string' || value.trim() === '') {
|
|
throw new Error(`Missing ${label}.`);
|
|
}
|
|
|
|
const absolutePath = resolve(packageRoot, value);
|
|
assertInsideDir(packageRoot, absolutePath, label);
|
|
return absolutePath;
|
|
}
|
|
|
|
export function resolveWorkspacePath(value, label) {
|
|
if (typeof value !== 'string' || value.trim() === '') {
|
|
throw new Error(`Missing ${label}.`);
|
|
}
|
|
|
|
const absolutePath = resolve(workspaceRoot, value);
|
|
assertInsideDir(workspaceRoot, absolutePath, label);
|
|
return absolutePath;
|
|
}
|
|
|
|
export function assertInsideDir(parent, child, label) {
|
|
if (!isInsideDir(parent, child)) {
|
|
throw new Error(`${label} must stay inside ${parent}: ${child}`);
|
|
}
|
|
}
|
|
|
|
export function isInsideDir(parent, child) {
|
|
const relativePath = relative(resolve(parent), resolve(child));
|
|
|
|
return relativePath === '' || (!!relativePath && !relativePath.startsWith('..') && !isAbsolute(relativePath));
|
|
}
|
|
|
|
export function formatWorkspacePath(filePath) {
|
|
return toPosixPath(relative(workspaceRoot, resolve(filePath)));
|
|
}
|
|
|
|
export function statSafe(filePath) {
|
|
try {
|
|
return statSync(filePath);
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
export function listFiles(directory, options = {}) {
|
|
if (!existsSync(directory)) {
|
|
return [];
|
|
}
|
|
|
|
const files = [];
|
|
const skipDirectories = new Set(options.skipDirectories ?? []);
|
|
|
|
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
if (entry.isDirectory() && skipDirectories.has(entry.name)) {
|
|
continue;
|
|
}
|
|
|
|
const entryPath = resolve(directory, entry.name);
|
|
|
|
if (entry.isDirectory()) {
|
|
files.push(...listFiles(entryPath, options));
|
|
continue;
|
|
}
|
|
|
|
if (entry.isFile()) {
|
|
files.push(entryPath);
|
|
}
|
|
}
|
|
|
|
return files;
|
|
}
|
|
|
|
export function readWebsiteLocales() {
|
|
const localeSourcePath = resolve(workspaceRoot, 'website/src/i18n/locales.ts');
|
|
const source = readFileSync(localeSourcePath, 'utf8');
|
|
const localeMatches = [...source.matchAll(/code:\s*["']([^"']+)["']/g)].map(match => match[1]);
|
|
|
|
if (localeMatches.length === 0) {
|
|
throw new Error(`Unable to read website locales from ${formatWorkspacePath(localeSourcePath)}.`);
|
|
}
|
|
|
|
return localeMatches;
|
|
}
|
|
|
|
export function getPnpmCommand() {
|
|
return process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
|
|
}
|
|
|
|
export function runCommand(command, args, options = {}) {
|
|
return spawnSync(command, args, {
|
|
cwd: options.cwd ?? workspaceRoot,
|
|
encoding: options.encoding,
|
|
env: options.env ?? process.env,
|
|
shell: options.shell ?? false,
|
|
stdio: options.stdio ?? 'inherit',
|
|
});
|
|
}
|
|
|
|
export function runCommandOrExit(command, args, options = {}) {
|
|
const result = runCommand(command, args, options);
|
|
|
|
if (result.status === 0) {
|
|
return result;
|
|
}
|
|
|
|
const cwd = options.cwd ?? workspaceRoot;
|
|
const label = options.label ?? `${command} ${args.join(' ')}`;
|
|
|
|
console.error(`[scripts] Command failed in ${cwd}: ${label}`);
|
|
|
|
if (result.error) {
|
|
console.error(result.error);
|
|
}
|
|
|
|
if (result.stderr) {
|
|
console.error(result.stderr);
|
|
}
|
|
|
|
process.exit(result.status ?? 1);
|
|
}
|
|
|
|
export function toPosixPath(value) {
|
|
return value.replace(/\\/g, '/');
|
|
}
|