Skip to content

Commit 89a41ed

Browse files
josephjclarklamine2000claudedoc-han
authored
Release/next (#1454)
* create output directory if it doesn't exist (#1452) * fix(cli): create output directory if it doesn't exist (#1445) The compile and collections commands threw ENOENT when --output-path pointed to a directory that hadn't been created yet. Add mkdir({ recursive: true }) before writeFile in both handlers, matching the pattern already used in the execute command's serialize-output.ts. Fixes #859 * changeset --------- Co-authored-by: Lamine Gueye <51838776+lamine2000@users.noreply.github.com> * Fix gitignore cache (#1453) * fix(cli): fix cache .gitignore written to fs root and undefined workflow name (#1444) * fix(cli): fix cache .gitignore written to fs root and undefined workflow name - Replace ensureGitIgnore path-walking logic with a direct getCacheRoot helper. The old loop used endsWith('.cli-cache') to find the cache root, but if the path never contained that segment (custom cachePath) it would walk all the way to '/' and write /.gitignore - Fall back to 'workflow' when plan.workflow.name is undefined, avoiding .cli-cache/undefined directories - Add cachePath to saveToCache/clearCache options types so custom cache paths set by workspace projects are correctly forwarded to getCachePath - Remove spurious await on the now-synchronous getCachePath call - Add regression tests covering expressionPath + cacheSteps (the exact scenario reported in #669, which had no test coverage) Fixes #669 * fix test: use correct cache subdir name derived from filename * refactor(cli): address review feedback on cache fix - Eliminate getCacheRoot helper: getCachePath(options) with no workflowName/stepId already returns the CACHE_DIR root naturally - Use .filter(Boolean) spread into path.resolve so undefined workflowName does not cause ERR_INVALID_ARG_TYPE (path.resolve throws on undefined) - Keep cachePath in saveToCache/clearCache Pick types so custom paths work - Rename gitignore test name to be more accurate Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * style(cli): fix prettier formatting Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Revert "style(cli): fix prettier formatting" This reverts commit 1e47fad. * style(cli): fix prettier v2 formatting Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(cli): simplify workflowName fallback per review Use workflowName ?? '' instead of .filter(Boolean) spread. path.resolve treats '' as a no-op so undefined workflowName still resolves to the bare CACHE_DIR root. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * clean up test * changeset * tidying up * typing --------- Co-authored-by: Lamine Gueye <51838776+lamine2000@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Merge & Sync improvements (#1450) * CLI: add --workflows flag to merge and deploy (#1437) * add --workflows flag * rename --workflows to --workflow and alias * changelog * update tests * format * mock: fix an issue where project obejts can get scribbed on * fix tests * update integration tests * improve error messages for invalid workspace * better diverence message * Fix incorrect divergence warnings on checkout (#1446) * improve error messages for invalid workspace * better diverence message * add failing test and notes * project: set the alias on a checked out project * fix divergence on checkout * fixes for tests * fix tests and enable project alias to be null * update tests * update more tests * relax alias lookup to fix tests * when merging, force-checkout the result * remove comment * types * fix one more test * update test * hide merge command * chore: ignore sentry errors by regexp (#1440) * feat: ignore sentry errors by regexp * chore: remove debug * feat: add severity override to ignored errors * changeset --------- Co-authored-by: Joe Clark <jclark@openfn.org> * versions --------- Co-authored-by: Lamine Gueye <51838776+lamine2000@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Farhan Y. <yahyafarhan48@gmail.com>
1 parent ebfddda commit 89a41ed

44 files changed

Lines changed: 1006 additions & 136 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

integration-tests/cli/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# @openfn/integration-tests-cli
22

3+
## 1.0.24
4+
5+
### Patch Changes
6+
7+
- Updated dependencies [25e01c9]
8+
- Updated dependencies [25e01c9]
9+
- @openfn/lightning-mock@2.4.19
10+
- @openfn/project@0.17.1
11+
312
## 1.0.23
413

514
### Patch Changes

integration-tests/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@openfn/integration-tests-cli",
33
"private": true,
4-
"version": "1.0.23",
4+
"version": "1.0.24",
55
"description": "CLI integration tests",
66
"author": "Open Function Group <admin@openfn.org>",
77
"license": "ISC",

integration-tests/cli/test/errors.test.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ test.serial('expression not found', async (t) => {
2222
const stdlogs = extractLogs(stdout);
2323
assertLog(t, stdlogs, /expression not found/i);
2424
assertLog(t, stdlogs, /failed to load the expression from blah.js/i);
25-
assertLog(t, stdlogs, /critical error: aborting command/i);
2625
});
2726

2827
test.serial('workflow not found', async (t) => {
@@ -33,7 +32,6 @@ test.serial('workflow not found', async (t) => {
3332

3433
assertLog(t, stdlogs, /workflow not found/i);
3534
assertLog(t, stdlogs, /failed to load a workflow from blah.json/i);
36-
assertLog(t, stdlogs, /critical error: aborting command/i);
3735
});
3836

3937
test.serial('job contains invalid js', async (t) => {
@@ -45,7 +43,6 @@ test.serial('job contains invalid js', async (t) => {
4543
assertLog(t, stdlogs, /failed to compile job/i);
4644
assertLog(t, stdlogs, /unexpected token \(2:10\)/i);
4745
assertLog(t, stdlogs, /check the syntax of the job expression/i);
48-
assertLog(t, stdlogs, /critical error: aborting command/i);
4946
});
5047

5148
// TODO this should really mention which job threw the error
@@ -60,7 +57,6 @@ test.serial('workflow references a job with invalid js', async (t) => {
6057
assertLog(t, stdlogs, /failed to compile job/i);
6158
assertLog(t, stdlogs, /unexpected token \(2:10\)/i);
6259
assertLog(t, stdlogs, /check the syntax of the job expression/i);
63-
assertLog(t, stdlogs, /critical error: aborting command/i);
6460
});
6561

6662
test.serial("can't find an expression referenced in a workflow", async (t) => {
@@ -77,7 +73,6 @@ test.serial("can't find an expression referenced in a workflow", async (t) => {
7773
stdlogs,
7874
/This workflow references a file which cannot be found at does-not-exist.js/i
7975
);
80-
assertLog(t, stdlogs, /critical error: aborting command/i);
8176
});
8277

8378
test.serial("can't find config referenced in a workflow", async (t) => {
@@ -98,7 +93,6 @@ test.serial("can't find config referenced in a workflow", async (t) => {
9893
stdlogs,
9994
/This workflow references a file which cannot be found at does-not-exist.js/i
10095
);
101-
assertLog(t, stdlogs, /critical error: aborting command/i);
10296
});
10397

10498
test.serial('circular workflow', async (t) => {
@@ -141,7 +135,6 @@ test.serial('invalid end (ambiguous)', async (t) => {
141135
const stdlogs = extractLogs(stdout);
142136

143137
assertLog(t, stdlogs, /Error: end pattern matched multiple steps/i);
144-
assertLog(t, stdlogs, /aborting/i);
145138
});
146139

147140
// These test error outputs within valid workflows

integration-tests/cli/test/project-v1.test.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ workflows:
108108
const projectsPath = path.resolve(TMP_DIR);
109109

110110
test.before(async () => {
111-
// await rm(TMP_DIR, { recursive: true });
111+
try {
112+
await rm(TMP_DIR, { recursive: true });
113+
} catch (e) {}
112114
await mkdir(`${TMP_DIR}/.projects`, { recursive: true });
113115

114116
await writeFile(`${TMP_DIR}/openfn.yaml`, '');
@@ -120,7 +122,7 @@ test.before(async () => {
120122
});
121123

122124
test.serial('list available projects', async (t) => {
123-
const { stdout } = await run(`openfn projects -w ${projectsPath}`);
125+
const { stdout } = await run(`openfn projects --workspace ${projectsPath}`);
124126

125127
t.regex(stdout, /hello-world/);
126128
t.regex(stdout, /8dbc4349-52b4-4bf2-be10-fdf06da52c46/);
@@ -130,7 +132,7 @@ test.serial('list available projects', async (t) => {
130132

131133
// checkout a project from a yaml file
132134
test.serial('Checkout a project', async (t) => {
133-
await run(`openfn checkout hello-world -w ${projectsPath}`);
135+
await run(`openfn checkout hello-world --workspace ${projectsPath}`);
134136

135137
// check workflow.yaml
136138
const workflowYaml = await readFile(
@@ -167,7 +169,7 @@ steps:
167169
// note: order of tests is important here
168170
test.serial('execute a workflow from the checked out project', async (t) => {
169171
// cheeky bonus test of checkout by alias
170-
await run(`openfn checkout main -w ${projectsPath}`);
172+
await run(`openfn checkout main --workspace ${projectsPath}`);
171173

172174
// execute a workflow
173175
const { stdout } = await run(
@@ -191,8 +193,9 @@ test.serial('merge a project', async (t) => {
191193
t.is(initial, 'fn(() => ({ x: 1}))');
192194

193195
// Run the merge
194-
await run(`openfn merge hello-world-staging -w ${projectsPath} --force`);
195-
196+
const { stdout } = await run(
197+
`openfn merge hello-world-staging --workspace ${projectsPath} --force --log debug`
198+
);
196199
// Check the step is updated
197200
const merged = await readStep();
198201
t.is(merged, "log('hello world')");

integration-tests/cli/test/project-v2.test.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -131,15 +131,15 @@ test.before(async () => {
131131
});
132132

133133
test.serial('list available projects', async (t) => {
134-
const { stdout } = await run(`openfn projects -w ${TMP_DIR}`);
134+
const { stdout } = await run(`openfn projects --workspace ${TMP_DIR}`);
135135
t.regex(stdout, /sandboxing-simple/);
136136
t.regex(stdout, /a272a529-716a-4de7-a01c-a082916c6d23/);
137137
t.regex(stdout, /staging/);
138138
t.regex(stdout, /bc6629fb-7dc8-4b28-93af-901e2bd58dc4/);
139139
});
140140

141141
test.serial('Checkout a project', async (t) => {
142-
await run(`openfn checkout staging -w ${TMP_DIR}`);
142+
await run(`openfn checkout staging --workspace ${TMP_DIR}`);
143143

144144
// check workflow.yaml
145145
const workflowYaml = await readFile(
@@ -174,12 +174,13 @@ steps:
174174

175175
test.serial('execute a workflow from the checked out project', async (t) => {
176176
// cheeky bonus test of checkout by alias
177-
await run(`openfn checkout main -w ${TMP_DIR}`);
177+
await run(`openfn checkout main --workspace ${TMP_DIR} --force`);
178178

179179
// execute a workflow
180-
await run(
180+
const { stdout } = await run(
181181
`openfn hello-workflow -o ${TMP_DIR}/output.json --workspace ${TMP_DIR}`
182182
);
183+
console.log(stdout);
183184

184185
const output = await readFile(`${TMP_DIR}/output.json`, 'utf8');
185186
const finalState = JSON.parse(output);
@@ -189,7 +190,7 @@ test.serial('execute a workflow from the checked out project', async (t) => {
189190
test.serial(
190191
'execute a workflow from the checked out project with a credential map',
191192
async (t) => {
192-
await run(`openfn checkout main --log debug -w ${TMP_DIR}`);
193+
await run(`openfn checkout main --log debug --workspace ${TMP_DIR}`);
193194

194195
// Modify the checked out workflow code
195196
await writeFile(
@@ -248,7 +249,7 @@ test.serial(
248249
// Important: the collection value MUST be as string
249250
server.collections.upsert('stuff', 'x', JSON.stringify({ id: 'x' }));
250251

251-
await run(`openfn checkout main --log debug -w ${TMP_DIR}`);
252+
await run(`openfn checkout main --log debug --workspace ${TMP_DIR}`);
252253

253254
// Modify the checked out workflow code
254255
await writeFile(
@@ -313,7 +314,7 @@ workspace:
313314
);
314315

315316
test.serial('merge a project', async (t) => {
316-
await run(`openfn checkout main -w ${TMP_DIR}`);
317+
await run(`openfn checkout main --workspace ${TMP_DIR}`);
317318

318319
const readStep = () =>
319320
readFile(
@@ -326,7 +327,9 @@ test.serial('merge a project', async (t) => {
326327
t.is(initial, 'fn(() => ({ x: 1}))');
327328

328329
// Run the merge
329-
const { stdout } = await run(`openfn merge staging -w ${TMP_DIR} --force`);
330+
const { stdout } = await run(
331+
`openfn merge staging --workspace ${TMP_DIR} --force`
332+
);
330333

331334
// Check the step is updated
332335
const merged = await readStep();

packages/cli/CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
# @openfn/cli
22

3+
## 1.38.0
4+
5+
### Minor Changes
6+
7+
- 25e01c9: Allow users to specifiy which workflows to deploy or merge by passing `-w`.
8+
9+
NOTE: the `-w` alias has been repurposed from `--workspace` to `--workflow`. This may affect your local development environment. If so, just expand `-w` to `--workspace`.
10+
11+
### Patch Changes
12+
13+
- 4bf5394: Write the .gitignore file for cli cache to the cache root. This means the command will generate more ignore files, but prevents them getting generated at the system root
14+
- 8180b70: Ensure output path exists on compile and collections commands
15+
- 25e01c9: Fix an issue on checkout where incorrect divergence warnings can be shown
16+
- Updated dependencies [25e01c9]
17+
- Updated dependencies [25e01c9]
18+
- @openfn/lexicon@2.2.1
19+
- @openfn/project@0.17.1
20+
321
## 1.37.0
422

523
### Minor Changes

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@openfn/cli",
3-
"version": "1.37.0",
3+
"version": "1.38.0",
44
"description": "CLI devtools for the OpenFn toolchain",
55
"engines": {
66
"node": ">=18",

packages/cli/src/collections/handler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import path from 'node:path';
2-
import { readFile, writeFile } from 'node:fs/promises';
2+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
33

44
import { Logger } from '../util/logger';
55
import request from './request';
@@ -94,6 +94,7 @@ export const get = async (options: GetOptions, logger: Logger) => {
9494
null,
9595
options.pretty ? 2 : undefined
9696
);
97+
await mkdir(path.dirname(options.outputPath!), { recursive: true });
9798
await writeFile(options.outputPath!, content);
9899
logger.always(`Wrote items to ${options.outputPath}`);
99100
} else {

packages/cli/src/compile/handler.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { writeFile } from 'node:fs/promises';
1+
import { mkdir, writeFile } from 'node:fs/promises';
2+
import { dirname } from 'node:path';
23
import type { CompileOptions } from './command';
34
import type { Logger } from '../util/logger';
45

@@ -22,6 +23,7 @@ const compileHandler = async (options: CompileOptions, logger: Logger) => {
2223
if (options.outputStdout) {
2324
logger.success('Result:\n\n' + result);
2425
} else {
26+
await mkdir(dirname(options.outputPath!), { recursive: true });
2527
await writeFile(options.outputPath!, result as string);
2628
logger.success(`Compiled to ${options.outputPath}`);
2729
}

packages/cli/src/execute/handler.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import loadState from '../util/load-state';
1515
import validateAdaptors from '../util/validate-adaptors';
1616
import loadPlan from '../util/load-plan';
1717
import assertPath from '../util/assert-path';
18-
import { clearCache, getCachePath } from '../util/cache';
18+
import { clearCache } from '../util/cache';
1919
import fuzzyMatchStep from '../util/fuzzy-match-step';
2020
import abort from '../util/abort';
2121
import validatePlan from '../util/validate-plan';
@@ -175,15 +175,6 @@ const executeHandler = async (options: ExecuteOptions, logger: Logger) => {
175175
try {
176176
const result = await execute(finalPlan, state, options, logger);
177177

178-
if (options.cacheSteps) {
179-
logger.success(
180-
`Cached output written to ${getCachePath(
181-
options,
182-
plan.workflow.name
183-
)} (see info logs for details)`
184-
);
185-
}
186-
187178
await serializeOutput(options, result, logger);
188179
const duration = printDuration(new Date().getTime() - start);
189180
if (result?.errors) {

0 commit comments

Comments
 (0)