Skip to content

feat: compose and .env template#231

Merged
kmendell merged 17 commits intomainfrom
feat/templates
May 29, 2025
Merged

feat: compose and .env template#231
kmendell merged 17 commits intomainfrom
feat/templates

Conversation

@kmendell
Copy link
Member

@kmendell kmendell commented May 28, 2025

Summary by CodeRabbit

  • New Features
    • Introduced a comprehensive template system for Docker Compose stacks, enabling users to select, use, and manage both local and remote templates.
    • Added a template selection dialog for streamlined stack creation.
    • Implemented template registry management, allowing users to add, refresh, and remove remote template registries from settings.
    • Provided a community template registry with example templates for popular applications.
  • Improvements
    • Enhanced documentation with detailed guides on creating and using templates and registries.
    • Updated UI components for dialogs and sidebar navigation.
    • Improved theming and compatibility with Tailwind CSS v4.
  • Bug Fixes
    • Fixed border color consistency for Tailwind CSS v4 compatibility.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 28, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

This update introduces a comprehensive Docker Compose template management system. It adds support for local and remote template registries, new API endpoints for template operations, and extensive UI components for template selection and registry management. The change includes new documentation, configuration, and type definitions, as well as CSS and theme adjustments for improved consistency and compatibility.

Changes

File(s) Change Summary
components.json Tailwind base color changed from "gray" to "violet".
package.json Added @internationalized/date to devDependencies.
docs/docs/templates/create-a-registry.md, docs/docs/templates/use-templates.md Added comprehensive guides for creating registries and using templates.
docs/sidebars.ts Added "Templates" category to documentation sidebar; updated import style and formatting.
src/app.css Added Tailwind v4 border color fix, removed legacy color blocks, reorganized and expanded theme CSS variables for radius and colors.
src/lib/components/arcane-button.svelte Added 'template' action type and corresponding icon/config for template usage.
src/lib/components/sidebar.svelte Added "Templates" menu item under settings with icon and link.
src/lib/components/template-selection-dialog.svelte New dialog component for selecting and applying Docker Compose templates.
src/lib/components/ui/dialog/dialog-close.svelte, src/lib/components/ui/dialog/dialog-trigger.svelte New wrapper components for dialog close and trigger actions.
src/lib/components/ui/dialog/dialog-content.svelte, src/lib/components/ui/dialog/dialog-description.svelte, src/lib/components/ui/dialog/dialog-footer.svelte, src/lib/components/ui/dialog/dialog-header.svelte, src/lib/components/ui/dialog/dialog-overlay.svelte, src/lib/components/ui/dialog/dialog-title.svelte Added or updated data-slot attributes, adjusted CSS classes, improved structure and styling for dialog UI components.
src/lib/components/ui/dialog/index.ts Updated dialog exports to use new local trigger and close components.
src/lib/services/api/template-api-service.ts Added TemplateAPIService class for template-related API operations.
src/lib/services/settings-service.ts, src/lib/stores/settings-store.ts Added templateRegistries property to default settings and store state.
src/lib/services/template-registry-service.ts New TemplateRegistryService class for fetching/caching registries and templates, with validation and conversion methods.
src/lib/services/template-service.ts New TemplateService class for managing local/remote templates, creation, deletion, loading, and registry management.
src/lib/types/settings.type.ts, src/lib/types/template-registry.ts Added TemplateRegistryConfig, TemplateRegistry, and RemoteTemplate interfaces; extended Settings with templateRegistries.
src/routes/+layout.svelte Swapped navigation component from navbar to sidebar.
src/routes/api/templates/+server.ts, src/routes/api/templates/[id]/+server.ts, src/routes/api/templates/[id]/content/+server.ts, src/routes/api/templates/[id]/download/+server.ts, src/routes/api/templates/registries/+server.ts, src/routes/api/templates/registries/test/+server.ts, src/routes/api/templates/stats/+server.ts Added new API routes for template CRUD, content, download, registry management, registry testing, and statistics.
src/routes/settings/templates/+page.server.ts, src/routes/settings/templates/+page.svelte Added server and UI page for managing template settings, registries, and statistics.
src/routes/stacks/new/+page.server.ts, src/routes/stacks/new/+page.svelte Enhanced stack creation page: loads templates, adds template selection dialog, and updates stack creation workflow.
template-registry.json Added a community Docker Compose template registry JSON file with two template entries.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented May 28, 2025

Deploying arcane with  Cloudflare Pages  Cloudflare Pages

Latest commit: d292c59
Status: ✅  Deploy successful!
Preview URL: https://3d1ca100.arcane-7v3.pages.dev
Branch Preview URL: https://feat-templates.arcane-7v3.pages.dev

View logs

@kmendell kmendell marked this pull request as ready for review May 28, 2025 22:48
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 18

🧹 Nitpick comments (16)
src/routes/settings/templates/+page.server.ts (2)

3-3: Remove unused import.

The templateRegistryService import is not used anywhere in this file and should be removed to keep the code clean.

-import { templateRegistryService } from '$lib/services/template-registry-service';

23-32: Improve error handling robustness.

The error handling calls getSettings() again in the catch block, which could potentially fail as well. Consider using a more robust fallback or caching the settings from a previous successful load.

 } catch (error) {
 	console.error('Error loading template settings:', error);
-	const fallbackSettings = await getSettings();
+	let fallbackSettings;
+	try {
+		fallbackSettings = await getSettings();
+	} catch (settingsError) {
+		console.error('Error loading fallback settings:', settingsError);
+		// Use minimal default settings or handle gracefully
+		fallbackSettings = { templateRegistries: [] }; // Minimal fallback
+	}
 	return {
 		settings: fallbackSettings,
 		localTemplateCount: 0,
 		remoteTemplateCount: 0,
 		error: error instanceof Error ? error.message : 'Failed to load template data'
 	};
 }
src/routes/api/templates/registries/test/+server.ts (1)

19-23: Enhance error handling specificity.

Consider providing more specific error messages to help users troubleshoot registry issues.

-		if (!registry) {
-			return error(400, { message: 'Failed to fetch registry or invalid format' });
-		}
+		if (!registry) {
+			return error(400, { 
+				message: 'Registry validation failed',
+				details: 'Unable to fetch registry or invalid JSON format. Please verify the URL and ensure it returns a valid template registry.'
+			});
+		}
src/routes/api/templates/[id]/+server.ts (2)

5-20: Add input validation for template ID.

Consider validating the template ID format and checking existence more explicitly.

 export const GET: RequestHandler = async ({ params }) => {
 	try {
 		const { id } = params;
+		
+		if (!id || typeof id !== 'string') {
+			return error(400, { message: 'Invalid template ID' });
+		}
+		
 		const templates = await TemplateService.getComposeTemplates();
 		const template = templates.find((t) => t.id === id);

45-58: Add existence check for DELETE operation.

The DELETE operation should verify the template exists before attempting deletion.

 export const DELETE: RequestHandler = async ({ params }) => {
 	try {
 		const { id } = params;
+		
+		// Verify template exists
+		const templates = await TemplateService.getComposeTemplates();
+		const template = templates.find((t) => t.id === id);
+		
+		if (!template) {
+			return error(404, { message: 'Template not found' });
+		}
+		
 		await TemplateService.deleteTemplate(id);
src/routes/api/templates/registries/+server.ts (1)

7-7: Consider using singleton pattern for TemplateService.

Creating a new instance of TemplateService here while using templateRegistryService as a singleton creates inconsistency. Consider following the same pattern for both services.

-const templateService = new TemplateService();
+import { templateService } from '$lib/services/template-service';

Or ensure both services follow the same instantiation pattern throughout the codebase.

docs/docs/templates/create-a-registry.md (2)

15-37: Fix JSON formatting: Replace hard tabs with spaces.

The JSON example uses hard tabs which violates markdown formatting standards and could cause display issues.

Apply this diff to replace tabs with 2-space indentation:

 {
-	"name": "My Company Templates",
-	"description": "Docker templates for internal applications",
-	"version": "1.0.0",
-	"author": "Your Team",
-	"url": "https://github.com/yourcompany/docker-templates",
-	"templates": [
-		{
-			"id": "internal-app",
-			"name": "Internal Application",
-			"description": "Company application stack with database",
-			"version": "1.0.0",
-			"author": "DevOps Team",
-			"compose_url": "https://raw.githubusercontent.com/yourcompany/docker-templates/main/internal-app/docker-compose.yml",
-			"env_url": "https://raw.githubusercontent.com/yourcompany/docker-templates/main/internal-app/.env.example",
-			"documentation_url": "https://github.com/yourcompany/docker-templates/tree/main/internal-app",
-			"tags": ["internal", "webapp", "postgres"],
-			"updated_at": "2024-12-01T10:00:00Z"
-		}
-	]
+  "name": "My Company Templates",
+  "description": "Docker templates for internal applications",
+  "version": "1.0.0",
+  "author": "Your Team",
+  "url": "https://github.com/yourcompany/docker-templates",
+  "templates": [
+    {
+      "id": "internal-app",
+      "name": "Internal Application",
+      "description": "Company application stack with database",
+      "version": "1.0.0",
+      "author": "DevOps Team",
+      "compose_url": "https://raw.githubusercontent.com/yourcompany/docker-templates/main/internal-app/docker-compose.yml",
+      "env_url": "https://raw.githubusercontent.com/yourcompany/docker-templates/main/internal-app/.env.example",
+      "documentation_url": "https://github.com/yourcompany/docker-templates/tree/main/internal-app",
+      "tags": ["internal", "webapp", "postgres"],
+      "updated_at": "2024-12-01T10:00:00Z"
+    }
+  ]
 }
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

17-17: Hard tabs
Column: 1

(MD010, no-hard-tabs)


18-18: Hard tabs
Column: 1

(MD010, no-hard-tabs)


19-19: Hard tabs
Column: 1

(MD010, no-hard-tabs)


20-20: Hard tabs
Column: 1

(MD010, no-hard-tabs)


21-21: Hard tabs
Column: 1

(MD010, no-hard-tabs)


22-22: Hard tabs
Column: 1

(MD010, no-hard-tabs)


23-23: Hard tabs
Column: 1

(MD010, no-hard-tabs)


24-24: Hard tabs
Column: 1

(MD010, no-hard-tabs)


25-25: Hard tabs
Column: 1

(MD010, no-hard-tabs)


26-26: Hard tabs
Column: 1

(MD010, no-hard-tabs)


27-27: Hard tabs
Column: 1

(MD010, no-hard-tabs)


28-28: Hard tabs
Column: 1

(MD010, no-hard-tabs)


29-29: Hard tabs
Column: 1

(MD010, no-hard-tabs)


30-30: Hard tabs
Column: 1

(MD010, no-hard-tabs)


31-31: Hard tabs
Column: 1

(MD010, no-hard-tabs)


32-32: Hard tabs
Column: 1

(MD010, no-hard-tabs)


33-33: Hard tabs
Column: 1

(MD010, no-hard-tabs)


34-34: Hard tabs
Column: 1

(MD010, no-hard-tabs)


35-35: Hard tabs
Column: 1

(MD010, no-hard-tabs)


58-64: Add language specifiers to code blocks.

The markdown linter flagged missing language specifiers for better syntax highlighting and accessibility.

Apply these diffs to add language specifiers:

-```
+```text
 your-template/
 ├── docker-compose.yml    # Required: Main compose file
 ├── .env.example         # Optional: Environment variables
 └── README.md           # Optional: Documentation

```diff
-```
+```text
 docker-templates/
 ├── registry.json
 ├── wordpress/
 │   ├── docker-compose.yml
 │   ├── .env.example
 │   └── README.md
 ├── nextcloud/
 │   ├── docker-compose.yml
 │   ├── .env.example
 │   └── README.md
 └── nginx-proxy/
     ├── docker-compose.yml
     └── README.md


Also applies to: 95-109

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

58-58: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

</details>

</details>

</blockquote></details>
<details>
<summary>src/lib/services/template-registry-service.ts (1)</summary><blockquote>

`6-9`: **Consider implementing cache size management.**

The current implementation doesn't limit cache size, which could lead to memory issues over time if many registries are fetched.

Consider adding a maximum cache size and implementing an LRU (Least Recently Used) eviction policy:

```diff
 export class TemplateRegistryService {
 	private cache = new Map<string, { data: TemplateRegistry; timestamp: number }>();
 	private readonly defaultCacheTtl = 3600; // 1 hour
+	private readonly maxCacheSize = 50; // Maximum number of cached registries
+
+	private evictOldestEntry(): void {
+		if (this.cache.size <= this.maxCacheSize) return;
+		
+		let oldestKey: string | undefined;
+		let oldestTime = Infinity;
+		
+		for (const [key, value] of this.cache.entries()) {
+			if (value.timestamp < oldestTime) {
+				oldestTime = value.timestamp;
+				oldestKey = key;
+			}
+		}
+		
+		if (oldestKey) {
+			this.cache.delete(oldestKey);
+		}
+	}
src/routes/settings/templates/+page.svelte (2)

74-75: Remove console.log statements from production code.

Debug logging should be removed or replaced with proper logging utilities.

-			console.log('Testing registry config:', config);
			const registry = await templateRegistryService.fetchRegistry(config);
			
-			console.log('Registry test successful, adding to store');

Also applies to: 81-81


166-166: Remove debug logging of settings.

Logging the entire settings store could potentially expose sensitive configuration in production.

-		console.log('Saving settings to server:', $settingsStore);
src/lib/services/template-service.ts (2)

28-31: Replace this with class name in static context.

Using this in static properties can be confusing as it refers to the class itself. It's clearer to use the class name explicitly.

 export class TemplateService {
 	private static templatesDir = path.join(process.cwd(), 'data/templates');
-	private static composeTemplatesDir = path.join(this.templatesDir, 'compose');
-	private static envTemplateFile = path.join(this.templatesDir, '.env.template');
+	private static composeTemplatesDir = path.join(TemplateService.templatesDir, 'compose');
+	private static envTemplateFile = path.join(TemplateService.templatesDir, '.env.template');

404-413: Improve error handling robustness.

The error handling assumes error.message exists after checking error instanceof Error, but if the check fails, accessing error.message would throw.

 			if (template.metadata.envUrl) {
 				try {
 					envContent = (await templateRegistryService.fetchEnvContent(template.metadata.envUrl)) || undefined;
 				} catch (error) {
 					// Only log if it's not a 404 - missing env files are expected
-					if (error instanceof Error && !error.message.includes('404') && !error.message.includes('Not Found')) {
+					if (error instanceof Error) {
+						if (!error.message.includes('404') && !error.message.includes('Not Found')) {
+							console.error('Error fetching env content:', error);
+						}
+					} else {
 						console.error('Error fetching env content:', error);
 					}
 					envContent = undefined;
 				}
 			}
src/lib/components/template-selection-dialog.svelte (3)

20-20: Consider using dependency injection or a singleton for the API service.

Creating a new TemplateAPIService instance for each component instance could lead to unnecessary overhead. Consider injecting this as a dependency or using a singleton pattern.

-	const templateAPI = new TemplateAPIService();
+	import { templateAPI } from '$lib/services/api/template-api-service';

Or if using dependency injection:

+	interface Props {
+		open: boolean;
+		templates: ComposeTemplate[];
+		onSelect: (template: ComposeTemplate) => void;
+		templateAPI?: TemplateAPIService;
+	}
+
-	let { open = $bindable(), templates, onSelect }: Props = $props();
+	let { open = $bindable(), templates, onSelect, templateAPI = new TemplateAPIService() }: Props = $props();

27-28: Reduce code duplication in loading state management.

The pattern loadingStates = new Map(loadingStates) is repeated multiple times to trigger Svelte reactivity. Consider extracting this into a helper function to reduce duplication and improve maintainability.

+	function updateLoadingStates() {
+		loadingStates = new Map(loadingStates);
+	}
+
	async function handleSelect(template: ComposeTemplate) {
		// Set loading state
		loadingStates.set(template.id, true);
-		loadingStates = new Map(loadingStates);
+		updateLoadingStates();

		// ... rest of function

		} finally {
			// Clear loading state
			loadingStates.delete(template.id);
-			loadingStates = new Map(loadingStates);
+			updateLoadingStates();
		}
	}

Also applies to: 58-59, 67-68, 91-92


251-258: Consider using modern CSS line-clamp property.

The webkit-specific line-clamp implementation may not work in all browsers. Modern browsers now support the standard line-clamp property.

	.line-clamp-2 {
-		display: -webkit-box;
-		-webkit-line-clamp: 2;
-		-webkit-box-orient: vertical;
+		display: -webkit-box; /* Fallback for older browsers */
+		-webkit-line-clamp: 2; /* Fallback for older browsers */
+		-webkit-box-orient: vertical; /* Fallback for older browsers */
+		line-clamp: 2; /* Modern standard */
		overflow: hidden;
	}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e92e822 and d292c59.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (38)
  • components.json (1 hunks)
  • docs/docs/templates/create-a-registry.md (1 hunks)
  • docs/docs/templates/use-templates.md (1 hunks)
  • docs/sidebars.ts (1 hunks)
  • package.json (1 hunks)
  • src/app.css (2 hunks)
  • src/lib/components/arcane-button.svelte (2 hunks)
  • src/lib/components/sidebar.svelte (2 hunks)
  • src/lib/components/template-selection-dialog.svelte (1 hunks)
  • src/lib/components/ui/dialog/dialog-close.svelte (1 hunks)
  • src/lib/components/ui/dialog/dialog-content.svelte (2 hunks)
  • src/lib/components/ui/dialog/dialog-description.svelte (1 hunks)
  • src/lib/components/ui/dialog/dialog-footer.svelte (1 hunks)
  • src/lib/components/ui/dialog/dialog-header.svelte (1 hunks)
  • src/lib/components/ui/dialog/dialog-overlay.svelte (1 hunks)
  • src/lib/components/ui/dialog/dialog-title.svelte (1 hunks)
  • src/lib/components/ui/dialog/dialog-trigger.svelte (1 hunks)
  • src/lib/components/ui/dialog/index.ts (1 hunks)
  • src/lib/services/api/template-api-service.ts (1 hunks)
  • src/lib/services/settings-service.ts (3 hunks)
  • src/lib/services/template-registry-service.ts (1 hunks)
  • src/lib/services/template-service.ts (1 hunks)
  • src/lib/stores/settings-store.ts (1 hunks)
  • src/lib/types/settings.type.ts (2 hunks)
  • src/lib/types/template-registry.ts (1 hunks)
  • src/routes/+layout.svelte (1 hunks)
  • src/routes/api/templates/+server.ts (1 hunks)
  • src/routes/api/templates/[id]/+server.ts (1 hunks)
  • src/routes/api/templates/[id]/content/+server.ts (1 hunks)
  • src/routes/api/templates/[id]/download/+server.ts (1 hunks)
  • src/routes/api/templates/registries/+server.ts (1 hunks)
  • src/routes/api/templates/registries/test/+server.ts (1 hunks)
  • src/routes/api/templates/stats/+server.ts (1 hunks)
  • src/routes/settings/templates/+page.server.ts (1 hunks)
  • src/routes/settings/templates/+page.svelte (1 hunks)
  • src/routes/stacks/new/+page.server.ts (1 hunks)
  • src/routes/stacks/new/+page.svelte (8 hunks)
  • template-registry.json (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (7)
src/routes/stacks/new/+page.server.ts (2)
src/lib/services/template-service.ts (1)
  • TemplateService (28-498)
src/lib/constants.ts (1)
  • defaultComposeTemplate (20-35)
src/routes/settings/templates/+page.server.ts (4)
src/routes/stacks/new/+page.server.ts (1)
  • load (5-26)
src/lib/services/template-service.ts (1)
  • TemplateService (28-498)
src/lib/stores/settings-store.ts (1)
  • getSettings (62-64)
src/lib/services/settings-service.ts (1)
  • getSettings (104-184)
src/routes/api/templates/[id]/content/+server.ts (2)
src/lib/services/template-service.ts (1)
  • TemplateService (28-498)
src/routes/api/templates/[id]/+server.ts (1)
  • GET (5-20)
src/routes/api/templates/stats/+server.ts (5)
src/lib/services/template-service.ts (1)
  • TemplateService (28-498)
src/routes/api/templates/[id]/content/+server.ts (1)
  • GET (7-32)
src/routes/api/templates/[id]/+server.ts (1)
  • GET (5-20)
src/routes/api/templates/+server.ts (1)
  • GET (7-88)
src/routes/api/templates/registries/+server.ts (1)
  • GET (9-17)
src/lib/types/settings.type.ts (1)
src/lib/types/template-registry.ts (1)
  • TemplateRegistryConfig (22-28)
src/lib/types/template-registry.ts (1)
src/lib/types/settings.type.ts (1)
  • TemplateRegistryConfig (36-42)
src/lib/services/template-registry-service.ts (3)
src/lib/types/template-registry.ts (3)
  • TemplateRegistry (1-6)
  • TemplateRegistryConfig (22-28)
  • RemoteTemplate (8-20)
src/lib/types/settings.type.ts (1)
  • TemplateRegistryConfig (36-42)
src/lib/services/template-service.ts (1)
  • ComposeTemplate (7-26)
🪛 LanguageTool
docs/docs/templates/create-a-registry.md

[uncategorized] ~69-~69: Loose punctuation mark.
Context: ...Reference ### Required Fields - name: Registry display name - description: ...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~78-~78: Loose punctuation mark.
Context: ...ate Object Fields Required: - id: Unique identifier (alphanumeric, hyphen...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~87-~87: Loose punctuation mark.
Context: ...601 timestamp Optional: - author: Template creator - tags: Array of key...

(UNLIKELY_OPENING_PUNCTUATION)


[grammar] ~115-~115: It seems that “to” is missing before the verb.
Context: ...nsure all file URLs are accessible 3. Add to Arcane: Settings → Templates → Add...

(MISSING_TO_BETWEEN_BE_AND_VB)

🪛 markdownlint-cli2 (0.17.2)
docs/docs/templates/create-a-registry.md

17-17: Hard tabs
Column: 1

(MD010, no-hard-tabs)


18-18: Hard tabs
Column: 1

(MD010, no-hard-tabs)


19-19: Hard tabs
Column: 1

(MD010, no-hard-tabs)


20-20: Hard tabs
Column: 1

(MD010, no-hard-tabs)


21-21: Hard tabs
Column: 1

(MD010, no-hard-tabs)


22-22: Hard tabs
Column: 1

(MD010, no-hard-tabs)


23-23: Hard tabs
Column: 1

(MD010, no-hard-tabs)


24-24: Hard tabs
Column: 1

(MD010, no-hard-tabs)


25-25: Hard tabs
Column: 1

(MD010, no-hard-tabs)


26-26: Hard tabs
Column: 1

(MD010, no-hard-tabs)


27-27: Hard tabs
Column: 1

(MD010, no-hard-tabs)


28-28: Hard tabs
Column: 1

(MD010, no-hard-tabs)


29-29: Hard tabs
Column: 1

(MD010, no-hard-tabs)


30-30: Hard tabs
Column: 1

(MD010, no-hard-tabs)


31-31: Hard tabs
Column: 1

(MD010, no-hard-tabs)


32-32: Hard tabs
Column: 1

(MD010, no-hard-tabs)


33-33: Hard tabs
Column: 1

(MD010, no-hard-tabs)


34-34: Hard tabs
Column: 1

(MD010, no-hard-tabs)


35-35: Hard tabs
Column: 1

(MD010, no-hard-tabs)


41-41: Emphasis used instead of a heading
null

(MD036, no-emphasis-as-heading)


48-48: Emphasis used instead of a heading
null

(MD036, no-emphasis-as-heading)


58-58: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


95-95: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

docs/docs/templates/use-templates.md

46-46: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

🪛 Biome (1.9.4)
src/lib/services/template-service.ts

[error] 39-39: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 41-41: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 47-47: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 52-52: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 55-55: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 59-59: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 79-79: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 93-93: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 94-94: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 97-97: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 105-105: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 108-108: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 121-121: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 134-134: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 146-146: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 161-161: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 168-168: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 176-176: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 177-177: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 180-180: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 184-184: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 255-255: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (60)
package.json (1)

25-25:

Details

✅ Verification successful

Validate placement of @internationalized/date
The @internationalized/date package is currently added under devDependencies. If you're using it in runtime UI code (e.g., components under src/), it should live in dependencies to be included in production builds. Otherwise, keep it here for tooling/tests only.

Run this to confirm where it’s imported:


🏁 Script executed:

rg -n "@internationalized/date" -A1 src/

Length of output: 40


🏁 Script executed:

#!/bin/bash
# Search for any imports/usages of @internationalized/date across the entire repo
rg -n '@internationalized/date' -A1 .

# Confirm its declaration location in package.json
grep -n '"@internationalized/date"' package.json

Length of output: 848


🏁 Script executed:

#!/bin/bash
# Show the dependencies and devDependencies blocks in package.json to confirm where @internationalized/date is declared
echo "=== dependencies ==="
rg -n '"dependencies"' -A10 package.json
echo ""
echo "=== devDependencies ==="
rg -n '"devDependencies"' -A10 package.json

Length of output: 959


No runtime imports of @internationalized/date detected
We searched the entire repo (rg -n '@internationalized/date' -A1 .) and confirmed the only references are in package.json (devDependencies) and package-lock.json. There are no imports in src/ or any other code.

• Keeping it under devDependencies is correct.
• (Optional) You may remove it entirely if it’s not used for tooling/tests.

components.json (1)

6-6: LGTM! Theme color update aligns with new template features.

The change from "gray" to "violet" as the base color provides a fresh visual identity that complements the new template management functionality being introduced.

src/lib/components/ui/dialog/dialog-description.svelte (1)

8-8: Good addition for component standardization.

Adding the data-slot="dialog-description" attribute improves component identification and follows the pattern being applied across other dialog components.

src/lib/stores/settings-store.ts (1)

36-36: Proper settings store extension for template functionality.

The addition of templateRegistries: [] correctly extends the settings structure to support the new template registry management features, following the established pattern.

src/lib/components/ui/dialog/dialog-overlay.svelte (1)

8-8: Good improvements to dialog overlay.

Both changes enhance the component:

  • The data-slot="dialog-overlay" attribute maintains consistency with other dialog components
  • Reducing opacity from 80% to 50% improves background visibility while maintaining focus
src/lib/components/sidebar.svelte (2)

2-2: Proper icon import for template navigation.

The LayoutTemplate icon import is correctly added to support the new Templates menu item.


40-44: Excellent integration of template navigation.

The new Templates menu item is perfectly implemented:

  • Follows the existing settings submenu structure
  • Uses appropriate icon and URL pattern
  • Logically placed within the Settings section
src/lib/components/ui/dialog/dialog-title.svelte (1)

8-8:

Details

✅ Verification successful

Good addition of data-slot attribute for component targeting.

The addition of data-slot="dialog-title" improves component identification and enables better styling consistency across dialog components. However, verify that removing tracking-tight doesn't negatively impact the visual design of dialog titles.


🏁 Script executed:

#!/bin/bash
# Description: Check if tracking-tight is used elsewhere in dialog components for consistency
# Expected: Verify if other dialog components use tracking-tight or if this removal is part of design system updates

echo "Searching for tracking-tight usage in dialog components..."
fd "dialog.*\.svelte$" src/lib/components/ui/dialog/ --exec rg -H "tracking-tight" {}

echo -e "\nSearching for data-slot usage patterns in dialog components..."
fd "dialog.*\.svelte$" src/lib/components/ui/dialog/ --exec rg -H "data-slot" {}

Length of output: 1835


Approve dialog-title.svelte changes

  • Added data-slot="dialog-title" to align with other dialog subcomponents.
  • Confirmed via search that no other dialog-*.svelte files use the tracking-tight class, so its removal keeps styling consistent across the component suite.

All set!

src/routes/+layout.svelte (1)

4-4:

Details

❓ Verification inconclusive

Verify sidebar component provides equivalent functionality to navbar.

The change from navbar to sidebar navigation is a significant UX update that aligns with the template management features. Ensure the sidebar component maintains feature parity with the previous navbar implementation.


🏁 Script executed:

#!/bin/bash
# Description: Verify that both navbar and sidebar components exist and compare their functionality
# Expected: Confirm sidebar component exists and has similar navigation capabilities

echo "Checking if both navigation components exist..."
ls -la src/lib/components/navbar.svelte 2>/dev/null || echo "navbar.svelte not found"
ls -la src/lib/components/sidebar.svelte 2>/dev/null || echo "sidebar.svelte not found"

echo -e "\nChecking sidebar component for navigation features..."
if [ -f "src/lib/components/sidebar.svelte" ]; then
    echo "Sidebar component content preview:"
    head -20 src/lib/components/sidebar.svelte
fi

Length of output: 1514


Verify Sidebar Navigation Feature Parity

Please confirm that replacing the top‐level navbar with the new sidebar covers all previously provided navigation features:

• src/routes/+layout.svelte (line 4): import changed

-import Nav from '$lib/components/navbar.svelte';
+import Nav from '$lib/components/sidebar.svelte';

• src/lib/components/navbar.svelte: file has been removed
• src/lib/components/sidebar.svelte: ensure it provides the same routes, responsive behavior (mobile collapse), user menu, theme toggles, etc., as the old navbar

Let me know once you’ve validated full feature parity.

src/lib/components/ui/dialog/dialog-header.svelte (2)

3-3: Good import consolidation for better readability.

Consolidating related imports on a single line improves code readability and organization.


8-8:

Details

✅ Verification successful

Verify spacing change from space-y to gap works correctly with flex layout.

The changes improve component consistency by adding data-slot="dialog-header" and modernizing spacing with gap-2. However, verify that the change from space-y-1.5 to gap-2 maintains the intended visual spacing in the flex column layout.


🏁 Script executed:

#!/bin/bash
# Description: Check how gap vs space-y is used across other dialog components for consistency
# Expected: Verify consistent spacing approach across dialog components

echo "Checking spacing utilities in dialog components..."
fd "dialog.*\.svelte$" src/lib/components/ui/dialog/ --exec rg -H "(space-[xy]|gap-)" {}

echo -e "\nChecking for flex layout usage in dialog components..."
fd "dialog.*\.svelte$" src/lib/components/ui/dialog/ --exec rg -H "flex.*col" {}

Length of output: 1607


Spacing consistency confirmed
Both dialog-header.svelte and dialog-footer.svelte now use flex-based layouts with gap-2, matching each other and the intended design. No further adjustments are needed.

src/lib/components/ui/dialog/dialog-footer.svelte (2)

2-2: Good import consolidation for better readability.

Consolidating related imports improves code organization and maintainability.


8-8:

Details

✅ Verification successful

Verify responsive spacing behavior after removing sm:space-x-2.

The addition of data-slot="dialog-footer" and modernization to gap-2 improves consistency. However, verify that removing the responsive sm:space-x-2 spacing doesn't negatively impact the mobile layout, as gap-2 applies uniform spacing regardless of screen size.


🏁 Script executed:

#!/bin/bash
# Description: Check responsive spacing patterns across dialog components
# Expected: Verify consistent responsive spacing approach in dialog system

echo "Checking responsive spacing patterns in dialog components..."
fd "dialog.*\.svelte$" src/lib/components/ui/dialog/ --exec rg -H "(sm:|md:|lg:)" {}

echo -e "\nChecking flex-row usage with gap spacing..."
fd "dialog.*\.svelte$" src/lib/components/ui/dialog/ --exec rg -H "flex-row.*gap" {}

Length of output: 1229


Responsive spacing confirmed as safe
The gap-2 utility provides consistent row- and column-axis spacing in dialog-footer (mobile and sm+ layouts), so removing sm:space-x-2 won’t regress the layout. Across dialog components we see:

  • src/lib/components/ui/dialog/dialog-footer.svelte: flex flex-col-reverse gap-2 sm:flex-row sm:justify-end
  • src/lib/components/ui/dialog/dialog-header.svelte: flex flex-col gap-2 text-center sm:text-left
  • src/lib/components/ui/dialog/dialog-content.svelte: grid with gap-4

No further action required.

src/lib/components/ui/dialog/index.ts (1)

9-10: LGTM! Clean refactoring to wrapper components.

The change from importing DialogPrimitive.Trigger and DialogPrimitive.Close to local wrapper components maintains the public API while adding better control through typed props, ref binding, and data-slot attributes. This aligns well with the broader dialog component enhancements.

src/lib/types/settings.type.ts (1)

53-53: LGTM! Template registries integration looks good.

The addition of the templateRegistries field properly integrates template registry management into the settings system.

src/routes/api/templates/[id]/content/+server.ts (1)

7-32: LGTM! Solid error handling and response structure.

The request handler has comprehensive error handling and returns a well-structured response with all necessary template information including content and metadata.

src/lib/components/arcane-button.svelte (2)

3-3: LGTM! Clean import addition.

The LayoutTemplate import aligns well with the template functionality being added.


5-5: LGTM! Proper extension of Action union type.

The addition of 'template' to the Action union type follows the established pattern correctly.

src/routes/settings/templates/+page.server.ts (1)

8-22: LGTM! Well-structured server load function.

The main logic is well-implemented with:

  • Proper async/await usage
  • Clear template counting logic using filter
  • Appropriate return structure for the page
src/lib/components/ui/dialog/dialog-close.svelte (1)

1-7: LGTM! Clean UI primitive wrapper.

This is a well-implemented wrapper component that:

  • Properly types the props using DialogPrimitive.CloseProps
  • Uses the correct $bindable pattern for ref forwarding
  • Includes a useful data-slot attribute for styling
  • Follows Svelte 5 best practices
src/lib/services/settings-service.ts (2)

26-26: LGTM! Proper addition to default settings.

The templateRegistries property is correctly added to the default settings with an appropriate empty array initialization.


131-131: LGTM! Consistent settings loading pattern.

The templateRegistries property is properly integrated into both encrypted and unencrypted settings loading branches, following the same pattern as other settings properties like registryCredentials. The fallback to baseSettings.templateRegistries ensures backward compatibility.

Also applies to: 144-144

src/lib/components/ui/dialog/dialog-trigger.svelte (1)

1-7: LGTM! Consistent UI primitive wrapper.

This component follows the same excellent pattern as the dialog-close component:

  • Proper TypeScript typing with DialogPrimitive.TriggerProps
  • Correct use of $bindable for ref forwarding
  • Useful data-slot attribute for styling
  • Clean, minimal implementation
src/routes/stacks/new/+page.server.ts (1)

9-9: Good use of concurrent loading.

The use of Promise.all to load templates and environment template concurrently is an excellent performance optimization that reduces the overall loading time.

docs/sidebars.ts (1)

38-51: Well-structured documentation addition.

The new Templates section follows the established pattern and is appropriately positioned before the Guides section. The consistent use of single quotes and proper indentation maintains code quality.

template-registry.json (2)

13-17: Clarify the purpose of placeholder URLs.

The template uses example.com placeholder URLs that won't resolve to actual resources. If this is intended as a sample/example registry file, consider:

  1. Adding a comment or documentation indicating this is an example
  2. Using more obviously fake URLs like https://example.com/placeholder to make it clearer
  3. Or providing working example URLs to real template repositories

Are these placeholder URLs intentional? If so, should we add documentation indicating this is an example file?


1-32: Well-structured registry format.

The JSON structure correctly implements the template registry schema with appropriate metadata fields, versioning, and template definitions. The inclusion of tags, author information, and timestamps provides good template categorization and management capabilities.

src/routes/api/templates/stats/+server.ts (3)

9-9: Efficient concurrent data loading.

Good use of Promise.all to load templates and registries concurrently, which optimizes the API response time.


14-21: Comprehensive statistics calculation.

The statistics provide a good overview of the template system state, including counts of local/remote templates, registries, and templates with environment configuration. The logic for filtering enabled registries and templates with environment content is correct.


24-27: Proper error handling.

The error handling appropriately logs the error and returns a meaningful HTTP 500 response with a descriptive message.

src/routes/api/templates/registries/test/+server.ts (1)

13-17: Good configuration approach for testing.

The hardcoded test configuration is appropriate for a test endpoint. The fixed values ensure consistent testing behavior.

src/lib/components/ui/dialog/dialog-content.svelte (3)

2-6: Good refactoring of imports and types.

The consolidation of imports and moving WithoutChildrenOrChild to utils is a good organizational improvement. The icon renaming to XIcon is more descriptive.


24-25: Beneficial addition of data-slot attribute.

The data-slot="dialog-content" attribute improves component identification for testing and debugging purposes. The responsive CSS classes enhance mobile experience.


29-30: Improved close button styling.

The updated CSS classes and simplified icon usage improve the close button's appearance and accessibility.

src/routes/api/templates/registries/+server.ts (2)

27-31: Good validation pattern before adding registry.

Testing the registry before adding it is an excellent practice that prevents adding invalid registries to the system.


9-17: Simple and effective GET implementation.

The GET handler is straightforward and includes proper error handling.

src/lib/services/api/template-api-service.ts (1)

1-89: Well-structured API service with good security practices.

The TemplateAPIService class follows consistent patterns and implements proper security measures:

✅ Consistent use of encodeURIComponent() for ID parameters
✅ Proper HTTP method usage (GET, POST, PUT, DELETE)
✅ Clear method documentation
✅ Consistent return pattern (res.data)

The implementation looks solid and follows REST API conventions effectively.

docs/docs/templates/create-a-registry.md (1)

115-115: Fix grammar: Add missing "to" before verb.

The sentence structure needs correction for proper grammar.

-3. **Add to Arcane**: Settings → Templates → Add Registry
+3. **Add to Arcane**: Settings → Templates → Add Registry

Actually, the current text is fine as it's a UI instruction format. The static analysis tool may be incorrect here.

🧰 Tools
🪛 LanguageTool

[grammar] ~115-~115: It seems that “to” is missing before the verb.
Context: ...nsure all file URLs are accessible 3. Add to Arcane: Settings → Templates → Add...

(MISSING_TO_BETWEEN_BE_AND_VB)

src/lib/types/template-registry.ts (1)

1-20: Well-defined template interfaces.

The TemplateRegistry and RemoteTemplate interfaces are well-structured with appropriate required and optional fields. The field types and naming conventions are consistent and follow good TypeScript practices.

src/routes/api/templates/[id]/download/+server.ts (2)

28-50: Good error handling with proper fallback logic.

The error handling for template content loading is well-designed, with appropriate fallback when environment files fail but compose content succeeds. The logging provides good debugging information.


61-61:

Details

❌ Incorrect review comment

Inconsistent method usage: static vs instance.

The code uses a static method TemplateService.createTemplate() while the rest of the handler uses the instance templateService. This inconsistency could indicate a design issue.

Choose one approach consistently:

Option 1: Use instance method (recommended)

-			await TemplateService.createTemplate(savedName, templateContent.content, template.description, templateContent.envContent);
+			await templateService.createTemplate(savedName, templateContent.content, template.description, templateContent.envContent);

Option 2: Use static method consistently

-		const allTemplates = await templateService.loadAllTemplates();
+		const allTemplates = await TemplateService.loadAllTemplates();
-			templateContent = await templateService.loadTemplateContent(template);
+			templateContent = await TemplateService.loadTemplateContent(template);
-					const composeContent = await templateService.loadComposeContent(template);
+					const composeContent = await TemplateService.loadComposeContent(template);

Let me check the TemplateService implementation to determine the correct pattern:


🏁 Script executed:

#!/bin/bash
# Check TemplateService method signatures to determine static vs instance methods
ast-grep --pattern 'class TemplateService {
  $$$
  static $STATIC_METHOD($$$) {
    $$$
  }
  $$$
  $INSTANCE_METHOD($$$) {
    $$$
  }
  $$$
}'

Length of output: 147


I can’t locate the TemplateService implementation with the previous pattern search. Let’s find where it’s defined and how its methods are declared:


🏁 Script executed:

#!/bin/bash
# 1. Locate the TemplateService class declaration
rg "class TemplateService" -n .

# 2. Locate any createTemplate definitions/usages
rg "createTemplate" -n .

Length of output: 724


Let’s confirm how the other service methods are declared:


🏁 Script executed:

#!/bin/bash
# Check if these methods are static or instance in TemplateService
rg "loadAllTemplates" -n src/lib/services/template-service.ts
rg "loadTemplateContent" -n src/lib/services/template-service.ts
rg "loadComposeContent" -n src/lib/services/template-service.ts

Length of output: 437


Static vs. Instance Methods Are Correct as Implemented

The createTemplate method is declared static (line 104 of template-service.ts), while loadAllTemplates, loadTemplateContent, and loadComposeContent are instance methods (lines 310, 390, 429). Invoking them as

  • TemplateService.createTemplate(…)
  • templateService.loadAllTemplates(…), etc.

matches their definitions. No changes required.

Likely an incorrect or invalid review comment.

src/lib/components/template-selection-dialog.svelte (9)

1-24: Well-structured component setup!

Good use of TypeScript interfaces for props and appropriate state management with Map for tracking loading states.


25-61: Excellent async handling and error management!

The function correctly handles remote template content fetching with proper loading states and comprehensive error handling. Good use of try-catch-finally pattern.


63-94: Robust download handling with detailed error extraction!

Good defensive programming with early return and comprehensive error message extraction that handles multiple error formats.


100-249: Excellent UI implementation with great UX considerations!

The component provides clear visual distinction between local and remote templates, proper loading states, helpful empty state guidance, and comprehensive template metadata display.


251-258: Standard line-clamp implementation!

Good use of webkit prefixes for cross-browser compatibility.


82-89: Excellent error handling implementation.

The error handling logic properly extracts error messages from different error types and provides meaningful feedback to users. This demonstrates good defensive programming practices.


34-48: Well-structured remote template handling.

The logic for fetching remote template content is well-implemented with proper error checking and content merging. The fallback for envContent ensures data integrity.


111-116: Helpful empty state with actionable guidance.

The empty state provides clear guidance to users on how to access templates through the settings page, which improves user experience and discoverability.


158-165: Proper loading state UI implementation.

The loading states are correctly implemented with disabled buttons and loading indicators, providing good user feedback during async operations.

Also applies to: 217-233

src/lib/services/template-registry-service.ts (3)

10-60: Well-implemented registry fetching with proper caching and validation!

Good separation of browser vs server logic, proper use of proxy endpoint for security, and comprehensive error handling.


148-168: Clean data transformation implementation!

The method properly converts remote templates to the local format with appropriate metadata mapping and lazy loading support.


170-187: Good validation and utility methods!

Proper validation with descriptive error messages and standard singleton pattern implementation.

src/routes/api/templates/+server.ts (2)

1-5: Standard route handler setup.

Appropriate imports and service instantiation.


90-151: Well-structured POST handler with dual functionality.

Good separation of concerns between proxy requests and template creation. Consider extracting the proxy logic into a shared function to maintain consistency with the GET handler.

src/routes/stacks/new/+page.svelte (3)

4-4: Icon import change looks good.

The change from Wand2 to Wand aligns with the updated icon usage at line 145.


85-99: Template selection handler implementation looks good.

The function properly handles template content, environment variables, and name auto-population with appropriate sanitization.


178-181: UI button arrangement looks good.

The template button is appropriately placed and has proper loading/disabled states.

src/app.css (2)

9-26: Tailwind v4 compatibility fix is well-documented.

Good addition of explicit border color styles to maintain visual consistency with Tailwind v3. The comment clearly explains the rationale.


97-132: Theme variable organization improvements look good.

The addition of radius variants and sidebar-specific color variables provides better theming flexibility. The systematic naming convention is maintained.

Comment on lines +36 to +42
export interface TemplateRegistryConfig {
url: string;
name: string;
enabled: boolean;
last_updated?: string;
cache_ttl?: number;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove duplicate interface definition.

The TemplateRegistryConfig interface is already defined in src/lib/types/template-registry.ts (lines 22-28) with identical structure. This duplication violates the DRY principle and could lead to maintenance issues.

Apply this diff to import the existing interface instead:

+import type { TemplateRegistryConfig } from './template-registry';
+
 export interface AuthSettings {
 	localAuthEnabled: boolean;
 	oidcEnabled: boolean;
@@ -33,12 +35,6 @@
 	};
 }
 
-export interface TemplateRegistryConfig {
-	url: string;
-	name: string;
-	enabled: boolean;
-	last_updated?: string;
-	cache_ttl?: number;
-}
-
 export interface Settings {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface TemplateRegistryConfig {
url: string;
name: string;
enabled: boolean;
last_updated?: string;
cache_ttl?: number;
}
// at the top of src/lib/types/settings.type.ts
import type { TemplateRegistryConfig } from './template-registry';
export interface AuthSettings {
localAuthEnabled: boolean;
oidcEnabled: boolean;
}
// (the duplicate TemplateRegistryConfig definition has been removed)
export interface Settings {
// …other fields…
// e.g. templateRegistries: TemplateRegistryConfig[];
}
🤖 Prompt for AI Agents
In src/lib/types/settings.type.ts around lines 36 to 42, remove the duplicate
TemplateRegistryConfig interface definition and instead import the existing
TemplateRegistryConfig interface from src/lib/types/template-registry.ts. This
eliminates redundancy and ensures a single source of truth for the interface
definition.

import type { RequestHandler } from './$types';
import { TemplateService } from '$lib/services/template-service';

const templateService = new TemplateService();
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Consider using static methods for consistency.

The instantiation of TemplateService here contrasts with the related endpoint in src/routes/api/templates/[id]/+server.ts which uses static method TemplateService.getComposeTemplates(). This inconsistency suggests potential architectural confusion.

Verify the intended pattern for TemplateService usage across API endpoints:


🏁 Script executed:

#!/bin/bash
# Description: Check TemplateService usage patterns across API routes

# Find all API routes using TemplateService
rg -A 5 -B 2 "TemplateService" --type ts src/routes/api/templates/

Length of output: 7663


🏁 Script executed:

#!/bin/bash
# Locate the TemplateService implementation
SERVICE_FILE=$(fd template-service.ts -t f | head -n 1)
echo "Service file: $SERVICE_FILE"
echo "-----"
sed -n '1,200p' "$SERVICE_FILE"

Length of output: 5956


#!/bin/bash
# Check for instance methods loadAllTemplates and getRegistries in TemplateService
grep -nE "loadAllTemplates|getRegistries" src/lib/services/template-service.ts || echo "No matches found"

Standardize TemplateService usage to static methods

The TemplateService class only defines static methods (e.g. getComposeTemplates, createTemplate, deleteTemplate, etc.). All current endpoints instantiate it and call non-existent instance methods (loadAllTemplates(), getRegistries()), which will lead to runtime errors. Please remove the new TemplateService() instantiation and update these calls:

src/routes/api/templates/stats/+server.ts

  • Remove
    const templateService = new TemplateService();
    await templateService.loadAllTemplates();
    await templateService.getRegistries();
  • Replace with
    import { templateRegistryService } from '$lib/services/template-registry-service';
    const [templates, registries] = await Promise.all([
      TemplateService.getComposeTemplates(),
      templateRegistryService.getRegistries()
    ]);

src/routes/api/templates/registries/+server.ts

  • Remove instantiation and
    await templateService.getRegistries();
  • Replace with
    import { templateRegistryService } from '$lib/services/template-registry-service';
    await templateRegistryService.getRegistries();

src/routes/api/templates/[id]/content/+server.ts

  • Replace
    const templateService = new TemplateService();
    const templates = await templateService.loadAllTemplates();
    with
    const templates = await TemplateService.getComposeTemplates();

src/routes/api/templates/[id]/download/+server.ts and the root /api/templates/+server.ts

  • Remove unnecessary instantiation and call the appropriate static methods directly (e.g., TemplateService.createTemplate, TemplateService.deleteTemplate, etc.).

This ensures all endpoints consistently invoke the class’s static API and avoids calling undefined instance methods.

🤖 Prompt for AI Agents
In src/routes/api/templates/[id]/content/+server.ts at line 5, remove the
instantiation of TemplateService with "new TemplateService()". Replace the call
to the non-existent instance method loadAllTemplates() with a call to the static
method TemplateService.getComposeTemplates(). This aligns with the consistent
usage pattern of TemplateService static methods across API endpoints and
prevents runtime errors from calling undefined instance methods.

Comment on lines +5 to +11
export const POST: RequestHandler = async ({ request }) => {
try {
const { url } = await request.json();

if (!url) {
return error(400, { message: 'URL is required' });
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add URL validation and consider security implications.

The endpoint accepts arbitrary URLs which could be exploited for Server-Side Request Forgery (SSRF) attacks. Consider adding URL validation to restrict to HTTPS and block internal/localhost addresses.

 export const POST: RequestHandler = async ({ request }) => {
 	try {
 		const { url } = await request.json();
 
 		if (!url) {
 			return error(400, { message: 'URL is required' });
 		}
+
+		// Validate URL format and security
+		try {
+			const parsedUrl = new URL(url);
+			if (parsedUrl.protocol !== 'https:') {
+				return error(400, { message: 'Only HTTPS URLs are allowed' });
+			}
+			if (['localhost', '127.0.0.1', '0.0.0.0'].includes(parsedUrl.hostname)) {
+				return error(400, { message: 'Internal URLs are not allowed' });
+			}
+		} catch {
+			return error(400, { message: 'Invalid URL format' });
+		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const POST: RequestHandler = async ({ request }) => {
try {
const { url } = await request.json();
if (!url) {
return error(400, { message: 'URL is required' });
}
export const POST: RequestHandler = async ({ request }) => {
try {
const { url } = await request.json();
if (!url) {
return error(400, { message: 'URL is required' });
}
// Validate URL format and security
try {
const parsedUrl = new URL(url);
if (parsedUrl.protocol !== 'https:') {
return error(400, { message: 'Only HTTPS URLs are allowed' });
}
if (['localhost', '127.0.0.1', '0.0.0.0'].includes(parsedUrl.hostname)) {
return error(400, { message: 'Internal URLs are not allowed' });
}
} catch {
return error(400, { message: 'Invalid URL format' });
}
// …rest of your logic…
🤖 Prompt for AI Agents
In src/routes/api/templates/registries/test/+server.ts around lines 5 to 11, the
POST handler accepts arbitrary URLs without validation, posing SSRF risks. Add
validation to ensure the URL uses HTTPS and reject URLs pointing to localhost or
internal IP ranges. Implement checks to parse the URL, verify the protocol is
HTTPS, and block requests to private or loopback addresses before proceeding.

Comment on lines +57 to +72
async function addRegistry() {
if (!newRegistryName.trim() || !newRegistryUrl.trim()) {
toast.error('Registry name and URL are required');
return;
}

if (isLoading.addingRegistry) return;
isLoading.addingRegistry = true;

try {
// Test the registry before adding
const config: TemplateRegistryConfig = {
url: newRegistryUrl.trim(),
name: newRegistryName.trim(),
enabled: true
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding URL validation for security.

The current implementation only relies on HTML5 URL validation. Consider validating that the URL uses HTTPS and has a valid format before testing the registry.

		if (!newRegistryName.trim() || !newRegistryUrl.trim()) {
			toast.error('Registry name and URL are required');
			return;
		}

+		// Validate URL format and protocol
+		try {
+			const url = new URL(newRegistryUrl.trim());
+			if (url.protocol !== 'https:') {
+				toast.error('Registry URL must use HTTPS for security');
+				return;
+			}
+		} catch (e) {
+			toast.error('Invalid URL format');
+			return;
+		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function addRegistry() {
if (!newRegistryName.trim() || !newRegistryUrl.trim()) {
toast.error('Registry name and URL are required');
return;
}
if (isLoading.addingRegistry) return;
isLoading.addingRegistry = true;
try {
// Test the registry before adding
const config: TemplateRegistryConfig = {
url: newRegistryUrl.trim(),
name: newRegistryName.trim(),
enabled: true
};
async function addRegistry() {
if (!newRegistryName.trim() || !newRegistryUrl.trim()) {
toast.error('Registry name and URL are required');
return;
}
// Validate URL format and protocol
try {
const url = new URL(newRegistryUrl.trim());
if (url.protocol !== 'https:') {
toast.error('Registry URL must use HTTPS for security');
return;
}
} catch (e) {
toast.error('Invalid URL format');
return;
}
if (isLoading.addingRegistry) return;
isLoading.addingRegistry = true;
try {
// Test the registry before adding
const config: TemplateRegistryConfig = {
url: newRegistryUrl.trim(),
name: newRegistryName.trim(),
enabled: true
};
// …
}
}
🤖 Prompt for AI Agents
In src/routes/settings/templates/+page.svelte around lines 57 to 72, the
addRegistry function currently does not validate that the URL uses HTTPS or has
a valid format beyond basic HTML5 validation. To fix this, add a check before
proceeding that verifies the newRegistryUrl starts with "https://" and matches a
proper URL pattern. If the URL is invalid or not HTTPS, show an error toast and
prevent further execution. This ensures only secure and well-formed URLs are
accepted.

Comment on lines +36 to +73
static async getComposeTemplates(): Promise<ComposeTemplate[]> {
try {
// Ensure directories exist
await this.ensureDirectoriesExist();

const files = await fs.readdir(this.composeTemplatesDir);
const yamlFiles = files.filter((file) => (file.endsWith('.yaml') || file.endsWith('.yml')) && !file.startsWith('.'));

const templates: ComposeTemplate[] = [];

for (const file of yamlFiles) {
const filePath = path.join(this.composeTemplatesDir, file);
const content = await fs.readFile(filePath, 'utf8');
const id = path.basename(file, path.extname(file));

// Try to extract description from comment at top of file
const description = this.extractDescriptionFromFile(content);

// Check for accompanying .env file
const envContent = await this.loadTemplateEnvFile(id);

templates.push({
id,
name: this.formatTemplateName(id),
description: description || 'Custom Docker Compose template',
content,
envContent,
isCustom: true,
isRemote: false
});
}

return templates.sort((a, b) => a.name.localeCompare(b.name));
} catch (error) {
console.error('Error loading compose templates:', error);
return [];
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Eliminate code duplication between static and instance methods.

The getComposeTemplates() static method and loadLocalTemplates() instance method contain nearly identical code. This violates the DRY principle and makes maintenance harder.

Consider refactoring to share the implementation:

+	/**
+	 * Internal method to load local templates
+	 */
+	private static async loadLocalTemplatesInternal(): Promise<ComposeTemplate[]> {
+		try {
+			// Ensure directories exist
+			await this.ensureDirectoriesExist();
+
+			const files = await fs.readdir(this.composeTemplatesDir);
+			const yamlFiles = files.filter((file) => (file.endsWith('.yaml') || file.endsWith('.yml')) && !file.startsWith('.'));
+
+			const templates: ComposeTemplate[] = [];
+
+			for (const file of yamlFiles) {
+				const filePath = path.join(this.composeTemplatesDir, file);
+				const content = await fs.readFile(filePath, 'utf8');
+				const id = path.basename(file, path.extname(file));
+
+				// Try to extract description from comment at top of file
+				const description = this.extractDescriptionFromFile(content);
+
+				// Check for accompanying .env file
+				const envContent = await this.loadTemplateEnvFile(id);
+
+				templates.push({
+					id,
+					name: this.formatTemplateName(id),
+					description: description || 'Custom Docker Compose template',
+					content,
+					envContent,
+					isCustom: true,
+					isRemote: false
+				});
+			}
+
+			return templates.sort((a, b) => a.name.localeCompare(b.name));
+		} catch (error) {
+			console.error('Error loading compose templates:', error);
+			return [];
+		}
+	}
+
 	static async getComposeTemplates(): Promise<ComposeTemplate[]> {
-		// ... current implementation
+		return this.loadLocalTemplatesInternal();
 	}

 	async loadLocalTemplates(): Promise<ComposeTemplate[]> {
-		// ... current implementation
+		return TemplateService.loadLocalTemplatesInternal();
 	}

Also applies to: 325-362

🧰 Tools
🪛 Biome (1.9.4)

[error] 39-39: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 41-41: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 47-47: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 52-52: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 55-55: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 59-59: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

🤖 Prompt for AI Agents
In src/lib/services/template-service.ts around lines 36 to 73, the static method
getComposeTemplates and the instance method loadLocalTemplates (lines 325-362)
have nearly identical code, causing duplication. Refactor by extracting the
shared logic into a private helper method that both methods can call, passing
necessary parameters if needed. This will centralize the template loading logic,
eliminate redundancy, and simplify maintenance.

kmendell and others added 3 commits May 28, 2025 19:10
…ng sanitization

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Signed-off-by: Kyle Mendell <ksm@ofkm.us>
@kmendell kmendell merged commit c47d15f into main May 29, 2025
8 checks passed
@kmendell kmendell deleted the feat/templates branch May 29, 2025 00:30
@coderabbitai coderabbitai bot mentioned this pull request Jun 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant