diff --git a/apps/api/src/lib/import/adapters/mock.adapter.ts b/apps/api/src/lib/import/adapters/mock.adapter.ts
index 195e47d..3b225c2 100644
--- a/apps/api/src/lib/import/adapters/mock.adapter.ts
+++ b/apps/api/src/lib/import/adapters/mock.adapter.ts
@@ -1,4 +1,3 @@
-import { Buffer } from 'node:buffer';
import type { IPageScraperAdapter, ScrapedPageData } from '@minimalblock/core';
function normalizeDomain(url: URL): string {
@@ -7,7 +6,7 @@ function normalizeDomain(url: URL): string {
function buildMockDataUrl(label: string, color: string): string {
const svg = ``;
- return `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`;
+ return `data:image/svg+xml;base64,${btoa(svg)}`;
}
export class MockAdapter implements IPageScraperAdapter {
diff --git a/apps/api/src/lib/import/pipeline/image-intelligence.pipeline.ts b/apps/api/src/lib/import/pipeline/image-intelligence.pipeline.ts
index 39cc00e..3a6635c 100644
--- a/apps/api/src/lib/import/pipeline/image-intelligence.pipeline.ts
+++ b/apps/api/src/lib/import/pipeline/image-intelligence.pipeline.ts
@@ -1,4 +1,3 @@
-import { Buffer } from 'node:buffer';
import type { ImportedImageCandidate, ProductImportData } from '@minimalblock/core';
import { GeminiImageClassifier, ImageDeduplicationService } from '@minimalblock/ai';
@@ -33,7 +32,7 @@ export class ImageIntelligencePipeline {
headers: { 'user-agent': 'MinimalBlockBot/1.0', accept: 'image/*' },
});
if (!res.ok) return null;
- return Buffer.from(await res.arrayBuffer());
+ return new Uint8Array(await res.arrayBuffer());
} catch {
return null;
}
@@ -41,7 +40,7 @@ export class ImageIntelligencePipeline {
);
// Perceptual deduplication
- const hashes = buffers.map((buf) => buf ? this.deduplicator.computeHash(new Uint8Array(buf)) : '0000000000000000');
+ const hashes = buffers.map((buf) => buf ? this.deduplicator.computeHash(buf) : '0000000000000000');
const duplicateIndexes = new Set(this.deduplicator.findDuplicates(hashes));
// Build base64 images for Gemini classification (only non-failed uploads)
@@ -50,7 +49,7 @@ export class ImageIntelligencePipeline {
const buf = buffers[i];
if (buf && candidates[i].mimeType && candidates[i].mimeType !== 'image/svg+xml') {
geminiImages.push({
- base64: buf.toString('base64'),
+ base64: btoa(String.fromCharCode(...buf)),
mimeType: candidates[i].mimeType!,
originalIndex: i,
});
diff --git a/apps/api/src/lib/import/pipeline/image-upload.pipeline.ts b/apps/api/src/lib/import/pipeline/image-upload.pipeline.ts
index fb60262..d39e954 100644
--- a/apps/api/src/lib/import/pipeline/image-upload.pipeline.ts
+++ b/apps/api/src/lib/import/pipeline/image-upload.pipeline.ts
@@ -1,4 +1,3 @@
-import { Buffer } from 'node:buffer';
import { generateId, type ImportedImageCandidate, type MediaAssetType } from '@minimalblock/core';
import type { ScrapedImageCandidate } from '@minimalblock/core';
import type { SupabaseClient } from '@supabase/supabase-js';
@@ -17,11 +16,14 @@ function mimeExtension(mimeType: string): string {
}
}
-function decodeDataUrl(raw: string): { mimeType: string; buffer: Buffer } | null {
+function decodeDataUrl(raw: string): { mimeType: string; buffer: Uint8Array } | null {
if (!raw.startsWith('data:')) return null;
const match = raw.match(/^data:([^;]+);base64,(.+)$/);
if (!match) return null;
- return { mimeType: match[1], buffer: Buffer.from(match[2], 'base64') };
+ const binaryStr = atob(match[2]);
+ const bytes = new Uint8Array(binaryStr.length);
+ for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);
+ return { mimeType: match[1], buffer: bytes };
}
export type UploadedImportImage = ImportedImageCandidate;
@@ -76,7 +78,7 @@ export class ImageUploadPipeline {
? mimeType
: 'image/jpeg';
- const bytes = decoded?.buffer ?? Buffer.from(await response!.arrayBuffer());
+ const bytes = decoded?.buffer ?? new Uint8Array(await response!.arrayBuffer());
const fileName = `${Date.now()}-${slugify(image.title ?? image.alt ?? `import-${image.ordinal}`)}.${mimeExtension(normalizedMimeType)}`;
const storageKey = `${this.ownerId}/imports/${fileName}`;
diff --git a/apps/api/src/worker.ts b/apps/api/src/worker.ts
index 4f75437..bdefaef 100644
--- a/apps/api/src/worker.ts
+++ b/apps/api/src/worker.ts
@@ -4,4 +4,4 @@ export default {
async fetch(request: Request, env: ApiEnv): Promise {
return handleRequest(request, env);
},
-} satisfies ExportedHandler;
+};
diff --git a/apps/docs/src/.vitepress/config.mts b/apps/docs/src/.vitepress/config.mts
index fcdc2bc..513addd 100644
--- a/apps/docs/src/.vitepress/config.mts
+++ b/apps/docs/src/.vitepress/config.mts
@@ -2,6 +2,10 @@ import { defineConfig } from 'vitepress'
export default defineConfig({
srcDir: '../../../docs',
+ head: [
+ ['meta', { name: 'robots', content: 'noindex, nofollow, noarchive, noimageindex' }],
+ ['meta', { name: 'googlebot', content: 'noindex, nofollow' }],
+ ],
locales: {
en: {
diff --git a/apps/docs/src/.vitepress/public/robots.txt b/apps/docs/src/.vitepress/public/robots.txt
new file mode 100644
index 0000000..0c9922f
--- /dev/null
+++ b/apps/docs/src/.vitepress/public/robots.txt
@@ -0,0 +1,20 @@
+User-agent: *
+Disallow: /
+
+User-agent: GPTBot
+Disallow: /
+
+User-agent: ChatGPT-User
+Disallow: /
+
+User-agent: CCBot
+Disallow: /
+
+User-agent: anthropic-ai
+Disallow: /
+
+User-agent: Claude-Web
+Disallow: /
+
+User-agent: Google-Extended
+Disallow: /
diff --git a/apps/web/index.html b/apps/web/index.html
index 67a4c9d..0e3696d 100644
--- a/apps/web/index.html
+++ b/apps/web/index.html
@@ -24,6 +24,8 @@
+
+
diff --git a/apps/web/public/robots.txt b/apps/web/public/robots.txt
new file mode 100644
index 0000000..0c9922f
--- /dev/null
+++ b/apps/web/public/robots.txt
@@ -0,0 +1,20 @@
+User-agent: *
+Disallow: /
+
+User-agent: GPTBot
+Disallow: /
+
+User-agent: ChatGPT-User
+Disallow: /
+
+User-agent: CCBot
+Disallow: /
+
+User-agent: anthropic-ai
+Disallow: /
+
+User-agent: Claude-Web
+Disallow: /
+
+User-agent: Google-Extended
+Disallow: /