refactor: remove bundled personal portfolio

This commit is contained in:
2026-09-16 10:15:51 +03:30
parent db8f257a98
commit 76ab2d1078
115 changed files with 102 additions and 5460 deletions

View File

@@ -7,10 +7,24 @@ const cwd = path.resolve('.next/standalone');
const directory = await mkdtemp(path.join(tmpdir(), 'portfolio-e2e-'));
const port = process.env.PLAYWRIGHT_PORT || '43119';
const env = { ...process.env, DATA_DIR: directory, SITE_URL: `http://127.0.0.1:${port}`, HOSTNAME: '127.0.0.1', PORT: port, OWNER_EMAIL: 'e2e@example.com', OWNER_PASSWORD: 'Only-for-isolated-e2e-tests-2026' };
for (const command of ['import-legacy', 'create-owner']) {
for (const command of ['init', 'create-owner']) {
const result = spawnSync(process.execPath, ['maintenance.mjs', command], { cwd, env, stdio: 'inherit' });
if (result.status !== 0) { await rm(directory, { recursive: true, force: true }); process.exit(result.status || 1); }
}
// Generic sample content keeps browser fixtures independent of a private portfolio.
const seeded = spawnSync(process.execPath, ['--import', 'tsx', '-e', `
const { initialize } = require('./src/server/initialize.ts');
const { readContent, saveContent } = require('./src/server/content.ts');
const db = initialize();
saveContent(db, 'languages', { code: 'fa', name: 'فارسی', direction: 'rtl', enabled: true, messages: {} });
const project = readContent(db).entries.find(item => item.kind === 'projects');
project.shared.liveUrl = 'https://example.com';
project.shared.caseStudy.gallery = [1, 2].map(index => ({ image: project.shared.image, alt: 'Sample image ' + index, caption: 'Sample caption ' + index }));
saveContent(db, 'entries', project);
saveContent(db, 'entries', { ...project, id: 'another-project', position: 1, shared: { ...project.shared, title: 'Another project' } });
db.sqlite.close();
`], { cwd: path.resolve('.'), env, stdio: 'inherit' });
if (seeded.status !== 0) { await rm(directory, { recursive: true, force: true }); process.exit(seeded.status || 1); }
const server = spawn(process.execPath, ['server.js'], { cwd, env, stdio: 'inherit' });
process.on('SIGTERM', () => server.kill()); process.on('SIGINT', () => server.kill());
server.on('exit', async code => { await rm(directory, { recursive: true, force: true }); process.exit(code || 0); });

View File

@@ -1,30 +0,0 @@
// Run only when refreshing the explicit legacy-import fixture, before editing legacy data.
import { mkdirSync, writeFileSync } from 'node:fs';
import { messagesByLocale } from '../src/i18n/content';
import { getProjects } from '../src/data/projects';
import { getExperiences } from '../src/data/experience';
import { getEducation } from '../src/data/education';
import { getMilestones } from '../src/data/milestones';
import { filterTabs, skillsByDiscipline } from '../src/data/skills';
const placeholders: Record<string, string[]> = {
ariaLabel: ['progress'], progressLabel: ['progress'], liveProjectLabel: ['title'],
openGalleryImage: ['caption'], selectGalleryImage: ['index', 'caption'],
imageCounter: ['current', 'total'], logoAlt: ['company'], viewCertificateLabel: ['title'],
};
const fixture = { messages: messagesByLocale, entries: Object.fromEntries(['en', 'fa'].map(locale => [locale, {
projects: getProjects(locale as 'en' | 'fa'), experience: getExperiences(locale as 'en' | 'fa'),
education: [{ id: 'education', ...getEducation(locale as 'en' | 'fa') }],
milestones: getMilestones(locale as 'en' | 'fa'),
skills: filterTabs.map(tab => ({ id: tab.id, label: messagesByLocale[locale as 'en' | 'fa'].skills.filterLabels[tab.id], ...skillsByDiscipline[tab.id] })),
}])) };
mkdirSync('src/seed', { recursive: true });
writeFileSync('src/seed/legacy.json', JSON.stringify(fixture, (key, value) => {
if (typeof value !== 'function') return value;
const names = placeholders[key];
let result = value(...names.map((name, index) => ['progress', 'index', 'current', 'total'].includes(name) ? 98760 + index : `{${name}}`));
names.forEach((name, index) => {
result = result.replaceAll(String(98760 + index), `{${name}}`).replaceAll(new Intl.NumberFormat('fa-IR', { useGrouping: false }).format(98760 + index), `{${name}}`);
});
return result;
}, 2) + '\n');

View File

@@ -40,7 +40,7 @@ fi
docker exec "$container_name" node -e "fetch('http://127.0.0.1:3000/').then(async r=>{if(!r.ok||!(await r.text()).includes('Your Name'))process.exit(1)})"
# Exercise the packaged maintenance command and native SQLite driver in the image.
docker exec "$container_name" node maintenance.mjs import-legacy
docker exec "$container_name" node maintenance.mjs init
docker exec --env OWNER_EMAIL=smoke@example.com --env OWNER_PASSWORD=Isolated-container-smoke-password-2026 "$container_name" node maintenance.mjs create-owner
docker exec -i "$container_name" node <<'NODE'
@@ -53,26 +53,16 @@ async function expectOk(path, options) {
}
const english = await expectOk('/');
await expectOk('/fa');
await expectOk('/projects/interanet-solutions');
await expectOk('/fa/projects/interanet-solutions');
await expectOk('/projects/sample-projects');
const html = await english.text();
const staticAsset = html.match(/(?:src|href)="(\/_next\/static\/[^"?#]+)/)?.[1];
if (!staticAsset) throw new Error('No built static asset was found in the home page.');
await expectOk(staticAsset);
const optimized = await expectOk(
'/_next/image?url=%2Fimages%2Favatar.png&w=256&q=75',
{ headers: { Accept: 'image/webp' } },
);
const contentType = optimized.headers.get('content-type') ?? '';
if (!/^image\/(?:webp|png)$/.test(contentType)) {
throw new Error(`Unexpected optimized image type: ${contentType}`);
}
if ((await optimized.arrayBuffer()).byteLength === 0) {
throw new Error('Optimized image response was empty.');
}
await expectOk('/images/portfolio-placeholder.svg');
NODE
# Upload through the real authenticated endpoint, then replace the container.
@@ -103,7 +93,7 @@ for (let attempt = 0; attempt < 30; attempt++) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
if (!ready) throw new Error('Replacement container did not start');
const page = await fetch(`${origin}/projects/interanet-solutions`);
const page = await fetch(`${origin}/projects/sample-projects`);
if (!page.ok) throw new Error('Database did not survive container replacement');
const key = readFileSync('/data/smoke-media-key', 'utf8');
const media = await fetch(`${origin}/media/${key}`);