Ryan Malloy e92b7f8700 Initial commit: TigerStyle Life9 v1.0.0
Because cats have 9 lives, but servers don't - so they need
backup-restore! Complete backup solution with S3/MinIO support.

- Full WordPress backup (files + database)
- S3 / MinIO / S3-compatible storage backends
- Scheduled automatic backups
- Disaster recovery / one-click restore
- Backup integrity validation
- Cat-themed admin interface

Includes build.sh and .distignore for WordPress-installable release ZIPs.
2026-05-27 14:32:00 -06:00

382 lines
11 KiB
JavaScript

#!/usr/bin/env node
/**
* Copy Astro build assets to WordPress plugin structure
* and generate PHP-readable manifest
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
class WordPressAssetCopier {
constructor() {
this.projectRoot = path.resolve(__dirname, '..');
this.astroDistDir = path.join(this.projectRoot, 'admin/assets/dist');
this.wpAdminDir = path.join(this.projectRoot, 'admin');
this.manifest = {};
this.stats = {
filesCopied: 0,
errors: 0
};
}
async run() {
console.log('🚀 Starting WordPress asset integration...');
try {
// Ensure directories exist
await this.ensureDirectories();
// Parse Astro build output
await this.parseAstroOutput();
// Generate WordPress manifest
await this.generateManifest();
// Copy additional assets
await this.copyStaticAssets();
// Generate asset loader
await this.generateAssetLoader();
console.log(`✅ Successfully processed ${this.stats.filesCopied} files`);
if (this.stats.errors > 0) {
console.warn(`⚠️ ${this.stats.errors} errors encountered`);
}
} catch (error) {
console.error('❌ Asset integration failed:', error.message);
process.exit(1);
}
}
async ensureDirectories() {
const dirs = [
path.join(this.wpAdminDir, 'js'),
path.join(this.wpAdminDir, 'css'),
path.join(this.wpAdminDir, 'images')
];
for (const dir of dirs) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
}
}
async parseAstroOutput() {
if (!fs.existsSync(this.astroDistDir)) {
throw new Error(`Astro dist directory not found: ${this.astroDistDir}`);
}
// Read all files from dist directory
const files = await this.getAllFiles(this.astroDistDir);
for (const file of files) {
const relativePath = path.relative(this.astroDistDir, file);
const ext = path.extname(file).toLowerCase();
// Categorize and process files
if (ext === '.html') {
await this.processHtmlFile(file, relativePath);
} else if (ext === '.js') {
await this.processJsFile(file, relativePath);
} else if (ext === '.css') {
await this.processCssFile(file, relativePath);
} else if (['.png', '.jpg', '.jpeg', '.svg', '.gif'].includes(ext)) {
await this.processImageFile(file, relativePath);
}
}
}
async processHtmlFile(file, relativePath) {
const content = fs.readFileSync(file, 'utf-8');
const pageName = path.basename(relativePath, '.html');
// Extract CSS and JS references
const cssMatches = content.match(/<link[^>]*href="([^"]*\.css)"[^>]*>/g) || [];
const jsMatches = content.match(/<script[^>]*src="([^"]*\.js)"[^>]*>/g) || [];
const assets = {
css: cssMatches.map(match => {
const href = match.match(/href="([^"]*)"/)[1];
return href.startsWith('./') ? href.substring(2) : href;
}),
js: jsMatches.map(match => {
const src = match.match(/src="([^"]*)"/)[1];
return src.startsWith('./') ? src.substring(2) : src;
}),
html: relativePath
};
this.manifest[pageName] = assets;
// Copy HTML file to admin templates
const templateDir = path.join(this.wpAdminDir, 'templates');
if (!fs.existsSync(templateDir)) {
fs.mkdirSync(templateDir, { recursive: true });
}
const destPath = path.join(templateDir, `${pageName}.html`);
fs.copyFileSync(file, destPath);
this.stats.filesCopied++;
}
async processJsFile(file, relativePath) {
const destPath = path.join(this.wpAdminDir, relativePath);
const destDir = path.dirname(destPath);
if (!fs.existsSync(destDir)) {
fs.mkdirSync(destDir, { recursive: true });
}
// Process JavaScript for WordPress compatibility
let content = fs.readFileSync(file, 'utf-8');
// Replace placeholder variables with WordPress equivalents
content = content.replace(/__WP_NONCE__/g, 'window.tigerStyleLife9.nonce');
content = content.replace(/__WP_AJAX_URL__/g, 'window.tigerStyleLife9.ajaxUrl');
content = content.replace(/__WP_REST_URL__/g, 'window.tigerStyleLife9.restUrl');
content = content.replace(/__PLUGIN_URL__/g, 'window.tigerStyleLife9.pluginUrl');
fs.writeFileSync(destPath, content);
this.stats.filesCopied++;
}
async processCssFile(file, relativePath) {
const destPath = path.join(this.wpAdminDir, relativePath);
const destDir = path.dirname(destPath);
if (!fs.existsSync(destDir)) {
fs.mkdirSync(destDir, { recursive: true });
}
// Process CSS for WordPress admin compatibility
let content = fs.readFileSync(file, 'utf-8');
// Ensure all styles are scoped to plugin container
if (!content.includes('.tigerstyle-life9-container')) {
console.warn(`CSS file ${relativePath} may not be properly scoped`);
}
fs.writeFileSync(destPath, content);
this.stats.filesCopied++;
}
async processImageFile(file, relativePath) {
const destPath = path.join(this.wpAdminDir, relativePath);
const destDir = path.dirname(destPath);
if (!fs.existsSync(destDir)) {
fs.mkdirSync(destDir, { recursive: true });
}
fs.copyFileSync(file, destPath);
this.stats.filesCopied++;
}
async generateManifest() {
// Generate PHP manifest file
const phpManifest = this.generatePhpManifest();
const manifestPath = path.join(this.wpAdminDir, 'assets-manifest.php');
fs.writeFileSync(manifestPath, phpManifest);
// Generate JSON manifest for development
const jsonManifest = JSON.stringify(this.manifest, null, 2);
const jsonPath = path.join(this.wpAdminDir, 'assets-manifest.json');
fs.writeFileSync(jsonPath, jsonManifest);
console.log(`📝 Generated manifest with ${Object.keys(this.manifest).length} pages`);
}
generatePhpManifest() {
return `<?php
/**
* Auto-generated asset manifest
* Generated: ${new Date().toISOString()}
* Do not edit manually
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
return ${this.phpStringify(this.manifest, 0)};`;
}
phpStringify(obj, indent = 0) {
const spaces = ' '.repeat(indent);
if (Array.isArray(obj)) {
if (obj.length === 0) return '[]';
const items = obj.map(item =>
`${spaces} ${this.phpStringify(item, indent + 1)}`
).join(',\n');
return `[\n${items}\n${spaces}]`;
}
if (typeof obj === 'object' && obj !== null) {
const keys = Object.keys(obj);
if (keys.length === 0) return '[]';
const items = keys.map(key =>
`${spaces} '${key}' => ${this.phpStringify(obj[key], indent + 1)}`
).join(',\n');
return `[\n${items}\n${spaces}]`;
}
if (typeof obj === 'string') {
return `'${obj.replace(/'/g, "\\'")}'`;
}
if (typeof obj === 'boolean') {
return obj ? 'true' : 'false';
}
if (typeof obj === 'number') {
return obj.toString();
}
return 'null';
}
async copyStaticAssets() {
const publicDir = path.join(this.projectRoot, 'src/astro/public');
if (fs.existsSync(publicDir)) {
const files = await this.getAllFiles(publicDir);
for (const file of files) {
const relativePath = path.relative(publicDir, file);
const destPath = path.join(this.wpAdminDir, 'static', relativePath);
const destDir = path.dirname(destPath);
if (!fs.existsSync(destDir)) {
fs.mkdirSync(destDir, { recursive: true });
}
fs.copyFileSync(file, destPath);
this.stats.filesCopied++;
}
}
}
async generateAssetLoader() {
const loaderContent = `<?php
/**
* WordPress Asset Loader for TigerStyle Life9
* Auto-generated asset loading functions
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
/**
* Load assets for a specific page
*
* @param string $page_name Page name (e.g., 'backup-interface')
* @param array $extra_data Extra data to localize
*/
function tigerstyle_life9_load_page_assets($page_name, $extra_data = []) {
$manifest = require_once TIGERSTYLE_LIFE9_PLUGIN_DIR . 'admin/assets-manifest.php';
if (!isset($manifest[$page_name])) {
return false;
}
$assets = $manifest[$page_name];
$plugin_url = TIGERSTYLE_LIFE9_PLUGIN_URL;
// Enqueue CSS files
if (!empty($assets['css'])) {
foreach ($assets['css'] as $index => $css_file) {
wp_enqueue_style(
"tigerstyle-life9-{$page_name}-{$index}",
$plugin_url . 'admin/' . $css_file,
[],
TIGERSTYLE_LIFE9_VERSION
);
}
}
// Enqueue JavaScript files
if (!empty($assets['js'])) {
foreach ($assets['js'] as $index => $js_file) {
$handle = "tigerstyle-life9-{$page_name}-{$index}";
wp_enqueue_script(
$handle,
$plugin_url . 'admin/' . $js_file,
['jquery', 'wp-api'],
TIGERSTYLE_LIFE9_VERSION,
true
);
// Localize script data on the first JS file
if ($index === 0) {
$localize_data = array_merge([
'nonce' => wp_create_nonce('tigerstyle_life9_nonce'),
'ajaxUrl' => admin_url('admin-ajax.php'),
'restUrl' => rest_url('tigerstyle-life9/v1/'),
'pluginUrl' => $plugin_url,
'currentUser' => get_current_user_id(),
'capabilities' => [
'manage_backups' => current_user_can('manage_options'),
'download_backups' => current_user_can('manage_options')
],
'strings' => [
'backupStarted' => __('Backup Started', 'tigerstyle-life9'),
'backupComplete' => __('Backup Complete', 'tigerstyle-life9'),
'error' => __('Error', 'tigerstyle-life9'),
'confirm' => __('Are you sure?', 'tigerstyle-life9')
]
], $extra_data);
wp_localize_script($handle, 'tigerStyleLife9', $localize_data);
}
}
}
return true;
}`;
const loaderPath = path.join(this.wpAdminDir, 'asset-loader.php');
fs.writeFileSync(loaderPath, loaderContent);
console.log('🔧 Generated WordPress asset loader');
}
async getAllFiles(dir) {
const files = [];
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...await this.getAllFiles(fullPath));
} else {
files.push(fullPath);
}
}
return files;
}
}
// Run the copier
const copier = new WordPressAssetCopier();
copier.run().catch(console.error);