@jungvonmatt/contentful-fakes 1.4.0
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/.env +3 -0
- package/.eslintrc.cjs +16 -0
- package/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +40 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +64 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +27 -0
- package/dist/lib/faker.d.ts +8 -0
- package/dist/lib/faker.js +318 -0
- package/dist/lib/helper.d.ts +2 -0
- package/dist/lib/helper.js +9 -0
- package/package.json +49 -0
- package/src/cli.ts +97 -0
- package/src/index.ts +47 -0
- package/src/lib/faker.ts +451 -0
- package/src/lib/helper.ts +11 -0
- package/tsconfig.json +9 -0
package/.env
ADDED
package/.eslintrc.cjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
root: true,
|
|
3
|
+
plugins: ['prettier'],
|
|
4
|
+
extends: ['xo', 'xo-typescript/space', 'plugin:prettier/recommended'],
|
|
5
|
+
parser: '@typescript-eslint/parser',
|
|
6
|
+
parserOptions: {
|
|
7
|
+
project: ['./tsconfig.json'],
|
|
8
|
+
tsconfigRootDir: __dirname,
|
|
9
|
+
extraFileExtensions: ['.cjs'],
|
|
10
|
+
},
|
|
11
|
+
ignorePatterns: ['**/*.cjs', 'src/**/*.test.ts', 'src/__test__/*'],
|
|
12
|
+
env: {
|
|
13
|
+
node: true,
|
|
14
|
+
jest: true,
|
|
15
|
+
}
|
|
16
|
+
};
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
# [1.4.0](https://github.com/jungvonmatt/contentful-ssg/compare/v1.3.2...v1.4.0) (2022-01-19)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* 🎸 Adds new contentful-fakes package ([bb67694](https://github.com/jungvonmatt/contentful-ssg/commit/bb676946532281864c6355f294e984f792f9cfa3))
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Jung von Matt TECH (https://www.jvm.com/)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[![NPM version][npm-image]][npm-url]
|
|
2
|
+
|
|
3
|
+
# JvM Contentful fake data generator
|
|
4
|
+
|
|
5
|
+
Generate fake data files based on your Contentful content models
|
|
6
|
+
|
|
7
|
+
## Getting started
|
|
8
|
+
|
|
9
|
+
### Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install --save-dev @jungvonmatt/contentful-fakes
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Commands
|
|
16
|
+
|
|
17
|
+
### help
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx contentful-fakes help [command]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### create
|
|
24
|
+
|
|
25
|
+
Fetch all content entries and store them as yaml in the configured directory
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx contentful-fakes create [options]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
#### options
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
-c, --content-type` `<content-type...> Specify content-types
|
|
35
|
+
-e, --extension <extension> Specify output format (default: "yaml")
|
|
36
|
+
-o, --output-directory <directory> Specify output directory (default: "data")
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
[npm-url]: https://www.npmjs.com/package/@jungvonmatt/contentful-fakes
|
|
40
|
+
[npm-image]: https://img.shields.io/npm/v/@jungvonmatt/contentful-fakes.svg
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import { outputFile } from 'fs-extra';
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import dotenv from 'dotenv';
|
|
7
|
+
import dotenvExpand from 'dotenv-expand';
|
|
8
|
+
import { getConfig } from '@jungvonmatt/contentful-ssg/lib/config';
|
|
9
|
+
import { forEachAsync } from '@jungvonmatt/contentful-ssg/lib/array';
|
|
10
|
+
import { stringify } from '@jungvonmatt/contentful-ssg/converter';
|
|
11
|
+
import { logError, askMissing, confirm } from '@jungvonmatt/contentful-ssg/lib/ui';
|
|
12
|
+
import { createFakes } from './index.js';
|
|
13
|
+
const env = dotenv.config();
|
|
14
|
+
dotenvExpand(env);
|
|
15
|
+
const parseArgs = (cmd) => ({
|
|
16
|
+
environment: cmd.env,
|
|
17
|
+
verbose: Boolean(cmd.verbose),
|
|
18
|
+
contentTypes: cmd.contentType,
|
|
19
|
+
});
|
|
20
|
+
const errorHandler = (error, silence) => {
|
|
21
|
+
if (!silence) {
|
|
22
|
+
const { errors } = error;
|
|
23
|
+
logError(error);
|
|
24
|
+
(errors || []).forEach((error) => {
|
|
25
|
+
logError(error);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
process.exit(1);
|
|
29
|
+
};
|
|
30
|
+
const actionRunner = (fn, log = true) => (...args) => fn(...args).catch((error) => errorHandler(error, !log));
|
|
31
|
+
const program = new Command();
|
|
32
|
+
program
|
|
33
|
+
.command('create')
|
|
34
|
+
.description('Create fake objects')
|
|
35
|
+
.option('-c, --content-type <content-type...>', 'Specify content-types')
|
|
36
|
+
.option('-e, --extension <extension>', 'Specify output format', 'yaml')
|
|
37
|
+
.option('-o, --output-directory <directory>', 'Specify output directory', 'data')
|
|
38
|
+
.option('-f, --force', 'Overwrite')
|
|
39
|
+
.action(actionRunner(async (cmd) => {
|
|
40
|
+
const contentTypes = cmd?.contentType ?? [];
|
|
41
|
+
const force = cmd?.force ?? false;
|
|
42
|
+
const format = cmd?.extension ?? '';
|
|
43
|
+
const outputDirectory = cmd?.outputDirectory ?? '';
|
|
44
|
+
const config = await getConfig(parseArgs(cmd));
|
|
45
|
+
const verified = await askMissing(config);
|
|
46
|
+
const fakes = await createFakes(verified, contentTypes);
|
|
47
|
+
await forEachAsync(Object.entries(fakes), async ([contentTypeId, fakeData]) => {
|
|
48
|
+
const [defaultData, minData] = fakeData;
|
|
49
|
+
const dir = path.join(outputDirectory, contentTypeId);
|
|
50
|
+
const filenameDefault = path.join(dir, `default.${format}`);
|
|
51
|
+
const filenameMin = path.join(dir, `min.${format}`);
|
|
52
|
+
if (!existsSync(filenameDefault) ||
|
|
53
|
+
force ||
|
|
54
|
+
(await confirm(`Overwrite file: ${filenameDefault}`))) {
|
|
55
|
+
await outputFile(filenameDefault, stringify(defaultData, format));
|
|
56
|
+
}
|
|
57
|
+
if (!existsSync(filenameMin) ||
|
|
58
|
+
force ||
|
|
59
|
+
(await confirm(`Overwrite file: ${filenameMin}`))) {
|
|
60
|
+
await outputFile(filenameMin, stringify(minData, format));
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}));
|
|
64
|
+
program.parse(process.argv);
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getEnvironment } from '@jungvonmatt/contentful-ssg/lib/contentful';
|
|
2
|
+
import { getMockData } from './lib/faker.js';
|
|
3
|
+
export async function createFakes(config, contentTypeIds) {
|
|
4
|
+
const environment = await getEnvironment(config);
|
|
5
|
+
const { items: contentTypes } = await environment.getContentTypes();
|
|
6
|
+
const { items } = await environment.getEditorInterfaces();
|
|
7
|
+
const interfaces = Object.fromEntries(items
|
|
8
|
+
.filter((item) => !contentTypeIds.length || contentTypeIds.includes(item.sys.contentType.sys.id))
|
|
9
|
+
.map((item) => {
|
|
10
|
+
const { id } = item.sys.contentType.sys;
|
|
11
|
+
const contentType = contentTypes.find((ct) => ct.sys.id === id);
|
|
12
|
+
const { controls } = item;
|
|
13
|
+
const fields = controls.map((control) => {
|
|
14
|
+
const { fieldId } = control;
|
|
15
|
+
const fieldSettings = contentType.fields.find((field) => field.id === fieldId);
|
|
16
|
+
return { interface: control, settings: fieldSettings };
|
|
17
|
+
});
|
|
18
|
+
return [id, fields];
|
|
19
|
+
}));
|
|
20
|
+
const data = await getMockData(interfaces);
|
|
21
|
+
return Object.fromEntries(Object.entries(interfaces).map(([contentTypeId, fields]) => {
|
|
22
|
+
const { [contentTypeId]: fakeData = {} } = data;
|
|
23
|
+
const required = Object.fromEntries(fields.map((field) => [field.settings.id, field.settings.required]));
|
|
24
|
+
const minData = Object.fromEntries(Object.entries(fakeData).filter(([fieldId]) => required?.[fieldId]));
|
|
25
|
+
return [contentTypeId, [fakeData, minData]];
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ContentFields, KeyValueMap, Control } from 'contentful-management/types';
|
|
2
|
+
export declare type FieldInfo = {
|
|
3
|
+
interface: Control;
|
|
4
|
+
settings: Partial<ContentFields>;
|
|
5
|
+
};
|
|
6
|
+
export declare type ContentTypes = Record<string, FieldInfo[]>;
|
|
7
|
+
export declare const getMockData: (contentTypes: ContentTypes) => Promise<KeyValueMap>;
|
|
8
|
+
export declare const createData: (fields: FieldInfo[]) => Promise<KeyValueMap>;
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { FIELD_TYPE_SYMBOL, FIELD_TYPE_TEXT, FIELD_TYPE_RICHTEXT, FIELD_TYPE_NUMBER, FIELD_TYPE_INTEGER, FIELD_TYPE_DATE, FIELD_TYPE_LOCATION, FIELD_TYPE_ARRAY, FIELD_TYPE_BOOLEAN, FIELD_TYPE_LINK, FIELD_TYPE_OBJECT, LINK_TYPE_ASSET, LINK_TYPE_ENTRY, } from '@jungvonmatt/contentful-ssg/lib/contentful';
|
|
2
|
+
import faker from 'faker';
|
|
3
|
+
import casual from 'casual';
|
|
4
|
+
import RandExp from 'randexp';
|
|
5
|
+
import { oneOf, randomInt } from './helper.js';
|
|
6
|
+
export const getMockData = async (contentTypes) => {
|
|
7
|
+
const dataEntries = await Promise.all(Object.entries(contentTypes).map(async ([name, fields]) => [name, await createData(fields)]));
|
|
8
|
+
return Object.fromEntries(dataEntries);
|
|
9
|
+
};
|
|
10
|
+
export const createData = async (fields) => {
|
|
11
|
+
const entries = await Promise.all(fields.map(async (field) => {
|
|
12
|
+
const id = field?.settings?.id || 'undefined';
|
|
13
|
+
switch (field.settings.type) {
|
|
14
|
+
case FIELD_TYPE_SYMBOL:
|
|
15
|
+
return [id, await getSymbolFake(field)];
|
|
16
|
+
case FIELD_TYPE_INTEGER:
|
|
17
|
+
return [id, await getIntegerFake(field)];
|
|
18
|
+
case FIELD_TYPE_NUMBER:
|
|
19
|
+
return [id, await getNumberFake(field)];
|
|
20
|
+
case FIELD_TYPE_TEXT:
|
|
21
|
+
return [id, await getTextFake(field)];
|
|
22
|
+
case FIELD_TYPE_LINK:
|
|
23
|
+
return [id, await getLinkFake(field)];
|
|
24
|
+
case FIELD_TYPE_ARRAY:
|
|
25
|
+
return [id, await getArrayFake(field)];
|
|
26
|
+
case FIELD_TYPE_BOOLEAN:
|
|
27
|
+
return [id, faker.datatype.boolean()];
|
|
28
|
+
case FIELD_TYPE_DATE:
|
|
29
|
+
return [id, faker.date.future()];
|
|
30
|
+
case FIELD_TYPE_RICHTEXT:
|
|
31
|
+
return [id, await getRichtextFake(field)];
|
|
32
|
+
case FIELD_TYPE_OBJECT:
|
|
33
|
+
return [id, {}];
|
|
34
|
+
case FIELD_TYPE_LOCATION:
|
|
35
|
+
return [
|
|
36
|
+
id,
|
|
37
|
+
{
|
|
38
|
+
lat: faker.address.latitude(),
|
|
39
|
+
lng: faker.address.longitude(),
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
default:
|
|
43
|
+
return [id, undefined];
|
|
44
|
+
}
|
|
45
|
+
}));
|
|
46
|
+
return Object.fromEntries(entries);
|
|
47
|
+
};
|
|
48
|
+
const getRegexValue = (regex) => {
|
|
49
|
+
const { pattern, flags } = regex;
|
|
50
|
+
if (pattern === '^\\w[\\w.-]*@([\\w-]+\\.)+[\\w-]+$') {
|
|
51
|
+
return faker.internet.exampleEmail();
|
|
52
|
+
}
|
|
53
|
+
if (pattern.includes('\\/\\/(\\w+:{0,1}\\w*@)?(\\S+)(:[0-9]+)?(\\/|\\/([\\w#!:.?+=&%@!\\-/]))?$')) {
|
|
54
|
+
return faker.internet.url();
|
|
55
|
+
}
|
|
56
|
+
if (pattern === '^(0?[1-9]|1[012])[- /.](0?[1-9]|[12][0-9]|3[01])[- /.](19|20)?\\d\\d$') {
|
|
57
|
+
return casual.time('MM-DD-YYYY');
|
|
58
|
+
}
|
|
59
|
+
if (pattern === '^(0?[1-9]|[12][0-9]|3[01])[- /.](0?[1-9]|1[012])[- /.](19|20)?\\d\\d$') {
|
|
60
|
+
return casual.time('DD-MM-YYYY');
|
|
61
|
+
}
|
|
62
|
+
if (pattern === '^(0?[1-9]|1[012]):[0-5][0-9](:[0-5][0-9])?\\s*[aApP][mM]$') {
|
|
63
|
+
return casual.time('hh:mm a');
|
|
64
|
+
}
|
|
65
|
+
if (pattern === '^(0?[0-9]|1[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$') {
|
|
66
|
+
return casual.time('HH:mm');
|
|
67
|
+
}
|
|
68
|
+
if (pattern === '^\\d[ -.]?\\(?\\d\\d\\d\\)?[ -.]?\\d\\d\\d[ -.]?\\d\\d\\d\\d$') {
|
|
69
|
+
return faker.phone.phoneNumber();
|
|
70
|
+
}
|
|
71
|
+
if (pattern === '^\\d{5}$|^\\d{5}-\\d{4}$}') {
|
|
72
|
+
return faker.address.zipCode();
|
|
73
|
+
}
|
|
74
|
+
return new RandExp(pattern, flags);
|
|
75
|
+
};
|
|
76
|
+
const getTextForId = (id) => {
|
|
77
|
+
if ([
|
|
78
|
+
'username',
|
|
79
|
+
'first_name',
|
|
80
|
+
'last_name',
|
|
81
|
+
'full_name',
|
|
82
|
+
'password',
|
|
83
|
+
'name_prefix',
|
|
84
|
+
'name_suffix',
|
|
85
|
+
'company_name',
|
|
86
|
+
'company_suffix',
|
|
87
|
+
'catch_phrase',
|
|
88
|
+
'phone',
|
|
89
|
+
'ip',
|
|
90
|
+
'domain',
|
|
91
|
+
'url',
|
|
92
|
+
'email',
|
|
93
|
+
'user_agent',
|
|
94
|
+
'country',
|
|
95
|
+
'city',
|
|
96
|
+
'zip',
|
|
97
|
+
'street',
|
|
98
|
+
'address',
|
|
99
|
+
'address1',
|
|
100
|
+
'address2',
|
|
101
|
+
'state',
|
|
102
|
+
'state_abbr',
|
|
103
|
+
'latitude',
|
|
104
|
+
'longitude',
|
|
105
|
+
'building_number',
|
|
106
|
+
].includes(id)) {
|
|
107
|
+
return casual?.[id]() ?? faker.lorem.word();
|
|
108
|
+
}
|
|
109
|
+
if (['uuid', 'uid', 'id'].includes(id)) {
|
|
110
|
+
return casual.uuid;
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
113
|
+
};
|
|
114
|
+
const getIntegerFake = async (field) => {
|
|
115
|
+
return getNumberFake(field, 1);
|
|
116
|
+
};
|
|
117
|
+
const getNumberFake = async (field, precision = 0.005) => {
|
|
118
|
+
const { validations = [] } = field.settings;
|
|
119
|
+
const { in: values } = validations.find((validation) => Boolean(validation?.in)) || {};
|
|
120
|
+
if (values) {
|
|
121
|
+
return oneOf(values);
|
|
122
|
+
}
|
|
123
|
+
const { range } = validations.find((validation) => Boolean(validation?.range)) || {};
|
|
124
|
+
if (range) {
|
|
125
|
+
const { min, max } = range;
|
|
126
|
+
if (typeof min !== 'undefined' && typeof max !== 'undefined') {
|
|
127
|
+
return faker.datatype.number({ min, max, precision });
|
|
128
|
+
}
|
|
129
|
+
if (typeof min !== 'undefined') {
|
|
130
|
+
return faker.datatype.number({ min, precision });
|
|
131
|
+
}
|
|
132
|
+
if (typeof max !== 'undefined') {
|
|
133
|
+
return faker.datatype.number({ max, precision });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return faker.datatype.number({ precision });
|
|
137
|
+
};
|
|
138
|
+
const getSymbolFake = async (field) => {
|
|
139
|
+
const { widgetId } = field.interface;
|
|
140
|
+
const { validations = [], id } = field.settings;
|
|
141
|
+
const { in: values } = validations.find((validation) => Boolean(validation?.in)) || {};
|
|
142
|
+
if (values) {
|
|
143
|
+
return oneOf(values);
|
|
144
|
+
}
|
|
145
|
+
let result = '';
|
|
146
|
+
if (widgetId === 'urlEditor') {
|
|
147
|
+
result = faker.internet.url();
|
|
148
|
+
}
|
|
149
|
+
if (widgetId === 'slugEditor') {
|
|
150
|
+
result = faker.lorem.slug();
|
|
151
|
+
}
|
|
152
|
+
const byId = getTextForId(id);
|
|
153
|
+
if (!result && byId) {
|
|
154
|
+
return byId;
|
|
155
|
+
}
|
|
156
|
+
if (!result && ['dropdown', 'radio'].includes(widgetId)) {
|
|
157
|
+
result = faker.lorem.word();
|
|
158
|
+
}
|
|
159
|
+
const { regexp } = validations.find((validation) => Boolean(validation?.regexp)) || {};
|
|
160
|
+
if (!result && regexp) {
|
|
161
|
+
result = getRegexValue(regexp);
|
|
162
|
+
}
|
|
163
|
+
if (!result) {
|
|
164
|
+
result = faker.lorem.sentence();
|
|
165
|
+
}
|
|
166
|
+
const { size } = validations.find((validation) => Boolean(validation?.size)) || {};
|
|
167
|
+
if (size) {
|
|
168
|
+
const { min = 0, max = 255 } = size;
|
|
169
|
+
const count = randomInt(min, max);
|
|
170
|
+
result = result.slice(0, count);
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
};
|
|
174
|
+
const getTextFake = async (field) => {
|
|
175
|
+
const { validations = [], id } = field.settings;
|
|
176
|
+
const { in: values } = validations.find((validation) => Boolean(validation?.in)) || {};
|
|
177
|
+
let result = '';
|
|
178
|
+
if (values) {
|
|
179
|
+
result = oneOf(values);
|
|
180
|
+
}
|
|
181
|
+
const byId = getTextForId(id);
|
|
182
|
+
if (!result && byId) {
|
|
183
|
+
result = byId;
|
|
184
|
+
}
|
|
185
|
+
const { regexp } = validations.find((validation) => Boolean(validation?.regexp)) || {};
|
|
186
|
+
if (!result && regexp) {
|
|
187
|
+
result = getRegexValue(regexp);
|
|
188
|
+
}
|
|
189
|
+
if (!result) {
|
|
190
|
+
result = faker.lorem.lines();
|
|
191
|
+
}
|
|
192
|
+
const { size } = validations.find((validation) => Boolean(validation?.size)) || {};
|
|
193
|
+
if (size) {
|
|
194
|
+
const { min = 0, max = 50000 } = size;
|
|
195
|
+
const count = randomInt(min, max);
|
|
196
|
+
result = result.slice(0, count);
|
|
197
|
+
}
|
|
198
|
+
return result;
|
|
199
|
+
};
|
|
200
|
+
const getAssetFake = async () => {
|
|
201
|
+
const sizes = [640, 480, 600, 800, 1024];
|
|
202
|
+
const width = oneOf(sizes);
|
|
203
|
+
const height = oneOf(sizes);
|
|
204
|
+
return {
|
|
205
|
+
mime_type: 'image/jpeg',
|
|
206
|
+
url: faker.image.image(width, height),
|
|
207
|
+
title: faker.lorem.words(),
|
|
208
|
+
description: faker.lorem.lines(),
|
|
209
|
+
width,
|
|
210
|
+
height,
|
|
211
|
+
file_size: faker.datatype.number({ min: 1000, max: 200000 }),
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
const getEntryScheme = async (field) => {
|
|
215
|
+
const { validations } = field.settings;
|
|
216
|
+
const { linkContentType = [] } = validations.find((validation) => Boolean(validation?.linkContentType)) || {};
|
|
217
|
+
if (linkContentType.length > 0) {
|
|
218
|
+
return {
|
|
219
|
+
id: 'default',
|
|
220
|
+
content_type: oneOf(linkContentType),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
return {};
|
|
224
|
+
};
|
|
225
|
+
const getLinkFake = async (field) => {
|
|
226
|
+
if (field?.settings?.linkType === LINK_TYPE_ASSET) {
|
|
227
|
+
return getAssetFake();
|
|
228
|
+
}
|
|
229
|
+
return getEntryScheme(field);
|
|
230
|
+
};
|
|
231
|
+
const getRichtextFake = async (field) => {
|
|
232
|
+
const { validations } = field.settings;
|
|
233
|
+
const { enabledMarks: marks = [] } = validations.find((validation) => Boolean(validation?.enabledMarks)) || {};
|
|
234
|
+
const { enabledNodeTypes: nodeTypes = [] } = validations.find((validation) => Boolean(validation?.enabledNodeTypes)) || {};
|
|
235
|
+
return {
|
|
236
|
+
node_type: 'document',
|
|
237
|
+
data: {},
|
|
238
|
+
content: [
|
|
239
|
+
...nodeTypes
|
|
240
|
+
.filter((nodeType) => nodeType.startsWith('heading'))
|
|
241
|
+
.map((nodeType) => ({
|
|
242
|
+
node_type: nodeType,
|
|
243
|
+
content: [
|
|
244
|
+
{
|
|
245
|
+
node_type: 'text',
|
|
246
|
+
value: nodeType,
|
|
247
|
+
marks: [],
|
|
248
|
+
data: {},
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
data: {},
|
|
252
|
+
})),
|
|
253
|
+
...marks.map((mark) => ({
|
|
254
|
+
node_type: 'paragraph',
|
|
255
|
+
content: [
|
|
256
|
+
{
|
|
257
|
+
node_type: 'text',
|
|
258
|
+
value: mark,
|
|
259
|
+
marks: [mark],
|
|
260
|
+
data: {},
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
data: {},
|
|
264
|
+
})),
|
|
265
|
+
{
|
|
266
|
+
node_type: 'paragraph',
|
|
267
|
+
content: [
|
|
268
|
+
{
|
|
269
|
+
node_type: 'text',
|
|
270
|
+
value: faker.lorem.sentence(),
|
|
271
|
+
marks: [],
|
|
272
|
+
data: {},
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
data: {},
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
};
|
|
279
|
+
};
|
|
280
|
+
const getArrayFake = async (field) => {
|
|
281
|
+
const { validations } = field.settings;
|
|
282
|
+
const { size } = validations.find((validation) => Boolean(validation?.size)) || {};
|
|
283
|
+
let count = randomInt(0, 10);
|
|
284
|
+
if (size) {
|
|
285
|
+
count = randomInt(size?.min ?? 0, size?.max ?? (size?.min ?? 0) + 10);
|
|
286
|
+
}
|
|
287
|
+
if (field.settings.items.type === FIELD_TYPE_SYMBOL) {
|
|
288
|
+
return Promise.all(Array(count)
|
|
289
|
+
.fill('')
|
|
290
|
+
.map(async () => getSymbolFake({
|
|
291
|
+
...field,
|
|
292
|
+
settings: { ...field.settings.items, id: field.settings.id },
|
|
293
|
+
})));
|
|
294
|
+
}
|
|
295
|
+
if (field.settings.items.type === FIELD_TYPE_TEXT) {
|
|
296
|
+
return Promise.all(Array(count)
|
|
297
|
+
.fill('')
|
|
298
|
+
.map(async () => getTextFake({
|
|
299
|
+
...field,
|
|
300
|
+
settings: { ...field.settings.items, id: field.settings.id },
|
|
301
|
+
})));
|
|
302
|
+
}
|
|
303
|
+
if (field.settings.items.type === FIELD_TYPE_LINK &&
|
|
304
|
+
field.settings.items.linkType === LINK_TYPE_ASSET) {
|
|
305
|
+
return Promise.all(Array(count)
|
|
306
|
+
.fill('')
|
|
307
|
+
.map(async () => getAssetFake()));
|
|
308
|
+
}
|
|
309
|
+
if (field.settings.items.type === FIELD_TYPE_LINK &&
|
|
310
|
+
field.settings.items.linkType === LINK_TYPE_ENTRY) {
|
|
311
|
+
return Promise.all(Array(count)
|
|
312
|
+
.fill('')
|
|
313
|
+
.map(async () => getEntryScheme({
|
|
314
|
+
...field,
|
|
315
|
+
settings: { ...field.settings.items, id: field.settings.id },
|
|
316
|
+
})));
|
|
317
|
+
}
|
|
318
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const oneOf = (array = []) => {
|
|
2
|
+
const index = randomInt(0, array.length);
|
|
3
|
+
return array[index];
|
|
4
|
+
};
|
|
5
|
+
export const randomInt = (min = 0, max = 1000000) => {
|
|
6
|
+
min = Math.ceil(min || 0);
|
|
7
|
+
max = Math.floor(max || 1000000);
|
|
8
|
+
return Math.floor(Math.random() * (max - min)) + min;
|
|
9
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jungvonmatt/contentful-fakes",
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "Create fake data based on contentful data models",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"typings": "./dist/types.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"contentful-fakes": "./dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"clean": "rimraf ./dist",
|
|
16
|
+
"lint": "eslint --color src --fix --ext .ts",
|
|
17
|
+
"precompile": "npm run clean",
|
|
18
|
+
"compile": "tsc --build",
|
|
19
|
+
"postcompile": "chmod +x ./dist/cli.js",
|
|
20
|
+
"watch": "tsc --build --watch"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/jungvonmatt/contentful-ssg.git"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"fake",
|
|
28
|
+
"data",
|
|
29
|
+
"contentful"
|
|
30
|
+
],
|
|
31
|
+
"author": "Ben Zörb",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/jungvonmatt/contentful-ssg/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/jungvonmatt/contentful-ssg#readme",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@jungvonmatt/contentful-ssg": "^1.4.0",
|
|
39
|
+
"casual": "^1.6.2",
|
|
40
|
+
"faker": "^5.5.3",
|
|
41
|
+
"fs-extra": "^10.0.0",
|
|
42
|
+
"randexp": "^0.5.3"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/chance": "^1.1.3",
|
|
46
|
+
"@types/faker": "^6.6.9"
|
|
47
|
+
},
|
|
48
|
+
"gitHead": "0d323f9884ae8fa48049e25bcd127954ee7988b8"
|
|
49
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/* eslint-env node */
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { existsSync } from 'fs';
|
|
6
|
+
import { outputFile } from 'fs-extra';
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
import dotenv from 'dotenv';
|
|
9
|
+
import dotenvExpand from 'dotenv-expand';
|
|
10
|
+
import { ContentfulConfig } from '@jungvonmatt/contentful-ssg';
|
|
11
|
+
import { getConfig } from '@jungvonmatt/contentful-ssg/lib/config';
|
|
12
|
+
import { forEachAsync } from '@jungvonmatt/contentful-ssg/lib/array';
|
|
13
|
+
import { stringify } from '@jungvonmatt/contentful-ssg/converter';
|
|
14
|
+
import { logError, askMissing, confirm } from '@jungvonmatt/contentful-ssg/lib/ui';
|
|
15
|
+
import { createFakes } from './index.js';
|
|
16
|
+
|
|
17
|
+
const env = dotenv.config();
|
|
18
|
+
dotenvExpand(env);
|
|
19
|
+
|
|
20
|
+
type CommandArgs = {
|
|
21
|
+
outputDirectory: string;
|
|
22
|
+
extension: string;
|
|
23
|
+
contentType: string[];
|
|
24
|
+
env: string;
|
|
25
|
+
verbose: boolean;
|
|
26
|
+
force: boolean;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const parseArgs = (cmd: CommandArgs) => ({
|
|
30
|
+
environment: cmd.env,
|
|
31
|
+
verbose: Boolean(cmd.verbose),
|
|
32
|
+
contentTypes: cmd.contentType,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
type CommandError = Error & {
|
|
36
|
+
errors?: Error[];
|
|
37
|
+
};
|
|
38
|
+
const errorHandler = (error: CommandError, silence: boolean) => {
|
|
39
|
+
if (!silence) {
|
|
40
|
+
const { errors } = error;
|
|
41
|
+
logError(error);
|
|
42
|
+
(errors || []).forEach((error) => {
|
|
43
|
+
logError(error);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
process.exit(1);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const actionRunner =
|
|
51
|
+
(fn, log = true) =>
|
|
52
|
+
(...args) =>
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call
|
|
54
|
+
fn(...args).catch((error) => errorHandler(error, !log));
|
|
55
|
+
const program = new Command();
|
|
56
|
+
|
|
57
|
+
program
|
|
58
|
+
.command('create')
|
|
59
|
+
.description('Create fake objects')
|
|
60
|
+
.option('-c, --content-type <content-type...>', 'Specify content-types')
|
|
61
|
+
.option('-e, --extension <extension>', 'Specify output format', 'yaml')
|
|
62
|
+
.option('-o, --output-directory <directory>', 'Specify output directory', 'data')
|
|
63
|
+
.option('-f, --force', 'Overwrite')
|
|
64
|
+
.action(
|
|
65
|
+
actionRunner(async (cmd: CommandArgs) => {
|
|
66
|
+
const contentTypes: string[] = cmd?.contentType ?? [];
|
|
67
|
+
const force: boolean = cmd?.force ?? false;
|
|
68
|
+
const format: string = cmd?.extension ?? '';
|
|
69
|
+
const outputDirectory: string = cmd?.outputDirectory ?? '';
|
|
70
|
+
const config = await getConfig(parseArgs(cmd));
|
|
71
|
+
const verified = await askMissing(config);
|
|
72
|
+
const fakes = await createFakes(verified as ContentfulConfig, contentTypes);
|
|
73
|
+
await forEachAsync(Object.entries(fakes), async ([contentTypeId, fakeData]) => {
|
|
74
|
+
const [defaultData, minData] = fakeData;
|
|
75
|
+
const dir = path.join(outputDirectory, contentTypeId);
|
|
76
|
+
const filenameDefault = path.join(dir, `default.${format}`);
|
|
77
|
+
const filenameMin = path.join(dir, `min.${format}`);
|
|
78
|
+
if (
|
|
79
|
+
!existsSync(filenameDefault) ||
|
|
80
|
+
force ||
|
|
81
|
+
(await confirm(`Overwrite file: ${filenameDefault}`))
|
|
82
|
+
) {
|
|
83
|
+
await outputFile(filenameDefault, stringify(defaultData, format));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (
|
|
87
|
+
!existsSync(filenameMin) ||
|
|
88
|
+
force ||
|
|
89
|
+
(await confirm(`Overwrite file: ${filenameMin}`))
|
|
90
|
+
) {
|
|
91
|
+
await outputFile(filenameMin, stringify(minData, format));
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
})
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
program.parse(process.argv);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ContentfulConfig, KeyValueMap } from '@jungvonmatt/contentful-ssg';
|
|
2
|
+
import { getEnvironment } from '@jungvonmatt/contentful-ssg/lib/contentful';
|
|
3
|
+
import { getMockData, ContentTypes } from './lib/faker.js';
|
|
4
|
+
|
|
5
|
+
export async function createFakes(
|
|
6
|
+
config: ContentfulConfig,
|
|
7
|
+
contentTypeIds: string[]
|
|
8
|
+
): Promise<Record<string, KeyValueMap[]>> {
|
|
9
|
+
const environment = await getEnvironment(config);
|
|
10
|
+
const { items: contentTypes } = await environment.getContentTypes();
|
|
11
|
+
const { items } = await environment.getEditorInterfaces();
|
|
12
|
+
|
|
13
|
+
const interfaces: ContentTypes = Object.fromEntries(
|
|
14
|
+
items
|
|
15
|
+
.filter(
|
|
16
|
+
(item) => !contentTypeIds.length || contentTypeIds.includes(item.sys.contentType.sys.id)
|
|
17
|
+
)
|
|
18
|
+
.map((item) => {
|
|
19
|
+
const { id } = item.sys.contentType.sys;
|
|
20
|
+
const contentType = contentTypes.find((ct) => ct.sys.id === id);
|
|
21
|
+
const { controls } = item;
|
|
22
|
+
|
|
23
|
+
const fields = controls.map((control) => {
|
|
24
|
+
const { fieldId } = control;
|
|
25
|
+
const fieldSettings = contentType.fields.find((field) => field.id === fieldId);
|
|
26
|
+
return { interface: control, settings: fieldSettings };
|
|
27
|
+
});
|
|
28
|
+
return [id, fields];
|
|
29
|
+
})
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const data = await getMockData(interfaces);
|
|
33
|
+
|
|
34
|
+
return Object.fromEntries(
|
|
35
|
+
Object.entries(interfaces).map(([contentTypeId, fields]) => {
|
|
36
|
+
const { [contentTypeId]: fakeData = {} } = data;
|
|
37
|
+
const required = Object.fromEntries(
|
|
38
|
+
fields.map((field) => [field.settings.id, field.settings.required])
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const minData = Object.fromEntries(
|
|
42
|
+
Object.entries(fakeData).filter(([fieldId]) => required?.[fieldId])
|
|
43
|
+
);
|
|
44
|
+
return [contentTypeId, [fakeData, minData]];
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
}
|
package/src/lib/faker.ts
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
4
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
5
|
+
// See validations
|
|
6
|
+
// https://www.contentful.com/developers/docs/references/content-management-api/#/reference/content-types/content-type
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
FIELD_TYPE_SYMBOL,
|
|
10
|
+
FIELD_TYPE_TEXT,
|
|
11
|
+
FIELD_TYPE_RICHTEXT,
|
|
12
|
+
FIELD_TYPE_NUMBER,
|
|
13
|
+
FIELD_TYPE_INTEGER,
|
|
14
|
+
FIELD_TYPE_DATE,
|
|
15
|
+
FIELD_TYPE_LOCATION,
|
|
16
|
+
FIELD_TYPE_ARRAY,
|
|
17
|
+
FIELD_TYPE_BOOLEAN,
|
|
18
|
+
FIELD_TYPE_LINK,
|
|
19
|
+
FIELD_TYPE_OBJECT,
|
|
20
|
+
LINK_TYPE_ASSET,
|
|
21
|
+
LINK_TYPE_ENTRY,
|
|
22
|
+
} from '@jungvonmatt/contentful-ssg/lib/contentful';
|
|
23
|
+
|
|
24
|
+
import { ContentFields, KeyValueMap, Control } from 'contentful-management/types';
|
|
25
|
+
|
|
26
|
+
import faker from 'faker';
|
|
27
|
+
import casual from 'casual';
|
|
28
|
+
import RandExp from 'randexp';
|
|
29
|
+
import { oneOf, randomInt } from './helper.js';
|
|
30
|
+
|
|
31
|
+
export type FieldInfo = {
|
|
32
|
+
interface: Control;
|
|
33
|
+
settings: Partial<ContentFields>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type ContentTypes = Record<string, FieldInfo[]>;
|
|
37
|
+
|
|
38
|
+
export const getMockData = async (contentTypes: ContentTypes): Promise<KeyValueMap> => {
|
|
39
|
+
const dataEntries = await Promise.all(
|
|
40
|
+
Object.entries(contentTypes).map(async ([name, fields]) => [name, await createData(fields)])
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return Object.fromEntries(dataEntries);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const createData = async (fields: FieldInfo[]): Promise<KeyValueMap> => {
|
|
47
|
+
const entries = await Promise.all(
|
|
48
|
+
fields.map(async (field) => {
|
|
49
|
+
const id = field?.settings?.id || 'undefined';
|
|
50
|
+
switch (field.settings.type) {
|
|
51
|
+
case FIELD_TYPE_SYMBOL:
|
|
52
|
+
return [id, await getSymbolFake(field)];
|
|
53
|
+
case FIELD_TYPE_INTEGER:
|
|
54
|
+
return [id, await getIntegerFake(field)];
|
|
55
|
+
case FIELD_TYPE_NUMBER:
|
|
56
|
+
return [id, await getNumberFake(field)];
|
|
57
|
+
case FIELD_TYPE_TEXT:
|
|
58
|
+
return [id, await getTextFake(field)];
|
|
59
|
+
case FIELD_TYPE_LINK:
|
|
60
|
+
return [id, await getLinkFake(field)];
|
|
61
|
+
case FIELD_TYPE_ARRAY:
|
|
62
|
+
return [id, await getArrayFake(field)];
|
|
63
|
+
case FIELD_TYPE_BOOLEAN:
|
|
64
|
+
return [id, faker.datatype.boolean()];
|
|
65
|
+
case FIELD_TYPE_DATE:
|
|
66
|
+
return [id, faker.date.future()];
|
|
67
|
+
case FIELD_TYPE_RICHTEXT:
|
|
68
|
+
return [id, await getRichtextFake(field)];
|
|
69
|
+
case FIELD_TYPE_OBJECT:
|
|
70
|
+
return [id, {}];
|
|
71
|
+
case FIELD_TYPE_LOCATION:
|
|
72
|
+
return [
|
|
73
|
+
id,
|
|
74
|
+
{
|
|
75
|
+
lat: faker.address.latitude(),
|
|
76
|
+
lng: faker.address.longitude(),
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
default:
|
|
80
|
+
return [id, undefined];
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
return Object.fromEntries(entries);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
type RegexType = {
|
|
89
|
+
pattern: string;
|
|
90
|
+
flags: string;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const getRegexValue = (regex: RegexType) => {
|
|
94
|
+
const { pattern, flags } = regex;
|
|
95
|
+
|
|
96
|
+
// First try to handle regex values predefined in contentful
|
|
97
|
+
// Email:
|
|
98
|
+
if (pattern === '^\\w[\\w.-]*@([\\w-]+\\.)+[\\w-]+$') {
|
|
99
|
+
return faker.internet.exampleEmail();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Url:
|
|
103
|
+
if (
|
|
104
|
+
pattern.includes('\\/\\/(\\w+:{0,1}\\w*@)?(\\S+)(:[0-9]+)?(\\/|\\/([\\w#!:.?+=&%@!\\-/]))?$')
|
|
105
|
+
) {
|
|
106
|
+
return faker.internet.url();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Date (US)
|
|
110
|
+
if (pattern === '^(0?[1-9]|1[012])[- /.](0?[1-9]|[12][0-9]|3[01])[- /.](19|20)?\\d\\d$') {
|
|
111
|
+
return casual.time('MM-DD-YYYY');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Date (European)
|
|
115
|
+
if (pattern === '^(0?[1-9]|[12][0-9]|3[01])[- /.](0?[1-9]|1[012])[- /.](19|20)?\\d\\d$') {
|
|
116
|
+
return casual.time('DD-MM-YYYY');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 12h Time
|
|
120
|
+
if (pattern === '^(0?[1-9]|1[012]):[0-5][0-9](:[0-5][0-9])?\\s*[aApP][mM]$') {
|
|
121
|
+
return casual.time('hh:mm a');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 24h time
|
|
125
|
+
if (pattern === '^(0?[0-9]|1[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$') {
|
|
126
|
+
return casual.time('HH:mm');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// US Phone number
|
|
130
|
+
if (pattern === '^\\d[ -.]?\\(?\\d\\d\\d\\)?[ -.]?\\d\\d\\d[ -.]?\\d\\d\\d\\d$') {
|
|
131
|
+
return faker.phone.phoneNumber();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// US Zip code
|
|
135
|
+
if (pattern === '^\\d{5}$|^\\d{5}-\\d{4}$}') {
|
|
136
|
+
return faker.address.zipCode();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return new RandExp(pattern, flags);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const getTextForId = (id: string) => {
|
|
143
|
+
if (
|
|
144
|
+
[
|
|
145
|
+
'username',
|
|
146
|
+
'first_name',
|
|
147
|
+
'last_name',
|
|
148
|
+
'full_name',
|
|
149
|
+
'password',
|
|
150
|
+
'name_prefix',
|
|
151
|
+
'name_suffix',
|
|
152
|
+
'company_name',
|
|
153
|
+
'company_suffix',
|
|
154
|
+
'catch_phrase',
|
|
155
|
+
'phone',
|
|
156
|
+
'ip',
|
|
157
|
+
'domain',
|
|
158
|
+
'url',
|
|
159
|
+
'email',
|
|
160
|
+
'user_agent',
|
|
161
|
+
'country',
|
|
162
|
+
'city',
|
|
163
|
+
'zip',
|
|
164
|
+
'street',
|
|
165
|
+
'address',
|
|
166
|
+
'address1',
|
|
167
|
+
'address2',
|
|
168
|
+
'state',
|
|
169
|
+
'state_abbr',
|
|
170
|
+
'latitude',
|
|
171
|
+
'longitude',
|
|
172
|
+
'building_number',
|
|
173
|
+
].includes(id)
|
|
174
|
+
) {
|
|
175
|
+
return casual?.[id]() ?? faker.lorem.word();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (['uuid', 'uid', 'id'].includes(id)) {
|
|
179
|
+
return casual.uuid;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return undefined;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const getIntegerFake = async (field: FieldInfo): Promise<unknown> => {
|
|
186
|
+
return getNumberFake(field, 1);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const getNumberFake = async (field: FieldInfo, precision = 0.005): Promise<unknown> => {
|
|
190
|
+
const { validations = [] } = field.settings;
|
|
191
|
+
|
|
192
|
+
const { in: values } = validations.find((validation) => Boolean(validation?.in)) || {};
|
|
193
|
+
if (values) {
|
|
194
|
+
return oneOf(values);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const { range } = validations.find((validation) => Boolean(validation?.range)) || {};
|
|
198
|
+
if (range) {
|
|
199
|
+
const { min, max } = range;
|
|
200
|
+
if (typeof min !== 'undefined' && typeof max !== 'undefined') {
|
|
201
|
+
return faker.datatype.number({ min, max, precision });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (typeof min !== 'undefined') {
|
|
205
|
+
return faker.datatype.number({ min, precision });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (typeof max !== 'undefined') {
|
|
209
|
+
return faker.datatype.number({ max, precision });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return faker.datatype.number({ precision });
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const getSymbolFake = async (field: FieldInfo): Promise<unknown> => {
|
|
217
|
+
const { widgetId } = field.interface;
|
|
218
|
+
const { validations = [], id } = field.settings;
|
|
219
|
+
|
|
220
|
+
const { in: values } = validations.find((validation) => Boolean(validation?.in)) || {};
|
|
221
|
+
if (values) {
|
|
222
|
+
return oneOf(values);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
let result = '';
|
|
226
|
+
|
|
227
|
+
if (widgetId === 'urlEditor') {
|
|
228
|
+
result = faker.internet.url();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (widgetId === 'slugEditor') {
|
|
232
|
+
result = faker.lorem.slug();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const byId = getTextForId(id);
|
|
236
|
+
if (!result && byId) {
|
|
237
|
+
return byId;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!result && ['dropdown', 'radio'].includes(widgetId)) {
|
|
241
|
+
result = faker.lorem.word();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const { regexp } = validations.find((validation) => Boolean(validation?.regexp)) || {};
|
|
245
|
+
if (!result && regexp) {
|
|
246
|
+
result = getRegexValue(regexp);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!result) {
|
|
250
|
+
result = faker.lorem.sentence();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const { size } = validations.find((validation) => Boolean(validation?.size)) || {};
|
|
254
|
+
if (size) {
|
|
255
|
+
const { min = 0, max = 255 } = size;
|
|
256
|
+
const count = randomInt(min, max);
|
|
257
|
+
result = result.slice(0, count);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return result;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const getTextFake = async (field: FieldInfo): Promise<unknown> => {
|
|
264
|
+
const { validations = [], id } = field.settings;
|
|
265
|
+
const { in: values } = validations.find((validation) => Boolean(validation?.in)) || {};
|
|
266
|
+
|
|
267
|
+
let result = '';
|
|
268
|
+
if (values) {
|
|
269
|
+
result = oneOf(values as string[]);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const byId = getTextForId(id);
|
|
273
|
+
if (!result && byId) {
|
|
274
|
+
result = byId;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const { regexp } = validations.find((validation) => Boolean(validation?.regexp)) || {};
|
|
278
|
+
if (!result && regexp) {
|
|
279
|
+
result = getRegexValue(regexp);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!result) {
|
|
283
|
+
result = faker.lorem.lines();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const { size } = validations.find((validation) => Boolean(validation?.size)) || {};
|
|
287
|
+
if (size) {
|
|
288
|
+
const { min = 0, max = 50000 } = size;
|
|
289
|
+
const count = randomInt(min, max);
|
|
290
|
+
result = result.slice(0, count);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return result;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const getAssetFake = async (): Promise<KeyValueMap> => {
|
|
297
|
+
const sizes = [640, 480, 600, 800, 1024];
|
|
298
|
+
const width = oneOf(sizes);
|
|
299
|
+
const height = oneOf(sizes);
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
mime_type: 'image/jpeg',
|
|
303
|
+
url: faker.image.image(width, height),
|
|
304
|
+
title: faker.lorem.words(),
|
|
305
|
+
description: faker.lorem.lines(),
|
|
306
|
+
width,
|
|
307
|
+
height,
|
|
308
|
+
file_size: faker.datatype.number({ min: 1000, max: 200000 }),
|
|
309
|
+
};
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const getEntryScheme = async (field: FieldInfo): Promise<KeyValueMap> => {
|
|
313
|
+
const { validations } = field.settings;
|
|
314
|
+
|
|
315
|
+
const { linkContentType = [] } =
|
|
316
|
+
validations.find((validation) => Boolean(validation?.linkContentType)) || {};
|
|
317
|
+
|
|
318
|
+
if (linkContentType.length > 0) {
|
|
319
|
+
return {
|
|
320
|
+
id: 'default',
|
|
321
|
+
content_type: oneOf(linkContentType),
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return {};
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const getLinkFake = async (field: FieldInfo): Promise<KeyValueMap> => {
|
|
329
|
+
if (field?.settings?.linkType === LINK_TYPE_ASSET) {
|
|
330
|
+
return getAssetFake();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return getEntryScheme(field);
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const getRichtextFake = async (field: FieldInfo): Promise<KeyValueMap> => {
|
|
337
|
+
const { validations } = field.settings;
|
|
338
|
+
|
|
339
|
+
const { enabledMarks: marks = [] } =
|
|
340
|
+
validations.find((validation) => Boolean(validation?.enabledMarks)) || {};
|
|
341
|
+
|
|
342
|
+
const { enabledNodeTypes: nodeTypes = [] } =
|
|
343
|
+
validations.find((validation) => Boolean(validation?.enabledNodeTypes)) || {};
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
node_type: 'document',
|
|
347
|
+
data: {},
|
|
348
|
+
content: [
|
|
349
|
+
...nodeTypes
|
|
350
|
+
.filter((nodeType) => nodeType.startsWith('heading'))
|
|
351
|
+
.map((nodeType) => ({
|
|
352
|
+
node_type: nodeType,
|
|
353
|
+
content: [
|
|
354
|
+
{
|
|
355
|
+
node_type: 'text',
|
|
356
|
+
value: nodeType,
|
|
357
|
+
marks: [],
|
|
358
|
+
data: {},
|
|
359
|
+
},
|
|
360
|
+
],
|
|
361
|
+
data: {},
|
|
362
|
+
})),
|
|
363
|
+
...marks.map((mark) => ({
|
|
364
|
+
node_type: 'paragraph',
|
|
365
|
+
content: [
|
|
366
|
+
{
|
|
367
|
+
node_type: 'text',
|
|
368
|
+
value: mark,
|
|
369
|
+
marks: [mark],
|
|
370
|
+
data: {},
|
|
371
|
+
},
|
|
372
|
+
],
|
|
373
|
+
data: {},
|
|
374
|
+
})),
|
|
375
|
+
{
|
|
376
|
+
node_type: 'paragraph',
|
|
377
|
+
content: [
|
|
378
|
+
{
|
|
379
|
+
node_type: 'text',
|
|
380
|
+
value: faker.lorem.sentence(),
|
|
381
|
+
marks: [],
|
|
382
|
+
data: {},
|
|
383
|
+
},
|
|
384
|
+
],
|
|
385
|
+
data: {},
|
|
386
|
+
},
|
|
387
|
+
],
|
|
388
|
+
};
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const getArrayFake = async (field: FieldInfo): Promise<unknown[]> => {
|
|
392
|
+
const { validations } = field.settings;
|
|
393
|
+
const { size } = validations.find((validation) => Boolean(validation?.size)) || {};
|
|
394
|
+
let count = randomInt(0, 10);
|
|
395
|
+
if (size) {
|
|
396
|
+
count = randomInt(size?.min ?? 0, size?.max ?? (size?.min ?? 0) + 10);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (field.settings.items.type === FIELD_TYPE_SYMBOL) {
|
|
400
|
+
return Promise.all(
|
|
401
|
+
Array(count)
|
|
402
|
+
.fill('')
|
|
403
|
+
.map(async () =>
|
|
404
|
+
getSymbolFake({
|
|
405
|
+
...field,
|
|
406
|
+
settings: { ...field.settings.items, id: field.settings.id },
|
|
407
|
+
})
|
|
408
|
+
)
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (field.settings.items.type === FIELD_TYPE_TEXT) {
|
|
413
|
+
return Promise.all(
|
|
414
|
+
Array(count)
|
|
415
|
+
.fill('')
|
|
416
|
+
.map(async () =>
|
|
417
|
+
getTextFake({
|
|
418
|
+
...field,
|
|
419
|
+
settings: { ...field.settings.items, id: field.settings.id },
|
|
420
|
+
})
|
|
421
|
+
)
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (
|
|
426
|
+
field.settings.items.type === FIELD_TYPE_LINK &&
|
|
427
|
+
field.settings.items.linkType === LINK_TYPE_ASSET
|
|
428
|
+
) {
|
|
429
|
+
return Promise.all(
|
|
430
|
+
Array(count)
|
|
431
|
+
.fill('')
|
|
432
|
+
.map(async () => getAssetFake())
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (
|
|
437
|
+
field.settings.items.type === FIELD_TYPE_LINK &&
|
|
438
|
+
field.settings.items.linkType === LINK_TYPE_ENTRY
|
|
439
|
+
) {
|
|
440
|
+
return Promise.all(
|
|
441
|
+
Array(count)
|
|
442
|
+
.fill('')
|
|
443
|
+
.map(async () =>
|
|
444
|
+
getEntryScheme({
|
|
445
|
+
...field,
|
|
446
|
+
settings: { ...field.settings.items, id: field.settings.id },
|
|
447
|
+
})
|
|
448
|
+
)
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const oneOf = <T = string>(array: T[] = []): T => {
|
|
2
|
+
const index = randomInt(0, array.length);
|
|
3
|
+
|
|
4
|
+
return array[index];
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const randomInt = (min = 0, max = 1000000) => {
|
|
8
|
+
min = Math.ceil(min || 0);
|
|
9
|
+
max = Math.floor(max || 1000000);
|
|
10
|
+
return Math.floor(Math.random() * (max - min)) + min;
|
|
11
|
+
};
|