Skip to content

Commit

Permalink
feat: implement preset-next
Browse files Browse the repository at this point in the history
  • Loading branch information
beeman committed Oct 24, 2023
1 parent 4e2e5f3 commit d7f3deb
Show file tree
Hide file tree
Showing 31 changed files with 1,148 additions and 11 deletions.
2 changes: 1 addition & 1 deletion e2e/preset-anchor-e2e/tests/preset-anchor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe('preset-anchor', () => {
let projectDirectory: string

beforeAll(() => {
projectDirectory = createTestProject('preset-react-e2e')
projectDirectory = createTestProject('preset-anchor-e2e')

// The plugin has been built and published to a local registry in the jest globalSetup
// Install the plugin built with the latest source code into the test repo
Expand Down
6 changes: 3 additions & 3 deletions e2e/preset-next-e2e/tests/preset-next.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ describe('preset-next', () => {
let projectDirectory: string

beforeAll(() => {
projectDirectory = createTestProject('preset-react-e2e')
projectDirectory = createTestProject('preset-next-e2e')

// The plugin has been built and published to a local registry in the jest globalSetup
// Install the plugin built with the latest source code into the test repo
execSync(`npm install @solana-developers/preset-next@e2e`, {
execSync(`npm install ${packageName}@e2e`, {
cwd: projectDirectory,
stdio: 'inherit',
env: process.env,
Expand All @@ -36,7 +36,7 @@ describe('preset-next', () => {

it('should be installed', () => {
// npm ls will fail if the package is not installed properly
execSync('npm ls @solana-developers/preset-next', {
execSync(`npm ls ${packageName}`, {
cwd: projectDirectory,
stdio: 'inherit',
})
Expand Down
8 changes: 6 additions & 2 deletions packages/preset-common/src/utils/application-cleanup.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Tree } from '@nx/devkit'
import { join } from 'path'

export function applicationCleanup(tree: Tree, path: string) {
tree.children(path).forEach((file) => tree.delete(join(path, file)))
export function applicationCleanup(tree: Tree, path: string, files: string[] = []) {
tree.children(path).forEach((file) => {
if (files.includes(join(path, file)) || !files.length) {
tree.delete(join(path, file))
}
})
}
1 change: 1 addition & 0 deletions packages/preset-common/src/utils/package-versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ export const packageVersion = {
},
bs58: '5.0.0',
daisyui: '3.9.3',
encoding: '0.1.13',
}
2 changes: 1 addition & 1 deletion packages/preset-next/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
}
},
{
"files": ["./package.json"],
"files": ["./package.json", "./generators.json"],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/nx-plugin-checks": "error"
Expand Down
10 changes: 10 additions & 0 deletions packages/preset-next/generators.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"generators": {
"application": {
"factory": "./src/generators/application/application-next-generator",
"schema": "./src/generators/application/application-next-schema.json",
"description": "application generator",
"aliases": ["preset"]
}
}
}
10 changes: 9 additions & 1 deletion packages/preset-next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@
"name": "@solana-developers/preset-next",
"version": "0.0.1",
"dependencies": {
"@nx/devkit": "17.0.1",
"@nx/js": "17.0.1",
"@nx/linter": "17.0.1",
"@nx/next": "^17.0.1",
"@solana-developers/preset-anchor": "*",
"@solana-developers/preset-common": "*",
"@solana-developers/preset-react": "*",
"tslib": "^2.3.0"
},
"type": "commonjs",
"main": "./src/index.js",
"typings": "./src/index.d.ts"
"typings": "./src/index.d.ts",
"generators": "./generators.json"
}
6 changes: 5 additions & 1 deletion packages/preset-next/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["packages/preset-next/**/*.ts", "packages/preset-next/package.json"]
"lintFilePatterns": [
"packages/preset-next/**/*.ts",
"packages/preset-next/package.json",
"packages/preset-next/generators.json"
]
}
},
"test": {
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { getProjects, readProjectConfiguration, Tree } from '@nx/devkit'
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'
import { getRecursiveFileContents } from '@solana-developers/preset-common'
import { ApplicationReactUiLibrary } from '@solana-developers/preset-react'

import { normalizeApplicationNextSchema } from '../../utils'
import { applicationNextGenerator } from './application-next-generator'
import { ApplicationNextSchema, NormalizedApplicationNextSchema } from './application-next-schema'

describe('application generator', () => {
let tree: Tree
const rawOptions: ApplicationNextSchema = { name: 'test-app' }
const options: NormalizedApplicationNextSchema = normalizeApplicationNextSchema(rawOptions)

beforeEach(() => {
tree = createTreeWithEmptyWorkspace()
})

describe('default apps', () => {
it.each([['none'], ['tailwind']])('should generate default app with "%s" ui', async (uiLibrary) => {
await applicationNextGenerator(tree, { ...rawOptions, uiLibrary: uiLibrary as ApplicationReactUiLibrary })

const appConfig = readProjectConfiguration(tree, options.name)
const anchorConfig = readProjectConfiguration(tree, options.anchorName)
expect(appConfig).toBeDefined()
expect(anchorConfig).toBeDefined()

const contents = getRecursiveFileContents(tree, '.')
const stringified = JSON.stringify(contents, null, 2)
expect(stringified).toMatchSnapshot()
})
})

describe('custom apps', () => {
it('should generate 4 Next apps and 2 Anchor apps', async () => {
await applicationNextGenerator(tree, { ...rawOptions, uiLibrary: 'none' })
await applicationNextGenerator(tree, { ...rawOptions, name: 'app-1', uiLibrary: 'none' })
await applicationNextGenerator(tree, { ...rawOptions, name: 'app-2', uiLibrary: 'none' })
await applicationNextGenerator(tree, { ...rawOptions, name: 'app-3', anchorName: 'anchor-1', uiLibrary: 'none' })

const app0 = readProjectConfiguration(tree, options.name)
const app1 = readProjectConfiguration(tree, 'app-1')
const app2 = readProjectConfiguration(tree, 'app-2')
const app3 = readProjectConfiguration(tree, 'app-3')
const anchor0Config = readProjectConfiguration(tree, options.anchorName)
const anchor1Config = readProjectConfiguration(tree, 'anchor-1')
const projects = getProjects(tree)

expect(app0).toBeDefined()
expect(app1).toBeDefined()
expect(app2).toBeDefined()
expect(app3).toBeDefined()
expect(anchor0Config).toBeDefined()
expect(anchor1Config).toBeDefined()
expect(projects.size).toEqual(6)
})

it('should generate app without anchor', async () => {
await applicationNextGenerator(tree, { ...rawOptions, uiLibrary: 'none', withAnchor: false })
const projects = getProjects(tree)
const appProject = projects.has(options.name)
const anchorProject = projects.has(options.anchorName)

expect(projects.size).toEqual(1)
expect(appProject).toBeDefined()
expect(anchorProject).toBeFalsy()
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {
addDependenciesToPackageJson,
formatFiles,
generateFiles,
getProjects,
installPackagesTask,
Tree,
} from '@nx/devkit'
import { getNpmScope } from '@nx/js/src/utils/package-json/get-npm-scope'
import { applicationAnchorGenerator } from '@solana-developers/preset-anchor'
import { applicationCleanup, packageVersion } from '@solana-developers/preset-common'
import {
applicationReactDependencies,
applicationTailwindConfig,
walletAdapterDependencies,
} from '@solana-developers/preset-react'
import { join } from 'path'
import { applicationSubstitutions, generateNextApplication, normalizeApplicationNextSchema } from '../../utils'
import { ApplicationNextSchema } from './application-next-schema'

export async function applicationNextGenerator(tree: Tree, rawOptions: ApplicationNextSchema) {
const options = normalizeApplicationNextSchema(rawOptions)
const project = await generateNextApplication(tree, options)
const npmScope = getNpmScope(tree)

// Clean up the default project files.
const cleanup = [
'/app/global.css',
'/app/page.module.css',
'/app/layout.tsx',
'/app/page.tsx',
'/public/favicon.ico',
'/public/.gitkeep',
]
applicationCleanup(tree, join(project.sourceRoot, 'app'), cleanup)

const substitutions = applicationSubstitutions({
name: options.name,
npmScope,
})

// Generate the common files.
generateFiles(tree, join(__dirname, 'files/common'), project.root, substitutions)

// Generate the files from the templates.
generateFiles(tree, join(__dirname, 'files/ui', options.uiLibrary), project.root, substitutions)

// Add the dependencies for the base application.
applicationReactDependencies(tree, options)

addDependenciesToPackageJson(tree, { encoding: packageVersion.encoding }, {})

// Add the dependencies for the wallet adapter.
walletAdapterDependencies(tree)

if (options.uiLibrary === 'tailwind') {
// Add the tailwind config.
await applicationTailwindConfig(tree, options.name)
}

if (options.withAnchor && !getProjects(tree).has(options.anchorName)) {
// Add the anchor application.
await applicationAnchorGenerator(tree, {
name: options.anchorName,
programName: options.anchorProgramName,
skipFormat: true,
})
}

// Format the files.
if (!options.skipFormat) {
await formatFiles(tree)
}

// Install the packages on exit.
return () => {
installPackagesTask(tree, true)
}
}

export default applicationNextGenerator
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ApplicationReactUiLibrary } from '@solana-developers/preset-react'

export interface ApplicationNextSchema {
anchorName?: string
anchorProgramName?: string
name: string
skipFormat?: boolean
uiLibrary?: ApplicationReactUiLibrary
withAnchor?: boolean
}

export type NormalizedApplicationNextSchema = Required<ApplicationNextSchema>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"$schema": "http://json-schema.org/schema",
"$id": "ApplicationNextSchema",
"title": "",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "What name would you like to use?"
}
},
"required": ["name"]
}
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"use client"
import { ReactNode } from 'react'
import Link from 'next/link'
import dynamic from "next/dynamic";

const WalletMultiButton = dynamic(
async () =>
(await import("@solana/wallet-adapter-react-ui")).WalletMultiButton,
{ ssr: false }
)
export function AppLayout({ children }: { children: ReactNode }) {
const { pathname } = { pathname: '' }
const pages = [
{ label: 'Airdrop', path: '/airdrop' },
{ label: 'Page 1', path: '/page-1' },
{ label: 'Page 2', path: '/page-2' },
]

return (
<div>
<div>
<div>
<Link href="/" >
<%= packageName %>
</Link>
<ul >
{pages.map(({ label, path }) => (
<li key={path}>
<Link href={path}>
{label}
</Link>
</li>
))}
</ul>
</div>
<div>
<WalletMultiButton />
</div>
</div>
<div>{children}</div>
<footer>
<aside>
<p>Generated with <%= packageName %>@<%= packageVersion %></p>
</aside>
</footer>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
html,
body {
height: 100%;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import './global.css'
import { AppLayout } from './app-layout'
import { SolanaProvider } from './solana-provider'

export const metadata = {
title: 'Welcome to <%= name %>',
description: 'Generated by <%= packageName %>@<%= packageVersion %>',
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<SolanaProvider>
<AppLayout>
{children}
</AppLayout>
</SolanaProvider>
</body>
</html>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { IDL } from '@<%= npmScope %>/anchor';

export default async function Index() {
return (
<div>
<pre>{JSON.stringify(IDL, null, 2)}</pre>
</div>
)
}
Loading

0 comments on commit d7f3deb

Please sign in to comment.