@edcalderon/versioning 1.4.6 → 1.4.7

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/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.4.7](https://github.com/edcalderon/my-second-brain/tree/main/packages/versioning) (2026-03-16)
9
+
10
+ ### Added
11
+ - ✨ New `workspace-scripts` extension (v1.0.0)
12
+ - `versioning scripts sync` — auto-generate `dev:all`, `build:all`, and per-app scripts in root package.json
13
+ - `versioning scripts list` — display current workspace script configuration
14
+ - `versioning scripts detect` — find new workspace apps not yet tracked in config
15
+ - `versioning scripts preview` — preview generated scripts without writing
16
+ - 🔄 `postSync` hook auto-detects new apps added to pnpm-workspace.yaml
17
+ - ⚙️ Config-driven via `extensionConfig.workspace-scripts` in versioning.config.json
18
+ - 🏃 Runner support: `concurrently` (default) or `turbo`
19
+ - 📦 Managed script tracking: safely adds/updates/removes only scripts it owns
20
+ - 🧪 Comprehensive test suite for workspace-scripts extension
21
+
8
22
  ## [1.4.6](https://github.com/edcalderon/my-second-brain/tree/main/packages/versioning) (2026-03-01)
9
23
 
10
24
  ### Added
package/README.md CHANGED
@@ -8,17 +8,19 @@ A comprehensive versioning and changelog management tool designed for monorepos
8
8
 
9
9
  ---
10
10
 
11
- ## 📋 Latest Changes (v1.4.6)
11
+ ## 📋 Latest Changes (v1.4.7)
12
12
 
13
13
  ### Added
14
- - 🔧 `readme-maintainer` extension (`versioning update-readme`) — auto-updates README with the latest CHANGELOG entry
15
- - 🎯 Uses `package.json` version as authoritative source; falls back to highest semver in CHANGELOG
16
- - 📂 Logs resolved paths for transparency
17
- - 📝 `update-readme` script added to all packages and apps
18
-
19
- ### Fixed
20
- - 🐛 Fixed README updater picking wrong version from malformed/misordered CHANGELOGs
21
- - 🐛 Fixed `@ed/auth` → `@edcalderon/auth` import in `apps/dashboard`
14
+ - New `workspace-scripts` extension (v1.0.0)
15
+ - `versioning scripts sync` auto-generate `dev:all`, `build:all`, and per-app scripts in root package.json
16
+ - `versioning scripts list` display current workspace script configuration
17
+ - `versioning scripts detect` find new workspace apps not yet tracked in config
18
+ - `versioning scripts preview` — preview generated scripts without writing
19
+ - 🔄 `postSync` hook auto-detects new apps added to pnpm-workspace.yaml
20
+ - ⚙️ Config-driven via `extensionConfig.workspace-scripts` in versioning.config.json
21
+ - 🏃 Runner support: `concurrently` (default) or `turbo`
22
+ - 📦 Managed script tracking: safely adds/updates/removes only scripts it owns
23
+ - 🧪 Comprehensive test suite for workspace-scripts extension
22
24
 
23
25
  For full version history, see [CHANGELOG.md](./CHANGELOG.md) and [GitHub releases](https://github.com/edcalderon/my-second-brain/releases)
24
26
 
@@ -0,0 +1,47 @@
1
+ import { VersioningExtension } from '../../extensions';
2
+ export interface WorkspaceAppConfig {
3
+ /** The pnpm filter-compatible package name (auto-resolved from app's package.json if omitted) */
4
+ filter?: string;
5
+ /** The npm script to run for dev mode. Default: "dev" */
6
+ command?: string;
7
+ /** Extra args appended to the dev command (e.g. "-p 3000" or "--port 3001") */
8
+ args?: string;
9
+ /** The npm script to run for build. Default: "build" */
10
+ buildCommand?: string;
11
+ /** Extra args appended to the build command */
12
+ buildArgs?: string;
13
+ /** Optional port number (used for display/documentation only) */
14
+ port?: number;
15
+ /** Whether to include this app in dev:all / build:all. Default: true */
16
+ enabled?: boolean;
17
+ }
18
+ export interface WorkspaceScriptsConfig {
19
+ /** Master switch. Default: true */
20
+ enabled: boolean;
21
+ /** Apps included in concurrent dev/build scripts, keyed by workspace-relative path */
22
+ apps: Record<string, WorkspaceAppConfig>;
23
+ /** Generate individual dev:<app> scripts for each app. Default: true */
24
+ individualScripts?: boolean;
25
+ /** Generate build:all and individual build:<app> scripts. Default: true */
26
+ buildScripts?: boolean;
27
+ /** The concurrency runner: "concurrently" or "turbo". Default: "concurrently" */
28
+ runner?: 'concurrently' | 'turbo';
29
+ /** Auto-detect new apps in the workspace and prompt. Default: true */
30
+ autoDetect?: boolean;
31
+ }
32
+ export interface GeneratedScripts {
33
+ /** The full map of script-name → command to write into root package.json */
34
+ scripts: Record<string, string>;
35
+ /** Human-readable summary of what was generated */
36
+ summary: string[];
37
+ }
38
+ export declare function generateWorkspaceScripts(rootDir: string, wsCfg: WorkspaceScriptsConfig): Promise<GeneratedScripts>;
39
+ export declare function syncScriptsToPackageJson(rootDir: string, generated: GeneratedScripts, dryRun?: boolean): Promise<{
40
+ added: string[];
41
+ updated: string[];
42
+ removed: string[];
43
+ }>;
44
+ export declare function detectNewApps(rootDir: string, wsCfg: WorkspaceScriptsConfig): Promise<string[]>;
45
+ declare const extension: VersioningExtension;
46
+ export default extension;
47
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,507 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.generateWorkspaceScripts = generateWorkspaceScripts;
37
+ exports.syncScriptsToPackageJson = syncScriptsToPackageJson;
38
+ exports.detectNewApps = detectNewApps;
39
+ const fs = __importStar(require("fs-extra"));
40
+ const path = __importStar(require("path"));
41
+ // ─────────────────────────────────────────────────────────────────
42
+ // EXTENSION METADATA
43
+ // ─────────────────────────────────────────────────────────────────
44
+ const EXTENSION_NAME = 'workspace-scripts';
45
+ const EXTENSION_VERSION = '1.0.0';
46
+ // ─────────────────────────────────────────────────────────────────
47
+ // MANAGED SCRIPT MARKER
48
+ // ─────────────────────────────────────────────────────────────────
49
+ /**
50
+ * All scripts generated by this extension are prefixed with one of these keys.
51
+ * During sync we only touch scripts whose key starts with a managed prefix.
52
+ */
53
+ const MANAGED_PREFIXES = ['dev', 'build'];
54
+ // A hidden marker script key so we know which scripts we own.
55
+ const MARKER_KEY = '__workspace_scripts_managed';
56
+ // ─────────────────────────────────────────────────────────────────
57
+ // HELPERS
58
+ // ─────────────────────────────────────────────────────────────────
59
+ function loadWorkspaceScriptsConfig(config) {
60
+ const wsCfg = config?.extensionConfig?.['workspace-scripts'];
61
+ if (!wsCfg || wsCfg.enabled === false)
62
+ return null;
63
+ return {
64
+ enabled: true,
65
+ apps: wsCfg.apps || {},
66
+ individualScripts: wsCfg.individualScripts !== false,
67
+ buildScripts: wsCfg.buildScripts !== false,
68
+ runner: wsCfg.runner || 'concurrently',
69
+ autoDetect: wsCfg.autoDetect !== false,
70
+ };
71
+ }
72
+ /** Derive a short name from a workspace path like "apps/dashboard" → "dashboard" */
73
+ function shortName(wsPath) {
74
+ const parts = wsPath.split('/');
75
+ return parts[parts.length - 1];
76
+ }
77
+ /**
78
+ * Read the package name from a workspace app's package.json.
79
+ * Falls back to deriving from path if package.json doesn't exist.
80
+ */
81
+ async function resolveFilter(rootDir, wsPath, appCfg) {
82
+ if (appCfg.filter)
83
+ return appCfg.filter;
84
+ const pkgPath = path.join(rootDir, wsPath, 'package.json');
85
+ if (await fs.pathExists(pkgPath)) {
86
+ try {
87
+ const pkg = await fs.readJson(pkgPath);
88
+ if (pkg.name)
89
+ return pkg.name;
90
+ }
91
+ catch {
92
+ // fall through
93
+ }
94
+ }
95
+ // Fallback: use the directory name
96
+ return shortName(wsPath);
97
+ }
98
+ /** Build a single pnpm --filter command string */
99
+ function buildFilterCmd(filter, command, args) {
100
+ let cmd = `pnpm --filter ${filter} ${command}`;
101
+ if (args)
102
+ cmd += ` ${args}`;
103
+ return cmd;
104
+ }
105
+ async function generateWorkspaceScripts(rootDir, wsCfg) {
106
+ const scripts = {};
107
+ const summary = [];
108
+ const enabledApps = Object.entries(wsCfg.apps).filter(([, cfg]) => cfg.enabled !== false);
109
+ if (enabledApps.length === 0) {
110
+ summary.push('No enabled apps found in workspace-scripts config');
111
+ return { scripts, summary };
112
+ }
113
+ // ── Dev scripts ───────────────────────────────────────────────
114
+ const devCmds = [];
115
+ for (const [wsPath, appCfg] of enabledApps) {
116
+ const filter = await resolveFilter(rootDir, wsPath, appCfg);
117
+ const cmd = appCfg.command || 'dev';
118
+ const filterCmd = buildFilterCmd(filter, cmd, appCfg.args);
119
+ devCmds.push(filterCmd);
120
+ // Individual dev:<name> script
121
+ if (wsCfg.individualScripts) {
122
+ const name = shortName(wsPath);
123
+ scripts[`dev:${name}`] = filterCmd;
124
+ summary.push(` dev:${name} → ${filterCmd}`);
125
+ }
126
+ }
127
+ // dev:all — concurrent execution
128
+ if (devCmds.length > 0) {
129
+ if (wsCfg.runner === 'turbo') {
130
+ scripts['dev:all'] = 'turbo run dev';
131
+ summary.push(` dev:all → turbo run dev`);
132
+ }
133
+ else {
134
+ const quoted = devCmds.map((c) => `"${c}"`).join(' ');
135
+ scripts['dev:all'] = `concurrently ${quoted}`;
136
+ summary.push(` dev:all → concurrently (${devCmds.length} apps)`);
137
+ }
138
+ }
139
+ // Primary dev → first app (convenience shortcut)
140
+ if (devCmds.length > 0) {
141
+ scripts['dev'] = devCmds[0];
142
+ summary.push(` dev → ${devCmds[0]}`);
143
+ }
144
+ // ── Build scripts ─────────────────────────────────────────────
145
+ if (wsCfg.buildScripts) {
146
+ const buildCmds = [];
147
+ for (const [wsPath, appCfg] of enabledApps) {
148
+ const filter = await resolveFilter(rootDir, wsPath, appCfg);
149
+ const cmd = appCfg.buildCommand || 'build';
150
+ const filterCmd = buildFilterCmd(filter, cmd, appCfg.buildArgs);
151
+ buildCmds.push(filterCmd);
152
+ if (wsCfg.individualScripts) {
153
+ const name = shortName(wsPath);
154
+ scripts[`build:${name}`] = filterCmd;
155
+ summary.push(` build:${name} → ${filterCmd}`);
156
+ }
157
+ }
158
+ // build:all
159
+ if (buildCmds.length > 0) {
160
+ if (wsCfg.runner === 'turbo') {
161
+ scripts['build:all'] = 'turbo run build';
162
+ }
163
+ else {
164
+ scripts['build:all'] = 'pnpm --recursive build';
165
+ }
166
+ summary.push(` build:all → pnpm --recursive build`);
167
+ }
168
+ // Primary build → recursive
169
+ scripts['build'] = 'pnpm --recursive build';
170
+ summary.push(` build → pnpm --recursive build`);
171
+ }
172
+ return { scripts, summary };
173
+ }
174
+ // ─────────────────────────────────────────────────────────────────
175
+ // CORE: SYNC SCRIPTS INTO PACKAGE.JSON
176
+ // ─────────────────────────────────────────────────────────────────
177
+ async function syncScriptsToPackageJson(rootDir, generated, dryRun = false) {
178
+ const pkgPath = path.join(rootDir, 'package.json');
179
+ if (!(await fs.pathExists(pkgPath))) {
180
+ throw new Error(`Root package.json not found at ${pkgPath}`);
181
+ }
182
+ const pkg = (await fs.readJson(pkgPath)) || {};
183
+ if (!pkg.scripts)
184
+ pkg.scripts = {};
185
+ const added = [];
186
+ const updated = [];
187
+ const removed = [];
188
+ // Identify currently managed scripts (from a previous sync)
189
+ const previouslyManaged = new Set();
190
+ if (pkg.scripts[MARKER_KEY]) {
191
+ try {
192
+ const managed = JSON.parse(pkg.scripts[MARKER_KEY]);
193
+ managed.forEach((k) => previouslyManaged.add(k));
194
+ }
195
+ catch {
196
+ // corrupted marker, ignore
197
+ }
198
+ }
199
+ // Remove scripts that we previously managed but are no longer generated
200
+ for (const key of previouslyManaged) {
201
+ if (!(key in generated.scripts)) {
202
+ if (pkg.scripts[key] !== undefined) {
203
+ if (!dryRun)
204
+ delete pkg.scripts[key];
205
+ removed.push(key);
206
+ }
207
+ }
208
+ }
209
+ // Add/update generated scripts
210
+ for (const [key, value] of Object.entries(generated.scripts)) {
211
+ if (pkg.scripts[key] === undefined) {
212
+ added.push(key);
213
+ }
214
+ else if (pkg.scripts[key] !== value) {
215
+ updated.push(key);
216
+ }
217
+ if (!dryRun)
218
+ pkg.scripts[key] = value;
219
+ }
220
+ // Update the marker with currently managed keys
221
+ if (!dryRun) {
222
+ pkg.scripts[MARKER_KEY] = JSON.stringify(Object.keys(generated.scripts));
223
+ }
224
+ if (!dryRun) {
225
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
226
+ }
227
+ return { added, updated, removed };
228
+ }
229
+ // ─────────────────────────────────────────────────────────────────
230
+ // CORE: DETECT NEW APPS IN WORKSPACE
231
+ // ─────────────────────────────────────────────────────────────────
232
+ async function detectNewApps(rootDir, wsCfg) {
233
+ const newApps = [];
234
+ // Read pnpm-workspace.yaml to find all workspace patterns
235
+ const workspaceYamlPath = path.join(rootDir, 'pnpm-workspace.yaml');
236
+ if (!(await fs.pathExists(workspaceYamlPath)))
237
+ return newApps;
238
+ const yamlContent = await fs.readFile(workspaceYamlPath, 'utf-8');
239
+ // Simple YAML parsing for pnpm-workspace.yaml (packages: - pattern)
240
+ const patterns = [];
241
+ const lines = yamlContent.split('\n');
242
+ let inPackages = false;
243
+ for (const line of lines) {
244
+ const trimmed = line.trim();
245
+ if (trimmed === 'packages:') {
246
+ inPackages = true;
247
+ continue;
248
+ }
249
+ if (inPackages) {
250
+ if (trimmed.startsWith('-')) {
251
+ const pattern = trimmed.replace(/^-\s*['"]?/, '').replace(/['"]?\s*$/, '');
252
+ patterns.push(pattern);
253
+ }
254
+ else if (trimmed && !trimmed.startsWith('#')) {
255
+ inPackages = false;
256
+ }
257
+ }
258
+ }
259
+ // Expand glob patterns (simple: only handles "foo/*" patterns)
260
+ const knownPaths = new Set(Object.keys(wsCfg.apps));
261
+ for (const pattern of patterns) {
262
+ if (pattern.endsWith('/*')) {
263
+ const dir = pattern.slice(0, -2);
264
+ const absDir = path.join(rootDir, dir);
265
+ if (await fs.pathExists(absDir)) {
266
+ const entries = await fs.readdir(absDir);
267
+ for (const entry of entries) {
268
+ const entryPath = `${dir}/${entry}`;
269
+ const fullPath = path.join(absDir, entry);
270
+ const stat = await fs.stat(fullPath);
271
+ if (!stat.isDirectory())
272
+ continue;
273
+ // Check it has a package.json (is a workspace package)
274
+ const hasPkg = await fs.pathExists(path.join(fullPath, 'package.json'));
275
+ if (!hasPkg)
276
+ continue;
277
+ if (!knownPaths.has(entryPath)) {
278
+ newApps.push(entryPath);
279
+ }
280
+ }
281
+ }
282
+ }
283
+ }
284
+ return newApps;
285
+ }
286
+ // ─────────────────────────────────────────────────────────────────
287
+ // EXTENSION REGISTRATION
288
+ // ─────────────────────────────────────────────────────────────────
289
+ const extension = {
290
+ name: EXTENSION_NAME,
291
+ description: 'Automates dev:all, build:all, and per-app scripts in root package.json',
292
+ version: EXTENSION_VERSION,
293
+ hooks: {
294
+ postSync: async (options) => {
295
+ try {
296
+ const configPath = options?.config ?? 'versioning.config.json';
297
+ if (!(await fs.pathExists(configPath)))
298
+ return;
299
+ const config = await fs.readJson(configPath);
300
+ const wsCfg = loadWorkspaceScriptsConfig(config);
301
+ if (!wsCfg)
302
+ return;
303
+ if (wsCfg.autoDetect) {
304
+ const rootDir = process.cwd();
305
+ const newApps = await detectNewApps(rootDir, wsCfg);
306
+ if (newApps.length > 0) {
307
+ console.log('');
308
+ console.log('🔍 workspace-scripts: New apps detected in workspace:');
309
+ for (const app of newApps) {
310
+ console.log(` 📦 ${app}`);
311
+ }
312
+ console.log('');
313
+ console.log(' Run `versioning scripts add` to configure them,');
314
+ console.log(' or add them to versioning.config.json → extensionConfig → workspace-scripts → apps');
315
+ console.log('');
316
+ }
317
+ }
318
+ }
319
+ catch (error) {
320
+ console.warn('⚠️ workspace-scripts postSync hook failed:', error instanceof Error ? error.message : String(error));
321
+ }
322
+ },
323
+ },
324
+ register: async (program, config) => {
325
+ const scriptsCmd = program
326
+ .command('scripts')
327
+ .description('Manage dev/build scripts in root package.json (workspace-scripts extension)');
328
+ // ── versioning scripts sync ───────────────────────────────
329
+ scriptsCmd
330
+ .command('sync')
331
+ .description('Regenerate dev:all, build:all, and per-app scripts from config')
332
+ .option('--dry-run', 'Preview changes without writing', false)
333
+ .action(async (options) => {
334
+ try {
335
+ const rootDir = process.cwd();
336
+ const wsCfg = loadWorkspaceScriptsConfig(config);
337
+ if (!wsCfg) {
338
+ console.log('⚠️ workspace-scripts is not configured or disabled.');
339
+ console.log(' Add "workspace-scripts" to extensionConfig in versioning.config.json');
340
+ return;
341
+ }
342
+ console.log('🔄 Generating workspace scripts...\n');
343
+ const generated = await generateWorkspaceScripts(rootDir, wsCfg);
344
+ if (generated.summary.length === 0) {
345
+ console.log('⚠️ No apps configured. Nothing to generate.');
346
+ return;
347
+ }
348
+ console.log('📋 Scripts to generate:');
349
+ for (const line of generated.summary) {
350
+ console.log(line);
351
+ }
352
+ console.log('');
353
+ const result = await syncScriptsToPackageJson(rootDir, generated, options.dryRun);
354
+ if (options.dryRun) {
355
+ console.log('🔍 Dry run — no changes written.\n');
356
+ }
357
+ if (result.added.length > 0) {
358
+ console.log(`✅ Added: ${result.added.join(', ')}`);
359
+ }
360
+ if (result.updated.length > 0) {
361
+ console.log(`🔄 Updated: ${result.updated.join(', ')}`);
362
+ }
363
+ if (result.removed.length > 0) {
364
+ console.log(`🗑️ Removed: ${result.removed.join(', ')}`);
365
+ }
366
+ if (result.added.length === 0 && result.updated.length === 0 && result.removed.length === 0) {
367
+ console.log('✅ Scripts are already up to date.');
368
+ }
369
+ console.log('');
370
+ }
371
+ catch (error) {
372
+ console.error('❌ Script sync failed:', error instanceof Error ? error.message : String(error));
373
+ process.exit(1);
374
+ }
375
+ });
376
+ // ── versioning scripts list ───────────────────────────────
377
+ scriptsCmd
378
+ .command('list')
379
+ .description('Show current workspace script configuration')
380
+ .action(async () => {
381
+ try {
382
+ const rootDir = process.cwd();
383
+ const wsCfg = loadWorkspaceScriptsConfig(config);
384
+ if (!wsCfg) {
385
+ console.log('⚠️ workspace-scripts is not configured or disabled.');
386
+ return;
387
+ }
388
+ console.log('📋 Workspace Scripts Configuration\n');
389
+ console.log(` Runner: ${wsCfg.runner}`);
390
+ console.log(` Individual scripts: ${wsCfg.individualScripts ? 'yes' : 'no'}`);
391
+ console.log(` Build scripts: ${wsCfg.buildScripts ? 'yes' : 'no'}`);
392
+ console.log(` Auto-detect: ${wsCfg.autoDetect ? 'yes' : 'no'}`);
393
+ console.log('');
394
+ const enabledApps = Object.entries(wsCfg.apps).filter(([, cfg]) => cfg.enabled !== false);
395
+ const disabledApps = Object.entries(wsCfg.apps).filter(([, cfg]) => cfg.enabled === false);
396
+ if (enabledApps.length > 0) {
397
+ console.log(' Enabled apps:');
398
+ for (const [wsPath, appCfg] of enabledApps) {
399
+ const filter = await resolveFilter(rootDir, wsPath, appCfg);
400
+ const cmd = appCfg.command || 'dev';
401
+ const portInfo = appCfg.port ? ` (port ${appCfg.port})` : '';
402
+ console.log(` 📦 ${wsPath} → ${filter} ${cmd}${appCfg.args ? ' ' + appCfg.args : ''}${portInfo}`);
403
+ }
404
+ }
405
+ if (disabledApps.length > 0) {
406
+ console.log('\n Disabled apps:');
407
+ for (const [wsPath] of disabledApps) {
408
+ console.log(` ⬚ ${wsPath}`);
409
+ }
410
+ }
411
+ // Check for new untracked apps
412
+ if (wsCfg.autoDetect) {
413
+ const newApps = await detectNewApps(rootDir, wsCfg);
414
+ if (newApps.length > 0) {
415
+ console.log('\n 🔍 New apps detected (not yet configured):');
416
+ for (const app of newApps) {
417
+ console.log(` 🆕 ${app}`);
418
+ }
419
+ }
420
+ }
421
+ console.log('');
422
+ }
423
+ catch (error) {
424
+ console.error('❌ Failed to list scripts:', error instanceof Error ? error.message : String(error));
425
+ process.exit(1);
426
+ }
427
+ });
428
+ // ── versioning scripts detect ─────────────────────────────
429
+ scriptsCmd
430
+ .command('detect')
431
+ .description('Detect new workspace apps not yet in scripts config')
432
+ .action(async () => {
433
+ try {
434
+ const rootDir = process.cwd();
435
+ const wsCfg = loadWorkspaceScriptsConfig(config);
436
+ if (!wsCfg) {
437
+ console.log('⚠️ workspace-scripts is not configured or disabled.');
438
+ return;
439
+ }
440
+ const newApps = await detectNewApps(rootDir, wsCfg);
441
+ if (newApps.length === 0) {
442
+ console.log('✅ All workspace apps are tracked in scripts config.');
443
+ return;
444
+ }
445
+ console.log(`🔍 Found ${newApps.length} new app(s) not in workspace-scripts config:\n`);
446
+ for (const app of newApps) {
447
+ const pkgPath = path.join(rootDir, app, 'package.json');
448
+ let pkgName = shortName(app);
449
+ if (await fs.pathExists(pkgPath)) {
450
+ try {
451
+ const pkg = await fs.readJson(pkgPath);
452
+ if (pkg.name)
453
+ pkgName = pkg.name;
454
+ }
455
+ catch {
456
+ // ignore
457
+ }
458
+ }
459
+ console.log(` 📦 ${app} (${pkgName})`);
460
+ }
461
+ console.log('\n📝 To add them, update versioning.config.json:');
462
+ console.log(' extensionConfig → workspace-scripts → apps\n');
463
+ // Show example config for the first new app
464
+ if (newApps.length > 0) {
465
+ const example = newApps[0];
466
+ console.log(' Example:');
467
+ console.log(` "${example}": { "command": "dev", "args": "-p 3000" }\n`);
468
+ }
469
+ }
470
+ catch (error) {
471
+ console.error('❌ Detection failed:', error instanceof Error ? error.message : String(error));
472
+ process.exit(1);
473
+ }
474
+ });
475
+ // ── versioning scripts preview ────────────────────────────
476
+ scriptsCmd
477
+ .command('preview')
478
+ .description('Preview the scripts that would be generated without writing')
479
+ .action(async () => {
480
+ try {
481
+ const rootDir = process.cwd();
482
+ const wsCfg = loadWorkspaceScriptsConfig(config);
483
+ if (!wsCfg) {
484
+ console.log('⚠️ workspace-scripts is not configured or disabled.');
485
+ return;
486
+ }
487
+ const generated = await generateWorkspaceScripts(rootDir, wsCfg);
488
+ if (Object.keys(generated.scripts).length === 0) {
489
+ console.log('⚠️ No scripts would be generated.');
490
+ return;
491
+ }
492
+ console.log('📋 Preview — scripts that would be written to root package.json:\n');
493
+ const maxKeyLen = Math.max(...Object.keys(generated.scripts).map((k) => k.length));
494
+ for (const [key, value] of Object.entries(generated.scripts)) {
495
+ console.log(` "${key.padEnd(maxKeyLen)}" : "${value}"`);
496
+ }
497
+ console.log('');
498
+ }
499
+ catch (error) {
500
+ console.error('❌ Preview failed:', error instanceof Error ? error.message : String(error));
501
+ process.exit(1);
502
+ }
503
+ });
504
+ },
505
+ };
506
+ exports.default = extension;
507
+ //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edcalderon/versioning",
3
- "version": "1.4.6",
3
+ "version": "1.4.7",
4
4
  "description": "A comprehensive versioning and changelog management tool for monorepos",
5
5
  "main": "dist/index.js",
6
6
  "bin": {