@fuzdev/fuz_gitops 0.78.1 → 0.79.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 (179) hide show
  1. package/README.md +78 -24
  2. package/dist/ModulesDetail.svelte +15 -22
  3. package/dist/ModulesDetail.svelte.d.ts.map +1 -1
  4. package/dist/ModulesNav.svelte +4 -1
  5. package/dist/ModulesPage.svelte +1 -3
  6. package/dist/ModulesPage.svelte.d.ts.map +1 -1
  7. package/dist/PageHeader.svelte +2 -2
  8. package/dist/PageHeader.svelte.d.ts +0 -3
  9. package/dist/PageHeader.svelte.d.ts.map +1 -1
  10. package/dist/PullRequestsDetail.svelte +10 -9
  11. package/dist/PullRequestsDetail.svelte.d.ts.map +1 -1
  12. package/dist/PullRequestsPage.svelte +1 -3
  13. package/dist/PullRequestsPage.svelte.d.ts.map +1 -1
  14. package/dist/ReposTable.svelte +27 -48
  15. package/dist/ReposTable.svelte.d.ts.map +1 -1
  16. package/dist/ReposTree.svelte +5 -19
  17. package/dist/ReposTree.svelte.d.ts.map +1 -1
  18. package/dist/ReposTreeNav.svelte +5 -7
  19. package/dist/ReposTreeNav.svelte.d.ts.map +1 -1
  20. package/dist/TablePage.svelte +1 -3
  21. package/dist/TablePage.svelte.d.ts.map +1 -1
  22. package/dist/TreeItemPage.svelte +1 -3
  23. package/dist/TreeItemPage.svelte.d.ts.map +1 -1
  24. package/dist/TreePage.svelte +1 -3
  25. package/dist/TreePage.svelte.d.ts.map +1 -1
  26. package/dist/ci_reconcile.d.ts +6 -10
  27. package/dist/ci_reconcile.d.ts.map +1 -1
  28. package/dist/ci_reconcile.js +6 -8
  29. package/dist/dependency_graph.d.ts +26 -49
  30. package/dist/dependency_graph.d.ts.map +1 -1
  31. package/dist/dependency_graph.js +37 -80
  32. package/dist/dependency_updater.d.ts +1 -1
  33. package/dist/dependency_updater.d.ts.map +1 -1
  34. package/dist/dependency_updater.js +6 -1
  35. package/dist/fetch_repo_data.d.ts +34 -8
  36. package/dist/fetch_repo_data.d.ts.map +1 -1
  37. package/dist/fetch_repo_data.js +44 -22
  38. package/dist/fs_fetch_value_cache.d.ts +1 -1
  39. package/dist/fs_fetch_value_cache.js +1 -1
  40. package/dist/git_operations.d.ts +6 -49
  41. package/dist/git_operations.d.ts.map +1 -1
  42. package/dist/git_operations.js +13 -126
  43. package/dist/github.d.ts +17 -2
  44. package/dist/github.d.ts.map +1 -1
  45. package/dist/github.js +15 -6
  46. package/dist/github_helpers.d.ts.map +1 -1
  47. package/dist/github_helpers.js +3 -11
  48. package/dist/gitops_analyze.task.d.ts +24 -2
  49. package/dist/gitops_analyze.task.d.ts.map +1 -1
  50. package/dist/gitops_analyze.task.js +43 -30
  51. package/dist/gitops_config.d.ts +34 -79
  52. package/dist/gitops_config.d.ts.map +1 -1
  53. package/dist/gitops_config.js +76 -53
  54. package/dist/gitops_constants.d.ts +6 -4
  55. package/dist/gitops_constants.d.ts.map +1 -1
  56. package/dist/gitops_constants.js +6 -4
  57. package/dist/gitops_plan.task.d.ts +29 -3
  58. package/dist/gitops_plan.task.d.ts.map +1 -1
  59. package/dist/gitops_plan.task.js +80 -58
  60. package/dist/gitops_publish.task.d.ts +62 -2
  61. package/dist/gitops_publish.task.d.ts.map +1 -1
  62. package/dist/gitops_publish.task.js +187 -102
  63. package/dist/gitops_run.task.d.ts +74 -0
  64. package/dist/gitops_run.task.d.ts.map +1 -1
  65. package/dist/gitops_run.task.js +174 -160
  66. package/dist/gitops_sync.task.d.ts +65 -3
  67. package/dist/gitops_sync.task.d.ts.map +1 -1
  68. package/dist/gitops_sync.task.js +154 -30
  69. package/dist/gitops_task_helpers.d.ts +86 -59
  70. package/dist/gitops_task_helpers.d.ts.map +1 -1
  71. package/dist/gitops_task_helpers.js +120 -79
  72. package/dist/gitops_validate.task.d.ts +1 -2
  73. package/dist/gitops_validate.task.d.ts.map +1 -1
  74. package/dist/gitops_validate.task.js +15 -22
  75. package/dist/graph_validation.d.ts +16 -27
  76. package/dist/graph_validation.d.ts.map +1 -1
  77. package/dist/graph_validation.js +23 -64
  78. package/dist/local_repo.d.ts +50 -62
  79. package/dist/local_repo.d.ts.map +1 -1
  80. package/dist/local_repo.js +80 -193
  81. package/dist/log_helpers.d.ts +8 -8
  82. package/dist/log_helpers.d.ts.map +1 -1
  83. package/dist/multi_repo_publisher.d.ts +10 -0
  84. package/dist/multi_repo_publisher.d.ts.map +1 -1
  85. package/dist/multi_repo_publisher.js +115 -51
  86. package/dist/npm_registry.d.ts +23 -19
  87. package/dist/npm_registry.d.ts.map +1 -1
  88. package/dist/npm_registry.js +20 -47
  89. package/dist/operations.d.ts +75 -180
  90. package/dist/operations.d.ts.map +1 -1
  91. package/dist/operations.js +15 -7
  92. package/dist/operations_defaults.d.ts +17 -2
  93. package/dist/operations_defaults.d.ts.map +1 -1
  94. package/dist/operations_defaults.js +104 -121
  95. package/dist/output_helpers.d.ts +36 -0
  96. package/dist/output_helpers.d.ts.map +1 -1
  97. package/dist/output_helpers.js +37 -3
  98. package/dist/preflight_checks.d.ts +20 -27
  99. package/dist/preflight_checks.d.ts.map +1 -1
  100. package/dist/preflight_checks.js +37 -126
  101. package/dist/publish_steps.d.ts +2 -4
  102. package/dist/publish_steps.d.ts.map +1 -1
  103. package/dist/publish_steps.js +2 -6
  104. package/dist/publishing_event.d.ts +2 -8
  105. package/dist/publishing_event.d.ts.map +1 -1
  106. package/dist/publishing_event.js +5 -4
  107. package/dist/publishing_event_handler.d.ts +10 -8
  108. package/dist/publishing_event_handler.d.ts.map +1 -1
  109. package/dist/publishing_event_handler.js +14 -12
  110. package/dist/publishing_plan.d.ts +16 -1
  111. package/dist/publishing_plan.d.ts.map +1 -1
  112. package/dist/publishing_plan.js +328 -250
  113. package/dist/publishing_plan_logging.d.ts +1 -1
  114. package/dist/publishing_plan_logging.d.ts.map +1 -1
  115. package/dist/publishing_plan_logging.js +21 -14
  116. package/dist/repo.svelte.d.ts +7 -0
  117. package/dist/repo.svelte.d.ts.map +1 -1
  118. package/dist/repo.svelte.js +3 -0
  119. package/dist/repo_readiness.d.ts +210 -0
  120. package/dist/repo_readiness.d.ts.map +1 -0
  121. package/dist/repo_readiness.js +516 -0
  122. package/dist/repos_status.d.ts +3340 -0
  123. package/dist/repos_status.d.ts.map +1 -0
  124. package/dist/repos_status.js +555 -0
  125. package/dist/repos_status_load.d.ts +65 -0
  126. package/dist/repos_status_load.d.ts.map +1 -0
  127. package/dist/repos_status_load.js +131 -0
  128. package/package.json +7 -27
  129. package/src/lib/ci_reconcile.ts +7 -11
  130. package/src/lib/dependency_graph.ts +45 -97
  131. package/src/lib/dependency_updater.ts +7 -1
  132. package/src/lib/fetch_repo_data.ts +66 -29
  133. package/src/lib/fs_fetch_value_cache.ts +1 -1
  134. package/src/lib/git_operations.ts +15 -187
  135. package/src/lib/github.ts +20 -8
  136. package/src/lib/github_helpers.ts +5 -12
  137. package/src/lib/gitops_analyze.task.ts +79 -41
  138. package/src/lib/gitops_config.ts +88 -137
  139. package/src/lib/gitops_constants.ts +6 -4
  140. package/src/lib/gitops_plan.task.ts +113 -61
  141. package/src/lib/gitops_publish.task.ts +255 -122
  142. package/src/lib/gitops_run.task.ts +250 -178
  143. package/src/lib/gitops_sync.task.ts +208 -33
  144. package/src/lib/gitops_task_helpers.ts +166 -110
  145. package/src/lib/gitops_validate.task.ts +17 -27
  146. package/src/lib/graph_validation.ts +25 -86
  147. package/src/lib/local_repo.ts +109 -316
  148. package/src/lib/log_helpers.ts +8 -18
  149. package/src/lib/multi_repo_publisher.ts +138 -53
  150. package/src/lib/npm_registry.ts +37 -62
  151. package/src/lib/operations.ts +78 -150
  152. package/src/lib/operations_defaults.ts +108 -154
  153. package/src/lib/output_helpers.ts +55 -3
  154. package/src/lib/preflight_checks.ts +47 -183
  155. package/src/lib/publish_steps.ts +15 -13
  156. package/src/lib/publishing_event.ts +5 -4
  157. package/src/lib/publishing_event_handler.ts +18 -16
  158. package/src/lib/publishing_plan.ts +451 -287
  159. package/src/lib/publishing_plan_logging.ts +32 -23
  160. package/src/lib/repo.svelte.ts +8 -0
  161. package/src/lib/repo_readiness.ts +647 -0
  162. package/src/lib/repos_status.ts +671 -0
  163. package/src/lib/repos_status_load.ts +173 -0
  164. package/dist/config_reconcile.d.ts +0 -46
  165. package/dist/config_reconcile.d.ts.map +0 -1
  166. package/dist/config_reconcile.js +0 -64
  167. package/dist/paths.d.ts +0 -7
  168. package/dist/paths.d.ts.map +0 -1
  169. package/dist/paths.js +0 -6
  170. package/dist/repo_ops.d.ts +0 -59
  171. package/dist/repo_ops.d.ts.map +0 -1
  172. package/dist/repo_ops.js +0 -170
  173. package/dist/resolved_gitops_config.d.ts +0 -9
  174. package/dist/resolved_gitops_config.d.ts.map +0 -1
  175. package/dist/resolved_gitops_config.js +0 -12
  176. package/src/lib/config_reconcile.ts +0 -98
  177. package/src/lib/paths.ts +0 -6
  178. package/src/lib/repo_ops.ts +0 -216
  179. package/src/lib/resolved_gitops_config.ts +0 -27
@@ -1,13 +1,15 @@
1
1
  import { z } from 'zod';
2
2
  import { createInterface } from 'node:readline/promises';
3
3
  import { styleText as st } from 'node:util';
4
- import { get_gitops_ready } from './gitops_task_helpers.js';
4
+ import { gate_publish_readiness, get_gitops_ready, log_readiness_block } from './gitops_task_helpers.js';
5
+ import { default_gitops_operations, default_repos_operations } from './operations_defaults.js';
5
6
  import { execute_publishing_plan } from './multi_repo_publisher.js';
6
- import { stdout_handler } from './publishing_event_handler.js';
7
- import { generate_publishing_plan, log_publishing_plan } from './publishing_plan.js';
7
+ import { mask_secrets, masking_handler, redact_secrets, stdout_handler } from './publishing_event_handler.js';
8
+ import { generate_publishing_plan } from './publishing_plan.js';
9
+ import { log_publishing_plan } from './publishing_plan_logging.js';
8
10
  import { derive_publish_steps, format_publish_steps } from './publish_steps.js';
9
11
  import { decide_publish_gate, publish_run_failed } from './publish_gate.js';
10
- import { format_and_output } from './output_helpers.js';
12
+ import { format_and_output, output_is_machine, route_human_output } from './output_helpers.js';
11
13
  import { GITOPS_CONFIG_PATH_DEFAULT, GITOPS_NPM_WAIT_TIMEOUT_DEFAULT } from './gitops_constants.js';
12
14
  /** @nodocs */
13
15
  export const Args = z.strictObject({
@@ -15,20 +17,29 @@ export const Args = z.strictObject({
15
17
  .string()
16
18
  .meta({ description: 'path to the gitops config file, absolute or relative to the cwd' })
17
19
  .default(GITOPS_CONFIG_PATH_DEFAULT),
18
- dir: z
20
+ registry: z
19
21
  .string()
20
- .meta({ description: 'path containing the repos, defaults to the parent of the config dir' })
22
+ .meta({
23
+ description: 'path to the repos.toml registry, when `repos` would not find it walking up from the cwd'
24
+ })
21
25
  .optional(),
22
26
  peer_strategy: z
23
- .enum(['exact', 'caret', 'tilde'])
24
- .meta({ description: 'version strategy for peer dependencies' })
27
+ .enum(['exact', 'caret', 'tilde', 'gte'])
28
+ .meta({
29
+ description: 'range prefix for a rewritten dependency of any type whose range has none (an existing prefix is kept; a wildcard becomes ^)'
30
+ })
25
31
  .default('caret'),
26
32
  wetrun: z.boolean().meta({ description: 'actually publish (default is dry run)' }).default(false),
27
33
  format: z
28
34
  .enum(['stdout', 'json', 'markdown'])
29
35
  .meta({ description: 'output format' })
30
36
  .default('stdout'),
31
- deploy: z.boolean().meta({ description: 'deploy all repos after publishing' }).default(false),
37
+ deploy: z
38
+ .boolean()
39
+ .meta({
40
+ description: 'deploy each repo the run changed (published, or any dependency updated) after publishing'
41
+ })
42
+ .default(false),
32
43
  plan: z
33
44
  .boolean()
34
45
  .meta({ description: 'show the plan and confirm before publishing; --no-plan to skip' })
@@ -46,12 +57,6 @@ export const Args = z.strictObject({
46
57
  .boolean()
47
58
  .meta({ description: 'show additional details in plan output' })
48
59
  .default(false),
49
- sync: z
50
- .boolean()
51
- .meta({
52
- description: 'sync repos (switch branch, pull, install) before the dry run instead of reading the working tree as-is; always on for --wetrun'
53
- })
54
- .default(false),
55
60
  preview: z
56
61
  .boolean()
57
62
  .meta({ description: 'show the ordered side-effects a --wetrun would perform' })
@@ -59,107 +64,187 @@ export const Args = z.strictObject({
59
64
  });
60
65
  /** @nodocs */
61
66
  export const task = {
62
- summary: 'publish all repos in dependency order',
67
+ summary: 'publish the packages the plan publishes, in dependency order (a dry run unless --wetrun)',
63
68
  Args,
64
69
  run: async ({ args, log }) => {
65
- const { config, dir, peer_strategy, wetrun, format, deploy, plan, max_wait, emit_json, outfile, verbose, sync, preview } = args;
66
- // Load repos. A dry run reads the working tree as-is unless `--sync`;
67
- // a real publish (`--wetrun`) always syncs so preflight sees the canonical branches.
68
- const { local_repos: repos } = await get_gitops_ready({
69
- config,
70
- dir,
71
- download: false, // Don't download if missing
72
- sync: sync || wetrun,
73
- log
74
- });
75
- // Generate the plan once; the executor consumes this exact plan (no second pass).
76
- const publishing_plan = await generate_publishing_plan(repos, { verbose });
77
- const preview_steps = preview ? derive_publish_steps(publishing_plan, { deploy }) : null;
78
- // Decide whether to show the plan + confirm, block, or proceed (the decision table lives
79
- // in `decide_publish_gate`; readline + exit stay here at the edge).
80
- const gate = decide_publish_gate({ wetrun, show_plan: plan, plan: publishing_plan });
81
- // A real publish that shows its plan prints it first — including before a `blocked` throw,
82
- // so the operator sees the errors that blocked it.
83
- if (gate.action !== 'proceed') {
84
- log.info(st('cyan', 'Publishing Plan'));
85
- log_publishing_plan(publishing_plan, log, { verbose });
70
+ const outcome = await run_gitops_publish(args, log);
71
+ if (outcome === 'cancelled') {
72
+ process.exit(0);
86
73
  }
87
- if (gate.action === 'blocked') {
88
- throw new Error(gate.message);
74
+ else if (outcome === 'failed') {
75
+ process.exit(1);
89
76
  }
90
- else if (gate.action === 'confirm') {
91
- if (preview_steps)
92
- log_preview(preview_steps, log);
93
- // Ask for confirmation
94
- log.info(st('yellow', '⚠️ This will publish the packages shown above.'));
95
- process.stdout.write('Continue with publishing? (y/n): ');
96
- const confirmed = await prompt_for_confirmation();
97
- if (!confirmed) {
98
- log.info('Publishing cancelled');
99
- process.exit(0);
100
- }
77
+ }
78
+ };
79
+ const default_gitops_publish_deps = {
80
+ load_repos: get_gitops_ready,
81
+ repos_ops: default_repos_operations,
82
+ ops: default_gitops_operations,
83
+ confirm: async () => {
84
+ process.stderr.write('Continue with publishing? (y/n): ');
85
+ return prompt_for_confirmation();
86
+ }
87
+ };
88
+ /**
89
+ * Runs `gro gitops_publish`, in order: load the repos as they sit,
90
+ * generate the plan, and — for `--wetrun` — run the readiness gate
91
+ * (`gate_publish_readiness`, read-only: `repos status --fetch`) before
92
+ * printing the plan and asking to confirm, so a refusal or a "no" changes
93
+ * nothing. Then preflight and the executor. A dry run skips the gate and
94
+ * prints the diagnostics' readiness block instead. Under `--emit_json`, or
95
+ * `--format json` or `markdown` without `--outfile`, the log goes to stderr
96
+ * and stdout carries the events or the report alone (`route_human_output`).
97
+ *
98
+ * @returns `cancelled` when the operator declines, `failed` when the run failed (its output already written), else `done`
99
+ * @throws {TaskError} when the readiness gate refuses or loading fails
100
+ * @nodocs
101
+ */
102
+ export const run_gitops_publish = async (args, log, deps = {}) => {
103
+ const { load_repos, repos_ops, ops, confirm } = { ...default_gitops_publish_deps, ...deps };
104
+ const { config, registry, peer_strategy, wetrun, format, deploy, plan, max_wait, emit_json, outfile, verbose, preview } = args;
105
+ // With a machine-readable stdout, every human line goes to stderr: the log (the plan, the
106
+ // readiness block and gate, the executor's progress) and, in `confirm`, the prompt
107
+ const write_stdout = route_human_output(log, to_child_stdout(args) === 'stderr');
108
+ // Load repos as they sit; a real publish gates on their state below rather than moving them
109
+ const { local_repos: repos } = await load_repos({ config, registry, log });
110
+ // Generate the plan once; the executor consumes this exact plan (no second pass).
111
+ const publishing_plan = await generate_publishing_plan(repos, { verbose, ops: ops.changeset });
112
+ const preview_steps = preview ? derive_publish_steps(publishing_plan, { deploy }) : null;
113
+ // Decide whether to show the plan + confirm, block, or proceed (the decision table lives
114
+ // in `decide_publish_gate`; readline + exit stay at the edge).
115
+ const gate = decide_publish_gate({ wetrun, show_plan: plan, plan: publishing_plan });
116
+ if (wetrun) {
117
+ // The readiness gate: fetch, then refuse unless every npm repo is ready. Read-only, and
118
+ // before the prompt and any side effect. A plan with errors is refused without fetching:
119
+ // below when the plan is shown, else by the executor before anything else it does.
120
+ if (publishing_plan.errors.length === 0) {
121
+ await gate_publish_readiness({
122
+ local_repos: repos,
123
+ registry,
124
+ publishing: new Set(publishing_plan.version_changes.map((vc) => vc.package_name)),
125
+ log,
126
+ repos_ops
127
+ });
101
128
  }
102
- else if (preview_steps && format === 'stdout') {
103
- // proceed (dry run or --no-plan): only render to stdout for the human format;
104
- // json/markdown carry the preview in their structured output, so logging here too
105
- // would corrupt that stream.
129
+ }
130
+ else {
131
+ log_readiness_block(repos, log);
132
+ }
133
+ // A real publish that shows its plan prints it first — including before a `blocked` throw,
134
+ // so the operator sees the errors that blocked it.
135
+ if (gate.action !== 'proceed') {
136
+ log.info(st('cyan', 'Publishing Plan'));
137
+ log_publishing_plan(publishing_plan, log, { verbose });
138
+ }
139
+ if (gate.action === 'blocked') {
140
+ throw new Error(gate.message);
141
+ }
142
+ else if (gate.action === 'confirm') {
143
+ if (preview_steps)
106
144
  log_preview(preview_steps, log);
145
+ // Ask for confirmation
146
+ log.info(st('yellow', '⚠️ This will publish the packages shown above.'));
147
+ const confirmed = await confirm();
148
+ if (!confirmed) {
149
+ log.info('Publishing cancelled');
150
+ return 'cancelled';
107
151
  }
108
- // Publishing options
109
- const options = {
110
- wetrun,
111
- version_strategy: peer_strategy,
112
- deploy,
113
- max_wait,
114
- log,
115
- // Live JSON-lines stream when requested; events also surface on the result.
116
- events: emit_json ? stdout_handler() : undefined
152
+ }
153
+ else if (preview_steps && format === 'stdout') {
154
+ // proceed (dry run or --no-plan): only render to stdout for the human format;
155
+ // json/markdown carry the preview in their structured output, so logging here too
156
+ // would corrupt that stream.
157
+ log_preview(preview_steps, log);
158
+ }
159
+ // Publishing options
160
+ const options = {
161
+ wetrun,
162
+ version_strategy: peer_strategy,
163
+ deploy,
164
+ max_wait,
165
+ log,
166
+ ops,
167
+ registry,
168
+ // Live JSON-lines stream when requested, secrets masked since failure messages carry
169
+ // npm's stderr; events also surface on the result.
170
+ events: emit_json ? masking_handler(stdout_handler(write_stdout)) : undefined,
171
+ child_stdout: to_child_stdout(args)
172
+ };
173
+ // Execute publishing (may throw on fatal errors like circular dependencies)
174
+ let result;
175
+ let fatal_error = null;
176
+ try {
177
+ result = await execute_publishing_plan(repos, publishing_plan, options);
178
+ }
179
+ catch (error) {
180
+ // Construct a failure result for fatal errors so output can still be generated
181
+ fatal_error = error instanceof Error ? error : new Error(String(error));
182
+ result = {
183
+ ok: false,
184
+ published: [],
185
+ // Note: FATAL_ERROR is a placeholder - only fatal_error.message is displayed in output
186
+ failed: [{ name: 'FATAL_ERROR', error: fatal_error }],
187
+ duration: 0,
188
+ events: [],
189
+ summary: { total: 0, published: 0, failed: 1, skipped: 0, duration: 0 },
190
+ plan_errors: publishing_plan.errors,
191
+ plan_warnings: publishing_plan.warnings
117
192
  };
118
- // Execute publishing (may throw on fatal errors like circular dependencies)
119
- let result;
120
- let fatal_error = null;
121
- try {
122
- result = await execute_publishing_plan(repos, publishing_plan, options);
123
- }
124
- catch (error) {
125
- // Construct a failure result for fatal errors so output can still be generated
126
- fatal_error = error instanceof Error ? error : new Error(String(error));
127
- result = {
128
- ok: false,
129
- published: [],
130
- // Note: FATAL_ERROR is a placeholder - only fatal_error.message is displayed in output
131
- failed: [{ name: 'FATAL_ERROR', error: fatal_error }],
132
- duration: 0,
133
- events: [],
134
- summary: { total: 0, published: 0, failed: 1, skipped: 0, duration: 0 },
135
- plan_errors: publishing_plan.errors,
136
- plan_warnings: publishing_plan.warnings
137
- };
138
- }
139
- // Format and output result (always runs, even on fatal errors)
140
- // Note: stdout format is handled by the executor's logging
141
- if (format !== 'stdout') {
142
- await format_and_output({ result, fatal_error, preview_steps }, create_publish_formatters(), {
143
- format,
144
- outfile,
145
- log
146
- });
147
- }
148
- // Exit with error if failed
149
- if (publish_run_failed(result, fatal_error)) {
150
- process.exit(1);
151
- }
152
193
  }
194
+ // Format and output result (always runs, even on fatal errors)
195
+ // Note: stdout format is handled by the executor's logging
196
+ if (format !== 'stdout') {
197
+ await format_and_output({ result, fatal_error, preview_steps }, create_publish_formatters(), {
198
+ format,
199
+ outfile,
200
+ log,
201
+ write_stdout
202
+ });
203
+ }
204
+ return publish_run_failed(result, fatal_error) ? 'failed' : 'done';
205
+ };
206
+ /**
207
+ * Where `gro publish` and `gro deploy`, streaming live, show their stdout: on
208
+ * our stderr when our stdout carries a machine-readable stream — the
209
+ * `--emit_json` events, or a JSON or markdown report not sent to `--outfile` —
210
+ * so their output can't corrupt it, else on our stdout. The same test sends
211
+ * the task's own log and prompt to stderr.
212
+ *
213
+ * @nodocs
214
+ */
215
+ export const to_child_stdout = (args) => args.emit_json || output_is_machine(args.format, args.outfile) ? 'stderr' : 'stdout';
216
+ /**
217
+ * A failed package's markdown: the message's first line on the bullet, and the
218
+ * rest — the end of a command's stderr — in an indented fenced block, its fence
219
+ * longer than any backtick run inside so stderr can't close it.
220
+ *
221
+ * @param name - the package name
222
+ * @param message - the failure message
223
+ * @nodocs
224
+ */
225
+ export const format_failure_markdown = (name, message) => {
226
+ const [head, ...rest] = message.split('\n');
227
+ const lines = [`- \`${name}\`: ${head}`];
228
+ if (rest.length === 0)
229
+ return lines;
230
+ const longest_run = Math.max(0, ...(rest.join('\n').match(/`+/g) ?? []).map((r) => r.length));
231
+ const fence = '`'.repeat(Math.max(3, longest_run + 1));
232
+ lines.push('', ` ${fence}`, ...rest.map((l) => ` ${l}`), ` ${fence}`);
233
+ return lines;
153
234
  };
154
235
  const create_publish_formatters = () => ({
155
- json: (data) => JSON.stringify(data.preview_steps ? { ...data.result, preview: data.preview_steps } : data.result, null, 2),
236
+ json: (data) => {
237
+ // the result's events are the executor's unmasked capture; mask them as the live stream is
238
+ const result = { ...data.result, events: data.result.events.map(mask_secrets) };
239
+ return JSON.stringify(data.preview_steps ? { ...result, preview: data.preview_steps } : result, null, 2);
240
+ },
156
241
  markdown: (data) => format_result_markdown(data.result, data.fatal_error, data.preview_steps),
157
242
  stdout: () => {
158
243
  // stdout format is handled by the executor's logging
159
244
  // This should never be called due to early return in task
160
245
  }
161
246
  });
162
- /** Logs the ordered side-effect preview to stdout. */
247
+ /** Logs the ordered side-effect preview. */
163
248
  const log_preview = (steps, log) => {
164
249
  log.info(st('cyan', '\nSide-effect preview (what a real publish would perform):'));
165
250
  for (const line of format_publish_steps(steps)) {
@@ -175,7 +260,7 @@ const format_result_markdown = (result, fatal_error, preview_steps) => {
175
260
  if (fatal_error) {
176
261
  lines.push('## ❌ Fatal Error');
177
262
  lines.push('');
178
- lines.push(`**Error**: ${fatal_error.message}`);
263
+ lines.push(`**Error**: ${redact_secrets(fatal_error.message)}`);
179
264
  lines.push('');
180
265
  lines.push('Publishing could not proceed due to the error above.');
181
266
  lines.push('');
@@ -200,7 +285,7 @@ const format_result_markdown = (result, fatal_error, preview_steps) => {
200
285
  lines.push('## Failed Packages');
201
286
  lines.push('');
202
287
  for (const { name, error } of result.failed) {
203
- lines.push(`- \`${name}\`: ${error.message}`);
288
+ lines.push(...format_failure_markdown(name, redact_secrets(error.message)));
204
289
  }
205
290
  }
206
291
  if (result.plan_warnings.length > 0) {
@@ -235,7 +320,7 @@ const format_result_markdown = (result, fatal_error, preview_steps) => {
235
320
  const prompt_for_confirmation = async () => {
236
321
  const rl = createInterface({
237
322
  input: process.stdin,
238
- output: process.stdout
323
+ output: process.stderr
239
324
  });
240
325
  const answer = await rl.question('');
241
326
  rl.close();
@@ -1,8 +1,12 @@
1
1
  import { type Task } from '@fuzdev/gro';
2
+ import type { Logger } from '@fuzdev/fuz_util/log.ts';
2
3
  import { z } from 'zod';
4
+ import type { ReposOperations } from './operations.js';
5
+ /** @nodocs */
3
6
  export declare const Args: z.ZodObject<{
4
7
  _: z.ZodDefault<z.ZodArray<z.ZodString>>;
5
8
  config: z.ZodDefault<z.ZodString>;
9
+ registry: z.ZodOptional<z.ZodString>;
6
10
  concurrency: z.ZodDefault<z.ZodNumber>;
7
11
  format: z.ZodDefault<z.ZodEnum<{
8
12
  text: "text";
@@ -11,5 +15,75 @@ export declare const Args: z.ZodObject<{
11
15
  outfile: z.ZodOptional<z.ZodString>;
12
16
  }, z.core.$strict>;
13
17
  export type Args = z.infer<typeof Args>;
18
+ /**
19
+ * How a command run in one repo ended: it exited with a code, a signal killed
20
+ * it, or it never started.
21
+ *
22
+ * @nodocs
23
+ */
24
+ export type GitopsRunOutcome = {
25
+ kind: 'exited';
26
+ code: number;
27
+ } | {
28
+ kind: 'signaled';
29
+ signal: string;
30
+ } | {
31
+ kind: 'error';
32
+ message: string;
33
+ };
34
+ /**
35
+ * What a command run in one repo printed, and how it ended.
36
+ *
37
+ * @nodocs
38
+ */
39
+ export interface GitopsRunCommandOutput {
40
+ outcome: GitopsRunOutcome;
41
+ stdout: string;
42
+ stderr: string;
43
+ }
44
+ /**
45
+ * One repo's result, as the JSON output carries it.
46
+ *
47
+ * @nodocs
48
+ */
49
+ export interface GitopsRunResult {
50
+ repo_name: string;
51
+ repo_dir: string;
52
+ status: 'success' | 'failure';
53
+ /** The command's exit code; `null` when a signal killed it or it never started. */
54
+ exit_code: number | null;
55
+ /** The signal that killed the command, else `null`. */
56
+ signal: string | null;
57
+ stdout: string;
58
+ stderr: string;
59
+ duration_ms: number;
60
+ /** Why the command never started. */
61
+ error?: string;
62
+ }
63
+ /**
64
+ * The side effects `run_gitops_run` reaches through, injectable for tests.
65
+ *
66
+ * @nodocs
67
+ */
68
+ export interface GitopsRunDeps {
69
+ /** Runs `repos status` to resolve the config's repos. */
70
+ repos_ops: ReposOperations;
71
+ /** Runs `command` through `sh -c` in `cwd`, capturing its output. */
72
+ run_command: (options: {
73
+ command: string;
74
+ cwd: string;
75
+ }) => Promise<GitopsRunCommandOutput>;
76
+ }
77
+ /** @nodocs */
14
78
  export declare const task: Task<Args>;
79
+ /**
80
+ * Runs `gro gitops_run`: resolves the config's repos through `repos status`,
81
+ * refusing a missing one, runs the command in each, `concurrency` at a time,
82
+ * and reports every result. Under `--format json` without `--outfile`, the log
83
+ * goes to stderr and stdout carries the JSON alone (`route_human_output`).
84
+ *
85
+ * @throws {TaskError} when no command is given, resolving the repos fails, or the command fails in any repo (after reporting)
86
+ * @nodocs
87
+ */
88
+ export declare const run_gitops_run: (args: Args, log: Logger, deps?: Partial<GitopsRunDeps>) => Promise<void>;
15
89
  //# sourceMappingURL=gitops_run.task.d.ts.map
@@ -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,EAAa,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAUxB,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"}
1
+ {"version":3,"file":"gitops_run.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_run.task.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AASxB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAIvD,cAAc;AACd,eAAO,MAAM,IAAI;;;;;;;;;;kBA0Bf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GACzB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtC;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACtC,OAAO,EAAE,gBAAgB,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;IAC9B,mFAAmF;IACnF,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,uDAAuD;IACvD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC7B,yDAAyD;IACzD,SAAS,EAAE,eAAe,CAAC;IAC3B,qEAAqE;IACrE,WAAW,EAAE,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAC;CAC5F;AAiBD,cAAc;AACd,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CAM3B,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GAC1B,MAAM,IAAI,EACV,KAAK,MAAM,EACX,OAAM,OAAO,CAAC,aAAa,CAAM,KAC/B,OAAO,CAAC,IAAI,CAkGd,CAAC"}