workato-dev-api 1.0.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/.env.example +1 -0
- package/README.md +104 -0
- package/cli.js +171 -0
- package/lib.js +323 -0
- package/package.json +22 -0
- package/test/cli.test.js +989 -0
package/.env.example
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
WORKATO_API_TOKEN=your_token_here
|
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# workato-dev-api
|
|
2
|
+
|
|
3
|
+
A zero-dependency CLI for the [Workato Developer API](https://docs.workato.com/workato-api.html). Read and edit recipes, connections, data tables, projects, folders, and jobs from your terminal.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install -g workato-dev-api
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or use without installing:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npx workato-dev-api <command>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Authentication
|
|
18
|
+
|
|
19
|
+
Set `WORKATO_API_TOKEN` in a `.env` file. The CLI checks these locations in order, with **later files winning**:
|
|
20
|
+
|
|
21
|
+
1. `<package-dir>/.env` — lowest priority (rarely used)
|
|
22
|
+
2. `~/.env` — your home directory default
|
|
23
|
+
3. `./.env` (cwd) — **highest priority**, project-specific override
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
# .env
|
|
27
|
+
WORKATO_API_TOKEN=your_token_here
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
You can also export it directly in your shell environment.
|
|
31
|
+
|
|
32
|
+
## Commands
|
|
33
|
+
|
|
34
|
+
### Read
|
|
35
|
+
|
|
36
|
+
| Command | Description |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `workato get <recipe_id>` | Fetch recipe code JSON → saved to `recipe_<id>_code.json` |
|
|
39
|
+
| `workato list-recipes` | List recipes. Filters: `--folder <id>`, `--project <id>`, `--page <n>` |
|
|
40
|
+
| `workato list-projects` | List all projects |
|
|
41
|
+
| `workato list-folders` | List folders. Filter: `--parent <id>` |
|
|
42
|
+
| `workato list-connections` | List connections. Filter: `--folder <id>` |
|
|
43
|
+
| `workato list-data-tables` | List data tables. Filter: `--project <id>` |
|
|
44
|
+
| `workato get-data-table <id>` | Fetch data table schema and details |
|
|
45
|
+
| `workato get-jobs <recipe_id>` | List recent jobs. Filters: `--limit <n>`, `--status <status>` |
|
|
46
|
+
| `workato get-job <recipe_id> <job_id>` | Fetch a single job |
|
|
47
|
+
|
|
48
|
+
### Write
|
|
49
|
+
|
|
50
|
+
| Command | Description |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `workato create "<name>" <code.json>` | Create a recipe from a full code JSON file |
|
|
53
|
+
| `workato create-api-trigger "<name>"` | Create a recipe with a bare API Platform trigger |
|
|
54
|
+
| `workato update-step <recipe_id> <step_as_id> <patch.json>` | Deep-merge a patch into one step (by `as` ID) |
|
|
55
|
+
| `workato put-code <recipe_id> <code.json>` | Replace an entire recipe's code |
|
|
56
|
+
| `workato start <recipe_id>` | Start a recipe |
|
|
57
|
+
| `workato stop <recipe_id>` | Stop a recipe |
|
|
58
|
+
| `workato delete <recipe_id>` | Delete a recipe |
|
|
59
|
+
|
|
60
|
+
## Examples
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
# Fetch recipe code and save to file
|
|
64
|
+
workato get 167603
|
|
65
|
+
|
|
66
|
+
# List all recipes in a project
|
|
67
|
+
workato list-recipes --project 14318
|
|
68
|
+
|
|
69
|
+
# List jobs that failed
|
|
70
|
+
workato get-jobs 167603 --limit 20 --status failed
|
|
71
|
+
|
|
72
|
+
# Start a recipe
|
|
73
|
+
workato start 167603
|
|
74
|
+
|
|
75
|
+
# Patch a single step's input fields
|
|
76
|
+
cat > patch.json <<'EOF'
|
|
77
|
+
{
|
|
78
|
+
"input": {
|
|
79
|
+
"language": "fr"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
EOF
|
|
83
|
+
workato update-step 167603 5df21cfd patch.json
|
|
84
|
+
|
|
85
|
+
# Replace entire recipe code
|
|
86
|
+
workato put-code 167603 recipe_167603_code.json
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Recipe code structure
|
|
90
|
+
|
|
91
|
+
A recipe's code is a JSON object. The top-level object is the **trigger** step; action steps live in `code.block[]`. Each step has a unique `as` field (8-char hex) used for cross-step wiring (datapills).
|
|
92
|
+
|
|
93
|
+
`workato get <id>` saves the code to `recipe_<id>_code.json` so you can inspect and edit it before pushing back with `put-code`.
|
|
94
|
+
|
|
95
|
+
## Development
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
git clone ...
|
|
99
|
+
cd workato-dev-api
|
|
100
|
+
cp .env.example .env # add your token
|
|
101
|
+
npm test # runs 88 unit tests, no network required
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Tests use Node's built-in `node:test` runner — no extra dependencies.
|
package/cli.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const {
|
|
7
|
+
loadEnv,
|
|
8
|
+
cmdGet, cmdListRecipes, cmdListProjects, cmdListFolders,
|
|
9
|
+
cmdListConnections, cmdListDataTables, cmdGetDataTable,
|
|
10
|
+
cmdGetJobs, cmdGetJob,
|
|
11
|
+
cmdCreate, cmdCreateApiTrigger, cmdUpdateStep, cmdPutCode,
|
|
12
|
+
cmdStart, cmdStop, cmdDelete,
|
|
13
|
+
} = require('./lib');
|
|
14
|
+
|
|
15
|
+
// Load order — last one wins, so highest-priority sources go last:
|
|
16
|
+
// package dir → home dir → cwd (cwd always wins)
|
|
17
|
+
loadEnv(path.join(__dirname, '.env'));
|
|
18
|
+
loadEnv(path.join(os.homedir(), '.env'));
|
|
19
|
+
loadEnv(path.join(process.cwd(), '.env'));
|
|
20
|
+
|
|
21
|
+
if (!process.env.WORKATO_API_TOKEN) {
|
|
22
|
+
console.error('Error: WORKATO_API_TOKEN not set.\nCreate a .env file in your current directory with:\n WORKATO_API_TOKEN=your_token_here');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Parse argv: separate --flag value pairs from positional args
|
|
27
|
+
function parseArgs(argv) {
|
|
28
|
+
const positional = [];
|
|
29
|
+
const flags = {};
|
|
30
|
+
for (let i = 0; i < argv.length; i++) {
|
|
31
|
+
if (argv[i].startsWith('--')) {
|
|
32
|
+
const key = argv[i].slice(2);
|
|
33
|
+
// treat next token as value unless it's also a flag or missing
|
|
34
|
+
const next = argv[i + 1];
|
|
35
|
+
if (next !== undefined && !next.startsWith('--')) {
|
|
36
|
+
flags[key] = next;
|
|
37
|
+
i++;
|
|
38
|
+
} else {
|
|
39
|
+
flags[key] = true;
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
positional.push(argv[i]);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return { positional, flags };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function usage() {
|
|
49
|
+
console.error(`
|
|
50
|
+
workato <command> [options]
|
|
51
|
+
|
|
52
|
+
Read commands:
|
|
53
|
+
get <recipe_id> Fetch recipe code → recipe_<id>_code.json
|
|
54
|
+
list-recipes [--folder <id>] [--project <id>] [--page <n>]
|
|
55
|
+
list-projects
|
|
56
|
+
list-folders [--parent <id>]
|
|
57
|
+
list-connections [--folder <id>]
|
|
58
|
+
list-data-tables [--project <id>]
|
|
59
|
+
get-data-table <id> Fetch data table schema/details
|
|
60
|
+
get-jobs <recipe_id> [--limit <n>] [--status <status>]
|
|
61
|
+
get-job <recipe_id> <job_id>
|
|
62
|
+
|
|
63
|
+
Write commands:
|
|
64
|
+
create "<name>" <code.json> Create recipe from full code JSON
|
|
65
|
+
create-api-trigger "<name>" Create recipe with a bare API Platform trigger
|
|
66
|
+
update-step <recipe_id> <step_as_id> <patch.json> Deep-merge patch into a step
|
|
67
|
+
put-code <recipe_id> <code.json> Replace entire recipe code
|
|
68
|
+
start <recipe_id> Start a recipe
|
|
69
|
+
stop <recipe_id> Stop a recipe
|
|
70
|
+
delete <recipe_id> Delete a recipe
|
|
71
|
+
|
|
72
|
+
Environment:
|
|
73
|
+
WORKATO_API_TOKEN Required. Set in a .env file in your current directory.
|
|
74
|
+
`.trim());
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const [,, cmd, ...rawArgs] = process.argv;
|
|
79
|
+
const { positional: args, flags } = parseArgs(rawArgs);
|
|
80
|
+
|
|
81
|
+
(async () => {
|
|
82
|
+
try {
|
|
83
|
+
switch (cmd) {
|
|
84
|
+
// ── Read ──────────────────────────────────────────────────────────────
|
|
85
|
+
case 'get':
|
|
86
|
+
if (!args[0]) usage();
|
|
87
|
+
await cmdGet(args[0]);
|
|
88
|
+
break;
|
|
89
|
+
|
|
90
|
+
case 'list-recipes':
|
|
91
|
+
await cmdListRecipes({
|
|
92
|
+
folder_id: flags.folder,
|
|
93
|
+
project_id: flags.project,
|
|
94
|
+
page: flags.page,
|
|
95
|
+
});
|
|
96
|
+
break;
|
|
97
|
+
|
|
98
|
+
case 'list-projects':
|
|
99
|
+
await cmdListProjects();
|
|
100
|
+
break;
|
|
101
|
+
|
|
102
|
+
case 'list-folders':
|
|
103
|
+
await cmdListFolders({ parent_id: flags.parent });
|
|
104
|
+
break;
|
|
105
|
+
|
|
106
|
+
case 'list-connections':
|
|
107
|
+
await cmdListConnections({ folder_id: flags.folder });
|
|
108
|
+
break;
|
|
109
|
+
|
|
110
|
+
case 'list-data-tables':
|
|
111
|
+
await cmdListDataTables({ project_id: flags.project });
|
|
112
|
+
break;
|
|
113
|
+
|
|
114
|
+
case 'get-data-table':
|
|
115
|
+
if (!args[0]) usage();
|
|
116
|
+
await cmdGetDataTable(args[0]);
|
|
117
|
+
break;
|
|
118
|
+
|
|
119
|
+
case 'get-jobs':
|
|
120
|
+
if (!args[0]) usage();
|
|
121
|
+
await cmdGetJobs(args[0], { limit: flags.limit, status: flags.status });
|
|
122
|
+
break;
|
|
123
|
+
|
|
124
|
+
case 'get-job':
|
|
125
|
+
if (!args[0] || !args[1]) usage();
|
|
126
|
+
await cmdGetJob(args[0], args[1]);
|
|
127
|
+
break;
|
|
128
|
+
|
|
129
|
+
// ── Write ─────────────────────────────────────────────────────────────
|
|
130
|
+
case 'create':
|
|
131
|
+
if (!args[0] || !args[1]) usage();
|
|
132
|
+
await cmdCreate(args[0], args[1]);
|
|
133
|
+
break;
|
|
134
|
+
|
|
135
|
+
case 'create-api-trigger':
|
|
136
|
+
await cmdCreateApiTrigger(args[0] || 'New API Recipe');
|
|
137
|
+
break;
|
|
138
|
+
|
|
139
|
+
case 'update-step':
|
|
140
|
+
if (!args[0] || !args[1] || !args[2]) usage();
|
|
141
|
+
await cmdUpdateStep(args[0], args[1], args[2]);
|
|
142
|
+
break;
|
|
143
|
+
|
|
144
|
+
case 'put-code':
|
|
145
|
+
if (!args[0] || !args[1]) usage();
|
|
146
|
+
await cmdPutCode(args[0], args[1]);
|
|
147
|
+
break;
|
|
148
|
+
|
|
149
|
+
case 'start':
|
|
150
|
+
if (!args[0]) usage();
|
|
151
|
+
await cmdStart(args[0]);
|
|
152
|
+
break;
|
|
153
|
+
|
|
154
|
+
case 'stop':
|
|
155
|
+
if (!args[0]) usage();
|
|
156
|
+
await cmdStop(args[0]);
|
|
157
|
+
break;
|
|
158
|
+
|
|
159
|
+
case 'delete':
|
|
160
|
+
if (!args[0]) usage();
|
|
161
|
+
await cmdDelete(args[0]);
|
|
162
|
+
break;
|
|
163
|
+
|
|
164
|
+
default:
|
|
165
|
+
usage();
|
|
166
|
+
}
|
|
167
|
+
} catch (err) {
|
|
168
|
+
console.error('Error:', err.message);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
})();
|
package/lib.js
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
|
|
7
|
+
// ── Config ────────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
const _config = {
|
|
10
|
+
baseUrl: 'https://app.trial.workato.com/api',
|
|
11
|
+
token: null,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function loadEnv(envPath) {
|
|
15
|
+
const p = envPath ?? path.join(process.cwd(), '.env');
|
|
16
|
+
if (!fs.existsSync(p)) return;
|
|
17
|
+
for (const line of fs.readFileSync(p, 'utf8').split('\n')) {
|
|
18
|
+
const match = line.match(/^([^#=]+)=(.*)$/);
|
|
19
|
+
if (match) process.env[match[1].trim()] = match[2].trim();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function setConfig(cfg) {
|
|
24
|
+
Object.assign(_config, cfg);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getToken() {
|
|
28
|
+
return _config.token || process.env.WORKATO_API_TOKEN;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ── HTTP helpers ──────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
async function apiGet(urlPath) {
|
|
34
|
+
const res = await fetch(`${_config.baseUrl}${urlPath}`, {
|
|
35
|
+
headers: { 'Authorization': `Bearer ${getToken()}` },
|
|
36
|
+
});
|
|
37
|
+
if (!res.ok) throw new Error(`GET ${urlPath} failed: ${res.status} ${await res.text()}`);
|
|
38
|
+
return res.json();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function apiPost(urlPath, body) {
|
|
42
|
+
const res = await fetch(`${_config.baseUrl}${urlPath}`, {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: {
|
|
45
|
+
'Authorization': `Bearer ${getToken()}`,
|
|
46
|
+
'Content-Type': 'application/json',
|
|
47
|
+
},
|
|
48
|
+
body: JSON.stringify(body),
|
|
49
|
+
});
|
|
50
|
+
if (!res.ok) throw new Error(`POST ${urlPath} failed: ${res.status} ${await res.text()}`);
|
|
51
|
+
return res.json();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function apiPut(urlPath, body) {
|
|
55
|
+
const res = await fetch(`${_config.baseUrl}${urlPath}`, {
|
|
56
|
+
method: 'PUT',
|
|
57
|
+
headers: {
|
|
58
|
+
'Authorization': `Bearer ${getToken()}`,
|
|
59
|
+
'Content-Type': 'application/json',
|
|
60
|
+
},
|
|
61
|
+
body: JSON.stringify(body),
|
|
62
|
+
});
|
|
63
|
+
if (!res.ok) throw new Error(`PUT ${urlPath} failed: ${res.status} ${await res.text()}`);
|
|
64
|
+
return res.json();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function apiDelete(urlPath) {
|
|
68
|
+
const res = await fetch(`${_config.baseUrl}${urlPath}`, {
|
|
69
|
+
method: 'DELETE',
|
|
70
|
+
headers: { 'Authorization': `Bearer ${getToken()}` },
|
|
71
|
+
});
|
|
72
|
+
if (!res.ok) throw new Error(`DELETE ${urlPath} failed: ${res.status} ${await res.text()}`);
|
|
73
|
+
const text = await res.text();
|
|
74
|
+
return text ? JSON.parse(text) : {};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── Recipe code helpers ───────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
// Recursively find a step by its `as` value within block arrays
|
|
80
|
+
function findStep(block, asId) {
|
|
81
|
+
for (const step of block) {
|
|
82
|
+
if (step.as === asId) return step;
|
|
83
|
+
if (step.block) {
|
|
84
|
+
const found = findStep(step.block, asId);
|
|
85
|
+
if (found) return found;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Deep merge source into target (mutates target). Arrays/primitives are replaced, objects are merged.
|
|
92
|
+
function deepMerge(target, source) {
|
|
93
|
+
for (const [key, val] of Object.entries(source)) {
|
|
94
|
+
if (
|
|
95
|
+
val && typeof val === 'object' && !Array.isArray(val) &&
|
|
96
|
+
target[key] && typeof target[key] === 'object' && !Array.isArray(target[key])
|
|
97
|
+
) {
|
|
98
|
+
deepMerge(target[key], val);
|
|
99
|
+
} else {
|
|
100
|
+
target[key] = val;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function extractCode(data) {
|
|
106
|
+
const recipe = data.recipe ?? data;
|
|
107
|
+
return JSON.parse(recipe.code);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function randomHex(n) {
|
|
111
|
+
return crypto.randomBytes(n).toString('hex').slice(0, n);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function randomUUID() {
|
|
115
|
+
return crypto.randomUUID();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function apiTriggerCode() {
|
|
119
|
+
return {
|
|
120
|
+
number: 0,
|
|
121
|
+
provider: 'workato_api_platform',
|
|
122
|
+
name: 'receive_request',
|
|
123
|
+
as: randomHex(8),
|
|
124
|
+
title: null,
|
|
125
|
+
description: null,
|
|
126
|
+
keyword: 'trigger',
|
|
127
|
+
dynamicPickListSelection: {},
|
|
128
|
+
toggleCfg: {},
|
|
129
|
+
input: {
|
|
130
|
+
request: { content_type: 'json', schema: '[]' },
|
|
131
|
+
response: { content_type: 'json', responses: [] },
|
|
132
|
+
},
|
|
133
|
+
extended_output_schema: [],
|
|
134
|
+
extended_input_schema: [],
|
|
135
|
+
block: [],
|
|
136
|
+
uuid: randomUUID(),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function apiTriggerConfig() {
|
|
141
|
+
return [
|
|
142
|
+
{ keyword: 'application', name: 'workato_api_platform', provider: 'workato_api_platform', skip_validation: false, account_id: null },
|
|
143
|
+
];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ── Read commands ─────────────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
async function cmdGet(recipeId) {
|
|
149
|
+
const data = await apiGet(`/recipes/${recipeId}`);
|
|
150
|
+
const code = extractCode(data);
|
|
151
|
+
const outFile = `recipe_${recipeId}_code.json`;
|
|
152
|
+
fs.writeFileSync(outFile, JSON.stringify(code, null, 2));
|
|
153
|
+
console.log(JSON.stringify(code, null, 2));
|
|
154
|
+
console.error(`\nSaved to ${outFile}`);
|
|
155
|
+
return code;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function cmdListRecipes(opts = {}) {
|
|
159
|
+
const params = new URLSearchParams();
|
|
160
|
+
if (opts.folder_id) params.set('folder_id', opts.folder_id);
|
|
161
|
+
if (opts.project_id) params.set('project_id', opts.project_id);
|
|
162
|
+
if (opts.page) params.set('page', opts.page);
|
|
163
|
+
if (opts.per_page) params.set('per_page', opts.per_page);
|
|
164
|
+
const qs = params.toString();
|
|
165
|
+
const data = await apiGet(`/recipes${qs ? '?' + qs : ''}`);
|
|
166
|
+
console.log(JSON.stringify(data, null, 2));
|
|
167
|
+
return data;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function cmdListProjects() {
|
|
171
|
+
const data = await apiGet('/projects');
|
|
172
|
+
console.log(JSON.stringify(data, null, 2));
|
|
173
|
+
return data;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function cmdListFolders(opts = {}) {
|
|
177
|
+
const params = new URLSearchParams();
|
|
178
|
+
if (opts.parent_id) params.set('parent_id', opts.parent_id);
|
|
179
|
+
const qs = params.toString();
|
|
180
|
+
const data = await apiGet(`/folders${qs ? '?' + qs : ''}`);
|
|
181
|
+
console.log(JSON.stringify(data, null, 2));
|
|
182
|
+
return data;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function cmdListConnections(opts = {}) {
|
|
186
|
+
const params = new URLSearchParams();
|
|
187
|
+
if (opts.folder_id) params.set('folder_id', opts.folder_id);
|
|
188
|
+
const qs = params.toString();
|
|
189
|
+
const data = await apiGet(`/connections${qs ? '?' + qs : ''}`);
|
|
190
|
+
console.log(JSON.stringify(data, null, 2));
|
|
191
|
+
return data;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function cmdListDataTables(opts = {}) {
|
|
195
|
+
const params = new URLSearchParams();
|
|
196
|
+
if (opts.project_id) params.set('project_id', opts.project_id);
|
|
197
|
+
const qs = params.toString();
|
|
198
|
+
const data = await apiGet(`/data_tables${qs ? '?' + qs : ''}`);
|
|
199
|
+
console.log(JSON.stringify(data, null, 2));
|
|
200
|
+
return data;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function cmdGetDataTable(id) {
|
|
204
|
+
const data = await apiGet(`/data_tables/${id}`);
|
|
205
|
+
console.log(JSON.stringify(data, null, 2));
|
|
206
|
+
return data;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function cmdGetJobs(recipeId, opts = {}) {
|
|
210
|
+
const params = new URLSearchParams();
|
|
211
|
+
if (opts.limit) params.set('per_page', opts.limit);
|
|
212
|
+
if (opts.status) params.set('status', opts.status);
|
|
213
|
+
const qs = params.toString();
|
|
214
|
+
const data = await apiGet(`/recipes/${recipeId}/jobs${qs ? '?' + qs : ''}`);
|
|
215
|
+
console.log(JSON.stringify(data, null, 2));
|
|
216
|
+
return data;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function cmdGetJob(recipeId, jobId) {
|
|
220
|
+
const data = await apiGet(`/recipes/${recipeId}/jobs/${jobId}`);
|
|
221
|
+
console.log(JSON.stringify(data, null, 2));
|
|
222
|
+
return data;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ── Write commands ────────────────────────────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
async function cmdCreate(name, codeFile) {
|
|
228
|
+
const code = JSON.parse(fs.readFileSync(codeFile, 'utf8'));
|
|
229
|
+
const result = await apiPost('/recipes', {
|
|
230
|
+
recipe: { name, code: JSON.stringify(code) },
|
|
231
|
+
});
|
|
232
|
+
console.log(JSON.stringify(result, null, 2));
|
|
233
|
+
const id = (result.recipe ?? result).id;
|
|
234
|
+
console.error(`\nCreated recipe id: ${id}`);
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function cmdCreateApiTrigger(name) {
|
|
239
|
+
const code = apiTriggerCode();
|
|
240
|
+
const config = apiTriggerConfig();
|
|
241
|
+
const result = await apiPost('/recipes', {
|
|
242
|
+
recipe: { name, code: JSON.stringify(code), config: JSON.stringify(config) },
|
|
243
|
+
});
|
|
244
|
+
console.log(JSON.stringify(result, null, 2));
|
|
245
|
+
const id = (result.recipe ?? result).id;
|
|
246
|
+
console.error(`\nCreated recipe id: ${id}`);
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function cmdUpdateStep(recipeId, asId, patchFile) {
|
|
251
|
+
const patch = JSON.parse(fs.readFileSync(patchFile, 'utf8'));
|
|
252
|
+
|
|
253
|
+
const data = await apiGet(`/recipes/${recipeId}`);
|
|
254
|
+
const code = extractCode(data);
|
|
255
|
+
|
|
256
|
+
let step;
|
|
257
|
+
if (code.as === asId) {
|
|
258
|
+
step = code;
|
|
259
|
+
} else {
|
|
260
|
+
step = findStep(code.block ?? [], asId);
|
|
261
|
+
}
|
|
262
|
+
if (!step) throw new Error(`Step with as="${asId}" not found`);
|
|
263
|
+
|
|
264
|
+
console.error(`Found step: ${step.keyword} (as: ${asId})`);
|
|
265
|
+
deepMerge(step, patch);
|
|
266
|
+
|
|
267
|
+
const result = await apiPut(`/recipes/${recipeId}`, {
|
|
268
|
+
recipe: { code: JSON.stringify(code) },
|
|
269
|
+
});
|
|
270
|
+
console.log(JSON.stringify(result, null, 2));
|
|
271
|
+
console.error(`\nStep ${asId} updated successfully.`);
|
|
272
|
+
return result;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function cmdPutCode(recipeId, codeFile) {
|
|
276
|
+
const code = JSON.parse(fs.readFileSync(codeFile, 'utf8'));
|
|
277
|
+
const result = await apiPut(`/recipes/${recipeId}`, {
|
|
278
|
+
recipe: { code: JSON.stringify(code) },
|
|
279
|
+
});
|
|
280
|
+
console.log(JSON.stringify(result, null, 2));
|
|
281
|
+
console.error('\nRecipe code replaced successfully.');
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function cmdStart(recipeId) {
|
|
286
|
+
const result = await apiPut(`/recipes/${recipeId}/start`, {});
|
|
287
|
+
console.log(JSON.stringify(result, null, 2));
|
|
288
|
+
console.error(`\nRecipe ${recipeId} started.`);
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function cmdStop(recipeId) {
|
|
293
|
+
const result = await apiPut(`/recipes/${recipeId}/stop`, {});
|
|
294
|
+
console.log(JSON.stringify(result, null, 2));
|
|
295
|
+
console.error(`\nRecipe ${recipeId} stopped.`);
|
|
296
|
+
return result;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function cmdDelete(recipeId) {
|
|
300
|
+
const result = await apiDelete(`/recipes/${recipeId}`);
|
|
301
|
+
console.log(JSON.stringify(result, null, 2));
|
|
302
|
+
console.error(`\nRecipe ${recipeId} deleted.`);
|
|
303
|
+
return result;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ── Exports ───────────────────────────────────────────────────────────────────
|
|
307
|
+
|
|
308
|
+
module.exports = {
|
|
309
|
+
// config
|
|
310
|
+
loadEnv, setConfig, getToken,
|
|
311
|
+
// http
|
|
312
|
+
apiGet, apiPost, apiPut, apiDelete,
|
|
313
|
+
// helpers
|
|
314
|
+
findStep, deepMerge, extractCode,
|
|
315
|
+
apiTriggerCode, apiTriggerConfig,
|
|
316
|
+
// read commands
|
|
317
|
+
cmdGet, cmdListRecipes, cmdListProjects, cmdListFolders,
|
|
318
|
+
cmdListConnections, cmdListDataTables, cmdGetDataTable,
|
|
319
|
+
cmdGetJobs, cmdGetJob,
|
|
320
|
+
// write commands
|
|
321
|
+
cmdCreate, cmdCreateApiTrigger, cmdUpdateStep, cmdPutCode,
|
|
322
|
+
cmdStart, cmdStop, cmdDelete,
|
|
323
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "workato-dev-api",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI for the Workato Developer API — recipes, connections, data tables, and more",
|
|
5
|
+
"bin": {
|
|
6
|
+
"workato": "cli.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"test": "node --test test/cli.test.js"
|
|
10
|
+
},
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=18"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"workato",
|
|
16
|
+
"cli",
|
|
17
|
+
"api",
|
|
18
|
+
"recipes",
|
|
19
|
+
"automation"
|
|
20
|
+
],
|
|
21
|
+
"license": "MIT"
|
|
22
|
+
}
|