A music player that connects to your cloud/distributed storage.
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v4 405 lines 11 kB view raw
1import { dotenvRun } from "@dotenv-run/esbuild"; 2import lume from "lume/mod.ts"; 3 4import brotli from "lume/plugins/brotli.ts"; 5import esbuild from "lume/plugins/esbuild.ts"; 6import postcss from "lume/plugins/postcss.ts"; 7import sourceMaps from "lume/plugins/source_maps.ts"; 8 9import * as path from "@std/path"; 10import { ensureDirSync } from "@std/fs/ensure-dir"; 11import { walkSync } from "@std/fs/walk"; 12import { nodeModulesPolyfillPlugin } from "esbuild-plugins-node-modules-polyfill"; 13import { wasmLoader } from "esbuild-plugin-wasm"; 14import autoprefixer from "autoprefixer"; 15import cssnano from "cssnano"; 16 17import { Uint8ArrayReader, Uint8ArrayWriter, ZipWriter } from "@zip-js/zip-js"; 18 19import { create as createCID } from "~/common/cid.js"; 20 21const site = lume({ 22 dest: "./dist", 23 src: "./src", 24 server: { 25 debugBar: false, 26 middlewares: [], 27 }, 28}); 29 30export default site; 31 32//////////////////////////////////////////// 33// JS 34//////////////////////////////////////////// 35 36site.use(esbuild({ 37 extensions: [".js"], 38 options: { 39 alias: { 40 "@automerge/automerge": "https://esm.sh/@automerge/automerge@^3.2.3", 41 }, 42 bundle: true, 43 format: "esm", 44 minify: true, 45 external: ["./file-tree.json", "@awesome.me/webawesome/*"], 46 platform: "browser", 47 plugins: [ 48 // @ts-ignore 49 dotenvRun({ 50 files: [".env"], 51 }), 52 // Force @atcute/uint8array to use the browser entry (dist/index.js) 53 // instead of the Node entry (dist/index.node.js) which imports from 54 // node:crypto. The @deno/loader Workspace defaults to platform "node", 55 // causing the "node" export condition to match before "default". 56 { 57 name: "atcute-uint8array-browser", 58 setup(build) { 59 build.onLoad( 60 { filter: /@atcute\+uint8array.*index\.node\.js$/ }, 61 async (args) => { 62 const browserPath = args.path.replace( 63 "index.node.js", 64 "index.js", 65 ); 66 const contents = await Deno.readTextFile(browserPath); 67 return { contents, loader: "js" }; 68 }, 69 ); 70 }, 71 }, 72 { 73 name: "atcute-tid-browser", 74 setup(build) { 75 build.onLoad( 76 { filter: /@atcute\+tid.*random-node\.js$/ }, 77 async (args) => { 78 const browserPath = args.path.replace( 79 "random-node.js", 80 "random-web.js", 81 ); 82 const contents = await Deno.readTextFile(browserPath); 83 return { contents, loader: "js" }; 84 }, 85 ); 86 }, 87 }, 88 { 89 name: "atcute-multibase-browser", 90 setup(build) { 91 build.onLoad( 92 { filter: /@atcute[+/]multibase.*-node\.js$/ }, 93 async (args) => { 94 const browserPath = args.path.replace( 95 "-node.js", 96 "-web.js", 97 ); 98 const contents = await Deno.readTextFile(browserPath); 99 return { contents, loader: "js" }; 100 }, 101 ); 102 }, 103 }, 104 // nanoid ships a browser entry (index.browser.js) but esbuild resolves 105 // the default condition (index.js) which uses Buffer.allocUnsafe. 106 { 107 name: "nanoid-browser", 108 setup(build) { 109 build.onLoad( 110 { filter: /nanoid\/index\.js$/ }, 111 async (args) => { 112 const browserPath = args.path.replace( 113 "index.js", 114 "index.browser.js", 115 ); 116 const contents = await Deno.readTextFile(browserPath); 117 return { contents, loader: "js" }; 118 }, 119 ); 120 }, 121 }, 122 nodeModulesPolyfillPlugin({ 123 fallback: "empty", 124 modules: [], 125 }), 126 wasmLoader(), 127 ], 128 splitting: true, 129 target: "esnext", 130 }, 131})); 132 133site.add([".js"]); 134 135// *.inline.js files are inlined into their companion HTML at build/serve time. 136// Exclude them from the regular build so esbuild doesn't try to bundle them. 137site.ignore((p) => p.endsWith(".inline.js") || p.endsWith("SKILL.md")); 138 139//////////////////////////////////////////// 140// CSS 141//////////////////////////////////////////// 142 143site.use(postcss({ 144 plugins: [ 145 autoprefixer(), 146 cssnano({ 147 preset: "default", 148 }), 149 ], 150})); 151 152site.add([".css"]); 153 154site.remoteFile( 155 "vendor/98.css", 156 import.meta.resolve("./node_modules/98.css/dist/98.css"), 157); 158 159//////////////////////////////////////////// 160// BINARY ASSETS 161//////////////////////////////////////////// 162 163site.add("/favicons", "/"); 164site.add("/fonts"); 165site.add("/images"); 166site.add("/testing"); 167site.add([".woff2"]); 168 169site.remoteFile( 170 "vendor/ms_sans_serif.woff2", 171 import.meta.resolve( 172 "./node_modules/98.css/fonts/converted/ms_sans_serif.woff2", 173 ), 174); 175 176site.remoteFile( 177 "vendor/ms_sans_serif_bold.woff2", 178 import.meta.resolve( 179 "./node_modules/98.css/fonts/converted/ms_sans_serif_bold.woff2", 180 ), 181); 182 183site.remoteFile( 184 "fonts/98.css/ms_sans_serif.woff2", 185 import.meta.resolve( 186 "./node_modules/98.css/fonts/converted/ms_sans_serif.woff2", 187 ), 188); 189 190site.remoteFile( 191 "fonts/98.css/ms_sans_serif_bold.woff2", 192 import.meta.resolve( 193 "./node_modules/98.css/fonts/converted/ms_sans_serif_bold.woff2", 194 ), 195); 196 197//////////////////////////////////////////// 198// DEFINITIONS 199//////////////////////////////////////////// 200 201site.add("/definitions"); 202 203// HELPERS 204 205site.filter("facetURI", (text) => { 206 if (text.includes("://")) { 207 return text; 208 } else { 209 return `diffuse://${text}`; 210 } 211}); 212 213site.filter("facetLoaderURL", (text) => { 214 let key = "path"; 215 216 if (text.includes("://")) { 217 key = "uri"; 218 } 219 220 return `l/?${key}=${encodeURIComponent(text)}`; 221}); 222 223//////////////////////////////////////////// 224// PHOSPHOR ICONS 225//////////////////////////////////////////// 226 227function phosphor(path: string) { 228 site.remoteFile( 229 `vendor/@phosphor-icons/web/${path}`, 230 import.meta.resolve(`./node_modules/@phosphor-icons/web/src/${path}`), 231 ); 232 233 site.add(`vendor/@phosphor-icons/web/${path}`); 234} 235 236["bold", "duotone", "fill", "light", "regular", "light"].forEach((v) => { 237 const f = v === "regular" ? "" : `-${v[0].toUpperCase()}${v.slice(1)}`; 238 phosphor(`${v}/selection.json`); 239 phosphor(`${v}/style.css`); 240 phosphor(`${v}/Phosphor${f}.svg`); 241 phosphor(`${v}/Phosphor${f}.ttf`); 242 phosphor(`${v}/Phosphor${f}.woff`); 243 phosphor(`${v}/Phosphor${f}.woff2`); 244}); 245 246//////////////////////////////////////////// 247// WEB AWESOME 248//////////////////////////////////////////// 249 250for ( 251 const f of walkSync("./node_modules/@awesome.me/webawesome/dist-cdn/", { 252 includeDirs: false, 253 }) 254) { 255 const relativePath = f.path.replace( 256 /^node_modules\/@awesome\.me\/webawesome\/dist-cdn\//, 257 "", 258 ); 259 260 const destPath = `vendor/@awesome.me/webawesome/${relativePath}`; 261 262 site.remoteFile( 263 destPath, 264 import.meta.resolve( 265 `./node_modules/@awesome.me/webawesome/dist-cdn/${relativePath}`, 266 ), 267 ); 268 269 site.copy(destPath); 270} 271 272//////////////////////////////////////////// 273// MISC 274//////////////////////////////////////////// 275 276site.add([".html"]); 277site.add([".json"]); 278site.add([".webmanifest"]); 279 280site.script("copy-type-defs", () => { 281 for ( 282 const f of walkSync( 283 "./src/", 284 { includeDirs: false, exts: [".d.ts"] }, 285 ) 286 ) { 287 const dest = "dist/" + f.path.replace(/^src\//, ""); 288 const dir = path.dirname(dest); 289 ensureDirSync(dir); 290 Deno.copyFileSync(f.path, dest); 291 } 292}); 293 294// SKILLS 295 296site.remoteFile( 297 "skills/diffuse-facet/docs/architecture.txt", 298 import.meta.resolve("./docs/ARCHITECTURE.md"), 299); 300 301site.remoteFile( 302 "skills/diffuse-facet/example/index.html", 303 import.meta.resolve("./src/facets/data/sources/index.html"), 304); 305 306site.add("skills/diffuse-facet/docs/architecture.txt"); 307site.add("skills/diffuse-facet/example/index.html"); 308site.add("/definitions", "/skills/diffuse-facet/docs/definitions"); 309site.copy("skills/diffuse-facet/SKILL.md"); 310site.add("skills"); 311 312site.addEventListener("afterBuild", async () => { 313 const skillsDir = "dist/skills/diffuse-facet"; 314 const zipWriter = new ZipWriter(new Uint8ArrayWriter()); 315 316 for (const entry of walkSync(skillsDir, { includeDirs: false })) { 317 if (entry.path.endsWith(".br")) continue; 318 await zipWriter.add( 319 "diffuse-facet/" + entry.path.slice(skillsDir.length + 1), 320 new Uint8ArrayReader(Deno.readFileSync(entry.path)), 321 ); 322 } 323 324 Deno.writeFileSync("dist/skills/diffuse-facet.zip", await zipWriter.close()); 325}); 326 327//////////////////////////////////////////// 328// FILE TREE 329//////////////////////////////////////////// 330 331site.addEventListener("afterBuild", async () => { 332 const RAW = 0x55; 333 334 async function buildFileTree( 335 dir: string, 336 prefix = "", 337 ): Promise<Record<string, string>> { 338 const tree: Record<string, string> = {}; 339 340 for (const entry of Deno.readDirSync(dir)) { 341 const entryPath = path.join(dir, entry.name); 342 const entryKey = prefix ? `${prefix}/${entry.name}` : entry.name; 343 if (entry.isDirectory) { 344 Object.assign(tree, await buildFileTree(entryPath, entryKey)); 345 } else { 346 const data = Deno.readFileSync(entryPath); 347 tree[entryKey] = await createCID(RAW, data); 348 } 349 } 350 351 return tree; 352 } 353 354 const tree = await buildFileTree("dist/"); 355 const sorted = Object.fromEntries( 356 Object.keys(tree).sort().map((k) => [k, tree[k]]), 357 ); 358 359 Deno.writeTextFileSync( 360 "./dist/file-tree.json", 361 JSON.stringify(sorted, null, 2), 362 ); 363}); 364 365//////////////////////////////////////////// 366// INLINE JS FOR FACETS 367//////////////////////////////////////////// 368 369const SCRIPT_SRC_RE = 370 /<script type="module" src="([^"]+\.inline\.js)"><\/script>/; 371 372site.process([".html"], (pages) => { 373 for (const page of pages) { 374 const content = page.text; 375 if (!content) continue; 376 const match = SCRIPT_SRC_RE.exec(content); 377 if (!match) continue; 378 379 const jsPath = path.join("src", match[1]); 380 try { 381 page.text = htmlWithInlineJs({ content, jsPath, match: match[0] }); 382 } catch { 383 // leave as-is if the source file can't be read 384 } 385 } 386}); 387 388function htmlWithInlineJs({ content, match, jsPath }: { 389 content: string; 390 match: string; 391 jsPath: string; 392}): string { 393 const js = 394 Deno.readTextFileSync(jsPath).split("\n").map((line) => ` ${line}`).join( 395 "\n", 396 ).trimEnd() + "\n"; 397 return content.replace(match, `<script type="module">\n${js}</script>`); 398} 399 400//////////////////////////////////////////// 401// COMPRESSION 402//////////////////////////////////////////// 403 404site.use(brotli()); 405site.use(sourceMaps());