Files
classroomio/packages/storybook/scripts/upload-storybook.ts
T
Rotimi Best c7854efae3 feat: redo settings and all org pages. (#601)
* feat: new components, deploy storybook

* refactor: simplify component field definitions and enhance button loading state

- Removed unnecessary type imports and simplified the FIELDS array definitions across multiple components in Storybook.
- Updated button component to use a loading state instead of isLoading, improving clarity and consistency in props.
- Adjusted button stories to reflect the new loading prop and enhanced the button's loading visual feedback.
- Minor CSS adjustments for better layout and styling consistency.

* chore: add field, card, password components

* feat: improve page component & update AuthUI path

* chore: update bits-ui to version 2.14.4 and all org related UI's

- Updated bits-ui dependency in pnpm-lock.yaml and package.json to version 2.14.4.
- Refactored imports for RoleBasedSecurity and UpgradeBanner components to use the new ui feature directory.
- Removed unused Course components and related files to clean up the codebase.
- Enhanced various components to improve consistency and maintainability.

* chore: move all setting buttons to the top and update auth ui styles

* chore: remove svelte-loading-spinners and replace with @cio/ui/base/spinner

- Removed all instances of svelte-loading-spinners from the project.
- Updated components to use the new Spinner from @cio/ui/base/spinner.
- Cleaned up pnpm-lock.yaml and package.json to reflect the removal of svelte-loading-spinners.
- Added new CSS animations for rocket launch effects in app.css.
- Enhanced email change flow with updated verification messages in translations.

* chore: remove supabase file upload from dashboard and move to r2

* chore: update organization API and UI components

- Added new API methods for organization plan operations and team management.
- Introduced middleware for API key authentication and organization admin checks.
- Refactored organization-related components to utilize the new API structure.
- Updated various UI components to improve consistency and maintainability.
- Enhanced error handling and validation for organization-related actions.
- Removed deprecated services and streamlined the codebase.
2025-12-02 09:54:38 +01:00

199 lines
5.7 KiB
TypeScript

#!/usr/bin/env tsx
import 'dotenv/config';
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { join, relative } from 'path';
import { readFileSync, readdirSync, statSync } from 'fs';
interface UploadOptions {
key: string;
content: Buffer | string;
contentType?: string;
}
class StorybookUploader {
private s3Client: S3Client | null = null;
private buildDir: string;
private bucket: string;
constructor() {
this.buildDir = join(process.cwd(), 'storybook-static');
this.bucket = 'assets';
this.initializeS3Client();
}
private initializeS3Client() {
const accessKey = process.env.CLOUDFLARE_ACCESS_KEY;
const secretKey = process.env.CLOUDFLARE_SECRET_ACCESS_KEY;
const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
if (accessKey && secretKey && accountId) {
this.s3Client = new S3Client({
region: 'auto',
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: accessKey,
secretAccessKey: secretKey
}
});
console.log('✅ S3 client initialized for Cloudflare R2');
} else {
console.warn('⚠️ Cloudflare R2 credentials not found. Cannot upload to R2.');
throw new Error('Missing Cloudflare R2 credentials');
}
}
private getContentType(filePath: string): string {
const ext = filePath.split('.').pop()?.toLowerCase();
const contentTypes: Record<string, string> = {
html: 'text/html',
js: 'application/javascript',
json: 'application/json',
css: 'text/css',
png: 'image/png',
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
gif: 'image/gif',
svg: 'image/svg+xml',
ico: 'image/x-icon',
woff: 'font/woff',
woff2: 'font/woff2',
ttf: 'font/ttf',
eot: 'application/vnd.ms-fontobject',
map: 'application/json',
txt: 'text/plain',
xml: 'application/xml'
};
return contentTypes[ext || ''] || 'application/octet-stream';
}
private getCacheControl(filePath: string): string {
// Cache static assets longer, HTML files shorter
if (filePath.endsWith('.html')) {
return 'public, max-age=300'; // 5 minutes for HTML
}
if (filePath.match(/\.(js|css|woff|woff2|ttf|png|jpg|jpeg|gif|svg|ico)$/)) {
return 'public, max-age=31536000, immutable'; // 1 year for assets
}
return 'public, max-age=3600'; // 1 hour default
}
private getAllFiles(dir: string, baseDir: string = dir): string[] {
const files: string[] = [];
const entries = readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = join(dir, entry.name);
const relativePath = relative(baseDir, fullPath);
if (entry.isDirectory()) {
files.push(...this.getAllFiles(fullPath, baseDir));
} else {
files.push(relativePath);
}
}
return files;
}
async uploadFile(filePath: string): Promise<void> {
if (!this.s3Client) {
throw new Error('S3 client not initialized. Check your Cloudflare R2 credentials.');
}
try {
const fullPath = join(this.buildDir, filePath);
const content = readFileSync(fullPath);
const contentType = this.getContentType(filePath);
const cacheControl = this.getCacheControl(filePath);
// Upload to storybook/ directory in R2
const key = `storybook/${filePath}`;
const command = new PutObjectCommand({
Bucket: this.bucket,
Key: key,
Body: content,
ContentType: contentType,
CacheControl: cacheControl
});
await this.s3Client.send(command);
console.log(`✅ Uploaded: ${filePath}`);
} catch (error) {
console.error(`❌ Error uploading ${filePath}:`, error);
throw error;
}
}
async uploadAll(): Promise<void> {
try {
// Check if build directory exists
try {
statSync(this.buildDir);
} catch {
throw new Error(`Build directory not found: ${this.buildDir}. Please run 'pnpm build' first.`);
}
console.log('🔄 Starting Storybook upload to R2...');
console.log(`📁 Build directory: ${this.buildDir}`);
const files = this.getAllFiles(this.buildDir);
console.log(`📦 Found ${files.length} files to upload`);
// Upload files in batches to avoid overwhelming the API
const batchSize = 10;
for (let i = 0; i < files.length; i += batchSize) {
const batch = files.slice(i, i + batchSize);
await Promise.all(batch.map((file) => this.uploadFile(file)));
console.log(`📤 Uploaded batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(files.length / batchSize)}`);
}
console.log('🎉 Storybook upload completed successfully!');
console.log(`📊 Total files uploaded: ${files.length}`);
} catch (error) {
console.error('❌ Failed to upload Storybook:', error);
throw error;
}
}
}
// CLI interface
async function main() {
const args = process.argv.slice(2);
if (args.includes('--help') || args.includes('-h')) {
console.log(`
Usage: tsx scripts/upload-storybook.ts
Examples:
tsx scripts/upload-storybook.ts
Environment Variables Required for R2 Upload:
CLOUDFLARE_ACCESS_KEY
CLOUDFLARE_SECRET_ACCESS_KEY
CLOUDFLARE_ACCOUNT_ID
`);
return;
}
const uploader = new StorybookUploader();
await uploader.uploadAll();
}
// Run the script if called directly
if (process.argv[1] && process.argv[1].endsWith('upload-storybook.ts')) {
main()
.then(() => {
console.log('Script completed successfully');
process.exit(0);
})
.catch((error) => {
console.error('Script failed:', error);
process.exit(1);
});
}
export { StorybookUploader };