@emeryld/manager 0.2.1 → 0.2.3

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.
@@ -0,0 +1,292 @@
1
+ // src/release.js
2
+ import { readFile, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { stageCommitPush } from './git.js';
5
+ import { askLine, promptSingleKey, promptYesNoAll } from './prompts.js';
6
+ import { bumpVersion, isSemver } from './semver.js';
7
+ import { colors, formatPkgName, logGlobal, logPkg } from './utils/log.js';
8
+ import { run } from './utils/run.js';
9
+ const bumpMap = {
10
+ '1': 'patch',
11
+ '2': 'minor',
12
+ '3': 'major',
13
+ // mapped from pre-release submenu later, but keep named keys for nonInteractive
14
+ patch: 'patch',
15
+ minor: 'minor',
16
+ major: 'major',
17
+ prepatch: 'prepatch',
18
+ preminor: 'preminor',
19
+ premajor: 'premajor',
20
+ prerelease: 'prerelease',
21
+ };
22
+ function preBumpFromChoice(choice) {
23
+ switch (choice) {
24
+ case '1':
25
+ return 'prepatch';
26
+ case '2':
27
+ return 'preminor';
28
+ case '3':
29
+ return 'premajor';
30
+ case '4':
31
+ return 'prerelease';
32
+ default:
33
+ return undefined;
34
+ }
35
+ }
36
+ async function writePackageJson(pkgPath, json) {
37
+ await writeFile(pkgPath, `${JSON.stringify(json, null, 2)}\n`);
38
+ }
39
+ async function refreshPackagesFromDisk(pkgs) {
40
+ await Promise.all(pkgs.map(async (pkg) => {
41
+ const raw = await readFile(pkg.packageJsonPath, 'utf8');
42
+ const json = JSON.parse(raw);
43
+ pkg.json = json;
44
+ pkg.version = json.version ?? pkg.version;
45
+ if (json.name)
46
+ pkg.name = json.name;
47
+ }));
48
+ }
49
+ async function ensureNpmAuth() {
50
+ try {
51
+ await run('pnpm', ['whoami']);
52
+ }
53
+ catch {
54
+ throw new Error('Not authenticated to registry. Run "pnpm login" and retry.');
55
+ }
56
+ }
57
+ function ensureNotPrivate(targets) {
58
+ const privates = targets.filter((p) => p.json?.private);
59
+ if (privates.length) {
60
+ const names = privates.map((p) => p.name).join(', ');
61
+ throw new Error(`Refusing to publish private packages: ${names}`);
62
+ }
63
+ }
64
+ async function publishPackage(pkg, opts = {}) {
65
+ const args = ['publish', '--access', 'public'];
66
+ let tag = opts.tag;
67
+ if (!tag) {
68
+ tag = inferTagFromVersion(pkg.version || '');
69
+ }
70
+ if (tag)
71
+ args.push('--tag', tag);
72
+ if (opts.dryRun)
73
+ args.push('--dry-run');
74
+ if (opts.provenance)
75
+ args.push('--provenance');
76
+ logPkg(pkg, `Publishing ${pkg.name}…`, colors.green);
77
+ try {
78
+ await run('pnpm', args, { cwd: pkg.path });
79
+ logPkg(pkg, `${pkg.name} published.`, colors.green);
80
+ const tagName = `${pkg.name}@${pkg.version}`;
81
+ const tagMessage = opts.releaseNotes
82
+ ? `release ${pkg.version}\n\n${opts.releaseNotes}`
83
+ : `release ${pkg.version}`;
84
+ await run('git', ['tag', '-a', tagName, '-m', tagMessage]);
85
+ await run('git', ['push', 'origin', tagName]);
86
+ }
87
+ catch (err) {
88
+ console.error(colors.red(`Publish failed for ${pkg.name}.`));
89
+ console.error(colors.dim('To revert the version commit: git revert HEAD && git push'));
90
+ throw err;
91
+ }
92
+ }
93
+ async function applyVersionStrategy(strategy, targetPkg, packages, opts = {}) {
94
+ if (strategy.mode === 'noop') {
95
+ console.log('\nSkipping version bump (no version change selected).');
96
+ return { changedPaths: [], commitMessage: '' };
97
+ }
98
+ if (strategy.mode === 'sync') {
99
+ console.log();
100
+ logGlobal('Syncing all package versions…', colors.cyan);
101
+ await run('pnpm', ['sync', strategy.version]);
102
+ packages.forEach((pkg) => {
103
+ pkg.version = strategy.version;
104
+ pkg.json.version = strategy.version;
105
+ });
106
+ return {
107
+ changedPaths: [
108
+ 'package.json',
109
+ ...packages.map((pkg) => path.relative(process.cwd(), pkg.packageJsonPath)),
110
+ ],
111
+ commitMessage: `chore(release): sync ${strategy.version}`,
112
+ };
113
+ }
114
+ // mode === 'bump'
115
+ const nextVersion = bumpVersion(targetPkg.version, strategy.bumpType, strategy.preid);
116
+ console.log(`\nUpdating ${formatPkgName(targetPkg)} version to ${colors.green(nextVersion)}`);
117
+ targetPkg.json.version = nextVersion;
118
+ targetPkg.version = nextVersion;
119
+ await writePackageJson(targetPkg.packageJsonPath, targetPkg.json);
120
+ return {
121
+ changedPaths: [path.relative(process.cwd(), targetPkg.packageJsonPath)],
122
+ commitMessage: opts?.releaseNotes
123
+ ? `chore(release): ${targetPkg.name}@${nextVersion} - ${opts.releaseNotes}`
124
+ : `chore(release): ${targetPkg.name}@${nextVersion}`,
125
+ };
126
+ }
127
+ function strategyFromOptions(targetPkg, selection, opts) {
128
+ if (!opts?.nonInteractive)
129
+ return undefined;
130
+ if (opts.syncVersion) {
131
+ if (!isSemver(opts.syncVersion))
132
+ throw new Error(`Invalid version "${opts.syncVersion}"`);
133
+ return { mode: 'sync', version: opts.syncVersion };
134
+ }
135
+ if (opts.bumpType)
136
+ return { mode: 'bump', bumpType: opts.bumpType, preid: opts.preid };
137
+ return { mode: 'noop' };
138
+ }
139
+ function inferTagFromVersion(version) {
140
+ const match = version.match(/-([0-9A-Za-z-]+)(?:\.\d+)?$/);
141
+ if (!match)
142
+ return undefined;
143
+ const preid = match[1].toLowerCase();
144
+ if (['alpha', 'beta', 'rc'].includes(preid))
145
+ return preid;
146
+ return 'next';
147
+ }
148
+ function inferPreidFromVersion(version) {
149
+ const match = version.match(/-([0-9A-Za-z-]+)/);
150
+ return match?.[1]?.toLowerCase();
151
+ }
152
+ export async function promptVersionStrategy(targetPkg, selection, opts) {
153
+ const viaOpts = strategyFromOptions(targetPkg, selection, opts);
154
+ if (viaOpts)
155
+ return viaOpts;
156
+ console.log('\nCurrent package versions:');
157
+ for (const pkg of selection) {
158
+ const marker = selection.length === 1 && pkg.dirName === targetPkg.dirName ? '*' : '•';
159
+ console.log(` ${marker} ${formatPkgName(pkg)} ${pkg.dirName} ${colors.yellow(pkg.version)}`);
160
+ }
161
+ console.log('\nSelect version strategy:');
162
+ console.log(' 1) Patch');
163
+ console.log(' 2) Minor');
164
+ console.log(' 3) Major');
165
+ console.log(' 4) Pre-release (alpha / beta / rc)');
166
+ console.log(' 5) Sync packages');
167
+ console.log(' 6) No version change');
168
+ const choice = await promptSingleKey('Choice: ', (key) => {
169
+ if (['1', '2', '3', '4', '5', '6'].includes(key))
170
+ return key;
171
+ return undefined;
172
+ });
173
+ console.log(choice);
174
+ // Sync
175
+ if (choice === '5') {
176
+ const desired = await askLine('Enter unified version: ');
177
+ if (!isSemver(desired))
178
+ throw new Error(`Invalid version "${desired}"`);
179
+ return { mode: 'sync', version: desired };
180
+ }
181
+ // No version change
182
+ if (choice === '6') {
183
+ const ans = await promptYesNoAll('Publish without changing any versions?');
184
+ if (ans === 'no') {
185
+ console.log('Aborting.');
186
+ process.exit(0);
187
+ }
188
+ return { mode: 'noop' };
189
+ }
190
+ // Pre-release submenu
191
+ let preid;
192
+ let bumpType;
193
+ if (choice === '4') {
194
+ const currentVersion = targetPkg.version || '?';
195
+ const defaultPreid = inferPreidFromVersion(currentVersion) || 'alpha';
196
+ const preview = (type) => {
197
+ try {
198
+ return bumpVersion(currentVersion, type, defaultPreid);
199
+ }
200
+ catch {
201
+ return '?';
202
+ }
203
+ };
204
+ console.log('\nSelect pre-release bump type:');
205
+ console.log(` 1) prepatch (${currentVersion} -> ${preview('prepatch')})`);
206
+ console.log(` 2) preminor (${currentVersion} -> ${preview('preminor')})`);
207
+ console.log(` 3) premajor (${currentVersion} -> ${preview('premajor')})`);
208
+ console.log(` 4) prerelease (${currentVersion} -> ${preview('prerelease')})`);
209
+ const preChoice = await promptSingleKey('Choice: ', (key) => {
210
+ if (['1', '2', '3', '4'].includes(key))
211
+ return key;
212
+ return undefined;
213
+ });
214
+ console.log(preChoice);
215
+ bumpType = preBumpFromChoice(preChoice);
216
+ if (!bumpType)
217
+ throw new Error('Invalid pre-release selection.');
218
+ const rawPreid = await askLine('Pre-release identifier (e.g. alpha, beta, rc) [alpha]: ');
219
+ preid = (rawPreid.trim() || 'alpha').toLowerCase();
220
+ }
221
+ else {
222
+ // Regular patch / minor / major
223
+ bumpType = bumpMap[choice];
224
+ }
225
+ if (!bumpType)
226
+ throw new Error('Invalid selection.');
227
+ // Preview what will happen
228
+ if (selection.length === 1) {
229
+ const proposed = bumpVersion(targetPkg.version, bumpType, preid);
230
+ console.log(`\nTarget package: ${formatPkgName(targetPkg)} -> ${colors.green(proposed)}`);
231
+ const others = selection.filter((pkg) => pkg.dirName !== targetPkg.dirName);
232
+ if (others.length) {
233
+ console.log('Other packages:');
234
+ others.forEach((pkg) => {
235
+ console.log(` - ${formatPkgName(pkg)} ${colors.yellow(pkg.version)}`);
236
+ });
237
+ }
238
+ }
239
+ else {
240
+ console.log(`\nApplying a ${bumpType}${preid ? ` (${preid})` : ''} bump to all selected packages:`);
241
+ selection.forEach((pkg) => {
242
+ const nextVersion = bumpVersion(pkg.version, bumpType, preid);
243
+ const arrow = nextVersion === pkg.version ? '─' : '→';
244
+ console.log(` - ${formatPkgName(pkg)} ${colors.yellow(pkg.version)} ${arrow} ${colors.green(nextVersion)}`);
245
+ });
246
+ }
247
+ const confirm = await promptYesNoAll(selection.length === 1
248
+ ? `Apply ${bumpType}${preid ? ` (${preid})` : ''} bump to ${targetPkg.name}?`
249
+ : `Apply ${bumpType}${preid ? ` (${preid})` : ''} bump to all selected packages?`);
250
+ if (confirm === 'no') {
251
+ console.log('Aborting.');
252
+ process.exit(0);
253
+ }
254
+ return { mode: 'bump', bumpType, preid };
255
+ }
256
+ export async function releaseSingle(targetPkg, packages, opts = {}) {
257
+ await ensureNpmAuth();
258
+ await refreshPackagesFromDisk([targetPkg]);
259
+ ensureNotPrivate([targetPkg]);
260
+ console.log(`\n${formatPkgName(targetPkg)} ${colors.dim('(single release)')}`);
261
+ const strategy = await promptVersionStrategy(targetPkg, [targetPkg], opts);
262
+ const { changedPaths, commitMessage } = await applyVersionStrategy(strategy, targetPkg, packages, opts);
263
+ await stageCommitPush(changedPaths, commitMessage);
264
+ await publishPackage(targetPkg, opts);
265
+ }
266
+ export async function releaseMultiple(targets, packages, opts = {}) {
267
+ await ensureNpmAuth();
268
+ await refreshPackagesFromDisk(targets);
269
+ ensureNotPrivate(targets);
270
+ console.log(`\n🚀 ${'*'} Publishing all selected packages`);
271
+ const strategy = await promptVersionStrategy(targets[0], targets, opts);
272
+ let changedPaths = [];
273
+ let commitMessage = '';
274
+ if (strategy.mode === 'sync') {
275
+ const result = await applyVersionStrategy(strategy, targets[0], packages, opts);
276
+ changedPaths = result.changedPaths;
277
+ commitMessage = result.commitMessage;
278
+ }
279
+ else if (strategy.mode === 'bump') {
280
+ for (const pkg of targets) {
281
+ const result = await applyVersionStrategy(strategy, pkg, packages, opts);
282
+ changedPaths.push(...result.changedPaths);
283
+ }
284
+ commitMessage = `chore(release): ${targets.map((p) => `${p.name}@${p.version}`).join(', ')}`;
285
+ }
286
+ if (changedPaths.length || commitMessage) {
287
+ await stageCommitPush(changedPaths, commitMessage || `chore(release): publish ${targets.length} package(s)`);
288
+ }
289
+ for (const pkg of targets) {
290
+ await publishPackage(pkg, opts);
291
+ }
292
+ }
package/dist/semver.js ADDED
@@ -0,0 +1,17 @@
1
+ // src/semver.js
2
+ import { inc, valid, } from 'semver';
3
+ export function bumpVersion(version, type, preid) {
4
+ let next;
5
+ if (preid) {
6
+ next = inc(version, type, preid);
7
+ }
8
+ else {
9
+ next = inc(version, type);
10
+ }
11
+ if (!next)
12
+ throw new Error(`Cannot bump invalid version "${version}" by "${type}"`);
13
+ return next;
14
+ }
15
+ export function isSemver(version) {
16
+ return Boolean(valid(version));
17
+ }
@@ -0,0 +1,11 @@
1
+ // src/utils/colors.js
2
+ const ansi = (code) => (text) => `\x1b[${code}m${text}\x1b[0m`;
3
+ export const colors = {
4
+ cyan: ansi(36),
5
+ green: ansi(32),
6
+ yellow: ansi(33),
7
+ magenta: ansi(35),
8
+ red: ansi(31),
9
+ bold: ansi(1),
10
+ dim: ansi(2),
11
+ };
@@ -0,0 +1,19 @@
1
+ // src/utils/log.js
2
+ import { colors } from './colors.js';
3
+ export const defaultPackageColor = 'cyan';
4
+ export const globalEmoji = '🚀';
5
+ export const formatPkgName = (pkg) => {
6
+ const primary = pkg.substitute;
7
+ const displayName = pkg.name ?? pkg.dirName;
8
+ const fileLabel = pkg.dirName;
9
+ const colorizer = colors[pkg.color ?? defaultPackageColor];
10
+ return `${colorizer(colors.bold(primary))} ${colors.dim(`(${displayName}) [${fileLabel}]`)}`;
11
+ };
12
+ export const logPkg = (pkg, message, colorizer = colors.cyan) => {
13
+ const label = colors[pkg.color ?? defaultPackageColor](pkg.substitute);
14
+ console.log(`${label} ${colorizer(message)}`);
15
+ };
16
+ export const logGlobal = (message, colorizer = colors.magenta) => {
17
+ console.log(`${globalEmoji} ${colorizer(message)}`);
18
+ };
19
+ export { colors };
@@ -0,0 +1,21 @@
1
+ // src/utils/run.js
2
+ import { spawn } from 'node:child_process';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ export const rootDir = path.resolve(path.dirname(__filename), '..', '..');
7
+ export function run(command, args, options = {}) {
8
+ return new Promise((resolve, reject) => {
9
+ const child = spawn(command, args, {
10
+ cwd: rootDir,
11
+ stdio: 'inherit',
12
+ ...options,
13
+ });
14
+ child.on('close', (code) => {
15
+ if (code === 0)
16
+ resolve();
17
+ else
18
+ reject(new Error(`Command "${command} ${args.join(' ')}" exited with ${code}`));
19
+ });
20
+ });
21
+ }
@@ -0,0 +1,246 @@
1
+ // src/workspace.js
2
+ import { spawnSync } from 'node:child_process';
3
+ import { run, rootDir } from './utils/run.js';
4
+ import { logGlobal, logPkg, colors } from './utils/log.js';
5
+ import { collectGitStatus, gitAdd, gitCommit } from './git.js';
6
+ import { askLine, promptSingleKey } from './prompts.js';
7
+ const dependencyFiles = new Set([
8
+ 'package.json',
9
+ 'pnpm-lock.yaml',
10
+ 'package-lock.json',
11
+ 'npm-shrinkwrap.json',
12
+ 'pnpm-workspace.yaml',
13
+ ]);
14
+ function extractPathFromStatus(line) {
15
+ const match = line.match(/^[AMDR\? ][\S\?]\s+(.*)$/);
16
+ if (!match)
17
+ return undefined;
18
+ const rawPath = match[1].trim().replace(/"/g, '');
19
+ const normalized = rawPath.includes(' -> ')
20
+ ? rawPath.split(' -> ').pop()
21
+ : rawPath;
22
+ return normalized;
23
+ }
24
+ function dependencyPathsFromStatus(status) {
25
+ return status
26
+ .map(extractPathFromStatus)
27
+ .filter((p) => Boolean(p && dependencyFiles.has(p.split('/').pop() ?? '')));
28
+ }
29
+ function formatPkgLabel(pkg) {
30
+ return pkg.substitute ?? pkg.name ?? pkg.dirName;
31
+ }
32
+ function logDependencyChanges(paths, targets) {
33
+ if (paths.length === 0)
34
+ return;
35
+ const byDir = new Map(targets.map((t) => [t.dirName, formatPkgLabel(t)]));
36
+ logGlobal('Dependency file changes detected:', colors.cyan);
37
+ paths.forEach((p) => {
38
+ const parts = p.split('/');
39
+ const file = parts[parts.length - 1];
40
+ const pkgIndex = parts.indexOf('packages');
41
+ if (pkgIndex !== -1 && parts[pkgIndex + 1]) {
42
+ const dir = parts[pkgIndex + 1];
43
+ const fileLabel = parts.slice(pkgIndex + 2).join('/') || 'package.json';
44
+ console.log(` • ${colors.dim(`${byDir.get(dir) ?? dir} (${fileLabel})`)}`);
45
+ return;
46
+ }
47
+ if (file === 'pnpm-lock.yaml' ||
48
+ file === 'package-lock.json' ||
49
+ file === 'npm-shrinkwrap.json') {
50
+ console.log(` • ${colors.dim(`workspace lockfile (${file})`)}`);
51
+ return;
52
+ }
53
+ if (file === 'pnpm-workspace.yaml') {
54
+ console.log(` • ${colors.dim('workspace pnpm-workspace.yaml')}`);
55
+ return;
56
+ }
57
+ if (file === 'package.json') {
58
+ console.log(` • ${colors.dim('workspace package.json')}`);
59
+ return;
60
+ }
61
+ console.log(` • ${colors.dim(p)}`);
62
+ });
63
+ }
64
+ function readDiff(path) {
65
+ const res = spawnSync('git', ['diff', '--unified=0', '--', path], {
66
+ cwd: rootDir,
67
+ encoding: 'utf8',
68
+ });
69
+ if (typeof res.stdout === 'string')
70
+ return res.stdout;
71
+ return '';
72
+ }
73
+ function parseVersionChanges(path, label) {
74
+ const diff = readDiff(path);
75
+ if (!diff)
76
+ return [];
77
+ const changes = new Map();
78
+ const lineRe = /^[\-\+]\s+"([^"]+)":\s*"([^"]+)"/;
79
+ diff.split('\n').forEach((line) => {
80
+ if (line.startsWith('+++') || line.startsWith('---'))
81
+ return;
82
+ const match = line.match(lineRe);
83
+ if (!match)
84
+ return;
85
+ const [, dep, version] = match;
86
+ const entry = changes.get(dep) ?? {};
87
+ if (line.startsWith('-'))
88
+ entry.from = version;
89
+ if (line.startsWith('+'))
90
+ entry.to = version;
91
+ changes.set(dep, entry);
92
+ });
93
+ return [...changes.entries()]
94
+ .filter(([, v]) => v.from && v.to && v.from !== v.to)
95
+ .map(([dep, v]) => ({
96
+ dep,
97
+ from: v.from,
98
+ to: v.to,
99
+ label,
100
+ }));
101
+ }
102
+ function summarizeVersionChanges(paths, targets) {
103
+ const byDir = new Map(targets.map((t) => [t.dirName, formatPkgLabel(t)]));
104
+ const changes = [];
105
+ paths
106
+ .filter((p) => p.endsWith('package.json'))
107
+ .forEach((p) => {
108
+ const parts = p.split('/');
109
+ const pkgIndex = parts.indexOf('packages');
110
+ const label = pkgIndex !== -1 && parts[pkgIndex + 1]
111
+ ? (byDir.get(parts[pkgIndex + 1]) ?? parts[pkgIndex + 1])
112
+ : 'workspace';
113
+ changes.push(...parseVersionChanges(p, label));
114
+ });
115
+ if (changes.length === 0)
116
+ return '';
117
+ const grouped = new Map();
118
+ changes.forEach((c) => {
119
+ const list = grouped.get(c.label) ?? [];
120
+ list.push(c);
121
+ grouped.set(c.label, list);
122
+ });
123
+ const summaries = [];
124
+ grouped.forEach((list, label) => {
125
+ const slice = list.slice(0, 3).map((c) => `${c.dep} ${c.from}→${c.to}`);
126
+ const extra = list.length > slice.length ? ` (+${list.length - slice.length} more)` : '';
127
+ summaries.push(`${label}: ${slice.join(', ')}${extra}`);
128
+ });
129
+ return summaries.join('; ');
130
+ }
131
+ function buildUpdateCommitMessage(paths, targets) {
132
+ const changeSummary = summarizeVersionChanges(paths, targets);
133
+ if (changeSummary)
134
+ return `chore(deps): ${changeSummary}`;
135
+ const labels = new Set();
136
+ const byDir = new Map(targets.map((t) => [t.dirName, formatPkgLabel(t)]));
137
+ paths.forEach((p) => {
138
+ const parts = p.split('/');
139
+ const pkgIndex = parts.indexOf('packages');
140
+ if (pkgIndex !== -1 && parts[pkgIndex + 1]) {
141
+ const dir = parts[pkgIndex + 1];
142
+ labels.add(byDir.get(dir) ?? dir);
143
+ return;
144
+ }
145
+ if (parts[parts.length - 1] === 'package.json') {
146
+ labels.add('workspace deps');
147
+ }
148
+ });
149
+ if (labels.size === 0 && targets.length === 1) {
150
+ labels.add(formatPkgLabel(targets[0]));
151
+ }
152
+ const labelList = [...labels];
153
+ if (labelList.length === 0)
154
+ return 'chore(deps): update dependencies';
155
+ if (labelList.length === 1)
156
+ return `chore(deps): update ${labelList[0]}`;
157
+ return `chore(deps): update ${labelList.join(', ')}`;
158
+ }
159
+ async function promptCommitMessage(proposed) {
160
+ const confirm = await promptSingleKey(`Use commit message "${proposed}"? (y/n): `, (key) => {
161
+ if (key === 'y')
162
+ return 'yes';
163
+ if (key === 'n')
164
+ return 'no';
165
+ return undefined;
166
+ });
167
+ if (confirm === 'yes')
168
+ return proposed;
169
+ let custom = '';
170
+ while (!custom.trim()) {
171
+ // eslint-disable-next-line no-await-in-loop
172
+ custom = await askLine('Enter commit message: ');
173
+ if (!custom.trim())
174
+ console.log(colors.red('Commit message cannot be empty.'));
175
+ }
176
+ return custom.trim();
177
+ }
178
+ export async function runCleanInstall() {
179
+ logGlobal('Cleaning workspace…', colors.cyan);
180
+ await run('pnpm', ['run', 'clean']);
181
+ logGlobal('Reinstalling dependencies…', colors.cyan);
182
+ await run('pnpm', ['install']);
183
+ }
184
+ export async function updateDependencies(targets) {
185
+ const preStatus = await collectGitStatus();
186
+ if (targets.length === 1) {
187
+ const filterArg = targets[0].name ?? `./packages/${targets[0].dirName}`;
188
+ logPkg(targets[0], `Updating dependencies…`);
189
+ await run('pnpm', ['-r', '--filter', filterArg, 'update']);
190
+ }
191
+ else {
192
+ logGlobal('Updating dependencies across the workspace…', colors.cyan);
193
+ await run('pnpm', ['-r', 'update']);
194
+ }
195
+ const postStatus = await collectGitStatus();
196
+ const depPaths = dependencyPathsFromStatus(postStatus);
197
+ const uniqueDepPaths = [...new Set(depPaths)];
198
+ if (uniqueDepPaths.length === 0) {
199
+ logGlobal('No dependency file changes detected; skipping commit.', colors.dim);
200
+ return;
201
+ }
202
+ if (preStatus.length) {
203
+ logGlobal('Working tree had changes before update; will only stage dependency files for the commit.', colors.yellow);
204
+ }
205
+ logDependencyChanges(uniqueDepPaths, targets);
206
+ const proposed = buildUpdateCommitMessage(uniqueDepPaths, targets);
207
+ const message = await promptCommitMessage(proposed);
208
+ logGlobal('Staging dependency changes…', colors.cyan);
209
+ await gitAdd(uniqueDepPaths);
210
+ logGlobal('Creating commit…', colors.cyan);
211
+ await gitCommit(message);
212
+ logGlobal(`Commit created: ${message}`, colors.green);
213
+ logGlobal('Pushing to origin…', colors.cyan);
214
+ await run('git', ['push']);
215
+ logGlobal('Push complete.', colors.green);
216
+ }
217
+ export async function typecheckAll() {
218
+ logGlobal('Running typecheck for all packages…', colors.cyan);
219
+ await run('pnpm', ['typecheck']);
220
+ }
221
+ export async function typecheckSingle(pkg) {
222
+ const filterArg = pkg.name ?? `./packages/${pkg.dirName}`;
223
+ logPkg(pkg, `Running typecheck…`);
224
+ await run('pnpm', ['run', '--filter', filterArg, 'typecheck']);
225
+ }
226
+ export async function buildAll() {
227
+ logGlobal('Running build for all packages…', colors.cyan);
228
+ await run('pnpm', ['build']);
229
+ }
230
+ export async function buildSingle(pkg) {
231
+ const filterArg = pkg.name ?? `./packages/${pkg.dirName}`;
232
+ logPkg(pkg, `Running build…`);
233
+ await run('pnpm', ['run', '--filter', filterArg, 'build']);
234
+ }
235
+ export async function buildPackageLocally(pkg) {
236
+ logPkg(pkg, 'Building local dist before publish…');
237
+ await run('pnpm', ['run', 'build'], { cwd: pkg.path });
238
+ }
239
+ export async function testAll() {
240
+ logGlobal('Running tests for all packages…', colors.cyan);
241
+ await run('pnpm', ['test']);
242
+ }
243
+ export async function testSingle(pkg) {
244
+ logPkg(pkg, `Running tests…`);
245
+ await run('pnpm', ['test', '--', `packages/${pkg.dirName}`]);
246
+ }
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@emeryld/manager",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Interactive manager for pnpm monorepos (update/test/build/publish).",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {
8
- "manager-cli": "bin/manager-cli.js"
8
+ "manager-cli": "dist/manager-cli.js"
9
9
  },
10
10
  "files": [
11
- "src",
11
+ "dist",
12
12
  "bin",
13
13
  "tsconfig.base.json",
14
14
  "tsconfig.json"
@@ -39,7 +39,7 @@
39
39
  "swc": false
40
40
  },
41
41
  "scripts": {
42
- "build": "tsc -p tsconfig.base.json",
42
+ "build": "tsc -p tsconfig.base.json && node scripts/copy-manager-cli.mjs",
43
43
  "typecheck": "tsc -p tsconfig.base.json --noEmit",
44
44
  "test": "cross-env TS_NODE_PROJECT=tsconfig.test.json node --loader ts-node/esm test/helper-cli.test.ts"
45
45
  }