diff --git a/README.md b/README.md index 6d94ff6..a66cc2c 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ Currently, the CLI supports the following commands: - `fusionauth email:upload` - Upload a specific template or all email templates to a FusionAuth server. - `fusionauth email:watch` - Watch the email template directory and upload changes to a FusionAuth server. - `fusionauth email:create` - Create a new email template locally. +Fake user generation + - `fusionauth import:generate` - Generate JSON for importing fake users for testing. - Lambdas - `fusionauth lambda:update` - Update a lambda on a FusionAuth server. - `fusionauth lambda:delete` - Delete a lambda from a FusionAuth server. diff --git a/package-lock.json b/package-lock.json index 2d43a84..fdbfe93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@commander-js/extra-typings": "11.0.0", + "@faker-js/faker": "^8.4.1", "@fusionauth/typescript-client": "1.47.0", "chalk": "5.3.0", "chokidar": "3.5.3", @@ -58,6 +59,21 @@ "node": ">=12" } }, + "node_modules/@faker-js/faker": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz", + "integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0", + "npm": ">=6.14.13" + } + }, "node_modules/@fusionauth/typescript-client": { "version": "1.47.0", "resolved": "https://registry.npmjs.org/@fusionauth/typescript-client/-/typescript-client-1.47.0.tgz", diff --git a/package.json b/package.json index d6f72ca..079355d 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "homepage": "https://github.com/FusionAuth/fusionauth-node-cli#readme", "dependencies": { "@commander-js/extra-typings": "11.0.0", + "@faker-js/faker": "^8.4.1", "@fusionauth/typescript-client": "1.47.0", "chalk": "5.3.0", "chokidar": "3.5.3", diff --git a/src/commands/import-generate.ts b/src/commands/import-generate.ts new file mode 100644 index 0000000..a0e635a --- /dev/null +++ b/src/commands/import-generate.ts @@ -0,0 +1,130 @@ +import {Command} from '@commander-js/extra-typings'; +import {FusionAuthClient} from '@fusionauth/typescript-client'; +import {readFile} from 'fs/promises'; +import chalk from 'chalk'; +import {join} from 'path'; +import {errorAndExit} from '../utils.js'; +import { faker } from '@faker-js/faker'; +import * as fs from 'fs'; + +const action = async function ({numberOfFiles, countPerFile, applicationId, groupId, tmpDir, filePrefix} +: { + numberOfFiles?: string | undefined; + countPerFile?: string | undefined; + applicationId?: string | undefined; + groupId?: string | undefined; + tmpDir?: string | undefined; + filePrefix?: string | undefined; +} +): Promise { + console.log(`Generating users`); + try { + const finalNumberOfFiles = (numberOfFiles !== undefined ? parseInt(numberOfFiles) : 10); + const finalCountPerFile = (countPerFile !== undefined ? parseInt(countPerFile) : 1000); + const finalTmpDir = (tmpDir !== undefined ? tmpDir : "tmp"); + const finalFilePrefix = (filePrefix !== undefined ? filePrefix : "output"); + const finalAppId = (applicationId !== undefined ? applicationId : '85a03867-dccf-4882-adde-1a79aeec50df'); + const finalGroupId = (groupId !== undefined ? groupId : 'a730d8c9-d060-4016-935e-170a5baaa4c7'); + + // Ensure the tmp directory exists + if (!fs.existsSync(finalTmpDir)) { + fs.mkdirSync(finalTmpDir, { recursive: true }); + } + + for (let i = 0; i < finalNumberOfFiles; i++) { + const jsonData = generateData(finalCountPerFile, finalAppId, finalGroupId, i * finalCountPerFile); + const filePath = join(finalTmpDir, finalFilePrefix + i); + fs.writeFile(filePath, JSON.stringify({"users": jsonData}), (err) => { + if (err) { + console.error('Error writing to file:', err); + return; + } + //console.log('Data has been written to', filePath); + }); + } + console.log(chalk.green(`Users generated`)); + } + catch (e: unknown) { + errorAndExit(`Error updating lambda: `, e); + } +} + +// noinspection JSUnusedGlobalSymbols +export const importGenerate = new Command('import:generate') + .description('Generate sample import data') + .option('-n, --numberOfFiles ', 'The number of files.') + .option('-c, --countPerFile ', 'The count of records per file.') + .option('-a, --applicationId ', 'The application to register users to.') + .option('-g, --groupId ', 'The group id to add users to.') + .option('-d, --tmpDir ', 'The directory to write files to.', 'tmp') + .option('-f, --filePrefix ', 'The file prefix for output files.', 'output') + .action(action); + + +function generateData(numObjects: number, appId: string, groupId: string, startNumber: number) { + const data = []; + for (let i = 0; i < numObjects; i++) { + const obj = { + active: true, + birthDate: faker.date.past().toISOString().split('T')[0], + data: { + displayName: faker.person.firstName() + ' ' + faker.person.lastName(), + favoriteColors: [faker.internet.color(), faker.internet.color()] + }, + email: `example${i + 1 + startNumber}@example.com`, + encryptionScheme: 'salted-pbkdf2-hmac-sha256', + expiry: faker.date.future().getTime(), + factor: 24000, + firstName: faker.person.firstName(), + fullName: faker.person.fullName(), + imageUrl: faker.image.url(), + insertInstant: faker.date.past().getTime(), + lastName: faker.person.lastName(), + memberships: [ + { + data: { + externalId: faker.string.uuid() + }, + groupId: groupId + } + ], + middleName: faker.person.middleName(), + mobilePhone: faker.phone.number(), + password: "yjs0Mj2qttSprPgtTVb+iGNMc66yBawfO1GXVTR3z7g=", + passwordChangeRequired: false, + preferredLanguages: ['en_US','en_GB'], + registrations: [ + { + applicationId: appId, + data: { + birthplace: faker.location.city() + }, + insertInstant: faker.date.past().getTime(), + preferredLanguages: ['en_US'], + username: faker.internet.userName(), + verified: faker.datatype.boolean() + } + ], + salt: 'k0eyvRy0S8lFp+IArLRGFJrm6dNM3tVGuAztU38lS3A=', + timezone: faker.location.timeZone(), + twoFactor: { + methods: [ + { + method: 'sms', + mobilePhone: faker.phone.number() + }, + { + method: 'email', + email: `example${i + 1 + startNumber}@example.com` + } + ] + }, + usernameStatus: 'ACTIVE', + username: `example${i + 1 + startNumber}`, + verified: faker.datatype.boolean() + }; + data.push(obj); + } + return data; +} + diff --git a/src/commands/index.ts b/src/commands/index.ts index d1dd673..0ec0594 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -12,3 +12,4 @@ export * from './message-upload.js'; export * from './theme-watch.js'; export * from './theme-upload.js'; export * from './theme-download.js'; +export * from './import-generate.js';