@fuzdev/fuz_gitops 0.58.0 → 0.60.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.
Files changed (93) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +15 -0
  3. package/dist/ModulesDetail.svelte +5 -4
  4. package/dist/ModulesDetail.svelte.d.ts +3 -3
  5. package/dist/ModulesDetail.svelte.d.ts.map +1 -1
  6. package/dist/ModulesNav.svelte +4 -4
  7. package/dist/ModulesNav.svelte.d.ts +3 -3
  8. package/dist/ModulesNav.svelte.d.ts.map +1 -1
  9. package/dist/ModulesPage.svelte +5 -4
  10. package/dist/ModulesPage.svelte.d.ts +3 -3
  11. package/dist/ModulesPage.svelte.d.ts.map +1 -1
  12. package/dist/PageHeader.svelte +8 -4
  13. package/dist/PageHeader.svelte.d.ts +3 -3
  14. package/dist/PageHeader.svelte.d.ts.map +1 -1
  15. package/dist/PullRequestsDetail.svelte +5 -4
  16. package/dist/PullRequestsDetail.svelte.d.ts +3 -3
  17. package/dist/PullRequestsDetail.svelte.d.ts.map +1 -1
  18. package/dist/PullRequestsPage.svelte +9 -10
  19. package/dist/PullRequestsPage.svelte.d.ts +5 -5
  20. package/dist/PullRequestsPage.svelte.d.ts.map +1 -1
  21. package/dist/ReposTable.svelte +5 -4
  22. package/dist/ReposTable.svelte.d.ts +3 -3
  23. package/dist/ReposTable.svelte.d.ts.map +1 -1
  24. package/dist/ReposTree.svelte +6 -4
  25. package/dist/ReposTree.svelte.d.ts +3 -3
  26. package/dist/ReposTree.svelte.d.ts.map +1 -1
  27. package/dist/ReposTreeNav.svelte +6 -4
  28. package/dist/ReposTreeNav.svelte.d.ts +3 -3
  29. package/dist/ReposTreeNav.svelte.d.ts.map +1 -1
  30. package/dist/TablePage.svelte +5 -4
  31. package/dist/TablePage.svelte.d.ts +3 -3
  32. package/dist/TablePage.svelte.d.ts.map +1 -1
  33. package/dist/TreeItemPage.svelte +6 -4
  34. package/dist/TreeItemPage.svelte.d.ts +3 -3
  35. package/dist/TreeItemPage.svelte.d.ts.map +1 -1
  36. package/dist/TreePage.svelte +5 -4
  37. package/dist/TreePage.svelte.d.ts +3 -3
  38. package/dist/TreePage.svelte.d.ts.map +1 -1
  39. package/dist/fetch_repo_data.d.ts +1 -1
  40. package/dist/fetch_repo_data.js +1 -1
  41. package/dist/fs_fetch_value_cache.d.ts +1 -1
  42. package/dist/fs_fetch_value_cache.js +1 -1
  43. package/dist/gitops_analyze.task.d.ts +1 -1
  44. package/dist/gitops_analyze.task.d.ts.map +1 -1
  45. package/dist/gitops_analyze.task.js +6 -5
  46. package/dist/gitops_constants.d.ts +27 -0
  47. package/dist/gitops_constants.d.ts.map +1 -0
  48. package/dist/gitops_constants.js +26 -0
  49. package/dist/gitops_plan.task.d.ts +2 -2
  50. package/dist/gitops_plan.task.d.ts.map +1 -1
  51. package/dist/gitops_plan.task.js +7 -6
  52. package/dist/gitops_publish.task.d.ts +1 -1
  53. package/dist/gitops_publish.task.d.ts.map +1 -1
  54. package/dist/gitops_publish.task.js +7 -6
  55. package/dist/gitops_run.task.d.ts +14 -0
  56. package/dist/gitops_run.task.d.ts.map +1 -0
  57. package/dist/gitops_run.task.js +173 -0
  58. package/dist/gitops_sync.task.d.ts +1 -1
  59. package/dist/gitops_sync.task.d.ts.map +1 -1
  60. package/dist/gitops_sync.task.js +8 -7
  61. package/dist/gitops_task_helpers.d.ts +7 -3
  62. package/dist/gitops_task_helpers.d.ts.map +1 -1
  63. package/dist/gitops_task_helpers.js +16 -7
  64. package/dist/gitops_validate.task.d.ts +1 -1
  65. package/dist/gitops_validate.task.d.ts.map +1 -1
  66. package/dist/gitops_validate.task.js +6 -5
  67. package/dist/local_repo.d.ts +3 -1
  68. package/dist/local_repo.d.ts.map +1 -1
  69. package/dist/local_repo.js +34 -3
  70. package/dist/multi_repo_publisher.d.ts.map +1 -1
  71. package/dist/multi_repo_publisher.js +6 -6
  72. package/dist/publishing_plan.js +4 -4
  73. package/dist/repo_ops.d.ts.map +1 -1
  74. package/dist/repo_ops.js +2 -1
  75. package/package.json +5 -6
  76. package/src/lib/fetch_repo_data.ts +1 -1
  77. package/src/lib/fs_fetch_value_cache.ts +1 -1
  78. package/src/lib/gitops_analyze.task.ts +6 -5
  79. package/src/lib/gitops_constants.ts +30 -0
  80. package/src/lib/gitops_plan.task.ts +7 -6
  81. package/src/lib/gitops_publish.task.ts +7 -6
  82. package/src/lib/gitops_run.task.ts +218 -0
  83. package/src/lib/gitops_sync.task.ts +8 -7
  84. package/src/lib/gitops_task_helpers.ts +20 -9
  85. package/src/lib/gitops_validate.task.ts +6 -5
  86. package/src/lib/local_repo.ts +45 -2
  87. package/src/lib/multi_repo_publisher.ts +11 -6
  88. package/src/lib/publishing_plan.ts +4 -4
  89. package/src/lib/repo_ops.ts +2 -1
  90. package/dist/constants.d.ts +0 -9
  91. package/dist/constants.d.ts.map +0 -1
  92. package/dist/constants.js +0 -8
  93. package/src/lib/constants.ts +0 -8
@@ -5,15 +5,16 @@ import { DependencyGraphBuilder } from './dependency_graph.js';
5
5
  import { validate_dependency_graph } from './graph_validation.js';
6
6
  import { format_wildcard_dependencies, format_dev_cycles, format_production_cycles, } from './log_helpers.js';
7
7
  import { format_and_output } from './output_helpers.js';
8
+ import { GITOPS_CONFIG_PATH_DEFAULT } from './gitops_constants.js';
8
9
  /** @nodocs */
9
10
  export const Args = z.strictObject({
10
- path: z
11
+ config: z
11
12
  .string()
12
13
  .meta({ description: 'path to the gitops config file, absolute or relative to the cwd' })
13
- .default('gitops.config.ts'),
14
+ .default(GITOPS_CONFIG_PATH_DEFAULT),
14
15
  dir: z
15
16
  .string()
16
- .meta({ description: 'path containing the repos, defaults to the parent of the `path` dir' })
17
+ .meta({ description: 'path containing the repos, defaults to the parent of the config dir' })
17
18
  .optional(),
18
19
  format: z
19
20
  .enum(['stdout', 'json', 'markdown'])
@@ -26,9 +27,9 @@ export const task = {
26
27
  Args,
27
28
  summary: 'analyze dependency structure and relationships across repos',
28
29
  run: async ({ args, log }) => {
29
- const { path, dir, format, outfile } = args;
30
+ const { config, dir, format, outfile } = args;
30
31
  // Get repos ready (without downloading)
31
- const { local_repos } = await get_gitops_ready({ path, dir, download: false, log });
32
+ const { local_repos } = await get_gitops_ready({ config, dir, download: false, log });
32
33
  // Build dependency graph and validate (but don't throw on cycles for analyze)
33
34
  const { graph, publishing_order: order } = validate_dependency_graph(local_repos, {
34
35
  log,
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Shared constants for gitops tasks and operations.
3
+ *
4
+ * Naming convention: GITOPS_{NAME}_DEFAULT for user-facing defaults.
5
+ */
6
+ /**
7
+ * Maximum number of iterations for fixed-point iteration during publishing.
8
+ * Used in both plan generation and actual publishing to resolve transitive dependency cascades.
9
+ *
10
+ * In practice, most repos converge in 2-3 iterations.
11
+ * Deep dependency chains may require more iterations.
12
+ */
13
+ export declare const GITOPS_MAX_ITERATIONS_DEFAULT = 10;
14
+ /**
15
+ * Default path to the gitops configuration file.
16
+ */
17
+ export declare const GITOPS_CONFIG_PATH_DEFAULT = "gitops.config.ts";
18
+ /**
19
+ * Default number of repos to process concurrently during parallel operations.
20
+ */
21
+ export declare const GITOPS_CONCURRENCY_DEFAULT = 5;
22
+ /**
23
+ * Default timeout in milliseconds for waiting on NPM package propagation (10 minutes).
24
+ * NPM's CDN uses eventual consistency, so published packages may not be immediately available.
25
+ */
26
+ export declare const GITOPS_NPM_WAIT_TIMEOUT_DEFAULT = 600000;
27
+ //# sourceMappingURL=gitops_constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitops_constants.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAEhD;;GAEG;AACH,eAAO,MAAM,0BAA0B,qBAAqB,CAAC;AAE7D;;GAEG;AACH,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C;;;GAGG;AACH,eAAO,MAAM,+BAA+B,SAAU,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Shared constants for gitops tasks and operations.
3
+ *
4
+ * Naming convention: GITOPS_{NAME}_DEFAULT for user-facing defaults.
5
+ */
6
+ /**
7
+ * Maximum number of iterations for fixed-point iteration during publishing.
8
+ * Used in both plan generation and actual publishing to resolve transitive dependency cascades.
9
+ *
10
+ * In practice, most repos converge in 2-3 iterations.
11
+ * Deep dependency chains may require more iterations.
12
+ */
13
+ export const GITOPS_MAX_ITERATIONS_DEFAULT = 10;
14
+ /**
15
+ * Default path to the gitops configuration file.
16
+ */
17
+ export const GITOPS_CONFIG_PATH_DEFAULT = 'gitops.config.ts';
18
+ /**
19
+ * Default number of repos to process concurrently during parallel operations.
20
+ */
21
+ export const GITOPS_CONCURRENCY_DEFAULT = 5;
22
+ /**
23
+ * Default timeout in milliseconds for waiting on NPM package propagation (10 minutes).
24
+ * NPM's CDN uses eventual consistency, so published packages may not be immediately available.
25
+ */
26
+ export const GITOPS_NPM_WAIT_TIMEOUT_DEFAULT = 600_000; // 10 minutes
@@ -2,7 +2,7 @@ import type { Task } from '@ryanatkn/gro';
2
2
  import { z } from 'zod';
3
3
  /** @nodocs */
4
4
  export declare const Args: z.ZodObject<{
5
- path: z.ZodDefault<z.ZodString>;
5
+ config: z.ZodDefault<z.ZodString>;
6
6
  dir: z.ZodOptional<z.ZodString>;
7
7
  format: z.ZodDefault<z.ZodEnum<{
8
8
  json: "json";
@@ -20,7 +20,7 @@ export type Args = z.infer<typeof Args>;
20
20
  * Usage:
21
21
  * gro gitops_plan
22
22
  * gro gitops_plan --dir ../repos
23
- * gro gitops_plan --path ./custom.config.ts
23
+ * gro gitops_plan --config ./custom.config.ts
24
24
  *
25
25
  * @nodocs
26
26
  */
@@ -1 +1 @@
1
- {"version":3,"file":"gitops_plan.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_plan.task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,eAAe,CAAC;AACxC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAYtB,cAAc;AACd,eAAO,MAAM,IAAI;;;;;;;;;;kBAef,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CAkC3B,CAAC"}
1
+ {"version":3,"file":"gitops_plan.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_plan.task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,eAAe,CAAC;AACxC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAatB,cAAc;AACd,eAAO,MAAM,IAAI;;;;;;;;;;kBAef,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CAkC3B,CAAC"}
@@ -3,15 +3,16 @@ import { styleText as st } from 'node:util';
3
3
  import { get_gitops_ready } from './gitops_task_helpers.js';
4
4
  import { generate_publishing_plan, log_publishing_plan, } from './publishing_plan.js';
5
5
  import { format_and_output } from './output_helpers.js';
6
+ import { GITOPS_CONFIG_PATH_DEFAULT } from './gitops_constants.js';
6
7
  /** @nodocs */
7
8
  export const Args = z.strictObject({
8
- path: z
9
+ config: z
9
10
  .string()
10
11
  .meta({ description: 'path to the gitops config file, absolute or relative to the cwd' })
11
- .default('gitops.config.ts'),
12
+ .default(GITOPS_CONFIG_PATH_DEFAULT),
12
13
  dir: z
13
14
  .string()
14
- .meta({ description: 'path containing the repos, defaults to the parent of the `path` dir' })
15
+ .meta({ description: 'path containing the repos, defaults to the parent of the config dir' })
15
16
  .optional(),
16
17
  format: z
17
18
  .enum(['stdout', 'json', 'markdown'])
@@ -27,7 +28,7 @@ export const Args = z.strictObject({
27
28
  * Usage:
28
29
  * gro gitops_plan
29
30
  * gro gitops_plan --dir ../repos
30
- * gro gitops_plan --path ./custom.config.ts
31
+ * gro gitops_plan --config ./custom.config.ts
31
32
  *
32
33
  * @nodocs
33
34
  */
@@ -35,11 +36,11 @@ export const task = {
35
36
  summary: 'generate a publishing plan based on changesets',
36
37
  Args,
37
38
  run: async ({ args, log }) => {
38
- const { dir, path, format, outfile, verbose } = args;
39
+ const { dir, config, format, outfile, verbose } = args;
39
40
  log.info(st('cyan', 'Generating multi-repo publishing plan...'));
40
41
  // Load local repos
41
42
  const { local_repos } = await get_gitops_ready({
42
- path,
43
+ config,
43
44
  dir,
44
45
  download: false, // Don't download if missing
45
46
  log,
@@ -2,7 +2,7 @@ import type { Task } from '@ryanatkn/gro';
2
2
  import { z } from 'zod';
3
3
  /** @nodocs */
4
4
  export declare const Args: z.ZodObject<{
5
- path: z.ZodDefault<z.ZodString>;
5
+ config: z.ZodDefault<z.ZodString>;
6
6
  dir: z.ZodOptional<z.ZodString>;
7
7
  peer_strategy: z.ZodDefault<z.ZodEnum<{
8
8
  exact: "exact";
@@ -1 +1 @@
1
- {"version":3,"file":"gitops_publish.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_publish.task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,eAAe,CAAC;AACxC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAatB,cAAc;AACd,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;kBAqCf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC,cAAc;AACd,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CA0F3B,CAAC"}
1
+ {"version":3,"file":"gitops_publish.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_publish.task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,eAAe,CAAC;AACxC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AActB,cAAc;AACd,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;kBAqCf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC,cAAc;AACd,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CA0F3B,CAAC"}
@@ -5,15 +5,16 @@ import { get_gitops_ready } from './gitops_task_helpers.js';
5
5
  import { publish_repos, } from './multi_repo_publisher.js';
6
6
  import { generate_publishing_plan, log_publishing_plan } from './publishing_plan.js';
7
7
  import { format_and_output } from './output_helpers.js';
8
+ import { GITOPS_CONFIG_PATH_DEFAULT, GITOPS_NPM_WAIT_TIMEOUT_DEFAULT } from './gitops_constants.js';
8
9
  /** @nodocs */
9
10
  export const Args = z.strictObject({
10
- path: z
11
+ config: z
11
12
  .string()
12
13
  .meta({ description: 'path to the gitops config file, absolute or relative to the cwd' })
13
- .default('gitops.config.ts'),
14
+ .default(GITOPS_CONFIG_PATH_DEFAULT),
14
15
  dir: z
15
16
  .string()
16
- .meta({ description: 'path containing the repos, defaults to the parent of the `path` dir' })
17
+ .meta({ description: 'path containing the repos, defaults to the parent of the config dir' })
17
18
  .optional(),
18
19
  peer_strategy: z
19
20
  .enum(['exact', 'caret', 'tilde'])
@@ -36,7 +37,7 @@ export const Args = z.strictObject({
36
37
  max_wait: z
37
38
  .number()
38
39
  .meta({ description: 'max time to wait for npm propagation in ms' })
39
- .default(600000), // 10 minutes
40
+ .default(GITOPS_NPM_WAIT_TIMEOUT_DEFAULT),
40
41
  skip_install: z
41
42
  .boolean()
42
43
  .meta({ description: 'skip npm install after dependency updates' })
@@ -49,10 +50,10 @@ export const task = {
49
50
  summary: 'publish all repos in dependency order',
50
51
  Args,
51
52
  run: async ({ args, log }) => {
52
- const { path, dir, peer_strategy, dry_run, format, deploy, plan, max_wait, skip_install, outfile, verbose, } = args;
53
+ const { config, dir, peer_strategy, dry_run, format, deploy, plan, max_wait, skip_install, outfile, verbose, } = args;
53
54
  // Load repos
54
55
  const { local_repos: repos } = await get_gitops_ready({
55
- path,
56
+ config,
56
57
  dir,
57
58
  download: false, // Don't download if missing
58
59
  log,
@@ -0,0 +1,14 @@
1
+ import { type Task } from '@ryanatkn/gro';
2
+ import { z } from 'zod';
3
+ export declare const Args: z.ZodObject<{
4
+ command: z.ZodString;
5
+ config: z.ZodDefault<z.ZodString>;
6
+ concurrency: z.ZodDefault<z.ZodNumber>;
7
+ format: z.ZodDefault<z.ZodEnum<{
8
+ json: "json";
9
+ text: "text";
10
+ }>>;
11
+ }, z.core.$strict>;
12
+ export type Args = z.infer<typeof Args>;
13
+ export declare const task: Task<Args>;
14
+ //# sourceMappingURL=gitops_run.task.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitops_run.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_run.task.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,IAAI,EAAC,MAAM,eAAe,CAAC;AACnD,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAStB,eAAO,MAAM,IAAI;;;;;;;;kBAaf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAaxC,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CAoL3B,CAAC"}
@@ -0,0 +1,173 @@
1
+ import { TaskError } 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
+ import { get_repo_paths } from './repo_ops.js';
8
+ import { GITOPS_CONCURRENCY_DEFAULT, GITOPS_CONFIG_PATH_DEFAULT } from './gitops_constants.js';
9
+ export const Args = z.strictObject({
10
+ command: z.string().meta({ description: 'shell command to run in each repo' }),
11
+ config: z
12
+ .string()
13
+ .meta({ description: 'path to the gitops config file' })
14
+ .default(GITOPS_CONFIG_PATH_DEFAULT),
15
+ concurrency: z
16
+ .number()
17
+ .int()
18
+ .min(1)
19
+ .meta({ description: 'maximum number of repos to run in parallel' })
20
+ .default(GITOPS_CONCURRENCY_DEFAULT),
21
+ format: z.enum(['text', 'json']).meta({ description: 'output format' }).default('text'),
22
+ });
23
+ export const task = {
24
+ Args,
25
+ summary: 'run a shell command across all repos in parallel',
26
+ run: async ({ args, log }) => {
27
+ const { command, config, concurrency, format } = args;
28
+ // Get repo paths (lightweight, no library.ts loading needed)
29
+ const config_path = resolve(config);
30
+ const repos = await get_repo_paths(config_path);
31
+ if (repos.length === 0) {
32
+ throw new TaskError('No repos found in config');
33
+ }
34
+ log.info(`Running ${st('cyan', command)} across ${repos.length} repos (concurrency: ${concurrency})`);
35
+ const start_time = performance.now();
36
+ // Run command in parallel across all repos
37
+ const results = await map_concurrent_settled(repos, async (repo) => {
38
+ const repo_start = performance.now();
39
+ const repo_name = repo.name;
40
+ const repo_dir = repo.path;
41
+ try {
42
+ // Parse command into cmd + args for spawn
43
+ // For now, we use shell mode to support pipes/redirects/etc
44
+ const spawned = await spawn_out('sh', ['-c', command], {
45
+ cwd: repo_dir,
46
+ });
47
+ const duration_ms = performance.now() - repo_start;
48
+ const success = spawned.result.ok;
49
+ const result = {
50
+ repo_name,
51
+ repo_dir,
52
+ status: success ? 'success' : 'failure',
53
+ exit_code: spawned.result.code ?? 0,
54
+ stdout: spawned.stdout || '',
55
+ stderr: spawned.stderr || '',
56
+ duration_ms,
57
+ };
58
+ return result;
59
+ }
60
+ catch (error) {
61
+ const duration_ms = performance.now() - repo_start;
62
+ return {
63
+ repo_name,
64
+ repo_dir,
65
+ status: 'failure',
66
+ exit_code: -1,
67
+ stdout: '',
68
+ stderr: '',
69
+ duration_ms,
70
+ error: String(error),
71
+ };
72
+ }
73
+ }, concurrency);
74
+ const total_duration_ms = performance.now() - start_time;
75
+ // Process results
76
+ const successes = [];
77
+ const failures = [];
78
+ for (const result of results) {
79
+ if (result.status === 'fulfilled') {
80
+ const run_result = result.value;
81
+ if (run_result.status === 'success') {
82
+ successes.push(run_result);
83
+ }
84
+ else {
85
+ failures.push(run_result);
86
+ }
87
+ }
88
+ else {
89
+ // This shouldn't happen since we catch errors in the task fn
90
+ // but handle it anyway
91
+ failures.push({
92
+ repo_name: 'unknown',
93
+ repo_dir: 'unknown',
94
+ status: 'failure',
95
+ exit_code: -1,
96
+ stdout: '',
97
+ stderr: '',
98
+ duration_ms: 0,
99
+ error: String(result.reason),
100
+ });
101
+ }
102
+ }
103
+ // Output results based on format
104
+ if (format === 'json') {
105
+ const json_output = {
106
+ command,
107
+ concurrency,
108
+ repos: [...successes, ...failures],
109
+ summary: {
110
+ total: repos.length,
111
+ success: successes.length,
112
+ failure: failures.length,
113
+ duration_ms: Math.round(total_duration_ms),
114
+ },
115
+ };
116
+ // eslint-disable-next-line no-console
117
+ console.log(JSON.stringify(json_output, null, 2));
118
+ }
119
+ else {
120
+ // Text format
121
+ log.info(''); // blank line
122
+ // Show successes
123
+ if (successes.length > 0) {
124
+ log.info(st('green', `✓ ${successes.length} succeeded:`));
125
+ for (const result of successes) {
126
+ const duration = `${Math.round(result.duration_ms)}ms`;
127
+ log.info(st('gray', ` ${result.repo_name} ${st('blue', `(${duration})`)}`));
128
+ }
129
+ }
130
+ // Show failures with details
131
+ if (failures.length > 0) {
132
+ log.info(''); // blank line
133
+ log.error(st('red', `✗ ${failures.length} failed:`));
134
+ for (const result of failures) {
135
+ const duration = `${Math.round(result.duration_ms)}ms`;
136
+ log.error(st('gray', ` ${result.repo_name} ${st('blue', `(${duration})`)}`));
137
+ if (result.error) {
138
+ log.error(st('gray', ` Error: ${result.error}`));
139
+ }
140
+ else if (result.exit_code !== 0) {
141
+ log.error(st('gray', ` Exit code: ${result.exit_code}`));
142
+ }
143
+ if (result.stderr) {
144
+ // Show first few lines of stderr
145
+ const stderr_lines = result.stderr.trim().split('\n');
146
+ const preview_lines = stderr_lines.slice(0, 3);
147
+ for (const line of preview_lines) {
148
+ log.error(st('gray', ` ${line}`));
149
+ }
150
+ if (stderr_lines.length > 3) {
151
+ log.error(st('gray', ` ... (${stderr_lines.length - 3} more lines)`));
152
+ }
153
+ }
154
+ }
155
+ }
156
+ // Summary
157
+ log.info(''); // blank line
158
+ const total = repos.length;
159
+ const success_rate = ((successes.length / total) * 100).toFixed(0);
160
+ const duration = `${Math.round(total_duration_ms)}ms`;
161
+ if (failures.length === 0) {
162
+ log.info(st('green', `✓ All ${total} repos succeeded in ${duration} (${success_rate}% success rate)`));
163
+ }
164
+ else {
165
+ log.info(st('yellow', `⚠ ${successes.length}/${total} repos succeeded in ${duration} (${success_rate}% success rate)`));
166
+ }
167
+ }
168
+ // Exit with error if any failures (so CI fails)
169
+ if (failures.length > 0) {
170
+ throw new TaskError(`${failures.length} repos failed`);
171
+ }
172
+ },
173
+ };
@@ -2,7 +2,7 @@ import { type Task } from '@ryanatkn/gro';
2
2
  import { z } from 'zod';
3
3
  /** @nodocs */
4
4
  export declare const Args: z.ZodObject<{
5
- path: z.ZodDefault<z.ZodString>;
5
+ config: z.ZodDefault<z.ZodString>;
6
6
  dir: z.ZodOptional<z.ZodString>;
7
7
  outdir: z.ZodOptional<z.ZodString>;
8
8
  download: z.ZodDefault<z.ZodBoolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"gitops_sync.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_sync.task.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,IAAI,EAAC,MAAM,eAAe,CAAC;AACnD,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAetB,cAAc;AACd,eAAO,MAAM,IAAI;;;;;;kBAkBf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC;;;;GAIG;AACH,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CAiE3B,CAAC"}
1
+ {"version":3,"file":"gitops_sync.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_sync.task.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,IAAI,EAAC,MAAM,eAAe,CAAC;AACnD,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAgBtB,cAAc;AACd,eAAO,MAAM,IAAI;;;;;;kBAkBf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC;;;;GAIG;AACH,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CAiE3B,CAAC"}
@@ -5,21 +5,22 @@ import { format_file } from '@ryanatkn/gro/format_file.js';
5
5
  import { basename, resolve } from 'node:path';
6
6
  import { print_path } from '@ryanatkn/gro/paths.js';
7
7
  import { load_from_env } from '@ryanatkn/gro/env.js';
8
- import { load_package_json } from '@ryanatkn/gro/package_json.js';
8
+ import { package_json_load } from '@ryanatkn/gro/package_json.js';
9
9
  import { existsSync } from 'node:fs';
10
10
  import { fetch_repo_data } from './fetch_repo_data.js';
11
11
  import { create_fs_fetch_value_cache } from './fs_fetch_value_cache.js';
12
12
  import { get_gitops_ready } from './gitops_task_helpers.js';
13
+ import { GITOPS_CONFIG_PATH_DEFAULT } from './gitops_constants.js';
13
14
  // TODO add flag to ignore or invalidate cache -- no-cache? clean?
14
15
  /** @nodocs */
15
16
  export const Args = z.strictObject({
16
- path: z
17
+ config: z
17
18
  .string()
18
19
  .meta({ description: 'path to the gitops config file, absolute or relative to the cwd' })
19
- .default('gitops.config.ts'),
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 `path` dir' })
23
+ .meta({ description: 'path containing the repos, defaults to the parent of the config dir' })
23
24
  .optional(),
24
25
  outdir: z
25
26
  .string()
@@ -40,8 +41,8 @@ export const task = {
40
41
  Args,
41
42
  summary: 'syncs local repos and generates UI data from repo metadata',
42
43
  run: async ({ args, log, svelte_config, invoke_task }) => {
43
- const { path, dir, outdir = svelte_config.routes_path, download, check } = args;
44
- const { local_repos } = await get_gitops_ready({ path, dir, download, log });
44
+ const { config, dir, outdir = svelte_config.routes_path, download, check } = args;
45
+ const { local_repos } = await get_gitops_ready({ config, dir, download, log });
45
46
  const outfile = resolve(outdir, 'repos.ts');
46
47
  // This searches the parent directory for the env var, so we don't use SvelteKit's $env imports
47
48
  const token = load_from_env('SECRET_GITHUB_API_TOKEN');
@@ -57,7 +58,7 @@ export const task = {
57
58
  log.info('fetching remote repo data');
58
59
  const repos_json = await fetch_repo_data(local_repos, token, cache.data, log);
59
60
  // TODO should package_json be provided in the Gro task/gen contexts? check if it's always loaded
60
- const package_json = await load_package_json();
61
+ const package_json = await package_json_load();
61
62
  const repo_specifier = package_json.name === '@fuzdev/fuz_gitops'
62
63
  ? '$lib/repo.svelte.js'
63
64
  : '@fuzdev/fuz_gitops/repo.svelte.js';
@@ -18,12 +18,14 @@ import { type GitopsConfig } from './gitops_config.js';
18
18
  import { type LocalRepo } from './local_repo.js';
19
19
  import type { GitOperations, NpmOperations } from './operations.js';
20
20
  export interface GetGitopsReadyOptions {
21
- path: string;
21
+ config: string;
22
22
  dir?: string;
23
23
  download: boolean;
24
24
  log?: Logger;
25
25
  git_ops?: GitOperations;
26
26
  npm_ops?: NpmOperations;
27
+ parallel?: boolean;
28
+ concurrency?: number;
27
29
  }
28
30
  /**
29
31
  * Central initialization function for all gitops tasks.
@@ -31,7 +33,7 @@ export interface GetGitopsReadyOptions {
31
33
  * Initialization sequence:
32
34
  * 1. Loads and normalizes config from `gitops.config.ts`
33
35
  * 2. Resolves local repo paths (creates missing with `--download`)
34
- * 3. Switches branches and pulls latest changes
36
+ * 3. Switches branches and pulls latest changes (in parallel by default)
35
37
  * 4. Auto-installs deps if package.json changed during pull
36
38
  *
37
39
  * Priority for path resolution:
@@ -41,6 +43,8 @@ export interface GetGitopsReadyOptions {
41
43
  *
42
44
  * @param options.git_ops for testing (defaults to real git operations)
43
45
  * @param options.npm_ops for testing (defaults to real npm operations)
46
+ * @param options.parallel whether to load repos in parallel (default: true)
47
+ * @param options.concurrency max concurrent repo loads (default: 5)
44
48
  * @returns initialized config and fully loaded repos ready for operations
45
49
  * @throws {TaskError} if config loading or repo resolution fails
46
50
  */
@@ -51,7 +55,7 @@ export declare const get_gitops_ready: (options: GetGitopsReadyOptions) => Promi
51
55
  local_repos: Array<LocalRepo>;
52
56
  }>;
53
57
  export interface ResolveGitopsPathsOptions {
54
- path: string;
58
+ config: string;
55
59
  dir?: string;
56
60
  config_repos_dir?: string;
57
61
  }
@@ -1 +1 @@
1
- {"version":3,"file":"gitops_task_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_task_helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAqB,KAAK,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAuC,KAAK,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAGrF,OAAO,KAAK,EAAC,aAAa,EAAE,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAElE,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,OAAO,CAAC,EAAE,aAAa,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gBAAgB,GAC5B,SAAS,qBAAqB,KAC5B,OAAO,CAAC;IACV,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,YAAY,CAAC;IAC5B,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAC9B,CA8BA,CAAC;AAEF,MAAM,WAAW,yBAAyB;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,oBAAoB,GAChC,SAAS,yBAAyB,KAChC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAczC,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAU,aAAa,MAAM,KAAG,OAAO,CAAC,YAAY,CAMpF,CAAC"}
1
+ {"version":3,"file":"gitops_task_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_task_helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAqB,KAAK,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAuC,KAAK,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAGrF,OAAO,KAAK,EAAC,aAAa,EAAE,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAElE,MAAM,WAAW,qBAAqB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,gBAAgB,GAC5B,SAAS,qBAAqB,KAC5B,OAAO,CAAC;IACV,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,YAAY,CAAC;IAC5B,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAC9B,CAqCA,CAAC;AAEF,MAAM,WAAW,yBAAyB;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,oBAAoB,GAChC,SAAS,yBAAyB,KAChC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAczC,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAU,aAAa,MAAM,KAAG,OAAO,CAAC,YAAY,CAMpF,CAAC"}
@@ -27,7 +27,7 @@ import { DEFAULT_REPOS_DIR } from './paths.js';
27
27
  * Initialization sequence:
28
28
  * 1. Loads and normalizes config from `gitops.config.ts`
29
29
  * 2. Resolves local repo paths (creates missing with `--download`)
30
- * 3. Switches branches and pulls latest changes
30
+ * 3. Switches branches and pulls latest changes (in parallel by default)
31
31
  * 4. Auto-installs deps if package.json changed during pull
32
32
  *
33
33
  * Priority for path resolution:
@@ -37,16 +37,18 @@ import { DEFAULT_REPOS_DIR } from './paths.js';
37
37
  *
38
38
  * @param options.git_ops for testing (defaults to real git operations)
39
39
  * @param options.npm_ops for testing (defaults to real npm operations)
40
+ * @param options.parallel whether to load repos in parallel (default: true)
41
+ * @param options.concurrency max concurrent repo loads (default: 5)
40
42
  * @returns initialized config and fully loaded repos ready for operations
41
43
  * @throws {TaskError} if config loading or repo resolution fails
42
44
  */
43
45
  export const get_gitops_ready = async (options) => {
44
- const { path, dir, download, log, git_ops, npm_ops } = options;
45
- const config_path = resolve(path);
46
+ const { config, dir, download, log, git_ops, npm_ops, parallel, concurrency } = options;
47
+ const config_path = resolve(config);
46
48
  const gitops_config = await import_gitops_config(config_path);
47
49
  // Priority: explicit dir arg → config repos_dir → default (two dirs up from config)
48
50
  const repos_dir = resolve_gitops_paths({
49
- path,
51
+ config,
50
52
  dir,
51
53
  config_repos_dir: gitops_config.repos_dir,
52
54
  }).repos_dir;
@@ -60,12 +62,19 @@ export const get_gitops_ready = async (options) => {
60
62
  log,
61
63
  npm_ops,
62
64
  });
63
- const local_repos = await local_repos_load({ local_repo_paths, log, git_ops, npm_ops });
65
+ const local_repos = await local_repos_load({
66
+ local_repo_paths,
67
+ log,
68
+ git_ops,
69
+ npm_ops,
70
+ parallel,
71
+ concurrency,
72
+ });
64
73
  return { config_path, repos_dir, gitops_config, local_repos };
65
74
  };
66
75
  export const resolve_gitops_paths = (options) => {
67
- const { path, dir, config_repos_dir } = options;
68
- const config_path = resolve(path);
76
+ const { config, dir, config_repos_dir } = options;
77
+ const config_path = resolve(config);
69
78
  const config_dir = dirname(config_path);
70
79
  // Priority: explicit dir arg → config repos_dir → default (parent of config dir)
71
80
  const repos_dir = dir !== undefined
@@ -2,7 +2,7 @@ import type { Task } from '@ryanatkn/gro';
2
2
  import { z } from 'zod';
3
3
  /** @nodocs */
4
4
  export declare const Args: z.ZodObject<{
5
- path: z.ZodDefault<z.ZodString>;
5
+ config: z.ZodDefault<z.ZodString>;
6
6
  dir: z.ZodOptional<z.ZodString>;
7
7
  verbose: z.ZodDefault<z.ZodBoolean>;
8
8
  }, z.core.$strict>;
@@ -1 +1 @@
1
- {"version":3,"file":"gitops_validate.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_validate.task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,eAAe,CAAC;AACxC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAUtB,cAAc;AACd,eAAO,MAAM,IAAI;;;;kBAUf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC,cAAc;AACd,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CA6N3B,CAAC"}
1
+ {"version":3,"file":"gitops_validate.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_validate.task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,eAAe,CAAC;AACxC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAWtB,cAAc;AACd,eAAO,MAAM,IAAI;;;;kBAUf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC,cAAc;AACd,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CA6N3B,CAAC"}
@@ -6,15 +6,16 @@ import { DependencyGraphBuilder } from './dependency_graph.js';
6
6
  import { generate_publishing_plan, log_publishing_plan } from './publishing_plan.js';
7
7
  import { publish_repos } from './multi_repo_publisher.js';
8
8
  import { log_dependency_analysis } from './log_helpers.js';
9
+ import { GITOPS_CONFIG_PATH_DEFAULT } from './gitops_constants.js';
9
10
  /** @nodocs */
10
11
  export const Args = z.strictObject({
11
- path: z
12
+ config: z
12
13
  .string()
13
14
  .meta({ description: 'path to the gitops config file, absolute or relative to the cwd' })
14
- .default('gitops.config.ts'),
15
+ .default(GITOPS_CONFIG_PATH_DEFAULT),
15
16
  dir: z
16
17
  .string()
17
- .meta({ description: 'path containing the repos, defaults to the parent of the `path` dir' })
18
+ .meta({ description: 'path containing the repos, defaults to the parent of the config dir' })
18
19
  .optional(),
19
20
  verbose: z.boolean().meta({ description: 'show additional details' }).default(false),
20
21
  });
@@ -23,14 +24,14 @@ export const task = {
23
24
  Args,
24
25
  summary: 'validate gitops configuration by running all read-only commands and checking for issues',
25
26
  run: async ({ args, log }) => {
26
- const { path, dir, verbose } = args;
27
+ const { config, dir, verbose } = args;
27
28
  log.info(st('cyan', 'Running Gitops Validation Suite'));
28
29
  log.info(st('dim', 'This runs all read-only commands and checks for consistency.'));
29
30
  const results = [];
30
31
  const start_time = Date.now();
31
32
  // Load repos once (shared by all commands)
32
33
  log.info(st('dim', 'Loading repositories...'));
33
- const { local_repos } = await get_gitops_ready({ path, dir, download: false, log });
34
+ const { local_repos } = await get_gitops_ready({ config, dir, download: false, log });
34
35
  log.info(st('dim', ` Found ${local_repos.length} local repos`));
35
36
  // 1. Run gitops_analyze
36
37
  log.info(st('yellow', 'Running gitops_analyze...'));
@@ -71,11 +71,13 @@ export declare const local_repos_ensure: ({ resolved_config, repos_dir, gitops_c
71
71
  log?: Logger;
72
72
  npm_ops?: NpmOperations;
73
73
  }) => Promise<Array<LocalRepoPath>>;
74
- export declare const local_repos_load: ({ local_repo_paths, log, git_ops, npm_ops, }: {
74
+ export declare const local_repos_load: ({ local_repo_paths, log, git_ops, npm_ops, parallel, concurrency, }: {
75
75
  local_repo_paths: Array<LocalRepoPath>;
76
76
  log?: Logger;
77
77
  git_ops?: GitOperations;
78
78
  npm_ops?: NpmOperations;
79
+ parallel?: boolean;
80
+ concurrency?: number;
79
81
  }) => Promise<Array<LocalRepo>>;
80
82
  export declare const local_repo_locate: ({ repo_config, repos_dir, }: {
81
83
  repo_config: GitopsRepoConfig;