@emeryld/manager 0.8.1 → 1.0.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/dist/helper-cli/prompts.js +13 -27
- package/dist/prompts.js +44 -63
- package/dist/utils/package-filter.js +7 -0
- package/dist/version-control.js +34 -15
- package/dist/workspace.js +1 -7
- package/package.json +1 -1
|
@@ -100,12 +100,12 @@ function formatInteractiveLines(state, selectedIndex, title, searchState) {
|
|
|
100
100
|
lines.push(colors.yellow('No scripts match your search.'));
|
|
101
101
|
}
|
|
102
102
|
lines.push('');
|
|
103
|
-
lines.push(colors.dim(`Use ↑/↓ (or j/k) to move, 1-${PAGE_SIZE} to run, ${PREVIOUS_KEY} prev page (when shown), ${NEXT_KEY} next page (when shown), ${BACK_KEY} back, Enter to run,
|
|
103
|
+
lines.push(colors.dim(`Use ↑/↓ (or j/k) to move, 1-${PAGE_SIZE} to run, ${PREVIOUS_KEY} prev page (when shown), ${NEXT_KEY} next page (when shown), ${BACK_KEY} back, Enter to run, Type to filter (numbers run scripts), Esc/Ctrl+C to exit.`));
|
|
104
104
|
if (searchState?.active) {
|
|
105
105
|
lines.push(colors.dim('Search mode: type to filter, Backspace edits the query, Esc returns to navigation, Enter runs the highlighted entry.'));
|
|
106
106
|
}
|
|
107
107
|
else {
|
|
108
|
-
lines.push(colors.dim('
|
|
108
|
+
lines.push(colors.dim('Type any non-number character to start filtering.'));
|
|
109
109
|
}
|
|
110
110
|
return lines;
|
|
111
111
|
}
|
|
@@ -230,13 +230,13 @@ export async function promptForScript(entries, title) {
|
|
|
230
230
|
selectedIndex = 0;
|
|
231
231
|
render();
|
|
232
232
|
};
|
|
233
|
-
const enterSearchMode = () => {
|
|
233
|
+
const enterSearchMode = (initialQuery = '') => {
|
|
234
234
|
if (searchActive)
|
|
235
235
|
return;
|
|
236
236
|
pageBeforeSearch = state.page;
|
|
237
237
|
selectionBeforeSearch = selectedIndex;
|
|
238
238
|
searchActive = true;
|
|
239
|
-
searchQuery =
|
|
239
|
+
searchQuery = initialQuery;
|
|
240
240
|
applySearch();
|
|
241
241
|
};
|
|
242
242
|
const exitSearchMode = () => {
|
|
@@ -256,8 +256,10 @@ export async function promptForScript(entries, title) {
|
|
|
256
256
|
const isEnter = buffer.length === 1 && (buffer[0] === 0x0d || buffer[0] === 0x0a);
|
|
257
257
|
const isEscape = buffer.length === 1 && buffer[0] === 0x1b;
|
|
258
258
|
const isBackspace = buffer.length === 1 && (buffer[0] === 0x7f || buffer[0] === 0x08);
|
|
259
|
-
const
|
|
260
|
-
const
|
|
259
|
+
const ascii = buffer.length === 1 ? buffer[0] : undefined;
|
|
260
|
+
const isPrintable = ascii !== undefined && ascii >= 0x20 && ascii <= 0x7e;
|
|
261
|
+
const isDigit = ascii !== undefined && ascii >= 0x30 && ascii <= 0x39;
|
|
262
|
+
const typedChar = isPrintable && ascii !== undefined ? String.fromCharCode(ascii) : '';
|
|
261
263
|
if (isCtrlC) {
|
|
262
264
|
cleanup();
|
|
263
265
|
process.exit(1);
|
|
@@ -270,10 +272,6 @@ export async function promptForScript(entries, title) {
|
|
|
270
272
|
cleanup();
|
|
271
273
|
process.exit(1);
|
|
272
274
|
}
|
|
273
|
-
if (!searchActive && isSpace) {
|
|
274
|
-
enterSearchMode();
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
275
|
if (isArrowUp ||
|
|
278
276
|
(buffer.length === 1 && (buffer[0] === 0x6b || buffer[0] === 0x4b))) {
|
|
279
277
|
selectedIndex =
|
|
@@ -307,14 +305,14 @@ export async function promptForScript(entries, title) {
|
|
|
307
305
|
return;
|
|
308
306
|
}
|
|
309
307
|
if (isPrintable) {
|
|
310
|
-
searchQuery +=
|
|
308
|
+
searchQuery += typedChar;
|
|
311
309
|
applySearch();
|
|
312
310
|
return;
|
|
313
311
|
}
|
|
314
312
|
return;
|
|
315
313
|
}
|
|
316
|
-
if (
|
|
317
|
-
const numericValue =
|
|
314
|
+
if (isDigit && ascii !== undefined) {
|
|
315
|
+
const numericValue = ascii - 0x30;
|
|
318
316
|
const option = state.options.find((opt) => opt.hotkey === numericValue);
|
|
319
317
|
if (option)
|
|
320
318
|
activateOption(option);
|
|
@@ -322,20 +320,8 @@ export async function promptForScript(entries, title) {
|
|
|
322
320
|
process.stdout.write('\x07');
|
|
323
321
|
return;
|
|
324
322
|
}
|
|
325
|
-
if (
|
|
326
|
-
(
|
|
327
|
-
(buffer[0] >= 0x61 && buffer[0] <= 0x7a))) {
|
|
328
|
-
const char = String.fromCharCode(buffer[0]).toLowerCase();
|
|
329
|
-
const foundIndex = baseEntries.findIndex((entry) => entry.displayName.toLowerCase().startsWith(char));
|
|
330
|
-
if (foundIndex !== -1) {
|
|
331
|
-
const page = Math.floor(foundIndex / PAGE_SIZE);
|
|
332
|
-
state = buildVisibleOptions(filteredEntries, page);
|
|
333
|
-
selectedIndex = foundIndex % PAGE_SIZE;
|
|
334
|
-
render();
|
|
335
|
-
}
|
|
336
|
-
else {
|
|
337
|
-
process.stdout.write('\x07');
|
|
338
|
-
}
|
|
323
|
+
if (isPrintable) {
|
|
324
|
+
enterSearchMode(typedChar);
|
|
339
325
|
return;
|
|
340
326
|
}
|
|
341
327
|
process.stdout.write('\x07');
|
package/dist/prompts.js
CHANGED
|
@@ -2,9 +2,43 @@
|
|
|
2
2
|
import readline from 'node:readline/promises';
|
|
3
3
|
import { stdin as input, stdout as output } from 'node:process';
|
|
4
4
|
import { colors } from './utils/log.js';
|
|
5
|
+
import { promptForScript } from './helper-cli/prompts.js';
|
|
6
|
+
import { normalizeScripts } from './helper-cli/scripts.js';
|
|
5
7
|
export const publishCliState = {
|
|
6
8
|
autoDecision: undefined,
|
|
7
9
|
};
|
|
10
|
+
const noopHandler = () => { };
|
|
11
|
+
const yesNoAllChoices = [
|
|
12
|
+
{
|
|
13
|
+
name: 'Yes',
|
|
14
|
+
description: 'Apply once',
|
|
15
|
+
emoji: 'Y',
|
|
16
|
+
color: 'green',
|
|
17
|
+
handler: noopHandler,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'Yes to all',
|
|
21
|
+
description: 'Apply to all remaining prompts',
|
|
22
|
+
emoji: 'YA',
|
|
23
|
+
color: 'brightGreen',
|
|
24
|
+
handler: noopHandler,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'No',
|
|
28
|
+
description: 'Skip once',
|
|
29
|
+
emoji: 'N',
|
|
30
|
+
color: 'red',
|
|
31
|
+
handler: noopHandler,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'No to all',
|
|
35
|
+
description: 'Skip all remaining prompts',
|
|
36
|
+
emoji: 'NA',
|
|
37
|
+
color: 'brightRed',
|
|
38
|
+
handler: noopHandler,
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
const yesNoAllEntries = normalizeScripts(yesNoAllChoices);
|
|
8
42
|
export function promptSingleKey(message, resolver) {
|
|
9
43
|
const supportsRawMode = typeof input.setRawMode === 'function' && input.isTTY;
|
|
10
44
|
if (!supportsRawMode) {
|
|
@@ -71,78 +105,25 @@ export async function promptYesNoAll(question) {
|
|
|
71
105
|
console.log(`${question} (auto-${publishCliState.autoDecision} via "all")`);
|
|
72
106
|
return publishCliState.autoDecision;
|
|
73
107
|
}
|
|
74
|
-
// Allow yes/no + "apply to all" variants: yy, ya, nn, na
|
|
75
|
-
const promptMessage = `${question} (yy/ya/nn/na): `;
|
|
76
|
-
const supportsRawMode = typeof input.setRawMode === 'function' && input.isTTY;
|
|
77
|
-
const readDecisionInput = async () => {
|
|
78
|
-
if (!supportsRawMode) {
|
|
79
|
-
const fallback = await askLine(promptMessage);
|
|
80
|
-
return fallback.trim().toLowerCase();
|
|
81
|
-
}
|
|
82
|
-
return new Promise((resolve) => {
|
|
83
|
-
const wasRaw = input.isRaw;
|
|
84
|
-
if (!wasRaw) {
|
|
85
|
-
input.setRawMode(true);
|
|
86
|
-
input.resume();
|
|
87
|
-
}
|
|
88
|
-
process.stdout.write(promptMessage);
|
|
89
|
-
const cleanup = () => {
|
|
90
|
-
input.off('data', onData);
|
|
91
|
-
if (!wasRaw) {
|
|
92
|
-
input.setRawMode(false);
|
|
93
|
-
input.pause();
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
let collected = '';
|
|
97
|
-
const onData = (buffer) => {
|
|
98
|
-
const str = buffer.toString('utf8');
|
|
99
|
-
for (const char of str) {
|
|
100
|
-
if (char === '\u0003') {
|
|
101
|
-
cleanup();
|
|
102
|
-
process.stdout.write('\n');
|
|
103
|
-
process.exit(1);
|
|
104
|
-
}
|
|
105
|
-
if (char === '\u0008' || char === '\u007f') {
|
|
106
|
-
if (collected.length > 0) {
|
|
107
|
-
collected = collected.slice(0, -1);
|
|
108
|
-
process.stdout.write('\b \b');
|
|
109
|
-
}
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
if (char === '\r' || char === '\n') {
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
if (!/^[a-zA-Z]$/.test(char)) {
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
process.stdout.write(char);
|
|
119
|
-
collected += char;
|
|
120
|
-
if (collected.length === 2) {
|
|
121
|
-
cleanup();
|
|
122
|
-
process.stdout.write('\n');
|
|
123
|
-
resolve(collected.toLowerCase());
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
input.on('data', onData);
|
|
129
|
-
});
|
|
130
|
-
};
|
|
131
108
|
// eslint-disable-next-line no-constant-condition
|
|
132
109
|
while (true) {
|
|
133
|
-
const
|
|
134
|
-
if (
|
|
110
|
+
const selection = await promptForScript(yesNoAllEntries, question);
|
|
111
|
+
if (!selection) {
|
|
112
|
+
console.log(colors.yellow('Please select an option to continue.'));
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (selection.name === 'Yes')
|
|
135
116
|
return 'yes';
|
|
136
|
-
if (
|
|
117
|
+
if (selection.name === 'Yes to all') {
|
|
137
118
|
publishCliState.autoDecision = 'yes';
|
|
138
119
|
return 'yes';
|
|
139
120
|
}
|
|
140
|
-
if (
|
|
121
|
+
if (selection.name === 'No')
|
|
141
122
|
return 'no';
|
|
142
|
-
if (
|
|
123
|
+
if (selection.name === 'No to all') {
|
|
143
124
|
publishCliState.autoDecision = 'no';
|
|
144
125
|
return 'no';
|
|
145
126
|
}
|
|
146
|
-
console.log(colors.red('Please
|
|
127
|
+
console.log(colors.red('Please select a valid option to continue.'));
|
|
147
128
|
}
|
|
148
129
|
}
|
package/dist/version-control.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { askLine } from './prompts.js';
|
|
2
2
|
import { run } from './utils/run.js';
|
|
3
3
|
import { colors, logGlobal } from './utils/log.js';
|
|
4
|
+
import { packageFilterArg } from './utils/package-filter.js';
|
|
4
5
|
const DEP_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies'];
|
|
5
6
|
const FIELD_LABELS = {
|
|
6
7
|
dependencies: 'deps',
|
|
@@ -49,17 +50,35 @@ function getMostCommonVersion(versions) {
|
|
|
49
50
|
}
|
|
50
51
|
return best;
|
|
51
52
|
}
|
|
52
|
-
function
|
|
53
|
+
function ensureVersionControlTargets(targets, allPackages, feature) {
|
|
53
54
|
if (allPackages.length === 0) {
|
|
54
55
|
logGlobal('No packages found to run version control actions.', colors.yellow);
|
|
55
56
|
return false;
|
|
56
57
|
}
|
|
57
|
-
if (targets.length
|
|
58
|
-
logGlobal(
|
|
58
|
+
if (targets.length === 0) {
|
|
59
|
+
logGlobal(`Select at least one package before running ${feature}.`, colors.yellow);
|
|
59
60
|
return false;
|
|
60
61
|
}
|
|
61
62
|
return true;
|
|
62
63
|
}
|
|
64
|
+
function buildPackageFilterArgs(targets, allPackages) {
|
|
65
|
+
if (!targets.length || targets.length === allPackages.length) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
const seen = new Set();
|
|
69
|
+
const args = [];
|
|
70
|
+
for (const pkg of targets) {
|
|
71
|
+
const filterArg = packageFilterArg(pkg);
|
|
72
|
+
if (seen.has(filterArg))
|
|
73
|
+
continue;
|
|
74
|
+
seen.add(filterArg);
|
|
75
|
+
args.push('--filter', filterArg);
|
|
76
|
+
}
|
|
77
|
+
return args;
|
|
78
|
+
}
|
|
79
|
+
function buildRecursiveUpdateArgs(targets, allPackages, spec) {
|
|
80
|
+
return ['update', '-r', ...buildPackageFilterArgs(targets, allPackages), spec];
|
|
81
|
+
}
|
|
63
82
|
function formatConflictDetails(versions) {
|
|
64
83
|
return [...versions.entries()]
|
|
65
84
|
.map(([range, entries]) => {
|
|
@@ -73,7 +92,7 @@ function formatConflictDetails(versions) {
|
|
|
73
92
|
.join(' | ');
|
|
74
93
|
}
|
|
75
94
|
async function updateByPattern(targets, allPackages) {
|
|
76
|
-
if (!
|
|
95
|
+
if (!ensureVersionControlTargets(targets, allPackages, 'Regex dependency update'))
|
|
77
96
|
return;
|
|
78
97
|
const scopeLabel = describeVersionControlScope(targets, allPackages);
|
|
79
98
|
const defaultPattern = '@emeryld/*';
|
|
@@ -90,15 +109,15 @@ async function updateByPattern(targets, allPackages) {
|
|
|
90
109
|
? rawPattern
|
|
91
110
|
: `${rawPattern}@${normalizedVersion}`;
|
|
92
111
|
logGlobal(`Updating dependencies using ${colors.cyan(spec)} for ${scopeLabel}…`, colors.cyan);
|
|
93
|
-
await run('pnpm',
|
|
112
|
+
await run('pnpm', buildRecursiveUpdateArgs(targets, allPackages, spec));
|
|
94
113
|
logGlobal('Pattern update complete.', colors.green);
|
|
95
114
|
}
|
|
96
115
|
async function runVersionHealthScan(targets, allPackages) {
|
|
97
|
-
if (!
|
|
116
|
+
if (!ensureVersionControlTargets(targets, allPackages, 'Version health scan'))
|
|
98
117
|
return;
|
|
99
118
|
const scopeLabel = describeVersionControlScope(targets, allPackages);
|
|
100
119
|
logGlobal(`Scanning version misalignments for ${scopeLabel}…`, colors.cyan);
|
|
101
|
-
const dependencyMap = collectDependencyMap(
|
|
120
|
+
const dependencyMap = collectDependencyMap(targets);
|
|
102
121
|
const conflicts = [...dependencyMap.entries()].filter(([, versions]) => versions.size > 1);
|
|
103
122
|
if (conflicts.length === 0) {
|
|
104
123
|
logGlobal('No version conflicts detected.', colors.green);
|
|
@@ -110,12 +129,12 @@ async function runVersionHealthScan(targets, allPackages) {
|
|
|
110
129
|
});
|
|
111
130
|
}
|
|
112
131
|
async function alignDependencyVersions(targets, allPackages) {
|
|
113
|
-
if (!
|
|
132
|
+
if (!ensureVersionControlTargets(targets, allPackages, 'Dependency alignment'))
|
|
114
133
|
return;
|
|
115
134
|
const scopeLabel = describeVersionControlScope(targets, allPackages);
|
|
116
|
-
const dependencyMap = collectDependencyMap(
|
|
135
|
+
const dependencyMap = collectDependencyMap(targets);
|
|
117
136
|
if (!dependencyMap.size) {
|
|
118
|
-
logGlobal(
|
|
137
|
+
logGlobal(`No dependencies found in ${scopeLabel}.`, colors.yellow);
|
|
119
138
|
return;
|
|
120
139
|
}
|
|
121
140
|
const misaligned = [...dependencyMap.entries()]
|
|
@@ -131,7 +150,7 @@ async function alignDependencyVersions(targets, allPackages) {
|
|
|
131
150
|
const dependency = dependencyInput || defaultDependency;
|
|
132
151
|
const versions = dependencyMap.get(dependency);
|
|
133
152
|
if (!versions) {
|
|
134
|
-
logGlobal(`Dependency "${dependency}" not tracked across
|
|
153
|
+
logGlobal(`Dependency "${dependency}" not tracked across ${scopeLabel}.`, colors.yellow);
|
|
135
154
|
return;
|
|
136
155
|
}
|
|
137
156
|
const defaultVersion = getMostCommonVersion(versions) ?? [...versions.keys()][0];
|
|
@@ -146,15 +165,15 @@ async function alignDependencyVersions(targets, allPackages) {
|
|
|
146
165
|
}
|
|
147
166
|
const spec = `${dependency}@${targetVersion}`;
|
|
148
167
|
logGlobal(`Aligning ${colors.cyan(dependency)} to ${colors.cyan(targetVersion)} across ${scopeLabel}…`, colors.cyan);
|
|
149
|
-
await run('pnpm',
|
|
168
|
+
await run('pnpm', buildRecursiveUpdateArgs(targets, allPackages, spec));
|
|
150
169
|
logGlobal('Dependency alignment complete.', colors.green);
|
|
151
170
|
}
|
|
152
171
|
async function showDriftReport(targets, allPackages) {
|
|
153
|
-
if (!
|
|
172
|
+
if (!ensureVersionControlTargets(targets, allPackages, 'Drift report'))
|
|
154
173
|
return;
|
|
155
174
|
const scopeLabel = describeVersionControlScope(targets, allPackages);
|
|
156
175
|
logGlobal(`Computing drift report for ${scopeLabel}…`, colors.cyan);
|
|
157
|
-
const dependencyMap = collectDependencyMap(
|
|
176
|
+
const dependencyMap = collectDependencyMap(targets);
|
|
158
177
|
const driftLines = [];
|
|
159
178
|
for (const [dep, versions] of dependencyMap.entries()) {
|
|
160
179
|
if (versions.size <= 1)
|
|
@@ -176,7 +195,7 @@ async function showDriftReport(targets, allPackages) {
|
|
|
176
195
|
driftLines.forEach((line) => console.log(` ${line}`));
|
|
177
196
|
}
|
|
178
197
|
async function checkLockfile(targets, allPackages) {
|
|
179
|
-
if (!
|
|
198
|
+
if (!ensureVersionControlTargets(targets, allPackages, 'Lockfile check'))
|
|
180
199
|
return;
|
|
181
200
|
const scopeLabel = describeVersionControlScope(targets, allPackages);
|
|
182
201
|
logGlobal(`Verifying lockfile matches the ${scopeLabel} manifests…`, colors.cyan);
|
package/dist/workspace.js
CHANGED
|
@@ -4,6 +4,7 @@ import { rm } from 'node:fs/promises';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { run, rootDir } from './utils/run.js';
|
|
6
6
|
import { logGlobal, logPkg, colors } from './utils/log.js';
|
|
7
|
+
import { packageFilterArg } from './utils/package-filter.js';
|
|
7
8
|
import { collectGitStatus, gitAdd, gitCommit } from './git.js';
|
|
8
9
|
import { askLine, promptSingleKey } from './prompts.js';
|
|
9
10
|
const dependencyFiles = new Set([
|
|
@@ -186,13 +187,6 @@ async function promptCommitMessage(proposed) {
|
|
|
186
187
|
}
|
|
187
188
|
return custom.trim();
|
|
188
189
|
}
|
|
189
|
-
function packageFilterArg(pkg) {
|
|
190
|
-
if (pkg.name)
|
|
191
|
-
return pkg.name;
|
|
192
|
-
if (!pkg.relativeDir || pkg.relativeDir === '.')
|
|
193
|
-
return '.';
|
|
194
|
-
return `./${pkg.relativeDir}`;
|
|
195
|
-
}
|
|
196
190
|
async function runLifecycleScenario(pkg, scenario) {
|
|
197
191
|
if (pkg) {
|
|
198
192
|
logPkg(pkg, scenario.singleMessage);
|