@git.zone/cli 3.2.1 → 5.0.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 (135) hide show
  1. package/.smartconfig.json +0 -1
  2. package/dist_ts/00_commitinfo_data.js +2 -2
  3. package/dist_ts/classes.gitzoneconfig.js +1 -1
  4. package/dist_ts/classes.project.js +1 -1
  5. package/dist_ts/gitzone.cli.js +1 -1
  6. package/dist_ts/gitzone.logging.js +1 -1
  7. package/dist_ts/helpers.changelog.js +1 -1
  8. package/dist_ts/helpers.climode.js +1 -1
  9. package/dist_ts/helpers.smartconfig.js +61 -11
  10. package/dist_ts/helpers.smartconfigmigrations.js +1 -1
  11. package/dist_ts/helpers.tsdocker.js +1 -1
  12. package/dist_ts/helpers.workflow.d.ts +0 -2
  13. package/dist_ts/helpers.workflow.js +23 -12
  14. package/dist_ts/index.js +1 -1
  15. package/dist_ts/mod_audit/index.js +1 -1
  16. package/dist_ts/mod_commit/index.js +1 -1
  17. package/dist_ts/mod_commit/mod.helpers.d.ts +3 -3
  18. package/dist_ts/mod_commit/mod.helpers.js +10 -10
  19. package/dist_ts/mod_commit/mod.plugins.js +1 -1
  20. package/dist_ts/mod_commit/mod.ui.js +1 -1
  21. package/dist_ts/mod_config/classes.commitconfig.js +1 -1
  22. package/dist_ts/mod_config/classes.releaseconfig.js +1 -1
  23. package/dist_ts/mod_config/index.js +23 -24
  24. package/dist_ts/mod_config/mod.plugins.js +1 -1
  25. package/dist_ts/mod_deprecate/index.js +1 -1
  26. package/dist_ts/mod_deprecate/mod.plugins.js +1 -1
  27. package/dist_ts/mod_docker/classes.dockerpruner.js +7 -1
  28. package/dist_ts/mod_docker/index.js +1 -1
  29. package/dist_ts/mod_docker/mod.plugins.js +1 -1
  30. package/dist_ts/mod_format/classes.baseformatter.js +1 -1
  31. package/dist_ts/mod_format/classes.diffreporter.js +1 -1
  32. package/dist_ts/mod_format/classes.formatcontext.js +1 -1
  33. package/dist_ts/mod_format/classes.formatplanner.js +1 -1
  34. package/dist_ts/mod_format/classes.formatstats.js +1 -1
  35. package/dist_ts/mod_format/formatters/assets.formatter.js +1 -1
  36. package/dist_ts/mod_format/formatters/cleanup.formatter.js +1 -1
  37. package/dist_ts/mod_format/formatters/copy.formatter.js +1 -1
  38. package/dist_ts/mod_format/formatters/gitignore.formatter.js +1 -1
  39. package/dist_ts/mod_format/formatters/license.formatter.js +1 -1
  40. package/dist_ts/mod_format/formatters/packagejson.formatter.js +1 -1
  41. package/dist_ts/mod_format/formatters/prettier.formatter.js +1 -1
  42. package/dist_ts/mod_format/formatters/readme.formatter.js +1 -1
  43. package/dist_ts/mod_format/formatters/smartconfig.formatter.js +1 -1
  44. package/dist_ts/mod_format/formatters/templates.formatter.js +1 -1
  45. package/dist_ts/mod_format/formatters/tsconfig.formatter.js +1 -1
  46. package/dist_ts/mod_format/index.js +1 -1
  47. package/dist_ts/mod_format/interfaces.format.js +1 -1
  48. package/dist_ts/mod_format/mod.plugins.js +1 -1
  49. package/dist_ts/mod_helpers/index.js +1 -1
  50. package/dist_ts/mod_helpers/mod.plugins.js +1 -1
  51. package/dist_ts/mod_meta/index.js +1 -1
  52. package/dist_ts/mod_meta/meta.classes.meta.js +1 -1
  53. package/dist_ts/mod_meta/meta.interfaces.js +1 -1
  54. package/dist_ts/mod_meta/meta.plugins.js +1 -1
  55. package/dist_ts/mod_open/index.js +1 -1
  56. package/dist_ts/mod_open/mod.plugins.js +1 -1
  57. package/dist_ts/mod_release/helpers.releasebranch.d.ts +46 -0
  58. package/dist_ts/mod_release/helpers.releasebranch.js +579 -0
  59. package/dist_ts/mod_release/index.d.ts +10 -0
  60. package/dist_ts/mod_release/index.js +300 -92
  61. package/dist_ts/mod_release/mod.plugins.js +1 -1
  62. package/dist_ts/mod_services/classes.dockercontainer.d.ts +50 -3
  63. package/dist_ts/mod_services/classes.dockercontainer.js +646 -84
  64. package/dist_ts/mod_services/classes.globalregistry.d.ts +10 -24
  65. package/dist_ts/mod_services/classes.globalregistry.js +210 -62
  66. package/dist_ts/mod_services/classes.globalregistrystore.d.ts +48 -0
  67. package/dist_ts/mod_services/classes.globalregistrystore.js +156 -0
  68. package/dist_ts/mod_services/classes.interprocesslock.d.ts +21 -0
  69. package/dist_ts/mod_services/classes.interprocesslock.js +141 -0
  70. package/dist_ts/mod_services/classes.serviceconfiguration.d.ts +15 -12
  71. package/dist_ts/mod_services/classes.serviceconfiguration.js +188 -91
  72. package/dist_ts/mod_services/classes.servicedatamarker.d.ts +32 -7
  73. package/dist_ts/mod_services/classes.servicedatamarker.js +145 -49
  74. package/dist_ts/mod_services/classes.servicemanager.d.ts +70 -28
  75. package/dist_ts/mod_services/classes.servicemanager.js +995 -421
  76. package/dist_ts/mod_services/classes.servicenames.d.ts +8 -0
  77. package/dist_ts/mod_services/classes.servicenames.js +54 -0
  78. package/dist_ts/mod_services/classes.serviceoptions.js +1 -1
  79. package/dist_ts/mod_services/classes.servicepruner.d.ts +3 -0
  80. package/dist_ts/mod_services/classes.servicepruner.js +208 -46
  81. package/dist_ts/mod_services/helpers.js +1 -1
  82. package/dist_ts/mod_services/index.d.ts +2 -0
  83. package/dist_ts/mod_services/index.js +81 -58
  84. package/dist_ts/mod_services/mod.plugins.js +1 -1
  85. package/dist_ts/mod_standard/index.js +2 -2
  86. package/dist_ts/mod_standard/mod.plugins.js +1 -1
  87. package/dist_ts/mod_start/index.js +1 -1
  88. package/dist_ts/mod_start/mod.plugins.js +1 -1
  89. package/dist_ts/mod_template/index.js +1 -1
  90. package/dist_ts/mod_template/mod.plugins.js +1 -1
  91. package/dist_ts/mod_tools/classes.packagemanager.js +1 -1
  92. package/dist_ts/mod_tools/index.js +1 -1
  93. package/dist_ts/mod_tools/mod.plugins.js +1 -1
  94. package/dist_ts/paths.js +1 -1
  95. package/dist_ts/plugins.d.ts +3 -1
  96. package/dist_ts/plugins.js +4 -2
  97. package/dist_ts_migration/001.service-selection.d.ts +9 -0
  98. package/dist_ts_migration/001.service-selection.js +51 -0
  99. package/dist_ts_migration/002.runtime-config.d.ts +19 -0
  100. package/dist_ts_migration/002.runtime-config.js +170 -0
  101. package/dist_ts_migration/003.service-data-marker.d.ts +9 -0
  102. package/dist_ts_migration/003.service-data-marker.js +156 -0
  103. package/dist_ts_migration/004.global-registry.d.ts +13 -0
  104. package/dist_ts_migration/004.global-registry.js +165 -0
  105. package/dist_ts_migration/index.d.ts +11 -0
  106. package/dist_ts_migration/index.js +59 -0
  107. package/package.json +6 -3
  108. package/readme.hints.md +102 -25
  109. package/readme.md +144 -41
  110. package/readme.plan.md +29 -16
  111. package/ts/00_commitinfo_data.ts +1 -1
  112. package/ts/helpers.smartconfig.ts +63 -10
  113. package/ts/helpers.workflow.ts +41 -12
  114. package/ts/mod_commit/mod.helpers.ts +12 -8
  115. package/ts/mod_config/index.ts +22 -23
  116. package/ts/mod_docker/classes.dockerpruner.ts +6 -0
  117. package/ts/mod_release/helpers.releasebranch.ts +1282 -0
  118. package/ts/mod_release/index.ts +608 -129
  119. package/ts/mod_services/classes.dockercontainer.ts +912 -96
  120. package/ts/mod_services/classes.globalregistry.ts +290 -89
  121. package/ts/mod_services/classes.globalregistrystore.ts +279 -0
  122. package/ts/mod_services/classes.interprocesslock.ts +189 -0
  123. package/ts/mod_services/classes.serviceconfiguration.ts +215 -108
  124. package/ts/mod_services/classes.servicedatamarker.ts +210 -52
  125. package/ts/mod_services/classes.servicemanager.ts +1332 -512
  126. package/ts/mod_services/classes.servicenames.ts +76 -0
  127. package/ts/mod_services/classes.servicepruner.ts +310 -52
  128. package/ts/mod_services/index.ts +89 -62
  129. package/ts/mod_standard/index.ts +1 -1
  130. package/ts/plugins.ts +4 -0
  131. package/ts_migration/001.service-selection.ts +77 -0
  132. package/ts_migration/002.runtime-config.ts +218 -0
  133. package/ts_migration/003.service-data-marker.ts +216 -0
  134. package/ts_migration/004.global-registry.ts +272 -0
  135. package/ts_migration/index.ts +83 -0
@@ -1,7 +1,8 @@
1
1
  import * as plugins from "./plugins.js";
2
- import { rename, writeFile } from "fs/promises";
2
+ import { withInterProcessLock } from "./mod_services/classes.interprocesslock.js";
3
3
 
4
4
  export const CLI_NAMESPACE = "@git.zone/cli";
5
+ const smartconfigSnapshots = new WeakMap<Record<string, any>, string | undefined>();
5
6
 
6
7
  const isPlainObject = (value: unknown): value is Record<string, any> => {
7
8
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -16,7 +17,9 @@ export const readSmartconfigFile = async (
16
17
  ): Promise<Record<string, any>> => {
17
18
  const smartconfigPath = getSmartconfigPath(cwd);
18
19
  if (!(await plugins.smartfs.file(smartconfigPath).exists())) {
19
- return {};
20
+ const data = {};
21
+ smartconfigSnapshots.set(data, undefined);
22
+ return data;
20
23
  }
21
24
 
22
25
  const content = (await plugins.smartfs
@@ -24,9 +27,16 @@ export const readSmartconfigFile = async (
24
27
  .encoding("utf8")
25
28
  .read()) as string;
26
29
  if (content.trim() === "") {
27
- return {};
30
+ const data = {};
31
+ smartconfigSnapshots.set(data, content);
32
+ return data;
33
+ }
34
+ const parsed = JSON.parse(content);
35
+ if (!isPlainObject(parsed)) {
36
+ throw new Error(".smartconfig.json must contain a JSON object at its root.");
28
37
  }
29
- return JSON.parse(content);
38
+ smartconfigSnapshots.set(parsed, content);
39
+ return parsed;
30
40
  };
31
41
 
32
42
  export const writeSmartconfigFile = async (
@@ -34,10 +44,48 @@ export const writeSmartconfigFile = async (
34
44
  cwd: string = process.cwd(),
35
45
  ): Promise<void> => {
36
46
  const smartconfigPath = getSmartconfigPath(cwd);
37
- const tempPath = `${smartconfigPath}.tmp-${Date.now()}`;
38
- const content = JSON.stringify(data, null, 2);
39
- await writeFile(tempPath, content, "utf8");
40
- await rename(tempPath, smartconfigPath);
47
+ if (!smartconfigSnapshots.has(data)) {
48
+ throw new Error("Refusing to write .smartconfig.json without a loaded file snapshot.");
49
+ }
50
+ await withInterProcessLock(
51
+ {
52
+ lockPath: `${smartconfigPath}.lock`,
53
+ description: "Project smartconfig",
54
+ },
55
+ async () => {
56
+ let currentSnapshot: string | undefined;
57
+ try {
58
+ currentSnapshot = await plugins.fs.readFile(smartconfigPath, "utf8");
59
+ } catch (error) {
60
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
61
+ throw error;
62
+ }
63
+ }
64
+ if (currentSnapshot !== smartconfigSnapshots.get(data)) {
65
+ throw new Error(
66
+ "Refusing to overwrite .smartconfig.json because it changed after loading.",
67
+ );
68
+ }
69
+
70
+ const content = `${JSON.stringify(data, null, 2)}\n`;
71
+ const tempPath = `${smartconfigPath}.tmp-${process.pid}-${plugins.crypto
72
+ .randomBytes(12)
73
+ .toString("hex")}`;
74
+ try {
75
+ await plugins.fs.writeFile(tempPath, content, {
76
+ encoding: "utf8",
77
+ flag: "wx",
78
+ });
79
+ await plugins.fs.rename(tempPath, smartconfigPath);
80
+ smartconfigSnapshots.set(data, content);
81
+ } catch (error) {
82
+ await plugins.fs.rm(tempPath, { force: true }).catch(() => {});
83
+ throw new Error("Could not persist .smartconfig.json atomically.", {
84
+ cause: error,
85
+ });
86
+ }
87
+ },
88
+ );
41
89
  };
42
90
 
43
91
  export const normalizeCliConfigPath = (configPath: string): string => {
@@ -69,10 +117,13 @@ export const getCliNamespaceConfig = (
69
117
  smartconfigData: Record<string, any>,
70
118
  ): Record<string, any> => {
71
119
  const cliConfig = smartconfigData[CLI_NAMESPACE];
120
+ if (cliConfig === undefined) {
121
+ return {};
122
+ }
72
123
  if (isPlainObject(cliConfig)) {
73
124
  return cliConfig;
74
125
  }
75
- return {};
126
+ throw new Error(`${CLI_NAMESPACE} in .smartconfig.json must be a JSON object.`);
76
127
  };
77
128
 
78
129
  export const getCliConfigValueFromData = (
@@ -121,8 +172,10 @@ export const setCliConfigValueInData = (
121
172
  ): Record<string, any> => {
122
173
  const segments = getCliConfigPathSegments(configPath);
123
174
 
124
- if (!isPlainObject(smartconfigData[CLI_NAMESPACE])) {
175
+ if (smartconfigData[CLI_NAMESPACE] === undefined) {
125
176
  smartconfigData[CLI_NAMESPACE] = {};
177
+ } else if (!isPlainObject(smartconfigData[CLI_NAMESPACE])) {
178
+ throw new Error(`${CLI_NAMESPACE} in .smartconfig.json must be a JSON object.`);
126
179
  }
127
180
 
128
181
  if (segments.length === 0) {
@@ -70,7 +70,6 @@ export interface IReleaseWorkflowConfig {
70
70
  source?: "pendingChangelog" | "manual";
71
71
  };
72
72
  preflight?: {
73
- requireCleanTree?: boolean;
74
73
  test?: boolean;
75
74
  build?: boolean;
76
75
  testCommand?: string;
@@ -101,7 +100,6 @@ export interface IResolvedReleaseWorkflow {
101
100
  confirmation: TConfirmationMode;
102
101
  plan: string[];
103
102
  targets: TReleaseTarget[];
104
- requireCleanTree: boolean;
105
103
  runTests: boolean;
106
104
  runBuild: boolean;
107
105
  testCommand: string;
@@ -202,6 +200,22 @@ export const resolveDockerTestSetting = (valueArg: unknown): boolean => {
202
200
  return valueArg;
203
201
  };
204
202
 
203
+ const resolveGitPushSetting = (
204
+ valueArg: unknown,
205
+ settingArg: "pushBranch" | "pushTags",
206
+ gitEnabledArg: boolean,
207
+ ): boolean => {
208
+ if (!gitEnabledArg) {
209
+ return valueArg === undefined ? true : valueArg === true;
210
+ }
211
+ if (valueArg !== undefined && valueArg !== true) {
212
+ throw new Error(
213
+ `release.targets.git.${settingArg} must be true when the Git target is enabled`,
214
+ );
215
+ }
216
+ return true;
217
+ };
218
+
205
219
  const isStringRecord = (valueArg: unknown): valueArg is Record<string, unknown> => {
206
220
  return typeof valueArg === "object" && valueArg !== null && !Array.isArray(valueArg);
207
221
  };
@@ -340,15 +354,19 @@ const getTargetOverride = (argvArg: any): TReleaseTarget[] | undefined => {
340
354
  return targets.length > 0 ? targets : undefined;
341
355
  };
342
356
 
357
+ const releaseTargetOrder: TReleaseTarget[] = ["git", "npm", "docker"];
358
+
343
359
  const buildReleasePlan = (options: {
344
- requireCleanTree: boolean;
360
+ mergeRequested: boolean;
345
361
  runTests: boolean;
346
362
  runBuild: boolean;
347
363
  targets: TReleaseTarget[];
348
364
  }): string[] => {
349
- const plan: string[] = [];
350
- if (options.requireCleanTree) plan.push("preflight.cleanTree");
365
+ const plan: string[] = ["preflight.branch", "preflight.cleanTree"];
351
366
  if (options.runTests) plan.push("preflight.test");
367
+ if (options.mergeRequested) {
368
+ plan.push("integration.main", "integration.pushMain");
369
+ }
352
370
  plan.push("core.version", "core.changelog", "core.commit", "core.tag");
353
371
  if (options.runBuild) plan.push("core.build");
354
372
  for (const target of options.targets) {
@@ -421,6 +439,16 @@ export const resolveReleaseWorkflow = async (argvArg: any): Promise<IResolvedRel
421
439
  const npmEnabled = npmConfig.enabled ?? npmRegistries.length > 0;
422
440
  const gitEnabled = gitConfig.enabled ?? true;
423
441
  const dockerEnabled = dockerConfig.enabled ?? false;
442
+ const pushBranch = resolveGitPushSetting(
443
+ gitConfig.pushBranch,
444
+ "pushBranch",
445
+ gitEnabled,
446
+ );
447
+ const pushTags = resolveGitPushSetting(
448
+ gitConfig.pushTags,
449
+ "pushTags",
450
+ gitEnabled,
451
+ );
424
452
 
425
453
  let confirmation = normalizeConfirmation(releaseConfig.confirmation, "prompt");
426
454
  if (argvArg.plan) {
@@ -429,14 +457,12 @@ export const resolveReleaseWorkflow = async (argvArg: any): Promise<IResolvedRel
429
457
  confirmation = "auto";
430
458
  }
431
459
 
432
- let requireCleanTree = releaseConfig.preflight?.requireCleanTree ?? true;
433
460
  let runTests = releaseConfig.preflight?.test ?? false;
434
461
  let runBuild = releaseConfig.preflight?.build ?? true;
435
462
  if (argvArg.t || argvArg.test) runTests = true;
436
463
  if (argvArg.b || argvArg.build) runBuild = true;
437
464
  if (isDisabled(argvArg, "test")) runTests = false;
438
465
  if (isDisabled(argvArg, "build")) runBuild = false;
439
- if (isDisabled(argvArg, "preflight")) requireCleanTree = false;
440
466
 
441
467
  const configuredTargets: TReleaseTarget[] = [];
442
468
  if (gitEnabled) configuredTargets.push("git");
@@ -449,13 +475,16 @@ export const resolveReleaseWorkflow = async (argvArg: any): Promise<IResolvedRel
449
475
  if (isDisabled(argvArg, "publish")) {
450
476
  targets = targets.filter((target) => target === "git");
451
477
  }
452
- targets = unique(targets);
478
+ const selectedTargets = unique(targets);
479
+ targets = releaseTargetOrder.filter((target) =>
480
+ selectedTargets.includes(target),
481
+ );
482
+ const mergeRequested = argvArg.merge === true;
453
483
 
454
484
  return {
455
485
  confirmation,
456
- plan: buildReleasePlan({ requireCleanTree, runTests, runBuild, targets }),
486
+ plan: buildReleasePlan({ mergeRequested, runTests, runBuild, targets }),
457
487
  targets,
458
- requireCleanTree,
459
488
  runTests,
460
489
  runBuild,
461
490
  testCommand: releaseConfig.preflight?.testCommand || "pnpm test",
@@ -465,8 +494,8 @@ export const resolveReleaseWorkflow = async (argvArg: any): Promise<IResolvedRel
465
494
  changelogVersionHeading: "## {{date}} - {{version}}",
466
495
  gitEnabled,
467
496
  gitRemote: gitConfig.remote || "origin",
468
- pushBranch: gitConfig.pushBranch ?? true,
469
- pushTags: gitConfig.pushTags ?? true,
497
+ pushBranch,
498
+ pushTags,
470
499
  npmEnabled,
471
500
  npmRegistries,
472
501
  npmAccessLevel: npmConfig.accessLevel || "public",
@@ -37,9 +37,9 @@ export async function detectCurrentBranch(): Promise<string> {
37
37
  * Detects the project type based on presence of package.json and/or deno.json
38
38
  * @returns The project type
39
39
  */
40
- export async function detectProjectType(): Promise<ProjectType> {
41
- const packageJsonPath = plugins.path.join(paths.cwd, 'package.json');
42
- const denoJsonPath = plugins.path.join(paths.cwd, 'deno.json');
40
+ export async function detectProjectType(cwdArg = paths.cwd): Promise<ProjectType> {
41
+ const packageJsonPath = plugins.path.join(cwdArg, 'package.json');
42
+ const denoJsonPath = plugins.path.join(cwdArg, 'deno.json');
43
43
 
44
44
  const hasPackageJson = await plugins.smartfs.file(packageJsonPath).exists();
45
45
  const hasDenoJson = await plugins.smartfs.file(denoJsonPath).exists();
@@ -96,9 +96,12 @@ export function calculateNewVersion(currentVersion: string, versionType: Version
96
96
  * @param projectType The project type to determine which file to read
97
97
  * @returns The current version string
98
98
  */
99
- export async function readCurrentVersion(projectType: ProjectType): Promise<string> {
99
+ export async function readCurrentVersion(
100
+ projectType: ProjectType,
101
+ cwdArg = paths.cwd,
102
+ ): Promise<string> {
100
103
  if (projectType === 'npm' || projectType === 'both') {
101
- const packageJsonPath = plugins.path.join(paths.cwd, 'package.json');
104
+ const packageJsonPath = plugins.path.join(cwdArg, 'package.json');
102
105
  const content = (await plugins.smartfs
103
106
  .file(packageJsonPath)
104
107
  .encoding('utf8')
@@ -110,7 +113,7 @@ export async function readCurrentVersion(projectType: ProjectType): Promise<stri
110
113
  }
111
114
  return packageJson.version;
112
115
  } else {
113
- const denoJsonPath = plugins.path.join(paths.cwd, 'deno.json');
116
+ const denoJsonPath = plugins.path.join(cwdArg, 'deno.json');
114
117
  const content = (await plugins.smartfs
115
118
  .file(denoJsonPath)
116
119
  .encoding('utf8')
@@ -148,10 +151,11 @@ export async function updateVersionFile(filePath: string, newVersion: string): P
148
151
  export async function updateProjectVersionFiles(
149
152
  projectType: ProjectType,
150
153
  newVersion: string,
154
+ cwdArg = paths.cwd,
151
155
  ): Promise<string[]> {
152
156
  const filesToUpdate: string[] = [];
153
- const packageJsonPath = plugins.path.join(paths.cwd, 'package.json');
154
- const denoJsonPath = plugins.path.join(paths.cwd, 'deno.json');
157
+ const packageJsonPath = plugins.path.join(cwdArg, 'package.json');
158
+ const denoJsonPath = plugins.path.join(cwdArg, 'deno.json');
155
159
 
156
160
  if (projectType === 'npm' || projectType === 'both') {
157
161
  await updateVersionFile(packageJsonPath, newVersion);
@@ -293,7 +293,7 @@ async function handleShow(mode: ICliMode): Promise<void> {
293
293
  const targets = release.targets || {};
294
294
  printConfigSection("Release Workflow", [
295
295
  ["confirmation", formatValue(release.confirmation)],
296
- ["require clean tree", formatValue(release.preflight?.requireCleanTree)],
296
+ ["clean tree", "required"],
297
297
  ["run tests", formatValue(release.preflight?.test)],
298
298
  ["run build", formatValue(release.preflight?.build)],
299
299
  ["test command", formatValue(release.preflight?.testCommand)],
@@ -773,12 +773,6 @@ async function handleRelease(mode: ICliMode): Promise<void> {
773
773
  choices: ["prompt", "auto", "plan"],
774
774
  default: currentRelease.confirmation || "prompt",
775
775
  });
776
- const requireCleanTree = await askValue<boolean>(interactInstance, {
777
- type: "confirm",
778
- name: "requireCleanTree",
779
- message: "Require a clean git tree before release?",
780
- default: currentRelease.preflight?.requireCleanTree ?? true,
781
- });
782
776
  const runTests = await askValue<boolean>(interactInstance, {
783
777
  type: "confirm",
784
778
  name: "runTests",
@@ -828,18 +822,8 @@ async function handleRelease(mode: ICliMode): Promise<void> {
828
822
  message: "Git remote:",
829
823
  default: currentTargets.git?.remote || "origin",
830
824
  }),
831
- pushBranch: await askValue<boolean>(interactInstance, {
832
- type: "confirm",
833
- name: "pushBranch",
834
- message: "Push release commit branch?",
835
- default: currentTargets.git?.pushBranch ?? true,
836
- }),
837
- pushTags: await askValue<boolean>(interactInstance, {
838
- type: "confirm",
839
- name: "pushTags",
840
- message: "Push release tags?",
841
- default: currentTargets.git?.pushTags ?? true,
842
- }),
825
+ pushBranch: true,
826
+ pushTags: true,
843
827
  };
844
828
  } else {
845
829
  releaseTargets.git = { ...(currentTargets.git || {}), enabled: false };
@@ -947,12 +931,13 @@ async function handleRelease(mode: ICliMode): Promise<void> {
947
931
  }
948
932
 
949
933
  setCliConfigValueInData(smartconfigData, "schemaVersion", CURRENT_GITZONE_CLI_SCHEMA_VERSION);
934
+ const currentPreflight = { ...(currentRelease.preflight || {}) };
935
+ delete currentPreflight.requireCleanTree;
950
936
  setCliConfigValueInData(smartconfigData, "release", {
951
937
  ...currentRelease,
952
938
  confirmation,
953
939
  preflight: {
954
- ...(currentRelease.preflight || {}),
955
- requireCleanTree,
940
+ ...currentPreflight,
956
941
  test: runTests,
957
942
  build: runBuild,
958
943
  testCommand: testCommand.trim(),
@@ -1175,7 +1160,7 @@ async function handleServices(mode: ICliMode): Promise<void> {
1175
1160
  const { ServiceManager } =
1176
1161
  await import("../mod_services/classes.servicemanager.js");
1177
1162
  const serviceManager = new ServiceManager();
1178
- await serviceManager.init();
1163
+ await serviceManager.init("config");
1179
1164
  await serviceManager.configureServices();
1180
1165
  }
1181
1166
 
@@ -1704,7 +1689,7 @@ async function validateReleaseConfig(
1704
1689
  if (preflight.build === true && preflight.buildCommand === "") {
1705
1690
  findings.push({
1706
1691
  level: "warn",
1707
- message: "Release build preflight has an empty command",
1692
+ message: "Release build has an empty command",
1708
1693
  fix: "Set release.preflight.buildCommand or unset it to use pnpm build.",
1709
1694
  });
1710
1695
  }
@@ -1816,6 +1801,20 @@ async function validateGitTarget(
1816
1801
  });
1817
1802
  return;
1818
1803
  }
1804
+ if (gitTarget.pushBranch !== undefined && gitTarget.pushBranch !== true) {
1805
+ findings.push({
1806
+ level: "error",
1807
+ message: "Enabled Git release targets must push main",
1808
+ fix: "Set release.targets.git.pushBranch to true or disable the Git target.",
1809
+ });
1810
+ }
1811
+ if (gitTarget.pushTags !== undefined && gitTarget.pushTags !== true) {
1812
+ findings.push({
1813
+ level: "error",
1814
+ message: "Enabled Git release targets must push release tags",
1815
+ fix: "Set release.targets.git.pushTags to true or disable the Git target.",
1816
+ });
1817
+ }
1819
1818
  findings.push({
1820
1819
  level: "ok",
1821
1820
  message: `Git release target is enabled (${gitTarget.remote || "origin"})`,
@@ -117,6 +117,12 @@ export class DockerPruner {
117
117
  if (!labelsArg) {
118
118
  return false;
119
119
  }
120
+ if (labelsArg['git.zone.operation'] === 'service-bind-operation') {
121
+ return false;
122
+ }
123
+ if (labelsArg['git.zone.service'] === 'minio') {
124
+ return false;
125
+ }
120
126
  return (
121
127
  typeof labelsArg[toolLabelKey] === 'string' &&
122
128
  !!labelsArg[toolLabelKey] &&