supatypes 1.0.2 → 1.0.3

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/README.md CHANGED
@@ -40,6 +40,7 @@ Running `init` creates a `.supatypes.json` file:
40
40
  ```json
41
41
  {
42
42
  "server": "root@1.2.3.4",
43
+ "sshPort": 2222,
43
44
  "sshKey": "~/.ssh/id_rsa",
44
45
  "dbContainer": "supabase-db-abc123",
45
46
  "output": "./database.types.ts"
@@ -51,6 +52,7 @@ Running `init` creates a `.supatypes.json` file:
51
52
  | Field | Required | Description |
52
53
  |-------|----------|-------------|
53
54
  | `server` | Yes | SSH connection string (e.g. `root@1.2.3.4`) |
55
+ | `sshPort` | No | SSH port (default: `22`) |
54
56
  | `sshKey` | One of these | Path to SSH private key |
55
57
  | `sshPassword` | One of these | SSH password (uses `sshpass`) |
56
58
  | `dbContainer` | Yes | Docker container name for the Supabase PostgreSQL instance |
package/bin/cli.js CHANGED
@@ -20,6 +20,7 @@ Options (generate):
20
20
  Config file (.supatypes.json):
21
21
  {
22
22
  "server": "root@1.2.3.4",
23
+ "sshPort": 2222,
23
24
  "sshKey": "~/.ssh/id_rsa",
24
25
  "dbContainer": "supabase-db-abc123",
25
26
  "output": "./database.types.ts"
@@ -28,6 +29,7 @@ Config file (.supatypes.json):
28
29
  You can also use a password instead of an SSH key:
29
30
  {
30
31
  "server": "root@1.2.3.4",
32
+ "sshPort": 2222,
31
33
  "sshPassword": "your-password",
32
34
  "dbContainer": "supabase-db-abc123",
33
35
  "output": "./database.types.ts"
package/lib/generate.js CHANGED
@@ -1,167 +1,169 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { execFileSync } from "node:child_process";
4
- import { fileURLToPath } from "node:url";
5
-
6
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
- const REMOTE_SCRIPT = path.join(__dirname, "remote-generator.js");
8
- const REMOTE_DIR = "/tmp/supatypes";
9
- const REMOTE_SCRIPT_PATH = `${REMOTE_DIR}/generator.js`;
10
- const REMOTE_OUTPUT_PATH = `${REMOTE_DIR}/output.ts`;
11
-
12
- export async function generate({ configPath, outputOverride, dryRun }) {
13
- // Load config
14
- const resolvedConfig = path.resolve(configPath);
15
- if (!fs.existsSync(resolvedConfig)) {
16
- console.error(`Config file not found: ${resolvedConfig}`);
17
- console.error('Run "npx supatypes init" to create one.');
18
- process.exit(1);
19
- }
20
-
21
- const config = JSON.parse(fs.readFileSync(resolvedConfig, "utf8"));
22
- const { server, dbContainer } = config;
23
- const sshKey = config.sshKey ? expandHome(config.sshKey) : null;
24
- const sshPassword = config.sshPassword || null;
25
- const output = path.resolve(outputOverride || config.output || "./database.types.ts");
26
-
27
- // Validate
28
- if (!server) { console.error("Missing 'server' in config"); process.exit(1); }
29
- if (!dbContainer) { console.error("Missing 'dbContainer' in config"); process.exit(1); }
30
-
31
- if (sshKey && !fs.existsSync(sshKey)) {
32
- console.error(`SSH key not found: ${sshKey}`);
33
- process.exit(1);
34
- }
35
-
36
- const sshBase = buildSshBase(server, sshKey, sshPassword);
37
- const scpBase = buildScpBase(server, sshKey, sshPassword);
38
-
39
- if (dryRun) {
40
- console.log("Dry run — would execute:");
41
- console.log(` 1. Create ${REMOTE_DIR} on ${server}`);
42
- console.log(` 2. Upload generator script to ${REMOTE_SCRIPT_PATH}`);
43
- console.log(` 3. Run: node ${REMOTE_SCRIPT_PATH} ${dbContainer} ${REMOTE_OUTPUT_PATH}`);
44
- console.log(` 4. Download ${REMOTE_OUTPUT_PATH} to ${output}`);
45
- console.log(` 5. Clean up ${REMOTE_DIR} on server`);
46
- return;
47
- }
48
-
49
- console.log("supatypes\n");
50
- console.log(` Server: ${server}`);
51
- console.log(` Container: ${dbContainer}`);
52
- console.log(` Output: ${output}`);
53
- console.log(` Auth: ${sshKey ? "SSH key" : "password"}\n`);
54
-
55
- try {
56
- // Step 1: Create remote directory
57
- process.stdout.write("Creating remote workspace...");
58
- ssh(sshBase, `mkdir -p ${REMOTE_DIR}`);
59
- console.log(" done");
60
-
61
- // Step 2: Upload generator script
62
- process.stdout.write("Uploading generator...");
63
- scp(scpBase, REMOTE_SCRIPT, `${server}:${REMOTE_SCRIPT_PATH}`);
64
- console.log(" done");
65
-
66
- // Step 3: Run generator on server
67
- process.stdout.write("Generating types...");
68
- const remoteOutput = ssh(
69
- sshBase,
70
- `cd ${REMOTE_DIR} && node generator.js ${dbContainer} ${REMOTE_OUTPUT_PATH}`
71
- );
72
- console.log(" done");
73
-
74
- // Parse stats from output
75
- const statsMatch = remoteOutput.match(/__STATS__(.+)/);
76
- if (statsMatch) {
77
- const stats = JSON.parse(statsMatch[1]);
78
- console.log(`\n Tables: ${stats.tables}`);
79
- console.log(` Views: ${stats.views}`);
80
- console.log(` Functions: ${stats.functions}`);
81
- }
82
-
83
- // Step 4: Download result
84
- process.stdout.write("\nDownloading types...");
85
- const outputDir = path.dirname(output);
86
- if (!fs.existsSync(outputDir)) {
87
- fs.mkdirSync(outputDir, { recursive: true });
88
- }
89
- scp(scpBase, `${server}:${REMOTE_OUTPUT_PATH}`, output);
90
- console.log(" done");
91
-
92
- // Step 5: Clean up
93
- process.stdout.write("Cleaning up remote files...");
94
- ssh(sshBase, `rm -rf ${REMOTE_DIR}`);
95
- console.log(" done");
96
-
97
- const fileSize = fs.statSync(output).size;
98
- console.log(`\nTypes saved to ${output} (${formatBytes(fileSize)})`);
99
- } catch (err) {
100
- console.error(`\n\nFailed: ${err.message}`);
101
-
102
- // Try to clean up even on failure
103
- try {
104
- ssh(sshBase, `rm -rf ${REMOTE_DIR}`);
105
- } catch {
106
- // ignore cleanup errors
107
- }
108
-
109
- process.exit(1);
110
- }
111
- }
112
-
113
- function expandHome(p) {
114
- if (p.startsWith("~/")) {
115
- return path.join(process.env.HOME || process.env.USERPROFILE || "", p.slice(2));
116
- }
117
- return p;
118
- }
119
-
120
- function buildSshBase(server, sshKey, sshPassword) {
121
- const opts = ["-o", "StrictHostKeyChecking=accept-new", "-o", "ConnectTimeout=10"];
122
-
123
- if (sshPassword) {
124
- // Use sshpass for password-based auth
125
- return ["sshpass", "-p", sshPassword, "ssh", ...opts, server];
126
- }
127
-
128
- if (sshKey) {
129
- return ["ssh", "-i", sshKey, ...opts, server];
130
- }
131
-
132
- // Default: let SSH use its default key
133
- return ["ssh", ...opts, server];
134
- }
135
-
136
- function buildScpBase(server, sshKey, sshPassword) {
137
- const opts = ["-o", "StrictHostKeyChecking=accept-new", "-o", "ConnectTimeout=10"];
138
-
139
- if (sshPassword) {
140
- return ["sshpass", "-p", sshPassword, "scp", ...opts];
141
- }
142
-
143
- if (sshKey) {
144
- return ["scp", "-i", sshKey, ...opts];
145
- }
146
-
147
- return ["scp", ...opts];
148
- }
149
-
150
- function ssh(base, command) {
151
- const [bin, ...args] = base;
152
- return execFileSync(bin, [...args, command], {
153
- encoding: "utf8",
154
- maxBuffer: 50 * 1024 * 1024,
155
- });
156
- }
157
-
158
- function scp(base, src, dest) {
159
- const [bin, ...args] = base;
160
- execFileSync(bin, [...args, src, dest], { encoding: "utf8" });
161
- }
162
-
163
- function formatBytes(bytes) {
164
- if (bytes < 1024) return `${bytes} B`;
165
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
166
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
167
- }
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { execFileSync } from "node:child_process";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const REMOTE_SCRIPT = path.join(__dirname, "remote-generator.js");
8
+ const REMOTE_DIR = "/tmp/supatypes";
9
+ const REMOTE_SCRIPT_PATH = `${REMOTE_DIR}/generator.js`;
10
+ const REMOTE_OUTPUT_PATH = `${REMOTE_DIR}/output.ts`;
11
+
12
+ export async function generate({ configPath, outputOverride, dryRun }) {
13
+ // Load config
14
+ const resolvedConfig = path.resolve(configPath);
15
+ if (!fs.existsSync(resolvedConfig)) {
16
+ console.error(`Config file not found: ${resolvedConfig}`);
17
+ console.error('Run "npx supatypes init" to create one.');
18
+ process.exit(1);
19
+ }
20
+
21
+ const config = JSON.parse(fs.readFileSync(resolvedConfig, "utf8"));
22
+ const { server, dbContainer } = config;
23
+ const sshPort = config.sshPort || 22;
24
+ const sshKey = config.sshKey ? expandHome(config.sshKey) : null;
25
+ const sshPassword = config.sshPassword || null;
26
+ const output = path.resolve(outputOverride || config.output || "./database.types.ts");
27
+
28
+ // Validate
29
+ if (!server) { console.error("Missing 'server' in config"); process.exit(1); }
30
+ if (!dbContainer) { console.error("Missing 'dbContainer' in config"); process.exit(1); }
31
+
32
+ if (sshKey && !fs.existsSync(sshKey)) {
33
+ console.error(`SSH key not found: ${sshKey}`);
34
+ process.exit(1);
35
+ }
36
+
37
+ const sshBase = buildSshBase(server, sshPort, sshKey, sshPassword);
38
+ const scpBase = buildScpBase(server, sshPort, sshKey, sshPassword);
39
+
40
+ if (dryRun) {
41
+ console.log("Dry run would execute:");
42
+ console.log(` 1. Create ${REMOTE_DIR} on ${server}`);
43
+ console.log(` 2. Upload generator script to ${REMOTE_SCRIPT_PATH}`);
44
+ console.log(` 3. Run: node ${REMOTE_SCRIPT_PATH} ${dbContainer} ${REMOTE_OUTPUT_PATH}`);
45
+ console.log(` 4. Download ${REMOTE_OUTPUT_PATH} to ${output}`);
46
+ console.log(` 5. Clean up ${REMOTE_DIR} on server`);
47
+ return;
48
+ }
49
+
50
+ console.log("supatypes\n");
51
+ console.log(` Server: ${server}`);
52
+ console.log(` Port: ${sshPort}`);
53
+ console.log(` Container: ${dbContainer}`);
54
+ console.log(` Output: ${output}`);
55
+ console.log(` Auth: ${sshKey ? "SSH key" : "password"}\n`);
56
+
57
+ try {
58
+ // Step 1: Create remote directory
59
+ process.stdout.write("Creating remote workspace...");
60
+ ssh(sshBase, `mkdir -p ${REMOTE_DIR}`);
61
+ console.log(" done");
62
+
63
+ // Step 2: Upload generator script
64
+ process.stdout.write("Uploading generator...");
65
+ scp(scpBase, REMOTE_SCRIPT, `${server}:${REMOTE_SCRIPT_PATH}`);
66
+ console.log(" done");
67
+
68
+ // Step 3: Run generator on server
69
+ process.stdout.write("Generating types...");
70
+ const remoteOutput = ssh(
71
+ sshBase,
72
+ `cd ${REMOTE_DIR} && node generator.js ${dbContainer} ${REMOTE_OUTPUT_PATH}`
73
+ );
74
+ console.log(" done");
75
+
76
+ // Parse stats from output
77
+ const statsMatch = remoteOutput.match(/__STATS__(.+)/);
78
+ if (statsMatch) {
79
+ const stats = JSON.parse(statsMatch[1]);
80
+ console.log(`\n Tables: ${stats.tables}`);
81
+ console.log(` Views: ${stats.views}`);
82
+ console.log(` Functions: ${stats.functions}`);
83
+ }
84
+
85
+ // Step 4: Download result
86
+ process.stdout.write("\nDownloading types...");
87
+ const outputDir = path.dirname(output);
88
+ if (!fs.existsSync(outputDir)) {
89
+ fs.mkdirSync(outputDir, { recursive: true });
90
+ }
91
+ scp(scpBase, `${server}:${REMOTE_OUTPUT_PATH}`, output);
92
+ console.log(" done");
93
+
94
+ // Step 5: Clean up
95
+ process.stdout.write("Cleaning up remote files...");
96
+ ssh(sshBase, `rm -rf ${REMOTE_DIR}`);
97
+ console.log(" done");
98
+
99
+ const fileSize = fs.statSync(output).size;
100
+ console.log(`\nTypes saved to ${output} (${formatBytes(fileSize)})`);
101
+ } catch (err) {
102
+ console.error(`\n\nFailed: ${err.message}`);
103
+
104
+ // Try to clean up even on failure
105
+ try {
106
+ ssh(sshBase, `rm -rf ${REMOTE_DIR}`);
107
+ } catch {
108
+ // ignore cleanup errors
109
+ }
110
+
111
+ process.exit(1);
112
+ }
113
+ }
114
+
115
+ function expandHome(p) {
116
+ if (p.startsWith("~/")) {
117
+ return path.join(process.env.HOME || process.env.USERPROFILE || "", p.slice(2));
118
+ }
119
+ return p;
120
+ }
121
+
122
+ function buildSshBase(server, port, sshKey, sshPassword) {
123
+ const opts = ["-p", String(port), "-o", "StrictHostKeyChecking=accept-new", "-o", "ConnectTimeout=10"];
124
+
125
+ if (sshPassword) {
126
+ // Use sshpass for password-based auth
127
+ return ["sshpass", "-p", sshPassword, "ssh", ...opts, server];
128
+ }
129
+
130
+ if (sshKey) {
131
+ return ["ssh", "-i", sshKey, ...opts, server];
132
+ }
133
+
134
+ // Default: let SSH use its default key
135
+ return ["ssh", ...opts, server];
136
+ }
137
+
138
+ function buildScpBase(server, port, sshKey, sshPassword) {
139
+ const opts = ["-P", String(port), "-o", "StrictHostKeyChecking=accept-new", "-o", "ConnectTimeout=10"];
140
+
141
+ if (sshPassword) {
142
+ return ["sshpass", "-p", sshPassword, "scp", ...opts];
143
+ }
144
+
145
+ if (sshKey) {
146
+ return ["scp", "-i", sshKey, ...opts];
147
+ }
148
+
149
+ return ["scp", ...opts];
150
+ }
151
+
152
+ function ssh(base, command) {
153
+ const [bin, ...args] = base;
154
+ return execFileSync(bin, [...args, command], {
155
+ encoding: "utf8",
156
+ maxBuffer: 50 * 1024 * 1024,
157
+ });
158
+ }
159
+
160
+ function scp(base, src, dest) {
161
+ const [bin, ...args] = base;
162
+ execFileSync(bin, [...args, src, dest], { encoding: "utf8" });
163
+ }
164
+
165
+ function formatBytes(bytes) {
166
+ if (bytes < 1024) return `${bytes} B`;
167
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
168
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
169
+ }
package/lib/init.js CHANGED
@@ -33,6 +33,7 @@ export async function init() {
33
33
 
34
34
  const sshUser = await ask(rl, "SSH username", "root");
35
35
  const sshHost = await ask(rl, "SSH server host (e.g. 1.2.3.4)", "");
36
+ const sshPort = await ask(rl, "SSH port", "22");
36
37
  const server = `${sshUser}@${sshHost}`;
37
38
  const authMethod = await ask(rl, "Auth method: key or password?", "key");
38
39
 
@@ -52,6 +53,8 @@ export async function init() {
52
53
 
53
54
  const config = { server, dbContainer, output };
54
55
 
56
+ const parsedPort = parseInt(sshPort, 10);
57
+ if (parsedPort !== 22) config.sshPort = parsedPort;
55
58
  if (sshKey) config.sshKey = sshKey;
56
59
  if (sshPassword) config.sshPassword = sshPassword;
57
60
 
package/package.json CHANGED
@@ -1,27 +1,27 @@
1
- {
2
- "name": "supatypes",
3
- "version": "1.0.2",
4
- "description": "Generate TypeScript types from a remote Supabase PostgreSQL database via SSH",
5
- "license": "MIT",
6
- "author": "MadStoneDev",
7
- "type": "module",
8
- "bin": {
9
- "supatypes": "bin/cli.js"
10
- },
11
- "files": [
12
- "bin/",
13
- "lib/",
14
- "README.md"
15
- ],
16
- "keywords": [
17
- "supabase",
18
- "typescript",
19
- "types",
20
- "codegen",
21
- "postgresql",
22
- "database"
23
- ],
24
- "engines": {
25
- "node": ">=18"
26
- }
27
- }
1
+ {
2
+ "name": "supatypes",
3
+ "version": "1.0.3",
4
+ "description": "Generate TypeScript types from a remote Supabase PostgreSQL database via SSH",
5
+ "license": "MIT",
6
+ "author": "MadStoneDev",
7
+ "type": "module",
8
+ "bin": {
9
+ "supatypes": "bin/cli.js"
10
+ },
11
+ "files": [
12
+ "bin/",
13
+ "lib/",
14
+ "README.md"
15
+ ],
16
+ "keywords": [
17
+ "supabase",
18
+ "typescript",
19
+ "types",
20
+ "codegen",
21
+ "postgresql",
22
+ "database"
23
+ ],
24
+ "engines": {
25
+ "node": ">=18"
26
+ }
27
+ }