@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.
@@ -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 { fileURLToPath } from 'url';
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
- const schema = convertToJSON(cleanConfig);
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
  /**