@usebetterdev/console-cli 0.3.0-beta.2
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/dist/check.d.ts +24 -0
- package/dist/check.js +9 -0
- package/dist/check.js.map +1 -0
- package/dist/chunk-DYJHYSYZ.js +13 -0
- package/dist/chunk-DYJHYSYZ.js.map +1 -0
- package/dist/chunk-G7RB5NFW.js +31 -0
- package/dist/chunk-G7RB5NFW.js.map +1 -0
- package/dist/chunk-JKGOP2VK.js +161 -0
- package/dist/chunk-JKGOP2VK.js.map +1 -0
- package/dist/chunk-ZEP3C2V6.js +112 -0
- package/dist/chunk-ZEP3C2V6.js.map +1 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +184 -0
- package/dist/cli.js.map +1 -0
- package/dist/init.d.ts +32 -0
- package/dist/init.js +12 -0
- package/dist/init.js.map +1 -0
- package/dist/migrate.d.ts +8 -0
- package/dist/migrate.js +7 -0
- package/dist/migrate.js.map +1 -0
- package/dist/sql/console-tables.sql +32 -0
- package/dist/token.d.ts +22 -0
- package/dist/token.js +11 -0
- package/dist/token.js.map +1 -0
- package/package.json +64 -0
package/dist/check.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
interface ConsoleCheckResult {
|
|
2
|
+
check: string;
|
|
3
|
+
passed: boolean;
|
|
4
|
+
message?: string;
|
|
5
|
+
}
|
|
6
|
+
interface ConsoleCheckOutput {
|
|
7
|
+
passed: boolean;
|
|
8
|
+
results: ConsoleCheckResult[];
|
|
9
|
+
warnings: ConsoleCheckResult[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Check console-related environment variables.
|
|
13
|
+
* Exported for unit testing.
|
|
14
|
+
*/
|
|
15
|
+
declare function checkEnvVars(): {
|
|
16
|
+
results: ConsoleCheckResult[];
|
|
17
|
+
warnings: ConsoleCheckResult[];
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Run all console health checks: table structure, indexes, and env vars.
|
|
21
|
+
*/
|
|
22
|
+
declare function runConsoleCheck(databaseUrl: string): Promise<ConsoleCheckOutput>;
|
|
23
|
+
|
|
24
|
+
export { type ConsoleCheckOutput, type ConsoleCheckResult, checkEnvVars, runConsoleCheck };
|
package/dist/check.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/migrate.ts
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { dirname, join } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
function generateConsoleMigrationSql() {
|
|
7
|
+
return readFileSync(join(__dirname, "sql/console-tables.sql"), "utf-8");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
generateConsoleMigrationSql
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=chunk-DYJHYSYZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/migrate.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n/**\n * Load and return the console tables SQL migration.\n * This SQL creates both `better_console_sessions` and `better_console_magic_links`\n * tables with all required columns and indexes.\n */\nexport function generateConsoleMigrationSql(): string {\n return readFileSync(join(__dirname, \"sql/console-tables.sql\"), \"utf-8\");\n}\n"],"mappings":";AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;AAE9B,IAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AAOjD,SAAS,8BAAsC;AACpD,SAAO,aAAa,KAAK,WAAW,wBAAwB,GAAG,OAAO;AACxE;","names":[]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// src/token.ts
|
|
2
|
+
function bytesToHex(bytes) {
|
|
3
|
+
let hex = "";
|
|
4
|
+
for (const b of bytes) {
|
|
5
|
+
hex += b.toString(16).padStart(2, "0");
|
|
6
|
+
}
|
|
7
|
+
return hex;
|
|
8
|
+
}
|
|
9
|
+
function generateConnectionToken() {
|
|
10
|
+
const bytes = new Uint8Array(32);
|
|
11
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
12
|
+
return bytesToHex(bytes);
|
|
13
|
+
}
|
|
14
|
+
async function hashConnectionToken(token) {
|
|
15
|
+
const encoder = new TextEncoder();
|
|
16
|
+
const data = encoder.encode(token);
|
|
17
|
+
const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", data);
|
|
18
|
+
return `sha256:${bytesToHex(new Uint8Array(hashBuffer))}`;
|
|
19
|
+
}
|
|
20
|
+
async function generateTokenPair() {
|
|
21
|
+
const token = generateConnectionToken();
|
|
22
|
+
const hash = await hashConnectionToken(token);
|
|
23
|
+
return { token, hash };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
generateConnectionToken,
|
|
28
|
+
hashConnectionToken,
|
|
29
|
+
generateTokenPair
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=chunk-G7RB5NFW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/token.ts"],"sourcesContent":["/**\n * Connection token utilities using the Web Crypto API.\n * Works in Node, Deno, and Bun — no node:crypto imports.\n */\n\nfunction bytesToHex(bytes: Uint8Array): string {\n let hex = \"\";\n for (const b of bytes) {\n hex += b.toString(16).padStart(2, \"0\");\n }\n return hex;\n}\n\n/**\n * Generate a connection token: 32 random bytes as a 64-char hex string.\n */\nexport function generateConnectionToken(): string {\n const bytes = new Uint8Array(32);\n globalThis.crypto.getRandomValues(bytes);\n return bytesToHex(bytes);\n}\n\n/**\n * SHA-256 hash a connection token. Returns \"sha256:<hex>\".\n */\nexport async function hashConnectionToken(token: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(token);\n const hashBuffer = await globalThis.crypto.subtle.digest(\"SHA-256\", data);\n return `sha256:${bytesToHex(new Uint8Array(hashBuffer))}`;\n}\n\n/**\n * Generate a token and its hash in one call.\n * Returns `{ token, hash }` where hash is \"sha256:<hex>\".\n */\nexport async function generateTokenPair(): Promise<{\n token: string;\n hash: string;\n}> {\n const token = generateConnectionToken();\n const hash = await hashConnectionToken(token);\n return { token, hash };\n}\n"],"mappings":";AAKA,SAAS,WAAW,OAA2B;AAC7C,MAAI,MAAM;AACV,aAAW,KAAK,OAAO;AACrB,WAAO,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAAA,EACvC;AACA,SAAO;AACT;AAKO,SAAS,0BAAkC;AAChD,QAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,aAAW,OAAO,gBAAgB,KAAK;AACvC,SAAO,WAAW,KAAK;AACzB;AAKA,eAAsB,oBAAoB,OAAgC;AACxE,QAAM,UAAU,IAAI,YAAY;AAChC,QAAM,OAAO,QAAQ,OAAO,KAAK;AACjC,QAAM,aAAa,MAAM,WAAW,OAAO,OAAO,OAAO,WAAW,IAAI;AACxE,SAAO,UAAU,WAAW,IAAI,WAAW,UAAU,CAAC,CAAC;AACzD;AAMA,eAAsB,oBAGnB;AACD,QAAM,QAAQ,wBAAwB;AACtC,QAAM,OAAO,MAAM,oBAAoB,KAAK;AAC5C,SAAO,EAAE,OAAO,KAAK;AACvB;","names":[]}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// src/check.ts
|
|
2
|
+
import { Pool } from "pg";
|
|
3
|
+
var SESSIONS_COLUMNS = [
|
|
4
|
+
"id",
|
|
5
|
+
"email",
|
|
6
|
+
"token_hash",
|
|
7
|
+
"permissions",
|
|
8
|
+
"expires_at",
|
|
9
|
+
"created_at"
|
|
10
|
+
];
|
|
11
|
+
var MAGIC_LINKS_COLUMNS = [
|
|
12
|
+
"id",
|
|
13
|
+
"email",
|
|
14
|
+
"code_hash",
|
|
15
|
+
"session_id",
|
|
16
|
+
"token_hash",
|
|
17
|
+
"failed_attempts",
|
|
18
|
+
"expires_at",
|
|
19
|
+
"used_at",
|
|
20
|
+
"created_at"
|
|
21
|
+
];
|
|
22
|
+
var EXPECTED_INDEXES = [
|
|
23
|
+
{
|
|
24
|
+
table: "better_console_sessions",
|
|
25
|
+
index: "idx_console_sessions_token_hash"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
table: "better_console_sessions",
|
|
29
|
+
index: "idx_console_sessions_expires_at"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
table: "better_console_magic_links",
|
|
33
|
+
index: "idx_console_magic_links_session_id"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
table: "better_console_magic_links",
|
|
37
|
+
index: "idx_console_magic_links_expires_at"
|
|
38
|
+
}
|
|
39
|
+
];
|
|
40
|
+
var VALID_TOKEN_HASH = /^(sha256:)?[0-9a-f]{64}$/;
|
|
41
|
+
async function checkTableColumns(pool, tableName, expectedColumns) {
|
|
42
|
+
const results = [];
|
|
43
|
+
const prefix = tableName;
|
|
44
|
+
const r = await pool.query(
|
|
45
|
+
`SELECT column_name FROM information_schema.columns
|
|
46
|
+
WHERE table_schema = 'public' AND table_name = $1
|
|
47
|
+
ORDER BY ordinal_position`,
|
|
48
|
+
[tableName]
|
|
49
|
+
);
|
|
50
|
+
const cols = r.rows.map(
|
|
51
|
+
(row) => row.column_name
|
|
52
|
+
);
|
|
53
|
+
if (cols.length === 0) {
|
|
54
|
+
results.push({
|
|
55
|
+
check: `${prefix} table exists`,
|
|
56
|
+
passed: false,
|
|
57
|
+
message: `${tableName} table not found`
|
|
58
|
+
});
|
|
59
|
+
return results;
|
|
60
|
+
}
|
|
61
|
+
results.push({ check: `${prefix} table exists`, passed: true });
|
|
62
|
+
for (const col of expectedColumns) {
|
|
63
|
+
const ok = cols.includes(col);
|
|
64
|
+
const result = {
|
|
65
|
+
check: `${prefix}.${col}`,
|
|
66
|
+
passed: ok
|
|
67
|
+
};
|
|
68
|
+
if (!ok) {
|
|
69
|
+
result.message = `missing column: ${col}`;
|
|
70
|
+
}
|
|
71
|
+
results.push(result);
|
|
72
|
+
}
|
|
73
|
+
return results;
|
|
74
|
+
}
|
|
75
|
+
async function checkIndexes(pool) {
|
|
76
|
+
const results = [];
|
|
77
|
+
for (const { table, index } of EXPECTED_INDEXES) {
|
|
78
|
+
const r = await pool.query(
|
|
79
|
+
`SELECT 1 FROM pg_indexes
|
|
80
|
+
WHERE schemaname = 'public' AND tablename = $1 AND indexname = $2`,
|
|
81
|
+
[table, index]
|
|
82
|
+
);
|
|
83
|
+
const exists = r.rows.length > 0;
|
|
84
|
+
const result = {
|
|
85
|
+
check: `index ${index}`,
|
|
86
|
+
passed: exists
|
|
87
|
+
};
|
|
88
|
+
if (!exists) {
|
|
89
|
+
result.message = `index ${index} not found on ${table}`;
|
|
90
|
+
}
|
|
91
|
+
results.push(result);
|
|
92
|
+
}
|
|
93
|
+
return results;
|
|
94
|
+
}
|
|
95
|
+
function checkEnvVars() {
|
|
96
|
+
const results = [];
|
|
97
|
+
const warnings = [];
|
|
98
|
+
const tokenHash = process.env.BETTER_CONSOLE_TOKEN_HASH;
|
|
99
|
+
if (tokenHash === void 0 || tokenHash.length === 0) {
|
|
100
|
+
results.push({
|
|
101
|
+
check: "BETTER_CONSOLE_TOKEN_HASH is set",
|
|
102
|
+
passed: false,
|
|
103
|
+
message: "BETTER_CONSOLE_TOKEN_HASH env var is not set"
|
|
104
|
+
});
|
|
105
|
+
} else if (!VALID_TOKEN_HASH.test(tokenHash)) {
|
|
106
|
+
results.push({
|
|
107
|
+
check: "BETTER_CONSOLE_TOKEN_HASH format",
|
|
108
|
+
passed: false,
|
|
109
|
+
message: 'BETTER_CONSOLE_TOKEN_HASH is malformed \u2014 expected "sha256:<64 hex chars>" or raw 64 hex chars'
|
|
110
|
+
});
|
|
111
|
+
} else {
|
|
112
|
+
results.push({
|
|
113
|
+
check: "BETTER_CONSOLE_TOKEN_HASH is set",
|
|
114
|
+
passed: true
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const allowedEmails = process.env.BETTER_CONSOLE_ALLOWED_EMAILS;
|
|
118
|
+
if (allowedEmails === void 0 || allowedEmails.length === 0) {
|
|
119
|
+
warnings.push({
|
|
120
|
+
check: "BETTER_CONSOLE_ALLOWED_EMAILS is set",
|
|
121
|
+
passed: false,
|
|
122
|
+
message: "BETTER_CONSOLE_ALLOWED_EMAILS not set \u2014 only needed for magic link mode"
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return { results, warnings };
|
|
126
|
+
}
|
|
127
|
+
async function runConsoleCheck(databaseUrl) {
|
|
128
|
+
const pool = new Pool({ connectionString: databaseUrl });
|
|
129
|
+
const results = [];
|
|
130
|
+
const warnings = [];
|
|
131
|
+
try {
|
|
132
|
+
results.push(
|
|
133
|
+
...await checkTableColumns(
|
|
134
|
+
pool,
|
|
135
|
+
"better_console_sessions",
|
|
136
|
+
SESSIONS_COLUMNS
|
|
137
|
+
)
|
|
138
|
+
);
|
|
139
|
+
results.push(
|
|
140
|
+
...await checkTableColumns(
|
|
141
|
+
pool,
|
|
142
|
+
"better_console_magic_links",
|
|
143
|
+
MAGIC_LINKS_COLUMNS
|
|
144
|
+
)
|
|
145
|
+
);
|
|
146
|
+
results.push(...await checkIndexes(pool));
|
|
147
|
+
const envChecks = checkEnvVars();
|
|
148
|
+
results.push(...envChecks.results);
|
|
149
|
+
warnings.push(...envChecks.warnings);
|
|
150
|
+
} finally {
|
|
151
|
+
await pool.end();
|
|
152
|
+
}
|
|
153
|
+
const passed = results.every((r) => r.passed);
|
|
154
|
+
return { passed, results, warnings };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export {
|
|
158
|
+
checkEnvVars,
|
|
159
|
+
runConsoleCheck
|
|
160
|
+
};
|
|
161
|
+
//# sourceMappingURL=chunk-JKGOP2VK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/check.ts"],"sourcesContent":["import { Pool } from \"pg\";\n\nexport interface ConsoleCheckResult {\n check: string;\n passed: boolean;\n message?: string;\n}\n\nexport interface ConsoleCheckOutput {\n passed: boolean;\n results: ConsoleCheckResult[];\n warnings: ConsoleCheckResult[];\n}\n\nconst SESSIONS_COLUMNS = [\n \"id\",\n \"email\",\n \"token_hash\",\n \"permissions\",\n \"expires_at\",\n \"created_at\",\n];\n\nconst MAGIC_LINKS_COLUMNS = [\n \"id\",\n \"email\",\n \"code_hash\",\n \"session_id\",\n \"token_hash\",\n \"failed_attempts\",\n \"expires_at\",\n \"used_at\",\n \"created_at\",\n];\n\nconst EXPECTED_INDEXES: ReadonlyArray<{\n table: string;\n index: string;\n}> = [\n {\n table: \"better_console_sessions\",\n index: \"idx_console_sessions_token_hash\",\n },\n {\n table: \"better_console_sessions\",\n index: \"idx_console_sessions_expires_at\",\n },\n {\n table: \"better_console_magic_links\",\n index: \"idx_console_magic_links_session_id\",\n },\n {\n table: \"better_console_magic_links\",\n index: \"idx_console_magic_links_expires_at\",\n },\n];\n\n/** Matches \"sha256:\" followed by exactly 64 hex chars, or just 64 hex chars. */\nconst VALID_TOKEN_HASH = /^(sha256:)?[0-9a-f]{64}$/;\n\nasync function checkTableColumns(\n pool: Pool,\n tableName: string,\n expectedColumns: readonly string[],\n): Promise<ConsoleCheckResult[]> {\n const results: ConsoleCheckResult[] = [];\n const prefix = tableName;\n\n const r = await pool.query(\n `SELECT column_name FROM information_schema.columns\n WHERE table_schema = 'public' AND table_name = $1\n ORDER BY ordinal_position`,\n [tableName],\n );\n const cols = (r.rows as { column_name: string }[]).map(\n (row) => row.column_name,\n );\n\n if (cols.length === 0) {\n results.push({\n check: `${prefix} table exists`,\n passed: false,\n message: `${tableName} table not found`,\n });\n return results;\n }\n results.push({ check: `${prefix} table exists`, passed: true });\n\n for (const col of expectedColumns) {\n const ok = cols.includes(col);\n const result: ConsoleCheckResult = {\n check: `${prefix}.${col}`,\n passed: ok,\n };\n if (!ok) {\n result.message = `missing column: ${col}`;\n }\n results.push(result);\n }\n\n return results;\n}\n\nasync function checkIndexes(pool: Pool): Promise<ConsoleCheckResult[]> {\n const results: ConsoleCheckResult[] = [];\n\n for (const { table, index } of EXPECTED_INDEXES) {\n const r = await pool.query(\n `SELECT 1 FROM pg_indexes\n WHERE schemaname = 'public' AND tablename = $1 AND indexname = $2`,\n [table, index],\n );\n const exists = r.rows.length > 0;\n const result: ConsoleCheckResult = {\n check: `index ${index}`,\n passed: exists,\n };\n if (!exists) {\n result.message = `index ${index} not found on ${table}`;\n }\n results.push(result);\n }\n\n return results;\n}\n\n/**\n * Check console-related environment variables.\n * Exported for unit testing.\n */\nexport function checkEnvVars(): {\n results: ConsoleCheckResult[];\n warnings: ConsoleCheckResult[];\n} {\n const results: ConsoleCheckResult[] = [];\n const warnings: ConsoleCheckResult[] = [];\n\n const tokenHash = process.env.BETTER_CONSOLE_TOKEN_HASH;\n\n if (tokenHash === undefined || tokenHash.length === 0) {\n results.push({\n check: \"BETTER_CONSOLE_TOKEN_HASH is set\",\n passed: false,\n message: \"BETTER_CONSOLE_TOKEN_HASH env var is not set\",\n });\n } else if (!VALID_TOKEN_HASH.test(tokenHash)) {\n results.push({\n check: \"BETTER_CONSOLE_TOKEN_HASH format\",\n passed: false,\n message:\n 'BETTER_CONSOLE_TOKEN_HASH is malformed — expected \"sha256:<64 hex chars>\" or raw 64 hex chars',\n });\n } else {\n results.push({\n check: \"BETTER_CONSOLE_TOKEN_HASH is set\",\n passed: true,\n });\n }\n\n const allowedEmails = process.env.BETTER_CONSOLE_ALLOWED_EMAILS;\n if (allowedEmails === undefined || allowedEmails.length === 0) {\n warnings.push({\n check: \"BETTER_CONSOLE_ALLOWED_EMAILS is set\",\n passed: false,\n message:\n \"BETTER_CONSOLE_ALLOWED_EMAILS not set — only needed for magic link mode\",\n });\n }\n\n return { results, warnings };\n}\n\n/**\n * Run all console health checks: table structure, indexes, and env vars.\n */\nexport async function runConsoleCheck(\n databaseUrl: string,\n): Promise<ConsoleCheckOutput> {\n const pool = new Pool({ connectionString: databaseUrl });\n const results: ConsoleCheckResult[] = [];\n const warnings: ConsoleCheckResult[] = [];\n\n try {\n results.push(\n ...(await checkTableColumns(\n pool,\n \"better_console_sessions\",\n SESSIONS_COLUMNS,\n )),\n );\n results.push(\n ...(await checkTableColumns(\n pool,\n \"better_console_magic_links\",\n MAGIC_LINKS_COLUMNS,\n )),\n );\n results.push(...(await checkIndexes(pool)));\n\n const envChecks = checkEnvVars();\n results.push(...envChecks.results);\n warnings.push(...envChecks.warnings);\n } finally {\n await pool.end();\n }\n\n const passed = results.every((r) => r.passed);\n return { passed, results, warnings };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateTokenPair
|
|
3
|
+
} from "./chunk-G7RB5NFW.js";
|
|
4
|
+
|
|
5
|
+
// src/init.ts
|
|
6
|
+
import pc from "picocolors";
|
|
7
|
+
import {
|
|
8
|
+
intro,
|
|
9
|
+
outro,
|
|
10
|
+
text,
|
|
11
|
+
note,
|
|
12
|
+
cancel,
|
|
13
|
+
isCancel
|
|
14
|
+
} from "@clack/prompts";
|
|
15
|
+
var InitCancelledError = class extends Error {
|
|
16
|
+
constructor() {
|
|
17
|
+
super("Init cancelled.");
|
|
18
|
+
this.name = "InitCancelledError";
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
var CONTROL_CHARS = /[\x00-\x1f\x7f]/;
|
|
22
|
+
function validateEmail(value) {
|
|
23
|
+
const trimmed = value.trim();
|
|
24
|
+
if (!trimmed) {
|
|
25
|
+
return "Email is required";
|
|
26
|
+
}
|
|
27
|
+
if (CONTROL_CHARS.test(trimmed)) {
|
|
28
|
+
return "Email must not contain control characters";
|
|
29
|
+
}
|
|
30
|
+
if (!trimmed.includes("@")) {
|
|
31
|
+
return "Must be a valid email address";
|
|
32
|
+
}
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
async function runInit(flags) {
|
|
36
|
+
const nonInteractive = flags?.nonInteractive === true;
|
|
37
|
+
const emailFlag = flags?.email;
|
|
38
|
+
if (nonInteractive && !emailFlag) {
|
|
39
|
+
throw new Error("--email is required in non-interactive mode");
|
|
40
|
+
}
|
|
41
|
+
if (!nonInteractive) {
|
|
42
|
+
intro("better-console init");
|
|
43
|
+
}
|
|
44
|
+
const { token, hash } = await generateTokenPair();
|
|
45
|
+
let email;
|
|
46
|
+
if (emailFlag) {
|
|
47
|
+
const validationError = validateEmail(emailFlag);
|
|
48
|
+
if (validationError) {
|
|
49
|
+
throw new Error(`Invalid --email: ${validationError}`);
|
|
50
|
+
}
|
|
51
|
+
email = emailFlag.trim();
|
|
52
|
+
} else {
|
|
53
|
+
const emailInput = await text({
|
|
54
|
+
message: "Admin email address",
|
|
55
|
+
placeholder: "admin@myapp.com",
|
|
56
|
+
validate: validateEmail
|
|
57
|
+
});
|
|
58
|
+
if (isCancel(emailInput)) {
|
|
59
|
+
cancel("Init cancelled.");
|
|
60
|
+
throw new InitCancelledError();
|
|
61
|
+
}
|
|
62
|
+
email = emailInput;
|
|
63
|
+
}
|
|
64
|
+
const envBlock = [
|
|
65
|
+
`BETTER_CONSOLE_TOKEN_HASH=${hash}`,
|
|
66
|
+
`BETTER_CONSOLE_ALLOWED_EMAILS=${email}`
|
|
67
|
+
].join("\n");
|
|
68
|
+
const tokenNote = [
|
|
69
|
+
pc.bold("Connection token (save this \u2014 it won't be shown again):"),
|
|
70
|
+
"",
|
|
71
|
+
pc.green(token)
|
|
72
|
+
].join("\n");
|
|
73
|
+
const nextStepsText = [
|
|
74
|
+
pc.bold(pc.yellow("1.")) + pc.bold(" Add env vars to your .env file:"),
|
|
75
|
+
"",
|
|
76
|
+
pc.blue(` BETTER_CONSOLE_TOKEN_HASH=${hash}`),
|
|
77
|
+
pc.blue(` BETTER_CONSOLE_ALLOWED_EMAILS=${email}`),
|
|
78
|
+
"",
|
|
79
|
+
pc.bold(pc.yellow("2.")) + pc.bold(" Add console middleware to your app"),
|
|
80
|
+
"",
|
|
81
|
+
pc.bold(pc.yellow("3.")) + pc.bold(" Create console tables (if not using Drizzle push):"),
|
|
82
|
+
"",
|
|
83
|
+
pc.green(" $ npx @usebetterdev/console-cli migrate --dry-run"),
|
|
84
|
+
"",
|
|
85
|
+
pc.bold(pc.yellow("4.")) + pc.bold(" Verify setup:"),
|
|
86
|
+
"",
|
|
87
|
+
pc.green(
|
|
88
|
+
" $ npx @usebetterdev/console-cli check --database-url $DATABASE_URL"
|
|
89
|
+
)
|
|
90
|
+
].join("\n");
|
|
91
|
+
if (nonInteractive) {
|
|
92
|
+
console.log(tokenNote);
|
|
93
|
+
console.log("");
|
|
94
|
+
console.log(envBlock);
|
|
95
|
+
console.log("");
|
|
96
|
+
console.log(pc.bold("Next steps:"));
|
|
97
|
+
console.log(nextStepsText);
|
|
98
|
+
} else {
|
|
99
|
+
note(tokenNote, "Connection token");
|
|
100
|
+
note(envBlock, "Environment variables");
|
|
101
|
+
note(nextStepsText, "Next steps");
|
|
102
|
+
outro("Console initialized!");
|
|
103
|
+
}
|
|
104
|
+
return { token, hash, email };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export {
|
|
108
|
+
InitCancelledError,
|
|
109
|
+
validateEmail,
|
|
110
|
+
runInit
|
|
111
|
+
};
|
|
112
|
+
//# sourceMappingURL=chunk-ZEP3C2V6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/init.ts"],"sourcesContent":["import pc from \"picocolors\";\nimport {\n intro,\n outro,\n text,\n note,\n cancel,\n isCancel,\n} from \"@clack/prompts\";\nimport { generateTokenPair } from \"./token.js\";\n\nexport interface InitFlags {\n email?: string | undefined;\n nonInteractive?: boolean | undefined;\n}\n\nexport interface InitOutput {\n token: string;\n hash: string;\n email: string;\n}\n\nexport class InitCancelledError extends Error {\n constructor() {\n super(\"Init cancelled.\");\n this.name = \"InitCancelledError\";\n }\n}\n\nconst CONTROL_CHARS = /[\\x00-\\x1f\\x7f]/;\n\n/**\n * Validate an email address for use in env output.\n * Rejects control characters/newlines that could inject extra env lines.\n * Returns an error message string on failure, or undefined on success.\n */\nexport function validateEmail(value: string): string | undefined {\n const trimmed = value.trim();\n if (!trimmed) {\n return \"Email is required\";\n }\n if (CONTROL_CHARS.test(trimmed)) {\n return \"Email must not contain control characters\";\n }\n if (!trimmed.includes(\"@\")) {\n return \"Must be a valid email address\";\n }\n return undefined;\n}\n\n/**\n * Run the console init flow.\n *\n * Generates a connection token pair and prompts for an admin email.\n * In non-interactive mode (`-n`), `--email` is required.\n *\n * Returns the generated output for programmatic use; also prints\n * env vars and next steps to stdout.\n *\n * Throws `InitCancelledError` if the user cancels a prompt.\n */\nexport async function runInit(flags?: InitFlags): Promise<InitOutput> {\n const nonInteractive = flags?.nonInteractive === true;\n const emailFlag = flags?.email;\n\n if (nonInteractive && !emailFlag) {\n throw new Error(\"--email is required in non-interactive mode\");\n }\n\n if (!nonInteractive) {\n intro(\"better-console init\");\n }\n\n const { token, hash } = await generateTokenPair();\n\n let email: string;\n\n if (emailFlag) {\n const validationError = validateEmail(emailFlag);\n if (validationError) {\n throw new Error(`Invalid --email: ${validationError}`);\n }\n email = emailFlag.trim();\n } else {\n const emailInput = await text({\n message: \"Admin email address\",\n placeholder: \"admin@myapp.com\",\n validate: validateEmail,\n });\n if (isCancel(emailInput)) {\n cancel(\"Init cancelled.\");\n throw new InitCancelledError();\n }\n email = emailInput;\n }\n\n const envBlock = [\n `BETTER_CONSOLE_TOKEN_HASH=${hash}`,\n `BETTER_CONSOLE_ALLOWED_EMAILS=${email}`,\n ].join(\"\\n\");\n\n const tokenNote = [\n pc.bold(\"Connection token (save this — it won't be shown again):\"),\n \"\",\n pc.green(token),\n ].join(\"\\n\");\n\n const nextStepsText = [\n pc.bold(pc.yellow(\"1.\")) +\n pc.bold(\" Add env vars to your .env file:\"),\n \"\",\n pc.blue(` BETTER_CONSOLE_TOKEN_HASH=${hash}`),\n pc.blue(` BETTER_CONSOLE_ALLOWED_EMAILS=${email}`),\n \"\",\n pc.bold(pc.yellow(\"2.\")) +\n pc.bold(\" Add console middleware to your app\"),\n \"\",\n pc.bold(pc.yellow(\"3.\")) +\n pc.bold(\" Create console tables (if not using Drizzle push):\"),\n \"\",\n pc.green(\" $ npx @usebetterdev/console-cli migrate --dry-run\"),\n \"\",\n pc.bold(pc.yellow(\"4.\")) +\n pc.bold(\" Verify setup:\"),\n \"\",\n pc.green(\n \" $ npx @usebetterdev/console-cli check --database-url $DATABASE_URL\",\n ),\n ].join(\"\\n\");\n\n if (nonInteractive) {\n console.log(tokenNote);\n console.log(\"\");\n console.log(envBlock);\n console.log(\"\");\n console.log(pc.bold(\"Next steps:\"));\n console.log(nextStepsText);\n } else {\n note(tokenNote, \"Connection token\");\n note(envBlock, \"Environment variables\");\n note(nextStepsText, \"Next steps\");\n outro(\"Console initialized!\");\n }\n\n return { token, hash, email };\n}\n"],"mappings":";;;;;AAAA,OAAO,QAAQ;AACf;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAcA,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAC5C,cAAc;AACZ,UAAM,iBAAiB;AACvB,SAAK,OAAO;AAAA,EACd;AACF;AAEA,IAAM,gBAAgB;AAOf,SAAS,cAAc,OAAmC;AAC/D,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AACA,MAAI,cAAc,KAAK,OAAO,GAAG;AAC/B,WAAO;AAAA,EACT;AACA,MAAI,CAAC,QAAQ,SAAS,GAAG,GAAG;AAC1B,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAaA,eAAsB,QAAQ,OAAwC;AACpE,QAAM,iBAAiB,OAAO,mBAAmB;AACjD,QAAM,YAAY,OAAO;AAEzB,MAAI,kBAAkB,CAAC,WAAW;AAChC,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AAEA,MAAI,CAAC,gBAAgB;AACnB,UAAM,qBAAqB;AAAA,EAC7B;AAEA,QAAM,EAAE,OAAO,KAAK,IAAI,MAAM,kBAAkB;AAEhD,MAAI;AAEJ,MAAI,WAAW;AACb,UAAM,kBAAkB,cAAc,SAAS;AAC/C,QAAI,iBAAiB;AACnB,YAAM,IAAI,MAAM,oBAAoB,eAAe,EAAE;AAAA,IACvD;AACA,YAAQ,UAAU,KAAK;AAAA,EACzB,OAAO;AACL,UAAM,aAAa,MAAM,KAAK;AAAA,MAC5B,SAAS;AAAA,MACT,aAAa;AAAA,MACb,UAAU;AAAA,IACZ,CAAC;AACD,QAAI,SAAS,UAAU,GAAG;AACxB,aAAO,iBAAiB;AACxB,YAAM,IAAI,mBAAmB;AAAA,IAC/B;AACA,YAAQ;AAAA,EACV;AAEA,QAAM,WAAW;AAAA,IACf,6BAA6B,IAAI;AAAA,IACjC,iCAAiC,KAAK;AAAA,EACxC,EAAE,KAAK,IAAI;AAEX,QAAM,YAAY;AAAA,IAChB,GAAG,KAAK,8DAAyD;AAAA,IACjE;AAAA,IACA,GAAG,MAAM,KAAK;AAAA,EAChB,EAAE,KAAK,IAAI;AAEX,QAAM,gBAAgB;AAAA,IACpB,GAAG,KAAK,GAAG,OAAO,IAAI,CAAC,IACrB,GAAG,KAAK,kCAAkC;AAAA,IAC5C;AAAA,IACA,GAAG,KAAK,gCAAgC,IAAI,EAAE;AAAA,IAC9C,GAAG,KAAK,oCAAoC,KAAK,EAAE;AAAA,IACnD;AAAA,IACA,GAAG,KAAK,GAAG,OAAO,IAAI,CAAC,IACrB,GAAG,KAAK,qCAAqC;AAAA,IAC/C;AAAA,IACA,GAAG,KAAK,GAAG,OAAO,IAAI,CAAC,IACrB,GAAG,KAAK,qDAAqD;AAAA,IAC/D;AAAA,IACA,GAAG,MAAM,sDAAsD;AAAA,IAC/D;AAAA,IACA,GAAG,KAAK,GAAG,OAAO,IAAI,CAAC,IACrB,GAAG,KAAK,gBAAgB;AAAA,IAC1B;AAAA,IACA,GAAG;AAAA,MACD;AAAA,IACF;AAAA,EACF,EAAE,KAAK,IAAI;AAEX,MAAI,gBAAgB;AAClB,YAAQ,IAAI,SAAS;AACrB,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,QAAQ;AACpB,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,GAAG,KAAK,aAAa,CAAC;AAClC,YAAQ,IAAI,aAAa;AAAA,EAC3B,OAAO;AACL,SAAK,WAAW,kBAAkB;AAClC,SAAK,UAAU,uBAAuB;AACtC,SAAK,eAAe,YAAY;AAChC,UAAM,sBAAsB;AAAA,EAC9B;AAEA,SAAO,EAAE,OAAO,MAAM,MAAM;AAC9B;","names":[]}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
runConsoleCheck
|
|
4
|
+
} from "./chunk-JKGOP2VK.js";
|
|
5
|
+
import {
|
|
6
|
+
InitCancelledError,
|
|
7
|
+
runInit
|
|
8
|
+
} from "./chunk-ZEP3C2V6.js";
|
|
9
|
+
import {
|
|
10
|
+
generateConsoleMigrationSql
|
|
11
|
+
} from "./chunk-DYJHYSYZ.js";
|
|
12
|
+
import {
|
|
13
|
+
generateTokenPair
|
|
14
|
+
} from "./chunk-G7RB5NFW.js";
|
|
15
|
+
|
|
16
|
+
// src/cli.ts
|
|
17
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
18
|
+
import { dirname, join } from "path";
|
|
19
|
+
import { fileURLToPath } from "url";
|
|
20
|
+
import { Command } from "commander";
|
|
21
|
+
import pc from "picocolors";
|
|
22
|
+
function formatDate() {
|
|
23
|
+
const d = /* @__PURE__ */ new Date();
|
|
24
|
+
const y = d.getFullYear();
|
|
25
|
+
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
26
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
27
|
+
const h = String(d.getHours()).padStart(2, "0");
|
|
28
|
+
const min = String(d.getMinutes()).padStart(2, "0");
|
|
29
|
+
const sec = String(d.getSeconds()).padStart(2, "0");
|
|
30
|
+
return `${y}${m}${day}_${h}${min}${sec}`;
|
|
31
|
+
}
|
|
32
|
+
function writeSqlOutput(sql, output, defaultFilename, force) {
|
|
33
|
+
let filePath;
|
|
34
|
+
if (output.endsWith(".sql")) {
|
|
35
|
+
mkdirSync(dirname(output), { recursive: true });
|
|
36
|
+
filePath = output;
|
|
37
|
+
} else {
|
|
38
|
+
mkdirSync(output, { recursive: true });
|
|
39
|
+
filePath = join(output, defaultFilename);
|
|
40
|
+
}
|
|
41
|
+
if (existsSync(filePath) && !force) {
|
|
42
|
+
console.error(
|
|
43
|
+
pc.red(`File already exists: ${filePath}`)
|
|
44
|
+
);
|
|
45
|
+
console.error(pc.dim("Use --force to overwrite."));
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
writeFileSync(filePath, sql, "utf-8");
|
|
49
|
+
console.log(`${pc.green("\u2713")} Wrote ${filePath}`);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
53
|
+
var { version } = JSON.parse(
|
|
54
|
+
readFileSync(join(__dirname, "../package.json"), "utf-8")
|
|
55
|
+
);
|
|
56
|
+
var program = new Command();
|
|
57
|
+
program.name("better-console").description("CLI for Better Console").version(version);
|
|
58
|
+
program.command("init").description("Generate a connection token and print environment variables").option("--email <email>", "Admin email (skips prompt)").option("-n, --non-interactive", "Disable all prompts (requires --email)").action(
|
|
59
|
+
async (opts) => {
|
|
60
|
+
try {
|
|
61
|
+
await runInit({
|
|
62
|
+
email: opts.email,
|
|
63
|
+
nonInteractive: opts.nonInteractive
|
|
64
|
+
});
|
|
65
|
+
} catch (err) {
|
|
66
|
+
if (err instanceof InitCancelledError) {
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
70
|
+
console.error(pc.red(message));
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
program.command("migrate").description("Generate SQL to create console tables").option("--dry-run", "Print SQL to stdout").option("--force", "Overwrite existing file").option(
|
|
76
|
+
"-o, --output <path>",
|
|
77
|
+
"Write to file (.sql) or directory (default: ./console-migrations)"
|
|
78
|
+
).action(
|
|
79
|
+
async (opts) => {
|
|
80
|
+
try {
|
|
81
|
+
const sql = generateConsoleMigrationSql();
|
|
82
|
+
if (opts.dryRun) {
|
|
83
|
+
process.stdout.write(sql);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const output = opts.output ?? join(process.cwd(), "console-migrations");
|
|
87
|
+
const defaultFilename = `${formatDate()}_better_console_tables.sql`;
|
|
88
|
+
const written = writeSqlOutput(
|
|
89
|
+
sql,
|
|
90
|
+
output,
|
|
91
|
+
defaultFilename,
|
|
92
|
+
opts.force === true
|
|
93
|
+
);
|
|
94
|
+
if (!written) {
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
} catch (err) {
|
|
98
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
99
|
+
console.error(pc.red(message));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
var tokenCmd = program.command("token").description("Manage connection tokens");
|
|
105
|
+
tokenCmd.command("generate").description("Generate a new connection token pair").action(async () => {
|
|
106
|
+
try {
|
|
107
|
+
const { token, hash } = await generateTokenPair();
|
|
108
|
+
console.log(
|
|
109
|
+
pc.bold("Connection token (save this \u2014 it won't be shown again):")
|
|
110
|
+
);
|
|
111
|
+
console.log(pc.green(token));
|
|
112
|
+
console.log("");
|
|
113
|
+
console.log(pc.bold("Token hash (add to your .env):"));
|
|
114
|
+
console.log(`BETTER_CONSOLE_TOKEN_HASH=${hash}`);
|
|
115
|
+
} catch (err) {
|
|
116
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
117
|
+
console.error(pc.red(message));
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
tokenCmd.command("rotate").description("Generate a new token (invalidates all existing sessions)").action(async () => {
|
|
122
|
+
try {
|
|
123
|
+
console.log(
|
|
124
|
+
pc.yellow(
|
|
125
|
+
"Warning: rotating the connection token will invalidate all existing console sessions."
|
|
126
|
+
)
|
|
127
|
+
);
|
|
128
|
+
console.log("");
|
|
129
|
+
const { token, hash } = await generateTokenPair();
|
|
130
|
+
console.log(
|
|
131
|
+
pc.bold(
|
|
132
|
+
"New connection token (save this \u2014 it won't be shown again):"
|
|
133
|
+
)
|
|
134
|
+
);
|
|
135
|
+
console.log(pc.green(token));
|
|
136
|
+
console.log("");
|
|
137
|
+
console.log(pc.bold("New token hash (update your .env):"));
|
|
138
|
+
console.log(`BETTER_CONSOLE_TOKEN_HASH=${hash}`);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
141
|
+
console.error(pc.red(message));
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
program.command("check").description("Verify console tables, indexes, and environment variables").option("--database-url <url>", "Database URL (default: DATABASE_URL env)").action(async (opts) => {
|
|
146
|
+
const url = opts.databaseUrl ?? process.env.DATABASE_URL;
|
|
147
|
+
if (!url || typeof url !== "string") {
|
|
148
|
+
console.error(
|
|
149
|
+
pc.red(
|
|
150
|
+
"check requires --database-url or DATABASE_URL environment variable"
|
|
151
|
+
)
|
|
152
|
+
);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
const { passed, results, warnings } = await runConsoleCheck(url);
|
|
157
|
+
for (const w of warnings) {
|
|
158
|
+
console.log(
|
|
159
|
+
`${pc.yellow("\u26A0")} ${pc.yellow(w.check)}${w.message ? pc.dim(` ${w.message}`) : ""}`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
for (const r of results) {
|
|
163
|
+
const icon = r.passed ? pc.green("\u2713") : pc.red("\u2717");
|
|
164
|
+
const label = r.passed ? r.check : pc.red(r.check);
|
|
165
|
+
const msg = r.message ? pc.dim(` ${r.message}`) : "";
|
|
166
|
+
console.log(`${icon} ${label}${msg}`);
|
|
167
|
+
}
|
|
168
|
+
if (!passed) {
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
} catch (err) {
|
|
172
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
173
|
+
console.error(pc.red(message));
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
var isMain = typeof process !== "undefined" && process.argv[1] !== void 0 && (process.argv[1].endsWith("cli.js") || process.argv[1].endsWith("cli.ts") || process.argv[1].endsWith("/better-console"));
|
|
178
|
+
if (isMain) {
|
|
179
|
+
program.parse();
|
|
180
|
+
}
|
|
181
|
+
export {
|
|
182
|
+
program
|
|
183
|
+
};
|
|
184
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport { runInit, InitCancelledError } from \"./init.js\";\nimport { generateConsoleMigrationSql } from \"./migrate.js\";\nimport { generateTokenPair } from \"./token.js\";\nimport { runConsoleCheck } from \"./check.js\";\n\nfunction formatDate(): string {\n const d = new Date();\n const y = d.getFullYear();\n const m = String(d.getMonth() + 1).padStart(2, \"0\");\n const day = String(d.getDate()).padStart(2, \"0\");\n const h = String(d.getHours()).padStart(2, \"0\");\n const min = String(d.getMinutes()).padStart(2, \"0\");\n const sec = String(d.getSeconds()).padStart(2, \"0\");\n return `${y}${m}${day}_${h}${min}${sec}`;\n}\n\n/**\n * Write SQL to a file. If `output` ends in `.sql`, treat it as a file path.\n * Otherwise treat it as a directory and generate a timestamped filename inside it.\n * Returns false if the file already exists and `force` is not set.\n */\nfunction writeSqlOutput(\n sql: string,\n output: string,\n defaultFilename: string,\n force: boolean,\n): boolean {\n let filePath: string;\n if (output.endsWith(\".sql\")) {\n mkdirSync(dirname(output), { recursive: true });\n filePath = output;\n } else {\n mkdirSync(output, { recursive: true });\n filePath = join(output, defaultFilename);\n }\n if (existsSync(filePath) && !force) {\n console.error(\n pc.red(`File already exists: ${filePath}`),\n );\n console.error(pc.dim(\"Use --force to overwrite.\"));\n return false;\n }\n writeFileSync(filePath, sql, \"utf-8\");\n console.log(`${pc.green(\"✓\")} Wrote ${filePath}`);\n return true;\n}\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst { version } = JSON.parse(\n readFileSync(join(__dirname, \"../package.json\"), \"utf-8\"),\n) as { version: string };\n\nconst program = new Command();\n\nprogram\n .name(\"better-console\")\n .description(\"CLI for Better Console\")\n .version(version);\n\n// --- init ---\nprogram\n .command(\"init\")\n .description(\"Generate a connection token and print environment variables\")\n .option(\"--email <email>\", \"Admin email (skips prompt)\")\n .option(\"-n, --non-interactive\", \"Disable all prompts (requires --email)\")\n .action(\n async (opts: { email?: string; nonInteractive?: boolean }) => {\n try {\n await runInit({\n email: opts.email,\n nonInteractive: opts.nonInteractive,\n });\n } catch (err) {\n if (err instanceof InitCancelledError) {\n process.exit(0);\n }\n const message = err instanceof Error ? err.message : String(err);\n console.error(pc.red(message));\n process.exit(1);\n }\n },\n );\n\n// --- migrate ---\nprogram\n .command(\"migrate\")\n .description(\"Generate SQL to create console tables\")\n .option(\"--dry-run\", \"Print SQL to stdout\")\n .option(\"--force\", \"Overwrite existing file\")\n .option(\n \"-o, --output <path>\",\n \"Write to file (.sql) or directory (default: ./console-migrations)\",\n )\n .action(\n async (opts: {\n dryRun?: boolean;\n force?: boolean;\n output?: string;\n }) => {\n try {\n const sql = generateConsoleMigrationSql();\n\n if (opts.dryRun) {\n process.stdout.write(sql);\n return;\n }\n\n const output =\n opts.output ?? join(process.cwd(), \"console-migrations\");\n const defaultFilename = `${formatDate()}_better_console_tables.sql`;\n const written = writeSqlOutput(\n sql,\n output,\n defaultFilename,\n opts.force === true,\n );\n if (!written) {\n process.exit(1);\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(pc.red(message));\n process.exit(1);\n }\n },\n );\n\n// --- token (subcommand group) ---\nconst tokenCmd = program\n .command(\"token\")\n .description(\"Manage connection tokens\");\n\ntokenCmd\n .command(\"generate\")\n .description(\"Generate a new connection token pair\")\n .action(async () => {\n try {\n const { token, hash } = await generateTokenPair();\n console.log(\n pc.bold(\"Connection token (save this — it won't be shown again):\"),\n );\n console.log(pc.green(token));\n console.log(\"\");\n console.log(pc.bold(\"Token hash (add to your .env):\"));\n console.log(`BETTER_CONSOLE_TOKEN_HASH=${hash}`);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(pc.red(message));\n process.exit(1);\n }\n });\n\ntokenCmd\n .command(\"rotate\")\n .description(\"Generate a new token (invalidates all existing sessions)\")\n .action(async () => {\n try {\n console.log(\n pc.yellow(\n \"Warning: rotating the connection token will invalidate all existing console sessions.\",\n ),\n );\n console.log(\"\");\n const { token, hash } = await generateTokenPair();\n console.log(\n pc.bold(\n \"New connection token (save this — it won't be shown again):\",\n ),\n );\n console.log(pc.green(token));\n console.log(\"\");\n console.log(pc.bold(\"New token hash (update your .env):\"));\n console.log(`BETTER_CONSOLE_TOKEN_HASH=${hash}`);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(pc.red(message));\n process.exit(1);\n }\n });\n\n// --- check ---\nprogram\n .command(\"check\")\n .description(\"Verify console tables, indexes, and environment variables\")\n .option(\"--database-url <url>\", \"Database URL (default: DATABASE_URL env)\")\n .action(async (opts: { databaseUrl?: string }) => {\n const url = opts.databaseUrl ?? process.env.DATABASE_URL;\n if (!url || typeof url !== \"string\") {\n console.error(\n pc.red(\n \"check requires --database-url or DATABASE_URL environment variable\",\n ),\n );\n process.exit(1);\n }\n try {\n const { passed, results, warnings } = await runConsoleCheck(url);\n\n for (const w of warnings) {\n console.log(\n `${pc.yellow(\"⚠\")} ${pc.yellow(w.check)}${w.message ? pc.dim(` ${w.message}`) : \"\"}`,\n );\n }\n\n for (const r of results) {\n const icon = r.passed ? pc.green(\"✓\") : pc.red(\"✗\");\n const label = r.passed ? r.check : pc.red(r.check);\n const msg = r.message ? pc.dim(` ${r.message}`) : \"\";\n console.log(`${icon} ${label}${msg}`);\n }\n\n if (!passed) {\n process.exit(1);\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(pc.red(message));\n process.exit(1);\n }\n });\n\nconst isMain =\n typeof process !== \"undefined\" &&\n process.argv[1] !== undefined &&\n (process.argv[1].endsWith(\"cli.js\") ||\n process.argv[1].endsWith(\"cli.ts\") ||\n process.argv[1].endsWith(\"/better-console\"));\nif (isMain) {\n program.parse();\n}\n\nexport { program };\n"],"mappings":"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","names":[]}
|
package/dist/init.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
interface InitFlags {
|
|
2
|
+
email?: string | undefined;
|
|
3
|
+
nonInteractive?: boolean | undefined;
|
|
4
|
+
}
|
|
5
|
+
interface InitOutput {
|
|
6
|
+
token: string;
|
|
7
|
+
hash: string;
|
|
8
|
+
email: string;
|
|
9
|
+
}
|
|
10
|
+
declare class InitCancelledError extends Error {
|
|
11
|
+
constructor();
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Validate an email address for use in env output.
|
|
15
|
+
* Rejects control characters/newlines that could inject extra env lines.
|
|
16
|
+
* Returns an error message string on failure, or undefined on success.
|
|
17
|
+
*/
|
|
18
|
+
declare function validateEmail(value: string): string | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Run the console init flow.
|
|
21
|
+
*
|
|
22
|
+
* Generates a connection token pair and prompts for an admin email.
|
|
23
|
+
* In non-interactive mode (`-n`), `--email` is required.
|
|
24
|
+
*
|
|
25
|
+
* Returns the generated output for programmatic use; also prints
|
|
26
|
+
* env vars and next steps to stdout.
|
|
27
|
+
*
|
|
28
|
+
* Throws `InitCancelledError` if the user cancels a prompt.
|
|
29
|
+
*/
|
|
30
|
+
declare function runInit(flags?: InitFlags): Promise<InitOutput>;
|
|
31
|
+
|
|
32
|
+
export { InitCancelledError, type InitFlags, type InitOutput, runInit, validateEmail };
|
package/dist/init.js
ADDED
package/dist/init.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load and return the console tables SQL migration.
|
|
3
|
+
* This SQL creates both `better_console_sessions` and `better_console_magic_links`
|
|
4
|
+
* tables with all required columns and indexes.
|
|
5
|
+
*/
|
|
6
|
+
declare function generateConsoleMigrationSql(): string;
|
|
7
|
+
|
|
8
|
+
export { generateConsoleMigrationSql };
|
package/dist/migrate.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
-- Better Console: sessions table
|
|
2
|
+
CREATE TABLE IF NOT EXISTS better_console_sessions (
|
|
3
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
4
|
+
email TEXT NOT NULL,
|
|
5
|
+
token_hash TEXT NOT NULL UNIQUE,
|
|
6
|
+
permissions TEXT NOT NULL,
|
|
7
|
+
expires_at TIMESTAMPTZ NOT NULL,
|
|
8
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
CREATE INDEX IF NOT EXISTS idx_console_sessions_token_hash
|
|
12
|
+
ON better_console_sessions(token_hash);
|
|
13
|
+
CREATE INDEX IF NOT EXISTS idx_console_sessions_expires_at
|
|
14
|
+
ON better_console_sessions(expires_at);
|
|
15
|
+
|
|
16
|
+
-- Better Console: magic links table
|
|
17
|
+
CREATE TABLE IF NOT EXISTS better_console_magic_links (
|
|
18
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
19
|
+
email TEXT NOT NULL,
|
|
20
|
+
code_hash TEXT NOT NULL,
|
|
21
|
+
session_id UUID NOT NULL UNIQUE,
|
|
22
|
+
token_hash TEXT,
|
|
23
|
+
failed_attempts INTEGER NOT NULL DEFAULT 0,
|
|
24
|
+
expires_at TIMESTAMPTZ NOT NULL,
|
|
25
|
+
used_at TIMESTAMPTZ,
|
|
26
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
CREATE INDEX IF NOT EXISTS idx_console_magic_links_session_id
|
|
30
|
+
ON better_console_magic_links(session_id);
|
|
31
|
+
CREATE INDEX IF NOT EXISTS idx_console_magic_links_expires_at
|
|
32
|
+
ON better_console_magic_links(expires_at);
|
package/dist/token.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connection token utilities using the Web Crypto API.
|
|
3
|
+
* Works in Node, Deno, and Bun — no node:crypto imports.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Generate a connection token: 32 random bytes as a 64-char hex string.
|
|
7
|
+
*/
|
|
8
|
+
declare function generateConnectionToken(): string;
|
|
9
|
+
/**
|
|
10
|
+
* SHA-256 hash a connection token. Returns "sha256:<hex>".
|
|
11
|
+
*/
|
|
12
|
+
declare function hashConnectionToken(token: string): Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Generate a token and its hash in one call.
|
|
15
|
+
* Returns `{ token, hash }` where hash is "sha256:<hex>".
|
|
16
|
+
*/
|
|
17
|
+
declare function generateTokenPair(): Promise<{
|
|
18
|
+
token: string;
|
|
19
|
+
hash: string;
|
|
20
|
+
}>;
|
|
21
|
+
|
|
22
|
+
export { generateConnectionToken, generateTokenPair, hashConnectionToken };
|
package/dist/token.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@usebetterdev/console-cli",
|
|
3
|
+
"description": "CLI for Better Console — init, migrate, token management, health checks.",
|
|
4
|
+
"version": "0.3.0-beta.2",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": "github:usebetter-dev/usebetter",
|
|
7
|
+
"bugs": "https://github.com/usebetter-dev/usebetter/issues",
|
|
8
|
+
"homepage": "https://github.com/usebetter-dev/usebetter#readme",
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public",
|
|
11
|
+
"registry": "https://registry.npmjs.org/"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "./dist/cli.js",
|
|
15
|
+
"bin": {
|
|
16
|
+
"better-console": "./dist/cli.js"
|
|
17
|
+
},
|
|
18
|
+
"exports": {
|
|
19
|
+
".": "./dist/cli.js",
|
|
20
|
+
"./migrate": {
|
|
21
|
+
"import": "./dist/migrate.js",
|
|
22
|
+
"types": "./dist/migrate.d.ts"
|
|
23
|
+
},
|
|
24
|
+
"./check": {
|
|
25
|
+
"import": "./dist/check.js",
|
|
26
|
+
"types": "./dist/check.d.ts"
|
|
27
|
+
},
|
|
28
|
+
"./init": {
|
|
29
|
+
"import": "./dist/init.js",
|
|
30
|
+
"types": "./dist/init.d.ts"
|
|
31
|
+
},
|
|
32
|
+
"./token": {
|
|
33
|
+
"import": "./dist/token.js",
|
|
34
|
+
"types": "./dist/token.d.ts"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"prepare": "mkdir -p dist && test -f dist/cli.js || echo '#!/usr/bin/env node' > dist/cli.js",
|
|
42
|
+
"build": "tsup && mkdir -p dist/sql && cp src/sql/*.sql dist/sql/",
|
|
43
|
+
"lint": "oxlint",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"typecheck": "tsc --noEmit"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@clack/prompts": "^0.10.0",
|
|
49
|
+
"@usebetterdev/console": "workspace:*",
|
|
50
|
+
"commander": "^12.1.0",
|
|
51
|
+
"pg": "^8.13.0",
|
|
52
|
+
"picocolors": "^1.1.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^22.10.0",
|
|
56
|
+
"@types/pg": "^8.11.0",
|
|
57
|
+
"tsup": "^8.3.5",
|
|
58
|
+
"typescript": "~5.7.2",
|
|
59
|
+
"vitest": "^2.1.6"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=22"
|
|
63
|
+
}
|
|
64
|
+
}
|