@backifydev/cli 0.2.0 → 0.2.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/README.md +14 -7
- package/dist/index.js +151 -29
- package/package.json +28 -28
package/README.md
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
# @backifydev/cli
|
|
2
|
-
|
|
3
|
-
Set up and check a Backify application repository.
|
|
4
|
-
|
|
5
|
-
```sh
|
|
6
|
-
npx @backifydev/cli init --app app_xxxxx
|
|
1
|
+
# @backifydev/cli
|
|
2
|
+
|
|
3
|
+
Set up and check a Backify application repository.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npx @backifydev/cli init --app app_xxxxx
|
|
7
7
|
npx @backifydev/cli doctor
|
|
8
8
|
npx @backifydev/cli sync
|
|
9
|
+
npx @backifydev/cli login
|
|
9
10
|
```
|
|
10
11
|
|
|
12
|
+
`login` opens the Backify portal in your browser. Sign in and select an organization; the portal returns the organization and access token to the CLI through a random, short-lived callback bound to `127.0.0.1`. The CLI stores them in `~/.backify.json`. Set `portalUrl` in that file to use another portal, or `BACKIFY_PORTAL_URL` in the environment. `login <token> [organizationId]` remains available for scripted development.
|
|
13
|
+
|
|
11
14
|
`init` installs `@backifydev/sdk`, writes `backify.json`, and maintains the Backify blocks in `AGENTS.md`, `CLAUDE.md`, and `.github/copilot-instructions.md`. It preserves other instructions in those files.
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
The app configuration stores `instructionVersion`, matching the version declared in each managed block and its immutable guide at `https://agent.backify.dev/v<number>`. The agent root always serves the latest guide as plain text. `doctor` detects version/content drift and malformed markers. Use `npx @backifydev/cli@latest sync` to explicitly upgrade all blocks and the configuration together; unrelated configuration and unmanaged instructions remain unchanged.
|
|
17
|
+
|
|
18
|
+
You can start from an empty folder without a Git repository. `init` creates a minimal private `package.json` before installing the SDK, without choosing a framework. Add the application's framework and development/build scripts afterward. Existing manifests are preserved; in a non-empty application or monorepo, run `init` from the appropriate JavaScript/TypeScript package directory.
|
|
19
|
+
|
|
20
|
+
See the [agent setup guide](https://agent.backify.dev/).
|
package/dist/index.js
CHANGED
|
@@ -4,14 +4,17 @@
|
|
|
4
4
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, rmSync } from "node:fs";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
import { join as join2 } from "node:path";
|
|
7
|
+
import { randomBytes } from "node:crypto";
|
|
8
|
+
import { createServer } from "node:http";
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
7
10
|
|
|
8
11
|
// src/setup.ts
|
|
9
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
10
|
-
import { dirname, join } from "node:path";
|
|
12
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from "node:fs";
|
|
13
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
11
14
|
import { spawnSync } from "node:child_process";
|
|
12
15
|
|
|
13
16
|
// ../platform/dist/index.js
|
|
14
|
-
var INSTRUCTION_VERSION =
|
|
17
|
+
var INSTRUCTION_VERSION = 2;
|
|
15
18
|
var SDK_PACKAGE = "@backifydev/sdk";
|
|
16
19
|
var DOCS_URL = "https://docs.backify.dev";
|
|
17
20
|
var capability = (name, imp, description, stateful = false, provisioner = name) => ({ name, import: imp, version: 1, stateful, provisioner, docs: `${DOCS_URL}/capabilities/${name}`, description, destructiveRemovalRequiresApproval: stateful });
|
|
@@ -38,25 +41,62 @@ function managedInstructions() {
|
|
|
38
41
|
|
|
39
42
|
# Backify Platform Instructions
|
|
40
43
|
|
|
41
|
-
|
|
44
|
+
Instruction version: ${INSTRUCTION_VERSION}
|
|
45
|
+
Versioned guide: https://agent.backify.dev/v${INSTRUCTION_VERSION}
|
|
42
46
|
|
|
43
|
-
|
|
47
|
+
## Scope and platform policy
|
|
44
48
|
|
|
45
|
-
|
|
49
|
+
This application runs on Backify. These rules govern application code targeting Backify, not the implementation of the Backify platform itself.
|
|
50
|
+
Use \`@backifydev/sdk\` for platform capabilities. Do not directly integrate or provision cloud infrastructure, hosted authentication, databases, storage, queues, or schedulers when an equivalent Backify capability exists. If a capability is missing, explain the gap and ask before adding an external platform dependency.
|
|
46
51
|
|
|
47
|
-
|
|
52
|
+
## SDK capability map
|
|
53
|
+
|
|
54
|
+
- Authentication and authorization: \`auth\`.
|
|
55
|
+
- Persistent data and files: \`db\` and \`storage\`.
|
|
56
|
+
- Server functions: \`defineFunction\`; background work and schedules: \`jobs\` and \`jobs.schedule\`.
|
|
57
|
+
- Events, outbound HTTP, and AI: \`events\`, \`http.fetch\`, and \`ai\`.
|
|
58
|
+
- Server-side secrets, configuration, audit, and logs: \`secrets\`, \`config\`, \`audit\`, and \`logs\`.
|
|
59
|
+
|
|
60
|
+
Verify API signatures against the installed SDK types and the relevant guide at ${DOCS_URL}/capabilities/. Never invent SDK methods. Use \`@backifydev/sdk/local\` for tests and local development; \`@backifydev/sdk/runtime\` is for runtime hosts, not application code.
|
|
61
|
+
|
|
62
|
+
## Security and data safety
|
|
63
|
+
|
|
64
|
+
Keep secrets server-side. Never commit or log credentials, expose secrets to browser code, or place cloud-management credentials in applications. Enforce authorization in trusted server code and scope data access to the authenticated user or organization as required.
|
|
65
|
+
Scope cookies to the exact application hostname by omitting the \`Domain\` attribute. Never use a parent-domain cookie unless explicitly required.
|
|
66
|
+
Require explicit confirmation before destructive data operations or removing stateful resources. Removing a capability declaration does not authorize deleting its data.
|
|
67
|
+
|
|
68
|
+
## Implementation workflow
|
|
69
|
+
|
|
70
|
+
Inspect the framework, lockfile, scripts, configuration, and applicable repository instructions before editing. Preserve the existing framework and unrelated work. Implement the requested application rather than stopping at SDK setup; ask what to build if requirements are missing.
|
|
71
|
+
Run relevant tests, typechecking, lint, and builds using the repository's scripts when available. Report changes, checks, and remaining blockers accurately; do not claim unverified success.
|
|
72
|
+
|
|
73
|
+
## Instruction maintenance
|
|
74
|
+
|
|
75
|
+
The authoritative documentation is ${DOCS_URL}/. The root at https://agent.backify.dev/ always shows the latest guide; use the versioned guide above for this repository's installed instructions.
|
|
76
|
+
Keep \`backify.json\` \`instructionVersion\` aligned with this block. Run \`npx @backifydev/cli@latest doctor\` to check consistency and \`npx @backifydev/cli@latest sync\` to adopt a newer instruction version. Never silently upgrade instructions during an unrelated feature change.
|
|
77
|
+
Preserve all content outside this managed block and all more-specific repository instructions.
|
|
48
78
|
|
|
49
79
|
${MANAGED_END}`;
|
|
50
80
|
}
|
|
81
|
+
function managedInstructionIssues(content) {
|
|
82
|
+
const normalized = content.replace(/\r\n/g, "\n"), starts = normalized.split(MANAGED_START).length - 1, ends = normalized.split(MANAGED_END).length - 1;
|
|
83
|
+
if (starts === 0 && ends === 0)
|
|
84
|
+
return ["does not contain Backify managed instructions"];
|
|
85
|
+
if (starts !== 1 || ends !== 1)
|
|
86
|
+
return ["contains duplicate or incomplete Backify managed markers"];
|
|
87
|
+
const start = normalized.indexOf(MANAGED_START), end = normalized.indexOf(MANAGED_END);
|
|
88
|
+
if (end < start)
|
|
89
|
+
return ["contains reversed Backify managed markers"];
|
|
90
|
+
return normalized.slice(start, end + MANAGED_END.length) === managedInstructions() ? [] : ["contains stale or inconsistent Backify managed instructions; run backify sync"];
|
|
91
|
+
}
|
|
51
92
|
function updateManagedInstructions(content) {
|
|
93
|
+
const issues = managedInstructionIssues(content);
|
|
94
|
+
if (issues.some((issue) => issue.includes("markers")))
|
|
95
|
+
throw new Error(issues.join("; "));
|
|
52
96
|
const block = managedInstructions(), start = content.indexOf(MANAGED_START), end = content.indexOf(MANAGED_END);
|
|
53
|
-
if (start >= 0
|
|
97
|
+
if (start >= 0)
|
|
54
98
|
return content.slice(0, start) + block + content.slice(end + MANAGED_END.length);
|
|
55
|
-
return content.trim() ?
|
|
56
|
-
|
|
57
|
-
${block}
|
|
58
|
-
` : `${block}
|
|
59
|
-
`;
|
|
99
|
+
return content.trim() ? content.trimEnd() + "\n\n" + block + "\n" : block + "\n";
|
|
60
100
|
}
|
|
61
101
|
function createConfig(appId) {
|
|
62
102
|
return { $schema: `${DOCS_URL}/schema/backify.json`, platform: "backify", sdk: SDK_PACKAGE, appId, instructionVersion: INSTRUCTION_VERSION, docs: DOCS_URL, instructions: { preferBackifyCapabilities: true, allowDirectCloudInfrastructure: false } };
|
|
@@ -89,12 +129,25 @@ function detectPackageManager(root = process.cwd()) {
|
|
|
89
129
|
return "npm";
|
|
90
130
|
}
|
|
91
131
|
function updateInstructions(root = process.cwd()) {
|
|
92
|
-
|
|
132
|
+
const updates = instructionFiles.map((relative) => {
|
|
93
133
|
const path = join(root, relative);
|
|
134
|
+
return { path, content: updateManagedInstructions(existsSync(path) ? readFileSync(path, "utf8") : "") };
|
|
135
|
+
});
|
|
136
|
+
for (const { path, content } of updates) {
|
|
94
137
|
mkdirSync(dirname(path), { recursive: true });
|
|
95
|
-
writeFileSync(path,
|
|
138
|
+
writeFileSync(path, content);
|
|
96
139
|
}
|
|
97
140
|
}
|
|
141
|
+
function synchronize(root = process.cwd()) {
|
|
142
|
+
const path = join(root, "backify.json");
|
|
143
|
+
if (!existsSync(path)) throw new Error("backify.json is missing; run backify init --app app_xxxxx");
|
|
144
|
+
const value = JSON.parse(readFileSync(path, "utf8")), issues = validateConfig(value);
|
|
145
|
+
if (issues.length) throw new Error(issues.join("; "));
|
|
146
|
+
if (value.instructionVersion > INSTRUCTION_VERSION) throw new Error("This app uses newer instructions. Install a matching or newer CLI before syncing.");
|
|
147
|
+
updateInstructions(root);
|
|
148
|
+
value.instructionVersion = INSTRUCTION_VERSION;
|
|
149
|
+
writeFileSync(path, JSON.stringify(value, null, 2) + "\n");
|
|
150
|
+
}
|
|
98
151
|
function writeConfig(appId, root = process.cwd()) {
|
|
99
152
|
writeFileSync(join(root, "backify.json"), JSON.stringify(createConfig(appId), null, 2) + "\n");
|
|
100
153
|
}
|
|
@@ -111,9 +164,17 @@ function installSdk(root = process.cwd()) {
|
|
|
111
164
|
const manager = detectPackageManager(root), args2 = manager === "npm" ? ["install", SDK_PACKAGE] : ["add", SDK_PACKAGE], result = spawnSync(manager, args2, { cwd: root, stdio: "inherit", shell: process.platform === "win32" });
|
|
112
165
|
if (result.status !== 0) throw new Error(`Could not install ${SDK_PACKAGE} with ${manager}`);
|
|
113
166
|
}
|
|
167
|
+
function ensurePackageManifest(root) {
|
|
168
|
+
const path = join(root, "package.json");
|
|
169
|
+
if (existsSync(path)) return;
|
|
170
|
+
const setupFiles = /* @__PURE__ */ new Set([".git", ".gitignore", ".github", "AGENTS.md", "CLAUDE.md", "README.md", "LICENSE", "backify.json"]);
|
|
171
|
+
if (readdirSync(root).some((file) => !setupFiles.has(file))) throw new Error("package.json is missing. Run backify init from the existing application's JavaScript/TypeScript package directory, or start in an empty folder. See https://agent.backify.dev/");
|
|
172
|
+
const name = basename(resolve(root)).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") || "backify-app";
|
|
173
|
+
writeFileSync(path, JSON.stringify({ name, version: "0.0.0", private: true, type: "module" }, null, 2) + "\n", { flag: "wx" });
|
|
174
|
+
}
|
|
114
175
|
function initialize(appId, root = process.cwd(), install = true) {
|
|
115
176
|
if (!/^app_[A-Za-z0-9_-]+$/.test(appId)) throw new Error("Provide an application ID using --app app_xxxxx");
|
|
116
|
-
|
|
177
|
+
ensurePackageManifest(root);
|
|
117
178
|
if (install) installSdk(root);
|
|
118
179
|
writeConfig(appId, root);
|
|
119
180
|
updateInstructions(root);
|
|
@@ -128,15 +189,14 @@ function doctor(root = process.cwd()) {
|
|
|
128
189
|
} catch {
|
|
129
190
|
issues.push("backify.json is missing or invalid JSON");
|
|
130
191
|
}
|
|
131
|
-
if (config
|
|
192
|
+
if (config && config.instructionVersion !== INSTRUCTION_VERSION) issues.push(`Repository instruction version ${config.instructionVersion}; CLI version ${INSTRUCTION_VERSION}. Use the matching CLI or run backify sync to upgrade.`);
|
|
132
193
|
for (const relative of instructionFiles) {
|
|
133
194
|
const path = join(root, relative);
|
|
134
195
|
if (!existsSync(path)) {
|
|
135
196
|
issues.push(`${relative} is missing`);
|
|
136
197
|
continue;
|
|
137
198
|
}
|
|
138
|
-
|
|
139
|
-
if (!text.includes(MANAGED_START) || !text.includes(MANAGED_END)) issues.push(`${relative} does not contain Backify managed instructions`);
|
|
199
|
+
issues.push(...managedInstructionIssues(readFileSync(path, "utf8")).map((issue) => `${relative} ${issue}`));
|
|
140
200
|
}
|
|
141
201
|
let version = "unknown";
|
|
142
202
|
try {
|
|
@@ -160,6 +220,67 @@ var option = (name) => {
|
|
|
160
220
|
const index = args.indexOf(name);
|
|
161
221
|
return index >= 0 ? args[index + 1] : void 0;
|
|
162
222
|
};
|
|
223
|
+
function openBrowser(url) {
|
|
224
|
+
const command2 = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open", args2 = process.platform === "win32" ? ["/c", "start", "", '"' + url + '"'] : [url];
|
|
225
|
+
const child = spawn(command2, args2, { stdio: "ignore", detached: true, shell: process.platform === "win32" });
|
|
226
|
+
child.unref();
|
|
227
|
+
}
|
|
228
|
+
async function browserLogin() {
|
|
229
|
+
const state = randomBytes(24).toString("base64url"), server = createServer((req, res) => {
|
|
230
|
+
if (req.method === "OPTIONS") {
|
|
231
|
+
res.writeHead(204, { "access-control-allow-origin": "*", "access-control-allow-methods": "POST, OPTIONS", "access-control-allow-headers": "content-type" });
|
|
232
|
+
return res.end();
|
|
233
|
+
}
|
|
234
|
+
if (req.method !== "POST" || req.url !== "/callback") {
|
|
235
|
+
res.writeHead(404);
|
|
236
|
+
return res.end();
|
|
237
|
+
}
|
|
238
|
+
let raw = "";
|
|
239
|
+
req.setEncoding("utf8");
|
|
240
|
+
req.on("data", (part) => raw += part);
|
|
241
|
+
req.on("end", () => {
|
|
242
|
+
try {
|
|
243
|
+
const value = JSON.parse(raw);
|
|
244
|
+
if (value.state !== state || typeof value.token !== "string" || typeof value.organizationId !== "string") throw new Error("Invalid callback");
|
|
245
|
+
res.writeHead(200, { "access-control-allow-origin": "*", "content-type": "text/plain" });
|
|
246
|
+
res.end("Backify CLI connected. You can close this tab.");
|
|
247
|
+
userConfig.token = value.token;
|
|
248
|
+
userConfig.organizationId = value.organizationId;
|
|
249
|
+
writeFileSync2(configPath, JSON.stringify(userConfig, null, 2));
|
|
250
|
+
server.close();
|
|
251
|
+
resolveLogin();
|
|
252
|
+
} catch {
|
|
253
|
+
res.writeHead(400, { "access-control-allow-origin": "*" });
|
|
254
|
+
res.end("Invalid or expired Backify CLI login.");
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
let resolveLogin = () => {
|
|
259
|
+
};
|
|
260
|
+
const completed = new Promise((resolve2) => resolveLogin = resolve2);
|
|
261
|
+
server.listen(0, "127.0.0.1", () => {
|
|
262
|
+
const address = server.address();
|
|
263
|
+
if (!address || typeof address === "string") throw new Error("Could not start local login callback");
|
|
264
|
+
const callback = `http://127.0.0.1:${address.port}/callback`;
|
|
265
|
+
const portal = new URL(process.env.BACKIFY_PORTAL_URL ?? userConfig.portalUrl ?? "https://backify.dev");
|
|
266
|
+
portal.searchParams.set("cli_callback", callback);
|
|
267
|
+
portal.searchParams.set("cli_state", state);
|
|
268
|
+
console.log(`Opening ${portal.origin} to sign in to Backify\u2026`);
|
|
269
|
+
openBrowser(portal.toString());
|
|
270
|
+
});
|
|
271
|
+
const timeout = setTimeout(() => {
|
|
272
|
+
server.close();
|
|
273
|
+
rejectLogin(new Error("Backify login timed out. Run backify login to try again."));
|
|
274
|
+
}, 5 * 6e4);
|
|
275
|
+
let rejectLogin = () => {
|
|
276
|
+
};
|
|
277
|
+
const failed = new Promise((_, reject) => rejectLogin = reject);
|
|
278
|
+
try {
|
|
279
|
+
await Promise.race([completed, failed]);
|
|
280
|
+
} finally {
|
|
281
|
+
clearTimeout(timeout);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
163
284
|
async function main() {
|
|
164
285
|
switch (command) {
|
|
165
286
|
case "init":
|
|
@@ -167,12 +288,7 @@ async function main() {
|
|
|
167
288
|
console.log("Backify initialized. SDK, configuration, and agent instructions are ready.");
|
|
168
289
|
break;
|
|
169
290
|
case "sync": {
|
|
170
|
-
|
|
171
|
-
if (!existsSync2(path)) throw new Error("backify.json is missing; run backify init --app app_xxxxx");
|
|
172
|
-
const value = JSON.parse(readFileSync2(path, "utf8"));
|
|
173
|
-
value.instructionVersion = INSTRUCTION_VERSION;
|
|
174
|
-
writeFileSync2(path, JSON.stringify(value, null, 2) + "\n");
|
|
175
|
-
updateInstructions();
|
|
291
|
+
synchronize();
|
|
176
292
|
console.log(`Backify instructions synchronized to version ${INSTRUCTION_VERSION}.`);
|
|
177
293
|
break;
|
|
178
294
|
}
|
|
@@ -186,10 +302,16 @@ async function main() {
|
|
|
186
302
|
break;
|
|
187
303
|
}
|
|
188
304
|
case "login":
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
305
|
+
if (args[0]) {
|
|
306
|
+
userConfig.token = args[0];
|
|
307
|
+
userConfig.organizationId = args[1] ?? userConfig.organizationId;
|
|
308
|
+
writeFileSync2(configPath, JSON.stringify(userConfig, null, 2));
|
|
309
|
+
console.log("Logged in to Backify");
|
|
310
|
+
} else {
|
|
311
|
+
await browserLogin();
|
|
312
|
+
console.log("Logged in to Backify");
|
|
313
|
+
}
|
|
314
|
+
;
|
|
193
315
|
break;
|
|
194
316
|
case "logout":
|
|
195
317
|
rmSync(configPath, { force: true });
|
package/package.json
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@backifydev/cli",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Command-line setup and diagnostics for Backify applications",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": { "backify": "
|
|
7
|
-
"files": ["dist/index.js", "README.md"],
|
|
8
|
-
"scripts": {
|
|
9
|
-
"prebuild": "npm run build --workspace @backify/platform",
|
|
10
|
-
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js",
|
|
11
|
-
"prepack": "npm run build",
|
|
12
|
-
"pretypecheck": "npm run build --workspace @backify/platform",
|
|
13
|
-
"typecheck": "tsc --noEmit",
|
|
14
|
-
"pretest": "npm run build --workspace @backify/platform",
|
|
15
|
-
"test": "vitest run"
|
|
16
|
-
},
|
|
17
|
-
"publishConfig": { "access": "public" },
|
|
1
|
+
{
|
|
2
|
+
"name": "@backifydev/cli",
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "Command-line setup and diagnostics for Backify applications",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": { "backify": "dist/index.js" },
|
|
7
|
+
"files": ["dist/index.js", "README.md"],
|
|
8
|
+
"scripts": {
|
|
9
|
+
"prebuild": "npm run build --workspace @backify/platform",
|
|
10
|
+
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js",
|
|
11
|
+
"prepack": "npm run build",
|
|
12
|
+
"pretypecheck": "npm run build --workspace @backify/platform",
|
|
13
|
+
"typecheck": "tsc --noEmit",
|
|
14
|
+
"pretest": "npm run build --workspace @backify/platform",
|
|
15
|
+
"test": "vitest run"
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": { "access": "public" },
|
|
18
18
|
"repository": { "type": "git", "url": "git+https://github.com/frejnorling/backify.git", "directory": "packages/cli" },
|
|
19
|
-
"homepage": "https://
|
|
20
|
-
"license": "MIT",
|
|
21
|
-
"engines": { "node": ">=20" },
|
|
22
|
-
"devDependencies": {
|
|
23
|
-
"@backify/platform": "*",
|
|
24
|
-
"@types/node": "^24.3.1",
|
|
25
|
-
"esbuild": "^0.25.10",
|
|
26
|
-
"typescript": "^5.9.2",
|
|
27
|
-
"vitest": "^
|
|
28
|
-
}
|
|
29
|
-
}
|
|
19
|
+
"homepage": "https://agent.backify.dev/",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"engines": { "node": ">=20" },
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@backify/platform": "*",
|
|
24
|
+
"@types/node": "^24.3.1",
|
|
25
|
+
"esbuild": "^0.25.10",
|
|
26
|
+
"typescript": "^5.9.2",
|
|
27
|
+
"vitest": "^4.1.11"
|
|
28
|
+
}
|
|
29
|
+
}
|