const gulp = require('gulp'); const fs = require('fs-extra'); const path = require('path'); const chalk = require('chalk'); const stringify = require('json-stringify-pretty-compact'); const typescript = require('typescript'); const ts = require('gulp-typescript'); const argv = require('yargs').argv; function getManifest() { const json = {}; if (fs.existsSync('src')) { json.root = 'src'; } else { json.root = 'dist'; } const modulePath = path.join(json.root, 'module.json'); if (fs.existsSync(modulePath)) { json.file = fs.readJSONSync(modulePath); json.name = 'module.json'; } else { return; } return json; } /** * TypeScript transformers * @returns {typescript.TransformerFactory} */ function createTransformer() { /** * @param {typescript.Node} node */ function shouldMutateModuleSpecifier(node) { if (!typescript.isImportDeclaration(node) && !typescript.isExportDeclaration(node)) return false; if (node.moduleSpecifier === undefined) return false; if (!typescript.isStringLiteral(node.moduleSpecifier)) return false; if (!node.moduleSpecifier.text.startsWith('./') && !node.moduleSpecifier.text.startsWith('../')) return false; if (path.extname(node.moduleSpecifier.text) !== '') return false; return true; } /** * Transforms import/export declarations to append `.js` extension * @param {typescript.TransformationContext} context */ function importTransformer(context) { return (node) => { /** * @param {typescript.Node} node */ function visitor(node) { if (shouldMutateModuleSpecifier(node)) { if (typescript.isImportDeclaration(node)) { const newModuleSpecifier = typescript.createLiteral(`${node.moduleSpecifier.text}.js`); return typescript.updateImportDeclaration( node, node.decorators, node.modifiers, node.importClause, newModuleSpecifier, ); } else if (typescript.isExportDeclaration(node)) { const newModuleSpecifier = typescript.createLiteral(`${node.moduleSpecifier.text}.js`); return typescript.updateExportDeclaration( node, node.decorators, node.modifiers, node.exportClause, newModuleSpecifier, ); } } return typescript.visitEachChild(node, visitor, context); } return typescript.visitNode(node, visitor); }; } return importTransformer; } const tsConfig = ts.createProject('tsconfig.json', { getCustomTransformers: (_program) => ({ after: [createTransformer()], }), }); /********************/ /* BUILD */ /********************/ /** * Build TypeScript */ function buildTS() { return gulp.src('src/**/*.ts').pipe(tsConfig()).pipe(gulp.dest('dist')); } /** * Copy static files */ async function copyFiles() { const statics = ['lang', 'module.json']; try { for (const file of statics) { if (fs.existsSync(path.join('src', file))) { await fs.copy(path.join('src', file), path.join('dist', file)); } } return Promise.resolve(); } catch (err) { Promise.reject(err); } } /** * Watch for changes for each build step */ function buildWatch() { gulp.watch('src/**/*.ts', { ignoreInitial: false }, buildTS); gulp.watch( ['src/fonts', 'src/lang', 'src/templates', 'src/*.json', 'src/packs'], { ignoreInitial: false }, copyFiles, ); } /********************/ /* CLEAN */ /********************/ /** * Remove built files from `dist` folder * while ignoring source files */ async function clean() { const name = path.basename(path.resolve('.')); const files = []; // If the project uses TypeScript if (fs.existsSync(path.join('src', 'module', `${name}.ts`))) { files.push('lang', 'module', `${name}.js`, 'module.json'); } console.log(' ', chalk.yellow('Files to clean:')); console.log(' ', chalk.blueBright(files.join('\n '))); // Attempt to remove the files try { for (const filePath of files) { await fs.remove(path.join('dist', filePath)); } return Promise.resolve(); } catch (err) { Promise.reject(err); } } /********************/ /* LINK */ /********************/ /** * Link build to User Data folder */ async function linkUserData() { const name = path.basename(path.resolve('.')); const config = fs.readJSONSync('foundryconfig.json'); let destDir; try { if ( fs.existsSync(path.resolve('.', 'dist', 'module.json')) || fs.existsSync(path.resolve('.', 'src', 'module.json')) ) { destDir = 'modules'; } else { throw Error(`Could not find ${chalk.blueBright('module.json')}`); } let linkDir; if (config.dataPath) { if (!fs.existsSync(path.join(config.dataPath, 'Data'))) throw Error('User Data path invalid, no Data directory found'); linkDir = path.join(config.dataPath, 'Data', destDir, name); } else { throw Error('No User Data path defined in foundryconfig.json'); } if (argv.clean || argv.c) { console.log(chalk.yellow(`Removing build in ${chalk.blueBright(linkDir)}`)); await fs.remove(linkDir); } else if (!fs.existsSync(linkDir)) { console.log(chalk.green(`Copying build to ${chalk.blueBright(linkDir)}`)); await fs.symlink(path.resolve('./dist'), linkDir); } return Promise.resolve(); } catch (err) { Promise.reject(err); } } /*********************/ /* PACKAGE */ /*********************/ /** * Update version and URLs in the manifest JSON */ function updateManifest(cb) { const packageJson = fs.readJSONSync('package.json'); const packageLockJson = fs.readJSONSync('package-lock.json'); const manifest = getManifest(); if (!manifest) cb(Error(chalk.red('Manifest JSON not found'))); try { const version = argv.update || argv.u; /* Update version */ const versionMatch = /^(\d{1,}).(\d{1,}).(\d{1,})$/; const currentVersion = manifest.file.version; let targetVersion = ''; if (!version) { cb(Error('Missing version number')); } if (versionMatch.test(version)) { targetVersion = version; } else { targetVersion = currentVersion.replace(versionMatch, (substring, major, minor, patch) => { console.log(substring, Number(major) + 1, Number(minor) + 1, Number(patch) + 1); if (version === 'major') { return `${Number(major) + 1}.0.0`; } else if (version === 'minor') { return `${major}.${Number(minor) + 1}.0`; } else if (version === 'patch') { return `${major}.${minor}.${Number(patch) + 1}`; } else { return ''; } }); } if (targetVersion === '') { return cb(Error(chalk.red('Error: Incorrect version arguments.'))); } if (targetVersion === currentVersion) { return cb(Error(chalk.red('Error: Target version is identical to current version.'))); } console.log(`Updating version number to '${targetVersion}'`); packageJson.version = targetVersion; packageLockJson.version = targetVersion; manifest.file.version = targetVersion; /* Update URL */ const result = `https://git.f3l.de/ghost/${packageJson.name}/-/jobs/artifacts/${targetVersion}/download?job=build`; manifest.file.download = result; const prettyProjectJson = stringify(manifest.file, { maxLength: 40, indent: 4, }) + '\n'; fs.writeJSONSync('package.json', packageJson, { spaces: 4 }); fs.writeJSONSync('package-lock.json', packageLockJson, { spaces: 4 }); fs.writeFileSync(path.join(manifest.root, manifest.name), prettyProjectJson, 'utf8'); return cb(); } catch (err) { cb(err); } } const execBuild = gulp.parallel(buildTS, copyFiles); exports.build = gulp.series(clean, execBuild); exports.watch = buildWatch; exports.clean = clean; exports.link = linkUserData; exports.updateManifest = updateManifest;