Skip to content

Commit

Permalink
feat: add pkgRoot option to publish a sub-directory
Browse files Browse the repository at this point in the history
  • Loading branch information
pvdlg committed Dec 20, 2017
1 parent abcc70b commit e36a56b
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 71 deletions.
37 changes: 32 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,13 @@ Use either `NPM_TOKEN` for token authentication or `NPM_USERNAME`, `NPM_PASSWORD

### Options

| Options | Description | Default |
| ------------ | ---------------------------------------------------------------------------------------------------------------------- | ------- |
| `npmPublish` | Whether to publish the `npm` package to the registry. If `false` the `package.json` version will still be updated. | `true` |
| `tarballDir` | Directory path in which to generate the the package tarball. If `false` the tarball is not be kept on the file system. | `false` |
| Options | Description | Default |
|--------------|---------------------------------------------------------------------------------------------------------------------|---------|
| `npmPublish` | Whether to publish the `npm` package to the registry. If `false` the `package.json` version will still be updated. | `true` |
| `pkgRoot` | Directory path to publish. | `.` |
| `tarballDir` | Directory path in which to write the the package tarball. If `false` the tarball is not be kept on the file system. | `false` |

**Note**: The `pkgRoot` directory must contains a `package.json`. The version will be updated only in the `package.json` and `npm-shrinkwrap.json` within the `pkgRoot` directory.

### Npm configuration

Expand Down Expand Up @@ -74,7 +77,7 @@ Each individual plugin can be disabled, replaced or used with other plugins in t
}
```

The `npmPublish` and `tarballDir` option can be used to skip the publishing to the `npm` registry and instead, release the package tarball with another plugin. For example with the [github](https://github.com/semantic-release/github):
The `npmPublish` and `tarballDir` option can be used to skip the publishing to the `npm` registry and instead, release the package tarball with another plugin. For example with the [github](https://github.com/semantic-release/github) plugin:

```json
{
Expand All @@ -95,3 +98,27 @@ The `npmPublish` and `tarballDir` option can be used to skip the publishing to t
}
}
```

When publishing from a sub-directory with the `pkgRoot` option, the `package.json` and `npm-shrinkwrap.json` updated with the new version can be moved to another directory with a `postpublish` [npm script](https://docs.npmjs.com/misc/scripts). For example with the [git](https://github.com/semantic-release/git) plugin:

```json
{
"release": {
"verifyConditions": ["@semantic-release/conditions-travis", "@semantic-release/npm", "@semantic-release/git"],
"getLastRelease": "@semantic-release/npm",
"publish": [
{
"path": "@semantic-release/npm",
"pkgRoot": "dist"
},
{
"path": "@semantic-release/git",
"assets": ["package.json", "npm-shrinkwrap.json"]
},
]
},
"scripts": {
"postpublish": "cp -r dist/package.json . && cp -r dist/npm-shrinkwrap.json ."
}
}
```
21 changes: 16 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const getLastReleaseNpm = require('./lib/get-last-release');
let verified;

async function verifyConditions(pluginConfig, {options, logger}) {
// If the npm publish plugin is used and has `npmPublish` or `tarballDir` configured, validate them now in order to prevent any release if the configuration is wrong
// If the npm publish plugin is used and has `npmPublish`, `tarballDir` or `pkgRoot` configured, validate them now in order to prevent any release if the configuration is wrong
if (options.publish) {
const publishPlugin = castArray(options.publish).find(
config => config.path && config.path === '@semantic-release/npm'
Expand All @@ -19,18 +19,29 @@ async function verifyConditions(pluginConfig, {options, logger}) {
if (publishPlugin && publishPlugin.tarballDir) {
pluginConfig.tarballDir = publishPlugin.tarballDir;
}
if (publishPlugin && publishPlugin.pkgRoot) {
pluginConfig.pkgRoot = publishPlugin.pkgRoot;
}
}
setLegacyToken();
const pkg = await getPkg();
const pkg = await getPkg(pluginConfig.pkgRoot);
await verifyNpm(pluginConfig, pkg, logger);
verified = true;
}

async function getLastRelease(pluginConfig, {logger}) {
async function getLastRelease(pluginConfig, {options, logger}) {
setLegacyToken();
// Reload package.json in case a previous external step updated it
const pkg = await getPkg();
const pkg = await getPkg(pluginConfig.pkgRoot);
if (!verified) {
if (options.publish) {
const publishPlugin = castArray(options.publish).find(
config => config.path && config.path === '@semantic-release/npm'
);
if (publishPlugin && publishPlugin.pkgRoot) {
pluginConfig.pkgRoot = publishPlugin.pkgRoot;
}
}
await verifyNpm(pluginConfig, pkg, logger);
verified = true;
}
Expand All @@ -40,7 +51,7 @@ async function getLastRelease(pluginConfig, {logger}) {
async function publish(pluginConfig, {nextRelease: {version}, logger}) {
setLegacyToken();
// Reload package.json in case a previous external step updated it
const pkg = await getPkg();
const pkg = await getPkg(pluginConfig.pkgRoot);
if (!verified) {
await verifyNpm(pluginConfig, pkg, logger);
verified = true;
Expand Down
24 changes: 13 additions & 11 deletions lib/get-pkg.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
const readPkgUp = require('read-pkg-up');
const readPkg = require('read-pkg');
const SemanticReleaseError = require('@semantic-release/error');

module.exports = async () => {
const {pkg} = await readPkgUp();
module.exports = async pkgRoot => {
try {
const pkg = await readPkg(pkgRoot);
if (!pkg.name) {
throw new SemanticReleaseError('No "name" found in package.json.', 'ENOPKGNAME');
}

if (!pkg) {
throw new SemanticReleaseError('A package.json file is required to release on npm.', 'ENOPKG');
return pkg;
} catch (err) {
if (err.code === 'ENOENT') {
throw new SemanticReleaseError('A package.json file is required to release on npm.', 'ENOPKG');
}
throw err;
}

if (!pkg.name) {
throw new SemanticReleaseError('No "name" found in package.json.', 'ENOPKGNAME');
}

return pkg;
};
9 changes: 5 additions & 4 deletions lib/publish.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@ const execa = require('execa');
const getRegistry = require('./get-registry');
const updatePackageVersion = require('./update-package-version');

module.exports = async ({npmPublish, tarballDir}, {publishConfig, name}, version, logger) => {
module.exports = async ({npmPublish, tarballDir, pkgRoot}, {publishConfig, name}, version, logger) => {
const basePath = pkgRoot || '.';
const registry = await getRegistry(publishConfig, name);
await updatePackageVersion(version, logger);
await updatePackageVersion(version, basePath, logger);

if (tarballDir) {
logger.log('Creating npm package version %s', version);
const tarball = await execa.stdout('npm', ['pack']);
const tarball = await execa.stdout('npm', ['pack', `./${basePath}`]);
await move(tarball, path.join(tarballDir.trim(), tarball));
}

if (npmPublish !== false) {
logger.log('Publishing version %s to npm registry', version);
const shell = await execa('npm', ['publish', '--registry', registry]);
const shell = await execa('npm', ['publish', `./${basePath}`, '--registry', registry]);
process.stdout.write(shell.stdout);
}
};
19 changes: 11 additions & 8 deletions lib/update-package-version.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
const path = require('path');
const {readFile, writeFile, pathExists} = require('fs-extra');

module.exports = async (version, logger) => {
const pkg = (await readFile('./package.json')).toString();
module.exports = async (version, basePath, logger) => {
const packagePath = path.join(basePath, 'package.json');
const shrinkwrapPath = path.join(basePath, 'npm-shrinkwrap.json');
const pkg = (await readFile(packagePath)).toString();

await writeFile('./package.json', replaceVersion(pkg, version));
logger.log('Wrote version %s to package.json', version);
await writeFile(packagePath, replaceVersion(pkg, version));
logger.log('Wrote version %s to %s', version, packagePath);

if (await pathExists('./npm-shrinkwrap.json')) {
const shrinkwrap = (await readFile('./npm-shrinkwrap.json')).toString();
await writeFile('./npm-shrinkwrap.json', replaceVersion(shrinkwrap, version));
logger.log('Wrote version %s to npm-shrinkwrap.json', version);
if (await pathExists(shrinkwrapPath)) {
const shrinkwrap = (await readFile(shrinkwrapPath)).toString();
await writeFile(shrinkwrapPath, replaceVersion(shrinkwrap, version));
logger.log('Wrote version %s to %s', version, shrinkwrapPath);
}
};

Expand Down
6 changes: 5 additions & 1 deletion lib/verify.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const SemanticReleaseError = require('@semantic-release/error');
const getRegistry = require('./get-registry');
const setNpmrcAuth = require('./set-npmrc-auth');

module.exports = async ({npmPublish, tarballDir}, pkg, logger) => {
module.exports = async ({npmPublish, tarballDir, pkgRoot}, pkg, logger) => {
if (!isUndefined(npmPublish) && !isBoolean(npmPublish)) {
throw new SemanticReleaseError('The "npmPublish" options, if defined, must be a Boolean.', 'EINVALIDNPMPUBLISH');
}
Expand All @@ -13,6 +13,10 @@ module.exports = async ({npmPublish, tarballDir}, pkg, logger) => {
throw new SemanticReleaseError('The "tarballDir" options, if defined, must be a String.', 'EINVALIDTARBALLDIR');
}

if (!isUndefined(pkgRoot) && !isString(pkgRoot)) {
throw new SemanticReleaseError('The "pkgRoot" options, if defined, must be a String.', 'EINVALIDPKGROOT');
}

const registry = await getRegistry(pkg.publishConfig, pkg.name);
await setNpmrcAuth(registry, logger);
try {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"nerf-dart": "^1.0.0",
"npm-conf": "^1.1.3",
"npm-registry-client": "^8.5.0",
"read-pkg-up": "^3.0.0",
"read-pkg": "^3.0.0",
"registry-auth-token": "^3.3.1"
},
"devDependencies": {
Expand Down
15 changes: 12 additions & 3 deletions test/get-pkg.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import test from 'ava';
import {writeJson, writeFile} from 'fs-extra';
import {outputJson, writeFile} from 'fs-extra';
import tempy from 'tempy';
import getPkg from '../lib/get-pkg';

Expand All @@ -18,13 +18,22 @@ test.afterEach.always(() => {

test.serial('Verify name and return parsed package.json', async t => {
const pkg = {name: 'package', version: '0.0.0'};
await writeJson('./package.json', pkg);
await outputJson('./package.json', pkg);

const result = await getPkg();
t.is(pkg.name, result.name);
t.is(pkg.version, result.version);
});

test.serial('Verify name and return parsed package.json from a sub-directory', async t => {
const pkg = {name: 'package', version: '0.0.0'};
await outputJson('./dist/package.json', pkg);

const result = await getPkg('dist');
t.is(pkg.name, result.name);
t.is(pkg.version, result.version);
});

test.serial('Throw error if missing package.json', async t => {
const error = await t.throws(getPkg());

Expand All @@ -33,7 +42,7 @@ test.serial('Throw error if missing package.json', async t => {
});

test.serial('Throw error if missing package name', async t => {
await writeJson('./package.json', {version: '0.0.0'});
await outputJson('./package.json', {version: '0.0.0'});

const error = await t.throws(getPkg());

Expand Down
Loading

0 comments on commit e36a56b

Please sign in to comment.