From b01dc139a97e5c44f5ada3cd938e2fe1a37908f9 Mon Sep 17 00:00:00 2001 From: dev-ig Date: Mon, 20 Nov 2023 15:08:40 +0100 Subject: [PATCH 1/5] Added new command init functionality: create nomo_cli.config.js --- package.json | 2 +- src/init/init.ts | 87 +++++++++++++++++++++++++++++++------- src/init/interface.ts | 97 ++++++++++++++++++++++++++----------------- 3 files changed, 132 insertions(+), 54 deletions(-) diff --git a/package.json b/package.json index 8cedf3e..3657f1c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nomo-webon-cli", - "version": "0.1.1", + "version": "0.1.0", "description": "A CLI for building and deploying Nomo WebOns", "repository": { "type": "git", diff --git a/src/init/init.ts b/src/init/init.ts index 8ab2775..997eb91 100644 --- a/src/init/init.ts +++ b/src/init/init.ts @@ -1,9 +1,8 @@ import * as fs from "fs"; import * as path from "path"; -import { NomoManifest } from "./interface"; +import { NomoManifest, NomoCliConfig, GeneratedFile } from "./interface"; async function getUserInput(prompt: string): Promise { - // Import inquirer dynamically const inquirer = require("inquirer"); const { userInput } = await (inquirer as any).prompt([ @@ -17,28 +16,84 @@ async function getUserInput(prompt: string): Promise { return userInput; } +async function generateNomoManifestContent( + webonId: string +): Promise { + return { + nomo_manifest_version: "1.1.0", + webon_id: webonId, + webon_name: await getUserInput("Enter webon_name: "), + webon_version: "0.1.0", + permissions: [], + }; +} + +function generateNomoCliConfigContent(webonId: string): NomoCliConfig { + return { + deployTargets: { + production: { + rawSSH: { + sshHost: "root@", + sshBaseDir: `/var/www/production_webons/${webonId}/`, + publicBaseUrl: `https://w.nomo.app/${webonId}`, + }, + }, + staging: { + rawSSH: { + sshHost: process.env.SSH_TARGET || "", + sshBaseDir: `/var/www/html/webons/${webonId}/`, + publicBaseUrl: `https://staging.nomo.app/${webonId}`, + sshPort: 51110, + }, + }, + }, + }; +} + +function writeFile(file: GeneratedFile): void { + fs.writeFileSync(file.filePath, file.content); + console.log(`${path.basename(file.filePath)} created successfully.`); +} + export async function init(args: { assetDir: string }): Promise { const assetDir = args.assetDir; const manifestFilePath = path.join(assetDir, "nomo_manifest.json"); + const cliConfigFilePath = path.join(process.cwd(), "nomo_cli.config.js"); // Check if nomo_manifest.json already exists if (fs.existsSync(manifestFilePath)) { console.log("nomo_manifest.json already exists."); } else { - // Prompt user for input const webonId = await getUserInput("Enter webon_id: "); - const webonName = await getUserInput("Enter webon_name: "); - - // Create nomo_manifest.json with user input - const nomoManifest: NomoManifest = { - nomo_manifest_version: "1.1.0", - webon_id: webonId, - webon_name: webonName, - webon_version: "0.1.0", - permissions: [], - }; - - fs.writeFileSync(manifestFilePath, JSON.stringify(nomoManifest, null, 2)); - console.log("nomo_manifest.json created successfully."); + const nomoManifest = await generateNomoManifestContent(webonId); + + writeFile({ + filePath: manifestFilePath, + content: JSON.stringify(nomoManifest, null, 2), + }); + } + + // Check if nomo_cli.config.js already exists + if (fs.existsSync(cliConfigFilePath)) { + console.log("nomo_cli.config.js already exists."); + } else { + const nomoManifestContent = fs.readFileSync(manifestFilePath, "utf-8"); + const nomoManifest: NomoManifest = JSON.parse(nomoManifestContent); + + const webonId = nomoManifest.webon_id; + const nomoCliConfig = generateNomoCliConfigContent(webonId); + + writeFile({ + filePath: cliConfigFilePath, + content: `/** + * This is a sample configuration that can be adapted to your needs. + */ + +const nomoCliConfig = ${JSON.stringify(nomoCliConfig, null, 2)}; + +module.exports = { + nomoCliConfig, +};`, + }); } } diff --git a/src/init/interface.ts b/src/init/interface.ts index cfc2f88..2c53136 100644 --- a/src/init/interface.ts +++ b/src/init/interface.ts @@ -1,38 +1,61 @@ export interface NomoManifest { - /** - * If min_nomo_version is set, then outdated versions of the Nomo App will refuse to install the WebOn. - */ - min_nomo_version?: string | null; - /** - * nomo_manifest_version should be 1.1.0. - */ - nomo_manifest_version: string; - /** - * A list of permissions for security-critical features. - */ - permissions: string[]; - /** - * webon_id should be the reverse-domain of a domain that is owned by the WebOn-author. - * See https://en.wikipedia.org/wiki/Reverse_domain_name_notation for more details about the reverse domain name notation. - */ - webon_id: string; - /** - * webon_name is the user-visible name of the WebOn. - */ - webon_name: string; - /** - * webon_version should comply with the semantic versioning standard. - * See https://semver.org/ for details. - */ - webon_version: string; - /** - * If true, then the WebOn could be displayed in both card-mode and fullscreen-mode. - * If false, then the WebOn will only be displayed in fullscreen-mode. - */ - card_mode?: boolean; - /** - * If defined, then the WebOn can decide whether a navigation bar should be shown or not. - */ - show_navbar?: boolean; - } - \ No newline at end of file + /** + * If min_nomo_version is set, then outdated versions of the Nomo App will refuse to install the WebOn. + */ + min_nomo_version?: string | null; + /** + * nomo_manifest_version should be 1.1.0. + */ + nomo_manifest_version: string; + /** + * A list of permissions for security-critical features. + */ + permissions: string[]; + /** + * webon_id should be the reverse-domain of a domain that is owned by the WebOn-author. + * See https://en.wikipedia.org/wiki/Reverse_domain_name_notation for more details about the reverse domain name notation. + */ + webon_id: string; + /** + * webon_name is the user-visible name of the WebOn. + */ + webon_name: string; + /** + * webon_version should comply with the semantic versioning standard. + * See https://semver.org/ for details. + */ + webon_version: string; + /** + * If true, then the WebOn could be displayed in both card-mode and fullscreen-mode. + * If false, then the WebOn will only be displayed in fullscreen-mode. + */ + card_mode?: boolean; + /** + * If defined, then the WebOn can decide whether a navigation bar should be shown or not. + */ + show_navbar?: boolean; +} + +export interface NomoCliConfig { + deployTargets: { + production: { + rawSSH: { + sshHost: string; + sshBaseDir: string; + publicBaseUrl: string; + }; + }; + staging: { + rawSSH: { + sshHost: string; + sshBaseDir: string; + publicBaseUrl: string; + sshPort: number; + }; + }; + }; +} +export interface GeneratedFile { + filePath: string; + content: string; +} \ No newline at end of file From 63b4269784b3132a56fd77ddb24449c81a813904 Mon Sep 17 00:00:00 2001 From: dev-ig Date: Mon, 20 Nov 2023 15:08:48 +0100 Subject: [PATCH 2/5] Added create cli_configs.js --- nomo_cli.config.js | 34 +++++++++++++++++----------------- out/nomo_manifest.json | 3 +-- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/nomo_cli.config.js b/nomo_cli.config.js index ee7d26e..60efea7 100644 --- a/nomo_cli.config.js +++ b/nomo_cli.config.js @@ -3,25 +3,25 @@ */ const nomoCliConfig = { - deployTargets: { - production: { - rawSSH: { - sshHost: "root@", - sshBaseDir: "/var/www/production_webons/${webon_id}/", - publicBaseUrl: "https://w.nomo.app/${webon_id}", - }, + "deployTargets": { + "production": { + "rawSSH": { + "sshHost": "root@", + "sshBaseDir": "/var/www/production_webons/demo.nomo.app/", + "publicBaseUrl": "https://w.nomo.app/demo.nomo.app" + } }, - staging: { - rawSSH: { - sshHost: process.env.SSH_TARGET, - sshBaseDir: "/var/www/html/webons/${webon_id}/", - publicBaseUrl: "https://staging.nomo.app/${webon_id}", - sshPort: 51110, - }, - }, - }, + "staging": { + "rawSSH": { + "sshHost": "some_value", + "sshBaseDir": "/var/www/html/webons/demo.nomo.app/", + "publicBaseUrl": "https://staging.nomo.app/demo.nomo.app", + "sshPort": 51110 + } + } + } }; module.exports = { nomoCliConfig, -}; +}; \ No newline at end of file diff --git a/out/nomo_manifest.json b/out/nomo_manifest.json index b9e416a..514318b 100644 --- a/out/nomo_manifest.json +++ b/out/nomo_manifest.json @@ -1,8 +1,7 @@ { "nomo_manifest_version": "1.1.0", "webon_id": "demo.nomo.app", - "webon_name": "DEMO APP", + "webon_name": "Demo App", "webon_version": "0.1.0", - "min_nomo_version": "0.3.1", "permissions": [] } \ No newline at end of file From 8f44d1975b36d53c258d7241d41c4648afa3d617 Mon Sep 17 00:00:00 2001 From: dev-ig Date: Mon, 20 Nov 2023 15:18:51 +0100 Subject: [PATCH 3/5] refactored some code --- nomo_cli.config.js | 27 --------------------------- out/nomo_manifest.json | 7 ------- src/init/init.ts | 8 +++++--- 3 files changed, 5 insertions(+), 37 deletions(-) delete mode 100644 nomo_cli.config.js delete mode 100644 out/nomo_manifest.json diff --git a/nomo_cli.config.js b/nomo_cli.config.js deleted file mode 100644 index 60efea7..0000000 --- a/nomo_cli.config.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * This is a sample configuration that can be adapted to your needs. - */ - -const nomoCliConfig = { - "deployTargets": { - "production": { - "rawSSH": { - "sshHost": "root@", - "sshBaseDir": "/var/www/production_webons/demo.nomo.app/", - "publicBaseUrl": "https://w.nomo.app/demo.nomo.app" - } - }, - "staging": { - "rawSSH": { - "sshHost": "some_value", - "sshBaseDir": "/var/www/html/webons/demo.nomo.app/", - "publicBaseUrl": "https://staging.nomo.app/demo.nomo.app", - "sshPort": 51110 - } - } - } -}; - -module.exports = { - nomoCliConfig, -}; \ No newline at end of file diff --git a/out/nomo_manifest.json b/out/nomo_manifest.json deleted file mode 100644 index 514318b..0000000 --- a/out/nomo_manifest.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "nomo_manifest_version": "1.1.0", - "webon_id": "demo.nomo.app", - "webon_name": "Demo App", - "webon_version": "0.1.0", - "permissions": [] -} \ No newline at end of file diff --git a/src/init/init.ts b/src/init/init.ts index 997eb91..f45518c 100644 --- a/src/init/init.ts +++ b/src/init/init.ts @@ -17,12 +17,13 @@ async function getUserInput(prompt: string): Promise { } async function generateNomoManifestContent( - webonId: string + webonId: string, + webonName: string ): Promise { return { nomo_manifest_version: "1.1.0", webon_id: webonId, - webon_name: await getUserInput("Enter webon_name: "), + webon_name: webonName, webon_version: "0.1.0", permissions: [], }; @@ -64,8 +65,9 @@ export async function init(args: { assetDir: string }): Promise { if (fs.existsSync(manifestFilePath)) { console.log("nomo_manifest.json already exists."); } else { + const webonName = await getUserInput("Enter webon_name: "); const webonId = await getUserInput("Enter webon_id: "); - const nomoManifest = await generateNomoManifestContent(webonId); + const nomoManifest = await generateNomoManifestContent(webonId, webonName); writeFile({ filePath: manifestFilePath, From 0b191549d50be4f1f0b01dd2cb72a24d9a82f297 Mon Sep 17 00:00:00 2001 From: dev-ig Date: Mon, 20 Nov 2023 15:34:34 +0100 Subject: [PATCH 4/5] Refactored some code. --- src/init/init.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/init/init.ts b/src/init/init.ts index f45518c..4d76307 100644 --- a/src/init/init.ts +++ b/src/init/init.ts @@ -41,7 +41,7 @@ function generateNomoCliConfigContent(webonId: string): NomoCliConfig { }, staging: { rawSSH: { - sshHost: process.env.SSH_TARGET || "", + sshHost: process.env.SSH_TARGET || "Set your env SSH_TARGET like: export SSH_TARGET= ", sshBaseDir: `/var/www/html/webons/${webonId}/`, publicBaseUrl: `https://staging.nomo.app/${webonId}`, sshPort: 51110, From 14b0cfa81edc53b82d82aea0ad9c2d5c315eb556 Mon Sep 17 00:00:00 2001 From: dev-ig Date: Mon, 20 Nov 2023 16:48:34 +0100 Subject: [PATCH 5/5] Added webon_id checks in init --- src/init/init.ts | 19 ++++++++- src/util/validate-manifest.ts | 75 +++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 src/util/validate-manifest.ts diff --git a/src/init/init.ts b/src/init/init.ts index 4d76307..fddcbd2 100644 --- a/src/init/init.ts +++ b/src/init/init.ts @@ -1,6 +1,7 @@ import * as fs from "fs"; import * as path from "path"; import { NomoManifest, NomoCliConfig, GeneratedFile } from "./interface"; +import { isValidWebOnId } from "../util/validate-manifest"; async function getUserInput(prompt: string): Promise { const inquirer = require("inquirer"); @@ -41,7 +42,9 @@ function generateNomoCliConfigContent(webonId: string): NomoCliConfig { }, staging: { rawSSH: { - sshHost: process.env.SSH_TARGET || "Set your env SSH_TARGET like: export SSH_TARGET= ", + sshHost: + process.env.SSH_TARGET || + "Set your env SSH_TARGET like: export SSH_TARGET= ", sshBaseDir: `/var/www/html/webons/${webonId}/`, publicBaseUrl: `https://staging.nomo.app/${webonId}`, sshPort: 51110, @@ -66,7 +69,8 @@ export async function init(args: { assetDir: string }): Promise { console.log("nomo_manifest.json already exists."); } else { const webonName = await getUserInput("Enter webon_name: "); - const webonId = await getUserInput("Enter webon_id: "); + const webonId = await getValidWebOnId("Enter unique webon_id: "); + const nomoManifest = await generateNomoManifestContent(webonId, webonName); writeFile({ @@ -99,3 +103,14 @@ module.exports = { }); } } + +async function getValidWebOnId(prompt: string): Promise { + let webonId = await getUserInput(prompt); + while (!isValidWebOnId(webonId)) { + console.error(`Invalid webon_id: ${webonId}`); + webonId = await getUserInput( + "Enter an unique valid webon_id like demo.web.app for example:" + ); + } + return webonId; +} diff --git a/src/util/validate-manifest.ts b/src/util/validate-manifest.ts new file mode 100644 index 0000000..857f146 --- /dev/null +++ b/src/util/validate-manifest.ts @@ -0,0 +1,75 @@ +import { NomoManifest } from "../init/interface"; + +class WebOnError extends Error { + constructor(message: string) { + super(message); + this.name = "WebOnError"; + } +} + +async function validateManifest( + manifest: NomoManifest, + webonUrl: string, + { devMode }: { devMode: boolean } +): Promise { + const webonVersion = manifest.webon_version; + if (!_isValidSemanticVersion(webonVersion)) { + throw new WebOnError( + `webon_version ${webonVersion} does not comply with semantic versioning regexp` + ); + } + + const webonId = manifest.webon_id; + if (!isValidWebOnId(webonId)) { + throw new WebOnError(`webon_id ${webonId} does not comply with regexp`); + } + + const manifestVersion = manifest.nomo_manifest_version; + if (!_isValidSemanticVersion(manifestVersion)) { + throw new WebOnError( + `nomo_manifest_version ${manifestVersion} does not comply with semantic versioning regexp` + ); + } + + if (manifest.webon_name.trim() == null) { + throw new WebOnError("webon_name is empty"); + } + + const minNomoVersion = manifest.min_nomo_version; + if (minNomoVersion != null) { + if (!_isValidSemanticVersion(minNomoVersion)) { + throw new WebOnError( + `min_nomo_version ${minNomoVersion} does not comply with semantic versioning regexp` + ); + } + // Assume you have a function similar to versionTwoGreaterThanVersionOne + const currentVersion = "1.2.0"; // You need to replace this with the actual version + if (versionTwoGreaterThanVersionOne(currentVersion, minNomoVersion)) { + throw new WebOnError( + `Nomo App outdated! This WebOn requires ${minNomoVersion}, but the current version is ${currentVersion}` + ); + } + } +} + + +function _isValidSemanticVersion(version: string): boolean { + const pattern = /^(\d+)\.(\d+)\.(\d+)$/; + const regex = new RegExp(pattern); + return regex.test(version); +} + +// Assuming versionTwoGreaterThanVersionOne is a function you have implemented +function versionTwoGreaterThanVersionOne( + versionTwo: string, + versionOne: string +): boolean { + // Implement the comparison logic here + return false; +} + +export function isValidWebOnId(webon_id: string): boolean { + const webonIdRegExp = + /^(?:[a-zA-Z0-9_-]+\.)*[a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)+$/; + return webonIdRegExp.test(webon_id); + } \ No newline at end of file