UNPKG

@vendure/cli

Version:

A modern, headless ecommerce framework

181 lines (177 loc) 9.13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.addJobQueueCommand = void 0; const prompts_1 = require("@clack/prompts"); const change_case_1 = require("change-case"); const ts_morph_1 = require("ts-morph"); const cli_command_1 = require("../../../shared/cli-command"); const plugin_resolution_1 = require("../../../shared/plugin-resolution"); const shared_prompts_1 = require("../../../shared/shared-prompts"); const ast_utils_1 = require("../../../utilities/ast-utils"); const utils_1 = require("../../../utilities/utils"); const cancelledMessage = 'Add API extension cancelled'; exports.addJobQueueCommand = new cli_command_1.CliCommand({ id: 'add-job-queue', category: 'Plugin: Job Queue', description: 'Defines an new job queue on a service', run: options => addJobQueue(options), }); async function addJobQueue(options) { var _a, _b; const providedVendurePlugin = options === null || options === void 0 ? void 0 : options.plugin; const { project } = await (0, shared_prompts_1.analyzeProject)({ providedVendurePlugin, cancelledMessage, config: options === null || options === void 0 ? void 0 : options.config, }); const { plugin: resolvedPlugin, shouldPromptForSelection } = (0, plugin_resolution_1.resolvePluginFromOptions)(project, { providedPlugin: providedVendurePlugin, pluginName: options === null || options === void 0 ? void 0 : options.pluginName, isNonInteractive: (options === null || options === void 0 ? void 0 : options.isNonInteractive) === true, }); if (options === null || options === void 0 ? void 0 : options.isNonInteractive) { if (!(options === null || options === void 0 ? void 0 : options.name)) { throw new Error('Job queue name must be specified in non-interactive mode.\n' + 'Usage: npx vendure add -j <PluginName> --name <job-queue-name> --selected-service <service-name>'); } if (!(options === null || options === void 0 ? void 0 : options.selectedService)) { throw new Error('Service must be specified in non-interactive mode.\n' + 'Usage: npx vendure add -j <PluginName> --name <job-queue-name> --selected-service <service-name>'); } } const plugin = resolvedPlugin !== null && resolvedPlugin !== void 0 ? resolvedPlugin : (await (0, shared_prompts_1.selectPlugin)(project, cancelledMessage)); let serviceRef; if (options === null || options === void 0 ? void 0 : options.isNonInteractive) { const existingServices = (0, shared_prompts_1.getServices)(project).filter(sr => { return sr.classDeclaration .getSourceFile() .getDirectoryPath() .includes(plugin.getSourceFile().getDirectoryPath()); }); const selectedService = existingServices.find(sr => sr.name === options.selectedService); if (!selectedService) { const availableServices = existingServices.map(sr => sr.name); if (availableServices.length === 0) { throw new Error(`No services found in plugin "${plugin.name}".\n` + 'Please first create a service using: npx vendure add -s <ServiceName>'); } else { throw new Error(`Service "${options.selectedService}" not found in plugin "${plugin.name}". Available services:\n` + availableServices.map(name => ` - ${name}`).join('\n')); } } serviceRef = selectedService; prompts_1.log.info(`Using service: ${serviceRef.name}`); } else { serviceRef = await (0, shared_prompts_1.selectServiceRef)(project, plugin); } if (!serviceRef) { throw new Error('Service is required for job queue'); } const jobQueueName = (_a = options === null || options === void 0 ? void 0 : options.name) !== null && _a !== void 0 ? _a : (await (0, utils_1.withInteractiveTimeout)(async () => { return await (0, prompts_1.text)({ message: 'What is the name of the job queue?', initialValue: 'my-background-task', validate: input => { if (!/^[a-z][a-z-0-9]+$/.test(input)) { return 'The job queue name must be lowercase and contain only letters, numbers and dashes'; } }, }); })); if (!(options === null || options === void 0 ? void 0 : options.isNonInteractive) && (0, prompts_1.isCancel)(jobQueueName)) { (0, prompts_1.cancel)(cancelledMessage); process.exit(0); } (0, ast_utils_1.addImportsToFile)(serviceRef.classDeclaration.getSourceFile(), { moduleSpecifier: '@vendure/core', namedImports: ['JobQueue', 'JobQueueService', 'SerializedRequestContext'], }); (0, ast_utils_1.addImportsToFile)(serviceRef.classDeclaration.getSourceFile(), { moduleSpecifier: '@vendure/common/lib/generated-types', namedImports: ['JobState'], }); (0, ast_utils_1.addImportsToFile)(serviceRef.classDeclaration.getSourceFile(), { moduleSpecifier: '@nestjs/common', namedImports: ['OnModuleInit'], }); serviceRef.injectDependency({ name: 'jobQueueService', type: 'JobQueueService', }); const jobQueuePropertyName = (0, change_case_1.camelCase)(jobQueueName) + 'Queue'; serviceRef.classDeclaration.insertProperty(0, { name: jobQueuePropertyName, scope: ts_morph_1.Scope.Private, type: writer => writer.write('JobQueue<{ ctx: SerializedRequestContext, someArg: string; }>'), }); serviceRef.classDeclaration.addImplements('OnModuleInit'); let onModuleInitMethod = serviceRef.classDeclaration.getMethod('onModuleInit'); if (!onModuleInitMethod) { const constructor = serviceRef.classDeclaration.getConstructors()[0]; const constructorChildIndex = (_b = constructor === null || constructor === void 0 ? void 0 : constructor.getChildIndex()) !== null && _b !== void 0 ? _b : 0; onModuleInitMethod = serviceRef.classDeclaration.insertMethod(constructorChildIndex + 1, { name: 'onModuleInit', isAsync: false, returnType: 'void', scope: ts_morph_1.Scope.Public, }); } onModuleInitMethod.setIsAsync(true); onModuleInitMethod.setReturnType('Promise<void>'); const body = onModuleInitMethod.getBody(); if (ts_morph_1.Node.isBlock(body)) { body.addStatements(writer => { writer .write(`this.${jobQueuePropertyName} = await this.jobQueueService.createQueue({ name: '${jobQueueName}', process: async job => { // Deserialize the RequestContext from the job data const ctx = RequestContext.deserialize(job.data.ctx); // The "someArg" property is passed in when the job is triggered const someArg = job.data.someArg; // Inside the \`process\` function we define how each job // in the queue will be processed. // Let's simulate some long-running task const totalItems = 10; for (let i = 0; i < totalItems; i++) { await new Promise(resolve => setTimeout(resolve, 500)); // You can optionally respond to the job being cancelled // during processing. This can be useful for very long-running // tasks which can be cancelled by the user. if (job.state === JobState.CANCELLED) { throw new Error('Job was cancelled'); } // Progress can be reported as a percentage like this job.setProgress(Math.floor(i / totalItems * 100)); } // The value returned from the \`process\` function is stored // as the "result" field of the job return { processedCount: totalItems, message: \`Successfully processed \${totalItems} items\`, }; }, })`) .newLine(); }).forEach(s => s.formatText()); } serviceRef.classDeclaration .addMethod({ name: `trigger${(0, change_case_1.pascalCase)(jobQueueName)}`, scope: ts_morph_1.Scope.Public, parameters: [{ name: 'ctx', type: 'RequestContext' }], statements: writer => { writer.write(`return this.${jobQueuePropertyName}.add({ ctx: ctx.serialize(), someArg: 'foo', })`); }, }) .formatText(); prompts_1.log.success(`New job queue created in ${serviceRef.name}`); await project.save(); return { project, modifiedSourceFiles: [serviceRef.classDeclaration.getSourceFile()], serviceRef }; } //# sourceMappingURL=add-job-queue.js.map