File size: 5,078 Bytes
a861001
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import { spawnSync } from "node:child_process";
import {
  chmodSync,
  copyFileSync,
  cpSync,
  mkdirSync,
  rmSync,
  symlinkSync,
  writeFileSync,
} from "node:fs";
import { homedir } from "node:os";
import { basename, dirname, join, resolve } from "node:path";

const executableName = process.platform === "win32" ? "rsih.exe" : "rsih";
const outputDirectory = resolve("dist");
const outputPath = join(outputDirectory, executableName);
mkdirSync(outputDirectory, { recursive: true });

const build = spawnSync(
  "bun",
  ["build", "src/cli.ts", "--compile", "--outfile", outputPath],
  {
    cwd: process.cwd(),
    stdio: "inherit",
  },
);

if (build.error?.code === "ENOENT") {
  throw new Error(
    "Bun is required to build the standalone binary. Install Bun or use the npm-linked CLI.",
  );
}

if (build.status !== 0) {
  process.exitCode = build.status ?? 1;
} else {
  if (process.platform !== "win32") chmodSync(outputPath, 0o755);
  copyFileSync(resolve("package.json"), join(outputDirectory, "package.json"));
  copyFileSync(resolve("README.md"), join(outputDirectory, "README.md"));
  // No CHANGELOG.md ships with the binary. Pi resolves the changelog next to
  // the package.json it reads, so pairing Pi's release notes with RSIH's
  // version makes every Pi release look unseen and replays "What's New" on
  // every start; a missing file reads as no entries, which is what we want.
  // Built-in Genomes. `resolveHarnessGenome` looks for `<PI_PACKAGE_DIR>/genomes`
  // after the project and user directories, so `--genome paperlab` and
  // `--genome harness-rsi` work from any cwd without an install step.
  // Clear the target first so a Genome removed from config/genomes does not
  // keep shipping from earlier builds.
  rmSync(join(outputDirectory, "genomes"), { recursive: true, force: true });
  cpSync(resolve("config", "genomes"), join(outputDirectory, "genomes"), {
    recursive: true,
  });
  cpSync(
    resolve(
      "node_modules",
      "@earendil-works",
      "pi-coding-agent",
      "docs",
    ),
    join(outputDirectory, "docs"),
    { recursive: true },
  );
  cpSync(
    resolve(
      "node_modules",
      "@earendil-works",
      "pi-coding-agent",
      "examples",
    ),
    join(outputDirectory, "examples"),
    { recursive: true },
  );
  cpSync(
    resolve(
      "node_modules",
      "@earendil-works",
      "pi-coding-agent",
      "dist",
      "modes",
      "interactive",
      "theme",
    ),
    join(outputDirectory, "theme"),
    { recursive: true },
  );
  cpSync(
    resolve(
      "node_modules",
      "@earendil-works",
      "pi-coding-agent",
      "dist",
      "modes",
      "interactive",
      "assets",
    ),
    join(outputDirectory, "assets"),
    { recursive: true },
  );
  cpSync(
    resolve(
      "node_modules",
      "@earendil-works",
      "pi-coding-agent",
      "dist",
      "core",
      "export-html",
    ),
    join(outputDirectory, "export-html"),
    { recursive: true },
  );
  console.log(`Built ${outputPath}`);

  if (process.argv.includes("--install")) install(outputPath);
}

/**
 * Install the built binary together with everything it resolves relative to
 * itself.
 *
 * The binary is not self-sufficient: `PI_PACKAGE_DIR` is the directory the
 * executable sits in, and that is where it looks for package.json (the version),
 * the built-in Genome seeds, themes and Pi's own assets. Copying just the
 * executable onto the PATH produces an rsih that reports version 0.0.0 and
 * cannot find `paperlab` or `harness-rsi` at all. So the whole payload goes to a
 * library directory and only a link goes on the PATH.
 */
function install(outputPath) {
  const binDirectory = resolve(
    process.env.RSIH_INSTALL_DIR ?? join(homedir(), ".local", "bin"),
  );
  const libDirectory = resolve(
    process.env.RSIH_LIB_DIR ?? join(homedir(), ".local", "lib", "rsih"),
  );
  const executable = basename(outputPath);

  mkdirSync(libDirectory, { recursive: true });
  cpSync(dirname(outputPath), libDirectory, { recursive: true, force: true });
  const target = join(libDirectory, executable);
  if (process.platform !== "win32") chmodSync(target, 0o755);
  console.log(`Installed payload to ${libDirectory}`);

  if (process.platform === "win32") {
    // Symlinks need elevation on Windows, so the payload directory itself goes
    // on the PATH.
    console.log(`Add ${libDirectory} to your PATH.`);
    return;
  }

  mkdirSync(binDirectory, { recursive: true });
  const link = join(binDirectory, executable);
  rmSync(link, { force: true });
  symlinkSync(target, link);
  console.log(`Linked ${link}`);

  // `gee` is `rsih --genome harness-rsi`. It cannot be a link, because the
  // binary would have no way to know which name invoked it.
  const gee = join(binDirectory, "gee");
  rmSync(gee, { force: true });
  writeFileSync(
    gee,
    `#!/bin/sh\n# Generated by rsih. Equivalent to: rsih --genome harness-rsi\nexec ${JSON.stringify(link)} --genome harness-rsi "$@"\n`,
    "utf8",
  );
  chmodSync(gee, 0o755);
  console.log(`Wrote ${gee}`);
}