@fuzdev/fuz_gitops 0.72.0 → 0.73.0

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.
Files changed (110) hide show
  1. package/README.md +13 -6
  2. package/dist/changeset_generator.d.ts.map +1 -1
  3. package/dist/changeset_generator.js +2 -16
  4. package/dist/changeset_reader.d.ts +1 -1
  5. package/dist/changeset_reader.d.ts.map +1 -1
  6. package/dist/dependency_graph.js +1 -1
  7. package/dist/git_operations.d.ts +5 -0
  8. package/dist/git_operations.d.ts.map +1 -1
  9. package/dist/git_operations.js +5 -0
  10. package/dist/gitops_analyze.task.d.ts +1 -0
  11. package/dist/gitops_analyze.task.d.ts.map +1 -1
  12. package/dist/gitops_analyze.task.js +12 -17
  13. package/dist/gitops_plan.task.d.ts +1 -0
  14. package/dist/gitops_plan.task.d.ts.map +1 -1
  15. package/dist/gitops_plan.task.js +9 -2
  16. package/dist/gitops_publish.task.d.ts +2 -2
  17. package/dist/gitops_publish.task.d.ts.map +1 -1
  18. package/dist/gitops_publish.task.js +83 -28
  19. package/dist/gitops_run.task.d.ts +2 -1
  20. package/dist/gitops_run.task.d.ts.map +1 -1
  21. package/dist/gitops_run.task.js +40 -7
  22. package/dist/gitops_sync.task.d.ts +1 -0
  23. package/dist/gitops_sync.task.d.ts.map +1 -1
  24. package/dist/gitops_sync.task.js +16 -2
  25. package/dist/gitops_task_helpers.d.ts +16 -2
  26. package/dist/gitops_task_helpers.d.ts.map +1 -1
  27. package/dist/gitops_task_helpers.js +11 -3
  28. package/dist/gitops_validate.task.d.ts +1 -0
  29. package/dist/gitops_validate.task.d.ts.map +1 -1
  30. package/dist/gitops_validate.task.js +20 -20
  31. package/dist/graph_validation.d.ts +16 -1
  32. package/dist/graph_validation.d.ts.map +1 -1
  33. package/dist/graph_validation.js +15 -0
  34. package/dist/local_repo.d.ts +22 -10
  35. package/dist/local_repo.d.ts.map +1 -1
  36. package/dist/local_repo.js +92 -75
  37. package/dist/log_helpers.d.ts +0 -5
  38. package/dist/log_helpers.d.ts.map +1 -1
  39. package/dist/log_helpers.js +8 -19
  40. package/dist/multi_repo_publisher.d.ts +21 -2
  41. package/dist/multi_repo_publisher.d.ts.map +1 -1
  42. package/dist/multi_repo_publisher.js +279 -287
  43. package/dist/operations.d.ts +6 -26
  44. package/dist/operations.d.ts.map +1 -1
  45. package/dist/operations_defaults.d.ts.map +1 -1
  46. package/dist/operations_defaults.js +1 -19
  47. package/dist/output_helpers.d.ts.map +1 -1
  48. package/dist/output_helpers.js +7 -6
  49. package/dist/paths.d.ts +0 -4
  50. package/dist/paths.d.ts.map +1 -1
  51. package/dist/paths.js +0 -4
  52. package/dist/publish_gate.d.ts +44 -0
  53. package/dist/publish_gate.d.ts.map +1 -0
  54. package/dist/publish_gate.js +32 -0
  55. package/dist/publish_steps.d.ts +60 -0
  56. package/dist/publish_steps.d.ts.map +1 -0
  57. package/dist/publish_steps.js +113 -0
  58. package/dist/publishing_event.d.ts +12 -19
  59. package/dist/publishing_event.d.ts.map +1 -1
  60. package/dist/publishing_event.js +16 -25
  61. package/dist/publishing_plan.d.ts +1 -2
  62. package/dist/publishing_plan.d.ts.map +1 -1
  63. package/dist/publishing_plan.js +18 -1
  64. package/dist/publishing_plan_helpers.d.ts +1 -1
  65. package/dist/publishing_plan_helpers.d.ts.map +1 -1
  66. package/dist/publishing_plan_helpers.js +2 -15
  67. package/dist/publishing_plan_logging.d.ts.map +1 -1
  68. package/dist/publishing_plan_logging.js +15 -23
  69. package/dist/version_utils.d.ts +17 -3
  70. package/dist/version_utils.d.ts.map +1 -1
  71. package/dist/version_utils.js +22 -0
  72. package/package.json +5 -6
  73. package/src/lib/changeset_generator.ts +10 -19
  74. package/src/lib/changeset_reader.ts +1 -2
  75. package/src/lib/dependency_graph.ts +1 -1
  76. package/src/lib/git_operations.ts +5 -0
  77. package/src/lib/gitops_analyze.task.ts +18 -23
  78. package/src/lib/gitops_plan.task.ts +10 -2
  79. package/src/lib/gitops_publish.task.ts +92 -27
  80. package/src/lib/gitops_run.task.ts +39 -7
  81. package/src/lib/gitops_sync.task.ts +18 -3
  82. package/src/lib/gitops_task_helpers.ts +20 -3
  83. package/src/lib/gitops_validate.task.ts +30 -22
  84. package/src/lib/graph_validation.ts +26 -0
  85. package/src/lib/local_repo.ts +114 -83
  86. package/src/lib/log_helpers.ts +8 -26
  87. package/src/lib/multi_repo_publisher.ts +336 -353
  88. package/src/lib/operations.ts +6 -22
  89. package/src/lib/operations_defaults.ts +1 -19
  90. package/src/lib/output_helpers.ts +7 -6
  91. package/src/lib/paths.ts +0 -5
  92. package/src/lib/publish_gate.ts +53 -0
  93. package/src/lib/publish_steps.ts +149 -0
  94. package/src/lib/publishing_event.ts +16 -25
  95. package/src/lib/publishing_plan.ts +23 -3
  96. package/src/lib/publishing_plan_helpers.ts +5 -18
  97. package/src/lib/publishing_plan_logging.ts +19 -39
  98. package/src/lib/version_utils.ts +30 -9
  99. package/dist/npm_install_helpers.d.ts +0 -23
  100. package/dist/npm_install_helpers.d.ts.map +0 -1
  101. package/dist/npm_install_helpers.js +0 -60
  102. package/dist/semver.d.ts +0 -26
  103. package/dist/semver.d.ts.map +0 -1
  104. package/dist/semver.js +0 -137
  105. package/dist/serialization_types.d.ts +0 -59
  106. package/dist/serialization_types.d.ts.map +0 -1
  107. package/dist/serialization_types.js +0 -42
  108. package/src/lib/npm_install_helpers.ts +0 -85
  109. package/src/lib/semver.ts +0 -170
  110. package/src/lib/serialization_types.ts +0 -92
package/README.md CHANGED
@@ -96,12 +96,19 @@ gro gitops_sync --download # clone missing repos first
96
96
  ### Diagnostic commands (read-only)
97
97
 
98
98
  ```bash
99
- gro gitops_validate # run all validation checks (analyze + plan + dry run)
100
- gro gitops_analyze # analyze dependency graph and detect cycles
101
- gro gitops_plan # generate publishing plan showing version changes and cascades
102
- gro gitops_publish # simulate publishing without side effects (dry run default)
99
+ gro gitops_validate # run all validation checks (analyze + plan + dry run)
100
+ gro gitops_analyze # analyze dependency graph and detect cycles
101
+ gro gitops_plan # generate publishing plan showing version changes and cascades
102
+ gro gitops_publish # simulate publishing without side effects (dry run default)
103
+ gro gitops_publish --preview # show the ordered side-effects a --wetrun would perform
103
104
  ```
104
105
 
106
+ These read each repo's working tree exactly as it sits on disk — no branch
107
+ switching, pulling, or installing — so they're safe to run with feature
108
+ branches checked out and uncommitted changes. Pass `--sync` to refresh repos
109
+ (switch to the configured branch, pull, install) before running, or run
110
+ `gro gitops_sync` first.
111
+
105
112
  ### Publishing packages
106
113
 
107
114
  ```bash
@@ -119,8 +126,8 @@ gro gitops_publish --wetrun --no-plan # skip plan confirmation
119
126
  - ./docs/troubleshooting.md - Common errors and
120
127
  debugging tips
121
128
 
122
- Getting started as a dev? Start with [Gro](https://github.com/grogarden/gro)
123
- and the [Fuz template](https://github.com/fuz-dev/fuz_template).
129
+ Getting started as a dev? Start with [Gro](https://github.com/fuzdev/gro)
130
+ and the [Fuz template](https://github.com/fuzdev/fuz_template).
124
131
 
125
132
  TODO
126
133
 
@@ -1 +1 @@
1
- {"version":3,"file":"changeset_generator.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/changeset_generator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,2BAA2B,CAAC;AAEhE,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,iBAAiB,CAAC;AAGlD,MAAM,WAAW,uBAAuB;IACvC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IACvC,QAAQ,EAAE,OAAO,CAAC;CAClB;AAED;;;GAGG;AACH,eAAO,MAAM,uCAAuC,GACnD,MAAM,SAAS,EACf,SAAS,KAAK,CAAC,uBAAuB,CAAC,EACvC,UAAS;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,YAAY,CAAA;CAAM,KACjD,OAAO,CAAC,MAAM,CAiChB,CAAC;AAwBF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GACtC,cAAc,MAAM,EACpB,SAAS,KAAK,CAAC,uBAAuB,CAAC,EACvC,WAAW,OAAO,GAAG,OAAO,GAAG,OAAO,KACpC,MAsCF,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACrC,cAAc,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,oBAAoB,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,KAC/C,KAAK,CAAC,uBAAuB,CAoB/B,CAAC"}
1
+ {"version":3,"file":"changeset_generator.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/changeset_generator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,2BAA2B,CAAC;AAMhE,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,iBAAiB,CAAC;AAGlD,MAAM,WAAW,uBAAuB;IACvC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IACvC,QAAQ,EAAE,OAAO,CAAC;CAClB;AAED;;;GAGG;AACH,eAAO,MAAM,uCAAuC,GACnD,MAAM,SAAS,EACf,SAAS,KAAK,CAAC,uBAAuB,CAAC,EACvC,UAAS;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,YAAY,CAAA;CAAM,KACjD,OAAO,CAAC,MAAM,CAiChB,CAAC;AAWF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GACtC,cAAc,MAAM,EACpB,SAAS,KAAK,CAAC,uBAAuB,CAAC,EACvC,WAAW,OAAO,GAAG,OAAO,GAAG,OAAO,KACpC,MAsCF,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACrC,cAAc,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,oBAAoB,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,KAC/C,KAAK,CAAC,uBAAuB,CAoB/B,CAAC"}
@@ -7,7 +7,7 @@
7
7
  * @module
8
8
  */
9
9
  import { join } from 'node:path';
10
- import { strip_version_prefix } from './version_utils.js';
10
+ import { strip_version_prefix, required_bump_for_dependency_update, } from './version_utils.js';
11
11
  import { default_fs_operations } from './operations_defaults.js';
12
12
  /**
13
13
  * Creates a changeset file for dependency updates.
@@ -40,21 +40,7 @@ export const create_changeset_for_dependency_updates = async (repo, updates, opt
40
40
  log?.info(` Created changeset: ${filename}`);
41
41
  return filepath;
42
42
  };
43
- const calculate_required_bump = (repo, updates) => {
44
- const current_version = repo.library.package_json.version || '0.0.0';
45
- const [major] = current_version.split('.').map(Number);
46
- const is_pre_1_0 = major === 0;
47
- // Check if any dependency had breaking changes
48
- const has_breaking = updates.some((u) => u.breaking);
49
- if (has_breaking) {
50
- // Breaking changes propagate
51
- // Pre-1.0: use minor for breaking changes
52
- // 1.0+: use major for breaking changes
53
- return is_pre_1_0 ? 'minor' : 'major';
54
- }
55
- // For non-breaking dependency updates, always use patch
56
- return 'patch';
57
- };
43
+ const calculate_required_bump = (repo, updates) => required_bump_for_dependency_update(repo.library.package_json.version || '0.0.0', updates.some((u) => u.breaking));
58
44
  /**
59
45
  * Generates markdown changeset content for dependency updates.
60
46
  *
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import type { Logger } from '@fuzdev/fuz_util/log.js';
10
10
  import type { LocalRepo } from './local_repo.js';
11
- import type { BumpType } from './semver.js';
11
+ import { type BumpType } from './version_utils.js';
12
12
  export interface ChangesetInfo {
13
13
  filename: string;
14
14
  packages: Array<{
@@ -1 +1 @@
1
- {"version":3,"file":"changeset_reader.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/changeset_reader.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAKpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,aAAa,CAAC;AAG1C,MAAM,WAAW,aAAa;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,QAAQ,CAAA;KAAC,CAAC,CAAC;IACrD,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,uBAAuB,GACnC,SAAS,MAAM,EACf,iBAAyB,KACvB,aAAa,GAAG,IAmClB,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAChC,UAAU,MAAM,EAChB,MAAM,MAAM,KACV,OAAO,CAAC,aAAa,GAAG,IAAI,CAgB9B,CAAC;AAEF,eAAO,MAAM,eAAe,GAC3B,MAAM,SAAS,EACf,MAAM,MAAM,KACV,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAsB9B,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,GAC1C,YAAY,KAAK,CAAC,aAAa,CAAC,EAChC,cAAc,MAAM,KAClB,QAAQ,GAAG,IAcb,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,OAAO,CAarE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,MAAM,MAAM,KACV,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,QAAQ,CAAA;CAAC,GAAG,IAAI,CAsBvD,CAAC"}
1
+ {"version":3,"file":"changeset_reader.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/changeset_reader.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAKpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAA6C,KAAK,QAAQ,EAAC,MAAM,oBAAoB,CAAC;AAE7F,MAAM,WAAW,aAAa;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,QAAQ,CAAA;KAAC,CAAC,CAAC;IACrD,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,uBAAuB,GACnC,SAAS,MAAM,EACf,iBAAyB,KACvB,aAAa,GAAG,IAmClB,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAChC,UAAU,MAAM,EAChB,MAAM,MAAM,KACV,OAAO,CAAC,aAAa,GAAG,IAAI,CAgB9B,CAAC;AAEF,eAAO,MAAM,eAAe,GAC3B,MAAM,SAAS,EACf,MAAM,MAAM,KACV,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAsB9B,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,GAC1C,YAAY,KAAK,CAAC,aAAa,CAAC,EAChC,cAAc,MAAM,KAClB,QAAQ,GAAG,IAcb,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,OAAO,CAarE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,MAAM,MAAM,KACV,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,QAAQ,CAAA;CAAC,GAAG,IAAI,CAsBvD,CAAC"}
@@ -31,7 +31,7 @@ export class DependencyGraph {
31
31
  repo,
32
32
  dependencies: new Map(),
33
33
  dependents: new Set(),
34
- publishable: !!library.package_json.private === false, // eslint-disable-line @typescript-eslint/no-unnecessary-boolean-literal-compare
34
+ publishable: !library.package_json.private,
35
35
  };
36
36
  // Extract dependencies
37
37
  const deps = library.package_json.dependencies || EMPTY_OBJECT;
@@ -20,6 +20,11 @@ export declare const git_tag: (tag_name: string, message?: string, options?: Spa
20
20
  * Pushes a tag to origin and throws if anything goes wrong.
21
21
  */
22
22
  export declare const git_push_tag: (tag_name: string, origin?: GitOrigin, options?: SpawnOptions) => Promise<void>;
23
+ /**
24
+ * Returns `true` if the working tree has any changes — staged, unstaged, or
25
+ * untracked (`git status --porcelain`). Broader than `git_list_uncommitted_files`,
26
+ * which reports only tracked working-tree changes relative to HEAD.
27
+ */
23
28
  export declare const git_has_changes: (options?: SpawnOptions) => Promise<boolean>;
24
29
  /**
25
30
  * Lists uncommitted files in the working tree (`git diff --name-only HEAD`).
@@ -1 +1 @@
1
- {"version":3,"file":"git_operations.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/git_operations.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAMN,KAAK,SAAS,EACd,KAAK,SAAS,EACd,MAAM,yBAAyB,CAAC;AAEjC;;GAEG;AACH,eAAO,MAAM,OAAO,GACnB,OAAO,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,EAC7B,UAAU,YAAY,KACpB,OAAO,CAAC,IAAI,CAQd,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,UAAU,GAAU,SAAS,MAAM,EAAE,UAAU,YAAY,KAAG,OAAO,CAAC,IAAI,CAOtF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAC9B,OAAO,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,EAC7B,SAAS,MAAM,EACf,UAAU,YAAY,KACpB,OAAO,CAAC,IAAI,CAGd,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,OAAO,GACnB,UAAU,MAAM,EAChB,UAAU,MAAM,EAChB,UAAU,YAAY,KACpB,OAAO,CAAC,IAAI,CASd,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,GACxB,UAAU,MAAM,EAChB,SAAQ,SAAiC,EACzC,UAAU,YAAY,KACpB,OAAO,CAAC,IAAI,CAOd,CAAC;AAEF,eAAO,MAAM,eAAe,GAAU,UAAU,YAAY,KAAG,OAAO,CAAC,OAAO,CAG7E,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,0BAA0B,GACtC,UAAU,YAAY,KACpB,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAQvB,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAChC,aAAa,MAAM,EACnB,WAAW,MAAM,EACjB,WAAW,MAAM,EACjB,UAAU,YAAY,KACpB,OAAO,CAAC,OAAO,CAOjB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS,GAAU,UAAU,MAAM,EAAE,UAAU,YAAY,KAAG,OAAO,CAAC,IAAI,CAStF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,GAAU,UAAU,YAAY,KAAG,OAAO,CAAC,IAAI,CAOxE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAC7B,QAAQ,SAAS,EACjB,OAAM,OAAc,EACpB,UAAU,YAAY,KACpB,OAAO,CAAC,IAAI,CAoBd,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gCAAgC,GAAU,UAAU,YAAY,KAAG,OAAO,CAAC,MAAM,CAM7F,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gCAAgC,GAC5C,SAAS,MAAM,EACf,UAAU,YAAY,KACpB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oCAAoC,GAChD,UAAU,YAAY,KACpB,OAAO,CAAC,OAAO,CAGjB,CAAC;AAEF,eAAO,MAAM,cAAc,GAC1B,SAAQ,MAAiB,EACzB,UAAU,YAAY,KACpB,OAAO,CAAC,OAAO,CAQjB,CAAC"}
1
+ {"version":3,"file":"git_operations.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/git_operations.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAMN,KAAK,SAAS,EACd,KAAK,SAAS,EACd,MAAM,yBAAyB,CAAC;AAEjC;;GAEG;AACH,eAAO,MAAM,OAAO,GACnB,OAAO,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,EAC7B,UAAU,YAAY,KACpB,OAAO,CAAC,IAAI,CAQd,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,UAAU,GAAU,SAAS,MAAM,EAAE,UAAU,YAAY,KAAG,OAAO,CAAC,IAAI,CAOtF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAC9B,OAAO,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,EAC7B,SAAS,MAAM,EACf,UAAU,YAAY,KACpB,OAAO,CAAC,IAAI,CAGd,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,OAAO,GACnB,UAAU,MAAM,EAChB,UAAU,MAAM,EAChB,UAAU,YAAY,KACpB,OAAO,CAAC,IAAI,CASd,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,GACxB,UAAU,MAAM,EAChB,SAAQ,SAAiC,EACzC,UAAU,YAAY,KACpB,OAAO,CAAC,IAAI,CAOd,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAU,UAAU,YAAY,KAAG,OAAO,CAAC,OAAO,CAG7E,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,0BAA0B,GACtC,UAAU,YAAY,KACpB,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAQvB,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAChC,aAAa,MAAM,EACnB,WAAW,MAAM,EACjB,WAAW,MAAM,EACjB,UAAU,YAAY,KACpB,OAAO,CAAC,OAAO,CAOjB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS,GAAU,UAAU,MAAM,EAAE,UAAU,YAAY,KAAG,OAAO,CAAC,IAAI,CAStF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,GAAU,UAAU,YAAY,KAAG,OAAO,CAAC,IAAI,CAOxE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAC7B,QAAQ,SAAS,EACjB,OAAM,OAAc,EACpB,UAAU,YAAY,KACpB,OAAO,CAAC,IAAI,CAoBd,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gCAAgC,GAAU,UAAU,YAAY,KAAG,OAAO,CAAC,MAAM,CAM7F,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gCAAgC,GAC5C,SAAS,MAAM,EACf,UAAU,YAAY,KACpB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oCAAoC,GAChD,UAAU,YAAY,KACpB,OAAO,CAAC,OAAO,CAGjB,CAAC;AAEF,eAAO,MAAM,cAAc,GAC1B,SAAQ,MAAiB,EACzB,UAAU,YAAY,KACpB,OAAO,CAAC,OAAO,CAQjB,CAAC"}
@@ -45,6 +45,11 @@ export const git_push_tag = async (tag_name, origin = 'origin', options) => {
45
45
  throw Error(`git_push_tag failed for tag '${tag_name}' with ${spawn_result_to_message(result)}${stderr ? ': ' + stderr.trim() : ''}`);
46
46
  }
47
47
  };
48
+ /**
49
+ * Returns `true` if the working tree has any changes — staged, unstaged, or
50
+ * untracked (`git status --porcelain`). Broader than `git_list_uncommitted_files`,
51
+ * which reports only tracked working-tree changes relative to HEAD.
52
+ */
48
53
  export const git_has_changes = async (options) => {
49
54
  const { stdout } = await spawn_out('git', ['status', '--porcelain'], options);
50
55
  return stdout ? stdout.trim().length > 0 : false;
@@ -10,6 +10,7 @@ export declare const Args: z.ZodObject<{
10
10
  markdown: "markdown";
11
11
  }>>;
12
12
  outfile: z.ZodOptional<z.ZodString>;
13
+ sync: z.ZodDefault<z.ZodBoolean>;
13
14
  }, z.core.$strict>;
14
15
  export type Args = z.infer<typeof Args>;
15
16
  /** @nodocs */
@@ -1 +1 @@
1
- {"version":3,"file":"gitops_analyze.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_analyze.task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,aAAa,CAAC;AACtC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAgBtB,cAAc;AACd,eAAO,MAAM,IAAI;;;;;;;;;kBAcf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC,cAAc;AACd,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CAkC3B,CAAC"}
1
+ {"version":3,"file":"gitops_analyze.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_analyze.task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,aAAa,CAAC;AACtC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAgBtB,cAAc;AACd,eAAO,MAAM,IAAI;;;;;;;;;;kBAqBf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC,cAAc;AACd,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CAsB3B,CAAC"}
@@ -1,8 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { styleText as st } from 'node:util';
3
3
  import { get_gitops_ready } from './gitops_task_helpers.js';
4
- import { DependencyGraphBuilder } from './dependency_graph.js';
5
- import { validate_dependency_graph } from './graph_validation.js';
4
+ import { analyze_repos } from './graph_validation.js';
6
5
  import { format_wildcard_dependencies, format_dev_cycles, format_production_cycles, } from './log_helpers.js';
7
6
  import { format_and_output } from './output_helpers.js';
8
7
  import { GITOPS_CONFIG_PATH_DEFAULT } from './gitops_constants.js';
@@ -21,27 +20,23 @@ export const Args = z.strictObject({
21
20
  .meta({ description: 'output format' })
22
21
  .default('stdout'),
23
22
  outfile: z.string().meta({ description: 'write output to file instead of logging' }).optional(),
23
+ sync: z
24
+ .boolean()
25
+ .meta({
26
+ description: 'sync repos (switch branch, pull, install) before analyzing instead of reading the working tree as-is',
27
+ })
28
+ .default(false),
24
29
  });
25
30
  /** @nodocs */
26
31
  export const task = {
27
32
  Args,
28
33
  summary: 'analyze dependency structure and relationships across repos',
29
34
  run: async ({ args, log }) => {
30
- const { config, dir, format, outfile } = args;
31
- // Get repos ready (without downloading)
32
- const { local_repos } = await get_gitops_ready({ config, dir, download: false, log });
33
- // Build dependency graph and validate (but don't throw on cycles for analyze)
34
- const { graph, publishing_order: order } = validate_dependency_graph(local_repos, {
35
- log,
36
- throw_on_prod_cycles: false, // Analyze should report, not throw
37
- log_cycles: false, // We'll show cycles in our formatted output
38
- log_order: false, // We'll show order in our formatted output
39
- });
40
- // Perform additional analysis
41
- const builder = new DependencyGraphBuilder();
42
- const analysis = builder.analyze(graph);
43
- // Publishing order (may be null if prod cycles exist)
44
- const publishing_order = order.length > 0 ? order : null;
35
+ const { config, dir, format, outfile, sync } = args;
36
+ // Get repos ready (without downloading); read the working tree as-is unless `--sync`
37
+ const { local_repos } = await get_gitops_ready({ config, dir, download: false, sync, log });
38
+ // Build the dependency graph and analyze cycles/wildcards (tolerating cycles)
39
+ const { graph, analysis, publishing_order } = analyze_repos(local_repos);
45
40
  // Format and output using output_helpers
46
41
  const data = {
47
42
  repos: local_repos,
@@ -11,6 +11,7 @@ export declare const Args: z.ZodObject<{
11
11
  }>>;
12
12
  outfile: z.ZodOptional<z.ZodString>;
13
13
  verbose: z.ZodDefault<z.ZodBoolean>;
14
+ sync: z.ZodDefault<z.ZodBoolean>;
14
15
  }, z.core.$strict>;
15
16
  export type Args = z.infer<typeof Args>;
16
17
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"gitops_plan.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_plan.task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,aAAa,CAAC;AACtC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAatB,cAAc;AACd,eAAO,MAAM,IAAI;;;;;;;;;;kBAef,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CAkC3B,CAAC"}
1
+ {"version":3,"file":"gitops_plan.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_plan.task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,aAAa,CAAC;AACtC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAatB,cAAc;AACd,eAAO,MAAM,IAAI;;;;;;;;;;;kBAsBf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CAmC3B,CAAC"}
@@ -20,6 +20,12 @@ export const Args = z.strictObject({
20
20
  .default('stdout'),
21
21
  outfile: z.string().meta({ description: 'write output to file instead of logging' }).optional(),
22
22
  verbose: z.boolean().meta({ description: 'show additional details' }).default(false),
23
+ sync: z
24
+ .boolean()
25
+ .meta({
26
+ description: 'sync repos (switch branch, pull, install) before planning instead of reading the working tree as-is',
27
+ })
28
+ .default(false),
23
29
  });
24
30
  /**
25
31
  * Generate a publishing plan showing what would happen during multi-repo publishing.
@@ -36,13 +42,14 @@ export const task = {
36
42
  summary: 'generate a publishing plan based on changesets',
37
43
  Args,
38
44
  run: async ({ args, log }) => {
39
- const { dir, config, format, outfile, verbose } = args;
45
+ const { dir, config, format, outfile, verbose, sync } = args;
40
46
  log.info(st('cyan', 'Generating multi-repo publishing plan...'));
41
- // Load local repos
47
+ // Load local repos; read the working tree as-is unless `--sync`
42
48
  const { local_repos } = await get_gitops_ready({
43
49
  config,
44
50
  dir,
45
51
  download: false, // Don't download if missing
52
+ sync,
46
53
  log,
47
54
  });
48
55
  if (local_repos.length === 0) {
@@ -17,12 +17,12 @@ export declare const Args: z.ZodObject<{
17
17
  }>>;
18
18
  deploy: z.ZodDefault<z.ZodBoolean>;
19
19
  plan: z.ZodDefault<z.ZodBoolean>;
20
- 'no-plan': z.ZodDefault<z.ZodBoolean>;
21
20
  max_wait: z.ZodDefault<z.ZodNumber>;
22
- skip_install: z.ZodDefault<z.ZodBoolean>;
23
21
  emit_json: z.ZodDefault<z.ZodBoolean>;
24
22
  outfile: z.ZodOptional<z.ZodString>;
25
23
  verbose: z.ZodDefault<z.ZodBoolean>;
24
+ sync: z.ZodDefault<z.ZodBoolean>;
25
+ preview: z.ZodDefault<z.ZodBoolean>;
26
26
  }, z.core.$strict>;
27
27
  export type Args = z.infer<typeof Args>;
28
28
  /** @nodocs */
@@ -1 +1 @@
1
- {"version":3,"file":"gitops_publish.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_publish.task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,aAAa,CAAC;AACtC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAetB,cAAc;AACd,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;kBAsCf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC,cAAc;AACd,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CA+F3B,CAAC"}
1
+ {"version":3,"file":"gitops_publish.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_publish.task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,aAAa,CAAC;AAEtC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAiBtB,cAAc;AACd,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;kBA4Cf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC,cAAc;AACd,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CAiH3B,CAAC"}
@@ -2,9 +2,11 @@ import { z } from 'zod';
2
2
  import { createInterface } from 'node:readline/promises';
3
3
  import { styleText as st } from 'node:util';
4
4
  import { get_gitops_ready } from './gitops_task_helpers.js';
5
- import { publish_repos, } from './multi_repo_publisher.js';
5
+ import { execute_publishing_plan, } from './multi_repo_publisher.js';
6
6
  import { stdout_handler } from './publishing_event_handler.js';
7
7
  import { generate_publishing_plan, log_publishing_plan } from './publishing_plan.js';
8
+ import { derive_publish_steps, format_publish_steps } from './publish_steps.js';
9
+ import { decide_publish_gate, publish_run_failed } from './publish_gate.js';
8
10
  import { format_and_output } from './output_helpers.js';
9
11
  import { GITOPS_CONFIG_PATH_DEFAULT, GITOPS_NPM_WAIT_TIMEOUT_DEFAULT } from './gitops_constants.js';
10
12
  /** @nodocs */
@@ -27,47 +29,64 @@ export const Args = z.strictObject({
27
29
  .meta({ description: 'output format' })
28
30
  .default('stdout'),
29
31
  deploy: z.boolean().meta({ description: 'deploy all repos after publishing' }).default(false),
30
- plan: z.boolean().meta({ description: 'dual of no-plan' }).default(true),
31
- 'no-plan': z
32
+ plan: z
32
33
  .boolean()
33
- .meta({ description: 'skip plan confirmation before publishing' })
34
- .default(false),
34
+ .meta({ description: 'show the plan and confirm before publishing; --no-plan to skip' })
35
+ .default(true),
35
36
  max_wait: z
36
37
  .number()
37
38
  .meta({ description: 'max time to wait for npm propagation in ms' })
38
39
  .default(GITOPS_NPM_WAIT_TIMEOUT_DEFAULT),
39
- skip_install: z
40
- .boolean()
41
- .meta({ description: 'skip npm install after dependency updates' })
42
- .default(false),
43
40
  emit_json: z
44
41
  .boolean()
45
42
  .meta({ description: 'stream structured publishing events as JSON-lines to stdout' })
46
43
  .default(false),
47
44
  outfile: z.string().meta({ description: 'write output to file instead of logging' }).optional(),
48
45
  verbose: z.boolean().meta({ description: 'show additional details in plan output' }).default(false),
46
+ sync: z
47
+ .boolean()
48
+ .meta({
49
+ description: 'sync repos (switch branch, pull, install) before the dry run instead of reading the working tree as-is; always on for --wetrun',
50
+ })
51
+ .default(false),
52
+ preview: z
53
+ .boolean()
54
+ .meta({ description: 'show the ordered side-effects a --wetrun would perform' })
55
+ .default(false),
49
56
  });
50
57
  /** @nodocs */
51
58
  export const task = {
52
59
  summary: 'publish all repos in dependency order',
53
60
  Args,
54
61
  run: async ({ args, log }) => {
55
- const { config, dir, peer_strategy, wetrun, format, deploy, plan, max_wait, skip_install, emit_json, outfile, verbose, } = args;
56
- // Load repos
62
+ const { config, dir, peer_strategy, wetrun, format, deploy, plan, max_wait, emit_json, outfile, verbose, sync, preview, } = args;
63
+ // Load repos. A dry run reads the working tree as-is unless `--sync`;
64
+ // a real publish (`--wetrun`) always syncs so preflight sees the canonical branches.
57
65
  const { local_repos: repos } = await get_gitops_ready({
58
66
  config,
59
67
  dir,
60
68
  download: false, // Don't download if missing
69
+ sync: sync || wetrun,
61
70
  log,
62
71
  });
63
- // Show plan if requested (skip for dry runs)
64
- if (plan && wetrun) {
72
+ // Generate the plan once; the executor consumes this exact plan (no second pass).
73
+ const publishing_plan = await generate_publishing_plan(repos, { verbose });
74
+ const preview_steps = preview ? derive_publish_steps(publishing_plan, { deploy }) : null;
75
+ // Decide whether to show the plan + confirm, block, or proceed (the decision table lives
76
+ // in `decide_publish_gate`; readline + exit stay here at the edge).
77
+ const gate = decide_publish_gate({ wetrun, show_plan: plan, plan: publishing_plan });
78
+ // A real publish that shows its plan prints it first — including before a `blocked` throw,
79
+ // so the operator sees the errors that blocked it.
80
+ if (gate.action !== 'proceed') {
65
81
  log.info(st('cyan', 'Publishing Plan'));
66
- const plan_result = await generate_publishing_plan(repos, { log, verbose });
67
- log_publishing_plan(plan_result, log, { verbose });
68
- if (plan_result.errors.length > 0) {
69
- throw new Error('Cannot proceed with publishing due to errors');
70
- }
82
+ log_publishing_plan(publishing_plan, log, { verbose });
83
+ }
84
+ if (gate.action === 'blocked') {
85
+ throw new Error(gate.message);
86
+ }
87
+ else if (gate.action === 'confirm') {
88
+ if (preview_steps)
89
+ log_preview(preview_steps, log);
71
90
  // Ask for confirmation
72
91
  log.info(st('yellow', '⚠️ This will publish the packages shown above.'));
73
92
  process.stdout.write('Continue with publishing? (y/n): ');
@@ -77,14 +96,18 @@ export const task = {
77
96
  process.exit(0);
78
97
  }
79
98
  }
99
+ else if (preview_steps && format === 'stdout') {
100
+ // proceed (dry run or --no-plan): only render to stdout for the human format;
101
+ // json/markdown carry the preview in their structured output, so logging here too
102
+ // would corrupt that stream.
103
+ log_preview(preview_steps, log);
104
+ }
80
105
  // Publishing options
81
106
  const options = {
82
107
  wetrun,
83
- update_deps: true, // Always update dependencies
84
108
  version_strategy: peer_strategy,
85
109
  deploy,
86
110
  max_wait,
87
- skip_install,
88
111
  log,
89
112
  // Live JSON-lines stream when requested; events also surface on the result.
90
113
  events: emit_json ? stdout_handler() : undefined,
@@ -93,7 +116,7 @@ export const task = {
93
116
  let result;
94
117
  let fatal_error = null;
95
118
  try {
96
- result = await publish_repos(repos, options);
119
+ result = await execute_publishing_plan(repos, publishing_plan, options);
97
120
  }
98
121
  catch (error) {
99
122
  // Construct a failure result for fatal errors so output can still be generated
@@ -106,33 +129,42 @@ export const task = {
106
129
  duration: 0,
107
130
  events: [],
108
131
  summary: { total: 0, published: 0, failed: 1, skipped: 0, duration: 0 },
132
+ plan_errors: publishing_plan.errors,
133
+ plan_warnings: publishing_plan.warnings,
109
134
  };
110
135
  }
111
136
  // Format and output result (always runs, even on fatal errors)
112
- // Note: stdout format is handled by publish_repos function's logging
137
+ // Note: stdout format is handled by the executor's logging
113
138
  if (format !== 'stdout') {
114
- await format_and_output({ result, fatal_error }, create_publish_formatters(), {
139
+ await format_and_output({ result, fatal_error, preview_steps }, create_publish_formatters(), {
115
140
  format,
116
141
  outfile,
117
142
  log,
118
143
  });
119
144
  }
120
145
  // Exit with error if failed
121
- if (!result.ok || fatal_error) {
146
+ if (publish_run_failed(result, fatal_error)) {
122
147
  process.exit(1);
123
148
  }
124
149
  },
125
150
  };
126
151
  const create_publish_formatters = () => ({
127
- json: (data) => JSON.stringify(data.result, null, 2),
128
- markdown: (data) => format_result_markdown(data.result, data.fatal_error),
152
+ json: (data) => JSON.stringify(data.preview_steps ? { ...data.result, preview: data.preview_steps } : data.result, null, 2),
153
+ markdown: (data) => format_result_markdown(data.result, data.fatal_error, data.preview_steps),
129
154
  stdout: () => {
130
- // stdout format is handled by publish_repos function's logging
155
+ // stdout format is handled by the executor's logging
131
156
  // This should never be called due to early return in task
132
157
  },
133
158
  });
159
+ /** Logs the ordered side-effect preview to stdout. */
160
+ const log_preview = (steps, log) => {
161
+ log.info(st('cyan', '\nSide-effect preview (what a real publish would perform):'));
162
+ for (const line of format_publish_steps(steps)) {
163
+ log.info(st('dim', ` ${line}`));
164
+ }
165
+ };
134
166
  // Format the publishing result as markdown
135
- const format_result_markdown = (result, fatal_error) => {
167
+ const format_result_markdown = (result, fatal_error, preview_steps) => {
136
168
  const lines = [];
137
169
  lines.push('# Publishing Result');
138
170
  lines.push('');
@@ -168,6 +200,29 @@ const format_result_markdown = (result, fatal_error) => {
168
200
  lines.push(`- \`${name}\`: ${error.message}`);
169
201
  }
170
202
  }
203
+ if (result.plan_warnings.length > 0) {
204
+ lines.push('');
205
+ lines.push('## Plan Warnings');
206
+ lines.push('');
207
+ for (const warning of result.plan_warnings)
208
+ lines.push(`- ${warning}`);
209
+ }
210
+ if (result.plan_errors.length > 0) {
211
+ lines.push('');
212
+ lines.push('## Plan Errors');
213
+ lines.push('');
214
+ for (const plan_error of result.plan_errors)
215
+ lines.push(`- ${plan_error}`);
216
+ }
217
+ if (preview_steps) {
218
+ lines.push('');
219
+ lines.push('## Side-Effect Preview');
220
+ lines.push('');
221
+ lines.push('```');
222
+ for (const line of format_publish_steps(preview_steps))
223
+ lines.push(line);
224
+ lines.push('```');
225
+ }
171
226
  return lines;
172
227
  };
173
228
  /**
@@ -1,13 +1,14 @@
1
1
  import { type Task } from '@fuzdev/gro';
2
2
  import { z } from 'zod';
3
3
  export declare const Args: z.ZodObject<{
4
- command: z.ZodString;
4
+ _: z.ZodDefault<z.ZodArray<z.ZodString>>;
5
5
  config: z.ZodDefault<z.ZodString>;
6
6
  concurrency: z.ZodDefault<z.ZodNumber>;
7
7
  format: z.ZodDefault<z.ZodEnum<{
8
8
  text: "text";
9
9
  json: "json";
10
10
  }>>;
11
+ outfile: z.ZodOptional<z.ZodString>;
11
12
  }, z.core.$strict>;
12
13
  export type Args = z.infer<typeof Args>;
13
14
  export declare const task: Task<Args>;
@@ -1 +1 @@
1
- {"version":3,"file":"gitops_run.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_run.task.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,IAAI,EAAC,MAAM,aAAa,CAAC;AACjD,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAStB,eAAO,MAAM,IAAI;;;;;;;;kBAaf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAaxC,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CAgL3B,CAAC"}
1
+ {"version":3,"file":"gitops_run.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_run.task.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,IAAI,EAAC,MAAM,aAAa,CAAC;AACjD,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAUtB,eAAO,MAAM,IAAI;;;;;;;;;kBAmBf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAaxC,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CAyM3B,CAAC"}
@@ -2,12 +2,15 @@ import { TaskError } from '@fuzdev/gro';
2
2
  import { z } from 'zod';
3
3
  import { map_concurrent_settled } from '@fuzdev/fuz_util/async.js';
4
4
  import { spawn_out } from '@fuzdev/fuz_util/process.js';
5
+ import { writeFile } from 'node:fs/promises';
5
6
  import { styleText as st } from 'node:util';
6
7
  import { resolve } from 'node:path';
7
8
  import { get_repo_paths } from './repo_ops.js';
8
9
  import { GITOPS_CONCURRENCY_DEFAULT, GITOPS_CONFIG_PATH_DEFAULT } from './gitops_constants.js';
9
10
  export const Args = z.strictObject({
10
- command: z.string().meta({ description: 'shell command to run in each repo' }),
11
+ // Positional rest args (gro convention) so `gro gitops_run "npm test"` works;
12
+ // joined with spaces and passed to `sh -c`, so quote commands that contain flags.
13
+ _: z.array(z.string()).meta({ description: 'shell command to run in each repo' }).default([]),
11
14
  config: z
12
15
  .string()
13
16
  .meta({ description: 'path to the gitops config file' })
@@ -19,12 +22,20 @@ export const Args = z.strictObject({
19
22
  .meta({ description: 'maximum number of repos to run in parallel' })
20
23
  .default(GITOPS_CONCURRENCY_DEFAULT),
21
24
  format: z.enum(['text', 'json']).meta({ description: 'output format' }).default('text'),
25
+ outfile: z
26
+ .string()
27
+ .meta({ description: 'with --format json, write clean JSON to this file instead of stdout' })
28
+ .optional(),
22
29
  });
23
30
  export const task = {
24
31
  Args,
25
32
  summary: 'run a shell command across all repos in parallel',
26
33
  run: async ({ args, log }) => {
27
- const { command, config, concurrency, format } = args;
34
+ const { _, config, concurrency, format, outfile } = args;
35
+ const command = _.join(' ').trim();
36
+ if (!command) {
37
+ throw new TaskError('No command provided, e.g. `gro gitops_run "npm test"`');
38
+ }
28
39
  // Get repo paths (lightweight, no library-metadata loading needed)
29
40
  const config_path = resolve(config);
30
41
  const repos = await get_repo_paths(config_path);
@@ -113,18 +124,40 @@ export const task = {
113
124
  duration_ms: Math.round(total_duration_ms),
114
125
  },
115
126
  };
116
- // eslint-disable-next-line no-console
117
- console.log(JSON.stringify(json_output, null, 2));
127
+ const json = JSON.stringify(json_output, null, 2);
128
+ if (outfile) {
129
+ // Clean machine-readable output, free of gro's logging prefixes.
130
+ await writeFile(outfile, json);
131
+ log.info(`wrote JSON output to ${outfile}`);
132
+ }
133
+ else {
134
+ // eslint-disable-next-line no-console
135
+ console.log(json);
136
+ }
118
137
  }
119
138
  else {
120
139
  // Text format
121
140
  log.info(''); // blank line
122
- // Show successes
141
+ // Show successes, including the command's output so read-only commands
142
+ // (`git rev-parse`, `cat package.json`, …) are useful without `--format json`.
123
143
  if (successes.length > 0) {
124
144
  log.info(st('green', `✓ ${successes.length} succeeded:`));
125
145
  for (const result of successes) {
126
- const duration = `${Math.round(result.duration_ms)}ms`;
127
- log.info(st('gray', ` ${result.repo_name} ${st('blue', `(${duration})`)}`));
146
+ const duration = st('blue', `(${Math.round(result.duration_ms)}ms)`);
147
+ const label = st('gray', ` ${result.repo_name}`);
148
+ const out = result.stdout.trim();
149
+ const out_lines = out ? out.split('\n') : [];
150
+ if (out_lines.length <= 1) {
151
+ // single-line (or empty) output inline after the repo name
152
+ log.info(out ? `${label} ${out} ${duration}` : `${label} ${duration}`);
153
+ }
154
+ else {
155
+ // multi-line output indented under the repo
156
+ log.info(`${label} ${duration}`);
157
+ for (const line of out_lines) {
158
+ log.info(st('gray', ` ${line}`));
159
+ }
160
+ }
128
161
  }
129
162
  }
130
163
  // Show failures with details
@@ -7,6 +7,7 @@ export declare const Args: z.ZodObject<{
7
7
  outdir: z.ZodOptional<z.ZodString>;
8
8
  download: z.ZodDefault<z.ZodBoolean>;
9
9
  check: z.ZodDefault<z.ZodBoolean>;
10
+ allow_dirty: z.ZodDefault<z.ZodBoolean>;
10
11
  }, z.core.$strict>;
11
12
  export type Args = z.infer<typeof Args>;
12
13
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"gitops_sync.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_sync.task.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,IAAI,EAAC,MAAM,aAAa,CAAC;AACjD,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAiBtB,cAAc;AACd,eAAO,MAAM,IAAI;;;;;;kBAkBf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC;;;;GAIG;AACH,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CAqF3B,CAAC"}
1
+ {"version":3,"file":"gitops_sync.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_sync.task.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,IAAI,EAAC,MAAM,aAAa,CAAC;AACjD,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAiBtB,cAAc;AACd,eAAO,MAAM,IAAI;;;;;;;kBAyBf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC;;;;GAIG;AACH,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CA6F3B,CAAC"}
@@ -32,6 +32,12 @@ export const Args = z.strictObject({
32
32
  .boolean()
33
33
  .meta({ description: 'check repos are ready without fetching remote data' })
34
34
  .default(false),
35
+ allow_dirty: z
36
+ .boolean()
37
+ .meta({
38
+ description: 'sync (switch branch, pull) tolerating uncommitted changes instead of failing on a dirty workspace',
39
+ })
40
+ .default(false),
35
41
  });
36
42
  /**
37
43
  * This is a task not a `.gen.` file because it makes network calls.
@@ -42,8 +48,16 @@ export const task = {
42
48
  Args,
43
49
  summary: 'syncs local repos and generates UI data from repo metadata',
44
50
  run: async ({ args, log, svelte_config, invoke_task }) => {
45
- const { config, dir, outdir = svelte_config.routes_path, download, check } = args;
46
- const { local_repos } = await get_gitops_ready({ config, dir, download, log });
51
+ const { config, dir, outdir = svelte_config.routes_path, download, check, allow_dirty } = args;
52
+ // `gitops_sync` is the task whose job is to mutate working trees, so it always syncs.
53
+ const { local_repos } = await get_gitops_ready({
54
+ config,
55
+ dir,
56
+ download,
57
+ sync: true,
58
+ allow_dirty,
59
+ log,
60
+ });
47
61
  const outfile_json = resolve(outdir, 'repos.json');
48
62
  const outfile_ts = resolve(outdir, 'repos.ts');
49
63
  // This searches the parent directory for the env var, so we don't use SvelteKit's $env imports