From f769c07437172c2cac07ce950167b2333588842a Mon Sep 17 00:00:00 2001 From: Pat McBennett Date: Fri, 8 Oct 2021 11:39:41 +0100 Subject: [PATCH] Command-line processing now test covered (#610) * Command-line processing now test covered * Report highest-level exceptions * Add comment for stdin --- CHANGELOG.md | 2 + index.js | 337 ----------------- package.json | 8 +- src/DatasetHandler.js | 4 +- src/DatasetHandler.test.js | 126 +++---- src/VocabWatcher.test.js | 2 +- src/commandLineProcessor.js | 344 ++++++++++++++++++ src/commandLineProcessor.test.ts | 155 ++++++++ src/generator/VocabGenerator.test.js | 88 ++--- src/index.js | 22 ++ .../yamlConfig/vocab-list-noVersion.yml | 27 -- 11 files changed, 633 insertions(+), 482 deletions(-) delete mode 100644 index.js create mode 100644 src/commandLineProcessor.js create mode 100644 src/commandLineProcessor.test.ts create mode 100644 src/index.js delete mode 100644 test/resources/yamlConfig/vocab-list-noVersion.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fc86cb8..e13e6511 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html ## Unreleased +- Moved all the command-line processing code to be now under code coverage. + ## 1.0.4 2021-10-01 - Added an optional `widocoLanguages` configuration option for each vocabulary diff --git a/index.js b/index.js deleted file mode 100644 index b35f7e10..00000000 --- a/index.js +++ /dev/null @@ -1,337 +0,0 @@ -#!/usr/bin/env node - -// Normally we'd only want to mock out local storage for testing, but in this -// case we want to use our generated vocabularies that depend on -// localStorage for runtime context (e.g. the currently selected language). -// So since we want to use those vocabularies in our Node application here, -// they need a mocked local storage to work with. -require("mock-local-storage"); - -const path = require("path"); -const debugModule = require("debug"); -const yargs = require("yargs"); -const App = require("./src/App"); -const { getArtifactDirectoryRoot } = require("./src/Util"); -const CommandLine = require("./src/CommandLine"); - -const debug = debugModule("artifact-generator:index"); - -const SUPPORTED_COMMANDS = [ - CommandLine.COMMAND_GENERATE(), - CommandLine.COMMAND_INITIALIZE(), - CommandLine.COMMAND_WATCH(), - CommandLine.COMMAND_VALIDATE(), -]; - -function validateCommandLine(argv, options) { - // argv._ contains the commands passed to the program - if (argv._.length !== 1) { - // Only one command is expected - throw new Error( - `Exactly one command is expected (got [${ - argv._.length - }], [${argv._.toString()}]), expected one of [${SUPPORTED_COMMANDS}].` - ); - } - - if (typeof argv._[0] === "number") { - throw new Error( - `Invalid command: command must be a string, but we got the number [${argv._[0]}]. Expected one of [${SUPPORTED_COMMANDS}].` - ); - } - - if (SUPPORTED_COMMANDS.indexOf(argv._[0]) === -1) { - throw new Error( - `Unknown command: [${argv._[0]}] is not a recognized command. Expected one of [${SUPPORTED_COMMANDS}].` - ); - } - return true; -} - -yargs - .command( - CommandLine.COMMAND_GENERATE(), - "Generate code artifacts from RDF vocabularies.", - (yargs) => - yargs - .alias("i", "inputResources") - .array("inputResources") - .describe( - "inputResources", - "One or more ontology resources (i.e. local RDF files, or HTTP URI's) used to generate source-code artifacts representing the contained vocabulary terms." - ) - - .alias("l", "vocabListFile") - .describe( - "vocabListFile", - "Name of a YAML file providing a list of individual vocabs to bundle together into a single artifact (or potentially multiple artifacts for multiple programming languages)." - ) - - .alias("li", "vocabListFileIgnore") - .describe( - "vocabListFileIgnore", - "Globbing pattern for files or directories to ignore when searching for vocabulary list files." - ) - - // This override is really only relevant if we are generating from a - // single vocabulary - if used with a vocab list file, it only applies - // to the first vocabulary listed. - .alias("no", "namespaceOverride") - .describe( - "namespaceOverride", - "Overrides our namespace determination code to provide an explicit namespace IRI." - ) - - .alias("lv", "solidCommonVocabVersion") - .describe( - "solidCommonVocabVersion", - "The version of the Vocab Term to depend on." - ) - .default("solidCommonVocabVersion", "^0.5.3") - - .alias("in", "runNpmInstall") - .boolean("runNpmInstall") - .describe( - "runNpmInstall", - "If set will attempt to NPM install the generated artifact from within the output directory." - ) - .default("runNpmInstall", false) - - .alias("p", "publish") - .array("publish") - .describe( - "publish", - "the values provided to this option will be used as keys to trigger publication according to configurations in the associated YAML file. If not using a YAML file, this option can be used as a flag." - ) - - .alias("tsr", "termSelectionResource") - .describe( - "termSelectionResource", - "Generates Vocab Terms from only the specified ontology resource (file or IRI)." - ) - - .alias("av", "artifactVersion") - .describe( - "artifactVersion", - "The version of the artifact(s) to be generated." - ) - .default("artifactVersion", "0.0.1") - - .alias("mnp", "moduleNamePrefix") - .describe( - "moduleNamePrefix", - "A prefix for the name of the output module" - ) - .default("moduleNamePrefix", "generated-vocab-") - - .alias("nr", "npmRegistry") - .describe( - "npmRegistry", - "The NPM Registry where artifacts will be published" - ) - .default("npmRegistry", "http://localhost:4873") - - .alias("w", "runWidoco") - .boolean("runWidoco") - .describe( - "runWidoco", - "If set will run Widoco to generate documentation for this vocabulary." - ) - - .alias("s", "supportBundling") - .boolean("supportBundling") - .describe( - "supportBundling", - "If set will use bundling support within generated artifact (currently supports Rollup only)." - ) - .default("supportBundling", true) - - .boolean("force") - .describe( - "force", - "Forces generation, even if the target artifacts are considered up-to-date" - ) - .alias("f", "force") - .default("force", false) - - .boolean("clearOutputDirectory") - .describe( - "clearOutputDirectory", - "Deletes the entire output directory before generation" - ) - .alias("c", "clearOutputDirectory") - .default("clearOutputDirectory", false) - - // Must provide either an input vocab file, or a file containing a - // list of vocab files (but how can we demand at least one of these - // two...?). - .conflicts("inputResources", "vocabListFile") - .strict(), - (argv) => { - if (!argv.inputResources && !argv.vocabListFile) { - debugModule(argv.help); - debugModule.enable("artifact-generator:*"); - throw new Error( - "You must provide input, either a single vocabulary using '--inputResources' (e.g. a local RDF file, or a URL that resolves to an RDF vocabulary), or a YAML file using '--vocabListFile' listing multiple vocabularies." - ); - } - runGeneration(argv); - } - ) - .command( - CommandLine.COMMAND_INITIALIZE(), - "Initializes a configuration YAML file used for fine-grained " + - "control of artifact generation.", - (yargs) => yargs, - (argv) => { - runInitialization(argv); - } - ) - .command( - CommandLine.COMMAND_VALIDATE(), - "Validates a configuration YAML file used for artifact generation.", - (yargs) => - yargs - .alias("l", "vocabListFile") - .describe( - "vocabListFile", - "Name of a YAML file providing a list of individual vocabs to bundle together into a single artifact (or potentially multiple artifacts for multiple programming languages)." - ) - .demandOption(["vocabListFile"]), - (argv) => { - runValidation(argv); - } - ) - .command( - CommandLine.COMMAND_WATCH(), - "Starts a process watching the configured vocabulary" + - " resources, and automatically re-generates artifacts whenever it detects" + - " a vocabulary change.", - (yargs) => - yargs - .alias("l", "vocabListFile") - .describe( - "vocabListFile", - "Name of a YAML file providing a list of individual vocabs to bundle together into a single artifact (or potentially multiple artifacts for multiple programming languages)." - ) - .demandOption(["vocabListFile"]), - (argv) => { - runWatcher(argv); - } - ) - - // The following options are shared between the different commands - .alias("q", "quiet") - .boolean("quiet") - .describe( - "quiet", - `If set will not display logging output to console (but you can still use the DEBUG environment variable, set to 'artifact-generator:*').` - ) - .default("quiet", false) - - .alias("np", "noprompt") - .boolean("noprompt") - .describe( - "noprompt", - "If set will not ask any interactive questions and will attempt to perform artifact generation automatically." - ) - .default("noprompt", false) - - .alias("o", "outputDirectory") - .describe( - "outputDirectory", - "The output directory for the" + - " generated artifacts (defaults to the current directory)." - ) - .default("outputDirectory", ".") - .check(validateCommandLine) - .help().argv; - -function configureLog(argv) { - // Unless specifically told to be quiet (i.e. no logging output, although that - // will still be overridden by the DEBUG environment variable!), then - // determine if any application-specific namespaces have been enabled. If they - // haven't been, then turn them all on. - if (!argv.quiet) { - // Retrieve all currently enabled debug namespaces (and then restore them!). - const namespaces = debugModule.disable(); - debugModule.enable(namespaces); - - // Unless our debug logging has been explicitly configured, turn all - // debugging on. - if (namespaces.indexOf("artifact-generator") === -1) { - debugModule.enable("artifact-generator:*"); - } - } -} - -function runGeneration(argv) { - configureLog(argv); - new App(argv) - .run() - .then((data) => { - debug( - `\nGeneration process successful to directory [${path.join( - data.outputDirectory, - getArtifactDirectoryRoot(data) - )}]!` - ); - process.exit(0); - }) - .catch((error) => { - debug(`Generation process failed: [${error}]`); - process.exit(-1); - }); -} - -function runInitialization(argv) { - configureLog(argv); - new App(argv) - .init() - .then((data) => { - debug(`\nSuccessfully initialized configuration file [${data}]`); - process.exit(0); - }) - .catch((error) => { - debug(`Configuration file initialization process failed: [${error}]`); - process.exit(-1); - }); -} - -function runValidation(argv) { - configureLog(argv); - new App(argv) - .validate() - .then((data) => { - debug(`\nThe provided configuration is valid`); - process.exit(0); - }) - .catch((error) => { - debug(`Invalid configuration: [${error}]`); - process.exit(-1); - }); -} - -async function runWatcher(argv) { - configureLog(argv); - - const app = new App(argv); - const watcherCount = await app.watch(); - const plural = watcherCount != 1; - debug( - `\nSuccessfully initialized file watcher${ - plural ? "s" : "" - } on [${watcherCount}] vocabulary bundle configuration file${ - plural ? "s" : "" - }...` - ); - - // Use console to communicate with the user - we can't rely on 'debug' since - // it needs to be configured before it'll output anything. - console.log("Press Enter to terminate"); - process.stdin.on("data", () => { - // On user input, exit - app.unwatch(); - process.exit(0); - }); -} diff --git a/package.json b/package.json index 7dc8a5ec..5c4294cb 100644 --- a/package.json +++ b/package.json @@ -2,14 +2,14 @@ "name": "@inrupt/artifact-generator", "version": "1.0.4", "description": "A generator for building artifacts (e.g. Java JARs, NPM modules, etc.) from RDF vocabularies.", - "main": "index.js", + "main": "src/index.js", "bin": { - "artifact-generator": "index.js" + "artifact-generator": "src/index.js" }, "scripts": { "test": "npm run format && npm run lint && jest", - "start": "node index.js", - "format": "prettier --write \"index.js\" \"src/**/*.js\"", + "start": "node src/index.js", + "format": "prettier --write \"src/**/*.js\"", "lint": "eslint src --ext js,jsx", "preversion": "npm test" }, diff --git a/src/DatasetHandler.js b/src/DatasetHandler.js index 2b19186e..95423217 100644 --- a/src/DatasetHandler.js +++ b/src/DatasetHandler.js @@ -630,7 +630,7 @@ module.exports = class DatasetHandler { return result; } - buildTemplateInput() { + async buildTemplateInput() { const result = {}; result.rollupVersion = this.vocabData.rollupVersion; @@ -701,7 +701,7 @@ module.exports = class DatasetHandler { result.storeLocalCopyOfVocabDirectory = this.vocabData.storeLocalCopyOfVocabDirectory; - Resource.storeLocalCopyOfResource( + await Resource.storeLocalCopyOfResource( result.storeLocalCopyOfVocabDirectory, result.vocabName, result.namespace, diff --git a/src/DatasetHandler.test.js b/src/DatasetHandler.test.js index e903fe55..449ffd96 100644 --- a/src/DatasetHandler.test.js +++ b/src/DatasetHandler.test.js @@ -45,7 +45,7 @@ describe("Dataset Handler", () => { }); describe("Ontology description ", () => { - it("should use English description if multiple", () => { + it("should use English description if multiple", async () => { const commentInEnglish = rdf.literal("Some comment in English", "en"); const commentInIrish = rdf.literal("Tráchtann cuid acu i nGaeilge", "ga"); const commentInFrench = rdf.literal( @@ -65,11 +65,11 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.description).toContain(commentInEnglish.value); }); - it("should use description that starts with explicit English tag", () => { + it("should use description that starts with explicit English tag", async () => { const commentInUsEnglish = rdf.literal( "Some comment in US English", "en-US" @@ -92,11 +92,11 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.description).toContain(commentInUsEnglish.value); }); - it("should fallback to no language tag if no explicit English", () => { + it("should fallback to no language tag if no explicit English", async () => { const commentWithNoLocale = rdf.literal( "Some comment with no language tag" ); @@ -118,13 +118,13 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.description).toContain(commentWithNoLocale.value); }); }); describe("Edge-case vocabulary cases ", () => { - it("should ignore properties defined on the namespace IRI", () => { + it("should ignore properties defined on the namespace IRI", async () => { const dataset = rdf .dataset() .add(rdf.quad(rdf.namedNode(OWL_NAMESPACE), RDF.type, RDF.Property)) @@ -134,7 +134,7 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.properties.length).toBe(0); }); }); @@ -152,7 +152,7 @@ describe("Dataset Handler", () => { "fr" ); - it("should give full description of matching labels and comments in all languages", () => { + it("should give full description of matching labels and comments in all languages", async () => { const dataset = rdf .dataset() .add(rdf.quad(OWL.Ontology, RDF.type, RDFS.Class)) @@ -169,14 +169,14 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.classes.length).toEqual(1); const description = result.classes[0].termDescription; expect(description).toContain("[4] labels and comments"); expect(description).toContain("languages [NoLocale, en, fr, ga]"); }); - it("should treat 'skosxl:literalForm' as labels in all languages", () => { + it("should treat 'skosxl:literalForm' as labels in all languages", async () => { const dataset = rdf .dataset() .add(rdf.quad(OWL.Ontology, RDF.type, SKOSXL.Label)) @@ -189,7 +189,7 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.properties.length).toEqual(1); const description = result.properties[0].termDescription; expect(description).toContain("[4] labels ("); @@ -197,7 +197,7 @@ describe("Dataset Handler", () => { expect(description).toContain("but no long-form descriptions"); }); - it("should report all lang tags, but not report mismatch if only on @en", () => { + it("should report all lang tags, but not report mismatch if only on @en", async () => { const dataset = rdf .dataset() .add(rdf.quad(OWL.Ontology, RDF.type, RDFS.Class)) @@ -215,7 +215,7 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.classes.length).toEqual(1); const description = result.classes[0].termDescription; expect(description).toContain("[3] labels"); @@ -225,7 +225,7 @@ describe("Dataset Handler", () => { expect(description).not.toContain("mismatch"); }); - it("should report difference is only in @en vs NoLocale", () => { + it("should report difference is only in @en vs NoLocale", async () => { const dataset = rdf .dataset() .add(rdf.quad(OWL.Ontology, RDF.type, RDFS.Class)) @@ -236,7 +236,7 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.classes.length).toEqual(1); const description = result.classes[0].termDescription; expect(description).toContain("only in English"); @@ -245,7 +245,7 @@ describe("Dataset Handler", () => { expect(description).toContain("we consider the same"); }); - it("should describe single matching non-English label and comment", () => { + it("should describe single matching non-English label and comment", async () => { const dataset = rdf .dataset() .add(rdf.quad(OWL.Ontology, RDF.type, RDFS.Class)) @@ -256,14 +256,14 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.classes.length).toEqual(1); const description = result.classes[0].termDescription; expect(description).toContain("language [ga]"); expect(description).toContain("[1] label and comment"); }); - it("should describe single mismatching non-English label and comment", () => { + it("should describe single mismatching non-English label and comment", async () => { const dataset = rdf .dataset() .add(rdf.quad(OWL.Ontology, RDF.type, RDFS.Class)) @@ -274,7 +274,7 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.classes.length).toEqual(1); const description = result.classes[0].termDescription; expect(description).toContain("has a mismatch"); @@ -283,7 +283,7 @@ describe("Dataset Handler", () => { expect(description).toContain("language [fr]"); }); - it("should describe multiple matching non-English label and comment", () => { + it("should describe multiple matching non-English label and comment", async () => { const dataset = rdf .dataset() .add(rdf.quad(OWL.Ontology, RDF.type, RDFS.Class)) @@ -296,14 +296,14 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.classes.length).toEqual(1); const description = result.classes[0].termDescription; expect(description).toContain("[2] labels and comments"); expect(description).toContain("languages [fr, ga]"); }); - it("should say no long-form description if no comment at all", () => { + it("should say no long-form description if no comment at all", async () => { const dataset = rdf .dataset() .add(rdf.quad(OWL.Ontology, RDF.type, RDFS.Class)) @@ -314,14 +314,14 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.classes.length).toEqual(1); expect(result.classes[0].termDescription).toContain( "no long-form descriptions at all" ); }); - it("should describe mismatch label and missing comment", () => { + it("should describe mismatch label and missing comment", async () => { const dataset = rdf .dataset() .add(rdf.quad(OWL.Ontology, RDF.type, RDFS.Class)) @@ -333,7 +333,7 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.classes.length).toEqual(1); const description = result.classes[0].termDescription; expect(description).toContain("mismatch"); @@ -341,7 +341,7 @@ describe("Dataset Handler", () => { expect(description).toContain("language [en]"); }); - it("should describe single label and no comments", () => { + it("should describe single label and no comments", async () => { const dataset = rdf .dataset() .add(rdf.quad(OWL.Ontology, RDF.type, RDFS.Class)) @@ -351,7 +351,7 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.classes.length).toEqual(1); const description = result.classes[0].termDescription; expect(description).toContain("term has a label"); @@ -359,7 +359,7 @@ describe("Dataset Handler", () => { expect(description).toContain("no long-form descriptions at all"); }); - it("should describe multiple labels and no comments", () => { + it("should describe multiple labels and no comments", async () => { const dataset = rdf .dataset() .add(rdf.quad(OWL.Ontology, RDF.type, RDFS.Class)) @@ -371,7 +371,7 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.classes.length).toEqual(1); const description = result.classes[0].termDescription; expect(description).toContain("term has [3] labels"); @@ -379,7 +379,7 @@ describe("Dataset Handler", () => { expect(description).toContain("no long-form descriptions at all"); }); - it("should describe single label and comment in English only", () => { + it("should describe single label and comment in English only", async () => { const dataset = rdf .dataset() .add(rdf.quad(OWL.Ontology, RDF.type, RDFS.Class)) @@ -390,13 +390,13 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.classes.length).toEqual(1); const description = result.classes[0].termDescription; expect(description).toContain("descriptions only in English"); }); - it("should describe single label and comment in no-locale only", () => { + it("should describe single label and comment in no-locale only", async () => { const dataset = rdf .dataset() .add(rdf.quad(OWL.Ontology, RDF.type, RDFS.Class)) @@ -407,7 +407,7 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.classes.length).toEqual(1); const description = result.classes[0].termDescription; expect(description).toContain( @@ -415,7 +415,7 @@ describe("Dataset Handler", () => { ); }); - it("should describe mismatch comment and multiple missing label", () => { + it("should describe mismatch comment and multiple missing label", async () => { const dataset = rdf .dataset() .add(rdf.quad(OWL.Ontology, RDF.type, RDFS.Class)) @@ -426,7 +426,7 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.classes.length).toEqual(1); const description = result.classes[0].termDescription; expect(description).toContain("mismatch"); @@ -436,7 +436,7 @@ describe("Dataset Handler", () => { }); describe("handle sub-classes or sub-properties", () => { - it("should handle sub-classes", () => { + it("should handle sub-classes", async () => { const dataset = rdf .dataset() .add(rdf.quad(OWL.Ontology, RDFS.subClassOf, SKOS.Concept)); @@ -445,12 +445,12 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.classes.length).toEqual(1); expect(result.classes[0].name).toEqual("Ontology"); }); - it("should handle sub-properties", () => { + it("should handle sub-properties", async () => { const dataset = rdf .dataset() .add( @@ -468,13 +468,13 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.properties.length).toEqual(1); expect(result.properties[0].name).toEqual("label"); }); }); - it("should ignore vocab terms not in our namespace, if configured to do so", () => { + it("should ignore vocab terms not in our namespace, if configured to do so", async () => { const dataset = rdf .dataset() .addAll(vocabMetadata) @@ -490,11 +490,11 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], ignoreNonVocabTerms: true, }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.classes.length).toEqual(0); }); - it("should makes exceptions for vocab terms found in common vocabs - RDF:langString", () => { + it("should makes exceptions for vocab terms found in common vocabs - RDF:langString", async () => { const dataset = rdf .dataset() .addAll(vocabMetadata) @@ -503,11 +503,11 @@ describe("Dataset Handler", () => { const handler = new DatasetHandler(dataset, rdf.dataset(), { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.properties.length).toEqual(0); }); - it("should makes exceptions for vocab terms found in common vocabs - XSD:duration", () => { + it("should makes exceptions for vocab terms found in common vocabs - XSD:duration", async () => { const dataset = rdf .dataset() .addAll(vocabMetadata) @@ -523,11 +523,11 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.properties.length).toEqual(0); }); - it("should de-duplicate terms defined with multiple predicates we look for", () => { + it("should de-duplicate terms defined with multiple predicates we look for", async () => { // Note: This test relies on the order different predicates are processing // in the implementation - i.e. if a subject matches multiple RDF types, // then only the first one will be used. @@ -573,7 +573,7 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.classes.length).toEqual(1); expect(result.classes[0].name).toEqual("testTermClass"); @@ -589,7 +589,7 @@ describe("Dataset Handler", () => { expect(result.constantStrings.length).toEqual(0); }); - it("should skip classes and sub-classes from other, but well-known, vocabs", () => { + it("should skip classes and sub-classes from other, but well-known, vocabs", async () => { // Create terms that look they come from a well-known vocab. const testTermClass = rdf.namedNode(`${RDF_NAMESPACE}testTermClass`); const testTermSubClass = rdf.namedNode(`${RDF_NAMESPACE}testTermSubClass`); @@ -607,7 +607,7 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.classes.length).toEqual(0); }); @@ -649,7 +649,7 @@ describe("Dataset Handler", () => { expect(handler.findPreferredNamespacePrefix(NS)).toEqual("foaf"); }); - it("should throw an error if the vocabulary does not define any term", () => { + it("should throw an error if the vocabulary does not define any term", async () => { const NS = "http://xmlns.com/foaf/0.1/"; const NS_IRI = rdf.namedNode(NS); @@ -664,12 +664,12 @@ describe("Dataset Handler", () => { inputResources: ["does not matter"], }); - expect(() => { - handler.buildTemplateInput(); - }).toThrow(`[${NS}] does not contain any terms.`); + await expect(handler.buildTemplateInput()).rejects.toThrow( + `[${NS}] does not contain any terms.` + ); }); - it("should override the namespace of the terms", () => { + it("should override the namespace of the terms", async () => { const namespaceOverride = "https://override.namespace.org"; const testTermClass = `${NAMESPACE}testTermClass`; const dataset = rdf @@ -683,11 +683,11 @@ describe("Dataset Handler", () => { nameAndPrefixOverride: "does not matter", }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.namespace).toEqual(namespaceOverride); }); - it("should override the namespace of the terms if the heuristic namespace determination fails.", () => { + it("should override the namespace of the terms if the heuristic namespace determination fails.", async () => { const namespaceOverride = "http://rdf-extension.com#"; const otherNamespace = "https://another.long.namespace.org#"; const testTermClass = `${NAMESPACE}testTermClass`; @@ -710,7 +710,7 @@ describe("Dataset Handler", () => { nameAndPrefixOverride: "does not matter", }); - const result = handler.buildTemplateInput(); + const result = await handler.buildTemplateInput(); expect(result.namespace).toEqual(namespaceOverride); }); @@ -736,15 +736,7 @@ describe("Dataset Handler", () => { storeLocalCopyOfVocabDirectory: testLocalCopyDirectory, }); - handler.buildTemplateInput(); - - // TODO: Look into refactoring this to work with async N3 Writer... - // Specifically `static Resource.storeLocalCopyOfResource()` - // Force a deliberate short time interval here to give our writer time - // to generate the local file. - await new Promise((resolve) => { - setTimeout(resolve, 100); - }); + await handler.buildTemplateInput(); const matches = fs .readdirSync(testLocalCopyDirectory) diff --git a/src/VocabWatcher.test.js b/src/VocabWatcher.test.js index 70bc2720..e0959f55 100644 --- a/src/VocabWatcher.test.js +++ b/src/VocabWatcher.test.js @@ -89,7 +89,7 @@ describe("Vocabulary watcher", () => { // online resources it references. expect(watcher.getWatchedResourceList().length).toBe(1); watcher.unwatch(); - }); + }, 10000); it("should not generate an initial artifact without changes", async () => { const watcher = new VocabWatcher( diff --git a/src/commandLineProcessor.js b/src/commandLineProcessor.js new file mode 100644 index 00000000..e465ce9c --- /dev/null +++ b/src/commandLineProcessor.js @@ -0,0 +1,344 @@ +#!/usr/bin/env node + +// Normally we'd only want to mock out local storage for testing, but in this +// case we want to use our generated vocabularies that depend on +// localStorage for runtime context (e.g. the currently selected language). +// So since we want to use those vocabularies in our Node application here, +// they need a mocked local storage to work with. +require("mock-local-storage"); + +const path = require("path"); +const debugModule = require("debug"); +const yargs = require("yargs"); +const App = require("./App"); +const { getArtifactDirectoryRoot } = require("./Util"); +const CommandLine = require("./CommandLine"); + +const debug = debugModule("artifact-generator:commandLineProcessor"); + +const SUPPORTED_COMMANDS = [ + CommandLine.COMMAND_GENERATE(), + CommandLine.COMMAND_INITIALIZE(), + CommandLine.COMMAND_WATCH(), + CommandLine.COMMAND_VALIDATE(), +]; + +function validateCommandLine(argv, options) { + // argv._ contains the commands passed to the program + if (argv._.length !== 1) { + // Only one command is expected + throw new Error( + `Exactly one command is expected (got [${ + argv._.length + }], [${argv._.toString()}]), expected one of [${SUPPORTED_COMMANDS}].` + ); + } + + if (typeof argv._[0] === "number") { + throw new Error( + `Invalid command: command must be a string, but we got the number [${argv._[0]}]. Expected one of [${SUPPORTED_COMMANDS}].` + ); + } + + if (SUPPORTED_COMMANDS.indexOf(argv._[0]) === -1) { + throw new Error( + `Unknown command: [${argv._[0]}] is not a recognized command. Expected one of [${SUPPORTED_COMMANDS}].` + ); + } + return true; +} + +function processCommandLine(exitOnFail, commandLineArgs) { + // To support multiple unit tests, create an instance of Yargs per + // execution (otherwise the Yargs state gets corrupted). + const yargsInstance = yargs(); + + return ( + yargsInstance + .help() + .fail(false) + .exitProcess(exitOnFail) + .command( + CommandLine.COMMAND_GENERATE(), + "Generate code artifacts from RDF vocabularies.", + (yargs) => + yargs + .alias("i", "inputResources") + .array("inputResources") + .describe( + "inputResources", + "One or more ontology resources (i.e. local RDF files, or HTTP URI's) used to generate source-code artifacts representing the contained vocabulary terms." + ) + + .alias("l", "vocabListFile") + .describe( + "vocabListFile", + "Name of a YAML file providing a list of individual vocabs to bundle together into a single artifact (or potentially multiple artifacts for multiple programming languages)." + ) + + .alias("li", "vocabListFileIgnore") + .describe( + "vocabListFileIgnore", + "Globbing pattern for files or directories to ignore when searching for vocabulary list files." + ) + + // This override is really only relevant if we are generating from a + // single vocabulary - if used with a vocab list file, it only applies + // to the first vocabulary listed. + .alias("no", "namespaceOverride") + .describe( + "namespaceOverride", + "Overrides our namespace determination code to provide an explicit namespace IRI." + ) + + .alias("lv", "solidCommonVocabVersion") + .describe( + "solidCommonVocabVersion", + "The version of the Vocab Term to depend on." + ) + .default("solidCommonVocabVersion", "^0.5.3") + + .alias("in", "runNpmInstall") + .boolean("runNpmInstall") + .describe( + "runNpmInstall", + "If set will attempt to NPM install the generated artifact from within the output directory." + ) + .default("runNpmInstall", false) + + .alias("p", "publish") + .array("publish") + .describe( + "publish", + "the values provided to this option will be used as keys to trigger publication according to configurations in the associated YAML file. If not using a YAML file, this option can be used as a flag." + ) + + .alias("tsr", "termSelectionResource") + .describe( + "termSelectionResource", + "Generates Vocab Terms from only the specified ontology resource (file or IRI)." + ) + + .alias("av", "artifactVersion") + .describe( + "artifactVersion", + "The version of the artifact(s) to be generated." + ) + .default("artifactVersion", "0.0.1") + + .alias("mnp", "moduleNamePrefix") + .describe( + "moduleNamePrefix", + "A prefix for the name of the output module" + ) + .default("moduleNamePrefix", "generated-vocab-") + + .alias("nr", "npmRegistry") + .describe( + "npmRegistry", + "The NPM Registry where artifacts will be published" + ) + .default("npmRegistry", "http://localhost:4873") + + .alias("w", "runWidoco") + .boolean("runWidoco") + .describe( + "runWidoco", + "If set will run Widoco to generate documentation for this vocabulary." + ) + + .alias("s", "supportBundling") + .boolean("supportBundling") + .describe( + "supportBundling", + "If set will use bundling support within generated artifact (currently supports Rollup only)." + ) + .default("supportBundling", true) + + .boolean("force") + .describe( + "force", + "Forces generation, even if the target artifacts are considered up-to-date" + ) + .alias("f", "force") + .default("force", false) + + .boolean("clearOutputDirectory") + .describe( + "clearOutputDirectory", + "Deletes the entire output directory before generation" + ) + .alias("c", "clearOutputDirectory") + .default("clearOutputDirectory", false) + + // Must provide either an input vocab file, or a file containing a + // list of vocab files (but how can we demand at least one of these + // two...?). + .conflicts("inputResources", "vocabListFile") + .strict(), + (argv) => { + if (!argv.inputResources && !argv.vocabListFile) { + const message = + "You must provide input, either a single vocabulary using '--inputResources' (e.g. a local RDF file, or a URL that resolves to an RDF vocabulary), or a YAML file using '--vocabListFile' listing multiple vocabularies."; + debugModule.enable("artifact-generator:*"); + debug(message); + throw new Error(message); + } + return runGeneration(argv); + } + ) + .command( + CommandLine.COMMAND_INITIALIZE(), + "Initializes a configuration YAML file used for fine-grained " + + "control of artifact generation.", + (yargs) => yargs, + (argv) => { + return runInitialization(argv); + } + ) + .command( + CommandLine.COMMAND_VALIDATE(), + "Validates a configuration YAML file used for artifact generation.", + (yargs) => + yargs + .alias("l", "vocabListFile") + .describe( + "vocabListFile", + "Name of a YAML file providing a list of individual vocabs to bundle together into a single artifact (or potentially multiple artifacts for multiple programming languages)." + ) + .demandOption(["vocabListFile"]), + (argv) => { + return runValidation(argv); + } + ) + .command( + CommandLine.COMMAND_WATCH(), + "Starts a process watching the configured vocabulary" + + " resources, and automatically re-generates artifacts whenever it detects" + + " a vocabulary change.", + (yargs) => + yargs + .alias("l", "vocabListFile") + .describe( + "vocabListFile", + "Name of a YAML file providing a list of individual vocabs to bundle together into a single artifact (or potentially multiple artifacts for multiple programming languages)." + ) + .demandOption(["vocabListFile"]), + (argv) => { + return runWatcher(argv); + } + ) + + // The following options are shared between the different commands + .alias("q", "quiet") + .boolean("quiet") + .describe( + "quiet", + `If set will not display logging output to console (but you can still use the DEBUG environment variable, set to 'artifact-generator:*').` + ) + .default("quiet", false) + + .alias("np", "noprompt") + .boolean("noprompt") + .describe( + "noprompt", + "If set will not ask any interactive questions and will attempt to perform artifact generation automatically." + ) + .default("noprompt", false) + + .alias("o", "outputDirectory") + .describe( + "outputDirectory", + "The output directory for the" + + " generated artifacts (defaults to the current directory)." + ) + .default("outputDirectory", ".") + .check(validateCommandLine) + .parse(commandLineArgs) + ); +} + +function configureLog(argv) { + // Unless specifically told to be quiet (i.e. no logging output, although that + // will still be overridden by the DEBUG environment variable!), then + // determine if any application-specific namespaces have been enabled. If they + // haven't been, then turn them all on. + if (!argv.quiet) { + // Retrieve all currently enabled debug namespaces (and then restore them!). + const namespaces = debugModule.disable(); + debugModule.enable(namespaces); + + // Unless our debug logging has been explicitly configured, turn all + // debugging on. + if (namespaces.indexOf("artifact-generator") === -1) { + debugModule.enable("artifact-generator:*"); + } + } +} + +function runGeneration(argv) { + configureLog(argv); + return new App(argv) + .run() + .then((data) => { + debug( + `\nGeneration process successful to directory [${path.join( + data.outputDirectory, + getArtifactDirectoryRoot(data) + )}]!` + ); + }) + .catch((error) => { + const message = `Generation process failed: [${error}]`; + debug(message); + throw new Error(message); + }); +} + +function runInitialization(argv) { + configureLog(argv); + // Note: No `.catch()` block needed here, as initialization can't fail (even + // using weird characters (e.g., '@#$!*&^') in the output directory name + // won't throw on Linux). + new App(argv).init().then((data) => { + debug(`\nSuccessfully initialized configuration file [${data}]`); + }); +} + +async function runValidation(argv) { + configureLog(argv); + return await new App(argv) + .validate() + .then((data) => { + debug(`\nThe provided configuration is valid`); + }) + .catch((error) => { + const message = `Invalid configuration: [${error}]`; + debug(message); + throw new Error(message); + }); +} + +async function runWatcher(argv) { + configureLog(argv); + + const app = new App(argv); + const watcherCount = await app.watch(); + const plural = watcherCount != 1; + debug( + `\nSuccessfully initialized file watcher${ + plural ? "s" : "" + } on [${watcherCount}] vocabulary bundle configuration file${ + plural ? "s" : "" + }.` + ); + + debug("Press Enter to terminate"); + + // Pass back our cleanup function. + argv.unwatchFunction = () => { + app.unwatch(); + }; +} + +module.exports = processCommandLine; diff --git a/src/commandLineProcessor.test.ts b/src/commandLineProcessor.test.ts new file mode 100644 index 00000000..08ba2d08 --- /dev/null +++ b/src/commandLineProcessor.test.ts @@ -0,0 +1,155 @@ +const fs = require("fs"); + +const processCommandLine = require("./commandLineProcessor"); + +describe("Command line argument handling", () => { + it("should succeed validation", async () => { + const filename = "test/resources/yamlConfig/namespace-override.yml"; + const validArguments = ["validate", "--vocabListFile", filename]; + + const response = await processCommandLine(false, validArguments); + expect(response._).toHaveLength(1); + expect(response.vocabListFile).toEqual(filename); + + // Call again to exercise our debug namespace being picked up this 2nd + // time around (first call should have enabled it). + processCommandLine(false, validArguments); + }); + + it("should fail validation", async () => { + const nonExistFile = "should-not-exist.yml"; + const invalidArguments = ["validate", "--vocabListFile", nonExistFile]; + // const x = await processCommandLine(false, invalidArguments); + // console.log(x); + await expect(processCommandLine(false, invalidArguments)).rejects.toThrow( + nonExistFile + ); + }); + + it("should succeed initialization", () => { + const outputDirectory = + "test/Generated/UNIT_TEST/commandLineProcessor/init"; + const validArguments = [ + "init", + "--outputDirectory", + outputDirectory, + "--noprompt", + ]; + + const filename = `${outputDirectory}/sample-vocab.yml`; + try { + fs.unlinkSync(filename); + } catch (error) { + // Ignore file-not-found errors! + } + const response = processCommandLine(false, validArguments); + expect(response._).toHaveLength(1); + expect(fs.existsSync(filename)).toBe(true); + }); + + it("should succeed watching one resource", async () => { + const filename = "test/resources/yamlConfig/namespace-override.yml"; + const validArguments = ["watch", "--vocabListFile", filename]; + + const result = await processCommandLine(false, validArguments); + expect(result.unwatchFunction).toBeDefined(); + result.unwatchFunction(); + }); + + it("should succeed watching multiple resource", async () => { + const filename = "test/resources/watcher/vocab-list-*.yml"; + const validArguments = ["watch", "--vocabListFile", filename]; + + const result = await processCommandLine(false, validArguments); + expect(result.unwatchFunction).toBeDefined(); + result.unwatchFunction(); + }); + + it("should fail watch", async () => { + const nonExistFile = "should-not-exist.yml"; + const invalidArguments = [ + "watch", + "--vocabListFile", + nonExistFile, + "--noprompt", + ]; + + // Deliberately provide non-existent file... + await expect(processCommandLine(false, invalidArguments)).rejects.toThrow( + nonExistFile + ); + }); + + it("should succeed generation", async () => { + const filename = "test/resources/yamlConfig/namespace-override.yml"; + const validArguments = [ + "generate", + "--vocabListFile", + filename, + "--noprompt", + ]; + + const response = await processCommandLine(false, validArguments); + expect(response._).toHaveLength(1); + expect(response.vocabListFile).toEqual(filename); + }); + + it("should fail generation if no input files", () => { + const invalidArguments = ["generate", "--noprompt"]; + expect(() => processCommandLine(false, invalidArguments)).toThrow( + "You must provide input" + ); + }); + + it("should fail generation if input not found", async () => { + const nonExistFile = "should-not-exist.yml"; + const invalidArguments = [ + "generate", + "--vocabListFile", + nonExistFile, + "--noprompt", + ]; + await expect(processCommandLine(false, invalidArguments)).rejects.toThrow( + "Generation process failed" + ); + }); + + it("should run in quiet mode", async () => { + const filename = "test/resources/yamlConfig/namespace-override.yml"; + const response = await processCommandLine(false, [ + "validate", + "--vocabListFile", + filename, + "--quiet", + "--noprompt", + ]); + expect(response._).toHaveLength(1); + expect(response.vocabListFile).toEqual(filename); + }); + + it("should throw if no command", () => { + expect(() => + processCommandLine(false, ["--vocabListFile", "some-dummy-file.yml"]) + ).toThrow("one command is expected"); + }); + + it("should throw if command is a number", () => { + expect(() => + processCommandLine(false, [ + "666", + "--vocabListFile", + "some-dummy-file.yml", + ]) + ).toThrow("but we got the number [666]"); + }); + + it("should throw if invalid command", () => { + expect(() => + processCommandLine(false, [ + "Unknown-command", + "--vocabListFile", + "some-dummy-file.yml", + ]) + ).toThrow("Unknown command"); + }); +}); diff --git a/src/generator/VocabGenerator.test.js b/src/generator/VocabGenerator.test.js index 45bf5d20..4d478e85 100644 --- a/src/generator/VocabGenerator.test.js +++ b/src/generator/VocabGenerator.test.js @@ -282,8 +282,8 @@ describe("Artifact generator unit tests", () => { }); describe("Building the Template input", () => { - it("should create a simple JSON object with all the fields", () => { - const result = vocabGenerator.buildTemplateInput( + it("should create a simple JSON object with all the fields", async () => { + const result = await vocabGenerator.buildTemplateInput( VocabGenerator.merge([dataset, datasetExtension]), VocabGenerator.merge([datasetExtension]) ); @@ -384,8 +384,8 @@ describe("Artifact generator unit tests", () => { ); }); - it("Should merge A and B, and generate code from A and B", () => { - const result = vocabGenerator.buildTemplateInput( + it("Should merge A and B, and generate code from A and B", async () => { + const result = await vocabGenerator.buildTemplateInput( VocabGenerator.merge([dataSetA, dataSetB]), VocabGenerator.merge([dataSetA, dataSetB]) ); @@ -394,8 +394,8 @@ describe("Artifact generator unit tests", () => { expect(result.properties[0].name).toBe("givenName"); }); - it("Should merge A and B, and generate code from A (not B)", () => { - const result = vocabGenerator.buildTemplateInput( + it("Should merge A and B, and generate code from A (not B)", async () => { + const result = await vocabGenerator.buildTemplateInput( VocabGenerator.merge([dataSetA, dataSetB]), VocabGenerator.merge([dataSetA]) ); @@ -404,8 +404,8 @@ describe("Artifact generator unit tests", () => { expect(result.properties.length).toBe(0); }); - it("Should merge A and B, and generate code from B (not A)", () => { - const result = vocabGenerator.buildTemplateInput( + it("Should merge A and B, and generate code from B (not A)", async () => { + const result = await vocabGenerator.buildTemplateInput( VocabGenerator.merge([dataSetA, dataSetB]), VocabGenerator.merge([dataSetB]) ); @@ -414,8 +414,8 @@ describe("Artifact generator unit tests", () => { expect(result.properties[0].name).toBe("givenName"); }); - it("Should merge A B and C, and generate code from A and B (not C)", () => { - const result = vocabGenerator.buildTemplateInput( + it("Should merge A B and C, and generate code from A and B (not C)", async () => { + const result = await vocabGenerator.buildTemplateInput( VocabGenerator.merge([dataSetA, dataSetB, dataSetC]), VocabGenerator.merge([dataSetA, dataSetB]) ); @@ -425,8 +425,8 @@ describe("Artifact generator unit tests", () => { expect(result.properties.length).toBe(1); }); - it("Should handle empty datasets", () => { - const result = vocabGenerator.buildTemplateInput( + it("Should handle empty datasets", async () => { + const result = await vocabGenerator.buildTemplateInput( VocabGenerator.merge([emptyDataSet]), VocabGenerator.merge([emptyDataSet]) ); @@ -438,8 +438,8 @@ describe("Artifact generator unit tests", () => { expect(result.properties.length).toBe(0); }); - it("Should use the label value if no comment and no definition", () => { - const result = vocabGenerator.buildTemplateInput( + it("Should use the label value if no comment and no definition", async () => { + const result = await vocabGenerator.buildTemplateInput( VocabGenerator.merge([dataSetA, dataSetB]), VocabGenerator.merge([dataSetB]) ); @@ -449,8 +449,8 @@ describe("Artifact generator unit tests", () => { expect(result.properties[0].comment).toBe("Given Name"); }); - it("Should use the definition value if no comment", () => { - const result = vocabGenerator.buildTemplateInput( + it("Should use the definition value if no comment", async () => { + const result = await vocabGenerator.buildTemplateInput( dataSetD, VocabGenerator.merge([emptyDataSet]) ); @@ -460,7 +460,7 @@ describe("Artifact generator unit tests", () => { expect(result.properties[0].comment).toBe("Family Name"); }); - it("Should take any comment for the class or property if english or default cant be found", () => { + it("Should take any comment for the class or property if english or default cant be found", async () => { const dataSetFrenchOnlyComment = rdf .dataset() .addAll([ @@ -472,7 +472,7 @@ describe("Artifact generator unit tests", () => { ), ]); - const result = vocabGenerator.buildTemplateInput( + const result = await vocabGenerator.buildTemplateInput( VocabGenerator.merge([dataSetA, dataSetFrenchOnlyComment]), VocabGenerator.merge([dataSetFrenchOnlyComment]) ); @@ -482,12 +482,12 @@ describe("Artifact generator unit tests", () => { expect(result.properties[0].comment).toBe("Given Name comment in french"); }); - it("Should return empty comment if nothing found at all", () => { + it("Should return empty comment if nothing found at all", async () => { const noDescriptivePredicates = rdf .dataset() .add(rdf.quad(SCHEMA_DOT_ORG.givenName, RDF.type, RDF.Property)); - const result = vocabGenerator.buildTemplateInput( + const result = await vocabGenerator.buildTemplateInput( VocabGenerator.merge([dataSetA, noDescriptivePredicates]), VocabGenerator.merge([noDescriptivePredicates]) ); @@ -497,14 +497,14 @@ describe("Artifact generator unit tests", () => { expect(result.properties[0].comment).toBe(""); }); - it("Should allow the prefix for the name of the module can be configured", () => { + it("Should allow the prefix for the name of the module can be configured", async () => { const generator = new VocabGenerator({ inputResources: [], artifactVersion: "1.0.0", moduleNamePrefix: "my-company-prefix-", }); - const result = generator.buildTemplateInput( + const result = await generator.buildTemplateInput( VocabGenerator.merge([dataset]), VocabGenerator.merge([]) ); @@ -512,14 +512,14 @@ describe("Artifact generator unit tests", () => { expect(result.artifactName).toBe("my-company-prefix-schema"); }); - it("Should create label vocab terms for literals", () => { + it("Should create label vocab terms for literals", async () => { const generator = new VocabGenerator({ inputResources: [], artifactVersion: "1.0.0", moduleNamePrefix: "my-company-prefix-", }); - const result = generator.buildTemplateInput( + const result = await generator.buildTemplateInput( VocabGenerator.merge([literalDataset]), VocabGenerator.merge([]) ); @@ -560,14 +560,14 @@ describe("Artifact generator unit tests", () => { ); }); - it("Should create comments vocab terms for literals", () => { + it("Should create comments vocab terms for literals", async () => { const generator = new VocabGenerator({ inputResources: [], artifactVersion: "1.0.0", moduleNamePrefix: "my-company-prefix-", }); - const result = generator.buildTemplateInput( + const result = await generator.buildTemplateInput( VocabGenerator.merge([literalDataset]), VocabGenerator.merge([]) ); @@ -608,14 +608,14 @@ describe("Artifact generator unit tests", () => { ); }); - it("Should create defination vocab terms for literals", () => { + it("Should create defination vocab terms for literals", async () => { const generator = new VocabGenerator({ inputResources: [], artifactVersion: "1.0.0", moduleNamePrefix: "my-company-prefix-", }); - const result = generator.buildTemplateInput( + const result = await generator.buildTemplateInput( VocabGenerator.merge([literalDataset]), VocabGenerator.merge([]) ); @@ -658,8 +658,8 @@ describe("Artifact generator unit tests", () => { }); describe("Vocab terms from extension dataset", () => { - it("should override label terms of the main datasets", () => { - const result = vocabGenerator.buildTemplateInput( + it("should override label terms of the main datasets", async () => { + const result = await vocabGenerator.buildTemplateInput( VocabGenerator.merge([ dataSetA, dataSetB, @@ -688,8 +688,8 @@ describe("Artifact generator unit tests", () => { expect(familyName.labels[0].value).toBe("Override Family Name"); }); - it("should override comment terms of the main datasets", () => { - const result = vocabGenerator.buildTemplateInput( + it("should override comment terms of the main datasets", async () => { + const result = await vocabGenerator.buildTemplateInput( VocabGenerator.merge([ dataSetA, dataSetB, @@ -722,8 +722,8 @@ describe("Artifact generator unit tests", () => { ); }); - it("should override label with alternativeNames from the vocab terms", () => { - const result = vocabGenerator.buildTemplateInput( + it("should override label with alternativeNames from the vocab terms", async () => { + const result = await vocabGenerator.buildTemplateInput( VocabGenerator.merge([ dataSetA, dataSetB, @@ -752,14 +752,14 @@ describe("Artifact generator unit tests", () => { expect(familyName.labels[0].value).toBe("Alt Family Name"); }); - it("Should create definition vocab terms for literals from extensions", () => { + it("Should create definition vocab terms for literals from extensions", async () => { const generator = new VocabGenerator({ inputResources: [], artifactVersion: "1.0.0", moduleNamePrefix: "my-company-prefix-", }); - const result = generator.buildTemplateInput( + const result = await generator.buildTemplateInput( VocabGenerator.merge([dataset, literalDataset]), VocabGenerator.merge([literalDataset]) ); @@ -800,8 +800,8 @@ describe("Artifact generator unit tests", () => { ); }); - it("should take description from the rdfs:comment of an owl:Ontology term", () => { - const result = vocabGenerator.buildTemplateInput( + it("should take description from the rdfs:comment of an owl:Ontology term", async () => { + const result = await vocabGenerator.buildTemplateInput( VocabGenerator.merge([dataset, owlOntologyDataset]), VocabGenerator.merge([owlOntologyDataset]) ); @@ -814,7 +814,7 @@ describe("Artifact generator unit tests", () => { ); }); - it("should default description to empty string if rdfs:comment of an owl:Ontology term is not found", () => { + it("should default description to empty string if rdfs:comment of an owl:Ontology term is not found", async () => { const owlOntologyDatasetWithNoComment = rdf.dataset().addAll([ rdf.quad(extSubject, RDF.type, OWL.Ontology), rdf.quad(extSubject, RDFS.label, rdf.literal("Extension label")), @@ -836,7 +836,7 @@ describe("Artifact generator unit tests", () => { ), ]); - const result = vocabGenerator.buildTemplateInput( + const result = await vocabGenerator.buildTemplateInput( VocabGenerator.merge([dataset, owlOntologyDatasetWithNoComment]), VocabGenerator.merge([owlOntologyDatasetWithNoComment]) ); @@ -848,8 +848,8 @@ describe("Artifact generator unit tests", () => { expect(result.descriptionFallback).toBeUndefined(); }); - it("should read authors from owl:Ontology terms", () => { - const result = vocabGenerator.buildTemplateInput( + it("should read authors from owl:Ontology terms", async () => { + const result = await vocabGenerator.buildTemplateInput( VocabGenerator.merge([dataset, owlOntologyDataset]), VocabGenerator.merge([owlOntologyDataset]) ); @@ -857,7 +857,7 @@ describe("Artifact generator unit tests", () => { expect(result.authorSet.has("Jarlath Holleran")); }); - it("should default to lit-js@inrupt.com if authors in not contained in owl:Ontology terms", () => { + it("should default to lit-js@inrupt.com if authors in not contained in owl:Ontology terms", async () => { const owlOntologyDatasetWithNoAuthor = rdf.dataset().addAll([ rdf.quad(extSubject, RDF.type, OWL.Ontology), rdf.quad(extSubject, RDFS.label, rdf.literal("Extension label")), @@ -879,7 +879,7 @@ describe("Artifact generator unit tests", () => { OWL.Class ), ]); - const result = vocabGenerator.buildTemplateInput( + const result = await vocabGenerator.buildTemplateInput( VocabGenerator.merge([dataset, owlOntologyDatasetWithNoAuthor]), VocabGenerator.merge([owlOntologyDatasetWithNoAuthor]) ); diff --git a/src/index.js b/src/index.js new file mode 100644 index 00000000..a0d12b3a --- /dev/null +++ b/src/index.js @@ -0,0 +1,22 @@ +#!/usr/bin/env node + +const processCommandLine = require("./commandLineProcessor"); + +try { + processCommandLine(true, process.argv.slice(2)).then((argv) => { + // TODO: This watcher clean up should really be inside the watcher code + // itself, but for that we need to mock `process.stdin` (to emulate the + // developer hitting the 'Enter' key), so just do it here for now! + if (argv._[0] === "watch") { + process.stdin.on("data", () => { + argv.unwatchFunction(); + process.exit(0); + }); + } + }); + + return 0; +} catch (error) { + console.log(`Error running Artifact Generator: [${error.toString()}]`); + return -2; +} diff --git a/test/resources/yamlConfig/vocab-list-noVersion.yml b/test/resources/yamlConfig/vocab-list-noVersion.yml deleted file mode 100644 index ee5bb225..00000000 --- a/test/resources/yamlConfig/vocab-list-noVersion.yml +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file contains a simple list of vocabularies that we bundle together to -# form the collective set vocabularies within a single artifact. -# -artifactName: generated-vocab-common-TEST -## -# The generator version MUST be present -## -# artifactGeneratorVersion: 0.1.0 - -artifactToGenerate: - - programmingLanguage: Java - artifactVersion: 3.2.1-SNAPSHOT - artifactNamePrefix: "" - artifactNameSuffix: "" - - javaPackageName: com.inrupt.testing - solidCommonVocabVersion: "0.1.0-SNAPSHOT" - artifactDirectoryName: Java - templateInternal: solidCommonVocabDependent/java/rdf4j/vocab.hbs - sourceFileExtension: java - -vocabList: - - description: Snippet of Schema.org from Google, Microsoft, Yahoo and Yandex - inputResources: - - ./schema-snippet.ttl - termSelectionResource: schema-inrupt-ext.ttl