workato-dev-api 1.0.0 → 1.1.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/CLAUDE.md +153 -0
- package/README.md +20 -0
- package/cli.js +22 -1
- package/lib.js +26 -0
- package/package.json +1 -1
- package/test/cli.test.js +138 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Workato Dev Environment — Claude Notes
|
|
2
|
+
|
|
3
|
+
## CLI: `workato-dev-api`
|
|
4
|
+
|
|
5
|
+
Use `npx workato-dev-api <command>` (or `workato <command>` if installed globally) for all Workato operations — reads and writes alike.
|
|
6
|
+
|
|
7
|
+
Requires `WORKATO_API_TOKEN` in a `.env` file (cwd `.env` takes highest priority). Base URL: `https://app.trial.workato.com/api`.
|
|
8
|
+
|
|
9
|
+
### Commands
|
|
10
|
+
|
|
11
|
+
| Command | Description |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `workato get <recipe_id>` | Fetch recipe code JSON → saved to `recipe_<id>_code.json` |
|
|
14
|
+
| `workato list-recipes` | List recipes. Filters: `--folder <id>`, `--project <id>`, `--page <n>` |
|
|
15
|
+
| `workato list-projects` | List all projects |
|
|
16
|
+
| `workato list-folders` | List folders. Filter: `--parent <id>` |
|
|
17
|
+
| `workato list-connections` | List connections. Filter: `--folder <id>` |
|
|
18
|
+
| `workato list-data-tables` | List data tables. Filter: `--project <id>` |
|
|
19
|
+
| `workato get-data-table <id>` | Fetch data table schema and details |
|
|
20
|
+
| `workato get-jobs <recipe_id>` | List recent jobs. Filters: `--limit <n>`, `--status <status>` |
|
|
21
|
+
| `workato get-job <recipe_id> <job_id>` | Fetch a single job |
|
|
22
|
+
| `workato create "<name>" <code.json>` | Create a recipe from a full code JSON file |
|
|
23
|
+
| `workato create-api-trigger "<name>"` | Create a recipe with a bare API Platform trigger |
|
|
24
|
+
| `workato update-step <recipe_id> <step_as_id> <patch.json>` | Deep-merge a patch into one step (by `as` ID) |
|
|
25
|
+
| `workato put-code <recipe_id> <code.json>` | Replace entire recipe code |
|
|
26
|
+
| `workato start <recipe_id>` | Start a recipe |
|
|
27
|
+
| `workato stop <recipe_id>` | Stop a recipe |
|
|
28
|
+
| `workato delete <recipe_id>` | Delete a recipe |
|
|
29
|
+
|
|
30
|
+
### How `update-step` works
|
|
31
|
+
- Fetches current recipe code
|
|
32
|
+
- Finds the step whose `as` field matches `<step_as_id>` (searches recursively into nested `block` arrays; trigger is the top-level code object)
|
|
33
|
+
- Deep-merges the patch JSON into that step (objects merged, arrays/primitives replaced)
|
|
34
|
+
- PUTs the full updated code back
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Recipe Code Structure
|
|
39
|
+
|
|
40
|
+
A recipe's `code` field is a JSON-stringified object. The top-level object is the **trigger** step; action steps live in `code.block[]`.
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"number": 0,
|
|
45
|
+
"provider": "<provider>",
|
|
46
|
+
"name": "<action_name>",
|
|
47
|
+
"as": "<8-char-hex>", // unique step ID used for wiring
|
|
48
|
+
"keyword": "trigger", // "trigger" | "action" | "if" | "foreach" etc.
|
|
49
|
+
"dynamicPickListSelection": {},
|
|
50
|
+
"toggleCfg": {},
|
|
51
|
+
"input": { ... }, // field values and wiring go here
|
|
52
|
+
"extended_output_schema": [...], // THIS is the output schema — must be specified manually
|
|
53
|
+
"extended_input_schema": [...], // describes available input fields (e.g. data table columns) — must be specified manually
|
|
54
|
+
"block": [...], // nested steps
|
|
55
|
+
"uuid": "<uuid>",
|
|
56
|
+
"title": null,
|
|
57
|
+
"description": null
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
There is no non-extended `output_schema` / `input_schema` — `extended_output_schema` and `extended_input_schema` are the only schema fields. For some built-in connector actions (e.g. OpenAI `transcription`), the output schema is known server-side and the step can omit `extended_output_schema` entirely — downstream steps can still wire from it using the datapill syntax. For custom/dynamic steps (e.g. API Platform trigger, Data Table), you must provide these schemas explicitly.
|
|
62
|
+
|
|
63
|
+
The recipe also has a separate `config` array (JSON-stringified) listing which connections are used:
|
|
64
|
+
```json
|
|
65
|
+
[
|
|
66
|
+
{ "keyword": "application", "name": "<provider>", "provider": "<provider>", "skip_validation": false, "account_id": <connection_id_or_null> }
|
|
67
|
+
]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Field Wiring Syntax
|
|
73
|
+
|
|
74
|
+
All field values in a step's `input` object use one of three forms:
|
|
75
|
+
|
|
76
|
+
### 1. Datapill — reference another step's output
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
"#{_dp('{\"pill_type\":\"output\",\"provider\":\"<provider>\",\"line\":\"<step_as_id>\",\"path\":[\"<field1>\",\"<field2>\"]}')}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- `provider`: the provider of the source step (e.g. `workato_api_platform`, `open_ai`, `workato_db_table`, `google_drive`)
|
|
83
|
+
- `line`: the `as` value (8-char hex) of the source step
|
|
84
|
+
- `path`: JSON array of strings navigating the output schema
|
|
85
|
+
|
|
86
|
+
**Example — wire trigger's `request.file_content` into an OpenAI step:**
|
|
87
|
+
```json
|
|
88
|
+
"file_content": "#{_dp('{\"pill_type\":\"output\",\"provider\":\"workato_api_platform\",\"line\":\"8f52532b\",\"path\":[\"request\",\"file_content\"]}')}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Example — wire OpenAI step's `text` output into a data table column:**
|
|
92
|
+
```json
|
|
93
|
+
"03886fe9_176d_4bdd_9296_d48219b345c8": "#{_dp('{\"pill_type\":\"output\",\"provider\":\"open_ai\",\"line\":\"5df21cfd\",\"path\":[\"text\"]}')}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 2. Formula mode — Workato functions/expressions
|
|
97
|
+
|
|
98
|
+
Prefix the value with `=` inside the string:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
"=\"#{now}\"" // current timestamp
|
|
102
|
+
"=\"#{uuid}\"" // generate a UUID
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 3. Static literal
|
|
106
|
+
|
|
107
|
+
Plain string value — no special syntax:
|
|
108
|
+
```json
|
|
109
|
+
"language": "en",
|
|
110
|
+
"table_id": "3512"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Data Table Column Names
|
|
116
|
+
|
|
117
|
+
Data table column field names in `input.parameters` are **UUID-style strings with underscores**, not human-readable names. You must fetch the recipe's existing code (or the data table schema) to get the correct column key for each field.
|
|
118
|
+
|
|
119
|
+
Example from Audio Transcripts table:
|
|
120
|
+
- `transcript_text` → `03886fe9_176d_4bdd_9296_d48219b345c8`
|
|
121
|
+
- `transcribed_at` → `aa4e76dd_0de1_4f0a_946b_fb0a4e1ecff5`
|
|
122
|
+
- `file_name` → `33ee499b_51cc_4ddf_ba03_6a5b8eca79f5`
|
|
123
|
+
- `file_id` → `0f05f324_040d_4d11_b201_05afaa850729`
|
|
124
|
+
- `recorded_at` → `aefc6da4_93a5_40e9_b933_13c2cac095ac`
|
|
125
|
+
|
|
126
|
+
Always use `workato get-data-table <id>` or read the recipe code to look up the actual column UUIDs before wiring.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Reference Recipe
|
|
131
|
+
|
|
132
|
+
**Transcribe Audio** (ID: 167603) — `https://app.trial.workato.com/recipes/167603-transcribe-audio`
|
|
133
|
+
|
|
134
|
+
This recipe demonstrates complete working wiring across all three step types (API Platform trigger → OpenAI action → Data Table action). Use it as the canonical wiring reference.
|
|
135
|
+
|
|
136
|
+
Trigger `as`: `8f52532b` | OpenAI step `as`: `5df21cfd` | Data Table step `as`: `1614a36d`
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Current Project
|
|
141
|
+
|
|
142
|
+
**"Get Audio Transcript"** — Project ID: `14318`, Folder ID: `20245`
|
|
143
|
+
|
|
144
|
+
Key recipe IDs:
|
|
145
|
+
- `167582` — Get Audio Transcript (callable, RecipeOps trigger)
|
|
146
|
+
- `167583` — Audio File Orchestrator (Google Drive trigger)
|
|
147
|
+
- `167603` — Transcribe Audio (API Platform trigger, reference recipe)
|
|
148
|
+
|
|
149
|
+
Key connection IDs:
|
|
150
|
+
- OpenAI: `14358`
|
|
151
|
+
- RecipeOps: `14233`
|
|
152
|
+
|
|
153
|
+
Data Table: **Audio Transcripts** (ID: `3512`) in project `14318`.
|
package/README.md
CHANGED
|
@@ -29,8 +29,28 @@ WORKATO_API_TOKEN=your_token_here
|
|
|
29
29
|
|
|
30
30
|
You can also export it directly in your shell environment.
|
|
31
31
|
|
|
32
|
+
## Claude Code setup
|
|
33
|
+
|
|
34
|
+
In a clean working directory, run these two commands once before starting Claude Code:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
npx workato-dev-api auth YOUR_API_TOKEN
|
|
38
|
+
npx workato-dev-api bootstrap-claude
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
That's it. The first command saves your token to `.env`. The second drops a `CLAUDE.md` into the directory so Claude Code automatically has full context — recipe structure, wiring syntax, data table column names, and project reference IDs.
|
|
42
|
+
|
|
43
|
+
Then just open Claude Code in that directory and start working.
|
|
44
|
+
|
|
32
45
|
## Commands
|
|
33
46
|
|
|
47
|
+
### Setup
|
|
48
|
+
|
|
49
|
+
| Command | Description |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `workato bootstrap-claude` | Copy `CLAUDE.md` into the current directory |
|
|
52
|
+
| `workato auth <token>` | Save your API token to `.env` in the current directory |
|
|
53
|
+
|
|
34
54
|
### Read
|
|
35
55
|
|
|
36
56
|
| Command | Description |
|
package/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ const os = require('os');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const {
|
|
7
7
|
loadEnv,
|
|
8
|
+
cmdBootstrapClaude, cmdAuth,
|
|
8
9
|
cmdGet, cmdListRecipes, cmdListProjects, cmdListFolders,
|
|
9
10
|
cmdListConnections, cmdListDataTables, cmdGetDataTable,
|
|
10
11
|
cmdGetJobs, cmdGetJob,
|
|
@@ -12,6 +13,22 @@ const {
|
|
|
12
13
|
cmdStart, cmdStop, cmdDelete,
|
|
13
14
|
} = require('./lib');
|
|
14
15
|
|
|
16
|
+
// Setup commands run before env/token setup
|
|
17
|
+
const _setupCmd = process.argv[2];
|
|
18
|
+
if (_setupCmd === 'bootstrap-claude') {
|
|
19
|
+
cmdBootstrapClaude(process.cwd());
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
if (_setupCmd === 'auth') {
|
|
23
|
+
const token = process.argv[3];
|
|
24
|
+
if (!token) {
|
|
25
|
+
console.error('Usage: workato auth <token>');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
cmdAuth(token, process.cwd());
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
|
|
15
32
|
// Load order — last one wins, so highest-priority sources go last:
|
|
16
33
|
// package dir → home dir → cwd (cwd always wins)
|
|
17
34
|
loadEnv(path.join(__dirname, '.env'));
|
|
@@ -19,7 +36,7 @@ loadEnv(path.join(os.homedir(), '.env'));
|
|
|
19
36
|
loadEnv(path.join(process.cwd(), '.env'));
|
|
20
37
|
|
|
21
38
|
if (!process.env.WORKATO_API_TOKEN) {
|
|
22
|
-
console.error('Error: WORKATO_API_TOKEN not set.\
|
|
39
|
+
console.error('Error: WORKATO_API_TOKEN not set.\nRun: workato auth <your_token>\nOr create a .env file with: WORKATO_API_TOKEN=your_token_here');
|
|
23
40
|
process.exit(1);
|
|
24
41
|
}
|
|
25
42
|
|
|
@@ -49,6 +66,10 @@ function usage() {
|
|
|
49
66
|
console.error(`
|
|
50
67
|
workato <command> [options]
|
|
51
68
|
|
|
69
|
+
Setup:
|
|
70
|
+
bootstrap-claude Copy CLAUDE.md into the current directory
|
|
71
|
+
auth <token> Save API token to .env in the current directory
|
|
72
|
+
|
|
52
73
|
Read commands:
|
|
53
74
|
get <recipe_id> Fetch recipe code → recipe_<id>_code.json
|
|
54
75
|
list-recipes [--folder <id>] [--project <id>] [--page <n>]
|
package/lib.js
CHANGED
|
@@ -143,6 +143,30 @@ function apiTriggerConfig() {
|
|
|
143
143
|
];
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
// ── Setup commands ────────────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
function cmdBootstrapClaude(destDir) {
|
|
149
|
+
const src = path.join(__dirname, 'CLAUDE.md');
|
|
150
|
+
const dest = path.join(destDir ?? process.cwd(), 'CLAUDE.md');
|
|
151
|
+
fs.copyFileSync(src, dest);
|
|
152
|
+
console.log(`CLAUDE.md written to ${dest}`);
|
|
153
|
+
return dest;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function cmdAuth(token, destDir) {
|
|
157
|
+
const envPath = path.join(destDir ?? process.cwd(), '.env');
|
|
158
|
+
let content = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf8') : '';
|
|
159
|
+
const line = `WORKATO_API_TOKEN=${token}`;
|
|
160
|
+
if (/^WORKATO_API_TOKEN=/m.test(content)) {
|
|
161
|
+
content = content.replace(/^WORKATO_API_TOKEN=.*/m, line);
|
|
162
|
+
} else {
|
|
163
|
+
content = content ? content.trimEnd() + '\n' + line + '\n' : line + '\n';
|
|
164
|
+
}
|
|
165
|
+
fs.writeFileSync(envPath, content);
|
|
166
|
+
console.log(`Token saved to ${envPath}`);
|
|
167
|
+
return envPath;
|
|
168
|
+
}
|
|
169
|
+
|
|
146
170
|
// ── Read commands ─────────────────────────────────────────────────────────────
|
|
147
171
|
|
|
148
172
|
async function cmdGet(recipeId) {
|
|
@@ -313,6 +337,8 @@ module.exports = {
|
|
|
313
337
|
// helpers
|
|
314
338
|
findStep, deepMerge, extractCode,
|
|
315
339
|
apiTriggerCode, apiTriggerConfig,
|
|
340
|
+
// setup commands
|
|
341
|
+
cmdBootstrapClaude, cmdAuth,
|
|
316
342
|
// read commands
|
|
317
343
|
cmdGet, cmdListRecipes, cmdListProjects, cmdListFolders,
|
|
318
344
|
cmdListConnections, cmdListDataTables, cmdGetDataTable,
|
package/package.json
CHANGED
package/test/cli.test.js
CHANGED
|
@@ -982,6 +982,144 @@ describe('cmdUpdateStep — deeply nested step', () => {
|
|
|
982
982
|
});
|
|
983
983
|
});
|
|
984
984
|
|
|
985
|
+
// ── cmdBootstrapClaude ────────────────────────────────────────────────────────
|
|
986
|
+
|
|
987
|
+
describe('cmdBootstrapClaude', () => {
|
|
988
|
+
function tmpDir() {
|
|
989
|
+
const d = path.join(os.tmpdir(), `workato-test-${Date.now()}-${Math.random()}`);
|
|
990
|
+
fs.mkdirSync(d, { recursive: true });
|
|
991
|
+
return d;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
test('copies CLAUDE.md into the destination directory', () => {
|
|
995
|
+
const dir = tmpDir();
|
|
996
|
+
try {
|
|
997
|
+
lib.cmdBootstrapClaude(dir);
|
|
998
|
+
assert.ok(fs.existsSync(path.join(dir, 'CLAUDE.md')), 'CLAUDE.md should exist in dest dir');
|
|
999
|
+
} finally {
|
|
1000
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
test('written file content matches the source CLAUDE.md', () => {
|
|
1005
|
+
const dir = tmpDir();
|
|
1006
|
+
try {
|
|
1007
|
+
lib.cmdBootstrapClaude(dir);
|
|
1008
|
+
const srcPath = path.join(__dirname, '..', 'CLAUDE.md');
|
|
1009
|
+
const src = fs.readFileSync(srcPath, 'utf8');
|
|
1010
|
+
const dest = fs.readFileSync(path.join(dir, 'CLAUDE.md'), 'utf8');
|
|
1011
|
+
assert.equal(dest, src);
|
|
1012
|
+
} finally {
|
|
1013
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
|
|
1017
|
+
test('returns the destination file path', () => {
|
|
1018
|
+
const dir = tmpDir();
|
|
1019
|
+
try {
|
|
1020
|
+
const result = lib.cmdBootstrapClaude(dir);
|
|
1021
|
+
assert.equal(result, path.join(dir, 'CLAUDE.md'));
|
|
1022
|
+
} finally {
|
|
1023
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
test('overwrites an existing CLAUDE.md', () => {
|
|
1028
|
+
const dir = tmpDir();
|
|
1029
|
+
try {
|
|
1030
|
+
fs.writeFileSync(path.join(dir, 'CLAUDE.md'), 'old content');
|
|
1031
|
+
lib.cmdBootstrapClaude(dir);
|
|
1032
|
+
const content = fs.readFileSync(path.join(dir, 'CLAUDE.md'), 'utf8');
|
|
1033
|
+
assert.notEqual(content, 'old content');
|
|
1034
|
+
} finally {
|
|
1035
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1036
|
+
}
|
|
1037
|
+
});
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
// ── cmdAuth ───────────────────────────────────────────────────────────────────
|
|
1041
|
+
|
|
1042
|
+
describe('cmdAuth', () => {
|
|
1043
|
+
function tmpDir() {
|
|
1044
|
+
const d = path.join(os.tmpdir(), `workato-auth-test-${Date.now()}-${Math.random()}`);
|
|
1045
|
+
fs.mkdirSync(d, { recursive: true });
|
|
1046
|
+
return d;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
test('creates .env with token when file does not exist', () => {
|
|
1050
|
+
const dir = tmpDir();
|
|
1051
|
+
try {
|
|
1052
|
+
lib.cmdAuth('mytoken123', dir);
|
|
1053
|
+
const content = fs.readFileSync(path.join(dir, '.env'), 'utf8');
|
|
1054
|
+
assert.ok(content.includes('WORKATO_API_TOKEN=mytoken123'));
|
|
1055
|
+
} finally {
|
|
1056
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
test('returns the .env file path', () => {
|
|
1061
|
+
const dir = tmpDir();
|
|
1062
|
+
try {
|
|
1063
|
+
const result = lib.cmdAuth('tok', dir);
|
|
1064
|
+
assert.equal(result, path.join(dir, '.env'));
|
|
1065
|
+
} finally {
|
|
1066
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
test('updates existing WORKATO_API_TOKEN line in .env', () => {
|
|
1071
|
+
const dir = tmpDir();
|
|
1072
|
+
try {
|
|
1073
|
+
fs.writeFileSync(path.join(dir, '.env'), 'WORKATO_API_TOKEN=oldtoken\n');
|
|
1074
|
+
lib.cmdAuth('newtoken', dir);
|
|
1075
|
+
const content = fs.readFileSync(path.join(dir, '.env'), 'utf8');
|
|
1076
|
+
assert.ok(content.includes('WORKATO_API_TOKEN=newtoken'), 'should have new token');
|
|
1077
|
+
assert.ok(!content.includes('oldtoken'), 'should not have old token');
|
|
1078
|
+
assert.equal((content.match(/WORKATO_API_TOKEN=/g) || []).length, 1, 'only one token line');
|
|
1079
|
+
} finally {
|
|
1080
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
|
|
1084
|
+
test('appends token to .env that has other keys but no WORKATO_API_TOKEN', () => {
|
|
1085
|
+
const dir = tmpDir();
|
|
1086
|
+
try {
|
|
1087
|
+
fs.writeFileSync(path.join(dir, '.env'), 'OTHER_KEY=value\n');
|
|
1088
|
+
lib.cmdAuth('mytoken', dir);
|
|
1089
|
+
const content = fs.readFileSync(path.join(dir, '.env'), 'utf8');
|
|
1090
|
+
assert.ok(content.includes('OTHER_KEY=value'), 'existing key preserved');
|
|
1091
|
+
assert.ok(content.includes('WORKATO_API_TOKEN=mytoken'), 'token appended');
|
|
1092
|
+
} finally {
|
|
1093
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
test('token value containing = is preserved verbatim', () => {
|
|
1098
|
+
const dir = tmpDir();
|
|
1099
|
+
try {
|
|
1100
|
+
lib.cmdAuth('tok==extra==', dir);
|
|
1101
|
+
const content = fs.readFileSync(path.join(dir, '.env'), 'utf8');
|
|
1102
|
+
assert.ok(content.includes('WORKATO_API_TOKEN=tok==extra=='));
|
|
1103
|
+
} finally {
|
|
1104
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
|
|
1108
|
+
test('preserves other keys in .env when updating token', () => {
|
|
1109
|
+
const dir = tmpDir();
|
|
1110
|
+
try {
|
|
1111
|
+
fs.writeFileSync(path.join(dir, '.env'), 'FOO=bar\nWORKATO_API_TOKEN=old\nBAZ=qux\n');
|
|
1112
|
+
lib.cmdAuth('updated', dir);
|
|
1113
|
+
const content = fs.readFileSync(path.join(dir, '.env'), 'utf8');
|
|
1114
|
+
assert.ok(content.includes('FOO=bar'), 'FOO preserved');
|
|
1115
|
+
assert.ok(content.includes('BAZ=qux'), 'BAZ preserved');
|
|
1116
|
+
assert.ok(content.includes('WORKATO_API_TOKEN=updated'), 'token updated');
|
|
1117
|
+
} finally {
|
|
1118
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
1121
|
+
});
|
|
1122
|
+
|
|
985
1123
|
// Restore console at end
|
|
986
1124
|
process.on('exit', () => {
|
|
987
1125
|
console.log = origLog;
|