@fuzdev/fuz_gitops 0.57.0 → 0.59.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 +2 -2
- package/README.md +15 -0
- package/dist/ModulesDetail.svelte +5 -4
- package/dist/ModulesDetail.svelte.d.ts +3 -3
- package/dist/ModulesDetail.svelte.d.ts.map +1 -1
- package/dist/ModulesNav.svelte +4 -4
- package/dist/ModulesNav.svelte.d.ts +3 -3
- package/dist/ModulesNav.svelte.d.ts.map +1 -1
- package/dist/ModulesPage.svelte +5 -4
- package/dist/ModulesPage.svelte.d.ts +3 -3
- package/dist/ModulesPage.svelte.d.ts.map +1 -1
- package/dist/PageHeader.svelte +8 -4
- package/dist/PageHeader.svelte.d.ts +3 -3
- package/dist/PageHeader.svelte.d.ts.map +1 -1
- package/dist/PullRequestsDetail.svelte +5 -4
- package/dist/PullRequestsDetail.svelte.d.ts +3 -3
- package/dist/PullRequestsDetail.svelte.d.ts.map +1 -1
- package/dist/PullRequestsPage.svelte +9 -10
- package/dist/PullRequestsPage.svelte.d.ts +5 -5
- package/dist/PullRequestsPage.svelte.d.ts.map +1 -1
- package/dist/ReposTable.svelte +5 -4
- package/dist/ReposTable.svelte.d.ts +3 -3
- package/dist/ReposTable.svelte.d.ts.map +1 -1
- package/dist/ReposTree.svelte +6 -4
- package/dist/ReposTree.svelte.d.ts +3 -3
- package/dist/ReposTree.svelte.d.ts.map +1 -1
- package/dist/ReposTreeNav.svelte +6 -4
- package/dist/ReposTreeNav.svelte.d.ts +3 -3
- package/dist/ReposTreeNav.svelte.d.ts.map +1 -1
- package/dist/TablePage.svelte +5 -4
- package/dist/TablePage.svelte.d.ts +3 -3
- package/dist/TablePage.svelte.d.ts.map +1 -1
- package/dist/TreeItemPage.svelte +6 -4
- package/dist/TreeItemPage.svelte.d.ts +3 -3
- package/dist/TreeItemPage.svelte.d.ts.map +1 -1
- package/dist/TreePage.svelte +5 -4
- package/dist/TreePage.svelte.d.ts +3 -3
- package/dist/TreePage.svelte.d.ts.map +1 -1
- package/dist/fetch_repo_data.d.ts +1 -1
- 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 +1 -1
- package/dist/gitops_analyze.task.d.ts +1 -1
- package/dist/gitops_analyze.task.d.ts.map +1 -1
- package/dist/gitops_analyze.task.js +6 -5
- package/dist/gitops_constants.d.ts +27 -0
- package/dist/gitops_constants.d.ts.map +1 -0
- package/dist/gitops_constants.js +26 -0
- package/dist/gitops_plan.task.d.ts +2 -2
- package/dist/gitops_plan.task.d.ts.map +1 -1
- package/dist/gitops_plan.task.js +7 -6
- package/dist/gitops_publish.task.d.ts +1 -1
- package/dist/gitops_publish.task.d.ts.map +1 -1
- package/dist/gitops_publish.task.js +7 -6
- package/dist/gitops_run.task.d.ts +14 -0
- package/dist/gitops_run.task.d.ts.map +1 -0
- package/dist/gitops_run.task.js +173 -0
- package/dist/gitops_sync.task.d.ts +1 -1
- package/dist/gitops_sync.task.d.ts.map +1 -1
- package/dist/gitops_sync.task.js +6 -5
- package/dist/gitops_task_helpers.d.ts +7 -3
- package/dist/gitops_task_helpers.d.ts.map +1 -1
- package/dist/gitops_task_helpers.js +16 -7
- package/dist/gitops_validate.task.d.ts +1 -1
- package/dist/gitops_validate.task.d.ts.map +1 -1
- package/dist/gitops_validate.task.js +6 -5
- package/dist/local_repo.d.ts +3 -1
- package/dist/local_repo.d.ts.map +1 -1
- package/dist/local_repo.js +34 -3
- package/dist/multi_repo_publisher.d.ts.map +1 -1
- package/dist/multi_repo_publisher.js +6 -6
- package/dist/publishing_plan.js +4 -4
- package/dist/repo_ops.d.ts.map +1 -1
- package/dist/repo_ops.js +2 -1
- package/package.json +11 -11
- package/src/lib/fetch_repo_data.ts +1 -1
- package/src/lib/fs_fetch_value_cache.ts +1 -1
- package/src/lib/gitops_analyze.task.ts +6 -5
- package/src/lib/gitops_constants.ts +30 -0
- package/src/lib/gitops_plan.task.ts +7 -6
- package/src/lib/gitops_publish.task.ts +7 -6
- package/src/lib/gitops_run.task.ts +218 -0
- package/src/lib/gitops_sync.task.ts +6 -5
- package/src/lib/gitops_task_helpers.ts +20 -9
- package/src/lib/gitops_validate.task.ts +6 -5
- package/src/lib/local_repo.ts +45 -2
- package/src/lib/multi_repo_publisher.ts +11 -6
- package/src/lib/publishing_plan.ts +4 -4
- package/src/lib/repo_ops.ts +2 -1
- package/dist/constants.d.ts +0 -9
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js +0 -8
- package/src/lib/constants.ts +0 -8
package/dist/local_repo.js
CHANGED
|
@@ -4,7 +4,9 @@ import { existsSync } from 'node:fs';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { TaskError } from '@ryanatkn/gro';
|
|
6
6
|
import { spawn } from '@fuzdev/fuz_util/process.js';
|
|
7
|
+
import { map_concurrent_settled } from '@fuzdev/fuz_util/async.js';
|
|
7
8
|
import { default_git_operations, default_npm_operations } from './operations_defaults.js';
|
|
9
|
+
import { GITOPS_CONCURRENCY_DEFAULT } from './gitops_constants.js';
|
|
8
10
|
/**
|
|
9
11
|
* Loads repo data with automatic syncing and dependency management.
|
|
10
12
|
*
|
|
@@ -162,10 +164,39 @@ export const local_repos_ensure = async ({ resolved_config, repos_dir, gitops_co
|
|
|
162
164
|
}
|
|
163
165
|
return local_repo_paths;
|
|
164
166
|
};
|
|
165
|
-
export const local_repos_load = async ({ local_repo_paths, log, git_ops = default_git_operations, npm_ops = default_npm_operations, }) => {
|
|
167
|
+
export const local_repos_load = async ({ local_repo_paths, log, git_ops = default_git_operations, npm_ops = default_npm_operations, parallel = true, concurrency = GITOPS_CONCURRENCY_DEFAULT, }) => {
|
|
168
|
+
if (!parallel) {
|
|
169
|
+
// Sequential loading (original behavior)
|
|
170
|
+
const loaded = [];
|
|
171
|
+
for (const local_repo_path of local_repo_paths) {
|
|
172
|
+
loaded.push(await local_repo_load({ local_repo_path, log, git_ops, npm_ops })); // eslint-disable-line no-await-in-loop
|
|
173
|
+
}
|
|
174
|
+
return loaded;
|
|
175
|
+
}
|
|
176
|
+
// Parallel loading with concurrency limit
|
|
177
|
+
const results = await map_concurrent_settled(local_repo_paths, async (local_repo_path) => {
|
|
178
|
+
return local_repo_load({ local_repo_path, log, git_ops, npm_ops });
|
|
179
|
+
}, concurrency);
|
|
180
|
+
// Check for failures and collect successes
|
|
166
181
|
const loaded = [];
|
|
167
|
-
|
|
168
|
-
|
|
182
|
+
const errors = [];
|
|
183
|
+
for (let i = 0; i < results.length; i++) {
|
|
184
|
+
const result = results[i];
|
|
185
|
+
if (result.status === 'fulfilled') {
|
|
186
|
+
loaded.push(result.value);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
const repo_path = local_repo_paths[i];
|
|
190
|
+
errors.push({
|
|
191
|
+
repo_name: repo_path.repo_name,
|
|
192
|
+
error: String(result.reason),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// If any repos failed to load, throw with details
|
|
197
|
+
if (errors.length > 0) {
|
|
198
|
+
const error_details = errors.map((e) => ` ${e.repo_name}: ${e.error}`).join('\n');
|
|
199
|
+
throw new TaskError(`Failed to load ${errors.length} repos:\n${error_details}`);
|
|
169
200
|
}
|
|
170
201
|
return loaded;
|
|
171
202
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multi_repo_publisher.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/multi_repo_publisher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAKpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAsB,KAAK,eAAe,EAAC,MAAM,yBAAyB,CAAC;AAIlF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"multi_repo_publisher.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/multi_repo_publisher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAKpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAsB,KAAK,eAAe,EAAC,MAAM,yBAAyB,CAAC;AAIlF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iBAAiB,CAAC;AAUtD,MAAM,WAAW,iBAAiB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,CAAC,EAAE,eAAe,CAAC;IACnC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,gBAAgB,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IACvC,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,gBAAgB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,EAAE,KAAK,CAAC,gBAAgB,CAAC,CAAC;IACnC,MAAM,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAC,CAAC,CAAC;IAC5C,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,aAAa,GACzB,OAAO,KAAK,CAAC,SAAS,CAAC,EACvB,SAAS,iBAAiB,KACxB,OAAO,CAAC,gBAAgB,CAqU1B,CAAC"}
|
|
@@ -6,7 +6,7 @@ import { validate_dependency_graph } from './graph_validation.js';
|
|
|
6
6
|
import {} from './preflight_checks.js';
|
|
7
7
|
import { needs_update, is_breaking_change, detect_bump_type } from './version_utils.js';
|
|
8
8
|
import { default_gitops_operations } from './operations_defaults.js';
|
|
9
|
-
import {
|
|
9
|
+
import { GITOPS_MAX_ITERATIONS_DEFAULT, GITOPS_NPM_WAIT_TIMEOUT_DEFAULT, } from './gitops_constants.js';
|
|
10
10
|
import { install_with_cache_healing } from './npm_install_helpers.js';
|
|
11
11
|
export const publish_repos = async (repos, options) => {
|
|
12
12
|
const start_time = Date.now();
|
|
@@ -47,9 +47,9 @@ export const publish_repos = async (repos, options) => {
|
|
|
47
47
|
// This handles transitive dependency updates (auto-generated changesets)
|
|
48
48
|
let iteration = 0;
|
|
49
49
|
let converged = false;
|
|
50
|
-
while (!converged && iteration <
|
|
50
|
+
while (!converged && iteration < GITOPS_MAX_ITERATIONS_DEFAULT) {
|
|
51
51
|
iteration++;
|
|
52
|
-
log?.info(st('cyan', `\n🚀 Publishing iteration ${iteration}/${
|
|
52
|
+
log?.info(st('cyan', `\n🚀 Publishing iteration ${iteration}/${GITOPS_MAX_ITERATIONS_DEFAULT}...\n`));
|
|
53
53
|
// Track if any packages were published in this iteration
|
|
54
54
|
let published_in_iteration = false;
|
|
55
55
|
let published_count = 0;
|
|
@@ -108,7 +108,7 @@ export const publish_repos = async (repos, options) => {
|
|
|
108
108
|
max_attempts: 30,
|
|
109
109
|
initial_delay: 1000,
|
|
110
110
|
max_delay: 60000,
|
|
111
|
-
timeout: options.max_wait
|
|
111
|
+
timeout: options.max_wait ?? GITOPS_NPM_WAIT_TIMEOUT_DEFAULT,
|
|
112
112
|
},
|
|
113
113
|
log,
|
|
114
114
|
});
|
|
@@ -186,11 +186,11 @@ export const publish_repos = async (repos, options) => {
|
|
|
186
186
|
converged = true;
|
|
187
187
|
log?.info(st('green', `\n✓ Converged after ${iteration} iteration(s) - no new changesets\n`));
|
|
188
188
|
}
|
|
189
|
-
else if (iteration ===
|
|
189
|
+
else if (iteration === GITOPS_MAX_ITERATIONS_DEFAULT) {
|
|
190
190
|
// Count packages that still have changesets (not yet published)
|
|
191
191
|
const pending_count = order.length - published.size;
|
|
192
192
|
const estimated_iterations = Math.ceil(pending_count / 2); // Rough estimate
|
|
193
|
-
log?.warn(st('yellow', `\n⚠️ Reached maximum iterations (${
|
|
193
|
+
log?.warn(st('yellow', `\n⚠️ Reached maximum iterations (${GITOPS_MAX_ITERATIONS_DEFAULT}) without full convergence\n` +
|
|
194
194
|
` ${pending_count} package(s) may still have changesets to process\n` +
|
|
195
195
|
` Estimated ${estimated_iterations} more iteration(s) needed - run 'gro gitops_publish' again\n`));
|
|
196
196
|
}
|
package/dist/publishing_plan.js
CHANGED
|
@@ -2,7 +2,7 @@ import { styleText as st } from 'node:util';
|
|
|
2
2
|
import { validate_dependency_graph } from './graph_validation.js';
|
|
3
3
|
import { is_breaking_change, compare_bump_types, calculate_next_version } from './version_utils.js';
|
|
4
4
|
import { default_changeset_operations } from './operations_defaults.js';
|
|
5
|
-
import {
|
|
5
|
+
import { GITOPS_MAX_ITERATIONS_DEFAULT } from './gitops_constants.js';
|
|
6
6
|
import { calculate_dependency_updates, get_required_bump_for_dependencies, } from './publishing_plan_helpers.js';
|
|
7
7
|
// Re-export logging functions
|
|
8
8
|
export { log_publishing_plan } from './publishing_plan_logging.js';
|
|
@@ -128,7 +128,7 @@ export const generate_publishing_plan = async (repos, options = {}) => {
|
|
|
128
128
|
// Loop until no new version changes are discovered
|
|
129
129
|
let iteration = 0;
|
|
130
130
|
let changed = true;
|
|
131
|
-
while (changed && iteration <
|
|
131
|
+
while (changed && iteration < GITOPS_MAX_ITERATIONS_DEFAULT) {
|
|
132
132
|
changed = false;
|
|
133
133
|
iteration++;
|
|
134
134
|
// Verbose iteration tracking
|
|
@@ -241,7 +241,7 @@ export const generate_publishing_plan = async (repos, options = {}) => {
|
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
243
|
// Check if we hit iteration limit without convergence
|
|
244
|
-
if (iteration ===
|
|
244
|
+
if (iteration === GITOPS_MAX_ITERATIONS_DEFAULT && changed) {
|
|
245
245
|
// Calculate how many packages still need processing
|
|
246
246
|
const pending_packages = [];
|
|
247
247
|
// Recalculate one more time to see what's pending
|
|
@@ -262,7 +262,7 @@ export const generate_publishing_plan = async (repos, options = {}) => {
|
|
|
262
262
|
// Add warning with diagnostics
|
|
263
263
|
const pending_count = pending_packages.length;
|
|
264
264
|
const estimated_iterations = Math.ceil(pending_count / 2); // Rough estimate
|
|
265
|
-
warnings.push(`Reached maximum iterations (${
|
|
265
|
+
warnings.push(`Reached maximum iterations (${GITOPS_MAX_ITERATIONS_DEFAULT}) without full convergence - ` +
|
|
266
266
|
`${pending_count} package(s) may still need processing: ${pending_packages.join(', ')}. ` +
|
|
267
267
|
`Estimated ${estimated_iterations} more iteration(s) needed.`);
|
|
268
268
|
}
|
package/dist/repo_ops.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repo_ops.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/repo_ops.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"repo_ops.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/repo_ops.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAUH,uDAAuD;AACvD,eAAO,MAAM,oBAAoB,wIAavB,CAAC;AAEX,4EAA4E;AAC5E,eAAO,MAAM,0BAA0B,yLAsB7B,CAAC;AAEX,MAAM,WAAW,WAAW;IAC3B,+DAA+D;IAC/D,YAAY,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACnC,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qDAAqD;IACrD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,sDAAsD;IACtD,WAAW,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,QAAQ;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAU,cAAc,MAAM,KAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CA4BlF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAAI,WAAW,MAAM,EAAE,UAAU,WAAW,KAAG,OA2B9E,CAAC;AAEF;;;;;;GAMG;AACH,wBAAuB,eAAe,CACrC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,WAAW,GACnB,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,CAAC,CAwCzC;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAC9B,KAAK,MAAM,EACX,UAAU,WAAW,KACnB,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAMvB,CAAC"}
|
package/dist/repo_ops.js
CHANGED
|
@@ -13,6 +13,7 @@ import { readdir, stat } from 'node:fs/promises';
|
|
|
13
13
|
import { join, resolve, dirname } from 'node:path';
|
|
14
14
|
import { load_gitops_config } from './gitops_config.js';
|
|
15
15
|
import { DEFAULT_REPOS_DIR } from './paths.js';
|
|
16
|
+
import { GITOPS_CONFIG_PATH_DEFAULT } from './gitops_constants.js';
|
|
16
17
|
/** Default directories to exclude from file walking */
|
|
17
18
|
export const DEFAULT_EXCLUDE_DIRS = [
|
|
18
19
|
'node_modules',
|
|
@@ -60,7 +61,7 @@ export const DEFAULT_EXCLUDE_EXTENSIONS = [
|
|
|
60
61
|
* @returns Array of repo info with name, path, and url
|
|
61
62
|
*/
|
|
62
63
|
export const get_repo_paths = async (config_path) => {
|
|
63
|
-
const resolved_config_path = resolve(config_path ??
|
|
64
|
+
const resolved_config_path = resolve(config_path ?? GITOPS_CONFIG_PATH_DEFAULT);
|
|
64
65
|
const config = await load_gitops_config(resolved_config_path);
|
|
65
66
|
if (!config) {
|
|
66
67
|
throw new Error(`No gitops config found at ${resolved_config_path}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fuzdev/fuz_gitops",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.0",
|
|
4
4
|
"description": "a tool for managing many repos",
|
|
5
5
|
"glyph": "🪄",
|
|
6
6
|
"logo": "logo.svg",
|
|
@@ -40,27 +40,27 @@
|
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@changesets/changelog-git": "^0.2.1",
|
|
43
|
-
"@fuzdev/fuz_code": "^0.
|
|
44
|
-
"@fuzdev/fuz_css": "^0.
|
|
45
|
-
"@fuzdev/fuz_ui": "^0.
|
|
46
|
-
"@fuzdev/fuz_util": "^0.
|
|
43
|
+
"@fuzdev/fuz_code": "^0.38.0",
|
|
44
|
+
"@fuzdev/fuz_css": "^0.42.1",
|
|
45
|
+
"@fuzdev/fuz_ui": "^0.172.0",
|
|
46
|
+
"@fuzdev/fuz_util": "^0.44.1",
|
|
47
47
|
"@ryanatkn/eslint-config": "^0.9.0",
|
|
48
|
-
"@ryanatkn/gro": "^0.
|
|
48
|
+
"@ryanatkn/gro": "^0.182.0",
|
|
49
49
|
"@sveltejs/adapter-static": "^3.0.10",
|
|
50
|
-
"@sveltejs/kit": "^2.49.
|
|
50
|
+
"@sveltejs/kit": "^2.49.1",
|
|
51
51
|
"@sveltejs/package": "^2.5.7",
|
|
52
52
|
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
|
53
53
|
"@types/node": "^24.10.1",
|
|
54
54
|
"eslint": "^9.39.1",
|
|
55
|
-
"eslint-plugin-svelte": "^3.13.
|
|
55
|
+
"eslint-plugin-svelte": "^3.13.1",
|
|
56
56
|
"prettier": "^3.6.2",
|
|
57
57
|
"prettier-plugin-svelte": "^3.4.0",
|
|
58
|
-
"svelte": "^5.45.
|
|
58
|
+
"svelte": "^5.45.6",
|
|
59
59
|
"svelte-check": "^4.3.4",
|
|
60
60
|
"tslib": "^2.8.1",
|
|
61
61
|
"typescript": "^5.9.3",
|
|
62
|
-
"typescript-eslint": "^8.48.
|
|
63
|
-
"vitest": "^4.0.
|
|
62
|
+
"typescript-eslint": "^8.48.1",
|
|
63
|
+
"vitest": "^4.0.15"
|
|
64
64
|
},
|
|
65
65
|
"prettier": {
|
|
66
66
|
"plugins": [
|
|
@@ -18,7 +18,7 @@ import type {LocalRepo} from './local_repo.js';
|
|
|
18
18
|
* Repos with failed fetches will have `null` for check_runs or pull_requests.
|
|
19
19
|
*
|
|
20
20
|
* @param delay milliseconds between API requests (default: 33ms)
|
|
21
|
-
* @param cache optional cache from
|
|
21
|
+
* @param cache optional cache from fuz_util's fetch.js for response memoization
|
|
22
22
|
* @returns array of Repo objects with GitHub metadata attached
|
|
23
23
|
*/
|
|
24
24
|
export const fetch_repo_data = async (
|
|
@@ -17,7 +17,7 @@ export interface FetchCache {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* Creates file-system backed cache for
|
|
20
|
+
* Creates file-system backed cache for fuz_util's fetch.js API responses.
|
|
21
21
|
*
|
|
22
22
|
* Cache invalidation strategy: If cache file can't be read or parsed, entire
|
|
23
23
|
* cache is cleared (delete file) and starts fresh. This handles format changes.
|
|
@@ -13,16 +13,17 @@ import {
|
|
|
13
13
|
format_production_cycles,
|
|
14
14
|
} from './log_helpers.js';
|
|
15
15
|
import {format_and_output, type OutputFormatters} from './output_helpers.js';
|
|
16
|
+
import {GITOPS_CONFIG_PATH_DEFAULT} from './gitops_constants.js';
|
|
16
17
|
|
|
17
18
|
/** @nodocs */
|
|
18
19
|
export const Args = z.strictObject({
|
|
19
|
-
|
|
20
|
+
config: z
|
|
20
21
|
.string()
|
|
21
22
|
.meta({description: 'path to the gitops config file, absolute or relative to the cwd'})
|
|
22
|
-
.default(
|
|
23
|
+
.default(GITOPS_CONFIG_PATH_DEFAULT),
|
|
23
24
|
dir: z
|
|
24
25
|
.string()
|
|
25
|
-
.meta({description: 'path containing the repos, defaults to the parent of the
|
|
26
|
+
.meta({description: 'path containing the repos, defaults to the parent of the config dir'})
|
|
26
27
|
.optional(),
|
|
27
28
|
format: z
|
|
28
29
|
.enum(['stdout', 'json', 'markdown'])
|
|
@@ -37,10 +38,10 @@ export const task: Task<Args> = {
|
|
|
37
38
|
Args,
|
|
38
39
|
summary: 'analyze dependency structure and relationships across repos',
|
|
39
40
|
run: async ({args, log}) => {
|
|
40
|
-
const {
|
|
41
|
+
const {config, dir, format, outfile} = args;
|
|
41
42
|
|
|
42
43
|
// Get repos ready (without downloading)
|
|
43
|
-
const {local_repos} = await get_gitops_ready({
|
|
44
|
+
const {local_repos} = await get_gitops_ready({config, dir, download: false, log});
|
|
44
45
|
|
|
45
46
|
// Build dependency graph and validate (but don't throw on cycles for analyze)
|
|
46
47
|
const {graph, publishing_order: order} = validate_dependency_graph(local_repos, {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants for gitops tasks and operations.
|
|
3
|
+
*
|
|
4
|
+
* Naming convention: GITOPS_{NAME}_DEFAULT for user-facing defaults.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Maximum number of iterations for fixed-point iteration during publishing.
|
|
9
|
+
* Used in both plan generation and actual publishing to resolve transitive dependency cascades.
|
|
10
|
+
*
|
|
11
|
+
* In practice, most repos converge in 2-3 iterations.
|
|
12
|
+
* Deep dependency chains may require more iterations.
|
|
13
|
+
*/
|
|
14
|
+
export const GITOPS_MAX_ITERATIONS_DEFAULT = 10;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Default path to the gitops configuration file.
|
|
18
|
+
*/
|
|
19
|
+
export const GITOPS_CONFIG_PATH_DEFAULT = 'gitops.config.ts';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Default number of repos to process concurrently during parallel operations.
|
|
23
|
+
*/
|
|
24
|
+
export const GITOPS_CONCURRENCY_DEFAULT = 5;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Default timeout in milliseconds for waiting on NPM package propagation (10 minutes).
|
|
28
|
+
* NPM's CDN uses eventual consistency, so published packages may not be immediately available.
|
|
29
|
+
*/
|
|
30
|
+
export const GITOPS_NPM_WAIT_TIMEOUT_DEFAULT = 600_000; // 10 minutes
|
|
@@ -10,16 +10,17 @@ import {
|
|
|
10
10
|
type LogPlanOptions,
|
|
11
11
|
} from './publishing_plan.js';
|
|
12
12
|
import {format_and_output, type OutputFormatters} from './output_helpers.js';
|
|
13
|
+
import {GITOPS_CONFIG_PATH_DEFAULT} from './gitops_constants.js';
|
|
13
14
|
|
|
14
15
|
/** @nodocs */
|
|
15
16
|
export const Args = z.strictObject({
|
|
16
|
-
|
|
17
|
+
config: z
|
|
17
18
|
.string()
|
|
18
19
|
.meta({description: 'path to the gitops config file, absolute or relative to the cwd'})
|
|
19
|
-
.default(
|
|
20
|
+
.default(GITOPS_CONFIG_PATH_DEFAULT),
|
|
20
21
|
dir: z
|
|
21
22
|
.string()
|
|
22
|
-
.meta({description: 'path containing the repos, defaults to the parent of the
|
|
23
|
+
.meta({description: 'path containing the repos, defaults to the parent of the config dir'})
|
|
23
24
|
.optional(),
|
|
24
25
|
format: z
|
|
25
26
|
.enum(['stdout', 'json', 'markdown'])
|
|
@@ -37,7 +38,7 @@ export type Args = z.infer<typeof Args>;
|
|
|
37
38
|
* Usage:
|
|
38
39
|
* gro gitops_plan
|
|
39
40
|
* gro gitops_plan --dir ../repos
|
|
40
|
-
* gro gitops_plan --
|
|
41
|
+
* gro gitops_plan --config ./custom.config.ts
|
|
41
42
|
*
|
|
42
43
|
* @nodocs
|
|
43
44
|
*/
|
|
@@ -45,13 +46,13 @@ export const task: Task<Args> = {
|
|
|
45
46
|
summary: 'generate a publishing plan based on changesets',
|
|
46
47
|
Args,
|
|
47
48
|
run: async ({args, log}): Promise<void> => {
|
|
48
|
-
const {dir,
|
|
49
|
+
const {dir, config, format, outfile, verbose} = args;
|
|
49
50
|
|
|
50
51
|
log.info(st('cyan', 'Generating multi-repo publishing plan...'));
|
|
51
52
|
|
|
52
53
|
// Load local repos
|
|
53
54
|
const {local_repos} = await get_gitops_ready({
|
|
54
|
-
|
|
55
|
+
config,
|
|
55
56
|
dir,
|
|
56
57
|
download: false, // Don't download if missing
|
|
57
58
|
log,
|
|
@@ -11,16 +11,17 @@ import {
|
|
|
11
11
|
} from './multi_repo_publisher.js';
|
|
12
12
|
import {generate_publishing_plan, log_publishing_plan} from './publishing_plan.js';
|
|
13
13
|
import {format_and_output, type OutputFormatters} from './output_helpers.js';
|
|
14
|
+
import {GITOPS_CONFIG_PATH_DEFAULT, GITOPS_NPM_WAIT_TIMEOUT_DEFAULT} from './gitops_constants.js';
|
|
14
15
|
|
|
15
16
|
/** @nodocs */
|
|
16
17
|
export const Args = z.strictObject({
|
|
17
|
-
|
|
18
|
+
config: z
|
|
18
19
|
.string()
|
|
19
20
|
.meta({description: 'path to the gitops config file, absolute or relative to the cwd'})
|
|
20
|
-
.default(
|
|
21
|
+
.default(GITOPS_CONFIG_PATH_DEFAULT),
|
|
21
22
|
dir: z
|
|
22
23
|
.string()
|
|
23
|
-
.meta({description: 'path containing the repos, defaults to the parent of the
|
|
24
|
+
.meta({description: 'path containing the repos, defaults to the parent of the config dir'})
|
|
24
25
|
.optional(),
|
|
25
26
|
peer_strategy: z
|
|
26
27
|
.enum(['exact', 'caret', 'tilde'])
|
|
@@ -43,7 +44,7 @@ export const Args = z.strictObject({
|
|
|
43
44
|
max_wait: z
|
|
44
45
|
.number()
|
|
45
46
|
.meta({description: 'max time to wait for npm propagation in ms'})
|
|
46
|
-
.default(
|
|
47
|
+
.default(GITOPS_NPM_WAIT_TIMEOUT_DEFAULT),
|
|
47
48
|
skip_install: z
|
|
48
49
|
.boolean()
|
|
49
50
|
.meta({description: 'skip npm install after dependency updates'})
|
|
@@ -59,7 +60,7 @@ export const task: Task<Args> = {
|
|
|
59
60
|
Args,
|
|
60
61
|
run: async ({args, log}): Promise<void> => {
|
|
61
62
|
const {
|
|
62
|
-
|
|
63
|
+
config,
|
|
63
64
|
dir,
|
|
64
65
|
peer_strategy,
|
|
65
66
|
dry_run,
|
|
@@ -74,7 +75,7 @@ export const task: Task<Args> = {
|
|
|
74
75
|
|
|
75
76
|
// Load repos
|
|
76
77
|
const {local_repos: repos} = await get_gitops_ready({
|
|
77
|
-
|
|
78
|
+
config,
|
|
78
79
|
dir,
|
|
79
80
|
download: false, // Don't download if missing
|
|
80
81
|
log,
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import {TaskError, type Task} from '@ryanatkn/gro';
|
|
2
|
+
import {z} from 'zod';
|
|
3
|
+
import {map_concurrent_settled} from '@fuzdev/fuz_util/async.js';
|
|
4
|
+
import {spawn_out} from '@fuzdev/fuz_util/process.js';
|
|
5
|
+
import {styleText as st} from 'node:util';
|
|
6
|
+
import {resolve} from 'node:path';
|
|
7
|
+
|
|
8
|
+
import {get_repo_paths} from './repo_ops.js';
|
|
9
|
+
import {GITOPS_CONCURRENCY_DEFAULT, GITOPS_CONFIG_PATH_DEFAULT} from './gitops_constants.js';
|
|
10
|
+
|
|
11
|
+
export const Args = z.strictObject({
|
|
12
|
+
command: z.string().meta({description: 'shell command to run in each repo'}),
|
|
13
|
+
config: z
|
|
14
|
+
.string()
|
|
15
|
+
.meta({description: 'path to the gitops config file'})
|
|
16
|
+
.default(GITOPS_CONFIG_PATH_DEFAULT),
|
|
17
|
+
concurrency: z
|
|
18
|
+
.number()
|
|
19
|
+
.int()
|
|
20
|
+
.min(1)
|
|
21
|
+
.meta({description: 'maximum number of repos to run in parallel'})
|
|
22
|
+
.default(GITOPS_CONCURRENCY_DEFAULT),
|
|
23
|
+
format: z.enum(['text', 'json']).meta({description: 'output format'}).default('text'),
|
|
24
|
+
});
|
|
25
|
+
export type Args = z.infer<typeof Args>;
|
|
26
|
+
|
|
27
|
+
interface RunResult {
|
|
28
|
+
repo_name: string;
|
|
29
|
+
repo_dir: string;
|
|
30
|
+
status: 'success' | 'failure';
|
|
31
|
+
exit_code: number;
|
|
32
|
+
stdout: string;
|
|
33
|
+
stderr: string;
|
|
34
|
+
duration_ms: number;
|
|
35
|
+
error?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const task: Task<Args> = {
|
|
39
|
+
Args,
|
|
40
|
+
summary: 'run a shell command across all repos in parallel',
|
|
41
|
+
run: async ({args, log}) => {
|
|
42
|
+
const {command, config, concurrency, format} = args;
|
|
43
|
+
|
|
44
|
+
// Get repo paths (lightweight, no library.ts loading needed)
|
|
45
|
+
const config_path = resolve(config);
|
|
46
|
+
const repos = await get_repo_paths(config_path);
|
|
47
|
+
|
|
48
|
+
if (repos.length === 0) {
|
|
49
|
+
throw new TaskError('No repos found in config');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
log.info(
|
|
53
|
+
`Running ${st('cyan', command)} across ${repos.length} repos (concurrency: ${concurrency})`,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const start_time = performance.now();
|
|
57
|
+
|
|
58
|
+
// Run command in parallel across all repos
|
|
59
|
+
const results = await map_concurrent_settled(
|
|
60
|
+
repos,
|
|
61
|
+
async (repo) => {
|
|
62
|
+
const repo_start = performance.now();
|
|
63
|
+
const repo_name = repo.name;
|
|
64
|
+
const repo_dir = repo.path;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
// Parse command into cmd + args for spawn
|
|
68
|
+
// For now, we use shell mode to support pipes/redirects/etc
|
|
69
|
+
const spawned = await spawn_out('sh', ['-c', command], {
|
|
70
|
+
cwd: repo_dir,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const duration_ms = performance.now() - repo_start;
|
|
74
|
+
const success = spawned.result.ok;
|
|
75
|
+
|
|
76
|
+
const result: RunResult = {
|
|
77
|
+
repo_name,
|
|
78
|
+
repo_dir,
|
|
79
|
+
status: success ? 'success' : 'failure',
|
|
80
|
+
exit_code: spawned.result.code ?? 0,
|
|
81
|
+
stdout: spawned.stdout || '',
|
|
82
|
+
stderr: spawned.stderr || '',
|
|
83
|
+
duration_ms,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return result;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
const duration_ms = performance.now() - repo_start;
|
|
89
|
+
return {
|
|
90
|
+
repo_name,
|
|
91
|
+
repo_dir,
|
|
92
|
+
status: 'failure' as const,
|
|
93
|
+
exit_code: -1,
|
|
94
|
+
stdout: '',
|
|
95
|
+
stderr: '',
|
|
96
|
+
duration_ms,
|
|
97
|
+
error: String(error),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
concurrency,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const total_duration_ms = performance.now() - start_time;
|
|
105
|
+
|
|
106
|
+
// Process results
|
|
107
|
+
const successes: Array<RunResult> = [];
|
|
108
|
+
const failures: Array<RunResult> = [];
|
|
109
|
+
|
|
110
|
+
for (const result of results) {
|
|
111
|
+
if (result.status === 'fulfilled') {
|
|
112
|
+
const run_result = result.value;
|
|
113
|
+
if (run_result.status === 'success') {
|
|
114
|
+
successes.push(run_result);
|
|
115
|
+
} else {
|
|
116
|
+
failures.push(run_result);
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
// This shouldn't happen since we catch errors in the task fn
|
|
120
|
+
// but handle it anyway
|
|
121
|
+
failures.push({
|
|
122
|
+
repo_name: 'unknown',
|
|
123
|
+
repo_dir: 'unknown',
|
|
124
|
+
status: 'failure',
|
|
125
|
+
exit_code: -1,
|
|
126
|
+
stdout: '',
|
|
127
|
+
stderr: '',
|
|
128
|
+
duration_ms: 0,
|
|
129
|
+
error: String(result.reason),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Output results based on format
|
|
135
|
+
if (format === 'json') {
|
|
136
|
+
const json_output = {
|
|
137
|
+
command,
|
|
138
|
+
concurrency,
|
|
139
|
+
repos: [...successes, ...failures],
|
|
140
|
+
summary: {
|
|
141
|
+
total: repos.length,
|
|
142
|
+
success: successes.length,
|
|
143
|
+
failure: failures.length,
|
|
144
|
+
duration_ms: Math.round(total_duration_ms),
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
// eslint-disable-next-line no-console
|
|
148
|
+
console.log(JSON.stringify(json_output, null, 2));
|
|
149
|
+
} else {
|
|
150
|
+
// Text format
|
|
151
|
+
log.info(''); // blank line
|
|
152
|
+
|
|
153
|
+
// Show successes
|
|
154
|
+
if (successes.length > 0) {
|
|
155
|
+
log.info(st('green', `✓ ${successes.length} succeeded:`));
|
|
156
|
+
for (const result of successes) {
|
|
157
|
+
const duration = `${Math.round(result.duration_ms)}ms`;
|
|
158
|
+
log.info(st('gray', ` ${result.repo_name} ${st('blue', `(${duration})`)}`));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Show failures with details
|
|
163
|
+
if (failures.length > 0) {
|
|
164
|
+
log.info(''); // blank line
|
|
165
|
+
log.error(st('red', `✗ ${failures.length} failed:`));
|
|
166
|
+
for (const result of failures) {
|
|
167
|
+
const duration = `${Math.round(result.duration_ms)}ms`;
|
|
168
|
+
log.error(st('gray', ` ${result.repo_name} ${st('blue', `(${duration})`)}`));
|
|
169
|
+
|
|
170
|
+
if (result.error) {
|
|
171
|
+
log.error(st('gray', ` Error: ${result.error}`));
|
|
172
|
+
} else if (result.exit_code !== 0) {
|
|
173
|
+
log.error(st('gray', ` Exit code: ${result.exit_code}`));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (result.stderr) {
|
|
177
|
+
// Show first few lines of stderr
|
|
178
|
+
const stderr_lines = result.stderr.trim().split('\n');
|
|
179
|
+
const preview_lines = stderr_lines.slice(0, 3);
|
|
180
|
+
for (const line of preview_lines) {
|
|
181
|
+
log.error(st('gray', ` ${line}`));
|
|
182
|
+
}
|
|
183
|
+
if (stderr_lines.length > 3) {
|
|
184
|
+
log.error(st('gray', ` ... (${stderr_lines.length - 3} more lines)`));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Summary
|
|
191
|
+
log.info(''); // blank line
|
|
192
|
+
const total = repos.length;
|
|
193
|
+
const success_rate = ((successes.length / total) * 100).toFixed(0);
|
|
194
|
+
const duration = `${Math.round(total_duration_ms)}ms`;
|
|
195
|
+
|
|
196
|
+
if (failures.length === 0) {
|
|
197
|
+
log.info(
|
|
198
|
+
st(
|
|
199
|
+
'green',
|
|
200
|
+
`✓ All ${total} repos succeeded in ${duration} (${success_rate}% success rate)`,
|
|
201
|
+
),
|
|
202
|
+
);
|
|
203
|
+
} else {
|
|
204
|
+
log.info(
|
|
205
|
+
st(
|
|
206
|
+
'yellow',
|
|
207
|
+
`⚠ ${successes.length}/${total} repos succeeded in ${duration} (${success_rate}% success rate)`,
|
|
208
|
+
),
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Exit with error if any failures (so CI fails)
|
|
214
|
+
if (failures.length > 0) {
|
|
215
|
+
throw new TaskError(`${failures.length} repos failed`);
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
};
|
|
@@ -11,18 +11,19 @@ import {existsSync} from 'node:fs';
|
|
|
11
11
|
import {fetch_repo_data} from './fetch_repo_data.js';
|
|
12
12
|
import {create_fs_fetch_value_cache} from './fs_fetch_value_cache.js';
|
|
13
13
|
import {get_gitops_ready} from './gitops_task_helpers.js';
|
|
14
|
+
import {GITOPS_CONFIG_PATH_DEFAULT} from './gitops_constants.js';
|
|
14
15
|
|
|
15
16
|
// TODO add flag to ignore or invalidate cache -- no-cache? clean?
|
|
16
17
|
|
|
17
18
|
/** @nodocs */
|
|
18
19
|
export const Args = z.strictObject({
|
|
19
|
-
|
|
20
|
+
config: z
|
|
20
21
|
.string()
|
|
21
22
|
.meta({description: 'path to the gitops config file, absolute or relative to the cwd'})
|
|
22
|
-
.default(
|
|
23
|
+
.default(GITOPS_CONFIG_PATH_DEFAULT),
|
|
23
24
|
dir: z
|
|
24
25
|
.string()
|
|
25
|
-
.meta({description: 'path containing the repos, defaults to the parent of the
|
|
26
|
+
.meta({description: 'path containing the repos, defaults to the parent of the config dir'})
|
|
26
27
|
.optional(),
|
|
27
28
|
outdir: z
|
|
28
29
|
.string()
|
|
@@ -45,9 +46,9 @@ export const task: Task<Args> = {
|
|
|
45
46
|
Args,
|
|
46
47
|
summary: 'syncs local repos and generates UI data from repo metadata',
|
|
47
48
|
run: async ({args, log, svelte_config, invoke_task}) => {
|
|
48
|
-
const {
|
|
49
|
+
const {config, dir, outdir = svelte_config.routes_path, download, check} = args;
|
|
49
50
|
|
|
50
|
-
const {local_repos} = await get_gitops_ready({
|
|
51
|
+
const {local_repos} = await get_gitops_ready({config, dir, download, log});
|
|
51
52
|
|
|
52
53
|
const outfile = resolve(outdir, 'repos.ts');
|
|
53
54
|
|