diff --git a/canister_templates/experimental.wasm b/canister_templates/experimental.wasm index 9c2a7e52b3..cba1b33970 100644 Binary files a/canister_templates/experimental.wasm and b/canister_templates/experimental.wasm differ diff --git a/canister_templates/stable.wasm b/canister_templates/stable.wasm index ade5aa48b5..534852b9fc 100644 Binary files a/canister_templates/stable.wasm and b/canister_templates/stable.wasm differ diff --git a/src/compiler/custom_js_modules/acorn/acorn.ts b/src/build/experimental/commands/compile/custom_js_modules/acorn/acorn.ts similarity index 100% rename from src/compiler/custom_js_modules/acorn/acorn.ts rename to src/build/experimental/commands/compile/custom_js_modules/acorn/acorn.ts diff --git a/src/compiler/custom_js_modules/acorn/walk.ts b/src/build/experimental/commands/compile/custom_js_modules/acorn/walk.ts similarity index 100% rename from src/compiler/custom_js_modules/acorn/walk.ts rename to src/build/experimental/commands/compile/custom_js_modules/acorn/walk.ts diff --git a/src/compiler/custom_js_modules/async_hooks.ts b/src/build/experimental/commands/compile/custom_js_modules/async_hooks.ts similarity index 100% rename from src/compiler/custom_js_modules/async_hooks.ts rename to src/build/experimental/commands/compile/custom_js_modules/async_hooks.ts diff --git a/src/compiler/custom_js_modules/https.ts b/src/build/experimental/commands/compile/custom_js_modules/https.ts similarity index 100% rename from src/compiler/custom_js_modules/https.ts rename to src/build/experimental/commands/compile/custom_js_modules/https.ts diff --git a/src/compiler/custom_js_modules/perf_hooks.ts b/src/build/experimental/commands/compile/custom_js_modules/perf_hooks.ts similarity index 100% rename from src/compiler/custom_js_modules/perf_hooks.ts rename to src/build/experimental/commands/compile/custom_js_modules/perf_hooks.ts diff --git a/src/build/experimental/commands/compile/index.ts b/src/build/experimental/commands/compile/index.ts index e466e75d0f..e1344fb665 100644 --- a/src/build/experimental/commands/compile/index.ts +++ b/src/build/experimental/commands/compile/index.ts @@ -6,6 +6,7 @@ import { } from '../../../stable/commands/compile'; import { execSyncPretty } from '../../../stable/utils/exec_sync_pretty'; import { CanisterConfig } from '../../../stable/utils/types'; +import { logSuccess } from '../../utils/log_success'; import { getCandidAndMethodMeta } from './candid_and_method_meta'; import { setupFileWatcher } from './file_watcher/setup_file_watcher'; import { getContext } from './get_context'; @@ -79,6 +80,10 @@ export async function runCommand( canisterName, methodMeta.post_upgrade?.index ?? -1 ); + + if (canisterConfig.custom?.candid_gen === 'http') { + logSuccess(canisterName, canisterId); + } } function buildAssets(canisterConfig: CanisterConfig, ioType: IOType): void { diff --git a/src/build/experimental/commands/compile/javascript.ts b/src/build/experimental/commands/compile/javascript.ts index 7c6a04adbe..39e06eda20 100644 --- a/src/build/experimental/commands/compile/javascript.ts +++ b/src/build/experimental/commands/compile/javascript.ts @@ -133,7 +133,10 @@ export function getBuildOptions( const customJsModulesPath = join( AZLE_PACKAGE_PATH, 'src', - 'compiler', + 'build', + 'experimental', + 'commands', + 'compile', 'custom_js_modules' ); diff --git a/src/compiler/new_command.ts b/src/build/experimental/commands/new.ts similarity index 85% rename from src/compiler/new_command.ts rename to src/build/experimental/commands/new.ts index b3fd5bace0..5c32d4d201 100644 --- a/src/compiler/new_command.ts +++ b/src/build/experimental/commands/new.ts @@ -3,11 +3,9 @@ import { readFile, writeFile } from 'fs/promises'; import { copy } from 'fs-extra/esm'; import { join } from 'path'; -import { AZLE_PACKAGE_PATH } from './utils/global_paths'; +import { AZLE_PACKAGE_PATH } from '../../stable/utils/global_paths'; -export async function generateNewAzleProject( - azleVersion: string -): Promise { +export async function runCommand(azleVersion: string): Promise { if (process.argv[3] === undefined) { console.error('You must provide a name for your Azle project'); return; diff --git a/src/build/experimental/utils/experimental_message.ts b/src/build/experimental/utils/experimental_message.ts index c259221cef..336ac74bbf 100644 --- a/src/build/experimental/utils/experimental_message.ts +++ b/src/build/experimental/utils/experimental_message.ts @@ -1,4 +1,4 @@ -export function experimentalMessage(description: string): string { +export function experimentalMessageDfxJson(description: string): string { return `Azle: Experimental mode must be enabled to use ${description}. You can enable experimental mode in your dfx.json file like this: { "canisters": { @@ -13,3 +13,7 @@ export function experimentalMessage(description: string): string { } `; } + +export function experimentalMessageCli(description: string): string { + return `Azle: Experimental mode must be enabled to use ${description}. You can enable experimental mode with the --experimental flag as the final argument`; +} diff --git a/src/build/experimental/utils/log_success.ts b/src/build/experimental/utils/log_success.ts new file mode 100644 index 0000000000..0ca86da304 --- /dev/null +++ b/src/build/experimental/utils/log_success.ts @@ -0,0 +1,16 @@ +import { execSyncPretty } from '../../stable/utils/exec_sync_pretty'; + +export function logSuccess(canisterName: string, canisterId: string): void { + const replicaWebServerPort = execSyncPretty(`dfx info webserver-port`) + .toString() + .trim(); + + const url = + process.env.DFX_NETWORK === 'ic' + ? `https://${canisterId}.raw.icp0.io` + : `http://${canisterId}.localhost:${replicaWebServerPort}`; + + console.info( + `\nCanister ${canisterName} serving HTTP requests at: ${url}\n` + ); +} diff --git a/src/build/index.ts b/src/build/index.ts index 5f429efe02..e84f08d0bd 100755 --- a/src/build/index.ts +++ b/src/build/index.ts @@ -1,13 +1,22 @@ #!/usr/bin/env -S tsx --abort-on-uncaught-exception import { IOType } from 'child_process'; +import { join } from 'path'; +import { version as azleVersion } from '../../package.json'; import { runCommand as runExperimentalCompileCommand } from './experimental/commands/compile'; import { runCommand as runUploadAssetsCommand } from './experimental/commands/upload_assets'; -import { experimentalMessage } from './experimental/utils/experimental_message'; +import { + experimentalMessageCli, + experimentalMessageDfxJson +} from './experimental/utils/experimental_message'; +import { runCommand as runCleanCommand } from './stable/commands/clean'; import { runCommand as runStableCompileCommand } from './stable/commands/compile'; -import { runCommand as runInstallDfxExtension } from './stable/commands/install_dfx_extension'; +import { runCommand as runInstallDfxExtensionCommand } from './stable/commands/install_dfx_extension'; +import { runCommand as runNewCommand } from './stable/commands/new'; +import { runCommand as runVersionCommand } from './stable/commands/version'; import { getCanisterConfig } from './stable/utils/get_canister_config'; +import { AZLE_PACKAGE_PATH } from './stable/utils/global_paths'; import { Command } from './stable/utils/types'; build(); @@ -24,19 +33,37 @@ async function build(): Promise { const ioType = process.env.AZLE_VERBOSE === 'true' ? 'inherit' : 'pipe'; if (command === 'install-dfx-extension') { - handleCommandInstallDfxExtension(ioType); + handleInstallDfxExtensionCommand(ioType); return; } if (command === 'upload-assets') { - await handleCommandUploadAssets(); + await handleUploadAssetsCommand(); return; } if (command === 'compile') { - await handleCommandCompile(ioType); + await handleCompileCommand(ioType); + + return; + } + + if (command === '--version') { + runVersionCommand(); + + return; + } + + if (command === 'clean') { + await runCleanCommand(); + + return; + } + + if (command === 'new') { + await handleNewCommand(); return; } @@ -46,11 +73,11 @@ async function build(): Promise { ); } -function handleCommandInstallDfxExtension(ioType: IOType): void { - runInstallDfxExtension(ioType); +function handleInstallDfxExtensionCommand(ioType: IOType): void { + runInstallDfxExtensionCommand(ioType); } -async function handleCommandUploadAssets(): Promise { +async function handleUploadAssetsCommand(): Promise { const canisterName = process.argv[3]; const canisterConfig = await getCanisterConfig(canisterName); @@ -58,14 +85,16 @@ async function handleCommandUploadAssets(): Promise { if (experimental === false) { if (canisterConfig.custom?.assets !== undefined) { - throw new Error(experimentalMessage('the upload-assets command')); + throw new Error( + experimentalMessageDfxJson('the upload-assets command') + ); } } else { await runUploadAssetsCommand(); } } -async function handleCommandCompile(ioType: IOType): Promise { +async function handleCompileCommand(ioType: IOType): Promise { const canisterName = process.argv[3]; const canisterConfig = await getCanisterConfig(canisterName); @@ -92,3 +121,25 @@ async function handleCommandCompile(ioType: IOType): Promise { ); } } + +async function handleNewCommand(): Promise { + const experimental = process.argv.includes('--experimental'); + + if (experimental === false) { + if (process.argv.includes('--http-server')) { + throw new Error(experimentalMessageCli('the --http-server option')); + } + + const templatePath = join( + AZLE_PACKAGE_PATH, + 'examples', + 'hello_world_candid_rpc' + ); + + await runNewCommand(azleVersion, templatePath); + } else { + const templatePath = join(AZLE_PACKAGE_PATH, 'examples', 'hello_world'); + + await runNewCommand(azleVersion, templatePath); + } +} diff --git a/src/build/stable/commands/clean.ts b/src/build/stable/commands/clean.ts new file mode 100644 index 0000000000..2d28bfbb6c --- /dev/null +++ b/src/build/stable/commands/clean.ts @@ -0,0 +1,33 @@ +import { rm } from 'fs/promises'; + +import { GLOBAL_AZLE_CONFIG_DIR } from '../utils/global_paths'; + +export async function runCommand(): Promise { + await rm(GLOBAL_AZLE_CONFIG_DIR, { + recursive: true, + force: true + }); + + console.info(`~/.config/azle directory deleted`); + + await rm('.azle', { + recursive: true, + force: true + }); + + console.info(`.azle directory deleted`); + + await rm('.dfx', { + recursive: true, + force: true + }); + + console.info(`.dfx directory deleted`); + + await rm('node_modules', { + recursive: true, + force: true + }); + + console.info(`node_modules directory deleted`); +} diff --git a/src/build/stable/commands/new.ts b/src/build/stable/commands/new.ts new file mode 100644 index 0000000000..669ee0a24b --- /dev/null +++ b/src/build/stable/commands/new.ts @@ -0,0 +1,33 @@ +import { readFile, writeFile } from 'fs/promises'; +// @ts-ignore +import { copy } from 'fs-extra/esm'; +import { join } from 'path'; + +export async function runCommand( + azleVersion: string, + templatePath: string +): Promise { + if (process.argv[3] === undefined) { + console.error('You must provide a name for your Azle project'); + return; + } + + const projectName = process.argv[3]; + + await copy(templatePath, projectName); + + const packageJson = ( + await readFile(join(templatePath, 'package.json')) + ).toString(); + + let parsedPackageJson = JSON.parse(packageJson); + + parsedPackageJson.dependencies.azle = `^${azleVersion}`; + + await writeFile( + join(projectName, 'package.json'), + JSON.stringify(parsedPackageJson, null, 4) + ); + + console.info(`${projectName} created successfully`); +} diff --git a/src/build/stable/commands/version.ts b/src/build/stable/commands/version.ts new file mode 100644 index 0000000000..6cbc3280bc --- /dev/null +++ b/src/build/stable/commands/version.ts @@ -0,0 +1,5 @@ +import { version } from '../../../../package.json'; + +export function runCommand(): void { + console.info(version); +} diff --git a/src/build/stable/utils/types.ts b/src/build/stable/utils/types.ts index 6aa65fa904..afa62d2c3d 100644 --- a/src/build/stable/utils/types.ts +++ b/src/build/stable/utils/types.ts @@ -7,7 +7,7 @@ export type CandidAndMethodMeta = { methodMeta: MethodMeta; }; -export type CandidGen = 'automatic' | 'custom'; +export type CandidGen = 'automatic' | 'custom' | 'http'; // TODO in stable we should detect if certain properties exist // TODO and throw if not in experimental mode diff --git a/src/compiler/compile_typescript_code.ts b/src/compiler/compile_typescript_code.ts deleted file mode 100644 index d9733563a8..0000000000 --- a/src/compiler/compile_typescript_code.ts +++ /dev/null @@ -1,274 +0,0 @@ -import { build } from 'esbuild'; -import esbuildPluginTsc from 'esbuild-plugin-tsc'; -import * as path from 'path'; - -import { experimentalMessage } from '../lib/experimental/experimental'; -import { AZLE_PACKAGE_PATH } from './utils/global_paths'; -import { Result } from './utils/result'; -import { JavaScript, TypeScript } from './utils/types'; - -export async function compileTypeScriptToJavaScript( - main: string, - wasmedgeQuickJsPath: string, - esmAliases: Record, - esmExternals: string[], - experimental: boolean -): Promise> { - const imports = getImports(main, experimental); - - const bundledJavaScript = await bundleFromString( - imports, - wasmedgeQuickJsPath, - esmAliases, - esmExternals, - experimental - ); - - return { - ok: bundledJavaScript - }; -} - -function getImports(main: string, experimental: boolean): string { - if (experimental === false) { - return /*TS*/ ` - import 'azle/src/lib/stable/globals'; - - import { DidVisitor, getDefaultVisitorData, IDL, toDidString } from 'azle'; - - export * from './${main}'; - import * as CanisterMethods from './${main}'; - - if (globalThis._azleWasmtimeCandidEnvironment === false) { - const canister = new CanisterMethods.default(); - globalThis._azleCanisterClassInstance = canister; - } - - const canisterIdlType = IDL.Service(globalThis._azleCanisterMethodIdlTypes); - const candid = canisterIdlType.accept(new DidVisitor(), { - ...getDefaultVisitorData(), - isFirstService: true, - systemFuncs: globalThis._azleInitAndPostUpgradeIdlTypes - }); - - - globalThis.candidInfoFunction = () => { - return JSON.stringify({ - candid: toDidString(candid), - canisterMethods: globalThis._azleCanisterMethods - }); - }; - - globalThis.exports.canisterMethods = globalThis._azleCanisterMethods; - `; - } else { - return /*TS*/ ` - import 'azle/src/lib/stable/globals'; - import 'azle/src/lib/experimental/globals'; - - import 'reflect-metadata'; - - // TODO remove the ethersGetUrl registration once we implement lower-level http for ethers - import { ethersGetUrl, Server } from 'azle/src/lib/experimental'; - import { ethers } from 'ethers'; - ethers.FetchRequest.registerGetUrl(ethersGetUrl); - - import { DidVisitor, getDefaultVisitorData, IDL, toDidString } from 'azle'; - export { Principal } from '@dfinity/principal'; - export * from './${main}'; - import * as CanisterMethods from './${main}'; - - if (isClassSyntaxExport(CanisterMethods)) { - if (globalThis._azleWasmtimeCandidEnvironment === false) { - const canister = new CanisterMethods.default(); - globalThis._azleCanisterClassInstance = canister; - } - - const canisterIdlType = IDL.Service(globalThis._azleCanisterMethodIdlTypes); - const candid = canisterIdlType.accept(new DidVisitor(), { - ...getDefaultVisitorData(), - isFirstService: true, - systemFuncs: globalThis._azleInitAndPostUpgradeIdlTypes - }); - - globalThis.candidInfoFunction = () => { - return JSON.stringify({ - candid: toDidString(candid), - canisterMethods: globalThis._azleCanisterMethods - }); - }; - - globalThis.exports.canisterMethods = globalThis._azleCanisterMethods; - } - else { - // TODO This setTimeout is here to allow asynchronous operations during canister initialization - // for Server canisters that have chosen not to use export default Server - // This seems to work no matter how many async tasks are awaited, but I am still unsure about how it will - // behave in all async situations - setTimeout(() => { - const canisterMethods = CanisterMethods.default !== undefined ? CanisterMethods.default() : Server(() => globalThis._azleNodeServer)(); - - globalThis.candidInfoFunction = () => { - const candidInfo = canisterMethods.getIdlType([]).accept(new DidVisitor(), { - ...getDefaultVisitorData(), - isFirstService: true, - systemFuncs: canisterMethods.getSystemFunctionIdlTypes() - }); - - return JSON.stringify({ - candid: toDidString(candidInfo), - canisterMethods: { - // TODO The spread is because canisterMethods is a function with properties - // TODO we should probably just grab the props out that we need - ...canisterMethods - } - }); - }; - - // TODO I do not know how to get the module exports yet with wasmedge_quickjs - globalThis.exports.canisterMethods = canisterMethods; - }); - } - - function isClassSyntaxExport(module) { - const isNothing = module === undefined || module.default === undefined; - const isFunctionalSyntaxExport = - module?.default?.isCanister === true || - module?.default?.isRecursive === true; - return !isNothing && !isFunctionalSyntaxExport; - } - `; - } -} - -export async function bundleFromString( - ts: TypeScript, - wasmedgeQuickJsPath: string, - esmAliases: Record, - esmExternals: string[], - experimental: boolean -): Promise { - const finalWasmedgeQuickJsPath = - process.env.AZLE_WASMEDGE_QUICKJS_DIR ?? wasmedgeQuickJsPath; - - const externalImplemented = [ - '_node:fs', - '_node:os', - '_node:crypto', - 'qjs:os', - '_encoding', - 'wasi_net', - 'wasi_http' - ]; - - // These are modules that should not be included in the build from the Azle side (our side) - const externalNotImplementedAzle: string[] = []; - - // These are modules that should not be included in the build from the developer side - // These are specified in the dfx.json canister object esm_externals property - const externalNotImplementedDev = esmExternals; - - // These will cause runtime errors if their functionality is dependend upon - const externalNotImplemented = [ - ...externalNotImplementedAzle, - ...externalNotImplementedDev - ]; - - const customJsModulesPath = path.join( - AZLE_PACKAGE_PATH, - 'src', - 'compiler', - 'custom_js_modules' - ); - - // TODO tree-shaking does not seem to work with stdin. I have learned this from sad experience - const buildResult = await build({ - stdin: { - contents: ts, - resolveDir: process.cwd() - }, - format: 'esm', - bundle: true, - treeShaking: true, - write: false, - logLevel: 'silent', - target: 'es2020', - preserveSymlinks: true, - alias: { - internal: `${finalWasmedgeQuickJsPath}/modules/internal`, - util: `${finalWasmedgeQuickJsPath}/modules/util`, - fs: `${finalWasmedgeQuickJsPath}/modules/fs`, - fmt: `${finalWasmedgeQuickJsPath}/modules/fmt`, - assert: `${finalWasmedgeQuickJsPath}/modules/assert.js`, - buffer: `${finalWasmedgeQuickJsPath}/modules/buffer.js`, - path: `${finalWasmedgeQuickJsPath}/modules/path.js`, - stream: `${finalWasmedgeQuickJsPath}/modules/stream.js`, - process: `${finalWasmedgeQuickJsPath}/modules/process.js`, - url: `${finalWasmedgeQuickJsPath}/modules/url.js`, - events: `${finalWasmedgeQuickJsPath}/modules/events.js`, - string_decoder: `${finalWasmedgeQuickJsPath}/modules/string_decoder.js`, - punycode: `${finalWasmedgeQuickJsPath}/modules/punycode.js`, - querystring: `${finalWasmedgeQuickJsPath}/modules/querystring.js`, - whatwg_url: `${finalWasmedgeQuickJsPath}/modules/whatwg_url.js`, - encoding: `${finalWasmedgeQuickJsPath}/modules/encoding.js`, - http: `${finalWasmedgeQuickJsPath}/modules/http.js`, - os: `${finalWasmedgeQuickJsPath}/modules/os.js`, - // crypto: `${finalWasmedgeQuickJsPath}/modules/crypto.js`, // TODO waiting on wasi-crypto - crypto: 'crypto-browserify', // TODO we really want the wasmedge-quickjs version once wasi-crypto is working - zlib: 'pako', - 'internal/deps/acorn/acorn/dist/acorn': path.join( - customJsModulesPath, - 'acorn', - 'acorn.ts' - ), // TODO acorn stuff is a bug, wasmedge-quickjs should probably add these files - 'internal/deps/acorn/acorn-walk/dist/walk': path.join( - customJsModulesPath, - 'acorn', - 'walk.ts' - ), // TODO acorn stuff is a bug, wasmedge-quickjs should probably add these files - perf_hooks: path.join(customJsModulesPath, 'perf_hooks.ts'), - async_hooks: path.join(customJsModulesPath, 'async_hooks.ts'), - https: path.join(customJsModulesPath, 'https.ts'), - ...esmAliases - }, - external: [...externalImplemented, ...externalNotImplemented], - plugins: [ - { - name: 'Azle experimental check', - setup(build): void { - if (experimental === false) { - build.onResolve( - { - filter: /^internal$|^util$|^fs$|^fmt$|^assert$|^buffer$|^path$|^stream$|^process$|^url$|^events$|^string_decoder$|^punycode$|^querystring$|^whatwg_url$|^encoding$|^http$|^os$|^crypto$|^zlib$|^internal\/deps\/acorn\/acorn\/dist\/acorn$|^internal\/deps\/acorn\/acorn-walk\/dist\/walk$|^perf_hooks$|^async_hooks$|^https$|^_node:fs$|^_node:os$|^_node:crypto$|^qjs:os$|^_encoding$|^wasi_net$|^wasi_http$/ - }, - (args) => { - throw new Error(experimentalMessage(args.path)); - } - ); - } - } - }, - esbuildPluginTsc() - ] - }); - - const bundleArray = buildResult.outputFiles[0].contents; - const bundleString = Buffer.from(bundleArray).toString('utf-8'); - - // TODO consuming code tries to require assert.js which is now an ES module - // TODO in wasmedge-quickjs, so the expected output is now on the .default property - // TODO this has only come up with assert for now - return bundleString - .replace( - /__toCommonJS\(assert_exports\)\);/g, - `__toCommonJS(assert_exports)).default;` - ) - .replace( - /__toCommonJS\(stream_exports\)\);/g, - `__toCommonJS(stream_exports)).default;` - ) - .replace( - /__toCommonJS\(http_exports\)\);/g, - `__toCommonJS(http_exports)).default;` - ); -} diff --git a/src/compiler/file_uploader/bytes_to_human_readable.ts b/src/compiler/file_uploader/bytes_to_human_readable.ts deleted file mode 100644 index 0c8901953e..0000000000 --- a/src/compiler/file_uploader/bytes_to_human_readable.ts +++ /dev/null @@ -1,25 +0,0 @@ -export function bytesToHumanReadable(sizeInBytes: number): string { - const suffixes = ['B', 'KiB', 'MiB', 'GiB']; - - const result = suffixes.reduce( - (acc, suffix) => { - if (acc.done) { - return acc; - } - if (acc.size < 1024.0) { - return { - ...acc, - unit: suffix, - done: true - }; - } - return { - ...acc, - size: acc.size / 1024.0 - }; - }, - { size: sizeInBytes, unit: '', done: false } - ); - - return `${result.size.toFixed(2)} ${result.unit}`; -} diff --git a/src/compiler/file_uploader/expand_paths.ts b/src/compiler/file_uploader/expand_paths.ts deleted file mode 100644 index 1ec7e3ac57..0000000000 --- a/src/compiler/file_uploader/expand_paths.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { readdir, stat } from 'fs/promises'; -import { join } from 'path'; - -import { Dest, Src } from '.'; - -export async function expandPaths( - paths: [Src, Dest][] -): Promise<[string, string][]> { - return paths.reduce( - async (accPromise: Promise<[Src, Dest][]>, [srcPath, destPath]) => { - const acc = await accPromise; - return [...acc, ...(await expandPath(srcPath, destPath))]; - }, - Promise.resolve([]) - ); -} - -async function expandPath( - srcPath: Src, - destPath: Dest -): Promise<[Src, Dest][]> { - const stats = await stat(srcPath); - if (stats.isDirectory()) { - return await expandDirectory(srcPath, destPath); - } else { - return [[srcPath, destPath]]; - } -} - -async function expandDirectory( - srcDir: string, - destDir: string -): Promise<[Src, Dest][]> { - try { - const contents = await readdir(srcDir); - return contents.reduce( - async (accPromise: Promise<[Src, Dest][]>, name) => { - const acc = await accPromise; - const srcPath = join(srcDir, name); - const destPath = join(destDir, name); - return [...acc, ...(await expandPath(srcPath, destPath))]; - }, - Promise.resolve([]) - ); - } catch (error: any) { - throw new Error(`Error reading directory: ${error.message}`); - } -} diff --git a/src/compiler/file_uploader/get_files_to_upload.ts b/src/compiler/file_uploader/get_files_to_upload.ts deleted file mode 100644 index 26a8e26031..0000000000 --- a/src/compiler/file_uploader/get_files_to_upload.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { getCanisterConfig } from '../utils/get_canister_config'; -import { unwrap } from '../utils/result'; - -export async function getFilesToUpload( - canisterName: string, - srcPath?: string, - destPath?: string -): Promise<[string, string][]> { - if (srcPath !== undefined && destPath !== undefined) { - return [[srcPath, destPath]]; - } - if (srcPath === undefined && destPath === undefined) { - // If both paths are undefined, look at the dfx.json for the assets to upload - const dfxJson = unwrap(await getCanisterConfig(canisterName)); - return dfxJson.assets ?? []; - } - throw new Error( - 'The source path and destination path must be either both defined or both undefined' - ); -} diff --git a/src/compiler/file_uploader/incomplete_files.ts b/src/compiler/file_uploader/incomplete_files.ts deleted file mode 100644 index 7a98ebae16..0000000000 --- a/src/compiler/file_uploader/incomplete_files.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { filterAsync } from '../utils/filter_async'; -import { Dest, Src } from '.'; -import { UploaderActor } from './uploader_actor'; - -export async function getListOfIncompleteFiles( - paths: [Src, Dest][], - actor: UploaderActor -): Promise<[Src, Dest][]> { - return filterAsync( - paths, - async ([_, path]) => !(await hasValidHash(path, actor)) - ); -} - -async function hasValidHash( - path: string, - actor: UploaderActor -): Promise { - try { - const hashOption = await actor._azle_get_file_hash(path); - return hashOption.length === 1; - } catch { - return false; - } -} diff --git a/src/compiler/file_uploader/index.ts b/src/compiler/file_uploader/index.ts deleted file mode 100644 index c0b4f78759..0000000000 --- a/src/compiler/file_uploader/index.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { getCanisterId } from '../../../dfx'; -import { generateUploaderIdentity } from '../uploader_identity'; -import { expandPaths } from './expand_paths'; -import { onBeforeExit } from './on_before_exit'; -import { uploadFile } from './upload_file'; -import { createActor } from './uploader_actor'; - -export type Src = string; -export type Dest = string; - -export async function uploadFiles( - canisterName: string, - paths: [Src, Dest][] -): Promise { - if ( - paths.length === 0 || - process.env.AZLE_DISABLE_AUTO_FILE_UPLOAD === 'true' - ) { - return; - } - - const canisterId = getCanisterId(canisterName); - const identityName = generateUploaderIdentity(canisterName); - const actor = await createActor(canisterId, identityName); - - const chunkSize = 2_000_000; // The current message limit is about 2 MB - - const expandedPaths = await expandPaths(paths); - - for (const [srcPath, destPath] of expandedPaths) { - // Await each upload so the canister doesn't get overwhelmed by requests - await uploadFile(srcPath, destPath, chunkSize, actor); - } - - console.info( - 'Finished uploading files. Waiting for all chunks to finish uploading...' // TODO remove after https://github.com/demergent-labs/azle/issues/1996 is complete - ); - - onBeforeExit(expandedPaths, actor); -} diff --git a/src/compiler/file_uploader/on_before_exit.ts b/src/compiler/file_uploader/on_before_exit.ts deleted file mode 100644 index 5109335b17..0000000000 --- a/src/compiler/file_uploader/on_before_exit.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Dest, Src } from '.'; -import { getListOfIncompleteFiles } from './incomplete_files'; -import { UploaderActor } from './uploader_actor'; - -export function onBeforeExit(paths: [Src, Dest][], actor: UploaderActor): void { - let cleanUpComplete = false; - - process.on('beforeExit', async () => { - if (cleanUpComplete) { - // If any async behavior happens in 'beforeExit' then 'beforeExit' - // will run again. This is need to prevent an infinite loop. - // Once clean up is complete we are ready to exit - console.info('File hashing finished'); - return; - } - - console.info('Cleaning up incomplete files'); - await cleanup(paths, actor); - cleanUpComplete = true; - return; - }); -} - -async function cleanup( - paths: [Src, Dest][], - actor: UploaderActor -): Promise { - const incompleteFiles = await getListOfIncompleteFiles(paths, actor); - for (const [_, path] of incompleteFiles) { - await actor._azle_clear_file_and_info(path); - } -} diff --git a/src/compiler/file_uploader/upload_file.ts b/src/compiler/file_uploader/upload_file.ts deleted file mode 100644 index c285974728..0000000000 --- a/src/compiler/file_uploader/upload_file.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { open, stat } from 'fs/promises'; - -import { hashFile } from '../../../scripts/hash_file'; -import { Dest, Src } from '.'; -import { bytesToHumanReadable } from './bytes_to_human_readable'; -import { UploaderActor } from './uploader_actor'; - -export async function uploadFile( - srcPath: Src, - destPath: Dest, - chunkSize: number, - actor: UploaderActor -): Promise { - if (!(await shouldBeUploaded(srcPath, destPath, actor))) { - return; - } - const uploadStartTime = process.hrtime.bigint(); - const fileSize = (await stat(srcPath)).size; - const file = await open(srcPath, 'r'); - for (let startIndex = 0; startIndex <= fileSize; startIndex += chunkSize) { - let buffer = Buffer.alloc(chunkSize); - const { buffer: bytesToUpload, bytesRead } = await file.read( - buffer, - 0, - chunkSize, - startIndex - ); - - await throttle(); - const percentComplete = calculatePercentComplete( - startIndex + bytesRead, - fileSize - ); - console.info( - `Uploading chunk: ${srcPath} | ${bytesToHumanReadable( - startIndex + bytesRead - )}/${bytesToHumanReadable(fileSize)} : ${percentComplete.toFixed( - 2 - )}%` - ); - // Don't await here! Awaiting the agent will result in about a 4x increase in upload time. - // The above throttling is sufficient to manage the speed of uploads - actor - ._azle_upload_file_chunk( - destPath, - uploadStartTime, - BigInt(startIndex), - bytesToUpload.subarray(0, bytesRead), - BigInt(fileSize) - ) - .catch((error) => { - console.error(error); - }); - } - file.close(); - console.info(); -} - -async function throttle(): Promise { - // We can only process about 4Mib per second. So if chunks are about - // 2 MiB or less then we can only send off two per second. - if (process.env.DFX_NETWORK === 'ic') { - await new Promise((resolve) => setTimeout(resolve, 2_000)); // Mainnet requires more throttling. We found 2_000 by trial and error - } else { - await new Promise((resolve) => setTimeout(resolve, 500)); // Should be 500 (ie 1 every 1/2 second or 2 every second) - } -} -function calculatePercentComplete( - bytesComplete: number, - fileSize: number -): number { - if (bytesComplete === 0 && fileSize === 0) { - return 100; - } - return (bytesComplete / Math.max(fileSize, 1)) * 100; -} - -async function shouldBeUploaded( - srcPath: string, - destPath: string, - actor: UploaderActor -): Promise { - const localHash = (await hashFile(srcPath)).toString('hex'); - const canisterHashOption = await actor._azle_get_file_hash(destPath); - if (canisterHashOption.length === 0) { - return true; - } - return localHash !== canisterHashOption[0]; -} diff --git a/src/compiler/file_uploader/uploader_actor.ts b/src/compiler/file_uploader/uploader_actor.ts deleted file mode 100644 index cf6d7cc43f..0000000000 --- a/src/compiler/file_uploader/uploader_actor.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Actor, ActorMethod, ActorSubclass } from '@dfinity/agent'; - -import { createAuthenticatedAgent } from '../../../dfx'; - -export type UploaderActor = ActorSubclass<_SERVICE>; - -export async function createActor( - canisterId: string, - identityName: string -): Promise { - const agent = await createAuthenticatedAgent(identityName); - - return Actor.createActor<_SERVICE>( - ({ IDL }) => { - return IDL.Service({ - _azle_clear_file_and_info: IDL.Func([IDL.Text], [], []), - _azle_get_file_hash: IDL.Func( - [IDL.Text], - [IDL.Opt(IDL.Text)], - [] - ), - _azle_upload_file_chunk: IDL.Func( - [ - IDL.Text, - IDL.Nat64, - IDL.Nat64, - IDL.Vec(IDL.Nat8), - IDL.Nat64 - ], - [], - [] - ) - }); - }, - { - agent, - canisterId - } - ); -} - -interface _SERVICE { - _azle_clear_file_and_info: ActorMethod<[string], void>; - _azle_get_file_hash: ActorMethod<[string], [] | [string]>; - _azle_upload_file_chunk: ActorMethod< - [string, bigint, bigint, Uint8Array, bigint], - void - >; -} diff --git a/src/compiler/file_watcher/file_watcher.ts b/src/compiler/file_watcher/file_watcher.ts deleted file mode 100644 index 59c978ff8f..0000000000 --- a/src/compiler/file_watcher/file_watcher.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { Actor, ActorMethod, ActorSubclass } from '@dfinity/agent'; -import { watch } from 'chokidar'; -import { writeFile } from 'fs/promises'; - -import { createAuthenticatedAgent } from '../../../dfx'; -import { getCanisterJavaScript } from '../get_canister_javascript'; -import { generateUploaderIdentity } from '../uploader_identity'; -import { ok } from '../utils/result'; - -type ActorReloadJs = ActorSubclass<_SERVICE>; -interface _SERVICE { - _azle_reload_js: ActorMethod< - [bigint, bigint, Uint8Array, bigint, number], - void - >; -} - -// We have made this mutable to help with speed -// We don't want to have to create the agent on each file change -let actor: ActorReloadJs | undefined; - -const reloadedJsPath = process.argv[2]; -const canisterId = process.argv[3]; -const mainPath = process.argv[4]; -const wasmedgeQuickJsPath = process.argv[5]; -const esmAliases = JSON.parse(process.argv[6]); -const esmExternals = JSON.parse(process.argv[7]); -const canisterName = process.argv[8]; -const postUpgradeIndex = Number(process.argv[9]); -const experimental = process.argv[10] === 'true' ? true : false; - -// TODO https://github.com/demergent-labs/azle/issues/1664 -const watcher = watch([`**/*.ts`, `**/*.js`], { - ignored: ['**/.dfx/**', '**/.azle/**', '**/node_modules/**', '**/target/**'] -}); - -watcher.on('all', async (event, path) => { - if (actor === undefined) { - actor = await createActorReloadJs(canisterName); - } - - if (process.env.AZLE_VERBOSE === 'true') { - console.info('event', event); - console.info('path', path); - } - - if (event === 'change') { - try { - await reloadJs( - actor, - reloadedJsPath, - mainPath, - wasmedgeQuickJsPath, - experimental - ); - } catch (error) { - console.error(error); - } - } -}); - -async function reloadJs( - actor: ActorReloadJs, - reloadedJsPath: string, - mainPath: string, - wasmedgeQuickJsPath: string, - experimental: boolean -): Promise { - const canisterJavaScriptResult = await getCanisterJavaScript( - mainPath, - wasmedgeQuickJsPath, - esmAliases, - esmExternals, - experimental - ); - - if (!ok(canisterJavaScriptResult)) { - if (process.env.AZLE_VERBOSE === 'true') { - console.error(canisterJavaScriptResult.err!.error); - console.error(canisterJavaScriptResult.err!.suggestion); - console.error(canisterJavaScriptResult.err!.exitCode); - } - - throw new Error(`TypeScript compilation failed`); - } - - const reloadedJs = Buffer.from(canisterJavaScriptResult.ok); - - const chunkSize = 2_000_000; // The current message limit is about 2 MiB - const timestamp = process.hrtime.bigint(); - let chunkNumber = 0n; - - for (let i = 0; i < reloadedJs.length; i += chunkSize) { - const chunk = reloadedJs.slice(i, i + chunkSize); - - if (process.env.AZLE_VERBOSE === 'true') { - console.info( - `Uploading chunk: ${timestamp}, ${chunkNumber}, ${chunk.length}, ${reloadedJs.length}` - ); - } - - actor - ._azle_reload_js( - timestamp, - chunkNumber, - chunk, - BigInt(reloadedJs.length), - postUpgradeIndex - ) - .catch((error) => { - if (process.env.AZLE_VERBOSE === 'true') { - console.error(error); - } - }); - - chunkNumber += 1n; - } - - if (process.env.AZLE_VERBOSE === 'true') { - console.info(`Finished uploading chunks`); - } - - await writeFile(reloadedJsPath, reloadedJs); -} - -async function createActorReloadJs( - canisterName: string -): Promise { - const identityName = generateUploaderIdentity(canisterName); - const agent = await createAuthenticatedAgent(identityName); - - return Actor.createActor( - ({ IDL }) => { - return IDL.Service({ - _azle_reload_js: IDL.Func( - [ - IDL.Nat64, - IDL.Nat64, - IDL.Vec(IDL.Nat8), - IDL.Nat64, - IDL.Int32 - ], - [], - [] - ) - }); - }, - { - agent, - canisterId - } - ); -} diff --git a/src/compiler/file_watcher/file_watcher_loader.js b/src/compiler/file_watcher/file_watcher_loader.js deleted file mode 100644 index 8b64a2ff06..0000000000 --- a/src/compiler/file_watcher/file_watcher_loader.js +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env node - -import 'tsx'; -import('./file_watcher.ts'); diff --git a/src/compiler/file_watcher/setup_file_watcher.ts b/src/compiler/file_watcher/setup_file_watcher.ts deleted file mode 100644 index b9a90aedd7..0000000000 --- a/src/compiler/file_watcher/setup_file_watcher.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { spawn } from 'child_process'; -import { join } from 'path'; - -import { execSyncPretty } from '../utils/exec_sync_pretty'; -import { AZLE_PACKAGE_PATH } from '../utils/global_paths'; - -export function setupFileWatcher( - reloadedJsPath: string, - canisterId: string, - mainPath: string, - wasmedgeQuickJsPath: string, - esmAliases: Record, - esmExternals: string[], - canisterName: string, - postUpgradeIndex: number, - experimental: boolean -): void { - try { - // TODO should we check that this was successful in killing - // TODO the process and then warn the user if not? - // TODO should we figure out why the || true - // TODO does not result in a 0 exit code - // TODO and look into removing the try catch? - execSyncPretty(`pkill -f ./file_watcher_loader.js || true`); - } catch (error) { - // For some reason pkill throws even with || true - } - - if (process.env.AZLE_AUTORELOAD !== 'true') { - return; - } - - const watcherProcess = spawn( - 'node', - [ - join( - AZLE_PACKAGE_PATH, - 'src', - 'compiler', - 'file_watcher', - 'file_watcher_loader.js' - ), - reloadedJsPath, - canisterId, - mainPath, - wasmedgeQuickJsPath, - JSON.stringify(esmAliases), - JSON.stringify(esmExternals), - canisterName, - postUpgradeIndex.toString(), - experimental.toString() - ], - { - detached: true, - stdio: 'inherit' - } - ); - - watcherProcess.unref(); -} diff --git a/src/compiler/generate_candid_and_canister_methods.ts b/src/compiler/generate_candid_and_canister_methods.ts deleted file mode 100644 index 903bca187e..0000000000 --- a/src/compiler/generate_candid_and_canister_methods.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { readFile } from 'fs/promises'; - -import { CanisterMethods } from './utils/types'; - -export async function generateCandidAndCanisterMethods( - wasmFilePath: string, - experimental: boolean -): Promise<{ - candid: string; - canisterMethods: CanisterMethods; -}> { - const wasmBuffer = await readFile( - process.env.AZLE_WASM_DEST ?? wasmFilePath - ); - - const wasmModule = new WebAssembly.Module(wasmBuffer); - const wasmInstance = new WebAssembly.Instance(wasmModule, { - ic0: { - accept_message: (): void => {}, - call_cycles_add: (): void => {}, - call_cycles_add128: (): void => {}, - call_data_append: (): void => {}, - call_new: (): void => {}, - call_on_cleanup: (): void => {}, - call_perform: (): void => {}, - canister_cycle_balance: (): void => {}, - canister_cycle_balance128: (): void => {}, - canister_self_copy: (): void => {}, - canister_self_size: (): void => {}, - canister_version: (): void => {}, - certified_data_set: (): void => {}, - data_certificate_copy: (): void => {}, - data_certificate_present: (): void => {}, - data_certificate_size: (): void => {}, - debug_print: (ptr: number, len: number): void => { - const memory = new Uint8Array( - (wasmInstance.exports.memory as any).buffer, - ptr, - len - ); - const message = new TextDecoder('utf8').decode(memory); - console.info(message); - }, - global_timer_set: (): void => {}, - instruction_counter: (): void => {}, - is_controller: (): void => {}, - msg_arg_data_copy: (): void => {}, - msg_arg_data_size: (): void => {}, - msg_caller_copy: (): void => {}, - msg_caller_size: (): void => {}, - msg_cycles_accept: (): void => {}, - msg_cycles_accept128: (): void => {}, - msg_cycles_available: (): void => {}, - msg_cycles_available128: (): void => {}, - msg_cycles_refunded: (): void => {}, - msg_cycles_refunded128: (): void => {}, - msg_method_name_copy: (): void => {}, - msg_method_name_size: (): void => {}, - msg_reject_code: (): void => {}, - msg_reject_msg_copy: (): void => {}, - msg_reject_msg_size: (): void => {}, - msg_reject: (): void => {}, - msg_reply_data_append: (): void => {}, - msg_reply: (): void => {}, - performance_counter: (): void => {}, - stable_grow: (): void => {}, - stable_read: (): void => {}, - stable_size: (): void => {}, - stable_write: (): void => {}, - stable64_grow: (): void => {}, - stable64_read: (): void => {}, - stable64_size: (): void => {}, - stable64_write: (): void => {}, - time: (): bigint => 0n, - trap: (): void => {} - } - // env: { - // azle_log(ptr: number, len: number) { - // const memory = new Uint8Array( - // (wasmInstance.exports.memory as any).buffer, - // ptr, - // len - // ); - // const message = new TextDecoder('utf8').decode(memory); - // console.info(message); - // } - // } - }); - - // TODO can we simplify this to be more like azle_log above? - const candidPointer = (wasmInstance.exports as any).get_candid_pointer( - experimental === true ? 1 : 0 - ); - - const memory = new Uint8Array((wasmInstance.exports.memory as any).buffer); - - let candidBytes: number[] = []; - let i = candidPointer; - while (memory[i] !== 0) { - candidBytes.push(memory[i]); - i += 1; - } - - const resultString = Buffer.from(candidBytes).toString(); - - return JSON.parse(resultString); -} diff --git a/src/compiler/generate_cargo_toml_files.ts b/src/compiler/generate_cargo_toml_files.ts deleted file mode 100644 index 882368e813..0000000000 --- a/src/compiler/generate_cargo_toml_files.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { OptLevel, Toml } from './utils/types'; - -export function generateWorkspaceCargoToml(optLevel: OptLevel): Toml { - const optLevels = { - '0': `opt-level = 'z'`, - '1': ` - opt-level = 'z' - lto = "thin" - `, - '2': ` - opt-level = 'z' - lto = "thin" - codegen-units = 1 - `, - '3': ` - opt-level = 'z' - lto = "fat" - `, - '4': ` - opt-level = 'z' - lto = "fat" - codegen-units = 1 - ` - }; - - return ` - # This code is automatically generated by Azle - - [workspace] - members = [ - "canister", - "open_value_sharing" - ] - - [profile.release] - ${optLevels[optLevel]} - `; -} diff --git a/src/compiler/generate_wasm_binary.ts b/src/compiler/generate_wasm_binary.ts deleted file mode 100644 index 5df280d7ed..0000000000 --- a/src/compiler/generate_wasm_binary.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { IOType } from 'child_process'; - -import { logGlobalDependencies } from './log_global_dependencies'; -import { prepareRustStagingArea } from './prepare_rust_staging_area'; -import { execSyncPretty } from './utils/exec_sync_pretty'; -import { - EXPERIMENTAL_STATIC_CANISTER_TEMPLATE_PATH, - STABLE_STATIC_CANISTER_TEMPLATE_PATH -} from './utils/global_paths'; -import { CanisterConfig, CompilerInfo } from './utils/types'; -import { manipulateWasmBinary } from './wasm_binary_manipulation'; - -export async function generateWasmBinary( - canisterName: string, - stdio: IOType, - js: string, - compilerInfo: CompilerInfo, - canisterConfig: CanisterConfig, - canisterPath: string, - experimental: boolean -): Promise { - if (process.env.AZLE_GEN_WASM === 'true') { - await logGlobalDependencies(); - - await prepareRustStagingArea(canisterConfig, canisterPath); - - compileRustCodeNatively( - STABLE_STATIC_CANISTER_TEMPLATE_PATH, - canisterName, - false, - stdio - ); - - compileRustCodeNatively( - EXPERIMENTAL_STATIC_CANISTER_TEMPLATE_PATH, - canisterName, - true, - stdio - ); - } - - await manipulateWasmBinary( - canisterName, - js, - compilerInfo, - canisterConfig, - experimental - ); -} - -function compileRustCodeNatively( - wasmDest: string, - canisterName: string, - experimental: boolean, - stdio: IOType -): void { - execSyncPretty( - `CARGO_TARGET_DIR=target cargo build --target wasm32-wasi --manifest-path .azle/${canisterName}/canister/Cargo.toml --release${ - experimental === true ? ' --features "experimental"' : '' - }`, - stdio - ); - - execSyncPretty( - `wasi2ic target/wasm32-wasi/release/canister.wasm ${wasmDest}`, - stdio - ); -} diff --git a/src/compiler/get_candid_and_canister_methods.ts b/src/compiler/get_candid_and_canister_methods.ts deleted file mode 100644 index b77671abd5..0000000000 --- a/src/compiler/get_candid_and_canister_methods.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { IOType } from 'child_process'; -import { readFile } from 'fs/promises'; -import { join } from 'path'; - -import { generateCandidAndCanisterMethods } from './generate_candid_and_canister_methods'; -import { generateWasmBinary } from './generate_wasm_binary'; -import { AZLE_PACKAGE_PATH } from './utils/global_paths'; -import { - CandidGen, - CanisterConfig, - CanisterMethods, - CompilerInfo -} from './utils/types'; - -export async function getCandidAndCanisterMethods( - candidGen: CandidGen = 'http', - candidPath: string, - canisterName: string, - stdioType: IOType, - envVars: [string, string][], - rustStagingWasmPath: string, - js: string, - canisterConfig: CanisterConfig, - canisterPath: string, - experimental: boolean -): Promise<{ - candid: string; - canisterMethods: CanisterMethods; -}> { - if (candidGen === 'automatic' || candidGen === 'custom') { - const customCandid = - candidGen === 'custom' - ? (await readFile(candidPath)).toString() - : ''; - - const compilerInfo: CompilerInfo = { - canister_methods: { - candid: customCandid, - queries: [], - updates: [], - callbacks: {} - }, - env_vars: envVars - }; - - await generateWasmBinary( - canisterName, - stdioType, - js, - compilerInfo, - canisterConfig, - canisterPath, - experimental - ); - - const { candid, canisterMethods } = - await generateCandidAndCanisterMethods( - rustStagingWasmPath, - experimental - ); - - return { - candid: candidGen === 'custom' ? customCandid : candid, - canisterMethods - }; - } - - if (candidGen === 'http') { - const candid = ( - await readFile(join(AZLE_PACKAGE_PATH, 'server.did')) - ).toString(); - - const canisterMethods: CanisterMethods = { - candid, - queries: [ - { - name: 'http_request', - index: 0, - composite: true - } - ], - updates: [ - { - name: 'http_request_update', - index: 1 - } - ], - init: { name: 'init', index: 2 }, - post_upgrade: { name: 'postUpgrade', index: 3 }, - callbacks: {} - }; - - return { - candid, - canisterMethods - }; - } - - throw new Error(`Unsupported candid_gen: ${candidGen}`); -} diff --git a/src/compiler/get_canister_javascript.ts b/src/compiler/get_canister_javascript.ts deleted file mode 100644 index e858337f66..0000000000 --- a/src/compiler/get_canister_javascript.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { compileTypeScriptToJavaScript } from './compile_typescript_code'; -import { dim, red } from './utils/colors'; -import { Err, ok, Result } from './utils/result'; -import { - AzleError, - TsCompilationError, - TsSyntaxErrorLocation -} from './utils/types'; - -export async function getCanisterJavaScript( - mainPath: string, - wasmedgeQuickJsPath: string, - esmAliases: Record, - esmExternals: string[], - experimental: boolean -): Promise> { - const typeScriptCompilationResult = await compileTypeScriptToJavaScript( - mainPath, - wasmedgeQuickJsPath, - esmAliases, - esmExternals, - experimental - ); - - if (!ok(typeScriptCompilationResult)) { - const azleErrorResult = compilationErrorToAzleErrorResult( - typeScriptCompilationResult.err - ); - - return azleErrorResult; - } - - return typeScriptCompilationResult; -} - -function compilationErrorToAzleErrorResult(error: unknown): Err { - if (isTsCompilationError(error)) { - const firstError = error.errors[0]; - const codeSnippet = generateVisualDisplayOfErrorLocation( - firstError.location - ); - return Err({ - error: `TypeScript error: ${firstError.text}`, - suggestion: codeSnippet, - exitCode: 5 - }); - } else { - return Err({ - error: `Unable to compile TS to JS: ${error}`, - exitCode: 6 - }); - } -} - -function isTsCompilationError(error: unknown): error is TsCompilationError { - if ( - error && - typeof error === 'object' && - 'stack' in error && - 'message' in error && - 'errors' in error && - 'warnings' in error - ) { - return true; - } - return false; -} - -function generateVisualDisplayOfErrorLocation( - location: TsSyntaxErrorLocation -): string { - const { file, line, column, lineText } = location; - const marker = red('^'.padStart(column + 1)); - const preciseLocation = dim(`${file}:${line}:${column}`); - const previousLine = - line > 1 - ? dim(`${(line - 1).toString().padStart(line.toString().length)}| `) - : ''; - const offendingLine = `${dim(`${line}| `)}${lineText}`; - const subsequentLine = `${dim( - `${(line + 1).toString().padStart(line.toString().length)}| ` - )}${marker}`; - return `${preciseLocation}\n${previousLine}\n${offendingLine}\n${subsequentLine}`; -} diff --git a/src/compiler/get_consumer_config.ts b/src/compiler/get_consumer_config.ts deleted file mode 100644 index 83824b8170..0000000000 --- a/src/compiler/get_consumer_config.ts +++ /dev/null @@ -1,330 +0,0 @@ -import { execSync } from 'child_process'; -import { readFile } from 'fs/promises'; -import { glob } from 'glob'; - -import { yellow } from './utils/colors'; -import { CanisterConfig } from './utils/types'; - -const DEFAULT_KILL_SWITCH: Consumer['killSwitch'] = true; -const DEFAULT_PLATFORMS = ['icp']; -const DEFAULT_ASSETS = ['cycles']; -const DEFAULT_SHARED_PERCENTAGE: Consumer['sharedPercentage'] = 10; -const DEFAULT_PERIOD: Consumer['period'] = 1_440; -const DEFAULT_SHARING_HEURISTIC: Consumer['sharingHeuristic'] = - 'BURNED_WEIGHTED_HALVING'; -const DEFAULT_WEIGHT: Dependency['weight'] = 1; - -export type Consumer = { - killSwitch: boolean; - platforms: string[]; - assets: string[]; - sharedPercentage: number; - period: number; - sharingHeuristic: 'BURNED_WEIGHTED_HALVING'; - depthWeights: DepthWeights; - dependencies: Dependency[]; -}; - -export type DepthWeights = { - [key: number]: number; -}; - -export type Dependency = { - name: string; - depth: number; - weight: number; - platform: string; - asset: string; - paymentMechanism: string; - custom: Record; -}; - -export type ConsumerConfig = { - killSwitch?: Consumer['killSwitch']; - platforms?: Consumer['platforms']; - assets?: Consumer['assets']; - sharedPercentage?: Consumer['sharedPercentage']; - period?: Consumer['period']; - sharingHeuristic?: Consumer['sharingHeuristic']; - weights?: { - [packageName: string]: number; - }; -}; - -export type DependencyConfig = { - platform: Dependency['platform']; - asset: Dependency['asset']; - payment_mechanism: Dependency['paymentMechanism']; - custom: Dependency['custom']; -}; - -type DependencyTree = { - name: string; - dependencies?: DependenciesInTree; -}; - -type DependenciesInTree = { - [name: string]: DependencyInTree; -}; - -type DependencyInTree = { - version: string; - resolved: string; - overriden: boolean; - dependencies?: DependenciesInTree; -}; - -export async function getConsumer( - canisterConfig: CanisterConfig -): Promise { - const consumerConfig = getConsumerConfig(canisterConfig); - - const killSwitch = consumerConfig?.killSwitch ?? DEFAULT_KILL_SWITCH; - const platforms = consumerConfig?.platforms ?? DEFAULT_PLATFORMS; - const assets = consumerConfig?.assets ?? DEFAULT_ASSETS; - const sharedPercentage = - consumerConfig?.sharedPercentage ?? DEFAULT_SHARED_PERCENTAGE; - const period = consumerConfig?.period ?? DEFAULT_PERIOD; - const sharingHeuristic = - consumerConfig?.sharingHeuristic ?? DEFAULT_SHARING_HEURISTIC; - - if ( - killSwitch === true || - !platforms.includes('icp') || - !assets.includes('cycles') - ) { - return { - killSwitch, - platforms, - assets, - sharedPercentage: 0, - period: DEFAULT_PERIOD, - sharingHeuristic: 'BURNED_WEIGHTED_HALVING', - depthWeights: {}, - dependencies: [] - }; - } - - if (sharedPercentage > 100) { - throw new Error( - `OpenValueSharing: shared percentage cannot be greater than 100` - ); - } - - if (period === 0) { - throw new Error(`OpenValueSharing: period cannot be 0`); - } - - if (sharingHeuristic !== 'BURNED_WEIGHTED_HALVING') { - throw new Error( - `OpenValueSharing: heuristic ${DEFAULT_SHARING_HEURISTIC} is not supported. Only BURNED_WEIGHTED_HALVING is currently supported` - ); - } - - logWarningPeriod(consumerConfig); - - const dependenciesUnnormalized = - await getDependenciesUnnormalized(consumerConfig); - const dependencies = normalizeDependencies(dependenciesUnnormalized); - - const depthWeights = getDepthWeights(dependencies); - - return { - killSwitch, - platforms, - assets, - sharedPercentage, - period, - sharingHeuristic, - dependencies, - depthWeights - }; -} - -function getConsumerConfig( - canisterConfig: CanisterConfig -): ConsumerConfig | undefined { - return canisterConfig.custom?.openValueSharing; -} - -function logWarningPeriod(consumerConfig?: ConsumerConfig): void { - if (consumerConfig?.period !== undefined) { - console.warn( - yellow( - `\nOpenValueSharing: to avoid problematic behavior, it is not currently recommended to change the period manually\n` - ) - ); - } -} - -async function getDependenciesUnnormalized( - consumerConfig?: ConsumerConfig -): Promise { - const dependencyConfigPaths = await glob( - 'node_modules/**/.openvaluesharing.json' - ); - - // TODO notice the || true here - // TODO this is to overcome the ELSPROBLEMS error that is thrown on an invalid tree - // TODO I do not understand fully why this happens from my research - // TODO but the tree seems to still be output even if there is an error - // TODO the error output goes to stderr and the JSON goes to stdout - // TODO so we use || true to stop the execSync from throwing - // TODO this could end up being an issue in the future - const dependencyTree: DependencyTree = JSON.parse( - execSync(`npm ls --all --json || true`, { - stdio: ['pipe', 'pipe', 'ignore'] - }) - .toString() - .trim() - ); - - return await dependencyConfigPaths.reduce( - async (acc: Promise, dependencyConfigPath) => { - const accResolved = await acc; - - const npmPackagePath = dependencyConfigPath.replace( - '/.openvaluesharing.json', - '' - ); - - const npmPackageName = await getNpmPackageName(npmPackagePath); - - const dependencyConfigIcp = - await getDependencyConfigIcp(dependencyConfigPath); - - if (dependencyConfigIcp === undefined) { - return accResolved; - } - - const { payment_mechanism, ...dependencyConfigIcpProps } = - dependencyConfigIcp; - - const depth = getNpmPackageDepth( - dependencyTree.dependencies, - npmPackageName - ); - - if (depth === null) { - throw new Error( - `OpenValueSharing: could not determine depth for package "${npmPackageName}"` - ); - } - - return [ - ...accResolved, - { - name: npmPackageName, - depth, - weight: - consumerConfig?.weights?.[npmPackageName] ?? - DEFAULT_WEIGHT, - ...dependencyConfigIcpProps, - paymentMechanism: payment_mechanism - } - ]; - }, - Promise.resolve([]) - ); -} - -async function getNpmPackageName(npmPackagePath: string): Promise { - const packageJsonString = ( - await readFile(`${npmPackagePath}/package.json`) - ).toString(); - const packageJson: { [key: string]: any; name: string } = - JSON.parse(packageJsonString); - const npmPackageName = packageJson.name; - - return npmPackageName; -} - -async function getDependencyConfigIcp( - dependencyConfigPath: string -): Promise { - const dependencyConfigs: DependencyConfig[] = JSON.parse( - (await readFile(dependencyConfigPath)).toString() - ); - - const dependencyConfigsIcp = dependencyConfigs.filter( - (dependencyConfig) => dependencyConfig.platform === 'icp' - ); - const dependencyConfigIcp = dependencyConfigsIcp[0]; - - return dependencyConfigIcp; -} - -function getNpmPackageDepth( - dependencies: DependenciesInTree | undefined, - npmPackageName: string, - depth: number = 0 -): number | null { - const finalDepth = Object.entries(dependencies ?? {}).reduce( - (acc: number | null, [dependencyName, dependencyValue]) => { - if (acc !== null) { - return acc; - } else { - if (dependencyName === npmPackageName) { - return depth; - } - - return getNpmPackageDepth( - dependencyValue.dependencies, - npmPackageName, - depth + 1 - ); - } - }, - null - ); - - return finalDepth; -} - -function normalizeDependencies(dependencies: Dependency[]): Dependency[] { - const dependenciesTrimmed = dependencies.filter( - (dependency) => dependency.weight !== 0 - ); - - const uniqueDepths = [ - ...new Set(dependenciesTrimmed.map((dependency) => dependency.depth)) - ].sort((a, b) => a - b); - - const depthMapping = uniqueDepths.reduce((map, depth, index) => { - return map.set(depth, index); - }, new Map()); - - const normalizedDependencies = dependenciesTrimmed - .map((dependency) => { - const depth = depthMapping.get(dependency.depth); - - if (depth === undefined) { - throw new Error( - `OpenValueSharing: depth for package "${dependency.name}" cannot be undefined` - ); - } - - return { - ...dependency, - depth - }; - }) - .sort((a, b) => { - if (a.depth !== b.depth) { - return a.depth - b.depth; - } else { - return a.name.localeCompare(b.name); - } - }); - - return normalizedDependencies; -} - -function getDepthWeights(dependencies: Dependency[]): DepthWeights { - return dependencies.reduce((acc: DepthWeights, dependency) => { - return { - ...acc, - [dependency.depth]: (acc[dependency.depth] ?? 0) + dependency.weight - }; - }, {}); -} diff --git a/src/compiler/get_names.ts b/src/compiler/get_names.ts deleted file mode 100644 index 30c38ac803..0000000000 --- a/src/compiler/get_names.ts +++ /dev/null @@ -1,90 +0,0 @@ -// TODO make this function's return type explicit https://github.com/demergent-labs/azle/issues/1860 -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - -import { join } from 'path'; - -import { getCanisterId } from '../../dfx'; -import { version } from '../../package.json'; -import { - getCanisterConfig, - getCanisterName, - getStdIoType, - unwrap -} from './utils'; -import { execSyncPretty } from './utils/exec_sync_pretty'; -import { GLOBAL_AZLE_CONFIG_DIR } from './utils/global_paths'; -import { CanisterConfig } from './utils/types'; - -export async function getNames() { - const stdioType = getStdIoType(); - - const wasmedgeQuickJsName = `wasmedge-quickjs_${version}`; - - const wasmedgeQuickJsPath = join( - GLOBAL_AZLE_CONFIG_DIR, - wasmedgeQuickJsName - ); - - const replicaWebServerPort = execSyncPretty(`dfx info webserver-port`) - .toString() - .trim(); - - const canisterName = unwrap(getCanisterName(process.argv)); - const canisterPath = join('.azle', canisterName); - - const canisterConfig = unwrap(await getCanisterConfig(canisterName)); - const candidPath = process.env.CANISTER_CANDID_PATH; - - if (candidPath === undefined) { - throw new Error(`Azle: CANISTER_CANDID_PATH is not defined`); - } - - const envVars = getEnvVars(canisterConfig); - - const rustStagingWasmPath = join(canisterPath, `${canisterName}.wasm`); - - const canisterId = getCanisterId(canisterName); - - const reloadedJsPath = join('.azle', canisterName, 'main_reloaded.js'); - - const esmAliases = canisterConfig.esm_aliases ?? {}; - const esmExternals = canisterConfig.esm_externals ?? []; - - const experimental = canisterConfig.custom?.experimental ?? false; - - return { - stdioType, - wasmedgeQuickJsName, - wasmedgeQuickJsPath, - replicaWebServerPort, - canisterName, - canisterPath, - canisterConfig, - candidPath, - envVars, - rustStagingWasmPath, - canisterId, - reloadedJsPath, - esmAliases, - esmExternals, - experimental - }; -} - -function getEnvVars(canisterConfig: CanisterConfig): [string, string][] { - const env = [...(canisterConfig.env ?? []), 'AZLE_AUTORELOAD']; - - return env - .filter((envVarName) => process.env[envVarName] !== undefined) - .map((envVarName) => { - const envVarValue = process.env[envVarName]; - - if (envVarValue === undefined) { - throw new Error( - `Environment variable ${envVarName} must be undefined` - ); - } - - return [envVarName, envVarValue]; - }); -} diff --git a/src/compiler/handle_cli.ts b/src/compiler/handle_cli.ts deleted file mode 100644 index 13568f0760..0000000000 --- a/src/compiler/handle_cli.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { IOType } from 'child_process'; -import { rm } from 'fs/promises'; -import { join } from 'path'; - -import { version as azleVersion } from '../../package.json'; -import { uploadFiles } from './file_uploader'; -import { getFilesToUpload } from './file_uploader/get_files_to_upload'; -import { generateNewAzleProject } from './new_command'; -import { getStdIoType } from './utils'; -import { execSyncPretty } from './utils/exec_sync_pretty'; -import { - AZLE_PACKAGE_PATH, - GLOBAL_AZLE_CONFIG_DIR -} from './utils/global_paths'; - -export async function handleCli(): Promise { - const commandName = process.argv[2]; - - if (commandName === 'new') { - await handleCommandNew(); - - return true; - } - - if (commandName === 'clean') { - await handleCommandClean(); - - return true; - } - - if (commandName === 'upload-assets') { - await handleUploadAssets(); - - return true; - } - - if (commandName === '--version') { - handleVersionCommand(); - - return true; - } - - if (commandName === 'install-dfx-extension') { - installDfxExtension(getStdIoType()); - - return true; - } - - return false; -} - -async function handleCommandNew(): Promise { - await generateNewAzleProject(azleVersion); -} - -async function handleCommandClean(): Promise { - await rm(GLOBAL_AZLE_CONFIG_DIR, { - recursive: true, - force: true - }); - - console.info(`~/.config/azle directory deleted`); - - await rm('.azle', { - recursive: true, - force: true - }); - - console.info(`.azle directory deleted`); - - await rm('.dfx', { - recursive: true, - force: true - }); - - console.info(`.dfx directory deleted`); - - await rm('node_modules', { - recursive: true, - force: true - }); - - console.info(`node_modules directory deleted`); -} - -async function handleUploadAssets(): Promise { - const canisterName = process.argv[3]; - const srcPath = process.argv[4]; - const destPath = process.argv[5]; - const filesToUpload = await getFilesToUpload( - canisterName, - srcPath, - destPath - ); - await uploadFiles(canisterName, filesToUpload); -} - -function handleVersionCommand(): void { - console.info(azleVersion); -} - -// TODO this is just temporary -// TODO until we either make azle an official extension in the DFINITY dfx extensions repo -// TODO or we have a better way for the developer to install the extension locally -function installDfxExtension(stdioType: IOType): void { - const dfxExtensionDirectoryPath = join(AZLE_PACKAGE_PATH, 'dfx_extension'); - execSyncPretty( - `cd ${dfxExtensionDirectoryPath} && ./install.sh`, - stdioType - ); -} diff --git a/src/compiler/index.ts b/src/compiler/index.ts deleted file mode 100755 index 7e6add6be8..0000000000 --- a/src/compiler/index.ts +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env -S tsx --abort-on-uncaught-exception - -import { mkdir, rm, writeFile } from 'fs/promises'; -import { join } from 'path'; - -import { setupFileWatcher } from './file_watcher/setup_file_watcher'; -import { generateWasmBinary } from './generate_wasm_binary'; -import { getCandidAndCanisterMethods } from './get_candid_and_canister_methods'; -import { getCanisterJavaScript } from './get_canister_javascript'; -import { getNames } from './get_names'; -import { handleCli } from './handle_cli'; -import { logSuccess, time, unwrap } from './utils'; -import { green } from './utils/colors'; -import { execSyncPretty } from './utils/exec_sync_pretty'; -import { CompilerInfo } from './utils/types'; - -azle(); - -async function azle(): Promise { - const commandExecuted = await handleCli(); - - if (commandExecuted === true) { - return; - } - - const { - stdioType, - wasmedgeQuickJsPath, - replicaWebServerPort, - canisterName, - canisterPath, - canisterConfig, - candidPath, - envVars, - rustStagingWasmPath, - canisterId, - reloadedJsPath, - esmAliases, - esmExternals, - experimental - } = await getNames(); - - await time( - `\nBuilding canister ${green(canisterName)}`, - 'default', - async () => { - createAzleDirectories(canisterPath); - - const canisterJavaScript = unwrap( - await getCanisterJavaScript( - canisterConfig.main, - wasmedgeQuickJsPath, - esmAliases, - esmExternals, - experimental - ) - ); - - await writeFile(join(canisterPath, 'main.js'), canisterJavaScript); - - const { candid, canisterMethods } = - await getCandidAndCanisterMethods( - canisterConfig.candid_gen, - candidPath, - canisterName, - stdioType, - envVars, - rustStagingWasmPath, - canisterJavaScript, - canisterConfig, - canisterPath, - experimental - ); - - // This is for the dfx.json candid property - await writeFile(candidPath, candid); - - const compilerInfo: CompilerInfo = { - // The spread is because canisterMethods is a function with properties - canister_methods: { - ...canisterMethods - }, - env_vars: envVars - }; - - await generateWasmBinary( - canisterName, - stdioType, - canisterJavaScript, - compilerInfo, - canisterConfig, - canisterPath, - experimental - ); - - if ( - canisterConfig.build_assets !== undefined && - canisterConfig.build_assets !== null - ) { - execSyncPretty(canisterConfig.build_assets, stdioType); - } - - setupFileWatcher( - reloadedJsPath, - canisterId, - canisterConfig.main, - wasmedgeQuickJsPath, - esmAliases, - esmExternals, - canisterName, - canisterMethods.post_upgrade?.index ?? -1, - experimental - ); - } - ); - - logSuccess(canisterName, canisterId, replicaWebServerPort); -} - -async function createAzleDirectories(canisterPath: string): Promise { - await rm(canisterPath, { recursive: true, force: true }); - await mkdir(canisterPath, { recursive: true }); -} diff --git a/src/compiler/log_global_dependencies.ts b/src/compiler/log_global_dependencies.ts deleted file mode 100644 index dc7a498863..0000000000 --- a/src/compiler/log_global_dependencies.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { writeFile } from 'fs/promises'; -import { join } from 'path'; - -import { execSyncPretty } from './utils/exec_sync_pretty'; -import { AZLE_PACKAGE_PATH } from './utils/global_paths'; - -export async function logGlobalDependencies(): Promise { - const wasiVersion = execSyncPretty('cargo install --list | grep wasi2ic'); - const nodeVersion = execSyncPretty('node --version'); - const rustVersion = execSyncPretty('rustc --version'); - - const globalDependencies = - `wasi2ic version: ${wasiVersion}` + - `\n` + - `node version: ${nodeVersion}` + - `\n` + - `rustc version: ${rustVersion}`; - - await writeFile( - join(AZLE_PACKAGE_PATH, 'global_dependencies'), - globalDependencies - ); -} diff --git a/src/compiler/prepare_rust_staging_area.ts b/src/compiler/prepare_rust_staging_area.ts deleted file mode 100644 index c1f32b0fbc..0000000000 --- a/src/compiler/prepare_rust_staging_area.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { existsSync } from 'fs'; -import { mkdir, writeFile } from 'fs/promises'; -// @ts-ignore -import { copy } from 'fs-extra/esm'; -import { join } from 'path'; - -import { generateWorkspaceCargoToml } from './generate_cargo_toml_files'; -import { AZLE_PACKAGE_PATH } from './utils/global_paths'; -import { CanisterConfig, Toml } from './utils/types'; - -export async function prepareRustStagingArea( - canisterConfig: CanisterConfig, - canisterPath: string -): Promise { - const workspaceCargoToml: Toml = generateWorkspaceCargoToml( - canisterConfig.opt_level ?? '0' - ); - - await writeFile(`${canisterPath}/Cargo.toml`, workspaceCargoToml); - - await copy( - join(AZLE_PACKAGE_PATH, 'Cargo.lock'), - `${canisterPath}/Cargo.lock` - ); - - if (!existsSync(`${canisterPath}/canister`)) { - await mkdir(`${canisterPath}/canister`); - } - - await copy( - `${AZLE_PACKAGE_PATH}/src/compiler/rust/canister`, - `${canisterPath}/canister` - ); - - if (!existsSync(`${canisterPath}/open_value_sharing`)) { - await mkdir(`${canisterPath}/open_value_sharing`); - } - - await copy( - `${AZLE_PACKAGE_PATH}/src/compiler/rust/open_value_sharing`, - `${canisterPath}/open_value_sharing` - ); -} diff --git a/src/compiler/uploader_identity.ts b/src/compiler/uploader_identity.ts deleted file mode 100644 index c1d84d5d05..0000000000 --- a/src/compiler/uploader_identity.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - addController, - generateIdentity, - getPrincipal, - identityExists -} from '../../dfx'; - -export const AZLE_UPLOADER_IDENTITY_NAME = - process.env.AZLE_UPLOADER_IDENTITY_NAME ?? '_azle_file_uploader_identity'; - -export function generateUploaderIdentity(canisterName: string): string { - if (!identityExists(AZLE_UPLOADER_IDENTITY_NAME)) { - generateIdentity(AZLE_UPLOADER_IDENTITY_NAME); - } - - const principal = getPrincipal(AZLE_UPLOADER_IDENTITY_NAME); - - addController(canisterName, AZLE_UPLOADER_IDENTITY_NAME, principal); - - return AZLE_UPLOADER_IDENTITY_NAME; -} diff --git a/src/compiler/utils/colors.ts b/src/compiler/utils/colors.ts deleted file mode 100644 index 6019b89d75..0000000000 --- a/src/compiler/utils/colors.ts +++ /dev/null @@ -1,23 +0,0 @@ -export function red(text: string): string { - return `\x1b[31m${text}\x1b[0m`; -} - -export function yellow(text: string): string { - return `\x1b[33m${text}\x1b[0m`; -} - -export function green(text: string): string { - return `\x1b[32m${text}\x1b[0m`; -} - -export function blue(text: string): string { - return `\x1b[34m${text}\x1b[0m`; -} - -export function purple(text: string): string { - return `\x1b[35m${text}\x1b[0m`; -} - -export function dim(text: string): string { - return `\x1b[2m${text}\x1b[0m`; -} diff --git a/src/compiler/utils/exec_sync_pretty.ts b/src/compiler/utils/exec_sync_pretty.ts deleted file mode 100644 index a4cf4bd821..0000000000 --- a/src/compiler/utils/exec_sync_pretty.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { execSync, IOType } from 'child_process'; - -export function execSyncPretty(command: string, stdio?: IOType): Buffer { - try { - return execSync(command, { stdio }); - } catch (error) { - throw new Error(`Azle build error`); - } -} diff --git a/src/compiler/utils/filter_async.ts b/src/compiler/utils/filter_async.ts deleted file mode 100644 index f36ac19fb8..0000000000 --- a/src/compiler/utils/filter_async.ts +++ /dev/null @@ -1,12 +0,0 @@ -export async function filterAsync( - elements: T[], - callback: (element: T) => Promise -): Promise { - return await elements.reduce(async (accPromise: Promise, element) => { - const acc = await accPromise; - if (await callback(element)) { - return [...acc, element]; - } - return acc; - }, Promise.resolve([])); -} diff --git a/src/compiler/utils/get_canister_config.ts b/src/compiler/utils/get_canister_config.ts deleted file mode 100644 index 3f0112230d..0000000000 --- a/src/compiler/utils/get_canister_config.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { existsSync } from 'fs'; -import { readFile } from 'fs/promises'; - -import { blue, green, purple, red, yellow } from './colors'; -import { Err, Ok, Result } from './result'; -import { AzleError, CanisterConfig, DfxJson } from './types'; - -export async function getCanisterConfig( - canisterName: string -): Promise> { - const exampleDfxJson = colorFormattedDfxJsonExample(canisterName); - - if (!existsSync(`dfx.json`)) { - return Err({ - error: 'Missing dfx.json', - suggestion: `Create a dfx.json file in the current directory with the following format:\n\n${exampleDfxJson}`, - exitCode: 2 - }); - } - - const dfxJson: DfxJson = JSON.parse( - (await readFile('dfx.json')).toString() - ); - const canisterConfig = dfxJson.canisters[canisterName]; - - if (!canisterConfig) { - return Err({ - error: `Unable to find canister "${canisterName}" in ./dfx.json`, - suggestion: `Make sure your dfx.json contains an entry for "${canisterName}". For example:\n\n${exampleDfxJson}`, - exitCode: 3 - }); - } - - const { main } = canisterConfig; - - if (main === undefined) { - const missingFields = [['"main"', main]] - .filter(([_, value]) => !value) - .map(([field, _]) => field); - const fieldOrFields = missingFields.length === 1 ? 'field' : 'fields'; - const missingFieldNames = missingFields.join(', '); - return Err({ - error: `Missing ${fieldOrFields} ${missingFieldNames} in ./dfx.json`, - suggestion: `Make sure your dfx.json looks similar to the following:\n\n${exampleDfxJson}`, - exitCode: 4 - }); - } - - return Ok(canisterConfig); -} - -function colorFormattedDfxJsonExample(canisterName: string): string { - return ` ${yellow('{')} - ${red('"canisters"')}: ${purple('{')} - ${red(`"${canisterName}"`)}: ${blue('{')} - ${red('"type"')}: ${green('"azle"')}, - ${red('"main"')}: ${green('"src/index.ts"')} - ${blue('}')} - ${purple('}')} - ${yellow('}')}`; -} diff --git a/src/compiler/utils/get_canister_name.ts b/src/compiler/utils/get_canister_name.ts deleted file mode 100644 index 899e4313b2..0000000000 --- a/src/compiler/utils/get_canister_name.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { version } from '../../../package.json'; -import { dim, green } from './colors'; -import { Err, Ok, Result } from './result'; -import { AzleError } from './types'; - -export function getCanisterName(args: string[]): Result { - const canisterNames = args.slice(2).filter((arg) => !isCliFlag(arg)); - - if (canisterNames.length === 0) { - return Err({ suggestion: `azle v${version}\n\n${getUsageInfo()}` }); - } - - if (canisterNames.length > 1) { - return Err({ - error: 'Too many arguments', - suggestion: getUsageInfo(), - exitCode: 1 - }); - } - const canisterName = canisterNames[0]; - - return Ok(canisterName); -} - -function getUsageInfo(): string { - return `Usage: azle ${dim('[-v|--verbose]')} ${green('')}`; -} - -function isCliFlag(arg: string): boolean { - return arg.startsWith('--') || arg.startsWith('-'); -} diff --git a/src/compiler/utils/global_paths.ts b/src/compiler/utils/global_paths.ts deleted file mode 100644 index 318191bd6c..0000000000 --- a/src/compiler/utils/global_paths.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { homedir } from 'os'; -import { dirname, join, resolve } from 'path'; -import { fileURLToPath } from 'url'; - -export const GLOBAL_AZLE_CONFIG_DIR = resolve( - homedir(), - join('.config', 'azle') -); - -export const AZLE_PACKAGE_PATH = join( - dirname(fileURLToPath(import.meta.url)), - '..', - '..', - '..' -); - -export const STABLE_STATIC_CANISTER_TEMPLATE_PATH = join( - AZLE_PACKAGE_PATH, - 'canister_templates', - 'stable.wasm' -); - -export const EXPERIMENTAL_STATIC_CANISTER_TEMPLATE_PATH = join( - AZLE_PACKAGE_PATH, - 'canister_templates', - 'experimental.wasm' -); diff --git a/src/compiler/utils/index.ts b/src/compiler/utils/index.ts deleted file mode 100644 index e69ad09ff9..0000000000 --- a/src/compiler/utils/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { IOType } from 'child_process'; - -export { getCanisterConfig } from './get_canister_config'; -export { getCanisterName } from './get_canister_name'; -export { logSuccess } from './log_success'; -export { unwrap } from './result'; -export { time } from './time'; - -export function getStdIoType(): IOType { - return isVerboseMode() ? 'inherit' : 'pipe'; -} - -export function isVerboseMode(): boolean { - return ( - process.argv.includes('--verbose') || - process.argv.includes('-v') || - process.env.AZLE_VERBOSE === 'true' - ); -} diff --git a/src/compiler/utils/log_success.ts b/src/compiler/utils/log_success.ts deleted file mode 100644 index 6f0a9a12d9..0000000000 --- a/src/compiler/utils/log_success.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { green } from './colors'; - -export function logSuccess( - canisterName: string, - canisterId: string, - replicaWebServerPort: string -): void { - const url = - process.env.DFX_NETWORK === 'ic' - ? `https://${canisterId}.raw.icp0.io` - : `http://${canisterId}.localhost:${replicaWebServerPort}`; - - console.info( - `\nšŸŽ‰ Canister ${green(canisterName)} will be available at ${green( - url - )}\n` - ); -} diff --git a/src/compiler/utils/result.ts b/src/compiler/utils/result.ts deleted file mode 100644 index db5f6ca11b..0000000000 --- a/src/compiler/utils/result.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { red } from './colors'; -import { AzleError } from './types'; - -export type Ok = { - ok: T; -}; - -export type Err = { - err: T; -}; - -export type Result = Partial<{ - ok: Ok; - err: Err; -}>; - -export function ok(result: Result): result is Ok { - return result.err === undefined; -} - -export function unwrap(result: Result): Ok | never { - if (!ok(result)) { - const err = result.err as NonNullable; - exitWithError(err); - } - return result.ok; -} - -export function Err(err: T): Err { - return { err }; -} - -export function Ok(ok: T): Ok { - return { ok }; -} - -function exitWithError(payload: AzleError): never { - if (payload.error) { - console.error(`\nšŸ’£ ${red(payload.error)}`); - } - if (payload.suggestion) { - console.error(`\n${payload.suggestion}`); - } - console.info(`\nšŸ’€ Build failed`); - process.exit(payload.exitCode ?? 0); -} diff --git a/src/compiler/utils/time.ts b/src/compiler/utils/time.ts deleted file mode 100644 index bfdc4ba1c0..0000000000 --- a/src/compiler/utils/time.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { dim } from './colors'; - -export async function time( - label: string, - mode: 'inline' | 'default', - callback: () => Promise -): Promise { - const startTime = process.hrtime(); - console.info(label); - const result = await callback(); - const endTime = process.hrtime(startTime); - const duration = parseHrTimeToSeconds(endTime); - - if (mode === 'inline') { - const leadingNewLinesCount = (label.match(/^[\n]+/g) || [''])[0].length; - const cursorUp = `\x1b[${1 + leadingNewLinesCount}A`; - process.stdout.write(`${cursorUp}${label} ${dim(`${duration}s`)}\n`); - } else { - console.info(`\nDone in ${duration}s`); - } - - return result; -} - -function parseHrTimeToSeconds( - hrTime: [number, number], - precision: number = 2 -): string { - const seconds = (hrTime[0] + hrTime[1] / 1_000_000_000).toFixed(precision); - return seconds; -} diff --git a/src/compiler/utils/types.ts b/src/compiler/utils/types.ts deleted file mode 100644 index dc70cc3a68..0000000000 --- a/src/compiler/utils/types.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { RequireExactlyOne } from '../../lib/experimental'; -import { ConsumerConfig } from '../get_consumer_config'; - -export type AzleError = { - error?: string; - suggestion?: string; - exitCode?: number; -}; - -export type DfxJson = Readonly<{ - canisters: Readonly<{ - [key: string]: CanisterConfig; - }>; -}>; - -export type JavaScript = string; - -export type CanisterConfig = Readonly<{ - type: 'azle'; - main: string; - build?: string; - build_assets?: string; - candid?: string; - candid_gen?: CandidGen; - wasm?: string; - env?: string[]; - opt_level?: OptLevel; - assets?: [string, string][]; - esm_aliases?: Record; - esm_externals?: string[]; - // TODO we should move all custom properties into custom in a subsequent PR - custom?: { - experimental?: boolean; - openValueSharing?: ConsumerConfig; - }; -}>; - -export type OptLevel = '0' | '1' | '2' | '3' | '4'; - -export type CandidGen = 'automatic' | 'custom' | 'http'; - -export type CompilerInfo = { - canister_methods: CanisterMethods; - env_vars: [string, string][]; -}; - -export type CanisterMethods = { - candid: string; - queries: CanisterMethod[]; - updates: CanisterMethod[]; - init?: CanisterMethod; - pre_upgrade?: CanisterMethod; - post_upgrade?: CanisterMethod; - heartbeat?: CanisterMethod; - inspect_message?: CanisterMethod; - callbacks: { - [key: string]: (...args: any) => any; - }; -}; - -export type CanisterMethod = { - name: string; - composite?: boolean; - index: number; -}; - -export type Plugin = { - path: string; - register_function: string; -}; - -export type RunOptions = { - rootPath: string; -}; - -export type Rust = string; - -export type SpawnSyncError = RequireExactlyOne<{ - Error: string; - Signal: NodeJS.Signals; - Status: number; -}>; - -export type Toml = string; - -export type TsCompilationError = { - stack: string; - message: string; - errors: TsSyntaxError[]; - warnings: unknown[]; -}; - -export type TsSyntaxErrorLocation = { - column: number; - file: string; - length: number; - line: number; - lineText: string; - namespace: string; - suggestion: string; -}; - -export type TsSyntaxError = { - detail?: unknown; - location: TsSyntaxErrorLocation; - notes: unknown[]; - pluginName: string; - text: string; -}; - -export type TypeScript = string; diff --git a/src/compiler/wasm_binary_manipulation.ts b/src/compiler/wasm_binary_manipulation.ts deleted file mode 100644 index 0ee257e389..0000000000 --- a/src/compiler/wasm_binary_manipulation.ts +++ /dev/null @@ -1,252 +0,0 @@ -// import { func, indexLiteral, instr } from '@webassemblyjs/ast'; -// import { addExport } from '@webassemblyjs/wasm-edit'; -// import { decode, encode } from '@webassemblyjs/wasm-parser'; -import binaryen from 'binaryen'; -import { readFile, writeFile } from 'fs/promises'; -import { join } from 'path'; - -import { getConsumer } from './get_consumer_config'; -import { - AZLE_PACKAGE_PATH, - EXPERIMENTAL_STATIC_CANISTER_TEMPLATE_PATH, - STABLE_STATIC_CANISTER_TEMPLATE_PATH -} from './utils/global_paths'; -import { CanisterConfig, CompilerInfo } from './utils/types'; - -// TODO put the licenses in the binary? Or with Azle? Probably with Azle actually -// TODO it would be neat to be the licenses in all Azle binaries though -// TODO can we make the start function just load the passive segment into memory? - -export async function manipulateWasmBinary( - canisterName: string, - js: string, - compilerInfo: CompilerInfo, - canisterConfig: CanisterConfig, - experimental: boolean -): Promise { - const originalWasm = await readFile( - experimental === true - ? EXPERIMENTAL_STATIC_CANISTER_TEMPLATE_PATH - : STABLE_STATIC_CANISTER_TEMPLATE_PATH - ); - - const module = binaryen.readBinary(originalWasm); - - compilerInfo.canister_methods.queries.forEach( - ({ name: functionName, index, composite }) => { - addCanisterMethod( - module, - `${ - composite === true - ? 'canister_composite_query' - : 'canister_query' - } ${functionName}`, - 'execute_method_js', - index, - true - ); - } - ); - - compilerInfo.canister_methods.updates.forEach( - ({ name: functionName, index }) => { - addCanisterMethod( - module, - `canister_update ${functionName}`, - 'execute_method_js', - index, - true - ); - } - ); - - addCanisterMethod( - module, - 'canister_init', - 'init', - compilerInfo.canister_methods.init?.index ?? -1, - true - ); - - addCanisterMethod( - module, - 'canister_post_upgrade', - 'post_upgrade', - compilerInfo.canister_methods.post_upgrade?.index ?? -1, - true - ); - - if (compilerInfo.canister_methods.pre_upgrade !== undefined) { - addCanisterMethod( - module, - 'canister_pre_upgrade', - 'execute_method_js', - compilerInfo.canister_methods.pre_upgrade.index, - false - ); - } - - if (compilerInfo.canister_methods.inspect_message !== undefined) { - addCanisterMethod( - module, - 'canister_inspect_message', - 'execute_method_js', - compilerInfo.canister_methods.inspect_message.index, - false - ); - } - - if (compilerInfo.canister_methods.heartbeat !== undefined) { - addCanisterMethod( - module, - 'canister_heartbeat', - 'execute_method_js', - compilerInfo.canister_methods.heartbeat.index, - false - ); - } - - const memoryInfo = module.getMemoryInfo(); - - const numMemorySegments = module.getNumMemorySegments(); - - let segments = []; - - for (let i = 0; i < numMemorySegments; i++) { - const segment = module.getMemorySegmentInfoByIndex(i); - - segments.push(segment); - } - - const normalizedSegments = segments.map((segment) => { - return { - offset: module.i32.const(segment.offset), - data: new Uint8Array(segment.data), - passive: segment.passive - }; - }); - - const consumer = await getConsumer(canisterConfig); - - const jsEncoded = new TextEncoder().encode(js); - const wasmDataEncoded = new TextEncoder().encode( - JSON.stringify({ - env_vars: compilerInfo.env_vars, - consumer, - management_did: ( - await readFile( - join(AZLE_PACKAGE_PATH, 'canisters', 'management', 'ic.did') - ) - ).toString(), - experimental - }) - ); - - module.setMemory(memoryInfo.initial, memoryInfo.max ?? -1, null, [ - ...normalizedSegments, - { - offset: 0, - data: jsEncoded, - passive: true - }, - { - offset: 0, - data: wasmDataEncoded, - passive: true - } - ]); - - module.removeFunction('js_passive_data_size'); - module.removeFunction('init_js_passive_data'); - - module.addFunction( - 'js_passive_data_size', - binaryen.none, - binaryen.i32, - [], - module.i32.const(jsEncoded.byteLength) - ); - - const jsPassiveDataSegmentNumber = normalizedSegments.length; - - module.addFunction( - 'init_js_passive_data', - binaryen.createType([binaryen.i32]), - binaryen.i32, // TODO just to stop weird Rust optimizations - [], - module.block(null, [ - module.memory.init( - jsPassiveDataSegmentNumber.toString() as unknown as number, - module.local.get(0, binaryen.i32), - module.i32.const(0), - module.i32.const(jsEncoded.byteLength) - ), - module.data.drop( - jsPassiveDataSegmentNumber.toString() as unknown as number - ), - module.return(module.local.get(0, binaryen.i32)) - ]) - ); - - module.removeFunction('wasm_data_passive_data_size'); - module.removeFunction('init_wasm_data_passive_data'); - - module.addFunction( - 'wasm_data_passive_data_size', - binaryen.none, - binaryen.i32, - [], - module.i32.const(wasmDataEncoded.byteLength) - ); - - const wasmDataPassiveDataSegmentNumber = normalizedSegments.length + 1; - - module.addFunction( - 'init_wasm_data_passive_data', - binaryen.createType([binaryen.i32]), - binaryen.i32, // TODO just to stop weird Rust optimizations - [], - module.block(null, [ - module.memory.init( - wasmDataPassiveDataSegmentNumber.toString() as unknown as number, - module.local.get(0, binaryen.i32), - module.i32.const(0), - module.i32.const(wasmDataEncoded.byteLength) - ), - module.data.drop( - wasmDataPassiveDataSegmentNumber.toString() as unknown as number - ), - module.return(module.local.get(0, binaryen.i32)) - ]) - ); - - // TODO do we need to validate? It takes some extra time - // module.validate(); - - const newWasm = module.emitBinary(); - - await writeFile(`.azle/${canisterName}/${canisterName}.wasm`, newWasm); -} - -function addCanisterMethod( - module: binaryen.Module, - exportName: string, - functionToCall: string, - index: number, - passArgData: boolean -): void { - const funcBody = module.block(null, [ - module.call( - functionToCall, - [ - module.i32.const(index), - module.i32.const(passArgData === true ? 1 : 0) - ], - binaryen.none - ) - ]); - - module.addFunction(exportName, binaryen.none, binaryen.none, [], funcBody); - - module.addFunctionExport(exportName, exportName); -} diff --git a/test/index.ts b/test/index.ts index 5354554e40..efe82b76bf 100644 --- a/test/index.ts +++ b/test/index.ts @@ -6,7 +6,7 @@ import { describe, expect, test } from '@jest/globals'; import { join } from 'path'; import { getCanisterId } from '../dfx'; -import { execSyncPretty } from '../src/compiler/utils/exec_sync_pretty'; +import { execSyncPretty } from '../src/build/stable/utils/exec_sync_pretty'; export { expect } from '@jest/globals'; export type Test = () => void;