go-dev 0.6.0 → 0.7.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 +17 -2
- package/bin/go-dev +2 -16
- package/package.json +3 -2
- package/spike/README.md +77 -0
- package/spike/opentui/index.js +181 -0
- package/spike/opentui/package.json +14 -0
- package/spike/shared/fake-log-source.js +89 -0
- package/spike/terminal-kit/index.js +206 -0
- package/spike/terminal-kit/package.json +13 -0
- package/src/cli-args.js +9 -2
- package/src/config.js +1 -1
- package/src/dependency-resolver.js +72 -52
- package/src/index.js +3 -7
- package/src/interactive.js +318 -0
- package/src/orchestrator.js +21 -5
- package/src/run.js +57 -0
- package/src/save-preset.js +103 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const yaml = require('js-yaml');
|
|
3
|
+
const { loadConfig } = require('./config');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Renders a value as a YAML scalar, quoting it only when it isn't a plain,
|
|
7
|
+
* safe identifier. Service/mode names come from config keys (already safe);
|
|
8
|
+
* user-typed preset names may need quoting.
|
|
9
|
+
*/
|
|
10
|
+
function scalar(value) {
|
|
11
|
+
return /^[A-Za-z0-9_][A-Za-z0-9_.-]*$/.test(value) ? value : JSON.stringify(value);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Builds the YAML block for a single preset entry, indented under `presets:`.
|
|
16
|
+
*
|
|
17
|
+
* @param {string} name
|
|
18
|
+
* @param {{ services: string[], modes?: Record<string, string> }} selection
|
|
19
|
+
* @param {string} indent - leading indentation for the entry key (e.g. ' ').
|
|
20
|
+
*/
|
|
21
|
+
function buildPresetBlock(name, selection, indent) {
|
|
22
|
+
const step = ' ';
|
|
23
|
+
const lines = [
|
|
24
|
+
`${indent}${scalar(name)}:`,
|
|
25
|
+
`${indent}${step}services: [${selection.services.map(scalar).join(', ')}]`,
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const modes = selection.modes ?? {};
|
|
29
|
+
const modeEntries = Object.entries(modes).filter(([, mode]) => mode != null);
|
|
30
|
+
if (modeEntries.length > 0) {
|
|
31
|
+
lines.push(`${indent}${step}modes:`);
|
|
32
|
+
for (const [service, mode] of modeEntries) {
|
|
33
|
+
lines.push(`${indent}${step}${step}${scalar(service)}: ${scalar(mode)}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return lines.join('\n');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Inserts a preset into a raw YAML config, preserving the rest of the file
|
|
42
|
+
* (comments, key order, formatting). Falls back to a full re-dump only when the
|
|
43
|
+
* existing `presets:` key isn't a plain block we can append to.
|
|
44
|
+
*
|
|
45
|
+
* @param {string} raw
|
|
46
|
+
* @param {string} name
|
|
47
|
+
* @param {object} selection
|
|
48
|
+
* @returns {string} the new file content
|
|
49
|
+
*/
|
|
50
|
+
function insertPreset(raw, name, selection) {
|
|
51
|
+
const lines = raw.split('\n');
|
|
52
|
+
const presetsIndex = lines.findIndex((line) => /^(\s*)presets:\s*$/.test(line));
|
|
53
|
+
|
|
54
|
+
// Case A: a plain `presets:` block key exists — insert as its first entry.
|
|
55
|
+
if (presetsIndex >= 0) {
|
|
56
|
+
const baseIndent = lines[presetsIndex].match(/^(\s*)/)[1] + ' ';
|
|
57
|
+
const block = buildPresetBlock(name, selection, baseIndent);
|
|
58
|
+
lines.splice(presetsIndex + 1, 0, block);
|
|
59
|
+
return lines.join('\n');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Case B: no `presets:` key at all — append a fresh block at the end.
|
|
63
|
+
const hasPresetsKey = lines.some((line) => /^\s*presets:/.test(line));
|
|
64
|
+
if (!hasPresetsKey) {
|
|
65
|
+
const trimmed = raw.replace(/\s*$/, '');
|
|
66
|
+
const block = buildPresetBlock(name, selection, ' ');
|
|
67
|
+
return `${trimmed}\n\npresets:\n${block}\n`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Case C: `presets:` exists but inline (e.g. `presets: {}`) — re-dump to stay
|
|
71
|
+
// structurally correct. Comments are lost; acceptable for this edge case.
|
|
72
|
+
const parsed = yaml.load(raw) ?? {};
|
|
73
|
+
parsed.presets = parsed.presets ?? {};
|
|
74
|
+
parsed.presets[name] = {
|
|
75
|
+
services: selection.services,
|
|
76
|
+
...(Object.keys(selection.modes ?? {}).length > 0 ? { modes: selection.modes } : {}),
|
|
77
|
+
};
|
|
78
|
+
return yaml.dump(parsed, { lineWidth: 120 });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Persists a selection as a named preset in the given config file, then
|
|
83
|
+
* re-validates the file by loading it. On validation failure the original
|
|
84
|
+
* content is restored and the error is re-thrown.
|
|
85
|
+
*
|
|
86
|
+
* @param {string} configPath
|
|
87
|
+
* @param {string} name
|
|
88
|
+
* @param {{ services: string[], modes?: Record<string, string> }} selection
|
|
89
|
+
*/
|
|
90
|
+
function savePreset(configPath, name, selection) {
|
|
91
|
+
const original = fs.readFileSync(configPath, 'utf8');
|
|
92
|
+
const updated = insertPreset(original, name, selection);
|
|
93
|
+
|
|
94
|
+
fs.writeFileSync(configPath, updated, 'utf8');
|
|
95
|
+
try {
|
|
96
|
+
loadConfig(configPath);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
fs.writeFileSync(configPath, original, 'utf8');
|
|
99
|
+
throw new Error(`Failed to save preset '${name}': ${error.message}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = { savePreset, insertPreset };
|