This is an Astro + Deno project that lists S3-compatible bucket contents as a web index. It runs on Cloudflare Pages using the @astrojs/cloudflare adapter in server output mode.
# Install dependencies (Deno manages dependencies via deno.json imports)
deno install
# Local development server
deno task dev
# Build with TypeScript check
deno task build
# Deploy to Cloudflare Pages
deno task deploy# Cloudflare Pages dev (requires dist/ to exist)
deno task dev:cloudflare- Copy
.dev.vars.exampleto.dev.vars - Fill in S3 credentials and configuration
- For Cloudflare deployment, also copy
wrangler.toml.exampletowrangler.toml
# Set secrets for Cloudflare Pages
deno run npm:wrangler secret put BUCKET_ENDPOINT
deno run npm:wrangler secret put BUCKET_REGION
deno run npm:wrangler secret put BUCKET_ACCESS_KEY_ID
deno run npm:wrangler secret put BUCKET_SECRET_ACCESS_KEY
deno run npm:wrangler secret put BUCKET_DOWNLOAD_URL- Language: TypeScript with Astro components (
.astrofiles) - Type Checking: Run via
deno task build(includesastro check) - No TypeScript config file - Deno handles TypeScript settings internally
- Use explicit type annotations for function parameters and return types
- Use
interfacefor object types,typefor unions and primitives - Define exported types in dedicated files or at the top of relevant files
// Example: Union type pattern used in this project
export type Entry =
| {
type: 'directory';
name: string;
anchor: string;
lastModified?: Date;
size?: number;
humanReadableSize?: string;
}
| {
type: 'file';
name: string;
anchor: string;
fullPath: string;
lastModified: Date;
size: number;
humanReadableSize: string;
extension: string;
};- Formatter: Deno's built-in
deno fmt - Indentation: 2 spaces
- Line Width: 80 characters
- Quotes: Single quotes (
') - Semicolons: Required
- Tabs: Disabled (use spaces)
- Trailing Commas: Allowed
# Format all code
deno fmt- Style: Named imports preferred
- Import extensions: Include
.tsextension for relative imports - Sort order: Group imports logically (stdlib → external → relative)
- Type imports: Use
import typefor type-only imports
import { S3mini } from 's3mini';
import { parseDate, strip } from './utils.ts';
import type { Entry, FSListing } from './s3.ts';| Construct | Convention | Example |
|---|---|---|
| Variables | camelCase | bucketName, s3Client |
| Constants | SCREAMING_SNAKE_CASE | BUCKET_ENDPOINT |
| Functions | camelCase | listBucket, parseViewParams |
| Types | PascalCase | Entry, FSListing, ViewParams |
| Files | kebab-case | breadcrumb.ts, index-page.astro |
| Components | PascalCase (Astro) | IndexPage.astro, FourOhFour.astro |
- Use
try/catchblocks for async operations - Log errors with
console.error()before rethrowing - Let errors propagate to the framework level when appropriate
- Handle edge cases with explicit checks and early returns
try {
const response = await client.listObjectsPaged(bucketName, token);
if (response?.objects) {
allObjects.push(...response.objects);
}
token = response?.nextContinuationToken;
} catch (e) {
console.error('Error listing objects:', e);
throw e;
}- Frontmatter: Place imports at the top, type definitions after imports
- Props interface: Define in the component file, export if reused
- Client scripts: Use
<script is:inline>for client-side JS - Template logic: Use ternary operators for conditional rendering
- Global styles: Place in
<style is:global>in Layout.astro
---
import type { BreadcrumbEntry } from '../functions/breadcrumb';
interface Props {
breadCrums: BreadcrumbEntry[];
requestPath: string;
}
const { breadCrums, requestPath } = Astro.props;
---
<header>
{breadCrums.map((crumb) => (
<a href={crumb.path}>{crumb.name}</a>
))}
</header>- Use Astro's
astro:envAPI for type-safe environment variables - Define schema in
astro.config.tsusingenvField - Import from
astro:env/serverfor server-side access - Public variables:
context: 'server', access: 'public' - Secret variables:
context: 'server', access: 'secret'
# Run Deno linter
deno lintLinting is configured in deno.json to include src/ and exclude dist/.
- Use CSS variables for theming (see Layout.astro for dark mode)
- Scoped styles in Astro components (default behavior)
- Global styles in Layout.astro with
<style is:global> - Responsive design with media queries (max-width: 600px breakpoint)
/home/lihua/projects/s3-browser
├── src/
│ ├── components/ # Astro UI components
│ │ ├── IndexPage.astro # Main listing component
│ │ ├── FourOhFour.astro # 404 page
│ │ └── Icon.astro # Icon helper
│ ├── functions/ # Business logic (TypeScript)
│ │ ├── s3.ts # S3 listing and entry types
│ │ ├── sort.ts # Sorting and view parameters
│ │ ├── breadcrumb.ts # Breadcrumb generation
│ │ ├── utils.ts # Utility functions
│ │ ├── env.ts # Environment variable exports
│ │ └── icon.ts # Icon helper functions
│ ├── layouts/ # Astro layouts
│ │ └── Layout.astro # Main HTML layout with global styles
│ ├── middleware/ # Astro middleware
│ │ └── index.ts # Redirect/trailing slash handling
│ └── pages/ # Astro pages (file-based routing)
│ └── [...index].astro # Catch-all route for bucket paths
├── public/ # Static assets
├── astro.config.ts # Astro configuration
├── deno.json # Deno configuration and tasks
└── wrangler.toml # Cloudflare Pages configuration
- Pages:
[...index].astrocatches all paths for bucket listing - Components: Pure UI components in
components/ - Logic: Business logic separated into
functions/directory - S3 Operations: Centralized in
functions/s3.tswithEntryandFSListingtypes - Middleware: Handles trailing slashes and proxying non-bucket requests
| Variable | Description | Context | Access |
|---|---|---|---|
BUCKET_ENDPOINT |
S3 endpoint URL | server | secret |
BUCKET_REGION |
S3 region | server | secret |
BUCKET_ACCESS_KEY_ID |
AWS access key | server | secret |
BUCKET_SECRET_ACCESS_KEY |
AWS secret key | server | secret |
BUCKET_DOWNLOAD_URL |
Public download base URL | server | secret |
DISABLE_SE_INDEX |
Disable search engines | server | public |
.dev.vars: Local development (gitignored)wrangler.toml: Cloudflare deployment config (gitignored, use example)astro.config.ts: Defines env schema for type safety
Secrets must be set via Wrangler CLI for Cloudflare Pages:
deno run npm:wrangler secret put <VARIABLE_NAME>- This project maintains both English (
README.md) and Chinese (README.zh.md) versions - When modifying either README, you must synchronize both files
- Keep the same structure and headings in both versions
- Translate content while preserving the meaning
Last Updated: 2026-02-17