@manojkmfsi/monodog 1.1.22 → 1.1.23

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
@@ -1,5 +1,11 @@
1
1
  # @manojkmfsi/monoapp
2
2
 
3
+ ## 1.1.23
4
+
5
+ ### Patch Changes
6
+
7
+ - [`b90aa90`](https://github.com/manojkmfsi/monodog/commit/b90aa90bfbf516128af033c883a21ca4d09f713d) - dcdsdsvdvdvxvcxvxvx
8
+
3
9
  ## 1.1.22
4
10
 
5
11
  ### Patch Changes
@@ -7,6 +7,7 @@ exports.createChangeset = createChangeset;
7
7
  exports.checkPublishStatus = checkPublishStatus;
8
8
  exports.triggerPublish = triggerPublish;
9
9
  const logger_1 = require("../middleware/logger");
10
+ const auth_middleware_1 = require("../middleware/auth-middleware");
10
11
  const changeset_service_1 = require("../services/changeset-service");
11
12
  /**
12
13
  * Get all workspace packages
@@ -80,17 +81,34 @@ async function previewPublish(req, res) {
80
81
  // Perform validation checks
81
82
  const errors = [];
82
83
  const warnings = [];
84
+ // Get authenticated user
85
+ const session = (0, auth_middleware_1.getSessionFromRequest)(req);
86
+ const authUser = req.user;
87
+ const userPermission = req.permission.permission || 'read';
83
88
  // Check 1: Working tree clean
84
89
  const workingTreeClean = isClean;
85
90
  if (!workingTreeClean) {
86
91
  errors.push('Working tree has uncommitted changes');
87
92
  }
88
- // Check 2: User permissions (simplified - always true for now)
89
- const permissions = true;
93
+ // Check 2: User permissions
94
+ const permissionHierarchy = {
95
+ admin: 4,
96
+ maintain: 3,
97
+ write: 2,
98
+ read: 1,
99
+ none: 0,
100
+ };
101
+ const userLevel = permissionHierarchy[userPermission] || 0;
102
+ const requiredLevel = permissionHierarchy['write'] || 0;
103
+ const permissions = userLevel >= requiredLevel;
104
+ if (!permissions) {
105
+ errors.push(`Insufficient permissions. Required: write, Got: ${userPermission}`);
106
+ }
90
107
  // Check 3: CI passing (simplified - always true for now)
91
108
  const ciPassing = true;
92
109
  // Check 4: Version available on npm (simplified - always true for now)
93
110
  const versionAvailable = true;
111
+ logger_1.AppLogger.info(`Publishing preview for user: ${authUser?.login} (permission: ${userPermission})`);
94
112
  const isValid = errors.length === 0;
95
113
  res.json({
96
114
  success: true,
@@ -126,6 +144,8 @@ async function previewPublish(req, res) {
126
144
  async function createChangeset(req, res) {
127
145
  try {
128
146
  const { packages: selectedPackageNames, bumps, summary } = req.body;
147
+ const authUser = req.user;
148
+ const userPermission = req.permission.permission || 'read';
129
149
  if (!selectedPackageNames || !Array.isArray(selectedPackageNames)) {
130
150
  res.status(400).json({
131
151
  success: false,
@@ -142,9 +162,29 @@ async function createChangeset(req, res) {
142
162
  });
143
163
  return;
144
164
  }
165
+ // Check permissions
166
+ const permissionHierarchy = {
167
+ admin: 4,
168
+ maintain: 3,
169
+ write: 2,
170
+ read: 1,
171
+ none: 0,
172
+ };
173
+ const userLevel = permissionHierarchy[userPermission] || 0;
174
+ const requiredLevel = permissionHierarchy['write'] || 0;
175
+ if (userLevel < requiredLevel) {
176
+ logger_1.AppLogger.warn(`User ${authUser?.login} attempted to create changeset without write permission`);
177
+ res.status(403).json({
178
+ success: false,
179
+ error: 'Forbidden',
180
+ message: `This action requires write permission. You have: ${userPermission}`,
181
+ });
182
+ return;
183
+ }
145
184
  const rootPath = req.app.locals.rootPath;
185
+ logger_1.AppLogger.info(`Creating changeset for user: ${authUser?.login} (permission: ${userPermission})`);
146
186
  // Generate the changeset
147
- const result = await (0, changeset_service_1.generateChangeset)(rootPath, selectedPackageNames, bumps || [], summary);
187
+ const result = await (0, changeset_service_1.generateChangeset)(rootPath, selectedPackageNames, bumps || [], summary, authUser?.login);
148
188
  if (!result.success) {
149
189
  res.status(400).json({
150
190
  success: false,
@@ -202,6 +242,27 @@ async function checkPublishStatus(req, res) {
202
242
  async function triggerPublish(req, res) {
203
243
  try {
204
244
  const rootPath = req.app.locals.rootPath;
245
+ const authUser = req.user;
246
+ const userPermission = req.permission.permission || 'read';
247
+ // Check permissions
248
+ const permissionHierarchy = {
249
+ admin: 4,
250
+ maintain: 3,
251
+ write: 2,
252
+ read: 1,
253
+ none: 0,
254
+ };
255
+ const userLevel = permissionHierarchy[userPermission] || 0;
256
+ const requiredLevel = permissionHierarchy['maintain'] || 0;
257
+ if (userLevel < requiredLevel) {
258
+ logger_1.AppLogger.warn(`User ${authUser?.login} attempted to trigger publish without maintain permission`);
259
+ res.status(403).json({
260
+ success: false,
261
+ error: 'Forbidden',
262
+ message: `This action requires maintain permission. You have: ${userPermission}`,
263
+ });
264
+ return;
265
+ }
205
266
  // Check if working tree is clean
206
267
  const isClean = true; //await isWorkingTreeClean(rootPath);
207
268
  if (!isClean) {
@@ -222,8 +283,9 @@ async function triggerPublish(req, res) {
222
283
  });
223
284
  return;
224
285
  }
225
- // Trigger publish pipeline
226
- const result = await (0, changeset_service_1.triggerPublishPipeline)(rootPath);
286
+ logger_1.AppLogger.info(`Triggering publish for user: ${authUser?.login} (permission: ${userPermission})`);
287
+ // Trigger publish pipeline with user context
288
+ const result = await (0, changeset_service_1.triggerPublishPipeline)(rootPath, authUser?.login);
227
289
  if (!result.success) {
228
290
  res.status(500).json({
229
291
  success: false,
@@ -90,9 +90,30 @@ function authenticationMiddleware(req, res, next) {
90
90
  });
91
91
  return;
92
92
  }
93
- // Attach session to request
93
+ // Attach session and user info to request
94
94
  req.session = session;
95
- logger_1.AppLogger.debug(`Authenticated request from user: ${session.user.login}`);
95
+ req.user = {
96
+ login: session.user.login,
97
+ id: session.user.id,
98
+ };
99
+ // Attach permission from session (if available)
100
+ if (session.permission) {
101
+ req.permission = session.permission;
102
+ }
103
+ else {
104
+ // Default to 'read' if no permission fetched
105
+ req.permission = {
106
+ permission: 'read',
107
+ role: 'Denied',
108
+ userId: session.user.id,
109
+ username: session.user.login,
110
+ owner: '',
111
+ repo: '',
112
+ cachedAt: Date.now(),
113
+ ttl: 0,
114
+ };
115
+ }
116
+ logger_1.AppLogger.debug(`Authenticated request from user: ${session.user.login} with permission: ${req.permission?.permission || 'unknown'}`);
96
117
  next();
97
118
  }
98
119
  /**
@@ -124,10 +145,12 @@ function repositoryPermissionMiddleware(requiredPermission) {
124
145
  read: 1,
125
146
  none: 0,
126
147
  };
127
- const userLevel = permissionHierarchy[permission.permission] || 0;
148
+ // Handle both string and object formats for permission
149
+ const userPermissionString = typeof permission === 'string' ? permission : (permission.permission || 'none');
150
+ const userLevel = permissionHierarchy[userPermissionString] || 0;
128
151
  const requiredLevel = permissionHierarchy[requiredPermission] || 0;
129
152
  if (userLevel < requiredLevel) {
130
- logger_1.AppLogger.warn(`User ${session.user.login} lacks permission for action requiring ${requiredPermission}`);
153
+ logger_1.AppLogger.warn(`User ${session.user.login} lacks permission for action requiring ${requiredPermission} (has ${userPermissionString})`);
131
154
  res.status(403).json({
132
155
  error: 'Forbidden',
133
156
  message: `This action requires ${requiredPermission} permission`,
@@ -7,7 +7,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const express_1 = require("express");
8
8
  const github_oauth_service_1 = require("../services/github-oauth-service");
9
9
  const auth_middleware_1 = require("../middleware/auth-middleware");
10
+ const permission_service_1 = require("../services/permission-service");
10
11
  const logger_1 = require("../middleware/logger");
12
+ const utilities_1 = require("../utils/utilities");
11
13
  const router = (0, express_1.Router)();
12
14
  // OAuth configuration (should come from environment variables)
13
15
  // const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID || '';
@@ -138,13 +140,34 @@ router.get('/callback', async (req, res) => {
138
140
  // Get user information
139
141
  logger_1.AppLogger.debug('Retrieving authenticated user information');
140
142
  const user = await (0, github_oauth_service_1.getAuthenticatedUser)(tokenResponse.access_token);
141
- // Create session
143
+ // Fetch user's repository permission
144
+ let permission = null;
145
+ try {
146
+ logger_1.AppLogger.debug(`Fetching repository permission for user ${user.login}`);
147
+ // Extract repository info from git remote
148
+ const repoInfo = await (0, utilities_1.getRepositoryInfoFromGit)();
149
+ if (!repoInfo) {
150
+ logger_1.AppLogger.warn('Could not extract repository info from git remote - permission fetch skipped');
151
+ }
152
+ else {
153
+ const { owner, repo } = repoInfo;
154
+ permission = await (0, permission_service_1.getUserRepositoryPermission)(tokenResponse.access_token, user.id, user.login, owner, repo);
155
+ logger_1.AppLogger.info(`User ${user.login} has ${permission.permission} permission on ${owner}/${repo}`);
156
+ }
157
+ }
158
+ catch (permError) {
159
+ logger_1.AppLogger.error(`Failed to fetch repository permission: ${permError}`);
160
+ // Continue without permission - will be checked on protected routes
161
+ permission = null;
162
+ }
163
+ // Create session with permission
142
164
  const session = {
143
165
  accessToken: tokenResponse.access_token,
144
166
  expiresIn: 3600, // 1 hour default
145
167
  expiresAt: Date.now() + 24 * 60 * 60 * 1000, // 24 hours
146
168
  user,
147
169
  scopes: tokenResponse.scope.split(','),
170
+ permission, // Include fetched permission in session
148
171
  };
149
172
  // Store session and get token
150
173
  const sessionToken = (0, auth_middleware_1.storeSession)(session);
@@ -152,7 +175,7 @@ router.get('/callback', async (req, res) => {
152
175
  const redirectUrl = getRedirectUrl(state) || '/';
153
176
  // Clear state
154
177
  clearState(state);
155
- logger_1.AppLogger.info(`User authenticated: ${user.login}`);
178
+ logger_1.AppLogger.info(`User authenticated: ${user.login} with permission: ${permission?.permission || 'unknown'}`);
156
179
  res.json({
157
180
  success: true,
158
181
  message: 'Authentication successful',
@@ -164,6 +187,10 @@ router.get('/callback', async (req, res) => {
164
187
  name: user.name,
165
188
  avatar_url: user.avatar_url,
166
189
  },
190
+ permission: permission ? {
191
+ level: permission.permission,
192
+ role: permission.role,
193
+ } : null,
167
194
  });
168
195
  }
169
196
  catch (error) {
@@ -203,6 +230,7 @@ router.get('/me', auth_middleware_1.authenticationMiddleware, (req, res) => {
203
230
  },
204
231
  scopes: session.scopes,
205
232
  expiresAt: session.expiresAt,
233
+ permission: session.permission || null,
206
234
  });
207
235
  }
208
236
  catch (error) {
@@ -10,31 +10,37 @@ const publishRouter = express_1.default.Router();
10
10
  /**
11
11
  * GET /api/publish/packages
12
12
  * Get all workspace packages for publishing
13
+ * Requires: read permission
13
14
  */
14
15
  publishRouter.get('/packages', auth_middleware_1.authenticationMiddleware, publish_controller_1.getPublishPackages);
15
16
  /**
16
17
  * GET /api/publish/changesets
17
18
  * Get existing unpublished changesets
19
+ * Requires: read permission
18
20
  */
19
21
  publishRouter.get('/changesets', auth_middleware_1.authenticationMiddleware, publish_controller_1.getPublishChangesets);
20
22
  /**
21
23
  * POST /api/publish/preview
22
24
  * Preview the publish plan (calculate new versions, affected packages)
25
+ * Requires: read permission
23
26
  */
24
27
  publishRouter.post('/preview', auth_middleware_1.authenticationMiddleware, publish_controller_1.previewPublish);
25
28
  /**
26
29
  * POST /api/publish/changesets
27
30
  * Create a new changeset for the selected packages
31
+ * Requires: write permission
28
32
  */
29
- publishRouter.post('/changesets', auth_middleware_1.authenticationMiddleware, publish_controller_1.createChangeset);
33
+ publishRouter.post('/changesets', auth_middleware_1.authenticationMiddleware, (0, auth_middleware_1.repositoryPermissionMiddleware)('write'), publish_controller_1.createChangeset);
30
34
  /**
31
35
  * GET /api/publish/status
32
36
  * Check publish readiness (working tree, changesets, etc.)
37
+ * Requires: read permission
33
38
  */
34
39
  publishRouter.get('/status', auth_middleware_1.authenticationMiddleware, publish_controller_1.checkPublishStatus);
35
40
  /**
36
41
  * POST /api/publish/trigger
37
42
  * Trigger the publishing workflow
43
+ * Requires: maintain permission
38
44
  */
39
- publishRouter.post('/trigger', auth_middleware_1.authenticationMiddleware, publish_controller_1.triggerPublish);
45
+ publishRouter.post('/trigger', auth_middleware_1.authenticationMiddleware, (0, auth_middleware_1.repositoryPermissionMiddleware)('maintain'), publish_controller_1.triggerPublish);
40
46
  exports.default = publishRouter;
@@ -120,7 +120,7 @@ async function validateChangeset(rootPath, packages, summary) {
120
120
  /**
121
121
  * Generate a new changeset
122
122
  */
123
- async function generateChangeset(rootPath, packages, bumps, summary) {
123
+ async function generateChangeset(rootPath, packages, bumps, summary, createdBy) {
124
124
  try {
125
125
  // Validate input
126
126
  const validation = await validateChangeset(rootPath, packages, summary);
@@ -154,7 +154,7 @@ async function generateChangeset(rootPath, packages, bumps, summary) {
154
154
  content += summary;
155
155
  // Write changeset file
156
156
  await promises_1.default.writeFile(changesetPath, content, 'utf-8');
157
- logger_1.AppLogger.info(`Changeset created: ${changesetName}`);
157
+ logger_1.AppLogger.info(`Changeset created: ${changesetName} by user: ${createdBy || 'unknown'}`);
158
158
  return {
159
159
  success: true,
160
160
  message: 'Changeset created successfully',
@@ -186,8 +186,9 @@ async function isWorkingTreeClean(rootPath) {
186
186
  /**
187
187
  * Trigger CI pipeline for publishing
188
188
  */
189
- async function triggerPublishPipeline(rootPath) {
189
+ async function triggerPublishPipeline(rootPath, publishedBy) {
190
190
  try {
191
+ logger_1.AppLogger.info(`Publishing workflow triggered by user: ${publishedBy || 'unknown'}`);
191
192
  // Check if publish workflow exists
192
193
  const publishWorkflowPath = path_1.default.join(rootPath, '.github', 'workflows', 'release.yml');
193
194
  try {
@@ -228,43 +229,6 @@ async function triggerPublishPipeline(rootPath) {
228
229
  logger_1.AppLogger.warn(`Git operations failed: ${gitError}`);
229
230
  // Continue anyway - changesets might already be committed
230
231
  }
231
- // Trigger the workflow via GitHub API if we have a token
232
- try {
233
- const githubToken = process.env.GITHUB_TOKEN;
234
- if (githubToken) {
235
- // Get repo info from package.json or git remote
236
- const { stdout: remoteUrl } = await execPromise('git remote get-url origin', {
237
- cwd: rootPath,
238
- });
239
- // Parse GitHub repo from URL (e.g., git@github.com:user/repo.git)
240
- const repoMatch = remoteUrl.match(/github\.com[:/]([^/]+)\/(.+?)(\.git)?$/);
241
- if (repoMatch) {
242
- const [, owner, repo] = repoMatch;
243
- const repoName = repo.replace(/\.git$/, '');
244
- // Trigger the workflow
245
- const response = await fetch(`https://api.github.com/repos/${owner}/${repoName}/actions/workflows/release.yml/dispatches`, {
246
- method: 'POST',
247
- headers: {
248
- Authorization: `Bearer ${githubToken}`,
249
- Accept: 'application/vnd.github.v3+json',
250
- },
251
- body: JSON.stringify({
252
- ref: 'main',
253
- }),
254
- });
255
- if (response.ok) {
256
- logger_1.AppLogger.info('GitHub workflow triggered successfully');
257
- }
258
- else {
259
- logger_1.AppLogger.warn(`Failed to trigger workflow: ${response.statusText}`);
260
- }
261
- }
262
- }
263
- }
264
- catch (workflowError) {
265
- logger_1.AppLogger.warn(`Failed to trigger workflow: ${workflowError}`);
266
- // Still return success as the changeset was created
267
- }
268
232
  logger_1.AppLogger.info('Publish pipeline initiated');
269
233
  return {
270
234
  success: true,
@@ -40,6 +40,7 @@ exports.calculatePackageHealth = void 0;
40
40
  exports.resolveWorkspaceGlobs = resolveWorkspaceGlobs;
41
41
  exports.getWorkspacesFromRoot = getWorkspacesFromRoot;
42
42
  exports.parsePackageInfo = parsePackageInfo;
43
+ exports.getRepositoryInfoFromGit = getRepositoryInfoFromGit;
43
44
  exports.scanMonorepo = scanMonorepo;
44
45
  exports.generateMonorepoStats = generateMonorepoStats;
45
46
  exports.findCircularDependencies = findCircularDependencies;
@@ -407,3 +408,51 @@ function findMonorepoRoot() {
407
408
  logger_1.AppLogger.warn('Could not find monorepo root, using process.cwd(): ' + process.cwd());
408
409
  return process.cwd();
409
410
  }
411
+ /**
412
+ * Extracts GitHub repository owner and repo name from git remote URL
413
+ * Supports both SSH (git@github.com:owner/repo.git) and HTTPS (https://github.com/owner/repo.git) formats
414
+ */
415
+ async function getRepositoryInfoFromGit(repoPath = process.cwd()) {
416
+ try {
417
+ const { exec } = await import('child_process');
418
+ const { promisify } = await import('util');
419
+ const execPromise = promisify(exec);
420
+ // Get the git remote URL
421
+ const { stdout } = await execPromise('git remote get-url origin', {
422
+ cwd: repoPath,
423
+ timeout: 5000,
424
+ });
425
+ const remoteUrl = stdout.trim();
426
+ if (!remoteUrl) {
427
+ logger_1.AppLogger.warn('No git remote found');
428
+ return null;
429
+ }
430
+ // Parse different URL formats
431
+ let owner = '';
432
+ let repo = '';
433
+ // SSH format: git@github.com:owner/repo.git
434
+ const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/(.+?)(\.git)?$/);
435
+ if (sshMatch) {
436
+ owner = sshMatch[1];
437
+ repo = sshMatch[2].replace(/\.git$/, '');
438
+ }
439
+ else {
440
+ // HTTPS format: https://github.com/owner/repo.git or https://github.com/owner/repo
441
+ const httpsMatch = remoteUrl.match(/github\.com[:/]([^/]+)\/(.+?)(\.git)?$/);
442
+ if (httpsMatch) {
443
+ owner = httpsMatch[1];
444
+ repo = httpsMatch[2].replace(/\.git$/, '');
445
+ }
446
+ }
447
+ if (!owner || !repo) {
448
+ logger_1.AppLogger.warn(`Could not parse repository info from git remote: ${remoteUrl}`);
449
+ return null;
450
+ }
451
+ logger_1.AppLogger.debug(`Extracted repository info: owner=${owner}, repo=${repo}`);
452
+ return { owner, repo };
453
+ }
454
+ catch (error) {
455
+ logger_1.AppLogger.error(`Failed to get repository info from git: ${error}`);
456
+ return null;
457
+ }
458
+ }