@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.
- package/README.md +91 -17
- package/dist/ModulesDetail.svelte +22 -27
- package/dist/ModulesDetail.svelte.d.ts.map +1 -1
- package/dist/ModulesNav.svelte +8 -5
- package/dist/ModulesNav.svelte.d.ts.map +1 -1
- package/dist/ModulesPage.svelte +3 -5
- package/dist/ModulesPage.svelte.d.ts.map +1 -1
- package/dist/PageFooter.svelte +2 -2
- package/dist/PageHeader.svelte +6 -6
- package/dist/PageHeader.svelte.d.ts +0 -3
- package/dist/PageHeader.svelte.d.ts.map +1 -1
- package/dist/PullRequestsDetail.svelte +29 -20
- package/dist/PullRequestsDetail.svelte.d.ts.map +1 -1
- package/dist/PullRequestsPage.svelte +4 -6
- package/dist/PullRequestsPage.svelte.d.ts.map +1 -1
- package/dist/ReposTable.svelte +43 -55
- package/dist/ReposTable.svelte.d.ts.map +1 -1
- package/dist/ReposTree.svelte +9 -21
- package/dist/ReposTree.svelte.d.ts.map +1 -1
- package/dist/ReposTreeNav.svelte +9 -12
- package/dist/ReposTreeNav.svelte.d.ts.map +1 -1
- package/dist/TablePage.svelte +3 -5
- package/dist/TablePage.svelte.d.ts.map +1 -1
- package/dist/TreeItemPage.svelte +3 -5
- package/dist/TreeItemPage.svelte.d.ts.map +1 -1
- package/dist/TreePage.svelte +3 -5
- package/dist/TreePage.svelte.d.ts.map +1 -1
- package/dist/changeset_generator.d.ts.map +1 -1
- package/dist/changeset_generator.js +2 -2
- package/dist/changeset_reader.d.ts.map +1 -1
- package/dist/changeset_reader.js +3 -3
- package/dist/ci_reconcile.d.ts +6 -10
- package/dist/ci_reconcile.d.ts.map +1 -1
- package/dist/ci_reconcile.js +6 -8
- package/dist/dependency_graph.d.ts +26 -49
- package/dist/dependency_graph.d.ts.map +1 -1
- package/dist/dependency_graph.js +40 -83
- package/dist/dependency_updater.d.ts +1 -1
- package/dist/dependency_updater.d.ts.map +1 -1
- package/dist/dependency_updater.js +14 -9
- package/dist/fetch_repo_data.d.ts +34 -8
- package/dist/fetch_repo_data.d.ts.map +1 -1
- package/dist/fetch_repo_data.js +44 -22
- package/dist/fs_fetch_value_cache.d.ts +1 -1
- package/dist/fs_fetch_value_cache.d.ts.map +1 -1
- package/dist/fs_fetch_value_cache.js +3 -3
- package/dist/git_operations.d.ts +6 -49
- package/dist/git_operations.d.ts.map +1 -1
- package/dist/git_operations.js +13 -126
- package/dist/github.d.ts +17 -2
- package/dist/github.d.ts.map +1 -1
- package/dist/github.js +17 -8
- package/dist/github_helpers.d.ts.map +1 -1
- package/dist/github_helpers.js +3 -11
- package/dist/gitops_analyze.task.d.ts +24 -2
- package/dist/gitops_analyze.task.d.ts.map +1 -1
- package/dist/gitops_analyze.task.js +47 -34
- package/dist/gitops_config.d.ts +34 -79
- package/dist/gitops_config.d.ts.map +1 -1
- package/dist/gitops_config.js +76 -53
- package/dist/gitops_constants.d.ts +6 -4
- package/dist/gitops_constants.d.ts.map +1 -1
- package/dist/gitops_constants.js +6 -4
- package/dist/gitops_plan.task.d.ts +29 -3
- package/dist/gitops_plan.task.d.ts.map +1 -1
- package/dist/gitops_plan.task.js +83 -61
- package/dist/gitops_publish.task.d.ts +62 -2
- package/dist/gitops_publish.task.d.ts.map +1 -1
- package/dist/gitops_publish.task.js +193 -105
- package/dist/gitops_run.task.d.ts +74 -0
- package/dist/gitops_run.task.d.ts.map +1 -1
- package/dist/gitops_run.task.js +176 -162
- package/dist/gitops_sync.task.d.ts +65 -3
- package/dist/gitops_sync.task.d.ts.map +1 -1
- package/dist/gitops_sync.task.js +163 -37
- package/dist/gitops_task_helpers.d.ts +86 -59
- package/dist/gitops_task_helpers.d.ts.map +1 -1
- package/dist/gitops_task_helpers.js +120 -79
- package/dist/gitops_validate.task.d.ts +1 -2
- package/dist/gitops_validate.task.d.ts.map +1 -1
- package/dist/gitops_validate.task.js +25 -32
- package/dist/graph_validation.d.ts +16 -27
- package/dist/graph_validation.d.ts.map +1 -1
- package/dist/graph_validation.js +24 -65
- package/dist/local_repo.d.ts +50 -62
- package/dist/local_repo.d.ts.map +1 -1
- package/dist/local_repo.js +83 -196
- package/dist/log_helpers.d.ts +8 -8
- package/dist/log_helpers.d.ts.map +1 -1
- package/dist/multi_repo_publisher.d.ts +10 -0
- package/dist/multi_repo_publisher.d.ts.map +1 -1
- package/dist/multi_repo_publisher.js +125 -61
- package/dist/npm_registry.d.ts +23 -19
- package/dist/npm_registry.d.ts.map +1 -1
- package/dist/npm_registry.js +21 -48
- package/dist/operations.d.ts +75 -180
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +15 -7
- package/dist/operations_defaults.d.ts +17 -2
- package/dist/operations_defaults.d.ts.map +1 -1
- package/dist/operations_defaults.js +111 -128
- package/dist/output_helpers.d.ts +36 -0
- package/dist/output_helpers.d.ts.map +1 -1
- package/dist/output_helpers.js +37 -3
- package/dist/preflight_checks.d.ts +20 -27
- package/dist/preflight_checks.d.ts.map +1 -1
- package/dist/preflight_checks.js +37 -126
- package/dist/publish_gate.d.ts.map +1 -1
- package/dist/publish_steps.d.ts +2 -4
- package/dist/publish_steps.d.ts.map +1 -1
- package/dist/publish_steps.js +4 -8
- package/dist/publishing_event.d.ts +2 -8
- package/dist/publishing_event.d.ts.map +1 -1
- package/dist/publishing_event.js +20 -17
- package/dist/publishing_event_handler.d.ts +10 -8
- package/dist/publishing_event_handler.d.ts.map +1 -1
- package/dist/publishing_event_handler.js +19 -17
- package/dist/publishing_plan.d.ts +16 -1
- package/dist/publishing_plan.d.ts.map +1 -1
- package/dist/publishing_plan.js +332 -254
- package/dist/publishing_plan_helpers.d.ts.map +1 -1
- package/dist/publishing_plan_helpers.js +3 -3
- package/dist/publishing_plan_logging.d.ts +1 -1
- package/dist/publishing_plan_logging.d.ts.map +1 -1
- package/dist/publishing_plan_logging.js +22 -15
- package/dist/repo.svelte.d.ts +7 -0
- package/dist/repo.svelte.d.ts.map +1 -1
- package/dist/repo.svelte.js +3 -0
- package/dist/repo_readiness.d.ts +210 -0
- package/dist/repo_readiness.d.ts.map +1 -0
- package/dist/repo_readiness.js +516 -0
- package/dist/repos_status.d.ts +3340 -0
- package/dist/repos_status.d.ts.map +1 -0
- package/dist/repos_status.js +555 -0
- package/dist/repos_status_load.d.ts +65 -0
- package/dist/repos_status_load.d.ts.map +1 -0
- package/dist/repos_status_load.js +131 -0
- package/dist/version_utils.js +1 -1
- package/package.json +15 -34
- package/src/lib/cargo_toml.ts +3 -3
- package/src/lib/changeset_generator.ts +19 -19
- package/src/lib/changeset_reader.ts +18 -18
- package/src/lib/ci_reconcile.ts +12 -16
- package/src/lib/dependency_graph.ts +61 -113
- package/src/lib/dependency_updater.ts +36 -30
- package/src/lib/fetch_repo_data.ts +71 -34
- package/src/lib/fs_fetch_value_cache.ts +15 -11
- package/src/lib/git_operations.ts +22 -194
- package/src/lib/github.ts +33 -21
- package/src/lib/github_helpers.ts +9 -16
- package/src/lib/gitops_analyze.task.ts +104 -66
- package/src/lib/gitops_config.ts +90 -139
- package/src/lib/gitops_constants.ts +6 -4
- package/src/lib/gitops_plan.task.ts +128 -76
- package/src/lib/gitops_publish.task.ts +280 -144
- package/src/lib/gitops_run.task.ts +265 -193
- package/src/lib/gitops_sync.task.ts +236 -52
- package/src/lib/gitops_task_helpers.ts +170 -114
- package/src/lib/gitops_validate.task.ts +44 -54
- package/src/lib/graph_validation.ts +28 -89
- package/src/lib/local_repo.ts +128 -335
- package/src/lib/log_helpers.ts +18 -28
- package/src/lib/multi_repo_publisher.ts +192 -110
- package/src/lib/npm_registry.ts +42 -67
- package/src/lib/operations.ts +99 -160
- package/src/lib/operations_defaults.ts +157 -201
- package/src/lib/output_helpers.ts +58 -6
- package/src/lib/preflight_checks.ts +52 -188
- package/src/lib/publish_gate.ts +7 -9
- package/src/lib/publish_steps.ts +27 -25
- package/src/lib/publishing_event.ts +26 -23
- package/src/lib/publishing_event_handler.ts +24 -22
- package/src/lib/publishing_plan.ts +474 -310
- package/src/lib/publishing_plan_helpers.ts +16 -12
- package/src/lib/publishing_plan_logging.ts +53 -44
- package/src/lib/repo.svelte.ts +16 -8
- package/src/lib/repo_readiness.ts +647 -0
- package/src/lib/repos_status.ts +671 -0
- package/src/lib/repos_status_load.ts +173 -0
- package/src/lib/version_utils.ts +3 -3
- package/dist/config_reconcile.d.ts +0 -46
- package/dist/config_reconcile.d.ts.map +0 -1
- package/dist/config_reconcile.js +0 -60
- package/dist/paths.d.ts +0 -7
- package/dist/paths.d.ts.map +0 -1
- package/dist/paths.js +0 -6
- package/dist/repo_ops.d.ts +0 -59
- package/dist/repo_ops.d.ts.map +0 -1
- package/dist/repo_ops.js +0 -170
- package/dist/resolved_gitops_config.d.ts +0 -9
- package/dist/resolved_gitops_config.d.ts.map +0 -1
- package/dist/resolved_gitops_config.js +0 -12
- package/src/lib/config_reconcile.ts +0 -94
- package/src/lib/paths.ts +0 -6
- package/src/lib/repo_ops.ts +0 -216
- package/src/lib/resolved_gitops_config.ts +0 -27
|
@@ -1,156 +1,212 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared initialization logic for all gitops tasks.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
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 `
|
|
12
|
+
* `gitops_publish.task.ts`, `gitops_validate.task.ts`, and `gitops_run.task.ts`.
|
|
12
13
|
*
|
|
13
|
-
* Accepts `
|
|
14
|
-
*
|
|
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
|
|
|
19
|
-
import {TaskError} from '@fuzdev/gro';
|
|
20
|
-
import {styleText as st} from 'node:util';
|
|
21
|
-
import {resolve
|
|
22
|
-
import {
|
|
23
|
-
import
|
|
20
|
+
import { TaskError } from '@fuzdev/gro';
|
|
21
|
+
import { styleText as st } from 'node:util';
|
|
22
|
+
import { resolve } from 'node:path';
|
|
23
|
+
import type { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
24
|
+
import { to_error_message } from '@fuzdev/fuz_util/error.ts';
|
|
24
25
|
|
|
25
|
-
import {load_gitops_config, type GitopsConfig} from './gitops_config.ts';
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
import { load_gitops_config, type GitopsConfig } from './gitops_config.ts';
|
|
27
|
+
import {
|
|
28
|
+
local_repos_load,
|
|
29
|
+
local_repos_resolve,
|
|
30
|
+
repo_is_npm,
|
|
31
|
+
type LocalRepo,
|
|
32
|
+
type LocalRepoPath
|
|
33
|
+
} from './local_repo.ts';
|
|
34
|
+
import { load_repos_status, to_repos_command } from './repos_status_load.ts';
|
|
35
|
+
import {
|
|
36
|
+
check_publish_readiness,
|
|
37
|
+
format_readiness_ahead,
|
|
38
|
+
format_readiness_block,
|
|
39
|
+
repos_not_at_rest
|
|
40
|
+
} from './repo_readiness.ts';
|
|
41
|
+
import type { ReposStatusReport } from './repos_status.ts';
|
|
42
|
+
import type { ReposOperations } from './operations.ts';
|
|
43
|
+
import { default_repos_operations } from './operations_defaults.ts';
|
|
30
44
|
|
|
31
|
-
export interface
|
|
45
|
+
export interface ResolveGitopsReposOptions {
|
|
46
|
+
/** Path to the gitops config, absolute or relative to the cwd. */
|
|
32
47
|
config: string;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
log?: Logger;
|
|
36
|
-
git_ops?: GitOperations;
|
|
37
|
-
npm_ops?: NpmOperations;
|
|
38
|
-
parallel?: boolean;
|
|
39
|
-
concurrency?: number;
|
|
48
|
+
/** A `repos.toml` to use instead of the one `repos` finds walking up from the cwd. */
|
|
49
|
+
registry?: string;
|
|
40
50
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* sit on disk — the safe default for read-only diagnostics. Defaults to `true`.
|
|
51
|
+
* The package whose generated data the run writes; when it's public, a
|
|
52
|
+
* private repo in the config fails the resolve.
|
|
44
53
|
*/
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
host?: { name: string; private: boolean };
|
|
55
|
+
log?: Logger;
|
|
56
|
+
repos_ops?: ReposOperations;
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* 1. Loads and normalizes config from `gitops.config.ts`
|
|
55
|
-
* 2. Resolves local repo paths (creates missing with `--download`)
|
|
56
|
-
* 3. If `sync`, switches branches and pulls latest changes (in parallel by default)
|
|
57
|
-
* 4. If `sync`, auto-installs deps if `package.json` changed during pull
|
|
58
|
-
*
|
|
59
|
-
* With `sync: false` (the default for read-only diagnostics), steps 3-4 are
|
|
60
|
-
* skipped and repos are loaded exactly as checked out — no branch switch, pull,
|
|
61
|
-
* install, or clean-workspace check.
|
|
62
|
-
*
|
|
63
|
-
* Priority for path resolution:
|
|
64
|
-
* - `dir` argument (explicit override)
|
|
65
|
-
* - Config `repos_dir` setting
|
|
66
|
-
* - `DEFAULT_REPOS_DIR` constant
|
|
60
|
+
* Resolves the gitops config's repos through `repos status`: loads the
|
|
61
|
+
* config's registry keys, reports on them, and resolves each to its checkout,
|
|
62
|
+
* in config order. Reads nothing but git state and writes nothing.
|
|
67
63
|
*
|
|
68
|
-
* @
|
|
69
|
-
* @
|
|
70
|
-
* @param options.parallel - whether to load repos in parallel (default: true)
|
|
71
|
-
* @param options.concurrency - max concurrent repo loads (default: 5)
|
|
72
|
-
* @param options.sync - sync working trees before loading (default: true)
|
|
73
|
-
* @param options.allow_dirty - when syncing, tolerate uncommitted changes (default: false)
|
|
74
|
-
* @returns initialized config and fully loaded repos ready for operations
|
|
75
|
-
* @throws {TaskError} if config loading or repo resolution fails
|
|
64
|
+
* @returns the config, the `repos status` report, and each repo's path and entry
|
|
65
|
+
* @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`
|
|
76
66
|
*/
|
|
77
|
-
export const
|
|
78
|
-
options:
|
|
67
|
+
export const resolve_gitops_repos = async (
|
|
68
|
+
options: ResolveGitopsReposOptions
|
|
79
69
|
): Promise<{
|
|
80
70
|
config_path: string;
|
|
81
|
-
repos_dir: string;
|
|
82
71
|
gitops_config: GitopsConfig;
|
|
83
|
-
|
|
72
|
+
report: ReposStatusReport;
|
|
73
|
+
local_repo_paths: Array<LocalRepoPath>;
|
|
84
74
|
}> => {
|
|
85
|
-
const {config,
|
|
86
|
-
options;
|
|
75
|
+
const { config, registry, host, log, repos_ops = default_repos_operations } = options;
|
|
87
76
|
const config_path = resolve(config);
|
|
88
77
|
const gitops_config = await import_gitops_config(config_path);
|
|
78
|
+
const keys = gitops_config.repos;
|
|
79
|
+
if (keys.length === 0) {
|
|
80
|
+
throw new TaskError(`No repos are configured in ${config_path}`);
|
|
81
|
+
}
|
|
89
82
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
);
|
|
101
|
-
const resolved_config = resolve_gitops_config(gitops_config, repos_dir);
|
|
83
|
+
log?.info(`reading the state of ${keys.length} repos from \`repos status\``);
|
|
84
|
+
log?.debug('repos status targets', keys);
|
|
85
|
+
const loaded = await load_repos_status({ keys, registry, repos_ops });
|
|
86
|
+
if (!loaded.ok) {
|
|
87
|
+
// an unknown key is the config's to fix, so name the config
|
|
88
|
+
throw new TaskError(
|
|
89
|
+
loaded.error?.kind === 'unknown_entry' ? `${config_path}: ${loaded.message}` : loaded.message
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
const { report } = loaded;
|
|
102
93
|
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
download,
|
|
108
|
-
log,
|
|
109
|
-
npm_ops,
|
|
110
|
-
});
|
|
94
|
+
const resolved = local_repos_resolve({ keys, report, host, registry });
|
|
95
|
+
if (!resolved.ok) {
|
|
96
|
+
throw new TaskError(`${config_path}: ${resolved.message}`);
|
|
97
|
+
}
|
|
111
98
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
log,
|
|
115
|
-
git_ops,
|
|
116
|
-
npm_ops,
|
|
117
|
-
parallel,
|
|
118
|
-
concurrency,
|
|
119
|
-
sync,
|
|
120
|
-
allow_dirty,
|
|
121
|
-
});
|
|
99
|
+
return { config_path, gitops_config, report, local_repo_paths: resolved.value };
|
|
100
|
+
};
|
|
122
101
|
|
|
123
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Central initialization function for the gitops tasks that load libraries:
|
|
104
|
+
* resolves the config's repos through `repos status` (`resolve_gitops_repos`),
|
|
105
|
+
* then loads each repo's library as its working tree sits (`local_repos_load`).
|
|
106
|
+
* Moves no ref; gro caches each library at `.gro/library.json` in its repo, at
|
|
107
|
+
* a clean commit.
|
|
108
|
+
*
|
|
109
|
+
* @returns the loaded repos, in config order
|
|
110
|
+
* @throws {TaskError} if resolving the repos or loading them fails
|
|
111
|
+
*/
|
|
112
|
+
export const get_gitops_ready = async (
|
|
113
|
+
options: ResolveGitopsReposOptions
|
|
114
|
+
): Promise<{ local_repos: Array<LocalRepo> }> => {
|
|
115
|
+
const { local_repo_paths } = await resolve_gitops_repos(options);
|
|
116
|
+
const local_repos = await local_repos_load({ local_repo_paths, log: options.log });
|
|
117
|
+
return { local_repos };
|
|
124
118
|
};
|
|
125
119
|
|
|
126
|
-
export interface
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
120
|
+
export interface GatePublishReadinessOptions {
|
|
121
|
+
/** The loaded repos; the npm ones are gated. */
|
|
122
|
+
local_repos: ReadonlyArray<LocalRepo>;
|
|
123
|
+
/** A `repos.toml` to use instead of the one `repos` finds walking up from the cwd. */
|
|
124
|
+
registry?: string;
|
|
125
|
+
/**
|
|
126
|
+
* The package names the plan publishes, to say whether a repo's commits ahead
|
|
127
|
+
* of origin go out with its release or stay unpushed.
|
|
128
|
+
*/
|
|
129
|
+
publishing?: ReadonlySet<string>;
|
|
130
|
+
log?: Logger;
|
|
131
|
+
repos_ops?: ReposOperations;
|
|
130
132
|
}
|
|
131
133
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
/**
|
|
135
|
+
* The readiness gate `gitops_publish --wetrun` runs before its confirmation
|
|
136
|
+
* prompt: fetches every npm repo from origin (`repos status <keys…> --fetch
|
|
137
|
+
* --json`, which writes remote-tracking refs and nothing else) and refuses
|
|
138
|
+
* unless each is ready (`check_publish_readiness`) — on its registry branch,
|
|
139
|
+
* clean, idle, in sync with origin or ahead of it, fetched without error, no
|
|
140
|
+
* other live session in its checkout, and nothing left to a person. Each
|
|
141
|
+
* ready repo ahead of origin is logged, saying whether its release push
|
|
142
|
+
* carries those commits or they stay unpushed.
|
|
143
|
+
*
|
|
144
|
+
* Every npm repo, not just those the plan publishes or rewrites: the plan
|
|
145
|
+
* reads each one's working tree (changesets, versions, dependency ranges), so
|
|
146
|
+
* a repo off its branch or behind origin can hide a changeset and leave the
|
|
147
|
+
* plan wrong about what to publish. Changes nothing.
|
|
148
|
+
*
|
|
149
|
+
* @param options - the loaded repos, the `--registry` if any, the package names the plan publishes, a logger, and the `repos` runner
|
|
150
|
+
* @throws {TaskError} if `repos status` fails or any npm repo isn't ready, naming each problem and its fix
|
|
151
|
+
*/
|
|
152
|
+
export const gate_publish_readiness = async (
|
|
153
|
+
options: GatePublishReadinessOptions
|
|
154
|
+
): Promise<void> => {
|
|
155
|
+
const { local_repos, registry, publishing, log, repos_ops = default_repos_operations } = options;
|
|
156
|
+
const npm_repos = local_repos.filter(repo_is_npm);
|
|
157
|
+
const keys = npm_repos.map((r) => r.entry.key);
|
|
158
|
+
if (keys.length === 0) return;
|
|
138
159
|
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
160
|
+
log?.info(`fetching ${keys.length} npm repos to check they're ready to publish`);
|
|
161
|
+
const loaded = await load_repos_status({ keys, registry, fetch: true, repos_ops });
|
|
162
|
+
if (!loaded.ok) {
|
|
163
|
+
throw new TaskError(`the readiness check failed: ${loaded.message}`);
|
|
164
|
+
}
|
|
165
|
+
const repos_command = to_repos_command(registry);
|
|
166
|
+
const checked = check_publish_readiness({ report: loaded.report, keys, repos_command });
|
|
167
|
+
if (!checked.ok) {
|
|
168
|
+
throw new TaskError(checked.message);
|
|
169
|
+
}
|
|
170
|
+
log?.info(st('green', `all ${keys.length} npm repos are ready to publish`));
|
|
171
|
+
const name_by_key = new Map(npm_repos.map((r) => [r.entry.key, r.library.name] as const));
|
|
172
|
+
for (const ahead of checked.ahead) {
|
|
173
|
+
const name = name_by_key.get(ahead.key);
|
|
174
|
+
const publishes = name !== undefined && publishing?.has(name) === true;
|
|
175
|
+
log?.info(st('yellow', format_readiness_ahead(ahead, publishes, { repos_command })));
|
|
176
|
+
}
|
|
177
|
+
};
|
|
146
178
|
|
|
147
|
-
|
|
179
|
+
/**
|
|
180
|
+
* Logs the diagnostics' readiness block as warnings (stderr, so a `--format
|
|
181
|
+
* json` or `markdown` document on stdout stays clean): each npm repo not at
|
|
182
|
+
* rest, and how. Logs nothing when every one is at rest. Reads the entries
|
|
183
|
+
* `repos status` already reported, from local refs.
|
|
184
|
+
*
|
|
185
|
+
* @param local_repos - the loaded repos; the npm ones are reported
|
|
186
|
+
* @param log - where the warnings go
|
|
187
|
+
* @param now - the current time in unix seconds (defaults to the clock)
|
|
188
|
+
*/
|
|
189
|
+
export const log_readiness_block = (
|
|
190
|
+
local_repos: ReadonlyArray<LocalRepo>,
|
|
191
|
+
log: Logger,
|
|
192
|
+
now: number = Math.floor(Date.now() / 1000)
|
|
193
|
+
): void => {
|
|
194
|
+
const not_ready = repos_not_at_rest(local_repos.filter(repo_is_npm).map((r) => r.entry));
|
|
195
|
+
for (const line of format_readiness_block(not_ready, now)) {
|
|
196
|
+
log.warn(st('yellow', line));
|
|
197
|
+
}
|
|
148
198
|
};
|
|
149
199
|
|
|
150
|
-
|
|
151
|
-
|
|
200
|
+
const import_gitops_config = async (config_path: string): Promise<GitopsConfig> => {
|
|
201
|
+
let gitops_config: GitopsConfig | null;
|
|
202
|
+
try {
|
|
203
|
+
gitops_config = await load_gitops_config(config_path);
|
|
204
|
+
} catch (err) {
|
|
205
|
+
// an invalid config is the user's to fix, not an unexpected task failure
|
|
206
|
+
throw new TaskError(to_error_message(err));
|
|
207
|
+
}
|
|
152
208
|
if (!gitops_config) {
|
|
153
|
-
throw new TaskError(st('red', `No gitops config found at ${
|
|
209
|
+
throw new TaskError(st('red', `No gitops config found at ${config_path}`));
|
|
154
210
|
}
|
|
155
211
|
return gitops_config;
|
|
156
212
|
};
|
|
@@ -1,37 +1,31 @@
|
|
|
1
|
-
import type {Task} from '@fuzdev/gro';
|
|
2
|
-
import {z} from 'zod';
|
|
3
|
-
import {styleText as st} from 'node:util';
|
|
1
|
+
import type { Task } from '@fuzdev/gro';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { styleText as st } from 'node:util';
|
|
4
4
|
|
|
5
|
-
import {get_gitops_ready} from './gitops_task_helpers.ts';
|
|
6
|
-
import {analyze_repos
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from './
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {GITOPS_CONFIG_PATH_DEFAULT} from './gitops_constants.ts';
|
|
15
|
-
import {reconcile_ci, repo_has_workflows} from './ci_reconcile.ts';
|
|
5
|
+
import { get_gitops_ready, log_readiness_block } from './gitops_task_helpers.ts';
|
|
6
|
+
import { analyze_repos } from './graph_validation.ts';
|
|
7
|
+
import type { DependencyAnalysis } from './dependency_graph.ts';
|
|
8
|
+
import { generate_publishing_plan, type PublishingPlan } from './publishing_plan.ts';
|
|
9
|
+
import { log_publishing_plan } from './publishing_plan_logging.ts';
|
|
10
|
+
import { execute_publishing_plan, type PublishingOptions } from './multi_repo_publisher.ts';
|
|
11
|
+
import { log_dependency_analysis } from './log_helpers.ts';
|
|
12
|
+
import { GITOPS_CONFIG_PATH_DEFAULT } from './gitops_constants.ts';
|
|
13
|
+
import { reconcile_ci, repo_has_workflows } from './ci_reconcile.ts';
|
|
16
14
|
|
|
17
15
|
/** @nodocs */
|
|
18
16
|
export const Args = z.strictObject({
|
|
19
17
|
config: z
|
|
20
18
|
.string()
|
|
21
|
-
.meta({description: 'path to the gitops config file, absolute or relative to the cwd'})
|
|
19
|
+
.meta({ description: 'path to the gitops config file, absolute or relative to the cwd' })
|
|
22
20
|
.default(GITOPS_CONFIG_PATH_DEFAULT),
|
|
23
|
-
|
|
21
|
+
registry: z
|
|
24
22
|
.string()
|
|
25
|
-
.meta({description: 'path containing the repos, defaults to the parent of the config dir'})
|
|
26
|
-
.optional(),
|
|
27
|
-
verbose: z.boolean().meta({description: 'show additional details'}).default(false),
|
|
28
|
-
sync: z
|
|
29
|
-
.boolean()
|
|
30
23
|
.meta({
|
|
31
24
|
description:
|
|
32
|
-
'
|
|
25
|
+
'path to the repos.toml registry, when `repos` would not find it walking up from the cwd'
|
|
33
26
|
})
|
|
34
|
-
.
|
|
27
|
+
.optional(),
|
|
28
|
+
verbose: z.boolean().meta({ description: 'show additional details' }).default(false)
|
|
35
29
|
});
|
|
36
30
|
export type Args = z.infer<typeof Args>;
|
|
37
31
|
|
|
@@ -40,8 +34,8 @@ export const task: Task<Args> = {
|
|
|
40
34
|
Args,
|
|
41
35
|
summary:
|
|
42
36
|
'validate gitops configuration by running all read-only commands and checking for issues',
|
|
43
|
-
run: async ({args, log}) => {
|
|
44
|
-
const {config,
|
|
37
|
+
run: async ({ args, log }) => {
|
|
38
|
+
const { config, registry, verbose } = args;
|
|
45
39
|
|
|
46
40
|
log.info(st('cyan', 'Running Gitops Validation Suite'));
|
|
47
41
|
log.info(st('dim', 'This runs all read-only commands and checks for consistency.'));
|
|
@@ -59,17 +53,18 @@ export const task: Task<Args> = {
|
|
|
59
53
|
|
|
60
54
|
const start_time = Date.now();
|
|
61
55
|
|
|
62
|
-
// Load repos once (shared by all commands)
|
|
56
|
+
// Load repos once (shared by all commands), as they sit, and say which aren't at rest
|
|
63
57
|
log.info(st('dim', 'Loading repositories...'));
|
|
64
|
-
const {local_repos} = await get_gitops_ready({config,
|
|
58
|
+
const { local_repos } = await get_gitops_ready({ config, registry, log });
|
|
65
59
|
log.info(st('dim', ` Found ${local_repos.length} local repos`));
|
|
60
|
+
log_readiness_block(local_repos, log);
|
|
66
61
|
|
|
67
62
|
// 1. Run gitops_analyze
|
|
68
63
|
log.info(st('yellow', 'Running gitops_analyze...'));
|
|
69
64
|
const analyze_start = Date.now();
|
|
70
65
|
try {
|
|
71
66
|
// Build dependency graph and analyze cycles/wildcards (tolerating cycles)
|
|
72
|
-
const {analysis} = analyze_repos(local_repos);
|
|
67
|
+
const { analysis } = analyze_repos(local_repos);
|
|
73
68
|
|
|
74
69
|
const analyze_duration = Date.now() - analyze_start;
|
|
75
70
|
|
|
@@ -93,7 +88,7 @@ export const task: Task<Args> = {
|
|
|
93
88
|
duration: analyze_duration,
|
|
94
89
|
warning_details,
|
|
95
90
|
info_details,
|
|
96
|
-
analysis
|
|
91
|
+
analysis
|
|
97
92
|
});
|
|
98
93
|
|
|
99
94
|
log.info(st('green', ` ✓ gitops_analyze completed in ${analyze_duration}ms`));
|
|
@@ -111,7 +106,7 @@ export const task: Task<Args> = {
|
|
|
111
106
|
success: false,
|
|
112
107
|
warnings: 0,
|
|
113
108
|
errors: 1,
|
|
114
|
-
duration: analyze_duration
|
|
109
|
+
duration: analyze_duration
|
|
115
110
|
});
|
|
116
111
|
log.error(st('red', ` ✗ gitops_analyze failed: ${error}`));
|
|
117
112
|
}
|
|
@@ -121,7 +116,7 @@ export const task: Task<Args> = {
|
|
|
121
116
|
log.info(st('yellow', 'Running gitops_plan...'));
|
|
122
117
|
const plan_start = Date.now();
|
|
123
118
|
try {
|
|
124
|
-
const plan = await generate_publishing_plan(local_repos, {verbose});
|
|
119
|
+
const plan = await generate_publishing_plan(local_repos, { verbose });
|
|
125
120
|
publishing_plan = plan;
|
|
126
121
|
const plan_duration = Date.now() - plan_start;
|
|
127
122
|
|
|
@@ -133,12 +128,12 @@ export const task: Task<Args> = {
|
|
|
133
128
|
success: errors === 0,
|
|
134
129
|
warnings,
|
|
135
130
|
errors,
|
|
136
|
-
duration: plan_duration
|
|
131
|
+
duration: plan_duration
|
|
137
132
|
});
|
|
138
133
|
|
|
139
134
|
log.info(st('green', ` ✓ gitops_plan completed in ${plan_duration}ms`));
|
|
140
135
|
if (verbose) {
|
|
141
|
-
log_publishing_plan(plan, log, {verbose});
|
|
136
|
+
log_publishing_plan(plan, log, { verbose });
|
|
142
137
|
}
|
|
143
138
|
if (warnings > 0) {
|
|
144
139
|
log.warn(st('yellow', ` ⚠️ Found ${warnings} warning(s)`));
|
|
@@ -153,7 +148,7 @@ export const task: Task<Args> = {
|
|
|
153
148
|
success: false,
|
|
154
149
|
warnings: 0,
|
|
155
150
|
errors: 1,
|
|
156
|
-
duration: plan_duration
|
|
151
|
+
duration: plan_duration
|
|
157
152
|
});
|
|
158
153
|
log.error(st('red', ` ✗ gitops_plan failed: ${error}`));
|
|
159
154
|
}
|
|
@@ -164,14 +159,14 @@ export const task: Task<Args> = {
|
|
|
164
159
|
try {
|
|
165
160
|
const options: PublishingOptions = {
|
|
166
161
|
wetrun: false,
|
|
167
|
-
log: undefined
|
|
162
|
+
log: undefined // Silent for validation
|
|
168
163
|
};
|
|
169
164
|
|
|
170
165
|
// Reuse the plan from step 2 (regenerate only if that step failed to produce one).
|
|
171
166
|
const result = await execute_publishing_plan(
|
|
172
167
|
local_repos,
|
|
173
|
-
publishing_plan ?? (await generate_publishing_plan(local_repos, {verbose})),
|
|
174
|
-
options
|
|
168
|
+
publishing_plan ?? (await generate_publishing_plan(local_repos, { verbose })),
|
|
169
|
+
options
|
|
175
170
|
);
|
|
176
171
|
const dry_duration = Date.now() - dry_start;
|
|
177
172
|
|
|
@@ -184,7 +179,7 @@ export const task: Task<Args> = {
|
|
|
184
179
|
success: result.ok,
|
|
185
180
|
warnings: 0,
|
|
186
181
|
errors,
|
|
187
|
-
duration: dry_duration
|
|
182
|
+
duration: dry_duration
|
|
188
183
|
});
|
|
189
184
|
|
|
190
185
|
log.info(st('green', ` ✓ gitops_publish (dry run) completed in ${dry_duration}ms`));
|
|
@@ -198,40 +193,35 @@ export const task: Task<Args> = {
|
|
|
198
193
|
success: false,
|
|
199
194
|
warnings: 0,
|
|
200
195
|
errors: 1,
|
|
201
|
-
duration: dry_duration
|
|
196
|
+
duration: dry_duration
|
|
202
197
|
});
|
|
203
198
|
log.error(st('red', ` ✗ gitops_publish (dry run) failed: ${error}`));
|
|
204
199
|
}
|
|
205
200
|
|
|
206
|
-
// 4. Reconcile each repo's declared `ci` against actual workflow files on disk.
|
|
201
|
+
// 4. Reconcile each repo's registry-declared `ci` against actual workflow files on disk.
|
|
207
202
|
log.info(st('yellow', 'Running ci_reconcile...'));
|
|
208
203
|
const ci_start = Date.now();
|
|
209
204
|
try {
|
|
210
205
|
const ci_drift = reconcile_ci(
|
|
211
206
|
local_repos.map((r) => ({
|
|
212
|
-
repo_url: r.
|
|
213
|
-
ci: r.
|
|
207
|
+
repo_url: r.entry.url,
|
|
208
|
+
ci: r.entry.ci,
|
|
214
209
|
has_workflows: repo_has_workflows(r.repo_dir),
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
// always `true` today. The gate exists for a future caller that loads a
|
|
218
|
-
// partial set; until then the skip path is inert and untested.
|
|
219
|
-
checkable: true,
|
|
220
|
-
archived: r.repo_config.archived,
|
|
221
|
-
})),
|
|
210
|
+
archived: r.entry.archived
|
|
211
|
+
}))
|
|
222
212
|
);
|
|
223
213
|
const ci_duration = Date.now() - ci_start;
|
|
224
214
|
const drift_details = ci_drift.map((d) =>
|
|
225
215
|
d.kind === 'missing_ci'
|
|
226
216
|
? `${d.repo_url}: ci=true but no workflow files`
|
|
227
|
-
: `${d.repo_url}: ci=false but workflow files present
|
|
217
|
+
: `${d.repo_url}: ci=false but workflow files present`
|
|
228
218
|
);
|
|
229
219
|
results.push({
|
|
230
220
|
command: 'ci_reconcile',
|
|
231
221
|
success: ci_drift.length === 0,
|
|
232
222
|
warnings: 0,
|
|
233
223
|
errors: ci_drift.length,
|
|
234
|
-
duration: ci_duration
|
|
224
|
+
duration: ci_duration
|
|
235
225
|
});
|
|
236
226
|
if (ci_drift.length === 0) {
|
|
237
227
|
log.info(st('green', ` ✓ ci_reconcile completed in ${ci_duration}ms`));
|
|
@@ -248,7 +238,7 @@ export const task: Task<Args> = {
|
|
|
248
238
|
success: false,
|
|
249
239
|
warnings: 0,
|
|
250
240
|
errors: 1,
|
|
251
|
-
duration: ci_duration
|
|
241
|
+
duration: ci_duration
|
|
252
242
|
});
|
|
253
243
|
log.error(st('red', ` ✗ ci_reconcile failed: ${error}`));
|
|
254
244
|
}
|
|
@@ -295,7 +285,7 @@ export const task: Task<Args> = {
|
|
|
295
285
|
log.info(st('green', '✓ All validation checks passed'));
|
|
296
286
|
if (total_warnings > 0) {
|
|
297
287
|
log.warn(
|
|
298
|
-
st('yellow', `⚠️ Note: ${total_warnings} warning(s) found - review output above.`)
|
|
288
|
+
st('yellow', `⚠️ Note: ${total_warnings} warning(s) found - review output above.`)
|
|
299
289
|
);
|
|
300
290
|
}
|
|
301
291
|
} else {
|
|
@@ -305,5 +295,5 @@ export const task: Task<Args> = {
|
|
|
305
295
|
log.error(st('red', '❌ Validation failed - review the errors above.'));
|
|
306
296
|
throw new Error('Validation failed');
|
|
307
297
|
}
|
|
308
|
-
}
|
|
298
|
+
}
|
|
309
299
|
};
|