retold 4.0.3 → 4.0.4
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/settings.local.json +26 -1
- package/README.md +20 -0
- package/docs/_sidebar.md +2 -1
- package/docs/architecture/architecture.md +2 -2
- package/docs/architecture/modules.md +3 -4
- package/docs/contributing.md +50 -0
- package/docs/modules/orator.md +0 -7
- package/docs/retold-catalog.json +110 -26
- package/docs/retold-keyword-index.json +15118 -15139
- package/docs/testing.md +122 -0
- package/modules/Include-Retold-Module-List.sh +1 -1
- package/package.json +7 -4
- package/source/retold-manager/package.json +23 -0
- package/source/retold-manager/retold-manager.js +65 -0
- package/source/retold-manager/source/Retold-Manager-App.js +1532 -0
- package/source/retold-manager/source/Retold-Manager-ModuleCatalog.js +75 -0
- package/source/retold-manager/source/Retold-Manager-ProcessRunner.js +706 -0
- package/source/retold-manager/source/views/PictView-TUI-Checkout.js +45 -0
- package/source/retold-manager/source/views/PictView-TUI-Header.js +41 -0
- package/source/retold-manager/source/views/PictView-TUI-Layout.js +53 -0
- package/source/retold-manager/source/views/PictView-TUI-Status.js +45 -0
- package/source/retold-manager/source/views/PictView-TUI-StatusBar.js +41 -0
- package/source/retold-manager/source/views/PictView-TUI-Update.js +45 -0
- package/examples/quickstart/layer1/package-lock.json +0 -344
- package/examples/quickstart/layer2/package-lock.json +0 -4468
- package/examples/quickstart/layer3/package-lock.json +0 -1936
- package/examples/quickstart/layer4/package-lock.json +0 -13206
- package/examples/quickstart/layer5/package-lock.json +0 -345
- package/examples/todo-list/cli-client/package-lock.json +0 -418
- package/examples/todo-list/console-client/package-lock.json +0 -426
- package/examples/todo-list/server/package-lock.json +0 -6113
- package/examples/todo-list/web-client/package-lock.json +0 -12030
|
@@ -0,0 +1,1532 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retold Manager -- Main Application
|
|
3
|
+
*
|
|
4
|
+
* A pict-terminalui application for managing the retold module suite.
|
|
5
|
+
* Provides a file browser for navigating module groups and a terminal
|
|
6
|
+
* output area for running npm/git operations.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const blessed = require('blessed');
|
|
10
|
+
const libChildProcess = require('child_process');
|
|
11
|
+
const libFs = require('fs');
|
|
12
|
+
const libPath = require('path');
|
|
13
|
+
|
|
14
|
+
const libPictApplication = require('pict-application');
|
|
15
|
+
const libPictTerminalUI = require('pict-terminalui');
|
|
16
|
+
|
|
17
|
+
const libModuleCatalog = require('./Retold-Manager-ModuleCatalog.js');
|
|
18
|
+
const libProcessRunner = require('./Retold-Manager-ProcessRunner.js');
|
|
19
|
+
|
|
20
|
+
// Views
|
|
21
|
+
const libViewLayout = require('./views/PictView-TUI-Layout.js');
|
|
22
|
+
const libViewHeader = require('./views/PictView-TUI-Header.js');
|
|
23
|
+
const libViewStatusBar = require('./views/PictView-TUI-StatusBar.js');
|
|
24
|
+
const libViewStatus = require('./views/PictView-TUI-Status.js');
|
|
25
|
+
const libViewUpdate = require('./views/PictView-TUI-Update.js');
|
|
26
|
+
const libViewCheckout = require('./views/PictView-TUI-Checkout.js');
|
|
27
|
+
|
|
28
|
+
// Maximum lines to display when viewing a file
|
|
29
|
+
const FILE_VIEW_LINE_LIMIT = 500;
|
|
30
|
+
|
|
31
|
+
class RetoldManagerApp extends libPictApplication
|
|
32
|
+
{
|
|
33
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
34
|
+
{
|
|
35
|
+
super(pFable, pOptions, pServiceHash);
|
|
36
|
+
|
|
37
|
+
this.terminalUI = null;
|
|
38
|
+
this.processRunner = null;
|
|
39
|
+
|
|
40
|
+
// Blessed widget references for direct manipulation
|
|
41
|
+
this._fileBrowser = null;
|
|
42
|
+
this._terminalOutput = null;
|
|
43
|
+
this._screen = null;
|
|
44
|
+
|
|
45
|
+
// Register views
|
|
46
|
+
this.pict.addView('TUI-Layout', libViewLayout.default_configuration, libViewLayout);
|
|
47
|
+
this.pict.addView('TUI-Header', libViewHeader.default_configuration, libViewHeader);
|
|
48
|
+
this.pict.addView('TUI-StatusBar', libViewStatusBar.default_configuration, libViewStatusBar);
|
|
49
|
+
this.pict.addView('TUI-Status', libViewStatus.default_configuration, libViewStatus);
|
|
50
|
+
this.pict.addView('TUI-Update', libViewUpdate.default_configuration, libViewUpdate);
|
|
51
|
+
this.pict.addView('TUI-Checkout', libViewCheckout.default_configuration, libViewCheckout);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
onAfterInitializeAsync(fCallback)
|
|
55
|
+
{
|
|
56
|
+
// Initialize shared application state
|
|
57
|
+
this.pict.AppData.Manager =
|
|
58
|
+
{
|
|
59
|
+
AppName: 'Retold Manager',
|
|
60
|
+
AppVersion: '0.0.1',
|
|
61
|
+
StatusMessage: 'Ready',
|
|
62
|
+
|
|
63
|
+
Browser:
|
|
64
|
+
{
|
|
65
|
+
Level: 'groups',
|
|
66
|
+
GroupIndex: -1,
|
|
67
|
+
GroupName: '',
|
|
68
|
+
GroupLabel: '',
|
|
69
|
+
ModuleName: '',
|
|
70
|
+
ModulePath: '',
|
|
71
|
+
SubPath: '',
|
|
72
|
+
CurrentPath: 'retold/modules/',
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Create the terminal UI environment
|
|
77
|
+
this.terminalUI = new libPictTerminalUI(this.pict,
|
|
78
|
+
{
|
|
79
|
+
Title: 'Retold Manager'
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Create the blessed screen
|
|
83
|
+
let tmpScreen = this.terminalUI.createScreen();
|
|
84
|
+
|
|
85
|
+
// Build the blessed widget layout
|
|
86
|
+
this._createBlessedLayout(tmpScreen);
|
|
87
|
+
|
|
88
|
+
// Create the process runner (pass pict.log for activity logging)
|
|
89
|
+
this.processRunner = new libProcessRunner(
|
|
90
|
+
this._terminalOutput,
|
|
91
|
+
this._screen,
|
|
92
|
+
(pState, pMessage) =>
|
|
93
|
+
{
|
|
94
|
+
this._updateStatus(pMessage);
|
|
95
|
+
},
|
|
96
|
+
this.pict.log);
|
|
97
|
+
|
|
98
|
+
// File logging state -- the stream reference when active, null when off
|
|
99
|
+
this._fileLogStream = null;
|
|
100
|
+
|
|
101
|
+
// When true, a Y/N confirmation prompt is active -- suppress other key handlers
|
|
102
|
+
this._awaitingConfirmation = false;
|
|
103
|
+
|
|
104
|
+
// Bind navigation keys
|
|
105
|
+
this._bindNavigation(tmpScreen);
|
|
106
|
+
|
|
107
|
+
// Enable file logging by default
|
|
108
|
+
this._toggleFileLogging();
|
|
109
|
+
|
|
110
|
+
// Populate the initial file list (groups)
|
|
111
|
+
this._populateFileList();
|
|
112
|
+
|
|
113
|
+
// Render the layout view (triggers Header + StatusBar)
|
|
114
|
+
this.pict.views['TUI-Layout'].render();
|
|
115
|
+
|
|
116
|
+
// Do the initial blessed screen render
|
|
117
|
+
tmpScreen.render();
|
|
118
|
+
|
|
119
|
+
// Focus the file browser
|
|
120
|
+
this._fileBrowser.focus();
|
|
121
|
+
|
|
122
|
+
return super.onAfterInitializeAsync(fCallback);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ─────────────────────────────────────────────
|
|
126
|
+
// Widget Layout
|
|
127
|
+
// ─────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
_createBlessedLayout(pScreen)
|
|
130
|
+
{
|
|
131
|
+
this._screen = pScreen;
|
|
132
|
+
|
|
133
|
+
// Application container
|
|
134
|
+
let tmpAppContainer = blessed.box(
|
|
135
|
+
{
|
|
136
|
+
parent: pScreen,
|
|
137
|
+
top: 0,
|
|
138
|
+
left: 0,
|
|
139
|
+
width: '100%',
|
|
140
|
+
height: '100%',
|
|
141
|
+
});
|
|
142
|
+
this.terminalUI.registerWidget('#TUI-Application-Container', tmpAppContainer);
|
|
143
|
+
|
|
144
|
+
// Header bar -- 2 lines high
|
|
145
|
+
let tmpHeader = blessed.box(
|
|
146
|
+
{
|
|
147
|
+
parent: pScreen,
|
|
148
|
+
top: 0,
|
|
149
|
+
left: 0,
|
|
150
|
+
width: '100%',
|
|
151
|
+
height: 2,
|
|
152
|
+
tags: true,
|
|
153
|
+
style:
|
|
154
|
+
{
|
|
155
|
+
fg: 'white',
|
|
156
|
+
bg: 'blue',
|
|
157
|
+
bold: true,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
this.terminalUI.registerWidget('#TUI-Header', tmpHeader);
|
|
161
|
+
|
|
162
|
+
// File browser list -- left side, max 40 chars wide
|
|
163
|
+
this._fileBrowser = blessed.list(
|
|
164
|
+
{
|
|
165
|
+
parent: pScreen,
|
|
166
|
+
top: 2,
|
|
167
|
+
left: 0,
|
|
168
|
+
width: 40,
|
|
169
|
+
bottom: 1,
|
|
170
|
+
items: [],
|
|
171
|
+
keys: true,
|
|
172
|
+
vi: true,
|
|
173
|
+
mouse: true,
|
|
174
|
+
border: { type: 'line' },
|
|
175
|
+
label: ' Module Groups ',
|
|
176
|
+
scrollbar:
|
|
177
|
+
{
|
|
178
|
+
style: { bg: 'blue' },
|
|
179
|
+
},
|
|
180
|
+
style:
|
|
181
|
+
{
|
|
182
|
+
fg: 'white',
|
|
183
|
+
bg: 'black',
|
|
184
|
+
selected: { fg: 'black', bg: 'cyan', bold: true },
|
|
185
|
+
item: { fg: 'white' },
|
|
186
|
+
border: { fg: 'cyan' },
|
|
187
|
+
label: { fg: 'cyan', bold: true },
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
this.terminalUI.registerWidget('#TUI-FileBrowser', this._fileBrowser);
|
|
191
|
+
|
|
192
|
+
// Terminal output log -- right side
|
|
193
|
+
this._terminalOutput = blessed.log(
|
|
194
|
+
{
|
|
195
|
+
parent: pScreen,
|
|
196
|
+
top: 2,
|
|
197
|
+
left: 40,
|
|
198
|
+
right: 0,
|
|
199
|
+
bottom: 1,
|
|
200
|
+
label: ' Terminal Output ',
|
|
201
|
+
border: { type: 'line' },
|
|
202
|
+
tags: true,
|
|
203
|
+
scrollable: true,
|
|
204
|
+
scrollOnInput: true,
|
|
205
|
+
mouse: true,
|
|
206
|
+
scrollback: 2000,
|
|
207
|
+
scrollbar:
|
|
208
|
+
{
|
|
209
|
+
style: { bg: 'green' },
|
|
210
|
+
},
|
|
211
|
+
style:
|
|
212
|
+
{
|
|
213
|
+
fg: 'white',
|
|
214
|
+
bg: 'black',
|
|
215
|
+
border: { fg: 'green' },
|
|
216
|
+
label: { fg: 'green', bold: true },
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
this.terminalUI.registerWidget('#TUI-TerminalOutput', this._terminalOutput);
|
|
220
|
+
|
|
221
|
+
// Status bar -- bottom 1 line
|
|
222
|
+
let tmpStatusBar = blessed.box(
|
|
223
|
+
{
|
|
224
|
+
parent: pScreen,
|
|
225
|
+
bottom: 0,
|
|
226
|
+
left: 0,
|
|
227
|
+
width: '100%',
|
|
228
|
+
height: 1,
|
|
229
|
+
tags: true,
|
|
230
|
+
style:
|
|
231
|
+
{
|
|
232
|
+
fg: 'white',
|
|
233
|
+
bg: 'gray',
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
this.terminalUI.registerWidget('#TUI-StatusBar', tmpStatusBar);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ─────────────────────────────────────────────
|
|
240
|
+
// File Browser Navigation
|
|
241
|
+
// ─────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
_populateFileList()
|
|
244
|
+
{
|
|
245
|
+
let tmpBrowser = this.pict.AppData.Manager.Browser;
|
|
246
|
+
let tmpItems = [];
|
|
247
|
+
|
|
248
|
+
switch (tmpBrowser.Level)
|
|
249
|
+
{
|
|
250
|
+
case 'groups':
|
|
251
|
+
{
|
|
252
|
+
for (let i = 0; i < libModuleCatalog.Groups.length; i++)
|
|
253
|
+
{
|
|
254
|
+
let tmpGroup = libModuleCatalog.Groups[i];
|
|
255
|
+
tmpItems.push(` ${tmpGroup.Label}/ (${tmpGroup.Modules.length} modules) -- ${tmpGroup.Description}`);
|
|
256
|
+
}
|
|
257
|
+
this._fileBrowser.setLabel(' Module Groups ');
|
|
258
|
+
tmpBrowser.CurrentPath = 'retold/modules/';
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
case 'modules':
|
|
263
|
+
{
|
|
264
|
+
let tmpGroupPath = libPath.join(libModuleCatalog.BasePath, tmpBrowser.GroupName);
|
|
265
|
+
let tmpEntries = [];
|
|
266
|
+
|
|
267
|
+
try
|
|
268
|
+
{
|
|
269
|
+
let tmpRawEntries = libFs.readdirSync(tmpGroupPath);
|
|
270
|
+
for (let i = 0; i < tmpRawEntries.length; i++)
|
|
271
|
+
{
|
|
272
|
+
let tmpEntryPath = libPath.join(tmpGroupPath, tmpRawEntries[i]);
|
|
273
|
+
try
|
|
274
|
+
{
|
|
275
|
+
let tmpStat = libFs.statSync(tmpEntryPath);
|
|
276
|
+
if (tmpStat.isDirectory() && !tmpRawEntries[i].startsWith('.'))
|
|
277
|
+
{
|
|
278
|
+
tmpEntries.push(tmpRawEntries[i]);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
catch (pError)
|
|
282
|
+
{
|
|
283
|
+
// Skip entries we can't stat
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
catch (pError)
|
|
288
|
+
{
|
|
289
|
+
this._terminalOutput.log(`{red-fg}Error reading directory: ${pError.message}{/red-fg}`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
tmpEntries.sort();
|
|
293
|
+
|
|
294
|
+
tmpItems.push(' ../');
|
|
295
|
+
for (let i = 0; i < tmpEntries.length; i++)
|
|
296
|
+
{
|
|
297
|
+
tmpItems.push(` ${tmpEntries[i]}/`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
this._fileBrowser.setLabel(` ${tmpBrowser.GroupName}/ `);
|
|
301
|
+
tmpBrowser.CurrentPath = `retold/modules/${tmpBrowser.GroupName}/`;
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
case 'files':
|
|
306
|
+
{
|
|
307
|
+
let tmpBrowsePath = tmpBrowser.SubPath
|
|
308
|
+
? libPath.join(tmpBrowser.ModulePath, tmpBrowser.SubPath)
|
|
309
|
+
: tmpBrowser.ModulePath;
|
|
310
|
+
let tmpEntries = [];
|
|
311
|
+
|
|
312
|
+
try
|
|
313
|
+
{
|
|
314
|
+
let tmpRawEntries = libFs.readdirSync(tmpBrowsePath);
|
|
315
|
+
for (let i = 0; i < tmpRawEntries.length; i++)
|
|
316
|
+
{
|
|
317
|
+
if (tmpRawEntries[i].startsWith('.') && tmpRawEntries[i] !== '.gitignore')
|
|
318
|
+
{
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
if (tmpRawEntries[i] === 'node_modules')
|
|
322
|
+
{
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
let tmpEntryPath = libPath.join(tmpBrowsePath, tmpRawEntries[i]);
|
|
327
|
+
try
|
|
328
|
+
{
|
|
329
|
+
let tmpStat = libFs.statSync(tmpEntryPath);
|
|
330
|
+
if (tmpStat.isDirectory())
|
|
331
|
+
{
|
|
332
|
+
tmpEntries.push(tmpRawEntries[i] + '/');
|
|
333
|
+
}
|
|
334
|
+
else
|
|
335
|
+
{
|
|
336
|
+
tmpEntries.push(tmpRawEntries[i]);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
catch (pError)
|
|
340
|
+
{
|
|
341
|
+
tmpEntries.push(tmpRawEntries[i]);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
catch (pError)
|
|
346
|
+
{
|
|
347
|
+
this._terminalOutput.log(`{red-fg}Error reading directory: ${pError.message}{/red-fg}`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
tmpEntries.sort((a, b) =>
|
|
351
|
+
{
|
|
352
|
+
// Directories first, then files
|
|
353
|
+
let tmpAIsDir = a.endsWith('/');
|
|
354
|
+
let tmpBIsDir = b.endsWith('/');
|
|
355
|
+
if (tmpAIsDir && !tmpBIsDir) return -1;
|
|
356
|
+
if (!tmpAIsDir && tmpBIsDir) return 1;
|
|
357
|
+
return a.localeCompare(b);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
tmpItems.push(' ../');
|
|
361
|
+
for (let i = 0; i < tmpEntries.length; i++)
|
|
362
|
+
{
|
|
363
|
+
tmpItems.push(` ${tmpEntries[i]}`);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
let tmpLabelSuffix = tmpBrowser.SubPath ? `${tmpBrowser.SubPath}/` : '';
|
|
367
|
+
this._fileBrowser.setLabel(` ${tmpBrowser.ModuleName}/${tmpLabelSuffix} `);
|
|
368
|
+
tmpBrowser.CurrentPath = tmpBrowser.SubPath
|
|
369
|
+
? `retold/modules/${tmpBrowser.GroupName}/${tmpBrowser.ModuleName}/${tmpBrowser.SubPath}/`
|
|
370
|
+
: `retold/modules/${tmpBrowser.GroupName}/${tmpBrowser.ModuleName}/`;
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
this._fileBrowser.setItems(tmpItems);
|
|
376
|
+
this._fileBrowser.select(0);
|
|
377
|
+
|
|
378
|
+
this._updateHeader();
|
|
379
|
+
this._updateStatus(this.pict.AppData.Manager.StatusMessage);
|
|
380
|
+
this._screen.render();
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
_drillIn(pIndex)
|
|
384
|
+
{
|
|
385
|
+
let tmpBrowser = this.pict.AppData.Manager.Browser;
|
|
386
|
+
|
|
387
|
+
switch (tmpBrowser.Level)
|
|
388
|
+
{
|
|
389
|
+
case 'groups':
|
|
390
|
+
{
|
|
391
|
+
if (pIndex < 0 || pIndex >= libModuleCatalog.Groups.length)
|
|
392
|
+
{
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
let tmpGroup = libModuleCatalog.Groups[pIndex];
|
|
396
|
+
tmpBrowser.GroupIndex = pIndex;
|
|
397
|
+
tmpBrowser.GroupName = tmpGroup.Name;
|
|
398
|
+
tmpBrowser.GroupLabel = tmpGroup.Label;
|
|
399
|
+
tmpBrowser.Level = 'modules';
|
|
400
|
+
this._populateFileList();
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
case 'modules':
|
|
405
|
+
{
|
|
406
|
+
// Index 0 is '../'
|
|
407
|
+
if (pIndex === 0)
|
|
408
|
+
{
|
|
409
|
+
this._drillOut();
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Get the module name from the list item text
|
|
414
|
+
let tmpItemText = this._fileBrowser.getItem(pIndex).getText().trim();
|
|
415
|
+
let tmpModuleName = tmpItemText.replace(/\/$/, '');
|
|
416
|
+
|
|
417
|
+
tmpBrowser.ModuleName = tmpModuleName;
|
|
418
|
+
tmpBrowser.ModulePath = libPath.join(libModuleCatalog.BasePath, tmpBrowser.GroupName, tmpModuleName);
|
|
419
|
+
tmpBrowser.SubPath = '';
|
|
420
|
+
tmpBrowser.Level = 'files';
|
|
421
|
+
|
|
422
|
+
this._populateFileList();
|
|
423
|
+
this._showModuleWelcome();
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
case 'files':
|
|
428
|
+
{
|
|
429
|
+
// Index 0 is '../'
|
|
430
|
+
if (pIndex === 0)
|
|
431
|
+
{
|
|
432
|
+
this._drillOut();
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
let tmpItemText = this._fileBrowser.getItem(pIndex).getText().trim();
|
|
437
|
+
let tmpBrowsePath = tmpBrowser.SubPath
|
|
438
|
+
? libPath.join(tmpBrowser.ModulePath, tmpBrowser.SubPath)
|
|
439
|
+
: tmpBrowser.ModulePath;
|
|
440
|
+
|
|
441
|
+
if (tmpItemText.endsWith('/'))
|
|
442
|
+
{
|
|
443
|
+
// It's a subdirectory -- navigate into it
|
|
444
|
+
let tmpSubdir = tmpItemText.replace(/\/$/, '');
|
|
445
|
+
tmpBrowser.SubPath = tmpBrowser.SubPath
|
|
446
|
+
? libPath.join(tmpBrowser.SubPath, tmpSubdir)
|
|
447
|
+
: tmpSubdir;
|
|
448
|
+
this._populateFileList();
|
|
449
|
+
}
|
|
450
|
+
else
|
|
451
|
+
{
|
|
452
|
+
// It's a file -- show its contents
|
|
453
|
+
let tmpFilePath = libPath.join(tmpBrowsePath, tmpItemText);
|
|
454
|
+
this._showFileContents(tmpFilePath, tmpItemText);
|
|
455
|
+
}
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
_drillOut()
|
|
462
|
+
{
|
|
463
|
+
let tmpBrowser = this.pict.AppData.Manager.Browser;
|
|
464
|
+
|
|
465
|
+
switch (tmpBrowser.Level)
|
|
466
|
+
{
|
|
467
|
+
case 'files':
|
|
468
|
+
{
|
|
469
|
+
if (tmpBrowser.SubPath)
|
|
470
|
+
{
|
|
471
|
+
// We're in a subfolder -- go up one level within the module
|
|
472
|
+
let tmpParent = libPath.dirname(tmpBrowser.SubPath);
|
|
473
|
+
tmpBrowser.SubPath = (tmpParent === '.') ? '' : tmpParent;
|
|
474
|
+
this._populateFileList();
|
|
475
|
+
}
|
|
476
|
+
else
|
|
477
|
+
{
|
|
478
|
+
// We're at the module root -- go back to module list
|
|
479
|
+
tmpBrowser.ModuleName = '';
|
|
480
|
+
tmpBrowser.ModulePath = '';
|
|
481
|
+
tmpBrowser.SubPath = '';
|
|
482
|
+
tmpBrowser.Level = 'modules';
|
|
483
|
+
this._populateFileList();
|
|
484
|
+
}
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
case 'modules':
|
|
489
|
+
{
|
|
490
|
+
tmpBrowser.GroupIndex = -1;
|
|
491
|
+
tmpBrowser.GroupName = '';
|
|
492
|
+
tmpBrowser.GroupLabel = '';
|
|
493
|
+
tmpBrowser.Level = 'groups';
|
|
494
|
+
this._populateFileList();
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
case 'groups':
|
|
499
|
+
{
|
|
500
|
+
// Already at top -- no-op
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// ─────────────────────────────────────────────
|
|
507
|
+
// Module Operations
|
|
508
|
+
// ─────────────────────────────────────────────
|
|
509
|
+
|
|
510
|
+
_getModulePath()
|
|
511
|
+
{
|
|
512
|
+
let tmpBrowser = this.pict.AppData.Manager.Browser;
|
|
513
|
+
|
|
514
|
+
// If we're inside a module's files, use its path
|
|
515
|
+
if (tmpBrowser.Level === 'files' && tmpBrowser.ModulePath)
|
|
516
|
+
{
|
|
517
|
+
return tmpBrowser.ModulePath;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// If we're at the module list level, use the highlighted module
|
|
521
|
+
if (tmpBrowser.Level === 'modules')
|
|
522
|
+
{
|
|
523
|
+
let tmpSelected = this._fileBrowser.selected;
|
|
524
|
+
if (tmpSelected > 0) // Skip '../' at index 0
|
|
525
|
+
{
|
|
526
|
+
let tmpItemText = this._fileBrowser.getItem(tmpSelected).getText().trim();
|
|
527
|
+
let tmpModuleName = tmpItemText.replace(/\/$/, '');
|
|
528
|
+
return libPath.join(libModuleCatalog.BasePath, tmpBrowser.GroupName, tmpModuleName);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
_runModuleOperation(pCommand, pArgs, pLineLimit)
|
|
536
|
+
{
|
|
537
|
+
if (this._awaitingConfirmation) { return; }
|
|
538
|
+
|
|
539
|
+
let tmpModulePath = this._getModulePath();
|
|
540
|
+
|
|
541
|
+
if (!tmpModulePath)
|
|
542
|
+
{
|
|
543
|
+
this._terminalOutput.setContent('');
|
|
544
|
+
this._terminalOutput.log('{yellow-fg}{bold}Select a module first.{/bold}{/yellow-fg}');
|
|
545
|
+
this._terminalOutput.log('');
|
|
546
|
+
this._terminalOutput.log('Navigate into a module group, then select or enter a module');
|
|
547
|
+
this._terminalOutput.log('before running operations.');
|
|
548
|
+
this._screen.render();
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
this.processRunner.run(pCommand, pArgs, tmpModulePath, pLineLimit);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
_runDiff()
|
|
556
|
+
{
|
|
557
|
+
if (this._awaitingConfirmation) { return; }
|
|
558
|
+
|
|
559
|
+
let tmpModulePath = this._getModulePath();
|
|
560
|
+
|
|
561
|
+
if (!tmpModulePath)
|
|
562
|
+
{
|
|
563
|
+
this._terminalOutput.setContent('');
|
|
564
|
+
this._terminalOutput.log('{yellow-fg}{bold}Select a module first.{/bold}{/yellow-fg}');
|
|
565
|
+
this._terminalOutput.log('');
|
|
566
|
+
this._terminalOutput.log('Navigate into a module group, then select or enter a module');
|
|
567
|
+
this._terminalOutput.log('before running operations.');
|
|
568
|
+
this._screen.render();
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
this.processRunner.runSequence(
|
|
573
|
+
[
|
|
574
|
+
{
|
|
575
|
+
command: 'git',
|
|
576
|
+
args: ['diff', '--stat'],
|
|
577
|
+
label: 'Changed files overview (including dist/):'
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
command: 'git',
|
|
581
|
+
args: ['diff', '--', '.', ':!dist'],
|
|
582
|
+
label: 'Full diff (excluding dist/):'
|
|
583
|
+
}
|
|
584
|
+
],
|
|
585
|
+
tmpModulePath);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
_runPublish()
|
|
589
|
+
{
|
|
590
|
+
if (this._awaitingConfirmation) { return; }
|
|
591
|
+
|
|
592
|
+
let tmpModulePath = this._getModulePath();
|
|
593
|
+
|
|
594
|
+
if (!tmpModulePath)
|
|
595
|
+
{
|
|
596
|
+
this._terminalOutput.setContent('');
|
|
597
|
+
this._terminalOutput.log('{yellow-fg}{bold}Select a module first.{/bold}{/yellow-fg}');
|
|
598
|
+
this._terminalOutput.log('');
|
|
599
|
+
this._terminalOutput.log('Navigate into a module group, then select or enter a module');
|
|
600
|
+
this._terminalOutput.log('before running operations.');
|
|
601
|
+
this._screen.render();
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Clear and start the pre-publish validation report
|
|
606
|
+
this._terminalOutput.setContent('');
|
|
607
|
+
this._terminalOutput.log('{bold}{yellow-fg}Pre-publish validation{/yellow-fg}{/bold}');
|
|
608
|
+
this._terminalOutput.log('');
|
|
609
|
+
this._screen.render();
|
|
610
|
+
|
|
611
|
+
if (this.pict.log)
|
|
612
|
+
{
|
|
613
|
+
this.pict.log.info(`PUBLISH Pre-publish validation for ${tmpModulePath}`);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// ── Step 1: Read local package.json ──
|
|
617
|
+
let tmpPkgPath = libPath.join(tmpModulePath, 'package.json');
|
|
618
|
+
let tmpPkg;
|
|
619
|
+
try
|
|
620
|
+
{
|
|
621
|
+
tmpPkg = JSON.parse(libFs.readFileSync(tmpPkgPath, 'utf8'));
|
|
622
|
+
}
|
|
623
|
+
catch (pError)
|
|
624
|
+
{
|
|
625
|
+
this._terminalOutput.log(`{red-fg}{bold}Cannot read package.json:{/bold} ${pError.message}{/red-fg}`);
|
|
626
|
+
this._screen.render();
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
let tmpPackageName = tmpPkg.name || libPath.basename(tmpModulePath);
|
|
631
|
+
let tmpLocalVersion = tmpPkg.version || '0.0.0';
|
|
632
|
+
|
|
633
|
+
this._terminalOutput.log(`{bold}Package:{/bold} ${tmpPackageName}`);
|
|
634
|
+
this._terminalOutput.log(`{bold}Local:{/bold} v${tmpLocalVersion}`);
|
|
635
|
+
this._screen.render();
|
|
636
|
+
|
|
637
|
+
// ── Step 2: Fetch the currently published version from npm ──
|
|
638
|
+
let tmpPublishedVersion = null;
|
|
639
|
+
try
|
|
640
|
+
{
|
|
641
|
+
tmpPublishedVersion = libChildProcess.execSync(
|
|
642
|
+
`npm view ${tmpPackageName} version`,
|
|
643
|
+
{ cwd: tmpModulePath, encoding: 'utf8', timeout: 15000 }
|
|
644
|
+
).trim();
|
|
645
|
+
}
|
|
646
|
+
catch (pError)
|
|
647
|
+
{
|
|
648
|
+
// Package may not be published yet (404)
|
|
649
|
+
tmpPublishedVersion = null;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (tmpPublishedVersion)
|
|
653
|
+
{
|
|
654
|
+
this._terminalOutput.log(`{bold}npm:{/bold} v${tmpPublishedVersion}`);
|
|
655
|
+
|
|
656
|
+
if (tmpPublishedVersion === tmpLocalVersion)
|
|
657
|
+
{
|
|
658
|
+
this._terminalOutput.log('');
|
|
659
|
+
this._terminalOutput.log('{red-fg}{bold}✗ Version mismatch:{/bold} local version matches what is already published on npm.{/red-fg}');
|
|
660
|
+
this._terminalOutput.log('{red-fg} Bump the version with [v] before publishing.{/red-fg}');
|
|
661
|
+
this._terminalOutput.log('');
|
|
662
|
+
this._terminalOutput.log('{bold}────────────────────────────────────────{/bold}');
|
|
663
|
+
this._terminalOutput.log('{red-fg}{bold}✗ Publish aborted{/bold}{/red-fg}');
|
|
664
|
+
if (this.pict.log)
|
|
665
|
+
{
|
|
666
|
+
this.pict.log.info(`PUBLISH Aborted ${tmpPackageName} -- v${tmpLocalVersion} already on npm`);
|
|
667
|
+
}
|
|
668
|
+
this._updateStatus(`Publish aborted -- version ${tmpLocalVersion} already on npm`);
|
|
669
|
+
this._screen.render();
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
else
|
|
673
|
+
{
|
|
674
|
+
this._terminalOutput.log(`{green-fg} ✓ Local version differs from published{/green-fg}`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
else
|
|
678
|
+
{
|
|
679
|
+
this._terminalOutput.log('{bold}npm:{/bold} {gray-fg}(not yet published){/gray-fg}');
|
|
680
|
+
this._terminalOutput.log(`{green-fg} ✓ First publish{/green-fg}`);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
this._terminalOutput.log('');
|
|
684
|
+
this._screen.render();
|
|
685
|
+
|
|
686
|
+
// ── Step 3: Build the set of all retold ecosystem package names ──
|
|
687
|
+
let tmpEcosystemNames = {};
|
|
688
|
+
for (let i = 0; i < libModuleCatalog.Groups.length; i++)
|
|
689
|
+
{
|
|
690
|
+
let tmpGroup = libModuleCatalog.Groups[i];
|
|
691
|
+
for (let j = 0; j < tmpGroup.Modules.length; j++)
|
|
692
|
+
{
|
|
693
|
+
tmpEcosystemNames[tmpGroup.Modules[j]] = true;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// ── Step 4: Find ecosystem deps and check their versions against npm ──
|
|
698
|
+
let tmpAllDeps = {};
|
|
699
|
+
if (tmpPkg.dependencies)
|
|
700
|
+
{
|
|
701
|
+
let tmpKeys = Object.keys(tmpPkg.dependencies);
|
|
702
|
+
for (let i = 0; i < tmpKeys.length; i++)
|
|
703
|
+
{
|
|
704
|
+
tmpAllDeps[tmpKeys[i]] = { range: tmpPkg.dependencies[tmpKeys[i]], section: 'dependencies' };
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (tmpPkg.devDependencies)
|
|
708
|
+
{
|
|
709
|
+
let tmpKeys = Object.keys(tmpPkg.devDependencies);
|
|
710
|
+
for (let i = 0; i < tmpKeys.length; i++)
|
|
711
|
+
{
|
|
712
|
+
tmpAllDeps[tmpKeys[i]] = { range: tmpPkg.devDependencies[tmpKeys[i]], section: 'devDependencies' };
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
let tmpEcosystemDeps = [];
|
|
717
|
+
let tmpDepNames = Object.keys(tmpAllDeps);
|
|
718
|
+
for (let i = 0; i < tmpDepNames.length; i++)
|
|
719
|
+
{
|
|
720
|
+
if (tmpEcosystemNames[tmpDepNames[i]])
|
|
721
|
+
{
|
|
722
|
+
tmpEcosystemDeps.push(tmpDepNames[i]);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
let tmpHasProblems = false;
|
|
727
|
+
|
|
728
|
+
if (tmpEcosystemDeps.length === 0)
|
|
729
|
+
{
|
|
730
|
+
this._terminalOutput.log('{gray-fg}No retold ecosystem dependencies found.{/gray-fg}');
|
|
731
|
+
}
|
|
732
|
+
else
|
|
733
|
+
{
|
|
734
|
+
this._terminalOutput.log(`{bold}Ecosystem dependency check{/bold} (${tmpEcosystemDeps.length} packages)`);
|
|
735
|
+
this._terminalOutput.log('');
|
|
736
|
+
|
|
737
|
+
for (let i = 0; i < tmpEcosystemDeps.length; i++)
|
|
738
|
+
{
|
|
739
|
+
let tmpDepName = tmpEcosystemDeps[i];
|
|
740
|
+
let tmpDepInfo = tmpAllDeps[tmpDepName];
|
|
741
|
+
let tmpLocalRange = tmpDepInfo.range;
|
|
742
|
+
|
|
743
|
+
// Skip file: references (local dev links)
|
|
744
|
+
if (tmpLocalRange.startsWith('file:'))
|
|
745
|
+
{
|
|
746
|
+
this._terminalOutput.log(` {gray-fg}${tmpDepName} ${tmpLocalRange} (local link -- skipped){/gray-fg}`);
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
let tmpLatestVersion = null;
|
|
751
|
+
try
|
|
752
|
+
{
|
|
753
|
+
tmpLatestVersion = libChildProcess.execSync(
|
|
754
|
+
`npm view ${tmpDepName} version`,
|
|
755
|
+
{ cwd: tmpModulePath, encoding: 'utf8', timeout: 15000 }
|
|
756
|
+
).trim();
|
|
757
|
+
}
|
|
758
|
+
catch (pError)
|
|
759
|
+
{
|
|
760
|
+
this._terminalOutput.log(` {yellow-fg}${tmpDepName} ${tmpLocalRange} (could not fetch from npm){/yellow-fg}`);
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Check if the local range covers the latest version
|
|
765
|
+
// Simple check: extract the version digits from the range (strip ^, ~, >= etc.)
|
|
766
|
+
let tmpRangeVersion = tmpLocalRange.replace(/^[\^~>=<]*/, '');
|
|
767
|
+
if (tmpRangeVersion === tmpLatestVersion)
|
|
768
|
+
{
|
|
769
|
+
this._terminalOutput.log(` {green-fg}✓ ${tmpDepName} ${tmpLocalRange} (latest: ${tmpLatestVersion}){/green-fg}`);
|
|
770
|
+
}
|
|
771
|
+
else
|
|
772
|
+
{
|
|
773
|
+
// Check if the range prefix (^ or ~) might cover the latest
|
|
774
|
+
let tmpRangePrefix = tmpLocalRange.match(/^[\^~]/);
|
|
775
|
+
let tmpCoversLatest = false;
|
|
776
|
+
|
|
777
|
+
if (tmpRangePrefix)
|
|
778
|
+
{
|
|
779
|
+
// Parse major.minor.patch for both
|
|
780
|
+
let tmpRangeParts = tmpRangeVersion.split('.').map(Number);
|
|
781
|
+
let tmpLatestParts = tmpLatestVersion.split('.').map(Number);
|
|
782
|
+
|
|
783
|
+
if (tmpRangePrefix[0] === '^')
|
|
784
|
+
{
|
|
785
|
+
// ^ allows changes that don't modify the left-most non-zero digit
|
|
786
|
+
if (tmpRangeParts[0] > 0)
|
|
787
|
+
{
|
|
788
|
+
tmpCoversLatest = (tmpLatestParts[0] === tmpRangeParts[0])
|
|
789
|
+
&& (tmpLatestParts[1] > tmpRangeParts[1]
|
|
790
|
+
|| (tmpLatestParts[1] === tmpRangeParts[1] && tmpLatestParts[2] >= tmpRangeParts[2]));
|
|
791
|
+
}
|
|
792
|
+
else if (tmpRangeParts[1] > 0)
|
|
793
|
+
{
|
|
794
|
+
tmpCoversLatest = (tmpLatestParts[0] === 0 && tmpLatestParts[1] === tmpRangeParts[1])
|
|
795
|
+
&& (tmpLatestParts[2] >= tmpRangeParts[2]);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
else if (tmpRangePrefix[0] === '~')
|
|
799
|
+
{
|
|
800
|
+
// ~ allows patch-level changes
|
|
801
|
+
tmpCoversLatest = (tmpLatestParts[0] === tmpRangeParts[0] && tmpLatestParts[1] === tmpRangeParts[1])
|
|
802
|
+
&& (tmpLatestParts[2] >= tmpRangeParts[2]);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
if (tmpCoversLatest)
|
|
807
|
+
{
|
|
808
|
+
this._terminalOutput.log(` {green-fg}✓ ${tmpDepName} ${tmpLocalRange} (latest: ${tmpLatestVersion} -- covered by range){/green-fg}`);
|
|
809
|
+
}
|
|
810
|
+
else
|
|
811
|
+
{
|
|
812
|
+
tmpHasProblems = true;
|
|
813
|
+
this._terminalOutput.log(` {red-fg}{bold}✗ ${tmpDepName}{/bold} ${tmpLocalRange} → latest: ${tmpLatestVersion}{/red-fg}`);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
this._screen.render();
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
this._terminalOutput.log('');
|
|
822
|
+
this._screen.render();
|
|
823
|
+
|
|
824
|
+
if (tmpHasProblems)
|
|
825
|
+
{
|
|
826
|
+
this._terminalOutput.log('{red-fg}{bold}✗ Ecosystem dependencies are out of date.{/bold}{/red-fg}');
|
|
827
|
+
this._terminalOutput.log('{red-fg} Update package.json and run [i]nstall before publishing.{/red-fg}');
|
|
828
|
+
this._terminalOutput.log('');
|
|
829
|
+
this._terminalOutput.log('{bold}────────────────────────────────────────{/bold}');
|
|
830
|
+
this._terminalOutput.log('{red-fg}{bold}✗ Publish aborted{/bold}{/red-fg}');
|
|
831
|
+
if (this.pict.log)
|
|
832
|
+
{
|
|
833
|
+
this.pict.log.info(`PUBLISH Aborted ${tmpPackageName} -- ecosystem deps out of date`);
|
|
834
|
+
}
|
|
835
|
+
this._updateStatus('Publish aborted -- ecosystem deps out of date');
|
|
836
|
+
this._screen.render();
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// ── Step 5: All checks passed -- show summary and ask for confirmation ──
|
|
841
|
+
this._terminalOutput.log('{green-fg}{bold}✓ All pre-publish checks passed{/bold}{/green-fg}');
|
|
842
|
+
this._terminalOutput.log('');
|
|
843
|
+
|
|
844
|
+
// ── Step 6: Fetch recent commit log ──
|
|
845
|
+
this._terminalOutput.log('{bold}Recent commits:{/bold}');
|
|
846
|
+
this._terminalOutput.log('');
|
|
847
|
+
|
|
848
|
+
let tmpCommitLog = '';
|
|
849
|
+
try
|
|
850
|
+
{
|
|
851
|
+
// Try to get commits since the published version tag first
|
|
852
|
+
let tmpTagPatterns = [`v${tmpPublishedVersion}`, tmpPublishedVersion];
|
|
853
|
+
let tmpFoundTag = false;
|
|
854
|
+
|
|
855
|
+
if (tmpPublishedVersion)
|
|
856
|
+
{
|
|
857
|
+
for (let i = 0; i < tmpTagPatterns.length; i++)
|
|
858
|
+
{
|
|
859
|
+
try
|
|
860
|
+
{
|
|
861
|
+
tmpCommitLog = libChildProcess.execSync(
|
|
862
|
+
`git log ${tmpTagPatterns[i]}..HEAD --oneline`,
|
|
863
|
+
{ cwd: tmpModulePath, encoding: 'utf8', timeout: 10000 }
|
|
864
|
+
).trim();
|
|
865
|
+
if (tmpCommitLog)
|
|
866
|
+
{
|
|
867
|
+
tmpFoundTag = true;
|
|
868
|
+
break;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
catch (pError)
|
|
872
|
+
{
|
|
873
|
+
// Tag doesn't exist, try the next pattern
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// Fall back to recent commits if no tag was found
|
|
879
|
+
if (!tmpFoundTag || !tmpCommitLog)
|
|
880
|
+
{
|
|
881
|
+
tmpCommitLog = libChildProcess.execSync(
|
|
882
|
+
'git log --oneline -20',
|
|
883
|
+
{ cwd: tmpModulePath, encoding: 'utf8', timeout: 10000 }
|
|
884
|
+
).trim();
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
catch (pError)
|
|
888
|
+
{
|
|
889
|
+
tmpCommitLog = '';
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
if (tmpCommitLog)
|
|
893
|
+
{
|
|
894
|
+
let tmpCommitLines = tmpCommitLog.split('\n');
|
|
895
|
+
for (let i = 0; i < tmpCommitLines.length; i++)
|
|
896
|
+
{
|
|
897
|
+
let tmpLine = tmpCommitLines[i].replace(/\{/g, '\\{').replace(/\}/g, '\\}');
|
|
898
|
+
this._terminalOutput.log(` {gray-fg}${tmpLine}{/gray-fg}`);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
else
|
|
902
|
+
{
|
|
903
|
+
this._terminalOutput.log(' {gray-fg}(no commits found){/gray-fg}');
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
this._terminalOutput.log('');
|
|
907
|
+
this._terminalOutput.log('{bold}{blue-fg}────────────────────────────────────────{/blue-fg}{/bold}');
|
|
908
|
+
this._terminalOutput.log('');
|
|
909
|
+
|
|
910
|
+
// Summary block
|
|
911
|
+
if (tmpPublishedVersion)
|
|
912
|
+
{
|
|
913
|
+
this._terminalOutput.log(` {bold}${tmpPackageName}{/bold} v${tmpPublishedVersion} → v${tmpLocalVersion}`);
|
|
914
|
+
}
|
|
915
|
+
else
|
|
916
|
+
{
|
|
917
|
+
this._terminalOutput.log(` {bold}${tmpPackageName}{/bold} v${tmpLocalVersion} {gray-fg}(first publish){/gray-fg}`);
|
|
918
|
+
}
|
|
919
|
+
this._terminalOutput.log('');
|
|
920
|
+
this._terminalOutput.log('{bold}{yellow-fg}Publish to npm? [Y] yes [N] no{/yellow-fg}{/bold}');
|
|
921
|
+
this._updateStatus('Publish? Y/N');
|
|
922
|
+
this._awaitingConfirmation = true;
|
|
923
|
+
this._screen.render();
|
|
924
|
+
|
|
925
|
+
// ── Step 7: Wait for Y/N confirmation ──
|
|
926
|
+
let tmpSelf = this;
|
|
927
|
+
let tmpConfirmHandler = function (pCh, pKey)
|
|
928
|
+
{
|
|
929
|
+
// Remove this one-shot listener immediately
|
|
930
|
+
tmpSelf._screen.removeListener('keypress', tmpConfirmHandler);
|
|
931
|
+
tmpSelf._awaitingConfirmation = false;
|
|
932
|
+
|
|
933
|
+
if (pCh === 'y' || pCh === 'Y')
|
|
934
|
+
{
|
|
935
|
+
tmpSelf._terminalOutput.log('');
|
|
936
|
+
tmpSelf._terminalOutput.log('{green-fg}{bold}Publishing...{/bold}{/green-fg}');
|
|
937
|
+
tmpSelf._terminalOutput.log('');
|
|
938
|
+
tmpSelf._screen.render();
|
|
939
|
+
|
|
940
|
+
if (tmpSelf.pict.log)
|
|
941
|
+
{
|
|
942
|
+
tmpSelf.pict.log.info(`PUBLISH Confirmed -- publishing ${tmpPackageName} v${tmpLocalVersion}`);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
tmpSelf.processRunner.run('npm', ['publish'], tmpModulePath, null, { append: true });
|
|
946
|
+
}
|
|
947
|
+
else
|
|
948
|
+
{
|
|
949
|
+
tmpSelf._terminalOutput.log('');
|
|
950
|
+
tmpSelf._terminalOutput.log('{bold}────────────────────────────────────────{/bold}');
|
|
951
|
+
tmpSelf._terminalOutput.log('{yellow-fg}{bold}Publish cancelled by user{/bold}{/yellow-fg}');
|
|
952
|
+
if (tmpSelf.pict.log)
|
|
953
|
+
{
|
|
954
|
+
tmpSelf.pict.log.info(`PUBLISH Cancelled by user -- ${tmpPackageName} v${tmpLocalVersion}`);
|
|
955
|
+
}
|
|
956
|
+
tmpSelf._updateStatus('Publish cancelled');
|
|
957
|
+
tmpSelf._screen.render();
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
|
|
961
|
+
this._screen.on('keypress', tmpConfirmHandler);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// ─────────────────────────────────────────────
|
|
965
|
+
// Content Display
|
|
966
|
+
// ─────────────────────────────────────────────
|
|
967
|
+
|
|
968
|
+
_showModuleWelcome()
|
|
969
|
+
{
|
|
970
|
+
let tmpBrowser = this.pict.AppData.Manager.Browser;
|
|
971
|
+
|
|
972
|
+
this._terminalOutput.setContent('');
|
|
973
|
+
this._terminalOutput.log(`{bold}Module: ${tmpBrowser.ModuleName}{/bold}`);
|
|
974
|
+
this._terminalOutput.log(`{gray-fg}Path: ${tmpBrowser.ModulePath}{/gray-fg}`);
|
|
975
|
+
this._terminalOutput.log('');
|
|
976
|
+
this._terminalOutput.log('{bold}Operations:{/bold}');
|
|
977
|
+
this._terminalOutput.log(' {cyan-fg}[i]{/cyan-fg} npm install {cyan-fg}[t]{/cyan-fg} npm test {cyan-fg}[y]{/cyan-fg} npm run types');
|
|
978
|
+
this._terminalOutput.log(' {cyan-fg}[b]{/cyan-fg} npm run build {cyan-fg}[v]{/cyan-fg} Bump version {cyan-fg}[d]{/cyan-fg} git diff');
|
|
979
|
+
this._terminalOutput.log(' {cyan-fg}[o]{/cyan-fg} git commit {cyan-fg}[p]{/cyan-fg} git pull {cyan-fg}[u]{/cyan-fg} git push');
|
|
980
|
+
this._terminalOutput.log(' {cyan-fg}[!]{/cyan-fg} npm publish {cyan-fg}[x]{/cyan-fg} Kill process');
|
|
981
|
+
this._terminalOutput.log('');
|
|
982
|
+
this._terminalOutput.log('{bold}All Modules:{/bold}');
|
|
983
|
+
this._terminalOutput.log(' {cyan-fg}[s]{/cyan-fg} Status.sh {cyan-fg}[r]{/cyan-fg} Update.sh {cyan-fg}[c]{/cyan-fg} Checkout.sh');
|
|
984
|
+
this._terminalOutput.log('');
|
|
985
|
+
this._terminalOutput.log('{bold}Output:{/bold}');
|
|
986
|
+
this._terminalOutput.log(' {cyan-fg}[/]{/cyan-fg} Search output {cyan-fg}]{/cyan-fg} Next match {cyan-fg}[{/cyan-fg} Prev match {cyan-fg}[Esc]{/cyan-fg} Clear search');
|
|
987
|
+
this._terminalOutput.log('');
|
|
988
|
+
this._terminalOutput.log(' {cyan-fg}[g]{/cyan-fg} Go to groups');
|
|
989
|
+
this._terminalOutput.log('');
|
|
990
|
+
this._terminalOutput.log('Select a file to view its contents, or press a shortcut key.');
|
|
991
|
+
|
|
992
|
+
// Try to show package.json version info
|
|
993
|
+
let tmpPkgPath = libPath.join(tmpBrowser.ModulePath, 'package.json');
|
|
994
|
+
try
|
|
995
|
+
{
|
|
996
|
+
let tmpPkg = JSON.parse(libFs.readFileSync(tmpPkgPath, 'utf8'));
|
|
997
|
+
this._terminalOutput.log('');
|
|
998
|
+
this._terminalOutput.log(`{bold}${tmpPkg.name || tmpBrowser.ModuleName}{/bold} v${tmpPkg.version || '?'}`);
|
|
999
|
+
if (tmpPkg.description)
|
|
1000
|
+
{
|
|
1001
|
+
this._terminalOutput.log(`{gray-fg}${tmpPkg.description}{/gray-fg}`);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
catch (pError)
|
|
1005
|
+
{
|
|
1006
|
+
// No package.json or not parseable -- skip
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
this._screen.render();
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
_showFileContents(pFilePath, pFileName)
|
|
1013
|
+
{
|
|
1014
|
+
this._terminalOutput.setContent('');
|
|
1015
|
+
this._terminalOutput.log(`{bold}${pFileName}{/bold}`);
|
|
1016
|
+
this._terminalOutput.log(`{gray-fg}${pFilePath}{/gray-fg}`);
|
|
1017
|
+
this._terminalOutput.log('');
|
|
1018
|
+
|
|
1019
|
+
try
|
|
1020
|
+
{
|
|
1021
|
+
let tmpStat = libFs.statSync(pFilePath);
|
|
1022
|
+
|
|
1023
|
+
// Skip very large files
|
|
1024
|
+
if (tmpStat.size > 1024 * 512)
|
|
1025
|
+
{
|
|
1026
|
+
this._terminalOutput.log(`{yellow-fg}File is too large to display (${(tmpStat.size / 1024).toFixed(0)} KB){/yellow-fg}`);
|
|
1027
|
+
this._screen.render();
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
let tmpContent = libFs.readFileSync(pFilePath, 'utf8');
|
|
1032
|
+
let tmpLines = tmpContent.split('\n');
|
|
1033
|
+
|
|
1034
|
+
let tmpDisplayLimit = Math.min(tmpLines.length, FILE_VIEW_LINE_LIMIT);
|
|
1035
|
+
for (let i = 0; i < tmpDisplayLimit; i++)
|
|
1036
|
+
{
|
|
1037
|
+
// Escape curly braces so blessed doesn't parse them as markup tags
|
|
1038
|
+
let tmpLine = tmpLines[i].replace(/\{/g, '\\{').replace(/\}/g, '\\}');
|
|
1039
|
+
this._terminalOutput.log(tmpLine);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
if (tmpLines.length > FILE_VIEW_LINE_LIMIT)
|
|
1043
|
+
{
|
|
1044
|
+
this._terminalOutput.log('');
|
|
1045
|
+
this._terminalOutput.log(`{yellow-fg}... truncated (showing ${FILE_VIEW_LINE_LIMIT} of ${tmpLines.length} lines){/yellow-fg}`);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
this._updateStatus(`Viewing: ${pFileName} (${tmpLines.length} lines)`);
|
|
1049
|
+
}
|
|
1050
|
+
catch (pError)
|
|
1051
|
+
{
|
|
1052
|
+
this._terminalOutput.log(`{red-fg}Error reading file: ${pError.message}{/red-fg}`);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
this._screen.render();
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
_showDirectoryContents(pDirPath, pDirName)
|
|
1059
|
+
{
|
|
1060
|
+
this._terminalOutput.setContent('');
|
|
1061
|
+
this._terminalOutput.log(`{bold}Directory: ${pDirName}/{/bold}`);
|
|
1062
|
+
this._terminalOutput.log(`{gray-fg}${pDirPath}{/gray-fg}`);
|
|
1063
|
+
this._terminalOutput.log('');
|
|
1064
|
+
|
|
1065
|
+
try
|
|
1066
|
+
{
|
|
1067
|
+
let tmpEntries = libFs.readdirSync(pDirPath);
|
|
1068
|
+
tmpEntries.sort();
|
|
1069
|
+
|
|
1070
|
+
for (let i = 0; i < tmpEntries.length; i++)
|
|
1071
|
+
{
|
|
1072
|
+
let tmpEntryPath = libPath.join(pDirPath, tmpEntries[i]);
|
|
1073
|
+
try
|
|
1074
|
+
{
|
|
1075
|
+
let tmpStat = libFs.statSync(tmpEntryPath);
|
|
1076
|
+
if (tmpStat.isDirectory())
|
|
1077
|
+
{
|
|
1078
|
+
this._terminalOutput.log(` {cyan-fg}${tmpEntries[i]}/{/cyan-fg}`);
|
|
1079
|
+
}
|
|
1080
|
+
else
|
|
1081
|
+
{
|
|
1082
|
+
let tmpSize = tmpStat.size;
|
|
1083
|
+
let tmpSizeStr = tmpSize < 1024 ? `${tmpSize} B` : `${(tmpSize / 1024).toFixed(1)} KB`;
|
|
1084
|
+
this._terminalOutput.log(` ${tmpEntries[i]} {gray-fg}(${tmpSizeStr}){/gray-fg}`);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
catch (pError)
|
|
1088
|
+
{
|
|
1089
|
+
this._terminalOutput.log(` ${tmpEntries[i]}`);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
this._updateStatus(`Directory: ${pDirName}/ (${tmpEntries.length} entries)`);
|
|
1094
|
+
}
|
|
1095
|
+
catch (pError)
|
|
1096
|
+
{
|
|
1097
|
+
this._terminalOutput.log(`{red-fg}Error reading directory: ${pError.message}{/red-fg}`);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
this._screen.render();
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// ─────────────────────────────────────────────
|
|
1104
|
+
// File Logging
|
|
1105
|
+
// ─────────────────────────────────────────────
|
|
1106
|
+
|
|
1107
|
+
_getLogFilePath()
|
|
1108
|
+
{
|
|
1109
|
+
let tmpNow = new Date();
|
|
1110
|
+
let tmpYear = tmpNow.getFullYear();
|
|
1111
|
+
let tmpMonth = String(tmpNow.getMonth() + 1).padStart(2, '0');
|
|
1112
|
+
let tmpDay = String(tmpNow.getDate()).padStart(2, '0');
|
|
1113
|
+
let tmpRepoRoot = libPath.resolve(libModuleCatalog.BasePath, '..');
|
|
1114
|
+
|
|
1115
|
+
return libPath.join(tmpRepoRoot, `Retold-Manager-Log-${tmpYear}-${tmpMonth}-${tmpDay}.log`);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
_toggleFileLogging()
|
|
1119
|
+
{
|
|
1120
|
+
let tmpLog = this.pict.log;
|
|
1121
|
+
|
|
1122
|
+
if (this._fileLogStream)
|
|
1123
|
+
{
|
|
1124
|
+
// Turn off: close the writer and remove from all stream arrays
|
|
1125
|
+
tmpLog.info('--- File logging disabled ---');
|
|
1126
|
+
this._fileLogStream.closeWriter();
|
|
1127
|
+
|
|
1128
|
+
let tmpUUID = this._fileLogStream.loggerUUID;
|
|
1129
|
+
delete tmpLog.activeLogStreams[tmpUUID];
|
|
1130
|
+
|
|
1131
|
+
let tmpFilterOut = (pStream) => pStream.loggerUUID !== tmpUUID;
|
|
1132
|
+
tmpLog.logStreams = tmpLog.logStreams.filter(tmpFilterOut);
|
|
1133
|
+
tmpLog.logStreamsTrace = tmpLog.logStreamsTrace.filter(tmpFilterOut);
|
|
1134
|
+
tmpLog.logStreamsDebug = tmpLog.logStreamsDebug.filter(tmpFilterOut);
|
|
1135
|
+
tmpLog.logStreamsInfo = tmpLog.logStreamsInfo.filter(tmpFilterOut);
|
|
1136
|
+
tmpLog.logStreamsWarn = tmpLog.logStreamsWarn.filter(tmpFilterOut);
|
|
1137
|
+
tmpLog.logStreamsError = tmpLog.logStreamsError.filter(tmpFilterOut);
|
|
1138
|
+
tmpLog.logStreamsFatal = tmpLog.logStreamsFatal.filter(tmpFilterOut);
|
|
1139
|
+
|
|
1140
|
+
this._fileLogStream = null;
|
|
1141
|
+
|
|
1142
|
+
this._terminalOutput.log('{yellow-fg}File logging OFF{/yellow-fg}');
|
|
1143
|
+
this._updateStatus('Logging disabled');
|
|
1144
|
+
}
|
|
1145
|
+
else
|
|
1146
|
+
{
|
|
1147
|
+
// Turn on: create a simpleflatfile logger and add it to fable-log
|
|
1148
|
+
let tmpLogPath = this._getLogFilePath();
|
|
1149
|
+
let tmpProviders = tmpLog._Providers;
|
|
1150
|
+
|
|
1151
|
+
if (!tmpProviders.simpleflatfile)
|
|
1152
|
+
{
|
|
1153
|
+
this._terminalOutput.log('{red-fg}simpleflatfile log provider not available{/red-fg}');
|
|
1154
|
+
this._screen.render();
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
let tmpStreamDef =
|
|
1159
|
+
{
|
|
1160
|
+
loggertype: 'simpleflatfile',
|
|
1161
|
+
level: 'info',
|
|
1162
|
+
path: tmpLogPath,
|
|
1163
|
+
outputloglinestoconsole: false,
|
|
1164
|
+
outputobjectstoconsole: false,
|
|
1165
|
+
Context: 'RetoldManager',
|
|
1166
|
+
};
|
|
1167
|
+
|
|
1168
|
+
let tmpLogger = new tmpProviders.simpleflatfile(tmpStreamDef, tmpLog);
|
|
1169
|
+
tmpLogger.initialize();
|
|
1170
|
+
tmpLog.addLogger(tmpLogger, 'info');
|
|
1171
|
+
|
|
1172
|
+
this._fileLogStream = tmpLogger;
|
|
1173
|
+
|
|
1174
|
+
tmpLog.info('--- File logging enabled ---');
|
|
1175
|
+
|
|
1176
|
+
this._terminalOutput.log(`{green-fg}File logging ON{/green-fg} {gray-fg}${tmpLogPath}{/gray-fg}`);
|
|
1177
|
+
this._updateStatus(`Logging to ${libPath.basename(tmpLogPath)}`);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
this._updateHeader();
|
|
1181
|
+
this._screen.render();
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// ─────────────────────────────────────────────
|
|
1185
|
+
// Header & Status Updates
|
|
1186
|
+
// ─────────────────────────────────────────────
|
|
1187
|
+
|
|
1188
|
+
_updateHeader()
|
|
1189
|
+
{
|
|
1190
|
+
let tmpBrowser = this.pict.AppData.Manager.Browser;
|
|
1191
|
+
let tmpHeaderWidget = this.terminalUI.getWidget('#TUI-Header');
|
|
1192
|
+
|
|
1193
|
+
if (!tmpHeaderWidget)
|
|
1194
|
+
{
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
let tmpHasModule = (tmpBrowser.Level === 'files' && tmpBrowser.ModulePath)
|
|
1199
|
+
|| (tmpBrowser.Level === 'modules');
|
|
1200
|
+
|
|
1201
|
+
// Show [l]og shortcut only when logging is OFF so the user knows how to re-enable it
|
|
1202
|
+
let tmpNavKeys = this._fileLogStream
|
|
1203
|
+
? '[s]tatus [r] update [c]heckout [/] search | [g] groups [Tab] focus [q] quit'
|
|
1204
|
+
: '[s]tatus [r] update [c]heckout [/] search | [l]og [g] groups [Tab] focus [q] quit';
|
|
1205
|
+
|
|
1206
|
+
if (tmpHasModule)
|
|
1207
|
+
{
|
|
1208
|
+
// Determine the target module name to display
|
|
1209
|
+
let tmpTargetModule = '';
|
|
1210
|
+
if (tmpBrowser.Level === 'files')
|
|
1211
|
+
{
|
|
1212
|
+
tmpTargetModule = tmpBrowser.ModuleName;
|
|
1213
|
+
}
|
|
1214
|
+
else if (tmpBrowser.Level === 'modules')
|
|
1215
|
+
{
|
|
1216
|
+
// Show the highlighted module if one is selected (not ../)
|
|
1217
|
+
let tmpSelected = this._fileBrowser.selected;
|
|
1218
|
+
if (tmpSelected > 0)
|
|
1219
|
+
{
|
|
1220
|
+
tmpTargetModule = this._fileBrowser.getItem(tmpSelected).getText().trim().replace(/\/$/, '');
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
if (tmpTargetModule)
|
|
1225
|
+
{
|
|
1226
|
+
tmpHeaderWidget.setContent(
|
|
1227
|
+
`{bold} Retold Manager{/bold} | {cyan-fg}${tmpTargetModule}{/cyan-fg} [i]nstall [t]est t[y]pes [b]uild [v]ersion [d]iff c[o]mmit [p]ull p[u]sh [!] publish | [x] kill ${tmpNavKeys}`
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
else
|
|
1231
|
+
{
|
|
1232
|
+
tmpHeaderWidget.setContent(
|
|
1233
|
+
`{bold} Retold Manager{/bold} | ${tmpNavKeys}`
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
else
|
|
1238
|
+
{
|
|
1239
|
+
tmpHeaderWidget.setContent(
|
|
1240
|
+
`{bold} Retold Manager{/bold} | ${tmpNavKeys}`
|
|
1241
|
+
);
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
if (this._screen)
|
|
1245
|
+
{
|
|
1246
|
+
this._screen.render();
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
_updateStatus(pMessage)
|
|
1251
|
+
{
|
|
1252
|
+
this.pict.AppData.Manager.StatusMessage = pMessage;
|
|
1253
|
+
|
|
1254
|
+
if (this.pict.views['TUI-StatusBar'])
|
|
1255
|
+
{
|
|
1256
|
+
this.pict.views['TUI-StatusBar'].render();
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
if (this._screen)
|
|
1260
|
+
{
|
|
1261
|
+
this._screen.render();
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// ─────────────────────────────────────────────
|
|
1266
|
+
// Key Bindings
|
|
1267
|
+
// ─────────────────────────────────────────────
|
|
1268
|
+
|
|
1269
|
+
_bindNavigation(pScreen)
|
|
1270
|
+
{
|
|
1271
|
+
let tmpList = this._fileBrowser;
|
|
1272
|
+
|
|
1273
|
+
// Enter -- drill into selected item
|
|
1274
|
+
tmpList.on('select', (pItem, pIndex) =>
|
|
1275
|
+
{
|
|
1276
|
+
this._drillIn(pIndex);
|
|
1277
|
+
});
|
|
1278
|
+
|
|
1279
|
+
// Update the header when the cursor moves in the module list
|
|
1280
|
+
// so the targeted module name stays current
|
|
1281
|
+
tmpList.on('select item', () =>
|
|
1282
|
+
{
|
|
1283
|
+
this._updateHeader();
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
// Backspace / Escape -- go up one level
|
|
1287
|
+
tmpList.key(['backspace', 'escape'], () =>
|
|
1288
|
+
{
|
|
1289
|
+
this._drillOut();
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
// Tab -- toggle focus between file browser and terminal output
|
|
1293
|
+
pScreen.key(['tab'], () =>
|
|
1294
|
+
{
|
|
1295
|
+
if (this._fileBrowser === pScreen.focused)
|
|
1296
|
+
{
|
|
1297
|
+
this._terminalOutput.focus();
|
|
1298
|
+
}
|
|
1299
|
+
else
|
|
1300
|
+
{
|
|
1301
|
+
this._fileBrowser.focus();
|
|
1302
|
+
}
|
|
1303
|
+
this._screen.render();
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
// Module operation shortcuts
|
|
1307
|
+
pScreen.key(['i'], () => { this._runModuleOperation('npm', ['install']); });
|
|
1308
|
+
pScreen.key(['t'], () => { this._runModuleOperation('npm', ['test'], 0); });
|
|
1309
|
+
pScreen.key(['y'], () => { this._runModuleOperation('npm', ['run', 'types']); });
|
|
1310
|
+
pScreen.key(['b'], () => { this._runModuleOperation('npm', ['run', 'build']); });
|
|
1311
|
+
pScreen.key(['v'], () => { this._runModuleOperation('npm', ['version', 'patch', '--no-git-tag-version']); });
|
|
1312
|
+
pScreen.key(['d'], () => { this._runDiff(); });
|
|
1313
|
+
pScreen.key(['o'], () =>
|
|
1314
|
+
{
|
|
1315
|
+
if (this._awaitingConfirmation) { return; }
|
|
1316
|
+
|
|
1317
|
+
let tmpModulePath = this._getModulePath();
|
|
1318
|
+
if (!tmpModulePath)
|
|
1319
|
+
{
|
|
1320
|
+
this._terminalOutput.setContent('');
|
|
1321
|
+
this._terminalOutput.log('{yellow-fg}{bold}Select a module first.{/bold}{/yellow-fg}');
|
|
1322
|
+
this._terminalOutput.log('');
|
|
1323
|
+
this._terminalOutput.log('Navigate into a module group, then select or enter a module');
|
|
1324
|
+
this._terminalOutput.log('before running operations.');
|
|
1325
|
+
this._screen.render();
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
this._awaitingConfirmation = true;
|
|
1330
|
+
|
|
1331
|
+
let tmpPrompt = blessed.textbox(
|
|
1332
|
+
{
|
|
1333
|
+
parent: this._screen,
|
|
1334
|
+
bottom: 1,
|
|
1335
|
+
left: 40,
|
|
1336
|
+
right: 0,
|
|
1337
|
+
height: 3,
|
|
1338
|
+
border: { type: 'line' },
|
|
1339
|
+
label: ' Commit Message ',
|
|
1340
|
+
inputOnFocus: true,
|
|
1341
|
+
style:
|
|
1342
|
+
{
|
|
1343
|
+
fg: 'white',
|
|
1344
|
+
bg: 'black',
|
|
1345
|
+
border: { fg: 'yellow' },
|
|
1346
|
+
label: { fg: 'yellow', bold: true },
|
|
1347
|
+
},
|
|
1348
|
+
});
|
|
1349
|
+
|
|
1350
|
+
let tmpCleanup = (pValue) =>
|
|
1351
|
+
{
|
|
1352
|
+
tmpPrompt.destroy();
|
|
1353
|
+
this._awaitingConfirmation = false;
|
|
1354
|
+
|
|
1355
|
+
if (pValue && pValue.trim().length > 0)
|
|
1356
|
+
{
|
|
1357
|
+
// Shell-escape the message: wrap in single quotes, escape any internal single quotes
|
|
1358
|
+
let tmpMessage = pValue.trim().replace(/'/g, "'\\''");
|
|
1359
|
+
this.processRunner.run('git', ['commit', '-a', '-m', `'${tmpMessage}'`], tmpModulePath);
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
this._fileBrowser.focus();
|
|
1363
|
+
this._screen.render();
|
|
1364
|
+
};
|
|
1365
|
+
|
|
1366
|
+
tmpPrompt.on('submit', (pValue) =>
|
|
1367
|
+
{
|
|
1368
|
+
tmpCleanup(pValue);
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
tmpPrompt.on('cancel', () =>
|
|
1372
|
+
{
|
|
1373
|
+
tmpCleanup(null);
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
tmpPrompt.focus();
|
|
1377
|
+
tmpPrompt.readInput();
|
|
1378
|
+
this._screen.render();
|
|
1379
|
+
});
|
|
1380
|
+
pScreen.key(['p'], () => { this._runModuleOperation('git', ['pull']); });
|
|
1381
|
+
pScreen.key(['u'], () => { this._runModuleOperation('git', ['push']); });
|
|
1382
|
+
pScreen.key(['!'], () => { this._runPublish(); });
|
|
1383
|
+
|
|
1384
|
+
// Global script operations (run against all modules)
|
|
1385
|
+
pScreen.key(['s'], () =>
|
|
1386
|
+
{
|
|
1387
|
+
if (this._awaitingConfirmation) { return; }
|
|
1388
|
+
this.pict.views['TUI-Status'].runScript(this.processRunner, libModuleCatalog.BasePath);
|
|
1389
|
+
});
|
|
1390
|
+
pScreen.key(['r'], () =>
|
|
1391
|
+
{
|
|
1392
|
+
if (this._awaitingConfirmation) { return; }
|
|
1393
|
+
this.pict.views['TUI-Update'].runScript(this.processRunner, libModuleCatalog.BasePath);
|
|
1394
|
+
});
|
|
1395
|
+
pScreen.key(['c'], () =>
|
|
1396
|
+
{
|
|
1397
|
+
if (this._awaitingConfirmation) { return; }
|
|
1398
|
+
this.pict.views['TUI-Checkout'].runScript(this.processRunner, libModuleCatalog.BasePath);
|
|
1399
|
+
});
|
|
1400
|
+
|
|
1401
|
+
// Kill running process
|
|
1402
|
+
pScreen.key(['x'], () =>
|
|
1403
|
+
{
|
|
1404
|
+
if (this.processRunner && this.processRunner.isRunning())
|
|
1405
|
+
{
|
|
1406
|
+
this.processRunner.kill();
|
|
1407
|
+
this._terminalOutput.log('{yellow-fg}{bold}Process killed by user{/bold}{/yellow-fg}');
|
|
1408
|
+
this._updateStatus('Killed');
|
|
1409
|
+
this._screen.render();
|
|
1410
|
+
}
|
|
1411
|
+
});
|
|
1412
|
+
|
|
1413
|
+
// Toggle file logging
|
|
1414
|
+
pScreen.key(['l'], () =>
|
|
1415
|
+
{
|
|
1416
|
+
this._toggleFileLogging();
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1419
|
+
// Quit
|
|
1420
|
+
pScreen.key(['q'], () =>
|
|
1421
|
+
{
|
|
1422
|
+
this.terminalUI.destroyScreen();
|
|
1423
|
+
});
|
|
1424
|
+
|
|
1425
|
+
// Search output buffer with /
|
|
1426
|
+
pScreen.key(['/'], () =>
|
|
1427
|
+
{
|
|
1428
|
+
if (this._awaitingConfirmation) { return; }
|
|
1429
|
+
if (!this.processRunner.hasBuffer()) { return; }
|
|
1430
|
+
if (this.processRunner.isRunning()) { return; }
|
|
1431
|
+
|
|
1432
|
+
this._awaitingConfirmation = true;
|
|
1433
|
+
|
|
1434
|
+
// Create an inline prompt at the bottom of the terminal output
|
|
1435
|
+
let tmpPrompt = blessed.textbox(
|
|
1436
|
+
{
|
|
1437
|
+
parent: this._screen,
|
|
1438
|
+
bottom: 1,
|
|
1439
|
+
left: 40,
|
|
1440
|
+
right: 0,
|
|
1441
|
+
height: 3,
|
|
1442
|
+
border: { type: 'line' },
|
|
1443
|
+
label: ' Search ',
|
|
1444
|
+
inputOnFocus: true,
|
|
1445
|
+
style:
|
|
1446
|
+
{
|
|
1447
|
+
fg: 'white',
|
|
1448
|
+
bg: 'black',
|
|
1449
|
+
border: { fg: 'yellow' },
|
|
1450
|
+
label: { fg: 'yellow', bold: true },
|
|
1451
|
+
},
|
|
1452
|
+
});
|
|
1453
|
+
|
|
1454
|
+
let tmpCleanup = (pValue) =>
|
|
1455
|
+
{
|
|
1456
|
+
tmpPrompt.destroy();
|
|
1457
|
+
this._awaitingConfirmation = false;
|
|
1458
|
+
|
|
1459
|
+
if (pValue && pValue.trim().length > 0)
|
|
1460
|
+
{
|
|
1461
|
+
this.processRunner.search(pValue.trim());
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
this._fileBrowser.focus();
|
|
1465
|
+
this._screen.render();
|
|
1466
|
+
};
|
|
1467
|
+
|
|
1468
|
+
// Use the submit event directly -- more reliable than readInput callback
|
|
1469
|
+
// for getting the entered value in blessed ^0.1.x
|
|
1470
|
+
tmpPrompt.on('submit', (pValue) =>
|
|
1471
|
+
{
|
|
1472
|
+
tmpCleanup(pValue);
|
|
1473
|
+
});
|
|
1474
|
+
|
|
1475
|
+
tmpPrompt.on('cancel', () =>
|
|
1476
|
+
{
|
|
1477
|
+
tmpCleanup(null);
|
|
1478
|
+
});
|
|
1479
|
+
|
|
1480
|
+
tmpPrompt.focus();
|
|
1481
|
+
tmpPrompt.readInput();
|
|
1482
|
+
this._screen.render();
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
// Search navigation: next match (] to avoid conflict with other keys)
|
|
1486
|
+
pScreen.key([']'], () =>
|
|
1487
|
+
{
|
|
1488
|
+
if (this._awaitingConfirmation) { return; }
|
|
1489
|
+
if (this.processRunner.isSearchActive())
|
|
1490
|
+
{
|
|
1491
|
+
this.processRunner.searchNavigate(1);
|
|
1492
|
+
}
|
|
1493
|
+
});
|
|
1494
|
+
|
|
1495
|
+
// Search navigation: previous match
|
|
1496
|
+
pScreen.key(['['], () =>
|
|
1497
|
+
{
|
|
1498
|
+
if (this._awaitingConfirmation) { return; }
|
|
1499
|
+
if (this.processRunner.isSearchActive())
|
|
1500
|
+
{
|
|
1501
|
+
this.processRunner.searchNavigate(-1);
|
|
1502
|
+
}
|
|
1503
|
+
});
|
|
1504
|
+
|
|
1505
|
+
// Escape clears search mode and restores full output
|
|
1506
|
+
pScreen.key(['escape'], () =>
|
|
1507
|
+
{
|
|
1508
|
+
if (this._awaitingConfirmation) { return; }
|
|
1509
|
+
if (this.processRunner.isSearchActive())
|
|
1510
|
+
{
|
|
1511
|
+
this.processRunner.searchClear();
|
|
1512
|
+
}
|
|
1513
|
+
});
|
|
1514
|
+
|
|
1515
|
+
// Quick navigation: go to top-level groups
|
|
1516
|
+
pScreen.key(['g'], () =>
|
|
1517
|
+
{
|
|
1518
|
+
let tmpBrowser = this.pict.AppData.Manager.Browser;
|
|
1519
|
+
tmpBrowser.Level = 'groups';
|
|
1520
|
+
tmpBrowser.GroupIndex = -1;
|
|
1521
|
+
tmpBrowser.GroupName = '';
|
|
1522
|
+
tmpBrowser.GroupLabel = '';
|
|
1523
|
+
tmpBrowser.ModuleName = '';
|
|
1524
|
+
tmpBrowser.ModulePath = '';
|
|
1525
|
+
tmpBrowser.SubPath = '';
|
|
1526
|
+
this._populateFileList();
|
|
1527
|
+
this._fileBrowser.focus();
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
module.exports = RetoldManagerApp;
|