@oclif/plugin-command-snapshot 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/LICENSE +21 -0
- package/README.md +43 -0
- package/lib/commands/schema/compare.d.ts +15 -0
- package/lib/commands/schema/compare.js +95 -0
- package/lib/commands/schema/generate.d.ts +25 -0
- package/lib/commands/schema/generate.js +134 -0
- package/lib/commands/snapshot/compare.d.ts +44 -0
- package/lib/commands/snapshot/compare.js +120 -0
- package/lib/commands/snapshot/generate.d.ts +12 -0
- package/lib/commands/snapshot/generate.js +23 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +3 -0
- package/lib/snapshot-command.d.ts +10 -0
- package/lib/snapshot-command.js +25 -0
- package/oclif.manifest.json +1 -0
- package/package.json +77 -0
- package/schemas/schema-compare.json +41 -0
- package/schemas/schema-generate.json +12 -0
- package/schemas/snapshot-compare.json +77 -0
- package/schemas/snapshot-generate.json +32 -0
- package/yarn.lock +6283 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
|
+
|
|
5
|
+
### [2.2.2](https://github.com/oclif/plugin-command-snapshot/compare/v2.2.1...v2.2.2) (2021-06-30)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* dont fail if schemas folder isn't found ([b12dc79](https://github.com/oclif/plugin-command-snapshot/commit/b12dc796619fe2469b2b4ea8fc524f999b1c614e))
|
|
11
|
+
|
|
12
|
+
### [2.2.1](https://github.com/oclif/plugin-command-snapshot/compare/v2.2.0...v2.2.1) (2021-06-30)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* allow for empty schemas dir ([bf3d74e](https://github.com/oclif/plugin-command-snapshot/commit/bf3d74e3c071ef524402e1bdb20e50cbff990b80))
|
|
18
|
+
|
|
19
|
+
## [2.2.0](https://github.com/oclif/plugin-command-snapshot/compare/v2.1.2...v2.2.0) (2021-06-30)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
* add --ignorevoid flag to schema:generate ([9c4a48e](https://github.com/oclif/plugin-command-snapshot/commit/9c4a48e076e750c2320a47bc4b7c2f4bfc7029ae))
|
|
25
|
+
|
|
26
|
+
### [2.1.2](https://github.com/oclif/plugin-command-snapshot/compare/v2.1.1...v2.1.2) (2021-06-07)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Bug Fixes
|
|
30
|
+
|
|
31
|
+
* disallow void return type ([4655cfd](https://github.com/oclif/plugin-command-snapshot/commit/4655cfd86eb1429c6b0a8b036bb22e43336b6e09))
|
|
32
|
+
|
|
33
|
+
### [2.1.1](https://github.com/oclif/plugin-command-snapshot/compare/v2.1.0...v2.1.1) (2021-06-04)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### Bug Fixes
|
|
37
|
+
|
|
38
|
+
* ensure lib files are published ([c54b061](https://github.com/oclif/plugin-command-snapshot/commit/c54b06183aa39eaed60869421d3dd02e90fa27c0))
|
|
39
|
+
|
|
40
|
+
## [2.1.0](https://github.com/oclif/plugin-command-snapshot/compare/v2.0.0...v2.1.0) (2021-06-03)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
### Features
|
|
44
|
+
|
|
45
|
+
* add commands for generating and comparing JSON schemas ([#90](https://github.com/oclif/plugin-command-snapshot/issues/90)) ([0e31395](https://github.com/oclif/plugin-command-snapshot/commit/0e313950317179383786d939591d5e03116e5c27))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
### Bug Fixes
|
|
49
|
+
|
|
50
|
+
* update return type for schema:generate ([36d99b3](https://github.com/oclif/plugin-command-snapshot/commit/36d99b3dc3d325c5ed81ea88f818f8ab59a67470))
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 Salesforce.com
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
@oclif/plugin-command-snapshot
|
|
2
|
+
========
|
|
3
|
+
|
|
4
|
+
Generates and compares OCLIF plugins snapshot files
|
|
5
|
+
|
|
6
|
+
[](https://oclif.io)
|
|
7
|
+
[](https://npmjs.org/package/@oclif/plugin-command-snapshot)
|
|
8
|
+
[](https://npmjs.org/package/@oclif/plugin-command-snapshot)
|
|
9
|
+
[](https://github.com/nramyasri-sf/@oclif/plugin-command-snapshot/blob/master/package.json)
|
|
10
|
+
|
|
11
|
+
<!-- toc -->
|
|
12
|
+
* [Usage](#usage)
|
|
13
|
+
* [generates a snapshot file that has a list or commands and flags in the current CLI or plugin](#generates-a-snapshot-file-that-has-a-list-or-commands-and-flags-in-the-current-cli-or-plugin)
|
|
14
|
+
* [compare the current CLI or plugin commands and flags with a snapshot file to identify changes](#compare-the-current-cli-or-plugin-commands-and-flags-with-a-snapshot-file-to-identify-changes)
|
|
15
|
+
<!-- tocstop -->
|
|
16
|
+
|
|
17
|
+
# Usage
|
|
18
|
+
This plugin is used to take snapshot of commands and flags in OCLIF plugins. These snapshots can be used to enforce a deprecation policy or to keep track of changes.
|
|
19
|
+
|
|
20
|
+
To include it in your plugin, add it to the `devPlugins` section in your package.json.
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
"oclif": {
|
|
24
|
+
...
|
|
25
|
+
"devPlugins": [
|
|
26
|
+
"@oclif/plugin-command-snapshot"
|
|
27
|
+
...
|
|
28
|
+
]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Use the `snapshot:generate` command in your development process.
|
|
32
|
+
|
|
33
|
+
```sh-session
|
|
34
|
+
# generates a snapshot file that has a list or commands and flags in the current CLI or plugin
|
|
35
|
+
$ ./bin/run snapshot:generate
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Use the `snapshot:compare` command in your continuos integration. This command will fail if changes are detected. The snapshot file should be updated and committed to the repository when legitimate changes are made.
|
|
39
|
+
|
|
40
|
+
```sh-session
|
|
41
|
+
# compare the current CLI or plugin commands and flags with a snapshot file to identify changes
|
|
42
|
+
$ ./bin/run snapshot:compare
|
|
43
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Operation } from 'just-diff';
|
|
2
|
+
import { SnapshotCommand } from '../../snapshot-command';
|
|
3
|
+
export declare type SchemaComparison = Array<{
|
|
4
|
+
op: Operation;
|
|
5
|
+
path: (string | number)[];
|
|
6
|
+
value: any;
|
|
7
|
+
}>;
|
|
8
|
+
export default class SchemaCompare extends SnapshotCommand {
|
|
9
|
+
static flags: {
|
|
10
|
+
filepath: import("@oclif/core/lib/interfaces").OptionFlag<string>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<SchemaComparison>;
|
|
13
|
+
private readExistingSchema;
|
|
14
|
+
private generateLatestSchema;
|
|
15
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const semver = require("semver");
|
|
6
|
+
const _ = require("lodash");
|
|
7
|
+
const just_diff_1 = require("just-diff");
|
|
8
|
+
const core_1 = require("@oclif/core");
|
|
9
|
+
const snapshot_command_1 = require("../../snapshot-command");
|
|
10
|
+
const generate_1 = require("./generate");
|
|
11
|
+
const chalk_1 = require("chalk");
|
|
12
|
+
class SchemaCompare extends snapshot_command_1.SnapshotCommand {
|
|
13
|
+
async run() {
|
|
14
|
+
const { flags } = await this.parse(SchemaCompare);
|
|
15
|
+
try {
|
|
16
|
+
fs.accessSync(flags.filepath);
|
|
17
|
+
}
|
|
18
|
+
catch (_a) {
|
|
19
|
+
this.log(`${flags.filepath} not found.`);
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
const existingSchema = this.readExistingSchema(flags.filepath);
|
|
23
|
+
const latestSchema = await this.generateLatestSchema();
|
|
24
|
+
const changes = just_diff_1.diff(latestSchema, existingSchema);
|
|
25
|
+
if (changes.length === 0) {
|
|
26
|
+
this.log('No changes have been detected.');
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
const humandReadableChanges = {};
|
|
30
|
+
for (const change of changes) {
|
|
31
|
+
const objPath = change.path.join('.');
|
|
32
|
+
const existing = _.get(existingSchema, objPath);
|
|
33
|
+
const latest = _.get(latestSchema, objPath);
|
|
34
|
+
const [commandId] = objPath.split('.definitions');
|
|
35
|
+
const readablePath = objPath.replace(`${commandId}.`, '');
|
|
36
|
+
if (!humandReadableChanges[commandId]) {
|
|
37
|
+
humandReadableChanges[commandId] = [];
|
|
38
|
+
}
|
|
39
|
+
switch (change.op) {
|
|
40
|
+
case 'replace':
|
|
41
|
+
humandReadableChanges[commandId].push(`${chalk_1.underline(readablePath)} was changed from ${chalk_1.cyan(existing)} to ${chalk_1.cyan(latest)}`);
|
|
42
|
+
break;
|
|
43
|
+
case 'add':
|
|
44
|
+
humandReadableChanges[commandId].push(`${chalk_1.underline(readablePath)} was ${chalk_1.cyan('added')} to latest schema`);
|
|
45
|
+
break;
|
|
46
|
+
case 'remove':
|
|
47
|
+
humandReadableChanges[commandId].push(`${chalk_1.underline(readablePath)} was ${chalk_1.cyan('removed')} from latest schema`);
|
|
48
|
+
break;
|
|
49
|
+
default:
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
this.log();
|
|
54
|
+
this.log(chalk_1.bold(chalk_1.red('Found the following schema changes:')));
|
|
55
|
+
for (const [commandId, changes] of Object.entries(humandReadableChanges)) {
|
|
56
|
+
this.log();
|
|
57
|
+
this.log(chalk_1.bold(commandId));
|
|
58
|
+
for (const change of changes) {
|
|
59
|
+
this.log(` - ${change}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
this.log();
|
|
63
|
+
this.log('If intended, please update the schema file(s) and run again.');
|
|
64
|
+
process.exitCode = 1;
|
|
65
|
+
return changes;
|
|
66
|
+
}
|
|
67
|
+
readExistingSchema(filePath) {
|
|
68
|
+
const contents = fs.readdirSync(filePath);
|
|
69
|
+
const folderIsVersioned = contents.every(c => semver.valid(c));
|
|
70
|
+
const schemasDir = folderIsVersioned ? path.join(filePath, semver.rsort(contents)[0] || '') : filePath;
|
|
71
|
+
const schemaFiles = fs.readdirSync(schemasDir).map(f => path.join(schemasDir, f)).filter(f => !fs.statSync(f).isDirectory());
|
|
72
|
+
let schemas = {};
|
|
73
|
+
if (schemaFiles.length === 1 && schemaFiles[0].endsWith('schema.json')) {
|
|
74
|
+
schemas = JSON.parse(fs.readFileSync(schemaFiles[0]).toString('utf8'));
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
for (const file of schemaFiles) {
|
|
78
|
+
const schema = JSON.parse(fs.readFileSync(file).toString('utf8'));
|
|
79
|
+
schemas[path.basename(file.replace(/-/g, ':')).replace('.json', '')] = schema;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return schemas;
|
|
83
|
+
}
|
|
84
|
+
async generateLatestSchema() {
|
|
85
|
+
const generator = new generate_1.SchemaGenerator(this);
|
|
86
|
+
return generator.generate();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.default = SchemaCompare;
|
|
90
|
+
SchemaCompare.flags = {
|
|
91
|
+
filepath: core_1.Flags.string({
|
|
92
|
+
description: 'path of the generated snapshot file',
|
|
93
|
+
default: './schemas',
|
|
94
|
+
}),
|
|
95
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Schema } from 'ts-json-schema-generator';
|
|
2
|
+
import { SnapshotCommand } from '../../snapshot-command';
|
|
3
|
+
export declare type SchemasMap = {
|
|
4
|
+
[key: string]: Schema;
|
|
5
|
+
};
|
|
6
|
+
export declare type GenerateResponse = string[];
|
|
7
|
+
export declare class SchemaGenerator {
|
|
8
|
+
private base;
|
|
9
|
+
private ignorevoid;
|
|
10
|
+
private classToId;
|
|
11
|
+
constructor(base: SnapshotCommand, ignorevoid?: boolean);
|
|
12
|
+
generate(): Promise<SchemasMap>;
|
|
13
|
+
private getAllCmdFiles;
|
|
14
|
+
private parseFile;
|
|
15
|
+
private validateReturnType;
|
|
16
|
+
private determineCommandId;
|
|
17
|
+
}
|
|
18
|
+
export default class SchemaGenerate extends SnapshotCommand {
|
|
19
|
+
static flags: {
|
|
20
|
+
filepath: import("@oclif/core/lib/interfaces").OptionFlag<string>;
|
|
21
|
+
singlefile: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
22
|
+
ignorevoid: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
23
|
+
};
|
|
24
|
+
run(): Promise<GenerateResponse>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SchemaGenerator = void 0;
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const core_1 = require("@oclif/core");
|
|
7
|
+
const ts_json_schema_generator_1 = require("ts-json-schema-generator");
|
|
8
|
+
const snapshot_command_1 = require("../../snapshot-command");
|
|
9
|
+
const chalk_1 = require("chalk");
|
|
10
|
+
class SchemaGenerator {
|
|
11
|
+
constructor(base, ignorevoid = true) {
|
|
12
|
+
this.base = base;
|
|
13
|
+
this.ignorevoid = ignorevoid;
|
|
14
|
+
this.classToId = {};
|
|
15
|
+
}
|
|
16
|
+
async generate() {
|
|
17
|
+
for (const cmd of this.base.commands) {
|
|
18
|
+
// eslint-disable-next-line no-await-in-loop
|
|
19
|
+
const loadedCmd = await cmd.load(); // commands are loaded async in oclif/core
|
|
20
|
+
this.classToId[loadedCmd.name] = loadedCmd.id;
|
|
21
|
+
}
|
|
22
|
+
const schemas = {};
|
|
23
|
+
for (const file of this.getAllCmdFiles()) {
|
|
24
|
+
const { returnType, commandId } = this.parseFile(file);
|
|
25
|
+
if (this.ignorevoid && returnType === 'void')
|
|
26
|
+
continue;
|
|
27
|
+
try {
|
|
28
|
+
const config = {
|
|
29
|
+
path: file,
|
|
30
|
+
type: returnType,
|
|
31
|
+
skipTypeCheck: true,
|
|
32
|
+
};
|
|
33
|
+
const schema = ts_json_schema_generator_1.createGenerator(config).createSchema(config.type);
|
|
34
|
+
schemas[commandId] = schema;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error.message.toLowerCase().includes('no root type')) {
|
|
38
|
+
throw new Error(`Schema generator could not find the ${chalk_1.red(returnType)} type. Please make sure that ${chalk_1.red(returnType)} is exported.`);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return schemas;
|
|
46
|
+
}
|
|
47
|
+
getAllCmdFiles(dirPath = path.join(this.base.config.root, 'src', 'commands'), allFiles = []) {
|
|
48
|
+
const files = fs.readdirSync(dirPath);
|
|
49
|
+
files.forEach(file => {
|
|
50
|
+
const fPath = path.join(dirPath, file);
|
|
51
|
+
if (fs.statSync(fPath).isDirectory()) {
|
|
52
|
+
allFiles = this.getAllCmdFiles(fPath, allFiles);
|
|
53
|
+
}
|
|
54
|
+
else if (file.endsWith('ts')) {
|
|
55
|
+
allFiles.push(fPath);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
return allFiles;
|
|
59
|
+
}
|
|
60
|
+
parseFile(file) {
|
|
61
|
+
const returnTypeRegex = /(?<=async\srun\(\):\sPromise<)(.*?)(>*)(?=>)/g;
|
|
62
|
+
const contents = fs.readFileSync(file, 'utf8');
|
|
63
|
+
const [returnType] = returnTypeRegex.exec(contents) || [];
|
|
64
|
+
if (!returnType) {
|
|
65
|
+
throw new Error(`No return type found for file ${file}`);
|
|
66
|
+
}
|
|
67
|
+
const commandId = this.determineCommandId(contents);
|
|
68
|
+
if (!commandId) {
|
|
69
|
+
throw new Error(`No commandId found for file ${file}`);
|
|
70
|
+
}
|
|
71
|
+
this.validateReturnType(returnType, commandId);
|
|
72
|
+
return { returnType, commandId };
|
|
73
|
+
}
|
|
74
|
+
validateReturnType(returnType, commandId) {
|
|
75
|
+
const notAllowed = this.ignorevoid ? ['any', 'unknown'] : ['any', 'unknown', 'void'];
|
|
76
|
+
const vaugeTypes = ['JsonMap', 'JsonCollection', 'AnyJson'];
|
|
77
|
+
if (notAllowed.includes(returnType)) {
|
|
78
|
+
throw new Error(`${returnType} (from ${commandId}) is not allowed. Please use a more specific type.`);
|
|
79
|
+
}
|
|
80
|
+
else if (vaugeTypes.includes(returnType)) {
|
|
81
|
+
throw new Error(`${returnType} (from ${commandId}) is too vauge. Please use a more specific type.`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
determineCommandId(contents) {
|
|
85
|
+
for (const [className, cmdId] of Object.entries(this.classToId)) {
|
|
86
|
+
const regex = new RegExp(` class ${className} `, 'g');
|
|
87
|
+
if (regex.test(contents)) {
|
|
88
|
+
return cmdId;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.SchemaGenerator = SchemaGenerator;
|
|
94
|
+
class SchemaGenerate extends snapshot_command_1.SnapshotCommand {
|
|
95
|
+
async run() {
|
|
96
|
+
const { flags } = await this.parse(SchemaGenerate);
|
|
97
|
+
const generator = new SchemaGenerator(this, flags.ignorevoid);
|
|
98
|
+
const schemas = await generator.generate();
|
|
99
|
+
const directory = flags.filepath.replace('{version}', this.config.version);
|
|
100
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
101
|
+
const files = [];
|
|
102
|
+
if (flags.singlefile) {
|
|
103
|
+
const filePath = path.join(directory, 'schema.json');
|
|
104
|
+
fs.writeFileSync(filePath, JSON.stringify(schemas, null, 2));
|
|
105
|
+
this.log(`Generated JSON schema file "${filePath}"`);
|
|
106
|
+
files.push(filePath);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
for (const [cmdId, schema] of Object.entries(schemas)) {
|
|
110
|
+
const fileName = `${cmdId.replace(/:/g, '-')}.json`;
|
|
111
|
+
const filePath = path.join(directory, fileName);
|
|
112
|
+
fs.writeFileSync(filePath, JSON.stringify(schema, null, 2));
|
|
113
|
+
this.log(`Generated JSON schema file "${filePath}"`);
|
|
114
|
+
files.push(filePath);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return files;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
exports.default = SchemaGenerate;
|
|
121
|
+
SchemaGenerate.flags = {
|
|
122
|
+
filepath: core_1.Flags.string({
|
|
123
|
+
description: 'directory to save the generated schema files; can use "{version}" to insert the current CLI/plugin version',
|
|
124
|
+
default: './schemas',
|
|
125
|
+
}),
|
|
126
|
+
singlefile: core_1.Flags.boolean({
|
|
127
|
+
description: 'put generated schema into a single file',
|
|
128
|
+
default: false,
|
|
129
|
+
}),
|
|
130
|
+
ignorevoid: core_1.Flags.boolean({
|
|
131
|
+
description: 'ignore commands that return void',
|
|
132
|
+
default: true,
|
|
133
|
+
}),
|
|
134
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { SnapshotCommand, SnapshotEntry } from '../../snapshot-command';
|
|
2
|
+
interface Change {
|
|
3
|
+
name: string;
|
|
4
|
+
removed?: boolean;
|
|
5
|
+
added?: boolean;
|
|
6
|
+
}
|
|
7
|
+
declare type CommandChange = {
|
|
8
|
+
plugin: string;
|
|
9
|
+
flags: Change[];
|
|
10
|
+
} & Change;
|
|
11
|
+
export declare type CompareResponse = {
|
|
12
|
+
addedCommands?: string[];
|
|
13
|
+
removedCommands?: string[];
|
|
14
|
+
removedFlags?: string[];
|
|
15
|
+
diffCommands?: CommandChange[];
|
|
16
|
+
};
|
|
17
|
+
export default class Compare extends SnapshotCommand {
|
|
18
|
+
static flags: {
|
|
19
|
+
filepath: import("@oclif/core/lib/interfaces").OptionFlag<string>;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Compare a snapshot with the current commands
|
|
23
|
+
* @param {CommandChange[]} initialCommands Command list from the snapshot
|
|
24
|
+
* @param {CommandChange[]} updatedCommands Command list from runtime
|
|
25
|
+
* @returns all the command differences
|
|
26
|
+
*/
|
|
27
|
+
compareSnapshot(initialCommands: SnapshotEntry[], updatedCommands: CommandChange[]): Promise<CompareResponse>;
|
|
28
|
+
/**
|
|
29
|
+
* Compares a flag snapshot with the current command's flags
|
|
30
|
+
* @param {string[]} initialFlags Flag list from the snapshot
|
|
31
|
+
* @param {string[]} updatedFlags Flag list from runtime
|
|
32
|
+
* @param {string} initialCommand Command the flags belong to
|
|
33
|
+
* @return {boolean} true if no changes, false otherwise
|
|
34
|
+
*/
|
|
35
|
+
diffCommandFlags(initialFlags: string[], updatedFlags: Change[]): {
|
|
36
|
+
addedFlags: string[];
|
|
37
|
+
removedFlags: string[];
|
|
38
|
+
updatedFlags: Change[];
|
|
39
|
+
changedFlags: Change[];
|
|
40
|
+
};
|
|
41
|
+
get changed(): CommandChange[];
|
|
42
|
+
run(): Promise<CompareResponse>;
|
|
43
|
+
}
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const _ = require("lodash");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const os_1 = require("os");
|
|
7
|
+
const snapshot_command_1 = require("../../snapshot-command");
|
|
8
|
+
const chalk = require("chalk");
|
|
9
|
+
class Compare extends snapshot_command_1.SnapshotCommand {
|
|
10
|
+
/**
|
|
11
|
+
* Compare a snapshot with the current commands
|
|
12
|
+
* @param {CommandChange[]} initialCommands Command list from the snapshot
|
|
13
|
+
* @param {CommandChange[]} updatedCommands Command list from runtime
|
|
14
|
+
* @returns all the command differences
|
|
15
|
+
*/
|
|
16
|
+
async compareSnapshot(initialCommands, updatedCommands) {
|
|
17
|
+
const removedCommands = [];
|
|
18
|
+
const diffCommands = [];
|
|
19
|
+
initialCommands.forEach(initialCommand => {
|
|
20
|
+
const updatedCommand = updatedCommands.find(updatedCommand => {
|
|
21
|
+
// Protect against old snapshot files that don't have the plugin entry filled out.
|
|
22
|
+
const samePlugin = initialCommand.plugin ? initialCommand.plugin === updatedCommand.plugin : true;
|
|
23
|
+
return initialCommand.command === updatedCommand.name && samePlugin;
|
|
24
|
+
});
|
|
25
|
+
if (updatedCommand) {
|
|
26
|
+
const { changedFlags } = this.diffCommandFlags(initialCommand.flags, updatedCommand.flags);
|
|
27
|
+
if (changedFlags.length > 0) {
|
|
28
|
+
diffCommands.push(updatedCommand);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
removedCommands.push(initialCommand.command);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
const initialCommandNames = initialCommands.map(initialCommand => initialCommand.command);
|
|
36
|
+
const updatedCommandNames = updatedCommands.map(updatedCommand => updatedCommand.name);
|
|
37
|
+
const addedCommands = _.difference(updatedCommandNames, initialCommandNames);
|
|
38
|
+
if (removedCommands.length === 0 && addedCommands.length === 0 && diffCommands.length === 0) {
|
|
39
|
+
this.log('No changes have been detected.');
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
// Fail the process since there are changes to the snapshot file
|
|
43
|
+
process.exitCode = 1;
|
|
44
|
+
this.log(`The following commands and flags have modified: (${chalk.green('+')} added, ${chalk.red('-')} removed)${os_1.EOL}`);
|
|
45
|
+
removedCommands.forEach(command => {
|
|
46
|
+
this.log(chalk.red(`\t-${command}`));
|
|
47
|
+
});
|
|
48
|
+
addedCommands.forEach(command => {
|
|
49
|
+
this.log(chalk.green(`\t+${command}`));
|
|
50
|
+
});
|
|
51
|
+
const removedFlags = [];
|
|
52
|
+
if (diffCommands.length > 0) {
|
|
53
|
+
diffCommands.forEach(command => {
|
|
54
|
+
this.log(`\t ${command.name}`);
|
|
55
|
+
command.flags.forEach(flag => {
|
|
56
|
+
if (flag.added || flag.removed) {
|
|
57
|
+
const color = flag.added ? chalk.green : chalk.red;
|
|
58
|
+
this.log(color(`\t\t${flag.added ? '+' : '-'}${flag.name}`));
|
|
59
|
+
}
|
|
60
|
+
if (flag.removed)
|
|
61
|
+
removedFlags.push();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
this.log(`${os_1.EOL}Command or flag differences detected. If intended, please update the snapshot file and run again.`);
|
|
66
|
+
// Check if existent commands have been deleted
|
|
67
|
+
if (removedCommands.length > 0 || removedFlags.length > 0) {
|
|
68
|
+
this.log(chalk.red(`${os_1.EOL}Since there are deletions, a major version bump is required.`));
|
|
69
|
+
}
|
|
70
|
+
return { addedCommands, removedCommands, removedFlags, diffCommands };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Compares a flag snapshot with the current command's flags
|
|
74
|
+
* @param {string[]} initialFlags Flag list from the snapshot
|
|
75
|
+
* @param {string[]} updatedFlags Flag list from runtime
|
|
76
|
+
* @param {string} initialCommand Command the flags belong to
|
|
77
|
+
* @return {boolean} true if no changes, false otherwise
|
|
78
|
+
*/
|
|
79
|
+
diffCommandFlags(initialFlags, updatedFlags) {
|
|
80
|
+
const updatedFlagNames = updatedFlags.map(updatedFlag => updatedFlag.name);
|
|
81
|
+
const addedFlags = _.difference(updatedFlagNames, initialFlags);
|
|
82
|
+
const removedFlags = _.difference(initialFlags, updatedFlagNames);
|
|
83
|
+
const changedFlags = [];
|
|
84
|
+
updatedFlags.forEach(updatedFlag => {
|
|
85
|
+
if (addedFlags.includes(updatedFlag.name)) {
|
|
86
|
+
updatedFlag.added = true;
|
|
87
|
+
changedFlags.push(updatedFlag);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
removedFlags.forEach(removedFlag => {
|
|
91
|
+
changedFlags.push({ name: removedFlag, removed: true });
|
|
92
|
+
// The removed flags in not included in the updated flags, but we want it to
|
|
93
|
+
// so it shows removed.
|
|
94
|
+
updatedFlags.push({ name: removedFlag, removed: true });
|
|
95
|
+
});
|
|
96
|
+
return { addedFlags, removedFlags, updatedFlags, changedFlags };
|
|
97
|
+
}
|
|
98
|
+
get changed() {
|
|
99
|
+
return this.commands.map(command => {
|
|
100
|
+
return {
|
|
101
|
+
name: command.id,
|
|
102
|
+
plugin: command.pluginName || '',
|
|
103
|
+
flags: Object.entries(command.flags).map(flagName => ({ name: flagName[0] })),
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async run() {
|
|
108
|
+
const { flags } = await this.parse(Compare);
|
|
109
|
+
const oldCommandFlags = JSON.parse(fs.readFileSync(flags.filepath).toString('utf8'));
|
|
110
|
+
const resultnewCommandFlags = this.changed;
|
|
111
|
+
return this.compareSnapshot(oldCommandFlags, resultnewCommandFlags);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
exports.default = Compare;
|
|
115
|
+
Compare.flags = {
|
|
116
|
+
filepath: core_1.Flags.string({
|
|
117
|
+
description: 'path of the generated snapshot file',
|
|
118
|
+
default: './command-snapshot.json',
|
|
119
|
+
}),
|
|
120
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { SnapshotCommand } from '../../snapshot-command';
|
|
2
|
+
export declare type Snapshots = {
|
|
3
|
+
command: string;
|
|
4
|
+
plugin: string;
|
|
5
|
+
flags: string[];
|
|
6
|
+
}[];
|
|
7
|
+
export default class Generate extends SnapshotCommand {
|
|
8
|
+
static flags: {
|
|
9
|
+
filepath: import("@oclif/core/lib/interfaces").OptionFlag<string>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<Snapshots>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const snapshot_command_1 = require("../../snapshot-command");
|
|
6
|
+
class Generate extends snapshot_command_1.SnapshotCommand {
|
|
7
|
+
async run() {
|
|
8
|
+
const numberOfSpaceChar = 4;
|
|
9
|
+
const { flags } = await this.parse(Generate);
|
|
10
|
+
const resultCommands = this.entries;
|
|
11
|
+
const filePath = flags.filepath.replace('{version}', this.config.version);
|
|
12
|
+
fs.writeFileSync(filePath, JSON.stringify(resultCommands, null, numberOfSpaceChar));
|
|
13
|
+
this.log(`Generated snapshot file "${filePath}"`);
|
|
14
|
+
return resultCommands;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.default = Generate;
|
|
18
|
+
Generate.flags = {
|
|
19
|
+
filepath: core_1.Flags.string({
|
|
20
|
+
description: 'path to save the generated snapshot file; can use "{version}" to replace the current CLI/plugin version',
|
|
21
|
+
default: './command-snapshot.json',
|
|
22
|
+
}),
|
|
23
|
+
};
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export declare type SnapshotEntry = {
|
|
3
|
+
command: string;
|
|
4
|
+
plugin: string;
|
|
5
|
+
flags: string[];
|
|
6
|
+
};
|
|
7
|
+
export declare abstract class SnapshotCommand extends Command {
|
|
8
|
+
get commands(): import("@oclif/core/lib/interfaces").Command.Plugin[];
|
|
9
|
+
get entries(): SnapshotEntry[];
|
|
10
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SnapshotCommand = void 0;
|
|
4
|
+
const core_1 = require("@oclif/core");
|
|
5
|
+
const _ = require("lodash");
|
|
6
|
+
class SnapshotCommand extends core_1.Command {
|
|
7
|
+
get commands() {
|
|
8
|
+
var _a;
|
|
9
|
+
const devPlugins = (_a = this.config.pjson.oclif.devPlugins) !== null && _a !== void 0 ? _a : [];
|
|
10
|
+
const commands = this.config.commands
|
|
11
|
+
// Ignore dev plugins
|
|
12
|
+
.filter(command => { var _a; return !devPlugins.includes((_a = command.pluginName) !== null && _a !== void 0 ? _a : ''); });
|
|
13
|
+
return _.sortBy(commands, 'id');
|
|
14
|
+
}
|
|
15
|
+
get entries() {
|
|
16
|
+
return this.commands.map(command => {
|
|
17
|
+
return {
|
|
18
|
+
command: command.id,
|
|
19
|
+
plugin: command.pluginName,
|
|
20
|
+
flags: Object.keys(command.flags).sort(),
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.SnapshotCommand = SnapshotCommand;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":"3.0.0","commands":{"schema:compare":{"id":"schema:compare","strict":true,"pluginName":"@oclif/plugin-command-snapshot","pluginAlias":"@oclif/plugin-command-snapshot","pluginType":"core","aliases":[],"flags":{"filepath":{"name":"filepath","type":"option","description":"path of the generated snapshot file","multiple":false,"default":"./schemas"}},"args":[]},"schema:generate":{"id":"schema:generate","strict":true,"pluginName":"@oclif/plugin-command-snapshot","pluginAlias":"@oclif/plugin-command-snapshot","pluginType":"core","aliases":[],"flags":{"filepath":{"name":"filepath","type":"option","description":"directory to save the generated schema files; can use \"{version}\" to insert the current CLI/plugin version","multiple":false,"default":"./schemas"},"singlefile":{"name":"singlefile","type":"boolean","description":"put generated schema into a single file","allowNo":false},"ignorevoid":{"name":"ignorevoid","type":"boolean","description":"ignore commands that return void","allowNo":false}},"args":[]},"snapshot:compare":{"id":"snapshot:compare","strict":true,"pluginName":"@oclif/plugin-command-snapshot","pluginAlias":"@oclif/plugin-command-snapshot","pluginType":"core","aliases":[],"flags":{"filepath":{"name":"filepath","type":"option","description":"path of the generated snapshot file","multiple":false,"default":"./command-snapshot.json"}},"args":[]},"snapshot:generate":{"id":"snapshot:generate","strict":true,"pluginName":"@oclif/plugin-command-snapshot","pluginAlias":"@oclif/plugin-command-snapshot","pluginType":"core","aliases":[],"flags":{"filepath":{"name":"filepath","type":"option","description":"path to save the generated snapshot file; can use \"{version}\" to replace the current CLI/plugin version","multiple":false,"default":"./command-snapshot.json"}},"args":[]}}}
|