diff --git a/package-lock.json b/package-lock.json index 85c3667..2d03ac3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,8 @@ "@junobuild/admin": "^0.0.56", "@junobuild/cli-tools": "^0.0.14", "@junobuild/config-loader": "^0.0.5", - "@junobuild/core-peer": "^0.0.26", + "@junobuild/core-peer": "^0.0.26-next-2024-09-12.13", + "@junobuild/did-tools": "^0.0.2-next-2024-09-12.12", "@junobuild/utils": "^0.0.25", "conf": "^13.0.1", "open": "^10.1.0", @@ -1523,19 +1524,34 @@ } }, "node_modules/@junobuild/core-peer": { - "version": "0.0.26", - "resolved": "https://registry.npmjs.org/@junobuild/core-peer/-/core-peer-0.0.26.tgz", - "integrity": "sha512-VEX9YBjpAHqFsPb4NXuTyvBpq6mjSYyEt1/VYSwe6mNfCCYWYE/wdZHLx0D9WPPuHXgwEWzBjC4fx0BAPThe7g==", + "version": "0.0.26-next-2024-09-12.13", + "resolved": "https://registry.npmjs.org/@junobuild/core-peer/-/core-peer-0.0.26-next-2024-09-12.13.tgz", + "integrity": "sha512-eXEl8XLdn8CqkJRdt0AYuucK0vESkWrRyTKuWQg5ouD550bQ7wrHOiHemFVqNunP0A4U1E4yZZPACz1/Py5kaA==", "license": "MIT", "dependencies": { "@junobuild/storage": "*", "@junobuild/utils": "*" }, "peerDependencies": { - "@dfinity/agent": "^2.0.0", - "@dfinity/auth-client": "^2.0.0", - "@dfinity/identity": "^2.0.0", - "@dfinity/principal": "^2.0.0" + "@dfinity/agent": "*", + "@dfinity/auth-client": "*", + "@dfinity/identity": "*", + "@dfinity/principal": "*" + } + }, + "node_modules/@junobuild/did-tools": { + "version": "0.0.2-next-2024-09-12.12", + "resolved": "https://registry.npmjs.org/@junobuild/did-tools/-/did-tools-0.0.2-next-2024-09-12.12.tgz", + "integrity": "sha512-hU/8wh8X8Asth+CuiE8hpdOXPYJRfELoXLkdWqSZ9YLt1xWm5VJYOe1gndFZScknQvlhjsVM1tGaOwbOnFv5cA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.6", + "@babel/plugin-transform-modules-commonjs": "^7.24.6", + "@babel/preset-typescript": "^7.24.6", + "@babel/traverse": "^7.24.6" + }, + "peerDependencies": { + "@junobuild/utils": "*" } }, "node_modules/@junobuild/storage": { @@ -7720,14 +7736,25 @@ } }, "@junobuild/core-peer": { - "version": "0.0.26", - "resolved": "https://registry.npmjs.org/@junobuild/core-peer/-/core-peer-0.0.26.tgz", - "integrity": "sha512-VEX9YBjpAHqFsPb4NXuTyvBpq6mjSYyEt1/VYSwe6mNfCCYWYE/wdZHLx0D9WPPuHXgwEWzBjC4fx0BAPThe7g==", + "version": "0.0.26-next-2024-09-12.13", + "resolved": "https://registry.npmjs.org/@junobuild/core-peer/-/core-peer-0.0.26-next-2024-09-12.13.tgz", + "integrity": "sha512-eXEl8XLdn8CqkJRdt0AYuucK0vESkWrRyTKuWQg5ouD550bQ7wrHOiHemFVqNunP0A4U1E4yZZPACz1/Py5kaA==", "requires": { "@junobuild/storage": "*", "@junobuild/utils": "*" } }, + "@junobuild/did-tools": { + "version": "0.0.2-next-2024-09-12.12", + "resolved": "https://registry.npmjs.org/@junobuild/did-tools/-/did-tools-0.0.2-next-2024-09-12.12.tgz", + "integrity": "sha512-hU/8wh8X8Asth+CuiE8hpdOXPYJRfELoXLkdWqSZ9YLt1xWm5VJYOe1gndFZScknQvlhjsVM1tGaOwbOnFv5cA==", + "requires": { + "@babel/core": "^7.24.6", + "@babel/plugin-transform-modules-commonjs": "^7.24.6", + "@babel/preset-typescript": "^7.24.6", + "@babel/traverse": "^7.24.6" + } + }, "@junobuild/storage": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@junobuild/storage/-/storage-0.0.6.tgz", diff --git a/package.json b/package.json index 15f23ac..c58633a 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "@junobuild/admin": "^0.0.56", "@junobuild/cli-tools": "^0.0.14", "@junobuild/config-loader": "^0.0.5", - "@junobuild/core-peer": "^0.0.26", + "@junobuild/core-peer": "^0.0.26-next-2024-09-12.13", + "@junobuild/did-tools": "^0.0.2-next-2024-09-12.12", "@junobuild/utils": "^0.0.25", "conf": "^13.0.1", "open": "^10.1.0", diff --git a/src/constants/dev.constants.ts b/src/constants/dev.constants.ts index 13bc6f0..e8837e1 100644 --- a/src/constants/dev.constants.ts +++ b/src/constants/dev.constants.ts @@ -1,6 +1,12 @@ import {join} from 'node:path'; -export const DEVELOPER_PROJECT_SATELLITE_PATH = join(process.cwd(), 'src', 'satellite'); +export const DEVELOPER_PROJECT_SRC_PATH = join(process.cwd(), 'src'); +export const DEVELOPER_PROJECT_SATELLITE_PATH = join(DEVELOPER_PROJECT_SRC_PATH, 'satellite'); +export const DEVELOPER_PROJECT_SATELLITE_DECLARATIONS_PATH = join( + DEVELOPER_PROJECT_SRC_PATH, + 'declarations', + 'satellite' +); export const TEMPLATE_PATH = '../templates/eject'; export const TEMPLATE_SATELLITE_PATH = join(TEMPLATE_PATH, 'src', 'satellite'); diff --git a/src/services/build.services.ts b/src/services/build.services.ts index 79a9cf2..a4c00eb 100644 --- a/src/services/build.services.ts +++ b/src/services/build.services.ts @@ -1,16 +1,18 @@ import {execute, gzipFile, spawn} from '@junobuild/cli-tools'; +import {generateApi} from '@junobuild/did-tools'; import {green, grey, magenta, yellow} from 'kleur'; import {existsSync} from 'node:fs'; -import {lstat, mkdir, rename, writeFile} from 'node:fs/promises'; +import {lstat, mkdir, readFile, rename, writeFile} from 'node:fs/promises'; import {join, relative} from 'node:path'; import ora, {type Ora} from 'ora'; -import {DEVELOPER_PROJECT_SATELLITE_PATH, IC_WASM_MIN_VERSION} from '../constants/dev.constants'; -import {copySatelliteDid, readSatelliteDid} from '../utils/did.utils'; +import {detectJunoDevConfigType} from '../configs/juno.dev.config'; import { - checkCandidExtractorInstalled, - checkIcWasmVersion, - checkRustVersion -} from '../utils/env.utils'; + DEVELOPER_PROJECT_SATELLITE_DECLARATIONS_PATH, + DEVELOPER_PROJECT_SATELLITE_PATH, + IC_WASM_MIN_VERSION +} from '../constants/dev.constants'; +import {copySatelliteDid, readSatelliteDid} from '../utils/did.utils'; +import {checkCargoBinInstalled, checkIcWasmVersion, checkRustVersion} from '../utils/env.utils'; import {confirmAndExit} from '../utils/prompt.utils'; const CARGO_RELEASE_DIR = join(process.cwd(), 'target', 'wasm32-unknown-unknown', 'release'); @@ -36,6 +38,12 @@ export const build = async () => { return; } + const {valid: validDidc} = await checkJunoDidc(); + + if (!validDidc) { + return; + } + const args = [ 'build', '--target', @@ -58,6 +66,8 @@ export const build = async () => { try { await did(); + await didc(); + await api(); await icWasm(); @@ -105,6 +115,72 @@ const did = async () => { ); }; +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 generate = async (type: 'js' | 'ts') => { + await spawn({ + command: 'junobuild-didc', + args: ['-i', SATELLITE_CUSTOM_DID_FILE, '-t', type, '-o', satellitedIdl(type)] + }); + }; + + const promises = (['js', 'ts'] as Array<'js' | 'ts'>).map(generate); + + await Promise.all(promises); +}; + +const api = async () => { + const inputFile = satellitedIdl('ts'); + + if (!existsSync(inputFile)) { + return; + } + + const detectedConfig = detectJunoDevConfigType(); + const outputLanguage = detectedConfig?.configType === 'ts' ? 'ts' : 'js'; + + const outputFile = `${DEVELOPER_PROJECT_SATELLITE_DECLARATIONS_PATH}/satellite.api.${outputLanguage}`; + + const readCoreLib = async (): Promise<'core' | 'core-peer'> => { + try { + const packageJson = await readFile(join(process.cwd(), 'package.json'), 'utf-8'); + const {dependencies} = JSON.parse(packageJson) as {dependencies?: Record}; + return Object.keys(dependencies ?? {}).includes('@junobuild/core-peer') + ? 'core-peer' + : 'core'; + } catch (err: unknown) { + // This should not block the developer therefore we fallback to core + return 'core'; + } + }; + + const coreLib = await readCoreLib(); + + await generateApi({ + inputFile, + outputFile, + transformerOptions: { + outputLanguage, + coreLib + } + }); +}; + const icWasm = async () => { await mkdir(DEPLOY_DIR, {recursive: true}); @@ -190,7 +266,7 @@ const checkIcWasm = async (): Promise<{valid: boolean}> => { await confirmAndExit( `The ${magenta('ic-wasm')} ${yellow( `v${IC_WASM_MIN_VERSION}` - )} tool is required to build a satellite but appears to be not available. Would you like to install it on your machine?` + )} tool is required to build a satellite but appears to be not available. Would you like to install it?` ); await execute({ @@ -203,7 +279,10 @@ const checkIcWasm = async (): Promise<{valid: boolean}> => { }; const checkCandidExtractor = async (): Promise<{valid: boolean}> => { - const {valid} = await checkCandidExtractorInstalled(); + const {valid} = await checkCargoBinInstalled({ + command: 'candid-extractor', + args: ['--version'] + }); if (valid === false) { return {valid}; @@ -213,12 +292,38 @@ const checkCandidExtractor = async (): Promise<{valid: boolean}> => { await confirmAndExit( `The ${magenta( 'candid-extractor' - )} tool is required to generate the API ("did file") of your custom satellite but appears to be not available. Would you like to install it on your machine?` + )} tool is required to generate the API ("did file"). Would you like to install it?` + ); + + await execute({ + command: 'cargo', + args: ['install', 'candid-extractor'] + }); + } + + return {valid: true}; +}; + +const checkJunoDidc = async (): Promise<{valid: boolean}> => { + const {valid} = await checkCargoBinInstalled({ + command: 'junobuild-didc', + args: ['--version'] + }); + + if (valid === false) { + return {valid}; + } + + if (valid === 'error') { + await confirmAndExit( + `It seems that ${magenta( + 'junobuild-didc' + )} is not installed. This is a useful tool for generating automatically JavaScript or TypeScript bindings. Would you like to install it?` ); await execute({ command: 'cargo', - args: ['install', `candid-extractor`] + args: ['install', `junobuild-didc`] }); } diff --git a/src/utils/env.utils.ts b/src/utils/env.utils.ts index d68244e..79cdc3e 100644 --- a/src/utils/env.utils.ts +++ b/src/utils/env.utils.ts @@ -119,11 +119,18 @@ export const assertDockerRunning = async () => { } }; -export const checkCandidExtractorInstalled = async (): Promise<{valid: boolean | 'error'}> => { +export const checkCargoBinInstalled = async ({ + command, + args +}: { + command: string; + args?: readonly string[]; +}): Promise<{valid: boolean | 'error'}> => { try { await spawn({ - command: 'candid-extractor', - silentErrors: true + command, + args, + silentOut: true }); return {valid: true};