@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,103 +1,144 @@
1
1
  /**
2
2
  * Shared initialization logic for all gitops tasks.
3
3
  *
4
- * Provides `get_gitops_ready()` which orchestrates:
5
- * - Config loading and normalization
6
- * - Repo resolution (local path discovery)
7
- * - Branch switching and syncing
8
- * - Dependency installation
4
+ * `resolve_gitops_repos()` loads the config's registry keys, runs
5
+ * `repos status <keysâ€Ļ> --json`, and resolves each key to its checkout.
6
+ * `get_gitops_ready()` then loads each repo's library as its working tree
7
+ * sits. `gate_publish_readiness()` is the read-only gate a real publish runs
8
+ * before its prompt, and `log_readiness_block()` the diagnostics' report of
9
+ * repos not at rest (both over `repo_readiness.ts`).
9
10
  *
10
11
  * Used by: `gitops_sync.task.ts`, `gitops_analyze.task.ts`, `gitops_plan.task.ts`,
11
- * `gitops_publish.task.ts`, and `gitops_validate.task.ts`.
12
+ * `gitops_publish.task.ts`, `gitops_validate.task.ts`, and `gitops_run.task.ts`.
12
13
  *
13
- * Accepts `git_ops` and `npm_ops` parameters to support testing via operations pattern
14
- * (see `operations.ts` for dependency injection details).
14
+ * Accepts `repos_ops` to support testing via the operations pattern (see
15
+ * `operations.ts` for dependency injection details).
15
16
  *
16
17
  * @module
17
18
  */
18
19
  import { TaskError } from '@fuzdev/gro';
19
20
  import { styleText as st } from 'node:util';
20
- import { resolve, dirname } from 'node:path';
21
- import { print_path } from '@fuzdev/gro/paths.ts';
21
+ import { resolve } from 'node:path';
22
+ import { to_error_message } from '@fuzdev/fuz_util/error.ts';
22
23
  import { load_gitops_config } from './gitops_config.js';
23
- import { local_repos_load, local_repos_ensure } from './local_repo.js';
24
- import { resolve_gitops_config } from './resolved_gitops_config.js';
25
- import { DEFAULT_REPOS_DIR } from './paths.js';
24
+ import { local_repos_load, local_repos_resolve, repo_is_npm } from './local_repo.js';
25
+ import { load_repos_status, to_repos_command } from './repos_status_load.js';
26
+ import { check_publish_readiness, format_readiness_ahead, format_readiness_block, repos_not_at_rest } from './repo_readiness.js';
27
+ import { default_repos_operations } from './operations_defaults.js';
26
28
  /**
27
- * Central initialization function for all gitops tasks.
29
+ * Resolves the gitops config's repos through `repos status`: loads the
30
+ * config's registry keys, reports on them, and resolves each to its checkout,
31
+ * in config order. Reads nothing but git state and writes nothing.
28
32
  *
29
- * Initialization sequence:
30
- * 1. Loads and normalizes config from `gitops.config.ts`
31
- * 2. Resolves local repo paths (creates missing with `--download`)
32
- * 3. If `sync`, switches branches and pulls latest changes (in parallel by default)
33
- * 4. If `sync`, auto-installs deps if `package.json` changed during pull
33
+ * @returns the config, the `repos status` report, and each repo's path and entry
34
+ * @throws {TaskError} if the config is missing, invalid, or lists no repos, `repos status` fails, or any configured repo is unknown, a reference, missing, not a repo, unprobed, or private under a public `host`
35
+ */
36
+ export const resolve_gitops_repos = async (options) => {
37
+ const { config, registry, host, log, repos_ops = default_repos_operations } = options;
38
+ const config_path = resolve(config);
39
+ const gitops_config = await import_gitops_config(config_path);
40
+ const keys = gitops_config.repos;
41
+ if (keys.length === 0) {
42
+ throw new TaskError(`No repos are configured in ${config_path}`);
43
+ }
44
+ log?.info(`reading the state of ${keys.length} repos from \`repos status\``);
45
+ log?.debug('repos status targets', keys);
46
+ const loaded = await load_repos_status({ keys, registry, repos_ops });
47
+ if (!loaded.ok) {
48
+ // an unknown key is the config's to fix, so name the config
49
+ throw new TaskError(loaded.error?.kind === 'unknown_entry' ? `${config_path}: ${loaded.message}` : loaded.message);
50
+ }
51
+ const { report } = loaded;
52
+ const resolved = local_repos_resolve({ keys, report, host, registry });
53
+ if (!resolved.ok) {
54
+ throw new TaskError(`${config_path}: ${resolved.message}`);
55
+ }
56
+ return { config_path, gitops_config, report, local_repo_paths: resolved.value };
57
+ };
58
+ /**
59
+ * Central initialization function for the gitops tasks that load libraries:
60
+ * resolves the config's repos through `repos status` (`resolve_gitops_repos`),
61
+ * then loads each repo's library as its working tree sits (`local_repos_load`).
62
+ * Moves no ref; gro caches each library at `.gro/library.json` in its repo, at
63
+ * a clean commit.
34
64
  *
35
- * With `sync: false` (the default for read-only diagnostics), steps 3-4 are
36
- * skipped and repos are loaded exactly as checked out — no branch switch, pull,
37
- * install, or clean-workspace check.
65
+ * @returns the loaded repos, in config order
66
+ * @throws {TaskError} if resolving the repos or loading them fails
67
+ */
68
+ export const get_gitops_ready = async (options) => {
69
+ const { local_repo_paths } = await resolve_gitops_repos(options);
70
+ const local_repos = await local_repos_load({ local_repo_paths, log: options.log });
71
+ return { local_repos };
72
+ };
73
+ /**
74
+ * The readiness gate `gitops_publish --wetrun` runs before its confirmation
75
+ * prompt: fetches every npm repo from origin (`repos status <keysâ€Ļ> --fetch
76
+ * --json`, which writes remote-tracking refs and nothing else) and refuses
77
+ * unless each is ready (`check_publish_readiness`) — on its registry branch,
78
+ * clean, idle, in sync with origin or ahead of it, fetched without error, no
79
+ * other live session in its checkout, and nothing left to a person. Each
80
+ * ready repo ahead of origin is logged, saying whether its release push
81
+ * carries those commits or they stay unpushed.
38
82
  *
39
- * Priority for path resolution:
40
- * - `dir` argument (explicit override)
41
- * - Config `repos_dir` setting
42
- * - `DEFAULT_REPOS_DIR` constant
83
+ * Every npm repo, not just those the plan publishes or rewrites: the plan
84
+ * reads each one's working tree (changesets, versions, dependency ranges), so
85
+ * a repo off its branch or behind origin can hide a changeset and leave the
86
+ * plan wrong about what to publish. Changes nothing.
43
87
  *
44
- * @param options.git_ops - for testing (defaults to real git operations)
45
- * @param options.npm_ops - for testing (defaults to real npm operations)
46
- * @param options.parallel - whether to load repos in parallel (default: true)
47
- * @param options.concurrency - max concurrent repo loads (default: 5)
48
- * @param options.sync - sync working trees before loading (default: true)
49
- * @param options.allow_dirty - when syncing, tolerate uncommitted changes (default: false)
50
- * @returns initialized config and fully loaded repos ready for operations
51
- * @throws {TaskError} if config loading or repo resolution fails
88
+ * @param options - the loaded repos, the `--registry` if any, the package names the plan publishes, a logger, and the `repos` runner
89
+ * @throws {TaskError} if `repos status` fails or any npm repo isn't ready, naming each problem and its fix
52
90
  */
53
- export const get_gitops_ready = async (options) => {
54
- const { config, dir, download, log, git_ops, npm_ops, parallel, concurrency, sync, allow_dirty } = options;
55
- const config_path = resolve(config);
56
- const gitops_config = await import_gitops_config(config_path);
57
- // Priority: explicit dir arg → config repos_dir → default (two dirs up from config)
58
- const repos_dir = resolve_gitops_paths({
59
- config,
60
- dir,
61
- config_repos_dir: gitops_config.repos_dir,
62
- }).repos_dir;
63
- log?.info(`resolving gitops configs on the filesystem in ${repos_dir}`, gitops_config.repos.map((r) => r.repo_url));
64
- const resolved_config = resolve_gitops_config(gitops_config, repos_dir);
65
- const local_repo_paths = await local_repos_ensure({
66
- resolved_config,
67
- repos_dir,
68
- gitops_config,
69
- download,
70
- log,
71
- npm_ops,
72
- });
73
- const local_repos = await local_repos_load({
74
- local_repo_paths,
75
- log,
76
- git_ops,
77
- npm_ops,
78
- parallel,
79
- concurrency,
80
- sync,
81
- allow_dirty,
82
- });
83
- return { config_path, repos_dir, gitops_config, local_repos };
91
+ export const gate_publish_readiness = async (options) => {
92
+ const { local_repos, registry, publishing, log, repos_ops = default_repos_operations } = options;
93
+ const npm_repos = local_repos.filter(repo_is_npm);
94
+ const keys = npm_repos.map((r) => r.entry.key);
95
+ if (keys.length === 0)
96
+ return;
97
+ log?.info(`fetching ${keys.length} npm repos to check they're ready to publish`);
98
+ const loaded = await load_repos_status({ keys, registry, fetch: true, repos_ops });
99
+ if (!loaded.ok) {
100
+ throw new TaskError(`the readiness check failed: ${loaded.message}`);
101
+ }
102
+ const repos_command = to_repos_command(registry);
103
+ const checked = check_publish_readiness({ report: loaded.report, keys, repos_command });
104
+ if (!checked.ok) {
105
+ throw new TaskError(checked.message);
106
+ }
107
+ log?.info(st('green', `all ${keys.length} npm repos are ready to publish`));
108
+ const name_by_key = new Map(npm_repos.map((r) => [r.entry.key, r.library.name]));
109
+ for (const ahead of checked.ahead) {
110
+ const name = name_by_key.get(ahead.key);
111
+ const publishes = name !== undefined && publishing?.has(name) === true;
112
+ log?.info(st('yellow', format_readiness_ahead(ahead, publishes, { repos_command })));
113
+ }
84
114
  };
85
- export const resolve_gitops_paths = (options) => {
86
- const { config, dir, config_repos_dir } = options;
87
- const config_path = resolve(config);
88
- const config_dir = dirname(config_path);
89
- // Priority: explicit dir arg → config repos_dir → default (parent of config dir)
90
- const repos_dir = dir !== undefined
91
- ? resolve(dir)
92
- : config_repos_dir !== undefined
93
- ? resolve(config_dir, config_repos_dir)
94
- : resolve(config_dir, DEFAULT_REPOS_DIR);
95
- return { config_path, repos_dir };
115
+ /**
116
+ * Logs the diagnostics' readiness block as warnings (stderr, so a `--format
117
+ * json` or `markdown` document on stdout stays clean): each npm repo not at
118
+ * rest, and how. Logs nothing when every one is at rest. Reads the entries
119
+ * `repos status` already reported, from local refs.
120
+ *
121
+ * @param local_repos - the loaded repos; the npm ones are reported
122
+ * @param log - where the warnings go
123
+ * @param now - the current time in unix seconds (defaults to the clock)
124
+ */
125
+ export const log_readiness_block = (local_repos, log, now = Math.floor(Date.now() / 1000)) => {
126
+ const not_ready = repos_not_at_rest(local_repos.filter(repo_is_npm).map((r) => r.entry));
127
+ for (const line of format_readiness_block(not_ready, now)) {
128
+ log.warn(st('yellow', line));
129
+ }
96
130
  };
97
- export const import_gitops_config = async (config_path) => {
98
- const gitops_config = await load_gitops_config(config_path);
131
+ const import_gitops_config = async (config_path) => {
132
+ let gitops_config;
133
+ try {
134
+ gitops_config = await load_gitops_config(config_path);
135
+ }
136
+ catch (err) {
137
+ // an invalid config is the user's to fix, not an unexpected task failure
138
+ throw new TaskError(to_error_message(err));
139
+ }
99
140
  if (!gitops_config) {
100
- throw new TaskError(st('red', `No gitops config found at ${print_path(config_path)}`));
141
+ throw new TaskError(st('red', `No gitops config found at ${config_path}`));
101
142
  }
102
143
  return gitops_config;
103
144
  };
@@ -3,9 +3,8 @@ import { z } from 'zod';
3
3
  /** @nodocs */
4
4
  export declare const Args: z.ZodObject<{
5
5
  config: z.ZodDefault<z.ZodString>;
6
- dir: z.ZodOptional<z.ZodString>;
6
+ registry: z.ZodOptional<z.ZodString>;
7
7
  verbose: z.ZodDefault<z.ZodBoolean>;
8
- sync: z.ZodDefault<z.ZodBoolean>;
9
8
  }, z.core.$strict>;
10
9
  export type Args = z.infer<typeof Args>;
11
10
  /** @nodocs */
@@ -1 +1 @@
1
- {"version":3,"file":"gitops_validate.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_validate.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;;;;;kBAiBf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC,cAAc;AACd,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CA8Q3B,CAAC"}
1
+ {"version":3,"file":"gitops_validate.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_validate.task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAaxB,cAAc;AACd,eAAO,MAAM,IAAI;;;;kBAaf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC,cAAc;AACd,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CA0Q3B,CAAC"}
@@ -1,8 +1,9 @@
1
1
  import { z } from 'zod';
2
2
  import { styleText as st } from 'node:util';
3
- import { get_gitops_ready } from './gitops_task_helpers.js';
3
+ import { get_gitops_ready, log_readiness_block } from './gitops_task_helpers.js';
4
4
  import { analyze_repos } from './graph_validation.js';
5
- import { generate_publishing_plan, log_publishing_plan, } from './publishing_plan.js';
5
+ import { generate_publishing_plan } from './publishing_plan.js';
6
+ import { log_publishing_plan } from './publishing_plan_logging.js';
6
7
  import { execute_publishing_plan } from './multi_repo_publisher.js';
7
8
  import { log_dependency_analysis } from './log_helpers.js';
8
9
  import { GITOPS_CONFIG_PATH_DEFAULT } from './gitops_constants.js';
@@ -13,32 +14,29 @@ export const Args = z.strictObject({
13
14
  .string()
14
15
  .meta({ description: 'path to the gitops config file, absolute or relative to the cwd' })
15
16
  .default(GITOPS_CONFIG_PATH_DEFAULT),
16
- dir: z
17
+ registry: z
17
18
  .string()
18
- .meta({ description: 'path containing the repos, defaults to the parent of the config dir' })
19
- .optional(),
20
- verbose: z.boolean().meta({ description: 'show additional details' }).default(false),
21
- sync: z
22
- .boolean()
23
19
  .meta({
24
- description: 'sync repos (switch branch, pull, install) before validating instead of reading the working tree as-is',
20
+ description: 'path to the repos.toml registry, when `repos` would not find it walking up from the cwd'
25
21
  })
26
- .default(false),
22
+ .optional(),
23
+ verbose: z.boolean().meta({ description: 'show additional details' }).default(false)
27
24
  });
28
25
  /** @nodocs */
29
26
  export const task = {
30
27
  Args,
31
28
  summary: 'validate gitops configuration by running all read-only commands and checking for issues',
32
29
  run: async ({ args, log }) => {
33
- const { config, dir, verbose, sync } = args;
30
+ const { config, registry, verbose } = args;
34
31
  log.info(st('cyan', 'Running Gitops Validation Suite'));
35
32
  log.info(st('dim', 'This runs all read-only commands and checks for consistency.'));
36
33
  const results = [];
37
34
  const start_time = Date.now();
38
- // Load repos once (shared by all commands); read the working tree as-is unless `--sync`
35
+ // Load repos once (shared by all commands), as they sit, and say which aren't at rest
39
36
  log.info(st('dim', 'Loading repositories...'));
40
- const { local_repos } = await get_gitops_ready({ config, dir, download: false, sync, log });
37
+ const { local_repos } = await get_gitops_ready({ config, registry, log });
41
38
  log.info(st('dim', ` Found ${local_repos.length} local repos`));
39
+ log_readiness_block(local_repos, log);
42
40
  // 1. Run gitops_analyze
43
41
  log.info(st('yellow', 'Running gitops_analyze...'));
44
42
  const analyze_start = Date.now();
@@ -65,7 +63,7 @@ export const task = {
65
63
  duration: analyze_duration,
66
64
  warning_details,
67
65
  info_details,
68
- analysis,
66
+ analysis
69
67
  });
70
68
  log.info(st('green', ` ✓ gitops_analyze completed in ${analyze_duration}ms`));
71
69
  // Print detailed analysis
@@ -81,7 +79,7 @@ export const task = {
81
79
  success: false,
82
80
  warnings: 0,
83
81
  errors: 1,
84
- duration: analyze_duration,
82
+ duration: analyze_duration
85
83
  });
86
84
  log.error(st('red', ` ✗ gitops_analyze failed: ${error}`));
87
85
  }
@@ -100,7 +98,7 @@ export const task = {
100
98
  success: errors === 0,
101
99
  warnings,
102
100
  errors,
103
- duration: plan_duration,
101
+ duration: plan_duration
104
102
  });
105
103
  log.info(st('green', ` ✓ gitops_plan completed in ${plan_duration}ms`));
106
104
  if (verbose) {
@@ -120,7 +118,7 @@ export const task = {
120
118
  success: false,
121
119
  warnings: 0,
122
120
  errors: 1,
123
- duration: plan_duration,
121
+ duration: plan_duration
124
122
  });
125
123
  log.error(st('red', ` ✗ gitops_plan failed: ${error}`));
126
124
  }
@@ -130,7 +128,7 @@ export const task = {
130
128
  try {
131
129
  const options = {
132
130
  wetrun: false,
133
- log: undefined, // Silent for validation
131
+ log: undefined // Silent for validation
134
132
  };
135
133
  // Reuse the plan from step 2 (regenerate only if that step failed to produce one).
136
134
  const result = await execute_publishing_plan(local_repos, publishing_plan ?? (await generate_publishing_plan(local_repos, { verbose })), options);
@@ -143,7 +141,7 @@ export const task = {
143
141
  success: result.ok,
144
142
  warnings: 0,
145
143
  errors,
146
- duration: dry_duration,
144
+ duration: dry_duration
147
145
  });
148
146
  log.info(st('green', ` ✓ gitops_publish (dry run) completed in ${dry_duration}ms`));
149
147
  if (errors > 0) {
@@ -157,24 +155,19 @@ export const task = {
157
155
  success: false,
158
156
  warnings: 0,
159
157
  errors: 1,
160
- duration: dry_duration,
158
+ duration: dry_duration
161
159
  });
162
160
  log.error(st('red', ` ✗ gitops_publish (dry run) failed: ${error}`));
163
161
  }
164
- // 4. Reconcile each repo's declared `ci` against actual workflow files on disk.
162
+ // 4. Reconcile each repo's registry-declared `ci` against actual workflow files on disk.
165
163
  log.info(st('yellow', 'Running ci_reconcile...'));
166
164
  const ci_start = Date.now();
167
165
  try {
168
166
  const ci_drift = reconcile_ci(local_repos.map((r) => ({
169
- repo_url: r.repo_config.repo_url,
170
- ci: r.repo_config.ci,
167
+ repo_url: r.entry.url,
168
+ ci: r.entry.ci,
171
169
  has_workflows: repo_has_workflows(r.repo_dir),
172
- // TODO: `local_repos` only ever holds checked-out repos — a missing repo
173
- // throws in `local_repos_ensure` before we reach here — so `checkable` is
174
- // always `true` today. The gate exists for a future caller that loads a
175
- // partial set; until then the skip path is inert and untested.
176
- checkable: true,
177
- archived: r.repo_config.archived,
170
+ archived: r.entry.archived
178
171
  })));
179
172
  const ci_duration = Date.now() - ci_start;
180
173
  const drift_details = ci_drift.map((d) => d.kind === 'missing_ci'
@@ -185,7 +178,7 @@ export const task = {
185
178
  success: ci_drift.length === 0,
186
179
  warnings: 0,
187
180
  errors: ci_drift.length,
188
- duration: ci_duration,
181
+ duration: ci_duration
189
182
  });
190
183
  if (ci_drift.length === 0) {
191
184
  log.info(st('green', ` ✓ ci_reconcile completed in ${ci_duration}ms`));
@@ -204,7 +197,7 @@ export const task = {
204
197
  success: false,
205
198
  warnings: 0,
206
199
  errors: 1,
207
- duration: ci_duration,
200
+ duration: ci_duration
208
201
  });
209
202
  log.error(st('red', ` ✗ ci_reconcile failed: ${error}`));
210
203
  }
@@ -255,5 +248,5 @@ export const task = {
255
248
  log.error(st('red', '❌ Validation failed - review the errors above.'));
256
249
  throw new Error('Validation failed');
257
250
  }
258
- },
251
+ }
259
252
  };
@@ -1,45 +1,35 @@
1
1
  /**
2
- * Shared dependency graph validation logic used across multiple workflows.
2
+ * Dependency graph validation shared by the publishing plan and the analysis tasks.
3
3
  *
4
- * Consolidates graph building, cycle detection, and publishing order computation
5
- * that was duplicated in three places: `multi_repo_publisher.ts`, `publishing_plan.ts`,
6
- * and `gitops_analyze.task.ts`.
4
+ * Builds the graph, detects cycles by type, and computes the publishing order,
5
+ * reporting cycles and sort failures in the result rather than throwing, so
6
+ * each caller decides how to surface them: the plan turns them into errors,
7
+ * `gitops_analyze` and `gitops_validate` format them.
7
8
  *
8
- * Options pattern supports different behaviors: analyze can tolerate cycles for
9
- * reporting, while publish must throw on production cycles.
10
- *
11
- * See also: `dependency_graph.ts` for core graph data structure and algorithms.
9
+ * See also: `dependency_graph.ts` for the core graph data structure and algorithms.
12
10
  *
13
11
  * @module
14
12
  */
15
- import type { Logger } from '@fuzdev/fuz_util/log.ts';
16
- import { DependencyGraph, DependencyGraphBuilder } from './dependency_graph.js';
13
+ import { DependencyGraph, type DependencyAnalysis } from './dependency_graph.js';
17
14
  import { type LocalRepo } from './local_repo.js';
18
15
  export interface GraphValidationResult {
19
16
  graph: DependencyGraph;
20
17
  publishing_order: Array<string>;
21
18
  production_cycles: Array<Array<string>>;
22
19
  dev_cycles: Array<Array<string>>;
20
+ /** Why the topological sort failed, when it did; `publishing_order` is then empty. */
23
21
  sort_error?: string;
24
22
  }
25
23
  /**
26
- * Shared utility for building dependency graph, detecting cycles, and computing publishing order.
27
- * This centralizes logic that was duplicated across `multi_repo_publisher`, `publishing_plan`, and `gitops_analyze`.
24
+ * Builds the dependency graph, detects cycles, and computes the publishing order
25
+ * (prod/peer dependencies only, so dev cycles don't block it).
26
+ *
27
+ * Never throws on cycles: a production/peer cycle leaves `publishing_order` empty
28
+ * and sets `sort_error`, and the caller reports it.
28
29
  *
29
- * @param options.throw_on_prod_cycles - whether to throw an error if production cycles are detected (default: true)
30
- * @param options.log_cycles - whether to log cycle information (default: true)
31
- * @param options.log_order - whether to log publishing order (default: true)
32
- * @returns graph validation result with graph, publishing order, and detected cycles
33
- * @throws {TaskError} if production cycles detected and `throw_on_prod_cycles` is true
30
+ * @returns the graph, publishing order, and detected cycles
34
31
  */
35
- export declare const validate_dependency_graph: (repos: Array<LocalRepo>, options?: {
36
- log?: Logger;
37
- throw_on_prod_cycles?: boolean;
38
- log_cycles?: boolean;
39
- log_order?: boolean;
40
- }) => GraphValidationResult;
41
- /** Cycles, wildcard deps, and missing peers produced by `DependencyGraphBuilder.analyze`. */
42
- export type DependencyAnalysis = ReturnType<DependencyGraphBuilder['analyze']>;
32
+ export declare const validate_dependency_graph: (repos: Array<LocalRepo>) => GraphValidationResult;
43
33
  export interface RepoAnalysis {
44
34
  graph: DependencyGraph;
45
35
  analysis: DependencyAnalysis;
@@ -49,8 +39,7 @@ export interface RepoAnalysis {
49
39
  /**
50
40
  * Builds the dependency graph and runs cycle/wildcard analysis, tolerating cycles
51
41
  * (reports rather than throws). The shared core of `gitops_analyze` and
52
- * `gitops_validate`, which format the result themselves — so this stays silent
53
- * (no `log`) to avoid emitting a redundant "Analyzing dependencies" line.
42
+ * `gitops_validate`, which format the result themselves.
54
43
  */
55
44
  export declare const analyze_repos: (repos: Array<LocalRepo>) => RepoAnalysis;
56
45
  //# sourceMappingURL=graph_validation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"graph_validation.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/graph_validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAIpD,OAAO,EAAC,eAAe,EAAE,sBAAsB,EAAC,MAAM,uBAAuB,CAAC;AAC9E,OAAO,EAAc,KAAK,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAE5D,MAAM,WAAW,qBAAqB;IACrC,KAAK,EAAE,eAAe,CAAC;IACvB,gBAAgB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAChC,iBAAiB,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACxC,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,GACrC,OAAO,KAAK,CAAC,SAAS,CAAC,EACvB,UAAS;IACR,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;CACf,KACJ,qBA8DF,CAAC;AAEF,6FAA6F;AAC7F,MAAM,MAAM,kBAAkB,GAAG,UAAU,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC,CAAC;AAE/E,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,eAAe,CAAC;IACvB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,sFAAsF;IACtF,gBAAgB,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;CACvC;AAED;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,KAAK,CAAC,SAAS,CAAC,KAAG,YASvD,CAAC"}
1
+ {"version":3,"file":"graph_validation.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/graph_validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,eAAe,EAAE,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACjF,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE9D,MAAM,WAAW,qBAAqB;IACrC,KAAK,EAAE,eAAe,CAAC;IACvB,gBAAgB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAChC,iBAAiB,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACxC,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACjC,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,GAAI,OAAO,KAAK,CAAC,SAAS,CAAC,KAAG,qBAoBnE,CAAC;AAEF,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,eAAe,CAAC;IACvB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,sFAAsF;IACtF,gBAAgB,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;CACvC;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,KAAK,CAAC,SAAS,CAAC,KAAG,YAQvD,CAAC"}
@@ -1,75 +1,36 @@
1
1
  /**
2
- * Shared dependency graph validation logic used across multiple workflows.
2
+ * Dependency graph validation shared by the publishing plan and the analysis tasks.
3
3
  *
4
- * Consolidates graph building, cycle detection, and publishing order computation
5
- * that was duplicated in three places: `multi_repo_publisher.ts`, `publishing_plan.ts`,
6
- * and `gitops_analyze.task.ts`.
4
+ * Builds the graph, detects cycles by type, and computes the publishing order,
5
+ * reporting cycles and sort failures in the result rather than throwing, so
6
+ * each caller decides how to surface them: the plan turns them into errors,
7
+ * `gitops_analyze` and `gitops_validate` format them.
7
8
  *
8
- * Options pattern supports different behaviors: analyze can tolerate cycles for
9
- * reporting, while publish must throw on production cycles.
10
- *
11
- * See also: `dependency_graph.ts` for core graph data structure and algorithms.
9
+ * See also: `dependency_graph.ts` for the core graph data structure and algorithms.
12
10
  *
13
11
  * @module
14
12
  */
15
- import { TaskError } from '@fuzdev/gro';
16
- import { styleText as st } from 'node:util';
17
- import { DependencyGraph, DependencyGraphBuilder } from './dependency_graph.js';
13
+ import { DependencyGraph } from './dependency_graph.js';
18
14
  import { repo_is_npm } from './local_repo.js';
19
15
  /**
20
- * Shared utility for building dependency graph, detecting cycles, and computing publishing order.
21
- * This centralizes logic that was duplicated across `multi_repo_publisher`, `publishing_plan`, and `gitops_analyze`.
16
+ * Builds the dependency graph, detects cycles, and computes the publishing order
17
+ * (prod/peer dependencies only, so dev cycles don't block it).
18
+ *
19
+ * Never throws on cycles: a production/peer cycle leaves `publishing_order` empty
20
+ * and sets `sort_error`, and the caller reports it.
22
21
  *
23
- * @param options.throw_on_prod_cycles - whether to throw an error if production cycles are detected (default: true)
24
- * @param options.log_cycles - whether to log cycle information (default: true)
25
- * @param options.log_order - whether to log publishing order (default: true)
26
- * @returns graph validation result with graph, publishing order, and detected cycles
27
- * @throws {TaskError} if production cycles detected and `throw_on_prod_cycles` is true
22
+ * @returns the graph, publishing order, and detected cycles
28
23
  */
29
- export const validate_dependency_graph = (repos, options = {}) => {
30
- const { log, throw_on_prod_cycles = true, log_cycles = true, log_order = true } = options;
31
- // Build dependency graph
32
- log?.info('📊 Analyzing dependencies...');
33
- const builder = new DependencyGraphBuilder();
34
- const graph = builder.build_from_repos(repos);
35
- // Check for cycles
24
+ export const validate_dependency_graph = (repos) => {
25
+ const graph = new DependencyGraph(repos);
36
26
  const { production_cycles, dev_cycles } = graph.detect_cycles_by_type();
37
- // Log production cycles
38
- if (production_cycles.length > 0 && log_cycles) {
39
- log?.error(st('red', '❌ Production/peer dependency cycles detected:'));
40
- for (const cycle of production_cycles) {
41
- log?.error(` ${cycle.join(' → ')}`);
42
- }
43
- if (throw_on_prod_cycles) {
44
- throw new TaskError(`Cannot publish with production/peer dependency cycles. ` +
45
- `These must be resolved before publishing.`);
46
- }
47
- }
48
- // Log dev cycles (informational, not an error)
49
- if (dev_cycles.length > 0 && log_cycles) {
50
- log?.info(st('dim', 'â„šī¸ Dev dependency cycles detected (this is normal):'));
51
- for (const cycle of dev_cycles) {
52
- log?.info(st('dim', ` ${cycle.join(' → ')}`));
53
- }
54
- }
55
- // Compute publishing order
56
27
  let publishing_order;
57
28
  let sort_error;
58
29
  try {
59
30
  publishing_order = graph.topological_sort(true); // exclude dev deps to break cycles
60
- if (log_order && publishing_order.length > 0) {
61
- log?.info(` Publishing order: ${publishing_order.join(' → ')}`);
62
- }
63
31
  }
64
32
  catch (error) {
65
- // Capture the sort error message for callers to report
66
33
  sort_error = 'Failed to compute publishing order: ' + error;
67
- // If topological sort fails (due to cycles), return empty array
68
- // Only throw if production cycles exist AND throw_on_prod_cycles is true
69
- if (production_cycles.length > 0 && throw_on_prod_cycles) {
70
- throw new TaskError(sort_error);
71
- }
72
- // Otherwise, return empty publishing order (let caller handle it)
73
34
  publishing_order = [];
74
35
  }
75
36
  return {
@@ -77,22 +38,20 @@ export const validate_dependency_graph = (repos, options = {}) => {
77
38
  publishing_order,
78
39
  production_cycles,
79
40
  dev_cycles,
80
- sort_error,
41
+ sort_error
81
42
  };
82
43
  };
83
44
  /**
84
45
  * Builds the dependency graph and runs cycle/wildcard analysis, tolerating cycles
85
46
  * (reports rather than throws). The shared core of `gitops_analyze` and
86
- * `gitops_validate`, which format the result themselves — so this stays silent
87
- * (no `log`) to avoid emitting a redundant "Analyzing dependencies" line.
47
+ * `gitops_validate`, which format the result themselves.
88
48
  */
89
49
  export const analyze_repos = (repos) => {
90
- // Only npm packages form the dependency graph; non-npm repos (e.g. cargo) are excluded.
91
- const { graph, publishing_order: order } = validate_dependency_graph(repos.filter(repo_is_npm), {
92
- throw_on_prod_cycles: false, // report, don't throw
93
- log_cycles: false, // callers format their own cycle output
94
- log_order: false,
95
- });
96
- const analysis = new DependencyGraphBuilder().analyze(graph);
97
- return { graph, analysis, publishing_order: order.length > 0 ? order : null };
50
+ // only npm packages form the dependency graph; non-npm repos (e.g. cargo) are excluded
51
+ const { graph, publishing_order } = validate_dependency_graph(repos.filter(repo_is_npm));
52
+ return {
53
+ graph,
54
+ analysis: graph.analyze(),
55
+ publishing_order: publishing_order.length > 0 ? publishing_order : null
56
+ };
98
57
  };