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
39 changes: 36 additions & 3 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ export default defineConfig({
starlightLlmsTxt({
projectName: 'LocalStack',
description:
'LocalStack is a cloud service emulator that runs in a single container on your laptop or in your CI environment. It provides an easy-to-use test/mocking framework for developing cloud applications, with support for AWS services and Snowflake.',
'LocalStack is a cloud service emulator that runs in a single container on your laptop or in your CI environment. It provides an easy-to-use test/mocking framework for developing cloud applications, with support for AWS services, Snowflake, and Azure.',
customSets: [
{
label: 'AWS',
Expand All @@ -198,8 +198,13 @@ export default defineConfig({
description: 'Documentation for LocalStack Snowflake emulation',
paths: ['snowflake/**'],
},
{
label: 'Azure',
description: 'Documentation for LocalStack Azure emulation',
paths: ['azure/**'],
},
],
exclude: ['aws/changelog', 'snowflake/changelog'],
exclude: ['aws/changelog', 'snowflake/changelog', 'azure/changelog'],
rawContent: true,
}),
starlightImageZoom({
Expand All @@ -210,7 +215,7 @@ export default defineConfig({
}),
starlightLinksValidator({
errorOnRelativeLinks: true,
errorOnLocalLinks: false, // Allow localhost links in tutorials (they're instructional)
errorOnLocalLinks: false, // Allow localhost links in tutorials (they're instructional)
errorOnInvalidHashes: true,
}),
starlightUtils({
Expand Down Expand Up @@ -547,6 +552,34 @@ export default defineConfig({
},
],
},
{
label: 'Azure',
collapsed: true,
items: [
{
label: 'Welcome',
slug: 'azure',
},
{
label: 'Getting Started',
autogenerate: { directory: 'azure/getting-started' },
collapsed: true,
},
{
label: 'Local Azure Services',
slug: 'azure/services',
},
{
label: 'Integrations',
autogenerate: { directory: 'azure/integrations' },
collapsed: true,
},
{
label: 'Changelog',
slug: 'azure/changelog',
},
],
},
],
}),
markdoc(),
Expand Down
3 changes: 3 additions & 0 deletions public/js/icon-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
Welcome: 'cube-icon',
'Getting Started': 'rocket-icon',
'Local AWS Services': 'cube-icon',
'Local Azure Services': 'cube-icon',
Features: 'cube-icon',
'Feature Coverage': 'buildings-icon',
'Sample Apps': 'file-icon',
Expand Down Expand Up @@ -101,6 +102,8 @@
window.location.href = '/aws/';
} else if (selectedValue === 'Snowflake') {
window.location.href = '/snowflake/';
} else if (selectedValue === 'Azure') {
window.location.href = '/azure/';
}

setTimeout(() => {
Expand Down
3 changes: 3 additions & 0 deletions src/components/LanguageSelectWithGetStarted.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import Default from '@astrojs/starlight/components/LanguageSelect.astro';
// Get the current page route
const route = Astro.locals.starlightRoute;
const isSnowflakePage = route.id.startsWith('snowflake');
const isAzurePage = route.id.startsWith('azure');

let getStartedUrl = 'https://app.localstack.cloud/sign-up';

if (isSnowflakePage) {
getStartedUrl += '?emulator=snowflake';
} else if (isAzurePage) {
getStartedUrl += '?emulator=azure';
}
---

Expand Down
5 changes: 5 additions & 0 deletions src/components/PageTitleWithCopyButton.astro
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,17 @@ const hiddenPaths = [
'snowflake/sample-apps',
'snowflake/capabilities',
'snowflake/integrations',
'azure',
'azure/services',
'azure/sample-apps',
'azure/integrations',
];

// Check if copy page should be hidden (via frontmatter or path match)
const isHiddenPath = hiddenPaths.includes(route.id);
const hideCopyPage = route.entry?.data?.hideCopyPage ?? isHiddenPath;


// Construct the full page URL
const siteUrl = Astro.site?.toString().replace(/\/$/, '') || 'https://docs.localstack.cloud';
const pageUrl = `${siteUrl}/${route.id}/`;
Expand Down
28 changes: 28 additions & 0 deletions src/components/SearchableAzureServices.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
import { getCollection } from 'astro:content';
import { SearchableAzureServices } from './SearchableAzureServices.tsx';

const allServices = await getCollection('docs', ({ id }) => {
return id.startsWith('azure/services/') && !id.includes('/index');
});

const sortedServices = allServices.sort((a, b) => {
const titleA = a.data.title || a.data.linkTitle || '';
const titleB = b.data.title || b.data.linkTitle || '';
return titleA.localeCompare(titleB);
});

const serviceData = sortedServices.map((service) => {
const title = service.data.title || service.data.linkTitle || 'Unknown Service';
const description = service.data.description || `Implementation details for ${title} API`;
const href = `/${service.id}`;

return {
title,
description,
href,
};
});
---

<SearchableAzureServices services={serviceData} client:load />
78 changes: 78 additions & 0 deletions src/components/SearchableAzureServices.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React, { useMemo, useState } from 'react';
import { ServiceBox } from './ServiceBox.tsx';

interface Service {
title: string;
description: string;
href: string;
}

interface SearchableAzureServicesProps {
services: Service[];
}

export const SearchableAzureServices: React.FC<SearchableAzureServicesProps> = ({
services,
}) => {
const [searchTerm, setSearchTerm] = useState('');

const filteredServices = useMemo(() => {
if (!searchTerm.trim()) {
return services;
}

const lowercaseSearch = searchTerm.toLowerCase();
return services.filter(
(service) =>
service.title.toLowerCase().includes(lowercaseSearch) ||
service.description.toLowerCase().includes(lowercaseSearch)
);
}, [services, searchTerm]);

return (
<div className="searchable-services">
<div className="search-container">
<div className="search-input-wrapper">
<svg
className="search-icon"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
<input
type="text"
placeholder="Search for Azure Service Name ..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="search-input"
/>
</div>
</div>

{filteredServices.length === 0 && searchTerm.trim() ? (
<div className="no-results">
<p>No services found matching "{searchTerm}"</p>
</div>
) : (
<div className="service-grid">
{filteredServices.map((service, index) => (
<ServiceBox
key={`${service.href}-${index}`}
title={service.title}
description={service.description}
href={service.href}
/>
))}
</div>
)}
</div>
);
};
Loading