@emeryld/manager 0.6.3 → 0.6.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/dist/packages.js CHANGED
@@ -1,161 +1,17 @@
1
1
  // src/packages.js
2
2
  import path from 'node:path';
3
- import { pathToFileURL } from 'node:url';
4
- import { readdir, readFile, stat } from 'node:fs/promises';
3
+ import { readFile, stat } from 'node:fs/promises';
4
+ import { colorFromSeed, deriveSubstitute, inferManifestFromWorkspace, loadWorkspaceManifest, mergeManifestEntries, normalizeManifestPath, } from './packages/manifest-utils.js';
5
5
  const rootDir = process.cwd();
6
- const ignoredDirs = new Set([
7
- 'node_modules',
8
- '.git',
9
- '.turbo',
10
- '.next',
11
- 'dist',
12
- 'build',
13
- '.cache',
14
- 'coverage',
15
- ]);
16
- const colorPalette = ['cyan', 'green', 'yellow', 'magenta', 'red'];
17
6
  let manifestState;
18
- function manifestFilePath() {
19
- return path.join(rootDir, 'scripts', 'packages.mjs');
20
- }
21
- function isManifestMissing(error) {
22
- if (typeof error !== 'object' || error === null)
23
- return false;
24
- const code = error.code;
25
- return code === 'ERR_MODULE_NOT_FOUND' || code === 'ENOENT';
26
- }
27
- function normalizeManifestPath(value) {
28
- const absolute = path.resolve(rootDir, value || '');
29
- let relative = path.relative(rootDir, absolute);
30
- if (!relative)
31
- return '.';
32
- relative = relative.replace(/\\/g, '/');
33
- return relative.replace(/^(?:\.\/)+/, '');
34
- }
35
- function colorFromSeed(seed) {
36
- const normalized = `${seed}`.trim() || 'package';
37
- let hash = 0;
38
- for (let i = 0; i < normalized.length; i++) {
39
- hash = (hash * 31 + normalized.charCodeAt(i)) >>> 0;
40
- }
41
- return colorPalette[hash % colorPalette.length];
42
- }
43
- function deriveSubstitute(name) {
44
- const trimmed = (name || '').trim();
45
- if (!trimmed)
46
- return '';
47
- const segments = trimmed.split(/[@\/\-]/).filter(Boolean);
48
- const transformed = segments
49
- .map((segment) => segment)
50
- .filter(Boolean)
51
- .join(' ');
52
- return transformed || trimmed;
53
- }
54
- async function findPackageJsonFiles(baseDir) {
55
- const results = new Set();
56
- const queue = [baseDir];
57
- while (queue.length) {
58
- const current = queue.shift();
59
- let entries;
60
- try {
61
- entries = await readdir(current, { withFileTypes: true });
62
- }
63
- catch {
64
- continue;
65
- }
66
- for (const entry of entries) {
67
- if (entry.isFile() && entry.name === 'package.json') {
68
- results.add(path.join(current, entry.name));
69
- }
70
- }
71
- for (const entry of entries) {
72
- if (!entry.isDirectory())
73
- continue;
74
- if (entry.isSymbolicLink())
75
- continue;
76
- if (ignoredDirs.has(entry.name))
77
- continue;
78
- queue.push(path.join(current, entry.name));
79
- }
80
- }
81
- return [...results].sort();
82
- }
83
- async function loadWorkspaceManifest() {
84
- const manifestPath = manifestFilePath();
85
- try {
86
- const manifestModule = await import(pathToFileURL(manifestPath).href);
87
- if (Array.isArray(manifestModule?.PACKAGE_MANIFEST)) {
88
- return manifestModule.PACKAGE_MANIFEST;
89
- }
90
- }
91
- catch (error) {
92
- if (isManifestMissing(error))
93
- return undefined;
94
- throw error;
95
- }
96
- return undefined;
97
- }
98
- async function inferManifestFromWorkspace() {
99
- const manifest = [];
100
- const pkgJsonPaths = await findPackageJsonFiles(rootDir);
101
- for (const pkgJsonPath of pkgJsonPaths) {
102
- try {
103
- const raw = await readFile(pkgJsonPath, 'utf8');
104
- const json = JSON.parse(raw);
105
- const pkgDir = path.dirname(pkgJsonPath);
106
- const pkgName = json.name?.trim() || path.basename(pkgDir) || 'package';
107
- manifest.push({
108
- name: pkgName,
109
- path: normalizeManifestPath(path.relative(rootDir, pkgDir)),
110
- color: colorFromSeed(pkgName),
111
- substitute: deriveSubstitute(pkgName),
112
- });
113
- }
114
- catch {
115
- continue;
116
- }
117
- }
118
- return manifest;
119
- }
120
- function mergeManifestEntries(inferred, overrides) {
121
- const normalizedOverrides = new Map();
122
- overrides?.forEach((entry) => {
123
- const normalized = normalizeManifestPath(entry.path);
124
- if (!normalized)
125
- return;
126
- normalizedOverrides.set(normalized, { ...entry, path: normalized });
127
- });
128
- const merged = [];
129
- for (const baseEntry of inferred) {
130
- const normalized = normalizeManifestPath(baseEntry.path);
131
- const override = normalizedOverrides.get(normalized);
132
- if (override) {
133
- normalizedOverrides.delete(normalized);
134
- const name = override.name || baseEntry.name;
135
- const color = override.color ?? baseEntry.color ?? colorFromSeed(name);
136
- const substitute = override.substitute ?? baseEntry.substitute ?? deriveSubstitute(name) ?? name;
137
- merged.push({ name, path: normalized, color, substitute });
138
- }
139
- else {
140
- merged.push({ ...baseEntry, path: normalized });
141
- }
142
- }
143
- normalizedOverrides.forEach((entry) => {
144
- const name = entry.name || path.basename(entry.path) || 'package';
145
- const color = entry.color ?? colorFromSeed(name);
146
- const substitute = entry.substitute ?? deriveSubstitute(name) ?? name;
147
- merged.push({ name, path: entry.path, color, substitute });
148
- });
149
- return merged;
150
- }
151
7
  async function ensureManifestState(forceReload = false) {
152
8
  if (manifestState && !forceReload)
153
9
  return manifestState;
154
10
  const [workspaceManifest, inferred] = await Promise.all([
155
- loadWorkspaceManifest(),
156
- inferManifestFromWorkspace(),
11
+ loadWorkspaceManifest(rootDir),
12
+ inferManifestFromWorkspace(rootDir),
157
13
  ]);
158
- const entries = mergeManifestEntries(inferred, workspaceManifest);
14
+ const entries = mergeManifestEntries(rootDir, inferred, workspaceManifest);
159
15
  const byName = new Map(entries.map((pkg) => [pkg.name.toLowerCase(), pkg]));
160
16
  const byPath = new Map(entries.map((pkg) => [pkg.path.toLowerCase(), pkg]));
161
17
  manifestState = {
@@ -180,7 +36,7 @@ export async function loadPackages() {
180
36
  const meta = byPath.get(relativePath.toLowerCase()) ??
181
37
  byName.get((pkgName ?? '').toLowerCase());
182
38
  const substitute = meta?.substitute ?? deriveSubstitute(pkgName) ?? path.basename(pkgDir);
183
- const color = meta?.color ?? colorFromSeed(pkgName);
39
+ const color = (meta?.color ?? colorFromSeed(pkgName));
184
40
  let dockerfilePath;
185
41
  try {
186
42
  const candidate = path.join(pkgDir, 'Dockerfile');
@@ -1,11 +1,2 @@
1
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
- };
2
+ export { colors } from '../colors-shared.js';
package/dist/workspace.js CHANGED
@@ -1,5 +1,7 @@
1
1
  // src/workspace.js
2
2
  import { spawnSync } from 'node:child_process';
3
+ import { rm } from 'node:fs/promises';
4
+ import path from 'node:path';
3
5
  import { run, rootDir } from './utils/run.js';
4
6
  import { logGlobal, logPkg, colors } from './utils/log.js';
5
7
  import { collectGitStatus, gitAdd, gitCommit } from './git.js';
@@ -191,12 +193,83 @@ function packageFilterArg(pkg) {
191
193
  return '.';
192
194
  return `./${pkg.relativeDir}`;
193
195
  }
196
+ async function runLifecycleScenario(pkg, scenario) {
197
+ if (pkg) {
198
+ logPkg(pkg, scenario.singleMessage);
199
+ await run('pnpm', scenario.singleArgs(pkg));
200
+ return;
201
+ }
202
+ logGlobal(scenario.allMessage, colors.cyan);
203
+ await run('pnpm', scenario.allArgs);
204
+ }
205
+ const TYPECHECK_SCENARIO = {
206
+ allArgs: ['typecheck'],
207
+ allMessage: 'Running typecheck for all packages…',
208
+ singleArgs: (pkg) => ['run', '--filter', packageFilterArg(pkg), 'typecheck'],
209
+ singleMessage: 'Running typecheck…',
210
+ };
211
+ const BUILD_SCENARIO = {
212
+ allArgs: ['build'],
213
+ allMessage: 'Running build for all packages…',
214
+ singleArgs: (pkg) => ['run', '--filter', packageFilterArg(pkg), 'build'],
215
+ singleMessage: 'Running build…',
216
+ };
217
+ const TEST_SCENARIO = {
218
+ allArgs: ['test'],
219
+ allMessage: 'Running tests for all packages…',
220
+ singleArgs: (pkg) => [
221
+ 'test',
222
+ '--',
223
+ pkg.relativeDir === '.' ? '.' : pkg.relativeDir,
224
+ ],
225
+ singleMessage: 'Running tests…',
226
+ };
194
227
  export async function runCleanInstall() {
195
228
  logGlobal('Cleaning workspace…', colors.cyan);
196
229
  await run('pnpm', ['run', 'clean']);
197
230
  logGlobal('Reinstalling dependencies…', colors.cyan);
198
231
  await run('pnpm', ['install']);
199
232
  }
233
+ const CLEAN_TARGETS = [
234
+ 'node_modules',
235
+ 'dist',
236
+ 'build',
237
+ '.turbo',
238
+ '.next',
239
+ '.expo',
240
+ '.parcel-cache',
241
+ '.cache',
242
+ 'coverage',
243
+ 'tmp',
244
+ '.tmp',
245
+ ];
246
+ async function removePath(target) {
247
+ try {
248
+ await rm(target, { recursive: true, force: true });
249
+ return true;
250
+ }
251
+ catch (error) {
252
+ console.warn(colors.yellow(` failed to remove ${target}: ${String(error)}`));
253
+ return false;
254
+ }
255
+ }
256
+ export async function cleanPackages(targets) {
257
+ for (const pkg of targets) {
258
+ logPkg(pkg, 'Cleaning build artifacts and caches…');
259
+ const results = await Promise.all(CLEAN_TARGETS.map(async (name) => {
260
+ const target = path.join(pkg.path, name);
261
+ const removed = await removePath(target);
262
+ return { name, removed };
263
+ }));
264
+ const removed = results.filter((r) => r.removed).map((r) => r.name);
265
+ if (removed.length === 0) {
266
+ console.log(colors.dim(' nothing to clean'));
267
+ }
268
+ else {
269
+ console.log(colors.dim(` removed ${removed.join(', ')}`));
270
+ }
271
+ }
272
+ }
200
273
  export async function updateDependencies(targets) {
201
274
  const preStatus = await collectGitStatus();
202
275
  if (targets.length === 1) {
@@ -231,32 +304,33 @@ export async function updateDependencies(targets) {
231
304
  logGlobal('Push complete.', colors.green);
232
305
  }
233
306
  export async function typecheckAll() {
234
- logGlobal('Running typecheck for all packages…', colors.cyan);
235
- await run('pnpm', ['typecheck']);
307
+ await runLifecycleScenario(undefined, TYPECHECK_SCENARIO);
236
308
  }
237
309
  export async function typecheckSingle(pkg) {
238
- const filterArg = packageFilterArg(pkg);
239
- logPkg(pkg, `Running typecheck…`);
240
- await run('pnpm', ['run', '--filter', filterArg, 'typecheck']);
310
+ await runLifecycleScenario(pkg, TYPECHECK_SCENARIO);
241
311
  }
242
312
  export async function buildAll() {
243
- logGlobal('Running build for all packages…', colors.cyan);
244
- await run('pnpm', ['build']);
313
+ await runLifecycleScenario(undefined, BUILD_SCENARIO);
245
314
  }
246
315
  export async function buildSingle(pkg) {
247
- const filterArg = packageFilterArg(pkg);
248
- logPkg(pkg, `Running build…`);
249
- await run('pnpm', ['run', '--filter', filterArg, 'build']);
316
+ await runLifecycleScenario(pkg, BUILD_SCENARIO);
317
+ }
318
+ export async function rebuildPackages(targets) {
319
+ await cleanPackages(targets);
320
+ if (targets.length === 1) {
321
+ await buildSingle(targets[0]);
322
+ }
323
+ else {
324
+ await buildAll();
325
+ }
250
326
  }
251
327
  export async function buildPackageLocally(pkg) {
252
328
  logPkg(pkg, 'Building local dist before publish…');
253
329
  await run('pnpm', ['run', 'build'], { cwd: pkg.path });
254
330
  }
255
331
  export async function testAll() {
256
- logGlobal('Running tests for all packages…', colors.cyan);
257
- await run('pnpm', ['test']);
332
+ await runLifecycleScenario(undefined, TEST_SCENARIO);
258
333
  }
259
334
  export async function testSingle(pkg) {
260
- logPkg(pkg, `Running tests…`);
261
- await run('pnpm', ['test', '--', pkg.relativeDir === '.' ? '.' : pkg.relativeDir]);
335
+ await runLifecycleScenario(pkg, TEST_SCENARIO);
262
336
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emeryld/manager",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
4
4
  "description": "Interactive manager for pnpm monorepos (update/test/build/publish).",
5
5
  "license": "MIT",
6
6
  "type": "module",