anvilsign in

collin/mahjong

1#!/usr/bin/env node
2// Deploy the built game to antics.gg as a multi-file bundle.
3//
4// npm run build && node scripts/antics-deploy.mjs # keyless: a fresh link each time
5// node scripts/antics-deploy.mjs --project <proj_id> # under a project: stable /p/ share link
6//
7// A keyless deploy needs no account at all. Deploying under a project needs a
8// token from `npx antics-cli login`, read from ANTICS_TOKEN or ~/.antics/token.
9
10import { readdir, readFile } from 'node:fs/promises';
11import { homedir } from 'node:os';
12import { join, relative, extname } from 'node:path';
13
14const HOST = process.env.ANTICS_HOST || 'https://antics.gg';
15const DIST = new URL('../dist', import.meta.url).pathname;
16
17// Anything not in this list rides along as a data: URI.
18const TEXT = new Set(['.html', '.js', '.css', '.svg', '.json', '.webmanifest', '.txt', '.map']);
19const MIME = {
20 '.png': 'image/png',
21 '.jpg': 'image/jpeg',
22 '.webp': 'image/webp',
23 '.mp3': 'audio/mpeg',
24 '.woff2': 'font/woff2',
25 '.ico': 'image/x-icon',
26};
27
28async function* walk(dir) {
29 for (const e of await readdir(dir, { withFileTypes: true })) {
30 const p = join(dir, e.name);
31 if (e.isDirectory()) yield* walk(p);
32 else yield p;
33 }
34}
35
36async function collect() {
37 const files = {};
38 let bytes = 0;
39 for await (const p of walk(DIST)) {
40 const rel = relative(DIST, p);
41 const ext = extname(rel);
42 const buf = await readFile(p);
43 bytes += buf.length;
44 files[rel] = TEXT.has(ext)
45 ? buf.toString('utf8')
46 : `data:${MIME[ext] ?? 'application/octet-stream'};base64,${buf.toString('base64')}`;
47 }
48 return { files, bytes };
49}
50
51async function token() {
52 if (process.env.ANTICS_TOKEN) return process.env.ANTICS_TOKEN;
53 try {
54 return (await readFile(join(homedir(), '.antics', 'token'), 'utf8')).trim();
55 } catch {
56 return null;
57 }
58}
59
60const args = process.argv.slice(2);
61const projectAt = args.indexOf('--project');
62const project = projectAt >= 0 ? args[projectAt + 1] : null;
63
64const { files, bytes } = await collect();
65console.log(`deploying ${Object.keys(files).length} files, ${(bytes / 1024 / 1024).toFixed(1)} MB from dist/`);
66
67const body = { files };
68const headers = { 'content-type': 'application/json' };
69if (project) {
70 body.project = project;
71 const t = await token();
72 if (!t) {
73 console.error('a --project deploy needs a token: run `npx antics-cli login`, or set ANTICS_TOKEN');
74 process.exit(1);
75 }
76 headers.authorization = `Bearer ${t}`;
77}
78
79const res = await fetch(`${HOST}/api/deploy`, {
80 method: 'POST',
81 headers,
82 body: JSON.stringify(body),
83});
84const out = await res.json().catch(() => ({}));
85if (!res.ok) {
86 console.error(`deploy failed: ${res.status}`, out);
87 process.exit(1);
88}
89console.log('deployed.');
90if (out.playUrl) console.log(` play (this build): ${out.playUrl}`);
91if (out.shareUrl) console.log(` share (follows updates): ${out.shareUrl}`);
92if (out.hash) console.log(` hash: ${out.hash}`);