37b986aa4b
## What Vendors a narrowed copy of [`@genql/cli@3.0.5`](https://github.com/remorses/genql) (MIT) into `packages/twenty-client-sdk/src/generate/genql/` and repoints the two client generators at it, then removes `@genql/cli` from `twenty-client-sdk`, `twenty-sdk` and `create-twenty-app`. ## Why `@genql/cli` was used **only** to generate the typed GraphQL client from an SDL string. It is unmaintained and pulls in vulnerable/abandoned transitives — `undici@5` (**30 Dependabot alerts**), `native-fetch`, `listr`, `yargs`, etc. None of these were ever executed by Twenty: the sole consumer of `undici`/`native-fetch` is `@genql/cli`'s live-endpoint schema-introspection path, and Twenty always passes a schema string, never an endpoint. Removing the package eliminates the dependency at the source — for Twenty and for scaffolded end-user apps. ## What changed vs upstream The vendored copy (`genql/README.md` + `genql/LICENSE`) keeps the `render/` and `runtime/` trees verbatim and narrows the orchestration: - **Dropped the endpoint/introspection path** (`schema/fetchSchema.ts`) — the only `undici`/`native-fetch`/`qs` consumer. - **Dropped `listr`** — generation tasks run as plain sequential `async` functions (file contents unchanged). - **Replaced `fs-extra`/`mkdirp`/`rimraf`** with `node:fs`. - **Runtime templates are imported as `?raw`** and bundled, instead of read from `node_modules` at generation time. - **Kept `prettier@^2.8` and `@graphql-tools/*`** so the generated output is byte-for-byte identical. ## Verification - **Byte-identical output**: regenerating the metadata client from its committed schema produces a recursive-diff-clean result vs the previous `@genql/cli` output (including the copied `runtime/` folder). The core client generates and esbuild-bundles cleanly. - The public `twenty-client-sdk/generate` barrel API is unchanged (twenty-server / twenty-sdk consumers unaffected). - `undici@^5`, `native-fetch`, `@genql/cli`, `listr`, `yargs@^15` and `subscriptions-transport-ws@0.9` are gone from `yarn.lock` (net −364 lines). - `twenty-client-sdk` and `twenty-sdk` typecheck, lint and build; `twenty-client-sdk` tests pass (9/9). ## Notes - The vendored folder is excluded from `oxlint`/`oxfmt` (it is third-party code, with `@ts-nocheck` on the verbatim renderers, mirroring the generated output). - Stacks conceptually on #21334 (drops `@genql/runtime`); the two are independent and only overlap trivially in `yarn.lock`. `@genql/runtime` is intentionally left for that PR.
123 lines
3.2 KiB
TypeScript
123 lines
3.2 KiB
TypeScript
import { appendFile, copyFile, writeFile } from 'node:fs/promises';
|
|
import { join } from 'node:path';
|
|
|
|
import { build } from 'esbuild';
|
|
import { DEFAULT_API_URL_NAME } from 'twenty-shared/application';
|
|
|
|
import { buildClientWrapperSource } from './client-wrapper';
|
|
import { emptyDir, ensureDir, move, remove } from './fs-utils';
|
|
import { generate } from './genql';
|
|
import twentyClientTemplateSource from './twenty-client-template.ts?raw';
|
|
|
|
const COMMON_SCALAR_TYPES = {
|
|
DateTime: 'string',
|
|
JSON: 'Record<string, unknown>',
|
|
UUID: 'string',
|
|
};
|
|
|
|
export const GENERATED_CORE_DIR = 'core/generated';
|
|
|
|
// Generates the core API client from a GraphQL schema string.
|
|
// Produces both TypeScript source and compiled ESM/CJS bundles.
|
|
export const generateCoreClientFromSchema = async ({
|
|
schema,
|
|
outputPath,
|
|
clientWrapperTemplateSource,
|
|
}: {
|
|
schema: string;
|
|
outputPath: string;
|
|
clientWrapperTemplateSource?: string;
|
|
}): Promise<void> => {
|
|
const templateSource =
|
|
clientWrapperTemplateSource ?? twentyClientTemplateSource;
|
|
const tempPath = `${outputPath}.tmp`;
|
|
|
|
await ensureDir(tempPath);
|
|
await emptyDir(tempPath);
|
|
|
|
try {
|
|
await generate({
|
|
schema,
|
|
output: tempPath,
|
|
scalarTypes: COMMON_SCALAR_TYPES,
|
|
});
|
|
|
|
const clientContent = buildClientWrapperSource(templateSource, {
|
|
apiClientName: 'CoreApiClient',
|
|
defaultUrl: `\`\${process.env.${DEFAULT_API_URL_NAME}}/graphql\``,
|
|
includeUploadFile: true,
|
|
});
|
|
|
|
await appendFile(join(tempPath, 'index.ts'), clientContent);
|
|
|
|
await remove(outputPath);
|
|
await move(tempPath, outputPath);
|
|
|
|
await compileGeneratedClient(outputPath);
|
|
} catch (error) {
|
|
await remove(tempPath);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// Generates the core client and replaces the pre-built stub inside
|
|
// an installed twenty-client-sdk package (dist/core.mjs and dist/core.cjs).
|
|
// Generated source files are kept in dist/generated-core/ for consumers
|
|
// that need the raw .ts files (e.g. the app:dev upload step).
|
|
export const replaceCoreClient = async ({
|
|
packageRoot,
|
|
schema,
|
|
}: {
|
|
packageRoot: string;
|
|
schema: string;
|
|
}): Promise<void> => {
|
|
const generatedPath = join(packageRoot, 'dist', GENERATED_CORE_DIR);
|
|
|
|
await generateCoreClientFromSchema({ schema, outputPath: generatedPath });
|
|
|
|
await copyFile(
|
|
join(generatedPath, 'index.mjs'),
|
|
join(packageRoot, 'dist', 'core.mjs'),
|
|
);
|
|
await copyFile(
|
|
join(generatedPath, 'index.cjs'),
|
|
join(packageRoot, 'dist', 'core.cjs'),
|
|
);
|
|
};
|
|
|
|
const compileGeneratedClient = async (generatedDir: string): Promise<void> => {
|
|
const entryPoint = join(generatedDir, 'index.ts');
|
|
const outfile = join(generatedDir, 'index.mjs');
|
|
|
|
await build({
|
|
entryPoints: [entryPoint],
|
|
outfile,
|
|
bundle: true,
|
|
format: 'esm',
|
|
platform: 'node',
|
|
target: 'node18',
|
|
sourcemap: false,
|
|
minify: false,
|
|
});
|
|
|
|
await build({
|
|
entryPoints: [entryPoint],
|
|
outfile: join(generatedDir, 'index.cjs'),
|
|
bundle: true,
|
|
format: 'cjs',
|
|
platform: 'node',
|
|
target: 'node18',
|
|
sourcemap: false,
|
|
minify: false,
|
|
});
|
|
|
|
await writeFile(
|
|
join(generatedDir, 'package.json'),
|
|
JSON.stringify(
|
|
{ type: 'module', main: 'index.mjs', module: 'index.mjs' },
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
};
|