
Recently I refactored the prebuild code. One thing I did not touch (yet) was the various site dependencies. They were then ported to NX over the previous system with build priorities we had in place. And while that was a welcome improvement, it did not really address the bigger picture: we don't actually need to build any of these dependencies to build the site. All we need to do is help NodeJS so it can resolve everything from the source. So this commit does that by adding a top-level run script `yarn buildsiteconfigure` which calls `yarn reconfigure` but with the BUILDSITE variable set. When that variable is set, the reconfigure script will skip all optional steps and generate a slightly different package.json file that tells NodeJS to looks the package from source, rather than to look for the build files. After running this script, the prebuild step for the various sites will run without problems, even on a fresh repo where nothing has ever been build. So I've updated the prebuild script in the sites to run this. For local development, please use `uyarn predev` so that you don't muck with all the package.json files. If you do so (by accident or not) a `yarn reconfigure` run will put everything back in its place.
478 lines
14 KiB
JavaScript
478 lines
14 KiB
JavaScript
import path from 'path'
|
|
import fs from 'fs'
|
|
import glob from 'glob'
|
|
import yaml from 'js-yaml'
|
|
import chalk from 'chalk'
|
|
import mustache from 'mustache'
|
|
import conf from '../lerna.json' assert { type: 'json' }
|
|
const { version } = conf
|
|
import { software, publishedTypes as types, designs, plugins } from '../config/software/index.mjs'
|
|
import { capitalize } from '../packages/core/src/index.mjs'
|
|
|
|
// Working directory
|
|
const cwd = process.cwd()
|
|
|
|
/*
|
|
* When we're building a site (on Vercel for example) SITEBUILD
|
|
* will be set and we'll do things differently to speed up the build.
|
|
* To make that check easy, we setup this SITEBUILD variable
|
|
*/
|
|
const SITEBUILD = process.env.SITEBUILD || false
|
|
|
|
if (SITEBUILD) console.log('Site build | Configure monorepo accordingly')
|
|
|
|
/*
|
|
* This object holds info about the repository
|
|
*/
|
|
const repo = {
|
|
path: cwd,
|
|
defaults: readConfigFile('defaults.yaml'),
|
|
keywords: readConfigFile('keywords.yaml'),
|
|
badges: SITEBUILD ? null : readConfigFile('badges.yaml'),
|
|
scripts: readConfigFile('scripts.yaml'),
|
|
changelog: SITEBUILD ? null : readConfigFile('changelog.yaml'),
|
|
changetypes: ['Breaking', 'Added', 'Changed', 'Deprecated', 'Removed', 'Fixed', 'Security'],
|
|
dependencies: readConfigFile('dependencies.yaml', { version }),
|
|
exceptions: readConfigFile('exceptions.yaml'),
|
|
templates: {
|
|
pkg: readTemplateFile('package.dflt.json'),
|
|
changelog: SITEBUILD ? null : readTemplateFile('changelog.dflt.md'),
|
|
readme: SITEBUILD ? null : readTemplateFile('readme.dflt.md'),
|
|
build: SITEBUILD ? null : readTemplateFile('build.dflt.mjs'),
|
|
pluginTests: SITEBUILD ? null : readTemplateFile('plugin.test.mjs'),
|
|
designTests: SITEBUILD ? null : readTemplateFile('design.test.mjs.mustache'),
|
|
data: SITEBUILD ? null : readTemplateFile('data.dflt.mjs.mustache'),
|
|
},
|
|
dirs: foldersByType(),
|
|
contributors: SITEBUILD ? null : fs.readFileSync(path.join(cwd, 'CONTRIBUTORS.md'), 'utf-8'),
|
|
ac: SITEBUILD
|
|
? null
|
|
: JSON.parse(fs.readFileSync(path.join(cwd, '.all-contributorsrc'), 'utf-8')),
|
|
}
|
|
|
|
/*
|
|
* Now let's get to work
|
|
*/
|
|
const log = process.stdout
|
|
|
|
// Step 0: Avoid symlink so Windows users don't complain
|
|
const copyThese = [
|
|
{
|
|
from: ['scripts', 'banner.mjs'],
|
|
to: ['packages', 'new-design', 'lib', 'banner.mjs'],
|
|
},
|
|
]
|
|
for (const cp of copyThese) {
|
|
fs.copyFile(path.join(repo.path, ...cp.from), path.join(repo.path, ...cp.to), () => null)
|
|
}
|
|
|
|
// Step 1: Generate main README file from template
|
|
if (!SITEBUILD) {
|
|
log.write(chalk.blueBright('Generating out main README file...'))
|
|
fs.writeFileSync(
|
|
path.join(repo.path, 'README.md'),
|
|
mustache.render(
|
|
fs.readFileSync(path.join(repo.path, 'config', 'templates', 'readme.main.md'), 'utf-8'),
|
|
{ allcontributors: repo.ac.contributors.length }
|
|
) + repo.contributors
|
|
)
|
|
log.write(chalk.green(' Done\n'))
|
|
}
|
|
|
|
// Step 2: Validate package configuration
|
|
if (!SITEBUILD) {
|
|
log.write(chalk.blueBright('Validating configuration...'))
|
|
if (validate()) log.write(chalk.green(' Done\n'))
|
|
}
|
|
|
|
// Step 3: Generate package.json, pkg.mjs, README, and CHANGELOG
|
|
log.write(chalk.blueBright('Generating package-specific files...'))
|
|
for (const pkg of Object.values(software)) {
|
|
fs.writeFileSync(
|
|
path.join(cwd, pkg.folder, pkg.name, 'package.json'),
|
|
JSON.stringify(packageJson(pkg), null, 2) + '\n'
|
|
)
|
|
if (!SITEBUILD) {
|
|
if (pkg.type !== 'site') {
|
|
fs.writeFileSync(
|
|
path.join(cwd, pkg.folder, pkg.name, 'data.mjs'),
|
|
mustache.render(repo.templates.data, { name: fullName(pkg.name), version })
|
|
)
|
|
fs.writeFileSync(path.join(cwd, pkg.folder, pkg.name, 'README.md'), readme(pkg))
|
|
if (repo.exceptions.customBuild.indexOf(pkg.name) === -1) {
|
|
fs.writeFileSync(path.join(cwd, pkg.folder, pkg.name, 'build.mjs'), repo.templates.build)
|
|
}
|
|
fs.writeFileSync(path.join(cwd, pkg.folder, pkg.name, 'CHANGELOG.md'), changelog(pkg))
|
|
}
|
|
}
|
|
}
|
|
log.write(chalk.green(' Done\n'))
|
|
|
|
// Step 4: Generate overall CHANGELOG.md
|
|
if (!SITEBUILD) fs.writeFileSync(path.join(repo.path, 'CHANGELOG.md'), changelog('global'))
|
|
|
|
// Step 5: Generate tests for designs and plugins
|
|
if (!SITEBUILD) {
|
|
for (const design in designs) {
|
|
fs.writeFileSync(
|
|
path.join(repo.path, 'designs', design, 'tests', 'shared.test.mjs'),
|
|
mustache.render(repo.templates.designTests, { name: design, Name: capitalize(design) })
|
|
)
|
|
}
|
|
for (const plugin in plugins) {
|
|
fs.writeFileSync(
|
|
path.join(repo.path, 'plugins', plugin, 'tests', 'shared.test.mjs'),
|
|
repo.templates.pluginTests
|
|
)
|
|
}
|
|
}
|
|
// All done
|
|
log.write(chalk.green(' All done\n'))
|
|
process.exit()
|
|
|
|
/*
|
|
* Generates a list of folders by type
|
|
*/
|
|
function foldersByType() {
|
|
const dirs = {}
|
|
for (const dir of types) {
|
|
dirs[dir] = glob.sync('*', { cwd: path.join(cwd, dir) })
|
|
}
|
|
|
|
return dirs
|
|
}
|
|
|
|
/**
|
|
* Reads a template file
|
|
*/
|
|
function readTemplateFile(file) {
|
|
return fs.readFileSync(path.join(cwd, 'config', 'templates', file), 'utf-8')
|
|
}
|
|
|
|
/**
|
|
* Reads a YAML config file, with mustache replacements if needed
|
|
*/
|
|
function readConfigFile(file, replace = false) {
|
|
if (replace)
|
|
return yaml.load(
|
|
mustache.render(fs.readFileSync(path.join(cwd, 'config', file), 'utf-8'), replace)
|
|
)
|
|
return yaml.load(fs.readFileSync(path.join(cwd, 'config', file), 'utf-8'))
|
|
}
|
|
|
|
/**
|
|
* Reads info.md from the package directory
|
|
* Returns its contents if it exists, or an empty string if not
|
|
*/
|
|
function readInfoFile(pkg) {
|
|
let markup = ''
|
|
try {
|
|
markup = fs.readFileSync(path.join(cwd, pkg.folder, pkg.name, 'info.md'), 'utf-8')
|
|
} catch (err) {
|
|
return ''
|
|
}
|
|
|
|
return markup
|
|
}
|
|
|
|
/**
|
|
* Returns an array of keywords for a package
|
|
*/
|
|
function keywords(pkg) {
|
|
if (pkg.type === 'site') return []
|
|
if (typeof repo.keywords[pkg.name] !== 'undefined') return repo.keywords[pkg.name]
|
|
if (typeof repo.keywords[pkg.type] !== 'undefined') return repo.keywords[pkg.type]
|
|
else {
|
|
console.log(
|
|
chalk.redBright.bold('Problem:'),
|
|
chalk.redBright(`No keywords for package ${pkg.name} which is of type ${pkg.type}`)
|
|
)
|
|
process.exit()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns an plain object of scripts for a package
|
|
*/
|
|
function scripts(pkg) {
|
|
let runScripts = {}
|
|
if (pkg.type !== 'site') {
|
|
for (const key of Object.keys(repo.scripts._)) {
|
|
runScripts[key] = mustache.render(repo.scripts._[key], {
|
|
name: pkg.name,
|
|
})
|
|
}
|
|
}
|
|
if (typeof repo.scripts._types[pkg.type] !== 'undefined') {
|
|
for (const key of Object.keys(repo.scripts._types[pkg.type])) {
|
|
runScripts[key] = mustache.render(repo.scripts._types[pkg.type][key], {
|
|
name: pkg.name,
|
|
})
|
|
}
|
|
}
|
|
if (typeof repo.scripts[pkg.name] !== 'undefined') {
|
|
for (const key of Object.keys(repo.scripts[pkg.name])) {
|
|
if (repo.scripts[pkg.name][key] === '!') delete runScripts[key]
|
|
else
|
|
runScripts[key] = mustache.render(repo.scripts[pkg.name][key], {
|
|
name: pkg.name,
|
|
})
|
|
}
|
|
}
|
|
|
|
// make windows versions of build prebuild scripts
|
|
runScripts.wbuild = runScripts.wbuild || runScripts.build
|
|
runScripts.prewbuild = runScripts.prewbuild || runScripts.prebuild
|
|
|
|
// make prebuild:all and windows versions of build:all and prebuild:all
|
|
if (runScripts['build:all'] !== undefined) {
|
|
runScripts['wbuild:all'] = runScripts['wbuild:all'] || (runScripts.wbuild && 'yarn wbuild')
|
|
runScripts['prebuild:all'] =
|
|
runScripts['prebuild:all'] || (runScripts.prebuild && 'yarn prebuild')
|
|
runScripts['prewbuild:all'] =
|
|
runScripts['prewbuild:all'] || (runScripts.prewbuild && 'yarn prewbuild')
|
|
}
|
|
|
|
return runScripts
|
|
}
|
|
|
|
/**
|
|
* Returns an plain object with the of dependencies for a package
|
|
* section is the key in the dependencies.yaml fine, one of:
|
|
*
|
|
* - _ (for dependencies)
|
|
* - dev (for devDependencies)
|
|
* - peer (for peerDependencies)
|
|
*
|
|
*/
|
|
function dependencies(section, pkg) {
|
|
let dependencies = {}
|
|
if (
|
|
typeof repo.dependencies._types[pkg.type] !== 'undefined' &&
|
|
typeof repo.dependencies._types[pkg.type][section] !== 'undefined'
|
|
)
|
|
dependencies = repo.dependencies._types[pkg.type][section]
|
|
if (typeof repo.dependencies[pkg.name] === 'undefined') return dependencies
|
|
if (typeof repo.dependencies[pkg.name][section] !== 'undefined')
|
|
return { ...dependencies, ...repo.dependencies[pkg.name][section] }
|
|
|
|
return dependencies
|
|
}
|
|
|
|
/**
|
|
* Creates a package.json file for a package
|
|
*/
|
|
function packageJson(pkg) {
|
|
let pkgConf = {}
|
|
// Let's keep these at the top
|
|
pkgConf.name = fullName(pkg.name)
|
|
pkgConf.version = version
|
|
pkgConf.description = pkg.description
|
|
pkgConf = {
|
|
...pkgConf,
|
|
...JSON.parse(mustache.render(repo.templates.pkg, { name: pkg.name })),
|
|
}
|
|
pkgConf.keywords = pkgConf.keywords.concat(keywords(pkg))
|
|
pkgConf.scripts = scripts(pkg)
|
|
/*
|
|
* If we building a site simply override the module entry so that we don't have
|
|
* to build any dependencies, but instead can just load them from source
|
|
*/
|
|
if (SITEBUILD) {
|
|
pkgConf.module = 'src/index.mjs'
|
|
pkgConf.exports = { '.': './src/index.mjs' }
|
|
}
|
|
if (repo.exceptions.skipTests.indexOf(pkg.name) !== -1) {
|
|
pkgConf.scripts.test = `echo "skipping tests for ${pkg.name}"`
|
|
pkgConf.scripts.testci = `echo "skipping tests for ${pkg.name}"`
|
|
}
|
|
pkgConf.dependencies = dependencies('_', pkg)
|
|
pkgConf.devDependencies = dependencies('dev', pkg)
|
|
pkgConf.peerDependencies = dependencies('peer', pkg)
|
|
if (typeof repo.exceptions.packageJson[pkg.name] !== 'undefined') {
|
|
pkgConf = {
|
|
...pkgConf,
|
|
...repo.exceptions.packageJson[pkg.name],
|
|
}
|
|
for (let key of Object.keys(repo.exceptions.packageJson[pkg.name])) {
|
|
if (repo.exceptions.packageJson[pkg.name][key] === '!') delete pkgConf[key]
|
|
}
|
|
}
|
|
|
|
if (pkg.type === 'site') {
|
|
delete pkgConf.keywords
|
|
delete pkgConf.type
|
|
delete pkgConf.module
|
|
delete pkgConf.exports
|
|
delete pkgConf.files
|
|
delete pkgConf.publishConfig
|
|
pkgConf.private = true
|
|
}
|
|
|
|
return pkgConf
|
|
}
|
|
|
|
/**
|
|
* Returns an string with the markup for badges in the readme file
|
|
*/
|
|
function badges(pkgName) {
|
|
let markup = ''
|
|
for (let group of ['_all', '_social']) {
|
|
markup += "<p align='center'>"
|
|
for (let key of Object.keys(repo.badges[group])) {
|
|
const name = key === 'contributors' ? repo.ac.contributors.length : pkgName
|
|
markup += formatBadge(repo.badges[group][key], name, fullName(pkgName))
|
|
}
|
|
markup += '</p>'
|
|
}
|
|
|
|
return markup
|
|
}
|
|
|
|
/**
|
|
* Formats a badge for a readme file
|
|
*/
|
|
function formatBadge(badge, name, fullname) {
|
|
return `<a
|
|
href="${mustache.render(badge.link, { name, fullname })}"
|
|
title="${mustache.render(badge.alt, { name, fullname })}"
|
|
><img src="${mustache.render(badge.img, { name, fullname })}"
|
|
alt="${mustache.render(badge.alt, { name, fullname })}"/>
|
|
</a>`
|
|
}
|
|
/**
|
|
* Returns the full (namespaced) name of a package
|
|
*/
|
|
function fullName(name) {
|
|
if (repo.exceptions.noNamespace.indexOf(name) !== -1) return name
|
|
else return `@freesewing/${name}`
|
|
}
|
|
|
|
/**
|
|
* Creates a README.md file for a package
|
|
*/
|
|
function readme(pkg) {
|
|
let markup = mustache.render(repo.templates.readme, {
|
|
fullname: fullName(pkg.name),
|
|
description: pkg.description,
|
|
badges: badges(pkg.name),
|
|
info: readInfoFile(pkg),
|
|
contributors: repo.contributors,
|
|
})
|
|
|
|
return markup
|
|
}
|
|
|
|
/**
|
|
* Creates a CHANGELOG.md file for a package
|
|
*/
|
|
function changelog(pkg) {
|
|
let markup = mustache.render(repo.templates.changelog, {
|
|
fullname: pkg === 'global' ? 'FreeSewing (global)' : fullName(pkg.name),
|
|
changelog: pkg === 'global' ? globalChangelog() : packageChangelog(pkg.name),
|
|
})
|
|
|
|
return markup
|
|
}
|
|
|
|
/**
|
|
* Generates the global changelog data
|
|
*/
|
|
function globalChangelog() {
|
|
let markup = ''
|
|
for (let v in repo.changelog) {
|
|
let changes = repo.changelog[v]
|
|
markup += '\n## ' + v
|
|
if (v !== 'Unreleased') markup += ' (' + formatDate(changes.date) + ')'
|
|
markup += '\n\n'
|
|
for (let pkg of ['global', ...Object.keys(software)]) {
|
|
let changed = false
|
|
for (let type of repo.changetypes) {
|
|
if (
|
|
typeof changes[type] !== 'undefined' &&
|
|
changes[type] !== null &&
|
|
typeof changes[type][pkg] !== 'undefined' &&
|
|
changes[type][pkg] !== null
|
|
) {
|
|
if (!changed) changed = ''
|
|
changed += '\n#### ' + type + '\n\n'
|
|
for (let change of changes[type][pkg]) changed += ' - ' + change + '\n'
|
|
}
|
|
}
|
|
if (changed) markup += '### ' + pkg + '\n' + changed + '\n'
|
|
}
|
|
}
|
|
|
|
return markup
|
|
}
|
|
|
|
/**
|
|
* Generates the changelog data for a package
|
|
*/
|
|
function packageChangelog(pkgName) {
|
|
let version
|
|
let markup = ''
|
|
for (let v in repo.changelog) {
|
|
version = v
|
|
let changes = repo.changelog[v]
|
|
let changed = false
|
|
for (let type of repo.changetypes) {
|
|
if (
|
|
changes[type] &&
|
|
(Array.isArray(changes[type][pkgName]) || Array.isArray(changes[type].all))
|
|
) {
|
|
if (!changed) changed = ''
|
|
changed += '\n### ' + type + '\n\n'
|
|
if (Array.isArray(changes[type][pkgName])) {
|
|
for (let change of changes[type][pkgName]) changed += ' - ' + change + '\n'
|
|
}
|
|
if (Array.isArray(changes[type].all)) {
|
|
for (let change of changes[type].all) changed += ' - ' + change + '\n'
|
|
}
|
|
}
|
|
}
|
|
if (v !== 'Unreleased' && changed) {
|
|
markup += '\n## ' + v
|
|
markup += ' (' + formatDate(changes.date) + ')'
|
|
markup += '\n'
|
|
markup += changed
|
|
}
|
|
}
|
|
|
|
markup += '\n\nThis is the **initial release**, and the start of this change log.\n'
|
|
if (version === '2.0.0')
|
|
markup += `
|
|
> Prior to version 2, FreeSewing was not a JavaScript project.
|
|
> As such, that history is out of scope for this change log.
|
|
`
|
|
|
|
return markup
|
|
}
|
|
|
|
function formatDate(date) {
|
|
let d = new Date(date),
|
|
month = '' + (d.getUTCMonth() + 1),
|
|
day = '' + d.getUTCDate(),
|
|
year = d.getUTCFullYear()
|
|
|
|
if (month.length < 2) month = '0' + month
|
|
if (day.length < 2) day = '0' + day
|
|
|
|
return [year, month, day].join('-')
|
|
}
|
|
|
|
/**
|
|
* Make sure we have (at least) a description for each package
|
|
*/
|
|
function validate() {
|
|
for (const type in repo.dirs) {
|
|
for (const dir of repo.dirs[type]) {
|
|
if (typeof software?.[dir]?.description !== 'string') {
|
|
log.write(chalk.redBright(` No description for package ${type}/${dir}` + '\n'))
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|