Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/thatdeveloperguy-website / scripts/assemble-release.ts |
| Date | Not specified in source |
| Repository | Janady13/thatdeveloperguy-website |
| Revision | 2bea2ebdff0e34494bff597d4fa9c8422a0c3a94 |
| Branch | main |
| Path or archive member | scripts/assemble-release.ts |
| Original SHA-256 | a0922af70d5ab7b2a5a29e47cdf3e403eb64662758414c45f76591c3ecc4de8d |
| Git blob SHA | 3a6c92e5bcd5c17defb44845345584aaa58ee3c5 |
| Reading-copy SHA-256 | a0922af70d5ab7b2a5a29e47cdf3e403eb64662758414c45f76591c3ecc4de8d |
Recovered source transcript
Download complete source text ↓58 lines · 4.4 KB
/**
* build/client → dist/site (the only public document root) + dist/deployment (private host config).
* TDG_RELEASE_KIND=demo builds for the noindex demo host; anything else is a production candidate.
*/
import { createHash } from 'node:crypto';
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
import { execFileSync } from 'node:child_process';
import { dirname, join, relative, resolve } from 'node:path';
import manifest from '../src/generated/route-manifest.json' with { type: 'json' };
import { PRODUCTION_ORIGIN } from '../src/engines/seo/urls/production-origin.ts';
import { robotsFor } from '../src/engines/seo/crawl/robots-policy.ts';
import { renderHostConfig } from '../src/engines/seo/crawl/host-config.ts';
import { sitemapIndexXml, sitemapXml } from '../src/engines/seo/sitemaps/build-sitemap.ts';
import { llmsDocuments } from '../src/engines/seo/discovery/build-llms.ts';
import { pages } from '../src/generated/index.ts';
const root = resolve(import.meta.dirname, '..');
const kind = process.env.TDG_RELEASE_KIND === 'demo' ? 'demo' : 'production';
const host = process.env.TDG_DEMO_HOST ?? 'tdg.thatwebhostingguy.com';
const site = resolve(root, 'dist/site'), deployment = resolve(root, 'dist/deployment');
rmSync(resolve(root, 'dist'), { recursive: true, force: true }); mkdirSync(site, { recursive: true }); mkdirSync(deployment, { recursive: true });
cpSync(resolve(root, 'build/client'), site, { recursive: true });
rmSync(join(site, '__spa-fallback.html'), { force: true }); // unknown URLs are real 404s, never a 200 shell
renameSync(join(site, '404/index.html'), join(site, '404.html')); rmSync(join(site, '404'), { recursive: true, force: true });
// Discovery files: exactly one generated owner (records/crawler-policy.json mirrors the node's approved matrix).
writeFileSync(join(site, 'robots.txt'), robotsFor(kind));
for (const document of llmsDocuments(pages, kind)) {
const target = join(site, document.path.replace(/^\/+/, ''));
mkdirSync(dirname(target), { recursive: true });
writeFileSync(target, document.content);
}
if (kind === 'production') { writeFileSync(join(site, 'sitemap.xml'), sitemapXml(manifest.routes as never)); writeFileSync(join(site, 'sitemap-index.xml'), sitemapIndexXml()); }
// CSP: hash every inline script the router emits; JSON-LD data blocks are not executed and need no hash.
const hashes = new Set<string>();
const walk = (dir: string): string[] => readdirSync(dir).flatMap(name => { const p = join(dir, name); return statSync(p).isDirectory() ? walk(p) : [p]; });
const files = walk(site);
for (const file of files.filter(f => f.endsWith('.html'))) {
const html = readFileSync(file, 'utf8');
for (const match of html.matchAll(/<script(?![^>]*\bsrc=)([^>]*)>([\s\S]*?)<\/script>/g)) {
if (/application\/ld\+json/.test(match[1]!)) continue;
hashes.add(`'sha256-${createHash('sha256').update(match[2]!).digest('base64')}'`);
}
}
const csp = [
"default-src 'self'", `script-src 'self' 'wasm-unsafe-eval' ${[...hashes].join(' ')}`, "style-src 'self' 'unsafe-inline'", "img-src 'self' data: blob:", "font-src 'self'",
"connect-src 'self'", "worker-src 'self' blob:", "object-src 'none'", "base-uri 'self'", "form-action 'self'", "frame-ancestors 'none'", 'upgrade-insecure-requests',
].join('; ');
writeFileSync(join(deployment, 'csp.conf'), `# GENERATED by scripts/assemble-release.ts — ${hashes.size} inline-script hashes for this build\nadd_header Content-Security-Policy "${csp}" always;\n`);
writeFileSync(join(deployment, 'site.conf'), renderHostConfig(kind, kind === 'demo' ? host : new URL(PRODUCTION_ORIGIN).host));
let commit = 'uncommitted'; try { commit = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim(); } catch { /* not a git checkout */ }
const checksums = Object.fromEntries(files.map(f => [relative(site, f), createHash('sha256').update(readFileSync(f)).digest('hex')]));
writeFileSync(join(deployment, 'build-manifest.json'), JSON.stringify({ kind, host: kind === 'demo' ? host : new URL(PRODUCTION_ORIGIN).host, level: manifest.level, commit, builtAt: new Date().toISOString(), routes: manifest.routes.map(r => r.path), files: files.length, inlineScriptHashes: hashes.size, checksums }, null, 2) + '\n');
console.log(`${kind} release: ${files.length} files in dist/site, ${manifest.routes.length} routes, ${hashes.size} inline-script hashes, commit ${commit.slice(0, 8)}`);