jitsu-cli 1.10.4 → 1.11.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/bin/jitsu +3 -0
- package/build.mts +39 -0
- package/compiled/package.json +27 -29
- package/compiled/src/commands/build.js +43 -55
- package/compiled/src/commands/config/handlers.js +292 -0
- package/compiled/src/commands/config/index.js +148 -0
- package/compiled/src/commands/config/resources.js +88 -0
- package/compiled/src/commands/default-workspace.js +39 -0
- package/compiled/src/commands/deploy.js +127 -78
- package/compiled/src/commands/init.js +17 -21
- package/compiled/src/commands/login.js +24 -27
- package/compiled/src/commands/shared.js +12 -17
- package/compiled/src/commands/spec.js +31 -0
- package/compiled/src/commands/test.js +7 -10
- package/compiled/src/commands/whoami.js +8 -12
- package/compiled/src/index.js +40 -24
- package/compiled/src/lib/api-client.js +52 -0
- package/compiled/src/lib/auth-file.js +67 -0
- package/compiled/src/lib/body-builder.js +53 -0
- package/compiled/src/lib/body-fields.js +47 -0
- package/compiled/src/lib/chalk-code-highlight.js +15 -20
- package/compiled/src/lib/compiled-function.js +27 -29
- package/compiled/src/lib/dotted.js +34 -0
- package/compiled/src/lib/indent.js +3 -8
- package/compiled/src/lib/project-config.js +20 -0
- package/compiled/src/lib/renderer.js +33 -0
- package/compiled/src/lib/spec.js +11 -0
- package/compiled/src/lib/template.js +7 -11
- package/compiled/src/lib/version.js +13 -20
- package/compiled/src/templates/functions.js +13 -18
- package/dist/main.js +45496 -86270
- package/dist/main.js.map +7 -1
- package/package.json +27 -29
- package/src/commands/build.ts +22 -27
- package/src/commands/config/handlers.ts +339 -0
- package/src/commands/config/index.ts +171 -0
- package/src/commands/config/resources.ts +110 -0
- package/src/commands/default-workspace.ts +44 -0
- package/src/commands/deploy.ts +145 -44
- package/src/commands/login.ts +3 -1
- package/src/commands/spec.ts +32 -0
- package/src/index.ts +36 -19
- package/src/lib/api-client.ts +64 -0
- package/src/lib/auth-file.ts +83 -0
- package/src/lib/body-builder.ts +68 -0
- package/src/lib/body-fields.ts +61 -0
- package/src/lib/compiled-function.ts +30 -23
- package/src/lib/dotted.ts +43 -0
- package/src/lib/project-config.ts +32 -0
- package/src/lib/renderer.ts +44 -0
- package/src/lib/spec.ts +32 -0
- package/tsconfig.json +2 -19
- package/.turbo/turbo-build.log +0 -28
- package/.turbo/turbo-clean.log +0 -5
- package/babel.config.cjs +0 -4
- package/dist/140.js +0 -452
- package/dist/140.js.map +0 -1
- package/dist/233.js +0 -4890
- package/dist/233.js.map +0 -1
- package/dist/445e7f36f8a19c2bf682.js +0 -900
- package/webpack.config.js +0 -49
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
|
|
5
|
+
export type AuthInfo = { host: string; apikey: string };
|
|
6
|
+
|
|
7
|
+
export type AuthFile = {
|
|
8
|
+
host?: string;
|
|
9
|
+
apikey?: string;
|
|
10
|
+
defaultWorkspace?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const DEFAULT_HOST = "https://use.jitsu.com";
|
|
14
|
+
|
|
15
|
+
export function authFilePath(): string {
|
|
16
|
+
return `${homedir()}/.jitsu/jitsu-cli.json`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function readAuthFile(): AuthFile | undefined {
|
|
20
|
+
const path = authFilePath();
|
|
21
|
+
if (!fs.existsSync(path)) return undefined;
|
|
22
|
+
return JSON.parse(readFileSync(path, { encoding: "utf-8" }));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Merge `patch` into the existing auth file, creating the file (and parent dir)
|
|
26
|
+
// if necessary. Setting a field to `undefined` removes it.
|
|
27
|
+
export function updateAuthFile(patch: Partial<AuthFile>): AuthFile {
|
|
28
|
+
const path = authFilePath();
|
|
29
|
+
const existing = readAuthFile() ?? {};
|
|
30
|
+
const next: AuthFile = { ...existing };
|
|
31
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
32
|
+
if (v === undefined) delete (next as any)[k];
|
|
33
|
+
else (next as any)[k] = v;
|
|
34
|
+
}
|
|
35
|
+
mkdirSync(`${homedir()}/.jitsu`, { recursive: true });
|
|
36
|
+
writeFileSync(path, JSON.stringify(next, null, 2));
|
|
37
|
+
return next;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function readDefaultWorkspace(): string | undefined {
|
|
41
|
+
return readAuthFile()?.defaultWorkspace;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Resolve host + apikey for an authenticated CLI command. Order of precedence:
|
|
45
|
+
// 1. --host / --apikey flags
|
|
46
|
+
// 2. ~/.jitsu/jitsu-cli.json
|
|
47
|
+
// 3. JITSU_HOST / JITSU_APIKEY env vars
|
|
48
|
+
// 4. https://use.jitsu.com (host only)
|
|
49
|
+
export function resolveAuth(opts: { host?: string; apikey?: string }): AuthInfo {
|
|
50
|
+
let host = opts.host;
|
|
51
|
+
let apikey = opts.apikey;
|
|
52
|
+
|
|
53
|
+
if (!host || !apikey) {
|
|
54
|
+
const file = readAuthFile();
|
|
55
|
+
if (file) {
|
|
56
|
+
if (!host) host = file.host;
|
|
57
|
+
if (!apikey) apikey = file.apikey;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!host) host = process.env.JITSU_HOST;
|
|
62
|
+
if (!apikey) apikey = process.env.JITSU_APIKEY;
|
|
63
|
+
if (!host) host = DEFAULT_HOST;
|
|
64
|
+
|
|
65
|
+
if (!apikey) {
|
|
66
|
+
throw new Error("Not authenticated. Run `jitsu login`, set JITSU_APIKEY, or pass --apikey <key>.");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { host: normalizeHost(host), apikey };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function normalizeHost(host: string): string {
|
|
73
|
+
let url = host;
|
|
74
|
+
if (!url.startsWith("http")) {
|
|
75
|
+
if (url.startsWith("localhost") || /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/.test(url)) {
|
|
76
|
+
url = "http://" + url;
|
|
77
|
+
} else {
|
|
78
|
+
url = "https://" + url;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (url.endsWith("/")) url = url.slice(0, -1);
|
|
82
|
+
return url;
|
|
83
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import yaml from "js-yaml";
|
|
3
|
+
import { parseScalar, setDottedPath } from "./dotted";
|
|
4
|
+
|
|
5
|
+
export type BodySources = {
|
|
6
|
+
// Path to a yaml/json file. `-` means stdin.
|
|
7
|
+
file?: string;
|
|
8
|
+
// Inline JSON string (full body or partial).
|
|
9
|
+
json?: string;
|
|
10
|
+
// Already-parsed dotted-path fields, e.g. { "credentials.password": "secret" }.
|
|
11
|
+
fields?: Record<string, string>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// Builds a single request body by merging (deep) sources in this order:
|
|
15
|
+
// 1. -f / --file
|
|
16
|
+
// 2. --json
|
|
17
|
+
// 3. dotted-path flags
|
|
18
|
+
// Later sources override earlier ones at the leaf level. Arrays are replaced, not concatenated.
|
|
19
|
+
// Returns undefined if no source is provided.
|
|
20
|
+
export function buildBody(sources: BodySources): unknown {
|
|
21
|
+
const layers: any[] = [];
|
|
22
|
+
if (sources.file) layers.push(loadFile(sources.file));
|
|
23
|
+
if (sources.json) layers.push(parseInlineJson(sources.json));
|
|
24
|
+
if (sources.fields && Object.keys(sources.fields).length > 0) {
|
|
25
|
+
const obj: any = {};
|
|
26
|
+
for (const [path, raw] of Object.entries(sources.fields)) {
|
|
27
|
+
setDottedPath(obj, path, parseScalar(raw));
|
|
28
|
+
}
|
|
29
|
+
layers.push(obj);
|
|
30
|
+
}
|
|
31
|
+
if (layers.length === 0) return undefined;
|
|
32
|
+
return layers.reduce((acc, layer) => deepMerge(acc, layer), {});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function loadFile(path: string): unknown {
|
|
36
|
+
const text = path === "-" ? fs.readFileSync(0, "utf-8") : fs.readFileSync(path, "utf-8");
|
|
37
|
+
// js-yaml's safeLoad handles JSON too (JSON is valid YAML). But trim first — empty file is null.
|
|
38
|
+
const parsed = yaml.load(text);
|
|
39
|
+
if (parsed === null || parsed === undefined) {
|
|
40
|
+
throw new Error(`File ${path} parsed as empty`);
|
|
41
|
+
}
|
|
42
|
+
return parsed;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseInlineJson(s: string): unknown {
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(s);
|
|
48
|
+
} catch (e) {
|
|
49
|
+
throw new Error(`--json value is not valid JSON: ${(e as Error).message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isPlainObject(v: unknown): v is Record<string, unknown> {
|
|
54
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function deepMerge(target: any, source: any): any {
|
|
58
|
+
if (!isPlainObject(target) || !isPlainObject(source)) return source;
|
|
59
|
+
const out: any = { ...target };
|
|
60
|
+
for (const [k, v] of Object.entries(source)) {
|
|
61
|
+
if (isPlainObject(v) && isPlainObject(out[k])) {
|
|
62
|
+
out[k] = deepMerge(out[k], v);
|
|
63
|
+
} else {
|
|
64
|
+
out[k] = v;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Pre-extracts body field flags from process.argv before Commander parses it.
|
|
2
|
+
//
|
|
3
|
+
// Why: we want users to write `--credentials.password=secret` and `--destinationType=postgres`
|
|
4
|
+
// as ad-hoc body fields, but Commander would reject unknown options. The simplest robust
|
|
5
|
+
// solution is to filter argv: anything matching `--<name>=<value>` whose head segment is NOT
|
|
6
|
+
// a reserved Commander option is captured here, and the rest is left for Commander.
|
|
7
|
+
//
|
|
8
|
+
// Single global stash is fine because the CLI runs one command per process.
|
|
9
|
+
|
|
10
|
+
// Reserved option names — any `--name=value` whose head is in this set is left for Commander.
|
|
11
|
+
// Everything else becomes a body field (top-level for flat names, nested for dotted ones).
|
|
12
|
+
const RESERVED = new Set([
|
|
13
|
+
"workspace",
|
|
14
|
+
"output",
|
|
15
|
+
"host",
|
|
16
|
+
"apikey",
|
|
17
|
+
"file",
|
|
18
|
+
"json",
|
|
19
|
+
"cascade",
|
|
20
|
+
"strict",
|
|
21
|
+
"from",
|
|
22
|
+
"to",
|
|
23
|
+
"help",
|
|
24
|
+
"version",
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
const fields: Record<string, string> = {};
|
|
28
|
+
|
|
29
|
+
// Activate body-field extraction only for the `config` command group. Otherwise the
|
|
30
|
+
// preprocessor would steal flags from `deploy --type=function --name=foo` etc.
|
|
31
|
+
function shouldExtract(argv: string[]): boolean {
|
|
32
|
+
for (let i = 2; i < argv.length; i++) {
|
|
33
|
+
const a = argv[i];
|
|
34
|
+
if (a.startsWith("-")) continue;
|
|
35
|
+
return a === "config";
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function preprocessArgv(argv: string[]): string[] {
|
|
41
|
+
if (!shouldExtract(argv)) return argv;
|
|
42
|
+
const out: string[] = [];
|
|
43
|
+
for (const arg of argv) {
|
|
44
|
+
const m = /^--([a-zA-Z][\w.-]*)=([\s\S]*)$/.exec(arg);
|
|
45
|
+
if (m) {
|
|
46
|
+
const head = m[1].split(".")[0];
|
|
47
|
+
if (!RESERVED.has(head)) {
|
|
48
|
+
fields[m[1]] = m[2];
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
out.push(arg);
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function consumeBodyFields(): Record<string, string> {
|
|
58
|
+
const copy = { ...fields };
|
|
59
|
+
for (const k of Object.keys(fields)) delete fields[k];
|
|
60
|
+
return copy;
|
|
61
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { JitsuFunction } from "@jitsu/protocols/functions";
|
|
2
2
|
import fs from "fs";
|
|
3
|
-
import
|
|
3
|
+
import * as esbuild from "esbuild";
|
|
4
4
|
import { assertDefined, assertTrue } from "juava";
|
|
5
|
+
import path from "path";
|
|
5
6
|
|
|
6
7
|
export type CompiledFunction = {
|
|
7
8
|
func: JitsuFunction;
|
|
@@ -14,10 +15,11 @@ export type CompiledFunction = {
|
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
function getSlug(filePath: string) {
|
|
17
|
-
return filePath.split("/").pop()?.replace(".ts", "");
|
|
18
|
+
return filePath.split("/").pop()?.replace(".ts", "").replace(".js", "");
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export async function getFunctionFromFilePath(
|
|
22
|
+
projectDir: string,
|
|
21
23
|
filePath: string,
|
|
22
24
|
kind: "function" | "profile",
|
|
23
25
|
profileBuilders: any[] = []
|
|
@@ -28,29 +30,34 @@ export async function getFunctionFromFilePath(
|
|
|
28
30
|
throw new Error(`Cannot load function from file ${filePath}: path is not a file`);
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
// Transform ESM to CJS without bundling - just convert the module format
|
|
34
|
+
// This avoids resolving dependencies which may be symlinked
|
|
35
|
+
const result = await esbuild.transform(fs.readFileSync(filePath, "utf-8"), {
|
|
36
|
+
loader: filePath.endsWith(".ts") ? "ts" : "js",
|
|
37
|
+
format: "cjs",
|
|
38
|
+
platform: "node",
|
|
35
39
|
});
|
|
36
40
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
const code = result.code;
|
|
42
|
+
const module: { exports: Record<string, any> } = { exports: {} };
|
|
43
|
+
const exports = module.exports;
|
|
44
|
+
// Provide require stub for external imports that we mock out
|
|
45
|
+
const require = (id: string) => {
|
|
46
|
+
// External dependencies are not needed for config extraction
|
|
47
|
+
return {};
|
|
48
|
+
};
|
|
49
|
+
eval(code);
|
|
50
|
+
// After eval, module.exports contains the actual exports
|
|
51
|
+
const moduleExports = module.exports;
|
|
44
52
|
assertDefined(
|
|
45
|
-
|
|
46
|
-
`Function from ${filePath} doesn't have default export. Exported symbols: ${Object.keys(
|
|
53
|
+
moduleExports.default,
|
|
54
|
+
`Function from ${filePath} doesn't have default export. Exported symbols: ${Object.keys(moduleExports)}`
|
|
47
55
|
);
|
|
48
|
-
assertTrue(typeof
|
|
49
|
-
|
|
50
|
-
let
|
|
51
|
-
let id = exports.config?.id;
|
|
56
|
+
assertTrue(typeof moduleExports.default === "function", `Default export from ${filePath} is not a function`);
|
|
57
|
+
let name = moduleExports.config?.name || moduleExports.config?.slug || getSlug(filePath);
|
|
58
|
+
let id = moduleExports.config?.id;
|
|
52
59
|
if (kind === "profile") {
|
|
53
|
-
const profileBuilderId =
|
|
60
|
+
const profileBuilderId = moduleExports.config?.profileBuilderId;
|
|
54
61
|
const profileBuilder = profileBuilders.find(pb => pb.id === profileBuilderId);
|
|
55
62
|
if (!profileBuilder) {
|
|
56
63
|
throw new Error(
|
|
@@ -67,12 +74,12 @@ export async function getFunctionFromFilePath(
|
|
|
67
74
|
}
|
|
68
75
|
|
|
69
76
|
return {
|
|
70
|
-
func:
|
|
77
|
+
func: moduleExports.default,
|
|
71
78
|
meta: {
|
|
72
|
-
slug:
|
|
79
|
+
slug: moduleExports.config?.slug || getSlug(filePath),
|
|
73
80
|
id: id,
|
|
74
81
|
name: name,
|
|
75
|
-
description:
|
|
82
|
+
description: moduleExports.config?.description,
|
|
76
83
|
},
|
|
77
84
|
};
|
|
78
85
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Parse a value string from a dotted-path flag.
|
|
2
|
+
// Heuristic: if it parses cleanly as JSON and starts with `[`, `{`, `"`, or is a
|
|
3
|
+
// number/boolean/null literal, take the JSON value. Otherwise treat as a plain string.
|
|
4
|
+
// This lets users pass arrays/objects without quoting hell while keeping bare strings
|
|
5
|
+
// (`--name=foo`) working without quoting them as `"foo"`.
|
|
6
|
+
export function parseScalar(raw: string): unknown {
|
|
7
|
+
if (raw === "") return "";
|
|
8
|
+
const trimmed = raw.trim();
|
|
9
|
+
const first = trimmed[0];
|
|
10
|
+
const looksLikeJson =
|
|
11
|
+
first === "{" ||
|
|
12
|
+
first === "[" ||
|
|
13
|
+
first === '"' ||
|
|
14
|
+
trimmed === "true" ||
|
|
15
|
+
trimmed === "false" ||
|
|
16
|
+
trimmed === "null" ||
|
|
17
|
+
/^-?\d/.test(trimmed);
|
|
18
|
+
if (looksLikeJson) {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(trimmed);
|
|
21
|
+
} catch {
|
|
22
|
+
// fall through to string
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return raw;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Build an object by setting `path` (e.g. "credentials.password") to `value`.
|
|
29
|
+
// Numeric path segments (`a.0.b`) are NOT special — kept as object keys. If you
|
|
30
|
+
// need arrays, pass them as JSON values (e.g. --keys='["a","b"]').
|
|
31
|
+
export function setDottedPath(target: any, path: string, value: unknown): any {
|
|
32
|
+
const parts = path.split(".");
|
|
33
|
+
let node = target;
|
|
34
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
35
|
+
const key = parts[i];
|
|
36
|
+
if (node[key] == null || typeof node[key] !== "object" || Array.isArray(node[key])) {
|
|
37
|
+
node[key] = {};
|
|
38
|
+
}
|
|
39
|
+
node = node[key];
|
|
40
|
+
}
|
|
41
|
+
node[parts[parts.length - 1]] = value;
|
|
42
|
+
return target;
|
|
43
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { existsSync, readFileSync } from "fs";
|
|
3
|
+
import { b } from "./chalk-code-highlight";
|
|
4
|
+
|
|
5
|
+
// Per-project Jitsu configuration, committed alongside the project so a deploy
|
|
6
|
+
// doesn't need machine-global state or CLI flags. Vercel-style (jitsu.json).
|
|
7
|
+
export type ProjectConfig = {
|
|
8
|
+
// Workspace id or slug to deploy to. Overridden by the -w/--workspace flag.
|
|
9
|
+
workspace?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// Reads per-project config. Precedence:
|
|
13
|
+
// 1. jitsu.json at the project root
|
|
14
|
+
// 2. "jitsu" key in package.json
|
|
15
|
+
// Returns {} when neither is present. `packageJson` is passed in to avoid a
|
|
16
|
+
// second read of a file the caller already parsed.
|
|
17
|
+
export function loadProjectConfig(projectDir: string, packageJson?: any): ProjectConfig {
|
|
18
|
+
const jitsuJsonPath = path.resolve(projectDir, "jitsu.json");
|
|
19
|
+
if (existsSync(jitsuJsonPath)) {
|
|
20
|
+
let parsed: any;
|
|
21
|
+
try {
|
|
22
|
+
parsed = JSON.parse(readFileSync(jitsuJsonPath, "utf-8"));
|
|
23
|
+
} catch (e) {
|
|
24
|
+
throw new Error(`Failed to parse ${b(jitsuJsonPath)}: ${e instanceof Error ? e.message : String(e)}`);
|
|
25
|
+
}
|
|
26
|
+
return parsed ?? {};
|
|
27
|
+
}
|
|
28
|
+
if (packageJson?.jitsu && typeof packageJson.jitsu === "object") {
|
|
29
|
+
return packageJson.jitsu as ProjectConfig;
|
|
30
|
+
}
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import yaml from "js-yaml";
|
|
2
|
+
|
|
3
|
+
export interface Renderer {
|
|
4
|
+
format: string;
|
|
5
|
+
render(value: unknown): string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const renderers: Record<string, Renderer> = {
|
|
9
|
+
yaml: {
|
|
10
|
+
format: "yaml",
|
|
11
|
+
render(value) {
|
|
12
|
+
if (value === undefined) return "";
|
|
13
|
+
// skipInvalid drops things like undefined/functions silently.
|
|
14
|
+
// noRefs avoids YAML anchors (`&id001`) which confuse readers and most YAML consumers.
|
|
15
|
+
return yaml.dump(value, { skipInvalid: true, noRefs: true, sortKeys: false, lineWidth: 120 });
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
json: {
|
|
19
|
+
format: "json",
|
|
20
|
+
render(value) {
|
|
21
|
+
return JSON.stringify(value, null, 2) + "\n";
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const SUPPORTED_OUTPUTS = Object.keys(renderers);
|
|
27
|
+
export const DEFAULT_OUTPUT = "yaml";
|
|
28
|
+
|
|
29
|
+
export function getRenderer(format?: string): Renderer {
|
|
30
|
+
const key = (format ?? DEFAULT_OUTPUT).toLowerCase();
|
|
31
|
+
const r = renderers[key];
|
|
32
|
+
if (!r) {
|
|
33
|
+
throw new Error(`Unsupported output format '${format}'. Supported: ${SUPPORTED_OUTPUTS.join(", ")}`);
|
|
34
|
+
}
|
|
35
|
+
return r;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function registerRenderer(r: Renderer) {
|
|
39
|
+
renderers[r.format.toLowerCase()] = r;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function print(value: unknown, format?: string) {
|
|
43
|
+
process.stdout.write(getRenderer(format).render(value));
|
|
44
|
+
}
|
package/src/lib/spec.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ApiClient } from "./api-client";
|
|
2
|
+
import { AuthInfo } from "./auth-file";
|
|
3
|
+
|
|
4
|
+
// Minimal subset of OpenAPI 3 we use. Avoids a runtime dep on openapi-types.
|
|
5
|
+
export type OpenApiSpec = {
|
|
6
|
+
openapi?: string;
|
|
7
|
+
info?: { title?: string; version?: string; description?: string };
|
|
8
|
+
paths?: Record<string, Record<string, OpenApiOperation>>;
|
|
9
|
+
components?: { schemas?: Record<string, any> };
|
|
10
|
+
};
|
|
11
|
+
export type OpenApiOperation = {
|
|
12
|
+
summary?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
tags?: string[];
|
|
15
|
+
parameters?: any[];
|
|
16
|
+
requestBody?: any;
|
|
17
|
+
responses?: Record<string, any>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export async function fetchSpec(auth: AuthInfo): Promise<OpenApiSpec> {
|
|
21
|
+
// /api/spec is public — no auth required, but reusing the client keeps host handling consistent.
|
|
22
|
+
const client = new ApiClient(auth);
|
|
23
|
+
return client.request<OpenApiSpec>({ method: "GET", path: "/api/spec" });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Resolves the operation that matches a (templated) path + method, e.g.
|
|
27
|
+
// ("/api/{workspaceId}/config/{type}", "post") → operation node with summary/description.
|
|
28
|
+
export function findOperation(spec: OpenApiSpec, path: string, method: string): OpenApiOperation | undefined {
|
|
29
|
+
const item = spec.paths?.[path];
|
|
30
|
+
if (!item) return undefined;
|
|
31
|
+
return item[method.toLowerCase()];
|
|
32
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -1,26 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
+
"extends": "@jitsu/common-config/tsconfig.json",
|
|
2
3
|
"compilerOptions": {
|
|
3
4
|
"outDir": "./compiled",
|
|
4
|
-
"rootDir": "."
|
|
5
|
-
"noImplicitAny": false,
|
|
6
|
-
"allowSyntheticDefaultImports": true,
|
|
7
|
-
"importHelpers": true,
|
|
8
|
-
"removeComments": true,
|
|
9
|
-
"target": "ES2021",
|
|
10
|
-
"module": "commonjs",
|
|
11
|
-
"lib": [
|
|
12
|
-
"dom","esnext"
|
|
13
|
-
],
|
|
14
|
-
"allowJs": true,
|
|
15
|
-
"strict": true,
|
|
16
|
-
"esModuleInterop": true,
|
|
17
|
-
"moduleResolution": "node",
|
|
18
|
-
"resolveJsonModule": true,
|
|
19
|
-
"skipLibCheck": true
|
|
5
|
+
"rootDir": "."
|
|
20
6
|
},
|
|
21
|
-
"exclude": [
|
|
22
|
-
"node_modules"
|
|
23
|
-
],
|
|
24
7
|
"include": [
|
|
25
8
|
"src/**/*"
|
|
26
9
|
]
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
jitsu-cli:build: cache hit, replaying output [2mdcca113b76fcc4b6[0m
|
|
2
|
-
[32mjitsu-cli:build: [0m
|
|
3
|
-
[32mjitsu-cli:build: [0m> jitsu-cli@0.0.0 build /Users/ildarnurislamov/Projects/newjitsu/cli/jitsu-cli
|
|
4
|
-
[32mjitsu-cli:build: [0m> pnpm compile && webpack
|
|
5
|
-
[32mjitsu-cli:build: [0m
|
|
6
|
-
[32mjitsu-cli:build: [0m
|
|
7
|
-
[32mjitsu-cli:build: [0m> jitsu-cli@0.0.0 compile /Users/ildarnurislamov/Projects/newjitsu/cli/jitsu-cli
|
|
8
|
-
[32mjitsu-cli:build: [0m> tsc -p .
|
|
9
|
-
[32mjitsu-cli:build: [0m
|
|
10
|
-
[32mjitsu-cli:build: [0masset main.js 3.45 MiB [emitted] (name: main) 1 related asset
|
|
11
|
-
[32mjitsu-cli:build: [0masset 233.js 130 KiB [emitted] (id hint: vendors) 1 related asset
|
|
12
|
-
[32mjitsu-cli:build: [0masset 445e7f36f8a19c2bf682.js 37.3 KiB [emitted] [immutable] [from: ../../node_modules/.pnpm/@rollup+plugin-typescript@11.1.6_rollup@3.29.5_tslib@2.8.0_typescript@5.6.3/node_modules/@rollup/plugin-typescript/dist/es/index.js] (auxiliary name: main)
|
|
13
|
-
[32mjitsu-cli:build: [0masset 140.js 10 KiB [emitted] 1 related asset
|
|
14
|
-
[32mjitsu-cli:build: [0morphan modules 574 KiB [orphan] 100 modules
|
|
15
|
-
[32mjitsu-cli:build: [0mruntime modules 3.62 KiB 10 modules
|
|
16
|
-
[32mjitsu-cli:build: [0mcacheable modules 2.84 MiB (javascript) 37.3 KiB (asset)
|
|
17
|
-
[32mjitsu-cli:build: [0m javascript modules 2.49 MiB 428 modules
|
|
18
|
-
[32mjitsu-cli:build: [0m json modules 363 KiB
|
|
19
|
-
[32mjitsu-cli:build: [0m modules by path ../../node_modules/.pnpm/iconv-lite@0.4.24/node_modules/iconv-lite/ 86.7 KiB
|
|
20
|
-
[32mjitsu-cli:build: [0m ../../node_modules/.pnpm/iconv-lite@0.4.24/node_modules/iconv-lite/encodings/tab...(truncated) 8.78 KiB [built] [code generated]
|
|
21
|
-
[32mjitsu-cli:build: [0m + 7 modules
|
|
22
|
-
[32mjitsu-cli:build: [0m + 4 modules
|
|
23
|
-
[32mjitsu-cli:build: [0m ../../node_modules/.pnpm/@rollup+plugin-typescript@11.1.6_rollup@3.29.5_tslib@2....(truncated) 37.3 KiB (asset) 42 bytes (javascript) [built] [code generated]
|
|
24
|
-
[32mjitsu-cli:build: [0moptional modules 84 bytes [optional]
|
|
25
|
-
[32mjitsu-cli:build: [0m external "worker_threads" 42 bytes [optional] [built] [code generated]
|
|
26
|
-
[32mjitsu-cli:build: [0m external "node:stream/web" 42 bytes [optional] [built] [code generated]
|
|
27
|
-
[32mjitsu-cli:build: [0m+ 26 modules
|
|
28
|
-
[32mjitsu-cli:build: [0mwebpack 5.99.5 compiled successfully in 4904 ms
|
package/.turbo/turbo-clean.log
DELETED
package/babel.config.cjs
DELETED