|
import fs from 'fs'; |
|
import path from 'path'; |
|
|
|
const SRC_DIR = path.join(process.cwd(), 'src'); |
|
const OUTPUT_FILE = path.join(process.cwd(), 'src', 'index.ts'); |
|
|
|
const IGNORE_LIST = ['module.ts','index.ts', 'index.js', '.git', 'node_modules', 'dist']; |
|
|
|
function shouldIgnore(filePath: string): boolean { |
|
if (filePath === OUTPUT_FILE) return true; |
|
const relative = path.relative(SRC_DIR, filePath); |
|
return IGNORE_LIST.some(ignore => relative.includes(ignore)); |
|
} |
|
|
|
function collectFiles(dir: string, files: string[] = []): string[] { |
|
const items = fs.readdirSync(dir); |
|
for (const item of items) { |
|
const fullPath = path.join(dir, item); |
|
if (shouldIgnore(fullPath)) continue; |
|
|
|
if (fs.statSync(fullPath).isDirectory()) { |
|
collectFiles(fullPath, files); |
|
} else if (item.endsWith('.ts') && !item.startsWith('index')) { |
|
files.push(fullPath); |
|
} |
|
} |
|
return files; |
|
} |
|
|
|
function getTag(filePath: string): 'table' | 'reducer' | 'procedure' | 'view' | 'lifecycle' | 'other' { |
|
const content = fs.readFileSync(filePath, 'utf-8').toLowerCase(); |
|
if (content.includes('// table:')) return 'table'; |
|
if (content.includes('// reducer:')) return 'reducer'; |
|
if (content.includes('// procedure:')) return 'procedure'; |
|
if (content.includes('// view:')) return 'view'; |
|
if (content.includes('// init') || |
|
content.includes('// onconnect') || |
|
content.includes('// ondisconnect') || |
|
content.includes('// lifecycle')) return 'lifecycle'; |
|
return 'other'; |
|
} |
|
|
|
async function build() { |
|
console.log('π Collecting source files...'); |
|
const allFiles = collectFiles(SRC_DIR); |
|
|
|
const tables = allFiles.filter(f => getTag(f) === 'table'); |
|
const lifecycles = allFiles.filter(f => getTag(f) === 'lifecycle'); |
|
const reducers = allFiles.filter(f => getTag(f) === 'reducer'); |
|
const procedures = allFiles.filter(f => getTag(f) === 'procedure'); |
|
const views = allFiles.filter(f => getTag(f) === 'view'); |
|
const others = allFiles.filter(f => getTag(f) === 'other'); |
|
|
|
console.log(`Found ${allFiles.length} files β Tables: ${tables.length}`); |
|
|
|
let output = `// =============================================\n`; |
|
output += `// AUTO-GENERATED by build.ts - DO NOT EDIT THIS FILE!\n`; |
|
output += `// Generated: ${new Date().toISOString()}\n`; |
|
output += `// =============================================\n\n`; |
|
|
|
output += `import { schema, table, t } from 'spacetimedb/server';\n`; |
|
output += `import { ScheduleAt } from 'spacetimedb';\n\n`; |
|
|
|
// 1. TABLES FIRST (without 'export') |
|
for (const file of tables) { |
|
let content = fs.readFileSync(file, 'utf-8') |
|
.replace(/^import .*$/gm, '') |
|
.replace(/^export\s+const\s+/gm, 'const ') // β Remove 'export' from tables |
|
.replace(/^\/\/\s*(table|reducer|procedure|view|lifecycle):?.*$/gm, '') |
|
.replace(/^\s*[\r\n]+/gm, '') |
|
.trim(); |
|
|
|
if (content) { |
|
output += `// === Table from: ${path.relative(SRC_DIR, file)} ===\n`; |
|
output += content + '\n\n'; |
|
} |
|
} |
|
|
|
// 2. Collect table names for schema() |
|
const tableNames: string[] = []; |
|
tables.forEach(file => { |
|
const content = fs.readFileSync(file, 'utf-8'); |
|
const match = content.match(/\/\/\s*table:\s*(\w+)/i); |
|
if (match && match[1]) { |
|
tableNames.push(match[1].trim()); |
|
} |
|
}); |
|
|
|
// 3. SCHEMA() β AFTER TABLES |
|
output += `// === SCHEMA (must come before reducers, procedures, views, etc.) ===\n`; |
|
output += `const spacetimedb = schema({\n`; |
|
tableNames.forEach((name, i) => { |
|
output += ` ${name}${i < tableNames.length - 1 ? ',' : ''}\n`; |
|
}); |
|
output += `});\n\n`; |
|
|
|
output += `export default spacetimedb;\n\n`; |
|
|
|
// 4. LIFECYCLE + REDUCERS + PROCEDURES + VIEWS (AFTER SCHEMA) |
|
output += `// === LIFECYCLE HOOKS, REDUCERS, PROCEDURES & VIEWS ===\n\n`; |
|
|
|
const postSchemaFiles = [...lifecycles, ...reducers, ...procedures, ...views, ...others]; |
|
|
|
for (const file of postSchemaFiles) { |
|
let content = fs.readFileSync(file, 'utf-8') |
|
.replace(/^import .*$/gm, '') |
|
.replace(/^\/\/\s*(table|reducer|procedure|view|lifecycle):?.*$/gm, '') |
|
.replace(/^\s*[\r\n]+/gm, '') |
|
.trim(); |
|
|
|
if (content) { |
|
const tag = getTag(file); |
|
const displayTag = tag === 'lifecycle' ? 'Lifecycle' : tag.charAt(0).toUpperCase() + tag.slice(1); |
|
output += `// === ${displayTag} from: ${path.relative(SRC_DIR, file)} ===\n`; |
|
output += content + '\n\n'; |
|
} |
|
} |
|
|
|
fs.writeFileSync(OUTPUT_FILE, output, 'utf-8'); |
|
|
|
console.log(`β
Build completed successfully!`); |
|
console.log(` Tables : ${tables.length}`); |
|
console.log(` Lifecycle : ${lifecycles.length}`); |
|
console.log(` Reducers : ${reducers.length}`); |
|
console.log(` Procedures : ${procedures.length}`); |
|
console.log(` Views : ${views.length}`); |
|
} |
|
|
|
build().catch((err) => { |
|
console.error('β Build failed:', err.message); |
|
process.exit(1); |
|
}); |