1
0
Fork 0
freesewing/scripts/reconfigure.js
Joost De Cock 9aef847147 chore: Install plugin dev dependencies during workflow
This updates the automated generation for github workflows
to (also) install the dev dependencies for plugins.

This should resolve issues with workflows failing.
Or at the very least bring us closer to them working.

Shout-out to Jason for the peer pressure :)
2022-01-14 13:08:52 +01:00

544 lines
16 KiB
JavaScript

/* eslint-disable no-console */
const path = require('path')
const fs = require('fs')
const fse = require('fs-extra')
const glob = require('glob')
const yaml = require('js-yaml')
const chalk = require('chalk')
const handlebars = require('handlebars')
const Mustache = require('mustache')
const { version } = require('../lerna.json')
const capitalize = require('@freesewing/utils/capitalize')
const repoPath = process.cwd()
const config = {
repoPath,
defaults: readConfigFile('defaults.yaml'),
descriptions: readConfigFile('descriptions.yaml'),
keywords: readConfigFile('keywords.yaml'),
badges: readConfigFile('badges.yaml'),
scripts: readConfigFile('scripts.yaml'),
changelog: readConfigFile('changelog.yaml'),
changetypes: ['Added', 'Changed', 'Deprecated', 'Removed', 'Fixed', 'Security'],
dependencies: readConfigFile('dependencies.yaml', { version }),
exceptions: readConfigFile('exceptions.yaml'),
templates: {
pkg: readTemplateFile('package.dflt.json'),
rollup: readTemplateFile('rollup.config.dflt.js'),
changelog: readTemplateFile('changelog.dflt.md'),
readme: readTemplateFile('readme.dflt.md')
}
}
const packages = glob.sync('*', {
cwd: path.join(config.repoPath, 'packages')
})
const contributors = fs.readFileSync(path.join(repoPath, 'CONTRIBUTORS.md'), 'utf-8')
const acconfig = JSON.parse(fs.readFileSync(path.join(repoPath, '.all-contributorsrc'), 'utf-8'))
const mainReadme = Mustache.render(
fs.readFileSync(path.join(repoPath, 'config', 'templates', 'readme.main.md'), 'utf-8'),
{ allcontributors: acconfig.contributors.length }
)
fs.writeFileSync(path.join(repoPath, 'README.md'), mainReadme + contributors)
validate(packages, config)
reconfigure(packages, config)
process.exit()
/**
* Reads a template file
*/
function readTemplateFile(file) {
return fs.readFileSync(path.join(repoPath, 'config', 'templates', file), 'utf-8')
}
/**
* Reads a pattern example file
*/
function readExampleFile(file, subdir = false) {
return fs.readFileSync(
subdir
? path.join(
repoPath,
'packages',
'create-freesewing-pattern',
'template',
'default',
'example',
file
)
: path.join(
repoPath,
'packages',
'create-freesewing-pattern',
'template',
'default',
'example',
subdir,
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(repoPath, 'config', file), 'utf-8'), replace)
)
return yaml.load(fs.readFileSync(path.join(repoPath, '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(repoPath, 'packages', pkg, 'info.md'), 'utf-8')
} catch (err) {
return ''
}
return markup
}
/**
* Figure out what sort of package this is.
* Returns a string, one of:
* - pattern
* - plugin
* - other
*/
function packageType(pkg, config) {
if (pkg.substring(0, 7) === 'plugin-') return 'plugin'
if (config.descriptions[pkg].substring(0, 21) === 'A FreeSewing pattern ') return 'pattern'
return 'other'
}
/**
* Returns an array of keywords for a package
*/
function keywords(pkg, config, type) {
if (typeof config.keywords[pkg] !== 'undefined') return config.keywords[pkg]
if (typeof config.keywords[type] !== 'undefined') return config.keywords[type]
else {
console.log(
chalk.redBright.bold('Problem:'),
chalk.redBright(`No keywords for package ${pkg} which is of type ${type}`)
)
process.exit()
}
}
/**
* Returns an plain object of scripts for a package
*/
function scripts(pkg, config, type) {
let runScripts = {}
for (let key of Object.keys(config.scripts._)) {
runScripts[key] = Mustache.render(config.scripts._[key], {
name: pkg
})
}
if (typeof config.scripts._types[type] !== 'undefined') {
for (let key of Object.keys(config.scripts._types[type])) {
runScripts[key] = Mustache.render(config.scripts._types[type][key], {
name: pkg
})
}
}
if (typeof config.scripts[pkg] !== 'undefined') {
for (let key of Object.keys(config.scripts[pkg])) {
if (config.scripts[pkg][key] === '!') delete runScripts[key]
else
runScripts[key] = Mustache.render(config.scripts[pkg][key], {
name: pkg
})
}
}
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 deps(section, pkg, config, type) {
let dependencies = {}
if (
typeof config.dependencies._types[type] !== 'undefined' &&
typeof config.dependencies._types[type][section] !== 'undefined'
)
dependencies = config.dependencies._types[type][section]
if (typeof config.dependencies[pkg] === 'undefined') return dependencies
if (typeof config.dependencies[pkg][section] !== 'undefined')
return { ...dependencies, ...config.dependencies[pkg][section] }
return dependencies
}
/**
* These merely call deps() for the relevant dependency section
*/
function dependencies(pkg, config, type) {
return deps('_', pkg, config, type)
}
function devDependencies(pkg, config, type) {
return deps('dev', pkg, config, type)
}
function peerDependencies(pkg, config, type) {
return deps('peer', pkg, config, type)
}
/**
* Creates a package.json file for a package
*/
function packageConfig(pkg, config) {
let type = packageType(pkg, config)
let pkgConf = {}
// Let's keep these at the top
pkgConf.name = fullName(pkg, config)
pkgConf.version = version
pkgConf.description = config.descriptions[pkg]
pkgConf = {
...pkgConf,
...JSON.parse(Mustache.render(config.templates.pkg, { name: pkg }))
}
pkgConf.keywords = pkgConf.keywords.concat(keywords(pkg, config, type))
pkgConf.scripts = scripts(pkg, config, type)
pkgConf.dependencies = dependencies(pkg, config, type)
pkgConf.devDependencies = devDependencies(pkg, config, type)
pkgConf.peerDependencies = peerDependencies(pkg, config, type)
if (typeof config.exceptions.packageJson[pkg] !== 'undefined') {
pkgConf = {
...pkgConf,
...config.exceptions.packageJson[pkg]
}
for (let key of Object.keys(config.exceptions.packageJson[pkg])) {
if (config.exceptions.packageJson[pkg][key] === '!') delete pkgConf[key]
}
}
if (config.exceptions.namedExports.indexOf(pkg) !== -1) {
pkgConf.rollup.exports = 'named'
}
return pkgConf
}
/**
* Returns an string with the markup for badges in the readme file
*/
function badges(pkg, config) {
let markup = ''
for (let group of ['_all', '_social']) {
markup += "<p align='center'>"
for (let key of Object.keys(config.badges[group])) {
const name = (key === 'contributors')
? acconfig.contributors.length
: pkg
markup += formatBadge(config.badges[group][key], name, fullName(pkg, config))
}
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(pkg, config) {
if (config.exceptions.noNamespace.indexOf(pkg) !== -1) return pkg
else return `@freesewing/${pkg}`
}
/**
* Creates a README.md file for a package
*/
function readme(pkg, config) {
let markup = Mustache.render(config.templates.readme, {
fullname: fullName(pkg, config),
description: config.descriptions[pkg],
badges: badges(pkg, config),
info: readInfoFile(pkg),
contributors
})
return markup
}
/**
* Creates a CHANGELOG.md file for a package
*/
function changelog(pkg, config) {
let markup = Mustache.render(config.templates.changelog, {
fullname: pkg === 'global' ? 'FreeSewing (global)' : fullName(pkg, config),
changelog: pkg === 'global' ? globalChangelog(config) : packageChangelog(pkg, config)
})
return markup
}
/**
* Generates the global changelog data
*/
function globalChangelog(config) {
let markup = ''
for (let v in config.changelog) {
let changes = config.changelog[v]
markup += '\n## ' + v
if (v !== 'Unreleased') markup += ' (' + formatDate(changes.date) + ')'
markup += '\n\n'
for (let pkg of packages) {
let changed = false
for (let type of config.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(pkg, config) {
let log = {}
let version
let markup = ''
for (let v in config.changelog) {
version = v
let changes = config.changelog[v]
let changed = false
for (let type of config.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 (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.getMonth() + 1),
day = '' + d.getDate(),
year = d.getFullYear()
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(pkgs, config) {
console.log(chalk.blueBright('Validating package descriptions'))
for (let pkg of pkgs) {
if (typeof config.descriptions[pkg] !== 'string') {
console.log(
chalk.redBright.bold('Problem:'),
chalk.redBright(`No description for package ${pkg}`)
)
process.exit()
}
}
console.log(chalk.yellowBright.bold('Looks good'))
return true
}
/**
* Creates and 'example' directory for patterns,
* same result as what gets done by create-freesewing-pattern.
*/
function configurePatternExample(pkg, config) {
// Create example dir structure
let source = path.join(
config.repoPath,
'packages',
'create-freesewing-pattern',
'template',
'freesewing',
'example'
)
let dest = path.join(config.repoPath, 'packages', pkg, 'example')
fse.ensureDirSync(path.join(dest, 'src'))
fse.ensureDirSync(path.join(dest, 'public'))
// Copy files
for (let file of ['.babelrc', '.env'])
fs.copyFileSync(path.join(source, file), path.join(dest, file))
for (let file of ['index.js', 'serviceWorker.js', 'layout.css'])
fs.copyFileSync(path.join(source, 'src', file), path.join(dest, 'src', file))
fs.copyFileSync(
path.join(source, 'public', 'favicon.ico'),
path.join(dest, 'public', 'favicon.ico')
)
// Write templates
let replace = {
name: pkg,
version,
author: 'freesewing',
yarn: true,
language: 'en'
}
for (let file of ['package.json', 'README.md', 'netlify.toml']) {
let template = handlebars.compile(fs.readFileSync(path.join(source, file), 'utf-8'))
fs.writeFileSync(path.join(dest, file), template(replace))
}
for (let file of ['index.html', 'manifest.json', 'layout.css']) {
let template = handlebars.compile(fs.readFileSync(path.join(source, 'public', file), 'utf-8'))
fs.writeFileSync(path.join(dest, 'public', file), template(replace))
}
let template = handlebars.compile(fs.readFileSync(path.join(source, 'src', 'App.js'), 'utf-8'))
fs.writeFileSync(path.join(dest, 'src', 'App.js'), template(replace))
}
/**
* Adds unit tests for patterns and plugins
*/
function configurePkgUnitTests(type, pkg, config) {
// Create tests directory
const dest = path.join(config.repoPath, 'packages', pkg, 'tests')
fse.ensureDirSync(dest)
const source = path.join(config.repoPath, 'config', 'templates', 'tests', `${type}s`)
// Write templates
const peerdeps = peerDependencies(pkg, config, type)
const devdeps = devDependencies(pkg, config, type)
const replace = (type === 'pattern')
? {
version,
pattern: pkg,
Pattern: capitalize(pkg),
peerdeps: Object.keys(peerdeps)
.map((dep) => dep + '@' + peerdeps[dep])
.join(' ')
}
: {
version,
plugin: pkg,
Plugin: capitalize(pkg),
peerdeps: Object.keys(peerdeps)
.map((dep) => dep + '@' + peerdeps[dep])
.join(' '),
devdeps: Object.keys(devdeps)
.map((dep) => dep + '@' + devdeps[dep])
.join(' ')
}
for (const file of ['shared.test.mjs']) {
fs.writeFileSync(
path.join(dest, file),
Mustache.render(fs.readFileSync(path.join(source, file + '.template'), 'utf-8'), replace)
)
}
// Add workflow file for Github actions
fs.writeFileSync(
path.join(config.repoPath, '.github', 'workflows', `tests.${pkg}.yml`),
Mustache.render(
fs.readFileSync(
path.join(config.repoPath, 'config', 'templates', 'workflows', `tests.${type}.yml`),
'utf-8'
),
replace
)
)
}
/**
* Puts a package.json, rollup.config.js, README.md, and CHANGELOG.md
* into every subdirectory under the packages directory.
* Also creates an example dir for pattern packages, and writes
* the global CHANGELOG.md.
* New: Adds unit tests for patterns
*/
function reconfigure(pkgs, config) {
for (const pkg of pkgs) {
console.log(chalk.blueBright(`Reconfiguring ${pkg}`))
if (config.exceptions.customPackageJson.indexOf(pkg) === -1) {
const pkgConfig = packageConfig(pkg, config)
fs.writeFileSync(
path.join(config.repoPath, 'packages', pkg, 'package.json'),
JSON.stringify(pkgConfig, null, 2) + '\n'
)
}
if (config.exceptions.customRollup.indexOf(pkg) === -1) {
fs.writeFileSync(
path.join(config.repoPath, 'packages', pkg, 'rollup.config.js'),
config.templates.rollup
)
}
if (config.exceptions.customReadme.indexOf(pkg) === -1) {
fs.writeFileSync(path.join(config.repoPath, 'packages', pkg, 'README.md'), readme(pkg, config))
}
if (config.exceptions.customChangelog.indexOf(pkg) === -1) {
fs.writeFileSync(
path.join(config.repoPath, 'packages', pkg, 'CHANGELOG.md'),
changelog(pkg, config)
)
}
const type = packageType(pkg, config)
if (type === 'pattern') configurePatternExample(pkg, config)
if (['pattern', 'plugin'].indexOf(type) !== -1) configurePkgUnitTests(type, pkg, config)
}
fs.writeFileSync(path.join(config.repoPath, 'CHANGELOG.md'), changelog('global', config))
console.log(chalk.yellowBright.bold('All done.'))
}