@garyr/pt-cli 0.38.0 → 0.39.1

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.
@@ -2,24 +2,114 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import chalk from 'chalk';
4
4
  import { loadConfig, saveConfig } from '../config.js';
5
+ /**
6
+ * Validate JSON file exists and contains valid JSON
7
+ */
8
+ function validateJsonFile(filePath) {
9
+ try {
10
+ // Check if file exists
11
+ if (!fs.existsSync(filePath)) {
12
+ return {
13
+ valid: false,
14
+ error: `File not found: ${filePath}`
15
+ };
16
+ }
17
+ // Check file size (reasonable limit to prevent reading huge files)
18
+ const stats = fs.statSync(filePath);
19
+ if (stats.size > 10 * 1024 * 1024) { // 10MB limit
20
+ return {
21
+ valid: false,
22
+ error: `File too large (${stats.size} bytes). Maximum size is 10MB.`
23
+ };
24
+ }
25
+ // Read and parse JSON
26
+ const content = fs.readFileSync(filePath, 'utf8');
27
+ if (!content.trim()) {
28
+ return {
29
+ valid: false,
30
+ error: 'File is empty'
31
+ };
32
+ }
33
+ const data = JSON.parse(content);
34
+ return { valid: true, data };
35
+ }
36
+ catch (e) {
37
+ const error = e;
38
+ return {
39
+ valid: false,
40
+ error: `JSON parse error: ${error.message}`
41
+ };
42
+ }
43
+ }
44
+ /**
45
+ * Validate template structure
46
+ */
47
+ function validateTemplateStructure(data) {
48
+ if (!data || typeof data !== 'object') {
49
+ return {
50
+ valid: false,
51
+ error: 'Template data must be a JSON object'
52
+ };
53
+ }
54
+ // Basic structure validation
55
+ if (data.description && typeof data.description !== 'string') {
56
+ return {
57
+ valid: false,
58
+ error: 'Template description must be a string'
59
+ };
60
+ }
61
+ if (data.variables && Array.isArray(data.variables)) {
62
+ for (let i = 0; i < data.variables.length; i++) {
63
+ const v = data.variables[i];
64
+ if (!v.name || typeof v.name !== 'string') {
65
+ return {
66
+ valid: false,
67
+ error: `Variable at index ${i} must have a string 'name' field`
68
+ };
69
+ }
70
+ }
71
+ }
72
+ return { valid: true };
73
+ }
5
74
  export function addCommand(name, jsonStr, options = {}) {
6
75
  const config = loadConfig();
76
+ // Determine if we're reading from file or string
77
+ const isFile = !!options.file;
78
+ let data;
7
79
  try {
8
- let data;
9
- if (options.file) {
80
+ if (isFile) {
81
+ // Validate file first
10
82
  const filePath = path.resolve(options.file);
11
- data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
83
+ const validation = validateJsonFile(filePath);
84
+ if (!validation.valid) {
85
+ console.error(chalk.red(`Error: ${validation.error}`));
86
+ console.error(chalk.gray(`File: ${filePath}`));
87
+ process.exit(1);
88
+ }
89
+ data = validation.data;
12
90
  }
13
91
  else if (jsonStr) {
14
- data = JSON.parse(jsonStr);
92
+ // Parse JSON string directly
93
+ try {
94
+ data = JSON.parse(jsonStr);
95
+ }
96
+ catch (e) {
97
+ const error = e;
98
+ console.error(chalk.red(`Error: Invalid JSON string - ${error.message}`));
99
+ process.exit(1);
100
+ }
15
101
  }
16
102
  else {
17
103
  console.error('Error: Either a JSON string or --file <path> must be provided.');
18
104
  process.exit(1);
19
105
  }
20
- if (!config.templates)
21
- config.templates = {};
22
- // Basic validation: ensure we aren't accidentally adding a full config object
106
+ // Validate template structure
107
+ const structureValidation = validateTemplateStructure(data);
108
+ if (!structureValidation.valid) {
109
+ console.error(chalk.red(`Error: Invalid template structure - ${structureValidation.error}`));
110
+ process.exit(1);
111
+ }
112
+ // Check for full config object
23
113
  if (data && data.templates && typeof data.templates === 'object') {
24
114
  console.error(chalk.red('Error: The provided JSON appears to be a full configuration file, not a single template.'));
25
115
  console.error(chalk.gray('If you want to import a specific template from it, extract that template object first.'));
@@ -31,7 +121,7 @@ export function addCommand(name, jsonStr, options = {}) {
31
121
  }
32
122
  catch (e) {
33
123
  const error = e;
34
- console.error(chalk.red(`Failed to parse template JSON: ${error.message}`));
124
+ console.error(chalk.red(`Failed to process template: ${error.message}`));
35
125
  process.exit(1);
36
126
  }
37
127
  }
@@ -9,15 +9,22 @@ export function configCommand(templateName, options = {}) {
9
9
  name: templateName,
10
10
  ...config.templates[templateName]
11
11
  };
12
- console.log(JSON.stringify(output, null, 2));
12
+ // Safely drain stdout before allowing the process to close
13
+ process.stdout.write(JSON.stringify(output, null, 2) + '\n', () => {
14
+ process.exit(0);
15
+ });
13
16
  }
14
17
  else {
15
- console.error(chalk.red(`Error: Template "${templateName}" not found.`));
16
- process.exit(1);
18
+ process.stderr.write(chalk.red(`Error: Template "${templateName}" not found.\n`), () => {
19
+ process.exit(1);
20
+ });
17
21
  }
18
22
  }
19
23
  else {
20
- console.log(JSON.stringify(config, null, 2));
24
+ // Safely drain the entire global config payload
25
+ process.stdout.write(JSON.stringify(config, null, 2) + '\n', () => {
26
+ process.exit(0);
27
+ });
21
28
  }
22
29
  return;
23
30
  }
@@ -535,7 +535,11 @@ export async function learn(sourcePath, updateTemplate = null, options = {}) {
535
535
  name: targetName,
536
536
  ...templateConfig
537
537
  };
538
- console.log(JSON.stringify(output, null, 2));
538
+ // Force the application to wait until every single byte of this JSON string
539
+ // safely clears the operating system's pipe buffer before letting the process die.
540
+ process.stdout.write(JSON.stringify(output, null, 2) + '\n', () => {
541
+ process.exit(0);
542
+ });
539
543
  return;
540
544
  }
541
545
  config.templates[targetName] = templateConfig;
@@ -577,10 +581,10 @@ function extractStructure(dirPath, rootPath, ignorePatterns) {
577
581
  let info = "";
578
582
  const gitkeepPath = path.join(fullPath, '.gitkeep.md');
579
583
  const infoPath = path.join(fullPath, '.info.md');
580
- if (fs.existsSync(gitkeepPath))
581
- info = fs.readFileSync(gitkeepPath, 'utf-8').trim();
582
- else if (fs.existsSync(infoPath))
584
+ if (fs.existsSync(infoPath))
583
585
  info = fs.readFileSync(infoPath, 'utf-8').trim();
586
+ else if (fs.existsSync(gitkeepPath))
587
+ info = fs.readFileSync(gitkeepPath, 'utf-8').trim();
584
588
  nodes.push({ name: entry.name, info: info, children: children });
585
589
  }
586
590
  }
package/dist/config.js CHANGED
@@ -84,12 +84,22 @@ export function loadConfig() {
84
84
  catch (err) {
85
85
  const error = err;
86
86
  console.error(chalk.red(`\nError loading config: ${error.message}`));
87
- // If we have a backup, maybe suggest using it
88
87
  const backupPath = getConfigPath() + '.bak';
89
88
  if (fs.existsSync(backupPath)) {
90
89
  console.error(chalk.yellow(`A backup exists at ${backupPath}. You may want to restore it.`));
91
90
  }
92
- process.exit(1);
91
+ // Allow the event loop to flush console streams out to Godot before dying
92
+ setTimeout(() => {
93
+ process.exit(1);
94
+ }, 5);
95
+ // 👇 Add this return statement to satisfy the TypeScript compiler
96
+ // The application will terminate before this empty config can be used.
97
+ return {
98
+ version: '3.0',
99
+ templates: {},
100
+ default_post_config: [],
101
+ variables: []
102
+ };
93
103
  }
94
104
  }
95
105
  export function normalizeVariable(v) {
package/dist/index.js CHANGED
@@ -32,15 +32,18 @@ program
32
32
  }
33
33
  catch (err) {
34
34
  if (options.json) {
35
- console.log(JSON.stringify({
35
+ // Fix: Ensure the error JSON payload isn't truncated before exiting
36
+ process.stdout.write(JSON.stringify({
36
37
  type: 'error',
37
38
  message: err.message || String(err)
38
- }));
39
+ }) + '\n', () => {
40
+ process.exit(1);
41
+ });
39
42
  }
40
43
  else {
41
44
  console.error(chalk.red(`Error: ${err.message || err}`));
45
+ process.exit(1);
42
46
  }
43
- process.exit(1);
44
47
  }
45
48
  });
46
49
  program
@@ -39,17 +39,20 @@ project=MyProject
39
39
  ```
40
40
 
41
41
  During initialization, the system will:
42
+
42
43
  1. Load `prefix='rst_{{ project }}'` from `.env`
43
44
  2. Detect that `prefix` contains a `{{ project }}` placeholder
44
45
  3. Resolve `{{ project }}` to `MyProject`
45
46
  4. Set `prefix` to `rst_MyProject`
46
47
 
47
48
  This is particularly useful for:
49
+
48
50
  - **Project naming conventions**: `prefix='app_{{ env }}'` + `env=prod` → `app_prod`
49
51
  - **Path templates**: `template_path='docs/{{ project }}'` + `project=wiki` → `docs/wiki`
50
52
  - **Multi-level configurations**: Combine multiple `.env` files with nested references
51
53
 
52
54
  **How it works:**
55
+
53
56
  - Variables are expanded iteratively (up to 10 passes) to prevent infinite loops
54
57
  - Circular references are detected and stopped gracefully
55
58
  - Missing nested variables remain as `{{ variable }}` placeholders
@@ -64,6 +67,7 @@ This is particularly useful for:
64
67
  - **Team collaboration**: Share common variable values across team projects
65
68
 
66
69
  **Example:**
70
+
67
71
  ```bash
68
72
  # Project structure:
69
73
  my-project/
@@ -75,6 +79,7 @@ my-project/
75
79
  When you run `pt init my-template sub-project`, the `prefix` variable will be pre-filled with `rst_` from the parent `.env` file.
76
80
 
77
81
  **Behavior:**
82
+
78
83
  - Scans from the current directory up to 3 parent levels
79
84
  - Uses values from `.env` as defaults (still prompts if not in `.env`)
80
85
  - `--vars` CLI option overrides `.env` values
@@ -109,6 +114,7 @@ If the directory contains a `.pt-template.json` or `template.json` file with a `
109
114
  3. Alternatively, initialize a temporary project from your learned template (`pt init`), refine it manually, and then use `pt update` from that directory to "re-learn" the refined state.
110
115
 
111
116
  **Security Note:** All post-config commands are subject to security validation:
117
+
112
118
  - Dangerous commands (e.g., `curl`, `python`, `chmod`) trigger warnings with 5-second cancellation
113
119
  - Absolute blocks (e.g., `sudo`, `rm -rf`, `dd`) are never allowed
114
120
  - Rate limiting prevents runaway execution (50 commands per run)
@@ -177,7 +183,7 @@ Each task supports:
177
183
 
178
184
  ## Default Post-Config
179
185
 
180
- Default post-config tasks are defined at the top level of `~/.pt/config.yaml` under `default_post_config`. They serve as suggestions when creating or updating templates via `pt learn`.
186
+ Default post-config tasks are defined at the top level of `~/.pt/config.yaml` under `default_post_config`. They serve as suggestions when creating or updating templates via `pt learn`.
181
187
 
182
188
  Unlike previous versions, default tasks are **not** automatically applied during `pt init`. Instead, you select which ones to include when learning a template, and those selections are baked into the template's `post_config` list. This eliminates the need to repeat boilerplate setup (e.g. `git init`) across templates while keeping each template fully self-contained.
183
189
 
@@ -189,21 +195,21 @@ default_post_config:
189
195
  description: "Initialize git repository"
190
196
  - command: "git add -A && git commit -m 'Initial commit'"
191
197
  description: "Initial git commit"
192
- checked: false # default on, but user must manually check
198
+ checked: false # default on, but user must manually check
193
199
  - command: "git lfs install"
194
200
  description: "Install git-lfs hooks"
195
- type: "godot" # only applies to godot projects
201
+ type: "godot" # only applies to godot projects
196
202
  ```
197
203
 
198
204
  ### Fields
199
205
 
200
206
  Each default task supports the same fields as template post-config:
201
207
 
202
- | Field | Description |
203
- | ------------- | -------------------------------------------------------------------------------- |
204
- | `command` | Shell command to run |
205
- | `description` | Shown to user during interactive selection |
206
- | `checked` | Default checkbox state (`true` by default); set `false` to require manual opt-in |
208
+ | Field | Description |
209
+ | ------------- | -------------------------------------------------------------------------------------------------- |
210
+ | `command` | Shell command to run |
211
+ | `description` | Shown to user during interactive selection |
212
+ | `checked` | Default checkbox state (`true` by default); set `false` to require manual opt-in |
207
213
  | `type` | Filter by project type (e.g. `"javascript"`); if set, task only applies when template type matches |
208
214
 
209
215
  ### Behavior
@@ -215,6 +221,7 @@ Each default task supports the same fields as template post-config:
215
221
 
216
222
  You can view current default tasks using `pt config` or `pt default-post-config`.
217
223
  To update default tasks programmatically or via CLI, use the `pt default-post-config` command:
224
+
218
225
  - `pt default-post-config`: List current default post-config tasks.
219
226
  - `pt default-post-config --set --json '...'`: Replace the default post-config tasks list via a JSON string or file.
220
227
 
@@ -299,6 +306,7 @@ Each entry supports:
299
306
  A plausible scenario for customizing a new project's `package.json` and `README.md`:
300
307
 
301
308
  **1. Define in `config.yaml`**:
309
+
302
310
  ```yaml
303
311
  templates:
304
312
  node_web_app:
@@ -317,6 +325,7 @@ templates:
317
325
  ```
318
326
 
319
327
  **2. Template source (`templates/package.json.tmpl`)**:
328
+
320
329
  ```json
321
330
  {
322
331
  "name": "{{project_name}}",
@@ -327,6 +336,7 @@ templates:
327
336
 
328
337
  **3. Resulting project file**:
329
338
  If the user enters `my-service` and `Jane Doe`, the file `package.json` will be created with:
339
+
330
340
  ```json
331
341
  {
332
342
  "name": "my-service",
package/doc/usage.md CHANGED
@@ -60,12 +60,14 @@ pt init my-template my-new-project
60
60
  ```
61
61
 
62
62
  **Key Features:**
63
+
63
64
  - **Automatic Scanning:** Scans up to 3 parent directories for `.env` files
64
65
  - **Variable Pre-filling:** Values from `.env` are used as defaults in prompts
65
66
  - **Nested Variables:** Supports nested placeholders like `prefix='app_{{ env }}'`
66
67
  - **Override Support:** Use `--vars` to override `.env` values: `--vars project=OverriddenProject`
67
68
 
68
69
  **Example with Nested Variables:**
70
+
69
71
  ```bash
70
72
  # .env file
71
73
  prefix='app_{{ env }}'
@@ -79,6 +81,7 @@ project=MyApp
79
81
  ```
80
82
 
81
83
  **Security Note:** `.env` files are not committed to version control. Use `.gitignore` to exclude them:
84
+
82
85
  ```bash
83
86
  # .gitignore
84
87
  .env
@@ -102,11 +105,13 @@ project=MyProject
102
105
  ```
103
106
 
104
107
  **Use Cases:**
108
+
105
109
  - **Project naming conventions:** `prefix='app_{{ env }}'` + `env=prod` → `app_prod`
106
110
  - **Path templates:** `template_path='docs/{{ project }}'` + `project=wiki` → `docs/wiki`
107
111
  - **Multi-level configurations:** Combine multiple `.env` files with nested references
108
112
 
109
113
  **How it works:**
114
+
110
115
  - Variables are expanded iteratively (up to 10 passes) to prevent infinite loops
111
116
  - Circular references are detected and stopped gracefully
112
117
  - Missing nested variables remain as `{{ variable }}` placeholders
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@garyr/pt-cli",
3
- "version": "0.38.0",
3
+ "version": "0.39.1",
4
4
  "description": "Project Template CLI - Learn structures and initialize projects",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -7,23 +7,124 @@ export interface AddOptions {
7
7
  file?: string;
8
8
  }
9
9
 
10
+ /**
11
+ * Validate JSON file exists and contains valid JSON
12
+ */
13
+ function validateJsonFile(filePath: string): { valid: boolean; data?: any; error?: string } {
14
+ try {
15
+ // Check if file exists
16
+ if (!fs.existsSync(filePath)) {
17
+ return {
18
+ valid: false,
19
+ error: `File not found: ${filePath}`
20
+ };
21
+ }
22
+
23
+ // Check file size (reasonable limit to prevent reading huge files)
24
+ const stats = fs.statSync(filePath);
25
+ if (stats.size > 10 * 1024 * 1024) { // 10MB limit
26
+ return {
27
+ valid: false,
28
+ error: `File too large (${stats.size} bytes). Maximum size is 10MB.`
29
+ };
30
+ }
31
+
32
+ // Read and parse JSON
33
+ const content = fs.readFileSync(filePath, 'utf8');
34
+ if (!content.trim()) {
35
+ return {
36
+ valid: false,
37
+ error: 'File is empty'
38
+ };
39
+ }
40
+
41
+ const data = JSON.parse(content);
42
+ return { valid: true, data };
43
+ } catch (e) {
44
+ const error = e as Error;
45
+ return {
46
+ valid: false,
47
+ error: `JSON parse error: ${error.message}`
48
+ };
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Validate template structure
54
+ */
55
+ function validateTemplateStructure(data: any): { valid: boolean; error?: string } {
56
+ if (!data || typeof data !== 'object') {
57
+ return {
58
+ valid: false,
59
+ error: 'Template data must be a JSON object'
60
+ };
61
+ }
62
+
63
+ // Basic structure validation
64
+ if (data.description && typeof data.description !== 'string') {
65
+ return {
66
+ valid: false,
67
+ error: 'Template description must be a string'
68
+ };
69
+ }
70
+
71
+ if (data.variables && Array.isArray(data.variables)) {
72
+ for (let i = 0; i < data.variables.length; i++) {
73
+ const v = data.variables[i];
74
+ if (!v.name || typeof v.name !== 'string') {
75
+ return {
76
+ valid: false,
77
+ error: `Variable at index ${i} must have a string 'name' field`
78
+ };
79
+ }
80
+ }
81
+ }
82
+
83
+ return { valid: true };
84
+ }
85
+
10
86
  export function addCommand(name: string, jsonStr: string | undefined, options: AddOptions = {}) {
11
87
  const config = loadConfig();
88
+
89
+ // Determine if we're reading from file or string
90
+ const isFile = !!options.file;
91
+ let data: any;
92
+
12
93
  try {
13
- let data;
14
- if (options.file) {
15
- const filePath = path.resolve(options.file);
16
- data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
94
+ if (isFile) {
95
+ // Validate file first
96
+ const filePath = path.resolve(options.file!);
97
+ const validation = validateJsonFile(filePath);
98
+
99
+ if (!validation.valid) {
100
+ console.error(chalk.red(`Error: ${validation.error}`));
101
+ console.error(chalk.gray(`File: ${filePath}`));
102
+ process.exit(1);
103
+ }
104
+
105
+ data = validation.data;
17
106
  } else if (jsonStr) {
18
- data = JSON.parse(jsonStr);
107
+ // Parse JSON string directly
108
+ try {
109
+ data = JSON.parse(jsonStr);
110
+ } catch (e) {
111
+ const error = e as Error;
112
+ console.error(chalk.red(`Error: Invalid JSON string - ${error.message}`));
113
+ process.exit(1);
114
+ }
19
115
  } else {
20
116
  console.error('Error: Either a JSON string or --file <path> must be provided.');
21
117
  process.exit(1);
22
118
  }
23
119
 
24
- if (!config.templates) config.templates = {};
120
+ // Validate template structure
121
+ const structureValidation = validateTemplateStructure(data);
122
+ if (!structureValidation.valid) {
123
+ console.error(chalk.red(`Error: Invalid template structure - ${structureValidation.error}`));
124
+ process.exit(1);
125
+ }
25
126
 
26
- // Basic validation: ensure we aren't accidentally adding a full config object
127
+ // Check for full config object
27
128
  if (data && data.templates && typeof data.templates === 'object') {
28
129
  console.error(chalk.red('Error: The provided JSON appears to be a full configuration file, not a single template.'));
29
130
  console.error(chalk.gray('If you want to import a specific template from it, extract that template object first.'));
@@ -35,7 +136,7 @@ export function addCommand(name: string, jsonStr: string | undefined, options: A
35
136
  console.log(chalk.green(`✓ Template "${name}" saved successfully.`));
36
137
  } catch (e) {
37
138
  const error = e as Error;
38
- console.error(chalk.red(`Failed to parse template JSON: ${error.message}`));
139
+ console.error(chalk.red(`Failed to process template: ${error.message}`));
39
140
  process.exit(1);
40
141
  }
41
142
  }
@@ -15,13 +15,20 @@ export function configCommand(templateName: string | undefined, options: ConfigO
15
15
  name: templateName,
16
16
  ...config.templates[templateName]
17
17
  };
18
- console.log(JSON.stringify(output, null, 2));
18
+ // Safely drain stdout before allowing the process to close
19
+ process.stdout.write(JSON.stringify(output, null, 2) + '\n', () => {
20
+ process.exit(0);
21
+ });
19
22
  } else {
20
- console.error(chalk.red(`Error: Template "${templateName}" not found.`));
21
- process.exit(1);
23
+ process.stderr.write(chalk.red(`Error: Template "${templateName}" not found.\n`), () => {
24
+ process.exit(1);
25
+ });
22
26
  }
23
27
  } else {
24
- console.log(JSON.stringify(config, null, 2));
28
+ // Safely drain the entire global config payload
29
+ process.stdout.write(JSON.stringify(config, null, 2) + '\n', () => {
30
+ process.exit(0);
31
+ });
25
32
  }
26
33
  return;
27
34
  }
@@ -542,11 +542,16 @@ export async function learn(sourcePath: string, updateTemplate: string | null =
542
542
  }
543
543
 
544
544
  if (options.json) {
545
- const output = {
546
- name: targetName,
547
- ...templateConfig
548
- };
549
- console.log(JSON.stringify(output, null, 2));
545
+ const output = {
546
+ name: targetName,
547
+ ...templateConfig
548
+ };
549
+
550
+ // Force the application to wait until every single byte of this JSON string
551
+ // safely clears the operating system's pipe buffer before letting the process die.
552
+ process.stdout.write(JSON.stringify(output, null, 2) + '\n', () => {
553
+ process.exit(0);
554
+ });
550
555
  return;
551
556
  }
552
557
 
@@ -588,8 +593,8 @@ function extractStructure(dirPath: string, rootPath: string, ignorePatterns?: st
588
593
  let info = "";
589
594
  const gitkeepPath = path.join(fullPath, '.gitkeep.md');
590
595
  const infoPath = path.join(fullPath, '.info.md');
591
- if (fs.existsSync(gitkeepPath)) info = fs.readFileSync(gitkeepPath, 'utf-8').trim();
592
- else if (fs.existsSync(infoPath)) info = fs.readFileSync(infoPath, 'utf-8').trim();
596
+ if (fs.existsSync(infoPath)) info = fs.readFileSync(infoPath, 'utf-8').trim();
597
+ else if (fs.existsSync(gitkeepPath)) info = fs.readFileSync(gitkeepPath, 'utf-8').trim();
593
598
  nodes.push({ name: entry.name, info: info, children: children });
594
599
  }
595
600
  } catch (e) {}
package/src/config.ts CHANGED
@@ -148,12 +148,24 @@ export function loadConfig(): PtConfig {
148
148
  } catch (err) {
149
149
  const error = err as Error;
150
150
  console.error(chalk.red(`\nError loading config: ${error.message}`));
151
- // If we have a backup, maybe suggest using it
152
151
  const backupPath = getConfigPath() + '.bak';
153
152
  if (fs.existsSync(backupPath)) {
154
153
  console.error(chalk.yellow(`A backup exists at ${backupPath}. You may want to restore it.`));
155
154
  }
156
- process.exit(1);
155
+
156
+ // Allow the event loop to flush console streams out to Godot before dying
157
+ setTimeout(() => {
158
+ process.exit(1);
159
+ }, 5);
160
+
161
+ // 👇 Add this return statement to satisfy the TypeScript compiler
162
+ // The application will terminate before this empty config can be used.
163
+ return {
164
+ version: '3.0',
165
+ templates: {},
166
+ default_post_config: [],
167
+ variables: []
168
+ };
157
169
  }
158
170
  }
159
171
 
package/src/index.ts CHANGED
@@ -39,14 +39,17 @@ program
39
39
  await learn(pathArg || '.', null, options);
40
40
  } catch (err: any) {
41
41
  if (options.json) {
42
- console.log(JSON.stringify({
42
+ // Fix: Ensure the error JSON payload isn't truncated before exiting
43
+ process.stdout.write(JSON.stringify({
43
44
  type: 'error',
44
45
  message: err.message || String(err)
45
- }));
46
+ }) + '\n', () => {
47
+ process.exit(1);
48
+ });
46
49
  } else {
47
50
  console.error(chalk.red(`Error: ${err.message || err}`));
51
+ process.exit(1);
48
52
  }
49
- process.exit(1);
50
53
  }
51
54
  });
52
55