Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/constants/build.constants.ts
Original file line number Diff line number Diff line change
@@ -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.`;
99 changes: 99 additions & 0 deletions src/services/functions/build/build.did.services.ts
Original file line number Diff line number Diff line change
@@ -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
}
});
};
111 changes: 10 additions & 101 deletions src/services/functions/build/build.rust.services.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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';

Expand Down Expand Up @@ -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...';
Expand All @@ -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({
Expand Down Expand Up @@ -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<BuildArgs, 'paths'> = {}): Promise<
BuildType | {error: string}
> => {
Expand Down