mirror of
https://github.com/bknd-io/bknd/
synced 2026-08-02 16:16:02 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 99c783e7f0 | |||
| 4defc4d1e7 | |||
| fd1323fecc | |||
| 44d7c092f8 | |||
| 45cf746855 | |||
| db961b5447 | |||
| 48d432ddb2 | |||
| 980886b5c5 | |||
| 827e7faa7f | |||
| e6b17f6424 |
+2
-1
@@ -63,7 +63,7 @@
|
||||
"dayjs": "^1.11.19",
|
||||
"fast-xml-parser": "^5.3.1",
|
||||
"hono": "4.10.4",
|
||||
"json-schema-library": "10.0.0-rc7",
|
||||
"json-schema-library": "10.0.0-rc7",
|
||||
"json-schema-to-ts": "^3.1.1",
|
||||
"jsonv-ts": "^0.10.1",
|
||||
"kysely": "0.28.8",
|
||||
@@ -108,6 +108,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"commander": "^14.0.2",
|
||||
"dotenv": "^17.2.3",
|
||||
"errore": "^0.14.0",
|
||||
"jotai": "^2.12.2",
|
||||
"jsdom": "^26.1.0",
|
||||
"kysely-generic-sqlite": "^1.2.1",
|
||||
|
||||
@@ -4,7 +4,9 @@ import type { CliBkndConfig, CliCommand } from "cli/types";
|
||||
import { Option } from "commander";
|
||||
import { config, type App, type CreateAppConfig, type MaybePromise, registries } from "bknd";
|
||||
import dotenv from "dotenv";
|
||||
import * as errore from "errore";
|
||||
import c from "picocolors";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import {
|
||||
PLATFORMS,
|
||||
@@ -18,6 +20,26 @@ import { createRuntimeApp, makeConfig } from "bknd/adapter";
|
||||
import { colorizeConsole, isBun } from "bknd/utils";
|
||||
import { withConfigOptions, type WithConfigOptions } from "cli/utils/options";
|
||||
|
||||
class ConfigLoadError extends errore.createTaggedError({
|
||||
name: "ConfigLoadError",
|
||||
message: "Failed to load config: $reason",
|
||||
}) {}
|
||||
|
||||
class NeedsBunError extends errore.createTaggedError({
|
||||
name: "NeedsBunError",
|
||||
message: "Config requires Bun runtime",
|
||||
}) {}
|
||||
|
||||
class NeedsTypeStrippingError extends errore.createTaggedError({
|
||||
name: "NeedsTypeStrippingError",
|
||||
message: "Node $version needs --experimental-strip-types for .ts config",
|
||||
}) {}
|
||||
|
||||
class ReexecFailedError extends errore.createTaggedError({
|
||||
name: "ReexecFailedError",
|
||||
message: "Re-exec failed: $reason",
|
||||
}) {}
|
||||
|
||||
const env_files = [".env", ".dev.vars"];
|
||||
dotenv.config({
|
||||
path: env_files.map((file) => path.resolve(process.cwd(), file)),
|
||||
@@ -55,6 +77,106 @@ if (!registries.media.has(local)) {
|
||||
registries.media.register(local, StorageLocalAdapter);
|
||||
}
|
||||
|
||||
function needsTypeStripping(configFilePath: string): boolean {
|
||||
if (!/\.[mc]?ts$/.test(configFilePath)) return false;
|
||||
const [major, minor] = process.versions.node.split(".").map(Number);
|
||||
// Node v22.06 introduced experimental TypeScript support via strip types.
|
||||
return major === 22 && minor! < 18 && minor! > 5;
|
||||
}
|
||||
|
||||
function reexecWithTypeStripping(): ReexecFailedError | never {
|
||||
if (process.env.__BKND_REEXEC) {
|
||||
return new ReexecFailedError({
|
||||
reason: "TS config still failed after re-exec with --experimental-strip-types",
|
||||
});
|
||||
}
|
||||
|
||||
const cliPath = path.resolve(process.argv[1]!);
|
||||
const args = ["--experimental-strip-types", cliPath, ...process.argv.slice(2)];
|
||||
|
||||
console.info(
|
||||
c.yellow("Node <22.18 detected, re-executing with --experimental-strip-types"),
|
||||
);
|
||||
|
||||
const result = errore.try({
|
||||
try: () =>
|
||||
execFileSync(process.execPath, args, {
|
||||
stdio: "inherit",
|
||||
env: { ...process.env, __BKND_REEXEC: "1" },
|
||||
}),
|
||||
catch: (e) =>
|
||||
new ReexecFailedError({
|
||||
reason: "Failed to re-exec with --experimental-strip-types",
|
||||
cause: e,
|
||||
}),
|
||||
});
|
||||
|
||||
if (result instanceof Error) return result;
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
async function loadConfigFile(
|
||||
configFilePath: string,
|
||||
): Promise<ConfigLoadError | NeedsBunError | NeedsTypeStrippingError | CliBkndConfig> {
|
||||
if (needsTypeStripping(configFilePath) && !process.execArgv.includes("--experimental-strip-types")) {
|
||||
return new NeedsTypeStrippingError({
|
||||
version: process.versions.node,
|
||||
});
|
||||
}
|
||||
|
||||
const result = await errore.tryAsync({
|
||||
try: () => import(configFilePath).then((m) => m.default as CliBkndConfig),
|
||||
catch: (e) => {
|
||||
const needsBun =
|
||||
(e instanceof ReferenceError && /\bBun\b.*not defined/.test(e.message)) ||
|
||||
(e instanceof Error && "code" in e && e.code === "ERR_UNSUPPORTED_ESM_URL_SCHEME" && /bun:/.test(e.message));
|
||||
if (needsBun) return new NeedsBunError();
|
||||
return new ConfigLoadError({
|
||||
reason: e instanceof Error ? e.message : String(e),
|
||||
cause: e,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function reexecUnderBun(): ReexecFailedError | never {
|
||||
if (process.env.__BKND_REEXEC) {
|
||||
return new ReexecFailedError({
|
||||
reason: "Config requires Bun but still failed under Bun runtime",
|
||||
});
|
||||
}
|
||||
|
||||
const bunPath = process.env.BUN_INSTALL
|
||||
? path.join(process.env.BUN_INSTALL, "bin", "bun")
|
||||
: "bun";
|
||||
|
||||
const cliPath = path.resolve(process.argv[1]!);
|
||||
const args = [cliPath, ...process.argv.slice(2)];
|
||||
|
||||
console.info(
|
||||
c.yellow("Config requires Bun runtime, re-executing:"),
|
||||
c.cyan(`bun ${args.join(" ")}`),
|
||||
);
|
||||
|
||||
const result = errore.try({
|
||||
try: () =>
|
||||
execFileSync(bunPath, args, {
|
||||
stdio: "inherit",
|
||||
env: { ...process.env, __BKND_REEXEC: "1" },
|
||||
}),
|
||||
catch: (e) =>
|
||||
new ReexecFailedError({
|
||||
reason: "Could not re-exec under Bun. Install Bun (https://bun.sh) or use bknd/adapter/node in your config.",
|
||||
cause: e,
|
||||
}),
|
||||
});
|
||||
|
||||
if (result instanceof Error) return result;
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
type MakeAppConfig = {
|
||||
connection?: CreateAppConfig["connection"];
|
||||
server?: { platform?: Platform };
|
||||
@@ -99,12 +221,33 @@ export async function makeAppFromEnv(options: Partial<RunOptions> = {}) {
|
||||
// check configuration file to be present
|
||||
} else if (configFilePath) {
|
||||
console.info("Using config from", c.cyan(configFilePath));
|
||||
try {
|
||||
const config = (await import(configFilePath).then((m) => m.default)) as CliBkndConfig;
|
||||
app = await makeConfigApp(config, options.server);
|
||||
} catch (e) {
|
||||
console.error("Failed to load config:", e);
|
||||
process.exit(1);
|
||||
const configResult = await loadConfigFile(configFilePath);
|
||||
|
||||
if (configResult instanceof Error) {
|
||||
errore.matchError(configResult, {
|
||||
NeedsTypeStrippingError: () => {
|
||||
const reexecResult = reexecWithTypeStripping();
|
||||
// only reached on failure — success calls process.exit(0)
|
||||
console.error(c.red(reexecResult.message));
|
||||
process.exit(1);
|
||||
},
|
||||
NeedsBunError: () => {
|
||||
const reexecResult = reexecUnderBun();
|
||||
// only reached on failure — success calls process.exit(0)
|
||||
console.error(c.red(reexecResult.message));
|
||||
process.exit(1);
|
||||
},
|
||||
ConfigLoadError: (e) => {
|
||||
console.error(c.red("Failed to load config:"), e.reason);
|
||||
process.exit(1);
|
||||
},
|
||||
Error: (e) => {
|
||||
console.error(c.red("Failed to load config:"), e.message);
|
||||
process.exit(1);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
app = await makeConfigApp(configResult, options.server);
|
||||
}
|
||||
|
||||
// try to use an in-memory connection
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"commander": "^14.0.2",
|
||||
"dotenv": "^17.2.3",
|
||||
"errore": "^0.14.0",
|
||||
"jotai": "^2.12.2",
|
||||
"jsdom": "^26.1.0",
|
||||
"kysely-generic-sqlite": "^1.2.1",
|
||||
@@ -1936,6 +1937,8 @@
|
||||
|
||||
"error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="],
|
||||
|
||||
"errore": ["errore@0.14.0", "", { "bin": { "errore": "dist/cli.js" } }, "sha512-2RI7BGdxWlJe6yJ3DK0pIvLNzXEE4M41VmF2HrH7C2HldTjYiORQNJ+ufsvAvOUGW/4rRHPFOpooP3ebIRIEXw=="],
|
||||
|
||||
"es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
@@ -2462,7 +2465,7 @@
|
||||
|
||||
"jest-worker": ["jest-worker@24.9.0", "", { "dependencies": { "merge-stream": "^2.0.0", "supports-color": "^6.1.0" } }, "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw=="],
|
||||
|
||||
"jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="],
|
||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
|
||||
"jotai": ["jotai@2.15.1", "", { "peerDependencies": { "@babel/core": ">=7.0.0", "@babel/template": ">=7.0.0", "@types/react": ">=17.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@babel/core", "@babel/template", "@types/react", "react"] }, "sha512-yHT1HAZ3ba2Q8wgaUQ+xfBzEtcS8ie687I8XVCBinfg4bNniyqLIN+utPXWKQE93LMF5fPbQSVRZqgpcN5yd6Q=="],
|
||||
|
||||
@@ -3846,7 +3849,7 @@
|
||||
|
||||
"@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
|
||||
|
||||
"@bknd/plasmic/@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
|
||||
"@bknd/plasmic/@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="],
|
||||
|
||||
"@bundled-es-modules/tough-cookie/tough-cookie": ["tough-cookie@4.1.4", "", { "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" } }, "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag=="],
|
||||
|
||||
@@ -4046,8 +4049,6 @@
|
||||
|
||||
"@swc/helpers/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
|
||||
"@tailwindcss/node/lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.6.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg=="],
|
||||
@@ -4750,7 +4751,7 @@
|
||||
|
||||
"@babel/preset-env/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.3", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", "resolve": "^1.14.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg=="],
|
||||
|
||||
"@bknd/plasmic/@types/bun/bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
|
||||
"@bknd/plasmic/@types/bun/bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="],
|
||||
|
||||
"@bundled-es-modules/tough-cookie/tough-cookie/universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="],
|
||||
|
||||
|
||||
@@ -95,27 +95,12 @@ export default {
|
||||
|
||||
The `app` function is useful if you need a cross-platform way to access the environment variables. For example, on Cloudflare Workers, you can only access environment variables inside a request handler. If you're exclusively using a node-like environment, it's safe to access the environment variables directly from `process.env`.
|
||||
|
||||
If you're using `npx bknd run`, make sure to create a file in a file format that `node` can load, otherwise you may run into an error that the file couldn't be found:
|
||||
TypeScript configuration files (`.ts`) are fully supported and work out of the box with `npx bknd run` — no additional flags or tools required.
|
||||
|
||||
```
|
||||
[INF] 2025-03-28 18:02:21 Using config from bknd.config.ts
|
||||
[ERR] 2025-03-28 18:02:21 Failed to load config: Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'bknd.config.ts' imported from [...]
|
||||
at packageResolve (node:internal/modules/esm/resolve:857:9)
|
||||
at [...] {
|
||||
code: 'ERR_MODULE_NOT_FOUND'
|
||||
}
|
||||
```
|
||||
|
||||
If you still want to use a `.ts` extension, you can start the CLI e.g. using `node` (>=v22.6.0):
|
||||
If your config imports from a runtime-specific adapter (e.g. `bknd/adapter/bun`), you need to run the CLI with that runtime:
|
||||
|
||||
```sh
|
||||
node --experimental-strip-types node_modules/.bin/bknd run
|
||||
```
|
||||
|
||||
Or with `tsx`:
|
||||
|
||||
```sh
|
||||
npx tsx node_modules/.bin/bknd run
|
||||
bun node_modules/.bin/bknd run
|
||||
```
|
||||
|
||||
### Turso/LibSQL database
|
||||
|
||||
+5
-1
@@ -8,6 +8,7 @@
|
||||
"node": ">=22.13"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "bun i && bun run --filter bknd build:all",
|
||||
"updater": "bun x npm-check-updates -ui",
|
||||
"ci": "find . -name 'node_modules' -type d -exec rm -rf {} + && bun install",
|
||||
"npm:local": "verdaccio --config verdaccio.yml",
|
||||
@@ -24,5 +25,8 @@
|
||||
"typescript": "^5.9.3",
|
||||
"verdaccio": "^6.2.1"
|
||||
},
|
||||
"workspaces": ["app", "packages/*"]
|
||||
"workspaces": [
|
||||
"app",
|
||||
"packages/*"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user