@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 +81 -0
- package/dist/api-client.js +46 -0
- package/dist/cli.js +66 -0
- package/dist/project.js +19 -0
- package/dist/update.js +54 -0
- package/package.json +39 -0
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
|
+
});
|
package/dist/project.js
ADDED
|
@@ -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
|
+
}
|