293ff4c462
<img width="1388" height="858" alt="image" src="https://github.com/user-attachments/assets/59f16bf7-5908-4624-b3af-51416bbebba3" /> ## What When an app is built (`twenty build` / `twenty publish`), the SDK now generates a marketplace cover image and sets it as the app's screenshot, but only when the app declares a `logoUrl` and has no `screenshots`. The cover composites the app's logo and the Twenty logo over the branded halftone backdrop, matching the design reference. ## Why Most apps ship a logo but no screenshots, so their marketplace detail page had no hero visual. This gives them a polished cover for free, with no per-app design work. ## Notes for reviewers - Generation lives in the build path (`operations/build.ts`), not `buildManifest`, so `twenty dev` and the shared manifest builder are untouched. It is best-effort: on failure it logs a warning and the build continues. - The cover is written to `.twenty/output` and registered as a public asset + screenshot, so the existing copy/checksum/serve pipeline handles it unchanged. No app source files are modified. - Adds `sharp` as a runtime dependency of `twenty-sdk` (a build-time tool, like `esbuild`); it is not bundled into built apps. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22011?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
97 lines
2.3 KiB
TypeScript
97 lines
2.3 KiB
TypeScript
import fs from 'fs';
|
|
import path from 'path';
|
|
import { type PackageJson } from 'type-fest';
|
|
import { defineConfig } from 'vite';
|
|
import tsconfigPaths from 'vite-tsconfig-paths';
|
|
|
|
import packageJson from './package.json';
|
|
|
|
const copyCoverAssetsPlugin = () => ({
|
|
name: 'copy-cover-assets',
|
|
closeBundle() {
|
|
const source = path.resolve(
|
|
__dirname,
|
|
'src/cli/utilities/build/cover/assets/halftone-backdrop.png',
|
|
);
|
|
const destinationDir = path.resolve(__dirname, 'dist/assets');
|
|
|
|
fs.mkdirSync(destinationDir, { recursive: true });
|
|
fs.copyFileSync(source, path.join(destinationDir, 'halftone-backdrop.png'));
|
|
},
|
|
});
|
|
|
|
export default defineConfig(() => {
|
|
return {
|
|
root: __dirname,
|
|
cacheDir: '../../node_modules/.vite/packages/twenty-sdk-node',
|
|
resolve: {
|
|
alias: {
|
|
'@/': path.resolve(__dirname, 'src') + '/',
|
|
},
|
|
},
|
|
plugins: [
|
|
tsconfigPaths({
|
|
root: __dirname,
|
|
}),
|
|
copyCoverAssetsPlugin(),
|
|
],
|
|
build: {
|
|
emptyOutDir: false,
|
|
outDir: 'dist',
|
|
lib: {
|
|
entry: {
|
|
cli: 'src/cli/cli.ts',
|
|
operations: 'src/cli/operations/index.ts',
|
|
'front-component-renderer/build':
|
|
'src/front-component-renderer/build/index.ts',
|
|
},
|
|
name: 'twenty-sdk',
|
|
},
|
|
rollupOptions: {
|
|
external: (id: string) => {
|
|
if (/^node:/.test(id)) {
|
|
return true;
|
|
}
|
|
|
|
const builtins = [
|
|
'child_process',
|
|
'crypto',
|
|
'fs',
|
|
'fs/promises',
|
|
'module',
|
|
'os',
|
|
'path',
|
|
'stream',
|
|
'url',
|
|
'util',
|
|
];
|
|
|
|
if (builtins.includes(id)) {
|
|
return true;
|
|
}
|
|
|
|
const deps = Object.keys(
|
|
(packageJson as PackageJson).dependencies || {},
|
|
);
|
|
|
|
return deps.some((dep) => id === dep || id.startsWith(dep + '/'));
|
|
},
|
|
output: [
|
|
{
|
|
format: 'es' as const,
|
|
entryFileNames: '[name].mjs',
|
|
},
|
|
{
|
|
format: 'cjs' as const,
|
|
interop: 'auto' as const,
|
|
esModule: true,
|
|
exports: 'named' as const,
|
|
entryFileNames: '[name].cjs',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
logLevel: 'warn' as const,
|
|
};
|
|
});
|