snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,264 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { dirname, relative, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import {
type ComponentDoc,
type ParserOptions,
type PropItem,
} from 'react-docgen-typescript';
import ts from 'typescript';
import { DESIGN_TOKENS } from '../design-tokens/designTokens';
import { collectLeaves } from '../design-tokens/pipeline/collectLeaves';
import { DOCUMENTED_COMPONENTS } from '../docs/components';
import { DocumentationParser } from '../docs/DocumentationParser';
import { formatDocumentationTokenValue } from '../docs/formatDocumentationTokenValue';
import { normalizeDocumentationDefaultValue } from '../docs/normalizeDocumentationDefaultValue';
import { normalizeDocumentationPropType } from '../docs/normalizeDocumentationPropType';
import {
type ComponentDocumentation,
type TokenDocumentation,
} from '../docs/types';
const HIDDEN_PROP_TAGS = ['ignore', 'internal'];
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const sourceRoot = resolve(packageRoot, 'src');
const configurationPath = resolve(packageRoot, 'tsconfig.json');
const configuration = ts.readConfigFile(configurationPath, ts.sys.readFile);
if (configuration.error) {
throw new Error(
ts.flattenDiagnosticMessageText(configuration.error.messageText, '\n'),
);
}
const { options, errors } = ts.parseJsonConfigFileContent(
configuration.config,
ts.sys,
packageRoot,
);
if (errors.length > 0) {
throw new Error(
errors
.map((error) => ts.flattenDiagnosticMessageText(error.messageText, '\n'))
.join('\n'),
);
}
const sourcePaths = DOCUMENTED_COMPONENTS.map((component) =>
resolve(sourceRoot, component.source),
);
const program = ts.createProgram(sourcePaths, {
...options,
preserveSymlinks: true,
});
const checker = program.getTypeChecker();
const isReactNativeAttribute = (prop: PropItem): boolean =>
prop.declarations !== undefined &&
prop.declarations.length > 0 &&
prop.declarations.every((declaration) =>
declaration.fileName.includes('@types/react/'),
);
const isHiddenProp = (prop: PropItem): boolean =>
HIDDEN_PROP_TAGS.some((tag) => tag in (prop.tags ?? {}));
const isDeclaredInTwentyUi = (prop: PropItem): boolean =>
prop.declarations?.some((declaration) =>
declaration.fileName.startsWith(sourceRoot),
) ?? false;
const parserOptions: ParserOptions = {
shouldExtractLiteralValuesFromEnum: true,
shouldIncludePropTagMap: true,
propFilter: (prop) => !isReactNativeAttribute(prop) && !isHiddenProp(prop),
};
const parser = new DocumentationParser(program, parserOptions);
const documentedChildrenParser = new DocumentationParser(program, {
...parserOptions,
skipChildrenPropWithoutDoc: false,
});
const extractProps = ({
symbol,
name,
propDescriptions = {},
propDefaults = {},
}: {
symbol: ts.Symbol;
name: string;
propDescriptions?: Partial<Record<string, string>>;
propDefaults?: Partial<Record<string, string>>;
}): ComponentDocumentation['props'] => {
const declaration = symbol.valueDeclaration;
if (!declaration) {
throw new Error(`Could not find the declaration for ${name}`);
}
const componentParser =
'children' in propDescriptions ? documentedChildrenParser : parser;
const parsed: ComponentDoc | null = componentParser.getComponentInfo(
symbol,
declaration.getSourceFile(),
() => name,
);
if (!parsed || Object.keys(parsed.props).length === 0) {
throw new Error(`Could not extract props for ${name}`);
}
return Object.values(parsed.props)
.sort((left, right) => left.name.localeCompare(right.name, 'en'))
.map((prop) => {
const description = (
propDescriptions[prop.name] ?? prop.description
).trim();
if (description.length === 0 && isDeclaredInTwentyUi(prop)) {
throw new Error(
`Missing description for ${name}.${prop.name}. Document the prop in its props type or documentation metadata.`,
);
}
return {
name: prop.name,
type: normalizeDocumentationPropType({
type: prop.type,
required: prop.required,
}),
required: prop.required,
defaultValue:
propDefaults[prop.name] ??
normalizeDocumentationDefaultValue(prop.defaultValue),
description,
};
});
};
const components: ComponentDocumentation[] = DOCUMENTED_COMPONENTS.map(
(component) => {
const source = program.getSourceFile(resolve(sourceRoot, component.source));
const moduleSymbol = source && checker.getSymbolAtLocation(source);
const symbol =
moduleSymbol &&
checker
.getExportsOfModule(moduleSymbol)
.find((candidate) => candidate.name === component.name);
if (!symbol?.valueDeclaration) {
throw new Error(`Could not find the export for ${component.name}`);
}
const type = checker.getTypeOfSymbolAtLocation(
symbol,
symbol.valueDeclaration,
);
const metadata = {
name: component.name,
entryPoint: component.entryPoint,
slug: component.slug,
};
const props =
type.getCallSignatures().length > 0
? extractProps({
symbol,
name: component.name,
propDescriptions:
'propDescriptions' in component
? component.propDescriptions
: undefined,
propDefaults:
'propDefaults' in component ? component.propDefaults : undefined,
})
: [];
const partNames: readonly string[] | undefined =
'parts' in component ? component.parts : undefined;
const partPropDescriptions: Partial<
Record<string, Partial<Record<string, string>>>
> =
'partPropDescriptions' in component ? component.partPropDescriptions : {};
const componentParts = type
.getProperties()
.filter((part) => partNames?.includes(part.name) ?? props.length === 0);
const parts = componentParts.map((part) => {
const declaration = part.valueDeclaration;
if (!declaration || !ts.isPropertyAssignment(declaration)) {
throw new Error(`Could not resolve ${component.name}.${part.name}`);
}
let partSymbol = checker.getSymbolAtLocation(declaration.initializer);
if (!partSymbol) {
throw new Error(`Could not find ${component.name}.${part.name}`);
}
if (partSymbol.flags & ts.SymbolFlags.Alias) {
partSymbol = checker.getAliasedSymbol(partSymbol);
}
return {
name: part.name,
props: extractProps({
symbol: partSymbol,
name: `${component.name}.${part.name}`,
propDescriptions: partPropDescriptions[part.name],
}),
};
});
if (props.length === 0 && parts.length === 0) {
throw new Error(`Could not extract parts for ${component.name}`);
}
return {
...metadata,
props,
...(parts.length > 0 ? { parts } : {}),
};
},
);
const tokens: TokenDocumentation[] = collectLeaves(DESIGN_TOKENS)
.map((leaf) => ({
path: leaf.path.join('.'),
cssVariable: leaf.varName,
light: formatDocumentationTokenValue(leaf.light),
dark: formatDocumentationTokenValue(leaf.dark),
isNumber: leaf.unit === 'number',
}))
.sort((left, right) =>
left.path.localeCompare(right.path, 'en', { numeric: true }),
);
const outputs = [
{ name: 'components.docs.json', data: components },
{ name: 'tokens.docs.json', data: tokens },
];
const isCheckMode = process.argv.includes('--check');
for (const output of outputs) {
const outputPath = resolve(packageRoot, 'generated', output.name);
const content = `${JSON.stringify(output.data, null, 2)}\n`;
if (isCheckMode) {
if (
!existsSync(outputPath) ||
readFileSync(outputPath, 'utf8') !== content
) {
process.stderr.write(
`Stale documentation data: ${relative(packageRoot, outputPath)}. Run npx nx generate:ui twenty-docs.\n`,
);
process.exitCode = 1;
}
} else {
mkdirSync(dirname(outputPath), { recursive: true });
writeFileSync(outputPath, content);
}
}