From c75710ba479f307d10a62dbaca523fe360e38bcd Mon Sep 17 00:00:00 2001 From: Derek Anderson Date: Mon, 1 Jul 2024 11:09:55 -0500 Subject: [PATCH] pins the sdk to another version, and adds escape wrapper around headers Signed-off-by: Derek Anderson --- package.json | 2 +- src/commands/sites/preview.ts | 384 ++++++++++++------------- src/commands/sites/shared.ts | 512 ++++++++++++++++++---------------- 3 files changed, 470 insertions(+), 428 deletions(-) diff --git a/package.json b/package.json index 98a332d..e5dfe1f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockless/cli", - "version": "0.0.4-development", + "version": "0.0.5-development", "description": "blockless cli client, manage, interact with and deploy blockless applications.", "main": "dist/src/index.js", "private": false, diff --git a/src/commands/sites/preview.ts b/src/commands/sites/preview.ts index 5cb956c..d43a387 100644 --- a/src/commands/sites/preview.ts +++ b/src/commands/sites/preview.ts @@ -1,191 +1,199 @@ -import fs from "fs" -import Chalk from "chalk" -import { store } from "../../store" -import { execSync } from "child_process" -import { resolve } from "path" -import { parseBlsConfig } from "../../lib/blsConfig" -import { logger } from "../../lib/logger" -import { run as runBuild } from "./build" -import { run as runInstall } from "../offchain/install" -import prompRuntimeConfirm from "../../prompts/runtime/confirm" -import Fastify from "fastify" +import fs from "fs"; +import Chalk from "chalk"; +import { store } from "../../store"; +import { execSync } from "child_process"; +import { resolve } from "path"; +import { parseBlsConfig } from "../../lib/blsConfig"; +import { logger } from "../../lib/logger"; +import { run as runBuild } from "./build"; +import { run as runInstall } from "../offchain/install"; +import prompRuntimeConfirm from "../../prompts/runtime/confirm"; +import Fastify from "fastify"; import { getPortPromise } from "portfinder"; export const run = async (options: any) => { - const { - systemPath = `${store.system.homedir}/.bls/`, - path = process.cwd(), - debug = true, - rebuild = false, - } = options - - const runtimePath = `${systemPath}runtime/bls-runtime` - - // Validate Runtime Path - try { - if (!fs.existsSync(runtimePath)) { - const { confirm } = await prompRuntimeConfirm() - - if (!confirm) { - throw new Error("Cancelled by user, aborting invoke.") - } else { - await runInstall({ yes: true, inline: true }) - console.log(Chalk.green('Installation successful!')) - console.log('') - console.log('') - } - } - } catch (error) { - logger.error('Failed to install blockless runtime, please try installing the runtime manually.') - return - } - - try { - // Fetch BLS config - const { build, build_release } = parseBlsConfig() - - // Execute the build command - await runBuild({ path, debug, rebuild }) - - // check for and store unmodified wasm file name to change later - const buildConfig = !debug ? build_release : build - const buildDir = resolve(path, buildConfig.dir || 'build') - const manifestPath = resolve(buildDir, 'manifest.json') - - // the runtime requires absolute paths - let manifestData = fs.readFileSync(manifestPath, "utf8") - let manifest = JSON.parse(manifestData) - manifest.drivers_root_path = `${systemPath}/extensions` - manifest.modules = manifest.modules.map((m: any) => { - m.file = resolve(buildDir, m.file) - return m - }) - fs.writeFileSync(manifestPath, JSON.stringify(manifest)) - - // prepare environment variables - // pass environment variables to bls runtime - let envString = '' - - if (!!options.env) { - let envVars = [] as string[] - let envVarsKeys = [] as string[] - - // Validate environment variables - const vars = typeof options.env === 'string' ? [options.env] : options.env - vars.map((v: string) => { - const split = v.split('=') - if (split.length !== 2) return - - envVars.push(v) - envVarsKeys.push(split[0]) - }) - - // Include environment string if there are variables - if (envVars.length > 0) { - envString = `env ${envVars.join(' ')} BLS_LIST_VARS=\"${envVarsKeys.join(';')}\"` - } - } - - const fastify = Fastify({ - logger: false, - maxParamLength: 10000 - }) - - await fastify.register(import('@fastify/rate-limit'), { - max: 100, - timeWindow: '1 minute' - }) - - await fastify.register(import('@fastify/cors')) - - fastify.get("*", async (request, reply) => { - let qs = '' - let headerString = '' - let requestPath = decodeURIComponent(request.url.trim()) - - if (requestPath.includes('?')) { - qs = requestPath.split('?')[1] - requestPath = requestPath.split('?')[0] - } - - if (request.headers) { - headerString = Object.entries(request.headers) - .map(([key, value]) => `${key}=${value}`) - .join('&') - } - - let envString = '' - let envVars = [] as string[] - let envVarsKeys = [] as string[] - - if (!!options.env) { - // Validate environment variables - const vars = typeof options.env === 'string' ? [options.env] : options.env - vars.map((v: string) => { - const split = v.split('=') - if (split.length !== 2) return - - envVars.push(v) - envVarsKeys.push(split[0]) - }) - } - - envVars.push(`BLS_REQUEST_PATH="${requestPath}"`) - envVars.push(`BLS_REQUEST_QUERY="${qs}"`) - envVars.push(`BLS_REQUEST_METHOD="${request.method}"`) - envVars.push(`BLS_REQUEST_HEADERS="${headerString}"`) - envVarsKeys.push('BLS_REQUEST_PATH') - envVarsKeys.push('BLS_REQUEST_QUERY') - envVarsKeys.push('BLS_REQUEST_METHOD') - envVarsKeys.push('BLS_REQUEST_HEADERS') - - if (request.body) { - envVars.push(`BLS_REQUEST_BODY="${encodeURIComponent(JSON.stringify(request.body))}"`) - envVarsKeys.push('BLS_REQUEST_BODY') - } - - // Include environment string if there are variables - if (envVars.length > 0) { - envString = `env ${envVars.join(' ')} BLS_LIST_VARS=\"${envVarsKeys.join(';')}\"` - } - - const result = execSync(`echo "${decodeURIComponent(request.url.trim())}" | ${envString} ${runtimePath} ${manifestPath}`, { - cwd: path, - maxBuffer: (10000 * 1024) - }).toString() - - if (!manifest.contentType || manifest.contentType === 'json' && result) { - try { - const resultJson = JSON.parse(result) - - reply - .header("Content-Type", "application/json") - .send(resultJson) - } catch (error) { } - } else if (manifest.contentType === "html" && result) { - const body = result - - if (body.startsWith("data:")) { - const data = body.split(",")[1] - const contentType = body.split(",")[0].split(":")[1].split(";")[0] - const base64data = Buffer.from(data, "base64") - reply.type(contentType).send(base64data) - } else { - reply - .header("Content-Type", "text/html") - .send(body) - } - } else { - reply.send(result) - } - }) - - const port = await getPortPromise({ port: 3000, stopPort: 4000 }) - - fastify.listen({ port }).then(async () => { - console.log(`Serving http://127.0.0.1:${port} ...`) - }) - } catch (error: any) { - logger.error('Failed to invoke function.', error.message) - } -} \ No newline at end of file + const { + systemPath = `${store.system.homedir}/.bls/`, + path = process.cwd(), + debug = true, + rebuild = false, + } = options; + + const runtimePath = `${systemPath}runtime/bls-runtime`; + + // Validate Runtime Path + try { + if (!fs.existsSync(runtimePath)) { + const { confirm } = await prompRuntimeConfirm(); + + if (!confirm) { + throw new Error("Cancelled by user, aborting invoke."); + } else { + await runInstall({ yes: true, inline: true }); + console.log(Chalk.green("Installation successful!")); + console.log(""); + console.log(""); + } + } + } catch (error) { + logger.error( + "Failed to install blockless runtime, please try installing the runtime manually.", + ); + return; + } + + try { + // Fetch BLS config + const { build, build_release } = parseBlsConfig(); + + // Execute the build command + await runBuild({ path, debug, rebuild }); + + // check for and store unmodified wasm file name to change later + const buildConfig = !debug ? build_release : build; + const buildDir = resolve(path, buildConfig.dir || "build"); + const manifestPath = resolve(buildDir, "manifest.json"); + + // the runtime requires absolute paths + let manifestData = fs.readFileSync(manifestPath, "utf8"); + let manifest = JSON.parse(manifestData); + manifest.drivers_root_path = `${systemPath}/extensions`; + manifest.modules = manifest.modules.map((m: any) => { + m.file = resolve(buildDir, m.file); + return m; + }); + fs.writeFileSync(manifestPath, JSON.stringify(manifest)); + + // prepare environment variables + // pass environment variables to bls runtime + let envString = ""; + + if (!!options.env) { + let envVars = [] as string[]; + let envVarsKeys = [] as string[]; + + // Validate environment variables + const vars = + typeof options.env === "string" ? [options.env] : options.env; + vars.map((v: string) => { + const split = v.split("="); + if (split.length !== 2) return; + + envVars.push(v); + envVarsKeys.push(split[0]); + }); + + // Include environment string if there are variables + if (envVars.length > 0) { + envString = `env ${envVars.join(" ")} BLS_LIST_VARS=\"${envVarsKeys.join(";")}\"`; + } + } + + const fastify = Fastify({ + logger: false, + maxParamLength: 10000, + }); + + await fastify.register(import("@fastify/rate-limit"), { + max: 100, + timeWindow: "1 minute", + }); + + await fastify.register(import("@fastify/cors")); + + fastify.get("*", async (request, reply) => { + let qs = ""; + let headerString = ""; + let requestPath = decodeURIComponent(request.url.trim()); + + if (requestPath.includes("?")) { + qs = requestPath.split("?")[1]; + requestPath = requestPath.split("?")[0]; + } + + if (request.headers) { + headerString = Object.entries(request.headers) + .map(([key, value]) => `${key}=${encodeURIComponent(`${value}`)}`) + .join("&"); + } + + let envString = ""; + let envVars = [] as string[]; + let envVarsKeys = [] as string[]; + + if (!!options.env) { + // Validate environment variables + const vars = + typeof options.env === "string" ? [options.env] : options.env; + vars.map((v: string) => { + const split = v.split("="); + if (split.length !== 2) return; + + envVars.push(v); + envVarsKeys.push(split[0]); + }); + } + + envVars.push(`BLS_REQUEST_PATH="${requestPath}"`); + envVars.push(`BLS_REQUEST_QUERY="${qs}"`); + envVars.push(`BLS_REQUEST_METHOD="${request.method}"`); + envVars.push(`BLS_REQUEST_HEADERS="${headerString}"`); + envVarsKeys.push("BLS_REQUEST_PATH"); + envVarsKeys.push("BLS_REQUEST_QUERY"); + envVarsKeys.push("BLS_REQUEST_METHOD"); + envVarsKeys.push("BLS_REQUEST_HEADERS"); + + if (request.body) { + envVars.push( + `BLS_REQUEST_BODY="${encodeURIComponent(JSON.stringify(request.body))}"`, + ); + envVarsKeys.push("BLS_REQUEST_BODY"); + } + + // Include environment string if there are variables + if (envVars.length > 0) { + envString = `env ${envVars.join(" ")} BLS_LIST_VARS=\"${envVarsKeys.join(";")}\"`; + } + + const result = execSync( + `echo "${decodeURIComponent(request.url.trim())}" | ${envString} ${runtimePath} ${manifestPath}`, + { + cwd: path, + maxBuffer: 10000 * 1024, + }, + ).toString(); + + if ( + !manifest.contentType || + (manifest.contentType === "json" && result) + ) { + try { + const resultJson = JSON.parse(result); + + reply.header("Content-Type", "application/json").send(resultJson); + } catch (error) {} + } else if (manifest.contentType === "html" && result) { + const body = result; + + if (body.startsWith("data:")) { + const data = body.split(",")[1]; + const contentType = body.split(",")[0].split(":")[1].split(";")[0]; + const base64data = Buffer.from(data, "base64"); + reply.type(contentType).send(base64data); + } else { + reply.header("Content-Type", "text/html").send(body); + } + } else { + reply.send(result); + } + }); + + const port = await getPortPromise({ port: 3000, stopPort: 4000 }); + + fastify.listen({ port }).then(async () => { + console.log(`Serving http://127.0.0.1:${port} ...`); + }); + } catch (error: any) { + logger.error("Failed to invoke function.", error.message); + } +}; diff --git a/src/commands/sites/shared.ts b/src/commands/sites/shared.ts index ed4ab2f..593592c 100644 --- a/src/commands/sites/shared.ts +++ b/src/commands/sites/shared.ts @@ -1,265 +1,293 @@ -import Chalk from 'chalk' -import os from 'os' -import fs, { existsSync } from "fs" -import path, { resolve } from "path" -import { execSync } from "child_process" -import { IBlsBuildConfig } from "../function/interfaces" -import { copyFileSync, getDirectoryContents } from '../../lib/dir' -import { slugify } from '../../lib/strings' -import { getTsExportedFunctions } from '../../lib/sourceFile' +import Chalk from "chalk"; +import os from "os"; +import fs, { existsSync } from "fs"; +import path, { resolve } from "path"; +import { execSync } from "child_process"; +import { IBlsBuildConfig } from "../function/interfaces"; +import { copyFileSync, getDirectoryContents } from "../../lib/dir"; +import { slugify } from "../../lib/strings"; +import { getTsExportedFunctions } from "../../lib/sourceFile"; interface IFunctionRoute { - path: string - url: string - nameSlug: string - name: string - wildcard: string | null - exportedFunctions: string[] + path: string; + url: string; + nameSlug: string; + name: string; + wildcard: string | null; + exportedFunctions: string[]; } /** * Build a WASM project based on the build config. - * - * @param wasmName - * @param buildDir - * @param path - * @param buildConfig - * @param debug + * + * @param wasmName + * @param buildDir + * @param path + * @param buildConfig + * @param debug */ export const buildSiteWasm = async ( - wasmName: string, - buildDir: string, - path: string, - buildConfig: IBlsBuildConfig, - debug: boolean) => { - console.log(`${Chalk.yellow(`Building:`)} site ${wasmName} in ${buildDir}...`) - - if (buildConfig.command) { - try { - // Identify package manager - const packageManager = buildConfig.command.split(" ", 2)[0] - - // Run install command - switch (packageManager) { - case 'npm': - execSync(`npm install`, { cwd: path, stdio: 'ignore' }) - break - - case 'cargo': - execSync(`cargo update`, { cwd: path, stdio: 'ignore' }) - break - } - } catch {} - - // Run framework build command - execSync(buildConfig.command, { - cwd: path, - stdio: "inherit" - }) - } - - // Compile wasm for blockless site - const publicDir = resolve(path, buildConfig.public_dir || 'out') - - return await doCompile(wasmName, publicDir, buildDir) -} + wasmName: string, + buildDir: string, + path: string, + buildConfig: IBlsBuildConfig, + debug: boolean, +) => { + console.log( + `${Chalk.yellow(`Building:`)} site ${wasmName} in ${buildDir}...`, + ); + + if (buildConfig.command) { + try { + // Identify package manager + const packageManager = buildConfig.command.split(" ", 2)[0]; + + // Run install command + switch (packageManager) { + case "npm": + execSync(`npm install`, { cwd: path, stdio: "ignore" }); + break; + + case "cargo": + execSync(`cargo update`, { cwd: path, stdio: "ignore" }); + break; + } + } catch {} + + // Run framework build command + execSync(buildConfig.command, { + cwd: path, + stdio: "inherit", + }); + } + + // Compile wasm for blockless site + const publicDir = resolve(path, buildConfig.public_dir || "out"); + + return await doCompile(wasmName, publicDir, buildDir); +}; /** * Helper function to compile - * - * @param source - * @param dest - * @returns + * + * @param source + * @param dest + * @returns */ -const doCompile = (name: string, source: string, dest: string = path.resolve(process.cwd(), '.bls')) => { - return new Promise(async (resovle, reject) => { - try { - // Pack files and generate a tempory assembly script - console.log('') - const { dir } = generateCompileDirectory(source, dest) - - console.log('') - console.log(`${Chalk.yellow(`Generating site:`)} at ${dest} ...`) - execSync(`npm install && npm run build -- -o ${path.resolve(dest, name)}`, { - cwd: dir - }) - console.log('') - - // Clear temp source files - fs.rmSync(dir, { recursive: true, force: true }) - - resovle(path.resolve(dest, name)) - } catch (error) { - reject(error) - } - }) -} +const doCompile = ( + name: string, + source: string, + dest: string = path.resolve(process.cwd(), ".bls"), +) => { + return new Promise(async (resovle, reject) => { + try { + // Pack files and generate a tempory assembly script + console.log(""); + const { dir } = generateCompileDirectory(source, dest); + + console.log(""); + console.log(`${Chalk.yellow(`Generating site:`)} at ${dest} ...`); + execSync( + `npm install && npm run build -- -o ${path.resolve(dest, name)}`, + { + cwd: dir, + }, + ); + console.log(""); + + // Clear temp source files + fs.rmSync(dir, { recursive: true, force: true }); + + resovle(path.resolve(dest, name)); + } catch (error) { + reject(error); + } + }); +}; /** * Read source folder's file recurrsively, pack all folder contents in a single .ts file - * - * @param source - * @param dest + * + * @param source + * @param dest */ const packFiles = (source: string) => { - const files = {} as { [key: string]: string } - if (!!source && fs.existsSync(source) && fs.statSync(source).isDirectory()) { - const contents = getDirectoryContents(source) + const files = {} as { [key: string]: string }; + if (!!source && fs.existsSync(source) && fs.statSync(source).isDirectory()) { + const contents = getDirectoryContents(source); - for (const c in contents) { - const fileName = c.replace(source, '') - files[fileName] = contents[c] - } - } + for (const c in contents) { + const fileName = c.replace(source, ""); + files[fileName] = contents[c]; + } + } - return files -} + return files; +}; /** * Read source folder's routes and pack dynamic routes - * - * @param source + * + * @param source */ const packRoutes = (source: string, dest: string) => { - const functionsPath = path.resolve(source, '..', 'assembly', 'routes') - const routes = [] as IFunctionRoute[] - - /** - * Traverse through the given directory recursively - * and fill in route details - * @param dirPath - */ - const traverseRoutes = (dirPath: string) => { - const files = fs.readdirSync(dirPath) - - files.forEach(file => { - const filePath = path.join(dirPath, file) - - if (fs.statSync(filePath).isDirectory()) { traverseRoutes(filePath) } - else if (file.endsWith('.ts')) { - // Check whether exported functions exist - const exportedFunctions = getTsExportedFunctions(filePath) - if (!exportedFunctions || exportedFunctions.length === 0) return - - // Fetch file name and path - const name = file.replace('.ts', '') - let relativePath = filePath.replace(functionsPath, '') - let url = relativePath.replace('.ts', '') - - // Test wheather the route is a wildcard or a static route - let wildcardParam = null - const isWildcard = /^\[.*\]$/.test(name) - - // If the route is a wildcard route, update path references - if (isWildcard) { - const matches = name.match(/\[(.*?)\]/g) - if (matches && matches.length > 0) { - wildcardParam = matches[0].slice(1, -1) - relativePath = relativePath.replace(matches[0], `wildcard_${wildcardParam}`) - url = url.replace(`wildcard_${wildcardParam}`, '').replace(name, '') - } - } - - // Handle index routes without the index path - if (url.endsWith('/index')) { - url = url.replace(new RegExp('/index$'), '') - } - - // Clear out all leading slashes - if (relativePath.startsWith('/')) { - relativePath = relativePath.substring(1); - } - - // Fill in the route details - routes.push({ - name: slugify(name), - path: relativePath, - url, - nameSlug: relativePath.replace('.ts', '').replace('[', '').replace(']', '').replace(new RegExp(path.sep, 'g'), '__'), - wildcard: isWildcard ? wildcardParam : null, - exportedFunctions: getTsExportedFunctions(filePath) - }) - - // Move route to the build folder - console.log(`${Chalk.yellow(`Compiling route:`)} ${relativePath} ...`) - copyFileSync(filePath, path.resolve(dest, relativePath)) - } - }) - } - - // Look through all routes and fill in route definations - if (fs.existsSync(functionsPath)) { - traverseRoutes(functionsPath) - } - - // Sort routes to place all wildcard routes at the end, - // Allowing for static nested routes along with wildcards - routes.sort((r1, r2) => { - if (!!r1.wildcard && !r2.wildcard) { return 1 } - else if (!r1.wildcard && !!r2.wildcard) { return -1 } - else { return 0 } - }) - - return routes -} + const functionsPath = path.resolve(source, "..", "assembly", "routes"); + const routes = [] as IFunctionRoute[]; + + /** + * Traverse through the given directory recursively + * and fill in route details + * @param dirPath + */ + const traverseRoutes = (dirPath: string) => { + const files = fs.readdirSync(dirPath); + + files.forEach((file) => { + const filePath = path.join(dirPath, file); + + if (fs.statSync(filePath).isDirectory()) { + traverseRoutes(filePath); + } else if (file.endsWith(".ts")) { + // Check whether exported functions exist + const exportedFunctions = getTsExportedFunctions(filePath); + if (!exportedFunctions || exportedFunctions.length === 0) return; + + // Fetch file name and path + const name = file.replace(".ts", ""); + let relativePath = filePath.replace(functionsPath, ""); + let url = relativePath.replace(".ts", ""); + + // Test wheather the route is a wildcard or a static route + let wildcardParam = null; + const isWildcard = /^\[.*\]$/.test(name); + + // If the route is a wildcard route, update path references + if (isWildcard) { + const matches = name.match(/\[(.*?)\]/g); + if (matches && matches.length > 0) { + wildcardParam = matches[0].slice(1, -1); + relativePath = relativePath.replace( + matches[0], + `wildcard_${wildcardParam}`, + ); + url = url + .replace(`wildcard_${wildcardParam}`, "") + .replace(name, ""); + } + } + + // Handle index routes without the index path + if (url.endsWith("/index")) { + url = url.replace(new RegExp("/index$"), ""); + } + + // Clear out all leading slashes + if (relativePath.startsWith("/")) { + relativePath = relativePath.substring(1); + } + + // Fill in the route details + routes.push({ + name: slugify(name), + path: relativePath, + url, + nameSlug: relativePath + .replace(".ts", "") + .replace("[", "") + .replace("]", "") + .replace(new RegExp(path.sep, "g"), "__"), + wildcard: isWildcard ? wildcardParam : null, + exportedFunctions: getTsExportedFunctions(filePath), + }); + + // Move route to the build folder + console.log(`${Chalk.yellow(`Compiling route:`)} ${relativePath} ...`); + copyFileSync(filePath, path.resolve(dest, relativePath)); + } + }); + }; + + // Look through all routes and fill in route definations + if (fs.existsSync(functionsPath)) { + traverseRoutes(functionsPath); + } + + // Sort routes to place all wildcard routes at the end, + // Allowing for static nested routes along with wildcards + routes.sort((r1, r2) => { + if (!!r1.wildcard && !r2.wildcard) { + return 1; + } else if (!r1.wildcard && !!r2.wildcard) { + return -1; + } else { + return 0; + } + }); + + return routes; +}; /** * Generate assets and directory for compiling the blockless site - * - * @param source - * @returns + * + * @param source + * @returns */ -const generateCompileDirectory = (source: string, dest: string): { dir: string, file: string } => { - // Create working directories - if (!fs.existsSync(dest)) fs.mkdirSync(dest) - const buildDir = path.resolve(dest, 'build') - const buildEntryDir = path.resolve(buildDir, 'entry') - const buildRoutesDir = path.resolve(buildEntryDir, 'routes') - if (!fs.existsSync(buildDir)) fs.mkdirSync(buildDir) - if (fs.existsSync(buildEntryDir)) fs.rmSync(buildEntryDir, { recursive: true }) - fs.mkdirSync(buildEntryDir) - if (!fs.existsSync(buildRoutesDir)) fs.mkdirSync(buildRoutesDir) - - // Prepare static files and dynamic routes - const sources = packFiles(source) - const routes = packRoutes(dest, buildRoutesDir) - - let assetsContent = '' - let routesImport = '' - let routesContent = '' - - for (const s in sources) { - assetsContent += `assets.set("${s}", "${sources[s]}")\n` - } - - for (const r in routes) { - const route = routes[r] - routesImport += `import * as bls_route_${route.nameSlug} from './routes/${route.path}'\n` - - route.exportedFunctions.map(f => { - if (!!route.wildcard) { - routesContent += `if (method === '${f}' && validateWildcardRoute(req.url, '${route.url}')) { req.query.set('${route.wildcard}', extractWildcardParam(req.url, '${route.url}')); response = bls_route_${route.nameSlug}.${f}(req).toString() } else ` - } else { - routesContent += `if (method === '${f}' && validateRoute(req.url, '${route.url}')) { response = bls_route_${route.nameSlug}.${f}(req).toString() } else ` - } - }) - } - - const packageJsonScript = `{ +const generateCompileDirectory = ( + source: string, + dest: string, +): { dir: string; file: string } => { + // Create working directories + if (!fs.existsSync(dest)) fs.mkdirSync(dest); + const buildDir = path.resolve(dest, "build"); + const buildEntryDir = path.resolve(buildDir, "entry"); + const buildRoutesDir = path.resolve(buildEntryDir, "routes"); + if (!fs.existsSync(buildDir)) fs.mkdirSync(buildDir); + if (fs.existsSync(buildEntryDir)) + fs.rmSync(buildEntryDir, { recursive: true }); + fs.mkdirSync(buildEntryDir); + if (!fs.existsSync(buildRoutesDir)) fs.mkdirSync(buildRoutesDir); + + // Prepare static files and dynamic routes + const sources = packFiles(source); + const routes = packRoutes(dest, buildRoutesDir); + + let assetsContent = ""; + let routesImport = ""; + let routesContent = ""; + + for (const s in sources) { + assetsContent += `assets.set("${s}", "${sources[s]}")\n`; + } + + for (const r in routes) { + const route = routes[r]; + routesImport += `import * as bls_route_${route.nameSlug} from './routes/${route.path}'\n`; + + route.exportedFunctions.map((f) => { + if (!!route.wildcard) { + routesContent += `if (method === '${f}' && validateWildcardRoute(req.url, '${route.url}')) { req.query.set('${route.wildcard}', extractWildcardParam(req.url, '${route.url}')); response = bls_route_${route.nameSlug}.${f}(req).toString() } else `; + } else { + routesContent += `if (method === '${f}' && validateRoute(req.url, '${route.url}')) { response = bls_route_${route.nameSlug}.${f}(req).toString() } else `; + } + }); + } + + const packageJsonScript = `{ "scripts": { "build": "asc index.ts --config asconfig.json" }, "dependencies": { "@assemblyscript/wasi-shim": "^0.1.0", - "@blockless/sdk": "^1.1.2", + "@blockless/sdk": "github:blocklessnetwork/sdk-assemblyscript#parse-json-lists-quickfix", "as-wasi": "^0.6.0" }, "devDependencies": { "assemblyscript": "^0.25.0" } -}` +}`; - const asConfigScript = `{ + const asConfigScript = `{ "extends": "./node_modules/@assemblyscript/wasi-shim/asconfig.json", "targets": { "release": { @@ -270,9 +298,9 @@ const generateCompileDirectory = (source: string, dest: string): { dir: string, "noAssert": false } } -}` - - const script = ` +}`; + + const script = ` import { http } from "@blockless/sdk/assembly" const assets = new Map() @@ -343,16 +371,22 @@ http.HttpComponent.serve((req: http.Request) => { return new http.Response(response) .header('Content-Type', contentType) .status(status) -})` - - const filePath = path.resolve(buildEntryDir, 'index.ts') - - fs.writeFileSync(filePath, script) - fs.writeFileSync(path.resolve(buildEntryDir, 'asconfig.json'), asConfigScript) - fs.writeFileSync(path.resolve(buildEntryDir, 'package.json'), packageJsonScript) - - return { - dir: buildEntryDir, - file: filePath - } -} \ No newline at end of file +})`; + + const filePath = path.resolve(buildEntryDir, "index.ts"); + + fs.writeFileSync(filePath, script); + fs.writeFileSync( + path.resolve(buildEntryDir, "asconfig.json"), + asConfigScript, + ); + fs.writeFileSync( + path.resolve(buildEntryDir, "package.json"), + packageJsonScript, + ); + + return { + dir: buildEntryDir, + file: filePath, + }; +};