@ak--47/dungeon-master 1.5.1 → 1.5.3
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/.claude/skills/analyze-soup/SKILL.md +6 -1
- package/.claude/skills/create-dungeon/SKILL.md +159 -54
- package/.claude/skills/verify-dungeon/SKILL.md +28 -8
- package/.claude/skills/verify-dungeon/references/counting-semantics.md +31 -6
- package/.claude/skills/verify-dungeon/references/report-format.md +5 -7
- package/.claude/skills/verify-dungeon/references/sql-recipes.md +44 -25
- package/.claude/skills/write-hooks/SKILL.md +33 -5
- package/CHANGELOG.md +93 -0
- package/README.md +26 -0
- package/index.js +3 -1
- package/lib/core/dungeon-to-json.js +220 -0
- package/lib/core/extract-comments.js +120 -0
- package/package.json +2 -2
- package/scripts/dungeon-to-json.mjs +5 -124
- package/types.d.ts +73 -0
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Converts a JavaScript dungeon file to JSON that can be loaded into the UI
|
|
4
|
+
* Converts a JavaScript dungeon file to JSON that can be loaded into the UI.
|
|
5
|
+
* Thin CLI wrapper around the exported `dungeonToJSON` (lib/core/dungeon-to-json.js).
|
|
5
6
|
* Usage: node scripts/dungeon-to-json.js <input.js> [output-name]
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import { writeFileSync } from 'fs';
|
|
9
10
|
import path from 'path';
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
import { dungeonToJSON } from '../lib/core/dungeon-to-json.js';
|
|
13
12
|
|
|
14
13
|
// Get command line arguments
|
|
15
14
|
const args = process.argv.slice(2);
|
|
@@ -25,34 +24,10 @@ const inputPath = path.resolve(args[0]);
|
|
|
25
24
|
const outputName = args[1] || path.basename(inputPath, '.js');
|
|
26
25
|
|
|
27
26
|
try {
|
|
28
|
-
// Import the JavaScript module
|
|
29
27
|
console.log(`📖 Loading ${inputPath}...`);
|
|
30
|
-
const module = await import(`file://${inputPath}`);
|
|
31
|
-
const config = module.default;
|
|
32
|
-
|
|
33
|
-
if (!config) {
|
|
34
|
-
throw new Error('No default export found in the module');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Extract hooks if they exist
|
|
38
|
-
const hooksFunction = config.hook;
|
|
39
|
-
const hooksString = hooksFunction ? hooksFunction.toString() : null;
|
|
40
|
-
|
|
41
|
-
// Create a clean config without the hook function
|
|
42
|
-
const cleanConfig = { ...config };
|
|
43
|
-
delete cleanConfig.hook;
|
|
44
|
-
|
|
45
|
-
// Convert to JSON-serializable format
|
|
46
28
|
console.log('🔄 Converting to JSON format...');
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// Create the dungeon state object (same format as UI saves)
|
|
50
|
-
const dungeonState = {
|
|
51
|
-
schema,
|
|
52
|
-
hooks: hooksString,
|
|
53
|
-
timestamp: new Date().toISOString(),
|
|
54
|
-
version: '4.0'
|
|
55
|
-
};
|
|
29
|
+
// includeCredentials: true preserves the legacy UI round-trip behavior (full config).
|
|
30
|
+
const dungeonState = await dungeonToJSON(inputPath, { includeCredentials: true });
|
|
56
31
|
|
|
57
32
|
// Write to JSON file
|
|
58
33
|
const outputPath = path.join(path.dirname(inputPath), `${outputName}.json`);
|
|
@@ -67,97 +42,3 @@ try {
|
|
|
67
42
|
console.error(error.stack);
|
|
68
43
|
process.exit(1);
|
|
69
44
|
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Convert JavaScript config to JSON-serializable format
|
|
73
|
-
* Detects functions and converts them to object representation
|
|
74
|
-
*/
|
|
75
|
-
function convertToJSON(value) {
|
|
76
|
-
// Null/undefined
|
|
77
|
-
if (value === null || value === undefined) {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Primitives
|
|
82
|
-
if (typeof value === 'boolean' || typeof value === 'number' || typeof value === 'string') {
|
|
83
|
-
return value;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Functions - convert to object representation
|
|
87
|
-
if (typeof value === 'function') {
|
|
88
|
-
return convertFunctionToObject(value);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Arrays
|
|
92
|
-
if (Array.isArray(value)) {
|
|
93
|
-
return value.map(item => convertToJSON(item));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Objects
|
|
97
|
-
if (typeof value === 'object') {
|
|
98
|
-
const result = {};
|
|
99
|
-
for (const [key, val] of Object.entries(value)) {
|
|
100
|
-
result[key] = convertToJSON(val);
|
|
101
|
-
}
|
|
102
|
-
return result;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Fallback
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Convert a function to its object representation
|
|
111
|
-
* Tries to detect the function type and extract parameters
|
|
112
|
-
*/
|
|
113
|
-
function convertFunctionToObject(func) {
|
|
114
|
-
const funcString = func.toString();
|
|
115
|
-
|
|
116
|
-
// Arrow function
|
|
117
|
-
if (funcString.startsWith('(') || funcString.startsWith('_') || funcString.includes('=>')) {
|
|
118
|
-
return {
|
|
119
|
-
functionName: 'arrow',
|
|
120
|
-
body: funcString
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Bound chance methods (e.g., chance.name.bind(chance))
|
|
125
|
-
if (funcString.includes('.bind(')) {
|
|
126
|
-
const match = funcString.match(/chance\.(\w+)\.bind/);
|
|
127
|
-
if (match) {
|
|
128
|
-
return {
|
|
129
|
-
functionName: `chance.${match[1]}`,
|
|
130
|
-
args: []
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Try to detect common utility functions
|
|
136
|
-
// This is a best-effort approach - some complex functions might not be detected
|
|
137
|
-
const commonFunctions = [
|
|
138
|
-
'weighNumRange',
|
|
139
|
-
'weighArray',
|
|
140
|
-
'weighChoices',
|
|
141
|
-
'pickAWinner',
|
|
142
|
-
'date',
|
|
143
|
-
'integer',
|
|
144
|
-
'uid',
|
|
145
|
-
'comma'
|
|
146
|
-
];
|
|
147
|
-
|
|
148
|
-
for (const fnName of commonFunctions) {
|
|
149
|
-
if (funcString.includes(fnName)) {
|
|
150
|
-
// Extract args (this is simplified - real parsing would be more complex)
|
|
151
|
-
return {
|
|
152
|
-
functionName: fnName,
|
|
153
|
-
args: [] // Args would need to be extracted, but that's complex
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Generic function - just store as arrow function
|
|
159
|
-
return {
|
|
160
|
-
functionName: 'arrow',
|
|
161
|
-
body: funcString
|
|
162
|
-
};
|
|
163
|
-
}
|
package/types.d.ts
CHANGED
|
@@ -1508,6 +1508,79 @@ export declare function parseJSONDungeon(json: object): Dungeon;
|
|
|
1508
1508
|
/** Validate that an object has the minimum shape of a dungeon config. Throws on shape violations. */
|
|
1509
1509
|
export declare function validateDungeonShape(config: unknown): void;
|
|
1510
1510
|
|
|
1511
|
+
/**
|
|
1512
|
+
* The serialized form of a function found in a dungeon schema. Produced by `dungeonToJSON`
|
|
1513
|
+
* and revived by `parseJSONDungeon`.
|
|
1514
|
+
*
|
|
1515
|
+
* - `functionName: "arrow"` with a `body` — an inline/closure function, re-eval'd on revive.
|
|
1516
|
+
* - `functionName: "chance.<method>"` — a bound chance method, e.g. `chance.name.bind(chance)`.
|
|
1517
|
+
* - `functionName: "<utility>"` (weighNumRange, weighArray, …) — a detected utility; args are
|
|
1518
|
+
* not recoverable from the stringified source, so it revives to null (best effort).
|
|
1519
|
+
*
|
|
1520
|
+
* `dataType` records the function's sampled output type so the field's type is preserved even
|
|
1521
|
+
* when the generator itself can't be revived.
|
|
1522
|
+
*/
|
|
1523
|
+
export interface SerializedFunction {
|
|
1524
|
+
/** "arrow", "chance.<method>", or a known utility name. */
|
|
1525
|
+
functionName: string;
|
|
1526
|
+
/** Stringified function source (present for arrow/closure forms). */
|
|
1527
|
+
body?: string;
|
|
1528
|
+
/** Captured call arguments (best effort; usually empty for detected utilities). */
|
|
1529
|
+
args?: unknown[];
|
|
1530
|
+
/**
|
|
1531
|
+
* Inferred output type, sampled at serialization time:
|
|
1532
|
+
* "number" | "string" | "boolean" | "date" | "object" | "<elementType>[]" | "array".
|
|
1533
|
+
* Omitted when sampling failed (e.g. the function threw).
|
|
1534
|
+
*/
|
|
1535
|
+
dataType?: string;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
/**
|
|
1539
|
+
* The JSON/UI wrapper representation of a dungeon, as produced by `dungeonToJSON` and
|
|
1540
|
+
* consumed by `parseJSONDungeon`. Functions in `schema` are serialized to
|
|
1541
|
+
* {@link SerializedFunction} objects; everything else is plain JSON.
|
|
1542
|
+
*/
|
|
1543
|
+
export interface DungeonJSON {
|
|
1544
|
+
/** The dungeon config with functions converted to {@link SerializedFunction} objects (hook excluded). */
|
|
1545
|
+
schema: Record<string, unknown>;
|
|
1546
|
+
/** The `hook` function stringified, or null if the dungeon has no hook. */
|
|
1547
|
+
hooks: string | null;
|
|
1548
|
+
/** ISO timestamp of when the JSON was produced. */
|
|
1549
|
+
timestamp: string;
|
|
1550
|
+
/** UI schema format version. */
|
|
1551
|
+
version: string;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
/** The doc blocks extracted from a dungeon's source by `extractComments`. */
|
|
1555
|
+
export interface DungeonComments {
|
|
1556
|
+
/** Cleaned text of the `// ── OVERVIEW ──` block, or null if absent. */
|
|
1557
|
+
overview: string | null;
|
|
1558
|
+
/** Cleaned text of the `// ── HOOK STORIES ──` block, or null if absent. */
|
|
1559
|
+
hookStories: string | null;
|
|
1560
|
+
/** Every `// ── LABEL ──` header followed by a block comment, keyed by exact label. */
|
|
1561
|
+
sections: Record<string, string>;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
/**
|
|
1565
|
+
* Convert a dungeon into its JSON representation (the inverse of `parseJSONDungeon`).
|
|
1566
|
+
* Accepts a config object, a file path, raw JS source, or an array of file paths
|
|
1567
|
+
* (returns an array). Credentials are stripped unless `includeCredentials` is set.
|
|
1568
|
+
* Best effort: arrow functions and `chance.*` methods round-trip; detected utility
|
|
1569
|
+
* calls lose their arguments. Always async.
|
|
1570
|
+
*/
|
|
1571
|
+
export declare function dungeonToJSON(input: Dungeon, options?: { includeCredentials?: boolean }): Promise<DungeonJSON>;
|
|
1572
|
+
export declare function dungeonToJSON(input: string, options?: { includeCredentials?: boolean }): Promise<DungeonJSON>;
|
|
1573
|
+
export declare function dungeonToJSON(input: string[], options?: { includeCredentials?: boolean }): Promise<DungeonJSON[]>;
|
|
1574
|
+
|
|
1575
|
+
/**
|
|
1576
|
+
* Extract the human-readable doc blocks (OVERVIEW, HOOK STORIES, …) from a dungeon's
|
|
1577
|
+
* SOURCE. Operates on a file path or raw source string — never imports the dungeon
|
|
1578
|
+
* (importing discards comments). Best effort: relies on the canonical `// ── LABEL ──`
|
|
1579
|
+
* header + block-comment convention.
|
|
1580
|
+
*/
|
|
1581
|
+
export declare function extractComments(input: string): DungeonComments;
|
|
1582
|
+
export declare function extractComments(input: string[]): DungeonComments[];
|
|
1583
|
+
|
|
1511
1584
|
// ============= Text Generator Types =============
|
|
1512
1585
|
|
|
1513
1586
|
/**
|