@protobase/cli 0.5.0 → 0.6.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/dist/auth/migration-file.js +18 -0
- package/dist/auth/register.js +18 -0
- package/dist/auth/write-migration.js +20 -0
- package/dist/build/vite-bundle.js +1 -1
- package/dist/dev/entry.js +6 -2
- package/dist/dev/run.js +1 -1
- package/dist/main.js +3 -1
- package/dist/package.js +1 -1
- package/dist/serve/app.js +1 -1
- package/dist/serve/host-modules.js +1 -1
- package/dist/serve/lifecycle.js +2 -0
- package/dist/users/commands.js +8 -2
- package/dist/users/register.js +5 -4
- package/package.json +6 -6
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/auth/migration-file.ts
|
|
2
|
+
const numbered = /^(\d+)_.*\.sql$/;
|
|
3
|
+
const namePart = /^[a-z0-9][a-z0-9_-]*$/;
|
|
4
|
+
/**
|
|
5
|
+
* The name of the next migration in a folder holding `files`: one past the highest number, as wide as the widest
|
|
6
|
+
* (`009_…` after `008_…`; three digits in an empty folder), then `name`.
|
|
7
|
+
*/
|
|
8
|
+
const nextMigrationName = (files, name) => {
|
|
9
|
+
if (!namePart.test(name)) throw new Error(`Invalid migration name "${name}": use lowercase letters, digits, "_" or "-"`);
|
|
10
|
+
const numbers = files.map((file) => numbered.exec(file)?.[1]).filter((found) => found !== void 0);
|
|
11
|
+
const next = Math.max(0, ...numbers.map(Number)) + 1;
|
|
12
|
+
const width = Math.max(3, ...numbers.map((found) => found.length));
|
|
13
|
+
return `${String(next).padStart(width, "0")}_${name}.sql`;
|
|
14
|
+
};
|
|
15
|
+
/** The file's contents: what wrote it and why, then Better Auth's statements, one per paragraph. */
|
|
16
|
+
const migrationText = (sql) => `-- The auth schema changes this version of Protobase needs, written by \`protobase auth migration\`.\n-- Applied with the project's other migrations; edit or remove statements the project handles differently.\n\n${sql}\n`;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { migrationText, nextMigrationName };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { loadProjectAuth } from "../users/load-auth.js";
|
|
2
|
+
import { writeAuthMigration } from "./write-migration.js";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
//#region src/auth/register.ts
|
|
5
|
+
const out = (text) => process.stdout.write(text);
|
|
6
|
+
const registerAuth = (program) => {
|
|
7
|
+
program.command("auth").description("The auth schema of the project in the current directory").command("migration").description("Write the auth schema changes this Protobase version needs as the project's next SQL migration, compared with its database").option("--dir <dir>", "migrations folder", "db/migrations").option("--name <name>", "file name after the number", "auth").action(async (opts) => {
|
|
8
|
+
const file = await writeAuthMigration({
|
|
9
|
+
auth: await loadProjectAuth(process.cwd()),
|
|
10
|
+
dir: path.resolve(opts.dir),
|
|
11
|
+
name: opts.name
|
|
12
|
+
});
|
|
13
|
+
out(file ? `wrote ${path.relative(process.cwd(), file)}; apply it with the project's migrations\n` : "the auth schema is up to date; nothing to write\n");
|
|
14
|
+
process.exit(0);
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
export { registerAuth };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { migrationText, nextMigrationName } from "./migration-file.js";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { mkdir, readdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import { authSchemaMigration } from "@protobase/server";
|
|
5
|
+
//#region src/auth/write-migration.ts
|
|
6
|
+
/**
|
|
7
|
+
* Writes the auth schema changes the database still needs as the next migration in `dir`, and returns its path;
|
|
8
|
+
* `undefined` when the database is current. The changes are found by comparing the database with what Better Auth
|
|
9
|
+
* needs, so the project's earlier migrations must be applied to it first.
|
|
10
|
+
*/
|
|
11
|
+
const writeAuthMigration = async ({ auth, dir, name }) => {
|
|
12
|
+
const sql = await authSchemaMigration(auth);
|
|
13
|
+
if (sql === void 0) return void 0;
|
|
14
|
+
await mkdir(dir, { recursive: true });
|
|
15
|
+
const file = path.join(dir, nextMigrationName(await readdir(dir), name));
|
|
16
|
+
await writeFile(file, migrationText(sql), { flag: "wx" });
|
|
17
|
+
return file;
|
|
18
|
+
};
|
|
19
|
+
//#endregion
|
|
20
|
+
export { writeAuthMigration };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { bunBuild } from "./bun-build.js";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { build } from "vite";
|
|
3
4
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
4
5
|
import { tmpdir } from "node:os";
|
|
5
|
-
import { build } from "vite";
|
|
6
6
|
//#region src/build/vite-bundle.ts
|
|
7
7
|
const viteBuild = async ({ root, entry, outFile, plugins, logLevel = "warn" }) => {
|
|
8
8
|
await build({
|
package/dist/dev/entry.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { loadProject, projectDb } from "./project.js";
|
|
2
|
+
import { checkAuthSchema, configExports, createAdmin, hasUsers } from "@protobase/server";
|
|
2
3
|
import { Hono } from "hono";
|
|
3
|
-
import { configExports, createAdmin, hasUsers } from "@protobase/server";
|
|
4
4
|
//#region src/dev/entry.ts
|
|
5
5
|
const projectDir = process.env.PROTOBASE_PROJECT;
|
|
6
6
|
const project = await loadProject(projectDir, JSON.parse(process.env.PROTOBASE_EXTENSIONS ?? "[]"));
|
|
7
7
|
const db = projectDb(project, projectDir);
|
|
8
8
|
const { resources, views, pages, userMenu } = configExports(project.exports);
|
|
9
9
|
if (!project.authenticate) throw new Error("No authenticator: export `authenticate` (and `auth`) from protobase.config.ts; see https://docs.protobase.net/reference/auth/");
|
|
10
|
+
const store = globalThis;
|
|
11
|
+
if (project.auth && !store.__protobaseAuthSchemaChecked) {
|
|
12
|
+
await checkAuthSchema(project.auth);
|
|
13
|
+
store.__protobaseAuthSchemaChecked = true;
|
|
14
|
+
}
|
|
10
15
|
const admin = createAdmin({
|
|
11
16
|
resources,
|
|
12
17
|
views,
|
|
@@ -21,7 +26,6 @@ const admin = createAdmin({
|
|
|
21
26
|
}
|
|
22
27
|
});
|
|
23
28
|
var entry_default = new Hono().route("/", admin);
|
|
24
|
-
const store = globalThis;
|
|
25
29
|
if (project.auth && !store.__protobaseNoUsersShown) {
|
|
26
30
|
store.__protobaseNoUsersShown = true;
|
|
27
31
|
hasUsers(project.auth).then((any) => {
|
package/dist/dev/run.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { loadNearestEnvFile } from "./env.js";
|
|
1
2
|
import { adminAppDir, adminAppMain, uiPackageDir } from "../project/admin-app.js";
|
|
2
3
|
import { extensionImports, extensionStyles } from "../project/extensions.js";
|
|
3
4
|
import { moduleFile } from "../module-file.js";
|
|
4
5
|
import { layoutCheck } from "../build/layout-check.js";
|
|
5
6
|
import { projectUiPlugin } from "../project/project-ui.js";
|
|
6
|
-
import { loadNearestEnvFile } from "./env.js";
|
|
7
7
|
import { checkDatabase } from "./check-database.js";
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
9
9
|
import path from "node:path";
|
package/dist/main.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { loadNearestEnvFile } from "./dev/env.js";
|
|
2
|
+
import { registerAuth } from "./auth/register.js";
|
|
1
3
|
import { manifestFile } from "./bundle/manifest.js";
|
|
2
4
|
import { resolveExtensions } from "./project/extensions.js";
|
|
3
5
|
import { buildDeployBundle } from "./build/deploy-bundle.js";
|
|
4
6
|
import { buildServeRuntime } from "./build/serve-runtime.js";
|
|
5
7
|
import { resolveConnectionUrl } from "./db/connect.js";
|
|
6
8
|
import { parseHosts } from "./dev/hosts.js";
|
|
7
|
-
import { loadNearestEnvFile } from "./dev/env.js";
|
|
8
9
|
import { runDev } from "./dev/run.js";
|
|
9
10
|
import { runDoctor } from "./doctor/run.js";
|
|
10
11
|
import { runScaffold } from "./scaffold/run.js";
|
|
@@ -84,6 +85,7 @@ program.command("serve").description("Serve a bundle written by `protobase build
|
|
|
84
85
|
});
|
|
85
86
|
registerUsers(program);
|
|
86
87
|
registerToken(program);
|
|
88
|
+
registerAuth(program);
|
|
87
89
|
const run = (argv) => program.parseAsync(argv).catch((error) => {
|
|
88
90
|
process.stderr.write(`error: ${error.message}\n`);
|
|
89
91
|
process.exitCode = 1;
|
package/dist/package.js
CHANGED
package/dist/serve/app.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { isApiPath } from "../bundle/manifest.js";
|
|
2
2
|
import { serveStaticSite } from "./static-site.js";
|
|
3
|
+
import { configExports, createAdmin } from "@protobase/server";
|
|
3
4
|
import { Hono } from "hono";
|
|
4
5
|
import { logger } from "hono/logger";
|
|
5
|
-
import { configExports, createAdmin } from "@protobase/server";
|
|
6
6
|
//#region src/serve/app.ts
|
|
7
7
|
const createServeApp = ({ project, db, requestLog, site }) => {
|
|
8
8
|
const { resources, views, pages, userMenu } = configExports(project.config);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import * as server from "@protobase/server";
|
|
1
2
|
import * as postgres$1 from "postgres";
|
|
2
3
|
import * as schema from "@protobase/schema";
|
|
3
4
|
import * as nodeServer from "@hono/node-server";
|
|
4
5
|
import * as pg$1 from "pg";
|
|
5
6
|
import * as kysely from "kysely";
|
|
6
7
|
import * as hono from "hono";
|
|
7
|
-
import * as server from "@protobase/server";
|
|
8
8
|
import * as apiReference from "@scalar/hono-api-reference";
|
|
9
9
|
import * as aipParsers from "aip-parsers";
|
|
10
10
|
import * as betterAuth from "better-auth";
|
package/dist/serve/lifecycle.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createPgDb } from "../project/pg-db.js";
|
|
2
2
|
import { createServeApp } from "./app.js";
|
|
3
|
+
import { checkAuthSchema } from "@protobase/server";
|
|
3
4
|
import { createAdaptorServer } from "@hono/node-server";
|
|
4
5
|
import pg from "pg";
|
|
5
6
|
//#region src/serve/lifecycle.ts
|
|
@@ -13,6 +14,7 @@ const listen = (server, port) => new Promise((resolve, reject) => {
|
|
|
13
14
|
const closeServer = (server) => new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
|
|
14
15
|
const startServe = async ({ project, env, createDb = (url) => createPgDb(pg, url), site }) => {
|
|
15
16
|
if (!project.db && !env.databaseUrl) throw new Error("No database: set DATABASE_URL or export `db` from protobase.config.ts");
|
|
17
|
+
if (project.auth) await checkAuthSchema(project.auth);
|
|
16
18
|
const ownDb = project.db ? void 0 : createDb(env.databaseUrl);
|
|
17
19
|
const db = project.db ?? ownDb;
|
|
18
20
|
const app = createServeApp({
|
package/dist/users/commands.js
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { resolvePassword } from "./password.js";
|
|
2
2
|
//#region src/users/commands.ts
|
|
3
|
+
const githubId = /^[1-9][0-9]*$/;
|
|
3
4
|
const createUserCommand = async (api, options, deps, out) => {
|
|
4
5
|
if (!options.email.includes("@")) throw new Error(`"${options.email}" is not an email address`);
|
|
6
|
+
if (options.githubId !== void 0 && !githubId.test(options.githubId)) throw new Error(`"${options.githubId}" is not a GitHub user id, a number such as 583231`);
|
|
5
7
|
const { password, generated } = await resolvePassword(options, deps);
|
|
6
8
|
const role = !await api.hasUsers() ? "admin" : options.role;
|
|
7
9
|
const user = await api.createUser({
|
|
8
10
|
email: options.email,
|
|
9
11
|
password,
|
|
10
12
|
...role && { role },
|
|
11
|
-
...options.name && { name: options.name }
|
|
13
|
+
...options.name && { name: options.name },
|
|
14
|
+
...options.githubId && { accounts: [{
|
|
15
|
+
providerId: "github",
|
|
16
|
+
accountId: options.githubId
|
|
17
|
+
}] }
|
|
12
18
|
});
|
|
13
|
-
out(`Created ${user.role} ${user.email}\n`);
|
|
19
|
+
out(`Created ${user.role} ${user.email}${options.githubId ? `, linked to GitHub user ${options.githubId}` : ""}\n`);
|
|
14
20
|
if (generated) out(`Password (shown once): ${password}\n`);
|
|
15
21
|
};
|
|
16
22
|
const listUsersCommand = async (api, out) => {
|
package/dist/users/register.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { loadProjectAuth } from "./load-auth.js";
|
|
1
2
|
import { readHidden, rejectPasswordArgument } from "./password.js";
|
|
2
3
|
import { createUserCommand, listRolesCommand, listUsersCommand } from "./commands.js";
|
|
3
4
|
import { deleteUserCommand, setDisabledCommand, setRoleCommand } from "./manage.js";
|
|
4
|
-
import { loadProjectAuth } from "./load-auth.js";
|
|
5
5
|
import { parseTtl } from "./ttl.js";
|
|
6
6
|
import { Option } from "commander";
|
|
7
|
-
import { createInterface } from "node:readline/promises";
|
|
8
7
|
import { createUser, deleteUser, hasUsers, issueToken, listUsers, roleChoices, setUserBanned, setUserRole } from "@protobase/server";
|
|
8
|
+
import { createInterface } from "node:readline/promises";
|
|
9
9
|
//#region src/users/register.ts
|
|
10
10
|
const out = (text) => process.stdout.write(text);
|
|
11
11
|
const usersApi = (auth) => ({
|
|
@@ -34,14 +34,15 @@ const ask = async (question) => {
|
|
|
34
34
|
const finish = (code) => process.exit(code);
|
|
35
35
|
const registerUsers = (program) => {
|
|
36
36
|
const users = program.command("users").description("Manage the admin users of the project in the current directory");
|
|
37
|
-
users.command("create").description("Create a user; the first user is always an admin").argument("<email>").option("--name <name>", "display name").option("--role <role>", "role for users after the first (default: the project's default role; see `users roles`)").option("--password-stdin", "read the password from stdin", false).option("--generate-password", "generate a strong password and print it once", false).addOption(new Option("--password <value>").hideHelp()).action(async (email, opts) => {
|
|
37
|
+
users.command("create").description("Create a user; the first user is always an admin").argument("<email>").option("--name <name>", "display name").option("--role <role>", "role for users after the first (default: the project's default role; see `users roles`)").option("--password-stdin", "read the password from stdin", false).option("--generate-password", "generate a strong password and print it once", false).option("--github-id <id>", "link the user to this GitHub user id, so signing in with GitHub finds them whatever their address").addOption(new Option("--password <value>").hideHelp()).action(async (email, opts) => {
|
|
38
38
|
rejectPasswordArgument(opts.password);
|
|
39
39
|
await createUserCommand(await projectApi(), {
|
|
40
40
|
email,
|
|
41
41
|
name: opts.name,
|
|
42
42
|
role: opts.role,
|
|
43
43
|
passwordStdin: opts.passwordStdin,
|
|
44
|
-
generatePassword: opts.generatePassword
|
|
44
|
+
generatePassword: opts.generatePassword,
|
|
45
|
+
githubId: opts.githubId
|
|
45
46
|
}, {
|
|
46
47
|
stdin: process.stdin,
|
|
47
48
|
prompt: readHidden
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "The protobase command: scaffold, doctor, dev, build and serve a Protobase project.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@hono/node-server": "^2.1.3",
|
|
36
36
|
"@hono/vite-dev-server": "^0.26.1",
|
|
37
|
-
"@protobase/layout": "0.
|
|
38
|
-
"@protobase/query": "0.
|
|
39
|
-
"@protobase/schema": "0.
|
|
40
|
-
"@protobase/server": "0.
|
|
41
|
-
"@protobase/ui": "0.
|
|
37
|
+
"@protobase/layout": "0.6.0",
|
|
38
|
+
"@protobase/query": "0.6.0",
|
|
39
|
+
"@protobase/schema": "0.6.0",
|
|
40
|
+
"@protobase/server": "0.6.0",
|
|
41
|
+
"@protobase/ui": "0.6.0",
|
|
42
42
|
"@scalar/hono-api-reference": "^0.12.10",
|
|
43
43
|
"@tailwindcss/vite": "^4.3.3",
|
|
44
44
|
"@vitejs/plugin-react": "^6.1.2",
|