@ak--47/dungeon-master 1.0.0 → 1.1.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/README.md +7 -7
- package/dungeons/technical/ad-spend-schema.json +128 -0
- package/dungeons/technical/ad-spend.js +116 -0
- package/dungeons/technical/anonymous-users-schema.json +91 -0
- package/dungeons/technical/anonymous-users.js +102 -0
- package/dungeons/technical/array-of-object-lookup-schema.json +191 -0
- package/dungeons/{array-of-object-lookup.js → technical/array-of-object-lookup.js} +11 -4
- package/dungeons/technical/experiments-schema.json +197 -0
- package/dungeons/technical/experiments.js +168 -0
- package/dungeons/technical/foobar-schema.json +362 -0
- package/dungeons/{foobar.js → technical/foobar.js} +2 -2
- package/dungeons/technical/group-analytics-schema.json +149 -0
- package/dungeons/technical/group-analytics.js +151 -0
- package/dungeons/technical/mirror-strategies-schema.json +84 -0
- package/dungeons/technical/mirror-strategies.js +126 -0
- package/dungeons/technical/nested-objects-schema.json +145 -0
- package/dungeons/technical/nested-objects.js +207 -0
- package/dungeons/technical/retention-cadence-schema.json +37 -0
- package/dungeons/{retention-cadence.js → technical/retention-cadence.js} +1 -1
- package/dungeons/technical/sanity-schema.json +185 -0
- package/dungeons/{sanity.js → technical/sanity.js} +3 -2
- package/dungeons/technical/scale-test-schema.json +70 -0
- package/dungeons/technical/scale-test.js +90 -0
- package/dungeons/technical/scd-schema.json +270 -0
- package/dungeons/{scd.js → technical/scd.js} +19 -10
- package/dungeons/technical/simple-schema.json +278 -0
- package/dungeons/{simple.js → technical/simple.js} +6 -6
- package/dungeons/technical/simplest-schema.json +635 -0
- package/dungeons/{simplest.js → technical/simplest.js} +6 -6
- package/dungeons/technical/text-generation-schema.json +1059 -0
- package/dungeons/{text-generation.js → technical/text-generation.js} +23 -9
- package/dungeons/vertical/community-schema.json +557 -0
- package/dungeons/vertical/community.js +788 -0
- package/dungeons/vertical/devtools-schema.json +580 -0
- package/dungeons/vertical/devtools.js +837 -0
- package/dungeons/vertical/ecommerce-schema.json +586 -0
- package/dungeons/{ecommerce.js → vertical/ecommerce.js} +34 -3
- package/dungeons/vertical/education-schema.json +697 -0
- package/dungeons/{education.js → vertical/education.js} +54 -36
- package/dungeons/vertical/fintech-schema.json +563 -0
- package/dungeons/{fintech.js → vertical/fintech.js} +91 -53
- package/dungeons/vertical/fitness-schema.json +508 -0
- package/dungeons/vertical/fitness.js +802 -0
- package/dungeons/vertical/food-delivery-schema.json +678 -0
- package/dungeons/{food.js → vertical/food-delivery.js} +51 -23
- package/dungeons/vertical/gaming-schema.json +394 -0
- package/dungeons/{gaming.js → vertical/gaming.js} +9 -7
- package/dungeons/vertical/healthcare-schema.json +527 -0
- package/dungeons/vertical/healthcare.js +769 -0
- package/dungeons/vertical/insurance-application-schema.json +466 -0
- package/dungeons/{insurance-application.js → vertical/insurance-application.js} +33 -29
- package/dungeons/vertical/logistics-schema.json +552 -0
- package/dungeons/vertical/logistics.js +793 -0
- package/dungeons/vertical/marketplace-schema.json +511 -0
- package/dungeons/vertical/marketplace.js +844 -0
- package/dungeons/vertical/media-schema.json +787 -0
- package/dungeons/{media.js → vertical/media.js} +94 -16
- package/dungeons/vertical/rpg-schema.json +654 -0
- package/dungeons/{rpg.js → vertical/rpg.js} +78 -57
- package/dungeons/vertical/sass-schema.json +653 -0
- package/dungeons/{sass.js → vertical/sass.js} +49 -23
- package/dungeons/vertical/social-schema.json +552 -0
- package/dungeons/{social.js → vertical/social.js} +60 -14
- package/dungeons/vertical/travel-schema.json +558 -0
- package/dungeons/vertical/travel.js +736 -0
- package/index.js +19 -15
- package/lib/core/config-validator.js +286 -1
- package/lib/core/context.js +7 -5
- package/lib/generators/adspend.js +3 -3
- package/lib/generators/events.js +141 -14
- package/lib/generators/funnels.js +67 -7
- package/lib/generators/text.js +2 -2
- package/lib/orchestrators/user-loop.js +441 -22
- package/lib/templates/soup-presets.js +2 -2
- package/lib/utils/utils.js +4 -5
- package/package.json +4 -3
- package/scripts/dungeon-to-json.mjs +163 -0
- package/scripts/extract-dungeon-schema.mjs +297 -0
- package/scripts/json-to-dungeon.mjs +200 -0
- package/scripts/run-dungeon.mjs +76 -0
- package/scripts/run-many.mjs +118 -0
- package/scripts/verify-runner.mjs +42 -0
- package/types.d.ts +313 -1
- package/dungeons/array-of-object-lookup-schema.json +0 -327
- package/dungeons/ecommerce-schema.json +0 -462
- package/dungeons/education-schema.json +0 -2409
- package/dungeons/fintech-schema.json +0 -14034
- package/dungeons/foobar-schema.json +0 -403
- package/dungeons/food-delivery-schema.json +0 -192
- package/dungeons/food-delivery.js +0 -602
- package/dungeons/food-schema.json +0 -1152
- package/dungeons/gaming-schema.json +0 -1270
- package/dungeons/insurance-application-schema.json +0 -204
- package/dungeons/media-schema.json +0 -906
- package/dungeons/retention-cadence-schema.json +0 -78
- package/dungeons/rpg-schema.json +0 -4526
- package/dungeons/sanity-schema.json +0 -255
- package/dungeons/sass-schema.json +0 -1291
- package/dungeons/scd-schema.json +0 -919
- package/dungeons/simple-schema.json +0 -608
- package/dungeons/simplest-schema.json +0 -1418
- package/dungeons/social-schema.json +0 -1118
- package/dungeons/text-generation-schema.json +0 -3096
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Converts a frontend JSON dungeon file to a proper JavaScript dungeon file
|
|
5
|
+
* Usage: node scripts/json-to-dungeon.js <input.json> [output-name]
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
|
|
14
|
+
// Get command line arguments
|
|
15
|
+
const args = process.argv.slice(2);
|
|
16
|
+
|
|
17
|
+
if (args.length === 0) {
|
|
18
|
+
console.error('❌ Error: Please provide an input JSON file');
|
|
19
|
+
console.log('Usage: node scripts/json-to-dungeon.js <input.json> [output-name]');
|
|
20
|
+
console.log('Example: node scripts/json-to-dungeon.js /path/to/dungeon.json my-dungeon');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const inputPath = path.resolve(args[0]);
|
|
25
|
+
const outputName = args[1] || path.basename(inputPath, '.json');
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
// Read the JSON file
|
|
29
|
+
console.log(`📖 Reading ${inputPath}...`);
|
|
30
|
+
const jsonContent = readFileSync(inputPath, 'utf-8');
|
|
31
|
+
const dungeonData = JSON.parse(jsonContent);
|
|
32
|
+
|
|
33
|
+
// Extract schema and hooks
|
|
34
|
+
const schema = dungeonData.schema || dungeonData;
|
|
35
|
+
const hooksString = dungeonData.hooks || null;
|
|
36
|
+
|
|
37
|
+
// Convert to JavaScript
|
|
38
|
+
console.log('🔄 Converting to JavaScript module...');
|
|
39
|
+
const jsContent = convertToJavaScript(schema, hooksString, outputName);
|
|
40
|
+
|
|
41
|
+
// Write to same directory as input file
|
|
42
|
+
const outputPath = path.join(path.dirname(inputPath), `${outputName}.js`);
|
|
43
|
+
writeFileSync(outputPath, jsContent, 'utf-8');
|
|
44
|
+
|
|
45
|
+
console.log(`✅ Successfully created ${outputPath}`);
|
|
46
|
+
console.log(`\n💡 You can now import and use this dungeon:`);
|
|
47
|
+
console.log(` import config from '${path.relative(process.cwd(), outputPath)}';`);
|
|
48
|
+
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error('❌ Error:', error.message);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Convert JSON schema to JavaScript module string
|
|
56
|
+
*/
|
|
57
|
+
function convertToJavaScript(schema, hooksString, name) {
|
|
58
|
+
const lines = [];
|
|
59
|
+
|
|
60
|
+
// File header
|
|
61
|
+
lines.push(`/**`);
|
|
62
|
+
lines.push(` * Generated dungeon: ${name}`);
|
|
63
|
+
lines.push(` * Created: ${new Date().toISOString()}`);
|
|
64
|
+
lines.push(` * Source: Converted from frontend JSON`);
|
|
65
|
+
lines.push(` */`);
|
|
66
|
+
lines.push('');
|
|
67
|
+
|
|
68
|
+
// Imports
|
|
69
|
+
lines.push(`import Chance from 'chance';`);
|
|
70
|
+
lines.push(`let chance = new Chance();`);
|
|
71
|
+
lines.push(`import dayjs from "dayjs";`);
|
|
72
|
+
lines.push(`import utc from "dayjs/plugin/utc.js";`);
|
|
73
|
+
lines.push(`dayjs.extend(utc);`);
|
|
74
|
+
lines.push(`import { uid, comma } from 'ak-tools';`);
|
|
75
|
+
lines.push(`import {
|
|
76
|
+
pickAWinner, weighNumRange, date, integer, weighChoices,
|
|
77
|
+
choose, range, exhaust, maybe, takeSome, randomElement, randomInt,
|
|
78
|
+
weighArray
|
|
79
|
+
} from "../lib/utils/utils.js";`);
|
|
80
|
+
lines.push(`import { createTextGenerator, generateBatch } from "../lib/generators/text.js";`);
|
|
81
|
+
lines.push('');
|
|
82
|
+
|
|
83
|
+
// Config object
|
|
84
|
+
lines.push(`/** @type {import('../types').Dungeon} */`);
|
|
85
|
+
lines.push(`const config = ${convertValueToJS(schema, 1)};`);
|
|
86
|
+
lines.push('');
|
|
87
|
+
|
|
88
|
+
// Hook function (if exists)
|
|
89
|
+
if (hooksString) {
|
|
90
|
+
lines.push(`// Apply the hook function`);
|
|
91
|
+
lines.push(`config.hook = ${hooksString};`);
|
|
92
|
+
lines.push('');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Export
|
|
96
|
+
lines.push(`export default config;`);
|
|
97
|
+
lines.push('');
|
|
98
|
+
|
|
99
|
+
return lines.join('\n');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Convert a JavaScript value to its source code representation
|
|
104
|
+
*/
|
|
105
|
+
function convertValueToJS(value, indentLevel = 0) {
|
|
106
|
+
const indent = '\t'.repeat(indentLevel);
|
|
107
|
+
const nextIndent = '\t'.repeat(indentLevel + 1);
|
|
108
|
+
|
|
109
|
+
// Null
|
|
110
|
+
if (value === null || value === undefined) {
|
|
111
|
+
return 'null';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Boolean
|
|
115
|
+
if (typeof value === 'boolean') {
|
|
116
|
+
return String(value);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Number
|
|
120
|
+
if (typeof value === 'number') {
|
|
121
|
+
return String(value);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// String
|
|
125
|
+
if (typeof value === 'string') {
|
|
126
|
+
// Handle multi-line strings
|
|
127
|
+
if (value.includes('\n')) {
|
|
128
|
+
return '`' + value.replace(/`/g, '\\`') + '`';
|
|
129
|
+
}
|
|
130
|
+
// Use double quotes for regular strings
|
|
131
|
+
return '"' + value.replace(/"/g, '\\"') + '"';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Function (stored as object with functionName and args)
|
|
135
|
+
if (value && typeof value === 'object' && value.functionName) {
|
|
136
|
+
const funcName = value.functionName;
|
|
137
|
+
const args = value.args || [];
|
|
138
|
+
const argsStr = args.map(arg => convertValueToJS(arg, 0)).join(', ');
|
|
139
|
+
|
|
140
|
+
// Handle arrow functions
|
|
141
|
+
if (funcName === 'arrow') {
|
|
142
|
+
return value.body || '() => {}';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Handle chance bindings
|
|
146
|
+
if (funcName.startsWith('chance.')) {
|
|
147
|
+
return `${funcName}.bind(chance)`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Regular function calls
|
|
151
|
+
return `${funcName}(${argsStr})`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Array
|
|
155
|
+
if (Array.isArray(value)) {
|
|
156
|
+
if (value.length === 0) {
|
|
157
|
+
return '[]';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Check if it's a simple array (all primitives)
|
|
161
|
+
const allPrimitives = value.every(v =>
|
|
162
|
+
v === null ||
|
|
163
|
+
typeof v === 'boolean' ||
|
|
164
|
+
typeof v === 'number' ||
|
|
165
|
+
typeof v === 'string'
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
if (allPrimitives && value.length <= 5) {
|
|
169
|
+
// Inline simple arrays
|
|
170
|
+
const items = value.map(v => convertValueToJS(v, 0));
|
|
171
|
+
return `[${items.join(', ')}]`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Multi-line arrays
|
|
175
|
+
const items = value.map(v => `${nextIndent}${convertValueToJS(v, indentLevel + 1)}`);
|
|
176
|
+
return `[\n${items.join(',\n')}\n${indent}]`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Object
|
|
180
|
+
if (typeof value === 'object') {
|
|
181
|
+
const keys = Object.keys(value);
|
|
182
|
+
|
|
183
|
+
if (keys.length === 0) {
|
|
184
|
+
return '{}';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const entries = keys.map(key => {
|
|
188
|
+
const val = convertValueToJS(value[key], indentLevel + 1);
|
|
189
|
+
// Use quotes for keys with special characters or spaces
|
|
190
|
+
const needsQuotes = /[^a-zA-Z0-9_$]/.test(key) || /^\d/.test(key);
|
|
191
|
+
const keyStr = needsQuotes ? `"${key}"` : key;
|
|
192
|
+
return `${nextIndent}${keyStr}: ${val}`;
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
return `{\n${entries.join(',\n')}\n${indent}}`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Fallback
|
|
199
|
+
return 'null';
|
|
200
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import generate from '../index.js';
|
|
5
|
+
const { NODE_ENV = "unknown" } = process.env;
|
|
6
|
+
|
|
7
|
+
// Get the dungeon file path from command line arguments
|
|
8
|
+
const dungeonPath = process.argv[2];
|
|
9
|
+
|
|
10
|
+
if (!dungeonPath) {
|
|
11
|
+
console.error('❌ Error: No dungeon file specified');
|
|
12
|
+
console.error('Usage: node run-dungeon.js <path-to-dungeon.js>');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Resolve the absolute path
|
|
17
|
+
const absolutePath = path.isAbsolute(dungeonPath)
|
|
18
|
+
? dungeonPath
|
|
19
|
+
: path.resolve(process.cwd(), dungeonPath);
|
|
20
|
+
|
|
21
|
+
// Handle Ctrl+C gracefully — let user-loop finish current user, then exit
|
|
22
|
+
process.on('SIGINT', () => {
|
|
23
|
+
// Second Ctrl+C forces immediate exit
|
|
24
|
+
process.on('SIGINT', () => process.exit(1));
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
console.log(`\n🎲 Running dungeon: ${path.basename(absolutePath)}\n`);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// Dynamically import the dungeon configuration
|
|
31
|
+
const { default: config } = await import(absolutePath);
|
|
32
|
+
|
|
33
|
+
if (!config) {
|
|
34
|
+
console.error('❌ Error: Dungeon file must export a default configuration object');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Run the dungeon (always verbose when run locally)
|
|
39
|
+
config.verbose = true;
|
|
40
|
+
const results = await generate(config);
|
|
41
|
+
|
|
42
|
+
// Display results
|
|
43
|
+
console.log('\n✅ Dungeon run complete!');
|
|
44
|
+
console.log('═'.repeat(50));
|
|
45
|
+
console.log(`📊 Results:`);
|
|
46
|
+
console.log(` Users: ${results.userCount.toLocaleString()}`);
|
|
47
|
+
console.log(` Events: ${results.eventCount.toLocaleString()}`);
|
|
48
|
+
if (results.groupCount) {
|
|
49
|
+
console.log(` Groups: ${results.groupCount.toLocaleString()}`);
|
|
50
|
+
}
|
|
51
|
+
if (results.time) {
|
|
52
|
+
console.log(` Duration: ${results.time.human || results.time.delta + 'ms'}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (results.avgEPS) {
|
|
56
|
+
console.log(` Avg EPS: ${results.avgEPS.toLocaleString()}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (results.files && results.files.length > 0) {
|
|
60
|
+
console.log(`\n📁 Files written:`);
|
|
61
|
+
results.files.forEach(file => {
|
|
62
|
+
console.log(` - ${file}`);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log('═'.repeat(50) + '\n');
|
|
67
|
+
|
|
68
|
+
// if (NODE_ENV === 'dev') debugger;
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error('❌ Error running dungeon:', error.message);
|
|
74
|
+
console.error(error.stack);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { fork } from 'child_process';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import dotenv from "dotenv";
|
|
6
|
+
dotenv.config();
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
import main from "../index.js";
|
|
12
|
+
const CONCURRENCY = 10
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Run multiple dungeons concurrently from the command line.
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* node scripts/run-many.mjs dungeons/vertical/gaming.js dungeons/vertical/media.js dungeons/vertical/food-delivery.js
|
|
19
|
+
* node scripts/run-many.mjs dungeons/vertical/*.js
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const args = process.argv.slice(2);
|
|
23
|
+
|
|
24
|
+
if (process.env.__SIM_CHILD) {
|
|
25
|
+
// CHILD: run the assigned dungeon
|
|
26
|
+
const dungeonPath = process.env.__SIM_PATH;
|
|
27
|
+
(async () => {
|
|
28
|
+
try {
|
|
29
|
+
const mod = await import(path.resolve(dungeonPath));
|
|
30
|
+
const spec = mod.default;
|
|
31
|
+
spec.name = path.basename(dungeonPath, '.js');
|
|
32
|
+
spec.verbose = false;
|
|
33
|
+
const result = await main(spec);
|
|
34
|
+
const summary = { eventCount: result.eventCount, userCount: result.userCount, time: result.time };
|
|
35
|
+
if (process.send) process.send({ name: spec.name, success: true, summary }, undefined, undefined, () => process.exit(0));
|
|
36
|
+
else process.exit(0);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.error(`[${dungeonPath}] Error:`, err.message);
|
|
39
|
+
if (process.send) process.send({ name: dungeonPath, success: false, error: err.message });
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
})();
|
|
43
|
+
} else {
|
|
44
|
+
// PARENT: fork children with concurrency limit
|
|
45
|
+
if (args.length === 0) {
|
|
46
|
+
console.error("Usage: node scripts/run-many.mjs <dungeon1.js> <dungeon2.js> ...");
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const limit = CONCURRENCY;
|
|
51
|
+
let running = 0;
|
|
52
|
+
const queue = args.map(p => ({ path: p, name: path.basename(p, '.js') }));
|
|
53
|
+
const results = [];
|
|
54
|
+
const total = queue.length;
|
|
55
|
+
|
|
56
|
+
console.log(`\n${"═".repeat(60)}`);
|
|
57
|
+
console.log(`🎲 Running ${total} dungeon(s) with concurrency ${limit}`);
|
|
58
|
+
console.log(`${"═".repeat(60)}\n`);
|
|
59
|
+
|
|
60
|
+
function runOne(dungeon) {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
console.log(`▶ Starting: ${dungeon.name}`);
|
|
63
|
+
const startTime = Date.now();
|
|
64
|
+
const child = fork(__filename, [], {
|
|
65
|
+
env: { ...process.env, __SIM_CHILD: "1", __SIM_PATH: dungeon.path },
|
|
66
|
+
stdio: ['inherit', 'inherit', 'inherit', 'ipc']
|
|
67
|
+
});
|
|
68
|
+
child.on('message', (msg) => {
|
|
69
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
70
|
+
if (msg.success) {
|
|
71
|
+
console.log(`✅ Finished: ${dungeon.name} (${elapsed}s)`);
|
|
72
|
+
} else {
|
|
73
|
+
console.error(`❌ Failed: ${dungeon.name} - ${msg.error} (${elapsed}s)`);
|
|
74
|
+
}
|
|
75
|
+
resolve(msg);
|
|
76
|
+
});
|
|
77
|
+
child.on('error', (err) => {
|
|
78
|
+
console.error(`❌ Fork error: ${dungeon.name}`, err.message);
|
|
79
|
+
reject(err);
|
|
80
|
+
});
|
|
81
|
+
child.on('exit', (code) => {
|
|
82
|
+
if (code !== 0 && code !== null) {
|
|
83
|
+
reject(new Error(`${dungeon.name} exited with code ${code}`));
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function runNext() {
|
|
90
|
+
while (running < limit && queue.length > 0) {
|
|
91
|
+
const dungeon = queue.shift();
|
|
92
|
+
running++;
|
|
93
|
+
runOne(dungeon)
|
|
94
|
+
.then(res => results.push(res))
|
|
95
|
+
.catch(e => results.push({ name: dungeon.name, success: false, error: e.message }))
|
|
96
|
+
.finally(() => { running--; runNext(); });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
runNext();
|
|
100
|
+
|
|
101
|
+
const wait = () => new Promise(resolve => {
|
|
102
|
+
const check = () => {
|
|
103
|
+
if (results.length === total) resolve(results);
|
|
104
|
+
else setTimeout(check, 250);
|
|
105
|
+
};
|
|
106
|
+
check();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const all = await wait();
|
|
110
|
+
const succeeded = all.filter(r => r.success).length;
|
|
111
|
+
const failed = all.filter(r => !r.success).length;
|
|
112
|
+
|
|
113
|
+
console.log(`\n${"═".repeat(60)}`);
|
|
114
|
+
console.log(`🏁 Done: ${succeeded} succeeded, ${failed} failed out of ${total}`);
|
|
115
|
+
console.log(`${"═".repeat(60)}\n`);
|
|
116
|
+
|
|
117
|
+
if (failed > 0) process.exit(1);
|
|
118
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify Runner — runs a dungeon at constrained params for hook verification.
|
|
3
|
+
*
|
|
4
|
+
* Usage: node scripts/verify-runner.mjs <dungeon-path> [run-name]
|
|
5
|
+
*
|
|
6
|
+
* Example: node scripts/verify-runner.mjs dungeons/streaming.js verify-streaming
|
|
7
|
+
*/
|
|
8
|
+
import generate from '../index.js';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
|
|
11
|
+
const dungeonPath = process.argv[2];
|
|
12
|
+
if (!dungeonPath) {
|
|
13
|
+
console.error('Usage: node scripts/verify-runner.mjs <dungeon-path> [run-name]');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const runName = process.argv[3] || 'verify-hooks';
|
|
18
|
+
const absolutePath = path.isAbsolute(dungeonPath)
|
|
19
|
+
? dungeonPath
|
|
20
|
+
: path.resolve(process.cwd(), dungeonPath);
|
|
21
|
+
|
|
22
|
+
const { default: config } = await import(absolutePath);
|
|
23
|
+
|
|
24
|
+
const results = await generate({
|
|
25
|
+
...config,
|
|
26
|
+
token: "",
|
|
27
|
+
numUsers: 1000,
|
|
28
|
+
numEvents: 100_000,
|
|
29
|
+
format: "json",
|
|
30
|
+
gzip: false,
|
|
31
|
+
writeToDisk: true,
|
|
32
|
+
name: runName,
|
|
33
|
+
concurrency: 1,
|
|
34
|
+
verbose: false,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
console.log(JSON.stringify({
|
|
38
|
+
eventCount: results.eventCount,
|
|
39
|
+
userCount: results.userCount,
|
|
40
|
+
files: results.files,
|
|
41
|
+
duration: results.time?.human || results.time?.delta + 'ms'
|
|
42
|
+
}));
|