maiass 5.10.6 → 5.12.2
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/bootstrap.js +3 -3
- package/lib/config-command.js +1 -1
- package/lib/config-manager.js +1 -1
- package/lib/maiass-pipeline.js +110 -11
- package/lib/maiass-variables.js +10 -4
- package/lib/pull-request.js +140 -0
- package/lib/repo-provider.js +98 -0
- package/maiass.mjs +28 -8
- package/package.json +1 -1
package/lib/bootstrap.js
CHANGED
|
@@ -583,9 +583,9 @@ MAIASS_STAGINGBRANCH=${config.branches.staging}
|
|
|
583
583
|
#MAIASS_BITBUCKET_WORKSPACE=
|
|
584
584
|
#MAIASS_BITBUCKET_REPO_SLUG=
|
|
585
585
|
|
|
586
|
-
# Pull Request Configuration
|
|
587
|
-
#
|
|
588
|
-
#
|
|
586
|
+
# Pull Request Configuration — when 'on', open PR URL instead of direct merge.
|
|
587
|
+
# Also auto-engaged if a direct push to develop is rejected (e.g. branch protection).
|
|
588
|
+
#MAIASS_DEVELOP_PULLREQUESTS=off
|
|
589
589
|
|
|
590
590
|
# Logging Configuration
|
|
591
591
|
#MAIASS_LOGGING=true
|
package/lib/config-command.js
CHANGED
|
@@ -69,7 +69,7 @@ function displayConfig(config, options = {}) {
|
|
|
69
69
|
'AI Integration': ['MAIASS_AI_MODE', 'MAIASS_AI_TOKEN', 'MAIASS_AI_MODEL', 'MAIASS_AI_TEMPERATURE', 'MAIASS_AI_HOST', 'MAIASS_AI_MAX_CHARACTERS', 'MAIASS_AI_COMMIT_MESSAGE_STYLE'],
|
|
70
70
|
'Git Branches': ['MAIASS_DEVELOPBRANCH', 'MAIASS_STAGINGBRANCH', 'MAIASS_MAINBRANCH'],
|
|
71
71
|
'Repository Settings': ['MAIASS_REPO_TYPE', 'MAIASS_GITHUB_OWNER', 'MAIASS_GITHUB_REPO', 'MAIASS_BITBUCKET_WORKSPACE', 'MAIASS_BITBUCKET_REPO_SLUG'],
|
|
72
|
-
'Pull Requests': ['
|
|
72
|
+
'Pull Requests': ['MAIASS_DEVELOP_PULLREQUESTS'],
|
|
73
73
|
'Version Management': ['MAIASS_VERSION_PATH', 'MAIASS_VERSION_PRIMARY_FILE', 'MAIASS_VERSION_PRIMARY_TYPE', 'MAIASS_VERSION_PRIMARY_LINE_START', 'MAIASS_VERSION_SECONDARY_FILES'],
|
|
74
74
|
'Changelog': ['MAIASS_CHANGELOG_PATH', 'MAIASS_CHANGELOG_NAME', 'MAIASS_CHANGELOG_INTERNAL_NAME']
|
|
75
75
|
};
|
package/lib/config-manager.js
CHANGED
|
@@ -115,7 +115,7 @@ export function writeConfig(configPath, config, options = {}) {
|
|
|
115
115
|
'AI': ['MAIASS_AI_MODE', 'MAIASS_AI_TOKEN', 'MAIASS_AI_MODEL', 'MAIASS_AI_TEMPERATURE', 'MAIASS_AI_ENDPOINT', 'MAIASS_AI_MAX_CHARACTERS', 'MAIASS_AI_COMMIT_MESSAGE_STYLE'],
|
|
116
116
|
'Branches': ['MAIASS_DEVELOPBRANCH', 'MAIASS_STAGINGBRANCH', 'MAIASS_MAINBRANCH'],
|
|
117
117
|
'Repository': ['MAIASS_REPO_TYPE', 'MAIASS_GITHUB_OWNER', 'MAIASS_GITHUB_REPO', 'MAIASS_BITBUCKET_WORKSPACE', 'MAIASS_BITBUCKET_REPO_SLUG'],
|
|
118
|
-
'Pull Requests': ['
|
|
118
|
+
'Pull Requests': ['MAIASS_DEVELOP_PULLREQUESTS'],
|
|
119
119
|
'Versioning': ['MAIASS_VERSION_PATH', 'MAIASS_VERSION_PRIMARY_FILE', 'MAIASS_VERSION_PRIMARY_TYPE', 'MAIASS_VERSION_PRIMARY_LINE_START', 'MAIASS_VERSION_SECONDARY_FILES'],
|
|
120
120
|
'Changelog': ['MAIASS_CHANGELOG_PATH', 'MAIASS_CHANGELOG_NAME', 'MAIASS_CHANGELOG_INTERNAL_NAME'],
|
|
121
121
|
'Other': []
|
package/lib/maiass-pipeline.js
CHANGED
|
@@ -16,6 +16,7 @@ import { getSingleCharInput, getLineInput } from './input-utils.js';
|
|
|
16
16
|
import { updateChangelog as updateChangelogNew } from './changelog.js';
|
|
17
17
|
import { displayHeader } from './header.js';
|
|
18
18
|
import { checkForUpdates } from './update-check.js';
|
|
19
|
+
import { createPullRequest } from './pull-request.js';
|
|
19
20
|
import { execSync } from 'child_process';
|
|
20
21
|
import path from 'path';
|
|
21
22
|
import fs from 'fs';
|
|
@@ -317,9 +318,34 @@ async function handleMergeToDevelop(branchInfo, commitResult, options = {}) {
|
|
|
317
318
|
return { success: true, simplified: true };
|
|
318
319
|
}
|
|
319
320
|
|
|
321
|
+
// autoMerge (-a): skip prompts and proceed with merge. If direct merge is
|
|
322
|
+
// blocked, fall back to opening a PR in the browser (auto mode = "do whatever
|
|
323
|
+
// is needed without asking").
|
|
324
|
+
const autoMerge = process.env.MAIASS_AUTO_MERGE_TO_DEVELOP === 'true';
|
|
325
|
+
|
|
326
|
+
// PR flow: if MAIASS_DEVELOP_PULLREQUESTS=on, push source and open a PR URL
|
|
327
|
+
// instead of doing a direct merge. The pipeline ends here — the user merges
|
|
328
|
+
// the PR on the platform, then re-runs maiass on develop for the version bump.
|
|
329
|
+
if (process.env.MAIASS_DEVELOP_PULLREQUESTS === 'on') {
|
|
330
|
+
log.blue(SYMBOLS.INFO, `MAIASS_DEVELOP_PULLREQUESTS=on — opening PR instead of direct merge`);
|
|
331
|
+
const prResult = createPullRequest({
|
|
332
|
+
source: currentBranch,
|
|
333
|
+
target: developBranch,
|
|
334
|
+
title: `Merge ${currentBranch} into ${developBranch}`
|
|
335
|
+
});
|
|
336
|
+
if (!prResult.success) {
|
|
337
|
+
log.error(SYMBOLS.CROSS, `Failed to create pull request: ${prResult.error}`);
|
|
338
|
+
return { success: false, error: prResult.error };
|
|
339
|
+
}
|
|
340
|
+
log.success(SYMBOLS.CHECKMARK, `Pull request opened in browser`);
|
|
341
|
+
log.blue(SYMBOLS.INFO, `${prResult.url}`);
|
|
342
|
+
log.blue(SYMBOLS.INFO, `After merging the PR, re-run maiass on ${developBranch} to bump the version.`);
|
|
343
|
+
return { success: true, pullRequest: true, url: prResult.url };
|
|
344
|
+
}
|
|
345
|
+
|
|
320
346
|
// Determine tagging strategy for this version bump
|
|
321
347
|
const taggingDecision = shouldTagRelease(versionBump, tag);
|
|
322
|
-
|
|
348
|
+
|
|
323
349
|
// Prompt user for merge (and tagging if needed)
|
|
324
350
|
if (!force) {
|
|
325
351
|
log.blue(SYMBOLS.INFO, `Ready to merge changes to ${developBranch} branch`);
|
|
@@ -401,14 +427,58 @@ async function handleMergeToDevelop(branchInfo, commitResult, options = {}) {
|
|
|
401
427
|
}
|
|
402
428
|
|
|
403
429
|
log.success(SYMBOLS.CHECKMARK, `Successfully merged ${currentBranch} into ${developBranch}`);
|
|
404
|
-
|
|
430
|
+
|
|
431
|
+
// Detect blocked direct push (e.g. branch protection requires PR).
|
|
432
|
+
// Run a dry-run push — if it fails, the eventual push will fail too. Only do
|
|
433
|
+
// this when the remote actually exists; otherwise there's nothing to dry-run.
|
|
434
|
+
if (remoteExists()) {
|
|
435
|
+
const dryRun = executeGitCommand(`git push --dry-run origin ${developBranch}`, true);
|
|
436
|
+
if (!dryRun.success) {
|
|
437
|
+
log.warning(SYMBOLS.WARNING, `Direct push to ${developBranch} appears blocked (likely branch protection)`);
|
|
438
|
+
|
|
439
|
+
// Decide whether to fall back to PR flow:
|
|
440
|
+
// - autoMerge (-am): yes, automatically — that's exactly what -am promises
|
|
441
|
+
// - interactive: prompt the user
|
|
442
|
+
let useFallback = autoMerge;
|
|
443
|
+
if (!autoMerge) {
|
|
444
|
+
const reply = await getSingleCharInput(`Open a pull request instead? [Y/n] `);
|
|
445
|
+
useFallback = reply !== 'n';
|
|
446
|
+
} else {
|
|
447
|
+
log.info(SYMBOLS.INFO, '-am mode — falling back to PR flow');
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (useFallback) {
|
|
451
|
+
// Roll back the local merge so the working copy is back to clean develop,
|
|
452
|
+
// then return to the original branch so we push the right ref for the PR
|
|
453
|
+
executeGitCommand(`git reset --hard HEAD~1`);
|
|
454
|
+
await switchToBranch(originalBranch || currentBranch);
|
|
455
|
+
|
|
456
|
+
const prResult = createPullRequest({
|
|
457
|
+
source: currentBranch,
|
|
458
|
+
target: developBranch,
|
|
459
|
+
title: `Merge ${currentBranch} into ${developBranch}`
|
|
460
|
+
});
|
|
461
|
+
if (!prResult.success) {
|
|
462
|
+
log.error(SYMBOLS.CROSS, `Failed to create pull request: ${prResult.error}`);
|
|
463
|
+
return { success: false, error: prResult.error };
|
|
464
|
+
}
|
|
465
|
+
log.success(SYMBOLS.CHECKMARK, `Pull request opened in browser`);
|
|
466
|
+
log.blue(SYMBOLS.INFO, `${prResult.url}`);
|
|
467
|
+
log.blue(SYMBOLS.INFO, `After merging the PR, re-run maiass on ${developBranch} to bump the version.`);
|
|
468
|
+
return { success: true, pullRequest: true, url: prResult.url };
|
|
469
|
+
}
|
|
470
|
+
// User declined PR fallback — continue and let the later push fail with a clear error
|
|
471
|
+
log.warning(SYMBOLS.WARNING, `Continuing with direct merge — push may fail at version bump stage`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
405
475
|
// Log the merge to devlog.sh (equivalent to logthis in maiass.sh)
|
|
406
476
|
logMerge(currentBranch, developBranch, originalGitInfo, 'Merged');
|
|
407
|
-
|
|
408
|
-
return {
|
|
409
|
-
success: true,
|
|
410
|
-
merged: true,
|
|
411
|
-
taggingDecision: taggingDecision
|
|
477
|
+
|
|
478
|
+
return {
|
|
479
|
+
success: true,
|
|
480
|
+
merged: true,
|
|
481
|
+
taggingDecision: taggingDecision
|
|
412
482
|
};
|
|
413
483
|
}
|
|
414
484
|
|
|
@@ -458,12 +528,19 @@ async function handleVersionManagement(branchInfo, mergeResult, options = {}) {
|
|
|
458
528
|
|
|
459
529
|
logger.header(SYMBOLS.INFO, 'Version Management Phase');
|
|
460
530
|
|
|
461
|
-
// Must be on develop branch for version management — check before reading version files
|
|
531
|
+
// Must be on develop branch for version management — check before reading version files.
|
|
532
|
+
// If the user explicitly asked for a bump (passed major/minor/patch), this is an error.
|
|
533
|
+
// Otherwise it's a graceful skip — they may have run maiass on a feature branch without
|
|
534
|
+
// intending a version bump, or the merge was deferred to a PR.
|
|
462
535
|
const currentBranch = getCurrentBranch();
|
|
463
536
|
if (currentBranch !== developBranch) {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
537
|
+
if (versionBumpExplicit) {
|
|
538
|
+
console.error(colors.Red(`${SYMBOLS.CROSS} Version management must be done on ${developBranch} branch`));
|
|
539
|
+
console.error(colors.Red(`${SYMBOLS.CROSS} Current branch: ${currentBranch}`));
|
|
540
|
+
return { success: false, error: `Not on ${developBranch} branch` };
|
|
541
|
+
}
|
|
542
|
+
log.blue(SYMBOLS.INFO, `Skipping version management — on ${currentBranch}, not ${developBranch}`);
|
|
543
|
+
return { success: true, skipped: true, reason: 'not on develop' };
|
|
467
544
|
}
|
|
468
545
|
|
|
469
546
|
// Pull latest develop from remote before reading version files.
|
|
@@ -710,6 +787,19 @@ async function handleReleaseBranchWorkflow(newVersion, versionInfo, developBranc
|
|
|
710
787
|
}
|
|
711
788
|
|
|
712
789
|
logger.success(SYMBOLS.CHECKMARK, `Release workflow completed: ${versionInfo.current} → ${newVersion}`);
|
|
790
|
+
|
|
791
|
+
// Return to the user's original branch unless they started on develop or
|
|
792
|
+
// a release branch. Mirrors handleSimpleVersionBump so the user isn't left
|
|
793
|
+
// stranded on develop after a release runs from a feature branch.
|
|
794
|
+
const originalBranch = originalGitInfo?.currentBranch;
|
|
795
|
+
if (originalBranch && originalBranch !== developBranch && !originalBranch.startsWith('release/')) {
|
|
796
|
+
logger.info(SYMBOLS.INFO, `Returning to original branch: ${originalBranch}`);
|
|
797
|
+
const switched = await switchToBranch(originalBranch);
|
|
798
|
+
if (!switched) {
|
|
799
|
+
logger.warning(SYMBOLS.WARNING, `Failed to switch back to ${originalBranch}`);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
713
803
|
return {
|
|
714
804
|
success: true,
|
|
715
805
|
version: newVersion,
|
|
@@ -825,6 +915,15 @@ export async function runMaiassPipeline(options = {}) {
|
|
|
825
915
|
logger.info(SYMBOLS.INFO, 'Thank you for using MAIASS!');
|
|
826
916
|
return { success: true, cancelled: true, phase: 'merge-cancelled' };
|
|
827
917
|
}
|
|
918
|
+
|
|
919
|
+
// If PR flow was used, the merge will happen on the platform — stop the
|
|
920
|
+
// pipeline here. Version management will run on the next maiass invocation
|
|
921
|
+
// after the user merges the PR.
|
|
922
|
+
if (mergeResult.pullRequest) {
|
|
923
|
+
console.log();
|
|
924
|
+
logger.info(SYMBOLS.INFO, 'Thank you for using MAIASS!');
|
|
925
|
+
return { success: true, pullRequest: true, url: mergeResult.url };
|
|
926
|
+
}
|
|
828
927
|
|
|
829
928
|
console.log('');
|
|
830
929
|
|
package/lib/maiass-variables.js
CHANGED
|
@@ -6,8 +6,14 @@ import path from 'path';
|
|
|
6
6
|
export const MAIASS_VARIABLES = {
|
|
7
7
|
// Core system variables
|
|
8
8
|
'MAIASS_DEBUG': { default: 'false', description: 'Enable debug mode' },
|
|
9
|
-
'MAIASS_AUTOPUSH_COMMITS': { default: 'false', description: 'Automatically push commits' },
|
|
10
9
|
'MAIASS_BRAND': { default: 'MAIASS', description: 'Brand name for display' },
|
|
10
|
+
|
|
11
|
+
// Auto-yes flags — set by -a (all four) or -ac (first three only).
|
|
12
|
+
// Setting any of these in .env.maiass makes that prompt auto-approve permanently.
|
|
13
|
+
'MAIASS_AUTO_STAGE_UNSTAGED': { default: 'false', description: 'Auto-stage unstaged changes during commit phase' },
|
|
14
|
+
'MAIASS_AUTO_PUSH_COMMITS': { default: 'false', description: 'Auto-push commits to origin' },
|
|
15
|
+
'MAIASS_AUTO_APPROVE_AI_SUGGESTIONS': { default: 'false', description: 'Auto-approve AI commit message suggestions' },
|
|
16
|
+
'MAIASS_AUTO_MERGE_TO_DEVELOP': { default: 'false', description: 'Auto-merge feature branch into develop without prompting' },
|
|
11
17
|
'MAIASS_VERBOSITY': { default: 'brief', description: 'Verbosity level (brief/normal/verbose)' },
|
|
12
18
|
'MAIASS_LOGGING': { default: 'false', description: 'Enable logging to file' },
|
|
13
19
|
'MAIASS_LOG_FILE': { default: 'maiass.log', description: 'Log file name' },
|
|
@@ -46,9 +52,9 @@ export const MAIASS_VARIABLES = {
|
|
|
46
52
|
// Release configuration
|
|
47
53
|
'MAIASS_AUTO_TAG_RELEASES': { default: 'true', description: 'Automatically tag releases without prompting' },
|
|
48
54
|
|
|
49
|
-
// Pull request configuration
|
|
50
|
-
|
|
51
|
-
'
|
|
55
|
+
// Pull request configuration — when 'on', push source and open PR URL instead of direct merge.
|
|
56
|
+
// Also auto-engaged when a direct push to develop is rejected (e.g. branch protection).
|
|
57
|
+
'MAIASS_DEVELOP_PULLREQUESTS': { default: 'off', description: 'Open PR for merges into develop instead of direct merge' },
|
|
52
58
|
|
|
53
59
|
// Repository provider configuration
|
|
54
60
|
'MAIASS_BITBUCKET_WORKSPACE': { default: '', description: 'Bitbucket workspace name' },
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { execSync, spawn } from 'child_process';
|
|
2
|
+
import { detectRepoProvider } from './repo-provider.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Build a provider-specific URL that opens the PR creation form pre-filled
|
|
6
|
+
* with source/target branches and a title.
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} args
|
|
9
|
+
* @param {string} args.provider - 'github' | 'bitbucket' | 'gitlab'
|
|
10
|
+
* @param {string} args.owner - Repo owner / workspace
|
|
11
|
+
* @param {string} args.repo - Repo name / slug
|
|
12
|
+
* @param {string} args.source - Source branch (the changes)
|
|
13
|
+
* @param {string} args.target - Target branch (where they're merging into)
|
|
14
|
+
* @param {string} [args.title] - Optional PR title
|
|
15
|
+
* @returns {string|null} The URL, or null if provider unsupported
|
|
16
|
+
*/
|
|
17
|
+
export function buildPullRequestUrl({ provider, owner, repo, source, target, title = '' }) {
|
|
18
|
+
const encodedTitle = encodeURIComponent(title);
|
|
19
|
+
|
|
20
|
+
if (provider === 'github') {
|
|
21
|
+
// GitHub's quick_pull form: /compare/target...source?quick_pull=1&title=...
|
|
22
|
+
const base = `https://github.com/${owner}/${repo}/compare/${target}...${source}?quick_pull=1`;
|
|
23
|
+
return title ? `${base}&title=${encodedTitle}` : base;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (provider === 'bitbucket') {
|
|
27
|
+
// Bitbucket: /pull-requests/new?source=...&dest=...&title=...
|
|
28
|
+
const base = `https://bitbucket.org/${owner}/${repo}/pull-requests/new?source=${source}&dest=${target}`;
|
|
29
|
+
return title ? `${base}&title=${encodedTitle}` : base;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (provider === 'gitlab') {
|
|
33
|
+
// GitLab: /-/merge_requests/new?merge_request[source_branch]=...&...
|
|
34
|
+
const params = new URLSearchParams({
|
|
35
|
+
'merge_request[source_branch]': source,
|
|
36
|
+
'merge_request[target_branch]': target
|
|
37
|
+
});
|
|
38
|
+
if (title) params.set('merge_request[title]', title);
|
|
39
|
+
return `https://gitlab.com/${owner}/${repo}/-/merge_requests/new?${params.toString()}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Open a URL in the user's default browser using the platform-native command.
|
|
47
|
+
* Uses spawn so it doesn't block, and detaches so the parent can exit cleanly.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} url
|
|
50
|
+
* @returns {boolean} True if the open command was launched
|
|
51
|
+
*/
|
|
52
|
+
export function openInBrowser(url) {
|
|
53
|
+
const platform = process.platform;
|
|
54
|
+
let cmd, args;
|
|
55
|
+
|
|
56
|
+
if (platform === 'darwin') {
|
|
57
|
+
cmd = 'open';
|
|
58
|
+
args = [url];
|
|
59
|
+
} else if (platform === 'win32') {
|
|
60
|
+
cmd = 'cmd';
|
|
61
|
+
args = ['/c', 'start', '""', url];
|
|
62
|
+
} else {
|
|
63
|
+
// Linux + others: xdg-open is the standard
|
|
64
|
+
cmd = 'xdg-open';
|
|
65
|
+
args = [url];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const child = spawn(cmd, args, { detached: true, stdio: 'ignore' });
|
|
70
|
+
child.unref();
|
|
71
|
+
return true;
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Push a branch to origin, setting upstream if needed.
|
|
79
|
+
* Used before opening the PR URL so the source branch exists on the remote.
|
|
80
|
+
*/
|
|
81
|
+
function pushBranchToOrigin(branch) {
|
|
82
|
+
try {
|
|
83
|
+
execSync(`git push --set-upstream origin ${branch}`, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
84
|
+
return { success: true };
|
|
85
|
+
} catch (error) {
|
|
86
|
+
// Fall back to a plain push if upstream is already set
|
|
87
|
+
try {
|
|
88
|
+
execSync(`git push origin ${branch}`, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
89
|
+
return { success: true };
|
|
90
|
+
} catch (innerError) {
|
|
91
|
+
return { success: false, error: innerError.message };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Push the source branch and open a PR-creation URL in the browser.
|
|
98
|
+
* Mirrors the bashmaiass `perform_merge_operation` PR path.
|
|
99
|
+
*
|
|
100
|
+
* @param {Object} args
|
|
101
|
+
* @param {string} args.source - Source branch
|
|
102
|
+
* @param {string} args.target - Target branch
|
|
103
|
+
* @param {string} [args.title] - Optional PR title
|
|
104
|
+
* @param {Object} [deps] - Injectable dependencies for testing
|
|
105
|
+
* @param {Function} [deps.detect] - Provider detection function (defaults to detectRepoProvider)
|
|
106
|
+
* @param {Function} [deps.push] - Branch push function (defaults to pushBranchToOrigin)
|
|
107
|
+
* @param {Function} [deps.open] - Browser open function (defaults to openInBrowser)
|
|
108
|
+
* @returns {{success: boolean, url?: string, error?: string}}
|
|
109
|
+
*/
|
|
110
|
+
export function createPullRequest({ source, target, title }, deps = {}) {
|
|
111
|
+
const detect = deps.detect || detectRepoProvider;
|
|
112
|
+
const push = deps.push || pushBranchToOrigin;
|
|
113
|
+
const open = deps.open || openInBrowser;
|
|
114
|
+
|
|
115
|
+
const repoInfo = detect();
|
|
116
|
+
if (!repoInfo) {
|
|
117
|
+
return { success: false, error: 'Could not detect repository provider from git remote' };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const url = buildPullRequestUrl({
|
|
121
|
+
provider: repoInfo.provider,
|
|
122
|
+
owner: repoInfo.owner,
|
|
123
|
+
repo: repoInfo.repo,
|
|
124
|
+
source,
|
|
125
|
+
target,
|
|
126
|
+
title
|
|
127
|
+
});
|
|
128
|
+
if (!url) {
|
|
129
|
+
return { success: false, error: `Unsupported provider: ${repoInfo.provider}` };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Push source branch so the PR has commits to compare against
|
|
133
|
+
const pushResult = push(source);
|
|
134
|
+
if (!pushResult.success) {
|
|
135
|
+
return { success: false, error: `Failed to push ${source}: ${pushResult.error}` };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
open(url);
|
|
139
|
+
return { success: true, url };
|
|
140
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parse a git remote URL and extract provider, owner, and repo.
|
|
5
|
+
* Supports common GitHub, Bitbucket, and GitLab URL formats:
|
|
6
|
+
*
|
|
7
|
+
* https://github.com/owner/repo.git
|
|
8
|
+
* git@github.com:owner/repo.git
|
|
9
|
+
* https://bitbucket.org/workspace/slug.git
|
|
10
|
+
* git@bitbucket.org:workspace/slug.git
|
|
11
|
+
* https://gitlab.com/group/project.git
|
|
12
|
+
* git@gitlab.com:group/project.git
|
|
13
|
+
*
|
|
14
|
+
* @param {string} url - Remote URL (e.g. from `git config --get remote.origin.url`)
|
|
15
|
+
* @returns {{provider: string, owner: string, repo: string} | null}
|
|
16
|
+
*/
|
|
17
|
+
export function parseRemoteUrl(url) {
|
|
18
|
+
if (!url) return null;
|
|
19
|
+
|
|
20
|
+
// Match host + path from either SSH (git@host:path) or HTTPS (https://host/path) form
|
|
21
|
+
const sshMatch = url.match(/^git@([^:]+):(.+?)(?:\.git)?\/?$/);
|
|
22
|
+
const httpsMatch = url.match(/^https?:\/\/(?:[^@]+@)?([^/]+)\/(.+?)(?:\.git)?\/?$/);
|
|
23
|
+
|
|
24
|
+
const match = sshMatch || httpsMatch;
|
|
25
|
+
if (!match) return null;
|
|
26
|
+
|
|
27
|
+
const host = match[1].toLowerCase();
|
|
28
|
+
const path = match[2];
|
|
29
|
+
|
|
30
|
+
// Need at least owner/repo — anything less can't be a valid remote
|
|
31
|
+
const parts = path.split('/');
|
|
32
|
+
if (parts.length < 2) return null;
|
|
33
|
+
|
|
34
|
+
// Last segment is repo; everything before is the owner (handles GitLab subgroups)
|
|
35
|
+
const repo = parts.pop();
|
|
36
|
+
const owner = parts.join('/');
|
|
37
|
+
|
|
38
|
+
let provider = null;
|
|
39
|
+
if (host.includes('github.com')) provider = 'github';
|
|
40
|
+
else if (host.includes('bitbucket.org')) provider = 'bitbucket';
|
|
41
|
+
else if (host.includes('gitlab.com') || host.includes('gitlab.')) provider = 'gitlab';
|
|
42
|
+
else return null;
|
|
43
|
+
|
|
44
|
+
return { provider, owner, repo };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Read the origin remote URL via git. Returns null if no remote or not a git repo.
|
|
49
|
+
*/
|
|
50
|
+
export function getOriginUrl() {
|
|
51
|
+
try {
|
|
52
|
+
const url = execSync('git config --get remote.origin.url', {
|
|
53
|
+
encoding: 'utf8',
|
|
54
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
55
|
+
}).trim();
|
|
56
|
+
return url || null;
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Detect repo provider/owner/repo from the current git remote.
|
|
64
|
+
* Respects existing env vars so the user can override auto-detection in `.env.maiass`.
|
|
65
|
+
*
|
|
66
|
+
* Sets the following on `process.env` if not already set:
|
|
67
|
+
* MAIASS_REPO_PROVIDER, MAIASS_GITHUB_OWNER, MAIASS_GITHUB_REPO,
|
|
68
|
+
* MAIASS_BITBUCKET_WORKSPACE, MAIASS_BITBUCKET_REPO_SLUG
|
|
69
|
+
*
|
|
70
|
+
* @returns {{provider: string, owner: string, repo: string} | null}
|
|
71
|
+
*/
|
|
72
|
+
export function detectRepoProvider() {
|
|
73
|
+
// Respect explicit override — if all the relevant vars are already set, skip detection
|
|
74
|
+
const overrideProvider = process.env.MAIASS_REPO_PROVIDER;
|
|
75
|
+
if (overrideProvider) {
|
|
76
|
+
return {
|
|
77
|
+
provider: overrideProvider,
|
|
78
|
+
owner: process.env.MAIASS_GITHUB_OWNER || process.env.MAIASS_BITBUCKET_WORKSPACE || '',
|
|
79
|
+
repo: process.env.MAIASS_GITHUB_REPO || process.env.MAIASS_BITBUCKET_REPO_SLUG || ''
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const url = getOriginUrl();
|
|
84
|
+
const parsed = parseRemoteUrl(url);
|
|
85
|
+
if (!parsed) return null;
|
|
86
|
+
|
|
87
|
+
// Cache to env so the rest of the pipeline can read consistently
|
|
88
|
+
process.env.MAIASS_REPO_PROVIDER = parsed.provider;
|
|
89
|
+
if (parsed.provider === 'github') {
|
|
90
|
+
process.env.MAIASS_GITHUB_OWNER = parsed.owner;
|
|
91
|
+
process.env.MAIASS_GITHUB_REPO = parsed.repo;
|
|
92
|
+
} else if (parsed.provider === 'bitbucket') {
|
|
93
|
+
process.env.MAIASS_BITBUCKET_WORKSPACE = parsed.owner;
|
|
94
|
+
process.env.MAIASS_BITBUCKET_REPO_SLUG = parsed.repo;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return parsed;
|
|
98
|
+
}
|
package/maiass.mjs
CHANGED
|
@@ -94,16 +94,33 @@ if (firstArg && versionBumpTypes.includes(firstArg)) {
|
|
|
94
94
|
command = 'maiass';
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
// Auto modes:
|
|
98
|
+
// -a / --auto: full auto — stage, push, merge to develop, version bump
|
|
99
|
+
// (kept identical to historical behaviour for CI compatibility)
|
|
100
|
+
// -ac / --auto-commit: auto-yes for commit phase only — stops after commit
|
|
101
|
+
// (no merge, no bump). Useful for CI runs that just want
|
|
102
|
+
// the AI commit captured without touching develop.
|
|
103
|
+
const isAutoCommit = args.includes('--auto-commit') || args.includes('-ac');
|
|
104
|
+
const isAuto = !isAutoCommit && (args.includes('--auto') || args.includes('-a'));
|
|
105
|
+
|
|
106
|
+
if (isAuto || isAutoCommit) {
|
|
107
|
+
// Shared auto-yes vars for both modes
|
|
100
108
|
process.env.MAIASS_AUTO_STAGE_UNSTAGED = 'true';
|
|
101
109
|
process.env.MAIASS_AUTO_PUSH_COMMITS = 'true';
|
|
102
|
-
process.env.MAIASS_AUTO_MERGE_TO_DEVELOP = 'true';
|
|
103
110
|
process.env.MAIASS_AUTO_APPROVE_AI_SUGGESTIONS = 'true';
|
|
104
|
-
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (isAuto) {
|
|
114
|
+
// -a: also auto-merge to develop (legacy behaviour)
|
|
115
|
+
process.env.MAIASS_AUTO_MERGE_TO_DEVELOP = 'true';
|
|
116
|
+
if (process.env.MAIASS_DEBUG === 'true') {
|
|
117
|
+
logger.debug('[DEBUG] Auto mode enabled — full pipeline runs unattended');
|
|
118
|
+
}
|
|
119
|
+
} else if (isAutoCommit) {
|
|
120
|
+
// -ac: stop after commit phase. Implemented by treating it as commits-only
|
|
121
|
+
process.env.MAIASS_AUTO_FINISH_AFTER_COMMIT = 'true';
|
|
105
122
|
if (process.env.MAIASS_DEBUG === 'true') {
|
|
106
|
-
logger.debug('[DEBUG] Auto-mode enabled
|
|
123
|
+
logger.debug('[DEBUG] Auto-commit mode enabled — stops after commit phase');
|
|
107
124
|
}
|
|
108
125
|
}
|
|
109
126
|
|
|
@@ -124,6 +141,7 @@ const validFlags = [
|
|
|
124
141
|
'--version', '-v',
|
|
125
142
|
'--account-info',
|
|
126
143
|
'--auto', '-a',
|
|
144
|
+
'--auto-commit', '-ac',
|
|
127
145
|
'--commits-only', '-c',
|
|
128
146
|
'--auto-stage',
|
|
129
147
|
'--setup', '--bootstrap',
|
|
@@ -182,7 +200,8 @@ if (args.includes('--help') || args.includes('-h') || command === 'help') {
|
|
|
182
200
|
console.log(' account-info Show your account status (masked token)');
|
|
183
201
|
console.log('\nOptions:');
|
|
184
202
|
console.log(' --account-info Show your account status (masked token)');
|
|
185
|
-
console.log(' --auto
|
|
203
|
+
console.log(' --auto, -a Full auto — stage, commit, push, merge to develop, bump version');
|
|
204
|
+
console.log(' --auto-commit, -ac Auto-yes for commit phase only — stops after commit (no merge, no bump)');
|
|
186
205
|
console.log(' --commits-only, -c Generate AI commits without version management');
|
|
187
206
|
console.log(' --auto-stage Automatically stage all changes');
|
|
188
207
|
console.log(' --setup, --bootstrap Run interactive project setup');
|
|
@@ -279,7 +298,8 @@ if (args.includes('--show-bb-excerpt')) { showBitbucketExcerpt(); process.exit(
|
|
|
279
298
|
// Handle the main MAIASS workflow
|
|
280
299
|
await handleMaiassCommand({
|
|
281
300
|
_: process.argv.slice(2).filter(arg => !arg.startsWith('-')),
|
|
282
|
-
|
|
301
|
+
// -ac is equivalent to -c (commits-only) plus auto-yes prompts
|
|
302
|
+
'commits-only': args.includes('--commits-only') || args.includes('-c') || args.includes('--auto-commit') || args.includes('-ac'),
|
|
283
303
|
'auto-stage': args.includes('--auto-stage'),
|
|
284
304
|
'auto': args.includes('--auto') || args.includes('-a'),
|
|
285
305
|
'version-bump': versionBump,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "maiass",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.12.2",
|
|
5
5
|
"description": "AI commit message generator, semantic versioning, and changelog automation for Git. One command stages, commits with AI, bumps version, and merges branches. Free credits on install — no sign-up needed.",
|
|
6
6
|
"main": "maiass.mjs",
|
|
7
7
|
"bin": {
|