Skip to content

Commit

Permalink
🪲 Introduce node engine versions and improve error reporting from cre…
Browse files Browse the repository at this point in the history
…ate-lz-oapp (#344)
  • Loading branch information
janjakubnanista authored Feb 6, 2024
1 parent da6a148 commit 151a92e
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 12 deletions.
7 changes: 7 additions & 0 deletions .changeset/funny-pears-deny.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"create-lz-oapp": patch
"@layerzerolabs/oapp-example": patch
"@layerzerolabs/oft-example": patch
---

Improve error handling and introduce node engine specifiers
1 change: 1 addition & 0 deletions examples/oapp/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v18.16.0
3 changes: 3 additions & 0 deletions examples/oapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,8 @@
"solidity-bytes-utils": "^0.8.2",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
},
"engines": {
"node": ">=18.16.0"
}
}
1 change: 1 addition & 0 deletions examples/oft/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v18.16.0
3 changes: 3 additions & 0 deletions examples/oft/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,8 @@
"solidity-bytes-utils": "^0.8.2",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
},
"engines": {
"node": ">=18.16.0"
}
}
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,8 @@
"prettier-plugin-solidity": "^1.3.1",
"turbo": "1.11.0"
},
"packageManager": "[email protected]"
"packageManager": "[email protected]",
"engines": {
"node": ">=18.16.0"
}
}
34 changes: 33 additions & 1 deletion packages/create-lz-oapp/src/components/error.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React from "react";
import type { Config } from "@/types";
import { Box, Text } from "ink";
import { Box, Newline, Text } from "ink";
import {
BadGitRefError,
DestinationNotEmptyError,
DownloadError,
MissingGitRefError,
} from "@/utilities/cloning";
import { InstallationError } from "@/utilities/installation";

interface ErrorMessageProps {
config: Config;
Expand Down Expand Up @@ -58,6 +59,37 @@ export const ErrorMessage: React.FC<ErrorMessageProps> = ({
</Box>
);

case error instanceof InstallationError:
return (
<Box flexDirection="column">
<Text color="red">
There was a problem installing NPM dependencies:
</Text>

<Box margin={1} borderStyle="round" borderColor="gray">
<Text>{error.stdout}</Text>
</Box>

<Text bold>To try again:</Text>

<Box
margin={1}
borderStyle="round"
borderColor="gray"
flexDirection="column"
>
<Text color="green"># Navigate to your project</Text>
<Text color="cyan">cd {config.destination}</Text>
<Newline />
<Text color="green"># Reattempt the installation</Text>
<Text color="cyan">
{config.packageManager.executable}{" "}
{config.packageManager.args.join(" ")}
</Text>
</Box>
</Box>
);

case error instanceof Error:
return <DefaultErrorMessage error={error} />;

Expand Down
14 changes: 12 additions & 2 deletions packages/create-lz-oapp/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@ import { Command } from "commander";
import { promptForConfig } from "@/utilities/prompts";
import { ConfigSummary } from "@/components/config";
import { Setup } from "@/components/setup";
import { promptToContinue } from "@layerzerolabs/io-devtools";
import {
LogLevel,
promptToContinue,
setDefaultLogLevel,
} from "@layerzerolabs/io-devtools";
import { printLogo } from "@layerzerolabs/io-devtools/swag";
import { version } from "../package.json";
import {
ciOption,
destinationOption,
exampleOption,
logLevelOption,
packageManagerOption,
} from "./options";
import type { Config, Example, PackageManager } from "./types";
Expand All @@ -20,6 +25,7 @@ interface Args {
ci?: boolean;
destination?: string;
example?: Example;
logLevel?: LogLevel;
packageManager: PackageManager;
}

Expand All @@ -29,13 +35,17 @@ new Command("create-lz-oapp")
.addOption(ciOption)
.addOption(destinationOption)
.addOption(exampleOption)
.addOption(logLevelOption)
.addOption(packageManagerOption)
.action(async (args: Args) => {
printLogo();

// We'll provide a CI mode - a non-interctaive mode in which all input is taken
// from the CLI arguments and if something is missing, an error is thrown
const { ci } = args;
const { ci, logLevel = "info" } = args;

// We'll set a default log level for any loggers created past this point
setDefaultLogLevel(logLevel);

// First we get the config
const config = ci
Expand Down
14 changes: 13 additions & 1 deletion packages/create-lz-oapp/src/options.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { InvalidOptionArgumentError, Option } from 'commander'
import { AVAILABLE_PACKAGE_MANAGERS, EXAMPLES } from './config'
import { isDirectory, isFile } from '@layerzerolabs/io-devtools'
import { LogLevel, isDirectory, isFile } from '@layerzerolabs/io-devtools'
import { resolve } from 'path'

export const packageManagerOption = new Option('-p,--package-manager <name>', 'Node package manager to use')
Expand Down Expand Up @@ -38,3 +38,15 @@ export const destinationOption = new Option('-d,--destination <path>', 'Project
})

export const ciOption = new Option('--ci', 'Run in CI (non-interactive) mode').default(false)

export const logLevelOption = new Option('--log-level <level>', 'Log level')
.choices([
LogLevel.error,
LogLevel.warn,
LogLevel.info,
LogLevel.http,
LogLevel.verbose,
LogLevel.debug,
LogLevel.silly,
])
.default(LogLevel.info)
8 changes: 8 additions & 0 deletions packages/create-lz-oapp/src/utilities/cloning.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Config, Example } from '@/types'
import { createModuleLogger } from '@layerzerolabs/io-devtools'
import { rm } from 'fs/promises'
import { resolve } from 'path'
import tiged from 'tiged'
Expand All @@ -20,7 +21,11 @@ export const createExampleGitURL = (example: Example): string => {
}

export const cloneExample = async ({ example, destination }: Config) => {
const logger = createModuleLogger('cloning')

const url = createExampleGitURL(example)
logger.verbose(`Cloning example from ${url} to ${destination}`)

const emitter = tiged(url, {
disableCache: true,
mode: 'git',
Expand All @@ -31,6 +36,9 @@ export const cloneExample = async ({ example, destination }: Config) => {
// First we clone the whole proejct
await emitter.clone(destination)

logger.verbose(`Cloned example from ${url} to ${destination}`)
logger.verbose(`Cleaning up`)

// Then we cleanup what we don't want to be included
await cleanupExample(destination)
} catch (error: unknown) {
Expand Down
50 changes: 43 additions & 7 deletions packages/create-lz-oapp/src/utilities/installation.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
import type { Config, PackageManager } from '@/types'
import { createModuleLogger } from '@layerzerolabs/io-devtools'
import { spawn } from 'child_process'
import which from 'which'

export const installDependencies = (config: Config) =>
new Promise<void>((resolve, reject) => {
const logger = createModuleLogger('installation')

// We'll store combined stdout and stderr in this variable
const std: string[] = []

// This function will handle stdout/stderr streams from the child process
const handleStd = (chunk: string) => {
std.push(chunk)

logger.verbose(chunk)
}

/**
* Spawn the installation process.
*/
Expand All @@ -19,16 +32,39 @@ export const installDependencies = (config: Config) =>
},
})

child.stdout.setEncoding('utf8')
child.stderr.setEncoding('utf8')
child.stdout.on('data', handleStd)
child.stderr.on('data', handleStd)

child.on('close', (code) => {
if (code !== 0) {
reject(
new Error(
`Failed to install dependencies: ${config.packageManager.label} install exited with code ${code}`
)
)
} else resolve()
switch (code) {
// The null case happens when the script receives a sigterm signall
// (i.e. is cancelled by the user)
case null:
return reject(new Error(`Failed to install dependencies: Installation interrupted`))

// 0 exit code means success
case 0:
return resolve()

// And any other non-zero exit code means an error
default:
return reject(new InstallationError(config.packageManager, code, std.join('')))
}
})
})

export const isPackageManagerAvailable = ({ executable }: PackageManager): boolean =>
!!which.sync(executable, { nothrow: true })

export class InstallationError extends Error {
constructor(
public readonly packageManager: PackageManager,
public readonly exitCode: number,
public readonly stdout: string,
message: string = `Failed to install dependencies: ${packageManager.label} exited with code ${exitCode}`
) {
super(message)
}
}

0 comments on commit 151a92e

Please sign in to comment.