jitsu-cli 2.14.0-canary.20260128.21613fb → 2.14.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/compiled/package.json +13 -3
- 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 +54 -17
- package/compiled/src/commands/spec.js +31 -0
- package/compiled/src/index.js +21 -2
- 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/dotted.js +34 -0
- package/compiled/src/lib/renderer.js +33 -0
- package/compiled/src/lib/spec.js +11 -0
- package/dist/main.js +6034 -2702
- package/dist/main.js.map +4 -4
- package/package.json +20 -10
- 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 +93 -18
- 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/dotted.ts +43 -0
- package/src/lib/renderer.ts +44 -0
- package/src/lib/spec.ts +32 -0
package/bin/jitsu
ADDED
package/compiled/package.json
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jitsu-cli",
|
|
3
|
-
"version": "2.14.0
|
|
3
|
+
"version": "2.14.0",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/jitsucom/jitsu",
|
|
7
|
+
"directory": "cli/jitsu-cli"
|
|
8
|
+
},
|
|
4
9
|
"description": "",
|
|
5
10
|
"author": "Jitsu Dev Team <dev@jitsu.com>",
|
|
6
11
|
"publishConfig": {
|
|
7
12
|
"access": "public"
|
|
8
13
|
},
|
|
9
14
|
"bin": {
|
|
10
|
-
"jitsu-cli": "./bin/jitsu-cli"
|
|
15
|
+
"jitsu-cli": "./bin/jitsu-cli",
|
|
16
|
+
"jitsu": "./bin/jitsu"
|
|
11
17
|
},
|
|
12
18
|
"license": "MIT",
|
|
13
19
|
"private": false,
|
|
@@ -15,6 +21,7 @@
|
|
|
15
21
|
"clean": "rm -rf ./dist ./compiled",
|
|
16
22
|
"typecheck": "tsc --noEmit",
|
|
17
23
|
"build": "tsc && tsx build.mts",
|
|
24
|
+
"cli": "tsx src/index.ts",
|
|
18
25
|
"run": "pnpm build && node dist/main.js",
|
|
19
26
|
"login": "pnpm build && node dist/main.js login",
|
|
20
27
|
"init child": "pnpm build && node dist/main.js init",
|
|
@@ -48,12 +55,15 @@
|
|
|
48
55
|
"etag": "^1.8.1",
|
|
49
56
|
"inquirer": "^9.2.11",
|
|
50
57
|
"jest": "^29.7.0",
|
|
58
|
+
"js-yaml": "^4.2.0",
|
|
59
|
+
"@types/js-yaml": "^4.0.9",
|
|
51
60
|
"json5": "^2.2.3",
|
|
52
61
|
"juava": "workspace:*",
|
|
53
62
|
"lodash": "catalog:",
|
|
54
63
|
"prismjs": "^1.30.0",
|
|
55
64
|
"semver": "^7.5.4",
|
|
56
65
|
"tsx": "catalog:",
|
|
57
|
-
"vitest": "catalog:"
|
|
66
|
+
"vitest": "catalog:",
|
|
67
|
+
"vite": "^6.4.3"
|
|
58
68
|
}
|
|
59
69
|
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { ApiClient, ApiError } from "../../lib/api-client";
|
|
2
|
+
import { readDefaultWorkspace, resolveAuth } from "../../lib/auth-file";
|
|
3
|
+
import { buildBody } from "../../lib/body-builder";
|
|
4
|
+
import { consumeBodyFields } from "../../lib/body-fields";
|
|
5
|
+
import { print } from "../../lib/renderer";
|
|
6
|
+
async function resolveWorkspaceId(client, idOrSlug) {
|
|
7
|
+
try {
|
|
8
|
+
const ws = await client.request({
|
|
9
|
+
method: "GET",
|
|
10
|
+
path: `/api/workspace/${encodeURIComponent(idOrSlug)}`,
|
|
11
|
+
});
|
|
12
|
+
return ws.id;
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
if (e instanceof ApiError && e.status === 404) {
|
|
16
|
+
throw new Error(`Workspace '${idOrSlug}' not found (or you don't have access)`);
|
|
17
|
+
}
|
|
18
|
+
throw e;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function gatherBody(opts) {
|
|
22
|
+
const fields = consumeBodyFields();
|
|
23
|
+
return buildBody({ file: opts.file, json: opts.json, fields });
|
|
24
|
+
}
|
|
25
|
+
function requireWorkspace(opts) {
|
|
26
|
+
if (opts.workspace)
|
|
27
|
+
return opts.workspace;
|
|
28
|
+
const fallback = readDefaultWorkspace();
|
|
29
|
+
if (fallback)
|
|
30
|
+
return fallback;
|
|
31
|
+
throw new Error("--workspace / -w is required (or set a default via `jitsu set-default-workspace <id-or-slug>`)");
|
|
32
|
+
}
|
|
33
|
+
async function requireResolvedWorkspaceId(opts, client) {
|
|
34
|
+
return resolveWorkspaceId(client, requireWorkspace(opts));
|
|
35
|
+
}
|
|
36
|
+
export async function runList(resource, opts) {
|
|
37
|
+
const auth = resolveAuth(opts);
|
|
38
|
+
const client = new ApiClient(auth);
|
|
39
|
+
switch (resource.kind) {
|
|
40
|
+
case "workspace": {
|
|
41
|
+
const data = await client.request({ method: "GET", path: "/api/workspace" });
|
|
42
|
+
print(data, opts.output);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
case "configObject": {
|
|
46
|
+
const ws = await requireResolvedWorkspaceId(opts, client);
|
|
47
|
+
const data = await client.request({
|
|
48
|
+
method: "GET",
|
|
49
|
+
path: `/api/${encodeURIComponent(ws)}/config/${resource.type}`,
|
|
50
|
+
});
|
|
51
|
+
print(data.objects ?? data, opts.output);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
case "link": {
|
|
55
|
+
const ws = await requireResolvedWorkspaceId(opts, client);
|
|
56
|
+
const data = await client.request({
|
|
57
|
+
method: "GET",
|
|
58
|
+
path: `/api/${encodeURIComponent(ws)}/config/link`,
|
|
59
|
+
});
|
|
60
|
+
print(data.links ?? data, opts.output);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
case "profile-builder": {
|
|
64
|
+
const ws = await requireResolvedWorkspaceId(opts, client);
|
|
65
|
+
const data = await client.request({
|
|
66
|
+
method: "GET",
|
|
67
|
+
path: `/api/${encodeURIComponent(ws)}/config/profile-builder`,
|
|
68
|
+
});
|
|
69
|
+
print(data.profileBuilders ?? data, opts.output);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export async function runGet(resource, id, opts) {
|
|
75
|
+
const auth = resolveAuth(opts);
|
|
76
|
+
const client = new ApiClient(auth);
|
|
77
|
+
switch (resource.kind) {
|
|
78
|
+
case "workspace": {
|
|
79
|
+
const data = await client.request({ method: "GET", path: `/api/workspace/${encodeURIComponent(id)}` });
|
|
80
|
+
print(data, opts.output);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
case "configObject": {
|
|
84
|
+
const ws = await requireResolvedWorkspaceId(opts, client);
|
|
85
|
+
const data = await client.request({
|
|
86
|
+
method: "GET",
|
|
87
|
+
path: `/api/${encodeURIComponent(ws)}/config/${resource.type}/${encodeURIComponent(id)}`,
|
|
88
|
+
});
|
|
89
|
+
print(data, opts.output);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
default:
|
|
93
|
+
throw new Error(`get is not supported for ${resource.noun}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export async function runCreate(resource, opts) {
|
|
97
|
+
const auth = resolveAuth(opts);
|
|
98
|
+
const client = new ApiClient(auth);
|
|
99
|
+
const body = gatherBody(opts) ?? {};
|
|
100
|
+
switch (resource.kind) {
|
|
101
|
+
case "workspace": {
|
|
102
|
+
const data = await client.request({ method: "POST", path: "/api/workspace", body });
|
|
103
|
+
print(data, opts.output);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
case "configObject": {
|
|
107
|
+
const ws = await requireResolvedWorkspaceId(opts, client);
|
|
108
|
+
const data = await client.request({
|
|
109
|
+
method: "POST",
|
|
110
|
+
path: `/api/${encodeURIComponent(ws)}/config/${resource.type}`,
|
|
111
|
+
body,
|
|
112
|
+
});
|
|
113
|
+
print(data, opts.output);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
case "link": {
|
|
117
|
+
const ws = await requireResolvedWorkspaceId(opts, client);
|
|
118
|
+
const data = await client.request({
|
|
119
|
+
method: "POST",
|
|
120
|
+
path: `/api/${encodeURIComponent(ws)}/config/link`,
|
|
121
|
+
body,
|
|
122
|
+
});
|
|
123
|
+
print(data, opts.output);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
case "profile-builder": {
|
|
127
|
+
const ws = await requireResolvedWorkspaceId(opts, client);
|
|
128
|
+
const data = await client.request({
|
|
129
|
+
method: "POST",
|
|
130
|
+
path: `/api/${encodeURIComponent(ws)}/config/profile-builder`,
|
|
131
|
+
body,
|
|
132
|
+
});
|
|
133
|
+
print(data, opts.output);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
export async function runUpdate(resource, id, opts) {
|
|
139
|
+
const auth = resolveAuth(opts);
|
|
140
|
+
const client = new ApiClient(auth);
|
|
141
|
+
const body = gatherBody(opts);
|
|
142
|
+
if (body === undefined) {
|
|
143
|
+
throw new Error("update requires at least one of -f, --json, or --field.path=value flags");
|
|
144
|
+
}
|
|
145
|
+
switch (resource.kind) {
|
|
146
|
+
case "workspace": {
|
|
147
|
+
if (!id)
|
|
148
|
+
throw new Error("workspace id or slug is required");
|
|
149
|
+
const data = await client.request({
|
|
150
|
+
method: "PUT",
|
|
151
|
+
path: `/api/workspace/${encodeURIComponent(id)}`,
|
|
152
|
+
body,
|
|
153
|
+
});
|
|
154
|
+
print(data, opts.output);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
case "configObject": {
|
|
158
|
+
const ws = await requireResolvedWorkspaceId(opts, client);
|
|
159
|
+
if (!id)
|
|
160
|
+
throw new Error("id is required");
|
|
161
|
+
const data = await client.request({
|
|
162
|
+
method: "PUT",
|
|
163
|
+
path: `/api/${encodeURIComponent(ws)}/config/${resource.type}/${encodeURIComponent(id)}`,
|
|
164
|
+
body,
|
|
165
|
+
});
|
|
166
|
+
print(data ?? { ok: true }, opts.output);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
case "link": {
|
|
170
|
+
const ws = await requireResolvedWorkspaceId(opts, client);
|
|
171
|
+
const finalBody = mergePositionalId(body, id, "id");
|
|
172
|
+
const data = await client.request({
|
|
173
|
+
method: "PUT",
|
|
174
|
+
path: `/api/${encodeURIComponent(ws)}/config/link`,
|
|
175
|
+
body: finalBody,
|
|
176
|
+
});
|
|
177
|
+
print(data, opts.output);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
case "profile-builder": {
|
|
181
|
+
const ws = await requireResolvedWorkspaceId(opts, client);
|
|
182
|
+
if (!id)
|
|
183
|
+
throw new Error("id is required");
|
|
184
|
+
const finalBody = mergeNestedPositionalId(body, id, "profileBuilder", "id");
|
|
185
|
+
const data = await client.request({
|
|
186
|
+
method: "PUT",
|
|
187
|
+
path: `/api/${encodeURIComponent(ws)}/config/profile-builder`,
|
|
188
|
+
body: finalBody,
|
|
189
|
+
});
|
|
190
|
+
print(data, opts.output);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function mergePositionalId(body, id, key) {
|
|
196
|
+
if (id === undefined)
|
|
197
|
+
return body;
|
|
198
|
+
if (body && typeof body === "object" && body[key] !== undefined && body[key] !== id) {
|
|
199
|
+
throw new Error(`positional id '${id}' conflicts with body.${key} '${body[key]}'`);
|
|
200
|
+
}
|
|
201
|
+
return { ...(body && typeof body === "object" ? body : {}), [key]: id };
|
|
202
|
+
}
|
|
203
|
+
function mergeNestedPositionalId(body, id, outer, inner) {
|
|
204
|
+
const base = body && typeof body === "object" ? body : {};
|
|
205
|
+
const nested = base[outer] && typeof base[outer] === "object" ? base[outer] : {};
|
|
206
|
+
if (nested[inner] !== undefined && nested[inner] !== id) {
|
|
207
|
+
throw new Error(`positional id '${id}' conflicts with body.${outer}.${inner} '${nested[inner]}'`);
|
|
208
|
+
}
|
|
209
|
+
return { ...base, [outer]: { ...nested, [inner]: id } };
|
|
210
|
+
}
|
|
211
|
+
export async function runDelete(resource, id, opts) {
|
|
212
|
+
const auth = resolveAuth(opts);
|
|
213
|
+
const client = new ApiClient(auth);
|
|
214
|
+
switch (resource.kind) {
|
|
215
|
+
case "workspace": {
|
|
216
|
+
if (!id)
|
|
217
|
+
throw new Error("workspace id is required");
|
|
218
|
+
const data = await client.request({
|
|
219
|
+
method: "DELETE",
|
|
220
|
+
path: "/api/workspace",
|
|
221
|
+
body: { workspaceId: id },
|
|
222
|
+
});
|
|
223
|
+
print(data, opts.output);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
case "configObject": {
|
|
227
|
+
const ws = await requireResolvedWorkspaceId(opts, client);
|
|
228
|
+
if (!id)
|
|
229
|
+
throw new Error("id is required");
|
|
230
|
+
const query = {};
|
|
231
|
+
if (opts.cascade)
|
|
232
|
+
query.cascade = "true";
|
|
233
|
+
if (opts.strict)
|
|
234
|
+
query.strict = "true";
|
|
235
|
+
const data = await client.request({
|
|
236
|
+
method: "DELETE",
|
|
237
|
+
path: `/api/${encodeURIComponent(ws)}/config/${resource.type}/${encodeURIComponent(id)}`,
|
|
238
|
+
query,
|
|
239
|
+
});
|
|
240
|
+
print(data ?? { ok: true }, opts.output);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
case "link": {
|
|
244
|
+
const ws = await requireResolvedWorkspaceId(opts, client);
|
|
245
|
+
const query = {};
|
|
246
|
+
if (id) {
|
|
247
|
+
query.id = id;
|
|
248
|
+
}
|
|
249
|
+
else if (opts.from && opts.to) {
|
|
250
|
+
query.fromId = opts.from;
|
|
251
|
+
query.toId = opts.to;
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
throw new Error("connection delete requires either <id> or both --from and --to");
|
|
255
|
+
}
|
|
256
|
+
const data = await client.request({
|
|
257
|
+
method: "DELETE",
|
|
258
|
+
path: `/api/${encodeURIComponent(ws)}/config/link`,
|
|
259
|
+
query,
|
|
260
|
+
});
|
|
261
|
+
print(data ?? { ok: true }, opts.output);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
case "profile-builder": {
|
|
265
|
+
const ws = await requireResolvedWorkspaceId(opts, client);
|
|
266
|
+
if (!id)
|
|
267
|
+
throw new Error("id is required");
|
|
268
|
+
const data = await client.request({
|
|
269
|
+
method: "DELETE",
|
|
270
|
+
path: `/api/${encodeURIComponent(ws)}/config/profile-builder`,
|
|
271
|
+
query: { id },
|
|
272
|
+
});
|
|
273
|
+
print(data ?? { ok: true }, opts.output);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
export async function runTest(resource, opts) {
|
|
279
|
+
if (resource.kind !== "configObject" || !resource.supportsTest) {
|
|
280
|
+
throw new Error(`test is not supported for ${resource.noun}`);
|
|
281
|
+
}
|
|
282
|
+
const auth = resolveAuth(opts);
|
|
283
|
+
const client = new ApiClient(auth);
|
|
284
|
+
const ws = await requireResolvedWorkspaceId(opts, client);
|
|
285
|
+
const body = gatherBody(opts) ?? {};
|
|
286
|
+
const data = await client.request({
|
|
287
|
+
method: "POST",
|
|
288
|
+
path: `/api/${encodeURIComponent(ws)}/config/${resource.type}/test`,
|
|
289
|
+
body,
|
|
290
|
+
});
|
|
291
|
+
print(data, opts.output);
|
|
292
|
+
}
|
|
@@ -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
|
+
}
|