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
5 changes: 4 additions & 1 deletion src/cli/env.loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ export const loadEnv = (): JunoCliEnv => {
const envContainerUrl =
containerUrl ?? (mode === 'development' ? 'http://127.0.0.1:5987' : undefined);

const ci = process.env.CI === 'true';

return {
mode: mode ?? 'production',
containerUrl: envContainerUrl,
console: loadEnvConsole({args, mode}),
config: loadEnvConfig({mode})
config: loadEnvConfig({mode}),
ci
};
};

Expand Down
6 changes: 6 additions & 0 deletions src/constants/dev.constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import {join} from 'node:path';

export const SATELLITE_PROJECT_NAME = 'satellite';
export const SPUTNIK_PROJECT_NAME = 'sputnik';

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(
Expand Down Expand Up @@ -46,4 +49,7 @@ export const PACKAGE_JSON_PATH = join(process.cwd(), 'package.json');
export const SPUTNIK_INDEX_MJS = 'sputnik.index.mjs';
export const DEPLOY_SPUTNIK_PATH = join(DEPLOY_LOCAL_REPLICA_PATH, SPUTNIK_INDEX_MJS);

export const JUNO_ACTION_SPUTNIK_PATH = '/juno/src/sputnik';
export const SPUTNIK_CARGO_TOML = join(JUNO_ACTION_SPUTNIK_PATH, CARGO_TOML);

export const SATELLITE_OUTPUT = join(DEPLOY_LOCAL_REPLICA_PATH, 'satellite.wasm');
6 changes: 5 additions & 1 deletion src/constants/help.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export const FUNCTIONS_EJECT_DESCRIPTION =

export const FUNCTIONS_BUILD_NOTES = `- If no language is provided, the CLI attempts to determine the appropriate build.
- Language can be shortened to ${magenta('rs')} for Rust, ${magenta('ts')} for TypeScript and ${magenta('mjs')} for JavaScript.
- The path option maps to ${magenta('--manifest-path')} for Rust (Cargo) or to the source file for TypeScript and JavaScript (e.g. ${magenta('index.ts')} or ${magenta('index.mjs')}).
- Use ${magenta('--cargo-path')} to specify a specific crate path. For Rust builds, this maps to ${magenta('--manifest-path')} for ${magenta('cargo build')}. For TypeScript and JavaScript, it points to the Rust crate (commonly "Sputnik") that imports the functions.
- An optional ${magenta('--source-path')} to specify the source file for TypeScript and JavaScript (e.g. ${magenta('index.ts')} or ${magenta('index.mjs')}).
- The watch option rebuilds when source files change, with a default debounce delay of 10 seconds; optionally, pass a delay in milliseconds.`;

export const CHANGES_LIST_DESCRIPTION = 'List all submitted or applied changes.';
Expand All @@ -50,5 +51,8 @@ export const OPTIONS_UPGRADE = `${yellow('--clear-chunks')} Clear any pre
export const OPTIONS_URL = `${yellow('-m, --mode')} Set env mode. For example production or a custom string. Default is production.
${yellow('--container-url')} Override a custom container URL. If not provided, defaults to production or the local container in development mode.
${yellow('--console-url')} Specify a custom URL to access the developer Console.`;
export const OPTIONS_BUILD = `${yellow('-l, --lang')} Specify the language for building the serverless functions: ${magenta('rust')}, ${magenta('typescript')} or ${magenta('javascript')}.
${yellow('--cargo-path')} Path to the Rust manifest.
${yellow('--source-path')} Optional path to the TypeScript or JavaScript entry file.`;

export const NOTE_KEEP_STAGED = `The option ${yellow('--keep-staged')} only applies when ${yellow('--no-apply')} is NOT used (i.e. the change is applied immediately).`;
6 changes: 3 additions & 3 deletions src/help/dev.start.help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import {cyan, green, magenta, yellow} from 'kleur';
import {
DEV_START_DESCRIPTION,
FUNCTIONS_BUILD_NOTES,
OPTION_HELP
OPTION_HELP,
OPTIONS_BUILD
} from '../constants/help.constants';
import {helpOutput} from './common.help';
import {TITLE} from './help';

const usage = `Usage: ${green('juno')} ${cyan('dev')} ${magenta('start')} ${yellow('[options]')}

Options:
${yellow('-l, --lang')} Language used when watching for file changes: ${magenta('rust')}, ${magenta('typescript')} or ${magenta('javascript')}.
${yellow('-p, --path')} Path to the source file or manifest used when watching.
${OPTIONS_BUILD}
${yellow('-w, --watch')} Rebuild your functions automatically when source files change.
${OPTION_HELP}

Expand Down
6 changes: 3 additions & 3 deletions src/help/functions.build.help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import {cyan, green, magenta, yellow} from 'kleur';
import {
FUNCTIONS_BUILD_DESCRIPTION,
FUNCTIONS_BUILD_NOTES,
OPTION_HELP
OPTION_HELP,
OPTIONS_BUILD
} from '../constants/help.constants';
import {helpOutput} from './common.help';
import {TITLE} from './help';

const usage = `Usage: ${green('juno')} ${cyan('functions')} ${magenta('build')} ${yellow('[options]')}

Options:
${yellow('-l, --lang')} Specify the language for building the serverless functions: ${magenta('rust')}, ${magenta('typescript')} or ${magenta('javascript')}.
${yellow('-p, --path')} Path to the source to bundle.
${OPTIONS_BUILD}
${yellow('-w, --watch')} Rebuild your functions automatically when source files change.
${OPTION_HELP}

Expand Down
61 changes: 15 additions & 46 deletions src/services/functions/build/build.javascript.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,45 @@
import {isEmptyString, notEmptyString} from '@dfinity/utils';
import {buildEsm, execute, formatBytes, type PackageJson} from '@junobuild/cli-tools';
import {notEmptyString} from '@dfinity/utils';
import {buildEsm, execute, formatBytes} from '@junobuild/cli-tools';
import type {Metafile} from 'esbuild';
import {green, magenta, red, yellow} from 'kleur';
import {existsSync} from 'node:fs';
import {mkdir} from 'node:fs/promises';
import {join} from 'node:path';
import {
DEPLOY_LOCAL_REPLICA_PATH,
DEPLOY_SPUTNIK_PATH,
DEVELOPER_PROJECT_SATELLITE_PATH,
INDEX_MJS,
INDEX_TS,
PACKAGE_JSON_PATH
INDEX_TS
} from '../../../constants/dev.constants';
import type {BuildArgs, BuildLang} from '../../../types/build';
import type {BuildArgs, BuildLang, BuildMetadata} from '../../../types/build';
import {formatTime} from '../../../utils/format.utils';
import {readPackageJson} from '../../../utils/pkg.utils';
import {detectPackageManager} from '../../../utils/pm.utils';
import {confirmAndExit} from '../../../utils/prompt.utils';
import {prepareJavaScriptBuildMetadata} from './build.metadata.services';

export const buildTypeScript = async ({
path,
paths,
exitOnError
}: Pick<BuildArgs, 'path' | 'exitOnError'> = {}) => {
await build({lang: 'ts', path, exitOnError});
}: Pick<BuildArgs, 'paths' | 'exitOnError'> = {}) => {
await build({lang: 'ts', paths, exitOnError});
};

export const buildJavaScript = async ({
path,
paths,
exitOnError
}: Pick<BuildArgs, 'path' | 'exitOnError'> = {}) => {
await build({lang: 'mjs', path, exitOnError});
}: Pick<BuildArgs, 'paths' | 'exitOnError'> = {}) => {
await build({lang: 'mjs', paths, exitOnError});
};

type BuildArgsTsJs = {lang: Omit<BuildLang, 'rs'>} & Pick<BuildArgs, 'path' | 'exitOnError'>;
type BuildArgsTsJs = {lang: Omit<BuildLang, 'rs'>} & Pick<BuildArgs, 'paths' | 'exitOnError'>;

const build = async ({exitOnError, ...params}: BuildArgsTsJs) => {
await installEsbuild();

await createTargetDir();

try {
const metadata = await prepareMetadata();
const metadata = await prepareJavaScriptBuildMetadata();

const buildResult = await buildWithEsbuild({params, metadata});

Expand All @@ -59,14 +57,14 @@ interface BuildResult {
}

const buildWithEsbuild = async ({
params: {lang, path},
params: {lang, paths},
metadata
}: {
params: Omit<BuildArgsTsJs, 'exitOnError'>;
metadata: BuildMetadata;
}): Promise<BuildResult> => {
const infile =
path ?? join(DEVELOPER_PROJECT_SATELLITE_PATH, lang === 'mjs' ? INDEX_MJS : INDEX_TS);
paths?.source ?? join(DEVELOPER_PROJECT_SATELLITE_PATH, lang === 'mjs' ? INDEX_MJS : INDEX_TS);

// We pass the package information as metadata so the Docker container can read it and embed it into the `juno:package` custom section of the WASM’s public metadata.
const banner = {
Expand Down Expand Up @@ -137,35 +135,6 @@ const hasEsbuild = async (): Promise<boolean> => {
}
};

type BuildMetadata = Omit<PackageJson, 'dependencies'> | undefined;

const prepareMetadata = async (): Promise<BuildMetadata> => {
if (!existsSync(PACKAGE_JSON_PATH)) {
// No package.json therefore no metadata to pass to the build in the container.
return undefined;
}

try {
const {juno, version, name} = await readPackageJson();

if (isEmptyString(juno?.functions?.version) && isEmptyString(version)) {
// No version detected therefore no metadata to the build in the container.
return undefined;
}

const functionsVersion = juno?.functions?.version;

return {
...(notEmptyString(name) && {name}),
...(notEmptyString(version) && {version}),
...(notEmptyString(functionsVersion) && {juno})
};
} catch (err: unknown) {
console.log(red('⚠️ Could not read build metadata from package.json.'));
throw err;
}
};

const printResults = ({
metadata,
buildResult
Expand Down
113 changes: 113 additions & 0 deletions src/services/functions/build/build.metadata.services.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import {isEmptyString, isNullish, notEmptyString} from '@dfinity/utils';
import type {JunoPackage} from '@junobuild/config';
import {red} from 'kleur';
import {existsSync} from 'node:fs';
import {writeFile} from 'node:fs/promises';
import {
JUNO_PACKAGE_JSON_PATH,
PACKAGE_JSON_PATH,
SATELLITE_PROJECT_NAME
} from '../../../constants/dev.constants';
import {type BuildMetadata, type BuildType} from '../../../types/build';
import {readPackageJson} from '../../../utils/pkg.utils';

export const prepareJunoPkgForSatellite = async ({buildType}: {buildType: BuildType}) => {
// We do not write a juno.package.json for legacy build
if (buildType.build === 'legacy') {
return;
}

const {version, satelliteVersion} = buildType;

const pkg: JunoPackage = {
version,
name: SATELLITE_PROJECT_NAME,
dependencies: {
'@junobuild/satellite': satelliteVersion
}
};

await writeFile(JUNO_PACKAGE_JSON_PATH, JSON.stringify(pkg, null, 2), 'utf-8');
};

export const prepareJunoPkgForSputnik = async ({
buildType
}: {
buildType: BuildType;
}): Promise<{success: 'ok' | 'skip'} | {error: string}> => {
// We do not write a juno.package.json for legacy build
if (buildType.build === 'legacy') {
return {success: 'skip'};
}

const metadata = await prepareJavaScriptDevMetadata();

if ('error' in metadata) {
return {error: metadata.error};
}

const {satelliteVersion, sputnikVersion} = buildType;

if (isNullish(sputnikVersion) || isEmptyString(sputnikVersion)) {
return {error: `⚠️ Cannot resolve the Sputnik "version" in Cargo metadata. Aborting build!`};
}

const pkg: JunoPackage = {
...metadata,
dependencies: {
'@junobuild/satellite': satelliteVersion,
'@junobuild/sputnik': sputnikVersion
}
};

await writeFile(JUNO_PACKAGE_JSON_PATH, JSON.stringify(pkg, null, 2), 'utf-8');

return {success: 'ok'};
};

export const prepareJavaScriptDevMetadata = async (): Promise<JunoPackage | {error: string}> => {
const metadata = await prepareJavaScriptBuildMetadata();

const pkgName = metadata?.juno?.functions?.name ?? metadata?.name;
const pkgVersion = metadata?.juno?.functions?.version ?? metadata?.version;

if (isNullish(pkgName) || isEmptyString(pkgName)) {
return {error: `⚠️ Missing "name" in package metadata. Aborting build!`};
}

if (isNullish(pkgVersion) || isEmptyString(pkgVersion)) {
return {error: `⚠️ Missing "version" in package metadata. Aborting build!`};
}

return {
name: pkgName,
version: pkgVersion
};
};

export const prepareJavaScriptBuildMetadata = async (): Promise<BuildMetadata> => {
if (!existsSync(PACKAGE_JSON_PATH)) {
// No package.json therefore no metadata to pass to the build in the container.
return undefined;
}

try {
const {juno, version, name} = await readPackageJson();

if (isEmptyString(juno?.functions?.version) && isEmptyString(version)) {
// No version detected therefore no metadata to the build in the container.
return undefined;
}

const functionsVersion = juno?.functions?.version;

return {
...(notEmptyString(name) && {name}),
...(notEmptyString(version) && {version}),
...(notEmptyString(functionsVersion) && {juno})
};
} catch (err: unknown) {
console.log(red('⚠️ Could not read build metadata from package.json.'));
throw err;
}
};
Loading
Loading