@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,42 +1,47 @@
|
|
|
1
|
-
import type {Task} from '@fuzdev/gro';
|
|
2
|
-
import {z} from 'zod';
|
|
3
|
-
import {styleText as st} from 'node:util';
|
|
4
|
-
import type {Logger} from '@fuzdev/fuz_util/log.ts';
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import type { Task } from '@fuzdev/gro';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { styleText as st } from 'node:util';
|
|
4
|
+
import type { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
5
|
+
|
|
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
|
-
format_production_cycles
|
|
17
|
+
format_production_cycles
|
|
14
18
|
} from './log_helpers.ts';
|
|
15
|
-
import {
|
|
16
|
-
|
|
19
|
+
import {
|
|
20
|
+
format_and_output,
|
|
21
|
+
output_is_machine,
|
|
22
|
+
route_human_output,
|
|
23
|
+
type OutputFormatters
|
|
24
|
+
} from './output_helpers.ts';
|
|
25
|
+
import { GITOPS_CONFIG_PATH_DEFAULT } from './gitops_constants.ts';
|
|
17
26
|
|
|
18
27
|
/** @nodocs */
|
|
19
28
|
export const Args = z.strictObject({
|
|
20
29
|
config: z
|
|
21
30
|
.string()
|
|
22
|
-
.meta({description: 'path to the gitops config file, absolute or relative to the cwd'})
|
|
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
|
-
.meta({description: 'output format'})
|
|
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
|
|
|
@@ -44,37 +49,70 @@ export type Args = z.infer<typeof Args>;
|
|
|
44
49
|
export const task: Task<Args> = {
|
|
45
50
|
Args,
|
|
46
51
|
summary: 'analyze dependency structure and relationships across repos',
|
|
47
|
-
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
|
-
}
|
|
52
|
+
run: async ({ args, log }) => {
|
|
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
|
+
}
|
|
68
66
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
analysis,
|
|
73
|
-
publishing_order,
|
|
74
|
-
};
|
|
67
|
+
const default_gitops_analyze_deps: GitopsAnalyzeDeps = {
|
|
68
|
+
load_repos: get_gitops_ready
|
|
69
|
+
};
|
|
75
70
|
|
|
76
|
-
|
|
77
|
-
|
|
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));
|
|
87
|
+
|
|
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
|
+
);
|
|
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
|
|
@@ -88,32 +126,32 @@ interface AnalysisData {
|
|
|
88
126
|
const create_formatters = (): OutputFormatters<AnalysisData> => ({
|
|
89
127
|
json: (data) => format_json(data.graph, data.analysis, data.publishing_order),
|
|
90
128
|
markdown: (data) => format_markdown(data.graph, data.analysis, data.publishing_order),
|
|
91
|
-
stdout: (data, log) => format_stdout(data.graph, data.analysis, data.publishing_order, log)
|
|
129
|
+
stdout: (data, log) => format_stdout(data.graph, data.analysis, data.publishing_order, log)
|
|
92
130
|
});
|
|
93
131
|
|
|
94
132
|
// Helper to calculate common statistics
|
|
95
133
|
const calculate_stats = (graph: DependencyGraph) => {
|
|
96
134
|
const total_deps = Array.from(graph.nodes.values()).reduce(
|
|
97
135
|
(sum, node) => sum + node.dependencies.size,
|
|
98
|
-
0
|
|
136
|
+
0
|
|
99
137
|
);
|
|
100
138
|
const internal_deps = Array.from(graph.nodes.values()).reduce(
|
|
101
139
|
(sum, node) =>
|
|
102
140
|
sum + Array.from(node.dependencies.keys()).filter((name) => graph.nodes.has(name)).length,
|
|
103
|
-
0
|
|
141
|
+
0
|
|
104
142
|
);
|
|
105
|
-
return {total_deps, internal_deps};
|
|
143
|
+
return { total_deps, internal_deps };
|
|
106
144
|
};
|
|
107
145
|
|
|
108
146
|
const format_json = (
|
|
109
147
|
graph: DependencyGraph,
|
|
110
148
|
analysis: DependencyAnalysis,
|
|
111
|
-
publishing_order: Array<string> | null
|
|
149
|
+
publishing_order: Array<string> | null
|
|
112
150
|
): string => {
|
|
113
151
|
const output = {
|
|
114
152
|
graph: graph.toJSON(),
|
|
115
153
|
analysis,
|
|
116
|
-
publishing_order
|
|
154
|
+
publishing_order
|
|
117
155
|
};
|
|
118
156
|
return JSON.stringify(output, null, 2);
|
|
119
157
|
};
|
|
@@ -121,12 +159,12 @@ const format_json = (
|
|
|
121
159
|
const format_markdown = (
|
|
122
160
|
graph: DependencyGraph,
|
|
123
161
|
analysis: DependencyAnalysis,
|
|
124
|
-
publishing_order: Array<string> | null
|
|
162
|
+
publishing_order: Array<string> | null
|
|
125
163
|
): Array<string> => {
|
|
126
164
|
const lines: Array<string> = ['# Dependency Analysis'];
|
|
127
165
|
|
|
128
166
|
// Summary stats
|
|
129
|
-
const {total_deps, internal_deps} = calculate_stats(graph);
|
|
167
|
+
const { total_deps, internal_deps } = calculate_stats(graph);
|
|
130
168
|
|
|
131
169
|
lines.push('', '## Summary', '');
|
|
132
170
|
lines.push(`- **Total packages**: ${graph.nodes.size}`);
|
|
@@ -170,7 +208,7 @@ const format_markdown = (
|
|
|
170
208
|
lines.push('', '## ⚠️ Wildcard Dependencies', '');
|
|
171
209
|
lines.push('| Package | Dependency | Version |');
|
|
172
210
|
lines.push('|---------|------------|---------|');
|
|
173
|
-
for (const {pkg, dep, version} of analysis.wildcard_deps) {
|
|
211
|
+
for (const { pkg, dep, version } of analysis.wildcard_deps) {
|
|
174
212
|
lines.push(`| \`${pkg}\` | \`${dep}\` | \`${version}\` |`);
|
|
175
213
|
}
|
|
176
214
|
}
|
|
@@ -179,7 +217,7 @@ const format_markdown = (
|
|
|
179
217
|
lines.push('', '## Internal Dependencies', '');
|
|
180
218
|
for (const node of graph.nodes.values()) {
|
|
181
219
|
const internal_deps = Array.from(node.dependencies.entries()).filter(([name]) =>
|
|
182
|
-
graph.nodes.has(name)
|
|
220
|
+
graph.nodes.has(name)
|
|
183
221
|
);
|
|
184
222
|
if (internal_deps.length > 0) {
|
|
185
223
|
lines.push(`- **${node.name}**`);
|
|
@@ -197,7 +235,7 @@ const format_stdout = (
|
|
|
197
235
|
graph: DependencyGraph,
|
|
198
236
|
analysis: DependencyAnalysis,
|
|
199
237
|
publishing_order: Array<string> | null,
|
|
200
|
-
log: Logger
|
|
238
|
+
log: Logger
|
|
201
239
|
): void => {
|
|
202
240
|
log.info(st('cyan', `📊 Analyzing ${graph.nodes.size} repositories...`));
|
|
203
241
|
|
|
@@ -216,14 +254,14 @@ const format_stdout = (
|
|
|
216
254
|
log.info(st('yellow', 'Dependency relationships:'));
|
|
217
255
|
for (const node of graph.nodes.values()) {
|
|
218
256
|
const internal_deps = Array.from(node.dependencies.entries()).filter(([name]) =>
|
|
219
|
-
graph.nodes.has(name)
|
|
257
|
+
graph.nodes.has(name)
|
|
220
258
|
);
|
|
221
259
|
if (internal_deps.length > 0) {
|
|
222
260
|
log.info(` ${st('cyan', node.name)}`);
|
|
223
261
|
for (const [dep_name, spec] of internal_deps) {
|
|
224
262
|
const type_color = spec.type === 'peer' ? 'magenta' : spec.type === 'dev' ? 'dim' : 'white';
|
|
225
263
|
log.info(
|
|
226
|
-
` ${st(type_color, '→')} ${dep_name} ${st('dim', `(${spec.type}: ${spec.version})`)}
|
|
264
|
+
` ${st(type_color, '→')} ${dep_name} ${st('dim', `(${spec.type}: ${spec.version})`)}`
|
|
227
265
|
);
|
|
228
266
|
}
|
|
229
267
|
}
|
|
@@ -252,7 +290,7 @@ const format_stdout = (
|
|
|
252
290
|
}
|
|
253
291
|
|
|
254
292
|
// Summary
|
|
255
|
-
const {total_deps, internal_deps} = calculate_stats(graph);
|
|
293
|
+
const { total_deps, internal_deps } = calculate_stats(graph);
|
|
256
294
|
|
|
257
295
|
log.info('');
|
|
258
296
|
log.info(st('cyan', 'Summary:'));
|
package/src/lib/gitops_config.ts
CHANGED
|
@@ -1,174 +1,125 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The gitops config: which repos a project's gitops tasks work over, as a flat
|
|
3
|
+
* list of `repos.toml` registry keys.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* This allows users to provide minimal configs (e.g., just URLs as strings) while
|
|
9
|
-
* the system works with normalized configs internally for type safety.
|
|
5
|
+
* Every other fact about a repo — its dir, URL, branch, visibility, `ci`, and
|
|
6
|
+
* `archived` — comes from the registry, through `repos status --json`, so the
|
|
7
|
+
* config declares nothing that could drift from it.
|
|
10
8
|
*
|
|
11
9
|
* @module
|
|
12
10
|
*/
|
|
13
11
|
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
import {strip_end} from '@fuzdev/fuz_util/string.ts';
|
|
17
|
-
import type {GitBranch} from '@fuzdev/fuz_util/git.ts';
|
|
12
|
+
import { existsSync } from 'node:fs';
|
|
13
|
+
import { z } from 'zod';
|
|
18
14
|
|
|
19
|
-
import {
|
|
15
|
+
import type { ReposEntryStatus } from './repos_status.ts';
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
/** A project's gitops config. */
|
|
18
|
+
export const GitopsConfig = z.strictObject({
|
|
19
|
+
repos: z
|
|
20
|
+
.array(z.string().min(1))
|
|
21
|
+
.meta({ description: 'repos.toml registry keys of owned repos, in the order tasks list them' })
|
|
22
|
+
});
|
|
23
|
+
export type GitopsConfig = z.infer<typeof GitopsConfig>;
|
|
25
24
|
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
-
) => RawGitopsConfig | Promise<RawGitopsConfig>;
|
|
25
|
+
/** A config module's default export in function form. */
|
|
26
|
+
export type CreateGitopsConfig = () => GitopsConfig | Promise<GitopsConfig>;
|
|
29
27
|
|
|
30
|
-
export interface
|
|
31
|
-
|
|
32
|
-
repos_dir?: string;
|
|
28
|
+
export interface GitopsConfigModule {
|
|
29
|
+
readonly default: GitopsConfig | CreateGitopsConfig;
|
|
33
30
|
}
|
|
34
31
|
|
|
35
32
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* `
|
|
33
|
+
* The private repos a public host package must not publish. `gitops_sync` writes
|
|
34
|
+
* every configured repo's GitHub metadata into the host project's generated
|
|
35
|
+
* `repos.json` — a public site's data when the host package is public — so a
|
|
36
|
+
* private repo in that config would leak. Empty when the host is private.
|
|
37
|
+
*
|
|
38
|
+
* @param entries - the configured repos' registry entries
|
|
39
|
+
* @param host_is_private - whether the host `package.json` sets `private: true`
|
|
39
40
|
*/
|
|
40
|
-
export
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
* @example
|
|
46
|
-
* ```ts
|
|
47
|
-
* 'https://github.com/fuzdev/fuz_ui'
|
|
48
|
-
* ```
|
|
49
|
-
*/
|
|
50
|
-
repo_url: Url;
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Relative or absolute path to the repo's local directory.
|
|
54
|
-
* If `null`, the directory is inferred from the URL and cwd.
|
|
55
|
-
* @example
|
|
56
|
-
* ```ts
|
|
57
|
-
* 'relative/path/to/repo'
|
|
58
|
-
* ```
|
|
59
|
-
* @example
|
|
60
|
-
* ```ts
|
|
61
|
-
* '/absolute/path/to/repo'
|
|
62
|
-
* ```
|
|
63
|
-
*/
|
|
64
|
-
repo_dir: string | null;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* The branch name to use when fetching the repo. Defaults to `main`.
|
|
68
|
-
*/
|
|
69
|
-
branch: GitBranch;
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Visibility of the repo on its host. Defaults to `'public'`.
|
|
73
|
-
*/
|
|
74
|
-
visibility: GitopsRepoVisibility;
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Whether the repo runs CI. Defaults to `true` for public repos and `false`
|
|
78
|
-
* for private repos, unless set explicitly.
|
|
79
|
-
*/
|
|
80
|
-
ci: boolean;
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Whether the repo is archived (read-only) on its host. Defaults to `false`.
|
|
84
|
-
*/
|
|
85
|
-
archived: boolean;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export interface RawGitopsRepoConfig {
|
|
89
|
-
repo_url: Url;
|
|
90
|
-
repo_dir?: string | null;
|
|
91
|
-
branch?: GitBranch;
|
|
92
|
-
/** Visibility of the repo on its host. Defaults to `'public'`. */
|
|
93
|
-
visibility?: GitopsRepoVisibility;
|
|
94
|
-
/** Whether the repo runs CI. Defaults to `true` for public, `false` for private. */
|
|
95
|
-
ci?: boolean;
|
|
96
|
-
/** Whether the repo is archived (read-only) on its host. Defaults to `false`. */
|
|
97
|
-
archived?: boolean;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export const create_empty_gitops_config = (): GitopsConfig => ({
|
|
101
|
-
repos: [],
|
|
102
|
-
repos_dir: DEFAULT_REPOS_DIR,
|
|
103
|
-
});
|
|
41
|
+
export const gitops_config_leaked_private_repos = (
|
|
42
|
+
entries: ReadonlyArray<ReposEntryStatus>,
|
|
43
|
+
host_is_private: boolean
|
|
44
|
+
): Array<ReposEntryStatus> =>
|
|
45
|
+
host_is_private ? [] : entries.filter((e) => e.visibility === 'private');
|
|
104
46
|
|
|
105
47
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
48
|
+
* Loads a gitops config module and validates it.
|
|
49
|
+
*
|
|
50
|
+
* @returns the config, or `null` when no file exists at `config_path`
|
|
51
|
+
* @throws {Error} if the module's default export isn't a valid config
|
|
108
52
|
*/
|
|
109
|
-
export const normalize_gitops_config = (raw_config: RawGitopsConfig): GitopsConfig => {
|
|
110
|
-
const empty_config = create_empty_gitops_config();
|
|
111
|
-
// All of the raw config properties are optional,
|
|
112
|
-
// so fall back to the empty values when `undefined`.
|
|
113
|
-
const {repos, repos_dir} = raw_config;
|
|
114
|
-
return {
|
|
115
|
-
repos: repos ? repos.map((r) => parse_fuz_repo_config(r)) : empty_config.repos,
|
|
116
|
-
// Default to two dirs up from config if not specified
|
|
117
|
-
repos_dir: repos_dir ?? DEFAULT_REPOS_DIR,
|
|
118
|
-
};
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const parse_fuz_repo_config = (r: Url | RawGitopsRepoConfig): GitopsRepoConfig => {
|
|
122
|
-
if (typeof r === 'string') {
|
|
123
|
-
return {
|
|
124
|
-
repo_url: r,
|
|
125
|
-
repo_dir: null,
|
|
126
|
-
branch: 'main' as GitBranch, // TODO @zts use flavored for GitBranch
|
|
127
|
-
visibility: 'public',
|
|
128
|
-
ci: true,
|
|
129
|
-
archived: false,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
const visibility = r.visibility ?? 'public';
|
|
133
|
-
return {
|
|
134
|
-
repo_url: strip_end(r.repo_url, '.git'),
|
|
135
|
-
repo_dir: r.repo_dir ?? null,
|
|
136
|
-
branch: r.branch ?? ('main' as GitBranch), // TODO @zts use flavored for GitBranch
|
|
137
|
-
visibility,
|
|
138
|
-
ci: r.ci ?? visibility === 'public',
|
|
139
|
-
archived: r.archived ?? false,
|
|
140
|
-
};
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
export interface GitopsConfigModule {
|
|
144
|
-
readonly default: RawGitopsConfig | CreateGitopsConfig;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
53
|
export const load_gitops_config = async (config_path: string): Promise<GitopsConfig | null> => {
|
|
148
54
|
if (!existsSync(config_path)) {
|
|
149
55
|
// No user config file found.
|
|
150
56
|
return null;
|
|
151
57
|
}
|
|
152
|
-
// Import the user's `gitops.config.ts`.
|
|
153
|
-
|
|
58
|
+
// Import the user's `gitops.config.ts`. An import or call failure keeps its
|
|
59
|
+
// stack, which carries the config's file and line; validation errors don't need one.
|
|
60
|
+
let config_module: unknown;
|
|
61
|
+
try {
|
|
62
|
+
config_module = await import(config_path);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
throw Error(`Failed to import the gitops config at ${config_path}:\n${error_with_stack(err)}`);
|
|
65
|
+
}
|
|
154
66
|
validate_gitops_config_module(config_module, config_path);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
67
|
+
let raw: unknown;
|
|
68
|
+
try {
|
|
69
|
+
raw =
|
|
70
|
+
typeof config_module.default === 'function'
|
|
71
|
+
? await config_module.default()
|
|
72
|
+
: config_module.default;
|
|
73
|
+
} catch (err) {
|
|
74
|
+
throw Error(`The gitops config at ${config_path} threw:\n${error_with_stack(err)}`);
|
|
75
|
+
}
|
|
76
|
+
return parse_gitops_config(raw, config_path);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const error_with_stack = (err: unknown): string =>
|
|
80
|
+
err instanceof Error ? (err.stack ?? err.message) : String(err);
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Validates a loaded config value: registry keys, each listed once.
|
|
84
|
+
*
|
|
85
|
+
* @throws {Error} naming the config and what's wrong with it
|
|
86
|
+
*/
|
|
87
|
+
export const parse_gitops_config = (raw: unknown, config_path: string): GitopsConfig => {
|
|
88
|
+
const parsed = GitopsConfig.safeParse(raw);
|
|
89
|
+
if (!parsed.success) {
|
|
90
|
+
const hint =
|
|
91
|
+
"\n `repos` lists repos.toml registry keys; a repo's url, dir, branch, visibility, ci, and archived come from the registry";
|
|
92
|
+
throw Error(
|
|
93
|
+
`Invalid gitops config at ${config_path}:\n${z.prettifyError(parsed.error)}${hint}`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const config = parsed.data;
|
|
97
|
+
const problems: Array<string> = [];
|
|
98
|
+
const seen: Set<string> = new Set();
|
|
99
|
+
for (const key of config.repos) {
|
|
100
|
+
if (key.includes('/')) {
|
|
101
|
+
problems.push(`\`${key}\` isn't a registry key — list repos by their repos.toml key`);
|
|
102
|
+
} else if (seen.has(key)) {
|
|
103
|
+
problems.push(`\`${key}\` is listed more than once`);
|
|
104
|
+
}
|
|
105
|
+
seen.add(key);
|
|
106
|
+
}
|
|
107
|
+
if (problems.length) {
|
|
108
|
+
throw Error(`Invalid gitops config at ${config_path}:\n ${problems.join('\n ')}`);
|
|
109
|
+
}
|
|
110
|
+
return config;
|
|
160
111
|
};
|
|
161
112
|
|
|
162
113
|
export const validate_gitops_config_module: (
|
|
163
114
|
config_module: any,
|
|
164
|
-
config_path: string
|
|
115
|
+
config_path: string
|
|
165
116
|
) => asserts config_module is GitopsConfigModule = (config_module, config_path) => {
|
|
166
117
|
const config = config_module.default;
|
|
167
118
|
if (!config) {
|
|
168
|
-
throw Error(`Invalid
|
|
119
|
+
throw Error(`Invalid gitops config module at ${config_path}: expected a default export`);
|
|
169
120
|
} else if (!(typeof config === 'function' || typeof config === 'object')) {
|
|
170
121
|
throw Error(
|
|
171
|
-
`Invalid
|
|
122
|
+
`Invalid gitops config module at ${config_path}: the default export must be a function or object`
|
|
172
123
|
);
|
|
173
124
|
}
|
|
174
125
|
};
|
|
@@ -7,11 +7,13 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Maximum number of
|
|
11
|
-
*
|
|
10
|
+
* Maximum number of fixed-point iterations plan generation runs to resolve
|
|
11
|
+
* transitive dependency cascades. Publishing executes the frozen plan in a
|
|
12
|
+
* single pass and doesn't iterate.
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
14
|
+
* Each iteration reaches at least one more level of dependents, so a deep dependency
|
|
15
|
+
* chain needs more; a plan that hits the limit still changing warns, naming
|
|
16
|
+
* the packages left.
|
|
15
17
|
*/
|
|
16
18
|
export const GITOPS_MAX_ITERATIONS_DEFAULT = 10;
|
|
17
19
|
|