@backifydev/cli 0.2.0

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.
Files changed (3) hide show
  1. package/README.md +13 -0
  2. package/dist/index.js +210 -0
  3. package/package.json +29 -0
package/README.md ADDED
@@ -0,0 +1,13 @@
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
+ npx @backifydev/cli doctor
8
+ npx @backifydev/cli sync
9
+ ```
10
+
11
+ `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
+
13
+ See the [agent setup guide](https://docs.backify.dev/setup).
package/dist/index.js ADDED
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, rmSync } from "node:fs";
5
+ import { homedir } from "node:os";
6
+ import { join as join2 } from "node:path";
7
+
8
+ // src/setup.ts
9
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
10
+ import { dirname, join } from "node:path";
11
+ import { spawnSync } from "node:child_process";
12
+
13
+ // ../platform/dist/index.js
14
+ var INSTRUCTION_VERSION = 1;
15
+ var SDK_PACKAGE = "@backifydev/sdk";
16
+ var DOCS_URL = "https://docs.backify.dev";
17
+ var capability = (name, imp, description, stateful = false, provisioner = name) => ({ name, import: imp, version: 1, stateful, provisioner, docs: `${DOCS_URL}/capabilities/${name}`, description, destructiveRemovalRequiresApproval: stateful });
18
+ var CAPABILITIES = [
19
+ capability("auth", "auth", "Application users, organizations, roles, and permissions"),
20
+ capability("database", "db", "Persistent application data", true),
21
+ capability("storage", "storage", "Persistent files and objects", true),
22
+ capability("functions", "functions", "Server function declarations"),
23
+ capability("jobs", "jobs", "Reliable background work", true),
24
+ capability("scheduler", "jobs.schedule", "Scheduled job declarations"),
25
+ capability("events", "events", "Application events and subscriptions"),
26
+ capability("secrets", "secrets", "Server-side secret retrieval", true),
27
+ capability("http", "http", "Governed outbound HTTP"),
28
+ capability("ai", "ai", "Provider-independent generative AI"),
29
+ capability("audit", "audit", "Structured audit records"),
30
+ capability("logs", "logs", "Structured application logging"),
31
+ capability("config", "config", "Non-secret application configuration")
32
+ ];
33
+ var CAPABILITY_REGISTRY = Object.fromEntries(CAPABILITIES.map((x) => [x.name, x]));
34
+ var MANAGED_START = "<!-- BACKIFY:MANAGED:START -->";
35
+ var MANAGED_END = "<!-- BACKIFY:MANAGED:END -->";
36
+ function managedInstructions() {
37
+ return `${MANAGED_START}
38
+
39
+ # Backify Platform Instructions
40
+
41
+ This application runs on Backify.
42
+
43
+ Use \`@backifydev/sdk\` for application platform capabilities. Prefer Backify for authentication, database, storage, server functions, background and scheduled jobs, events, secrets, AI, external HTTP access, audit logging, application logging, and configuration.
44
+
45
+ Do not directly provision or integrate Azure, AWS, GCP, Supabase, Firebase, Auth0, database hosting, object storage, queues, schedulers, or infrastructure-as-code when an equivalent Backify capability exists.
46
+
47
+ The authoritative documentation is ${DOCS_URL}. If unsure, check it before adding an external platform dependency. Preserve all repository instructions outside this managed block.
48
+
49
+ ${MANAGED_END}`;
50
+ }
51
+ function updateManagedInstructions(content) {
52
+ const block = managedInstructions(), start = content.indexOf(MANAGED_START), end = content.indexOf(MANAGED_END);
53
+ if (start >= 0 && end >= start)
54
+ return content.slice(0, start) + block + content.slice(end + MANAGED_END.length);
55
+ return content.trim() ? `${content.trimEnd()}
56
+
57
+ ${block}
58
+ ` : `${block}
59
+ `;
60
+ }
61
+ function createConfig(appId) {
62
+ return { $schema: `${DOCS_URL}/schema/backify.json`, platform: "backify", sdk: SDK_PACKAGE, appId, instructionVersion: INSTRUCTION_VERSION, docs: DOCS_URL, instructions: { preferBackifyCapabilities: true, allowDirectCloudInfrastructure: false } };
63
+ }
64
+ function validateConfig(value) {
65
+ const errors = [];
66
+ if (!value || typeof value !== "object")
67
+ return ["backify.json must contain an object"];
68
+ const x = value;
69
+ if (x.platform !== "backify")
70
+ errors.push("platform must be 'backify'");
71
+ if (x.sdk !== SDK_PACKAGE)
72
+ errors.push(`sdk must be '${SDK_PACKAGE}'`);
73
+ if (typeof x.appId !== "string" || !/^app_[A-Za-z0-9_-]+$/.test(x.appId))
74
+ errors.push("appId must use the app_<id> format");
75
+ if (!Number.isInteger(x.instructionVersion) || Number(x.instructionVersion) < 1)
76
+ errors.push("instructionVersion must be a positive integer");
77
+ if (x.instructions?.preferBackifyCapabilities !== true)
78
+ errors.push("instructions.preferBackifyCapabilities must be true");
79
+ if (x.instructions?.allowDirectCloudInfrastructure !== false)
80
+ errors.push("instructions.allowDirectCloudInfrastructure must be false");
81
+ return errors;
82
+ }
83
+ var BACKIFY_SCHEMA = { $schema: "https://json-schema.org/draft/2020-12/schema", $id: `${DOCS_URL}/schema/backify.json`, type: "object", additionalProperties: false, required: ["$schema", "platform", "sdk", "appId", "instructionVersion", "docs", "instructions"], properties: { $schema: { const: `${DOCS_URL}/schema/backify.json` }, platform: { const: "backify" }, sdk: { const: SDK_PACKAGE }, appId: { type: "string", pattern: "^app_[A-Za-z0-9_-]+$" }, instructionVersion: { type: "integer", minimum: 1 }, docs: { type: "string", format: "uri" }, instructions: { type: "object", additionalProperties: false, required: ["preferBackifyCapabilities", "allowDirectCloudInfrastructure"], properties: { preferBackifyCapabilities: { const: true }, allowDirectCloudInfrastructure: { const: false } } } } };
84
+
85
+ // src/setup.ts
86
+ var instructionFiles = ["AGENTS.md", "CLAUDE.md", join(".github", "copilot-instructions.md")];
87
+ function detectPackageManager(root = process.cwd()) {
88
+ for (const [file, name] of [["pnpm-lock.yaml", "pnpm"], ["yarn.lock", "yarn"], ["bun.lockb", "bun"], ["bun.lock", "bun"], ["package-lock.json", "npm"]]) if (existsSync(join(root, file))) return name;
89
+ return "npm";
90
+ }
91
+ function updateInstructions(root = process.cwd()) {
92
+ for (const relative of instructionFiles) {
93
+ const path = join(root, relative);
94
+ mkdirSync(dirname(path), { recursive: true });
95
+ writeFileSync(path, updateManagedInstructions(existsSync(path) ? readFileSync(path, "utf8") : ""));
96
+ }
97
+ }
98
+ function writeConfig(appId, root = process.cwd()) {
99
+ writeFileSync(join(root, "backify.json"), JSON.stringify(createConfig(appId), null, 2) + "\n");
100
+ }
101
+ function sdkInstalled(root = process.cwd()) {
102
+ try {
103
+ const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
104
+ return Boolean(pkg.dependencies?.[SDK_PACKAGE] || pkg.devDependencies?.[SDK_PACKAGE]);
105
+ } catch {
106
+ return false;
107
+ }
108
+ }
109
+ function installSdk(root = process.cwd()) {
110
+ if (sdkInstalled(root)) return;
111
+ 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
+ if (result.status !== 0) throw new Error(`Could not install ${SDK_PACKAGE} with ${manager}`);
113
+ }
114
+ function initialize(appId, root = process.cwd(), install = true) {
115
+ if (!/^app_[A-Za-z0-9_-]+$/.test(appId)) throw new Error("Provide an application ID using --app app_xxxxx");
116
+ if (!existsSync(join(root, "package.json"))) throw new Error("Run backify init in a JavaScript/TypeScript repository containing package.json");
117
+ if (install) installSdk(root);
118
+ writeConfig(appId, root);
119
+ updateInstructions(root);
120
+ }
121
+ function doctor(root = process.cwd()) {
122
+ const issues = [], warnings = [];
123
+ if (!sdkInstalled(root)) issues.push(`${SDK_PACKAGE} is not installed`);
124
+ let config;
125
+ try {
126
+ config = JSON.parse(readFileSync(join(root, "backify.json"), "utf8"));
127
+ issues.push(...validateConfig(config));
128
+ } catch {
129
+ issues.push("backify.json is missing or invalid JSON");
130
+ }
131
+ if (config?.instructionVersion < INSTRUCTION_VERSION) warnings.push(`Repository instruction version ${config.instructionVersion}; current version ${INSTRUCTION_VERSION}. Run backify sync.`);
132
+ for (const relative of instructionFiles) {
133
+ const path = join(root, relative);
134
+ if (!existsSync(path)) {
135
+ issues.push(`${relative} is missing`);
136
+ continue;
137
+ }
138
+ const text = readFileSync(path, "utf8");
139
+ if (!text.includes(MANAGED_START) || !text.includes(MANAGED_END)) issues.push(`${relative} does not contain Backify managed instructions`);
140
+ }
141
+ let version = "unknown";
142
+ try {
143
+ const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
144
+ version = pkg.dependencies?.[SDK_PACKAGE] ?? pkg.devDependencies?.[SDK_PACKAGE] ?? "missing";
145
+ } catch {
146
+ }
147
+ return { valid: issues.length === 0, appId: config?.appId, sdkVersion: version, instructionVersion: config?.instructionVersion, currentInstructionVersion: INSTRUCTION_VERSION, issues, warnings };
148
+ }
149
+
150
+ // src/index.ts
151
+ var configPath = join2(homedir(), ".backify.json");
152
+ var userConfig = existsSync2(configPath) ? JSON.parse(readFileSync2(configPath, "utf8")) : { apiUrl: process.env.BACKIFY_API_URL ?? "http://localhost:5080/api" };
153
+ var [command, ...args] = process.argv.slice(2);
154
+ async function call(path, init) {
155
+ const response = await fetch(userConfig.apiUrl + path, { ...init, headers: { "content-type": "application/json", "X-Organization-ID": userConfig.organizationId ?? "", ...userConfig.token ? { authorization: `Bearer ${userConfig.token}` } : {} } });
156
+ if (!response.ok) throw new Error(await response.text());
157
+ return response.status === 204 ? {} : response.json();
158
+ }
159
+ var option = (name) => {
160
+ const index = args.indexOf(name);
161
+ return index >= 0 ? args[index + 1] : void 0;
162
+ };
163
+ async function main() {
164
+ switch (command) {
165
+ case "init":
166
+ initialize(option("--app") ?? args.find((x) => !x.startsWith("-")) ?? "");
167
+ console.log("Backify initialized. SDK, configuration, and agent instructions are ready.");
168
+ break;
169
+ case "sync": {
170
+ const path = join2(process.cwd(), "backify.json");
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();
176
+ console.log(`Backify instructions synchronized to version ${INSTRUCTION_VERSION}.`);
177
+ break;
178
+ }
179
+ case "doctor": {
180
+ const result = doctor();
181
+ console.log(`Backify doctor: ${result.valid ? "healthy" : "invalid"}`);
182
+ console.log(`App: ${result.appId ?? "missing"} \xB7 SDK: ${result.sdkVersion} \xB7 Instructions: ${result.instructionVersion ?? "missing"}/${result.currentInstructionVersion}`);
183
+ for (const warning of result.warnings) console.warn(`warning: ${warning}`);
184
+ for (const issue of result.issues) console.error(`error: ${issue}`);
185
+ if (!result.valid) process.exitCode = 1;
186
+ break;
187
+ }
188
+ case "login":
189
+ userConfig.token = args[0] ?? "development";
190
+ userConfig.organizationId = args[1] ?? userConfig.organizationId;
191
+ writeFileSync2(configPath, JSON.stringify(userConfig, null, 2));
192
+ console.log("Logged in to Backify");
193
+ break;
194
+ case "logout":
195
+ rmSync(configPath, { force: true });
196
+ break;
197
+ case "apps":
198
+ console.table(await call("/apps"));
199
+ break;
200
+ case "deploy":
201
+ console.log(await call(`/apps/${args[0]}/deployments/preview`, { method: "POST", body: JSON.stringify({ sourceRevision: args[1] ?? "local" }) }));
202
+ break;
203
+ default:
204
+ console.log("backify init --app app_xxxxx | sync | doctor | login | logout | apps | deploy");
205
+ }
206
+ }
207
+ main().catch((error) => {
208
+ console.error(error instanceof Error ? error.message : error);
209
+ process.exitCode = 1;
210
+ });
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@backifydev/cli",
3
+ "version": "0.2.0",
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
+ "repository": { "type": "git", "url": "git+https://github.com/frejnorling/backify.git", "directory": "packages/cli" },
19
+ "homepage": "https://docs.backify.dev/setup",
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": "^3.2.4"
28
+ }
29
+ }