feat(nx-dev): add Nx Cloud Page (#26865)

This commit is contained in:
Nicholas Cunningham 2024-07-19 12:53:25 -06:00 committed by GitHub
parent f1f8ab7fe7
commit 51d5d23eb8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 2248 additions and 6 deletions

View File

@ -1,6 +1,7 @@
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { blogApi } from '../../lib/blog.api'; import { blogApi } from '../../lib/blog.api';
import { BlogContainer } from '@nx/nx-dev/ui-blog'; import { BlogContainer } from '@nx/nx-dev/ui-blog';
import { DefaultLayout } from '@nx/nx-dev/ui-common';
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Nx Blog - Updates from the Nx & Nx Cloud team', title: 'Nx Blog - Updates from the Nx & Nx Cloud team',
@ -29,5 +30,9 @@ async function getBlogs() {
export default async function BlogIndex() { export default async function BlogIndex() {
const blogs = await getBlogs(); const blogs = await getBlogs();
return <BlogContainer blogPosts={blogs} />; return (
<DefaultLayout>
<BlogContainer blogPosts={blogs} />
</DefaultLayout>
);
} }

View File

@ -81,9 +81,7 @@ export default function RootLayout({
</head> </head>
<body className="h-full bg-white text-slate-700 antialiased selection:bg-blue-500 selection:text-white dark:bg-slate-900 dark:text-slate-400 dark:selection:bg-sky-500"> <body className="h-full bg-white text-slate-700 antialiased selection:bg-blue-500 selection:text-white dark:bg-slate-900 dark:text-slate-400 dark:selection:bg-sky-500">
<AnnouncementBanner /> <AnnouncementBanner />
<Header />
{children} {children}
<Footer />
<GlobalScripts gaMeasurementId={gaMeasurementId} /> <GlobalScripts gaMeasurementId={gaMeasurementId} />
</body> </body>
</html> </html>

View File

@ -0,0 +1,68 @@
import {
Hero,
TrustedBy,
FasterAndCheaper,
UnderstandWorkspace,
EnhancedWithAi,
AutomatedAgentsManagement,
AgentNumberOverTime,
Statistics,
} from '@nx/nx-dev/ui-cloud';
import { CallToAction, DefaultLayout } from '@nx/nx-dev/ui-common';
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Nx Cloud',
description:
'Nx Cloud is the end-to-end solution for smart, efficient and maintainable CI.',
openGraph: {
url: 'https://nx.dev/nx-cloud',
title: 'Nx Cloud',
description:
'Nx Cloud is the end-to-end solution for smart, efficient and maintainable CI.',
images: [
{
url: 'https://nx.dev/socials/nx-media.png',
width: 800,
height: 421,
alt: 'Nx: Smart Monorepos · Fast CI',
type: 'image/jpeg',
},
],
siteName: 'NxDev',
type: 'website',
},
};
export default function NxCloudPage(): JSX.Element {
return (
<DefaultLayout>
<Hero />
<TrustedBy />
<div className="mt-32 lg:mt-56">
<FasterAndCheaper />
</div>
<div className="mt-32 lg:mt-56">
<UnderstandWorkspace />
</div>
<div className="mt-32 lg:mt-56">
<EnhancedWithAi />
</div>
<div className="mt-32 lg:mt-56">
<AutomatedAgentsManagement />
</div>
<div className="mt-32 lg:mt-56">
<AgentNumberOverTime />
</div>
<div className="mt-32 lg:mt-56">
<Statistics />
</div>
<div className="mt-32 lg:mt-56">
<CallToAction />
</div>
</DefaultLayout>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

View File

@ -480,7 +480,6 @@ const nxCloudUrls = {
'/ci': '/ci/intro/ci-with-nx', '/ci': '/ci/intro/ci-with-nx',
'/concepts/more-concepts/illustrated-dte': '/concepts/more-concepts/illustrated-dte':
'/ci/concepts/parallelization-distribution', '/ci/concepts/parallelization-distribution',
'/nx-cloud/:path*': '/ci/:path*',
'/core-features/:path*': '/features/:path*', '/core-features/:path*': '/features/:path*',
'/ci/recipes/set-up/connect-to-cloud': '/ci/intro/connect-to-nx-cloud', '/ci/recipes/set-up/connect-to-cloud': '/ci/intro/connect-to-nx-cloud',
'/ci/intro/connect-to-cloud': '/ci/intro/connect-to-nx-cloud', '/ci/intro/connect-to-cloud': '/ci/intro/connect-to-nx-cloud',

12
nx-dev/ui-cloud/.babelrc Normal file
View File

@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}

View File

@ -0,0 +1,18 @@
{
"extends": ["plugin:@nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@ -0,0 +1,7 @@
# ui-cloud
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test ui-cloud` to execute the unit tests via [Jest](https://jestjs.io).

View File

@ -0,0 +1,9 @@
{
"name": "ui-cloud",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "nx-dev/ui-cloud/src",
"projectType": "library",
"tags": [],
"// targets": "to see all targets run: nx show project ui-cloud --web",
"targets": {}
}

View File

@ -0,0 +1,9 @@
export * from './lib/layout';
export * from './lib/hero';
export * from './lib/trusted-by';
export * from './lib/faster-and-cheaper';
export * from './lib/understand-workspace';
export * from './lib/enhance-with-ai';
export * from './lib/automated-agents-management';
export * from './lib/agent-number-over-time';
export * from './lib/statistics';

View File

@ -0,0 +1,189 @@
'use client';
import { SectionHeading } from '@nx/nx-dev/ui-common';
import { motion } from 'framer-motion';
import { useEffect, useState } from 'react';
interface Agent {
date: string;
available: number;
used: number;
}
function generateAgents(): Agent[] {
const items: { date: string; available: number; used: number }[] = [];
for (let i = 1; i <= 30; i++) {
const used = Math.floor(Math.random() * 81); // Random percentage from 0 to 80
const available =
used + Math.floor(Math.random() * (100 - used - 20 + 1) + 20); // Random percentage that is at least 20 more than "used"
items.push({ date: `Aug ${String(i).padStart(2, '0')}`, available, used });
}
for (let i = 1; i <= 30; i++) {
const available = Math.floor(Math.random() * (41 - 5)) + 5; // Random percentage from 5 to 41
const lowerBound = available * 0.98; // Lower bound of the 2% error range
const used =
Math.floor(Math.random() * (available - lowerBound)) + lowerBound;
items.push({ date: `Aug ${String(i).padStart(2, '0')}`, available, used });
}
return items;
}
export function AgentNumberOverTime(): JSX.Element {
const [agents, setAgents] = useState<Agent[]>([]);
useEffect(() => {
let ignore = false;
if (!ignore) setAgents(generateAgents());
return () => {
ignore = true;
};
}, []);
// Set total number of items and gaps
const totalGaps = agents.length - 1;
// Set gap width in percent
const gapWidthPercent = 0.3;
// Calculate total width of gaps
const totalGapWidthPercent = totalGaps * gapWidthPercent;
// Calculate the remaining length for items
const remainingPercent = 100 - totalGapWidthPercent;
// Calculate the width of each item
const itemWidthPercent = remainingPercent / agents.length;
const variants = {
hidden: {
opacity: 0,
transition: {
when: 'afterChildren',
},
},
visible: (i: number) => ({
opacity: 1,
transition: {
delay: i || 0,
},
}),
};
const itemVariants = {
visible: (i: number) => ({
opacity: 1,
y: 0,
transition: {
delay: i * 0.035,
duration: 0.65,
ease: 'easeOut',
when: 'beforeChildren',
staggerChildren: 0.3,
},
}),
hidden: {
opacity: 0,
y: 4,
transition: {
when: 'afterChildren',
},
},
};
return (
<section id="agent-number-over-time" className="overflow-hidden">
<div className="mx-auto max-w-7xl md:px-6 lg:px-8">
<div className="mx-auto max-w-2xl text-center">
<SectionHeading
as="h2"
variant="title"
id="optimize-agent-utilization"
>
Optimize agent utilization <br /> Save money
</SectionHeading>
<SectionHeading as="p" variant="subtitle" className="mt-6">
Nx Cloud uses historical data and models to optimize the utilization
of your agents.
</SectionHeading>
</div>
<motion.div
initial="hidden"
variants={variants}
whileInView="visible"
viewport={{ once: true }}
className="relative mt-16 w-full rounded-md border border-slate-100 bg-white p-4 sm:mt-20 dark:border-slate-800 dark:bg-slate-950"
>
<div className="mt-2 text-base font-medium">
Number of idle agents VS used agents over time
</div>
<div className="flex justify-end gap-3 text-xs">
<div className="flex items-center gap-1">
<span className="m-1 h-2.5 w-2.5 flex-none rounded-full bg-blue-500" />
Available agents count
</div>
<div className="flex items-center gap-1">
<span className="m-1 h-2.5 w-2.5 flex-none rounded-full bg-green-500" />
Used agents count
</div>
</div>
<div className="mt-2 text-xs">Agents count</div>
<div
className="mt-1 flex h-56 flex-row items-end space-y-1 border-b border-l border-slate-200 p-1 dark:border-slate-700"
style={{ gap: gapWidthPercent + '%' }}
>
{agents.map((i, idx) => (
<motion.div
custom={idx}
variants={itemVariants}
key={`without-agents-${i.available}-${idx}`}
data-tooltip={`${Math.round(i.available - i.used)} agents idle`}
className="relative flex h-full flex-col-reverse"
style={{ width: itemWidthPercent + '%' }}
>
<motion.div
custom={idx}
variants={itemVariants}
className="absolute inset-x-0 bottom-0 w-full rounded-sm bg-blue-500"
style={{
height: i.available + '%',
}}
/>
<motion.div
custom={idx + 1}
variants={itemVariants}
className="absolute inset-x-0 bottom-0 w-full bg-green-500"
style={{ height: i.used + '%' }}
/>
</motion.div>
))}
</div>
<div className="mt-1 flex flex-row justify-between text-xs">
<span>Time</span>
<span>Day 1</span>
<span>Day 2</span>
<span>Day 3</span>
<span>Day 4</span>
<span>Day 5</span>
<span>Day 6</span>
<span>Day 7</span>
<span>Day 8</span>
<span>Day 9</span>
<span>Day 10</span>
</div>
<motion.div
custom={4}
variants={variants}
className="absolute left-1/2 top-1/2 flex h-12 w-auto flex-row items-center gap-2"
>
<svg
aria-hidden="true"
role="img"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
className="mt-4 h-8 w-8 -rotate-[18deg] transform text-slate-600 dark:text-slate-400"
>
<path d="M.348 12.633a1.145 1.145 0 0 1 1.681 0l2.785 2.938c.466-5 3.975-9.317 8.877-10.346 3.608-.757 7.33.419 9.954 3.146.468.486.474 1.28.013 1.774-.46.494-1.213.5-1.68.014-2.064-2.143-4.988-3.068-7.823-2.473-3.873.813-6.64 4.239-6.98 8.194l3.078-3.247a1.145 1.145 0 0 1 1.681 0c.464.49.464 1.284 0 1.774l-4.952 5.226a1.154 1.154 0 0 1-.84.367c-.305 0-.61-.122-.841-.367L.348 14.407a1.304 1.304 0 0 1 0-1.774Z" />
</svg>
<span className="inline-flex cursor-default items-center rounded-md bg-slate-50 px-2 py-1 text-xs font-medium text-slate-600 ring-1 ring-inset ring-slate-500/10 dark:bg-slate-400/10 dark:text-slate-400 dark:ring-slate-400/20">
Nx Cloud enabled
</span>
</motion.div>
</motion.div>
</div>
</section>
);
}

View File

@ -0,0 +1,243 @@
'use client';
import {
AdjustmentsVerticalIcon,
ArrowLongDownIcon,
CircleStackIcon,
CodeBracketSquareIcon,
} from '@heroicons/react/24/outline';
import { SectionHeading } from '@nx/nx-dev/ui-common';
import { motion } from 'framer-motion';
import { NxCloudIcon } from '@nx/nx-dev/ui-icons';
export function AutomatedAgentsManagement(): JSX.Element {
const variants = {
hidden: {
opacity: 0,
transition: {
when: 'afterChildren',
},
},
visible: (i: number) => ({
opacity: 1,
transition: {
delay: i || 0,
},
}),
};
const itemVariants = {
visible: (i: number) => ({
opacity: 1,
y: 0,
transition: {
delay: i * 0.035,
duration: 0.65,
ease: 'easeOut',
when: 'beforeChildren',
staggerChildren: 0.3,
},
}),
hidden: {
opacity: 0,
y: 4,
transition: {
when: 'afterChildren',
},
},
};
return (
<section id="competitive-compute" className="overflow-hidden">
<div className="mx-auto max-w-7xl md:px-6 lg:px-8">
<div className="grid grid-cols-1 gap-x-8 gap-y-16 sm:gap-y-20 lg:grid-cols-2 lg:items-start">
<div className="px-6 md:px-0 lg:pr-4 lg:pt-4">
<div className="mx-auto max-w-2xl lg:mx-0 lg:max-w-lg">
<SectionHeading
as="h2"
variant="title"
id="seamless-distribution"
>
Seamless distribution, faster CI
</SectionHeading>
<SectionHeading as="p" variant="subtitle" className="mt-6">
Nx Cloud dynamically adapts to your CI needs - providing an
excellent developer experience while minimizing your costs.
</SectionHeading>
<dl className="mt-12 max-w-xl space-y-4 text-base leading-7">
<div className="relative pl-9">
<dt className="inline font-semibold text-slate-950 dark:text-white">
<AdjustmentsVerticalIcon
aria-hidden="true"
className="absolute left-1 top-1 h-5 w-5"
/>
Perfect provisioning.{' '}
</dt>
<dd className="inline">
Instruct Nx Cloud to dynamically allocate the right number
of agents for each pull request.
</dd>
</div>
<div className="relative pl-9">
<dt className="inline font-semibold text-slate-950 dark:text-white">
<CircleStackIcon
aria-hidden="true"
className="absolute left-1 top-1 h-5 w-5"
/>
Effortless CI integration.{' '}
</dt>
<dd className="inline">
Utilize additional compute from Nx Cloud and enjoy faster
and cheaper CI with your existing provider.
</dd>
</div>
<div className="relative pl-9">
<dt className="inline font-semibold text-slate-950 dark:text-white">
<CodeBracketSquareIcon
aria-hidden="true"
className="absolute left-1 top-1 h-5 w-5"
/>
Simple configuration.{' '}
</dt>
<dd className="inline">
Add a single line to your CI configuration to enable
distribution, computation caching, E2E test splitting, and
more.
</dd>
</div>
</dl>
</div>
</div>
<div className="mt-4 flex flex-col justify-items-stretch gap-4 sm:px-6 lg:px-0">
<motion.div
initial="hidden"
variants={variants}
whileInView="visible"
viewport={{ once: true }}
className="relative rounded-md border border-slate-100 p-4 dark:border-slate-800"
>
<div className="rounded-lg border border-dashed border-slate-300 p-2 dark:border-slate-500">
<p className="text-xs text-slate-600 dark:text-slate-400">
Your CI provider
</p>
<div className="mt-4 grid grid-cols-2 items-center gap-1">
<motion.p variants={itemVariants}>
<span className="inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20">
nx affected -t lint build test
</span>
</motion.p>
<p className="mt-1 text-xs">
on job:{' '}
<span className="inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:bg-slate-900 dark:text-slate-400 dark:ring-slate-700">
main-linux
</span>
</p>
</div>
</div>
</motion.div>
<motion.div
custom={0.5}
initial="hidden"
variants={variants}
whileInView="visible"
viewport={{ once: true }}
className="text-center"
>
<ArrowLongDownIcon
aria-hidden="true"
className="inline-flex h-6 w-6"
/>
</motion.div>
<motion.div
custom={1}
initial="hidden"
variants={variants}
whileInView="visible"
viewport={{ once: true }}
className="coding z-10 mx-4 rounded-lg border border-slate-200 p-4 font-mono text-xs leading-normal text-slate-800 subpixel-antialiased dark:border-slate-700 dark:bg-slate-800 dark:text-slate-200"
>
<div className="flex items-start">
<p>
<span className="mx-0.5 text-green-600 dark:text-green-400">
~
</span>{' '}
<span>$</span>
</p>
<p className="typing flex-1 pl-1">
npx nx-cloud start-ci-run --distribute-on="15
linux-medium-plus-js"
</p>
</div>
</motion.div>
<motion.div
custom={1.5}
initial="hidden"
variants={variants}
whileInView="visible"
viewport={{ once: true }}
className="text-center"
>
<ArrowLongDownIcon
aria-hidden="true"
className="inline-flex h-6 w-6"
/>
</motion.div>
<motion.div
custom={2}
initial="hidden"
variants={variants}
whileInView="visible"
viewport={{ once: true }}
className="relative grid grid-cols-2 gap-2 rounded-md border border-slate-100 p-4 dark:border-slate-800"
>
<div className="rounded-lg border border-dashed border-slate-300 p-2 dark:border-slate-500">
<p className="text-xs text-slate-600 dark:text-slate-400">
Your CI provider
</p>
<div className="mt-4">
<p>
<span className="inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20">
nx affected -t lint build test
</span>
</p>
<p className="mt-1 text-xs">
on job:{' '}
<span className="inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:bg-slate-900 dark:text-slate-400 dark:ring-slate-700">
main-linux
</span>
</p>
</div>
</div>
<div className="rounded-lg border border-dashed border-slate-500 p-2 dark:border-slate-500">
<p className="flex items-center gap-1 text-xs text-slate-600 dark:text-slate-400">
<NxCloudIcon className="h-4 w-4" aria-hidden="true" />
Nx Cloud
</p>
<div className="mt-4 flex flex-col items-start gap-1 text-xs">
<div className="flex items-center gap-2">
<span className="inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20">
nx run build
</span>
on Agent 1, Agent 2, Agent 3
</div>
<div className="flex items-center gap-2">
<span className="inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20">
nx run test
</span>
on Agent 1, Agent 4
</div>
<div className="flex items-center gap-2">
<span className="inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20">
nx run lint
</span>
on Agent 1, Agent 2, Agent 4
</div>
</div>
</div>
</motion.div>
</div>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,87 @@
import Link from 'next/link';
export function CallToAction(): JSX.Element {
return (
<section className="relative isolate px-6 py-32 sm:py-40 lg:px-8">
<svg
className="absolute inset-0 -z-10 h-full w-full stroke-black/10 [mask-image:radial-gradient(100%_100%_at_top_right,white,transparent)] dark:stroke-white/10"
aria-hidden="true"
>
<defs>
<pattern
id="1d4240dd-898f-445f-932d-e2872fd12de3"
width={200}
height={200}
x="50%"
y={0}
patternUnits="userSpaceOnUse"
>
<path d="M.5 200V.5H200" fill="none" />
</pattern>
</defs>
<svg
x="50%"
y={0}
className="overflow-visible fill-slate-200/20 dark:fill-slate-800/20"
>
<path
d="M-200 0h201v201h-201Z M600 0h201v201h-201Z M-400 600h201v201h-201Z M200 800h201v201h-201Z"
strokeWidth={0}
/>
</svg>
<rect
width="100%"
height="100%"
strokeWidth={0}
fill="url(#1d4240dd-898f-445f-932d-e2872fd12de3)"
/>
</svg>
<div
className="absolute inset-x-0 top-10 -z-10 flex transform-gpu justify-center overflow-hidden blur-3xl"
aria-hidden="true"
>
<div
className="aspect-[1108/632] w-[69.25rem] flex-none bg-gradient-to-r from-[#80caff] to-[#4f46e5] opacity-20"
style={{
clipPath:
'polygon(73.6% 51.7%, 91.7% 11.8%, 100% 46.4%, 97.4% 82.2%, 92.5% 84.9%, 75.7% 64%, 55.3% 47.5%, 46.5% 49.4%, 45% 62.9%, 50.3% 87.2%, 21.3% 64.1%, 0.1% 100%, 5.4% 51.1%, 21.4% 63.9%, 58.9% 0.2%, 73.6% 51.7%)',
}}
/>
</div>
<div className="mx-auto max-w-2xl text-center">
<h2
id="cta"
className="text-3xl font-medium tracking-tight text-slate-950 sm:text-5xl dark:text-white"
>
Boost your productivity
<br />
Start using Nx Cloud today
</h2>
<p className="mx-auto mt-6 max-w-xl text-2xl leading-8 text-slate-700 dark:text-slate-300">
Experience the next generation of CI tooling.
</p>
<div className="mt-10 flex items-center justify-center gap-x-6">
<a
href="https://cloud.nx.app"
className="rounded-md bg-slate-950 px-3.5 py-2.5 text-sm font-semibold text-slate-100 shadow-sm hover:bg-slate-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white dark:bg-white dark:text-slate-900 dark:hover:bg-slate-100"
>
Get started
</a>
<Link
href="/contact"
title="Get in touch"
className="group text-sm font-semibold leading-6 text-slate-950 dark:text-white"
>
Contact us{' '}
<span
aria-hidden="true"
className="inline-block transition group-hover:translate-x-1"
>
</span>
</Link>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,73 @@
import { cx } from '@nx/nx-dev/ui-primitives';
import Link from 'next/link';
import { ReactNode } from 'react';
export const BentoGrid = ({
className,
children,
}: {
className?: string;
children?: ReactNode;
}) => {
return (
<div
className={cx(
'mx-auto grid max-w-7xl grid-cols-1 gap-4 md:auto-rows-[24rem] md:grid-cols-2 lg:grid-cols-3',
className
)}
>
{children}
</div>
);
};
export const BentoGridItem = ({
className,
title,
description,
header,
url = '',
icon,
}: {
className?: string;
title?: string | ReactNode;
description?: string | ReactNode;
header?: ReactNode;
icon?: ReactNode;
url?: string;
}) => {
return (
<div
className={cx(
'group/bento shadow-input row-span-1 flex flex-col justify-between space-y-4 overflow-hidden rounded-xl border border-slate-200 bg-white p-4 transition duration-200 dark:border-slate-800 dark:bg-slate-950 dark:shadow-none',
className
)}
>
<div className="pointer-events-none relative">
<div className="mb-2 mt-2 flex flex-row items-center gap-2 font-sans font-bold text-slate-600 dark:text-slate-200">
{icon} {title}
</div>
<div className="font-sans text-sm font-normal text-slate-600 dark:text-slate-400">
{description}
</div>
{url ? (
<Link
href={url}
title="Learn more"
className="float-right text-sm font-medium transition duration-200 group-hover/bento:text-blue-500 group-hover/bento:dark:text-sky-500"
>
<span className="group absolute inset-0" />
<span
className="inline-block transition duration-200 group-hover/bento:translate-x-2"
aria-hidden="true"
>
</span>
</Link>
) : null}
</div>
{header}
</div>
);
};

View File

@ -0,0 +1,56 @@
import { cx } from '@nx/nx-dev/ui-primitives';
export const Spotlight = ({
className,
fill,
}: {
className?: string;
fill?: string;
}) => {
return (
<svg
className={cx(
'animate-spotlight pointer-events-none absolute z-[1] h-[169%] w-[138%] opacity-0 lg:w-[84%]',
className
)}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 3787 2842"
fill="none"
>
<g filter="url(#filter)">
<ellipse
cx="1924.71"
cy="273.501"
rx="1924.71"
ry="273.501"
transform="matrix(-0.822377 -0.568943 -0.568943 0.822377 3631.88 2291.09)"
fill={fill || 'white'}
fillOpacity="0.21"
/>
</g>
<defs>
<filter
id="filter"
x="0.860352"
y="0.838989"
width="3785.16"
height="2840.26"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix"></feFlood>
<feBlend
mode="normal"
in="SourceGraphic"
in2="BackgroundImageFix"
result="shape"
/>
<feGaussianBlur
stdDeviation="151"
result="effect1_foregroundBlur_1065_8"
/>
</filter>
</defs>
</svg>
);
};

View File

@ -0,0 +1,71 @@
import {
CloudArrowDownIcon,
CodeBracketIcon,
RectangleGroupIcon,
ServerStackIcon,
} from '@heroicons/react/24/outline';
import { SectionHeading } from '@nx/nx-dev/ui-common';
const features = [
{
name: 'Task error insights',
description:
"Debug task errors on your CI pipeline directly in your pipeline's UI.",
icon: CodeBracketIcon,
},
{
name: 'Dynamic Nx Agent sizing',
description:
"Automatically adjust Nx Agents' resource classes and numbers depending on your workspace usage and needs.",
icon: ServerStackIcon,
},
{
name: 'Task cache miss diagnosis',
description: 'Understand why a task has a cache miss and how to fix it.',
icon: CloudArrowDownIcon,
},
{
name: 'Organization insights',
description:
"Understand your teams' workspaces: shared code usage, ownership, bottlenecks.",
icon: RectangleGroupIcon,
},
];
export function EnhancedWithAi(): JSX.Element {
return (
<section id="ai-for-your-ci">
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="mx-auto max-w-3xl text-center">
<span className="inline-flex items-center rounded-md bg-blue-50 px-2 py-1 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-700/10 dark:bg-blue-400/10 dark:text-blue-400 dark:ring-blue-400/30">
Coming soon
</span>
<SectionHeading as="h2" variant="title" id="deep-understanding">
AI for your CI
</SectionHeading>
<SectionHeading as="p" variant="subtitle" className="mt-6">
With the knowledge of your workspace structure, your CI and commit
history, Nx Cloud can optimize CI resource usage, help resolve
issues, provide powerful analytics and suggest refactorings.
</SectionHeading>
</div>
<dl className="mx-auto mt-16 grid max-w-5xl grid-cols-1 gap-6 sm:mt-20 md:grid-cols-2 lg:mt-24 lg:gap-12">
{features.map((feature) => (
<div key={feature.name} className="relative pl-9">
<dt className="text-base font-semibold leading-7 text-slate-950 dark:text-white">
<feature.icon
className="absolute left-1 top-1 h-5 w-5"
aria-hidden="true"
/>
{feature.name}
</dt>
<dd className="mt-2 text-base leading-7">
{feature.description}
</dd>
</div>
))}
</dl>
</div>
</section>
);
}

View File

@ -0,0 +1,97 @@
'use client';
import { Variants, motion } from 'framer-motion';
import { Spotlight } from './elements/spotlight';
import { AnimateValue } from '@nx/nx-dev/ui-animations';
export function FasterAndCheaper(): JSX.Element {
const spotlight: Variants = {
offscreen: {
display: 'none',
},
onscreen: {
display: 'block',
},
};
return (
<section>
<motion.div
initial="offscreen"
whileInView="onscreen"
viewport={{ once: true }}
className="relative mx-auto max-w-7xl px-6 lg:px-8"
>
<motion.div
variants={spotlight}
className="absolute left-1/2 z-0 h-[50%] w-[50%] transform-gpu"
>
<Spotlight className="w-full max-w-full" fill="pink" />
</motion.div>
<div className="mx-auto max-w-2xl text-center">
<h2 className="text-3xl font-medium tracking-tight text-slate-950 sm:text-5xl dark:text-white">
Both faster & cheaper
</h2>
<p className="mt-6 text-2xl leading-7 text-slate-700 dark:text-slate-300">
Nx Cloud makes your CI significantly faster and cheaper, while also
making it more maintainable and reliable.
</p>
</div>
<div className="relative z-20 mx-auto mt-16 max-w-2xl sm:mt-20 lg:mt-24 lg:max-w-5xl">
<div className="grid grid-cols-1 gap-x-8 gap-y-16 lg:grid-cols-2">
<article className="group/card relative grid transform-gpu cursor-default items-center gap-4 overflow-hidden rounded-xl border border-slate-200 bg-white/50 backdrop-blur-sm transition duration-200 hover:-translate-y-4 hover:shadow-xl md:col-span-1 dark:border-slate-800 dark:bg-slate-950/50 dark:shadow-none">
<div className="p-4 text-center">
<div className="mt-2 text-3xl font-medium leading-7 text-slate-950 transition duration-200 lg:text-8xl dark:text-white">
<AnimateValue num={30} suffix=" - " />
<AnimateValue num={70} suffix="%" />
</div>
<div className="mt-4 text-3xl font-medium leading-7 text-slate-950 transition duration-200 lg:text-5xl dark:text-white">
Faster CI
</div>
<div className="mt-4 text-sm text-slate-600 transition duration-200 group-hover/card:text-slate-400 dark:text-slate-400 group-hover/card:dark:text-slate-600">
Reported by enterprises using Nx Cloud.
</div>
</div>
</article>
<article className="group/card relative grid transform-gpu cursor-default items-center gap-4 overflow-hidden rounded-xl border border-slate-200 bg-white/50 backdrop-blur-sm transition duration-200 hover:-translate-y-4 hover:shadow-xl md:col-span-1 dark:border-slate-800 dark:bg-slate-950/50 dark:shadow-none">
<div className="p-4 text-center">
<div className="mt-4 text-center text-3xl font-medium leading-7 text-slate-950 transition duration-200 lg:text-5xl dark:text-white">
Halve your CI bill
</div>
<div className="mt-6">
<div className="flex items-center">
<div className="w-28 shrink-0 border-r-2 border-slate-200 py-3 pr-2 text-right text-slate-700 transition duration-200 dark:border-slate-800 dark:text-slate-300">
CI
</div>
<div className="flex-grow py-1.5 font-semibold">
<div className="w-full flex-grow items-center justify-end rounded-r-lg border border-l-0 border-slate-200 bg-slate-100 px-4 py-2 text-right text-slate-900 transition duration-200 dark:border-slate-800 dark:bg-slate-700 dark:text-white">
<span className="drop-shadow-sm">$6k</span>
</div>
</div>
</div>
<div className="flex items-center">
<div className="w-28 shrink-0 border-r-2 border-slate-200 py-3 pr-2 text-right font-medium text-slate-700 transition duration-200 dark:border-slate-800 dark:text-slate-300">
CI + Nx Cloud
</div>
<div className="flex-grow py-1.5 font-semibold">
<div className="w-1/2 rounded-r-lg border border-l-0 border-slate-200 bg-gradient-to-r from-emerald-500 to-green-500 px-4 py-2 text-right text-white transition duration-200 dark:border-slate-800">
<span className="drop-shadow-sm">$3.2k</span>
</div>
</div>
</div>
</div>
<p className="mt-6 text-xs text-slate-400 transition duration-200 dark:text-slate-600">
<span className="underline">
Cost per month for CI compute.
</span>{' '}
Data collected based on a typical month of CI runs measured on
the Nx OSS monorepo.
</p>
</div>
</article>
</div>
</div>
</motion.div>
</section>
);
}

View File

@ -0,0 +1,202 @@
'use client';
import { Dialog, Transition } from '@headlessui/react';
import { cx } from '@nx/nx-dev/ui-primitives';
import { PlayIcon } from '@heroicons/react/24/outline';
import { motion } from 'framer-motion';
import { ComponentProps, Fragment, useState } from 'react';
import { ButtonLink, SectionHeading } from '@nx/nx-dev/ui-common';
import { MovingBorder } from '@nx/nx-dev/ui-animations';
import Image from 'next/image';
export function Hero(): JSX.Element {
const [isOpen, setIsOpen] = useState(false);
return (
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="mx-auto max-w-3xl text-center">
<SectionHeading as="h1" variant="display">
Fast CI <br /> Built for Monorepos
</SectionHeading>
<SectionHeading
as="p"
variant="subtitle"
className="mx-auto mt-6 max-w-xl"
>
Nx Cloud is the end-to-end solution for smart, efficient and
maintainable CI.
</SectionHeading>
<div className="mt-10 flex items-center justify-center gap-x-6">
<ButtonLink
href="https://cloud.nx.app"
title="Get started"
variant="primary"
size="default"
>
Get started
</ButtonLink>
<ButtonLink
href="/ci/intro/ci-with-nx"
title="Learn More"
variant="secondary"
size="default"
>
Learn More
</ButtonLink>
<a
href="https://staging.nx.app/orgs/62d013d4d26f260059f7765e/workspaces/62d013ea0852fe0a2df74438/overview"
target="_blank"
rel="noreferrer"
title="Live demo"
className="group text-sm font-semibold leading-6 text-slate-950 dark:text-white"
>
Live demo{' '}
<span
aria-hidden="true"
className="inline-block transition group-hover:translate-x-1"
>
</span>
</a>
</div>
</div>
<div className="relative overflow-hidden pt-16">
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="relative mb-6 mt-0 overflow-hidden rounded-xl bg-transparent p-[0.5px] text-xl shadow-lg">
<div className="absolute inset-0">
<MovingBorder duration={4500} rx="5%" ry="5%">
<div className="h-20 w-20 bg-[radial-gradient(var(--blue-500)_40%,transparent_60%)] opacity-[0.8] dark:bg-[radial-gradient(var(--pink-500)_40%,transparent_60%)]" />
</MovingBorder>
</div>
<picture className="relative flex h-full w-full items-center justify-center overflow-hidden rounded-xl border border-slate-100 bg-slate-50 antialiased backdrop-blur-xl dark:border-slate-900 dark:bg-slate-900/[0.8]">
<Image
src="/images/cloud/nrwl-ocean.avif"
alt="App screenshot: overview"
width={2550}
height={1622}
loading="eager"
priority
/>
</picture>
<div className="absolute inset-0 z-10 grid h-full w-full items-center justify-center">
<PlayButton onClick={() => setIsOpen(true)} />
</div>
</div>
</div>
</div>
{/*MODAL*/}
<Transition appear show={isOpen} as={Fragment}>
<Dialog
as="div"
open={isOpen}
onClose={() => setIsOpen(false)}
className="relative z-10"
>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black/25 backdrop-blur-sm" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative w-auto transform overflow-hidden rounded-2xl border border-slate-600 text-left align-middle shadow-xl transition-all focus:outline-none dark:border-slate-800">
<iframe
width="812"
height="468"
src="https://www.youtube.com/embed/4VI-q943J3o?si=3tR-EkCKLfLvHYzL"
title="YouTube video player"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen
className="max-w-full"
/>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
}
function PlayButton({
className,
...props
}: ComponentProps<'div'>): JSX.Element {
const parent = {
initial: {
width: 82,
transition: {
when: 'afterChildren',
},
},
hover: {
width: 296,
transition: {
duration: 0.125,
type: 'tween',
ease: 'easeOut',
},
},
};
const child = {
initial: {
opacity: 0,
x: -6,
},
hover: {
x: 0,
opacity: 1,
transition: {
duration: 0.015,
type: 'tween',
ease: 'easeOut',
},
},
};
return (
<div
className={cx(
'group relative overflow-hidden rounded-full bg-transparent p-[1px] shadow-md',
className
)}
{...props}
>
<div className="absolute inset-0">
<MovingBorder duration={5000} rx="5%" ry="5%">
<div className="h-20 w-20 bg-[radial-gradient(var(--blue-500)_40%,transparent_60%)] opacity-[0.8] dark:bg-[radial-gradient(var(--pink-500)_40%,transparent_60%)]" />
</MovingBorder>
</div>
<motion.div
initial="initial"
whileHover="hover"
variants={parent}
className="relative isolate flex h-20 w-20 cursor-pointer items-center justify-center gap-6 rounded-full border border-slate-100 bg-white/[0.6] p-6 text-sm text-slate-950 antialiased backdrop-blur-xl"
>
<PlayIcon
aria-hidden="true"
className="absolute left-6 top-6 h-8 w-8"
/>
<motion.div variants={child} className="absolute left-20 top-4 w-48">
<p className="text-base font-medium">See how Nx Cloud works</p>
<p className="text-slate-700">In under 9 minutes</p>
</motion.div>
</motion.div>
</div>
);
}

View File

@ -0,0 +1,25 @@
export function Layout({
children,
}: {
children: React.ReactNode;
}): JSX.Element {
return (
<div>
<div className="relative isolate pt-14">
<div
className="absolute inset-x-0 -top-40 -z-10 h-full transform-gpu overflow-hidden blur-3xl sm:-top-80"
aria-hidden="true"
>
<div
className="relative left-[calc(50%-11rem)] aspect-[1155/678] w-[36.125rem] -translate-x-1/2 rotate-[30deg] bg-gradient-to-tr from-[#ff80b5] to-[#9089fc] opacity-20 sm:left-[calc(50%-30rem)] sm:w-[72.1875rem]"
style={{
clipPath:
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
}}
/>
</div>
<main className="py-24 sm:py-32">{children}</main>
</div>
</div>
);
}

View File

@ -0,0 +1,159 @@
'use client';
import { motion } from 'framer-motion';
import { useEffect, useState } from 'react';
import { SectionHeading } from '@nx/nx-dev/ui-common';
/**
* Calculate the total number of years worth of compute.
*
* @param {number} millis - The total number of seconds.
* @return {number} The total number of years.
*/
function getTotalYears(millis: number): number {
/**
* The number of millis in a year is approximately:
* 86 400 000 millis/day * 365.25 days/year 31 557 600 000 seconds/year.
*/
const yearMillis = Number(31557600000);
return Math.round(millis / yearMillis);
}
/**
* Fetches the time saved from a remote API.
*
* @returns {Promise} A promise that resolves to an object containing the time saved data.
* @returns {Date} The date the time saved data was retrieved.
* @returns {number} The time saved in the last 7 days.
* @returns {number} The time saved in the last 30 days.
* @returns {number} The time's saved since the start.
*/
function fetchTimeSaved(): Promise<{
date: Date;
last7days: number;
last30days: number;
sinceStart: number;
}> {
const apiUrl = 'https://cloud.nx.app/time-saved';
return fetch(apiUrl)
.then(
(response) =>
response.json() as Promise<{
date: Date;
last7days: number;
last30days: number;
sinceStart: number;
}>
)
.catch(() => ({
date: new Date(),
last7days: Math.round(Math.random() * 1000000000),
last30days: Math.round(Math.random() * 100000000000),
sinceStart: Math.round(Math.random() * 10000000000000),
}));
}
const stats = [
{
id: 1,
name: 'Developers using Nx',
value: 2,
suffix: 'M+',
},
{
id: 3,
name: 'Active workspaces',
value: '4k',
suffix: '+',
},
{ id: 2, name: 'Compute time saved', value: 800, suffix: '+ years' },
{ id: 4, name: 'Runs daily', value: 100, suffix: 'k+' },
];
export function Statistics(): JSX.Element {
const variants = {
hidden: {
opacity: 0,
transition: {
when: 'afterChildren',
},
},
visible: (i: number) => ({
opacity: 1,
transition: {
delay: i || 0,
},
}),
};
const itemVariants = {
visible: (i: number) => ({
opacity: 1,
y: 0,
transition: {
delay: i * 0.25,
duration: 0.65,
ease: 'easeOut',
when: 'beforeChildren',
staggerChildren: 0.3,
},
}),
hidden: {
opacity: 0,
y: 4,
transition: {
when: 'afterChildren',
},
},
};
const [timeSaved, setTimeSaved] = useState<number>(800);
useEffect(() => {
let ignore = false;
fetchTimeSaved().then((data) => {
if (!ignore) {
setTimeSaved(getTotalYears(data.sinceStart));
}
});
return () => {
ignore = true;
};
}, []);
return (
<section className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="mx-auto max-w-2xl lg:mx-0 lg:max-w-xl">
<SectionHeading as="h2" variant="title" id="statistics">
Trusted by startups and Fortune 500 companies
</SectionHeading>
<SectionHeading as="p" variant="subtitle" className="mt-6">
Nx Cloud provides plans for open source projects, startups, and large
enterprises.
</SectionHeading>
</div>
<motion.dl
initial="hidden"
variants={variants}
whileInView="visible"
viewport={{ once: true }}
className="mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-x-8 gap-y-10 text-slate-950 sm:mt-20 sm:grid-cols-2 sm:gap-y-16 lg:mx-0 lg:max-w-none lg:grid-cols-4 dark:text-white"
>
{stats.map((stat, idx) => (
<motion.div
key={`statistic-${idx}`}
custom={idx}
variants={itemVariants}
className="flex flex-col gap-y-3 border-l border-black/10 pl-6 dark:border-white/10"
>
<dt className="text-sm leading-6 text-slate-600 dark:text-slate-500">
{stat.name}
</dt>
<dd className="order-first text-3xl font-semibold tracking-tight">
{stat.name === 'Compute time saved' ? timeSaved : stat.value}
{stat.suffix}
</dd>
</motion.div>
))}
</motion.dl>
</section>
);
}

View File

@ -0,0 +1,47 @@
import {
AwsAmplifyIcon,
CapitalOneIcon,
CiscoIcon,
RedwoodJsIcon,
RoyalBankOfCanadaIcon,
ShopifyIcon,
StorybookIcon,
VmwareIcon,
} from '@nx/nx-dev/ui-icons';
export function TrustedBy(): JSX.Element {
return (
<section className="">
<div className="mx-auto max-w-7xl px-4 pb-12 sm:px-6 lg:px-8 lg:pb-16">
<h2 className="text-center text-lg font-medium leading-8 text-slate-400">
Startups and Fortune 500 companies trust Nx Cloud
</h2>
<div className="mt-4 grid grid-cols-2 gap-0.5 md:grid-cols-8">
<div className="col-span-1 flex items-center justify-center">
<RoyalBankOfCanadaIcon className="h-14 w-14 text-slate-300 dark:text-slate-600" />
</div>
<div className="col-span-1 flex items-center justify-center">
<AwsAmplifyIcon className="h-14 w-14 text-slate-300 dark:text-slate-600" />
</div>
<div className="col-span-1 flex items-center justify-center">
<CapitalOneIcon className="h-28 w-28 text-slate-300 dark:text-slate-600" />
</div>
<div className="col-span-1 flex items-center justify-center">
<ShopifyIcon className="h-12 w-12 text-slate-300 dark:text-slate-600" />
</div>
<div className="col-span-1 flex items-center justify-center">
<StorybookIcon className="h-12 w-12 text-slate-300 dark:text-slate-600" />
</div>
<div className="col-span-1 flex items-center justify-center ">
<VmwareIcon className="h-28 w-28 text-slate-300 dark:text-slate-600" />
</div>
<div className="col-span-1 flex items-center justify-center">
<RedwoodJsIcon className="h-12 w-12 text-slate-300 dark:text-slate-600" />
</div>
<div className="col-span-1 flex items-center justify-center">
<CiscoIcon className="h-20 w-20 text-slate-300 dark:text-slate-600" />
</div>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,826 @@
'use client';
import {
ArrowLongRightIcon,
ArrowsRightLeftIcon,
ChevronRightIcon,
ClipboardDocumentIcon,
DocumentIcon,
InboxArrowDownIcon,
LockClosedIcon,
SparklesIcon,
} from '@heroicons/react/24/outline';
import { SectionHeading } from '@nx/nx-dev/ui-common';
import { BentoGrid, BentoGridItem } from './elements/bento-grid';
import { cx } from '@nx/nx-dev/ui-primitives';
import { animate, motion, useMotionValue, useTransform } from 'framer-motion';
import { useEffect } from 'react';
export function UnderstandWorkspace(): JSX.Element {
return (
<section>
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="mx-auto max-w-2xl text-center">
<SectionHeading as="h2" variant="title" id="deep-understanding">
Deep understanding of your workspace
</SectionHeading>
<SectionHeading as="p" variant="subtitle" className="mt-6">
Nx Cloud knows your build tools, how your projects relate to each
other, and what every task does.
</SectionHeading>
</div>
{/*FEATURES CONTAINER*/}
<BentoGrid className="mx-auto mt-20 w-full md:auto-rows-[22rem]">
{items.map((item, i) => (
<BentoGridItem
key={i}
title={item.title}
description={item.description}
header={item.header}
className={cx('[&>p:text-lg]', item.className)}
icon={item.icon}
/>
))}
</BentoGrid>
</div>
</section>
);
}
const Caching = () => {
const variants = {
hidden: {
opacity: 0,
transition: {
when: 'afterChildren',
},
},
visible: {
opacity: 1,
},
};
const items = [
{
cache: 'remote',
target: 'build',
project: 'website',
},
{
cache: 'remote',
target: 'test',
project: 'express',
},
{
cache: 'remote',
target: 'build',
project: 'eslint',
},
{
cache: 'remote',
target: 'lint',
project: 'autoloan',
},
{
cache: 'remote',
target: 'test',
project: 'website',
},
{
cache: 'remote',
target: 'lint',
project: 'website',
},
{
cache: 'remote',
target: 'build-base',
project: 'express',
},
{
cache: 'remote',
target: 'build',
project: 'express',
},
{
cache: 'remote',
target: 'lint',
project: 'express',
},
{
cache: 'remote',
target: 'test',
project: 'autoloan',
},
];
return (
<motion.div
initial="hidden"
variants={variants}
whileInView="visible"
viewport={{ once: true }}
className="flex h-full min-h-[12rem] w-full flex-1 flex-col gap-2"
>
<motion.div className="flex items-center gap-1.5 text-center text-sm italic">
<SparklesIcon aria-hidden="true" className="h-4 w-4" />
<span className="font-semibold">
<Counter value={items.length + 123} duration={5} />
</span>
tasks replayed instantly with cache
</motion.div>
<div className="flex flex-1 flex-col divide-y divide-neutral-100 overflow-auto rounded-lg border border-neutral-100 dark:divide-slate-700 dark:border-slate-700 dark:bg-slate-950">
{items.map((i, idx) => (
<div
key={`project-${i}-${idx}`}
className="flex w-full flex-row items-center gap-2 p-2 transition-colors ease-out hover:bg-slate-50/40 dark:hover:bg-slate-900/40"
>
<div className="m-1 h-2.5 w-2.5 flex-none rounded-full bg-emerald-500" />
<div className="flex min-w-[4rem]">
<span className="inline-flex cursor-default items-center rounded-md bg-slate-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20">
{i.cache}
</span>
</div>
<div
className="grow truncate text-left text-sm font-medium"
data-testid="task-target"
>
{i.target}
</div>
<span className="inline-flex cursor-default items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20 dark:bg-green-500/10 dark:text-green-400 dark:ring-green-500/20">
{i.project}
</span>
<div className="text-xs">&lt; 1s</div>
</div>
))}
</div>
</motion.div>
);
};
const FlakyTasks = () => {
const variants = {
hidden: {
opacity: 0,
transition: {
when: 'afterChildren',
},
},
visible: {
opacity: 1,
},
};
const itemVariants = {
visible: (i: number) => ({
opacity: 1,
x: 0,
transition: {
delay: i * 0.2,
duration: 0.275,
ease: 'easeOut',
when: 'beforeChildren',
staggerChildren: 0.3,
},
}),
hidden: {
opacity: 0,
x: -100,
transition: {
when: 'afterChildren',
},
},
};
return (
<motion.div
initial="hidden"
variants={variants}
whileInView="visible"
viewport={{ once: true }}
className="flex h-full min-h-[12rem] w-full flex-1 flex-col gap-2"
>
<motion.div className="flex items-center gap-1.5 text-center text-sm">
Running
<ChevronRightIcon aria-hidden="true" className="h-3 w-3" />
<code className="text-xs font-medium">
nx affected --targets=e2e,test
</code>
</motion.div>
<div className="flex flex-1 flex-col divide-y divide-neutral-100 overflow-auto rounded-lg border border-neutral-100 dark:divide-slate-700 dark:border-slate-700 dark:bg-slate-950">
<motion.div
custom={1}
variants={itemVariants}
className="flex w-full flex-row items-center gap-2 p-2 transition-colors ease-out hover:bg-slate-50/40 dark:hover:bg-slate-900/40"
>
<div className="m-1 h-2.5 w-2.5 flex-none rounded-full bg-emerald-500" />
<div className="flex min-w-[5rem]">
<span className="inline-flex cursor-default items-center rounded-md bg-slate-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20">
miss
</span>
</div>
<div className="grow truncate text-left text-sm font-medium">
test
</div>
<motion.span
custom={3}
variants={itemVariants}
className="inline-flex cursor-default items-center rounded-md bg-yellow-400/10 px-2 py-1 text-xs font-medium text-yellow-500 ring-1 ring-inset ring-yellow-400/20 dark:bg-yellow-400/10 dark:text-yellow-500 dark:ring-yellow-400/20"
>
flaky
</motion.span>
<motion.span
custom={2}
variants={itemVariants}
className="inline-flex cursor-default items-center rounded-md bg-slate-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20"
>
1 retry
</motion.span>
<span className="inline-flex cursor-default items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20 dark:bg-green-500/10 dark:text-green-400 dark:ring-green-500/20">
website
</span>
</motion.div>
<motion.div
custom={2}
variants={itemVariants}
className="flex w-full flex-row items-center gap-2 p-2 transition-colors ease-out hover:bg-slate-50/40 dark:hover:bg-slate-900/40"
>
<div className="flex items-center gap-2">
<div className="m-1 h-2.5 w-2.5 flex-none rounded-full bg-red-500"></div>
<div className="flex min-w-[5rem]">
<span className="whitespace-nowrap text-sm font-medium">
Failed
</span>
</div>
</div>
<div className="grow truncate text-left text-sm font-medium">e2e</div>
<span className="inline-flex cursor-default items-center rounded-md bg-slate-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20">
1 retry
</span>
<span className="inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-600/10 dark:bg-red-400/10 dark:text-red-400 dark:ring-red-400/20">
website
</span>
</motion.div>
<motion.div
custom={3}
variants={itemVariants}
className="flex w-full flex-row items-center gap-2 p-2 transition-colors ease-out hover:bg-slate-50/40 dark:hover:bg-slate-900/40"
>
<div className="flex items-center gap-2">
<div className="flex-none animate-pulse rounded-full bg-yellow-500/20 p-1">
<div className="h-2 w-2 rounded-full bg-yellow-500" />
</div>
<div className="flex min-w-[5rem]">
<span className="whitespace-nowrap text-sm font-medium">
In progress
</span>
</div>
</div>
<div className="grow truncate text-left text-sm font-medium">
test
</div>
<motion.span
custom={15}
variants={itemVariants}
className="inline-flex cursor-default items-center rounded-md bg-yellow-400/10 px-2 py-1 text-xs font-medium text-yellow-500 ring-1 ring-inset ring-yellow-400/20"
>
flaky
</motion.span>
<motion.span
custom={10}
variants={itemVariants}
className="inline-flex cursor-default items-center rounded-md bg-slate-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20"
>
1 retry
</motion.span>
<span className="inline-flex cursor-default items-center rounded-md bg-slate-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20">
ui-cdk
</span>
</motion.div>
<motion.div
custom={4}
variants={itemVariants}
className="flex w-full flex-row items-center gap-2 p-2 transition-colors ease-out hover:bg-slate-50/40 dark:hover:bg-slate-900/40"
>
<div className="m-1 h-2.5 w-2.5 flex-none rounded-full bg-emerald-500" />
<div className="flex min-w-[5rem]">
<span className="inline-flex cursor-default items-center rounded-md bg-slate-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20">
remote
</span>
</div>
<div className="grow truncate text-left text-sm font-medium">
lint
</div>
<span className="inline-flex cursor-default items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20 dark:bg-green-500/10 dark:text-green-400 dark:ring-green-500/20">
website
</span>
</motion.div>
<motion.div
custom={5}
variants={itemVariants}
className="flex w-full flex-row items-center gap-2 p-2 transition-colors ease-out hover:bg-slate-50/40 dark:hover:bg-slate-900/40"
>
<div className="flex items-center gap-2">
<div className="flex-none animate-pulse rounded-full bg-yellow-500/20 p-1">
<div className="h-2 w-2 rounded-full bg-yellow-500" />
</div>
<div className="flex min-w-[5rem]">
<span className="whitespace-nowrap text-sm font-medium">
In progress
</span>
</div>
</div>
<div className="grow truncate text-left text-sm font-medium">
build
</div>
<span className="inline-flex cursor-default items-center rounded-md bg-slate-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20">
ui-cdk
</span>
</motion.div>
</div>
</motion.div>
);
};
const SplitE2eTests = () => {
const variants = {
hidden: {
opacity: 0,
transition: {
when: 'afterChildren',
},
},
visible: {
opacity: 1,
},
};
const uiDialogsTests = [
'e2e/libs/ui-cdk/spec-1.tsx',
'e2e/libs/ui-cdk/spec-2.tsx',
];
const websiteTests = [
'e2e/apps/website/spec-1.tsx',
'e2e/apps/website/spec-2.tsx',
'e2e/apps/website/spec-3.tsx',
'e2e/apps/website/spec-4.tsx',
'e2e/apps/website/spec-5.tsx',
'e2e/apps/website/spec-6.tsx',
];
return (
<motion.div
initial="hidden"
variants={variants}
whileInView="visible"
viewport={{ once: true }}
className="flex h-full min-h-[12rem] w-full flex-1 flex-col gap-2"
>
<motion.div className="flex items-center gap-1.5 text-center text-sm">
Running
<ChevronRightIcon aria-hidden="true" className="h-3 w-3" />
<code className="text-xs font-medium">nx affected --targets=e2e</code>
</motion.div>
<div className="flex flex-1 flex-col divide-y divide-neutral-100 overflow-auto rounded-lg border border-neutral-100 bg-white dark:divide-slate-700 dark:border-slate-700 dark:bg-slate-950">
<div className="flex w-full flex-row items-center gap-2 p-2 transition-colors ease-out hover:bg-slate-50/40 dark:hover:bg-slate-900/40">
<div className="flex items-center gap-2">
<div className="flex-none animate-pulse rounded-full bg-yellow-500/20 p-1">
<div className="h-2 w-2 rounded-full bg-yellow-500" />
</div>
<div className="flex min-w-[5rem]">
<span className="whitespace-nowrap text-sm font-medium">
In progress
</span>
</div>
</div>
<div className="grow truncate text-left text-sm font-medium">e2e</div>
<span className="inline-flex items-center rounded-md bg-slate-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20">
ui-cdk
</span>
</div>
{uiDialogsTests.map((i, idx) => (
<div
key={`${i}-${idx}`}
className="flex w-full flex-row items-center gap-2 p-2 transition-colors ease-out hover:bg-slate-50/40 dark:hover:bg-slate-900/40"
>
<div className="w-4" />
<ArrowLongRightIcon aria-hidden="true" className="h-4 w-4" />
<div className="flex-none animate-pulse rounded-full bg-yellow-500/20 p-1">
<div className="h-2 w-2 rounded-full bg-yellow-500" />
</div>
<div className="grow truncate text-left text-sm">{i}</div>
</div>
))}
<div className="flex w-full flex-row items-center gap-2 p-2 transition-colors ease-out hover:bg-slate-50/40 dark:hover:bg-slate-900/40">
<div className="flex items-center gap-2">
<div className="flex-none animate-pulse rounded-full bg-yellow-500/20 p-1">
<div className="h-2 w-2 rounded-full bg-yellow-500" />
</div>
<div className="flex min-w-[5rem]">
<span className="whitespace-nowrap text-sm font-medium">
In progress
</span>
</div>
</div>
<div className="grow truncate text-left text-sm font-medium">e2e</div>
<span className="inline-flex items-center rounded-md bg-slate-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20">
website
</span>
</div>
{websiteTests.map((i, idx) => (
<div
key={`${i}-${idx}`}
className="flex w-full flex-row items-center gap-2 p-2 transition-colors ease-out hover:bg-slate-50/40 dark:hover:bg-slate-900/40"
>
<div className="w-4" />
<ArrowLongRightIcon aria-hidden="true" className="h-4 w-4" />
<div className="flex-none animate-pulse rounded-full bg-yellow-500/20 p-1">
<div className="h-2 w-2 rounded-full bg-yellow-500" />
</div>
<div className="grow truncate text-left text-sm">{i}</div>
</div>
))}
</div>
</motion.div>
);
};
const TaskDistribution = () => {
const variants = {
hidden: {
opacity: 0,
transition: {
when: 'afterChildren',
},
},
visible: {
opacity: 1,
},
};
const itemVariants = {
visible: (i: number) => ({
opacity: 1,
x: 0,
transition: {
delay: i * 0.2,
duration: 0.275,
ease: 'easeOut',
when: 'beforeChildren',
staggerChildren: 0.3,
},
}),
hidden: {
opacity: 0,
x: -100,
transition: {
when: 'afterChildren',
},
},
};
const agent1Items = ['website:build-base', 'website:build'];
const agent2Items = ['docs:lint', 'express:test', 'website:lint'];
const agent3Items = ['graph-client:build', 'plugin:test'];
const nxReplayItems = [
'graph-client:lint',
'plugin:lint',
'website:test',
'vite:test',
'vite:build',
];
const notStartedTasks = ['js:build', 'js:lint'];
return (
<motion.div
initial="hidden"
variants={variants}
whileInView="visible"
viewport={{ once: true }}
className="relative flex h-full min-h-[12rem] w-full flex-1 flex-col gap-2"
>
<div className="grid max-h-full grid-cols-2 items-stretch gap-8 overflow-hidden p-1 lg:grid-cols-3">
<motion.div
custom={2}
variants={itemVariants}
className="relative overflow-x-hidden rounded-lg bg-white p-2 ring-1 ring-neutral-100 dark:bg-slate-950 dark:ring-slate-700"
>
<p className="text-sm font-medium text-slate-700 dark:text-slate-400">
Main Workflow
</p>
<div className="mt-2 flex flex-col gap-1">
<p className="overflow-x-auto truncate py-2 font-mono text-xs font-medium text-slate-900 dark:text-slate-400">
nx affected --target=build,lint,test
</p>
<div className="flex h-1.5 w-full flex-row rounded-full">
<div
title="2 tasks in completed"
className="cursor-pointer rounded-l-full bg-green-400 dark:bg-green-600"
style={{ flexGrow: 8 }}
/>
<div
title="12 tasks in progress"
className="cursor-pointer bg-yellow-400 dark:bg-yellow-600"
style={{ flexGrow: 12 }}
/>
<div
title="24 tasks not started"
className="cursor-pointer rounded-r-full bg-slate-100 dark:bg-slate-600"
style={{ flexGrow: 24 }}
/>
</div>
<div className="mt-2 flex flex-1 flex-col divide-y divide-neutral-100 overflow-auto rounded-lg border border-neutral-100 dark:divide-slate-700 dark:border-slate-700">
{notStartedTasks.map((i, idx) => (
<motion.div
key={`${i}-${idx}`}
custom={idx + 3}
variants={itemVariants}
className="flex w-full flex-row items-center gap-2 p-2 transition-colors ease-out hover:bg-slate-50/40 dark:hover:bg-slate-800/40"
>
<div className="text-xs">{i}</div>
</motion.div>
))}
</div>
</div>
</motion.div>
<motion.div
custom={3}
variants={itemVariants}
className="relative flex flex-col gap-2 overflow-x-hidden rounded-lg bg-white p-2 ring-1 ring-neutral-100 dark:bg-slate-950 dark:ring-slate-700"
>
<p className="text-sm font-medium text-slate-700 dark:text-slate-400">
Nx Agents
</p>
<div className="flex flex-1 flex-col overflow-auto">
<motion.div custom={4} variants={itemVariants} className="relative">
<div className="sticky top-0 z-10 mt-1 rounded-md border border-neutral-100 bg-slate-50 p-2 transition hover:bg-slate-100 dark:border-slate-800 dark:bg-slate-900 dark:hover:bg-slate-800">
<div className="flex items-center gap-x-2 text-xs font-medium">
<div className="flex-none animate-pulse rounded-full bg-yellow-500/20 p-1">
<div className="h-2 w-2 rounded-full bg-yellow-500"></div>
</div>
Agent 1<span className="flex-grow"></span>
<span className="mr-1 opacity-80">{agent1Items.length}</span>
</div>
</div>
<ul className="my-2 overflow-y-auto overflow-x-hidden">
{agent1Items.map((i, idx) => (
<motion.li
key={`agent-${i}-${idx}`}
custom={idx + 5}
variants={itemVariants}
className="truncate p-1 pl-4 text-xs"
>
{i}
</motion.li>
))}
</ul>
</motion.div>
<motion.div custom={6} variants={itemVariants} className="relative">
<div className="sticky top-0 z-10 mt-1 rounded-md border border-neutral-100 bg-slate-50 p-2 transition hover:bg-slate-100 dark:border-slate-800 dark:bg-slate-900 dark:hover:bg-slate-800">
<div className="flex items-center gap-x-2 text-xs font-medium">
<div className="flex-none animate-pulse rounded-full bg-yellow-500/20 p-1">
<div className="h-2 w-2 rounded-full bg-yellow-500"></div>
</div>
Agent 2<span className="flex-grow"></span>
<span className="mr-1 opacity-80">{agent2Items.length}</span>
</div>
</div>
<ul className="my-2 overflow-y-auto overflow-x-hidden">
{agent2Items.map((i, idx) => (
<motion.li
key={`agent-${i}-${idx}`}
custom={idx + 7}
variants={itemVariants}
className="truncate p-1 pl-4 text-xs"
>
{i}
</motion.li>
))}
</ul>
</motion.div>
<motion.div
custom={8}
variants={itemVariants}
className="relative hidden lg:block"
>
<div className="sticky top-0 z-10 mt-1 rounded-md border border-neutral-100 bg-slate-50 p-2 transition hover:bg-slate-100 dark:border-slate-800 dark:bg-slate-900 dark:hover:bg-slate-800">
<div className="flex items-center gap-x-2 text-xs font-medium">
<div className="flex-none animate-pulse rounded-full bg-yellow-500/20 p-1">
<div className="h-2 w-2 rounded-full bg-yellow-500"></div>
</div>
Agent 3<span className="flex-grow"></span>
<span className="mr-1 opacity-80">{agent3Items.length}</span>
</div>
</div>
<ul className="my-2 overflow-y-auto overflow-x-hidden">
{agent3Items.map((i, idx) => (
<motion.li
key={`agent-${i}-${idx}`}
custom={idx + 9}
variants={itemVariants}
className="truncate p-1 pl-4 text-xs"
>
{i}
</motion.li>
))}
</ul>
</motion.div>
</div>
</motion.div>
<motion.div
custom={4}
variants={itemVariants}
className="relative hidden flex-col gap-2 overflow-x-hidden rounded-lg bg-white p-2 ring-1 ring-neutral-100 lg:flex dark:bg-slate-950 dark:ring-slate-700"
>
<p className="text-sm font-medium text-slate-700 dark:text-slate-400">
Nx Replay
</p>
<div className="flex flex-1 flex-col divide-y divide-neutral-100 overflow-auto rounded-lg border border-neutral-100 dark:divide-slate-800 dark:border-slate-800">
{nxReplayItems.map((i, idx) => (
<motion.div
key={`replay-${i}-${idx}`}
custom={idx + 10}
variants={itemVariants}
className="flex w-full flex-row items-center gap-2 p-2 text-xs transition-colors ease-out hover:bg-slate-50/40 dark:hover:bg-slate-800/40"
>
{i}
</motion.div>
))}
</div>
</motion.div>
</div>
</motion.div>
);
};
const ActionableErrorFeedback = () => {
const variants = {
hidden: {
opacity: 0,
transition: {
when: 'afterChildren',
},
},
visible: {
opacity: 1,
},
};
return (
<motion.div
initial="hidden"
variants={variants}
whileInView="visible"
viewport={{ once: true }}
className="flex h-full min-h-[12rem] w-full flex-1 flex-col gap-1"
>
<div className="flex items-center gap-1.5">
{/*STATUS*/}
<div className="flex items-center gap-2">
<div className="m-1 h-2.5 w-2.5 flex-none rounded-full bg-red-500"></div>
<span
className="whitespace-nowrap text-lg font-medium"
data-testid="status-label"
>
Failed
</span>
with code: 1
</div>
</div>
{/*COMPARE & FLAKY*/}
<div className="flex items-center justify-end gap-1.5">
<button
type="button"
className="cursor-default rounded bg-white px-2 py-1 text-xs font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 dark:bg-white/10 dark:text-slate-400 dark:ring-slate-700"
>
Compare to similar tasks
</button>
<button
type="button"
className="cursor-default rounded bg-white px-2 py-1 text-xs font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 dark:bg-white/10 dark:text-slate-400 dark:ring-slate-700"
>
Set as "not flaky"
</button>
</div>
<div>
<div className="border-b border-neutral-100 dark:border-slate-700">
<div className="-mb-px flex space-x-4">
<span className="cursor-default whitespace-nowrap border-b-2 border-transparent px-0.5 py-2 text-xs font-medium text-gray-500 dark:text-slate-400">
Attempt 1
</span>
<span className="cursor-default whitespace-nowrap border-b-2 border-blue-500 px-0.5 py-2 text-xs font-medium text-blue-500 dark:border-sky-600 dark:text-sky-600">
Attempt 2
</span>
</div>
</div>
</div>
<p className="text-xs">Feb 23, 2024 08:57:49 - 08:57:54 (4s)</p>
<motion.div className="terminal-output flex max-h-full min-h-[2rem] flex-col overflow-visible rounded-lg border border-slate-200 bg-slate-50 font-mono text-xs leading-normal text-slate-800 subpixel-antialiased dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200">
<div className="flex items-center justify-between gap-4 rounded-t-lg border-b border-slate-200 bg-slate-100 px-2 py-1 dark:border-slate-700 dark:bg-slate-800">
<div className="font-sans text-sm font-medium">
<span className="group relative flex cursor-pointer items-center overflow-hidden whitespace-nowrap subpixel-antialiased dark:text-slate-300">
<span>nx run nx-dev:build</span>
<span className="transform opacity-0 transition-all">
<ClipboardDocumentIcon className="h-4 w-4" />
</span>
</span>
</div>
</div>
<div className="overflow-auto">
<pre className="overflow-x-hidden p-1 dark:text-slate-400">
{`nx run nx-dev:sitemap ✨ [next-sitemap]
Loading next-sitemap config:file:///home/workflows/workspace/nx-dev/nx-dev/next-sitemap.config.js
[next-sitemap] Unable to find export-maker.
Make sure to build the project using "next build" command
node:internal/process/promises:289
triggerUncaughtException(err, true /* fromPromise */); ^
[Error:ENOENT: no such file or directory, stat '/home/workflows/workspace/dist/nx-dev/nx-dev/.next/export-marker.json']
errno: -2, code: 'ENOENT', syscall: 'stat', path: '/home/workflows/workspace/dist/nx-dev/nx-dev/.next/export-marker.json'
Node.js v20.9.0 Warning: command "pnpm next-sitemap --config
./nx-dev/nx-dev/next-sitemap.config.js" exited with non-zero status code`}
</pre>
</div>
</motion.div>
</motion.div>
);
};
export function Counter({
value,
duration = 2,
}: {
value: number;
duration?: number;
}) {
const count = useMotionValue(0);
const rounded = useTransform(count, Math.round);
useEffect(() => {
const animation = animate(count, value, {
type: 'tween',
ease: 'easeOut',
duration,
});
return animation.stop;
}, []);
return <motion.span>{rounded}</motion.span>;
}
const items = [
{
title: 'Nx Agents: seamless task distribution',
description: (
<>
Nx Cloud manages CI agent machines, offloading complex machine
provisioning from your CI provider. Integration is as simple as
initiating a distributed run on your provider, with Nx Agents handling
the rest - streamlining operations and enhancing cost efficiency.
</>
),
header: <TaskDistribution />,
className: 'md:col-span-2',
icon: <ArrowsRightLeftIcon className="h-4 w-4 text-neutral-500" />,
},
{
title: 'Nx Replay: secure computation cache',
description: (
<>
Nx Cloud caches and restores task results, across PRs and local
machines, drastically accelerating your CI runs.
</>
),
header: <Caching />,
className: 'md:col-span-1',
icon: <LockClosedIcon className="h-4 w-4 text-neutral-500" />,
},
{
title: 'Atomizer: E2E test splitting',
description: (
<>
Nx Cloud splits large e2e projects into fine-grained test runs, enabling
more efficient distribution and dramatically reducing CI times.
</>
),
header: <SplitE2eTests />,
className: 'md:col-span-1',
icon: <ClipboardDocumentIcon className="h-4 w-4 text-neutral-500" />,
},
{
title: 'Flaky task detection',
description: (
<>
Nx Cloud automatically identifies flaky tasks from any tool and
initiates a re-run of those specific tasks, optimizing resource usage
for peak efficiency.
</>
),
header: <FlakyTasks />,
className: 'md:col-span-1',
icon: <DocumentIcon className="h-4 w-4 text-neutral-500" />,
},
{
title: 'Actionable feedback',
description: (
<>
Nx Cloud lets you see what went wrong, and how to fix it. It understands
your command and lets you find the logs you need.
</>
),
header: <ActionableErrorFeedback />,
className: 'md:col-span-1',
icon: <InboxArrowDownIcon className="h-4 w-4 text-neutral-500" />,
},
];

View File

@ -0,0 +1,17 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
],
"extends": "../../tsconfig.base.json"
}

View File

@ -0,0 +1,24 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": [
"node",
"@nx/react/typings/cssmodule.d.ts",
"@nx/react/typings/image.d.ts"
]
},
"exclude": [
"jest.config.ts",
"src/**/*.spec.ts",
"src/**/*.test.ts",
"src/**/*.spec.tsx",
"src/**/*.test.tsx",
"src/**/*.spec.js",
"src/**/*.test.js",
"src/**/*.spec.jsx",
"src/**/*.test.jsx"
],
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
}

View File

@ -8,7 +8,7 @@ export function Footer(): JSX.Element {
solutions: [ solutions: [
{ name: 'Nx Enterprise', href: '/enterprise' }, { name: 'Nx Enterprise', href: '/enterprise' },
{ name: 'Nx', href: 'https://nx.dev' }, { name: 'Nx', href: 'https://nx.dev' },
{ name: 'Nx Cloud', href: 'https://nx.app/?utm_source=nx.dev' }, { name: 'Nx Cloud', href: '/nx-cloud' },
], ],
resources: [ resources: [
{ name: 'Blog', href: '/blog' }, { name: 'Blog', href: '/blog' },

View File

@ -106,7 +106,7 @@ export const plans: MenuItem[] = [
name: 'Nx Cloud', name: 'Nx Cloud',
description: description:
'End-to-end solution for smart, efficient and maintainable CI.', 'End-to-end solution for smart, efficient and maintainable CI.',
href: 'https://nx.app', href: '/nx-cloud',
icon: null, icon: null,
isNew: false, isNew: false,
isHighlight: false, isHighlight: false,

View File

@ -88,6 +88,7 @@
"@nx/nx-dev/models-package": ["nx-dev/models-package/src/index.ts"], "@nx/nx-dev/models-package": ["nx-dev/models-package/src/index.ts"],
"@nx/nx-dev/ui-animations": ["nx-dev/ui-animations/src/index.ts"], "@nx/nx-dev/ui-animations": ["nx-dev/ui-animations/src/index.ts"],
"@nx/nx-dev/ui-blog": ["nx-dev/ui-blog/src/index.ts"], "@nx/nx-dev/ui-blog": ["nx-dev/ui-blog/src/index.ts"],
"@nx/nx-dev/ui-cloud": ["nx-dev/ui-cloud/src/index.ts"],
"@nx/nx-dev/ui-commands": ["nx-dev/ui-commands/src/index.ts"], "@nx/nx-dev/ui-commands": ["nx-dev/ui-commands/src/index.ts"],
"@nx/nx-dev/ui-common": ["nx-dev/ui-common/src/index.ts"], "@nx/nx-dev/ui-common": ["nx-dev/ui-common/src/index.ts"],
"@nx/nx-dev/ui-common/*": ["nx-dev/ui-common/*"], "@nx/nx-dev/ui-common/*": ["nx-dev/ui-common/*"],