maiass 5.7.31
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/LICENSE +26 -0
- package/README.md +347 -0
- package/build.js +127 -0
- package/lib/account-info.js +476 -0
- package/lib/colors.js +49 -0
- package/lib/commit.js +885 -0
- package/lib/config-command.js +310 -0
- package/lib/config-manager.js +344 -0
- package/lib/config.js +150 -0
- package/lib/devlog.js +182 -0
- package/lib/env-display.js +162 -0
- package/lib/git-info.js +509 -0
- package/lib/header.js +152 -0
- package/lib/input-utils.js +116 -0
- package/lib/logger.js +285 -0
- package/lib/machine-fingerprint.js +229 -0
- package/lib/maiass-command.js +79 -0
- package/lib/maiass-pipeline.js +1204 -0
- package/lib/maiass-variables.js +152 -0
- package/lib/secure-storage.js +256 -0
- package/lib/symbols.js +200 -0
- package/lib/token-validator.js +184 -0
- package/lib/version-command.js +256 -0
- package/lib/version-manager.js +902 -0
- package/maiass-standalone.cjs +148 -0
- package/maiass.cjs +34 -0
- package/maiass.mjs +167 -0
- package/package.json +45 -0
- package/setup-env.js +83 -0
|
@@ -0,0 +1,902 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import colors from './colors.js';
|
|
5
|
+
import { SYMBOLS } from './symbols.js';
|
|
6
|
+
import { log, logger } from './logger.js';
|
|
7
|
+
import { loadEnvironmentConfig } from './config.js';
|
|
8
|
+
import { debuglog } from 'util';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Execute git command safely
|
|
12
|
+
* @param {string} command - Git command to execute
|
|
13
|
+
* @param {boolean} silent - Whether to suppress errors
|
|
14
|
+
* @returns {Promise<Object>} Command result with success, output, and error
|
|
15
|
+
*/
|
|
16
|
+
function executeGitCommand(command, silent = false) {
|
|
17
|
+
try {
|
|
18
|
+
const result = execSync(`git ${command}`, {
|
|
19
|
+
encoding: 'utf8',
|
|
20
|
+
stdio: silent ? 'pipe' : ['pipe', 'pipe', 'ignore']
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
success: true,
|
|
25
|
+
output: result.trim(),
|
|
26
|
+
error: null
|
|
27
|
+
};
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return {
|
|
30
|
+
success: false,
|
|
31
|
+
output: '',
|
|
32
|
+
error: error.message
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Supported version file types and their parsing/updating logic
|
|
39
|
+
*/
|
|
40
|
+
const VERSION_FILE_TYPES = {
|
|
41
|
+
json: {
|
|
42
|
+
extensions: ['.json'],
|
|
43
|
+
detect: (content) => {
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(content);
|
|
46
|
+
return parsed.version !== undefined;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
extract: (content) => {
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(content);
|
|
54
|
+
return parsed.version || null;
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
update: (content, newVersion) => {
|
|
60
|
+
try {
|
|
61
|
+
const parsed = JSON.parse(content);
|
|
62
|
+
parsed.version = newVersion;
|
|
63
|
+
return JSON.stringify(parsed, null, 2) + '\n';
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
text: {
|
|
70
|
+
extensions: ['.txt', '.version', ''],
|
|
71
|
+
detect: (content, filename) => {
|
|
72
|
+
// Simple text files that contain just a version number
|
|
73
|
+
const basename = path.basename(filename).toLowerCase();
|
|
74
|
+
if (basename === 'version' || basename.includes('version')) {
|
|
75
|
+
return /^\d+\.\d+\.\d+/.test(content.trim());
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
},
|
|
79
|
+
extract: (content) => {
|
|
80
|
+
const match = content.match(/^(\d+\.\d+\.\d+)/);
|
|
81
|
+
return match ? match[1] : null;
|
|
82
|
+
},
|
|
83
|
+
update: (content, newVersion) => {
|
|
84
|
+
return content.replace(/^\d+\.\d+\.\d+/, newVersion);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
php: {
|
|
88
|
+
extensions: ['.php','pattern'],
|
|
89
|
+
detect: (content) => {
|
|
90
|
+
return /Version:\s*\d+\.\d+\.\d+/.test(content) ||
|
|
91
|
+
/define\s*\(\s*['"].*VERSION['"]/.test(content);
|
|
92
|
+
},
|
|
93
|
+
extract: (content) => {
|
|
94
|
+
// WordPress style header
|
|
95
|
+
let match = content.match(/Version:\s*(\d+\.\d+\.\d+)/);
|
|
96
|
+
if (match) return match[1];
|
|
97
|
+
|
|
98
|
+
// PHP define
|
|
99
|
+
match = content.match(/define\s*\(\s*['"].*VERSION['"],\s*['"](\d+\.\d+\.\d+)['"]/);
|
|
100
|
+
return match ? match[1] : null;
|
|
101
|
+
},
|
|
102
|
+
update: (content, newVersion) => {
|
|
103
|
+
// Update WordPress style header
|
|
104
|
+
content = content.replace(
|
|
105
|
+
/(Version:\s*)\d+\.\d+\.\d+/g,
|
|
106
|
+
`$1${newVersion}`
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Update PHP define
|
|
110
|
+
content = content.replace(
|
|
111
|
+
/(define\s*\(\s*['"].*VERSION['"],\s*['"])\d+\.\d+\.\d+(['"])/g,
|
|
112
|
+
`$1${newVersion}$2`
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
return content;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Parse semantic version string
|
|
122
|
+
* @param {string} version - Version string (e.g., "1.2.3")
|
|
123
|
+
* @returns {Object|null} Parsed version object or null if invalid
|
|
124
|
+
*/
|
|
125
|
+
export function parseVersion(version) {
|
|
126
|
+
if (!version) return null;
|
|
127
|
+
|
|
128
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
|
|
129
|
+
if (!match) return null;
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
major: parseInt(match[1], 10),
|
|
133
|
+
minor: parseInt(match[2], 10),
|
|
134
|
+
patch: parseInt(match[3], 10),
|
|
135
|
+
prerelease: match[4] || null,
|
|
136
|
+
raw: version
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Compare two semantic versions
|
|
142
|
+
* @param {string} version1 - First version
|
|
143
|
+
* @param {string} version2 - Second version
|
|
144
|
+
* @returns {number} -1 if v1 < v2, 0 if equal, 1 if v1 > v2
|
|
145
|
+
*/
|
|
146
|
+
export function compareVersions(version1, version2) {
|
|
147
|
+
const v1 = parseVersion(version1);
|
|
148
|
+
const v2 = parseVersion(version2);
|
|
149
|
+
|
|
150
|
+
if (!v1 || !v2) return 0;
|
|
151
|
+
|
|
152
|
+
if (v1.major !== v2.major) return v1.major - v2.major;
|
|
153
|
+
if (v1.minor !== v2.minor) return v1.minor - v2.minor;
|
|
154
|
+
if (v1.patch !== v2.patch) return v1.patch - v2.patch;
|
|
155
|
+
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Bump version according to type
|
|
161
|
+
* @param {string} currentVersion - Current version string
|
|
162
|
+
* @param {string} bumpType - Type of bump (major, minor, patch)
|
|
163
|
+
* @returns {string|null} New version string or null if invalid
|
|
164
|
+
*/
|
|
165
|
+
export function bumpVersion(currentVersion, bumpType) {
|
|
166
|
+
const parsed = parseVersion(currentVersion);
|
|
167
|
+
if (!parsed) return null;
|
|
168
|
+
|
|
169
|
+
switch (bumpType.toLowerCase()) {
|
|
170
|
+
case 'major':
|
|
171
|
+
return `${parsed.major + 1}.0.0`;
|
|
172
|
+
case 'minor':
|
|
173
|
+
return `${parsed.major}.${parsed.minor + 1}.0`;
|
|
174
|
+
case 'patch':
|
|
175
|
+
return `${parsed.major}.${parsed.minor}.${parsed.patch + 1}`;
|
|
176
|
+
default:
|
|
177
|
+
// Check if it's a specific version
|
|
178
|
+
if (parseVersion(bumpType)) {
|
|
179
|
+
return bumpType;
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get WordPress plugin/theme configuration from environment variables
|
|
187
|
+
* @param {string} projectPath - Path to project directory
|
|
188
|
+
* @returns {Object} WordPress configuration
|
|
189
|
+
*/
|
|
190
|
+
function getWordPressConfig(projectPath = process.cwd()) {
|
|
191
|
+
logger.debug(`Loading WordPress config from: ${projectPath}`);
|
|
192
|
+
|
|
193
|
+
// Check if .env.maiass exists in current directory
|
|
194
|
+
const envFile = path.join(projectPath, '.env.maiass');
|
|
195
|
+
logger.debug(`Looking for .env.maiass at: ${envFile}`);
|
|
196
|
+
|
|
197
|
+
if (fs.existsSync(envFile)) {
|
|
198
|
+
logger.debug(`.env.maiass file exists`);
|
|
199
|
+
try {
|
|
200
|
+
const envContent = fs.readFileSync(envFile, 'utf8');
|
|
201
|
+
logger.debug(`.env.maiass content:`);
|
|
202
|
+
logger.debug(envContent);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
logger.error(`Could not read .env.maiass: ${error.message}`);
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
logger.debug(`.env.maiass file not found`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const envVars = loadEnvironmentConfig();
|
|
211
|
+
logger.debug(`Environment variables loaded`);
|
|
212
|
+
// Load environment variables
|
|
213
|
+
// Show all loaded environment variables for debugging
|
|
214
|
+
logger.debug(`All loaded environment variables:`);
|
|
215
|
+
const relevantVars = ['MAIASS_PLUGIN_PATH', 'MAIASS_THEME_PATH', 'MAIASS_VERSION_CONSTANT', 'MAIASS_REPO_TYPE'];
|
|
216
|
+
relevantVars.forEach(varName => {
|
|
217
|
+
const value = envVars[varName];
|
|
218
|
+
logger.debug(` ${varName}: ${value || '(not set)'}`);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const config = {
|
|
222
|
+
pluginPath: envVars.MAIASS_PLUGIN_PATH || null,
|
|
223
|
+
themePath: envVars.MAIASS_THEME_PATH || null,
|
|
224
|
+
versionConstant: envVars.MAIASS_VERSION_CONSTANT || null
|
|
225
|
+
};
|
|
226
|
+
logger.debug(`WordPress config loaded:`, config);
|
|
227
|
+
logger.debug(`WordPress config:`);
|
|
228
|
+
logger.debug(` Plugin Path: ${config.pluginPath || '(not set)'}`);
|
|
229
|
+
logger.debug(` Theme Path: ${config.themePath || '(not set)'}`);
|
|
230
|
+
logger.debug(` Version Constant: ${config.versionConstant || '(not set)'}`);
|
|
231
|
+
|
|
232
|
+
return config;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Convert slug to uppercase with underscores for PHP constant
|
|
237
|
+
* @param {string} slug - Plugin/theme slug
|
|
238
|
+
* @returns {string} Formatted constant name
|
|
239
|
+
*/
|
|
240
|
+
function slugToConstant(slug) {
|
|
241
|
+
return slug
|
|
242
|
+
.replace(/[^a-zA-Z0-9_]/g, '_') // Replace non-alphanumeric with underscores
|
|
243
|
+
.replace(/_+/g, '_') // Replace multiple underscores with single
|
|
244
|
+
.replace(/^_|_$/g, '') // Remove leading/trailing underscores
|
|
245
|
+
.toUpperCase();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Generate version constant name from plugin/theme path
|
|
250
|
+
* @param {string} pluginOrThemePath - Path to plugin or theme
|
|
251
|
+
* @returns {string} Generated constant name
|
|
252
|
+
*/
|
|
253
|
+
function generateVersionConstant(pluginOrThemePath) {
|
|
254
|
+
let slug;
|
|
255
|
+
|
|
256
|
+
if (pluginOrThemePath.includes('wp-content/plugins/')) {
|
|
257
|
+
slug = pluginOrThemePath.split('wp-content/plugins/')[1].split('/')[0];
|
|
258
|
+
} else if (pluginOrThemePath.includes('wp-content/themes/')) {
|
|
259
|
+
slug = pluginOrThemePath.split('wp-content/themes/')[1].split('/')[0];
|
|
260
|
+
} else {
|
|
261
|
+
// Extract last directory name as slug
|
|
262
|
+
slug = path.basename(pluginOrThemePath);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return `${slugToConstant(slug)}_VERSION`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Update WordPress theme style.css version header
|
|
270
|
+
* @param {string} filePath - Path to the style.css file
|
|
271
|
+
* @param {string} newVersion - New version value
|
|
272
|
+
* @returns {boolean} Success status
|
|
273
|
+
*/
|
|
274
|
+
function updateThemeStyleVersion(filePath, newVersion) {
|
|
275
|
+
logger.debug(`Checking theme style.css: ${filePath}`);
|
|
276
|
+
|
|
277
|
+
if (!fs.existsSync(filePath)) {
|
|
278
|
+
logger.debug(`File not found: ${filePath}`);
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
logger.debug(`style.css exists: ${filePath}`);
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
let content = fs.readFileSync(filePath, 'utf8');
|
|
286
|
+
logger.debug(` style.css content length: ${content.length} characters`);
|
|
287
|
+
|
|
288
|
+
// WordPress theme header pattern for Version (handles various formats)
|
|
289
|
+
// Matches: "Version:", "version:", "* Version:", "* version:", etc.
|
|
290
|
+
const versionPattern = /^(\s*\*?\s*[Vv]ersion:\s*)([0-9]+\.[0-9]+\.[0-9]+.*)$/gm;
|
|
291
|
+
logger.debug(` Search pattern: ${versionPattern.source}`);
|
|
292
|
+
|
|
293
|
+
// Test the pattern and show results
|
|
294
|
+
const matches = content.match(versionPattern);
|
|
295
|
+
if (matches) {
|
|
296
|
+
logger.debug(` Found ${matches.length} version header(s):`);
|
|
297
|
+
matches.forEach((match, index) => {
|
|
298
|
+
console.log(colors.Gray(` ${index + 1}: ${match.trim()}`));
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Replace with uniform format: always write "Version: x.x.x"
|
|
302
|
+
content = content.replace(versionPattern, `Version: ${newVersion}`);
|
|
303
|
+
console.log(colors.BGreen(`${SYMBOLS.CHECKMARK} Updated theme version in style.css (standardized format)`));
|
|
304
|
+
} else {
|
|
305
|
+
logger.debug(` No version header found in style.css`);
|
|
306
|
+
|
|
307
|
+
// Show the first part of the file to help debug
|
|
308
|
+
const lines = content.split('\n');
|
|
309
|
+
logger.debug(` First 15 lines of style.css:`);
|
|
310
|
+
lines.slice(0, 15).forEach((line, index) => {
|
|
311
|
+
console.log(colors.Gray(` ${index + 1}: ${line}`));
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
318
|
+
logger.debug(` style.css written successfully`);
|
|
319
|
+
return true;
|
|
320
|
+
} catch (error) {
|
|
321
|
+
console.error(colors.Red(`${SYMBOLS.CROSS} Error updating ${filePath}: ${error.message}`));
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Update PHP version constant in file
|
|
328
|
+
* @param {string} filePath - Path to the PHP file
|
|
329
|
+
* @param {string} constantName - Name of the constant to update
|
|
330
|
+
* @param {string} newVersion - New version value
|
|
331
|
+
* @returns {boolean} Success status
|
|
332
|
+
*/
|
|
333
|
+
function updatePhpVersionConstant(filePath, constantName, newVersion) {
|
|
334
|
+
logger.debug(` Checking PHP file: ${filePath}`);
|
|
335
|
+
|
|
336
|
+
if (!fs.existsSync(filePath)) {
|
|
337
|
+
logger.debug(` File not found: ${filePath}`);
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
logger.debug(` File exists: ${filePath}`);
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
let content = fs.readFileSync(filePath, 'utf8');
|
|
345
|
+
logger.debug(` File content length: ${content.length} characters`);
|
|
346
|
+
|
|
347
|
+
const definePattern = new RegExp(`^\\s*define\\s*\\(\\s*['"]${constantName}['"].*$`, 'gm');
|
|
348
|
+
logger.debug(` Search pattern: ${definePattern.source}`);
|
|
349
|
+
logger.debug(` Looking for constant: ${constantName}`);
|
|
350
|
+
|
|
351
|
+
// Test the pattern and show results
|
|
352
|
+
const matches = content.match(definePattern);
|
|
353
|
+
if (matches) {
|
|
354
|
+
logger.debug(` Found ${matches.length} match(es):`);
|
|
355
|
+
matches.forEach((match, index) => {
|
|
356
|
+
console.log(colors.Gray(` ${index + 1}: ${match.trim()}`));
|
|
357
|
+
});
|
|
358
|
+
} else {
|
|
359
|
+
logger.debug(` No matches found for pattern`);
|
|
360
|
+
|
|
361
|
+
// Show a sample of the file content to help debug
|
|
362
|
+
const lines = content.split('\n');
|
|
363
|
+
logger.debug(` First 10 lines of file:`);
|
|
364
|
+
lines.slice(0, 10).forEach((line, index) => {
|
|
365
|
+
console.log(colors.Gray(` ${index + 1}: ${line}`));
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// Look for any define statements
|
|
369
|
+
const anyDefinePattern = /^\s*define\s*\(/gm;
|
|
370
|
+
const defineMatches = content.match(anyDefinePattern);
|
|
371
|
+
if (defineMatches) {
|
|
372
|
+
logger.debug(` Found ${defineMatches.length} define statement(s) in file`);
|
|
373
|
+
} else {
|
|
374
|
+
logger.debug(` No define statements found in file`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const newDefine = `define('${constantName}', '${newVersion}');`;
|
|
379
|
+
logger.debug(` New define statement: ${newDefine}`);
|
|
380
|
+
|
|
381
|
+
if (definePattern.test(content)) {
|
|
382
|
+
// Replace existing define
|
|
383
|
+
content = content.replace(definePattern, newDefine);
|
|
384
|
+
console.log(colors.BGreen(`${SYMBOLS.CHECKMARK} Updated ${constantName} in ${path.basename(filePath)}`));
|
|
385
|
+
} else {
|
|
386
|
+
// Add new define after opening PHP tag
|
|
387
|
+
const phpOpenTag = /<\?php/;
|
|
388
|
+
logger.debug(` Looking for PHP opening tag...`);
|
|
389
|
+
|
|
390
|
+
if (phpOpenTag.test(content)) {
|
|
391
|
+
logger.debug(` Found PHP opening tag, adding new define`);
|
|
392
|
+
content = content.replace(phpOpenTag, `<?php\n\n${newDefine}`);
|
|
393
|
+
console.log(colors.BGreen(`${SYMBOLS.CHECKMARK} Added ${constantName} to ${path.basename(filePath)}`));
|
|
394
|
+
} else {
|
|
395
|
+
console.log(colors.BYellow(`${SYMBOLS.WARNING} Could not find PHP opening tag in ${path.basename(filePath)}`));
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
401
|
+
logger.debug(` File written successfully`);
|
|
402
|
+
return true;
|
|
403
|
+
} catch (error) {
|
|
404
|
+
console.error(colors.Red(`${SYMBOLS.CROSS} Error updating ${filePath}: ${error.message}`));
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Update WordPress plugin/theme version files
|
|
411
|
+
* @param {string} newVersion - New version to set
|
|
412
|
+
* @param {string} projectPath - Path to project directory
|
|
413
|
+
* @returns {boolean} Success status
|
|
414
|
+
*/
|
|
415
|
+
function updateWordPressVersions(newVersion, projectPath = process.cwd()) {
|
|
416
|
+
const wpConfig = getWordPressConfig(projectPath);
|
|
417
|
+
let success = true;
|
|
418
|
+
|
|
419
|
+
// Handle plugin path
|
|
420
|
+
if (wpConfig.pluginPath) {
|
|
421
|
+
const pluginPath = path.isAbsolute(wpConfig.pluginPath)
|
|
422
|
+
? wpConfig.pluginPath
|
|
423
|
+
: path.join(projectPath, wpConfig.pluginPath);
|
|
424
|
+
|
|
425
|
+
// Determine main plugin file
|
|
426
|
+
let mainPluginFile;
|
|
427
|
+
if (fs.existsSync(pluginPath) && fs.statSync(pluginPath).isDirectory()) {
|
|
428
|
+
// Look for main plugin file (usually matches directory name)
|
|
429
|
+
const pluginName = path.basename(pluginPath);
|
|
430
|
+
const possibleFiles = [
|
|
431
|
+
path.join(pluginPath, `${pluginName}.php`),
|
|
432
|
+
path.join(pluginPath, 'plugin.php'),
|
|
433
|
+
path.join(pluginPath, 'index.php')
|
|
434
|
+
];
|
|
435
|
+
|
|
436
|
+
mainPluginFile = possibleFiles.find(file => fs.existsSync(file));
|
|
437
|
+
} else if (pluginPath.endsWith('.php')) {
|
|
438
|
+
mainPluginFile = pluginPath;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (mainPluginFile) {
|
|
442
|
+
const constantName = wpConfig.versionConstant || generateVersionConstant(wpConfig.pluginPath);
|
|
443
|
+
if (!updatePhpVersionConstant(mainPluginFile, constantName, newVersion)) {
|
|
444
|
+
success = false;
|
|
445
|
+
}
|
|
446
|
+
} else {
|
|
447
|
+
console.log(colors.BYellow(`${SYMBOLS.WARNING} Could not find main plugin file in ${pluginPath}`));
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Handle theme path
|
|
452
|
+
if (wpConfig.themePath) {
|
|
453
|
+
logger.debug(` Processing theme path: ${wpConfig.themePath}`);
|
|
454
|
+
|
|
455
|
+
const themePath = path.isAbsolute(wpConfig.themePath)
|
|
456
|
+
? wpConfig.themePath
|
|
457
|
+
: path.join(projectPath, wpConfig.themePath);
|
|
458
|
+
|
|
459
|
+
logger.debug(` Resolved theme path: ${themePath}`);
|
|
460
|
+
logger.debug(` Checking if theme path exists...`);
|
|
461
|
+
|
|
462
|
+
// Look for functions.php in theme directory
|
|
463
|
+
let functionsFile;
|
|
464
|
+
if (fs.existsSync(themePath)) {
|
|
465
|
+
logger.debug(` Theme path exists`);
|
|
466
|
+
|
|
467
|
+
if (fs.statSync(themePath).isDirectory()) {
|
|
468
|
+
logger.debug(` Theme path is a directory, looking for functions.php`);
|
|
469
|
+
functionsFile = path.join(themePath, 'functions.php');
|
|
470
|
+
logger.debug(` Functions file path: ${functionsFile}`);
|
|
471
|
+
} else {
|
|
472
|
+
logger.debug(` Theme path is a file`);
|
|
473
|
+
}
|
|
474
|
+
} else {
|
|
475
|
+
logger.debug(` Theme path does not exist: ${themePath}`);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (wpConfig.themePath.endsWith('functions.php')) {
|
|
479
|
+
logger.debug(` Theme path ends with functions.php, using directly`);
|
|
480
|
+
functionsFile = themePath;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
logger.debug(` Final functions file path: ${functionsFile || '(not determined)'}`);
|
|
484
|
+
|
|
485
|
+
if (functionsFile && fs.existsSync(functionsFile)) {
|
|
486
|
+
logger.debug(` Functions file exists, proceeding with update`);
|
|
487
|
+
|
|
488
|
+
const constantName = wpConfig.versionConstant || generateVersionConstant(wpConfig.themePath);
|
|
489
|
+
logger.debug(` Using constant name: ${constantName}`);
|
|
490
|
+
|
|
491
|
+
if (!updatePhpVersionConstant(functionsFile, constantName, newVersion)) {
|
|
492
|
+
success = false;
|
|
493
|
+
}
|
|
494
|
+
} else {
|
|
495
|
+
console.log(colors.BYellow(`${SYMBOLS.WARNING} Could not find functions.php in ${themePath}`));
|
|
496
|
+
if (functionsFile) {
|
|
497
|
+
logger.debug(` Expected functions.php at: ${functionsFile}`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Also update style.css if it exists in the theme directory
|
|
502
|
+
const styleFile = path.join(themePath, 'style.css');
|
|
503
|
+
logger.debug(` Checking for style.css at: ${styleFile}`);
|
|
504
|
+
debuglog(` Checking for style.css at: ${styleFile}`);
|
|
505
|
+
if (fs.existsSync(styleFile)) {
|
|
506
|
+
logger.debug(` style.css found, updating theme version header`);
|
|
507
|
+
if (!updateThemeStyleVersion(styleFile, newVersion)) {
|
|
508
|
+
success = false;
|
|
509
|
+
}
|
|
510
|
+
} else {
|
|
511
|
+
logger.debug(` style.css not found at: ${styleFile}`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return success;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Detect version files in the current directory
|
|
520
|
+
* @param {string} projectPath - Path to project directory
|
|
521
|
+
* @returns {Array} Array of detected version files
|
|
522
|
+
*/
|
|
523
|
+
export function detectVersionFiles(projectPath = process.cwd()) {
|
|
524
|
+
const versionFiles = [];
|
|
525
|
+
|
|
526
|
+
// Common version file patterns to check
|
|
527
|
+
const filesToCheck = [
|
|
528
|
+
'package.json',
|
|
529
|
+
'composer.json',
|
|
530
|
+
'VERSION',
|
|
531
|
+
'version.txt',
|
|
532
|
+
'style.css', // WordPress themes
|
|
533
|
+
'plugin.php', // WordPress plugins
|
|
534
|
+
'functions.php'
|
|
535
|
+
];
|
|
536
|
+
|
|
537
|
+
for (const filename of filesToCheck) {
|
|
538
|
+
const filePath = path.join(projectPath, filename);
|
|
539
|
+
|
|
540
|
+
if (fs.existsSync(filePath)) {
|
|
541
|
+
try {
|
|
542
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
543
|
+
const ext = path.extname(filename);
|
|
544
|
+
|
|
545
|
+
// Determine file type and check if it contains version info
|
|
546
|
+
for (const [typeName, typeConfig] of Object.entries(VERSION_FILE_TYPES)) {
|
|
547
|
+
if (typeConfig.extensions.includes(ext) || typeConfig.extensions.includes('')) {
|
|
548
|
+
if (typeConfig.detect(content, filename)) {
|
|
549
|
+
const version = typeConfig.extract(content);
|
|
550
|
+
if (version) {
|
|
551
|
+
versionFiles.push({
|
|
552
|
+
path: filePath,
|
|
553
|
+
filename,
|
|
554
|
+
type: typeName,
|
|
555
|
+
currentVersion: version,
|
|
556
|
+
content
|
|
557
|
+
});
|
|
558
|
+
break; // Found matching type, move to next file
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
} catch (error) {
|
|
564
|
+
// Skip files that can't be read
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return versionFiles;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Get the latest version from git tags
|
|
575
|
+
* @returns {Promise<string|null>} Latest version tag or null
|
|
576
|
+
*/
|
|
577
|
+
export async function getLatestVersionFromTags() {
|
|
578
|
+
try {
|
|
579
|
+
const result = await executeGitCommand('tag -l');
|
|
580
|
+
if (!result.success) return null;
|
|
581
|
+
|
|
582
|
+
const tags = result.output
|
|
583
|
+
.split('\n')
|
|
584
|
+
.filter(tag => tag.trim())
|
|
585
|
+
.filter(tag => /^\d+\.\d+\.\d+$/.test(tag))
|
|
586
|
+
.sort((a, b) => compareVersions(b, a)); // Sort descending
|
|
587
|
+
|
|
588
|
+
return tags.length > 0 ? tags[0] : null;
|
|
589
|
+
} catch {
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Get current project version from files or git tags
|
|
596
|
+
* @param {string} projectPath - Path to project directory
|
|
597
|
+
* @returns {Promise<Object>} Version information
|
|
598
|
+
*/
|
|
599
|
+
export async function getCurrentVersion(projectPath = process.cwd()) {
|
|
600
|
+
const versionFiles = detectVersionFiles(projectPath);
|
|
601
|
+
const tagVersion = await getLatestVersionFromTags();
|
|
602
|
+
|
|
603
|
+
let primaryVersion = null;
|
|
604
|
+
let primarySource = null;
|
|
605
|
+
|
|
606
|
+
// Prioritize package.json if it exists
|
|
607
|
+
const packageJson = versionFiles.find(f => f.filename === 'package.json');
|
|
608
|
+
if (packageJson) {
|
|
609
|
+
primaryVersion = packageJson.currentVersion;
|
|
610
|
+
primarySource = 'package.json';
|
|
611
|
+
} else if (versionFiles.length > 0) {
|
|
612
|
+
// Use first detected version file
|
|
613
|
+
primaryVersion = versionFiles[0].currentVersion;
|
|
614
|
+
primarySource = versionFiles[0].filename;
|
|
615
|
+
} else if (tagVersion) {
|
|
616
|
+
primaryVersion = tagVersion;
|
|
617
|
+
primarySource = 'git tags';
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
return {
|
|
621
|
+
current: primaryVersion,
|
|
622
|
+
source: primarySource,
|
|
623
|
+
files: versionFiles,
|
|
624
|
+
tagVersion,
|
|
625
|
+
hasVersionFiles: versionFiles.length > 0
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Update secondary version files based on MAIASS_VERSION_SECONDARY_FILES config
|
|
631
|
+
* Format: "file:type:pattern|file:type:pattern"
|
|
632
|
+
* Types: txt, json, php, pattern
|
|
633
|
+
* Pattern type uses {version} placeholder
|
|
634
|
+
* @param {string} newVersion - New version to set
|
|
635
|
+
* @param {string} config - Pipe-separated config string
|
|
636
|
+
* @param {boolean} dryRun - If true, don't actually write files
|
|
637
|
+
* @returns {Promise<Object>} Update results
|
|
638
|
+
*/
|
|
639
|
+
async function updateSecondaryVersionFiles(newVersion, config, dryRun = false) {
|
|
640
|
+
const results = {
|
|
641
|
+
success: true,
|
|
642
|
+
updated: [],
|
|
643
|
+
failed: []
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
if (!config) return results;
|
|
647
|
+
|
|
648
|
+
// Parse pipe-separated config
|
|
649
|
+
const fileConfigs = config.split('|').filter(c => c.trim());
|
|
650
|
+
|
|
651
|
+
for (const fileConfig of fileConfigs) {
|
|
652
|
+
try {
|
|
653
|
+
// Split on colons: filename:type:pattern
|
|
654
|
+
const parts = fileConfig.split(':');
|
|
655
|
+
if (parts.length < 2) {
|
|
656
|
+
results.failed.push({
|
|
657
|
+
file: fileConfig,
|
|
658
|
+
error: 'Invalid config format (expected file:type:pattern)'
|
|
659
|
+
});
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const filename = parts[0].trim();
|
|
664
|
+
const type = parts[1].trim() || 'txt';
|
|
665
|
+
let pattern = parts.slice(2).join(':').trim(); // Everything after second colon
|
|
666
|
+
|
|
667
|
+
// Remove escape backslashes from pattern (e.g., \" becomes ")
|
|
668
|
+
pattern = pattern.replace(/\\"/g, '"').replace(/\\'/g, "'");
|
|
669
|
+
|
|
670
|
+
if (!fs.existsSync(filename)) {
|
|
671
|
+
logger.warning(SYMBOLS.WARNING, `Skipping ${filename} (not found)`);
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Read file content
|
|
676
|
+
let content = fs.readFileSync(filename, 'utf8');
|
|
677
|
+
let updated = false;
|
|
678
|
+
|
|
679
|
+
if (type === 'pattern') {
|
|
680
|
+
// Pattern type: replace {version} placeholder in the pattern
|
|
681
|
+
// First escape all regex special chars in the pattern
|
|
682
|
+
const escapedPattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
683
|
+
// Then replace the escaped {version} with version regex
|
|
684
|
+
const searchPattern = escapedPattern.replace('\\{version\\}', '[0-9]+\\.[0-9]+\\.[0-9]+');
|
|
685
|
+
const replacePattern = pattern.replace('{version}', newVersion);
|
|
686
|
+
const regex = new RegExp(searchPattern, 'g');
|
|
687
|
+
|
|
688
|
+
if (content.match(regex)) {
|
|
689
|
+
content = content.replace(regex, replacePattern);
|
|
690
|
+
updated = true;
|
|
691
|
+
}
|
|
692
|
+
} else if (type === 'txt') {
|
|
693
|
+
// Text type: find line starting with pattern and replace version
|
|
694
|
+
const lines = content.split('\n');
|
|
695
|
+
for (let i = 0; i < lines.length; i++) {
|
|
696
|
+
if (lines[i].startsWith(pattern)) {
|
|
697
|
+
// Replace version number in this line
|
|
698
|
+
lines[i] = lines[i].replace(/\d+\.\d+\.\d+/, newVersion);
|
|
699
|
+
updated = true;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
content = lines.join('\n');
|
|
703
|
+
} else if (type === 'json') {
|
|
704
|
+
// JSON type: update specific key
|
|
705
|
+
try {
|
|
706
|
+
const json = JSON.parse(content);
|
|
707
|
+
const keys = pattern.split('.');
|
|
708
|
+
let obj = json;
|
|
709
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
710
|
+
obj = obj[keys[i]];
|
|
711
|
+
}
|
|
712
|
+
obj[keys[keys.length - 1]] = newVersion;
|
|
713
|
+
content = JSON.stringify(json, null, 2) + '\n';
|
|
714
|
+
updated = true;
|
|
715
|
+
} catch (e) {
|
|
716
|
+
results.failed.push({
|
|
717
|
+
file: filename,
|
|
718
|
+
error: `JSON parse error: ${e.message}`
|
|
719
|
+
});
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if (updated && !dryRun) {
|
|
725
|
+
fs.writeFileSync(filename, content, 'utf8');
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
if (updated) {
|
|
729
|
+
logger.success(SYMBOLS.CHECKMARK, `Updated version to ${newVersion} in ${filename}`);
|
|
730
|
+
results.updated.push({
|
|
731
|
+
file: filename,
|
|
732
|
+
type,
|
|
733
|
+
pattern,
|
|
734
|
+
newVersion
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
} catch (error) {
|
|
739
|
+
results.failed.push({
|
|
740
|
+
file: fileConfig,
|
|
741
|
+
error: error.message
|
|
742
|
+
});
|
|
743
|
+
results.success = false;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
return results;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Update version in all detected files
|
|
752
|
+
* @param {string} newVersion - New version to set
|
|
753
|
+
* @param {Array} versionFiles - Array of version files to update
|
|
754
|
+
* @param {boolean} dryRun - If true, don't actually write files
|
|
755
|
+
* @returns {Promise<Object>} Update results
|
|
756
|
+
*/
|
|
757
|
+
export async function updateVersionFiles(newVersion, versionFiles, dryRun = false) {
|
|
758
|
+
const results = {
|
|
759
|
+
success: true,
|
|
760
|
+
updated: [],
|
|
761
|
+
failed: [],
|
|
762
|
+
dryRun
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
// Update primary version files
|
|
766
|
+
for (const file of versionFiles) {
|
|
767
|
+
try {
|
|
768
|
+
const typeConfig = VERSION_FILE_TYPES[file.type];
|
|
769
|
+
const updatedContent = typeConfig.update(file.content, newVersion);
|
|
770
|
+
|
|
771
|
+
if (!updatedContent) {
|
|
772
|
+
results.failed.push({
|
|
773
|
+
file: file.filename,
|
|
774
|
+
error: 'Failed to update content'
|
|
775
|
+
});
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (!dryRun) {
|
|
780
|
+
fs.writeFileSync(file.path, updatedContent, 'utf8');
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
results.updated.push({
|
|
784
|
+
file: file.filename,
|
|
785
|
+
path: file.path,
|
|
786
|
+
oldVersion: file.currentVersion,
|
|
787
|
+
newVersion
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
} catch (error) {
|
|
791
|
+
results.failed.push({
|
|
792
|
+
file: file.filename,
|
|
793
|
+
error: error.message
|
|
794
|
+
});
|
|
795
|
+
results.success = false;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Update secondary version files if configured
|
|
800
|
+
const secondaryFiles = process.env.MAIASS_VERSION_SECONDARY_FILES;
|
|
801
|
+
if (secondaryFiles) {
|
|
802
|
+
logger.info(SYMBOLS.INFO, 'Updating secondary version files...');
|
|
803
|
+
const secondaryResults = await updateSecondaryVersionFiles(newVersion, secondaryFiles, dryRun);
|
|
804
|
+
results.updated.push(...secondaryResults.updated);
|
|
805
|
+
results.failed.push(...secondaryResults.failed);
|
|
806
|
+
if (!secondaryResults.success) {
|
|
807
|
+
results.success = false;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Update WordPress plugin/theme versions if configured
|
|
812
|
+
if (!dryRun) {
|
|
813
|
+
// change to use logger
|
|
814
|
+
logger.info(SYMBOLS.INFO, 'Checking for WordPress plugin/theme version updates...');
|
|
815
|
+
const wpSuccess = updateWordPressVersions(newVersion);
|
|
816
|
+
if (!wpSuccess) {
|
|
817
|
+
results.success = false;
|
|
818
|
+
results.failed.push({
|
|
819
|
+
file: 'WordPress files',
|
|
820
|
+
error: 'Failed to update some WordPress plugin/theme version constants'
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
} else {
|
|
824
|
+
// For dry run, just check if WordPress config exists
|
|
825
|
+
const wpConfig = getWordPressConfig();
|
|
826
|
+
if (wpConfig.pluginPath || wpConfig.themePath) {
|
|
827
|
+
// change to use logger
|
|
828
|
+
logger.info(SYMBOLS.INFO, 'Would update WordPress plugin/theme versions (dry run)');
|
|
829
|
+
if (wpConfig.pluginPath) {
|
|
830
|
+
const constantName = wpConfig.versionConstant || generateVersionConstant(wpConfig.pluginPath);
|
|
831
|
+
logger.info(SYMBOLS.INFO, ` Plugin: ${wpConfig.pluginPath} (${constantName})`);
|
|
832
|
+
}
|
|
833
|
+
if (wpConfig.themePath) {
|
|
834
|
+
const constantName = wpConfig.versionConstant || generateVersionConstant(wpConfig.themePath);
|
|
835
|
+
logger.info(SYMBOLS.INFO, ` Theme: ${wpConfig.themePath} (${constantName})`);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
return results;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Create git tag for version
|
|
845
|
+
* @param {string} version - Version to tag
|
|
846
|
+
* @param {string} message - Tag message
|
|
847
|
+
* @param {boolean} dryRun - If true, don't actually create tag
|
|
848
|
+
* @returns {Promise<Object>} Tag creation result
|
|
849
|
+
*/
|
|
850
|
+
export async function createVersionTag(version, message = null, dryRun = false) {
|
|
851
|
+
const tagMessage = message || `Release version ${version}`;
|
|
852
|
+
|
|
853
|
+
if (dryRun) {
|
|
854
|
+
return {
|
|
855
|
+
success: true,
|
|
856
|
+
dryRun: true,
|
|
857
|
+
tag: version,
|
|
858
|
+
message: tagMessage
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
try {
|
|
863
|
+
// Check if tag already exists
|
|
864
|
+
const existingTag = await executeGitCommand(`tag -l ${version}`);
|
|
865
|
+
if (existingTag.success && existingTag.output.trim()) {
|
|
866
|
+
return {
|
|
867
|
+
success: false,
|
|
868
|
+
error: `Tag ${version} already exists`
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// Create annotated tag
|
|
873
|
+
const result = await executeGitCommand(`tag -a ${version} -m "${tagMessage}"`);
|
|
874
|
+
|
|
875
|
+
return {
|
|
876
|
+
success: result.success,
|
|
877
|
+
tag: version,
|
|
878
|
+
message: tagMessage,
|
|
879
|
+
error: result.success ? null : result.error
|
|
880
|
+
};
|
|
881
|
+
} catch (error) {
|
|
882
|
+
return {
|
|
883
|
+
success: false,
|
|
884
|
+
error: error.message
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Validate version string
|
|
891
|
+
* @param {string} version - Version string to validate
|
|
892
|
+
* @returns {Object} Validation result
|
|
893
|
+
*/
|
|
894
|
+
export function validateVersion(version) {
|
|
895
|
+
const parsed = parseVersion(version);
|
|
896
|
+
|
|
897
|
+
return {
|
|
898
|
+
valid: parsed !== null,
|
|
899
|
+
parsed,
|
|
900
|
+
error: parsed ? null : 'Invalid semantic version format (expected: MAJOR.MINOR.PATCH)'
|
|
901
|
+
};
|
|
902
|
+
}
|