@bretwardjames/ghp-core 0.1.6 → 0.1.7

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/index.cjs CHANGED
@@ -21,7 +21,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  BranchLinker: () => BranchLinker,
24
+ CLI_TO_VSCODE_MAP: () => CLI_TO_VSCODE_MAP,
25
+ DEFAULT_VALUES: () => DEFAULT_VALUES,
24
26
  GitHubAPI: () => GitHubAPI,
27
+ SETTING_DISPLAY_NAMES: () => SETTING_DISPLAY_NAMES,
28
+ SYNCABLE_KEYS: () => SYNCABLE_KEYS,
29
+ VSCODE_TO_CLI_MAP: () => VSCODE_TO_CLI_MAP,
25
30
  branchExists: () => branchExists,
26
31
  buildIssueUrl: () => buildIssueUrl,
27
32
  buildOrgProjectUrl: () => buildOrgProjectUrl,
@@ -29,28 +34,39 @@ __export(index_exports, {
29
34
  buildPullRequestUrl: () => buildPullRequestUrl,
30
35
  buildRepoUrl: () => buildRepoUrl,
31
36
  checkoutBranch: () => checkoutBranch,
37
+ computeSettingsDiff: () => computeSettingsDiff,
32
38
  createBranch: () => createBranch,
33
39
  detectRepository: () => detectRepository,
34
40
  fetchOrigin: () => fetchOrigin,
41
+ formatConflict: () => formatConflict,
35
42
  generateBranchName: () => generateBranchName,
36
43
  getAllBranches: () => getAllBranches,
37
44
  getCommitsAhead: () => getCommitsAhead,
38
45
  getCommitsBehind: () => getCommitsBehind,
39
46
  getCurrentBranch: () => getCurrentBranch,
40
47
  getDefaultBranch: () => getDefaultBranch,
48
+ getDiffSummary: () => getDiffSummary,
41
49
  getLocalBranches: () => getLocalBranches,
42
50
  getRemoteBranches: () => getRemoteBranches,
43
51
  getRepositoryRoot: () => getRepositoryRoot,
52
+ hasDifferences: () => hasDifferences,
44
53
  hasUncommittedChanges: () => hasUncommittedChanges,
45
54
  isGitRepository: () => isGitRepository,
55
+ normalizeVSCodeSettings: () => normalizeVSCodeSettings,
46
56
  parseBranchLink: () => parseBranchLink,
47
57
  parseGitHubUrl: () => parseGitHubUrl,
48
58
  parseIssueUrl: () => parseIssueUrl,
49
59
  pullLatest: () => pullLatest,
50
60
  queries: () => queries_exports,
51
61
  removeBranchLinkFromBody: () => removeBranchLinkFromBody,
62
+ resolveConflicts: () => resolveConflicts,
52
63
  sanitizeForBranchName: () => sanitizeForBranchName,
53
- setBranchLinkInBody: () => setBranchLinkInBody
64
+ setBranchLinkInBody: () => setBranchLinkInBody,
65
+ skip: () => skip,
66
+ toVSCodeSettings: () => toVSCodeSettings,
67
+ useCli: () => useCli,
68
+ useCustom: () => useCustom,
69
+ useVSCode: () => useVSCode
54
70
  });
55
71
  module.exports = __toCommonJS(index_exports);
56
72
 
@@ -1331,10 +1347,177 @@ async function getDefaultBranch(options = {}) {
1331
1347
  }
1332
1348
  return "master";
1333
1349
  }
1350
+
1351
+ // src/sync.ts
1352
+ var SYNCABLE_KEYS = [
1353
+ "mainBranch",
1354
+ "branchPattern",
1355
+ "startWorkingStatus",
1356
+ "doneStatus"
1357
+ ];
1358
+ var SETTING_DISPLAY_NAMES = {
1359
+ mainBranch: "Main Branch",
1360
+ branchPattern: "Branch Name Pattern",
1361
+ startWorkingStatus: "Start Working Status",
1362
+ doneStatus: "Done/PR Merged Status"
1363
+ };
1364
+ var VSCODE_TO_CLI_MAP = {
1365
+ "mainBranch": "mainBranch",
1366
+ "branchNamePattern": "branchPattern",
1367
+ "startWorkingStatus": "startWorkingStatus",
1368
+ "prMergedStatus": "doneStatus"
1369
+ };
1370
+ var CLI_TO_VSCODE_MAP = {
1371
+ mainBranch: "mainBranch",
1372
+ branchPattern: "branchNamePattern",
1373
+ startWorkingStatus: "startWorkingStatus",
1374
+ doneStatus: "prMergedStatus"
1375
+ };
1376
+ var DEFAULT_VALUES = {
1377
+ mainBranch: "main",
1378
+ branchPattern: "{user}/{number}-{title}",
1379
+ startWorkingStatus: "In Progress",
1380
+ doneStatus: "Done"
1381
+ };
1382
+ function normalizeVSCodeSettings(vscodeSettings) {
1383
+ const result = {};
1384
+ for (const [vscodeKey, cliKey] of Object.entries(VSCODE_TO_CLI_MAP)) {
1385
+ const value = vscodeSettings[vscodeKey];
1386
+ if (typeof value === "string" && value.trim() !== "") {
1387
+ result[cliKey] = value;
1388
+ }
1389
+ }
1390
+ return result;
1391
+ }
1392
+ function toVSCodeSettings(settings, includePrefix = true) {
1393
+ const result = {};
1394
+ const prefix = includePrefix ? "ghProjects." : "";
1395
+ for (const [cliKey, value] of Object.entries(settings)) {
1396
+ if (value !== void 0) {
1397
+ const vscodeKey = CLI_TO_VSCODE_MAP[cliKey];
1398
+ if (vscodeKey) {
1399
+ result[`${prefix}${vscodeKey}`] = value;
1400
+ }
1401
+ }
1402
+ }
1403
+ return result;
1404
+ }
1405
+ function computeSettingsDiff(cliSettings, vscodeSettings) {
1406
+ const diff = {
1407
+ conflicts: [],
1408
+ matching: [],
1409
+ cliOnly: [],
1410
+ vscodeOnly: []
1411
+ };
1412
+ for (const key of SYNCABLE_KEYS) {
1413
+ const cliValue = cliSettings[key];
1414
+ const vscodeValue = vscodeSettings[key];
1415
+ const cliDefined = cliValue !== void 0 && cliValue !== "";
1416
+ const vscodeDefined = vscodeValue !== void 0 && vscodeValue !== "";
1417
+ if (cliDefined && vscodeDefined) {
1418
+ if (cliValue === vscodeValue) {
1419
+ diff.matching.push({ key, value: cliValue });
1420
+ } else {
1421
+ diff.conflicts.push({
1422
+ key,
1423
+ displayName: SETTING_DISPLAY_NAMES[key],
1424
+ cliValue,
1425
+ vscodeValue
1426
+ });
1427
+ }
1428
+ } else if (cliDefined) {
1429
+ diff.cliOnly.push({ key, value: cliValue });
1430
+ } else if (vscodeDefined) {
1431
+ diff.vscodeOnly.push({ key, value: vscodeValue });
1432
+ }
1433
+ }
1434
+ return diff;
1435
+ }
1436
+ function hasDifferences(diff) {
1437
+ return diff.conflicts.length > 0 || diff.cliOnly.length > 0 || diff.vscodeOnly.length > 0;
1438
+ }
1439
+ function resolveConflicts(diff, choices, syncUnique = true) {
1440
+ const cliUpdates = {};
1441
+ const vscodeUpdates = {};
1442
+ for (const conflict of diff.conflicts) {
1443
+ const choice = choices[conflict.key] || { type: "skip" };
1444
+ switch (choice.type) {
1445
+ case "cli":
1446
+ if (conflict.cliValue !== void 0) {
1447
+ vscodeUpdates[conflict.key] = conflict.cliValue;
1448
+ }
1449
+ break;
1450
+ case "vscode":
1451
+ if (conflict.vscodeValue !== void 0) {
1452
+ cliUpdates[conflict.key] = conflict.vscodeValue;
1453
+ }
1454
+ break;
1455
+ case "custom":
1456
+ cliUpdates[conflict.key] = choice.value;
1457
+ vscodeUpdates[conflict.key] = choice.value;
1458
+ break;
1459
+ case "skip":
1460
+ break;
1461
+ }
1462
+ }
1463
+ if (syncUnique) {
1464
+ for (const { key, value } of diff.cliOnly) {
1465
+ vscodeUpdates[key] = value;
1466
+ }
1467
+ for (const { key, value } of diff.vscodeOnly) {
1468
+ cliUpdates[key] = value;
1469
+ }
1470
+ }
1471
+ return {
1472
+ cli: cliUpdates,
1473
+ vscode: toVSCodeSettings(vscodeUpdates, false)
1474
+ };
1475
+ }
1476
+ function useCli() {
1477
+ return { type: "cli" };
1478
+ }
1479
+ function useVSCode() {
1480
+ return { type: "vscode" };
1481
+ }
1482
+ function useCustom(value) {
1483
+ return { type: "custom", value };
1484
+ }
1485
+ function skip() {
1486
+ return { type: "skip" };
1487
+ }
1488
+ function formatConflict(conflict) {
1489
+ return `${conflict.displayName}:
1490
+ CLI: "${conflict.cliValue ?? "(not set)"}"
1491
+ VSCode: "${conflict.vscodeValue ?? "(not set)"}"`;
1492
+ }
1493
+ function getDiffSummary(diff) {
1494
+ const parts = [];
1495
+ if (diff.conflicts.length > 0) {
1496
+ parts.push(`${diff.conflicts.length} conflicting setting(s)`);
1497
+ }
1498
+ if (diff.cliOnly.length > 0) {
1499
+ parts.push(`${diff.cliOnly.length} CLI-only setting(s)`);
1500
+ }
1501
+ if (diff.vscodeOnly.length > 0) {
1502
+ parts.push(`${diff.vscodeOnly.length} VSCode-only setting(s)`);
1503
+ }
1504
+ if (diff.matching.length > 0) {
1505
+ parts.push(`${diff.matching.length} matching setting(s)`);
1506
+ }
1507
+ if (parts.length === 0) {
1508
+ return "No settings to compare";
1509
+ }
1510
+ return parts.join(", ");
1511
+ }
1334
1512
  // Annotate the CommonJS export names for ESM import in node:
1335
1513
  0 && (module.exports = {
1336
1514
  BranchLinker,
1515
+ CLI_TO_VSCODE_MAP,
1516
+ DEFAULT_VALUES,
1337
1517
  GitHubAPI,
1518
+ SETTING_DISPLAY_NAMES,
1519
+ SYNCABLE_KEYS,
1520
+ VSCODE_TO_CLI_MAP,
1338
1521
  branchExists,
1339
1522
  buildIssueUrl,
1340
1523
  buildOrgProjectUrl,
@@ -1342,26 +1525,37 @@ async function getDefaultBranch(options = {}) {
1342
1525
  buildPullRequestUrl,
1343
1526
  buildRepoUrl,
1344
1527
  checkoutBranch,
1528
+ computeSettingsDiff,
1345
1529
  createBranch,
1346
1530
  detectRepository,
1347
1531
  fetchOrigin,
1532
+ formatConflict,
1348
1533
  generateBranchName,
1349
1534
  getAllBranches,
1350
1535
  getCommitsAhead,
1351
1536
  getCommitsBehind,
1352
1537
  getCurrentBranch,
1353
1538
  getDefaultBranch,
1539
+ getDiffSummary,
1354
1540
  getLocalBranches,
1355
1541
  getRemoteBranches,
1356
1542
  getRepositoryRoot,
1543
+ hasDifferences,
1357
1544
  hasUncommittedChanges,
1358
1545
  isGitRepository,
1546
+ normalizeVSCodeSettings,
1359
1547
  parseBranchLink,
1360
1548
  parseGitHubUrl,
1361
1549
  parseIssueUrl,
1362
1550
  pullLatest,
1363
1551
  queries,
1364
1552
  removeBranchLinkFromBody,
1553
+ resolveConflicts,
1365
1554
  sanitizeForBranchName,
1366
- setBranchLinkInBody
1555
+ setBranchLinkInBody,
1556
+ skip,
1557
+ toVSCodeSettings,
1558
+ useCli,
1559
+ useCustom,
1560
+ useVSCode
1367
1561
  });
package/dist/index.d.cts CHANGED
@@ -697,6 +697,161 @@ declare function buildProjectUrl(owner: string, projectNumber: number): string;
697
697
  */
698
698
  declare function buildOrgProjectUrl(org: string, projectNumber: number): string;
699
699
 
700
+ /**
701
+ * Settings Sync Module
702
+ *
703
+ * Provides shared logic for bidirectional sync between ghp-cli and VSCode extension.
704
+ * This module handles the 4 settings that overlap between CLI and VSCode:
705
+ * - mainBranch
706
+ * - branchPattern (CLI) / branchNamePattern (VSCode)
707
+ * - startWorkingStatus
708
+ * - doneStatus (CLI) / prMergedStatus (VSCode)
709
+ */
710
+ /**
711
+ * The canonical setting keys used in sync operations.
712
+ * These are the CLI key names (used as the canonical form).
713
+ */
714
+ type SyncableSettingKey = 'mainBranch' | 'branchPattern' | 'startWorkingStatus' | 'doneStatus';
715
+ /**
716
+ * Settings that can be synced between CLI and VSCode.
717
+ * Uses CLI key names as the canonical form.
718
+ */
719
+ interface SyncableSettings {
720
+ mainBranch?: string;
721
+ branchPattern?: string;
722
+ startWorkingStatus?: string;
723
+ doneStatus?: string;
724
+ }
725
+ /**
726
+ * A source of settings (CLI or VSCode)
727
+ */
728
+ type SettingsSource = 'cli' | 'vscode';
729
+ /**
730
+ * Represents a conflict where a setting has different values in CLI and VSCode
731
+ */
732
+ interface SettingConflict {
733
+ key: SyncableSettingKey;
734
+ displayName: string;
735
+ cliValue: string | undefined;
736
+ vscodeValue: string | undefined;
737
+ }
738
+ /**
739
+ * Result of comparing CLI and VSCode settings
740
+ */
741
+ interface SettingsDiff {
742
+ /** Settings that differ between CLI and VSCode */
743
+ conflicts: SettingConflict[];
744
+ /** Settings that are the same in both */
745
+ matching: Array<{
746
+ key: SyncableSettingKey;
747
+ value: string;
748
+ }>;
749
+ /** Settings only defined in CLI */
750
+ cliOnly: Array<{
751
+ key: SyncableSettingKey;
752
+ value: string;
753
+ }>;
754
+ /** Settings only defined in VSCode */
755
+ vscodeOnly: Array<{
756
+ key: SyncableSettingKey;
757
+ value: string;
758
+ }>;
759
+ }
760
+ /**
761
+ * User's choice for how to resolve a conflict
762
+ */
763
+ type ConflictResolution = {
764
+ type: 'cli';
765
+ } | {
766
+ type: 'vscode';
767
+ } | {
768
+ type: 'custom';
769
+ value: string;
770
+ } | {
771
+ type: 'skip';
772
+ };
773
+ /**
774
+ * Map of user choices for each conflicting setting
775
+ */
776
+ type ConflictChoices = Partial<Record<SyncableSettingKey, ConflictResolution>>;
777
+ /**
778
+ * Result of resolving conflicts - settings to write to each target
779
+ */
780
+ interface ResolvedSettings {
781
+ /** Settings to write to CLI config */
782
+ cli: SyncableSettings;
783
+ /** Settings to write to VSCode (using VSCode key names) */
784
+ vscode: Record<string, string>;
785
+ }
786
+ /**
787
+ * All syncable setting keys
788
+ */
789
+ declare const SYNCABLE_KEYS: readonly SyncableSettingKey[];
790
+ /**
791
+ * Human-readable names for each setting
792
+ */
793
+ declare const SETTING_DISPLAY_NAMES: Record<SyncableSettingKey, string>;
794
+ /**
795
+ * Mapping from VSCode setting keys to CLI setting keys
796
+ */
797
+ declare const VSCODE_TO_CLI_MAP: Record<string, SyncableSettingKey>;
798
+ /**
799
+ * Mapping from CLI setting keys to VSCode setting keys
800
+ */
801
+ declare const CLI_TO_VSCODE_MAP: Record<SyncableSettingKey, string>;
802
+ /**
803
+ * Default values for each setting
804
+ */
805
+ declare const DEFAULT_VALUES: Record<SyncableSettingKey, string>;
806
+ /**
807
+ * Convert VSCode settings object to canonical CLI key names
808
+ */
809
+ declare function normalizeVSCodeSettings(vscodeSettings: Record<string, unknown>): SyncableSettings;
810
+ /**
811
+ * Convert CLI settings to VSCode key names (with ghProjects. prefix)
812
+ */
813
+ declare function toVSCodeSettings(settings: SyncableSettings, includePrefix?: boolean): Record<string, string>;
814
+ /**
815
+ * Compare settings from CLI and VSCode and identify differences
816
+ */
817
+ declare function computeSettingsDiff(cliSettings: SyncableSettings, vscodeSettings: SyncableSettings): SettingsDiff;
818
+ /**
819
+ * Check if there are any differences that need syncing
820
+ */
821
+ declare function hasDifferences(diff: SettingsDiff): boolean;
822
+ /**
823
+ * Resolve conflicts based on user choices and compute final settings for each target
824
+ *
825
+ * @param diff The diff result from computeSettingsDiff
826
+ * @param choices User's choices for each conflict
827
+ * @param syncUnique Whether to sync settings that only exist in one source to the other
828
+ */
829
+ declare function resolveConflicts(diff: SettingsDiff, choices: ConflictChoices, syncUnique?: boolean): ResolvedSettings;
830
+ /**
831
+ * Helper to create a "use CLI" resolution
832
+ */
833
+ declare function useCli(): ConflictResolution;
834
+ /**
835
+ * Helper to create a "use VSCode" resolution
836
+ */
837
+ declare function useVSCode(): ConflictResolution;
838
+ /**
839
+ * Helper to create a "custom value" resolution
840
+ */
841
+ declare function useCustom(value: string): ConflictResolution;
842
+ /**
843
+ * Helper to create a "skip" resolution
844
+ */
845
+ declare function skip(): ConflictResolution;
846
+ /**
847
+ * Format a conflict for display (useful for CLI output)
848
+ */
849
+ declare function formatConflict(conflict: SettingConflict): string;
850
+ /**
851
+ * Get a summary of the diff for display
852
+ */
853
+ declare function getDiffSummary(diff: SettingsDiff): string;
854
+
700
855
  /**
701
856
  * GraphQL query strings for GitHub Projects V2 API.
702
857
  *
@@ -833,4 +988,4 @@ declare namespace queries {
833
988
  export { queries_ADD_COMMENT_MUTATION as ADD_COMMENT_MUTATION, queries_ADD_LABELS_MUTATION as ADD_LABELS_MUTATION, queries_ADD_TO_PROJECT_MUTATION as ADD_TO_PROJECT_MUTATION, queries_COLLABORATORS_QUERY as COLLABORATORS_QUERY, queries_CREATE_ISSUE_MUTATION as CREATE_ISSUE_MUTATION, queries_ISSUES_WITH_LABEL_QUERY as ISSUES_WITH_LABEL_QUERY, queries_ISSUE_AND_LABEL_QUERY as ISSUE_AND_LABEL_QUERY, queries_ISSUE_DETAILS_QUERY as ISSUE_DETAILS_QUERY, queries_ISSUE_FOR_UPDATE_QUERY as ISSUE_FOR_UPDATE_QUERY, queries_ISSUE_NODE_ID_QUERY as ISSUE_NODE_ID_QUERY, queries_ISSUE_TYPES_QUERY as ISSUE_TYPES_QUERY, queries_LABEL_EXISTS_QUERY as LABEL_EXISTS_QUERY, queries_PROJECT_FIELDS_QUERY as PROJECT_FIELDS_QUERY, queries_PROJECT_ITEMS_QUERY as PROJECT_ITEMS_QUERY, queries_PROJECT_VIEWS_QUERY as PROJECT_VIEWS_QUERY, queries_RECENT_ISSUES_QUERY as RECENT_ISSUES_QUERY, queries_REMOVE_LABELS_MUTATION as REMOVE_LABELS_MUTATION, queries_REPOSITORY_ID_QUERY as REPOSITORY_ID_QUERY, queries_REPOSITORY_PROJECTS_QUERY as REPOSITORY_PROJECTS_QUERY, queries_UPDATE_ISSUE_BODY_MUTATION as UPDATE_ISSUE_BODY_MUTATION, queries_UPDATE_ISSUE_MUTATION as UPDATE_ISSUE_MUTATION, queries_UPDATE_ISSUE_TYPE_MUTATION as UPDATE_ISSUE_TYPE_MUTATION, queries_UPDATE_ITEM_FIELD_MUTATION as UPDATE_ITEM_FIELD_MUTATION, queries_UPDATE_ITEM_STATUS_MUTATION as UPDATE_ITEM_STATUS_MUTATION, queries_VIEWER_QUERY as VIEWER_QUERY };
834
989
  }
835
990
 
836
- export { type AssigneeInfo, type AuthError, BranchLinker, type Collaborator, type DateFieldValue, type FieldInfo, type FieldValue, type FieldValueConnection, GitHubAPI, type GitHubAPIOptions, type GitOptions, type IssueDetails, type IssueReference, type IterationFieldValue, type LabelInfo, type NumberFieldValue, type Project, type ProjectConfig, type ProjectItem, type ProjectItemContent, type ProjectItemsQueryResponse, type ProjectV2, type ProjectV2Field, type ProjectV2Item, type ProjectV2View, type ProjectWithViews, type ProjectsQueryResponse, type RepoInfo, type SingleSelectFieldValue, type StatusField, type TextFieldValue, type TokenProvider, branchExists, buildIssueUrl, buildOrgProjectUrl, buildProjectUrl, buildPullRequestUrl, buildRepoUrl, checkoutBranch, createBranch, detectRepository, fetchOrigin, generateBranchName, getAllBranches, getCommitsAhead, getCommitsBehind, getCurrentBranch, getDefaultBranch, getLocalBranches, getRemoteBranches, getRepositoryRoot, hasUncommittedChanges, isGitRepository, parseBranchLink, parseGitHubUrl, parseIssueUrl, pullLatest, queries, removeBranchLinkFromBody, sanitizeForBranchName, setBranchLinkInBody };
991
+ export { type AssigneeInfo, type AuthError, BranchLinker, CLI_TO_VSCODE_MAP, type Collaborator, type ConflictChoices, type ConflictResolution, DEFAULT_VALUES, type DateFieldValue, type FieldInfo, type FieldValue, type FieldValueConnection, GitHubAPI, type GitHubAPIOptions, type GitOptions, type IssueDetails, type IssueReference, type IterationFieldValue, type LabelInfo, type NumberFieldValue, type Project, type ProjectConfig, type ProjectItem, type ProjectItemContent, type ProjectItemsQueryResponse, type ProjectV2, type ProjectV2Field, type ProjectV2Item, type ProjectV2View, type ProjectWithViews, type ProjectsQueryResponse, type RepoInfo, type ResolvedSettings, SETTING_DISPLAY_NAMES, SYNCABLE_KEYS, type SettingConflict, type SettingsDiff, type SettingsSource, type SingleSelectFieldValue, type StatusField, type SyncableSettingKey, type SyncableSettings, type TextFieldValue, type TokenProvider, VSCODE_TO_CLI_MAP, branchExists, buildIssueUrl, buildOrgProjectUrl, buildProjectUrl, buildPullRequestUrl, buildRepoUrl, checkoutBranch, computeSettingsDiff, createBranch, detectRepository, fetchOrigin, formatConflict, generateBranchName, getAllBranches, getCommitsAhead, getCommitsBehind, getCurrentBranch, getDefaultBranch, getDiffSummary, getLocalBranches, getRemoteBranches, getRepositoryRoot, hasDifferences, hasUncommittedChanges, isGitRepository, normalizeVSCodeSettings, parseBranchLink, parseGitHubUrl, parseIssueUrl, pullLatest, queries, removeBranchLinkFromBody, resolveConflicts, sanitizeForBranchName, setBranchLinkInBody, skip, toVSCodeSettings, useCli, useCustom, useVSCode };
package/dist/index.d.ts CHANGED
@@ -697,6 +697,161 @@ declare function buildProjectUrl(owner: string, projectNumber: number): string;
697
697
  */
698
698
  declare function buildOrgProjectUrl(org: string, projectNumber: number): string;
699
699
 
700
+ /**
701
+ * Settings Sync Module
702
+ *
703
+ * Provides shared logic for bidirectional sync between ghp-cli and VSCode extension.
704
+ * This module handles the 4 settings that overlap between CLI and VSCode:
705
+ * - mainBranch
706
+ * - branchPattern (CLI) / branchNamePattern (VSCode)
707
+ * - startWorkingStatus
708
+ * - doneStatus (CLI) / prMergedStatus (VSCode)
709
+ */
710
+ /**
711
+ * The canonical setting keys used in sync operations.
712
+ * These are the CLI key names (used as the canonical form).
713
+ */
714
+ type SyncableSettingKey = 'mainBranch' | 'branchPattern' | 'startWorkingStatus' | 'doneStatus';
715
+ /**
716
+ * Settings that can be synced between CLI and VSCode.
717
+ * Uses CLI key names as the canonical form.
718
+ */
719
+ interface SyncableSettings {
720
+ mainBranch?: string;
721
+ branchPattern?: string;
722
+ startWorkingStatus?: string;
723
+ doneStatus?: string;
724
+ }
725
+ /**
726
+ * A source of settings (CLI or VSCode)
727
+ */
728
+ type SettingsSource = 'cli' | 'vscode';
729
+ /**
730
+ * Represents a conflict where a setting has different values in CLI and VSCode
731
+ */
732
+ interface SettingConflict {
733
+ key: SyncableSettingKey;
734
+ displayName: string;
735
+ cliValue: string | undefined;
736
+ vscodeValue: string | undefined;
737
+ }
738
+ /**
739
+ * Result of comparing CLI and VSCode settings
740
+ */
741
+ interface SettingsDiff {
742
+ /** Settings that differ between CLI and VSCode */
743
+ conflicts: SettingConflict[];
744
+ /** Settings that are the same in both */
745
+ matching: Array<{
746
+ key: SyncableSettingKey;
747
+ value: string;
748
+ }>;
749
+ /** Settings only defined in CLI */
750
+ cliOnly: Array<{
751
+ key: SyncableSettingKey;
752
+ value: string;
753
+ }>;
754
+ /** Settings only defined in VSCode */
755
+ vscodeOnly: Array<{
756
+ key: SyncableSettingKey;
757
+ value: string;
758
+ }>;
759
+ }
760
+ /**
761
+ * User's choice for how to resolve a conflict
762
+ */
763
+ type ConflictResolution = {
764
+ type: 'cli';
765
+ } | {
766
+ type: 'vscode';
767
+ } | {
768
+ type: 'custom';
769
+ value: string;
770
+ } | {
771
+ type: 'skip';
772
+ };
773
+ /**
774
+ * Map of user choices for each conflicting setting
775
+ */
776
+ type ConflictChoices = Partial<Record<SyncableSettingKey, ConflictResolution>>;
777
+ /**
778
+ * Result of resolving conflicts - settings to write to each target
779
+ */
780
+ interface ResolvedSettings {
781
+ /** Settings to write to CLI config */
782
+ cli: SyncableSettings;
783
+ /** Settings to write to VSCode (using VSCode key names) */
784
+ vscode: Record<string, string>;
785
+ }
786
+ /**
787
+ * All syncable setting keys
788
+ */
789
+ declare const SYNCABLE_KEYS: readonly SyncableSettingKey[];
790
+ /**
791
+ * Human-readable names for each setting
792
+ */
793
+ declare const SETTING_DISPLAY_NAMES: Record<SyncableSettingKey, string>;
794
+ /**
795
+ * Mapping from VSCode setting keys to CLI setting keys
796
+ */
797
+ declare const VSCODE_TO_CLI_MAP: Record<string, SyncableSettingKey>;
798
+ /**
799
+ * Mapping from CLI setting keys to VSCode setting keys
800
+ */
801
+ declare const CLI_TO_VSCODE_MAP: Record<SyncableSettingKey, string>;
802
+ /**
803
+ * Default values for each setting
804
+ */
805
+ declare const DEFAULT_VALUES: Record<SyncableSettingKey, string>;
806
+ /**
807
+ * Convert VSCode settings object to canonical CLI key names
808
+ */
809
+ declare function normalizeVSCodeSettings(vscodeSettings: Record<string, unknown>): SyncableSettings;
810
+ /**
811
+ * Convert CLI settings to VSCode key names (with ghProjects. prefix)
812
+ */
813
+ declare function toVSCodeSettings(settings: SyncableSettings, includePrefix?: boolean): Record<string, string>;
814
+ /**
815
+ * Compare settings from CLI and VSCode and identify differences
816
+ */
817
+ declare function computeSettingsDiff(cliSettings: SyncableSettings, vscodeSettings: SyncableSettings): SettingsDiff;
818
+ /**
819
+ * Check if there are any differences that need syncing
820
+ */
821
+ declare function hasDifferences(diff: SettingsDiff): boolean;
822
+ /**
823
+ * Resolve conflicts based on user choices and compute final settings for each target
824
+ *
825
+ * @param diff The diff result from computeSettingsDiff
826
+ * @param choices User's choices for each conflict
827
+ * @param syncUnique Whether to sync settings that only exist in one source to the other
828
+ */
829
+ declare function resolveConflicts(diff: SettingsDiff, choices: ConflictChoices, syncUnique?: boolean): ResolvedSettings;
830
+ /**
831
+ * Helper to create a "use CLI" resolution
832
+ */
833
+ declare function useCli(): ConflictResolution;
834
+ /**
835
+ * Helper to create a "use VSCode" resolution
836
+ */
837
+ declare function useVSCode(): ConflictResolution;
838
+ /**
839
+ * Helper to create a "custom value" resolution
840
+ */
841
+ declare function useCustom(value: string): ConflictResolution;
842
+ /**
843
+ * Helper to create a "skip" resolution
844
+ */
845
+ declare function skip(): ConflictResolution;
846
+ /**
847
+ * Format a conflict for display (useful for CLI output)
848
+ */
849
+ declare function formatConflict(conflict: SettingConflict): string;
850
+ /**
851
+ * Get a summary of the diff for display
852
+ */
853
+ declare function getDiffSummary(diff: SettingsDiff): string;
854
+
700
855
  /**
701
856
  * GraphQL query strings for GitHub Projects V2 API.
702
857
  *
@@ -833,4 +988,4 @@ declare namespace queries {
833
988
  export { queries_ADD_COMMENT_MUTATION as ADD_COMMENT_MUTATION, queries_ADD_LABELS_MUTATION as ADD_LABELS_MUTATION, queries_ADD_TO_PROJECT_MUTATION as ADD_TO_PROJECT_MUTATION, queries_COLLABORATORS_QUERY as COLLABORATORS_QUERY, queries_CREATE_ISSUE_MUTATION as CREATE_ISSUE_MUTATION, queries_ISSUES_WITH_LABEL_QUERY as ISSUES_WITH_LABEL_QUERY, queries_ISSUE_AND_LABEL_QUERY as ISSUE_AND_LABEL_QUERY, queries_ISSUE_DETAILS_QUERY as ISSUE_DETAILS_QUERY, queries_ISSUE_FOR_UPDATE_QUERY as ISSUE_FOR_UPDATE_QUERY, queries_ISSUE_NODE_ID_QUERY as ISSUE_NODE_ID_QUERY, queries_ISSUE_TYPES_QUERY as ISSUE_TYPES_QUERY, queries_LABEL_EXISTS_QUERY as LABEL_EXISTS_QUERY, queries_PROJECT_FIELDS_QUERY as PROJECT_FIELDS_QUERY, queries_PROJECT_ITEMS_QUERY as PROJECT_ITEMS_QUERY, queries_PROJECT_VIEWS_QUERY as PROJECT_VIEWS_QUERY, queries_RECENT_ISSUES_QUERY as RECENT_ISSUES_QUERY, queries_REMOVE_LABELS_MUTATION as REMOVE_LABELS_MUTATION, queries_REPOSITORY_ID_QUERY as REPOSITORY_ID_QUERY, queries_REPOSITORY_PROJECTS_QUERY as REPOSITORY_PROJECTS_QUERY, queries_UPDATE_ISSUE_BODY_MUTATION as UPDATE_ISSUE_BODY_MUTATION, queries_UPDATE_ISSUE_MUTATION as UPDATE_ISSUE_MUTATION, queries_UPDATE_ISSUE_TYPE_MUTATION as UPDATE_ISSUE_TYPE_MUTATION, queries_UPDATE_ITEM_FIELD_MUTATION as UPDATE_ITEM_FIELD_MUTATION, queries_UPDATE_ITEM_STATUS_MUTATION as UPDATE_ITEM_STATUS_MUTATION, queries_VIEWER_QUERY as VIEWER_QUERY };
834
989
  }
835
990
 
836
- export { type AssigneeInfo, type AuthError, BranchLinker, type Collaborator, type DateFieldValue, type FieldInfo, type FieldValue, type FieldValueConnection, GitHubAPI, type GitHubAPIOptions, type GitOptions, type IssueDetails, type IssueReference, type IterationFieldValue, type LabelInfo, type NumberFieldValue, type Project, type ProjectConfig, type ProjectItem, type ProjectItemContent, type ProjectItemsQueryResponse, type ProjectV2, type ProjectV2Field, type ProjectV2Item, type ProjectV2View, type ProjectWithViews, type ProjectsQueryResponse, type RepoInfo, type SingleSelectFieldValue, type StatusField, type TextFieldValue, type TokenProvider, branchExists, buildIssueUrl, buildOrgProjectUrl, buildProjectUrl, buildPullRequestUrl, buildRepoUrl, checkoutBranch, createBranch, detectRepository, fetchOrigin, generateBranchName, getAllBranches, getCommitsAhead, getCommitsBehind, getCurrentBranch, getDefaultBranch, getLocalBranches, getRemoteBranches, getRepositoryRoot, hasUncommittedChanges, isGitRepository, parseBranchLink, parseGitHubUrl, parseIssueUrl, pullLatest, queries, removeBranchLinkFromBody, sanitizeForBranchName, setBranchLinkInBody };
991
+ export { type AssigneeInfo, type AuthError, BranchLinker, CLI_TO_VSCODE_MAP, type Collaborator, type ConflictChoices, type ConflictResolution, DEFAULT_VALUES, type DateFieldValue, type FieldInfo, type FieldValue, type FieldValueConnection, GitHubAPI, type GitHubAPIOptions, type GitOptions, type IssueDetails, type IssueReference, type IterationFieldValue, type LabelInfo, type NumberFieldValue, type Project, type ProjectConfig, type ProjectItem, type ProjectItemContent, type ProjectItemsQueryResponse, type ProjectV2, type ProjectV2Field, type ProjectV2Item, type ProjectV2View, type ProjectWithViews, type ProjectsQueryResponse, type RepoInfo, type ResolvedSettings, SETTING_DISPLAY_NAMES, SYNCABLE_KEYS, type SettingConflict, type SettingsDiff, type SettingsSource, type SingleSelectFieldValue, type StatusField, type SyncableSettingKey, type SyncableSettings, type TextFieldValue, type TokenProvider, VSCODE_TO_CLI_MAP, branchExists, buildIssueUrl, buildOrgProjectUrl, buildProjectUrl, buildPullRequestUrl, buildRepoUrl, checkoutBranch, computeSettingsDiff, createBranch, detectRepository, fetchOrigin, formatConflict, generateBranchName, getAllBranches, getCommitsAhead, getCommitsBehind, getCurrentBranch, getDefaultBranch, getDiffSummary, getLocalBranches, getRemoteBranches, getRepositoryRoot, hasDifferences, hasUncommittedChanges, isGitRepository, normalizeVSCodeSettings, parseBranchLink, parseGitHubUrl, parseIssueUrl, pullLatest, queries, removeBranchLinkFromBody, resolveConflicts, sanitizeForBranchName, setBranchLinkInBody, skip, toVSCodeSettings, useCli, useCustom, useVSCode };
package/dist/index.js CHANGED
@@ -1281,9 +1281,176 @@ async function getDefaultBranch(options = {}) {
1281
1281
  }
1282
1282
  return "master";
1283
1283
  }
1284
+
1285
+ // src/sync.ts
1286
+ var SYNCABLE_KEYS = [
1287
+ "mainBranch",
1288
+ "branchPattern",
1289
+ "startWorkingStatus",
1290
+ "doneStatus"
1291
+ ];
1292
+ var SETTING_DISPLAY_NAMES = {
1293
+ mainBranch: "Main Branch",
1294
+ branchPattern: "Branch Name Pattern",
1295
+ startWorkingStatus: "Start Working Status",
1296
+ doneStatus: "Done/PR Merged Status"
1297
+ };
1298
+ var VSCODE_TO_CLI_MAP = {
1299
+ "mainBranch": "mainBranch",
1300
+ "branchNamePattern": "branchPattern",
1301
+ "startWorkingStatus": "startWorkingStatus",
1302
+ "prMergedStatus": "doneStatus"
1303
+ };
1304
+ var CLI_TO_VSCODE_MAP = {
1305
+ mainBranch: "mainBranch",
1306
+ branchPattern: "branchNamePattern",
1307
+ startWorkingStatus: "startWorkingStatus",
1308
+ doneStatus: "prMergedStatus"
1309
+ };
1310
+ var DEFAULT_VALUES = {
1311
+ mainBranch: "main",
1312
+ branchPattern: "{user}/{number}-{title}",
1313
+ startWorkingStatus: "In Progress",
1314
+ doneStatus: "Done"
1315
+ };
1316
+ function normalizeVSCodeSettings(vscodeSettings) {
1317
+ const result = {};
1318
+ for (const [vscodeKey, cliKey] of Object.entries(VSCODE_TO_CLI_MAP)) {
1319
+ const value = vscodeSettings[vscodeKey];
1320
+ if (typeof value === "string" && value.trim() !== "") {
1321
+ result[cliKey] = value;
1322
+ }
1323
+ }
1324
+ return result;
1325
+ }
1326
+ function toVSCodeSettings(settings, includePrefix = true) {
1327
+ const result = {};
1328
+ const prefix = includePrefix ? "ghProjects." : "";
1329
+ for (const [cliKey, value] of Object.entries(settings)) {
1330
+ if (value !== void 0) {
1331
+ const vscodeKey = CLI_TO_VSCODE_MAP[cliKey];
1332
+ if (vscodeKey) {
1333
+ result[`${prefix}${vscodeKey}`] = value;
1334
+ }
1335
+ }
1336
+ }
1337
+ return result;
1338
+ }
1339
+ function computeSettingsDiff(cliSettings, vscodeSettings) {
1340
+ const diff = {
1341
+ conflicts: [],
1342
+ matching: [],
1343
+ cliOnly: [],
1344
+ vscodeOnly: []
1345
+ };
1346
+ for (const key of SYNCABLE_KEYS) {
1347
+ const cliValue = cliSettings[key];
1348
+ const vscodeValue = vscodeSettings[key];
1349
+ const cliDefined = cliValue !== void 0 && cliValue !== "";
1350
+ const vscodeDefined = vscodeValue !== void 0 && vscodeValue !== "";
1351
+ if (cliDefined && vscodeDefined) {
1352
+ if (cliValue === vscodeValue) {
1353
+ diff.matching.push({ key, value: cliValue });
1354
+ } else {
1355
+ diff.conflicts.push({
1356
+ key,
1357
+ displayName: SETTING_DISPLAY_NAMES[key],
1358
+ cliValue,
1359
+ vscodeValue
1360
+ });
1361
+ }
1362
+ } else if (cliDefined) {
1363
+ diff.cliOnly.push({ key, value: cliValue });
1364
+ } else if (vscodeDefined) {
1365
+ diff.vscodeOnly.push({ key, value: vscodeValue });
1366
+ }
1367
+ }
1368
+ return diff;
1369
+ }
1370
+ function hasDifferences(diff) {
1371
+ return diff.conflicts.length > 0 || diff.cliOnly.length > 0 || diff.vscodeOnly.length > 0;
1372
+ }
1373
+ function resolveConflicts(diff, choices, syncUnique = true) {
1374
+ const cliUpdates = {};
1375
+ const vscodeUpdates = {};
1376
+ for (const conflict of diff.conflicts) {
1377
+ const choice = choices[conflict.key] || { type: "skip" };
1378
+ switch (choice.type) {
1379
+ case "cli":
1380
+ if (conflict.cliValue !== void 0) {
1381
+ vscodeUpdates[conflict.key] = conflict.cliValue;
1382
+ }
1383
+ break;
1384
+ case "vscode":
1385
+ if (conflict.vscodeValue !== void 0) {
1386
+ cliUpdates[conflict.key] = conflict.vscodeValue;
1387
+ }
1388
+ break;
1389
+ case "custom":
1390
+ cliUpdates[conflict.key] = choice.value;
1391
+ vscodeUpdates[conflict.key] = choice.value;
1392
+ break;
1393
+ case "skip":
1394
+ break;
1395
+ }
1396
+ }
1397
+ if (syncUnique) {
1398
+ for (const { key, value } of diff.cliOnly) {
1399
+ vscodeUpdates[key] = value;
1400
+ }
1401
+ for (const { key, value } of diff.vscodeOnly) {
1402
+ cliUpdates[key] = value;
1403
+ }
1404
+ }
1405
+ return {
1406
+ cli: cliUpdates,
1407
+ vscode: toVSCodeSettings(vscodeUpdates, false)
1408
+ };
1409
+ }
1410
+ function useCli() {
1411
+ return { type: "cli" };
1412
+ }
1413
+ function useVSCode() {
1414
+ return { type: "vscode" };
1415
+ }
1416
+ function useCustom(value) {
1417
+ return { type: "custom", value };
1418
+ }
1419
+ function skip() {
1420
+ return { type: "skip" };
1421
+ }
1422
+ function formatConflict(conflict) {
1423
+ return `${conflict.displayName}:
1424
+ CLI: "${conflict.cliValue ?? "(not set)"}"
1425
+ VSCode: "${conflict.vscodeValue ?? "(not set)"}"`;
1426
+ }
1427
+ function getDiffSummary(diff) {
1428
+ const parts = [];
1429
+ if (diff.conflicts.length > 0) {
1430
+ parts.push(`${diff.conflicts.length} conflicting setting(s)`);
1431
+ }
1432
+ if (diff.cliOnly.length > 0) {
1433
+ parts.push(`${diff.cliOnly.length} CLI-only setting(s)`);
1434
+ }
1435
+ if (diff.vscodeOnly.length > 0) {
1436
+ parts.push(`${diff.vscodeOnly.length} VSCode-only setting(s)`);
1437
+ }
1438
+ if (diff.matching.length > 0) {
1439
+ parts.push(`${diff.matching.length} matching setting(s)`);
1440
+ }
1441
+ if (parts.length === 0) {
1442
+ return "No settings to compare";
1443
+ }
1444
+ return parts.join(", ");
1445
+ }
1284
1446
  export {
1285
1447
  BranchLinker,
1448
+ CLI_TO_VSCODE_MAP,
1449
+ DEFAULT_VALUES,
1286
1450
  GitHubAPI,
1451
+ SETTING_DISPLAY_NAMES,
1452
+ SYNCABLE_KEYS,
1453
+ VSCODE_TO_CLI_MAP,
1287
1454
  branchExists,
1288
1455
  buildIssueUrl,
1289
1456
  buildOrgProjectUrl,
@@ -1291,26 +1458,37 @@ export {
1291
1458
  buildPullRequestUrl,
1292
1459
  buildRepoUrl,
1293
1460
  checkoutBranch,
1461
+ computeSettingsDiff,
1294
1462
  createBranch,
1295
1463
  detectRepository,
1296
1464
  fetchOrigin,
1465
+ formatConflict,
1297
1466
  generateBranchName,
1298
1467
  getAllBranches,
1299
1468
  getCommitsAhead,
1300
1469
  getCommitsBehind,
1301
1470
  getCurrentBranch,
1302
1471
  getDefaultBranch,
1472
+ getDiffSummary,
1303
1473
  getLocalBranches,
1304
1474
  getRemoteBranches,
1305
1475
  getRepositoryRoot,
1476
+ hasDifferences,
1306
1477
  hasUncommittedChanges,
1307
1478
  isGitRepository,
1479
+ normalizeVSCodeSettings,
1308
1480
  parseBranchLink,
1309
1481
  parseGitHubUrl,
1310
1482
  parseIssueUrl,
1311
1483
  pullLatest,
1312
1484
  queries_exports as queries,
1313
1485
  removeBranchLinkFromBody,
1486
+ resolveConflicts,
1314
1487
  sanitizeForBranchName,
1315
- setBranchLinkInBody
1488
+ setBranchLinkInBody,
1489
+ skip,
1490
+ toVSCodeSettings,
1491
+ useCli,
1492
+ useCustom,
1493
+ useVSCode
1316
1494
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bretwardjames/ghp-core",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Shared core library for GitHub Projects tools",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",