@agentlink.sh/cli 0.11.0 → 0.12.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.
|
@@ -505,6 +505,42 @@ async function waitForProjectReady(ref, spinner, timeoutMs = 3e5) {
|
|
|
505
505
|
Check status at: https://supabase.com/dashboard/project/${ref}`
|
|
506
506
|
);
|
|
507
507
|
}
|
|
508
|
+
async function fetchPoolerUrl(projectRef) {
|
|
509
|
+
const token = process.env.SUPABASE_ACCESS_TOKEN;
|
|
510
|
+
if (!token) {
|
|
511
|
+
throw new Error("SUPABASE_ACCESS_TOKEN is required to fetch pooler config");
|
|
512
|
+
}
|
|
513
|
+
const res = await fetch(
|
|
514
|
+
`https://api.supabase.com/v1/projects/${projectRef}/config/database/pooler`,
|
|
515
|
+
{
|
|
516
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
517
|
+
}
|
|
518
|
+
);
|
|
519
|
+
if (!res.ok) {
|
|
520
|
+
const body = await res.text();
|
|
521
|
+
throw new Error(`Failed to fetch pooler config (${res.status}): ${body}`);
|
|
522
|
+
}
|
|
523
|
+
const entries = await res.json();
|
|
524
|
+
const pooler = entries.find(
|
|
525
|
+
(e) => e.database_type === "PRIMARY" && e.pool_mode === "transaction"
|
|
526
|
+
) ?? entries.find(
|
|
527
|
+
(e) => e.database_type === "PRIMARY"
|
|
528
|
+
) ?? entries[0];
|
|
529
|
+
if (!pooler) {
|
|
530
|
+
throw new Error("No pooler configuration found for project");
|
|
531
|
+
}
|
|
532
|
+
return {
|
|
533
|
+
host: pooler.db_host,
|
|
534
|
+
port: pooler.db_port,
|
|
535
|
+
user: pooler.db_user,
|
|
536
|
+
dbName: pooler.db_name,
|
|
537
|
+
connectionString: pooler.connection_string
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
async function resolvePoolerDbUrl(projectRef, password) {
|
|
541
|
+
const pooler = await fetchPoolerUrl(projectRef);
|
|
542
|
+
return `postgresql://${pooler.user}:${encodeURIComponent(password)}@${pooler.host}:${pooler.port}/${pooler.dbName}`;
|
|
543
|
+
}
|
|
508
544
|
function saveProjectCredential(projectRef, dbPassword) {
|
|
509
545
|
const creds = loadCredentials();
|
|
510
546
|
if (!creds.project_credentials) {
|
|
@@ -584,6 +620,8 @@ export {
|
|
|
584
620
|
updatePostgrestConfig,
|
|
585
621
|
updateAuthConfig,
|
|
586
622
|
waitForProjectReady,
|
|
623
|
+
fetchPoolerUrl,
|
|
624
|
+
resolvePoolerDbUrl,
|
|
587
625
|
saveProjectCredential,
|
|
588
626
|
loadProjectCredential,
|
|
589
627
|
resolveDbUrlForEnv,
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
credentialsPath,
|
|
5
5
|
detectClosestRegion,
|
|
6
6
|
ensureAccessToken,
|
|
7
|
+
fetchPoolerUrl,
|
|
7
8
|
getApiKeys,
|
|
8
9
|
getProject,
|
|
9
10
|
listOrganizations,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
loadCredentials,
|
|
14
15
|
loadProjectCredential,
|
|
15
16
|
resolveDbUrlForEnv,
|
|
17
|
+
resolvePoolerDbUrl,
|
|
16
18
|
runCloudSQL,
|
|
17
19
|
saveCredentials,
|
|
18
20
|
saveProjectCredential,
|
|
@@ -20,13 +22,14 @@ import {
|
|
|
20
22
|
updateAuthConfig,
|
|
21
23
|
updatePostgrestConfig,
|
|
22
24
|
waitForProjectReady
|
|
23
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-4EUOAXWZ.js";
|
|
24
26
|
import "./chunk-G3HVUCWY.js";
|
|
25
27
|
export {
|
|
26
28
|
createProject,
|
|
27
29
|
credentialsPath,
|
|
28
30
|
detectClosestRegion,
|
|
29
31
|
ensureAccessToken,
|
|
32
|
+
fetchPoolerUrl,
|
|
30
33
|
getApiKeys,
|
|
31
34
|
getProject,
|
|
32
35
|
listOrganizations,
|
|
@@ -36,6 +39,7 @@ export {
|
|
|
36
39
|
loadCredentials,
|
|
37
40
|
loadProjectCredential,
|
|
38
41
|
resolveDbUrlForEnv,
|
|
42
|
+
resolvePoolerDbUrl,
|
|
39
43
|
runCloudSQL,
|
|
40
44
|
saveCredentials,
|
|
41
45
|
saveProjectCredential,
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
loadCredentials,
|
|
17
17
|
loadProjectCredential,
|
|
18
18
|
resolveDbUrlForEnv,
|
|
19
|
+
resolvePoolerDbUrl,
|
|
19
20
|
runCloudSQL,
|
|
20
21
|
runCommand,
|
|
21
22
|
saveCredentials,
|
|
@@ -26,7 +27,7 @@ import {
|
|
|
26
27
|
updatePostgrestConfig,
|
|
27
28
|
validateProjectName,
|
|
28
29
|
waitForProjectReady
|
|
29
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-4EUOAXWZ.js";
|
|
30
31
|
import {
|
|
31
32
|
SKILLS_VERSION,
|
|
32
33
|
SUPPORTED_SUPABASE_CLI,
|
|
@@ -186,20 +187,33 @@ function listEnvironments(cwd) {
|
|
|
186
187
|
var check_setup_default = "SELECT jsonb_build_object(\n 'extensions', jsonb_build_object(\n 'pg_net', EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_net'),\n 'pg_cron', EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_cron'),\n 'vault', EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'supabase_vault'),\n 'pgmq', EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pgmq')\n ),\n 'queues', jsonb_build_object(\n 'agentlink_tasks', EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'pgmq' AND table_name = 'q_agentlink_tasks'\n )\n ),\n 'tables', jsonb_build_object(\n 'profiles', EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = 'profiles'\n ),\n 'tenants', EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = 'tenants'\n ),\n 'memberships', EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = 'memberships'\n ),\n 'invitations', EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = 'invitations'\n )\n ),\n 'functions', jsonb_build_object(\n '_internal_admin_get_secret',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public'\n AND routine_name = '_internal_admin_get_secret'\n ),\n '_internal_admin_call_edge_function',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public'\n AND routine_name = '_internal_admin_call_edge_function'\n ),\n 'api._admin_enqueue_task',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = '_admin_enqueue_task'\n ),\n 'api._admin_queue_read',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = '_admin_queue_read'\n ),\n 'api._admin_queue_delete',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = '_admin_queue_delete'\n ),\n 'api._admin_queue_archive',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = '_admin_queue_archive'\n ),\n 'set_updated_at',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public'\n AND routine_name = 'set_updated_at'\n ),\n '_internal_admin_handle_new_user',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public'\n AND routine_name = '_internal_admin_handle_new_user'\n ),\n 'api.profile_get',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = 'profile_get'\n ),\n 'api.profile_update',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = 'profile_update'\n ),\n '_hook_before_user_created',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public'\n AND routine_name = '_hook_before_user_created'\n ),\n '_hook_send_email',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public'\n AND routine_name = '_hook_send_email'\n ),\n '_auth_tenant_id',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public'\n AND routine_name = '_auth_tenant_id'\n ),\n '_auth_tenant_role',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public'\n AND routine_name = '_auth_tenant_role'\n ),\n '_auth_has_role',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public'\n AND routine_name = '_auth_has_role'\n ),\n '_auth_is_tenant_member',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public'\n AND routine_name = '_auth_is_tenant_member'\n ),\n 'api.tenant_select',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = 'tenant_select'\n ),\n 'api.tenant_list',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = 'tenant_list'\n ),\n 'api.tenant_create',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = 'tenant_create'\n ),\n 'api.invitation_create',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = 'invitation_create'\n ),\n 'api.invitation_accept',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = 'invitation_accept'\n ),\n 'api.membership_list',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = 'membership_list'\n )\n ),\n 'triggers', jsonb_build_object(\n 'trg_profiles_updated_at',\n EXISTS (\n SELECT 1 FROM pg_trigger WHERE tgname = 'trg_profiles_updated_at'\n ),\n 'trg_auth_users_new_user',\n EXISTS (\n SELECT 1 FROM pg_trigger WHERE tgname = 'trg_auth_users_new_user'\n ),\n 'trg_tenants_updated_at',\n EXISTS (\n SELECT 1 FROM pg_trigger WHERE tgname = 'trg_tenants_updated_at'\n )\n ),\n 'secrets', jsonb_build_object(\n 'SUPABASE_URL',\n EXISTS (SELECT 1 FROM vault.secrets WHERE name = 'SUPABASE_URL'),\n 'SB_PUBLISHABLE_KEY',\n EXISTS (SELECT 1 FROM vault.secrets WHERE name = 'SB_PUBLISHABLE_KEY'),\n 'SB_SECRET_KEY',\n EXISTS (SELECT 1 FROM vault.secrets WHERE name = 'SB_SECRET_KEY')\n ),\n 'api_schema', EXISTS (\n SELECT 1 FROM information_schema.schemata WHERE schema_name = 'api'\n ),\n 'ready', (\n EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_net')\n AND EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_cron')\n AND EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'supabase_vault')\n AND EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pgmq')\n AND EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = 'api')\n AND EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'pgmq' AND table_name = 'q_agentlink_tasks'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public' AND routine_name = '_internal_admin_get_secret'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public' AND routine_name = '_internal_admin_call_edge_function'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = '_admin_enqueue_task'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = '_admin_queue_read'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = '_admin_queue_delete'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = '_admin_queue_archive'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = 'profiles'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = 'tenants'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = 'memberships'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = 'invitations'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public' AND routine_name = 'set_updated_at'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public' AND routine_name = '_internal_admin_handle_new_user'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = 'profile_get'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = 'profile_update'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public' AND routine_name = '_hook_before_user_created'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public' AND routine_name = '_hook_send_email'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public' AND routine_name = '_auth_tenant_id'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public' AND routine_name = '_auth_tenant_role'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public' AND routine_name = '_auth_has_role'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public' AND routine_name = '_auth_is_tenant_member'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = 'tenant_select'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = 'tenant_list'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = 'tenant_create'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = 'invitation_create'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = 'invitation_accept'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = 'membership_list'\n )\n AND EXISTS (\n SELECT 1 FROM pg_trigger WHERE tgname = 'trg_profiles_updated_at'\n )\n AND EXISTS (\n SELECT 1 FROM pg_trigger WHERE tgname = 'trg_auth_users_new_user'\n )\n AND EXISTS (\n SELECT 1 FROM pg_trigger WHERE tgname = 'trg_tenants_updated_at'\n )\n AND EXISTS (SELECT 1 FROM vault.secrets WHERE name = 'SUPABASE_URL')\n AND EXISTS (SELECT 1 FROM vault.secrets WHERE name = 'SB_PUBLISHABLE_KEY')\n AND EXISTS (SELECT 1 FROM vault.secrets WHERE name = 'SB_SECRET_KEY')\n )\n) AS setup_status;\n";
|
|
187
188
|
|
|
188
189
|
// src/sql.ts
|
|
190
|
+
function upsertSecret(value, name) {
|
|
191
|
+
const safeValue = value.replace(/'/g, "''");
|
|
192
|
+
return `DO $$ DECLARE
|
|
193
|
+
_id uuid;
|
|
194
|
+
BEGIN
|
|
195
|
+
SELECT id INTO _id FROM vault.secrets WHERE name = '${name}';
|
|
196
|
+
IF _id IS NOT NULL THEN
|
|
197
|
+
PERFORM vault.update_secret(_id, '${safeValue}', '${name}');
|
|
198
|
+
ELSE
|
|
199
|
+
PERFORM vault.create_secret('${safeValue}', '${name}');
|
|
200
|
+
END IF;
|
|
201
|
+
END $$;`;
|
|
202
|
+
}
|
|
189
203
|
function seedSQL(publishableKey, secretKey) {
|
|
190
204
|
return [
|
|
191
|
-
"
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
"
|
|
205
|
+
upsertSecret("http://host.docker.internal:54321", "SUPABASE_URL"),
|
|
206
|
+
upsertSecret(publishableKey, "SB_PUBLISHABLE_KEY"),
|
|
207
|
+
upsertSecret(secretKey, "SB_SECRET_KEY"),
|
|
208
|
+
upsertSecret("", "ALLOWED_SIGNUP_DOMAINS")
|
|
195
209
|
].join("\n");
|
|
196
210
|
}
|
|
197
211
|
function cloudSeedSQL(apiUrl, publishableKey, secretKey) {
|
|
198
212
|
return [
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
"
|
|
213
|
+
upsertSecret(apiUrl, "SUPABASE_URL"),
|
|
214
|
+
upsertSecret(publishableKey, "SB_PUBLISHABLE_KEY"),
|
|
215
|
+
upsertSecret(secretKey, "SB_SECRET_KEY"),
|
|
216
|
+
upsertSecret("", "ALLOWED_SIGNUP_DOMAINS")
|
|
203
217
|
].join("\n");
|
|
204
218
|
}
|
|
205
219
|
|
|
@@ -1517,6 +1531,7 @@ async function check(envFlag) {
|
|
|
1517
1531
|
import fs5 from "fs";
|
|
1518
1532
|
import os from "os";
|
|
1519
1533
|
import path5 from "path";
|
|
1534
|
+
import { input } from "@inquirer/prompts";
|
|
1520
1535
|
function stripQuotes(val) {
|
|
1521
1536
|
if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
|
|
1522
1537
|
return val.slice(1, -1);
|
|
@@ -1721,12 +1736,130 @@ Migration created: supabase/migrations/${filename}`);
|
|
|
1721
1736
|
fs5.rmSync(tmpDir, { recursive: true, force: true });
|
|
1722
1737
|
}
|
|
1723
1738
|
}
|
|
1739
|
+
async function dbRebuild(options) {
|
|
1740
|
+
const cwd = options.cwd;
|
|
1741
|
+
const migrationsDir = path5.join(cwd, "supabase", "migrations");
|
|
1742
|
+
if (fs5.existsSync(migrationsDir)) {
|
|
1743
|
+
const files = fs5.readdirSync(migrationsDir).filter((f) => f.endsWith(".sql"));
|
|
1744
|
+
for (const file of files) {
|
|
1745
|
+
fs5.unlinkSync(path5.join(migrationsDir, file));
|
|
1746
|
+
}
|
|
1747
|
+
console.log(`Deleted ${files.length} migration file(s)`);
|
|
1748
|
+
}
|
|
1749
|
+
const progressPath2 = path5.join(cwd, ".agentlink-progress.json");
|
|
1750
|
+
if (fs5.existsSync(progressPath2)) {
|
|
1751
|
+
fs5.unlinkSync(progressPath2);
|
|
1752
|
+
console.log("Cleared scaffold progress");
|
|
1753
|
+
}
|
|
1754
|
+
console.log("\nRe-applying schemas...");
|
|
1755
|
+
await dbApply({ cwd, dbUrl: options.dbUrl, env: options.env, skipTypes: true });
|
|
1756
|
+
console.log("\nRegenerating migration...");
|
|
1757
|
+
await dbMigrate("agentlink_setup", { cwd, dbUrl: options.dbUrl, env: options.env });
|
|
1758
|
+
console.log("\nGenerating types...");
|
|
1759
|
+
try {
|
|
1760
|
+
await dbTypes({ cwd, dbUrl: options.dbUrl, env: options.env });
|
|
1761
|
+
} catch (err) {
|
|
1762
|
+
console.warn(`Warning: type generation failed \u2014 ${err.message}`);
|
|
1763
|
+
}
|
|
1764
|
+
console.log("\nDone. Database rebuilt from schema files.");
|
|
1765
|
+
}
|
|
1766
|
+
async function dbUrlCheck(options) {
|
|
1767
|
+
const mode = await resolveDbMode(options.cwd, options.dbUrl, options.env);
|
|
1768
|
+
if (mode.kind !== "cloud" || !mode.projectRef) {
|
|
1769
|
+
const dbUrl = await resolveDbUrl(options.cwd, options.dbUrl);
|
|
1770
|
+
console.log(dbUrl);
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
await ensureAccessToken(true, options.cwd);
|
|
1774
|
+
const password = loadProjectCredential(mode.projectRef);
|
|
1775
|
+
if (!password) {
|
|
1776
|
+
throw new Error(
|
|
1777
|
+
`No stored password for project ${mode.projectRef}.
|
|
1778
|
+
Run: agentlink env relink dev`
|
|
1779
|
+
);
|
|
1780
|
+
}
|
|
1781
|
+
const poolerUrl = await resolvePoolerDbUrl(mode.projectRef, password);
|
|
1782
|
+
console.log(`Pooler URL: ${poolerUrl}`);
|
|
1783
|
+
const currentUrl = readEnvValue(options.cwd, "SUPABASE_DB_URL");
|
|
1784
|
+
if (currentUrl === poolerUrl) {
|
|
1785
|
+
console.log("\u2714 .env.local is up to date");
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
if (currentUrl) {
|
|
1789
|
+
console.log(`
|
|
1790
|
+
Current: ${currentUrl}`);
|
|
1791
|
+
console.log(`Expected: ${poolerUrl}`);
|
|
1792
|
+
}
|
|
1793
|
+
if (options.fix) {
|
|
1794
|
+
const envPath = path5.join(options.cwd, ".env.local");
|
|
1795
|
+
if (fs5.existsSync(envPath)) {
|
|
1796
|
+
let content = fs5.readFileSync(envPath, "utf-8");
|
|
1797
|
+
if (/^SUPABASE_DB_URL=/m.test(content)) {
|
|
1798
|
+
content = content.replace(/^SUPABASE_DB_URL=.*$/m, `SUPABASE_DB_URL=${poolerUrl}`);
|
|
1799
|
+
} else {
|
|
1800
|
+
content = content.trimEnd() + `
|
|
1801
|
+
SUPABASE_DB_URL=${poolerUrl}
|
|
1802
|
+
`;
|
|
1803
|
+
}
|
|
1804
|
+
fs5.writeFileSync(envPath, content);
|
|
1805
|
+
console.log("\n\u2714 .env.local updated with correct pooler URL");
|
|
1806
|
+
}
|
|
1807
|
+
} else if (currentUrl !== poolerUrl) {
|
|
1808
|
+
console.log("\nRun with --fix to update .env.local");
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
var passwordTheme = {
|
|
1812
|
+
prefix: { idle: blue("?"), done: blue("\u2714") },
|
|
1813
|
+
style: {
|
|
1814
|
+
answer: (text) => amber(text),
|
|
1815
|
+
message: (text) => bold(text),
|
|
1816
|
+
help: (text) => dim(text)
|
|
1817
|
+
}
|
|
1818
|
+
};
|
|
1819
|
+
async function dbPassword(cwd, value) {
|
|
1820
|
+
const manifest = readManifest(cwd);
|
|
1821
|
+
if (!manifest) {
|
|
1822
|
+
throw new Error("No agentlink.json found. Run `agentlink` to scaffold a project first.");
|
|
1823
|
+
}
|
|
1824
|
+
const defaultEnv = manifest.cloud?.default;
|
|
1825
|
+
if (!defaultEnv || defaultEnv === "local" || !manifest.cloud?.environments?.[defaultEnv]) {
|
|
1826
|
+
throw new Error("No cloud environment configured. This command is for cloud projects.");
|
|
1827
|
+
}
|
|
1828
|
+
const env2 = manifest.cloud.environments[defaultEnv];
|
|
1829
|
+
const projectRef = env2.projectRef;
|
|
1830
|
+
if (value) {
|
|
1831
|
+
saveProjectCredential(projectRef, value);
|
|
1832
|
+
console.log(`Password saved for project ${dim(projectRef)}`);
|
|
1833
|
+
} else {
|
|
1834
|
+
const current = loadProjectCredential(projectRef);
|
|
1835
|
+
if (current) {
|
|
1836
|
+
const masked = current.length > 8 ? current.slice(0, 4) + "\u25CF".repeat(current.length - 8) + current.slice(-4) : "\u25CF".repeat(current.length);
|
|
1837
|
+
console.log(`Current password: ${dim(masked)} (project: ${projectRef})`);
|
|
1838
|
+
console.log();
|
|
1839
|
+
}
|
|
1840
|
+
const resetUrl = `https://supabase.com/dashboard/project/${projectRef}/settings/database`;
|
|
1841
|
+
console.log(` ${dim("Reset your password at:")}`);
|
|
1842
|
+
console.log(` ${dim(resetUrl)}`);
|
|
1843
|
+
console.log();
|
|
1844
|
+
const newPassword = await input({
|
|
1845
|
+
message: "Database password:",
|
|
1846
|
+
theme: passwordTheme,
|
|
1847
|
+
transformer: (v, { isFinal }) => {
|
|
1848
|
+
if (isFinal) return "\u25CF".repeat(Math.min(v.length, 8));
|
|
1849
|
+
return "\u25CF".repeat(v.length);
|
|
1850
|
+
},
|
|
1851
|
+
validate: (val) => val.trim().length > 0 || "Password is required"
|
|
1852
|
+
});
|
|
1853
|
+
saveProjectCredential(projectRef, newPassword.trim());
|
|
1854
|
+
console.log(`Password saved for project ${dim(projectRef)}`);
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1724
1857
|
|
|
1725
1858
|
// src/deploy.ts
|
|
1726
1859
|
import fs6 from "fs";
|
|
1727
1860
|
import os2 from "os";
|
|
1728
1861
|
import path6 from "path";
|
|
1729
|
-
import { confirm, input } from "@inquirer/prompts";
|
|
1862
|
+
import { confirm, input as input2 } from "@inquirer/prompts";
|
|
1730
1863
|
|
|
1731
1864
|
// src/migration-analysis.ts
|
|
1732
1865
|
var PATTERNS = [
|
|
@@ -1933,8 +2066,8 @@ async function resolveEnvironments(cwd, opts) {
|
|
|
1933
2066
|
}
|
|
1934
2067
|
await ensureAccessToken(opts.ci, cwd);
|
|
1935
2068
|
const targetEnv = resolveCloudEnv(manifest.cloud, targetName);
|
|
1936
|
-
const
|
|
1937
|
-
const targetDbUrl = resolveDbUrlForEnv(targetEnv,
|
|
2069
|
+
const dbPassword2 = resolveTargetPassword(targetEnv.projectRef);
|
|
2070
|
+
const targetDbUrl = resolveDbUrlForEnv(targetEnv, dbPassword2);
|
|
1938
2071
|
const devDbUrl = await resolveDbUrl(cwd);
|
|
1939
2072
|
return { devDbUrl, targetEnv, targetDbUrl, targetName };
|
|
1940
2073
|
}
|
|
@@ -2099,7 +2232,7 @@ async function syncSecrets(cwd, resolved, opts) {
|
|
|
2099
2232
|
`);
|
|
2100
2233
|
const toSet = {};
|
|
2101
2234
|
for (const key of missingKeys) {
|
|
2102
|
-
const value = await
|
|
2235
|
+
const value = await input2({
|
|
2103
2236
|
message: `${key}:`,
|
|
2104
2237
|
theme,
|
|
2105
2238
|
validate: (val) => val.trim().length > 0 || "Value is required (press Ctrl+C to skip all)"
|
|
@@ -2115,7 +2248,7 @@ async function syncSecrets(cwd, resolved, opts) {
|
|
|
2115
2248
|
// src/env.ts
|
|
2116
2249
|
import fs8 from "fs";
|
|
2117
2250
|
import path8 from "path";
|
|
2118
|
-
import { confirm as confirm2, input as
|
|
2251
|
+
import { confirm as confirm2, input as input3, select } from "@inquirer/prompts";
|
|
2119
2252
|
|
|
2120
2253
|
// src/claude-md.ts
|
|
2121
2254
|
import fs7 from "fs";
|
|
@@ -2259,14 +2392,14 @@ async function envAdd(name, cwd, opts = {}) {
|
|
|
2259
2392
|
}
|
|
2260
2393
|
await ensureAccessToken(opts.nonInteractive, cwd);
|
|
2261
2394
|
let env2;
|
|
2262
|
-
let
|
|
2395
|
+
let dbPassword2;
|
|
2263
2396
|
if (opts.projectRef) {
|
|
2264
2397
|
const projects = await listProjects();
|
|
2265
2398
|
const project = projects.find((p) => p.id === opts.projectRef);
|
|
2266
2399
|
if (!project) {
|
|
2267
2400
|
throw new Error(`Project ${opts.projectRef} not found.`);
|
|
2268
2401
|
}
|
|
2269
|
-
|
|
2402
|
+
dbPassword2 = await promptForPassword(opts.nonInteractive, project.id);
|
|
2270
2403
|
env2 = {
|
|
2271
2404
|
projectRef: project.id,
|
|
2272
2405
|
region: project.region,
|
|
@@ -2286,17 +2419,17 @@ async function envAdd(name, cwd, opts = {}) {
|
|
|
2286
2419
|
if (method === "existing") {
|
|
2287
2420
|
const result = await connectExistingProject();
|
|
2288
2421
|
env2 = result.env;
|
|
2289
|
-
|
|
2422
|
+
dbPassword2 = result.dbPassword;
|
|
2290
2423
|
} else {
|
|
2291
2424
|
const result = await createNewProject(name, opts);
|
|
2292
2425
|
env2 = result.env;
|
|
2293
|
-
|
|
2426
|
+
dbPassword2 = result.dbPassword;
|
|
2294
2427
|
}
|
|
2295
2428
|
}
|
|
2296
2429
|
addCloudEnvironment(cwd, name, env2);
|
|
2297
2430
|
console.log(`
|
|
2298
2431
|
${blue("\u2714")} Environment "${name}" added to agentlink.json`);
|
|
2299
|
-
saveProjectCredential(env2.projectRef,
|
|
2432
|
+
saveProjectCredential(env2.projectRef, dbPassword2);
|
|
2300
2433
|
console.log(` ${blue("\u2714")} Credentials stored securely`);
|
|
2301
2434
|
if (name.startsWith("dev") || name === "staging") {
|
|
2302
2435
|
const shouldSync = opts.nonInteractive ? false : await confirm2({
|
|
@@ -2318,7 +2451,7 @@ async function envAdd(name, cwd, opts = {}) {
|
|
|
2318
2451
|
console.log(` Deploy with: ${dim("npx @agentlink.sh/cli@latest deploy --env " + name)}`);
|
|
2319
2452
|
}
|
|
2320
2453
|
}
|
|
2321
|
-
async function promptForPassword(nonInteractive) {
|
|
2454
|
+
async function promptForPassword(nonInteractive, projectRef) {
|
|
2322
2455
|
if (nonInteractive) {
|
|
2323
2456
|
const envPass = process.env.SUPABASE_DB_PASSWORD;
|
|
2324
2457
|
if (!envPass) {
|
|
@@ -2326,7 +2459,13 @@ async function promptForPassword(nonInteractive) {
|
|
|
2326
2459
|
}
|
|
2327
2460
|
return envPass;
|
|
2328
2461
|
}
|
|
2329
|
-
|
|
2462
|
+
if (projectRef) {
|
|
2463
|
+
const resetUrl = `https://supabase.com/dashboard/project/${projectRef}/settings/database`;
|
|
2464
|
+
console.log(` ${dim("Don't know the password? Reset it at:")}`);
|
|
2465
|
+
console.log(` ${dim(resetUrl)}`);
|
|
2466
|
+
console.log();
|
|
2467
|
+
}
|
|
2468
|
+
return await input3({
|
|
2330
2469
|
message: "Database password:",
|
|
2331
2470
|
theme: theme2,
|
|
2332
2471
|
transformer: (value, { isFinal }) => {
|
|
@@ -2350,18 +2489,18 @@ async function connectExistingProject() {
|
|
|
2350
2489
|
}))
|
|
2351
2490
|
});
|
|
2352
2491
|
const project = projects.find((p) => p.id === projectRef);
|
|
2353
|
-
const
|
|
2492
|
+
const dbPassword2 = await promptForPassword(false, project.id);
|
|
2354
2493
|
return {
|
|
2355
2494
|
env: {
|
|
2356
2495
|
projectRef: project.id,
|
|
2357
2496
|
region: project.region,
|
|
2358
2497
|
apiUrl: `https://${project.id}.supabase.co`
|
|
2359
2498
|
},
|
|
2360
|
-
dbPassword
|
|
2499
|
+
dbPassword: dbPassword2
|
|
2361
2500
|
};
|
|
2362
2501
|
}
|
|
2363
2502
|
async function createNewProject(envName, opts) {
|
|
2364
|
-
const { listOrganizations: listOrganizations2, listRegions: listRegions2 } = await import("./cloud-
|
|
2503
|
+
const { listOrganizations: listOrganizations2, listRegions: listRegions2 } = await import("./cloud-QNZQNIS5.js");
|
|
2365
2504
|
const orgs = await listOrganizations2();
|
|
2366
2505
|
if (orgs.length === 0) {
|
|
2367
2506
|
throw new Error("No Supabase organizations found. Create one at https://supabase.com/dashboard.");
|
|
@@ -2377,13 +2516,13 @@ async function createNewProject(envName, opts) {
|
|
|
2377
2516
|
theme: theme2,
|
|
2378
2517
|
choices: regions.map((r) => ({ name: `${r.name} (${r.id})`, value: r.id }))
|
|
2379
2518
|
});
|
|
2380
|
-
const projectName = await
|
|
2519
|
+
const projectName = await input3({
|
|
2381
2520
|
message: "Project name:",
|
|
2382
2521
|
default: `agentlink-${envName}`,
|
|
2383
2522
|
theme: theme2
|
|
2384
2523
|
});
|
|
2385
2524
|
const { randomBytes } = await import("crypto");
|
|
2386
|
-
const
|
|
2525
|
+
const dbPassword2 = randomBytes(24).toString("base64url");
|
|
2387
2526
|
console.log(`
|
|
2388
2527
|
Creating project "${projectName}" in ${region}...
|
|
2389
2528
|
`);
|
|
@@ -2391,7 +2530,7 @@ async function createNewProject(envName, opts) {
|
|
|
2391
2530
|
orgId,
|
|
2392
2531
|
name: projectName,
|
|
2393
2532
|
region,
|
|
2394
|
-
dbPass:
|
|
2533
|
+
dbPass: dbPassword2
|
|
2395
2534
|
});
|
|
2396
2535
|
await waitForProjectReady(project.id);
|
|
2397
2536
|
console.log(` ${blue("\u2714")} Project created: ${project.id}`);
|
|
@@ -2401,7 +2540,7 @@ async function createNewProject(envName, opts) {
|
|
|
2401
2540
|
region: project.region,
|
|
2402
2541
|
apiUrl: `https://${project.id}.supabase.co`
|
|
2403
2542
|
},
|
|
2404
|
-
dbPassword
|
|
2543
|
+
dbPassword: dbPassword2
|
|
2405
2544
|
};
|
|
2406
2545
|
}
|
|
2407
2546
|
async function envUse(name, cwd) {
|
|
@@ -2450,7 +2589,7 @@ async function envUse(name, cwd) {
|
|
|
2450
2589
|
await ensureAccessToken(true, cwd);
|
|
2451
2590
|
const env2 = manifest.cloud.environments[name];
|
|
2452
2591
|
const keys = await getApiKeys(env2.projectRef);
|
|
2453
|
-
const
|
|
2592
|
+
const dbPassword2 = loadProjectCredential(env2.projectRef);
|
|
2454
2593
|
const frontend = manifest.frontend;
|
|
2455
2594
|
const urlKey = frontend === "nextjs" ? "NEXT_PUBLIC_SUPABASE_URL" : "VITE_SUPABASE_URL";
|
|
2456
2595
|
const keyKey = frontend === "nextjs" ? "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY" : "VITE_SUPABASE_PUBLISHABLE_KEY";
|
|
@@ -2460,8 +2599,8 @@ async function envUse(name, cwd) {
|
|
|
2460
2599
|
SB_PUBLISHABLE_KEY: keys.publishableKey,
|
|
2461
2600
|
SB_SECRET_KEY: keys.secretKey
|
|
2462
2601
|
};
|
|
2463
|
-
if (
|
|
2464
|
-
vars.SUPABASE_DB_URL = resolveDbUrlForEnv(env2,
|
|
2602
|
+
if (dbPassword2) {
|
|
2603
|
+
vars.SUPABASE_DB_URL = resolveDbUrlForEnv(env2, dbPassword2);
|
|
2465
2604
|
}
|
|
2466
2605
|
}
|
|
2467
2606
|
writeManagedEnv(cwd, name, vars);
|
|
@@ -2516,6 +2655,120 @@ async function envRemove(name, cwd, opts = {}) {
|
|
|
2516
2655
|
removeCloudEnvironment(cwd, name);
|
|
2517
2656
|
console.log(` ${blue("\u2714")} Environment "${name}" removed.`);
|
|
2518
2657
|
}
|
|
2658
|
+
async function envRelink(name, cwd, opts = {}) {
|
|
2659
|
+
const manifest = readManifest(cwd);
|
|
2660
|
+
if (!manifest) {
|
|
2661
|
+
throw new Error("No agentlink.json found. Run `agentlink` to scaffold a project first.");
|
|
2662
|
+
}
|
|
2663
|
+
if (opts.token) {
|
|
2664
|
+
process.env.SUPABASE_ACCESS_TOKEN = opts.token;
|
|
2665
|
+
}
|
|
2666
|
+
await ensureAccessToken(opts.nonInteractive, cwd);
|
|
2667
|
+
let env2;
|
|
2668
|
+
let dbPassword2;
|
|
2669
|
+
if (opts.projectRef) {
|
|
2670
|
+
const projects = await listProjects();
|
|
2671
|
+
const project = projects.find((p) => p.id === opts.projectRef);
|
|
2672
|
+
if (!project) {
|
|
2673
|
+
throw new Error(`Project ${opts.projectRef} not found.`);
|
|
2674
|
+
}
|
|
2675
|
+
dbPassword2 = await promptForPassword(opts.nonInteractive, project.id);
|
|
2676
|
+
env2 = {
|
|
2677
|
+
projectRef: project.id,
|
|
2678
|
+
region: project.region,
|
|
2679
|
+
apiUrl: `https://${project.id}.supabase.co`
|
|
2680
|
+
};
|
|
2681
|
+
} else if (opts.nonInteractive) {
|
|
2682
|
+
throw new Error("--project-ref is required in non-interactive mode.");
|
|
2683
|
+
} else {
|
|
2684
|
+
const method = await select({
|
|
2685
|
+
message: `How do you want to relink the "${name}" environment?`,
|
|
2686
|
+
theme: theme2,
|
|
2687
|
+
choices: [
|
|
2688
|
+
{ name: "Connect an existing Supabase project", value: "existing" },
|
|
2689
|
+
{ name: "Create a new Supabase project", value: "new" }
|
|
2690
|
+
]
|
|
2691
|
+
});
|
|
2692
|
+
if (method === "existing") {
|
|
2693
|
+
const result = await connectExistingProject();
|
|
2694
|
+
env2 = result.env;
|
|
2695
|
+
dbPassword2 = result.dbPassword;
|
|
2696
|
+
} else {
|
|
2697
|
+
const result = await createNewProject(name, opts);
|
|
2698
|
+
env2 = result.env;
|
|
2699
|
+
dbPassword2 = result.dbPassword;
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
if (!manifest.cloud) {
|
|
2703
|
+
manifest.cloud = { default: "local", environments: {} };
|
|
2704
|
+
}
|
|
2705
|
+
manifest.cloud.environments[name] = env2;
|
|
2706
|
+
writeManifest(cwd, manifest);
|
|
2707
|
+
console.log(`
|
|
2708
|
+
${blue("\u2714")} Environment "${name}" relinked to ${env2.projectRef}`);
|
|
2709
|
+
saveProjectCredential(env2.projectRef, dbPassword2);
|
|
2710
|
+
console.log(` ${blue("\u2714")} Credentials stored securely`);
|
|
2711
|
+
const keys = await getApiKeys(env2.projectRef);
|
|
2712
|
+
let dbUrl;
|
|
2713
|
+
try {
|
|
2714
|
+
dbUrl = await resolvePoolerDbUrl(env2.projectRef, dbPassword2);
|
|
2715
|
+
} catch {
|
|
2716
|
+
dbUrl = resolveDbUrlForEnv(env2, dbPassword2);
|
|
2717
|
+
}
|
|
2718
|
+
const frontend = manifest.frontend;
|
|
2719
|
+
const urlKey = frontend === "nextjs" ? "NEXT_PUBLIC_SUPABASE_URL" : "VITE_SUPABASE_URL";
|
|
2720
|
+
const keyKey = frontend === "nextjs" ? "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY" : "VITE_SUPABASE_PUBLISHABLE_KEY";
|
|
2721
|
+
writeManagedEnv(cwd, name, {
|
|
2722
|
+
[urlKey]: env2.apiUrl,
|
|
2723
|
+
[keyKey]: keys.publishableKey,
|
|
2724
|
+
SB_PUBLISHABLE_KEY: keys.publishableKey,
|
|
2725
|
+
SB_SECRET_KEY: keys.secretKey,
|
|
2726
|
+
SUPABASE_DB_URL: dbUrl
|
|
2727
|
+
});
|
|
2728
|
+
console.log(` ${blue("\u2714")} .env.local updated (pooler URL from API)`);
|
|
2729
|
+
writeClaudeMd({
|
|
2730
|
+
projectDir: cwd,
|
|
2731
|
+
mode: "cloud",
|
|
2732
|
+
projectRef: env2.projectRef,
|
|
2733
|
+
region: env2.region,
|
|
2734
|
+
envName: name
|
|
2735
|
+
});
|
|
2736
|
+
console.log(` ${blue("\u2714")} CLAUDE.md updated`);
|
|
2737
|
+
console.log(`
|
|
2738
|
+
${blue("\u25CF")} Linking to Supabase project...`);
|
|
2739
|
+
await runCommand(
|
|
2740
|
+
`${sb()} link --project-ref ${env2.projectRef}`,
|
|
2741
|
+
cwd,
|
|
2742
|
+
void 0,
|
|
2743
|
+
{ SUPABASE_DB_PASSWORD: dbPassword2 }
|
|
2744
|
+
);
|
|
2745
|
+
console.log(` ${blue("\u2714")} Linked`);
|
|
2746
|
+
const migrationsDir = path8.join(cwd, "supabase", "migrations");
|
|
2747
|
+
const hasMigrations = fs8.existsSync(migrationsDir) && fs8.readdirSync(migrationsDir).some((f) => f.endsWith(".sql"));
|
|
2748
|
+
if (hasMigrations) {
|
|
2749
|
+
console.log(`
|
|
2750
|
+
${blue("\u25CF")} Pushing migrations to new project...`);
|
|
2751
|
+
await runCommand(
|
|
2752
|
+
`${sb()} db push`,
|
|
2753
|
+
cwd,
|
|
2754
|
+
void 0,
|
|
2755
|
+
{ SUPABASE_DB_PASSWORD: dbPassword2 }
|
|
2756
|
+
);
|
|
2757
|
+
console.log(` ${blue("\u2714")} Migrations pushed`);
|
|
2758
|
+
}
|
|
2759
|
+
const functionsDir = path8.join(cwd, "supabase", "functions");
|
|
2760
|
+
if (fs8.existsSync(functionsDir)) {
|
|
2761
|
+
const hasFunctions = fs8.readdirSync(functionsDir, { withFileTypes: true }).some((e) => e.isDirectory() && !e.name.startsWith("_"));
|
|
2762
|
+
if (hasFunctions) {
|
|
2763
|
+
console.log(`
|
|
2764
|
+
${blue("\u25CF")} Deploying edge functions...`);
|
|
2765
|
+
await runCommand(`${sb()} functions deploy --project-ref ${env2.projectRef}`, cwd);
|
|
2766
|
+
console.log(` ${blue("\u2714")} Edge functions deployed`);
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
console.log(`
|
|
2770
|
+
${blue("Done.")} Environment "${name}" relinked to ${dim(env2.projectRef)}`);
|
|
2771
|
+
}
|
|
2519
2772
|
async function scaffoldCiTemplate(cwd, envName) {
|
|
2520
2773
|
const workflowDir = path8.join(cwd, ".github", "workflows");
|
|
2521
2774
|
fs8.mkdirSync(workflowDir, { recursive: true });
|
|
@@ -3382,7 +3635,7 @@ import { execSync } from "child_process";
|
|
|
3382
3635
|
import fs15 from "fs";
|
|
3383
3636
|
import path15 from "path";
|
|
3384
3637
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3385
|
-
import { confirm as confirm5, input as
|
|
3638
|
+
import { confirm as confirm5, input as input5, select as select3 } from "@inquirer/prompts";
|
|
3386
3639
|
|
|
3387
3640
|
// src/banner.ts
|
|
3388
3641
|
var TITLE = [
|
|
@@ -3504,7 +3757,7 @@ function clearSession(name) {
|
|
|
3504
3757
|
// src/scaffold.ts
|
|
3505
3758
|
import fs13 from "fs";
|
|
3506
3759
|
import path13 from "path";
|
|
3507
|
-
import { confirm as confirm3, input as
|
|
3760
|
+
import { confirm as confirm3, input as input4 } from "@inquirer/prompts";
|
|
3508
3761
|
|
|
3509
3762
|
// src/claude-settings.ts
|
|
3510
3763
|
import fs12 from "fs";
|
|
@@ -3521,7 +3774,7 @@ var CLAUDE_SETTINGS = {
|
|
|
3521
3774
|
"Bash(supabase functions:*)",
|
|
3522
3775
|
"Bash(psql:*)",
|
|
3523
3776
|
"Bash(npx create-next-app@latest:*)",
|
|
3524
|
-
"Bash(npx
|
|
3777
|
+
"Bash(npx @agentlink.sh/cli@latest:*)",
|
|
3525
3778
|
"Bash(npm install:*)",
|
|
3526
3779
|
"Bash(npx next:*)",
|
|
3527
3780
|
"Bash(npx vite:*)",
|
|
@@ -3748,7 +4001,7 @@ async function scaffold(options) {
|
|
|
3748
4001
|
console.log(dim(` If you don't know this password you can reset it from your Supabase dashboard:`));
|
|
3749
4002
|
console.log(dim(` ${link(resetUrl)}`));
|
|
3750
4003
|
console.log();
|
|
3751
|
-
const dbPass2 = await
|
|
4004
|
+
const dbPass2 = await input4({
|
|
3752
4005
|
message: "Database password for existing project",
|
|
3753
4006
|
theme: theme3,
|
|
3754
4007
|
validate: (val) => val.trim().length > 0 || "Password is required"
|
|
@@ -3836,16 +4089,19 @@ SUPABASE_DB_PASSWORD=${ctx.dbPass}
|
|
|
3836
4089
|
}
|
|
3837
4090
|
spinner.text = "Setting up database \u2014 Pushing infrastructure migrations";
|
|
3838
4091
|
await runCommand(`${sb()} db push`, projectDir, void 0, cloudEnv);
|
|
3839
|
-
spinner.text = "Setting up database \u2014 Writing application migration";
|
|
3840
|
-
const setupBase = /* @__PURE__ */ new Date();
|
|
3841
|
-
const setupContent = getTemplateSQLEntries().join("\n\n");
|
|
3842
4092
|
const migrationsDir = path13.join(projectDir, "supabase", "migrations");
|
|
3843
|
-
const
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
4093
|
+
const existingSetup = fs13.readdirSync(migrationsDir).find((f) => f.endsWith("_agentlink_setup.sql"));
|
|
4094
|
+
if (!existingSetup) {
|
|
4095
|
+
spinner.text = "Setting up database \u2014 Writing application migration";
|
|
4096
|
+
const setupBase = /* @__PURE__ */ new Date();
|
|
4097
|
+
const setupContent = getTemplateSQLEntries().join("\n\n");
|
|
4098
|
+
const setupVersion = setupBase.toISOString().replace(/\D/g, "").slice(0, 14);
|
|
4099
|
+
const setupFilename = `${setupVersion}_agentlink_setup.sql`;
|
|
4100
|
+
fs13.writeFileSync(path13.join(migrationsDir, setupFilename), setupContent);
|
|
4101
|
+
spinner.text = "Setting up database \u2014 Writing post-setup migrations";
|
|
4102
|
+
const postBase = new Date(setupBase.getTime() + 2e3);
|
|
4103
|
+
writeMigrationTemplates(projectDir, POST_SETUP_MIGRATIONS, postBase);
|
|
4104
|
+
}
|
|
3849
4105
|
spinner.text = "Setting up database \u2014 Pushing migrations";
|
|
3850
4106
|
await runCommand(`${sb()} db push`, projectDir, void 0, cloudEnv);
|
|
3851
4107
|
spinner.text = "Setting up database \u2014 Storing vault secrets";
|
|
@@ -3890,19 +4146,22 @@ SUPABASE_DB_PASSWORD=${ctx.dbPass}
|
|
|
3890
4146
|
for (const sql of getTemplateSQLEntries()) {
|
|
3891
4147
|
await runSQL(sql, ctx.dbUrl);
|
|
3892
4148
|
}
|
|
3893
|
-
spinner.text = "Setting up database \u2014 Writing application migration";
|
|
3894
|
-
const setupBase = /* @__PURE__ */ new Date();
|
|
3895
|
-
const setupContent = getTemplateSQLEntries().join("\n\n");
|
|
3896
4149
|
const migrationsDir = path13.join(projectDir, "supabase", "migrations");
|
|
3897
|
-
const
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
await repairMigrations(projectDir,
|
|
4150
|
+
const existingSetup = fs13.readdirSync(migrationsDir).find((f) => f.endsWith("_agentlink_setup.sql"));
|
|
4151
|
+
if (!existingSetup) {
|
|
4152
|
+
spinner.text = "Setting up database \u2014 Writing application migration";
|
|
4153
|
+
const setupBase = /* @__PURE__ */ new Date();
|
|
4154
|
+
const setupContent = getTemplateSQLEntries().join("\n\n");
|
|
4155
|
+
const setupVersion = setupBase.toISOString().replace(/\D/g, "").slice(0, 14);
|
|
4156
|
+
const setupFilename = `${setupVersion}_agentlink_setup.sql`;
|
|
4157
|
+
fs13.writeFileSync(path13.join(migrationsDir, setupFilename), setupContent);
|
|
4158
|
+
await repairMigrations(projectDir, [setupVersion]);
|
|
4159
|
+
spinner.text = "Setting up database \u2014 Writing post-setup migrations";
|
|
4160
|
+
const postBase = new Date(setupBase.getTime() + 2e3);
|
|
4161
|
+
const postVersions = writeMigrationTemplates(projectDir, POST_SETUP_MIGRATIONS, postBase);
|
|
4162
|
+
if (postVersions.length > 0) {
|
|
4163
|
+
await repairMigrations(projectDir, postVersions);
|
|
4164
|
+
}
|
|
3906
4165
|
}
|
|
3907
4166
|
spinner.text = "Setting up database \u2014 Storing vault secrets";
|
|
3908
4167
|
await runSQL(seedSQL(ctx.publishableKey, ctx.secretKey), ctx.dbUrl);
|
|
@@ -4720,7 +4979,7 @@ Run without --resume to start fresh.`
|
|
|
4720
4979
|
if (nonInteractive) {
|
|
4721
4980
|
throw new Error("Project name required in non-interactive mode. Provide a name as the first argument.");
|
|
4722
4981
|
}
|
|
4723
|
-
name = await
|
|
4982
|
+
name = await input5({
|
|
4724
4983
|
message: "Project name",
|
|
4725
4984
|
default: "my-app",
|
|
4726
4985
|
theme: theme5,
|
|
@@ -4744,7 +5003,7 @@ ${red("Error:")} ${error}`);
|
|
|
4744
5003
|
if (nonInteractive) {
|
|
4745
5004
|
throw new Error("Project name required in non-interactive mode. Provide a name as the first argument.");
|
|
4746
5005
|
}
|
|
4747
|
-
name = await
|
|
5006
|
+
name = await input5({
|
|
4748
5007
|
message: "Project name",
|
|
4749
5008
|
default: "my-app",
|
|
4750
5009
|
theme: theme5,
|
|
@@ -4959,7 +5218,7 @@ program.command("info [name]").description("Show documentation about AgentLink c
|
|
|
4959
5218
|
program.command("login").description("Authenticate with Supabase via OAuth (opens browser)").action(async () => {
|
|
4960
5219
|
try {
|
|
4961
5220
|
const { oauthLogin } = await import("./oauth-VTSNCG7U.js");
|
|
4962
|
-
const { saveCredentials: saveCredentials2, loadCredentials: loadCredentials2, credentialsPath: credentialsPath2 } = await import("./cloud-
|
|
5221
|
+
const { saveCredentials: saveCredentials2, loadCredentials: loadCredentials2, credentialsPath: credentialsPath2 } = await import("./cloud-QNZQNIS5.js");
|
|
4963
5222
|
console.log();
|
|
4964
5223
|
const tokens = await oauthLogin();
|
|
4965
5224
|
saveCredentials2({
|
|
@@ -5073,6 +5332,35 @@ ${red("Error:")} ${err.message}`);
|
|
|
5073
5332
|
process.exit(1);
|
|
5074
5333
|
}
|
|
5075
5334
|
});
|
|
5335
|
+
db.command("rebuild").description("Nuke migrations, re-apply schemas, and regenerate migration files").option("--debug", "Write debug log to agentlink-debug.log").option("--db-url <url>", "Database URL (auto-detects from .env.local or supabase status)").option("--env <name>", "Target cloud environment").action(async (opts) => {
|
|
5336
|
+
if (opts.debug) initLog(true);
|
|
5337
|
+
try {
|
|
5338
|
+
await dbRebuild({ dbUrl: opts.dbUrl, cwd: process.cwd(), env: opts.env });
|
|
5339
|
+
} catch (err) {
|
|
5340
|
+
console.error(`
|
|
5341
|
+
${red("Error:")} ${err.message}`);
|
|
5342
|
+
process.exit(1);
|
|
5343
|
+
}
|
|
5344
|
+
});
|
|
5345
|
+
db.command("url").description("Show the correct pooler DB URL from Supabase (--fix to update .env.local)").option("--fix", "Update .env.local with the correct URL").option("--debug", "Write debug log to agentlink-debug.log").option("--db-url <url>", "Database URL (auto-detects from .env.local or supabase status)").option("--env <name>", "Target cloud environment").action(async (opts) => {
|
|
5346
|
+
if (opts.debug) initLog(true);
|
|
5347
|
+
try {
|
|
5348
|
+
await dbUrlCheck({ dbUrl: opts.dbUrl, cwd: process.cwd(), env: opts.env, fix: opts.fix });
|
|
5349
|
+
} catch (err) {
|
|
5350
|
+
console.error(`
|
|
5351
|
+
${red("Error:")} ${err.message}`);
|
|
5352
|
+
process.exit(1);
|
|
5353
|
+
}
|
|
5354
|
+
});
|
|
5355
|
+
db.command("password [value]").description("Show or set the database password for the active cloud project").action(async (value) => {
|
|
5356
|
+
try {
|
|
5357
|
+
await dbPassword(process.cwd(), value);
|
|
5358
|
+
} catch (err) {
|
|
5359
|
+
console.error(`
|
|
5360
|
+
${red("Error:")} ${err.message}`);
|
|
5361
|
+
process.exit(1);
|
|
5362
|
+
}
|
|
5363
|
+
});
|
|
5076
5364
|
program.addCommand(db);
|
|
5077
5365
|
program.command("deploy").description("Deploy schema changes and edge functions to a target environment").option("--env <name>", "Target environment (default: prod)").option("--ci", "Non-interactive mode for CI/CD").option("--allow-warnings", "Proceed past data-risk warnings (CI only)").option("--dry-run", "Show what would be deployed without applying").option("--setup-ci", "Scaffold GitHub Actions workflow").option("--debug", "Write debug log to agentlink-debug.log").action(async (opts) => {
|
|
5078
5366
|
if (opts.debug) initLog(true);
|
|
@@ -5114,6 +5402,16 @@ ${red("Error:")} ${err.message}`);
|
|
|
5114
5402
|
process.exit(1);
|
|
5115
5403
|
}
|
|
5116
5404
|
});
|
|
5405
|
+
env.command("relink <name>").description("Relink an environment to a new Supabase project (keeps migrations)").option("--token <token>", "Supabase access token").option("--org <id>", "Supabase organization ID").option("--region <region>", "Supabase Cloud region").option("--project-ref <ref>", "Connect to an existing Supabase project").option("--non-interactive", "Error instead of prompting").option("--debug", "Write debug log to agentlink-debug.log").action(async (name, opts) => {
|
|
5406
|
+
if (opts.debug) initLog(true);
|
|
5407
|
+
try {
|
|
5408
|
+
await envRelink(name, process.cwd(), opts);
|
|
5409
|
+
} catch (err) {
|
|
5410
|
+
console.error(`
|
|
5411
|
+
${red("Error:")} ${err.message}`);
|
|
5412
|
+
process.exit(1);
|
|
5413
|
+
}
|
|
5414
|
+
});
|
|
5117
5415
|
env.command("remove <name>").description("Remove a cloud environment").option("-y, --yes", "Skip confirmation").option("--debug", "Write debug log to agentlink-debug.log").action(async (name, opts) => {
|
|
5118
5416
|
if (opts.debug) initLog(true);
|
|
5119
5417
|
try {
|