Files
lifeforge/scripts/generateCollectionsSchemas.js
Melvin Chia 2364d7949f 25w30
Former-commit-id: cff5e184f0cc274cd9b69e86241c22275b539cb4 [formerly 567f96d4baeb189ad4102c8d89041fe563fb78f0] [formerly a0b4dfddcdf8c3607f077720b10544c24d7be271 [formerly 3b4c1e287ccfedfc2c9a851e9245469793b6ffe0]]
Former-commit-id: 94b4dcd0904254e9b6e953f3155c0adf07f8f909 [formerly a12e98efc5a5914e11513e7275411dac2b65f2c0]
Former-commit-id: 935668eed616dea264b8439776ef112cee53150a
2025-07-25 20:50:05 +08:00

152 lines
5.9 KiB
JavaScript

import chalk from 'chalk';
import dotenv from 'dotenv';
import fs from 'fs';
import _ from 'lodash';
import path from 'path';
import Pocketbase, {} from 'pocketbase';
import prettier from 'prettier';
dotenv.config({
path: path.resolve(__dirname, '../server/env/.env.local')
});
if (!process.env.PB_HOST || !process.env.PB_EMAIL || !process.env.PB_PASSWORD) {
console.error('Please provide PB_HOST, PB_EMAIL, and PB_PASSWORD in your environment variables.');
process.exit(1);
}
const pb = new Pocketbase(process.env.PB_HOST);
let SCHEMA_STRING = `
import flattenSchemas from '@functions/utils/flattenSchema'
import { z } from 'zod/v4'
export const SCHEMAS = {
`;
try {
await pb
.collection('_superusers')
.authWithPassword(process.env.PB_EMAIL, process.env.PB_PASSWORD);
if (!pb.authStore.isSuperuser || !pb.authStore.isValid) {
console.error('Invalid credentials.');
process.exit(1);
}
}
catch {
console.error('Server is not reachable or credentials are invalid.');
process.exit(1);
}
const allModules = [
...fs.readdirSync('./server/src/apps', { withFileTypes: true }),
...fs.readdirSync('./server/src/core/lib', { withFileTypes: true })
];
const modulesMap = {};
const allCollections = await pb.collections.getFullList();
const collections = allCollections.filter(e => !e.system);
for (const collection of collections) {
const module = allModules.find(e => collection.name.startsWith(_.snakeCase(e.name)));
if (!module) {
console.log(chalk.yellow('[WARNING]') +
` Collection ${collection.name} does not have a corresponding module.`);
continue;
}
if (!modulesMap[module.name]) {
modulesMap[module.name] = [];
}
modulesMap[module.name]?.push(collection);
}
console.log(chalk.green('[INFO]') +
` Found ${Object.values(modulesMap).flat().length} collections across ${Object.keys(modulesMap).length} modules.`);
for (const module of allModules) {
if (!modulesMap[module.name]) {
continue;
}
const collections = modulesMap[module.name];
if (!collections) {
console.warn(chalk.yellow('[WARNING]') +
` No collections found for module ${chalk.bold(module.name)}.`);
continue;
}
const moduleName = collections[0].name.split('__')[0];
SCHEMA_STRING += ` ${moduleName}: {\n`;
for (const collection of collections ?? []) {
console.log(chalk.blue('[INFO]') +
` Found ${collection.fields.length} fields in collection ${chalk.bold(collection.name)} in module ${chalk.bold(moduleName)}.`);
const zodSchemaObject = {};
for (const field of collection.fields) {
if (field.name === 'id') {
// Skip fields that are auto-generated by PocketBase
continue;
}
switch (field.type) {
case 'text':
zodSchemaObject[field.name] = 'z.string()';
break;
case 'richtext':
zodSchemaObject[field.name] = 'z.string()';
break;
case 'number':
zodSchemaObject[field.name] = 'z.number()';
break;
case 'bool':
zodSchemaObject[field.name] = 'z.boolean()';
break;
case 'email':
zodSchemaObject[field.name] = 'z.email()';
break;
case 'url':
zodSchemaObject[field.name] = 'z.url()';
break;
case 'date':
zodSchemaObject[field.name] = 'z.string()';
break;
case 'autodate':
zodSchemaObject[field.name] = 'z.string()';
break;
case 'select':
const value = [...field.values, ...(field.required ? [] : [''])];
zodSchemaObject[field.name] =
field.maxSelect > 1
? `z.array(z.enum(${JSON.stringify(value)}))`
: `z.enum(${JSON.stringify(value)})`;
break;
case 'file':
zodSchemaObject[field.name] =
field.maxSelect > 1 ? 'z.array(z.string())' : 'z.string()';
break;
case 'relation':
zodSchemaObject[field.name] =
field.maxSelect > 1 ? `z.array(z.string())` : `z.string()`;
break;
case 'json':
zodSchemaObject[field.name] = 'z.any()';
break;
case 'geoPoint':
zodSchemaObject[field.name] =
'z.object({ lat: z.number(), lon: z.number() })';
break;
case 'password':
zodSchemaObject[field.name] = 'z.string()';
break;
default:
console.warn(chalk.yellow('[WARNING]') +
` Unknown field type ${field.type} for field ${field.name} in collection ${collection.name}.`);
continue;
}
}
const zodSchemaString = `z.object({\n${Object.entries(zodSchemaObject)
.map(([key, value]) => ` ${key}: ${value},`)
.join('\n')}\n}),`;
SCHEMA_STRING += ` ${collection.name.split('__').pop()}: ${zodSchemaString}\n`;
console.log(chalk.green('[INFO]') +
` Generated Zod schema for collection ${chalk.bold(collection.name)} in module ${chalk.bold(moduleName)}.`);
}
SCHEMA_STRING += ` },\n`;
}
SCHEMA_STRING += `}
const COLLECTION_SCHEMAS = flattenSchemas(SCHEMAS)
export default COLLECTION_SCHEMAS
`;
const formattedSchemaString = await prettier.format(SCHEMA_STRING, {
parser: 'typescript'
});
fs.writeFileSync(path.resolve(__dirname, '../server/src/core/schema.ts'), formattedSchemaString);