longcelot-sheet-db
Version:
Google Sheets-backed staging database adapter for Node.js with schema-first design
205 lines • 8.43 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.seedCommand = seedCommand;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const chalk_1 = __importDefault(require("chalk"));
const sheetAdapter_1 = require("../../adapter/sheetAdapter");
const actorConfig_1 = require("../../utils/actorConfig");
const cliFiles_1 = require("../../utils/cliFiles");
async function loadSeedData(seedFilePath) {
const mod = require(seedFilePath);
const exported = mod.default ?? mod;
if (typeof exported === 'function') {
return await exported(process.env);
}
return exported;
}
async function seedCommand(seedFile, opts) {
console.log(chalk_1.default.blue.bold('🌱 Seeding data into Google Sheets...\n'));
require('dotenv').config();
const requiredEnvVars = [
'GOOGLE_CLIENT_ID',
'GOOGLE_CLIENT_SECRET',
'GOOGLE_REDIRECT_URI',
'ADMIN_SHEET_ID',
];
for (const envVar of requiredEnvVars) {
if (!process.env[envVar]) {
console.error(chalk_1.default.red(`❌ Missing environment variable: ${envVar}`));
process.exit(1);
}
}
// Load tokens
const tokensPath = (0, cliFiles_1.resolveTokensPath)();
if (!fs_1.default.existsSync(tokensPath)) {
console.error(chalk_1.default.red('❌ No OAuth tokens found. Run: lsdb sync'));
process.exit(1);
}
let tokens;
try {
tokens = JSON.parse(fs_1.default.readFileSync(tokensPath, 'utf-8'));
}
catch {
console.error(chalk_1.default.red('❌ Failed to read tokens file.'));
process.exit(1);
}
// Load seed file
const seedFilePath = path_1.default.resolve(process.cwd(), seedFile);
if (!fs_1.default.existsSync(seedFilePath)) {
console.error(chalk_1.default.red(`❌ Seed file not found: ${seedFilePath}`));
process.exit(1);
}
let seedData;
try {
seedData = await loadSeedData(seedFilePath);
}
catch (err) {
console.error(chalk_1.default.red(`❌ Failed to load seed file: ${err}`));
process.exit(1);
}
let config;
try {
config = require((0, cliFiles_1.resolveConfigPath)()).default;
}
catch {
console.error(chalk_1.default.red('❌ lsdb.config.ts not found. Run: lsdb init'));
process.exit(1);
}
const adapter = (0, sheetAdapter_1.createSheetAdapter)({
adminSheetId: process.env.ADMIN_SHEET_ID,
credentials: {
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
redirectUri: process.env.GOOGLE_REDIRECT_URI,
},
tokens,
});
// Load and register all schemas
const schemasDir = path_1.default.join(process.cwd(), 'schemas');
for (const actorEntry of config.actors) {
const actor = (0, actorConfig_1.resolveActorName)(actorEntry);
const actorDir = path_1.default.join(schemasDir, actor);
if (!fs_1.default.existsSync(actorDir))
continue;
const files = fs_1.default.readdirSync(actorDir).filter((f) => f.endsWith('.ts'));
for (const file of files) {
try {
const schema = require(path_1.default.join(actorDir, file)).default;
adapter.registerSchema(schema);
}
catch { }
}
}
// Use admin context for seeding
const adapterWithContext = adapter.withContext({ userId: 'seed-cli', actor: 'admin', actorSheetId: process.env.ADMIN_SHEET_ID });
const isSkipExisting = opts?.skipExisting ?? false;
const isUpsert = opts?.upsert ?? false;
async function seedTable(ctx, tableName, records) {
let inserted = 0;
let skipped = 0;
let updated = 0;
let failed = 0;
for (const record of records) {
try {
const row = record;
if (isUpsert) {
// Determine unique column(s) for upsert — use _id if present, else first provided key
const whereKey = Object.keys(row)[0];
const where = { [whereKey]: row[whereKey] };
await ctx.table(tableName).upsert({ where, data: row, skipFKValidation: true });
updated++;
}
else if (isSkipExisting) {
// Try create; skip on unique constraint violation
try {
await ctx.table(tableName).create(row, { skipFKValidation: true });
inserted++;
}
catch (err) {
if (err instanceof Error && err.message.includes('Unique constraint')) {
skipped++;
}
else {
throw err;
}
}
}
else {
await ctx.table(tableName).create(row, { skipFKValidation: true });
inserted++;
}
}
catch (err) {
console.error(chalk_1.default.red(` ✖ Failed: ${err}`));
failed++;
}
}
return { inserted, skipped, updated, failed };
}
let totalInserted = 0;
let totalFailed = 0;
if (opts?.allActors) {
console.log(chalk_1.default.cyan('\nSeeding to all actor sheets (--all-actors)...'));
let users = [];
try {
users = await adapterWithContext.table('users').findMany();
}
catch {
console.error(chalk_1.default.red('❌ Could not read admin users table. Make sure `users` schema is registered and admin sheet has been synced.'));
process.exit(1);
}
if (!users || users.length === 0) {
console.warn(chalk_1.default.yellow('No users found in admin users table. Nothing to seed.'));
return;
}
let totalInsertedActors = 0;
let totalFailedActors = 0;
for (const user of users) {
if (!user.actor_sheet_id)
continue;
const targetAdapter = adapter.withContext({ userId: 'seed-cli', actor: 'admin', actorSheetId: user.actor_sheet_id });
for (const [tableName, records] of Object.entries(seedData)) {
if (!Array.isArray(records))
continue;
const result = await seedTable(targetAdapter, tableName, records);
totalInsertedActors += result.inserted + result.updated;
totalFailedActors += result.failed;
}
}
console.log();
console.log(chalk_1.default.bold(`Seed complete (all-actors): ${totalInsertedActors} inserted/updated, ${totalFailedActors} failed.`));
if (totalFailedActors > 0)
process.exit(1);
return;
}
for (const [tableName, records] of Object.entries(seedData)) {
if (!Array.isArray(records)) {
console.warn(chalk_1.default.yellow(` ⚠️ Skipping "${tableName}": expected an array of records`));
continue;
}
console.log(chalk_1.default.cyan(`\nSeeding ${tableName} (${records.length} records)...`));
const result = await seedTable(adapterWithContext, tableName, records);
const status = result.failed === 0 ? chalk_1.default.green('✓') : chalk_1.default.yellow('⚠');
const parts = [];
if (result.inserted > 0)
parts.push(`${result.inserted} inserted`);
if (result.updated > 0)
parts.push(`${result.updated} upserted`);
if (result.skipped > 0)
parts.push(`${result.skipped} skipped`);
if (result.failed > 0)
parts.push(`${result.failed} failed`);
console.log(` ${status} ${parts.join(', ')}`);
totalInserted += result.inserted + result.updated;
totalFailed += result.failed;
}
console.log();
console.log(chalk_1.default.bold(`Seed complete: ${totalInserted} inserted/updated, ${totalFailed} failed.`));
if (totalFailed > 0)
process.exit(1);
}
//# sourceMappingURL=seed.js.map