@protobase/cli 0.8.1 → 0.8.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/dev/dev-jobs.js +25 -0
- package/dist/dev/entry.js +4 -1
- package/dist/dev/run.js +6 -0
- package/dist/main.js +11 -0
- package/dist/migration/load-project.js +2 -1
- package/dist/migration/parts.js +18 -10
- package/dist/migration/register.js +1 -1
- package/dist/package.js +1 -1
- package/dist/serve/app.js +7 -2
- package/dist/serve/env.js +5 -1
- package/dist/serve/lifecycle.js +31 -9
- package/dist/worker/start-worker.js +51 -0
- package/package.json +6 -6
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { checkJobsSchema, startJobs } from "@protobase/server";
|
|
2
|
+
//#region src/dev/dev-jobs.ts
|
|
3
|
+
const store = globalThis;
|
|
4
|
+
/**
|
|
5
|
+
* Runs the project's jobs in the dev server: started on the first load, once the jobs schema checks out, and given the
|
|
6
|
+
* reloaded definitions on every load after it, so an edited job runs its new code from the next job on.
|
|
7
|
+
*/
|
|
8
|
+
const devJobs = async (db, definitions) => {
|
|
9
|
+
if (store.__protobaseJobs) return store.__protobaseJobs.update(definitions);
|
|
10
|
+
if (definitions.length === 0) return;
|
|
11
|
+
await checkJobsSchema(db);
|
|
12
|
+
store.__protobaseJobs = await startJobs({
|
|
13
|
+
db,
|
|
14
|
+
jobs: definitions,
|
|
15
|
+
onError: (error) => console.error(error)
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
/** Lets running jobs finish and stops them, when the dev server closes. */
|
|
19
|
+
const stopDevJobs = async () => {
|
|
20
|
+
const runtime = store.__protobaseJobs;
|
|
21
|
+
delete store.__protobaseJobs;
|
|
22
|
+
await runtime?.stop();
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
export { devJobs, stopDevJobs };
|
package/dist/dev/entry.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { devJobs } from "./dev-jobs.js";
|
|
1
2
|
import { loadProject, projectDb } from "./project.js";
|
|
2
|
-
import { checkAuthSchema, checkChanges, configExports, createAdmin, hasUsers } from "@protobase/server";
|
|
3
|
+
import { checkAuthSchema, checkChanges, configExports, createAdmin, hasUsers, jobDefinitions } from "@protobase/server";
|
|
3
4
|
import { Hono } from "hono";
|
|
4
5
|
//#region src/dev/entry.ts
|
|
5
6
|
const projectDir = process.env.PROTOBASE_PROJECT;
|
|
@@ -13,6 +14,7 @@ if (project.auth && !store.__protobaseAuthSchemaChecked) {
|
|
|
13
14
|
store.__protobaseAuthSchemaChecked = true;
|
|
14
15
|
}
|
|
15
16
|
await checkChanges(db, resources.map((resource) => resource.toModel()), project.events);
|
|
17
|
+
await devJobs(db, jobDefinitions(project.jobs ?? {}));
|
|
16
18
|
const admin = createAdmin({
|
|
17
19
|
resources,
|
|
18
20
|
views,
|
|
@@ -24,6 +26,7 @@ const admin = createAdmin({
|
|
|
24
26
|
...project.functions && { functions: project.functions },
|
|
25
27
|
...project.files && { files: project.files },
|
|
26
28
|
...project.events && { events: project.events },
|
|
29
|
+
jobs: jobDefinitions(project.jobs ?? {}),
|
|
27
30
|
options: {
|
|
28
31
|
onUnhandledError: (error) => console.error(error),
|
|
29
32
|
...project.options
|
package/dist/dev/run.js
CHANGED
|
@@ -5,6 +5,7 @@ import { moduleFile } from "../module-file.js";
|
|
|
5
5
|
import { layoutCheck } from "../build/layout-check.js";
|
|
6
6
|
import { projectUiPlugin } from "../project/project-ui.js";
|
|
7
7
|
import { checkDatabase } from "./check-database.js";
|
|
8
|
+
import { stopDevJobs } from "./dev-jobs.js";
|
|
8
9
|
import { existsSync } from "node:fs";
|
|
9
10
|
import path from "node:path";
|
|
10
11
|
import { createServer, searchForWorkspaceRoot } from "vite";
|
|
@@ -23,6 +24,10 @@ const reloadOnProjectChange = (dirs) => ({
|
|
|
23
24
|
if (dirs.some((dir) => file.startsWith(`${dir}${path.sep}`)) && !file.includes(`${path.sep}node_modules${path.sep}`)) server.environments.ssr.moduleGraph.invalidateAll();
|
|
24
25
|
}
|
|
25
26
|
});
|
|
27
|
+
const stopJobsOnClose = {
|
|
28
|
+
name: "protobase-jobs",
|
|
29
|
+
closeBundle: () => stopDevJobs()
|
|
30
|
+
};
|
|
26
31
|
const appDependencies = [
|
|
27
32
|
"react",
|
|
28
33
|
"react/jsx-runtime",
|
|
@@ -65,6 +70,7 @@ const runDev = async (options, out) => {
|
|
|
65
70
|
},
|
|
66
71
|
plugins: [
|
|
67
72
|
reloadOnProjectChange([options.projectDir, ...extensions.map((extension) => extension.dir)]),
|
|
73
|
+
stopJobsOnClose,
|
|
68
74
|
projectUiPlugin(options.projectDir, extensions),
|
|
69
75
|
extensionImports(options.projectDir, extensions),
|
|
70
76
|
extensionStyles(extensions),
|
package/dist/main.js
CHANGED
|
@@ -9,8 +9,10 @@ import { parseHosts } from "./dev/hosts.js";
|
|
|
9
9
|
import { runDev } from "./dev/run.js";
|
|
10
10
|
import { runDoctor } from "./doctor/run.js";
|
|
11
11
|
import { runScaffold } from "./scaffold/run.js";
|
|
12
|
+
import { closeOnSignals } from "./serve/signals.js";
|
|
12
13
|
import { serveBundle } from "./serve/serve-bundle.js";
|
|
13
14
|
import { serveBundleDir } from "./serve/serve-dir.js";
|
|
15
|
+
import { loadWorkerBundle, startWorker } from "./worker/start-worker.js";
|
|
14
16
|
import { registerOrganizations } from "./organizations/register.js";
|
|
15
17
|
import { registerFiles } from "./files/register.js";
|
|
16
18
|
import { registerMigration } from "./migration/register.js";
|
|
@@ -86,6 +88,15 @@ program.command("serve").description("Serve a bundle written by `protobase build
|
|
|
86
88
|
if (statSync(bundle).isDirectory()) await serveBundleDir(bundle, process.env, out);
|
|
87
89
|
else await serveBundle(bundle, process.env, out);
|
|
88
90
|
});
|
|
91
|
+
program.command("worker").description("Run the jobs of a bundle from `protobase build` in a process of their own, until SIGTERM or SIGINT, as `serve` runs them beside the API").argument("<bundle>", "the bundle folder, e.g. dist, or its dist/protobase.config.js").action(async (bundle) => {
|
|
92
|
+
loadNearestEnvFile(process.cwd());
|
|
93
|
+
const worker = await startWorker({
|
|
94
|
+
project: await loadWorkerBundle(bundle),
|
|
95
|
+
databaseUrl: process.env.DATABASE_URL || void 0
|
|
96
|
+
});
|
|
97
|
+
closeOnSignals(worker.stop);
|
|
98
|
+
out(`protobase worker running ${worker.jobs} job${worker.jobs === 1 ? "" : "s"}\n`);
|
|
99
|
+
});
|
|
89
100
|
registerUsers(program);
|
|
90
101
|
registerOrganizations(program);
|
|
91
102
|
registerToken(program);
|
|
@@ -3,7 +3,7 @@ import { importProjectFile } from "../project/import-project-file.js";
|
|
|
3
3
|
import { createPgDb } from "../project/pg-db.js";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
|
-
import { configExports, mergeConfig } from "@protobase/server";
|
|
6
|
+
import { configExports, jobDefinitions, mergeConfig } from "@protobase/server";
|
|
7
7
|
import pg from "pg";
|
|
8
8
|
//#region src/migration/load-project.ts
|
|
9
9
|
const configOf = async (file) => (await importProjectFile(file)).default ?? {};
|
|
@@ -32,6 +32,7 @@ const loadMigrationProject = async (dir, extensions = []) => {
|
|
|
32
32
|
db: project.db ?? ownDb,
|
|
33
33
|
...project.auth && { auth: project.auth },
|
|
34
34
|
...project.events && { events: project.events },
|
|
35
|
+
jobs: jobDefinitions(project.jobs ?? {}),
|
|
35
36
|
close: async () => {
|
|
36
37
|
await ownDb?.destroy();
|
|
37
38
|
}
|
package/dist/migration/parts.js
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
|
-
import { authSchemaMigration, changesMigration } from "@protobase/server";
|
|
1
|
+
import { authSchemaMigration, changesMigration, jobsSchemaMigration } from "@protobase/server";
|
|
2
2
|
//#region src/migration/parts.ts
|
|
3
3
|
/** Every feature the project's config enables, in the order their files are written. */
|
|
4
|
-
const migrationParts = (project) => [
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
const migrationParts = (project) => [
|
|
5
|
+
...project.auth ? [{
|
|
6
|
+
name: "auth",
|
|
7
|
+
title: "The auth schema changes this version of Protobase needs",
|
|
8
|
+
sql: () => authSchemaMigration(project.auth)
|
|
9
|
+
}] : [],
|
|
10
|
+
...project.jobs.length > 0 ? [{
|
|
11
|
+
name: "jobs",
|
|
12
|
+
title: "The jobs schema this version of Protobase needs",
|
|
13
|
+
sql: () => jobsSchemaMigration(project.db)
|
|
14
|
+
}] : [],
|
|
15
|
+
{
|
|
16
|
+
name: "changes",
|
|
17
|
+
title: "The change capture the resources' events need",
|
|
18
|
+
sql: () => changesMigration(project.db, project.models, project.events)
|
|
19
|
+
}
|
|
20
|
+
];
|
|
13
21
|
//#endregion
|
|
14
22
|
export { migrationParts };
|
|
@@ -7,7 +7,7 @@ import path from "node:path";
|
|
|
7
7
|
const out = (text) => process.stdout.write(text);
|
|
8
8
|
const collect = (value, previous) => [...previous, value];
|
|
9
9
|
const registerMigration = (program) => {
|
|
10
|
-
program.command("migration").description("Write what the database still needs for every feature the config enables (auth, events) as the project's next SQL migrations").option("--dir <dir>", "migrations folder", "db/migrations").option("--extend <dir>", "merge the protobase.config.ts in this folder before the project's, as dev and build do, repeatable", collect, []).action(async (opts) => {
|
|
10
|
+
program.command("migration").description("Write what the database still needs for every feature the config enables (auth, events, jobs) as the project's next SQL migrations").option("--dir <dir>", "migrations folder", "db/migrations").option("--extend <dir>", "merge the protobase.config.ts in this folder before the project's, as dev and build do, repeatable", collect, []).action(async (opts) => {
|
|
11
11
|
const project = await loadMigrationProject(process.cwd(), resolveExtensions(opts.extend));
|
|
12
12
|
try {
|
|
13
13
|
const files = await writeMigrations(migrationParts(project), path.resolve(opts.dir));
|
package/dist/package.js
CHANGED
package/dist/serve/app.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { isApiPath } from "../bundle/manifest.js";
|
|
2
2
|
import { serveStaticSite } from "./static-site.js";
|
|
3
|
-
import { configExports, createAdmin } from "@protobase/server";
|
|
3
|
+
import { configExports, createAdmin, jobDefinitions, jobsDrainApp } from "@protobase/server";
|
|
4
4
|
import { Hono } from "hono";
|
|
5
5
|
import { logger } from "hono/logger";
|
|
6
6
|
//#region src/serve/app.ts
|
|
7
|
-
const createServeApp = ({ project, db, requestLog, site }) => {
|
|
7
|
+
const createServeApp = ({ project, db, requestLog, site, jobs, drainToken }) => {
|
|
8
8
|
const { resources, views, pages, userMenu } = configExports(project.config);
|
|
9
9
|
const admin = createAdmin({
|
|
10
10
|
resources,
|
|
@@ -17,6 +17,7 @@ const createServeApp = ({ project, db, requestLog, site }) => {
|
|
|
17
17
|
...project.functions && { functions: project.functions },
|
|
18
18
|
...project.files && { files: project.files },
|
|
19
19
|
...project.events && { events: project.events },
|
|
20
|
+
jobs: jobDefinitions(project.jobs ?? {}),
|
|
20
21
|
options: {
|
|
21
22
|
onUnhandledError: (error) => console.error(error),
|
|
22
23
|
...project.options
|
|
@@ -26,6 +27,10 @@ const createServeApp = ({ project, db, requestLog, site }) => {
|
|
|
26
27
|
if (requestLog) app.use(logger());
|
|
27
28
|
app.get("/health", (c) => c.json({ status: "ok" }));
|
|
28
29
|
if (site) app.use("*", (c, next) => isApiPath(site.api, c.req.path) ? next() : serveStaticSite(site, c.req.raw));
|
|
30
|
+
if (jobs && drainToken) app.route("/", jobsDrainApp({
|
|
31
|
+
runtime: jobs,
|
|
32
|
+
token: drainToken
|
|
33
|
+
}));
|
|
29
34
|
app.route("/", admin);
|
|
30
35
|
return app;
|
|
31
36
|
};
|
package/dist/serve/env.js
CHANGED
|
@@ -5,11 +5,15 @@ const readServeEnv = (env) => {
|
|
|
5
5
|
if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error(`PORT must be a port number, not "${env.PORT}"`);
|
|
6
6
|
const minutes = env.PROTOBASE_FILES_CLEANUP_MINUTES ? Number(env.PROTOBASE_FILES_CLEANUP_MINUTES) : 60;
|
|
7
7
|
if (!Number.isInteger(minutes) || minutes < 0) throw new Error(`PROTOBASE_FILES_CLEANUP_MINUTES must be a whole number of minutes, not "${env.PROTOBASE_FILES_CLEANUP_MINUTES}"`);
|
|
8
|
+
const jobs = env.PROTOBASE_JOBS ?? "on";
|
|
9
|
+
if (jobs !== "on" && jobs !== "off") throw new Error(`PROTOBASE_JOBS must be on or off, not "${jobs}"`);
|
|
8
10
|
return {
|
|
9
11
|
port,
|
|
10
12
|
databaseUrl: env.DATABASE_URL || void 0,
|
|
11
13
|
requestLog: Boolean(env.REQUEST_LOG),
|
|
12
|
-
filesCleanupMinutes: minutes
|
|
14
|
+
filesCleanupMinutes: minutes,
|
|
15
|
+
runJobs: jobs === "on",
|
|
16
|
+
...env.PROTOBASE_JOBS_DRAIN_TOKEN && { drainToken: env.PROTOBASE_JOBS_DRAIN_TOKEN }
|
|
13
17
|
};
|
|
14
18
|
};
|
|
15
19
|
//#endregion
|
package/dist/serve/lifecycle.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createPgDb } from "../project/pg-db.js";
|
|
2
2
|
import { createServeApp } from "./app.js";
|
|
3
3
|
import { startCleanupTimer } from "./cleanup-timer.js";
|
|
4
|
-
import { checkAuthSchema, checkChanges, configExports, createFileCleanup } from "@protobase/server";
|
|
4
|
+
import { checkAuthSchema, checkChanges, checkJobsSchema, configExports, createFileCleanup, jobDefinitions, startJobs } from "@protobase/server";
|
|
5
5
|
import { createAdaptorServer } from "@hono/node-server";
|
|
6
6
|
import pg from "pg";
|
|
7
7
|
//#region src/serve/lifecycle.ts
|
|
@@ -19,14 +19,35 @@ const startServe = async ({ project, env, createDb = (url) => createPgDb(pg, url
|
|
|
19
19
|
const ownDb = project.db ? void 0 : createDb(env.databaseUrl);
|
|
20
20
|
const db = project.db ?? ownDb;
|
|
21
21
|
const { resources } = configExports(project.config);
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
db,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
const definitions = jobDefinitions(project.jobs ?? {});
|
|
23
|
+
const startJobsAndListen = async () => {
|
|
24
|
+
await checkChanges(db, resources.map((resource) => resource.toModel()), project.events);
|
|
25
|
+
if (definitions.length > 0) await checkJobsSchema(db);
|
|
26
|
+
const jobs = definitions.length > 0 ? await startJobs({
|
|
27
|
+
db,
|
|
28
|
+
jobs: definitions,
|
|
29
|
+
work: env.runJobs,
|
|
30
|
+
onError: (error) => console.error(error)
|
|
31
|
+
}) : void 0;
|
|
32
|
+
const app = createServeApp({
|
|
33
|
+
project,
|
|
34
|
+
db,
|
|
35
|
+
requestLog: env.requestLog,
|
|
36
|
+
site,
|
|
37
|
+
...jobs && { jobs },
|
|
38
|
+
...env.drainToken && { drainToken: env.drainToken }
|
|
39
|
+
});
|
|
40
|
+
const server = createAdaptorServer({ fetch: app.fetch });
|
|
41
|
+
await listen(server, env.port).catch(async (error) => {
|
|
42
|
+
await jobs?.stop();
|
|
43
|
+
throw error;
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
server,
|
|
47
|
+
jobs
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
const { server, jobs } = await startJobsAndListen().catch(async (error) => {
|
|
30
51
|
await ownDb?.destroy();
|
|
31
52
|
throw error;
|
|
32
53
|
});
|
|
@@ -39,6 +60,7 @@ const startServe = async ({ project, env, createDb = (url) => createPgDb(pg, url
|
|
|
39
60
|
const close = async () => {
|
|
40
61
|
await closeServer(server);
|
|
41
62
|
await timer?.stop();
|
|
63
|
+
await jobs?.stop();
|
|
42
64
|
await ownDb?.destroy();
|
|
43
65
|
};
|
|
44
66
|
return {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { readManifest } from "../bundle/manifest.js";
|
|
2
|
+
import { checkBundleVersion } from "../serve/bundle-version.js";
|
|
3
|
+
import { createPgDb } from "../project/pg-db.js";
|
|
4
|
+
import { loadBundle } from "../serve/load-bundle.js";
|
|
5
|
+
import { statSync } from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { checkJobsSchema, jobDefinitions, startJobs } from "@protobase/server";
|
|
8
|
+
import pg from "pg";
|
|
9
|
+
//#region src/worker/start-worker.ts
|
|
10
|
+
const bundleConfigFile = async (bundle) => {
|
|
11
|
+
if (!statSync(bundle).isDirectory()) return bundle;
|
|
12
|
+
return path.join(bundle, (await readManifest(bundle)).server);
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Runs the project's jobs, and nothing else, until `stop`: the same handlers `protobase serve` runs, in a process of
|
|
16
|
+
* their own. It reads the jobs schema at start and refuses one that is missing or behind, like `serve`.
|
|
17
|
+
*/
|
|
18
|
+
const startWorker = async ({ project, databaseUrl, createDb = (url) => createPgDb(pg, url) }) => {
|
|
19
|
+
const definitions = jobDefinitions(project.jobs ?? {});
|
|
20
|
+
if (definitions.length === 0) throw new Error("The bundle has no jobs: export them from protobase.config.ts as `jobs`");
|
|
21
|
+
if (!project.db && !databaseUrl) throw new Error("No database: set DATABASE_URL or export `db` from protobase.config.ts");
|
|
22
|
+
const ownDb = project.db ? void 0 : createDb(databaseUrl);
|
|
23
|
+
const db = project.db ?? ownDb;
|
|
24
|
+
const start = async () => {
|
|
25
|
+
await checkJobsSchema(db);
|
|
26
|
+
return startJobs({
|
|
27
|
+
db,
|
|
28
|
+
jobs: definitions,
|
|
29
|
+
onError: (error) => console.error(error)
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
const jobs = await start().catch(async (error) => {
|
|
33
|
+
await ownDb?.destroy();
|
|
34
|
+
throw error;
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
jobs: definitions.length,
|
|
38
|
+
stop: async () => {
|
|
39
|
+
await jobs.stop();
|
|
40
|
+
await ownDb?.destroy();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
/** `protobase worker <bundle>`: the bundle's jobs until SIGTERM or SIGINT, once its manifest shows a version this CLI runs. */
|
|
45
|
+
const loadWorkerBundle = async (bundle) => {
|
|
46
|
+
const file = await bundleConfigFile(bundle);
|
|
47
|
+
await checkBundleVersion(file);
|
|
48
|
+
return loadBundle(file);
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
51
|
+
export { bundleConfigFile, loadWorkerBundle, startWorker };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/cli",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
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.8.
|
|
38
|
-
"@protobase/query": "0.8.
|
|
39
|
-
"@protobase/schema": "0.8.
|
|
40
|
-
"@protobase/server": "0.8.
|
|
41
|
-
"@protobase/ui": "0.8.
|
|
37
|
+
"@protobase/layout": "0.8.2",
|
|
38
|
+
"@protobase/query": "0.8.2",
|
|
39
|
+
"@protobase/schema": "0.8.2",
|
|
40
|
+
"@protobase/server": "0.8.2",
|
|
41
|
+
"@protobase/ui": "0.8.2",
|
|
42
42
|
"@scalar/hono-api-reference": "^0.12.10",
|
|
43
43
|
"@tailwindcss/vite": "^4.3.3",
|
|
44
44
|
"@vitejs/plugin-react": "^6.1.2",
|