Fix nx lint setup (#3234)

* Fix nx lint setup

* Fixes

* Fixes

* Add missing metadata

Fixes

Fix

Fixes

* Fix
This commit is contained in:
Charles Bochet 2024-01-04 16:39:57 +01:00 committed by GitHub
parent 52d4f8e466
commit c15e138d72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 2961 additions and 3287 deletions

View File

@ -19,86 +19,15 @@
"tools/eslint-rules"
]
},
"dependencies": {
"patch-package": "^8.0.0",
"tslib": "^2.3.0"
},
"devDependencies": {
"@graphql-codegen/cli": "^3.3.1",
"@graphql-codegen/client-preset": "^4.1.0",
"@graphql-codegen/typescript": "^3.0.4",
"@graphql-codegen/typescript-operations": "^3.0.4",
"@graphql-codegen/typescript-react-apollo": "^3.3.7",
"@nx/eslint": "17.2.7",
"@nx/eslint-plugin": "17.2.7",
"@nx/jest": "17.2.7",
"@nx/js": "17.2.7",
"@storybook/addon-actions": "^7.6.3",
"@storybook/addon-coverage": "^1.0.0",
"@storybook/addon-essentials": "^7.6.3",
"@storybook/addon-interactions": "^7.6.3",
"@storybook/addon-links": "^7.6.3",
"@storybook/addon-onboarding": "^1.0.9",
"@storybook/addon-themes": "^7.6.3",
"@storybook/blocks": "^7.6.3",
"@storybook/react": "^7.6.3",
"@storybook/react-vite": "^7.6.3",
"@storybook/test": "^7.6.3",
"@storybook/test-runner": "^0.16.0",
"@stylistic/eslint-plugin": "^1.5.0",
"@swc-node/register": "~1.6.7",
"@swc/core": "~1.3.100",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^13.4.0",
"@types/apollo-upload-client": "^17.0.2",
"@types/deep-equal": "^1.0.1",
"@types/jest": "^29.5.11",
"@types/js-cookie": "^3.0.3",
"@types/lodash.camelcase": "^4.3.7",
"@types/lodash.debounce": "^4.0.7",
"@types/lodash.kebabcase": "^4.1.7",
"@types/lodash.snakecase": "^4.1.9",
"@types/luxon": "^3.3.0",
"@types/node": "20.10.0",
"@types/react": "^18.2.39",
"@types/react-datepicker": "^4.11.2",
"@types/react-dom": "^18.2.15",
"@types/scroll-into-view": "^1.16.0",
"@types/uuid": "^9.0.1",
"@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.10.0",
"@typescript-eslint/utils": "^6.9.1",
"@vitejs/plugin-react-swc": "^3.5.0",
"chromatic": "^6.18.0",
"concurrently": "^8.0.1",
"cross-var": "^1.1.0",
"dotenv-cli": "^7.2.1",
"eslint": "^8.53.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-prefer-arrow": "^1.2.3",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.4",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-storybook": "^0.6.15",
"eslint-plugin-unused-imports": "^3.0.0",
"http-server": "^14.1.1",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"msw": "^2.0.11",
"msw-storybook-addon": "2.0.0--canary.122.b3ed3b1.0",
"nx": "17.2.7",
"prettier": "3.1.0",
"storybook": "^7.6.3",
"storybook-addon-cookie": "^3.1.0",
"storybook-addon-pseudo-states": "^2.1.2",
"ts-jest": "^29.1.1",
"ts-node": "10.9.1",
"typescript": "^5.3.3",
"vite": "^5.0.0",
"vite-plugin-svgr": "^4.2.0"
},
"dependencies": {
"patch-package": "^8.0.0",
"tslib": "^2.3.0"
"nx": "^17.2.8"
}
}

View File

@ -48,6 +48,12 @@ prod-postgres-build:
prod-postgres-run:
@docker run -d -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres --name twenty-postgres twenty-postgres
prod-website-build:
@cd ../.. && docker build -f ./packages/twenty-docker/prod/twenty-website/Dockerfile --build-arg GITHUB_TOKEN=REPLACE_ME --tag twenty-website . && cd -
prod-website-run:
@docker run -d -p 3000:3000 --name twenty-website twenty-website
release-front:
@cd ../.. && docker buildx build \
--push \

View File

@ -10,7 +10,6 @@ COPY ./tools/eslint-rules /app/tools/eslint-rules
COPY ./packages/twenty-server /app/packages/twenty-server
RUN yarn
RUN yarn
RUN yarn nx build twenty-server
WORKDIR /app/packages/twenty-server

View File

@ -0,0 +1,30 @@
FROM node:18.17.1-alpine as twenty-website-build
ARG GITHUB_TOKEN
ARG BASE_URL
ENV GITHUB_TOKEN=$GITHUB_TOKEN
ENV BASE_URL=$BASE_URL
WORKDIR /app
COPY ./package.json .
COPY ./.eslintrc.js .
COPY ./yarn.lock .
COPY ./.yarnrc.yml .
COPY ./.yarn/releases /app/.yarn/releases
COPY ./packages/twenty-website /app/packages/twenty-website
RUN yarn
RUN yarn nx build twenty-website
FROM node:18.17.1-alpine as twenty-website
WORKDIR /app/packages/twenty-website
COPY --from=twenty-website-build /app /app
LABEL org.opencontainers.image.source=https://github.com/twentyhq/twenty
LABEL org.opencontainers.image.description="This image provides a consistent and reproducible environment for the website."
CMD ["/bin/sh", "-c", "yarn nx start twenty-website"]

View File

@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
"nx": "NX_DEFAULT_PROJECT=twenty-docs node ../../node_modules/nx/bin/nx.js",
"docusaurus": "docusaurus",
"start": "docusaurus start --host 0.0.0.0 --port 5001",
"build": "docusaurus build",

View File

@ -80,7 +80,7 @@ module.exports = {
rules: {
'storybook/no-uninstalled-addons': [
'error',
{ packageJsonLocation: path.resolve('../../package.json') },
{ packageJsonLocation: path.resolve('package.json') },
],
},
},

View File

@ -4,6 +4,7 @@
"private": true,
"type": "module",
"scripts": {
"nx": "NX_DEFAULT_PROJECT=twenty-front node ../../node_modules/nx/bin/nx.js",
"start": "vite --host",
"start:clean": "yarn start --force",
"build": "tsc && vite build && yarn build:inject-runtime-env",
@ -94,6 +95,79 @@
"xlsx-ugnis": "^0.19.3",
"zod": "^3.22.2"
},
"devDependencies": {
"@graphql-codegen/cli": "^3.3.1",
"@graphql-codegen/client-preset": "^4.1.0",
"@graphql-codegen/typescript": "^3.0.4",
"@graphql-codegen/typescript-operations": "^3.0.4",
"@graphql-codegen/typescript-react-apollo": "^3.3.7",
"@storybook/addon-actions": "^7.6.3",
"@storybook/addon-coverage": "^1.0.0",
"@storybook/addon-essentials": "^7.6.7",
"@storybook/addon-interactions": "^7.6.7",
"@storybook/addon-links": "^7.6.7",
"@storybook/addon-onboarding": "^1.0.10",
"@storybook/addon-themes": "^7.6.7",
"@storybook/blocks": "^7.6.3",
"@storybook/react": "^7.6.3",
"@storybook/react-vite": "^7.6.3",
"@storybook/test": "^7.6.3",
"@storybook/test-runner": "^0.16.0",
"@stylistic/eslint-plugin": "^1.5.0",
"@swc-node/register": "~1.6.7",
"@swc/core": "~1.3.100",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^13.4.0",
"@types/apollo-upload-client": "^17.0.2",
"@types/deep-equal": "^1.0.1",
"@types/jest": "^29.5.11",
"@types/js-cookie": "^3.0.3",
"@types/lodash.camelcase": "^4.3.7",
"@types/lodash.debounce": "^4.0.7",
"@types/lodash.kebabcase": "^4.1.7",
"@types/lodash.snakecase": "^4.1.9",
"@types/luxon": "^3.3.0",
"@types/node": "^20.10.6",
"@types/react": "^18.2.39",
"@types/react-datepicker": "^4.11.2",
"@types/react-dom": "^18.2.15",
"@types/scroll-into-view": "^1.16.0",
"@types/uuid": "^9.0.1",
"@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.10.0",
"@typescript-eslint/utils": "^6.9.1",
"@vitejs/plugin-react-swc": "^3.5.0",
"chromatic": "^6.18.0",
"concurrently": "^8.0.1",
"cross-var": "^1.1.0",
"dotenv-cli": "^7.2.1",
"eslint": "^8.53.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-prefer-arrow": "^1.2.3",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.4",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-storybook": "^0.6.15",
"eslint-plugin-unused-imports": "^3.0.0",
"http-server": "^14.1.1",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"msw": "^2.0.11",
"msw-storybook-addon": "2.0.0--canary.122.b3ed3b1.0",
"prettier": "3.1.0",
"storybook": "^7.6.3",
"storybook-addon-cookie": "^3.2.0",
"storybook-addon-pseudo-states": "^2.1.2",
"ts-jest": "^29.1.1",
"ts-node": "10.9.1",
"typescript": "^5.3.3",
"vite": "^5.0.0",
"vite-plugin-svgr": "^4.2.0"
},
"engines": {
"node": "^18.16.0",
"npm": "please-use-yarn",

View File

@ -182,6 +182,12 @@ export enum FieldMetadataType {
Uuid = 'UUID'
}
export type FullName = {
__typename?: 'FullName';
firstName: Scalars['String']['output'];
lastName: Scalars['String']['output'];
};
export type IdFilterComparison = {
eq?: InputMaybe<Scalars['ID']['input']>;
gt?: InputMaybe<Scalars['ID']['input']>;
@ -389,6 +395,11 @@ export enum RelationMetadataType {
OneToOne = 'ONE_TO_ONE'
}
export type Sentry = {
__typename?: 'Sentry';
dsn: Scalars['String']['output'];
};
export type Support = {
__typename?: 'Support';
supportDriver: Scalars['String']['output'];
@ -454,7 +465,7 @@ export type User = {
lastName: Scalars['String']['output'];
passwordHash?: Maybe<Scalars['String']['output']>;
updatedAt: Scalars['DateTime']['output'];
workspaceMember: UserWorkspaceMember;
workspaceMember: WorkspaceMember;
};
export type UserEdge = {
@ -465,21 +476,6 @@ export type UserEdge = {
node: User;
};
export type UserWorkspaceMember = {
__typename?: 'UserWorkspaceMember';
avatarUrl?: Maybe<Scalars['String']['output']>;
colorScheme: Scalars['String']['output'];
id: Scalars['ID']['output'];
locale: Scalars['String']['output'];
name: UserWorkspaceMemberName;
};
export type UserWorkspaceMemberName = {
__typename?: 'UserWorkspaceMemberName';
firstName: Scalars['String']['output'];
lastName: Scalars['String']['output'];
};
export type Workspace = {
__typename?: 'Workspace';
allowImpersonation: Scalars['Boolean']['output'];
@ -502,6 +498,15 @@ export type WorkspaceEdge = {
node: Workspace;
};
export type WorkspaceMember = {
__typename?: 'WorkspaceMember';
avatarUrl?: Maybe<Scalars['String']['output']>;
colorScheme: Scalars['String']['output'];
id: Scalars['ID']['output'];
locale: Scalars['String']['output'];
name: FullName;
};
export type Field = {
__typename?: 'field';
createdAt: Scalars['DateTime']['output'];

View File

@ -6,6 +6,7 @@
"private": true,
"license": "UNLICENSED",
"scripts": {
"nx": "NX_DEFAULT_PROJECT=twenty-server node ../../node_modules/nx/bin/nx.js",
"prebuild": "rimraf dist",
"build": "yarn prebuild && nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
@ -17,9 +18,9 @@
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register ../../node_modules/.bin/jest --runInBand",
"test:e2e": "./scripts/run-integration.sh",
"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js",
"typeorm": "ts-node -r tsconfig-paths/register ../../node_modules/typeorm/cli.js",
"typeorm:migrate": "yarn typeorm migration:run -d ./src/database/typeorm/metadata/metadata.datasource.ts && yarn typeorm migration:run -d ./src/database/typeorm/core/core.datasource.ts",
"database:init": "yarn database:setup && yarn database:seed:dev",
"database:setup": "npx ts-node ./scripts/setup-db.ts && yarn database:migrate",
@ -131,8 +132,7 @@
"@types/uuid": "^9.0.2",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-loader": "^9.2.3",
"tsconfig-paths": "4.1.0"
"ts-loader": "^9.2.3"
},
"resolutions": {
"graphql": "16.8.0"

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['../../.eslintrc.js'],
rules: {
'no-console': 'off',
'prefer-arrow/prefer-arrow-functions': 'off',
},
};

View File

@ -1,7 +0,0 @@
{
"extends": [
"next/core-web-vitals",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
]
}

View File

@ -1,5 +0,0 @@
{
"singleQuote": true,
"trailingComma": "all",
"endOfLine": "auto"
}

View File

@ -6,7 +6,7 @@ This is not related in anyway to the main app, which you can find in twenty-fron
## Getting Started
We're using Nest.JS
We're using Next.JS
From the root directory:
```bash

View File

@ -3,6 +3,7 @@
"version": "0.2.2",
"private": true,
"scripts": {
"nx": "NX_DEFAULT_PROJECT=twenty-website node ../../node_modules/nx/bin/nx.js",
"dev": "next dev",
"build": "next build",
"start": "next start",
@ -14,14 +15,18 @@
"@jsdevtools/rehype-toc": "^3.0.2",
"@nivo/calendar": "^0.84.0",
"@nivo/core": "^0.84.0",
"@nx/eslint-plugin": "^17.2.8",
"@octokit/graphql": "^7.0.2",
"@stoplight/elements": "^7.16.2",
"@tabler/icons-react": "^2.44.0",
"better-sqlite3": "^9.2.2",
"graphiql": "^3.0.10",
"graphql": "^16.8.1",
"next": "14.0.4",
"next-mdx-remote": "^4.4.1",
"react": "^18",
"react-dom": "^18",
"react-icons": "^4.12.0",
"rehype-slug": "^6.0.0",
"remark-behead": "^3.1.0",
"remark-gfm": "^3.0.1"

View File

@ -1,65 +1,68 @@
'use client'
'use client';
import styled from '@emotion/styled';
import Link from 'next/link';
export interface User {
login: string;
avatarUrl: string;
}
login: string;
avatarUrl: string;
}
const AvatarGridContainer = styled.div`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
grid-gap: 10px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
grid-gap: 10px;
`;
const AvatarItem = styled.div`
position: relative;
position: relative;
width: 100%;
img {
width: 100%;
height: auto;
display: block;
}
img {
width: 100%;
height: auto;
display: block;
}
.username {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
background-color: rgba(0, 0, 0, 0.7);
color: white;
text-align: center;
visibility: hidden;
opacity: 0;
transition:
opacity 0.3s ease,
visibility 0.3s;
}
.username {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
background-color: rgba(0, 0, 0, 0.7);
color: white;
text-align: center;
visibility: hidden;
opacity: 0;
transition: opacity 0.3s ease, visibility 0.3s;
}
&:hover .username {
visibility: visible;
opacity: 1;
}
&:hover .username {
visibility: visible;
opacity: 1;
}
`;
import React from 'react';
const AvatarGrid = ({ users }: { users: User[] }) => {
return (
<AvatarGridContainer>
{users.map(user => (
<Link href={`/developers/contributors/${user.login}`} key={`l_${user.login}`}>
<AvatarItem key={user.login}>
<img src={user.avatarUrl} alt={user.login} />
<span className="username">{user.login}</span>
</AvatarItem>
</Link>
))}
</AvatarGridContainer>
);
return (
<AvatarGridContainer>
{users.map((user) => (
<Link
href={`/developers/contributors/${user.login}`}
key={`l_${user.login}`}
>
<AvatarItem key={user.login}>
<img src={user.avatarUrl} alt={user.login} />
<span className="username">{user.login}</span>
</AvatarItem>
</Link>
))}
</AvatarGridContainer>
);
};
export default AvatarGrid;

View File

@ -1,10 +1,11 @@
'use client';
import styled from '@emotion/styled';
import { Logo } from './Logo';
import { DiscordIcon, GithubIcon2, LinkedInIcon, XIcon } from './Icons';
import { usePathname } from 'next/navigation';
import { DiscordIcon, GithubIcon2, LinkedInIcon, XIcon } from './Icons';
import { Logo } from './Logo';
const FooterContainer = styled.div`
padding: 64px 96px 64px 96px;
display: flex;

View File

@ -1,12 +1,14 @@
'use client';
import styled from '@emotion/styled';
import { Logo } from './Logo';
import { IBM_Plex_Mono } from 'next/font/google';
import { DiscordIcon, GithubIcon, GithubIcon2, XIcon } from './Icons';
import { usePathname } from 'next/navigation';
import { ExternalArrow } from '@/app/components/ExternalArrow';
import { DiscordIcon, GithubIcon, GithubIcon2, XIcon } from './Icons';
import { Logo } from './Logo';
const IBMPlexMono = IBM_Plex_Mono({
weight: '500',
subsets: ['latin'],

View File

@ -1,12 +1,14 @@
'use client';
import styled from '@emotion/styled';
import { Logo } from './Logo';
import { IBM_Plex_Mono } from 'next/font/google';
import { GithubIcon } from './Icons';
import { useState } from 'react';
import styled from '@emotion/styled';
import { IBM_Plex_Mono } from 'next/font/google';
import { ExternalArrow } from '@/app/components/ExternalArrow';
import { GithubIcon } from './Icons';
import { Logo } from './Logo';
const IBMPlexMono = IBM_Plex_Mono({
weight: '500',
subsets: ['latin'],

View File

@ -1,7 +1,8 @@
import React, { ReactNode, useState } from 'react';
import TokenForm, {
TokenFormProps,
} from '@/app/components/PlaygroundTokenForm';
import React, { ReactNode, useState } from 'react';
type PlaygroundProps = TokenFormProps & {
children?: ReactNode;

View File

@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import { parseJson } from 'nx/src/utils/json';
import { TbLoader2 } from 'react-icons/tb';
import styled from '@emotion/styled';
import { parseJson } from 'nx/src/utils/json';
export type TokenFormProps = {
setOpenApiJson?: (json: object) => void;

View File

@ -1,9 +1,11 @@
'use client'
'use client';
import { ResponsiveTimeRange } from '@nivo/calendar'
import { ResponsiveTimeRange } from '@nivo/calendar';
export const ActivityLog = ({ data }: { data: { value: number, day: string }[] }) => {
return <ResponsiveTimeRange
data={data}
/>;
}
export const ActivityLog = ({
data,
}: {
data: { value: number; day: string }[];
}) => {
return <ResponsiveTimeRange data={data} />;
};

View File

@ -1,9 +1,8 @@
import Image from 'next/image';
import Database from 'better-sqlite3';
import AvatarGrid from '@/app/components/AvatarGrid';
import { ResponsiveTimeRange } from '@nivo/calendar'
import { ActivityLog } from './components/ActivityLog';
import { Metadata } from 'next';
import Image from 'next/image';
import { ActivityLog } from './components/ActivityLog';
interface Contributor {
login: string;
@ -11,21 +10,22 @@ interface Contributor {
pullRequestCount: number;
}
export function generateMetadata({ params }: { params: { slug: string } }): Metadata {
return {
title: params.slug + ' | Contributors',
};
export function generateMetadata({
params,
}: {
params: { slug: string };
}): Metadata {
return {
title: params.slug + ' | Contributors',
};
}
export default async function ({ params }: { params: { slug: string } }) {
const db = new Database('db.sqlite', { readonly: true });
export default async function (
{ params }: { params: { slug: string } }) {
const db = new Database('db.sqlite', { readonly: true });
const contributor = db.prepare(`
const contributor = db
.prepare(
`
SELECT
u.login,
u.avatarUrl,
@ -35,9 +35,13 @@ export default async function (
users u
WHERE
u.login = :user_id
`).get({'user_id' : params.slug}) as Contributor;
`,
)
.get({ user_id: params.slug }) as Contributor;
const pullRequestActivity = db.prepare(`
const pullRequestActivity = db
.prepare(
`
SELECT
COUNT(*) as value,
DATE(createdAt) as day
@ -49,11 +53,13 @@ const pullRequestActivity = db.prepare(`
DATE(createdAt)
ORDER BY
DATE(createdAt)
`).all({'user_id': params.slug}) as { value: number, day: string }[];
`,
)
.all({ user_id: params.slug }) as { value: number; day: string }[];
const pullRequestList = db.prepare(`
const pullRequestList = db
.prepare(
`
SELECT
id,
title,
@ -69,32 +75,46 @@ const pullRequestList = db.prepare(`
authorId = (SELECT id FROM users WHERE login = :user_id)
ORDER BY
DATE(createdAt) DESC
`).all({'user_id': params.slug}) as { title: string, createdAt: string, url: string }[];
`,
)
.all({ user_id: params.slug }) as {
title: string;
createdAt: string;
url: string;
}[];
db.close();
return (
<div style={{maxWidth: '900px', display: 'flex', padding: '40px', gap: '24px'}}>
<div style={{ flexDirection: 'column', width: '240px'}}>
<Image src={contributor.avatarUrl} alt={contributor.login} width={240} height={240} />
<div
style={{
maxWidth: '900px',
display: 'flex',
padding: '40px',
gap: '24px',
}}
>
<div style={{ flexDirection: 'column', width: '240px' }}>
<Image
src={contributor.avatarUrl}
alt={contributor.login}
width={240}
height={240}
/>
<h1>{contributor.login}</h1>
</div>
<div style={{flexDirection: 'column'}}>
<div style={{width: '450px', height: '200px'}}>
<ActivityLog
data={pullRequestActivity}
/>
</div>
<div style={{width: '450px'}}>
{pullRequestList.map(pr => (
<div>
<a href={pr.url}>{pr.title}</a>
</div>
))}
<div style={{ flexDirection: 'column' }}>
<div style={{ width: '450px', height: '200px' }}>
<ActivityLog data={pullRequestActivity} />
</div>
<div style={{ width: '450px' }}>
{pullRequestList.map((pr) => (
<div>
<a href={pr.url}>{pr.title}</a>
</div>
))}
</div>
</div>
</div>
);
};
}

View File

@ -1,19 +1,23 @@
import Database from 'better-sqlite3';
export async function GET(
request: Request,
{ params }: { params: { slug: string } }) {
const db = new Database('db.sqlite', { readonly: true });
request: Request,
{ params }: { params: { slug: string } },
) {
const db = new Database('db.sqlite', { readonly: true });
if(params.slug !== 'users' && params.slug !== 'labels' && params.slug !== 'pullRequests' && params.slug !== 'issues') {
return Response.json({ error: 'Invalid table name' }, { status: 400 });
}
if (
params.slug !== 'users' &&
params.slug !== 'labels' &&
params.slug !== 'pullRequests' &&
params.slug !== 'issues'
) {
return Response.json({ error: 'Invalid table name' }, { status: 400 });
}
const rows = db.prepare('SELECT * FROM ' + params.slug).all();
db.close();
const rows = db.prepare('SELECT * FROM ' + params.slug).all();
return Response.json(rows);
db.close();
return Response.json(rows);
}

View File

@ -1,5 +1,5 @@
import Database from 'better-sqlite3';
import { graphql } from '@octokit/graphql';
import Database from 'better-sqlite3';
const db = new Database('db.sqlite', { verbose: console.log });
@ -83,8 +83,13 @@ const query = graphql.defaults({
},
});
async function fetchData(cursor: string | null = null, isIssues: boolean = false, accumulatedData: Array<PullRequestNode | IssueNode> = []): Promise<Array<PullRequestNode | IssueNode>> {
const { repository } = await query<RepoData>(`
async function fetchData(
cursor: string | null = null,
isIssues: boolean = false,
accumulatedData: Array<PullRequestNode | IssueNode> = [],
): Promise<Array<PullRequestNode | IssueNode>> {
const { repository } = await query<RepoData>(
`
query ($cursor: String) {
repository(owner: "twentyhq", name: "twenty") {
pullRequests(first: 100, after: $cursor, orderBy: {field: CREATED_AT, direction: DESC}) @skip(if: ${isIssues}) {
@ -148,10 +153,17 @@ async function fetchData(cursor: string | null = null, isIssues: boolean = false
}
}
}
`, { cursor });
`,
{ cursor },
);
const newAccumulatedData: Array<PullRequestNode | IssueNode> = [...accumulatedData, ...(isIssues ? repository.issues.nodes : repository.pullRequests.nodes)];
const pageInfo = isIssues ? repository.issues.pageInfo : repository.pullRequests.pageInfo;
const newAccumulatedData: Array<PullRequestNode | IssueNode> = [
...accumulatedData,
...(isIssues ? repository.issues.nodes : repository.pullRequests.nodes),
];
const pageInfo = isIssues
? repository.issues.pageInfo
: repository.pullRequests.pageInfo;
if (pageInfo.hasNextPage) {
return fetchData(pageInfo.endCursor, isIssues, newAccumulatedData);
@ -173,11 +185,12 @@ async function fetchAssignableUsers(): Promise<Set<string>> {
}
`);
return new Set(repository.assignableUsers.nodes.map(user => user.login));
return new Set(repository.assignableUsers.nodes.map((user) => user.login));
}
const initDb = () => {
db.prepare(`
db.prepare(
`
CREATE TABLE IF NOT EXISTS pullRequests (
id TEXT PRIMARY KEY,
title TEXT,
@ -190,9 +203,11 @@ const initDb = () => {
authorId TEXT,
FOREIGN KEY (authorId) REFERENCES users(id)
);
`).run();
`,
).run();
db.prepare(`
db.prepare(
`
CREATE TABLE IF NOT EXISTS issues (
id TEXT PRIMARY KEY,
title TEXT,
@ -204,9 +219,11 @@ const initDb = () => {
authorId TEXT,
FOREIGN KEY (authorId) REFERENCES users(id)
);
`).run();
`,
).run();
db.prepare(`
db.prepare(
`
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
login TEXT,
@ -214,81 +231,133 @@ const initDb = () => {
url TEXT,
isEmployee BOOLEAN
);
`).run();
`,
).run();
db.prepare(`
db.prepare(
`
CREATE TABLE IF NOT EXISTS labels (
id TEXT PRIMARY KEY,
name TEXT,
color TEXT,
description TEXT
);
`).run();
`,
).run();
db.prepare(`
db.prepare(
`
CREATE TABLE IF NOT EXISTS pullRequestLabels (
pullRequestId TEXT,
labelId TEXT,
FOREIGN KEY (pullRequestId) REFERENCES pullRequests(id),
FOREIGN KEY (labelId) REFERENCES labels(id)
);
`).run();
`,
).run();
db.prepare(`
db.prepare(
`
CREATE TABLE IF NOT EXISTS issueLabels (
issueId TEXT,
labelId TEXT,
FOREIGN KEY (issueId) REFERENCES issues(id),
FOREIGN KEY (labelId) REFERENCES labels(id)
);
`).run();
`,
).run();
};
export async function GET() {
initDb();
initDb();
// TODO if we ever hit API Rate Limiting
const lastPRCursor = null;
const lastIssueCursor = null;
// TODO if we ever hit API Rate Limiting
const lastPRCursor = null;
const lastIssueCursor = null;
const assignableUsers = await fetchAssignableUsers();
const prs = (await fetchData(lastPRCursor)) as Array<PullRequestNode>;
const issues = (await fetchData(lastIssueCursor, true)) as Array<IssueNode>;
const assignableUsers = await fetchAssignableUsers();
const prs = await fetchData(lastPRCursor) as Array<PullRequestNode>;
const issues = await fetchData(lastIssueCursor, true) as Array<IssueNode>;
const insertPR = db.prepare('INSERT INTO pullRequests (id, title, body, url, createdAt, updatedAt, closedAt, mergedAt, authorId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING');
const insertIssue = db.prepare('INSERT INTO issues (id, title, body, url, createdAt, updatedAt, closedAt, authorId) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING');
const insertUser = db.prepare('INSERT INTO users (id, login, avatarUrl, url, isEmployee) VALUES (?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING');
const insertLabel = db.prepare('INSERT INTO labels (id, name, color, description) VALUES (?, ?, ?, ?) ON CONFLICT(id) DO NOTHING');
const insertPullRequestLabel = db.prepare('INSERT INTO pullRequestLabels (pullRequestId, labelId) VALUES (?, ?)');
const insertIssueLabel = db.prepare('INSERT INTO issueLabels (issueId, labelId) VALUES (?, ?)');
const insertPR = db.prepare(
'INSERT INTO pullRequests (id, title, body, url, createdAt, updatedAt, closedAt, mergedAt, authorId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING',
);
const insertIssue = db.prepare(
'INSERT INTO issues (id, title, body, url, createdAt, updatedAt, closedAt, authorId) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING',
);
const insertUser = db.prepare(
'INSERT INTO users (id, login, avatarUrl, url, isEmployee) VALUES (?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING',
);
const insertLabel = db.prepare(
'INSERT INTO labels (id, name, color, description) VALUES (?, ?, ?, ?) ON CONFLICT(id) DO NOTHING',
);
const insertPullRequestLabel = db.prepare(
'INSERT INTO pullRequestLabels (pullRequestId, labelId) VALUES (?, ?)',
);
const insertIssueLabel = db.prepare(
'INSERT INTO issueLabels (issueId, labelId) VALUES (?, ?)',
);
for (const pr of prs) {
console.log(pr);
if(pr.author == null) { continue; }
insertUser.run(pr.author.resourcePath, pr.author.login, pr.author.avatarUrl, pr.author.url, assignableUsers.has(pr.author.login) ? 1 : 0);
insertPR.run(pr.id, pr.title, pr.body, pr.url, pr.createdAt, pr.updatedAt, pr.closedAt, pr.mergedAt, pr.author.resourcePath);
for (const pr of prs) {
console.log(pr);
if (pr.author == null) {
continue;
}
insertUser.run(
pr.author.resourcePath,
pr.author.login,
pr.author.avatarUrl,
pr.author.url,
assignableUsers.has(pr.author.login) ? 1 : 0,
);
insertPR.run(
pr.id,
pr.title,
pr.body,
pr.url,
pr.createdAt,
pr.updatedAt,
pr.closedAt,
pr.mergedAt,
pr.author.resourcePath,
);
for (const label of pr.labels.nodes) {
insertLabel.run(label.id, label.name, label.color, label.description);
insertPullRequestLabel.run(pr.id, label.id);
}
}
for (const label of pr.labels.nodes) {
insertLabel.run(label.id, label.name, label.color, label.description);
insertPullRequestLabel.run(pr.id, label.id);
}
}
for (const issue of issues) {
if(issue.author == null) { continue; }
insertUser.run(issue.author.resourcePath, issue.author.login, issue.author.avatarUrl, issue.author.url, assignableUsers.has(issue.author.login) ? 1 : 0);
for (const issue of issues) {
if (issue.author == null) {
continue;
}
insertUser.run(
issue.author.resourcePath,
issue.author.login,
issue.author.avatarUrl,
issue.author.url,
assignableUsers.has(issue.author.login) ? 1 : 0,
);
insertIssue.run(issue.id, issue.title, issue.body, issue.url, issue.createdAt, issue.updatedAt, issue.closedAt, issue.author.resourcePath);
insertIssue.run(
issue.id,
issue.title,
issue.body,
issue.url,
issue.createdAt,
issue.updatedAt,
issue.closedAt,
issue.author.resourcePath,
);
for (const label of issue.labels.nodes) {
insertLabel.run(label.id, label.name, label.color, label.description);
insertIssueLabel.run(issue.id, label.id);
}
}
for (const label of issue.labels.nodes) {
insertLabel.run(label.id, label.name, label.color, label.description);
insertIssueLabel.run(issue.id, label.id);
}
}
db.close();
db.close();
return new Response("Data synced", { status: 200 });
};
return new Response('Data synced', { status: 200 });
}

View File

@ -1,5 +1,5 @@
import Image from 'next/image';
import Database from 'better-sqlite3';
import AvatarGrid from '@/app/components/AvatarGrid';
interface Contributor {
@ -9,11 +9,11 @@ interface Contributor {
}
const Contributors = async () => {
const db = new Database('db.sqlite', { readonly: true });
const contributors = db.prepare(`SELECT
const contributors = db
.prepare(
`SELECT
u.login,
u.avatarUrl,
COUNT(pr.id) AS pullRequestCount
@ -25,9 +25,10 @@ const Contributors = async () => {
u.id
ORDER BY
pullRequestCount DESC;
`).all() as Contributor[];
`,
)
.all() as Contributor[];
db.close();
return (

View File

@ -3,6 +3,7 @@
import { createGraphiQLFetcher } from '@graphiql/toolkit';
import { GraphiQL } from 'graphiql';
import dynamic from 'next/dynamic';
import 'graphiql/graphiql.css';
// Create a named function for your component

View File

@ -1,9 +1,9 @@
'use client';
import { CacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';
import { useServerInsertedHTML } from 'next/navigation';
import { useState } from 'react';
import createCache from '@emotion/cache';
import { CacheProvider } from '@emotion/react';
import { useServerInsertedHTML } from 'next/navigation';
export default function RootStyleRegistry({ children }) {
const [{ cache, flush }] = useState(() => {

View File

@ -1,10 +1,10 @@
import fs from 'fs';
import path from 'path';
import { compileMDX } from 'next-mdx-remote/rsc';
import { ReactElement } from 'react';
import gfm from 'remark-gfm';
import rehypeToc from '@jsdevtools/rehype-toc';
import fs from 'fs';
import { compileMDX } from 'next-mdx-remote/rsc';
import path from 'path';
import rehypeSlug from 'rehype-slug';
import gfm from 'remark-gfm';
interface ItemInfo {
title: string;

View File

@ -1,9 +1,12 @@
import type { Metadata } from 'next';
import { Metadata } from 'next';
import { Gabarito } from 'next/font/google';
import EmotionRootStyleRegistry from './emotion-root-style-registry';
import { HeaderDesktop } from './components/HeaderDesktop';
import { FooterDesktop } from './components/FooterDesktop';
import { HeaderMobile } from '@/app/components/HeaderMobile';
import { FooterDesktop } from './components/FooterDesktop';
import { HeaderDesktop } from './components/HeaderDesktop';
import EmotionRootStyleRegistry from './emotion-root-style-registry';
import './layout.css';
export const metadata: Metadata = {

View File

@ -1,8 +1,9 @@
import { Metadata } from 'next';
import { compileMDX } from 'next-mdx-remote/rsc';
import gfm from 'remark-gfm';
import { ContentContainer } from '../components/ContentContainer';
import remarkBehead from 'remark-behead';
import type { Metadata } from 'next';
import gfm from 'remark-gfm';
import { ContentContainer } from '../components/ContentContainer';
interface Release {
id: number;

View File

@ -1,8 +1,9 @@
import { ContentContainer } from '@/app/components/ContentContainer';
import { getPosts, Directory, FileContent } from '@/app/get-posts';
import Link from 'next/link';
import * as TablerIcons from '@tabler/icons-react';
import { FunctionComponent } from 'react';
import * as TablerIcons from '@tabler/icons-react';
import Link from 'next/link';
import { ContentContainer } from '@/app/components/ContentContainer';
import { Directory, FileContent, getPosts } from '@/app/get-posts';
function loadIcon(iconName?: string) {
const name = iconName ? iconName : 'IconCategory';

5481
yarn.lock

File diff suppressed because it is too large Load Diff