maiass 5.9.23 → 5.9.26
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/lib/account-info.js +3 -4
- package/lib/bootstrap.js +102 -35
- package/lib/commit.js +7 -12
- package/lib/devlog.js +14 -10
- package/lib/secure-storage.js +22 -23
- package/lib/token-validator.js +6 -7
- package/lib/version-manager.js +89 -13
- package/package.json +1 -1
package/lib/account-info.js
CHANGED
|
@@ -88,7 +88,7 @@ async function createAnonymousSubscriptionIfNeeded() {
|
|
|
88
88
|
|
|
89
89
|
if (stored) {
|
|
90
90
|
log.success(SYMBOLS.CHECKMARK, 'Anonymous subscription created and stored securely');
|
|
91
|
-
log.info(SYMBOLS.INFO, ` API Key:
|
|
91
|
+
log.info(SYMBOLS.INFO, ` API Key: [stored securely]`);
|
|
92
92
|
log.info(SYMBOLS.INFO, ` Credits: ${credits || 'N/A'}`);
|
|
93
93
|
|
|
94
94
|
if (subscriptionId) {
|
|
@@ -412,13 +412,12 @@ export async function handleAccountInfoCommand(options = {}) {
|
|
|
412
412
|
const custEmail = data.customer_email || '-';
|
|
413
413
|
const statusField = data.status || result.status;
|
|
414
414
|
|
|
415
|
-
const maskedKey = maskToken(apiKey);
|
|
416
415
|
const credit = 'Nugét';
|
|
417
|
-
|
|
416
|
+
|
|
418
417
|
console.log('');
|
|
419
418
|
console.log('Account Info');
|
|
420
419
|
console.log('------------');
|
|
421
|
-
console.log(`API Token:
|
|
420
|
+
console.log(`API Token: [stored securely]`);
|
|
422
421
|
|
|
423
422
|
const subscriptionId = process.env.MAIASS_SUBSCRIPTION_ID;
|
|
424
423
|
if (subscriptionId) {
|
package/lib/bootstrap.js
CHANGED
|
@@ -58,6 +58,11 @@ function loadExistingValues() {
|
|
|
58
58
|
* @returns {string} Detected project type
|
|
59
59
|
*/
|
|
60
60
|
function detectProjectType() {
|
|
61
|
+
// Check for Swift/Xcode project — search root and one level deep
|
|
62
|
+
if (detectPbxproj() || fs.existsSync('Package.swift')) {
|
|
63
|
+
return 'swift';
|
|
64
|
+
}
|
|
65
|
+
|
|
61
66
|
// Check for WordPress
|
|
62
67
|
if (fs.existsSync('wp-config.php') || fs.existsSync('wp-content')) {
|
|
63
68
|
if (fs.existsSync('style.css')) {
|
|
@@ -66,9 +71,7 @@ function detectProjectType() {
|
|
|
66
71
|
return 'wordpress-theme';
|
|
67
72
|
}
|
|
68
73
|
}
|
|
69
|
-
|
|
70
|
-
const files = fs.readdirSync('.');
|
|
71
|
-
for (const file of files) {
|
|
74
|
+
for (const file of fs.readdirSync('.')) {
|
|
72
75
|
if (file.endsWith('.php')) {
|
|
73
76
|
const content = fs.readFileSync(file, 'utf8');
|
|
74
77
|
if (content.includes('Plugin Name:')) {
|
|
@@ -78,40 +81,77 @@ function detectProjectType() {
|
|
|
78
81
|
}
|
|
79
82
|
return 'wordpress-site';
|
|
80
83
|
}
|
|
81
|
-
|
|
84
|
+
|
|
82
85
|
// Check for Craft CMS
|
|
83
86
|
if (fs.existsSync('craft') || fs.existsSync('config/general.php')) {
|
|
84
87
|
return 'craft';
|
|
85
88
|
}
|
|
86
|
-
|
|
89
|
+
|
|
87
90
|
// Default to bespoke
|
|
88
91
|
return 'bespoke';
|
|
89
92
|
}
|
|
90
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Find the .pbxproj file by searching the current directory and one level of subdirectories.
|
|
96
|
+
* Xcode projects are often nested: ProjectRoot/MyApp/MyApp.xcodeproj/project.pbxproj
|
|
97
|
+
* Skips hidden dirs, node_modules, and build artefact folders.
|
|
98
|
+
* @returns {string|null} Relative path to the .pbxproj, or null
|
|
99
|
+
*/
|
|
100
|
+
function detectPbxproj() {
|
|
101
|
+
const SKIP = new Set(['node_modules', '.git', 'build', 'dist', 'Pods', 'DerivedData']);
|
|
102
|
+
|
|
103
|
+
function findInDir(dir, depth = 0) {
|
|
104
|
+
try {
|
|
105
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
106
|
+
// Check for .xcodeproj bundles at this level
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
if (entry.isDirectory() && entry.name.endsWith('.xcodeproj')) {
|
|
109
|
+
const pbxproj = path.join(dir, entry.name, 'project.pbxproj');
|
|
110
|
+
if (fs.existsSync(pbxproj)) {
|
|
111
|
+
// Return relative to cwd
|
|
112
|
+
return path.relative(process.cwd(), pbxproj);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Recurse one level into non-skipped subdirectories
|
|
117
|
+
if (depth < 2) {
|
|
118
|
+
for (const entry of entries) {
|
|
119
|
+
if (entry.isDirectory() && !SKIP.has(entry.name) && !entry.name.startsWith('.')) {
|
|
120
|
+
const found = findInDir(path.join(dir, entry.name), depth + 1);
|
|
121
|
+
if (found) return found;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} catch {}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return findInDir(process.cwd());
|
|
130
|
+
}
|
|
131
|
+
|
|
91
132
|
/**
|
|
92
133
|
* Detect version source file
|
|
93
134
|
* @returns {string} Detected version file
|
|
94
135
|
*/
|
|
95
136
|
function detectVersionSource() {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (fs.existsSync('VERSION'))
|
|
103
|
-
return 'VERSION';
|
|
104
|
-
}
|
|
137
|
+
// Swift: prefer .pbxproj over anything else
|
|
138
|
+
const pbxproj = detectPbxproj();
|
|
139
|
+
if (pbxproj) return pbxproj;
|
|
140
|
+
|
|
141
|
+
if (fs.existsSync('package.json')) return 'package.json';
|
|
142
|
+
if (fs.existsSync('composer.json')) return 'composer.json';
|
|
143
|
+
if (fs.existsSync('VERSION')) return 'VERSION';
|
|
105
144
|
return 'package.json'; // Default
|
|
106
145
|
}
|
|
107
146
|
|
|
108
147
|
/**
|
|
109
148
|
* Infer version file type from filename/extension
|
|
110
149
|
* @param {string} filename - Version file name or path
|
|
111
|
-
* @returns {string} File type: 'json', 'php', or '
|
|
150
|
+
* @returns {string} File type: 'json', 'php', 'txt', or 'xcodeproj'
|
|
112
151
|
*/
|
|
113
152
|
function inferVersionFileType(filename) {
|
|
114
153
|
if (!filename) return 'txt';
|
|
154
|
+
if (filename.endsWith('.pbxproj')) return 'xcodeproj';
|
|
115
155
|
if (filename.endsWith('.json')) return 'json';
|
|
116
156
|
if (filename.endsWith('.php')) return 'php';
|
|
117
157
|
if (filename.endsWith('.css')) return 'php'; // CSS version headers use same pattern as PHP
|
|
@@ -155,7 +195,7 @@ export async function bootstrapProject() {
|
|
|
155
195
|
config.projectType = await configureProjectType(existing);
|
|
156
196
|
|
|
157
197
|
// Step 3: Version source
|
|
158
|
-
config.versionSource = await configureVersionSource(existing);
|
|
198
|
+
config.versionSource = await configureVersionSource(existing, config.projectType);
|
|
159
199
|
|
|
160
200
|
// Step 4: Features selection
|
|
161
201
|
config.features = await chooseFeatures(existing);
|
|
@@ -236,14 +276,15 @@ async function configureProjectType(existing) {
|
|
|
236
276
|
console.log(` ${colors.BCyan('3)')} wordpress-plugin - WordPress plugin with main PHP file versioning`);
|
|
237
277
|
console.log(` ${colors.BCyan('4)')} wordpress-site - Full WordPress installation`);
|
|
238
278
|
console.log(` ${colors.BCyan('5)')} craft - Craft CMS project`);
|
|
279
|
+
console.log(` ${colors.BCyan('6)')} swift - Swift/Xcode app (.pbxproj versioning)`);
|
|
239
280
|
console.log('');
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
const
|
|
243
|
-
|
|
281
|
+
|
|
282
|
+
const types = ['bespoke', 'wordpress-theme', 'wordpress-plugin', 'wordpress-site', 'craft', 'swift'];
|
|
283
|
+
const defaultChoice = types.indexOf(current) + 1 || 1;
|
|
284
|
+
const choice = await getLineInput(`Select project type [1-6, Enter for ${defaultChoice}=${current}]: `);
|
|
285
|
+
|
|
244
286
|
if (!choice) return current;
|
|
245
|
-
|
|
246
|
-
const types = ['bespoke', 'wordpress-theme', 'wordpress-plugin', 'wordpress-site', 'craft'];
|
|
287
|
+
|
|
247
288
|
const index = parseInt(choice) - 1;
|
|
248
289
|
|
|
249
290
|
if (index >= 0 && index < types.length) {
|
|
@@ -258,27 +299,53 @@ async function configureProjectType(existing) {
|
|
|
258
299
|
/**
|
|
259
300
|
* Step 3: Configure version source
|
|
260
301
|
*/
|
|
261
|
-
async function configureVersionSource(existing) {
|
|
302
|
+
async function configureVersionSource(existing, projectType = 'bespoke') {
|
|
262
303
|
console.log('');
|
|
263
304
|
console.log(colors.BCyan('📦 Version Source Configuration'));
|
|
264
305
|
console.log('');
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
const
|
|
268
|
-
|
|
306
|
+
|
|
307
|
+
const isSwift = projectType === 'swift';
|
|
308
|
+
const pbxproj = detectPbxproj();
|
|
309
|
+
|
|
310
|
+
// For Swift, use the found .pbxproj as default; fall back to non-Swift detection otherwise
|
|
311
|
+
const detected = isSwift ? (pbxproj || null) : detectVersionSource();
|
|
312
|
+
// When project type is swift and a pbxproj was found, only keep the existing setting
|
|
313
|
+
// if it's also a .pbxproj (i.e. don't let a stale 'VERSION' or 'package.json' override it)
|
|
314
|
+
const existingIsCompatible = !isSwift || !pbxproj || (existing.MAIASS_VERSION_PRIMARY_FILE || '').endsWith('.pbxproj');
|
|
315
|
+
const current = (existingIsCompatible ? existing.MAIASS_VERSION_PRIMARY_FILE : null) || detected || '';
|
|
316
|
+
|
|
269
317
|
console.log('MAIASS needs to know where your project version is stored.');
|
|
270
318
|
console.log('This file will be updated automatically when you bump versions.');
|
|
271
319
|
console.log('');
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
console.log(
|
|
320
|
+
|
|
321
|
+
if (isSwift) {
|
|
322
|
+
console.log(colors.BCyan('🍎 Swift/Xcode versioning'));
|
|
323
|
+
console.log(colors.Gray(' MAIASS manages MARKETING_VERSION (e.g. 1.2.3) in your .pbxproj.'));
|
|
324
|
+
console.log(colors.Gray(' CURRENT_PROJECT_VERSION (build number) is auto-incremented on each bump.'));
|
|
325
|
+
console.log(colors.Gray(' Both are read by Xcode directly — no Info.plist edits needed.'));
|
|
326
|
+
console.log('');
|
|
327
|
+
if (pbxproj) {
|
|
328
|
+
console.log(`${SYMBOLS.CHECKMARK} Found: ${colors.BGreen(pbxproj)}`);
|
|
329
|
+
} else {
|
|
330
|
+
console.log(`${SYMBOLS.WARNING} ${colors.BYellow('No .xcodeproj found in this directory.')}`);
|
|
331
|
+
console.log(colors.Gray(' Enter the path manually, e.g. MyApp.xcodeproj/project.pbxproj'));
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
if (detected) {
|
|
335
|
+
console.log(`${SYMBOLS.INFO} Detected version file: ${colors.BGreen(detected)}`);
|
|
336
|
+
}
|
|
337
|
+
if (existing.MAIASS_VERSION_PRIMARY_FILE) {
|
|
338
|
+
console.log(`${SYMBOLS.INFO} Current setting: ${colors.BGreen(existing.MAIASS_VERSION_PRIMARY_FILE)}`);
|
|
339
|
+
}
|
|
340
|
+
console.log(colors.Gray('Common options: package.json, composer.json, VERSION, style.css'));
|
|
276
341
|
}
|
|
277
342
|
console.log('');
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
343
|
+
|
|
344
|
+
const prompt = current
|
|
345
|
+
? `Version source file [Enter for ${current}]: `
|
|
346
|
+
: `Version source file: `;
|
|
347
|
+
const file = await getLineInput(prompt);
|
|
348
|
+
|
|
282
349
|
const result = file || current;
|
|
283
350
|
if (file) {
|
|
284
351
|
console.log(`${SYMBOLS.CHECKMARK} Version source set to: ${colors.BGreen(result)}`);
|
package/lib/commit.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// Commit functionality for MAIASS - port of maiass.sh commit behavior
|
|
2
2
|
import { execSync } from 'child_process';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import path from 'path';
|
|
3
6
|
import { log, redact } from './logger.js';
|
|
4
7
|
import { SYMBOLS } from './symbols.js';
|
|
5
8
|
import { getGitInfo, getGitStatus } from './git-info.js';
|
|
@@ -229,7 +232,7 @@ async function createAnonymousSubscriptionIfNeeded() {
|
|
|
229
232
|
|
|
230
233
|
if (stored) {
|
|
231
234
|
log.success(SYMBOLS.CHECKMARK, 'Anonymous subscription created and stored securely');
|
|
232
|
-
log.info(SYMBOLS.INFO, ` API Key:
|
|
235
|
+
log.info(SYMBOLS.INFO, ` API Key: [stored securely]`);
|
|
233
236
|
log.info(SYMBOLS.INFO, ` Credits: ${credits || 'N/A'}`);
|
|
234
237
|
|
|
235
238
|
if (subscriptionId) {
|
|
@@ -823,20 +826,12 @@ async function handleStagedCommit(gitInfo, options = {}) {
|
|
|
823
826
|
if (jiraTicket && finalCommitMessage && !finalCommitMessage.startsWith(jiraTicket)) {
|
|
824
827
|
finalCommitMessage = `${jiraTicket} ${finalCommitMessage}`;
|
|
825
828
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
const fs = (await import('fs')).default;
|
|
829
|
-
const os = (await import('os')).default;
|
|
830
|
-
const path = (await import('path')).default;
|
|
829
|
+
// Write commit message to a temp file on all platforms — avoids shell quoting and injection risks
|
|
830
|
+
{
|
|
831
831
|
const tmpFile = path.join(os.tmpdir(), `maiass-commit-msg-${Date.now()}.txt`);
|
|
832
832
|
fs.writeFileSync(tmpFile, finalCommitMessage, { encoding: 'utf8' });
|
|
833
|
-
|
|
834
|
-
result = executeGitCommand(commitCommand, quietMode);
|
|
833
|
+
result = executeGitCommand(`git commit -F "${tmpFile}"`, quietMode);
|
|
835
834
|
fs.unlinkSync(tmpFile);
|
|
836
|
-
} else {
|
|
837
|
-
// Use echo/pipe for non-Windows
|
|
838
|
-
commitCommand = `echo ${JSON.stringify(commitMessage)} | git commit -F -`;
|
|
839
|
-
result = executeGitCommand(commitCommand, quietMode);
|
|
840
835
|
}
|
|
841
836
|
|
|
842
837
|
if (result === null) {
|
package/lib/devlog.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Development logging utility for MAIASS
|
|
2
2
|
// Node.js equivalent of the devlog.sh integration from maiass.sh
|
|
3
|
-
import {
|
|
3
|
+
import { execFile, execSync } from 'child_process';
|
|
4
4
|
import { existsSync } from 'fs';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import colors from './colors.js';
|
|
@@ -89,20 +89,24 @@ export function logThis(message, options = {}) {
|
|
|
89
89
|
return null;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
//
|
|
93
|
-
const
|
|
92
|
+
// Normalise message for single-line logging (no shell escaping needed — args array is used)
|
|
93
|
+
const normalisedMessage = message.replace(/\n/g, '; ');
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
// Build args array — no shell interpolation, so no injection risk
|
|
96
|
+
let executable, args;
|
|
96
97
|
if (process.platform === 'win32') {
|
|
97
|
-
|
|
98
|
+
executable = 'powershell.exe';
|
|
99
|
+
args = ['-ExecutionPolicy', 'Bypass', '-NonInteractive', '-Command',
|
|
100
|
+
`devlog -s '${normalisedMessage.replace(/'/g, "''")}' '?' '${project}' '${client}' '${jiraTicket}' '${subClient}'`];
|
|
98
101
|
} else {
|
|
99
|
-
|
|
102
|
+
executable = 'devlog.sh';
|
|
103
|
+
args = [normalisedMessage, '?', project, client, jiraTicket, subClient];
|
|
100
104
|
}
|
|
101
105
|
|
|
102
|
-
logger.debug(`Executing devlog
|
|
103
|
-
|
|
106
|
+
logger.debug(`Executing devlog: ${executable} ${args.join(' ')}`);
|
|
107
|
+
|
|
104
108
|
// Execute asynchronously - don't block the main workflow (fire-and-forget)
|
|
105
|
-
|
|
109
|
+
execFile(executable, args, { encoding: 'utf8' }, (error, stdout, stderr) => {
|
|
106
110
|
if (error) {
|
|
107
111
|
|
|
108
112
|
if (process.env.MAIASS_DEBUG === 'true') {
|
|
@@ -113,7 +117,7 @@ export function logThis(message, options = {}) {
|
|
|
113
117
|
|
|
114
118
|
// Only log success confirmation, not the verbose stdout output
|
|
115
119
|
|
|
116
|
-
logger.debug(`Logged to devlog: ${
|
|
120
|
+
logger.debug(`Logged to devlog: ${normalisedMessage}`);
|
|
117
121
|
});
|
|
118
122
|
|
|
119
123
|
// Return immediately (don't wait for devlog.sh to complete)
|
package/lib/secure-storage.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Windows uses encrypted file storage in AppData
|
|
4
4
|
// Compatible with bashmaiass approach but uses NODEMAIASS service names
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { execFileSync, spawnSync } from 'child_process';
|
|
7
7
|
import os from 'os';
|
|
8
8
|
import fs from 'fs';
|
|
9
9
|
import path from 'path';
|
|
@@ -98,8 +98,8 @@ export function storeSecureVariable(varName, varValue) {
|
|
|
98
98
|
|
|
99
99
|
try {
|
|
100
100
|
if (os.platform() === 'darwin') {
|
|
101
|
-
// macOS: Use keychain via security command
|
|
102
|
-
|
|
101
|
+
// macOS: Use keychain via security command — args array avoids shell injection
|
|
102
|
+
execFileSync('security', ['add-generic-password', '-U', '-s', serviceName, '-a', varName, '-w', varValue], {
|
|
103
103
|
stdio: 'pipe'
|
|
104
104
|
});
|
|
105
105
|
} else if (os.platform() === 'win32') {
|
|
@@ -139,12 +139,12 @@ export function storeSecureVariable(varName, varValue) {
|
|
|
139
139
|
logger.debug(`Stored ${varName} in Windows secure storage (encrypted file)`);
|
|
140
140
|
}
|
|
141
141
|
} else {
|
|
142
|
-
// Linux: Use secret-tool if available
|
|
142
|
+
// Linux: Use secret-tool if available — spawnSync with input avoids shell + pipe injection
|
|
143
143
|
try {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
144
|
+
execFileSync('which', ['secret-tool'], { stdio: 'pipe' });
|
|
145
|
+
spawnSync('secret-tool', ['store', '--label', `NODEMAIASS ${varName} (${serviceName})`, 'service', serviceName, 'key', varName], {
|
|
146
|
+
input: varValue,
|
|
147
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
148
148
|
});
|
|
149
149
|
} catch (error) {
|
|
150
150
|
if (debugMode) {
|
|
@@ -183,8 +183,8 @@ export function retrieveSecureVariable(varName) {
|
|
|
183
183
|
let value = null;
|
|
184
184
|
|
|
185
185
|
if (os.platform() === 'darwin') {
|
|
186
|
-
// macOS: Use keychain via security command
|
|
187
|
-
value =
|
|
186
|
+
// macOS: Use keychain via security command — args array avoids shell injection
|
|
187
|
+
value = execFileSync('security', ['find-generic-password', '-s', serviceName, '-a', varName, '-w'], {
|
|
188
188
|
stdio: 'pipe',
|
|
189
189
|
encoding: 'utf8'
|
|
190
190
|
}).trim();
|
|
@@ -216,10 +216,10 @@ export function retrieveSecureVariable(varName) {
|
|
|
216
216
|
return null;
|
|
217
217
|
}
|
|
218
218
|
} else {
|
|
219
|
-
// Linux: Use secret-tool if available
|
|
219
|
+
// Linux: Use secret-tool if available — args array avoids shell injection
|
|
220
220
|
try {
|
|
221
|
-
|
|
222
|
-
value =
|
|
221
|
+
execFileSync('which', ['secret-tool'], { stdio: 'pipe' });
|
|
222
|
+
value = execFileSync('secret-tool', ['lookup', 'service', serviceName, 'key', varName], {
|
|
223
223
|
stdio: 'pipe',
|
|
224
224
|
encoding: 'utf8'
|
|
225
225
|
}).trim();
|
|
@@ -259,8 +259,8 @@ export function removeSecureVariable(varName) {
|
|
|
259
259
|
|
|
260
260
|
try {
|
|
261
261
|
if (os.platform() === 'darwin') {
|
|
262
|
-
// macOS: Use keychain via security command
|
|
263
|
-
|
|
262
|
+
// macOS: Use keychain via security command — args array avoids shell injection
|
|
263
|
+
execFileSync('security', ['delete-generic-password', '-s', serviceName, '-a', varName], {
|
|
264
264
|
stdio: 'pipe'
|
|
265
265
|
});
|
|
266
266
|
} else if (os.platform() === 'win32') {
|
|
@@ -302,13 +302,12 @@ export function removeSecureVariable(varName) {
|
|
|
302
302
|
return false;
|
|
303
303
|
}
|
|
304
304
|
} else {
|
|
305
|
-
// Linux: secret-tool doesn't have direct delete,
|
|
305
|
+
// Linux: secret-tool doesn't have direct delete, so store empty value — spawnSync with input avoids shell injection
|
|
306
306
|
try {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
stdio: 'pipe',
|
|
311
|
-
shell: true
|
|
307
|
+
execFileSync('which', ['secret-tool'], { stdio: 'pipe' });
|
|
308
|
+
spawnSync('secret-tool', ['store', '--label', `NODEMAIASS ${varName} (${serviceName})`, 'service', serviceName, 'key', varName], {
|
|
309
|
+
input: '',
|
|
310
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
312
311
|
});
|
|
313
312
|
} catch (error) {
|
|
314
313
|
if (debugMode) {
|
|
@@ -391,14 +390,14 @@ export function loadSecureVariables() {
|
|
|
391
390
|
export function isSecureStorageAvailable() {
|
|
392
391
|
try {
|
|
393
392
|
if (os.platform() === 'darwin') {
|
|
394
|
-
|
|
393
|
+
execFileSync('which', ['security'], { stdio: 'pipe' });
|
|
395
394
|
return true;
|
|
396
395
|
} else if (os.platform() === 'win32') {
|
|
397
396
|
// Windows: Always available (uses encrypted file storage)
|
|
398
397
|
return true;
|
|
399
398
|
} else {
|
|
400
399
|
// Linux: Check for secret-tool
|
|
401
|
-
|
|
400
|
+
execFileSync('which', ['secret-tool'], { stdio: 'pipe' });
|
|
402
401
|
return true;
|
|
403
402
|
}
|
|
404
403
|
} catch (error) {
|
package/lib/token-validator.js
CHANGED
|
@@ -152,9 +152,8 @@ export function displayTokenValidation() {
|
|
|
152
152
|
console.log(`[DEBUG] ${display.symbol} ${tokenConfig.description} (${tokenConfig.name}): ${display.color(display.status)}`);
|
|
153
153
|
|
|
154
154
|
if (validation && validation.valid) {
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
console.log(colors.Gray(`[DEBUG] Preview: ${maskedToken}`));
|
|
155
|
+
// Confirm token is present without logging any part of its value
|
|
156
|
+
console.log(colors.Gray(`[DEBUG] Status: [present]`));
|
|
158
157
|
}
|
|
159
158
|
});
|
|
160
159
|
});
|
|
@@ -165,12 +164,12 @@ export function displayTokenValidation() {
|
|
|
165
164
|
const tokenValue = process.env[tokenConfig.name];
|
|
166
165
|
const validation = tokenValue ? validateTokenValue(tokenValue, tokenConfig) : null;
|
|
167
166
|
const display = getValidationDisplay(validation);
|
|
168
|
-
|
|
167
|
+
|
|
169
168
|
console.log(`[DEBUG] ${display.symbol} ${tokenConfig.description} (${tokenConfig.name}): ${display.color(display.status)}`);
|
|
170
|
-
|
|
169
|
+
|
|
171
170
|
if (validation && validation.valid) {
|
|
172
|
-
|
|
173
|
-
console.log(colors.Gray(`[DEBUG]
|
|
171
|
+
// Confirm token is present without logging any part of its value
|
|
172
|
+
console.log(colors.Gray(`[DEBUG] Status: [present]`));
|
|
174
173
|
}
|
|
175
174
|
});
|
|
176
175
|
|
package/lib/version-manager.js
CHANGED
|
@@ -84,6 +84,28 @@ const VERSION_FILE_TYPES = {
|
|
|
84
84
|
return content.replace(/^\d+\.\d+\.\d+/, newVersion);
|
|
85
85
|
}
|
|
86
86
|
},
|
|
87
|
+
xcodeproj: {
|
|
88
|
+
extensions: ['.pbxproj'],
|
|
89
|
+
detect: (content) => /MARKETING_VERSION\s*=\s*\d+\.\d+(?:\.\d+)?\s*;/.test(content),
|
|
90
|
+
extract: (content) => {
|
|
91
|
+
// Match 1.0 or 1.0.0 style versions
|
|
92
|
+
const match = content.match(/MARKETING_VERSION\s*=\s*(\d+\.\d+(?:\.\d+)?)\s*;/);
|
|
93
|
+
return match ? match[1] : null;
|
|
94
|
+
},
|
|
95
|
+
update: (content, newVersion) => {
|
|
96
|
+
// Bump MARKETING_VERSION everywhere it appears (all targets)
|
|
97
|
+
content = content.replace(
|
|
98
|
+
/(MARKETING_VERSION\s*=\s*)\d+\.\d+(?:\.\d+)?(\s*;)/g,
|
|
99
|
+
`$1${newVersion}$2`
|
|
100
|
+
);
|
|
101
|
+
// Auto-increment CURRENT_PROJECT_VERSION (build number)
|
|
102
|
+
content = content.replace(
|
|
103
|
+
/(CURRENT_PROJECT_VERSION\s*=\s*)(\d+)(\s*;)/g,
|
|
104
|
+
(_, prefix, num, suffix) => `${prefix}${parseInt(num, 10) + 1}${suffix}`
|
|
105
|
+
);
|
|
106
|
+
return content;
|
|
107
|
+
}
|
|
108
|
+
},
|
|
87
109
|
php: {
|
|
88
110
|
extensions: ['.php','pattern'],
|
|
89
111
|
detect: (content) => {
|
|
@@ -125,13 +147,14 @@ const VERSION_FILE_TYPES = {
|
|
|
125
147
|
export function parseVersion(version) {
|
|
126
148
|
if (!version) return null;
|
|
127
149
|
|
|
128
|
-
|
|
150
|
+
// Accept x.y.z or x.y (treat x.y as x.y.0)
|
|
151
|
+
const match = version.match(/^(\d+)\.(\d+)(?:\.(\d+))?(?:-(.+))?$/);
|
|
129
152
|
if (!match) return null;
|
|
130
|
-
|
|
153
|
+
|
|
131
154
|
return {
|
|
132
155
|
major: parseInt(match[1], 10),
|
|
133
156
|
minor: parseInt(match[2], 10),
|
|
134
|
-
patch: parseInt(match[3], 10),
|
|
157
|
+
patch: match[3] !== undefined ? parseInt(match[3], 10) : 0,
|
|
135
158
|
prerelease: match[4] || null,
|
|
136
159
|
raw: version
|
|
137
160
|
};
|
|
@@ -318,7 +341,7 @@ function updateThemeStyleVersion(filePath, newVersion) {
|
|
|
318
341
|
logger.debug(` style.css written successfully`);
|
|
319
342
|
return true;
|
|
320
343
|
} catch (error) {
|
|
321
|
-
console.error(colors.Red(`${SYMBOLS.CROSS} Error updating ${filePath}: ${error.message}`));
|
|
344
|
+
console.error(colors.Red(`${SYMBOLS.CROSS} Error updating ${filePath}: ${error.message}`)); // codeql[js/clear-text-logging] -- filePath and error.message are file I/O data, not credentials
|
|
322
345
|
return false;
|
|
323
346
|
}
|
|
324
347
|
}
|
|
@@ -381,7 +404,7 @@ function updatePhpVersionConstant(filePath, constantName, newVersion) {
|
|
|
381
404
|
if (definePattern.test(content)) {
|
|
382
405
|
// Replace existing define
|
|
383
406
|
content = content.replace(definePattern, newDefine);
|
|
384
|
-
console.log(colors.BGreen(`${SYMBOLS.CHECKMARK} Updated ${constantName} in ${path.basename(filePath)}`));
|
|
407
|
+
console.log(colors.BGreen(`${SYMBOLS.CHECKMARK} Updated ${constantName} in ${path.basename(filePath)}`)); // codeql[js/clear-text-logging] -- constantName and filePath are version metadata, not credentials
|
|
385
408
|
} else {
|
|
386
409
|
// Add new define after opening PHP tag
|
|
387
410
|
const phpOpenTag = /<\?php/;
|
|
@@ -390,9 +413,9 @@ function updatePhpVersionConstant(filePath, constantName, newVersion) {
|
|
|
390
413
|
if (phpOpenTag.test(content)) {
|
|
391
414
|
logger.debug(` Found PHP opening tag, adding new define`);
|
|
392
415
|
content = content.replace(phpOpenTag, `<?php\n\n${newDefine}`);
|
|
393
|
-
console.log(colors.BGreen(`${SYMBOLS.CHECKMARK} Added ${constantName} to ${path.basename(filePath)}`));
|
|
416
|
+
console.log(colors.BGreen(`${SYMBOLS.CHECKMARK} Added ${constantName} to ${path.basename(filePath)}`)); // codeql[js/clear-text-logging] -- constantName and filePath are version metadata, not credentials
|
|
394
417
|
} else {
|
|
395
|
-
console.log(colors.BYellow(`${SYMBOLS.WARNING} Could not find PHP opening tag in ${path.basename(filePath)}`));
|
|
418
|
+
console.log(colors.BYellow(`${SYMBOLS.WARNING} Could not find PHP opening tag in ${path.basename(filePath)}`)); // codeql[js/clear-text-logging] -- filePath is a file path, not a credential
|
|
396
419
|
return false;
|
|
397
420
|
}
|
|
398
421
|
}
|
|
@@ -401,7 +424,7 @@ function updatePhpVersionConstant(filePath, constantName, newVersion) {
|
|
|
401
424
|
logger.debug(` File written successfully`);
|
|
402
425
|
return true;
|
|
403
426
|
} catch (error) {
|
|
404
|
-
console.error(colors.Red(`${SYMBOLS.CROSS} Error updating ${filePath}: ${error.message}`));
|
|
427
|
+
console.error(colors.Red(`${SYMBOLS.CROSS} Error updating ${filePath}: ${error.message}`)); // codeql[js/clear-text-logging] -- filePath and error.message are file I/O data, not credentials
|
|
405
428
|
return false;
|
|
406
429
|
}
|
|
407
430
|
}
|
|
@@ -444,7 +467,7 @@ function updateWordPressVersions(newVersion, projectPath = process.cwd()) {
|
|
|
444
467
|
success = false;
|
|
445
468
|
}
|
|
446
469
|
} else {
|
|
447
|
-
console.log(colors.BYellow(`${SYMBOLS.WARNING} Could not find main plugin file in ${pluginPath}`));
|
|
470
|
+
console.log(colors.BYellow(`${SYMBOLS.WARNING} Could not find main plugin file in ${pluginPath}`)); // codeql[js/clear-text-logging] -- pluginPath is a file path, not a credential
|
|
448
471
|
}
|
|
449
472
|
}
|
|
450
473
|
|
|
@@ -492,7 +515,7 @@ function updateWordPressVersions(newVersion, projectPath = process.cwd()) {
|
|
|
492
515
|
success = false;
|
|
493
516
|
}
|
|
494
517
|
} else {
|
|
495
|
-
console.log(colors.BYellow(`${SYMBOLS.WARNING} Could not find functions.php in ${themePath}`));
|
|
518
|
+
console.log(colors.BYellow(`${SYMBOLS.WARNING} Could not find functions.php in ${themePath}`)); // codeql[js/clear-text-logging] -- themePath is a file path, not a credential
|
|
496
519
|
if (functionsFile) {
|
|
497
520
|
logger.debug(` Expected functions.php at: ${functionsFile}`);
|
|
498
521
|
}
|
|
@@ -543,7 +566,11 @@ function extractVersionByType(content, type, lineStart) {
|
|
|
543
566
|
if (type === 'php' || type === 'pattern') {
|
|
544
567
|
return VERSION_FILE_TYPES.php.extract(content);
|
|
545
568
|
}
|
|
546
|
-
|
|
569
|
+
|
|
570
|
+
if (type === 'xcodeproj') {
|
|
571
|
+
return VERSION_FILE_TYPES.xcodeproj.extract(content);
|
|
572
|
+
}
|
|
573
|
+
|
|
547
574
|
// Unknown type - try generic version extraction
|
|
548
575
|
const match = content.match(/(\d+\.\d+\.\d+)/);
|
|
549
576
|
return match ? match[1] : null;
|
|
@@ -564,7 +591,10 @@ export function detectVersionFiles(projectPath = process.cwd()) {
|
|
|
564
591
|
const primaryFileRaw = process.env.MAIASS_VERSION_PRIMARY_FILE;
|
|
565
592
|
const primaryFile = primaryFileRaw ? primaryFileRaw.split('\\').join('/') : primaryFileRaw;
|
|
566
593
|
const primaryTypeEnv = process.env.MAIASS_VERSION_PRIMARY_TYPE;
|
|
567
|
-
const primaryType = primaryTypeEnv || (
|
|
594
|
+
const primaryType = primaryTypeEnv || (
|
|
595
|
+
primaryFile && primaryFile.endsWith('.pbxproj') ? 'xcodeproj' :
|
|
596
|
+
primaryFile && primaryFile.endsWith('.json') ? 'json' : 'txt'
|
|
597
|
+
);
|
|
568
598
|
const primaryLineStart = process.env.MAIASS_VERSION_PRIMARY_LINE_START || '';
|
|
569
599
|
|
|
570
600
|
if (primaryFile) {
|
|
@@ -602,9 +632,55 @@ export function detectVersionFiles(projectPath = process.cwd()) {
|
|
|
602
632
|
}
|
|
603
633
|
|
|
604
634
|
// Fallback: scan common version file patterns in project root
|
|
635
|
+
// Check for Xcode project first — .pbxproj lives inside *.xcodeproj bundle.
|
|
636
|
+
// Search root and up to 2 levels of subdirectories (nested project structures).
|
|
637
|
+
{
|
|
638
|
+
const SKIP = new Set(['node_modules', '.git', 'build', 'dist', 'Pods', 'DerivedData']);
|
|
639
|
+
|
|
640
|
+
function findPbxproj(dir, depth = 0) {
|
|
641
|
+
try {
|
|
642
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
643
|
+
for (const entry of entries) {
|
|
644
|
+
if (entry.isDirectory() && entry.name.endsWith('.xcodeproj')) {
|
|
645
|
+
const pbxprojPath = path.join(dir, entry.name, 'project.pbxproj');
|
|
646
|
+
if (fs.existsSync(pbxprojPath)) return pbxprojPath;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (depth < 2) {
|
|
650
|
+
for (const entry of entries) {
|
|
651
|
+
if (entry.isDirectory() && !SKIP.has(entry.name) && !entry.name.startsWith('.')) {
|
|
652
|
+
const found = findPbxproj(path.join(dir, entry.name), depth + 1);
|
|
653
|
+
if (found) return found;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
} catch {}
|
|
658
|
+
return null;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const pbxprojPath = findPbxproj(projectPath);
|
|
662
|
+
if (pbxprojPath) {
|
|
663
|
+
try {
|
|
664
|
+
const content = fs.readFileSync(pbxprojPath, 'utf8');
|
|
665
|
+
const version = VERSION_FILE_TYPES.xcodeproj.extract(content);
|
|
666
|
+
if (version) {
|
|
667
|
+
versionFiles.push({
|
|
668
|
+
path: pbxprojPath,
|
|
669
|
+
filename: path.relative(projectPath, pbxprojPath),
|
|
670
|
+
type: 'xcodeproj',
|
|
671
|
+
currentVersion: version,
|
|
672
|
+
content,
|
|
673
|
+
isPrimary: true
|
|
674
|
+
});
|
|
675
|
+
return versionFiles; // .pbxproj is authoritative for Swift projects
|
|
676
|
+
}
|
|
677
|
+
} catch {}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
605
681
|
const filesToCheck = [
|
|
606
682
|
'package.json',
|
|
607
|
-
'composer.json',
|
|
683
|
+
'composer.json',
|
|
608
684
|
'VERSION',
|
|
609
685
|
'version.txt',
|
|
610
686
|
'style.css',
|