diff --git a/apps/getting-started/package.json b/apps/getting-started/package.json index eaf81a52..b75a2df6 100644 --- a/apps/getting-started/package.json +++ b/apps/getting-started/package.json @@ -6,15 +6,17 @@ "main": "index.js", "scripts": { "develop": "flatfile develop index.ts", + "deploy": "flatfile deploy index.ts", "paginate": "flatfile develop paginate-records.ts" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { + "@flatfile/api": "^1.8.5", "@flatfile/cross-env-config": "*", "@flatfile/hooks": "^1.3.0", - "@flatfile/api": "^1.8.5", + "@flatfile/listener": "^1.0.5", "@flatfile/plugin-record-hook": "^1.5.2", "actions": "^1.3.0", "ansi-colors": "^4.1.3", @@ -28,4 +30,4 @@ "devDependencies": { "@types/node": "^18.16.0" } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 614a6c0f..2eb08164 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "@flatfile/api": "^1.8.5", "@flatfile/cross-env-config": "*", "@flatfile/hooks": "^1.3.0", + "@flatfile/listener": "^1.0.5", "@flatfile/plugin-record-hook": "^1.5.2", "actions": "^1.3.0", "ansi-colors": "^4.1.3", @@ -33306,7 +33307,7 @@ }, "packages/embedded-utils": { "name": "@flatfile/embedded-utils", - "version": "1.2.4", + "version": "1.3.0", "license": "ISC", "dependencies": { "@flatfile/api": "^1.8.9", @@ -33430,10 +33431,10 @@ }, "packages/javascript": { "name": "@flatfile/javascript", - "version": "1.3.7", + "version": "1.3.9", "license": "ISC", "dependencies": { - "@flatfile/embedded-utils": "^1.2.4", + "@flatfile/embedded-utils": "^1.3.0", "@flatfile/listener": "^1.0.4", "@flatfile/plugin-record-hook": "^1.5.2" }, @@ -36769,7 +36770,7 @@ }, "packages/react": { "name": "@flatfile/react", - "version": "7.9.9", + "version": "7.9.10", "license": "ISC", "dependencies": { "@flatfile/api": "^1.8.5", @@ -38734,9 +38735,9 @@ }, "packages/vue": { "name": "@flatfile/vue", - "version": "1.0.17", + "version": "1.0.18", "dependencies": { - "@flatfile/embedded-utils": "1.2.4", + "@flatfile/embedded-utils": "1.3.0", "@flatfile/listener": "^1.0.4", "@flatfile/plugin-record-hook": "^1.5.2", "@vue/runtime-dom": "^3.3.4", diff --git a/packages/cli/src/x/actions/deploy.action.ts b/packages/cli/src/x/actions/deploy.action.ts index 9be81b6a..f889684f 100644 --- a/packages/cli/src/x/actions/deploy.action.ts +++ b/packages/cli/src/x/actions/deploy.action.ts @@ -20,6 +20,29 @@ const readPackageJson = util.promisify(require('read-package-json')) type ListenerTopics = Flatfile.EventTopic | '**' +/** + * @description Handles selecting an agent from a list of agents based on user input. + * If multiple agents are provided, it prompts the user to select one. Otherwise, it + * returns the first or only agent depending on the input. + * + * @param {Flatfile.Agent[] | undefined} data - list of Flatfile.Agent objects that + * can be filtered or selected by the user based on their slugs or other properties. + * + * @param {string | undefined} slug - identifier assigned to each agent in the + * environment, which the function uses to find and return the selected agent. + * + * @param {ora.Ora} validatingSpinner - ora spinner used to display informative failure + * messages when multiple agents are found in the environment. + * + * @returns {Flatfile.Agent} a selected agent from a list of multiple agents, based + * on user input. + * + * * `data`: The found agent or `undefined` if there's no data. + * * `slug`: The selected slug or `` if no slug was provided. + * * `validatingSpinner`: The Ora spinner used for validating the selection process. + * + * The output is directly returned without any additional summaries or information. + */ async function handleAgentSelection( data: Flatfile.Agent[] | undefined, slug: string | undefined, @@ -73,38 +96,99 @@ async function handleAgentSelection( } } -function findActiveTopics(allTopics: ListenerTopics[], client: any, topicsWithListeners = new Set()) { +/** + * @description Processes a list of listener topics and sub-clients to filter and + * identify active topics based on inclusion patterns. It iterates over the listeners, + * nodes, and topics to find matching and non-matching patterns and add them to a Set + * data structure for further processing. + * + * @param {ListenerTopics[]} allTopics - list of all topics that the listener is + * interested in, which are filtered and added to a new Set data structure to form + * the active topics list returned by the function. + * + * @param {any} client - client to search for listeners in, and its `listeners` + * property is traversed to find active topics. + * + * @param {new_expression} topicsWithListeners - Set of all the active topics that + * have listeners attached to them after filtering out any cron events and iterating + * over nested clients. + * + * @returns {object} a set of top-level and nested topics with listeners. + */ +function findActiveTopics( + allTopics: ListenerTopics[], + client: any, + topicsWithListeners = new Set() +) { client.listeners?.forEach((listener: ListenerTopics | ListenerTopics[]) => { - const listenerTopics = Array.isArray(listener[0]) ? listener[0] : [listener[0]] - listenerTopics.forEach(listenerTopic => { + const listenerTopics = Array.isArray(listener[0]) + ? listener[0] + : [listener[0]] + listenerTopics.forEach((listenerTopic) => { if (listenerTopic === '**') { // Filter cron events out of '**' list - they must be added explicitly - const filteredTopics = allTopics.filter(event => !event.startsWith('cron:')) - filteredTopics.forEach(topic => topicsWithListeners.add(topic)) + const filteredTopics = allTopics.filter( + (event) => !event.startsWith('cron:') + ) + filteredTopics.forEach((topic) => topicsWithListeners.add(topic)) } else if (listenerTopic.includes('**')) { const [prefix] = listenerTopic.split(':') - allTopics.forEach(topic => { if (topic.split(':')[0] === prefix) topicsWithListeners.add(topic) }) + allTopics.forEach((topic) => { + if (topic.split(':')[0] === prefix) topicsWithListeners.add(topic) + }) } else if (allTopics.includes(listenerTopic)) { topicsWithListeners.add(listenerTopic) } }) }) - client.nodes?.forEach((nestedClient: any) => findActiveTopics(allTopics, nestedClient, topicsWithListeners)) + client.nodes?.forEach((nestedClient: any) => + findActiveTopics(allTopics, nestedClient, topicsWithListeners) + ) return topicsWithListeners } -async function getActiveTopics(file: string): Promise{ +/** + * @description Retrieves a list of active topics from a given file and returns them + * as an array of `Flatfile.EventTopic`. + * + * @param {string} file - Flatfile file to be analyzed for active topics, and the + * function retrieves and returns an array of event topics from that file. + * + * @returns {Promise} an array of Flatfile event topics. + */ +async function getActiveTopics(file: string): Promise { const allTopics = Object.values(Flatfile.events.EventTopic) let mount try { mount = await import(url.pathToFileURL(file).href) - } catch(e) { + } catch (e) { return program.error(messages.error(e)) } - return Array.from(findActiveTopics(allTopics, mount.default)) as Flatfile.EventTopic[]; + return Array.from( + findActiveTopics(allTopics, mount.default) + ) as Flatfile.EventTopic[] } +/** + * @description * Deploys an event listener to a Flatfile environment using an + * authenticated API client + * * Creates a build package from a source file, validates it, and deploys it to the + * environment. + * + * @param {string | null | undefined} file - entry file for Flatfile, which is used + * to compile and deploy an event listener code package. + * + * @param {Partial<{ + * slug: string + * topics: string + * apiUrl: string + * token: string + * }>} options - 2-level nested object that allows passing various deployment + * configuration settings, including the `slug`, `topics`, `apiUrl`, and `token` properties. + * + * @returns {Promise} a list of events generated by the deployed event listener. + */ export async function deployAction( file?: string | null | undefined, options?: Partial<{ @@ -164,8 +248,10 @@ export async function deployAction( path.basename(file!) ) ) - + const fileContent = fs.readFileSync(file, 'utf8') + console.log({ file, fileContent }) const entry = result.split(path.sep).join(path.posix.sep) + // console.log({ entry }) fs.writeFileSync(path.join(outDir, '_entry.js'), entry, 'utf8') const buildingSpinner = ora({ text: `Building deployable code package`, @@ -204,9 +290,10 @@ export async function deployAction( }).start() try { - const { err, code } = await ncc(path.join(outDir, '_entry.js'), { + const { err, code, map } = await ncc(path.join(outDir, '_entry.js'), { minify: liteMode, target: 'es2020', + sourceMap: true, cache: false, // TODO: add debug flag to add this and other debug options quiet: true, @@ -215,7 +302,11 @@ export async function deployAction( const deployFile = path.join(outDir, 'deploy.js') fs.writeFileSync(deployFile, code, 'utf8') - const activeTopics: Flatfile.EventTopic[] = await getActiveTopics(deployFile) + const mapFile = path.join(outDir, 'deploy.js.map') + fs.writeFileSync(mapFile, map, 'utf8') + const activeTopics: Flatfile.EventTopic[] = await getActiveTopics( + deployFile + ) if (err) { return program.error(messages.error(err)) @@ -227,6 +318,9 @@ export async function deployAction( topics: activeTopics, compiler: 'js', source: code, + // TODO: Add this to the Agent Table + // @ts-ignore + map, slug: slug ?? selectedAgent?.slug, }, })