@emeryld/manager 0.8.2 → 1.0.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.
- package/dist/prompts.js +44 -63
- package/dist/publish.js +80 -0
- package/package.json +1 -1
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/publish.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
// src/publish.js
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { lt, valid } from 'semver';
|
|
2
6
|
import { runHelperCli } from './helper-cli.js';
|
|
3
7
|
import { buildPackageSelectionMenu, runStepLoop } from './menu.js';
|
|
4
8
|
import { getOrderedPackages, loadPackages, resolvePackage } from './packages.js';
|
|
@@ -7,6 +11,67 @@ import { ensureWorkingTreeCommitted } from './preflight.js';
|
|
|
7
11
|
import { publishCliState } from './prompts.js';
|
|
8
12
|
import { createRrrPackage, runCreatePackageCli, } from './create-package/index.js';
|
|
9
13
|
import { colors, logGlobal } from './utils/log.js';
|
|
14
|
+
import { run } from './utils/run.js';
|
|
15
|
+
const MANAGER_PACKAGE = '@emeryld/manager';
|
|
16
|
+
const UPDATE_CHECK_TIMEOUT_MS = 2500;
|
|
17
|
+
const managerRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
18
|
+
const managerPackageJsonPath = path.join(managerRoot, 'package.json');
|
|
19
|
+
async function readJsonFile(filePath) {
|
|
20
|
+
try {
|
|
21
|
+
const raw = await readFile(filePath, 'utf8');
|
|
22
|
+
return JSON.parse(raw);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function getCurrentManagerVersion() {
|
|
29
|
+
const pkg = await readJsonFile(managerPackageJsonPath);
|
|
30
|
+
return pkg?.version;
|
|
31
|
+
}
|
|
32
|
+
async function fetchLatestVersion(pkgName) {
|
|
33
|
+
if (typeof fetch !== 'function')
|
|
34
|
+
return undefined;
|
|
35
|
+
const encoded = pkgName.replace('/', '%2F');
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
const timeout = setTimeout(() => controller.abort(), UPDATE_CHECK_TIMEOUT_MS);
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetch(`https://registry.npmjs.org/${encoded}`, {
|
|
40
|
+
headers: { Accept: 'application/vnd.npm.install-v1+json' },
|
|
41
|
+
signal: controller.signal,
|
|
42
|
+
});
|
|
43
|
+
if (!response.ok)
|
|
44
|
+
return undefined;
|
|
45
|
+
const data = (await response.json());
|
|
46
|
+
return data?.['dist-tags']?.latest;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
clearTimeout(timeout);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function isManagerLocalInstall(rootDir) {
|
|
56
|
+
const relative = path.relative(rootDir, managerRoot);
|
|
57
|
+
return Boolean(relative && !relative.startsWith('..') && !path.isAbsolute(relative));
|
|
58
|
+
}
|
|
59
|
+
async function getManagerUpdateInfo(rootDir) {
|
|
60
|
+
const current = await getCurrentManagerVersion();
|
|
61
|
+
if (!current)
|
|
62
|
+
return undefined;
|
|
63
|
+
const latest = await fetchLatestVersion(MANAGER_PACKAGE);
|
|
64
|
+
if (!latest)
|
|
65
|
+
return undefined;
|
|
66
|
+
if (!valid(current) || !valid(latest) || !lt(current, latest)) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
const command = 'pnpm';
|
|
70
|
+
const args = isManagerLocalInstall(rootDir)
|
|
71
|
+
? ['update', '--latest', MANAGER_PACKAGE]
|
|
72
|
+
: ['add', '-g', `${MANAGER_PACKAGE}@latest`];
|
|
73
|
+
return { current, latest, command, args };
|
|
74
|
+
}
|
|
10
75
|
function resolveTargetsFromArg(packages, arg) {
|
|
11
76
|
if (arg.toLowerCase() === 'all')
|
|
12
77
|
return getOrderedPackages(packages);
|
|
@@ -92,12 +157,27 @@ function optsFromParsed(p) {
|
|
|
92
157
|
async function runPackageSelectionLoop(packages, helperArgs) {
|
|
93
158
|
let argv = [...helperArgs];
|
|
94
159
|
let currentPackages = packages;
|
|
160
|
+
const updateInfo = await getManagerUpdateInfo(process.cwd());
|
|
95
161
|
// eslint-disable-next-line no-constant-condition
|
|
96
162
|
while (true) {
|
|
97
163
|
let lastStep;
|
|
164
|
+
const updateEntry = updateInfo
|
|
165
|
+
? {
|
|
166
|
+
name: 'Update @emeryld/manager to latest version',
|
|
167
|
+
emoji: '⬆️',
|
|
168
|
+
description: `Current v${updateInfo.current}, latest v${updateInfo.latest}`,
|
|
169
|
+
handler: async () => {
|
|
170
|
+
logGlobal(`Updating ${MANAGER_PACKAGE} to v${updateInfo.latest}...`, colors.cyan);
|
|
171
|
+
await run(updateInfo.command, updateInfo.args);
|
|
172
|
+
logGlobal(`${MANAGER_PACKAGE} updated. Re-run manager-cli to use v${updateInfo.latest}.`, colors.green);
|
|
173
|
+
lastStep = 'back';
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
: undefined;
|
|
98
177
|
await runHelperCli({
|
|
99
178
|
title: 'Pick one of the packages or all',
|
|
100
179
|
scripts: [
|
|
180
|
+
...(updateEntry ? [updateEntry] : []),
|
|
101
181
|
...buildPackageSelectionMenu(currentPackages, (step) => {
|
|
102
182
|
lastStep = step;
|
|
103
183
|
}),
|