Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"root": true,
"env": { "browser": true, "es2021": true, "serviceworker": true },
"parserOptions": { "ecmaVersion": 2021 },
"extends": ["eslint:recommended"],
"overrides": [
{
"files": ["Templates/*.js"],
"rules": {
"no-undef": "off",
"no-unused-vars": "off"
}
}
]
}
22 changes: 11 additions & 11 deletions Publish.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ if ($LASTEXITCODE -ne 0) {
exit 1
}
$version = ($buildOutput | Select-Object -Last 1).ToString().Trim()
Write-Host "Version: $version"
Write-Information "Version: $version" -InformationAction Continue

# Check if tag already exists
$existingTag = git tag -l $version
Expand All @@ -71,7 +71,7 @@ if ($existingTag) {
# Create and push tag
git tag $version
git push origin $version
Write-Host "Tag $version pushed."
Write-Information "Tag $version pushed." -InformationAction Continue

if ($SkipPublishVerification) {
Write-Warning "Not waiting for the release run (-SkipPublishVerification). Nothing has confirmed that a package reached nuget.org."
Expand All @@ -82,7 +82,7 @@ if ($SkipPublishVerification) {
$originUrl = git remote get-url origin
$repoFullName = ($originUrl -replace '^.*github\.com[:/]', '') -replace '\.git$', ''

Write-Host "Waiting for the release run for $version..."
Write-Information "Waiting for the release run for $version..." -InformationAction Continue

# The run takes a few seconds to appear after the tag push.
$runId = $null
Expand All @@ -100,28 +100,28 @@ if (-not $runId) {
exit 1
}

Write-Host "Run: https://github.com/$repoFullName/actions/runs/$runId"
Write-Information "Run: https://github.com/$repoFullName/actions/runs/$runId" -InformationAction Continue
gh run watch $runId --repo $repoFullName --exit-status --interval 20
$runExitCode = $LASTEXITCODE

if ($runExitCode -ne 0) {
Write-Host ""
Write-Host "The release run did not succeed: https://github.com/$repoFullName/actions/runs/$runId" -ForegroundColor Red
Write-Information "" -InformationAction Continue
Write-Information "The release run did not succeed: https://github.com/$repoFullName/actions/runs/$runId" -InformationAction Continue

# A refused job — an exhausted Actions budget, for instance — fails before any step runs, so it
# has no failed step to report. The check-run annotation is the only place the reason appears.
$jobId = gh api "repos/$repoFullName/actions/runs/$runId/jobs" --jq '.jobs[0].id' 2>$null
if ($LASTEXITCODE -eq 0 -and $jobId) {
$annotation = gh api "repos/$repoFullName/check-runs/$jobId/annotations" --jq '.[0].message' 2>$null
if ($LASTEXITCODE -eq 0 -and $annotation) {
Write-Host "Reason: $annotation" -ForegroundColor Red
Write-Information "Reason: $annotation" -InformationAction Continue
}
}

Write-Host ""
Write-Host "Tag $version is pushed but no package was published. Once the cause is fixed:" -ForegroundColor Yellow
Write-Host " gh run rerun $runId --repo $repoFullName --failed" -ForegroundColor Cyan
Write-Information "" -InformationAction Continue
Write-Information "Tag $version is pushed but no package was published. Once the cause is fixed:" -InformationAction Continue
Write-Information " gh run rerun $runId --repo $repoFullName --failed" -InformationAction Continue
exit 1
}

Write-Host "Package $version published." -ForegroundColor Green
Write-Information "Package $version published." -InformationAction Continue
3 changes: 2 additions & 1 deletion SchemaMagic.Core/CoreSchemaAnalysisService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,6 @@ private static int SearchProjectForEntities(string projectPath, IEnumerable<stri
catch (Exception)
{
// Silently skip files that can't be parsed
continue;
}
}
}
Expand All @@ -567,7 +566,9 @@ private static int SearchProjectForEntities(string projectPath, IEnumerable<stri
private static string? FindSolutionDirectory(string? startDirectory)
{
if (string.IsNullOrEmpty(startDirectory))
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ LOW RISK

Suggestion: The added null/empty check for 'startDirectory' has no test coverage. Given that this file is identified as complex and has decreasing overall coverage, please add a unit test to verify this boundary condition.

See Coverage in Codacy
See Complexity in Codacy

return null;
}

var currentDirectory = new DirectoryInfo(startDirectory);

Expand Down
9 changes: 3 additions & 6 deletions SchemaMagic.Web/Services/GitHubService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,7 @@ public async Task<List<DbContextInfo>> FindDbContextFilesAsync(string repoUrl, s
{
throw new InvalidOperationException("GitHub API rate limit exceeded. Please provide a Personal Access Token for higher limits, or try again later.");
}
catch (InvalidOperationException)
{
// Re-throw our own exceptions
throw;
}
catch (Exception ex)
catch (Exception ex) when (ex is not InvalidOperationException)
{
throw new InvalidOperationException($"Error accessing GitHub repository: {ex.Message}");
}
Expand Down Expand Up @@ -229,7 +224,9 @@ private static (string Owner, string Name)? ParseGitHubUrl(string url)
{
var uri = new Uri(url);
if (uri.Host != "github.com")
{
return null;
}

var segments = uri.AbsolutePath.Trim('/').Split('/');
if (segments.Length >= 2)
Expand Down
58 changes: 29 additions & 29 deletions SchemaMagic.Web/wwwroot/js/app.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,43 @@
window.openSchemaInNewTab = (htmlContent) => {
const blob = new Blob([htmlContent], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const newWindow = window.open(url, '_blank');
// Clean up the URL after a delay
setTimeout(() => URL.revokeObjectURL(url), 1000);
const blob = new Blob([htmlContent], { type: "text/html" });
const url = URL.createObjectURL(blob);
const newWindow = window.open(url, "_blank");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ LOW RISK

Nitpick: Remove the unused newWindow variable assignment. If the intent is to verify if the window opened, add a null check instead.

See Issue in Codacy


// Clean up the URL after a delay
setTimeout(() => URL.revokeObjectURL(url), 1000);
};

window.downloadFile = (fileName, content, contentType) => {
const blob = new Blob([content], { type: contentType });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
const blob = new Blob([content], { type: contentType });
const url = URL.createObjectURL(blob);

const link = document.createElement("a");
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);

URL.revokeObjectURL(url);
};

// Set focus on repository input when page loads
window.setRepositoryInputFocus = () => {
const repoInput = document.getElementById('repoUrl');
if (repoInput) {
repoInput.focus();
}
const repoInput = document.getElementById("repoUrl");
if (repoInput) {
repoInput.focus();
}
};

// Enhanced error handling
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason);
window.addEventListener("unhandledrejection", (event) => {
console.error("Unhandled promise rejection:", event.reason);
});

// Performance monitoring
window.addEventListener('load', () => {
if ('performance' in window) {
const loadTime = performance.now();
console.log(`SchemaMagic Web loaded in ${loadTime.toFixed(2)}ms`);
}
});
window.addEventListener("load", () => {
if ("performance" in window) {
const loadTime = performance.now();
console.log(`SchemaMagic Web loaded in ${loadTime.toFixed(2)}ms`);
}
});
81 changes: 55 additions & 26 deletions SchemaMagic.Web/wwwroot/service-worker.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,68 @@
// Caution! Be sure you understand the caveats before publishing an application with
// offline support. See https://aka.ms/blazor-offline-considerations

self.importScripts('./service-worker-assets.js');
self.addEventListener('install', event => event.waitUntil(onInstall(event)));
self.addEventListener('fetch', event => event.respondWith(onFetch(event)));
self.importScripts("./service-worker-assets.js");
self.addEventListener("install", (event) => event.waitUntil(onInstall(event)));
self.addEventListener("fetch", (event) => event.respondWith(onFetch(event)));

const cacheNamePrefix = 'schemamagic-web-cache-';
const cacheNamePrefix = "schemamagic-web-cache-";
const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/ ];
const offlineAssetsExclude = [ /^service-worker\.js$/ ];
const offlineAssetsInclude = [
/\.dll$/,
/\.pdb$/,
/\.wasm/,
/\.html/,
/\.js$/,
/\.json$/,
/\.css$/,
/\.woff$/,
/\.png$/,
/\.jpe?g$/,
/\.gif$/,
/\.ico$/,
/\.blat$/,
/\.dat$/,
];
const offlineAssetsExclude = [/^service-worker\.js$/];

async function onInstall(event) {
console.info('Service worker: Install');
console.info("Service worker: Install");

// Fetch and cache all matching items from the assets manifest
const assetsRequests = self.assetsManifest.assets
.filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url)))
.filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url)))
.map(asset => new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' }));
await caches.open(cacheName).then(cache => cache.addAll(assetsRequests));
// Fetch and cache all matching items from the assets manifest
const assetsRequests = self.assetsManifest.assets
.filter((asset) =>
offlineAssetsInclude.some((pattern) => pattern.test(asset.url)),
)
.filter(
(asset) =>
!offlineAssetsExclude.some((pattern) =>
pattern.test(asset.url),
),
)
.map(
(asset) =>
new Request(asset.url, {
integrity: asset.hash,
cache: "no-cache",
}),
);
await caches.open(cacheName).then((cache) => cache.addAll(assetsRequests));
}

async function onFetch(event) {
let cachedResponse = null;
if (event.request.method === 'GET') {
// For all navigation requests, try to serve index.html from cache
// If you need some URLs to be server-rendered, edit the following check to exclude those URLs
const shouldServeIndexHtml = event.request.mode === 'navigate'
&& !event.request.url.includes('/api/')
&& !event.request.url.includes('/_framework/');
let cachedResponse = null;
if (event.request.method === "GET") {
// For all navigation requests, try to serve index.html from cache
// If you need some URLs to be server-rendered, edit the following check to exclude those URLs
const shouldServeIndexHtml =
event.request.mode === "navigate" &&
!event.request.url.includes("/api/") &&
!event.request.url.includes("/_framework/");

const request = shouldServeIndexHtml ? 'index.html' : event.request;
const cache = await caches.open(cacheName);
cachedResponse = await cache.match(request);
}
const request = shouldServeIndexHtml ? "index.html" : event.request;
const cache = await caches.open(cacheName);
cachedResponse = await cache.match(request);
}

return cachedResponse || fetch(event.request);
}
return cachedResponse || fetch(event.request);
}
12 changes: 7 additions & 5 deletions SchemaMagic/GitHubService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -201,11 +201,9 @@ private async Task<Dictionary<string, string>> FindEntityFilesAsync(
{
// Prioritize files in common entity directories
var path = item.Path.ToLower();
if (path.Contains("/models/")) return 0;
if (path.Contains("/entities/")) return 1;
if (path.Contains("/domain/")) return 2;
if (path.Contains("/data/")) return 3;
return 4;
var priorityDirectories = new[] { "/models/", "/entities/", "/domain/", "/data/" };
var priority = Array.FindIndex(priorityDirectories, directory => path.Contains(directory));
return priority < 0 ? priorityDirectories.Length : priority;
})
.ThenBy(item => item.Path)
.Take(500) // Limit to prevent too many API calls
Expand All @@ -218,7 +216,9 @@ private async Task<Dictionary<string, string>> FindEntityFilesAsync(
{
// Stop if we found all entities
if (remainingEntities.Count == 0)
{
break;
}

try
{
Expand Down Expand Up @@ -350,7 +350,9 @@ private static (string Owner, string Name)? ParseGitHubUrl(string url)
{
var uri = new Uri(url);
if (uri.Host != "github.com")
{
return null;
}

var segments = uri.AbsolutePath.Trim('/').Split('/');
if (segments.Length >= 2)
Expand Down
4 changes: 2 additions & 2 deletions SchemaMagic/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace SchemaMagic;

internal class Program
internal static class Program
{
static async Task<int> Main(string[] args)
{
Expand Down Expand Up @@ -109,7 +109,7 @@ GitHub repository (private with PAT):
📦 NuGet: https://www.nuget.org/packages/SchemaMagic
";

rootCommand.SetAction(async (parseResult, cancellationToken) =>
rootCommand.SetAction(async (parseResult, _) =>
{
var dbContextFile = parseResult.GetValue(dbContextFileArgument);
var githubRepo = parseResult.GetValue(githubRepoOption);
Expand Down
8 changes: 2 additions & 6 deletions SchemaMagic/SchemaGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,6 @@ public string GenerateSchemaVisualization()
Console.WriteLine("============================================================");
Console.WriteLine($"🔍 Processing DbContext: {Path.GetFileName(_dbContextPath)}");

// Read DbContext content
var sourceCode = File.ReadAllText(_dbContextPath);
var fileName = Path.GetFileName(_dbContextPath);

// Use the Core library for analysis with the actual file path for better entity discovery
var result = CoreSchemaAnalysisService.AnalyzeDbContextFile(_dbContextPath);

Expand Down Expand Up @@ -120,8 +116,8 @@ public string GenerateSchemaVisualization()
{
var customCss = File.ReadAllText(_customCssPath);
var entitiesJson = JsonSerializer.Serialize(result.Entities, _jsonSerializerOptions);
var documentGuid = _documentGuid ?? Guid.NewGuid().ToString();
htmlContent = ModularHtmlTemplate.GenerateWithCustomCss(entitiesJson, documentGuid, customCss);
var customCssGuid = _documentGuid ?? Guid.NewGuid().ToString();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 HIGH RISK

Use the deterministic GUID from the analysis result to ensure layout persistence when custom CSS is applied. Using Guid.NewGuid() here breaks the tool's ability to retrieve saved table positions from localStorage, as the document GUID will change on every regeneration.

htmlContent = ModularHtmlTemplate.GenerateWithCustomCss(entitiesJson, customCssGuid, customCss);
}
else
{
Expand Down
2 changes: 1 addition & 1 deletion SimpleTestDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public class User
public bool IsActive { get; set; } = true;

[Comment("Email verification status - true when user confirms email")]
public bool EmailVerified { get; set; } = false;
public bool EmailVerified { get; set; }

/// <summary>
/// Timestamp of the user's last successful login
Expand Down
Loading
Loading