mirror of
https://github.com/hanzoai/pack.git
synced 2026-09-30 23:43:13 +00:00
feat(node): nx support (#606)
* feat(example): add node-nx-next workspace scaffold - Initialize an Nx workspace with a Next.js application using pnpm. - Configure workspace tooling including TypeScript, Prettier, and ESLint. - Include a generation script to automate the workspace setup process. Generated-by: aiautocommit * feat(node): add BuildScriptContains helper to PackageJson Generated-by: aiautocommit * feat(node): add support for Nx workspaces with Next.js - Implement Nx workspace detection and package resolution. - Enable automatic Nx build commands when no root build script is detected. - Add RAILPACK_NX_APP environment variable to support selecting specific apps in multi-app workspaces. - Refactor Next.js detection logic to better support monorepo and Nx-inferred architectures. - Include unit tests for Nx workspace detection and build/start command generation. Generated-by: aiautocommit * docs: add documentation for Nx monorepo support - Explain how Railpack handles Nx workspaces, including automatic detection and support for inferred tasks. - Document the new `RAILPACK_NX_APP` configuration variable to target specific apps in multi-app workspaces. - Detail the build and start behavior for Next.js applications within an Nx context. Generated-by: aiautocommit * test: add snapshot for Nx Node.js and Next.js example Generated-by: aiautocommit * chore: bump railpack images and set version to dev - Update builder and runtime images to mise-2026.7.7 - Include RAILPACK_VERSION environment variable in build plan Generated-by: aiautocommit
This commit is contained in:
+207
@@ -0,0 +1,207 @@
|
||||
{
|
||||
"caches": {
|
||||
"apt": {
|
||||
"directory": "/var/cache/apt",
|
||||
"type": "locked"
|
||||
},
|
||||
"apt-lists": {
|
||||
"directory": "/var/lib/apt/lists",
|
||||
"type": "locked"
|
||||
},
|
||||
"next-apps-web": {
|
||||
"directory": "/app/apps/web/.next/cache",
|
||||
"type": "shared"
|
||||
},
|
||||
"node-modules": {
|
||||
"directory": "/app/node_modules/.cache",
|
||||
"type": "shared"
|
||||
},
|
||||
"pnpm-install": {
|
||||
"directory": "/opt/pnpm/store",
|
||||
"type": "shared"
|
||||
}
|
||||
},
|
||||
"deploy": {
|
||||
"base": {
|
||||
"step": "packages:apt:runtime"
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"include": [
|
||||
"/mise/shims",
|
||||
"/mise/installs",
|
||||
"/usr/local/bin/mise",
|
||||
"/etc/mise/config.toml",
|
||||
"/root/.local/state/mise"
|
||||
],
|
||||
"step": "packages:mise"
|
||||
},
|
||||
{
|
||||
"include": [
|
||||
"/app/node_modules"
|
||||
],
|
||||
"step": "build"
|
||||
},
|
||||
{
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
".yarn"
|
||||
],
|
||||
"include": [
|
||||
"/root/.cache",
|
||||
"."
|
||||
],
|
||||
"step": "build"
|
||||
}
|
||||
],
|
||||
"startCommand": "cd apps/web \u0026\u0026 next start",
|
||||
"variables": {
|
||||
"CI": "true",
|
||||
"NODE_ENV": "production",
|
||||
"NPM_CONFIG_FETCH_RETRIES": "5",
|
||||
"NPM_CONFIG_FUND": "false",
|
||||
"NPM_CONFIG_PRODUCTION": "false",
|
||||
"NPM_CONFIG_UPDATE_NOTIFIER": "false",
|
||||
"RAILPACK_VERSION": "dev"
|
||||
}
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"assets": {
|
||||
"generated-mise-toml": "[generated-mise-toml]"
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"path": "/mise/shims"
|
||||
},
|
||||
{
|
||||
"customName": "create mise config",
|
||||
"name": "generated-mise-toml",
|
||||
"path": "/etc/mise/config.toml"
|
||||
},
|
||||
{
|
||||
"cmd": "mise install",
|
||||
"customName": "install mise packages: node, pnpm"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"image": "ghcr.io/railwayapp/railpack-builder:mise-2026.7.7"
|
||||
}
|
||||
],
|
||||
"name": "packages:mise",
|
||||
"variables": {
|
||||
"MISE_CACHE_DIR": "/mise/cache",
|
||||
"MISE_CONFIG_DIR": "/mise",
|
||||
"MISE_DATA_DIR": "/mise",
|
||||
"MISE_INSTALLS_DIR": "/mise/installs",
|
||||
"MISE_SHIMS_DIR": "/mise/shims"
|
||||
}
|
||||
},
|
||||
{
|
||||
"caches": [
|
||||
"pnpm-install"
|
||||
],
|
||||
"commands": [
|
||||
{
|
||||
"path": "/app/node_modules/.bin"
|
||||
},
|
||||
{
|
||||
"cmd": "mkdir -p /app/node_modules/.cache"
|
||||
},
|
||||
{
|
||||
"dest": ".npmrc",
|
||||
"src": ".npmrc"
|
||||
},
|
||||
{
|
||||
"dest": "apps/web/package.json",
|
||||
"src": "apps/web/package.json"
|
||||
},
|
||||
{
|
||||
"dest": "package.json",
|
||||
"src": "package.json"
|
||||
},
|
||||
{
|
||||
"dest": "pnpm-lock.yaml",
|
||||
"src": "pnpm-lock.yaml"
|
||||
},
|
||||
{
|
||||
"dest": "pnpm-workspace.yaml",
|
||||
"src": "pnpm-workspace.yaml"
|
||||
},
|
||||
{
|
||||
"path": "/opt/pnpm"
|
||||
},
|
||||
{
|
||||
"cmd": "pnpm add -g node-gyp"
|
||||
},
|
||||
{
|
||||
"cmd": "pnpm install --frozen-lockfile --prefer-offline"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"step": "packages:mise"
|
||||
}
|
||||
],
|
||||
"name": "install",
|
||||
"variables": {
|
||||
"CI": "true",
|
||||
"NODE_ENV": "production",
|
||||
"NPM_CONFIG_FETCH_RETRIES": "5",
|
||||
"NPM_CONFIG_FUND": "false",
|
||||
"NPM_CONFIG_PRODUCTION": "false",
|
||||
"NPM_CONFIG_UPDATE_NOTIFIER": "false",
|
||||
"PNPM_HOME": "/opt/pnpm",
|
||||
"PNPM_STORE_DIR": "/opt/pnpm/store"
|
||||
}
|
||||
},
|
||||
{
|
||||
"caches": [
|
||||
"node-modules",
|
||||
"next-apps-web"
|
||||
],
|
||||
"commands": [
|
||||
{
|
||||
"cmd": "nx build @node-nx-next/web"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"step": "install"
|
||||
},
|
||||
{
|
||||
"include": [
|
||||
"."
|
||||
],
|
||||
"local": true
|
||||
}
|
||||
],
|
||||
"name": "build",
|
||||
"secrets": [
|
||||
"*"
|
||||
],
|
||||
"variables": {
|
||||
"NEXT_TELEMETRY_DISABLED": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"caches": [
|
||||
"apt",
|
||||
"apt-lists"
|
||||
],
|
||||
"commands": [
|
||||
{
|
||||
"cmd": "sh -c 'apt-get update \u0026\u0026 apt-get install -y libatomic1'",
|
||||
"customName": "install apt packages: libatomic1"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"image": "ghcr.io/railwayapp/railpack-runtime:mise-2026.7.7"
|
||||
}
|
||||
],
|
||||
"name": "packages:apt:runtime"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -177,8 +177,10 @@ func (p *NodeProvider) StartCommandHelp() string {
|
||||
"2. A \"main\" field in your package.json pointing to your entry file:\n" +
|
||||
" \"main\": \"src/server.js\"\n\n" +
|
||||
"3. An index.js or index.ts file in your project root\n\n" +
|
||||
"4. An Nx workspace with a Next.js app (nx.json + next)\n\n" +
|
||||
"If you have a static site, you can set the RAILPACK_SPA_OUTPUT_DIR environment variable\n" +
|
||||
"containing the directory of your built static files."
|
||||
"containing the directory of your built static files.\n\n" +
|
||||
"For multi-app Nx workspaces, set RAILPACK_NX_APP to select which app to deploy."
|
||||
}
|
||||
|
||||
func (p *NodeProvider) GetStartCommand(ctx *generate.GenerateContext) string {
|
||||
@@ -191,6 +193,11 @@ func (p *NodeProvider) GetStartCommand(ctx *generate.GenerateContext) string {
|
||||
} else if p.isNuxt() {
|
||||
// Default Nuxt start command
|
||||
return "node .output/server/index.mjs"
|
||||
} else if pkg, _, ok := p.resolveNxDeployPackage(ctx); ok {
|
||||
// Prefer next start from the app dir so prune does not require the nx CLI at runtime
|
||||
if p.isNextAppPackage(pkg, ctx) {
|
||||
return nxNextStartCommand(pkg)
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
@@ -199,15 +206,23 @@ func (p *NodeProvider) GetStartCommand(ctx *generate.GenerateContext) string {
|
||||
func (p *NodeProvider) Build(ctx *generate.GenerateContext, build *generate.CommandStepBuilder) {
|
||||
build.AddInput(ctx.NewLocalLayer())
|
||||
|
||||
_, ok := p.packageJson.Scripts["build"]
|
||||
if ok {
|
||||
if p.packageJson.HasScript("build") {
|
||||
build.AddCommands([]plan.Command{
|
||||
plan.NewExecCommand(p.packageManager.RunCmd("build")),
|
||||
})
|
||||
|
||||
if p.isNext() {
|
||||
// TODO I don't love how we are adding runtime config vars in `build`
|
||||
build.AddVariables(map[string]string{"NEXT_TELEMETRY_DISABLED": "1"})
|
||||
}
|
||||
} else if _, projectName, ok := p.resolveNxDeployPackage(ctx); ok {
|
||||
// TODO this `resolveNxDeployPackage` logic feels messy, we should refactor this a bit
|
||||
|
||||
// Nx infers targets from plugins; root package.json often has no build script
|
||||
build.AddCommands([]plan.Command{
|
||||
plan.NewExecCommand(nxBuildCommand(projectName)),
|
||||
})
|
||||
build.AddVariables(map[string]string{"NEXT_TELEMETRY_DISABLED": "1"})
|
||||
}
|
||||
|
||||
p.addCachesToBuildStep(ctx, build)
|
||||
@@ -237,10 +252,12 @@ func (p *NodeProvider) addCachesToBuildStep(ctx *generate.GenerateContext, build
|
||||
build.AddCache(ctx.Caches.AddCache("node-modules", NODE_MODULES_CACHE))
|
||||
|
||||
p.addFrameworkCaches(ctx, build, "next", func(pkg *WorkspacePackage, ctx *generate.GenerateContext) bool {
|
||||
if pkg.PackageJson.HasScript("build") {
|
||||
return strings.Contains(pkg.PackageJson.Scripts["build"], "next build")
|
||||
// Script-based Next apps (including monorepo packages with explicit next build)
|
||||
if pkg.PackageJson.BuildScriptContains("next build") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
// Nx and similar toolchains infer targets; detect via next dep / next.config
|
||||
return p.isNextAppPackage(pkg, ctx)
|
||||
}, ".next/cache")
|
||||
|
||||
p.addFrameworkCaches(ctx, build, "remix", func(pkg *WorkspacePackage, ctx *generate.GenerateContext) bool {
|
||||
|
||||
@@ -2,7 +2,6 @@ package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/railwayapp/railpack/core/app"
|
||||
@@ -175,6 +174,11 @@ func TestGetNextApps(t *testing.T) {
|
||||
path: "../../../examples/node-turborepo",
|
||||
want: []string{"apps/web"},
|
||||
},
|
||||
{
|
||||
name: "nx next workspace",
|
||||
path: "../../../examples/node-nx-next",
|
||||
want: []string{"apps/web"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -185,10 +189,10 @@ func TestGetNextApps(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
nextPackages, err := provider.getPackagesWithFramework(ctx, func(pkg *WorkspacePackage, ctx *generate.GenerateContext) bool {
|
||||
if pkg.PackageJson.HasScript("build") {
|
||||
return strings.Contains(pkg.PackageJson.Scripts["build"], "next build")
|
||||
if pkg.PackageJson.BuildScriptContains("next build") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return provider.isNextAppPackage(pkg, ctx)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/railwayapp/railpack/core/generate"
|
||||
)
|
||||
|
||||
// isNx reports whether this is an Nx workspace we can drive with nx CLI fallbacks.
|
||||
func (p *NodeProvider) isNx(ctx *generate.GenerateContext) bool {
|
||||
if !ctx.App.HasFile("nx.json") {
|
||||
return false
|
||||
}
|
||||
|
||||
if p.workspace != nil && p.workspace.HasDependency("nx") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// isNextAppPackage identifies a deployable Next.js package in a monorepo.
|
||||
// Root packages that only list next as a shared dependency (common in Nx scaffolds)
|
||||
// are excluded unless they have a next.config or a next build script.
|
||||
func (p *NodeProvider) isNextAppPackage(pkg *WorkspacePackage, ctx *generate.GenerateContext) bool {
|
||||
if pkg == nil || pkg.PackageJson == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if p.packageHasNextConfig(ctx, pkg.Path) {
|
||||
return true
|
||||
}
|
||||
|
||||
if !pkg.PackageJson.hasDependency("next") {
|
||||
return false
|
||||
}
|
||||
|
||||
if pkg.PackageJson.BuildScriptContains("next build") {
|
||||
return true
|
||||
}
|
||||
|
||||
// Workspace packages with next but no scripts (Nx inferred targets)
|
||||
return pkg.Path != ""
|
||||
}
|
||||
|
||||
func (p *NodeProvider) packageHasNextConfig(ctx *generate.GenerateContext, packagePath string) bool {
|
||||
for _, name := range nextConfigFiles {
|
||||
path := name
|
||||
if packagePath != "" {
|
||||
path = filepath.Join(packagePath, name)
|
||||
}
|
||||
if ctx.App.HasFile(path) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *NodeProvider) getNxNextPackages(ctx *generate.GenerateContext) []*WorkspacePackage {
|
||||
packages := []*WorkspacePackage{}
|
||||
if p.workspace == nil {
|
||||
return packages
|
||||
}
|
||||
|
||||
if p.isNextAppPackage(p.workspace.Root, ctx) {
|
||||
packages = append(packages, p.workspace.Root)
|
||||
}
|
||||
|
||||
for _, pkg := range p.workspace.Packages {
|
||||
if p.isNextAppPackage(pkg, ctx) {
|
||||
packages = append(packages, pkg)
|
||||
}
|
||||
}
|
||||
|
||||
return packages
|
||||
}
|
||||
|
||||
// nxProjectName returns the name Nx uses for a package (package.json name, or directory basename).
|
||||
func nxProjectName(pkg *WorkspacePackage) string {
|
||||
if pkg.PackageJson != nil && pkg.PackageJson.Name != "" {
|
||||
return pkg.PackageJson.Name
|
||||
}
|
||||
if pkg.Path != "" {
|
||||
return filepath.Base(pkg.Path)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func matchesNxAppSelector(pkg *WorkspacePackage, selector string) bool {
|
||||
if selector == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if pkg.PackageJson != nil && pkg.PackageJson.Name == selector {
|
||||
return true
|
||||
}
|
||||
|
||||
if pkg.Path == selector {
|
||||
return true
|
||||
}
|
||||
|
||||
if pkg.Path != "" && filepath.Base(pkg.Path) == selector {
|
||||
return true
|
||||
}
|
||||
|
||||
// "@scope/web" matches selector "web"
|
||||
if pkg.PackageJson != nil {
|
||||
name := pkg.PackageJson.Name
|
||||
if i := strings.LastIndex(name, "/"); i >= 0 && name[i+1:] == selector {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// resolveNxDeployPackage picks the Nx app to build/start when root scripts are absent.
|
||||
// Returns the workspace package, Nx project name, and whether a selection was made.
|
||||
func (p *NodeProvider) resolveNxDeployPackage(ctx *generate.GenerateContext) (*WorkspacePackage, string, bool) {
|
||||
if !p.isNx(ctx) {
|
||||
return nil, "", false
|
||||
}
|
||||
|
||||
packages := p.getNxNextPackages(ctx)
|
||||
if len(packages) == 0 {
|
||||
return nil, "", false
|
||||
}
|
||||
|
||||
if selector, _ := ctx.Env.GetConfigVariable("NX_APP"); selector != "" {
|
||||
for _, pkg := range packages {
|
||||
if matchesNxAppSelector(pkg, selector) {
|
||||
name := nxProjectName(pkg)
|
||||
ctx.Logger.LogInfo("Using Nx app %s (RAILPACK_NX_APP=%s)", name, selector)
|
||||
return pkg, name, true
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Logger.LogWarn("RAILPACK_NX_APP=%s did not match any Next.js app package", selector)
|
||||
return nil, "", false
|
||||
}
|
||||
|
||||
if len(packages) == 1 {
|
||||
pkg := packages[0]
|
||||
name := nxProjectName(pkg)
|
||||
ctx.Logger.LogInfo("Using Nx app %s", name)
|
||||
return pkg, name, true
|
||||
}
|
||||
|
||||
names := make([]string, 0, len(packages))
|
||||
for _, pkg := range packages {
|
||||
names = append(names, nxProjectName(pkg))
|
||||
}
|
||||
ctx.Logger.LogWarn("Multiple Next.js apps found in Nx workspace (%s). Set RAILPACK_NX_APP to choose one.", strings.Join(names, ", "))
|
||||
return nil, "", false
|
||||
}
|
||||
|
||||
// nxBuildCommand returns the shell command to build an Nx project.
|
||||
func nxBuildCommand(projectName string) string {
|
||||
return fmt.Sprintf("nx build %s", projectName)
|
||||
}
|
||||
|
||||
// nxNextStartCommand returns a production start command for a Next.js app package.
|
||||
func nxNextStartCommand(pkg *WorkspacePackage) string {
|
||||
if pkg.Path == "" {
|
||||
return DefaultNextStartCommand
|
||||
}
|
||||
return fmt.Sprintf("cd %s && %s", pkg.Path, DefaultNextStartCommand)
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/railwayapp/railpack/core/plan"
|
||||
testingUtils "github.com/railwayapp/railpack/core/testing"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIsNx(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "nx next workspace",
|
||||
path: "../../../examples/node-nx-next",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "plain next app",
|
||||
path: "../../../examples/node-next",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "turborepo",
|
||||
path: "../../../examples/node-turborepo",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := testingUtils.CreateGenerateContext(t, tt.path)
|
||||
provider := NodeProvider{}
|
||||
err := provider.Initialize(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.want, provider.isNx(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveNxDeployPackage(t *testing.T) {
|
||||
t.Run("single next app in nx workspace", func(t *testing.T) {
|
||||
ctx := testingUtils.CreateGenerateContext(t, "../../../examples/node-nx-next")
|
||||
provider := NodeProvider{}
|
||||
err := provider.Initialize(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
pkg, name, ok := provider.resolveNxDeployPackage(ctx)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "apps/web", pkg.Path)
|
||||
require.Equal(t, "@node-nx-next/web", name)
|
||||
})
|
||||
|
||||
t.Run("RAILPACK_NX_APP selects by short name", func(t *testing.T) {
|
||||
ctx := testingUtils.CreateGenerateContext(t, "../../../examples/node-nx-next")
|
||||
ctx.Env.Variables["RAILPACK_NX_APP"] = "web"
|
||||
provider := NodeProvider{}
|
||||
err := provider.Initialize(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
pkg, name, ok := provider.resolveNxDeployPackage(ctx)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "apps/web", pkg.Path)
|
||||
require.Equal(t, "@node-nx-next/web", name)
|
||||
})
|
||||
|
||||
t.Run("RAILPACK_NX_APP selects by package name", func(t *testing.T) {
|
||||
ctx := testingUtils.CreateGenerateContext(t, "../../../examples/node-nx-next")
|
||||
ctx.Env.Variables["RAILPACK_NX_APP"] = "@node-nx-next/web"
|
||||
provider := NodeProvider{}
|
||||
err := provider.Initialize(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
pkg, name, ok := provider.resolveNxDeployPackage(ctx)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "apps/web", pkg.Path)
|
||||
require.Equal(t, "@node-nx-next/web", name)
|
||||
})
|
||||
|
||||
t.Run("non-nx project", func(t *testing.T) {
|
||||
ctx := testingUtils.CreateGenerateContext(t, "../../../examples/node-next")
|
||||
provider := NodeProvider{}
|
||||
err := provider.Initialize(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, ok := provider.resolveNxDeployPackage(ctx)
|
||||
require.False(t, ok)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNxBuildAndStartCommands(t *testing.T) {
|
||||
ctx := testingUtils.CreateGenerateContext(t, "../../../examples/node-nx-next")
|
||||
provider := NodeProvider{}
|
||||
err := provider.Initialize(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "cd apps/web && next start", provider.GetStartCommand(ctx))
|
||||
|
||||
// Build step should use nx when root has no build script
|
||||
build := ctx.NewCommandStep("build")
|
||||
provider.Build(ctx, build)
|
||||
|
||||
var foundNxBuild bool
|
||||
for _, cmd := range build.Commands {
|
||||
if execCmd, ok := cmd.(plan.ExecCommand); ok && execCmd.Cmd == "nx build @node-nx-next/web" {
|
||||
foundNxBuild = true
|
||||
break
|
||||
}
|
||||
}
|
||||
require.True(t, foundNxBuild, "expected nx build command in build step")
|
||||
require.Equal(t, "1", build.Variables["NEXT_TELEMETRY_DISABLED"])
|
||||
}
|
||||
|
||||
func TestNxDoesNotOverrideRootScripts(t *testing.T) {
|
||||
ctx := testingUtils.CreateGenerateContext(t, "../../../examples/node-turborepo")
|
||||
provider := NodeProvider{}
|
||||
err := provider.Initialize(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// turborepo has root start/build scripts and is not Nx
|
||||
require.False(t, provider.isNx(ctx))
|
||||
require.Equal(t, "npm run start", provider.GetStartCommand(ctx))
|
||||
}
|
||||
|
||||
func TestMatchesNxAppSelector(t *testing.T) {
|
||||
pkg := &WorkspacePackage{
|
||||
Path: "apps/web",
|
||||
PackageJson: &PackageJson{
|
||||
Name: "@node-nx-next/web",
|
||||
},
|
||||
}
|
||||
|
||||
require.True(t, matchesNxAppSelector(pkg, "@node-nx-next/web"))
|
||||
require.True(t, matchesNxAppSelector(pkg, "apps/web"))
|
||||
require.True(t, matchesNxAppSelector(pkg, "web"))
|
||||
require.False(t, matchesNxAppSelector(pkg, "docs"))
|
||||
require.False(t, matchesNxAppSelector(pkg, ""))
|
||||
}
|
||||
@@ -42,6 +42,10 @@ func (p *PackageJson) GetScript(name string) string {
|
||||
return p.Scripts[name]
|
||||
}
|
||||
|
||||
func (p *PackageJson) BuildScriptContains(value string) bool {
|
||||
return strings.Contains(p.GetScript("build"), value)
|
||||
}
|
||||
|
||||
func (p *PackageJson) hasDependency(dependency string) bool {
|
||||
if p.Dependencies != nil {
|
||||
if _, ok := p.Dependencies[dependency]; ok {
|
||||
|
||||
@@ -12,6 +12,7 @@ func TestPackageJson(t *testing.T) {
|
||||
packageJson := NewPackageJson()
|
||||
assert.Equal(t, packageJson.HasScript("start"), false)
|
||||
assert.Equal(t, packageJson.GetScript("build"), "")
|
||||
assert.False(t, packageJson.BuildScriptContains("next build"))
|
||||
assert.Equal(t, packageJson.hasDependency("react"), false)
|
||||
})
|
||||
|
||||
@@ -53,6 +54,8 @@ func TestPackageJson(t *testing.T) {
|
||||
assert.True(t, packageJson.HasScript("start"))
|
||||
assert.Equal(t, "next start", packageJson.GetScript("start"))
|
||||
assert.Equal(t, "next build", packageJson.GetScript("build"))
|
||||
assert.True(t, packageJson.BuildScriptContains("next build"))
|
||||
assert.False(t, packageJson.BuildScriptContains("vite build"))
|
||||
assert.Equal(t, "next dev", packageJson.GetScript("dev"))
|
||||
assert.False(t, packageJson.HasScript("test"))
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ Railpack determines the start command in the following order:
|
||||
| `RAILPACK_NODE_PRUNE_CMD` | Custom command to prune dependencies | `npm prune --omit=dev --ignore-scripts` |
|
||||
| `RAILPACK_NODE_INSTALL_PATTERNS` | Custom patterns to install dependencies | `prisma` |
|
||||
| `RAILPACK_ANGULAR_PROJECT` | Name of the Angular project to build | `my-app` |
|
||||
| `RAILPACK_NX_APP` | Nx app to build and start (project name, package name, or path) | `web` or `@org/web` |
|
||||
|
||||
### Package Managers
|
||||
|
||||
@@ -124,18 +125,20 @@ Railpack supports building native modules and automatically configures `node-gyp
|
||||
### Monorepo Support
|
||||
|
||||
Railpack automatically supports monorepo (workspaces) configurations with all major
|
||||
package managers. No special configuration is required.
|
||||
package managers. No special configuration is required.
|
||||
|
||||
**Supported Approaches:**
|
||||
|
||||
- **npm, bun, yarn**: Uses the `workspaces` field in `package.json`
|
||||
- **pnpm**: Uses `pnpm-workspace.yaml` configuration
|
||||
- **Nx**: Detects `nx.json` and builds Next.js apps even when targets are
|
||||
inferred (no root `build`/`start` scripts)
|
||||
|
||||
See the [examples
|
||||
folder](https://github.com/railwayapp/railpack/tree/main/examples) in the
|
||||
repository for workspace examples across different package managers (e.g.,
|
||||
`node-pnpm-workspaces`, `node-npm-workspaces`, `node-yarn-workspaces`,
|
||||
`node-bun-workspaces`).
|
||||
`node-bun-workspaces`, `node-nx-next`).
|
||||
|
||||
When building a monorepo, Railpack will:
|
||||
|
||||
@@ -145,8 +148,24 @@ When building a monorepo, Railpack will:
|
||||
- Cache workspace node_modules appropriately
|
||||
|
||||
If your monorepo requires building a specific workspace package, ensure
|
||||
your build and start scripts are defined in the root `package.json` or use
|
||||
a [config file](/architecture/user-config) to specify custom commands.
|
||||
your build and start scripts are defined in the root `package.json`, set
|
||||
`RAILPACK_NX_APP` for multi-app Nx workspaces, or use a
|
||||
[config file](/architecture/user-config) to specify custom commands.
|
||||
|
||||
#### Nx
|
||||
|
||||
Stock Nx workspaces often rely on [inferred
|
||||
tasks](https://nx.dev/docs/concepts/inferred-tasks) instead of `package.json`
|
||||
scripts. When Railpack detects Nx and the root has no `build`/`start` scripts:
|
||||
|
||||
- **Build**: `nx build <project>` (uses the package name, e.g.
|
||||
`@org/web`)
|
||||
- **Start** (Next.js): `cd apps/web && next start` so runtime does not depend
|
||||
on the `nx` CLI
|
||||
|
||||
With a single Next.js app, selection is automatic. With multiple apps, set
|
||||
`RAILPACK_NX_APP` to the project name, package name, or package path (e.g.
|
||||
`web`, `@org/web`, or `apps/web`).
|
||||
|
||||
### Install
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Editor configuration, see http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
@@ -0,0 +1,49 @@
|
||||
# See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
dist
|
||||
tmp
|
||||
out-tsc
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
.nx/cache
|
||||
.nx/workspace-data
|
||||
.nx/migrate-runs
|
||||
.claude/worktrees
|
||||
.claude/settings.local.json
|
||||
|
||||
# Next.js
|
||||
.next
|
||||
out
|
||||
@@ -0,0 +1,2 @@
|
||||
strict-peer-dependencies=false
|
||||
auto-install-peers=true
|
||||
@@ -0,0 +1,5 @@
|
||||
# Add files here to ignore them from prettier formatting
|
||||
/dist
|
||||
/coverage
|
||||
/.nx/cache
|
||||
/.nx/workspace-data
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"singleQuote": true
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
# NodeNxNext
|
||||
|
||||
<a alt="Nx logo" href="https://nx.dev" target="_blank" rel="noreferrer"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo.png" width="45"></a>
|
||||
|
||||
✨ Your new, shiny [Nx workspace](https://nx.dev) is ready ✨.
|
||||
|
||||
[Learn more about this workspace setup and its capabilities](https://nx.dev/nx-api/next?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or run `npx nx graph` to visually explore what was created. Now, let's get you up to speed!
|
||||
|
||||
## Run tasks
|
||||
|
||||
To run the dev server for your app, use:
|
||||
|
||||
```sh
|
||||
npx nx dev web
|
||||
```
|
||||
|
||||
To create a production bundle:
|
||||
|
||||
```sh
|
||||
npx nx build web
|
||||
```
|
||||
|
||||
To see all available targets to run for a project, run:
|
||||
|
||||
```sh
|
||||
npx nx show project web
|
||||
```
|
||||
|
||||
These targets are either [inferred automatically](https://nx.dev/concepts/inferred-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or defined in the `project.json` or `package.json` files.
|
||||
|
||||
[More about running tasks in the docs »](https://nx.dev/features/run-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
|
||||
## Add new projects
|
||||
|
||||
While you could add new projects to your workspace manually, you might want to leverage [Nx plugins](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) and their [code generation](https://nx.dev/features/generate-code?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) feature.
|
||||
|
||||
Use the plugin's generator to create new projects.
|
||||
|
||||
To generate a new application, use:
|
||||
|
||||
```sh
|
||||
npx nx g @nx/next:app demo
|
||||
```
|
||||
|
||||
To generate a new library, use:
|
||||
|
||||
```sh
|
||||
npx nx g @nx/react:lib mylib
|
||||
```
|
||||
|
||||
You can use `npx nx list` to get a list of installed plugins. Then, run `npx nx list <plugin-name>` to learn about more specific capabilities of a particular plugin. Alternatively, [install Nx Console](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) to browse plugins and generators in your IDE.
|
||||
|
||||
[Learn more about Nx plugins »](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) | [Browse the plugin registry »](https://nx.dev/plugin-registry?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
|
||||
## Set up CI!
|
||||
|
||||
### Step 1
|
||||
|
||||
To connect to Nx Cloud, run the following command:
|
||||
|
||||
```sh
|
||||
npx nx connect
|
||||
```
|
||||
|
||||
Connecting to Nx Cloud ensures a [fast and scalable CI](https://nx.dev/ci/intro/why-nx-cloud?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) pipeline. It includes features such as:
|
||||
|
||||
- [Remote caching](https://nx.dev/ci/features/remote-cache?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
- [Task distribution across multiple machines](https://nx.dev/ci/features/distribute-task-execution?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
- [Automated e2e test splitting](https://nx.dev/ci/features/split-e2e-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
- [Task flakiness detection and rerunning](https://nx.dev/ci/features/flaky-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
|
||||
### Step 2
|
||||
|
||||
Use the following command to configure a CI workflow for your workspace:
|
||||
|
||||
```sh
|
||||
npx nx g ci-workflow
|
||||
```
|
||||
|
||||
[Learn more about Nx on CI](https://nx.dev/ci/intro/ci-with-nx#ready-get-started-with-your-provider?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
|
||||
## Install Nx Console
|
||||
|
||||
Nx Console is an editor extension that enriches your developer experience. It lets you run tasks, generate code, and improves code autocompletion in your IDE. It is available for VSCode and IntelliJ.
|
||||
|
||||
[Install Nx Console »](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
|
||||
## Useful links
|
||||
|
||||
Learn more:
|
||||
|
||||
- [Learn more about this workspace setup](https://nx.dev/nx-api/next?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
- [Learn about Nx on CI](https://nx.dev/ci/intro/ci-with-nx?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
- [Releasing Packages with Nx release](https://nx.dev/features/manage-releases?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
- [What are Nx plugins?](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
|
||||
And join the Nx community:
|
||||
- [Discord](https://go.nx.dev/community)
|
||||
- [Follow us on X](https://twitter.com/nxdevtools) or [LinkedIn](https://www.linkedin.com/company/nrwl)
|
||||
- [Our Youtube channel](https://www.youtube.com/@nxdevtools)
|
||||
- [Our blog](https://nx.dev/blog?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"jsc": {
|
||||
"target": "es2017",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"decorators": true,
|
||||
"dynamicImport": true
|
||||
},
|
||||
"transform": {
|
||||
"decoratorMetadata": true,
|
||||
"legacyDecorator": true
|
||||
},
|
||||
"keepClassNames": true,
|
||||
"externalHelpers": true,
|
||||
"loose": true
|
||||
},
|
||||
"module": {
|
||||
"type": "commonjs"
|
||||
},
|
||||
"sourceMaps": true,
|
||||
"exclude": [
|
||||
"jest.config.[ct]s",
|
||||
".*\\.spec.tsx?$",
|
||||
".*\\.test.tsx?$",
|
||||
"./src/jest-setup.ts$",
|
||||
"./**/jest-setup.ts$",
|
||||
".*.js$",
|
||||
".*.d.ts$"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import nextEslintPluginNext from '@next/eslint-plugin-next';
|
||||
import nx from '@nx/eslint-plugin';
|
||||
import baseConfig from '../../eslint.config.mjs';
|
||||
|
||||
export default [
|
||||
{ plugins: { '@next/next': nextEslintPluginNext } },
|
||||
...nx.configs['flat/react-typescript'],
|
||||
...baseConfig,
|
||||
{
|
||||
ignores: ['.next/**/*', '**/out-tsc'],
|
||||
},
|
||||
];
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
declare module '*.svg' {
|
||||
const content: any;
|
||||
export const ReactComponent: any;
|
||||
export default content;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
@@ -0,0 +1,9 @@
|
||||
//@ts-check
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
// Next.js options go here
|
||||
// See: https://nextjs.org/docs/app/api-reference/config/next-config-js
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "@node-nx-next/web",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"next": "~16.1.6",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,3 @@
|
||||
export async function GET(request: Request) {
|
||||
return new Response('Hello, from API!');
|
||||
}
|
||||
@@ -0,0 +1,501 @@
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
font-family:
|
||||
ui-sans-serif,
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
Segoe UI,
|
||||
Roboto,
|
||||
Helvetica Neue,
|
||||
Arial,
|
||||
Noto Sans,
|
||||
sans-serif,
|
||||
Apple Color Emoji,
|
||||
Segoe UI Emoji,
|
||||
Segoe UI Symbol,
|
||||
Noto Color Emoji;
|
||||
line-height: 1.5;
|
||||
tab-size: 4;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
body {
|
||||
font-family: inherit;
|
||||
line-height: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
p,
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
box-sizing: border-box;
|
||||
border-width: 0;
|
||||
border-style: solid;
|
||||
border-color: currentColor;
|
||||
}
|
||||
h1,
|
||||
h2 {
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
pre {
|
||||
font-family:
|
||||
ui-monospace,
|
||||
SFMono-Regular,
|
||||
Menlo,
|
||||
Monaco,
|
||||
Consolas,
|
||||
Liberation Mono,
|
||||
Courier New,
|
||||
monospace;
|
||||
}
|
||||
svg {
|
||||
display: block;
|
||||
vertical-align: middle;
|
||||
shape-rendering: auto;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
pre {
|
||||
background-color: rgba(55, 65, 81, 1);
|
||||
border-radius: 0.25rem;
|
||||
color: rgba(229, 231, 235, 1);
|
||||
font-family:
|
||||
ui-monospace,
|
||||
SFMono-Regular,
|
||||
Menlo,
|
||||
Monaco,
|
||||
Consolas,
|
||||
Liberation Mono,
|
||||
Courier New,
|
||||
monospace;
|
||||
overflow: scroll;
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
box-shadow:
|
||||
0 0 #0000,
|
||||
0 0 #0000,
|
||||
0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
||||
0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.rounded {
|
||||
border-radius: 1.5rem;
|
||||
}
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
.container {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 768px;
|
||||
padding-bottom: 3rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
color: rgba(55, 65, 81, 1);
|
||||
width: 100%;
|
||||
}
|
||||
#welcome {
|
||||
margin-top: 2.5rem;
|
||||
}
|
||||
#welcome h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.025em;
|
||||
line-height: 1;
|
||||
}
|
||||
#welcome span {
|
||||
display: block;
|
||||
font-size: 1.875rem;
|
||||
font-weight: 300;
|
||||
line-height: 2.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
#hero {
|
||||
align-items: center;
|
||||
background-color: hsla(214, 62%, 21%, 1);
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
color: rgba(55, 65, 81, 1);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
margin-top: 3.5rem;
|
||||
}
|
||||
#hero .text-container {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
#hero .text-container h2 {
|
||||
font-size: 1.5rem;
|
||||
line-height: 2rem;
|
||||
position: relative;
|
||||
}
|
||||
#hero .text-container h2 svg {
|
||||
color: hsla(162, 47%, 50%, 1);
|
||||
height: 2rem;
|
||||
left: -0.25rem;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 2rem;
|
||||
}
|
||||
#hero .text-container h2 span {
|
||||
margin-left: 2.5rem;
|
||||
}
|
||||
#hero .text-container a {
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
border-radius: 0.75rem;
|
||||
color: rgba(55, 65, 81, 1);
|
||||
display: inline-block;
|
||||
margin-top: 1.5rem;
|
||||
padding: 1rem 2rem;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
#hero .logo-container {
|
||||
display: none;
|
||||
justify-content: center;
|
||||
padding-left: 2rem;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
#hero .logo-container svg {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
width: 66.666667%;
|
||||
}
|
||||
#middle-content {
|
||||
align-items: flex-start;
|
||||
display: grid;
|
||||
gap: 4rem;
|
||||
grid-template-columns: 1fr;
|
||||
margin-top: 3.5rem;
|
||||
}
|
||||
#learning-materials {
|
||||
padding: 2.5rem 2rem;
|
||||
}
|
||||
#learning-materials h2 {
|
||||
font-weight: 500;
|
||||
font-size: 1.25rem;
|
||||
letter-spacing: -0.025em;
|
||||
line-height: 1.75rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
.list-item-link {
|
||||
align-items: center;
|
||||
border-radius: 0.75rem;
|
||||
display: flex;
|
||||
margin-top: 1rem;
|
||||
padding: 1rem;
|
||||
transition-property:
|
||||
background-color,
|
||||
border-color,
|
||||
color,
|
||||
fill,
|
||||
stroke,
|
||||
opacity,
|
||||
box-shadow,
|
||||
transform,
|
||||
filter,
|
||||
backdrop-filter,
|
||||
-webkit-backdrop-filter;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
width: 100%;
|
||||
}
|
||||
.list-item-link svg:first-child {
|
||||
margin-right: 1rem;
|
||||
height: 1.5rem;
|
||||
transition-property:
|
||||
background-color,
|
||||
border-color,
|
||||
color,
|
||||
fill,
|
||||
stroke,
|
||||
opacity,
|
||||
box-shadow,
|
||||
transform,
|
||||
filter,
|
||||
backdrop-filter,
|
||||
-webkit-backdrop-filter;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
width: 1.5rem;
|
||||
}
|
||||
.list-item-link > span {
|
||||
flex-grow: 1;
|
||||
font-weight: 400;
|
||||
transition-property:
|
||||
background-color,
|
||||
border-color,
|
||||
color,
|
||||
fill,
|
||||
stroke,
|
||||
opacity,
|
||||
box-shadow,
|
||||
transform,
|
||||
filter,
|
||||
backdrop-filter,
|
||||
-webkit-backdrop-filter;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
.list-item-link > span > span {
|
||||
color: rgba(107, 114, 128, 1);
|
||||
display: block;
|
||||
flex-grow: 1;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 300;
|
||||
line-height: 1rem;
|
||||
transition-property:
|
||||
background-color,
|
||||
border-color,
|
||||
color,
|
||||
fill,
|
||||
stroke,
|
||||
opacity,
|
||||
box-shadow,
|
||||
transform,
|
||||
filter,
|
||||
backdrop-filter,
|
||||
-webkit-backdrop-filter;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
.list-item-link svg:last-child {
|
||||
height: 1rem;
|
||||
transition-property: all;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
width: 1rem;
|
||||
}
|
||||
.list-item-link:hover {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
background-color: hsla(162, 47%, 50%, 1);
|
||||
}
|
||||
.list-item-link:hover > span {
|
||||
}
|
||||
.list-item-link:hover > span > span {
|
||||
color: rgba(243, 244, 246, 1);
|
||||
}
|
||||
.list-item-link:hover svg:last-child {
|
||||
transform: translateX(0.25rem);
|
||||
}
|
||||
#other-links {
|
||||
}
|
||||
.button-pill {
|
||||
padding: 1.5rem 2rem;
|
||||
transition-duration: 300ms;
|
||||
transition-property:
|
||||
background-color,
|
||||
border-color,
|
||||
color,
|
||||
fill,
|
||||
stroke,
|
||||
opacity,
|
||||
box-shadow,
|
||||
transform,
|
||||
filter,
|
||||
backdrop-filter,
|
||||
-webkit-backdrop-filter;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
.button-pill svg {
|
||||
transition-property:
|
||||
background-color,
|
||||
border-color,
|
||||
color,
|
||||
fill,
|
||||
stroke,
|
||||
opacity,
|
||||
box-shadow,
|
||||
transform,
|
||||
filter,
|
||||
backdrop-filter,
|
||||
-webkit-backdrop-filter;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
flex-shrink: 0;
|
||||
width: 3rem;
|
||||
}
|
||||
.button-pill > span {
|
||||
letter-spacing: -0.025em;
|
||||
font-weight: 400;
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.75rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
.button-pill span span {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 300;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
.button-pill:hover svg,
|
||||
.button-pill:hover {
|
||||
color: rgba(255, 255, 255, 1) !important;
|
||||
}
|
||||
#nx-console:hover {
|
||||
background-color: rgba(0, 122, 204, 1);
|
||||
}
|
||||
#nx-console svg {
|
||||
color: rgba(0, 122, 204, 1);
|
||||
}
|
||||
#nx-console-jetbrains {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
#nx-console-jetbrains:hover {
|
||||
background-color: rgba(255, 49, 140, 1);
|
||||
}
|
||||
#nx-console-jetbrains svg {
|
||||
color: rgba(255, 49, 140, 1);
|
||||
}
|
||||
#nx-repo:hover {
|
||||
background-color: rgba(24, 23, 23, 1);
|
||||
}
|
||||
#nx-repo svg {
|
||||
color: rgba(24, 23, 23, 1);
|
||||
}
|
||||
#nx-cloud {
|
||||
margin-bottom: 2rem;
|
||||
margin-top: 2rem;
|
||||
padding: 2.5rem 2rem;
|
||||
}
|
||||
#nx-cloud > div {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
#nx-cloud > div svg {
|
||||
border-radius: 0.375rem;
|
||||
flex-shrink: 0;
|
||||
width: 3rem;
|
||||
}
|
||||
#nx-cloud > div h2 {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 400;
|
||||
letter-spacing: -0.025em;
|
||||
line-height: 1.75rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
#nx-cloud > div h2 span {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 300;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
#nx-cloud p {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
#nx-cloud pre {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
#nx-cloud a {
|
||||
color: rgba(107, 114, 128, 1);
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
margin-top: 1.5rem;
|
||||
text-align: right;
|
||||
}
|
||||
#nx-cloud a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
#commands {
|
||||
padding: 2.5rem 2rem;
|
||||
margin-top: 3.5rem;
|
||||
}
|
||||
#commands h2 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 400;
|
||||
letter-spacing: -0.025em;
|
||||
line-height: 1.75rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
#commands p {
|
||||
font-size: 1rem;
|
||||
font-weight: 300;
|
||||
line-height: 1.5rem;
|
||||
margin-top: 1rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
details {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-top: 1rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
details pre > span {
|
||||
color: rgba(181, 181, 181, 1);
|
||||
display: block;
|
||||
}
|
||||
summary {
|
||||
border-radius: 0.5rem;
|
||||
display: flex;
|
||||
font-weight: 400;
|
||||
padding: 0.5rem;
|
||||
cursor: pointer;
|
||||
transition-property:
|
||||
background-color,
|
||||
border-color,
|
||||
color,
|
||||
fill,
|
||||
stroke,
|
||||
opacity,
|
||||
box-shadow,
|
||||
transform,
|
||||
filter,
|
||||
backdrop-filter,
|
||||
-webkit-backdrop-filter;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
summary:hover {
|
||||
background-color: rgba(243, 244, 246, 1);
|
||||
}
|
||||
summary svg {
|
||||
height: 1.5rem;
|
||||
margin-right: 1rem;
|
||||
width: 1.5rem;
|
||||
}
|
||||
#love {
|
||||
color: rgba(107, 114, 128, 1);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
margin-top: 3.5rem;
|
||||
opacity: 0.6;
|
||||
text-align: center;
|
||||
}
|
||||
#love svg {
|
||||
color: rgba(252, 165, 165, 1);
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
display: inline;
|
||||
margin-top: -0.25rem;
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
#hero {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
#hero .logo-container {
|
||||
display: flex;
|
||||
}
|
||||
#middle-content {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import './global.css';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Welcome to ',
|
||||
description: 'Generated by create-nx-workspace',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
.page {
|
||||
}
|
||||
@@ -0,0 +1,469 @@
|
||||
import styles from './page.module.css';
|
||||
|
||||
export default function Index() {
|
||||
/*
|
||||
* Replace the elements below with your own.
|
||||
*
|
||||
* Note: The corresponding styles are in the ./index.css file.
|
||||
*/
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<div className="wrapper">
|
||||
<div className="container">
|
||||
<div id="welcome">
|
||||
<h1>
|
||||
<span> Hello there, </span>
|
||||
Welcome @node-nx-next/web 👋
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div id="hero" className="rounded">
|
||||
<div className="text-container">
|
||||
<h2>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"
|
||||
/>
|
||||
</svg>
|
||||
<span>You're up and running</span>
|
||||
</h2>
|
||||
<a href="#commands"> What's next? </a>
|
||||
</div>
|
||||
<div className="logo-container">
|
||||
<svg
|
||||
fill="currentColor"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M11.987 14.138l-3.132 4.923-5.193-8.427-.012 8.822H0V4.544h3.691l5.247 8.833.005-3.998 3.044 4.759zm.601-5.761c.024-.048 0-3.784.008-3.833h-3.65c.002.059-.005 3.776-.003 3.833h3.645zm5.634 4.134a2.061 2.061 0 0 0-1.969 1.336 1.963 1.963 0 0 1 2.343-.739c.396.161.917.422 1.33.283a2.1 2.1 0 0 0-1.704-.88zm3.39 1.061c-.375-.13-.8-.277-1.109-.681-.06-.08-.116-.17-.176-.265a2.143 2.143 0 0 0-.533-.642c-.294-.216-.68-.322-1.18-.322a2.482 2.482 0 0 0-2.294 1.536 2.325 2.325 0 0 1 4.002.388.75.75 0 0 0 .836.334c.493-.105.46.36 1.203.518v-.133c-.003-.446-.246-.55-.75-.733zm2.024 1.266a.723.723 0 0 0 .347-.638c-.01-2.957-2.41-5.487-5.37-5.487a5.364 5.364 0 0 0-4.487 2.418c-.01-.026-1.522-2.39-1.538-2.418H8.943l3.463 5.423-3.379 5.32h3.54l1.54-2.366 1.568 2.366h3.541l-3.21-5.052a.7.7 0 0 1-.084-.32 2.69 2.69 0 0 1 2.69-2.691h.001c1.488 0 1.736.89 2.057 1.308.634.826 1.9.464 1.9 1.541a.707.707 0 0 0 1.066.596zm.35.133c-.173.372-.56.338-.755.639-.176.271.114.412.114.412s.337.156.538-.311c.104-.231.14-.488.103-.74z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="middle-content">
|
||||
<div id="learning-materials" className="rounded shadow">
|
||||
<h2>Learning materials</h2>
|
||||
<a
|
||||
href="https://nx.dev/getting-started/intro?utm_source=nx-project"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="list-item-link"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
Documentation
|
||||
<span> Everything is in there </span>
|
||||
</span>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="https://nx.dev/blog/?utm_source=nx-project"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="list-item-link"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
Blog
|
||||
<span> Changelog, features & events </span>
|
||||
</span>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.youtube.com/@NxDevtools/videos?utm_source=nx-project&sub_confirmation=1"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="list-item-link"
|
||||
>
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>YouTube</title>
|
||||
<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" />
|
||||
</svg>
|
||||
<span>
|
||||
YouTube channel
|
||||
<span> Nx Show, talks & tutorials </span>
|
||||
</span>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="https://nx.dev/react-tutorial/1-code-generation?utm_source=nx-project"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="list-item-link"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5M7.188 2.239l.777 2.897M5.136 7.965l-2.898-.777M13.95 4.05l-2.122 2.122m-5.657 5.656l-2.12 2.122"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
Interactive tutorials
|
||||
<span> Create an app, step-by-step </span>
|
||||
</span>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="https://nxplaybook.com/?utm_source=nx-project"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="list-item-link"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M12 14l9-5-9-5-9 5 9 5z" />
|
||||
<path d="M12 14l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14z" />
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M12 14l9-5-9-5-9 5 9 5zm0 0l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14zm-4 6v-7.5l4-2.222"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
Video courses
|
||||
<span> Nx custom courses </span>
|
||||
</span>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div id="other-links">
|
||||
<a
|
||||
id="nx-console"
|
||||
className="button-pill rounded shadow"
|
||||
href="https://marketplace.visualstudio.com/items?itemName=nrwl.angular-console&utm_source=nx-project"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>Visual Studio Code</title>
|
||||
<path d="M23.15 2.587L18.21.21a1.494 1.494 0 0 0-1.705.29l-9.46 8.63-4.12-3.128a.999.999 0 0 0-1.276.057L.327 7.261A1 1 0 0 0 .326 8.74L3.899 12 .326 15.26a1 1 0 0 0 .001 1.479L1.65 17.94a.999.999 0 0 0 1.276.057l4.12-3.128 9.46 8.63a1.492 1.492 0 0 0 1.704.29l4.942-2.377A1.5 1.5 0 0 0 24 20.06V3.939a1.5 1.5 0 0 0-.85-1.352zm-5.146 14.861L10.826 12l7.178-5.448v10.896z" />
|
||||
</svg>
|
||||
<span>
|
||||
Install Nx Console for VSCode
|
||||
<span>The official VSCode extension for Nx.</span>
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
id="nx-console-jetbrains"
|
||||
className="button-pill rounded shadow"
|
||||
href="https://plugins.jetbrains.com/plugin/21060-nx-console"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<svg
|
||||
height="48"
|
||||
width="48"
|
||||
viewBox="20 20 60 60"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="m22.5 22.5h60v60h-60z" />
|
||||
<g fill="#fff">
|
||||
<path d="m29.03 71.25h22.5v3.75h-22.5z" />
|
||||
<path d="m28.09 38 1.67-1.58a1.88 1.88 0 0 0 1.47.87c.64 0 1.06-.44 1.06-1.31v-5.98h2.58v6a3.48 3.48 0 0 1 -.87 2.6 3.56 3.56 0 0 1 -2.57.95 3.84 3.84 0 0 1 -3.34-1.55z" />
|
||||
<path d="m36 30h7.53v2.19h-5v1.44h4.49v2h-4.42v1.49h5v2.21h-7.6z" />
|
||||
<path d="m47.23 32.29h-2.8v-2.29h8.21v2.27h-2.81v7.1h-2.6z" />
|
||||
<path d="m29.13 43.08h4.42a3.53 3.53 0 0 1 2.55.83 2.09 2.09 0 0 1 .6 1.53 2.16 2.16 0 0 1 -1.44 2.09 2.27 2.27 0 0 1 1.86 2.29c0 1.61-1.31 2.59-3.55 2.59h-4.44zm5 2.89c0-.52-.42-.8-1.18-.8h-1.29v1.64h1.24c.79 0 1.25-.26 1.25-.81zm-.9 2.66h-1.57v1.73h1.62c.8 0 1.24-.31 1.24-.86 0-.5-.4-.87-1.27-.87z" />
|
||||
<path d="m38 43.08h4.1a4.19 4.19 0 0 1 3 1 2.93 2.93 0 0 1 .9 2.19 3 3 0 0 1 -1.93 2.89l2.24 3.27h-3l-1.88-2.84h-.87v2.84h-2.56zm4 4.5c.87 0 1.39-.43 1.39-1.11 0-.75-.54-1.12-1.4-1.12h-1.44v2.26z" />
|
||||
<path d="m49.59 43h2.5l4 9.44h-2.79l-.67-1.69h-3.63l-.67 1.69h-2.71zm2.27 5.73-1-2.65-1.06 2.65z" />
|
||||
<path d="m56.46 43.05h2.6v9.37h-2.6z" />
|
||||
<path d="m60.06 43.05h2.42l3.37 5v-5h2.57v9.37h-2.26l-3.53-5.14v5.14h-2.57z" />
|
||||
<path d="m68.86 51 1.45-1.73a4.84 4.84 0 0 0 3 1.13c.71 0 1.08-.24 1.08-.65 0-.4-.31-.6-1.59-.91-2-.46-3.53-1-3.53-2.93 0-1.74 1.37-3 3.62-3a5.89 5.89 0 0 1 3.86 1.25l-1.26 1.84a4.63 4.63 0 0 0 -2.62-.92c-.63 0-.94.25-.94.6 0 .42.32.61 1.63.91 2.14.46 3.44 1.16 3.44 2.91 0 1.91-1.51 3-3.79 3a6.58 6.58 0 0 1 -4.35-1.5z" />
|
||||
</g>
|
||||
</svg>
|
||||
<span>
|
||||
Install Nx Console for JetBrains
|
||||
<span>
|
||||
Available for WebStorm, Intellij IDEA Ultimate and more!
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
<div id="nx-cloud" className="rounded shadow">
|
||||
<div>
|
||||
<svg
|
||||
id="nx-cloud-logo"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
stroke="currentColor"
|
||||
fill="transparent"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeWidth="2"
|
||||
d="M23 3.75V6.5c-3.036 0-5.5 2.464-5.5 5.5s-2.464 5.5-5.5 5.5-5.5 2.464-5.5 5.5H3.75C2.232 23 1 21.768 1 20.25V3.75C1 2.232 2.232 1 3.75 1h16.5C21.768 1 23 2.232 23 3.75Z"
|
||||
/>
|
||||
<path
|
||||
strokeWidth="2"
|
||||
d="M23 6v14.1667C23 21.7307 21.7307 23 20.1667 23H6c0-3.128 2.53867-5.6667 5.6667-5.6667 3.128 0 5.6666-2.5386 5.6666-5.6666C17.3333 8.53867 19.872 6 23 6Z"
|
||||
/>
|
||||
</svg>
|
||||
<h2>
|
||||
Nx Cloud
|
||||
<span>Enable faster CI & better DX</span>
|
||||
</h2>
|
||||
</div>
|
||||
<p>
|
||||
You can activate distributed tasks executions and caching by
|
||||
running:
|
||||
</p>
|
||||
<pre>nx connect</pre>
|
||||
<a
|
||||
href="https://nx.app/?utm_source=nx-project"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{' '}
|
||||
What is Nx Cloud?{' '}
|
||||
</a>
|
||||
</div>
|
||||
<a
|
||||
id="nx-repo"
|
||||
className="button-pill rounded shadow"
|
||||
href="https://github.com/nrwl/nx?utm_source=nx-project"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
|
||||
</svg>
|
||||
<span>
|
||||
Nx is open source
|
||||
<span> Love Nx? Give us a star! </span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="commands" className="rounded shadow">
|
||||
<h2>Next steps</h2>
|
||||
<p>Here are some things you can do with Nx:</p>
|
||||
<details>
|
||||
<summary>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
Add UI library
|
||||
</summary>
|
||||
<pre>
|
||||
<span># Generate UI lib</span>
|
||||
nx g @nx/next:library ui
|
||||
<span># Add a component</span>
|
||||
nx g @nx/next:component ui/src/lib/button
|
||||
</pre>
|
||||
</details>
|
||||
<details>
|
||||
<summary>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
View project details
|
||||
</summary>
|
||||
<pre>nx show project @node-nx-next/web --web</pre>
|
||||
</details>
|
||||
<details>
|
||||
<summary>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
View interactive project graph
|
||||
</summary>
|
||||
<pre>nx graph</pre>
|
||||
</details>
|
||||
<details>
|
||||
<summary>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
Run affected commands
|
||||
</summary>
|
||||
<pre>
|
||||
<span># see what's been affected by changes</span>
|
||||
nx affected:graph
|
||||
<span># run tests for current changes</span>
|
||||
nx affected:test
|
||||
<span># run e2e tests for current changes</span>
|
||||
nx affected:e2e
|
||||
</pre>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<p id="love">
|
||||
Carefully crafted with
|
||||
<svg
|
||||
fill="currentColor"
|
||||
stroke="none"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
|
||||
/>
|
||||
</svg>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "preserve",
|
||||
"noEmit": true,
|
||||
"emitDeclarationOnly": false,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"../../apps/web/.next/types/**/*.ts",
|
||||
"../../dist/apps/web/.next/types/**/*.ts",
|
||||
"next-env.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"out-tsc",
|
||||
"dist",
|
||||
"node_modules",
|
||||
"jest.config.ts",
|
||||
"jest.config.cts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.ts",
|
||||
".next",
|
||||
"eslint.config.js",
|
||||
"eslint.config.cjs",
|
||||
"eslint.config.mjs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import nx from '@nx/eslint-plugin';
|
||||
|
||||
export default [
|
||||
...nx.configs['flat/base'],
|
||||
...nx.configs['flat/typescript'],
|
||||
...nx.configs['flat/javascript'],
|
||||
{
|
||||
ignores: ['**/dist', '**/out-tsc'],
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||
rules: {
|
||||
'@nx/enforce-module-boundaries': [
|
||||
'error',
|
||||
{
|
||||
enforceBuildableLibDependency: true,
|
||||
allow: ['^.*/eslint(\\.base)?\\.config\\.[cm]?[jt]s$'],
|
||||
depConstraints: [
|
||||
{
|
||||
sourceTag: '*',
|
||||
onlyDependOnLibsWithTags: ['*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'**/*.ts',
|
||||
'**/*.tsx',
|
||||
'**/*.cts',
|
||||
'**/*.mts',
|
||||
'**/*.js',
|
||||
'**/*.jsx',
|
||||
'**/*.cjs',
|
||||
'**/*.mjs',
|
||||
],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
];
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env zsh
|
||||
|
||||
root="${0:A:h}"
|
||||
scratch="$root/tmp/node-nx-next"
|
||||
|
||||
rm -rf "$scratch"
|
||||
mkdir -p "$scratch"
|
||||
|
||||
(
|
||||
cd "$scratch"
|
||||
pnpm dlx create-nx-workspace@latest . \
|
||||
--preset=next \
|
||||
--workspaces \
|
||||
--appName=web \
|
||||
--nextAppDir=true \
|
||||
--pm=pnpm \
|
||||
--nxCloud=skip \
|
||||
--interactive=false \
|
||||
--skipGit \
|
||||
--e2eTestRunner=none \
|
||||
--unitTestRunner=none
|
||||
)
|
||||
|
||||
# create-nx-workspace requires an empty target; sync into the example dir.
|
||||
rsync -a --delete --exclude=generate.sh --exclude=tmp/ "$scratch/" "$root/"
|
||||
rm -rf "$scratch"
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"$schema": "./node_modules/nx/schemas/nx-schema.json",
|
||||
"analytics": false,
|
||||
"namedInputs": {
|
||||
"default": ["{projectRoot}/**/*", "sharedGlobals"],
|
||||
"production": [
|
||||
"default",
|
||||
"!{projectRoot}/.eslintrc.json",
|
||||
"!{projectRoot}/eslint.config.mjs"
|
||||
],
|
||||
"sharedGlobals": []
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"plugin": "@nx/js/typescript",
|
||||
"options": {
|
||||
"typecheck": {
|
||||
"targetName": "typecheck"
|
||||
},
|
||||
"build": {
|
||||
"targetName": "build",
|
||||
"configName": "tsconfig.lib.json",
|
||||
"buildDepsName": "build-deps",
|
||||
"watchDepsName": "watch-deps"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"plugin": "@nx/next/plugin",
|
||||
"options": {
|
||||
"startTargetName": "start",
|
||||
"buildTargetName": "build",
|
||||
"devTargetName": "dev",
|
||||
"serveStaticTargetName": "serve-static",
|
||||
"buildDepsTargetName": "build-deps",
|
||||
"watchDepsTargetName": "watch-deps"
|
||||
}
|
||||
},
|
||||
{
|
||||
"plugin": "@nx/eslint/plugin",
|
||||
"options": {
|
||||
"targetName": "lint"
|
||||
}
|
||||
}
|
||||
],
|
||||
"generators": {
|
||||
"@nx/next": {
|
||||
"application": {
|
||||
"style": "css",
|
||||
"linter": "eslint"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "@node-nx-next/source",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"next": "~16.1.6",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.8.0",
|
||||
"@next/eslint-plugin-next": "^16.1.6",
|
||||
"@nx/eslint": "23.0.0",
|
||||
"@nx/eslint-plugin": "23.0.0",
|
||||
"@nx/js": "23.0.0",
|
||||
"@nx/next": "23.0.0",
|
||||
"@nx/workspace": "23.0.0",
|
||||
"@swc-node/register": "~1.11.1",
|
||||
"@swc/cli": "~0.8.0",
|
||||
"@swc/core": "~1.15.5",
|
||||
"@swc/helpers": "~0.5.18",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"eslint": "^9.8.0",
|
||||
"eslint-config-next": "^16.1.6",
|
||||
"eslint-config-prettier": "^10.0.0",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"eslint-plugin-jsx-a11y": "6.10.1",
|
||||
"eslint-plugin-react": "7.35.0",
|
||||
"eslint-plugin-react-hooks": "5.0.0",
|
||||
"nx": "23.0.0",
|
||||
"prettier": "~3.6.2",
|
||||
"tslib": "^2.3.0",
|
||||
"typescript": "~5.9.2",
|
||||
"typescript-eslint": "^8.40.0"
|
||||
}
|
||||
}
|
||||
Generated
+11239
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
- "apps/*"
|
||||
@@ -0,0 +1,17 @@
|
||||
[
|
||||
{
|
||||
"httpCheck": {
|
||||
"path": "/",
|
||||
"expected": 200,
|
||||
"internalPort": 3000
|
||||
}
|
||||
},
|
||||
{
|
||||
"httpCheck": {
|
||||
"path": "/api/hello",
|
||||
"expected": 200,
|
||||
"internalPort": 3000,
|
||||
"expectedOutput": "Hello, from API!"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"declarationMap": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"importHelpers": true,
|
||||
"isolatedModules": true,
|
||||
"lib": ["es2022"],
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"noEmitOnError": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitOverride": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "es2022",
|
||||
"customConditions": ["@node-nx-next/source"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compileOnSave": false,
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./apps/web"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user