various fixes. added version#s and added a playable browser build of the solitaire game (including deploy to filebrowser script)
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Build the static solitaire site into `dist/`.
|
||||
*
|
||||
* Everything here is BUILD-time. The output is plain files on disk — upload them anywhere that
|
||||
* serves static content and the game runs entirely in the visitor's browser. No server, no API, no
|
||||
* network call at runtime.
|
||||
*
|
||||
* The one thing a browser cannot do is run TypeScript: Node 22's type-stripping is a Node feature.
|
||||
* So `tsc` emits ES2022 modules, rewriting the `.ts` import extensions the engine uses into `.js`.
|
||||
*/
|
||||
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const dist = join(root, 'dist');
|
||||
|
||||
rmSync(dist, { recursive: true, force: true });
|
||||
mkdirSync(dist, { recursive: true });
|
||||
|
||||
execFileSync(
|
||||
'npx',
|
||||
[
|
||||
'tsc',
|
||||
'--ignoreConfig',
|
||||
'src/web/main.ts',
|
||||
'--outDir', dist,
|
||||
'--rootDir', 'src',
|
||||
'--target', 'es2022',
|
||||
'--module', 'es2022',
|
||||
'--moduleResolution', 'bundler',
|
||||
'--lib', 'es2022,dom',
|
||||
'--types', '',
|
||||
'--strict',
|
||||
'--allowImportingTsExtensions',
|
||||
'--rewriteRelativeImportExtensions',
|
||||
],
|
||||
{ cwd: root, stdio: 'inherit' },
|
||||
);
|
||||
|
||||
/**
|
||||
* Stamp the build into the page.
|
||||
*
|
||||
* Once this is published, "what is actually deployed?" stops being answerable by looking at the
|
||||
* source. The stamp is written into index.html at copy time rather than generated into `src/`, so
|
||||
* no generated file has to be ignored, imported, or kept in step.
|
||||
*
|
||||
* `-dirty` marks a build made from an uncommitted tree — the honest state of most test deploys, and
|
||||
* exactly the thing you want to know when a fix appears to have had no effect.
|
||||
*/
|
||||
function buildStamp(): string {
|
||||
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')) as { version: string };
|
||||
let git = 'nogit';
|
||||
try {
|
||||
const sha = execFileSync('git', ['rev-parse', '--short', 'HEAD'], { cwd: root })
|
||||
.toString()
|
||||
.trim();
|
||||
const dirty =
|
||||
execFileSync('git', ['status', '--porcelain'], { cwd: root }).toString().trim().length > 0;
|
||||
git = sha + (dirty ? '-dirty' : '');
|
||||
} catch {
|
||||
// A build from a tarball with no git history is still a valid build.
|
||||
}
|
||||
const when = new Date().toISOString().replace('T', ' ').slice(0, 16);
|
||||
return `v${pkg.version} · ${git} · ${when}Z`;
|
||||
}
|
||||
|
||||
const stamp = buildStamp();
|
||||
const page = readFileSync(join(root, 'src/web/index.html'), 'utf8').replaceAll('__BUILD__', stamp);
|
||||
writeFileSync(join(dist, 'index.html'), page);
|
||||
|
||||
// A tiny note for whoever unzips this later and wonders what it needs.
|
||||
writeFileSync(
|
||||
join(dist, 'README.txt'),
|
||||
[
|
||||
'Station Master — solitaire, static build.',
|
||||
'',
|
||||
'Upload the contents of this folder to any static host and open index.html.',
|
||||
'There is no server component. The game runs entirely in the browser and saves',
|
||||
'to localStorage. Add ?seed=1234 to the URL for a reproducible deal.',
|
||||
'',
|
||||
'Note: ES modules require the files to be SERVED over http(s). Opening',
|
||||
'index.html directly from the filesystem will be blocked by the browser.',
|
||||
'To try it locally: npx http-server dist (or any static file server)',
|
||||
'',
|
||||
].join('\n'),
|
||||
);
|
||||
|
||||
console.log(`built -> ${dist}\n ${stamp}`);
|
||||
Reference in New Issue
Block a user