From d8a35f6561222136ed88524137ff20b6fefb54a3 Mon Sep 17 00:00:00 2001 From: vzhang03 Date: Thu, 1 Aug 2024 11:07:59 -0400 Subject: [PATCH] Finishing verbose mode and error messaging with CLI moduleg --- dev/v8-data/dataset_description.json | 79 ++++++++++++++++++++++++++++ package-lock.json | 18 ++----- packages/cli/package.json | 8 ++- packages/cli/src/data.ts | 18 +++---- packages/cli/src/index.ts | 31 ++++++++--- packages/metadata/src/PluginCache.ts | 12 ++--- packages/metadata/src/index.ts | 7 ++- 7 files changed, 129 insertions(+), 44 deletions(-) diff --git a/dev/v8-data/dataset_description.json b/dev/v8-data/dataset_description.json index 750d129..676815e 100644 --- a/dev/v8-data/dataset_description.json +++ b/dev/v8-data/dataset_description.json @@ -126,6 +126,85 @@ "levels": [ "y" ] + }, + { + "@type": "PropertyValue", + "name": "slider_start", + "description": "The starting value of the slider.", + "value": "number", + "minValue": 50, + "maxValue": 50 + }, + { + "@type": "PropertyValue", + "name": "correct", + "description": "`true` if the participant got the correct answer, `false` otherwise.", + "value": "boolean", + "levels": [ + false + ] + }, + { + "@type": "PropertyValue", + "name": "device_id", + "description": "The [device ID](https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo/deviceId) of the selected camera.", + "value": "string", + "levels": [ + "224904af1fc7ec895c764b361a04a167504d8af498a36c576e...", + "ed812fc33d145bfb870d47b64ec9702b3456f6640af448d78b..." + ] + }, + { + "@type": "PropertyValue", + "name": "extension_type", + "description": "The name(s) of the extension(s) used in the trial.", + "value": "string" + }, + { + "@type": "PropertyValue", + "name": "extension_version", + "description": "The version(s) of the extension(s) used in the trial.", + "value": "numeric" + }, + { + "@type": "PropertyValue", + "name": "record_video_data", + "description": "[Base 64 encoded](https://developer.mozilla.org/en-US/docs/Glossary/Base64) representation of the video data.", + "value": "string", + "levels": [ + "GkXfo6NChoEBQveBAULygQRC84EIQoKIbWF0cm9za2FCh4EEQo...", + "GkXfo59ChoEBQveBAULygQRC84EIQoKEd2VibUKHgQRChYECGF..." + ] + }, + { + "@type": "PropertyValue", + "name": "task", + "description": "unknown", + "value": "string", + "levels": [ + "draw", + "replay" + ] + }, + { + "@type": "PropertyValue", + "name": "mouse_tracking_data", + "description": "* An array of objects containing mouse movement data for the trial. Each object has an `x`, a `y`, a `t`, and an * `event` property. The `x` and `y` properties specify the mouse coordinates in pixels relative to the top left * corner of the viewport and `t` specifies the time in milliseconds since the start of the trial. The `event` * will be either 'mousemove', 'mousedown', or 'mouseup' depending on which event was generated.", + "value": "object" + }, + { + "@type": "PropertyValue", + "name": "mouse_tracking_targets", + "description": "* An object contain the pixel coordinates of elements on the screen specified by the `.targets` parameter. Each key * in this object will be a `selector` property, containing the CSS selector string used to find the element. The object * corresponding to each key will contain `x` and `y` properties specifying the top-left corner of the object, `width` * and `height` values, plus `top`, `bottom`, `left`, and `right` parameters which specify the * [bounding rectangle](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) of the element.", + "value": "object" + }, + { + "@type": "PropertyValue", + "name": "index", + "description": "The index of the current trial across the whole experiment.", + "value": "numeric", + "minValue": 0, + "maxValue": 16 } ] } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index fc1a549..bc24915 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8201,7 +8201,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -9205,7 +9204,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "dev": true, "engines": { "node": ">=6" } @@ -10816,7 +10814,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -17051,7 +17048,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -19441,7 +19437,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -19509,7 +19504,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -19524,7 +19518,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -19535,8 +19528,7 @@ "node_modules/wrap-ansi/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/wrappy": { "version": "1.0.2", @@ -19615,7 +19607,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -19639,7 +19630,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -19657,7 +19647,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "engines": { "node": ">=12" } @@ -19696,13 +19685,14 @@ }, "packages/cli": { "name": "jspsych-metadata-cli", - "version": "1.0.2", + "version": "1.0.3", "dependencies": { "@inquirer/prompts": "^5.1.2", "fuzzy": "^0.1.3", "inquirer": "^10.0.1", "inquirer-autocomplete-prompt": "^3.0.1", - "metadata": "file:../metadata/" + "metadata": "file:../metadata/", + "yargs": "^17.7.2" }, "bin": { "jspsych-metadata-cli": "dist/cjs/index.cjs" diff --git a/packages/cli/package.json b/packages/cli/package.json index 664f616..b33e276 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -9,7 +9,7 @@ "metadata", "cli" ], - "repository": { + "repository": { "url": "https://github.com/jspsych/metadata" }, "bin": { @@ -25,9 +25,7 @@ "data:abs": "node dist/esm/cli.js ~/Documents/work/ursi2024/metadata/dev/data ~/Documents/work/ursi2024/metadata/dev/options/metadata-options.json", "data:rec": "node dist/esm/cli.js ../../dev/data-rec", "options": "node dist/esm/cli.js ../../dev/data ../../dev/options/metadata-options.json", - "build": "npm run build:esm && npm run build:cjs", - "build:esm": "npm run build:no-prompt:esm && npm run build:data:esm && npm run build:validate:esm && npm run build:files:esm && npm run build:utils:esm && npm run build:cli:esm", "build:no-prompt:esm": "esbuild src/cli.ts --bundle --format=esm --platform=node --outfile=dist/esm/cli.js", "build:data:esm": "esbuild src/data.ts --packages=external --format=esm --platform=node --outfile=dist/esm/data.js", @@ -36,7 +34,6 @@ "build:utils:esm": "esbuild src/utils.ts --packages=external --format=esm --platform=node --outfile=dist/esm/utils.js", "build:cli:esm": "esbuild src/index.ts --packages=external --format=esm --platform=node --outfile=dist/esm/index.js", "cli": "node dist/esm/index.js", - "build:cjs": "npm run build:no-prompt:cjs && npm run build:data:cjs && npm run build:validate:cjs && npm run build:files:cjs && npm run build:utils:cjs && npm run build:cli:cjs", "build:no-prompt:cjs": "esbuild src/cli.ts --bundle --format=cjs --platform=node --outfile=dist/cjs/cli.js", "build:data:cjs": "esbuild src/data.ts --packages=external --format=cjs --platform=node --outfile=dist/cjs/data.js", @@ -50,6 +47,7 @@ "fuzzy": "^0.1.3", "inquirer": "^10.0.1", "inquirer-autocomplete-prompt": "^3.0.1", - "metadata": "file:../metadata/" + "metadata": "file:../metadata/", + "yargs": "^17.7.2" } } diff --git a/packages/cli/src/data.ts b/packages/cli/src/data.ts index c12e4f5..c19c334 100644 --- a/packages/cli/src/data.ts +++ b/packages/cli/src/data.ts @@ -11,7 +11,7 @@ export const generatePath = (inputPath) => { } }; -const copyFileWithStructure = async (sourceFilePath, targetDirectoryPath) => { +const copyFileWithStructure = async (sourceFilePath, verbose, targetDirectoryPath) => { try { sourceFilePath = expandHomeDir(sourceFilePath); targetDirectoryPath = expandHomeDir(targetDirectoryPath); @@ -25,16 +25,16 @@ const copyFileWithStructure = async (sourceFilePath, targetDirectoryPath) => { // Copy the file await fs.promises.copyFile(sourceFilePath, targetFilePath); - // console.log(`File copied from ${sourceFilePath} to ${targetFilePath}`); can delete + if (verbose) console.log(`File copied from ${sourceFilePath} to ${targetFilePath}`); } catch (error) { console.error(`Failed to copy file from ${sourceFilePath} to ${targetDirectoryPath}:`, error); } }; // processing single file, need to refactor this into a seperate call -const processFile = async (metadata, directoryPath, file, targetDirectoryPath?) => { +const processFile = async (metadata, directoryPath, file, verbose, targetDirectoryPath?) => { const filePath = path.join(directoryPath, file); - // console.log("Reading file:", filePath); -> does not need to count the number of files that read + if (verbose) console.log("Reading file:", filePath); try { const content = await fs.promises.readFile(filePath, "utf8"); @@ -53,7 +53,7 @@ const processFile = async (metadata, directoryPath, file, targetDirectoryPath?) return false; } - if (targetDirectoryPath) await copyFileWithStructure(filePath, targetDirectoryPath); // error catching to create backwards compability with CLI and old cli prompting + if (targetDirectoryPath) await copyFileWithStructure(filePath, verbose, targetDirectoryPath); // error catching to create backwards compability with CLI and old cli prompting } catch (err) { console.error(`Error reading file ${file}: ${err} Please ensure this is data generated by JsPsych.`); return false; @@ -63,7 +63,7 @@ const processFile = async (metadata, directoryPath, file, targetDirectoryPath?) } // Processing directory recursively up to one level -export const processDirectory = async (metadata, directoryPath, targetDirectoryPath?) => { +export const processDirectory = async (metadata, directoryPath, verbose, targetDirectoryPath?) => { let total = 0; let failed = 0; @@ -89,7 +89,7 @@ export const processDirectory = async (metadata, directoryPath, targetDirectoryP await processDirectoryRecursive(itemPath, level + 1); } else { total += 1; - if (!await processFile(metadata, currentPath, item.name, targetDirectoryPath)) failed += 1; // returns false if failed + if (!await processFile(metadata, currentPath, item.name, verbose, targetDirectoryPath)) failed += 1; // returns false if failed } } @@ -105,12 +105,12 @@ export const processDirectory = async (metadata, directoryPath, targetDirectoryP }; // Processing metadata options json -export const processOptions = async (metadata, filePath) => { +export const processOptions = async (metadata, filePath, verbose) => { try { const metadata_options_path = generatePath(filePath); const data = fs.readFileSync(metadata_options_path, "utf8"); // synchronous read - console.log("\nmetadata options:", data, "\n"); // log the raw data + if (verbose) console.log("\nmetadata options:", data, "\n"); // log the raw data var metadata_options = JSON.parse(data); // parse the JSON data metadata.updateMetadata(metadata_options); diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 0f80b41..afc2926 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,12 +1,23 @@ #!/usr/bin/env node +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; import { input, select } from '@inquirer/prompts'; import JsPsychMetadata from "metadata"; import { processDirectory, processOptions, saveTextToPath, loadMetadata } from "./data.js"; import { validateDirectory, validateJson } from './validateFunctions.js'; import { createDirectoryWithStructure } from './handleFiles.js'; -async function metadataOptionsPrompt(metadata){ +// Define a type for the parsed arguments +interface Args { + verbose?: boolean; + [x: string]: unknown; +} + +// Parse the arguments and cast them to the defined type +const argv = yargs(hideBin(process.argv)).argv as Args; + +async function metadataOptionsPrompt(metadata, verbose){ const answer = await select({ message: 'Would you like to customize the metadata by providing a .json specifying changes?', choices: [ @@ -34,7 +45,7 @@ async function metadataOptionsPrompt(metadata){ } }); - await processOptions(metadata, optionsPath); + await processOptions(metadata, optionsPath, verbose); } return optionsPath; @@ -108,7 +119,7 @@ const promptName = async () => { return project_name; } -const promptData = async (metadata, targetDirectoryPath) => { +const promptData = async (metadata, verbose, targetDirectoryPath) => { // can prompt an additional reading data -> keeps reading data until it is done and then writes it to the data_directory of the folder var data_path; @@ -122,23 +133,27 @@ const promptData = async (metadata, targetDirectoryPath) => { } }); - await processDirectory(metadata, data_path, targetDirectoryPath); // will check if already existing metadata and won't need to prompt + await processDirectory(metadata, data_path, verbose, targetDirectoryPath); // will check if already existing metadata and won't need to prompt } const main = async () => { - const metadata = new JsPsychMetadata(); - var [project_path, new_project] = await promptProjectStructure(metadata); // -> if reading from existing will want to look for if dataset_description file exists + const verbose = argv.verbose ? argv.verbose : false; + + const metadata = new JsPsychMetadata(verbose); + var [ project_path, new_project ] = await promptProjectStructure(metadata); // -> if reading from existing will want to look for if dataset_description file exists if (new_project) { const project_name = await promptName(); project_path = `${project_path}/${project_name}`; createDirectoryWithStructure(project_path); // change the message } - await promptData(metadata, `${project_path}/data`); + await promptData(metadata, verbose, `${project_path}/data`); - await metadataOptionsPrompt(metadata); // passing in options file to overwite existing file + await metadataOptionsPrompt(metadata, verbose); // passing in options file to overwite existing file const metadataString = JSON.stringify(metadata.getMetadata(), null, 2); // Assuming getMetadata() is the function that retrieves your metadata + + if (argv.verbose) console.log("Final metadata string:\n\n", metadataString); saveTextToPath(metadataString,`${project_path}/dataset_description.json`); }; diff --git a/packages/metadata/src/PluginCache.ts b/packages/metadata/src/PluginCache.ts index f28fa35..63fecc9 100644 --- a/packages/metadata/src/PluginCache.ts +++ b/packages/metadata/src/PluginCache.ts @@ -15,10 +15,10 @@ export class PluginCache { * @returns {Promise} The description of the plugin variable if found, otherwise null. * @throws Will throw an error if the fetch operation fails. */ - async getPluginInfo(pluginType: string, variableName: string, version, extension?) { + async getPluginInfo(pluginType: string, variableName: string, version, verbose, extension?) { // fetches if it doesn't exist if (!(pluginType in this.pluginFields)) { - const fields = await this.generatePluginFields(pluginType, version, extension); + const fields = await this.generatePluginFields(pluginType, version, verbose, extension); this.pluginFields[pluginType] = fields; } @@ -31,8 +31,8 @@ export class PluginCache { }; } - private async generatePluginFields(pluginType: string, version, extension?) { - const script = await this.fetchScript(pluginType, version, extension); + private async generatePluginFields(pluginType: string, version, verbose, extension?) { + const script = await this.fetchScript(pluginType, version, verbose, extension); // parses if they exist if (script !== undefined && script !== null && script !== ""){ @@ -65,10 +65,10 @@ export class PluginCache { else return `https://unpkg.com/@jspsych/plugin-${pluginType}/src/index.ts`; // most common case - plugin with no version } - private async fetchScript(pluginType: string, version: string, extension?: boolean) { + private async fetchScript(pluginType: string, version: string, verbose, extension?: boolean) { const unpkgUrl = this.generateUnpkg(pluginType, version, extension); - // console.log("-> fetching information for [", pluginType, "] from ->", unpkgUrl); should figure out verbose mode to display + if (verbose) console.log("-> fetching information for [", pluginType, "] from ->", unpkgUrl); try { const response = await fetch(unpkgUrl); diff --git a/packages/metadata/src/index.ts b/packages/metadata/src/index.ts index d42bec1..6ec0174 100644 --- a/packages/metadata/src/index.ts +++ b/packages/metadata/src/index.ts @@ -43,6 +43,8 @@ export default class JsPsychMetadata { "extension_version", ]); + private verbose: boolean = false; + /** * Creates an instance of JsPsychMetadata while passing in JsPsych object to have access to context * allowing it to access the screen printing information. @@ -50,7 +52,7 @@ export default class JsPsychMetadata { * @constructor * @param {JsPsych} JsPsych */ - constructor() { + constructor(verbose?: boolean) { this.metadata = {}; // generates default metadata this.setMetadataField("name", "title"); @@ -61,6 +63,7 @@ export default class JsPsychMetadata { this.authors = new AuthorsMap(); this.variables = new VariablesMap(); this.pluginCache = new PluginCache(); + this.verbose = verbose; } /** @@ -482,7 +485,7 @@ export default class JsPsychMetadata { * @throws Will throw an error if the fetch operation fails. */ private async getPluginInfo(pluginType: string, variableName: string, version, extension?) { - return this.pluginCache.getPluginInfo(pluginType, variableName, version, extension); + return this.pluginCache.getPluginInfo(pluginType, variableName, version, this.verbose, extension); } }