Advertisement
Guest User

Untitled

a guest
Jun 24th, 2019
63
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.59 KB | None | 0 0
  1. #!/usr/bin/env node
  2. 'use strict';
  3.  
  4. const util = require('util');
  5. const _ = require('lodash');
  6. const yaml = require('js-yaml');
  7. const path = require('path');
  8. const glob = require('glob');
  9. const semver = require('semver');
  10. const NodeGit = require('nodegit');
  11. const fs = require("fs");
  12. const writeFile = util.promisify(fs.writeFile);
  13. const unlink = util.promisify(fs.unlink);
  14. const symlink = util.promisify(fs.symlink);
  15.  
  16. /**
  17. * Converts a utf-8 byte buffer or a YAML/JSON string into
  18. * an object and returns it.
  19. * @param {string|Buffer|Object} data
  20. * @return {Object}
  21. */
  22. function objectFactory(data) {
  23. // If we were given a byte Buffer, parse it as utf-8 string.
  24. if (data instanceof Buffer) {
  25. data = data.toString('utf-8');
  26. } else if (_.isObject(data)) {
  27. // if we were given a a JS object, return it now.
  28. return data;
  29. }
  30.  
  31. // If we now have a string, then assume it is a YAML/JSON string.
  32. if (_.isString(data)) {
  33. data = yaml.safeLoad(data);
  34. } else {
  35. throw new Error(
  36. 'Could not convert data into an object. ' +
  37. 'Data must be a utf-8 byte buffer or a YAML/JSON string'
  38. );
  39. }
  40.  
  41. return data;
  42. }
  43.  
  44.  
  45. function writeYamlFile(object, outputPath) {
  46. return writeFile(outputPath, yaml.dump(object));
  47. }
  48.  
  49. const defaultOptions = {
  50. gitReference: 'HEAD',
  51. gitReferenceType: NodeGit.Reference.TYPE.SYMBOLIC, // NodeGit.Reference.TYPE.
  52. shouldSymlink: true,
  53. // TODO add option to generate and expect JSON rather than yaml
  54. contentType: 'yaml',
  55. schemaVersionRegex: /.*\/(\d+\.\d+\.\d+).yaml$/
  56. };
  57.  
  58. class EventSchema {
  59. constructor(
  60. schemaDirectory,
  61. repository,
  62. options = {}
  63. ) {
  64. _.defaults(this, options, defaultOptions);
  65.  
  66. this.schemaDirectory = schemaDirectory;
  67. this.repository = repository;
  68.  
  69. // this.schemaVersionRegex = new RegExp(`(\d+\.\d+\.\d+)\.${this.contentType}$`);
  70. }
  71.  
  72. static async init(schemaDirectory) {
  73. const absoluteDir = path.resolve(schemaDirectory);
  74. const repo = await NodeGit.Repository.open(
  75. await NodeGit.Repository.discover(absoluteDir, 0, '/')
  76. );
  77. return new EventSchema(schemaDirectory, repo);
  78. }
  79.  
  80. // TODO: Can we create a function that inspects the diff of the schemas
  81. // auto generates a semver.inc 'release' argument value?
  82. // E.g. if only descriptions change, this can be 'patch'.
  83. // If fields added, this can be 'minor'.
  84. // If field types changed, this can be 'major'.
  85. // static releaseTypeOfChange(origSchema, newSchema) {
  86. // }
  87.  
  88. extractVersion(schemaPath) {
  89. const match = schemaPath.match(this.schemaVersionRegex);
  90. if (match) {
  91. return match[1];
  92. } else {
  93. return null;
  94. }
  95. }
  96.  
  97. // TODO: Is this useful? We could just always assume we should work with HEAD via repo.getHeadCommit()
  98. async getCommit() {
  99. if (this.gitReferenceType == NodeGit.Reference.TYPE.SYMBOLIC) {
  100. // If this is a valid name, it is something like HEAD or refs/heads/branchname
  101. if (NodeGit.Reference.isValidName(this.gitReference)) {
  102. const oid = await NodeGit.Reference.nameToId(this.repository, this.gitReference);
  103. return this.repository.getCommit(oid);
  104. } else {
  105. // assume this is a branch
  106. return this.repository.getBranchCommit(this.gitReference);
  107. }
  108. } else if (this.gitReferenceType == NodeGit.Reference.TYPE.OID) {
  109. // Else this is a sha commit id.
  110. return this.repository.getCommit(this.gitReference);
  111. } else {
  112. throw new Error(`invalid gitReferenceType ${this.gitReferenceType}`);
  113. }
  114. }
  115.  
  116. async schemaDirectoryEntries() {
  117. const commit = await this.getCommit();
  118. const tree = await NodeGit.Tree.lookup(this.repository, commit.treeId());
  119.  
  120. const schemaDirectoryTreeEntry = await tree.getEntry(this.schemaDirectory);
  121. if (!schemaDirectoryTreeEntry.isTree()) {
  122. throw new Error(`${this.schemaDirectory} is not a git tree!`);
  123. }
  124. const schemaDirectoryTree = await schemaDirectoryTreeEntry.getTree();
  125. return schemaDirectoryTree.entries();
  126. }
  127.  
  128. async entryContent(entry) {
  129. const blob = await entry.getBlob();
  130. return blob.content();
  131. }
  132.  
  133. async schemaEntries() {
  134. const directoryEntries = await this.schemaDirectoryEntries();
  135. const schemaEntries = _.filter(directoryEntries, (entry) => {
  136. return entry.isFile() && entry.path().match(this.schemaVersionRegex);
  137. });
  138. return _.sortBy(schemaEntries, (entry) => entry.path());
  139. }
  140.  
  141. async versions() {
  142. const schemaEntries = await this.schemaEntries();
  143. return schemaEntries.map(entry => this.extractVersion(entry.path()));
  144. }
  145.  
  146. async latestVersion() {
  147. const versions = await this.versions();
  148. return _.last(versions);
  149. }
  150.  
  151. async versionEntry(version) {
  152. return _.find(await this.schemaEntries(), (entry) => {
  153. return version == this.extractVersion(entry.path());
  154. })
  155. }
  156.  
  157. async versionContent(version) {
  158. return this.entryContent(await this.versionEntry(version));
  159. }
  160.  
  161. async versionObject(version) {
  162. return objectFactory(await this.versionContent(version));
  163. }
  164.  
  165. async latestVersionContent() {
  166. return this.versionContent(await this.latestVersion());
  167. }
  168.  
  169. async latestVersionObject() {
  170. return this.versionObject(await this.latestVersion());
  171. }
  172.  
  173. async needsNewVersion(candidateSchema) {
  174. const latestSchema = await this.latestVersionObject();
  175. return !_.isEqual(latestSchema, candidateSchema);
  176. }
  177.  
  178. async nextVersion(release='minor') {
  179. const latestVersion = await this.latestVersion();
  180. return semver.inc(latestVersion, release);
  181. }
  182.  
  183. async nextVersionPath() {
  184. const nextVersion = await this.nextVersion();
  185. return `${this.schemaDirectory}/${nextVersion}.${this.contentType}`;
  186. }
  187.  
  188. // createExtensionlessSymlink(schemaPath) {
  189. // const extensionlessPath = path.join(this.schemaDirectory, path.basename(schemaPath, `.${this.contentType}`))
  190. // const destPath = path.basename(schemaPath);
  191. // return symlink(path.basename(schemaPath), extensionlessPath);
  192. // }
  193.  
  194. async generateNextVersion(candidateSchema) {
  195. const nextVersionPath = await this.nextVersionPath();
  196. // TODO: dereference schema $refs.
  197. await writeYamlFile(candidateSchema, nextVersionPath);
  198. return nextVersionPath;
  199. // if (this.shouldSymlink) {
  200. // this.createExtensionlessSymlink(nextVersionPath);
  201. // }
  202.  
  203. // TODO: I want to git add this new file right here, but
  204. // I can't because the repo index is locked while this filter clean process runs.
  205.  
  206.  
  207. // WIP...
  208. // const lockedIndex = await NodeGit.Index.open("index.lock");
  209. // lockedIndex.write();
  210.  
  211. // const index = await this.repository.refreshIndex();
  212.  
  213. // let relock = false;
  214. // if (fs.existsSync('.git/index.lock')) {
  215. // console.error('index.lock exists, removing');
  216. // await unlink('.git/index.lock');
  217. // relock = true;
  218. // }
  219.  
  220. // const index = await this.repository.refreshIndex();
  221. // console.error(`adding ${nextVersionPath} to ${index.path()}`);
  222.  
  223. // await index.addByPath(nextVersionPath);
  224. // await index.write();
  225.  
  226. // if (relock) {
  227. // console.error("relocking");
  228. // fs.closeSync(fs.openSync('.git/index.lock', 'w'));
  229. // }
  230.  
  231. }
  232.  
  233. toString() {
  234. return `Schema ${this.schemaDirectory}`;
  235. }
  236. }
  237.  
  238.  
  239. if (require.main === module) {
  240. const schemaPath = process.argv[2];
  241. const data = fs.readFileSync(0, 'utf-8');
  242. const candidateSchema = objectFactory(data);
  243.  
  244. EventSchema.init(path.dirname(schemaPath)).then(async (es) => {
  245. const needsNewVersion = await es.needsNewVersion(candidateSchema);
  246. if (needsNewVersion) {
  247. console.error(`${es} needs new version: ${await es.nextVersion()}`);
  248. try {
  249. const newVersionPath = await es.generateNextVersion(candidateSchema);
  250. console.error(`Generated new schema version for ${es}. Before committing, please run: git add ${newVersionPath}`);
  251.  
  252. } catch(err) {
  253. console.error("FAILED ", err);
  254. }
  255. } else {
  256. console.error(`${es} does not need new version. Latest is ${await es.latestVersion()}`);
  257. }
  258. });
  259. }
  260.  
  261.  
  262. module.exports = {
  263. EventSchema,
  264. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement