Skip to content

Commit

Permalink
Version 2.4.0 (#807)
Browse files Browse the repository at this point in the history
* feat: setup @jujulego/logger

* feat: always print ink on stdout

* fix: logs colors when output is piped

* fix: unit tests

* fix: e2e tests

* feat: change log format

* fix: e2e tests

* fix: e2e tests

* fix: tests

* chore: remove legacy code & deps

* test: thread gateway

* chore: fix engines

* chore: bump version number

* chore: relock

* Optimize e2e tests (#809)

* chore: copy e2e project folder

* chore: copy e2e project folder (other tests)

* chore: fix e2e plugin tests

* chore: prevent toto to be logged in tests

* fix: build error

---------

Co-authored-by: Julien Capellari <[email protected]>

* Remove global spinner (#810)

* feat: remove global spinner

* fix: e2e tests

* chore: use custom plugin for swc

* Support args in task expressions (#820)

* feat(tasks): support args inside task expression

* feat(tasks): inject args from tasks expression in Task commands

* fix: error handling

* chore: bump version number

* Add fallback syntax (#839)

* chore: upgrade tasks

* feat: add fallback syntax

* feat: add fallback e2e tests

* fix: fallback e2e tests

* fix: fallback e2e tests

* chore: update README

* Run hook scripts (#847)

* feat: add hooks option in global configuration

* feat: handle hook option in ScriptTask

* fix: hook false ignored from options

* fix: unit tests

* feat: test ScriptTask with hooks

* feat: add logs for hooks

* feat: remove test scripts

* feat: add e2e tests for hook scripts

* fix: forgotten .only

* docs: add a hooks section in th readme

---------

Co-authored-by: Julien Capellari <[email protected]>

* chore: bump version number

* Sort list output (#876)

* feat: sort list output

* feat: remove default arrays from doc

* fix: tests

* fix: e2e tests

* chore: refacto list command

* feat: add order option

* feat(list): test sort options

---------

Co-authored-by: Julien Capellari <[email protected]>

* chore: bump version number

* Setup rollup (#881)

* chore: setup rollup

* fix: simplify some imports

* fix: plugin import

* fix: tests

* fix: build

* fix: e2e plugin test

* chore: upgrade aegis (#882)

* Change sequence operator (#889)

* feat: change sequence operator

* feat: update tests

* feat: update readme

* chore: bump version number

* chore: rework rollup config

* Use task expression on run command (#892)

* feat: add task expression on run

* feat: deprecate group

* docs: update readme & command descriptions

* feat: rework error handling

* chore: rename task expr service

* feat: add e2e tests

* Use task expression on each (#893)

* feat: use task expression on each

* feat: add e2e test

* feat: test extract script util

* chore: bump version number

* Scrollable task spinner (#909)

* feat: feat rework task spinner

* feat: fix task order

* feat: fix & stabilize e2e tests

* feat: make task-tree scroll

* feat: add tree completed component

* feat: factorize task flatener

* feat: add stats at then end

* fix: build error

* fix: remove old tests

* fix: handle no raw mode

* fix: e2e tests

* fix: remove cast

* fix: tests

* fix: lockfile

* fix: task command tests

* fix: sonarcloud review

---------

Co-authored-by: Julien Capellari <[email protected]>

---------

Co-authored-by: Julien Capellari <[email protected]>
Co-authored-by: Julien Capellari <[email protected]>
  • Loading branch information
3 people authored Nov 25, 2023
1 parent 77d8345 commit 9ccb890
Show file tree
Hide file tree
Showing 112 changed files with 3,606 additions and 4,295 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/javascript.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
run: yarn install --immutable

- name: Build
run: yarn build:prod
run: yarn build

- name: Upload build artifacts
uses: actions/upload-artifact@v3
Expand Down
1 change: 1 addition & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/sonarlint.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .swcrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"jsc": {
"loose": true,
"externalHelpers": true,
"parser": {
"syntax": "typescript",
"tsx": true,
Expand Down
31 changes: 20 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,44 @@ Zero-config monorepo cli
Jill uses the `workspaces` attribute of your root package.json manifest to build your workspaces dependency tree.
That done it can offer you various utilities:
- `jill list` prints a list of all your workspaces, with many useful filters
- `jill run` build all workspace's local dependencies before run a given script
- `jill each` do the same as `run` but for a list of workspaces, optimizing builds.<br />
Supports the same filters as `list`.
- `jill run` Run a task expression in a workspace, after having built all its dependencies.
- `jill group` Deprecated in favor of run
- `jill each` Run a task expression in many workspace, after having built all theirs dependencies.
- `jill tree` prints current workspace's local dependency tree

It supports both `npm` and `yarn`.

### Experimental features
- `jill group` same as `run` but allows to run multiple scripts in sequence or in parallel using the task syntax
### Hook scripts
Jill will run hook script like npm do, for both npm and yarn. As npm, when you type `jill run test`, it will first run
`pretest` if it exists, then `test` and finally `posttest`.

#### Task syntax _(only supported by `jill group` command yet)_
Allows to instruct multiple tasks with the given orchestration. The orchetraction is given by the following operators:
- `->` in sequence
This feature can be disabled using the `--no-hooks` option: `jill run --no-hooks test`.

#### Task expression syntax
Allows to instruct multiple tasks with the given orchestration. The orchestration is given by the following operators:
- `&&` in sequence
- `||` fallbacks
- `//` in parallel

##### Examples:
- This will run scripts **taskA**, **taskB** and **taskC** in order, one after another.
```shell
jill group 'taskA -> taskB -> taskC'
jill run 'taskA && taskB && taskC'
```

- This will run first **taskA**, if it fails it will run **taskB**, then **taskC** in order, until one succeed.
```shell
jill run 'taskA || taskB || taskC'
```

- This will run scripts **taskA**, **taskB** and **taskC** in parallel.
```shell
jill group 'taskA // taskB // taskC'
jill run 'taskA // taskB // taskC'
```

- And you can create more complex flows: this will run **taskA** and **taskB** in parallel, and then **taskC** when both tasks are ended
```shell
jill group '(taskA // taskB) -> taskC'
jill run '(taskA // taskB) && taskC'
```

## Installation
Expand Down
3 changes: 3 additions & 0 deletions bin/jill.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env node

import '../dist/main.js';
3 changes: 0 additions & 3 deletions bin/jill.mjs

This file was deleted.

182 changes: 141 additions & 41 deletions e2e/each.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,74 @@ import { fileExists } from '@/tools/utils.js';

import { jill } from './utils.js';

// Setup
const bed = new TestBed();

beforeAll(() => {
const wksC = bed.addWorkspace('wks-c', {
scripts: {
// language=bash
build: 'node -e "require(\'node:fs\').writeFileSync(\'build.txt\', \'built\')"',
// language=bash
prehooked: 'node -e "require(\'node:fs\').writeFileSync(\'pre-hook.txt\', \'hooked\')"',
// language=bash
hooked: 'node -e "require(\'node:fs\').writeFileSync(\'hook.txt\', \'hooked\')"',
// language=bash
posthooked: 'node -e "require(\'node:fs\').writeFileSync(\'post-hook.txt\', \'hooked\')"',
}
});

const wksB = bed.addWorkspace('wks-b', {
scripts: {
// language=bash
build: 'node -e "require(\'node:fs\').writeFileSync(\'build.txt\', \'built\')"',
// language=bash
start: 'node -e "require(\'node:fs\').writeFileSync(\'start.txt\', \'started\')"',
// language=bash
prehooked: 'node -e "require(\'node:fs\').writeFileSync(\'pre-hook.txt\', \'hooked\')"',
// language=bash
hooked: 'node -e "require(\'node:fs\').writeFileSync(\'hook.txt\', \'hooked\')"',
// language=bash
posthooked: 'node -e "require(\'node:fs\').writeFileSync(\'post-hook.txt\', \'hooked\')"',
// language=bash
fails: 'node -e "process.exit(1)"',
}
})
.addDependency(wksC, true);

bed.addWorkspace('wks-a', {
scripts: {
// language=bash
build: 'node -e "require(\'node:fs\').writeFileSync(\'build.txt\', \'built\')"',
// language=bash
start: 'node -e "require(\'node:fs\').writeFileSync(\'start.txt\', \'started\')"'
}
})
.addDependency(wksB)
.addDependency(wksC, true);
});

// Tests
describe('jill each', () => {
describe.each(['npm', 'yarn'] as const)('using %s', (packageManager) => {
// Setup
// Create project folder
let baseDir: string;
let tmpDir: string;
let prjDir: string;

beforeEach(async () => {
const bed = new TestBed();
beforeAll(async () => {
baseDir = await bed.createProjectPackage(packageManager);
tmpDir = path.dirname(baseDir);
}, 15000);

const wksC = bed.addWorkspace('wks-c', {
scripts: {
// language=bash
build: 'node -e "require(\'node:fs\').writeFileSync(\'build.txt\', \'built\')"',
}
});
const wksB = bed.addWorkspace('wks-b', {
scripts: {
// language=bash
build: 'node -e "require(\'node:fs\').writeFileSync(\'build.txt\', \'built\')"',
// language=bash
start: 'node -e "require(\'node:fs\').writeFileSync(\'start.txt\', \'started\')"',
// language=bash
fails: 'node -e "process.exit(1)"',
}
})
.addDependency(wksC, true);
bed.addWorkspace('wks-a', {
scripts: {
// language=bash
start: 'node -e "require(\'node:fs\').writeFileSync(\'start.txt\', \'started\')"'
}
})
.addDependency(wksB)
.addDependency(wksC, true);
beforeEach(async (ctx) => {
prjDir = path.join(tmpDir, ctx.task.id);

prjDir = await bed.createProjectPackage(packageManager);
}, 15000);
await fs.cp(baseDir, prjDir, { force: true, recursive: true });
});

afterEach(async () => {
await fs.rm(prjDir, { recursive: true });
afterAll(async () => {
await fs.rm(tmpDir, { recursive: true });
});

// Tests
Expand All @@ -55,10 +84,11 @@ describe('jill each', () => {
expect(res.code).toBe(0);

expect(res.screen.screen).toMatchLines([
expect.ignoreColor(/^. Run build in wks-c \(took [0-9.]+m?s\)$/),
expect.ignoreColor(/^. Run start in wks-b \(took [0-9.]+m?s\)$/),
expect.ignoreColor(/^. Run build in wks-b \(took [0-9.]+m?s\)$/),
expect.ignoreColor(/^. Run start in wks-a \(took [0-9.]+m?s\)$/),
expect.ignoreColor(/^. Run build in wks-b \(took [0-9.]+m?s\)$/),
expect.ignoreColor(/^. Run start in wks-b \(took [0-9.]+m?s\)$/),
expect.ignoreColor(/^. Run build in wks-c \(took [0-9.]+m?s\)$/),
expect.ignoreColor(/^. 4 done$/),
]);

// Check script result
Expand All @@ -75,16 +105,87 @@ describe('jill each', () => {
.resolves.toBe('started');
});

it('should run build and start script on each workspace (and build dependencies)', async () => {
const res = await jill('each "build && start"', { cwd: prjDir });

// Check jill output
expect(res.code).toBe(0);

expect(res.screen.screen).toMatchLines([
expect.ignoreColor(/^. In sequence \(took [0-9.]+m?s\)$/),
expect.ignoreColor(/^ {2}. Run build in wks-a \(took [0-9.]+m?s\)$/),
expect.ignoreColor(/^ {2}. Run start in wks-a \(took [0-9.]+m?s\)$/),
expect.ignoreColor(/^. In sequence \(took [0-9.]+m?s\)$/),
expect.ignoreColor(/^ {2}. Run build in wks-b \(took [0-9.]+m?s\)$/),
expect.ignoreColor(/^ {2}. Run start in wks-b \(took [0-9.]+m?s\)$/),
expect.ignoreColor(/^. Run build in wks-c \(took [0-9.]+m?s\)$/),
expect.ignoreColor(/^. 5 done$/),
]);

// Check script result
await expect(fs.readFile(path.join(prjDir, 'wks-c', 'build.txt'), 'utf8'))
.resolves.toBe('built');

await expect(fs.readFile(path.join(prjDir, 'wks-b', 'build.txt'), 'utf8'))
.resolves.toBe('built');

await expect(fs.readFile(path.join(prjDir, 'wks-b', 'start.txt'), 'utf8'))
.resolves.toBe('started');

await expect(fs.readFile(path.join(prjDir, 'wks-a', 'build.txt'), 'utf8'))
.resolves.toBe('built');

await expect(fs.readFile(path.join(prjDir, 'wks-a', 'start.txt'), 'utf8'))
.resolves.toBe('started');
});

it('should run hooked script with its hooks on each workspace (and build dependencies)', async () => {
const res = await jill('each hooked', { cwd: prjDir });

// Check jill output
expect(res.code).toBe(0);

expect(res.screen.screen).toMatchLines([
expect.ignoreColor(/^. Run hooked in wks-b \(took [0-9.]+m?s\)$/),
expect.ignoreColor(/^. Run build in wks-c \(took [0-9.]+m?s\)$/),
expect.ignoreColor(/^. Run hooked in wks-c \(took [0-9.]+m?s\)$/),
expect.ignoreColor(/^. 7 done$/),
]);

// Check script result
await expect(fs.readFile(path.join(prjDir, 'wks-c', 'pre-hook.txt'), 'utf8'))
.resolves.toBe('hooked');

await expect(fs.readFile(path.join(prjDir, 'wks-c', 'hook.txt'), 'utf8'))
.resolves.toBe('hooked');

await expect(fs.readFile(path.join(prjDir, 'wks-c', 'post-hook.txt'), 'utf8'))
.resolves.toBe('hooked');

await expect(fs.readFile(path.join(prjDir, 'wks-c', 'build.txt'), 'utf8'))
.resolves.toBe('built');

await expect(fs.readFile(path.join(prjDir, 'wks-b', 'pre-hook.txt'), 'utf8'))
.resolves.toBe('hooked');

await expect(fs.readFile(path.join(prjDir, 'wks-b', 'hook.txt'), 'utf8'))
.resolves.toBe('hooked');

await expect(fs.readFile(path.join(prjDir, 'wks-b', 'post-hook.txt'), 'utf8'))
.resolves.toBe('hooked');
});

it('should run fails script on each workspace (and build dependencies)', async () => {
const res = await jill('each fails', { cwd: prjDir });

// Check jill output
expect(res.code).toBe(1);

expect(res.screen.screen).toMatchLines([
expect.ignoreColor(/^. Run build in wks-c \(took [0-9.]+m?s\)$/),
expect.ignoreColor(/^. Run fails in wks-b \(took [0-9.]+m?s\)$/),
expect.ignoreColor(/^ {2}.( yarn exec)? node -e "process.exit\(1\)" \(took [0-9.]+m?s\)$/),
expect.ignoreColor(/^. Run build in wks-c \(took [0-9.]+m?s\)$/),
expect.ignoreColor(/^. 1 done, . 1 failed$/),
]);

// Check script result
Expand All @@ -106,7 +207,7 @@ describe('jill each', () => {

// Check jill output
expect(res.code).toBe(1);
expect(res.screen.screen).toMatchLines([
expect(res.stderr).toMatchLines([
expect.ignoreColor(/^. No matching workspace found !$/),
]);

Expand All @@ -129,9 +230,8 @@ describe('jill each', () => {

// Check jill output
expect(res.code).toBe(0);
expect(res.stdout).toHaveLength(1);

const plan = JSON.parse(res.stdout[0]);
const plan = JSON.parse(res.stdout.join('\n'));
expect(plan).toHaveLength(8);

expect(plan[0]).toMatchObject({
Expand Down Expand Up @@ -240,8 +340,8 @@ describe('jill each', () => {
}
});

await expect(fileExists(path.join(prjDir, 'wks-c', 'script.txt'))).resolves.toBe(false);
await expect(fileExists(path.join(prjDir, 'wks-b', 'script.txt'))).resolves.toBe(false);
await expect(fileExists(path.join(prjDir, 'wks-c', 'hook.txt'))).resolves.toBe(false);
await expect(fileExists(path.join(prjDir, 'wks-b', 'hook.txt'))).resolves.toBe(false);
await expect(fileExists(path.join(prjDir, 'wks-b', 'start.txt'))).resolves.toBe(false);
await expect(fileExists(path.join(prjDir, 'wks-a', 'start.txt'))).resolves.toBe(false);
});
Expand Down
Loading

0 comments on commit 9ccb890

Please sign in to comment.