@alexaegis/standard-version
Version:
Standard version
176 lines (175 loc) • 6.5 kB
JavaScript
;
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const node_path = require("node:path");
const glob = require("glob");
const jsYaml = require("js-yaml");
const node_fs = require("node:fs");
const PACKAGE_JSON_NAME = "package.json";
const PACKAGE_JSON_DEPENDENCY_FIELDS = [
"dependencies",
"devDependencies",
"optionalDependencies",
"peerDependencies"
];
const collectPackages = () => {
const workspaceRoot = getWorkspaceRoot();
if (!workspaceRoot) {
throw new Error("not in a workspace");
}
const pnpmWorkspace = jsYaml.load(
node_fs.readFileSync(node_path.join(workspaceRoot, "pnpm-workspace.yaml"), { encoding: "utf8" })
);
const workspacePackageJsonPath = node_path.join(workspaceRoot, PACKAGE_JSON_NAME);
const workspacePackageJson = JSON.parse(
node_fs.readFileSync(workspacePackageJsonPath, { encoding: "utf8" })
);
let workspaces = normalizePackageJsonWorkspacesField(workspacePackageJson.workspaces);
if (pnpmWorkspace.packages) {
workspaces = [...workspaces, ...pnpmWorkspace.packages];
}
const packagePaths = glob.globSync(workspaces, {
ignore: ["node_modules"],
cwd: workspaceRoot,
absolute: true
}).filter((path) => node_fs.statSync(path).isDirectory());
const workspacePackage = {
packageKind: "root",
packagePath: workspaceRoot,
packageJson: workspacePackageJson,
packageJsonPath: workspacePackageJsonPath,
workspacePackagePatterns: workspaces,
packagePathFromRootPackage: "."
};
const subPackages = packagePaths.map((packagePath) => {
const packageJsonPath = node_path.join(packagePath, PACKAGE_JSON_NAME);
try {
return {
packageKind: "regular",
packagePath: packagePath.toString(),
packageJsonPath,
packageJson: JSON.parse(
node_fs.readFileSync(packageJsonPath, { encoding: "utf8" })
),
packagePathFromRootPackage: node_path.relative(workspaceRoot, node_path.dirname(packageJsonPath))
};
} catch {
return void 0;
}
}).filter((pkg) => !!pkg);
return { workspacePackage, subPackages };
};
const getWorkspaceRoot = (cwd = process.cwd()) => {
return collectPackageJsonPathsUpDirectoryTree(cwd)[0];
};
const collectPackageJsonPathsUpDirectoryTree = (cwd = process.cwd()) => {
return collectPackageJsonPathsUpDirectoryTreeInternal(cwd);
};
const collectPackageJsonPathsUpDirectoryTreeInternal = (cwd, collection = []) => {
const path = node_path.normalize(cwd);
if (node_fs.existsSync(node_path.join(path, "package.json"))) {
collection.unshift(path);
}
const parentPath = node_path.join(path, "..");
if (parentPath !== path) {
return collectPackageJsonPathsUpDirectoryTreeInternal(parentPath, collection);
}
return collection;
};
const normalizePackageJsonWorkspacesField = (packageJsonWorkspaces) => {
if (Array.isArray(packageJsonWorkspaces)) {
return packageJsonWorkspaces;
} else if (packageJsonWorkspaces) {
return [
...packageJsonWorkspaces.packages ?? [],
...packageJsonWorkspaces.nohoist ?? []
];
} else {
return [];
}
};
const createGenericUpdater = (packages) => {
return {
readVersion: (contents) => {
const results = [...contents.matchAll(/"version": "(.*)"/g)];
return results[0]?.[1] ?? "0.0.0";
},
writeVersion: (contents, version) => {
return packages.map((localPackage) => localPackage.packageJson.name).filter((name) => !!name).reduce(
(r, localPackageName) => r.replaceAll(
new RegExp(`"${localPackageName}": "(workspace:)([~^])?.*"`, "g"),
`"${localPackageName}": "$1$2"`
// the workspace protocol does not include versions but keeps the specifier. This is how pnpm leaves them on install.
).replaceAll(
new RegExp(`"${localPackageName}": "(?!workspace:)([~^])?.*"`, "g"),
`"${localPackageName}": "$1${version}"`
).replaceAll(
new RegExp(`${localPackageName}@([^s
\r]+)`, "g"),
`${localPackageName}@${version}`
),
contents.replace(/"version": ".*"/, `"version": "${version}"`)
);
}
};
};
const workspaceDependencyVersionRegexp = /^(workspace:)([\^~])?.*$/g;
const nonWorkspaceDependencyVersionRegexp = /^(?!workspace:)([\^~])?.*$/g;
const createPackageJsonUpdater = (packages) => {
return {
readVersion: (contents) => {
const results = [...contents.matchAll(/"version": "(.*)"/g)];
return results[0]?.[1] ?? "0.0.0";
},
writeVersion: (contents, version) => {
const packageJson = JSON.parse(contents);
const indent = " ";
const newline = contents.includes("\r\n") ? "\r\n" : "\n";
packageJson.version = version;
for (const localPackageName of packages.map((localPackage) => localPackage.packageJson.name).filter((name) => !!name)) {
for (const dependencyField of PACKAGE_JSON_DEPENDENCY_FIELDS) {
const dependencies = packageJson[dependencyField];
const localDependency = dependencies?.[localPackageName];
if (dependencies && localDependency) {
dependencies[localPackageName] = localDependency.replaceAll(workspaceDependencyVersionRegexp, "$1$2").replaceAll(nonWorkspaceDependencyVersionRegexp, `$1${version}`);
}
}
}
const json = JSON.stringify(packageJson, void 0, indent);
if (newline === "\r\n") {
return json.replaceAll("\n", "\r\n") + "\r\n";
}
return json + "\n";
}
};
};
const createStandardVersionConfig = () => {
const { workspacePackage, subPackages } = collectPackages();
const packageJsonUpdater = createPackageJsonUpdater(subPackages);
const genericUpdater = createGenericUpdater(subPackages);
return {
scripts: {
postbump: "pnpm install",
prechangelog: "git add pnpm-lock.yaml"
},
bumpFiles: [
{
filename: workspacePackage.packageJsonPath,
updater: packageJsonUpdater
},
{
filename: node_path.join(workspacePackage.packagePath, "readme.md"),
updater: genericUpdater
},
...subPackages.map((pkg) => ({
filename: pkg.packageJsonPath,
updater: packageJsonUpdater
})),
...subPackages.map((pkg) => ({
filename: node_path.join(pkg.packagePath, "readme.md"),
updater: genericUpdater
}))
]
};
};
exports.collectPackages = collectPackages;
exports.createStandardVersionConfig = createStandardVersionConfig;