const gulp = require('gulp'); const prefix = require('gulp-autoprefixer'); const sourcemaps = require('gulp-sourcemaps'); const sass = require('gulp-sass')(require('sass')); const zip = require('gulp-zip'); const fs = require('fs'); const fetch = require('node-fetch'); const replace = require('gulp-replace'); const FormData = require('form-data'); /* ----------------------------------------- */ /* Export Tasks /* ----------------------------------------- */ exports.default = gulp.series( compileScss, watchUpdates ); exports.build = gulp.series( compileScss, checkVersion, ensureOutputDirExists, packageCompendiums, updateSystemJson, zipRelease ); exports.compile = gulp.series( compileScss, ensureOutputDirExists, packageCompendiums, ); exports.release = gulp.series( exports.build, uploadToPackageRegistry, publishToFoundry ); /* ----------------------------------------- */ /* Compile Sass /* ----------------------------------------- */ // Small error handler helper function. function handleError(err) { console.log(err.toString()); this.emit('end'); } const SYSTEM_SCSS = ["scss/**/*.scss"]; function compileScss() { // Configure options for sass output. For example, 'expanded' or 'nested' let options = { outputStyle: 'expanded' }; return gulp.src(SYSTEM_SCSS) .pipe( sass(options) .on('error', handleError) ) .pipe(prefix({ cascade: false })) .pipe(gulp.dest("./css")) } /* ----------------------------------------- */ /* Watch Updates /* ----------------------------------------- */ function watchUpdates() { gulp.watch(SYSTEM_SCSS, css); } /* ----------------------------------------- */ /* Zip Release /* ----------------------------------------- */ function zipRelease() { return gulp.src([ './**/*', '!./node_modules/**', '!./.git/**', '!./.gitignore', '!./gulpfile.js', '!./package-lock.json', '!./package.json', '!./scss/**/*', '!./.github/**/*', '!./.gitlab-ci.yml', '!./README.md', '!./compendiums/**/*', '!./*.zip' ], { base: '.' }) .pipe(zip('kidsonbrooms.zip')) .pipe(gulp.dest('.')); } /* ----------------------------------------- */ /* Version Check /* ----------------------------------------- */ function checkVersion(done) { const Manifest = JSON.parse(fs.readFileSync('system.json')); const manifestVersion = Manifest.version; const gitTag = process.env.CI_COMMIT_TAG; if (gitTag && manifestVersion !== gitTag) { console.error(`Version mismatch between tag (${gitTag}) and manifest (${manifestVersion})!`); process.exit(1); } else { console.log(`Version check passed: ${manifestVersion}`); done(); } } /* ----------------------------------------- */ /* Bundle Compendium /* ----------------------------------------- */ const { exec } = require('child_process'); function packageCompendiums(done) { const packsDir = './compendiums'; // Adjust to your compendium source directory const outputDir = './packs'; const moduleId = 'kidsonbrooms'; // Replace with your actual module ID // Read all subdirectories in the packsDir if (!fs.existsSync(packsDir)) { console.log(`Compendium directory ${packsDir} does not exist. Skipping packaging.`); done(); return; } // Read all files and directories in the packsDir fs.readdir(packsDir, (err, files) => { if (err) { console.error(`Error reading directory ${packsDir}: ${err}`); process.exit(1); } // Filter to get only directories const folders = files.filter(file => { const fullPath = path.join(packsDir, file); return fs.statSync(fullPath).isDirectory(); }); if (folders.length === 0) { console.log(`No compendium folders found in ${packsDir}. Skipping packaging.`); done(); return; } let completed = 0; folders.forEach(folder => { const packName = folder; // Use the folder name as the pack name const inputPath = path.join(packsDir, folder); const command = `npx fvtt package pack --type System --id ${moduleId} -n "${packName}" --in "${inputPath}" --out "${outputDir}" --yaml`; exec(command, (err, stdout, stderr) => { if (err) { console.error(`Error packaging compendium ${packName}:\n${stderr}`); process.exit(1); } else { console.log(`Compendium ${packName} packaged successfully.`); console.log(stdout); completed++; // When all compendiums have been processed, call done() if (completed === folders.length) { done(); } } }); }); }); } /* ----------------------------------------- */ /* Ensure Output Directory Exists /* ----------------------------------------- */ function ensureOutputDirExists() { const outputDir = './packs'; if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir); } return Promise.resolve(); } /* ----------------------------------------- */ /* Upload to Package Registry /* ----------------------------------------- */ async function uploadToPackageRegistry(done) { const manifestFile = 'system.json'; const zipFile = process.env.ZIPFILE || 'kidsonbrooms.zip'; const packageRegistryUrl = process.env.PACKAGE_REGISTRY_URL; const ciJobToken = process.env.CI_JOB_TOKEN; if (!packageRegistryUrl || !ciJobToken) { console.error('PACKAGE_REGISTRY_URL or CI_JOB_TOKEN is not defined.'); process.exit(1); } try { // Upload manifest file const manifestUploadUrl = `${packageRegistryUrl}/${manifestFile}`; console.log(`Uploading ${manifestFile} to ${manifestUploadUrl}`); let response = await fetch(manifestUploadUrl, { method: 'PUT', headers: { 'JOB-TOKEN': ciJobToken, 'Content-Type': 'application/octet-stream', }, body: fs.createReadStream(manifestFile), }); if (response.ok) { console.log(`Uploaded ${manifestFile} successfully.`); } else { console.error(`Failed to upload ${manifestFile}: ${response.statusText}`); process.exit(1); } // Upload zip file const zipUploadUrl = `${packageRegistryUrl}/${zipFile}`; console.log(`Uploading ${zipFile} to ${zipUploadUrl}`); response = await fetch(zipUploadUrl, { method: 'PUT', headers: { 'JOB-TOKEN': ciJobToken, 'Content-Type': 'application/octet-stream', }, body: fs.createReadStream(zipFile), }); if (response.ok) { console.log(`Uploaded ${zipFile} successfully.`); } else { console.error(`Failed to upload ${zipFile}: ${response.statusText}`); process.exit(1); } done(); } catch (error) { console.error(`Error uploading files: ${error.message}`); process.exit(1); } } /* ----------------------------------------- */ /* Publish to FoundryVTT /* ----------------------------------------- */ async function publishToFoundry(done) { const moduleManifestPath = 'system.json'; const moduleManifest = JSON.parse(fs.readFileSync(moduleManifestPath)); const id = moduleManifest.name; const version = moduleManifest.version; const compMin = moduleManifest.compatibility.minimum; const compVer = moduleManifest.compatibility.verified; const compMax = moduleManifest.compatibility.maximum; const manifest = process.env.MANIFEST_RELEASE_URL || `https://gitlab.com/${process.env.CI_PROJECT_NAMESPACE}/${process.env.CI_PROJECT_NAME}/-/releases/${process.env.CI_COMMIT_TAG}/downloads/${moduleManifestPath}`; const notes = `https://gitlab.com/${process.env.CI_PROJECT_NAMESPACE}/${process.env.CI_PROJECT_NAME}/-/tags/${process.env.CI_COMMIT_TAG}`; const dryRun = process.env.dry_run === 'true'; const authToken = process.env.FOUNDRY_API_KEY; if (!authToken) { console.error('Foundry VTT API authentication token (FOUNDRY_API_KEY) is not defined.'); process.exit(1); } // Construct the payload const payload = { id: id, release: { version: version, manifest: manifest, notes: notes, compatibility: { minimum: compMin, verified: compVer, maximum: compMax, }, }, }; if (dryRun) { payload['dry-run'] = true; } // Send the POST request to Foundry VTT API const response = await fetch('https://api.foundryvtt.com/_api/packages/release_version', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: authToken, }, body: JSON.stringify(payload), }); let responseData; if (contentType && contentType.includes('application/json')) { responseData = await response.json(); } else { responseData = await response.text(); } if (responseData.status === 'success') { console.log('Successfully published to Foundry VTT:'); console.log(JSON.stringify(responseData, null, 2)); done(); } else { console.error('Failed to publish to Foundry VTT:'); console.error(JSON.stringify(responseData, null, 2)); process.exit(1); } } /* ----------------------------------------- */ /* Update systen.json with Download URL /* ----------------------------------------- */ function updateSystemJson(done) { const ManifestPath = 'system.json'; const Manifest = JSON.parse(fs.readFileSync(ManifestPath)); const zipUrl = process.env.ZIPFILE_PERMALINK_URL || 'https://gitlab.com/wintermyst/kidsonbrooms/-/raw/master/kidsonbrooms.zip?inline=false'; Manifest.download = zipUrl; fs.writeFileSync(ManifestPath, JSON.stringify(Manifest, null, 2)); console.log(`Updated module.json with download URL: ${zipUrl}`); done(); }