@fuzdev/fuz_gitops 0.72.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 (110) 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 +2 -2
  17. package/dist/gitops_publish.task.d.ts.map +1 -1
  18. package/dist/gitops_publish.task.js +83 -28
  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 +21 -2
  41. package/dist/multi_repo_publisher.d.ts.map +1 -1
  42. package/dist/multi_repo_publisher.js +279 -287
  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 +12 -19
  59. package/dist/publishing_event.d.ts.map +1 -1
  60. package/dist/publishing_event.js +16 -25
  61. package/dist/publishing_plan.d.ts +1 -2
  62. package/dist/publishing_plan.d.ts.map +1 -1
  63. package/dist/publishing_plan.js +18 -1
  64. package/dist/publishing_plan_helpers.d.ts +1 -1
  65. package/dist/publishing_plan_helpers.d.ts.map +1 -1
  66. package/dist/publishing_plan_helpers.js +2 -15
  67. package/dist/publishing_plan_logging.d.ts.map +1 -1
  68. package/dist/publishing_plan_logging.js +15 -23
  69. package/dist/version_utils.d.ts +17 -3
  70. package/dist/version_utils.d.ts.map +1 -1
  71. package/dist/version_utils.js +22 -0
  72. package/package.json +5 -6
  73. package/src/lib/changeset_generator.ts +10 -19
  74. package/src/lib/changeset_reader.ts +1 -2
  75. package/src/lib/dependency_graph.ts +1 -1
  76. package/src/lib/git_operations.ts +5 -0
  77. package/src/lib/gitops_analyze.task.ts +18 -23
  78. package/src/lib/gitops_plan.task.ts +10 -2
  79. package/src/lib/gitops_publish.task.ts +92 -27
  80. package/src/lib/gitops_run.task.ts +39 -7
  81. package/src/lib/gitops_sync.task.ts +18 -3
  82. package/src/lib/gitops_task_helpers.ts +20 -3
  83. package/src/lib/gitops_validate.task.ts +30 -22
  84. package/src/lib/graph_validation.ts +26 -0
  85. package/src/lib/local_repo.ts +114 -83
  86. package/src/lib/log_helpers.ts +8 -26
  87. package/src/lib/multi_repo_publisher.ts +336 -353
  88. package/src/lib/operations.ts +6 -22
  89. package/src/lib/operations_defaults.ts +1 -19
  90. package/src/lib/output_helpers.ts +7 -6
  91. package/src/lib/paths.ts +0 -5
  92. package/src/lib/publish_gate.ts +53 -0
  93. package/src/lib/publish_steps.ts +149 -0
  94. package/src/lib/publishing_event.ts +16 -25
  95. package/src/lib/publishing_plan.ts +23 -3
  96. package/src/lib/publishing_plan_helpers.ts +5 -18
  97. package/src/lib/publishing_plan_logging.ts +19 -39
  98. package/src/lib/version_utils.ts +30 -9
  99. package/dist/npm_install_helpers.d.ts +0 -23
  100. package/dist/npm_install_helpers.d.ts.map +0 -1
  101. package/dist/npm_install_helpers.js +0 -60
  102. package/dist/semver.d.ts +0 -26
  103. package/dist/semver.d.ts.map +0 -1
  104. package/dist/semver.js +0 -137
  105. package/dist/serialization_types.d.ts +0 -59
  106. package/dist/serialization_types.d.ts.map +0 -1
  107. package/dist/serialization_types.js +0 -42
  108. package/src/lib/npm_install_helpers.ts +0 -85
  109. package/src/lib/semver.ts +0 -170
  110. package/src/lib/serialization_types.ts +0 -92
@@ -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
- };
package/src/lib/semver.ts DELETED
@@ -1,170 +0,0 @@
1
- // TODO: candidate for extraction to `@fuzdev/fuz_util`
2
-
3
- /**
4
- * Semantic Versioning 2.0.0 utilities
5
- * @see https://semver.org/
6
- *
7
- * @module
8
- */
9
-
10
- export type BumpType = 'major' | 'minor' | 'patch';
11
-
12
- export interface Semver {
13
- major: number;
14
- minor: number;
15
- patch: number;
16
- prerelease?: string;
17
- build?: string;
18
- }
19
-
20
- /**
21
- * SemVer 2.0.0 validation regex.
22
- * @see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
23
- */
24
- const SEMVER_REGEX =
25
- /^(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-]+)*))?$/;
26
-
27
- /**
28
- * Parses a semver version string.
29
- * Accepts optional 'v' prefix for convenience.
30
- */
31
- const semver_parse = (version: string): Semver => {
32
- // Remove leading 'v' if present (common in git tags)
33
- const clean = version.replace(/^v/, '');
34
-
35
- // Validate the cleaned version
36
- if (!SEMVER_REGEX.test(clean)) {
37
- throw new Error(`Invalid semver: ${version}`);
38
- }
39
-
40
- const match = SEMVER_REGEX.exec(clean)!;
41
- return {
42
- major: parseInt(match[1]!, 10),
43
- minor: parseInt(match[2]!, 10),
44
- patch: parseInt(match[3]!, 10),
45
- prerelease: match[4],
46
- build: match[5],
47
- };
48
- };
49
-
50
- const semver_to_string = (semver: Semver): string => {
51
- let version = `${semver.major}.${semver.minor}.${semver.patch}`;
52
- if (semver.prerelease) {
53
- version += `-${semver.prerelease}`;
54
- }
55
- if (semver.build) {
56
- version += `+${semver.build}`;
57
- }
58
- return version;
59
- };
60
-
61
- /**
62
- * Compares two prerelease versions according to SemVer 2.0.0 spec.
63
- * Returns -1 if a < b, 0 if a === b, 1 if a > b.
64
- */
65
- const semver_compare_prerelease = (a: string | undefined, b: string | undefined): number => {
66
- // Handle missing prereleases
67
- if (!a && !b) return 0;
68
- if (!a) return 1; // normal version > prerelease
69
- if (!b) return -1; // prerelease < normal version
70
-
71
- // Split into identifiers
72
- const a_parts = a.split('.');
73
- const b_parts = b.split('.');
74
-
75
- // Compare each identifier
76
- const min_length = Math.min(a_parts.length, b_parts.length);
77
- for (let i = 0; i < min_length; i++) {
78
- const a_part = a_parts[i]!;
79
- const b_part = b_parts[i]!;
80
-
81
- // Check if numeric
82
- const a_is_numeric = /^\d+$/.test(a_part);
83
- const b_is_numeric = /^\d+$/.test(b_part);
84
-
85
- if (a_is_numeric && b_is_numeric) {
86
- // Both numeric - compare numerically
87
- const a_num = parseInt(a_part, 10);
88
- const b_num = parseInt(b_part, 10);
89
- if (a_num !== b_num) {
90
- return a_num < b_num ? -1 : 1;
91
- }
92
- } else if (a_is_numeric && !b_is_numeric) {
93
- // Numeric identifiers always have lower precedence
94
- return -1;
95
- } else if (!a_is_numeric && b_is_numeric) {
96
- // Numeric identifiers always have lower precedence
97
- return 1;
98
- } else {
99
- // Both alphanumeric - compare lexically
100
- const cmp = a_part.localeCompare(b_part);
101
- if (cmp !== 0) {
102
- return cmp < 0 ? -1 : 1;
103
- }
104
- }
105
- }
106
-
107
- // All identifiers equal - larger set has higher precedence
108
- if (a_parts.length !== b_parts.length) {
109
- return a_parts.length < b_parts.length ? -1 : 1;
110
- }
111
-
112
- return 0;
113
- };
114
-
115
- /**
116
- * Compares two semver versions according to SemVer 2.0.0 spec.
117
- * Returns -1 if a < b, 0 if a === b, 1 if a > b.
118
- * Build metadata is ignored in precedence comparison.
119
- */
120
- export const semver_compare_versions = (a: string, b: string): number => {
121
- const v1 = semver_parse(a);
122
- const v2 = semver_parse(b);
123
-
124
- // Compare major
125
- if (v1.major !== v2.major) {
126
- return v1.major < v2.major ? -1 : 1;
127
- }
128
-
129
- // Compare minor
130
- if (v1.minor !== v2.minor) {
131
- return v1.minor < v2.minor ? -1 : 1;
132
- }
133
-
134
- // Compare patch
135
- if (v1.patch !== v2.patch) {
136
- return v1.patch < v2.patch ? -1 : 1;
137
- }
138
-
139
- // Compare prerelease (build metadata is ignored)
140
- return semver_compare_prerelease(v1.prerelease, v2.prerelease);
141
- };
142
-
143
- /**
144
- * Bumps a version according to the specified type.
145
- * Resets lower version numbers per SemVer spec.
146
- */
147
- export const semver_bump_version = (version: string, type: BumpType): string => {
148
- const semver = semver_parse(version);
149
-
150
- switch (type) {
151
- case 'major':
152
- semver.major++;
153
- semver.minor = 0;
154
- semver.patch = 0;
155
- break;
156
- case 'minor':
157
- semver.minor++;
158
- semver.patch = 0;
159
- break;
160
- case 'patch':
161
- semver.patch++;
162
- break;
163
- }
164
-
165
- // Remove prerelease and build when bumping
166
- semver.prerelease = undefined;
167
- semver.build = undefined;
168
-
169
- return semver_to_string(semver);
170
- };