@fuzdev/fuz_gitops 0.78.0 → 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 (196) hide show
  1. package/README.md +91 -17
  2. package/dist/ModulesDetail.svelte +22 -27
  3. package/dist/ModulesDetail.svelte.d.ts.map +1 -1
  4. package/dist/ModulesNav.svelte +8 -5
  5. package/dist/ModulesNav.svelte.d.ts.map +1 -1
  6. package/dist/ModulesPage.svelte +3 -5
  7. package/dist/ModulesPage.svelte.d.ts.map +1 -1
  8. package/dist/PageFooter.svelte +2 -2
  9. package/dist/PageHeader.svelte +6 -6
  10. package/dist/PageHeader.svelte.d.ts +0 -3
  11. package/dist/PageHeader.svelte.d.ts.map +1 -1
  12. package/dist/PullRequestsDetail.svelte +29 -20
  13. package/dist/PullRequestsDetail.svelte.d.ts.map +1 -1
  14. package/dist/PullRequestsPage.svelte +4 -6
  15. package/dist/PullRequestsPage.svelte.d.ts.map +1 -1
  16. package/dist/ReposTable.svelte +43 -55
  17. package/dist/ReposTable.svelte.d.ts.map +1 -1
  18. package/dist/ReposTree.svelte +9 -21
  19. package/dist/ReposTree.svelte.d.ts.map +1 -1
  20. package/dist/ReposTreeNav.svelte +9 -12
  21. package/dist/ReposTreeNav.svelte.d.ts.map +1 -1
  22. package/dist/TablePage.svelte +3 -5
  23. package/dist/TablePage.svelte.d.ts.map +1 -1
  24. package/dist/TreeItemPage.svelte +3 -5
  25. package/dist/TreeItemPage.svelte.d.ts.map +1 -1
  26. package/dist/TreePage.svelte +3 -5
  27. package/dist/TreePage.svelte.d.ts.map +1 -1
  28. package/dist/changeset_generator.d.ts.map +1 -1
  29. package/dist/changeset_generator.js +2 -2
  30. package/dist/changeset_reader.d.ts.map +1 -1
  31. package/dist/changeset_reader.js +3 -3
  32. package/dist/ci_reconcile.d.ts +6 -10
  33. package/dist/ci_reconcile.d.ts.map +1 -1
  34. package/dist/ci_reconcile.js +6 -8
  35. package/dist/dependency_graph.d.ts +26 -49
  36. package/dist/dependency_graph.d.ts.map +1 -1
  37. package/dist/dependency_graph.js +40 -83
  38. package/dist/dependency_updater.d.ts +1 -1
  39. package/dist/dependency_updater.d.ts.map +1 -1
  40. package/dist/dependency_updater.js +14 -9
  41. package/dist/fetch_repo_data.d.ts +34 -8
  42. package/dist/fetch_repo_data.d.ts.map +1 -1
  43. package/dist/fetch_repo_data.js +44 -22
  44. package/dist/fs_fetch_value_cache.d.ts +1 -1
  45. package/dist/fs_fetch_value_cache.d.ts.map +1 -1
  46. package/dist/fs_fetch_value_cache.js +3 -3
  47. package/dist/git_operations.d.ts +6 -49
  48. package/dist/git_operations.d.ts.map +1 -1
  49. package/dist/git_operations.js +13 -126
  50. package/dist/github.d.ts +17 -2
  51. package/dist/github.d.ts.map +1 -1
  52. package/dist/github.js +17 -8
  53. package/dist/github_helpers.d.ts.map +1 -1
  54. package/dist/github_helpers.js +3 -11
  55. package/dist/gitops_analyze.task.d.ts +24 -2
  56. package/dist/gitops_analyze.task.d.ts.map +1 -1
  57. package/dist/gitops_analyze.task.js +47 -34
  58. package/dist/gitops_config.d.ts +34 -79
  59. package/dist/gitops_config.d.ts.map +1 -1
  60. package/dist/gitops_config.js +76 -53
  61. package/dist/gitops_constants.d.ts +6 -4
  62. package/dist/gitops_constants.d.ts.map +1 -1
  63. package/dist/gitops_constants.js +6 -4
  64. package/dist/gitops_plan.task.d.ts +29 -3
  65. package/dist/gitops_plan.task.d.ts.map +1 -1
  66. package/dist/gitops_plan.task.js +83 -61
  67. package/dist/gitops_publish.task.d.ts +62 -2
  68. package/dist/gitops_publish.task.d.ts.map +1 -1
  69. package/dist/gitops_publish.task.js +193 -105
  70. package/dist/gitops_run.task.d.ts +74 -0
  71. package/dist/gitops_run.task.d.ts.map +1 -1
  72. package/dist/gitops_run.task.js +176 -162
  73. package/dist/gitops_sync.task.d.ts +65 -3
  74. package/dist/gitops_sync.task.d.ts.map +1 -1
  75. package/dist/gitops_sync.task.js +163 -37
  76. package/dist/gitops_task_helpers.d.ts +86 -59
  77. package/dist/gitops_task_helpers.d.ts.map +1 -1
  78. package/dist/gitops_task_helpers.js +120 -79
  79. package/dist/gitops_validate.task.d.ts +1 -2
  80. package/dist/gitops_validate.task.d.ts.map +1 -1
  81. package/dist/gitops_validate.task.js +25 -32
  82. package/dist/graph_validation.d.ts +16 -27
  83. package/dist/graph_validation.d.ts.map +1 -1
  84. package/dist/graph_validation.js +24 -65
  85. package/dist/local_repo.d.ts +50 -62
  86. package/dist/local_repo.d.ts.map +1 -1
  87. package/dist/local_repo.js +83 -196
  88. package/dist/log_helpers.d.ts +8 -8
  89. package/dist/log_helpers.d.ts.map +1 -1
  90. package/dist/multi_repo_publisher.d.ts +10 -0
  91. package/dist/multi_repo_publisher.d.ts.map +1 -1
  92. package/dist/multi_repo_publisher.js +125 -61
  93. package/dist/npm_registry.d.ts +23 -19
  94. package/dist/npm_registry.d.ts.map +1 -1
  95. package/dist/npm_registry.js +21 -48
  96. package/dist/operations.d.ts +75 -180
  97. package/dist/operations.d.ts.map +1 -1
  98. package/dist/operations.js +15 -7
  99. package/dist/operations_defaults.d.ts +17 -2
  100. package/dist/operations_defaults.d.ts.map +1 -1
  101. package/dist/operations_defaults.js +111 -128
  102. package/dist/output_helpers.d.ts +36 -0
  103. package/dist/output_helpers.d.ts.map +1 -1
  104. package/dist/output_helpers.js +37 -3
  105. package/dist/preflight_checks.d.ts +20 -27
  106. package/dist/preflight_checks.d.ts.map +1 -1
  107. package/dist/preflight_checks.js +37 -126
  108. package/dist/publish_gate.d.ts.map +1 -1
  109. package/dist/publish_steps.d.ts +2 -4
  110. package/dist/publish_steps.d.ts.map +1 -1
  111. package/dist/publish_steps.js +4 -8
  112. package/dist/publishing_event.d.ts +2 -8
  113. package/dist/publishing_event.d.ts.map +1 -1
  114. package/dist/publishing_event.js +20 -17
  115. package/dist/publishing_event_handler.d.ts +10 -8
  116. package/dist/publishing_event_handler.d.ts.map +1 -1
  117. package/dist/publishing_event_handler.js +19 -17
  118. package/dist/publishing_plan.d.ts +16 -1
  119. package/dist/publishing_plan.d.ts.map +1 -1
  120. package/dist/publishing_plan.js +332 -254
  121. package/dist/publishing_plan_helpers.d.ts.map +1 -1
  122. package/dist/publishing_plan_helpers.js +3 -3
  123. package/dist/publishing_plan_logging.d.ts +1 -1
  124. package/dist/publishing_plan_logging.d.ts.map +1 -1
  125. package/dist/publishing_plan_logging.js +22 -15
  126. package/dist/repo.svelte.d.ts +7 -0
  127. package/dist/repo.svelte.d.ts.map +1 -1
  128. package/dist/repo.svelte.js +3 -0
  129. package/dist/repo_readiness.d.ts +210 -0
  130. package/dist/repo_readiness.d.ts.map +1 -0
  131. package/dist/repo_readiness.js +516 -0
  132. package/dist/repos_status.d.ts +3340 -0
  133. package/dist/repos_status.d.ts.map +1 -0
  134. package/dist/repos_status.js +555 -0
  135. package/dist/repos_status_load.d.ts +65 -0
  136. package/dist/repos_status_load.d.ts.map +1 -0
  137. package/dist/repos_status_load.js +131 -0
  138. package/dist/version_utils.js +1 -1
  139. package/package.json +15 -34
  140. package/src/lib/cargo_toml.ts +3 -3
  141. package/src/lib/changeset_generator.ts +19 -19
  142. package/src/lib/changeset_reader.ts +18 -18
  143. package/src/lib/ci_reconcile.ts +12 -16
  144. package/src/lib/dependency_graph.ts +61 -113
  145. package/src/lib/dependency_updater.ts +36 -30
  146. package/src/lib/fetch_repo_data.ts +71 -34
  147. package/src/lib/fs_fetch_value_cache.ts +15 -11
  148. package/src/lib/git_operations.ts +22 -194
  149. package/src/lib/github.ts +33 -21
  150. package/src/lib/github_helpers.ts +9 -16
  151. package/src/lib/gitops_analyze.task.ts +104 -66
  152. package/src/lib/gitops_config.ts +90 -139
  153. package/src/lib/gitops_constants.ts +6 -4
  154. package/src/lib/gitops_plan.task.ts +128 -76
  155. package/src/lib/gitops_publish.task.ts +280 -144
  156. package/src/lib/gitops_run.task.ts +265 -193
  157. package/src/lib/gitops_sync.task.ts +236 -52
  158. package/src/lib/gitops_task_helpers.ts +170 -114
  159. package/src/lib/gitops_validate.task.ts +44 -54
  160. package/src/lib/graph_validation.ts +28 -89
  161. package/src/lib/local_repo.ts +128 -335
  162. package/src/lib/log_helpers.ts +18 -28
  163. package/src/lib/multi_repo_publisher.ts +192 -110
  164. package/src/lib/npm_registry.ts +42 -67
  165. package/src/lib/operations.ts +99 -160
  166. package/src/lib/operations_defaults.ts +157 -201
  167. package/src/lib/output_helpers.ts +58 -6
  168. package/src/lib/preflight_checks.ts +52 -188
  169. package/src/lib/publish_gate.ts +7 -9
  170. package/src/lib/publish_steps.ts +27 -25
  171. package/src/lib/publishing_event.ts +26 -23
  172. package/src/lib/publishing_event_handler.ts +24 -22
  173. package/src/lib/publishing_plan.ts +474 -310
  174. package/src/lib/publishing_plan_helpers.ts +16 -12
  175. package/src/lib/publishing_plan_logging.ts +53 -44
  176. package/src/lib/repo.svelte.ts +16 -8
  177. package/src/lib/repo_readiness.ts +647 -0
  178. package/src/lib/repos_status.ts +671 -0
  179. package/src/lib/repos_status_load.ts +173 -0
  180. package/src/lib/version_utils.ts +3 -3
  181. package/dist/config_reconcile.d.ts +0 -46
  182. package/dist/config_reconcile.d.ts.map +0 -1
  183. package/dist/config_reconcile.js +0 -60
  184. package/dist/paths.d.ts +0 -7
  185. package/dist/paths.d.ts.map +0 -1
  186. package/dist/paths.js +0 -6
  187. package/dist/repo_ops.d.ts +0 -59
  188. package/dist/repo_ops.d.ts.map +0 -1
  189. package/dist/repo_ops.js +0 -170
  190. package/dist/resolved_gitops_config.d.ts +0 -9
  191. package/dist/resolved_gitops_config.d.ts.map +0 -1
  192. package/dist/resolved_gitops_config.js +0 -12
  193. package/src/lib/config_reconcile.ts +0 -94
  194. package/src/lib/paths.ts +0 -6
  195. package/src/lib/repo_ops.ts +0 -216
  196. package/src/lib/resolved_gitops_config.ts +0 -27
@@ -1,20 +1,20 @@
1
1
  /**
2
2
  * Production implementations of operations interfaces.
3
3
  *
4
- * Provides real git, npm, fs, and build operations for production use.
4
+ * Provides real git, npm, fs, build, and `repos` operations for production use.
5
5
  * For interface definitions and dependency injection pattern, see `operations.ts`.
6
6
  *
7
7
  * @module
8
8
  */
9
- import { spawn_out } from '@fuzdev/fuz_util/process.ts';
9
+ import { spawn_out, spawn_process, spawn_result_to_message } from '@fuzdev/fuz_util/process.ts';
10
10
  import { readFile, writeFile, mkdir, stat } from 'node:fs/promises';
11
- import { git_checkout } from '@fuzdev/fuz_util/git.ts';
11
+ import { stripVTControlCharacters } from 'node:util';
12
12
  import { fs_classify_error } from '@fuzdev/fuz_util/fs.ts';
13
13
  import { EMPTY_OBJECT } from '@fuzdev/fuz_util/object.ts';
14
14
  import { has_changesets, read_changesets, predict_next_version } from './changeset_reader.js';
15
15
  import { wait_for_package } from './npm_registry.js';
16
16
  import { run_preflight_checks } from './preflight_checks.js';
17
- import { git_add, git_commit, git_add_and_commit, git_tag, git_push_tag, git_has_changes, git_list_uncommitted_files, git_has_file_changed, git_stash, git_stash_pop, git_switch_branch, git_current_branch_name_required, git_current_commit_hash_required, git_check_clean_workspace_as_boolean, git_has_remote, } from './git_operations.js';
17
+ import { git_add, git_commit, git_current_commit_hash_required } from './git_operations.js';
18
18
  /** Wrap an async function that returns a value */
19
19
  const wrap_with_value = async (fn) => {
20
20
  try {
@@ -56,121 +56,115 @@ export const default_changeset_operations = {
56
56
  catch (error) {
57
57
  return { ok: false, message: String(error) };
58
58
  }
59
- },
59
+ }
60
60
  };
61
61
  export const default_git_operations = {
62
- // Core git info
63
- current_branch_name: async (options) => {
64
- const { cwd } = options ?? EMPTY_OBJECT;
65
- return wrap_with_value(() => git_current_branch_name_required(cwd ? { cwd } : undefined));
66
- },
67
62
  current_commit_hash: async (options) => {
68
- const { branch, cwd } = options ?? EMPTY_OBJECT;
69
- return wrap_with_value(() => git_current_commit_hash_required(branch, cwd ? { cwd } : undefined));
70
- },
71
- check_clean_workspace: async (options) => {
72
63
  const { cwd } = options ?? EMPTY_OBJECT;
73
- return wrap_with_value(() => git_check_clean_workspace_as_boolean(cwd ? { cwd } : undefined));
74
- },
75
- // Branch operations
76
- checkout: async (options) => {
77
- const { branch, cwd } = options;
78
- return wrap_void(() => git_checkout(branch, cwd ? { cwd } : undefined));
79
- },
80
- pull: async (options) => {
81
- const { origin, branch, cwd } = options ?? EMPTY_OBJECT;
82
- try {
83
- // Omit the branch arg when absent: a trailing `''` makes git pull the remote's
84
- // default branch (origin/HEAD) instead of the current branch's upstream.
85
- const spawned = await spawn_out('git', ['pull', origin || 'origin', ...(branch ? [branch] : [])], cwd ? { cwd } : undefined);
86
- if (spawned.result.ok) {
87
- return { ok: true };
88
- }
89
- else {
90
- return { ok: false, message: spawned.stderr || 'Pull failed' };
91
- }
92
- }
93
- catch (error) {
94
- return { ok: false, message: String(error) };
95
- }
96
- },
97
- switch_branch: async (options) => {
98
- const { branch, pull, cwd } = options;
99
- return wrap_void(() => git_switch_branch(branch, pull, cwd ? { cwd } : undefined));
64
+ return wrap_with_value(() => git_current_commit_hash_required(cwd ? { cwd } : undefined));
100
65
  },
101
- has_remote: async (options) => {
102
- const { remote, cwd } = options ?? EMPTY_OBJECT;
103
- return wrap_with_value(() => git_has_remote(remote, cwd ? { cwd } : undefined));
104
- },
105
- // Staging and committing
106
66
  add: async (options) => {
107
67
  const { files, cwd } = options;
108
68
  return wrap_void(() => git_add(files, cwd ? { cwd } : undefined));
109
69
  },
110
70
  commit: async (options) => {
111
- const { message, cwd } = options;
112
- return wrap_void(() => git_commit(message, cwd ? { cwd } : undefined));
113
- },
114
- add_and_commit: async (options) => {
115
- const { files, message, cwd } = options;
116
- return wrap_void(() => git_add_and_commit(files, message, cwd ? { cwd } : undefined));
117
- },
118
- has_changes: async (options) => {
119
- const { cwd } = options ?? EMPTY_OBJECT;
120
- return wrap_with_value(() => git_has_changes(cwd ? { cwd } : undefined));
121
- },
122
- list_uncommitted_files: async (options) => {
123
- const { cwd } = options ?? EMPTY_OBJECT;
124
- return wrap_with_value(() => git_list_uncommitted_files(cwd ? { cwd } : undefined));
125
- },
126
- // Tagging
127
- tag: async (options) => {
128
- const { tag_name, message, cwd } = options;
129
- return wrap_void(() => git_tag(tag_name, message, cwd ? { cwd } : undefined));
130
- },
131
- push_tag: async (options) => {
132
- const { tag_name, origin, cwd } = options;
133
- return wrap_void(() => git_push_tag(tag_name, origin, cwd ? { cwd } : undefined));
134
- },
135
- // Stashing
136
- stash: async (options) => {
137
- const { message, cwd } = options ?? EMPTY_OBJECT;
138
- return wrap_void(() => git_stash(message, cwd ? { cwd } : undefined));
139
- },
140
- stash_pop: async (options) => {
141
- const { cwd } = options ?? EMPTY_OBJECT;
142
- return wrap_void(() => git_stash_pop(cwd ? { cwd } : undefined));
143
- },
144
- // File change detection
145
- has_file_changed: async (options) => {
146
- const { from_commit, to_commit, file_path, cwd } = options;
147
- return wrap_with_value(() => git_has_file_changed(from_commit, to_commit, file_path, cwd ? { cwd } : undefined));
148
- },
71
+ const { message, files, cwd } = options;
72
+ return wrap_void(() => git_commit(message, files, cwd ? { cwd } : undefined));
73
+ }
149
74
  };
75
+ /** The most lines of a child's stderr a failure carries. */
76
+ export const OUTPUT_TAIL_MAX_LINES = 20;
77
+ /** The most characters of a child's stderr a failure carries. */
78
+ export const OUTPUT_TAIL_MAX_CHARS = 4096;
79
+ /**
80
+ * The end of a process's output, for a failure message: its last lines, then
81
+ * its last characters, with terminal escape sequences stripped and trailing
82
+ * whitespace trimmed.
83
+ *
84
+ * @param text - the output, or as much of its end as was kept
85
+ * @param max_lines - the most lines to keep
86
+ * @param max_chars - the most characters to keep, applied after `max_lines`
87
+ */
88
+ export const output_tail = (text, max_lines = OUTPUT_TAIL_MAX_LINES, max_chars = OUTPUT_TAIL_MAX_CHARS) => {
89
+ const lines = stripVTControlCharacters(text).replace(/\r\n?/g, '\n').trimEnd().split('\n');
90
+ const tail = lines.slice(-max_lines).join('\n');
91
+ if (tail.length <= max_chars)
92
+ return drop_leading_blank_lines(tail);
93
+ // a character cut can split a line, and with it a secret masking would no longer
94
+ // recognize, so the partial first line goes
95
+ const cut = tail.slice(-max_chars);
96
+ if (tail[tail.length - max_chars - 1] === '\n')
97
+ return drop_leading_blank_lines(cut);
98
+ const nl = cut.indexOf('\n');
99
+ return nl === -1 ? '' : drop_leading_blank_lines(cut.slice(nl + 1));
100
+ };
101
+ // not `trimStart`, which would strip the first line's indentation
102
+ const drop_leading_blank_lines = (text) => text.replace(/^\n+/, '');
150
103
  export const default_process_operations = {
151
- spawn: async (options) => {
152
- const { cmd, args, cwd } = options;
104
+ run_interactive: async (options) => {
105
+ const { cmd, args, cwd, stdout = 'stdout' } = options;
153
106
  try {
154
- const spawned = await spawn_out(cmd, args, cwd ? { cwd } : undefined);
155
- if (spawned.result.ok) {
156
- return {
157
- ok: true,
158
- stdout: spawned.stdout || undefined,
159
- stderr: spawned.stderr || undefined,
160
- };
161
- }
162
- else {
163
- return {
164
- ok: false,
165
- message: 'Command failed',
166
- stderr: spawned.stderr || undefined,
167
- };
168
- }
107
+ // stdin and stdout go straight to the child (fd 2 for stdout routed to our stderr), so
108
+ // they stay a TTY when ours are — npm prompts for a one-time password only on a TTY.
109
+ // Only stderr is piped, to keep its end for the failure message while echoing it live.
110
+ const { child, closed } = spawn_process(cmd, args, {
111
+ cwd,
112
+ stdio: ['inherit', stdout === 'stderr' ? 2 : 'inherit', 'pipe']
113
+ });
114
+ // a rolling window over stderr's end, bounded however much the child writes
115
+ let kept = '';
116
+ child.stderr?.setEncoding('utf8');
117
+ child.stderr?.on('data', (chunk) => {
118
+ process.stderr.write(chunk);
119
+ kept += chunk;
120
+ if (kept.length > OUTPUT_TAIL_MAX_CHARS * 4)
121
+ kept = kept.slice(-OUTPUT_TAIL_MAX_CHARS * 2);
122
+ });
123
+ const result = await closed;
124
+ if (result.ok)
125
+ return { ok: true };
126
+ const stderr_tail = output_tail(kept);
127
+ return {
128
+ ok: false,
129
+ message: `\`${[cmd, ...args].join(' ')}\` failed (${spawn_result_to_message(result)})`,
130
+ stderr_tail: stderr_tail || undefined
131
+ };
169
132
  }
170
133
  catch (error) {
171
134
  return { ok: false, message: String(error) };
172
135
  }
173
- },
136
+ }
137
+ };
138
+ export const default_repos_operations = {
139
+ status: async (options) => {
140
+ const { keys, registry, fetch } = options;
141
+ const args = [...(registry === undefined ? [] : ['--registry', registry]), 'status'];
142
+ if (fetch)
143
+ args.push('--fetch');
144
+ // `--` so a key can never read as a flag
145
+ args.push('--json', '--', ...keys);
146
+ const spawned = await spawn_out('repos', args);
147
+ const { result } = spawned;
148
+ if (result.kind === 'error') {
149
+ const not_found = result.error.code === 'ENOENT';
150
+ return {
151
+ ok: false,
152
+ kind: not_found ? 'not_found' : 'failed',
153
+ message: result.error.message
154
+ };
155
+ }
156
+ if (result.kind === 'signaled') {
157
+ return { ok: false, kind: 'failed', message: `repos was killed by ${result.signal}` };
158
+ }
159
+ return {
160
+ ok: true,
161
+ output: {
162
+ stdout: spawned.stdout ?? '',
163
+ stderr: spawned.stderr ?? '',
164
+ exit_code: result.code
165
+ }
166
+ };
167
+ }
174
168
  };
175
169
  export const default_npm_operations = {
176
170
  wait_for_package: async (options) => {
@@ -180,7 +174,7 @@ export const default_npm_operations = {
180
174
  return { ok: true };
181
175
  }
182
176
  catch (error) {
183
- return { ok: false, message: String(error), timeout: true };
177
+ return { ok: false, message: String(error) };
184
178
  }
185
179
  },
186
180
  check_auth: async () => {
@@ -200,36 +194,25 @@ export const default_npm_operations = {
200
194
  },
201
195
  check_registry: async () => {
202
196
  try {
203
- const result = await spawn_out('npm', ['ping']);
204
- if (result.stdout) {
205
- return { ok: true };
206
- }
207
- return { ok: false, message: 'Failed to ping npm registry' };
208
- }
209
- catch (error) {
210
- return { ok: false, message: String(error) };
211
- }
212
- },
213
- install: async (options) => {
214
- const { cwd } = options ?? EMPTY_OBJECT;
215
- try {
216
- const spawned = await spawn_out('npm', ['install'], cwd ? { cwd } : undefined);
217
- if (spawned.result.ok) {
197
+ // the exit status alone: `npm ping` reports on stderr, writing nothing to stdout
198
+ const { result } = await spawn_out('npm', ['ping']);
199
+ if (result.ok) {
218
200
  return { ok: true };
219
201
  }
220
- else {
221
- return { ok: false, message: 'Install failed', stderr: spawned.stderr || undefined };
222
- }
202
+ return {
203
+ ok: false,
204
+ message: `Failed to ping npm registry (${spawn_result_to_message(result)})`
205
+ };
223
206
  }
224
207
  catch (error) {
225
208
  return { ok: false, message: String(error) };
226
209
  }
227
- },
210
+ }
228
211
  };
229
212
  export const default_preflight_operations = {
230
213
  run_preflight_checks: async (options) => {
231
214
  return run_preflight_checks(options);
232
- },
215
+ }
233
216
  };
234
217
  export const default_fs_operations = {
235
218
  readFile: async (options) => {
@@ -270,13 +253,12 @@ export const default_fs_operations = {
270
253
  catch {
271
254
  return false;
272
255
  }
273
- },
256
+ }
274
257
  };
275
258
  export const default_build_operations = {
276
259
  build_package: async (options) => {
277
- const { repo, log } = options;
260
+ const { repo } = options;
278
261
  try {
279
- log?.info(` Building ${repo.library.name}...`);
280
262
  const spawned = await spawn_out('gro', ['build'], { cwd: repo.repo_dir });
281
263
  if (spawned.result.ok) {
282
264
  return { ok: true };
@@ -285,14 +267,14 @@ export const default_build_operations = {
285
267
  return {
286
268
  ok: false,
287
269
  message: 'Build failed',
288
- output: spawned.stderr || spawned.stdout || 'Build failed',
270
+ output: spawned.stderr || spawned.stdout || 'Build failed'
289
271
  };
290
272
  }
291
273
  }
292
274
  catch (error) {
293
275
  return { ok: false, message: String(error) };
294
276
  }
295
- },
277
+ }
296
278
  };
297
279
  /**
298
280
  * Combined default operations for all gitops functionality.
@@ -305,4 +287,5 @@ export const default_gitops_operations = {
305
287
  preflight: default_preflight_operations,
306
288
  fs: default_fs_operations,
307
289
  build: default_build_operations,
290
+ repos: default_repos_operations
308
291
  };
@@ -1,9 +1,22 @@
1
1
  import type { Logger } from '@fuzdev/fuz_util/log.ts';
2
2
  export type OutputFormat = 'stdout' | 'json' | 'markdown';
3
+ /**
4
+ * Writes one machine-readable document, or one line of a stream, to stdout,
5
+ * ending it with a newline.
6
+ */
7
+ export type WriteStdout = (content: string) => void;
3
8
  export interface OutputOptions {
4
9
  format: OutputFormat;
5
10
  outfile?: string;
6
11
  log?: Logger;
12
+ /**
13
+ * Where a `json` or `markdown` document goes without `outfile`; the one
14
+ * `route_human_output` returns, so the document reaches stdout while the
15
+ * logger's lines go to stderr.
16
+ *
17
+ * @default `console.log`
18
+ */
19
+ write_stdout?: WriteStdout;
7
20
  }
8
21
  export interface OutputFormatters<T> {
9
22
  json: (data: T) => string;
@@ -13,6 +26,29 @@ export interface OutputFormatters<T> {
13
26
  */
14
27
  stdout: (data: T, log: Logger) => void;
15
28
  }
29
+ /**
30
+ * Whether a task's stdout carries a machine-readable document: a `json` or
31
+ * `markdown` report not sent to `--outfile`.
32
+ *
33
+ * @param format - the task's `--format`
34
+ * @param outfile - the task's `--outfile`, if any
35
+ */
36
+ export declare const output_is_machine: (format: string, outfile: string | undefined) => boolean;
37
+ /**
38
+ * Keeps a task's stdout for its machine-readable document or stream when
39
+ * `machine` is set: the logger's `info` and `debug` lines, which it writes with
40
+ * `console.log`, go where its errors go — stderr — and child loggers inherit
41
+ * that. Gro hands a task the logger its own lines go through, so gro's lines
42
+ * after the task runs (`✓`, the timings) follow to stderr; the two it prints
43
+ * before the task runs (`invoking`, `→ <task>`) stay on stdout, out of the
44
+ * task's reach.
45
+ *
46
+ * @param log - the task's logger
47
+ * @param machine - whether stdout carries a machine-readable document or stream
48
+ * @returns writes to the stdout the logger wrote to before, for the document
49
+ * @mutates log - overrides its `console` when `machine` is set
50
+ */
51
+ export declare const route_human_output: (log: Logger, machine: boolean) => WriteStdout;
16
52
  /**
17
53
  * Formats data and outputs to file or stdout based on options.
18
54
  *
@@ -1 +1 @@
1
- {"version":3,"file":"output_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/output_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAGpD,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;AAE1D,MAAM,WAAW,aAAa;IAC7B,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC;IAClC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;IAC1B,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,KAAK,CAAC,MAAM,CAAC,CAAC;IACrC;;OAEG;IACH,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAAU,CAAC,EACxC,MAAM,CAAC,EACP,YAAY,gBAAgB,CAAC,CAAC,CAAC,EAC/B,SAAS,aAAa,KACpB,OAAO,CAAC,IAAI,CA8Bd,CAAC"}
1
+ {"version":3,"file":"output_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/output_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAGtD,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;AAE1D;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAEpD,MAAM,WAAW,aAAa;IAC7B,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC;IAClC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;IAC1B,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,KAAK,CAAC,MAAM,CAAC,CAAC;IACrC;;OAEG;IACH,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,MAAM,EAAE,SAAS,MAAM,GAAG,SAAS,KAAG,OACvB,CAAC;AAE1D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,kBAAkB,GAAI,KAAK,MAAM,EAAE,SAAS,OAAO,KAAG,WAUlE,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAAU,CAAC,EACxC,MAAM,CAAC,EACP,YAAY,gBAAgB,CAAC,CAAC,CAAC,EAC/B,SAAS,aAAa,KACpB,OAAO,CAAC,IAAI,CA6Bd,CAAC"}
@@ -1,4 +1,37 @@
1
1
  import { writeFile } from 'node:fs/promises';
2
+ /**
3
+ * Whether a task's stdout carries a machine-readable document: a `json` or
4
+ * `markdown` report not sent to `--outfile`.
5
+ *
6
+ * @param format - the task's `--format`
7
+ * @param outfile - the task's `--outfile`, if any
8
+ */
9
+ export const output_is_machine = (format, outfile) => (format === 'json' || format === 'markdown') && !outfile;
10
+ /**
11
+ * Keeps a task's stdout for its machine-readable document or stream when
12
+ * `machine` is set: the logger's `info` and `debug` lines, which it writes with
13
+ * `console.log`, go where its errors go — stderr — and child loggers inherit
14
+ * that. Gro hands a task the logger its own lines go through, so gro's lines
15
+ * after the task runs (`✓`, the timings) follow to stderr; the two it prints
16
+ * before the task runs (`invoking`, `→ <task>`) stay on stdout, out of the
17
+ * task's reach.
18
+ *
19
+ * @param log - the task's logger
20
+ * @param machine - whether stdout carries a machine-readable document or stream
21
+ * @returns writes to the stdout the logger wrote to before, for the document
22
+ * @mutates log - overrides its `console` when `machine` is set
23
+ */
24
+ export const route_human_output = (log, machine) => {
25
+ const stdout = log.console;
26
+ if (machine) {
27
+ log.console = {
28
+ log: (...args) => stdout.error(...args),
29
+ warn: (...args) => stdout.warn(...args),
30
+ error: (...args) => stdout.error(...args)
31
+ };
32
+ }
33
+ return (content) => stdout.log(content);
34
+ };
2
35
  /**
3
36
  * Formats data and outputs to file or stdout based on options.
4
37
  *
@@ -10,7 +43,7 @@ import { writeFile } from 'node:fs/promises';
10
43
  * @throws {Error} if stdout format used with `outfile`, or if logger missing for stdout
11
44
  */
12
45
  export const format_and_output = async (data, formatters, options) => {
13
- const { format, outfile, log } = options;
46
+ const { format, outfile, log, write_stdout = write_console_log } = options;
14
47
  // Handle stdout format (special case - uses logger directly)
15
48
  if (format === 'stdout') {
16
49
  if (outfile) {
@@ -34,7 +67,8 @@ export const format_and_output = async (data, formatters, options) => {
34
67
  // line with `[taskname]`, corrupting JSON and cluttering markdown — keep the
35
68
  // machine-readable formats unprefixed and pipeable (use `--outfile` for output
36
69
  // fully free of Gro's own preamble).
37
- // eslint-disable-next-line no-console
38
- console.log(content);
70
+ write_stdout(content);
39
71
  }
40
72
  };
73
+ // eslint-disable-next-line no-console
74
+ const write_console_log = (content) => console.log(content);
@@ -1,47 +1,40 @@
1
1
  import type { Logger } from '@fuzdev/fuz_util/log.ts';
2
2
  import type { LocalRepo } from './local_repo.js';
3
- import type { GitOperations, NpmOperations, BuildOperations, ChangesetOperations } from './operations.js';
4
- export interface PreflightOptions {
5
- skip_changesets?: boolean;
6
- skip_build_validation?: boolean;
7
- required_branch?: string;
8
- check_remote?: boolean;
9
- estimate_time?: boolean;
10
- log?: Logger;
11
- }
3
+ import type { VersionChange } from './publishing_plan.js';
4
+ import type { NpmOperations, BuildOperations } from './operations.js';
12
5
  export interface PreflightResult {
13
6
  ok: boolean;
14
7
  warnings: Array<string>;
15
8
  errors: Array<string>;
16
- repos_with_changesets: Set<string>;
17
- repos_without_changesets: Set<string>;
18
- estimated_duration?: number;
19
- npm_username?: string;
20
9
  }
21
10
  export interface RunPreflightChecksOptions {
22
11
  repos: Array<LocalRepo>;
23
- preflight_options?: PreflightOptions;
24
- git_ops?: GitOperations;
12
+ /**
13
+ * The plan's version changes — every package the run publishes, explicit,
14
+ * escalated, and auto-generated alike. Preflight builds exactly these.
15
+ */
16
+ version_changes: Array<VersionChange>;
17
+ log?: Logger;
25
18
  npm_ops?: NpmOperations;
26
19
  build_ops?: BuildOperations;
27
- changeset_ops?: ChangesetOperations;
28
20
  }
29
21
  /**
30
- * Validates all requirements before publishing can proceed.
22
+ * Validates the publish-time requirements beyond repo git state:
23
+ * - every package the plan publishes builds (fail-fast to prevent broken state)
24
+ * - npm authentication
25
+ * - npm registry connectivity
31
26
  *
32
- * Performs comprehensive pre-flight validation:
33
- * - Clean workspaces (100% clean required - no uncommitted changes)
34
- * - Correct branch (usually main)
35
- * - Changesets present (unless `skip_changesets`=true)
36
- * - Builds successful (fail-fast to prevent broken state)
37
- * - Git remote reachability
38
- * - NPM authentication with username
39
- * - NPM registry connectivity
27
+ * What publishes is the plan's to decide, so preflight reads no changesets: it
28
+ * builds each package in `version_changes`. Git state — each repo on its
29
+ * registry branch, clean, idle, in sync with origin or ahead of it, and no live
30
+ * session in its checkout — is the readiness gate's, which `gitops_publish
31
+ * --wetrun` runs before the plan's confirmation prompt (`repo_readiness.ts`), so
32
+ * preflight reads no git.
40
33
  *
41
34
  * Build validation runs BEFORE any publishing to prevent the scenario where
42
35
  * version is bumped but build fails, leaving repo in broken state.
43
36
  *
44
- * @returns result with `ok`=false if any errors, plus warnings and detailed status
37
+ * @returns result with `ok`=false if any errors, plus warnings
45
38
  */
46
- export declare const run_preflight_checks: ({ repos, preflight_options, git_ops, npm_ops, build_ops, changeset_ops, }: RunPreflightChecksOptions) => Promise<PreflightResult>;
39
+ export declare const run_preflight_checks: ({ repos, version_changes, log, npm_ops, build_ops }: RunPreflightChecksOptions) => Promise<PreflightResult>;
47
40
  //# sourceMappingURL=preflight_checks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"preflight_checks.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/preflight_checks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAKpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EACX,aAAa,EACb,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,MAAM,iBAAiB,CAAC;AAQzB,MAAM,WAAW,gBAAgB;IAChC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,eAAe;IAC/B,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACxB,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtB,qBAAqB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACnC,wBAAwB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACtC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,yBAAyB;IACzC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACxB,iBAAiB,CAAC,EAAE,gBAAgB,CAAC;IACrC,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,aAAa,CAAC,EAAE,mBAAmB,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,oBAAoB,GAAU,2EAOxC,yBAAyB,KAAG,OAAO,CAAC,eAAe,CAwLrD,CAAC"}
1
+ {"version":3,"file":"preflight_checks.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/preflight_checks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAGtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGtE,MAAM,WAAW,eAAe;IAC/B,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACxB,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACtB;AAED,MAAM,WAAW,yBAAyB;IACzC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACxB;;;OAGG;IACH,eAAe,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,SAAS,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,oBAAoB,GAAU,qDAMxC,yBAAyB,KAAG,OAAO,CAAC,eAAe,CAkFrD,CAAC"}