const fs = require('node:fs') const path = require('node:path') const yaml = require('js-yaml') const prettier = require('prettier') const template = 'projects/start-sdk/docs/package-template' const upstream = 'Start9Labs/start-technologies/' const artifactHost = 'https://data.forgejo.org/forgejo' const header = '# Generated by scripts/generate-forgejo-workflows.cjs; edit the GitHub sources instead.\n' function generatedPaths(root) { return ['.forgejo', `${template}/.forgejo`].flatMap(directory => { const absolute = path.join(root, directory) if (!fs.existsSync(absolute)) return [] return fs .readdirSync(absolute, { recursive: true }) .filter( file => /\.ya?ml$/.test(file) && fs.readFileSync(path.join(absolute, file), 'utf8').startsWith(header), ) .map(file => `${directory}/${file}`) }) } async function generate(root) { const outputs = new Map() const visited = new Set() const config = JSON.parse( fs.readFileSync(path.join(root, '.prettierrc.json'), 'utf8'), ) const readYaml = file => yaml.load(fs.readFileSync(path.join(root, file), 'utf8')) function portUses(uses) { const prefix = `${upstream}.github/` if (uses.startsWith(prefix) && uses.endsWith('@master')) { const source = uses.slice(upstream.length, -'@master'.length) const manifest = source.startsWith('.github/workflows/') ? source : `${source}/action.yml` const document = readYaml(manifest) if (document.jobs || document.runs.steps?.some(step => step.uses)) { portFile(manifest, manifest.replace('.github/', '.forgejo/')) return `https://github.com/${upstream}${source.replace('.github/', '.forgejo/')}@master` } } if (uses.startsWith('actions/upload-artifact@')) return `${artifactHost}/upload-artifact@v4` if (uses.startsWith('actions/download-artifact@')) return `${artifactHost}/download-artifact@v4` return uses.startsWith('https://') || uses.startsWith('./') || uses.startsWith('docker://') ? uses : `https://github.com/${uses}` } function portStep(step, job) { if (!step.uses) return if (step.uses === `${upstream}.github/actions/upload-each@master`) { step.name = 'Upload s9pks' step.uses = `${artifactHost}/upload-artifact@v4` const { pattern, ...options } = step.with step.with = { ...options, name: job.strategy?.matrix ? "s9pks-${{ matrix.target || 'default' }}" : 's9pks', path: pattern, } } else if (step.uses.startsWith('softprops/action-gh-release@')) { step.name = 'Create Forgejo release' step.uses = `https://github.com/${upstream}.github/actions/forgejo-release@master` step.with.token = '${{ github.token }}' } else { step.uses = portUses(step.uses) } } function portFile(source, destination) { if (visited.has(destination)) return visited.add(destination) const document = readYaml(source) for (const job of Object.values(document.jobs || {})) { job['runs-on'] = 'ubuntu-latest' if (job.uses) job.uses = portUses(job.uses) for (const step of job.steps || []) portStep(step, job) } for (const step of document.runs?.steps || []) portStep(step, {}) outputs.set(destination, document) } for (const filename of fs .readdirSync(path.join(root, template, '.github/workflows')) .sort()) { if (/\.ya?ml$/.test(filename)) { portFile( `${template}/.github/workflows/${filename}`, `${template}/.forgejo/workflows/${filename}`, ) } } for (const [destination, document] of outputs) { const text = header + yaml.dump(document, { lineWidth: -1, noRefs: true, quotingType: "'" }) outputs.set( destination, await prettier.format(text, { ...config, parser: 'yaml' }), ) } return outputs } async function main() { const root = path.resolve(__dirname, '..') const outputs = await generate(root) const check = process.argv.includes('--check') for (const file of generatedPaths(root).filter(file => !outputs.has(file))) { if (check) throw new Error(`${file} is obsolete; regenerate Forgejo workflows`) fs.unlinkSync(path.join(root, file)) } for (const [destination, text] of outputs) { const file = path.join(root, destination) if (check) { if (!fs.existsSync(file) || fs.readFileSync(file, 'utf8') !== text) { throw new Error( `${destination} is stale; run node scripts/generate-forgejo-workflows.cjs`, ) } } else { fs.mkdirSync(path.dirname(file), { recursive: true }) fs.writeFileSync(file, text) } } console.log( `${check ? 'Checked' : 'Generated'} ${outputs.size} Forgejo workflows/actions`, ) } module.exports = { generate, generatedPaths } if (require.main === module) main().catch(error => { console.error(error) process.exitCode = 1 })