Skip to content

Instantly share code, notes, and snippets.

@ottomata
Created October 27, 2022 17:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ottomata/e21aaae6fd1be3f58ab59341a79cc2d7 to your computer and use it in GitHub Desktop.
Save ottomata/e21aaae6fd1be3f58ab59341a79cc2d7 to your computer and use it in GitHub Desktop.
// Quick and hacky script that will use dyff to show the diff between
// Any modified materialized schema version and its previous version.
//
// Defaults to using https://github.com/homeport/dyff, so install that first.
// This could be cleaned up and incorporated into jsonschema-tools itself, and
// then shown in CI.
//
jsonschema_tools = require('@wikimedia/jsonschema-tools');
const _ = require('lodash');
path = require('path');
const util = require('util');
const exec = util.promisify(require('child_process').exec);
/**
* Runs (and logs) command in cwd.
* @param {string} command
* @param {string} execOptions options to pass to child_process.exec
* @param {Object} logger If given, will call logger.debug(command) before executing it.
* @return {Promise} result child_process#exec
*/
function execCommand(command, execOptions, logger) {
if (execOptions) {
if (logger) {
logger.debug(`Running: \`${command}\` with `, execOptions);
}
return exec(command, execOptions);
} else {
if (logger) {
logger.debug(`Running: \`${command}\``);
}
return exec(command);
}
}
/**
* Finds the git root path relative to options.schemaBasePath
* @param {Object} options
* @return {string}
*/
async function findGitRoot(options = {}) {
options = jsonschema_tools.readConfig(options);
return (await execCommand(
// Need to execute the git command in the schemaBasePath for it to find the
// .git directory somewhere above schemaBasePath
'git rev-parse --show-toplevel', { cwd: options.schemaBasePath }, options.log
)).stdout.trim();
}
/**
* @param {Object} options
* @return {Array<string>}
*/
async function gitChangedMaterializedSchemaPaths(options = {}) {
options = jsonschema_tools.readConfig(options);
const gitRoot = await findGitRoot(options);
const execOptions = { cwd: options.schemaBasePath };
const command = `git diff --name-only --diff-filter=ACM HEAD^`;
const modifiedFiles = (await execCommand(command, execOptions, options.log)).stdout.trim().split('\n');
return _.filter(modifiedFiles, file => {
file_basename = path.basename(file);
return path.extname(file_basename) === '.' + options.contentTypes[0] &&
file_basename !== options.currentName &&
file_basename != `latest.${options.contentTypes[0]}`;
});
}
async function diffSchemas(sourceSchemaPath, destSchemaPath, options = {
diffCommand: 'dyff --color on between'
}) {
options = jsonschema_tools.readConfig(options);
const execOptions = { cwd: options.schemaBasePath };
const diffCommand = options.diffCommand
const out = await execCommand(
`${diffCommand} ${sourceSchemaPath} ${destSchemaPath}`,
execOptions,
options.log
);
return out.stdout;
}
async function printMaterializedDiffGitHead(options={}) {
options = jsonschema_tools.readConfig(options);
schemasChanged = await gitChangedMaterializedSchemaPaths(options);
console.log(schemasChanged);
allSchemasByTitle = await jsonschema_tools.findSchemasByTitle(options);
filteredAllSchemasByTitle = {};
Object.keys(allSchemasByTitle).forEach( title => {
filtered = allSchemasByTitle[title].filter( si => {
file = si.path;
return path.extname(path.basename(file)) === '.' + options.contentTypes[0] && path.basename(file) !== options.currentName
});
if (filtered.length > 0) {
filteredAllSchemasByTitle[title] = filtered;
}
});
Object.keys(filteredAllSchemasByTitle).forEach(async (title) => {
schemas = filteredAllSchemasByTitle[title];
for (let i = 0; i < schemas.length -1; i++) {
prevSI = schemas[i];
nextSI = schemas[i+1];
// console.log(nextSI.path);
if (schemasChanged.includes(nextSI.path)) {
diff = await diffSchemas(prevSI.path, nextSI.path)
console.log(diff);
}
};
});
}
printMaterializedDiffGitHead();
@ottomata
Copy link
Author

Example:

     _        __  __
   _| |_   _ / _|/ _|  between jsonschema/fragment/common/1.1.0.yaml
 / _' | | | | |_| |_       and jsonschema/fragment/common/2.0.0.yaml
| (_| | |_| |  _|  _|
 \__,_|\__, |_| |_|   returned seven differences
        |___/

$id
  ± value change
    - /fragment/common/1.1.0
    + /fragment/common/2.0.0

required
  + one list entry added:
    - dt

properties
  + one map entry added:
    dt:
    │ type: string
    │ description: |
    │ │ ISO-8601 formatted timestamp of when the event occurred/was generated in UTC), AKA 'event time'. This is different than meta.dt, which is used as the time the system received this event.
    │ │
    │ format: date-time
    │ maxLength: 128



properties.meta.required
  - one list entry removed:
    - dt

properties.meta.properties.dt.description
  ± value change
    - UTC event datetime, in ISO-8601 format
    + Time the event was received by the system, in UTC ISO-8601 format

examples.0
  + one map entry added:
    dt: "2021-01-01T00:00:00.0Z"

examples.0.$schema
  ± value change
    - /fragment/common/1.1.0
    + /fragment/common/2.0.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment