@protobase/cli 0.12.4 → 0.12.6
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/build/plugin-build.js +10 -1
- package/dist/build/plugin-styles.js +70 -0
- package/dist/dev/entry.js +3 -1
- package/dist/dev/run.js +2 -1
- package/dist/main.js +6 -1
- package/dist/package.js +1 -1
- package/dist/project/error-reporter.js +9 -0
- package/dist/project/project-admin.js +9 -4
- package/dist/serve/crash-reports.js +16 -0
- package/dist/serve/lifecycle.js +2 -2
- package/dist/serve/serve-bundle.js +6 -1
- package/package.json +7 -6
|
@@ -3,6 +3,7 @@ import { uiPackageDir } from "../project/admin-app.js";
|
|
|
3
3
|
import { extensionImports } from "../project/extensions.js";
|
|
4
4
|
import { sharedModules } from "../project/shared-modules.js";
|
|
5
5
|
import { pluginFieldTypes } from "./plugin-fields.js";
|
|
6
|
+
import { layerPluginUtilities } from "./plugin-styles.js";
|
|
6
7
|
import { createRequire } from "node:module";
|
|
7
8
|
import { existsSync } from "node:fs";
|
|
8
9
|
import path from "node:path";
|
|
@@ -69,6 +70,13 @@ const pluginStyles = (file, code) => ({
|
|
|
69
70
|
resolveId: (id) => id === file ? id : void 0,
|
|
70
71
|
load: (id) => id === file ? code : void 0
|
|
71
72
|
});
|
|
73
|
+
const layeredStyles = () => ({
|
|
74
|
+
name: "protobase-plugin-layered-styles",
|
|
75
|
+
enforce: "post",
|
|
76
|
+
generateBundle(_, bundle) {
|
|
77
|
+
for (const file of Object.values(bundle)) if (file.type === "asset" && file.fileName.endsWith(".css")) file.source = layerPluginUtilities(String(file.source));
|
|
78
|
+
}
|
|
79
|
+
});
|
|
72
80
|
const outputOf = (result) => {
|
|
73
81
|
if (Array.isArray(result)) return result[0];
|
|
74
82
|
if ("output" in result) return result;
|
|
@@ -106,7 +114,8 @@ const buildPlugin = async ({ pluginDir, outDir, name, projectDir, ignore = [] })
|
|
|
106
114
|
...projectDir === void 0 ? [] : [extensionImports(projectDir, [{ dir: pluginDir }])],
|
|
107
115
|
pluginStyles(styles, styleCode(pluginDir, target.theme, [outDir, ...ignore])),
|
|
108
116
|
react(),
|
|
109
|
-
tailwindcss()
|
|
117
|
+
tailwindcss(),
|
|
118
|
+
layeredStyles()
|
|
110
119
|
],
|
|
111
120
|
build: {
|
|
112
121
|
outDir,
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import postcss from "postcss";
|
|
2
|
+
//#region src/build/plugin-styles.ts
|
|
3
|
+
/**
|
|
4
|
+
* The layer, nested in `utilities`, that holds a plugin's utilities without a variant. Rules outside a nested layer win
|
|
5
|
+
* over the rules inside it, so the app's own utilities win over these, whatever the order of the stylesheets.
|
|
6
|
+
*/
|
|
7
|
+
const pluginBaseLayer = "plugin";
|
|
8
|
+
const withoutBrackets = (name) => {
|
|
9
|
+
let depth = 0;
|
|
10
|
+
let out = "";
|
|
11
|
+
for (const char of name) if (char === "[") depth += 1;
|
|
12
|
+
else if (char === "]") depth -= 1;
|
|
13
|
+
else if (depth === 0) out += char;
|
|
14
|
+
return out;
|
|
15
|
+
};
|
|
16
|
+
/** Whether a utility's class name has a variant, such as `md:flex`, `hover:bg-muted` or `[&>*]:p-2`. */
|
|
17
|
+
const hasVariant = (name) => withoutBrackets(name).includes(":");
|
|
18
|
+
const unescape = (name) => name.replace(/\\([0-9a-fA-F]{1,6}) ?|\\(.)/g, (_, hex, char) => hex ? String.fromCodePoint(parseInt(hex, 16)) : char);
|
|
19
|
+
/** The class names in a selector, also those inside :is(), :where() and :not(), unescaped: `.md\:flex` is `md:flex`. */
|
|
20
|
+
const classNames = (selector) => [...selector.matchAll(/\.((?:\\[0-9a-fA-F]{1,6} ?|\\.|[\w-]|[^\x00-\x7f])+)/g)].map(([, name]) => unescape(name));
|
|
21
|
+
const around = (node, children) => {
|
|
22
|
+
const copy = node.clone();
|
|
23
|
+
copy.removeAll();
|
|
24
|
+
return copy.append(children.map((child) => child.clone()));
|
|
25
|
+
};
|
|
26
|
+
const partition = (nodes) => {
|
|
27
|
+
const parts = {
|
|
28
|
+
base: [],
|
|
29
|
+
variant: []
|
|
30
|
+
};
|
|
31
|
+
for (const node of nodes) if (node.type === "rule") {
|
|
32
|
+
const varies = (selector) => classNames(selector).some(hasVariant);
|
|
33
|
+
const base = node.selectors.filter((selector) => !varies(selector));
|
|
34
|
+
const variant = node.selectors.filter(varies);
|
|
35
|
+
if (base.length > 0) parts.base.push(variant.length === 0 ? node : node.clone({ selectors: base }));
|
|
36
|
+
if (variant.length > 0) parts.variant.push(base.length === 0 ? node : node.clone({ selectors: variant }));
|
|
37
|
+
} else if (node.type === "atrule" && [
|
|
38
|
+
"media",
|
|
39
|
+
"supports",
|
|
40
|
+
"container"
|
|
41
|
+
].includes(node.name) && node.nodes) {
|
|
42
|
+
const inner = partition(node.nodes);
|
|
43
|
+
if (inner.base.length > 0) parts.base.push(around(node, inner.base));
|
|
44
|
+
if (inner.variant.length > 0) parts.variant.push(around(node, inner.variant));
|
|
45
|
+
} else parts.variant.push(node);
|
|
46
|
+
return parts;
|
|
47
|
+
};
|
|
48
|
+
const isUtilities = (node) => node.type === "atrule" && node.name === "layer" && node.params.trim() === "utilities" && node.nodes !== void 0;
|
|
49
|
+
/**
|
|
50
|
+
* A plugin's stylesheet with its utilities without a variant moved into a layer under the app's utilities. The app
|
|
51
|
+
* loads a plugin's stylesheet after its own, where a plugin's `.flex` would otherwise win over the app's `md:block` on
|
|
52
|
+
* the app's own elements. A plugin's variants stay after the app's utilities, so its `hidden md:flex` still works when
|
|
53
|
+
* only the app's stylesheet has `.hidden`.
|
|
54
|
+
*/
|
|
55
|
+
const layerPluginUtilities = (css) => {
|
|
56
|
+
const root = postcss.parse(css);
|
|
57
|
+
for (const node of root.nodes.filter(isUtilities)) {
|
|
58
|
+
const { base, variant } = partition(node.nodes);
|
|
59
|
+
const kept = variant.map((child) => child.clone());
|
|
60
|
+
node.removeAll();
|
|
61
|
+
if (base.length > 0) node.append(postcss.atRule({
|
|
62
|
+
name: "layer",
|
|
63
|
+
params: pluginBaseLayer
|
|
64
|
+
}).append(base.map((child) => child.clone())));
|
|
65
|
+
node.append(kept);
|
|
66
|
+
}
|
|
67
|
+
return root.toString();
|
|
68
|
+
};
|
|
69
|
+
//#endregion
|
|
70
|
+
export { classNames, hasVariant, layerPluginUtilities, pluginBaseLayer };
|
package/dist/dev/entry.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { devJobs } from "./dev-jobs.js";
|
|
2
|
+
import { projectErrorReporter } from "../project/error-reporter.js";
|
|
2
3
|
import { projectAdmin } from "../project/project-admin.js";
|
|
3
4
|
import { loadProject, projectDb } from "./project.js";
|
|
4
5
|
import { checkAuthSchema, checkChanges, checkWebhooksSchema, configExports, hasUsers } from "@protobase/server";
|
|
@@ -16,10 +17,11 @@ if (project.auth && !store.__protobaseAuthSchemaChecked) {
|
|
|
16
17
|
}
|
|
17
18
|
await checkChanges(db, resources.map((resource) => resource.toModel()), project.events);
|
|
18
19
|
if (project.webhooks) await checkWebhooksSchema(db, project.webhooks);
|
|
20
|
+
const reporter = await projectErrorReporter(project, process.env);
|
|
19
21
|
const { admin, jobs } = projectAdmin({
|
|
20
22
|
...project,
|
|
21
23
|
authenticate: project.authenticate
|
|
22
|
-
}, project.exports, db);
|
|
24
|
+
}, project.exports, db, void 0, reporter);
|
|
23
25
|
await devJobs(db, jobs);
|
|
24
26
|
var entry_default = new Hono().route("/", admin);
|
|
25
27
|
if (project.auth && !store.__protobaseNoUsersShown) {
|
package/dist/dev/run.js
CHANGED
package/dist/main.js
CHANGED
|
@@ -10,7 +10,9 @@ import { resolveConnectionUrl } from "./db/connect.js";
|
|
|
10
10
|
import { parseHosts } from "./dev/hosts.js";
|
|
11
11
|
import { runDev } from "./dev/run.js";
|
|
12
12
|
import { runDoctor } from "./doctor/run.js";
|
|
13
|
+
import { projectErrorReporter } from "./project/error-reporter.js";
|
|
13
14
|
import { runScaffold } from "./scaffold/run.js";
|
|
15
|
+
import { reportCrashes } from "./serve/crash-reports.js";
|
|
14
16
|
import { closeOnSignals } from "./serve/signals.js";
|
|
15
17
|
import { serveBundle } from "./serve/serve-bundle.js";
|
|
16
18
|
import { serveBundleDir } from "./serve/serve-dir.js";
|
|
@@ -110,8 +112,11 @@ program.command("serve").description("Serve a bundle written by `protobase build
|
|
|
110
112
|
});
|
|
111
113
|
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) => {
|
|
112
114
|
loadNearestEnvFile(process.cwd());
|
|
115
|
+
const project = await loadWorkerBundle(bundle);
|
|
116
|
+
const reporter = await projectErrorReporter(project, process.env);
|
|
117
|
+
if (reporter) reportCrashes(reporter);
|
|
113
118
|
const worker = await startWorker({
|
|
114
|
-
project
|
|
119
|
+
project,
|
|
115
120
|
databaseUrl: process.env.DATABASE_URL || void 0
|
|
116
121
|
});
|
|
117
122
|
closeOnSignals(worker.stop);
|
package/dist/package.js
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { errorReportingSettings, startErrorReporting } from "@protobase/server";
|
|
2
|
+
//#region src/project/error-reporter.ts
|
|
3
|
+
/** The project's error reporter, when its `options.errorReporting` or `PROTOBASE_SENTRY_DSN` sets a DSN. */
|
|
4
|
+
const projectErrorReporter = async (project, env) => {
|
|
5
|
+
const settings = errorReportingSettings(project.options?.errorReporting, env);
|
|
6
|
+
return settings ? startErrorReporting(settings) : void 0;
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
export { projectErrorReporter };
|
|
@@ -3,11 +3,13 @@ import { configExports, createAdmin, jobDefinitions } from "@protobase/server";
|
|
|
3
3
|
/**
|
|
4
4
|
* The admin API of a project over `db`, as `dev`, `serve` and `worker` build it: its config module's resources, views,
|
|
5
5
|
* pages and subscriptions, and the rest of its config; served from a bundle, the plugins it carries and their field
|
|
6
|
-
* types. `jobs` are the project's own and the delivery of its events.
|
|
6
|
+
* types. `jobs` are the project's own and the delivery of its events. With a `reporter`, every error the API turns
|
|
7
|
+
* into a 500 goes to the error tracker too, besides the project's `onUnhandledError` or the console.
|
|
7
8
|
*/
|
|
8
|
-
const projectAdmin = (project, config, db, ui) => {
|
|
9
|
+
const projectAdmin = (project, config, db, ui, reporter) => {
|
|
9
10
|
const { resources, views, pages, userMenu, bottomBars, subscriptions } = configExports(config);
|
|
10
11
|
const own = jobDefinitions(project.jobs ?? {});
|
|
12
|
+
const unhandled = project.options?.onUnhandledError ?? ((error) => console.error(error));
|
|
11
13
|
const admin = createAdmin({
|
|
12
14
|
resources,
|
|
13
15
|
views,
|
|
@@ -28,8 +30,11 @@ const projectAdmin = (project, config, db, ui) => {
|
|
|
28
30
|
fieldTypes: ui.fieldTypes
|
|
29
31
|
},
|
|
30
32
|
options: {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
...project.options,
|
|
34
|
+
onUnhandledError: (error, request) => {
|
|
35
|
+
reporter?.report(error, request);
|
|
36
|
+
unhandled(error, request);
|
|
37
|
+
}
|
|
33
38
|
}
|
|
34
39
|
});
|
|
35
40
|
return {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/serve/crash-reports.ts
|
|
2
|
+
/**
|
|
3
|
+
* Reports an exception or a rejection nothing handled, then ends the process as Node and Bun do without a listener:
|
|
4
|
+
* the error printed, exit code 1. The report has `flushMs` to reach the error tracker.
|
|
5
|
+
*/
|
|
6
|
+
const reportCrashes = (reporter, target = process, flushMs = 2e3) => {
|
|
7
|
+
const crash = (error) => {
|
|
8
|
+
console.error(error);
|
|
9
|
+
reporter.report(error);
|
|
10
|
+
reporter.flush(flushMs).then(() => target.exit(1));
|
|
11
|
+
};
|
|
12
|
+
target.on("uncaughtException", crash);
|
|
13
|
+
target.on("unhandledRejection", crash);
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
export { reportCrashes };
|
package/dist/serve/lifecycle.js
CHANGED
|
@@ -14,7 +14,7 @@ const listen = (server, port) => new Promise((resolve, reject) => {
|
|
|
14
14
|
});
|
|
15
15
|
});
|
|
16
16
|
const closeServer = (server) => new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
|
|
17
|
-
const startServe = async ({ project, env, createDb = (url) => createPgDb(pg, url), site, ui }) => {
|
|
17
|
+
const startServe = async ({ project, env, createDb = (url) => createPgDb(pg, url), site, ui, reporter }) => {
|
|
18
18
|
if (!project.db && !env.databaseUrl) throw new Error("No database: set DATABASE_URL or export `db` from protobase.config.ts");
|
|
19
19
|
if (project.auth) await checkAuthSchema(project.auth);
|
|
20
20
|
const ownDb = project.db ? void 0 : createDb(env.databaseUrl);
|
|
@@ -23,7 +23,7 @@ const startServe = async ({ project, env, createDb = (url) => createPgDb(pg, url
|
|
|
23
23
|
const startJobsAndListen = async () => {
|
|
24
24
|
await checkChanges(db, resources.map((resource) => resource.toModel()), project.events);
|
|
25
25
|
if (project.webhooks) await checkWebhooksSchema(db, project.webhooks);
|
|
26
|
-
const { admin, jobs: definitions } = projectAdmin(project, project.config, db, ui);
|
|
26
|
+
const { admin, jobs: definitions } = projectAdmin(project, project.config, db, ui, reporter);
|
|
27
27
|
if (definitions.length > 0) await checkJobsSchema(db);
|
|
28
28
|
const jobs = definitions.length > 0 ? await startJobs({
|
|
29
29
|
db,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { readManifest } from "../bundle/manifest.js";
|
|
2
|
+
import { projectErrorReporter } from "../project/error-reporter.js";
|
|
2
3
|
import { bundleUi } from "./bundle-ui.js";
|
|
3
4
|
import { checkBundleVersion } from "./bundle-version.js";
|
|
5
|
+
import { reportCrashes } from "./crash-reports.js";
|
|
4
6
|
import { readServeEnv } from "./env.js";
|
|
5
7
|
import { startServe } from "./lifecycle.js";
|
|
6
8
|
import { loadBundle } from "./load-bundle.js";
|
|
@@ -12,11 +14,14 @@ const serveBundle = async (file, env, out, site) => {
|
|
|
12
14
|
await checkBundleVersion(file);
|
|
13
15
|
const manifest = await readManifest(path.dirname(file));
|
|
14
16
|
const project = await loadBundle(file);
|
|
17
|
+
const reporter = await projectErrorReporter(project, env);
|
|
18
|
+
if (reporter) reportCrashes(reporter);
|
|
15
19
|
const server = await startServe({
|
|
16
20
|
project,
|
|
17
21
|
env: serveEnv,
|
|
18
22
|
site,
|
|
19
|
-
ui: bundleUi(manifest)
|
|
23
|
+
ui: bundleUi(manifest),
|
|
24
|
+
...reporter && { reporter }
|
|
20
25
|
});
|
|
21
26
|
closeOnSignals(server.close);
|
|
22
27
|
out(`protobase serve listening on port ${server.port}\n`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/cli",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.6",
|
|
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.12.
|
|
38
|
-
"@protobase/query": "0.12.
|
|
39
|
-
"@protobase/schema": "0.12.
|
|
40
|
-
"@protobase/server": "0.12.
|
|
41
|
-
"@protobase/ui": "0.12.
|
|
37
|
+
"@protobase/layout": "0.12.6",
|
|
38
|
+
"@protobase/query": "0.12.6",
|
|
39
|
+
"@protobase/schema": "0.12.6",
|
|
40
|
+
"@protobase/server": "0.12.6",
|
|
41
|
+
"@protobase/ui": "0.12.6",
|
|
42
42
|
"@scalar/hono-api-reference": "^0.12.10",
|
|
43
43
|
"@tailwindcss/vite": "^4.3.3",
|
|
44
44
|
"@vitejs/plugin-react": "^6.1.2",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"kysely": "^0.29.6",
|
|
52
52
|
"lightningcss": "^1.33.0",
|
|
53
53
|
"pg": "^8.23.1",
|
|
54
|
+
"postcss": "^8.5.29",
|
|
54
55
|
"postgres": "^3.4.9",
|
|
55
56
|
"ts-morph": "^28.0.0",
|
|
56
57
|
"vite": "^8.3.3",
|