linear-github-cli 1.0.3 → 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 CHANGED
@@ -20,7 +20,7 @@ const program = new commander_1.Command();
20
20
  program
21
21
  .name('lg')
22
22
  .description('Linear + GitHub Integration CLI - Create GitHub issues with Linear sync')
23
- .version('1.0.3');
23
+ .version('1.1.0');
24
24
  program
25
25
  .command('create-parent')
26
26
  .alias('parent')
@@ -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));
@@ -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;
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linear-github-cli",
3
- "version": "1.0.3",
3
+ "version": "1.1.1",
4
4
  "description": "CLI tool for creating GitHub issues with Linear integration",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {