linear-github-cli 1.3.0 → 1.3.4
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 +6 -6
- package/dist/branch-utils.js +29 -91
- package/dist/cli.js +10 -3
- package/dist/commands/commit-first.js +18 -3
- package/dist/commands/create-parent.js +30 -18
- package/dist/commands/create-sub.js +16 -15
- package/dist/github-client.js +12 -0
- package/dist/input-handler.js +15 -11
- package/dist/linear-client.js +35 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,9 +94,9 @@ lg parent
|
|
|
94
94
|
|
|
95
95
|
Follow the interactive prompts:
|
|
96
96
|
1. Select repository from dropdown
|
|
97
|
-
2. Enter issue title
|
|
97
|
+
2. Enter issue title (required)
|
|
98
98
|
3. Enter description (opens in editor)
|
|
99
|
-
4.
|
|
99
|
+
4. Set due date (YYYY-MM-DD, required)
|
|
100
100
|
5. Select GitHub labels (checkboxes). Choices: `feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `research`
|
|
101
101
|
6. Optionally select GitHub project
|
|
102
102
|
7. Optionally select Linear project (after sync)
|
|
@@ -112,9 +112,9 @@ lg sub
|
|
|
112
112
|
Follow the interactive prompts:
|
|
113
113
|
1. Select repository from dropdown
|
|
114
114
|
2. Select parent issue from list
|
|
115
|
-
3. Enter sub-issue title
|
|
115
|
+
3. Enter sub-issue title (required)
|
|
116
116
|
4. Enter description (opens in editor)
|
|
117
|
-
5.
|
|
117
|
+
5. Set due date (YYYY-MM-DD, required)
|
|
118
118
|
6. Select GitHub labels (same predefined list as above)
|
|
119
119
|
7. Optionally select Linear project (after sync)
|
|
120
120
|
|
|
@@ -132,7 +132,7 @@ lg create-sub --help
|
|
|
132
132
|
- ✅ **Project autocomplete**: Select GitHub and Linear projects from dropdowns
|
|
133
133
|
- ✅ **Parent issue selection**: Browse and select parent issues when creating sub-issues
|
|
134
134
|
- ✅ **GitHub label sync**: Multi-select from the seven standard labels (feat, fix, chore, docs, refactor, test, research); selections are mirrored to matching Linear team labels
|
|
135
|
-
- ✅ **Due date input**:
|
|
135
|
+
- ✅ **Due date input**: Required date picker with validation
|
|
136
136
|
- ✅ **Automatic Linear sync**: Waits for Linear sync and updates metadata (due date, project, labels)
|
|
137
137
|
- ✅ **Parent-child relationships**: Automatically links sub-issues to parent issues
|
|
138
138
|
- ✅ **Status automation**: Issues start in Linear backlog; rely on the Linear × GitHub PR automation for status changes
|
|
@@ -301,7 +301,7 @@ gh prms # Merge with squash and delete branch
|
|
|
301
301
|
### Workflow Overview
|
|
302
302
|
|
|
303
303
|
1. **Create issue** - Use `lg parent/sub` command
|
|
304
|
-
2. **Create branch** - Include issue number (e.g., `
|
|
304
|
+
2. **Create branch** - Include issue number (e.g., `username/LEA-123-task`)
|
|
305
305
|
3. **Create draft PR** - Right after branch creation, before work begins
|
|
306
306
|
- Include Linear issue ID in title (copy with `Cmd + .` in Linear)
|
|
307
307
|
- Include `solve: #123` or `Closes #123` in body
|
package/dist/branch-utils.js
CHANGED
|
@@ -1,37 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.sanitizeBranchName = sanitizeBranchName;
|
|
7
|
-
exports.selectBranchPrefix = selectBranchPrefix;
|
|
8
4
|
exports.generateBranchName = generateBranchName;
|
|
9
5
|
exports.extractLinearIssueId = extractLinearIssueId;
|
|
10
6
|
exports.extractBranchPrefix = extractBranchPrefix;
|
|
11
7
|
exports.checkUnpushedCommitsOnCurrentBranch = checkUnpushedCommitsOnCurrentBranch;
|
|
12
8
|
exports.createGitBranch = createGitBranch;
|
|
13
9
|
const child_process_1 = require("child_process");
|
|
14
|
-
const inquirer_1 = __importDefault(require("inquirer"));
|
|
15
10
|
/**
|
|
16
11
|
* Valid branch prefix types (must match commit_typed.sh)
|
|
17
12
|
*/
|
|
18
13
|
const VALID_BRANCH_PREFIXES = ['feat', 'fix', 'chore', 'docs', 'refactor', 'test', 'research'];
|
|
19
|
-
/**
|
|
20
|
-
* Maps GitHub labels to branch prefix types
|
|
21
|
-
* Since GitHub labels are already updated to match the standard list,
|
|
22
|
-
* labels should directly map to branch prefixes.
|
|
23
|
-
* @param label - GitHub label name (should be one of: feat, fix, chore, docs, refactor, test, research)
|
|
24
|
-
* @returns Branch prefix (same as label if valid, otherwise returns label as-is)
|
|
25
|
-
*/
|
|
26
|
-
function mapLabelToBranchPrefix(label) {
|
|
27
|
-
const labelLower = label.toLowerCase();
|
|
28
|
-
// If label is already a valid branch prefix, return it as-is
|
|
29
|
-
if (VALID_BRANCH_PREFIXES.includes(labelLower)) {
|
|
30
|
-
return labelLower;
|
|
31
|
-
}
|
|
32
|
-
// Fallback: return label as-is (shouldn't happen if labels are properly configured)
|
|
33
|
-
return labelLower;
|
|
34
|
-
}
|
|
35
14
|
/**
|
|
36
15
|
* Sanitizes a title string to be used as part of a git branch name
|
|
37
16
|
* - Converts to lowercase
|
|
@@ -65,88 +44,49 @@ function sanitizeBranchName(title) {
|
|
|
65
44
|
return sanitized;
|
|
66
45
|
}
|
|
67
46
|
/**
|
|
68
|
-
*
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
{ name: 'chore', value: 'chore' },
|
|
74
|
-
{ name: 'docs', value: 'docs' },
|
|
75
|
-
{ name: 'refactor', value: 'refactor' },
|
|
76
|
-
{ name: 'test', value: 'test' },
|
|
77
|
-
{ name: 'research', value: 'research' },
|
|
78
|
-
];
|
|
79
|
-
/**
|
|
80
|
-
* Selects a branch prefix from GitHub labels
|
|
81
|
-
* - If no labels: prompts user to select from standard prefixes
|
|
82
|
-
* - If one label: maps and returns branch prefix
|
|
83
|
-
* - If multiple labels: prompts user to select one
|
|
47
|
+
* Sanitizes a branch owner (username) to be used in a git branch name.
|
|
48
|
+
* - Converts to lowercase
|
|
49
|
+
* - Removes special characters (keeps alphanumeric and hyphens)
|
|
50
|
+
* - Collapses multiple consecutive hyphens
|
|
51
|
+
* - Removes leading/trailing hyphens
|
|
84
52
|
*
|
|
85
|
-
* @param
|
|
86
|
-
* @returns
|
|
53
|
+
* @param owner - The branch owner to sanitize
|
|
54
|
+
* @returns Sanitized branch owner portion
|
|
87
55
|
*/
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
? labels.map(label => ({
|
|
92
|
-
label,
|
|
93
|
-
prefix: mapLabelToBranchPrefix(label),
|
|
94
|
-
}))
|
|
95
|
-
: [];
|
|
96
|
-
// If no labels: prompt user to select from standard prefixes
|
|
97
|
-
if (mappedPrefixes.length === 0) {
|
|
98
|
-
const { selectedPrefix } = await inquirer_1.default.prompt([
|
|
99
|
-
{
|
|
100
|
-
type: 'list',
|
|
101
|
-
name: 'selectedPrefix',
|
|
102
|
-
message: 'Select branch prefix (no labels selected):',
|
|
103
|
-
choices: STANDARD_PREFIXES,
|
|
104
|
-
},
|
|
105
|
-
]);
|
|
106
|
-
return selectedPrefix;
|
|
107
|
-
}
|
|
108
|
-
// If only one label, return its mapped prefix
|
|
109
|
-
if (mappedPrefixes.length === 1) {
|
|
110
|
-
return mappedPrefixes[0].prefix;
|
|
56
|
+
function sanitizeBranchOwner(owner) {
|
|
57
|
+
if (!owner || owner.trim().length === 0) {
|
|
58
|
+
return '';
|
|
111
59
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}));
|
|
117
|
-
const { selectedPrefix } = await inquirer_1.default.prompt([
|
|
118
|
-
{
|
|
119
|
-
type: 'list',
|
|
120
|
-
name: 'selectedPrefix',
|
|
121
|
-
message: 'Select branch prefix (multiple labels selected):',
|
|
122
|
-
choices,
|
|
123
|
-
},
|
|
124
|
-
]);
|
|
125
|
-
return selectedPrefix;
|
|
60
|
+
let sanitized = owner.toLowerCase().replace(/[^a-z0-9-]/g, '');
|
|
61
|
+
sanitized = sanitized.replace(/-+/g, '-');
|
|
62
|
+
sanitized = sanitized.replace(/^-+|-+$/g, '');
|
|
63
|
+
return sanitized;
|
|
126
64
|
}
|
|
127
65
|
/**
|
|
128
|
-
* Generates a branch name from
|
|
129
|
-
* Format:
|
|
66
|
+
* Generates a branch name from owner, Linear ID, and title
|
|
67
|
+
* Format: owner/LinearID-sanitized-title
|
|
130
68
|
*
|
|
131
|
-
* @param
|
|
69
|
+
* @param owner - Branch owner (e.g., GitHub username)
|
|
132
70
|
* @param linearId - Linear issue ID (e.g., 'LEA-123')
|
|
133
71
|
* @param title - Issue title to sanitize
|
|
134
72
|
* @returns Full branch name
|
|
135
73
|
*/
|
|
136
|
-
function generateBranchName(
|
|
74
|
+
function generateBranchName(owner, linearId, title) {
|
|
75
|
+
const sanitizedOwner = sanitizeBranchOwner(owner);
|
|
137
76
|
const sanitizedTitle = sanitizeBranchName(title);
|
|
77
|
+
const ownerSegment = sanitizedOwner || 'user';
|
|
138
78
|
if (!sanitizedTitle) {
|
|
139
|
-
// If title is empty after sanitization, just use
|
|
140
|
-
return `${
|
|
79
|
+
// If title is empty after sanitization, just use owner and ID
|
|
80
|
+
return `${ownerSegment}/${linearId}`;
|
|
141
81
|
}
|
|
142
|
-
return `${
|
|
82
|
+
return `${ownerSegment}/${linearId}-${sanitizedTitle}`;
|
|
143
83
|
}
|
|
144
84
|
/**
|
|
145
85
|
* Extracts Linear issue ID from a branch name
|
|
146
|
-
* - Matches pattern:
|
|
86
|
+
* - Matches pattern: owner/LEA-123-title or owner/LEA-123
|
|
147
87
|
* - Uses regex to find Linear issue ID format: [A-Z]+-\d+
|
|
148
88
|
*
|
|
149
|
-
* @param branchName - Branch name (e.g., '
|
|
89
|
+
* @param branchName - Branch name (e.g., 'negoth/LEA-123-implement-login')
|
|
150
90
|
* @returns Linear issue ID (e.g., 'LEA-123') or null if not found
|
|
151
91
|
*/
|
|
152
92
|
function extractLinearIssueId(branchName) {
|
|
@@ -161,27 +101,25 @@ function extractLinearIssueId(branchName) {
|
|
|
161
101
|
* Extracts branch prefix (commit type) from a branch name
|
|
162
102
|
* - Extracts the part before the first '/' (e.g., 'research' from 'research/LEA-75-probit-model')
|
|
163
103
|
* - Validates against VALID_BRANCH_PREFIXES
|
|
164
|
-
* - Returns 'feat' as default if prefix is not found or invalid
|
|
165
104
|
*
|
|
166
105
|
* @param branchName - Branch name (e.g., 'research/LEA-75-probit-model')
|
|
167
|
-
* @returns Branch prefix (e.g., 'research') or
|
|
106
|
+
* @returns Branch prefix (e.g., 'research') or null if not found/invalid
|
|
168
107
|
*/
|
|
169
108
|
function extractBranchPrefix(branchName) {
|
|
170
109
|
if (!branchName || branchName.trim().length === 0) {
|
|
171
|
-
return
|
|
110
|
+
return null;
|
|
172
111
|
}
|
|
173
112
|
// Extract prefix before first '/'
|
|
174
113
|
const parts = branchName.split('/');
|
|
175
114
|
if (parts.length === 0 || !parts[0]) {
|
|
176
|
-
return
|
|
115
|
+
return null;
|
|
177
116
|
}
|
|
178
117
|
const prefix = parts[0].toLowerCase();
|
|
179
118
|
// Validate against valid prefixes
|
|
180
119
|
if (VALID_BRANCH_PREFIXES.includes(prefix)) {
|
|
181
120
|
return prefix;
|
|
182
121
|
}
|
|
183
|
-
|
|
184
|
-
return 'feat';
|
|
122
|
+
return null;
|
|
185
123
|
}
|
|
186
124
|
/**
|
|
187
125
|
* Checks for unpushed commits on the current branch
|
package/dist/cli.js
CHANGED
|
@@ -13,9 +13,16 @@ const create_sub_1 = require("./commands/create-sub");
|
|
|
13
13
|
const env_utils_1 = require("./env-utils");
|
|
14
14
|
// Load .env file from current working directory, parent directories, or home directory
|
|
15
15
|
(0, env_utils_1.loadEnvFile)();
|
|
16
|
-
// Check for updates
|
|
17
|
-
const
|
|
18
|
-
(0,
|
|
16
|
+
// Check for updates (support both dev and built paths)
|
|
17
|
+
const pkgPathCandidates = [
|
|
18
|
+
(0, path_1.resolve)(__dirname, 'package.json'),
|
|
19
|
+
(0, path_1.resolve)(__dirname, '..', 'package.json'),
|
|
20
|
+
];
|
|
21
|
+
const pkgPath = pkgPathCandidates.find(candidate => (0, fs_1.existsSync)(candidate));
|
|
22
|
+
if (pkgPath) {
|
|
23
|
+
const pkg = JSON.parse((0, fs_1.readFileSync)(pkgPath, 'utf-8'));
|
|
24
|
+
(0, update_notifier_1.default)({ pkg }).notify();
|
|
25
|
+
}
|
|
19
26
|
const program = new commander_1.Command();
|
|
20
27
|
program
|
|
21
28
|
.name('lg')
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.commitFirst = commitFirst;
|
|
4
7
|
const child_process_1 = require("child_process");
|
|
8
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
5
9
|
const branch_utils_1 = require("../branch-utils");
|
|
6
10
|
const linear_client_1 = require("../linear-client");
|
|
7
11
|
const env_utils_1 = require("../env-utils");
|
|
@@ -37,14 +41,25 @@ async function commitFirst() {
|
|
|
37
41
|
console.error('❌ Error: Not in a git repository or unable to get branch name');
|
|
38
42
|
process.exit(1);
|
|
39
43
|
}
|
|
40
|
-
// Step 2: Extract branch prefix and Linear issue ID from branch name
|
|
41
|
-
|
|
44
|
+
// Step 2: Extract branch prefix (if any) and Linear issue ID from branch name
|
|
45
|
+
let prefix = (0, branch_utils_1.extractBranchPrefix)(branchName);
|
|
42
46
|
const linearId = (0, branch_utils_1.extractLinearIssueId)(branchName);
|
|
43
47
|
if (!linearId) {
|
|
44
48
|
console.error(`❌ Error: Could not extract Linear issue ID from branch name: ${branchName}`);
|
|
45
|
-
console.error(' Branch name should follow pattern:
|
|
49
|
+
console.error(' Branch name should follow pattern: username/LEA-123-title');
|
|
46
50
|
process.exit(1);
|
|
47
51
|
}
|
|
52
|
+
if (!prefix) {
|
|
53
|
+
const { selectedPrefix } = await inquirer_1.default.prompt([
|
|
54
|
+
{
|
|
55
|
+
type: 'list',
|
|
56
|
+
name: 'selectedPrefix',
|
|
57
|
+
message: 'Select commit type:',
|
|
58
|
+
choices: ['feat', 'fix', 'chore', 'docs', 'refactor', 'test', 'research'],
|
|
59
|
+
},
|
|
60
|
+
]);
|
|
61
|
+
prefix = selectedPrefix;
|
|
62
|
+
}
|
|
48
63
|
console.log(`📋 Found Linear issue ID: ${linearId}`);
|
|
49
64
|
console.log(`📋 Using commit type: ${prefix}`);
|
|
50
65
|
// Step 3: Initialize Linear client and fetch issue data
|
|
@@ -83,22 +83,30 @@ async function createParentIssue() {
|
|
|
83
83
|
});
|
|
84
84
|
if (linearIssueId) {
|
|
85
85
|
console.log('✅ Found Linear issue, updating metadata...');
|
|
86
|
-
// Auto-find Linear project if GitHub project was selected
|
|
86
|
+
// Auto-find/create Linear project if GitHub project was selected
|
|
87
87
|
let linearProjectId = null;
|
|
88
|
+
let linearProjectName = null;
|
|
88
89
|
if (githubProject) {
|
|
89
90
|
console.log(` Looking for Linear project matching "${githubProject}"...`);
|
|
90
91
|
linearProjectId = await linearClient.findProjectByName(githubProject);
|
|
91
92
|
if (linearProjectId) {
|
|
93
|
+
linearProjectName = githubProject;
|
|
92
94
|
console.log(` ✅ Found matching Linear project: ${githubProject}`);
|
|
93
95
|
}
|
|
94
96
|
else {
|
|
95
|
-
console.log(` ⚠️ No matching Linear project found.
|
|
97
|
+
console.log(` ⚠️ No matching Linear project found. Creating "${githubProject}"...`);
|
|
98
|
+
const teamId = await linearClient.getIssueTeamId(linearIssueId);
|
|
99
|
+
const createdProject = await linearClient.createProject(githubProject, teamId || undefined);
|
|
100
|
+
if (createdProject) {
|
|
101
|
+
linearProjectId = createdProject.id;
|
|
102
|
+
linearProjectName = createdProject.name;
|
|
103
|
+
console.log(` ✅ Created Linear project: ${createdProject.name}`);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
console.log(` ⚠️ Failed to create Linear project. You can set it manually.`);
|
|
107
|
+
}
|
|
96
108
|
}
|
|
97
109
|
}
|
|
98
|
-
// If no auto-match, ask user (only if GitHub project wasn't selected)
|
|
99
|
-
if (!linearProjectId && !githubProject) {
|
|
100
|
-
linearProjectId = await inputHandler.selectLinearProject();
|
|
101
|
-
}
|
|
102
110
|
// Set labels on Linear issue
|
|
103
111
|
if (details.labels && details.labels.length > 0) {
|
|
104
112
|
console.log(` Setting labels: ${details.labels.join(', ')}`);
|
|
@@ -115,7 +123,7 @@ async function createParentIssue() {
|
|
|
115
123
|
if (success) {
|
|
116
124
|
console.log('✅ Linear issue metadata updated!');
|
|
117
125
|
if (linearProjectId) {
|
|
118
|
-
console.log(` Project: ${githubProject || '
|
|
126
|
+
console.log(` Project: ${linearProjectName || githubProject || 'linked'}`);
|
|
119
127
|
}
|
|
120
128
|
if (details.dueDate) {
|
|
121
129
|
console.log(` Due date: ${details.dueDate}`);
|
|
@@ -144,18 +152,22 @@ async function createParentIssue() {
|
|
|
144
152
|
console.log(` GitHub issue #${issue.number}`);
|
|
145
153
|
}
|
|
146
154
|
else {
|
|
147
|
-
|
|
148
|
-
if (
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
let branchOwner = await githubClient.getCurrentUsername();
|
|
156
|
+
if (!branchOwner) {
|
|
157
|
+
const { ownerInput } = await inquirer_1.default.prompt([
|
|
158
|
+
{
|
|
159
|
+
type: 'input',
|
|
160
|
+
name: 'ownerInput',
|
|
161
|
+
message: 'Branch username for naming (e.g., your GitHub login):',
|
|
162
|
+
validate: (input) => input.trim().length > 0 || 'Username is required',
|
|
163
|
+
},
|
|
164
|
+
]);
|
|
165
|
+
branchOwner = ownerInput.trim();
|
|
156
166
|
}
|
|
157
|
-
|
|
158
|
-
|
|
167
|
+
const branchName = (0, branch_utils_1.generateBranchName)(branchOwner ?? 'user', linearIssueIdentifier, details.title);
|
|
168
|
+
const success = await (0, branch_utils_1.createGitBranch)(branchName);
|
|
169
|
+
if (success) {
|
|
170
|
+
console.log(`✅ Branch created: ${branchName}`);
|
|
159
171
|
console.log(` Linear issue ID: ${linearIssueIdentifier}`);
|
|
160
172
|
console.log(` GitHub issue #${issue.number}`);
|
|
161
173
|
}
|
|
@@ -110,10 +110,7 @@ async function createSubIssue() {
|
|
|
110
110
|
else {
|
|
111
111
|
console.log(` ⚠️ Parent Linear issue not found yet (may need more time to sync)`);
|
|
112
112
|
}
|
|
113
|
-
// If no parent project,
|
|
114
|
-
if (!linearProjectId) {
|
|
115
|
-
linearProjectId = await inputHandler.selectLinearProject();
|
|
116
|
-
}
|
|
113
|
+
// If no parent project, leave project unset (no prompt)
|
|
117
114
|
// Set labels on Linear issue
|
|
118
115
|
if (details.labels && details.labels.length > 0) {
|
|
119
116
|
console.log(` Setting labels: ${details.labels.join(', ')}`);
|
|
@@ -172,18 +169,22 @@ async function createSubIssue() {
|
|
|
172
169
|
console.log(` GitHub issue #${subIssue.number}`);
|
|
173
170
|
}
|
|
174
171
|
else {
|
|
175
|
-
|
|
176
|
-
if (
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
172
|
+
let branchOwner = await githubClient.getCurrentUsername();
|
|
173
|
+
if (!branchOwner) {
|
|
174
|
+
const { ownerInput } = await inquirer_1.default.prompt([
|
|
175
|
+
{
|
|
176
|
+
type: 'input',
|
|
177
|
+
name: 'ownerInput',
|
|
178
|
+
message: 'Branch username for naming (e.g., your GitHub login):',
|
|
179
|
+
validate: (input) => input.trim().length > 0 || 'Username is required',
|
|
180
|
+
},
|
|
181
|
+
]);
|
|
182
|
+
branchOwner = ownerInput.trim();
|
|
184
183
|
}
|
|
185
|
-
|
|
186
|
-
|
|
184
|
+
const branchName = (0, branch_utils_1.generateBranchName)(branchOwner ?? 'user', linearIssueIdentifier, details.title);
|
|
185
|
+
const success = await (0, branch_utils_1.createGitBranch)(branchName);
|
|
186
|
+
if (success) {
|
|
187
|
+
console.log(`✅ Branch created: ${branchName}`);
|
|
187
188
|
console.log(` Linear issue ID: ${linearIssueIdentifier}`);
|
|
188
189
|
console.log(` GitHub issue #${subIssue.number}`);
|
|
189
190
|
}
|
package/dist/github-client.js
CHANGED
|
@@ -12,6 +12,18 @@ class GitHubClientWrapper {
|
|
|
12
12
|
this.octokit = new rest_1.Octokit({ auth: token });
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
+
async getCurrentUsername() {
|
|
16
|
+
try {
|
|
17
|
+
const output = (0, child_process_1.execSync)('gh api user -q .login', {
|
|
18
|
+
encoding: 'utf-8',
|
|
19
|
+
stdio: 'pipe',
|
|
20
|
+
}).trim();
|
|
21
|
+
return output || null;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
15
27
|
async getRepositories() {
|
|
16
28
|
// Use gh CLI to get accessible repos
|
|
17
29
|
const output = (0, child_process_1.execSync)('gh repo list --limit 100 --json nameWithOwner', {
|
package/dist/input-handler.js
CHANGED
|
@@ -6,6 +6,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.InputHandler = void 0;
|
|
7
7
|
const child_process_1 = require("child_process");
|
|
8
8
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
9
|
+
const isValidDateYmd = (input) => {
|
|
10
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(input))
|
|
11
|
+
return false;
|
|
12
|
+
const [year, month, day] = input.split('-').map(Number);
|
|
13
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
14
|
+
return (date.getUTCFullYear() === year &&
|
|
15
|
+
date.getUTCMonth() === month - 1 &&
|
|
16
|
+
date.getUTCDate() === day);
|
|
17
|
+
};
|
|
9
18
|
class InputHandler {
|
|
10
19
|
linearClient;
|
|
11
20
|
githubClient;
|
|
@@ -150,7 +159,7 @@ class InputHandler {
|
|
|
150
159
|
{
|
|
151
160
|
type: 'input',
|
|
152
161
|
name: 'title',
|
|
153
|
-
message: 'Issue title:',
|
|
162
|
+
message: 'Issue title (required):',
|
|
154
163
|
validate: (input) => input.length > 0 || 'Title is required',
|
|
155
164
|
},
|
|
156
165
|
{
|
|
@@ -169,29 +178,24 @@ class InputHandler {
|
|
|
169
178
|
validate: (input) => {
|
|
170
179
|
if (!input)
|
|
171
180
|
return true; // Optional
|
|
172
|
-
|
|
173
|
-
return !isNaN(date.getTime()) || 'Invalid date format';
|
|
181
|
+
return isValidDateYmd(input) || 'Invalid date format';
|
|
174
182
|
},
|
|
175
183
|
},
|
|
176
184
|
{
|
|
177
185
|
type: 'input',
|
|
178
186
|
name: 'dueDate',
|
|
179
|
-
message: 'Due date (YYYY-MM-DD):',
|
|
187
|
+
message: 'Due date (YYYY-MM-DD, required):',
|
|
180
188
|
validate: (input) => {
|
|
181
189
|
if (!input)
|
|
182
|
-
return
|
|
183
|
-
|
|
184
|
-
return !isNaN(date.getTime()) || 'Invalid date format';
|
|
190
|
+
return 'Due date is required';
|
|
191
|
+
return isValidDateYmd(input) || 'Invalid date format';
|
|
185
192
|
},
|
|
186
193
|
},
|
|
187
194
|
{
|
|
188
195
|
type: 'checkbox',
|
|
189
196
|
name: 'labels',
|
|
190
|
-
message: 'Select GitHub labels (
|
|
197
|
+
message: 'Select GitHub labels (optional):',
|
|
191
198
|
choices: labelChoices,
|
|
192
|
-
validate: (input) => {
|
|
193
|
-
return input.length > 0 || 'At least one label is required';
|
|
194
|
-
},
|
|
195
199
|
},
|
|
196
200
|
]);
|
|
197
201
|
let description = '';
|
package/dist/linear-client.js
CHANGED
|
@@ -28,6 +28,41 @@ class LinearClientWrapper {
|
|
|
28
28
|
return project?.id || null;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
async createProject(projectName, teamId) {
|
|
32
|
+
try {
|
|
33
|
+
const mutation = `mutation CreateProject($input: ProjectCreateInput!) {
|
|
34
|
+
projectCreate(input: $input) {
|
|
35
|
+
success
|
|
36
|
+
project { id name }
|
|
37
|
+
}
|
|
38
|
+
}`;
|
|
39
|
+
const input = { name: projectName };
|
|
40
|
+
if (teamId) {
|
|
41
|
+
input.teamIds = [teamId];
|
|
42
|
+
}
|
|
43
|
+
const response = await fetch('https://api.linear.app/graphql', {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: {
|
|
46
|
+
'Content-Type': 'application/json',
|
|
47
|
+
'Authorization': linearApiKey,
|
|
48
|
+
},
|
|
49
|
+
body: JSON.stringify({
|
|
50
|
+
query: mutation,
|
|
51
|
+
variables: { input },
|
|
52
|
+
}),
|
|
53
|
+
});
|
|
54
|
+
const result = await response.json();
|
|
55
|
+
if (result.errors || !result.data?.projectCreate?.success) {
|
|
56
|
+
console.error(`❌ Failed to create project "${projectName}":`, result.errors);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return result.data.projectCreate.project ?? null;
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
console.error(`❌ Error creating project "${projectName}":`, error);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
31
66
|
async getWorkflowStates(teamId) {
|
|
32
67
|
try {
|
|
33
68
|
// Get workflow states from teams
|