Skip to content

Commit

Permalink
Feature: publish into a npm repository (#19)
Browse files Browse the repository at this point in the history
* Initial implementation

* Fix

* Fix UT

* Finalize implementation

---------

Co-authored-by: Damien Guérin <[email protected]>
  • Loading branch information
gigaga and Damien Guérin authored Dec 18, 2023
1 parent 893a8df commit f0c6a37
Show file tree
Hide file tree
Showing 10 changed files with 811 additions and 93 deletions.
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,15 +141,10 @@ Global Options
- **Publish one or several OpenAPI files** specified in [configuration file](#configuration-file) and written in YAML or JSON
- **Bundle OpenAPI files** to merge all the files before publishing. By using (OpenAPI remote reference)[https://swagger.io/docs/specification/using-ref/], you can work on several files. It improves the maintainability and avoid having a large OpenAPI file!
- **Context uses**: it is possible to serve a same specification in [several contexts](#context-usage) (internal, public, etc.)
- **Context uses**: it is possible to publish a same specification in [several contexts](#context-usage) (internal, public, etc.)

#### Usage

> **Warning**
>
> To publish your OpenAPI files, the `curl` command must be available on the host where `openapi-dev-tool` is executed.
> `curl` is available under UNIX systems and Windows 10. For Windows < 10, you can download it from https://curl.haxx.se/windows/.
Usage can be displayed by typing the command

> `openapi-dev-tool help publish`
Expand All @@ -158,18 +153,21 @@ Usage can be displayed by typing the command
openapi-dev-tool publish
Publish into a software repository server (like Sonatype Nexus) one or serveral OpenAPI specifications
files
files. The repo can be a Maven repository or a npm repository.
Command Options
-b, --skipBundle Skips bundle openapi files into one before serving or publishing, default is
false
-g, --groupId string GroupId used in repo server, default is com.openapi
-t, --repoType string Repository server type. maven or npm are possible, default is maven
-g, --groupId string GroupId used in repo server, default is com.openapi for maven repo and @myCompany for npm repo
-s, --repoServer string Repository server url to store OpenAPI specification files
--repoSnapshotsServer string Repository server url to store OpenAPI snapshots specification files. If specified,
--repoServer will be used to store OpenAPI releases specification files.
-u, --repoUser string Repository server username
-p, --repoPassword string Repository server password
For maven repo only
-u, --repoUser string Repository server username. Authentication by using user/password
-p, --repoPassword string Repository server password. Authentication by using user/password
--repoToken string Repository server token. Authentication by using token. For npm repo only
-x, --skipValidation Skips OpenAPI validation process, default is false
Global Options
Expand All @@ -187,6 +185,10 @@ Global Options

#### Usage

> **Warning**
>
> Publishing locally is only possible to publish in local Maven repository and it uses the `mvn` command from PATH.
Usage can be displayed by typing the command

> `openapi-dev-tool help publish-local`
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lyra-network/openapi-dev-tool",
"version": "9.7.4",
"version": "9.8.0",
"description": "OpenAPI Development tool",
"main": "src/lib.js",
"scripts": {
Expand All @@ -19,8 +19,8 @@
"bin": {
"openapi-dev-tool": "./src/cli.js"
},
"engines" : {
"node" : ">=14.14.0"
"engines": {
"node": ">=14.14.0"
},
"repository": {
"type": "git",
Expand Down Expand Up @@ -59,6 +59,7 @@
"json-pointer": "^0.6.2",
"json-schema-ref-parser": "^9.0.9",
"jsonpath-plus": "^7.2.0",
"libnpmpublish": "^9.0.3",
"lodash.clonedeep": "^4.5.0",
"lodash.flatmap": "^4.5.0",
"lodash.flatten": "^4.4.0",
Expand Down
2 changes: 1 addition & 1 deletion src/commands/publish-local.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function publishLocal(config = { config: { specs: [] } }) {
api
);
}
archive = await generateSpecsArchive(api, fileToArchive);
archive = await generateSpecsArchive(api, [fileToArchive], 'maven');

// Find doublon
if (artifactIds.indexOf(paramCase(api.info.title)) !== -1) {
Expand Down
101 changes: 75 additions & 26 deletions src/commands/publish.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import colors from 'colors';
import path from 'path';
import { paramCase } from 'change-case';
import settle from 'promise-settle';
import { publish as npmPublish } from 'libnpmpublish';
import fs from 'fs';

import { generateSpecsArchive } from '../lib/archiver.js';
import { getTempDir } from '../lib/utils.js';
Expand All @@ -26,18 +28,35 @@ export function publish(config = { config: { specs: [] } }) {
new Promise(async (resolve, reject) => {
try {
const api = await bundleSpec(config, spec);
const tempDir = getTempDir();

let fileToArchive = spec.file;
let archive;
if (!config.skipBundle) {
fileToArchive = writeOpenApiDocumentToFile(
getTempDir().name,
tempDir.name,
path.basename(spec.file),
spec.file,
api
);
}
archive = await generateSpecsArchive(api, fileToArchive);
const files = [fileToArchive];
// If npm, we have to add a package.json file
const packageNpm = {
name: `${config.groupId}/${paramCase(api.info.title)}`,
version: api.info.version,
main: path.basename(spec.file),
};
if (config.repoType === 'npm') {
var packageNpmJson = JSON.stringify(packageNpm, null, 2);
fs.writeFileSync(
`${tempDir.name}/package.json`,
packageNpmJson,
'utf8'
);
files.push(`${tempDir.name}/package.json`);
}
archive = await generateSpecsArchive(api, files, config.repoType);

// Find doublon
if (artifactIds.indexOf(paramCase(api.info.title)) != -1) {
Expand Down Expand Up @@ -80,42 +99,72 @@ export function publish(config = { config: { specs: [] } }) {

if (
config.repoSnapshotsServer &&
api.info.version.match(/-snapshot/i)
api.info.version.match(/-snapshot/i) &&
config.repoType === 'maven'
) {
server = config.repoSnapshotsServer;
}

artifactIds.push(paramCase(api.info.title));

deployer.deploy(
{
groupId: config.groupId,
artifactId: paramCase(api.info.title),
version: api.info.version,
packaging: 'zip',
auth: {
username: config.repoUser,
password: config.repoPassword,
if (config.repoType === 'maven') {
deployer.deploy(
{
groupId: config.groupId,
artifactId: paramCase(api.info.title),
version: api.info.version,
packaging: 'zip',
auth: {
username: config.repoUser,
password: config.repoPassword,
},
pomDir: path.dirname(archive),
url: server,
artifact: archive,
insecure: true,
quiet: !config.verbose,
},
pomDir: path.dirname(archive),
url: server,
artifact: archive,
insecure: true,
quiet: !config.verbose,
},
(err) => {
if (err) {
(err) => {
if (err) {
reject();
console.error(
colors.red(
`Unable to publish specs '${paramCase(api.info.title)}'`
)
);
} else {
resolve();
}
}
);
} else {
let auth = { token: config.repoToken };
if (config.repoUser && config.repoPassword) {
auth = {
auth: Buffer.from(
`${config.repoUser}:${config.repoPassword}`
).toString('base64'),
};
}
npmPublish(packageNpm, fs.readFileSync(archive), {
registry: config.repoServer,
forceAuth: { ...auth },
strictSSL: false,
})
.then(() => {
resolve();
})
.catch((err) => {
reject();
console.error(
colors.red(
`Unable to publish specs '${paramCase(api.info.title)}'`
`Unable to publish specs '${paramCase(
api.info.title
)}': ${err}`
)
);
} else {
resolve();
}
}
);
});
}
} catch (err) {
console.error(
colors.red(
Expand Down
32 changes: 21 additions & 11 deletions src/lib/archiver.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,33 @@ import { getTempDir } from './utils.js';
// contains the whole of OpenAPI files
// ##################################################################

export function generateSpecsArchive(api, apiFilename) {
export function generateSpecsArchive(api, files, repoType) {
return new Promise((resolve) => {
const workDir = getTempDir();
const zipOutput = createWriteStream(
`${workDir.name}/${paramCase(api.info.title)}-${api.info.version}.zip`
);
const zip = archiver('zip');
// For maven, we generate a zip file
// For npm, we generate a tar file
const fileResult =
repoType === 'maven'
? `${workDir.name}/${paramCase(api.info.title)}-${api.info.version}.zip`
: `${workDir.name}/${paramCase(api.info.title)}-${
api.info.version
}.tar`;
const zipOutput = createWriteStream(fileResult);
const zip =
repoType === 'maven' ? archiver('zip') : archiver('tar', { gzip: true });

zip.pipe(zipOutput);
zip.directory(path.dirname(apiFilename), '/');
if (files && files.length > 0) {
files.forEach((element) => {
zip.file(element, {
name:
repoType === 'maven' ? '/' : 'package/' + path.basename(element),
});
});
}

zip.finalize();

zipOutput.on('close', () =>
resolve(
`${workDir.name}/${paramCase(api.info.title)}-${api.info.version}.zip`
)
);
zipOutput.on('close', () => resolve(fileResult));
});
}
28 changes: 22 additions & 6 deletions src/lib/config-definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,20 @@ const publishOptionsDefinitions = [
description:
'Skips bundle openapi files into one before serving or publishing, default is false',
},
{
name: 'repoType',
alias: 't',
type: String,
defaultValue: 'maven',
description:
'Repository server type. maven or npm are possible, default is maven',
},
{
name: 'groupId',
alias: 'g',
type: String,
defaultValue: 'com.openapi',
description: 'GroupId used in repo server, default is com.openapi',
description:
'GroupId used in repo server, default is com.openapi for maven repo and @myCompany for npm repo',
},
{
name: 'repoServer',
Expand All @@ -131,19 +139,27 @@ const publishOptionsDefinitions = [
name: 'repoSnapshotsServer',
type: String,
description:
'Repository server url to store OpenAPI snapshots specification files. If specified, --repoServer will be used to store OpenAPI releases specification files.',
'Repository server url to store OpenAPI snapshots specification files. If specified, --repoServer will be used to store OpenAPI releases specification files. For maven repo only',
},
{
name: 'repoUser',
alias: 'u',
type: String,
description: 'Repository server username',
description:
'Repository server username. Authentication by using user/password',
},
{
name: 'repoPassword',
alias: 'p',
type: String,
description: 'Repository server password',
description:
'Repository server password. Authentication by using user/password',
},
{
name: 'repoToken',
type: String,
description:
'Repository server token. Authentication by using token. For npm repo only',
},
{
name: 'skipValidation',
Expand Down Expand Up @@ -231,7 +247,7 @@ const publishUsage = commandLineUsage([
{
header: 'openapi-dev-tool publish',
content:
'Publish into a software repository server (like Sonartype Nexus) one or serveral OpenAPI specifications files',
'Publish into a software repository server (like Sonartype Nexus) one or serveral OpenAPI specifications files. The repo can be a Maven repository or a npm repository.',
},
{
header: 'Command Options',
Expand Down
Loading

0 comments on commit f0c6a37

Please sign in to comment.