🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@oclif/plugin-command-snapshot

Package Overview
Dependencies
Maintainers
5
Versions
163
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@oclif/plugin-command-snapshot - npm Package Compare versions

Comparing version
2.1.0
to
2.1.1
+16
lib/commands/schema/compare.d.ts
import { Operation } from 'just-diff';
import { flags } from '@oclif/command';
import { SnapshotCommand } from '../../snapshot-command';
export declare type SchemaComparison = Array<{
op: Operation;
path: (string | number)[];
value: any;
}>;
export default class SchemaCompare extends SnapshotCommand {
static flags: {
filepath: flags.IOptionFlag<string>;
};
run(): Promise<SchemaComparison>;
private readExistingSchema;
private generateLatestSchema;
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const path = require("path");
const fs = require("fs");
const semver = require("semver");
const _ = require("lodash");
const just_diff_1 = require("just-diff");
const command_1 = require("@oclif/command");
const snapshot_command_1 = require("../../snapshot-command");
const generate_1 = require("./generate");
const chalk_1 = require("chalk");
class SchemaCompare extends snapshot_command_1.SnapshotCommand {
async run() {
const { flags } = this.parse(SchemaCompare);
const existingSchema = this.readExistingSchema(flags.filepath);
const latestSchema = await this.generateLatestSchema();
const changes = just_diff_1.diff(latestSchema, existingSchema);
if (changes.length === 0) {
this.log('No changes have been detected.');
return [];
}
const humandReadableChanges = {};
for (const change of changes) {
const objPath = change.path.join('.');
const existing = _.get(existingSchema, objPath);
const latest = _.get(latestSchema, objPath);
const [commandId] = objPath.split('.definitions');
const readablePath = objPath.replace(`${commandId}.`, '');
if (!humandReadableChanges[commandId]) {
humandReadableChanges[commandId] = [];
}
switch (change.op) {
case 'replace':
humandReadableChanges[commandId].push(`${chalk_1.underline(readablePath)} was changed from ${chalk_1.cyan(existing)} to ${chalk_1.cyan(latest)}`);
break;
case 'add':
humandReadableChanges[commandId].push(`${chalk_1.underline(readablePath)} was ${chalk_1.cyan('added')} to latest schema`);
break;
case 'remove':
humandReadableChanges[commandId].push(`${chalk_1.underline(readablePath)} was ${chalk_1.cyan('removed')} from latest schema`);
break;
default:
break;
}
}
this.log();
this.log(chalk_1.bold(chalk_1.red('Found the following schema changes:')));
for (const [commandId, changes] of Object.entries(humandReadableChanges)) {
this.log();
this.log(chalk_1.bold(commandId));
for (const change of changes) {
this.log(` - ${change}`);
}
}
this.log();
this.log('If intended, please update the schema file(s) and run again.');
process.exitCode = 1;
return changes;
}
readExistingSchema(filePath) {
const contents = fs.readdirSync(filePath);
const folderIsVersioned = contents.every(c => semver.valid(c));
const schemasDir = folderIsVersioned ? path.join(filePath, semver.rsort(contents)[0]) : filePath;
const schemaFiles = fs.readdirSync(schemasDir).map(f => path.join(schemasDir, f)).filter(f => !fs.statSync(f).isDirectory());
let schemas = {};
if (schemaFiles.length === 1 && schemaFiles[0].endsWith('schema.json')) {
schemas = JSON.parse(fs.readFileSync(schemaFiles[0]).toString('utf8'));
}
else {
for (const file of schemaFiles) {
const schema = JSON.parse(fs.readFileSync(file).toString('utf8'));
schemas[path.basename(file.replace(/-/g, ':')).replace('.json', '')] = schema;
}
}
return schemas;
}
async generateLatestSchema() {
const generator = new generate_1.SchemaGenerator(this);
return generator.generate();
}
}
exports.default = SchemaCompare;
SchemaCompare.flags = {
filepath: command_1.flags.string({
description: 'path of the generated snapshot file',
default: './schemas',
}),
};
import { flags } from '@oclif/command';
import { Schema } from 'ts-json-schema-generator';
import { SnapshotCommand } from '../../snapshot-command';
export declare type SchemasMap = {
[key: string]: Schema;
};
export declare type GenerateResponse = string[];
export declare class SchemaGenerator {
private base;
private classToId;
constructor(base: SnapshotCommand);
generate(): Promise<SchemasMap>;
private getAllCmdFiles;
private parseFile;
private validateReturnType;
private determineCommandId;
}
export default class SchemaGenerate extends SnapshotCommand {
static flags: {
filepath: flags.IOptionFlag<string>;
singlefile: import("@oclif/parser/lib/flags").IBooleanFlag<boolean>;
};
run(): Promise<GenerateResponse>;
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SchemaGenerator = void 0;
const path = require("path");
const fs = require("fs");
const command_1 = require("@oclif/command");
const ts_json_schema_generator_1 = require("ts-json-schema-generator");
const snapshot_command_1 = require("../../snapshot-command");
const chalk_1 = require("chalk");
class SchemaGenerator {
constructor(base) {
this.base = base;
this.classToId = {};
}
async generate() {
for (const cmd of this.base.commands) {
// eslint-disable-next-line no-await-in-loop
const loadedCmd = await cmd.load(); // commands are loaded async in oclif/core
this.classToId[loadedCmd.name] = loadedCmd.id;
}
const schemas = {};
for (const file of this.getAllCmdFiles()) {
const { returnType, commandId } = this.parseFile(file);
try {
const config = {
path: file,
type: returnType,
skipTypeCheck: true,
};
const schema = ts_json_schema_generator_1.createGenerator(config).createSchema(config.type);
schemas[commandId] = schema;
}
catch (error) {
if (error.message.toLowerCase().includes('no root type')) {
throw new Error(`Schema generator could not find the ${chalk_1.red(returnType)} type. Please make sure that ${chalk_1.red(returnType)} is exported.`);
}
else {
throw error;
}
}
}
return schemas;
}
getAllCmdFiles(dirPath = path.join(this.base.config.root, 'src', 'commands'), allFiles = []) {
const files = fs.readdirSync(dirPath);
files.forEach(file => {
const fPath = path.join(dirPath, file);
if (fs.statSync(fPath).isDirectory()) {
allFiles = this.getAllCmdFiles(fPath, allFiles);
}
else if (file.endsWith('ts')) {
allFiles.push(fPath);
}
});
return allFiles;
}
parseFile(file) {
const returnTypeRegex = /(?<=async\srun\(\):\sPromise<)(.*?)(>*)(?=>)/g;
const contents = fs.readFileSync(file, 'utf8');
const [returnType] = returnTypeRegex.exec(contents) || [];
if (!returnType) {
throw new Error(`No return type found for file ${file}`);
}
this.validateReturnType(returnType);
const commandId = this.determineCommandId(contents);
if (!commandId) {
throw new Error(`No commandId found for file ${file}`);
}
return { returnType, commandId };
}
validateReturnType(returnType) {
const notAllowed = ['any', 'unknown'];
const vaugeTypes = ['JsonMap', 'JsonCollection', 'AnyJson'];
if (notAllowed.includes(returnType)) {
throw new Error(`${returnType} is not allowed. Please use a more specific type.`);
}
else if (vaugeTypes.includes(returnType)) {
throw new Error(`${returnType} is too vauge. Please use a more specific type.`);
}
}
determineCommandId(contents) {
for (const [className, cmdId] of Object.entries(this.classToId)) {
const regex = new RegExp(` class ${className} `, 'g');
if (regex.test(contents)) {
return cmdId;
}
}
}
}
exports.SchemaGenerator = SchemaGenerator;
class SchemaGenerate extends snapshot_command_1.SnapshotCommand {
async run() {
const { flags } = this.parse(SchemaGenerate);
const generator = new SchemaGenerator(this);
const schemas = await generator.generate();
const directory = flags.filepath.replace('{version}', this.config.version);
fs.mkdirSync(directory, { recursive: true });
const files = [];
if (flags.singlefile) {
const filePath = path.join(directory, 'schema.json');
fs.writeFileSync(filePath, JSON.stringify(schemas, null, 2));
this.log(`Generated JSON schema file "${filePath}"`);
files.push(filePath);
}
else {
for (const [cmdId, schema] of Object.entries(schemas)) {
const fileName = `${cmdId.replace(/:/g, '-')}.json`;
const filePath = path.join(directory, fileName);
fs.writeFileSync(filePath, JSON.stringify(schema, null, 2));
this.log(`Generated JSON schema file "${filePath}"`);
files.push(filePath);
}
}
return files;
}
}
exports.default = SchemaGenerate;
SchemaGenerate.flags = {
filepath: command_1.flags.string({
description: 'directory to save the generated schema files; can use "{version}" to insert the current CLI/plugin version',
default: './schemas',
}),
singlefile: command_1.flags.boolean({
description: 'put generated schema into a single file',
default: false,
}),
};
import { flags } from '@oclif/command';
import { SnapshotCommand, SnapshotEntry } from '../../snapshot-command';
interface Change {
name: string;
removed?: boolean;
added?: boolean;
}
declare type CommandChange = {
plugin: string;
flags: Change[];
} & Change;
export declare type CompareResponse = {
addedCommands?: string[];
removedCommands?: string[];
removedFlags?: string[];
diffCommands?: CommandChange[];
};
export default class Compare extends SnapshotCommand {
static flags: {
filepath: flags.IOptionFlag<string>;
};
/**
* Compare a snapshot with the current commands
* @param {CommandChange[]} initialCommands Command list from the snapshot
* @param {CommandChange[]} updatedCommands Command list from runtime
* @returns all the command differences
*/
compareSnapshot(initialCommands: SnapshotEntry[], updatedCommands: CommandChange[]): Promise<CompareResponse>;
/**
* Compares a flag snapshot with the current command's flags
* @param {string[]} initialFlags Flag list from the snapshot
* @param {string[]} updatedFlags Flag list from runtime
* @param {string} initialCommand Command the flags belong to
* @return {boolean} true if no changes, false otherwise
*/
diffCommandFlags(initialFlags: string[], updatedFlags: Change[]): {
addedFlags: string[];
removedFlags: string[];
updatedFlags: Change[];
changedFlags: Change[];
};
get changed(): CommandChange[];
run(): Promise<CompareResponse>;
}
export {};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const command_1 = require("@oclif/command");
const _ = require("lodash");
const fs = require("fs");
const os_1 = require("os");
const snapshot_command_1 = require("../../snapshot-command");
const chalk = require("chalk");
class Compare extends snapshot_command_1.SnapshotCommand {
/**
* Compare a snapshot with the current commands
* @param {CommandChange[]} initialCommands Command list from the snapshot
* @param {CommandChange[]} updatedCommands Command list from runtime
* @returns all the command differences
*/
async compareSnapshot(initialCommands, updatedCommands) {
const removedCommands = [];
const diffCommands = [];
initialCommands.forEach(initialCommand => {
const updatedCommand = updatedCommands.find(updatedCommand => {
// Protect against old snapshot files that don't have the plugin entry filled out.
const samePlugin = initialCommand.plugin ? initialCommand.plugin === updatedCommand.plugin : true;
return initialCommand.command === updatedCommand.name && samePlugin;
});
if (updatedCommand) {
const { changedFlags } = this.diffCommandFlags(initialCommand.flags, updatedCommand.flags);
if (changedFlags.length > 0) {
diffCommands.push(updatedCommand);
}
}
else {
removedCommands.push(initialCommand.command);
}
});
const initialCommandNames = initialCommands.map(initialCommand => initialCommand.command);
const updatedCommandNames = updatedCommands.map(updatedCommand => updatedCommand.name);
const addedCommands = _.difference(updatedCommandNames, initialCommandNames);
if (removedCommands.length === 0 && addedCommands.length === 0 && diffCommands.length === 0) {
this.log('No changes have been detected.');
return {};
}
// Fail the process since there are changes to the snapshot file
process.exitCode = 1;
this.log(`The following commands and flags have modified: (${chalk.green('+')} added, ${chalk.red('-')} removed)${os_1.EOL}`);
removedCommands.forEach(command => {
this.log(chalk.red(`\t-${command}`));
});
addedCommands.forEach(command => {
this.log(chalk.green(`\t+${command}`));
});
const removedFlags = [];
if (diffCommands.length > 0) {
diffCommands.forEach(command => {
this.log(`\t ${command.name}`);
command.flags.forEach(flag => {
if (flag.added || flag.removed) {
const color = flag.added ? chalk.green : chalk.red;
this.log(color(`\t\t${flag.added ? '+' : '-'}${flag.name}`));
}
if (flag.removed)
removedFlags.push();
});
});
}
this.log(`${os_1.EOL}Command or flag differences detected. If intended, please update the snapshot file and run again.`);
// Check if existent commands have been deleted
if (removedCommands.length > 0 || removedFlags.length > 0) {
this.log(chalk.red(`${os_1.EOL}Since there are deletions, a major version bump is required.`));
}
return { addedCommands, removedCommands, removedFlags, diffCommands };
}
/**
* Compares a flag snapshot with the current command's flags
* @param {string[]} initialFlags Flag list from the snapshot
* @param {string[]} updatedFlags Flag list from runtime
* @param {string} initialCommand Command the flags belong to
* @return {boolean} true if no changes, false otherwise
*/
diffCommandFlags(initialFlags, updatedFlags) {
const updatedFlagNames = updatedFlags.map(updatedFlag => updatedFlag.name);
const addedFlags = _.difference(updatedFlagNames, initialFlags);
const removedFlags = _.difference(initialFlags, updatedFlagNames);
const changedFlags = [];
updatedFlags.forEach(updatedFlag => {
if (addedFlags.includes(updatedFlag.name)) {
updatedFlag.added = true;
changedFlags.push(updatedFlag);
}
});
removedFlags.forEach(removedFlag => {
changedFlags.push({ name: removedFlag, removed: true });
// The removed flags in not included in the updated flags, but we want it to
// so it shows removed.
updatedFlags.push({ name: removedFlag, removed: true });
});
return { addedFlags, removedFlags, updatedFlags, changedFlags };
}
get changed() {
return this.commands.map(command => {
return {
name: command.id,
plugin: command.pluginName || '',
flags: Object.entries(command.flags).map(flagName => ({ name: flagName[0] })),
};
});
}
async run() {
const { flags } = this.parse(Compare);
const oldCommandFlags = JSON.parse(fs.readFileSync(flags.filepath).toString('utf8'));
const resultnewCommandFlags = this.changed;
return this.compareSnapshot(oldCommandFlags, resultnewCommandFlags);
}
}
exports.default = Compare;
Compare.flags = {
filepath: command_1.flags.string({
description: 'path of the generated snapshot file',
default: './command-snapshot.json',
}),
};
import { flags } from '@oclif/command';
import { SnapshotCommand } from '../../snapshot-command';
export declare type Snapshots = {
command: string;
plugin: string;
flags: string[];
}[];
export default class Generate extends SnapshotCommand {
static flags: {
filepath: flags.IOptionFlag<string>;
};
run(): Promise<Snapshots>;
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const command_1 = require("@oclif/command");
const fs = require("fs");
const snapshot_command_1 = require("../../snapshot-command");
class Generate extends snapshot_command_1.SnapshotCommand {
async run() {
const numberOfSpaceChar = 4;
const { flags } = this.parse(Generate);
const resultCommands = this.entries;
const filePath = flags.filepath.replace('{version}', this.config.version);
fs.writeFileSync(filePath, JSON.stringify(resultCommands, null, numberOfSpaceChar));
this.log(`Generated snapshot file "${filePath}"`);
return resultCommands;
}
}
exports.default = Generate;
Generate.flags = {
filepath: command_1.flags.string({
description: 'path to save the generated snapshot file; can use "{version}" to replace the current CLI/plugin version',
default: './command-snapshot.json',
}),
};
declare const _default: {};
export default _default;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {};
import { Command } from '@oclif/command';
export declare type SnapshotEntry = {
command: string;
plugin: string;
flags: string[];
};
export declare abstract class SnapshotCommand extends Command {
get commands(): import("@oclif/config").Command.Plugin[];
get entries(): SnapshotEntry[];
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SnapshotCommand = void 0;
const command_1 = require("@oclif/command");
const _ = require("lodash");
class SnapshotCommand extends command_1.Command {
get commands() {
var _a;
const devPlugins = (_a = this.config.pjson.oclif.devPlugins) !== null && _a !== void 0 ? _a : [];
const commands = this.config.commands
// Ignore dev plugins
.filter(command => { var _a; return !devPlugins.includes((_a = command.pluginName) !== null && _a !== void 0 ? _a : ''); });
return _.sortBy(commands, 'id');
}
get entries() {
return this.commands.map(command => {
return {
command: command.id,
plugin: command.pluginName,
flags: Object.keys(command.flags).sort(),
};
});
}
}
exports.SnapshotCommand = SnapshotCommand;
+7
-0

@@ -5,2 +5,9 @@ # Changelog

### [2.1.1](https://github.com/oclif/plugin-command-snapshot/compare/v2.1.0...v2.1.1) (2021-06-04)
### Bug Fixes
* ensure lib files are published ([c54b061](https://github.com/oclif/plugin-command-snapshot/commit/c54b06183aa39eaed60869421d3dd02e90fa27c0))
## [2.1.0](https://github.com/oclif/plugin-command-snapshot/compare/v2.0.0...v2.1.0) (2021-06-03)

@@ -7,0 +14,0 @@

+1
-1

@@ -1,1 +0,1 @@

{"version":"2.1.0","commands":{"schema:compare":{"id":"schema:compare","pluginName":"@oclif/plugin-command-snapshot","pluginType":"core","aliases":[],"flags":{"filepath":{"name":"filepath","type":"option","description":"path of the generated snapshot file","default":"./schemas"}},"args":[]},"schema:generate":{"id":"schema:generate","pluginName":"@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","default":"./schemas"},"singlefile":{"name":"singlefile","type":"boolean","description":"put generated schema into a single file","allowNo":false}},"args":[]},"snapshot:compare":{"id":"snapshot:compare","pluginName":"@oclif/plugin-command-snapshot","pluginType":"core","aliases":[],"flags":{"filepath":{"name":"filepath","type":"option","description":"path of the generated snapshot file","default":"./command-snapshot.json"}},"args":[]},"snapshot:generate":{"id":"snapshot:generate","pluginName":"@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","default":"./command-snapshot.json"}},"args":[]}}}
{"version":"2.1.1","commands":{"schema:compare":{"id":"schema:compare","pluginName":"@oclif/plugin-command-snapshot","pluginType":"core","aliases":[],"flags":{"filepath":{"name":"filepath","type":"option","description":"path of the generated snapshot file","default":"./schemas"}},"args":[]},"schema:generate":{"id":"schema:generate","pluginName":"@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","default":"./schemas"},"singlefile":{"name":"singlefile","type":"boolean","description":"put generated schema into a single file","allowNo":false}},"args":[]},"snapshot:compare":{"id":"snapshot:compare","pluginName":"@oclif/plugin-command-snapshot","pluginType":"core","aliases":[],"flags":{"filepath":{"name":"filepath","type":"option","description":"path of the generated snapshot file","default":"./command-snapshot.json"}},"args":[]},"snapshot:generate":{"id":"snapshot:generate","pluginName":"@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","default":"./command-snapshot.json"}},"args":[]}}}
{
"name": "@oclif/plugin-command-snapshot",
"description": "generates and compares OCLIF plugins snapshot files",
"version": "2.1.0",
"version": "2.1.1",
"author": "Ramyasri @nramyasri-sf",

@@ -6,0 +6,0 @@ "bugs": "https://github.com/oclif/plugin-command-snapshot/issues",