diff --git a/src/constants/build.constants.ts b/src/constants/build.constants.ts new file mode 100644 index 0000000..e465dc8 --- /dev/null +++ b/src/constants/build.constants.ts @@ -0,0 +1,12 @@ +import {join} from 'node:path'; +import {DEVELOPER_PROJECT_SATELLITE_PATH} from './dev.constants'; + +export const SATELLITE_DID_FILE = join(DEVELOPER_PROJECT_SATELLITE_PATH, 'satellite.did'); +export const EXTENSION_DID_FILE_NAME = 'satellite_extension.did'; +export const SATELLITE_CUSTOM_DID_FILE = join( + DEVELOPER_PROJECT_SATELLITE_PATH, + EXTENSION_DID_FILE_NAME +); + +export const AUTO_GENERATED = `// This file was automatically generated by the Juno CLI. +// Any modifications may be overwritten.`; diff --git a/src/services/functions/build/build.did.services.ts b/src/services/functions/build/build.did.services.ts new file mode 100644 index 0000000..3882b9f --- /dev/null +++ b/src/services/functions/build/build.did.services.ts @@ -0,0 +1,99 @@ +import {isNullish} from '@dfinity/utils'; +import {spawn} from '@junobuild/cli-tools'; +import {generateApi as generateApiLib} from '@junobuild/did-tools'; +import {existsSync} from 'node:fs'; +import {readFile, rename, rm} from 'node:fs/promises'; +import {join} from 'node:path'; +import {detectJunoConfigType} from '../../../configs/juno.config'; +import { + EXTENSION_DID_FILE_NAME, + SATELLITE_CUSTOM_DID_FILE +} from '../../../constants/build.constants'; +import {DEVELOPER_PROJECT_SATELLITE_DECLARATIONS_PATH} from '../../../constants/dev.constants'; +import {readPackageJson} from '../../../utils/pkg.utils'; +import {detectPackageManager} from '../../../utils/pm.utils'; + +const satellitedIdl = (type: 'js' | 'ts'): string => + `${DEVELOPER_PROJECT_SATELLITE_DECLARATIONS_PATH}/satellite.${type === 'ts' ? 'did.d.ts' : 'factory.did.js'}`; + +export const generateDid = async () => { + // No satellite_extension.did and therefore no services to generate to JS and TS. + if (!existsSync(SATELLITE_CUSTOM_DID_FILE)) { + return; + } + + // We check if the developer has added any API endpoints. If none, we do not need to generate the bindings for JS and TS. + const extensionDid = await readFile(SATELLITE_CUSTOM_DID_FILE, 'utf-8'); + const noAdditionalExtensionDid = 'service : { build_version : () -> (text) query }'; + + if (extensionDid.trim() === noAdditionalExtensionDid) { + return; + } + + const pm = detectPackageManager(); + + const command = pm === 'npm' || isNullish(pm) ? 'npx' : pm; + + // --actor-disabled: skip generating actor files, since we handle those ourselves + // --force: overwrite files. Required; otherwise, icp-bindgen would delete files at preprocess, + // which causes issues when multiple .did files are located in the same folder. + await spawn({ + command, + args: [ + 'icp-bindgen', + '--did-file', + SATELLITE_CUSTOM_DID_FILE, + '--out-dir', + DEVELOPER_PROJECT_SATELLITE_DECLARATIONS_PATH, + '--actor-disabled', + '--force' + ], + silentOut: true + }); + + // icp-bindgen generates the files in a `declarations` subfolder + // using a different suffix for JavaScript as the one we used to use. + // That's why we have to post-process the results. + const generatedFolder = join(DEVELOPER_PROJECT_SATELLITE_DECLARATIONS_PATH, 'declarations'); + + await rename(join(generatedFolder, `${EXTENSION_DID_FILE_NAME}.d.ts`), satellitedIdl('ts')); + await rename(join(generatedFolder, `${EXTENSION_DID_FILE_NAME}.js`), satellitedIdl('js')); + + await rm(generatedFolder, {recursive: true, force: true}); +}; + +export const generateApi = async () => { + const inputFile = satellitedIdl('ts'); + + if (!existsSync(inputFile)) { + return; + } + + const detectedConfig = detectJunoConfigType(); + const outputLanguage = detectedConfig?.configType === 'ts' ? 'ts' : 'js'; + + const outputFile = `${DEVELOPER_PROJECT_SATELLITE_DECLARATIONS_PATH}/satellite.api.${outputLanguage}`; + + const readCoreLib = async (): Promise<'core' | 'core-standalone'> => { + try { + const {dependencies} = await readPackageJson(); + return Object.keys(dependencies ?? {}).includes('@junobuild/core-standalone') + ? 'core-standalone' + : 'core'; + } catch (_err: unknown) { + // This should not block the developer therefore we fallback to core which is the common way of using the library + return 'core'; + } + }; + + const coreLib = await readCoreLib(); + + await generateApiLib({ + inputFile, + outputFile, + transformerOptions: { + outputLanguage, + coreLib + } + }); +}; diff --git a/src/services/functions/build/build.rust.services.ts b/src/services/functions/build/build.rust.services.ts index 8defbbf..846fb4c 100644 --- a/src/services/functions/build/build.rust.services.ts +++ b/src/services/functions/build/build.rust.services.ts @@ -1,17 +1,19 @@ import {isEmptyString, isNullish, nonNullish} from '@dfinity/utils'; import {execute, formatBytes, gzipFile, spawn} from '@junobuild/cli-tools'; -import {generateApi} from '@junobuild/did-tools'; import {red, yellow} from 'kleur'; import {existsSync} from 'node:fs'; -import {copyFile, lstat, mkdir, readFile, rename, rm, writeFile} from 'node:fs/promises'; +import {copyFile, lstat, mkdir, rename, writeFile} from 'node:fs/promises'; import {join, relative} from 'node:path'; import ora, {type Ora} from 'ora'; import {compare, minVersion, satisfies} from 'semver'; -import {detectJunoConfigType} from '../../../configs/juno.config'; +import { + AUTO_GENERATED, + EXTENSION_DID_FILE_NAME, + SATELLITE_CUSTOM_DID_FILE, + SATELLITE_DID_FILE +} from '../../../constants/build.constants'; import { DEPLOY_SPUTNIK_PATH, - DEVELOPER_PROJECT_SATELLITE_DECLARATIONS_PATH, - DEVELOPER_PROJECT_SATELLITE_PATH, JUNO_PACKAGE_JSON_PATH, SATELLITE_OUTPUT, SATELLITE_PROJECT_NAME, @@ -24,10 +26,9 @@ import {checkCandidExtractor, checkIcWasm, checkWasi2ic} from '../../../utils/bu import {readSatelliteDid} from '../../../utils/did.utils'; import {checkRustVersion} from '../../../utils/env.utils'; import {formatTime} from '../../../utils/format.utils'; -import {readPackageJson} from '../../../utils/pkg.utils'; -import {detectPackageManager} from '../../../utils/pm.utils'; import {isHeadless} from '../../../utils/process.utils'; import {readEmulatorConfigAndCreateDeployTargetDir} from '../../emulator/_fs.services'; +import {generateApi, generateDid} from './build.did.services'; import {prepareJunoPkgForSatellite, prepareJunoPkgForSputnik} from './build.metadata.services'; import {dispatchEmulatorTouchSatellite} from './touch.services'; @@ -139,8 +140,8 @@ export const buildRust = async ({ if (target !== 'wasm32-wasip1') { // TODO: support binding files for serverless functions in JS/TS - await didc(); - await api(); + await generateDid(); + await generateApi(); } spinner.text = 'Binding metadata...'; @@ -162,13 +163,6 @@ export const buildRust = async ({ await dispatchEmulatorTouchSatellite(); }; -const SATELLITE_DID_FILE = join(DEVELOPER_PROJECT_SATELLITE_PATH, 'satellite.did'); -const EXTENSION_DID_FILE_NAME = 'satellite_extension.did'; -const SATELLITE_CUSTOM_DID_FILE = join(DEVELOPER_PROJECT_SATELLITE_PATH, EXTENSION_DID_FILE_NAME); - -const AUTO_GENERATED = `// This file was automatically generated by the Juno CLI. -// Any modifications may be overwritten.`; - const did = async ({cargoOutputWasm}: {cargoOutputWasm: string}) => { let candid = ''; await spawn({ @@ -197,91 +191,6 @@ const did = async ({cargoOutputWasm}: {cargoOutputWasm: string}) => { ); }; -const satellitedIdl = (type: 'js' | 'ts'): string => - `${DEVELOPER_PROJECT_SATELLITE_DECLARATIONS_PATH}/satellite.${type === 'ts' ? 'did.d.ts' : 'factory.did.js'}`; - -const didc = async () => { - // No satellite_extension.did and therefore no services to generate to JS and TS. - if (!existsSync(SATELLITE_CUSTOM_DID_FILE)) { - return; - } - - // We check if the developer has added any API endpoints. If none, we do not need to generate the bindings for JS and TS. - const extensionDid = await readFile(SATELLITE_CUSTOM_DID_FILE, 'utf-8'); - const noAdditionalExtensionDid = 'service : { build_version : () -> (text) query }'; - - if (extensionDid.trim() === noAdditionalExtensionDid) { - return; - } - - const pm = detectPackageManager(); - - const command = pm === 'npm' || isNullish(pm) ? 'npx' : pm; - - // --actor-disabled: skip generating actor files, since we handle those ourselves - // --force: overwrite files. Required; otherwise, icp-bindgen would delete files at preprocess, - // which causes issues when multiple .did files are located in the same folder. - await spawn({ - command, - args: [ - 'icp-bindgen', - '--did-file', - SATELLITE_CUSTOM_DID_FILE, - '--out-dir', - DEVELOPER_PROJECT_SATELLITE_DECLARATIONS_PATH, - '--actor-disabled', - '--force' - ], - silentOut: true - }); - - // icp-bindgen generates the files in a `declarations` subfolder - // using a different suffix for JavaScript as the one we used to use. - // That's why we have to post-process the results. - const generatedFolder = join(DEVELOPER_PROJECT_SATELLITE_DECLARATIONS_PATH, 'declarations'); - - await rename(join(generatedFolder, `${EXTENSION_DID_FILE_NAME}.d.ts`), satellitedIdl('ts')); - await rename(join(generatedFolder, `${EXTENSION_DID_FILE_NAME}.js`), satellitedIdl('js')); - - await rm(generatedFolder, {recursive: true, force: true}); -}; - -const api = async () => { - const inputFile = satellitedIdl('ts'); - - if (!existsSync(inputFile)) { - return; - } - - const detectedConfig = detectJunoConfigType(); - const outputLanguage = detectedConfig?.configType === 'ts' ? 'ts' : 'js'; - - const outputFile = `${DEVELOPER_PROJECT_SATELLITE_DECLARATIONS_PATH}/satellite.api.${outputLanguage}`; - - const readCoreLib = async (): Promise<'core' | 'core-standalone'> => { - try { - const {dependencies} = await readPackageJson(); - return Object.keys(dependencies ?? {}).includes('@junobuild/core-standalone') - ? 'core-standalone' - : 'core'; - } catch (_err: unknown) { - // This should not block the developer therefore we fallback to core which is the common way of using the library - return 'core'; - } - }; - - const coreLib = await readCoreLib(); - - await generateApi({ - inputFile, - outputFile, - transformerOptions: { - outputLanguage, - coreLib - } - }); -}; - const extractBuildType = async ({paths}: Pick = {}): Promise< BuildType | {error: string} > => {