@syngy/account-cli-auth 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/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@syngy/account-cli-auth",
3
+ "version": "0.1.0",
4
+ "description": "Shared account CLI authentication helpers",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "rm -rf dist && tsc",
16
+ "dev": "tsx src/index.ts",
17
+ "test": "vitest run src/__tests__",
18
+ "type-check": "tsc --noEmit",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "dependencies": {
22
+ "commander": "^12.0.0",
23
+ "open": "^11.0.0",
24
+ "zod": "^3.24.1"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^20.19.27",
28
+ "tsx": "^4.21.0",
29
+ "typescript": "^5.9.3",
30
+ "vitest": "^3.0.8"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ }
35
+ }
@@ -0,0 +1,74 @@
1
+ import { mkdtempSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { describe, expect, it, vi } from "vitest";
5
+ import { createAccountAuthCommand, listProfiles } from "../index";
6
+
7
+ describe("account auth command", () => {
8
+ it("manages profile selection in the shared auth command", async () => {
9
+ const homeDir = mkdtempSync(join(tmpdir(), "account-cli-auth-command-"));
10
+ const logs: string[] = [];
11
+ const command = createAccountAuthCommand({
12
+ cliName: "qixi-cli",
13
+ cliVersion: "0.1.0",
14
+ configDirName: "qixi-cli",
15
+ homeDir: () => homeDir,
16
+ resolveRuntime: () => ({
17
+ host: "https://qixi.example.com",
18
+ accountHost: "https://account.example.com",
19
+ accountWebHost: "https://account.example.com",
20
+ scope: "user",
21
+ profile: undefined,
22
+ }),
23
+ output: (value) => logs.push(value),
24
+ });
25
+
26
+ await command.parseAsync(["node", "auth", "profiles", "use", "prod"]);
27
+ await command.parseAsync(["node", "auth", "profiles", "current"]);
28
+
29
+ expect(listProfiles({ homeDir, configDirName: "qixi-cli" })).toEqual([]);
30
+ expect(logs).toEqual(['Current profile set to "prod".', "prod"]);
31
+ });
32
+
33
+ it("runs jcode login from the shared auth command", async () => {
34
+ const homeDir = mkdtempSync(join(tmpdir(), "account-cli-auth-command-login-"));
35
+ const login = vi.fn().mockResolvedValue({
36
+ profile: "prod",
37
+ credentials: {
38
+ token: "access",
39
+ refreshToken: "refresh",
40
+ userId: "user_1",
41
+ host: "https://qixi.example.com",
42
+ accountHost: "https://account.example.com",
43
+ accountWebHost: "https://account.example.com",
44
+ scope: "user",
45
+ },
46
+ });
47
+ const command = createAccountAuthCommand({
48
+ cliName: "qixi-cli",
49
+ cliVersion: "0.1.0",
50
+ configDirName: "qixi-cli",
51
+ homeDir: () => homeDir,
52
+ resolveRuntime: () => ({
53
+ host: "https://qixi.example.com",
54
+ accountHost: "https://account.example.com",
55
+ accountWebHost: "https://account.example.com",
56
+ scope: "user",
57
+ profile: "prod",
58
+ }),
59
+ login,
60
+ output: () => undefined,
61
+ });
62
+
63
+ await command.parseAsync(["node", "auth", "login", "--profile", "prod", "--no-open"]);
64
+
65
+ expect(login).toHaveBeenCalledWith(
66
+ expect.objectContaining({
67
+ cliName: "qixi-cli",
68
+ configDirName: "qixi-cli",
69
+ profile: "prod",
70
+ openBrowser: false,
71
+ }),
72
+ );
73
+ });
74
+ });
@@ -0,0 +1,32 @@
1
+ import { mkdtempSync, readFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { describe, expect, it } from "vitest";
5
+ import { AccountCLICredentialsManager, listProfiles, saveCurrentProfile } from "../index";
6
+
7
+ describe("account CLI credentials", () => {
8
+ it("stores credentials under ~/.config/<cli-name>", () => {
9
+ const homeDir = mkdtempSync(join(tmpdir(), "account-cli-auth-"));
10
+ new AccountCLICredentialsManager({ homeDir, configDirName: "qixi-cli", profile: "prod" }).saveCredentials({
11
+ token: "access",
12
+ refreshToken: "refresh",
13
+ expiresAt: 123,
14
+ userId: "user_1",
15
+ phone: "+8613800000000",
16
+ host: "https://qixi.example.com",
17
+ accountHost: "https://account.example.com",
18
+ accountWebHost: "https://account-web.example.com",
19
+ scope: "user",
20
+ });
21
+
22
+ const saved = JSON.parse(readFileSync(join(homeDir, ".config", "qixi-cli", "profiles", "prod", "credentials.json"), "utf8"));
23
+ expect(saved.token).toBe("access");
24
+ expect(listProfiles({ homeDir, configDirName: "qixi-cli" })).toEqual(["prod"]);
25
+ });
26
+
27
+ it("uses current profile from the same config directory", () => {
28
+ const homeDir = mkdtempSync(join(tmpdir(), "account-cli-auth-"));
29
+ saveCurrentProfile("prod", { homeDir, configDirName: "qixi-cli" });
30
+ expect(new AccountCLICredentialsManager({ homeDir, configDirName: "qixi-cli" }).profile).toBe("prod");
31
+ });
32
+ });
@@ -0,0 +1,96 @@
1
+ import { mkdtempSync, readFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { loginWithAccountJCode } from "../index";
6
+
7
+ const openMock = vi.fn();
8
+
9
+ vi.mock("open", () => ({
10
+ default: openMock,
11
+ }));
12
+
13
+ function pin(data: unknown, init: ResponseInit = {}) {
14
+ return new Response(JSON.stringify({ data }), {
15
+ status: init.status ?? 200,
16
+ headers: { "Content-Type": "application/json" },
17
+ });
18
+ }
19
+
20
+ describe("account jcode login", () => {
21
+ const originalFetch = globalThis.fetch;
22
+
23
+ beforeEach(() => {
24
+ openMock.mockReset();
25
+ });
26
+
27
+ afterEach(() => {
28
+ globalThis.fetch = originalFetch;
29
+ });
30
+
31
+ it("pairs through account API and saves credentials", async () => {
32
+ const homeDir = mkdtempSync(join(tmpdir(), "account-cli-auth-login-"));
33
+ const calls: string[] = [];
34
+ globalThis.fetch = vi.fn(async (input: RequestInfo | URL) => {
35
+ const url = String(input);
36
+ calls.push(url);
37
+ if (url.endsWith("/auth/v1/cli-jcode/sessions")) {
38
+ return pin({
39
+ session_id: "jcs_test_1",
40
+ jcode: "12345678",
41
+ status: "pending",
42
+ login_url: "https://account.example.com/cli-jcode?session_id=jcs_test_1",
43
+ expires_at: "2026-06-26T12:00:00Z",
44
+ });
45
+ }
46
+ if (url.endsWith("/auth/v1/cli-jcode/sessions/jcs_test_1")) {
47
+ return pin({
48
+ session_id: "jcs_test_1",
49
+ status: "confirmed",
50
+ expires_at: "2026-06-26T12:00:00Z",
51
+ });
52
+ }
53
+ if (url.endsWith("/auth/v1/cli-jcode/exchange")) {
54
+ return pin({
55
+ user: { id: "web_user:+8613800000000", phone: "+8613800000000", aud: "web_user" },
56
+ session: {
57
+ access_token: "access-token",
58
+ refresh_token: "refresh-token",
59
+ expires_at: 1782460800,
60
+ token_type: "Bearer",
61
+ },
62
+ });
63
+ }
64
+ throw new Error(`unexpected URL ${url}`);
65
+ }) as typeof fetch;
66
+
67
+ const result = await loginWithAccountJCode({
68
+ cliName: "qixi-cli",
69
+ cliVersion: "0.1.0",
70
+ configDirName: "qixi-cli",
71
+ homeDir,
72
+ profile: "prod",
73
+ host: "https://qixi.example.com",
74
+ accountHost: "https://account.example.com",
75
+ accountWebHost: "https://account.example.com",
76
+ scope: "user",
77
+ openBrowser: false,
78
+ pollIntervalMs: 1,
79
+ maxPollAttempts: 2,
80
+ output: () => undefined,
81
+ });
82
+
83
+ expect(openMock).not.toHaveBeenCalled();
84
+ expect(calls).toEqual([
85
+ "https://account.example.com/auth/v1/cli-jcode/sessions",
86
+ "https://account.example.com/auth/v1/cli-jcode/sessions/jcs_test_1",
87
+ "https://account.example.com/auth/v1/cli-jcode/exchange",
88
+ ]);
89
+ expect(result.profile).toBe("prod");
90
+ const saved = JSON.parse(readFileSync(join(homeDir, ".config", "qixi-cli", "profiles", "prod", "credentials.json"), "utf8"));
91
+ expect(saved.token).toBe("access-token");
92
+ expect(saved.refreshToken).toBe("refresh-token");
93
+ expect(saved.userId).toBe("web_user:+8613800000000");
94
+ expect(saved.phone).toBe("+8613800000000");
95
+ });
96
+ });