@companyhelm/cli 0.4.0 → 0.4.2

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 (121) hide show
  1. package/dist/cli_io_interface.d.ts +10 -0
  2. package/dist/cli_io_interface.js +2 -0
  3. package/dist/cli_io_interface.js.map +1 -0
  4. package/dist/companyhelm_cli.d.ts +20 -0
  5. package/dist/companyhelm_cli.js +73 -0
  6. package/dist/companyhelm_cli.js.map +1 -0
  7. package/dist/console_io.d.ts +9 -0
  8. package/dist/console_io.js +13 -0
  9. package/dist/console_io.js.map +1 -0
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.js +4 -0
  12. package/dist/index.js.map +1 -0
  13. package/package.json +20 -28
  14. package/LICENSE +0 -21
  15. package/README.md +0 -66
  16. package/dist/cli.d.ts +0 -2
  17. package/dist/cli.js +0 -39
  18. package/dist/commands/dependencies.d.ts +0 -28
  19. package/dist/commands/dependencies.js +0 -292
  20. package/dist/commands/down.d.ts +0 -3
  21. package/dist/commands/down.js +0 -5
  22. package/dist/commands/interactive.d.ts +0 -7
  23. package/dist/commands/interactive.js +0 -25
  24. package/dist/commands/logs.d.ts +0 -3
  25. package/dist/commands/logs.js +0 -14
  26. package/dist/commands/register-commands.d.ts +0 -3
  27. package/dist/commands/register-commands.js +0 -23
  28. package/dist/commands/reset.d.ts +0 -7
  29. package/dist/commands/reset.js +0 -51
  30. package/dist/commands/set-image-version.d.ts +0 -31
  31. package/dist/commands/set-image-version.js +0 -87
  32. package/dist/commands/setup-github-app.d.ts +0 -13
  33. package/dist/commands/setup-github-app.js +0 -233
  34. package/dist/commands/startup-preferences.d.ts +0 -3
  35. package/dist/commands/startup-preferences.js +0 -39
  36. package/dist/commands/status.d.ts +0 -3
  37. package/dist/commands/status.js +0 -7
  38. package/dist/commands/up.d.ts +0 -3
  39. package/dist/commands/up.js +0 -39
  40. package/dist/core/bootstrap/DeploymentBootstrapper.d.ts +0 -15
  41. package/dist/core/bootstrap/DeploymentBootstrapper.js +0 -118
  42. package/dist/core/bootstrap/SeedSqlRenderer.d.ts +0 -12
  43. package/dist/core/bootstrap/SeedSqlRenderer.js +0 -44
  44. package/dist/core/config/ApiEnvFileWriter.d.ts +0 -8
  45. package/dist/core/config/ApiEnvFileWriter.js +0 -33
  46. package/dist/core/config/GithubAppConfig.d.ts +0 -6
  47. package/dist/core/config/GithubAppConfig.js +0 -26
  48. package/dist/core/config/GithubAppConfigStore.d.ts +0 -11
  49. package/dist/core/config/GithubAppConfigStore.js +0 -57
  50. package/dist/core/docker/ComposeTemplateRenderer.d.ts +0 -22
  51. package/dist/core/docker/ComposeTemplateRenderer.js +0 -66
  52. package/dist/core/docker/DockerStackManager.d.ts +0 -29
  53. package/dist/core/docker/DockerStackManager.js +0 -163
  54. package/dist/core/local/ApiLocalService.d.ts +0 -22
  55. package/dist/core/local/ApiLocalService.js +0 -65
  56. package/dist/core/local/LocalRepoSourceResolver.d.ts +0 -24
  57. package/dist/core/local/LocalRepoSourceResolver.js +0 -33
  58. package/dist/core/local/LocalServiceProcessManager.d.ts +0 -18
  59. package/dist/core/local/LocalServiceProcessManager.js +0 -83
  60. package/dist/core/local/WebLocalService.d.ts +0 -23
  61. package/dist/core/local/WebLocalService.js +0 -101
  62. package/dist/core/logs/LogsService.d.ts +0 -6
  63. package/dist/core/logs/LogsService.js +0 -14
  64. package/dist/core/process/CommandRunner.d.ts +0 -4
  65. package/dist/core/process/CommandRunner.js +0 -51
  66. package/dist/core/runner/RunnerSupervisor.d.ts +0 -29
  67. package/dist/core/runner/RunnerSupervisor.js +0 -90
  68. package/dist/core/runner/runner-bootstrap.d.ts +0 -2
  69. package/dist/core/runner/runner-bootstrap.js +0 -48
  70. package/dist/core/runtime/CliPackageMetadata.d.ts +0 -3
  71. package/dist/core/runtime/CliPackageMetadata.js +0 -8
  72. package/dist/core/runtime/CliRoot.d.ts +0 -2
  73. package/dist/core/runtime/CliRoot.js +0 -23
  74. package/dist/core/runtime/ImageCatalog.d.ts +0 -8
  75. package/dist/core/runtime/ImageCatalog.js +0 -12
  76. package/dist/core/runtime/LocalConfigStore.d.ts +0 -16
  77. package/dist/core/runtime/LocalConfigStore.js +0 -50
  78. package/dist/core/runtime/ManagedImages.d.ts +0 -10
  79. package/dist/core/runtime/ManagedImages.js +0 -27
  80. package/dist/core/runtime/PortAllocator.d.ts +0 -9
  81. package/dist/core/runtime/PortAllocator.js +0 -20
  82. package/dist/core/runtime/PublicImageTagRegistry.d.ts +0 -17
  83. package/dist/core/runtime/PublicImageTagRegistry.js +0 -168
  84. package/dist/core/runtime/RepoConfigStore.d.ts +0 -16
  85. package/dist/core/runtime/RepoConfigStore.js +0 -63
  86. package/dist/core/runtime/RuntimePaths.d.ts +0 -16
  87. package/dist/core/runtime/RuntimePaths.js +0 -43
  88. package/dist/core/runtime/RuntimeState.d.ts +0 -38
  89. package/dist/core/runtime/RuntimeState.js +0 -1
  90. package/dist/core/runtime/RuntimeStateStore.d.ts +0 -11
  91. package/dist/core/runtime/RuntimeStateStore.js +0 -81
  92. package/dist/core/runtime/Secrets.d.ts +0 -10
  93. package/dist/core/runtime/Secrets.js +0 -26
  94. package/dist/core/runtime/VersionCatalog.d.ts +0 -10
  95. package/dist/core/runtime/VersionCatalog.js +0 -21
  96. package/dist/core/services/ManagedServiceNames.d.ts +0 -5
  97. package/dist/core/services/ManagedServiceNames.js +0 -12
  98. package/dist/core/status/StatusService.d.ts +0 -19
  99. package/dist/core/status/StatusService.js +0 -30
  100. package/dist/core/ui/TerminalRenderer.d.ts +0 -17
  101. package/dist/core/ui/TerminalRenderer.js +0 -71
  102. package/dist/preflight/ApiPortPreflightCheck.d.ts +0 -6
  103. package/dist/preflight/ApiPortPreflightCheck.js +0 -10
  104. package/dist/preflight/DockerInstalledPreflightCheck.d.ts +0 -7
  105. package/dist/preflight/DockerInstalledPreflightCheck.js +0 -15
  106. package/dist/preflight/PortAvailabilityPreflightCheck.d.ts +0 -7
  107. package/dist/preflight/PortAvailabilityPreflightCheck.js +0 -31
  108. package/dist/preflight/PostgresPortPreflightCheck.d.ts +0 -6
  109. package/dist/preflight/PostgresPortPreflightCheck.js +0 -10
  110. package/dist/preflight/PreflightCheck.d.ts +0 -3
  111. package/dist/preflight/PreflightCheck.js +0 -1
  112. package/dist/preflight/WebPortPreflightCheck.d.ts +0 -6
  113. package/dist/preflight/WebPortPreflightCheck.js +0 -10
  114. package/dist/preflight/runStartupPreflightChecks.d.ts +0 -18
  115. package/dist/preflight/runStartupPreflightChecks.js +0 -42
  116. package/dist/templates/api.env.tpl +0 -3
  117. package/dist/templates/docker-compose.yaml.tpl +0 -24
  118. package/dist/templates/seed.sql.tpl +0 -76
  119. package/src/templates/api.env.tpl +0 -3
  120. package/src/templates/docker-compose.yaml.tpl +0 -24
  121. package/src/templates/seed.sql.tpl +0 -76
@@ -1,233 +0,0 @@
1
- import * as clack from "@clack/prompts";
2
- import chalk from "chalk";
3
- import { spawn } from "node:child_process";
4
- import { createInterface } from "node:readline";
5
- import { Writable } from "node:stream";
6
- import { unwrapPromptResult, requireInteractiveTerminal, InteractiveCommandCancelledError, hasInteractiveTerminal } from "./interactive.js";
7
- import { GithubAppConfigStore } from "../core/config/GithubAppConfigStore.js";
8
- import { normalizeGithubAppConfig } from "../core/config/GithubAppConfig.js";
9
- const GITHUB_NEW_APP_URL = "https://github.com/settings/apps/new";
10
- function createHiddenTerminalOutput() {
11
- return new Writable({
12
- write(_chunk, _encoding, callback) {
13
- callback();
14
- },
15
- });
16
- }
17
- function getBrowserOpenCommand(url) {
18
- if (process.platform === "darwin") {
19
- return { command: "open", args: [url] };
20
- }
21
- if (process.platform === "win32") {
22
- return { command: "cmd", args: ["/c", "start", "", url] };
23
- }
24
- return { command: "xdg-open", args: [url] };
25
- }
26
- async function openUrlInBrowser(url) {
27
- const { command, args } = getBrowserOpenCommand(url);
28
- await new Promise((resolve, reject) => {
29
- const child = spawn(command, args, {
30
- detached: true,
31
- stdio: "ignore",
32
- });
33
- child.once("error", reject);
34
- child.once("spawn", () => {
35
- child.unref();
36
- resolve();
37
- });
38
- });
39
- }
40
- function writeGithubAppCreationGuide(output) {
41
- const divider = chalk.dim("=".repeat(68));
42
- const label = (text) => chalk.bold(chalk.cyan(text));
43
- const value = (text) => chalk.white(text);
44
- output.write([
45
- divider,
46
- chalk.bold("Create a GitHub App before continuing"),
47
- chalk.dim("CompanyHelm needs a local GitHub App before startup can continue."),
48
- chalk.dim("Agents use that app to access and work on your repositories from isolated container workspaces."),
49
- chalk.dim("That lets each agent clone repos and operate safely without reusing your host checkout."),
50
- "",
51
- `${label("New app page")} ${chalk.underline(chalk.green(GITHUB_NEW_APP_URL))}`,
52
- "",
53
- chalk.bold("Required form values"),
54
- `${label("GitHub App name:")} ${value("Any name you like, e.g. companyhelm <your deployment name>")}`,
55
- `${label("Public link:")} ${value("Paste your public link here")}`,
56
- `${label("Setup URL:")} ${value("http://localhost:4173/github/install")}`,
57
- `${label("Redirect on update:")} ${value("Checked")}`,
58
- `${label("Webhook:")} ${value("Leave it inactive and uncheck the webhook option")}`,
59
- `${label("Permissions:")} ${value("Grant at least Contents so CompanyHelm can download repositories; add any additional permissions your agents need")}`,
60
- `${label("Where can this GitHub App be installed?")} ${value("Select Any account")}`,
61
- "",
62
- chalk.bold("Bring back after creation"),
63
- value("App URL, Client ID, and private key PEM"),
64
- "",
65
- divider,
66
- "",
67
- ].join("\n"));
68
- }
69
- async function promptTextValue(message, input, output, validate) {
70
- const value = await clack.text({
71
- message,
72
- input,
73
- output,
74
- validate,
75
- });
76
- return String(unwrapPromptResult(value, "GitHub App setup cancelled.", output)).trim();
77
- }
78
- export async function readPemFromTerminal(input = process.stdin, output = process.stdout) {
79
- requireInteractiveTerminal(input, output, "setup-github-app requires an interactive terminal.");
80
- output.write([
81
- "Generate a private key.",
82
- "Once downloaded copy the contents (e.g. cat ~/Downloads/{your-app-name}{date}.pem | pbcopy) and paste it here.",
83
- "",
84
- ].join("\n"));
85
- const readline = createInterface({
86
- input,
87
- output: createHiddenTerminalOutput(),
88
- terminal: false,
89
- });
90
- return await new Promise((resolve, reject) => {
91
- const lines = [];
92
- let settled = false;
93
- const finish = (callback) => {
94
- if (settled) {
95
- return;
96
- }
97
- settled = true;
98
- readline.close();
99
- callback();
100
- };
101
- readline.on("line", (line) => {
102
- lines.push(line);
103
- if (/^-----END [A-Z0-9 ]+-----$/.test(line.trim())) {
104
- finish(() => {
105
- try {
106
- const normalized = normalizeGithubAppConfig({
107
- appUrl: "https://github.com/apps/placeholder",
108
- appClientId: "placeholder",
109
- appPrivateKeyPem: `${lines.join("\n")}\n`,
110
- });
111
- resolve(normalized.appPrivateKeyPem);
112
- }
113
- catch (error) {
114
- reject(error);
115
- }
116
- });
117
- }
118
- });
119
- readline.on("close", () => {
120
- if (settled) {
121
- return;
122
- }
123
- settled = true;
124
- reject(new Error("GitHub App PEM input ended before a PEM end marker was received."));
125
- });
126
- readline.on("SIGINT", () => {
127
- finish(() => {
128
- reject(new InteractiveCommandCancelledError("GitHub App setup cancelled."));
129
- });
130
- });
131
- readline.on("error", (error) => {
132
- finish(() => {
133
- reject(error);
134
- });
135
- });
136
- });
137
- }
138
- export async function promptGithubAppConfig(input = process.stdin, output = process.stdout, openBrowser = openUrlInBrowser) {
139
- requireInteractiveTerminal(input, output, "setup-github-app requires an interactive terminal.");
140
- writeGithubAppCreationGuide(output);
141
- const shouldOpenBrowser = unwrapPromptResult(await clack.confirm({
142
- message: `Open ${GITHUB_NEW_APP_URL} in your browser now?`,
143
- active: "Yes",
144
- inactive: "No",
145
- initialValue: true,
146
- input,
147
- output,
148
- }), "GitHub App setup cancelled.", output);
149
- if (shouldOpenBrowser) {
150
- try {
151
- await openBrowser(GITHUB_NEW_APP_URL);
152
- output.write(`Opened ${GITHUB_NEW_APP_URL} in your browser.\n\n`);
153
- }
154
- catch {
155
- output.write([
156
- "Could not open a browser automatically.",
157
- `Open this URL manually: ${GITHUB_NEW_APP_URL}`,
158
- "",
159
- ].join("\n"));
160
- }
161
- }
162
- const appUrl = await promptTextValue("GitHub App URL", input, output, (value) => {
163
- try {
164
- new URL(String(value || "").trim());
165
- return undefined;
166
- }
167
- catch {
168
- return "Enter a valid GitHub App URL.";
169
- }
170
- });
171
- const appClientId = await promptTextValue("GitHub App Client ID (not the App ID)", input, output, (value) => (String(value || "").trim() ? undefined : "Client ID is required."));
172
- const appPrivateKeyPem = await readPemFromTerminal(input, output);
173
- return normalizeGithubAppConfig({
174
- appUrl,
175
- appClientId,
176
- appPrivateKeyPem,
177
- });
178
- }
179
- export async function ensureGithubAppConfig(store = new GithubAppConfigStore(), input = process.stdin, output = process.stdout, options = {}) {
180
- const existingConfig = store.load();
181
- if (existingConfig) {
182
- return existingConfig;
183
- }
184
- if (!hasInteractiveTerminal(input, output)) {
185
- return null;
186
- }
187
- const workspaceMode = options.workspaceMode ?? "dedicated";
188
- clack.intro("CompanyHelm GitHub auth", { output });
189
- clack.note(workspaceMode === "dedicated"
190
- ? [
191
- "No machine GitHub App config was found.",
192
- "Dedicated workspaces cannot access files from your host system.",
193
- "GitHub auth is recommended so agents can clone and work in your repositories.",
194
- ].join("\n")
195
- : [
196
- "No machine GitHub App config was found.",
197
- "GitHub auth is optional in current working directory mode.",
198
- "Set it up now if you want agents to access GitHub directly from this deployment.",
199
- ].join("\n"), "Optional setup", { output });
200
- const shouldSetup = unwrapPromptResult(await clack.confirm({
201
- message: "Set up GitHub auth now?",
202
- active: "Set it up",
203
- inactive: "Skip for now",
204
- initialValue: false,
205
- input,
206
- output,
207
- }), "GitHub App setup cancelled.", output);
208
- if (!shouldSetup) {
209
- clack.outro("Skipping GitHub App setup. Continuing startup without GitHub access.", { output });
210
- return null;
211
- }
212
- const config = await promptGithubAppConfig(input, output);
213
- const spinner = clack.spinner({ output });
214
- spinner.start("Saving machine GitHub App config");
215
- const configPath = store.save(config);
216
- spinner.stop(`Saved GitHub App config to ${configPath}`);
217
- clack.outro("GitHub App setup complete. Continuing startup.", { output });
218
- return config;
219
- }
220
- export function registerSetupGithubAppCommand(program, store = new GithubAppConfigStore()) {
221
- program
222
- .command("setup-github-app")
223
- .description("Save machine-wide GitHub App config for local deploys.")
224
- .action(async () => {
225
- clack.intro("CompanyHelm GitHub App setup");
226
- const config = await promptGithubAppConfig();
227
- const spinner = clack.spinner();
228
- spinner.start("Saving machine GitHub App config");
229
- const configPath = store.save(config);
230
- spinner.stop(`Saved GitHub App config to ${configPath}`);
231
- clack.outro(`Saved GitHub App config to ${configPath}.`);
232
- });
233
- }
@@ -1,3 +0,0 @@
1
- import type { Readable, Writable } from "node:stream";
2
- import { LocalConfigStore, type AgentWorkspaceMode } from "../core/runtime/LocalConfigStore.js";
3
- export declare function ensureAgentWorkspaceMode(store?: LocalConfigStore, input?: Readable, output?: Writable): Promise<AgentWorkspaceMode>;
@@ -1,39 +0,0 @@
1
- import * as clack from "@clack/prompts";
2
- import { unwrapPromptResult, hasInteractiveTerminal } from "./interactive.js";
3
- import { LocalConfigStore } from "../core/runtime/LocalConfigStore.js";
4
- const DEFAULT_WORKSPACE_MODE = "current-working-directory";
5
- export async function ensureAgentWorkspaceMode(store = new LocalConfigStore(), input = process.stdin, output = process.stdout) {
6
- const existingMode = store.load().agentWorkspaceMode;
7
- if (existingMode) {
8
- return existingMode;
9
- }
10
- if (!hasInteractiveTerminal(input, output)) {
11
- return DEFAULT_WORKSPACE_MODE;
12
- }
13
- clack.note([
14
- "Choose where agent threads should run.",
15
- "Dedicated workspaces keep agents isolated from your host filesystem.",
16
- "In dedicated mode agents will not have access to files on your system, so GitHub auth is recommended if you want them to clone your repositories.",
17
- "Current working directory mode mounts this checkout directly into agent threads.",
18
- ].join("\n"), "Agent workspace", { output });
19
- const selectedMode = unwrapPromptResult(await clack.select({
20
- message: "Where should agents operate?",
21
- options: [
22
- {
23
- value: "current-working-directory",
24
- label: "Current working directory",
25
- hint: "recommended: agents work directly in this checkout"
26
- },
27
- {
28
- value: "dedicated",
29
- label: "Dedicated workspaces directory",
30
- hint: "isolated thread workspaces"
31
- }
32
- ],
33
- initialValue: DEFAULT_WORKSPACE_MODE,
34
- input,
35
- output
36
- }), "Workspace selection cancelled.", output);
37
- store.setAgentWorkspaceMode(selectedMode);
38
- return selectedMode;
39
- }
@@ -1,3 +0,0 @@
1
- import type { Command } from "commander";
2
- import type { CommandDependencies } from "./dependencies.js";
3
- export declare function registerStatusCommand(program: Command, dependencies: CommandDependencies): void;
@@ -1,7 +0,0 @@
1
- import { TerminalRenderer } from "../core/ui/TerminalRenderer.js";
2
- export function registerStatusCommand(program, dependencies) {
3
- program.command("status").description("Show deployment status.").action(async () => {
4
- const renderer = new TerminalRenderer(process.stdout.isTTY);
5
- process.stdout.write(`${renderer.renderStatus(await dependencies.status())}\n`);
6
- });
7
- }
@@ -1,3 +0,0 @@
1
- import type { Command } from "commander";
2
- import type { CommandDependencies } from "./dependencies.js";
3
- export declare function registerUpCommand(program: Command, dependencies: CommandDependencies): void;
@@ -1,39 +0,0 @@
1
- const LOG_LEVELS = new Set(["debug", "info", "warn", "error"]);
2
- export function registerUpCommand(program, dependencies) {
3
- program
4
- .command("up")
5
- .description("Start or reconcile the local deployment.")
6
- .option("--log-level <level>", "Set log level for api, companyhelm-web, and runner.", "info")
7
- .option("--use-host-docker-runtime", "Run thread containers against the host Docker runtime instead of DinD sidecars.")
8
- .option("--api-repo-path [path]", "Start the API from a local repo path. Defaults to ../companyhelm-api when provided without a value.")
9
- .option("--web-repo-path [path]", "Start companyhelm-web from a local repo path. Defaults to ../companyhelm-web when provided without a value.")
10
- .action(async (options) => {
11
- const logLevel = String(options.logLevel || "").trim().toLowerCase();
12
- if (!LOG_LEVELS.has(logLevel)) {
13
- throw new Error(`Unsupported log level "${options.logLevel}". Expected one of: debug, info, warn, error.`);
14
- }
15
- const upOptions = {
16
- logLevel: logLevel,
17
- useHostDockerRuntime: Boolean(options.useHostDockerRuntime)
18
- };
19
- const apiRepoPath = normalizeLocalRepoOption(options.apiRepoPath);
20
- const webRepoPath = normalizeLocalRepoOption(options.webRepoPath);
21
- if (apiRepoPath !== undefined) {
22
- upOptions.apiRepoPath = apiRepoPath;
23
- }
24
- if (webRepoPath !== undefined) {
25
- upOptions.webRepoPath = webRepoPath;
26
- }
27
- await dependencies.up(upOptions);
28
- });
29
- }
30
- function normalizeLocalRepoOption(option) {
31
- if (option === true) {
32
- return true;
33
- }
34
- if (typeof option === "string") {
35
- const trimmed = option.trim();
36
- return trimmed.length > 0 ? trimmed : true;
37
- }
38
- return undefined;
39
- }
@@ -1,15 +0,0 @@
1
- import type { LogLevel } from "../../commands/dependencies.js";
2
- import type { GithubAppConfig } from "../config/GithubAppConfig.js";
3
- import type { RuntimeState } from "../runtime/RuntimeState.js";
4
- export declare class DeploymentBootstrapper {
5
- private readonly renderer;
6
- writeSeedSql(root: string, state: RuntimeState, passwordHash: string, passwordSalt: string): string;
7
- writeApiConfig(root: string, state: RuntimeState, logLevel?: LogLevel, options?: {
8
- databaseHost?: string;
9
- appPort?: number;
10
- runnerGrpcPort?: number;
11
- githubAppConfig?: GithubAppConfig | null;
12
- }): string;
13
- writeFrontendConfig(root: string, state: RuntimeState): string;
14
- private indentBlock;
15
- }
@@ -1,118 +0,0 @@
1
- import fs from "node:fs";
2
- import { RuntimePaths } from "../runtime/RuntimePaths.js";
3
- import { SeedSqlRenderer } from "./SeedSqlRenderer.js";
4
- export class DeploymentBootstrapper {
5
- renderer = new SeedSqlRenderer();
6
- writeSeedSql(root, state, passwordHash, passwordSalt) {
7
- const runtimePaths = new RuntimePaths(root);
8
- const outputPath = runtimePaths.seedFilePath();
9
- const sql = this.renderer.render({
10
- companyId: state.company.id,
11
- companyName: state.company.name,
12
- username: state.auth.username,
13
- passwordHash,
14
- passwordSalt,
15
- runnerName: state.runner.name,
16
- runnerSecret: state.runner.secret
17
- });
18
- fs.writeFileSync(outputPath, sql, "utf8");
19
- return outputPath;
20
- }
21
- writeApiConfig(root, state, logLevel = "info", options = {}) {
22
- const runtimePaths = new RuntimePaths(root);
23
- const outputPath = runtimePaths.apiConfigPath();
24
- const appPort = options.appPort ?? state.ports.apiHttp;
25
- const runnerGrpcPort = options.runnerGrpcPort ?? state.ports.runnerGrpc;
26
- const databaseHost = options.databaseHost ?? "postgres";
27
- const githubConfigLines = options.githubAppConfig
28
- ? [
29
- ' app_client_id: "${GITHUB_APP_CLIENT_ID}"',
30
- ' app_private_key_pem: "${GITHUB_APP_PRIVATE_KEY_PEM}"',
31
- ' app_link: "${GITHUB_APP_URL}"'
32
- ]
33
- : [
34
- ' app_client_id: "companyhelm-local-github-disabled"',
35
- ' app_private_key_pem: "companyhelm-local-github-disabled"',
36
- ' app_link: "https://github.com/apps/companyhelm-local-disabled"'
37
- ];
38
- const yaml = [
39
- "app:",
40
- ' host: "0.0.0.0"',
41
- ` port: ${appPort}`,
42
- ' graphqlEndpoint: "/graphql"',
43
- " graphiql: true",
44
- " grpc:",
45
- ' host: "0.0.0.0"',
46
- ` port: ${runnerGrpcPort}`,
47
- " heartbeat:",
48
- " intervalMs: 20000",
49
- " jitterMs: 10000",
50
- " workers:",
51
- " agentHeartbeats:",
52
- " intervalSeconds: 60",
53
- " jitterSeconds: 60",
54
- " batchSize: 10",
55
- " leaseSeconds: 120",
56
- " taskWorker:",
57
- " intervalSeconds: 60",
58
- " jitterSeconds: 60",
59
- " batchSize: 10",
60
- " leaseSeconds: 120",
61
- "database:",
62
- ' name: "companyhelm"',
63
- ` host: "${databaseHost}"`,
64
- " port: 5432",
65
- " roles:",
66
- " app_runtime:",
67
- ' username: "app-runtime"',
68
- ' password: "companyhelm-local-app-runtime-role-password"',
69
- " admin:",
70
- ' username: "postgres"',
71
- ' password: "postgres"',
72
- "github:",
73
- ...githubConfigLines,
74
- 'authProvider: "companyhelm"',
75
- "auth:",
76
- " companyhelm:",
77
- " jwt_private_key_pem: |-",
78
- this.indentBlock(state.auth.jwtPrivateKeyPem, 6),
79
- " jwt_public_key_pem: |-",
80
- this.indentBlock(state.auth.jwtPublicKeyPem, 6),
81
- ' jwt_issuer: "companyhelm.local"',
82
- ' jwt_audience: "companyhelm-web"',
83
- " jwt_expiration_seconds: 86400",
84
- "security:",
85
- " encryption:",
86
- ' key: "companyhelm-local-encryption-key"',
87
- `log_level: "${logLevel}"`,
88
- "log_pretty: false",
89
- ""
90
- ].join("\n");
91
- fs.writeFileSync(outputPath, yaml, "utf8");
92
- return outputPath;
93
- }
94
- writeFrontendConfig(root, state) {
95
- const runtimePaths = new RuntimePaths(root);
96
- const outputPath = runtimePaths.frontendConfigPath();
97
- const yaml = [
98
- "api:",
99
- ` graphqlApiUrl: "http://127.0.0.1:${state.ports.apiHttp}/graphql"`,
100
- ` runnerGrpcTarget: "localhost:${state.ports.runnerGrpc}"`,
101
- "auth:",
102
- ' provider: "companyhelm"',
103
- " companyhelm:",
104
- ' tokenStorageKey: "companyhelm.auth.token"',
105
- ""
106
- ].join("\n");
107
- fs.writeFileSync(outputPath, yaml, "utf8");
108
- return outputPath;
109
- }
110
- indentBlock(value, spaces) {
111
- const prefix = " ".repeat(spaces);
112
- return value
113
- .trimEnd()
114
- .split("\n")
115
- .map((line) => `${prefix}${line}`)
116
- .join("\n");
117
- }
118
- }
@@ -1,12 +0,0 @@
1
- export interface SeedSqlInput {
2
- companyId: string;
3
- companyName: string;
4
- username: string;
5
- passwordHash: string;
6
- passwordSalt?: string;
7
- runnerName: string;
8
- runnerSecret: string;
9
- }
10
- export declare class SeedSqlRenderer {
11
- render(input: SeedSqlInput): string;
12
- }
@@ -1,44 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import fs from "node:fs";
3
- import path from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- const __filename = fileURLToPath(import.meta.url);
6
- const __dirname = path.dirname(__filename);
7
- export class SeedSqlRenderer {
8
- render(input) {
9
- const templatePath = path.resolve(__dirname, "../../templates/seed.sql.tpl");
10
- const template = fs.readFileSync(templatePath, "utf8");
11
- const email = input.username.trim();
12
- const firstName = email.split("@")[0] || email;
13
- const runnerSecretHash = createHash("sha256").update(input.runnerSecret).digest("hex");
14
- const userId = deriveUuid(input.companyId, "user");
15
- const userAuthId = deriveUuid(input.companyId, "user-auth");
16
- const runnerId = deriveUuid(input.companyId, "runner");
17
- return template
18
- .replaceAll("{{COMPANY_ID}}", input.companyId)
19
- .replaceAll("{{COMPANY_NAME}}", input.companyName)
20
- .replaceAll("{{USER_ID}}", userId)
21
- .replaceAll("{{USER_AUTH_ID}}", userAuthId)
22
- .replaceAll("{{USER_FIRST_NAME}}", firstName)
23
- .replaceAll("{{USER_EMAIL}}", email)
24
- .replaceAll("{{PASSWORD_SALT}}", input.passwordSalt ?? "password-salt")
25
- .replaceAll("{{PASSWORD_HASH}}", input.passwordHash)
26
- .replaceAll("{{RUNNER_ID}}", runnerId)
27
- .replaceAll("{{RUNNER_NAME}}", input.runnerName)
28
- .replaceAll("{{RUNNER_SECRET_HASH}}", runnerSecretHash);
29
- }
30
- }
31
- function deriveUuid(companyId, scope) {
32
- const hex = createHash("sha256").update(`${companyId}:${scope}`).digest("hex");
33
- const chars = hex.slice(0, 32).split("");
34
- chars[12] = "4";
35
- chars[16] = ((Number.parseInt(chars[16] ?? "0", 16) & 0x3) | 0x8).toString(16);
36
- const normalized = chars.join("");
37
- return [
38
- normalized.slice(0, 8),
39
- normalized.slice(8, 12),
40
- normalized.slice(12, 16),
41
- normalized.slice(16, 20),
42
- normalized.slice(20, 32)
43
- ].join("-");
44
- }
@@ -1,8 +0,0 @@
1
- import type { GithubAppConfig } from "./GithubAppConfig.js";
2
- export declare class ApiEnvFileWriter {
3
- private readonly runtimePaths;
4
- private readonly templatePath;
5
- constructor(root: string);
6
- write(config: GithubAppConfig | null): string;
7
- private render;
8
- }
@@ -1,33 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import { RuntimePaths } from "../runtime/RuntimePaths.js";
5
- function escapeEnvValue(value) {
6
- return String(value || "")
7
- .replace(/\\/g, "\\\\")
8
- .replace(/\r\n/g, "\n")
9
- .replace(/\r/g, "\n")
10
- .replace(/\n/g, "\\n");
11
- }
12
- export class ApiEnvFileWriter {
13
- runtimePaths;
14
- templatePath;
15
- constructor(root) {
16
- this.runtimePaths = new RuntimePaths(root);
17
- this.templatePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../templates/api.env.tpl");
18
- }
19
- write(config) {
20
- fs.mkdirSync(this.runtimePaths.apiDirectoryPath(), { recursive: true });
21
- const contents = this.render(config);
22
- fs.writeFileSync(this.runtimePaths.apiEnvPath(), contents, "utf8");
23
- return this.runtimePaths.apiEnvPath();
24
- }
25
- render(config) {
26
- const template = fs.readFileSync(this.templatePath, "utf8");
27
- const rendered = template
28
- .replace("{{GITHUB_APP_URL}}", escapeEnvValue(config?.appUrl ?? ""))
29
- .replace("{{GITHUB_APP_CLIENT_ID}}", escapeEnvValue(config?.appClientId ?? ""))
30
- .replace("{{GITHUB_APP_PRIVATE_KEY_PEM}}", escapeEnvValue(config?.appPrivateKeyPem ?? ""));
31
- return rendered.endsWith("\n") ? rendered : `${rendered}\n`;
32
- }
33
- }
@@ -1,6 +0,0 @@
1
- export interface GithubAppConfig {
2
- appUrl: string;
3
- appClientId: string;
4
- appPrivateKeyPem: string;
5
- }
6
- export declare function normalizeGithubAppConfig(config: GithubAppConfig): GithubAppConfig;
@@ -1,26 +0,0 @@
1
- function normalizeRequiredValue(value, fieldName) {
2
- const normalized = String(value || "").trim();
3
- if (!normalized) {
4
- throw new Error(`GitHub App config field "${fieldName}" is required.`);
5
- }
6
- return normalized;
7
- }
8
- export function normalizeGithubAppConfig(config) {
9
- const appUrl = normalizeRequiredValue(config.appUrl, "appUrl");
10
- try {
11
- new URL(appUrl);
12
- }
13
- catch {
14
- throw new Error(`GitHub App config field "appUrl" must be a valid URL.`);
15
- }
16
- const appClientId = normalizeRequiredValue(config.appClientId, "appClientId");
17
- const rawPem = String(config.appPrivateKeyPem || "").replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim();
18
- if (!rawPem) {
19
- throw new Error('GitHub App config field "appPrivateKeyPem" is required.');
20
- }
21
- return {
22
- appUrl,
23
- appClientId,
24
- appPrivateKeyPem: `${rawPem}\n`,
25
- };
26
- }
@@ -1,11 +0,0 @@
1
- import { type GithubAppConfig } from "./GithubAppConfig.js";
2
- export declare class GithubAppConfigStore {
3
- private readonly configRoot;
4
- constructor(configRoot?: string);
5
- configPath(): string;
6
- hasConfig(): boolean;
7
- save(config: GithubAppConfig): string;
8
- load(): GithubAppConfig | null;
9
- loadOrThrow(): GithubAppConfig;
10
- delete(): void;
11
- }