UNPKG

clean-publish

Version:
253 lines (214 loc) 6.09 kB
import { promises as fs } from 'node:fs' import { basename, join } from 'node:path' import picomatch from 'picomatch' import { x } from 'tinyexec' import { glob, isDynamicPattern } from 'tinyglobby' import IGNORE_FIELDS from './exception/ignore-fields.js' import IGNORE_FILES from './exception/ignore-files.js' import NPM_SCRIPTS from './exception/npm-scripts.js' import { copy, deleteProperty, filterObjectByKey, isObject, pathToKeys, readJSON, remove, writeJSON } from './utils.js' // https://pnpm.io/package_json#publishconfig const PUBLISH_CONFIG_FIELDS = [ 'bin', 'main', 'exports', 'types', 'typings', 'module', 'browser', 'esnext', 'es2015', 'unpkg', 'umd:main', 'typesVersions', 'cpu', 'os' ] export function readPackageJSON() { return readJSON('package.json') } export function writePackageJSON(directoryName, packageJSON) { return writeJSON(join(directoryName, 'package.json'), packageJSON) } function applyPublishConfig(packageJson) { if (!packageJson.publishConfig) { return packageJson } const publishConfig = { ...packageJson.publishConfig } PUBLISH_CONFIG_FIELDS.forEach(field => { if (publishConfig[field]) { packageJson[field] = publishConfig[field] delete publishConfig[field] } }) if (!Object.keys(publishConfig).length) { // delete property by destructuring // eslint-disable-next-line no-unused-vars const { publishConfig: _, ...pkg } = packageJson return pkg } return { ...packageJson, publishConfig } } export function clearPackageJSON(packageJson, inputIgnoreFields) { const ignoreFields = inputIgnoreFields ? IGNORE_FIELDS.concat(inputIgnoreFields) : IGNORE_FIELDS const cleanPackageJSON = structuredClone(applyPublishConfig(packageJson)) ignoreFields.forEach(field => deleteProperty(cleanPackageJSON, pathToKeys(field)) ) if (packageJson.scripts && !ignoreFields.includes('scripts')) { cleanPackageJSON.scripts = filterObjectByKey(packageJson.scripts, script => NPM_SCRIPTS.includes(script) ) if ( cleanPackageJSON.scripts.publish && /^clean-publish( |$)/.test(cleanPackageJSON.scripts.publish) ) { // "custom" publish script is actually calling clean-publish delete cleanPackageJSON.scripts.publish } } for (const i in cleanPackageJSON) { if ( isObject(cleanPackageJSON[i]) && Object.keys(cleanPackageJSON[i]).length === 0 ) { delete cleanPackageJSON[i] } } return cleanPackageJSON } export function createIgnoreMatcher(ignorePattern) { if (ignorePattern instanceof RegExp) { return filename => !ignorePattern.test(filename) } if (isDynamicPattern(ignorePattern)) { const isMatch = picomatch(ignorePattern) return (_filename, path) => !isMatch(path) } return filename => filename !== ignorePattern } export function createFilesFilter(ignoreFiles) { const ignorePatterns = ignoreFiles ? IGNORE_FILES.concat(ignoreFiles).filter(Boolean) : IGNORE_FILES const filter = ignorePatterns.reduce((next, ignorePattern) => { const ignoreMatcher = createIgnoreMatcher(ignorePattern) if (!next) { return ignoreMatcher } return (filename, path) => ignoreMatcher(filename, path) && next(filename, path) }, null) return path => { const filename = basename(path) return filter(filename, path) } } export async function copyFiles(tempDir, filter) { const rootFiles = await fs.readdir('./') return Promise.all( rootFiles.map(async file => { if (file !== tempDir) { await copy(file, join(tempDir, file), { filter }) } }) ) } export async function publish( cwd, { access, dryRun, packageManager, packageManagerOptions = [], tag, stage } ) { const commands = stage ? ['stage', 'publish'] : ['publish'] const args = [...commands, ...packageManagerOptions] if (access) args.push('--access', access) if (tag) args.push('--tag', tag) if (dryRun) args.push('--dry-run') const exec = await x(packageManager, args, { nodeOptions: { cwd, stdio: 'inherit' } }) return { code: exec.exitCode, signal: null } } export async function createTempDirectory(name) { if (name) { try { await fs.mkdir(name) } catch (err) { if (err.code === 'EEXIST') { throw new Error(`Temporary directory "${name}" already exists.`, { cause: err }) } } return name } return await fs.mkdtemp('tmp') } export function removeTempDirectory(directoryName) { return remove(directoryName) } export async function runScript(script, ...args) { const exec = await x(script, args, { nodeOptions: { stdio: 'inherit' } }) return exec.exitCode === 0 } export function getReadmeUrlFromRepository(repository) { const repoUrl = typeof repository === 'object' ? repository.url : repository if (repoUrl) { const name = repoUrl.match(/[^/:]+\/[^/:]+$/)?.[0]?.replace(/\.git$/, '') return `https://github.com/${name}#readme` } return null } export async function cleanDocs(drectoryName, repository, homepage) { const readmePath = join(drectoryName, 'README.md') const readme = await fs.readFile(readmePath) const readmeUrl = getReadmeUrlFromRepository(repository) if (homepage || readmeUrl) { const cleaned = readme.toString().split(/\n##\s*\w/m)[0] + '\n## Docs\n' + `Read full docs **[here](${homepage || readmeUrl})**.\n` await fs.writeFile(readmePath, cleaned) } } export async function cleanComments(drectoryName) { const files = await glob(['**/*.js'], { cwd: drectoryName }) await Promise.all( files.map(async i => { const file = join(drectoryName, i) const content = await fs.readFile(file) const cleaned = content .toString() .replace(/\s*\/\/.*\n/gm, '\n') .replace(/\s*\/\*[^/]+\*\/\n?/gm, '\n') .replace(/\n+/gm, '\n') .replace(/^\n+/gm, '') await fs.writeFile(file, cleaned) }) ) }