@fuzdev/fuz_gitops 0.76.0 → 0.78.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 +5 -5
- package/dist/ModulesDetail.svelte +1 -1
- package/dist/PageFooter.svelte +2 -2
- package/dist/PageHeader.svelte +1 -1
- package/dist/PageHeader.svelte.d.ts +1 -1
- package/dist/ReposTable.svelte +1 -1
- package/dist/ReposTree.svelte +1 -1
- package/dist/cargo_toml.d.ts +33 -0
- package/dist/cargo_toml.d.ts.map +1 -0
- package/dist/cargo_toml.js +74 -0
- package/dist/changeset_generator.d.ts +1 -1
- package/dist/changeset_reader.d.ts +1 -1
- package/dist/ci_reconcile.d.ts +50 -0
- package/dist/ci_reconcile.d.ts.map +1 -0
- package/dist/ci_reconcile.js +46 -0
- package/dist/config_reconcile.d.ts +46 -0
- package/dist/config_reconcile.d.ts.map +1 -0
- package/dist/config_reconcile.js +60 -0
- package/dist/dependency_graph.d.ts +2 -2
- package/dist/dependency_graph.js +4 -4
- package/dist/dependency_updater.d.ts +1 -1
- package/dist/fetch_repo_data.d.ts +2 -2
- package/dist/fetch_repo_data.js +1 -1
- package/dist/fs_fetch_value_cache.d.ts +1 -1
- package/dist/fs_fetch_value_cache.js +3 -3
- package/dist/git_operations.d.ts +1 -1
- package/dist/git_operations.js +2 -2
- package/dist/github.d.ts +2 -2
- package/dist/github.js +1 -1
- package/dist/github_helpers.js +1 -1
- package/dist/gitops_analyze.task.d.ts.map +1 -1
- package/dist/gitops_analyze.task.js +15 -8
- package/dist/gitops_config.d.ts +27 -2
- package/dist/gitops_config.d.ts.map +1 -1
- package/dist/gitops_config.js +13 -2
- package/dist/gitops_run.task.js +2 -2
- package/dist/gitops_sync.task.js +4 -4
- package/dist/gitops_task_helpers.d.ts +1 -1
- package/dist/gitops_task_helpers.js +1 -1
- package/dist/gitops_validate.task.d.ts.map +1 -1
- package/dist/gitops_validate.task.js +54 -6
- package/dist/graph_validation.d.ts +2 -2
- package/dist/graph_validation.d.ts.map +1 -1
- package/dist/graph_validation.js +3 -1
- package/dist/local_repo.d.ts +17 -3
- package/dist/local_repo.d.ts.map +1 -1
- package/dist/local_repo.js +54 -6
- package/dist/log_helpers.d.ts +1 -1
- package/dist/multi_repo_publisher.d.ts +3 -3
- package/dist/multi_repo_publisher.d.ts.map +1 -1
- package/dist/multi_repo_publisher.js +5 -1
- package/dist/npm_registry.d.ts +1 -1
- package/dist/npm_registry.js +2 -2
- package/dist/operations.d.ts +4 -4
- package/dist/operations_defaults.js +4 -4
- package/dist/output_helpers.d.ts +1 -1
- package/dist/preflight_checks.d.ts +1 -1
- package/dist/preflight_checks.js +1 -1
- package/dist/publish_steps.js +1 -1
- package/dist/publishing_plan.d.ts +3 -3
- package/dist/publishing_plan.d.ts.map +1 -1
- package/dist/publishing_plan.js +11 -1
- package/dist/publishing_plan_logging.d.ts +1 -1
- package/dist/repo.svelte.d.ts +5 -5
- package/dist/repo.svelte.js +2 -2
- package/package.json +10 -10
- package/src/lib/cargo_toml.ts +82 -0
- package/src/lib/changeset_generator.ts +6 -6
- package/src/lib/changeset_reader.ts +3 -3
- package/src/lib/ci_reconcile.ts +73 -0
- package/src/lib/config_reconcile.ts +94 -0
- package/src/lib/dependency_graph.ts +5 -5
- package/src/lib/dependency_updater.ts +7 -7
- package/src/lib/fetch_repo_data.ts +6 -6
- package/src/lib/fs_fetch_value_cache.ts +3 -3
- package/src/lib/git_operations.ts +2 -2
- package/src/lib/github.ts +2 -2
- package/src/lib/github_helpers.ts +3 -3
- package/src/lib/gitops_analyze.task.ts +26 -18
- package/src/lib/gitops_config.ts +45 -5
- package/src/lib/gitops_plan.task.ts +4 -4
- package/src/lib/gitops_publish.task.ts +9 -9
- package/src/lib/gitops_run.task.ts +4 -4
- package/src/lib/gitops_sync.task.ts +8 -8
- package/src/lib/gitops_task_helpers.ts +7 -7
- package/src/lib/gitops_validate.task.ts +63 -11
- package/src/lib/graph_validation.ts +5 -4
- package/src/lib/local_repo.ts +81 -15
- package/src/lib/log_helpers.ts +2 -2
- package/src/lib/multi_repo_publisher.ts +15 -11
- package/src/lib/npm_registry.ts +3 -3
- package/src/lib/operations.ts +9 -9
- package/src/lib/operations_defaults.ts +9 -9
- package/src/lib/output_helpers.ts +1 -1
- package/src/lib/preflight_checks.ts +6 -6
- package/src/lib/publish_gate.ts +2 -2
- package/src/lib/publish_steps.ts +3 -3
- package/src/lib/publishing_event_handler.ts +1 -1
- package/src/lib/publishing_plan.ts +22 -10
- package/src/lib/publishing_plan_helpers.ts +3 -3
- package/src/lib/publishing_plan_logging.ts +2 -2
- package/src/lib/repo.svelte.ts +7 -7
- package/src/lib/repo_ops.ts +3 -3
- package/src/lib/resolved_gitops_config.ts +2 -2
package/README.md
CHANGED
|
@@ -40,19 +40,19 @@ npm i -D @fuzdev/fuz_gitops
|
|
|
40
40
|
|
|
41
41
|
```ts
|
|
42
42
|
// gitops_sync.task.ts
|
|
43
|
-
export * from '@fuzdev/fuz_gitops/gitops_sync.task.
|
|
43
|
+
export * from '@fuzdev/fuz_gitops/gitops_sync.task.ts';
|
|
44
44
|
|
|
45
45
|
// gitops_analyze.task.ts
|
|
46
|
-
export * from '@fuzdev/fuz_gitops/gitops_analyze.task.
|
|
46
|
+
export * from '@fuzdev/fuz_gitops/gitops_analyze.task.ts';
|
|
47
47
|
|
|
48
48
|
// gitops_plan.task.ts
|
|
49
|
-
export * from '@fuzdev/fuz_gitops/gitops_plan.task.
|
|
49
|
+
export * from '@fuzdev/fuz_gitops/gitops_plan.task.ts';
|
|
50
50
|
|
|
51
51
|
// gitops_publish.task.ts
|
|
52
|
-
export * from '@fuzdev/fuz_gitops/gitops_publish.task.
|
|
52
|
+
export * from '@fuzdev/fuz_gitops/gitops_publish.task.ts';
|
|
53
53
|
|
|
54
54
|
// gitops_validate.task.ts
|
|
55
|
-
export * from '@fuzdev/fuz_gitops/gitops_validate.task.
|
|
55
|
+
export * from '@fuzdev/fuz_gitops/gitops_validate.task.ts';
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
- run `gro gitops_sync` to sync repos and update the local data
|
package/dist/PageFooter.svelte
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import DocsFooter from '@fuzdev/fuz_ui/DocsFooter.svelte';
|
|
3
|
-
import {site_context} from '@fuzdev/fuz_ui/site.svelte.
|
|
4
|
-
import {FUZ_DEV_URL} from '@fuzdev/fuz_ui/constants.
|
|
3
|
+
import {site_context} from '@fuzdev/fuz_ui/site.svelte.ts';
|
|
4
|
+
import {FUZ_DEV_URL} from '@fuzdev/fuz_ui/constants.ts';
|
|
5
5
|
import Breadcrumb from '@fuzdev/fuz_ui/Breadcrumb.svelte';
|
|
6
6
|
|
|
7
7
|
const site = site_context.get();
|
package/dist/PageHeader.svelte
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type {PkgJson} from '@fuzdev/fuz_util/pkg_json.
|
|
2
|
+
import type {PkgJson} from '@fuzdev/fuz_util/pkg_json.ts';
|
|
3
3
|
import Breadcrumb from '@fuzdev/fuz_ui/Breadcrumb.svelte';
|
|
4
4
|
import type {Snippet} from 'svelte';
|
|
5
5
|
import type {SvelteHTMLElements} from 'svelte/elements';
|
package/dist/ReposTable.svelte
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import {page} from '$app/state';
|
|
3
3
|
import {resolve} from '$app/paths';
|
|
4
|
-
import {format_url} from '@fuzdev/fuz_util/url.
|
|
4
|
+
import {format_url} from '@fuzdev/fuz_util/url.ts';
|
|
5
5
|
|
|
6
6
|
import type {Repo} from './repo.svelte.js';
|
|
7
7
|
import {to_pull_url} from './github_helpers.js';
|
package/dist/ReposTree.svelte
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import LibrarySummary from '@fuzdev/fuz_ui/LibrarySummary.svelte';
|
|
3
3
|
import {resolve} from '$app/paths';
|
|
4
|
-
import {format_url} from '@fuzdev/fuz_util/url.
|
|
4
|
+
import {format_url} from '@fuzdev/fuz_util/url.ts';
|
|
5
5
|
import type {Snippet} from 'svelte';
|
|
6
6
|
|
|
7
7
|
import type {Repo} from './repo.svelte.js';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The handful of identity fields gitops reads from a Rust repo's `Cargo.toml`
|
|
3
|
+
* to render it on the dashboard. Everything is optional — a workspace root has
|
|
4
|
+
* no `name`, and any field may be absent or inherited.
|
|
5
|
+
*/
|
|
6
|
+
export interface CargoMetadata {
|
|
7
|
+
name?: string;
|
|
8
|
+
version?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
repository?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Best-effort read of a repo's root `Cargo.toml` for the identity fields the
|
|
14
|
+
* gitops dashboard renders. Returns `null` when there's no `Cargo.toml`.
|
|
15
|
+
*
|
|
16
|
+
* @param repo_dir - absolute path to the repo
|
|
17
|
+
*/
|
|
18
|
+
export declare const cargo_toml_load: (repo_dir: string) => Promise<CargoMetadata | null>;
|
|
19
|
+
/**
|
|
20
|
+
* Extracts `name`/`version`/`description`/`repository` from the `[package]` and
|
|
21
|
+
* `[workspace.package]` tables of a `Cargo.toml`.
|
|
22
|
+
*
|
|
23
|
+
* Deliberately not a full TOML parser: it scans for simple `key = "value"`
|
|
24
|
+
* string entries in those two tables, which covers both a single-crate manifest
|
|
25
|
+
* and a workspace root. Inline-table values like `version = { workspace = true }`
|
|
26
|
+
* (and the equivalent `version.workspace = true`) are ignored — a member crate
|
|
27
|
+
* inheriting from the workspace has no literal here, and gitops only ever reads a
|
|
28
|
+
* repo's root manifest, where these are concrete. The first non-empty value for a
|
|
29
|
+
* key wins, so a top-level `[package]` takes precedence over `[workspace.package]`
|
|
30
|
+
* when both appear.
|
|
31
|
+
*/
|
|
32
|
+
export declare const cargo_toml_parse: (contents: string) => CargoMetadata;
|
|
33
|
+
//# sourceMappingURL=cargo_toml.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cargo_toml.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/cargo_toml.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAOD;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAIpF,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,GAAI,UAAU,MAAM,KAAG,aAsBnD,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
/** The `Cargo.toml` tables that carry package identity (single-crate vs workspace root). */
|
|
5
|
+
const CARGO_METADATA_SECTIONS = new Set(['package', 'workspace.package']);
|
|
6
|
+
const CARGO_METADATA_KEYS = ['name', 'version', 'description', 'repository'];
|
|
7
|
+
/**
|
|
8
|
+
* Best-effort read of a repo's root `Cargo.toml` for the identity fields the
|
|
9
|
+
* gitops dashboard renders. Returns `null` when there's no `Cargo.toml`.
|
|
10
|
+
*
|
|
11
|
+
* @param repo_dir - absolute path to the repo
|
|
12
|
+
*/
|
|
13
|
+
export const cargo_toml_load = async (repo_dir) => {
|
|
14
|
+
const cargo_toml_path = join(repo_dir, 'Cargo.toml');
|
|
15
|
+
if (!existsSync(cargo_toml_path))
|
|
16
|
+
return null;
|
|
17
|
+
return cargo_toml_parse(await readFile(cargo_toml_path, 'utf8'));
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Extracts `name`/`version`/`description`/`repository` from the `[package]` and
|
|
21
|
+
* `[workspace.package]` tables of a `Cargo.toml`.
|
|
22
|
+
*
|
|
23
|
+
* Deliberately not a full TOML parser: it scans for simple `key = "value"`
|
|
24
|
+
* string entries in those two tables, which covers both a single-crate manifest
|
|
25
|
+
* and a workspace root. Inline-table values like `version = { workspace = true }`
|
|
26
|
+
* (and the equivalent `version.workspace = true`) are ignored — a member crate
|
|
27
|
+
* inheriting from the workspace has no literal here, and gitops only ever reads a
|
|
28
|
+
* repo's root manifest, where these are concrete. The first non-empty value for a
|
|
29
|
+
* key wins, so a top-level `[package]` takes precedence over `[workspace.package]`
|
|
30
|
+
* when both appear.
|
|
31
|
+
*/
|
|
32
|
+
export const cargo_toml_parse = (contents) => {
|
|
33
|
+
const metadata = {};
|
|
34
|
+
let in_metadata_section = false;
|
|
35
|
+
for (const raw_line of contents.split('\n')) {
|
|
36
|
+
const line = raw_line.trim();
|
|
37
|
+
if (line === '' || line.startsWith('#'))
|
|
38
|
+
continue;
|
|
39
|
+
// Section header — only `[package]`/`[workspace.package]` arm value capture.
|
|
40
|
+
if (line.startsWith('[')) {
|
|
41
|
+
const end = line.indexOf(']');
|
|
42
|
+
in_metadata_section = end !== -1 && CARGO_METADATA_SECTIONS.has(line.slice(1, end).trim());
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (!in_metadata_section)
|
|
46
|
+
continue;
|
|
47
|
+
const eq = line.indexOf('=');
|
|
48
|
+
if (eq === -1)
|
|
49
|
+
continue;
|
|
50
|
+
const key = line.slice(0, eq).trim();
|
|
51
|
+
if (!CARGO_METADATA_KEYS.includes(key))
|
|
52
|
+
continue;
|
|
53
|
+
if (metadata[key] !== undefined)
|
|
54
|
+
continue; // first value wins
|
|
55
|
+
const value = cargo_toml_string_value(line.slice(eq + 1).trim());
|
|
56
|
+
if (value !== null)
|
|
57
|
+
metadata[key] = value;
|
|
58
|
+
}
|
|
59
|
+
return metadata;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Reads a simple single- or double-quoted TOML string, ignoring any trailing
|
|
63
|
+
* inline comment. Returns `null` for non-string values (inline tables, arrays,
|
|
64
|
+
* booleans, numbers), which the caller skips.
|
|
65
|
+
*/
|
|
66
|
+
const cargo_toml_string_value = (raw) => {
|
|
67
|
+
const quote = raw[0];
|
|
68
|
+
if (quote !== '"' && quote !== "'")
|
|
69
|
+
return null;
|
|
70
|
+
const end = raw.indexOf(quote, 1);
|
|
71
|
+
if (end === -1)
|
|
72
|
+
return null;
|
|
73
|
+
return raw.slice(1, end);
|
|
74
|
+
};
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
-
import type { Logger } from '@fuzdev/fuz_util/log.
|
|
9
|
+
import type { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
10
10
|
import type { LocalRepo } from './local_repo.js';
|
|
11
11
|
import type { PublishedVersion } from './multi_repo_publisher.js';
|
|
12
12
|
import type { FsOperations } from './operations.js';
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
-
import type { Logger } from '@fuzdev/fuz_util/log.
|
|
9
|
+
import type { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
10
10
|
import type { LocalRepo } from './local_repo.js';
|
|
11
11
|
import { type BumpType } from './version_utils.js';
|
|
12
12
|
export interface ChangesetInfo {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reconciles each repo's declared `ci` flag against whether it actually has
|
|
3
|
+
* GitHub Actions workflow files on disk.
|
|
4
|
+
*
|
|
5
|
+
* The gitops config derives `ci` from visibility (on for public repos, off for
|
|
6
|
+
* private) with per-repo overrides; this check catches drift between that
|
|
7
|
+
* declaration and reality —
|
|
8
|
+
* a repo that claims CI but ships no workflow, or one that disclaims CI yet
|
|
9
|
+
* still carries one. Repos that aren't checked out locally can't be judged, so
|
|
10
|
+
* the caller marks them uncheckable and they're skipped. Archived repos are
|
|
11
|
+
* frozen on their host, so their CI state is intentionally left alone and they're
|
|
12
|
+
* skipped too.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
/** How a repo's declared `ci` diverges from its workflow files on disk. */
|
|
17
|
+
export type CiDriftKind =
|
|
18
|
+
/** `ci` is `true` but the repo has no workflow files. */
|
|
19
|
+
'missing_ci'
|
|
20
|
+
/** `ci` is `false` but the repo has workflow files. */
|
|
21
|
+
| 'stray_ci';
|
|
22
|
+
export interface CiDrift {
|
|
23
|
+
repo_url: string;
|
|
24
|
+
/** The declared/derived `ci` value. */
|
|
25
|
+
ci: boolean;
|
|
26
|
+
has_workflows: boolean;
|
|
27
|
+
kind: CiDriftKind;
|
|
28
|
+
}
|
|
29
|
+
export interface CiReconcileInput {
|
|
30
|
+
repo_url: string;
|
|
31
|
+
/** The declared/derived `ci` value from the gitops config. */
|
|
32
|
+
ci: boolean;
|
|
33
|
+
/** Whether the repo has at least one workflow file on disk. */
|
|
34
|
+
has_workflows: boolean;
|
|
35
|
+
/** Whether the repo is checked out locally; uncheckable repos are skipped. */
|
|
36
|
+
checkable: boolean;
|
|
37
|
+
/** Whether the repo is archived (frozen) on its host; archived repos are skipped. */
|
|
38
|
+
archived: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Compares each repo's declared `ci` against its actual workflow presence.
|
|
42
|
+
* @returns one `CiDrift` per repo whose declaration and reality disagree
|
|
43
|
+
*/
|
|
44
|
+
export declare const reconcile_ci: (repos: Array<CiReconcileInput>) => Array<CiDrift>;
|
|
45
|
+
/**
|
|
46
|
+
* Whether a local repo directory contains at least one GitHub Actions workflow.
|
|
47
|
+
* @param repo_dir - absolute or cwd-relative path to the repo's local directory
|
|
48
|
+
*/
|
|
49
|
+
export declare const repo_has_workflows: (repo_dir: string) => boolean;
|
|
50
|
+
//# sourceMappingURL=ci_reconcile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci_reconcile.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/ci_reconcile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,2EAA2E;AAC3E,MAAM,MAAM,WAAW;AACtB,yDAAyD;AACvD,YAAY;AACd,uDAAuD;GACrD,UAAU,CAAC;AAEd,MAAM,WAAW,OAAO;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,EAAE,EAAE,OAAO,CAAC;IACZ,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,EAAE,WAAW,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,EAAE,EAAE,OAAO,CAAC;IACZ,+DAA+D;IAC/D,aAAa,EAAE,OAAO,CAAC;IACvB,8EAA8E;IAC9E,SAAS,EAAE,OAAO,CAAC;IACnB,qFAAqF;IACrF,QAAQ,EAAE,OAAO,CAAC;CAClB;AAED;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,KAAK,CAAC,gBAAgB,CAAC,KAAG,KAAK,CAAC,OAAO,CAY1E,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,KAAG,OAIrD,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reconciles each repo's declared `ci` flag against whether it actually has
|
|
3
|
+
* GitHub Actions workflow files on disk.
|
|
4
|
+
*
|
|
5
|
+
* The gitops config derives `ci` from visibility (on for public repos, off for
|
|
6
|
+
* private) with per-repo overrides; this check catches drift between that
|
|
7
|
+
* declaration and reality —
|
|
8
|
+
* a repo that claims CI but ships no workflow, or one that disclaims CI yet
|
|
9
|
+
* still carries one. Repos that aren't checked out locally can't be judged, so
|
|
10
|
+
* the caller marks them uncheckable and they're skipped. Archived repos are
|
|
11
|
+
* frozen on their host, so their CI state is intentionally left alone and they're
|
|
12
|
+
* skipped too.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
/**
|
|
19
|
+
* Compares each repo's declared `ci` against its actual workflow presence.
|
|
20
|
+
* @returns one `CiDrift` per repo whose declaration and reality disagree
|
|
21
|
+
*/
|
|
22
|
+
export const reconcile_ci = (repos) => {
|
|
23
|
+
const drift = [];
|
|
24
|
+
for (const repo of repos) {
|
|
25
|
+
if (!repo.checkable || repo.archived)
|
|
26
|
+
continue;
|
|
27
|
+
const { repo_url, ci, has_workflows } = repo;
|
|
28
|
+
if (ci && !has_workflows) {
|
|
29
|
+
drift.push({ repo_url, ci, has_workflows, kind: 'missing_ci' });
|
|
30
|
+
}
|
|
31
|
+
else if (!ci && has_workflows) {
|
|
32
|
+
drift.push({ repo_url, ci, has_workflows, kind: 'stray_ci' });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return drift;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Whether a local repo directory contains at least one GitHub Actions workflow.
|
|
39
|
+
* @param repo_dir - absolute or cwd-relative path to the repo's local directory
|
|
40
|
+
*/
|
|
41
|
+
export const repo_has_workflows = (repo_dir) => {
|
|
42
|
+
const workflows_dir = join(repo_dir, '.github', 'workflows');
|
|
43
|
+
if (!existsSync(workflows_dir))
|
|
44
|
+
return false;
|
|
45
|
+
return readdirSync(workflows_dir).some((file) => file.endsWith('.yml') || file.endsWith('.yaml'));
|
|
46
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reconciles repo metadata across multiple gitops configs.
|
|
3
|
+
*
|
|
4
|
+
* The ecosystem keeps several gitops configs — a canonical superset plus
|
|
5
|
+
* narrower per-project subsets. They can't share one source of truth (the
|
|
6
|
+
* superset includes private repos that public configs may not reference), so the
|
|
7
|
+
* same repo is declared independently in each. This check flags when a subset's
|
|
8
|
+
* intrinsic fields drift from the canonical config, or when a subset lists a
|
|
9
|
+
* repo the canonical config doesn't. `repo_dir` is per-config and ignored.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import type { GitopsRepoConfig } from './gitops_config.js';
|
|
14
|
+
/** Repo fields that must agree across every config that lists the repo. */
|
|
15
|
+
declare const INTRINSIC_FIELDS: readonly ["visibility", "ci", "archived", "branch"];
|
|
16
|
+
export type IntrinsicField = (typeof INTRINSIC_FIELDS)[number];
|
|
17
|
+
export type ConfigDriftKind =
|
|
18
|
+
/** The repo is in a subset config but absent from the canonical config. */
|
|
19
|
+
'missing_from_canonical'
|
|
20
|
+
/** An intrinsic field disagrees between the subset and the canonical config. */
|
|
21
|
+
| 'field_mismatch';
|
|
22
|
+
export interface ConfigDrift {
|
|
23
|
+
repo_url: string;
|
|
24
|
+
/** Name of the subset config whose entry diverges. */
|
|
25
|
+
config: string;
|
|
26
|
+
kind: ConfigDriftKind;
|
|
27
|
+
/** The field that disagrees, set when `kind` is `'field_mismatch'`. */
|
|
28
|
+
field?: IntrinsicField;
|
|
29
|
+
/** The canonical config's value, set when `kind` is `'field_mismatch'`. */
|
|
30
|
+
canonical_value?: string;
|
|
31
|
+
/** The subset config's value, set when `kind` is `'field_mismatch'`. */
|
|
32
|
+
config_value?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface NamedRepos {
|
|
35
|
+
name: string;
|
|
36
|
+
repos: Array<GitopsRepoConfig>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Compares each subset config against the canonical config. Repos that live only
|
|
40
|
+
* in the canonical config are fine — the subsets are intentional partial views.
|
|
41
|
+
* @returns one `ConfigDrift` per subset entry that is missing from the canonical
|
|
42
|
+
* config or whose intrinsic fields disagree with it
|
|
43
|
+
*/
|
|
44
|
+
export declare const reconcile_configs: (canonical: NamedRepos, subsets: Array<NamedRepos>) => Array<ConfigDrift>;
|
|
45
|
+
export {};
|
|
46
|
+
//# sourceMappingURL=config_reconcile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config_reconcile.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/config_reconcile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAC;AAEzD,2EAA2E;AAC3E,QAAA,MAAM,gBAAgB,qDAAsD,CAAC;AAE7E,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/D,MAAM,MAAM,eAAe;AAC1B,2EAA2E;AACzE,wBAAwB;AAC1B,gFAAgF;GAC9E,gBAAgB,CAAC;AAEpB,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,eAAe,CAAC;IACtB,uEAAuE;IACvE,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,2EAA2E;IAC3E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,KAAK,CAAC,gBAAgB,CAAC,CAAC;CAC/B;AAeD;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAC7B,WAAW,UAAU,EACrB,SAAS,KAAK,CAAC,UAAU,CAAC,KACxB,KAAK,CAAC,WAAW,CA2BnB,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reconciles repo metadata across multiple gitops configs.
|
|
3
|
+
*
|
|
4
|
+
* The ecosystem keeps several gitops configs — a canonical superset plus
|
|
5
|
+
* narrower per-project subsets. They can't share one source of truth (the
|
|
6
|
+
* superset includes private repos that public configs may not reference), so the
|
|
7
|
+
* same repo is declared independently in each. This check flags when a subset's
|
|
8
|
+
* intrinsic fields drift from the canonical config, or when a subset lists a
|
|
9
|
+
* repo the canonical config doesn't. `repo_dir` is per-config and ignored.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
/** Repo fields that must agree across every config that lists the repo. */
|
|
14
|
+
const INTRINSIC_FIELDS = ['visibility', 'ci', 'archived', 'branch'];
|
|
15
|
+
const field_value = (repo, field) => {
|
|
16
|
+
switch (field) {
|
|
17
|
+
case 'visibility':
|
|
18
|
+
return repo.visibility;
|
|
19
|
+
case 'ci':
|
|
20
|
+
return String(repo.ci);
|
|
21
|
+
case 'archived':
|
|
22
|
+
return String(repo.archived);
|
|
23
|
+
case 'branch':
|
|
24
|
+
return repo.branch;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Compares each subset config against the canonical config. Repos that live only
|
|
29
|
+
* in the canonical config are fine — the subsets are intentional partial views.
|
|
30
|
+
* @returns one `ConfigDrift` per subset entry that is missing from the canonical
|
|
31
|
+
* config or whose intrinsic fields disagree with it
|
|
32
|
+
*/
|
|
33
|
+
export const reconcile_configs = (canonical, subsets) => {
|
|
34
|
+
const drift = [];
|
|
35
|
+
const canonical_by_url = new Map(canonical.repos.map((r) => [r.repo_url, r]));
|
|
36
|
+
for (const subset of subsets) {
|
|
37
|
+
for (const repo of subset.repos) {
|
|
38
|
+
const canon = canonical_by_url.get(repo.repo_url);
|
|
39
|
+
if (!canon) {
|
|
40
|
+
drift.push({ repo_url: repo.repo_url, config: subset.name, kind: 'missing_from_canonical' });
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
for (const field of INTRINSIC_FIELDS) {
|
|
44
|
+
const canonical_value = field_value(canon, field);
|
|
45
|
+
const config_value = field_value(repo, field);
|
|
46
|
+
if (canonical_value !== config_value) {
|
|
47
|
+
drift.push({
|
|
48
|
+
repo_url: repo.repo_url,
|
|
49
|
+
config: subset.name,
|
|
50
|
+
kind: 'field_mismatch',
|
|
51
|
+
field,
|
|
52
|
+
canonical_value,
|
|
53
|
+
config_value,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return drift;
|
|
60
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dependency graph data structure and algorithms for multi-repo publishing.
|
|
3
3
|
*
|
|
4
|
-
* Provides `DependencyGraph` class with topological sort (via `@fuzdev/fuz_util/sort.
|
|
4
|
+
* Provides `DependencyGraph` class with topological sort (via `@fuzdev/fuz_util/sort.ts`)
|
|
5
5
|
* and cycle detection by dependency type.
|
|
6
6
|
* For validation workflow and publishing order computation, see `graph_validation.ts`.
|
|
7
7
|
*
|
|
@@ -55,7 +55,7 @@ export declare class DependencyGraph {
|
|
|
55
55
|
/**
|
|
56
56
|
* Computes topological sort order for dependency graph.
|
|
57
57
|
*
|
|
58
|
-
* Delegates to `@fuzdev/fuz_util/sort.
|
|
58
|
+
* Delegates to `@fuzdev/fuz_util/sort.ts` for the sorting algorithm.
|
|
59
59
|
* Throws if cycles detected.
|
|
60
60
|
*
|
|
61
61
|
* @param exclude_dev - if true, excludes dev dependencies to break cycles
|
package/dist/dependency_graph.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dependency graph data structure and algorithms for multi-repo publishing.
|
|
3
3
|
*
|
|
4
|
-
* Provides `DependencyGraph` class with topological sort (via `@fuzdev/fuz_util/sort.
|
|
4
|
+
* Provides `DependencyGraph` class with topological sort (via `@fuzdev/fuz_util/sort.ts`)
|
|
5
5
|
* and cycle detection by dependency type.
|
|
6
6
|
* For validation workflow and publishing order computation, see `graph_validation.ts`.
|
|
7
7
|
*
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
|
-
import { EMPTY_OBJECT } from '@fuzdev/fuz_util/object.
|
|
11
|
-
import { topological_sort as topological_sort_generic } from '@fuzdev/fuz_util/sort.
|
|
10
|
+
import { EMPTY_OBJECT } from '@fuzdev/fuz_util/object.ts';
|
|
11
|
+
import { topological_sort as topological_sort_generic } from '@fuzdev/fuz_util/sort.ts';
|
|
12
12
|
export const DEPENDENCY_TYPE = {
|
|
13
13
|
PROD: 'prod',
|
|
14
14
|
PEER: 'peer',
|
|
@@ -79,7 +79,7 @@ export class DependencyGraph {
|
|
|
79
79
|
/**
|
|
80
80
|
* Computes topological sort order for dependency graph.
|
|
81
81
|
*
|
|
82
|
-
* Delegates to `@fuzdev/fuz_util/sort.
|
|
82
|
+
* Delegates to `@fuzdev/fuz_util/sort.ts` for the sorting algorithm.
|
|
83
83
|
* Throws if cycles detected.
|
|
84
84
|
*
|
|
85
85
|
* @param exclude_dev - if true, excludes dev dependencies to break cycles
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Logger } from '@fuzdev/fuz_util/log.
|
|
1
|
+
import type { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
2
2
|
import type { LocalRepo } from './local_repo.js';
|
|
3
3
|
import type { PublishedVersion } from './multi_repo_publisher.js';
|
|
4
4
|
import type { GitOperations, FsOperations } from './operations.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Logger } from '@fuzdev/fuz_util/log.
|
|
2
|
-
import type { FetchValueCache } from '@fuzdev/fuz_util/fetch.
|
|
1
|
+
import type { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
2
|
+
import type { FetchValueCache } from '@fuzdev/fuz_util/fetch.ts';
|
|
3
3
|
import type { RepoJson } from './repo.svelte.js';
|
|
4
4
|
import type { LocalRepo } from './local_repo.js';
|
|
5
5
|
/**
|
package/dist/fetch_repo_data.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
|
-
import { paths } from '@fuzdev/gro/paths.
|
|
4
|
-
import { format_file } from '@fuzdev/gro/format_file.
|
|
5
|
-
import { deserialize_cache, serialize_cache } from '@fuzdev/fuz_util/fetch.
|
|
3
|
+
import { paths } from '@fuzdev/gro/paths.ts';
|
|
4
|
+
import { format_file } from '@fuzdev/gro/format_file.ts';
|
|
5
|
+
import { deserialize_cache, serialize_cache } from '@fuzdev/fuz_util/fetch.ts';
|
|
6
6
|
import { existsSync } from 'node:fs';
|
|
7
7
|
/**
|
|
8
8
|
* Creates file-system backed cache for `fuz_util`'s `fetch.js` API responses.
|
package/dist/git_operations.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SpawnOptions } from 'node:child_process';
|
|
2
|
-
import { type GitBranch, type GitOrigin } from '@fuzdev/fuz_util/git.
|
|
2
|
+
import { type GitBranch, type GitOrigin } from '@fuzdev/fuz_util/git.ts';
|
|
3
3
|
/**
|
|
4
4
|
* Adds files to git staging area and throws if anything goes wrong.
|
|
5
5
|
*/
|
package/dist/git_operations.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { spawn_out, spawn_result_to_message } from '@fuzdev/fuz_util/process.
|
|
2
|
-
import { git_check_clean_workspace as gro_git_check_clean_workspace, git_checkout as gro_git_checkout, git_pull as gro_git_pull, git_current_branch_name as gro_git_current_branch_name, git_current_commit_hash as gro_git_current_commit_hash, } from '@fuzdev/fuz_util/git.
|
|
1
|
+
import { spawn_out, spawn_result_to_message } from '@fuzdev/fuz_util/process.ts';
|
|
2
|
+
import { git_check_clean_workspace as gro_git_check_clean_workspace, git_checkout as gro_git_checkout, git_pull as gro_git_pull, git_current_branch_name as gro_git_current_branch_name, git_current_commit_hash as gro_git_current_commit_hash, } from '@fuzdev/fuz_util/git.ts';
|
|
3
3
|
/**
|
|
4
4
|
* Adds files to git staging area and throws if anything goes wrong.
|
|
5
5
|
*/
|
package/dist/github.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { Logger } from '@fuzdev/fuz_util/log.
|
|
1
|
+
import type { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { type FetchValueCache } from '@fuzdev/fuz_util/fetch.
|
|
3
|
+
import { type FetchValueCache } from '@fuzdev/fuz_util/fetch.ts';
|
|
4
4
|
/**
|
|
5
5
|
* Minimal interface for GitHub API calls - works with both `Pkg` and `Repo`.
|
|
6
6
|
*/
|
package/dist/github.js
CHANGED
package/dist/github_helpers.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gitops_analyze.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_analyze.task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,aAAa,CAAC;AACtC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAgBtB,cAAc;AACd,eAAO,MAAM,IAAI;;;;;;;;;;kBAqBf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC,cAAc;AACd,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,
|
|
1
|
+
{"version":3,"file":"gitops_analyze.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_analyze.task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,aAAa,CAAC;AACtC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAgBtB,cAAc;AACd,eAAO,MAAM,IAAI;;;;;;;;;;kBAqBf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC,cAAc;AACd,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CAkC3B,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { styleText as st } from 'node:util';
|
|
3
3
|
import { get_gitops_ready } from './gitops_task_helpers.js';
|
|
4
|
+
import { repo_is_npm } from './local_repo.js';
|
|
4
5
|
import { analyze_repos } from './graph_validation.js';
|
|
5
6
|
import { format_wildcard_dependencies, format_dev_cycles, format_production_cycles, } from './log_helpers.js';
|
|
6
7
|
import { format_and_output } from './output_helpers.js';
|
|
@@ -35,11 +36,17 @@ export const task = {
|
|
|
35
36
|
const { config, dir, format, outfile, sync } = args;
|
|
36
37
|
// Get repos ready (without downloading); read the working tree as-is unless `--sync`
|
|
37
38
|
const { local_repos } = await get_gitops_ready({ config, dir, download: false, sync, log });
|
|
39
|
+
// Only npm packages form the dependency graph; note any non-npm repos (e.g. cargo)
|
|
40
|
+
// that are excluded so the omission isn't silent.
|
|
41
|
+
const non_npm_repos = local_repos.filter((r) => !repo_is_npm(r));
|
|
42
|
+
if (non_npm_repos.length > 0) {
|
|
43
|
+
log.info(st('dim', `excluding ${non_npm_repos.length} non-npm repo(s) from analysis (dashboard-only): ` +
|
|
44
|
+
non_npm_repos.map((r) => r.library.name).join(', ')));
|
|
45
|
+
}
|
|
38
46
|
// Build the dependency graph and analyze cycles/wildcards (tolerating cycles)
|
|
39
47
|
const { graph, analysis, publishing_order } = analyze_repos(local_repos);
|
|
40
48
|
// Format and output using output_helpers
|
|
41
49
|
const data = {
|
|
42
|
-
repos: local_repos,
|
|
43
50
|
graph,
|
|
44
51
|
analysis,
|
|
45
52
|
publishing_order,
|
|
@@ -50,8 +57,8 @@ export const task = {
|
|
|
50
57
|
// Create formatters for output_helpers
|
|
51
58
|
const create_formatters = () => ({
|
|
52
59
|
json: (data) => format_json(data.graph, data.analysis, data.publishing_order),
|
|
53
|
-
markdown: (data) => format_markdown(data.
|
|
54
|
-
stdout: (data, log) => format_stdout(data.
|
|
60
|
+
markdown: (data) => format_markdown(data.graph, data.analysis, data.publishing_order),
|
|
61
|
+
stdout: (data, log) => format_stdout(data.graph, data.analysis, data.publishing_order, log),
|
|
55
62
|
});
|
|
56
63
|
// Helper to calculate common statistics
|
|
57
64
|
const calculate_stats = (graph) => {
|
|
@@ -67,12 +74,12 @@ const format_json = (graph, analysis, publishing_order) => {
|
|
|
67
74
|
};
|
|
68
75
|
return JSON.stringify(output, null, 2);
|
|
69
76
|
};
|
|
70
|
-
const format_markdown = (
|
|
77
|
+
const format_markdown = (graph, analysis, publishing_order) => {
|
|
71
78
|
const lines = ['# Dependency Analysis'];
|
|
72
79
|
// Summary stats
|
|
73
80
|
const { total_deps, internal_deps } = calculate_stats(graph);
|
|
74
81
|
lines.push('', '## Summary', '');
|
|
75
|
-
lines.push(`- **Total packages**: ${
|
|
82
|
+
lines.push(`- **Total packages**: ${graph.nodes.size}`);
|
|
76
83
|
lines.push(`- **Total dependencies**: ${total_deps}`);
|
|
77
84
|
lines.push(`- **Internal dependencies**: ${internal_deps}`);
|
|
78
85
|
lines.push(`- **Wildcard dependencies**: ${analysis.wildcard_deps.length}`);
|
|
@@ -127,8 +134,8 @@ const format_markdown = (repos, graph, analysis, publishing_order) => {
|
|
|
127
134
|
}
|
|
128
135
|
return lines;
|
|
129
136
|
};
|
|
130
|
-
const format_stdout = (
|
|
131
|
-
log.info(st('cyan', `📊 Analyzing ${
|
|
137
|
+
const format_stdout = (graph, analysis, publishing_order, log) => {
|
|
138
|
+
log.info(st('cyan', `📊 Analyzing ${graph.nodes.size} repositories...`));
|
|
132
139
|
// Publishing order
|
|
133
140
|
if (publishing_order) {
|
|
134
141
|
log.info(st('yellow', 'Publishing order:'));
|
|
@@ -175,7 +182,7 @@ const format_stdout = (repos, graph, analysis, publishing_order, log) => {
|
|
|
175
182
|
const { total_deps, internal_deps } = calculate_stats(graph);
|
|
176
183
|
log.info('');
|
|
177
184
|
log.info(st('cyan', 'Summary:'));
|
|
178
|
-
log.info(` Total packages: ${
|
|
185
|
+
log.info(` Total packages: ${graph.nodes.size}`);
|
|
179
186
|
log.info(` Total dependencies: ${total_deps}`);
|
|
180
187
|
log.info(` Internal dependencies: ${internal_deps}`);
|
|
181
188
|
log.info(` Wildcard dependencies: ${analysis.wildcard_deps.length}`);
|