@bootmap/wpep-cli 1.0.7 → 1.0.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bootmap/wpep-cli",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "CLI tool for deploying Next.js static exports to WordPress via WP Elementor Publisher",
5
5
  "main": "bin/wpep.js",
6
6
  "type": "module",
@@ -47,49 +47,80 @@ export default async function deployCommand(options) {
47
47
 
48
48
  console.log(chalk.blue.bold(`\nDeploying to ${url} ...\n`));
49
49
 
50
+ // ── Auto-detect framework and patch config before building ──────────────
51
+ let nextConfigPath = null;
52
+ let nextConfigBackup = null;
53
+
54
+ const configCandidates = ['next.config.ts', 'next.config.mjs', 'next.config.js'];
55
+ for (const c of configCandidates) {
56
+ const p = path.join(process.cwd(), c);
57
+ if (fs.existsSync(p)) {
58
+ nextConfigPath = p;
59
+ break;
60
+ }
61
+ }
62
+
63
+ // If it's a Next.js project, inject output: "export" automatically
64
+ if (nextConfigPath) {
65
+ const originalContent = fs.readFileSync(nextConfigPath, 'utf8');
66
+
67
+ if (!originalContent.includes('output:') && !originalContent.includes("output :")) {
68
+ nextConfigBackup = originalContent;
69
+ console.log(chalk.yellow('⚡ Detected Next.js project without static export configured.'));
70
+ console.log(chalk.yellow(' Auto-injecting { output: "export" } for this deployment...\n'));
71
+
72
+ // Inject output: "export" right after the opening brace of the config object
73
+ const patched = originalContent.replace(
74
+ /(\bconst\s+\w+\s*(?::\s*\w+)?\s*=\s*\{)/,
75
+ '$1\n output: "export",'
76
+ );
77
+ fs.writeFileSync(nextConfigPath, patched, 'utf8');
78
+ }
79
+ }
80
+
50
81
  if (options.build !== false) {
51
82
  console.log(chalk.gray('Running npm run build...'));
52
83
  try {
53
84
  execSync('npm run build', { stdio: 'inherit' });
54
85
  } catch (e) {
86
+ // Restore original config before throwing
87
+ if (nextConfigBackup && nextConfigPath) {
88
+ fs.writeFileSync(nextConfigPath, nextConfigBackup, 'utf8');
89
+ console.log(chalk.gray('Restored original next.config after failed build.'));
90
+ }
55
91
  throw new Error('Build failed. See output above.');
56
92
  }
57
93
  }
58
94
 
95
+ // Restore the original next.config so git stays clean
96
+ if (nextConfigBackup && nextConfigPath) {
97
+ fs.writeFileSync(nextConfigPath, nextConfigBackup, 'utf8');
98
+ console.log(chalk.gray('Restored original next.config after build.\n'));
99
+ }
100
+
59
101
  let outputDir = options.dir || 'wpep-build';
60
102
  const targetDir = path.join(process.cwd(), 'wpep-build');
61
103
 
62
- if (!fs.existsSync(targetDir)) {
63
- // Try to auto-detect and move/copy to wpep-build
64
- const possibleDirs = ['out', 'dist', 'build'];
65
- let foundDir = null;
66
- for (const d of possibleDirs) {
67
- if (fs.existsSync(path.join(process.cwd(), d))) {
68
- foundDir = d;
69
- break;
70
- }
71
- }
104
+ // Always clear previous wpep-build so we deploy fresh output
105
+ if (fs.existsSync(targetDir)) {
106
+ fs.rmSync(targetDir, { recursive: true, force: true });
107
+ }
72
108
 
73
- if (foundDir) {
74
- console.log(chalk.gray(`Auto-detected build in "${foundDir}". Copying to "wpep-build"...`));
75
- fs.cpSync(path.join(process.cwd(), foundDir), targetDir, { recursive: true });
76
- } else if (fs.existsSync(path.join(process.cwd(), '.next'))) {
77
- console.log(chalk.red.bold('\n⚠️ ERROR: No static export directory found, but a ".next" directory exists.'));
78
- console.log(chalk.red('Next.js is currently building a Node.js server app, which cannot be hosted on WordPress.\n'));
79
-
80
- console.log(chalk.yellow.bold('--- How to fix your Next.js App ---'));
81
- console.log(chalk.yellow('To actually make your deployment work, you MUST configure Next.js to export static HTML.'));
82
- console.log(chalk.yellow('Open your next.config.ts (or .js) and add `output: "export"` inside your config object:\n'));
83
-
84
- console.log(chalk.cyan('const nextConfig: NextConfig = {'));
85
- console.log(chalk.green(' output: "export",'));
86
- console.log(chalk.cyan(' // ... rest of your config'));
87
- console.log(chalk.cyan('};\n'));
88
-
89
- process.exit(1);
109
+ // Auto-detect build output and copy to wpep-build
110
+ const possibleDirs = ['out', 'dist', 'build'];
111
+ let foundDir = null;
112
+ for (const d of possibleDirs) {
113
+ if (fs.existsSync(path.join(process.cwd(), d))) {
114
+ foundDir = d;
115
+ break;
90
116
  }
91
117
  }
92
118
 
119
+ if (foundDir) {
120
+ console.log(chalk.gray(`Auto-detected build in "${foundDir}". Copying to "wpep-build"...`));
121
+ fs.cpSync(path.join(process.cwd(), foundDir), targetDir, { recursive: true });
122
+ }
123
+
93
124
  outputDir = 'wpep-build';
94
125
  const outDir = path.join(process.cwd(), outputDir);
95
126
 
@@ -110,6 +141,7 @@ export default async function deployCommand(options) {
110
141
  spinner.text = `Creating deployment session for ${allFiles.length} files...`;
111
142
 
112
143
  let deployId;
144
+ let remoteManifest = null;
113
145
  try {
114
146
  const createRes = await fetch(`${url}/wp-json/wpep/v1/deployments/create`, {
115
147
  method: 'POST',
@@ -126,6 +158,7 @@ export default async function deployCommand(options) {
126
158
  throw new Error(`Invalid response from server: ${JSON.stringify(createData)}`);
127
159
  }
128
160
  deployId = createData.deployment_id;
161
+ remoteManifest = createData.remote_manifest;
129
162
  } catch (e) {
130
163
  spinner.fail('Failed to create deployment session.');
131
164
  throw e;
@@ -133,9 +166,27 @@ export default async function deployCommand(options) {
133
166
 
134
167
  spinner.succeed(`Created deployment session: ${chalk.bold(deployId)}`);
135
168
 
136
- console.log(chalk.gray('\nUploading files...'));
169
+ // Build a set of unchanged files by comparing hashes with the remote manifest
170
+ const unchangedFiles = new Set();
171
+ if (remoteManifest && remoteManifest.files) {
172
+ for (const [filePath, localHash] of Object.entries(filesList)) {
173
+ const remoteMeta = remoteManifest.files[filePath];
174
+ if (remoteMeta && remoteMeta === localHash) {
175
+ unchangedFiles.add(filePath);
176
+ }
177
+ }
178
+ }
179
+
180
+ const changedFiles = allFiles.filter(f => !unchangedFiles.has(path.relative(outDir, f)));
181
+ const skippedCount = allFiles.length - changedFiles.length;
182
+
183
+ if (skippedCount > 0) {
184
+ console.log(chalk.green(`\n⚡ ${skippedCount} unchanged files will be copied server-side (not re-uploaded).`));
185
+ }
186
+ console.log(chalk.gray(`Uploading ${changedFiles.length} changed files...\n`));
187
+
137
188
  let uploaded = 0;
138
- for (const file of allFiles) {
189
+ for (const file of changedFiles) {
139
190
  const relativePath = path.relative(outDir, file);
140
191
  const data = fs.readFileSync(file);
141
192
  const base64Data = data.toString('base64');
@@ -175,5 +226,6 @@ export default async function deployCommand(options) {
175
226
  throw e;
176
227
  }
177
228
 
178
- console.log(chalk.green.bold(`\n🎉 Success! Your site is live at ${url}\n`));
229
+ console.log(chalk.green.bold(`\n🎉 Success! Your site is live at ${url}`));
230
+ console.log(chalk.gray(` ${uploaded} files uploaded, ${skippedCount} files copied from previous deployment.\n`));
179
231
  }