supascan 0.0.0

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 (59) hide show
  1. package/.bun-version +1 -0
  2. package/.github/workflows/release-github.yml +70 -0
  3. package/.github/workflows/release-npm.yml +45 -0
  4. package/.github/workflows/tests.yml +36 -0
  5. package/LICENCE +22 -0
  6. package/README.md +115 -0
  7. package/apps/cli/build.ts +37 -0
  8. package/apps/cli/package.json +29 -0
  9. package/apps/cli/src/commands/analyze.ts +213 -0
  10. package/apps/cli/src/commands/dump.ts +68 -0
  11. package/apps/cli/src/commands/rpc.ts +67 -0
  12. package/apps/cli/src/context.ts +96 -0
  13. package/apps/cli/src/embedded-report.ts +1 -0
  14. package/apps/cli/src/formatters/console.ts +39 -0
  15. package/apps/cli/src/formatters/events.ts +95 -0
  16. package/apps/cli/src/index.ts +105 -0
  17. package/apps/cli/src/types.ts +9 -0
  18. package/apps/cli/src/utils/args.ts +46 -0
  19. package/apps/cli/src/utils/browser.ts +29 -0
  20. package/apps/cli/src/utils/files.ts +12 -0
  21. package/apps/cli/src/version.ts +3 -0
  22. package/apps/web/build.ts +68 -0
  23. package/apps/web/dev.ts +5 -0
  24. package/apps/web/index.html +75 -0
  25. package/apps/web/package.json +23 -0
  26. package/apps/web/src/App.tsx +129 -0
  27. package/apps/web/src/components/QueryBuilder.tsx +174 -0
  28. package/apps/web/src/components/QueryWindow.tsx +133 -0
  29. package/apps/web/src/components/RPCExecutor.tsx +176 -0
  30. package/apps/web/src/components/SchemaBrowser.tsx +269 -0
  31. package/apps/web/src/components/SmartTable.tsx +129 -0
  32. package/apps/web/src/components/TargetConfig.tsx +130 -0
  33. package/apps/web/src/components/TargetSummary.tsx +105 -0
  34. package/apps/web/src/hooks/useAnalysis.ts +54 -0
  35. package/apps/web/src/hooks/useNotification.ts +28 -0
  36. package/apps/web/src/hooks/useRPC.ts +53 -0
  37. package/apps/web/src/hooks/useSupabase.ts +46 -0
  38. package/apps/web/src/hooks/useTableQuery.ts +148 -0
  39. package/apps/web/src/index.tsx +18 -0
  40. package/apps/web/src/types.ts +16 -0
  41. package/apps/web/src/utils/hash.ts +27 -0
  42. package/context.test.ts +93 -0
  43. package/package.json +48 -0
  44. package/package.publish.json +18 -0
  45. package/packages/core/package.json +22 -0
  46. package/packages/core/src/analyzer.ts +212 -0
  47. package/packages/core/src/extractor.ts +233 -0
  48. package/packages/core/src/index.ts +9 -0
  49. package/packages/core/src/supabase.ts +316 -0
  50. package/packages/core/src/types/analyzer.types.ts +72 -0
  51. package/packages/core/src/types/event.types.ts +4 -0
  52. package/packages/core/src/types/events.types.ts +5 -0
  53. package/packages/core/src/types/extractor.types.ts +54 -0
  54. package/packages/core/src/types/result.types.ts +17 -0
  55. package/packages/core/src/types/supabase.types.ts +98 -0
  56. package/tsconfig.json +23 -0
  57. package/turbo.json +19 -0
  58. package/utils.test.ts +68 -0
  59. package/version.ts +3 -0
package/.bun-version ADDED
@@ -0,0 +1 @@
1
+ 1.3.2
@@ -0,0 +1,70 @@
1
+ name: Create GitHub Release
2
+ on:
3
+ push:
4
+ branches: [master]
5
+ workflow_dispatch:
6
+
7
+ jobs:
8
+ tests:
9
+ uses: ./.github/workflows/tests.yml
10
+
11
+ release:
12
+ needs: tests
13
+ runs-on: ubuntu-latest
14
+ permissions:
15
+ contents: write
16
+ id-token: write
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ with:
21
+ fetch-depth: 0
22
+
23
+ - uses: oven-sh/setup-bun@v2
24
+ with:
25
+ bun-version-file: ".bun-version"
26
+
27
+ - run: bun install --frozen-lockfile
28
+ - run: bun run build:binary
29
+
30
+ - name: Get version from package.json
31
+ id: pkg
32
+ run: echo "version=$(bun -e "console.log(require('./package.json').version)")" >> $GITHUB_OUTPUT
33
+
34
+ - name: Check if tag exists
35
+ id: tag-check
36
+ run: |
37
+ if git rev-parse "v${{ steps.pkg.outputs.version }}" >/dev/null 2>&1; then
38
+ echo "exists=true" >> $GITHUB_OUTPUT
39
+ else
40
+ echo "exists=false" >> $GITHUB_OUTPUT
41
+ fi
42
+
43
+ - name: Create and push tag
44
+ if: steps.tag-check.outputs.exists == 'false'
45
+ run: |
46
+ git config user.name "github-actions"
47
+ git config user.email "github-actions@github.com"
48
+ git tag v${{ steps.pkg.outputs.version }}
49
+ git push origin v${{ steps.pkg.outputs.version }}
50
+
51
+ - name: Check if release exists
52
+ id: release-check
53
+ run: |
54
+ if gh release view "v${{ steps.pkg.outputs.version }}" >/dev/null 2>&1; then
55
+ echo "exists=true" >> $GITHUB_OUTPUT
56
+ else
57
+ echo "exists=false" >> $GITHUB_OUTPUT
58
+ fi
59
+ env:
60
+ GH_TOKEN: ${{ github.token }}
61
+
62
+ - name: Create GitHub Release
63
+ if: steps.release-check.outputs.exists == 'false'
64
+ uses: softprops/action-gh-release@v1
65
+ with:
66
+ tag_name: v${{ steps.pkg.outputs.version }}
67
+ name: Release v${{ steps.pkg.outputs.version }}
68
+ generate_release_notes: true
69
+ files: |
70
+ dist/supascan
@@ -0,0 +1,45 @@
1
+ name: Publish to npm
2
+ on:
3
+ push:
4
+ branches: [master]
5
+ workflow_dispatch:
6
+
7
+ jobs:
8
+ tests:
9
+ uses: ./.github/workflows/tests.yml
10
+
11
+ publish:
12
+ needs: tests
13
+ runs-on: ubuntu-latest
14
+ permissions:
15
+ contents: read
16
+ id-token: write
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - uses: oven-sh/setup-bun@v2
22
+ with:
23
+ bun-version-file: ".bun-version"
24
+
25
+ - run: bun install --frozen-lockfile
26
+ - run: bun run build:bundle
27
+
28
+ - name: Get version from package.json
29
+ id: pkg
30
+ run: echo "version=$(bun -e "console.log(require('./package.json').version)")" >> $GITHUB_OUTPUT
31
+
32
+ - name: Check if version exists on npm
33
+ id: npm-check
34
+ run: |
35
+ if npm view supascan@${{ steps.pkg.outputs.version }} version 2>/dev/null; then
36
+ echo "exists=true" >> $GITHUB_OUTPUT
37
+ else
38
+ echo "exists=false" >> $GITHUB_OUTPUT
39
+ fi
40
+
41
+ - name: Publish to npm
42
+ if: steps.npm-check.outputs.exists == 'false'
43
+ run: bun publish --access public
44
+ env:
45
+ NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1,36 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+ workflow_call:
9
+
10
+ jobs:
11
+ test:
12
+ runs-on: ubuntu-latest
13
+ permissions:
14
+ contents: read
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - uses: oven-sh/setup-bun@v2
20
+ with:
21
+ bun-version-file: ".bun-version"
22
+
23
+ - name: Install dependencies
24
+ run: bun install --frozen-lockfile
25
+
26
+ - name: Lint
27
+ run: bun run lint
28
+
29
+ - name: Run tests
30
+ run: bun run test
31
+
32
+ - name: Build bundle
33
+ run: bun run build:bundle
34
+
35
+ - name: Build binary
36
+ run: bun run build:binary
package/LICENCE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-present Abhishek Govindarasu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # supascan
2
+
3
+ [![.github/workflows/tests.yml](https://github.com/abhishekg999/supascan/actions/workflows/tests.yml/badge.svg)](https://github.com/abhishekg999/supascan/actions/workflows/tests.yml) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/abhishekg999/supascan/master/LICENCE)
4
+
5
+ **supascan** is an automated security scanner for Supabase databases. It detects exposed data, analyzes Row Level Security (RLS) policies, tests RPC functions, and generates comprehensive security reports.
6
+
7
+ ## Features
8
+
9
+ - Automated schema and table discovery
10
+ - RLS policy effectiveness testing
11
+ - Exposed data detection with row count estimation
12
+ - RPC function parameter analysis and testing
13
+ - JWT token decoding and validation
14
+ - Multiple output formats (Console, JSON, HTML)
15
+ - Interactive HTML reports with live query interface
16
+ - Credential extraction from JavaScript files (experimental)
17
+
18
+ ## Installation
19
+
20
+ **Note:**
21
+
22
+ - Primarily tested with [Bun](https://bun.sh) runtime (Node.js support is experimental)
23
+
24
+ **Bun:**
25
+
26
+ ```bash
27
+ bun install -g supascan
28
+ ```
29
+
30
+ **NPM:**
31
+
32
+ ```bash
33
+ npm install -g supascan
34
+ ```
35
+
36
+ **From source:**
37
+
38
+ ```bash
39
+ git clone https://github.com/abhishekg999/supascan.git
40
+ cd supascan
41
+ bun install
42
+ bun run build
43
+ ```
44
+
45
+ ## Usage
46
+
47
+ To get basic options and usage:
48
+
49
+ ```bash
50
+ supascan --help
51
+ ```
52
+
53
+ ### Quick Start
54
+
55
+ ```bash
56
+ # Basic security scan
57
+ supascan --url https://your-project.supabase.co --key your-anon-key
58
+
59
+ # Generate HTML report
60
+ supascan --url https://your-project.supabase.co --key your-anon-key --html
61
+
62
+ # Analyze specific schema
63
+ supascan --url https://your-project.supabase.co --key your-anon-key --schema public
64
+
65
+ # Dump table data
66
+ supascan --url https://your-project.supabase.co --key your-anon-key --dump public.users --limit 100
67
+
68
+ # Test RPC function
69
+ supascan --url https://your-project.supabase.co --key your-anon-key --rpc public.my_function --args '{"param": "value"}'
70
+ ```
71
+
72
+ ## What supascan Detects
73
+
74
+ - **Exposed Tables**: Tables readable without authentication or with weak RLS
75
+ - **Data Leakage**: Estimated row counts for accessible tables
76
+ - **RPC Vulnerabilities**: Publicly callable functions and their parameters
77
+ - **JWT Issues**: Token expiration, role assignments, and claims
78
+ - **Schema Information**: Complete database structure visibility
79
+
80
+ ## Security Considerations
81
+
82
+ ⚠️ **Important**: This tool is for authorized security testing only.
83
+
84
+ - Only scan databases you own or have explicit permission to test
85
+ - Use on staging/development environments when possible
86
+ - Never use on production databases without proper authorization
87
+ - Be aware that scanning may trigger rate limits or monitoring alerts
88
+
89
+ Unauthorized database scanning may be illegal in your jurisdiction.
90
+
91
+ ## Development
92
+
93
+ ```bash
94
+ # Install dependencies
95
+ bun install
96
+
97
+ # Run locally
98
+ bun run start
99
+
100
+ # Run tests
101
+ bun test
102
+
103
+ # Build
104
+ bun run build
105
+ ```
106
+
107
+ ## License
108
+
109
+ supascan is distributed under the [MIT License](LICENCE).
110
+
111
+ ## Links
112
+
113
+ - **Homepage**: https://github.com/abhishekg999/supascan
114
+ - **Issues**: https://github.com/abhishekg999/supascan/issues
115
+ - **NPM**: https://www.npmjs.com/package/supascan
@@ -0,0 +1,37 @@
1
+ import { $ } from "bun";
2
+ import { mkdir, readFile } from "fs/promises";
3
+
4
+ console.log("Building CLI...");
5
+
6
+ await $`tsc --noEmit`;
7
+
8
+ console.log("Building web app for embedding...");
9
+ await $`cd ../web && bun run build`;
10
+
11
+ const webHtml = await readFile("../web/dist/index.html", "utf-8");
12
+
13
+ await Bun.write(
14
+ "./src/embedded-report.ts",
15
+ `export const reportTemplate = ${JSON.stringify(webHtml)};`,
16
+ );
17
+
18
+ await mkdir("./dist", { recursive: true });
19
+
20
+ const nodeBuild = await Bun.build({
21
+ entrypoints: ["./src/index.ts"],
22
+ outdir: "./dist",
23
+ minify: true,
24
+ target: "node",
25
+ banner: "#!/usr/bin/env node",
26
+ naming: {
27
+ entry: "supascan.js",
28
+ },
29
+ });
30
+
31
+ if (!nodeBuild.success) {
32
+ console.error("Failed to bundle CLI");
33
+ for (const log of nodeBuild.logs) console.error(log);
34
+ process.exit(1);
35
+ }
36
+
37
+ console.log("✓ CLI built: dist/supascan.js");
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "supascan",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "./dist/supascan.js",
7
+ "bin": {
8
+ "supascan": "./dist/supascan.js"
9
+ },
10
+ "files": [
11
+ "dist/**"
12
+ ],
13
+ "scripts": {
14
+ "build": "bun run build.ts",
15
+ "dev": "bun run src/index.ts",
16
+ "lint": "tsc --noEmit"
17
+ },
18
+ "dependencies": {
19
+ "@commander-js/extra-typings": "^14.0.0",
20
+ "@supabase/supabase-js": "^2.75.0",
21
+ "@supascan/core": "workspace:*",
22
+ "commander": "^14.0.1",
23
+ "picocolors": "^1.1.1"
24
+ },
25
+ "devDependencies": {
26
+ "@types/bun": "latest",
27
+ "typescript": "^5.9.3"
28
+ }
29
+ }
@@ -0,0 +1,213 @@
1
+ import {
2
+ analyze,
3
+ type AnalysisResult,
4
+ type RPCParameter,
5
+ } from "@supascan/core";
6
+ import pc from "picocolors";
7
+ import type { CLIContext } from "../context";
8
+ import { log } from "../formatters/console";
9
+ import { handleEvent } from "../formatters/events";
10
+
11
+ export async function executeAnalyzeCommand(
12
+ ctx: CLIContext,
13
+ options: { schema?: string },
14
+ ): Promise<void> {
15
+ if (ctx.html) {
16
+ const { reportTemplate } = await import("../embedded-report");
17
+ const { generateTempFilePath, writeHtmlFile } = await import(
18
+ "../utils/files"
19
+ );
20
+ const { openInBrowser } = await import("../utils/browser");
21
+
22
+ const config = {
23
+ url: ctx.url,
24
+ key: ctx.key,
25
+ headers: ctx.headers,
26
+ autorun: true,
27
+ };
28
+ const encoded = Buffer.from(JSON.stringify(config)).toString("base64");
29
+
30
+ const filePath = generateTempFilePath();
31
+ const htmlContent = reportTemplate.replace(
32
+ "</head>",
33
+ `<script>window.__SUPASCAN_CONFIG__ = "${encoded}";</script></head>`,
34
+ );
35
+ writeHtmlFile(filePath, htmlContent);
36
+
37
+ openInBrowser(filePath);
38
+ log.success(`HTML report generated: ${filePath}`);
39
+ return;
40
+ }
41
+
42
+ const analysisGen = analyze(ctx.client, ctx.url, ctx.key, options);
43
+ let analysisResult;
44
+
45
+ while (true) {
46
+ const next = await analysisGen.next();
47
+ if (next.done) {
48
+ analysisResult = next.value;
49
+ break;
50
+ }
51
+ handleEvent(ctx, next.value);
52
+ }
53
+
54
+ if (!analysisResult || !analysisResult.success) {
55
+ log.error(
56
+ "Analysis failed",
57
+ analysisResult?.error.message ?? "Unknown error",
58
+ );
59
+ process.exit(1);
60
+ }
61
+
62
+ if (ctx.json) {
63
+ console.log(JSON.stringify(analysisResult.value, null, 2));
64
+ } else {
65
+ displayAnalysisResult(analysisResult.value);
66
+ }
67
+ }
68
+
69
+ function displayAnalysisResult(result: AnalysisResult): void {
70
+ console.log();
71
+ console.log(pc.bold(pc.cyan("=".repeat(60))));
72
+ console.log(pc.bold(pc.cyan(" SUPABASE DATABASE ANALYSIS")));
73
+ console.log(pc.bold(pc.cyan("=".repeat(60))));
74
+ console.log();
75
+
76
+ console.log(pc.bold(pc.yellow("TARGET SUMMARY")));
77
+ console.log(pc.dim("-".repeat(20)));
78
+ console.log(pc.bold("Domain:"), pc.white(result.summary.domain));
79
+
80
+ if (result.summary.metadata?.service) {
81
+ console.log(pc.bold("Service:"), pc.white(result.summary.metadata.service));
82
+ }
83
+
84
+ if (result.summary.metadata?.region) {
85
+ console.log(
86
+ pc.bold("Project ID:"),
87
+ pc.white(result.summary.metadata.region),
88
+ );
89
+ }
90
+
91
+ if (result.summary.metadata?.title) {
92
+ console.log(pc.bold("Title:"), pc.white(result.summary.metadata.title));
93
+ }
94
+
95
+ if (result.summary.metadata?.version) {
96
+ console.log(pc.bold("Version:"), pc.white(result.summary.metadata.version));
97
+ }
98
+
99
+ if (result.summary.jwtInfo) {
100
+ console.log();
101
+ console.log(pc.bold(pc.yellow("JWT TOKEN INFO")));
102
+ console.log(pc.dim("-".repeat(20)));
103
+
104
+ if (result.summary.jwtInfo.iss) {
105
+ console.log(pc.bold("Issuer:"), pc.white(result.summary.jwtInfo.iss));
106
+ }
107
+ if (result.summary.jwtInfo.aud) {
108
+ console.log(pc.bold("Audience:"), pc.white(result.summary.jwtInfo.aud));
109
+ }
110
+ if (result.summary.jwtInfo.role) {
111
+ console.log(pc.bold("Role:"), pc.white(result.summary.jwtInfo.role));
112
+ }
113
+ if (result.summary.jwtInfo.exp) {
114
+ const expDate = new Date(result.summary.jwtInfo.exp * 1000);
115
+ console.log(pc.bold("Expires:"), pc.white(expDate.toISOString()));
116
+ }
117
+ if (result.summary.jwtInfo.iat) {
118
+ const iatDate = new Date(result.summary.jwtInfo.iat * 1000);
119
+ console.log(pc.bold("Issued:"), pc.white(iatDate.toISOString()));
120
+ }
121
+ }
122
+
123
+ console.log();
124
+ console.log(pc.bold(pc.cyan("DATABASE ANALYSIS")));
125
+ console.log(pc.dim("-".repeat(20)));
126
+ console.log(
127
+ pc.bold("Schemas discovered:"),
128
+ pc.green(result.schemas.length.toString()),
129
+ );
130
+ console.log();
131
+
132
+ Object.entries(result.schemaDetails).forEach(([schema, analysis]) => {
133
+ console.log(pc.bold(pc.cyan(`Schema: ${schema}`)));
134
+ console.log();
135
+
136
+ const exposedCount = Object.values(analysis.tableAccess).filter(
137
+ (a) => a.status === "readable",
138
+ ).length;
139
+ const deniedCount = Object.values(analysis.tableAccess).filter(
140
+ (a) => a.status === "denied",
141
+ ).length;
142
+ const emptyCount = Object.values(analysis.tableAccess).filter(
143
+ (a) => a.status === "empty",
144
+ ).length;
145
+
146
+ console.log(
147
+ pc.bold("Tables:"),
148
+ pc.green(analysis.tables.length.toString()),
149
+ );
150
+ console.log(
151
+ pc.dim(
152
+ ` ${exposedCount} exposed | ${emptyCount} empty/protected | ${deniedCount} denied`,
153
+ ),
154
+ );
155
+ console.log();
156
+
157
+ if (analysis.tables.length > 0) {
158
+ analysis.tables.forEach((table) => {
159
+ const access = analysis.tableAccess[table];
160
+ let indicator = "";
161
+ let description = "";
162
+
163
+ switch (access?.status) {
164
+ case "readable":
165
+ indicator = pc.green("[+]");
166
+ description = pc.dim(`(~${access.rowCount ?? "?"} rows exposed)`);
167
+ break;
168
+ case "empty":
169
+ indicator = pc.yellow("[-]");
170
+ description = pc.dim("(0 rows - empty or RLS)");
171
+ break;
172
+ case "denied":
173
+ indicator = pc.red("[X]");
174
+ description = pc.dim("(access denied)");
175
+ break;
176
+ }
177
+
178
+ console.log(` ${indicator} ${pc.white(table)} ${description}`);
179
+ });
180
+ } else {
181
+ console.log(pc.dim(" No tables found"));
182
+ }
183
+ console.log();
184
+
185
+ console.log(pc.bold("RPCs:"), pc.green(analysis.rpcs.length.toString()));
186
+ if (analysis.rpcFunctions.length > 0) {
187
+ analysis.rpcFunctions.forEach((rpc) => {
188
+ console.log(` * ${pc.white(rpc.name)}`);
189
+ if (rpc.parameters.length > 0) {
190
+ rpc.parameters.forEach((param: RPCParameter) => {
191
+ const required = param.required
192
+ ? pc.red("(required)")
193
+ : pc.dim("(optional)");
194
+ const type = param.format
195
+ ? `${param.type} (${param.format})`
196
+ : param.type;
197
+ console.log(
198
+ ` - ${pc.cyan(param.name)}: ${pc.yellow(type)} ${required}`,
199
+ );
200
+ });
201
+ } else {
202
+ console.log(pc.dim(" No parameters"));
203
+ }
204
+ });
205
+ } else {
206
+ console.log(pc.dim(" No RPCs found"));
207
+ }
208
+ console.log();
209
+ });
210
+
211
+ console.log(pc.bold(pc.cyan("=".repeat(60))));
212
+ console.log();
213
+ }
@@ -0,0 +1,68 @@
1
+ import { dumpTable } from "@supascan/core";
2
+ import type { CLIContext } from "../context";
3
+ import { log } from "../formatters/console";
4
+
5
+ export async function executeDumpCommand(
6
+ ctx: CLIContext,
7
+ options: { dump: string; limit: string },
8
+ ): Promise<void> {
9
+ const parts = options.dump.split(".");
10
+
11
+ if (parts.length === 1) {
12
+ const schema = parts[0];
13
+ if (!schema) {
14
+ log.error("Invalid dump format. Use schema.table or schema");
15
+ process.exit(1);
16
+ }
17
+
18
+ log.info(`Dumping swagger for schema: ${schema}`);
19
+
20
+ const { data, error } = await ctx.client.schema(schema).from("").select();
21
+
22
+ if (error) {
23
+ log.error("Failed to fetch swagger", error.message);
24
+ process.exit(1);
25
+ }
26
+
27
+ console.log(JSON.stringify(data, null, 2));
28
+ return;
29
+ }
30
+
31
+ if (parts.length === 2) {
32
+ const schema = parts[0];
33
+ const table = parts[1];
34
+
35
+ if (!schema || !table) {
36
+ log.error("Invalid dump format. Use schema.table");
37
+ process.exit(1);
38
+ }
39
+
40
+ const limit = parseInt(options.limit);
41
+ if (isNaN(limit) || limit <= 0) {
42
+ log.error("Invalid limit value");
43
+ process.exit(1);
44
+ }
45
+
46
+ log.info(`Dumping table: ${schema}.${table} (limit: ${limit})`);
47
+
48
+ const result = await dumpTable(ctx.client, schema, table, limit);
49
+
50
+ if (!result.success) {
51
+ log.error("Failed to dump table", result.error.message);
52
+ process.exit(1);
53
+ }
54
+
55
+ if (ctx.json) {
56
+ console.log(JSON.stringify(result.value, null, 2));
57
+ } else {
58
+ console.log(
59
+ `\nTable: ${schema}.${table} (${result.value.count} total rows, showing ${result.value.rows.length})\n`,
60
+ );
61
+ console.table(result.value.rows);
62
+ }
63
+ return;
64
+ }
65
+
66
+ log.error("Invalid dump format. Use schema.table or schema");
67
+ process.exit(1);
68
+ }