Skip to content

Commit

Permalink
feat: add custom workspace scripts with anchor-build option
Browse files Browse the repository at this point in the history
  • Loading branch information
beeman committed Nov 10, 2023
1 parent 5e81d87 commit b674ea9
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 20 deletions.
58 changes: 39 additions & 19 deletions packages/create-solana-dapp/bin/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#!/usr/bin/env node
import { intro, log, note, outro } from '@clack/prompts'
import { createWorkspace } from 'create-nx-workspace'
import { intro, log, note, outro, spinner } from '@clack/prompts'
import { execAndWait } from 'create-nx-workspace/src/utils/child-process-utils'
import { detectInvokedPackageManager } from 'create-nx-workspace/src/utils/package-manager'
import { getAppInfo } from '../lib/get-app-info'
import { getArgs } from '../lib/get-args'
import { customCreateWorkspace } from '../lib/custom-create-workspace'
import { join } from 'node:path'

async function main() {
const app = getAppInfo()
Expand All @@ -12,25 +14,43 @@ async function main() {
const args = await getArgs(process.argv, pm)

if (!args.dryRun) {
log.message(`Creating workspace...\n`)
const { directory } = await createWorkspace(`${args.package}`, {
anchor: args.anchor,
anchorName: args.anchorName,
appName: args.appName,
commit: {
name: 'Solana Developers',
email: '[email protected]',
message: 'chore: initial commit',
const s = spinner()
s.start(`Creating workspace...`)
const { directory } = await customCreateWorkspace(
`${args.package}`,
{
anchor: args.anchor,
anchorName: args.anchorName,
appName: args.appName,
commit: {
name: 'Solana Developers',
email: '[email protected]',
message: 'chore: initial commit',
},
dryRun: args.dryRun,
name: args.name,
nxCloud: false,
packageManager: args.packageManager,
ui: args.ui,
},
dryRun: args.dryRun,
name: args.name,
nxCloud: false,
packageManager: args.packageManager,
ui: args.ui,
})
async () => {
s.stop('Workspace created.')
if (args.anchor === 'none' || !args.anchorBuild) {
s.message('No anchor template to build.')
return
}
s.start('Building anchor...')
try {
await execAndWait(`npx nx run ${args.anchorName}:build`, join(args.name))
s.stop('Anchor built.')
} catch (e) {
s.stop('Anchor build failed.')
log.warn('Do you have Rust and Anchor installed?')
}
},
)

log.success('Workspace created.')
outro(`Run \`cd ${directory}\` to get started.`)
outro(`Run \`cd ${directory.replace(process.cwd(), '.')}\` to get started.`)
} else {
note(JSON.stringify(args, null, 2), 'Dry run, no changes were made.')
outro(`Would have created the workspace: ${args.name} with preset: ${args.preset}.`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
exports[`get-args expected args should get args with minimal parameters 1`] = `
{
"anchor": "counter",
"anchorBuild": false,
"anchorName": "anchor",
"appName": "web",
"dryRun": false,
Expand All @@ -17,6 +18,7 @@ exports[`get-args expected args should get args with minimal parameters 1`] = `
exports[`get-args expected args should get args with minimal parameters and package manager npm 1`] = `
{
"anchor": "counter",
"anchorBuild": false,
"anchorName": "anchor",
"appName": "web",
"dryRun": false,
Expand All @@ -31,6 +33,7 @@ exports[`get-args expected args should get args with minimal parameters and pack
exports[`get-args expected args should get args with minimal parameters and package manager pnpm 1`] = `
{
"anchor": "counter",
"anchorBuild": false,
"anchorName": "anchor",
"appName": "web",
"dryRun": false,
Expand All @@ -45,6 +48,7 @@ exports[`get-args expected args should get args with minimal parameters and pack
exports[`get-args expected args should get args with minimal parameters and package manager yarn 1`] = `
{
"anchor": "counter",
"anchorBuild": false,
"anchorName": "anchor",
"appName": "web",
"dryRun": false,
Expand All @@ -59,6 +63,7 @@ exports[`get-args expected args should get args with minimal parameters and pack
exports[`get-args expected args should get args with preset: --preset next --ui tailwind --anchor counter 1`] = `
{
"anchor": "counter",
"anchorBuild": false,
"anchorName": "anchor",
"appName": "web",
"dryRun": false,
Expand All @@ -73,6 +78,7 @@ exports[`get-args expected args should get args with preset: --preset next --ui
exports[`get-args expected args should get args with preset: --preset react --anchor counter --anchor-name program --ui tailwind 1`] = `
{
"anchor": "counter",
"anchorBuild": false,
"anchorName": "program",
"appName": "web",
"dryRun": false,
Expand All @@ -87,6 +93,7 @@ exports[`get-args expected args should get args with preset: --preset react --an
exports[`get-args expected args should get args with preset: --preset react --anchor none --ui none 1`] = `
{
"anchor": "none",
"anchorBuild": false,
"anchorName": "anchor",
"appName": "web",
"dryRun": false,
Expand All @@ -101,6 +108,7 @@ exports[`get-args expected args should get args with preset: --preset react --an
exports[`get-args expected args should get args with preset: --preset react --ui tailwind --anchor counter 1`] = `
{
"anchor": "counter",
"anchorBuild": false,
"anchorName": "anchor",
"appName": "web",
"dryRun": false,
Expand All @@ -112,9 +120,25 @@ exports[`get-args expected args should get args with preset: --preset react --ui
}
`;

exports[`get-args expected args should get args with preset: --preset=react --anchor=counter --anchor-name=program --anchor-build --ui=tailwind 1`] = `
{
"anchor": "counter",
"anchorBuild": true,
"anchorName": "program",
"appName": "web",
"dryRun": false,
"name": "my-app",
"package": "@solana-developers/[email protected]",
"packageManager": "npm",
"preset": "react",
"ui": "tailwind",
}
`;

exports[`get-args expected args should get args with preset: --preset=react --anchor=counter --anchor-name=program --ui=tailwind 1`] = `
{
"anchor": "counter",
"anchorBuild": false,
"anchorName": "program",
"appName": "web",
"dryRun": false,
Expand Down
78 changes: 78 additions & 0 deletions packages/create-solana-dapp/lib/custom-create-empty-workspace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { output } from '@nx/devkit'
import { CreateWorkspaceOptions } from 'create-nx-workspace'
import { execAndWait } from 'create-nx-workspace/src/utils/child-process-utils'
import { mapErrorToBodyLines } from 'create-nx-workspace/src/utils/error-utils'
import {
getPackageManagerCommand,
getPackageManagerVersion,
PackageManager,
} from 'create-nx-workspace/src/utils/package-manager'
import { getFileName } from 'create-nx-workspace/src/utils/string-utils'
import { unparse } from 'create-nx-workspace/src/utils/unparse'
import { join } from 'path'

/**
* Create a new Nx workspace
* @param tmpDir temporary directory to invoke nx cli
* @param name name of new nx workspace
* @param packageManager current package manager
* @param options options to pass to nx cli
* @returns
*/
export async function customCreateEmptyWorkspace<T extends CreateWorkspaceOptions>(
tmpDir: string,
name: string,
packageManager: PackageManager,
options: T,
): Promise<string> {
// Ensure to use packageManager for args
// if it's not already passed in from previous process
if (!options.packageManager) {
options.packageManager = packageManager
}

options.name = getFileName(name)
const directory = options.name

const args = unparse({
...options,
}).join(' ')

const pmc = getPackageManagerCommand(packageManager)

const command = `new ${args}`

const workingDir = process.cwd().replace(/\\/g, '/')
let nxWorkspaceRoot = `"${workingDir}"`

// If path contains spaces there is a problem in Windows for npm@6.
// In this case we have to escape the wrapping quotes.
if (process.platform === 'win32' && /\s/.test(nxWorkspaceRoot) && packageManager === 'npm') {
const pmVersion = +getPackageManagerVersion(packageManager, tmpDir).split('.')[0]
if (pmVersion < 7) {
nxWorkspaceRoot = `\\"${nxWorkspaceRoot.slice(1, -1)}\\"`
}
}
// let workspaceSetupSpinner = ora(`Creating your workspace in ${directory}`).start()

try {
const fullCommand = `${pmc.exec} nx ${command} --nxWorkspaceRoot=${nxWorkspaceRoot}`
await execAndWait(fullCommand, tmpDir)

// workspaceSetupSpinner.succeed(`Successfully created the workspace: ${directory}.`)
} catch (e) {
// workspaceSetupSpinner.fail()
if (e instanceof Error) {
output.error({
title: `Failed to create a workspace.`,
bodyLines: mapErrorToBodyLines(e),
})
} else {
console.error(e)
}
process.exit(1)
} finally {
// workspaceSetupSpinner.stop()
}
return join(workingDir, directory)
}
54 changes: 54 additions & 0 deletions packages/create-solana-dapp/lib/custom-create-sandbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { output } from '@nx/devkit'
import { execAndWait } from 'create-nx-workspace/src/utils/child-process-utils'
import { mapErrorToBodyLines } from 'create-nx-workspace/src/utils/error-utils'
import {
generatePackageManagerFiles,
getPackageManagerCommand,
PackageManager,
} from 'create-nx-workspace/src/utils/package-manager'
import { writeFileSync } from 'fs'
import { nxVersion } from 'nx/src/utils/versions'
import { join } from 'path'
import { dirSync } from 'tmp'

/**
* Creates a temporary directory and installs Nx in it.
* @param packageManager package manager to use
* @returns directory where Nx is installed
*/
export async function customCreateSandbox(packageManager: PackageManager) {
const { install, preInstall } = getPackageManagerCommand(packageManager)

const tmpDir = dirSync().name
try {
writeFileSync(
join(tmpDir, 'package.json'),
JSON.stringify({
dependencies: {
nx: nxVersion,
'@nx/workspace': nxVersion,
},
license: 'MIT',
}),
)
generatePackageManagerFiles(tmpDir, packageManager)

if (preInstall) {
await execAndWait(preInstall, tmpDir)
}

await execAndWait(install, tmpDir)
} catch (e) {
if (e instanceof Error) {
output.error({
title: `Failed to install dependencies`,
bodyLines: mapErrorToBodyLines(e),
})
} else {
console.error(e)
}
process.exit(1)
}

return tmpDir
}
50 changes: 50 additions & 0 deletions packages/create-solana-dapp/lib/custom-create-workspace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { CreateWorkspaceOptions } from 'create-nx-workspace'
import { createPreset } from 'create-nx-workspace/src/create-preset'
import { mapErrorToBodyLines } from 'create-nx-workspace/src/utils/error-utils'
import { initializeGitRepo } from 'create-nx-workspace/src/utils/git/git'
import { getThirdPartyPreset } from 'create-nx-workspace/src/utils/preset/get-third-party-preset'
import { customCreateEmptyWorkspace } from './custom-create-empty-workspace'
import { customCreateSandbox } from './custom-create-sandbox'

export async function customCreateWorkspace<T extends CreateWorkspaceOptions>(
preset: string,
options: T,
afterCreatePreset: (options: T) => Promise<void>,
) {
const { packageManager, name, skipGit = false, defaultBase = 'main', commit } = options

const tmpDir = await customCreateSandbox(packageManager)

// nx new requires preset currently. We should probably make it optional.
const directory = await customCreateEmptyWorkspace<T>(tmpDir, name, packageManager, { ...options, preset })

// If the preset is a third-party preset, we need to call createPreset to install it
// For first-party presets, it will created by createEmptyWorkspace instead.
// In createEmptyWorkspace, it will call `nx new` -> `@nx/workspace newGenerator` -> `@nx/workspace generatePreset`.

const thirdPartyPreset = await getThirdPartyPreset(preset)
if (thirdPartyPreset) {
// Hide the stdout of createPreset
await createPreset(thirdPartyPreset, options, packageManager, directory)
}
await afterCreatePreset(options)

if (!skipGit && commit) {
try {
await initializeGitRepo(directory, { defaultBase, commit })
} catch (e) {
if (e instanceof Error) {
console.error({
title: 'Could not initialize git repository',
bodyLines: mapErrorToBodyLines(e),
})
} else {
console.error(e)
}
}
}

return {
directory,
}
}
1 change: 1 addition & 0 deletions packages/create-solana-dapp/lib/get-args-result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CreateWorkspaceOptions } from 'create-nx-workspace'

export interface GetArgsResult {
anchor: string | undefined
anchorBuild: boolean
anchorName: string | undefined
appName: string
dryRun: boolean
Expand Down
1 change: 1 addition & 0 deletions packages/create-solana-dapp/lib/get-args.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ describe('get-args', () => {
'--preset react --anchor none --ui none',
'--preset react --anchor counter --anchor-name program --ui tailwind',
'--preset=react --anchor=counter --anchor-name=program --ui=tailwind',
'--preset=react --anchor=counter --anchor-name=program --anchor-build --ui=tailwind',
])('should get args with preset: %s', async (preset: string) => {
const result = await getArgs(['', '', 'my-app', ...preset.split(' ')])

Expand Down
4 changes: 3 additions & 1 deletion packages/create-solana-dapp/lib/get-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ export async function getArgs(argv: string[], pm: PackageManager = 'npm'): Promi
return value
},
)
.option('--app-name <name>', help(`Name of the frontend project (default: web)`))
.option('--anchor-build', help(`Build the anchor project (default: false)`), false)
.option('--anchor-name <anchor-name>', help(`Anchor project name (default: anchor)`))
.option('--app-name <name>', help(`Name of the frontend project (default: web)`))
.option('-pm, --package-manager <package-manager>', help(`Package manager to use (default: npm)`))
.option('-d, --dry-run', 'Dry run (default: false)')
.addHelpText(
Expand All @@ -65,6 +66,7 @@ Examples:
// Take the result from the command line and use it to populate the options
const options: GetArgsResult = {
anchor: result.anchor,
anchorBuild: result.anchorBuild,
anchorName: result.anchorName ?? 'anchor',
appName: result.appName ?? 'web',
dryRun: result.dryRun ?? false,
Expand Down
2 changes: 2 additions & 0 deletions packages/create-solana-dapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"@nx/devkit": "17.0.1",
"commander": "^11.1.0",
"create-nx-workspace": "17.0.1",
"nx": "17.0.1",
"tmp": "0.2.1",
"tslib": "^2.3.0"
},
"type": "commonjs",
Expand Down

0 comments on commit b674ea9

Please sign in to comment.