@manojkmfsi/monodog 1.1.44 → 1.1.48

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,353 @@
1
+ "use strict";
2
+ /**
3
+ * Release API Routes
4
+ * HTTP endpoints for the independent release system
5
+ * Exposes change detection, readiness checks, and publishing operations
6
+ */
7
+ var __importDefault = (this && this.__importDefault) || function (mod) {
8
+ return (mod && mod.__esModule) ? mod : { "default": mod };
9
+ };
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ const express_1 = require("express");
12
+ const publish_controller_1 = require("../services/publish-controller");
13
+ const release_readiness_service_1 = require("../services/release-readiness-service");
14
+ const change_tracker_service_1 = require("../services/change-tracker-service");
15
+ const npm_publish_service_1 = require("../services/npm-publish-service");
16
+ const fs_1 = __importDefault(require("fs"));
17
+ const path_1 = __importDefault(require("path"));
18
+ const router = (0, express_1.Router)();
19
+ /**
20
+ * GET /api/releases/packages
21
+ * Get list of packages available in workspace
22
+ */
23
+ router.get('/packages', async (req, res) => {
24
+ try {
25
+ const packages = publish_controller_1.publishController.getAvailablePackages(process.cwd());
26
+ res.json({
27
+ success: true,
28
+ packages,
29
+ count: packages.length,
30
+ });
31
+ }
32
+ catch (error) {
33
+ res.status(500).json({
34
+ success: false,
35
+ error: error instanceof Error ? error.message : 'Unknown error',
36
+ });
37
+ }
38
+ });
39
+ /**
40
+ * POST /api/releases/analyze
41
+ * Analyze changes for packages without publishing
42
+ */
43
+ router.post('/analyze', async (req, res) => {
44
+ try {
45
+ const { packageNames, packagePaths } = req.body;
46
+ if (!packageNames || !packagePaths) {
47
+ return res.status(400).json({
48
+ success: false,
49
+ error: 'Missing packageNames or packagePaths',
50
+ });
51
+ }
52
+ const analysis = {};
53
+ for (const packageName of packageNames) {
54
+ try {
55
+ const packagePath = packagePaths[packageName];
56
+ const currentVersion = getCurrentVersion(packageName, packagePath);
57
+ const changes = await change_tracker_service_1.changeTrackerService.analyzeChanges(packageName, packagePath, currentVersion, '');
58
+ analysis[packageName] = changes;
59
+ }
60
+ catch (error) {
61
+ analysis[packageName] = {
62
+ error: error instanceof Error ? error.message : 'Analysis failed',
63
+ };
64
+ }
65
+ }
66
+ res.json({
67
+ success: true,
68
+ analysis,
69
+ });
70
+ }
71
+ catch (error) {
72
+ res.status(500).json({
73
+ success: false,
74
+ error: error instanceof Error ? error.message : 'Analysis failed',
75
+ });
76
+ }
77
+ });
78
+ /**
79
+ * POST /api/releases/check-readiness
80
+ * Check if packages are ready for publishing
81
+ */
82
+ router.post('/check-readiness', async (req, res) => {
83
+ try {
84
+ const { packageNames, packagePaths } = req.body;
85
+ if (!packageNames || !packagePaths) {
86
+ return res.status(400).json({
87
+ success: false,
88
+ error: 'Missing packageNames or packagePaths',
89
+ });
90
+ }
91
+ const packages = packageNames.map((name) => ({
92
+ name,
93
+ path: packagePaths[name],
94
+ currentVersion: getCurrentVersion(name, packagePaths[name]),
95
+ }));
96
+ const readiness = await release_readiness_service_1.releaseReadinessService.checkReleaseReadiness(packages);
97
+ res.json({
98
+ success: true,
99
+ canProceed: readiness.canProceed,
100
+ summary: readiness.summary,
101
+ checks: readiness.allChecks,
102
+ globalBlockers: readiness.globalBlockers,
103
+ });
104
+ }
105
+ catch (error) {
106
+ res.status(500).json({
107
+ success: false,
108
+ error: error instanceof Error ? error.message : 'Readiness check failed',
109
+ });
110
+ }
111
+ });
112
+ /**
113
+ * POST /api/releases/prepare
114
+ * Prepare packages for publishing (validate and analyze)
115
+ */
116
+ router.post('/prepare', async (req, res) => {
117
+ try {
118
+ const { packageNames, packagePaths, dryRun = false } = req.body;
119
+ if (!packageNames || !packagePaths) {
120
+ return res.status(400).json({
121
+ success: false,
122
+ error: 'Missing packageNames or packagePaths',
123
+ });
124
+ }
125
+ const result = await publish_controller_1.publishController.preparePublish({
126
+ packageNames,
127
+ packagePaths,
128
+ dryRun,
129
+ });
130
+ res.json({
131
+ success: result.valid,
132
+ canProceed: result.valid,
133
+ readiness: result.readiness,
134
+ analysis: result.analysis,
135
+ });
136
+ }
137
+ catch (error) {
138
+ res.status(500).json({
139
+ success: false,
140
+ error: error instanceof Error ? error.message : 'Preparation failed',
141
+ });
142
+ }
143
+ });
144
+ /**
145
+ * GET /api/releases/status/:pipelineId
146
+ * Get status of a publish pipeline
147
+ */
148
+ router.get('/status/:pipelineId', async (req, res) => {
149
+ try {
150
+ const { pipelineId } = req.params;
151
+ const status = publish_controller_1.publishController.getPipelineStatus(pipelineId);
152
+ if (!status) {
153
+ return res.status(404).json({
154
+ success: false,
155
+ error: `Pipeline not found: ${pipelineId}`,
156
+ });
157
+ }
158
+ res.json({
159
+ success: true,
160
+ pipeline: status,
161
+ });
162
+ }
163
+ catch (error) {
164
+ res.status(500).json({
165
+ success: false,
166
+ error: error instanceof Error ? error.message : 'Failed to get status',
167
+ });
168
+ }
169
+ });
170
+ /**
171
+ * GET /api/releases/details/:pipelineId
172
+ * Get detailed information about a pipeline
173
+ */
174
+ router.get('/details/:pipelineId', async (req, res) => {
175
+ try {
176
+ const { pipelineId } = req.params;
177
+ const details = await publish_controller_1.publishController.getPipelineDetails(pipelineId);
178
+ res.json({
179
+ success: true,
180
+ details,
181
+ });
182
+ }
183
+ catch (error) {
184
+ res.status(404).json({
185
+ success: false,
186
+ error: error instanceof Error ? error.message : 'Pipeline not found',
187
+ });
188
+ }
189
+ });
190
+ /**
191
+ * GET /api/releases/pipelines
192
+ * Get all pipelines (with optional filtering)
193
+ */
194
+ router.get('/pipelines', async (req, res) => {
195
+ try {
196
+ const { status, limit = 50 } = req.query;
197
+ let pipelines = publish_controller_1.publishController.getAllPipelines();
198
+ if (status) {
199
+ pipelines = pipelines.filter(p => p.status === status);
200
+ }
201
+ pipelines = pipelines.slice(0, Number(limit));
202
+ res.json({
203
+ success: true,
204
+ pipelines,
205
+ count: pipelines.length,
206
+ });
207
+ }
208
+ catch (error) {
209
+ res.status(500).json({
210
+ success: false,
211
+ error: error instanceof Error ? error.message : 'Failed to list pipelines',
212
+ });
213
+ }
214
+ });
215
+ /**
216
+ * POST /api/releases/start
217
+ * Start a new publish pipeline
218
+ */
219
+ router.post('/start', async (req, res) => {
220
+ try {
221
+ const { packageNames, packagePaths, versionMap, method = 'auto', dryRun = false, autoTag = true, createReleases = false, } = req.body;
222
+ if (!packageNames || !packagePaths) {
223
+ return res.status(400).json({
224
+ success: false,
225
+ error: 'Missing packageNames or packagePaths',
226
+ });
227
+ }
228
+ console.info(`📦 Starting publish pipeline for: ${packageNames.join(', ')}`);
229
+ const pipeline = await publish_controller_1.publishController.publish({
230
+ packageNames,
231
+ packagePaths,
232
+ versionMap,
233
+ method,
234
+ dryRun,
235
+ autoTag,
236
+ createReleases,
237
+ });
238
+ res.status(201).json({
239
+ success: pipeline.status !== 'failed',
240
+ pipeline,
241
+ pipelineId: pipeline.pipelineId,
242
+ });
243
+ }
244
+ catch (error) {
245
+ res.status(500).json({
246
+ success: false,
247
+ error: error instanceof Error ? error.message : 'Failed to start publish',
248
+ });
249
+ }
250
+ });
251
+ /**
252
+ * POST /api/releases/cancel/:pipelineId
253
+ * Cancel a publishing pipeline
254
+ */
255
+ router.post('/cancel/:pipelineId', async (req, res) => {
256
+ try {
257
+ const { pipelineId } = req.params;
258
+ await publish_controller_1.publishController.cancelPipeline(pipelineId);
259
+ res.json({
260
+ success: true,
261
+ message: `Pipeline ${pipelineId} cancelled`,
262
+ });
263
+ }
264
+ catch (error) {
265
+ res.status(400).json({
266
+ success: false,
267
+ error: error instanceof Error ? error.message : 'Failed to cancel pipeline',
268
+ });
269
+ }
270
+ });
271
+ /**
272
+ * GET /api/releases/npm/:packageName/versions
273
+ * Get published versions for a package
274
+ */
275
+ router.get('/npm/:packageName/versions', async (req, res) => {
276
+ try {
277
+ const { packageName } = req.params;
278
+ const versions = await npm_publish_service_1.npmPublishService.getPublishedVersions(packageName);
279
+ res.json({
280
+ success: true,
281
+ packageName,
282
+ versions,
283
+ count: versions.length,
284
+ latest: versions[0] || null,
285
+ });
286
+ }
287
+ catch (error) {
288
+ res.status(500).json({
289
+ success: false,
290
+ error: error instanceof Error ? error.message : 'Failed to get versions',
291
+ });
292
+ }
293
+ });
294
+ /**
295
+ * GET /api/releases/npm/:packageName/:version/available
296
+ * Check if a version is available on npm
297
+ */
298
+ router.get('/npm/:packageName/:version/available', async (req, res) => {
299
+ try {
300
+ const { packageName, version } = req.params;
301
+ const available = await npm_publish_service_1.npmPublishService.isVersionAvailable(packageName, version);
302
+ res.json({
303
+ success: true,
304
+ packageName,
305
+ version,
306
+ available,
307
+ });
308
+ }
309
+ catch (error) {
310
+ res.status(500).json({
311
+ success: false,
312
+ error: error instanceof Error ? error.message : 'Failed to check version',
313
+ });
314
+ }
315
+ });
316
+ /**
317
+ * GET /api/releases/health
318
+ * Health check for release system
319
+ */
320
+ router.get('/health', async (req, res) => {
321
+ try {
322
+ const pipelines = publish_controller_1.publishController.getAllPipelines();
323
+ const activePipelines = pipelines.filter(p => p.status === 'publishing' || p.status === 'validating');
324
+ res.json({
325
+ success: true,
326
+ status: 'healthy',
327
+ activePipelines: activePipelines.length,
328
+ totalPipelines: pipelines.length,
329
+ timestamp: new Date().toISOString(),
330
+ });
331
+ }
332
+ catch (error) {
333
+ res.status(500).json({
334
+ success: false,
335
+ error: 'Release system health check failed',
336
+ });
337
+ }
338
+ });
339
+ /**
340
+ * Helper function to get current version
341
+ */
342
+ function getCurrentVersion(packageName, packagePath) {
343
+ try {
344
+ const pkgJsonPath = path_1.default.join(packagePath, 'package.json');
345
+ const content = fs_1.default.readFileSync(pkgJsonPath, 'utf8');
346
+ const pkgJson = JSON.parse(content);
347
+ return pkgJson.version || '0.0.0';
348
+ }
349
+ catch {
350
+ return '0.0.0';
351
+ }
352
+ }
353
+ exports.default = router;
@@ -0,0 +1,227 @@
1
+ "use strict";
2
+ /**
3
+ * Change Tracker Service
4
+ * Detects changes in packages using Conventional Commits and Git diffs
5
+ * Replaces dependency on .changeset/*.md files
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.changeTrackerService = exports.ChangeTrackerService = void 0;
9
+ const child_process_1 = require("child_process");
10
+ const util_1 = require("util");
11
+ // NOTE: ChangeTrack and CommitChange types will be imported from @prisma/client after DB integration
12
+ // Currently using inline types below for service implementation
13
+ const execAsync = (0, util_1.promisify)(child_process_1.exec);
14
+ class ChangeTrackerService {
15
+ /**
16
+ * Analyze changes for a package since last release
17
+ */
18
+ async analyzeChanges(packageName, packagePath, currentVersion, lastTagName = `${packageName}@${currentVersion}`) {
19
+ try {
20
+ // 1. Get commits since last tag
21
+ const commits = await this.getCommitsSinceTag(lastTagName, packagePath);
22
+ // 2. Get file diffs
23
+ const filesChanged = await this.getFileDiffsSinceTag(lastTagName, packagePath);
24
+ // 3. Determine change type from commits
25
+ const changeType = this.determineChangeType(commits);
26
+ // 4. Identify affected dependents
27
+ const affectedDependents = await this.identifyAffectedDependents(packageName);
28
+ return {
29
+ packageName,
30
+ currentVersion,
31
+ changeType,
32
+ commits,
33
+ filesChanged,
34
+ affectedDependents,
35
+ proposedVersion: this.calculateNextVersion(currentVersion, changeType),
36
+ isReleaseReady: changeType !== 'none',
37
+ };
38
+ }
39
+ catch (error) {
40
+ console.error(`Failed to analyze changes for ${packageName}:`, error);
41
+ throw error;
42
+ }
43
+ }
44
+ /**
45
+ * Parse commit message using Conventional Commits format
46
+ * https://www.conventionalcommits.org/
47
+ *
48
+ * Format: type(scope)!: subject
49
+ * BREAKING CHANGE: description
50
+ */
51
+ parseConventionalCommit(message, body) {
52
+ const conventionalRegex = /^(feat|fix|docs|style|refactor|test|chore|perf|ci|revert|build)(\(.+\))?(!)?:\s(.+)/;
53
+ const match = message.match(conventionalRegex);
54
+ if (!match) {
55
+ return {
56
+ type: 'chore',
57
+ scope: undefined,
58
+ isBreaking: false,
59
+ };
60
+ }
61
+ const [, type, scopeMatch, breakingIndicator, subject] = match;
62
+ const scope = scopeMatch ? scopeMatch.slice(1, -1) : undefined;
63
+ // Check for BREAKING CHANGE keyword in body
64
+ const isBreaking = !!breakingIndicator || (body ? /^BREAKING[\s-]CHANGE:/m.test(body) : false);
65
+ return {
66
+ type: type,
67
+ scope,
68
+ isBreaking,
69
+ };
70
+ }
71
+ /**
72
+ * Get commits since last tag
73
+ */
74
+ async getCommitsSinceTag(tagName, packagePath) {
75
+ try {
76
+ // Get commits since tag, limited to this package path
77
+ const { stdout } = await execAsync(`cd ${packagePath} && git log ${tagName}..HEAD --format='%H|||%ae|||%an|||%ai|||%s|||%b' --`, { shell: '/bin/bash' });
78
+ if (!stdout.trim()) {
79
+ return [];
80
+ }
81
+ const commits = stdout
82
+ .split('\n')
83
+ .filter(line => line.trim())
84
+ .map(line => {
85
+ const [hash, email, author, timestamp, subject, body] = line.split('|||');
86
+ const parsed = this.parseConventionalCommit(subject, body);
87
+ return {
88
+ hash,
89
+ message: subject,
90
+ author: author || email,
91
+ authorEmail: email,
92
+ committedAt: new Date(timestamp),
93
+ type: parsed.type,
94
+ scope: parsed.scope,
95
+ isBreaking: parsed.isBreaking,
96
+ body: body?.trim() || undefined,
97
+ };
98
+ });
99
+ return commits;
100
+ }
101
+ catch (error) {
102
+ // If tag doesn't exist, get all commits (first release)
103
+ console.warn(`Tag ${tagName} not found, analyzing all commits`);
104
+ return this.getAllCommits(packagePath);
105
+ }
106
+ }
107
+ /**
108
+ * Get all commits for a package
109
+ */
110
+ async getAllCommits(packagePath) {
111
+ try {
112
+ const { stdout } = await execAsync(`cd ${packagePath} && git log --format='%H|||%ae|||%an|||%ai|||%s|||%b' --`, { shell: '/bin/bash' });
113
+ if (!stdout.trim()) {
114
+ return [];
115
+ }
116
+ return stdout
117
+ .split('\n')
118
+ .filter(line => line.trim())
119
+ .map(line => {
120
+ const [hash, email, author, timestamp, subject, body] = line.split('|||');
121
+ const parsed = this.parseConventionalCommit(subject, body);
122
+ return {
123
+ hash,
124
+ message: subject,
125
+ author: author || email,
126
+ authorEmail: email,
127
+ committedAt: new Date(timestamp),
128
+ type: parsed.type,
129
+ scope: parsed.scope,
130
+ isBreaking: parsed.isBreaking,
131
+ body: body?.trim() || undefined,
132
+ };
133
+ });
134
+ }
135
+ catch (error) {
136
+ console.error(`Failed to get commits for ${packagePath}:`, error);
137
+ return [];
138
+ }
139
+ }
140
+ /**
141
+ * Get file diffs since last tag
142
+ */
143
+ async getFileDiffsSinceTag(tagName, packagePath) {
144
+ try {
145
+ // Get file stats in diff format
146
+ const { stdout } = await execAsync(`cd ${packagePath} && git diff --no-renames --numstat ${tagName}..HEAD --`, { shell: '/bin/bash' });
147
+ if (!stdout.trim()) {
148
+ return [];
149
+ }
150
+ return stdout
151
+ .split('\n')
152
+ .filter(line => line.trim())
153
+ .map(line => {
154
+ const [added, removed, filePath] = line.split('\t');
155
+ return {
156
+ path: filePath,
157
+ added: false,
158
+ removed: false,
159
+ modified: true,
160
+ linesAdded: parseInt(added) || 0,
161
+ linesRemoved: parseInt(removed) || 0,
162
+ };
163
+ });
164
+ }
165
+ catch (error) {
166
+ console.warn(`Failed to get diffs for ${packagePath}:`, error);
167
+ return [];
168
+ }
169
+ }
170
+ /**
171
+ * Determine change type based on commits
172
+ * Rules:
173
+ * - BREAKING CHANGE or ! → major
174
+ * - feat: → minor
175
+ * - fix:, refactor:, etc → patch
176
+ * - docs:, chore:, etc → none
177
+ */
178
+ determineChangeType(commits) {
179
+ if (commits.length === 0) {
180
+ return 'none';
181
+ }
182
+ // Check for breaking changes first
183
+ if (commits.some(c => c.isBreaking)) {
184
+ return 'major';
185
+ }
186
+ // Check for features
187
+ if (commits.some(c => c.type === 'feat')) {
188
+ return 'minor';
189
+ }
190
+ // Check for fixes and other meaningful changes
191
+ if (commits.some(c => ['fix', 'refactor', 'perf'].includes(c.type))) {
192
+ return 'patch';
193
+ }
194
+ // Everything else (docs, chore, style, test, etc) doesn't warrant a release
195
+ return 'none';
196
+ }
197
+ /**
198
+ * Calculate next semantic version
199
+ */
200
+ calculateNextVersion(currentVersion, changeType) {
201
+ if (changeType === 'none') {
202
+ return currentVersion;
203
+ }
204
+ const parts = currentVersion.split('.');
205
+ const [major, minor, patch] = parts.map(p => parseInt(p));
206
+ switch (changeType) {
207
+ case 'major':
208
+ return `${major + 1}.0.0`;
209
+ case 'minor':
210
+ return `${major}.${minor + 1}.0`;
211
+ case 'patch':
212
+ return `${major}.${minor}.${patch + 1}`;
213
+ default:
214
+ return currentVersion;
215
+ }
216
+ }
217
+ /**
218
+ * Identify packages that depend on this package
219
+ */
220
+ async identifyAffectedDependents(packageName) {
221
+ // TODO: Implement dependency graph analysis
222
+ // For now, return empty array - can be enhanced with dependency resolver
223
+ return [];
224
+ }
225
+ }
226
+ exports.ChangeTrackerService = ChangeTrackerService;
227
+ exports.changeTrackerService = new ChangeTrackerService();