@tech-leads-club/harness-toolkit 0.4.0 → 0.4.1

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.
package/bin/tlc-cli.ts CHANGED
@@ -9,18 +9,22 @@ import {
9
9
  rmSync,
10
10
  writeFileSync,
11
11
  } from "node:fs";
12
- import { delimiter, join } from "node:path";
12
+ import { join } from "node:path";
13
13
  import { coreFacade } from "../src/core/index.ts";
14
14
  import { emitJson, JSON_FLAG, takeJsonFlag, unknownFlags } from "../src/platform/cli-output.ts";
15
15
  import { linkDir, linkFile, seedConfig } from "../src/platform/links.ts";
16
16
  import {
17
+ EXECUTABLE_EXTENSIONS,
18
+ executableOnPath,
17
19
  flagsDir,
18
20
  isOnPath,
19
21
  launcherBinDir,
22
+ machineHome,
20
23
  projectConfigPath,
21
24
  projectStateDir,
22
25
  providerConfigDirs,
23
26
  runtimeHome,
27
+ sameLocation,
24
28
  } from "../src/platform/paths.ts";
25
29
  import { type Row, render, type Screen, type Section } from "../src/platform/screen.ts";
26
30
  import { createStyle, PLAIN, type Style } from "../src/platform/style.ts";
@@ -760,24 +764,16 @@ const GATE_FIELDS: Record<string, GateField> = {
760
764
  * name is first, so a POSIX `foo` is never beaten by a stray `foo.exe`
761
765
  * ([/decisions/ad-097.md](/decisions/ad-097.md)).
762
766
  */
763
- const EXECUTABLE_EXTENSIONS = ["", ".exe", ".cmd", ".bat", ".ps1"];
764
-
767
+ /**
768
+ * why this still exists beside `executableOnPath`: it also answers for a name that is already a path, which a PATH
769
+ * walk has nothing to say about. The walk itself is not repeated here
770
+ * ([/decisions/ad-101.md](/decisions/ad-101.md)).
771
+ */
765
772
  export function resolveExecutable(name: string, env: NodeJS.ProcessEnv = process.env): string | null {
766
- const candidates = (base: string): string[] => EXECUTABLE_EXTENSIONS.map((ext) => `${base}${ext}`);
767
-
768
773
  if (name.includes("/") || name.includes("\\")) {
769
- return candidates(name).find((candidate) => existsSync(candidate)) ?? null;
770
- }
771
- for (const dir of (env.PATH ?? "").split(delimiter)) {
772
- if (!dir) {
773
- continue;
774
- }
775
- const found = candidates(join(dir, name)).find((candidate) => existsSync(candidate));
776
- if (found) {
777
- return found;
778
- }
774
+ return EXECUTABLE_EXTENSIONS.map((ext) => `${name}${ext}`).find((c) => existsSync(c)) ?? null;
779
775
  }
780
- return null;
776
+ return executableOnPath(name, env);
781
777
  }
782
778
 
783
779
  /**
@@ -897,6 +893,29 @@ export function pricesHelpText(style: Style = PLAIN): string {
897
893
  return render(pricesHelpScreen(), style);
898
894
  }
899
895
 
896
+ /**
897
+ * The installed version, read from the runtime's own manifest.
898
+ *
899
+ * hazard: nothing showed it. `doctor` printed twenty rows and not one carried a version, and `update` on the npm
900
+ * route said `runtime → <path>` without naming what it was on or what it moved to
901
+ * ([/decisions/ad-101.md](/decisions/ad-101.md)).
902
+ */
903
+ export function runtimeVersion(home: string): string | null {
904
+ try {
905
+ const raw = JSON.parse(readFileSync(join(home, "package.json"), "utf8")) as { version?: unknown };
906
+ return typeof raw.version === "string" ? raw.version : null;
907
+ } catch {
908
+ return null;
909
+ }
910
+ }
911
+
912
+ /** why one line: an unchanged version is the common case and reads better as a sentence than as two rows. */
913
+ export function versionMoveLine(before: string | null, after: string | null): string {
914
+ return before !== null && after !== null && before !== after
915
+ ? `update: ${before} → ${after}`
916
+ : `update: already at ${after ?? before ?? "unknown"}`;
917
+ }
918
+
900
919
  export function resolveHarnessRoot(): string {
901
920
  const home = runtimeHome();
902
921
  try {
@@ -995,6 +1014,18 @@ export function npmRootFailureMessage(home: string): string {
995
1014
  * then reports it healthy while the command still does not exist.
996
1015
  */
997
1016
  export function launcherLines(dest: string): string[] {
1017
+ /**
1018
+ * hazard: this linked unconditionally. An install to a throwaway `TLC_INSTALL_DEST` therefore pointed the
1019
+ * machine's `tlc` at that directory — measured: a proof-of-concept install into a temp directory left the
1020
+ * operator's command running from `/tmp`, and every `tlc harness ...` after it resolved its runtime there
1021
+ * ([/decisions/ad-101.md](/decisions/ad-101.md)).
1022
+ *
1023
+ * invariant: only the machine's own runtime home owns the command on `PATH`. Installing somewhere else is a
1024
+ * deliberate act and must not reach into anybody's shell.
1025
+ */
1026
+ if (!sameLocation(dest, machineHome())) {
1027
+ return [`tlc not linked — ${dest} is not this machine's runtime home`];
1028
+ }
998
1029
  const dir = launcherBinDir();
999
1030
  const source = join(dest, "bin", "tlc");
1000
1031
  // hazard: `symlinkSync` happily creates a link to a path that is not there, and `existsSync` on a dangling link
@@ -1369,7 +1400,8 @@ function runUpdate(root: string): never {
1369
1400
  const dest = resolveHarnessRoot();
1370
1401
  const revisionBefore = runtimeRevision(dest).revision;
1371
1402
  const home = runtimeHome();
1372
- console.log(`update: runtime ${dest}`);
1403
+ const versionBefore = runtimeVersion(dest);
1404
+ console.log(`update: runtime → ${dest} (${versionBefore ?? "unknown"})`);
1373
1405
 
1374
1406
  if (!existsSync(join(dest, "bin", "tlc-exec.mjs"))) {
1375
1407
  console.error(`update: missing install at ${home}`);
@@ -1414,6 +1446,7 @@ function runUpdate(root: string): never {
1414
1446
  if ((sync.status ?? 1) !== 0) {
1415
1447
  process.exit(sync.status ?? 1);
1416
1448
  }
1449
+ console.log(versionMoveLine(versionBefore, runtimeVersion(dest)));
1417
1450
  } else if (kind === "unmanaged") {
1418
1451
  console.log(unmanagedRuntimeMessage(dest));
1419
1452
  } else {