@deftomat/opinionated
Version:
Opinionated tooling for JavaScript & TypeScript projects.
103 lines (102 loc) • 3.59 kB
JavaScript
import chalk from 'chalk';
import { findUpSync } from 'find-up';
import { globSync } from 'glob';
import fs from 'node:fs';
import path from 'node:path';
import { ToolError } from './errors.js';
import { createGitWorkflow } from './git.js';
const { bold, red } = chalk;
export function isMonorepoContext(context) {
return context.type === 'monorepo';
}
export function isMonorepoPackageContext(context) {
return context.type === 'monorepo-package';
}
export function isPackageContext(context) {
return context.type === 'package';
}
/**
* Returns the tool context for the given working directory.
*/
export function describeContext(cwd) {
try {
const specConnector = toSpecConnector(`${cwd}/package.json`);
const spec = specConnector.get();
const packageContext = {
type: 'package',
projectRoot: cwd,
projectSpec: specConnector,
packageRoot: cwd,
packageSpec: specConnector,
cachePath: toCachePath(cwd),
git: createGitWorkflow(cwd)
};
if (spec.workspaces) {
const context = {
type: 'monorepo',
projectRoot: cwd,
projectSpec: specConnector,
packages: listProjectPackages(cwd),
packageRoot: undefined,
packageSpec: undefined,
cachePath: toCachePath(cwd),
git: createGitWorkflow(cwd)
};
return context;
}
const parent = findUpSync('package.json', { cwd: `${cwd}/../` });
if (parent == null)
return packageContext;
const parentSpecConnector = toSpecConnector(parent);
const parentSpec = parentSpecConnector.get();
if (parentSpec.workspaces) {
const parentRoot = path.dirname(parent);
const context = {
type: 'monorepo-package',
projectRoot: parentRoot,
projectSpec: parentSpecConnector,
packageRoot: cwd,
packageSpec: specConnector,
cachePath: toCachePath(parentRoot),
git: createGitWorkflow(parentRoot)
};
return context;
}
return packageContext;
}
catch (error) {
if (error.code === 'ENOENT') {
throw new ToolError(red(`No ${bold('package.json')} found! Please make sure that you run this command in the root of JS/TS project.`));
}
throw Error(`Unexpected error:\n${error}`);
}
}
function toSpecConnector(path) {
return {
path,
get() {
return JSON.parse(fs.readFileSync(path).toString());
},
set(content) {
fs.writeFileSync(path, JSON.stringify(content, null, 2));
}
};
}
function toCachePath(projectRoot) {
return `${projectRoot}/node_modules/.cache/opinionated`;
}
/**
* Returns a list of packages and their absolute paths in the project.
*
* If the project is a part of the monorepo, then it returns all packages.
* Otherwise, it returns the project itself as a package.
*/
function listProjectPackages(projectRoot) {
const manifest = JSON.parse(fs.readFileSync(`${projectRoot}/package.json`).toString());
if (manifest.workspaces == null)
return [projectRoot];
return manifest.workspaces
.flatMap(pattern => globSync(pattern, { cwd: projectRoot, absolute: true }))
.filter(root => fs.lstatSync(root).isDirectory())
.filter(root => fs.existsSync(`${root}/package.json`));
}