@youtyan/code-viewer 0.6.8 → 0.6.9
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 +14 -2
- package/dist/code-viewer.js +421 -68
- package/package.json +1 -1
- package/skills/code-viewer-query/SKILL.md +3 -1
- package/web/app.js +10 -4
package/README.md
CHANGED
|
@@ -34,6 +34,8 @@ Requires Node.js 20 or newer when installed from npm. Development uses
|
|
|
34
34
|
viewer.
|
|
35
35
|
- Browse SQLite, PostgreSQL, MySQL, Redis, Elasticsearch, and S3-compatible
|
|
36
36
|
object storage (MinIO, LocalStack) with a built-in datastore viewer.
|
|
37
|
+
Local Supabase CLI (`supabase start`) Postgres projects are auto-discovered
|
|
38
|
+
too, without needing a `docker-compose.yml`.
|
|
37
39
|
- Read the built-in Help page for getting started, the `.code-viewer/`
|
|
38
40
|
project files, AI annotations, datastores, the agent skill, and
|
|
39
41
|
keybindings.
|
|
@@ -42,8 +44,9 @@ Requires Node.js 20 or newer when installed from npm. Development uses
|
|
|
42
44
|
`@youtyan/code-viewer` version and execution origin (npx cache vs
|
|
43
45
|
local), SQLite driver and snapshot store, Git, discovery summary,
|
|
44
46
|
per-source datastore connectivity (each discovered SQLite / docker
|
|
45
|
-
SQL / Redis / Elasticsearch / S3 source gets one row
|
|
46
|
-
minimal-read probe; failure rows include a paste-safe retry
|
|
47
|
+
SQL / Supabase CLI / Redis / Elasticsearch / S3 source gets one row
|
|
48
|
+
with a 2s minimal-read probe; failure rows include a paste-safe retry
|
|
49
|
+
hint),
|
|
47
50
|
Docker / Compose health (config dry-parse + `compose ps` per service),
|
|
48
51
|
and the listening port. Useful when `npx` cache mismatch (e.g.
|
|
49
52
|
`NODE_MODULE_VERSION` errors) needs a remediation hint, or when a
|
|
@@ -219,6 +222,15 @@ Services whose names collide across subdirectories are kept distinct via
|
|
|
219
222
|
`docker:<service>@<relDir>` ids (cwd-direct compose files keep the historical
|
|
220
223
|
`docker:<service>` id for backward compatibility).
|
|
221
224
|
|
|
225
|
+
**Supabase CLI** (`supabase start`) local projects are also auto-discovered,
|
|
226
|
+
even though the Supabase CLI does not write a `docker-compose.yml` into the
|
|
227
|
+
project directory. Any `supabase/config.toml` found by the same recursive scan
|
|
228
|
+
is picked up (`project_id` and `[db] port` are read from it), and the running
|
|
229
|
+
Postgres container is resolved directly via `docker ps` (matched by container
|
|
230
|
+
name and the `com.supabase.cli.project` label) instead of `docker compose ps`.
|
|
231
|
+
Connection defaults to the Supabase CLI's documented local credentials
|
|
232
|
+
(`postgres`/`postgres`/`postgres`).
|
|
233
|
+
|
|
222
234
|
**Redis** support: browse DB 0–15, SCAN keys, and view values per
|
|
223
235
|
type (string/hash/list as dedicated panes, set/zset/stream as JSON views).
|
|
224
236
|
Edit values, delete keys, and create new keys (string/hash/list/set/zset/stream)
|
package/dist/code-viewer.js
CHANGED
|
@@ -9612,7 +9612,7 @@ var init_spawn_runner = () => {};
|
|
|
9612
9612
|
// web-src/server/database/adapters/docker-utils.ts
|
|
9613
9613
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
9614
9614
|
function isDockerComposeServiceUnavailableError(err) {
|
|
9615
|
-
return err instanceof DockerComposeServiceUnavailableError || err instanceof DockerCommandUnavailableError;
|
|
9615
|
+
return err instanceof DockerComposeServiceUnavailableError || err instanceof DockerCommandUnavailableError || err instanceof SupabaseDbContainerUnavailableError;
|
|
9616
9616
|
}
|
|
9617
9617
|
function dockerCommand() {
|
|
9618
9618
|
return commandForExternal("docker");
|
|
@@ -9629,10 +9629,15 @@ function throwIfCachedComposeDockerCommandUnavailable(cwd) {
|
|
|
9629
9629
|
const failure = composePsCache.get(cwd)?.error || "";
|
|
9630
9630
|
throwIfDockerCommandUnavailableResult({ code: 1, stderr: failure });
|
|
9631
9631
|
}
|
|
9632
|
-
function
|
|
9632
|
+
function parseDockerJsonLines(stdout) {
|
|
9633
9633
|
const output = stdout.trim();
|
|
9634
|
-
|
|
9634
|
+
if (!output)
|
|
9635
|
+
return [];
|
|
9636
|
+
return output.startsWith("[") ? JSON.parse(output) : output.split(`
|
|
9635
9637
|
`).filter(Boolean).map((line) => JSON.parse(line));
|
|
9638
|
+
}
|
|
9639
|
+
function parseComposePsOutput(stdout) {
|
|
9640
|
+
const containers = parseDockerJsonLines(stdout);
|
|
9636
9641
|
const byService = new Map;
|
|
9637
9642
|
for (const container of containers) {
|
|
9638
9643
|
if (container.Service && container.Name && container.State === "running") {
|
|
@@ -9651,15 +9656,14 @@ function cacheComposePsFailure(cwd, result, now) {
|
|
|
9651
9656
|
error: stderr
|
|
9652
9657
|
});
|
|
9653
9658
|
}
|
|
9654
|
-
function
|
|
9655
|
-
throwIfAborted(signal,
|
|
9659
|
+
function runDockerCliAsync(args, opts) {
|
|
9660
|
+
throwIfAborted(opts.signal, opts.abortMessage);
|
|
9656
9661
|
if (spawnSyncImpl !== spawnSync2) {
|
|
9657
|
-
const
|
|
9658
|
-
const proc = spawnSyncImpl(command, ["compose", "ps", "--format", "json", "--status", "running"], {
|
|
9662
|
+
const proc = spawnSyncImpl(dockerCommand(), args, {
|
|
9659
9663
|
encoding: "utf8",
|
|
9660
9664
|
timeout: 5000,
|
|
9661
9665
|
stdio: ["ignore", "pipe", "pipe"],
|
|
9662
|
-
cwd
|
|
9666
|
+
...opts.cwd ? { cwd: opts.cwd } : {}
|
|
9663
9667
|
});
|
|
9664
9668
|
return Promise.resolve({
|
|
9665
9669
|
stdout: String(proc.stdout || ""),
|
|
@@ -9670,16 +9674,24 @@ ${proc.error.message}` : ""}`,
|
|
|
9670
9674
|
}
|
|
9671
9675
|
return spawnTextAsync({
|
|
9672
9676
|
command: dockerCommand(),
|
|
9673
|
-
args
|
|
9674
|
-
cwd,
|
|
9677
|
+
args,
|
|
9678
|
+
...opts.cwd ? { cwd: opts.cwd } : {},
|
|
9675
9679
|
timeoutMs: 5000,
|
|
9676
|
-
signal,
|
|
9680
|
+
signal: opts.signal,
|
|
9677
9681
|
killSignal: "SIGKILL",
|
|
9678
|
-
abortMessage:
|
|
9679
|
-
timeoutMessage:
|
|
9682
|
+
abortMessage: opts.abortMessage,
|
|
9683
|
+
timeoutMessage: opts.timeoutMessage,
|
|
9680
9684
|
rejectOnError: false
|
|
9681
9685
|
});
|
|
9682
9686
|
}
|
|
9687
|
+
function runComposePsAsync(cwd, signal) {
|
|
9688
|
+
return runDockerCliAsync(["compose", "ps", "--format", "json", "--status", "running"], {
|
|
9689
|
+
cwd,
|
|
9690
|
+
signal,
|
|
9691
|
+
abortMessage: "docker compose ps aborted",
|
|
9692
|
+
timeoutMessage: "docker compose ps timed out"
|
|
9693
|
+
});
|
|
9694
|
+
}
|
|
9683
9695
|
async function resolveRunningComposeContainerNameAsync(serviceName, cwd, signal) {
|
|
9684
9696
|
throwIfAborted(signal, "docker compose ps aborted");
|
|
9685
9697
|
const now = Date.now();
|
|
@@ -9777,7 +9789,128 @@ async function resolveRunningComposeContainerNameOrThrowAsync(serviceName, cwd,
|
|
|
9777
9789
|
}
|
|
9778
9790
|
return containerName;
|
|
9779
9791
|
}
|
|
9780
|
-
|
|
9792
|
+
function supabaseDbContainerName(projectId) {
|
|
9793
|
+
return `supabase_db_${projectId}`;
|
|
9794
|
+
}
|
|
9795
|
+
function parseDockerPsOutput(stdout) {
|
|
9796
|
+
return parseDockerJsonLines(stdout);
|
|
9797
|
+
}
|
|
9798
|
+
function runDockerPsAsync(args, signal) {
|
|
9799
|
+
return runDockerCliAsync(args, {
|
|
9800
|
+
signal,
|
|
9801
|
+
abortMessage: "docker ps aborted",
|
|
9802
|
+
timeoutMessage: "docker ps timed out"
|
|
9803
|
+
});
|
|
9804
|
+
}
|
|
9805
|
+
async function resolveRunningSupabaseDbContainerAsync(projectId, signal) {
|
|
9806
|
+
throwIfAborted(signal, "docker ps aborted");
|
|
9807
|
+
const containerName = supabaseDbContainerName(projectId);
|
|
9808
|
+
const now = Date.now();
|
|
9809
|
+
const cached = supabaseContainerCache.get(projectId);
|
|
9810
|
+
if (cached) {
|
|
9811
|
+
if (cached.containerName && cached.positiveExpiresAt > now) {
|
|
9812
|
+
return cached.containerName;
|
|
9813
|
+
}
|
|
9814
|
+
if (!cached.containerName && cached.negativeExpiresAt > now) {
|
|
9815
|
+
return null;
|
|
9816
|
+
}
|
|
9817
|
+
}
|
|
9818
|
+
let pending = supabaseContainerPending.get(projectId);
|
|
9819
|
+
if (!pending) {
|
|
9820
|
+
const controller = new AbortController;
|
|
9821
|
+
pending = {
|
|
9822
|
+
controller,
|
|
9823
|
+
refs: 0,
|
|
9824
|
+
done: false,
|
|
9825
|
+
promise: (async () => {
|
|
9826
|
+
const startedAt = Date.now();
|
|
9827
|
+
const cacheMiss = (negativeTtlMs) => {
|
|
9828
|
+
supabaseContainerCache.set(projectId, {
|
|
9829
|
+
containerName: null,
|
|
9830
|
+
positiveExpiresAt: startedAt,
|
|
9831
|
+
negativeExpiresAt: startedAt + negativeTtlMs
|
|
9832
|
+
});
|
|
9833
|
+
return null;
|
|
9834
|
+
};
|
|
9835
|
+
let proc;
|
|
9836
|
+
try {
|
|
9837
|
+
proc = await runDockerPsAsync([
|
|
9838
|
+
"ps",
|
|
9839
|
+
"--filter",
|
|
9840
|
+
`name=^/${containerName}$`,
|
|
9841
|
+
"--filter",
|
|
9842
|
+
`label=com.supabase.cli.project=${projectId}`,
|
|
9843
|
+
"--filter",
|
|
9844
|
+
"status=running",
|
|
9845
|
+
"--format",
|
|
9846
|
+
"json"
|
|
9847
|
+
], controller.signal);
|
|
9848
|
+
} catch (err) {
|
|
9849
|
+
if (isAbortLikeError(err, controller.signal))
|
|
9850
|
+
throw err;
|
|
9851
|
+
return cacheMiss(SUPABASE_CONTAINER_NEGATIVE_TTL_MS);
|
|
9852
|
+
}
|
|
9853
|
+
if (proc.code !== 0) {
|
|
9854
|
+
throwIfDockerCommandUnavailableResult(proc);
|
|
9855
|
+
return cacheMiss(SUPABASE_CONTAINER_NEGATIVE_TTL_MS);
|
|
9856
|
+
}
|
|
9857
|
+
let containers;
|
|
9858
|
+
try {
|
|
9859
|
+
containers = parseDockerPsOutput(proc.stdout);
|
|
9860
|
+
} catch {
|
|
9861
|
+
return cacheMiss(SUPABASE_CONTAINER_NEGATIVE_TTL_MS);
|
|
9862
|
+
}
|
|
9863
|
+
const match = containers.some((c) => (c.Names || "").replace(/^\//, "") === containerName);
|
|
9864
|
+
if (!match)
|
|
9865
|
+
return cacheMiss(SUPABASE_CONTAINER_NEGATIVE_TTL_MS);
|
|
9866
|
+
supabaseContainerCache.set(projectId, {
|
|
9867
|
+
containerName,
|
|
9868
|
+
positiveExpiresAt: startedAt + SUPABASE_CONTAINER_POSITIVE_TTL_MS,
|
|
9869
|
+
negativeExpiresAt: startedAt
|
|
9870
|
+
});
|
|
9871
|
+
return containerName;
|
|
9872
|
+
})().finally(() => {
|
|
9873
|
+
if (supabaseContainerPending.get(projectId) === pending) {
|
|
9874
|
+
supabaseContainerPending.delete(projectId);
|
|
9875
|
+
}
|
|
9876
|
+
if (pending)
|
|
9877
|
+
pending.done = true;
|
|
9878
|
+
})
|
|
9879
|
+
};
|
|
9880
|
+
supabaseContainerPending.set(projectId, pending);
|
|
9881
|
+
}
|
|
9882
|
+
pending.refs++;
|
|
9883
|
+
let released = false;
|
|
9884
|
+
const release = () => {
|
|
9885
|
+
if (released)
|
|
9886
|
+
return;
|
|
9887
|
+
released = true;
|
|
9888
|
+
pending.refs--;
|
|
9889
|
+
if (pending.refs <= 0 && !pending.done) {
|
|
9890
|
+
pending.controller.abort();
|
|
9891
|
+
}
|
|
9892
|
+
};
|
|
9893
|
+
signal?.addEventListener("abort", release, { once: true });
|
|
9894
|
+
if (signal?.aborted) {
|
|
9895
|
+
signal.removeEventListener("abort", release);
|
|
9896
|
+
release();
|
|
9897
|
+
throwIfAborted(signal, "docker ps aborted");
|
|
9898
|
+
}
|
|
9899
|
+
try {
|
|
9900
|
+
return await pending.promise;
|
|
9901
|
+
} finally {
|
|
9902
|
+
signal?.removeEventListener("abort", release);
|
|
9903
|
+
release();
|
|
9904
|
+
}
|
|
9905
|
+
}
|
|
9906
|
+
async function resolveRunningSupabaseDbContainerOrThrowAsync(projectId, signal) {
|
|
9907
|
+
const containerName = await resolveRunningSupabaseDbContainerAsync(projectId, signal);
|
|
9908
|
+
if (!containerName) {
|
|
9909
|
+
throw new SupabaseDbContainerUnavailableError(projectId);
|
|
9910
|
+
}
|
|
9911
|
+
return containerName;
|
|
9912
|
+
}
|
|
9913
|
+
var COMPOSE_CONTAINER_NAME_POSITIVE_TTL_MS = 30000, COMPOSE_CONTAINER_NAME_NEGATIVE_TTL_MS = 3000, COMPOSE_PS_FAILURE_TTL_MS = 15000, composePsCache, composePsPending, spawnSyncImpl, DockerComposeServiceUnavailableError, DockerCommandUnavailableError, SupabaseDbContainerUnavailableError, SUPABASE_CONTAINER_POSITIVE_TTL_MS = 15000, SUPABASE_CONTAINER_NEGATIVE_TTL_MS = 3000, supabaseContainerCache, supabaseContainerPending;
|
|
9781
9914
|
var init_docker_utils = __esm(() => {
|
|
9782
9915
|
init_command_resolver();
|
|
9783
9916
|
init_spawn_runner();
|
|
@@ -9802,6 +9935,17 @@ var init_docker_utils = __esm(() => {
|
|
|
9802
9935
|
this.name = "DockerCommandUnavailableError";
|
|
9803
9936
|
}
|
|
9804
9937
|
};
|
|
9938
|
+
SupabaseDbContainerUnavailableError = class SupabaseDbContainerUnavailableError extends Error {
|
|
9939
|
+
projectId;
|
|
9940
|
+
status = 503;
|
|
9941
|
+
constructor(projectId) {
|
|
9942
|
+
super(`Supabase local DB container for project "${projectId}" is not running. Start it with: supabase start`);
|
|
9943
|
+
this.name = "SupabaseDbContainerUnavailableError";
|
|
9944
|
+
this.projectId = projectId;
|
|
9945
|
+
}
|
|
9946
|
+
};
|
|
9947
|
+
supabaseContainerCache = new Map;
|
|
9948
|
+
supabaseContainerPending = new Map;
|
|
9805
9949
|
});
|
|
9806
9950
|
|
|
9807
9951
|
// web-src/server/database/adapters/sql-capture.ts
|
|
@@ -10674,13 +10818,9 @@ async function listDockerSchemasAsync(serviceName, kind, env, cwd, overrideDatab
|
|
|
10674
10818
|
throwIfCachedComposeDockerCommandUnavailable(cwd);
|
|
10675
10819
|
return setDockerSchemasCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
10676
10820
|
}
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
user,
|
|
10681
|
-
password,
|
|
10682
|
-
database
|
|
10683
|
-
};
|
|
10821
|
+
return fetchPostgresSchemasViaContainerAsync({ kind, containerName, user, password, database }, cacheKey, now, signal);
|
|
10822
|
+
}
|
|
10823
|
+
async function fetchPostgresSchemasViaContainerAsync(config, cacheKey, now, signal) {
|
|
10684
10824
|
try {
|
|
10685
10825
|
const sql = `SELECT schema_name FROM information_schema.schemata WHERE schema_name NOT IN ('pg_catalog', 'information_schema') AND schema_name NOT LIKE 'pg_toast%' AND schema_name NOT LIKE 'pg_temp_%' AND schema_name NOT LIKE 'pg_toast_temp_%' AND has_schema_privilege(schema_name, 'USAGE') ORDER BY CASE WHEN schema_name = 'public' THEN 0 ELSE 1 END, schema_name`;
|
|
10686
10826
|
const result = await execInContainerAsync(config, sql, 1e4, signal);
|
|
@@ -10699,6 +10839,38 @@ async function listDockerSchemasAsync(serviceName, kind, env, cwd, overrideDatab
|
|
|
10699
10839
|
return setDockerSchemasCache(cacheKey, ["public"], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
10700
10840
|
}
|
|
10701
10841
|
}
|
|
10842
|
+
function supabaseSchemasCacheKey(projectId) {
|
|
10843
|
+
return `supabase\x00${projectId}`;
|
|
10844
|
+
}
|
|
10845
|
+
async function listSupabaseSchemasAsync(projectId, signal) {
|
|
10846
|
+
const cacheKey = supabaseSchemasCacheKey(projectId);
|
|
10847
|
+
const now = Date.now();
|
|
10848
|
+
const cached = dockerSchemasCache.get(cacheKey);
|
|
10849
|
+
if (cached && cached.expiresAt > now)
|
|
10850
|
+
return [...cached.value];
|
|
10851
|
+
const containerName = await resolveRunningSupabaseDbContainerAsync(projectId, signal);
|
|
10852
|
+
if (!containerName) {
|
|
10853
|
+
return setDockerSchemasCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
10854
|
+
}
|
|
10855
|
+
return fetchPostgresSchemasViaContainerAsync({
|
|
10856
|
+
kind: "postgresql",
|
|
10857
|
+
containerName,
|
|
10858
|
+
user: SUPABASE_LOCAL_DB_USER,
|
|
10859
|
+
password: SUPABASE_LOCAL_DB_PASSWORD,
|
|
10860
|
+
database: SUPABASE_LOCAL_DB_NAME
|
|
10861
|
+
}, cacheKey, now, signal);
|
|
10862
|
+
}
|
|
10863
|
+
async function openSupabaseDockerAdapterAsync(projectId, schema, signal) {
|
|
10864
|
+
const containerName = await resolveRunningSupabaseDbContainerOrThrowAsync(projectId, signal);
|
|
10865
|
+
return createDockerAdapter({
|
|
10866
|
+
kind: "postgresql",
|
|
10867
|
+
containerName,
|
|
10868
|
+
user: SUPABASE_LOCAL_DB_USER,
|
|
10869
|
+
password: SUPABASE_LOCAL_DB_PASSWORD,
|
|
10870
|
+
database: SUPABASE_LOCAL_DB_NAME,
|
|
10871
|
+
...schema ? { schema } : {}
|
|
10872
|
+
});
|
|
10873
|
+
}
|
|
10702
10874
|
async function openDockerAdapterAsync(serviceName, kind, env, cwd, overrideDatabase, schema, signal) {
|
|
10703
10875
|
const containerName = await resolveRunningComposeContainerNameOrThrowAsync(serviceName, cwd, signal);
|
|
10704
10876
|
const user = env.POSTGRES_USER || env.MYSQL_USER || env.MARIADB_USER || (kind === "postgresql" ? "postgres" : "root");
|
|
@@ -10713,7 +10885,7 @@ async function openDockerAdapterAsync(serviceName, kind, env, cwd, overrideDatab
|
|
|
10713
10885
|
...kind === "postgresql" && schema ? { schema } : {}
|
|
10714
10886
|
});
|
|
10715
10887
|
}
|
|
10716
|
-
var COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000, DOCKER_DATABASES_POSITIVE_TTL_MS = 15000, DOCKER_DATABASES_NEGATIVE_TTL_MS = 3000, dockerDatabasesCache, dockerSchemasCache, spawnSyncImpl2, PG_RECORD_SEPARATOR = "\x1E", MYSQL_SPATIAL_TYPES;
|
|
10888
|
+
var COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000, DOCKER_DATABASES_POSITIVE_TTL_MS = 15000, DOCKER_DATABASES_NEGATIVE_TTL_MS = 3000, dockerDatabasesCache, dockerSchemasCache, spawnSyncImpl2, PG_RECORD_SEPARATOR = "\x1E", MYSQL_SPATIAL_TYPES, SUPABASE_LOCAL_DB_USER = "postgres", SUPABASE_LOCAL_DB_PASSWORD = "postgres", SUPABASE_LOCAL_DB_NAME = "postgres";
|
|
10717
10889
|
var init_docker = __esm(() => {
|
|
10718
10890
|
init_mutate();
|
|
10719
10891
|
init_sql_snapshot();
|
|
@@ -13599,6 +13771,39 @@ async function pathExistsAsync(path) {
|
|
|
13599
13771
|
return false;
|
|
13600
13772
|
}
|
|
13601
13773
|
}
|
|
13774
|
+
async function walkForMarkerFileAsync(dir, depth, omitSet, hasCapacity, visitDir, signal) {
|
|
13775
|
+
if (signal?.aborted || !hasCapacity())
|
|
13776
|
+
return;
|
|
13777
|
+
if (depth > MAX_SCAN_DEPTH)
|
|
13778
|
+
return;
|
|
13779
|
+
await visitDir(dir);
|
|
13780
|
+
if (signal?.aborted || !hasCapacity())
|
|
13781
|
+
return;
|
|
13782
|
+
let entries;
|
|
13783
|
+
try {
|
|
13784
|
+
entries = await readdir(dir);
|
|
13785
|
+
} catch {
|
|
13786
|
+
return;
|
|
13787
|
+
}
|
|
13788
|
+
for (const entry of entries) {
|
|
13789
|
+
if (signal?.aborted || !hasCapacity())
|
|
13790
|
+
return;
|
|
13791
|
+
if (omitSet.has(entry.toLowerCase()))
|
|
13792
|
+
continue;
|
|
13793
|
+
const full = join8(dir, entry);
|
|
13794
|
+
let entryStat;
|
|
13795
|
+
try {
|
|
13796
|
+
entryStat = await lstat(full);
|
|
13797
|
+
} catch {
|
|
13798
|
+
continue;
|
|
13799
|
+
}
|
|
13800
|
+
if (entryStat.isSymbolicLink())
|
|
13801
|
+
continue;
|
|
13802
|
+
if (entryStat.isDirectory()) {
|
|
13803
|
+
await walkForMarkerFileAsync(full, depth + 1, omitSet, hasCapacity, visitDir, signal);
|
|
13804
|
+
}
|
|
13805
|
+
}
|
|
13806
|
+
}
|
|
13602
13807
|
async function discoverDockerDatabasesAsync(cwd, omitDirNames = [], signal) {
|
|
13603
13808
|
const cacheKey = dockerDiscoveryCacheKey(cwd, omitDirNames);
|
|
13604
13809
|
const now = Date.now();
|
|
@@ -13610,13 +13815,7 @@ async function discoverDockerDatabasesAsync(cwd, omitDirNames = [], signal) {
|
|
|
13610
13815
|
const omitSet = new Set(omitDirNames.map((d) => d.toLowerCase()));
|
|
13611
13816
|
omitSet.add(".git");
|
|
13612
13817
|
omitSet.add("node_modules");
|
|
13613
|
-
|
|
13614
|
-
if (signal?.aborted)
|
|
13615
|
-
return;
|
|
13616
|
-
if (results.length >= MAX_DOCKER_SERVICES)
|
|
13617
|
-
return;
|
|
13618
|
-
if (depth > MAX_SCAN_DEPTH)
|
|
13619
|
-
return;
|
|
13818
|
+
await walkForMarkerFileAsync(cwd, 0, omitSet, () => results.length < MAX_DOCKER_SERVICES, async (dir) => {
|
|
13620
13819
|
for (const filename of COMPOSE_FILENAMES) {
|
|
13621
13820
|
const filepath = join8(dir, filename);
|
|
13622
13821
|
if (await pathExistsAsync(filepath)) {
|
|
@@ -13624,37 +13823,7 @@ async function discoverDockerDatabasesAsync(cwd, omitDirNames = [], signal) {
|
|
|
13624
13823
|
break;
|
|
13625
13824
|
}
|
|
13626
13825
|
}
|
|
13627
|
-
|
|
13628
|
-
return;
|
|
13629
|
-
if (results.length >= MAX_DOCKER_SERVICES)
|
|
13630
|
-
return;
|
|
13631
|
-
let entries;
|
|
13632
|
-
try {
|
|
13633
|
-
entries = await readdir(dir);
|
|
13634
|
-
} catch {
|
|
13635
|
-
return;
|
|
13636
|
-
}
|
|
13637
|
-
for (const entry of entries) {
|
|
13638
|
-
if (signal?.aborted)
|
|
13639
|
-
return;
|
|
13640
|
-
if (results.length >= MAX_DOCKER_SERVICES)
|
|
13641
|
-
return;
|
|
13642
|
-
if (omitSet.has(entry.toLowerCase()))
|
|
13643
|
-
continue;
|
|
13644
|
-
const full = join8(dir, entry);
|
|
13645
|
-
let entryStat;
|
|
13646
|
-
try {
|
|
13647
|
-
entryStat = await lstat(full);
|
|
13648
|
-
} catch {
|
|
13649
|
-
continue;
|
|
13650
|
-
}
|
|
13651
|
-
if (entryStat.isSymbolicLink())
|
|
13652
|
-
continue;
|
|
13653
|
-
if (entryStat.isDirectory())
|
|
13654
|
-
await scan(full, depth + 1);
|
|
13655
|
-
}
|
|
13656
|
-
}
|
|
13657
|
-
await scan(cwd, 0);
|
|
13826
|
+
}, signal);
|
|
13658
13827
|
if (signal?.aborted)
|
|
13659
13828
|
return cloneDockerDiscoveryResult(results);
|
|
13660
13829
|
if (results.length >= MAX_DOCKER_SERVICES) {
|
|
@@ -13749,7 +13918,120 @@ function isSafeDockerDatabaseName(value) {
|
|
|
13749
13918
|
return false;
|
|
13750
13919
|
return /^[A-Za-z0-9_$.-]+$/.test(value);
|
|
13751
13920
|
}
|
|
13752
|
-
|
|
13921
|
+
function cloneSupabaseDiscoveryResult(result) {
|
|
13922
|
+
return result.map((entry) => ({ ...entry }));
|
|
13923
|
+
}
|
|
13924
|
+
function parseSupabaseConfigToml(content) {
|
|
13925
|
+
let section = null;
|
|
13926
|
+
let projectId = null;
|
|
13927
|
+
let dbPort = null;
|
|
13928
|
+
for (const rawLine of content.split(`
|
|
13929
|
+
`)) {
|
|
13930
|
+
const line = rawLine.trim();
|
|
13931
|
+
if (!line || line.startsWith("#"))
|
|
13932
|
+
continue;
|
|
13933
|
+
const sectionMatch = line.match(/^\[([^\]]+)\]$/);
|
|
13934
|
+
if (sectionMatch) {
|
|
13935
|
+
section = sectionMatch[1];
|
|
13936
|
+
continue;
|
|
13937
|
+
}
|
|
13938
|
+
const kvMatch = line.match(/^([A-Za-z_][A-Za-z0-9_.-]*)\s*=\s*(.+)$/);
|
|
13939
|
+
if (!kvMatch)
|
|
13940
|
+
continue;
|
|
13941
|
+
const value = stripScalarSyntax(kvMatch[2]);
|
|
13942
|
+
if (section === null && kvMatch[1] === "project_id") {
|
|
13943
|
+
projectId = value;
|
|
13944
|
+
} else if (section === "db" && kvMatch[1] === "port") {
|
|
13945
|
+
dbPort = value;
|
|
13946
|
+
}
|
|
13947
|
+
}
|
|
13948
|
+
if (!projectId || !isSafeDockerServiceName(projectId))
|
|
13949
|
+
return null;
|
|
13950
|
+
return {
|
|
13951
|
+
projectId,
|
|
13952
|
+
dbPort: dbPort && /^\d+$/.test(dbPort) ? dbPort : DEFAULT_SUPABASE_DB_PORT
|
|
13953
|
+
};
|
|
13954
|
+
}
|
|
13955
|
+
async function discoverSupabaseCliProjectsAsync(cwd, omitDirNames = [], signal) {
|
|
13956
|
+
const cacheKey = discoveryCacheKey(cwd, omitDirNames);
|
|
13957
|
+
const now = Date.now();
|
|
13958
|
+
const cached = supabaseDiscoveryCache.get(cacheKey);
|
|
13959
|
+
if (cached && cached.expiresAt > now) {
|
|
13960
|
+
return cloneSupabaseDiscoveryResult(cached.result);
|
|
13961
|
+
}
|
|
13962
|
+
const omitSet = new Set(omitDirNames.map((d) => d.toLowerCase()));
|
|
13963
|
+
omitSet.add(".git");
|
|
13964
|
+
omitSet.add("node_modules");
|
|
13965
|
+
const results = [];
|
|
13966
|
+
await walkForMarkerFileAsync(cwd, 0, omitSet, () => results.length < MAX_SUPABASE_PROJECTS, async (dir) => {
|
|
13967
|
+
const configPath = join8(dir, "supabase", "config.toml");
|
|
13968
|
+
if (!await pathExistsAsync(configPath))
|
|
13969
|
+
return;
|
|
13970
|
+
try {
|
|
13971
|
+
const content = await readFile2(configPath, "utf-8");
|
|
13972
|
+
const parsed = parseSupabaseConfigToml(content);
|
|
13973
|
+
if (!parsed)
|
|
13974
|
+
return;
|
|
13975
|
+
const relDir = relative3(cwd, dir);
|
|
13976
|
+
const isRoot = relDir === "" || relDir === ".";
|
|
13977
|
+
const relDirSlash = relDir.replace(/\\/g, "/");
|
|
13978
|
+
const id = isRoot ? `supabase:${parsed.projectId}` : `supabase:${parsed.projectId}@${encodeURIComponent(relDirSlash)}`;
|
|
13979
|
+
const labelPath = isRoot ? "" : ` — ${relDirSlash}`;
|
|
13980
|
+
results.push({
|
|
13981
|
+
id,
|
|
13982
|
+
path: isRoot ? "supabase/config.toml" : `${relDirSlash}/supabase/config.toml`,
|
|
13983
|
+
name: `${parsed.projectId} (Supabase CLI, postgres@127.0.0.1:${parsed.dbPort}/postgres${labelPath})`,
|
|
13984
|
+
sizeBytes: 0,
|
|
13985
|
+
kind: "postgresql",
|
|
13986
|
+
projectId: parsed.projectId,
|
|
13987
|
+
relDirSlash,
|
|
13988
|
+
dbPort: parsed.dbPort
|
|
13989
|
+
});
|
|
13990
|
+
} catch {}
|
|
13991
|
+
}, signal);
|
|
13992
|
+
if (signal?.aborted)
|
|
13993
|
+
return cloneSupabaseDiscoveryResult(results);
|
|
13994
|
+
supabaseDiscoveryCache.set(cacheKey, {
|
|
13995
|
+
expiresAt: now + SUPABASE_DISCOVERY_TTL_MS,
|
|
13996
|
+
result: cloneSupabaseDiscoveryResult(results)
|
|
13997
|
+
});
|
|
13998
|
+
return cloneSupabaseDiscoveryResult(results);
|
|
13999
|
+
}
|
|
14000
|
+
function parseSupabaseDbId(dbId) {
|
|
14001
|
+
if (!dbId.startsWith("supabase:"))
|
|
14002
|
+
return null;
|
|
14003
|
+
const rest = dbId.slice("supabase:".length);
|
|
14004
|
+
if (!rest)
|
|
14005
|
+
return null;
|
|
14006
|
+
const atIdx = rest.indexOf("@");
|
|
14007
|
+
let projectId;
|
|
14008
|
+
let relDir = "";
|
|
14009
|
+
if (atIdx >= 0) {
|
|
14010
|
+
if (rest.indexOf("@", atIdx + 1) >= 0)
|
|
14011
|
+
return null;
|
|
14012
|
+
projectId = rest.slice(0, atIdx);
|
|
14013
|
+
try {
|
|
14014
|
+
relDir = decodeURIComponent(rest.slice(atIdx + 1));
|
|
14015
|
+
} catch {
|
|
14016
|
+
return null;
|
|
14017
|
+
}
|
|
14018
|
+
if (!isSafeDockerRelDir(relDir))
|
|
14019
|
+
return null;
|
|
14020
|
+
} else {
|
|
14021
|
+
projectId = rest;
|
|
14022
|
+
}
|
|
14023
|
+
if (!isSafeDockerServiceName(projectId))
|
|
14024
|
+
return null;
|
|
14025
|
+
return { projectId, relDir };
|
|
14026
|
+
}
|
|
14027
|
+
async function findSupabaseCliProjectByDbIdAsync(cwd, dbId, omitDirNames, signal) {
|
|
14028
|
+
const parsed = parseSupabaseDbId(dbId);
|
|
14029
|
+
if (!parsed)
|
|
14030
|
+
return null;
|
|
14031
|
+
const projects = await discoverSupabaseCliProjectsAsync(cwd, omitDirNames, signal);
|
|
14032
|
+
return projects.find((p) => p.projectId === parsed.projectId && p.relDirSlash === parsed.relDir) || null;
|
|
14033
|
+
}
|
|
14034
|
+
var SQLITE_EXTENSIONS, SQLITE_MAGIC = "SQLite format 3\x00", MAX_SCAN_DEPTH = 3, MAX_ENTRIES = 50, DOCKER_DISCOVERY_TTL_MS = 5000, SQLITE_DISCOVERY_TTL_MS = 5000, sqliteDiscoveryCache, COMPOSE_FILENAMES, MAX_DOCKER_SERVICES = 30, dockerDiscoveryCache, MAX_SUPABASE_PROJECTS = 30, SUPABASE_DISCOVERY_TTL_MS = 5000, DEFAULT_SUPABASE_DB_PORT = "54322", supabaseDiscoveryCache;
|
|
13753
14035
|
var init_discovery = __esm(() => {
|
|
13754
14036
|
SQLITE_EXTENSIONS = new Set([".db", ".sqlite", ".sqlite3", ".s3db"]);
|
|
13755
14037
|
sqliteDiscoveryCache = new Map;
|
|
@@ -13760,6 +14042,7 @@ var init_discovery = __esm(() => {
|
|
|
13760
14042
|
"compose.yaml"
|
|
13761
14043
|
];
|
|
13762
14044
|
dockerDiscoveryCache = new Map;
|
|
14045
|
+
supabaseDiscoveryCache = new Map;
|
|
13763
14046
|
});
|
|
13764
14047
|
|
|
13765
14048
|
// web-src/core/id.ts
|
|
@@ -16754,8 +17037,9 @@ function sanitizeCssSize(v) {
|
|
|
16754
17037
|
return isValidCssSize(v) ? v : undefined;
|
|
16755
17038
|
}
|
|
16756
17039
|
function isToolInternalDbId(dbId) {
|
|
16757
|
-
if (!dbId || dbId.startsWith("docker:"))
|
|
17040
|
+
if (!dbId || dbId.startsWith("docker:") || dbId.startsWith("supabase:")) {
|
|
16758
17041
|
return false;
|
|
17042
|
+
}
|
|
16759
17043
|
return dbId.split(/[\\/]+/).some((part) => part.toLowerCase() === ".code-viewer");
|
|
16760
17044
|
}
|
|
16761
17045
|
function sanitizeRedis(v) {
|
|
@@ -16934,6 +17218,11 @@ async function getAdapter(r, _cwd, signal) {
|
|
|
16934
17218
|
const cacheKey = r.schema ? `${r.dbId}\x00schema=${r.schema}` : r.dbId;
|
|
16935
17219
|
return dockerAdapterCache.getOrOpenAsync(cacheKey, () => openDockerAdapterAsync(docker.serviceName, docker.kind, docker.env, docker.composeDir, docker.database, r.schema, signal));
|
|
16936
17220
|
}
|
|
17221
|
+
if (r.supabase) {
|
|
17222
|
+
const projectId = r.supabase.projectId;
|
|
17223
|
+
const cacheKey = r.schema ? `${r.dbId}\x00schema=${r.schema}` : r.dbId;
|
|
17224
|
+
return dockerAdapterCache.getOrOpenAsync(cacheKey, () => openSupabaseDockerAdapterAsync(projectId, r.schema, signal));
|
|
17225
|
+
}
|
|
16937
17226
|
return getConnection(r.resolved);
|
|
16938
17227
|
}
|
|
16939
17228
|
function sanitizeFilename(name) {
|
|
@@ -16960,9 +17249,35 @@ async function resolvePostgresSchema(info, requestedSchema, signal) {
|
|
|
16960
17249
|
return "public";
|
|
16961
17250
|
return schemas[0] || "public";
|
|
16962
17251
|
}
|
|
17252
|
+
async function resolveSupabaseSchema(info, requestedSchema, signal) {
|
|
17253
|
+
if (requestedSchema)
|
|
17254
|
+
return requestedSchema;
|
|
17255
|
+
const schemas = await listSupabaseSchemasAsync(info.projectId, signal);
|
|
17256
|
+
if (schemas.includes("public"))
|
|
17257
|
+
return "public";
|
|
17258
|
+
return schemas[0] || "public";
|
|
17259
|
+
}
|
|
16963
17260
|
async function resolveDb(cwd, dbParam, omitDirNames, schemaParam, signal) {
|
|
16964
17261
|
if (!dbParam)
|
|
16965
17262
|
return textError("missing db parameter", 400);
|
|
17263
|
+
if (dbParam.startsWith("supabase:")) {
|
|
17264
|
+
const parsed = parseSupabaseDbId(dbParam);
|
|
17265
|
+
if (!parsed)
|
|
17266
|
+
return textError("invalid supabase db id", 400);
|
|
17267
|
+
const info = await findSupabaseCliProjectByDbIdAsync(cwd, dbParam, omitDirNames, signal);
|
|
17268
|
+
if (!info)
|
|
17269
|
+
return textError("supabase project not found", 404);
|
|
17270
|
+
const requestedSchema = normalizeSchemaParam(schemaParam);
|
|
17271
|
+
if (requestedSchema instanceof Response)
|
|
17272
|
+
return requestedSchema;
|
|
17273
|
+
const schema = await resolveSupabaseSchema(info, requestedSchema, signal);
|
|
17274
|
+
return {
|
|
17275
|
+
resolved: dbParam,
|
|
17276
|
+
dbId: dbParam,
|
|
17277
|
+
supabase: info,
|
|
17278
|
+
...schema ? { schema } : {}
|
|
17279
|
+
};
|
|
17280
|
+
}
|
|
16966
17281
|
if (dbParam.startsWith("docker:")) {
|
|
16967
17282
|
const parsed = parseDockerDbId(dbParam);
|
|
16968
17283
|
if (!parsed)
|
|
@@ -17049,17 +17364,22 @@ async function expandDockerServicesForFiles(dockerServices, listDockerDatabases,
|
|
|
17049
17364
|
}
|
|
17050
17365
|
async function createDbFilesResponse(cwd, omitDirNames, signal, deps = DEFAULT_DB_FILE_DISCOVERY_DEPS) {
|
|
17051
17366
|
ensureInit();
|
|
17052
|
-
const [sqliteSettled, dockerSettled] = await Promise.allSettled([
|
|
17367
|
+
const [sqliteSettled, dockerSettled, supabaseSettled] = await Promise.allSettled([
|
|
17053
17368
|
deps.discoverSqliteFiles(cwd, omitDirNames, signal),
|
|
17054
|
-
deps.discoverDockerDatabases(cwd, omitDirNames, signal)
|
|
17369
|
+
deps.discoverDockerDatabases(cwd, omitDirNames, signal),
|
|
17370
|
+
deps.discoverSupabaseCliProjects(cwd, omitDirNames, signal)
|
|
17055
17371
|
]);
|
|
17056
17372
|
if (sqliteSettled.status === "rejected") {
|
|
17057
17373
|
throw sqliteSettled.reason;
|
|
17058
17374
|
}
|
|
17375
|
+
if (supabaseSettled.status === "rejected") {
|
|
17376
|
+
throw supabaseSettled.reason;
|
|
17377
|
+
}
|
|
17059
17378
|
if (dockerSettled.status === "rejected" && isAbortLikeError(dockerSettled.reason, signal)) {
|
|
17060
17379
|
throw dockerSettled.reason;
|
|
17061
17380
|
}
|
|
17062
17381
|
const sqliteFiles = sqliteSettled.value;
|
|
17382
|
+
const supabaseProjects = supabaseSettled.value;
|
|
17063
17383
|
const dockerServices = dockerSettled.status === "fulfilled" ? dockerSettled.value : [];
|
|
17064
17384
|
const dockerErrors = [];
|
|
17065
17385
|
if (dockerSettled.status === "rejected") {
|
|
@@ -17077,7 +17397,8 @@ async function createDbFilesResponse(cwd, omitDirNames, signal, deps = DEFAULT_D
|
|
|
17077
17397
|
sizeBytes: f.sizeBytes,
|
|
17078
17398
|
kind: "sqlite"
|
|
17079
17399
|
})),
|
|
17080
|
-
...dockerEntries.map(toFileInfo)
|
|
17400
|
+
...dockerEntries.map(toFileInfo),
|
|
17401
|
+
...supabaseProjects.map(toFileInfo)
|
|
17081
17402
|
],
|
|
17082
17403
|
...dockerTruncated ? { truncated: true } : {},
|
|
17083
17404
|
...dockerErrors.length > 0 ? { dockerError: dockerErrors.join("; ") } : {}
|
|
@@ -17091,6 +17412,17 @@ async function createDbSchemasResponse(cwd, dbParam, schemaParam, omitDirNames,
|
|
|
17091
17412
|
const r = await resolveDb(cwd, dbParam, omitDirNames, schemaParam, signal);
|
|
17092
17413
|
if (r instanceof Response)
|
|
17093
17414
|
return { ok: false, response: r };
|
|
17415
|
+
if (r.supabase) {
|
|
17416
|
+
const projectId = r.supabase.projectId;
|
|
17417
|
+
const { result: schemas2, executedSql: executedSql2 } = await captureSql(() => listSupabaseSchemasAsync(projectId, signal));
|
|
17418
|
+
const body2 = {
|
|
17419
|
+
dbId: r.dbId,
|
|
17420
|
+
schemas: schemas2.map((name) => ({ name })),
|
|
17421
|
+
selectedSchema: r.schema,
|
|
17422
|
+
executedSql: executedSql2
|
|
17423
|
+
};
|
|
17424
|
+
return { ok: true, value: body2 };
|
|
17425
|
+
}
|
|
17094
17426
|
if (!r.docker || r.docker.kind !== "postgresql") {
|
|
17095
17427
|
const body2 = { dbId: r.dbId, schemas: [] };
|
|
17096
17428
|
return { ok: true, value: body2 };
|
|
@@ -18132,7 +18464,7 @@ async function handleClose(cwd, req, omitDirNames) {
|
|
|
18132
18464
|
const r = await resolveDb(cwd, body.db, omitDirNames, undefined, req.signal);
|
|
18133
18465
|
if (r instanceof Response)
|
|
18134
18466
|
return r;
|
|
18135
|
-
if (r.docker) {
|
|
18467
|
+
if (r.docker || r.supabase) {
|
|
18136
18468
|
dockerAdapterCache.close(r.dbId);
|
|
18137
18469
|
dockerAdapterCache.closePrefix(`${r.dbId}\x00`);
|
|
18138
18470
|
} else {
|
|
@@ -18347,7 +18679,8 @@ var init_handle = __esm(() => {
|
|
|
18347
18679
|
DEFAULT_DB_FILE_DISCOVERY_DEPS = {
|
|
18348
18680
|
discoverSqliteFiles: discoverSqliteFilesAsync,
|
|
18349
18681
|
discoverDockerDatabases: discoverDockerDatabasesAsync,
|
|
18350
|
-
listDockerDatabases: listDockerDatabasesAsync
|
|
18682
|
+
listDockerDatabases: listDockerDatabasesAsync,
|
|
18683
|
+
discoverSupabaseCliProjects: discoverSupabaseCliProjectsAsync
|
|
18351
18684
|
};
|
|
18352
18685
|
searchJobs = new Map;
|
|
18353
18686
|
snapshotJobs = new Map;
|
|
@@ -19085,6 +19418,9 @@ async function checkDiscovery(cwd, scopeOmitDirNames, signal) {
|
|
|
19085
19418
|
};
|
|
19086
19419
|
}
|
|
19087
19420
|
async function defaultDatastoreProbe(file, cwd, signal) {
|
|
19421
|
+
if (file.id.startsWith("supabase:")) {
|
|
19422
|
+
return probeSupabaseSource(file, cwd, signal);
|
|
19423
|
+
}
|
|
19088
19424
|
switch (file.kind) {
|
|
19089
19425
|
case "sqlite":
|
|
19090
19426
|
return probeSqliteSource(file, cwd, signal);
|
|
@@ -19135,6 +19471,23 @@ async function probeDockerSqlSource(file, cwd, signal) {
|
|
|
19135
19471
|
} catch {}
|
|
19136
19472
|
}
|
|
19137
19473
|
}
|
|
19474
|
+
async function probeSupabaseSource(file, cwd, signal) {
|
|
19475
|
+
const parsed = parseSupabaseDbId(file.id);
|
|
19476
|
+
if (!parsed)
|
|
19477
|
+
throw new Error("invalid supabase db id");
|
|
19478
|
+
const info = await findSupabaseCliProjectByDbIdAsync(cwd, file.id, undefined, signal);
|
|
19479
|
+
if (!info)
|
|
19480
|
+
throw new Error("supabase project not found");
|
|
19481
|
+
const adapter = await openSupabaseDockerAdapterAsync(info.projectId, undefined, signal);
|
|
19482
|
+
try {
|
|
19483
|
+
signal.throwIfAborted();
|
|
19484
|
+
await adapter.getTablesAsync(signal);
|
|
19485
|
+
} finally {
|
|
19486
|
+
try {
|
|
19487
|
+
adapter.close();
|
|
19488
|
+
} catch {}
|
|
19489
|
+
}
|
|
19490
|
+
}
|
|
19138
19491
|
async function probeRedisSource(file, cwd, signal) {
|
|
19139
19492
|
const info = await findDockerServiceByDbIdAsync(cwd, file.id, "redis", undefined, signal);
|
|
19140
19493
|
if (!info)
|
|
@@ -21734,7 +22087,7 @@ function validateMcpOptionalDbId(raw) {
|
|
|
21734
22087
|
const parsed = validateMcpDbId(raw);
|
|
21735
22088
|
if (parsed.ok !== true)
|
|
21736
22089
|
return parsed;
|
|
21737
|
-
if (!parsed.value.startsWith("docker:")) {
|
|
22090
|
+
if (!parsed.value.startsWith("docker:") && !parsed.value.startsWith("supabase:")) {
|
|
21738
22091
|
const pathError = validateMcpPath(parsed.value);
|
|
21739
22092
|
if (pathError)
|
|
21740
22093
|
return { ok: false, error: "invalid database path" };
|
package/package.json
CHANGED
|
@@ -39,7 +39,9 @@ browser's Database > Search tab, so the human can review the same workflow.
|
|
|
39
39
|
1. Discover the datastore ids the running server has detected. This is the
|
|
40
40
|
AI-friendly equivalent of opening the browser's Database tab and reading
|
|
41
41
|
the sidebar — it lists every SQLite file plus PostgreSQL / MySQL / Redis
|
|
42
|
-
/ Elasticsearch / S3 service that any nearby `docker-compose` exposes
|
|
42
|
+
/ Elasticsearch / S3 service that any nearby `docker-compose` exposes, as
|
|
43
|
+
well as any local Supabase CLI (`supabase start`) project found via a
|
|
44
|
+
`supabase/config.toml` (id form `supabase:<project_id>`).
|
|
43
45
|
Use the printed `id` as `--db` on every other command. Credentials and
|
|
44
46
|
internal config are stripped server-side.
|
|
45
47
|
|
package/web/app.js
CHANGED
|
@@ -18724,6 +18724,11 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
18724
18724
|
const service = rest.split(/[@:]/, 1)[0];
|
|
18725
18725
|
return service || "Docker";
|
|
18726
18726
|
}
|
|
18727
|
+
if (dbId.startsWith("supabase:")) {
|
|
18728
|
+
const rest = dbId.slice("supabase:".length);
|
|
18729
|
+
const projectId = rest.split(/[@]/, 1)[0];
|
|
18730
|
+
return projectId || "Supabase";
|
|
18731
|
+
}
|
|
18727
18732
|
const normalized = dbId.replace(/\\/g, "/");
|
|
18728
18733
|
const lastSlash = normalized.lastIndexOf("/");
|
|
18729
18734
|
return lastSlash >= 0 ? normalized.slice(lastSlash + 1) : normalized;
|
|
@@ -20016,7 +20021,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
20016
20021
|
const opt = document.createElement("option");
|
|
20017
20022
|
opt.value = f2.id;
|
|
20018
20023
|
const isDocker = f2.id.startsWith("docker:");
|
|
20019
|
-
const
|
|
20024
|
+
const isSupabase = f2.id.startsWith("supabase:");
|
|
20025
|
+
const label = isDocker ? `${f2.name} (Docker)` : isSupabase ? `${f2.name} (Supabase)` : `${f2.path} (${formatSize(f2.sizeBytes)})`;
|
|
20020
20026
|
opt.textContent = label;
|
|
20021
20027
|
optionsFragment.appendChild(opt);
|
|
20022
20028
|
}
|
|
@@ -20206,7 +20212,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
20206
20212
|
if (!currentDbInfo)
|
|
20207
20213
|
return labelFromDbId(initial.dbId);
|
|
20208
20214
|
const suffix = currentSchema ? ` / ${currentSchema}` : "";
|
|
20209
|
-
if (currentDbInfo.id.startsWith("docker:")) {
|
|
20215
|
+
if (currentDbInfo.id.startsWith("docker:") || currentDbInfo.id.startsWith("supabase:")) {
|
|
20210
20216
|
const m = currentDbInfo.name.match(/^(\S+)/);
|
|
20211
20217
|
return `${m ? m[1] : currentDbInfo.name}${suffix}`;
|
|
20212
20218
|
}
|
|
@@ -24709,7 +24715,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
24709
24715
|
],
|
|
24710
24716
|
[
|
|
24711
24717
|
"PostgreSQL",
|
|
24712
|
-
"Detected from compose files. Multiple databases per server, plus a schema selector for switching schemas without reopening. Same inline row edit / insert / delete as SQLite."
|
|
24718
|
+
"Detected from compose files. Multiple databases per server, plus a schema selector for switching schemas without reopening. Same inline row edit / insert / delete as SQLite. Local Supabase CLI (`supabase start`) projects are also auto-discovered from `supabase/config.toml`, without needing a docker-compose file."
|
|
24713
24719
|
],
|
|
24714
24720
|
[
|
|
24715
24721
|
"Redis",
|
|
@@ -25368,7 +25374,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
25368
25374
|
],
|
|
25369
25375
|
[
|
|
25370
25376
|
"PostgreSQL",
|
|
25371
|
-
"compose ファイルから検出。同一サーバー上の複数データベースに対応し、スキーマ切替セレクターで再オープンせずスキーマを切り替えられます。SQLite と同じく行のインライン編集 / 追加 /
|
|
25377
|
+
"compose ファイルから検出。同一サーバー上の複数データベースに対応し、スキーマ切替セレクターで再オープンせずスキーマを切り替えられます。SQLite と同じく行のインライン編集 / 追加 / 削除に対応。ローカルの Supabase CLI (`supabase start`) プロジェクトも `supabase/config.toml` から自動検出され、docker-compose ファイルは不要です。"
|
|
25372
25378
|
],
|
|
25373
25379
|
[
|
|
25374
25380
|
"Redis",
|