linear-github-cli 1.0.2 → 1.1.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/dist/cli.js +4 -5
- package/dist/commands/commit-first.js +3 -5
- package/dist/commands/create-parent.js +5 -0
- package/dist/commands/create-sub.js +12 -0
- package/dist/env-utils.js +43 -0
- package/dist/github-client.js +245 -0
- package/dist/input-handler.js +12 -0
- package/dist/lgcmf.js +3 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5,15 +5,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
const commander_1 = require("commander");
|
|
8
|
-
const dotenv_1 = require("dotenv");
|
|
9
8
|
const fs_1 = require("fs");
|
|
10
9
|
const path_1 = require("path");
|
|
11
10
|
const update_notifier_1 = __importDefault(require("update-notifier"));
|
|
12
11
|
const create_parent_1 = require("./commands/create-parent");
|
|
13
12
|
const create_sub_1 = require("./commands/create-sub");
|
|
14
|
-
|
|
15
|
-
//
|
|
16
|
-
(0,
|
|
13
|
+
const env_utils_1 = require("./env-utils");
|
|
14
|
+
// Load .env file from current working directory, parent directories, or home directory
|
|
15
|
+
(0, env_utils_1.loadEnvFile)();
|
|
17
16
|
// Check for updates
|
|
18
17
|
const pkg = JSON.parse((0, fs_1.readFileSync)((0, path_1.resolve)(__dirname, '..', 'package.json'), 'utf-8'));
|
|
19
18
|
(0, update_notifier_1.default)({ pkg }).notify();
|
|
@@ -21,7 +20,7 @@ const program = new commander_1.Command();
|
|
|
21
20
|
program
|
|
22
21
|
.name('lg')
|
|
23
22
|
.description('Linear + GitHub Integration CLI - Create GitHub issues with Linear sync')
|
|
24
|
-
.version('1.0
|
|
23
|
+
.version('1.1.0');
|
|
25
24
|
program
|
|
26
25
|
.command('create-parent')
|
|
27
26
|
.alias('parent')
|
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.commitFirst = commitFirst;
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
|
-
const dotenv_1 = require("dotenv");
|
|
6
|
-
const path_1 = require("path");
|
|
7
5
|
const branch_utils_1 = require("../branch-utils");
|
|
8
6
|
const linear_client_1 = require("../linear-client");
|
|
9
|
-
|
|
10
|
-
//
|
|
11
|
-
(0,
|
|
7
|
+
const env_utils_1 = require("../env-utils");
|
|
8
|
+
// Load .env file from current working directory, parent directories, or home directory
|
|
9
|
+
(0, env_utils_1.loadEnvFile)();
|
|
12
10
|
/**
|
|
13
11
|
* Creates the first commit with proper message format
|
|
14
12
|
* - Extracts branch prefix (commit type) and Linear issue ID from current branch name
|
|
@@ -65,6 +65,11 @@ async function createParentIssue() {
|
|
|
65
65
|
project: githubProject || undefined,
|
|
66
66
|
});
|
|
67
67
|
console.log(`✅ GitHub Issue #${issue.number} created: ${issue.url}`);
|
|
68
|
+
// Set GitHub Project date fields if project is selected
|
|
69
|
+
if (githubProject && (details.dueDate || details.startDate)) {
|
|
70
|
+
console.log('\n📅 Setting GitHub Project date fields...');
|
|
71
|
+
await githubClient.setProjectDateFields(repo, githubProject, issue.id, details.dueDate || undefined, details.startDate || undefined);
|
|
72
|
+
}
|
|
68
73
|
// Step 5: Wait for Linear sync, then update metadata
|
|
69
74
|
console.log('\n⏳ Waiting for Linear sync (5 seconds)...');
|
|
70
75
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
@@ -67,6 +67,18 @@ async function createSubIssue() {
|
|
|
67
67
|
});
|
|
68
68
|
console.log(`✅ Sub-Issue #${subIssue.number} created: ${subIssue.url}`);
|
|
69
69
|
console.log(` Parent: #${parentIssueNumber}`);
|
|
70
|
+
// Set GitHub Project date fields if dates are provided
|
|
71
|
+
if (details.dueDate || details.startDate) {
|
|
72
|
+
console.log('\n📅 Setting GitHub Project date fields...');
|
|
73
|
+
// sub-issueから直接プロジェクト情報を取得(Auto-add sub-issues to projectが有効な場合)
|
|
74
|
+
const projectName = await githubClient.getIssueProject(repo, subIssue.id);
|
|
75
|
+
if (projectName) {
|
|
76
|
+
await githubClient.setProjectDateFields(repo, projectName, subIssue.id, details.dueDate || undefined, details.startDate || undefined);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
console.log(' ⚠️ Sub-issue has no GitHub Project. Skipping date field setting.');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
70
82
|
// Step 5: Wait for Linear sync, then update metadata
|
|
71
83
|
console.log('\n⏳ Waiting for Linear sync (5 seconds)...');
|
|
72
84
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadEnvFile = loadEnvFile;
|
|
4
|
+
const dotenv_1 = require("dotenv");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
const os_1 = require("os");
|
|
8
|
+
/**
|
|
9
|
+
* Finds and loads .env file from multiple locations:
|
|
10
|
+
* 1. Current working directory and parent directories (up to filesystem root)
|
|
11
|
+
* 2. Home directory (~/.env)
|
|
12
|
+
*
|
|
13
|
+
* This allows the CLI to work both when:
|
|
14
|
+
* - Installed globally (user can create .env in their project or home directory)
|
|
15
|
+
* - Installed locally (works with project's .env file)
|
|
16
|
+
*/
|
|
17
|
+
function loadEnvFile() {
|
|
18
|
+
// First, try to find .env in current working directory and parent directories
|
|
19
|
+
let currentDir = process.cwd();
|
|
20
|
+
const root = process.platform === 'win32' ? currentDir.split('\\')[0] + '\\' : '/';
|
|
21
|
+
while (currentDir !== root) {
|
|
22
|
+
const envPath = (0, path_1.resolve)(currentDir, '.env');
|
|
23
|
+
if ((0, fs_1.existsSync)(envPath)) {
|
|
24
|
+
(0, dotenv_1.config)({ path: envPath });
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const parentDir = (0, path_1.dirname)(currentDir);
|
|
28
|
+
// Stop if we've reached the filesystem root (parent is same as current)
|
|
29
|
+
if (parentDir === currentDir) {
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
currentDir = parentDir;
|
|
33
|
+
}
|
|
34
|
+
// Fallback: try home directory
|
|
35
|
+
const homeEnvPath = (0, path_1.resolve)((0, os_1.homedir)(), '.env');
|
|
36
|
+
if ((0, fs_1.existsSync)(homeEnvPath)) {
|
|
37
|
+
(0, dotenv_1.config)({ path: homeEnvPath });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// If no .env file found, dotenv will use environment variables
|
|
41
|
+
// This is fine - we don't need to throw an error here
|
|
42
|
+
(0, dotenv_1.config)();
|
|
43
|
+
}
|
package/dist/github-client.js
CHANGED
|
@@ -119,5 +119,250 @@ class GitHubClientWrapper {
|
|
|
119
119
|
];
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Get project node ID by project name
|
|
124
|
+
* Supports both Organization and User accounts
|
|
125
|
+
*/
|
|
126
|
+
async getProjectNodeId(repo, projectName) {
|
|
127
|
+
try {
|
|
128
|
+
const [owner] = repo.split('/');
|
|
129
|
+
let projects = [];
|
|
130
|
+
// First try Organization
|
|
131
|
+
try {
|
|
132
|
+
const orgQuery = `query {
|
|
133
|
+
organization(login: "${owner}") {
|
|
134
|
+
projectsV2(first: 50) {
|
|
135
|
+
nodes {
|
|
136
|
+
id
|
|
137
|
+
title
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}`;
|
|
142
|
+
const orgOutput = (0, child_process_1.execSync)(`gh api graphql -f query="${orgQuery.replace(/"/g, '\\"')}"`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
143
|
+
const orgResult = JSON.parse(orgOutput);
|
|
144
|
+
if (orgResult.data?.organization) {
|
|
145
|
+
projects = orgResult.data.organization.projectsV2?.nodes || [];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (orgError) {
|
|
149
|
+
// Organization not found or error occurred, will try User below
|
|
150
|
+
}
|
|
151
|
+
// If no projects found from Organization, try User
|
|
152
|
+
if (projects.length === 0) {
|
|
153
|
+
try {
|
|
154
|
+
const userQuery = `query {
|
|
155
|
+
user(login: "${owner}") {
|
|
156
|
+
projectsV2(first: 50) {
|
|
157
|
+
nodes {
|
|
158
|
+
id
|
|
159
|
+
title
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}`;
|
|
164
|
+
const userOutput = (0, child_process_1.execSync)(`gh api graphql -f query="${userQuery.replace(/"/g, '\\"')}"`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
165
|
+
const userResult = JSON.parse(userOutput);
|
|
166
|
+
if (userResult.data?.user) {
|
|
167
|
+
projects = userResult.data.user.projectsV2?.nodes || [];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch (userError) {
|
|
171
|
+
// User not found or error occurred
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const project = projects.find((p) => p.title === projectName);
|
|
175
|
+
return project?.id || null;
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
console.error('⚠️ Failed to get project node ID:', error);
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Get project field ID by field name
|
|
184
|
+
*/
|
|
185
|
+
async getProjectFieldId(projectId, fieldName) {
|
|
186
|
+
try {
|
|
187
|
+
const query = `query {
|
|
188
|
+
node(id: "${projectId}") {
|
|
189
|
+
... on ProjectV2 {
|
|
190
|
+
fields(first: 50) {
|
|
191
|
+
nodes {
|
|
192
|
+
... on ProjectV2FieldCommon {
|
|
193
|
+
id
|
|
194
|
+
name
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}`;
|
|
201
|
+
const output = (0, child_process_1.execSync)(`gh api graphql -f query="${query.replace(/"/g, '\\"')}"`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
202
|
+
const result = JSON.parse(output);
|
|
203
|
+
const fields = result.data?.node?.fields?.nodes || [];
|
|
204
|
+
const field = fields.find((f) => f.name === fieldName);
|
|
205
|
+
return field?.id || null;
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
console.error(`⚠️ Failed to get field ID for "${fieldName}":`, error);
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get project item ID by issue ID
|
|
214
|
+
*/
|
|
215
|
+
async getProjectItemId(projectId, issueId) {
|
|
216
|
+
try {
|
|
217
|
+
const query = `query {
|
|
218
|
+
node(id: "${projectId}") {
|
|
219
|
+
... on ProjectV2 {
|
|
220
|
+
items(first: 100) {
|
|
221
|
+
nodes {
|
|
222
|
+
id
|
|
223
|
+
content {
|
|
224
|
+
... on Issue {
|
|
225
|
+
id
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}`;
|
|
233
|
+
const output = (0, child_process_1.execSync)(`gh api graphql -f query="${query.replace(/"/g, '\\"')}"`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
234
|
+
const result = JSON.parse(output);
|
|
235
|
+
const items = result.data?.node?.items?.nodes || [];
|
|
236
|
+
const item = items.find((i) => i.content?.id === issueId);
|
|
237
|
+
return item?.id || null;
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
console.error('⚠️ Failed to get project item ID:', error);
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Set date field value for a project item
|
|
246
|
+
*/
|
|
247
|
+
async setProjectItemDateField(projectId, itemId, fieldId, date) {
|
|
248
|
+
try {
|
|
249
|
+
const mutation = `mutation {
|
|
250
|
+
updateProjectV2ItemFieldValue(
|
|
251
|
+
input: {
|
|
252
|
+
projectId: "${projectId}"
|
|
253
|
+
itemId: "${itemId}"
|
|
254
|
+
fieldId: "${fieldId}"
|
|
255
|
+
value: {
|
|
256
|
+
date: "${date}"
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
) {
|
|
260
|
+
projectV2Item {
|
|
261
|
+
id
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}`;
|
|
265
|
+
const output = (0, child_process_1.execSync)(`gh api graphql -f query="${mutation.replace(/"/g, '\\"')}"`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
266
|
+
const result = JSON.parse(output);
|
|
267
|
+
return !!result.data?.updateProjectV2ItemFieldValue?.projectV2Item;
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
console.error('⚠️ Failed to set project date field:', error);
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Set GitHub Project date fields (Target and Start) for an issue
|
|
276
|
+
*/
|
|
277
|
+
async setProjectDateFields(repo, projectName, issueId, targetDate, startDate) {
|
|
278
|
+
try {
|
|
279
|
+
// Get project node ID
|
|
280
|
+
const projectId = await this.getProjectNodeId(repo, projectName);
|
|
281
|
+
if (!projectId) {
|
|
282
|
+
console.error(`⚠️ Project "${projectName}" not found`);
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
// Get project item ID
|
|
286
|
+
const itemId = await this.getProjectItemId(projectId, issueId);
|
|
287
|
+
if (!itemId) {
|
|
288
|
+
console.error(`⚠️ Issue not found in project "${projectName}"`);
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
let success = true;
|
|
292
|
+
// Set Target date field if provided
|
|
293
|
+
if (targetDate) {
|
|
294
|
+
const targetFieldId = await this.getProjectFieldId(projectId, 'Target');
|
|
295
|
+
if (targetFieldId) {
|
|
296
|
+
const result = await this.setProjectItemDateField(projectId, itemId, targetFieldId, targetDate);
|
|
297
|
+
if (result) {
|
|
298
|
+
console.log(` ✅ Set Target date: ${targetDate}`);
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
console.log(` ⚠️ Failed to set Target date`);
|
|
302
|
+
success = false;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
console.log(` ⚠️ Target field not found in project`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// Set Start date field if provided
|
|
310
|
+
if (startDate) {
|
|
311
|
+
const startFieldId = await this.getProjectFieldId(projectId, 'Start');
|
|
312
|
+
if (startFieldId) {
|
|
313
|
+
const result = await this.setProjectItemDateField(projectId, itemId, startFieldId, startDate);
|
|
314
|
+
if (result) {
|
|
315
|
+
console.log(` ✅ Set Start date: ${startDate}`);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
console.log(` ⚠️ Failed to set Start date`);
|
|
319
|
+
success = false;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
console.log(` ⚠️ Start field not found in project`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return success;
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
console.error('⚠️ Failed to set project date fields:', error);
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Get GitHub Project name from issue ID
|
|
335
|
+
*/
|
|
336
|
+
async getIssueProject(repo, issueId) {
|
|
337
|
+
try {
|
|
338
|
+
const [owner, name] = repo.split('/');
|
|
339
|
+
const query = `query {
|
|
340
|
+
repository(owner: "${owner}", name: "${name}") {
|
|
341
|
+
issue(id: "${issueId}") {
|
|
342
|
+
projectItems(first: 10) {
|
|
343
|
+
nodes {
|
|
344
|
+
project {
|
|
345
|
+
... on ProjectV2 {
|
|
346
|
+
title
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}`;
|
|
354
|
+
const output = (0, child_process_1.execSync)(`gh api graphql -f query="${query.replace(/"/g, '\\"')}"`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
355
|
+
const result = JSON.parse(output);
|
|
356
|
+
const projectItems = result.data?.repository?.issue?.projectItems?.nodes || [];
|
|
357
|
+
if (projectItems.length > 0) {
|
|
358
|
+
return projectItems[0].project?.title || null;
|
|
359
|
+
}
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
console.error('⚠️ Failed to get issue project:', error);
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
122
367
|
}
|
|
123
368
|
exports.GitHubClientWrapper = GitHubClientWrapper;
|
package/dist/input-handler.js
CHANGED
|
@@ -158,6 +158,17 @@ class InputHandler {
|
|
|
158
158
|
name: 'description',
|
|
159
159
|
message: 'Issue description (opens editor):',
|
|
160
160
|
},
|
|
161
|
+
{
|
|
162
|
+
type: 'input',
|
|
163
|
+
name: 'startDate',
|
|
164
|
+
message: 'Start date (YYYY-MM-DD, optional):',
|
|
165
|
+
validate: (input) => {
|
|
166
|
+
if (!input)
|
|
167
|
+
return true; // Optional
|
|
168
|
+
const date = new Date(input);
|
|
169
|
+
return !isNaN(date.getTime()) || 'Invalid date format';
|
|
170
|
+
},
|
|
171
|
+
},
|
|
161
172
|
{
|
|
162
173
|
type: 'input',
|
|
163
174
|
name: 'dueDate',
|
|
@@ -183,6 +194,7 @@ class InputHandler {
|
|
|
183
194
|
title: answers.title,
|
|
184
195
|
description: answers.description || '',
|
|
185
196
|
dueDate: answers.dueDate || '',
|
|
197
|
+
startDate: answers.startDate || '',
|
|
186
198
|
labels: answers.labels || [],
|
|
187
199
|
};
|
|
188
200
|
}
|
package/dist/lgcmf.js
CHANGED
|
@@ -4,14 +4,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
const dotenv_1 = require("dotenv");
|
|
8
7
|
const fs_1 = require("fs");
|
|
9
8
|
const path_1 = require("path");
|
|
10
9
|
const update_notifier_1 = __importDefault(require("update-notifier"));
|
|
11
10
|
const commit_first_1 = require("./commands/commit-first");
|
|
12
|
-
|
|
13
|
-
//
|
|
14
|
-
(0,
|
|
11
|
+
const env_utils_1 = require("./env-utils");
|
|
12
|
+
// Load .env file from current working directory, parent directories, or home directory
|
|
13
|
+
(0, env_utils_1.loadEnvFile)();
|
|
15
14
|
// Check for updates
|
|
16
15
|
const pkg = JSON.parse((0, fs_1.readFileSync)((0, path_1.resolve)(__dirname, '..', 'package.json'), 'utf-8'));
|
|
17
16
|
(0, update_notifier_1.default)({ pkg }).notify();
|