From 3f170ecaeb55e7fd877b344f18d7fd0348496f80 Mon Sep 17 00:00:00 2001 From: Wayne Nilsen Date: Mon, 14 Nov 2022 04:19:15 -0500 Subject: [PATCH] feat(cli): new subcommands (#283) ### Problem Statement We may want to use parts of this build system with parts of our own build system. I may have use of using my own build script to call rollup and tsc. ### Solution To start this conversation I suggest we make new subcommands for validating and doing other things step-by-step. This is somewhat designed as a sketch to gather feedback and see if you are interested in this kind of work. ### How has this been tested? starting to manually run some of the tests that run in CI noticed a bug something going on with CI build was not working for some reason --- [related issue](https://github.com/near/near-sdk-js/issues/280) Co-authored-by: Serhii Volovyk --- lib/cli/cli.d.ts | 12 ++++ lib/cli/cli.js | 127 +++++++++++++++++++++++++++--------- src/cli/cli.ts | 165 ++++++++++++++++++++++++++++++++++++----------- 3 files changed, 233 insertions(+), 71 deletions(-) diff --git a/lib/cli/cli.d.ts b/lib/cli/cli.d.ts index 10fcdae58..deb0b7f91 100644 --- a/lib/cli/cli.d.ts +++ b/lib/cli/cli.d.ts @@ -1,4 +1,16 @@ #!/usr/bin/env node +export declare function validateCom(source: string, { verbose }: { + verbose: boolean; +}): Promise; +export declare function checkTypescriptCom(source: string, { verbose }: { + verbose: boolean; +}): Promise; +export declare function createJsFileWithRullupCom(source: string, target: string, { verbose }: { + verbose: boolean; +}): Promise; +export declare function transpileJsAndBuildWasmCom(target: string, { verbose }: { + verbose: boolean; +}): Promise; export declare function buildCom(source: string, target: string, { verbose }: { verbose: boolean; }): Promise; diff --git a/lib/cli/cli.js b/lib/cli/cli.js index e1edd2ef5..3a650f604 100755 --- a/lib/cli/cli.js +++ b/lib/cli/cli.js @@ -24,44 +24,107 @@ program .argument("[target]", "Target file path and name.", "build/contract.wasm") .option("--verbose", "Whether to print more verbose output.", false) .action(buildCom)) + .addCommand(new Command("validateContract") + .usage("[source]") + .description("Validate a NEAR JS Smart-contract. Validates the contract by checking that all parameters are initialized in the constructor. Works only for typescript.") + .argument("[source]", "Contract to validate.", "src/index.ts") + .option("--verbose", "Whether to print more verbose output.", false) + .action(validateCom)) + .addCommand(new Command("checkTypescript") + .usage("[source]") + .description("Run TSC with some cli flags - warning - ignores tsconfig.json.") + .argument("[source]", "Typescript file to validate", "src/index.ts") + .option("--verbose", "Whether to print more verbose output.", false) + .action(checkTypescriptCom)) + .addCommand(new Command("createJsFileWithRullup") + .usage("[source] [target]") + .description("Create intermediate javascript file for later processing with QJSC") + .argument("[source]", "Contract to build.", "src/index.js") + .argument("[target]", "Target file path and name. The default corresponds to contract.js", "build/contract.wasm") + .option("--verbose", "Whether to print more verbose output.", false) + .action(createJsFileWithRullupCom)) + .addCommand(new Command("transpileJsAndBuildWasm") + .usage("[source] [target]") + .description("Transpiles the target javascript file into .c and .h using QJSC then compiles that into wasm using clang") + .argument("[target]", "Target file path and name. The js file must correspond to the same path with the js extension.", "build/contract.wasm") + .option("--verbose", "Whether to print more verbose output.", false) + .action(transpileJsAndBuildWasmCom)) .parse(); -export async function buildCom(source, target, { verbose = false }) { - const SOURCE_EXT = source.split(".").pop(); - const TARGET_DIR = dirname(target); - const TARGET_EXT = target.split(".").pop(); - const TARGET_FILE_NAME = basename(target, `.${TARGET_EXT}`); - const signale = new Signale({ scope: "build", interactive: !verbose }); - if (TARGET_EXT !== "wasm") { - signale.error(`Unsupported target ${TARGET_EXT}, make sure target ends with .wasm!`); - process.exit(1); - } - const ROLLUP_TARGET = `${TARGET_DIR}/${TARGET_FILE_NAME}.js`; - const QJSC_TARGET = `${TARGET_DIR}/${TARGET_FILE_NAME}.h`; - const CONTRACT_TARGET = `${TARGET_DIR}/${TARGET_FILE_NAME}.wasm`; - signale.await(`Building ${source} contract...`); - if (SOURCE_EXT === "ts") { - signale.await(`Typechecking ${source} with tsc...`); - await checkTsBuildWithTsc(source, verbose); +function getTargetDir(target) { + return dirname(target); +} +function getTargetExt(target) { + return target.split(".").pop(); +} +function getTargetFileName(target) { + return basename(target, `.${getTargetExt(target)}`); +} +function getRollupTarget(target) { + return `${getTargetDir(target)}/${getTargetFileName(target)}.js`; +} +function getQjscTarget(target) { + return `${getTargetDir(target)}/${getTargetFileName(target)}.h`; +} +function getContractTarget(target) { + return `${getTargetDir(target)}/${getTargetFileName(target)}.wasm`; +} +function requireTargetExt(target) { + if (getTargetExt(target) === "wasm") { + return; } - signale.await(`Creating ${TARGET_DIR} directory...`); - if (!fs.existsSync(TARGET_DIR)) { - fs.mkdirSync(TARGET_DIR, {}); + signal.error(`Unsupported target ${getTargetExt(target)}, make sure target ends with .wasm!`); + process.exit(1); +} +function ensureTargetDirExists(target) { + const targetDir = getTargetDir(target); + if (fs.existsSync(targetDir)) { + return; } - signal.await(`Validatig ${source} contract...`); + signal.await(`Creating ${targetDir} directory...`); + fs.mkdirSync(targetDir, {}); +} +export async function validateCom(source, { verbose = false }) { + signal.await(`Validating ${source} contract...`); if (!await validateContract(source, verbose)) { process.exit(1); } - signale.await(`Creating ${source} file with Rollup...`); - await createJsFileWithRullup(source, ROLLUP_TARGET, verbose); - signale.await(`Creating ${QJSC_TARGET} file with QJSC...`); - await createHeaderFileWithQjsc(ROLLUP_TARGET, QJSC_TARGET, verbose); - signale.await("Generating methods.h file..."); - await createMethodsHeaderFile(ROLLUP_TARGET, verbose); - signale.await(`Creating ${CONTRACT_TARGET} contract...`); - await createWasmContract(QJSC_TARGET, CONTRACT_TARGET, verbose); - signale.await("Executing wasi-stub..."); - await wasiStubContract(CONTRACT_TARGET, verbose); - signale.success(`Generated ${CONTRACT_TARGET} contract successfully!`); +} +export async function checkTypescriptCom(source, { verbose = false }) { + const sourceExt = source.split(".").pop(); + if (sourceExt !== "ts") { + signal.info(`Source file is not a typescript file ${source}`); + return; + } + signal.await(`Typechecking ${source} with tsc...`); + await checkTsBuildWithTsc(source, verbose); +} +export async function createJsFileWithRullupCom(source, target, { verbose = false }) { + requireTargetExt(target); + ensureTargetDirExists(target); + signal.await(`Creating ${source} file with Rollup...`); + await createJsFileWithRullup(source, getRollupTarget(target), verbose); +} +export async function transpileJsAndBuildWasmCom(target, { verbose = false }) { + requireTargetExt(target); + ensureTargetDirExists(target); + signal.await(`Creating ${getQjscTarget(target)} file with QJSC...`); + await createHeaderFileWithQjsc(getRollupTarget(target), getQjscTarget(target), verbose); + signal.await("Generating methods.h file..."); + await createMethodsHeaderFile(getRollupTarget(target), verbose); + signal.await(`Creating ${getContractTarget(target)} contract...`); + await createWasmContract(getQjscTarget(target), getContractTarget(target), verbose); + signal.await("Executing wasi-stub..."); + await wasiStubContract(getContractTarget(target), verbose); + signal.success(`Generated ${getContractTarget(target)} contract successfully!`); +} +export async function buildCom(source, target, { verbose = false }) { + requireTargetExt(target); + signal.await(`Building ${source} contract...`); + await checkTypescriptCom(source, { verbose }); + ensureTargetDirExists(target); + await validateCom(source, { verbose }); + await createJsFileWithRullupCom(source, target, { verbose }); + await transpileJsAndBuildWasmCom(target, { verbose }); } async function checkTsBuildWithTsc(sourceFileWithPath, verbose = false) { await executeCommand(`${TSC} --noEmit --skipLibCheck --experimentalDecorators --target es2020 --moduleResolution node ${sourceFileWithPath}`, verbose); diff --git a/src/cli/cli.ts b/src/cli/cli.ts index aac775239..9d959311b 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -32,63 +32,150 @@ program .option("--verbose", "Whether to print more verbose output.", false) .action(buildCom) ) + .addCommand( + new Command("validateContract") + .usage("[source]") + .description("Validate a NEAR JS Smart-contract. Validates the contract by checking that all parameters are initialized in the constructor. Works only for typescript.") + .argument("[source]", "Contract to validate.", "src/index.ts") + .option("--verbose", "Whether to print more verbose output.", false) + .action(validateCom) + ) + .addCommand( + new Command("checkTypescript") + .usage("[source]") + .description("Run TSC with some cli flags - warning - ignores tsconfig.json.") + .argument("[source]", "Typescript file to validate", "src/index.ts") + .option("--verbose", "Whether to print more verbose output.", false) + .action(checkTypescriptCom) + ) + .addCommand( + new Command("createJsFileWithRullup") + .usage("[source] [target]") + .description("Create intermediate javascript file for later processing with QJSC") + .argument("[source]", "Contract to build.", "src/index.js") + .argument("[target]", "Target file path and name. The default corresponds to contract.js", "build/contract.wasm") + .option("--verbose", "Whether to print more verbose output.", false) + .action(createJsFileWithRullupCom) + ) + .addCommand( + new Command("transpileJsAndBuildWasm") + .usage("[source] [target]") + .description("Transpiles the target javascript file into .c and .h using QJSC then compiles that into wasm using clang") + .argument("[target]", "Target file path and name. The js file must correspond to the same path with the js extension.", "build/contract.wasm") + .option("--verbose", "Whether to print more verbose output.", false) + .action(transpileJsAndBuildWasmCom) + ) .parse(); -export async function buildCom( - source: string, - target: string, - { verbose = false }: { verbose: boolean } -): Promise { - const SOURCE_EXT = source.split(".").pop(); - const TARGET_DIR = dirname(target); - const TARGET_EXT = target.split(".").pop(); - const TARGET_FILE_NAME = basename(target, `.${TARGET_EXT}`); - const signale = new Signale({ scope: "build", interactive: !verbose }); - - if (TARGET_EXT !== "wasm") { - signale.error( - `Unsupported target ${TARGET_EXT}, make sure target ends with .wasm!` - ); - process.exit(1); - } +function getTargetDir(target: string): string { + return dirname(target); +} + +function getTargetExt(target: string): string { + return target.split(".").pop(); +} + +function getTargetFileName(target: string): string { + return basename(target, `.${getTargetExt(target)}`); +} + +function getRollupTarget(target: string): string { + return `${getTargetDir(target)}/${getTargetFileName(target)}.js` +} - const ROLLUP_TARGET = `${TARGET_DIR}/${TARGET_FILE_NAME}.js`; - const QJSC_TARGET = `${TARGET_DIR}/${TARGET_FILE_NAME}.h`; - const CONTRACT_TARGET = `${TARGET_DIR}/${TARGET_FILE_NAME}.wasm`; +function getQjscTarget(target:string): string { + return `${getTargetDir(target)}/${getTargetFileName(target)}.h`; +} - signale.await(`Building ${source} contract...`); +function getContractTarget(target: string): string { + return `${getTargetDir(target)}/${getTargetFileName(target)}.wasm`; +} - if (SOURCE_EXT === "ts") { - signale.await(`Typechecking ${source} with tsc...`); - await checkTsBuildWithTsc(source, verbose); +function requireTargetExt(target: string): void { + if (getTargetExt(target) === "wasm") { + return; } - signale.await(`Creating ${TARGET_DIR} directory...`); - if (!fs.existsSync(TARGET_DIR)) { - fs.mkdirSync(TARGET_DIR, {}); + signal.error( + `Unsupported target ${getTargetExt(target)}, make sure target ends with .wasm!` + ); + process.exit(1); +} + +function ensureTargetDirExists(target: string): void { + const targetDir = getTargetDir(target); + if (fs.existsSync(targetDir)) { + return; } - signal.await(`Validatig ${source} contract...`); + signal.await(`Creating ${targetDir} directory...`); + fs.mkdirSync(targetDir, {}); +} + +export async function validateCom(source: string, { verbose = false}: {verbose: boolean}): Promise { + signal.await(`Validating ${source} contract...`); if (!await validateContract(source, verbose)) { process.exit(1); } +} + +export async function checkTypescriptCom(source: string, { verbose = false}: {verbose: boolean}): Promise { + const sourceExt = source.split(".").pop(); + if (sourceExt !== "ts") { + signal.info(`Source file is not a typescript file ${source}`) + return; + } + + signal.await(`Typechecking ${source} with tsc...`); + await checkTsBuildWithTsc(source, verbose); +} + +export async function createJsFileWithRullupCom(source: string, target: string, { verbose = false}: {verbose: boolean}): Promise { + requireTargetExt(target); + ensureTargetDirExists(target); + + signal.await(`Creating ${source} file with Rollup...`); + await createJsFileWithRullup(source, getRollupTarget(target), verbose); +} + + +export async function transpileJsAndBuildWasmCom(target: string, { verbose = false}: {verbose: boolean}): Promise { + requireTargetExt(target); + ensureTargetDirExists(target); + + signal.await(`Creating ${getQjscTarget(target)} file with QJSC...`); + await createHeaderFileWithQjsc(getRollupTarget(target), getQjscTarget(target), verbose); + + signal.await("Generating methods.h file..."); + await createMethodsHeaderFile(getRollupTarget(target), verbose); + + signal.await(`Creating ${getContractTarget(target)} contract...`); + await createWasmContract(getQjscTarget(target), getContractTarget(target), verbose); + + signal.await("Executing wasi-stub..."); + await wasiStubContract(getContractTarget(target), verbose); + + signal.success(`Generated ${getContractTarget(target)} contract successfully!`); +} + +export async function buildCom( + source: string, + target: string, + { verbose = false }: { verbose: boolean } +): Promise { + requireTargetExt(target); - signale.await(`Creating ${source} file with Rollup...`); - await createJsFileWithRullup(source, ROLLUP_TARGET, verbose); + signal.await(`Building ${source} contract...`); - signale.await(`Creating ${QJSC_TARGET} file with QJSC...`); - await createHeaderFileWithQjsc(ROLLUP_TARGET, QJSC_TARGET, verbose); + await checkTypescriptCom(source, { verbose }); - signale.await("Generating methods.h file..."); - await createMethodsHeaderFile(ROLLUP_TARGET, verbose); + ensureTargetDirExists(target); - signale.await(`Creating ${CONTRACT_TARGET} contract...`); - await createWasmContract(QJSC_TARGET, CONTRACT_TARGET, verbose); + await validateCom(source, { verbose }); - signale.await("Executing wasi-stub..."); - await wasiStubContract(CONTRACT_TARGET, verbose); + await createJsFileWithRullupCom(source, target, { verbose }); - signale.success(`Generated ${CONTRACT_TARGET} contract successfully!`); + await transpileJsAndBuildWasmCom(target, { verbose }); } async function checkTsBuildWithTsc(