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,148 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { red } from "../../lib/chalk-code-highlight";
|
|
3
|
+
import { DEFAULT_OUTPUT, SUPPORTED_OUTPUTS } from "../../lib/renderer";
|
|
4
|
+
import { resources, verbsFor } from "./resources";
|
|
5
|
+
import { runCreate, runDelete, runGet, runList, runTest, runUpdate } from "./handlers";
|
|
6
|
+
function withCommonOpts(cmd) {
|
|
7
|
+
return cmd
|
|
8
|
+
.option("-w, --workspace <id-or-slug>", "Target workspace id or slug")
|
|
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 in form keyId:secret (overrides ~/.jitsu/jitsu-cli.json)");
|
|
12
|
+
}
|
|
13
|
+
function withBodyOpts(cmd) {
|
|
14
|
+
return cmd
|
|
15
|
+
.option("-f, --file <path>", "Read body from yaml or json file (use `-` for stdin)")
|
|
16
|
+
.option("--json <json>", "Inline JSON body")
|
|
17
|
+
.addHelpText("after", [
|
|
18
|
+
"",
|
|
19
|
+
"Body fields can also be set ad-hoc via --<path>=<value> flags.",
|
|
20
|
+
"Examples:",
|
|
21
|
+
" --name=my-destination",
|
|
22
|
+
" --destinationType=postgres",
|
|
23
|
+
" --credentials.host=db.example.com",
|
|
24
|
+
" --credentials.password=secret",
|
|
25
|
+
' --credentials.keys=\'["a","b"]\'',
|
|
26
|
+
'Values starting with [, {, " or matching number/boolean/null are parsed as JSON;',
|
|
27
|
+
"everything else is a plain string. -f, --json, and --field flags merge in that order.",
|
|
28
|
+
].join("\n"));
|
|
29
|
+
}
|
|
30
|
+
function action(fn) {
|
|
31
|
+
return async (...args) => {
|
|
32
|
+
try {
|
|
33
|
+
await fn(...args);
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
37
|
+
console.error(red(`Error: ${msg}`));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function buildLeaf(resource, verb) {
|
|
43
|
+
switch (verb) {
|
|
44
|
+
case "list": {
|
|
45
|
+
const cmd = withCommonOpts(new Command("list"))
|
|
46
|
+
.description(`List ${resource.noun}`)
|
|
47
|
+
.action(action(async (opts) => runList(resource, opts)));
|
|
48
|
+
return cmd;
|
|
49
|
+
}
|
|
50
|
+
case "get": {
|
|
51
|
+
const idLabel = resource.kind === "workspace" ? "<id-or-slug>" : "<id>";
|
|
52
|
+
const cmd = withCommonOpts(new Command("get"))
|
|
53
|
+
.description(`Get a single ${singular(resource)} by id`)
|
|
54
|
+
.argument(idLabel, `Identifier of the ${singular(resource)}`)
|
|
55
|
+
.action(action(async (id, opts) => runGet(resource, id, opts)));
|
|
56
|
+
return cmd;
|
|
57
|
+
}
|
|
58
|
+
case "create": {
|
|
59
|
+
const cmd = withBodyOpts(withCommonOpts(new Command("create")))
|
|
60
|
+
.description(`Create a ${singular(resource)}`)
|
|
61
|
+
.action(action(async (opts) => runCreate(resource, opts)));
|
|
62
|
+
return cmd;
|
|
63
|
+
}
|
|
64
|
+
case "update": {
|
|
65
|
+
const idLabel = resource.kind === "link" ? "[id]" : "<id>";
|
|
66
|
+
const idDesc = resource.kind === "link"
|
|
67
|
+
? `Link id (optional — connections are upserts identified by fromId+toId)`
|
|
68
|
+
: `Identifier of the ${singular(resource)}`;
|
|
69
|
+
const cmd = withBodyOpts(withCommonOpts(new Command("update")))
|
|
70
|
+
.description(`Update a ${singular(resource)} (deep-merge into existing)`)
|
|
71
|
+
.argument(idLabel, idDesc)
|
|
72
|
+
.action(action(async (id, opts) => runUpdate(resource, id, opts)));
|
|
73
|
+
return cmd;
|
|
74
|
+
}
|
|
75
|
+
case "delete": {
|
|
76
|
+
const idLabel = resource.kind === "link" ? "[id]" : "<id>";
|
|
77
|
+
let cmd = withCommonOpts(new Command("delete"))
|
|
78
|
+
.alias("rm")
|
|
79
|
+
.description(`Delete a ${singular(resource)}`)
|
|
80
|
+
.argument(idLabel, `Identifier of the ${singular(resource)}`);
|
|
81
|
+
if (resource.kind === "configObject") {
|
|
82
|
+
cmd = cmd
|
|
83
|
+
.option("--cascade", "Also delete linked connections that reference this object")
|
|
84
|
+
.option("--strict", "Refuse to delete if linked connections exist");
|
|
85
|
+
}
|
|
86
|
+
if (resource.kind === "link") {
|
|
87
|
+
cmd = cmd
|
|
88
|
+
.option("--from <streamOrServiceId>", "Source id (use with --to instead of <id>)")
|
|
89
|
+
.option("--to <destinationId>", "Destination id (use with --from instead of <id>)");
|
|
90
|
+
}
|
|
91
|
+
cmd = cmd.action(action(async (id, opts) => runDelete(resource, id, opts)));
|
|
92
|
+
return cmd;
|
|
93
|
+
}
|
|
94
|
+
case "test": {
|
|
95
|
+
const cmd = withBodyOpts(withCommonOpts(new Command("test")))
|
|
96
|
+
.description(`Test connectivity for a ${singular(resource)} configuration`)
|
|
97
|
+
.action(action(async (opts) => runTest(resource, opts)));
|
|
98
|
+
return cmd;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function singular(r) {
|
|
103
|
+
return r.aliases[0] ?? r.noun.replace(/s$/, "");
|
|
104
|
+
}
|
|
105
|
+
export function buildConfigCommand() {
|
|
106
|
+
const config = new Command("config")
|
|
107
|
+
.description("Manage workspace configuration objects (destinations, streams, connections, ...)")
|
|
108
|
+
.addHelpText("after", [
|
|
109
|
+
"",
|
|
110
|
+
"Two equivalent invocation styles are supported:",
|
|
111
|
+
" jitsu config <noun> <verb> [args] e.g. jitsu config destinations list -w ws",
|
|
112
|
+
" jitsu config <verb> <noun> [args] e.g. jitsu config list destinations -w ws",
|
|
113
|
+
"",
|
|
114
|
+
"Resources:",
|
|
115
|
+
...resources.map(r => ` ${r.noun.padEnd(20)} ${r.description}`),
|
|
116
|
+
].join("\n"));
|
|
117
|
+
for (const resource of resources) {
|
|
118
|
+
const nounCmd = new Command(resource.noun).description(resource.description);
|
|
119
|
+
for (const alias of resource.aliases)
|
|
120
|
+
nounCmd.alias(alias);
|
|
121
|
+
for (const verb of verbsFor(resource.kind)) {
|
|
122
|
+
nounCmd.addCommand(buildLeaf(resource, verb));
|
|
123
|
+
}
|
|
124
|
+
if (resource.supportsTest) {
|
|
125
|
+
nounCmd.addCommand(buildLeaf(resource, "test"));
|
|
126
|
+
}
|
|
127
|
+
config.addCommand(nounCmd);
|
|
128
|
+
}
|
|
129
|
+
const allVerbs = ["list", "get", "create", "update", "delete", "test"];
|
|
130
|
+
for (const verb of allVerbs) {
|
|
131
|
+
const verbCmd = new Command(verb).description(`${capitalize(verb)} a configuration object (alias for the noun-first form)`);
|
|
132
|
+
for (const resource of resources) {
|
|
133
|
+
const applicable = verbsFor(resource.kind).includes(verb) || (verb === "test" && resource.supportsTest);
|
|
134
|
+
if (!applicable)
|
|
135
|
+
continue;
|
|
136
|
+
const leaf = buildLeaf(resource, verb);
|
|
137
|
+
leaf.name(resource.noun);
|
|
138
|
+
for (const alias of resource.aliases)
|
|
139
|
+
leaf.alias(alias);
|
|
140
|
+
verbCmd.addCommand(leaf);
|
|
141
|
+
}
|
|
142
|
+
config.addCommand(verbCmd);
|
|
143
|
+
}
|
|
144
|
+
return config;
|
|
145
|
+
}
|
|
146
|
+
function capitalize(s) {
|
|
147
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
148
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -1,68 +1,80 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const cuid_1 = tslib_1.__importDefault(require("cuid"));
|
|
12
|
-
const chalk_code_highlight_1 = require("../lib/chalk-code-highlight");
|
|
13
|
-
const compiled_function_1 = require("../lib/compiled-function");
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import inquirer from "inquirer";
|
|
4
|
+
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
5
|
+
import { loadPackageJson } from "./shared";
|
|
6
|
+
import cuid from "cuid";
|
|
7
|
+
import { b, green, red } from "../lib/chalk-code-highlight";
|
|
8
|
+
import { getFunctionFromFilePath } from "../lib/compiled-function";
|
|
9
|
+
import { loadProjectConfig } from "../lib/project-config";
|
|
10
|
+
import { readDefaultWorkspace } from "../lib/auth-file";
|
|
14
11
|
function readLoginFile() {
|
|
15
|
-
const configFile = `${
|
|
16
|
-
if (!
|
|
17
|
-
console.error(
|
|
12
|
+
const configFile = `${homedir()}/.jitsu/jitsu-cli.json`;
|
|
13
|
+
if (!existsSync(configFile)) {
|
|
14
|
+
console.error(red("Please login first with `jitsu-cli login` command or provide --apikey option"));
|
|
18
15
|
process.exit(1);
|
|
19
16
|
}
|
|
20
|
-
return JSON.parse(
|
|
17
|
+
return JSON.parse(readFileSync(configFile, { encoding: "utf-8" }));
|
|
21
18
|
}
|
|
22
|
-
async function deploy({ dir, workspace, name: names, ...params }) {
|
|
23
|
-
const { packageJson, projectDir } = await
|
|
19
|
+
export async function deploy({ dir, workspace, name: names, ...params }) {
|
|
20
|
+
const { packageJson, projectDir } = await loadPackageJson(dir || process.cwd());
|
|
24
21
|
const { host, apikey } = params.apikey
|
|
25
22
|
? { apikey: params.apikey, host: params.host || "https://use.jitsu.com" }
|
|
26
23
|
: readLoginFile();
|
|
27
|
-
console.log(`Deploying ${
|
|
28
|
-
const res = await (
|
|
24
|
+
console.log(`Deploying ${b(packageJson.name)} project.${names && names.length > 0 ? ` (selected functions: ${names.join(",")})` : ""}`);
|
|
25
|
+
const res = await fetch(`${host}/api/workspace`, {
|
|
29
26
|
method: "GET",
|
|
30
27
|
headers: {
|
|
31
28
|
Authorization: `Bearer ${apikey}`,
|
|
32
29
|
},
|
|
33
30
|
});
|
|
34
31
|
if (!res.ok) {
|
|
35
|
-
console.error(
|
|
32
|
+
console.error(red(`Cannot get workspace list:\n${b(await res.text())}`));
|
|
36
33
|
process.exit(1);
|
|
37
34
|
}
|
|
38
35
|
const workspaces = (await res.json());
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
const findWorkspace = (ref) => (ref ? workspaces.find(w => w.id === ref || w.slug === ref) : undefined);
|
|
37
|
+
const projectConfig = loadProjectConfig(projectDir, packageJson);
|
|
38
|
+
let workspaceObj;
|
|
39
|
+
if (workspace) {
|
|
40
|
+
workspaceObj = findWorkspace(workspace);
|
|
41
|
+
if (!workspaceObj) {
|
|
42
|
+
console.error(red(`Workspace '${b(workspace)}' not found (or you don't have access)`));
|
|
43
43
|
process.exit(1);
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
const softRef = projectConfig.workspace ?? readDefaultWorkspace();
|
|
48
|
+
workspaceObj = findWorkspace(softRef);
|
|
49
|
+
if (!workspaceObj) {
|
|
50
|
+
if (softRef) {
|
|
51
|
+
console.warn(`Configured workspace ${b(softRef)} not found or not accessible. Selecting manually.`);
|
|
52
|
+
}
|
|
53
|
+
if (workspaces.length === 0) {
|
|
54
|
+
console.error(`${red("No workspaces found")}`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
else if (workspaces.length === 1) {
|
|
58
|
+
workspaceObj = workspaces[0];
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const workspaceId = (await inquirer.prompt([
|
|
62
|
+
{
|
|
63
|
+
type: "list",
|
|
64
|
+
name: "workspaceId",
|
|
65
|
+
message: `Select workspace:`,
|
|
66
|
+
choices: workspaces.map(w => ({
|
|
67
|
+
name: `${w.name} (${w.id})`,
|
|
68
|
+
value: w.id,
|
|
69
|
+
})),
|
|
70
|
+
},
|
|
71
|
+
])).workspaceId;
|
|
72
|
+
workspaceObj = findWorkspace(workspaceId);
|
|
73
|
+
}
|
|
60
74
|
}
|
|
61
75
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (!workspaceId || !workspaceName) {
|
|
65
|
-
console.error((0, chalk_code_highlight_1.red)(`Workspace with id ${workspaceId} not found`));
|
|
76
|
+
if (!workspaceObj?.id || !workspaceObj?.name) {
|
|
77
|
+
console.error(red(`Workspace not found`));
|
|
66
78
|
process.exit(1);
|
|
67
79
|
}
|
|
68
80
|
await deployFunctions({ ...params, host, apikey, name: names }, projectDir, packageJson, workspaceObj, "function");
|
|
@@ -71,11 +83,15 @@ async function deploy({ dir, workspace, name: names, ...params }) {
|
|
|
71
83
|
async function deployFunctions({ host, apikey, name: names }, projectDir, packageJson, workspace, kind) {
|
|
72
84
|
const selected = names ? names.flatMap(n => n.split(",")).map(n => n.trim()) : undefined;
|
|
73
85
|
const dir = `dist/${kind}s`;
|
|
74
|
-
const functionsDir =
|
|
75
|
-
|
|
86
|
+
const functionsDir = path.resolve(projectDir, dir);
|
|
87
|
+
if (!existsSync(functionsDir)) {
|
|
88
|
+
console.warn(`No ${b(dir)} directory found, skipping ${kind}s. Please make sure that you have built the project.`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const functionsFiles = readdirSync(functionsDir);
|
|
76
92
|
if (functionsFiles.length === 0) {
|
|
77
|
-
console.warn(
|
|
78
|
-
|
|
93
|
+
console.warn(`No ${kind} files found in ${b(dir)}, skipping. Please make sure that you have built the project.`);
|
|
94
|
+
return;
|
|
79
95
|
}
|
|
80
96
|
const selectedFiles = [];
|
|
81
97
|
if (selected) {
|
|
@@ -85,7 +101,7 @@ async function deployFunctions({ host, apikey, name: names }, projectDir, packag
|
|
|
85
101
|
selectedFiles.push(file);
|
|
86
102
|
}
|
|
87
103
|
else {
|
|
88
|
-
console.error(
|
|
104
|
+
console.error(red(`Can't find function file ${b(file)} in ${b(dir)} directory. Please make sure that you have built the project.`));
|
|
89
105
|
process.exit(1);
|
|
90
106
|
}
|
|
91
107
|
}
|
|
@@ -95,49 +111,80 @@ async function deployFunctions({ host, apikey, name: names }, projectDir, packag
|
|
|
95
111
|
}
|
|
96
112
|
let profileBuilders = [];
|
|
97
113
|
if (kind == "profile") {
|
|
98
|
-
const res = await (
|
|
114
|
+
const res = await fetch(`${host}/api/${workspace.id}/config/profile-builder`, {
|
|
99
115
|
method: "GET",
|
|
100
116
|
headers: {
|
|
101
117
|
Authorization: `Bearer ${apikey}`,
|
|
102
118
|
},
|
|
103
119
|
});
|
|
104
120
|
if (!res.ok) {
|
|
105
|
-
console.error(
|
|
121
|
+
console.error(red(`Cannot get profile builders list:\n${b(await res.text())}`));
|
|
106
122
|
process.exit(1);
|
|
107
123
|
}
|
|
108
124
|
profileBuilders = (await res.json()).profileBuilders;
|
|
109
125
|
}
|
|
126
|
+
const existingFunctions = await fetchExistingFunctions({ host, apikey, workspaceId: workspace.id });
|
|
110
127
|
for (const file of selectedFiles) {
|
|
111
|
-
console.log(`${
|
|
112
|
-
await deployFunction({ host, apikey }, packageJson, workspace, kind,
|
|
128
|
+
console.log(`${b(`𝑓`)} Deploying function ${b(path.basename(file))} to workspace ${workspace.name} (${host}/${workspace.slug || workspace.id})`);
|
|
129
|
+
await deployFunction(projectDir, { host, apikey }, packageJson, workspace, kind, path.resolve(functionsDir, file), profileBuilders, existingFunctions);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async function fetchExistingFunctions({ host, apikey, workspaceId, }) {
|
|
133
|
+
const res = await fetch(`${host}/api/${workspaceId}/config/function`, {
|
|
134
|
+
headers: { Authorization: `Bearer ${apikey}` },
|
|
135
|
+
});
|
|
136
|
+
if (!res.ok) {
|
|
137
|
+
console.error(red(`Cannot list existing functions:\n${b(await res.text())}`));
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
const { objects } = (await res.json());
|
|
141
|
+
const bySlug = new Map();
|
|
142
|
+
const byId = new Map();
|
|
143
|
+
for (const f of objects) {
|
|
144
|
+
const entry = { id: f.id, slug: f.slug };
|
|
145
|
+
byId.set(f.id, entry);
|
|
146
|
+
if (f.slug)
|
|
147
|
+
bySlug.set(f.slug, entry);
|
|
113
148
|
}
|
|
149
|
+
return { bySlug, byId };
|
|
114
150
|
}
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
151
|
+
function cacheAfterCreate(cache, id, slug) {
|
|
152
|
+
const entry = { id, slug };
|
|
153
|
+
cache.byId.set(id, entry);
|
|
154
|
+
if (slug)
|
|
155
|
+
cache.bySlug.set(slug, entry);
|
|
156
|
+
}
|
|
157
|
+
function cacheAfterUpdate(cache, id, newSlug) {
|
|
158
|
+
const entry = cache.byId.get(id);
|
|
159
|
+
if (!entry) {
|
|
160
|
+
cacheAfterCreate(cache, id, newSlug);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (entry.slug && entry.slug !== newSlug) {
|
|
164
|
+
cache.bySlug.delete(entry.slug);
|
|
165
|
+
}
|
|
166
|
+
entry.slug = newSlug;
|
|
167
|
+
if (newSlug)
|
|
168
|
+
cache.bySlug.set(newSlug, entry);
|
|
169
|
+
}
|
|
170
|
+
async function deployFunction(projectDir, { host, apikey }, packageJson, workspace, kind, file, profileBuilders = [], existingFunctions = {
|
|
171
|
+
bySlug: new Map(),
|
|
172
|
+
byId: new Map(),
|
|
173
|
+
}) {
|
|
174
|
+
const code = readFileSync(file, "utf-8");
|
|
175
|
+
const wrapped = await getFunctionFromFilePath(projectDir, file, kind, profileBuilders);
|
|
118
176
|
const meta = wrapped.meta;
|
|
119
177
|
if (meta) {
|
|
120
178
|
console.log(` meta: slug=${meta.slug}, name=${meta.name || "not set"}`);
|
|
121
179
|
}
|
|
122
180
|
else {
|
|
123
|
-
console.log(`File ${
|
|
181
|
+
console.log(`File ${b(path.basename(file))} doesn't have function meta information. ${red("Skipping")}`);
|
|
124
182
|
return;
|
|
125
183
|
}
|
|
126
184
|
let existingFunctionId;
|
|
127
185
|
if (meta.slug) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
Authorization: `Bearer ${apikey}`,
|
|
131
|
-
},
|
|
132
|
-
});
|
|
133
|
-
if (!res.ok) {
|
|
134
|
-
console.error((0, chalk_code_highlight_1.red)(`Cannot add function to workspace:\n${(0, chalk_code_highlight_1.b)(await res.text())}`));
|
|
135
|
-
process.exit(1);
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
const existing = (await res.json());
|
|
139
|
-
existingFunctionId = existing.objects.find(f => f.slug === meta.slug || f.id === meta.id)?.id;
|
|
140
|
-
}
|
|
186
|
+
existingFunctionId =
|
|
187
|
+
existingFunctions.bySlug.get(meta.slug)?.id ?? (meta.id ? existingFunctions.byId.get(meta.id)?.id : undefined);
|
|
141
188
|
}
|
|
142
189
|
let functionPayload = {};
|
|
143
190
|
if (kind === "profile") {
|
|
@@ -152,8 +199,8 @@ async function deployFunction({ host, apikey }, packageJson, workspace, kind, fi
|
|
|
152
199
|
};
|
|
153
200
|
}
|
|
154
201
|
if (!existingFunctionId) {
|
|
155
|
-
const id = (
|
|
156
|
-
const res = await (
|
|
202
|
+
const id = cuid();
|
|
203
|
+
const res = await fetch(`${host}/api/${workspace.id}/config/function`, {
|
|
157
204
|
method: "POST",
|
|
158
205
|
headers: {
|
|
159
206
|
Authorization: `Bearer ${apikey}`,
|
|
@@ -172,16 +219,17 @@ async function deployFunction({ host, apikey }, packageJson, workspace, kind, fi
|
|
|
172
219
|
}),
|
|
173
220
|
});
|
|
174
221
|
if (!res.ok) {
|
|
175
|
-
console.error(
|
|
222
|
+
console.error(red(`Cannot add function to workspace:\n${b(await res.text())}`));
|
|
176
223
|
process.exit(1);
|
|
177
224
|
}
|
|
178
225
|
else {
|
|
179
|
-
|
|
226
|
+
cacheAfterCreate(existingFunctions, id, meta.slug);
|
|
227
|
+
console.log(`Function ${b(meta.name)} was successfully added to workspace ${workspace.name} with id: ${b(id)}`);
|
|
180
228
|
}
|
|
181
229
|
}
|
|
182
230
|
else {
|
|
183
231
|
const id = existingFunctionId;
|
|
184
|
-
const res = await (
|
|
232
|
+
const res = await fetch(`${host}/api/${workspace.id}/config/function/${id}`, {
|
|
185
233
|
method: "PUT",
|
|
186
234
|
headers: {
|
|
187
235
|
Authorization: `Bearer ${apikey}`,
|
|
@@ -199,11 +247,12 @@ async function deployFunction({ host, apikey }, packageJson, workspace, kind, fi
|
|
|
199
247
|
}),
|
|
200
248
|
});
|
|
201
249
|
if (!res.ok) {
|
|
202
|
-
console.error(
|
|
250
|
+
console.error(red(`⚠ Cannot deploy function ${b(meta.slug)}(${id}):\n${b(await res.text())}`));
|
|
203
251
|
process.exit(1);
|
|
204
252
|
}
|
|
205
253
|
else {
|
|
206
|
-
|
|
254
|
+
cacheAfterUpdate(existingFunctions, id, meta.slug);
|
|
255
|
+
console.log(`${green(`✓`)} ${b(meta.name)} deployed successfully!`);
|
|
207
256
|
}
|
|
208
257
|
}
|
|
209
258
|
}
|
|
@@ -1,46 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const functions_1 = require("../templates/functions");
|
|
10
|
-
const template_1 = require("../lib/template");
|
|
11
|
-
const version_1 = require("../lib/version");
|
|
12
|
-
async function init(dir, opts) {
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { b } from "../lib/chalk-code-highlight";
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import { functionProjectTemplate } from "../templates/functions";
|
|
6
|
+
import { write } from "../lib/template";
|
|
7
|
+
import { jitsuCliVersion } from "../lib/version";
|
|
8
|
+
export async function init(dir, opts) {
|
|
13
9
|
let projectName;
|
|
14
10
|
if (dir) {
|
|
15
|
-
dir =
|
|
11
|
+
dir = path.resolve(dir);
|
|
16
12
|
if (!fs.existsSync(dir)) {
|
|
17
13
|
fs.mkdirSync(dir);
|
|
18
14
|
}
|
|
19
15
|
else if (fs.readdirSync(dir).length > 0) {
|
|
20
|
-
const msg = `Directory ${
|
|
16
|
+
const msg = `Directory ${b(dir)} is not empty, can't create project there`;
|
|
21
17
|
if (opts?.allowNonEmptyDir) {
|
|
22
|
-
console.warn(`Directory ${
|
|
18
|
+
console.warn(`Directory ${b(dir)} is not empty. Will create project there, files may be overwritten`);
|
|
23
19
|
}
|
|
24
20
|
else {
|
|
25
21
|
console.error(msg);
|
|
26
22
|
process.exit(1);
|
|
27
23
|
}
|
|
28
24
|
}
|
|
29
|
-
projectName =
|
|
25
|
+
projectName = path.basename(dir);
|
|
30
26
|
}
|
|
31
27
|
else {
|
|
32
|
-
projectName = (await
|
|
28
|
+
projectName = (await inquirer.prompt([
|
|
33
29
|
{
|
|
34
30
|
type: "input",
|
|
35
31
|
name: "project",
|
|
36
32
|
message: `Enter project name. It will be used as a package name and directory name:`,
|
|
37
33
|
},
|
|
38
34
|
])).project;
|
|
39
|
-
dir =
|
|
35
|
+
dir = path.resolve(projectName);
|
|
40
36
|
}
|
|
41
|
-
|
|
37
|
+
write(dir, functionProjectTemplate, {
|
|
42
38
|
packageName: projectName,
|
|
43
|
-
jitsuVersion: opts?.jitsuVersion ||
|
|
39
|
+
jitsuVersion: opts?.jitsuVersion || jitsuCliVersion,
|
|
44
40
|
});
|
|
45
|
-
console.log(`Project ${
|
|
41
|
+
console.log(`Project ${b(projectName)} created!`);
|
|
46
42
|
}
|