jitsu-cli 2.14.0-beta.77 → 2.14.0-beta.85

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.
@@ -0,0 +1,52 @@
1
+ export class ApiError extends Error {
2
+ constructor(status, message, payload) {
3
+ super(message);
4
+ this.status = status;
5
+ this.payload = payload;
6
+ }
7
+ }
8
+ export class ApiClient {
9
+ constructor(auth) {
10
+ this.auth = auth;
11
+ }
12
+ url(path, query) {
13
+ const base = this.auth.host;
14
+ const p = path.startsWith("/") ? path : `/${path}`;
15
+ if (!query)
16
+ return `${base}${p}`;
17
+ const qs = Object.entries(query)
18
+ .filter(([, v]) => v !== undefined && v !== null && v !== "")
19
+ .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)
20
+ .join("&");
21
+ return qs ? `${base}${p}?${qs}` : `${base}${p}`;
22
+ }
23
+ async request(req) {
24
+ const method = req.method ?? "GET";
25
+ const headers = {
26
+ Accept: "application/json",
27
+ Authorization: `Bearer ${this.auth.apikey}`,
28
+ };
29
+ let body;
30
+ if (req.body !== undefined) {
31
+ headers["Content-Type"] = "application/json";
32
+ body = JSON.stringify(req.body);
33
+ }
34
+ const res = await fetch(this.url(req.path, req.query), { method, headers, body });
35
+ const text = await res.text();
36
+ let parsed = text;
37
+ if (text && res.headers.get("content-type")?.includes("application/json")) {
38
+ try {
39
+ parsed = JSON.parse(text);
40
+ }
41
+ catch {
42
+ }
43
+ }
44
+ if (!res.ok) {
45
+ const msg = (parsed && typeof parsed === "object" && "message" in parsed
46
+ ? String(parsed.message)
47
+ : undefined) ?? `HTTP ${res.status} ${method} ${req.path}`;
48
+ throw new ApiError(res.status, msg, parsed);
49
+ }
50
+ return parsed;
51
+ }
52
+ }
@@ -0,0 +1,67 @@
1
+ import * as fs from "fs";
2
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
3
+ import { homedir } from "os";
4
+ const DEFAULT_HOST = "https://use.jitsu.com";
5
+ export function authFilePath() {
6
+ return `${homedir()}/.jitsu/jitsu-cli.json`;
7
+ }
8
+ export function readAuthFile() {
9
+ const path = authFilePath();
10
+ if (!fs.existsSync(path))
11
+ return undefined;
12
+ return JSON.parse(readFileSync(path, { encoding: "utf-8" }));
13
+ }
14
+ export function updateAuthFile(patch) {
15
+ const path = authFilePath();
16
+ const existing = readAuthFile() ?? {};
17
+ const next = { ...existing };
18
+ for (const [k, v] of Object.entries(patch)) {
19
+ if (v === undefined)
20
+ delete next[k];
21
+ else
22
+ next[k] = v;
23
+ }
24
+ mkdirSync(`${homedir()}/.jitsu`, { recursive: true });
25
+ writeFileSync(path, JSON.stringify(next, null, 2));
26
+ return next;
27
+ }
28
+ export function readDefaultWorkspace() {
29
+ return readAuthFile()?.defaultWorkspace;
30
+ }
31
+ export function resolveAuth(opts) {
32
+ let host = opts.host;
33
+ let apikey = opts.apikey;
34
+ if (!host || !apikey) {
35
+ const file = readAuthFile();
36
+ if (file) {
37
+ if (!host)
38
+ host = file.host;
39
+ if (!apikey)
40
+ apikey = file.apikey;
41
+ }
42
+ }
43
+ if (!host)
44
+ host = process.env.JITSU_HOST;
45
+ if (!apikey)
46
+ apikey = process.env.JITSU_APIKEY;
47
+ if (!host)
48
+ host = DEFAULT_HOST;
49
+ if (!apikey) {
50
+ throw new Error("Not authenticated. Run `jitsu login`, set JITSU_APIKEY, or pass --apikey <key>.");
51
+ }
52
+ return { host: normalizeHost(host), apikey };
53
+ }
54
+ export function normalizeHost(host) {
55
+ let url = host;
56
+ if (!url.startsWith("http")) {
57
+ if (url.startsWith("localhost") || /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/.test(url)) {
58
+ url = "http://" + url;
59
+ }
60
+ else {
61
+ url = "https://" + url;
62
+ }
63
+ }
64
+ if (url.endsWith("/"))
65
+ url = url.slice(0, -1);
66
+ return url;
67
+ }
@@ -0,0 +1,53 @@
1
+ import * as fs from "fs";
2
+ import yaml from "js-yaml";
3
+ import { parseScalar, setDottedPath } from "./dotted";
4
+ export function buildBody(sources) {
5
+ const layers = [];
6
+ if (sources.file)
7
+ layers.push(loadFile(sources.file));
8
+ if (sources.json)
9
+ layers.push(parseInlineJson(sources.json));
10
+ if (sources.fields && Object.keys(sources.fields).length > 0) {
11
+ const obj = {};
12
+ for (const [path, raw] of Object.entries(sources.fields)) {
13
+ setDottedPath(obj, path, parseScalar(raw));
14
+ }
15
+ layers.push(obj);
16
+ }
17
+ if (layers.length === 0)
18
+ return undefined;
19
+ return layers.reduce((acc, layer) => deepMerge(acc, layer), {});
20
+ }
21
+ function loadFile(path) {
22
+ const text = path === "-" ? fs.readFileSync(0, "utf-8") : fs.readFileSync(path, "utf-8");
23
+ const parsed = yaml.load(text);
24
+ if (parsed === null || parsed === undefined) {
25
+ throw new Error(`File ${path} parsed as empty`);
26
+ }
27
+ return parsed;
28
+ }
29
+ function parseInlineJson(s) {
30
+ try {
31
+ return JSON.parse(s);
32
+ }
33
+ catch (e) {
34
+ throw new Error(`--json value is not valid JSON: ${e.message}`);
35
+ }
36
+ }
37
+ function isPlainObject(v) {
38
+ return typeof v === "object" && v !== null && !Array.isArray(v);
39
+ }
40
+ function deepMerge(target, source) {
41
+ if (!isPlainObject(target) || !isPlainObject(source))
42
+ return source;
43
+ const out = { ...target };
44
+ for (const [k, v] of Object.entries(source)) {
45
+ if (isPlainObject(v) && isPlainObject(out[k])) {
46
+ out[k] = deepMerge(out[k], v);
47
+ }
48
+ else {
49
+ out[k] = v;
50
+ }
51
+ }
52
+ return out;
53
+ }
@@ -0,0 +1,47 @@
1
+ const RESERVED = new Set([
2
+ "workspace",
3
+ "output",
4
+ "host",
5
+ "apikey",
6
+ "file",
7
+ "json",
8
+ "cascade",
9
+ "strict",
10
+ "from",
11
+ "to",
12
+ "help",
13
+ "version",
14
+ ]);
15
+ const fields = {};
16
+ function shouldExtract(argv) {
17
+ for (let i = 2; i < argv.length; i++) {
18
+ const a = argv[i];
19
+ if (a.startsWith("-"))
20
+ continue;
21
+ return a === "config";
22
+ }
23
+ return false;
24
+ }
25
+ export function preprocessArgv(argv) {
26
+ if (!shouldExtract(argv))
27
+ return argv;
28
+ const out = [];
29
+ for (const arg of argv) {
30
+ const m = /^--([a-zA-Z][\w.-]*)=([\s\S]*)$/.exec(arg);
31
+ if (m) {
32
+ const head = m[1].split(".")[0];
33
+ if (!RESERVED.has(head)) {
34
+ fields[m[1]] = m[2];
35
+ continue;
36
+ }
37
+ }
38
+ out.push(arg);
39
+ }
40
+ return out;
41
+ }
42
+ export function consumeBodyFields() {
43
+ const copy = { ...fields };
44
+ for (const k of Object.keys(fields))
45
+ delete fields[k];
46
+ return copy;
47
+ }
@@ -0,0 +1,34 @@
1
+ export function parseScalar(raw) {
2
+ if (raw === "")
3
+ return "";
4
+ const trimmed = raw.trim();
5
+ const first = trimmed[0];
6
+ const looksLikeJson = first === "{" ||
7
+ first === "[" ||
8
+ first === '"' ||
9
+ trimmed === "true" ||
10
+ trimmed === "false" ||
11
+ trimmed === "null" ||
12
+ /^-?\d/.test(trimmed);
13
+ if (looksLikeJson) {
14
+ try {
15
+ return JSON.parse(trimmed);
16
+ }
17
+ catch {
18
+ }
19
+ }
20
+ return raw;
21
+ }
22
+ export function setDottedPath(target, path, value) {
23
+ const parts = path.split(".");
24
+ let node = target;
25
+ for (let i = 0; i < parts.length - 1; i++) {
26
+ const key = parts[i];
27
+ if (node[key] == null || typeof node[key] !== "object" || Array.isArray(node[key])) {
28
+ node[key] = {};
29
+ }
30
+ node = node[key];
31
+ }
32
+ node[parts[parts.length - 1]] = value;
33
+ return target;
34
+ }
@@ -0,0 +1,33 @@
1
+ import yaml from "js-yaml";
2
+ const renderers = {
3
+ yaml: {
4
+ format: "yaml",
5
+ render(value) {
6
+ if (value === undefined)
7
+ return "";
8
+ return yaml.dump(value, { skipInvalid: true, noRefs: true, sortKeys: false, lineWidth: 120 });
9
+ },
10
+ },
11
+ json: {
12
+ format: "json",
13
+ render(value) {
14
+ return JSON.stringify(value, null, 2) + "\n";
15
+ },
16
+ },
17
+ };
18
+ export const SUPPORTED_OUTPUTS = Object.keys(renderers);
19
+ export const DEFAULT_OUTPUT = "yaml";
20
+ export function getRenderer(format) {
21
+ const key = (format ?? DEFAULT_OUTPUT).toLowerCase();
22
+ const r = renderers[key];
23
+ if (!r) {
24
+ throw new Error(`Unsupported output format '${format}'. Supported: ${SUPPORTED_OUTPUTS.join(", ")}`);
25
+ }
26
+ return r;
27
+ }
28
+ export function registerRenderer(r) {
29
+ renderers[r.format.toLowerCase()] = r;
30
+ }
31
+ export function print(value, format) {
32
+ process.stdout.write(getRenderer(format).render(value));
33
+ }
@@ -0,0 +1,11 @@
1
+ import { ApiClient } from "./api-client";
2
+ export async function fetchSpec(auth) {
3
+ const client = new ApiClient(auth);
4
+ return client.request({ method: "GET", path: "/api/spec" });
5
+ }
6
+ export function findOperation(spec, path, method) {
7
+ const item = spec.paths?.[path];
8
+ if (!item)
9
+ return undefined;
10
+ return item[method.toLowerCase()];
11
+ }