jitsu-cli 2.14.0-beta.10 → 2.14.0-beta.101

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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/bin/jitsu +3 -0
  3. package/build.mts +39 -0
  4. package/compiled/package.json +16 -22
  5. package/compiled/src/commands/build.js +16 -24
  6. package/compiled/src/commands/config/handlers.js +292 -0
  7. package/compiled/src/commands/config/index.js +148 -0
  8. package/compiled/src/commands/config/resources.js +88 -0
  9. package/compiled/src/commands/default-workspace.js +39 -0
  10. package/compiled/src/commands/deploy.js +55 -19
  11. package/compiled/src/commands/spec.js +31 -0
  12. package/compiled/src/index.js +21 -2
  13. package/compiled/src/lib/api-client.js +52 -0
  14. package/compiled/src/lib/auth-file.js +67 -0
  15. package/compiled/src/lib/body-builder.js +53 -0
  16. package/compiled/src/lib/body-fields.js +47 -0
  17. package/compiled/src/lib/compiled-function.js +23 -21
  18. package/compiled/src/lib/dotted.js +34 -0
  19. package/compiled/src/lib/renderer.js +33 -0
  20. package/compiled/src/lib/spec.js +11 -0
  21. package/dist/main.js +46422 -92895
  22. package/dist/main.js.map +7 -1
  23. package/package.json +37 -43
  24. package/src/commands/build.ts +21 -26
  25. package/src/commands/config/handlers.ts +339 -0
  26. package/src/commands/config/index.ts +171 -0
  27. package/src/commands/config/resources.ts +110 -0
  28. package/src/commands/default-workspace.ts +44 -0
  29. package/src/commands/deploy.ts +96 -20
  30. package/src/commands/spec.ts +32 -0
  31. package/src/index.ts +34 -17
  32. package/src/lib/api-client.ts +64 -0
  33. package/src/lib/auth-file.ts +83 -0
  34. package/src/lib/body-builder.ts +68 -0
  35. package/src/lib/body-fields.ts +61 -0
  36. package/src/lib/compiled-function.ts +30 -23
  37. package/src/lib/dotted.ts +43 -0
  38. package/src/lib/renderer.ts +44 -0
  39. package/src/lib/spec.ts +32 -0
  40. package/dist/140.js +0 -452
  41. package/dist/140.js.map +0 -1
  42. package/webpack.config.cjs +0 -53
@@ -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 { rollup } from "rollup";
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
- const bundle = await rollup({
32
- input: [filePath],
33
- external: ["@jitsu/functions-lib"],
34
- logLevel: "silent",
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 output = await bundle.generate({
38
- file: filePath,
39
- format: "commonjs",
40
- });
41
-
42
- const exports: Record<string, any> = {} as Record<string, any>;
43
- eval(output.output[0].code);
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
- exports.default,
46
- `Function from ${filePath} doesn't have default export. Exported symbols: ${Object.keys(exports)}`
53
+ moduleExports.default,
54
+ `Function from ${filePath} doesn't have default export. Exported symbols: ${Object.keys(moduleExports)}`
47
55
  );
48
- assertTrue(typeof exports.default === "function", `Default export from ${filePath} is not a function`);
49
-
50
- let name = exports.config?.name || exports.config?.slug || getSlug(filePath);
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 = exports.config?.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: exports.default,
77
+ func: moduleExports.default,
71
78
  meta: {
72
- slug: exports.config?.slug || getSlug(filePath),
79
+ slug: moduleExports.config?.slug || getSlug(filePath),
73
80
  id: id,
74
81
  name: name,
75
- description: exports.config?.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,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
+ }
@@ -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
+ }