Build & Tag (CDN Ready) #14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ccmjs workflow v3.0.0 | |
| name: Build & Tag (CDN Ready) | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Version (e.g. 1.0.0)" | |
| required: true | |
| permissions: | |
| contents: write | |
| jobs: | |
| tag: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout main | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: main | |
| fetch-depth: 0 | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| - name: Install dependencies | |
| run: npm install terser clean-css | |
| - name: Build files | |
| run: | | |
| node <<'EOF' | |
| import fs from "fs"; | |
| import path from "path"; | |
| import { minify } from "terser"; | |
| import CleanCSS from "clean-css"; | |
| const VERSION = process.env.VERSION; | |
| const repo = process.env.GITHUB_REPOSITORY; | |
| const [owner, name] = repo.split("/"); | |
| const BASE_URL = `https://${owner}.github.io/${name}/`; | |
| const CDN_BASE = `https://cdn.jsdelivr.net/gh/${owner}/${name}@v${VERSION}/`; | |
| // ---------- find main file ---------- | |
| const rootFiles = fs.readdirSync("."); | |
| const mainFile = rootFiles.find(f => f.endsWith(".mjs")); | |
| if (!mainFile) throw new Error("No main .mjs file found"); | |
| // ---------- collect resources ---------- | |
| function collect(dir) { | |
| if (!fs.existsSync(dir)) return []; | |
| const result = []; | |
| function walk(current) { | |
| const entries = fs.readdirSync(current, { withFileTypes: true }); | |
| for (const entry of entries) { | |
| const full = path.join(current, entry.name); | |
| if (entry.isDirectory()) { | |
| walk(full); | |
| } else { | |
| result.push(full); | |
| } | |
| } | |
| } | |
| walk(dir); | |
| return result; | |
| } | |
| const resourceFiles = collect("resources"); | |
| // ---------- JS / MJS ---------- | |
| async function processJS(file, isMain = false) { | |
| let content = fs.readFileSync(file, "utf-8"); | |
| content = content.replaceAll("././", BASE_URL); | |
| let outputName = file; | |
| if (isMain) { | |
| outputName = file.replace(".mjs", `-${VERSION}.mjs`); | |
| } | |
| const mapURL = CDN_BASE + outputName + ".map"; | |
| const result = await minify(content, { | |
| compress: true, | |
| mangle: true, | |
| sourceMap: { | |
| filename: outputName, | |
| url: false | |
| } | |
| }); | |
| const map = JSON.parse(result.map); | |
| map.file = outputName; | |
| map.sourceRoot = "ccmjs:///"; | |
| map.sources = [file]; | |
| map.sourcesContent = [content]; | |
| const minName = outputName.replace(/\.(m?js)$/, ".min.$1"); | |
| let code = result.code.replace(/\/\/# sourceMappingURL=.*$/gm, ""); | |
| fs.mkdirSync(path.dirname(minName), { recursive: true }); | |
| fs.writeFileSync( | |
| minName, | |
| code + `\n//# sourceMappingURL=${mapURL}` | |
| ); | |
| fs.writeFileSync( | |
| outputName + ".map", | |
| JSON.stringify(map) | |
| ); | |
| } | |
| // ---------- CSS ---------- | |
| function processCSS(file) { | |
| const content = fs.readFileSync(file, "utf-8"); | |
| const minified = new CleanCSS().minify(content).styles; | |
| const minName = file.replace(".css", ".min.css"); | |
| fs.mkdirSync(path.dirname(minName), { recursive: true }); | |
| fs.writeFileSync(minName, minified); | |
| } | |
| // ---------- RUN ---------- | |
| await processJS(mainFile, true); | |
| for (const file of resourceFiles) { | |
| if (file.endsWith(".js") || file.endsWith(".mjs")) { | |
| await processJS(file); | |
| } | |
| else if (file.endsWith(".css")) { | |
| processCSS(file); | |
| } | |
| // images etc. bleiben unverändert | |
| } | |
| // ---------- CLEAN ROOT ---------- | |
| for (const file of fs.readdirSync(".")) { | |
| if (file === ".git") continue; | |
| // keep output | |
| if (file.includes(".min.")) continue; | |
| if (file.endsWith(".map")) continue; | |
| // keep folders | |
| if (file === "resources") continue; | |
| if (file === "libs") continue; | |
| // delete original main file | |
| if (file === mainFile) { | |
| fs.rmSync(file, { force: true }); | |
| continue; | |
| } | |
| // delete everything else | |
| fs.rmSync(file, { recursive: true, force: true }); | |
| } | |
| // ---------- CLEAN ORIGINALS IN RESOURCES ---------- | |
| function cleanResources(dir = "resources") { | |
| if (!fs.existsSync(dir)) return; | |
| const entries = fs.readdirSync(dir, { withFileTypes: true }); | |
| for (const entry of entries) { | |
| const full = path.join(dir, entry.name); | |
| if (entry.isDirectory()) { | |
| cleanResources(full); | |
| } else { | |
| if ( | |
| full.endsWith(".js") || | |
| full.endsWith(".mjs") || | |
| full.endsWith(".css") | |
| ) { | |
| fs.rmSync(full, { force: true }); | |
| } | |
| } | |
| } | |
| } | |
| cleanResources(); | |
| EOF | |
| env: | |
| VERSION: ${{ github.event.inputs.version }} | |
| - name: Commit build | |
| run: | | |
| git config user.name "github-actions" | |
| git config user.email "github-actions@github.com" | |
| git add . | |
| git commit -m "build v${{ github.event.inputs.version }}" | |
| - name: Check if tag exists | |
| run: | | |
| if git rev-parse "v${{ github.event.inputs.version }}" >/dev/null 2>&1; then | |
| echo "❌ Tag already exists" | |
| exit 1 | |
| fi | |
| - name: Create and push tag | |
| run: | | |
| git tag v${{ github.event.inputs.version }} | |
| git push origin v${{ github.event.inputs.version }} |