Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: generate JavaScript and TypeScript bindings #138

Merged
merged 7 commits into from
Sep 13, 2024
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
49 changes: 38 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 7 additions & 1 deletion src/constants/dev.constants.ts
Original file line number Diff line number Diff line change
@@ -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');
Expand Down
127 changes: 116 additions & 11 deletions src/services/build.services.ts
Original file line number Diff line number Diff line change
@@ -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');
Expand All @@ -36,6 +38,12 @@ export const build = async () => {
return;
}

const {valid: validDidc} = await checkJunoDidc();

if (!validDidc) {
return;
}

const args = [
'build',
'--target',
Expand All @@ -58,6 +66,8 @@ export const build = async () => {

try {
await did();
await didc();
await api();

await icWasm();

Expand Down Expand Up @@ -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<string, string>};
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});

Expand Down Expand Up @@ -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({
Expand All @@ -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};
Expand All @@ -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`]
});
}

Expand Down
13 changes: 10 additions & 3 deletions src/utils/env.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
Loading