Files
Containr/app/frontend/scripts/generate-docs-snapshot.mjs
T
Tomas DvorakandDevin 8b6214a0f5 feat: public platform, admin console, templates, landing docs
Projects become publicly browsable after admin approval; management and
secret-adjacent reads (logs, variables, exec, connection URLs) require
owner/member/admin auth, and platform controls sit behind RequireAdmin.

- Backend: OptionalAuth middleware, read/authed/admin route groups,
  projects.is_approved + service_templates.owner_id migration,
  visibility-aware sqlc queries, admin overview/users/approval endpoints,
  EnsureAdminAccount bootstrap (ADMIN_* env), 30 seeded templates with
  owner CRUD, managed-database connection URLs gated owner+admin, fix
  DB.Stats() infinite recursion.
- Frontend: expandable persisted sidebar + clickable user menu, public
  landing page with searchable docs browser (GitHub sync, bundled
  snapshot + localStorage fallback), DocsBrowser shared with /docs,
  template create/edit/JSON-upload for owned templates, database create
  dialog, admin console (stats, approvals, project edit/delete, user
  admin toggles), HA/Security/Usage polish with dead CSS tokens mapped
  to real theme vars, anonymous-safe gating on every mutation control.
- Tests: safe window.localStorage access fixes happy-dom crashes in
  PlatformShell and canvas storage.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-22 19:58:59 +02:00

56 lines
2.2 KiB
JavaScript

// Bundles the repository docs/ tree into a generated TS module so the docs
// browser renders instantly and offline. The GitHub sync layer refreshes it
// at runtime when network is available.
import { readdirSync, readFileSync, statSync, writeFileSync, mkdirSync } from 'node:fs';
import { join, relative } from 'node:path';
import { fileURLToPath } from 'node:url';
const root = join(fileURLToPath(new URL('.', import.meta.url)), '..', '..', '..');
const docsDir = join(root, 'docs');
const outFile = join(root, 'app/frontend/src/generated/docs-snapshot.ts');
// Directories that are internal working notes rather than user documentation.
const SKIP_DIRS = new Set(['archive', 'design', 'superpowers']);
function walk(dir) {
const entries = [];
for (const name of readdirSync(dir)) {
const full = join(dir, name);
const stat = statSync(full);
if (stat.isDirectory()) {
if (!SKIP_DIRS.has(name)) entries.push(...walk(full));
} else if (name.toLowerCase().endsWith('.md')) {
entries.push(full);
}
}
return entries;
}
function titleOf(markdown, fallback) {
const match = markdown.match(/^#\s+(.+)$/m);
if (match) return match[1].replace(/[#*`]/g, '').trim();
return fallback
.replace(/\.md$/i, '')
.replace(/[-_]+/g, ' ')
.replace(/\b\w/g, (c) => c.toUpperCase());
}
const files = walk(docsDir).sort();
const docs = files.map((full) => {
const rel = relative(docsDir, full).replace(/\\/g, '/');
const markdown = readFileSync(full, 'utf8');
const section = rel.includes('/') ? rel.split('/')[0] : 'docs';
return {
path: rel,
section,
title: titleOf(markdown, rel.split('/').pop() ?? rel),
markdown,
};
});
const banner = `/**\n * Auto-generated by scripts/generate-docs-snapshot.mjs.\n * Bundled copy of the repository docs tree — the offline fallback for the\n * docs browser. Regenerate with \`npm run generate:docs\`.\n */\n\nexport type BundledDoc = {\n path: string;\n section: string;\n title: string;\n markdown: string;\n};\n\n`;
mkdirSync(join(outFile, '..'), { recursive: true });
writeFileSync(outFile, `${banner}export const bundledDocs: BundledDoc[] = ${JSON.stringify(docs, null, 2)};\n`);
console.log(`docs snapshot: ${docs.length} files → ${relative(root, outFile)}`);