Clean-room copy of the reusable engines from comiida, with all instance data, secrets, dependencies, and build output excluded: - app/ Astro theme skeleton (no comiida blog posts; hero image -> placeholder) - api/ SeedProject PHP framework (no vendor/.env/config.php) - content-pipeline/ engine only (scripts/admin/prompts; empty runtime state) - astroagent.config.json + app/.astroagent/skills Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SYHWLHihq3v9nxNwoPCKSn
2.9 KiB
2.9 KiB
CLI Commands — commands/
Scripts run via php console <CommandName>. No HTTP context — output goes to stdout.
File Naming
PascalCaseCommand.php — e.g. SyncDataCommand.php
Running Commands
php console SyncDataCommand
php console SyncDataCommand --date="2024-10-28" --force
php console CleanupDatabaseCommand --days=30 --dry-run
php console ProcessEmailQueueCommand --limit=50
Command Structure
<?php
// commands/SyncDataCommand.php
echo "=== Data Sync ===\n";
$options = getopt("", ["date:", "force"]);
$date = $options['date'] ?? 'today';
$force = isset($options['force']);
echo "Syncing for: $date\n";
try {
$records = Db::select("SELECT * FROM sp_sync_queue WHERE synced_at IS NULL");
foreach ($records as $record) {
if (!$force) {
$exists = Db::getRow("SELECT 1 FROM sp_data WHERE external_id = :id LIMIT 1",
[':id' => $record['external_id']]
);
if ($exists) { echo "Skipping {$record['external_id']}\n"; continue; }
}
Db::insert('sp_data', [
'external_id' => $record['external_id'],
'payload' => $record['payload'],
'synced_at' => date('Y-m-d H:i:s')
]);
echo "Inserted {$record['external_id']}\n";
}
echo "Done.\n";
} catch (Exception $e) {
echo "ERROR: " . $e->getMessage() . "\n";
exit(1);
}
Error Handling Pattern
Log individual failures and keep processing — exit with code 1 only on fatal errors:
$errors = [];
$processed = 0;
foreach ($items as $item) {
try {
processItem($item);
$processed++;
echo ".";
} catch (Exception $e) {
$errors[] = ['id' => $item['id'], 'error' => $e->getMessage()];
echo "E";
}
}
echo "\n\nProcessed: $processed | Errors: " . count($errors) . "\n";
if (!empty($errors)) {
foreach ($errors as $err) {
echo "Item #{$err['id']}: {$err['error']}\n";
}
exit(1);
}
Dry-Run Pattern
$dryRun = isset($options['dry-run']);
if ($dryRun) { echo "DRY RUN — no changes will be made\n"; }
$count = Db::getRow("SELECT COUNT(*) as n FROM sp_logs WHERE created_at < :date",
[':date' => $cutoffDate]
);
echo "Rows to delete: {$count['n']}\n";
if (!$dryRun) {
Db::delete('sp_logs', 'created_at < :date', [':date' => $cutoffDate]);
}
Cron Setup
crontab -e
# Every 5 minutes
*/5 * * * * cd /www/wwwroot/appSeedProject && php console ProcessEmailQueueCommand >> /var/log/email-queue.log 2>&1
# Every hour
0 * * * * cd /www/wwwroot/appSeedProject && php console SyncDataCommand >> /var/log/sync.log 2>&1
# Nightly cleanup at 2 AM
0 2 * * * cd /www/wwwroot/appSeedProject && php console CleanupDatabaseCommand --days=30 >> /var/log/cleanup.log 2>&1