@wordpress-flow/cli 1.2.12 → 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.
Files changed (2) hide show
  1. package/dist/index.js +595 -22
  2. 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
@@ -114937,10 +114950,6 @@ class BlockHashManager {
114937
114950
  logger.debug(`${blockName}: No stored hash, needs rebuild`);
114938
114951
  return true;
114939
114952
  }
114940
- if (storedHash.cliVersion !== this.cliVersion) {
114941
- logger.debug(`${blockName}: CLI version changed, needs rebuild`);
114942
- return true;
114943
- }
114944
114953
  const currentHash = this.calculateSourceHash(sourcePath);
114945
114954
  if (!currentHash) {
114946
114955
  logger.debug(`${blockName}: Source file missing, needs rebuild`);
@@ -116186,7 +116195,7 @@ class DockerEnvManager {
116186
116195
  // package.json
116187
116196
  var package_default = {
116188
116197
  name: "@wordpress-flow/cli",
116189
- version: "1.2.12",
116198
+ version: "1.2.13",
116190
116199
  type: "module",
116191
116200
  description: "TypeScript-based WordPress block creation system",
116192
116201
  main: "dist/index.js",
@@ -117159,6 +117168,7 @@ class DevCommand {
117159
117168
 
117160
117169
  // src/commands/build-command.ts
117161
117170
  import * as path22 from "path";
117171
+ import * as fs19 from "fs";
117162
117172
  import { fileURLToPath as fileURLToPath5 } from "url";
117163
117173
 
117164
117174
  // src/build/block-builder.ts
@@ -117751,6 +117761,12 @@ class WorkerPool {
117751
117761
  // src/commands/build-command.ts
117752
117762
  var __filename3 = fileURLToPath5(import.meta.url);
117753
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 {}
117754
117770
 
117755
117771
  class BuildCommand {
117756
117772
  configManager;
@@ -117759,6 +117775,7 @@ class BuildCommand {
117759
117775
  typeGenerator;
117760
117776
  phpGenerator;
117761
117777
  blockScripts;
117778
+ hashManager = null;
117762
117779
  constructor() {
117763
117780
  this.configManager = ConfigManager.getInstance();
117764
117781
  this.blockScanner = new BlockScanner;
@@ -117809,6 +117826,27 @@ class BuildCommand {
117809
117826
  } else {
117810
117827
  logger.info(`Found ${blocks.length} block(s): ${blocks.map((b) => b.name).join(", ")}`);
117811
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(", ")}`);
117812
117850
  const scriptsOutputDir = this.configManager.resolvePath(config.paths.scriptsDist);
117813
117851
  const workerPool = new WorkerPool({
117814
117852
  concurrency: options.concurrency,
@@ -117817,9 +117855,14 @@ class BuildCommand {
117817
117855
  webpackConfigPath: webpackConfig,
117818
117856
  scriptsPath
117819
117857
  });
117820
- const results = await workerPool.buildAll(blocks, (completed, total, blockName, success, error) => {
117858
+ const results = await workerPool.buildAll(blocksToRebuild, (completed, total, blockName, success, error) => {
117821
117859
  if (success) {
117822
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
+ }
117823
117866
  } else {
117824
117867
  logger.error(`❌ Failed: ${blockName} [${completed}/${total}]: ${error || "unknown error"}`);
117825
117868
  }
@@ -117848,11 +117891,17 @@ Failed to build ${failures.length} block(s):`);
117848
117891
  }
117849
117892
  await this.generateTypeDefinitions(outputDir);
117850
117893
  const successCount = results.filter((r) => r.success).length;
117894
+ const totalBlocks = blocks.length;
117895
+ const cachedCount = totalBlocks - blocksToRebuild.length;
117851
117896
  if (failures.length > 0) {
117852
- logger.warn(`Build completed with errors. Built ${successCount}/${blocks.length} block(s).`);
117897
+ logger.warn(`Build completed with errors. Built ${successCount}/${blocksToRebuild.length} block(s)${cachedCount > 0 ? `, ${cachedCount} cached` : ""}.`);
117853
117898
  process.exit(1);
117854
117899
  } else {
117855
- logger.success(`Build completed successfully! Built ${successCount} block(s).`);
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
+ }
117856
117905
  }
117857
117906
  } catch (error) {
117858
117907
  logger.error("Build operation failed:");
@@ -117881,7 +117930,7 @@ Failed to build ${failures.length} block(s):`);
117881
117930
  }
117882
117931
 
117883
117932
  // src/commands/build-templates-command.ts
117884
- import * as fs19 from "fs";
117933
+ import * as fs20 from "fs";
117885
117934
  import * as path23 from "path";
117886
117935
  class BuildTemplatesCommand {
117887
117936
  configManager;
@@ -117893,11 +117942,11 @@ class BuildTemplatesCommand {
117893
117942
  const config = this.configManager.getConfig();
117894
117943
  const templatesDir = options.templatesDir || this.configManager.resolvePath(config.paths.templates);
117895
117944
  const outputDir = options.outputDir || this.configManager.resolvePath(config.paths.templatesOutput);
117896
- if (!fs19.existsSync(templatesDir)) {
117945
+ if (!fs20.existsSync(templatesDir)) {
117897
117946
  logger.error(`Templates directory not found: ${templatesDir}`);
117898
117947
  return;
117899
117948
  }
117900
- fs19.mkdirSync(outputDir, { recursive: true });
117949
+ fs20.mkdirSync(outputDir, { recursive: true });
117901
117950
  const blocksOutputDir = this.configManager.resolvePath(config.paths.blocksDist);
117902
117951
  const blockRegistry = new BlockRegistry(blocksOutputDir);
117903
117952
  await blockRegistry.loadBuiltBlocks();
@@ -117935,17 +117984,17 @@ class BuildTemplatesCommand {
117935
117984
  }
117936
117985
  }
117937
117986
  async buildTemplate(templatePath, templateName, outputDir, renderer) {
117938
- const mdxContent = fs19.readFileSync(templatePath, "utf8");
117987
+ const mdxContent = fs20.readFileSync(templatePath, "utf8");
117939
117988
  const cleanContent = this.removeFrontmatter(mdxContent);
117940
117989
  const html5 = await renderer.renderMDXToHTML(cleanContent);
117941
117990
  const cleanHTML = renderer.postProcessHTML(html5);
117942
117991
  const outputPath = path23.join(outputDir, `${templateName}.html`);
117943
- fs19.writeFileSync(outputPath, cleanHTML, "utf8");
117992
+ fs20.writeFileSync(outputPath, cleanHTML, "utf8");
117944
117993
  logger.info(`✓ Generated: ${path23.relative(process.cwd(), outputPath)}`);
117945
117994
  }
117946
117995
  findMDXFiles(dir) {
117947
117996
  const files = [];
117948
- const entries = fs19.readdirSync(dir, { withFileTypes: true });
117997
+ const entries = fs20.readdirSync(dir, { withFileTypes: true });
117949
117998
  for (const entry of entries) {
117950
117999
  const fullPath = path23.join(dir, entry.name);
117951
118000
  if (entry.isDirectory()) {
@@ -117963,7 +118012,7 @@ class BuildTemplatesCommand {
117963
118012
  }
117964
118013
 
117965
118014
  // src/commands/env-command.ts
117966
- import * as fs20 from "fs";
118015
+ import * as fs21 from "fs";
117967
118016
  import * as path24 from "path";
117968
118017
  class EnvCommand {
117969
118018
  configManager;
@@ -117972,11 +118021,11 @@ class EnvCommand {
117972
118021
  }
117973
118022
  getDockerEnvManager() {
117974
118023
  const config = this.configManager.getConfig();
117975
- const packageJsonPath = path24.join(this.configManager.getConfigDir(), "package.json");
118024
+ const packageJsonPath2 = path24.join(this.configManager.getConfigDir(), "package.json");
117976
118025
  let projectName = "wordpress-project";
117977
- if (fs20.existsSync(packageJsonPath)) {
118026
+ if (fs21.existsSync(packageJsonPath2)) {
117978
118027
  try {
117979
- const pkg = JSON.parse(fs20.readFileSync(packageJsonPath, "utf8"));
118028
+ const pkg = JSON.parse(fs21.readFileSync(packageJsonPath2, "utf8"));
117980
118029
  projectName = pkg.name || projectName;
117981
118030
  } catch {}
117982
118031
  }
@@ -118025,6 +118074,501 @@ class EnvCommand {
118025
118074
  }
118026
118075
  }
118027
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
+
118028
118572
  // src/index.ts
118029
118573
  var program2 = new Command;
118030
118574
  program2.name("wordpress-flow").description("Build WordPress sites with React, TypeScript, and MDX - A content-first development framework").version(package_default.version);
@@ -118045,7 +118589,7 @@ program2.command("setup").description("Run the configuration wizard").action(asy
118045
118589
  process.exit(1);
118046
118590
  }
118047
118591
  });
118048
- 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) => {
118049
118593
  try {
118050
118594
  await ensureConfiguration();
118051
118595
  const command = new BuildCommand;
@@ -118055,7 +118599,8 @@ program2.command("build").description("Build WordPress blocks for Gutenberg").op
118055
118599
  webpackConfig: options.webpackConfig,
118056
118600
  watch: options.watch || false,
118057
118601
  blocks: options.blocks,
118058
- concurrency: options.concurrency ? parseInt(options.concurrency, 10) : undefined
118602
+ concurrency: options.concurrency ? parseInt(options.concurrency, 10) : undefined,
118603
+ noCache: !options.cache
118059
118604
  });
118060
118605
  logger.info("Build command completed successfully");
118061
118606
  } catch (error) {
@@ -118066,9 +118611,14 @@ program2.command("build").description("Build WordPress blocks for Gutenberg").op
118066
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", [
118067
118612
  "publish",
118068
118613
  "draft"
118069
- ]).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;
118070
118616
  try {
118071
118617
  await ensureConfiguration();
118618
+ if (options.env) {
118619
+ const tunnel = new SSHTunnel;
118620
+ closeTunnel = await tunnel.connect(options.env);
118621
+ }
118072
118622
  const command = new PullCommand;
118073
118623
  await command.execute({
118074
118624
  force: options.force || false,
@@ -118081,17 +118631,26 @@ program2.command("pull").description("Pull content from WordPress and convert to
118081
118631
  } catch (error) {
118082
118632
  logger.error("Pull command failed:", error);
118083
118633
  process.exit(1);
118634
+ } finally {
118635
+ closeTunnel?.();
118084
118636
  }
118085
118637
  });
118086
- 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;
118087
118640
  try {
118088
118641
  await ensureConfiguration();
118642
+ if (options.env) {
118643
+ const tunnel = new SSHTunnel;
118644
+ closeTunnel = await tunnel.connect(options.env);
118645
+ }
118089
118646
  const command = new PushCommand;
118090
118647
  await command.execute(options);
118091
118648
  logger.info("Push command completed successfully");
118092
118649
  } catch (error) {
118093
118650
  logger.error("Push command failed:", error);
118094
118651
  process.exit(1);
118652
+ } finally {
118653
+ closeTunnel?.();
118095
118654
  }
118096
118655
  });
118097
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) => {
@@ -118137,6 +118696,20 @@ program2.command("destroy").description("Remove Docker containers and volumes fo
118137
118696
  process.exit(1);
118138
118697
  }
118139
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
+ });
118140
118713
  var databaseCmd = program2.command("database").description("Database management commands");
118141
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) => {
118142
118715
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress-flow/cli",
3
- "version": "1.2.12",
3
+ "version": "1.2.13",
4
4
  "type": "module",
5
5
  "description": "TypeScript-based WordPress block creation system",
6
6
  "main": "dist/index.js",