@stripchatdev/create-stripchat-extension 0.1.21 → 0.1.23
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/dist/bin/cli.js +56 -43
- package/dist/package.json +1 -1
- package/dist/src/constants.d.ts +2 -12
- package/dist/src/constants.js +30 -27
- package/dist/src/executor.test.js +2 -2
- package/dist/src/mappers/mapProjectConfigToTemplateModel.js +3 -4
- package/dist/src/mappers/mapProjectConfigToTemplateModel.test.js +25 -17
- package/dist/src/planner.js +1 -1
- package/dist/src/planner.test.js +16 -14
- package/dist/src/types.d.ts +15 -6
- package/dist/src/validators/validateSlots.d.ts +2 -2
- package/dist/src/validators/validateSlots.js +19 -15
- package/dist/src/validators/validateSlots.test.js +86 -51
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/templates/AGENTS.md.ejs +15 -15
- package/templates/README.md.ejs +1 -1
- package/templates/public/manifest.json.ejs +4 -4
- package/templates/public/resolveSlotPage.js.ejs +3 -3
- package/templates/vite.config.ts.ejs +2 -2
package/dist/bin/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import { checkbox, input, select } from '@inquirer/prompts';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { parseArgs } from 'node:util';
|
|
5
5
|
import packageJson from '../package.json' with { type: 'json' };
|
|
6
|
-
import {
|
|
6
|
+
import { CATEGORIES, CATEGORY_LABELS, FRAMEWORK_LABELS, FRAMEWORKS, LANGUAGE_LABELS, LANGUAGES, REQUIRED_SLOTS_BY_CATEGORY, SLOT_RULES_BY_CATEGORY, SLOT_LABELS, } from '../src/constants.js';
|
|
7
7
|
import { executeCommands } from '../src/executor.js';
|
|
8
8
|
import { detectPackageManager } from '../src/helpers/detectPackageManager.js';
|
|
9
9
|
import { mapProjectConfigToTemplateData } from '../src/mappers/mapProjectConfigToTemplateModel.js';
|
|
@@ -15,9 +15,9 @@ Usage: create-stripchat-extension [options]
|
|
|
15
15
|
Create a new Stripchat extension project.
|
|
16
16
|
|
|
17
17
|
Options:
|
|
18
|
-
--name <name> Project name (used as directory and package name)
|
|
18
|
+
--name <name> Project name (used as a directory and package name)
|
|
19
19
|
--category <category> Extension category (${CATEGORIES.join(', ')})
|
|
20
|
-
--slots <items> Comma-separated slots (e.g. "
|
|
20
|
+
--slots <items> Comma-separated slots (e.g. "EXTENSION_SLOT_MAIN_GAME_FUN,EXTENSION_SLOT_RIGHT_OVERLAY") or empty string
|
|
21
21
|
--framework <framework> UI framework (${FRAMEWORKS.join(', ')})
|
|
22
22
|
--language <lang> Language (${LANGUAGES.join(', ')})
|
|
23
23
|
-h, --help Display this help message
|
|
@@ -77,54 +77,62 @@ const resolveCategory = (flagValue) => {
|
|
|
77
77
|
message: 'Extension category:',
|
|
78
78
|
});
|
|
79
79
|
};
|
|
80
|
+
const getSlotsErrorMessage = (category, requestedSlots) => {
|
|
81
|
+
const rules = SLOT_RULES_BY_CATEGORY[category];
|
|
82
|
+
const { invalid, violations } = validateSlots(requestedSlots, category);
|
|
83
|
+
if (invalid.length > 0) {
|
|
84
|
+
if (rules.optional.length === 0) {
|
|
85
|
+
return `Error: invalid slot(s): "${invalid.join(', ')}". This category doesn't have optional slots.`;
|
|
86
|
+
}
|
|
87
|
+
return `Error: invalid slot(s) "${invalid.join(', ')}". Choose from: ${rules.optional.join(', ')}`;
|
|
88
|
+
}
|
|
89
|
+
if (violations.length > 0) {
|
|
90
|
+
return `Error: ${violations.join(' ')}`;
|
|
91
|
+
}
|
|
92
|
+
return undefined;
|
|
93
|
+
};
|
|
94
|
+
const normalizePromptSlots = (value) => value.flatMap((item) => {
|
|
95
|
+
if (typeof item === 'string') {
|
|
96
|
+
return [item];
|
|
97
|
+
}
|
|
98
|
+
if (typeof item === 'object' && item !== null && 'value' in item && typeof item.value === 'string') {
|
|
99
|
+
return [item.value];
|
|
100
|
+
}
|
|
101
|
+
return [];
|
|
102
|
+
});
|
|
80
103
|
const resolveSlots = async (flagValue, category) => {
|
|
81
|
-
const { selectable, validations } = CATEGORY_SLOTS[category];
|
|
82
|
-
const exactOneOfValidation = validations?.find((validation) => validation.type === 'exactlyOneOf');
|
|
83
104
|
if (flagValue !== undefined) {
|
|
84
105
|
const requested = flagValue.split(',').map((s) => s.trim());
|
|
85
106
|
if (flagValue === '') {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
return Promise.resolve([]);
|
|
92
|
-
}
|
|
93
|
-
const { errors, invalid, valid } = validateSlots(requested, category);
|
|
94
|
-
if (invalid.length) {
|
|
95
|
-
if (selectable.length !== 0) {
|
|
96
|
-
console.error(`Error: invalid slot(s) "${invalid.join(', ')}". Choose from: ${selectable.join(', ')}`);
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
console.error(`Error: invalid slot(s): "${invalid.join(', ')}". This category doesn't have optional slots.`);
|
|
100
|
-
}
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
if (errors.length) {
|
|
104
|
-
console.error(`Error: ${errors.join('. ')}`);
|
|
105
|
-
process.exit(1);
|
|
107
|
+
return {
|
|
108
|
+
errorMessage: getSlotsErrorMessage(category, []),
|
|
109
|
+
slots: [],
|
|
110
|
+
};
|
|
106
111
|
}
|
|
107
|
-
return
|
|
112
|
+
return {
|
|
113
|
+
errorMessage: getSlotsErrorMessage(category, requested),
|
|
114
|
+
slots: validateSlots(requested, category).selected,
|
|
115
|
+
};
|
|
108
116
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
const optionalSlots = SLOT_RULES_BY_CATEGORY[category].optional;
|
|
118
|
+
const rawSelections = optionalSlots.length === 0
|
|
119
|
+
? []
|
|
120
|
+
: await checkbox({
|
|
121
|
+
choices: optionalSlots.map((slot) => ({
|
|
122
|
+
checked: false,
|
|
112
123
|
name: SLOT_LABELS[slot],
|
|
113
124
|
value: slot,
|
|
114
125
|
})),
|
|
115
|
-
message: 'Select
|
|
126
|
+
message: 'Select optional slots:',
|
|
127
|
+
required: false,
|
|
128
|
+
validate: (value) => getSlotsErrorMessage(category, normalizePromptSlots(value)) ?? true,
|
|
116
129
|
});
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
return
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
value: slot,
|
|
124
|
-
})),
|
|
125
|
-
message: 'Select optional slots:',
|
|
126
|
-
required: false,
|
|
127
|
-
});
|
|
130
|
+
const selections = normalizePromptSlots(rawSelections);
|
|
131
|
+
const { selected } = validateSlots(selections, category);
|
|
132
|
+
return {
|
|
133
|
+
errorMessage: getSlotsErrorMessage(category, selections),
|
|
134
|
+
slots: selected,
|
|
135
|
+
};
|
|
128
136
|
};
|
|
129
137
|
const resolveFramework = (flagValue) => {
|
|
130
138
|
if (flagValue) {
|
|
@@ -170,10 +178,15 @@ const main = async () => {
|
|
|
170
178
|
}
|
|
171
179
|
const name = await resolveName(opts.name);
|
|
172
180
|
const category = await resolveCategory(opts.category);
|
|
173
|
-
const hasSelectableSlots =
|
|
181
|
+
const hasSelectableSlots = SLOT_RULES_BY_CATEGORY[category].optional.length > 0;
|
|
174
182
|
let selectedSlots = [];
|
|
175
183
|
if (hasSelectableSlots) {
|
|
176
|
-
|
|
184
|
+
const resolvedSlots = await resolveSlots(opts.slots, category);
|
|
185
|
+
if (resolvedSlots.errorMessage) {
|
|
186
|
+
console.error(resolvedSlots.errorMessage);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
selectedSlots = resolvedSlots.slots;
|
|
177
190
|
}
|
|
178
191
|
const framework = category === 'chat-bots' ? 'vanilla' : await resolveFramework(opts.framework);
|
|
179
192
|
const language = await resolveLanguage(opts.language);
|
package/dist/package.json
CHANGED
package/dist/src/constants.d.ts
CHANGED
|
@@ -1,21 +1,11 @@
|
|
|
1
|
-
import type { TCategory, TPackageManager, TSlot } from './types.js';
|
|
1
|
+
import type { TCategory, TCategorySlotRules, TPackageManager, TSlot } from './types.js';
|
|
2
2
|
export declare const EXT_HELPER_VERSION: string;
|
|
3
3
|
export declare const CATEGORIES: TCategory[];
|
|
4
4
|
export declare const CATEGORY_LABELS: Record<TCategory, string>;
|
|
5
|
-
export
|
|
6
|
-
slots: TSlot[];
|
|
7
|
-
type: 'exactlyOneOf';
|
|
8
|
-
};
|
|
9
|
-
export type TCategorySlotsConfig = {
|
|
10
|
-
required: TSlot[];
|
|
11
|
-
selectable: TSlot[];
|
|
12
|
-
validations?: TSlotValidationRule[];
|
|
13
|
-
};
|
|
14
|
-
export declare const CATEGORY_SLOTS: Record<TCategory, TCategorySlotsConfig>;
|
|
5
|
+
export declare const SLOT_RULES_BY_CATEGORY: Record<TCategory, TCategorySlotRules>;
|
|
15
6
|
export declare const REQUIRED_SLOTS_BY_CATEGORY: Record<TCategory, TSlot[]>;
|
|
16
7
|
export declare const SELECTABLE_SLOTS_BY_CATEGORY: Record<TCategory, TSlot[]>;
|
|
17
8
|
export declare const SLOT_LABELS: Record<TSlot, string>;
|
|
18
|
-
export declare const SLOT_TYPES: Record<TSlot, string>;
|
|
19
9
|
export declare const FRAMEWORKS: readonly ["vanilla", "preact"];
|
|
20
10
|
export declare const FRAMEWORK_LABELS: Record<(typeof FRAMEWORKS)[number], string>;
|
|
21
11
|
export declare const LANGUAGES: readonly ["ts", "js"];
|
package/dist/src/constants.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import packageJson from '../package.json' with { type: 'json' };
|
|
2
1
|
// All packages use the same versioning
|
|
2
|
+
import packageJson from '../package.json' with { type: 'json' };
|
|
3
3
|
export const EXT_HELPER_VERSION = packageJson.version;
|
|
4
4
|
export const CATEGORIES = ['games-and-fun', 'sex-toys', 'chat-bots'];
|
|
5
5
|
export const CATEGORY_LABELS = {
|
|
@@ -7,43 +7,46 @@ export const CATEGORY_LABELS = {
|
|
|
7
7
|
'sex-toys': 'Sex Toys',
|
|
8
8
|
'chat-bots': 'Chat Bots',
|
|
9
9
|
};
|
|
10
|
-
export const
|
|
10
|
+
export const SLOT_RULES_BY_CATEGORY = {
|
|
11
11
|
'games-and-fun': {
|
|
12
|
-
|
|
13
|
-
selectable: ['rightOverlay', 'videoDecorativeOverlay'],
|
|
14
|
-
validations: [
|
|
12
|
+
groups: [
|
|
15
13
|
{
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
kind: 'exactly-one',
|
|
15
|
+
slots: ['EXTENSION_SLOT_RIGHT_OVERLAY', 'EXTENSION_SLOT_VIDEO_DECORATIVE_OVERLAY'],
|
|
18
16
|
},
|
|
19
17
|
],
|
|
18
|
+
optional: ['EXTENSION_SLOT_RIGHT_OVERLAY', 'EXTENSION_SLOT_VIDEO_DECORATIVE_OVERLAY'],
|
|
19
|
+
requiredBase: ['EXTENSION_SLOT_MAIN_GAME_FUN', 'EXTENSION_SLOT_BACKGROUND'],
|
|
20
20
|
},
|
|
21
21
|
'sex-toys': {
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
groups: [
|
|
23
|
+
{
|
|
24
|
+
kind: 'at-most-one',
|
|
25
|
+
slots: ['EXTENSION_SLOT_RIGHT_OVERLAY', 'EXTENSION_SLOT_VIDEO_DECORATIVE_OVERLAY'],
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
optional: [
|
|
29
|
+
'EXTENSION_SLOT_MOVEABLE_OVERLAY',
|
|
30
|
+
'EXTENSION_SLOT_RIGHT_OVERLAY',
|
|
31
|
+
'EXTENSION_SLOT_VIDEO_DECORATIVE_OVERLAY',
|
|
32
|
+
],
|
|
33
|
+
requiredBase: ['EXTENSION_SLOT_MAIN_SEX_TOY', 'EXTENSION_SLOT_BACKGROUND'],
|
|
24
34
|
},
|
|
25
35
|
'chat-bots': {
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
groups: [],
|
|
37
|
+
optional: ['EXTENSION_SLOT_VIDEO_DECORATIVE_OVERLAY'],
|
|
38
|
+
requiredBase: ['EXTENSION_SLOT_BACKGROUND'],
|
|
28
39
|
},
|
|
29
40
|
};
|
|
30
|
-
export const REQUIRED_SLOTS_BY_CATEGORY = Object.fromEntries(
|
|
31
|
-
export const SELECTABLE_SLOTS_BY_CATEGORY = Object.fromEntries(
|
|
41
|
+
export const REQUIRED_SLOTS_BY_CATEGORY = Object.fromEntries(Object.entries(SLOT_RULES_BY_CATEGORY).map(([category, rules]) => [category, rules.requiredBase]));
|
|
42
|
+
export const SELECTABLE_SLOTS_BY_CATEGORY = Object.fromEntries(Object.entries(SLOT_RULES_BY_CATEGORY).map(([category, rules]) => [category, rules.optional]));
|
|
32
43
|
export const SLOT_LABELS = {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
};
|
|
40
|
-
export const SLOT_TYPES = {
|
|
41
|
-
background: 'EXTENSION_SLOT_BACKGROUND',
|
|
42
|
-
mainGameFun: 'EXTENSION_SLOT_MAIN_GAME_FUN',
|
|
43
|
-
mainSexToy: 'EXTENSION_SLOT_MAIN_SEX_TOY',
|
|
44
|
-
moveableOverlay: 'EXTENSION_SLOT_MOVEABLE_OVERLAY',
|
|
45
|
-
rightOverlay: 'EXTENSION_SLOT_RIGHT_OVERLAY',
|
|
46
|
-
videoDecorativeOverlay: 'EXTENSION_SLOT_VIDEO_DECORATIVE_OVERLAY',
|
|
44
|
+
EXTENSION_SLOT_BACKGROUND: 'background',
|
|
45
|
+
EXTENSION_SLOT_MAIN_GAME_FUN: 'mainGameFun',
|
|
46
|
+
EXTENSION_SLOT_MAIN_SEX_TOY: 'mainSexToy',
|
|
47
|
+
EXTENSION_SLOT_MOVEABLE_OVERLAY: 'moveableOverlay',
|
|
48
|
+
EXTENSION_SLOT_RIGHT_OVERLAY: 'rightOverlay',
|
|
49
|
+
EXTENSION_SLOT_VIDEO_DECORATIVE_OVERLAY: 'videoDecorativeOverlay',
|
|
47
50
|
};
|
|
48
51
|
export const FRAMEWORKS = ['vanilla', 'preact'];
|
|
49
52
|
export const FRAMEWORK_LABELS = {
|
|
@@ -33,7 +33,7 @@ describe('executeCommands', () => {
|
|
|
33
33
|
language: 'ts',
|
|
34
34
|
name: 'my-ext',
|
|
35
35
|
packageManager: 'npm',
|
|
36
|
-
slots: ['
|
|
36
|
+
slots: ['EXTENSION_SLOT_MAIN_GAME_FUN', 'EXTENSION_SLOT_BACKGROUND'],
|
|
37
37
|
};
|
|
38
38
|
const commands = await buildCommands(config, tmpDir);
|
|
39
39
|
const cb = vi.fn();
|
|
@@ -63,7 +63,7 @@ describe('executeCommands', () => {
|
|
|
63
63
|
language: 'js',
|
|
64
64
|
name: 'my-ext',
|
|
65
65
|
packageManager: 'npm',
|
|
66
|
-
slots: ['
|
|
66
|
+
slots: ['EXTENSION_SLOT_MAIN_SEX_TOY', 'EXTENSION_SLOT_BACKGROUND'],
|
|
67
67
|
};
|
|
68
68
|
const commands = await buildCommands(config, tmpDir);
|
|
69
69
|
const corruptedIndex = commands.findIndex((c) => c.type === 'render');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { EXT_HELPER_VERSION, JSX_FRAMEWORKS, PACKAGE_MANAGER_COMMANDS_MAP,
|
|
1
|
+
import { EXT_HELPER_VERSION, JSX_FRAMEWORKS, PACKAGE_MANAGER_COMMANDS_MAP, SLOT_LABELS } from '../constants.js';
|
|
2
2
|
const buildSlotResolverEntries = (config) => config.slots.map((slot) => ({
|
|
3
|
-
|
|
3
|
+
slotCamelCased: SLOT_LABELS[slot],
|
|
4
4
|
slot,
|
|
5
5
|
}));
|
|
6
6
|
export const mapProjectConfigToTemplateData = (config) => {
|
|
@@ -20,7 +20,6 @@ export const mapProjectConfigToTemplateData = (config) => {
|
|
|
20
20
|
name: config.name,
|
|
21
21
|
packageManager: config.packageManager,
|
|
22
22
|
packageManagerCommands: PACKAGE_MANAGER_COMMANDS_MAP[config.packageManager],
|
|
23
|
-
|
|
24
|
-
slots: config.slots,
|
|
23
|
+
slotsEntries: buildSlotResolverEntries(config),
|
|
25
24
|
};
|
|
26
25
|
};
|
|
@@ -7,7 +7,7 @@ const makeConfig = (overrides = {}) => ({
|
|
|
7
7
|
language: 'ts',
|
|
8
8
|
name: 'test-ext',
|
|
9
9
|
packageManager: 'npm',
|
|
10
|
-
slots: ['
|
|
10
|
+
slots: ['EXTENSION_SLOT_MAIN_GAME_FUN', 'EXTENSION_SLOT_BACKGROUND'],
|
|
11
11
|
...overrides,
|
|
12
12
|
});
|
|
13
13
|
describe('mapProjectConfigToTemplateData', () => {
|
|
@@ -32,32 +32,38 @@ describe('mapProjectConfigToTemplateData', () => {
|
|
|
32
32
|
expect(result.isJsxFramework).toBe(false);
|
|
33
33
|
});
|
|
34
34
|
it('maps each slot to its SLOT_TYPE constant', () => {
|
|
35
|
-
const result = mapProjectConfigToTemplateData(makeConfig({
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
const result = mapProjectConfigToTemplateData(makeConfig({
|
|
36
|
+
slots: [
|
|
37
|
+
'EXTENSION_SLOT_MAIN_GAME_FUN',
|
|
38
|
+
'EXTENSION_SLOT_BACKGROUND',
|
|
39
|
+
'EXTENSION_SLOT_VIDEO_DECORATIVE_OVERLAY',
|
|
40
|
+
],
|
|
41
|
+
}));
|
|
42
|
+
expect(result.slotsEntries).toEqual([
|
|
43
|
+
{ slot: 'EXTENSION_SLOT_MAIN_GAME_FUN', slotCamelCased: 'mainGameFun' },
|
|
44
|
+
{ slot: 'EXTENSION_SLOT_BACKGROUND', slotCamelCased: 'background' },
|
|
45
|
+
{ slot: 'EXTENSION_SLOT_VIDEO_DECORATIVE_OVERLAY', slotCamelCased: 'videoDecorativeOverlay' },
|
|
40
46
|
]);
|
|
41
47
|
});
|
|
42
48
|
it('maps sex-toys category slots correctly', () => {
|
|
43
49
|
const result = mapProjectConfigToTemplateData(makeConfig({
|
|
44
50
|
category: 'sex-toys',
|
|
45
|
-
slots: ['
|
|
51
|
+
slots: ['EXTENSION_SLOT_MAIN_SEX_TOY', 'EXTENSION_SLOT_BACKGROUND', 'EXTENSION_SLOT_MOVEABLE_OVERLAY'],
|
|
46
52
|
}));
|
|
47
|
-
expect(result.
|
|
48
|
-
{ slot: '
|
|
49
|
-
{ slot: '
|
|
50
|
-
{ slot: '
|
|
53
|
+
expect(result.slotsEntries).toEqual([
|
|
54
|
+
{ slot: 'EXTENSION_SLOT_MAIN_SEX_TOY', slotCamelCased: 'mainSexToy' },
|
|
55
|
+
{ slot: 'EXTENSION_SLOT_BACKGROUND', slotCamelCased: 'background' },
|
|
56
|
+
{ slot: 'EXTENSION_SLOT_MOVEABLE_OVERLAY', slotCamelCased: 'moveableOverlay' },
|
|
51
57
|
]);
|
|
52
58
|
});
|
|
53
59
|
it('preserves slot order from config', () => {
|
|
54
|
-
const result = mapProjectConfigToTemplateData(makeConfig({ slots: ['
|
|
55
|
-
expect(result.
|
|
56
|
-
expect(result.
|
|
60
|
+
const result = mapProjectConfigToTemplateData(makeConfig({ slots: ['EXTENSION_SLOT_BACKGROUND', 'EXTENSION_SLOT_MAIN_GAME_FUN'] }));
|
|
61
|
+
expect(result.slotsEntries[0].slot).toBe('EXTENSION_SLOT_BACKGROUND');
|
|
62
|
+
expect(result.slotsEntries[1].slot).toBe('EXTENSION_SLOT_MAIN_GAME_FUN');
|
|
57
63
|
});
|
|
58
64
|
it('returns empty array when no slots provided', () => {
|
|
59
65
|
const result = mapProjectConfigToTemplateData(makeConfig({ slots: [] }));
|
|
60
|
-
expect(result.
|
|
66
|
+
expect(result.slotsEntries).toEqual([]);
|
|
61
67
|
});
|
|
62
68
|
it('passes config reference unchanged', () => {
|
|
63
69
|
const config = makeConfig();
|
|
@@ -69,13 +75,15 @@ describe('mapProjectConfigToTemplateData', () => {
|
|
|
69
75
|
framework: 'preact',
|
|
70
76
|
language: 'js',
|
|
71
77
|
name: 'my-project',
|
|
72
|
-
slots: ['
|
|
78
|
+
slots: ['EXTENSION_SLOT_MAIN_GAME_FUN'],
|
|
73
79
|
});
|
|
74
80
|
const result = mapProjectConfigToTemplateData(config);
|
|
75
81
|
expect(result.name).toBe('my-project');
|
|
76
82
|
expect(result.framework).toBe('preact');
|
|
77
83
|
expect(result.language).toBe('js');
|
|
78
|
-
expect(result.
|
|
84
|
+
expect(result.slotsEntries).toEqual([
|
|
85
|
+
{ slot: 'EXTENSION_SLOT_MAIN_GAME_FUN', slotCamelCased: 'mainGameFun' },
|
|
86
|
+
]);
|
|
79
87
|
});
|
|
80
88
|
it('does not include entryModulePath or slot', () => {
|
|
81
89
|
const result = mapProjectConfigToTemplateData(makeConfig());
|
package/dist/src/planner.js
CHANGED
|
@@ -121,6 +121,6 @@ const planSlotTemplates = async (projectDir, data, slotKey) => {
|
|
|
121
121
|
};
|
|
122
122
|
export const planProject = async (data, projectDir) => {
|
|
123
123
|
const globalCommands = await planGlobalTemplates(projectDir, data);
|
|
124
|
-
const slotCommandArrays = await Promise.all(data.
|
|
124
|
+
const slotCommandArrays = await Promise.all(data.slotsEntries.map(({ slotCamelCased }) => planSlotTemplates(projectDir, data, slotCamelCased)));
|
|
125
125
|
return [...globalCommands, ...slotCommandArrays.flat()];
|
|
126
126
|
};
|
package/dist/src/planner.test.js
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { CATEGORIES, FRAMEWORKS, LANGUAGES, SLOT_RULES_BY_CATEGORY, } from './constants.js';
|
|
3
3
|
import { mapProjectConfigToTemplateData } from './mappers/mapProjectConfigToTemplateModel.js';
|
|
4
4
|
import { planProject } from './planner.js';
|
|
5
5
|
const PROJECT_DIR = '/output/my-ext';
|
|
6
6
|
const buildLabel = (language, framework, category, slots) => `${language} + ${framework}, ${category} (${slots.join(', ')})`;
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
if (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
const buildSlotCombos = (category) => {
|
|
8
|
+
const rules = SLOT_RULES_BY_CATEGORY[category];
|
|
9
|
+
const base = [...rules.requiredBase];
|
|
10
|
+
const combos = [base];
|
|
11
|
+
if (category === 'games-and-fun') {
|
|
12
|
+
combos.length = 0;
|
|
13
|
+
combos.push([...base, 'EXTENSION_SLOT_RIGHT_OVERLAY'], [...base, 'EXTENSION_SLOT_VIDEO_DECORATIVE_OVERLAY']);
|
|
14
|
+
return combos;
|
|
15
|
+
}
|
|
16
|
+
if (category === 'sex-toys') {
|
|
17
|
+
combos.push([...base, 'EXTENSION_SLOT_MOVEABLE_OVERLAY'], [...base, 'EXTENSION_SLOT_RIGHT_OVERLAY'], [...base, 'EXTENSION_SLOT_VIDEO_DECORATIVE_OVERLAY'], [...base, 'EXTENSION_SLOT_MOVEABLE_OVERLAY', 'EXTENSION_SLOT_RIGHT_OVERLAY'], [...base, 'EXTENSION_SLOT_MOVEABLE_OVERLAY', 'EXTENSION_SLOT_VIDEO_DECORATIVE_OVERLAY']);
|
|
16
18
|
}
|
|
17
|
-
return
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
];
|
|
19
|
+
return combos;
|
|
20
|
+
};
|
|
21
|
+
const SLOT_COMBOS_BY_CATEGORY = Object.fromEntries(CATEGORIES.map((category) => {
|
|
22
|
+
return [category, buildSlotCombos(category)];
|
|
21
23
|
}));
|
|
22
24
|
const TEST_MATRIX = CATEGORIES.flatMap((category) => SLOT_COMBOS_BY_CATEGORY[category].flatMap((slots) => FRAMEWORKS.flatMap((framework) => LANGUAGES.map((language) => ({
|
|
23
25
|
category,
|
package/dist/src/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type TCategory = 'games-and-fun' | 'sex-toys' | 'chat-bots';
|
|
2
2
|
export type TFramework = 'vanilla' | 'preact';
|
|
3
3
|
export type TLanguage = 'ts' | 'js';
|
|
4
|
-
export type TSlot = '
|
|
4
|
+
export type TSlot = 'EXTENSION_SLOT_MAIN_GAME_FUN' | 'EXTENSION_SLOT_MAIN_SEX_TOY' | 'EXTENSION_SLOT_RIGHT_OVERLAY' | 'EXTENSION_SLOT_VIDEO_DECORATIVE_OVERLAY' | 'EXTENSION_SLOT_MOVEABLE_OVERLAY' | 'EXTENSION_SLOT_BACKGROUND';
|
|
5
5
|
export type TPackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun';
|
|
6
6
|
export type TProjectConfig = {
|
|
7
7
|
category: TCategory;
|
|
@@ -11,9 +11,10 @@ export type TProjectConfig = {
|
|
|
11
11
|
packageManager: TPackageManager;
|
|
12
12
|
slots: TSlot[];
|
|
13
13
|
};
|
|
14
|
-
export type
|
|
15
|
-
slot:
|
|
16
|
-
|
|
14
|
+
export type TSlotEntry = {
|
|
15
|
+
slot: TSlot;
|
|
16
|
+
/** The camel cased version of the slot (e.g. `mainGameFun`). It's used as html file names */
|
|
17
|
+
slotCamelCased: string;
|
|
17
18
|
};
|
|
18
19
|
export type TPackageManagerCommands = {
|
|
19
20
|
build: string;
|
|
@@ -34,8 +35,7 @@ export type TTemplateData = {
|
|
|
34
35
|
packageManager: TPackageManager;
|
|
35
36
|
packageManagerCommands: TPackageManagerCommands;
|
|
36
37
|
slot?: string;
|
|
37
|
-
|
|
38
|
-
slots: TSlot[];
|
|
38
|
+
slotsEntries: TSlotEntry[];
|
|
39
39
|
stylePath?: string;
|
|
40
40
|
};
|
|
41
41
|
export type TTemplateCommand = {
|
|
@@ -48,3 +48,12 @@ export type TTemplateCommand = {
|
|
|
48
48
|
srcPath: string;
|
|
49
49
|
type: 'render';
|
|
50
50
|
};
|
|
51
|
+
export type TSlotGroupRule = {
|
|
52
|
+
kind: 'at-most-one' | 'exactly-one';
|
|
53
|
+
slots: TSlot[];
|
|
54
|
+
};
|
|
55
|
+
export type TCategorySlotRules = {
|
|
56
|
+
groups: TSlotGroupRule[];
|
|
57
|
+
optional: TSlot[];
|
|
58
|
+
requiredBase: TSlot[];
|
|
59
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { TCategory, TSlot } from '../types.js';
|
|
2
2
|
export type TValidateSlotsResult = {
|
|
3
|
-
errors: string[];
|
|
4
3
|
invalid: string[];
|
|
5
|
-
|
|
4
|
+
selected: TSlot[];
|
|
5
|
+
violations: string[];
|
|
6
6
|
};
|
|
7
7
|
export declare const validateSlots: (slots: string[], category: TCategory) => TValidateSlotsResult;
|
|
@@ -1,27 +1,31 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SLOT_RULES_BY_CATEGORY } from '../constants.js';
|
|
2
|
+
const formatGroupMessage = (kind, slots) => {
|
|
3
|
+
const prefix = kind === 'exactly-one' ? 'Choose exactly one of' : 'Choose at most one of';
|
|
4
|
+
return `${prefix}: ${slots.join(', ')}.`;
|
|
5
|
+
};
|
|
2
6
|
export const validateSlots = (slots, category) => {
|
|
3
|
-
const
|
|
4
|
-
const allowed = new Set(
|
|
5
|
-
const
|
|
7
|
+
const rules = SLOT_RULES_BY_CATEGORY[category];
|
|
8
|
+
const allowed = new Set(rules.optional);
|
|
9
|
+
const selected = [];
|
|
6
10
|
const invalid = [];
|
|
7
|
-
const errors = [];
|
|
8
|
-
const hasOptionalSlots = allowed.size > 0;
|
|
9
|
-
if (!hasOptionalSlots) {
|
|
10
|
-
return { errors, invalid: slots, valid };
|
|
11
|
-
}
|
|
12
11
|
for (const slot of slots) {
|
|
13
12
|
if (allowed.has(slot)) {
|
|
14
|
-
|
|
13
|
+
selected.push(slot);
|
|
15
14
|
}
|
|
16
15
|
else {
|
|
17
16
|
invalid.push(slot);
|
|
18
17
|
}
|
|
19
18
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
const selectedSet = new Set(selected);
|
|
20
|
+
const violations = rules.groups.flatMap((group) => {
|
|
21
|
+
const count = group.slots.filter((slot) => selectedSet.has(slot)).length;
|
|
22
|
+
if (group.kind === 'exactly-one' && count !== 1) {
|
|
23
|
+
return [formatGroupMessage(group.kind, group.slots)];
|
|
24
|
+
}
|
|
25
|
+
if (group.kind === 'at-most-one' && count > 1) {
|
|
26
|
+
return [formatGroupMessage(group.kind, group.slots)];
|
|
24
27
|
}
|
|
28
|
+
return [];
|
|
25
29
|
});
|
|
26
|
-
return {
|
|
30
|
+
return { invalid, selected, violations };
|
|
27
31
|
};
|