From b17d3754c7d47b2b7a7c5f99c4960cd184544568 Mon Sep 17 00:00:00 2001 From: shamsartem Date: Thu, 4 Aug 2022 12:14:55 +0300 Subject: [PATCH] Add aqua compilation, fix some bugs --- README.md | 35 ++++++++ package-lock.json | 31 +++++-- package.json | 2 + src/commands/aqua.ts | 150 ++++++++++++++++++++++++++++++++ src/commands/init.ts | 22 +++-- src/lib/aquaCli.ts | 12 ++- src/lib/const.ts | 3 +- src/lib/helpers/downloadFile.ts | 15 ++-- 8 files changed, 245 insertions(+), 25 deletions(-) create mode 100644 src/commands/aqua.ts diff --git a/README.md b/README.md index 22cf0562d..e00371cab 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,7 @@ Pull request and release process: # Commands +* [`fluence aqua`](#fluence-aqua) * [`fluence autocomplete [SHELL]`](#fluence-autocomplete-shell) * [`fluence dependency [NAME]`](#fluence-dependency-name) * [`fluence deploy`](#fluence-deploy) @@ -120,6 +121,40 @@ Pull request and release process: * [`fluence service remove [NAME | PATH | URL]`](#fluence-service-remove-name--path--url) * [`fluence service repl [NAME | PATH | URL]`](#fluence-service-repl-name--path--url) +## `fluence aqua` + +Compile aqua file or directory that contains your .aqua files + +``` +USAGE + $ fluence aqua [-i ] [-o ] [--import ] [--air | --js] [--log-level ] + [--const ] [--no-relay] [--no-xor] [--dry] [--scheduled] [-w] [--no-input] + +FLAGS + -i, --input= Path to an aqua file or an input directory that contains your .aqua files + -o, --output= Path to the output directory. Will be created if it doesn't exists + -w, --watch Watch aqua file or folder for changes and recompile + --air Generate .air file instead of .ts + --const= Set log level + --dry Checks if compilation is succeeded, without output + --import=... Path to a directory to import from. May be used several times + --js Generate .js file instead of .ts + --log-level= Set log level + --no-input Don't interactively ask for any input from the user + --no-relay Do not generate a pass through the relay node + --no-xor Do not generate a wrapper that catches and displays errors + --scheduled Generate air code for script storage. Without error handling wrappers and hops on relay. Will + ignore other options + +DESCRIPTION + Compile aqua file or directory that contains your .aqua files + +EXAMPLES + $ fluence aqua +``` + +_See code: [dist/commands/aqua.ts](https://github.com/fluencelabs/fluence-cli/blob/v0.0.0/dist/commands/aqua.ts)_ + ## `fluence autocomplete [SHELL]` display autocomplete installation instructions diff --git a/package-lock.json b/package-lock.json index 0c3792d4c..31225ed95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@oclif/plugin-not-found": "^2.3.1", "ajv": "^8.11.0", "camelcase": "^5.2.0", + "chokidar": "^3.5.3", "decompress": "^4.2.1", "filenamify": "^4", "inquirer": "^8.2.4", @@ -38,6 +39,7 @@ "@tsconfig/node16-strictest": "^1.0.1", "@types/camelcase": "^5.2.0", "@types/chai": "^4", + "@types/chokidar": "^2.1.3", "@types/decompress": "^4.2.4", "@types/iarna__toml": "^2.0.2", "@types/inquirer": "^8.2.1", @@ -2136,6 +2138,16 @@ "integrity": "sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==", "dev": true }, + "node_modules/@types/chokidar": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@types/chokidar/-/chokidar-2.1.3.tgz", + "integrity": "sha512-6qK3xoLLAhQVTucQGHTySwOVA1crHRXnJeLwqK6KIFkkKa2aoMFXh+WEi8PotxDtvN6MQJLyYN9ag9P6NLV81w==", + "deprecated": "This is a stub types definition. chokidar provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "chokidar": "*" + } + }, "node_modules/@types/decompress": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/@types/decompress/-/decompress-4.2.4.tgz", @@ -3184,7 +3196,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, "engines": { "node": ">=8" } @@ -3570,7 +3581,6 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, "funding": [ { "type": "individual", @@ -6952,7 +6962,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -12415,7 +12424,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -16894,6 +16902,15 @@ "integrity": "sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==", "dev": true }, + "@types/chokidar": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@types/chokidar/-/chokidar-2.1.3.tgz", + "integrity": "sha512-6qK3xoLLAhQVTucQGHTySwOVA1crHRXnJeLwqK6KIFkkKa2aoMFXh+WEi8PotxDtvN6MQJLyYN9ag9P6NLV81w==", + "dev": true, + "requires": { + "chokidar": "*" + } + }, "@types/decompress": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/@types/decompress/-/decompress-4.2.4.tgz", @@ -17719,8 +17736,7 @@ "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" }, "binaryextensions": { "version": "4.18.0", @@ -18010,7 +18026,6 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, "requires": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -20613,7 +20628,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "requires": { "binary-extensions": "^2.0.0" } @@ -24789,7 +24803,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "requires": { "picomatch": "^2.2.1" } diff --git a/package.json b/package.json index 102b3b7a1..117fe4068 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@oclif/plugin-not-found": "^2.3.1", "ajv": "^8.11.0", "camelcase": "^5.2.0", + "chokidar": "^3.5.3", "decompress": "^4.2.1", "filenamify": "^4", "inquirer": "^8.2.4", @@ -59,6 +60,7 @@ "@tsconfig/node16-strictest": "^1.0.1", "@types/camelcase": "^5.2.0", "@types/chai": "^4", + "@types/chokidar": "^2.1.3", "@types/decompress": "^4.2.4", "@types/iarna__toml": "^2.0.2", "@types/inquirer": "^8.2.1", diff --git a/src/commands/aqua.ts b/src/commands/aqua.ts new file mode 100644 index 000000000..0404eecf2 --- /dev/null +++ b/src/commands/aqua.ts @@ -0,0 +1,150 @@ +/** + * Copyright 2022 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import color from "@oclif/color"; +import { Command, Flags } from "@oclif/core"; +import chokidar from "chokidar"; + +import { initAquaCli } from "../lib/aquaCli"; +import { NO_INPUT_FLAG } from "../lib/const"; +import { getIsInteractive } from "../lib/helpers/getIsInteractive"; +import { ensureFluenceAquaDir } from "../lib/paths"; +import { input } from "../lib/prompt"; + +export default class Aqua extends Command { + static override description = + "Compile aqua file or directory that contains your .aqua files"; + static override examples = ["<%= config.bin %> <%= command.id %>"]; + static override flags = { + input: Flags.string({ + description: + "Path to an aqua file or an input directory that contains your .aqua files", + helpValue: "", + char: "i", + }), + output: Flags.string({ + description: + "Path to the output directory. Will be created if it doesn't exists", + helpValue: "", + char: "o", + }), + import: Flags.string({ + description: + "Path to a directory to import from. May be used several times", + helpValue: "", + multiple: true, + }), + air: Flags.boolean({ + description: "Generate .air file instead of .ts", + exclusive: ["js"], + }), + js: Flags.boolean({ + description: "Generate .js file instead of .ts", + exclusive: ["air"], + }), + "log-level": Flags.string({ + description: "Set log level", + helpValue: "", + }), + const: Flags.string({ + description: "Set log level", + helpValue: "", + }), + "no-relay": Flags.boolean({ + description: "Do not generate a pass through the relay node", + }), + "no-xor": Flags.boolean({ + description: "Do not generate a wrapper that catches and displays errors", + }), + dry: Flags.boolean({ + description: "Checks if compilation is succeeded, without output", + }), + scheduled: Flags.boolean({ + description: + "Generate air code for script storage. Without error handling wrappers and hops on relay. Will ignore other options", + }), + watch: Flags.boolean({ + description: "Watch aqua file or folder for changes and recompile", + char: "w", + }), + ...NO_INPUT_FLAG, + }; + async run(): Promise { + const { flags } = await this.parse(Aqua); + const isInteractive = getIsInteractive(flags); + + const { + watch, + input: inputFromFlags = await input({ + isInteractive, + message: + "Enter path to an aqua file or an input directory that contains your .aqua files", + flagName: "input", + }), + output = await input({ + isInteractive, + message: + "Enter path to the output directory. Will be created if it doesn't exists", + flagName: "input", + }), + import: importsFromFlags, + ...aquaCliOptionalFlags + } = flags; + + const aquaCliFlags = { + input: inputFromFlags, + output, + import: [...(importsFromFlags ?? []), await ensureFluenceAquaDir()], + ...aquaCliOptionalFlags, + }; + + const aquaCli = await initAquaCli(this); + + const compile = (): Promise => + aquaCli({ flags: aquaCliFlags }, "Compiling"); + + if (!watch) { + return this.log(await compile()); + } + + const watchingNotification = (): void => + this.log( + `Watching for changes at ${color.yellow(aquaCliFlags.input)}...` + ); + + watchingNotification(); + + chokidar + .watch(aquaCliFlags.input, { + followSymlinks: false, + usePolling: false, + interval: 100, + binaryInterval: 300, + ignoreInitial: true, + }) + .on("all", (): void => { + compile() + .then((output): void => { + this.log(output); + watchingNotification(); + }) + .catch((error): void => { + this.log(error); + return watchingNotification(); + }); + }); + } +} diff --git a/src/commands/init.ts b/src/commands/init.ts index 72da4062c..d33f90fe8 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -27,7 +27,7 @@ import { initNewReadonlyFluenceConfig } from "../lib/configs/project/fluence"; import { CommandObj, FS_OPTIONS, - RECOMMENDED_GIT_IGNORE_CONTENT, + RECOMMENDED_GITIGNORE_CONTENT, NO_INPUT_FLAG, } from "../lib/const"; import { getIsInteractive } from "../lib/helpers/getIsInteractive"; @@ -101,8 +101,12 @@ const ensureRecommendedExtensions = async (): Promise => { try { fileContent = await fsPromises.readFile(extensionsJsonPath, FS_OPTIONS); } catch { - fileContent = JSON.stringify({}); - await fsPromises.writeFile(extensionsJsonPath, fileContent, FS_OPTIONS); + await fsPromises.writeFile( + extensionsJsonPath, + JSON.stringify(extensionsConfig, null, 2), + FS_OPTIONS + ); + return; } @@ -162,8 +166,12 @@ const ensureRecommendedSettings = async (): Promise => { try { fileContent = await fsPromises.readFile(settingsJsonPath, FS_OPTIONS); } catch { - fileContent = JSON.stringify({}); - await fsPromises.writeFile(settingsJsonPath, fileContent, FS_OPTIONS); + await fsPromises.writeFile( + settingsJsonPath, + JSON.stringify(await initSettingsConfig(), null, 2), + FS_OPTIONS + ); + return; } @@ -205,7 +213,7 @@ const ensureGitIgnore = async (): Promise => { currentGitIgnoreContent.split("\n") ); - const missingGitIgnoreEntries = RECOMMENDED_GIT_IGNORE_CONTENT.split("\n") + const missingGitIgnoreEntries = RECOMMENDED_GITIGNORE_CONTENT.split("\n") .filter((entry): boolean => !currentGitIgnoreEntries.has(entry)) .join("\n"); @@ -214,7 +222,7 @@ const ensureGitIgnore = async (): Promise => { ? currentGitIgnoreContent : `${currentGitIgnoreContent}\n# recommended by Fluence Labs:\n${missingGitIgnoreEntries}\n`; } catch { - newGitIgnoreContent = RECOMMENDED_GIT_IGNORE_CONTENT; + newGitIgnoreContent = RECOMMENDED_GITIGNORE_CONTENT; } return fsPromises.writeFile(gitIgnorePath, newGitIgnoreContent, FS_OPTIONS); diff --git a/src/lib/aquaCli.ts b/src/lib/aquaCli.ts index 0a3a512d0..615025b45 100644 --- a/src/lib/aquaCli.ts +++ b/src/lib/aquaCli.ts @@ -48,7 +48,17 @@ export type AquaCliInput = | { command?: never; flags: Flags<"input" | "output"> & - OptionalFlags<"js"> & { timeout?: never }; + OptionalFlags< + | "js" + | "air" + | "js" + | "log-level" + | "const" + | "no-relay" + | "no-xor" + | "dry" + | "scheduled" + > & { timeout?: never }; }; export type AquaCLI = { diff --git a/src/lib/const.ts b/src/lib/const.ts index cb04f9eae..a9720dfc4 100644 --- a/src/lib/const.ts +++ b/src/lib/const.ts @@ -121,11 +121,10 @@ export const FORCE_FLAG_NAME = "force"; export type CommandObj = Readonly>; -export const RECOMMENDED_GIT_IGNORE_CONTENT = `.idea +export const RECOMMENDED_GITIGNORE_CONTENT = `.idea .DS_Store .fluence **/node_modules -Cargo.lock **/target/ .vscode/settings.json .repl_history`; diff --git a/src/lib/helpers/downloadFile.ts b/src/lib/helpers/downloadFile.ts index 03208a246..0955b08d2 100644 --- a/src/lib/helpers/downloadFile.ts +++ b/src/lib/helpers/downloadFile.ts @@ -60,12 +60,15 @@ const downloadFile = async (path: string, url: string): Promise => { export const stringToCamelCaseName = (string: string): string => { const cleanString = string.replace(".tar.gz?raw=true", ""); - return camelcase( - filenamify( - cleanString.split(cleanString.includes("/") ? "/" : "\\").slice(-1)[0] ?? - "" - ) - ); + const withoutTrailingSlash = cleanString.replace(/\/$/, ""); + + const lastPortionOfPath = + withoutTrailingSlash + .split(withoutTrailingSlash.includes("/") ? "/" : "\\") + .slice(-1)[0] ?? ""; + + const validName = filenamify(lastPortionOfPath); + return camelcase(validName); }; const ARCHIVE_FILE = "archive.tar.gz";