const { join } = require('path') const { readFileSync, writeFileSync, existsSync, readdirSync, lstatSync, copyFileSync, mkdirSync, rmSync } = require('fs') const { spawnSync } = require('child_process') /** * * @param {Record} current * @param {Record} template * @param {string[]|undefined} skip * @returns */ function update(current, template, skip) { if (template !== undefined && Object.keys(template).length > 0) { let value = current ?? {} for (const [k, v] of Object.entries(template)) { if (skip !== undefined) { console.log('check ', k, 'in', skip) if (skip?.includes(k)) { continue } } if (value[k] !== v) { console.log('updating ', k, 'to', v) } value[k] = v } return value } return current } function copyFiles(source, target, replaces) { const files = readdirSync(source) for (const f of files) { const sourceFile = join(source, f) const targetFile = join(target, f) if (lstatSync(sourceFile).isDirectory()) { if (!existsSync(targetFile)) { mkdirSync(targetFile, { recursive: true }) } copyFiles(sourceFile, targetFile, replaces) } else { if (f.startsWith('~')) { // File should be removed from target folder const newTargetFile = join(target, f.substring(1)) if (existsSync(newTargetFile)) { rmSync(newTargetFile) } } else { if (replaces.includes(targetFile) && existsSync(targetFile)) { // We need replace content of file as defined. rmSync(targetFile) } if (!existsSync(targetFile)) { // Copy if not exists. copyFileSync(sourceFile, targetFile) } } } } } function updatePackage(packageRoot, templates) { const pkgFile = join(packageRoot, 'package.json') const packageSource = readFileSync(pkgFile) const currentPackage = JSON.parse(packageSource) let template for (const t of templates) { if (t.isDefault && currentPackage.template === undefined) { template = t } // Allow to configure explicit template selection if (currentPackage.template === t.package.name) { template = t break } if (t.package.peerDependencies !== undefined && currentPackage.template === undefined) { const peers = Object.keys(t.package.peerDependencies) const deps = Object.keys(currentPackage.dependencies ?? {}) const devDeps = Object.keys(currentPackage.devDependencies ?? {}) if (peers.length > 0 && (peers.every((v) => deps.includes(v)) || peers.every((v) => devDeps.includes(v)))) { // If we had every peer dep in package. template = t } } } if (template === undefined) { console.warn('No template found for package', currentPackage.template) return } console.log('updating => ', currentPackage.name, ' with template', template.package.name) const packageJson = template.package currentPackage.devDependencies = update(currentPackage.devDependencies, packageJson.devDependencies) currentPackage.dependencies = update(currentPackage.dependencies, packageJson.dependencies) if (template.package['#clean'] !== undefined) { for (const d of template.package['#clean']) { if (currentPackage.devDependencies) { delete currentPackage.devDependencies[d] } if (currentPackage.dependencies) { delete currentPackage.dependencies[d] } } } currentPackage.scripts = update(currentPackage.scripts, packageJson.scripts, currentPackage['#override']) // Replace files section currentPackage.files = packageJson.files if (template.package['#overrideKeys'] !== undefined) { for (const k of template.package['#overrideKeys']) { const v = packageJson[k] console.log(k, v) if (v) { currentPackage[k] = v } } } const preferedOrder = [ 'name', 'version', 'main', 'svelte', 'types', 'files', 'author', 'template', 'license', 'scripts', 'devDependencies', 'dependencies', 'repository', 'publishConfig' ] Object.keys(currentPackage).forEach((it) => { if (!preferedOrder.includes(it)) { preferedOrder.push(it) } }) const ordered = preferedOrder.reduce((obj, key) => { if (currentPackage[key] !== undefined) { obj[key] = currentPackage[key] } return obj }, {}) const newPackage = JSON.stringify(ordered, undefined, 2) if (packageSource !== newPackage) { writeFileSync(pkgFile, newPackage.trim() + '\n') } // Copy missing files. let replaces = [...(template.package['#replaces'] ?? [])] // We need to remove #ignores from replaces if (currentPackage['#ignore'] !== undefined) { replaces = replaces.filter((f) => !currentPackage['#ignore'].includes(f)) } copyFiles( template.root, packageRoot, replaces.map((f) => join(packageRoot, f)) ) // Clean some folders for (const p of template.package['#removes'] ?? []) { if (existsSync(join(packageRoot, p))) { rmSync(join(packageRoot, p), { recursive: true }) } } } const ignoreRoots = ['communication'] function listPackages() { const out = spawnSync('rush', ['list', '--json'], { encoding: 'utf8' }).stdout const projects = JSON.parse(out) return projects.projects.map((it) => it.fullPath).filter((it) => !ignoreRoots.some((qt) => it.includes(qt))) } console.log('running at:', __dirname) const templates = readdirSync(__dirname) .map((d) => join(__dirname, d)) .filter((d) => lstatSync(d).isDirectory() && existsSync(join(d, 'package.json'))) .map((d) => ({ root: d, isDefault: d.endsWith('default'), package: JSON.parse(readFileSync(join(d, 'package.json'))) })) console.log( 'Detected templates:', templates.map((t) => t.package.name + ':' + t.package.version) ) const projects = listPackages() for (const p of projects) { updatePackage(p, templates) }