From f76e124b272deea9c34a8269c7b8bc37f93ba488 Mon Sep 17 00:00:00 2001 From: ittechhunter Date: Thu, 4 Apr 2024 22:36:00 +0800 Subject: [PATCH 1/8] Add testnet connection --- gateway/config/flags.js | 2 ++ gateway/src/useAuth.js | 5 ++--- lib/dev.ts | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/gateway/config/flags.js b/gateway/config/flags.js index a3dd080e..075c7255 100644 --- a/gateway/config/flags.js +++ b/gateway/config/flags.js @@ -10,6 +10,8 @@ export const flags = { process.env.BOS_LOADER_WS || config.bosLoaderWs || "ws://127.0.0.1:4040", enableHotReload: process.env.ENABLE_HOT_RELOAD ?? config.enableHotReload ?? true, + network: + process.env.NETWORK || config.network || "mainnet", }; console.log("flags", flags); diff --git a/gateway/src/useAuth.js b/gateway/src/useAuth.js index 5c984e3b..56d4d93e 100644 --- a/gateway/src/useAuth.js +++ b/gateway/src/useAuth.js @@ -4,7 +4,6 @@ import { setupMeteorWallet } from "@near-wallet-selector/meteor-wallet"; import { setupModal } from "@near-wallet-selector/modal-ui"; import "@near-wallet-selector/modal-ui/styles.css"; import { setupMyNearWallet } from "@near-wallet-selector/my-near-wallet"; -import { setupNearWallet } from "@near-wallet-selector/near-wallet"; import { setupNeth } from "@near-wallet-selector/neth"; import { setupNightly } from "@near-wallet-selector/nightly"; import { setupSender } from "@near-wallet-selector/sender"; @@ -20,9 +19,10 @@ import { import React, { useCallback, useEffect, useState } from "react"; import { Link } from "react-router-dom"; import { useEthersProviderContext } from "./useWeb3"; +import { flags } from "../config/flags.js"; export const refreshAllowanceObj = {}; -const NetworkId = "mainnet"; +const NetworkId = flags.network; export function useAuth() { const [connected, setConnected] = useState(false); @@ -46,7 +46,6 @@ export function useAuth() { selector: setupWalletSelector({ network: NetworkId, modules: [ - setupNearWallet(), setupMyNearWallet(), setupSender(), setupHereWallet(), diff --git a/lib/dev.ts b/lib/dev.ts index a9486620..b7d15d2c 100644 --- a/lib/dev.ts +++ b/lib/dev.ts @@ -167,6 +167,7 @@ function startServer(opts: DevOptions, devJsonPath: string) { bosLoaderWs: `ws://127.0.0.1:${port}`, bosLoaderUrl: `http://127.0.0.1:${port}/api/loader`, enableHotReload: opts.NoHot ? false : true, + network: opts.network, }); const withEnv = injectHTML(data, { ENV_CONFIG: envConfig }); res.send(withEnv); From 3d1d660517a39cd509e56d90d99d864d4b43b291 Mon Sep 17 00:00:00 2001 From: ittechhunter Date: Fri, 12 Apr 2024 04:50:26 +0800 Subject: [PATCH 2/8] Fix building issue --- lib/build.ts | 23 ++++++++++++++++++++++- lib/utils/fs.ts | 4 ++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/build.ts b/lib/build.ts index b10b3fc3..8ccb4fdf 100644 --- a/lib/build.ts +++ b/lib/build.ts @@ -1,6 +1,6 @@ import path from "path"; import { readConfig } from "@/lib/config"; -import { writeJson, copy, loopThroughFiles, outputFile, readFile, readJson } from "@/lib/utils/fs"; +import { writeJson, copy, loopThroughFiles, outputFile, readFile, readJson, remove } from "@/lib/utils/fs"; import { transpileJS, EvalCustomSyntaxParams } from "@/lib/parser"; import { Log } from "@/lib/types"; import { UploadToIPFSOptions, uploadToIPFS } from "@/lib/ipfs"; @@ -66,6 +66,9 @@ export async function buildApp(src: string, dest: string, network: string = "mai aliases, }; + const new_build_files: string[] = []; + const original_build_files: string[] = []; + // module transpilation const loadingModules = log.loading(`Transpiling ${modules.length} modules`, LogLevels.BUILD); try { @@ -85,15 +88,18 @@ export async function buildApp(src: string, dest: string, network: string = "mai } }); logs.push(...new_logs); + // write to dest let new_file_name = path.relative(path.join(src, "module"), file).replace("/", "."); new_file_name = new_file_name.substring(0, new_file_name.length - path.extname(file).length); new_file_name += ".module.js"; const new_file_path = path.join(dest, "src", "widget", new_file_name); + new_build_files.push(new_file_path); await outputFile(new_file_path, new_file.code); } + loadingModules.finish(`Transpiled ${modules.length} modules`); } catch (e) { loadingModules.error(`Failed to transpile modules`); @@ -121,20 +127,35 @@ export async function buildApp(src: string, dest: string, network: string = "mai new_logs.forEach(log.send); logs.push(...new_logs); + // write to dest let new_file_name = path.relative(path.join(src, "widget"), file).replace("/", "."); new_file_name = new_file_name.substring(0, new_file_name.length - path.extname(file).length); new_file_name += ".jsx"; const new_file_path = path.join(dest, "src", "widget", new_file_name); + new_build_files.push(new_file_path); + await outputFile(new_file_path, new_file.code); } + loadingWidgets.finish(`Transpiled ${widgets.length} widgets`); } catch (e) { loadingWidgets.error(`Failed to transpile widgets`); throw e; } + // remove unnecessary build files + await loopThroughFiles(path.join(dest, "src", "widget"), async (file: string) => { + original_build_files.push(file); + }) + for (const file of original_build_files) { + if (new_build_files.includes(file)) + continue; + + await remove(file); + } + await log.wait( generateData(src, dest, config.accounts!.deploy!), "Generating data.json", diff --git a/lib/utils/fs.ts b/lib/utils/fs.ts index c9f1c9ab..e56f0d7e 100644 --- a/lib/utils/fs.ts +++ b/lib/utils/fs.ts @@ -1,4 +1,4 @@ -import { copy, readFile, lstat, readJson, writeJson, ensureDir, outputFile, readdir } from 'fs-extra'; +import { copy, readFile, lstat, readJson, writeJson, ensureDir, outputFile, readdir, remove } from 'fs-extra'; import path from 'path'; async function loopThroughFiles(pwd: string, callback: (file: string) => Promise) { @@ -16,4 +16,4 @@ async function loopThroughFiles(pwd: string, callback: (file: string) => Promise } } -export { copy, readJson, writeJson, ensureDir, outputFile, loopThroughFiles, readFile }; +export { copy, readJson, writeJson, ensureDir, outputFile, loopThroughFiles, readFile, remove }; From 3aa6edde389ce9dbec4dc0f2055ba4b76e179285 Mon Sep 17 00:00:00 2001 From: ittechhunter Date: Sat, 13 Apr 2024 03:12:01 +0800 Subject: [PATCH 3/8] Add a unit test --- tests/unit/build.ts | 72 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/tests/unit/build.ts b/tests/unit/build.ts index 56b16d10..e0cf25c7 100644 --- a/tests/unit/build.ts +++ b/tests/unit/build.ts @@ -83,6 +83,69 @@ const app_example_1_output = { }, null, 2) + "\n", }; +const app_example_2 = { + "./bos.config.json": JSON.stringify({ + ...DEFAULT_CONFIG, + account: "test.near", + ipfs: { + gateway: "https://testipfs/ipfs", + }, + format: true, + }), + "./aliases.json": JSON.stringify({ + "name": "world", + }), + "./ipfs/logo.svg": "", + "./module/hello/utils.ts": "const hello = (name: string) => `Hello, ${name}!`; export default { hello };", + "./widget/index.tsx": "type Hello = {}; const hello: Hello = 'hi'; export default hello;", + "./widget/index.metadata.json": JSON.stringify({ + name: "Hello", + description: "Hello world widget", + }), + "./widget/module.tsx": "VM.require('${module_hello_utils}'); export default hello('world');", + "./widget/config.jsx": "return

${config_account}${config_account_deploy}

;", + "./widget/alias.tsx": "export default

Hello ${alias_name}!

;", + "./widget/ipfs.tsx": "export default ;", + "./data/thing/data.json": JSON.stringify({ + "type": "efiz.near/type/thing", + }), + "./data/thing/datastring.jsonc": JSON.stringify({ + name: "Thing", + }), +}; + +const app_example_2_output = { + "/build/ipfs.json": JSON.stringify({ + "logo.svg": "QmHash", + }, null, 2) + "\n", + "/build/src/widget/hello.utils.module.js": "const hello = (name) => `Hello, ${name}!`;\nreturn { hello };\n", + "/build/src/widget/index.jsx": "const hello = \"hi\";\nreturn hello(props);\n", + "/build/src/widget/module.jsx": "VM.require(\"test.near/widget/hello.utils.module\");\nreturn hello(\"world\");\n", + "/build/src/widget/config.jsx": "return

test.neartest.near

;\n", + "/build/src/widget/alias.jsx": "return

Hello world!

;\n", + "/build/src/widget/ipfs.jsx": "return ;\n", + "/build/data.json": JSON.stringify({ + "test.near": { + thing: { + data: { + "type": "efiz.near/type/thing", + }, + datastring: JSON.stringify({ + name: "Thing", + }) + }, + widget: { + index: { + metadata: { + name: "Hello", + description: "Hello world widget", + } + }, + } + } + }, null, 2) + "\n", +}; + const unmockedFetch = global.fetch; const unmockedLog = global.log; @@ -110,4 +173,13 @@ describe('build', () => { expect(logs).toEqual([]); expect(vol.toJSON('/build')).toEqual(app_example_1_output); }) + + it('should build correctly without unnecessary files', async () => { + await buildApp('/app_example_1', '/build'); + expect(vol.toJSON('/build')).toEqual(app_example_1_output); + + vol.fromJSON(app_example_2, '/app_example_2'); + await buildApp('/app_example_2', '/build'); + expect(vol.toJSON('/build')).toEqual(app_example_2_output); + }) }) From a57975dc6b149b51f6013e215db3e6b787635012 Mon Sep 17 00:00:00 2001 From: ittechhunter Date: Sat, 20 Apr 2024 03:52:47 +0800 Subject: [PATCH 4/8] Add deploy code --- lib/cli.ts | 19 ++++++-- lib/deploy.ts | 97 ++++++++++++++++++++++++++++++++++++- lib/utils/fs.ts | 4 +- package.json | 1 + tests/unit/deploy.ts | 113 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 226 insertions(+), 8 deletions(-) create mode 100644 tests/unit/deploy.ts diff --git a/lib/cli.ts b/lib/cli.ts index 2c2a40a1..9b74449c 100644 --- a/lib/cli.ts +++ b/lib/cli.ts @@ -6,6 +6,7 @@ import path from "path"; import { dev } from "./dev"; import { cloneRepository } from "./repository"; import { buildWorkspace, devWorkspace } from "./workspace"; +import { deploy } from "./deploy"; const program = new Command(); @@ -122,11 +123,21 @@ async function run() { program .command("deploy") - .description("Deploy the project (not implemented)") - .argument("[string]", "app name") - .action((appName) => { - console.log("not yet supported"); + .description("Deploy the project") + .argument("[string]", "Workspace app name to deploy") + .option("--deploy-account-id ", "Account under which component code should be deployed") + .option("--signer-account-id ", "Account which will be used for signing deploy transaction, frequently the same as deploy-account-id") + .option("--signer-public-key ", "Public key for signing transactions in the format: `ed25519:`") + .option("--signer-private-key ", "Private key in `ed25519:` format for signing transaction") + .option("-n, --network ", "network to deploy for", "mainnet") + .option("-l, --loglevel ", "log level (ERROR, WARN, INFO, DEV, BUILD, DEBUG)") + .action(async (appName, opts) => { + global.log = new Logger(LogLevel?.[opts?.loglevel?.toUpperCase() as keyof typeof LogLevel] || LogLevel.BUILD); + await deploy(appName, opts).catch((e: Error) => { + log.error(e.stack || e.message); + }) }); + program .command("upload") .description("Upload data to SocialDB (not implemented)") diff --git a/lib/deploy.ts b/lib/deploy.ts index 40166781..b289b034 100644 --- a/lib/deploy.ts +++ b/lib/deploy.ts @@ -1,9 +1,102 @@ -import { BaseConfig } from "./config"; +import path from "path"; +import { exec, ExecException } from "child_process"; +import { BaseConfig, readConfig } from "./config"; +import { buildApp } from "./build"; +import { readWorkspace } from "./workspace"; +import { Network } from "./types"; +import { readdir, rename, remove } from "@/lib/utils/fs"; + +const DEPLOY_DIST_FOLDER = "build"; + +export type DeployOptions = { + deployAccountId: string; + signerAccountId: string; + signerPublicKey: string; + signerPrivateKey: string; + network?: Network; +}; // deploy the app widgets and modules -export async function deployAppCode(src: string, config: BaseConfig) { +export async function deployAppCode(src: string, dist: string, opts: DeployOptions) { + const deploying = log.loading(`[${src}] Deploying app`, LogLevels.BUILD); + + const config = await readConfig(path.join(src, "bos.config.json"), opts.network); + + // Move files from "src/widget" to "src/" + const srcDir = path.join(dist, "src", "widget"); + const targetDir = path.join(dist, "src"); + + const original_files = await readdir(targetDir).catch(() => ([])); + for (const file of original_files) { + if (file == "widget") + continue; + + await remove(file).catch(() => { + deploying.error(`Failed to remove ${path.join(targetDir, file)}`); + }) + } + + const new_files = await readdir(srcDir).catch(() => ([])); + if (new_files.length === 0) { + deploying.error(`Failed to read ${srcDir}`); + return; + } + + for (const file of new_files) { + rename(path.join(srcDir, file), path.join(targetDir, file)).catch(() => { + deploying.error(`Failed to move ${path.join(srcDir, file)}`); + }); + } + + remove(path.join(targetDir, "widget")).catch(() => { + deploying.error(`Failed to remove widget`); + }); + + // Deploy using bos-cli; + const BOS_DEPLOY_ACCOUNT_ID = config.accounts.deploy || opts.deployAccountId; + const BOS_SIGNER_ACCOUNT_ID = config.accounts.signer || opts.signerAccountId; + const BOS_SIGNER_PUBLIC_KEY = opts.signerPublicKey; + const BOS_SIGNER_PRIVATE_KEY = opts.signerPrivateKey; + + await exec( + `cd ${dist} && npx bos components deploy "${BOS_DEPLOY_ACCOUNT_ID}" sign-as "${BOS_SIGNER_ACCOUNT_ID}" network-config "${opts.network}" sign-with-plaintext-private-key --signer-public-key "${BOS_SIGNER_PUBLIC_KEY}" --signer-private-key "${BOS_SIGNER_PRIVATE_KEY}" send`, + (error: ExecException | null, stdout: string, stderr: string) => { + if (!error) { + deploying.finish(`[${src}] App deployed successfully`); + return; + } + + deploying.error(error.message); + } + ); } // publish data.json to SocialDB export async function deployAppData(src: string, config: BaseConfig) { } + +export async function deploy(appName: string, opts: DeployOptions) { + const src = "."; + if (!appName) { + const dist = path.join(src, DEPLOY_DIST_FOLDER); + await buildApp(src, dist, opts.network); + + deployAppCode(src, dist, opts); + + } else { + const { apps } = await readWorkspace(src); + + const findingApp = log.loading(`Finding ${appName} in the workspace`, LogLevels.BUILD); + const appSrc = apps.find((app) => app.includes(appName)); + if (!appSrc) { + findingApp.error(`Not found ${appName} in the workspace`); + return; + } + findingApp.finish(`Found ${appName} in the workspace`); + + const dist = path.join(DEPLOY_DIST_FOLDER, appSrc); + await buildApp(appSrc, dist, opts.network); + + deployAppCode(appSrc, dist, opts); + } +} diff --git a/lib/utils/fs.ts b/lib/utils/fs.ts index e56f0d7e..1ee8b93e 100644 --- a/lib/utils/fs.ts +++ b/lib/utils/fs.ts @@ -1,4 +1,4 @@ -import { copy, readFile, lstat, readJson, writeJson, ensureDir, outputFile, readdir, remove } from 'fs-extra'; +import { copy, readFile, lstat, readJson, writeJson, ensureDir, outputFile, readdir, remove, rename } from 'fs-extra'; import path from 'path'; async function loopThroughFiles(pwd: string, callback: (file: string) => Promise) { @@ -16,4 +16,4 @@ async function loopThroughFiles(pwd: string, callback: (file: string) => Promise } } -export { copy, readJson, writeJson, ensureDir, outputFile, loopThroughFiles, readFile, remove }; +export { copy, readJson, writeJson, ensureDir, outputFile, loopThroughFiles, readFile, readdir, remove, rename }; diff --git a/package.json b/package.json index ed494855..22dc494a 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "author": "", "license": "ISC", "dependencies": { + "bos-cli": "^0.3.13", "commander": "^11.1.0", "crypto-js": "^4.2.0", "express": "^4.18.2", diff --git a/tests/unit/deploy.ts b/tests/unit/deploy.ts new file mode 100644 index 00000000..17b90eee --- /dev/null +++ b/tests/unit/deploy.ts @@ -0,0 +1,113 @@ +import { deploy } from '@/lib/deploy'; +import { BaseConfig, DEFAULT_CONFIG } from '@/lib/config'; +import * as fs from '@/lib/utils/fs'; +import { LogLevel, Logger } from "@/lib/logger"; + +import { vol, } from 'memfs'; +jest.mock('fs', () => require('memfs').fs); +jest.mock('fs/promises', () => require('memfs').fs.promises); + +const app_example = { + "./bos.config.json": JSON.stringify({ + ...DEFAULT_CONFIG, + account: "test.near", + ipfs: { + gateway: "https://testipfs/ipfs", + }, + format: true, + }), + "./aliases.json": JSON.stringify({ + "name": "world", + }), + "./ipfs/logo.svg": "", + "./module/hello/utils.ts": "const hello = (name: string) => `Hello, ${name}!`; export default { hello };", + "./widget/index.tsx": "type Hello = {}; const hello: Hello = 'hi'; export default hello;", + "./widget/index.metadata.json": JSON.stringify({ + name: "Hello", + description: "Hello world widget", + }), + "./widget/nested/index.tsx": "type Hello = {}; const hello: Hello = 'hi'; export default hello;", + "./widget/nested/index.metadata.json": JSON.stringify({ + name: "Nested Hello", + description: "Nested Hello world widget", + }), + "./widget/module.tsx": "VM.require('${module_hello_utils}'); export default hello('world');", + "./widget/config.jsx": "return

${config_account}${config_account_deploy}

;", + "./widget/alias.tsx": "export default

Hello ${alias_name}!

;", + "./widget/ipfs.tsx": "export default ;", + "./data/thing/data.json": JSON.stringify({ + "type": "efiz.near/type/thing", + }), + "./data/thing/datastring.jsonc": JSON.stringify({ + name: "Thing", + }), +}; + +const app_example_output = { + "/build/ipfs.json": JSON.stringify({ + "logo.svg": "QmHash", + }, null, 2) + "\n", + "/build/src/hello.utils.module.js": "const hello = (name) => `Hello, ${name}!`;\nreturn { hello };\n", + "/build/src/index.jsx": "const hello = \"hi\";\nreturn hello(props);\n", + "/build/src/nested.index.jsx": "const hello = \"hi\";\nreturn hello(props);\n", + "/build/src/module.jsx": "VM.require(\"test.near/widget/hello.utils.module\");\nreturn hello(\"world\");\n", + "/build/src/config.jsx": "return

test.neartest.near

;\n", + "/build/src/alias.jsx": "return

Hello world!

;\n", + "/build/src/ipfs.jsx": "return ;\n", + "/build/data.json": JSON.stringify({ + "test.near": { + thing: { + data: { + "type": "efiz.near/type/thing", + }, + datastring: JSON.stringify({ + name: "Thing", + }) + }, + widget: { + index: { + metadata: { + name: "Hello", + description: "Hello world widget", + } + }, + "nested.index": { + metadata: { + name: "Nested Hello", + description: "Nested Hello world widget", + } + + } + } + } + }, null, 2) + "\n", +}; + +const unmockedFetch = global.fetch; +const unmockedLog = global.log; + +describe('build', () => { + beforeEach(() => { + vol.reset(); + vol.fromJSON(app_example, '/app_example'); + + global.fetch = (() => { + return Promise.resolve({ + json: () => Promise.resolve({ + cid: "QmHash", + }) + }) + }) as any; + global.log = new Logger(LogLevel.ERROR); + }) + afterAll(() => { + global.fetch = unmockedFetch; + global.log = unmockedLog; + }) + + it('should build correctly without logs', async () => { + const { logs } = await deploy('/app_example', {}); + expect(logs).toEqual([]); + expect(vol.toJSON('/build')).toEqual(app_example_output); + }) +}) From 5b1b7498aa6c5b47a42f91220c48a9a0b27b907e Mon Sep 17 00:00:00 2001 From: ittechhunter Date: Fri, 26 Apr 2024 22:19:13 +0800 Subject: [PATCH 5/8] Fix build code --- lib/build.ts | 22 ++++++---------------- tests/unit/build.ts | 6 +++--- tests/unit/deploy.ts | 6 +++--- 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/lib/build.ts b/lib/build.ts index 5b013332..14405e36 100644 --- a/lib/build.ts +++ b/lib/build.ts @@ -67,7 +67,6 @@ export async function buildApp(src: string, dest: string, network: string = "mai }; const new_build_files: string[] = []; - const original_build_files: string[] = []; // module transpilation const loadingModules = log.loading(`Transpiling ${modules.length} modules`, LogLevels.BUILD); @@ -90,7 +89,7 @@ export async function buildApp(src: string, dest: string, network: string = "mai logs.push(...new_logs); // write to dest - let new_file_name = path.relative(path.join(src, "module"), file).replace("/", "."); + let new_file_name = path.relative(path.join(src, "module"), file).replace(path.sep, "."); new_file_name = new_file_name.substring(0, new_file_name.length - path.extname(file).length); new_file_name += ".module.js"; @@ -129,7 +128,7 @@ export async function buildApp(src: string, dest: string, network: string = "mai logs.push(...new_logs); // write to dest - let new_file_name = path.relative(path.join(src, "widget"), file).replace("/", "."); + let new_file_name = path.relative(path.join(src, "widget"), file).replace(path.sep, "."); new_file_name = new_file_name.substring(0, new_file_name.length - path.extname(file).length); new_file_name += ".jsx"; @@ -146,22 +145,13 @@ export async function buildApp(src: string, dest: string, network: string = "mai } // remove unnecessary build files - await loopThroughFiles(path.join(dest, "src", "widget"), async (file: string) => { - original_build_files.push(file); - }) + const original_build_files = await readdir(path.join(dest, "src", "widget")).catch(() => []); for (const file of original_build_files) { - if (new_build_files.includes(file)) + const filePath = path.join(dest, "src", "widget", file); + if (new_build_files.includes(filePath)) continue; - const filePathArr = file.split(path.sep); - do { - const dir = filePathArr.join(path.sep); - const files = await readdir(dir).catch(() => ([])); - if (files.length == 0) - await remove(dir); - - filePathArr.pop(); - } while (filePathArr.length > 3) + await remove(filePath); } await log.wait( diff --git a/tests/unit/build.ts b/tests/unit/build.ts index 98991e5f..d510f4e1 100644 --- a/tests/unit/build.ts +++ b/tests/unit/build.ts @@ -47,9 +47,9 @@ const app_example_1_output = { "/build/ipfs.json": JSON.stringify({ "logo.svg": "QmHash", }, null, 2) + "\n", - "/build/src/widget/hello/utils.module.js": "const hello = (name) => `Hello, ${name}!`;\nreturn { hello };\n", + "/build/src/widget/hello.utils.module.js": "const hello = (name) => `Hello, ${name}!`;\nreturn { hello };\n", "/build/src/widget/index.jsx": "const hello = \"hi\";\nreturn hello(props);\n", - "/build/src/widget/nested/index.jsx": "const hello = \"hi\";\nreturn hello(props);\n", + "/build/src/widget/nested.index.jsx": "const hello = \"hi\";\nreturn hello(props);\n", "/build/src/widget/module.jsx": "VM.require(\"test.near/widget/hello.utils.module\");\nreturn hello(\"world\");\n", "/build/src/widget/config.jsx": "return

test.neartest.near

;\n", "/build/src/widget/alias.jsx": "return

Hello world!

;\n", @@ -118,7 +118,7 @@ const app_example_2_output = { "/build/ipfs.json": JSON.stringify({ "logo.svg": "QmHash", }, null, 2) + "\n", - "/build/src/widget/hello/utils.module.js": "const hello = (name) => `Hello, ${name}!`;\nreturn { hello };\n", + "/build/src/widget/hello.utils.module.js": "const hello = (name) => `Hello, ${name}!`;\nreturn { hello };\n", "/build/src/widget/index.jsx": "const hello = \"hi\";\nreturn hello(props);\n", "/build/src/widget/module.jsx": "VM.require(\"test.near/widget/hello.utils.module\");\nreturn hello(\"world\");\n", "/build/src/widget/config.jsx": "return

test.neartest.near

;\n", diff --git a/tests/unit/deploy.ts b/tests/unit/deploy.ts index e5830fb3..29bfd505 100644 --- a/tests/unit/deploy.ts +++ b/tests/unit/deploy.ts @@ -53,9 +53,9 @@ const app_example_output = { "/build/ipfs.json": JSON.stringify({ "logo.svg": "QmHash", }, null, 2) + "\n", - "/build/src/hello/utils.module.js": "const hello = (name) => `Hello, ${name}!`;\nreturn { hello };\n", + "/build/src/hello.utils.module.js": "const hello = (name) => `Hello, ${name}!`;\nreturn { hello };\n", "/build/src/index.jsx": "const hello = \"hi\";\nreturn hello(props);\n", - "/build/src/nested/index.jsx": "const hello = \"hi\";\nreturn hello(props);\n", + "/build/src/nested.index.jsx": "const hello = \"hi\";\nreturn hello(props);\n", "/build/src/module.jsx": "VM.require(\"test.near/widget/hello.utils.module\");\nreturn hello(\"world\");\n", "/build/src/config.jsx": "return

test.neartest.near

;\n", "/build/src/alias.jsx": "return

Hello world!

;\n", @@ -111,7 +111,7 @@ describe('deploy', () => { global.log = unmockedLog; }) - it('should build correctly', async () => { + it('should match expected input for bos-cli-rs', async () => { await deployAppCode('/app_example', '/build', {}); expect(vol.toJSON('/build')).toEqual(app_example_output); }) From 1705cfee0f3af95c50facfb383b70ce15c906883 Mon Sep 17 00:00:00 2001 From: ittechhunter Date: Sat, 27 Apr 2024 01:16:03 +0800 Subject: [PATCH 6/8] fix: Update ReadMe --- README.md | 2 +- lib/cli.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 89d5183c..9c5acba8 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ Commands: init [options] [path] Initialize a new project clone [account] [dest] Clone a SocialDB repository pull [account] Pull updates from a SocialDB repository - deploy [string] Deploy the project (not implemented) + deploy [options] [appName] Deploy the project upload [string] Upload data to SocialDB (not implemented) help [command] display help for command ``` diff --git a/lib/cli.ts b/lib/cli.ts index 9b74449c..88a6815f 100644 --- a/lib/cli.ts +++ b/lib/cli.ts @@ -124,7 +124,7 @@ async function run() { program .command("deploy") .description("Deploy the project") - .argument("[string]", "Workspace app name to deploy") + .argument("[appName]", "Workspace app name to deploy") .option("--deploy-account-id ", "Account under which component code should be deployed") .option("--signer-account-id ", "Account which will be used for signing deploy transaction, frequently the same as deploy-account-id") .option("--signer-public-key ", "Public key for signing transactions in the format: `ed25519:`") From 6f69cb7157156cb0e84c4118c455aff5cedcf028 Mon Sep 17 00:00:00 2001 From: ittechhunter Date: Tue, 30 Apr 2024 00:59:11 +0800 Subject: [PATCH 7/8] Check deploy command validation --- lib/deploy.ts | 89 +++++++++++++++++++++++++++++++------------------ lib/utils/fs.ts | 4 +-- 2 files changed, 58 insertions(+), 35 deletions(-) diff --git a/lib/deploy.ts b/lib/deploy.ts index ebb1c6dc..400d3198 100644 --- a/lib/deploy.ts +++ b/lib/deploy.ts @@ -5,7 +5,7 @@ import { BaseConfig, readConfig } from "@/lib/config"; import { buildApp } from "@/lib/build"; import { readWorkspace } from "@/lib/workspace"; import { Log, Network } from "@/lib/types"; -import { readdir, remove, move } from "@/lib/utils/fs"; +import { readdir, remove, move, pathExists } from "@/lib/utils/fs"; import { Logger } from "./logger"; const DEPLOY_DIST_FOLDER = "build"; @@ -20,8 +20,6 @@ export type DeployOptions = { // translate files from src/widget to src export async function translateForBosCli(dist: string) { - const translating = log.loading(`[${dist}] Translating files from src/widget to src`, LogLevels.BUILD); - const srcDir = path.join(dist, "src", "widget"); const targetDir = path.join(dist, "src"); @@ -29,32 +27,25 @@ export async function translateForBosCli(dist: string) { const original_files = await readdir(targetDir).catch(() => ([])); for (const file of new_files) { - await move(path.join(srcDir, file), path.join(targetDir, file), { overwrite: true }).catch(() => { - translating.error(`Failed to translate: ${path.join(srcDir, file)}`); - }); + await move(path.join(srcDir, file), path.join(targetDir, file), { overwrite: true }); } for (const file of original_files) { if (new_files.includes(file)) continue; - await remove(path.join(targetDir, file)).catch(() => { - translating.error(`Failed to remove: ${path.join(targetDir, file)}`); - }); + await remove(path.join(targetDir, file)); } - - translating.finish(`[${dist}] Translated successfully`); } // deploy the app widgets and modules export async function deployAppCode(src: string, dist: string, opts: DeployOptions) { - const deploying = log.loading(`[${src}] Deploying app`, LogLevels.BUILD); + const fullSrc = path.resolve(src); + const fullDist = path.resolve(dist); - await buildApp(src, dist, opts.network); - - await translateForBosCli(dist); - - // Deploy using bos-cli; + const deploying = log.loading(`[${fullSrc}] Deploying app`, LogLevels.BUILD); + + // Read config const config = await readConfig(path.join(src, "bos.config.json"), opts.network); const BOS_DEPLOY_ACCOUNT_ID = config.accounts.deploy || opts.deployAccountId; @@ -62,18 +53,41 @@ export async function deployAppCode(src: string, dist: string, opts: DeployOptio const BOS_SIGNER_PUBLIC_KEY = opts.signerPublicKey; const BOS_SIGNER_PRIVATE_KEY = opts.signerPrivateKey; + if (!BOS_DEPLOY_ACCOUNT_ID) { + deploying.error(`Necessary values not provided, please provide Account ID for deploy`); + return; + } else if (!BOS_SIGNER_ACCOUNT_ID) { + deploying.error(`Necessary values not provided, please provide Signer Account ID for deploy`); + return; + } else if (!BOS_SIGNER_PUBLIC_KEY || !BOS_SIGNER_PRIVATE_KEY) { + deploying.error(`Necessary values not provided, please provide private & public key for deploy`); + return; + } + + // Build + await buildApp(src, dist, opts.network); + + // Translate for bos cli + await log.wait( + translateForBosCli(dist), + `[${fullDist}] Translating files for bos cli`, + `[${fullDist}] Translated successfully`, + `[${fullDist}] Failed to translate`, + LogLevels.BUILD + ); + + // Exec bos-cli; exec( `cd ${dist} && npx bos components deploy "${BOS_DEPLOY_ACCOUNT_ID}" sign-as "${BOS_SIGNER_ACCOUNT_ID}" network-config "${opts.network}" sign-with-plaintext-private-key --signer-public-key "${BOS_SIGNER_PUBLIC_KEY}" --signer-private-key "${BOS_SIGNER_PRIVATE_KEY}" send`, (error: ExecException | null, stdout: string, stderr: string) => { if (!error) { - deploying.finish(`[${src}] App deployed successfully`); + deploying.finish(`[${fullSrc}] App deployed successfully`); return; } deploying.error(error.message); } ); - deploying.finish(`[${src}] App deployed successfully`); } // publish data.json to SocialDB @@ -82,24 +96,33 @@ export async function deployAppData(src: string, config: BaseConfig) { export async function deploy(appName: string, opts: DeployOptions) { const src = '.'; - if (!appName) { - const dist = path.join(src, DEPLOY_DIST_FOLDER); - await deployAppCode(src, dist, opts); - - } else { - const { apps } = await readWorkspace(src); - - const findingApp = log.loading(`Finding ${appName} in the workspace`, LogLevels.BUILD); - const appSrc = apps.find((app) => app.includes(appName)); - if (!appSrc) { - findingApp.error(`Not found ${appName} in the workspace`); + // Deploy single project + if (!appName) { + if (await pathExists(path.join(src, "bos.config.json"))) { // Check if the directory has bos.config.json file + await deployAppCode(src, path.join(src, DEPLOY_DIST_FOLDER), opts); return; + } else { // Check if the directory has bos.workspace.json file + if (await pathExists(path.join(src, "bos.workspace.json"))) { + log.error(`Please provide app name`); + return; + } } - findingApp.finish(`Found ${appName} in the workspace`); - const dist = path.join(DEPLOY_DIST_FOLDER, appSrc); + log.error(`[${src}] bos.config.json file is not existing in the project`); + return; + } - await deployAppCode(appSrc, dist, opts); + // Deploy workspace app + const { apps } = await readWorkspace(src); + + const findingApp = log.loading(`Finding ${appName} in the workspace`, LogLevels.BUILD); + const appSrc = apps.find((app) => app.includes(appName)); + if (!appSrc) { + findingApp.error(`Not found ${appName} in the workspace`); + return; } + findingApp.finish(`Found ${appName} in the workspace`); + + await deployAppCode(appSrc, path.join(DEPLOY_DIST_FOLDER, appSrc), opts); } diff --git a/lib/utils/fs.ts b/lib/utils/fs.ts index 1bbc1948..a925eb93 100644 --- a/lib/utils/fs.ts +++ b/lib/utils/fs.ts @@ -1,4 +1,4 @@ -import { copy, readFile, lstat, readJson, writeJson, ensureDir, outputFile, readdir, remove, move } from 'fs-extra'; +import { copy, readFile, lstat, readJson, writeJson, ensureDir, outputFile, readdir, remove, move, pathExists } from 'fs-extra'; import path from 'path'; async function loopThroughFiles(pwd: string, callback: (file: string) => Promise) { @@ -16,4 +16,4 @@ async function loopThroughFiles(pwd: string, callback: (file: string) => Promise } } -export { copy, readJson, writeJson, ensureDir, outputFile, loopThroughFiles, readFile, readdir, remove, move }; +export { copy, readJson, writeJson, ensureDir, outputFile, loopThroughFiles, readFile, readdir, remove, move, pathExists }; From 9b99ff86dae4add016a9a9847b7983fc339ea18f Mon Sep 17 00:00:00 2001 From: ittechhunter Date: Tue, 30 Apr 2024 01:35:24 +0800 Subject: [PATCH 8/8] Refactor validation check --- lib/deploy.ts | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/deploy.ts b/lib/deploy.ts index 400d3198..13631b4b 100644 --- a/lib/deploy.ts +++ b/lib/deploy.ts @@ -45,9 +45,21 @@ export async function deployAppCode(src: string, dist: string, opts: DeployOptio const deploying = log.loading(`[${fullSrc}] Deploying app`, LogLevels.BUILD); - // Read config - const config = await readConfig(path.join(src, "bos.config.json"), opts.network); + // Build + await buildApp(src, dist, opts.network); + // Translate for bos cli + await log.wait( + translateForBosCli(dist), + `[${fullDist}] Translating files for bos cli`, + `[${fullDist}] Translated successfully`, + `[${fullDist}] Failed to translate`, + LogLevels.BUILD + ); + + // Exec bos-cli; + const config = await readConfig(path.join(src, "bos.config.json"), opts.network); + const BOS_DEPLOY_ACCOUNT_ID = config.accounts.deploy || opts.deployAccountId; const BOS_SIGNER_ACCOUNT_ID = config.accounts.signer || opts.signerAccountId; const BOS_SIGNER_PUBLIC_KEY = opts.signerPublicKey; @@ -63,20 +75,7 @@ export async function deployAppCode(src: string, dist: string, opts: DeployOptio deploying.error(`Necessary values not provided, please provide private & public key for deploy`); return; } - - // Build - await buildApp(src, dist, opts.network); - - // Translate for bos cli - await log.wait( - translateForBosCli(dist), - `[${fullDist}] Translating files for bos cli`, - `[${fullDist}] Translated successfully`, - `[${fullDist}] Failed to translate`, - LogLevels.BUILD - ); - - // Exec bos-cli; + exec( `cd ${dist} && npx bos components deploy "${BOS_DEPLOY_ACCOUNT_ID}" sign-as "${BOS_SIGNER_ACCOUNT_ID}" network-config "${opts.network}" sign-with-plaintext-private-key --signer-public-key "${BOS_SIGNER_PUBLIC_KEY}" --signer-private-key "${BOS_SIGNER_PRIVATE_KEY}" send`, (error: ExecException | null, stdout: string, stderr: string) => {