From dccb09734354ad16dbb22752171b8f3f699a643b Mon Sep 17 00:00:00 2001 From: Guy Ben Aharon Date: Wed, 11 Mar 2026 17:49:16 +0200 Subject: [PATCH] fix: remove standalone --- package.json | 4 ++ src/client.ts | 11 +++-- src/index.ts | 27 ------------ src/types.ts | 3 +- test/client.test.ts | 53 +++++++++++++++++++--- test/index.test.ts | 105 ++++---------------------------------------- 6 files changed, 68 insertions(+), 135 deletions(-) diff --git a/package.json b/package.json index 704eee2..fa420f2 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,10 @@ "require": { "types": "./lib/index.d.ts", "default": "./lib/index.js" + }, + "default": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" } } }, diff --git a/src/client.ts b/src/client.ts index 1978043..62fd16c 100644 --- a/src/client.ts +++ b/src/client.ts @@ -9,15 +9,18 @@ const DEFAULT_TIMEOUT = 5000; export class OneCLI { private containerClient: ContainerClient; - constructor(options: OneCLIOptions) { - if (!options.apiKey) { - throw new OneCLIError("apiKey is required."); + constructor(options: OneCLIOptions = {}) { + const apiKey = options.apiKey ?? process.env.ONECLI_API_KEY; + if (!apiKey) { + throw new OneCLIError( + "apiKey is required. Pass it in options or set the ONECLI_API_KEY environment variable.", + ); } const url = options.url ?? process.env.ONECLI_URL ?? DEFAULT_URL; const timeout = options.timeout ?? DEFAULT_TIMEOUT; - this.containerClient = new ContainerClient(url, options.apiKey, timeout); + this.containerClient = new ContainerClient(url, apiKey, timeout); } /** diff --git a/src/index.ts b/src/index.ts index ebffe51..2ff0376 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,30 +7,3 @@ export type { ContainerConfig, ApplyContainerConfigOptions, } from "./container/types.js"; - -// --------------------------------------------------------------------------- -// Standalone convenience function -// --------------------------------------------------------------------------- - -import { OneCLI } from "./client.js"; - -/** - * Standalone helper: fetch the container config from OneCLI and push the - * corresponding `-e` and `-v` flags onto a Docker `run` argument array. - * - * Returns `true` if OneCLI was reachable and config was applied, - * `false` otherwise (including when `apiKey` is falsy). - * - * @param args Docker `run` argument array to mutate. - * @param apiKey User API key (`oc_...`). Pass `undefined` / empty to skip. - * @param url Base URL of OneCLI. Defaults to `ONECLI_URL` env or `https://app.onecli.sh`. - */ -export async function applyOneCLIConfig( - args: string[], - apiKey?: string | null, - url?: string, -): Promise { - if (!apiKey) return false; - const oc = new OneCLI({ apiKey, url }); - return oc.applyContainerConfig(args); -} diff --git a/src/types.ts b/src/types.ts index 3a4eb5b..1d30443 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,8 +1,9 @@ export interface OneCLIOptions { /** * User API key from the OneCLI dashboard (starts with `oc_`). + * Falls back to `ONECLI_API_KEY` env var if not provided. */ - apiKey: string; + apiKey?: string; /** * Base URL of the OneCLI instance. diff --git a/test/client.test.ts b/test/client.test.ts index 76c0bee..efdfe2c 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -3,31 +3,71 @@ import { OneCLI } from "../src/client.js"; import { OneCLIError } from "../src/errors.js"; describe("OneCLI", () => { - const originalEnv = process.env.ONECLI_URL; + const originalUrl = process.env.ONECLI_URL; + const originalApiKey = process.env.ONECLI_API_KEY; beforeEach(() => { delete process.env.ONECLI_URL; + delete process.env.ONECLI_API_KEY; }); afterEach(() => { - if (originalEnv !== undefined) { - process.env.ONECLI_URL = originalEnv; + if (originalUrl !== undefined) { + process.env.ONECLI_URL = originalUrl; } else { delete process.env.ONECLI_URL; } + if (originalApiKey !== undefined) { + process.env.ONECLI_API_KEY = originalApiKey; + } else { + delete process.env.ONECLI_API_KEY; + } }); describe("constructor", () => { - it("throws OneCLIError when apiKey is empty string", () => { + it("throws OneCLIError when no apiKey is provided and env var is not set", () => { + expect(() => new OneCLI()).toThrow(OneCLIError); + expect(() => new OneCLI()).toThrow("apiKey is required"); + }); + + it("throws OneCLIError when apiKey is empty string and env var is not set", () => { expect(() => new OneCLI({ apiKey: "" })).toThrow(OneCLIError); - expect(() => new OneCLI({ apiKey: "" })).toThrow("apiKey is required."); }); - it("accepts a valid apiKey", () => { + it("accepts apiKey from options", () => { const oc = new OneCLI({ apiKey: "oc_test123" }); expect(oc).toBeInstanceOf(OneCLI); }); + it("falls back to ONECLI_API_KEY env var", () => { + process.env.ONECLI_API_KEY = "oc_from_env"; + const oc = new OneCLI(); + expect(oc).toBeInstanceOf(OneCLI); + }); + + it("prefers options.apiKey over ONECLI_API_KEY env var", () => { + process.env.ONECLI_API_KEY = "oc_from_env"; + + const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue( + new Response(JSON.stringify({ env: {}, caCertificate: "", caCertificateContainerPath: "" })), + ); + + const oc = new OneCLI({ + apiKey: "oc_from_options", + url: "http://localhost:3000", + }); + oc.getContainerConfig(); + + expect(fetchSpy).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: { Authorization: "Bearer oc_from_options" }, + }), + ); + + fetchSpy.mockRestore(); + }); + it("uses url from options when provided", () => { const oc = new OneCLI({ apiKey: "oc_test", @@ -45,7 +85,6 @@ describe("OneCLI", () => { it("prefers options.url over ONECLI_URL env var", () => { process.env.ONECLI_URL = "http://env-url:3000"; - // We can verify by checking that fetch is called with the right URL const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue( new Response(JSON.stringify({ env: {}, caCertificate: "", caCertificateContainerPath: "" })), ); diff --git a/test/index.test.ts b/test/index.test.ts index 43cf9ee..5970cc1 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,103 +1,16 @@ -import { describe, it, expect, vi, afterEach } from "vitest"; -import { applyOneCLIConfig } from "../src/index.js"; +import { describe, it, expect } from "vitest"; +import { OneCLI, OneCLIError, ContainerClient } from "../src/index.js"; -const MOCK_CONFIG = { - env: { - HTTPS_PROXY: "http://x:aoc_token@proxy:18080", - HTTP_PROXY: "http://x:aoc_token@proxy:18080", - NODE_EXTRA_CA_CERTS: "/tmp/onecli-proxy-ca.pem", - NODE_USE_ENV_PROXY: "1", - }, - caCertificate: - "-----BEGIN CERTIFICATE-----\nTEST_CA\n-----END CERTIFICATE-----", - caCertificateContainerPath: "/tmp/onecli-proxy-ca.pem", -}; - -describe("applyOneCLIConfig", () => { - let fetchSpy: ReturnType; - - afterEach(() => { - fetchSpy?.mockRestore(); - }); - - it("returns false when apiKey is null", async () => { - const args: string[] = []; - const result = await applyOneCLIConfig(args, null); - expect(result).toBe(false); - expect(args).toEqual([]); - }); - - it("returns false when apiKey is undefined", async () => { - const args: string[] = []; - const result = await applyOneCLIConfig(args, undefined); - expect(result).toBe(false); - expect(args).toEqual([]); - }); - - it("returns false when apiKey is empty string", async () => { - const args: string[] = []; - const result = await applyOneCLIConfig(args, ""); - expect(result).toBe(false); - expect(args).toEqual([]); - }); - - it("returns true and mutates args on success", async () => { - fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue( - new Response(JSON.stringify(MOCK_CONFIG)), - ); - - const args = ["run", "-i", "--rm"]; - const result = await applyOneCLIConfig( - args, - "oc_test", - "http://localhost:3000", - ); - - expect(result).toBe(true); - expect(args.length).toBeGreaterThan(3); - expect(args).toContain("-e"); - }); - - it("returns false when server is unreachable", async () => { - fetchSpy = vi - .spyOn(globalThis, "fetch") - .mockRejectedValue(new Error("ECONNREFUSED")); - - const args = ["run", "-i", "--rm"]; - const result = await applyOneCLIConfig( - args, - "oc_test", - "http://localhost:3000", - ); - - expect(result).toBe(false); +describe("package exports", () => { + it("exports OneCLI class", () => { + expect(OneCLI).toBeDefined(); }); - it("passes url to the OneCLI client", async () => { - fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue( - new Response(JSON.stringify(MOCK_CONFIG)), - ); - - await applyOneCLIConfig([], "oc_test", "http://custom:4000"); - - expect(fetchSpy).toHaveBeenCalledWith( - "http://custom:4000/api/container-config", - expect.any(Object), - ); + it("exports OneCLIError", () => { + expect(OneCLIError).toBeDefined(); }); - it("passes apiKey as Bearer token", async () => { - fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue( - new Response(JSON.stringify(MOCK_CONFIG)), - ); - - await applyOneCLIConfig([], "oc_mykey123", "http://localhost:3000"); - - expect(fetchSpy).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ - headers: { Authorization: "Bearer oc_mykey123" }, - }), - ); + it("exports ContainerClient", () => { + expect(ContainerClient).toBeDefined(); }); });