unirepo-cli 0.3.0 → 0.4.1
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/README.md +19 -0
- package/package.json +1 -1
- package/src/commands/branch.js +1 -1
- package/src/commands/pr.js +175 -0
- package/src/github.js +60 -1
- package/src/index.js +36 -0
- package/src/templates.js +10 -0
- package/src/ui.js +12 -0
package/README.md
CHANGED
|
@@ -142,6 +142,7 @@ AI coding agents work best when they can see the full change at once.
|
|
|
142
142
|
| `status` | Show subtrees, branches, and what changed |
|
|
143
143
|
| `branch [name]` | Create or show the current push branch |
|
|
144
144
|
| `push [subtree...]` | Push changed subtrees upstream |
|
|
145
|
+
| `pr [subtree...]` | Open pull requests for changed subtree repos |
|
|
145
146
|
|
|
146
147
|
|
|
147
148
|
### init
|
|
@@ -213,6 +214,24 @@ Pushes changed subtrees upstream. Without arguments, auto-detects which subtrees
|
|
|
213
214
|
| `--branch <name>` | Override the upstream branch name |
|
|
214
215
|
| `--dry-run` | Show what would run without executing |
|
|
215
216
|
|
|
217
|
+
### pr
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
unirepo pr [subtree...] --title <title> [--body <text>]
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Opens one GitHub pull request per changed subtree repo. Without subtree arguments, `unirepo` auto-detects changed subtrees. Use explicit subtree names when you want PRs for only part of the workspace.
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
| Flag | Effect |
|
|
227
|
+
| --- | --- |
|
|
228
|
+
| `--title <title>` | Shared PR title for all selected repos |
|
|
229
|
+
| `--body <text>` | Shared PR description |
|
|
230
|
+
| `--base <name>` | Override the base branch for all selected repos |
|
|
231
|
+
| `--head <name>` | Override the head branch name (default: current branch) |
|
|
232
|
+
| `--draft` | Create draft pull requests |
|
|
233
|
+
| `--dry-run` | Show the `gh pr create` commands without executing |
|
|
234
|
+
|
|
216
235
|
|
|
217
236
|
## How It Works
|
|
218
237
|
|
package/package.json
CHANGED
package/src/commands/branch.js
CHANGED
|
@@ -49,6 +49,6 @@ export async function runBranch({ name }) {
|
|
|
49
49
|
ui.info(' 1. Make changes in subtree directories');
|
|
50
50
|
ui.info(' 2. Commit in the monorepo');
|
|
51
51
|
ui.info(` 3. unirepo push`);
|
|
52
|
-
ui.info(' 4.
|
|
52
|
+
ui.info(' 4. unirepo pr --title "..." --body "..."');
|
|
53
53
|
ui.blank();
|
|
54
54
|
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getCurrentBranch,
|
|
3
|
+
getSubtreePrefixes,
|
|
4
|
+
getChangedSubtrees,
|
|
5
|
+
getTrackedSubtreeBranch,
|
|
6
|
+
hasRemoteBranch,
|
|
7
|
+
} from '../git.js';
|
|
8
|
+
import { isGhAvailable, getGitHubRepoSlugFromUrl, createPullRequest } from '../github.js';
|
|
9
|
+
import { validateInsideMonorepo } from '../validate.js';
|
|
10
|
+
import * as ui from '../ui.js';
|
|
11
|
+
|
|
12
|
+
function quoteShellArg(value) {
|
|
13
|
+
return JSON.stringify(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function selectPrSubtrees(allPrefixes, changedPrefixes, requestedSubtrees) {
|
|
17
|
+
if (!requestedSubtrees || requestedSubtrees.length === 0) {
|
|
18
|
+
return changedPrefixes;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const prefixNames = new Set(allPrefixes.map((prefix) => prefix.name));
|
|
22
|
+
for (const name of requestedSubtrees) {
|
|
23
|
+
if (!prefixNames.has(name)) {
|
|
24
|
+
throw new Error(`"${name}" is not a tracked subtree. Run "unirepo status" to see available subtrees.`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return allPrefixes.filter((prefix) => requestedSubtrees.includes(prefix.name));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function resolvePrBaseBranch({ requestedBase, trackedBranch }) {
|
|
32
|
+
return requestedBase || trackedBranch || 'main';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function planPrTargets({
|
|
36
|
+
allPrefixes,
|
|
37
|
+
changedPrefixes,
|
|
38
|
+
requestedSubtrees,
|
|
39
|
+
requestedBase,
|
|
40
|
+
headBranch,
|
|
41
|
+
getTrackedBranchFn = () => null,
|
|
42
|
+
getRepoSlugFn = () => null,
|
|
43
|
+
}) {
|
|
44
|
+
return selectPrSubtrees(allPrefixes, changedPrefixes, requestedSubtrees).map((prefix) => ({
|
|
45
|
+
name: prefix.name,
|
|
46
|
+
repo: getRepoSlugFn(prefix.url),
|
|
47
|
+
url: prefix.url,
|
|
48
|
+
base: resolvePrBaseBranch({
|
|
49
|
+
requestedBase,
|
|
50
|
+
trackedBranch: getTrackedBranchFn(prefix.name),
|
|
51
|
+
}),
|
|
52
|
+
head: headBranch,
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function runPr({ subtrees: requestedSubtrees, title, body, base, head, draft, dryRun }) {
|
|
57
|
+
const cwd = process.cwd();
|
|
58
|
+
|
|
59
|
+
ui.header('Creating pull requests');
|
|
60
|
+
ui.blank();
|
|
61
|
+
|
|
62
|
+
validateInsideMonorepo(cwd);
|
|
63
|
+
|
|
64
|
+
if (!isGhAvailable()) {
|
|
65
|
+
throw new Error('GitHub CLI is not available or not authenticated. Install gh and run "gh auth login".');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const currentBranch = getCurrentBranch(cwd);
|
|
69
|
+
const headBranch = head || currentBranch;
|
|
70
|
+
const allPrefixes = getSubtreePrefixes(cwd);
|
|
71
|
+
const changedPrefixes = getChangedSubtrees(cwd);
|
|
72
|
+
const targets = planPrTargets({
|
|
73
|
+
allPrefixes,
|
|
74
|
+
changedPrefixes,
|
|
75
|
+
requestedSubtrees,
|
|
76
|
+
requestedBase: base,
|
|
77
|
+
headBranch,
|
|
78
|
+
getTrackedBranchFn: (prefixName) => getTrackedSubtreeBranch(cwd, prefixName),
|
|
79
|
+
getRepoSlugFn: getGitHubRepoSlugFromUrl,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (targets.length === 0) {
|
|
83
|
+
ui.info('No changed subtrees detected. Nothing to open PRs for.');
|
|
84
|
+
ui.info('Push a changed subtree first or pass explicit subtree names.');
|
|
85
|
+
ui.blank();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
ui.info(`Head branch: ${headBranch}`);
|
|
90
|
+
ui.info(`Subtrees: ${targets.map((target) => target.name).join(', ')}`);
|
|
91
|
+
ui.blank();
|
|
92
|
+
|
|
93
|
+
const dryRunCommands = [];
|
|
94
|
+
let succeeded = 0;
|
|
95
|
+
let failed = 0;
|
|
96
|
+
|
|
97
|
+
for (let i = 0; i < targets.length; i++) {
|
|
98
|
+
const target = targets[i];
|
|
99
|
+
|
|
100
|
+
ui.repoStep(i + 1, targets.length, target.name, dryRun ? 'Planning PR for' : 'Opening PR for');
|
|
101
|
+
ui.repoDetail('Repo', target.repo || target.url);
|
|
102
|
+
ui.repoDetail('Base', target.base);
|
|
103
|
+
ui.repoDetail('Head', target.head);
|
|
104
|
+
|
|
105
|
+
if (!target.repo) {
|
|
106
|
+
ui.error(`Failed to open PR for ${target.name}: remote URL is not a GitHub repository.`);
|
|
107
|
+
failed++;
|
|
108
|
+
ui.blank();
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!hasRemoteBranch(cwd, target.name, target.head)) {
|
|
113
|
+
ui.error(`Failed to open PR for ${target.name}: upstream branch "${target.head}" does not exist. Run "unirepo push ${target.name}" first.`);
|
|
114
|
+
failed++;
|
|
115
|
+
ui.blank();
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!hasRemoteBranch(cwd, target.name, target.base)) {
|
|
120
|
+
ui.error(`Failed to open PR for ${target.name}: base branch "${target.base}" does not exist upstream.`);
|
|
121
|
+
failed++;
|
|
122
|
+
ui.blank();
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const ghCommand = [
|
|
127
|
+
'gh pr create',
|
|
128
|
+
`--repo ${quoteShellArg(target.repo)}`,
|
|
129
|
+
`--base ${quoteShellArg(target.base)}`,
|
|
130
|
+
`--head ${quoteShellArg(target.head)}`,
|
|
131
|
+
`--title ${quoteShellArg(title)}`,
|
|
132
|
+
`--body ${quoteShellArg(body)}`,
|
|
133
|
+
draft ? '--draft' : '',
|
|
134
|
+
]
|
|
135
|
+
.filter(Boolean)
|
|
136
|
+
.join(' ');
|
|
137
|
+
|
|
138
|
+
if (dryRun) {
|
|
139
|
+
dryRunCommands.push(ghCommand);
|
|
140
|
+
succeeded++;
|
|
141
|
+
ui.success(`${target.name} PR planned`);
|
|
142
|
+
ui.blank();
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const url = createPullRequest({
|
|
148
|
+
repo: target.repo,
|
|
149
|
+
base: target.base,
|
|
150
|
+
head: target.head,
|
|
151
|
+
title,
|
|
152
|
+
body,
|
|
153
|
+
draft,
|
|
154
|
+
});
|
|
155
|
+
ui.success(`${target.name} PR opened${url ? `: ${url}` : ''}`);
|
|
156
|
+
succeeded++;
|
|
157
|
+
} catch (err) {
|
|
158
|
+
ui.error(`Failed to open PR for ${target.name}: ${err.message}`);
|
|
159
|
+
failed++;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
ui.blank();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (dryRunCommands.length > 0) {
|
|
166
|
+
ui.dryRun(dryRunCommands);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (failed === 0) {
|
|
170
|
+
ui.success(`${dryRun ? 'Planned' : 'Opened'} ${succeeded} pull request(s)`);
|
|
171
|
+
} else {
|
|
172
|
+
ui.warning(`${dryRun ? 'Planned' : 'Opened'} ${succeeded}, ${failed} failed`);
|
|
173
|
+
}
|
|
174
|
+
ui.blank();
|
|
175
|
+
}
|
package/src/github.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execSync } from 'node:child_process';
|
|
1
|
+
import { execFileSync, execSync } from 'node:child_process';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Thin wrapper around the `gh` CLI. We shell out rather than hitting the
|
|
@@ -23,6 +23,19 @@ function runGh(args, { timeout = 30000 } = {}) {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
function runGhArgs(args, { timeout = 30000 } = {}) {
|
|
27
|
+
try {
|
|
28
|
+
return execFileSync('gh', args, {
|
|
29
|
+
encoding: 'utf-8',
|
|
30
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
31
|
+
timeout,
|
|
32
|
+
});
|
|
33
|
+
} catch (err) {
|
|
34
|
+
const stderr = (err.stderr || '').toString().trim();
|
|
35
|
+
throw new Error(stderr || err.message);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
26
39
|
function runGhJson(args, opts) {
|
|
27
40
|
const out = runGh(args, opts);
|
|
28
41
|
if (!out.trim()) return [];
|
|
@@ -111,3 +124,49 @@ export function searchRepos(query, limit = SEARCH_LIMIT) {
|
|
|
111
124
|
isPrivate: r.isPrivate,
|
|
112
125
|
}));
|
|
113
126
|
}
|
|
127
|
+
|
|
128
|
+
export function getGitHubRepoSlugFromUrl(url) {
|
|
129
|
+
const trimmed = url.replace(/\/$/, '').replace(/\.git$/, '');
|
|
130
|
+
const patterns = [
|
|
131
|
+
/^https?:\/\/github\.com\/([^/]+)\/([^/]+)$/i,
|
|
132
|
+
/^git@github\.com:([^/]+)\/([^/]+)$/i,
|
|
133
|
+
/^ssh:\/\/git@github\.com\/([^/]+)\/([^/]+)$/i,
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
for (const pattern of patterns) {
|
|
137
|
+
const match = trimmed.match(pattern);
|
|
138
|
+
if (match) {
|
|
139
|
+
return `${match[1]}/${match[2]}`;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function createPullRequest({ repo, base, head, title, body, draft = false }) {
|
|
147
|
+
const args = [
|
|
148
|
+
'pr',
|
|
149
|
+
'create',
|
|
150
|
+
'--repo',
|
|
151
|
+
repo,
|
|
152
|
+
'--base',
|
|
153
|
+
base,
|
|
154
|
+
'--head',
|
|
155
|
+
head,
|
|
156
|
+
'--title',
|
|
157
|
+
title,
|
|
158
|
+
'--body',
|
|
159
|
+
body,
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
if (draft) {
|
|
163
|
+
args.push('--draft');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const output = runGhArgs(args);
|
|
167
|
+
const lines = output
|
|
168
|
+
.split('\n')
|
|
169
|
+
.map((line) => line.trim())
|
|
170
|
+
.filter(Boolean);
|
|
171
|
+
return lines.at(-1) || '';
|
|
172
|
+
}
|
package/src/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { runPull } from './commands/pull.js';
|
|
|
8
8
|
import { runStatus } from './commands/status.js';
|
|
9
9
|
import { runPush } from './commands/push.js';
|
|
10
10
|
import { runBranch } from './commands/branch.js';
|
|
11
|
+
import { runPr } from './commands/pr.js';
|
|
11
12
|
import { pathToFileURL } from 'node:url';
|
|
12
13
|
import { realpathSync } from 'node:fs';
|
|
13
14
|
|
|
@@ -37,10 +38,20 @@ export function parseArgs(argv) {
|
|
|
37
38
|
flags.json = true;
|
|
38
39
|
} else if (arg === '--dry-run') {
|
|
39
40
|
flags.dryRun = true;
|
|
41
|
+
} else if (arg === '--draft') {
|
|
42
|
+
flags.draft = true;
|
|
40
43
|
} else if (arg === '--prefix' && i + 1 < args.length) {
|
|
41
44
|
flags.prefix = args[++i];
|
|
42
45
|
} else if (arg === '--branch' && i + 1 < args.length) {
|
|
43
46
|
flags.branch = args[++i];
|
|
47
|
+
} else if (arg === '--title' && i + 1 < args.length) {
|
|
48
|
+
flags.title = args[++i];
|
|
49
|
+
} else if (arg === '--body' && i + 1 < args.length) {
|
|
50
|
+
flags.body = args[++i];
|
|
51
|
+
} else if (arg === '--base' && i + 1 < args.length) {
|
|
52
|
+
flags.base = args[++i];
|
|
53
|
+
} else if (arg === '--head' && i + 1 < args.length) {
|
|
54
|
+
flags.head = args[++i];
|
|
44
55
|
} else if (arg === '--help' || arg === '-h') {
|
|
45
56
|
flags.help = true;
|
|
46
57
|
} else if (arg.startsWith('-')) {
|
|
@@ -61,6 +72,7 @@ const COMMAND_USAGE = {
|
|
|
61
72
|
status: 'Usage: unirepo status [--json]',
|
|
62
73
|
push: 'Usage: unirepo push [subtree...] [--branch <name>] [--dry-run]',
|
|
63
74
|
branch: 'Usage: unirepo branch [name]',
|
|
75
|
+
pr: 'Usage: unirepo pr [subtree...] --title <title> [--body <text>] [--base <name>] [--head <name>] [--draft] [--dry-run]',
|
|
64
76
|
};
|
|
65
77
|
|
|
66
78
|
const COMMAND_FLAGS = {
|
|
@@ -71,6 +83,7 @@ const COMMAND_FLAGS = {
|
|
|
71
83
|
status: new Set(['json']),
|
|
72
84
|
push: new Set(['branch', 'dryRun']),
|
|
73
85
|
branch: new Set(),
|
|
86
|
+
pr: new Set(['title', 'body', 'base', 'head', 'draft', 'dryRun']),
|
|
74
87
|
};
|
|
75
88
|
|
|
76
89
|
const FLAG_NAMES = {
|
|
@@ -79,6 +92,11 @@ const FLAG_NAMES = {
|
|
|
79
92
|
dryRun: '--dry-run',
|
|
80
93
|
prefix: '--prefix',
|
|
81
94
|
branch: '--branch',
|
|
95
|
+
title: '--title',
|
|
96
|
+
body: '--body',
|
|
97
|
+
base: '--base',
|
|
98
|
+
head: '--head',
|
|
99
|
+
draft: '--draft',
|
|
82
100
|
};
|
|
83
101
|
|
|
84
102
|
export function validateCommandFlags(command, flags) {
|
|
@@ -191,6 +209,24 @@ export async function main() {
|
|
|
191
209
|
break;
|
|
192
210
|
}
|
|
193
211
|
|
|
212
|
+
case 'pr': {
|
|
213
|
+
if (!flags.title) {
|
|
214
|
+
ui.error('Usage: unirepo pr [subtree...] --title <title> [--body <text>] [--base <name>] [--head <name>] [--draft] [--dry-run]');
|
|
215
|
+
ui.info('Provide a shared PR title with --title. Use --body to set the description.');
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
await runPr({
|
|
219
|
+
subtrees: positional.length > 0 ? positional : undefined,
|
|
220
|
+
title: flags.title,
|
|
221
|
+
body: flags.body || '',
|
|
222
|
+
base: flags.base,
|
|
223
|
+
head: flags.head,
|
|
224
|
+
draft: flags.draft || false,
|
|
225
|
+
dryRun: flags.dryRun || false,
|
|
226
|
+
});
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
|
|
194
230
|
default:
|
|
195
231
|
ui.error(`Unknown command: ${command}`);
|
|
196
232
|
ui.blank();
|
package/src/templates.js
CHANGED
|
@@ -30,6 +30,7 @@ unirepo branch <branch>
|
|
|
30
30
|
unirepo pull
|
|
31
31
|
unirepo push --dry-run
|
|
32
32
|
unirepo push
|
|
33
|
+
unirepo pr --title "feat: ..." --body "..."
|
|
33
34
|
unirepo add <repo-url> --branch <branch>
|
|
34
35
|
\`\`\`
|
|
35
36
|
|
|
@@ -38,6 +39,7 @@ unirepo add <repo-url> --branch <branch>
|
|
|
38
39
|
- \`pull\` updates one or more tracked subtrees from upstream before or during your work. Use \`--prefix\` when you want a branch-specific pull for just one subtree.
|
|
39
40
|
- \`push --dry-run\` is the safe first step before a real push.
|
|
40
41
|
- \`push\` without subtree names auto-detects changed subtrees. \`push <subtree>\` pushes one subtree.
|
|
42
|
+
- \`pr\` opens one PR per changed or explicitly selected subtree repo after those branches have been pushed.
|
|
41
43
|
- \`add\` imports another repository as a subtree. Use \`--branch\` to import from a non-default upstream branch.
|
|
42
44
|
|
|
43
45
|
## Workflow
|
|
@@ -93,6 +95,13 @@ Git:
|
|
|
93
95
|
git subtree push --prefix=<subtree> <remote-or-url> <branch>
|
|
94
96
|
\`\`\`
|
|
95
97
|
|
|
98
|
+
7. Open one PR per upstream subtree repo after push.
|
|
99
|
+
CLI:
|
|
100
|
+
\`\`\`bash
|
|
101
|
+
unirepo pr --title "feat: ..." --body "..."
|
|
102
|
+
unirepo pr <subtree> --title "feat: ..."
|
|
103
|
+
\`\`\`
|
|
104
|
+
|
|
96
105
|
## Raw Git Subtree
|
|
97
106
|
|
|
98
107
|
Use these when operating without the CLI:
|
|
@@ -116,6 +125,7 @@ git subtree push --prefix=libfoo https://github.com/example/libfoo.git <branch>
|
|
|
116
125
|
- Open one PR per upstream subtree repo.
|
|
117
126
|
- Use the same branch name in each upstream repo.
|
|
118
127
|
- Target the default branch of the upstream repo unless that repo's workflow says otherwise.
|
|
128
|
+
- Run \`unirepo pr\` only after the head branch exists upstream for that subtree repo.
|
|
119
129
|
- Ensure each PR contains only changes from its own subtree.
|
|
120
130
|
`;
|
|
121
131
|
|
package/src/ui.js
CHANGED
|
@@ -179,6 +179,7 @@ ${chalk.bold('Commands:')}
|
|
|
179
179
|
${chalk.green('pull')} [subtree...] Pull subtree updates from upstream
|
|
180
180
|
${chalk.green('status')} Show tracked subtrees and changes
|
|
181
181
|
${chalk.green('push')} [subtree...] Push changed subtrees upstream
|
|
182
|
+
${chalk.green('pr')} [subtree...] Open PRs for changed subtree repos
|
|
182
183
|
${chalk.green('branch')} [name] Create a branch on all upstream repos
|
|
183
184
|
${chalk.green('version')} Show the installed CLI version
|
|
184
185
|
|
|
@@ -206,6 +207,14 @@ ${chalk.bold('Push options:')}
|
|
|
206
207
|
--branch <name> Branch name for upstream push (default: current)
|
|
207
208
|
--dry-run Show commands without executing
|
|
208
209
|
|
|
210
|
+
${chalk.bold('PR options:')}
|
|
211
|
+
--title <title> Shared PR title (required)
|
|
212
|
+
--body <text> Shared PR description
|
|
213
|
+
--base <name> Override the base branch for all selected repos
|
|
214
|
+
--head <name> Override the head branch (default: current branch)
|
|
215
|
+
--draft Create draft PRs
|
|
216
|
+
--dry-run Show gh pr create commands without executing
|
|
217
|
+
|
|
209
218
|
${chalk.bold('Examples:')}
|
|
210
219
|
${chalk.dim('# Create monorepo from multiple repos')}
|
|
211
220
|
npx unirepo init my-monorepo https://github.com/org/api.git https://github.com/org/web.git
|
|
@@ -228,5 +237,8 @@ ${chalk.bold('Examples:')}
|
|
|
228
237
|
|
|
229
238
|
${chalk.dim('# Push changes upstream')}
|
|
230
239
|
npx unirepo push --dry-run
|
|
240
|
+
|
|
241
|
+
${chalk.dim('# Open PRs for changed subtree repos')}
|
|
242
|
+
npx unirepo pr --title "feat: add auth flow" --body "Cross-repo auth changes"
|
|
231
243
|
`);
|
|
232
244
|
}
|