@youtyan/code-viewer 0.6.7 → 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 +18 -3
- package/dist/code-viewer.js +430 -73
- 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
|
|
@@ -178,7 +181,10 @@ separate project-level config file). `.DS_Store` and a broad set of
|
|
|
178
181
|
build/cache directories (`node_modules`, `dist`, `build`, `.next`, `.turbo`,
|
|
179
182
|
`.parcel-cache`, `.vite`, `.angular`, `.dart_tool`, `.venv`, …) are hidden by
|
|
180
183
|
default. Pass `--scope-omit-dir <name>` (repeatable) to override the omit
|
|
181
|
-
list on the command line for one session.
|
|
184
|
+
list on the command line for one session. `.devbox` and `.direnv` are always
|
|
185
|
+
excluded regardless of saved settings or CLI overrides: they hold thousands
|
|
186
|
+
of generated directories (Nix store link trees) that would stall the
|
|
187
|
+
worktree watcher.
|
|
182
188
|
|
|
183
189
|
The viewer keeps its per-project state under `.code-viewer/` at the repository
|
|
184
190
|
root:
|
|
@@ -216,6 +222,15 @@ Services whose names collide across subdirectories are kept distinct via
|
|
|
216
222
|
`docker:<service>@<relDir>` ids (cwd-direct compose files keep the historical
|
|
217
223
|
`docker:<service>` id for backward compatibility).
|
|
218
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
|
+
|
|
219
234
|
**Redis** support: browse DB 0–15, SCAN keys, and view values per
|
|
220
235
|
type (string/hash/list as dedicated panes, set/zset/stream as JSON views).
|
|
221
236
|
Edit values, delete keys, and create new keys (string/hash/list/set/zset/stream)
|
package/dist/code-viewer.js
CHANGED
|
@@ -1052,6 +1052,10 @@ function normalizeBlameRef(ref, base) {
|
|
|
1052
1052
|
}
|
|
1053
1053
|
return { base, ref: rawRef };
|
|
1054
1054
|
}
|
|
1055
|
+
function withAlwaysWorktreeOmitDirNames(names) {
|
|
1056
|
+
const missing = ALWAYS_WORKTREE_OMIT_DIR_NAMES.filter((name) => !names.includes(name));
|
|
1057
|
+
return missing.length ? [...names, ...missing] : names;
|
|
1058
|
+
}
|
|
1055
1059
|
function run(args, cwd) {
|
|
1056
1060
|
return runSync(resolveGitArgs(args), cwd, {
|
|
1057
1061
|
timeout: GIT_COMMAND_TIMEOUT_MS
|
|
@@ -2118,10 +2122,11 @@ function truncateToNHunks(diffText, n, maxLines = Number.POSITIVE_INFINITY) {
|
|
|
2118
2122
|
lineTruncated
|
|
2119
2123
|
};
|
|
2120
2124
|
}
|
|
2121
|
-
var BLAME_ZERO_SHA = "0000000000000000000000000000000000000000", WORKTREE_RECURSIVE_DEPTH_LIMIT = 32, WORKTREE_RECURSIVE_ENTRY_LIMIT = 50000, DEFAULT_REF_COMMIT_LIMIT = 100, MAX_REF_COMMIT_LIMIT = 500, COMMIT_FORMAT = "%H%x00%s%x00%an%x00%aI", DEFAULT_WORKTREE_OMIT_DIR_NAMES, GIT_COMMAND_TIMEOUT_MS = 20000, HISTORY_FORMAT = "%H%x00%s%x00%an%x00%aI%x00%P%x00%b", MAX_HISTORY_LIMIT = 200;
|
|
2125
|
+
var BLAME_ZERO_SHA = "0000000000000000000000000000000000000000", WORKTREE_RECURSIVE_DEPTH_LIMIT = 32, WORKTREE_RECURSIVE_ENTRY_LIMIT = 50000, DEFAULT_REF_COMMIT_LIMIT = 100, MAX_REF_COMMIT_LIMIT = 500, COMMIT_FORMAT = "%H%x00%s%x00%an%x00%aI", ALWAYS_WORKTREE_OMIT_DIR_NAMES, DEFAULT_WORKTREE_OMIT_DIR_NAMES, GIT_COMMAND_TIMEOUT_MS = 20000, HISTORY_FORMAT = "%H%x00%s%x00%an%x00%aI%x00%P%x00%b", MAX_HISTORY_LIMIT = 200;
|
|
2122
2126
|
var init_git = __esm(() => {
|
|
2123
2127
|
init_command_resolver();
|
|
2124
2128
|
init_runtime();
|
|
2129
|
+
ALWAYS_WORKTREE_OMIT_DIR_NAMES = [".devbox", ".direnv"];
|
|
2125
2130
|
DEFAULT_WORKTREE_OMIT_DIR_NAMES = [
|
|
2126
2131
|
"node_modules",
|
|
2127
2132
|
"bower_components",
|
|
@@ -2142,8 +2147,7 @@ var init_git = __esm(() => {
|
|
|
2142
2147
|
"out",
|
|
2143
2148
|
"target",
|
|
2144
2149
|
".gradle",
|
|
2145
|
-
|
|
2146
|
-
".direnv",
|
|
2150
|
+
...ALWAYS_WORKTREE_OMIT_DIR_NAMES,
|
|
2147
2151
|
".pnpm-store",
|
|
2148
2152
|
".turbo",
|
|
2149
2153
|
".parcel-cache",
|
|
@@ -9608,7 +9612,7 @@ var init_spawn_runner = () => {};
|
|
|
9608
9612
|
// web-src/server/database/adapters/docker-utils.ts
|
|
9609
9613
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
9610
9614
|
function isDockerComposeServiceUnavailableError(err) {
|
|
9611
|
-
return err instanceof DockerComposeServiceUnavailableError || err instanceof DockerCommandUnavailableError;
|
|
9615
|
+
return err instanceof DockerComposeServiceUnavailableError || err instanceof DockerCommandUnavailableError || err instanceof SupabaseDbContainerUnavailableError;
|
|
9612
9616
|
}
|
|
9613
9617
|
function dockerCommand() {
|
|
9614
9618
|
return commandForExternal("docker");
|
|
@@ -9625,10 +9629,15 @@ function throwIfCachedComposeDockerCommandUnavailable(cwd) {
|
|
|
9625
9629
|
const failure = composePsCache.get(cwd)?.error || "";
|
|
9626
9630
|
throwIfDockerCommandUnavailableResult({ code: 1, stderr: failure });
|
|
9627
9631
|
}
|
|
9628
|
-
function
|
|
9632
|
+
function parseDockerJsonLines(stdout) {
|
|
9629
9633
|
const output = stdout.trim();
|
|
9630
|
-
|
|
9634
|
+
if (!output)
|
|
9635
|
+
return [];
|
|
9636
|
+
return output.startsWith("[") ? JSON.parse(output) : output.split(`
|
|
9631
9637
|
`).filter(Boolean).map((line) => JSON.parse(line));
|
|
9638
|
+
}
|
|
9639
|
+
function parseComposePsOutput(stdout) {
|
|
9640
|
+
const containers = parseDockerJsonLines(stdout);
|
|
9632
9641
|
const byService = new Map;
|
|
9633
9642
|
for (const container of containers) {
|
|
9634
9643
|
if (container.Service && container.Name && container.State === "running") {
|
|
@@ -9647,15 +9656,14 @@ function cacheComposePsFailure(cwd, result, now) {
|
|
|
9647
9656
|
error: stderr
|
|
9648
9657
|
});
|
|
9649
9658
|
}
|
|
9650
|
-
function
|
|
9651
|
-
throwIfAborted(signal,
|
|
9659
|
+
function runDockerCliAsync(args, opts) {
|
|
9660
|
+
throwIfAborted(opts.signal, opts.abortMessage);
|
|
9652
9661
|
if (spawnSyncImpl !== spawnSync2) {
|
|
9653
|
-
const
|
|
9654
|
-
const proc = spawnSyncImpl(command, ["compose", "ps", "--format", "json", "--status", "running"], {
|
|
9662
|
+
const proc = spawnSyncImpl(dockerCommand(), args, {
|
|
9655
9663
|
encoding: "utf8",
|
|
9656
9664
|
timeout: 5000,
|
|
9657
9665
|
stdio: ["ignore", "pipe", "pipe"],
|
|
9658
|
-
cwd
|
|
9666
|
+
...opts.cwd ? { cwd: opts.cwd } : {}
|
|
9659
9667
|
});
|
|
9660
9668
|
return Promise.resolve({
|
|
9661
9669
|
stdout: String(proc.stdout || ""),
|
|
@@ -9666,16 +9674,24 @@ ${proc.error.message}` : ""}`,
|
|
|
9666
9674
|
}
|
|
9667
9675
|
return spawnTextAsync({
|
|
9668
9676
|
command: dockerCommand(),
|
|
9669
|
-
args
|
|
9670
|
-
cwd,
|
|
9677
|
+
args,
|
|
9678
|
+
...opts.cwd ? { cwd: opts.cwd } : {},
|
|
9671
9679
|
timeoutMs: 5000,
|
|
9672
|
-
signal,
|
|
9680
|
+
signal: opts.signal,
|
|
9673
9681
|
killSignal: "SIGKILL",
|
|
9674
|
-
abortMessage:
|
|
9675
|
-
timeoutMessage:
|
|
9682
|
+
abortMessage: opts.abortMessage,
|
|
9683
|
+
timeoutMessage: opts.timeoutMessage,
|
|
9676
9684
|
rejectOnError: false
|
|
9677
9685
|
});
|
|
9678
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
|
+
}
|
|
9679
9695
|
async function resolveRunningComposeContainerNameAsync(serviceName, cwd, signal) {
|
|
9680
9696
|
throwIfAborted(signal, "docker compose ps aborted");
|
|
9681
9697
|
const now = Date.now();
|
|
@@ -9773,7 +9789,128 @@ async function resolveRunningComposeContainerNameOrThrowAsync(serviceName, cwd,
|
|
|
9773
9789
|
}
|
|
9774
9790
|
return containerName;
|
|
9775
9791
|
}
|
|
9776
|
-
|
|
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;
|
|
9777
9914
|
var init_docker_utils = __esm(() => {
|
|
9778
9915
|
init_command_resolver();
|
|
9779
9916
|
init_spawn_runner();
|
|
@@ -9798,6 +9935,17 @@ var init_docker_utils = __esm(() => {
|
|
|
9798
9935
|
this.name = "DockerCommandUnavailableError";
|
|
9799
9936
|
}
|
|
9800
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;
|
|
9801
9949
|
});
|
|
9802
9950
|
|
|
9803
9951
|
// web-src/server/database/adapters/sql-capture.ts
|
|
@@ -10670,13 +10818,9 @@ async function listDockerSchemasAsync(serviceName, kind, env, cwd, overrideDatab
|
|
|
10670
10818
|
throwIfCachedComposeDockerCommandUnavailable(cwd);
|
|
10671
10819
|
return setDockerSchemasCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
10672
10820
|
}
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
user,
|
|
10677
|
-
password,
|
|
10678
|
-
database
|
|
10679
|
-
};
|
|
10821
|
+
return fetchPostgresSchemasViaContainerAsync({ kind, containerName, user, password, database }, cacheKey, now, signal);
|
|
10822
|
+
}
|
|
10823
|
+
async function fetchPostgresSchemasViaContainerAsync(config, cacheKey, now, signal) {
|
|
10680
10824
|
try {
|
|
10681
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`;
|
|
10682
10826
|
const result = await execInContainerAsync(config, sql, 1e4, signal);
|
|
@@ -10695,6 +10839,38 @@ async function listDockerSchemasAsync(serviceName, kind, env, cwd, overrideDatab
|
|
|
10695
10839
|
return setDockerSchemasCache(cacheKey, ["public"], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
10696
10840
|
}
|
|
10697
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
|
+
}
|
|
10698
10874
|
async function openDockerAdapterAsync(serviceName, kind, env, cwd, overrideDatabase, schema, signal) {
|
|
10699
10875
|
const containerName = await resolveRunningComposeContainerNameOrThrowAsync(serviceName, cwd, signal);
|
|
10700
10876
|
const user = env.POSTGRES_USER || env.MYSQL_USER || env.MARIADB_USER || (kind === "postgresql" ? "postgres" : "root");
|
|
@@ -10709,7 +10885,7 @@ async function openDockerAdapterAsync(serviceName, kind, env, cwd, overrideDatab
|
|
|
10709
10885
|
...kind === "postgresql" && schema ? { schema } : {}
|
|
10710
10886
|
});
|
|
10711
10887
|
}
|
|
10712
|
-
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";
|
|
10713
10889
|
var init_docker = __esm(() => {
|
|
10714
10890
|
init_mutate();
|
|
10715
10891
|
init_sql_snapshot();
|
|
@@ -13595,6 +13771,39 @@ async function pathExistsAsync(path) {
|
|
|
13595
13771
|
return false;
|
|
13596
13772
|
}
|
|
13597
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
|
+
}
|
|
13598
13807
|
async function discoverDockerDatabasesAsync(cwd, omitDirNames = [], signal) {
|
|
13599
13808
|
const cacheKey = dockerDiscoveryCacheKey(cwd, omitDirNames);
|
|
13600
13809
|
const now = Date.now();
|
|
@@ -13606,13 +13815,7 @@ async function discoverDockerDatabasesAsync(cwd, omitDirNames = [], signal) {
|
|
|
13606
13815
|
const omitSet = new Set(omitDirNames.map((d) => d.toLowerCase()));
|
|
13607
13816
|
omitSet.add(".git");
|
|
13608
13817
|
omitSet.add("node_modules");
|
|
13609
|
-
|
|
13610
|
-
if (signal?.aborted)
|
|
13611
|
-
return;
|
|
13612
|
-
if (results.length >= MAX_DOCKER_SERVICES)
|
|
13613
|
-
return;
|
|
13614
|
-
if (depth > MAX_SCAN_DEPTH)
|
|
13615
|
-
return;
|
|
13818
|
+
await walkForMarkerFileAsync(cwd, 0, omitSet, () => results.length < MAX_DOCKER_SERVICES, async (dir) => {
|
|
13616
13819
|
for (const filename of COMPOSE_FILENAMES) {
|
|
13617
13820
|
const filepath = join8(dir, filename);
|
|
13618
13821
|
if (await pathExistsAsync(filepath)) {
|
|
@@ -13620,37 +13823,7 @@ async function discoverDockerDatabasesAsync(cwd, omitDirNames = [], signal) {
|
|
|
13620
13823
|
break;
|
|
13621
13824
|
}
|
|
13622
13825
|
}
|
|
13623
|
-
|
|
13624
|
-
return;
|
|
13625
|
-
if (results.length >= MAX_DOCKER_SERVICES)
|
|
13626
|
-
return;
|
|
13627
|
-
let entries;
|
|
13628
|
-
try {
|
|
13629
|
-
entries = await readdir(dir);
|
|
13630
|
-
} catch {
|
|
13631
|
-
return;
|
|
13632
|
-
}
|
|
13633
|
-
for (const entry of entries) {
|
|
13634
|
-
if (signal?.aborted)
|
|
13635
|
-
return;
|
|
13636
|
-
if (results.length >= MAX_DOCKER_SERVICES)
|
|
13637
|
-
return;
|
|
13638
|
-
if (omitSet.has(entry.toLowerCase()))
|
|
13639
|
-
continue;
|
|
13640
|
-
const full = join8(dir, entry);
|
|
13641
|
-
let entryStat;
|
|
13642
|
-
try {
|
|
13643
|
-
entryStat = await lstat(full);
|
|
13644
|
-
} catch {
|
|
13645
|
-
continue;
|
|
13646
|
-
}
|
|
13647
|
-
if (entryStat.isSymbolicLink())
|
|
13648
|
-
continue;
|
|
13649
|
-
if (entryStat.isDirectory())
|
|
13650
|
-
await scan(full, depth + 1);
|
|
13651
|
-
}
|
|
13652
|
-
}
|
|
13653
|
-
await scan(cwd, 0);
|
|
13826
|
+
}, signal);
|
|
13654
13827
|
if (signal?.aborted)
|
|
13655
13828
|
return cloneDockerDiscoveryResult(results);
|
|
13656
13829
|
if (results.length >= MAX_DOCKER_SERVICES) {
|
|
@@ -13745,7 +13918,120 @@ function isSafeDockerDatabaseName(value) {
|
|
|
13745
13918
|
return false;
|
|
13746
13919
|
return /^[A-Za-z0-9_$.-]+$/.test(value);
|
|
13747
13920
|
}
|
|
13748
|
-
|
|
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;
|
|
13749
14035
|
var init_discovery = __esm(() => {
|
|
13750
14036
|
SQLITE_EXTENSIONS = new Set([".db", ".sqlite", ".sqlite3", ".s3db"]);
|
|
13751
14037
|
sqliteDiscoveryCache = new Map;
|
|
@@ -13756,6 +14042,7 @@ var init_discovery = __esm(() => {
|
|
|
13756
14042
|
"compose.yaml"
|
|
13757
14043
|
];
|
|
13758
14044
|
dockerDiscoveryCache = new Map;
|
|
14045
|
+
supabaseDiscoveryCache = new Map;
|
|
13759
14046
|
});
|
|
13760
14047
|
|
|
13761
14048
|
// web-src/core/id.ts
|
|
@@ -16750,8 +17037,9 @@ function sanitizeCssSize(v) {
|
|
|
16750
17037
|
return isValidCssSize(v) ? v : undefined;
|
|
16751
17038
|
}
|
|
16752
17039
|
function isToolInternalDbId(dbId) {
|
|
16753
|
-
if (!dbId || dbId.startsWith("docker:"))
|
|
17040
|
+
if (!dbId || dbId.startsWith("docker:") || dbId.startsWith("supabase:")) {
|
|
16754
17041
|
return false;
|
|
17042
|
+
}
|
|
16755
17043
|
return dbId.split(/[\\/]+/).some((part) => part.toLowerCase() === ".code-viewer");
|
|
16756
17044
|
}
|
|
16757
17045
|
function sanitizeRedis(v) {
|
|
@@ -16930,6 +17218,11 @@ async function getAdapter(r, _cwd, signal) {
|
|
|
16930
17218
|
const cacheKey = r.schema ? `${r.dbId}\x00schema=${r.schema}` : r.dbId;
|
|
16931
17219
|
return dockerAdapterCache.getOrOpenAsync(cacheKey, () => openDockerAdapterAsync(docker.serviceName, docker.kind, docker.env, docker.composeDir, docker.database, r.schema, signal));
|
|
16932
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
|
+
}
|
|
16933
17226
|
return getConnection(r.resolved);
|
|
16934
17227
|
}
|
|
16935
17228
|
function sanitizeFilename(name) {
|
|
@@ -16956,9 +17249,35 @@ async function resolvePostgresSchema(info, requestedSchema, signal) {
|
|
|
16956
17249
|
return "public";
|
|
16957
17250
|
return schemas[0] || "public";
|
|
16958
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
|
+
}
|
|
16959
17260
|
async function resolveDb(cwd, dbParam, omitDirNames, schemaParam, signal) {
|
|
16960
17261
|
if (!dbParam)
|
|
16961
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
|
+
}
|
|
16962
17281
|
if (dbParam.startsWith("docker:")) {
|
|
16963
17282
|
const parsed = parseDockerDbId(dbParam);
|
|
16964
17283
|
if (!parsed)
|
|
@@ -17045,17 +17364,22 @@ async function expandDockerServicesForFiles(dockerServices, listDockerDatabases,
|
|
|
17045
17364
|
}
|
|
17046
17365
|
async function createDbFilesResponse(cwd, omitDirNames, signal, deps = DEFAULT_DB_FILE_DISCOVERY_DEPS) {
|
|
17047
17366
|
ensureInit();
|
|
17048
|
-
const [sqliteSettled, dockerSettled] = await Promise.allSettled([
|
|
17367
|
+
const [sqliteSettled, dockerSettled, supabaseSettled] = await Promise.allSettled([
|
|
17049
17368
|
deps.discoverSqliteFiles(cwd, omitDirNames, signal),
|
|
17050
|
-
deps.discoverDockerDatabases(cwd, omitDirNames, signal)
|
|
17369
|
+
deps.discoverDockerDatabases(cwd, omitDirNames, signal),
|
|
17370
|
+
deps.discoverSupabaseCliProjects(cwd, omitDirNames, signal)
|
|
17051
17371
|
]);
|
|
17052
17372
|
if (sqliteSettled.status === "rejected") {
|
|
17053
17373
|
throw sqliteSettled.reason;
|
|
17054
17374
|
}
|
|
17375
|
+
if (supabaseSettled.status === "rejected") {
|
|
17376
|
+
throw supabaseSettled.reason;
|
|
17377
|
+
}
|
|
17055
17378
|
if (dockerSettled.status === "rejected" && isAbortLikeError(dockerSettled.reason, signal)) {
|
|
17056
17379
|
throw dockerSettled.reason;
|
|
17057
17380
|
}
|
|
17058
17381
|
const sqliteFiles = sqliteSettled.value;
|
|
17382
|
+
const supabaseProjects = supabaseSettled.value;
|
|
17059
17383
|
const dockerServices = dockerSettled.status === "fulfilled" ? dockerSettled.value : [];
|
|
17060
17384
|
const dockerErrors = [];
|
|
17061
17385
|
if (dockerSettled.status === "rejected") {
|
|
@@ -17073,7 +17397,8 @@ async function createDbFilesResponse(cwd, omitDirNames, signal, deps = DEFAULT_D
|
|
|
17073
17397
|
sizeBytes: f.sizeBytes,
|
|
17074
17398
|
kind: "sqlite"
|
|
17075
17399
|
})),
|
|
17076
|
-
...dockerEntries.map(toFileInfo)
|
|
17400
|
+
...dockerEntries.map(toFileInfo),
|
|
17401
|
+
...supabaseProjects.map(toFileInfo)
|
|
17077
17402
|
],
|
|
17078
17403
|
...dockerTruncated ? { truncated: true } : {},
|
|
17079
17404
|
...dockerErrors.length > 0 ? { dockerError: dockerErrors.join("; ") } : {}
|
|
@@ -17087,6 +17412,17 @@ async function createDbSchemasResponse(cwd, dbParam, schemaParam, omitDirNames,
|
|
|
17087
17412
|
const r = await resolveDb(cwd, dbParam, omitDirNames, schemaParam, signal);
|
|
17088
17413
|
if (r instanceof Response)
|
|
17089
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
|
+
}
|
|
17090
17426
|
if (!r.docker || r.docker.kind !== "postgresql") {
|
|
17091
17427
|
const body2 = { dbId: r.dbId, schemas: [] };
|
|
17092
17428
|
return { ok: true, value: body2 };
|
|
@@ -18128,7 +18464,7 @@ async function handleClose(cwd, req, omitDirNames) {
|
|
|
18128
18464
|
const r = await resolveDb(cwd, body.db, omitDirNames, undefined, req.signal);
|
|
18129
18465
|
if (r instanceof Response)
|
|
18130
18466
|
return r;
|
|
18131
|
-
if (r.docker) {
|
|
18467
|
+
if (r.docker || r.supabase) {
|
|
18132
18468
|
dockerAdapterCache.close(r.dbId);
|
|
18133
18469
|
dockerAdapterCache.closePrefix(`${r.dbId}\x00`);
|
|
18134
18470
|
} else {
|
|
@@ -18343,7 +18679,8 @@ var init_handle = __esm(() => {
|
|
|
18343
18679
|
DEFAULT_DB_FILE_DISCOVERY_DEPS = {
|
|
18344
18680
|
discoverSqliteFiles: discoverSqliteFilesAsync,
|
|
18345
18681
|
discoverDockerDatabases: discoverDockerDatabasesAsync,
|
|
18346
|
-
listDockerDatabases: listDockerDatabasesAsync
|
|
18682
|
+
listDockerDatabases: listDockerDatabasesAsync,
|
|
18683
|
+
discoverSupabaseCliProjects: discoverSupabaseCliProjectsAsync
|
|
18347
18684
|
};
|
|
18348
18685
|
searchJobs = new Map;
|
|
18349
18686
|
snapshotJobs = new Map;
|
|
@@ -19081,6 +19418,9 @@ async function checkDiscovery(cwd, scopeOmitDirNames, signal) {
|
|
|
19081
19418
|
};
|
|
19082
19419
|
}
|
|
19083
19420
|
async function defaultDatastoreProbe(file, cwd, signal) {
|
|
19421
|
+
if (file.id.startsWith("supabase:")) {
|
|
19422
|
+
return probeSupabaseSource(file, cwd, signal);
|
|
19423
|
+
}
|
|
19084
19424
|
switch (file.kind) {
|
|
19085
19425
|
case "sqlite":
|
|
19086
19426
|
return probeSqliteSource(file, cwd, signal);
|
|
@@ -19131,6 +19471,23 @@ async function probeDockerSqlSource(file, cwd, signal) {
|
|
|
19131
19471
|
} catch {}
|
|
19132
19472
|
}
|
|
19133
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
|
+
}
|
|
19134
19491
|
async function probeRedisSource(file, cwd, signal) {
|
|
19135
19492
|
const info = await findDockerServiceByDbIdAsync(cwd, file.id, "redis", undefined, signal);
|
|
19136
19493
|
if (!info)
|
|
@@ -21730,7 +22087,7 @@ function validateMcpOptionalDbId(raw) {
|
|
|
21730
22087
|
const parsed = validateMcpDbId(raw);
|
|
21731
22088
|
if (parsed.ok !== true)
|
|
21732
22089
|
return parsed;
|
|
21733
|
-
if (!parsed.value.startsWith("docker:")) {
|
|
22090
|
+
if (!parsed.value.startsWith("docker:") && !parsed.value.startsWith("supabase:")) {
|
|
21734
22091
|
const pathError = validateMcpPath(parsed.value);
|
|
21735
22092
|
if (pathError)
|
|
21736
22093
|
return { ok: false, error: "invalid database path" };
|
|
@@ -22164,7 +22521,7 @@ Examples:
|
|
|
22164
22521
|
}
|
|
22165
22522
|
warnIfLegacyConfigPresent();
|
|
22166
22523
|
if (scopeOmitDirCliOverride) {
|
|
22167
|
-
scopeOmitDirNames = scopeOmitDirCliOverride;
|
|
22524
|
+
scopeOmitDirNames = withAlwaysWorktreeOmitDirNames(scopeOmitDirCliOverride);
|
|
22168
22525
|
}
|
|
22169
22526
|
scopeWatchLimit = worktreeWatchDirectoryLimitFromEnv();
|
|
22170
22527
|
}
|
|
@@ -22180,7 +22537,7 @@ function applyPersistedSettings(state) {
|
|
|
22180
22537
|
const prevExclude = scopeExcludeNames;
|
|
22181
22538
|
const prevWatchLimit = scopeWatchLimit;
|
|
22182
22539
|
if (!scopeOmitDirCliOverride && Array.isArray(state.scopeOmitDirs) && state.scopeOmitDirs.length > 0) {
|
|
22183
|
-
scopeOmitDirNames = state.scopeOmitDirs;
|
|
22540
|
+
scopeOmitDirNames = withAlwaysWorktreeOmitDirNames(state.scopeOmitDirs);
|
|
22184
22541
|
} else if (!scopeOmitDirCliOverride) {
|
|
22185
22542
|
scopeOmitDirNames = DEFAULT_WORKTREE_OMIT_DIR_NAMES;
|
|
22186
22543
|
}
|
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",
|