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,88 @@
1
+ export const resources = [
2
+ {
3
+ noun: "workspaces",
4
+ aliases: ["workspace"],
5
+ kind: "workspace",
6
+ description: "Workspaces accessible to the current user",
7
+ },
8
+ {
9
+ noun: "destinations",
10
+ aliases: ["destination", "dest"],
11
+ kind: "configObject",
12
+ type: "destination",
13
+ supportsTest: true,
14
+ description: "Destinations (warehouses, databases, services receiving events)",
15
+ },
16
+ {
17
+ noun: "streams",
18
+ aliases: ["stream"],
19
+ kind: "configObject",
20
+ type: "stream",
21
+ supportsTest: true,
22
+ description: "Event streams (formerly known as sources)",
23
+ },
24
+ {
25
+ noun: "functions",
26
+ aliases: ["function", "fn"],
27
+ kind: "configObject",
28
+ type: "function",
29
+ description: "User-defined functions (UDFs). For dev workflow see `jitsu deploy`.",
30
+ },
31
+ {
32
+ noun: "services",
33
+ aliases: ["service"],
34
+ kind: "configObject",
35
+ type: "service",
36
+ supportsTest: true,
37
+ description: "External connector services (Airbyte protocol)",
38
+ },
39
+ {
40
+ noun: "domains",
41
+ aliases: ["domain"],
42
+ kind: "configObject",
43
+ type: "domain",
44
+ description: "Custom ingestion domains",
45
+ },
46
+ {
47
+ noun: "misc",
48
+ aliases: [],
49
+ kind: "configObject",
50
+ type: "misc",
51
+ description: "Miscellaneous configuration entities (free-form)",
52
+ },
53
+ {
54
+ noun: "notifications",
55
+ aliases: ["notification"],
56
+ kind: "configObject",
57
+ type: "notification",
58
+ description: "Alert channels (email/Slack)",
59
+ },
60
+ {
61
+ noun: "connections",
62
+ aliases: ["connection", "link", "links"],
63
+ kind: "link",
64
+ description: "Connections between streams/services and destinations",
65
+ },
66
+ {
67
+ noun: "profile-builders",
68
+ aliases: ["profile-builder"],
69
+ kind: "profile-builder",
70
+ description: "Profile builders (identity stitching)",
71
+ },
72
+ ];
73
+ export function verbsFor(kind) {
74
+ switch (kind) {
75
+ case "configObject":
76
+ return ["list", "get", "create", "update", "delete"];
77
+ case "workspace":
78
+ return ["list", "get", "create", "update", "delete"];
79
+ case "link":
80
+ return ["list", "create", "update", "delete"];
81
+ case "profile-builder":
82
+ return ["list", "create", "update", "delete"];
83
+ }
84
+ }
85
+ export function findResource(name) {
86
+ const lower = name.toLowerCase();
87
+ return resources.find(r => r.noun === lower || r.aliases.includes(lower));
88
+ }
@@ -0,0 +1,39 @@
1
+ import { ApiClient, ApiError } from "../lib/api-client";
2
+ import { authFilePath, readAuthFile, resolveAuth, updateAuthFile } from "../lib/auth-file";
3
+ import { red } from "../lib/chalk-code-highlight";
4
+ export async function setDefaultWorkspace(idOrSlug, opts) {
5
+ try {
6
+ const auth = resolveAuth(opts);
7
+ const client = new ApiClient(auth);
8
+ let workspace;
9
+ try {
10
+ workspace = await client.request({
11
+ method: "GET",
12
+ path: `/api/workspace/${encodeURIComponent(idOrSlug)}`,
13
+ });
14
+ }
15
+ catch (e) {
16
+ if (e instanceof ApiError && e.status === 404) {
17
+ throw new Error(`Workspace '${idOrSlug}' not found (or you don't have access)`);
18
+ }
19
+ throw e;
20
+ }
21
+ updateAuthFile({ defaultWorkspace: workspace.id });
22
+ const label = workspace.slug ? `${workspace.slug} (${workspace.id})` : workspace.id;
23
+ console.log(`Default workspace set to ${label}.`);
24
+ console.log(`Saved to ${authFilePath()}.`);
25
+ }
26
+ catch (e) {
27
+ console.error(red(`Error: ${e instanceof Error ? e.message : String(e)}`));
28
+ process.exit(1);
29
+ }
30
+ }
31
+ export async function unsetDefaultWorkspace() {
32
+ const file = readAuthFile();
33
+ if (!file?.defaultWorkspace) {
34
+ console.log("No default workspace is set.");
35
+ return;
36
+ }
37
+ updateAuthFile({ defaultWorkspace: undefined });
38
+ console.log("Default workspace unset.");
39
+ }
@@ -3,7 +3,6 @@ import { homedir } from "os";
3
3
  import inquirer from "inquirer";
4
4
  import { existsSync, readdirSync, readFileSync } from "fs";
5
5
  import { loadPackageJson } from "./shared";
6
- import fetch from "node-fetch";
7
6
  import cuid from "cuid";
8
7
  import { b, green, red } from "../lib/chalk-code-highlight";
9
8
  import { getFunctionFromFilePath } from "../lib/compiled-function";
@@ -68,10 +67,14 @@ async function deployFunctions({ host, apikey, name: names }, projectDir, packag
68
67
  const selected = names ? names.flatMap(n => n.split(",")).map(n => n.trim()) : undefined;
69
68
  const dir = `dist/${kind}s`;
70
69
  const functionsDir = path.resolve(projectDir, dir);
70
+ if (!existsSync(functionsDir)) {
71
+ console.warn(`No ${b(dir)} directory found, skipping ${kind}s. Please make sure that you have built the project.`);
72
+ return;
73
+ }
71
74
  const functionsFiles = readdirSync(functionsDir);
72
75
  if (functionsFiles.length === 0) {
73
- console.warn(red(`Can't find function files in ${b(dir)} directory. Please make sure that you have built the project.`));
74
- process.exit(1);
76
+ console.warn(`No ${kind} files found in ${b(dir)}, skipping. Please make sure that you have built the project.`);
77
+ return;
75
78
  }
76
79
  const selectedFiles = [];
77
80
  if (selected) {
@@ -103,14 +106,56 @@ async function deployFunctions({ host, apikey, name: names }, projectDir, packag
103
106
  }
104
107
  profileBuilders = (await res.json()).profileBuilders;
105
108
  }
109
+ const existingFunctions = await fetchExistingFunctions({ host, apikey, workspaceId: workspace.id });
106
110
  for (const file of selectedFiles) {
107
111
  console.log(`${b(`𝑓`)} Deploying function ${b(path.basename(file))} to workspace ${workspace.name} (${host}/${workspace.slug || workspace.id})`);
108
- await deployFunction({ host, apikey }, packageJson, workspace, kind, path.resolve(functionsDir, file), profileBuilders);
112
+ await deployFunction(projectDir, { host, apikey }, packageJson, workspace, kind, path.resolve(functionsDir, file), profileBuilders, existingFunctions);
113
+ }
114
+ }
115
+ async function fetchExistingFunctions({ host, apikey, workspaceId, }) {
116
+ const res = await fetch(`${host}/api/${workspaceId}/config/function`, {
117
+ headers: { Authorization: `Bearer ${apikey}` },
118
+ });
119
+ if (!res.ok) {
120
+ console.error(red(`Cannot list existing functions:\n${b(await res.text())}`));
121
+ process.exit(1);
122
+ }
123
+ const { objects } = (await res.json());
124
+ const bySlug = new Map();
125
+ const byId = new Map();
126
+ for (const f of objects) {
127
+ const entry = { id: f.id, slug: f.slug };
128
+ byId.set(f.id, entry);
129
+ if (f.slug)
130
+ bySlug.set(f.slug, entry);
131
+ }
132
+ return { bySlug, byId };
133
+ }
134
+ function cacheAfterCreate(cache, id, slug) {
135
+ const entry = { id, slug };
136
+ cache.byId.set(id, entry);
137
+ if (slug)
138
+ cache.bySlug.set(slug, entry);
139
+ }
140
+ function cacheAfterUpdate(cache, id, newSlug) {
141
+ const entry = cache.byId.get(id);
142
+ if (!entry) {
143
+ cacheAfterCreate(cache, id, newSlug);
144
+ return;
145
+ }
146
+ if (entry.slug && entry.slug !== newSlug) {
147
+ cache.bySlug.delete(entry.slug);
109
148
  }
149
+ entry.slug = newSlug;
150
+ if (newSlug)
151
+ cache.bySlug.set(newSlug, entry);
110
152
  }
111
- async function deployFunction({ host, apikey }, packageJson, workspace, kind, file, profileBuilders = []) {
153
+ async function deployFunction(projectDir, { host, apikey }, packageJson, workspace, kind, file, profileBuilders = [], existingFunctions = {
154
+ bySlug: new Map(),
155
+ byId: new Map(),
156
+ }) {
112
157
  const code = readFileSync(file, "utf-8");
113
- const wrapped = await getFunctionFromFilePath(file, kind, profileBuilders);
158
+ const wrapped = await getFunctionFromFilePath(projectDir, file, kind, profileBuilders);
114
159
  const meta = wrapped.meta;
115
160
  if (meta) {
116
161
  console.log(` meta: slug=${meta.slug}, name=${meta.name || "not set"}`);
@@ -121,19 +166,8 @@ async function deployFunction({ host, apikey }, packageJson, workspace, kind, fi
121
166
  }
122
167
  let existingFunctionId;
123
168
  if (meta.slug) {
124
- const res = await fetch(`${host}/api/${workspace.id}/config/function`, {
125
- headers: {
126
- Authorization: `Bearer ${apikey}`,
127
- },
128
- });
129
- if (!res.ok) {
130
- console.error(red(`Cannot add function to workspace:\n${b(await res.text())}`));
131
- process.exit(1);
132
- }
133
- else {
134
- const existing = (await res.json());
135
- existingFunctionId = existing.objects.find(f => f.slug === meta.slug || f.id === meta.id)?.id;
136
- }
169
+ existingFunctionId =
170
+ existingFunctions.bySlug.get(meta.slug)?.id ?? (meta.id ? existingFunctions.byId.get(meta.id)?.id : undefined);
137
171
  }
138
172
  let functionPayload = {};
139
173
  if (kind === "profile") {
@@ -172,6 +206,7 @@ async function deployFunction({ host, apikey }, packageJson, workspace, kind, fi
172
206
  process.exit(1);
173
207
  }
174
208
  else {
209
+ cacheAfterCreate(existingFunctions, id, meta.slug);
175
210
  console.log(`Function ${b(meta.name)} was successfully added to workspace ${workspace.name} with id: ${b(id)}`);
176
211
  }
177
212
  }
@@ -199,6 +234,7 @@ async function deployFunction({ host, apikey }, packageJson, workspace, kind, fi
199
234
  process.exit(1);
200
235
  }
201
236
  else {
237
+ cacheAfterUpdate(existingFunctions, id, meta.slug);
202
238
  console.log(`${green(`✓`)} ${b(meta.name)} deployed successfully!`);
203
239
  }
204
240
  }
@@ -0,0 +1,31 @@
1
+ import { Command } from "commander";
2
+ import { red } from "../lib/chalk-code-highlight";
3
+ import { normalizeHost, readAuthFile, resolveAuth } from "../lib/auth-file";
4
+ import { fetchSpec } from "../lib/spec";
5
+ import { DEFAULT_OUTPUT, SUPPORTED_OUTPUTS, print } from "../lib/renderer";
6
+ export function buildSpecCommand() {
7
+ return new Command("spec")
8
+ .description("Print the live OpenAPI spec served at /api/spec")
9
+ .option("-o, --output <format>", `Output format: ${SUPPORTED_OUTPUTS.join(", ")}`, DEFAULT_OUTPUT)
10
+ .option("-h, --host <host>", "Jitsu host (overrides ~/.jitsu/jitsu-cli.json)")
11
+ .option("-k, --apikey <api-key>", "API key (overrides ~/.jitsu/jitsu-cli.json)")
12
+ .action(async (opts) => {
13
+ try {
14
+ const auth = (() => {
15
+ try {
16
+ return resolveAuth(opts);
17
+ }
18
+ catch {
19
+ const host = opts.host ?? readAuthFile()?.host ?? "https://use.jitsu.com";
20
+ return { host: normalizeHost(host), apikey: "anonymous" };
21
+ }
22
+ })();
23
+ const spec = await fetchSpec(auth);
24
+ print(spec, opts.output);
25
+ }
26
+ catch (e) {
27
+ console.error(red(`Error: ${e instanceof Error ? e.message : String(e)}`));
28
+ process.exit(1);
29
+ }
30
+ });
31
+ }
@@ -7,9 +7,17 @@ import { build } from "./commands/build";
7
7
  import { test } from "./commands/test";
8
8
  import { jitsuCliVersion, jitsuCliPackageName } from "./lib/version";
9
9
  import { whoami } from "./commands/whoami";
10
- console.log(figlet.textSync("Jitsu CLI", { horizontalLayout: "full" }));
10
+ import { buildConfigCommand } from "./commands/config";
11
+ import { buildSpecCommand } from "./commands/spec";
12
+ import { setDefaultWorkspace, unsetDefaultWorkspace } from "./commands/default-workspace";
13
+ import { preprocessArgv } from "./lib/body-fields";
14
+ process.argv = preprocessArgv(process.argv);
15
+ const isPipedStdout = !process.stdout.isTTY;
16
+ if (!isPipedStdout) {
17
+ process.stderr.write(figlet.textSync("Jitsu CLI", { horizontalLayout: "full" }) + "\n");
18
+ }
11
19
  const p = new Command();
12
- p.name(jitsuCliPackageName).description("CLI command to create, test and deploy extensions for Jitsu Next");
20
+ p.name(jitsuCliPackageName).description("Jitsu CLI manage workspaces, configuration objects, and extensions");
13
21
  p.command("init")
14
22
  .description("Initialize a new Jitsu extension project")
15
23
  .arguments("[dir]")
@@ -45,6 +53,17 @@ p.command("deploy")
45
53
  .option("-t, --type <type>", "entity type to deploy", "function")
46
54
  .option("-n, --name <name...>", "limit deploy to provided entities only. (Optional)")
47
55
  .action(deploy);
56
+ p.command("set-default-workspace")
57
+ .description("Save a default workspace to ~/.jitsu/jitsu-cli.json. Subsequent `jitsu config` commands use it when -w is omitted.")
58
+ .argument("<id-or-slug>", "Workspace id or slug")
59
+ .option("-h, --host <host>", "Jitsu host (overrides ~/.jitsu/jitsu-cli.json)")
60
+ .option("-k, --apikey <api-key>", "API key in form keyId:secret (overrides ~/.jitsu/jitsu-cli.json)")
61
+ .action(setDefaultWorkspace);
62
+ p.command("unset-default-workspace")
63
+ .description("Remove the saved default workspace from ~/.jitsu/jitsu-cli.json")
64
+ .action(unsetDefaultWorkspace);
65
+ p.addCommand(buildConfigCommand());
66
+ p.addCommand(buildSpecCommand());
48
67
  p.version(jitsuCliPackageName + " " + jitsuCliVersion, "-v, --version");
49
68
  p.helpOption("--help", "display help for command");
50
69
  p.parse();
@@ -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
+ }
@@ -1,33 +1,35 @@
1
1
  import fs from "fs";
2
- import { rollup } from "rollup";
2
+ import * as esbuild from "esbuild";
3
3
  import { assertDefined, assertTrue } from "juava";
4
4
  function getSlug(filePath) {
5
- return filePath.split("/").pop()?.replace(".ts", "");
5
+ return filePath.split("/").pop()?.replace(".ts", "").replace(".js", "");
6
6
  }
7
- export async function getFunctionFromFilePath(filePath, kind, profileBuilders = []) {
7
+ export async function getFunctionFromFilePath(projectDir, filePath, kind, profileBuilders = []) {
8
8
  if (!fs.existsSync(filePath)) {
9
9
  throw new Error(`Cannot load function from file ${filePath}: file doesn't exist`);
10
10
  }
11
11
  else if (!fs.statSync(filePath).isFile()) {
12
12
  throw new Error(`Cannot load function from file ${filePath}: path is not a file`);
13
13
  }
14
- const bundle = await rollup({
15
- input: [filePath],
16
- external: ["@jitsu/functions-lib"],
17
- logLevel: "silent",
14
+ const result = await esbuild.transform(fs.readFileSync(filePath, "utf-8"), {
15
+ loader: filePath.endsWith(".ts") ? "ts" : "js",
16
+ format: "cjs",
17
+ platform: "node",
18
18
  });
19
- const output = await bundle.generate({
20
- file: filePath,
21
- format: "commonjs",
22
- });
23
- const exports = {};
24
- eval(output.output[0].code);
25
- assertDefined(exports.default, `Function from ${filePath} doesn't have default export. Exported symbols: ${Object.keys(exports)}`);
26
- assertTrue(typeof exports.default === "function", `Default export from ${filePath} is not a function`);
27
- let name = exports.config?.name || exports.config?.slug || getSlug(filePath);
28
- let id = exports.config?.id;
19
+ const code = result.code;
20
+ const module = { exports: {} };
21
+ const exports = module.exports;
22
+ const require = (id) => {
23
+ return {};
24
+ };
25
+ eval(code);
26
+ const moduleExports = module.exports;
27
+ assertDefined(moduleExports.default, `Function from ${filePath} doesn't have default export. Exported symbols: ${Object.keys(moduleExports)}`);
28
+ assertTrue(typeof moduleExports.default === "function", `Default export from ${filePath} is not a function`);
29
+ let name = moduleExports.config?.name || moduleExports.config?.slug || getSlug(filePath);
30
+ let id = moduleExports.config?.id;
29
31
  if (kind === "profile") {
30
- const profileBuilderId = exports.config?.profileBuilderId;
32
+ const profileBuilderId = moduleExports.config?.profileBuilderId;
31
33
  const profileBuilder = profileBuilders.find(pb => pb.id === profileBuilderId);
32
34
  if (!profileBuilder) {
33
35
  throw new Error(`Cannot find profile builder with id ${profileBuilderId} for profile function ${filePath}. Please setup Profile Builder in UI first.`);
@@ -39,12 +41,12 @@ export async function getFunctionFromFilePath(filePath, kind, profileBuilders =
39
41
  }
40
42
  }
41
43
  return {
42
- func: exports.default,
44
+ func: moduleExports.default,
43
45
  meta: {
44
- slug: exports.config?.slug || getSlug(filePath),
46
+ slug: moduleExports.config?.slug || getSlug(filePath),
45
47
  id: id,
46
48
  name: name,
47
- description: exports.config?.description,
49
+ description: moduleExports.config?.description,
48
50
  },
49
51
  };
50
52
  }
@@ -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
+ }