diff --git a/cli.mjs b/cli.mjs index b30b7ff8..82f8c900 100644 --- a/cli.mjs +++ b/cli.mjs @@ -1,10 +1,8 @@ import { checkbox, confirm, expand, input, select } from "@inquirer/prompts"; import fsExtra from "fs-extra"; -// TODO @brown-ccv #183: Upgrade to modular SDK instead of compat -import { cert, initializeApp } from "firebase-admin/app"; -import { getFirestore } from "firebase-admin/firestore"; -import { Command } from "commander"; +import { cert, initializeApp } from "firebase-admin/app"; // eslint-disable-line import/no-unresolved +import { getFirestore } from "firebase-admin/firestore"; // eslint-disable-line import/no-unresolved /** -------------------- GLOBALS -------------------- */ @@ -19,73 +17,16 @@ let OUTPUT_ROOT; // The root in which data is saved const INVALID_ACTION_ERROR = new Error("Invalid action: " + ACTION); const INVALID_DEPLOYMENT_ERROR = new Error("Invalid deployment: " + DEPLOYMENT); -/** -------------------- COMMANDER -------------------- */ -const commander = new Command(); -// default: [download | delete ] not provided, run main() as usual continuing with prompting -commander.action(() => {}); - -// download: optional argument studyID and participantID skips relative prompts -commander - .command(`download`) - .argument(`[studyID]`) - .argument(`[participantID]`) - .description(`Download experiment data from Firebase provided study ID and participant ID`) - .action((studyID, participantID) => { - ACTION = "download"; - STUDY_ID = studyID; - PARTICIPANT_ID = participantID; - }); - -// delete: optional argument studyID and participantID skips relative prompts -commander - .command(`delete`) - .argument(`[studyID]`) - .argument(`[participantID]`) - .description(`Delete experiment data from Firebase provided study ID and participant ID`) - .action((studyID, participantID) => { - ACTION = "delete"; - STUDY_ID = studyID; - PARTICIPANT_ID = participantID; - }); -commander.parse(); - -// print message if download or delete provided, along with optional args provided -if (ACTION !== undefined) { - console.log( - `${ACTION} data from Firebase given ${STUDY_ID === undefined ? "" : `study ID: ${STUDY_ID}`} ${PARTICIPANT_ID === undefined ? "" : `and participant ID: ${PARTICIPANT_ID}`}` - ); -} - /** -------------------- MAIN -------------------- */ // TODO @brown-ccv #289: Pass CLI arguments with commander (especially for action) async function main() { - if (ACTION === undefined) { - ACTION = await actionPrompt(); - } + ACTION = await actionPrompt(); DEPLOYMENT = await deploymentPrompt(); // TODO @brown-ccv #291: Enable downloading all study data at once - if (STUDY_ID === undefined) { - STUDY_ID = await studyIDPrompt(); - } else { - // when args directly passed in through CLI, check if study is valid - const studyCollection = await validateStudyFirebase(STUDY_ID); - if (!studyCollection) { - console.error("Please enter a valid study from your Firestore database"); - process.exit(1); - } - } + STUDY_ID = await studyIDPrompt(); // TODO @brown-ccv #291: Enable downloading all participant data at once - if (PARTICIPANT_ID === undefined) { - PARTICIPANT_ID = await participantIDPrompt(); - } else { - // when args directly passed in through CLI, check if participant is valid - const participantCollection = await validateParticipantFirebase(PARTICIPANT_ID); - if (!participantCollection) { - console.error(`Please enter a valid participant on the study "${STUDY_ID}"`); - process.exit(1); - } - } + PARTICIPANT_ID = await participantIDPrompt(); EXPERIMENT_IDS = await experimentIDPrompt(); switch (ACTION) { @@ -113,6 +54,7 @@ async function main() { } } main(); + /** -------------------- DOWNLOAD ACTION -------------------- */ /** Download data that's stored in Firebase */ @@ -244,14 +186,20 @@ async function deploymentPrompt() { /** Prompt the user to enter the ID of a study */ async function studyIDPrompt() { const invalidMessage = "Please enter a valid study from your Firestore database"; + const validateStudyFirebase = async (input) => { + // subcollection is programmatically generated, if it doesn't exist then input must not be a valid studyID + const studyIDCollections = await getStudyRef(input).listCollections(); + return studyIDCollections.find((c) => c.id === PARTICIPANTS_COL) ? true : invalidMessage; + }; + return await input({ message: "Select a study:", validate: async (input) => { if (!input) return invalidMessage; + switch (DEPLOYMENT) { case "firebase": - const studyCollection = await validateStudyFirebase(input); - return studyCollection || invalidMessage; + return validateStudyFirebase(input); default: throw INVALID_DEPLOYMENT_ERROR; } @@ -262,6 +210,12 @@ async function studyIDPrompt() { /** Prompt the user to enter the ID of a participant on the STUDY_ID study */ async function participantIDPrompt() { const invalidMessage = `Please enter a valid participant on the study "${STUDY_ID}"`; + const validateParticipantFirebase = async (input) => { + // subcollection is programmatically generated, if it doesn't exist then input must not be a valid participantID + const studyIDCollections = await getParticipantRef(STUDY_ID, input).listCollections(); + return studyIDCollections.find((c) => c.id === DATA_COL) ? true : invalidMessage; + }; + return await input({ message: "Select a participant:", validate: async (input) => { @@ -271,8 +225,7 @@ async function participantIDPrompt() { switch (DEPLOYMENT) { case "firebase": - const participantCollection = await validateParticipantFirebase(input); - return participantCollection || invalidMessage; + return validateParticipantFirebase(input); default: throw INVALID_DEPLOYMENT_ERROR; } @@ -359,21 +312,6 @@ async function confirmOverwritePrompt(file, overwriteAll) { return answer; } -/** -------------------- FIRESTORE VALIDATIONS -------------------- */ -/** helper to check if the given study (input) is in firestore */ -async function validateStudyFirebase(input) { - // subcollection is programmatically generated, if it doesn't exist then input must not be a valid studyID - const studyIDCollections = await getStudyRef(input).listCollections(); - return studyIDCollections.find((c) => c.id === PARTICIPANTS_COL); -} - -/** helper to check if the given participant (input) is in firestore under study */ -async function validateParticipantFirebase(input) { - // subcollection is programmatically generated, if it doesn't exist then input must not be a valid participantID - const studyIDCollections = await getParticipantRef(STUDY_ID, input).listCollections(); - return studyIDCollections.find((c) => c.id === DATA_COL); -} - /** -------------------- FIRESTORE HELPERS -------------------- */ const RESPONSES_COL = "participant_responses"; diff --git a/package-lock.json b/package-lock.json index 47f00e60..1981eae6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,6 @@ "@jspsych/plugin-instructions": "^1.1.3", "@jspsych/plugin-preload": "^1.1.2", "@jspsych/plugin-survey": "^1.0.1", - "commander": "^12.1.0", "electron-log": "^5.0.0", "electron-squirrel-startup": "^1.0.0", "firebase": "^10.11.0", @@ -656,15 +655,6 @@ "node": ">= 16.4.0" } }, - "node_modules/@electron-forge/cli/node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/@electron-forge/cli/node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -6316,11 +6306,13 @@ } }, "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 6" } }, "node_modules/commitizen": { @@ -9646,15 +9638,6 @@ "node": ">= 10" } }, - "node_modules/firebase-tools/node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/firebase-tools/node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -12556,6 +12539,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lint-staged/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/lint-staged/node_modules/emoji-regex": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", diff --git a/package.json b/package.json index 7f79f107..83ea4663 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,6 @@ "@jspsych/plugin-instructions": "^1.1.3", "@jspsych/plugin-preload": "^1.1.2", "@jspsych/plugin-survey": "^1.0.1", - "commander": "^12.1.0", "electron-log": "^5.0.0", "electron-squirrel-startup": "^1.0.0", "firebase": "^10.11.0",