diff --git a/package-lock.json b/package-lock.json index aebb520c..89dd645a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@dfinity/identity": "^2.3.0", "@dfinity/principal": "^2.3.0", "@junobuild/admin": "^0.1.6", - "@junobuild/cli-tools": "^0.1.6", + "@junobuild/cli-tools": "^0.1.7", "@junobuild/config-loader": "^0.2.0", "@junobuild/core": "^0.1.9", "@junobuild/did-tools": "^0.2.0", @@ -1475,9 +1475,9 @@ } }, "node_modules/@junobuild/cli-tools": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@junobuild/cli-tools/-/cli-tools-0.1.6.tgz", - "integrity": "sha512-WN769rzl9effZ39Tmegi8F0qDOU04Ks1QXMG8Yh0D3wquynwHOVSDQjrRYCZeYIfwCnkkFPCpYaOmaUGSv9l5A==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@junobuild/cli-tools/-/cli-tools-0.1.7.tgz", + "integrity": "sha512-URLlilOJ8f/cBsjRKTW80iaku316QAyxLXvjiCwNIsBo2Nc1z963qwFD2xy1zrq0cyqks4gTAy5L3E1+3QTxSQ==", "license": "MIT", "dependencies": { "file-type": "^20.4.1", @@ -7910,9 +7910,9 @@ "requires": {} }, "@junobuild/cli-tools": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@junobuild/cli-tools/-/cli-tools-0.1.6.tgz", - "integrity": "sha512-WN769rzl9effZ39Tmegi8F0qDOU04Ks1QXMG8Yh0D3wquynwHOVSDQjrRYCZeYIfwCnkkFPCpYaOmaUGSv9l5A==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@junobuild/cli-tools/-/cli-tools-0.1.7.tgz", + "integrity": "sha512-URLlilOJ8f/cBsjRKTW80iaku316QAyxLXvjiCwNIsBo2Nc1z963qwFD2xy1zrq0cyqks4gTAy5L3E1+3QTxSQ==", "requires": { "file-type": "^20.4.1", "listr": "^0.14.3", diff --git a/package.json b/package.json index 9aa58560..e50102f2 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@dfinity/identity": "^2.3.0", "@dfinity/principal": "^2.3.0", "@junobuild/admin": "^0.1.6", - "@junobuild/cli-tools": "^0.1.6", + "@junobuild/cli-tools": "^0.1.7", "@junobuild/config-loader": "^0.2.0", "@junobuild/core": "^0.1.9", "@junobuild/did-tools": "^0.2.0", diff --git a/src/constants/dev.constants.ts b/src/constants/dev.constants.ts index d208b4fe..034f442a 100644 --- a/src/constants/dev.constants.ts +++ b/src/constants/dev.constants.ts @@ -38,6 +38,10 @@ export const IC_WASM_MIN_VERSION = '0.8.5'; export const DOCKER_MIN_VERSION = '24.0.0'; export const DEPLOY_LOCAL_REPLICA_PATH = join(process.cwd(), 'target', 'deploy'); +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 SPUTNIK_PACKAGE_JSON = 'sputnik.package.json'; +export const PACKAGE_JSON_SPUTNIK_PATH = join(DEPLOY_LOCAL_REPLICA_PATH, SPUTNIK_PACKAGE_JSON); diff --git a/src/services/build/build.javascript.ts b/src/services/build/build.javascript.ts index a510f644..d8878270 100644 --- a/src/services/build/build.javascript.ts +++ b/src/services/build/build.javascript.ts @@ -1,16 +1,22 @@ -import {buildEsm, execute} from '@junobuild/cli-tools'; +import {isEmptyString, isNullish, notEmptyString} from '@dfinity/utils'; +import {buildEsm, execute, type PackageJson} from '@junobuild/cli-tools'; +import type {Metafile} from 'esbuild'; import {green, magenta, red, yellow} from 'kleur'; -import {mkdir} from 'node:fs/promises'; +import {existsSync} from 'node:fs'; +import {mkdir, writeFile} 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 + INDEX_TS, + PACKAGE_JSON_PATH, + PACKAGE_JSON_SPUTNIK_PATH } from '../../constants/dev.constants'; import type {BuildArgs, BuildLang} from '../../types/build'; import {formatBytes, formatTime} from '../../utils/format.utils'; +import {readPackageJson} from '../../utils/pkg.utils'; import {detectPackageManager} from '../../utils/pm.utils'; import {confirmAndExit} from '../../utils/prompt.utils'; @@ -29,10 +35,21 @@ const build = async (params: BuildArgsTsJs) => { await createTargetDir(); - await buildWithEsbuild(params); + const metadata = await prepareMetadata(); + + await copyMetadata(metadata); + + const buildResult = await buildWithEsbuild(params); + + printResults({metadata, buildResult}); }; -const buildWithEsbuild = async ({lang, path}: BuildArgsTsJs) => { +interface BuildResult { + version: string; + output: [string, Metafile['outputs'][0]]; +} + +const buildWithEsbuild = async ({lang, path}: BuildArgsTsJs): Promise => { const infile = path ?? join(DEVELOPER_PROJECT_SATELLITE_PATH, lang === 'mjs' ? INDEX_MJS : INDEX_TS); @@ -60,10 +77,10 @@ const buildWithEsbuild = async ({lang, path}: BuildArgsTsJs) => { process.exit(1); } - const [key, {bytes}] = entry[0]; - - console.log(`${green('✔')} Build complete at ${formatTime()} (esbuild ${version})`); - console.log(`→ ${yellow(key)} (${formatBytes(bytes)})`); + return { + output: entry[0], + version + }; }; const createTargetDir = async () => { @@ -98,3 +115,68 @@ const hasEsbuild = async (): Promise => { return false; } }; + +type BuildMetadata = Omit | undefined; + +const prepareMetadata = async (): Promise => { + 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} = 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(version) && {version}), + ...(notEmptyString(functionsVersion) && {juno}) + }; + } catch (_err: unknown) { + // We want to continue the build process even if copying package.json fails, + // since it's only used to set the extended custom version. + console.log('⚠️ Could not read build metadata from package.json.'); + return undefined; + } +}; + +const copyMetadata = async (metadata: BuildMetadata): Promise => { + if (isNullish(metadata)) { + // No metadata to pass to the build in the container. + return; + } + + try { + await writeFile(PACKAGE_JSON_SPUTNIK_PATH, JSON.stringify(metadata, null, 2), 'utf-8'); + } catch (_err: unknown) { + // We want to continue the build process even if copying package.json fails, + // since it's only used to set the extended custom version. + console.log('⚠️ Could not copy package.json for the build.'); + } +}; + +const printResults = ({ + metadata, + buildResult +}: { + metadata: BuildMetadata; + buildResult: BuildResult; +}) => { + const {output, version: esbuildVersion} = buildResult; + const [key, {bytes}] = output; + + // The version defined by the developer for their serverless functions - not the version of the Satellite provided by Juno. + const extendedVersion = metadata?.juno?.functions?.version ?? metadata?.version; + const version = notEmptyString(extendedVersion) ? `version ${extendedVersion}, ` : ' '; + + console.log( + `${green('✔')} Build complete at ${formatTime()} (${version}esbuild ${esbuildVersion})` + ); + console.log(`→ ${yellow(key)} (${formatBytes(bytes)})`); +}; diff --git a/src/utils/pkg.utils.ts b/src/utils/pkg.utils.ts index 0bb8686a..01b4e0ed 100644 --- a/src/utils/pkg.utils.ts +++ b/src/utils/pkg.utils.ts @@ -1,17 +1,5 @@ -import {readFile} from 'node:fs/promises'; -import {join} from 'node:path'; +import {type PackageJson, readPackageJson as readPackageJsonUtils} from '@junobuild/cli-tools'; +import {PACKAGE_JSON_PATH} from '../constants/dev.constants'; -export interface PackageJson { - dependencies?: Record; -} - -export const readPackageJson = async (): Promise => { - const packageJson = await readFile(join(process.cwd(), 'package.json'), 'utf-8'); - - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - const {dependencies} = JSON.parse(packageJson) as {dependencies?: Record}; - - return { - dependencies - }; -}; +export const readPackageJson = async (): Promise => + await readPackageJsonUtils({packageJsonPath: PACKAGE_JSON_PATH});