@fuzdev/fuz_gitops 0.71.0 → 0.73.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 (117) hide show
  1. package/README.md +13 -6
  2. package/dist/changeset_generator.d.ts.map +1 -1
  3. package/dist/changeset_generator.js +2 -16
  4. package/dist/changeset_reader.d.ts +1 -1
  5. package/dist/changeset_reader.d.ts.map +1 -1
  6. package/dist/dependency_graph.js +1 -1
  7. package/dist/git_operations.d.ts +5 -0
  8. package/dist/git_operations.d.ts.map +1 -1
  9. package/dist/git_operations.js +5 -0
  10. package/dist/gitops_analyze.task.d.ts +1 -0
  11. package/dist/gitops_analyze.task.d.ts.map +1 -1
  12. package/dist/gitops_analyze.task.js +12 -17
  13. package/dist/gitops_plan.task.d.ts +1 -0
  14. package/dist/gitops_plan.task.d.ts.map +1 -1
  15. package/dist/gitops_plan.task.js +9 -2
  16. package/dist/gitops_publish.task.d.ts +3 -2
  17. package/dist/gitops_publish.task.d.ts.map +1 -1
  18. package/dist/gitops_publish.task.js +90 -26
  19. package/dist/gitops_run.task.d.ts +2 -1
  20. package/dist/gitops_run.task.d.ts.map +1 -1
  21. package/dist/gitops_run.task.js +40 -7
  22. package/dist/gitops_sync.task.d.ts +1 -0
  23. package/dist/gitops_sync.task.d.ts.map +1 -1
  24. package/dist/gitops_sync.task.js +16 -2
  25. package/dist/gitops_task_helpers.d.ts +16 -2
  26. package/dist/gitops_task_helpers.d.ts.map +1 -1
  27. package/dist/gitops_task_helpers.js +11 -3
  28. package/dist/gitops_validate.task.d.ts +1 -0
  29. package/dist/gitops_validate.task.d.ts.map +1 -1
  30. package/dist/gitops_validate.task.js +20 -20
  31. package/dist/graph_validation.d.ts +16 -1
  32. package/dist/graph_validation.d.ts.map +1 -1
  33. package/dist/graph_validation.js +15 -0
  34. package/dist/local_repo.d.ts +22 -10
  35. package/dist/local_repo.d.ts.map +1 -1
  36. package/dist/local_repo.js +92 -75
  37. package/dist/log_helpers.d.ts +0 -5
  38. package/dist/log_helpers.d.ts.map +1 -1
  39. package/dist/log_helpers.js +8 -19
  40. package/dist/multi_repo_publisher.d.ts +29 -2
  41. package/dist/multi_repo_publisher.d.ts.map +1 -1
  42. package/dist/multi_repo_publisher.js +307 -233
  43. package/dist/operations.d.ts +6 -26
  44. package/dist/operations.d.ts.map +1 -1
  45. package/dist/operations_defaults.d.ts.map +1 -1
  46. package/dist/operations_defaults.js +1 -19
  47. package/dist/output_helpers.d.ts.map +1 -1
  48. package/dist/output_helpers.js +7 -6
  49. package/dist/paths.d.ts +0 -4
  50. package/dist/paths.d.ts.map +1 -1
  51. package/dist/paths.js +0 -4
  52. package/dist/publish_gate.d.ts +44 -0
  53. package/dist/publish_gate.d.ts.map +1 -0
  54. package/dist/publish_gate.js +32 -0
  55. package/dist/publish_steps.d.ts +60 -0
  56. package/dist/publish_steps.d.ts.map +1 -0
  57. package/dist/publish_steps.js +113 -0
  58. package/dist/publishing_event.d.ts +123 -0
  59. package/dist/publishing_event.d.ts.map +1 -0
  60. package/dist/publishing_event.js +140 -0
  61. package/dist/publishing_event_handler.d.ts +42 -0
  62. package/dist/publishing_event_handler.d.ts.map +1 -0
  63. package/dist/publishing_event_handler.js +75 -0
  64. package/dist/publishing_plan.d.ts +1 -2
  65. package/dist/publishing_plan.d.ts.map +1 -1
  66. package/dist/publishing_plan.js +18 -1
  67. package/dist/publishing_plan_helpers.d.ts +1 -1
  68. package/dist/publishing_plan_helpers.d.ts.map +1 -1
  69. package/dist/publishing_plan_helpers.js +2 -15
  70. package/dist/publishing_plan_logging.d.ts.map +1 -1
  71. package/dist/publishing_plan_logging.js +15 -23
  72. package/dist/repo_ops.d.ts +1 -1
  73. package/dist/repo_ops.js +1 -1
  74. package/dist/version_utils.d.ts +17 -3
  75. package/dist/version_utils.d.ts.map +1 -1
  76. package/dist/version_utils.js +22 -0
  77. package/package.json +6 -7
  78. package/src/lib/changeset_generator.ts +10 -19
  79. package/src/lib/changeset_reader.ts +1 -2
  80. package/src/lib/dependency_graph.ts +1 -1
  81. package/src/lib/git_operations.ts +5 -0
  82. package/src/lib/gitops_analyze.task.ts +18 -23
  83. package/src/lib/gitops_plan.task.ts +10 -2
  84. package/src/lib/gitops_publish.task.ts +100 -25
  85. package/src/lib/gitops_run.task.ts +39 -7
  86. package/src/lib/gitops_sync.task.ts +18 -3
  87. package/src/lib/gitops_task_helpers.ts +20 -3
  88. package/src/lib/gitops_validate.task.ts +30 -22
  89. package/src/lib/graph_validation.ts +26 -0
  90. package/src/lib/local_repo.ts +114 -83
  91. package/src/lib/log_helpers.ts +8 -26
  92. package/src/lib/multi_repo_publisher.ts +387 -269
  93. package/src/lib/operations.ts +6 -22
  94. package/src/lib/operations_defaults.ts +1 -19
  95. package/src/lib/output_helpers.ts +7 -6
  96. package/src/lib/paths.ts +0 -5
  97. package/src/lib/publish_gate.ts +53 -0
  98. package/src/lib/publish_steps.ts +149 -0
  99. package/src/lib/publishing_event.ts +151 -0
  100. package/src/lib/publishing_event_handler.ts +98 -0
  101. package/src/lib/publishing_plan.ts +23 -3
  102. package/src/lib/publishing_plan_helpers.ts +5 -18
  103. package/src/lib/publishing_plan_logging.ts +19 -39
  104. package/src/lib/repo_ops.ts +1 -1
  105. package/src/lib/version_utils.ts +30 -9
  106. package/dist/npm_install_helpers.d.ts +0 -23
  107. package/dist/npm_install_helpers.d.ts.map +0 -1
  108. package/dist/npm_install_helpers.js +0 -60
  109. package/dist/semver.d.ts +0 -26
  110. package/dist/semver.d.ts.map +0 -1
  111. package/dist/semver.js +0 -137
  112. package/dist/serialization_types.d.ts +0 -59
  113. package/dist/serialization_types.d.ts.map +0 -1
  114. package/dist/serialization_types.js +0 -42
  115. package/src/lib/npm_install_helpers.ts +0 -85
  116. package/src/lib/semver.ts +0 -170
  117. package/src/lib/serialization_types.ts +0 -92
@@ -7,8 +7,7 @@
7
7
  */
8
8
 
9
9
  import type {LocalRepo} from './local_repo.js';
10
- import type {BumpType} from './semver.js';
11
- import {needs_update} from './version_utils.js';
10
+ import {needs_update, required_bump_for_dependency_update, type BumpType} from './version_utils.js';
12
11
  import type {DependencyUpdate} from './publishing_plan.js';
13
12
 
14
13
  /**
@@ -42,7 +41,6 @@ export const calculate_dependency_updates = (
42
41
  current_version,
43
42
  new_version,
44
43
  type: 'dependencies',
45
- causes_republish: true,
46
44
  });
47
45
 
48
46
  if (breaking_packages.has(dep_name)) {
@@ -67,7 +65,6 @@ export const calculate_dependency_updates = (
67
65
  current_version,
68
66
  new_version,
69
67
  type: 'peerDependencies',
70
- causes_republish: true,
71
68
  });
72
69
 
73
70
  if (breaking_packages.has(dep_name)) {
@@ -92,7 +89,6 @@ export const calculate_dependency_updates = (
92
89
  current_version,
93
90
  new_version,
94
91
  type: 'devDependencies',
95
- causes_republish: false,
96
92
  });
97
93
  }
98
94
  }
@@ -131,17 +127,8 @@ export const get_required_bump_for_dependencies = (
131
127
  breaking_packages.has(update.updated_dependency),
132
128
  );
133
129
 
134
- const current_version = repo.library.package_json.version || '0.0.0';
135
- const [major] = current_version.split('.').map(Number);
136
- const is_pre_1_0 = major === 0;
137
-
138
- if (has_breaking_deps) {
139
- // Breaking changes propagate
140
- // Pre-1.0: use minor for breaking changes
141
- // 1.0+: use major for breaking changes
142
- return is_pre_1_0 ? 'minor' : 'major';
143
- }
144
-
145
- // For non-breaking dependency updates, use patch
146
- return 'patch';
130
+ return required_bump_for_dependency_update(
131
+ repo.library.package_json.version || '0.0.0',
132
+ has_breaking_deps,
133
+ );
147
134
  };
@@ -182,54 +182,34 @@ export const log_publishing_plan = (
182
182
  const with_escalation = ordered_changes.filter((vc) => vc.needs_bump_escalation);
183
183
  const with_auto_changesets = ordered_changes.filter((vc) => vc.will_generate_changeset);
184
184
 
185
- // Log each group with diff-style output
186
- if (with_changesets.length > 0) {
187
- log.info(st('cyan', 'Version Changes (from changesets):'));
188
- for (const change of with_changesets) {
189
- const idx = ordered_changes.indexOf(change);
185
+ // A single running counter across all groups so positions read 1..N with no
186
+ // gaps. (Numbering per-group index would make the first group skip the numbers
187
+ // that land in a later group, e.g. jump from `[9/12]` to `[11/12]`.)
188
+ const total = with_changesets.length + with_escalation.length + with_auto_changesets.length;
189
+ let position = 0;
190
+ const log_change_group = (
191
+ title: string,
192
+ color: 'cyan' | 'yellow',
193
+ changes: Array<VersionChange>,
194
+ ): void => {
195
+ if (changes.length === 0) return;
196
+ log.info(st(color, title));
197
+ for (const change of changes) {
190
198
  log_version_change_with_diffs(
191
199
  change,
192
- idx,
193
- ordered_changes.length,
200
+ position++,
201
+ total,
194
202
  dependency_updates,
195
203
  breaking_cascades,
196
204
  log,
197
205
  );
198
206
  }
199
207
  log.info('');
200
- }
201
-
202
- if (with_escalation.length > 0) {
203
- log.info(st('yellow', 'Version Changes (bump escalation):'));
204
- for (const change of with_escalation) {
205
- const idx = ordered_changes.indexOf(change);
206
- log_version_change_with_diffs(
207
- change,
208
- idx,
209
- ordered_changes.length,
210
- dependency_updates,
211
- breaking_cascades,
212
- log,
213
- );
214
- }
215
- log.info('');
216
- }
208
+ };
217
209
 
218
- if (with_auto_changesets.length > 0) {
219
- log.info(st('cyan', 'Version Changes (auto-generated):'));
220
- for (const change of with_auto_changesets) {
221
- const idx = ordered_changes.indexOf(change);
222
- log_version_change_with_diffs(
223
- change,
224
- idx,
225
- ordered_changes.length,
226
- dependency_updates,
227
- breaking_cascades,
228
- log,
229
- );
230
- }
231
- log.info('');
232
- }
210
+ log_change_group('Version Changes (from changesets):', 'cyan', with_changesets);
211
+ log_change_group('Version Changes (bump escalation):', 'yellow', with_escalation);
212
+ log_change_group('Version Changes (auto-generated):', 'cyan', with_auto_changesets);
233
213
  } else {
234
214
  log.info(st('dim', 'No packages to publish'));
235
215
  log.info('');
@@ -83,7 +83,7 @@ export interface RepoPath {
83
83
  * Get repo paths from gitops config without full git sync.
84
84
  * Lighter weight than `get_gitops_ready()` - just resolves paths.
85
85
  *
86
- * @param config_path - path to `gitops.config.ts` (defaults to `./gitops.config.ts`)
86
+ * @param config_path - path to the gitops config file (defaults to `gitops.config.ts`)
87
87
  * @returns array of repo info with name, path, and url
88
88
  */
89
89
  export const get_repo_paths = async (config_path?: string): Promise<Array<RepoPath>> => {
@@ -1,6 +1,7 @@
1
1
  // TODO: candidate for extraction to `@fuzdev/fuz_util`
2
2
 
3
- import type {BumpType} from './semver.js';
3
+ /** A semver bump kind: `major`, `minor`, or `patch`. */
4
+ export type BumpType = 'major' | 'minor' | 'patch';
4
5
 
5
6
  export const is_wildcard = (version: string): boolean => {
6
7
  return version === '*';
@@ -100,10 +101,7 @@ export const get_update_prefix = (
100
101
  * Pre-1.0: minor bumps are breaking
101
102
  * 1.0+: major bumps are breaking
102
103
  */
103
- export const is_breaking_change = (
104
- old_version: string,
105
- bump_type: 'major' | 'minor' | 'patch',
106
- ): boolean => {
104
+ export const is_breaking_change = (old_version: string, bump_type: BumpType): boolean => {
107
105
  const [major] = old_version.split('.').map(Number);
108
106
  const is_pre_1_0 = major === 0;
109
107
 
@@ -116,10 +114,33 @@ export const is_breaking_change = (
116
114
  }
117
115
  };
118
116
 
119
- export const detect_bump_type = (
120
- old_version: string,
121
- new_version: string,
122
- ): 'major' | 'minor' | 'patch' => {
117
+ /**
118
+ * The bump a package must take when one of its prod/peer dependencies updates.
119
+ * Pre-1.0: `minor` for a breaking dependency, otherwise `patch`.
120
+ * 1.0+: `major` for a breaking dependency, otherwise `patch`.
121
+ *
122
+ * Single source of truth for the dependency-driven bump rule, shared by the plan
123
+ * (`get_required_bump_for_dependencies`) and the auto-changeset generator
124
+ * (`calculate_required_bump`) so the two never drift.
125
+ *
126
+ * @param current_version - the package's current version, used to detect the pre-1.0 regime
127
+ * @param has_breaking_deps - whether any updated dependency is a breaking change
128
+ */
129
+ export const required_bump_for_dependency_update = (
130
+ current_version: string,
131
+ has_breaking_deps: boolean,
132
+ ): BumpType => {
133
+ const [major] = current_version.split('.').map(Number);
134
+ const is_pre_1_0 = major === 0;
135
+ if (has_breaking_deps) {
136
+ // Breaking changes propagate: pre-1.0 uses minor, 1.0+ uses major
137
+ return is_pre_1_0 ? 'minor' : 'major';
138
+ }
139
+ // Non-breaking dependency updates only need a patch
140
+ return 'patch';
141
+ };
142
+
143
+ export const detect_bump_type = (old_version: string, new_version: string): BumpType => {
123
144
  const old_parts = old_version.split('.').map(Number);
124
145
  const new_parts = new_version.split('.').map(Number);
125
146
 
@@ -1,23 +0,0 @@
1
- import type { Logger } from '@fuzdev/fuz_util/log.js';
2
- import type { LocalRepo } from './local_repo.js';
3
- import type { GitopsOperations } from './operations.js';
4
- /**
5
- * Installs npm dependencies with cache healing on ETARGET errors.
6
- *
7
- * **Strategy:**
8
- * 1. First attempt: regular `npm install`
9
- * 2. On ETARGET error (stale cache): `npm cache clean --force` then retry
10
- * 3. On other errors: fail immediately
11
- *
12
- * **Why ETARGET errors occur:**
13
- * After publishing a package and waiting for NPM registry propagation,
14
- * npm's local cache may still have stale "404" metadata. This healing
15
- * strategy clears the cache to force fresh metadata fetch.
16
- *
17
- * @param repo - the repository to install dependencies for
18
- * @param ops - gitops operations (for dependency injection)
19
- * @param log - optional logger
20
- * @throws Error if install fails (with details about cache healing attempts)
21
- */
22
- export declare const install_with_cache_healing: (repo: LocalRepo, ops: GitopsOperations, log?: Logger) => Promise<void>;
23
- //# sourceMappingURL=npm_install_helpers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"npm_install_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/npm_install_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAGpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iBAAiB,CAAC;AAmBtD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,0BAA0B,GACtC,MAAM,SAAS,EACf,KAAK,gBAAgB,EACrB,MAAM,MAAM,KACV,OAAO,CAAC,IAAI,CAuCd,CAAC"}
@@ -1,60 +0,0 @@
1
- import { styleText as st } from 'node:util';
2
- /**
3
- * Checks if an npm install error is caused by stale cache (ETARGET).
4
- * Detects various error message formats:
5
- * - "code ETARGET"
6
- * - "ETARGET"
7
- * - "notarget"
8
- * - "No matching version found"
9
- */
10
- const is_etarget_error = (message, stderr) => {
11
- const combined = `${message} ${stderr}`.toLowerCase();
12
- return (combined.includes('etarget') ||
13
- combined.includes('notarget') ||
14
- combined.includes('no matching version found'));
15
- };
16
- /**
17
- * Installs npm dependencies with cache healing on ETARGET errors.
18
- *
19
- * **Strategy:**
20
- * 1. First attempt: regular `npm install`
21
- * 2. On ETARGET error (stale cache): `npm cache clean --force` then retry
22
- * 3. On other errors: fail immediately
23
- *
24
- * **Why ETARGET errors occur:**
25
- * After publishing a package and waiting for NPM registry propagation,
26
- * npm's local cache may still have stale "404" metadata. This healing
27
- * strategy clears the cache to force fresh metadata fetch.
28
- *
29
- * @param repo - the repository to install dependencies for
30
- * @param ops - gitops operations (for dependency injection)
31
- * @param log - optional logger
32
- * @throws Error if install fails (with details about cache healing attempts)
33
- */
34
- export const install_with_cache_healing = async (repo, ops, log) => {
35
- // First attempt
36
- const install_result = await ops.npm.install({ cwd: repo.repo_dir });
37
- if (install_result.ok) {
38
- return; // Success
39
- }
40
- // Check if error is ETARGET (package not found due to stale cache)
41
- const stderr = install_result.stderr || '';
42
- const message = install_result.message || '';
43
- if (!is_etarget_error(message, stderr)) {
44
- // Different error - fail immediately without cache healing
45
- throw new Error(`Failed to install dependencies in ${repo.library.name}: ${install_result.message}${stderr ? `\n${stderr}` : ''}`);
46
- }
47
- // ETARGET error - try cache healing
48
- log?.warn(st('yellow', ` ⚠️ ETARGET error detected - cleaning npm cache...`));
49
- const cache_result = await ops.npm.cache_clean();
50
- if (!cache_result.ok) {
51
- throw new Error(`Failed to clean npm cache: ${cache_result.message}`);
52
- }
53
- log?.info(' ✓ Cache cleaned, retrying install...');
54
- // Retry install after cache clean
55
- const retry_result = await ops.npm.install({ cwd: repo.repo_dir });
56
- if (!retry_result.ok) {
57
- throw new Error(`Failed to install dependencies after cache clean in ${repo.library.name}: ${retry_result.message}${retry_result.stderr ? `\n${retry_result.stderr}` : ''}`);
58
- }
59
- log?.info(st('green', ` ✓ Dependencies installed successfully after cache heal`));
60
- };
package/dist/semver.d.ts DELETED
@@ -1,26 +0,0 @@
1
- /**
2
- * Semantic Versioning 2.0.0 utilities
3
- * @see https://semver.org/
4
- *
5
- * @module
6
- */
7
- export type BumpType = 'major' | 'minor' | 'patch';
8
- export interface Semver {
9
- major: number;
10
- minor: number;
11
- patch: number;
12
- prerelease?: string;
13
- build?: string;
14
- }
15
- /**
16
- * Compares two semver versions according to SemVer 2.0.0 spec.
17
- * Returns -1 if a < b, 0 if a === b, 1 if a > b.
18
- * Build metadata is ignored in precedence comparison.
19
- */
20
- export declare const semver_compare_versions: (a: string, b: string) => number;
21
- /**
22
- * Bumps a version according to the specified type.
23
- * Resets lower version numbers per SemVer spec.
24
- */
25
- export declare const semver_bump_version: (version: string, type: BumpType) => string;
26
- //# sourceMappingURL=semver.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"semver.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/semver.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAEnD,MAAM,WAAW,MAAM;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAiGD;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MAqB9D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,MAAM,EAAE,MAAM,QAAQ,KAAG,MAuBrE,CAAC"}
package/dist/semver.js DELETED
@@ -1,137 +0,0 @@
1
- // TODO: candidate for extraction to `@fuzdev/fuz_util`
2
- /**
3
- * SemVer 2.0.0 validation regex.
4
- * @see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
5
- */
6
- const SEMVER_REGEX = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
7
- /**
8
- * Parses a semver version string.
9
- * Accepts optional 'v' prefix for convenience.
10
- */
11
- const semver_parse = (version) => {
12
- // Remove leading 'v' if present (common in git tags)
13
- const clean = version.replace(/^v/, '');
14
- // Validate the cleaned version
15
- if (!SEMVER_REGEX.test(clean)) {
16
- throw new Error(`Invalid semver: ${version}`);
17
- }
18
- const match = SEMVER_REGEX.exec(clean);
19
- return {
20
- major: parseInt(match[1], 10),
21
- minor: parseInt(match[2], 10),
22
- patch: parseInt(match[3], 10),
23
- prerelease: match[4],
24
- build: match[5],
25
- };
26
- };
27
- const semver_to_string = (semver) => {
28
- let version = `${semver.major}.${semver.minor}.${semver.patch}`;
29
- if (semver.prerelease) {
30
- version += `-${semver.prerelease}`;
31
- }
32
- if (semver.build) {
33
- version += `+${semver.build}`;
34
- }
35
- return version;
36
- };
37
- /**
38
- * Compares two prerelease versions according to SemVer 2.0.0 spec.
39
- * Returns -1 if a < b, 0 if a === b, 1 if a > b.
40
- */
41
- const semver_compare_prerelease = (a, b) => {
42
- // Handle missing prereleases
43
- if (!a && !b)
44
- return 0;
45
- if (!a)
46
- return 1; // normal version > prerelease
47
- if (!b)
48
- return -1; // prerelease < normal version
49
- // Split into identifiers
50
- const a_parts = a.split('.');
51
- const b_parts = b.split('.');
52
- // Compare each identifier
53
- const min_length = Math.min(a_parts.length, b_parts.length);
54
- for (let i = 0; i < min_length; i++) {
55
- const a_part = a_parts[i];
56
- const b_part = b_parts[i];
57
- // Check if numeric
58
- const a_is_numeric = /^\d+$/.test(a_part);
59
- const b_is_numeric = /^\d+$/.test(b_part);
60
- if (a_is_numeric && b_is_numeric) {
61
- // Both numeric - compare numerically
62
- const a_num = parseInt(a_part, 10);
63
- const b_num = parseInt(b_part, 10);
64
- if (a_num !== b_num) {
65
- return a_num < b_num ? -1 : 1;
66
- }
67
- }
68
- else if (a_is_numeric && !b_is_numeric) {
69
- // Numeric identifiers always have lower precedence
70
- return -1;
71
- }
72
- else if (!a_is_numeric && b_is_numeric) {
73
- // Numeric identifiers always have lower precedence
74
- return 1;
75
- }
76
- else {
77
- // Both alphanumeric - compare lexically
78
- const cmp = a_part.localeCompare(b_part);
79
- if (cmp !== 0) {
80
- return cmp < 0 ? -1 : 1;
81
- }
82
- }
83
- }
84
- // All identifiers equal - larger set has higher precedence
85
- if (a_parts.length !== b_parts.length) {
86
- return a_parts.length < b_parts.length ? -1 : 1;
87
- }
88
- return 0;
89
- };
90
- /**
91
- * Compares two semver versions according to SemVer 2.0.0 spec.
92
- * Returns -1 if a < b, 0 if a === b, 1 if a > b.
93
- * Build metadata is ignored in precedence comparison.
94
- */
95
- export const semver_compare_versions = (a, b) => {
96
- const v1 = semver_parse(a);
97
- const v2 = semver_parse(b);
98
- // Compare major
99
- if (v1.major !== v2.major) {
100
- return v1.major < v2.major ? -1 : 1;
101
- }
102
- // Compare minor
103
- if (v1.minor !== v2.minor) {
104
- return v1.minor < v2.minor ? -1 : 1;
105
- }
106
- // Compare patch
107
- if (v1.patch !== v2.patch) {
108
- return v1.patch < v2.patch ? -1 : 1;
109
- }
110
- // Compare prerelease (build metadata is ignored)
111
- return semver_compare_prerelease(v1.prerelease, v2.prerelease);
112
- };
113
- /**
114
- * Bumps a version according to the specified type.
115
- * Resets lower version numbers per SemVer spec.
116
- */
117
- export const semver_bump_version = (version, type) => {
118
- const semver = semver_parse(version);
119
- switch (type) {
120
- case 'major':
121
- semver.major++;
122
- semver.minor = 0;
123
- semver.patch = 0;
124
- break;
125
- case 'minor':
126
- semver.minor++;
127
- semver.patch = 0;
128
- break;
129
- case 'patch':
130
- semver.patch++;
131
- break;
132
- }
133
- // Remove prerelease and build when bumping
134
- semver.prerelease = undefined;
135
- semver.build = undefined;
136
- return semver_to_string(semver);
137
- };
@@ -1,59 +0,0 @@
1
- /**
2
- * JSON-serializable types for command output formats.
3
- *
4
- * Gitops commands support `--format json` and `--format markdown` output modes
5
- * in addition to styled terminal output. These types define the JSON schema for:
6
- * - Dependency graph structures (`SerializedGraph`)
7
- * - Publishing plan predictions (`SerializedPublishingPlan`)
8
- *
9
- * Used by `gitops_analyze`, `gitops_plan`, and `gitops_publish` (dry run) when
10
- * `--format json` or `--outfile` is specified.
11
- *
12
- * @module
13
- */
14
- import type { DependencyGraph } from './dependency_graph.js';
15
- export interface SerializedNode {
16
- name: string;
17
- version: string;
18
- dependencies: Array<{
19
- name: string;
20
- type: string;
21
- version: string;
22
- }>;
23
- dependents: Array<string>;
24
- publishable: boolean;
25
- }
26
- export interface SerializedGraph {
27
- nodes: Array<SerializedNode>;
28
- edges: Array<[string, string]>;
29
- }
30
- export interface SerializedPublishingPlan {
31
- publishing_order: Array<string>;
32
- version_changes: Array<{
33
- package_name: string;
34
- from: string;
35
- to: string;
36
- bump_type: string;
37
- breaking: boolean;
38
- has_changesets: boolean;
39
- will_generate_changeset?: boolean;
40
- needs_bump_escalation?: boolean;
41
- existing_bump?: string;
42
- required_bump?: string;
43
- }>;
44
- dependency_updates: Array<{
45
- dependent_package: string;
46
- updated_dependency: string;
47
- new_version: string;
48
- type: string;
49
- causes_republish: boolean;
50
- }>;
51
- breaking_cascades: Record<string, Array<string>>;
52
- warnings: Array<string>;
53
- errors: Array<string>;
54
- }
55
- /**
56
- * Serializes a dependency graph to a JSON-safe format.
57
- */
58
- export declare const serialize_graph: (graph: DependencyGraph) => SerializedGraph;
59
- //# sourceMappingURL=serialization_types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"serialization_types.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/serialization_types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAE3D,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,KAAK,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;IACH,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1B,WAAW,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;IAC7B,KAAK,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,wBAAwB;IACxC,gBAAgB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAChC,eAAe,EAAE,KAAK,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;QACrB,IAAI,EAAE,MAAM,CAAC;QACb,EAAE,EAAE,MAAM,CAAC;QACX,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,OAAO,CAAC;QAClB,cAAc,EAAE,OAAO,CAAC;QACxB,uBAAuB,CAAC,EAAE,OAAO,CAAC;QAClC,qBAAqB,CAAC,EAAE,OAAO,CAAC;QAChC,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,aAAa,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC,CAAC;IACH,kBAAkB,EAAE,KAAK,CAAC;QACzB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,gBAAgB,EAAE,OAAO,CAAC;KAC1B,CAAC,CAAC;IACH,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACjD,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACxB,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACtB;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,eAAe,KAAG,eA6BxD,CAAC"}
@@ -1,42 +0,0 @@
1
- /**
2
- * JSON-serializable types for command output formats.
3
- *
4
- * Gitops commands support `--format json` and `--format markdown` output modes
5
- * in addition to styled terminal output. These types define the JSON schema for:
6
- * - Dependency graph structures (`SerializedGraph`)
7
- * - Publishing plan predictions (`SerializedPublishingPlan`)
8
- *
9
- * Used by `gitops_analyze`, `gitops_plan`, and `gitops_publish` (dry run) when
10
- * `--format json` or `--outfile` is specified.
11
- *
12
- * @module
13
- */
14
- /**
15
- * Serializes a dependency graph to a JSON-safe format.
16
- */
17
- export const serialize_graph = (graph) => {
18
- const nodes = [];
19
- const edges = [];
20
- // Serialize nodes
21
- for (const node of graph.nodes.values()) {
22
- const dependencies = Array.from(node.dependencies, ([dep_name, spec]) => ({
23
- name: dep_name,
24
- type: spec.type,
25
- version: spec.version,
26
- }));
27
- nodes.push({
28
- name: node.name,
29
- version: node.version,
30
- dependencies,
31
- dependents: Array.from(node.dependents),
32
- publishable: node.publishable,
33
- });
34
- }
35
- // Serialize edges
36
- for (const [from, tos] of graph.edges) {
37
- for (const to of tos) {
38
- edges.push([from, to]);
39
- }
40
- }
41
- return { nodes, edges };
42
- };
@@ -1,85 +0,0 @@
1
- import type {Logger} from '@fuzdev/fuz_util/log.js';
2
- import {styleText as st} from 'node:util';
3
-
4
- import type {LocalRepo} from './local_repo.js';
5
- import type {GitopsOperations} from './operations.js';
6
-
7
- /**
8
- * Checks if an npm install error is caused by stale cache (ETARGET).
9
- * Detects various error message formats:
10
- * - "code ETARGET"
11
- * - "ETARGET"
12
- * - "notarget"
13
- * - "No matching version found"
14
- */
15
- const is_etarget_error = (message: string, stderr: string): boolean => {
16
- const combined = `${message} ${stderr}`.toLowerCase();
17
- return (
18
- combined.includes('etarget') ||
19
- combined.includes('notarget') ||
20
- combined.includes('no matching version found')
21
- );
22
- };
23
-
24
- /**
25
- * Installs npm dependencies with cache healing on ETARGET errors.
26
- *
27
- * **Strategy:**
28
- * 1. First attempt: regular `npm install`
29
- * 2. On ETARGET error (stale cache): `npm cache clean --force` then retry
30
- * 3. On other errors: fail immediately
31
- *
32
- * **Why ETARGET errors occur:**
33
- * After publishing a package and waiting for NPM registry propagation,
34
- * npm's local cache may still have stale "404" metadata. This healing
35
- * strategy clears the cache to force fresh metadata fetch.
36
- *
37
- * @param repo - the repository to install dependencies for
38
- * @param ops - gitops operations (for dependency injection)
39
- * @param log - optional logger
40
- * @throws Error if install fails (with details about cache healing attempts)
41
- */
42
- export const install_with_cache_healing = async (
43
- repo: LocalRepo,
44
- ops: GitopsOperations,
45
- log?: Logger,
46
- ): Promise<void> => {
47
- // First attempt
48
- const install_result = await ops.npm.install({cwd: repo.repo_dir});
49
-
50
- if (install_result.ok) {
51
- return; // Success
52
- }
53
-
54
- // Check if error is ETARGET (package not found due to stale cache)
55
- const stderr = install_result.stderr || '';
56
- const message = install_result.message || '';
57
-
58
- if (!is_etarget_error(message, stderr)) {
59
- // Different error - fail immediately without cache healing
60
- throw new Error(
61
- `Failed to install dependencies in ${repo.library.name}: ${install_result.message}${stderr ? `\n${stderr}` : ''}`,
62
- );
63
- }
64
-
65
- // ETARGET error - try cache healing
66
- log?.warn(st('yellow', ` ⚠️ ETARGET error detected - cleaning npm cache...`));
67
-
68
- const cache_result = await ops.npm.cache_clean();
69
- if (!cache_result.ok) {
70
- throw new Error(`Failed to clean npm cache: ${cache_result.message}`);
71
- }
72
-
73
- log?.info(' ✓ Cache cleaned, retrying install...');
74
-
75
- // Retry install after cache clean
76
- const retry_result = await ops.npm.install({cwd: repo.repo_dir});
77
-
78
- if (!retry_result.ok) {
79
- throw new Error(
80
- `Failed to install dependencies after cache clean in ${repo.library.name}: ${retry_result.message}${retry_result.stderr ? `\n${retry_result.stderr}` : ''}`,
81
- );
82
- }
83
-
84
- log?.info(st('green', ` ✓ Dependencies installed successfully after cache heal`));
85
- };