@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
package/src/lib/local_repo.ts
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import type {
|
|
5
|
-
import {Library} from '@fuzdev/fuz_ui/library.svelte.ts';
|
|
6
|
-
import {existsSync} from 'node:fs';
|
|
7
|
-
import {join} from 'node:path';
|
|
8
|
-
import {TaskError} from '@fuzdev/gro';
|
|
9
|
-
import {library_load_from_repo} from '@fuzdev/gro/library_load.ts';
|
|
10
|
-
import type {Logger} from '@fuzdev/fuz_util/log.ts';
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
import
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import {GITOPS_CONCURRENCY_DEFAULT} from './gitops_constants.ts';
|
|
19
|
-
import {cargo_toml_load} from './cargo_toml.ts';
|
|
1
|
+
import { to_error_message } from '@fuzdev/fuz_util/error.ts';
|
|
2
|
+
import { library_json_from_modules, type LibraryJson } from '@fuzdev/fuz_util/library_json.ts';
|
|
3
|
+
import type { PackageJson } from '@fuzdev/fuz_util/package_json.ts';
|
|
4
|
+
import type { Result } from '@fuzdev/fuz_util/result.ts';
|
|
5
|
+
import { Library } from '@fuzdev/fuz_ui/library.svelte.ts';
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { TaskError } from '@fuzdev/gro';
|
|
9
|
+
import { library_load_from_repo } from '@fuzdev/gro/library_load.ts';
|
|
10
|
+
import type { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
11
|
+
import { map_concurrent_settled } from '@fuzdev/fuz_util/async.ts';
|
|
12
|
+
|
|
13
|
+
import { gitops_config_leaked_private_repos } from './gitops_config.ts';
|
|
14
|
+
import type { ReposEntryStatus, ReposStatusReport } from './repos_status.ts';
|
|
15
|
+
import { GITOPS_CONCURRENCY_DEFAULT } from './gitops_constants.ts';
|
|
16
|
+
import { cargo_toml_load } from './cargo_toml.ts';
|
|
17
|
+
import { to_repos_command } from './repos_status_load.ts';
|
|
20
18
|
|
|
21
19
|
/**
|
|
22
20
|
* Fully loaded local repo with `Library` and extracted dependency data.
|
|
@@ -35,211 +33,140 @@ export interface LocalRepo {
|
|
|
35
33
|
/** The repo's full `package.json` (with `dependencies`/`devDependencies`). */
|
|
36
34
|
package_json: PackageJson;
|
|
37
35
|
repo_dir: string;
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
/**
|
|
37
|
+
* The repo's registry entry as `repos status --json` reported it: its
|
|
38
|
+
* `branch`, `visibility`, `ci`, and `archived`, and its git state.
|
|
39
|
+
*/
|
|
40
|
+
entry: ReposEntryStatus;
|
|
40
41
|
dependencies?: Map<string, string>;
|
|
41
42
|
dev_dependencies?: Map<string, string>;
|
|
42
43
|
peer_dependencies?: Map<string, string>;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
/**
|
|
46
|
-
* A repo
|
|
47
|
-
*
|
|
47
|
+
* A configured repo resolved through `repos status`: present on disk, before
|
|
48
|
+
* its library is loaded. See `local_repos_resolve`.
|
|
48
49
|
*/
|
|
49
50
|
export interface LocalRepoPath {
|
|
50
|
-
|
|
51
|
-
repo_name: string;
|
|
51
|
+
/** The repo's registry key (for display/logging before `Library` is loaded). */
|
|
52
|
+
repo_name: string;
|
|
53
|
+
/** The workspace root joined with the entry's `dir`. */
|
|
52
54
|
repo_dir: string;
|
|
55
|
+
/** The registry's HTTPS URL for the repo. */
|
|
53
56
|
repo_url: string;
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
/** The repo's registry entry as `repos status --json` reported it. */
|
|
58
|
+
entry: ReposEntryStatus;
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
/**
|
|
59
|
-
*
|
|
62
|
+
* Resolves a gitops config's registry keys against a `repos status` report,
|
|
63
|
+
* in config order. Every key must name an owned repo that's present and was
|
|
64
|
+
* probed; each that doesn't is a problem, and any problem fails the whole
|
|
65
|
+
* resolve, naming them all.
|
|
66
|
+
*
|
|
67
|
+
* @param options.keys - the config's registry keys, in config order
|
|
68
|
+
* @param options.report - `repos status <keys…> --json`'s report
|
|
69
|
+
* @param options.host - the package whose generated data the run writes; when public, a private repo is a problem (`gitops_config_leaked_private_repos`)
|
|
70
|
+
* @param options.registry - the `--registry` the report was made with, repeated in the hints
|
|
71
|
+
* @returns the repos in config order, or a message listing every problem
|
|
60
72
|
*/
|
|
61
|
-
export
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
73
|
+
export const local_repos_resolve = (options: {
|
|
74
|
+
keys: ReadonlyArray<string>;
|
|
75
|
+
report: ReposStatusReport;
|
|
76
|
+
host?: { name: string; private: boolean };
|
|
77
|
+
registry?: string;
|
|
78
|
+
}): Result<{ value: Array<LocalRepoPath> }, { message: string; problems: Array<string> }> => {
|
|
79
|
+
const { keys, report, host, registry } = options;
|
|
80
|
+
const repos_command = to_repos_command(registry);
|
|
81
|
+
const by_key = new Map(report.entries.map((e) => [e.key, e] as const));
|
|
82
|
+
|
|
83
|
+
const problems: Array<string> = [];
|
|
84
|
+
const resolved: Array<LocalRepoPath> = [];
|
|
85
|
+
for (const key of keys) {
|
|
86
|
+
const entry = by_key.get(key);
|
|
87
|
+
if (!entry) {
|
|
88
|
+
// `repos` also takes a dir name or path as a target, reporting the entry by its key
|
|
89
|
+
const by_dir = report.entries.find((e) => e.dir === key);
|
|
90
|
+
problems.push(
|
|
91
|
+
by_dir
|
|
92
|
+
? `\`${key}\` is the dir of \`${by_dir.key}\`, not a registry key — list \`${by_dir.key}\``
|
|
93
|
+
: `\`${key}\` isn't in the \`repos status\` report — is it a registry key?`
|
|
94
|
+
);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const repo_dir = join(report.workspace, entry.dir);
|
|
98
|
+
if (entry.kind === 'reference') {
|
|
99
|
+
problems.push(`\`${key}\` is a third-party reference, not an owned repo`);
|
|
100
|
+
} else if (entry.presence.kind === 'missing') {
|
|
101
|
+
problems.push(
|
|
102
|
+
`\`${key}\` is missing at ${repo_dir} — \`${repos_command} sync ${key}\` clones it`
|
|
103
|
+
);
|
|
104
|
+
} else if (entry.presence.kind === 'not_a_repo') {
|
|
105
|
+
problems.push(`\`${key}\`: ${repo_dir} isn't a git repo`);
|
|
106
|
+
} else if (entry.probe_error !== null) {
|
|
107
|
+
problems.push(`\`${key}\`: probing ${repo_dir} failed: ${entry.probe_error.message}`);
|
|
108
|
+
} else {
|
|
109
|
+
resolved.push({ repo_name: key, repo_dir, repo_url: entry.url, entry });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (host) {
|
|
114
|
+
const configured = keys.map((k) => by_key.get(k)).filter((e) => e !== undefined);
|
|
115
|
+
for (const leaked of gitops_config_leaked_private_repos(configured, host.private)) {
|
|
116
|
+
problems.push(
|
|
117
|
+
`\`${leaked.key}\` is private, and ${host.name} is a public package whose generated repos.json would publish its metadata`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (problems.length) {
|
|
123
|
+
return {
|
|
124
|
+
ok: false,
|
|
125
|
+
message: `${problems.length === 1 ? 'a configured repo' : 'configured repos'} can't be loaded:\n ${problems.join('\n ')}`,
|
|
126
|
+
problems
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return { ok: true, value: resolved };
|
|
130
|
+
};
|
|
68
131
|
|
|
69
132
|
/**
|
|
70
|
-
* Loads
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* `gitops_analyze`/`gitops_plan`), the repo is loaded exactly as it sits on
|
|
74
|
-
* disk — no branch switch, pull, install, or clean-workspace check. This makes
|
|
75
|
-
* those commands safe to run on an active workspace with uncommitted changes or
|
|
76
|
-
* feature branches checked out.
|
|
133
|
+
* Loads a resolved repo as its working tree sits (the tasks read where each
|
|
134
|
+
* repo sits from `repos status`, and `repos sync` moves them). Moves no ref;
|
|
135
|
+
* gro caches the library at `.gro/library.json` in the repo, at a clean commit.
|
|
77
136
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* 1. Records current commit hash (for detecting changes)
|
|
81
|
-
* 2. Switches to target branch if needed (requires clean workspace unless `allow_dirty`)
|
|
82
|
-
* 3. Pulls latest changes from remote (skipped for local-only repos)
|
|
83
|
-
* 4. Validates workspace is clean after pull (skipped if `allow_dirty`)
|
|
84
|
-
* 5. Auto-installs dependencies if `package.json` changed
|
|
137
|
+
* 1. Loads `library_json` via `library_load_from_repo` (svelte-docinfo analysis)
|
|
138
|
+
* 2. Creates `Library` and extracts dependency maps
|
|
85
139
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* 7. Creates `Library` and extracts dependency maps
|
|
140
|
+
* A repo with no `package.json` but a Rust `Cargo.toml` loads as a
|
|
141
|
+
* dashboard-only `cargo` repo instead.
|
|
89
142
|
*
|
|
90
|
-
* @
|
|
91
|
-
* @param allow_dirty - when syncing, tolerate uncommitted changes instead of throwing (default `false`)
|
|
92
|
-
* @throws {TaskError} if syncing fails (dirty workspace, branch switch, install) or analysis fails
|
|
143
|
+
* @throws {TaskError} if the analysis fails
|
|
93
144
|
*/
|
|
94
145
|
export const local_repo_load = async ({
|
|
95
146
|
local_repo_path,
|
|
96
|
-
log
|
|
97
|
-
git_ops = default_git_operations,
|
|
98
|
-
npm_ops = default_npm_operations,
|
|
99
|
-
sync = true,
|
|
100
|
-
allow_dirty = false,
|
|
147
|
+
log
|
|
101
148
|
}: {
|
|
102
149
|
local_repo_path: LocalRepoPath;
|
|
103
150
|
log?: Logger;
|
|
104
|
-
git_ops?: GitOperations;
|
|
105
|
-
npm_ops?: NpmOperations;
|
|
106
|
-
sync?: boolean;
|
|
107
|
-
allow_dirty?: boolean;
|
|
108
151
|
}): Promise<LocalRepo> => {
|
|
109
|
-
const {
|
|
110
|
-
|
|
111
|
-
if (sync) {
|
|
112
|
-
// Record commit hash before any changes
|
|
113
|
-
const commit_before_result = await git_ops.current_commit_hash({cwd: repo_dir});
|
|
114
|
-
if (!commit_before_result.ok) {
|
|
115
|
-
throw new TaskError(
|
|
116
|
-
`Failed to get commit hash in ${repo_dir}: ${commit_before_result.message}`,
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
const commit_before = commit_before_result.value;
|
|
120
|
-
|
|
121
|
-
// Switch to target branch if needed
|
|
122
|
-
const branch_result = await git_ops.current_branch_name({cwd: repo_dir});
|
|
123
|
-
if (!branch_result.ok) {
|
|
124
|
-
throw new TaskError(`Failed to get current branch in ${repo_dir}: ${branch_result.message}`);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const switched_branches = branch_result.value !== repo_config.branch;
|
|
128
|
-
if (switched_branches) {
|
|
129
|
-
// Guard the switch on a clean workspace unless the caller opts into `allow_dirty`,
|
|
130
|
-
// in which case we let `git checkout` itself fail loudly if it can't proceed.
|
|
131
|
-
if (!allow_dirty) {
|
|
132
|
-
const clean_result = await git_ops.check_clean_workspace({cwd: repo_dir});
|
|
133
|
-
if (!clean_result.ok) {
|
|
134
|
-
throw new TaskError(`Failed to check workspace in ${repo_dir}: ${clean_result.message}`);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (!clean_result.value) {
|
|
138
|
-
throw new TaskError(
|
|
139
|
-
`Repo ${repo_dir} is not on branch "${repo_config.branch}" and the workspace is unclean, blocking switch`,
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const checkout_result = await git_ops.checkout({branch: repo_config.branch, cwd: repo_dir});
|
|
145
|
-
if (!checkout_result.ok) {
|
|
146
|
-
throw new TaskError(
|
|
147
|
-
`Failed to checkout branch "${repo_config.branch}" in ${repo_dir}: ${checkout_result.message}`,
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Only pull if remote exists (skip for local-only repos, test fixtures)
|
|
153
|
-
const origin_result = await git_ops.has_remote({remote: 'origin', cwd: repo_dir});
|
|
154
|
-
if (!origin_result.ok) {
|
|
155
|
-
throw new TaskError(`Failed to check for remote in ${repo_dir}: ${origin_result.message}`);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (origin_result.value) {
|
|
159
|
-
// Pull the configured branch explicitly. Without a branch, `git pull origin`
|
|
160
|
-
// targets the remote's default branch (origin/HEAD), which for a repo checked
|
|
161
|
-
// out on a non-default branch rebases the wrong branch onto it.
|
|
162
|
-
const pull_result = await git_ops.pull({branch: repo_config.branch, cwd: repo_dir});
|
|
163
|
-
if (!pull_result.ok) {
|
|
164
|
-
throw new TaskError(`Failed to pull in ${repo_dir}: ${pull_result.message}`);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Check clean workspace after pull to ensure we're in a good state
|
|
169
|
-
// (skipped when `allow_dirty`, since uncommitted changes are expected then)
|
|
170
|
-
if (!allow_dirty) {
|
|
171
|
-
const clean_after_result = await git_ops.check_clean_workspace({cwd: repo_dir});
|
|
172
|
-
if (!clean_after_result.ok) {
|
|
173
|
-
throw new TaskError(
|
|
174
|
-
`Failed to check workspace in ${repo_dir}: ${clean_after_result.message}`,
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (!clean_after_result.value) {
|
|
179
|
-
throw new TaskError(
|
|
180
|
-
`Workspace ${repo_dir} is unclean after pulling branch "${repo_config.branch}"`,
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Record commit hash after pull
|
|
186
|
-
const commit_after_result = await git_ops.current_commit_hash({cwd: repo_dir});
|
|
187
|
-
if (!commit_after_result.ok) {
|
|
188
|
-
throw new TaskError(
|
|
189
|
-
`Failed to get commit hash in ${repo_dir}: ${commit_after_result.message}`,
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
const commit_after = commit_after_result.value;
|
|
193
|
-
|
|
194
|
-
// Track if we got new commits
|
|
195
|
-
const got_new_commits = commit_before !== commit_after;
|
|
196
|
-
|
|
197
|
-
// Only install if package.json changed
|
|
198
|
-
if (got_new_commits) {
|
|
199
|
-
const changed_result = await git_ops.has_file_changed({
|
|
200
|
-
from_commit: commit_before,
|
|
201
|
-
to_commit: commit_after,
|
|
202
|
-
file_path: 'package.json',
|
|
203
|
-
cwd: repo_dir,
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
if (!changed_result.ok) {
|
|
207
|
-
throw new TaskError(
|
|
208
|
-
`Failed to check if package.json changed in ${repo_dir}: ${changed_result.message}`,
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (changed_result.value) {
|
|
213
|
-
const install_result = await npm_ops.install({cwd: repo_dir});
|
|
214
|
-
if (!install_result.ok) {
|
|
215
|
-
throw new TaskError(
|
|
216
|
-
`Failed to install dependencies in ${repo_dir}: ${install_result.message}${install_result.stderr ? `\n${install_result.stderr}` : ''}`,
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
152
|
+
const { entry, repo_dir, repo_name } = local_repo_path;
|
|
222
153
|
|
|
223
154
|
// A repo with no `package.json` but a Rust `Cargo.toml` isn't an npm package and can't be
|
|
224
155
|
// analyzed as a library. Load it as a dashboard-only `cargo` repo (CI, PRs, identity) that
|
|
225
156
|
// publishing/analysis skips. Anything else falls through to the npm loader below, whose
|
|
226
157
|
// error covers a genuinely missing or unreadable manifest.
|
|
227
158
|
if (!existsSync(join(repo_dir, 'package.json')) && existsSync(join(repo_dir, 'Cargo.toml'))) {
|
|
228
|
-
return local_repo_load_cargo({local_repo_path});
|
|
159
|
+
return local_repo_load_cargo({ local_repo_path });
|
|
229
160
|
}
|
|
230
161
|
|
|
231
162
|
// Load library metadata via svelte-docinfo analysis (cached under `.gro/library.json`).
|
|
232
163
|
let library_json: LibraryJson;
|
|
233
164
|
let package_json: PackageJson;
|
|
234
165
|
try {
|
|
235
|
-
({library_json, package_json} = await library_load_from_repo(repo_dir, {log
|
|
166
|
+
({ library_json, package_json } = await library_load_from_repo(repo_dir, { log }));
|
|
236
167
|
} catch (err) {
|
|
237
|
-
const message = to_error_message(err);
|
|
238
|
-
_log?.warn(
|
|
239
|
-
`Failed to load library metadata for repo "${repo_name}" in ${repo_dir}: ${message}`,
|
|
240
|
-
);
|
|
241
168
|
throw new TaskError(
|
|
242
|
-
`Failed to load library metadata for repo "${repo_name}" in ${repo_dir}: ${
|
|
169
|
+
`Failed to load library metadata for repo "${repo_name}" in ${repo_dir}: ${to_error_message(err)}`
|
|
243
170
|
);
|
|
244
171
|
}
|
|
245
172
|
const library = new Library(library_json);
|
|
@@ -249,8 +176,7 @@ export const local_repo_load = async ({
|
|
|
249
176
|
library,
|
|
250
177
|
package_json,
|
|
251
178
|
repo_dir,
|
|
252
|
-
|
|
253
|
-
repo_config,
|
|
179
|
+
entry
|
|
254
180
|
};
|
|
255
181
|
|
|
256
182
|
// Extract dependencies from the full package_json
|
|
@@ -269,8 +195,8 @@ export const local_repo_load = async ({
|
|
|
269
195
|
|
|
270
196
|
/**
|
|
271
197
|
* Whether a repo is an npm package and so participates in publishing and
|
|
272
|
-
* dependency analysis. Non-npm repos (e.g. Rust `cargo` repos) are still
|
|
273
|
-
*
|
|
198
|
+
* dependency analysis. Non-npm repos (e.g. Rust `cargo` repos) are still
|
|
199
|
+
* rendered on the dashboard, but excluded from the changeset cascade.
|
|
274
200
|
*/
|
|
275
201
|
export const repo_is_npm = (repo: LocalRepo): boolean => repo.kind === 'npm';
|
|
276
202
|
|
|
@@ -278,28 +204,28 @@ export const repo_is_npm = (repo: LocalRepo): boolean => repo.kind === 'npm';
|
|
|
278
204
|
* Loads a non-npm Rust repo as a dashboard-only `LocalRepo`. It has no
|
|
279
205
|
* `package.json`, so there's no `svelte-docinfo` analysis and no npm dependency
|
|
280
206
|
* graph — a lightweight `Library` is synthesized from the repo's `Cargo.toml`
|
|
281
|
-
* (best-effort name/version/description) and its
|
|
207
|
+
* (best-effort name/version/description) and its registry URL, which is
|
|
282
208
|
* what the dashboard renders (CI status, PRs, identity). Marked `private` so it
|
|
283
209
|
* never reads as an npm publish target, and tagged `cargo` so the publishing and
|
|
284
210
|
* analysis paths skip it (see `repo_is_npm`).
|
|
285
211
|
*/
|
|
286
212
|
const local_repo_load_cargo = async ({
|
|
287
|
-
local_repo_path
|
|
213
|
+
local_repo_path
|
|
288
214
|
}: {
|
|
289
215
|
local_repo_path: LocalRepoPath;
|
|
290
216
|
}): Promise<LocalRepo> => {
|
|
291
|
-
const {
|
|
217
|
+
const { entry, repo_dir, repo_name, repo_url } = local_repo_path;
|
|
292
218
|
|
|
293
219
|
const cargo = await cargo_toml_load(repo_dir);
|
|
294
220
|
|
|
295
|
-
// A Cargo workspace root has no `name`, so fall back to the
|
|
296
|
-
//
|
|
221
|
+
// A Cargo workspace root has no `name` or `repository`, so fall back to the
|
|
222
|
+
// registry key and URL.
|
|
297
223
|
const package_json: PackageJson = {
|
|
298
224
|
name: cargo?.name ?? repo_name,
|
|
299
225
|
version: cargo?.version ?? '0.0.0',
|
|
300
226
|
repository: cargo?.repository ?? repo_url,
|
|
301
227
|
private: true,
|
|
302
|
-
...(cargo?.description ? {description: cargo.description} : null)
|
|
228
|
+
...(cargo?.description ? { description: cargo.description } : null)
|
|
303
229
|
};
|
|
304
230
|
|
|
305
231
|
const library = new Library(library_json_from_modules(package_json, []));
|
|
@@ -309,87 +235,26 @@ const local_repo_load_cargo = async ({
|
|
|
309
235
|
library,
|
|
310
236
|
package_json,
|
|
311
237
|
repo_dir,
|
|
312
|
-
|
|
313
|
-
repo_config,
|
|
238
|
+
entry
|
|
314
239
|
};
|
|
315
240
|
};
|
|
316
241
|
|
|
317
|
-
export const local_repos_ensure = async ({
|
|
318
|
-
resolved_config,
|
|
319
|
-
repos_dir,
|
|
320
|
-
gitops_config,
|
|
321
|
-
download,
|
|
322
|
-
log,
|
|
323
|
-
npm_ops = default_npm_operations,
|
|
324
|
-
}: {
|
|
325
|
-
resolved_config: ResolvedGitopsConfig;
|
|
326
|
-
repos_dir: string;
|
|
327
|
-
gitops_config: GitopsConfig;
|
|
328
|
-
download: boolean;
|
|
329
|
-
log?: Logger;
|
|
330
|
-
npm_ops?: NpmOperations;
|
|
331
|
-
}): Promise<Array<LocalRepoPath>> => {
|
|
332
|
-
let local_repo_paths: Array<LocalRepoPath> | null = null;
|
|
333
|
-
|
|
334
|
-
if (!resolved_config.local_repos_missing) {
|
|
335
|
-
local_repo_paths = resolved_config.local_repo_paths;
|
|
336
|
-
} else {
|
|
337
|
-
if (download) {
|
|
338
|
-
const downloaded = await download_repos({
|
|
339
|
-
repos_dir,
|
|
340
|
-
local_repos_missing: resolved_config.local_repos_missing,
|
|
341
|
-
log,
|
|
342
|
-
npm_ops,
|
|
343
|
-
});
|
|
344
|
-
local_repo_paths = (resolved_config.local_repo_paths ?? [])
|
|
345
|
-
.concat(downloaded)
|
|
346
|
-
.sort(
|
|
347
|
-
(a, b) =>
|
|
348
|
-
gitops_config.repos.findIndex((r) => r.repo_url === a.repo_url) -
|
|
349
|
-
gitops_config.repos.findIndex((r) => r.repo_url === b.repo_url),
|
|
350
|
-
);
|
|
351
|
-
} else {
|
|
352
|
-
log?.error(
|
|
353
|
-
`Failed to resolve local repos in ${repos_dir} - do you need to pass \`--download\` or configure the directory?`, // TODO leaking task impl details
|
|
354
|
-
resolved_config.local_repos_missing.map((r) => r.repo_url),
|
|
355
|
-
);
|
|
356
|
-
throw new TaskError('Failed to resolve local configs');
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
if (!local_repo_paths) {
|
|
361
|
-
throw new TaskError('No repos are configured in `gitops_config.ts`');
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
return local_repo_paths;
|
|
365
|
-
};
|
|
366
|
-
|
|
367
242
|
export const local_repos_load = async ({
|
|
368
243
|
local_repo_paths,
|
|
369
244
|
log,
|
|
370
|
-
git_ops = default_git_operations,
|
|
371
|
-
npm_ops = default_npm_operations,
|
|
372
245
|
parallel = true,
|
|
373
|
-
concurrency = GITOPS_CONCURRENCY_DEFAULT
|
|
374
|
-
sync = true,
|
|
375
|
-
allow_dirty = false,
|
|
246
|
+
concurrency = GITOPS_CONCURRENCY_DEFAULT
|
|
376
247
|
}: {
|
|
377
248
|
local_repo_paths: Array<LocalRepoPath>;
|
|
378
249
|
log?: Logger;
|
|
379
|
-
git_ops?: GitOperations;
|
|
380
|
-
npm_ops?: NpmOperations;
|
|
381
250
|
parallel?: boolean;
|
|
382
251
|
concurrency?: number;
|
|
383
|
-
sync?: boolean;
|
|
384
|
-
allow_dirty?: boolean;
|
|
385
252
|
}): Promise<Array<LocalRepo>> => {
|
|
386
253
|
if (!parallel) {
|
|
387
|
-
//
|
|
254
|
+
// sequential loading
|
|
388
255
|
const loaded: Array<LocalRepo> = [];
|
|
389
256
|
for (const local_repo_path of local_repo_paths) {
|
|
390
|
-
loaded.push(
|
|
391
|
-
await local_repo_load({local_repo_path, log, git_ops, npm_ops, sync, allow_dirty}),
|
|
392
|
-
);
|
|
257
|
+
loaded.push(await local_repo_load({ local_repo_path, log }));
|
|
393
258
|
}
|
|
394
259
|
return loaded;
|
|
395
260
|
}
|
|
@@ -399,13 +264,13 @@ export const local_repos_load = async ({
|
|
|
399
264
|
local_repo_paths,
|
|
400
265
|
concurrency,
|
|
401
266
|
async (local_repo_path) => {
|
|
402
|
-
return local_repo_load({local_repo_path, log
|
|
403
|
-
}
|
|
267
|
+
return local_repo_load({ local_repo_path, log });
|
|
268
|
+
}
|
|
404
269
|
);
|
|
405
270
|
|
|
406
271
|
// Check for failures and collect successes
|
|
407
272
|
const loaded: Array<LocalRepo> = [];
|
|
408
|
-
const errors: Array<{repo_name: string; error: string}> = [];
|
|
273
|
+
const errors: Array<{ repo_name: string; error: string }> = [];
|
|
409
274
|
|
|
410
275
|
for (let i = 0; i < results.length; i++) {
|
|
411
276
|
const result = results[i]!;
|
|
@@ -415,7 +280,7 @@ export const local_repos_load = async ({
|
|
|
415
280
|
const repo_path = local_repo_paths[i]!;
|
|
416
281
|
errors.push({
|
|
417
282
|
repo_name: repo_path.repo_name,
|
|
418
|
-
error: String(result.reason)
|
|
283
|
+
error: String(result.reason)
|
|
419
284
|
});
|
|
420
285
|
}
|
|
421
286
|
}
|
|
@@ -428,75 +293,3 @@ export const local_repos_load = async ({
|
|
|
428
293
|
|
|
429
294
|
return loaded;
|
|
430
295
|
};
|
|
431
|
-
|
|
432
|
-
export const local_repo_locate = ({
|
|
433
|
-
repo_config,
|
|
434
|
-
repos_dir,
|
|
435
|
-
}: {
|
|
436
|
-
repo_config: GitopsRepoConfig;
|
|
437
|
-
repos_dir: string;
|
|
438
|
-
}): LocalRepoPath | LocalRepoMissing => {
|
|
439
|
-
const {repo_url} = repo_config;
|
|
440
|
-
const repo_name = strip_end(repo_url, '/').split('/').at(-1);
|
|
441
|
-
if (!repo_name) throw Error('Invalid `repo_config.repo_url` ' + repo_url);
|
|
442
|
-
|
|
443
|
-
const repo_git_ssh_url = to_repo_git_ssh_url(repo_url);
|
|
444
|
-
|
|
445
|
-
const repo_dir = repo_config.repo_dir ?? join(repos_dir, repo_name);
|
|
446
|
-
if (!existsSync(repo_dir)) {
|
|
447
|
-
return {type: 'local_repo_missing', repo_name, repo_url, repo_git_ssh_url, repo_config};
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
return {
|
|
451
|
-
type: 'local_repo_path',
|
|
452
|
-
repo_name,
|
|
453
|
-
repo_dir,
|
|
454
|
-
repo_url,
|
|
455
|
-
repo_git_ssh_url,
|
|
456
|
-
repo_config,
|
|
457
|
-
};
|
|
458
|
-
};
|
|
459
|
-
|
|
460
|
-
const to_repo_git_ssh_url = (repo_url: string): string => {
|
|
461
|
-
const url = new URL(repo_url);
|
|
462
|
-
return `git@${url.hostname}:${url.pathname.substring(1)}`;
|
|
463
|
-
};
|
|
464
|
-
|
|
465
|
-
const download_repos = async ({
|
|
466
|
-
repos_dir,
|
|
467
|
-
local_repos_missing,
|
|
468
|
-
log,
|
|
469
|
-
npm_ops = default_npm_operations,
|
|
470
|
-
}: {
|
|
471
|
-
repos_dir: string;
|
|
472
|
-
local_repos_missing: Array<LocalRepoMissing>;
|
|
473
|
-
log?: Logger;
|
|
474
|
-
npm_ops?: NpmOperations;
|
|
475
|
-
}): Promise<Array<LocalRepoPath>> => {
|
|
476
|
-
const resolved: Array<LocalRepoPath> = [];
|
|
477
|
-
for (const {repo_config, repo_git_ssh_url} of local_repos_missing) {
|
|
478
|
-
log?.info(`cloning repo ${repo_git_ssh_url} to ${repos_dir}`);
|
|
479
|
-
const clone_result = await spawn_out('git', ['clone', repo_git_ssh_url], {cwd: repos_dir});
|
|
480
|
-
if (!clone_result.result.ok) {
|
|
481
|
-
throw new TaskError(
|
|
482
|
-
`Failed to clone repo ${repo_git_ssh_url} to ${repos_dir}${clone_result.stderr ? ': ' + clone_result.stderr.trim() : ''}`,
|
|
483
|
-
);
|
|
484
|
-
}
|
|
485
|
-
const local_repo = local_repo_locate({repo_config, repos_dir});
|
|
486
|
-
if (local_repo.type === 'local_repo_missing') {
|
|
487
|
-
throw new TaskError(
|
|
488
|
-
`Failed to clone repo ${repo_git_ssh_url} to ${repos_dir}: directory not found after clone`,
|
|
489
|
-
);
|
|
490
|
-
}
|
|
491
|
-
// Always install dependencies after cloning
|
|
492
|
-
log?.info(`installing dependencies for newly cloned repo ${local_repo.repo_dir}`);
|
|
493
|
-
const install_result = await npm_ops.install({cwd: local_repo.repo_dir});
|
|
494
|
-
if (!install_result.ok) {
|
|
495
|
-
throw new TaskError(
|
|
496
|
-
`Failed to install dependencies in ${local_repo.repo_dir}: ${install_result.message}${install_result.stderr ? `\n${install_result.stderr}` : ''}`,
|
|
497
|
-
);
|
|
498
|
-
}
|
|
499
|
-
resolved.push(local_repo);
|
|
500
|
-
}
|
|
501
|
-
return resolved;
|
|
502
|
-
};
|