@backifydev/cli 0.2.0 → 0.2.1

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 +12 -7
  2. package/dist/index.js +85 -7
  3. package/package.json +29 -29
package/README.md CHANGED
@@ -1,13 +1,18 @@
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
- See the [agent setup guide](https://docs.backify.dev/setup).
16
+ 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.
17
+
18
+ See the [agent setup guide](https://docs.backify.dev/setup).
package/dist/index.js CHANGED
@@ -4,10 +4,13 @@
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
@@ -111,9 +114,17 @@ function installSdk(root = process.cwd()) {
111
114
  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
115
  if (result.status !== 0) throw new Error(`Could not install ${SDK_PACKAGE} with ${manager}`);
113
116
  }
117
+ function ensurePackageManifest(root) {
118
+ const path = join(root, "package.json");
119
+ if (existsSync(path)) return;
120
+ const setupFiles = /* @__PURE__ */ new Set([".git", ".gitignore", ".github", "AGENTS.md", "CLAUDE.md", "README.md", "LICENSE", "backify.json"]);
121
+ 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://docs.backify.dev/setup");
122
+ const name = basename(resolve(root)).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") || "backify-app";
123
+ writeFileSync(path, JSON.stringify({ name, version: "0.0.0", private: true, type: "module" }, null, 2) + "\n", { flag: "wx" });
124
+ }
114
125
  function initialize(appId, root = process.cwd(), install = true) {
115
126
  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");
127
+ ensurePackageManifest(root);
117
128
  if (install) installSdk(root);
118
129
  writeConfig(appId, root);
119
130
  updateInstructions(root);
@@ -160,6 +171,67 @@ var option = (name) => {
160
171
  const index = args.indexOf(name);
161
172
  return index >= 0 ? args[index + 1] : void 0;
162
173
  };
174
+ function openBrowser(url) {
175
+ const command2 = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open", args2 = process.platform === "win32" ? ["/c", "start", "", '"' + url + '"'] : [url];
176
+ const child = spawn(command2, args2, { stdio: "ignore", detached: true, shell: process.platform === "win32" });
177
+ child.unref();
178
+ }
179
+ async function browserLogin() {
180
+ const state = randomBytes(24).toString("base64url"), server = createServer((req, res) => {
181
+ if (req.method === "OPTIONS") {
182
+ res.writeHead(204, { "access-control-allow-origin": "*", "access-control-allow-methods": "POST, OPTIONS", "access-control-allow-headers": "content-type" });
183
+ return res.end();
184
+ }
185
+ if (req.method !== "POST" || req.url !== "/callback") {
186
+ res.writeHead(404);
187
+ return res.end();
188
+ }
189
+ let raw = "";
190
+ req.setEncoding("utf8");
191
+ req.on("data", (part) => raw += part);
192
+ req.on("end", () => {
193
+ try {
194
+ const value = JSON.parse(raw);
195
+ if (value.state !== state || typeof value.token !== "string" || typeof value.organizationId !== "string") throw new Error("Invalid callback");
196
+ res.writeHead(200, { "access-control-allow-origin": "*", "content-type": "text/plain" });
197
+ res.end("Backify CLI connected. You can close this tab.");
198
+ userConfig.token = value.token;
199
+ userConfig.organizationId = value.organizationId;
200
+ writeFileSync2(configPath, JSON.stringify(userConfig, null, 2));
201
+ server.close();
202
+ resolveLogin();
203
+ } catch {
204
+ res.writeHead(400, { "access-control-allow-origin": "*" });
205
+ res.end("Invalid or expired Backify CLI login.");
206
+ }
207
+ });
208
+ });
209
+ let resolveLogin = () => {
210
+ };
211
+ const completed = new Promise((resolve2) => resolveLogin = resolve2);
212
+ server.listen(0, "127.0.0.1", () => {
213
+ const address = server.address();
214
+ if (!address || typeof address === "string") throw new Error("Could not start local login callback");
215
+ const callback = `http://127.0.0.1:${address.port}/callback`;
216
+ const portal = new URL(process.env.BACKIFY_PORTAL_URL ?? userConfig.portalUrl ?? "https://backify.dev");
217
+ portal.searchParams.set("cli_callback", callback);
218
+ portal.searchParams.set("cli_state", state);
219
+ console.log(`Opening ${portal.origin} to sign in to Backify\u2026`);
220
+ openBrowser(portal.toString());
221
+ });
222
+ const timeout = setTimeout(() => {
223
+ server.close();
224
+ rejectLogin(new Error("Backify login timed out. Run backify login to try again."));
225
+ }, 5 * 6e4);
226
+ let rejectLogin = () => {
227
+ };
228
+ const failed = new Promise((_, reject) => rejectLogin = reject);
229
+ try {
230
+ await Promise.race([completed, failed]);
231
+ } finally {
232
+ clearTimeout(timeout);
233
+ }
234
+ }
163
235
  async function main() {
164
236
  switch (command) {
165
237
  case "init":
@@ -186,10 +258,16 @@ async function main() {
186
258
  break;
187
259
  }
188
260
  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");
261
+ if (args[0]) {
262
+ userConfig.token = args[0];
263
+ userConfig.organizationId = args[1] ?? userConfig.organizationId;
264
+ writeFileSync2(configPath, JSON.stringify(userConfig, null, 2));
265
+ console.log("Logged in to Backify");
266
+ } else {
267
+ await browserLogin();
268
+ console.log("Logged in to Backify");
269
+ }
270
+ ;
193
271
  break;
194
272
  case "logout":
195
273
  rmSync(configPath, { force: true });
package/package.json CHANGED
@@ -1,29 +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
- }
1
+ {
2
+ "name": "@backifydev/cli",
3
+ "version": "0.2.1",
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": "^4.1.11"
28
+ }
29
+ }