@redocly/cli 1.0.0-beta.96
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/bin/cli.js +3 -0
- package/lib/__mocks__/utils.d.ts +17 -0
- package/lib/__mocks__/utils.js +14 -0
- package/lib/__tests__/commands/bundle.test.d.ts +1 -0
- package/lib/__tests__/commands/bundle.test.js +92 -0
- package/lib/__tests__/commands/push-region.test.d.ts +1 -0
- package/lib/__tests__/commands/push-region.test.js +55 -0
- package/lib/__tests__/commands/push.test.d.ts +1 -0
- package/lib/__tests__/commands/push.test.js +153 -0
- package/lib/__tests__/utils.test.d.ts +1 -0
- package/lib/__tests__/utils.test.js +41 -0
- package/lib/assert-node-version.d.ts +1 -0
- package/lib/assert-node-version.js +10 -0
- package/lib/commands/bundle.d.ts +19 -0
- package/lib/commands/bundle.js +128 -0
- package/lib/commands/join.d.ts +7 -0
- package/lib/commands/join.js +421 -0
- package/lib/commands/lint.d.ts +11 -0
- package/lib/commands/lint.js +80 -0
- package/lib/commands/login.d.ts +6 -0
- package/lib/commands/login.js +28 -0
- package/lib/commands/preview-docs/index.d.ts +12 -0
- package/lib/commands/preview-docs/index.js +141 -0
- package/lib/commands/preview-docs/preview-server/default.hbs +24 -0
- package/lib/commands/preview-docs/preview-server/hot.js +42 -0
- package/lib/commands/preview-docs/preview-server/oauth2-redirect.html +21 -0
- package/lib/commands/preview-docs/preview-server/preview-server.d.ts +5 -0
- package/lib/commands/preview-docs/preview-server/preview-server.js +120 -0
- package/lib/commands/preview-docs/preview-server/server.d.ts +23 -0
- package/lib/commands/preview-docs/preview-server/server.js +85 -0
- package/lib/commands/push.d.ts +25 -0
- package/lib/commands/push.js +247 -0
- package/lib/commands/split/__tests__/index.test.d.ts +1 -0
- package/lib/commands/split/__tests__/index.test.js +70 -0
- package/lib/commands/split/index.d.ts +8 -0
- package/lib/commands/split/index.js +279 -0
- package/lib/commands/split/types.d.ts +37 -0
- package/lib/commands/split/types.js +52 -0
- package/lib/commands/stats.d.ts +5 -0
- package/lib/commands/stats.js +92 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +269 -0
- package/lib/js-utils.d.ts +3 -0
- package/lib/js-utils.js +16 -0
- package/lib/types.d.ts +13 -0
- package/lib/types.js +5 -0
- package/lib/utils.d.ts +28 -0
- package/lib/utils.js +260 -0
- package/package.json +54 -0
- package/src/__mocks__/utils.ts +11 -0
- package/src/__tests__/commands/bundle.test.ts +120 -0
- package/src/__tests__/commands/push-region.test.ts +51 -0
- package/src/__tests__/commands/push.test.ts +156 -0
- package/src/__tests__/utils.test.ts +50 -0
- package/src/assert-node-version.ts +8 -0
- package/src/commands/bundle.ts +178 -0
- package/src/commands/join.ts +488 -0
- package/src/commands/lint.ts +110 -0
- package/src/commands/login.ts +19 -0
- package/src/commands/preview-docs/index.ts +188 -0
- package/src/commands/preview-docs/preview-server/default.hbs +24 -0
- package/src/commands/preview-docs/preview-server/hot.js +42 -0
- package/src/commands/preview-docs/preview-server/oauth2-redirect.html +21 -0
- package/src/commands/preview-docs/preview-server/preview-server.ts +150 -0
- package/src/commands/preview-docs/preview-server/server.ts +91 -0
- package/src/commands/push.ts +355 -0
- package/src/commands/split/__tests__/fixtures/spec.json +70 -0
- package/src/commands/split/__tests__/fixtures/webhooks.json +88 -0
- package/src/commands/split/__tests__/index.test.ts +96 -0
- package/src/commands/split/index.ts +349 -0
- package/src/commands/split/types.ts +73 -0
- package/src/commands/stats.ts +115 -0
- package/src/index.ts +311 -0
- package/src/js-utils.ts +12 -0
- package/src/types.ts +13 -0
- package/src/utils.ts +300 -0
- package/tsconfig.json +9 -0
- package/tsconfig.tsbuildinfo +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Redocly CLI toolset
|
|
2
|
+
|
|
3
|
+
Redocly CLI toolbox with rich validation and bundling features.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
Currently, @redocly/cli supports these features:
|
|
13
|
+
|
|
14
|
+
- [x] Multifile validation. No need to bundle your file before validation.
|
|
15
|
+
- [x] Lightning-fast validation. Lint a 1 Mb file in less than one second.
|
|
16
|
+
- [x] Built-in rules for common validations.
|
|
17
|
+
- [x] Configurable severity levels for each rule.
|
|
18
|
+
- [x] Human-readable error messages with codeframes and stylish format options.
|
|
19
|
+
- [x] Intuitive suggestions for misspelled types or references.
|
|
20
|
+
- [x] Easy to implement custom rules.
|
|
21
|
+
- [x] Bundle a multifile definition into a single file.
|
|
22
|
+
- [x] Decorators to modify a validated definition during bundling.
|
|
23
|
+
- [x] Preview reference docs for local development.
|
|
24
|
+
- [x] Support for OpenAPI 2 (fka Swagger) and OpenAPI 3.0.
|
|
25
|
+
- [ ] Support for OpenAPI 3.1 ([coming soon](https://github.com/Redocly/redocly-cli/issues/160)).
|
|
26
|
+
|
|
27
|
+
## What makes this tool different
|
|
28
|
+
|
|
29
|
+
Unlike other OpenAPI linters, `@redocly/cli` defines the possible type tree of a valid OpenAPI definition and then traverses it. This approach is very similar to how linters for programming languages work and results in major performance benefits over other approaches. Extend functionality at different points in the lifecycle with preprocessors, rules, and decorators.
|
|
30
|
+
|
|
31
|
+
## TLDR
|
|
32
|
+
|
|
33
|
+
`npx @redocly/cli lint path-to-root-file.yaml`
|
|
34
|
+
|
|
35
|
+
## [Read the docs](https://redoc.ly/docs/cli/)
|
|
36
|
+
|
|
37
|
+
## Credits
|
|
38
|
+
|
|
39
|
+
Thanks to [graphql-js](https://github.com/graphql/graphql-js) and [eslint](https://github.com/eslint/eslint) for inspiration of the definition traversal approach and to [Swagger](https://github.com/swagger-api/swagger-editor), [Spectral](https://github.com/stoplightio/spectral), and [OAS-Kit](https://github.com/Mermade/oas-kit) for inspiring the ruleset.
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/// <reference types="jest" />
|
|
2
|
+
export declare const getFallbackEntryPointsOrExit: jest.Mock<any, [entrypoints: any]>;
|
|
3
|
+
export declare const getTotals: jest.Mock<{
|
|
4
|
+
errors: number;
|
|
5
|
+
}, []>;
|
|
6
|
+
export declare const dumpBundle: jest.Mock<string, []>;
|
|
7
|
+
export declare const slash: jest.Mock<any, any>;
|
|
8
|
+
export declare const pluralize: jest.Mock<any, any>;
|
|
9
|
+
export declare const getExecutionTime: jest.Mock<any, any>;
|
|
10
|
+
export declare const printExecutionTime: jest.Mock<any, any>;
|
|
11
|
+
export declare const printUnusedWarnings: jest.Mock<any, any>;
|
|
12
|
+
export declare const printLintTotals: jest.Mock<any, any>;
|
|
13
|
+
export declare const getOutputFileName: jest.Mock<{
|
|
14
|
+
outputFile: string;
|
|
15
|
+
ext: string;
|
|
16
|
+
}, []>;
|
|
17
|
+
export declare const handleError: jest.Mock<any, any>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleError = exports.getOutputFileName = exports.printLintTotals = exports.printUnusedWarnings = exports.printExecutionTime = exports.getExecutionTime = exports.pluralize = exports.slash = exports.dumpBundle = exports.getTotals = exports.getFallbackEntryPointsOrExit = void 0;
|
|
4
|
+
exports.getFallbackEntryPointsOrExit = jest.fn((entrypoints) => entrypoints.map(() => ({ path: '' })));
|
|
5
|
+
exports.getTotals = jest.fn(() => ({ errors: 0 }));
|
|
6
|
+
exports.dumpBundle = jest.fn(() => '');
|
|
7
|
+
exports.slash = jest.fn();
|
|
8
|
+
exports.pluralize = jest.fn();
|
|
9
|
+
exports.getExecutionTime = jest.fn();
|
|
10
|
+
exports.printExecutionTime = jest.fn();
|
|
11
|
+
exports.printUnusedWarnings = jest.fn();
|
|
12
|
+
exports.printLintTotals = jest.fn();
|
|
13
|
+
exports.getOutputFileName = jest.fn(() => ({ outputFile: 'test.yaml', ext: 'yaml' }));
|
|
14
|
+
exports.handleError = jest.fn();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const openapi_core_1 = require("@redocly/openapi-core");
|
|
13
|
+
const bundle_1 = require("../../commands/bundle");
|
|
14
|
+
jest.mock('@redocly/openapi-core');
|
|
15
|
+
jest.mock('../../utils');
|
|
16
|
+
openapi_core_1.getMergedConfig.mockImplementation(config => config);
|
|
17
|
+
describe('bundle', () => {
|
|
18
|
+
let processExitMock;
|
|
19
|
+
let exitCb;
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
processExitMock = jest.spyOn(process, 'exit').mockImplementation();
|
|
22
|
+
jest.spyOn(process, 'once').mockImplementation((_e, cb) => {
|
|
23
|
+
exitCb = cb;
|
|
24
|
+
return process.on(_e, cb);
|
|
25
|
+
});
|
|
26
|
+
jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
27
|
+
});
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
openapi_core_1.lint.mockClear();
|
|
30
|
+
openapi_core_1.bundle.mockClear();
|
|
31
|
+
openapi_core_1.getTotals.mockClear();
|
|
32
|
+
});
|
|
33
|
+
it('bundles definitions w/o linting', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
34
|
+
const entrypoints = ['foo.yaml', 'bar.yaml'];
|
|
35
|
+
yield bundle_1.handleBundle({
|
|
36
|
+
entrypoints,
|
|
37
|
+
ext: 'yaml',
|
|
38
|
+
format: 'codeframe',
|
|
39
|
+
}, '1.0.0');
|
|
40
|
+
expect(openapi_core_1.lint).toBeCalledTimes(0);
|
|
41
|
+
expect(openapi_core_1.bundle).toBeCalledTimes(entrypoints.length);
|
|
42
|
+
}));
|
|
43
|
+
it('exits with code 0 when bundles definitions', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
44
|
+
const entrypoints = ['foo.yaml', 'bar.yaml', 'foobar.yaml'];
|
|
45
|
+
yield bundle_1.handleBundle({
|
|
46
|
+
entrypoints,
|
|
47
|
+
ext: 'yaml',
|
|
48
|
+
format: 'codeframe',
|
|
49
|
+
}, '1.0.0');
|
|
50
|
+
exitCb === null || exitCb === void 0 ? void 0 : exitCb();
|
|
51
|
+
expect(processExitMock).toHaveBeenCalledWith(0);
|
|
52
|
+
}));
|
|
53
|
+
it('bundles definitions w/ linting', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
54
|
+
const entrypoints = ['foo.yaml', 'bar.yaml', 'foobar.yaml'];
|
|
55
|
+
yield bundle_1.handleBundle({
|
|
56
|
+
entrypoints,
|
|
57
|
+
ext: 'yaml',
|
|
58
|
+
format: 'codeframe',
|
|
59
|
+
lint: true,
|
|
60
|
+
}, '1.0.0');
|
|
61
|
+
expect(openapi_core_1.lint).toBeCalledTimes(entrypoints.length);
|
|
62
|
+
expect(openapi_core_1.bundle).toBeCalledTimes(entrypoints.length);
|
|
63
|
+
}));
|
|
64
|
+
it('exits with code 0 when bundles definitions w/linting w/o errors', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
65
|
+
const entrypoints = ['foo.yaml', 'bar.yaml', 'foobar.yaml'];
|
|
66
|
+
yield bundle_1.handleBundle({
|
|
67
|
+
entrypoints,
|
|
68
|
+
ext: 'yaml',
|
|
69
|
+
format: 'codeframe',
|
|
70
|
+
lint: true,
|
|
71
|
+
}, '1.0.0');
|
|
72
|
+
exitCb === null || exitCb === void 0 ? void 0 : exitCb();
|
|
73
|
+
expect(processExitMock).toHaveBeenCalledWith(0);
|
|
74
|
+
}));
|
|
75
|
+
it('exits with code 1 when bundles definitions w/linting w/errors', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
76
|
+
const entrypoints = ['foo.yaml'];
|
|
77
|
+
openapi_core_1.getTotals.mockReturnValue({
|
|
78
|
+
errors: 1,
|
|
79
|
+
warnings: 0,
|
|
80
|
+
ignored: 0
|
|
81
|
+
});
|
|
82
|
+
yield bundle_1.handleBundle({
|
|
83
|
+
entrypoints,
|
|
84
|
+
ext: 'yaml',
|
|
85
|
+
format: 'codeframe',
|
|
86
|
+
lint: true,
|
|
87
|
+
}, '1.0.0');
|
|
88
|
+
expect(openapi_core_1.lint).toBeCalledTimes(entrypoints.length);
|
|
89
|
+
exitCb === null || exitCb === void 0 ? void 0 : exitCb();
|
|
90
|
+
expect(processExitMock).toHaveBeenCalledWith(1);
|
|
91
|
+
}));
|
|
92
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const openapi_core_1 = require("@redocly/openapi-core");
|
|
13
|
+
const push_1 = require("../../commands/push");
|
|
14
|
+
const login_1 = require("../../commands/login");
|
|
15
|
+
jest.mock('fs');
|
|
16
|
+
jest.mock('node-fetch', () => ({
|
|
17
|
+
default: jest.fn(() => ({
|
|
18
|
+
ok: true,
|
|
19
|
+
json: jest.fn().mockResolvedValue({}),
|
|
20
|
+
})),
|
|
21
|
+
}));
|
|
22
|
+
jest.mock('@redocly/openapi-core');
|
|
23
|
+
jest.mock('../../commands/login');
|
|
24
|
+
jest.mock('../../utils');
|
|
25
|
+
openapi_core_1.getMergedConfig.mockImplementation((config) => config);
|
|
26
|
+
const mockPromptClientToken = login_1.promptClientToken;
|
|
27
|
+
describe('push-with-region', () => {
|
|
28
|
+
const redoclyClient = require('@redocly/openapi-core').__redoclyClient;
|
|
29
|
+
redoclyClient.isAuthorizedWithRedoclyByRegion = jest.fn().mockResolvedValue(false);
|
|
30
|
+
beforeAll(() => {
|
|
31
|
+
jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
32
|
+
});
|
|
33
|
+
it('should call login with default domain when region is US', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
34
|
+
redoclyClient.domain = 'redoc.ly';
|
|
35
|
+
yield push_1.handlePush({
|
|
36
|
+
upsert: true,
|
|
37
|
+
entrypoint: 'spec.json',
|
|
38
|
+
destination: '@org/my-api@1.0.0',
|
|
39
|
+
branchName: 'test',
|
|
40
|
+
});
|
|
41
|
+
expect(mockPromptClientToken).toBeCalledTimes(1);
|
|
42
|
+
expect(mockPromptClientToken).toHaveBeenCalledWith(redoclyClient.domain);
|
|
43
|
+
}));
|
|
44
|
+
it('should call login with EU domain when region is EU', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
45
|
+
redoclyClient.domain = 'eu.redocly.com';
|
|
46
|
+
yield push_1.handlePush({
|
|
47
|
+
upsert: true,
|
|
48
|
+
entrypoint: 'spec.json',
|
|
49
|
+
destination: '@org/my-api@1.0.0',
|
|
50
|
+
branchName: 'test',
|
|
51
|
+
});
|
|
52
|
+
expect(mockPromptClientToken).toBeCalledTimes(1);
|
|
53
|
+
expect(mockPromptClientToken).toHaveBeenCalledWith(redoclyClient.domain);
|
|
54
|
+
}));
|
|
55
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const openapi_core_1 = require("@redocly/openapi-core");
|
|
13
|
+
const push_1 = require("../../commands/push");
|
|
14
|
+
jest.mock('fs');
|
|
15
|
+
jest.mock('node-fetch', () => ({
|
|
16
|
+
default: jest.fn(() => ({
|
|
17
|
+
ok: true,
|
|
18
|
+
json: jest.fn().mockResolvedValue({}),
|
|
19
|
+
})),
|
|
20
|
+
}));
|
|
21
|
+
jest.mock('@redocly/openapi-core');
|
|
22
|
+
jest.mock('../../utils');
|
|
23
|
+
openapi_core_1.getMergedConfig.mockImplementation((config) => config);
|
|
24
|
+
describe('push', () => {
|
|
25
|
+
const redoclyClient = require('@redocly/openapi-core').__redoclyClient;
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
28
|
+
});
|
|
29
|
+
it('pushes definition', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
30
|
+
yield push_1.handlePush({
|
|
31
|
+
upsert: true,
|
|
32
|
+
entrypoint: 'spec.json',
|
|
33
|
+
destination: '@org/my-api@1.0.0',
|
|
34
|
+
branchName: 'test',
|
|
35
|
+
});
|
|
36
|
+
expect(redoclyClient.registryApi.prepareFileUpload).toBeCalledTimes(1);
|
|
37
|
+
expect(redoclyClient.registryApi.pushApi).toBeCalledTimes(1);
|
|
38
|
+
expect(redoclyClient.registryApi.pushApi).toHaveBeenLastCalledWith({
|
|
39
|
+
branch: 'test',
|
|
40
|
+
filePaths: ['filePath'],
|
|
41
|
+
isUpsert: true,
|
|
42
|
+
name: 'my-api',
|
|
43
|
+
organizationId: 'org',
|
|
44
|
+
rootFilePath: 'filePath',
|
|
45
|
+
version: '1.0.0',
|
|
46
|
+
});
|
|
47
|
+
}));
|
|
48
|
+
});
|
|
49
|
+
describe('transformPush', () => {
|
|
50
|
+
it('should adapt the existing syntax', () => {
|
|
51
|
+
const cb = jest.fn();
|
|
52
|
+
push_1.transformPush(cb)({
|
|
53
|
+
maybeEntrypointOrAliasOrDestination: 'openapi.yaml',
|
|
54
|
+
maybeDestination: '@testing_org/main@v1',
|
|
55
|
+
});
|
|
56
|
+
expect(cb).toBeCalledWith({
|
|
57
|
+
entrypoint: 'openapi.yaml',
|
|
58
|
+
destination: '@testing_org/main@v1',
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
it('should adapt the existing syntax (including branchName)', () => {
|
|
62
|
+
const cb = jest.fn();
|
|
63
|
+
push_1.transformPush(cb)({
|
|
64
|
+
maybeEntrypointOrAliasOrDestination: 'openapi.yaml',
|
|
65
|
+
maybeDestination: '@testing_org/main@v1',
|
|
66
|
+
maybeBranchName: 'other',
|
|
67
|
+
});
|
|
68
|
+
expect(cb).toBeCalledWith({
|
|
69
|
+
entrypoint: 'openapi.yaml',
|
|
70
|
+
destination: '@testing_org/main@v1',
|
|
71
|
+
branchName: 'other',
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
it('should use --branch option firstly', () => {
|
|
75
|
+
const cb = jest.fn();
|
|
76
|
+
push_1.transformPush(cb)({
|
|
77
|
+
maybeEntrypointOrAliasOrDestination: 'openapi.yaml',
|
|
78
|
+
maybeDestination: '@testing_org/main@v1',
|
|
79
|
+
maybeBranchName: 'other',
|
|
80
|
+
branch: 'priority-branch',
|
|
81
|
+
});
|
|
82
|
+
expect(cb).toBeCalledWith({
|
|
83
|
+
entrypoint: 'openapi.yaml',
|
|
84
|
+
destination: '@testing_org/main@v1',
|
|
85
|
+
branchName: 'priority-branch',
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
it('should work for a destination only', () => {
|
|
89
|
+
const cb = jest.fn();
|
|
90
|
+
push_1.transformPush(cb)({
|
|
91
|
+
maybeEntrypointOrAliasOrDestination: '@testing_org/main@v1',
|
|
92
|
+
});
|
|
93
|
+
expect(cb).toBeCalledWith({
|
|
94
|
+
destination: '@testing_org/main@v1',
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
it('should accept aliases for the old syntax', () => {
|
|
98
|
+
const cb = jest.fn();
|
|
99
|
+
push_1.transformPush(cb)({
|
|
100
|
+
maybeEntrypointOrAliasOrDestination: 'alias',
|
|
101
|
+
maybeDestination: '@testing_org/main@v1',
|
|
102
|
+
});
|
|
103
|
+
expect(cb).toBeCalledWith({
|
|
104
|
+
destination: '@testing_org/main@v1',
|
|
105
|
+
entrypoint: 'alias',
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
it('should accept no arguments at all', () => {
|
|
109
|
+
const cb = jest.fn();
|
|
110
|
+
push_1.transformPush(cb)({});
|
|
111
|
+
expect(cb).toBeCalledWith({});
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe('getDestinationProps', () => {
|
|
115
|
+
it('should get valid destination props for the full destination syntax', () => {
|
|
116
|
+
expect(push_1.getDestinationProps('@testing_org/main@v1', 'org-from-config')).toEqual([
|
|
117
|
+
'testing_org',
|
|
118
|
+
'main',
|
|
119
|
+
'v1',
|
|
120
|
+
]);
|
|
121
|
+
});
|
|
122
|
+
it('should fallback the organizationId from a config for the short destination syntax', () => {
|
|
123
|
+
expect(push_1.getDestinationProps('main@v1', 'org-from-config')).toEqual([
|
|
124
|
+
'org-from-config',
|
|
125
|
+
'main',
|
|
126
|
+
'v1',
|
|
127
|
+
]);
|
|
128
|
+
});
|
|
129
|
+
it('should fallback the organizationId from a config if no destination provided', () => {
|
|
130
|
+
expect(push_1.getDestinationProps(undefined, 'org-from-config')).toEqual(['org-from-config']);
|
|
131
|
+
});
|
|
132
|
+
it('should return empty organizationId if there is no one found', () => {
|
|
133
|
+
expect(push_1.getDestinationProps('main@v1', undefined)).toEqual([, 'main', 'v1']);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
describe('getApiEntrypoint', () => {
|
|
137
|
+
let config = {
|
|
138
|
+
apis: {
|
|
139
|
+
'main@v1': {
|
|
140
|
+
root: 'openapi.yaml',
|
|
141
|
+
},
|
|
142
|
+
main: {
|
|
143
|
+
root: 'latest.yaml',
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
it('should resolve the correct api for a valid name & version', () => {
|
|
148
|
+
expect(push_1.getApiEntrypoint({ name: 'main', version: 'v1', config })).toEqual('openapi.yaml');
|
|
149
|
+
});
|
|
150
|
+
it('should resolve the latest version of api if there is no matching version', () => {
|
|
151
|
+
expect(push_1.getApiEntrypoint({ name: 'main', version: 'latest', config })).toEqual('latest.yaml');
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("../utils");
|
|
4
|
+
jest.mock("os");
|
|
5
|
+
describe('isSubdir', () => {
|
|
6
|
+
it('can correctly determine if subdir', () => {
|
|
7
|
+
[
|
|
8
|
+
['/foo', '/foo', false],
|
|
9
|
+
['/foo', '/bar', false],
|
|
10
|
+
['/foo', '/foobar', false],
|
|
11
|
+
['/foo', '/foo/bar', true],
|
|
12
|
+
['/foo', '/foo/../bar', false],
|
|
13
|
+
['/foo', '/foo/./bar', true],
|
|
14
|
+
['/bar/../foo', '/foo/bar', true],
|
|
15
|
+
['/foo', './bar', false],
|
|
16
|
+
['/foo', '/foo/..bar', true],
|
|
17
|
+
].forEach(([parent, child, expectRes]) => {
|
|
18
|
+
expect(utils_1.isSubdir(parent, child)).toBe(expectRes);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
it('can correctly determine if subdir for windows-based paths', () => {
|
|
22
|
+
const os = require('os');
|
|
23
|
+
os.platform.mockImplementation(() => 'win32');
|
|
24
|
+
[
|
|
25
|
+
['C:/Foo', 'C:/Foo/Bar', true],
|
|
26
|
+
['C:\\Foo', 'C:\\Bar', false],
|
|
27
|
+
['C:\\Foo', 'D:\\Foo\\Bar', false],
|
|
28
|
+
].forEach(([parent, child, expectRes]) => {
|
|
29
|
+
expect(utils_1.isSubdir(parent, child)).toBe(expectRes);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
jest.resetModules();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
describe('pathToFilename', () => {
|
|
37
|
+
it('should use correct path separator', () => {
|
|
38
|
+
const processedPath = utils_1.pathToFilename('/user/createWithList', '_');
|
|
39
|
+
expect(processedPath).toEqual('user_createWithList');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
try {
|
|
6
|
+
require('assert-node-version')(path.join(__dirname, '../'));
|
|
7
|
+
}
|
|
8
|
+
catch (err) {
|
|
9
|
+
utils_1.exitWithError(err.message);
|
|
10
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { OutputFormat } from '@redocly/openapi-core';
|
|
2
|
+
import { OutputExtensions } from '../types';
|
|
3
|
+
export declare function handleBundle(argv: {
|
|
4
|
+
entrypoints: string[];
|
|
5
|
+
output?: string;
|
|
6
|
+
ext: OutputExtensions;
|
|
7
|
+
'max-problems'?: number;
|
|
8
|
+
'skip-rule'?: string[];
|
|
9
|
+
'skip-preprocessor'?: string[];
|
|
10
|
+
'skip-decorator'?: string[];
|
|
11
|
+
dereferenced?: boolean;
|
|
12
|
+
force?: boolean;
|
|
13
|
+
config?: string;
|
|
14
|
+
lint?: boolean;
|
|
15
|
+
format: OutputFormat;
|
|
16
|
+
metafile?: string;
|
|
17
|
+
extends?: string[];
|
|
18
|
+
'remove-unused-components'?: boolean;
|
|
19
|
+
}, version: string): Promise<void>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
12
|
+
var t = {};
|
|
13
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
14
|
+
t[p] = s[p];
|
|
15
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
16
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
17
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
18
|
+
t[p[i]] = s[p[i]];
|
|
19
|
+
}
|
|
20
|
+
return t;
|
|
21
|
+
};
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.handleBundle = void 0;
|
|
24
|
+
const openapi_core_1 = require("@redocly/openapi-core");
|
|
25
|
+
const utils_1 = require("../utils");
|
|
26
|
+
const perf_hooks_1 = require("perf_hooks");
|
|
27
|
+
const colorette_1 = require("colorette");
|
|
28
|
+
const fs_1 = require("fs");
|
|
29
|
+
function handleBundle(argv, version) {
|
|
30
|
+
var _a, _b, _c, _d;
|
|
31
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
32
|
+
const config = yield openapi_core_1.loadConfig(argv.config, argv.extends);
|
|
33
|
+
const removeUnusedComponents = argv['remove-unused-components'] &&
|
|
34
|
+
!((_b = (_a = config.rawConfig.lint) === null || _a === void 0 ? void 0 : _a.decorators) === null || _b === void 0 ? void 0 : _b.hasOwnProperty('remove-unused-components'));
|
|
35
|
+
const entrypoints = yield utils_1.getFallbackEntryPointsOrExit(argv.entrypoints, config);
|
|
36
|
+
const totals = { errors: 0, warnings: 0, ignored: 0 };
|
|
37
|
+
const maxProblems = argv['max-problems'];
|
|
38
|
+
for (const { path, alias } of entrypoints) {
|
|
39
|
+
try {
|
|
40
|
+
const startedAt = perf_hooks_1.performance.now();
|
|
41
|
+
const resolvedConfig = openapi_core_1.getMergedConfig(config, alias);
|
|
42
|
+
resolvedConfig.lint.skipRules(argv['skip-rule']);
|
|
43
|
+
resolvedConfig.lint.skipPreprocessors(argv['skip-preprocessor']);
|
|
44
|
+
resolvedConfig.lint.skipDecorators(argv['skip-decorator']);
|
|
45
|
+
if (argv.lint) {
|
|
46
|
+
if (config.lint.recommendedFallback) {
|
|
47
|
+
process.stderr.write(`No configurations were defined in extends -- using built in ${colorette_1.blue('recommended')} configuration by default.\n${colorette_1.red('Warning! This default behavior is going to be deprecated soon.')}\n\n`);
|
|
48
|
+
}
|
|
49
|
+
const results = yield openapi_core_1.lint({
|
|
50
|
+
ref: path,
|
|
51
|
+
config: resolvedConfig,
|
|
52
|
+
});
|
|
53
|
+
const fileLintTotals = openapi_core_1.getTotals(results);
|
|
54
|
+
totals.errors += fileLintTotals.errors;
|
|
55
|
+
totals.warnings += fileLintTotals.warnings;
|
|
56
|
+
totals.ignored += fileLintTotals.ignored;
|
|
57
|
+
openapi_core_1.formatProblems(results, {
|
|
58
|
+
format: argv.format || 'codeframe',
|
|
59
|
+
totals: fileLintTotals,
|
|
60
|
+
version,
|
|
61
|
+
maxProblems,
|
|
62
|
+
});
|
|
63
|
+
utils_1.printLintTotals(fileLintTotals, 2);
|
|
64
|
+
}
|
|
65
|
+
process.stderr.write(colorette_1.gray(`bundling ${path}...\n`));
|
|
66
|
+
const _e = yield openapi_core_1.bundle({
|
|
67
|
+
config: resolvedConfig,
|
|
68
|
+
ref: path,
|
|
69
|
+
dereference: argv.dereferenced,
|
|
70
|
+
removeUnusedComponents,
|
|
71
|
+
}), { bundle: result, problems } = _e, meta = __rest(_e, ["bundle", "problems"]);
|
|
72
|
+
const fileTotals = openapi_core_1.getTotals(problems);
|
|
73
|
+
const { outputFile, ext } = utils_1.getOutputFileName(path, entrypoints.length, argv.output, argv.ext);
|
|
74
|
+
if (fileTotals.errors === 0 || argv.force) {
|
|
75
|
+
if (!argv.output) {
|
|
76
|
+
const output = utils_1.dumpBundle(result.parsed, argv.ext || 'yaml', argv.dereferenced);
|
|
77
|
+
process.stdout.write(output);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
const output = utils_1.dumpBundle(result.parsed, ext, argv.dereferenced);
|
|
81
|
+
utils_1.saveBundle(outputFile, output);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
totals.errors += fileTotals.errors;
|
|
85
|
+
totals.warnings += fileTotals.warnings;
|
|
86
|
+
totals.ignored += fileTotals.ignored;
|
|
87
|
+
openapi_core_1.formatProblems(problems, {
|
|
88
|
+
format: argv.format,
|
|
89
|
+
maxProblems,
|
|
90
|
+
totals: fileTotals,
|
|
91
|
+
version,
|
|
92
|
+
});
|
|
93
|
+
if (argv.metafile) {
|
|
94
|
+
if (entrypoints.length > 1) {
|
|
95
|
+
process.stderr.write(colorette_1.yellow(`[WARNING] "--metafile" cannot be used with multiple entrypoints. Skipping...`));
|
|
96
|
+
}
|
|
97
|
+
{
|
|
98
|
+
fs_1.writeFileSync(argv.metafile, JSON.stringify(meta), 'utf-8');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const elapsed = utils_1.getExecutionTime(startedAt);
|
|
102
|
+
if (fileTotals.errors > 0) {
|
|
103
|
+
if (argv.force) {
|
|
104
|
+
process.stderr.write(`❓ Created a bundle for ${colorette_1.blue(path)} at ${colorette_1.blue(outputFile)} with errors ${colorette_1.green(elapsed)}.\n${colorette_1.yellow('Errors ignored because of --force')}.\n`);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
process.stderr.write(`❌ Errors encountered while bundling ${colorette_1.blue(path)}: bundle not created (use --force to ignore errors).\n`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
process.stderr.write(`📦 Created a bundle for ${colorette_1.blue(path)} at ${colorette_1.blue(outputFile)} ${colorette_1.green(elapsed)}.\n`);
|
|
112
|
+
}
|
|
113
|
+
const removedCount = (_d = (_c = meta.visitorsData) === null || _c === void 0 ? void 0 : _c['remove-unused-components']) === null || _d === void 0 ? void 0 : _d.removedCount;
|
|
114
|
+
if (removedCount) {
|
|
115
|
+
process.stderr.write(colorette_1.gray(`🧹 Removed ${removedCount} unused components.\n`));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
utils_1.handleError(e, path);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
utils_1.printUnusedWarnings(config.lint);
|
|
123
|
+
// defer process exit to allow STDOUT pipe to flush
|
|
124
|
+
// see https://github.com/nodejs/node-v0.x-archive/issues/3737#issuecomment-19156072
|
|
125
|
+
process.once('exit', () => process.exit(totals.errors === 0 || argv.force ? 0 : 1));
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
exports.handleBundle = handleBundle;
|