bzd-nodejs
How to write, build, test, and contribute Node.js/JavaScript/Vue code in the bzd monorepo
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
How to write, build, test, and contribute Node.js/JavaScript/Vue code in the bzd monorepo
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
How to run, test, build, type-check, format, and manage Python dependencies in the bzd monorepo
Core software design principles — ALWAYS load this before planning and before writing any implementation code
How to build, test, run, and maintain Bazel targets in the bzd monorepo — always load this before invoking any ./tools/bazel command
How to read, write, and wire BDL (Bzd Description Language) files and their Bazel rules in the bzd monorepo — load this before touching any .bdl file or bdl_* Bazel rule
How to write, build, test, and contribute C++ code in the bzd monorepo
How to build/test esp32/esp32s3/etc targets in the bzd monorepo.
| name | bzd-nodejs |
| description | How to write, build, test, and contribute Node.js/JavaScript/Vue code in the bzd monorepo |
| compatibility | opencode |
.js) — no .mjs, no .cjs for new code./tools/bazel — use @bzd_rules_nodejs//nodejs:defs.bzl custom macros, never raw nodejs_binarybzd_rules_nodejs) — never invoke system node or npm directlypnpm install or npm install manually)bzd_nodejs_web_binary)#bzd/ resolves to the monorepo root — always use this for cross-package importscamelCase for functions and variables (repo-wide convention)| Path | Purpose |
|---|---|
nodejs/core/ | Core runtime libraries: exception, log, format, cache, router, http, rest, websocket, etc. |
nodejs/core/tests/ | Unit tests for core libraries (*_test.js) |
nodejs/db/ | Database abstractions: key-value store, storage, timeseries |
nodejs/email/ | Email integration (SendGrid, etc.) |
nodejs/payment/ | Payment integration (Stripe) |
nodejs/styles/ | SCSS design tokens and themes |
nodejs/utils/ | Utility functions: array, pathlib, regexpr, object, query, etc. |
nodejs/vue/ | Vue 3 layer: apps, components, directives, router, plugins |
nodejs/vue/apps/ | Base frontend/backend app scaffolding (frontend.js, backend.js) |
nodejs/vue/apps/example/ | Minimal reference app (start here for a new app) |
nodejs/vue/components/ | Reusable Vue components: graph, layout, logger, menu, modal, terminal, etc. |
nodejs/vue/directives/ | Custom Vue directives: loading, resize, tooltip |
tools/nodejs/ | npm dependency lockfiles: requirements.in, requirements.json |
apps/ | Full application targets (each app has a frontend/ and/or backend/) |
# Test all Node.js targets
./tools/bazel test //nodejs/...
# Test a specific target
./tools/bazel test //nodejs/core/tests:format
./tools/bazel test //nodejs/core/tests:router
# Show full test output even on success
./tools/bazel test --test_output=all //nodejs/core/tests:format
# Build a backend binary
./tools/bazel build //nodejs/vue/apps/example:backend
# Build a frontend bundle (produces a .bundle directory)
./tools/bazel build //nodejs/vue/apps/example:frontend
# Run a backend server
./tools/bazel run //nodejs/vue/apps/example:backend
# Test all app targets
./tools/bazel test //apps/...
Any frontend-related change should be tested visually, using screenshot to ensure the change has been made correctly.
All Node.js targets use custom rules from @bzd_rules_nodejs//nodejs:defs.bzl.
load("@bzd_rules_nodejs//nodejs:defs.bzl",
"bzd_nodejs_library",
"bzd_nodejs_binary",
"bzd_nodejs_test",
"bzd_nodejs_web_binary",
"bzd_nodejs_web_library",
"bzd_nodejs_requirements_compile",
)
| Rule | Purpose |
|---|---|
bzd_nodejs_library | Reusable library (srcs, deps, packages) |
bzd_nodejs_binary | Runnable Node.js server binary |
bzd_nodejs_test | Node.js test — runs under Mocha automatically |
bzd_nodejs_web_binary | Vite-bundled frontend app (produces a .bundle directory) |
bzd_nodejs_web_library | Vite-compiled UMD/CJS library |
bzd_nodejs_requirements_compile | Converts requirements.in → requirements.json lockfile |
bzd_nodejs_static | Generates static files from a Node.js script |
Key design: bzd_nodejs_binary and bzd_nodejs_test are macros that create a <name>.install target first (runs pnpm offline to populate node_modules), then the actual binary/test target.
#bzd/ PrefixThe #bzd/ import prefix resolves to the monorepo root. Use it for all cross-package imports (any import referencing a file outside the current package/directory).
// Cross-package import — ALWAYS use #bzd/
import ExceptionFactory from "#bzd/nodejs/core/exception.js";
import LogFactory from "#bzd/nodejs/core/log.js";
import { HttpClient } from "#bzd/nodejs/core/http/client.js";
import config from "#bzd/nodejs/vue/apps/config.json" with { type: "json" };
// Relative import — OK within the same package/directory
import Format from "./format.js";
import Router from "../router.js";
// npm package import — bare package name, no prefix
import { Command } from "commander/esm.mjs";
Rule of thumb:
./, ../)#bzd/.js (ES Modules) for all new code") — no single quotes(x) => x)camelCasePascalCasevalue_, helperMethod_())console.log: use LogFactory instead (see Logging section)tools/nodejs/.prettierrc.json){
"arrowParens": "always",
"bracketSpacing": true,
"printWidth": 120,
"semi": true,
"singleQuote": false,
"useTabs": true,
"vueIndentScriptAndStyle": true
}
All modules create their own typed exception class using ExceptionFactory. This is the standard error-handling mechanism — never throw raw Error objects.
import ExceptionFactory from "#bzd/nodejs/core/exception.js";
const Exception = ExceptionFactory("mymodule", "submodule");
// Throw an error
Exception.error("Something went wrong: {}", detail);
// Assert a condition
Exception.assert(value > 0, "Value must be positive, got {}", value);
// Assert equality (deep comparison — works on objects, arrays, sets)
Exception.assertEqual(result, expected);
// Assert a precondition (user input validation)
Exception.assertPrecondition(input !== null, "Input must not be null");
// Assert that an async block throws with a matching message
await Exception.assertThrowsWithMatch(async () => {
await riskyOperation();
}, "expected error substring");
// Wrap a caught Error with context
const e = Exception.fromError(caughtError, "Context: {}", contextInfo);
import LogFactory from "#bzd/nodejs/core/log.js";
const Log = LogFactory("mymodule");
Log.info("Server started on port {}", port);
Log.warning("Deprecated feature used: {}", feature);
Log.error("Operation failed: {}", error);
Log.debug("Processing item {}", item);
nodejs/<module>/my_feature.js:import ExceptionFactory from "#bzd/nodejs/core/exception.js";
const Exception = ExceptionFactory("mymodule", "myfeature");
export class MyFeature {
constructor(config) {
this.config_ = config;
}
doSomething(value) {
Exception.assert(value !== null, "Value must not be null");
return value * 2;
}
}
export default MyFeature;
nodejs/<module>/BUILD.bazel:load("@bzd_rules_nodejs//nodejs:defs.bzl", "bzd_nodejs_library")
bzd_nodejs_library(
name = "my_feature",
srcs = ["my_feature.js"],
visibility = ["//visibility:public"],
deps = [
"//nodejs/core:exception",
"//nodejs/core:log",
],
)
"//nodejs/<module>:my_feature"Tests use Mocha and are named <name>_test.js. Mocha globals (describe, it) are available automatically — no import needed.
import ExceptionFactory from "#bzd/nodejs/core/exception.js";
import MyFeature from "#bzd/nodejs/mymodule/my_feature.js";
const Exception = ExceptionFactory("test", "myfeature");
describe("MyFeature", () => {
describe("doSomething", () => {
it("doubles a positive number", () => {
const f = new MyFeature({});
Exception.assertEqual(f.doSomething(3), 6);
});
it("throws on null input", () => {
const f = new MyFeature({});
Exception.assertThrowsWithMatch(() => {
f.doSomething(null);
}, "Value must not be null");
});
});
describe("async behavior", () => {
it("resolves correctly", async () => {
const result = await f.asyncMethod();
Exception.assertEqual(result, expectedValue);
});
});
});
load("@bzd_rules_nodejs//nodejs:defs.bzl", "bzd_nodejs_test")
bzd_nodejs_test(
name = "my_feature",
srcs = ["my_feature_test.js"],
main = "my_feature_test.js",
deps = [
"//nodejs/mymodule:my_feature",
"//nodejs/core:exception",
],
)
*_test.js files (preferred pattern)load("@bzd_rules_nodejs//nodejs:defs.bzl", "bzd_nodejs_test")
test_srcs = glob(["*_test.js"])
[bzd_nodejs_test(
name = src.replace("_test.js", ""),
srcs = [src],
main = src,
deps = [
"//nodejs/mymodule:my_feature",
"//nodejs/core:exception",
],
) for src in test_srcs]
bzd_nodejs_test(
name = "my_test",
srcs = ["my_test.js"],
main = "my_test.js",
packages = ["@nodejs_deps//:some_package"],
deps = ["//nodejs/core:exception"],
)
apps/my_app/
├── BUILD.bazel
├── api.json # REST/WebSocket API schema
├── backend.js # Node.js HTTP server entry point
└── frontend/
├── BUILD.bazel
├── app.js # Frontend entry point
└── app.vue # Root Vue component
BUILD.bazel (backend)load("@bzd_rules_nodejs//nodejs:defs.bzl", "bzd_nodejs_binary")
bzd_nodejs_binary(
name = "backend",
srcs = ["backend.js", "api.json"],
args = [
"--static",
"$(rootpath //apps/my_app/frontend:frontend).bundle",
],
data = ["//apps/my_app/frontend:frontend"],
main = "backend.js",
deps = ["//nodejs/vue/apps:backend"],
)
BUILD.bazel (frontend)load("@bzd_rules_nodejs//nodejs:defs.bzl", "bzd_nodejs_web_binary")
bzd_nodejs_web_binary(
name = "frontend",
srcs = ["app.js", "app.vue"],
config_scss = "//nodejs/styles:default.scss",
main = "app.js",
deps = ["//nodejs/vue/apps:frontend"],
)
backend.js skeletonimport APIv1 from "#bzd/api.json" with { type: "json" };
import Backend from "#bzd/nodejs/vue/apps/backend.js";
const backend = await Backend.make(APIv1).useAuthentication().useLogger().setup();
await backend.start();
frontend/app.mjs skeletonimport APIv1 from "#bzd/api.json" with { type: "json" };
import Frontend from "#bzd/nodejs/vue/apps/frontend.js";
import App from "./app.vue";
const frontend = Frontend.make(App).useRest(APIv1.rest).useAuthentication().useLogger().setup();
frontend.mount("#app");
Vue components use .vue single-file component (SFC) format with <template>, <script>, and optionally <style>.
<template>
<div class="bzd-my-component">
<slot name="header"></slot>
<div class="bzd-my-component-content">{{ message }}</div>
</div>
</template>
<script>
import ExceptionFactory from "#bzd/nodejs/core/exception.js";
const Exception = ExceptionFactory("components", "mycomponent");
export default {
props: {
message: { type: String, required: true },
},
data() {
return {
value_: null,
};
},
methods: {
handleClick() {
Exception.assert(this.message, "Message must not be empty");
},
},
};
</script>
<style lang="scss">
.bzd-my-component {
display: flex;
}
</style>
load("@bzd_rules_nodejs//nodejs:defs.bzl", "bzd_nodejs_library")
bzd_nodejs_library(
name = "my_component",
srcs = ["my_component.vue"],
visibility = ["//visibility:public"],
deps = [
"//nodejs:vue",
"//nodejs/core:exception",
],
)
All npm packages are declared in tools/nodejs/requirements.in and locked in tools/nodejs/requirements.json.
Add the package name to tools/nodejs/requirements.in:
my-package
my-package=1.2.3
Regenerate the lockfile:
./tools/bazel run //tools/nodejs:requirements
Use the package in a BUILD file via the packages attribute:
bzd_nodejs_library(
name = "my_lib",
srcs = ["my_lib.js"],
packages = ["@nodejs_deps//:my_package"],
visibility = ["//visibility:public"],
)
Import in code using the bare package name (no #bzd/ prefix):
import { something } from "my-package";
import { Command } from "commander/esm.mjs";
# Run the sanitizer (Prettier + linting) on changed files
./tools/bazel run //:sanitizer
# Run on ALL files in the repo
./tools/bazel run //:sanitizer -- --all
# Check-only mode (report issues, no writes)
./tools/bazel run //:sanitizer -- --check --all
Prettier is applied to .mjs, .cjs, .js, .vue, .css, and .scss files.
To exclude a directory from sanitizer checks, place a .sanitizerignore file in that directory.
# Full quality gate (tests + sanitizer)
./quality_gate.sh
# Test all Node.js targets
./tools/bazel test //nodejs/...
# Test all app targets
./tools/bazel test //apps/...
# Sanitizer check only
./tools/bazel run //:sanitizer -- --check --all
Always run ./tools/bazel run //:sanitizer before committing.