create-kontrolia-auth 2.3.0 → 2.4.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.
- package/README.md +16 -5
- package/dist/index.js +48 -1
- package/dist/steps/database.d.ts.map +1 -1
- package/dist/steps/database.js +27 -17
- package/dist/utils/supabase-management-api.d.ts +12 -0
- package/dist/utils/supabase-management-api.d.ts.map +1 -1
- package/dist/utils/supabase-management-api.js +32 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -17,11 +17,13 @@ Eso hace, en orden:
|
|
|
17
17
|
3. **Base de datos** — dos caminos independientes:
|
|
18
18
|
- _Ya tengo Supabase_ (Cloud o self-hosted): pegas URL + keys, sin Docker.
|
|
19
19
|
Si es un proyecto Supabase Cloud, además ofrece **configurar
|
|
20
|
-
automáticamente** los
|
|
21
|
-
exponer el schema `kontrolia_auth` en la API
|
|
22
|
-
Token Hook
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
automáticamente** los tres ajustes que solo viven en su Dashboard —
|
|
21
|
+
exponer el schema `kontrolia_auth` en la API, activar el Custom Access
|
|
22
|
+
Token Hook, y activar el servidor OAuth 2.1 (para que apps de otros
|
|
23
|
+
dominios puedan iniciar sesión) — vía la Management API de Supabase
|
|
24
|
+
(pide un Personal Access Token de tu cuenta, distinto a las keys del
|
|
25
|
+
proyecto; no se guarda). Si lo saltas, muestra las instrucciones
|
|
26
|
+
manuales exactas para lo que falte.
|
|
25
27
|
- _Crear uno nuevo self-hosted_: genera `docker/.env`, **levanta los
|
|
26
28
|
contenedores por ti** (`docker compose up -d`) y aplica las migraciones
|
|
27
29
|
en cuanto Postgres está listo.
|
|
@@ -49,11 +51,20 @@ pnpm --filter create-kontrolia-auth dev
|
|
|
49
51
|
| `npx create-kontrolia-auth update` | Descarga el código nuevo (`git pull`) sobre una copia ya instalada, instala dependencias, y aplica las migraciones nuevas. |
|
|
50
52
|
| `npx create-kontrolia-auth deploy` | Va directo al paso de despliegue (URLs, cliente OAuth, `.env.local`, creación automática en Vercel) sin repetir las preguntas de base de datos ni de aplicación — para cuando ya tienes todo corriendo y solo quieres (re)conectar auth-server o admin-panel a un nuevo destino. |
|
|
51
53
|
| `npx create-kontrolia-auth migrate` | Aplica/re-aplica las migraciones contra una base existente. |
|
|
54
|
+
| `npx create-kontrolia-auth grant-admin <email>` | Otorga platform admin a un usuario ya registrado, directo contra la base de datos (connection string, sin pasar por login). Es el camino de recuperación cuando no hay ningún platform admin — por ejemplo si instalaste sobre un proyecto Supabase que ya tenía usuarios de otra app, y por eso el primer registro no se ascendió automáticamente. |
|
|
52
55
|
| `npx create-kontrolia-auth doctor` | Solo revisa que tengas Node, pnpm, git y Docker. |
|
|
53
56
|
|
|
54
57
|
Las migraciones son **idempotentes** (solo tocan el schema `kontrolia_auth`), así
|
|
55
58
|
que `migrate` es seguro de correr las veces que necesites.
|
|
56
59
|
|
|
60
|
+
`grant-admin` usa el mismo modelo de confianza que `migrate`: quien tiene la
|
|
61
|
+
connection string de Postgres ya puede hacer cualquier cosa en esa base de
|
|
62
|
+
datos, así que no pasa por el login ni por el check de `is_platform_admin` de
|
|
63
|
+
la app — por diseño, es la única vía que funciona cuando no hay ningún
|
|
64
|
+
platform admin (la página "Platform admins" del admin-panel requiere ya serlo
|
|
65
|
+
para poder usarla). Una vez que tengas al menos uno, agregar más se hace desde
|
|
66
|
+
ahí, sin volver a tocar la terminal.
|
|
67
|
+
|
|
57
68
|
`update` es para quien instaló con `npx create-kontrolia-auth mi-app` y ya
|
|
58
69
|
tiene esa carpeta corriendo — no descarta cambios locales (se detiene si hay
|
|
59
70
|
algo sin guardar) y solo avanza si puede hacer fast-forward sobre `origin/main`.
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as p from "@clack/prompts";
|
|
3
|
-
import { migrate } from "@kontrolia/db";
|
|
3
|
+
import { grantPlatformAdmin, migrate } from "@kontrolia/db";
|
|
4
4
|
import { askApplicationStep } from "./steps/application.js";
|
|
5
5
|
import { askDatabaseStep } from "./steps/database.js";
|
|
6
6
|
import { askDeploymentStep } from "./steps/deployment.js";
|
|
@@ -42,6 +42,51 @@ async function runMigrateCommand() {
|
|
|
42
42
|
process.exitCode = 1;
|
|
43
43
|
p.outro(ok ? "Base de datos al día." : "Revisa la connection string y vuelve a intentar.");
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* `create-kontrolia-auth grant-admin <email>` — the break-glass recovery
|
|
47
|
+
* path for platform admins. The normal way to grant one (admin-panel's
|
|
48
|
+
* "Platform admins" page) requires already being a platform admin to call
|
|
49
|
+
* its API — which is exactly the problem when the automatic first-admin
|
|
50
|
+
* bootstrap didn't fire (e.g. installing onto an existing Supabase project
|
|
51
|
+
* that already had unrelated `auth.users` rows, so the "first user ever"
|
|
52
|
+
* check never matched) or a project ends up with zero admins some other
|
|
53
|
+
* way. This bypasses the app's authorization layer entirely, same trust
|
|
54
|
+
* model as `migrate`: whoever has the Postgres connection string can
|
|
55
|
+
* already do anything in that database.
|
|
56
|
+
*/
|
|
57
|
+
async function runGrantAdminCommand(email) {
|
|
58
|
+
console.clear();
|
|
59
|
+
p.intro("KontrolIA Auth — otorgar platform admin");
|
|
60
|
+
if (!email) {
|
|
61
|
+
p.log.error("Uso: npx create-kontrolia-auth grant-admin <email>");
|
|
62
|
+
p.outro("Falta el correo.");
|
|
63
|
+
process.exitCode = 1;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const url = await p.text({
|
|
67
|
+
message: "Connection string de Postgres",
|
|
68
|
+
placeholder: "postgres://postgres:...@host:5432/postgres",
|
|
69
|
+
});
|
|
70
|
+
if (p.isCancel(url) || !url) {
|
|
71
|
+
p.cancel("Cancelado.");
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
const s = p.spinner();
|
|
75
|
+
s.start(`Buscando a ${email} y otorgando platform admin`);
|
|
76
|
+
try {
|
|
77
|
+
const result = await grantPlatformAdmin({ connectionString: url, email });
|
|
78
|
+
s.stop(result.alreadyGranted
|
|
79
|
+
? `${result.email} ya era platform admin — nada que hacer.`
|
|
80
|
+
: `${result.email} ahora es platform admin.`);
|
|
81
|
+
p.outro("Listo. Si tenía una sesión abierta, debe cerrarla y volver a entrar para que el token nuevo lleve el claim.");
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
s.stop("Falló");
|
|
85
|
+
p.log.error(error.message);
|
|
86
|
+
process.exitCode = 1;
|
|
87
|
+
p.outro("Revisa el correo y la connection string, y vuelve a intentar.");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
45
90
|
/** `create-kontrolia-auth doctor` — just the requirements check. */
|
|
46
91
|
async function runDoctor() {
|
|
47
92
|
console.clear();
|
|
@@ -207,6 +252,8 @@ async function main() {
|
|
|
207
252
|
return runUpdateCommand();
|
|
208
253
|
if (first === "deploy")
|
|
209
254
|
return runDeployCommand();
|
|
255
|
+
if (first === "grant-admin")
|
|
256
|
+
return runGrantAdminCommand(args[1]);
|
|
210
257
|
// `create <dir>` / `install <dir>` / `<dir>` / (nothing) all reach install.
|
|
211
258
|
const dir = first === "create" || first === "install" ? args[1] : first;
|
|
212
259
|
return runInstall(dir);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/steps/database.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,UAAU,GAAG,iBAAiB,CAAC;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/steps/database.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,UAAU,GAAG,iBAAiB,CAAC;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;AA2DD;;;GAGG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAwF/E"}
|
package/dist/steps/database.js
CHANGED
|
@@ -3,29 +3,30 @@ import { generateJwtSecret } from "../utils/secrets.js";
|
|
|
3
3
|
import { generateSupabaseKeys } from "../utils/supabase-keys.js";
|
|
4
4
|
import { writeEnvFile } from "../utils/files.js";
|
|
5
5
|
import { textOrExit } from "../utils/prompts.js";
|
|
6
|
-
import { addExposedSchema, enableCustomAccessTokenHook, extractProjectRef } from "../utils/supabase-management-api.js";
|
|
6
|
+
import { addExposedSchema, enableCustomAccessTokenHook, enableOAuthServer, extractProjectRef } from "../utils/supabase-management-api.js";
|
|
7
7
|
/**
|
|
8
|
-
* Offers to configure the
|
|
9
|
-
* (exposed schemas
|
|
10
|
-
* instead of leaving them as manual steps — mirrors the
|
|
11
|
-
* automation: a separate, more powerful "Personal Access
|
|
12
|
-
* level, not the project's anon/service keys), captured
|
|
13
|
-
* and never persisted. Only offered for Supabase Cloud
|
|
14
|
-
* projects and custom domains have no Management API to
|
|
15
|
-
* which parts actually succeeded so the caller can fall back
|
|
16
|
-
* note for whichever one didn't.
|
|
8
|
+
* Offers to configure the three Supabase Cloud dashboard-only settings
|
|
9
|
+
* (exposed schemas, Custom Access Token Hook, OAuth 2.1 server) via the
|
|
10
|
+
* Management API instead of leaving them as manual steps — mirrors the
|
|
11
|
+
* Vercel deploy automation: a separate, more powerful "Personal Access
|
|
12
|
+
* Token" (account level, not the project's anon/service keys), captured
|
|
13
|
+
* with masked input and never persisted. Only offered for Supabase Cloud
|
|
14
|
+
* URLs (self-hosted projects and custom domains have no Management API to
|
|
15
|
+
* call). Returns which parts actually succeeded so the caller can fall back
|
|
16
|
+
* to a manual note for whichever one didn't.
|
|
17
17
|
*/
|
|
18
18
|
async function tryAutoConfigureSupabaseCloud(supabaseUrl) {
|
|
19
19
|
const projectRef = extractProjectRef(supabaseUrl);
|
|
20
20
|
if (!projectRef)
|
|
21
|
-
return { schemaDone: false, hookDone: false };
|
|
21
|
+
return { schemaDone: false, hookDone: false, oauthServerDone: false };
|
|
22
22
|
const auto = await p.confirm({
|
|
23
|
-
message: "¿Quieres que configure automáticamente el schema expuesto
|
|
23
|
+
message: "¿Quieres que configure automáticamente el schema expuesto, el Custom Access Token Hook, y el servidor OAuth 2.1 " +
|
|
24
|
+
"(necesario para que otras apps de dominios distintos inicien sesión) en tu proyecto Supabase? " +
|
|
24
25
|
"Necesito un Personal Access Token de tu cuenta Supabase (distinto a las keys del proyecto que ya diste).",
|
|
25
26
|
initialValue: true,
|
|
26
27
|
});
|
|
27
28
|
if (p.isCancel(auto) || !auto)
|
|
28
|
-
return { schemaDone: false, hookDone: false };
|
|
29
|
+
return { schemaDone: false, hookDone: false, oauthServerDone: false };
|
|
29
30
|
p.note("1. Entra a supabase.com/dashboard/account/tokens\n" +
|
|
30
31
|
'2. Dale click a "Generate new token"\n' +
|
|
31
32
|
"3. Ponle un nombre, confirma y cópialo (solo se ve una vez)\n" +
|
|
@@ -35,7 +36,7 @@ async function tryAutoConfigureSupabaseCloud(supabaseUrl) {
|
|
|
35
36
|
validate: (value) => (value.trim() ? undefined : "Requerido"),
|
|
36
37
|
});
|
|
37
38
|
if (p.isCancel(managementToken))
|
|
38
|
-
return { schemaDone: false, hookDone: false };
|
|
39
|
+
return { schemaDone: false, hookDone: false, oauthServerDone: false };
|
|
39
40
|
const schemaSpinner = p.spinner();
|
|
40
41
|
schemaSpinner.start("Exponiendo el schema kontrolia_auth en la API de datos");
|
|
41
42
|
const schemaResult = await addExposedSchema(managementToken, projectRef, "kontrolia_auth");
|
|
@@ -44,7 +45,11 @@ async function tryAutoConfigureSupabaseCloud(supabaseUrl) {
|
|
|
44
45
|
hookSpinner.start("Activando el Custom Access Token Hook");
|
|
45
46
|
const hookResult = await enableCustomAccessTokenHook(managementToken, projectRef);
|
|
46
47
|
hookSpinner.stop(hookResult.ok ? "Hook activado" : `Falló: ${hookResult.error}`);
|
|
47
|
-
|
|
48
|
+
const oauthSpinner = p.spinner();
|
|
49
|
+
oauthSpinner.start("Activando el servidor OAuth 2.1");
|
|
50
|
+
const oauthResult = await enableOAuthServer(managementToken, projectRef);
|
|
51
|
+
oauthSpinner.stop(oauthResult.ok ? "Servidor OAuth 2.1 activado" : `Falló: ${oauthResult.error}`);
|
|
52
|
+
return { schemaDone: schemaResult.ok, hookDone: hookResult.ok, oauthServerDone: oauthResult.ok };
|
|
48
53
|
}
|
|
49
54
|
/**
|
|
50
55
|
* Question A from the architecture plan: where does Postgres + GoTrue come
|
|
@@ -75,8 +80,8 @@ export async function askDatabaseStep(repoRoot) {
|
|
|
75
80
|
const anonKey = await textOrExit("Anon/public key");
|
|
76
81
|
const serviceRoleKey = await textOrExit("Service role key (server-only, nunca la expongas al navegador)");
|
|
77
82
|
const databaseUrl = await textOrExit("Connection string de Postgres", "postgres://postgres:...@db.tu-proyecto.supabase.co:5432/postgres");
|
|
78
|
-
const { schemaDone, hookDone } = await tryAutoConfigureSupabaseCloud(supabaseUrl);
|
|
79
|
-
if (!schemaDone || !hookDone) {
|
|
83
|
+
const { schemaDone, hookDone, oauthServerDone } = await tryAutoConfigureSupabaseCloud(supabaseUrl);
|
|
84
|
+
if (!schemaDone || !hookDone || !oauthServerDone) {
|
|
80
85
|
const pending = [];
|
|
81
86
|
if (!hookDone)
|
|
82
87
|
pending.push("Authentication → Hooks → activa kontrolia_auth.custom_access_token_hook.");
|
|
@@ -84,6 +89,11 @@ export async function askDatabaseStep(repoRoot) {
|
|
|
84
89
|
pending.push("Project Settings → Data API → en \"Exposed schemas\" agrega kontrolia_auth (queda como: public, graphql_public, kontrolia_auth). " +
|
|
85
90
|
"Sin esto, crear una organización u otras operaciones fallan con \"Invalid schema: kontrolia_auth\".");
|
|
86
91
|
}
|
|
92
|
+
if (!oauthServerDone) {
|
|
93
|
+
pending.push("Servidor OAuth 2.1 (necesario para que otras apps de dominios distintos inicien sesión): actívalo desde el Dashboard si ya " +
|
|
94
|
+
"tiene el toggle disponible (función en beta) — sin él, el registro de clientes OAuth y el login desde otros dominios fallan " +
|
|
95
|
+
'con "OAuth server is disabled".');
|
|
96
|
+
}
|
|
87
97
|
p.note("Si tu proyecto es Supabase Cloud (o self-hosted fuera de nuestro docker-compose), estos ajustes solo se pueden hacer " +
|
|
88
98
|
"desde el Dashboard — la API de Supabase no los expone del todo:\n\n" +
|
|
89
99
|
pending.map((line, i) => `${i + 1}. ${line}`).join("\n"), pending.length > 1 ? "Pasos manuales pendientes" : "Paso manual pendiente");
|
|
@@ -25,4 +25,16 @@ export declare function addExposedSchema(token: string, projectRef: string, sche
|
|
|
25
25
|
* is always named "postgres", so the pg-functions:// URI is fixed.
|
|
26
26
|
*/
|
|
27
27
|
export declare function enableCustomAccessTokenHook(token: string, projectRef: string): Promise<ManagementApiResult>;
|
|
28
|
+
/**
|
|
29
|
+
* Enables GoTrue's OAuth 2.1 authorization server — what makes cross-domain
|
|
30
|
+
* SSO possible (admin-panel exchanging a PKCE code for its own session
|
|
31
|
+
* instead of relying on a shared cookie). Without this, registering an OAuth
|
|
32
|
+
* client during install/deploy silently fails with
|
|
33
|
+
* `{"error_code":"feature_disabled","msg":"OAuth server is disabled"}`, and
|
|
34
|
+
* any third-party app trying to log in via the OAuth flow hits the same
|
|
35
|
+
* error. The API rejects `oauth_server_enabled` unless
|
|
36
|
+
* `oauth_server_authorization_path` is sent in the same request, even though
|
|
37
|
+
* conceptually it's just an on/off toggle.
|
|
38
|
+
*/
|
|
39
|
+
export declare function enableOAuthServer(token: string, projectRef: string): Promise<ManagementApiResult>;
|
|
28
40
|
//# sourceMappingURL=supabase-management-api.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supabase-management-api.d.ts","sourceRoot":"","sources":["../../src/utils/supabase-management-api.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"supabase-management-api.d.ts","sourceRoot":"","sources":["../../src/utils/supabase-management-api.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,OAAO,CAAC;IACZ,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGpE;AASD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA+BtH;AAED;;;;;GAKG;AACH,wBAAsB,2BAA2B,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAgBjH;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAgBvG"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const CUSTOM_ACCESS_TOKEN_HOOK_URI = "pg-functions://postgres/kontrolia_auth/custom_access_token_hook";
|
|
2
|
+
/** GoTrue's internal route for the OAuth 2.1 authorization endpoint — the API rejects oauth_server_enabled without it. */
|
|
3
|
+
const OAUTH_SERVER_AUTHORIZATION_PATH = "/oauth/authorize";
|
|
2
4
|
/**
|
|
3
5
|
* Extracts the project ref (subdomain) from a Supabase Cloud URL, e.g.
|
|
4
6
|
* "https://abcxyz.supabase.co" -> "abcxyz". Returns null for anything else
|
|
@@ -82,3 +84,33 @@ export async function enableCustomAccessTokenHook(token, projectRef) {
|
|
|
82
84
|
const body = (await res.json().catch(() => null));
|
|
83
85
|
return { ok: false, error: body?.message ?? `HTTP ${res.status}` };
|
|
84
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Enables GoTrue's OAuth 2.1 authorization server — what makes cross-domain
|
|
89
|
+
* SSO possible (admin-panel exchanging a PKCE code for its own session
|
|
90
|
+
* instead of relying on a shared cookie). Without this, registering an OAuth
|
|
91
|
+
* client during install/deploy silently fails with
|
|
92
|
+
* `{"error_code":"feature_disabled","msg":"OAuth server is disabled"}`, and
|
|
93
|
+
* any third-party app trying to log in via the OAuth flow hits the same
|
|
94
|
+
* error. The API rejects `oauth_server_enabled` unless
|
|
95
|
+
* `oauth_server_authorization_path` is sent in the same request, even though
|
|
96
|
+
* conceptually it's just an on/off toggle.
|
|
97
|
+
*/
|
|
98
|
+
export async function enableOAuthServer(token, projectRef) {
|
|
99
|
+
let res;
|
|
100
|
+
try {
|
|
101
|
+
res = await managementApiFetch(`/projects/${projectRef}/config/auth`, token, {
|
|
102
|
+
method: "PATCH",
|
|
103
|
+
body: JSON.stringify({
|
|
104
|
+
oauth_server_enabled: true,
|
|
105
|
+
oauth_server_authorization_path: OAUTH_SERVER_AUTHORIZATION_PATH,
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
return { ok: false, error: error.message };
|
|
111
|
+
}
|
|
112
|
+
if (res.ok)
|
|
113
|
+
return { ok: true };
|
|
114
|
+
const body = (await res.json().catch(() => null));
|
|
115
|
+
return { ok: false, error: body?.message ?? `HTTP ${res.status}` };
|
|
116
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-kontrolia-auth",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Turnkey installer for KontrolIA Auth. `npx create-kontrolia-auth mi-app` checks requirements, downloads the repo, installs deps, and runs a wizard: (1) connect to an existing Supabase project or create a new self-hosted one via Docker; (2) where to deploy auth-server/admin-panel (Docker, Vercel, Railway, Render, Coolify). Also exposes `update`, `migrate` and `doctor` subcommands.",
|
|
6
6
|
"keywords": [
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@clack/prompts": "^0.9.1",
|
|
37
37
|
"jose": "^5.9.6",
|
|
38
|
-
"@kontrolia/db": "2.
|
|
38
|
+
"@kontrolia/db": "2.1.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^22.10.2",
|