make-mp-data 1.0.14 โ 1.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/settings.json +4 -1
- package/cli.js +21 -1
- package/default.js +7 -6
- package/e2e.test.js +50 -0
- package/index.js +69 -32
- package/package.json +4 -2
- package/types.d.ts +53 -0
- package/utils.js +59 -12
package/.vscode/settings.json
CHANGED
package/cli.js
CHANGED
|
@@ -25,41 +25,61 @@ DOCS: https://github.com/ak--47/make-mp-data`)
|
|
|
25
25
|
.command('$0', 'model mixpanel data', () => { })
|
|
26
26
|
.option("token", {
|
|
27
27
|
demandOption: false,
|
|
28
|
+
alias: 't',
|
|
28
29
|
describe: 'project token; if supplied data will be sent to mixpanel',
|
|
29
30
|
type: 'string'
|
|
30
31
|
})
|
|
31
32
|
.option("seed", {
|
|
32
|
-
demandOption: false,
|
|
33
|
+
demandOption: false,
|
|
34
|
+
alias: 's',
|
|
33
35
|
describe: 'randomness seed; used to create distinct_ids',
|
|
34
36
|
type: 'string'
|
|
35
37
|
})
|
|
36
38
|
.option("format", {
|
|
37
39
|
demandOption: false,
|
|
38
40
|
default: 'csv',
|
|
41
|
+
alias: 'f',
|
|
39
42
|
describe: 'csv or json',
|
|
40
43
|
type: 'string'
|
|
41
44
|
})
|
|
42
45
|
.option("numDays", {
|
|
43
46
|
demandOption: false,
|
|
47
|
+
alias: 'd',
|
|
44
48
|
describe: 'number of days in past to model',
|
|
45
49
|
type: 'number',
|
|
46
50
|
})
|
|
47
51
|
.option("numUsers", {
|
|
48
52
|
demandOption: false,
|
|
53
|
+
alias: 'u',
|
|
49
54
|
describe: 'number of users to model',
|
|
50
55
|
type: 'number',
|
|
51
56
|
})
|
|
52
57
|
.option("numEvents", {
|
|
53
58
|
demandOption: false,
|
|
59
|
+
alias: 'e',
|
|
54
60
|
describe: 'number of events to model',
|
|
55
61
|
type: 'number',
|
|
56
62
|
})
|
|
57
63
|
.option("region", {
|
|
58
64
|
demandOption: false,
|
|
59
65
|
default: 'US',
|
|
66
|
+
alias: 'r',
|
|
60
67
|
describe: 'either US or EU',
|
|
61
68
|
type: 'string'
|
|
62
69
|
})
|
|
70
|
+
.option("writeToDisk", {
|
|
71
|
+
demandOption: false,
|
|
72
|
+
default: true,
|
|
73
|
+
describe: 'write data to disk',
|
|
74
|
+
alias: 'w',
|
|
75
|
+
type: 'boolean',
|
|
76
|
+
coerce: (value) => {
|
|
77
|
+
if (typeof value === 'string') {
|
|
78
|
+
return value.toLowerCase() === 'true';
|
|
79
|
+
}
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
})
|
|
63
83
|
.help()
|
|
64
84
|
.wrap(null)
|
|
65
85
|
.argv;
|
package/default.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const Chance = require('chance');
|
|
2
2
|
const chance = new Chance();
|
|
3
|
-
const { weightedRange, makeProducts, date } = require('./utils.js');
|
|
3
|
+
const { weightedRange, makeProducts, date, generateEmoji } = require('./utils.js');
|
|
4
4
|
|
|
5
5
|
const config = {
|
|
6
6
|
token: "",
|
|
@@ -36,7 +36,8 @@ const config = {
|
|
|
36
36
|
"event": "page view",
|
|
37
37
|
"weight": 10,
|
|
38
38
|
"properties": {
|
|
39
|
-
|
|
39
|
+
page: ["/", "/", "/help", "/account", "/watch", "/listen", "/product", "/people", "/peace"],
|
|
40
|
+
utm_source: ["$organic", "$organic", "$organic", "$organic", "google", "google", "google", "facebook", "facebook", "twitter", "linkedin"],
|
|
40
41
|
}
|
|
41
42
|
},
|
|
42
43
|
{
|
|
@@ -66,7 +67,8 @@ const config = {
|
|
|
66
67
|
}
|
|
67
68
|
],
|
|
68
69
|
superProps: {
|
|
69
|
-
platform: ["web", "mobile", "kiosk"],
|
|
70
|
+
platform: ["web", "mobile", "web", "mobile", "web", "kiosk"],
|
|
71
|
+
emotions: generateEmoji(),
|
|
70
72
|
|
|
71
73
|
},
|
|
72
74
|
/*
|
|
@@ -76,17 +78,16 @@ const config = {
|
|
|
76
78
|
userProps: {
|
|
77
79
|
title: chance.profession.bind(chance),
|
|
78
80
|
luckyNumber: weightedRange(42, 420),
|
|
79
|
-
|
|
81
|
+
vibe: generateEmoji(),
|
|
80
82
|
spiritAnimal: chance.animal.bind(chance)
|
|
81
83
|
},
|
|
82
84
|
|
|
83
85
|
scdProps: {
|
|
84
|
-
plan: ["free", "free", "free", "basic", "basic", "premium", "enterprise"],
|
|
86
|
+
plan: ["free", "free", "free", "free", "basic", "basic", "basic", "premium", "premium", "enterprise"],
|
|
85
87
|
MRR: weightedRange(0, 10000, 1000, .15),
|
|
86
88
|
NPS: weightedRange(0, 10, 150, 2),
|
|
87
89
|
marketingOptIn: [true, true, false],
|
|
88
90
|
dateOfRenewal: date(100, false),
|
|
89
|
-
|
|
90
91
|
},
|
|
91
92
|
|
|
92
93
|
/*
|
package/e2e.test.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* cSpell:disable */
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
/* eslint-disable no-undef */
|
|
4
|
+
/* eslint-disable no-debugger */
|
|
5
|
+
/* eslint-disable no-unused-vars */
|
|
6
|
+
const { generate } = require('./index.js');
|
|
7
|
+
require('dotenv').config();
|
|
8
|
+
const { execSync } = require("child_process");
|
|
9
|
+
const u = require('ak-tools');
|
|
10
|
+
|
|
11
|
+
const timeout = 60000;
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
describe('e2e', () => {
|
|
15
|
+
|
|
16
|
+
test('works as module', async () => {
|
|
17
|
+
console.log('MODULE TEST');
|
|
18
|
+
const results = await generate({ writeToDisk: false, numEvents: 1000, numUsers: 100, seed: "deal with it" });
|
|
19
|
+
const { eventData, groupProfilesData, lookupTableData, scdTableData, userProfilesData } = results;
|
|
20
|
+
expect(eventData.length).toBeGreaterThan(900);
|
|
21
|
+
expect(groupProfilesData.length).toBe(0);
|
|
22
|
+
expect(lookupTableData.length).toBe(0);
|
|
23
|
+
expect(scdTableData.length).toBeGreaterThan(200);
|
|
24
|
+
expect(userProfilesData.length).toBe(100);
|
|
25
|
+
|
|
26
|
+
}, timeout);
|
|
27
|
+
|
|
28
|
+
test('works as CLI', async () => {
|
|
29
|
+
console.log('CLI TEST');
|
|
30
|
+
const run = execSync(`node ./index.js --numEvents 1000 --numUsers 100 --seed "deal with it"`);
|
|
31
|
+
expect(run.toString().trim().includes('have a wonderful day :)')).toBe(true);
|
|
32
|
+
const csvs = (await u.ls('./data')).filter(a => a.includes('.csv'));
|
|
33
|
+
expect(csvs.length).toBe(5);
|
|
34
|
+
}, timeout);
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterAll(() => {
|
|
47
|
+
console.log('clearing...');
|
|
48
|
+
execSync(`npm run prune`);
|
|
49
|
+
console.log('...files cleared ๐');
|
|
50
|
+
});
|
package/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#! /usr/bin/env node
|
|
2
2
|
|
|
3
|
+
|
|
3
4
|
/*
|
|
4
5
|
make fake mixpanel data easily!
|
|
5
6
|
by AK
|
|
6
7
|
ak@mixpanel.com
|
|
7
8
|
*/
|
|
8
9
|
|
|
10
|
+
const RUNTIME = process.env.RUNTIME || "unspecified";
|
|
9
11
|
const mp = require("mixpanel-import");
|
|
10
12
|
const path = require("path");
|
|
11
13
|
const Chance = require("chance");
|
|
@@ -25,23 +27,37 @@ const {
|
|
|
25
27
|
range,
|
|
26
28
|
exhaust,
|
|
27
29
|
openFinder,
|
|
30
|
+
applySkew,
|
|
31
|
+
boxMullerRandom,
|
|
32
|
+
getUniqueKeys
|
|
28
33
|
} = require("./utils.js");
|
|
29
34
|
const dayjs = require("dayjs");
|
|
30
35
|
const utc = require("dayjs/plugin/utc");
|
|
31
|
-
const cliParams = require("./cli.js");
|
|
32
|
-
|
|
33
36
|
dayjs.extend(utc);
|
|
37
|
+
const cliParams = require("./cli.js");
|
|
38
|
+
// @ts-ignore
|
|
34
39
|
Array.prototype.pickOne = pick;
|
|
35
|
-
const
|
|
36
|
-
|
|
40
|
+
const NOW = dayjs().unix();
|
|
41
|
+
|
|
42
|
+
/** @typedef {import('./types.d.ts').Config} Config */
|
|
43
|
+
|
|
37
44
|
const PEAK_DAYS = [
|
|
38
|
-
dayjs().subtract(
|
|
45
|
+
dayjs().subtract(2, "day").unix(),
|
|
46
|
+
dayjs().subtract(3, "day").unix(),
|
|
39
47
|
dayjs().subtract(5, "day").unix(),
|
|
40
|
-
dayjs().subtract(
|
|
41
|
-
dayjs().subtract(
|
|
48
|
+
dayjs().subtract(7, "day").unix(),
|
|
49
|
+
dayjs().subtract(11, "day").unix(),
|
|
50
|
+
dayjs().subtract(13, "day").unix(),
|
|
51
|
+
dayjs().subtract(17, "day").unix(),
|
|
52
|
+
dayjs().subtract(19, "day").unix(),
|
|
53
|
+
dayjs().subtract(23, "day").unix(),
|
|
54
|
+
dayjs().subtract(29, "day").unix(),
|
|
42
55
|
];
|
|
43
56
|
|
|
44
|
-
|
|
57
|
+
/**
|
|
58
|
+
* generates fake mixpanel data
|
|
59
|
+
* @param {Config} config
|
|
60
|
+
*/
|
|
45
61
|
async function main(config) {
|
|
46
62
|
let {
|
|
47
63
|
seed = "every time a rug is micturated upon in this fair city...",
|
|
@@ -63,13 +79,31 @@ async function main(config) {
|
|
|
63
79
|
region = "US",
|
|
64
80
|
writeToDisk = false,
|
|
65
81
|
} = config;
|
|
66
|
-
|
|
82
|
+
|
|
83
|
+
//ensure we have a token or are writing to disk
|
|
84
|
+
if (require.main === module) {
|
|
85
|
+
if (!token) {
|
|
86
|
+
if (!writeToDisk) {
|
|
87
|
+
writeToDisk = true;
|
|
88
|
+
config.writeToDisk = true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
67
93
|
const uuidChance = new Chance(seed);
|
|
68
94
|
|
|
69
|
-
//the function which generates $distinct_id
|
|
95
|
+
//the function which generates $distinct_id + $created, skewing towards the present
|
|
70
96
|
function uuid() {
|
|
71
97
|
const distinct_id = uuidChance.guid();
|
|
72
|
-
|
|
98
|
+
let z = boxMullerRandom();
|
|
99
|
+
const skew = chance.normal({ mean: 10, dev: 3 });
|
|
100
|
+
z = applySkew(z, skew);
|
|
101
|
+
|
|
102
|
+
// Scale and shift the normally distributed value to fit the range of days
|
|
103
|
+
const maxZ = integer(2, 4);
|
|
104
|
+
const scaledZ = (z / maxZ + 1) / 2;
|
|
105
|
+
const daysAgoBorn = Math.round(scaledZ * (numDays - 1)) + 1;
|
|
106
|
+
|
|
73
107
|
return {
|
|
74
108
|
distinct_id,
|
|
75
109
|
...person(daysAgoBorn),
|
|
@@ -199,7 +233,14 @@ async function main(config) {
|
|
|
199
233
|
for (const writeData of datasetsToWrite) {
|
|
200
234
|
if (format === "csv") {
|
|
201
235
|
console.log(`writing ${path}`);
|
|
202
|
-
const
|
|
236
|
+
const columns = getUniqueKeys(writeData);
|
|
237
|
+
//papa parse needs nested JSON stringified
|
|
238
|
+
writeData.forEach((e) => {
|
|
239
|
+
for (const key in e) {
|
|
240
|
+
if (typeof e[key] === "object") e[key] = JSON.stringify(e[key]);
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
const csv = Papa.unparse(writeData, { columns });
|
|
203
244
|
await touch(path, csv);
|
|
204
245
|
console.log(`\tdone\n`);
|
|
205
246
|
} else {
|
|
@@ -214,13 +255,13 @@ async function main(config) {
|
|
|
214
255
|
const creds = { token };
|
|
215
256
|
/** @type {import('mixpanel-import').Options} */
|
|
216
257
|
const importOpts = {
|
|
258
|
+
region,
|
|
217
259
|
fixData: true,
|
|
218
260
|
verbose: false,
|
|
219
261
|
forceStream: true,
|
|
220
262
|
strict: false,
|
|
221
263
|
dryRun: false,
|
|
222
264
|
abridged: false,
|
|
223
|
-
region,
|
|
224
265
|
};
|
|
225
266
|
//send to mixpanel
|
|
226
267
|
if (token) {
|
|
@@ -300,25 +341,18 @@ function makeSCD(props, distinct_id, mutations, $created) {
|
|
|
300
341
|
return scdEntries;
|
|
301
342
|
}
|
|
302
343
|
|
|
303
|
-
function makeEvent(
|
|
304
|
-
distinct_id,
|
|
305
|
-
earliestTime,
|
|
306
|
-
events,
|
|
307
|
-
superProps,
|
|
308
|
-
groupKeys,
|
|
309
|
-
isFirstEvent = false
|
|
310
|
-
) {
|
|
344
|
+
function makeEvent(distinct_id, earliestTime, events, superProps, groupKeys, isFirstEvent = false) {
|
|
311
345
|
let chosenEvent = events.pickOne();
|
|
312
346
|
if (typeof chosenEvent === "string")
|
|
313
347
|
chosenEvent = { event: chosenEvent, properties: {} };
|
|
314
348
|
const event = {
|
|
315
349
|
event: chosenEvent.event,
|
|
316
350
|
distinct_id,
|
|
317
|
-
$source: "
|
|
351
|
+
$source: "AKsTimeSoup",
|
|
318
352
|
};
|
|
319
353
|
|
|
320
354
|
if (isFirstEvent) event.time = earliestTime;
|
|
321
|
-
if (!isFirstEvent) event.time =
|
|
355
|
+
if (!isFirstEvent) event.time = AKsTimeSoup(earliestTime, NOW, PEAK_DAYS);
|
|
322
356
|
|
|
323
357
|
const props = { ...chosenEvent.properties, ...superProps };
|
|
324
358
|
|
|
@@ -375,16 +409,16 @@ function buildFileNames(config) {
|
|
|
375
409
|
}
|
|
376
410
|
|
|
377
411
|
/**
|
|
378
|
-
*
|
|
412
|
+
* essentially, a timestamp generator with a twist
|
|
379
413
|
* @param {number} earliestTime - The earliest timestamp in Unix format.
|
|
380
414
|
* @param {number} latestTime - The latest timestamp in Unix format.
|
|
381
415
|
* @param {Array} peakDays - Array of Unix timestamps representing the start of peak days.
|
|
382
416
|
* @returns {number} - The generated event timestamp in Unix format.
|
|
383
417
|
*/
|
|
384
|
-
function
|
|
418
|
+
function AKsTimeSoup(earliestTime, latestTime = NOW, peakDays = PEAK_DAYS) {
|
|
385
419
|
// Define business hours
|
|
386
|
-
const peakStartHour =
|
|
387
|
-
const peakEndHour =
|
|
420
|
+
const peakStartHour = 4; // 4 AM
|
|
421
|
+
const peakEndHour = 23; // 11 PM
|
|
388
422
|
const likelihoodOfPeakDay = chance.integer({ min: integer(5, 42), max: integer(43, 69) }); // Randomize likelihood with CHAOS!~~
|
|
389
423
|
|
|
390
424
|
// Select a day, with a preference for peak days
|
|
@@ -393,7 +427,7 @@ function customTimeDistribution(earliestTime, latestTime, peakDays) {
|
|
|
393
427
|
selectedDay = peakDays.length > 0 ? chance.pickone(peakDays) : integer(earliestTime, latestTime);
|
|
394
428
|
} else {
|
|
395
429
|
// Introduce minor peaks by allowing some events to still occur during business hours
|
|
396
|
-
selectedDay = chance.bool({ likelihood:
|
|
430
|
+
selectedDay = chance.bool({ likelihood: integer(1, 42) })
|
|
397
431
|
? chance.pickone(peakDays)
|
|
398
432
|
: integer(earliestTime, latestTime);
|
|
399
433
|
}
|
|
@@ -407,12 +441,14 @@ function customTimeDistribution(earliestTime, latestTime, peakDays) {
|
|
|
407
441
|
let eventTime;
|
|
408
442
|
if (selectedDay === peakDays[0]) {
|
|
409
443
|
// Use a skewed distribution for peak days
|
|
410
|
-
eventTime = chance.normal({ mean: (businessEnd + businessStart) /
|
|
444
|
+
eventTime = chance.normal({ mean: (businessEnd + businessStart) / integer(1, 4), dev: (businessEnd - businessStart) / integer(2, 8) });
|
|
411
445
|
} else {
|
|
412
446
|
// For non-peak days, use a uniform distribution to add noise
|
|
413
|
-
eventTime = integer(businessStart, businessEnd);
|
|
447
|
+
eventTime = integer(integer(businessStart, businessEnd), integer(businessStart, businessEnd));
|
|
414
448
|
}
|
|
415
|
-
|
|
449
|
+
|
|
450
|
+
// usually, ensure the event time is within business hours
|
|
451
|
+
if (chance.bool({ likelihood: 42 })) eventTime = Math.min(Math.max(eventTime, businessStart), businessEnd);
|
|
416
452
|
|
|
417
453
|
return eventTime;
|
|
418
454
|
}
|
|
@@ -422,7 +458,7 @@ function customTimeDistribution(earliestTime, latestTime, peakDays) {
|
|
|
422
458
|
// this is for CLI
|
|
423
459
|
if (require.main === module) {
|
|
424
460
|
const args = cliParams();
|
|
425
|
-
const { token, seed, format, numDays, numUsers, numEvents, region } = args;
|
|
461
|
+
const { token, seed, format, numDays, numUsers, numEvents, region, writeToDisk } = args;
|
|
426
462
|
const suppliedConfig = args._[0];
|
|
427
463
|
|
|
428
464
|
//if the user specifics an separate config file
|
|
@@ -443,6 +479,7 @@ if (require.main === module) {
|
|
|
443
479
|
if (numUsers) config.numUsers = numUsers;
|
|
444
480
|
if (numEvents) config.numEvents = numEvents;
|
|
445
481
|
if (region) config.region = region;
|
|
482
|
+
if (writeToDisk) config.writeToDisk = writeToDisk;
|
|
446
483
|
|
|
447
484
|
main(config)
|
|
448
485
|
.then((data) => {
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "make-mp-data",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.16",
|
|
4
4
|
"description": "builds all mixpanel primitives for a given project",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"types": "types.d.ts",
|
|
6
7
|
"scripts": {
|
|
7
8
|
"start": "node index.js",
|
|
8
9
|
"prune": "rm ./data/*",
|
|
9
10
|
"go": "sh ./go.sh",
|
|
10
|
-
"post": "npm publish"
|
|
11
|
+
"post": "npm publish",
|
|
12
|
+
"test": "jest"
|
|
11
13
|
},
|
|
12
14
|
"repository": {
|
|
13
15
|
"type": "git",
|
package/types.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// types.d.ts
|
|
2
|
+
import { Chance } from "chance";
|
|
3
|
+
|
|
4
|
+
export interface Config {
|
|
5
|
+
token?: string;
|
|
6
|
+
seed?: string;
|
|
7
|
+
numDays?: number;
|
|
8
|
+
numEvents?: number;
|
|
9
|
+
numUsers?: number;
|
|
10
|
+
format?: "csv" | "json";
|
|
11
|
+
region?: string;
|
|
12
|
+
events?: EventConfig[];
|
|
13
|
+
superProps?: Record<string, string[]>; // Flexible for any string keys
|
|
14
|
+
userProps?: Record<string, any>; // Could be more specific based on actual usage
|
|
15
|
+
scdProps?: {
|
|
16
|
+
plan?: string[];
|
|
17
|
+
MRR?: number;
|
|
18
|
+
NPS?: number;
|
|
19
|
+
marketingOptIn?: boolean[];
|
|
20
|
+
dateOfRenewal?: Date;
|
|
21
|
+
};
|
|
22
|
+
groupKeys?: [string, number][];
|
|
23
|
+
groupProps?: Record<string, GroupProperty>; // Adjust according to usage
|
|
24
|
+
lookupTables?: LookupTable[];
|
|
25
|
+
writeToDisk?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface EventConfig {
|
|
29
|
+
event?: string;
|
|
30
|
+
weight?: number;
|
|
31
|
+
properties?: {
|
|
32
|
+
[key: string]: any; // Consider refining based on actual properties used
|
|
33
|
+
};
|
|
34
|
+
isFirstEvent?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface GroupProperty {
|
|
38
|
+
[key?: string]: any;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface LookupTable {
|
|
42
|
+
key?: string;
|
|
43
|
+
entries?: number;
|
|
44
|
+
attributes?: {
|
|
45
|
+
category?: string[];
|
|
46
|
+
demand?: string[];
|
|
47
|
+
supply?: string[];
|
|
48
|
+
manufacturer?: () => string;
|
|
49
|
+
price?: number;
|
|
50
|
+
rating?: number;
|
|
51
|
+
reviews?: number;
|
|
52
|
+
};
|
|
53
|
+
}
|
package/utils.js
CHANGED
|
@@ -20,12 +20,28 @@ function pick() {
|
|
|
20
20
|
function date(inTheLast = 30, isPast = true, format = 'YYYY-MM-DD') {
|
|
21
21
|
const now = dayjs.utc();
|
|
22
22
|
return function () {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
try {
|
|
24
|
+
const when = chance.integer({ min: 0, max: Math.abs(inTheLast) });
|
|
25
|
+
let then;
|
|
26
|
+
if (isPast) {
|
|
27
|
+
then = now.subtract(when, 'day')
|
|
28
|
+
.subtract(integer(0, 23), 'hour')
|
|
29
|
+
.subtract(integer(0, 59), 'minute')
|
|
30
|
+
.subtract(integer(0, 59), 'second');
|
|
31
|
+
}
|
|
32
|
+
if (!isPast) {
|
|
33
|
+
then = now.add(when, 'day')
|
|
34
|
+
.add(integer(0, 23), 'hour')
|
|
35
|
+
.add(integer(0, 59), 'minute')
|
|
36
|
+
.add(integer(0, 59), 'second');
|
|
37
|
+
}
|
|
38
|
+
if (format) return then?.format(format);
|
|
39
|
+
if (!format) return then?.toISOString();
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
if (format) return now?.format(format);
|
|
43
|
+
if (!format) return now?.toISOString();
|
|
44
|
+
}
|
|
29
45
|
};
|
|
30
46
|
}
|
|
31
47
|
|
|
@@ -74,6 +90,10 @@ function exhaust(arr) {
|
|
|
74
90
|
|
|
75
91
|
|
|
76
92
|
function integer(min, max) {
|
|
93
|
+
if (min === max) {
|
|
94
|
+
return min;
|
|
95
|
+
}
|
|
96
|
+
|
|
77
97
|
if (min > max) {
|
|
78
98
|
return chance.integer({
|
|
79
99
|
min: max,
|
|
@@ -81,10 +101,14 @@ function integer(min, max) {
|
|
|
81
101
|
});
|
|
82
102
|
}
|
|
83
103
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
104
|
+
if (min < max) {
|
|
105
|
+
return chance.integer({
|
|
106
|
+
min: min,
|
|
107
|
+
max: max
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return 0;
|
|
88
112
|
}
|
|
89
113
|
|
|
90
114
|
function makeProducts() {
|
|
@@ -168,7 +192,6 @@ function progress(thing, p) {
|
|
|
168
192
|
process.stdout.write(`${thing} processed ... ${comma(p)}`);
|
|
169
193
|
}
|
|
170
194
|
|
|
171
|
-
|
|
172
195
|
function person(bornDaysAgo = 30) {
|
|
173
196
|
//names and photos
|
|
174
197
|
const gender = chance.pickone(['male', 'female']);
|
|
@@ -213,7 +236,27 @@ function openFinder(path, callback) {
|
|
|
213
236
|
});
|
|
214
237
|
}
|
|
215
238
|
|
|
239
|
+
function getUniqueKeys(data) {
|
|
240
|
+
const keysSet = new Set();
|
|
241
|
+
data.forEach(item => {
|
|
242
|
+
Object.keys(item).forEach(key => keysSet.add(key));
|
|
243
|
+
});
|
|
244
|
+
return Array.from(keysSet);
|
|
245
|
+
}
|
|
216
246
|
|
|
247
|
+
//makes a random-sized array of emojis
|
|
248
|
+
function generateEmoji(max = 10, array = false) {
|
|
249
|
+
return function () {
|
|
250
|
+
const emojis = ['๐', '๐', '๐', '๐', '๐', '๐', '๐ก', '๐ฑ', '๐ญ', '๐ด', '๐คข', '๐ค ', '๐คก', '๐ฝ', '๐ป', '๐ฉ', '๐บ', '๐น', '๐พ', '๐ค', '๐ค', '๐ค', '๐ค', '๐ค', '๐ค', '๐', '๐', '๐', '๐', '๐', '๐', '๐ก', '๐ฑ', '๐ญ', '๐ด', '๐คข', '๐ค ', '๐คก', '๐ฝ', '๐ป', '๐ฉ', '๐บ', '๐น', '๐พ', '๐ค', '๐ค', '๐ค', '๐ค', '๐ค', '๐ค', '๐', '๐ฟ', '๐ฆ', '๐ง', '๐จ', '๐ฉ', '๐ด', '๐ต', '๐ถ', '๐ง', '๐ฎ', '๐ท', '๐', '๐ต', '๐ฉโโ๏ธ', '๐จโโ๏ธ', '๐ฉโ๐พ', '๐จโ๐พ', '๐ฉโ๐ณ', '๐จโ๐ณ', '๐ฉโ๐', '๐จโ๐', '๐ฉโ๐ค', '๐จโ๐ค', '๐ฉโ๐ซ', '๐จโ๐ซ', '๐ฉโ๐ญ', '๐จโ๐ญ', '๐ฉโ๐ป', '๐จโ๐ป', '๐ฉโ๐ผ', '๐จโ๐ผ', '๐ฉโ๐ง', '๐จโ๐ง', '๐ฉโ๐ฌ', '๐จโ๐ฌ', '๐ฉโ๐จ', '๐จโ๐จ', '๐ฉโ๐', '๐จโ๐', '๐ฉโโ๏ธ', '๐จโโ๏ธ', '๐ฉโ๐', '๐จโ๐', '๐ฉโโ๏ธ', '๐จโโ๏ธ', '๐คถ', '๐
', '๐ธ', '๐คด', '๐ฐ', '๐คต', '๐ผ', '๐คฐ', '๐', '๐', '๐
', '๐', '๐', '๐คฆ', '๐คท', '๐', '๐', '๐', '๐', '๐ด', '๐', '๐บ', '๐ถ', '๐', '๐คฒ', '๐', '๐', '๐', '๐ค', '๐', '๐', '๐', 'โ', '๐ค', '๐ค', '๐ค', 'โ๏ธ', '๐ค', '๐ค', '๐', '๐', '๐', '๐', '๐', 'โ๏ธ', 'โ', '๐ค', '๐', '๐', '๐', '๐ค', '๐ช', '๐', 'โ๏ธ', '๐คณ', '๐
', '๐', '๐', '๐ฃ', '๐', '๐', '๐ง ', '๐
', '๐', '๐', '๐', '๐ถ', '๐', '๐', '๐', '๐งฃ', '๐งค', '๐งฅ', '๐งฆ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐ ', '๐ก', '๐ข', '๐', '๐', '๐ฉ', '๐', '๐งข', 'โ', '๐ฟ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐ข', '๐ฃ', '๐ฏ', '๐', '๐', '๐ผ', '๐ต', '๐ถ', '๐', '๐', '๐', '๐ค', '๐ง', '๐ป', '๐ท', '๐ธ', '๐น', '๐บ', '๐ป', '๐ฅ', '๐ฑ', '๐ฒ', '๐ป', '๐ฅ', '๐จ', '๐ฑ', '๐ฒ', '๐น', '๐', '๐ฝ', '๐พ', '๐ฟ', '๐', '๐ผ', '๐ท', '๐ธ', '๐น', '๐ฅ', '๐ฝ', '๐', '๐', 'โ๏ธ', '๐', '๐ ', '๐บ', '๐ป', '๐', '๐ก', '๐', '๐', '๐ฌ', '๐ญ', '๐ก', '๐ก', '๐ฆ', '๐ฎ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐ฐ', '๐', '๐', '๐', '๐ท', '๐ฐ', '๐ด', '๐ต', '๐ถ', '๐ท', '๐ธ', '๐ณ', '๐งพ', '๐น', '๐ฑ', '๐ฒ', 'โ๏ธ', '๐ง', '๐จ', '๐ฉ', '๐ค', '๐ฅ', '๐ฆ', '๐ซ', '๐ช', '๐ฌ', '๐ญ', '๐ฎ', '๐ณ', 'โ๏ธ', 'โ๏ธ', '๐', '๐', '๐', '๐', '๐', '๐ผ', '๐', '๐', '๐', '๐
', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', 'โ๏ธ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐จ', 'โ', 'โ', '๐ ', '๐ก', 'โ๏ธ', '๐ซ', '๐น', '๐ก', '๐ง', '๐ฉ', 'โ๏ธ', '๐', 'โ๏ธ', '๐', 'โ', '๐งฐ', '๐งฒ', 'โ๏ธ', '๐งช', '๐งซ', '๐งฌ', '๐ฌ', '๐ญ', '๐ก', '๐', '๐', '๐', '๐', '๐ช', '๐ฝ', '๐ฟ', '๐', '๐งด', '๐งท', '๐งน', '๐งบ', '๐งป', '๐งผ', '๐งฝ', '๐งฏ', '๐ฌ', 'โฐ๏ธ', 'โฑ๏ธ', '๐ฟ', '๐บ', '๐งฑ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐ฎ', '๐', '๐งง', 'โ๏ธ', '๐ฉ', '๐จ', '๐ง'];
|
|
251
|
+
let num = integer(1, max);
|
|
252
|
+
let arr = [];
|
|
253
|
+
for (let i = 0; i < num; i++) {
|
|
254
|
+
arr.push(chance.pickone(emojis));
|
|
255
|
+
}
|
|
256
|
+
if (array) return arr;
|
|
257
|
+
if (!array) return arr.join(', ');
|
|
258
|
+
};
|
|
259
|
+
}
|
|
217
260
|
|
|
218
261
|
module.exports = {
|
|
219
262
|
weightedRange,
|
|
@@ -227,5 +270,9 @@ module.exports = {
|
|
|
227
270
|
choose,
|
|
228
271
|
range,
|
|
229
272
|
exhaust,
|
|
230
|
-
openFinder
|
|
273
|
+
openFinder,
|
|
274
|
+
applySkew,
|
|
275
|
+
boxMullerRandom,
|
|
276
|
+
generateEmoji,
|
|
277
|
+
getUniqueKeys
|
|
231
278
|
};
|