@cyberxon/xon 0.1.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.
package/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # xoncli
2
+
3
+ A small CLI for creating Xon projects and managing deployment requests.
4
+
5
+ ## Install
6
+
7
+ The package is published to GitHub Packages (private to this repo, not the public npm
8
+ registry) as `@balakrishnagujju/xon`. One-time setup per machine — add a GitHub
9
+ personal access token with the `read:packages` scope to your npm config:
10
+
11
+ ```sh
12
+ echo "@balakrishnagujju:registry=https://npm.pkg.github.com" >> ~/.npmrc
13
+ echo "//npm.pkg.github.com/:_authToken=<your GitHub token>" >> ~/.npmrc
14
+ ```
15
+
16
+ Then install globally, same as any other CLI:
17
+
18
+ ```sh
19
+ npm install -g @balakrishnagujju/xon
20
+ ```
21
+
22
+ The executable installed is `xon`.
23
+
24
+ ### Alternative: clone + link
25
+
26
+ If you don't want to set up a GitHub token, you can still run it from a checkout:
27
+
28
+ ```sh
29
+ git clone https://github.com/balakrishnagujju/xoncli.git
30
+ cd xoncli
31
+ npm install
32
+ npm link
33
+ ```
34
+
35
+ > `npm install -g git+https://...` looks tempting but is unreliable on current npm —
36
+ > it can link the package to npm's internal cache tmp folder, which gets cleaned up
37
+ > right after install and leaves `xon` broken. Use the GitHub Packages install above,
38
+ > or clone + `npm link` — not the git URL form.
39
+
40
+ **Contributing:** run `npm run build` and commit the resulting `dist/` changes
41
+ alongside any `src/` edits — the repo has no CI to build it for you.
42
+
43
+ **Publishing a new version:** bump `version` in `package.json`, then run
44
+ `npm publish` from a machine with a GitHub token that has the `write:packages` scope
45
+ configured the same way as above (with `write:packages` instead of `read:packages`).
46
+
47
+ ## Commands
48
+
49
+ Create a project:
50
+
51
+ ```sh
52
+ xon project create my-app
53
+ xon project create my-app --directory ./workspace
54
+ ```
55
+
56
+ Configure the API with `XON_BASE_URL` and optionally `XON_TOKEN`:
57
+
58
+ ```sh
59
+ export XON_BASE_URL=https://api.example.com
60
+ export XON_TOKEN=your-token
61
+ ```
62
+
63
+ Create a deployment request. The file is sent unchanged as the JSON request body:
64
+
65
+ ```sh
66
+ xon deploy create --file deploy-request.json
67
+ ```
68
+
69
+ Or provide connection options per command:
70
+
71
+ ```sh
72
+ xon deploy create --base-url https://api.example.com --token your-token --file deploy-request.json
73
+ ```
74
+
75
+ Fetch a deployment request:
76
+
77
+ ```sh
78
+ xon deploy get 123
79
+ ```
80
+
81
+ Both API commands print the JSON response. Non-2xx responses are reported with their HTTP status and API message when available.
@@ -0,0 +1,46 @@
1
+ export class ApiError extends Error {
2
+ status;
3
+ details;
4
+ constructor(message, status, details) {
5
+ super(message);
6
+ this.status = status;
7
+ this.details = details;
8
+ this.name = "ApiError";
9
+ }
10
+ }
11
+ export class ApiClient {
12
+ baseUrl;
13
+ token;
14
+ constructor(options) {
15
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
16
+ this.token = options.token;
17
+ }
18
+ async post(path, body) {
19
+ return this.request("POST", path, body);
20
+ }
21
+ async get(path) {
22
+ return this.request("GET", path);
23
+ }
24
+ async request(method, path, body) {
25
+ const response = await fetch(`${this.baseUrl}${path}`, {
26
+ method,
27
+ headers: {
28
+ Accept: "application/json",
29
+ ...(body === undefined ? {} : { "Content-Type": "application/json" }),
30
+ ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}),
31
+ },
32
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }),
33
+ });
34
+ const contentType = response.headers.get("content-type") ?? "";
35
+ const payload = contentType.includes("application/json")
36
+ ? await response.json()
37
+ : await response.text();
38
+ if (!response.ok) {
39
+ const message = typeof payload === "object" && payload !== null && "message" in payload
40
+ ? String(payload.message)
41
+ : `Request failed with status ${response.status}`;
42
+ throw new ApiError(message, response.status, payload);
43
+ }
44
+ return payload;
45
+ }
46
+ }
package/dist/cli.js ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ import { Command, CommanderError } from "commander";
3
+ import { ApiClient, ApiError } from "./api-client.js";
4
+ import { createProject } from "./project.js";
5
+ import { updateCli } from "./update.js";
6
+ const program = new Command();
7
+ function output(value) {
8
+ console.log(typeof value === "string" ? value : JSON.stringify(value, null, 2));
9
+ }
10
+ function apiClient(options) {
11
+ const baseUrl = options.baseUrl ?? process.env.XON_BASE_URL;
12
+ if (!baseUrl) {
13
+ throw new Error("An API base URL is required. Set XON_BASE_URL or pass --base-url.");
14
+ }
15
+ return new ApiClient({ baseUrl, token: options.token ?? process.env.XON_TOKEN });
16
+ }
17
+ program
18
+ .name("xon")
19
+ .description("Manage Xon projects and deployments")
20
+ .version("0.1.0")
21
+ .showSuggestionAfterError();
22
+ const project = program.command("project").description("Manage Xon projects");
23
+ project
24
+ .command("create <name>")
25
+ .description("Create a new Xon project")
26
+ .option("-d, --directory <path>", "Directory to create the project in", ".")
27
+ .action(async (name, options) => {
28
+ const root = await createProject(options.directory, name);
29
+ console.log(`Created project ${name} in ${root}`);
30
+ });
31
+ program
32
+ .command("update")
33
+ .description("Update xon to the latest version")
34
+ .action(async () => {
35
+ await updateCli();
36
+ });
37
+ const deploy = program.command("deploy").description("Manage deployment requests");
38
+ deploy
39
+ .command("create")
40
+ .description("Create a deployment request")
41
+ .requiredOption("-f, --file <path>", "Path to the deployment JSON payload")
42
+ .option("-b, --base-url <url>", "API base URL")
43
+ .option("-t, --token <token>", "Bearer token")
44
+ .action(async (options) => {
45
+ const payload = JSON.parse(await (await import("node:fs/promises")).readFile(options.file, "utf8"));
46
+ output(await apiClient(options).post("/deployRequests", payload));
47
+ });
48
+ deploy
49
+ .command("get <id>")
50
+ .description("Get a deployment request")
51
+ .option("-b, --base-url <url>", "API base URL")
52
+ .option("-t, --token <token>", "Bearer token")
53
+ .action(async (id, options) => {
54
+ output(await apiClient(options).get(`/deployRequests/${encodeURIComponent(id)}`));
55
+ });
56
+ program.parseAsync().catch((error) => {
57
+ if (error instanceof CommanderError) {
58
+ process.exitCode = error.exitCode;
59
+ return;
60
+ }
61
+ const message = error instanceof ApiError
62
+ ? `${error.message} (HTTP ${error.status})`
63
+ : error instanceof Error ? error.message : String(error);
64
+ console.error(`Error: ${message}`);
65
+ process.exitCode = 1;
66
+ });
@@ -0,0 +1,19 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ const projectFile = ".xon/project.json";
4
+ export async function createProject(directory, name) {
5
+ const root = path.resolve(directory, name);
6
+ const config = {
7
+ name,
8
+ apiVersion: "v1",
9
+ createdAt: new Date().toISOString(),
10
+ };
11
+ await mkdir(path.join(root, ".xon"), { recursive: true });
12
+ await mkdir(path.join(root, "src"), { recursive: true });
13
+ await writeFile(path.join(root, projectFile), `${JSON.stringify(config, null, 2)}\n`, { flag: "wx" });
14
+ return root;
15
+ }
16
+ export async function readProject(directory = ".") {
17
+ const contents = await readFile(path.resolve(directory, projectFile), "utf8");
18
+ return JSON.parse(contents);
19
+ }
package/dist/update.js ADDED
@@ -0,0 +1,54 @@
1
+ import { spawn } from "node:child_process";
2
+ import { fileURLToPath } from "node:url";
3
+ import path from "node:path";
4
+ function packageRoot() {
5
+ const dist = path.dirname(fileURLToPath(import.meta.url));
6
+ return path.dirname(dist);
7
+ }
8
+ function run(command, args, cwd) {
9
+ return new Promise((resolve, reject) => {
10
+ const child = spawn(command, args, { cwd, stdio: "inherit" });
11
+ child.on("error", reject);
12
+ child.on("exit", (code) => {
13
+ if (code === 0)
14
+ resolve();
15
+ else
16
+ reject(new Error(`${command} ${args.join(" ")} exited with code ${code}`));
17
+ });
18
+ });
19
+ }
20
+ function captureOutput(command, args, cwd) {
21
+ return new Promise((resolve, reject) => {
22
+ const child = spawn(command, args, { cwd, stdio: ["ignore", "pipe", "inherit"] });
23
+ let output = "";
24
+ child.stdout.on("data", (chunk) => {
25
+ output += chunk.toString();
26
+ });
27
+ child.on("error", reject);
28
+ child.on("exit", (code) => {
29
+ if (code === 0)
30
+ resolve(output);
31
+ else
32
+ reject(new Error(`${command} ${args.join(" ")} exited with code ${code}`));
33
+ });
34
+ });
35
+ }
36
+ export async function updateCli() {
37
+ const root = packageRoot();
38
+ let status;
39
+ try {
40
+ status = await captureOutput("git", ["status", "--porcelain"], root);
41
+ }
42
+ catch {
43
+ throw new Error(`xon isn't installed from a git checkout at ${root}, so it can't self-update. ` +
44
+ "Reinstall following the README instructions.");
45
+ }
46
+ if (status.trim().length > 0) {
47
+ throw new Error(`xon has local changes in ${root}. Commit or stash them before updating.`);
48
+ }
49
+ console.log(`Updating xon in ${root}...`);
50
+ await run("git", ["pull", "--ff-only"], root);
51
+ await run("npm", ["install"], root);
52
+ await run("npm", ["run", "build"], root);
53
+ console.log("xon is up to date.");
54
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@cyberxon/xon",
3
+ "version": "0.1.0",
4
+ "description": "A CLI for Xon projects and deployments",
5
+ "type": "module",
6
+ "license": "UNLICENSED",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/balakrishnagujju/xoncli.git"
10
+ },
11
+ "publishConfig": {
12
+ "registry": "https://registry.npmjs.org",
13
+ "access": "public"
14
+ },
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "bin": {
22
+ "xon": "dist/cli.js"
23
+ },
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "prepublishOnly": "npm run build",
27
+ "dev": "tsx src/cli.ts",
28
+ "start": "node dist/cli.js",
29
+ "test": "node --test --import tsx test/**/*.test.ts"
30
+ },
31
+ "dependencies": {
32
+ "commander": "^14.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^24.0.0",
36
+ "tsx": "^4.20.0",
37
+ "typescript": "^5.9.0"
38
+ }
39
+ }