@gitset-dev/cli 2.2.0 → 2.3.3
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/index.js +14 -2
- package/lib/manifest/index.js +127 -0
- package/lib/prompts/defaults.js +7 -1
- package/package.json +1 -1
- package/src/commands/auth.js +54 -0
- package/src/commands/labelspack.js +36 -5
- package/src/commands/release.js +98 -1
- package/src/utils/labels.js +7 -1
- package/src/utils/ui.js +23 -21
package/index.js
CHANGED
|
@@ -38,6 +38,7 @@ ${theme.bold('Local tools')} (no AI):
|
|
|
38
38
|
template Manage local templates (~/.gitset/templates), edit <tool> to customize
|
|
39
39
|
init Scaffold local templates
|
|
40
40
|
feedback Report a bug, suggest a feature, or share feedback
|
|
41
|
+
auth Check GitHub access, or sync org permissions (auth sync)
|
|
41
42
|
|
|
42
43
|
${theme.bold('Setup')}:
|
|
43
44
|
config interactive setup wizard
|
|
@@ -115,7 +116,9 @@ Manage & apply a label pack (local pack + gh).
|
|
|
115
116
|
--apply create/update the labels in the current repo via gh
|
|
116
117
|
--init write the default pack to ~/.gitset/labels.md
|
|
117
118
|
--add --name <x> --color <hex> --description "..." add a label
|
|
118
|
-
--
|
|
119
|
+
--from-repo import the current repo's labels (name, color,
|
|
120
|
+
description) into the pack; --yes to overwrite
|
|
121
|
+
without asking (--sync is an alias)`,
|
|
119
122
|
dependabot: `Usage: gitset dependabot [--resolve] [--dry-run] [--save-dev]
|
|
120
123
|
Resolve Dependabot alerts for the current repo (local + gh).`,
|
|
121
124
|
status: `Usage: gitset status
|
|
@@ -131,6 +134,12 @@ Scaffold the local template directory.`,
|
|
|
131
134
|
feedback: `Usage: gitset feedback
|
|
132
135
|
Interactively submit a bug report, feature suggestion, or general feedback.
|
|
133
136
|
Filed as a GitHub issue in gitset-dev/gitset via your own \`gh\` auth.`,
|
|
137
|
+
auth: `Usage: gitset auth [status|sync]
|
|
138
|
+
status (default) show your GitHub CLI auth status and access guidance
|
|
139
|
+
sync re-run GitHub authorization to approve additional
|
|
140
|
+
organizations or scopes (wraps \`gh auth refresh\`)
|
|
141
|
+
Gitset uses your own gh authentication locally — there is no Gitset account.
|
|
142
|
+
Web app permissions: gitset.dev/dashboard → GitHub connection.`,
|
|
134
143
|
config: `Usage: gitset config [set|list|remove|theme|path]
|
|
135
144
|
(no args) interactive setup wizard
|
|
136
145
|
set interactive wizard, or: set <provider> --key <key> [--model m] [--base-url u] [--default]
|
|
@@ -209,7 +218,10 @@ async function main() {
|
|
|
209
218
|
case 'feedback':
|
|
210
219
|
code = (await require('./src/commands/feedback')()) || 0; break;
|
|
211
220
|
|
|
212
|
-
case 'auth':
|
|
221
|
+
case 'auth':
|
|
222
|
+
code = require('./src/commands/auth')(rest) || 0; break;
|
|
223
|
+
|
|
224
|
+
case 'verify': case 'logout':
|
|
213
225
|
code = deprecatedAuth(command); break;
|
|
214
226
|
|
|
215
227
|
case 'help': case '--help': case '-h': case undefined:
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// VENDORED from gitset-core-v2/lib/manifest — do not edit here. Run `pnpm sync:ai`.
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const MANIFESTS = [
|
|
5
|
+
{ type: 'npm', file: 'package.json', label: 'package.json (npm)' },
|
|
6
|
+
{ type: 'cargo', file: 'Cargo.toml', label: 'Cargo.toml (Cargo)' },
|
|
7
|
+
{ type: 'python', file: 'pyproject.toml', label: 'pyproject.toml (Python)' },
|
|
8
|
+
{ type: 'gemspec', file: null, label: '*.gemspec (RubyGems)' },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
function listSupported() {
|
|
12
|
+
return MANIFESTS.map(({ type, label }) => ({ type, label }));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function detectManifestFile(fileNames) {
|
|
16
|
+
const names = Array.isArray(fileNames) ? fileNames : [];
|
|
17
|
+
for (const m of MANIFESTS) {
|
|
18
|
+
if (m.file && names.includes(m.file)) return { type: m.type, file: m.file };
|
|
19
|
+
}
|
|
20
|
+
const gem = names.find((f) => f.endsWith('.gemspec'));
|
|
21
|
+
if (gem) return { type: 'gemspec', file: gem };
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeVersion(tagName) {
|
|
26
|
+
if (typeof tagName !== 'string') return null;
|
|
27
|
+
const trimmed = tagName.trim();
|
|
28
|
+
return trimmed.replace(/^v/i, '') || null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function detectIndent(text) {
|
|
32
|
+
const m = text.match(/^[ \t]+(?=")/m);
|
|
33
|
+
return m ? m[0] : ' ';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function findTomlSection(content, sectionName) {
|
|
37
|
+
const escaped = sectionName.replace(/\./g, '\\.');
|
|
38
|
+
const headerRe = new RegExp(`^\\[${escaped}\\][ \\t]*$`, 'm');
|
|
39
|
+
const m = headerRe.exec(content);
|
|
40
|
+
if (!m) return null;
|
|
41
|
+
const bodyStart = m.index + m[0].length;
|
|
42
|
+
const rest = content.slice(bodyStart);
|
|
43
|
+
const next = rest.match(/^\[[^\]]+\][ \t]*$/m);
|
|
44
|
+
const bodyEnd = next ? bodyStart + next.index : content.length;
|
|
45
|
+
return { bodyStart, bodyEnd };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const TOML_VERSION_LINE_RE = /^([ \t]*version[ \t]*=[ \t]*)(["'])([^"']+)\2([ \t]*)$/m;
|
|
49
|
+
|
|
50
|
+
function readTomlSectionVersion(content, sectionName) {
|
|
51
|
+
const sec = findTomlSection(content, sectionName);
|
|
52
|
+
if (!sec) return null;
|
|
53
|
+
const body = content.slice(sec.bodyStart, sec.bodyEnd);
|
|
54
|
+
const m = TOML_VERSION_LINE_RE.exec(body);
|
|
55
|
+
return m ? m[3] : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function bumpTomlSectionVersion(content, sectionName, newVersion) {
|
|
59
|
+
const sec = findTomlSection(content, sectionName);
|
|
60
|
+
if (!sec) return null;
|
|
61
|
+
const body = content.slice(sec.bodyStart, sec.bodyEnd);
|
|
62
|
+
if (!TOML_VERSION_LINE_RE.test(body)) return null;
|
|
63
|
+
const newBody = body.replace(TOML_VERSION_LINE_RE, (_full, pre, quote, _old, trail) => `${pre}${quote}${newVersion}${quote}${trail}`);
|
|
64
|
+
return content.slice(0, sec.bodyStart) + newBody + content.slice(sec.bodyEnd);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const GEMSPEC_VERSION_LINE_RE = /^([ \t]*[\w.]+\.version[ \t]*=[ \t]*)(["'])([^"']+)\2([ \t]*)$/m;
|
|
68
|
+
|
|
69
|
+
function readGemspecVersion(content) {
|
|
70
|
+
const m = GEMSPEC_VERSION_LINE_RE.exec(content);
|
|
71
|
+
return m ? m[3] : null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function bumpGemspecVersion(content, newVersion) {
|
|
75
|
+
if (!GEMSPEC_VERSION_LINE_RE.test(content)) return null;
|
|
76
|
+
return content.replace(GEMSPEC_VERSION_LINE_RE, (_full, pre, quote, _old, trail) => `${pre}${quote}${newVersion}${quote}${trail}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function readVersion(type, content) {
|
|
80
|
+
switch (type) {
|
|
81
|
+
case 'npm': {
|
|
82
|
+
const parsed = JSON.parse(content);
|
|
83
|
+
return typeof parsed.version === 'string' ? parsed.version : null;
|
|
84
|
+
}
|
|
85
|
+
case 'cargo':
|
|
86
|
+
return readTomlSectionVersion(content, 'package');
|
|
87
|
+
case 'python':
|
|
88
|
+
return readTomlSectionVersion(content, 'project') || readTomlSectionVersion(content, 'tool.poetry');
|
|
89
|
+
case 'gemspec':
|
|
90
|
+
return readGemspecVersion(content);
|
|
91
|
+
default:
|
|
92
|
+
throw new Error(`Unsupported manifest type: ${type}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function bumpNpmVersion(content, newVersion) {
|
|
97
|
+
const parsed = JSON.parse(content);
|
|
98
|
+
if (typeof parsed.version !== 'string') return null;
|
|
99
|
+
parsed.version = newVersion;
|
|
100
|
+
const indent = detectIndent(content);
|
|
101
|
+
let out = JSON.stringify(parsed, null, indent);
|
|
102
|
+
if (content.endsWith('\n')) out += '\n';
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function bumpVersion(type, content, newVersion) {
|
|
107
|
+
switch (type) {
|
|
108
|
+
case 'npm':
|
|
109
|
+
return bumpNpmVersion(content, newVersion);
|
|
110
|
+
case 'cargo':
|
|
111
|
+
return bumpTomlSectionVersion(content, 'package', newVersion);
|
|
112
|
+
case 'python':
|
|
113
|
+
return bumpTomlSectionVersion(content, 'project', newVersion) || bumpTomlSectionVersion(content, 'tool.poetry', newVersion);
|
|
114
|
+
case 'gemspec':
|
|
115
|
+
return bumpGemspecVersion(content, newVersion);
|
|
116
|
+
default:
|
|
117
|
+
throw new Error(`Unsupported manifest type: ${type}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = {
|
|
122
|
+
listSupported,
|
|
123
|
+
detectManifestFile,
|
|
124
|
+
normalizeVersion,
|
|
125
|
+
readVersion,
|
|
126
|
+
bumpVersion,
|
|
127
|
+
};
|
package/lib/prompts/defaults.js
CHANGED
|
@@ -98,7 +98,13 @@ const release = {
|
|
|
98
98
|
'headings, ordering, and tone closely — treat it as the format to reproduce. ' +
|
|
99
99
|
'Never invent a repository path (e.g. placeholders like "your-project/your-repo" ' +
|
|
100
100
|
'or "user/repo") for a "Full Changelog" or compare link — only include such a link ' +
|
|
101
|
-
'when the real repository is given below, using that exact path; otherwise omit it.'
|
|
101
|
+
'when the real repository is given below, using that exact path; otherwise omit it. ' +
|
|
102
|
+
'Never attribute a change to a person (e.g. "by @username") — a commit author\'s ' +
|
|
103
|
+
'display name (e.g. "Jane Smith") is NOT their GitHub username and must never be ' +
|
|
104
|
+
'guessed into one (e.g. never invent "@jane-smith"). If a commit message already ' +
|
|
105
|
+
'ends in a real PR reference like "(#123)", you may link directly to that PR using ' +
|
|
106
|
+
'the given repository path (e.g. https://github.com/<repo>/pull/123), but state only ' +
|
|
107
|
+
'what changed — no author mention.',
|
|
102
108
|
user: [
|
|
103
109
|
repo && `Repository: ${clip(repo, 200)}`,
|
|
104
110
|
tag && `Release: ${clip(tag, 100)}`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitset-dev/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.3",
|
|
4
4
|
"description": "Gitset CLI — drafts commits, PRs, issues, READMEs and release notes with your own AI key. Fully local: your code and keys never touch a Gitset server. Draft. Refine. Ship.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const { execFileSync } = require('child_process');
|
|
2
|
+
const { log } = require('../utils/ui');
|
|
3
|
+
const theme = require('../utils/theme');
|
|
4
|
+
|
|
5
|
+
function ghAvailable() {
|
|
6
|
+
try { execFileSync('gh', ['--version'], { stdio: 'ignore' }); return true; }
|
|
7
|
+
catch { return false; }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function showStatus() {
|
|
11
|
+
console.log(`\n=== Gitset Auth (via GitHub CLI) ===\n`);
|
|
12
|
+
try {
|
|
13
|
+
execFileSync('gh', ['auth', 'status'], { stdio: 'inherit' });
|
|
14
|
+
} catch {
|
|
15
|
+
log('\nYou are not logged in with the GitHub CLI. Run: gh auth login', 'yellow');
|
|
16
|
+
}
|
|
17
|
+
console.log(`
|
|
18
|
+
${theme.bold('How Gitset access works')}
|
|
19
|
+
Locally, Gitset uses your own ${theme.accent('gh')} authentication for every GitHub
|
|
20
|
+
action (issues, PRs, releases, labels). No Gitset account is involved.
|
|
21
|
+
|
|
22
|
+
${theme.bold('Missing an organization or repository?')}
|
|
23
|
+
- Local CLI: run ${theme.accent('gitset auth sync')} to re-run GitHub's authorization
|
|
24
|
+
and approve additional organizations or scopes.
|
|
25
|
+
- Web app: open ${theme.accent('https://gitset.dev/dashboard')} → GitHub connection →
|
|
26
|
+
Manage organization access.
|
|
27
|
+
`);
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function sync() {
|
|
32
|
+
console.log(`\n=== Gitset Auth Sync ===\n`);
|
|
33
|
+
log('Re-running GitHub authorization. Approve any missing organizations when your browser opens.', 'dim');
|
|
34
|
+
try {
|
|
35
|
+
execFileSync('gh', ['auth', 'refresh'], { stdio: 'inherit' });
|
|
36
|
+
log('\n✔ GitHub access refreshed. Newly granted organizations are now available.', 'green');
|
|
37
|
+
return 0;
|
|
38
|
+
} catch {
|
|
39
|
+
log('\nAuthorization was not completed. You can retry with `gitset auth sync` or run `gh auth refresh` directly.', 'red');
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = function commandAuth(rest = []) {
|
|
45
|
+
if (!ghAvailable()) {
|
|
46
|
+
log('The `gh` CLI is required (https://cli.github.com). Gitset uses your own gh authentication — no Gitset account exists.', 'red');
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
const sub = (rest[0] || 'status').toLowerCase();
|
|
50
|
+
if (sub === 'sync' || sub === 'refresh') return sync();
|
|
51
|
+
if (sub === 'status') return showStatus();
|
|
52
|
+
log(`Unknown subcommand "${sub}". Use: gitset auth [status|sync]`, 'red');
|
|
53
|
+
return 1;
|
|
54
|
+
};
|
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
* `gitset labelspack` — manage & apply a label pack. Local pack +
|
|
5
5
|
* the user's `gh` CLI only. No backend, injection-safe (arg-array exec).
|
|
6
6
|
*
|
|
7
|
-
* gitset labelspack
|
|
8
|
-
* gitset labelspack --apply
|
|
9
|
-
* gitset labelspack --init
|
|
7
|
+
* gitset labelspack list the pack
|
|
8
|
+
* gitset labelspack --apply create/update those labels in the repo via gh
|
|
9
|
+
* gitset labelspack --init write the default pack to ~/.gitset/labels.md
|
|
10
|
+
* gitset labelspack --from-repo import the current repo's labels into the pack
|
|
10
11
|
* gitset labelspack --add --name x --color hex --description "..."
|
|
11
12
|
*/
|
|
12
13
|
const { execFileSync } = require('child_process');
|
|
13
14
|
const readline = require('readline');
|
|
14
|
-
const { getLabels, addLabel, writeDefaultPack, getRepoLabels } = require('../utils/labels');
|
|
15
|
+
const { getLabels, addLabel, writeDefaultPack, writePack, getRepoLabels } = require('../utils/labels');
|
|
15
16
|
|
|
16
17
|
const tty = () => process.stdout.isTTY;
|
|
17
18
|
const c = (code, s) => (tty() ? `\x1b[${code}m${s}\x1b[0m` : s);
|
|
@@ -35,6 +36,36 @@ async function runLabelspackCommand(argv) {
|
|
|
35
36
|
return 0;
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
if (argv.includes('--from-repo') || argv.includes('--sync')) {
|
|
40
|
+
if (!ghAvailable()) {
|
|
41
|
+
console.error(`${c('31', '✗')} GitHub CLI \`gh\` not found / not authenticated.`);
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
const repoLabels = getRepoLabels();
|
|
45
|
+
if (repoLabels.length === 0) {
|
|
46
|
+
console.error(`${c('31', '✗')} No labels found — run this inside a repository your \`gh\` account can access.`);
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
const { source, labels: current } = getLabels();
|
|
50
|
+
if (source.startsWith('local') && !argv.includes('--yes') && !argv.includes('-y')) {
|
|
51
|
+
if (!process.stdin.isTTY) {
|
|
52
|
+
console.error(`${c('31', '✗')} A label pack already exists (${current.length} labels). Pass --yes to overwrite.`);
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
const ok = (await ask(`Overwrite your existing pack (${current.length} labels) with ${repoLabels.length} labels from this repo? [y/N] `)).toLowerCase();
|
|
56
|
+
if (ok !== 'y') { console.log('Aborted.'); return 0; }
|
|
57
|
+
}
|
|
58
|
+
const imported = repoLabels.map((l) => ({
|
|
59
|
+
name: l.name,
|
|
60
|
+
color: String(l.color || '').replace('#', ''),
|
|
61
|
+
description: l.description || '',
|
|
62
|
+
}));
|
|
63
|
+
const f = writePack(imported);
|
|
64
|
+
console.log(`${c('32', '✓')} Imported ${imported.length} labels from this repository → ${f}`);
|
|
65
|
+
console.log(c('90', 'Apply them to any other repo: cd <repo> && gitset labelspack --apply'));
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
38
69
|
if (argv.includes('--add')) {
|
|
39
70
|
let name = flag(argv, '--name');
|
|
40
71
|
let color = flag(argv, '--color');
|
|
@@ -53,7 +84,7 @@ async function runLabelspackCommand(argv) {
|
|
|
53
84
|
|
|
54
85
|
const { source, labels } = getLabels();
|
|
55
86
|
|
|
56
|
-
if (!argv.includes('--apply')
|
|
87
|
+
if (!argv.includes('--apply')) {
|
|
57
88
|
console.log(c('1', `\nLabel pack (${source}) — ${labels.length} labels\n`));
|
|
58
89
|
for (const l of labels) {
|
|
59
90
|
console.log(` ${c('36', l.name)}${l.description ? ` ${c('90', l.description)}` : ''}`);
|
package/src/commands/release.js
CHANGED
|
@@ -4,6 +4,7 @@ const path = require('path');
|
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const { log, askQuestion, selectOption } = require('../utils/ui');
|
|
6
6
|
const genLocal = require('../../lib/generate-local');
|
|
7
|
+
const manifestLib = require('../../lib/manifest');
|
|
7
8
|
|
|
8
9
|
function execCommand(cmd) {
|
|
9
10
|
try {
|
|
@@ -32,11 +33,102 @@ function getCurrentBranch() {
|
|
|
32
33
|
return execCommand('git rev-parse --abbrev-ref HEAD') || 'HEAD';
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
function getRepoOwnerName() {
|
|
37
|
+
const remoteUrl = execCommand('git config --get remote.origin.url');
|
|
38
|
+
if (!remoteUrl) return '';
|
|
39
|
+
const match = remoteUrl.match(/[:/]([^/]+)\/([^/.]+)(?:\.git)?$/);
|
|
40
|
+
return match ? `${match[1]}/${match[2]}` : '';
|
|
41
|
+
}
|
|
42
|
+
|
|
35
43
|
function commitsToText(commits) {
|
|
36
44
|
if (!commits || commits.length === 0) return '';
|
|
37
45
|
return commits.map(c => `- ${c.hash || ''} ${c.message || ''} (${c.author || ''})`).join('\n');
|
|
38
46
|
}
|
|
39
47
|
|
|
48
|
+
async function syncManifestVersion(tagName) {
|
|
49
|
+
const newVersion = manifestLib.normalizeVersion(tagName);
|
|
50
|
+
if (!newVersion) return;
|
|
51
|
+
|
|
52
|
+
let fileNames;
|
|
53
|
+
try {
|
|
54
|
+
fileNames = fs.readdirSync(process.cwd());
|
|
55
|
+
} catch {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const found = manifestLib.detectManifestFile(fileNames);
|
|
60
|
+
if (!found) return;
|
|
61
|
+
|
|
62
|
+
const filePath = path.join(process.cwd(), found.file);
|
|
63
|
+
let content;
|
|
64
|
+
try {
|
|
65
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
66
|
+
} catch {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let oldVersion;
|
|
71
|
+
try {
|
|
72
|
+
oldVersion = manifestLib.readVersion(found.type, content);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
log(`\nCould not parse ${found.file}, skipping version sync (${err.message}).`, 'yellow');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!oldVersion) {
|
|
79
|
+
log(`\n${found.file} has no static top-level version field, skipping version sync.`, 'dim');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (oldVersion === newVersion) return;
|
|
84
|
+
|
|
85
|
+
let newContent;
|
|
86
|
+
try {
|
|
87
|
+
newContent = manifestLib.bumpVersion(found.type, content, newVersion);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
log(`\nCould not bump ${found.file}, skipping version sync (${err.message}).`, 'yellow');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!newContent) return;
|
|
93
|
+
|
|
94
|
+
log(`\n${found.file} version differs from this release:`, 'cyan');
|
|
95
|
+
log(` - version: ${oldVersion}`, 'red');
|
|
96
|
+
log(` + version: ${newVersion}`, 'green');
|
|
97
|
+
log(' Only the top-level version field changes — dependency versions are left untouched.', 'dim');
|
|
98
|
+
|
|
99
|
+
const bumpChoice = await selectOption(`Update ${found.file} to ${newVersion} as part of this release?`, [
|
|
100
|
+
{ label: `Yes, update ${found.file}`, value: 'yes' },
|
|
101
|
+
{ label: 'No, skip', value: 'no' },
|
|
102
|
+
]);
|
|
103
|
+
if (bumpChoice !== 'yes') return;
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
fs.writeFileSync(filePath, newContent);
|
|
107
|
+
execFileSync('git', ['add', found.file], { stdio: 'pipe' });
|
|
108
|
+
execFileSync('git', ['commit', '-m', `chore: bump version to ${newVersion}`], { stdio: 'pipe' });
|
|
109
|
+
log(`Committed: chore: bump version to ${newVersion}`, 'green');
|
|
110
|
+
} catch (err) {
|
|
111
|
+
log(`Failed to commit the version bump: ${err.message}`, 'red');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
log('\nThis commit needs to be pushed before creating the release, otherwise the release tag will not include it.', 'dim');
|
|
116
|
+
const pushChoice = await selectOption('Push this commit now?', [
|
|
117
|
+
{ label: 'Yes, push', value: 'yes' },
|
|
118
|
+
{ label: 'No, I\'ll push it myself', value: 'no' },
|
|
119
|
+
]);
|
|
120
|
+
if (pushChoice === 'yes') {
|
|
121
|
+
try {
|
|
122
|
+
execFileSync('git', ['push'], { stdio: 'inherit' });
|
|
123
|
+
log('Pushed.', 'green');
|
|
124
|
+
} catch {
|
|
125
|
+
log('Failed to push. Push manually before the release tag will reflect this version.', 'red');
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
log('Not pushed — the release tag will not include this version bump until you push it.', 'yellow');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
40
132
|
module.exports = async function commandRelease(options = {}) {
|
|
41
133
|
console.log('\n=== Gitset Release Manager (BYOAI) ===\n');
|
|
42
134
|
|
|
@@ -100,6 +192,7 @@ module.exports = async function commandRelease(options = {}) {
|
|
|
100
192
|
}
|
|
101
193
|
|
|
102
194
|
let currentNotes = '';
|
|
195
|
+
const repo = getRepoOwnerName();
|
|
103
196
|
|
|
104
197
|
const generate = async (instr = '') => {
|
|
105
198
|
log('\nGenerating release notes…', 'cyan');
|
|
@@ -108,6 +201,7 @@ module.exports = async function commandRelease(options = {}) {
|
|
|
108
201
|
tool: 'release',
|
|
109
202
|
ctx: {
|
|
110
203
|
tag: tagName,
|
|
204
|
+
repo,
|
|
111
205
|
commits: commitsToText(commits),
|
|
112
206
|
mode: commits.length === 0 ? 'manual' : 'summary',
|
|
113
207
|
instruction: instr,
|
|
@@ -163,7 +257,10 @@ module.exports = async function commandRelease(options = {}) {
|
|
|
163
257
|
const notesFile = 'RELEASE_NOTES.md';
|
|
164
258
|
fs.writeFileSync(notesFile, currentNotes);
|
|
165
259
|
log(`Saved notes to ${notesFile}`, 'dim');
|
|
166
|
-
|
|
260
|
+
|
|
261
|
+
await syncManifestVersion(tagName);
|
|
262
|
+
|
|
263
|
+
log('\nCreating release via gh CLI…', 'cyan');
|
|
167
264
|
try {
|
|
168
265
|
execFileSync('gh', ['release', 'create', tagName, '--title', tagName, '--notes-file', notesFile], { stdio: 'inherit' });
|
|
169
266
|
log('\nRelease created successfully!', 'green');
|
package/src/utils/labels.js
CHANGED
|
@@ -96,7 +96,13 @@ function getRepoLabels() {
|
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
function writePack(labels) {
|
|
100
|
+
fs.mkdirSync(DIR, { recursive: true, mode: 0o700 });
|
|
101
|
+
fs.writeFileSync(LABELS_FILE, serializePack(labels));
|
|
102
|
+
return LABELS_FILE;
|
|
103
|
+
}
|
|
104
|
+
|
|
99
105
|
module.exports = {
|
|
100
106
|
LABELS_FILE, DEFAULT_LABELS,
|
|
101
|
-
getLabels, addLabel, writeDefaultPack, getRepoLabels, parseLabelsYaml,
|
|
107
|
+
getLabels, addLabel, writeDefaultPack, writePack, getRepoLabels, parseLabelsYaml,
|
|
102
108
|
};
|
package/src/utils/ui.js
CHANGED
|
@@ -73,29 +73,31 @@ function askSecret(query) {
|
|
|
73
73
|
stdin.pause();
|
|
74
74
|
}
|
|
75
75
|
function onData(chunk) {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
76
|
+
const cleaned = String(chunk).replace(/\x1b\[[0-9;]*[a-zA-Z~]/g, '');
|
|
77
|
+
for (const c of cleaned) {
|
|
78
|
+
if (c === '\r' || c === '\n') {
|
|
79
|
+
cleanup();
|
|
80
|
+
process.stdout.write('\n');
|
|
81
|
+
resolve(value.trim());
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (c === '\u0003') { // Ctrl+C
|
|
85
|
+
cleanup();
|
|
86
|
+
process.stdout.write('\n');
|
|
87
|
+
reject(new Error('Cancelled.'));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (c === '\u007f' || c === '\b') { // backspace (DEL on most terminals, BS on some)
|
|
91
|
+
if (value.length) {
|
|
92
|
+
value = value.slice(0, -1);
|
|
93
|
+
process.stdout.write('\b \b');
|
|
94
|
+
}
|
|
95
|
+
continue;
|
|
93
96
|
}
|
|
94
|
-
|
|
97
|
+
if (c.charCodeAt(0) < 32) continue; // ignore other control bytes
|
|
98
|
+
value += c;
|
|
99
|
+
process.stdout.write('*');
|
|
95
100
|
}
|
|
96
|
-
if (ch.charCodeAt(0) < 32) return; // ignore other control/escape bytes
|
|
97
|
-
value += ch;
|
|
98
|
-
process.stdout.write('*');
|
|
99
101
|
}
|
|
100
102
|
stdin.on('data', onData);
|
|
101
103
|
});
|