@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 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
+ [![oclif](https://img.shields.io/badge/cli-oclif-brightgreen.svg)](https://oclif.io)
7
+ [![Version](https://img.shields.io/npm/v/@oclif/plugin-command-snapshot.svg)](https://npmjs.org/package/@oclif/plugin-command-snapshot)
8
+ [![Downloads/week](https://img.shields.io/npm/dw/@oclif/plugin-command-snapshot.svg)](https://npmjs.org/package/@oclif/plugin-command-snapshot)
9
+ [![License](https://img.shields.io/npm/l/@oclif/plugin-command-snapshot.svg)](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
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;
package/lib/index.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = {};
@@ -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":[]}}}