fix(installer): restore validateGitHubUrl strictness and fix prettier

- Restore original GitHub-only regex in deprecated validateGitHubUrl
  wrapper so existing tests pass (rejects non-GitHub URLs, trailing
  slashes)
- Run prettier to fix formatting in custom-module-manager.js
This commit is contained in:
Brian Madison 2026-04-09 17:56:46 -05:00
parent ed51e6c538
commit 225e5ee77b
1 changed files with 59 additions and 15 deletions

View File

@ -26,12 +26,30 @@ class CustomModuleManager {
*/ */
parseSource(input) { parseSource(input) {
if (!input || typeof input !== 'string') { if (!input || typeof input !== 'string') {
return { type: null, cloneUrl: null, subdir: null, localPath: null, cacheKey: null, displayName: null, isValid: false, error: 'Source is required' }; return {
type: null,
cloneUrl: null,
subdir: null,
localPath: null,
cacheKey: null,
displayName: null,
isValid: false,
error: 'Source is required',
};
} }
const trimmed = input.trim(); const trimmed = input.trim();
if (!trimmed) { if (!trimmed) {
return { type: null, cloneUrl: null, subdir: null, localPath: null, cacheKey: null, displayName: null, isValid: false, error: 'Source is required' }; return {
type: null,
cloneUrl: null,
subdir: null,
localPath: null,
cacheKey: null,
displayName: null,
isValid: false,
error: 'Source is required',
};
} }
// Local path detection: starts with /, ./, ../, or ~ // Local path detection: starts with /, ./, ../, or ~
@ -65,9 +83,9 @@ class CustomModuleManager {
if (remainder) { if (remainder) {
// Extract subdir from deep path patterns used by various Git hosts // Extract subdir from deep path patterns used by various Git hosts
const deepPathPatterns = [ const deepPathPatterns = [
/^\/(?:-\/)?tree\/[^/]+\/(.+)$/, // GitHub /tree/branch/path, GitLab /-/tree/branch/path /^\/(?:-\/)?tree\/[^/]+\/(.+)$/, // GitHub /tree/branch/path, GitLab /-/tree/branch/path
/^\/(?:-\/)?blob\/[^/]+\/(.+)$/, // /blob/branch/path (treat same as tree) /^\/(?:-\/)?blob\/[^/]+\/(.+)$/, // /blob/branch/path (treat same as tree)
/^\/src\/[^/]+\/(.+)$/, // Gitea/Forgejo /src/branch/path /^\/src\/[^/]+\/(.+)$/, // Gitea/Forgejo /src/branch/path
]; ];
for (const pattern of deepPathPatterns) { for (const pattern of deepPathPatterns) {
@ -91,7 +109,16 @@ class CustomModuleManager {
}; };
} }
return { type: null, cloneUrl: null, subdir: null, localPath: null, cacheKey: null, displayName: null, isValid: false, error: 'Not a valid Git URL or local path' }; return {
type: null,
cloneUrl: null,
subdir: null,
localPath: null,
cacheKey: null,
displayName: null,
isValid: false,
error: 'Not a valid Git URL or local path',
};
} }
/** /**
@ -104,7 +131,16 @@ class CustomModuleManager {
const resolved = path.resolve(expanded); const resolved = path.resolve(expanded);
if (!fs.pathExistsSync(resolved)) { if (!fs.pathExistsSync(resolved)) {
return { type: 'local', cloneUrl: null, subdir: null, localPath: resolved, cacheKey: null, displayName: path.basename(resolved), isValid: false, error: `Path does not exist: ${resolved}` }; return {
type: 'local',
cloneUrl: null,
subdir: null,
localPath: resolved,
cacheKey: null,
displayName: path.basename(resolved),
isValid: false,
error: `Path does not exist: ${resolved}`,
};
} }
return { return {
@ -126,16 +162,24 @@ class CustomModuleManager {
* @returns {Object} { owner, repo, isValid, error } * @returns {Object} { owner, repo, isValid, error }
*/ */
validateGitHubUrl(url) { validateGitHubUrl(url) {
const parsed = this.parseSource(url); if (!url || typeof url !== 'string') {
if (!parsed.isValid) { return { owner: null, repo: null, isValid: false, error: 'URL is required' };
return { owner: null, repo: null, isValid: false, error: parsed.error };
} }
if (parsed.type === 'local') { const trimmed = url.trim();
return { owner: null, repo: null, isValid: false, error: 'Not a valid GitHub URL (expected https://github.com/owner/repo)' };
// HTTPS format: https://github.com/owner/repo[.git] (strict, no trailing path)
const httpsMatch = trimmed.match(/^https?:\/\/github\.com\/([^/]+)\/([^/.]+?)(?:\.git)?$/);
if (httpsMatch) {
return { owner: httpsMatch[1], repo: httpsMatch[2], isValid: true, error: null };
} }
// Extract owner/repo from cacheKey (host/owner/repo)
const parts = parsed.cacheKey.split('/'); // SSH format: git@github.com:owner/repo[.git]
return { owner: parts[1] || null, repo: parts[2] || null, isValid: true, error: null }; const sshMatch = trimmed.match(/^git@github\.com:([^/]+)\/([^/.]+?)(?:\.git)?$/);
if (sshMatch) {
return { owner: sshMatch[1], repo: sshMatch[2], isValid: true, error: null };
}
return { owner: null, repo: null, isValid: false, error: 'Not a valid GitHub URL (expected https://github.com/owner/repo)' };
} }
// ─── Marketplace JSON ───────────────────────────────────────────────────── // ─── Marketplace JSON ─────────────────────────────────────────────────────