jitsu-cli 1.10.4 → 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.
- package/bin/jitsu +3 -0
- package/build.mts +39 -0
- package/compiled/package.json +26 -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 +90 -58
- 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 +39 -23
- 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/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 +45727 -86292
- package/dist/main.js.map +7 -1
- package/package.json +26 -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 +96 -20
- package/src/commands/login.ts +3 -1
- package/src/commands/spec.ts +32 -0
- package/src/index.ts +34 -17
- 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/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,52 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const shared_1 = require("./shared");
|
|
10
|
-
const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
|
|
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";
|
|
14
9
|
function readLoginFile() {
|
|
15
|
-
const configFile = `${
|
|
16
|
-
if (!
|
|
17
|
-
console.error(
|
|
10
|
+
const configFile = `${homedir()}/.jitsu/jitsu-cli.json`;
|
|
11
|
+
if (!existsSync(configFile)) {
|
|
12
|
+
console.error(red("Please login first with `jitsu-cli login` command or provide --apikey option"));
|
|
18
13
|
process.exit(1);
|
|
19
14
|
}
|
|
20
|
-
return JSON.parse(
|
|
15
|
+
return JSON.parse(readFileSync(configFile, { encoding: "utf-8" }));
|
|
21
16
|
}
|
|
22
|
-
async function deploy({ dir, workspace, name: names, ...params }) {
|
|
23
|
-
const { packageJson, projectDir } = await
|
|
17
|
+
export async function deploy({ dir, workspace, name: names, ...params }) {
|
|
18
|
+
const { packageJson, projectDir } = await loadPackageJson(dir || process.cwd());
|
|
24
19
|
const { host, apikey } = params.apikey
|
|
25
20
|
? { apikey: params.apikey, host: params.host || "https://use.jitsu.com" }
|
|
26
21
|
: readLoginFile();
|
|
27
|
-
console.log(`Deploying ${
|
|
28
|
-
const res = await (
|
|
22
|
+
console.log(`Deploying ${b(packageJson.name)} project.${names && names.length > 0 ? ` (selected functions: ${names.join(",")})` : ""}`);
|
|
23
|
+
const res = await fetch(`${host}/api/workspace`, {
|
|
29
24
|
method: "GET",
|
|
30
25
|
headers: {
|
|
31
26
|
Authorization: `Bearer ${apikey}`,
|
|
32
27
|
},
|
|
33
28
|
});
|
|
34
29
|
if (!res.ok) {
|
|
35
|
-
console.error(
|
|
30
|
+
console.error(red(`Cannot get workspace list:\n${b(await res.text())}`));
|
|
36
31
|
process.exit(1);
|
|
37
32
|
}
|
|
38
33
|
const workspaces = (await res.json());
|
|
39
34
|
let workspaceId = workspace;
|
|
40
35
|
if (!workspace) {
|
|
41
36
|
if (workspaces.length === 0) {
|
|
42
|
-
console.error(`${
|
|
37
|
+
console.error(`${red("No workspaces found")}`);
|
|
43
38
|
process.exit(1);
|
|
44
39
|
}
|
|
45
40
|
else if (workspaces.length === 1) {
|
|
46
41
|
workspaceId = workspaces[0].id;
|
|
47
42
|
}
|
|
48
43
|
else {
|
|
49
|
-
workspaceId = (await
|
|
44
|
+
workspaceId = (await inquirer.prompt([
|
|
50
45
|
{
|
|
51
46
|
type: "list",
|
|
52
47
|
name: "workspaceId",
|
|
@@ -62,7 +57,7 @@ async function deploy({ dir, workspace, name: names, ...params }) {
|
|
|
62
57
|
const workspaceObj = workspaces.find(w => w.id === workspaceId);
|
|
63
58
|
const workspaceName = workspaceObj?.name;
|
|
64
59
|
if (!workspaceId || !workspaceName) {
|
|
65
|
-
console.error(
|
|
60
|
+
console.error(red(`Workspace with id ${workspaceId} not found`));
|
|
66
61
|
process.exit(1);
|
|
67
62
|
}
|
|
68
63
|
await deployFunctions({ ...params, host, apikey, name: names }, projectDir, packageJson, workspaceObj, "function");
|
|
@@ -71,11 +66,15 @@ async function deploy({ dir, workspace, name: names, ...params }) {
|
|
|
71
66
|
async function deployFunctions({ host, apikey, name: names }, projectDir, packageJson, workspace, kind) {
|
|
72
67
|
const selected = names ? names.flatMap(n => n.split(",")).map(n => n.trim()) : undefined;
|
|
73
68
|
const dir = `dist/${kind}s`;
|
|
74
|
-
const functionsDir =
|
|
75
|
-
|
|
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
|
+
}
|
|
74
|
+
const functionsFiles = readdirSync(functionsDir);
|
|
76
75
|
if (functionsFiles.length === 0) {
|
|
77
|
-
console.warn(
|
|
78
|
-
|
|
76
|
+
console.warn(`No ${kind} files found in ${b(dir)}, skipping. Please make sure that you have built the project.`);
|
|
77
|
+
return;
|
|
79
78
|
}
|
|
80
79
|
const selectedFiles = [];
|
|
81
80
|
if (selected) {
|
|
@@ -85,7 +84,7 @@ async function deployFunctions({ host, apikey, name: names }, projectDir, packag
|
|
|
85
84
|
selectedFiles.push(file);
|
|
86
85
|
}
|
|
87
86
|
else {
|
|
88
|
-
console.error(
|
|
87
|
+
console.error(red(`Can't find function file ${b(file)} in ${b(dir)} directory. Please make sure that you have built the project.`));
|
|
89
88
|
process.exit(1);
|
|
90
89
|
}
|
|
91
90
|
}
|
|
@@ -95,49 +94,80 @@ async function deployFunctions({ host, apikey, name: names }, projectDir, packag
|
|
|
95
94
|
}
|
|
96
95
|
let profileBuilders = [];
|
|
97
96
|
if (kind == "profile") {
|
|
98
|
-
const res = await (
|
|
97
|
+
const res = await fetch(`${host}/api/${workspace.id}/config/profile-builder`, {
|
|
99
98
|
method: "GET",
|
|
100
99
|
headers: {
|
|
101
100
|
Authorization: `Bearer ${apikey}`,
|
|
102
101
|
},
|
|
103
102
|
});
|
|
104
103
|
if (!res.ok) {
|
|
105
|
-
console.error(
|
|
104
|
+
console.error(red(`Cannot get profile builders list:\n${b(await res.text())}`));
|
|
106
105
|
process.exit(1);
|
|
107
106
|
}
|
|
108
107
|
profileBuilders = (await res.json()).profileBuilders;
|
|
109
108
|
}
|
|
109
|
+
const existingFunctions = await fetchExistingFunctions({ host, apikey, workspaceId: workspace.id });
|
|
110
110
|
for (const file of selectedFiles) {
|
|
111
|
-
console.log(`${
|
|
112
|
-
await deployFunction({ host, apikey }, packageJson, workspace, kind,
|
|
111
|
+
console.log(`${b(`𝑓`)} Deploying function ${b(path.basename(file))} to workspace ${workspace.name} (${host}/${workspace.slug || workspace.id})`);
|
|
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);
|
|
113
148
|
}
|
|
149
|
+
entry.slug = newSlug;
|
|
150
|
+
if (newSlug)
|
|
151
|
+
cache.bySlug.set(newSlug, entry);
|
|
114
152
|
}
|
|
115
|
-
async function deployFunction({ host, apikey }, packageJson, workspace, kind, file, profileBuilders = []
|
|
116
|
-
|
|
117
|
-
|
|
153
|
+
async function deployFunction(projectDir, { host, apikey }, packageJson, workspace, kind, file, profileBuilders = [], existingFunctions = {
|
|
154
|
+
bySlug: new Map(),
|
|
155
|
+
byId: new Map(),
|
|
156
|
+
}) {
|
|
157
|
+
const code = readFileSync(file, "utf-8");
|
|
158
|
+
const wrapped = await getFunctionFromFilePath(projectDir, file, kind, profileBuilders);
|
|
118
159
|
const meta = wrapped.meta;
|
|
119
160
|
if (meta) {
|
|
120
161
|
console.log(` meta: slug=${meta.slug}, name=${meta.name || "not set"}`);
|
|
121
162
|
}
|
|
122
163
|
else {
|
|
123
|
-
console.log(`File ${
|
|
164
|
+
console.log(`File ${b(path.basename(file))} doesn't have function meta information. ${red("Skipping")}`);
|
|
124
165
|
return;
|
|
125
166
|
}
|
|
126
167
|
let existingFunctionId;
|
|
127
168
|
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
|
-
}
|
|
169
|
+
existingFunctionId =
|
|
170
|
+
existingFunctions.bySlug.get(meta.slug)?.id ?? (meta.id ? existingFunctions.byId.get(meta.id)?.id : undefined);
|
|
141
171
|
}
|
|
142
172
|
let functionPayload = {};
|
|
143
173
|
if (kind === "profile") {
|
|
@@ -152,8 +182,8 @@ async function deployFunction({ host, apikey }, packageJson, workspace, kind, fi
|
|
|
152
182
|
};
|
|
153
183
|
}
|
|
154
184
|
if (!existingFunctionId) {
|
|
155
|
-
const id = (
|
|
156
|
-
const res = await (
|
|
185
|
+
const id = cuid();
|
|
186
|
+
const res = await fetch(`${host}/api/${workspace.id}/config/function`, {
|
|
157
187
|
method: "POST",
|
|
158
188
|
headers: {
|
|
159
189
|
Authorization: `Bearer ${apikey}`,
|
|
@@ -172,16 +202,17 @@ async function deployFunction({ host, apikey }, packageJson, workspace, kind, fi
|
|
|
172
202
|
}),
|
|
173
203
|
});
|
|
174
204
|
if (!res.ok) {
|
|
175
|
-
console.error(
|
|
205
|
+
console.error(red(`Cannot add function to workspace:\n${b(await res.text())}`));
|
|
176
206
|
process.exit(1);
|
|
177
207
|
}
|
|
178
208
|
else {
|
|
179
|
-
|
|
209
|
+
cacheAfterCreate(existingFunctions, id, meta.slug);
|
|
210
|
+
console.log(`Function ${b(meta.name)} was successfully added to workspace ${workspace.name} with id: ${b(id)}`);
|
|
180
211
|
}
|
|
181
212
|
}
|
|
182
213
|
else {
|
|
183
214
|
const id = existingFunctionId;
|
|
184
|
-
const res = await (
|
|
215
|
+
const res = await fetch(`${host}/api/${workspace.id}/config/function/${id}`, {
|
|
185
216
|
method: "PUT",
|
|
186
217
|
headers: {
|
|
187
218
|
Authorization: `Bearer ${apikey}`,
|
|
@@ -199,11 +230,12 @@ async function deployFunction({ host, apikey }, packageJson, workspace, kind, fi
|
|
|
199
230
|
}),
|
|
200
231
|
});
|
|
201
232
|
if (!res.ok) {
|
|
202
|
-
console.error(
|
|
233
|
+
console.error(red(`⚠ Cannot deploy function ${b(meta.slug)}(${id}):\n${b(await res.text())}`));
|
|
203
234
|
process.exit(1);
|
|
204
235
|
}
|
|
205
236
|
else {
|
|
206
|
-
|
|
237
|
+
cacheAfterUpdate(existingFunctions, id, meta.slug);
|
|
238
|
+
console.log(`${green(`✓`)} ${b(meta.name)} deployed successfully!`);
|
|
207
239
|
}
|
|
208
240
|
}
|
|
209
241
|
}
|
|
@@ -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
|
}
|