@wordpress-flow/cli 1.2.11 → 1.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +656 -32
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -80975,6 +80975,16 @@ class ConfigManager {
|
|
|
80975
80975
|
}
|
|
80976
80976
|
return result2;
|
|
80977
80977
|
}
|
|
80978
|
+
getRawConfig() {
|
|
80979
|
+
if (!this.rawConfig) {
|
|
80980
|
+
throw new Error("Configuration not loaded. Call loadConfig() first.");
|
|
80981
|
+
}
|
|
80982
|
+
return this.rawConfig;
|
|
80983
|
+
}
|
|
80984
|
+
getEnvironments() {
|
|
80985
|
+
const raw = this.getRawConfig();
|
|
80986
|
+
return raw.environments || [];
|
|
80987
|
+
}
|
|
80978
80988
|
getConfig() {
|
|
80979
80989
|
if (!this.config) {
|
|
80980
80990
|
throw new Error("Configuration not loaded. Call loadConfig() first.");
|
|
@@ -81126,6 +81136,9 @@ class Logger {
|
|
|
81126
81136
|
step(step, total, message) {
|
|
81127
81137
|
console.log(import_chalk2.default.cyan(`[${step}/${total}] ${message}`));
|
|
81128
81138
|
}
|
|
81139
|
+
substep(message) {
|
|
81140
|
+
console.log(import_chalk2.default.gray(` ↳ ${message}`));
|
|
81141
|
+
}
|
|
81129
81142
|
}
|
|
81130
81143
|
var logger = Logger.getInstance();
|
|
81131
81144
|
// src/commands/pull-command.ts
|
|
@@ -114591,6 +114604,7 @@ var $watch = watch;
|
|
|
114591
114604
|
// src/dev/dev-mode-orchestrator.ts
|
|
114592
114605
|
import * as path17 from "path";
|
|
114593
114606
|
import * as fs15 from "fs";
|
|
114607
|
+
import * as esbuild from "esbuild";
|
|
114594
114608
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
114595
114609
|
|
|
114596
114610
|
// src/build/block-scanner.ts
|
|
@@ -114936,10 +114950,6 @@ class BlockHashManager {
|
|
|
114936
114950
|
logger.debug(`${blockName}: No stored hash, needs rebuild`);
|
|
114937
114951
|
return true;
|
|
114938
114952
|
}
|
|
114939
|
-
if (storedHash.cliVersion !== this.cliVersion) {
|
|
114940
|
-
logger.debug(`${blockName}: CLI version changed, needs rebuild`);
|
|
114941
|
-
return true;
|
|
114942
|
-
}
|
|
114943
114953
|
const currentHash = this.calculateSourceHash(sourcePath);
|
|
114944
114954
|
if (!currentHash) {
|
|
114945
114955
|
logger.debug(`${blockName}: Source file missing, needs rebuild`);
|
|
@@ -116185,7 +116195,7 @@ class DockerEnvManager {
|
|
|
116185
116195
|
// package.json
|
|
116186
116196
|
var package_default = {
|
|
116187
116197
|
name: "@wordpress-flow/cli",
|
|
116188
|
-
version: "1.2.
|
|
116198
|
+
version: "1.2.13",
|
|
116189
116199
|
type: "module",
|
|
116190
116200
|
description: "TypeScript-based WordPress block creation system",
|
|
116191
116201
|
main: "dist/index.js",
|
|
@@ -116547,14 +116557,11 @@ class DevModeOrchestrator {
|
|
|
116547
116557
|
return;
|
|
116548
116558
|
const config = this.configManager.getConfig();
|
|
116549
116559
|
const postTypes = config.sync.postTypes || ["post", "page"];
|
|
116550
|
-
console.log(`[DEBUG] Initial push - Post types: ${JSON.stringify(postTypes)}`);
|
|
116551
116560
|
const contentFiles = [];
|
|
116552
116561
|
for (const postType of postTypes) {
|
|
116553
116562
|
const typeDir = path17.join(this.contentDir, postType);
|
|
116554
|
-
console.log(`[DEBUG] Checking directory: ${typeDir}, exists: ${fs15.existsSync(typeDir)}`);
|
|
116555
116563
|
if (fs15.existsSync(typeDir)) {
|
|
116556
116564
|
const files = this.findMDXFiles(typeDir);
|
|
116557
|
-
console.log(`[DEBUG] Found ${files.length} files in ${postType}`);
|
|
116558
116565
|
contentFiles.push(...files);
|
|
116559
116566
|
}
|
|
116560
116567
|
}
|
|
@@ -116687,6 +116694,9 @@ class DevModeOrchestrator {
|
|
|
116687
116694
|
}
|
|
116688
116695
|
startWatchers() {
|
|
116689
116696
|
this.startBlockWatcher();
|
|
116697
|
+
if (this.scriptsPath) {
|
|
116698
|
+
this.startScriptWatcher();
|
|
116699
|
+
}
|
|
116690
116700
|
if (this.templatesDir && this.options.buildTemplates) {
|
|
116691
116701
|
this.startTemplateWatcher();
|
|
116692
116702
|
}
|
|
@@ -116730,6 +116740,20 @@ class DevModeOrchestrator {
|
|
|
116730
116740
|
logger.debug("Block watcher ready");
|
|
116731
116741
|
});
|
|
116732
116742
|
}
|
|
116743
|
+
startScriptWatcher() {
|
|
116744
|
+
if (!this.scriptsPath || !fs15.existsSync(this.scriptsPath))
|
|
116745
|
+
return;
|
|
116746
|
+
logger.debug(`Watching scripts: ${this.scriptsPath}`);
|
|
116747
|
+
const watcher = $watch("**/*.{ts,js}", {
|
|
116748
|
+
cwd: this.scriptsPath,
|
|
116749
|
+
...this.getWatcherOptions()
|
|
116750
|
+
});
|
|
116751
|
+
this.watchers.push(watcher);
|
|
116752
|
+
watcher.on("add", (filePath2) => this.handleScriptFileEvent("add", filePath2)).on("change", (filePath2) => this.handleScriptFileEvent("change", filePath2)).on("unlink", (filePath2) => this.handleScriptFileEvent("unlink", filePath2)).on("error", (error) => logger.error("Script watcher error:", error));
|
|
116753
|
+
watcher.on("ready", () => {
|
|
116754
|
+
logger.debug("Script watcher ready");
|
|
116755
|
+
});
|
|
116756
|
+
}
|
|
116733
116757
|
startTemplateWatcher() {
|
|
116734
116758
|
if (!this.templatesDir || !fs15.existsSync(this.templatesDir))
|
|
116735
116759
|
return;
|
|
@@ -116758,8 +116782,6 @@ class DevModeOrchestrator {
|
|
|
116758
116782
|
const config = this.configManager.getConfig();
|
|
116759
116783
|
const postTypes = config.sync.postTypes || ["post", "page"];
|
|
116760
116784
|
const watchPatterns = postTypes.map((type) => `${type}/**/*.mdx`);
|
|
116761
|
-
console.log(`[DEBUG] Post types from config: ${JSON.stringify(postTypes)}`);
|
|
116762
|
-
console.log(`[DEBUG] Watch patterns: ${JSON.stringify(watchPatterns)}`);
|
|
116763
116785
|
logger.debug(`Watching content: ${this.contentDir} (${postTypes.join(", ")})`);
|
|
116764
116786
|
const watcher = $watch(watchPatterns, {
|
|
116765
116787
|
cwd: this.contentDir,
|
|
@@ -116792,6 +116814,44 @@ class DevModeOrchestrator {
|
|
|
116792
116814
|
this.rescanBlocks();
|
|
116793
116815
|
}, this.rescanDebounceMs);
|
|
116794
116816
|
}
|
|
116817
|
+
handleScriptFileEvent(eventType, filePath2) {
|
|
116818
|
+
const fullPath = path17.resolve(this.scriptsPath, filePath2);
|
|
116819
|
+
const scriptName = path17.basename(filePath2, path17.extname(filePath2));
|
|
116820
|
+
if (eventType === "unlink") {
|
|
116821
|
+
const outputPath = path17.join(this.scriptsOutputDir, `${scriptName}.js`);
|
|
116822
|
+
if (fs15.existsSync(outputPath)) {
|
|
116823
|
+
fs15.unlinkSync(outputPath);
|
|
116824
|
+
console.log(`\uD83D\uDDD1️ ${scriptName}.js deleted`);
|
|
116825
|
+
}
|
|
116826
|
+
} else {
|
|
116827
|
+
this.buildScript(fullPath, scriptName);
|
|
116828
|
+
}
|
|
116829
|
+
}
|
|
116830
|
+
async buildScript(scriptPath, scriptName) {
|
|
116831
|
+
const startTime = Date.now();
|
|
116832
|
+
process.stdout.write(`\uD83D\uDCDC ${scriptName} → `);
|
|
116833
|
+
try {
|
|
116834
|
+
const outputPath = path17.join(this.scriptsOutputDir, `${scriptName}.js`);
|
|
116835
|
+
await esbuild.build({
|
|
116836
|
+
entryPoints: [scriptPath],
|
|
116837
|
+
outfile: outputPath,
|
|
116838
|
+
bundle: true,
|
|
116839
|
+
minify: false,
|
|
116840
|
+
platform: "browser",
|
|
116841
|
+
target: "es2015",
|
|
116842
|
+
format: "iife",
|
|
116843
|
+
external: ["react", "react-dom", "@wordpress/*", "jquery"],
|
|
116844
|
+
define: { "process.env.NODE_ENV": '"development"' },
|
|
116845
|
+
logLevel: "warning",
|
|
116846
|
+
allowOverwrite: true,
|
|
116847
|
+
sourcemap: true
|
|
116848
|
+
});
|
|
116849
|
+
const duration = `${((Date.now() - startTime) / 1000).toFixed(1)}s`;
|
|
116850
|
+
console.log(`✅ (${duration})`);
|
|
116851
|
+
} catch (error) {
|
|
116852
|
+
console.log(`❌ ${error.message}`);
|
|
116853
|
+
}
|
|
116854
|
+
}
|
|
116795
116855
|
handleTemplateFileEvent(eventType, filePath2) {
|
|
116796
116856
|
const fullPath = path17.resolve(this.templatesDir, filePath2);
|
|
116797
116857
|
this.changeQueue.queueTemplateChange(fullPath, eventType);
|
|
@@ -117108,6 +117168,7 @@ class DevCommand {
|
|
|
117108
117168
|
|
|
117109
117169
|
// src/commands/build-command.ts
|
|
117110
117170
|
import * as path22 from "path";
|
|
117171
|
+
import * as fs19 from "fs";
|
|
117111
117172
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
117112
117173
|
|
|
117113
117174
|
// src/build/block-builder.ts
|
|
@@ -117210,14 +117271,14 @@ class WebpackRunner {
|
|
|
117210
117271
|
// src/build/script-builder.ts
|
|
117211
117272
|
import * as fs17 from "fs";
|
|
117212
117273
|
import * as path19 from "path";
|
|
117213
|
-
import * as
|
|
117274
|
+
import * as esbuild2 from "esbuild";
|
|
117214
117275
|
class ScriptBuilder {
|
|
117215
117276
|
async buildScript(scriptPath, outputDir, scriptName) {
|
|
117216
117277
|
try {
|
|
117217
117278
|
logger.debug(`Building script: ${scriptName}`);
|
|
117218
117279
|
fs17.mkdirSync(outputDir, { recursive: true });
|
|
117219
117280
|
const outputPath = path19.join(outputDir, scriptName.replace(/\.ts$/, ".js"));
|
|
117220
|
-
await
|
|
117281
|
+
await esbuild2.build({
|
|
117221
117282
|
entryPoints: [scriptPath],
|
|
117222
117283
|
outfile: outputPath,
|
|
117223
117284
|
bundle: true,
|
|
@@ -117264,7 +117325,7 @@ class ScriptBuilder {
|
|
|
117264
117325
|
}
|
|
117265
117326
|
|
|
117266
117327
|
// src/build/block-builder.ts
|
|
117267
|
-
import * as
|
|
117328
|
+
import * as esbuild3 from "esbuild";
|
|
117268
117329
|
class BlockBuilder {
|
|
117269
117330
|
webpackRunner;
|
|
117270
117331
|
scriptBuilder;
|
|
@@ -117341,7 +117402,7 @@ if (document.readyState === 'loading') {
|
|
|
117341
117402
|
try {
|
|
117342
117403
|
logger.debug(`Generating SSR version for ${block.name} using esbuild`);
|
|
117343
117404
|
const ssrPath = path20.join(outputDir, "ssr.js");
|
|
117344
|
-
await
|
|
117405
|
+
await esbuild3.build({
|
|
117345
117406
|
entryPoints: [block.filePath],
|
|
117346
117407
|
outfile: ssrPath,
|
|
117347
117408
|
platform: "node",
|
|
@@ -117437,7 +117498,7 @@ if (document.readyState === 'loading') {
|
|
|
117437
117498
|
if (typeof global.React === "undefined") {
|
|
117438
117499
|
global.React = require_react();
|
|
117439
117500
|
}
|
|
117440
|
-
await
|
|
117501
|
+
await esbuild3.build({
|
|
117441
117502
|
entryPoints: [filePath2],
|
|
117442
117503
|
outfile: tempFile,
|
|
117443
117504
|
platform: "node",
|
|
@@ -117700,6 +117761,12 @@ class WorkerPool {
|
|
|
117700
117761
|
// src/commands/build-command.ts
|
|
117701
117762
|
var __filename3 = fileURLToPath5(import.meta.url);
|
|
117702
117763
|
var __dirname3 = path22.dirname(__filename3);
|
|
117764
|
+
var packageJsonPath = path22.join(__dirname3, "..", "..", "..", "package.json");
|
|
117765
|
+
var packageVersion = "1.2.12";
|
|
117766
|
+
try {
|
|
117767
|
+
const packageJson = JSON.parse(fs19.readFileSync(packageJsonPath, "utf8"));
|
|
117768
|
+
packageVersion = packageJson.version;
|
|
117769
|
+
} catch {}
|
|
117703
117770
|
|
|
117704
117771
|
class BuildCommand {
|
|
117705
117772
|
configManager;
|
|
@@ -117708,6 +117775,7 @@ class BuildCommand {
|
|
|
117708
117775
|
typeGenerator;
|
|
117709
117776
|
phpGenerator;
|
|
117710
117777
|
blockScripts;
|
|
117778
|
+
hashManager = null;
|
|
117711
117779
|
constructor() {
|
|
117712
117780
|
this.configManager = ConfigManager.getInstance();
|
|
117713
117781
|
this.blockScanner = new BlockScanner;
|
|
@@ -117758,6 +117826,27 @@ class BuildCommand {
|
|
|
117758
117826
|
} else {
|
|
117759
117827
|
logger.info(`Found ${blocks.length} block(s): ${blocks.map((b) => b.name).join(", ")}`);
|
|
117760
117828
|
}
|
|
117829
|
+
this.hashManager = new BlockHashManager(outputDir, packageVersion);
|
|
117830
|
+
let blocksToRebuild = blocks;
|
|
117831
|
+
if (!options.noCache) {
|
|
117832
|
+
const stats = this.hashManager.getStats(blocks);
|
|
117833
|
+
if (stats.cached > 0) {
|
|
117834
|
+
logger.info(`\uD83D\uDCE6 Cache: ${stats.cached} block(s) unchanged, ${stats.needsRebuild} need(s) rebuild`);
|
|
117835
|
+
blocksToRebuild = this.hashManager.filterBlocksNeedingRebuild(blocks);
|
|
117836
|
+
const skippedBlocks = blocks.filter((b) => !blocksToRebuild.find((rb) => rb.name === b.name));
|
|
117837
|
+
if (skippedBlocks.length > 0) {
|
|
117838
|
+
logger.info(`⏭️ Skipping: ${skippedBlocks.map((b) => b.name).join(", ")}`);
|
|
117839
|
+
}
|
|
117840
|
+
}
|
|
117841
|
+
} else {
|
|
117842
|
+
logger.info("\uD83D\uDD04 Cache disabled (--no-cache), rebuilding all blocks");
|
|
117843
|
+
this.hashManager.clearAllHashes();
|
|
117844
|
+
}
|
|
117845
|
+
if (blocksToRebuild.length === 0) {
|
|
117846
|
+
logger.success("✨ All blocks are up to date! Nothing to build.");
|
|
117847
|
+
return;
|
|
117848
|
+
}
|
|
117849
|
+
logger.info(`\uD83D\uDD28 Building ${blocksToRebuild.length} block(s): ${blocksToRebuild.map((b) => b.name).join(", ")}`);
|
|
117761
117850
|
const scriptsOutputDir = this.configManager.resolvePath(config.paths.scriptsDist);
|
|
117762
117851
|
const workerPool = new WorkerPool({
|
|
117763
117852
|
concurrency: options.concurrency,
|
|
@@ -117766,9 +117855,14 @@ class BuildCommand {
|
|
|
117766
117855
|
webpackConfigPath: webpackConfig,
|
|
117767
117856
|
scriptsPath
|
|
117768
117857
|
});
|
|
117769
|
-
const results = await workerPool.buildAll(
|
|
117858
|
+
const results = await workerPool.buildAll(blocksToRebuild, (completed, total, blockName, success, error) => {
|
|
117770
117859
|
if (success) {
|
|
117771
117860
|
logger.success(`✅ Built: ${blockName} [${completed}/${total}]`);
|
|
117861
|
+
const block = blocksToRebuild.find((b) => b.name === blockName);
|
|
117862
|
+
if (block && this.hashManager) {
|
|
117863
|
+
const sourceHash = this.hashManager.calculateSourceHash(block.filePath);
|
|
117864
|
+
this.hashManager.storeHash(blockName, sourceHash);
|
|
117865
|
+
}
|
|
117772
117866
|
} else {
|
|
117773
117867
|
logger.error(`❌ Failed: ${blockName} [${completed}/${total}]: ${error || "unknown error"}`);
|
|
117774
117868
|
}
|
|
@@ -117797,11 +117891,17 @@ Failed to build ${failures.length} block(s):`);
|
|
|
117797
117891
|
}
|
|
117798
117892
|
await this.generateTypeDefinitions(outputDir);
|
|
117799
117893
|
const successCount = results.filter((r) => r.success).length;
|
|
117894
|
+
const totalBlocks = blocks.length;
|
|
117895
|
+
const cachedCount = totalBlocks - blocksToRebuild.length;
|
|
117800
117896
|
if (failures.length > 0) {
|
|
117801
|
-
logger.warn(`Build completed with errors. Built ${successCount}/${
|
|
117897
|
+
logger.warn(`Build completed with errors. Built ${successCount}/${blocksToRebuild.length} block(s)${cachedCount > 0 ? `, ${cachedCount} cached` : ""}.`);
|
|
117802
117898
|
process.exit(1);
|
|
117803
117899
|
} else {
|
|
117804
|
-
|
|
117900
|
+
if (cachedCount > 0) {
|
|
117901
|
+
logger.success(`Build completed successfully! Built ${successCount} block(s), ${cachedCount} cached.`);
|
|
117902
|
+
} else {
|
|
117903
|
+
logger.success(`Build completed successfully! Built ${successCount} block(s).`);
|
|
117904
|
+
}
|
|
117805
117905
|
}
|
|
117806
117906
|
} catch (error) {
|
|
117807
117907
|
logger.error("Build operation failed:");
|
|
@@ -117830,7 +117930,7 @@ Failed to build ${failures.length} block(s):`);
|
|
|
117830
117930
|
}
|
|
117831
117931
|
|
|
117832
117932
|
// src/commands/build-templates-command.ts
|
|
117833
|
-
import * as
|
|
117933
|
+
import * as fs20 from "fs";
|
|
117834
117934
|
import * as path23 from "path";
|
|
117835
117935
|
class BuildTemplatesCommand {
|
|
117836
117936
|
configManager;
|
|
@@ -117842,11 +117942,11 @@ class BuildTemplatesCommand {
|
|
|
117842
117942
|
const config = this.configManager.getConfig();
|
|
117843
117943
|
const templatesDir = options.templatesDir || this.configManager.resolvePath(config.paths.templates);
|
|
117844
117944
|
const outputDir = options.outputDir || this.configManager.resolvePath(config.paths.templatesOutput);
|
|
117845
|
-
if (!
|
|
117945
|
+
if (!fs20.existsSync(templatesDir)) {
|
|
117846
117946
|
logger.error(`Templates directory not found: ${templatesDir}`);
|
|
117847
117947
|
return;
|
|
117848
117948
|
}
|
|
117849
|
-
|
|
117949
|
+
fs20.mkdirSync(outputDir, { recursive: true });
|
|
117850
117950
|
const blocksOutputDir = this.configManager.resolvePath(config.paths.blocksDist);
|
|
117851
117951
|
const blockRegistry = new BlockRegistry(blocksOutputDir);
|
|
117852
117952
|
await blockRegistry.loadBuiltBlocks();
|
|
@@ -117884,17 +117984,17 @@ class BuildTemplatesCommand {
|
|
|
117884
117984
|
}
|
|
117885
117985
|
}
|
|
117886
117986
|
async buildTemplate(templatePath, templateName, outputDir, renderer) {
|
|
117887
|
-
const mdxContent =
|
|
117987
|
+
const mdxContent = fs20.readFileSync(templatePath, "utf8");
|
|
117888
117988
|
const cleanContent = this.removeFrontmatter(mdxContent);
|
|
117889
117989
|
const html5 = await renderer.renderMDXToHTML(cleanContent);
|
|
117890
117990
|
const cleanHTML = renderer.postProcessHTML(html5);
|
|
117891
117991
|
const outputPath = path23.join(outputDir, `${templateName}.html`);
|
|
117892
|
-
|
|
117992
|
+
fs20.writeFileSync(outputPath, cleanHTML, "utf8");
|
|
117893
117993
|
logger.info(`✓ Generated: ${path23.relative(process.cwd(), outputPath)}`);
|
|
117894
117994
|
}
|
|
117895
117995
|
findMDXFiles(dir) {
|
|
117896
117996
|
const files = [];
|
|
117897
|
-
const entries =
|
|
117997
|
+
const entries = fs20.readdirSync(dir, { withFileTypes: true });
|
|
117898
117998
|
for (const entry of entries) {
|
|
117899
117999
|
const fullPath = path23.join(dir, entry.name);
|
|
117900
118000
|
if (entry.isDirectory()) {
|
|
@@ -117912,7 +118012,7 @@ class BuildTemplatesCommand {
|
|
|
117912
118012
|
}
|
|
117913
118013
|
|
|
117914
118014
|
// src/commands/env-command.ts
|
|
117915
|
-
import * as
|
|
118015
|
+
import * as fs21 from "fs";
|
|
117916
118016
|
import * as path24 from "path";
|
|
117917
118017
|
class EnvCommand {
|
|
117918
118018
|
configManager;
|
|
@@ -117921,11 +118021,11 @@ class EnvCommand {
|
|
|
117921
118021
|
}
|
|
117922
118022
|
getDockerEnvManager() {
|
|
117923
118023
|
const config = this.configManager.getConfig();
|
|
117924
|
-
const
|
|
118024
|
+
const packageJsonPath2 = path24.join(this.configManager.getConfigDir(), "package.json");
|
|
117925
118025
|
let projectName = "wordpress-project";
|
|
117926
|
-
if (
|
|
118026
|
+
if (fs21.existsSync(packageJsonPath2)) {
|
|
117927
118027
|
try {
|
|
117928
|
-
const pkg = JSON.parse(
|
|
118028
|
+
const pkg = JSON.parse(fs21.readFileSync(packageJsonPath2, "utf8"));
|
|
117929
118029
|
projectName = pkg.name || projectName;
|
|
117930
118030
|
} catch {}
|
|
117931
118031
|
}
|
|
@@ -117974,6 +118074,501 @@ class EnvCommand {
|
|
|
117974
118074
|
}
|
|
117975
118075
|
}
|
|
117976
118076
|
|
|
118077
|
+
// src/commands/deploy-command.ts
|
|
118078
|
+
import * as fs23 from "fs";
|
|
118079
|
+
import * as path26 from "path";
|
|
118080
|
+
|
|
118081
|
+
// src/wordpress/ssh-tunnel.ts
|
|
118082
|
+
import * as fs22 from "fs";
|
|
118083
|
+
import * as path25 from "path";
|
|
118084
|
+
import { spawn } from "child_process";
|
|
118085
|
+
class SSHTunnel {
|
|
118086
|
+
configManager;
|
|
118087
|
+
secrets = new Map;
|
|
118088
|
+
tunnelProcess = null;
|
|
118089
|
+
constructor() {
|
|
118090
|
+
this.configManager = ConfigManager.getInstance();
|
|
118091
|
+
this.loadSecrets();
|
|
118092
|
+
}
|
|
118093
|
+
loadSecrets() {
|
|
118094
|
+
const secretsPath = path25.join(process.cwd(), "wordpress-flow.secrets");
|
|
118095
|
+
if (!fs22.existsSync(secretsPath))
|
|
118096
|
+
return;
|
|
118097
|
+
try {
|
|
118098
|
+
const content4 = fs22.readFileSync(secretsPath, "utf8");
|
|
118099
|
+
for (const line of content4.split(`
|
|
118100
|
+
`)) {
|
|
118101
|
+
const trimmed = line.trim();
|
|
118102
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
118103
|
+
continue;
|
|
118104
|
+
const eqIndex = trimmed.indexOf("=");
|
|
118105
|
+
if (eqIndex === -1)
|
|
118106
|
+
continue;
|
|
118107
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
118108
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
118109
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
118110
|
+
value = value.slice(1, -1);
|
|
118111
|
+
}
|
|
118112
|
+
this.secrets.set(key, value);
|
|
118113
|
+
}
|
|
118114
|
+
} catch {}
|
|
118115
|
+
}
|
|
118116
|
+
resolveVariables(value) {
|
|
118117
|
+
return value.replace(/\$\{([^}]+)\}/g, (match2, varName) => {
|
|
118118
|
+
const secret = this.secrets.get(varName);
|
|
118119
|
+
if (secret !== undefined)
|
|
118120
|
+
return secret;
|
|
118121
|
+
const envVar = process.env[varName];
|
|
118122
|
+
if (envVar !== undefined)
|
|
118123
|
+
return envVar;
|
|
118124
|
+
return match2;
|
|
118125
|
+
});
|
|
118126
|
+
}
|
|
118127
|
+
getEnvironment(name2) {
|
|
118128
|
+
const environments = this.configManager.getEnvironments();
|
|
118129
|
+
const env2 = environments.find((e) => e.name === name2);
|
|
118130
|
+
if (!env2) {
|
|
118131
|
+
throw new Error(`Environment "${name2}" not found. Available: ${environments.map((e) => e.name).join(", ")}`);
|
|
118132
|
+
}
|
|
118133
|
+
return env2;
|
|
118134
|
+
}
|
|
118135
|
+
async connect(envName) {
|
|
118136
|
+
const env2 = this.getEnvironment(envName);
|
|
118137
|
+
if (!env2.database) {
|
|
118138
|
+
throw new Error(`No database config for environment "${envName}"`);
|
|
118139
|
+
}
|
|
118140
|
+
const db = env2.database;
|
|
118141
|
+
const dbHost = this.resolveVariables(db.host);
|
|
118142
|
+
const dbPort = db.port || 3306;
|
|
118143
|
+
const dbUser = this.resolveVariables(db.username);
|
|
118144
|
+
const dbPass = this.resolveVariables(db.password);
|
|
118145
|
+
const dbName = this.resolveVariables(db.database);
|
|
118146
|
+
const localPort = 13306 + Math.floor(Math.random() * 1000);
|
|
118147
|
+
const sshPort = env2.ssh.port || 22;
|
|
118148
|
+
const target = `${env2.ssh.user}@${env2.ssh.host}`;
|
|
118149
|
+
let sshFlags = `-p ${sshPort} -o ServerAliveInterval=30 -o StrictHostKeyChecking=no`;
|
|
118150
|
+
let prefix = "";
|
|
118151
|
+
if (env2.ssh.privateKeyPath) {
|
|
118152
|
+
sshFlags += ` -i ${this.resolveVariables(env2.ssh.privateKeyPath)}`;
|
|
118153
|
+
} else if (env2.ssh.password) {
|
|
118154
|
+
const password = this.resolveVariables(env2.ssh.password);
|
|
118155
|
+
const escaped = password.replace(/'/g, "'\\''");
|
|
118156
|
+
prefix = `sshpass -p '${escaped}' `;
|
|
118157
|
+
sshFlags += ` -o PreferredAuthentications=password -o PubkeyAuthentication=no`;
|
|
118158
|
+
}
|
|
118159
|
+
const tunnelCmd = `${prefix}ssh ${sshFlags} -N -L ${localPort}:${dbHost}:${dbPort} ${target}`;
|
|
118160
|
+
logger.substep(`SSH tunnel: localhost:${localPort} → ${envName} (${dbHost}:${dbPort})`);
|
|
118161
|
+
this.tunnelProcess = spawn("bash", ["-c", tunnelCmd], {
|
|
118162
|
+
stdio: "pipe",
|
|
118163
|
+
detached: false
|
|
118164
|
+
});
|
|
118165
|
+
await this.waitForPort(localPort, 15000);
|
|
118166
|
+
this.configManager.setDatabaseConnection({
|
|
118167
|
+
host: "127.0.0.1",
|
|
118168
|
+
port: localPort,
|
|
118169
|
+
database: dbName,
|
|
118170
|
+
username: dbUser,
|
|
118171
|
+
password: dbPass
|
|
118172
|
+
});
|
|
118173
|
+
return () => {
|
|
118174
|
+
this.tunnelProcess?.kill();
|
|
118175
|
+
this.tunnelProcess = null;
|
|
118176
|
+
};
|
|
118177
|
+
}
|
|
118178
|
+
waitForPort(port, timeoutMs) {
|
|
118179
|
+
const net = __require("net");
|
|
118180
|
+
const start2 = Date.now();
|
|
118181
|
+
return new Promise((resolve7, reject) => {
|
|
118182
|
+
const tryConnect = () => {
|
|
118183
|
+
if (Date.now() - start2 > timeoutMs) {
|
|
118184
|
+
return reject(new Error(`SSH tunnel timed out — port ${port} not reachable after ${timeoutMs / 1000}s`));
|
|
118185
|
+
}
|
|
118186
|
+
const socket = net.createConnection({ port, host: "127.0.0.1" });
|
|
118187
|
+
socket.setTimeout(500);
|
|
118188
|
+
socket.on("connect", () => {
|
|
118189
|
+
socket.destroy();
|
|
118190
|
+
resolve7();
|
|
118191
|
+
});
|
|
118192
|
+
socket.on("error", () => {
|
|
118193
|
+
socket.destroy();
|
|
118194
|
+
setTimeout(tryConnect, 300);
|
|
118195
|
+
});
|
|
118196
|
+
socket.on("timeout", () => {
|
|
118197
|
+
socket.destroy();
|
|
118198
|
+
setTimeout(tryConnect, 300);
|
|
118199
|
+
});
|
|
118200
|
+
};
|
|
118201
|
+
tryConnect();
|
|
118202
|
+
});
|
|
118203
|
+
}
|
|
118204
|
+
}
|
|
118205
|
+
|
|
118206
|
+
// src/commands/deploy-command.ts
|
|
118207
|
+
import { execSync as execSync3 } from "child_process";
|
|
118208
|
+
|
|
118209
|
+
class DeployCommand {
|
|
118210
|
+
configManager;
|
|
118211
|
+
secrets = new Map;
|
|
118212
|
+
constructor() {
|
|
118213
|
+
this.configManager = ConfigManager.getInstance();
|
|
118214
|
+
this.loadSecrets();
|
|
118215
|
+
}
|
|
118216
|
+
loadSecrets() {
|
|
118217
|
+
const secretsPath = path26.join(process.cwd(), "wordpress-flow.secrets");
|
|
118218
|
+
if (!fs23.existsSync(secretsPath)) {
|
|
118219
|
+
logger.debug("No secrets file found at wordpress-flow.secrets");
|
|
118220
|
+
return;
|
|
118221
|
+
}
|
|
118222
|
+
try {
|
|
118223
|
+
const content4 = fs23.readFileSync(secretsPath, "utf8");
|
|
118224
|
+
for (const line of content4.split(`
|
|
118225
|
+
`)) {
|
|
118226
|
+
const trimmed = line.trim();
|
|
118227
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
118228
|
+
continue;
|
|
118229
|
+
const eqIndex = trimmed.indexOf("=");
|
|
118230
|
+
if (eqIndex === -1)
|
|
118231
|
+
continue;
|
|
118232
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
118233
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
118234
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
118235
|
+
value = value.slice(1, -1);
|
|
118236
|
+
}
|
|
118237
|
+
this.secrets.set(key, value);
|
|
118238
|
+
}
|
|
118239
|
+
logger.debug(`Loaded ${this.secrets.size} secrets`);
|
|
118240
|
+
} catch (error) {
|
|
118241
|
+
logger.warn(`Failed to load secrets: ${error.message}`);
|
|
118242
|
+
}
|
|
118243
|
+
}
|
|
118244
|
+
resolveVariables(value) {
|
|
118245
|
+
return value.replace(/\$\{([^}]+)\}/g, (match2, varName) => {
|
|
118246
|
+
const secret = this.secrets.get(varName);
|
|
118247
|
+
if (secret !== undefined)
|
|
118248
|
+
return secret;
|
|
118249
|
+
const envVar = process.env[varName];
|
|
118250
|
+
if (envVar !== undefined)
|
|
118251
|
+
return envVar;
|
|
118252
|
+
logger.warn(`Variable ${varName} not found in secrets or environment`);
|
|
118253
|
+
return match2;
|
|
118254
|
+
});
|
|
118255
|
+
}
|
|
118256
|
+
buildSSHConfig(env2) {
|
|
118257
|
+
return `${env2.ssh.user}@${env2.ssh.host}`;
|
|
118258
|
+
}
|
|
118259
|
+
buildSSHParts(env2) {
|
|
118260
|
+
const port = env2.ssh.port || 22;
|
|
118261
|
+
let sshFlags = `-p ${port} -o ServerAliveInterval=30 -o StrictHostKeyChecking=no`;
|
|
118262
|
+
let prefix = "";
|
|
118263
|
+
if (env2.ssh.privateKeyPath) {
|
|
118264
|
+
const keyPath = this.resolveVariables(env2.ssh.privateKeyPath);
|
|
118265
|
+
sshFlags += ` -i ${keyPath}`;
|
|
118266
|
+
} else if (env2.ssh.password) {
|
|
118267
|
+
const password = this.resolveVariables(env2.ssh.password);
|
|
118268
|
+
const escaped = password.replace(/'/g, "'\\''");
|
|
118269
|
+
prefix = `sshpass -p '${escaped}' `;
|
|
118270
|
+
sshFlags += ` -o PreferredAuthentications=password -o PubkeyAuthentication=no`;
|
|
118271
|
+
}
|
|
118272
|
+
return { prefix, sshFlags };
|
|
118273
|
+
}
|
|
118274
|
+
sshExec(env2, remoteCmd, opts) {
|
|
118275
|
+
const { prefix, sshFlags } = this.buildSSHParts(env2);
|
|
118276
|
+
const target = this.buildSSHConfig(env2);
|
|
118277
|
+
const fullCmd = `${prefix}ssh ${sshFlags} ${target} "${remoteCmd.replace(/"/g, "\\\"")}"`;
|
|
118278
|
+
if (!opts?.silent)
|
|
118279
|
+
logger.debug(`SSH: ${remoteCmd}`);
|
|
118280
|
+
try {
|
|
118281
|
+
return execSync3(fullCmd, { encoding: "utf8", stdio: "pipe" }).trim();
|
|
118282
|
+
} catch (error) {
|
|
118283
|
+
const stderr = error.stderr?.toString() || "";
|
|
118284
|
+
const msg = error.message || String(error);
|
|
118285
|
+
if (msg.includes("sshpass: command not found") || msg.includes("sshpass: not found")) {
|
|
118286
|
+
throw new Error(`sshpass is required for password authentication.
|
|
118287
|
+
` + `Install: brew install sshpass (macOS) / apt install sshpass (Linux)
|
|
118288
|
+
` + `Or use SSH key authentication instead.`);
|
|
118289
|
+
}
|
|
118290
|
+
throw new Error(`SSH command failed: ${remoteCmd}
|
|
118291
|
+
${stderr || msg}`);
|
|
118292
|
+
}
|
|
118293
|
+
}
|
|
118294
|
+
scpUpload(env2, localPath, remotePath) {
|
|
118295
|
+
const { prefix, sshFlags } = this.buildSSHParts(env2);
|
|
118296
|
+
const target = this.buildSSHConfig(env2);
|
|
118297
|
+
const port = env2.ssh.port || 22;
|
|
118298
|
+
const scpFlags = sshFlags.replace(`-p ${port}`, "");
|
|
118299
|
+
const fullCmd = `${prefix}scp -P ${port} ${scpFlags} "${localPath}" "${target}:${remotePath}"`;
|
|
118300
|
+
logger.debug(`SCP upload: ${localPath} → ${remotePath}`);
|
|
118301
|
+
try {
|
|
118302
|
+
execSync3(fullCmd, { encoding: "utf8", stdio: "pipe" });
|
|
118303
|
+
} catch (error) {
|
|
118304
|
+
const stderr = error.stderr?.toString() || "";
|
|
118305
|
+
throw new Error(`SCP upload failed: ${stderr || error.message}`);
|
|
118306
|
+
}
|
|
118307
|
+
}
|
|
118308
|
+
async checkWordPressInstalled(env2) {
|
|
118309
|
+
const webroot = this.deriveWebroot(env2);
|
|
118310
|
+
logger.debug(`Checking WordPress at ${webroot}...`);
|
|
118311
|
+
try {
|
|
118312
|
+
const result2 = this.sshExec(env2, `test -f ${webroot}/wp-config.php && echo 'yes' || echo 'no'`, { silent: true });
|
|
118313
|
+
return { installed: result2 === "yes", webroot };
|
|
118314
|
+
} catch {
|
|
118315
|
+
return { installed: false, webroot };
|
|
118316
|
+
}
|
|
118317
|
+
}
|
|
118318
|
+
deriveWebroot(env2) {
|
|
118319
|
+
const themePath = env2.remotePaths.theme;
|
|
118320
|
+
const wpContentIdx = themePath.indexOf("/wp-content/");
|
|
118321
|
+
if (wpContentIdx !== -1) {
|
|
118322
|
+
return themePath.slice(0, wpContentIdx);
|
|
118323
|
+
}
|
|
118324
|
+
return path26.posix.resolve(themePath, "../../..");
|
|
118325
|
+
}
|
|
118326
|
+
askYesNo(question) {
|
|
118327
|
+
return new Promise((resolve7) => {
|
|
118328
|
+
const readline2 = __require("readline");
|
|
118329
|
+
const rl = readline2.createInterface({
|
|
118330
|
+
input: process.stdin,
|
|
118331
|
+
output: process.stdout
|
|
118332
|
+
});
|
|
118333
|
+
rl.question(`${question} (Y/n): `, (answer) => {
|
|
118334
|
+
rl.close();
|
|
118335
|
+
const n = answer.trim().toLowerCase();
|
|
118336
|
+
resolve7(n === "" || n === "y" || n === "yes");
|
|
118337
|
+
});
|
|
118338
|
+
});
|
|
118339
|
+
}
|
|
118340
|
+
askInput(question, defaultValue) {
|
|
118341
|
+
return new Promise((resolve7) => {
|
|
118342
|
+
const readline2 = __require("readline");
|
|
118343
|
+
const rl = readline2.createInterface({
|
|
118344
|
+
input: process.stdin,
|
|
118345
|
+
output: process.stdout
|
|
118346
|
+
});
|
|
118347
|
+
const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
|
|
118348
|
+
rl.question(prompt, (answer) => {
|
|
118349
|
+
rl.close();
|
|
118350
|
+
resolve7(answer.trim() || defaultValue || "");
|
|
118351
|
+
});
|
|
118352
|
+
});
|
|
118353
|
+
}
|
|
118354
|
+
async installWordPress(env2, webroot, sqlDump) {
|
|
118355
|
+
logger.progress("Installing WordPress on remote server...");
|
|
118356
|
+
logger.info("Checking for WP-CLI...");
|
|
118357
|
+
try {
|
|
118358
|
+
this.sshExec(env2, "wp --version", { silent: true });
|
|
118359
|
+
logger.debug("WP-CLI already available");
|
|
118360
|
+
} catch {
|
|
118361
|
+
logger.info("Installing WP-CLI...");
|
|
118362
|
+
this.sshExec(env2, `curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && chmod +x wp-cli.phar && mkdir -p ~/bin && mv wp-cli.phar ~/bin/wp`);
|
|
118363
|
+
logger.success("WP-CLI installed to ~/bin/wp");
|
|
118364
|
+
}
|
|
118365
|
+
const wp = "~/bin/wp";
|
|
118366
|
+
this.sshExec(env2, `mkdir -p ${webroot}`);
|
|
118367
|
+
logger.info("Downloading WordPress core...");
|
|
118368
|
+
this.sshExec(env2, `${wp} core download --path=${webroot} --locale=pl_PL --skip-content`);
|
|
118369
|
+
logger.success("WordPress core downloaded");
|
|
118370
|
+
if (env2.database) {
|
|
118371
|
+
const db = env2.database;
|
|
118372
|
+
const dbHost = this.resolveVariables(db.host);
|
|
118373
|
+
const dbPort = db.port || 3306;
|
|
118374
|
+
const dbName = this.resolveVariables(db.database);
|
|
118375
|
+
const dbUser = this.resolveVariables(db.username);
|
|
118376
|
+
const dbPass = this.resolveVariables(db.password);
|
|
118377
|
+
logger.info("Creating wp-config.php...");
|
|
118378
|
+
this.sshExec(env2, `${wp} config create --path=${webroot} --dbname=${dbName} --dbuser=${dbUser} --dbpass='${dbPass.replace(/'/g, "'\\''")}' --dbhost=${dbHost}:${dbPort} --skip-check`);
|
|
118379
|
+
logger.success("wp-config.php created");
|
|
118380
|
+
if (sqlDump && fs23.existsSync(sqlDump)) {
|
|
118381
|
+
logger.info(`Loading database dump from ${sqlDump}...`);
|
|
118382
|
+
await this.loadDump(env2, sqlDump);
|
|
118383
|
+
logger.success("Database imported from dump");
|
|
118384
|
+
} else {
|
|
118385
|
+
if (sqlDump) {
|
|
118386
|
+
logger.warn(`Dump file not found at ${sqlDump}, falling back to fresh install`);
|
|
118387
|
+
}
|
|
118388
|
+
logger.info("Installing WordPress database...");
|
|
118389
|
+
const siteUrl = env2.url || `https://${env2.ssh.host}`;
|
|
118390
|
+
const adminUser = await this.askInput("Admin username", "admin");
|
|
118391
|
+
const adminPassword = await this.askInput("Admin password");
|
|
118392
|
+
const adminEmail = await this.askInput("Admin email");
|
|
118393
|
+
const siteTitle = await this.askInput("Site title", env2.name);
|
|
118394
|
+
this.sshExec(env2, `${wp} core install --path=${webroot} --url='${siteUrl}' --title='${siteTitle.replace(/'/g, "'\\''")}' --admin_user='${adminUser}' --admin_password='${adminPassword.replace(/'/g, "'\\''")}' --admin_email='${adminEmail}' --skip-email`);
|
|
118395
|
+
logger.success("WordPress database installed");
|
|
118396
|
+
}
|
|
118397
|
+
} else {
|
|
118398
|
+
logger.warn(`No database config in environment — skipping wp-config.php creation.
|
|
118399
|
+
` + "You'll need to create it manually on the server.");
|
|
118400
|
+
}
|
|
118401
|
+
this.sshExec(env2, `mkdir -p ${webroot}/wp-content/themes ${webroot}/wp-content/plugins`);
|
|
118402
|
+
logger.success("WordPress installed on remote server");
|
|
118403
|
+
}
|
|
118404
|
+
async execute(options) {
|
|
118405
|
+
const config = this.configManager.getConfig();
|
|
118406
|
+
const environments = this.configManager.getEnvironments();
|
|
118407
|
+
const env2 = environments.find((e) => e.name === options.environment);
|
|
118408
|
+
if (!env2) {
|
|
118409
|
+
throw new Error(`Environment "${options.environment}" not found in config. ` + `Available: ${environments.map((e) => e.name).join(", ")}`);
|
|
118410
|
+
}
|
|
118411
|
+
console.log("");
|
|
118412
|
+
logger.progress(`Deploying to ${env2.name} (${env2.ssh.host})`);
|
|
118413
|
+
console.log("");
|
|
118414
|
+
try {
|
|
118415
|
+
const { installed, webroot } = await this.checkWordPressInstalled(env2);
|
|
118416
|
+
if (!installed) {
|
|
118417
|
+
logger.warn(`WordPress not found at ${webroot}`);
|
|
118418
|
+
const shouldInstall = await this.askYesNo("WordPress is not installed on the remote server. Install it now?");
|
|
118419
|
+
if (shouldInstall) {
|
|
118420
|
+
await this.installWordPress(env2, webroot, config.dev.sqlDump);
|
|
118421
|
+
} else {
|
|
118422
|
+
throw new Error(`WordPress is required on the remote server before deploying.
|
|
118423
|
+
` + "Install it manually or re-run deploy and choose to install.");
|
|
118424
|
+
}
|
|
118425
|
+
}
|
|
118426
|
+
const hasDump = options.dump !== undefined;
|
|
118427
|
+
let dumpPath;
|
|
118428
|
+
if (hasDump) {
|
|
118429
|
+
dumpPath = typeof options.dump === "string" ? options.dump : config.dev.sqlDump;
|
|
118430
|
+
if (!dumpPath) {
|
|
118431
|
+
throw new Error("No dump file specified. Use --dump <path> or set dev.sqlDump in config");
|
|
118432
|
+
}
|
|
118433
|
+
}
|
|
118434
|
+
const steps = [];
|
|
118435
|
+
if (!options.skipBuild)
|
|
118436
|
+
steps.push("build");
|
|
118437
|
+
steps.push("sync");
|
|
118438
|
+
if (hasDump) {
|
|
118439
|
+
steps.push("database");
|
|
118440
|
+
} else if (env2.database) {
|
|
118441
|
+
steps.push("content");
|
|
118442
|
+
}
|
|
118443
|
+
let step = 0;
|
|
118444
|
+
const logStep = (msg) => {
|
|
118445
|
+
step++;
|
|
118446
|
+
logger.step(step, steps.length, msg);
|
|
118447
|
+
};
|
|
118448
|
+
if (!options.skipBuild) {
|
|
118449
|
+
logStep("Building blocks and assets...");
|
|
118450
|
+
const buildCommand = new BuildCommand;
|
|
118451
|
+
await buildCommand.execute({ noCache: false });
|
|
118452
|
+
logger.success("Build completed");
|
|
118453
|
+
}
|
|
118454
|
+
logStep("Syncing files to remote server...");
|
|
118455
|
+
await this.syncFiles(env2, config.paths);
|
|
118456
|
+
if (hasDump && dumpPath) {
|
|
118457
|
+
logStep("Importing database...");
|
|
118458
|
+
await this.loadDump(env2, dumpPath);
|
|
118459
|
+
} else if (env2.database) {
|
|
118460
|
+
logStep("Pushing content to remote database...");
|
|
118461
|
+
await this.pushContent(env2);
|
|
118462
|
+
}
|
|
118463
|
+
console.log("");
|
|
118464
|
+
logger.success(`\uD83D\uDE80 Deployment to ${env2.name} completed!`);
|
|
118465
|
+
console.log("");
|
|
118466
|
+
} catch (error) {
|
|
118467
|
+
console.log("");
|
|
118468
|
+
logger.error("Deployment failed:", error);
|
|
118469
|
+
process.exit(1);
|
|
118470
|
+
}
|
|
118471
|
+
}
|
|
118472
|
+
async syncFiles(env2, paths) {
|
|
118473
|
+
const target = this.buildSSHConfig(env2);
|
|
118474
|
+
try {
|
|
118475
|
+
this.sshExec(env2, `mkdir -p ${env2.remotePaths.theme} ${env2.remotePaths.plugins}`, { silent: true });
|
|
118476
|
+
} catch {}
|
|
118477
|
+
logger.substep("theme");
|
|
118478
|
+
this.rsync(paths.theme + "/", `${target}:${env2.remotePaths.theme}/`, env2);
|
|
118479
|
+
logger.substep("plugins");
|
|
118480
|
+
this.rsync(paths.plugins + "/", `${target}:${env2.remotePaths.plugins}/`, env2);
|
|
118481
|
+
logger.success("Files synced");
|
|
118482
|
+
}
|
|
118483
|
+
rsync(source, destination, env2) {
|
|
118484
|
+
const { prefix, sshFlags } = this.buildSSHParts(env2);
|
|
118485
|
+
const rsyncCmd = `${prefix}rsync -avz --delete ` + `--exclude 'node_modules' --exclude '.git' --exclude '.DS_Store' --exclude '*.log' ` + `-e "ssh ${sshFlags}" ` + `"${source}" "${destination}"`;
|
|
118486
|
+
try {
|
|
118487
|
+
execSync3(rsyncCmd, { encoding: "utf8", stdio: "pipe" });
|
|
118488
|
+
} catch (error) {
|
|
118489
|
+
const msg = error.message || String(error);
|
|
118490
|
+
const stderr = error.stderr?.toString() || "";
|
|
118491
|
+
if (msg.includes("Disk quota exceeded") || stderr.includes("Disk quota exceeded")) {
|
|
118492
|
+
throw new Error("Server disk quota exceeded. Free up space and retry.");
|
|
118493
|
+
}
|
|
118494
|
+
if (msg.includes("Permission denied") || stderr.includes("Permission denied")) {
|
|
118495
|
+
throw new Error(`Permission denied writing to ${destination}.`);
|
|
118496
|
+
}
|
|
118497
|
+
if (msg.includes("Connection refused") || msg.includes("Connection timed out")) {
|
|
118498
|
+
throw new Error(`Cannot connect to ${env2.ssh.host}.`);
|
|
118499
|
+
}
|
|
118500
|
+
if (msg.includes("sshpass: command not found") || msg.includes("sshpass: not found")) {
|
|
118501
|
+
throw new Error("sshpass is required for password auth. Install: brew install sshpass");
|
|
118502
|
+
}
|
|
118503
|
+
if (msg.includes("child exited with status 11") || msg.includes("unexpected end of file")) {
|
|
118504
|
+
throw new Error("Connection interrupted. Check disk quota and try again.");
|
|
118505
|
+
}
|
|
118506
|
+
throw new Error(`Rsync failed:
|
|
118507
|
+
${stderr || msg}`);
|
|
118508
|
+
}
|
|
118509
|
+
}
|
|
118510
|
+
async pushContent(env2) {
|
|
118511
|
+
if (!env2.database) {
|
|
118512
|
+
throw new Error(`Database configuration not found for environment ${env2.name}`);
|
|
118513
|
+
}
|
|
118514
|
+
const tunnel = new SSHTunnel;
|
|
118515
|
+
const closeTunnel = await tunnel.connect(env2.name);
|
|
118516
|
+
try {
|
|
118517
|
+
logger.substep("Pushing MDX content to remote database");
|
|
118518
|
+
const pushCommand = new PushCommand;
|
|
118519
|
+
const result2 = await pushCommand.execute({ force: true });
|
|
118520
|
+
if (!result2.success) {
|
|
118521
|
+
const errors = result2.errors.map((e) => e.error).join(", ");
|
|
118522
|
+
throw new Error(`Failed to push content: ${errors}`);
|
|
118523
|
+
}
|
|
118524
|
+
logger.success(`Content pushed (${result2.filesProcessed} files)`);
|
|
118525
|
+
} finally {
|
|
118526
|
+
closeTunnel();
|
|
118527
|
+
}
|
|
118528
|
+
}
|
|
118529
|
+
async loadDump(env2, dumpPath) {
|
|
118530
|
+
if (!env2.database) {
|
|
118531
|
+
throw new Error(`Database configuration not found for environment ${env2.name}`);
|
|
118532
|
+
}
|
|
118533
|
+
if (!fs23.existsSync(dumpPath)) {
|
|
118534
|
+
throw new Error(`Dump file not found: ${dumpPath}`);
|
|
118535
|
+
}
|
|
118536
|
+
const db = env2.database;
|
|
118537
|
+
const dbHost = this.resolveVariables(db.host);
|
|
118538
|
+
const dbPort = db.port || 3306;
|
|
118539
|
+
const dbName = this.resolveVariables(db.database);
|
|
118540
|
+
const dbUser = this.resolveVariables(db.username);
|
|
118541
|
+
const dbPass = this.resolveVariables(db.password);
|
|
118542
|
+
logger.substep(`Uploading ${dumpPath}`);
|
|
118543
|
+
const remoteDumpPath = `/tmp/wordpress-flow-dump-${Date.now()}.sql`;
|
|
118544
|
+
this.scpUpload(env2, dumpPath, remoteDumpPath);
|
|
118545
|
+
logger.substep("Importing into database");
|
|
118546
|
+
const escapedPass = dbPass.replace(/'/g, "'\\''");
|
|
118547
|
+
const mysqlCmd = `grep -v '^mysqldump:' ${remoteDumpPath} | mysql -h ${dbHost} -P ${dbPort} -u ${dbUser} -p'${escapedPass}' ${dbName} && rm ${remoteDumpPath}`;
|
|
118548
|
+
this.sshExec(env2, mysqlCmd);
|
|
118549
|
+
const webroot = this.deriveWebroot(env2);
|
|
118550
|
+
const wp = "~/bin/wp";
|
|
118551
|
+
if (env2.url) {
|
|
118552
|
+
const config = this.configManager.getConfig();
|
|
118553
|
+
const devPort = config.dev.port || 8888;
|
|
118554
|
+
const oldUrl = `http://localhost:${devPort}`;
|
|
118555
|
+
const newUrl = env2.url.replace(/\/$/, "");
|
|
118556
|
+
logger.substep(`Rewriting URLs: ${oldUrl} → ${newUrl}`);
|
|
118557
|
+
this.sshExec(env2, `${wp} search-replace '${oldUrl}' '${newUrl}' --path=${webroot} --all-tables --skip-columns=guid`);
|
|
118558
|
+
} else {
|
|
118559
|
+
logger.warn("No 'url' in environment config — skipping URL rewrite");
|
|
118560
|
+
}
|
|
118561
|
+
const themeSlug = path26.posix.basename(env2.remotePaths.theme);
|
|
118562
|
+
logger.substep(`Activating theme: ${themeSlug}`);
|
|
118563
|
+
try {
|
|
118564
|
+
this.sshExec(env2, `${wp} theme activate ${themeSlug} --path=${webroot}`);
|
|
118565
|
+
} catch {
|
|
118566
|
+
logger.warn(`Could not activate theme "${themeSlug}"`);
|
|
118567
|
+
}
|
|
118568
|
+
logger.success("Database imported");
|
|
118569
|
+
}
|
|
118570
|
+
}
|
|
118571
|
+
|
|
117977
118572
|
// src/index.ts
|
|
117978
118573
|
var program2 = new Command;
|
|
117979
118574
|
program2.name("wordpress-flow").description("Build WordPress sites with React, TypeScript, and MDX - A content-first development framework").version(package_default.version);
|
|
@@ -117994,7 +118589,7 @@ program2.command("setup").description("Run the configuration wizard").action(asy
|
|
|
117994
118589
|
process.exit(1);
|
|
117995
118590
|
}
|
|
117996
118591
|
});
|
|
117997
|
-
program2.command("build").description("Build WordPress blocks for Gutenberg").option("--blocks-dir <dir>", "Source directory for blocks").option("--output-dir <dir>", "Output directory for built blocks").option("--webpack-config <path>", "Path to webpack config file").option("--watch", "Watch mode for development").option("-b, --blocks <names...>", "Specific block names to build").option("-c, --concurrency <number>", "Number of parallel workers (default: CPU cores - 1)").action(async (options) => {
|
|
118592
|
+
program2.command("build").description("Build WordPress blocks for Gutenberg").option("--blocks-dir <dir>", "Source directory for blocks").option("--output-dir <dir>", "Output directory for built blocks").option("--webpack-config <path>", "Path to webpack config file").option("--watch", "Watch mode for development").option("-b, --blocks <names...>", "Specific block names to build").option("-c, --concurrency <number>", "Number of parallel workers (default: CPU cores - 1)").option("--no-cache", "Disable cache and rebuild all blocks").action(async (options) => {
|
|
117998
118593
|
try {
|
|
117999
118594
|
await ensureConfiguration();
|
|
118000
118595
|
const command = new BuildCommand;
|
|
@@ -118004,7 +118599,8 @@ program2.command("build").description("Build WordPress blocks for Gutenberg").op
|
|
|
118004
118599
|
webpackConfig: options.webpackConfig,
|
|
118005
118600
|
watch: options.watch || false,
|
|
118006
118601
|
blocks: options.blocks,
|
|
118007
|
-
concurrency: options.concurrency ? parseInt(options.concurrency, 10) : undefined
|
|
118602
|
+
concurrency: options.concurrency ? parseInt(options.concurrency, 10) : undefined,
|
|
118603
|
+
noCache: !options.cache
|
|
118008
118604
|
});
|
|
118009
118605
|
logger.info("Build command completed successfully");
|
|
118010
118606
|
} catch (error) {
|
|
@@ -118015,9 +118611,14 @@ program2.command("build").description("Build WordPress blocks for Gutenberg").op
|
|
|
118015
118611
|
program2.command("pull").description("Pull content from WordPress and convert to MDX").option("-f, --force", "Force overwrite existing files").option("--post-types <types...>", "Specific post types to pull").option("--post-ids <ids...>", "Specific post IDs to pull").option("--status <statuses...>", "Post statuses to include", [
|
|
118016
118612
|
"publish",
|
|
118017
118613
|
"draft"
|
|
118018
|
-
]).option("--output-dir <dir>", "Output directory for MDX files").action(async (options) => {
|
|
118614
|
+
]).option("--output-dir <dir>", "Output directory for MDX files").option("--env <environment>", "Pull from a remote environment via SSH tunnel").action(async (options) => {
|
|
118615
|
+
let closeTunnel;
|
|
118019
118616
|
try {
|
|
118020
118617
|
await ensureConfiguration();
|
|
118618
|
+
if (options.env) {
|
|
118619
|
+
const tunnel = new SSHTunnel;
|
|
118620
|
+
closeTunnel = await tunnel.connect(options.env);
|
|
118621
|
+
}
|
|
118021
118622
|
const command = new PullCommand;
|
|
118022
118623
|
await command.execute({
|
|
118023
118624
|
force: options.force || false,
|
|
@@ -118030,17 +118631,26 @@ program2.command("pull").description("Pull content from WordPress and convert to
|
|
|
118030
118631
|
} catch (error) {
|
|
118031
118632
|
logger.error("Pull command failed:", error);
|
|
118032
118633
|
process.exit(1);
|
|
118634
|
+
} finally {
|
|
118635
|
+
closeTunnel?.();
|
|
118033
118636
|
}
|
|
118034
118637
|
});
|
|
118035
|
-
program2.command("push").description("Convert MDX files and push to WordPress").option("-f, --files <files...>", "Specific files to push").option("--force", "Force push, overriding conflicts").option("--dry-run", "Preview changes without pushing").action(async (options) => {
|
|
118638
|
+
program2.command("push").description("Convert MDX files and push to WordPress").option("-f, --files <files...>", "Specific files to push").option("--force", "Force push, overriding conflicts").option("--dry-run", "Preview changes without pushing").option("--env <environment>", "Push to a remote environment via SSH tunnel").action(async (options) => {
|
|
118639
|
+
let closeTunnel;
|
|
118036
118640
|
try {
|
|
118037
118641
|
await ensureConfiguration();
|
|
118642
|
+
if (options.env) {
|
|
118643
|
+
const tunnel = new SSHTunnel;
|
|
118644
|
+
closeTunnel = await tunnel.connect(options.env);
|
|
118645
|
+
}
|
|
118038
118646
|
const command = new PushCommand;
|
|
118039
118647
|
await command.execute(options);
|
|
118040
118648
|
logger.info("Push command completed successfully");
|
|
118041
118649
|
} catch (error) {
|
|
118042
118650
|
logger.error("Push command failed:", error);
|
|
118043
118651
|
process.exit(1);
|
|
118652
|
+
} finally {
|
|
118653
|
+
closeTunnel?.();
|
|
118044
118654
|
}
|
|
118045
118655
|
});
|
|
118046
118656
|
program2.command("build-templates").description("Build WordPress theme templates from MDX files").option("--templates-dir <dir>", "Source directory for template MDX files").option("--output-dir <dir>", "Output directory for HTML templates").action(async (options) => {
|
|
@@ -118086,6 +118696,20 @@ program2.command("destroy").description("Remove Docker containers and volumes fo
|
|
|
118086
118696
|
process.exit(1);
|
|
118087
118697
|
}
|
|
118088
118698
|
});
|
|
118699
|
+
program2.command("deploy <environment>").description("Deploy to a remote environment (staging, production, etc.)").option("--skip-build", "Skip the build step").option("--dump [path]", "Load SQL dump instead of pushing MDX (uses config sqlDump if no path provided)").action(async (environment, options) => {
|
|
118700
|
+
try {
|
|
118701
|
+
await ensureConfiguration();
|
|
118702
|
+
const command = new DeployCommand;
|
|
118703
|
+
await command.execute({
|
|
118704
|
+
environment,
|
|
118705
|
+
skipBuild: options.skipBuild || false,
|
|
118706
|
+
dump: options.dump
|
|
118707
|
+
});
|
|
118708
|
+
} catch (error) {
|
|
118709
|
+
logger.error("Deploy command failed:", error);
|
|
118710
|
+
process.exit(1);
|
|
118711
|
+
}
|
|
118712
|
+
});
|
|
118089
118713
|
var databaseCmd = program2.command("database").description("Database management commands");
|
|
118090
118714
|
databaseCmd.command("import").description("Import SQL dump into Docker database").option("-f, --file <path>", "Path to SQL file (defaults to config sqlDump)").action(async (options) => {
|
|
118091
118715
|
try {
|