@fuzdev/fuz_gitops 0.57.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/LICENSE +21 -0
- package/README.md +119 -0
- package/dist/ModulesDetail.svelte +180 -0
- package/dist/ModulesDetail.svelte.d.ts +10 -0
- package/dist/ModulesDetail.svelte.d.ts.map +1 -0
- package/dist/ModulesNav.svelte +43 -0
- package/dist/ModulesNav.svelte.d.ts +11 -0
- package/dist/ModulesNav.svelte.d.ts.map +1 -0
- package/dist/ModulesPage.svelte +50 -0
- package/dist/ModulesPage.svelte.d.ts +9 -0
- package/dist/ModulesPage.svelte.d.ts.map +1 -0
- package/dist/PageFooter.svelte +15 -0
- package/dist/PageFooter.svelte.d.ts +19 -0
- package/dist/PageFooter.svelte.d.ts.map +1 -0
- package/dist/PageHeader.svelte +35 -0
- package/dist/PageHeader.svelte.d.ts +19 -0
- package/dist/PageHeader.svelte.d.ts.map +1 -0
- package/dist/PullRequestsDetail.svelte +53 -0
- package/dist/PullRequestsDetail.svelte.d.ts +10 -0
- package/dist/PullRequestsDetail.svelte.d.ts.map +1 -0
- package/dist/PullRequestsPage.svelte +47 -0
- package/dist/PullRequestsPage.svelte.d.ts +11 -0
- package/dist/PullRequestsPage.svelte.d.ts.map +1 -0
- package/dist/ReposTable.svelte +189 -0
- package/dist/ReposTable.svelte.d.ts +9 -0
- package/dist/ReposTable.svelte.d.ts.map +1 -0
- package/dist/ReposTree.svelte +88 -0
- package/dist/ReposTree.svelte.d.ts +11 -0
- package/dist/ReposTree.svelte.d.ts.map +1 -0
- package/dist/ReposTreeNav.svelte +55 -0
- package/dist/ReposTreeNav.svelte.d.ts +11 -0
- package/dist/ReposTreeNav.svelte.d.ts.map +1 -0
- package/dist/TablePage.svelte +46 -0
- package/dist/TablePage.svelte.d.ts +9 -0
- package/dist/TablePage.svelte.d.ts.map +1 -0
- package/dist/TreeItemPage.svelte +75 -0
- package/dist/TreeItemPage.svelte.d.ts +10 -0
- package/dist/TreeItemPage.svelte.d.ts.map +1 -0
- package/dist/TreePage.svelte +64 -0
- package/dist/TreePage.svelte.d.ts +9 -0
- package/dist/TreePage.svelte.d.ts.map +1 -0
- package/dist/changeset_generator.d.ts +38 -0
- package/dist/changeset_generator.d.ts.map +1 -0
- package/dist/changeset_generator.js +110 -0
- package/dist/changeset_reader.d.ts +75 -0
- package/dist/changeset_reader.d.ts.map +1 -0
- package/dist/changeset_reader.js +167 -0
- package/dist/constants.d.ts +9 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +8 -0
- package/dist/dependency_graph.d.ts +120 -0
- package/dist/dependency_graph.d.ts.map +1 -0
- package/dist/dependency_graph.js +341 -0
- package/dist/dependency_updater.d.ts +46 -0
- package/dist/dependency_updater.d.ts.map +1 -0
- package/dist/dependency_updater.js +213 -0
- package/dist/fetch_repo_data.d.ts +19 -0
- package/dist/fetch_repo_data.d.ts.map +1 -0
- package/dist/fetch_repo_data.js +49 -0
- package/dist/fs_fetch_value_cache.d.ts +24 -0
- package/dist/fs_fetch_value_cache.d.ts.map +1 -0
- package/dist/fs_fetch_value_cache.js +61 -0
- package/dist/git_operations.d.ts +54 -0
- package/dist/git_operations.d.ts.map +1 -0
- package/dist/git_operations.js +144 -0
- package/dist/github.d.ts +91 -0
- package/dist/github.d.ts.map +1 -0
- package/dist/github.js +94 -0
- package/dist/github_helpers.d.ts +10 -0
- package/dist/github_helpers.d.ts.map +1 -0
- package/dist/github_helpers.js +13 -0
- package/dist/gitops_analyze.task.d.ts +17 -0
- package/dist/gitops_analyze.task.d.ts.map +1 -0
- package/dist/gitops_analyze.task.js +188 -0
- package/dist/gitops_config.d.ts +56 -0
- package/dist/gitops_config.d.ts.map +1 -0
- package/dist/gitops_config.js +63 -0
- package/dist/gitops_plan.task.d.ts +28 -0
- package/dist/gitops_plan.task.d.ts.map +1 -0
- package/dist/gitops_plan.task.js +217 -0
- package/dist/gitops_publish.task.d.ts +29 -0
- package/dist/gitops_publish.task.d.ts.map +1 -0
- package/dist/gitops_publish.task.js +178 -0
- package/dist/gitops_sync.task.d.ts +18 -0
- package/dist/gitops_sync.task.d.ts.map +1 -0
- package/dist/gitops_sync.task.js +95 -0
- package/dist/gitops_task_helpers.d.ts +63 -0
- package/dist/gitops_task_helpers.d.ts.map +1 -0
- package/dist/gitops_task_helpers.js +84 -0
- package/dist/gitops_validate.task.d.ts +12 -0
- package/dist/gitops_validate.task.d.ts.map +1 -0
- package/dist/gitops_validate.task.js +210 -0
- package/dist/graph_validation.d.ts +39 -0
- package/dist/graph_validation.d.ts.map +1 -0
- package/dist/graph_validation.js +79 -0
- package/dist/local_repo.d.ts +84 -0
- package/dist/local_repo.d.ts.map +1 -0
- package/dist/local_repo.js +213 -0
- package/dist/log_helpers.d.ts +43 -0
- package/dist/log_helpers.d.ts.map +1 -0
- package/dist/log_helpers.js +98 -0
- package/dist/multi_repo_publisher.d.ts +34 -0
- package/dist/multi_repo_publisher.d.ts.map +1 -0
- package/dist/multi_repo_publisher.js +364 -0
- package/dist/npm_install_helpers.d.ts +23 -0
- package/dist/npm_install_helpers.d.ts.map +1 -0
- package/dist/npm_install_helpers.js +60 -0
- package/dist/npm_registry.d.ts +46 -0
- package/dist/npm_registry.d.ts.map +1 -0
- package/dist/npm_registry.js +96 -0
- package/dist/operations.d.ts +409 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/operations.js +34 -0
- package/dist/operations_defaults.d.ts +19 -0
- package/dist/operations_defaults.d.ts.map +1 -0
- package/dist/operations_defaults.js +279 -0
- package/dist/output_helpers.d.ts +27 -0
- package/dist/output_helpers.d.ts.map +1 -0
- package/dist/output_helpers.js +39 -0
- package/dist/paths.d.ts +11 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +10 -0
- package/dist/preflight_checks.d.ts +47 -0
- package/dist/preflight_checks.d.ts.map +1 -0
- package/dist/preflight_checks.js +181 -0
- package/dist/publishing_plan.d.ts +100 -0
- package/dist/publishing_plan.d.ts.map +1 -0
- package/dist/publishing_plan.js +353 -0
- package/dist/publishing_plan_helpers.d.ts +30 -0
- package/dist/publishing_plan_helpers.d.ts.map +1 -0
- package/dist/publishing_plan_helpers.js +112 -0
- package/dist/publishing_plan_logging.d.ts +18 -0
- package/dist/publishing_plan_logging.d.ts.map +1 -0
- package/dist/publishing_plan_logging.js +342 -0
- package/dist/repo.svelte.d.ts +52 -0
- package/dist/repo.svelte.d.ts.map +1 -0
- package/dist/repo.svelte.js +70 -0
- package/dist/repo_ops.d.ts +57 -0
- package/dist/repo_ops.d.ts.map +1 -0
- package/dist/repo_ops.js +167 -0
- package/dist/resolved_gitops_config.d.ts +9 -0
- package/dist/resolved_gitops_config.d.ts.map +1 -0
- package/dist/resolved_gitops_config.js +12 -0
- package/dist/semver.d.ts +24 -0
- package/dist/semver.d.ts.map +1 -0
- package/dist/semver.js +140 -0
- package/dist/serialization_types.d.ts +57 -0
- package/dist/serialization_types.d.ts.map +1 -0
- package/dist/serialization_types.js +40 -0
- package/dist/version_utils.d.ts +48 -0
- package/dist/version_utils.d.ts.map +1 -0
- package/dist/version_utils.js +125 -0
- package/package.json +107 -0
- package/src/lib/changeset_generator.ts +162 -0
- package/src/lib/changeset_reader.ts +218 -0
- package/src/lib/constants.ts +8 -0
- package/src/lib/dependency_graph.ts +423 -0
- package/src/lib/dependency_updater.ts +297 -0
- package/src/lib/fetch_repo_data.ts +64 -0
- package/src/lib/fs_fetch_value_cache.ts +75 -0
- package/src/lib/git_operations.ts +208 -0
- package/src/lib/github.ts +128 -0
- package/src/lib/github_helpers.ts +31 -0
- package/src/lib/gitops_analyze.task.ts +261 -0
- package/src/lib/gitops_config.ts +123 -0
- package/src/lib/gitops_plan.task.ts +272 -0
- package/src/lib/gitops_publish.task.ts +227 -0
- package/src/lib/gitops_sync.task.ts +109 -0
- package/src/lib/gitops_task_helpers.ts +126 -0
- package/src/lib/gitops_validate.task.ts +248 -0
- package/src/lib/graph_validation.ts +109 -0
- package/src/lib/local_repo.ts +359 -0
- package/src/lib/log_helpers.ts +147 -0
- package/src/lib/multi_repo_publisher.ts +464 -0
- package/src/lib/npm_install_helpers.ts +85 -0
- package/src/lib/npm_registry.ts +143 -0
- package/src/lib/operations.ts +334 -0
- package/src/lib/operations_defaults.ts +335 -0
- package/src/lib/output_helpers.ts +64 -0
- package/src/lib/paths.ts +11 -0
- package/src/lib/preflight_checks.ts +269 -0
- package/src/lib/publishing_plan.ts +531 -0
- package/src/lib/publishing_plan_helpers.ts +145 -0
- package/src/lib/publishing_plan_logging.ts +470 -0
- package/src/lib/repo.svelte.ts +95 -0
- package/src/lib/repo_ops.ts +213 -0
- package/src/lib/resolved_gitops_config.ts +27 -0
- package/src/lib/semver.ts +166 -0
- package/src/lib/serialization_types.ts +90 -0
- package/src/lib/version_utils.ts +150 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic repository operations for scripts that work across repos.
|
|
3
|
+
*
|
|
4
|
+
* Provides lightweight utilities for:
|
|
5
|
+
* - Getting repo paths from gitops config (without full git sync)
|
|
6
|
+
* - Walking files in repos with sensible exclusions
|
|
7
|
+
* - Common exclusion patterns for node/svelte projects
|
|
8
|
+
*
|
|
9
|
+
* For full git sync/clone functionality, use `get_gitops_ready()` from gitops_task_helpers.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {existsSync} from 'node:fs';
|
|
13
|
+
import {readdir, stat} from 'node:fs/promises';
|
|
14
|
+
import {join, resolve, dirname} from 'node:path';
|
|
15
|
+
|
|
16
|
+
import {load_gitops_config} from './gitops_config.js';
|
|
17
|
+
import {DEFAULT_REPOS_DIR} from './paths.js';
|
|
18
|
+
|
|
19
|
+
/** Default directories to exclude from file walking */
|
|
20
|
+
export const DEFAULT_EXCLUDE_DIRS = [
|
|
21
|
+
'node_modules',
|
|
22
|
+
'.git',
|
|
23
|
+
'.gro',
|
|
24
|
+
'.svelte-kit',
|
|
25
|
+
'.deno',
|
|
26
|
+
'.vscode',
|
|
27
|
+
'.idea',
|
|
28
|
+
'dist',
|
|
29
|
+
'build',
|
|
30
|
+
'coverage',
|
|
31
|
+
'.cache',
|
|
32
|
+
'.turbo',
|
|
33
|
+
] as const;
|
|
34
|
+
|
|
35
|
+
/** Default binary/non-text extensions to exclude from content processing */
|
|
36
|
+
export const DEFAULT_EXCLUDE_EXTENSIONS = [
|
|
37
|
+
'.png',
|
|
38
|
+
'.jpg',
|
|
39
|
+
'.jpeg',
|
|
40
|
+
'.gif',
|
|
41
|
+
'.svg',
|
|
42
|
+
'.ico',
|
|
43
|
+
'.webp',
|
|
44
|
+
'.woff',
|
|
45
|
+
'.woff2',
|
|
46
|
+
'.ttf',
|
|
47
|
+
'.eot',
|
|
48
|
+
'.mp4',
|
|
49
|
+
'.webm',
|
|
50
|
+
'.mp3',
|
|
51
|
+
'.wav',
|
|
52
|
+
'.ogg',
|
|
53
|
+
'.zip',
|
|
54
|
+
'.tar',
|
|
55
|
+
'.gz',
|
|
56
|
+
'.lock',
|
|
57
|
+
'.pdf',
|
|
58
|
+
] as const;
|
|
59
|
+
|
|
60
|
+
export interface WalkOptions {
|
|
61
|
+
/** Additional directories to exclude (merged with defaults) */
|
|
62
|
+
exclude_dirs?: Array<string>;
|
|
63
|
+
/** Additional extensions to exclude (merged with defaults) */
|
|
64
|
+
exclude_extensions?: Array<string>;
|
|
65
|
+
/** Maximum file size in bytes (default: 10MB) */
|
|
66
|
+
max_file_size?: number;
|
|
67
|
+
/** Include directories in output (default: false) */
|
|
68
|
+
include_dirs?: boolean;
|
|
69
|
+
/** Use only provided exclusions, ignoring defaults */
|
|
70
|
+
no_defaults?: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface RepoPath {
|
|
74
|
+
name: string;
|
|
75
|
+
path: string;
|
|
76
|
+
url: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get repo paths from gitops config without full git sync.
|
|
81
|
+
* Lighter weight than `get_gitops_ready()` - just resolves paths.
|
|
82
|
+
*
|
|
83
|
+
* @param config_path Path to gitops.config.ts (defaults to ./gitops.config.ts)
|
|
84
|
+
* @returns Array of repo info with name, path, and url
|
|
85
|
+
*/
|
|
86
|
+
export const get_repo_paths = async (config_path?: string): Promise<Array<RepoPath>> => {
|
|
87
|
+
const resolved_config_path = resolve(config_path ?? 'gitops.config.ts');
|
|
88
|
+
const config = await load_gitops_config(resolved_config_path);
|
|
89
|
+
|
|
90
|
+
if (!config) {
|
|
91
|
+
throw new Error(`No gitops config found at ${resolved_config_path}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const config_dir = dirname(resolved_config_path);
|
|
95
|
+
const repos_dir = resolve(config_dir, config.repos_dir || DEFAULT_REPOS_DIR);
|
|
96
|
+
|
|
97
|
+
const repos: Array<RepoPath> = [];
|
|
98
|
+
|
|
99
|
+
for (const repo_config of config.repos) {
|
|
100
|
+
const url = repo_config.repo_url;
|
|
101
|
+
const name = url.split('/').at(-1);
|
|
102
|
+
if (!name) continue;
|
|
103
|
+
|
|
104
|
+
const path = repo_config.repo_dir
|
|
105
|
+
? resolve(config_dir, repo_config.repo_dir)
|
|
106
|
+
: join(repos_dir, name);
|
|
107
|
+
|
|
108
|
+
if (existsSync(path)) {
|
|
109
|
+
repos.push({name, path, url});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return repos;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check if a path should be excluded based on options.
|
|
118
|
+
*/
|
|
119
|
+
export const should_exclude_path = (file_path: string, options?: WalkOptions): boolean => {
|
|
120
|
+
const exclude_dirs = options?.no_defaults
|
|
121
|
+
? (options.exclude_dirs ?? [])
|
|
122
|
+
: [...DEFAULT_EXCLUDE_DIRS, ...(options?.exclude_dirs ?? [])];
|
|
123
|
+
|
|
124
|
+
const exclude_extensions = options?.no_defaults
|
|
125
|
+
? (options.exclude_extensions ?? [])
|
|
126
|
+
: [...DEFAULT_EXCLUDE_EXTENSIONS, ...(options?.exclude_extensions ?? [])];
|
|
127
|
+
|
|
128
|
+
const normalized = file_path.toLowerCase();
|
|
129
|
+
|
|
130
|
+
// Check excluded directories
|
|
131
|
+
for (const dir of exclude_dirs) {
|
|
132
|
+
// Must match as a full directory component, not a prefix
|
|
133
|
+
if (normalized.includes(`/${dir}/`) || normalized.endsWith(`/${dir}`)) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Check excluded extensions
|
|
139
|
+
for (const ext of exclude_extensions) {
|
|
140
|
+
if (normalized.endsWith(ext)) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return false;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Walk files in a directory, respecting common exclusions.
|
|
150
|
+
* Yields absolute paths to files (and optionally directories).
|
|
151
|
+
*
|
|
152
|
+
* @param dir Directory to walk
|
|
153
|
+
* @param options Walk options for exclusions and filtering
|
|
154
|
+
*/
|
|
155
|
+
export async function* walk_repo_files(
|
|
156
|
+
dir: string,
|
|
157
|
+
options?: WalkOptions,
|
|
158
|
+
): AsyncGenerator<string, void, undefined> {
|
|
159
|
+
const max_file_size = options?.max_file_size ?? 10 * 1024 * 1024;
|
|
160
|
+
const include_dirs = options?.include_dirs ?? false;
|
|
161
|
+
|
|
162
|
+
async function* walk(current_dir: string): AsyncGenerator<string, void, undefined> {
|
|
163
|
+
let entries;
|
|
164
|
+
try {
|
|
165
|
+
entries = await readdir(current_dir, {withFileTypes: true});
|
|
166
|
+
} catch {
|
|
167
|
+
// Skip directories we can't read
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
for (const entry of entries) {
|
|
172
|
+
const full_path = join(current_dir, entry.name);
|
|
173
|
+
|
|
174
|
+
if (should_exclude_path(full_path, options)) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (entry.isDirectory()) {
|
|
179
|
+
if (include_dirs) {
|
|
180
|
+
yield full_path;
|
|
181
|
+
}
|
|
182
|
+
yield* walk(full_path);
|
|
183
|
+
} else if (entry.isFile()) {
|
|
184
|
+
// Check file size
|
|
185
|
+
try {
|
|
186
|
+
const file_stat = await stat(full_path); // eslint-disable-line no-await-in-loop
|
|
187
|
+
if (file_stat.size <= max_file_size) {
|
|
188
|
+
yield full_path;
|
|
189
|
+
}
|
|
190
|
+
} catch {
|
|
191
|
+
// Skip files we can't stat
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
yield* walk(dir);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Collect all files from walk_repo_files into an array.
|
|
202
|
+
* Convenience function for when you need all paths upfront.
|
|
203
|
+
*/
|
|
204
|
+
export const collect_repo_files = async (
|
|
205
|
+
dir: string,
|
|
206
|
+
options?: WalkOptions,
|
|
207
|
+
): Promise<Array<string>> => {
|
|
208
|
+
const files: Array<string> = [];
|
|
209
|
+
for await (const file of walk_repo_files(dir, options)) {
|
|
210
|
+
files.push(file);
|
|
211
|
+
}
|
|
212
|
+
return files;
|
|
213
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type {GitopsConfig} from './gitops_config.js';
|
|
2
|
+
import {local_repo_locate, type LocalRepoPath, type LocalRepoMissing} from './local_repo.js';
|
|
3
|
+
|
|
4
|
+
export interface ResolvedGitopsConfig {
|
|
5
|
+
local_repos: Array<LocalRepoPath | LocalRepoMissing> | null;
|
|
6
|
+
local_repo_paths: Array<LocalRepoPath> | null;
|
|
7
|
+
local_repos_missing: Array<LocalRepoMissing> | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const resolve_gitops_config = (
|
|
11
|
+
gitops_config: GitopsConfig,
|
|
12
|
+
repos_dir: string,
|
|
13
|
+
): ResolvedGitopsConfig => {
|
|
14
|
+
const local_repos = gitops_config.repos.map((r) =>
|
|
15
|
+
local_repo_locate({repo_config: r, repos_dir}),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const local_repo_paths = local_repos.filter((r) => r.type === 'local_repo_path');
|
|
19
|
+
const local_repos_missing = local_repos.filter((r) => r.type === 'local_repo_missing');
|
|
20
|
+
|
|
21
|
+
const config: ResolvedGitopsConfig = {
|
|
22
|
+
local_repos: local_repos.length ? local_repos : null,
|
|
23
|
+
local_repo_paths: local_repo_paths.length ? local_repo_paths : null,
|
|
24
|
+
local_repos_missing: local_repos_missing.length ? local_repos_missing : null,
|
|
25
|
+
};
|
|
26
|
+
return config;
|
|
27
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic Versioning 2.0.0 utilities
|
|
3
|
+
* @see https://semver.org/
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type BumpType = 'major' | 'minor' | 'patch';
|
|
7
|
+
|
|
8
|
+
export interface Semver {
|
|
9
|
+
major: number;
|
|
10
|
+
minor: number;
|
|
11
|
+
patch: number;
|
|
12
|
+
prerelease?: string;
|
|
13
|
+
build?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* SemVer 2.0.0 validation regex.
|
|
18
|
+
* @see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
|
|
19
|
+
*/
|
|
20
|
+
const SEMVER_REGEX =
|
|
21
|
+
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Parses a semver version string.
|
|
25
|
+
* Accepts optional 'v' prefix for convenience.
|
|
26
|
+
*/
|
|
27
|
+
const semver_parse = (version: string): Semver => {
|
|
28
|
+
// Remove leading 'v' if present (common in git tags)
|
|
29
|
+
const clean = version.replace(/^v/, '');
|
|
30
|
+
|
|
31
|
+
// Validate the cleaned version
|
|
32
|
+
if (!SEMVER_REGEX.test(clean)) {
|
|
33
|
+
throw new Error(`Invalid semver: ${version}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const match = SEMVER_REGEX.exec(clean)!;
|
|
37
|
+
return {
|
|
38
|
+
major: parseInt(match[1]!, 10),
|
|
39
|
+
minor: parseInt(match[2]!, 10),
|
|
40
|
+
patch: parseInt(match[3]!, 10),
|
|
41
|
+
prerelease: match[4],
|
|
42
|
+
build: match[5],
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const semver_to_string = (semver: Semver): string => {
|
|
47
|
+
let version = `${semver.major}.${semver.minor}.${semver.patch}`;
|
|
48
|
+
if (semver.prerelease) {
|
|
49
|
+
version += `-${semver.prerelease}`;
|
|
50
|
+
}
|
|
51
|
+
if (semver.build) {
|
|
52
|
+
version += `+${semver.build}`;
|
|
53
|
+
}
|
|
54
|
+
return version;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Compares two prerelease versions according to SemVer 2.0.0 spec.
|
|
59
|
+
* Returns -1 if a < b, 0 if a === b, 1 if a > b.
|
|
60
|
+
*/
|
|
61
|
+
const semver_compare_prerelease = (a: string | undefined, b: string | undefined): number => {
|
|
62
|
+
// Handle missing prereleases
|
|
63
|
+
if (!a && !b) return 0;
|
|
64
|
+
if (!a) return 1; // normal version > prerelease
|
|
65
|
+
if (!b) return -1; // prerelease < normal version
|
|
66
|
+
|
|
67
|
+
// Split into identifiers
|
|
68
|
+
const a_parts = a.split('.');
|
|
69
|
+
const b_parts = b.split('.');
|
|
70
|
+
|
|
71
|
+
// Compare each identifier
|
|
72
|
+
const min_length = Math.min(a_parts.length, b_parts.length);
|
|
73
|
+
for (let i = 0; i < min_length; i++) {
|
|
74
|
+
const a_part = a_parts[i]!;
|
|
75
|
+
const b_part = b_parts[i]!;
|
|
76
|
+
|
|
77
|
+
// Check if numeric
|
|
78
|
+
const a_is_numeric = /^\d+$/.test(a_part);
|
|
79
|
+
const b_is_numeric = /^\d+$/.test(b_part);
|
|
80
|
+
|
|
81
|
+
if (a_is_numeric && b_is_numeric) {
|
|
82
|
+
// Both numeric - compare numerically
|
|
83
|
+
const a_num = parseInt(a_part, 10);
|
|
84
|
+
const b_num = parseInt(b_part, 10);
|
|
85
|
+
if (a_num !== b_num) {
|
|
86
|
+
return a_num < b_num ? -1 : 1;
|
|
87
|
+
}
|
|
88
|
+
} else if (a_is_numeric && !b_is_numeric) {
|
|
89
|
+
// Numeric identifiers always have lower precedence
|
|
90
|
+
return -1;
|
|
91
|
+
} else if (!a_is_numeric && b_is_numeric) {
|
|
92
|
+
// Numeric identifiers always have lower precedence
|
|
93
|
+
return 1;
|
|
94
|
+
} else {
|
|
95
|
+
// Both alphanumeric - compare lexically
|
|
96
|
+
const cmp = a_part.localeCompare(b_part);
|
|
97
|
+
if (cmp !== 0) {
|
|
98
|
+
return cmp < 0 ? -1 : 1;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// All identifiers equal - larger set has higher precedence
|
|
104
|
+
if (a_parts.length !== b_parts.length) {
|
|
105
|
+
return a_parts.length < b_parts.length ? -1 : 1;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return 0;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Compares two semver versions according to SemVer 2.0.0 spec.
|
|
113
|
+
* Returns -1 if a < b, 0 if a === b, 1 if a > b.
|
|
114
|
+
* Build metadata is ignored in precedence comparison.
|
|
115
|
+
*/
|
|
116
|
+
export const semver_compare_versions = (a: string, b: string): number => {
|
|
117
|
+
const v1 = semver_parse(a);
|
|
118
|
+
const v2 = semver_parse(b);
|
|
119
|
+
|
|
120
|
+
// Compare major
|
|
121
|
+
if (v1.major !== v2.major) {
|
|
122
|
+
return v1.major < v2.major ? -1 : 1;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Compare minor
|
|
126
|
+
if (v1.minor !== v2.minor) {
|
|
127
|
+
return v1.minor < v2.minor ? -1 : 1;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Compare patch
|
|
131
|
+
if (v1.patch !== v2.patch) {
|
|
132
|
+
return v1.patch < v2.patch ? -1 : 1;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Compare prerelease (build metadata is ignored)
|
|
136
|
+
return semver_compare_prerelease(v1.prerelease, v2.prerelease);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Bumps a version according to the specified type.
|
|
141
|
+
* Resets lower version numbers per SemVer spec.
|
|
142
|
+
*/
|
|
143
|
+
export const semver_bump_version = (version: string, type: BumpType): string => {
|
|
144
|
+
const semver = semver_parse(version);
|
|
145
|
+
|
|
146
|
+
switch (type) {
|
|
147
|
+
case 'major':
|
|
148
|
+
semver.major++;
|
|
149
|
+
semver.minor = 0;
|
|
150
|
+
semver.patch = 0;
|
|
151
|
+
break;
|
|
152
|
+
case 'minor':
|
|
153
|
+
semver.minor++;
|
|
154
|
+
semver.patch = 0;
|
|
155
|
+
break;
|
|
156
|
+
case 'patch':
|
|
157
|
+
semver.patch++;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Remove prerelease and build when bumping
|
|
162
|
+
semver.prerelease = undefined;
|
|
163
|
+
semver.build = undefined;
|
|
164
|
+
|
|
165
|
+
return semver_to_string(semver);
|
|
166
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-serializable types for command output formats.
|
|
3
|
+
*
|
|
4
|
+
* Gitops commands support `--format json` and `--format markdown` output modes
|
|
5
|
+
* in addition to styled terminal output. These types define the JSON schema for:
|
|
6
|
+
* - Dependency graph structures (`SerializedGraph`)
|
|
7
|
+
* - Publishing plan predictions (`SerializedPublishingPlan`)
|
|
8
|
+
*
|
|
9
|
+
* Used by `gitops_analyze`, `gitops_plan`, and `gitops_publish --dry_run` when
|
|
10
|
+
* `--format json` or `--outfile` is specified.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type {DependencyGraph} from './dependency_graph.js';
|
|
14
|
+
|
|
15
|
+
export interface SerializedNode {
|
|
16
|
+
name: string;
|
|
17
|
+
version: string;
|
|
18
|
+
dependencies: Array<{
|
|
19
|
+
name: string;
|
|
20
|
+
type: string;
|
|
21
|
+
version: string;
|
|
22
|
+
}>;
|
|
23
|
+
dependents: Array<string>;
|
|
24
|
+
publishable: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SerializedGraph {
|
|
28
|
+
nodes: Array<SerializedNode>;
|
|
29
|
+
edges: Array<[string, string]>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface SerializedPublishingPlan {
|
|
33
|
+
publishing_order: Array<string>;
|
|
34
|
+
version_changes: Array<{
|
|
35
|
+
package_name: string;
|
|
36
|
+
from: string;
|
|
37
|
+
to: string;
|
|
38
|
+
bump_type: string;
|
|
39
|
+
breaking: boolean;
|
|
40
|
+
has_changesets: boolean;
|
|
41
|
+
will_generate_changeset?: boolean;
|
|
42
|
+
needs_bump_escalation?: boolean;
|
|
43
|
+
existing_bump?: string;
|
|
44
|
+
required_bump?: string;
|
|
45
|
+
}>;
|
|
46
|
+
dependency_updates: Array<{
|
|
47
|
+
dependent_package: string;
|
|
48
|
+
updated_dependency: string;
|
|
49
|
+
new_version: string;
|
|
50
|
+
type: string;
|
|
51
|
+
causes_republish: boolean;
|
|
52
|
+
}>;
|
|
53
|
+
breaking_cascades: Record<string, Array<string>>;
|
|
54
|
+
warnings: Array<string>;
|
|
55
|
+
errors: Array<string>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Serializes a dependency graph to a JSON-safe format.
|
|
60
|
+
*/
|
|
61
|
+
export const serialize_graph = (graph: DependencyGraph): SerializedGraph => {
|
|
62
|
+
const nodes: Array<SerializedNode> = [];
|
|
63
|
+
const edges: Array<[string, string]> = [];
|
|
64
|
+
|
|
65
|
+
// Serialize nodes
|
|
66
|
+
for (const node of graph.nodes.values()) {
|
|
67
|
+
const dependencies = Array.from(node.dependencies, ([dep_name, spec]) => ({
|
|
68
|
+
name: dep_name,
|
|
69
|
+
type: spec.type,
|
|
70
|
+
version: spec.version,
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
nodes.push({
|
|
74
|
+
name: node.name,
|
|
75
|
+
version: node.version,
|
|
76
|
+
dependencies,
|
|
77
|
+
dependents: Array.from(node.dependents),
|
|
78
|
+
publishable: node.publishable,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Serialize edges
|
|
83
|
+
for (const [from, tos] of graph.edges) {
|
|
84
|
+
for (const to of tos) {
|
|
85
|
+
edges.push([from, to]);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {nodes, edges};
|
|
90
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type {BumpType} from './semver.js';
|
|
2
|
+
|
|
3
|
+
export const is_wildcard = (version: string): boolean => {
|
|
4
|
+
return version === '*';
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Strips version prefix (^, ~, >=, <=, etc) from a version string.
|
|
9
|
+
*/
|
|
10
|
+
export const strip_version_prefix = (version: string): string => {
|
|
11
|
+
return version.replace(/^(>=|<=|>|<|=|\^|~)/, '');
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Gets the version prefix (^, ~, >=, <=, or empty string).
|
|
16
|
+
*/
|
|
17
|
+
export const get_version_prefix = (version: string): string => {
|
|
18
|
+
const match = /^(>=|<=|>|<|=|\^|~)/.exec(version);
|
|
19
|
+
return match ? match[1]! : '';
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Normalizes version string for comparison.
|
|
24
|
+
*
|
|
25
|
+
* Strips prefixes (^, ~, >=) to get bare version number.
|
|
26
|
+
* Handles wildcards as-is. Used by needs_update to compare versions.
|
|
27
|
+
*
|
|
28
|
+
* @example normalize_version_for_comparison('^1.2.3') // '1.2.3'
|
|
29
|
+
* @example normalize_version_for_comparison('>=2.0.0') // '2.0.0'
|
|
30
|
+
* @example normalize_version_for_comparison('*') // '*'
|
|
31
|
+
*/
|
|
32
|
+
export const normalize_version_for_comparison = (version: string): string => {
|
|
33
|
+
// Handle wildcards
|
|
34
|
+
if (is_wildcard(version)) return version;
|
|
35
|
+
|
|
36
|
+
// Handle >= ranges - extract just the version number
|
|
37
|
+
if (version.startsWith('>=')) {
|
|
38
|
+
return version.substring(2).trim();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Strip other prefixes
|
|
42
|
+
return strip_version_prefix(version);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const needs_update = (current: string, new_version: string): boolean => {
|
|
46
|
+
// Always update wildcards
|
|
47
|
+
if (is_wildcard(current)) return true;
|
|
48
|
+
|
|
49
|
+
// Compare normalized versions
|
|
50
|
+
const current_normalized = normalize_version_for_comparison(current);
|
|
51
|
+
const new_normalized = normalize_version_for_comparison(new_version);
|
|
52
|
+
|
|
53
|
+
return current_normalized !== new_normalized;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Determines version prefix to use when updating dependencies.
|
|
58
|
+
*
|
|
59
|
+
* Strategy:
|
|
60
|
+
* - Wildcard (*): Use caret (^) as default
|
|
61
|
+
* - Has existing prefix: Preserve it (^, ~, >=, <=, etc)
|
|
62
|
+
* - No prefix: Use default_strategy
|
|
63
|
+
*
|
|
64
|
+
* This preserves user intent while handling wildcard replacements sensibly.
|
|
65
|
+
*
|
|
66
|
+
* @param default_strategy prefix to use when no existing prefix found
|
|
67
|
+
*/
|
|
68
|
+
export const get_update_prefix = (
|
|
69
|
+
current_version: string,
|
|
70
|
+
default_strategy: '^' | '~' | '' | '>=' = '^',
|
|
71
|
+
): string => {
|
|
72
|
+
// Use caret for wildcard replacements
|
|
73
|
+
if (is_wildcard(current_version)) {
|
|
74
|
+
return '^';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Preserve existing prefix if present
|
|
78
|
+
const existing_prefix = get_version_prefix(current_version);
|
|
79
|
+
if (existing_prefix) {
|
|
80
|
+
return existing_prefix;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Use default strategy
|
|
84
|
+
return default_strategy;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Determines if a bump is a breaking change based on semver rules.
|
|
89
|
+
* Pre-1.0: minor bumps are breaking
|
|
90
|
+
* 1.0+: major bumps are breaking
|
|
91
|
+
*/
|
|
92
|
+
export const is_breaking_change = (
|
|
93
|
+
old_version: string,
|
|
94
|
+
bump_type: 'major' | 'minor' | 'patch',
|
|
95
|
+
): boolean => {
|
|
96
|
+
const [major] = old_version.split('.').map(Number);
|
|
97
|
+
const is_pre_1_0 = major === 0;
|
|
98
|
+
|
|
99
|
+
if (is_pre_1_0) {
|
|
100
|
+
// In 0.x.x, minor bumps are breaking changes
|
|
101
|
+
return bump_type === 'minor' || bump_type === 'major';
|
|
102
|
+
} else {
|
|
103
|
+
// In 1.x.x+, only major bumps are breaking
|
|
104
|
+
return bump_type === 'major';
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const detect_bump_type = (
|
|
109
|
+
old_version: string,
|
|
110
|
+
new_version: string,
|
|
111
|
+
): 'major' | 'minor' | 'patch' => {
|
|
112
|
+
const old_parts = old_version.split('.').map(Number);
|
|
113
|
+
const new_parts = new_version.split('.').map(Number);
|
|
114
|
+
|
|
115
|
+
if (new_parts[0]! > old_parts[0]!) return 'major';
|
|
116
|
+
if (new_parts[1]! > old_parts[1]!) return 'minor';
|
|
117
|
+
return 'patch';
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Compares bump types. Returns positive if a > b, negative if a < b, 0 if equal.
|
|
122
|
+
*/
|
|
123
|
+
export const compare_bump_types = (a: BumpType, b: BumpType): number => {
|
|
124
|
+
const order: Record<BumpType, number> = {
|
|
125
|
+
major: 3,
|
|
126
|
+
minor: 2,
|
|
127
|
+
patch: 1,
|
|
128
|
+
};
|
|
129
|
+
return order[a] - order[b];
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export const calculate_next_version = (current_version: string, bump_type: BumpType): string => {
|
|
133
|
+
const parts = current_version.split('.').map(Number);
|
|
134
|
+
if (parts.length !== 3 || parts.some((p) => Number.isNaN(p))) {
|
|
135
|
+
throw new Error(`Invalid version format: ${current_version}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const [major, minor, patch] = parts;
|
|
139
|
+
|
|
140
|
+
switch (bump_type) {
|
|
141
|
+
case 'major':
|
|
142
|
+
return `${major! + 1}.0.0`;
|
|
143
|
+
case 'minor':
|
|
144
|
+
return `${major!}.${minor! + 1}.0`;
|
|
145
|
+
case 'patch':
|
|
146
|
+
return `${major!}.${minor!}.${patch! + 1}`;
|
|
147
|
+
default:
|
|
148
|
+
throw new Error(`Invalid bump type: ${bump_type}`);
|
|
149
|
+
}
|
|
150
|
+
};
|