@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
@@ -28,6 +28,14 @@ export interface GetGitopsReadyOptions {
28
28
  npm_ops?: NpmOperations;
29
29
  parallel?: boolean;
30
30
  concurrency?: number;
31
+ /**
32
+ * Sync each repo's working tree to its configured branch before loading
33
+ * (switch branch, pull, install). When `false`, repos load exactly as they
34
+ * sit on disk — the safe default for read-only diagnostics. Defaults to `true`.
35
+ */
36
+ sync?: boolean;
37
+ /** When syncing, tolerate uncommitted changes instead of throwing. Defaults to `false`. */
38
+ allow_dirty?: boolean;
31
39
  }
32
40
  /**
33
41
  * Central initialization function for all gitops tasks.
@@ -35,8 +43,12 @@ export interface GetGitopsReadyOptions {
35
43
  * Initialization sequence:
36
44
  * 1. Loads and normalizes config from `gitops.config.ts`
37
45
  * 2. Resolves local repo paths (creates missing with `--download`)
38
- * 3. Switches branches and pulls latest changes (in parallel by default)
39
- * 4. Auto-installs deps if `package.json` changed during pull
46
+ * 3. If `sync`, switches branches and pulls latest changes (in parallel by default)
47
+ * 4. If `sync`, auto-installs deps if `package.json` changed during pull
48
+ *
49
+ * With `sync: false` (the default for read-only diagnostics), steps 3-4 are
50
+ * skipped and repos are loaded exactly as checked out — no branch switch, pull,
51
+ * install, or clean-workspace check.
40
52
  *
41
53
  * Priority for path resolution:
42
54
  * - `dir` argument (explicit override)
@@ -47,6 +59,8 @@ export interface GetGitopsReadyOptions {
47
59
  * @param options.npm_ops - for testing (defaults to real npm operations)
48
60
  * @param options.parallel - whether to load repos in parallel (default: true)
49
61
  * @param options.concurrency - max concurrent repo loads (default: 5)
62
+ * @param options.sync - sync working trees before loading (default: true)
63
+ * @param options.allow_dirty - when syncing, tolerate uncommitted changes (default: false)
50
64
  * @returns initialized config and fully loaded repos ready for operations
51
65
  * @throws {TaskError} if config loading or repo resolution fails
52
66
  */
@@ -1 +1 @@
1
- {"version":3,"file":"gitops_task_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_task_helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAqB,KAAK,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAuC,KAAK,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAGrF,OAAO,KAAK,EAAC,aAAa,EAAE,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAElE,MAAM,WAAW,qBAAqB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,gBAAgB,GAC5B,SAAS,qBAAqB,KAC5B,OAAO,CAAC;IACV,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,YAAY,CAAC;IAC5B,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAC9B,CAqCA,CAAC;AAEF,MAAM,WAAW,yBAAyB;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,oBAAoB,GAChC,SAAS,yBAAyB,KAChC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAczC,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAU,aAAa,MAAM,KAAG,OAAO,CAAC,YAAY,CAMpF,CAAC"}
1
+ {"version":3,"file":"gitops_task_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_task_helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAqB,KAAK,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAuC,KAAK,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAGrF,OAAO,KAAK,EAAC,aAAa,EAAE,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAElE,MAAM,WAAW,qBAAqB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,2FAA2F;IAC3F,WAAW,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;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,CAwCA,CAAC;AAEF,MAAM,WAAW,yBAAyB;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,oBAAoB,GAChC,SAAS,yBAAyB,KAChC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAczC,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAU,aAAa,MAAM,KAAG,OAAO,CAAC,YAAY,CAMpF,CAAC"}
@@ -29,8 +29,12 @@ import { DEFAULT_REPOS_DIR } from './paths.js';
29
29
  * Initialization sequence:
30
30
  * 1. Loads and normalizes config from `gitops.config.ts`
31
31
  * 2. Resolves local repo paths (creates missing with `--download`)
32
- * 3. Switches branches and pulls latest changes (in parallel by default)
33
- * 4. Auto-installs deps if `package.json` changed during pull
32
+ * 3. If `sync`, switches branches and pulls latest changes (in parallel by default)
33
+ * 4. If `sync`, auto-installs deps if `package.json` changed during pull
34
+ *
35
+ * With `sync: false` (the default for read-only diagnostics), steps 3-4 are
36
+ * skipped and repos are loaded exactly as checked out — no branch switch, pull,
37
+ * install, or clean-workspace check.
34
38
  *
35
39
  * Priority for path resolution:
36
40
  * - `dir` argument (explicit override)
@@ -41,11 +45,13 @@ import { DEFAULT_REPOS_DIR } from './paths.js';
41
45
  * @param options.npm_ops - for testing (defaults to real npm operations)
42
46
  * @param options.parallel - whether to load repos in parallel (default: true)
43
47
  * @param options.concurrency - max concurrent repo loads (default: 5)
48
+ * @param options.sync - sync working trees before loading (default: true)
49
+ * @param options.allow_dirty - when syncing, tolerate uncommitted changes (default: false)
44
50
  * @returns initialized config and fully loaded repos ready for operations
45
51
  * @throws {TaskError} if config loading or repo resolution fails
46
52
  */
47
53
  export const get_gitops_ready = async (options) => {
48
- const { config, dir, download, log, git_ops, npm_ops, parallel, concurrency } = options;
54
+ const { config, dir, download, log, git_ops, npm_ops, parallel, concurrency, sync, allow_dirty } = options;
49
55
  const config_path = resolve(config);
50
56
  const gitops_config = await import_gitops_config(config_path);
51
57
  // Priority: explicit dir arg → config repos_dir → default (two dirs up from config)
@@ -71,6 +77,8 @@ export const get_gitops_ready = async (options) => {
71
77
  npm_ops,
72
78
  parallel,
73
79
  concurrency,
80
+ sync,
81
+ allow_dirty,
74
82
  });
75
83
  return { config_path, repos_dir, gitops_config, local_repos };
76
84
  };
@@ -5,6 +5,7 @@ export declare const Args: z.ZodObject<{
5
5
  config: z.ZodDefault<z.ZodString>;
6
6
  dir: z.ZodOptional<z.ZodString>;
7
7
  verbose: z.ZodDefault<z.ZodBoolean>;
8
+ sync: z.ZodDefault<z.ZodBoolean>;
8
9
  }, z.core.$strict>;
9
10
  export type Args = z.infer<typeof Args>;
10
11
  /** @nodocs */
@@ -1 +1 @@
1
- {"version":3,"file":"gitops_validate.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_validate.task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,aAAa,CAAC;AACtC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAWtB,cAAc;AACd,eAAO,MAAM,IAAI;;;;kBAUf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC,cAAc;AACd,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CA6N3B,CAAC"}
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,aAAa,CAAC;AACtC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AActB,cAAc;AACd,eAAO,MAAM,IAAI;;;;;kBAiBf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC,cAAc;AACd,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CA2N3B,CAAC"}
@@ -1,10 +1,9 @@
1
1
  import { z } from 'zod';
2
2
  import { styleText as st } from 'node:util';
3
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';
4
+ import { analyze_repos } from './graph_validation.js';
5
+ import { generate_publishing_plan, log_publishing_plan, } from './publishing_plan.js';
6
+ import { execute_publishing_plan } from './multi_repo_publisher.js';
8
7
  import { log_dependency_analysis } from './log_helpers.js';
9
8
  import { GITOPS_CONFIG_PATH_DEFAULT } from './gitops_constants.js';
10
9
  /** @nodocs */
@@ -18,34 +17,33 @@ export const Args = z.strictObject({
18
17
  .meta({ description: 'path containing the repos, defaults to the parent of the config dir' })
19
18
  .optional(),
20
19
  verbose: z.boolean().meta({ description: 'show additional details' }).default(false),
20
+ sync: z
21
+ .boolean()
22
+ .meta({
23
+ description: 'sync repos (switch branch, pull, install) before validating instead of reading the working tree as-is',
24
+ })
25
+ .default(false),
21
26
  });
22
27
  /** @nodocs */
23
28
  export const task = {
24
29
  Args,
25
30
  summary: 'validate gitops configuration by running all read-only commands and checking for issues',
26
31
  run: async ({ args, log }) => {
27
- const { config, dir, verbose } = args;
32
+ const { config, dir, verbose, sync } = args;
28
33
  log.info(st('cyan', 'Running Gitops Validation Suite'));
29
34
  log.info(st('dim', 'This runs all read-only commands and checks for consistency.'));
30
35
  const results = [];
31
36
  const start_time = Date.now();
32
- // Load repos once (shared by all commands)
37
+ // Load repos once (shared by all commands); read the working tree as-is unless `--sync`
33
38
  log.info(st('dim', 'Loading repositories...'));
34
- const { local_repos } = await get_gitops_ready({ config, dir, download: false, log });
39
+ const { local_repos } = await get_gitops_ready({ config, dir, download: false, sync, log });
35
40
  log.info(st('dim', ` Found ${local_repos.length} local repos`));
36
41
  // 1. Run gitops_analyze
37
42
  log.info(st('yellow', 'Running gitops_analyze...'));
38
43
  const analyze_start = Date.now();
39
44
  try {
40
- // Build dependency graph and validate (but don't throw on cycles for analyze)
41
- const { graph } = validate_dependency_graph(local_repos, {
42
- throw_on_prod_cycles: false, // Analyze should report, not throw
43
- log_cycles: false, // We'll collect our own statistics
44
- log_order: false,
45
- });
46
- // Perform additional analysis
47
- const builder = new DependencyGraphBuilder();
48
- const analysis = builder.analyze(graph);
45
+ // Build dependency graph and analyze cycles/wildcards (tolerating cycles)
46
+ const { analysis } = analyze_repos(local_repos);
49
47
  const analyze_duration = Date.now() - analyze_start;
50
48
  // Collect warnings, info, and errors
51
49
  const warning_details = [];
@@ -86,11 +84,13 @@ export const task = {
86
84
  });
87
85
  log.error(st('red', ` ✗ gitops_analyze failed: ${error}`));
88
86
  }
89
- // 2. Run gitops_plan
87
+ // 2. Run gitops_plan (generated once here and reused by the dry run below)
88
+ let publishing_plan;
90
89
  log.info(st('yellow', 'Running gitops_plan...'));
91
90
  const plan_start = Date.now();
92
91
  try {
93
- const plan = await generate_publishing_plan(local_repos, { log: undefined, verbose });
92
+ const plan = await generate_publishing_plan(local_repos, { verbose });
93
+ publishing_plan = plan;
94
94
  const plan_duration = Date.now() - plan_start;
95
95
  const warnings = plan.warnings.length;
96
96
  const errors = plan.errors.length;
@@ -129,10 +129,10 @@ export const task = {
129
129
  try {
130
130
  const options = {
131
131
  wetrun: false,
132
- update_deps: true,
133
132
  log: undefined, // Silent for validation
134
133
  };
135
- const result = await publish_repos(local_repos, options);
134
+ // Reuse the plan from step 2 (regenerate only if that step failed to produce one).
135
+ const result = await execute_publishing_plan(local_repos, publishing_plan ?? (await generate_publishing_plan(local_repos, { verbose })), options);
136
136
  const dry_duration = Date.now() - dry_start;
137
137
  // Dry run doesn't have warnings/errors in the same format
138
138
  // We'll just check if it succeeded
@@ -13,7 +13,7 @@
13
13
  * @module
14
14
  */
15
15
  import type { Logger } from '@fuzdev/fuz_util/log.js';
16
- import { DependencyGraph } from './dependency_graph.js';
16
+ import { DependencyGraph, DependencyGraphBuilder } from './dependency_graph.js';
17
17
  import type { LocalRepo } from './local_repo.js';
18
18
  export interface GraphValidationResult {
19
19
  graph: DependencyGraph;
@@ -38,4 +38,19 @@ export declare const validate_dependency_graph: (repos: Array<LocalRepo>, option
38
38
  log_cycles?: boolean;
39
39
  log_order?: boolean;
40
40
  }) => GraphValidationResult;
41
+ /** Cycles, wildcard deps, and missing peers produced by `DependencyGraphBuilder.analyze`. */
42
+ export type DependencyAnalysis = ReturnType<DependencyGraphBuilder['analyze']>;
43
+ export interface RepoAnalysis {
44
+ graph: DependencyGraph;
45
+ analysis: DependencyAnalysis;
46
+ /** Topological publishing order, or `null` when prod/peer cycles prevent ordering. */
47
+ publishing_order: Array<string> | null;
48
+ }
49
+ /**
50
+ * Builds the dependency graph and runs cycle/wildcard analysis, tolerating cycles
51
+ * (reports rather than throws). The shared core of `gitops_analyze` and
52
+ * `gitops_validate`, which format the result themselves — so this stays silent
53
+ * (no `log`) to avoid emitting a redundant "Analyzing dependencies" line.
54
+ */
55
+ export declare const analyze_repos: (repos: Array<LocalRepo>) => RepoAnalysis;
41
56
  //# sourceMappingURL=graph_validation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"graph_validation.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/graph_validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAIpD,OAAO,EAAC,eAAe,EAAyB,MAAM,uBAAuB,CAAC;AAC9E,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAE/C,MAAM,WAAW,qBAAqB;IACrC,KAAK,EAAE,eAAe,CAAC;IACvB,gBAAgB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAChC,iBAAiB,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACxC,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,GACrC,OAAO,KAAK,CAAC,SAAS,CAAC,EACvB,UAAS;IACR,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;CACf,KACJ,qBA8DF,CAAC"}
1
+ {"version":3,"file":"graph_validation.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/graph_validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAIpD,OAAO,EAAC,eAAe,EAAE,sBAAsB,EAAC,MAAM,uBAAuB,CAAC;AAC9E,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAE/C,MAAM,WAAW,qBAAqB;IACrC,KAAK,EAAE,eAAe,CAAC;IACvB,gBAAgB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAChC,iBAAiB,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACxC,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,GACrC,OAAO,KAAK,CAAC,SAAS,CAAC,EACvB,UAAS;IACR,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;CACf,KACJ,qBA8DF,CAAC;AAEF,6FAA6F;AAC7F,MAAM,MAAM,kBAAkB,GAAG,UAAU,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC,CAAC;AAE/E,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,eAAe,CAAC;IACvB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,sFAAsF;IACtF,gBAAgB,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;CACvC;AAED;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,KAAK,CAAC,SAAS,CAAC,KAAG,YAQvD,CAAC"}
@@ -79,3 +79,18 @@ export const validate_dependency_graph = (repos, options = {}) => {
79
79
  sort_error,
80
80
  };
81
81
  };
82
+ /**
83
+ * Builds the dependency graph and runs cycle/wildcard analysis, tolerating cycles
84
+ * (reports rather than throws). The shared core of `gitops_analyze` and
85
+ * `gitops_validate`, which format the result themselves — so this stays silent
86
+ * (no `log`) to avoid emitting a redundant "Analyzing dependencies" line.
87
+ */
88
+ export const analyze_repos = (repos) => {
89
+ const { graph, publishing_order: order } = validate_dependency_graph(repos, {
90
+ throw_on_prod_cycles: false, // report, don't throw
91
+ log_cycles: false, // callers format their own cycle output
92
+ log_order: false,
93
+ });
94
+ const analysis = new DependencyGraphBuilder().analyze(graph);
95
+ return { graph, analysis, publishing_order: order.length > 0 ? order : null };
96
+ };
@@ -41,27 +41,37 @@ export interface LocalRepoMissing {
41
41
  repo_config: GitopsRepoConfig;
42
42
  }
43
43
  /**
44
- * Loads repo data with automatic syncing and dependency management.
44
+ * Loads repo data, optionally syncing the working tree first.
45
45
  *
46
- * Workflow:
46
+ * When `sync` is `false` (the default for read-only diagnostics like
47
+ * `gitops_analyze`/`gitops_plan`), the repo is loaded exactly as it sits on
48
+ * disk — no branch switch, pull, install, or clean-workspace check. This makes
49
+ * those commands safe to run on an active workspace with uncommitted changes or
50
+ * feature branches checked out.
51
+ *
52
+ * When `sync` is `true` (used by `gitops_sync`), the working tree is brought in
53
+ * line with the configured branch first:
47
54
  * 1. Records current commit hash (for detecting changes)
48
- * 2. Switches to target branch if needed (requires clean workspace)
55
+ * 2. Switches to target branch if needed (requires clean workspace unless `allow_dirty`)
49
56
  * 3. Pulls latest changes from remote (skipped for local-only repos)
50
- * 4. Validates workspace is clean after pull
57
+ * 4. Validates workspace is clean after pull (skipped if `allow_dirty`)
51
58
  * 5. Auto-installs dependencies if `package.json` changed
59
+ *
60
+ * Either way it then:
52
61
  * 6. Loads `library_json` via `library_load_from_repo` (svelte-docinfo analysis)
53
62
  * 7. Creates `Library` and extracts dependency maps
54
63
  *
55
- * This ensures repos are always in sync with their configured branch
56
- * before being used by gitops commands.
57
- *
58
- * @throws {TaskError} if workspace dirty, branch switch fails, install fails, or analysis fails
64
+ * @param sync - sync the working tree to the configured branch before loading (default `true`)
65
+ * @param allow_dirty - when syncing, tolerate uncommitted changes instead of throwing (default `false`)
66
+ * @throws {TaskError} if syncing fails (dirty workspace, branch switch, install) or analysis fails
59
67
  */
60
- export declare const local_repo_load: ({ local_repo_path, log: _log, git_ops, npm_ops, }: {
68
+ export declare const local_repo_load: ({ local_repo_path, log: _log, git_ops, npm_ops, sync, allow_dirty, }: {
61
69
  local_repo_path: LocalRepoPath;
62
70
  log?: Logger;
63
71
  git_ops?: GitOperations;
64
72
  npm_ops?: NpmOperations;
73
+ sync?: boolean;
74
+ allow_dirty?: boolean;
65
75
  }) => Promise<LocalRepo>;
66
76
  export declare const local_repos_ensure: ({ resolved_config, repos_dir, gitops_config, download, log, npm_ops, }: {
67
77
  resolved_config: ResolvedGitopsConfig;
@@ -71,13 +81,15 @@ export declare const local_repos_ensure: ({ resolved_config, repos_dir, gitops_c
71
81
  log?: Logger;
72
82
  npm_ops?: NpmOperations;
73
83
  }) => Promise<Array<LocalRepoPath>>;
74
- export declare const local_repos_load: ({ local_repo_paths, log, git_ops, npm_ops, parallel, concurrency, }: {
84
+ export declare const local_repos_load: ({ local_repo_paths, log, git_ops, npm_ops, parallel, concurrency, sync, allow_dirty, }: {
75
85
  local_repo_paths: Array<LocalRepoPath>;
76
86
  log?: Logger;
77
87
  git_ops?: GitOperations;
78
88
  npm_ops?: NpmOperations;
79
89
  parallel?: boolean;
80
90
  concurrency?: number;
91
+ sync?: boolean;
92
+ allow_dirty?: boolean;
81
93
  }) => Promise<Array<LocalRepo>>;
82
94
  export declare const local_repo_locate: ({ repo_config, repos_dir, }: {
83
95
  repo_config: GitopsRepoConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"local_repo.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/local_repo.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAC,OAAO,EAAC,MAAM,kCAAkC,CAAC;AAKzD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAGpD,OAAO,KAAK,EAAC,aAAa,EAAE,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAGlE,OAAO,KAAK,EAAC,YAAY,EAAE,gBAAgB,EAAC,MAAM,oBAAoB,CAAC;AACvE,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,6BAA6B,CAAC;AAGtE;;;GAGG;AACH,MAAM,WAAW,SAAS;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,WAAW,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,gBAAgB,CAAC;IAC9B,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,iBAAiB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,iBAAiB,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,gBAAgB,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,oBAAoB,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,gBAAgB,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,eAAe,GAAU,mDAKnC;IACF,eAAe,EAAE,aAAa,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,OAAO,CAAC,EAAE,aAAa,CAAC;CACxB,KAAG,OAAO,CAAC,SAAS,CAuIpB,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAU,wEAOtC;IACF,eAAe,EAAE,oBAAoB,CAAC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,YAAY,CAAC;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,aAAa,CAAC;CACxB,KAAG,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAkC/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAU,qEAOpC;IACF,gBAAgB,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB,KAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CA2C3B,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,6BAG/B;IACF,WAAW,EAAE,gBAAgB,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;CAClB,KAAG,aAAa,GAAG,gBAoBnB,CAAC"}
1
+ {"version":3,"file":"local_repo.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/local_repo.ts"],"names":[],"mappings":"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"}
@@ -9,91 +9,108 @@ import { map_concurrent_settled } from '@fuzdev/fuz_util/async.js';
9
9
  import { default_git_operations, default_npm_operations } from './operations_defaults.js';
10
10
  import { GITOPS_CONCURRENCY_DEFAULT } from './gitops_constants.js';
11
11
  /**
12
- * Loads repo data with automatic syncing and dependency management.
12
+ * Loads repo data, optionally syncing the working tree first.
13
13
  *
14
- * Workflow:
14
+ * When `sync` is `false` (the default for read-only diagnostics like
15
+ * `gitops_analyze`/`gitops_plan`), the repo is loaded exactly as it sits on
16
+ * disk — no branch switch, pull, install, or clean-workspace check. This makes
17
+ * those commands safe to run on an active workspace with uncommitted changes or
18
+ * feature branches checked out.
19
+ *
20
+ * When `sync` is `true` (used by `gitops_sync`), the working tree is brought in
21
+ * line with the configured branch first:
15
22
  * 1. Records current commit hash (for detecting changes)
16
- * 2. Switches to target branch if needed (requires clean workspace)
23
+ * 2. Switches to target branch if needed (requires clean workspace unless `allow_dirty`)
17
24
  * 3. Pulls latest changes from remote (skipped for local-only repos)
18
- * 4. Validates workspace is clean after pull
25
+ * 4. Validates workspace is clean after pull (skipped if `allow_dirty`)
19
26
  * 5. Auto-installs dependencies if `package.json` changed
27
+ *
28
+ * Either way it then:
20
29
  * 6. Loads `library_json` via `library_load_from_repo` (svelte-docinfo analysis)
21
30
  * 7. Creates `Library` and extracts dependency maps
22
31
  *
23
- * This ensures repos are always in sync with their configured branch
24
- * before being used by gitops commands.
25
- *
26
- * @throws {TaskError} if workspace dirty, branch switch fails, install fails, or analysis fails
32
+ * @param sync - sync the working tree to the configured branch before loading (default `true`)
33
+ * @param allow_dirty - when syncing, tolerate uncommitted changes instead of throwing (default `false`)
34
+ * @throws {TaskError} if syncing fails (dirty workspace, branch switch, install) or analysis fails
27
35
  */
28
- export const local_repo_load = async ({ local_repo_path, log: _log, git_ops = default_git_operations, npm_ops = default_npm_operations, }) => {
36
+ export const local_repo_load = async ({ local_repo_path, log: _log, git_ops = default_git_operations, npm_ops = default_npm_operations, sync = true, allow_dirty = false, }) => {
29
37
  const { repo_config, repo_dir, repo_name, repo_git_ssh_url } = local_repo_path;
30
- // Record commit hash before any changes
31
- const commit_before_result = await git_ops.current_commit_hash({ cwd: repo_dir });
32
- if (!commit_before_result.ok) {
33
- throw new TaskError(`Failed to get commit hash in ${repo_dir}: ${commit_before_result.message}`);
34
- }
35
- const commit_before = commit_before_result.value;
36
- // Switch to target branch if needed
37
- const branch_result = await git_ops.current_branch_name({ cwd: repo_dir });
38
- if (!branch_result.ok) {
39
- throw new TaskError(`Failed to get current branch in ${repo_dir}: ${branch_result.message}`);
40
- }
41
- const switched_branches = branch_result.value !== repo_config.branch;
42
- if (switched_branches) {
43
- const clean_result = await git_ops.check_clean_workspace({ cwd: repo_dir });
44
- if (!clean_result.ok) {
45
- throw new TaskError(`Failed to check workspace in ${repo_dir}: ${clean_result.message}`);
38
+ if (sync) {
39
+ // Record commit hash before any changes
40
+ const commit_before_result = await git_ops.current_commit_hash({ cwd: repo_dir });
41
+ if (!commit_before_result.ok) {
42
+ throw new TaskError(`Failed to get commit hash in ${repo_dir}: ${commit_before_result.message}`);
46
43
  }
47
- if (!clean_result.value) {
48
- throw new TaskError(`Repo ${repo_dir} is not on branch "${repo_config.branch}" and the workspace is unclean, blocking switch`);
44
+ const commit_before = commit_before_result.value;
45
+ // Switch to target branch if needed
46
+ const branch_result = await git_ops.current_branch_name({ cwd: repo_dir });
47
+ if (!branch_result.ok) {
48
+ throw new TaskError(`Failed to get current branch in ${repo_dir}: ${branch_result.message}`);
49
49
  }
50
- const checkout_result = await git_ops.checkout({ branch: repo_config.branch, cwd: repo_dir });
51
- if (!checkout_result.ok) {
52
- throw new TaskError(`Failed to checkout branch "${repo_config.branch}" in ${repo_dir}: ${checkout_result.message}`);
50
+ const switched_branches = branch_result.value !== repo_config.branch;
51
+ if (switched_branches) {
52
+ // Guard the switch on a clean workspace unless the caller opts into `allow_dirty`,
53
+ // in which case we let `git checkout` itself fail loudly if it can't proceed.
54
+ if (!allow_dirty) {
55
+ const clean_result = await git_ops.check_clean_workspace({ cwd: repo_dir });
56
+ if (!clean_result.ok) {
57
+ throw new TaskError(`Failed to check workspace in ${repo_dir}: ${clean_result.message}`);
58
+ }
59
+ if (!clean_result.value) {
60
+ throw new TaskError(`Repo ${repo_dir} is not on branch "${repo_config.branch}" and the workspace is unclean, blocking switch`);
61
+ }
62
+ }
63
+ const checkout_result = await git_ops.checkout({ branch: repo_config.branch, cwd: repo_dir });
64
+ if (!checkout_result.ok) {
65
+ throw new TaskError(`Failed to checkout branch "${repo_config.branch}" in ${repo_dir}: ${checkout_result.message}`);
66
+ }
53
67
  }
54
- }
55
- // Only pull if remote exists (skip for local-only repos, test fixtures)
56
- const origin_result = await git_ops.has_remote({ remote: 'origin', cwd: repo_dir });
57
- if (!origin_result.ok) {
58
- throw new TaskError(`Failed to check for remote in ${repo_dir}: ${origin_result.message}`);
59
- }
60
- if (origin_result.value) {
61
- const pull_result = await git_ops.pull({ cwd: repo_dir });
62
- if (!pull_result.ok) {
63
- throw new TaskError(`Failed to pull in ${repo_dir}: ${pull_result.message}`);
68
+ // Only pull if remote exists (skip for local-only repos, test fixtures)
69
+ const origin_result = await git_ops.has_remote({ remote: 'origin', cwd: repo_dir });
70
+ if (!origin_result.ok) {
71
+ throw new TaskError(`Failed to check for remote in ${repo_dir}: ${origin_result.message}`);
64
72
  }
65
- }
66
- // Check clean workspace after pull to ensure we're in a good state
67
- const clean_after_result = await git_ops.check_clean_workspace({ cwd: repo_dir });
68
- if (!clean_after_result.ok) {
69
- throw new TaskError(`Failed to check workspace in ${repo_dir}: ${clean_after_result.message}`);
70
- }
71
- if (!clean_after_result.value) {
72
- throw new TaskError(`Workspace ${repo_dir} is unclean after pulling branch "${repo_config.branch}"`);
73
- }
74
- // Record commit hash after pull
75
- const commit_after_result = await git_ops.current_commit_hash({ cwd: repo_dir });
76
- if (!commit_after_result.ok) {
77
- throw new TaskError(`Failed to get commit hash in ${repo_dir}: ${commit_after_result.message}`);
78
- }
79
- const commit_after = commit_after_result.value;
80
- // Track if we got new commits
81
- const got_new_commits = commit_before !== commit_after;
82
- // Only install if package.json changed
83
- if (got_new_commits) {
84
- const changed_result = await git_ops.has_file_changed({
85
- from_commit: commit_before,
86
- to_commit: commit_after,
87
- file_path: 'package.json',
88
- cwd: repo_dir,
89
- });
90
- if (!changed_result.ok) {
91
- throw new TaskError(`Failed to check if package.json changed in ${repo_dir}: ${changed_result.message}`);
92
- }
93
- if (changed_result.value) {
94
- const install_result = await npm_ops.install({ cwd: repo_dir });
95
- if (!install_result.ok) {
96
- throw new TaskError(`Failed to install dependencies in ${repo_dir}: ${install_result.message}${install_result.stderr ? `\n${install_result.stderr}` : ''}`);
73
+ if (origin_result.value) {
74
+ const pull_result = await git_ops.pull({ cwd: repo_dir });
75
+ if (!pull_result.ok) {
76
+ throw new TaskError(`Failed to pull in ${repo_dir}: ${pull_result.message}`);
77
+ }
78
+ }
79
+ // Check clean workspace after pull to ensure we're in a good state
80
+ // (skipped when `allow_dirty`, since uncommitted changes are expected then)
81
+ if (!allow_dirty) {
82
+ const clean_after_result = await git_ops.check_clean_workspace({ cwd: repo_dir });
83
+ if (!clean_after_result.ok) {
84
+ throw new TaskError(`Failed to check workspace in ${repo_dir}: ${clean_after_result.message}`);
85
+ }
86
+ if (!clean_after_result.value) {
87
+ throw new TaskError(`Workspace ${repo_dir} is unclean after pulling branch "${repo_config.branch}"`);
88
+ }
89
+ }
90
+ // Record commit hash after pull
91
+ const commit_after_result = await git_ops.current_commit_hash({ cwd: repo_dir });
92
+ if (!commit_after_result.ok) {
93
+ throw new TaskError(`Failed to get commit hash in ${repo_dir}: ${commit_after_result.message}`);
94
+ }
95
+ const commit_after = commit_after_result.value;
96
+ // Track if we got new commits
97
+ const got_new_commits = commit_before !== commit_after;
98
+ // Only install if package.json changed
99
+ if (got_new_commits) {
100
+ const changed_result = await git_ops.has_file_changed({
101
+ from_commit: commit_before,
102
+ to_commit: commit_after,
103
+ file_path: 'package.json',
104
+ cwd: repo_dir,
105
+ });
106
+ if (!changed_result.ok) {
107
+ throw new TaskError(`Failed to check if package.json changed in ${repo_dir}: ${changed_result.message}`);
108
+ }
109
+ if (changed_result.value) {
110
+ const install_result = await npm_ops.install({ cwd: repo_dir });
111
+ if (!install_result.ok) {
112
+ throw new TaskError(`Failed to install dependencies in ${repo_dir}: ${install_result.message}${install_result.stderr ? `\n${install_result.stderr}` : ''}`);
113
+ }
97
114
  }
98
115
  }
99
116
  }
@@ -157,18 +174,18 @@ export const local_repos_ensure = async ({ resolved_config, repos_dir, gitops_co
157
174
  }
158
175
  return local_repo_paths;
159
176
  };
160
- export const local_repos_load = async ({ local_repo_paths, log, git_ops = default_git_operations, npm_ops = default_npm_operations, parallel = true, concurrency = GITOPS_CONCURRENCY_DEFAULT, }) => {
177
+ export const local_repos_load = async ({ local_repo_paths, log, git_ops = default_git_operations, npm_ops = default_npm_operations, parallel = true, concurrency = GITOPS_CONCURRENCY_DEFAULT, sync = true, allow_dirty = false, }) => {
161
178
  if (!parallel) {
162
179
  // Sequential loading (original behavior)
163
180
  const loaded = [];
164
181
  for (const local_repo_path of local_repo_paths) {
165
- loaded.push(await local_repo_load({ local_repo_path, log, git_ops, npm_ops }));
182
+ loaded.push(await local_repo_load({ local_repo_path, log, git_ops, npm_ops, sync, allow_dirty }));
166
183
  }
167
184
  return loaded;
168
185
  }
169
186
  // Parallel loading with concurrency limit
170
187
  const results = await map_concurrent_settled(local_repo_paths, concurrency, async (local_repo_path) => {
171
- return local_repo_load({ local_repo_path, log, git_ops, npm_ops });
188
+ return local_repo_load({ local_repo_path, log, git_ops, npm_ops, sync, allow_dirty });
172
189
  });
173
190
  // Check for failures and collect successes
174
191
  const loaded = [];
@@ -35,9 +35,4 @@ export declare const log_production_cycles: (analysis: ReturnType<DependencyGrap
35
35
  * Convenience function that calls all three logging functions in order.
36
36
  */
37
37
  export declare const log_dependency_analysis: (analysis: ReturnType<DependencyGraphBuilder["analyze"]>, log: Logger, indent?: string) => void;
38
- /**
39
- * Logs a simple bulleted list with a header.
40
- * Common pattern for warnings, info messages, and other lists.
41
- */
42
- export declare const log_list: (items: Array<string>, header: string, color: "cyan" | "yellow" | "red" | "dim", log: Logger, log_method?: "info" | "warn" | "error") => void;
43
38
  //# sourceMappingURL=log_helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"log_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/log_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAGpD,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,uBAAuB,CAAC;AAElE;;;GAGG;AACH,eAAO,MAAM,4BAA4B,GACxC,UAAU,UAAU,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC,KACrD,KAAK,CAAC,MAAM,CASd,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAC7B,UAAU,UAAU,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC,KACrD,KAAK,CAAC,MAAM,CAcd,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,GACpC,UAAU,UAAU,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC,KACrD,KAAK,CAAC,MAAM,CAcd,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,yBAAyB,GACrC,UAAU,UAAU,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC,EACvD,KAAK,MAAM,EACX,eAAW,KACT,IAKF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,GAC1B,UAAU,UAAU,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC,EACvD,KAAK,MAAM,EACX,eAAW,KACT,IAKF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,GACjC,UAAU,UAAU,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC,EACvD,KAAK,MAAM,EACX,eAAW,KACT,IAKF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GACnC,UAAU,UAAU,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC,EACvD,KAAK,MAAM,EACX,eAAW,KACT,IAIF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,QAAQ,GACpB,OAAO,KAAK,CAAC,MAAM,CAAC,EACpB,QAAQ,MAAM,EACd,OAAO,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,KAAK,EACxC,KAAK,MAAM,EACX,aAAY,MAAM,GAAG,MAAM,GAAG,OAAgB,KAC5C,IAWF,CAAC"}
1
+ {"version":3,"file":"log_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/log_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAGpD,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,uBAAuB,CAAC;AAElE;;;GAGG;AACH,eAAO,MAAM,4BAA4B,GACxC,UAAU,UAAU,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC,KACrD,KAAK,CAAC,MAAM,CAYd,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAC7B,UAAU,UAAU,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC,KACrD,KAAK,CAAC,MAAM,CAed,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,GACpC,UAAU,UAAU,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC,KACrD,KAAK,CAAC,MAAM,CAed,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,yBAAyB,GACrC,UAAU,UAAU,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC,EACvD,KAAK,MAAM,EACX,eAAW,KACT,IAKF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,GAC1B,UAAU,UAAU,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC,EACvD,KAAK,MAAM,EACX,eAAW,KACT,IAKF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,GACjC,UAAU,UAAU,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC,EACvD,KAAK,MAAM,EACX,eAAW,KACT,IAKF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GACnC,UAAU,UAAU,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC,EACvD,KAAK,MAAM,EACX,eAAW,KACT,IAIF,CAAC"}
@@ -7,7 +7,10 @@ export const format_wildcard_dependencies = (analysis) => {
7
7
  if (analysis.wildcard_deps.length === 0)
8
8
  return [];
9
9
  const lines = [];
10
- lines.push(st('yellow', `\n⚠️ Found ${analysis.wildcard_deps.length} wildcard dependencies:`));
10
+ // Blank separator as its own line so the logger prefixes it correctly; embedding
11
+ // a leading `\n` in the header would leave the header line unprefixed.
12
+ lines.push('');
13
+ lines.push(st('yellow', `⚠️ Found ${analysis.wildcard_deps.length} wildcard dependencies:`));
11
14
  for (const { pkg, dep, version } of analysis.wildcard_deps) {
12
15
  lines.push(` ${pkg} → ${dep} ${st('red', version)}`);
13
16
  }
@@ -21,7 +24,8 @@ export const format_dev_cycles = (analysis) => {
21
24
  if (analysis.dev_cycles.length === 0)
22
25
  return [];
23
26
  const lines = [];
24
- lines.push(st('dim', `\nℹ️ Found ${analysis.dev_cycles.length} dev circular dependencies (normal, non-blocking):`));
27
+ lines.push('');
28
+ lines.push(st('dim', `ℹ️ Found ${analysis.dev_cycles.length} dev circular dependencies (normal, non-blocking):`));
25
29
  for (const cycle of analysis.dev_cycles) {
26
30
  lines.push(st('dim', ` ${cycle.join(' → ')}`));
27
31
  }
@@ -35,7 +39,8 @@ export const format_production_cycles = (analysis) => {
35
39
  if (analysis.production_cycles.length === 0)
36
40
  return [];
37
41
  const lines = [];
38
- lines.push(st('red', `\n❌ Found ${analysis.production_cycles.length} production/peer circular dependencies (blocks publishing):`));
42
+ lines.push('');
43
+ lines.push(st('red', `❌ Found ${analysis.production_cycles.length} production/peer circular dependencies (blocks publishing):`));
39
44
  for (const cycle of analysis.production_cycles) {
40
45
  lines.push(` ${st('red', cycle.join(' → '))}`);
41
46
  }
@@ -80,19 +85,3 @@ export const log_dependency_analysis = (analysis, log, indent = '') => {
80
85
  log_production_cycles(analysis, log, indent);
81
86
  log_dev_cycles(analysis, log, indent);
82
87
  };
83
- /**
84
- * Logs a simple bulleted list with a header.
85
- * Common pattern for warnings, info messages, and other lists.
86
- */
87
- export const log_list = (items, header, color, log, log_method = 'info') => {
88
- if (items.length === 0)
89
- return;
90
- // Only add newline prefix if header is non-empty
91
- const formatted_header = header ? `\n${header}` : '';
92
- if (formatted_header) {
93
- log[log_method](st(color, formatted_header));
94
- }
95
- for (const item of items) {
96
- log[log_method](st(color, ` • ${item}`));
97
- }
98
- };