becky-cli 0.2.12

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 (42) hide show
  1. package/README.md +102 -0
  2. package/dist/ai/openai.d.ts +17 -0
  3. package/dist/ai/openai.js +67 -0
  4. package/dist/commands/config.d.ts +3 -0
  5. package/dist/commands/config.js +30 -0
  6. package/dist/commands/connect.d.ts +11 -0
  7. package/dist/commands/connect.js +36 -0
  8. package/dist/commands/doctor.d.ts +10 -0
  9. package/dist/commands/doctor.js +166 -0
  10. package/dist/commands/migrate.d.ts +9 -0
  11. package/dist/commands/migrate.js +208 -0
  12. package/dist/commands/postgres-setup.d.ts +12 -0
  13. package/dist/commands/postgres-setup.js +132 -0
  14. package/dist/commands/up.d.ts +13 -0
  15. package/dist/commands/up.js +58 -0
  16. package/dist/config/store.d.ts +19 -0
  17. package/dist/config/store.js +65 -0
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.js +197 -0
  20. package/dist/platform/detect.d.ts +10 -0
  21. package/dist/platform/detect.js +47 -0
  22. package/dist/platform/linux-ubuntu.d.ts +2 -0
  23. package/dist/platform/linux-ubuntu.js +48 -0
  24. package/dist/postgres/admin.d.ts +19 -0
  25. package/dist/postgres/admin.js +77 -0
  26. package/dist/project/alembicScaffold.d.ts +7 -0
  27. package/dist/project/alembicScaffold.js +161 -0
  28. package/dist/project/env.d.ts +25 -0
  29. package/dist/project/env.js +124 -0
  30. package/dist/project/fastapiPins.d.ts +3 -0
  31. package/dist/project/fastapiPins.js +13 -0
  32. package/dist/project/identity.d.ts +13 -0
  33. package/dist/project/identity.js +37 -0
  34. package/dist/project/load.d.ts +27 -0
  35. package/dist/project/load.js +67 -0
  36. package/dist/util/exec.d.ts +15 -0
  37. package/dist/util/exec.js +45 -0
  38. package/dist/util/log.d.ts +6 -0
  39. package/dist/util/log.js +27 -0
  40. package/dist/util/prompt.d.ts +2 -0
  41. package/dist/util/prompt.js +22 -0
  42. package/package.json +49 -0
@@ -0,0 +1,132 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { saveCredentials } from "../config/store.js";
4
+ import { detectPlatform, linuxDistroLabel } from "../platform/detect.js";
5
+ import { installPostgresUbuntu } from "../platform/linux-ubuntu.js";
6
+ import { buildDatabaseUrl, ensureServiceRunning, getPostgresStatus, setupDatabaseUser, } from "../postgres/admin.js";
7
+ import { projectDbName, projectDbUser, loadProject } from "../project/load.js";
8
+ import { resolveDbIdentity } from "../project/identity.js";
9
+ import { promptHidden, promptText } from "../util/prompt.js";
10
+ import { detail, error, info, step, success, warn } from "../util/log.js";
11
+ function loadSpecDefaults(specPath) {
12
+ const absolute = resolve(specPath);
13
+ const raw = readFileSync(absolute, "utf8");
14
+ const parsed = JSON.parse(raw);
15
+ if (typeof parsed.projectName === "string" && parsed.projectName.trim()) {
16
+ return {
17
+ database: projectDbName(parsed.projectName),
18
+ user: projectDbUser(parsed.projectName),
19
+ };
20
+ }
21
+ return null;
22
+ }
23
+ async function confirm(message, yes) {
24
+ if (yes)
25
+ return true;
26
+ const answer = await promptText(`${message} (y/N)`, "n");
27
+ return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
28
+ }
29
+ function assertLinuxUbuntu(platform) {
30
+ if (platform.os !== "linux") {
31
+ throw new Error(`PostgreSQL setup is not implemented for ${platform.os} yet. Linux (Ubuntu) is supported first.`);
32
+ }
33
+ if (platform.linuxDistro !== "ubuntu" && platform.linuxDistro !== "debian") {
34
+ throw new Error(`PostgreSQL setup on ${linuxDistroLabel(platform)} is not supported yet. Use Ubuntu or Debian for now.`);
35
+ }
36
+ }
37
+ export async function runPostgresSetup(options) {
38
+ const platform = detectPlatform();
39
+ assertLinuxUbuntu(platform);
40
+ let database = options.database?.trim();
41
+ let user = options.user?.trim();
42
+ if (options.specPath) {
43
+ const fromSpec = loadSpecDefaults(options.specPath);
44
+ if (fromSpec) {
45
+ if (!database)
46
+ database = fromSpec.database;
47
+ if (!user)
48
+ user = fromSpec.user;
49
+ detail(`From project-spec: user=${fromSpec.user} database=${fromSpec.database}`);
50
+ }
51
+ }
52
+ database = database || "my_backend";
53
+ user = user || database;
54
+ // When wired to a generated project directory, apply the same identity rules as `up`
55
+ if (options.specPath) {
56
+ try {
57
+ const projectDir = resolve(options.specPath, "..");
58
+ const project = loadProject(projectDir);
59
+ const identity = resolveDbIdentity(project, { user: options.user, database: options.database });
60
+ user = identity.user;
61
+ database = identity.database;
62
+ }
63
+ catch {
64
+ /* spec-only setup without full project layout */
65
+ }
66
+ }
67
+ if (user === "app" && database !== "app") {
68
+ warn(`Replacing legacy DB user "app" with "${database}"`);
69
+ user = database;
70
+ }
71
+ step(`Becky — PostgreSQL setup (${linuxDistroLabel(platform)})`);
72
+ detail("This command may ask for your sudo password to install packages and manage Postgres.");
73
+ let status = await getPostgresStatus();
74
+ if (!status.installed) {
75
+ info("PostgreSQL is not installed");
76
+ const proceed = await confirm("Install latest PostgreSQL from PGDG?", options.yes);
77
+ if (!proceed) {
78
+ warn("Aborted — PostgreSQL was not installed");
79
+ return;
80
+ }
81
+ await installPostgresUbuntu(platform);
82
+ status = await getPostgresStatus();
83
+ }
84
+ else {
85
+ success(`PostgreSQL already installed${status.version ? `: ${status.version}` : ""}`);
86
+ }
87
+ await ensureServiceRunning();
88
+ let password = options.password;
89
+ if (!password) {
90
+ password = await promptHidden("Database password (input hidden)");
91
+ if (!password) {
92
+ throw new Error("A database password is required");
93
+ }
94
+ }
95
+ step("Create database user and database");
96
+ detail(`User: ${user}`);
97
+ detail(`Database: ${database}`);
98
+ await setupDatabaseUser({
99
+ user,
100
+ password,
101
+ database,
102
+ });
103
+ const url = buildDatabaseUrl(user, password, database, options.host, options.port);
104
+ saveCredentials({
105
+ host: options.host,
106
+ port: options.port,
107
+ user,
108
+ password,
109
+ database,
110
+ databaseUrl: url,
111
+ });
112
+ info("Saved credentials to ~/.becky/credentials.json");
113
+ step("Done");
114
+ success("PostgreSQL is ready for local development");
115
+ console.log("");
116
+ console.log("Connection details:");
117
+ console.log(` Host: ${options.host}`);
118
+ console.log(` Port: ${options.port}`);
119
+ console.log(` User: ${user}`);
120
+ console.log(` Database: ${database}`);
121
+ console.log(` URL: ${url}`);
122
+ console.log("");
123
+ info("Wire a generated project with:");
124
+ console.log(" becky connect .");
125
+ console.log(" # or all-in-one:");
126
+ console.log(" becky up .");
127
+ }
128
+ export function printUnsupportedPlatform() {
129
+ const platform = detectPlatform();
130
+ error(`PostgreSQL setup is not available on ${platform.os} yet.`);
131
+ info("Linux (Ubuntu/Debian) is supported. macOS and Windows support is planned.");
132
+ }
@@ -0,0 +1,13 @@
1
+ export type UpOptions = {
2
+ dir: string;
3
+ user?: string;
4
+ password?: string;
5
+ database?: string;
6
+ host: string;
7
+ port: number;
8
+ yes: boolean;
9
+ skipInstall?: boolean;
10
+ skipMigrate?: boolean;
11
+ force?: boolean;
12
+ };
13
+ export declare function runUp(options: UpOptions): Promise<void>;
@@ -0,0 +1,58 @@
1
+ import { runConnect } from "./connect.js";
2
+ import { runMigrate } from "./migrate.js";
3
+ import { runPostgresSetup } from "./postgres-setup.js";
4
+ import { detail, info, step, success } from "../util/log.js";
5
+ import { loadProject } from "../project/load.js";
6
+ import { resolveDbIdentity } from "../project/identity.js";
7
+ export async function runUp(options) {
8
+ const project = loadProject(options.dir);
9
+ const { user, database } = resolveDbIdentity(project, {
10
+ user: options.user,
11
+ database: options.database,
12
+ });
13
+ step(`Becky — up (${project.spec.projectName})`);
14
+ detail("Will: ensure Postgres → create DB → write .env → migrate");
15
+ detail(`Postgres role: ${user}`);
16
+ detail(`Database: ${database} (local PostgreSQL on this machine)`);
17
+ if (!options.skipInstall) {
18
+ await runPostgresSetup({
19
+ user,
20
+ password: options.password,
21
+ database,
22
+ host: options.host,
23
+ port: options.port,
24
+ yes: options.yes,
25
+ specPath: project.specPath,
26
+ });
27
+ }
28
+ else {
29
+ info("Skipping postgres setup (--skip-install)");
30
+ }
31
+ await runConnect({
32
+ dir: options.dir,
33
+ user,
34
+ password: options.password,
35
+ database,
36
+ host: options.host,
37
+ port: options.port,
38
+ force: options.force ?? true,
39
+ });
40
+ if (!options.skipMigrate) {
41
+ await runMigrate({
42
+ dir: options.dir,
43
+ user,
44
+ password: options.password,
45
+ database,
46
+ host: options.host,
47
+ port: options.port,
48
+ });
49
+ }
50
+ else {
51
+ info("Skipping migrate (--skip-migrate)");
52
+ }
53
+ step("All set");
54
+ success("Postgres is ready and linked to this backend");
55
+ console.log("");
56
+ info("Start the API (from the generated project README), then:");
57
+ console.log(" becky doctor .");
58
+ }
@@ -0,0 +1,19 @@
1
+ export type StoredCredentials = {
2
+ host: string;
3
+ port: number;
4
+ user: string;
5
+ password: string;
6
+ database: string;
7
+ databaseUrl: string;
8
+ updatedAt: string;
9
+ };
10
+ export type BeckyConfig = {
11
+ openaiApiKey?: string;
12
+ defaultModel?: string;
13
+ };
14
+ export declare function getConfigDir(): string;
15
+ export declare function saveCredentials(creds: Omit<StoredCredentials, "updatedAt">): void;
16
+ export declare function loadCredentials(): StoredCredentials | null;
17
+ export declare function loadConfig(): BeckyConfig;
18
+ export declare function saveConfig(patch: Partial<BeckyConfig>): BeckyConfig;
19
+ export declare function resolveOpenAiKey(cliFlag?: string): string | undefined;
@@ -0,0 +1,65 @@
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ const CONFIG_DIR = join(homedir(), ".becky");
5
+ const CREDENTIALS_PATH = join(CONFIG_DIR, "credentials.json");
6
+ const CONFIG_PATH = join(CONFIG_DIR, "config.json");
7
+ function ensureConfigDir() {
8
+ if (!existsSync(CONFIG_DIR)) {
9
+ mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
10
+ }
11
+ }
12
+ function secureWrite(path, content) {
13
+ ensureConfigDir();
14
+ writeFileSync(path, content, { encoding: "utf8", mode: 0o600 });
15
+ try {
16
+ chmodSync(path, 0o600);
17
+ }
18
+ catch {
19
+ /* best effort on platforms that ignore mode */
20
+ }
21
+ }
22
+ export function getConfigDir() {
23
+ return CONFIG_DIR;
24
+ }
25
+ export function saveCredentials(creds) {
26
+ const payload = {
27
+ ...creds,
28
+ updatedAt: new Date().toISOString(),
29
+ };
30
+ secureWrite(CREDENTIALS_PATH, `${JSON.stringify(payload, null, 2)}\n`);
31
+ }
32
+ export function loadCredentials() {
33
+ try {
34
+ if (!existsSync(CREDENTIALS_PATH))
35
+ return null;
36
+ return JSON.parse(readFileSync(CREDENTIALS_PATH, "utf8"));
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ }
42
+ export function loadConfig() {
43
+ try {
44
+ if (!existsSync(CONFIG_PATH))
45
+ return {};
46
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
47
+ }
48
+ catch {
49
+ return {};
50
+ }
51
+ }
52
+ export function saveConfig(patch) {
53
+ const next = { ...loadConfig(), ...patch };
54
+ secureWrite(CONFIG_PATH, `${JSON.stringify(next, null, 2)}\n`);
55
+ return next;
56
+ }
57
+ export function resolveOpenAiKey(cliFlag) {
58
+ const fromFlag = cliFlag?.trim();
59
+ if (fromFlag)
60
+ return fromFlag;
61
+ const fromEnv = process.env.OPENAI_API_KEY?.trim() || process.env.BECKY_OPENAI_KEY?.trim();
62
+ if (fromEnv)
63
+ return fromEnv;
64
+ return loadConfig().openaiApiKey?.trim() || undefined;
65
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { Command } from "commander";
6
+ import { runConfigSetKey, runConfigSetModel, runConfigShow } from "./commands/config.js";
7
+ import { runConnect } from "./commands/connect.js";
8
+ import { runDoctor } from "./commands/doctor.js";
9
+ import { runMigrate } from "./commands/migrate.js";
10
+ import { runPostgresSetup } from "./commands/postgres-setup.js";
11
+ import { runUp } from "./commands/up.js";
12
+ import { detectPlatform } from "./platform/detect.js";
13
+ const program = new Command();
14
+ const pkg = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), "../package.json"), "utf8"));
15
+ program
16
+ .name("becky")
17
+ .description("Becky CLI — Postgres setup, connect backends, migrate, and doctor")
18
+ .version(pkg.version);
19
+ function fail(err) {
20
+ const message = err instanceof Error ? err.message : String(err);
21
+ console.error(`\nError: ${message}`);
22
+ process.exit(1);
23
+ }
24
+ const postgres = program.command("postgres").description("PostgreSQL utilities");
25
+ postgres
26
+ .command("setup")
27
+ .description("Install PostgreSQL (Ubuntu/Debian) and create a local user + database")
28
+ .option("-u, --user <name>", "Database user (defaults to project/database name)")
29
+ .option("-p, --password <password>", "Database password (prompted if omitted)")
30
+ .option("-d, --database <name>", "Database name (defaults from --spec or my_backend)")
31
+ .option("--host <host>", "Database host", "localhost")
32
+ .option("--port <port>", "Database port", "5432")
33
+ .option("--spec <path>", "project-spec.json — derive user + database from project name")
34
+ .option("-y, --yes", "Skip install confirmation prompts", false)
35
+ .action(async (opts) => {
36
+ try {
37
+ await runPostgresSetup({
38
+ user: opts.user,
39
+ password: opts.password,
40
+ database: opts.database,
41
+ host: opts.host,
42
+ port: Number(opts.port),
43
+ yes: Boolean(opts.yes),
44
+ specPath: opts.spec,
45
+ });
46
+ }
47
+ catch (err) {
48
+ if (detectPlatform().os !== "linux") {
49
+ console.error("\nTip: Linux (Ubuntu/Debian) is supported first.");
50
+ }
51
+ fail(err);
52
+ }
53
+ });
54
+ postgres
55
+ .command("status")
56
+ .description("Check whether PostgreSQL is installed and running")
57
+ .action(async () => {
58
+ const { getPostgresStatus } = await import("./postgres/admin.js");
59
+ const { detectPlatform, linuxDistroLabel } = await import("./platform/detect.js");
60
+ const platform = detectPlatform();
61
+ const status = await getPostgresStatus();
62
+ console.log(`Platform: ${platform.os}${platform.linuxDistro ? ` (${linuxDistroLabel(platform)})` : ""}`);
63
+ console.log(`Installed: ${status.installed ? "yes" : "no"}`);
64
+ if (status.version)
65
+ console.log(`Version: ${status.version}`);
66
+ console.log(`Service: ${status.serviceActive ? "running" : "not running"}`);
67
+ });
68
+ program
69
+ .command("connect [dir]")
70
+ .description("Write .env from credentials / project-spec.json and test the DB connection")
71
+ .option("-u, --user <name>", "Database user")
72
+ .option("-p, --password <password>", "Database password")
73
+ .option("-d, --database <name>", "Database name")
74
+ .option("--host <host>", "Database host")
75
+ .option("--port <port>", "Database port")
76
+ .option("-f, --force", "Overwrite existing .env", false)
77
+ .option("--skip-test", "Skip connection test", false)
78
+ .action(async (dir, opts) => {
79
+ try {
80
+ await runConnect({
81
+ dir: dir || ".",
82
+ user: opts.user,
83
+ password: opts.password,
84
+ database: opts.database,
85
+ host: opts.host,
86
+ port: opts.port != null ? Number(opts.port) : undefined,
87
+ force: Boolean(opts.force),
88
+ skipTest: Boolean(opts.skipTest),
89
+ });
90
+ }
91
+ catch (err) {
92
+ fail(err);
93
+ }
94
+ });
95
+ program
96
+ .command("migrate [dir]")
97
+ .description("Run stack migrations (Prisma / Alembic / scripts)")
98
+ .option("-u, --user <name>", "Database user")
99
+ .option("-p, --password <password>", "Database password")
100
+ .option("-d, --database <name>", "Database name")
101
+ .option("--host <host>", "Database host")
102
+ .option("--port <port>", "Database port")
103
+ .action(async (dir, opts) => {
104
+ try {
105
+ await runMigrate({
106
+ dir: dir || ".",
107
+ user: opts.user,
108
+ password: opts.password,
109
+ database: opts.database,
110
+ host: opts.host,
111
+ port: opts.port != null ? Number(opts.port) : undefined,
112
+ });
113
+ }
114
+ catch (err) {
115
+ fail(err);
116
+ }
117
+ });
118
+ program
119
+ .command("up [dir]")
120
+ .description("Postgres setup + connect + migrate for a generated project")
121
+ .option("-u, --user <name>", "Database user (defaults to project name slug)")
122
+ .option("-p, --password <password>", "Database password (prompted if omitted)")
123
+ .option("-d, --database <name>", "Database name (defaults from project-spec.json)")
124
+ .option("--host <host>", "Database host", "localhost")
125
+ .option("--port <port>", "Database port", "5432")
126
+ .option("-y, --yes", "Skip install confirmation prompts", false)
127
+ .option("--skip-install", "Skip postgres install/setup", false)
128
+ .option("--skip-migrate", "Skip migrations", false)
129
+ .option("-f, --force", "Overwrite .env", true)
130
+ .action(async (dir, opts) => {
131
+ try {
132
+ await runUp({
133
+ dir: dir || ".",
134
+ user: opts.user,
135
+ password: opts.password,
136
+ database: opts.database,
137
+ host: opts.host,
138
+ port: Number(opts.port),
139
+ yes: Boolean(opts.yes),
140
+ skipInstall: Boolean(opts.skipInstall),
141
+ skipMigrate: Boolean(opts.skipMigrate),
142
+ force: opts.force !== false,
143
+ });
144
+ }
145
+ catch (err) {
146
+ fail(err);
147
+ }
148
+ });
149
+ program
150
+ .command("doctor [dir]")
151
+ .description("Health-check Postgres + .env + API; use OpenAI when something fails")
152
+ .option("--api-key <key>", "OpenAI API key (or OPENAI_API_KEY / becky config)")
153
+ .option("--model <model>", "OpenAI model", "gpt-4o-mini")
154
+ .option("--skip-ai", "Only run checks — no OpenAI diagnosis", false)
155
+ .action(async (dir, opts) => {
156
+ try {
157
+ await runDoctor({
158
+ dir: dir || ".",
159
+ apiKey: opts.apiKey,
160
+ model: opts.model,
161
+ noAi: Boolean(opts.skipAi),
162
+ });
163
+ }
164
+ catch (err) {
165
+ fail(err);
166
+ }
167
+ });
168
+ const config = program.command("config").description("CLI configuration (OpenAI key, model)");
169
+ config
170
+ .command("set-key [key]")
171
+ .description("Save OpenAI API key to ~/.becky/config.json")
172
+ .action(async (key) => {
173
+ try {
174
+ await runConfigSetKey(key);
175
+ }
176
+ catch (err) {
177
+ fail(err);
178
+ }
179
+ });
180
+ config
181
+ .command("set-model <model>")
182
+ .description("Set default OpenAI model (e.g. gpt-4o-mini)")
183
+ .action((model) => {
184
+ try {
185
+ runConfigSetModel(model);
186
+ }
187
+ catch (err) {
188
+ fail(err);
189
+ }
190
+ });
191
+ config
192
+ .command("show")
193
+ .description("Show current CLI config (key is masked)")
194
+ .action(() => {
195
+ runConfigShow();
196
+ });
197
+ program.parseAsync(process.argv);
@@ -0,0 +1,10 @@
1
+ export type OsFamily = "linux" | "darwin" | "win32" | "unsupported";
2
+ export type LinuxDistro = "ubuntu" | "debian" | "other";
3
+ export type PlatformInfo = {
4
+ os: OsFamily;
5
+ linuxDistro?: LinuxDistro;
6
+ id?: string;
7
+ versionId?: string;
8
+ };
9
+ export declare function detectPlatform(): PlatformInfo;
10
+ export declare function linuxDistroLabel(info: PlatformInfo): string;
@@ -0,0 +1,47 @@
1
+ import { readFileSync } from "node:fs";
2
+ function parseOsRelease(content) {
3
+ const values = {};
4
+ for (const line of content.split("\n")) {
5
+ const match = line.match(/^([A-Z0-9_]+)=(.+)$/);
6
+ if (!match)
7
+ continue;
8
+ values[match[1]] = match[2].replace(/^"|"$/g, "");
9
+ }
10
+ return values;
11
+ }
12
+ export function detectPlatform() {
13
+ const platform = process.platform;
14
+ if (platform === "linux") {
15
+ try {
16
+ const release = parseOsRelease(readFileSync("/etc/os-release", "utf8"));
17
+ const id = release.ID?.toLowerCase();
18
+ const idLike = release.ID_LIKE?.toLowerCase() ?? "";
19
+ let linuxDistro = "other";
20
+ if (id === "ubuntu")
21
+ linuxDistro = "ubuntu";
22
+ else if (id === "debian" || idLike.includes("debian"))
23
+ linuxDistro = "debian";
24
+ return {
25
+ os: "linux",
26
+ linuxDistro,
27
+ id,
28
+ versionId: release.VERSION_ID,
29
+ };
30
+ }
31
+ catch {
32
+ return { os: "linux", linuxDistro: "other" };
33
+ }
34
+ }
35
+ if (platform === "darwin")
36
+ return { os: "darwin" };
37
+ if (platform === "win32")
38
+ return { os: "win32" };
39
+ return { os: "unsupported" };
40
+ }
41
+ export function linuxDistroLabel(info) {
42
+ if (info.id === "ubuntu")
43
+ return `Ubuntu ${info.versionId ?? ""}`.trim();
44
+ if (info.id === "debian")
45
+ return `Debian ${info.versionId ?? ""}`.trim();
46
+ return info.id ?? "Linux";
47
+ }
@@ -0,0 +1,2 @@
1
+ import type { PlatformInfo } from "../platform/detect.js";
2
+ export declare function installPostgresUbuntu(platform: PlatformInfo): Promise<void>;
@@ -0,0 +1,48 @@
1
+ import { commandExists, runOrThrow } from "../util/exec.js";
2
+ import { detail, info, step, success } from "../util/log.js";
3
+ const PGDG_LIST = "/etc/apt/sources.list.d/pgdg.list";
4
+ const PGDG_KEYRING = "/usr/share/keyrings/postgresql.gpg";
5
+ async function hasPgdgRepo() {
6
+ try {
7
+ const { readFileSync } = await import("node:fs");
8
+ readFileSync(PGDG_LIST, "utf8");
9
+ return true;
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ async function addPgdgRepo(codename) {
16
+ info("Adding PostgreSQL APT (PGDG) repository for latest packages");
17
+ await runOrThrow("sudo", ["apt-get", "update"], {}, "apt-get update failed");
18
+ await runOrThrow("sudo", ["apt-get", "install", "-y", "ca-certificates", "curl", "gnupg", "lsb-release"], {}, "Failed to install apt prerequisites");
19
+ await runOrThrow("sudo", [
20
+ "bash",
21
+ "-c",
22
+ `curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o ${PGDG_KEYRING}`,
23
+ ], {}, "Failed to add PostgreSQL signing key");
24
+ const listLine = `deb [signed-by=${PGDG_KEYRING}] http://apt.postgresql.org/pub/repos/apt ${codename}-pgdg main`;
25
+ await runOrThrow("sudo", ["bash", "-c", `echo ${JSON.stringify(listLine)} > ${PGDG_LIST}`], {}, "Failed to write PGDG apt source");
26
+ await runOrThrow("sudo", ["apt-get", "update"], {}, "apt-get update failed after adding PGDG");
27
+ success("PGDG repository configured");
28
+ }
29
+ export async function installPostgresUbuntu(platform) {
30
+ step("Install PostgreSQL (Ubuntu/Debian)");
31
+ if (!(await commandExists("apt-get"))) {
32
+ throw new Error("apt-get not found. This installer currently supports Ubuntu/Debian only.");
33
+ }
34
+ const codename = await runOrThrow("lsb_release", ["-cs"], {}, "Could not detect Ubuntu codename (lsb_release)");
35
+ detail(`Detected codename: ${codename}`);
36
+ if (!(await hasPgdgRepo())) {
37
+ await addPgdgRepo(codename);
38
+ }
39
+ else {
40
+ detail("PGDG repository already configured");
41
+ }
42
+ info("Installing PostgreSQL packages (latest from PGDG)");
43
+ await runOrThrow("sudo", ["apt-get", "install", "-y", "postgresql", "postgresql-contrib"], {}, "Failed to install PostgreSQL");
44
+ info("Enabling PostgreSQL service");
45
+ await runOrThrow("sudo", ["systemctl", "enable", "--now", "postgresql"], {}, "Failed to enable PostgreSQL service");
46
+ const version = await runOrThrow("psql", ["--version"], {}, "PostgreSQL installed but psql not found");
47
+ success(version);
48
+ }
@@ -0,0 +1,19 @@
1
+ export type PostgresStatus = {
2
+ installed: boolean;
3
+ version?: string;
4
+ serviceActive: boolean;
5
+ };
6
+ export declare function getPostgresStatus(): Promise<PostgresStatus>;
7
+ export declare function sqlLiteral(value: string): string;
8
+ export declare function postgresQuery(sql: string): Promise<string>;
9
+ export declare function roleExists(name: string): Promise<boolean>;
10
+ export declare function databaseExists(name: string): Promise<boolean>;
11
+ export type SetupDatabaseOptions = {
12
+ user: string;
13
+ password: string;
14
+ database: string;
15
+ grantPrivileges?: boolean;
16
+ };
17
+ export declare function setupDatabaseUser(options: SetupDatabaseOptions): Promise<void>;
18
+ export declare function buildDatabaseUrl(user: string, password: string, database: string, host?: string, port?: number): string;
19
+ export declare function ensureServiceRunning(): Promise<void>;