@git.zone/cli 6.0.0 → 6.1.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.
@@ -19,13 +19,9 @@ import {
19
19
  CURRENT_GITZONE_CLI_SCHEMA_VERSION,
20
20
  migrateSmartconfigData,
21
21
  } from "../helpers.smartconfigmigrations.js";
22
- import {
23
- formatTsdockerMinimumVersion,
24
- getDeclaredTsdockerRange,
25
- getTsdockerMinimumVersion,
26
- supportsTsdockerMinimumVersion,
27
- } from "../helpers.workflow.js";
22
+ import { getDeclaredTsdockerRange } from "../helpers.workflow.js";
28
23
  import { hasInstalledProjectTsdockerBinary } from "../helpers.tsdocker.js";
24
+ import { TsdockerReleaseClient } from "../mod_release/helpers.tsdockerprotocol.js";
29
25
 
30
26
  export { ReleaseConfig, CommitConfig };
31
27
 
@@ -269,7 +265,12 @@ async function handleShow(mode: ICliMode): Promise<void> {
269
265
  ["projectType", formatValue(cliConfig.projectType)],
270
266
  ["repository", formatRepository(cliConfig.module)],
271
267
  ["description", formatValue(cliConfig.module?.description)],
272
- ["npm package", formatValue(cliConfig.module?.npmPackagename || cliConfig.module?.npmPackageName)],
268
+ [
269
+ "npm package",
270
+ formatValue(
271
+ cliConfig.module?.npmPackagename || cliConfig.module?.npmPackageName,
272
+ ),
273
+ ],
273
274
  ["license", formatValue(cliConfig.module?.license)],
274
275
  ["keywords", formatList(cliConfig.module?.keywords)],
275
276
  ]);
@@ -309,12 +310,31 @@ async function handleShow(mode: ICliMode): Promise<void> {
309
310
  const rawAssets = cliConfig.assets || {};
310
311
  const assets = rawAssets.enabled === false ? {} : rawAssets;
311
312
  printConfigSection("Managed Assets", [
312
- ["enabled", formatValue(rawAssets.enabled === false ? false : rawAssets.kind ? true : undefined)],
313
+ [
314
+ "enabled",
315
+ formatValue(
316
+ rawAssets.enabled === false ? false : rawAssets.kind ? true : undefined,
317
+ ),
318
+ ],
313
319
  ["kind", formatValue(assets.kind)],
314
320
  ["cli name", formatValue(assets.cliName)],
315
- ["installer", formatValue(assets.installer?.enabled === false ? false : assets.kind ? true : undefined)],
321
+ [
322
+ "installer",
323
+ formatValue(
324
+ assets.installer?.enabled === false
325
+ ? false
326
+ : assets.kind
327
+ ? true
328
+ : undefined,
329
+ ),
330
+ ],
316
331
  ["npm wrapper", formatValue(assets.npmWrapper?.enabled)],
317
- ["release workflow", formatValue(assets.releaseWorkflow?.enabled ?? assets.releaseAssets?.enabled)],
332
+ [
333
+ "release workflow",
334
+ formatValue(
335
+ assets.releaseWorkflow?.enabled ?? assets.releaseAssets?.enabled,
336
+ ),
337
+ ],
318
338
  ]);
319
339
 
320
340
  console.log("Run `gitzone config doctor` to validate this configuration.");
@@ -379,7 +399,10 @@ async function handleRemove(
379
399
  const registries = config.getRegistries();
380
400
 
381
401
  if (registries.length === 0) {
382
- plugins.logger.log("warn", "No npm target registries configured to remove.");
402
+ plugins.logger.log(
403
+ "warn",
404
+ "No npm target registries configured to remove.",
405
+ );
383
406
  return;
384
407
  }
385
408
 
@@ -636,7 +659,9 @@ function showCommitHelp(): void {
636
659
 
637
660
  async function handleProject(mode: ICliMode): Promise<void> {
638
661
  if (!mode.interactive) {
639
- throw new Error("Project configuration requires interactive mode. Use `gitzone config set` for automation.");
662
+ throw new Error(
663
+ "Project configuration requires interactive mode. Use `gitzone config set` for automation.",
664
+ );
640
665
  }
641
666
 
642
667
  const smartconfigData = await readSmartconfigFile();
@@ -680,7 +705,11 @@ async function handleProject(mode: ICliMode): Promise<void> {
680
705
  type: "input",
681
706
  name: "npmPackagename",
682
707
  message: "npm package name:",
683
- default: moduleConfig.npmPackagename || moduleConfig.npmPackageName || packageJson.name || "",
708
+ default:
709
+ moduleConfig.npmPackagename ||
710
+ moduleConfig.npmPackageName ||
711
+ packageJson.name ||
712
+ "",
684
713
  });
685
714
  const license = await askValue<string>(interactInstance, {
686
715
  type: "input",
@@ -699,7 +728,11 @@ async function handleProject(mode: ICliMode): Promise<void> {
699
728
  : "",
700
729
  });
701
730
 
702
- setCliConfigValueInData(smartconfigData, "schemaVersion", CURRENT_GITZONE_CLI_SCHEMA_VERSION);
731
+ setCliConfigValueInData(
732
+ smartconfigData,
733
+ "schemaVersion",
734
+ CURRENT_GITZONE_CLI_SCHEMA_VERSION,
735
+ );
703
736
  setCliConfigValueInData(smartconfigData, "projectType", projectType);
704
737
  setCliConfigValueInData(smartconfigData, "module", {
705
738
  ...moduleConfig,
@@ -718,7 +751,9 @@ async function handleProject(mode: ICliMode): Promise<void> {
718
751
 
719
752
  async function handleCli(mode: ICliMode): Promise<void> {
720
753
  if (!mode.interactive) {
721
- throw new Error("CLI behavior configuration requires interactive mode. Use `gitzone config set` for automation.");
754
+ throw new Error(
755
+ "CLI behavior configuration requires interactive mode. Use `gitzone config set` for automation.",
756
+ );
722
757
  }
723
758
 
724
759
  const smartconfigData = await readSmartconfigFile();
@@ -745,7 +780,11 @@ async function handleCli(mode: ICliMode): Promise<void> {
745
780
  default: cliConfig.checkUpdates ?? true,
746
781
  });
747
782
 
748
- setCliConfigValueInData(smartconfigData, "schemaVersion", CURRENT_GITZONE_CLI_SCHEMA_VERSION);
783
+ setCliConfigValueInData(
784
+ smartconfigData,
785
+ "schemaVersion",
786
+ CURRENT_GITZONE_CLI_SCHEMA_VERSION,
787
+ );
749
788
  setCliConfigValueInData(smartconfigData, "cli", {
750
789
  output,
751
790
  interactive,
@@ -758,11 +797,14 @@ async function handleCli(mode: ICliMode): Promise<void> {
758
797
 
759
798
  async function handleRelease(mode: ICliMode): Promise<void> {
760
799
  if (!mode.interactive) {
761
- throw new Error("Release configuration requires interactive mode. Use `gitzone config set` for automation.");
800
+ throw new Error(
801
+ "Release configuration requires interactive mode. Use `gitzone config set` for automation.",
802
+ );
762
803
  }
763
804
 
764
805
  const smartconfigData = await readSmartconfigFile();
765
- const currentRelease = getCliConfigValueFromData(smartconfigData, "release") || {};
806
+ const currentRelease =
807
+ getCliConfigValueFromData(smartconfigData, "release") || {};
766
808
  const currentTargets = currentRelease.targets || {};
767
809
  const interactInstance = new plugins.smartinteract.SmartInteract();
768
810
 
@@ -865,13 +907,15 @@ async function handleRelease(mode: ICliMode): Promise<void> {
865
907
  const registry = await askValue<string>(interactInstance, {
866
908
  type: "input",
867
909
  name: "dockerRegistry",
868
- message: "Single tsdocker registry target (empty means all configured registries):",
910
+ message:
911
+ "Single tsdocker registry target (empty means all configured registries):",
869
912
  default: currentTargets.docker?.registry || "",
870
913
  });
871
914
  const buildRegistries = await askValue<string>(interactInstance, {
872
915
  type: "input",
873
916
  name: "dockerBuildRegistries",
874
- message: "Registries to authenticate before Docker builds (comma-separated, empty means all):",
917
+ message:
918
+ "Registries to authenticate before Docker builds (comma-separated, empty means all):",
875
919
  default: Array.isArray(currentTargets.docker?.buildRegistries)
876
920
  ? currentTargets.docker.buildRegistries.join(", ")
877
921
  : "",
@@ -879,7 +923,8 @@ async function handleRelease(mode: ICliMode): Promise<void> {
879
923
  const patterns = await askValue<string>(interactInstance, {
880
924
  type: "input",
881
925
  name: "dockerPatterns",
882
- message: "tsdocker Dockerfile patterns (comma-separated, empty means all):",
926
+ message:
927
+ "tsdocker Dockerfile patterns (comma-separated, empty means all):",
883
928
  default: Array.isArray(currentTargets.docker?.patterns)
884
929
  ? currentTargets.docker.patterns.join(", ")
885
930
  : "",
@@ -908,12 +953,6 @@ async function handleRelease(mode: ICliMode): Promise<void> {
908
953
  message: "Docker context for tsdocker (empty for default):",
909
954
  default: currentTargets.docker?.context || "",
910
955
  });
911
- const noBuild = await askValue<boolean>(interactInstance, {
912
- type: "confirm",
913
- name: "dockerNoBuild",
914
- message: "Skip tsdocker build and only push existing local registry images?",
915
- default: currentTargets.docker?.noBuild ?? false,
916
- });
917
956
  releaseTargets.docker = {
918
957
  enabled: true,
919
958
  engine: "tsdocker",
@@ -924,13 +963,16 @@ async function handleRelease(mode: ICliMode): Promise<void> {
924
963
  cached,
925
964
  parallel: parseDockerParallel(parallel),
926
965
  context: context.trim() || undefined,
927
- noBuild,
928
966
  };
929
967
  } else {
930
968
  releaseTargets.docker = { enabled: false, engine: "tsdocker" };
931
969
  }
932
970
 
933
- setCliConfigValueInData(smartconfigData, "schemaVersion", CURRENT_GITZONE_CLI_SCHEMA_VERSION);
971
+ setCliConfigValueInData(
972
+ smartconfigData,
973
+ "schemaVersion",
974
+ CURRENT_GITZONE_CLI_SCHEMA_VERSION,
975
+ );
934
976
  const currentPreflight = { ...(currentRelease.preflight || {}) };
935
977
  delete currentPreflight.requireCleanTree;
936
978
  setCliConfigValueInData(smartconfigData, "release", {
@@ -959,7 +1001,8 @@ async function handleFix(argvArg: any, mode: ICliMode): Promise<void> {
959
1001
  if (mode.json) {
960
1002
  printJson({
961
1003
  ok: false,
962
- error: "JSON output is not supported for `gitzone config fix`. Use `gitzone config doctor --json` for machine-readable diagnostics.",
1004
+ error:
1005
+ "JSON output is not supported for `gitzone config fix`. Use `gitzone config doctor --json` for machine-readable diagnostics.",
963
1006
  });
964
1007
  process.exitCode = 1;
965
1008
  return;
@@ -980,12 +1023,15 @@ async function handleFix(argvArg: any, mode: ICliMode): Promise<void> {
980
1023
 
981
1024
  if (!mode.yes) {
982
1025
  if (!mode.interactive) {
983
- throw new Error("Config fix requires an interactive terminal or `-y` to run non-interactively.");
1026
+ throw new Error(
1027
+ "Config fix requires an interactive terminal or `-y` to run non-interactively.",
1028
+ );
984
1029
  }
985
- const confirmed = await plugins.smartinteract.SmartInteract.getCliConfirmation(
986
- `Run configuration fixes for .smartconfig.json? (${counts.error} error, ${counts.warn} warning)`,
987
- true,
988
- );
1030
+ const confirmed =
1031
+ await plugins.smartinteract.SmartInteract.getCliConfirmation(
1032
+ `Run configuration fixes for .smartconfig.json? (${counts.error} error, ${counts.warn} warning)`,
1033
+ true,
1034
+ );
989
1035
  if (!confirmed) {
990
1036
  plugins.logger.log("info", "Config fix cancelled.");
991
1037
  return;
@@ -996,7 +1042,12 @@ async function handleFix(argvArg: any, mode: ICliMode): Promise<void> {
996
1042
  if (appliedKnownFixes) {
997
1043
  findings = await collectDoctorFindings();
998
1044
  counts = countDoctorFindings(findings);
999
- if (counts.error === 0 && counts.warn === 0 && !extraInstructions && !force) {
1045
+ if (
1046
+ counts.error === 0 &&
1047
+ counts.warn === 0 &&
1048
+ !extraInstructions &&
1049
+ !force
1050
+ ) {
1000
1051
  printDoctorResult(findings, mode);
1001
1052
  return;
1002
1053
  }
@@ -1032,7 +1083,9 @@ async function handleFix(argvArg: any, mode: ICliMode): Promise<void> {
1032
1083
  stdio: "inherit",
1033
1084
  });
1034
1085
  } catch (error) {
1035
- throw new Error(`Failed to run opencode: ${error instanceof Error ? error.message : String(error)}`);
1086
+ throw new Error(
1087
+ `Failed to run opencode: ${error instanceof Error ? error.message : String(error)}`,
1088
+ );
1036
1089
  }
1037
1090
 
1038
1091
  if (result.exitCode !== 0) {
@@ -1076,7 +1129,9 @@ async function applyKnownConfigFixes(mode: ICliMode): Promise<boolean> {
1076
1129
  async function collectDoctorFindings(): Promise<IDoctorFinding[]> {
1077
1130
  const findings: IDoctorFinding[] = [];
1078
1131
  const smartconfigPath = getSmartconfigPath();
1079
- const smartconfigExists = await plugins.smartfs.file(smartconfigPath).exists();
1132
+ const smartconfigExists = await plugins.smartfs
1133
+ .file(smartconfigPath)
1134
+ .exists();
1080
1135
 
1081
1136
  if (!smartconfigExists) {
1082
1137
  findings.push({
@@ -1108,7 +1163,13 @@ async function collectDoctorFindings(): Promise<IDoctorFinding[]> {
1108
1163
  });
1109
1164
  }
1110
1165
 
1111
- for (const legacyNamespace of ["gitzone", "tsdoc", "npmdocker", "npmci", "szci"]) {
1166
+ for (const legacyNamespace of [
1167
+ "gitzone",
1168
+ "tsdoc",
1169
+ "npmdocker",
1170
+ "npmci",
1171
+ "szci",
1172
+ ]) {
1112
1173
  if (smartconfigData[legacyNamespace]) {
1113
1174
  findings.push({
1114
1175
  level: "warn",
@@ -1119,7 +1180,10 @@ async function collectDoctorFindings(): Promise<IDoctorFinding[]> {
1119
1180
  }
1120
1181
 
1121
1182
  if (cliConfig.schemaVersion === CURRENT_GITZONE_CLI_SCHEMA_VERSION) {
1122
- findings.push({ level: "ok", message: `Schema version is current (${CURRENT_GITZONE_CLI_SCHEMA_VERSION})` });
1183
+ findings.push({
1184
+ level: "ok",
1185
+ message: `Schema version is current (${CURRENT_GITZONE_CLI_SCHEMA_VERSION})`,
1186
+ });
1123
1187
  } else {
1124
1188
  findings.push({
1125
1189
  level: "warn",
@@ -1129,7 +1193,10 @@ async function collectDoctorFindings(): Promise<IDoctorFinding[]> {
1129
1193
  }
1130
1194
 
1131
1195
  if (["npm", "service", "wcc", "website"].includes(cliConfig.projectType)) {
1132
- findings.push({ level: "ok", message: `Project type is ${cliConfig.projectType}` });
1196
+ findings.push({
1197
+ level: "ok",
1198
+ message: `Project type is ${cliConfig.projectType}`,
1199
+ });
1133
1200
  } else {
1134
1201
  findings.push({
1135
1202
  level: "warn",
@@ -1286,7 +1353,10 @@ async function handleMigrate(
1286
1353
  `Migrated .smartconfig.json from schema v${result.fromVersion} to v${result.toVersion}`,
1287
1354
  );
1288
1355
  } else {
1289
- plugins.logger.log("info", `.smartconfig.json already at schema v${result.toVersion}`);
1356
+ plugins.logger.log(
1357
+ "info",
1358
+ `.smartconfig.json already at schema v${result.toVersion}`,
1359
+ );
1290
1360
  }
1291
1361
  }
1292
1362
 
@@ -1389,7 +1459,8 @@ function formatTarget(enabled: unknown, targetConfig: any): string {
1389
1459
  if (Array.isArray(targetConfig.registries)) {
1390
1460
  details.push(`registries=${targetConfig.registries.length}`);
1391
1461
  }
1392
- if (targetConfig.accessLevel) details.push(`access=${targetConfig.accessLevel}`);
1462
+ if (targetConfig.accessLevel)
1463
+ details.push(`access=${targetConfig.accessLevel}`);
1393
1464
  if (targetConfig.engine) details.push(`engine=${targetConfig.engine}`);
1394
1465
  if (Array.isArray(targetConfig.patterns)) {
1395
1466
  details.push(`patterns=${targetConfig.patterns.length}`);
@@ -1397,14 +1468,10 @@ function formatTarget(enabled: unknown, targetConfig: any): string {
1397
1468
  if (targetConfig.cached) details.push("cached=true");
1398
1469
  if (targetConfig.parallel) details.push(`parallel=${targetConfig.parallel}`);
1399
1470
  if (targetConfig.context) details.push(`context=${targetConfig.context}`);
1400
- if (targetConfig.noBuild) details.push("noBuild=true");
1401
1471
  return details.length > 0 ? `${state} (${details.join(", ")})` : state;
1402
1472
  }
1403
1473
 
1404
- async function askValue<T>(
1405
- interactInstance: any,
1406
- options: any,
1407
- ): Promise<T> {
1474
+ async function askValue<T>(interactInstance: any, options: any): Promise<T> {
1408
1475
  const response = await interactInstance.askQuestion(options);
1409
1476
  return response.value as T;
1410
1477
  }
@@ -1452,7 +1519,10 @@ function formatDockerParallel(value: unknown): string {
1452
1519
 
1453
1520
  function parseDockerParallel(value: string): boolean | number {
1454
1521
  const normalizedValue = value.trim().toLowerCase();
1455
- if (!normalizedValue || ["false", "no", "off", "0"].includes(normalizedValue)) {
1522
+ if (
1523
+ !normalizedValue ||
1524
+ ["false", "no", "off", "0"].includes(normalizedValue)
1525
+ ) {
1456
1526
  return false;
1457
1527
  }
1458
1528
  if (["true", "yes", "on"].includes(normalizedValue)) {
@@ -1467,19 +1537,29 @@ function parseDockerParallel(value: string): boolean | number {
1467
1537
 
1468
1538
  function normalizeRegistryUrl(url: string): string {
1469
1539
  let normalizedUrl = url.trim();
1470
- if (!normalizedUrl.startsWith("http://") && !normalizedUrl.startsWith("https://")) {
1540
+ if (
1541
+ !normalizedUrl.startsWith("http://") &&
1542
+ !normalizedUrl.startsWith("https://")
1543
+ ) {
1471
1544
  normalizedUrl = `https://${normalizedUrl}`;
1472
1545
  }
1473
- return normalizedUrl.endsWith("/") ? normalizedUrl.slice(0, -1) : normalizedUrl;
1546
+ return normalizedUrl.endsWith("/")
1547
+ ? normalizedUrl.slice(0, -1)
1548
+ : normalizedUrl;
1474
1549
  }
1475
1550
 
1476
- function getDefaultEnabledTargets(currentTargets: Record<string, any>): string[] {
1551
+ function getDefaultEnabledTargets(
1552
+ currentTargets: Record<string, any>,
1553
+ ): string[] {
1477
1554
  const enabledTargets: string[] = [];
1478
1555
  const npmRegistries = currentTargets.npm?.registries;
1479
1556
  if (currentTargets.git?.enabled ?? true) {
1480
1557
  enabledTargets.push("git");
1481
1558
  }
1482
- if (currentTargets.npm?.enabled ?? (Array.isArray(npmRegistries) && npmRegistries.length > 0)) {
1559
+ if (
1560
+ currentTargets.npm?.enabled ??
1561
+ (Array.isArray(npmRegistries) && npmRegistries.length > 0)
1562
+ ) {
1483
1563
  enabledTargets.push("npm");
1484
1564
  }
1485
1565
  if (currentTargets.docker?.enabled ?? false) {
@@ -1518,7 +1598,8 @@ function buildConfigFixPrompt(
1518
1598
  `- Use schemaVersion ${CURRENT_GITZONE_CLI_SCHEMA_VERSION} for ` +
1519
1599
  "`@git.zone/cli`.",
1520
1600
  "- Use target-based release config: `release.targets.git`, `release.targets.npm`, and `release.targets.docker`.",
1521
- "- Docker release targets must use `release.targets.docker.engine = \"tsdocker\"`; Docker registries belong under `@git.zone/tsdocker`.",
1601
+ '- Docker release targets must use `release.targets.docker.engine = "tsdocker"`; Docker registries belong under `@git.zone/tsdocker`.',
1602
+ "- Remove `release.targets.docker.noBuild`; journaled Docker qualification always builds.",
1522
1603
  "- Keep npm registries only at `@git.zone/cli.release.targets.npm.registries`.",
1523
1604
  "- Do not add runtime legacy compatibility code. If legacy config exists, migrate it explicitly.",
1524
1605
  "- Do not commit, release, install dependencies, or modify unrelated files.",
@@ -1558,7 +1639,9 @@ function printDoctorResult(findings: IDoctorFinding[], mode: ICliMode): void {
1558
1639
  }
1559
1640
  }
1560
1641
  console.log("");
1561
- console.log(`Summary: ${counts.ok} ok, ${counts.warn} warning, ${counts.error} error`);
1642
+ console.log(
1643
+ `Summary: ${counts.ok} ok, ${counts.warn} warning, ${counts.error} error`,
1644
+ );
1562
1645
  console.log("");
1563
1646
  }
1564
1647
 
@@ -1572,8 +1655,14 @@ function validateCommitConfig(
1572
1655
  findings: IDoctorFinding[],
1573
1656
  ): void {
1574
1657
  const confirmation = commitConfig.confirmation;
1575
- if (confirmation === undefined || validConfirmationModes.includes(confirmation)) {
1576
- findings.push({ level: "ok", message: "Commit confirmation mode is valid" });
1658
+ if (
1659
+ confirmation === undefined ||
1660
+ validConfirmationModes.includes(confirmation)
1661
+ ) {
1662
+ findings.push({
1663
+ level: "ok",
1664
+ message: "Commit confirmation mode is valid",
1665
+ });
1577
1666
  } else {
1578
1667
  findings.push({
1579
1668
  level: "warn",
@@ -1584,7 +1673,10 @@ function validateCommitConfig(
1584
1673
 
1585
1674
  const steps = commitConfig.steps;
1586
1675
  if (steps === undefined) {
1587
- findings.push({ level: "ok", message: "Commit workflow uses default steps" });
1676
+ findings.push({
1677
+ level: "ok",
1678
+ message: "Commit workflow uses default steps",
1679
+ });
1588
1680
  return;
1589
1681
  }
1590
1682
  if (!Array.isArray(steps) || steps.length === 0) {
@@ -1660,8 +1752,14 @@ async function validateReleaseConfig(
1660
1752
  }
1661
1753
 
1662
1754
  const confirmation = releaseConfig.confirmation;
1663
- if (confirmation === undefined || validConfirmationModes.includes(confirmation)) {
1664
- findings.push({ level: "ok", message: "Release confirmation mode is valid" });
1755
+ if (
1756
+ confirmation === undefined ||
1757
+ validConfirmationModes.includes(confirmation)
1758
+ ) {
1759
+ findings.push({
1760
+ level: "ok",
1761
+ message: "Release confirmation mode is valid",
1762
+ });
1665
1763
  } else {
1666
1764
  findings.push({
1667
1765
  level: "warn",
@@ -1670,7 +1768,12 @@ async function validateReleaseConfig(
1670
1768
  });
1671
1769
  }
1672
1770
 
1673
- if (releaseConfig.registries || releaseConfig.accessLevel || releaseConfig.steps || releaseConfig.changelog) {
1771
+ if (
1772
+ releaseConfig.registries ||
1773
+ releaseConfig.accessLevel ||
1774
+ releaseConfig.steps ||
1775
+ releaseConfig.changelog
1776
+ ) {
1674
1777
  findings.push({
1675
1778
  level: "warn",
1676
1779
  message: "Legacy release keys are present outside release.targets",
@@ -1706,7 +1809,10 @@ async function validateAssetsConfig(
1706
1809
  findings: IDoctorFinding[],
1707
1810
  ): Promise<void> {
1708
1811
  if (rawAssetsConfig === undefined) {
1709
- findings.push({ level: "ok", message: "Managed assets are not configured" });
1812
+ findings.push({
1813
+ level: "ok",
1814
+ message: "Managed assets are not configured",
1815
+ });
1710
1816
  return;
1711
1817
  }
1712
1818
  if (!isPlainObject(rawAssetsConfig)) {
@@ -1738,15 +1844,21 @@ async function validateAssetsConfig(
1738
1844
  fix: "Set @git.zone/cli.assets.cliName.",
1739
1845
  });
1740
1846
  } else {
1741
- findings.push({ level: "ok", message: `Managed denoBinaryCli assets target ${cliName}` });
1847
+ findings.push({
1848
+ level: "ok",
1849
+ message: `Managed denoBinaryCli assets target ${cliName}`,
1850
+ });
1742
1851
  }
1743
1852
 
1744
1853
  const tsdenoConfig = smartconfigData["@git.zone/tsdeno"];
1745
- const binaryTargets = isPlainObject(rawAssetsConfig.binary) && Array.isArray(rawAssetsConfig.binary.targets)
1746
- ? rawAssetsConfig.binary.targets
1747
- : isPlainObject(tsdenoConfig) && Array.isArray(tsdenoConfig.compileTargets)
1748
- ? tsdenoConfig.compileTargets
1749
- : [];
1854
+ const binaryTargets =
1855
+ isPlainObject(rawAssetsConfig.binary) &&
1856
+ Array.isArray(rawAssetsConfig.binary.targets)
1857
+ ? rawAssetsConfig.binary.targets
1858
+ : isPlainObject(tsdenoConfig) &&
1859
+ Array.isArray(tsdenoConfig.compileTargets)
1860
+ ? tsdenoConfig.compileTargets
1861
+ : [];
1750
1862
  if (binaryTargets.length === 0) {
1751
1863
  findings.push({
1752
1864
  level: "error",
@@ -1754,22 +1866,37 @@ async function validateAssetsConfig(
1754
1866
  fix: "Add @git.zone/tsdeno.compileTargets or @git.zone/cli.assets.binary.targets.",
1755
1867
  });
1756
1868
  } else {
1757
- findings.push({ level: "ok", message: `Managed assets have ${binaryTargets.length} binary target(s)` });
1869
+ findings.push({
1870
+ level: "ok",
1871
+ message: `Managed assets have ${binaryTargets.length} binary target(s)`,
1872
+ });
1758
1873
  }
1759
1874
 
1760
1875
  const npmWrapper = rawAssetsConfig.npmWrapper;
1761
- if (isPlainObject(npmWrapper) && npmWrapper.enabled === true && typeof cliName === "string" && cliName.trim()) {
1876
+ if (
1877
+ isPlainObject(npmWrapper) &&
1878
+ npmWrapper.enabled === true &&
1879
+ typeof cliName === "string" &&
1880
+ cliName.trim()
1881
+ ) {
1762
1882
  const packageJson = await readPackageJson();
1763
1883
  const binFile = npmWrapper.binFile || `bin/${cliName}-wrapper.js`;
1764
- const postinstallFile = npmWrapper.postinstallFile || "scripts/install-binary.js";
1765
- if (!isPlainObject(packageJson.bin) || packageJson.bin[cliName] !== `./${binFile}`) {
1884
+ const postinstallFile =
1885
+ npmWrapper.postinstallFile || "scripts/install-binary.js";
1886
+ if (
1887
+ !isPlainObject(packageJson.bin) ||
1888
+ packageJson.bin[cliName] !== `./${binFile}`
1889
+ ) {
1766
1890
  findings.push({
1767
1891
  level: "warn",
1768
1892
  message: `package.json bin entry for ${cliName} is missing or outdated`,
1769
1893
  fix: "Run `gitzone format --only packagejson --write`.",
1770
1894
  });
1771
1895
  } else {
1772
- findings.push({ level: "ok", message: `package.json bin entry points to ${binFile}` });
1896
+ findings.push({
1897
+ level: "ok",
1898
+ message: `package.json bin entry points to ${binFile}`,
1899
+ });
1773
1900
  }
1774
1901
  if (packageJson.scripts?.postinstall !== `node ${postinstallFile}`) {
1775
1902
  findings.push({
@@ -1778,7 +1905,10 @@ async function validateAssetsConfig(
1778
1905
  fix: "Run `gitzone format --only packagejson --write`.",
1779
1906
  });
1780
1907
  } else {
1781
- findings.push({ level: "ok", message: `package.json postinstall runs ${postinstallFile}` });
1908
+ findings.push({
1909
+ level: "ok",
1910
+ message: `package.json postinstall runs ${postinstallFile}`,
1911
+ });
1782
1912
  }
1783
1913
  }
1784
1914
  }
@@ -1825,7 +1955,9 @@ async function validateNpmTarget(
1825
1955
  npmTarget: Record<string, any>,
1826
1956
  findings: IDoctorFinding[],
1827
1957
  ): Promise<void> {
1828
- const registries = Array.isArray(npmTarget.registries) ? npmTarget.registries : [];
1958
+ const registries = Array.isArray(npmTarget.registries)
1959
+ ? npmTarget.registries
1960
+ : [];
1829
1961
  const enabled = npmTarget.enabled ?? registries.length > 0;
1830
1962
  if (!enabled) {
1831
1963
  findings.push({ level: "ok", message: "npm release target is disabled" });
@@ -1841,14 +1973,20 @@ async function validateNpmTarget(
1841
1973
  return;
1842
1974
  }
1843
1975
 
1844
- if (npmTarget.accessLevel && !["public", "private"].includes(npmTarget.accessLevel)) {
1976
+ if (
1977
+ npmTarget.accessLevel &&
1978
+ !["public", "private"].includes(npmTarget.accessLevel)
1979
+ ) {
1845
1980
  findings.push({
1846
1981
  level: "error",
1847
1982
  message: `Invalid npm access level: ${npmTarget.accessLevel}`,
1848
1983
  fix: "Use public or private.",
1849
1984
  });
1850
1985
  }
1851
- if (npmTarget.alreadyPublished && !["success", "error"].includes(npmTarget.alreadyPublished)) {
1986
+ if (
1987
+ npmTarget.alreadyPublished &&
1988
+ !["success", "error"].includes(npmTarget.alreadyPublished)
1989
+ ) {
1852
1990
  findings.push({
1853
1991
  level: "error",
1854
1992
  message: `Invalid npm alreadyPublished behavior: ${npmTarget.alreadyPublished}`,
@@ -1901,7 +2039,9 @@ async function validateNpmRegistry(
1901
2039
  }
1902
2040
 
1903
2041
  try {
1904
- const result = await smartNetwork.checkEndpoint(normalizedRegistry, { timeout: 5000 });
2042
+ const result = await smartNetwork.checkEndpoint(normalizedRegistry, {
2043
+ timeout: 5000,
2044
+ });
1905
2045
  if (result.status >= 200 && result.status < 500) {
1906
2046
  findings.push({
1907
2047
  level: "ok",
@@ -1963,6 +2103,17 @@ async function validateDockerTarget(
1963
2103
  smartconfigData: Record<string, any>,
1964
2104
  findings: IDoctorFinding[],
1965
2105
  ): Promise<void> {
2106
+ if (
2107
+ dockerTarget.noBuild === true ||
2108
+ (dockerTarget.noBuild !== undefined &&
2109
+ typeof dockerTarget.noBuild !== "boolean")
2110
+ ) {
2111
+ findings.push({
2112
+ level: "error",
2113
+ message: "Docker release target cannot bypass qualification builds",
2114
+ fix: "Remove release.targets.docker.noBuild or set it to false.",
2115
+ });
2116
+ }
1966
2117
  if ("images" in dockerTarget) {
1967
2118
  findings.push({
1968
2119
  level: "error",
@@ -1973,7 +2124,10 @@ async function validateDockerTarget(
1973
2124
 
1974
2125
  const enabled = dockerTarget.enabled ?? false;
1975
2126
  if (!enabled) {
1976
- findings.push({ level: "ok", message: "Docker release target is disabled" });
2127
+ findings.push({
2128
+ level: "ok",
2129
+ message: "Docker release target is disabled",
2130
+ });
1977
2131
  return;
1978
2132
  }
1979
2133
 
@@ -1985,7 +2139,10 @@ async function validateDockerTarget(
1985
2139
  });
1986
2140
  }
1987
2141
 
1988
- if (dockerTarget.patterns !== undefined && !Array.isArray(dockerTarget.patterns)) {
2142
+ if (
2143
+ dockerTarget.patterns !== undefined &&
2144
+ !Array.isArray(dockerTarget.patterns)
2145
+ ) {
1989
2146
  findings.push({
1990
2147
  level: "error",
1991
2148
  message: "Docker release target patterns must be an array",
@@ -1994,10 +2151,10 @@ async function validateDockerTarget(
1994
2151
  }
1995
2152
 
1996
2153
  if (
1997
- dockerTarget.registry !== undefined
1998
- && (typeof dockerTarget.registry !== "string"
1999
- || !dockerTarget.registry.trim()
2000
- || /^https?:\/\//.test(dockerTarget.registry))
2154
+ dockerTarget.registry !== undefined &&
2155
+ (typeof dockerTarget.registry !== "string" ||
2156
+ !dockerTarget.registry.trim() ||
2157
+ /^https?:\/\//.test(dockerTarget.registry))
2001
2158
  ) {
2002
2159
  findings.push({
2003
2160
  level: "error",
@@ -2007,14 +2164,16 @@ async function validateDockerTarget(
2007
2164
  }
2008
2165
 
2009
2166
  if (
2010
- dockerTarget.buildRegistries !== undefined
2011
- && (!Array.isArray(dockerTarget.buildRegistries)
2012
- || dockerTarget.buildRegistries.some((registryArg: unknown) =>
2013
- typeof registryArg !== "string"
2014
- || !registryArg.trim()
2015
- || /^https?:\/\//.test(registryArg)
2016
- )
2017
- || new Set(dockerTarget.buildRegistries).size !== dockerTarget.buildRegistries.length)
2167
+ dockerTarget.buildRegistries !== undefined &&
2168
+ (!Array.isArray(dockerTarget.buildRegistries) ||
2169
+ dockerTarget.buildRegistries.some(
2170
+ (registryArg: unknown) =>
2171
+ typeof registryArg !== "string" ||
2172
+ !registryArg.trim() ||
2173
+ /^https?:\/\//.test(registryArg),
2174
+ ) ||
2175
+ new Set(dockerTarget.buildRegistries).size !==
2176
+ dockerTarget.buildRegistries.length)
2018
2177
  ) {
2019
2178
  findings.push({
2020
2179
  level: "error",
@@ -2023,7 +2182,10 @@ async function validateDockerTarget(
2023
2182
  });
2024
2183
  }
2025
2184
 
2026
- if (dockerTarget.test !== undefined && typeof dockerTarget.test !== "boolean") {
2185
+ if (
2186
+ dockerTarget.test !== undefined &&
2187
+ typeof dockerTarget.test !== "boolean"
2188
+ ) {
2027
2189
  findings.push({
2028
2190
  level: "error",
2029
2191
  message: `Invalid tsdocker image test setting: ${formatValue(dockerTarget.test)}`,
@@ -2043,7 +2205,8 @@ async function validateDockerTarget(
2043
2205
  if (!isPlainObject(tsdockerConfig)) {
2044
2206
  findings.push({
2045
2207
  level: "error",
2046
- message: "Docker release target is enabled but @git.zone/tsdocker config is missing",
2208
+ message:
2209
+ "Docker release target is enabled but @git.zone/tsdocker config is missing",
2047
2210
  fix: "Add @git.zone/tsdocker.registries and optional registryRepoMap/platforms config.",
2048
2211
  });
2049
2212
  } else {
@@ -2073,10 +2236,12 @@ function isPlainObject(value: unknown): value is Record<string, any> {
2073
2236
  }
2074
2237
 
2075
2238
  function isValidDockerParallel(value: unknown): boolean {
2076
- return value === undefined ||
2239
+ return (
2240
+ value === undefined ||
2077
2241
  value === false ||
2078
2242
  value === true ||
2079
- (typeof value === "number" && Number.isFinite(value) && value > 0);
2243
+ (typeof value === "number" && Number.isFinite(value) && value > 0)
2244
+ );
2080
2245
  }
2081
2246
 
2082
2247
  function validateTsdockerProjectConfig(
@@ -2138,13 +2303,16 @@ function validateTsdockerProjectConfig(
2138
2303
 
2139
2304
  async function validateTsdockerCommand(
2140
2305
  findings: IDoctorFinding[],
2141
- buildRegistriesArg: unknown,
2142
- imageTestsRequestedArg: boolean,
2306
+ _buildRegistriesArg: unknown,
2307
+ _imageTestsRequestedArg: boolean,
2143
2308
  ): Promise<void> {
2144
2309
  let packageJson: unknown;
2145
2310
  try {
2146
2311
  packageJson = JSON.parse(
2147
- await plugins.fs.readFile(plugins.path.join(process.cwd(), "package.json"), "utf8"),
2312
+ await plugins.fs.readFile(
2313
+ plugins.path.join(process.cwd(), "package.json"),
2314
+ "utf8",
2315
+ ),
2148
2316
  );
2149
2317
  } catch {
2150
2318
  packageJson = undefined;
@@ -2152,7 +2320,8 @@ async function validateTsdockerCommand(
2152
2320
  if (!getDeclaredTsdockerRange(packageJson)) {
2153
2321
  findings.push({
2154
2322
  level: "error",
2155
- message: "Docker release target requires project-local @git.zone/tsdocker",
2323
+ message:
2324
+ "Docker release target requires project-local @git.zone/tsdocker",
2156
2325
  fix: "Run pnpm add --save-dev @git.zone/tsdocker.",
2157
2326
  });
2158
2327
  }
@@ -2168,43 +2337,22 @@ async function validateTsdockerCommand(
2168
2337
  sourceFilePaths: [],
2169
2338
  });
2170
2339
  try {
2171
- const result = await smartshellInstance.execSpawn(
2172
- "pnpm",
2173
- ["exec", "tsdocker", "--version"],
2174
- { silent: true, timeout: 8000 },
2175
- );
2176
- if (result.exitCode === 0) {
2177
- findings.push({ level: "ok", message: "tsdocker command is available" });
2178
- const minimumTsdockerVersion = getTsdockerMinimumVersion(
2179
- Array.isArray(buildRegistriesArg) && buildRegistriesArg.length > 0,
2180
- imageTestsRequestedArg,
2181
- );
2182
- if (
2183
- minimumTsdockerVersion
2184
- && !supportsTsdockerMinimumVersion(
2185
- result.combinedOutput,
2186
- minimumTsdockerVersion,
2187
- )
2188
- ) {
2189
- const minimumVersion = formatTsdockerMinimumVersion(minimumTsdockerVersion);
2190
- findings.push({
2191
- level: "error",
2192
- message: `Docker release options require @git.zone/tsdocker >= ${minimumVersion}`,
2193
- fix: `Upgrade the project's @git.zone/tsdocker dev dependency to at least ${minimumVersion}.`,
2194
- });
2195
- }
2196
- } else {
2197
- findings.push({
2198
- level: "error",
2199
- message: "Project-local tsdocker command is not available",
2200
- fix: "Install @git.zone/tsdocker as a project dev dependency.",
2201
- });
2202
- }
2340
+ await new TsdockerReleaseClient(
2341
+ smartshellInstance,
2342
+ process.cwd(),
2343
+ ).assertCapabilities();
2344
+ findings.push({
2345
+ level: "ok",
2346
+ message: "Project-local tSDocker release protocol v1 is available",
2347
+ });
2203
2348
  } catch (error) {
2204
2349
  findings.push({
2205
2350
  level: "error",
2206
- message: "Could not execute project-local tsdocker",
2207
- fix: error instanceof Error ? error.message : String(error),
2351
+ message: "Project-local tSDocker release protocol is unavailable",
2352
+ fix:
2353
+ error instanceof Error
2354
+ ? error.message
2355
+ : "Install @git.zone/tsdocker >= 3.5.1.",
2208
2356
  });
2209
2357
  }
2210
2358
  }
@@ -2230,7 +2378,8 @@ async function validateDetectedProjectType(
2230
2378
  if (hasPackageJson && validProjectTypes.includes(cliConfig.projectType)) {
2231
2379
  findings.push({
2232
2380
  level: "ok",
2233
- message: "Detected project files match configured npm-compatible project type",
2381
+ message:
2382
+ "Detected project files match configured npm-compatible project type",
2234
2383
  });
2235
2384
  return;
2236
2385
  }
@@ -2238,7 +2387,8 @@ async function validateDetectedProjectType(
2238
2387
  if (hasDenoJson && !hasPackageJson) {
2239
2388
  findings.push({
2240
2389
  level: "warn",
2241
- message: "Detected a Deno-only project, but guided config supports npm-compatible project types",
2390
+ message:
2391
+ "Detected a Deno-only project, but guided config supports npm-compatible project types",
2242
2392
  fix: "Use `gitzone config set projectType npm|service|wcc|website` only for npm-compatible projects.",
2243
2393
  });
2244
2394
  }
@@ -2257,9 +2407,15 @@ export function showHelp(mode?: ICliMode): void {
2257
2407
  name: "show",
2258
2408
  description: "Display current @git.zone/cli configuration",
2259
2409
  },
2260
- { name: "project", description: "Configure project basics interactively" },
2410
+ {
2411
+ name: "project",
2412
+ description: "Configure project basics interactively",
2413
+ },
2261
2414
  { name: "cli", description: "Configure CLI behavior interactively" },
2262
- { name: "release", description: "Configure release workflow interactively" },
2415
+ {
2416
+ name: "release",
2417
+ description: "Configure release workflow interactively",
2418
+ },
2263
2419
  { name: "doctor", description: "Validate .smartconfig.json" },
2264
2420
  {
2265
2421
  name: "fix [instructions]",
@@ -2269,8 +2425,14 @@ export function showHelp(mode?: ICliMode): void {
2269
2425
  { name: "get <path>", description: "Read a single config value" },
2270
2426
  { name: "set <path> <value>", description: "Write a config value" },
2271
2427
  { name: "unset <path>", description: "Delete a config value" },
2272
- { name: "add [url]", description: "Add an npm release target registry" },
2273
- { name: "remove [url]", description: "Remove an npm release target registry" },
2428
+ {
2429
+ name: "add [url]",
2430
+ description: "Add an npm release target registry",
2431
+ },
2432
+ {
2433
+ name: "remove [url]",
2434
+ description: "Remove an npm release target registry",
2435
+ },
2274
2436
  { name: "clear", description: "Clear npm release target registries" },
2275
2437
  {
2276
2438
  name: "access [public|private]",
@@ -2306,11 +2468,19 @@ export function showHelp(mode?: ICliMode): void {
2306
2468
  console.log(
2307
2469
  " show Display current @git.zone/cli configuration",
2308
2470
  );
2309
- console.log(" project Configure project basics interactively");
2310
- console.log(" cli Configure CLI behavior interactively");
2311
- console.log(" release Configure release workflow interactively");
2471
+ console.log(
2472
+ " project Configure project basics interactively",
2473
+ );
2474
+ console.log(
2475
+ " cli Configure CLI behavior interactively",
2476
+ );
2477
+ console.log(
2478
+ " release Configure release workflow interactively",
2479
+ );
2312
2480
  console.log(" doctor Validate .smartconfig.json");
2313
- console.log(" fix [instructions] Repair .smartconfig.json via opencode");
2481
+ console.log(
2482
+ " fix [instructions] Repair .smartconfig.json via opencode",
2483
+ );
2314
2484
  console.log(
2315
2485
  " with -y, opencode runs with --dangerously-skip-permissions ⚠️",
2316
2486
  );
@@ -2318,13 +2488,17 @@ export function showHelp(mode?: ICliMode): void {
2318
2488
  console.log(" set <path> <value> Write a config value");
2319
2489
  console.log(" unset <path> Delete a config value");
2320
2490
  console.log(" add [url] Add an npm target registry URL");
2321
- console.log(" remove [url] Remove an npm target registry URL");
2491
+ console.log(
2492
+ " remove [url] Remove an npm target registry URL",
2493
+ );
2322
2494
  console.log(" clear Clear npm target registries");
2323
2495
  console.log(
2324
2496
  " access [public|private] Set npm target access level for publishing",
2325
2497
  );
2326
2498
  console.log(" commit [setting] [value] Configure commit options");
2327
- console.log(" migrate [version] Run version-targeted smartconfig migrations");
2499
+ console.log(
2500
+ " migrate [version] Run version-targeted smartconfig migrations",
2501
+ );
2328
2502
  console.log(
2329
2503
  " services Configure which services are enabled",
2330
2504
  );