@vendure/cli
Version:
A modern, headless ecommerce framework
181 lines (177 loc) • 9.13 kB
JavaScript
"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