@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.
Files changed (190) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +119 -0
  3. package/dist/ModulesDetail.svelte +180 -0
  4. package/dist/ModulesDetail.svelte.d.ts +10 -0
  5. package/dist/ModulesDetail.svelte.d.ts.map +1 -0
  6. package/dist/ModulesNav.svelte +43 -0
  7. package/dist/ModulesNav.svelte.d.ts +11 -0
  8. package/dist/ModulesNav.svelte.d.ts.map +1 -0
  9. package/dist/ModulesPage.svelte +50 -0
  10. package/dist/ModulesPage.svelte.d.ts +9 -0
  11. package/dist/ModulesPage.svelte.d.ts.map +1 -0
  12. package/dist/PageFooter.svelte +15 -0
  13. package/dist/PageFooter.svelte.d.ts +19 -0
  14. package/dist/PageFooter.svelte.d.ts.map +1 -0
  15. package/dist/PageHeader.svelte +35 -0
  16. package/dist/PageHeader.svelte.d.ts +19 -0
  17. package/dist/PageHeader.svelte.d.ts.map +1 -0
  18. package/dist/PullRequestsDetail.svelte +53 -0
  19. package/dist/PullRequestsDetail.svelte.d.ts +10 -0
  20. package/dist/PullRequestsDetail.svelte.d.ts.map +1 -0
  21. package/dist/PullRequestsPage.svelte +47 -0
  22. package/dist/PullRequestsPage.svelte.d.ts +11 -0
  23. package/dist/PullRequestsPage.svelte.d.ts.map +1 -0
  24. package/dist/ReposTable.svelte +189 -0
  25. package/dist/ReposTable.svelte.d.ts +9 -0
  26. package/dist/ReposTable.svelte.d.ts.map +1 -0
  27. package/dist/ReposTree.svelte +88 -0
  28. package/dist/ReposTree.svelte.d.ts +11 -0
  29. package/dist/ReposTree.svelte.d.ts.map +1 -0
  30. package/dist/ReposTreeNav.svelte +55 -0
  31. package/dist/ReposTreeNav.svelte.d.ts +11 -0
  32. package/dist/ReposTreeNav.svelte.d.ts.map +1 -0
  33. package/dist/TablePage.svelte +46 -0
  34. package/dist/TablePage.svelte.d.ts +9 -0
  35. package/dist/TablePage.svelte.d.ts.map +1 -0
  36. package/dist/TreeItemPage.svelte +75 -0
  37. package/dist/TreeItemPage.svelte.d.ts +10 -0
  38. package/dist/TreeItemPage.svelte.d.ts.map +1 -0
  39. package/dist/TreePage.svelte +64 -0
  40. package/dist/TreePage.svelte.d.ts +9 -0
  41. package/dist/TreePage.svelte.d.ts.map +1 -0
  42. package/dist/changeset_generator.d.ts +38 -0
  43. package/dist/changeset_generator.d.ts.map +1 -0
  44. package/dist/changeset_generator.js +110 -0
  45. package/dist/changeset_reader.d.ts +75 -0
  46. package/dist/changeset_reader.d.ts.map +1 -0
  47. package/dist/changeset_reader.js +167 -0
  48. package/dist/constants.d.ts +9 -0
  49. package/dist/constants.d.ts.map +1 -0
  50. package/dist/constants.js +8 -0
  51. package/dist/dependency_graph.d.ts +120 -0
  52. package/dist/dependency_graph.d.ts.map +1 -0
  53. package/dist/dependency_graph.js +341 -0
  54. package/dist/dependency_updater.d.ts +46 -0
  55. package/dist/dependency_updater.d.ts.map +1 -0
  56. package/dist/dependency_updater.js +213 -0
  57. package/dist/fetch_repo_data.d.ts +19 -0
  58. package/dist/fetch_repo_data.d.ts.map +1 -0
  59. package/dist/fetch_repo_data.js +49 -0
  60. package/dist/fs_fetch_value_cache.d.ts +24 -0
  61. package/dist/fs_fetch_value_cache.d.ts.map +1 -0
  62. package/dist/fs_fetch_value_cache.js +61 -0
  63. package/dist/git_operations.d.ts +54 -0
  64. package/dist/git_operations.d.ts.map +1 -0
  65. package/dist/git_operations.js +144 -0
  66. package/dist/github.d.ts +91 -0
  67. package/dist/github.d.ts.map +1 -0
  68. package/dist/github.js +94 -0
  69. package/dist/github_helpers.d.ts +10 -0
  70. package/dist/github_helpers.d.ts.map +1 -0
  71. package/dist/github_helpers.js +13 -0
  72. package/dist/gitops_analyze.task.d.ts +17 -0
  73. package/dist/gitops_analyze.task.d.ts.map +1 -0
  74. package/dist/gitops_analyze.task.js +188 -0
  75. package/dist/gitops_config.d.ts +56 -0
  76. package/dist/gitops_config.d.ts.map +1 -0
  77. package/dist/gitops_config.js +63 -0
  78. package/dist/gitops_plan.task.d.ts +28 -0
  79. package/dist/gitops_plan.task.d.ts.map +1 -0
  80. package/dist/gitops_plan.task.js +217 -0
  81. package/dist/gitops_publish.task.d.ts +29 -0
  82. package/dist/gitops_publish.task.d.ts.map +1 -0
  83. package/dist/gitops_publish.task.js +178 -0
  84. package/dist/gitops_sync.task.d.ts +18 -0
  85. package/dist/gitops_sync.task.d.ts.map +1 -0
  86. package/dist/gitops_sync.task.js +95 -0
  87. package/dist/gitops_task_helpers.d.ts +63 -0
  88. package/dist/gitops_task_helpers.d.ts.map +1 -0
  89. package/dist/gitops_task_helpers.js +84 -0
  90. package/dist/gitops_validate.task.d.ts +12 -0
  91. package/dist/gitops_validate.task.d.ts.map +1 -0
  92. package/dist/gitops_validate.task.js +210 -0
  93. package/dist/graph_validation.d.ts +39 -0
  94. package/dist/graph_validation.d.ts.map +1 -0
  95. package/dist/graph_validation.js +79 -0
  96. package/dist/local_repo.d.ts +84 -0
  97. package/dist/local_repo.d.ts.map +1 -0
  98. package/dist/local_repo.js +213 -0
  99. package/dist/log_helpers.d.ts +43 -0
  100. package/dist/log_helpers.d.ts.map +1 -0
  101. package/dist/log_helpers.js +98 -0
  102. package/dist/multi_repo_publisher.d.ts +34 -0
  103. package/dist/multi_repo_publisher.d.ts.map +1 -0
  104. package/dist/multi_repo_publisher.js +364 -0
  105. package/dist/npm_install_helpers.d.ts +23 -0
  106. package/dist/npm_install_helpers.d.ts.map +1 -0
  107. package/dist/npm_install_helpers.js +60 -0
  108. package/dist/npm_registry.d.ts +46 -0
  109. package/dist/npm_registry.d.ts.map +1 -0
  110. package/dist/npm_registry.js +96 -0
  111. package/dist/operations.d.ts +409 -0
  112. package/dist/operations.d.ts.map +1 -0
  113. package/dist/operations.js +34 -0
  114. package/dist/operations_defaults.d.ts +19 -0
  115. package/dist/operations_defaults.d.ts.map +1 -0
  116. package/dist/operations_defaults.js +279 -0
  117. package/dist/output_helpers.d.ts +27 -0
  118. package/dist/output_helpers.d.ts.map +1 -0
  119. package/dist/output_helpers.js +39 -0
  120. package/dist/paths.d.ts +11 -0
  121. package/dist/paths.d.ts.map +1 -0
  122. package/dist/paths.js +10 -0
  123. package/dist/preflight_checks.d.ts +47 -0
  124. package/dist/preflight_checks.d.ts.map +1 -0
  125. package/dist/preflight_checks.js +181 -0
  126. package/dist/publishing_plan.d.ts +100 -0
  127. package/dist/publishing_plan.d.ts.map +1 -0
  128. package/dist/publishing_plan.js +353 -0
  129. package/dist/publishing_plan_helpers.d.ts +30 -0
  130. package/dist/publishing_plan_helpers.d.ts.map +1 -0
  131. package/dist/publishing_plan_helpers.js +112 -0
  132. package/dist/publishing_plan_logging.d.ts +18 -0
  133. package/dist/publishing_plan_logging.d.ts.map +1 -0
  134. package/dist/publishing_plan_logging.js +342 -0
  135. package/dist/repo.svelte.d.ts +52 -0
  136. package/dist/repo.svelte.d.ts.map +1 -0
  137. package/dist/repo.svelte.js +70 -0
  138. package/dist/repo_ops.d.ts +57 -0
  139. package/dist/repo_ops.d.ts.map +1 -0
  140. package/dist/repo_ops.js +167 -0
  141. package/dist/resolved_gitops_config.d.ts +9 -0
  142. package/dist/resolved_gitops_config.d.ts.map +1 -0
  143. package/dist/resolved_gitops_config.js +12 -0
  144. package/dist/semver.d.ts +24 -0
  145. package/dist/semver.d.ts.map +1 -0
  146. package/dist/semver.js +140 -0
  147. package/dist/serialization_types.d.ts +57 -0
  148. package/dist/serialization_types.d.ts.map +1 -0
  149. package/dist/serialization_types.js +40 -0
  150. package/dist/version_utils.d.ts +48 -0
  151. package/dist/version_utils.d.ts.map +1 -0
  152. package/dist/version_utils.js +125 -0
  153. package/package.json +107 -0
  154. package/src/lib/changeset_generator.ts +162 -0
  155. package/src/lib/changeset_reader.ts +218 -0
  156. package/src/lib/constants.ts +8 -0
  157. package/src/lib/dependency_graph.ts +423 -0
  158. package/src/lib/dependency_updater.ts +297 -0
  159. package/src/lib/fetch_repo_data.ts +64 -0
  160. package/src/lib/fs_fetch_value_cache.ts +75 -0
  161. package/src/lib/git_operations.ts +208 -0
  162. package/src/lib/github.ts +128 -0
  163. package/src/lib/github_helpers.ts +31 -0
  164. package/src/lib/gitops_analyze.task.ts +261 -0
  165. package/src/lib/gitops_config.ts +123 -0
  166. package/src/lib/gitops_plan.task.ts +272 -0
  167. package/src/lib/gitops_publish.task.ts +227 -0
  168. package/src/lib/gitops_sync.task.ts +109 -0
  169. package/src/lib/gitops_task_helpers.ts +126 -0
  170. package/src/lib/gitops_validate.task.ts +248 -0
  171. package/src/lib/graph_validation.ts +109 -0
  172. package/src/lib/local_repo.ts +359 -0
  173. package/src/lib/log_helpers.ts +147 -0
  174. package/src/lib/multi_repo_publisher.ts +464 -0
  175. package/src/lib/npm_install_helpers.ts +85 -0
  176. package/src/lib/npm_registry.ts +143 -0
  177. package/src/lib/operations.ts +334 -0
  178. package/src/lib/operations_defaults.ts +335 -0
  179. package/src/lib/output_helpers.ts +64 -0
  180. package/src/lib/paths.ts +11 -0
  181. package/src/lib/preflight_checks.ts +269 -0
  182. package/src/lib/publishing_plan.ts +531 -0
  183. package/src/lib/publishing_plan_helpers.ts +145 -0
  184. package/src/lib/publishing_plan_logging.ts +470 -0
  185. package/src/lib/repo.svelte.ts +95 -0
  186. package/src/lib/repo_ops.ts +213 -0
  187. package/src/lib/resolved_gitops_config.ts +27 -0
  188. package/src/lib/semver.ts +166 -0
  189. package/src/lib/serialization_types.ts +90 -0
  190. package/src/lib/version_utils.ts +150 -0
@@ -0,0 +1,178 @@
1
+ import { z } from 'zod';
2
+ import { createInterface } from 'node:readline/promises';
3
+ import { styleText as st } from 'node:util';
4
+ import { get_gitops_ready } from './gitops_task_helpers.js';
5
+ import { publish_repos, } from './multi_repo_publisher.js';
6
+ import { generate_publishing_plan, log_publishing_plan } from './publishing_plan.js';
7
+ import { format_and_output } from './output_helpers.js';
8
+ /** @nodocs */
9
+ export const Args = z.strictObject({
10
+ path: z
11
+ .string()
12
+ .meta({ description: 'path to the gitops config file, absolute or relative to the cwd' })
13
+ .default('gitops.config.ts'),
14
+ dir: z
15
+ .string()
16
+ .meta({ description: 'path containing the repos, defaults to the parent of the `path` dir' })
17
+ .optional(),
18
+ peer_strategy: z
19
+ .enum(['exact', 'caret', 'tilde'])
20
+ .meta({ description: 'version strategy for peer dependencies' })
21
+ .default('caret'),
22
+ dry_run: z
23
+ .boolean()
24
+ .meta({ description: 'perform a dry run without actually publishing' })
25
+ .default(false),
26
+ format: z
27
+ .enum(['stdout', 'json', 'markdown'])
28
+ .meta({ description: 'output format' })
29
+ .default('stdout'),
30
+ deploy: z.boolean().meta({ description: 'deploy all repos after publishing' }).default(false),
31
+ plan: z.boolean().meta({ description: 'dual of no-plan' }).default(true),
32
+ 'no-plan': z
33
+ .boolean()
34
+ .meta({ description: 'skip plan confirmation before publishing' })
35
+ .default(false),
36
+ max_wait: z
37
+ .number()
38
+ .meta({ description: 'max time to wait for npm propagation in ms' })
39
+ .default(600000), // 10 minutes
40
+ skip_install: z
41
+ .boolean()
42
+ .meta({ description: 'skip npm install after dependency updates' })
43
+ .default(false),
44
+ outfile: z.string().meta({ description: 'write output to file instead of logging' }).optional(),
45
+ verbose: z.boolean().meta({ description: 'show additional details in plan output' }).default(false),
46
+ });
47
+ /** @nodocs */
48
+ export const task = {
49
+ summary: 'publish all repos in dependency order',
50
+ Args,
51
+ run: async ({ args, log }) => {
52
+ const { path, dir, peer_strategy, dry_run, format, deploy, plan, max_wait, skip_install, outfile, verbose, } = args;
53
+ // Load repos
54
+ const { local_repos: repos } = await get_gitops_ready({
55
+ path,
56
+ dir,
57
+ download: false, // Don't download if missing
58
+ log,
59
+ });
60
+ // Show plan if requested (skip for dry runs)
61
+ if (plan && !dry_run) {
62
+ log.info(st('cyan', 'Publishing Plan'));
63
+ const plan_result = await generate_publishing_plan(repos, { log, verbose });
64
+ log_publishing_plan(plan_result, log, { verbose });
65
+ if (plan_result.errors.length > 0) {
66
+ throw new Error('Cannot proceed with publishing due to errors');
67
+ }
68
+ // Ask for confirmation
69
+ log.info(st('yellow', '⚠️ This will publish the packages shown above.'));
70
+ process.stdout.write('Continue with publishing? (y/n): ');
71
+ const confirmed = await prompt_for_confirmation();
72
+ if (!confirmed) {
73
+ log.info('Publishing cancelled');
74
+ process.exit(0);
75
+ }
76
+ }
77
+ // Publishing options
78
+ const options = {
79
+ dry_run,
80
+ update_deps: true, // Always update dependencies
81
+ version_strategy: peer_strategy,
82
+ deploy,
83
+ max_wait,
84
+ skip_install,
85
+ log,
86
+ };
87
+ // Execute publishing (may throw on fatal errors like circular dependencies)
88
+ let result;
89
+ let fatal_error = null;
90
+ try {
91
+ result = await publish_repos(repos, options);
92
+ }
93
+ catch (error) {
94
+ // Construct a failure result for fatal errors so output can still be generated
95
+ fatal_error = error instanceof Error ? error : new Error(String(error));
96
+ result = {
97
+ ok: false,
98
+ published: [],
99
+ // Note: FATAL_ERROR is a placeholder - only fatal_error.message is displayed in output
100
+ failed: [{ name: 'FATAL_ERROR', error: fatal_error }],
101
+ duration: 0,
102
+ };
103
+ }
104
+ // Format and output result (always runs, even on fatal errors)
105
+ // Note: stdout format is handled by publish_repos function's logging
106
+ if (format !== 'stdout') {
107
+ await format_and_output({ result, fatal_error }, create_publish_formatters(), {
108
+ format,
109
+ outfile,
110
+ log,
111
+ });
112
+ }
113
+ // Exit with error if failed
114
+ if (!result.ok || fatal_error) {
115
+ process.exit(1);
116
+ }
117
+ },
118
+ };
119
+ const create_publish_formatters = () => ({
120
+ json: (data) => JSON.stringify(data.result, null, 2),
121
+ markdown: (data) => format_result_markdown(data.result, data.fatal_error),
122
+ stdout: () => {
123
+ // stdout format is handled by publish_repos function's logging
124
+ // This should never be called due to early return in task
125
+ },
126
+ });
127
+ // Format the publishing result as markdown
128
+ const format_result_markdown = (result, fatal_error) => {
129
+ const lines = [];
130
+ lines.push('# Publishing Result');
131
+ lines.push('');
132
+ // Show fatal error prominently if present
133
+ if (fatal_error) {
134
+ lines.push('## ❌ Fatal Error');
135
+ lines.push('');
136
+ lines.push(`**Error**: ${fatal_error.message}`);
137
+ lines.push('');
138
+ lines.push('Publishing could not proceed due to the error above.');
139
+ lines.push('');
140
+ return lines;
141
+ }
142
+ lines.push(`**Status**: ${result.ok ? '✅ Success' : '❌ Failed'}`);
143
+ lines.push(`**Duration**: ${(result.duration / 1000).toFixed(1)}s`);
144
+ lines.push(`**Published**: ${result.published.length} packages`);
145
+ if (result.failed.length > 0) {
146
+ lines.push(`**Failed**: ${result.failed.length} packages`);
147
+ }
148
+ if (result.published.length > 0) {
149
+ lines.push('');
150
+ lines.push('## Published Packages');
151
+ lines.push('');
152
+ for (const pkg of result.published) {
153
+ lines.push(`- \`${pkg.name}\`: ${pkg.old_version} → ${pkg.new_version}`);
154
+ }
155
+ }
156
+ if (result.failed.length > 0) {
157
+ lines.push('');
158
+ lines.push('## Failed Packages');
159
+ lines.push('');
160
+ for (const { name, error } of result.failed) {
161
+ lines.push(`- \`${name}\`: ${error.message}`);
162
+ }
163
+ }
164
+ return lines;
165
+ };
166
+ /**
167
+ * Prompts user for y/n confirmation.
168
+ * Returns true if user enters 'y', false otherwise.
169
+ */
170
+ const prompt_for_confirmation = async () => {
171
+ const rl = createInterface({
172
+ input: process.stdin,
173
+ output: process.stdout,
174
+ });
175
+ const answer = await rl.question('');
176
+ rl.close();
177
+ return answer.toLowerCase() === 'y';
178
+ };
@@ -0,0 +1,18 @@
1
+ import { type Task } from '@ryanatkn/gro';
2
+ import { z } from 'zod';
3
+ /** @nodocs */
4
+ export declare const Args: z.ZodObject<{
5
+ path: z.ZodDefault<z.ZodString>;
6
+ dir: z.ZodOptional<z.ZodString>;
7
+ outdir: z.ZodOptional<z.ZodString>;
8
+ download: z.ZodDefault<z.ZodBoolean>;
9
+ check: z.ZodDefault<z.ZodBoolean>;
10
+ }, z.core.$strict>;
11
+ export type Args = z.infer<typeof Args>;
12
+ /**
13
+ * This is a task not a `.gen.` file because it makes network calls.
14
+ *
15
+ * @nodocs
16
+ */
17
+ export declare const task: Task<Args>;
18
+ //# sourceMappingURL=gitops_sync.task.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,95 @@
1
+ import { TaskError } from '@ryanatkn/gro';
2
+ import { z } from 'zod';
3
+ import { readFile, writeFile } from 'node:fs/promises';
4
+ import { format_file } from '@ryanatkn/gro/format_file.js';
5
+ import { basename, resolve } from 'node:path';
6
+ import { print_path } from '@ryanatkn/gro/paths.js';
7
+ import { load_from_env } from '@ryanatkn/gro/env.js';
8
+ import { load_package_json } from '@ryanatkn/gro/package_json.js';
9
+ import { existsSync } from 'node:fs';
10
+ import { fetch_repo_data } from './fetch_repo_data.js';
11
+ import { create_fs_fetch_value_cache } from './fs_fetch_value_cache.js';
12
+ import { get_gitops_ready } from './gitops_task_helpers.js';
13
+ // TODO add flag to ignore or invalidate cache -- no-cache? clean?
14
+ /** @nodocs */
15
+ export const Args = z.strictObject({
16
+ path: z
17
+ .string()
18
+ .meta({ description: 'path to the gitops config file, absolute or relative to the cwd' })
19
+ .default('gitops.config.ts'),
20
+ dir: z
21
+ .string()
22
+ .meta({ description: 'path containing the repos, defaults to the parent of the `path` dir' })
23
+ .optional(),
24
+ outdir: z
25
+ .string()
26
+ .meta({ description: 'path to the directory for the generated files, defaults to $routes/' })
27
+ .optional(),
28
+ download: z.boolean().meta({ description: 'download all missing local repos' }).default(false),
29
+ check: z
30
+ .boolean()
31
+ .meta({ description: 'check repos are ready without fetching remote data' })
32
+ .default(false),
33
+ });
34
+ /**
35
+ * This is a task not a `.gen.` file because it makes network calls.
36
+ *
37
+ * @nodocs
38
+ */
39
+ export const task = {
40
+ Args,
41
+ summary: 'syncs local repos and generates UI data from repo metadata',
42
+ 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 });
45
+ const outfile = resolve(outdir, 'repos.ts');
46
+ // This searches the parent directory for the env var, so we don't use SvelteKit's $env imports
47
+ const token = load_from_env('SECRET_GITHUB_API_TOKEN');
48
+ if (!token) {
49
+ throw new TaskError('the env var SECRET_GITHUB_API_TOKEN was not found');
50
+ }
51
+ // Exit early if only checking repo readiness
52
+ if (check) {
53
+ log.info('repos are ready');
54
+ return;
55
+ }
56
+ const cache = await create_fs_fetch_value_cache('repos');
57
+ log.info('fetching remote repo data');
58
+ const repos_json = await fetch_repo_data(local_repos, token, cache.data, log);
59
+ // 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 repo_specifier = package_json.name === '@fuzdev/fuz_gitops'
62
+ ? '$lib/repo.svelte.js'
63
+ : '@fuzdev/fuz_gitops/repo.svelte.js';
64
+ log.info('generating ' + outfile);
65
+ // TODO the `basename` is used here because we don't have an `origin_id` like with gen,
66
+ // and this file gets re-exported,
67
+ // and we don't want the file to change based on where it's being generated,
68
+ // because for example linking to a local package would change the contents
69
+ const contents = `
70
+ // generated by ${basename(import.meta.filename)} !! do not edit directly or risk lost data
71
+
72
+ import type {RepoJson} from '${repo_specifier}';
73
+
74
+ export const repos_json: Array<RepoJson> = ${JSON.stringify(repos_json, null, '\t')};
75
+ `;
76
+ // TODO think about possibly using the `gen` functionality in this task, not sure what the API design could look like
77
+ const formatted = await format_file(contents, { filepath: outfile });
78
+ const existing = existsSync(outfile) ? await readFile(outfile, 'utf8') : '';
79
+ if (existing === formatted) {
80
+ log.info(`no changes to ${print_path(outfile)}`);
81
+ }
82
+ else {
83
+ log.info(`writing changes to ${print_path(outfile)}`);
84
+ await writeFile(outfile, formatted);
85
+ await invoke_task('gen');
86
+ }
87
+ const changed = await cache.save();
88
+ if (changed) {
89
+ log.info('repos cache updated');
90
+ }
91
+ else {
92
+ log.info('repos cache did not change');
93
+ }
94
+ },
95
+ };
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Shared initialization logic for all gitops tasks.
3
+ *
4
+ * Provides `get_gitops_ready()` which orchestrates:
5
+ * - Config loading and normalization
6
+ * - Repo resolution (local path discovery)
7
+ * - Branch switching and syncing
8
+ * - Dependency installation
9
+ *
10
+ * Used by: `gitops_sync.task.ts`, `gitops_analyze.task.ts`, `gitops_plan.task.ts`,
11
+ * `gitops_publish.task.ts`, and `gitops_validate.task.ts`.
12
+ *
13
+ * Accepts `git_ops` and `npm_ops` parameters to support testing via operations pattern
14
+ * (see `operations.ts` for dependency injection details).
15
+ */
16
+ import type { Logger } from '@fuzdev/fuz_util/log.js';
17
+ import { type GitopsConfig } from './gitops_config.js';
18
+ import { type LocalRepo } from './local_repo.js';
19
+ import type { GitOperations, NpmOperations } from './operations.js';
20
+ export interface GetGitopsReadyOptions {
21
+ path: string;
22
+ dir?: string;
23
+ download: boolean;
24
+ log?: Logger;
25
+ git_ops?: GitOperations;
26
+ npm_ops?: NpmOperations;
27
+ }
28
+ /**
29
+ * Central initialization function for all gitops tasks.
30
+ *
31
+ * Initialization sequence:
32
+ * 1. Loads and normalizes config from `gitops.config.ts`
33
+ * 2. Resolves local repo paths (creates missing with `--download`)
34
+ * 3. Switches branches and pulls latest changes
35
+ * 4. Auto-installs deps if package.json changed during pull
36
+ *
37
+ * Priority for path resolution:
38
+ * - `dir` argument (explicit override)
39
+ * - Config `repos_dir` setting
40
+ * - `DEFAULT_REPOS_DIR` constant
41
+ *
42
+ * @param options.git_ops for testing (defaults to real git operations)
43
+ * @param options.npm_ops for testing (defaults to real npm operations)
44
+ * @returns initialized config and fully loaded repos ready for operations
45
+ * @throws {TaskError} if config loading or repo resolution fails
46
+ */
47
+ export declare const get_gitops_ready: (options: GetGitopsReadyOptions) => Promise<{
48
+ config_path: string;
49
+ repos_dir: string;
50
+ gitops_config: GitopsConfig;
51
+ local_repos: Array<LocalRepo>;
52
+ }>;
53
+ export interface ResolveGitopsPathsOptions {
54
+ path: string;
55
+ dir?: string;
56
+ config_repos_dir?: string;
57
+ }
58
+ export declare const resolve_gitops_paths: (options: ResolveGitopsPathsOptions) => {
59
+ config_path: string;
60
+ repos_dir: string;
61
+ };
62
+ export declare const import_gitops_config: (config_path: string) => Promise<GitopsConfig>;
63
+ //# sourceMappingURL=gitops_task_helpers.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Shared initialization logic for all gitops tasks.
3
+ *
4
+ * Provides `get_gitops_ready()` which orchestrates:
5
+ * - Config loading and normalization
6
+ * - Repo resolution (local path discovery)
7
+ * - Branch switching and syncing
8
+ * - Dependency installation
9
+ *
10
+ * Used by: `gitops_sync.task.ts`, `gitops_analyze.task.ts`, `gitops_plan.task.ts`,
11
+ * `gitops_publish.task.ts`, and `gitops_validate.task.ts`.
12
+ *
13
+ * Accepts `git_ops` and `npm_ops` parameters to support testing via operations pattern
14
+ * (see `operations.ts` for dependency injection details).
15
+ */
16
+ import { TaskError } from '@ryanatkn/gro';
17
+ import { styleText as st } from 'node:util';
18
+ import { resolve, dirname } from 'node:path';
19
+ import { print_path } from '@ryanatkn/gro/paths.js';
20
+ import { load_gitops_config } from './gitops_config.js';
21
+ import { local_repos_load, local_repos_ensure } from './local_repo.js';
22
+ import { resolve_gitops_config } from './resolved_gitops_config.js';
23
+ import { DEFAULT_REPOS_DIR } from './paths.js';
24
+ /**
25
+ * Central initialization function for all gitops tasks.
26
+ *
27
+ * Initialization sequence:
28
+ * 1. Loads and normalizes config from `gitops.config.ts`
29
+ * 2. Resolves local repo paths (creates missing with `--download`)
30
+ * 3. Switches branches and pulls latest changes
31
+ * 4. Auto-installs deps if package.json changed during pull
32
+ *
33
+ * Priority for path resolution:
34
+ * - `dir` argument (explicit override)
35
+ * - Config `repos_dir` setting
36
+ * - `DEFAULT_REPOS_DIR` constant
37
+ *
38
+ * @param options.git_ops for testing (defaults to real git operations)
39
+ * @param options.npm_ops for testing (defaults to real npm operations)
40
+ * @returns initialized config and fully loaded repos ready for operations
41
+ * @throws {TaskError} if config loading or repo resolution fails
42
+ */
43
+ 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 gitops_config = await import_gitops_config(config_path);
47
+ // Priority: explicit dir arg → config repos_dir → default (two dirs up from config)
48
+ const repos_dir = resolve_gitops_paths({
49
+ path,
50
+ dir,
51
+ config_repos_dir: gitops_config.repos_dir,
52
+ }).repos_dir;
53
+ log?.info(`resolving gitops configs on the filesystem in ${repos_dir}`, gitops_config.repos.map((r) => r.repo_url));
54
+ const resolved_config = resolve_gitops_config(gitops_config, repos_dir);
55
+ const local_repo_paths = await local_repos_ensure({
56
+ resolved_config,
57
+ repos_dir,
58
+ gitops_config,
59
+ download,
60
+ log,
61
+ npm_ops,
62
+ });
63
+ const local_repos = await local_repos_load({ local_repo_paths, log, git_ops, npm_ops });
64
+ return { config_path, repos_dir, gitops_config, local_repos };
65
+ };
66
+ export const resolve_gitops_paths = (options) => {
67
+ const { path, dir, config_repos_dir } = options;
68
+ const config_path = resolve(path);
69
+ const config_dir = dirname(config_path);
70
+ // Priority: explicit dir arg → config repos_dir → default (parent of config dir)
71
+ const repos_dir = dir !== undefined
72
+ ? resolve(dir)
73
+ : config_repos_dir !== undefined
74
+ ? resolve(config_dir, config_repos_dir)
75
+ : resolve(config_dir, DEFAULT_REPOS_DIR);
76
+ return { config_path, repos_dir };
77
+ };
78
+ export const import_gitops_config = async (config_path) => {
79
+ const gitops_config = await load_gitops_config(config_path);
80
+ if (!gitops_config) {
81
+ throw new TaskError(st('red', `No gitops config found at ${print_path(config_path)}`));
82
+ }
83
+ return gitops_config;
84
+ };
@@ -0,0 +1,12 @@
1
+ import type { Task } from '@ryanatkn/gro';
2
+ import { z } from 'zod';
3
+ /** @nodocs */
4
+ export declare const Args: z.ZodObject<{
5
+ path: z.ZodDefault<z.ZodString>;
6
+ dir: z.ZodOptional<z.ZodString>;
7
+ verbose: z.ZodDefault<z.ZodBoolean>;
8
+ }, z.core.$strict>;
9
+ export type Args = z.infer<typeof Args>;
10
+ /** @nodocs */
11
+ export declare const task: Task<Args>;
12
+ //# sourceMappingURL=gitops_validate.task.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,210 @@
1
+ import { z } from 'zod';
2
+ import { styleText as st } from 'node:util';
3
+ import { get_gitops_ready } from './gitops_task_helpers.js';
4
+ import { validate_dependency_graph } from './graph_validation.js';
5
+ import { DependencyGraphBuilder } from './dependency_graph.js';
6
+ import { generate_publishing_plan, log_publishing_plan } from './publishing_plan.js';
7
+ import { publish_repos } from './multi_repo_publisher.js';
8
+ import { log_dependency_analysis } from './log_helpers.js';
9
+ /** @nodocs */
10
+ export const Args = z.strictObject({
11
+ path: z
12
+ .string()
13
+ .meta({ description: 'path to the gitops config file, absolute or relative to the cwd' })
14
+ .default('gitops.config.ts'),
15
+ dir: z
16
+ .string()
17
+ .meta({ description: 'path containing the repos, defaults to the parent of the `path` dir' })
18
+ .optional(),
19
+ verbose: z.boolean().meta({ description: 'show additional details' }).default(false),
20
+ });
21
+ /** @nodocs */
22
+ export const task = {
23
+ Args,
24
+ summary: 'validate gitops configuration by running all read-only commands and checking for issues',
25
+ run: async ({ args, log }) => {
26
+ const { path, dir, verbose } = args;
27
+ log.info(st('cyan', 'Running Gitops Validation Suite'));
28
+ log.info(st('dim', 'This runs all read-only commands and checks for consistency.'));
29
+ const results = [];
30
+ const start_time = Date.now();
31
+ // Load repos once (shared by all commands)
32
+ log.info(st('dim', 'Loading repositories...'));
33
+ const { local_repos } = await get_gitops_ready({ path, dir, download: false, log });
34
+ log.info(st('dim', ` Found ${local_repos.length} local repos`));
35
+ // 1. Run gitops_analyze
36
+ log.info(st('yellow', 'Running gitops_analyze...'));
37
+ const analyze_start = Date.now();
38
+ try {
39
+ // Build dependency graph and validate (but don't throw on cycles for analyze)
40
+ const { graph } = validate_dependency_graph(local_repos, {
41
+ throw_on_prod_cycles: false, // Analyze should report, not throw
42
+ log_cycles: false, // We'll collect our own statistics
43
+ log_order: false,
44
+ });
45
+ // Perform additional analysis
46
+ const builder = new DependencyGraphBuilder();
47
+ const analysis = builder.analyze(graph);
48
+ const analyze_duration = Date.now() - analyze_start;
49
+ // Collect warnings, info, and errors
50
+ const warning_details = [];
51
+ const info_details = [];
52
+ if (analysis.wildcard_deps.length > 0) {
53
+ warning_details.push('wildcard dependencies');
54
+ }
55
+ if (analysis.dev_cycles.length > 0) {
56
+ info_details.push('dev circular dependencies');
57
+ }
58
+ const warnings = warning_details.length;
59
+ const errors = analysis.production_cycles.length > 0 ? 1 : 0;
60
+ results.push({
61
+ command: 'gitops_analyze',
62
+ success: true,
63
+ warnings,
64
+ errors,
65
+ duration: analyze_duration,
66
+ warning_details,
67
+ info_details,
68
+ analysis,
69
+ });
70
+ log.info(st('green', ` ✓ gitops_analyze completed in ${analyze_duration}ms`));
71
+ // Print detailed analysis
72
+ log_dependency_analysis(analysis, log, ' ');
73
+ if (errors > 0) {
74
+ log.error(st('red', ` ❌ Found ${errors} error(s)`));
75
+ }
76
+ }
77
+ catch (error) {
78
+ const analyze_duration = Date.now() - analyze_start;
79
+ results.push({
80
+ command: 'gitops_analyze',
81
+ success: false,
82
+ warnings: 0,
83
+ errors: 1,
84
+ duration: analyze_duration,
85
+ });
86
+ log.error(st('red', ` ✗ gitops_analyze failed: ${error}`));
87
+ }
88
+ // 2. Run gitops_plan
89
+ log.info(st('yellow', 'Running gitops_plan...'));
90
+ const plan_start = Date.now();
91
+ try {
92
+ const plan = await generate_publishing_plan(local_repos, { log: undefined, verbose });
93
+ const plan_duration = Date.now() - plan_start;
94
+ const warnings = plan.warnings.length;
95
+ const errors = plan.errors.length;
96
+ results.push({
97
+ command: 'gitops_plan',
98
+ success: true,
99
+ warnings,
100
+ errors,
101
+ duration: plan_duration,
102
+ });
103
+ log.info(st('green', ` ✓ gitops_plan completed in ${plan_duration}ms`));
104
+ if (verbose) {
105
+ log_publishing_plan(plan, log, { verbose });
106
+ }
107
+ if (warnings > 0) {
108
+ log.warn(st('yellow', ` ⚠️ Found ${warnings} warning(s)`));
109
+ }
110
+ if (errors > 0) {
111
+ log.error(st('red', ` ❌ Found ${errors} error(s)`));
112
+ }
113
+ }
114
+ catch (error) {
115
+ const plan_duration = Date.now() - plan_start;
116
+ results.push({
117
+ command: 'gitops_plan',
118
+ success: false,
119
+ warnings: 0,
120
+ errors: 1,
121
+ duration: plan_duration,
122
+ });
123
+ log.error(st('red', ` ✗ gitops_plan failed: ${error}`));
124
+ }
125
+ // 3. Run gitops_publish --dry_run
126
+ log.info(st('yellow', 'Running gitops_publish --dry_run...'));
127
+ const dry_start = Date.now();
128
+ try {
129
+ const options = {
130
+ dry_run: true,
131
+ update_deps: true,
132
+ log: undefined, // Silent for validation
133
+ };
134
+ const result = await publish_repos(local_repos, options);
135
+ const dry_duration = Date.now() - dry_start;
136
+ // Dry run doesn't have warnings/errors in the same format
137
+ // We'll just check if it succeeded
138
+ const errors = result.ok ? 0 : result.failed.length;
139
+ results.push({
140
+ command: 'gitops_publish --dry_run',
141
+ success: result.ok,
142
+ warnings: 0,
143
+ errors,
144
+ duration: dry_duration,
145
+ });
146
+ log.info(st('green', ` ✓ gitops_publish --dry_run completed in ${dry_duration}ms`));
147
+ if (errors > 0) {
148
+ log.error(st('red', ` ❌ Found ${errors} error(s)`));
149
+ }
150
+ }
151
+ catch (error) {
152
+ const dry_duration = Date.now() - dry_start;
153
+ results.push({
154
+ command: 'gitops_publish --dry_run',
155
+ success: false,
156
+ warnings: 0,
157
+ errors: 1,
158
+ duration: dry_duration,
159
+ });
160
+ log.error(st('red', ` ✗ gitops_publish --dry_run failed: ${error}`));
161
+ }
162
+ // Summary
163
+ const total_duration = Date.now() - start_time;
164
+ const all_success = results.every((r) => r.success);
165
+ const total_warnings = results.reduce((sum, r) => sum + r.warnings, 0);
166
+ const total_errors = results.reduce((sum, r) => sum + r.errors, 0);
167
+ log.info(st('cyan', 'Validation Summary'));
168
+ log.info(` Total duration: ${(total_duration / 1000).toFixed(1)}s`);
169
+ log.info(` Commands run: ${results.length}`);
170
+ log.info(` Commands succeeded: ${results.filter((r) => r.success).length}`);
171
+ log.info(` Commands failed: ${results.filter((r) => !r.success).length}`);
172
+ log.info(` Total warnings: ${total_warnings}`);
173
+ log.info(` Total errors: ${total_errors}`);
174
+ // Individual command results
175
+ log.info(st('cyan', 'Command Results:'));
176
+ for (const result of results) {
177
+ const status_icon = result.success ? '✓' : '✗';
178
+ const status_color = result.success ? 'green' : 'red';
179
+ const duration = (result.duration / 1000).toFixed(1);
180
+ log.info(st(status_color, ` ${status_icon} ${result.command} (${duration}s)`));
181
+ if (result.warnings > 0) {
182
+ const details = result.warning_details?.length
183
+ ? ` (${result.warning_details.join(', ')})`
184
+ : '';
185
+ log.info(st('yellow', ` ⚠️ ${result.warnings} warning(s)${details}`));
186
+ }
187
+ if (result.info_details && result.info_details.length > 0) {
188
+ log.info(st('dim', ` ℹ️ ${result.info_details.join(', ')}`));
189
+ }
190
+ if (result.errors > 0) {
191
+ log.info(st('red', ` ❌ ${result.errors} error(s)`));
192
+ }
193
+ }
194
+ // Final verdict
195
+ log.info('');
196
+ if (all_success && total_errors === 0) {
197
+ log.info(st('green', '✓ All validation checks passed'));
198
+ if (total_warnings > 0) {
199
+ log.warn(st('yellow', `⚠️ Note: ${total_warnings} warning(s) found - review output above.`));
200
+ }
201
+ }
202
+ else if (all_success && total_errors > 0) {
203
+ log.warn(st('yellow', '⚠️ Validation completed but found errors - review output above.'));
204
+ }
205
+ else {
206
+ log.error(st('red', '❌ Validation failed - one or more commands did not complete.'));
207
+ throw new Error('Validation failed');
208
+ }
209
+ },
210
+ };