@coldge.com/gitbase 1.0.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.
- package/LICENSE +15 -0
- package/README.md +178 -0
- package/dist/api/supabase.js +166 -0
- package/dist/commands/add.js +34 -0
- package/dist/commands/branch.js +96 -0
- package/dist/commands/clone.js +65 -0
- package/dist/commands/commit.js +99 -0
- package/dist/commands/diff.js +125 -0
- package/dist/commands/fetch.js +49 -0
- package/dist/commands/init.js +90 -0
- package/dist/commands/log.js +30 -0
- package/dist/commands/login.js +29 -0
- package/dist/commands/merge.js +59 -0
- package/dist/commands/push.js +176 -0
- package/dist/commands/remote.js +142 -0
- package/dist/commands/revert.js +233 -0
- package/dist/commands/status.js +96 -0
- package/dist/commands/whoami.js +19 -0
- package/dist/index.js +130 -0
- package/dist/schema/extractor.js +113 -0
- package/dist/schema/queries.js +47 -0
- package/dist/storage/git.js +15 -0
- package/dist/utils/config.js +21 -0
- package/dist/utils/hashing.js +9 -0
- package/package.json +47 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { runQuery } from '../api/supabase.js';
|
|
2
|
+
import * as Queries from './queries.js';
|
|
3
|
+
export async function extractSchema(projectRef) {
|
|
4
|
+
const tables = await extractTables(projectRef);
|
|
5
|
+
const functions = await extractFunctions(projectRef);
|
|
6
|
+
const views = await extractViews(projectRef);
|
|
7
|
+
const triggers = await extractTriggers(projectRef);
|
|
8
|
+
const policies = await extractPolicies(projectRef);
|
|
9
|
+
const types = await extractTypes(projectRef);
|
|
10
|
+
return { tables, functions, views, triggers, policies, types };
|
|
11
|
+
}
|
|
12
|
+
async function extractTables(projectRef) {
|
|
13
|
+
const tableList = await runQuery(projectRef, Queries.LIST_TABLES);
|
|
14
|
+
if (!Array.isArray(tableList))
|
|
15
|
+
return {};
|
|
16
|
+
const tables = {};
|
|
17
|
+
for (const row of tableList) {
|
|
18
|
+
const tableName = row.table_name;
|
|
19
|
+
const rlsEnabled = row.rls_enabled;
|
|
20
|
+
const columns = await runQuery(projectRef, Queries.GET_COLUMNS(tableName));
|
|
21
|
+
let sql = `CREATE TABLE public."${tableName}" (\n`;
|
|
22
|
+
const colDefs = columns.map(c => {
|
|
23
|
+
let line = ` "${c.column_name}" ${c.data_type}`;
|
|
24
|
+
if (c.is_nullable === 'NO')
|
|
25
|
+
line += ' NOT NULL';
|
|
26
|
+
if (c.column_default)
|
|
27
|
+
line += ` DEFAULT ${c.column_default}`;
|
|
28
|
+
return line;
|
|
29
|
+
});
|
|
30
|
+
sql += colDefs.join(',\n');
|
|
31
|
+
sql += '\n);';
|
|
32
|
+
if (rlsEnabled) {
|
|
33
|
+
sql += `\nALTER TABLE public."${tableName}" ENABLE ROW LEVEL SECURITY;`;
|
|
34
|
+
}
|
|
35
|
+
tables[tableName] = sql;
|
|
36
|
+
}
|
|
37
|
+
return tables;
|
|
38
|
+
}
|
|
39
|
+
async function extractFunctions(projectRef) {
|
|
40
|
+
const funcs = await runQuery(projectRef, Queries.LIST_FUNCTIONS);
|
|
41
|
+
if (!Array.isArray(funcs))
|
|
42
|
+
return {};
|
|
43
|
+
const result = {};
|
|
44
|
+
for (const f of funcs) {
|
|
45
|
+
result[f.name] = f.definition;
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
async function extractViews(projectRef) {
|
|
50
|
+
const views = await runQuery(projectRef, Queries.LIST_VIEWS);
|
|
51
|
+
if (!Array.isArray(views))
|
|
52
|
+
return {};
|
|
53
|
+
const result = {};
|
|
54
|
+
for (const v of views) {
|
|
55
|
+
result[v.name] = `CREATE OR REPLACE VIEW public."${v.name}" AS\n${v.definition}`;
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
async function extractTriggers(projectRef) {
|
|
60
|
+
const triggers = await runQuery(projectRef, Queries.LIST_TRIGGERS);
|
|
61
|
+
if (!Array.isArray(triggers))
|
|
62
|
+
return {};
|
|
63
|
+
const result = {};
|
|
64
|
+
for (const t of triggers) {
|
|
65
|
+
result[t.name] = t.definition;
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
async function extractPolicies(projectRef) {
|
|
70
|
+
const policies = await runQuery(projectRef, Queries.LIST_POLICIES);
|
|
71
|
+
if (!Array.isArray(policies))
|
|
72
|
+
return {};
|
|
73
|
+
const result = {};
|
|
74
|
+
for (const p of policies) {
|
|
75
|
+
let sql = `CREATE POLICY "${p.name}"\nON public."${p.tablename}"\nFOR ${p.cmd}`;
|
|
76
|
+
// Handle roles
|
|
77
|
+
let roles = p.roles;
|
|
78
|
+
if (Array.isArray(roles)) {
|
|
79
|
+
roles = roles.join(', ');
|
|
80
|
+
}
|
|
81
|
+
else if (typeof roles === 'string' && roles.startsWith('{')) {
|
|
82
|
+
roles = roles.replace(/^\{|\}$/g, '').split(',').join(', ');
|
|
83
|
+
}
|
|
84
|
+
if (roles) {
|
|
85
|
+
sql += `\nTO ${roles}`;
|
|
86
|
+
}
|
|
87
|
+
if (p.qual)
|
|
88
|
+
sql += `\nUSING (${p.qual})`;
|
|
89
|
+
if (p.with_check)
|
|
90
|
+
sql += `\nWITH CHECK (${p.with_check})`;
|
|
91
|
+
sql += ';';
|
|
92
|
+
result[`${p.tablename}_${p.name}`] = sql;
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
async function extractTypes(projectRef) {
|
|
97
|
+
const types = await runQuery(projectRef, Queries.LIST_TYPES);
|
|
98
|
+
if (!Array.isArray(types))
|
|
99
|
+
return {};
|
|
100
|
+
const result = {};
|
|
101
|
+
// Group by type name since list returns rows of (name, label) for enums
|
|
102
|
+
const grouped = {};
|
|
103
|
+
for (const t of types) {
|
|
104
|
+
if (!grouped[t.name])
|
|
105
|
+
grouped[t.name] = [];
|
|
106
|
+
grouped[t.name].push(t.label);
|
|
107
|
+
}
|
|
108
|
+
for (const [name, labels] of Object.entries(grouped)) {
|
|
109
|
+
const labelsStr = labels.map(l => `'${l}'`).join(', ');
|
|
110
|
+
result[name] = `CREATE TYPE public."${name}" AS ENUM (${labelsStr});`;
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export const LIST_TABLES = `
|
|
2
|
+
SELECT c.relname as table_name, c.relrowsecurity as rls_enabled
|
|
3
|
+
FROM pg_class c
|
|
4
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
5
|
+
WHERE n.nspname = 'public'
|
|
6
|
+
AND c.relkind = 'r'
|
|
7
|
+
ORDER BY c.relname;
|
|
8
|
+
`;
|
|
9
|
+
export const GET_COLUMNS = (tableName) => `
|
|
10
|
+
SELECT column_name, data_type, is_nullable, column_default
|
|
11
|
+
FROM information_schema.columns
|
|
12
|
+
WHERE table_schema = 'public' AND table_name = '${tableName}'
|
|
13
|
+
ORDER BY ordinal_position;
|
|
14
|
+
`;
|
|
15
|
+
export const LIST_FUNCTIONS = `
|
|
16
|
+
SELECT proname as name, pg_get_functiondef(oid) as definition
|
|
17
|
+
FROM pg_proc
|
|
18
|
+
WHERE pronamespace = 'public'::regnamespace
|
|
19
|
+
ORDER BY proname;
|
|
20
|
+
`;
|
|
21
|
+
export const LIST_VIEWS = `
|
|
22
|
+
SELECT table_name as name, view_definition as definition
|
|
23
|
+
FROM information_schema.views
|
|
24
|
+
WHERE table_schema = 'public'
|
|
25
|
+
ORDER BY table_name;
|
|
26
|
+
`;
|
|
27
|
+
export const LIST_TRIGGERS = `
|
|
28
|
+
SELECT tgname as name, pg_get_triggerdef(oid) as definition
|
|
29
|
+
FROM pg_trigger
|
|
30
|
+
WHERE tgrelid IN (SELECT oid FROM pg_class WHERE relnamespace = 'public'::regnamespace)
|
|
31
|
+
AND tgisinternal = false
|
|
32
|
+
ORDER BY tgname;
|
|
33
|
+
`;
|
|
34
|
+
export const LIST_POLICIES = `
|
|
35
|
+
SELECT policyname as name, tablename, cmd, roles, qual, with_check
|
|
36
|
+
FROM pg_policies
|
|
37
|
+
WHERE schemaname = 'public'
|
|
38
|
+
ORDER BY tablename, policyname;
|
|
39
|
+
`;
|
|
40
|
+
export const LIST_TYPES = `
|
|
41
|
+
SELECT t.typname as name, e.enumlabel as label
|
|
42
|
+
FROM pg_type t
|
|
43
|
+
JOIN pg_enum e ON t.oid = e.enumtypid
|
|
44
|
+
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
45
|
+
WHERE n.nspname = 'public'
|
|
46
|
+
ORDER BY t.typname, e.enumsortorder;
|
|
47
|
+
`;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
const GITBASE_DIR = '.gitbase';
|
|
4
|
+
const OBJECTS_DIR = path.join(GITBASE_DIR, 'objects');
|
|
5
|
+
export async function readObject(hash) {
|
|
6
|
+
return fs.readFile(path.join(OBJECTS_DIR, hash), 'utf-8');
|
|
7
|
+
}
|
|
8
|
+
export async function readTree(treeHash) {
|
|
9
|
+
const content = await readObject(treeHash);
|
|
10
|
+
return JSON.parse(content);
|
|
11
|
+
}
|
|
12
|
+
export async function readCommit(commitHash) {
|
|
13
|
+
const content = await readObject(commitHash);
|
|
14
|
+
return JSON.parse(content);
|
|
15
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
const CONFIG_DIR = path.join(os.homedir(), '.config', 'gitbase');
|
|
5
|
+
const TOKEN_FILE = path.join(CONFIG_DIR, 'token.json');
|
|
6
|
+
export async function ensureConfigDir() {
|
|
7
|
+
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
|
8
|
+
}
|
|
9
|
+
export async function saveToken(token) {
|
|
10
|
+
await ensureConfigDir();
|
|
11
|
+
await fs.writeFile(TOKEN_FILE, JSON.stringify({ token }), 'utf-8');
|
|
12
|
+
}
|
|
13
|
+
export async function getToken() {
|
|
14
|
+
try {
|
|
15
|
+
const data = await fs.readFile(TOKEN_FILE, 'utf-8');
|
|
16
|
+
return JSON.parse(data).token;
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
export function hashString(content) {
|
|
3
|
+
return crypto.createHash('sha1').update(content).digest('hex');
|
|
4
|
+
}
|
|
5
|
+
export function canonicalize(sql) {
|
|
6
|
+
// Basic normalization: trim
|
|
7
|
+
// In a real implementation, we would parse the SQL and reprint it deterministically.
|
|
8
|
+
return sql.trim();
|
|
9
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@coldge.com/gitbase",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "The Time Machine for Supabase",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"gitb": "dist/index.js",
|
|
9
|
+
"gitbase": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"start": "node --loader ts-node/esm src/index.ts",
|
|
19
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"supabase",
|
|
23
|
+
"git",
|
|
24
|
+
"version-control",
|
|
25
|
+
"cli"
|
|
26
|
+
],
|
|
27
|
+
"author": "Coldge",
|
|
28
|
+
"license": "ISC",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"axios": "^1.13.5",
|
|
31
|
+
"chalk": "^5.6.2",
|
|
32
|
+
"diff": "^8.0.3",
|
|
33
|
+
"dotenv": "^17.3.1",
|
|
34
|
+
"pg": "^8.19.0",
|
|
35
|
+
"toposort": "^2.0.2",
|
|
36
|
+
"yargs": "^17.0.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/diff": "^7.0.2",
|
|
40
|
+
"@types/node": "^25.3.2",
|
|
41
|
+
"@types/pg": "^8.16.0",
|
|
42
|
+
"@types/toposort": "^2.0.7",
|
|
43
|
+
"@types/yargs": "^17.0.35",
|
|
44
|
+
"ts-node": "^10.9.2",
|
|
45
|
+
"typescript": "^5.9.3"
|
|
46
|
+
}
|
|
47
|
+
}
|