Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ yarn-error.log*
*.log

# lock
package-lock.json
yarn.lock

# generated
Expand Down
2 changes: 1 addition & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
package-lock=false
package-lock=true
loglevel=error
204 changes: 204 additions & 0 deletions docs/advanced/creating-custom-registries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
---
id: creating-custom-registries
title: Creating Custom Registries
hide_title: true
sidebar_label: Creating Custom Registries
---

# Creating Custom Registries

Allows custom registries to be plugged into the task system, which can provide shared tasks or augmented functionality. Registries are registered using [`registry()`][registry-api-docs].

## Structure

In order to be accepted by gulp, custom registries must follow a specific format.

```js
// as a function
function TestRegistry() {}

TestRegistry.prototype.init = function (gulpInst) {}
TestRegistry.prototype.get = function (name) {}
TestRegistry.prototype.set = function (name, fn) {}
TestRegistry.prototype.tasks = function () {}

// as a class
class TestRegistry {
init(gulpInst) {}

get(name) {}

set(name, fn) {}

tasks() {}
}
```

If a registry instance passed to `registry()` doesn't have all four methods, an error will be thrown.

## Registration

If we want to register our example registry from above, we will need to pass an instance of it to `registry()`.

```js
const { registry } = require('gulp');

// ... TestRegistry setup code

// good!
registry(new TestRegistry())

// bad!
registry(TestRegistry())
// This will trigger an error: 'Custom registries must be instantiated, but it looks like you passed a constructor'
```

## Methods

### `init(gulpInst)`

The `init()` method of a registry is called at the very end of the `registry()` function. The gulp instance passed as the only argument (`gulpInst`) can be used to pre-define tasks using
`gulpInst.task(taskName, fn)`.

#### Parameters

| parameter | type | note |
|:---------:|:----:|------|
| gulpInst | object | Instance of gulp. |

### `get(name)`

The `get()` method receives a task `name` for the custom registry to resolve and return, or `undefined` if no task with that name exists.

#### Parameters

| parameter | type | note |
|:---------:|:----:|------|
| name | string | Name of the task to be retrieved. |

### `set(name, fn)`

The `set()` method receives a task `name` and `fn`. This is called internally by `task()` to provide user-registered tasks to custom registries.

#### Parameters

| parameter | type | note |
|:---------:|:----:|------|
| name | string | Name of the task to be set. |
| fn | function | Task function to be set. |

### `tasks()`

Must return an object listing all tasks in the registry.

## Use Cases

### Sharing Tasks

To share common tasks with all your projects, you can expose an `init` method on the registry and it will receive the an instance of gulp as the only argument. You can then use `gulpInst.task(name, fn)` to register pre-defined tasks.

For example, you might want to share a `clean` task:

```js
const fs = require('fs');
const util = require('util');

const DefaultRegistry = require('undertaker-registry');
const del = require('del');

function CommonRegistry(opts){
DefaultRegistry.call(this);

opts = opts || {};

this.buildDir = opts.buildDir || './build';
}

util.inherits(CommonRegistry, DefaultRegistry);

CommonRegistry.prototype.init = function(gulpInst) {
const buildDir = this.buildDir;
const exists = fs.existsSync(buildDir);

if(exists){
throw new Error('Cannot initialize common tasks. ' + buildDir + ' directory exists.');
}

gulpInst.task('clean', function(){
return del([buildDir]);
});
}

module.exports = CommonRegistry;
```

Then to use it in a project:

```js
const { registry, series, task } = require('gulp');
const CommonRegistry = require('myorg-common-tasks');

registry(new CommonRegistry({ buildDir: '/dist' }));

task('build', series('clean', function build(cb) {
// do things
cb();
}));
```

### Sharing Functionality

By controlling how tasks are added to the registry, you can decorate them.

For example, if you wanted all tasks to share some data, you can use a custom registry to bind them to that data. Be sure to return the altered task, as per the description of registry methods above:

```js
const { registry, series, task } = require('gulp');
const util = require('util');
const DefaultRegistry = require('undertaker-registry');

// Some task defined somewhere else
const BuildRegistry = require('./build.js');
const ServeRegistry = require('./serve.js');

function ConfigRegistry(config){
DefaultRegistry.call(this);
this.config = config;
}

util.inherits(ConfigRegistry, DefaultRegistry);

ConfigRegistry.prototype.set = function set(name, fn) {
// The `DefaultRegistry` uses `this._tasks` for storage.
var task = this._tasks[name] = fn.bind(this.config);
return task;
};

registry(new BuildRegistry());
registry(new ServeRegistry());

// `registry` will reset each task in the registry with
// `ConfigRegistry.prototype.set` which will bind them to the config object.
registry(new ConfigRegistry({
src: './src',
build: './build',
bindTo: '0.0.0.0:8888'
}));

task('default', series('clean', 'build', 'serve', function(cb) {
console.log('Server bind to ' + this.bindTo);
console.log('Serving' + this.build);
cb();
}));
```

## Examples

* [undertaker-registry][undertaker-registry-example]: The Gulp 4 default registry.
* [undertaker-common-tasks][undertaker-common-tasks-example]: Proof-of-concept custom registry that pre-defines tasks.
* [undertaker-task-metadata][undertaker-task-metadata-example]: Proof-of-concept custom registry that attaches metadata to each task.

[registry-api-docs]: ../api/registry.md
[undertaker-registry-example]: https://github.com/gulpjs/undertaker-registry
[undertaker-common-tasks-example]: https://github.com/gulpjs/undertaker-common-tasks
[undertaker-task-metadata-example]: https://github.com/gulpjs/undertaker-task-metadata
33 changes: 26 additions & 7 deletions docs/api/registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ sidebar_label: registry()

# registry()


Allows custom registries to be plugged into the task system, which can provide shared tasks or augmented functionality.

**Note:** Only tasks registered with `task()` will be provided to the custom registry. The task functions passed directly to `series()` or `parallel()` will not be provided - if you need to customize the registry behavior, compose tasks with string references.
Expand Down Expand Up @@ -50,14 +49,34 @@ If a `registryInstance` is passed, nothing will be returned. If no arguments are

### Errors

When a constructor (instead of an instance) is passed as `registryInstance`, throws an error with the message, "Custom registries must be instantiated, but it looks like you passed a constructor".
#### Incorrect parameter

When a constructor (instead of an instance) is passed as `registryInstance`, throws an error with the message:

> Custom registries must be instantiated, but it looks like you passed a constructor.

#### Missing `get` method

When a registry without a `get` method is passed as `registryInstance`, throws an error with the message:

> Custom registry must have `get` function.

#### Missing `set` method

When a registry without a `set` method is passed as `registryInstance`, throws an error with the message:

> Custom registry must have `set` function.

#### Missing `init` method

When a registry without an `init` method is passed as `registryInstance`, throws an error with the message:

When a registry without a `get` method is passed as `registryInstance`, throws an error with the message, "Custom registry must have `get` function".
> Custom registry must have `init` function"

When a registry without a `set` method is passed as `registryInstance`, throws an error with the message, "Custom registry must have `set` function".
#### Missing `tasks` method

When a registry without an `init` method is passed as `registryInstance`, throws an error with the message, "Custom registry must have `init` function"
When a registry without a `tasks` method is passed as `registryInstance`, throws an error with the message:

When a registry without a `tasks` method is passed as `registryInstance`, throws an error with the message, "Custom registry must have `tasks` function".
> Custom registry must have `tasks` function.

[creating-custom-registries]: ../documentation-missing.md
[creating-custom-registries]: ../advanced/creating-custom-registries.md
100 changes: 100 additions & 0 deletions docs/recipes/automate-releases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
id: automate-releases
title: Automate Releases
hide_title: true
sidebar_label: Automate Releases
---

# Automate Releases

If your project follows a semantic versioning, it may be a good idea to automatize the steps needed to do a release.
The recipe below bumps the project version, commits the changes to git and creates a new GitHub release.

For publishing a GitHub release you'll need to [create a personal access token](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token) and add it to your project. However, we don't want to commit it, so we'll use [`dotenv`](https://www.npmjs.com/package/dotenv) to load it from a git-ignored `.env` file:

```
GH_TOKEN=ff34885...
```

Don't forget to add `.env` to your `.gitignore`.

Next, install all the necessary dependencies for this recipe:

```sh
npm install --save-dev conventional-recommended-bump conventional-changelog-cli conventional-github-releaser dotenv execa
```

Based on your environment, setup and preferences, your release workflow might look something like this:

``` js
const gulp = require('gulp');
const conventionalRecommendedBump = require('conventional-recommended-bump');
const conventionalGithubReleaser = require('conventional-github-releaser');
const execa = require('execa');
const fs = require('fs');
const { promisify } = require('util');
const dotenv = require('dotenv');

// load environment variables
const result = dotenv.config();

if (result.error) {
throw result.error;
}

// Conventional Changelog preset
const preset = 'angular';
// print output of commands into the terminal
const stdio = 'inherit';

async function bumpVersion() {
// get recommended version bump based on commits
const { releaseType } = await promisify(conventionalRecommendedBump)({ preset });
// bump version without committing and tagging
await execa('npm', ['version', releaseType, '--no-git-tag-version'], {
stdio,
});
}

async function changelog() {
await execa(
'npx',
[
'conventional-changelog',
'--preset',
preset,
'--infile',
'CHANGELOG.md',
'--same-file',
],
{ stdio }
);
}

async function commitTagPush() {
// even though we could get away with "require" in this case, we're taking the safe route
// because "require" caches the value, so if we happen to use "require" again somewhere else
// we wouldn't get the current value, but the value of the last time we called "require"
const { version } = JSON.parse(await promisify(fs.readFile)('package.json'));
const commitMsg = `chore: release ${version}`;
await execa('git', ['add', '.'], { stdio });
await execa('git', ['commit', '--message', commitMsg], { stdio });
await execa('git', ['tag', `v${version}`], { stdio });
await execa('git', ['push', '--follow-tags'], { stdio });
}

function githubRelease(done) {
conventionalGithubReleaser(
{ type: 'oauth', token: process.env.GH_TOKEN },
{ preset },
done
);
}

exports.release = gulp.series(
bumpVersion,
changelog,
commitTagPush,
githubRelease
);
```
Loading