feat: Created a mini nodeJS server with NewMan for testing without PostMan GUI.

This will mimic a run in a CD/CI environment or docker container.
This commit is contained in:
Simon Priet
2021-09-08 14:01:19 +02:00
parent 5fbd7c88fa
commit e69a613a37
5610 changed files with 740417 additions and 3 deletions

View File

@@ -0,0 +1,366 @@
4.1.3:
date: 2021-06-29
fixed bugs:
- >-
GH-418 Fixed a bug where response code type remained invalid after
conversion from v1 to v2.x
chores:
- Updated dependencies
4.1.2:
date: 2021-04-15
fixed bugs:
- >-
GH-394 Fixed a bug where variable.id was retained even if retainIds is set
to false
4.1.1:
date: 2021-04-08
fixed bugs:
- GH-392 Ensured response ids are unchanged on v2.x -> v1 conversions
chores:
- Updated dependencies
4.1.0:
date: 2021-03-16
new features:
- GH-381 Added support for `responses_order` field in v1 collection
chores:
- Updated dependencies
4.0.1:
date: 2021-02-08
fixed bugs:
- GH-358 Ensured all description fields are normalized to v1 string values
chores:
- Updated dependencies
4.0.0:
date: 2020-12-03
breaking changes:
- >-
GH-334 Fixed a bug where item and item-group identifier is incorrectly set
as "_postman_id" instead of "id" on v1 -> v2.x conversions
- GH-335 Dropped support for Node < v10
fixed bugs:
- GH-337 Fixed a bug where empty body options are retained
- >-
GH-336 Fixed a bug where empty and object-like protocolProfileBehavior are
retained
- >-
GH-333 Fixed a bug where object descriptions are not converted to string
on v2->v1 conversions
- >-
GH-332 Fixed a bug where private _postman_isSubFolder leaks on v1->v2
conversions
- >-
GH-331 Fixed a bug where variable.id was retained even if retainIds is set
to false
- >-
GH-330 Fixed a bug where event.id and event.script.id was retained even if
retainIds is set to false
chores:
- GH-335 Updated dependencies
- >-
Updated .npmignore to prevent the addition of tests and config files in
the published package
- Added system test for published package content
- Removed puppeteer dependency for browser tests
- Updated nyc configuration
- Updated ESLint rules
3.3.3:
date: 2020-07-13
chores:
- Updated dependencies
3.3.2:
date: 2020-06-05
fixed bugs:
- >-
GH-278 Fixed a bug where query param values are lost during v1 -> v2.x
conversions
- GH-307 Fixed a bug where variable type is always inferred or set as 'any'
3.3.1:
date: 2020-03-27
fixed bugs:
- GH-269 Fixed a bug where null events are dropped during normalization
chores:
- Updated dependencies
3.3.0:
date: 2020-03-02
new features:
- >-
GH-259 Updated URL parsing and unparsing logic to allow variables with
reserved characters in URL
fixed bugs:
- >-
GH-260 Fixed a bug where params with null key were repeated during v1 ->
v2.x conversions
chores:
- Updated dependencies
3.2.0:
date: 2019-09-04
new features:
- GH-235 Added support for request body options
- >-
GH-236 Added support for converting `protocolProfileBehavior` at folder
and collection level
chores:
- Updated dependencies
3.1.2:
date: 2019-08-01
chores:
- Updated dependencies
3.1.1:
date: 2019-05-17
fixed bugs:
- Fixed a bug where params with empty key get filtered
3.1.0:
date: 2019-05-13
new features:
- GH-207 Added support for GraphQL request body
chores:
- Updated dependencies
3.0.2:
date: 2019-04-29
chores:
- GH-198 Added support for multiple file in form-data
- Updated dependencies
3.0.1:
date: 2019-04-02
fixed bugs:
- GH-184 Set null body if dataMode is null during v1 -> v2.x conversions
chores:
- Updated dependencies
3.0.0:
date: 2018-11-8
breaking changes:
- GH-124 Dropped support for Node v4
fixed bugs:
- GH-112 Corrected retainIds behaviour for v1->v2 transformations
- GH-121 Added handling for null valued request bodies
chores:
- GH-122 Added `.gitattributes` to enforce consistent line endings
- GH-126 Switched to Travis for Windows tests
- GH-123 Removed NSP (replaced with Snyk)
- Updated dependencies
2.8.0:
date: 2018-09-20
new features:
- GH-118 Added support for the `protocolProfileBehaviour` data element
2.7.0:
date: 2018-09-11
new features:
- GH-117 Added support for disabled request body flag
2.6.3:
date: 2018-08-29
fixed bugs:
- GH-115 Coerced non-string formdata file values in request bodies to `null`
2.6.2:
date: 2018-08-6
fixed bugs:
- GH-111 Updated `url.parse` to correctly account for path variables
2.6.1:
date: 2018-07-24
fixed bugs:
- GH-110 Prevent folder id replacement in v2.x -> v1 transformations
chores:
- GH-109 Increased test coveragee to 95%
2.6.0:
date: 2018-07-10
new features:
- GH-107 Added support for `retainEmptyValues` in all flows
2.5.10:
date: 2018-07-4
fixed bugs:
- GH-105 Fixed a bug in url parsing protocol extraction
chores:
- GH-106 Updated dependencies
2.5.9:
date: 2018-04-30
fixed bugs:
- GH-103 Fixed `retainIds` behaviour for v1 to v2 conversions
chores:
- GH-104 Updated dependencies
2.5.8:
date: 2018-03-13
fixed bugs:
- >-
GH-102 Improved handling for `null` valued query parameters in
transformations from `v1`
chores:
- Updated dependencies
2.5.7:
date: 2018-03-1
fixed bugs:
- GH-101 Fixed text `type` mapping for `auth` and `variable` fields
2.5.6:
date: 2018-02-23
chores:
- GH-100 Auth mapping improvements
2.5.5:
date: 2018-02-6
fixed bugs:
- GH-99 Fixed empty description bug for v1 -> v2 transformations
chores:
- Updated dependencies
2.5.4:
date: 2018-01-7
new features:
- GH-98 Improved `script.exec` type safety in v1 normalization
chores:
- Updated dependencies
2.5.3:
date: 2017-12-18
fixed bugs:
- GH-97 Fixed a bug that caused query params to be duplicted
2.5.2:
date: 2017-12-12
fixed bugs:
- GH-96 Made empty description pruning optional with `retainEmptyValues`
2.5.1:
date: 2017-12-1
fixed bugs:
- GH-95 Improved type safety in folder and request transformations
2.5.0:
date: 2017-11-22
new features:
- >-
GH-94 Added support for the `prioritizeV2` option to v1 normalization and
v1 -> v2.x conversions
2.4.3:
date: 2017-11-20
fixed bugs:
- GH-92 Fixed legacy property handling for non-complete event normalization
2.4.2:
date: 2017-11-20
fixed bugs:
- GH-91 Fixed default auth state derivation logic
2.4.1:
date: 2017-11-18
fixed bugs:
- GH-90 Fixed empty stage handling logic for auth helpers
chores:
- GH-89 Reorganized package structure
- GH-88 Updated dependencies
2.4.0:
date: 2017-11-7
new features:
- GH-84 Added support for `noDefaults` for v1 normalizations
- GH-81 Added support for `mutate` to v1 normalizations
- GH-79 Added `normalize.*` function to sanitize v1 collections
- GH-78 Added support for inherited entity transformations
fixed bugs:
- GH-82 `noauth` and `normal` auth handlers are now left alone
chores:
- GH-87 Tested code on Node v8
2.3.1:
date: 2017-10-3
fixed bugs:
- GH-77 Corrected OAuth2 param transformations
2.3.0:
date: 2017-09-30
new features:
- GH-76 Added support for NTLM and Bearer Token auth transformations
2.2.1:
date: 2017-09-28
fixed bugs:
- GH-75 Restored support for string-object hybrid v2 URLs
2.2.0:
date: 2017-09-4
new features:
- >-
GH-66,67,68 Added support for transformations to and from the `v2.1.0`
format
fixed bugs:
- GH-62 Enforced an object structure for V2 collection request URLs
- GH-61 Prevented empty descriptions from showing up across transformations
chores:
- GH-65 Updated dependencies
2.1.5:
date: 2017-06-28
new features:
- >-
GH-60 Allowed both: `key` and `id` as identifers for path variables in v2
collections
2.1.4:
date: 2017-06-19
chores:
- No code changes
2.1.3:
date: 2017-06-09
fixed bugs:
- >-
Fixed a bug in the URL parse implementation, by copying the parser &
unparser from the sdk
2.1.2:
date: 2017-05-25
fixed bugs:
- >-
Handle an edge case where an unhandled ID is provided in the
`requestObject`
2.1.1:
date: 2017-05-22
new features:
- Added the ability to convert single responses as well
fixed bugs:
- >-
Fixed a bug where the `requestObject` was missing when doing v2 to v1
conversions
2.1.0:
date: 2017-05-12
initial release:
- >-
Descriptions for headers, url query params, url path variables, and
request body data are now handled in transformations
- >-
Fix disabled: true header (`v2`) <-> // (commented) header (`v1`)
transformations
- >-
Fixed bug where both enabled and disabled would appear in transformed
entities
- Request body transformations now follow `v1` - `v2` compliance
- >-
Removed unwanted `enabled: true` properties from query params, headers,
and request body data

202
node_modules/postman-collection-transformer/LICENSE.md generated vendored Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2015 Postdot Technologies Pvt. Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

174
node_modules/postman-collection-transformer/README.md generated vendored Normal file
View File

@@ -0,0 +1,174 @@
[![Build Status](https://travis-ci.com/postmanlabs/postman-collection-transformer.svg?branch=develop)](https://travis-ci.com/postmanlabs/postman-collection-transformer)
# postman-collection-transformer
Perform rapid conversion of JSON structure between Postman Collection Format v1 and v2.
The formats are documented at https://schema.getpostman.com
## Installation
For CLI usage:
$ npm install -g postman-collection-transformer
As a library:
$ npm install --save postman-collection-transformer
## Usage
#### Converting Entire Collections
The transformer provides a Command line API to convert collections.
Example:
$ postman-collection-transformer convert \
--input ./v1-collection.json \
--input-version 2.0.0 \
--output ./v2-collection.json \
--output-version 1.0.0 \
--pretty \
--overwrite
All options:
$ postman-collection-transformer convert -h
Usage: convert [options]
Convert Postman Collection from one format to another
Options:
-h, --help output usage information
-i, --input <path> path to the input postman collection file
-j, --input-version [version] the version of the input collection format standard (v1 or v2)
-o, --output <path> target file path where the converted collection will be written
-p, --output-version [version] required version to which the collection is needed to be converted to
-P, --pretty Pretty print the output
--retain-ids Retain the request and folder IDs during conversion (collection ID is always retained)
-w, --overwrite Overwrite the output file if it exists
If you'd rather use the transformer as a library:
```javascript
var transformer = require('postman-collection-transformer'),
collection = require('./path/to/collection.json'),
inspect = require('util').inspect,
options = {
inputVersion: '1.0.0',
outputVersion: '2.0.0',
retainIds: true // the transformer strips request-ids etc by default.
};
transformer.convert(collection, options, function (error, result) {
if (error) {
return console.error(error);
}
// result <== the converted collection as a raw Javascript object
console.log(inspect(result, {colors: true, depth: 10000}));
});
```
#### Converting Individual Requests
The transformer also allows you to convert individual requests (only supported when used as a library):
###### Example
```javascript
var transformer = require('postman-collection-transformer'),
objectToConvert = { /* A valid collection v1 Request or a collection v2 Item */ },
options = {
inputVersion: '1.0.0',
outputVersion: '2.0.0',
retainIds: true // the transformer strips request-ids etc by default.
};
transformer.convertSingle(objectToConvert, options, function (err, converted) {
console.log(converted);
});
```
#### Converting Individual Responses
You can convert individual responses too if needed:
###### Example
```javascript
var transformer = require('postman-collection-transformer'),
objectToConvert = { /* A v1 Response or a v2 Response */ },
options = {
inputVersion: '1.0.0',
outputVersion: '2.0.0',
retainIds: true // the transformer strips request-ids etc by default.
};
transformer.convertResponse(objectToConvert, options, function (err, converted) {
console.log(converted);
});
```
#### Normalizing v1 collections
The transformer also provides a Command line API to normalize collections for full forward compatibility.
Example:
$ postman-collection-transformer normalize \
--input ./v1-collection.json \
--normalize-version 1.0.0 \
--output ./v1-norm-collection.json \
--pretty \
--overwrite
All options:
$ postman-collection-transformer normalize -h
Usage: normalize [options]
Normalizes a postman collection according to the provided version
Options:
-i, --input <path> Path to the collection JSON file to be normalized
-n, --normalize-version <version> The version to normalizers the provided collection on
-o, --output <path> Path to the target file, where the normalized collection will be written
-P, --pretty Pretty print the output
--retain-ids Retain the request and folder IDs during conversion (collection ID is always retained)
-w, --overwrite Overwrite the output file if it exists
-h, --help Output usage information
If you'd rather use the transformer as a library:
```javascript
var transformer = require('postman-collection-transformer'),
collection = require('./path/to/collection.json'),
inspect = require('util').inspect,
options = {
normalizeVersion: '1.0.0',
mutate: false, // performs in-place normalization, false by default.
noDefaults: false, // when set to true, sensible defaults for missing properties are skipped. Default: false
prioritizeV2: false, // when set to true, v2 attributes are used as the source of truth for normalization.
retainEmptyValues: false, // when set to true, empty values are set to '', not removed. False by default.
retainIds: true // the transformer strips request-ids etc by default.
};
transformer.normalize(collection, options, function (error, result) {
if (error) {
return console.error(error);
}
// result <== the converted collection as a raw Javascript object
console.log(inspect(result, {colors: true, depth: 10000}));
});
```

View File

@@ -0,0 +1,148 @@
#!/usr/bin/env node
const fs = require('fs'),
log = require('intel'),
{ Command } = require('commander'),
stripJSONComments = require('strip-json-comments'),
transformer = require('..'),
program = new Command(),
FSWF_FLAG_W = { flag: 'w' },
FSWF_FLAG_WX = { flag: 'wx' },
/**
* Loads a JSON in a safe and compatible way from a file
*
* @param {String} path - The file path to read JSON content from.
*/
loadJSON = function (path) {
const data = fs.readFileSync(path); // eslint-disable-line security/detect-non-literal-fs-filename
return JSON.parse(stripJSONComments(data.toString()));
},
/**
* Writes a JSON blob to the given path.
* ``options.path`` must contain the path to the output file.
* If ``options.pretty`` is true, output will be pretty printed. (Default false)
* If ``options.overwrite`` is false, output file will be overwritten (Default true)
*
* @param {Object} data - The JSON data to be written.
* @param {Object} options - The options for JSON data writing.
* @param {Boolean} [options.pretty=false] - When set to true, indents JSON data by 4 spaces.
* @param {String} options.output - The file to write data to.
* @param {Boolean} [options.overwrite=false] - When set to true, allows overwriting files that exist.
* @param {Function} callback - A function to be invoked after writing is complete ,
*/
writeJSON = function (data, options, callback) {
let json;
try {
json = JSON.stringify(data, null, options.pretty ? 4 : 0);
}
catch (e) {
return callback(e);
}
// eslint-disable-next-line security/detect-non-literal-fs-filename
return fs.writeFile(options.output, json, options.overwrite ? FSWF_FLAG_W : FSWF_FLAG_WX, callback);
};
// Setup logging
log.basicConfig({
format: '%(date)s [%(levelname)s] %(message)s',
level: log.DEBUG
});
program
.usage('[command] [options]')
.version(require('../package.json').version);
// Describe the options and usage instruction for the `convert` command
program
.command('convert')
.description('Convert Postman Collection from one format to another')
.option('-e, --env <path>', 'optional path to the associated postman environment file to be used')
.option('-i, --input <path>', 'path to the input postman collection file')
.option('-j, --input-version [version]', 'the version of the input collection format standard (v1 or v2)')
.option('-o, --output <path>', 'target file path where the converted collection will be written')
.option('-p, --output-version [version]', 'required version to which the collection is needed to be converted to')
.option('-P, --pretty', 'Pretty print the output')
.option('--retain-ids', 'Retain the request and folder IDs during conversion (collection ID is always retained)')
.option('-w, --overwrite', 'Overwrite the output file if it exists')
.action((options) => {
let input;
if (!options.output) {
return log.error('Output file must be specified!');
}
if (!options.input) {
return log.error('Input file must be specified!');
}
try {
input = loadJSON(options.input);
}
catch (e) {
return log.error('Unable to load the input file!', e);
}
return transformer.convert(input, options, (err, result) => {
if (err) {
return log.error('Unable to convert the input:', err);
}
return writeJSON(result, options, (error) => {
if (error) {
log.error('Could not create output file %s', options.output, error);
}
});
});
});
program
.command('normalize')
.description('Normalizes a postman collection according to the provided version')
.option('-i, --input <path>', 'Path to the collection JSON file to be normalized')
.option('-n, --normalize-version <version>', 'The version to normalize the provided collection on')
.option('-o, --output <path>', 'Path to the target file, where the normalized collection will be written')
.option('-P, --pretty', 'Pretty print the output')
.option('--retain-ids', 'Retain the request and folder IDs during conversion (collection ID is always retained)')
.option('-w, --overwrite', 'Overwrite the output file if it exists')
.action((options) => {
if (!options.input) { return log.error('Input file must be specified!'); }
if (!options.output) { return log.error('Output file must be specified!'); }
let input;
try { input = loadJSON(options.input); }
catch (e) { return log.error('Unable to load the input file!', e); }
return transformer.normalize(input, options, (err, result) => {
if (err) { return log.error('Unable to convert the input: ', err); }
return writeJSON(result, options, (error) => {
error && log.error('Could not create output file %s', options.output, error);
});
});
});
// Describe the options and usage instructions for the `validate` command
program
.command('validate')
.description('Verify whether a postman collection adheres to version specifications')
.option('-i, --input <path>', 'path to the input postman collection file')
.option('-s, --schema [version]', 'the version of the input collection format standard')
.action((options) => {
console.warn('yet to be implemented', options);
// @todo implement with as little and concise code as possible with least external dependencies
});
// Warn on invalid command and then exits.
program.on('command:*', (command) => {
console.error(`error: invalid command \`${command}\`\n`);
program.help();
});
program.parse(process.argv);

4
node_modules/postman-collection-transformer/index.js generated vendored Normal file
View File

@@ -0,0 +1,4 @@
/**
* @module transformer
*/
module.exports = require('./lib');

View File

@@ -0,0 +1,61 @@
var _ = require('lodash').noConflict(),
regexes = {
fold: /\r\n([ \t])/g,
trim: /^\s*(.*\S)?\s*$/, // eslint-disable-line security/detect-unsafe-regex
header: /^((\/\/\s*)?\S+):(.*)$/gm // eslint-disable-line security/detect-unsafe-regex
},
headersCommentPrefix = '//';
module.exports = {
authMap: {
apikeyAuth: 'apikey',
basicAuth: 'basic',
bearerAuth: 'bearer',
digestAuth: 'digest',
hawkAuth: 'hawk',
oAuth1: 'oauth1',
oAuth2: 'oauth2',
ntlmAuth: 'ntlm',
awsSigV4: 'awsv4',
normal: null
},
/**
* Parses a string of headers to an object.
*
* @param {String} data - A string of newline concatenated header key-value pairs.
* @param {?Boolean} [legacy] - A flag to indicate whether the parsing is being done for v1 normalization or v1 to
* v2 conversion.
* @returns {Object[]|*} - The parsed list of header key-value pair objects.
* @private
*/
parseHeaders: function (data, legacy) {
if (!data) { return; }
var head,
headers = [],
statusValue = !legacy,
match = regexes.header.exec(data),
property = legacy ? 'enabled' : 'disabled';
data = data.toString().replace(regexes.fold, '$1');
while (match) {
head = {
key: match[1],
value: match[3].replace(regexes.trim, '$1')
};
if (_.startsWith(head.key, headersCommentPrefix)) {
head[property] = statusValue;
head.key = head.key.replace(headersCommentPrefix, '').trim();
}
headers.push(head);
match = regexes.header.exec(data);
}
return headers;
}
};

View File

@@ -0,0 +1,57 @@
var _ = require('lodash').noConflict(),
util = require('../util'),
/**
* Replenishes missing ids in v2.0.0.x collections.
*
* @param {*} currentItem - A collection entity on which to check for ids.
* @returns {Object|*} - The updated item, with the correct id fields in place.
*/
populateIds = function (currentItem) {
if (!currentItem) { return; }
// ID sanitization
if (currentItem._postman_id) {
currentItem.id = currentItem._postman_id;
delete currentItem._postman_id;
}
!currentItem.id && (currentItem.id = util.uid());
if (currentItem.response && currentItem.response.length) {
_.forEach(currentItem.response, populateIds);
}
if (currentItem.responses && currentItem.responses.length) {
_.forEach(currentItem.responses, populateIds);
}
var itemArray = currentItem.items || currentItem.item;
itemArray && itemArray.length && _.forEach(itemArray, populateIds);
return currentItem;
};
module.exports = {
authMap: {
apikey: 'apikeyAuth',
basic: 'basicAuth',
bearer: 'bearerAuth',
digest: 'digestAuth',
hawk: 'hawkAuth',
oauth1: 'oAuth1',
oauth2: 'oAuth2',
ntlm: 'ntlmAuth',
awsv4: 'awsSigV4',
noauth: null
},
modeMap: {
file: 'binary',
formdata: 'params',
graphql: 'graphql',
raw: 'raw',
urlencoded: 'urlencoded'
},
populateIds: populateIds
};

View File

@@ -0,0 +1,5 @@
exports.SCHEMA_V2_URL = 'https://schema.getpostman.com/json/collection/v2.0.0/collection.json';
exports.SCHEMA_V2_1_0_URL = 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json';
exports.SCHEMA_V1_URL = 'https://schema.getpostman.com/json/collection/v1.0.0/collection.json';
exports.PREREQUEST_EXT = '_preRequestScript';
exports.TESTS_EXT = '_tests';

View File

@@ -0,0 +1,152 @@
var semver = require('semver'),
FN = 'function',
generateConverter; // constructor
/**
* Prototype interface definition of a converter
*
* @param {Object} model - A manifest that defines the conversion process.
* @param {String} model.input - The input version to convert from.
* @param {String} model.output - The output version to convert to.
* @param {Function} model.convert - A function to convert entire collections.
* @param {Function} model.create - A function to perform creation operations.
* @param {Function} [model.init] - An initializer function to bootstrap the generated converter.
*
* @throws {Error} If model definition does not meet requirements
*/
generateConverter = function (model) {
var error;
// validate the model
if (!model) {
error = 'invalid definition of converter';
}
// ensure that the input version support is a valid semver
else if (!semver.valid(model.input)) {
error = 'input version support for converter is invalid';
}
// ensure that the output version support is a valid semver
else if (!semver.valid(model.output)) {
error = 'output version support for converter is invalid';
}
else if (typeof model.convert !== FN) {
error = 'convert function is not defined';
}
else if (typeof model.convertSingle !== FN) {
error = 'convertSingle function is not defined';
}
if (semver.satisfies(model.input, model.output)) {
error = 'input version ' + model.input + ' matches output version ' + model.output + ' for converter';
}
// If we had encountered any error during validation, we simply exit by executing the callback and forwarding the
// error.
if (error) {
throw new Error(error);
}
return model;
};
module.exports = {
/**
* All converters
*
* @type {object<Converter>}
*
* @note this form of loading is most likely not browser friendly, find browser alternative
*/
converters: {
'converter-v1-to-v2': require('./v1.0.0/converter-v1-to-v2'),
'converter-v1-to-v21': require('./v1.0.0/converter-v1-to-v21'),
'converter-v2-to-v1': require('./v2.0.0/converter-v2-to-v1'),
'converter-v21-to-v1': require('./v2.1.0/converter-v21-to-v1')
},
/**
* Fetches a converter for the given input and output versions
*
* @param {String} inputVersion - The version to convert from.
* @param {String} outputVersion - The version to convert to.
* @returns {Converter} - A converter for the given set of options.
*/
getConverter: function (inputVersion, outputVersion) {
var converter;
inputVersion = semver.clean(inputVersion);
outputVersion = semver.clean(outputVersion);
for (converter in this.converters) {
// eslint-disable-next-line no-prototype-builtins
converter = this.converters.hasOwnProperty(converter) && this.converters[converter];
if (converter && semver.eq(converter.input, inputVersion) && semver.eq(converter.output, outputVersion)) {
return generateConverter(converter);
}
}
},
/**
* Picks the appropriate converter and converts the given collection.
*
* @param {Object} collection - The collection to be converted.
* @param {Object} options - The set of options for request conversion.
* @param {Function} callback - The function to be invoked after the completion of conversion process.
*/
convert: function (collection, options, callback) {
var chosenConverter = this.getConverter(options.inputVersion, options.outputVersion);
if (!chosenConverter) {
return callback(new Error('no conversion path found'));
}
return chosenConverter.convert(collection, options, callback);
},
/**
* Picks the appropriate converter and converts the given object.
*
* @param {Object} object - A single V1 request or a V2 Item.
* @param {Object} options - The set of options for request conversion.
* @param {Function} callback - The function to be invoked after the completion of conversion process.
*/
convertSingle: function (object, options, callback) {
var chosenConverter = this.getConverter(options.inputVersion, options.outputVersion);
if (!chosenConverter) {
return callback(new Error('no conversion path found'));
}
return chosenConverter.convertSingle(object, options, callback);
},
/**
* Picks the appropriate converter and converts the given object.
*
* @param {Object} object - A single V1 Response or a V2 Response.
* @param {Object} options - The set of options for response conversion.
* @param {Function} callback - The function invoked to mark the completion of the conversion process.
* @returns {*}
*/
convertResponse: function (object, options, callback) {
var chosenConverter = this.getConverter(options.inputVersion, options.outputVersion);
if (!chosenConverter) {
return callback(new Error('no conversion path found'));
}
return chosenConverter.convertResponse(object, options, callback);
},
/**
* Returns a builder, which can be used to convert individual requests, etc.
*
* @param {Object} options - The set of options for builder creation.
* @returns {Function} - The builder for the given set of options.
*/
builder: function (options) {
var chosenConverter = this.getConverter(options.inputVersion, options.outputVersion);
return chosenConverter.create(options);
}
};

View File

@@ -0,0 +1,885 @@
/* eslint-disable object-shorthand */
var _ = require('lodash').noConflict(),
constants = require('../../constants'),
v1Common = require('../../common/v1'),
util = require('../../util'),
headersCommentPrefix = '//',
IS_SUBFOLDER = Symbol('_postman_isSubFolder'),
/**
* A constructor that is capable of being used for one-off conversions of requests, and folders.
*
* @param {Object} options - The set of options for builder construction.
* @class Builders
* @constructor
*/
Builders = function (options) {
this.options = options || {};
},
/**
* Parse formdata & urlencoded request data.
* - filter params missing property `key`
* - handle file type param value
* -cleanup `enabled` & `description`
*
* @param {Object[]} data - Data to parse.
* @param {?Boolean} [retainEmpty] - To retain empty values or not.
* @returns {Object[]} - Parsed data.
*/
parseFormData = function (data, retainEmpty) {
if (!(Array.isArray(data) && data.length)) { return []; }
var formdata = [],
i,
ii,
param;
for (i = 0, ii = data.length; i < ii; i++) {
// clone param to avoid mutating data.
// eslint-disable-next-line prefer-object-spread
param = Object.assign({}, data[i]);
// skip if param is missing property `key`,
// `key` is a required property, value can be null/undefined.
// because in a FormParam property lists, key is used for indexing.
if (_.isNil(param.key)) {
continue;
}
// for file `type`, set `value` to `src`
if (param.type === 'file' && !param.src && param.value) {
param.src = (_.isString(param.value) || _.isArray(param.value)) ? param.value : null;
delete param.value;
}
// `hasOwnProperty` check ensures that it don't delete undefined property: `enabled`
if (Object.prototype.hasOwnProperty.call(param, 'enabled')) {
// set `disabled` flag if `enabled` is false.
param.enabled === false && (param.disabled = true);
delete param.enabled; // cleanup
}
// Prevent empty descriptions from showing up in the converted results. This keeps collections clean.
util.cleanEmptyValue(param, 'description', retainEmpty);
formdata.push(param);
}
return formdata;
};
_.assign(Builders.prototype, {
/**
* Constructs a V2 compatible "info" object from a V1 Postman Collection
*
* @param {Object} collectionV1 - A v1 collection to derive collection metadata from.
* @returns {Object} - The resultant v2 info object.
*/
info: function (collectionV1) {
var info = {
_postman_id: collectionV1.id || util.uid(),
name: collectionV1.name
};
if (collectionV1.description) { info.description = collectionV1.description; }
else if (this.options.retainEmptyValues) { info.description = null; }
info.schema = constants.SCHEMA_V2_URL;
return info;
},
/**
* Facilitates sanitized variable transformations across all levels for v1 collection normalization.
*
* @param {Object} entity - The wrapper object containing variable definitions.
* @param {?Object} options - The set of options for the current variable transformation.
* @param {?Object} options.fallback - The set of fallback values to be applied when no variables exist.
* @param {?Boolean} options.noDefaults - When set to true, no defaults are applied.
* @param {?Boolean} options.retainIds - When set to true, ids are left as is.
* @returns {Object[]} - The set of sanitized variables.
*/
variable: function (entity, options) {
return util.handleVars(entity, options);
},
/**
* Constructs a V2 compatible URL object from a V1 request
*
* @param {Object} requestV1 - The source v1 request to extract the URL from.
* @returns {String|Object} - The resultant URL.
*/
url: function (requestV1) {
var queryParams = [],
pathVariables = [],
traversedVars = {},
queryParamAltered,
retainIds = this.options.retainIds,
parsed = util.urlparse(requestV1.url),
retainEmpty = this.options.retainEmptyValues;
// add query params
_.forEach(requestV1.queryParams, function (queryParam) {
(queryParam.enabled === false) && (queryParam.disabled = true);
delete queryParam.enabled;
util.cleanEmptyValue(queryParam, 'description', retainEmpty);
if (_.has(queryParam, 'equals')) {
if (queryParam.equals) {
(queryParam.value === null) && (queryParam.value = '');
}
else {
// = is not appended when the value is null. However,
// non empty value should be preserved
queryParam.value = queryParam.value || null;
}
queryParamAltered = true;
delete queryParam.equals;
}
queryParams.push(queryParam);
});
// only add query params from URL if not given explicitly
if (!_.size(queryParams)) {
// parsed query params are taken from the url, so no descriptions are available from them
queryParams = parsed.query;
}
// Merge path variables
_.forEach(requestV1.pathVariableData, function (pathVariable) {
pathVariable = _.clone(pathVariable);
util.cleanEmptyValue(pathVariable, 'description', retainEmpty);
if (!retainIds && pathVariable.id) {
delete pathVariable.id;
}
pathVariables.push(pathVariable);
traversedVars[pathVariable.key] = true;
});
// pathVariables in v1 are of the form {foo: bar}, so no descriptions can be obtained from them
_.forEach(requestV1.pathVariables, function (value, key) {
!traversedVars[key] && pathVariables.push({
value: value,
key: key
});
});
!_.isEmpty(queryParams) && (parsed.query = queryParams);
!_.isEmpty(pathVariables) && (parsed.variable = pathVariables);
// If the query params have been altered, update the raw stringified URL
queryParamAltered && (parsed.raw = util.urlunparse(parsed));
// return the objectified URL only if query param or path variable descriptions are present, string otherwise
return (parsed.query || parsed.variable) ? parsed : (parsed.raw || requestV1.url);
},
/**
* Extracts the HTTP Method from a V1 request
*
* @param {Object} requestV1 - The v1 request to extract the request method from.
* @returns {String} - The extracted request method.
*/
method: function (requestV1) {
return requestV1.method;
},
/**
* Constructs an array of Key-Values from a raw HTTP Header string.
*
* @param {Object} requestV1 - The v1 request to extract header information from.
* @returns {Object[]} - A list of header definition objects.
*/
header: function (requestV1) {
if (_.isArray(requestV1.headers)) {
return requestV1.headers;
}
var headers = [],
traversed = {},
headerData = requestV1.headerData || [],
retainEmpty = this.options.retainEmptyValues;
_.forEach(headerData, function (header) {
if (_.startsWith(header.key, headersCommentPrefix) || (header.enabled === false)) {
header.disabled = true;
header.key = header.key.replace(headersCommentPrefix, '').trim();
}
// prevent empty header descriptions from showing up in converted results. This keeps the collections clean
util.cleanEmptyValue(header, 'description', retainEmpty);
delete header.enabled;
headers.push(header); // @todo Improve this sequence to account for multi-valued headers
traversed[header.key] = true;
});
// requestV1.headers is a string, so no descriptions can be obtained from it
_.forEach(v1Common.parseHeaders(requestV1.headers), function (header) {
!traversed[header.key] && headers.push(header);
});
return headers;
},
/**
* Constructs a V2 Request compatible "body" object from a V1 Postman request
*
* @param {Object} requestV1 - The v1 request to extract the body from.
* @returns {{mode: *, content: (*|string)}}
*/
body: function (requestV1) {
var modes = {
binary: 'file',
graphql: 'graphql',
params: 'formdata',
raw: 'raw',
urlencoded: 'urlencoded'
},
data = {},
rawModeData,
graphqlModeData,
dataMode = modes[requestV1.dataMode],
retainEmpty = this.options.retainEmptyValues,
bodyOptions = {},
mode,
// flag indicating that all the props which holds request body data
// i.e, data (urlencoded, formdata), rawModeData (raw, file) and graphqlModeData (graphql)
// are empty.
emptyBody = _.isEmpty(requestV1.data) && _.isEmpty(requestV1.rawModeData) &&
_.isEmpty(requestV1.graphqlModeData);
// set body to null if:
// 1. emptyBody is true and dataMode is unset
// 2. dataMode is explicitly set to null
// @note the explicit null check is added to ensure that body is not set
// in case the dataMode was set to null by the app.
if ((!dataMode && emptyBody) || requestV1.dataMode === null) {
return retainEmpty ? null : undefined;
}
// set `rawModeData` if its a string
if (_.isString(requestV1.rawModeData)) {
rawModeData = requestV1.rawModeData;
}
// check if `rawModeData` is an array like: ['rawModeData']
else if (Array.isArray(requestV1.rawModeData) &&
requestV1.rawModeData.length === 1 &&
_.isString(requestV1.rawModeData[0])) {
rawModeData = requestV1.rawModeData[0];
}
// set graphqlModeData if its not empty
if (!_.isEmpty(requestV1.graphqlModeData)) {
graphqlModeData = requestV1.graphqlModeData;
}
// set data.mode.
// if dataMode is not set, infer from data or rawModeData or graphqlModeData
if (dataMode) {
data.mode = dataMode;
}
// at this point we are sure that the body is not empty so let's
// infer the data mode.
// @note its possible that multiple body types are set e.g, both
// rawModeData and graphqlModeData are set. So, the priority will be:
// raw -> formdata -> graphql (aligned with pre-graphql behaviour).
//
// set `formdata` if rawModeData is not set and data is an array
// `data` takes higher precedence over `rawModeData`.
else if (!rawModeData && Array.isArray(requestV1.data || requestV1.rawModeData)) {
data.mode = 'formdata';
}
// set `graphql` if graphqlModeData is set
else if (!rawModeData && graphqlModeData) {
data.mode = 'graphql';
}
// set `raw` mode as default
else {
data.mode = 'raw';
}
if (data.mode === 'raw') {
if (rawModeData) {
data[data.mode] = rawModeData;
}
else if (_.isString(requestV1.data)) {
data[data.mode] = requestV1.data;
}
else {
// empty string instead of retainEmpty check to have parity with other modes.
data[data.mode] = '';
}
}
else if (data.mode === 'graphql') {
data[data.mode] = graphqlModeData;
}
else if (data.mode === 'file') {
// rawModeData can be string or undefined.
data[data.mode] = { src: rawModeData };
}
else {
// parse data for formdata or urlencoded data modes.
// `rawModeData` is checked in case its of type `data`.
data[data.mode] = parseFormData(requestV1.data || requestV1.rawModeData, retainEmpty);
}
if (requestV1.dataOptions) {
// Convert v1 mode to v2 mode
for (mode in modes) {
if (!_.isEmpty(requestV1.dataOptions[mode])) {
bodyOptions[modes[mode]] = requestV1.dataOptions[mode];
}
}
!_.isEmpty(bodyOptions) && (data.options = bodyOptions);
}
if (requestV1.dataDisabled) { data.disabled = true; }
else if (retainEmpty) { data.disabled = false; }
return data;
},
/**
* Constructs a V2 "events" object from a V1 Postman Request
*
* @param {Object} entityV1 - The v1 entity to extract script information from.
* @returns {Object[]|*}
*/
event: function (entityV1) {
if (!entityV1) { return; }
const retainIds = this.options.retainIds;
// if prioritizeV2 is true, events is used as the source of truth
if ((util.notLegacy(entityV1, 'event') || this.options.prioritizeV2) && !_.isEmpty(entityV1.events)) {
// in v1, `events` is regarded as the source of truth if it exists, so handle that first and bail out.
// @todo: Improve this to order prerequest events before test events
_.forEach(entityV1.events, function (event) {
!event.listen && (event.listen = 'test');
// just delete the event.id if retainIds is not set
if (!retainIds && event.id) {
delete event.id;
}
if (event.script) {
// just delete the script.id if retainIds is not set
if (!retainIds && event.script.id) {
delete event.script.id;
}
!event.script.type && (event.script.type = 'text/javascript');
// @todo: Add support for src
_.isString(event.script.exec) && (event.script.exec = event.script.exec.split('\n'));
}
});
return entityV1.events;
}
var events = [];
// @todo: Extract both flows below into a common method
if (entityV1.tests) {
events.push({
listen: 'test',
script: {
type: 'text/javascript',
exec: _.isString(entityV1.tests) ?
entityV1.tests.split('\n') :
entityV1.tests
}
});
}
if (entityV1.preRequestScript) {
events.push({
listen: 'prerequest',
script: {
type: 'text/javascript',
exec: _.isString(entityV1.preRequestScript) ?
entityV1.preRequestScript.split('\n') :
entityV1.preRequestScript
}
});
}
return events.length ? events : undefined;
},
/**
* A number of auth parameter names have changed from V1 to V2. This function calls the appropriate
* mapper function, and creates the V2 auth parameter object.
*
* @param {Object} entityV1 - The v1 entity to derive auth information from.
* @param {?Object} options - The set of options for the current auth cleansing operation.
* @param {?Boolean} [options.includeNoauth=false] - When set to true, noauth is set to null.
* @returns {{type: *}}
*/
auth: function (entityV1, options) {
if (!entityV1) { return; }
if ((util.notLegacy(entityV1, 'auth') || this.options.prioritizeV2) && entityV1.auth) {
return util.authArrayToMap(entityV1, options);
}
if (!entityV1.currentHelper || (entityV1.currentHelper === null) || (entityV1.currentHelper === 'normal')) {
return;
}
var params,
type = v1Common.authMap[entityV1.currentHelper] || entityV1.currentHelper,
auth = {
type: type
};
// Some legacy versions of the App export Helper Attributes as a string.
if (_.isString(entityV1.helperAttributes)) {
try {
entityV1.helperAttributes = JSON.parse(entityV1.helperAttributes);
}
catch (e) {
return;
}
}
if (entityV1.helperAttributes && util.authMappersFromLegacy[entityV1.currentHelper]) {
params = util.authMappersFromLegacy[entityV1.currentHelper](entityV1.helperAttributes);
}
params && (auth[type] = params);
return auth;
},
/**
* Creates a V2 format request from a V1 Postman Collection Request
*
* @param {Object} requestV1 - The v1 request to be transformed.
* @returns {Object} - The converted v2 request.
*/
request: function (requestV1) {
var self = this,
request = {},
retainEmpty = self.options.retainEmptyValues,
units = ['auth', 'method', 'header', 'body', 'url'];
units.forEach(function (unit) {
request[unit] = self[unit](requestV1);
});
if (requestV1.description) { request.description = requestV1.description; }
else if (retainEmpty) { request.description = null; }
return request;
},
/**
* Converts a V1 cookie to a V2 cookie.
*
* @param {Object} cookieV1 - The v1 cookie object to convert.
* @returns {{expires: string, hostOnly: *, httpOnly: *, domain: *, path: *, secure: *, session: *, value: *}}
*/
cookie: function (cookieV1) {
return {
expires: (new Date(cookieV1.expirationDate * 1000)).toString(),
hostOnly: cookieV1.hostOnly,
httpOnly: cookieV1.httpOnly,
domain: cookieV1.domain,
path: cookieV1.path,
secure: cookieV1.secure,
session: cookieV1.session,
value: cookieV1.value,
key: cookieV1.name
};
},
/**
* Gets the saved request for the given response, and handles edge cases between Apps & Sync
*
* Handles a lot of edge cases, so the code is not very clean.
*
* The Flow followed here is:
*
* If responseV1.requestObject is present
* If it is a string
* Try parsing it as JSON
* If parsed,
* return it
* else
* It is a request ID
* If responseV1.request is present
* If it is a string
* Try parsing it as JSON
* If parsed,
* return it
* else
* It is a request ID
* Look up the collection for the request ID and return it, or return undefined.
*
* @param {Object} responseV1 - The v1 response to be converted.
* @returns {Object} - The converted saved request, in v2 format.
*/
savedRequest: function (responseV1) {
var self = this,
associatedRequestId;
if (responseV1.requestObject) {
if (_.isString(responseV1.requestObject)) {
try {
return JSON.parse(responseV1.requestObject);
}
catch (e) {
// if there was an error parsing it as JSON, it's probably an ID, so store it in the ID variable
associatedRequestId = responseV1.requestObject;
}
}
else {
return responseV1.requestObject;
}
}
if (responseV1.request) {
if (_.isString(responseV1.request)) {
try {
return JSON.parse(responseV1.request);
}
catch (e) {
// if there was an error parsing it as JSON, it's probably an ID, so store it in the ID variable
associatedRequestId = responseV1.request;
}
}
else {
return responseV1.request;
}
}
// we have a request ID
return associatedRequestId && _.get(self, ['cache', associatedRequestId]);
},
/**
* Since a V2 response contains the entire associated request that was sent, creating the response means it
* also must use the V1 request.
*
* @param {Object} responseV1 - The response object to convert from v1 to v2.
* @returns {Object} - The v2 response object.
*/
singleResponse: function (responseV1) {
var response = {},
self = this,
originalRequest;
originalRequest = self.savedRequest(responseV1);
// add ids to the v2 result only if both: the id and retainIds are truthy.
// this prevents successive exports to v2 from being overwhelmed by id diffs
self.options.retainIds && (response.id = responseV1.id || util.uid());
response.name = responseV1.name || 'response';
response.originalRequest = originalRequest ? self.request(originalRequest) : undefined;
response.status = responseV1.responseCode && responseV1.responseCode.name || undefined;
response.code = responseV1.responseCode && Number(responseV1.responseCode.code) || undefined;
response._postman_previewlanguage = responseV1.language;
response._postman_previewtype = responseV1.previewType;
response.header = responseV1.headers;
response.cookie = _.map(responseV1.cookies, function (cookie) {
return self.cookie(cookie);
});
response.responseTime = responseV1.time;
response.body = responseV1.text;
return response;
},
/**
* Constructs an array of "sample" responses (compatible with a V2 collection)
* from a Postman Collection V1 Request.
*
* If response ordering via `responses_order` field is present,
* ensure the ordering is respected while constructing responses array.
*
* @param {Object} requestV1 - The v1 request object to extract response information from.
* @returns {Object[]} - The list of v2 response definitions.
*/
response: function (requestV1) {
var self = this,
responses = _.get(requestV1, 'responses', []),
responsesCache = _.keyBy(responses, 'id'),
responses_order = _.get(requestV1, 'responses_order', []);
// If ordering of responses is not available
// Create a default ordering using the `responses` field
if (!(responses_order && responses_order.length)) {
responses_order = _.map(responses, 'id');
}
// Filter out any response id that is not available
responses_order = _.filter(responses_order, function (responseId) {
return _.has(responsesCache, responseId);
});
return _.map(responses_order, function (responseId) {
return self.singleResponse(responsesCache[responseId]);
});
},
/**
* Creates a V2 compatible ``item`` from a V1 Postman Collection Request
*
* @param {Object} requestV1 - Postman collection V1 request.
* @returns {Object} - The converted request object, in v2 format.
*/
singleItem: function (requestV1) {
if (!requestV1) { return; }
var self = this,
retainIds = self.options.retainIds,
units = ['request', 'response'],
variable = self.variable(requestV1, { retainIds: retainIds }),
item = {
name: requestV1.name || '', // Inline building to avoid additional function call
event: self.event(requestV1)
};
retainIds && (item.id = requestV1.id || util.uid());
// add protocolProfileBehavior property from requestV1 to the item
util.addProtocolProfileBehavior(requestV1, item);
units.forEach(function (unit) {
item[unit] = self[unit](requestV1);
});
variable && variable.length && (item.variable = variable);
return item;
},
/**
* Constructs an array of Items & ItemGroups compatible with the V2 format.
*
* @param {Object} collectionV1 - The v1 collection to derive folder information from.
* @returns {Object[]} - The list of item group definitions.
*/
itemGroups: function (collectionV1) {
var self = this,
items = [],
itemGroupCache = {},
retainEmpty = self.options.retainEmptyValues;
// Read all folder data, and prep it so that we can throw subfolders in the right places
itemGroupCache = _.reduce(collectionV1.folders, function (accumulator, folder) {
if (!folder) { return accumulator; }
var retainIds = self.options.retainIds,
auth = self.auth(folder),
event = self.event(folder),
variable = self.variable(folder, { retainIds: retainIds }),
result = {
name: folder.name,
item: []
};
retainIds && (result.id = folder.id || util.uid());
if (folder.description) { result.description = folder.description; }
else if (retainEmpty) { result.description = null; }
(auth || (auth === null)) && (result.auth = auth);
event && (result.event = event);
variable && variable.length && (result.variable = variable);
util.addProtocolProfileBehavior(folder, result);
accumulator[folder.id] = result;
return accumulator;
}, {});
// Populate each ItemGroup with subfolders
_.forEach(collectionV1.folders, function (folderV1) {
if (!folderV1) { return; }
var itemGroup = itemGroupCache[folderV1.id],
hasSubfolders = folderV1.folders_order && folderV1.folders_order.length,
hasRequests = folderV1.order && folderV1.order.length;
// Add subfolders
hasSubfolders && _.forEach(folderV1.folders_order, function (subFolderId) {
if (!itemGroupCache[subFolderId]) {
// todo: figure out what to do when a collection contains a subfolder ID,
// but the subfolder is not actually there.
return;
}
itemGroupCache[subFolderId][IS_SUBFOLDER] = true;
itemGroup.item.push(itemGroupCache[subFolderId]);
});
// Add items
hasRequests && _.forEach(folderV1.order, function (requestId) {
if (!self.cache[requestId]) {
// todo: what do we do here??
return;
}
itemGroup.item.push(self.singleItem(self.cache[requestId]));
});
});
// This compromises some self-healing, which was originally present, but the performance cost of
// doing self-healing the right way is high, so we directly rely on collectionV1.folders_order
// The self-healing way would be to iterate over itemGroupCache directly, but preserving the right order
// becomes a pain in that case.
_.forEach(_.uniq(collectionV1.folders_order || _.map(collectionV1.folders, 'id')), function (folderId) {
var itemGroup = itemGroupCache[folderId];
itemGroup && !_.get(itemGroup, IS_SUBFOLDER) && items.push(itemGroup);
});
// This is useful later
self.itemGroupCache = itemGroupCache;
return _.compact(items);
},
/**
* Creates a V2 compatible array of items from a V1 Postman Collection
*
* @param {Object} collectionV1 - A Postman Collection object in the V1 format.
* @returns {Object[]} - The list of item groups (folders) in v2 format.
*/
item: function (collectionV1) {
var self = this,
requestsCache = _.keyBy(collectionV1.requests, 'id'),
allRequests = _.map(collectionV1.requests, 'id'),
result;
self.cache = requestsCache;
result = self.itemGroups(collectionV1);
_.forEach(_.intersection(collectionV1.order, allRequests), function (requestId) {
var request = self.singleItem(requestsCache[requestId]);
request && (result.push(request));
});
return result;
}
});
module.exports = {
input: '1.0.0',
output: '2.0.0',
Builders: Builders,
/**
* Converts a single V1 request to a V2 item.
*
* @param {Object} request - The v1 request to convert to v2.
* @param {Object} options - The set of options for v1 -> v2 conversion.
* @param {Function} callback - The function to be invoked when the conversion has completed.
*/
convertSingle: function (request, options, callback) {
var builders = new Builders(options),
converted,
err;
try {
converted = builders.singleItem(_.cloneDeep(request));
}
catch (e) {
err = e;
}
if (callback) {
return callback(err, converted);
}
if (err) {
throw err;
}
return converted;
},
/**
* Converts a single V1 Response to a V2 Response.
*
* @param {Object} response - The v1 response to convert to v2.
* @param {Object} options - The set of options for v1 -> v2 conversion.
* @param {Function} callback - The function to be invoked when the conversion has completed.
*/
convertResponse: function (response, options, callback) {
var builders = new Builders(options),
converted,
err;
try {
converted = builders.singleResponse(_.cloneDeep(response));
}
catch (e) {
err = e;
}
if (callback) {
return callback(err, converted);
}
if (err) {
throw err;
}
return converted;
},
/**
* Converts a V1 collection to a V2 collection (performs ID replacement, etc as necessary).
*
* @param {Object} collection - The v1 collection to convert to v2.
* @param {Object} options - The set of options for v1 -> v2 conversion.
* @param {Function} callback - The function to be invoked when the conversion has completed.
*/
convert: function (collection, options, callback) {
collection = _.cloneDeep(collection);
var auth,
event,
variable,
newCollection = {},
units = ['info', 'item'],
builders = new Builders(options),
authOptions = { excludeNoauth: true },
varOpts = options && { fallback: options.env, retainIds: options.retainIds };
try {
units.forEach(function (unit) {
newCollection[unit] = builders[unit](collection);
});
(auth = builders.auth(collection, authOptions)) && (newCollection.auth = auth);
(event = builders.event(collection)) && (newCollection.event = event);
(variable = builders.variable(collection, varOpts)) && (newCollection.variable = variable);
util.addProtocolProfileBehavior(collection, newCollection);
}
catch (e) {
if (callback) {
return callback(e, null);
}
throw e;
}
if (callback) { return callback(null, newCollection); }
return newCollection;
}
};

View File

@@ -0,0 +1,157 @@
/* eslint-disable object-shorthand */
var inherits = require('inherits'),
_ = require('lodash').noConflict(),
url = require('../../url'),
util = require('../../util'),
constants = require('../../constants'),
BaseBuilders = require('./converter-v1-to-v2').Builders,
Builders;
inherits(Builders = function () {
Builders.super_.apply(this, arguments);
}, BaseBuilders);
_.assign(Builders.prototype, {
/**
* Derives v2.1.0 collection info from a v1.0.0 collection object.
*
* @param {Object} collectionV1 - The v1.0.0 collection object to be converted to v2.1.0.
* @return {Object} - The compiled v2.x collection info manifest.
*/
info: function (collectionV1) {
var info = Builders.super_.prototype.info.call(this, collectionV1);
info.schema = constants.SCHEMA_V2_1_0_URL;
return info;
},
/**
* Converts collection request urls from v1.0.0 to v2.1.0
*
* @param {Object} requestV1 - The v1.0.0 request url to be converted to v2.1.0.
* @return {Object} - The objectified v2.1.0 compliant URL.
*/
url: function (requestV1) {
var v21Url = Builders.super_.prototype.url.call(this, requestV1);
return _.isString(v21Url) ? url.parse(v21Url) : v21Url;
},
/**
* A number of auth parameter names have changed from V1 to V2. This function calls the appropriate
* mapper function, and creates the V2 auth parameter object.
*
* @param {Object} entityV1 - A Collection V1 compliant request instance.
* @param {?Object} options - The set of options for the current auth cleansing operation.
* @param {?Boolean} [options.includeNoauth=false] - When set to true, noauth is set to null.
* @returns {{type: *}} - The v2.1.0 compliant request object
*/
auth: function (entityV1, options) {
// if the current auth manifest is at a parent level, no further transformation is needed.
// @todo: Possible dead code, prune when confirmed
if (util.notLegacy(entityV1, 'auth') && entityV1.currentHelper) {
return util.sanitizeAuthArray(entityV1, options);
}
var auth = Builders.super_.prototype.auth.call(this, entityV1, options);
return util.authMapToArray({ auth: auth }, options);
}
});
module.exports = {
input: '1.0.0',
output: '2.1.0',
Builders: Builders,
/**
* Converts a single V1 request to a v2.1.0 item.
*
* @param {Object} request - The v1.0.0 request to be converted to a v2.1.0 format.
* @param {Object} options - The set of options for the current conversion sequence.
* @param {?Function} callback - The function invoked to mark the end of the current conversion process.
* @returns {*}
*/
convertSingle: function (request, options, callback) {
var err,
converted,
builders = new Builders(options);
try { converted = builders.singleItem(_.cloneDeep(request)); }
catch (e) { err = e; }
if (callback) { return callback(err, converted); }
if (err) { throw err; }
return converted;
},
/**
* Converts a single V1 Response to a v2.1.0 Response.
*
* @param {Object} response - The V1 compliant response to convert to a v2.1.0 format.
* @param {Object} options - The set of options for the current conversion process.
* @param {?Function} callback - The function invoked to mark the completion of the response conversion.
* @returns {*}
*/
convertResponse: function (response, options, callback) {
var err,
converted,
builders = new Builders(options);
try { converted = builders.singleResponse(_.cloneDeep(response)); }
catch (e) { err = e; }
if (callback) { return callback(err, converted); }
if (err) { throw err; }
return converted;
},
/**
* Converts a V1 collection to a V2 collection (performs ID replacement, etc as necessary).
*
* @param {Object} collection - The V1 collection instance to convert to a v2.1.0 format.
* @param {Object} options - The set of options for the current conversion sequence.
* @param {?Function} callback - The function invoked to mark the completion of the conversion process/
* @returns {*}
*/
convert: function (collection, options, callback) {
collection = _.cloneDeep(collection);
var auth,
event,
variable,
newCollection = {},
units = ['info', 'item'],
builders = new Builders(options),
authOptions = { excludeNoauth: true },
varOpts = options && { fallback: options.env, retainIds: options.retainIds };
try {
units.forEach(function (unit) {
newCollection[unit] = builders[unit](collection);
});
(auth = builders.auth(collection, authOptions)) && (newCollection.auth = auth);
(event = builders.event(collection)) && (newCollection.event = event);
(variable = builders.variable(collection, varOpts)) && (newCollection.variable = variable);
util.addProtocolProfileBehavior(collection, newCollection);
}
catch (e) {
if (callback) { return callback(e); }
throw e;
}
if (callback) { return callback(null, newCollection); }
return newCollection;
}
};

View File

@@ -0,0 +1,797 @@
/* eslint-disable object-shorthand */
var _ = require('lodash').noConflict(),
util = require('../../util'),
v2Common = require('../../common/v2'),
Builders = function (options) {
this.options = options || {};
};
_.assign(Builders.prototype, {
/**
* Converts v2 style auth manifests into their v1 equivalents.
*
* @param {Object} entityV2 - The v1 auth manifest to be transformed into v1.
* @param {?Object} options - The set of options for the current auth cleansing operation.
* @param {?Boolean} [options.includeNoauth=false] - When set to true, noauth is set to null.
*
* @returns {Object} The transformed auth object.
*/
auth: function (entityV2, options) {
return util.authMapToArray(entityV2, options);
},
/**
* Constructs a V1 "events" object from a V2 Postman entity
*
* @param {Object} entityV2 - The v2 event object to be converted.
* @returns {Object[]} - The resultant v1 script definition.
*/
events: function (entityV2) {
// events is treated as the source of truth in v1, so handle that first and bail out.
var source = entityV2.events || entityV2.event;
if (_.isArray(source)) {
// @todo: Improve this to order prerequest events before test events
_.forEach(source, function (event) {
!event.listen && (event.listen = 'test');
if (event.script) {
!event.script.type && (event.script.type = 'text/javascript');
// @todo: Add support for src
_.isString(event.script.exec) && (event.script.exec = event.script.exec.split('\n'));
}
});
return source;
}
},
/**
* Facilitates sanitized variable transformations across all levels for v1 collection normalization.
*
* @param {Object} entity - The wrapper object containing variable definitions.
* @param {?Object} options - The set of options for the current variable transformation.
* @param {?Object} options.fallback - The set of fallback values to be applied when no variables exist.
* @param {?Boolean} options.noDefaults - When set to true, no defaults are applied.
* @param {?Boolean} options.retainIds - When set to true, ids are left as is.
* @returns {Object[]} - The set of sanitized variables.
*/
variables: function (entity, options) {
return util.handleVars(entity, options);
},
/**
* Extracts all subfolders from a v2.0.0 collection or folder
*
* @param {Object} folderOrCollection - Thw entity to extract child items from.
* @returns {Object[]} - The list of extracted folder items.
*/
extractFolderItems: function (folderOrCollection) {
if (!folderOrCollection) { return; }
var i,
self = this,
folders = [],
items = folderOrCollection.item || folderOrCollection.items;
!_.isArray(items) && (items = [items]);
for (i = 0; i < items.length; i++) {
if (items[i] && (items[i].items || items[i].item)) {
folders.push(items[i]);
folders = [].concat(folders, self.extractFolderItems(items[i]));
}
}
return folders;
},
/**
* Extracts all requests from a v2.0.0 collection or folder
*
* @param {Object} folderOrCollection - The object to extract sub-items from.
* @returns {Object[]} - The list of extracted folder items.
*/
extractItems: function (folderOrCollection) {
if (!folderOrCollection) { return; }
var i,
self = this,
requests = [],
isFolder,
items = folderOrCollection.item || folderOrCollection.items;
!_.isArray(items) && (items = [items]);
for (i = 0; i < items.length; i++) {
isFolder = items[i] && (items[i].items || items[i].item);
if (items[i]) {
isFolder ? (requests = [].concat(requests, self.extractItems(items[i]))) : requests.push(items[i]);
}
}
return requests;
},
/**
* Constructs a monolithic raw HTTP header block from a V2 header array
*
* @param {Object} item - The wrapper object to extract headers from.
* @returns {*|string} - The resultant header string.
*/
headers: function (item) {
if (!(item && item.request)) { return; }
return _.map(item.request.headers || item.request.header, function (header) {
return (header.disabled ? '// ' : '') + header.key + ': ' + header.value;
}).join('\n');
},
/**
* Detects the data mode from a given Postman Collection V2 item
*
* @param {Object} item - .
* @returns {*|number|string}
*/
dataMode: function (item) {
return v2Common.modeMap[_.get(item, 'request.body.mode')];
},
/**
* Returns the appropriate request data based on the data mode.
*
* @param {Object} item - The .
* @returns {Object} - The converted request body.
*/
data: function (item) {
var self = this,
mode = _.get(item, 'request.body.mode'),
retainEmpty = this.options.retainEmptyValues;
if (mode === 'raw' || mode === 'file' || mode === 'graphql' || !mode) {
return retainEmpty ? null : undefined;
}
return _.map(item.request.body[mode], function (elem) {
// Only update the value in v1 if src in v2 is non-empty
if (elem && elem.type === 'file' && _.has(elem, 'src')) {
elem.value = (_.isString(elem.src) || _.isArray(elem.src)) ? elem.src : null;
delete elem.src;
}
// Prevents empty request body descriptions from showing up in the result, keeps collections clean.
elem.description = self.description(elem.description);
_.has(elem, 'disabled') && (elem.enabled = !elem.disabled);
delete elem.disabled;
return elem;
});
},
/**
* In case of raw request bodies, this constructs the proper raw data from a V2 item.
*
* @param {Object} item - The v2 request to derive body information from.
* @returns {String} - The inferred v1 request body mode.
*/
rawModeData: function (item) {
var mode = _.get(item, 'request.body.mode');
if (mode === 'raw') {
return item.request.body.raw;
}
else if (mode === 'file') {
return _.get(item.request.body, 'file.src');
}
return this.options.retainEmptyValues ? null : undefined;
},
/**
* Returns GraphQL data if mode is set to graphql.
*
* @param {Object} item - The v2 request to derive information form.
* @returns {Object} - GraphQL request body.
*/
graphqlModeData: function (item) {
var mode = _.get(item, 'request.body.mode');
if (mode === 'graphql') {
return item.request.body.graphql;
}
return this.options.retainEmptyValues ? null : undefined;
},
/**
* Creates options for body from v2 format.
*
* @param {Object} item - The v2 request to derive information form.
* @returns {Object} - The converted body options.
*/
dataOptions: function (item) {
var options = _.get(item, 'request.body.options'),
bodyOptions = {},
mode;
if (!options) {
return;
}
// Convert v2 mode to v1 mode
for (mode in v2Common.modeMap) {
if (!_.isEmpty(options[mode])) {
bodyOptions[v2Common.modeMap[mode]] = options[mode];
}
}
return _.isEmpty(bodyOptions) ? undefined : bodyOptions;
},
/**
* Creates an object of path-variables and their values from a V2 item
*
* @param {Object} item - The wrapper object containing path variable information.
* @returns {Object} - The resultant hash of path variables.
*/
pathVariables: function (item) {
var variable = _.get(item, 'request.url.variables') || _.get(item, 'request.url.variable');
if (!variable) { return; }
return _.transform(variable, function (accumulator, v) {
accumulator[v.key || v.id] = v.value; // v2.0.0 supports both key and id, v2.1.0 will drop id support
}, {});
},
/**
* Creates a V1 URL from a V2 item
*
* @param {Object} item - The v2 request to extract the URL from.
* @returns {String} - The extracted URL of the request.
*/
url: function (item) {
var url = _.get(item, 'request.url');
if (_.isString(url)) {
return url;
}
if (!url) {
return '';
}
return util.urlunparse(url);
},
/**
* Extracts test from a V2 collection
*
* @param {Object} item - The wrapper object to extract test code from.
* @returns {String} - The resultant test script code.
*/
tests: function (item) {
var allEvents = item.events || item.event,
events;
// Nothing to do if the item has no associated events
if (!allEvents) {
return;
}
events = _.filter(allEvents, { listen: 'test' });
return _.map(events, function (event) {
var tests = _.get(event, 'script.exec');
// @todo: Possible dead code, remove when confirmed
if (_.isArray(tests)) {
tests = tests.join('\n');
}
return tests;
}).join('\n');
},
/**
* Extracts the pre-request script from an Item
*
* @param {Object} item - The wrapper object to extract pre-request code from.
* @returns {String} - The resultant pre-request script code.
*/
preRequestScript: function (item) {
var allEvents = item.events || item.event,
events;
// Nothing to do if the item has no associated events
if (!allEvents) {
return;
}
events = _.filter(allEvents, { listen: 'prerequest' });
return _.map(events, function (event) {
var tests = _.get(event, 'script.exec');
// @todo: Possible dead code, remove when confirmed
if (_.isArray(tests)) {
tests = tests.join('\n');
}
return tests;
}).join('\n');
},
/**
* Converts a V2 cookie to a V1 cookie.
*
* @param {Object} cookieV2 - The v2 cookie object to be converted.
* @returns {{expirationDate: *, hostOnly: *, httpOnly: *,
* domain: (any), path: (any), secure: *, session: *, value: *, name: *}}
*/
cookie: function (cookieV2) {
return {
expirationDate: cookieV2.expires,
hostOnly: cookieV2.hostOnly,
httpOnly: cookieV2.httpOnly,
domain: cookieV2.domain,
path: cookieV2.path,
secure: cookieV2.secure,
session: cookieV2.session,
value: cookieV2.value,
name: cookieV2.key || cookieV2.name
};
},
/**
* Converts a V2 response object to a V1 response
*
* @param {Object} responseV2 - The v2 response to be converted.
* @returns {Object} - The converted v1 response.
*/
response: function (responseV2) {
var self = this,
response = {},
id = responseV2.id || responseV2._postman_id,
originalRequest = responseV2.originalRequest || responseV2.request;
// the true in the next line ensures that we don't recursively go on processing responses in a request.
response.request = originalRequest ? self.request({ request: originalRequest }, undefined, true) : undefined;
// add the requestObject to the response (needed by sync)
try {
response.request && (response.requestObject = JSON.stringify(response.request));
}
catch (e) { /* It's fine, not a fatal error, just move on. */ }
// do not attempt to regenerate response id here when `retainIds` is set to false
// if id is changed here the parent's `responses_order` also needs to be changed
// that can't be done yet
response.id = id || util.uid();
response.name = responseV2.name;
response.status = responseV2.status;
response.responseCode = {
code: responseV2.code,
name: responseV2.status,
// TODO: get a list of descriptions
detail: ''
};
response.language = responseV2._postman_previewlanguage || 'Text';
response.previewType = responseV2._postman_previewtype || 'html';
response.time = responseV2.responseTime;
response.headers = responseV2.headers || responseV2.header;
response.cookies = _.map(responseV2.cookies || responseV2.cookie, self.cookie);
response.text = responseV2.body;
response.rawDataType = 'text';
return response;
},
/**
* Extracts the array of responses from a V2 Item.
*
* @param {Object} item - The v2 item to extract saved responses from.
* @returns {Object[]} - The list of saved response objects for the current request.
*/
responses: function (item) {
var self = this,
allResponses = item.responses || item.response;
if (!allResponses) { return; }
return _.map(allResponses, function (response) {
return self.response(response, item);
});
},
/**
* Creates an ordering field for responses of a V2 Item.
*
* @param {Object} item - The v2 item to extract saved responses from.
* @returns {Object[]} - The order of responses within the V2 Item.
*/
responses_order: function (item) {
var allResponses = item.responses || item.response;
if (!allResponses) {
return [];
}
return _.map(allResponses, 'id');
},
/**
* Converts a V2 request to a V1 request.
*
* @param {Object} item - The v2 item to be converted.
* @param {Object} collectionId - The collection id related to the current conversion routine.
* @param {Boolean} [skipResponses=false] - When set to true, excludes saved responses from the result.
* @returns {{id: *, name: *, description: (*|string|builders.description), url: *, collectionId: *, method: *,
* currentHelper: *, helperAttributes: *}|*}
*/
request: function (item, collectionId, skipResponses) {
if (!item) { return; }
var units = ['headers', 'dataMode', 'data', 'rawModeData', 'graphqlModeData',
'pathVariables', 'tests', 'preRequestScript', 'url', 'dataOptions'],
self = this,
request,
description,
currentHelper,
helperAttributes,
req = item && item.request,
v2Auth = req && req.auth,
auth = self.auth(req),
events = self.events(item),
variables = self.variables(item, { retainIds: self.options.retainIds }),
url = req && req.url,
retainEmpty = self.options.retainEmptyValues,
urlObj = _.isString(url) ? util.urlparse(url) : url;
if (!skipResponses) {
units.push('responses');
units.push('responses_order');
}
if (v2Auth && v2Auth.type) {
// @todo: Add support for custom auth helpers
currentHelper = v2Common.authMap[v2Auth.type];
if (util.authMappersFromCurrent[currentHelper]) {
_.isArray(v2Auth[v2Auth.type]) && (v2Auth = util.authArrayToMap(req));
helperAttributes = util.authMappersFromCurrent[currentHelper](v2Auth[v2Auth.type]);
}
else {
helperAttributes = null;
}
}
else if (v2Auth === null) {
currentHelper = null;
helperAttributes = null;
}
request = {
// do not attempt to regenerate request id here when `retainIds` is set to false
// if id is changed here the parent's `order` also needs to be changed
// that can't be done yet
id: item.id || item._postman_id || util.uid(),
name: item.name,
collectionId: collectionId,
method: item.request ? item.request.method : undefined,
currentHelper: currentHelper,
helperAttributes: helperAttributes
};
// add protocolProfileBehavior property from item to the request
util.addProtocolProfileBehavior(item, request);
// only include the dataDisabled flag if truthy
if (req && req.body && _.has(req.body, 'disabled') && (req.body.disabled || retainEmpty)) {
request.dataDisabled = Boolean(req.body.disabled);
}
description = item.request && self.description(item.request.description);
// Prevent empty request descriptions from showing up in the converted result, keeps collections clean.
if (description) { request.description = description; }
else if (retainEmpty) { request.description = null; }
(auth || (auth === null)) && (request.auth = auth);
events && events.length && (request.events = events);
variables && variables.length && (request.variables = variables);
_.forEach(units, function (unit) {
request[unit] = self[unit](item);
});
// description transformations for v2 to v1
urlObj && (request.pathVariableData = _.map(urlObj.variables || urlObj.variable, function (v) {
var result = { key: v.key || v.id, value: v.value };
// Prevent empty path variable descriptions from showing up in converted results, keeps collections clean.
if (v.description) { result.description = self.description(v.description); }
else if (retainEmpty) { result.description = null; }
return result;
}));
urlObj && (request.queryParams = _.map(urlObj.query, function (queryParam) {
// Prevents empty query param descriptions from showing up in the result, keeps collections clean.
queryParam.description = self.description(queryParam.description);
_.has(queryParam, 'disabled') && (queryParam.enabled = !queryParam.disabled);
delete queryParam.disabled;
return queryParam;
}));
// item truthiness is already validated by this point
request.headerData = _.map(item.request && (item.request.headers || item.request.header), function (header) {
// Prevents empty query param descriptions from showing up in the result, keeps collections clean.
header.description = self.description(header.description);
_.has(header, 'disabled') && (header.enabled = !header.disabled);
delete header.disabled;
return header;
});
return request;
},
/**
* Creates a V1 compatible array of requests from a Postman V2 collection.
*
* @param {Object} collectionV2 - The v2 collection to derive v1 requests from.
* @returns {Object[]} - The list of v1 request objects.
*/
requests: function (collectionV2) {
var self = this,
requests = [],
info = collectionV2 && collectionV2.info,
id = info && (info.id || info._postman_id) || collectionV2.id;
_.forEach(self.extractItems(collectionV2), function (item) {
var requestV1 = self.request(item, id);
requests.push(requestV1);
});
return requests;
},
/**
* Creates a V1 compatible array of solo requestIds from a Postman collection V2
*
* @param {Object} collectionV2 - The v2 collection to be used for request order derivation.
* @returns {Object[]} - The request order for the resultant v1 collection.
*/
order: function (collectionV2) {
var itemArray = collectionV2.items || collectionV2.item,
allItems = _.isArray(itemArray) ? itemArray : [itemArray];
// eslint-disable-next-line lodash/prefer-compact
return _.filter(_.map(allItems, function (item) {
if (!item) { return; }
var isFolder = (item.items || item.item);
if (!isFolder) {
return item.id || item._postman_id;
}
}));
},
/**
* Creates a V1 compatible array of folder orders from a Postman collection V2
*
* @param {Object} folderOrCollection - The object to derive folder order details from.
* @returns {Object[]} - The list of folder ids that indicate the order.
*/
folders_order: function (folderOrCollection) {
var itemArray = folderOrCollection.items || folderOrCollection.item,
allItems = _.isArray(itemArray) ? itemArray : [itemArray];
// eslint-disable-next-line lodash/prefer-compact
return _.filter(_.map(allItems, function (item) {
if (!item) { return; }
var isFolder = (item.items || item.item);
if (isFolder) {
return item.id || item._postman_id;
}
}));
},
/**
* Creates an array of V1 compatible folders from a V2 collection
*
* @param {Object} collectionV2 - The v2 collection to derive folder structure information from.
* @returns {Object[]} - The list of folder definitions.
*/
folders: function (collectionV2) {
var self = this,
retainEmpty = self.options.retainEmptyValues;
return _.map(self.extractFolderItems(collectionV2), function (folder) {
if (!folder) { return; }
var folderItems = folder.items || folder.item,
description = self.description(folder.description),
auth = self.auth(folder),
events = self.events(folder),
variables = self.variables(folder, { retainIds: self.options.retainIds }),
result = {
// do not attempt to regenerate folder id here when `retainIds` is set to false
// if id is changed here the parent's `folder_order` also needs to be changed
// that can't be done yet
id: folder.id || folder._postman_id || util.uid(),
name: folder.name,
// eslint-disable-next-line lodash/prefer-compact
order: _.filter(_.map(folderItems, function (f) {
if (!f) { return; }
var isFolder = (f.items || f.item);
return !isFolder && (f.id || f._postman_id);
})),
folders_order: self.folders_order(folder)
};
((auth && auth.type) || (auth === null)) && (result.auth = auth);
events && events.length && (result.events = events);
variables && variables.length && (result.variables = variables);
util.addProtocolProfileBehavior(folder, result);
// Prevent empty folder descriptions from showing up in the result, keeps collections clean.
if (description) { result.description = description; }
else if (retainEmpty) { result.description = null; }
return result;
});
},
/**
* Creates the v1.0.0 compatible description string from the v2.0.0 description format.
*
* @param {Object} descriptionV2 - The v2 style description to be converted
*
* @returns {String} - The resultant v1 description.
*/
description: function (descriptionV2) {
var description,
retainEmpty = this.options.retainEmptyValues;
description = _.isObject(descriptionV2) ? descriptionV2.content : descriptionV2;
if (description) { return description; }
else if (retainEmpty) { return null; }
}
});
module.exports = {
input: '2.0.0',
output: '1.0.0',
Builders: Builders,
/**
* Converts a single V2 item to a V1 request.
*
* @param {Object} request - The request to be converted.
* @param {Object} options - The set of options for request conversion.
* @param {Function} callback - The function to be invoked after conversion has completed.
*/
convertSingle: function (request, options, callback) {
var builders = new Builders(options),
clone = _.cloneDeep(request),
converted,
err;
try {
clone = v2Common.populateIds(clone);
converted = builders.request(clone);
}
catch (e) {
err = e;
}
if (callback) {
return callback(err, converted);
}
if (err) {
throw err;
}
return converted;
},
/**
* Converts a single V2 item to a V1 request.
*
* @param {Object} response - The response to be converted.
* @param {Object} options - The set of options for request conversion.
* @param {Function} callback - The function to be invoked after conversion has completed.
*/
convertResponse: function (response, options, callback) {
var builders = new Builders(options),
converted,
err;
try {
converted = builders.response(_.cloneDeep(response));
}
catch (e) {
err = e;
}
if (callback) {
return callback(err, converted);
}
if (err) {
throw err;
}
return converted;
},
/**
* Converts a V2 collection to a V1 collection (performs ID replacement, etc as necessary).
*
* @param {Object} collection - The collection to be converted.
* @param {Object} options - The set of options for request conversion.
* @param {Function} callback - The function to be invoked after conversion has completed.
*/
convert: function (collection, options, callback) {
collection = _.cloneDeep(collection);
var auth,
events,
variables,
builders = new Builders(options),
authOptions = { excludeNoauth: true },
varOpts = options && { fallback: options.env, retainIds: options.retainIds },
units = ['order', 'folders_order', 'folders', 'requests'],
id = _.get(collection, 'info._postman_id') || _.get(collection, 'info.id'),
info = collection && collection.info,
newCollection = {
id: id && options && options.retainIds ? id : util.uid(),
name: info && info.name
};
// ensure that each item has an ID
collection = v2Common.populateIds(collection);
try {
// eslint-disable-next-line max-len
newCollection.description = builders.description(info && info.description);
(auth = builders.auth(collection, authOptions)) && (newCollection.auth = auth);
(events = builders.events(collection)) && (newCollection.events = events);
(variables = builders.variables(collection, varOpts)) && (newCollection.variables = variables);
util.addProtocolProfileBehavior(collection, newCollection);
units.forEach(function (unit) {
newCollection[unit] = builders[unit](collection);
});
}
catch (e) {
if (callback) {
return callback(e, null);
}
throw e;
}
if (callback) {
return callback(null, newCollection);
}
return newCollection;
}
};

View File

@@ -0,0 +1,129 @@
/* eslint-disable object-shorthand */
var _ = require('lodash').noConflict(),
util = require('../../util'),
inherits = require('inherits'),
v2Common = require('../../common/v2'),
BaseBuilders = require('../v2.0.0/converter-v2-to-v1').Builders,
Builders;
inherits(Builders = function () {
Builders.super_.apply(this, arguments);
}, BaseBuilders);
_.assign(Builders.prototype, {
/**
* Converts arrays of v2.1 style auth params to their v1.0.0 equivalent objects.
*
* @param {Object} entity - A v2.1 compliant wrapped auth manifest.
* @param {?Object} options - The set of options for the current auth cleansing operation.
* @param {?Boolean} [options.includeNoauth=false] - When set to true, noauth is set to null.
* @returns {Object} - A v1 compliant set of auth helper attributes.
*/
auth: function (entity, options) {
return util.sanitizeAuthArray(entity, options);
}
});
module.exports = {
input: '2.1.0',
output: '1.0.0',
Builders: Builders,
/**
* Converts a single V2 item to a V1 request.
*
* @param {Object} request - The request to be converted.
* @param {Object} options - The set of options for request conversion.
* @param {Function} callback - The function to be invoked after conversion has completed.
*/
convertSingle: function (request, options, callback) {
var err,
converted,
clone = _.cloneDeep(request),
builders = new Builders(options);
try {
clone = v2Common.populateIds(clone);
converted = builders.request(clone);
}
catch (e) { err = e; }
if (callback) { return callback(err, converted); }
if (err) { throw err; }
return converted;
},
/**
* Converts a single V2 item to a V1 request.
*
* @param {Object} response - The response to be converted.
* @param {Object} options - The set of options for request conversion.
* @param {Function} callback - The function to be invoked after conversion has completed.
*/
convertResponse: function (response, options, callback) {
var builders = new Builders(options),
converted,
err;
try { converted = builders.response(_.cloneDeep(response)); }
catch (e) { err = e; }
if (callback) { return callback(err, converted); }
if (err) { throw err; }
return converted;
},
/**
* Converts a V2 collection to a V1 collection (performs ID replacement, etc as necessary).
*
* @param {Object} collection - The collection to be converted.
* @param {Object} options - The set of options for request conversion.
* @param {Function} callback - The function to be invoked after conversion has completed.
*/
convert: function (collection, options, callback) {
collection = _.cloneDeep(collection);
var auth,
events,
variables,
builders = new Builders(options),
authOptions = { excludeNoauth: true },
units = ['order', 'folders_order', 'folders', 'requests'],
varOpts = options && { fallback: options.env, retainIds: options.retainIds },
id = _.get(collection, 'info._postman_id') || _.get(collection, 'info.id'),
info = collection && collection.info,
newCollection = {
id: id && options && options.retainIds ? id : util.uid(),
name: info && info.name
};
// ensure that each item has an id
collection = v2Common.populateIds(collection);
try {
// eslint-disable-next-line max-len
newCollection.description = builders.description(info && info.description);
(auth = builders.auth(collection, authOptions)) && (newCollection.auth = auth);
(events = builders.events(collection)) && (newCollection.events = events);
(variables = builders.variables(collection, varOpts)) && (newCollection.variables = variables);
util.addProtocolProfileBehavior(collection, newCollection);
units.forEach(function (unit) {
newCollection[unit] = builders[unit](collection);
});
}
catch (e) {
if (callback) { return callback(e, null); }
throw e;
}
if (callback) { return callback(null, newCollection); }
return newCollection;
}
};

View File

@@ -0,0 +1,97 @@
var semver = require('semver'),
util = require('./util'),
converter = require('./converters'),
normalizers = require('./normalizers');
module.exports = {
/**
* Converts a Collection between different versions, based on the given input.
*
* @param {Object} collection - The collection object to be converted.
* @param {Object} options - The set of conversion options.
* @param {String} options.outputVersion - The version to convert to.
* @param {String} options.inputVersion - The version to convert from.
* @param {Function} callback - The function invoked to mark the completion of the conversion process.
*/
convert: function (collection, options, callback) {
if (!options.outputVersion || !semver.valid(options.outputVersion, true)) {
return callback(new Error('Output version not specified or invalid: ' + options.outputVersion));
}
if (!options.inputVersion || !semver.valid(options.inputVersion, true)) {
return callback(new Error('Input version not specified or invalid: ' + options.inputVersion));
}
return converter.convert(collection, options, callback);
},
normalize: normalizers.normalize,
normalizeSingle: normalizers.normalizeSingle,
normalizeResponse: normalizers.normalizeResponse,
/**
* Export the utilities
*/
util: util,
/**
* Checks whether the given object is a v1 collection
*
* @param {Object} object - The Object to check for v1 collection compliance.
* @returns {Boolean} - A boolean result indicating whether or not the passed object was a v1 collection.
*/
isv1: function (object) {
return Boolean(object && object.name && object.order && object.requests);
},
/**
* Checks whether the given object is a v2 collection
*
* @param {Object} object - The Object to check for v2 collection compliance.
* @returns {Boolean} - A boolean result indicating whether or not the passed object was a v2 collection.
*/
isv2: function (object) {
return Boolean(object && object.info && object.info.schema);
},
/**
* Converts a single V1 request to a V2 Item, or a V2 item to a single V1 request.
*
* @param {Object} object - A V1 request or a V2 item.
* @param {Object} options - The set of options for response conversion.
* @param {String} options.outputVersion - The version to convert to.
* @param {String} options.inputVersion - The version to convert from.
* @param {Function} callback - The function invoked to mark the completion of the conversion process.
*/
convertSingle: function (object, options, callback) {
if (!options.outputVersion || !semver.valid(options.outputVersion, true)) {
return callback(new Error('Output version not specified or invalid: ' + options.outputVersion));
}
if (!options.inputVersion || !semver.valid(options.inputVersion, true)) {
return callback(new Error('Input version not specified or invalid: ' + options.inputVersion));
}
return converter.convertSingle(object, options, callback);
},
/**
* Converts a single V1 request to a V2 Item, or a V2 item to a single V1 request.
*
* @param {Object} object - A V1 request or a V2 item.
* @param {Object} options - The set of options for response conversion.
* @param {String} options.outputVersion - The version to convert to.
* @param {String} options.inputVersion - The version to convert from.
* @param {Function} callback - The function invoked to mark the completion of the conversion process.
*/
convertResponse: function (object, options, callback) {
if (!options.outputVersion || !semver.valid(options.outputVersion, true)) {
return callback(new Error('Output version not specified or invalid: ' + options.outputVersion));
}
if (!options.inputVersion || !semver.valid(options.inputVersion, true)) {
return callback(new Error('Input version not specified or invalid: ' + options.inputVersion));
}
return converter.convertResponse(object, options, callback);
}
};

View File

@@ -0,0 +1,79 @@
/* eslint-disable object-shorthand */
var semver = require('semver'),
// @todo: Add support for more normalizers
normalizers = {
'1.0.0': require('./v1')
};
module.exports = {
/**
* Accepts the arguments for normalization and invokes the appropriate normalizer with them.
*
* @param {Object} collection - The plain collection JSON to be normalized.
* @param {Object} options - A set of options for the current normalization.
* @param {String} options.normalizeVersion - The base collection schema version for which to normalize.
* @param {?Boolean} [options.mutate=false] - When set to true, normalization is done in place.
* @param {?Boolean} [options.noDefaults=false] - When set to true, sensible defaults are not added.
* @param {?Boolean} [options.prioritizeV2=false] - When set to true, v2 style properties are checked first.
* @param {?Boolean} [options.retainEmptyValues=false] - When set to true, empty values are set to '' instead of
* being removed.
* @param {Function} callback - A function invoked to indicate the completion of the normalization process.
* @returns {*}
*/
normalize: function (collection, options, callback) {
var version;
if (!options || !(version = semver.valid(options.normalizeVersion, true)) || !normalizers[version]) {
return callback(new Error('Version not specified or invalid: ' + options.normalizeVersion));
}
return normalizers[version].normalize(collection, options, callback);
},
/**
* Normalizes a single request or item as per the provided version.
*
* @param {Object} object - The entity to be normalized.
* @param {Object} options - The set of options to be applied to the current normalization.
* @param {String} options.normalizeVersion - The base collection schema version for which to normalize.
* @param {?Boolean} [options.mutate=false] - When set to true, normalization is done in place.
* @param {?Boolean} [options.noDefaults=false] - When set to true, sensible defaults are not added.
* @param {?Boolean} [options.prioritizeV2=false] - When set to true, v2 style properties are checked first.
* @param {?Boolean} [options.retainEmptyValues=false] - When set to true, empty values are set to '' instead of
* being removed.
* @param {Function} callback - The function invoked when the normalization has completed.
*/
normalizeSingle: function (object, options, callback) {
var version;
if (!options || !(version = semver.valid(options.normalizeVersion, true)) || !normalizers[version]) {
return callback(new Error('Version not specified or invalid: ' + options.normalizeVersion));
}
return normalizers[version].normalizeSingle(object, options, callback);
},
/**
* Normalizes a single response as per the provided version.
*
* @param {Object} response - The response to be normalized.
* @param {Object} options - The set of options to be applied to the current normalization.
* @param {String} options.normalizeVersion - The base collection schema version for which to normalize.
* @param {?Boolean} [options.mutate=false] - When set to true, normalization is done in place.
* @param {?Boolean} [options.noDefaults=false] - When set to true, sensible defaults are not added.
* @param {?Boolean} [options.prioritizeV2=false] - When set to true, v2 style properties are checked first.
* @param {?Boolean} [options.retainEmptyValues=false] - When set to true, empty values are set to '' instead of
* being removed.
* @param {Function} callback - The function invoked when the normalization has completed.
*/
normalizeResponse: function (response, options, callback) {
var version;
if (!options || !(version = semver.valid(options.normalizeVersion, true)) || !normalizers[version]) {
return callback(new Error('Version not specified or invalid: ' + options.normalizeVersion));
}
return normalizers[version].normalizeResponse(response, options, callback);
}
};

View File

@@ -0,0 +1,693 @@
/* eslint-disable object-shorthand */
var _ = require('lodash').noConflict(),
v1Common = require('../common/v1'),
v2Common = require('../common/v2'),
util = require('../util'),
url = require('../url'),
Builders = function (options) {
this.options = options || {};
},
script = function (entityV1, listen, key) {
return {
listen: listen,
script: {
type: 'text/javascript',
exec: _.isString(entityV1[key]) ? entityV1[key].split('\n') : entityV1[key]
}
};
},
authIdMap = {
apikey: 'apikeyAuth',
awsSigV4: 'awsSigV4',
basic: 'basicAuth',
bearer: 'bearerAuth',
digest: 'digestAuth',
hawk: 'hawkAuth',
ntlm: 'ntlmAuth',
oAuth1: 'oAuth1',
oAuth2: 'oAuth2'
},
/**
* Normalizes `description` field of an entity.
* If `description` field is absent, this is a no-op.
* Will mutate the entity.
*
* @param {Object} entity - Wrapper object, possibly containing a description field
* @param {Object} builder - Builder instance that will be called to perform normalization
* @param {Object} utilOptions - Options to be passed to util fn
*/
normalizeDescription = function (entity, builder, utilOptions) {
var retainEmptyValues = _.get(utilOptions, 'retainEmptyValues');
if (_.has(entity, 'description')) {
entity.description = builder.description(entity.description);
}
util.cleanEmptyValue(entity, 'description', retainEmptyValues);
return entity;
};
_.assign(Builders.prototype, {
/**
* Normalizes inherited v1 auth manifests.
*
* @param {Object} entityV1 - A v1 compliant wrapped auth manifest.
* @param {?Object} options - The set of options for the current auth cleansing operation.
* @param {?Boolean} [options.includeNoauth=false] - When set to true, noauth is set to ''.
*
* @returns {Object} - A v1 compliant set of auth helper attributes.
*/
auth: function (entityV1, options) {
if (!entityV1) { return; }
var auth,
params,
mapper,
currentHelper,
helperAttributes,
prioritizeV2 = this.options.prioritizeV2;
// if prioritize v2 is true, use auth as the source of truth
if (util.notLegacy(entityV1, 'auth') || (entityV1.auth && prioritizeV2)) {
return util.sanitizeAuthArray(entityV1, options);
}
if ((entityV1.currentHelper === null) || (entityV1.currentHelper === 'normal')) { return null; }
currentHelper = entityV1.currentHelper;
helperAttributes = entityV1.helperAttributes;
// if noDefaults is false and there is no currentHelper, bail out
if (!(currentHelper || this.options.noDefaults)) { return; }
// if there is a currentHelper without helperAttributes, bail out.
if (currentHelper && !helperAttributes) { return this.options.noDefaults ? undefined : null; }
!currentHelper && (currentHelper = authIdMap[helperAttributes && helperAttributes.id]);
auth = { type: v1Common.authMap[currentHelper] };
mapper = util.authMappersFromLegacy[currentHelper];
// @todo: Change this to support custom auth helpers
mapper && helperAttributes && (params = mapper(helperAttributes)) && (auth[auth.type] = params);
return util.authMapToArray({ auth: auth }, options);
},
/**
* Normalizes v1 collection events.
*
* @param {Object} entityV1 - The v1 entity to be normalized.
* @returns {Array|null} - The normalized events.
*/
events: function (entityV1) {
if (!entityV1) { return; }
if ((util.notLegacy(entityV1, 'event') || this.options.prioritizeV2) && !_.isEmpty(entityV1.events)) {
// @todo: Improve this to order prerequest events before test events
_.forEach(entityV1.events, function (event) {
!event.listen && (event.listen = 'test');
if (event.script) {
!event.script.type && (event.script.type = 'text/javascript');
// @todo: Add support for src
_.isString(event.script.exec) && (event.script.exec = event.script.exec.split('\n'));
}
});
return entityV1.events;
}
var events = [];
entityV1.preRequestScript && events.push(script(entityV1, 'prerequest', 'preRequestScript'));
entityV1.tests && events.push(script(entityV1, 'test', 'tests'));
if (events.length) { return events; }
// retain `null` events
if (entityV1.events === null) { return null; }
},
/**
* Facilitates sanitized variable transformations across all levels for v1 collection normalization.
*
* @param {Object} entity - The wrapper object containing variable definitions.
* @param {?Object} options - The set of options for the current variable transformation.
* @param {?Object} options.fallback - The set of fallback values to be applied when no variables exist.
* @param {?Boolean} options.noDefaults - When set to true, no defaults are applied.
* @param {?Boolean} options.retainIds - When set to true, ids are left as is.
* @returns {Object[]} - The set of sanitized variables.
*/
variables: function (entity, options) {
var self = this,
// Use builder's own options if override is not requested
results = util.handleVars(entity, options || this.options);
// Normalize descriptions that may have been passed in as objects
results = _.map(results, function (item) {
return normalizeDescription(item, self, options || self.options);
});
if (results.length) {
return results;
}
},
/**
* Sanitizes request v1 data.
*
* @param {Object} requestV1 - The wrapper v1 request object around the data list to be sanitized.
* @returns {Object[]} - The normalized list of request body parameters.
*/
data: function (requestV1) {
if (!requestV1) { return; }
var self = this,
mode = requestV1.dataMode,
noDefaults = this.options.noDefaults,
retainEmptyValues = this.options.retainEmptyValues;
if ((!mode || mode === 'binary') && !noDefaults) {
return retainEmptyValues ? [] : undefined;
}
if (!requestV1.data) { return; }
_.isArray(requestV1.data) && _.forEach(requestV1.data, function (datum) {
if (datum.type === 'file' && (_.has(datum, 'value') || !noDefaults)) {
datum.value = (_.isString(datum.value) || _.isArray(datum.value)) ? datum.value : null;
}
normalizeDescription(datum, self, self.options);
});
return requestV1.data;
},
/**
* Normalizes a list of header data from the incoming raw v1 request.
*
* @param {Object} requestV1 - The raw v1 request object.
* @returns {Object[]} - The normalized list of header datum values.
*/
headerData: function (requestV1) {
var self = this,
normalizedHeaderData;
if (!requestV1) { return; }
if (requestV1.headers && _.isEmpty(requestV1.headerData)) {
// this converts a newline concatenated string of headers to an array, so there are no descriptions
return v1Common.parseHeaders(requestV1.headers, true);
}
// however, if non empty headerData already exists, sanitize it.
normalizedHeaderData = _.map(requestV1.headerData, function (entity) {
return normalizeDescription(entity, self, self.options);
});
if (normalizedHeaderData.length) {
return normalizedHeaderData;
}
},
queryParams: function (requestV1) {
if (!requestV1) { return; }
var self = this,
normalizedQueryParams,
urlObj;
if (!requestV1.queryParams) {
return requestV1.url && (urlObj = url.parse(requestV1.url)) && urlObj.query;
}
normalizedQueryParams = _.map(requestV1.queryParams, function (entity) {
return normalizeDescription(entity, self, self.options);
});
if (normalizedQueryParams.length) {
return normalizedQueryParams;
}
},
/**
* Facilitates sanitized variable transformations across all levels for v1 collection normalization.
*
* @param {Object} entity - The wrapper object containing variable definitions.
* @param {?Object} [options] - The set of options for the current variable transformation.
* @param {?Object} [options.fallback] - The set of fallback values to be applied when no variables exist.
* @param {?Boolean} [options.noDefaults] - When set to true, no defaults are applied.
* @param {?Boolean} [options.retainEmptyValues] - When set to true, empty values are set to null instead of being
* removed.
* @param {?Boolean} [options.retainIds] - When set to true, ids are left as is.
* @returns {Object[]} - The set of sanitized variables.
*/
pathVariableData: function (entity, options) {
var self = this,
results = util.handleVars(entity, options, { isV1: true });
// Normalize descriptions that may have been passed in as objects
results = _.map(results, function (item) {
return normalizeDescription(item, self, self.options);
});
if (results.length) {
return results;
}
},
/**
* Normalizes a potentially raw v1 request object.
*
* @param {Object} requestV1 - The potentially raw v1 request object.
* @param {?String} collectionId - A unique identifier for the v1 collection.
* @param {?Boolean} [skipResponses=false] - When set to true, saved responses will be excluded from the result..
* @returns {Object} - The normalized v1 request object.
*/
request: function (requestV1, collectionId, skipResponses) {
if (!requestV1) { return; }
var map,
auth,
tests,
events,
mapper,
variables,
self = this,
helperAttributes,
preRequestScript,
options = this.options,
noDefaults = options.noDefaults,
retainEmpty = options.retainEmptyValues,
varOpts = { noDefaults: options.noDefaults, retainIds: options.retainIds },
units = ['queryParams', 'pathVariableData', 'headerData', 'data'];
if (!skipResponses) {
units.push('responses');
units.push('responses_order');
}
// if noDefaults is true, do not replace the id
// else
// if id is falsy, replace the id
// if retainIds is false, replace the id
!((options.retainIds && requestV1.id) || options.noDefaults) && (requestV1.id = util.uid());
normalizeDescription(requestV1, self, self.options);
units.forEach(function (unit) {
var result = self[unit](requestV1, self.options);
result && (requestV1[unit] = result);
});
if (requestV1.dataDisabled) { requestV1.dataDisabled = true; }
else if (retainEmpty) { requestV1.dataDisabled = false; }
else { delete requestV1.dataDisabled; }
// remove invalid protocolProfileBehavior property from requestV1
!util.addProtocolProfileBehavior(requestV1) && delete requestV1.protocolProfileBehavior;
collectionId && !noDefaults && (requestV1.collectionId = collectionId);
// normalized v1 requests should not have falsy helperAttributes or currentHelper
if (_.has(requestV1, 'currentHelper')) {
(requestV1.currentHelper === 'normal') && (requestV1.currentHelper = null);
if (!requestV1.currentHelper) {
(requestV1.currentHelper !== null) && (requestV1.currentHelper = null);
// @todo: Should currentHelper be recreated from helperAttributes.id if falsy?
requestV1.helperAttributes = null;
}
}
auth = self.auth(requestV1);
if (auth) {
requestV1.auth = auth;
if (_.has(requestV1, 'helperAttributes') && !requestV1.currentHelper) {
requestV1.currentHelper = authIdMap[auth.type];
}
}
else if (auth === null) { // eslint-disable-line security/detect-possible-timing-attacks
requestV1.auth = requestV1.currentHelper = requestV1.helperAttributes = null;
}
else { delete requestV1.auth; }
events = self.events(requestV1);
if (events || events === null) {
requestV1.events = events;
}
else {
delete requestV1.events;
}
variables = self.variables(requestV1, varOpts);
if (variables) {
requestV1.variables = variables;
}
else {
delete requestV1.variables;
}
if (requestV1.auth && (util.notLegacy(requestV1, 'auth') || options.prioritizeV2)) {
requestV1.currentHelper = v2Common.authMap[requestV1.auth.type];
(requestV1.currentHelper === null) && (requestV1.helperAttributes = null);
mapper = util.authMappersFromCurrent[requestV1.currentHelper];
if (mapper) {
(map = util.authArrayToMap(requestV1)) && (helperAttributes = mapper(map[requestV1.auth.type]));
helperAttributes && (requestV1.helperAttributes = helperAttributes);
}
}
if (requestV1.events && (util.notLegacy(requestV1, 'event') || options.prioritizeV2)) {
tests = preRequestScript = '';
_.forEach(requestV1.events, function (event) {
var exec = event && event.script && event.script.exec;
if (!_.isArray(exec)) { return; }
if (event.listen === 'prerequest') {
preRequestScript += exec.join('\n');
}
else if (event.listen === 'test') {
tests += exec.join('\n');
}
});
requestV1.preRequestScript = preRequestScript ? preRequestScript : null;
requestV1.tests = tests ? tests : null;
}
// prune
['preRequestScript', 'tests'].forEach(function (script) {
if (_.has(requestV1, script) && !requestV1[script] && requestV1[script] !== null) {
delete requestV1[script];
}
});
return requestV1;
},
/**
* Normalizes a potentially raw v1 response object.
*
* @param {Object} responseV1 - The potentially raw v1 response object.
* @returns {Object} - The normalized v1 response object.
*/
response: function (responseV1) {
var self = this;
// if noDefaults is true, do not replace the id
// else
// if id is falsy, replace the id
// if retainIds is false, replace the id
!((self.options.retainIds && responseV1.id) || self.options.noDefaults) && (responseV1.id = util.uid());
// the true in the next line ensures that we don't recursively go on processing responses in a request.
responseV1.request = self.request(responseV1.request, undefined, true);
!responseV1.language && (responseV1.language = 'Text');
!responseV1.previewType && (responseV1.previewType = 'html');
_.isEmpty(responseV1.cookies) && (delete responseV1.cookies);
return responseV1;
},
responses: function (requestV1) {
if (_.isEmpty(requestV1 && requestV1.responses)) { return; }
var self = this;
requestV1.responses.forEach(function (response) {
self.response(response);
});
return requestV1.responses;
},
/**
* Normalizes a request order list.
*
* @param {Object} entityV1 - An object containing a potentially raw list of folder ids.
* @returns {Array} - The normalized list of folder ids.
*/
order: function (entityV1) {
return !this.options.noDefaults && _.compact(entityV1 && entityV1.order);
},
/**
* Normalizes a folder order list.
*
* @param {Object} entityV1 - An object containing a potentially raw list of folder ids.
* @returns {Array} - The normalized list of folder ids.
*/
folders_order: function (entityV1) {
return !this.options.noDefaults && _.compact(entityV1 && entityV1.folders_order);
},
/**
* Normalizes a response order list.
*
* @param {Object} entityV1 - An object containing a potentially raw list of response ids.
* @returns {Array} - The normalized list of response ids.
*/
responses_order: function (entityV1) {
return !this.options.noDefaults && _.compact(entityV1 && entityV1.responses_order);
},
/**
* Normalizes a potentially raw v1 folders list.
*
* @param {Object} collectionV1 - The potentially raw v1 collection object.
* @returns {Object[]} - The normalized v1 collection folders list.
*/
folders: function (collectionV1) {
if (_.isEmpty(collectionV1 && collectionV1.folders)) { return; }
var auth,
events,
variables,
self = this,
order,
foldersOrder,
retainEmpty = self.options.retainEmptyValues,
varOpts = { noDefaults: self.options.noDefaults, retainIds: self.options.retainIds };
_.forEach(collectionV1.folders, function (folder) {
if (!folder) { return; }
// if noDefaults is true, do not replace the id
// else
// if id is falsy, replace the id
// if retainIds is false, replace the id
!((self.options.retainIds && folder.id) || self.options.noDefaults) && (folder.id = util.uid());
folder.description = self.description(folder.description);
util.cleanEmptyValue(folder, 'description', retainEmpty);
// remove invalid protocolProfileBehavior property
!util.addProtocolProfileBehavior(folder) && delete folder.protocolProfileBehavior;
auth = self.auth(folder);
!_.isEmpty((order = self.order(folder))) && (folder.order = order);
!_.isEmpty((foldersOrder = self.folders_order(folder))) && (folder.folders_order = foldersOrder);
(auth || (auth === null)) && (folder.auth = auth);
(events = self.events(folder)) && (folder.events = events);
(variables = self.variables(folder, varOpts)) && (folder.variables = variables);
});
return _.compact(collectionV1.folders);
},
/**
* Normalizes a potentially raw v1 request object.
*
* @param {Object} collectionV1 - The potentially raw v1 collection object.
* @returns {Object[]|*} - The normalized v1 request list.
*/
requests: function (collectionV1) {
if (_.isEmpty(collectionV1 && collectionV1.requests)) { return; }
var self = this;
collectionV1.requests.forEach(function (request) {
self.request(request);
});
return _.compact(collectionV1.requests);
},
/**
* Creates the v1.0.0 compatible description string.
*
* @param {Object} maybeObjectDescription - The description to be converted
*
* @returns {String} - The resultant v1 description.
*/
description: function (maybeObjectDescription) {
var description,
retainEmpty = _.get(this.options, 'retainEmptyValues'),
createDefaultValue = !_.get(this.options, 'noDefaults', false);
if (_.isObject(maybeObjectDescription)) {
description = _.toString(_.get(maybeObjectDescription, 'content'));
}
else {
description = maybeObjectDescription;
}
if (description) {
return description;
}
else if (description === undefined && createDefaultValue) {
return null;
}
else if (_.isEmpty(description) && retainEmpty) {
return null;
}
return undefined;
}
});
module.exports = {
/**
* Normalizes a single v1 request.
*
* @param {Object} request - The v1 request to be normalized.
* @param {Object} options - The set of options for the current normalization.
* @param {?Boolean} [options.mutate=false] - When set to true, normalization is done in place.
* @param {?Boolean} [options.noDefaults=false] - When set to true, sensible defaults are not added.
* @param {?Boolean} [options.prioritizeV2=false] - When set to true, v2 style properties are checked first.
* @param {?Boolean} [options.retainEmptyValues=false] - When set to true, empty values are set to '' instead of
* being removed.
* @param {Function} callback - A function that is invoked when the normalization has completed.
* @returns {*}
*/
normalizeSingle: function (request, options, callback) {
var err,
normalized,
builders = new Builders(options);
// At this stage, mutate will not be passed ordinarily. Hence, the falsy nature of options.mutate can be used
// to selectively clone the request.
options && !options.mutate && (request = _.cloneDeep(request));
try { normalized = builders.request(request); }
catch (e) { err = e; }
if (callback) { return callback(err, normalized); }
if (err) { throw err; }
return normalized;
},
/**
* Normalizes a single v1 response.
*
* @param {Object} response - The v1 request to be normalized.
* @param {Object} options - The set of options for the current normalization.
* @param {?Boolean} [options.mutate=false] - When set to true, normalization is done in place.
* @param {?Boolean} [options.noDefaults=false] - When set to true, sensible defaults are not added.
* @param {?Boolean} [options.prioritizeV2=false] - When set to true, v2 style properties are checked first.
* @param {?Boolean} [options.retainEmptyValues=false] - When set to true, empty values are set to '' instead of
* being removed.
* @param {Function} callback - A function that is invoked when the normalization has completed.
* @returns {*}
*/
normalizeResponse: function (response, options, callback) {
var err,
normalized,
builders = new Builders(options);
// At this stage, mutate will not be passed ordinarily. Hence, the falsy nature of options.mutate can be used
// to selectively clone the response.
options && !options.mutate && (response = _.cloneDeep(response));
try { normalized = builders.response(response); }
catch (e) { err = e; }
if (callback) { return callback(err, normalized); }
if (err) { throw err; }
return normalized;
},
/**
* Converts a V1 collection to a V2 collection (performs ID replacement, etc as necessary).
*
* @param {Object} collection - The v1 collection to be normalized.
* @param {Object} options - The options for the current normalization sequence.
* @param {?Boolean} [options.mutate=false] - When set to true, normalization is done in place.
* @param {?Boolean} [options.noDefaults=false] - When set to true, sensible defaults are not added.
* @param {?Boolean} [options.prioritizeV2=false] - When set to true, v2 style properties are checked first.
* @param {?Boolean} [options.retainEmptyValues=false] - When set to true, empty values are set to '' instead of
* being removed.
* @param {Function} callback - A function invoked to indicate that the normalization has completed.
* @returns {*}
*/
normalize: function (collection, options, callback) {
// At this stage, mutate will not be passed ordinarily. Hence, the falsy nature of options.mutate can be used
// to selectively clone the collection.
options && !options.mutate && (collection = _.cloneDeep(collection));
var auth,
authOptions = { excludeNoauth: true },
builders = new Builders(options),
units = ['events', 'variables', 'order', 'folders_order', 'folders', 'requests'];
// if noDefaults is true, do not replace the id
// else
// if id is falsy, replace the id
// if retainIds is false, replace the id
!((options.retainIds && collection.id) || options.noDefaults) && (collection.id = util.uid());
normalizeDescription(collection, builders, options);
// remove invalid protocolProfileBehavior property
!util.addProtocolProfileBehavior(collection) && delete collection.protocolProfileBehavior;
try {
auth = builders.auth(collection, authOptions);
if (auth || (options.retainEmptyValues && auth === null)) {
collection.auth = auth;
}
else {
delete collection.auth;
}
units.forEach(function (unit) {
var result,
_options;
if (unit === 'variables') {
_options = { retainIds: options.retainIds, noDefaults: options.noDefaults };
}
if (!_.isEmpty(result = builders[unit](collection, _options)) || (unit === 'folders')) {
collection[unit] = result;
}
});
}
catch (e) {
if (callback) { return callback(e, null); }
throw e;
}
if (callback) { return callback(null, collection); }
return collection;
}
};

663
node_modules/postman-collection-transformer/lib/url.js generated vendored Normal file
View File

@@ -0,0 +1,663 @@
/**
* @fileoverview
*
* Copied the URL parser and unparser from the SDK.
* @todo Move this into it's own separate module, and make the SDK and transformer depend on it.
*/
const _ = require('lodash').noConflict(),
E = '',
HASH = '#',
SLASH = '/',
COLON = ':',
EQUALS = '=',
AMPERSAND = '&',
AUTH_SEPARATOR = '@',
QUERY_SEPARATOR = '?',
DOMAIN_SEPARATOR = '.',
PROTOCOL_SEPARATOR = '://',
PROTOCOL_SEPARATOR_WITH_BACKSLASH = ':\\\\',
STRING = 'string',
FUNCTION = 'function',
SAFE_REPLACE_CHAR = '_',
CLOSING_SQUARE_BRACKET = ']',
URL_PROPERTIES_ORDER = ['protocol', 'auth', 'host', 'port', 'path', 'query', 'hash'],
REGEX_HASH = /#/g,
REGEX_EQUALS = /=/g, // eslint-disable-line no-div-regex
REGEX_AMPERSAND = /&/g,
REGEX_EXTRACT_VARS = /{{[^{}]*[.:/?#@&\]][^{}]*}}/g,
REGEX_EXTRACT_VARS_IN_PARAM = /{{[^{}]*[&#=][^{}]*}}/g,
/**
* Percent encode reserved chars (&, = and #) in the given string.
*
* @private
* @param {String} str - String to encode
* @param {Boolean} encodeEquals - Encode '=' if true
* @returns {String} - Encoded string
*/
encodeReservedChars = function (str, encodeEquals) {
if (!str) {
return str;
}
// eslint-disable-next-line lodash/prefer-includes
str.indexOf(AMPERSAND) !== -1 && (str = str.replace(REGEX_AMPERSAND, '%26'));
// eslint-disable-next-line lodash/prefer-includes
str.indexOf(HASH) !== -1 && (str = str.replace(REGEX_HASH, '%23'));
// eslint-disable-next-line lodash/prefer-includes
encodeEquals && str.indexOf(EQUALS) !== -1 && (str = str.replace(REGEX_EQUALS, '%3D'));
return str;
},
/**
* Normalize the given param string by percent-encoding the reserved chars
* such that it won't affect the re-parsing.
*
* @note `&`, `=` and `#` needs to be percent-encoded otherwise re-parsing
* the same URL string will generate different output
*
* @private
* @param {String} str - Parameter string to normalize
* @param {Boolean} encodeEquals - If true, encode '=' while normalizing
* @returns {String} - Normalized param string
*/
normalizeParam = function (str, encodeEquals) {
// bail out if the given sting is null or empty
if (!(str && typeof str === STRING)) {
return str;
}
// bail out if the given string does not include reserved chars
// eslint-disable-next-line lodash/prefer-includes
if (str.indexOf(AMPERSAND) === -1 && str.indexOf(HASH) === -1) {
// eslint-disable-next-line lodash/prefer-includes
if (!(encodeEquals && str.indexOf(EQUALS) !== -1)) {
return str;
}
}
var normalizedString = '',
pointer = 0,
variable,
match,
index;
// find all the instances of {{<variable>}} which includes reserved chars
while ((match = REGEX_EXTRACT_VARS_IN_PARAM.exec(str)) !== null) {
variable = match[0];
index = match.index;
// [pointer, index) string is normalized + the matched variable
normalizedString += encodeReservedChars(str.slice(pointer, index), encodeEquals) + variable;
// update the pointer
pointer = index + variable.length;
}
// whatever left in the string is normalized as well
if (pointer < str.length) {
normalizedString += encodeReservedChars(str.slice(pointer), encodeEquals);
}
return normalizedString;
},
/**
* Unparses a single query param into a string.
*
* @private
* @param {Object} obj - The query parameter object to be unparsed.
* @returns {String} - The unparsed query string.
*/
unparseQueryParam = function (obj) {
if (!obj) { return E; }
var key = obj.key,
value = obj.value,
result;
if (typeof key === STRING) {
result = normalizeParam(key, true);
}
else {
result = E;
}
if (typeof value === STRING) {
result += EQUALS + normalizeParam(value);
}
return result;
},
/**
* Parses a single query param string into an object.
*
* @private
* @param {String} param - The query parameter string to be parsed.
* @returns {Object} - The parsed query object.
*/
parseQueryParam = function (param) {
if (param === E) {
return { key: null, value: null };
}
var index = _.indexOf(param, EQUALS);
if (index < 0) {
return { key: param, value: null };
}
return {
key: param.substr(0, index),
value: param.substr(index + 1)
};
};
/**
* Tracks replacements done on a string and expose utility to patch replacements.
*
* @note due to performance reasons, it doesn't store the original string or
* perform ops on the string.
*
* @private
* @constructor
*/
function ReplacementTracker () {
this.replacements = [];
this._offset = 0;
this._length = 0;
}
/**
* Add new replacement to track.
*
* @param {String} value - value being replaced
* @param {Number} index - index of replacement
*/
ReplacementTracker.prototype.add = function (value, index) {
this.replacements.push({
value: value,
index: index - this._offset
});
this._offset += value.length - 1; // - 1 replaced character
this._length++;
};
/**
* Returns the total number of replacements.
*
* @returns {Number}
*/
ReplacementTracker.prototype.count = function () {
return this._length;
};
/**
* Finds the lower index of replacement position for a given value using inexact
* binary search.
*
* @param {Number} index - index to search in replacements
* @returns {Number}
*/
ReplacementTracker.prototype._findLowerIndex = function (index) {
var length = this.count(),
start = 0,
end = length - 1,
mid;
while (start <= end) {
mid = (start + end) >> 1; // divide by 2
if (this.replacements[mid].index >= index) {
end = mid - 1;
}
else {
start = mid + 1;
}
}
return start >= length ? -1 : start;
};
/**
* Patches a given string by apply all the applicable replacements done in the
* given range.
*
* @param {String} input - string to apply replacements on
* @param {Number} beginIndex - index from where to apply replacements in input
* @param {Number} endIndex - index until where to apply replacements in input
* @returns {String}
*/
ReplacementTracker.prototype._applyInString = function (input, beginIndex, endIndex) {
var index,
replacement,
replacementIndex,
replacementValue,
offset = 0,
length = this.count();
// bail out if no replacements are done in the given range OR empty string
if (!input || (index = this._findLowerIndex(beginIndex)) === -1) {
return input;
}
do {
replacement = this.replacements[index];
replacementIndex = replacement.index;
replacementValue = replacement.value;
// bail out if all the replacements are done in the given range
if (replacementIndex >= endIndex) {
break;
}
replacementIndex = offset + replacementIndex - beginIndex;
input = input.slice(0, replacementIndex) + replacementValue + input.slice(replacementIndex + 1);
offset += replacementValue.length - 1;
} while (++index < length);
return input;
};
/**
* Patches a given string or array of strings by apply all the applicable
* replacements done in the given range.
*
* @param {String|String[]} input - string or splitted string to apply replacements on
* @param {Number} beginIndex - index from where to apply replacements in input
* @param {Number} endIndex - index until where to apply replacements in input
* @returns {String|String[]}
*/
ReplacementTracker.prototype.apply = function (input, beginIndex, endIndex) {
var i,
ii,
length,
_endIndex,
_beginIndex,
value = input;
// apply replacements in string
if (typeof input === STRING) {
return this._applyInString(input, beginIndex, endIndex);
}
// apply replacements in the splitted string (Array)
_beginIndex = beginIndex;
// traverse all the segments until all the replacements are patched
for (i = 0, ii = input.length; i < ii; ++i) {
value = input[i];
_endIndex = _beginIndex + (length = value.length);
// apply replacements applicable for individual segment
input[i] = this._applyInString(value, _beginIndex, _endIndex);
_beginIndex += length + 1; // + 1 separator
}
return input;
};
/**
* Normalize the given string by replacing the variables which includes
* reserved characters in its name.
* The replaced characters are added to the given replacement tracker instance.
*
* @private
* @param {String} str - string to normalize
* @param {ReplacementTracker} replacements - tracker to store replacements
* @returns {String}
*/
function normalizeVariables (str, replacements) {
var normalizedString = E,
pointer = 0, // pointer till witch the string is normalized
variable,
match,
index;
// find all the instances of {{<variable>}} which includes reserved chars
// "Hello {{user#name}}!!!"
// ↑ (pointer = 0)
while ((match = REGEX_EXTRACT_VARS.exec(str)) !== null) {
// {{user#name}}
variable = match[0];
// starting index of the {{variable}} in the string
// "Hello {{user#name}}!!!"
// ↑ (index = 6)
index = match.index;
// [pointer, index) string is normalized + the safe replacement character
// "Hello " + "_"
normalizedString += str.slice(pointer, index) + SAFE_REPLACE_CHAR;
// track the replacement done for the {{variable}}
replacements.add(variable, index);
// update the pointer
// "Hello {{user#name}}!!!"
// ↑ (pointer = 19)
pointer = index + variable.length;
}
// avoid slicing the string in case of no matches
if (pointer === 0) {
return str;
}
// whatever left in the string is normalized as well
if (pointer < str.length) {
// "Hello _" + "!!!"
normalizedString += str.slice(pointer);
}
return normalizedString;
}
/**
* Update replaced characters in the URL object with its original value.
*
* @private
* @param {Object} url - url to apply replacements on
* @param {ReplacementTracker} replacements - tracked replacements
*/
function applyReplacements (url, replacements) {
var i,
ii,
prop;
// traverse each URL property in the given order
for (i = 0, ii = URL_PROPERTIES_ORDER.length; i < ii; ++i) {
prop = url[URL_PROPERTIES_ORDER[i]];
// bail out if the given property is not set (undefined or E)
if (!(prop && prop.value)) {
continue;
}
prop.value = replacements.apply(prop.value, prop.beginIndex, prop.endIndex);
}
return url;
}
/**
* Parses the input string by decomposing the URL into constituent parts,
* such as path, host, port, etc.
*
* @private
* @param {String} urlString - url string to parse
* @returns {Object}
*/
function url_parse (urlString) {
// trim leading whitespace characters
urlString = String(urlString).trimLeft();
var url = {
protocol: { value: undefined, beginIndex: 0, endIndex: 0 },
auth: { value: undefined, beginIndex: 0, endIndex: 0 },
host: { value: undefined, beginIndex: 0, endIndex: 0 },
port: { value: undefined, beginIndex: 0, endIndex: 0 },
path: { value: undefined, beginIndex: 0, endIndex: 0 },
query: { value: undefined, beginIndex: 0, endIndex: 0 },
hash: { value: undefined, beginIndex: 0, endIndex: 0 }
},
parsedUrl = {
raw: urlString,
protocol: undefined,
auth: undefined,
host: undefined,
port: undefined,
path: undefined,
query: undefined,
hash: undefined
},
replacements = new ReplacementTracker(),
pointer = 0,
length,
index,
port;
// bail out if input string is empty
if (!urlString) {
return parsedUrl;
}
// normalize the given string
urlString = normalizeVariables(urlString, replacements);
length = urlString.length;
// 1. url.hash
if ((index = urlString.indexOf(HASH)) !== -1) {
// extract from the back
url.hash.value = urlString.slice(index + 1);
url.hash.beginIndex = pointer + index + 1;
url.hash.endIndex = pointer + length;
urlString = urlString.slice(0, (length = index));
}
// 2. url.query
if ((index = urlString.indexOf(QUERY_SEPARATOR)) !== -1) {
// extract from the back
url.query.value = urlString.slice(index + 1).split(AMPERSAND);
url.query.beginIndex = pointer + index + 1;
url.query.endIndex = pointer + length;
urlString = urlString.slice(0, (length = index));
}
// 3. url.protocol
if ((index = urlString.indexOf(PROTOCOL_SEPARATOR)) !== -1) {
// extract from the front
url.protocol.value = urlString.slice(0, index);
url.protocol.beginIndex = pointer;
url.protocol.endIndex = pointer + index;
urlString = urlString.slice(index + 3);
length -= index + 3;
pointer += index + 3;
}
// protocol can be separated using :\\ as well
else if ((index = urlString.indexOf(PROTOCOL_SEPARATOR_WITH_BACKSLASH)) !== -1) {
// extract from the front
url.protocol.value = urlString.slice(0, index);
url.protocol.beginIndex = pointer;
url.protocol.endIndex = pointer + index;
urlString = urlString.slice(index + 3);
length -= index + 3;
pointer += index + 3;
}
// 4. url.path
urlString = urlString.replace(/\\/g, '/'); // sanitize path
if ((index = urlString.indexOf(SLASH)) !== -1) {
// extract from the back
url.path.value = urlString.slice(index + 1).split(SLASH);
url.path.beginIndex = pointer + index + 1;
url.path.endIndex = pointer + length;
urlString = urlString.slice(0, (length = index));
}
// 5. url.auth
if ((index = urlString.lastIndexOf(AUTH_SEPARATOR)) !== -1) {
// extract from the front
url.auth.value = urlString.slice(0, index);
url.auth.beginIndex = pointer;
url.auth.endIndex = pointer + index;
urlString = urlString.slice(index + 1);
length -= index + 1;
pointer += index + 1;
// separate username:password
if ((index = url.auth.value.indexOf(COLON)) === -1) {
url.auth.value = [url.auth.value];
}
else {
url.auth.value = [url.auth.value.slice(0, index), url.auth.value.slice(index + 1)];
}
}
// 6. url.port
if ((index = urlString.lastIndexOf(COLON)) !== -1 &&
// eslint-disable-next-line lodash/prefer-includes
(port = urlString.slice(index + 1)).indexOf(CLOSING_SQUARE_BRACKET) === -1
) {
// extract from the back
url.port.value = port;
url.port.beginIndex = pointer + index + 1;
url.port.endIndex = pointer + length;
urlString = urlString.slice(0, (length = index));
}
// 7. url.host
if (urlString) {
url.host.value = urlString.split(DOMAIN_SEPARATOR);
url.host.beginIndex = pointer;
url.host.endIndex = pointer + length;
}
// apply replacements back, if any
replacements.count() && applyReplacements(url, replacements);
// finally, prepare parsed url
parsedUrl.protocol = url.protocol.value;
parsedUrl.auth = url.auth.value;
parsedUrl.host = url.host.value;
parsedUrl.port = url.port.value;
parsedUrl.path = url.path.value;
parsedUrl.query = url.query.value;
parsedUrl.hash = url.hash.value;
return parsedUrl;
}
/* eslint-disable object-shorthand */
module.exports = {
parse: function (url) {
if (typeof url !== STRING) {
url = '';
}
url = url_parse(url);
var pathVariables,
pathVariableKeys = {};
if (url.auth) {
url.auth = {
user: url.auth[0],
password: url.auth[1]
};
}
if (url.query) {
url.query = url.query.map(parseQueryParam);
}
// extract path variables
pathVariables = _.transform(url.path, function (res, segment) {
// check if the segment has path variable prefix followed by the variable name and
// the variable is not already added in the list.
if (_.startsWith(segment, COLON) &&
segment !== COLON &&
!pathVariableKeys[segment]) {
pathVariableKeys[segment] = true;
res.push({ key: segment.slice(1) }); // remove path variable prefix.
}
}, []);
url.variable = pathVariables.length ? pathVariables : undefined;
return url;
},
unparse: function (urlObj) {
var rawUrl = E,
path,
queryString,
authString,
firstEnabledParam = true;
if (urlObj.protocol) {
rawUrl += (_.endsWith(urlObj.protocol, PROTOCOL_SEPARATOR) ?
urlObj.protocol : urlObj.protocol + PROTOCOL_SEPARATOR);
}
if (urlObj.auth) {
if (typeof urlObj.auth.user === STRING) {
authString = urlObj.auth.user;
}
if (typeof urlObj.auth.password === STRING) {
!authString && (authString = E);
authString += COLON + urlObj.auth.password;
}
if (typeof authString === STRING) {
rawUrl += authString + AUTH_SEPARATOR;
}
}
if (urlObj.host) {
rawUrl += (_.isArray(urlObj.host) ? urlObj.host.join(DOMAIN_SEPARATOR) : urlObj.host.toString());
}
if (typeof _.get(urlObj.port, 'toString') === FUNCTION) {
rawUrl += COLON + urlObj.port.toString();
}
if (urlObj.path) {
path = (_.isArray(urlObj.path) ? urlObj.path.join(SLASH) : urlObj.path.toString());
rawUrl += (_.startsWith(path, SLASH) ? path : SLASH + path);
}
if (urlObj.query && urlObj.query.length) {
queryString = _.reduce(urlObj.query, function (accumulator, param) {
// ignore disabled params
if (!param || param.disabled) {
return accumulator;
}
// don't add '&' for the very first enabled param
if (firstEnabledParam) {
firstEnabledParam = false;
}
// add '&' before concatenating param
else {
accumulator += AMPERSAND;
}
return accumulator + unparseQueryParam(param);
}, E);
// either all the params are disabled or a single param is like { key: '' } (http://localhost?)
// in that case, query separator ? must be included in the raw URL.
if (queryString === E && firstEnabledParam) {
// unset querystring if there are no enabled params
queryString = undefined;
}
if (typeof queryString === STRING) {
rawUrl += QUERY_SEPARATOR + queryString;
}
}
if (typeof urlObj.hash === STRING) {
rawUrl += HASH + urlObj.hash;
}
return rawUrl;
}
};

464
node_modules/postman-collection-transformer/lib/util.js generated vendored Normal file
View File

@@ -0,0 +1,464 @@
/* eslint-disable object-shorthand */
var _ = require('lodash'),
url = require('./url'),
rnd = Math.random;
module.exports = {
// @todo: Add support for a `json` type once it becomes available
typeMap: {
string: 'string',
boolean: 'boolean',
number: 'number'
},
/**
* Returns unique GUID on every call as per pseudo-number RFC4122 standards.
*
* @type {function}
* @returns {string}
*/
uid: function () {
var n,
r,
E = '',
H = '-'; // r = result , n = numeric variable for positional checks
// if "n" is not 9 or 14 or 19 or 24 return a random number or 4
// if "n" is not 15 generate a random number from 0 to 15
// `(n ^ 20 ? 16 : 4)` := unless "n" is 20, in which case a random number from 8 to 11 otherwise 4
//
// in other cases (if "n" is 9,14,19,24) insert "-"
// eslint-disable-next-line curly
for (r = n = E; n++ < 36; r += n * 51 & 52 ? (n ^ 15 ? 8 ^ rnd() * (n ^ 20 ? 16 : 4) : 4).toString(16) : H);
return r;
},
urlparse: function (u) {
return url.parse(u);
},
urlunparse: function (urlObj) {
return url.unparse(urlObj);
},
/**
* A generic utility method to sanitize variable transformations across collection formats.
*
* @param {Object} entity - A generic object that could contain variable data.
* @param {?Object} options - The set of options for variable handling.
* @param {?Object} options.fallback - The fallback values to be used if no variables are present.
* @param {?Boolean} options.noDefaults - When set to true, id will be retained.
* @param {?Boolean} options.retainEmptyValues - When set to true, empty property values will be set to ''
* @param {?Boolean} options.retainIds - When set to true, ids are left as is.
* instead of being deleted.
* @param {Object} [modifiers] - A set of behavioral modifiers for variable handling.
* @param {Boolean} [modifiers.isV1=false] - When set to true, looks for the pathVariableData property as well.
* @returns {Object[]} - The set of sanitized entity level variables.
*/
handleVars: function (entity, options, modifiers) {
!options && (options = {});
var self = this,
retainIds = options.retainIds,
noDefaults = options.noDefaults,
isV1 = modifiers && modifiers.isV1,
retainEmpty = options.retainEmptyValues,
source = entity && (entity.variables || entity.variable || (isV1 && entity.pathVariableData)),
fallback = options.fallback && options.fallback.values,
result = _.map(source || fallback, function (item) {
var result = {};
// add id only when retainIds is set
if (retainIds) {
// retain id only when `id` field is present
if (_.has(item, 'id')) {
result.id = item.id;
}
// or, create a new id if noDefaults is false
else if (!noDefaults) {
result.id = self.uid();
}
}
result.key = item.key || item.id;
result.value = item.value;
item.type && (result.type = item.type === 'text' ? 'string' : item.type);
item.disabled && (result.disabled = true);
if (item.description) { result.description = item.description; }
else if (retainEmpty) { result.description = null; }
return result;
});
if (result.length) { return result; }
},
/**
* Performs auth cleansing common to all sorts of auth transformations.
*
* @param {Object} entity - The wrapped auth entity to be cleaned.
* @param {?Object} options - The set of options for the current auth cleansing operation.
* @param {?Boolean} [options.excludeNoauth=false] - When set to true, noauth is set to null.
* @returns {Object|*} - The processed auth data.
*/
cleanAuth: function (entity, options) {
!options && (options = {});
var auth = entity && entity.auth;
if (auth === null) { return null; } // eslint-disable-line security/detect-possible-timing-attacks
if (!(auth && auth.type)) { return; }
if (auth.type === 'noauth') {
return options.excludeNoauth ? null : { type: 'noauth' };
}
return auth;
},
cleanEmptyValue: function (entity, property, retainEmpty) {
if (_.has(entity, property) && !entity[property]) {
retainEmpty ? (entity[property] = null) : (delete entity[property]);
}
return entity;
},
/**
* Transforms an array of auth params to their object equivalent.
*
* @param {Object} entity - The wrapper object for the array of auth params.
* @param {?Object} options - The set of options for the current auth cleansing operation.
* @param {?Boolean} [options.excludeNoauth=false] - When set to true, noauth is set to null.
* @returns {*}
*/
authArrayToMap: function (entity, options) {
var type,
result,
self = this,
auth = self.cleanAuth(entity, options);
if (!auth) { return auth; }
result = { type: (type = auth.type) };
if (type !== 'noauth') {
result[type] = _.transform(auth[type], function (result, param) {
result[param.key] = param.value;
}, {});
}
return result;
},
/**
* Transforms an object of auth params to their array equivalent.
*
* @param {Object} entity - The wrapper object for the array of auth params.
* @param {?Object} options - The set of options for the current auth cleansing operation.
* @param {?Boolean} [options.excludeNoauth=false] - When set to true, noauth is set to null.
* @returns {*}
*/
authMapToArray: function (entity, options) {
var type,
params,
result,
self = this,
auth = self.cleanAuth(entity, options);
if (!auth) { return auth; }
result = { type: (type = auth.type) };
if (type !== 'noauth') {
// @todo: Handle all non _ prefixed properties, ala request bodies
params = _.map(auth[type], function (value, key) {
return {
key: key,
value: value,
type: self.typeMap[typeof value] || 'any'
};
});
params.length && (result[type] = params);
}
return result;
},
/**
* Sanitizes a collection SDK compliant auth list.
*
* @param {Object} entity - The wrapper entity for the auth manifest.
* @param {?Object} options - The set of options for the current auth cleansing operation.
* @param {?Boolean} [options.excludeNoauth=false] - When set to true, noauth is set to null.
* @returns {Object[]} - An array of raw collection SDK compliant auth parameters.
*/
sanitizeAuthArray: function (entity, options) {
var type,
result,
self = this,
auth = self.cleanAuth(entity, options);
if (!auth) { return auth; }
result = { type: (type = auth.type) };
if (type !== 'noauth') {
result[type] = _.map(auth[type], function (param) {
return {
key: param.key,
value: param.value,
type: (param.type === 'text' ? 'string' : param.type) || self.typeMap[typeof param.value] || 'any'
};
});
}
return result;
},
/**
* A helper function to determine if the provided v1 entity has legacy properties.
*
* @private
* @param {Object} entityV1 - The v1 entity to be checked for the presence of legacy properties.
* @param {String} type - The type of property to be adjudged against.
* @returns {Boolean|*} - A flag to indicate the legacy property status of the passed v1 entity.
*/
notLegacy: function (entityV1, type) {
if (!entityV1) { return; }
switch (type) {
case 'event':
return !(entityV1.tests || entityV1.preRequestScript);
case 'auth':
return _.has(entityV1, 'auth') && !(_.has(entityV1, 'currentHelper') || entityV1.helperAttributes);
default:
return true;
}
},
authMappersFromLegacy: {
apikeyAuth: function (oldParams) {
return oldParams && {
key: oldParams.key,
value: oldParams.value,
in: oldParams.in
};
},
basicAuth: function (oldParams) {
return oldParams && {
username: oldParams.username,
password: oldParams.password,
saveHelperData: oldParams.saveToRequest,
showPassword: false
};
},
bearerAuth: function (oldParams) {
return oldParams && {
token: oldParams.token
};
},
digestAuth: function (oldParams) {
return oldParams && {
algorithm: oldParams.algorithm,
username: oldParams.username,
realm: oldParams.realm,
password: oldParams.password,
nonce: oldParams.nonce,
nonceCount: oldParams.nonceCount,
clientNonce: oldParams.clientNonce,
opaque: oldParams.opaque,
qop: oldParams.qop,
disableRetryRequest: oldParams.disableRetryRequest
};
},
oAuth1: function (oldParams) {
return oldParams && {
consumerKey: oldParams.consumerKey,
consumerSecret: oldParams.consumerSecret,
token: oldParams.token,
tokenSecret: oldParams.tokenSecret,
signatureMethod: oldParams.signatureMethod,
timestamp: oldParams.timestamp,
nonce: oldParams.nonce,
version: oldParams.version,
realm: oldParams.realm,
addParamsToHeader: oldParams.header,
autoAddParam: oldParams.auto,
addEmptyParamsToSign: oldParams.includeEmpty
};
},
hawkAuth: function (oldParams) {
return oldParams && {
authId: oldParams.hawk_id,
authKey: oldParams.hawk_key,
algorithm: oldParams.algorithm,
user: oldParams.user,
saveHelperData: oldParams.saveToRequest,
nonce: oldParams.nonce,
extraData: oldParams.ext,
appId: oldParams.app,
delegation: oldParams.dlg,
timestamp: oldParams.timestamp
};
},
ntlmAuth: function (oldParams) {
return oldParams && {
username: oldParams.username,
password: oldParams.password,
domain: oldParams.domain,
workstation: oldParams.workstation,
disableRetryRequest: oldParams.disableRetryRequest
};
},
oAuth2: function (oldParams) {
return oldParams && {
accessToken: oldParams.accessToken,
addTokenTo: oldParams.addTokenTo,
callBackUrl: oldParams.callBackUrl,
authUrl: oldParams.authUrl,
accessTokenUrl: oldParams.accessTokenUrl,
clientId: oldParams.clientId,
clientSecret: oldParams.clientSecret,
clientAuth: oldParams.clientAuth,
grantType: oldParams.grantType,
scope: oldParams.scope,
username: oldParams.username,
password: oldParams.password,
tokenType: oldParams.tokenType,
redirectUri: oldParams.redirectUri,
refreshToken: oldParams.refreshToken
};
},
// Only exists for consistency
awsSigV4: function (oldParams) {
return oldParams;
}
},
authMappersFromCurrent: {
apikeyAuth: function (newParams) {
return newParams && {
id: 'apikey',
key: newParams.key,
value: newParams.value,
in: newParams.in
};
},
basicAuth: function (newParams) {
return newParams && {
id: 'basic',
username: newParams.username,
password: newParams.password,
saveToRequest: newParams.saveHelperData
};
},
bearerAuth: function (newParams) {
return newParams && {
id: 'bearer',
token: newParams.token
};
},
digestAuth: function (newParams) {
return newParams && {
id: 'digest',
algorithm: newParams.algorithm,
username: newParams.username,
realm: newParams.realm,
password: newParams.password,
nonce: newParams.nonce,
nonceCount: newParams.nonceCount,
clientNonce: newParams.clientNonce,
opaque: newParams.opaque,
qop: newParams.qop,
disableRetryRequest: newParams.disableRetryRequest
};
},
oAuth1: function (newParams) {
return newParams && {
id: 'oAuth1',
consumerKey: newParams.consumerKey,
consumerSecret: newParams.consumerSecret,
token: newParams.token,
tokenSecret: newParams.tokenSecret,
signatureMethod: newParams.signatureMethod,
timestamp: newParams.timeStamp || newParams.timestamp,
nonce: newParams.nonce,
version: newParams.version,
realm: newParams.realm,
header: newParams.addParamsToHeader,
auto: newParams.autoAddParam,
includeEmpty: newParams.addEmptyParamsToSign
};
},
hawkAuth: function (newParams) {
return newParams && {
id: 'hawk',
hawk_id: newParams.authId,
hawk_key: newParams.authKey,
algorithm: newParams.algorithm,
user: newParams.user,
saveToRequest: newParams.saveHelperData,
nonce: newParams.nonce,
ext: newParams.extraData,
app: newParams.appId,
dlg: newParams.delegation,
timestamp: newParams.timestamp
};
},
ntlmAuth: function (newParams) {
return newParams && {
id: 'ntlm',
username: newParams.username,
password: newParams.password,
domain: newParams.domain,
workstation: newParams.workstation,
disableRetryRequest: newParams.disableRetryRequest
};
},
oAuth2: function (newParams) {
return newParams && {
id: 'oAuth2',
accessToken: newParams.accessToken,
addTokenTo: newParams.addTokenTo,
callBackUrl: newParams.callBackUrl,
authUrl: newParams.authUrl,
accessTokenUrl: newParams.accessTokenUrl,
clientId: newParams.clientId,
clientSecret: newParams.clientSecret,
clientAuth: newParams.clientAuth,
grantType: newParams.grantType,
scope: newParams.scope,
username: newParams.username,
password: newParams.password,
tokenType: newParams.tokenType,
redirectUri: newParams.redirectUri,
refreshToken: newParams.refreshToken
};
},
// Only exists for consistency
awsSigV4: function (newParams) {
return newParams;
}
},
/**
* Validate protocolProfileBehavior property's value.
*
* @param {Object} source - A generic object that could contain the protocolProfileBehavior property.
* @param {?Object} destination - The destination object that needs the addition of protocolProfileBehavior.
* @returns {Boolean} - A Boolean value to decide whether to include the property or not.
*/
addProtocolProfileBehavior: function (source, destination) {
var behavior = source && source.protocolProfileBehavior;
// make sure it's a non-empty plain object
if (!(_.isPlainObject(behavior) && !_.isEmpty(behavior))) { return false; }
destination && (destination.protocolProfileBehavior = behavior);
return true;
}
};

View File

@@ -0,0 +1,22 @@
(The MIT License)
Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
import commander from './index.js';
// wrapper to provide named exports for ESM.
export const {
program,
createCommand,
createArgument,
createOption,
CommanderError,
InvalidArgumentError,
Command,
Argument,
Option,
Help
} = commander;

View File

@@ -0,0 +1,27 @@
const { Argument } = require('./lib/argument.js');
const { Command } = require('./lib/command.js');
const { CommanderError, InvalidArgumentError } = require('./lib/error.js');
const { Help } = require('./lib/help.js');
const { Option } = require('./lib/option.js');
// @ts-check
/**
* Expose the root command.
*/
exports = module.exports = new Command();
exports.program = exports; // More explicit access to global command.
// Implicit export of createArgument, createCommand, and createOption.
/**
* Expose classes
*/
exports.Argument = Argument;
exports.Command = Command;
exports.CommanderError = CommanderError;
exports.Help = Help;
exports.InvalidArgumentError = InvalidArgumentError;
exports.InvalidOptionArgumentError = InvalidArgumentError; // Deprecated
exports.Option = Option;

View File

@@ -0,0 +1,131 @@
const { InvalidArgumentError } = require('./error.js');
// @ts-check
class Argument {
/**
* Initialize a new command argument with the given name and description.
* The default is that the argument is required, and you can explicitly
* indicate this with <> around the name. Put [] around the name for an optional argument.
*
* @param {string} name
* @param {string} [description]
*/
constructor(name, description) {
this.description = description || '';
this.variadic = false;
this.parseArg = undefined;
this.defaultValue = undefined;
this.defaultValueDescription = undefined;
this.argChoices = undefined;
switch (name[0]) {
case '<': // e.g. <required>
this.required = true;
this._name = name.slice(1, -1);
break;
case '[': // e.g. [optional]
this.required = false;
this._name = name.slice(1, -1);
break;
default:
this.required = true;
this._name = name;
break;
}
if (this._name.length > 3 && this._name.slice(-3) === '...') {
this.variadic = true;
this._name = this._name.slice(0, -3);
}
}
/**
* Return argument name.
*
* @return {string}
*/
name() {
return this._name;
};
/**
* @api private
*/
_concatValue(value, previous) {
if (previous === this.defaultValue || !Array.isArray(previous)) {
return [value];
}
return previous.concat(value);
}
/**
* Set the default value, and optionally supply the description to be displayed in the help.
*
* @param {any} value
* @param {string} [description]
* @return {Argument}
*/
default(value, description) {
this.defaultValue = value;
this.defaultValueDescription = description;
return this;
};
/**
* Set the custom handler for processing CLI command arguments into argument values.
*
* @param {Function} [fn]
* @return {Argument}
*/
argParser(fn) {
this.parseArg = fn;
return this;
};
/**
* Only allow option value to be one of choices.
*
* @param {string[]} values
* @return {Argument}
*/
choices(values) {
this.argChoices = values;
this.parseArg = (arg, previous) => {
if (!values.includes(arg)) {
throw new InvalidArgumentError(`Allowed choices are ${values.join(', ')}.`);
}
if (this.variadic) {
return this._concatValue(arg, previous);
}
return arg;
};
return this;
};
}
/**
* Takes an argument and returns its human readable equivalent for help usage.
*
* @param {Argument} arg
* @return {string}
* @api private
*/
function humanReadableArgName(arg) {
const nameOutput = arg.name() + (arg.variadic === true ? '...' : '');
return arg.required
? '<' + nameOutput + '>'
: '[' + nameOutput + ']';
}
exports.Argument = Argument;
exports.humanReadableArgName = humanReadableArgName;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,45 @@
// @ts-check
/**
* CommanderError class
* @class
*/
class CommanderError extends Error {
/**
* Constructs the CommanderError class
* @param {number} exitCode suggested exit code which could be used with process.exit
* @param {string} code an id string representing the error
* @param {string} message human-readable description of the error
* @constructor
*/
constructor(exitCode, code, message) {
super(message);
// properly capture stack trace in Node.js
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.code = code;
this.exitCode = exitCode;
this.nestedError = undefined;
}
}
/**
* InvalidArgumentError class
* @class
*/
class InvalidArgumentError extends CommanderError {
/**
* Constructs the InvalidArgumentError class
* @param {string} [message] explanation of why argument is invalid
* @constructor
*/
constructor(message) {
super(1, 'commander.invalidArgument', message);
// properly capture stack trace in Node.js
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
}
}
exports.CommanderError = CommanderError;
exports.InvalidArgumentError = InvalidArgumentError;

View File

@@ -0,0 +1,393 @@
const { humanReadableArgName } = require('./argument.js');
/**
* TypeScript import types for JSDoc, used by Visual Studio Code IntelliSense and `npm run typescript-checkJS`
* https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#import-types
* @typedef { import("./argument.js").Argument } Argument
* @typedef { import("./command.js").Command } Command
* @typedef { import("./option.js").Option } Option
*/
// @ts-check
// Although this is a class, methods are static in style to allow override using subclass or just functions.
class Help {
constructor() {
this.helpWidth = undefined;
this.sortSubcommands = false;
this.sortOptions = false;
}
/**
* Get an array of the visible subcommands. Includes a placeholder for the implicit help command, if there is one.
*
* @param {Command} cmd
* @returns {Command[]}
*/
visibleCommands(cmd) {
const visibleCommands = cmd.commands.filter(cmd => !cmd._hidden);
if (cmd._hasImplicitHelpCommand()) {
// Create a command matching the implicit help command.
const [, helpName, helpArgs] = cmd._helpCommandnameAndArgs.match(/([^ ]+) *(.*)/);
const helpCommand = cmd.createCommand(helpName)
.helpOption(false);
helpCommand.description(cmd._helpCommandDescription);
if (helpArgs) helpCommand.arguments(helpArgs);
visibleCommands.push(helpCommand);
}
if (this.sortSubcommands) {
visibleCommands.sort((a, b) => {
// @ts-ignore: overloaded return type
return a.name().localeCompare(b.name());
});
}
return visibleCommands;
}
/**
* Get an array of the visible options. Includes a placeholder for the implicit help option, if there is one.
*
* @param {Command} cmd
* @returns {Option[]}
*/
visibleOptions(cmd) {
const visibleOptions = cmd.options.filter((option) => !option.hidden);
// Implicit help
const showShortHelpFlag = cmd._hasHelpOption && cmd._helpShortFlag && !cmd._findOption(cmd._helpShortFlag);
const showLongHelpFlag = cmd._hasHelpOption && !cmd._findOption(cmd._helpLongFlag);
if (showShortHelpFlag || showLongHelpFlag) {
let helpOption;
if (!showShortHelpFlag) {
helpOption = cmd.createOption(cmd._helpLongFlag, cmd._helpDescription);
} else if (!showLongHelpFlag) {
helpOption = cmd.createOption(cmd._helpShortFlag, cmd._helpDescription);
} else {
helpOption = cmd.createOption(cmd._helpFlags, cmd._helpDescription);
}
visibleOptions.push(helpOption);
}
if (this.sortOptions) {
const getSortKey = (option) => {
// WYSIWYG for order displayed in help with short before long, no special handling for negated.
return option.short ? option.short.replace(/^-/, '') : option.long.replace(/^--/, '');
};
visibleOptions.sort((a, b) => {
return getSortKey(a).localeCompare(getSortKey(b));
});
}
return visibleOptions;
}
/**
* Get an array of the arguments if any have a description.
*
* @param {Command} cmd
* @returns {Argument[]}
*/
visibleArguments(cmd) {
// Side effect! Apply the legacy descriptions before the arguments are displayed.
if (cmd._argsDescription) {
cmd._args.forEach(argument => {
argument.description = argument.description || cmd._argsDescription[argument.name()] || '';
});
}
// If there are any arguments with a description then return all the arguments.
if (cmd._args.find(argument => argument.description)) {
return cmd._args;
};
return [];
}
/**
* Get the command term to show in the list of subcommands.
*
* @param {Command} cmd
* @returns {string}
*/
subcommandTerm(cmd) {
// Legacy. Ignores custom usage string, and nested commands.
const args = cmd._args.map(arg => humanReadableArgName(arg)).join(' ');
return cmd._name +
(cmd._aliases[0] ? '|' + cmd._aliases[0] : '') +
(cmd.options.length ? ' [options]' : '') + // simplistic check for non-help option
(args ? ' ' + args : '');
}
/**
* Get the option term to show in the list of options.
*
* @param {Option} option
* @returns {string}
*/
optionTerm(option) {
return option.flags;
}
/**
* Get the argument term to show in the list of arguments.
*
* @param {Argument} argument
* @returns {string}
*/
argumentTerm(argument) {
return argument.name();
}
/**
* Get the longest command term length.
*
* @param {Command} cmd
* @param {Help} helper
* @returns {number}
*/
longestSubcommandTermLength(cmd, helper) {
return helper.visibleCommands(cmd).reduce((max, command) => {
return Math.max(max, helper.subcommandTerm(command).length);
}, 0);
};
/**
* Get the longest option term length.
*
* @param {Command} cmd
* @param {Help} helper
* @returns {number}
*/
longestOptionTermLength(cmd, helper) {
return helper.visibleOptions(cmd).reduce((max, option) => {
return Math.max(max, helper.optionTerm(option).length);
}, 0);
};
/**
* Get the longest argument term length.
*
* @param {Command} cmd
* @param {Help} helper
* @returns {number}
*/
longestArgumentTermLength(cmd, helper) {
return helper.visibleArguments(cmd).reduce((max, argument) => {
return Math.max(max, helper.argumentTerm(argument).length);
}, 0);
};
/**
* Get the command usage to be displayed at the top of the built-in help.
*
* @param {Command} cmd
* @returns {string}
*/
commandUsage(cmd) {
// Usage
let cmdName = cmd._name;
if (cmd._aliases[0]) {
cmdName = cmdName + '|' + cmd._aliases[0];
}
let parentCmdNames = '';
for (let parentCmd = cmd.parent; parentCmd; parentCmd = parentCmd.parent) {
parentCmdNames = parentCmd.name() + ' ' + parentCmdNames;
}
return parentCmdNames + cmdName + ' ' + cmd.usage();
}
/**
* Get the description for the command.
*
* @param {Command} cmd
* @returns {string}
*/
commandDescription(cmd) {
// @ts-ignore: overloaded return type
return cmd.description();
}
/**
* Get the command description to show in the list of subcommands.
*
* @param {Command} cmd
* @returns {string}
*/
subcommandDescription(cmd) {
// @ts-ignore: overloaded return type
return cmd.description();
}
/**
* Get the option description to show in the list of options.
*
* @param {Option} option
* @return {string}
*/
optionDescription(option) {
if (option.negate) {
return option.description;
}
const extraInfo = [];
if (option.argChoices) {
extraInfo.push(
// use stringify to match the display of the default value
`choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`);
}
if (option.defaultValue !== undefined) {
extraInfo.push(`default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`);
}
if (extraInfo.length > 0) {
return `${option.description} (${extraInfo.join(', ')})`;
}
return option.description;
};
/**
* Get the argument description to show in the list of arguments.
*
* @param {Argument} argument
* @return {string}
*/
argumentDescription(argument) {
const extraInfo = [];
if (argument.argChoices) {
extraInfo.push(
// use stringify to match the display of the default value
`choices: ${argument.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`);
}
if (argument.defaultValue !== undefined) {
extraInfo.push(`default: ${argument.defaultValueDescription || JSON.stringify(argument.defaultValue)}`);
}
if (extraInfo.length > 0) {
const extraDescripton = `(${extraInfo.join(', ')})`;
if (argument.description) {
return `${argument.description} ${extraDescripton}`;
}
return extraDescripton;
}
return argument.description;
}
/**
* Generate the built-in help text.
*
* @param {Command} cmd
* @param {Help} helper
* @returns {string}
*/
formatHelp(cmd, helper) {
const termWidth = helper.padWidth(cmd, helper);
const helpWidth = helper.helpWidth || 80;
const itemIndentWidth = 2;
const itemSeparatorWidth = 2; // between term and description
function formatItem(term, description) {
if (description) {
const fullText = `${term.padEnd(termWidth + itemSeparatorWidth)}${description}`;
return helper.wrap(fullText, helpWidth - itemIndentWidth, termWidth + itemSeparatorWidth);
}
return term;
};
function formatList(textArray) {
return textArray.join('\n').replace(/^/gm, ' '.repeat(itemIndentWidth));
}
// Usage
let output = [`Usage: ${helper.commandUsage(cmd)}`, ''];
// Description
const commandDescription = helper.commandDescription(cmd);
if (commandDescription.length > 0) {
output = output.concat([commandDescription, '']);
}
// Arguments
const argumentList = helper.visibleArguments(cmd).map((argument) => {
return formatItem(helper.argumentTerm(argument), helper.argumentDescription(argument));
});
if (argumentList.length > 0) {
output = output.concat(['Arguments:', formatList(argumentList), '']);
}
// Options
const optionList = helper.visibleOptions(cmd).map((option) => {
return formatItem(helper.optionTerm(option), helper.optionDescription(option));
});
if (optionList.length > 0) {
output = output.concat(['Options:', formatList(optionList), '']);
}
// Commands
const commandList = helper.visibleCommands(cmd).map((cmd) => {
return formatItem(helper.subcommandTerm(cmd), helper.subcommandDescription(cmd));
});
if (commandList.length > 0) {
output = output.concat(['Commands:', formatList(commandList), '']);
}
return output.join('\n');
}
/**
* Calculate the pad width from the maximum term length.
*
* @param {Command} cmd
* @param {Help} helper
* @returns {number}
*/
padWidth(cmd, helper) {
return Math.max(
helper.longestOptionTermLength(cmd, helper),
helper.longestSubcommandTermLength(cmd, helper),
helper.longestArgumentTermLength(cmd, helper)
);
};
/**
* Wrap the given string to width characters per line, with lines after the first indented.
* Do not wrap if insufficient room for wrapping (minColumnWidth), or string is manually formatted.
*
* @param {string} str
* @param {number} width
* @param {number} indent
* @param {number} [minColumnWidth=40]
* @return {string}
*
*/
wrap(str, width, indent, minColumnWidth = 40) {
// Detect manually wrapped and indented strings by searching for line breaks
// followed by multiple spaces/tabs.
if (str.match(/[\n]\s+/)) return str;
// Do not wrap if not enough room for a wrapped column of text (as could end up with a word per line).
const columnWidth = width - indent;
if (columnWidth < minColumnWidth) return str;
const leadingStr = str.substr(0, indent);
const columnText = str.substr(indent);
const indentString = ' '.repeat(indent);
const regex = new RegExp('.{1,' + (columnWidth - 1) + '}([\\s\u200B]|$)|[^\\s\u200B]+?([\\s\u200B]|$)', 'g');
const lines = columnText.match(regex) || [];
return leadingStr + lines.map((line, i) => {
if (line.slice(-1) === '\n') {
line = line.slice(0, line.length - 1);
}
return ((i > 0) ? indentString : '') + line.trimRight();
}).join('\n');
}
}
exports.Help = Help;

View File

@@ -0,0 +1,194 @@
const { InvalidArgumentError } = require('./error.js');
// @ts-check
class Option {
/**
* Initialize a new `Option` with the given `flags` and `description`.
*
* @param {string} flags
* @param {string} [description]
*/
constructor(flags, description) {
this.flags = flags;
this.description = description || '';
this.required = flags.includes('<'); // A value must be supplied when the option is specified.
this.optional = flags.includes('['); // A value is optional when the option is specified.
// variadic test ignores <value,...> et al which might be used to describe custom splitting of single argument
this.variadic = /\w\.\.\.[>\]]$/.test(flags); // The option can take multiple values.
this.mandatory = false; // The option must have a value after parsing, which usually means it must be specified on command line.
const optionFlags = splitOptionFlags(flags);
this.short = optionFlags.shortFlag;
this.long = optionFlags.longFlag;
this.negate = false;
if (this.long) {
this.negate = this.long.startsWith('--no-');
}
this.defaultValue = undefined;
this.defaultValueDescription = undefined;
this.parseArg = undefined;
this.hidden = false;
this.argChoices = undefined;
}
/**
* Set the default value, and optionally supply the description to be displayed in the help.
*
* @param {any} value
* @param {string} [description]
* @return {Option}
*/
default(value, description) {
this.defaultValue = value;
this.defaultValueDescription = description;
return this;
};
/**
* Set the custom handler for processing CLI option arguments into option values.
*
* @param {Function} [fn]
* @return {Option}
*/
argParser(fn) {
this.parseArg = fn;
return this;
};
/**
* Whether the option is mandatory and must have a value after parsing.
*
* @param {boolean} [mandatory=true]
* @return {Option}
*/
makeOptionMandatory(mandatory = true) {
this.mandatory = !!mandatory;
return this;
};
/**
* Hide option in help.
*
* @param {boolean} [hide=true]
* @return {Option}
*/
hideHelp(hide = true) {
this.hidden = !!hide;
return this;
};
/**
* @api private
*/
_concatValue(value, previous) {
if (previous === this.defaultValue || !Array.isArray(previous)) {
return [value];
}
return previous.concat(value);
}
/**
* Only allow option value to be one of choices.
*
* @param {string[]} values
* @return {Option}
*/
choices(values) {
this.argChoices = values;
this.parseArg = (arg, previous) => {
if (!values.includes(arg)) {
throw new InvalidArgumentError(`Allowed choices are ${values.join(', ')}.`);
}
if (this.variadic) {
return this._concatValue(arg, previous);
}
return arg;
};
return this;
};
/**
* Return option name.
*
* @return {string}
*/
name() {
if (this.long) {
return this.long.replace(/^--/, '');
}
return this.short.replace(/^-/, '');
};
/**
* Return option name, in a camelcase format that can be used
* as a object attribute key.
*
* @return {string}
* @api private
*/
attributeName() {
return camelcase(this.name().replace(/^no-/, ''));
};
/**
* Check if `arg` matches the short or long flag.
*
* @param {string} arg
* @return {boolean}
* @api private
*/
is(arg) {
return this.short === arg || this.long === arg;
};
}
/**
* Convert string from kebab-case to camelCase.
*
* @param {string} str
* @return {string}
* @api private
*/
function camelcase(str) {
return str.split('-').reduce((str, word) => {
return str + word[0].toUpperCase() + word.slice(1);
});
}
/**
* Split the short and long flag out of something like '-m,--mixed <value>'
*
* @api private
*/
function splitOptionFlags(flags) {
let shortFlag;
let longFlag;
// Use original very loose parsing to maintain backwards compatibility for now,
// which allowed for example unintended `-sw, --short-word` [sic].
const flagParts = flags.split(/[ |,]+/);
if (flagParts.length > 1 && !/^[[<]/.test(flagParts[1])) shortFlag = flagParts.shift();
longFlag = flagParts.shift();
// Add support for lone short flag without significantly changing parsing!
if (!shortFlag && /^-[^-]$/.test(longFlag)) {
shortFlag = longFlag;
longFlag = undefined;
}
return { shortFlag, longFlag };
}
exports.Option = Option;
exports.splitOptionFlags = splitOptionFlags;

View File

@@ -0,0 +1,16 @@
{
"versions": [
{
"version": "*",
"target": {
"node": "supported"
},
"response": {
"type": "time-permitting"
},
"backing": {
"npm-funding": true
}
}
]
}

View File

@@ -0,0 +1,69 @@
{
"name": "commander",
"version": "8.0.0",
"description": "the complete solution for node.js command-line programs",
"keywords": [
"commander",
"command",
"option",
"parser",
"cli",
"argument",
"args",
"argv"
],
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/tj/commander.js.git"
},
"scripts": {
"lint": "eslint index.js esm.mjs \"lib/*.js\" \"tests/**/*.js\"",
"typescript-lint": "eslint typings/*.ts tests/*.ts",
"test": "jest && npm run test-typings",
"test-esm": "node --experimental-modules ./tests/esm-imports-test.mjs",
"test-typings": "tsd",
"typescript-checkJS": "tsc --allowJS --checkJS index.js lib/*.js --noEmit",
"test-all": "npm run test && npm run lint && npm run typescript-lint && npm run typescript-checkJS && npm run test-esm"
},
"main": "./index.js",
"files": [
"index.js",
"lib/*.js",
"esm.mjs",
"typings/index.d.ts",
"package-support.json"
],
"type": "commonjs",
"dependencies": {},
"devDependencies": {
"@types/jest": "^26.0.23",
"@types/node": "^14.17.3",
"@typescript-eslint/eslint-plugin": "^4.27.0",
"@typescript-eslint/parser": "^4.27.0",
"eslint": "^7.29.0",
"eslint-config-standard": "^16.0.3",
"eslint-plugin-jest": "^24.3.6",
"jest": "^27.0.4",
"standard": "^16.0.3",
"ts-jest": "^27.0.3",
"tsd": "^0.17.0",
"typescript": "^4.3.4"
},
"types": "typings/index.d.ts",
"jest": {
"testEnvironment": "node",
"collectCoverage": true,
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"testPathIgnorePatterns": [
"/node_modules/"
]
},
"engines": {
"node": ">= 12"
},
"support": true
}

View File

@@ -0,0 +1,732 @@
// Type definitions for commander
// Original definitions by: Alan Agius <https://github.com/alan-agius4>, Marcelo Dezem <https://github.com/mdezem>, vvakame <https://github.com/vvakame>, Jules Randolph <https://github.com/sveinburne>
// Using method rather than property for method-signature-style, to document method overloads separately. Allow either.
/* eslint-disable @typescript-eslint/method-signature-style */
/* eslint-disable @typescript-eslint/no-explicit-any */
export class CommanderError extends Error {
code: string;
exitCode: number;
message: string;
nestedError?: string;
/**
* Constructs the CommanderError class
* @param {number} exitCode suggested exit code which could be used with process.exit
* @param {string} code an id string representing the error
* @param {string} message human-readable description of the error
* @constructor
*/
constructor(exitCode: number, code: string, message: string);
}
export class InvalidArgumentError extends CommanderError {
/**
* Constructs the InvalidArgumentError class
* @param {string} [message] explanation of why argument is invalid
* @constructor
*/
constructor(message: string);
}
export { InvalidArgumentError as InvalidOptionArgumentError }; // deprecated old name
export class Argument {
description: string;
required: boolean;
variadic: boolean;
/**
* Initialize a new command argument with the given name and description.
* The default is that the argument is required, and you can explicitly
* indicate this with <> around the name. Put [] around the name for an optional argument.
*
* @param {string} name
* @param {string} [description]
*/
constructor(arg: string, description?: string);
/**
* Return argument name.
*/
name(): string;
/**
* Set the default value, and optionally supply the description to be displayed in the help.
*/
default(value: unknown, description?: string): this;
/**
* Set the custom handler for processing CLI command arguments into argument values.
*/
argParser<T>(fn: (value: string, previous: T) => T): this;
/**
* Only allow argument value to be one of choices.
*/
choices(values: string[]): this;
}
export class Option {
flags: string;
description: string;
required: boolean; // A value must be supplied when the option is specified.
optional: boolean; // A value is optional when the option is specified.
variadic: boolean;
mandatory: boolean; // The option must have a value after parsing, which usually means it must be specified on command line.
optionFlags: string;
short?: string;
long?: string;
negate: boolean;
defaultValue?: any;
defaultValueDescription?: string;
parseArg?: <T>(value: string, previous: T) => T;
hidden: boolean;
argChoices?: string[];
constructor(flags: string, description?: string);
/**
* Set the default value, and optionally supply the description to be displayed in the help.
*/
default(value: unknown, description?: string): this;
/**
* Calculate the full description, including defaultValue etc.
*/
fullDescription(): string;
/**
* Set the custom handler for processing CLI option arguments into option values.
*/
argParser<T>(fn: (value: string, previous: T) => T): this;
/**
* Whether the option is mandatory and must have a value after parsing.
*/
makeOptionMandatory(mandatory?: boolean): this;
/**
* Hide option in help.
*/
hideHelp(hide?: boolean): this;
/**
* Validation of option argument failed.
* Intended for use from custom argument processing functions.
*/
argumentRejected(messsage: string): never;
/**
* Only allow option value to be one of choices.
*/
choices(values: string[]): this;
/**
* Return option name.
*/
name(): string;
/**
* Return option name, in a camelcase format that can be used
* as a object attribute key.
*/
attributeName(): string;
}
export class Help {
/** output helpWidth, long lines are wrapped to fit */
helpWidth?: number;
sortSubcommands: boolean;
sortOptions: boolean;
constructor();
/** Get the command term to show in the list of subcommands. */
subcommandTerm(cmd: Command): string;
/** Get the command description to show in the list of subcommands. */
subcommandDescription(cmd: Command): string;
/** Get the option term to show in the list of options. */
optionTerm(option: Option): string;
/** Get the option description to show in the list of options. */
optionDescription(option: Option): string;
/** Get the argument term to show in the list of arguments. */
argumentTerm(argument: Argument): string;
/** Get the argument description to show in the list of arguments. */
argumentDescription(argument: Argument): string;
/** Get the command usage to be displayed at the top of the built-in help. */
commandUsage(cmd: Command): string;
/** Get the description for the command. */
commandDescription(cmd: Command): string;
/** Get an array of the visible subcommands. Includes a placeholder for the implicit help command, if there is one. */
visibleCommands(cmd: Command): Command[];
/** Get an array of the visible options. Includes a placeholder for the implicit help option, if there is one. */
visibleOptions(cmd: Command): Option[];
/** Get an array of the arguments which have descriptions. */
visibleArguments(cmd: Command): Argument[];
/** Get the longest command term length. */
longestSubcommandTermLength(cmd: Command, helper: Help): number;
/** Get the longest option term length. */
longestOptionTermLength(cmd: Command, helper: Help): number;
/** Get the longest argument term length. */
longestArgumentTermLength(cmd: Command, helper: Help): number;
/** Calculate the pad width from the maximum term length. */
padWidth(cmd: Command, helper: Help): number;
/**
* Wrap the given string to width characters per line, with lines after the first indented.
* Do not wrap if insufficient room for wrapping (minColumnWidth), or string is manually formatted.
*/
wrap(str: string, width: number, indent: number, minColumnWidth?: number): string;
/** Generate the built-in help text. */
formatHelp(cmd: Command, helper: Help): string;
}
export type HelpConfiguration = Partial<Help>;
export interface ParseOptions {
from: 'node' | 'electron' | 'user';
}
export interface HelpContext { // optional parameter for .help() and .outputHelp()
error: boolean;
}
export interface AddHelpTextContext { // passed to text function used with .addHelpText()
error: boolean;
command: Command;
}
export interface OutputConfiguration {
writeOut?(str: string): void;
writeErr?(str: string): void;
getOutHelpWidth?(): number;
getErrHelpWidth?(): number;
outputError?(str: string, write: (str: string) => void): void;
}
type AddHelpTextPosition = 'beforeAll' | 'before' | 'after' | 'afterAll';
type HookEvent = 'preAction' | 'postAction';
export interface OptionValues {
[key: string]: any;
}
export class Command {
args: string[];
processedArgs: any[];
commands: Command[];
parent: Command | null;
constructor(name?: string);
/**
* Set the program version to `str`.
*
* This method auto-registers the "-V, --version" flag
* which will print the version number when passed.
*
* You can optionally supply the flags and description to override the defaults.
*/
version(str: string, flags?: string, description?: string): this;
/**
* Define a command, implemented using an action handler.
*
* @remarks
* The command description is supplied using `.description`, not as a parameter to `.command`.
*
* @example
* ```ts
* program
* .command('clone <source> [destination]')
* .description('clone a repository into a newly created directory')
* .action((source, destination) => {
* console.log('clone command called');
* });
* ```
*
* @param nameAndArgs - command name and arguments, args are `<required>` or `[optional]` and last may also be `variadic...`
* @param opts - configuration options
* @returns new command
*/
command(nameAndArgs: string, opts?: CommandOptions): ReturnType<this['createCommand']>;
/**
* Define a command, implemented in a separate executable file.
*
* @remarks
* The command description is supplied as the second parameter to `.command`.
*
* @example
* ```ts
* program
* .command('start <service>', 'start named service')
* .command('stop [service]', 'stop named service, or all if no name supplied');
* ```
*
* @param nameAndArgs - command name and arguments, args are `<required>` or `[optional]` and last may also be `variadic...`
* @param description - description of executable command
* @param opts - configuration options
* @returns `this` command for chaining
*/
command(nameAndArgs: string, description: string, opts?: ExecutableCommandOptions): this;
/**
* Factory routine to create a new unattached command.
*
* See .command() for creating an attached subcommand, which uses this routine to
* create the command. You can override createCommand to customise subcommands.
*/
createCommand(name?: string): Command;
/**
* Add a prepared subcommand.
*
* See .command() for creating an attached subcommand which inherits settings from its parent.
*
* @returns `this` command for chaining
*/
addCommand(cmd: Command, opts?: CommandOptions): this;
/**
* Factory routine to create a new unattached argument.
*
* See .argument() for creating an attached argument, which uses this routine to
* create the argument. You can override createArgument to return a custom argument.
*/
createArgument(name: string, description?: string): Argument;
/**
* Define argument syntax for command.
*
* The default is that the argument is required, and you can explicitly
* indicate this with <> around the name. Put [] around the name for an optional argument.
*
* @example
*
* program.argument('<input-file>');
* program.argument('[output-file]');
*
* @returns `this` command for chaining
*/
argument<T>(flags: string, description: string, fn: (value: string, previous: T) => T, defaultValue?: T): this;
argument(name: string, description?: string, defaultValue?: unknown): this;
/**
* Define argument syntax for command, adding a prepared argument.
*
* @returns `this` command for chaining
*/
addArgument(arg: Argument): this;
/**
* Define argument syntax for command, adding multiple at once (without descriptions).
*
* See also .argument().
*
* @example
*
* program.arguments('<cmd> [env]');
*
* @returns `this` command for chaining
*/
arguments(names: string): this;
/**
* Override default decision whether to add implicit help command.
*
* addHelpCommand() // force on
* addHelpCommand(false); // force off
* addHelpCommand('help [cmd]', 'display help for [cmd]'); // force on with custom details
*
* @returns `this` command for chaining
*/
addHelpCommand(enableOrNameAndArgs?: string | boolean, description?: string): this;
/**
* Add hook for life cycle event.
*/
hook(event: HookEvent, listener: (thisCommand: Command, actionCommand: Command) => void | Promise<void>): this;
/**
* Register callback to use as replacement for calling process.exit.
*/
exitOverride(callback?: (err: CommanderError) => never|void): this;
/**
* You can customise the help with a subclass of Help by overriding createHelp,
* or by overriding Help properties using configureHelp().
*/
createHelp(): Help;
/**
* You can customise the help by overriding Help properties using configureHelp(),
* or with a subclass of Help by overriding createHelp().
*/
configureHelp(configuration: HelpConfiguration): this;
/** Get configuration */
configureHelp(): HelpConfiguration;
/**
* The default output goes to stdout and stderr. You can customise this for special
* applications. You can also customise the display of errors by overriding outputError.
*
* The configuration properties are all functions:
*
* // functions to change where being written, stdout and stderr
* writeOut(str)
* writeErr(str)
* // matching functions to specify width for wrapping help
* getOutHelpWidth()
* getErrHelpWidth()
* // functions based on what is being written out
* outputError(str, write) // used for displaying errors, and not used for displaying help
*/
configureOutput(configuration: OutputConfiguration): this;
/** Get configuration */
configureOutput(): OutputConfiguration;
/**
* Display the help or a custom message after an error occurs.
*/
showHelpAfterError(displayHelp?: boolean | string): this;
/**
* Register callback `fn` for the command.
*
* @example
* program
* .command('help')
* .description('display verbose help')
* .action(function() {
* // output help here
* });
*
* @returns `this` command for chaining
*/
action(fn: (...args: any[]) => void | Promise<void>): this;
/**
* Define option with `flags`, `description` and optional
* coercion `fn`.
*
* The `flags` string contains the short and/or long flags,
* separated by comma, a pipe or space. The following are all valid
* all will output this way when `--help` is used.
*
* "-p, --pepper"
* "-p|--pepper"
* "-p --pepper"
*
* @example
* // simple boolean defaulting to false
* program.option('-p, --pepper', 'add pepper');
*
* --pepper
* program.pepper
* // => Boolean
*
* // simple boolean defaulting to true
* program.option('-C, --no-cheese', 'remove cheese');
*
* program.cheese
* // => true
*
* --no-cheese
* program.cheese
* // => false
*
* // required argument
* program.option('-C, --chdir <path>', 'change the working directory');
*
* --chdir /tmp
* program.chdir
* // => "/tmp"
*
* // optional argument
* program.option('-c, --cheese [type]', 'add cheese [marble]');
*
* @returns `this` command for chaining
*/
option(flags: string, description?: string, defaultValue?: string | boolean): this;
option<T>(flags: string, description: string, fn: (value: string, previous: T) => T, defaultValue?: T): this;
/** @deprecated since v7, instead use choices or a custom function */
option(flags: string, description: string, regexp: RegExp, defaultValue?: string | boolean): this;
/**
* Define a required option, which must have a value after parsing. This usually means
* the option must be specified on the command line. (Otherwise the same as .option().)
*
* The `flags` string contains the short and/or long flags, separated by comma, a pipe or space.
*/
requiredOption(flags: string, description?: string, defaultValue?: string | boolean): this;
requiredOption<T>(flags: string, description: string, fn: (value: string, previous: T) => T, defaultValue?: T): this;
/** @deprecated since v7, instead use choices or a custom function */
requiredOption(flags: string, description: string, regexp: RegExp, defaultValue?: string | boolean): this;
/**
* Factory routine to create a new unattached option.
*
* See .option() for creating an attached option, which uses this routine to
* create the option. You can override createOption to return a custom option.
*/
createOption(flags: string, description?: string): Option;
/**
* Add a prepared Option.
*
* See .option() and .requiredOption() for creating and attaching an option in a single call.
*/
addOption(option: Option): this;
/**
* Whether to store option values as properties on command object,
* or store separately (specify false). In both cases the option values can be accessed using .opts().
*
* @returns `this` command for chaining
*/
storeOptionsAsProperties<T extends OptionValues>(): this & T;
storeOptionsAsProperties<T extends OptionValues>(storeAsProperties: true): this & T;
storeOptionsAsProperties(storeAsProperties?: boolean): this;
/**
* Retrieve option value.
*/
getOptionValue(key: string): any;
/**
* Store option value.
*/
setOptionValue(key: string, value: unknown): this;
/**
* Alter parsing of short flags with optional values.
*
* @example
* // for `.option('-f,--flag [value]'):
* .combineFlagAndOptionalValue(true) // `-f80` is treated like `--flag=80`, this is the default behaviour
* .combineFlagAndOptionalValue(false) // `-fb` is treated like `-f -b`
*
* @returns `this` command for chaining
*/
combineFlagAndOptionalValue(combine?: boolean): this;
/**
* Allow unknown options on the command line.
*
* @returns `this` command for chaining
*/
allowUnknownOption(allowUnknown?: boolean): this;
/**
* Allow excess command-arguments on the command line. Pass false to make excess arguments an error.
*
* @returns `this` command for chaining
*/
allowExcessArguments(allowExcess?: boolean): this;
/**
* Enable positional options. Positional means global options are specified before subcommands which lets
* subcommands reuse the same option names, and also enables subcommands to turn on passThroughOptions.
*
* The default behaviour is non-positional and global options may appear anywhere on the command line.
*
* @returns `this` command for chaining
*/
enablePositionalOptions(positional?: boolean): this;
/**
* Pass through options that come after command-arguments rather than treat them as command-options,
* so actual command-options come before command-arguments. Turning this on for a subcommand requires
* positional options to have been enabled on the program (parent commands).
*
* The default behaviour is non-positional and options may appear before or after command-arguments.
*
* @returns `this` command for chaining
*/
passThroughOptions(passThrough?: boolean): this;
/**
* Parse `argv`, setting options and invoking commands when defined.
*
* The default expectation is that the arguments are from node and have the application as argv[0]
* and the script being run in argv[1], with user parameters after that.
*
* Examples:
*
* program.parse(process.argv);
* program.parse(); // implicitly use process.argv and auto-detect node vs electron conventions
* program.parse(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
*
* @returns `this` command for chaining
*/
parse(argv?: string[], options?: ParseOptions): this;
/**
* Parse `argv`, setting options and invoking commands when defined.
*
* Use parseAsync instead of parse if any of your action handlers are async. Returns a Promise.
*
* The default expectation is that the arguments are from node and have the application as argv[0]
* and the script being run in argv[1], with user parameters after that.
*
* Examples:
*
* program.parseAsync(process.argv);
* program.parseAsync(); // implicitly use process.argv and auto-detect node vs electron conventions
* program.parseAsync(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
*
* @returns Promise
*/
parseAsync(argv?: string[], options?: ParseOptions): Promise<this>;
/**
* Parse options from `argv` removing known options,
* and return argv split into operands and unknown arguments.
*
* @example
* argv => operands, unknown
* --known kkk op => [op], []
* op --known kkk => [op], []
* sub --unknown uuu op => [sub], [--unknown uuu op]
* sub -- --unknown uuu op => [sub --unknown uuu op], []
*/
parseOptions(argv: string[]): ParseOptionsResult;
/**
* Return an object containing options as key-value pairs
*/
opts<T extends OptionValues>(): T;
/**
* Set the description.
*
* @returns `this` command for chaining
*/
description(str: string): this;
/** @deprecated since v8, instead use .argument to add command argument with description */
description(str: string, argsDescription: {[argName: string]: string}): this;
/**
* Get the description.
*/
description(): string;
/**
* Set an alias for the command.
*
* You may call more than once to add multiple aliases. Only the first alias is shown in the auto-generated help.
*
* @returns `this` command for chaining
*/
alias(alias: string): this;
/**
* Get alias for the command.
*/
alias(): string;
/**
* Set aliases for the command.
*
* Only the first alias is shown in the auto-generated help.
*
* @returns `this` command for chaining
*/
aliases(aliases: string[]): this;
/**
* Get aliases for the command.
*/
aliases(): string[];
/**
* Set the command usage.
*
* @returns `this` command for chaining
*/
usage(str: string): this;
/**
* Get the command usage.
*/
usage(): string;
/**
* Set the name of the command.
*
* @returns `this` command for chaining
*/
name(str: string): this;
/**
* Get the name of the command.
*/
name(): string;
/**
* Output help information for this command.
*
* Outputs built-in help, and custom text added using `.addHelpText()`.
*
*/
outputHelp(context?: HelpContext): void;
/** @deprecated since v7 */
outputHelp(cb?: (str: string) => string): void;
/**
* Return command help documentation.
*/
helpInformation(context?: HelpContext): string;
/**
* You can pass in flags and a description to override the help
* flags and help description for your command. Pass in false
* to disable the built-in help option.
*/
helpOption(flags?: string | boolean, description?: string): this;
/**
* Output help information and exit.
*
* Outputs built-in help, and custom text added using `.addHelpText()`.
*/
help(context?: HelpContext): never;
/** @deprecated since v7 */
help(cb?: (str: string) => string): never;
/**
* Add additional text to be displayed with the built-in help.
*
* Position is 'before' or 'after' to affect just this command,
* and 'beforeAll' or 'afterAll' to affect this command and all its subcommands.
*/
addHelpText(position: AddHelpTextPosition, text: string): this;
addHelpText(position: AddHelpTextPosition, text: (context: AddHelpTextContext) => string): this;
/**
* Add a listener (callback) for when events occur. (Implemented using EventEmitter.)
*/
on(event: string | symbol, listener: (...args: any[]) => void): this;
}
export interface CommandOptions {
hidden?: boolean;
isDefault?: boolean;
/** @deprecated since v7, replaced by hidden */
noHelp?: boolean;
}
export interface ExecutableCommandOptions extends CommandOptions {
executableFile?: string;
}
export interface ParseOptionsResult {
operands: string[];
unknown: string[];
}
export function createCommand(name?: string): Command;
export function createOption(flags: string, description?: string): Option;
export function createArgument(name: string, description?: string): Argument;
export const program: Command;

View File

@@ -0,0 +1,74 @@
{
"name": "postman-collection-transformer",
"version": "4.1.3",
"description": "Perform rapid conversation and validation of JSON structure between Postman Collection Format v1 and v2",
"author": "Postman Inc.",
"license": "Apache-2.0",
"main": "index.js",
"bin": "./bin/transform-collection.js",
"homepage": "https://github.com/postmanlabs/postman-collection-transformer#readme",
"bugs": {
"url": "https://github.com/postmanlabs/postman-collection-transformer/issues",
"email": "help@postman.com"
},
"repository": {
"type": "git",
"url": "git+https://github.com/postmanlabs/postman-collection-transformer.git"
},
"keywords": [
"postman",
"collection",
"json",
"format",
"converter",
"transformer"
],
"scripts": {
"test": "npm run test-lint && npm run test-system && npm run test-unit && npm run test-schema && npm run test-browser",
"test-browser": "node npm/test-browser.js",
"test-lint": "node npm/test-lint.js",
"test-schema": "node npm/test-schema.js",
"test-system": "node npm/test-system.js",
"test-unit": "nyc --nycrc-path=.nycrc.js node npm/test-unit.js"
},
"dependencies": {
"commander": "8.0.0",
"inherits": "2.0.4",
"intel": "1.2.0",
"lodash": "4.17.21",
"semver": "7.3.5",
"strip-json-comments": "3.1.1"
},
"devDependencies": {
"async": "^3.2.0",
"browserify": "^17.0.0",
"chai": "^4.3.4",
"chalk": "^4.1.0",
"eslint": "^7.22.0",
"eslint-plugin-jsdoc": "^35.4.1",
"eslint-plugin-lodash": "^7.2.0",
"eslint-plugin-mocha": "^9.0.0",
"eslint-plugin-security": "^1.4.0",
"js-yaml": "^4.0.0",
"karma": "^6.2.0",
"karma-browserify": "^8.0.0",
"karma-chrome-launcher": "^3.1.0",
"karma-mocha": "^2.0.1",
"karma-mocha-reporter": "^2.2.5",
"mocha": "^9.0.1",
"nyc": "^15.1.0",
"packity": "^0.3.2",
"parse-gitignore": "^1.0.1",
"pretty-ms": "^7.0.1",
"recursive-readdir": "^2.2.2",
"require-all": "^3.0.0",
"shelljs": "^0.8.4",
"superagent": "^6.1.0",
"tv4": "^1.3.0",
"watchify": "^4.0.0",
"yankee": "^1.0.8"
},
"engines": {
"node": ">=10"
}
}