@emeryld/manager 0.5.0 → 0.6.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/menu.js CHANGED
@@ -4,7 +4,9 @@ import { releaseMultiple, releaseSingle } from './release.js';
4
4
  import { getOrderedPackages } from './packages.js';
5
5
  import { runHelperCli } from './helper-cli.js';
6
6
  import { ensureWorkingTreeCommitted } from './preflight.js';
7
- export function makeStepEntries(targets, packages, state) {
7
+ import { run } from './utils/run.js';
8
+ function makeManagerStepEntries(targets, packages, state, options) {
9
+ const includeBack = options?.includeBack ?? true;
8
10
  return [
9
11
  {
10
12
  name: 'update dependencies',
@@ -74,22 +76,43 @@ export function makeStepEntries(targets, packages, state) {
74
76
  state.lastStep = 'full';
75
77
  },
76
78
  },
77
- {
78
- name: 'back',
79
- emoji: '↩️',
80
- description: 'Pick packages again',
79
+ ...(includeBack
80
+ ? [
81
+ {
82
+ name: 'back',
83
+ emoji: '↩️',
84
+ description: 'Pick packages again',
85
+ handler: async () => {
86
+ state.lastStep = 'back';
87
+ },
88
+ },
89
+ ]
90
+ : []),
91
+ ];
92
+ }
93
+ function makePackageScriptEntries(pkg) {
94
+ const scripts = pkg.json?.scripts ?? {};
95
+ const entries = [];
96
+ Object.entries(scripts)
97
+ .sort(([a], [b]) => a.localeCompare(b))
98
+ .forEach(([name, command]) => {
99
+ entries.push({
100
+ name,
101
+ emoji: '▶️',
102
+ description: typeof command === 'string' ? command : '',
81
103
  handler: async () => {
82
- state.lastStep = 'back';
104
+ await run('pnpm', ['run', name], { cwd: pkg.path });
83
105
  },
84
- },
85
- ];
106
+ });
107
+ });
108
+ return entries;
86
109
  }
87
110
  export function buildPackageSelectionMenu(packages, onStepComplete) {
88
111
  const ordered = getOrderedPackages(packages);
89
112
  const entries = ordered.map((pkg) => ({
90
- name: pkg.substitute,
113
+ name: pkg.name ?? pkg.substitute ?? pkg.dirName,
91
114
  emoji: colors[pkg.color]('●'),
92
- description: pkg.name ?? pkg.dirName,
115
+ description: pkg.relativeDir ?? pkg.dirName,
93
116
  handler: async () => {
94
117
  const step = await runStepLoop([pkg], packages);
95
118
  onStepComplete?.(step);
@@ -112,6 +135,48 @@ export function buildPackageSelectionMenu(packages, onStepComplete) {
112
135
  // This removes the infinite loop and returns to the step menu after each run.
113
136
  export async function runStepLoop(targets, packages) {
114
137
  const state = {};
138
+ // Single package: show combined menu (manager actions + package.json scripts)
139
+ if (targets.length === 1) {
140
+ const pkg = targets[0];
141
+ // eslint-disable-next-line no-constant-condition
142
+ while (true) {
143
+ const scriptEntries = makePackageScriptEntries(pkg);
144
+ const entries = [
145
+ {
146
+ name: 'manager actions',
147
+ emoji: globalEmoji,
148
+ description: 'update/test/build/publish',
149
+ handler: async () => {
150
+ const managerEntries = makeManagerStepEntries(targets, packages, state, {
151
+ includeBack: false,
152
+ });
153
+ await runHelperCli({
154
+ title: `Manager actions for ${pkg.name}`,
155
+ scripts: managerEntries,
156
+ argv: [],
157
+ });
158
+ },
159
+ },
160
+ ...scriptEntries,
161
+ {
162
+ name: 'back',
163
+ emoji: '↩️',
164
+ description: 'Pick packages again',
165
+ handler: () => {
166
+ state.lastStep = 'back';
167
+ },
168
+ },
169
+ ];
170
+ await runHelperCli({
171
+ title: `Actions for ${pkg.name}`,
172
+ scripts: entries,
173
+ argv: [],
174
+ });
175
+ if (state.lastStep === 'back')
176
+ return state.lastStep;
177
+ // loop to keep showing menu
178
+ }
179
+ }
115
180
  // Loop shows the step menu and executes a chosen action once.
116
181
  // After the handler completes, show the menu again.
117
182
  // Selecting "back" breaks and returns control to the package picker.
@@ -119,7 +184,7 @@ export async function runStepLoop(targets, packages) {
119
184
  // eslint-disable-next-line no-constant-condition
120
185
  while (true) {
121
186
  state.lastStep = undefined;
122
- const entries = makeStepEntries(targets, packages, state);
187
+ const entries = makeManagerStepEntries(targets, packages, state);
123
188
  await runHelperCli({
124
189
  title: `Actions for ${targets.length === 1 ? targets[0].name : 'selected packages'}`,
125
190
  scripts: entries,
package/dist/prompts.js CHANGED
@@ -1,7 +1,9 @@
1
1
  // src/prompts.js
2
2
  import readline from 'node:readline/promises';
3
3
  import { stdin as input, stdout as output } from 'node:process';
4
- export const publishCliState = { autoConfirmAll: false };
4
+ export const publishCliState = {
5
+ autoDecision: undefined,
6
+ };
5
7
  export function promptSingleKey(message, resolver) {
6
8
  const supportsRawMode = typeof input.setRawMode === 'function' && input.isTTY;
7
9
  if (!supportsRawMode) {
@@ -64,22 +66,26 @@ export async function askLine(question) {
64
66
  }
65
67
  }
66
68
  export async function promptYesNoAll(question) {
67
- if (publishCliState.autoConfirmAll) {
68
- console.log(`${question} (auto-confirmed via "all")`);
69
- return 'yes';
69
+ if (publishCliState.autoDecision) {
70
+ console.log(`${question} (auto-${publishCliState.autoDecision} via "all")`);
71
+ return publishCliState.autoDecision;
70
72
  }
71
- const result = await promptSingleKey(`${question} (y/n/a): `, (key) => {
72
- if (key === 'y')
73
+ // Allow yes/no + "apply to all" variants: y, ya, n, na
74
+ // Use readline-style input to capture multi-character tokens consistently.
75
+ // eslint-disable-next-line no-constant-condition
76
+ while (true) {
77
+ const answer = (await askLine(`${question} (y/ya/n/na): `)).toLowerCase();
78
+ if (answer === 'y')
73
79
  return 'yes';
74
- if (key === 'n')
80
+ if (answer === 'ya') {
81
+ publishCliState.autoDecision = 'yes';
82
+ return 'yes';
83
+ }
84
+ if (answer === 'n')
75
85
  return 'no';
76
- if (key === 'a')
77
- return 'all';
78
- return undefined;
79
- });
80
- if (result === 'all') {
81
- publishCliState.autoConfirmAll = true;
82
- return 'yes';
86
+ if (answer === 'na') {
87
+ publishCliState.autoDecision = 'no';
88
+ return 'no';
89
+ }
83
90
  }
84
- return result;
85
91
  }
package/dist/publish.js CHANGED
@@ -136,7 +136,7 @@ async function main() {
136
136
  if (packages.length === 0) {
137
137
  throw new Error('No packages with a package.json found in this workspace');
138
138
  }
139
- publishCliState.autoConfirmAll = true;
139
+ publishCliState.autoDecision = 'yes';
140
140
  if (!parsed.selectionArg) {
141
141
  throw new Error('Non-interactive mode requires a package selection: <pkg> or "all".');
142
142
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emeryld/manager",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Interactive manager for pnpm monorepos (update/test/build/publish).",
5
5
  "license": "MIT",
6
6
  "type": "module",