@fuzdev/fuz_gitops 0.78.1 → 0.79.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -24
- package/dist/ModulesDetail.svelte +15 -22
- package/dist/ModulesDetail.svelte.d.ts.map +1 -1
- package/dist/ModulesNav.svelte +4 -1
- package/dist/ModulesPage.svelte +1 -3
- package/dist/ModulesPage.svelte.d.ts.map +1 -1
- package/dist/PageHeader.svelte +2 -2
- package/dist/PageHeader.svelte.d.ts +0 -3
- package/dist/PageHeader.svelte.d.ts.map +1 -1
- package/dist/PullRequestsDetail.svelte +10 -9
- package/dist/PullRequestsDetail.svelte.d.ts.map +1 -1
- package/dist/PullRequestsPage.svelte +1 -3
- package/dist/PullRequestsPage.svelte.d.ts.map +1 -1
- package/dist/ReposTable.svelte +27 -48
- package/dist/ReposTable.svelte.d.ts.map +1 -1
- package/dist/ReposTree.svelte +5 -19
- package/dist/ReposTree.svelte.d.ts.map +1 -1
- package/dist/ReposTreeNav.svelte +5 -7
- package/dist/ReposTreeNav.svelte.d.ts.map +1 -1
- package/dist/TablePage.svelte +1 -3
- package/dist/TablePage.svelte.d.ts.map +1 -1
- package/dist/TreeItemPage.svelte +1 -3
- package/dist/TreeItemPage.svelte.d.ts.map +1 -1
- package/dist/TreePage.svelte +1 -3
- package/dist/TreePage.svelte.d.ts.map +1 -1
- 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 +37 -80
- package/dist/dependency_updater.d.ts +1 -1
- package/dist/dependency_updater.d.ts.map +1 -1
- package/dist/dependency_updater.js +6 -1
- 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.js +1 -1
- 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 +15 -6
- 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 +43 -30
- 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 +80 -58
- 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 +187 -102
- 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 +174 -160
- 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 +154 -30
- 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 +15 -22
- package/dist/graph_validation.d.ts +16 -27
- package/dist/graph_validation.d.ts.map +1 -1
- package/dist/graph_validation.js +23 -64
- package/dist/local_repo.d.ts +50 -62
- package/dist/local_repo.d.ts.map +1 -1
- package/dist/local_repo.js +80 -193
- 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 +115 -51
- package/dist/npm_registry.d.ts +23 -19
- package/dist/npm_registry.d.ts.map +1 -1
- package/dist/npm_registry.js +20 -47
- 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 +104 -121
- 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_steps.d.ts +2 -4
- package/dist/publish_steps.d.ts.map +1 -1
- package/dist/publish_steps.js +2 -6
- package/dist/publishing_event.d.ts +2 -8
- package/dist/publishing_event.d.ts.map +1 -1
- package/dist/publishing_event.js +5 -4
- 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 +14 -12
- package/dist/publishing_plan.d.ts +16 -1
- package/dist/publishing_plan.d.ts.map +1 -1
- package/dist/publishing_plan.js +328 -250
- 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 +21 -14
- 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/package.json +7 -27
- package/src/lib/ci_reconcile.ts +7 -11
- package/src/lib/dependency_graph.ts +45 -97
- package/src/lib/dependency_updater.ts +7 -1
- package/src/lib/fetch_repo_data.ts +66 -29
- package/src/lib/fs_fetch_value_cache.ts +1 -1
- package/src/lib/git_operations.ts +15 -187
- package/src/lib/github.ts +20 -8
- package/src/lib/github_helpers.ts +5 -12
- package/src/lib/gitops_analyze.task.ts +79 -41
- package/src/lib/gitops_config.ts +88 -137
- package/src/lib/gitops_constants.ts +6 -4
- package/src/lib/gitops_plan.task.ts +113 -61
- package/src/lib/gitops_publish.task.ts +255 -122
- package/src/lib/gitops_run.task.ts +250 -178
- package/src/lib/gitops_sync.task.ts +208 -33
- package/src/lib/gitops_task_helpers.ts +166 -110
- package/src/lib/gitops_validate.task.ts +17 -27
- package/src/lib/graph_validation.ts +25 -86
- package/src/lib/local_repo.ts +109 -316
- package/src/lib/log_helpers.ts +8 -18
- package/src/lib/multi_repo_publisher.ts +138 -53
- package/src/lib/npm_registry.ts +37 -62
- package/src/lib/operations.ts +78 -150
- package/src/lib/operations_defaults.ts +108 -154
- package/src/lib/output_helpers.ts +55 -3
- package/src/lib/preflight_checks.ts +47 -183
- package/src/lib/publish_steps.ts +15 -13
- package/src/lib/publishing_event.ts +5 -4
- package/src/lib/publishing_event_handler.ts +18 -16
- package/src/lib/publishing_plan.ts +451 -287
- package/src/lib/publishing_plan_logging.ts +32 -23
- package/src/lib/repo.svelte.ts +8 -0
- 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/dist/config_reconcile.d.ts +0 -46
- package/dist/config_reconcile.d.ts.map +0 -1
- package/dist/config_reconcile.js +0 -64
- 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 -98
- 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,14 +1,6 @@
|
|
|
1
1
|
import { spawn_out, spawn_result_to_message } from '@fuzdev/fuz_util/process.ts';
|
|
2
2
|
import type { SpawnOptions } from 'node:child_process';
|
|
3
|
-
import {
|
|
4
|
-
git_check_clean_workspace as gro_git_check_clean_workspace,
|
|
5
|
-
git_checkout as gro_git_checkout,
|
|
6
|
-
git_pull as gro_git_pull,
|
|
7
|
-
git_current_branch_name as gro_git_current_branch_name,
|
|
8
|
-
git_current_commit_hash as gro_git_current_commit_hash,
|
|
9
|
-
type GitBranch,
|
|
10
|
-
type GitOrigin
|
|
11
|
-
} from '@fuzdev/fuz_util/git.ts';
|
|
3
|
+
import { git_current_commit_hash as gro_git_current_commit_hash } from '@fuzdev/fuz_util/git.ts';
|
|
12
4
|
|
|
13
5
|
/**
|
|
14
6
|
* Adds files to git staging area and throws if anything goes wrong.
|
|
@@ -27,201 +19,37 @@ export const git_add = async (
|
|
|
27
19
|
};
|
|
28
20
|
|
|
29
21
|
/**
|
|
30
|
-
* Commits
|
|
22
|
+
* Commits `files` alone with a message, leaving anything else staged out of
|
|
23
|
+
* the commit, and throws if anything goes wrong. `files` must be non-empty:
|
|
24
|
+
* an empty list would commit the whole index.
|
|
31
25
|
*/
|
|
32
|
-
export const git_commit = async (
|
|
33
|
-
const { result, stderr } = await spawn_out('git', ['commit', '-m', message], options);
|
|
34
|
-
if (!result.ok) {
|
|
35
|
-
throw Error(
|
|
36
|
-
`git_commit failed with ${spawn_result_to_message(result)}${stderr ? ': ' + stderr.trim() : ''}`
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Adds files and commits in one operation and throws if anything goes wrong.
|
|
43
|
-
*/
|
|
44
|
-
export const git_add_and_commit = async (
|
|
45
|
-
files: string | Array<string>,
|
|
26
|
+
export const git_commit = async (
|
|
46
27
|
message: string,
|
|
28
|
+
files: Array<string>,
|
|
47
29
|
options?: SpawnOptions
|
|
48
30
|
): Promise<void> => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Creates a git tag and throws if anything goes wrong.
|
|
55
|
-
*/
|
|
56
|
-
export const git_tag = async (
|
|
57
|
-
tag_name: string,
|
|
58
|
-
message?: string,
|
|
59
|
-
options?: SpawnOptions
|
|
60
|
-
): Promise<void> => {
|
|
61
|
-
const args = message ? ['tag', '-a', tag_name, '-m', message] : ['tag', tag_name];
|
|
62
|
-
|
|
63
|
-
const { result, stderr } = await spawn_out('git', args, options);
|
|
64
|
-
if (!result.ok) {
|
|
65
|
-
throw Error(
|
|
66
|
-
`git_tag failed for tag '${tag_name}' with ${spawn_result_to_message(result)}${stderr ? ': ' + stderr.trim() : ''}`
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Pushes a tag to origin and throws if anything goes wrong.
|
|
73
|
-
*/
|
|
74
|
-
export const git_push_tag = async (
|
|
75
|
-
tag_name: string,
|
|
76
|
-
origin: GitOrigin = 'origin' as GitOrigin,
|
|
77
|
-
options?: SpawnOptions
|
|
78
|
-
): Promise<void> => {
|
|
79
|
-
const { result, stderr } = await spawn_out('git', ['push', origin, tag_name], options);
|
|
80
|
-
if (!result.ok) {
|
|
81
|
-
throw Error(
|
|
82
|
-
`git_push_tag failed for tag '${tag_name}' with ${spawn_result_to_message(result)}${stderr ? ': ' + stderr.trim() : ''}`
|
|
83
|
-
);
|
|
31
|
+
if (files.length === 0) {
|
|
32
|
+
throw Error('git_commit needs at least one file: an empty list would commit the whole index');
|
|
84
33
|
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Returns `true` if the working tree has any changes — staged, unstaged, or
|
|
89
|
-
* untracked (`git status --porcelain`). Broader than `git_list_uncommitted_files`,
|
|
90
|
-
* which reports only tracked working-tree changes relative to HEAD.
|
|
91
|
-
*/
|
|
92
|
-
export const git_has_changes = async (options?: SpawnOptions): Promise<boolean> => {
|
|
93
|
-
const { stdout } = await spawn_out('git', ['status', '--porcelain'], options);
|
|
94
|
-
return stdout ? stdout.trim().length > 0 : false;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Lists uncommitted files in the working tree (`git diff --name-only HEAD`).
|
|
99
|
-
*/
|
|
100
|
-
export const git_list_uncommitted_files = async (
|
|
101
|
-
options?: SpawnOptions
|
|
102
|
-
): Promise<Array<string>> => {
|
|
103
|
-
const { stdout } = await spawn_out('git', ['diff', '--name-only', 'HEAD'], options);
|
|
104
|
-
if (!stdout) return [];
|
|
105
|
-
|
|
106
|
-
return stdout
|
|
107
|
-
.split('\n')
|
|
108
|
-
.map((f) => f.trim())
|
|
109
|
-
.filter(Boolean);
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
export const git_has_file_changed = async (
|
|
113
|
-
from_commit: string,
|
|
114
|
-
to_commit: string,
|
|
115
|
-
file_path: string,
|
|
116
|
-
options?: SpawnOptions
|
|
117
|
-
): Promise<boolean> => {
|
|
118
|
-
const { stdout } = await spawn_out(
|
|
34
|
+
const { result, stderr } = await spawn_out(
|
|
119
35
|
'git',
|
|
120
|
-
['
|
|
36
|
+
['commit', '-m', message, '--', ...files],
|
|
121
37
|
options
|
|
122
38
|
);
|
|
123
|
-
return stdout ? stdout.trim().length > 0 : false;
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Stashes current changes and throws if anything goes wrong.
|
|
128
|
-
*/
|
|
129
|
-
export const git_stash = async (message?: string, options?: SpawnOptions): Promise<void> => {
|
|
130
|
-
const args = message ? ['stash', 'push', '-m', message] : ['stash', 'push'];
|
|
131
|
-
|
|
132
|
-
const { result, stderr } = await spawn_out('git', args, options);
|
|
133
39
|
if (!result.ok) {
|
|
134
40
|
throw Error(
|
|
135
|
-
`
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Applies stashed changes and throws if anything goes wrong.
|
|
142
|
-
*/
|
|
143
|
-
export const git_stash_pop = async (options?: SpawnOptions): Promise<void> => {
|
|
144
|
-
const { result, stderr } = await spawn_out('git', ['stash', 'pop'], options);
|
|
145
|
-
if (!result.ok) {
|
|
146
|
-
throw Error(
|
|
147
|
-
`git_stash_pop failed with ${spawn_result_to_message(result)}${stderr ? ': ' + stderr.trim() : ''}`
|
|
41
|
+
`git_commit failed with ${spawn_result_to_message(result)}${stderr ? ': ' + stderr.trim() : ''}`
|
|
148
42
|
);
|
|
149
43
|
}
|
|
150
44
|
};
|
|
151
45
|
|
|
152
46
|
/**
|
|
153
|
-
*
|
|
47
|
+
* Wrapper for gro's `git_current_commit_hash` that reads `HEAD` and throws if null.
|
|
154
48
|
*/
|
|
155
|
-
export const
|
|
156
|
-
|
|
157
|
-
pull: boolean = true,
|
|
158
|
-
options?: SpawnOptions
|
|
159
|
-
): Promise<void> => {
|
|
160
|
-
// Check if workspace is clean first
|
|
161
|
-
const error = await gro_git_check_clean_workspace(options);
|
|
162
|
-
if (error) {
|
|
163
|
-
throw Error(`Cannot switch branch: ${error}`);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Checkout the branch
|
|
167
|
-
await gro_git_checkout(branch, options);
|
|
168
|
-
|
|
169
|
-
// Pull latest changes if requested
|
|
170
|
-
if (pull) {
|
|
171
|
-
await gro_git_pull(undefined, undefined, options);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Verify workspace is still clean
|
|
175
|
-
const error_after = await gro_git_check_clean_workspace(options);
|
|
176
|
-
if (error_after) {
|
|
177
|
-
throw Error(`Workspace unclean after switching to ${branch}: ${error_after}`);
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Wrapper for gro's `git_current_branch_name` that throws if null.
|
|
183
|
-
*/
|
|
184
|
-
export const git_current_branch_name_required = async (options?: SpawnOptions): Promise<string> => {
|
|
185
|
-
const branch = await gro_git_current_branch_name(options);
|
|
186
|
-
if (!branch) {
|
|
187
|
-
throw new Error('Failed to get current branch name');
|
|
188
|
-
}
|
|
189
|
-
return branch;
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Wrapper for gro's `git_current_commit_hash` that throws if null.
|
|
194
|
-
*/
|
|
195
|
-
export const git_current_commit_hash_required = async (
|
|
196
|
-
branch?: string,
|
|
197
|
-
options?: SpawnOptions
|
|
198
|
-
): Promise<string> => {
|
|
199
|
-
const hash = await gro_git_current_commit_hash(branch, options);
|
|
49
|
+
export const git_current_commit_hash_required = async (options?: SpawnOptions): Promise<string> => {
|
|
50
|
+
const hash = await gro_git_current_commit_hash(undefined, options);
|
|
200
51
|
if (!hash) {
|
|
201
|
-
throw new Error(
|
|
52
|
+
throw new Error('Failed to get the current commit hash');
|
|
202
53
|
}
|
|
203
54
|
return hash;
|
|
204
55
|
};
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Wrapper for gro's `git_check_clean_workspace` that returns a boolean.
|
|
208
|
-
*/
|
|
209
|
-
export const git_check_clean_workspace_as_boolean = async (
|
|
210
|
-
options?: SpawnOptions
|
|
211
|
-
): Promise<boolean> => {
|
|
212
|
-
const error = await gro_git_check_clean_workspace(options);
|
|
213
|
-
return error === null;
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
export const git_has_remote = async (
|
|
217
|
-
remote: string = 'origin',
|
|
218
|
-
options?: SpawnOptions
|
|
219
|
-
): Promise<boolean> => {
|
|
220
|
-
const { stdout } = await spawn_out('git', ['remote'], options);
|
|
221
|
-
if (!stdout) return false;
|
|
222
|
-
const remotes = stdout
|
|
223
|
-
.split('\n')
|
|
224
|
-
.map((r) => r.trim())
|
|
225
|
-
.filter(Boolean);
|
|
226
|
-
return remotes.includes(remote);
|
|
227
|
-
};
|
package/src/lib/github.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { fetch_value, type FetchValueCache } from '@fuzdev/fuz_util/fetch.ts';
|
|
4
|
+
import type { Result } from '@fuzdev/fuz_util/result.ts';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
* Minimal interface for GitHub API calls
|
|
7
|
+
* Minimal interface for GitHub API calls, satisfied structurally by fuz_ui's `Library`.
|
|
7
8
|
*/
|
|
8
9
|
export interface GithubRepoInfo {
|
|
9
10
|
owner_name: string | null;
|
|
@@ -35,9 +36,10 @@ export const fetch_github_pull_requests = async (
|
|
|
35
36
|
log?: Logger;
|
|
36
37
|
token?: string;
|
|
37
38
|
api_version?: string;
|
|
39
|
+
fetch?: typeof globalThis.fetch;
|
|
38
40
|
} = {}
|
|
39
41
|
): Promise<GithubPullRequests | null> => {
|
|
40
|
-
const { cache, log, token, api_version } = options;
|
|
42
|
+
const { cache, log, token, api_version, fetch } = options;
|
|
41
43
|
if (!repo_info.owner_name) throw Error('owner_name is required');
|
|
42
44
|
const headers = api_version ? new Headers({ 'x-github-api-version': api_version }) : undefined;
|
|
43
45
|
const url = `https://api.github.com/repos/${repo_info.owner_name}/${repo_info.repo_name}/pulls`;
|
|
@@ -46,7 +48,8 @@ export const fetch_github_pull_requests = async (
|
|
|
46
48
|
parse: GithubPullRequests.parse,
|
|
47
49
|
token,
|
|
48
50
|
cache,
|
|
49
|
-
log
|
|
51
|
+
log,
|
|
52
|
+
fetch
|
|
50
53
|
});
|
|
51
54
|
if (!fetched.ok) {
|
|
52
55
|
// TODO @many this is messy but I think it's the main case we need to worry about?
|
|
@@ -77,6 +80,13 @@ export const GithubCheckRuns = z.object({
|
|
|
77
80
|
export type GithubCheckRuns = z.infer<typeof GithubCheckRuns>;
|
|
78
81
|
|
|
79
82
|
/**
|
|
83
|
+
* Fetches the check runs on `ref` and reduces them to one overall status and
|
|
84
|
+
* conclusion.
|
|
85
|
+
*
|
|
86
|
+
* @param repo_info - the repo's GitHub owner and name
|
|
87
|
+
* @param options - the cache, logger, token, API version, ref (`main` by default), and `fetch`
|
|
88
|
+
* @returns the reduced check runs, `null` for a ref with none (a repo without CI, or a commit CI skipped), or a failure when the request or its parse failed
|
|
89
|
+
* @throws {Error} on a 401 response (check `SECRET_GITHUB_API_TOKEN`) or a repo without an owner
|
|
80
90
|
* @see https://docs.github.com/en/rest/checks/runs?apiVersion=2022-11-28#list-check-runs-for-a-git-reference
|
|
81
91
|
*/
|
|
82
92
|
export const fetch_github_check_runs = async (
|
|
@@ -87,9 +97,10 @@ export const fetch_github_check_runs = async (
|
|
|
87
97
|
token?: string;
|
|
88
98
|
api_version?: string;
|
|
89
99
|
ref?: string;
|
|
100
|
+
fetch?: typeof globalThis.fetch;
|
|
90
101
|
} = {}
|
|
91
|
-
): Promise<GithubCheckRunsItem | null
|
|
92
|
-
const { cache, log, token, api_version, ref = 'main' } = options;
|
|
102
|
+
): Promise<Result<{ value: GithubCheckRunsItem | null }, { status: number; message: string }>> => {
|
|
103
|
+
const { cache, log, token, api_version, ref = 'main', fetch } = options;
|
|
93
104
|
if (!repo_info.owner_name) throw Error('owner_name is required');
|
|
94
105
|
const headers = api_version ? new Headers({ 'x-github-api-version': api_version }) : undefined;
|
|
95
106
|
const url = `https://api.github.com/repos/${repo_info.owner_name}/${repo_info.repo_name}/commits/${ref}/check-runs`;
|
|
@@ -98,16 +109,17 @@ export const fetch_github_check_runs = async (
|
|
|
98
109
|
parse: (v) => reduce_check_runs(GithubCheckRuns.parse(v).check_runs),
|
|
99
110
|
token,
|
|
100
111
|
cache,
|
|
101
|
-
log
|
|
112
|
+
log,
|
|
113
|
+
fetch
|
|
102
114
|
});
|
|
103
115
|
if (!fetched.ok) {
|
|
104
116
|
// TODO @many this is messy but I think it's the main case we need to worry about?
|
|
105
117
|
if (fetched.status === 401) {
|
|
106
118
|
throw Error('401 response fetching github CI status - check your SECRET_GITHUB_API_TOKEN');
|
|
107
119
|
}
|
|
108
|
-
return
|
|
120
|
+
return { ok: false, status: fetched.status, message: fetched.message };
|
|
109
121
|
}
|
|
110
|
-
return fetched.value;
|
|
122
|
+
return { ok: true, value: fetched.value };
|
|
111
123
|
};
|
|
112
124
|
|
|
113
125
|
const reduce_check_runs = (check_runs: Array<GithubCheckRunsItem>): GithubCheckRunsItem | null => {
|
|
@@ -14,18 +14,11 @@ export const to_pull_requests = (
|
|
|
14
14
|
repos: Array<Repo>,
|
|
15
15
|
filter_pull_request?: FilterPullRequest
|
|
16
16
|
): Array<PullRequestMeta> =>
|
|
17
|
-
repos
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
repo.package_json.homepage &&
|
|
23
|
-
(!filter_pull_request || filter_pull_request(pull_request, repo))
|
|
24
|
-
? { repo, pull_request }
|
|
25
|
-
: null
|
|
26
|
-
);
|
|
27
|
-
})
|
|
28
|
-
.filter((v) => v !== null);
|
|
17
|
+
repos.flatMap((repo) =>
|
|
18
|
+
(repo.pull_requests ?? [])
|
|
19
|
+
.filter((pull_request) => !filter_pull_request || filter_pull_request(pull_request, repo))
|
|
20
|
+
.map((pull_request) => ({ repo, pull_request }))
|
|
21
|
+
);
|
|
29
22
|
|
|
30
23
|
export const to_pull_url = (repo_url: string, pull: GithubPullRequest): string =>
|
|
31
24
|
ensure_end(repo_url, '/') + 'pull/' + pull.number;
|
|
@@ -3,16 +3,25 @@ import { z } from 'zod';
|
|
|
3
3
|
import { styleText as st } from 'node:util';
|
|
4
4
|
import type { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
import {
|
|
7
|
+
get_gitops_ready,
|
|
8
|
+
log_readiness_block,
|
|
9
|
+
type ResolveGitopsReposOptions
|
|
10
|
+
} from './gitops_task_helpers.ts';
|
|
11
|
+
import type { DependencyAnalysis, DependencyGraph } from './dependency_graph.ts';
|
|
12
|
+
import { repo_is_npm, type LocalRepo } from './local_repo.ts';
|
|
13
|
+
import { analyze_repos } from './graph_validation.ts';
|
|
10
14
|
import {
|
|
11
15
|
format_wildcard_dependencies,
|
|
12
16
|
format_dev_cycles,
|
|
13
17
|
format_production_cycles
|
|
14
18
|
} from './log_helpers.ts';
|
|
15
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
format_and_output,
|
|
21
|
+
output_is_machine,
|
|
22
|
+
route_human_output,
|
|
23
|
+
type OutputFormatters
|
|
24
|
+
} from './output_helpers.ts';
|
|
16
25
|
import { GITOPS_CONFIG_PATH_DEFAULT } from './gitops_constants.ts';
|
|
17
26
|
|
|
18
27
|
/** @nodocs */
|
|
@@ -21,22 +30,18 @@ export const Args = z.strictObject({
|
|
|
21
30
|
.string()
|
|
22
31
|
.meta({ description: 'path to the gitops config file, absolute or relative to the cwd' })
|
|
23
32
|
.default(GITOPS_CONFIG_PATH_DEFAULT),
|
|
24
|
-
|
|
33
|
+
registry: z
|
|
25
34
|
.string()
|
|
26
|
-
.meta({
|
|
35
|
+
.meta({
|
|
36
|
+
description:
|
|
37
|
+
'path to the repos.toml registry, when `repos` would not find it walking up from the cwd'
|
|
38
|
+
})
|
|
27
39
|
.optional(),
|
|
28
40
|
format: z
|
|
29
41
|
.enum(['stdout', 'json', 'markdown'])
|
|
30
42
|
.meta({ description: 'output format' })
|
|
31
43
|
.default('stdout'),
|
|
32
|
-
outfile: z.string().meta({ description: 'write output to file instead of logging' }).optional()
|
|
33
|
-
sync: z
|
|
34
|
-
.boolean()
|
|
35
|
-
.meta({
|
|
36
|
-
description:
|
|
37
|
-
'sync repos (switch branch, pull, install) before analyzing instead of reading the working tree as-is'
|
|
38
|
-
})
|
|
39
|
-
.default(false)
|
|
44
|
+
outfile: z.string().meta({ description: 'write output to file instead of logging' }).optional()
|
|
40
45
|
});
|
|
41
46
|
export type Args = z.infer<typeof Args>;
|
|
42
47
|
|
|
@@ -45,36 +50,69 @@ export const task: Task<Args> = {
|
|
|
45
50
|
Args,
|
|
46
51
|
summary: 'analyze dependency structure and relationships across repos',
|
|
47
52
|
run: async ({ args, log }) => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const { local_repos } = await get_gitops_ready({ config, dir, download: false, sync, log });
|
|
52
|
-
|
|
53
|
-
// Only npm packages form the dependency graph; note any non-npm repos (e.g. cargo)
|
|
54
|
-
// that are excluded so the omission isn't silent.
|
|
55
|
-
const non_npm_repos = local_repos.filter((r) => !repo_is_npm(r));
|
|
56
|
-
if (non_npm_repos.length > 0) {
|
|
57
|
-
log.info(
|
|
58
|
-
st(
|
|
59
|
-
'dim',
|
|
60
|
-
`excluding ${non_npm_repos.length} non-npm repo(s) from analysis (dashboard-only): ` +
|
|
61
|
-
non_npm_repos.map((r) => r.library.name).join(', ')
|
|
62
|
-
)
|
|
63
|
-
);
|
|
64
|
-
}
|
|
53
|
+
await run_gitops_analyze(args, log);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
65
56
|
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
/**
|
|
58
|
+
* The side effects `run_gitops_analyze` reaches through, injectable for tests.
|
|
59
|
+
*
|
|
60
|
+
* @nodocs
|
|
61
|
+
*/
|
|
62
|
+
export interface GitopsAnalyzeDeps {
|
|
63
|
+
/** Loads the configured repos as they sit (`get_gitops_ready`). */
|
|
64
|
+
load_repos: (options: ResolveGitopsReposOptions) => Promise<{ local_repos: Array<LocalRepo> }>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const default_gitops_analyze_deps: GitopsAnalyzeDeps = {
|
|
68
|
+
load_repos: get_gitops_ready
|
|
69
|
+
};
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Runs `gro gitops_analyze`: loads the repos as they sit, logs the readiness
|
|
73
|
+
* block, and outputs the dependency analysis. Under `--format json` or
|
|
74
|
+
* `markdown` without `--outfile`, the log goes to stderr and stdout carries
|
|
75
|
+
* the document alone (`route_human_output`).
|
|
76
|
+
*
|
|
77
|
+
* @nodocs
|
|
78
|
+
*/
|
|
79
|
+
export const run_gitops_analyze = async (
|
|
80
|
+
args: Args,
|
|
81
|
+
log: Logger,
|
|
82
|
+
deps: Partial<GitopsAnalyzeDeps> = {}
|
|
83
|
+
): Promise<void> => {
|
|
84
|
+
const { load_repos } = { ...default_gitops_analyze_deps, ...deps };
|
|
85
|
+
const { config, registry, format, outfile } = args;
|
|
86
|
+
const write_stdout = route_human_output(log, output_is_machine(format, outfile));
|
|
75
87
|
|
|
76
|
-
|
|
88
|
+
// Read the working trees as they sit, and say which aren't at rest
|
|
89
|
+
const { local_repos } = await load_repos({ config, registry, log });
|
|
90
|
+
log_readiness_block(local_repos, log);
|
|
91
|
+
|
|
92
|
+
// Only npm packages form the dependency graph; note any non-npm repos (e.g. cargo)
|
|
93
|
+
// that are excluded so the omission isn't silent.
|
|
94
|
+
const non_npm_repos = local_repos.filter((r) => !repo_is_npm(r));
|
|
95
|
+
if (non_npm_repos.length > 0) {
|
|
96
|
+
log.info(
|
|
97
|
+
st(
|
|
98
|
+
'dim',
|
|
99
|
+
`excluding ${non_npm_repos.length} non-npm repo(s) from analysis (dashboard-only): ` +
|
|
100
|
+
non_npm_repos.map((r) => r.library.name).join(', ')
|
|
101
|
+
)
|
|
102
|
+
);
|
|
77
103
|
}
|
|
104
|
+
|
|
105
|
+
// Build the dependency graph and analyze cycles/wildcards (tolerating cycles)
|
|
106
|
+
const { graph, analysis, publishing_order } = analyze_repos(local_repos);
|
|
107
|
+
|
|
108
|
+
// Format and output using output_helpers
|
|
109
|
+
const data = {
|
|
110
|
+
graph,
|
|
111
|
+
analysis,
|
|
112
|
+
publishing_order
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
await format_and_output(data, create_formatters(), { format, outfile, log, write_stdout });
|
|
78
116
|
};
|
|
79
117
|
|
|
80
118
|
// Data type for analysis output
|