+45
| <p align="center"> | ||
| <img src="https://tekir.io/logo.svg" width="80" alt="tekir" /> | ||
| </p> | ||
| <h1 align="center">@tekir/cli</h1> | ||
| <p align="center">tekir command-line tool: serve, build, and any provider-registered command</p> | ||
| <p align="center"> | ||
| <a href="https://www.npmjs.com/package/@tekir/cli"><img src="https://img.shields.io/npm/v/@tekir/cli.svg" alt="npm version" /></a> | ||
| <a href="https://www.npmjs.com/package/@tekir/cli"><img src="https://img.shields.io/npm/dm/@tekir/cli.svg" alt="npm downloads" /></a> | ||
| <a href="https://github.com/tekir-io/tekir/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@tekir/cli.svg" alt="license" /></a> | ||
| </p> | ||
| <p align="center"> | ||
| <a href="https://tekir.io">Website</a> · <a href="https://docs.tekir.io">Documentation</a> · <a href="https://github.com/tekir-io/tekir">GitHub</a> | ||
| </p> | ||
| --- | ||
| ## Installation | ||
| ```bash | ||
| # Globally | ||
| bun add -g @tekir/cli | ||
| # Or as a dev dependency in your project | ||
| bun add -d @tekir/cli | ||
| ``` | ||
| ## Usage | ||
| ```bash | ||
| tekir serve # Start the server (in-process) | ||
| tekir serve --dev # Start with watch mode (NODE_ENV=development) | ||
| tekir build --outdir ./dist # Plain bundle | ||
| tekir build --compile --outfile server # Single self-contained executable | ||
| tekir <command> # Any built-in / provider / user command | ||
| ``` | ||
| For full usage and configuration, see the [documentation](https://docs.tekir.io/installation). | ||
| ## License | ||
| MIT |
+78
-72
@@ -18,2 +18,11 @@ #!/usr/bin/env node | ||
| // arg for an entry path. | ||
| // | ||
| // `--env-file <path>` (multi) is forwarded to the underlying runtime as | ||
| // its native `--env-file` flag, not parsed by tekir. The bin filters out | ||
| // missing files (with a warning) before forwarding, so monorepo dev | ||
| // scripts that chain optional per-package `.env` files work even when | ||
| // some of them don't exist yet. For non-watch in-process commands the | ||
| // bin re-execs itself once under the runtime to apply the flags; for | ||
| // `serve --dev` the existing `--watch` spawn already covers the same | ||
| // surface in a single hop. | ||
| import { existsSync, readFileSync } from 'node:fs' | ||
@@ -30,6 +39,3 @@ import { spawn, spawnSync } from 'node:child_process' | ||
| * `rest` in original order so the in-app dispatcher and Bun.build see | ||
| * only their own flags. `--env-file` is the same surface as Bun's and | ||
| * Node 20.6+'s runtime flag of the same name; tekir handles it itself | ||
| * because the bin imports the entry in-process and the underlying | ||
| * runtime never sees the flag otherwise. | ||
| * only their own flags. | ||
| */ | ||
@@ -85,39 +91,33 @@ function extractTekirFlags(argv) { | ||
| /** | ||
| * Minimal `.env` loader matching the dotenv conventions Bun and Node use | ||
| * for their own `--env-file` flag: | ||
| * - blank lines and `#` comments skipped | ||
| * - optional `export KEY=value` prefix | ||
| * - single- or double-quoted values are unwrapped | ||
| * - shell-provided env (anything already set when tekir started) wins; | ||
| * later files override earlier ones for the same key | ||
| * | ||
| * Caller passes `shellKeys` (a Set of keys that existed before any file | ||
| * load) so the precedence rule is independent of load order. | ||
| * Strip `--env-file` flags from argv when we're about to re-spawn ourselves | ||
| * under the runtime: the runtime applies them as native flags before | ||
| * loading our bin, so the bin must not see them again or it would loop. | ||
| */ | ||
| function loadEnvFile(path, shellKeys) { | ||
| let content | ||
| try { | ||
| content = readFileSync(path, 'utf8') | ||
| } catch (err) { | ||
| console.error(`[tekir] Could not read --env-file '${path}': ${err.message}`) | ||
| process.exit(1) | ||
| function stripEnvFileFlags(argv) { | ||
| const out = [] | ||
| for (let i = 0; i < argv.length; i++) { | ||
| const t = argv[i] | ||
| if (t === '--env-file') { i++; continue } | ||
| if (t.startsWith('--env-file=')) continue | ||
| out.push(t) | ||
| } | ||
| for (const rawLine of content.split(/\r?\n/)) { | ||
| let line = rawLine.trim() | ||
| if (!line || line.startsWith('#')) continue | ||
| if (line.startsWith('export ')) line = line.slice('export '.length).trimStart() | ||
| const eq = line.indexOf('=') | ||
| if (eq === -1) continue | ||
| const key = line.slice(0, eq).trim() | ||
| if (!key) continue | ||
| let value = line.slice(eq + 1).trim() | ||
| if (value.length >= 2) { | ||
| const f = value[0] | ||
| const l = value[value.length - 1] | ||
| if ((f === '"' && l === '"') || (f === "'" && l === "'")) { | ||
| value = value.slice(1, -1) | ||
| } | ||
| return out | ||
| } | ||
| /** | ||
| * Filter user-supplied env-file paths to those that exist on disk. Both | ||
| * Bun and Node 20.6+'s native `--env-file` flag hard-error on a missing | ||
| * file, which would break monorepos that chain optional per-package | ||
| * `.env` files. Warn for each absent file so typos still surface. | ||
| */ | ||
| function existingEnvFiles(paths) { | ||
| const out = [] | ||
| for (const f of paths) { | ||
| if (existsSync(f)) { | ||
| out.push(f) | ||
| } else { | ||
| console.warn(`[tekir] --env-file '${f}' not found, skipping`) | ||
| } | ||
| if (!shellKeys.has(key)) process.env[key] = value | ||
| } | ||
| return out | ||
| } | ||
@@ -159,5 +159,5 @@ | ||
| console.error('Environment files: pass `--env-file <path>` (multi) to load `.env`-style files') | ||
| console.error(' before the entry runs. Shell-provided env wins; later files override earlier') | ||
| console.error(' ones. `tekir serve --dev` also forwards them to `bun --watch` so edits to a') | ||
| console.error(' loaded `.env` take effect on each restart.') | ||
| console.error(' via the runtime\'s own `--env-file` flag (Bun, or Node 20.6+). tekir filters') | ||
| console.error(' out missing files with a warning before forwarding, so optional per-package') | ||
| console.error(' `.env` files in a monorepo do not need to exist for the rest to load.') | ||
| console.error('') | ||
@@ -205,14 +205,22 @@ console.error('Build flags: --target, --format esm|cjs|iife, --minify / --no-minify,') | ||
| /** | ||
| * `Bun.build` is Bun-only. On Node we re-spawn the same bin under `bun` | ||
| * with the original argv preserved; if Bun is not on PATH we print an | ||
| * actionable error. | ||
| * Re-spawn this same bin under `runtime` with native `--env-file` flags | ||
| * applied first. The runtime parses the env-files into its own | ||
| * `process.env` before our bin's second invocation imports the entry, so | ||
| * we never have to ship a hand-rolled dotenv parser. Exits the parent | ||
| * with the child's exit code. | ||
| */ | ||
| function ensureBunOrReexec() { | ||
| if (isBun) return | ||
| const result = spawnSync('bun', [process.argv[1] ?? '', ...process.argv.slice(2)], { | ||
| stdio: 'inherit', | ||
| }) | ||
| function reexecUnder(runtime, envFilesToForward) { | ||
| const childArgv = [ | ||
| ...envFilesToForward.map(f => `--env-file=${f}`), | ||
| process.argv[1] ?? '', | ||
| ...stripEnvFileFlags(rawArgv), | ||
| ] | ||
| const result = spawnSync(runtime, childArgv, { stdio: 'inherit' }) | ||
| if (result.error && /** @type {NodeJS.ErrnoException} */ (result.error).code === 'ENOENT') { | ||
| console.error('[tekir] `tekir build` requires Bun (Bun.build is the bundler).') | ||
| console.error(' Install Bun: https://bun.sh, or run via `bunx @tekir/cli build`.') | ||
| if (runtime === 'bun') { | ||
| console.error('[tekir] `bun` is required for this command but is not on PATH.') | ||
| console.error(' Install Bun: https://bun.sh.') | ||
| } else { | ||
| console.error(`[tekir] '${runtime}' is not on PATH.`) | ||
| } | ||
| process.exit(1) | ||
@@ -226,13 +234,8 @@ } | ||
| const command = argv[0] | ||
| const presentEnvFiles = existingEnvFiles(envFiles) | ||
| // Preload env files into the parent process before any in-process import | ||
| // or subprocess spawn. Shell-provided env wins; later files override | ||
| // earlier ones for the same key (matches Bun's and Node's `--env-file` | ||
| // load order). | ||
| if (envFiles.length > 0) { | ||
| const shellKeys = new Set(Object.keys(process.env)) | ||
| for (const f of envFiles) loadEnvFile(f, shellKeys) | ||
| } | ||
| if (!command) { | ||
| // Bare `tekir`: behaves like `tekir serve`. If env-files were passed, | ||
| // re-exec under the current runtime so it loads them first. | ||
| if (presentEnvFiles.length > 0) reexecUnder(isBun ? 'bun' : 'node', presentEnvFiles) | ||
| const entry = await findEntry(entryFlag) | ||
@@ -245,4 +248,10 @@ await runEntry(entry, undefined, []) | ||
| if (command === 'build') { | ||
| ensureBunOrReexec() | ||
| // From here on we are guaranteed to be running under Bun. | ||
| // `Bun.build` is Bun-only. Re-exec under bun if we're on Node, OR if | ||
| // the user passed `--env-file` flags (so bun loads them as the build | ||
| // process boots and any custom plugin code that reads `process.env` | ||
| // sees them). | ||
| if (!isBun || presentEnvFiles.length > 0) { | ||
| reexecUnder('bun', presentEnvFiles) | ||
| } | ||
| // We are now guaranteed to be running under Bun with env applied. | ||
| const entry = await findEntry(entryFlag) | ||
@@ -262,16 +271,9 @@ const flags = argv.slice(1) | ||
| // has to spawn a subprocess; in-app `tekir()` still receives `serve` as | ||
| // the command and starts the server normally inside it. | ||
| // the command and starts the server normally inside it. `--env-file` | ||
| // flags are forwarded to the runtime alongside `--watch` so editing a | ||
| // loaded `.env` re-applies on the next restart. | ||
| if (command === 'serve' && rest.includes('--dev')) { | ||
| const watchRest = rest.filter(a => a !== '--dev') | ||
| const runner = isBun ? 'bun' : 'node' | ||
| const watchFlags = ['--watch'] | ||
| // Bun's watch mode re-reads `--env-file` on each restart, so editing | ||
| // an `.env` mid-watch picks up immediately. Node 20.6+ also supports | ||
| // `--env-file`; older Node versions reject it, so we only forward it | ||
| // when running under Bun. Either way the parent has already preloaded | ||
| // the files into `process.env`, which the spawned child inherits, so | ||
| // initial boot env is correct on both runtimes. | ||
| if (isBun) { | ||
| for (const f of envFiles) watchFlags.push(`--env-file=${f}`) | ||
| } | ||
| const watchFlags = ['--watch', ...presentEnvFiles.map(f => `--env-file=${f}`)] | ||
| const proc = spawn(runner, [...watchFlags, entry, 'serve', ...watchRest], { | ||
@@ -283,3 +285,7 @@ stdio: 'inherit', | ||
| } else { | ||
| // Any other command imports the entry in-process. If env-files were | ||
| // passed, re-exec under the runtime first so its native `--env-file` | ||
| // flag applies them before our second invocation runs the entry. | ||
| if (presentEnvFiles.length > 0) reexecUnder(isBun ? 'bun' : 'node', presentEnvFiles) | ||
| await runEntry(entry, command, rest) | ||
| } |
+1
-1
| { | ||
| "name": "@tekir/cli", | ||
| "version": "0.1.0", | ||
| "version": "0.1.1", | ||
| "description": "tekir command-line tool: serve, build, generate-key, and provider-registered commands.", | ||
@@ -5,0 +5,0 @@ "author": "dev@tekir.io", |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
No README
QualityPackage does not have a README. This may indicate a failed publish or a low quality package.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
14299
15.29%3
50%265
2.32%0
-100%46
Infinity%3
-40%