diff --git a/package-lock.json b/package-lock.json index 0660afe2..211df956 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@junobuild/admin": "^0.5.2", "@junobuild/cdn": "^0.1.2", "@junobuild/cli-tools": "^0.2.3", - "@junobuild/config": "^0.3.0", + "@junobuild/config": "^0.3.1", "@junobuild/config-loader": "^0.2.1", "@junobuild/core": "^0.1.16", "@junobuild/did-tools": "^0.2.1", @@ -1547,9 +1547,9 @@ } }, "node_modules/@junobuild/config": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@junobuild/config/-/config-0.3.0.tgz", - "integrity": "sha512-ZbYS88Pj0PiKdRF2c5bVpc/C3WJv2HfHaDW0aoCxp7wF11ZR+d/dDL+p7+BNMAwN+a0GQfOm3yV0awJUh/4BHQ==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@junobuild/config/-/config-0.3.1.tgz", + "integrity": "sha512-GtqmV3/96A2bK0SZVGsTzKRLjQ0dXcnM7JyVlNPDc34hV/y3o85HfuykW2E3BDcyLCesAoYr3dk/TqPv2Ykvgw==", "license": "MIT", "peerDependencies": { "@dfinity/zod-schemas": "^0.0.2", @@ -8062,9 +8062,9 @@ } }, "@junobuild/config": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@junobuild/config/-/config-0.3.0.tgz", - "integrity": "sha512-ZbYS88Pj0PiKdRF2c5bVpc/C3WJv2HfHaDW0aoCxp7wF11ZR+d/dDL+p7+BNMAwN+a0GQfOm3yV0awJUh/4BHQ==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@junobuild/config/-/config-0.3.1.tgz", + "integrity": "sha512-GtqmV3/96A2bK0SZVGsTzKRLjQ0dXcnM7JyVlNPDc34hV/y3o85HfuykW2E3BDcyLCesAoYr3dk/TqPv2Ykvgw==", "requires": {} }, "@junobuild/config-loader": { diff --git a/package.json b/package.json index 77e40ed8..7e1d1426 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "@junobuild/admin": "^0.5.2", "@junobuild/cdn": "^0.1.2", "@junobuild/cli-tools": "^0.2.3", - "@junobuild/config": "^0.3.0", + "@junobuild/config": "^0.3.1", "@junobuild/config-loader": "^0.2.1", "@junobuild/core": "^0.1.16", "@junobuild/did-tools": "^0.2.1", diff --git a/src/services/dev/start/docker.services.ts b/src/services/dev/start/docker.services.ts index c7532428..62feee6a 100644 --- a/src/services/dev/start/docker.services.ts +++ b/src/services/dev/start/docker.services.ts @@ -3,6 +3,7 @@ import {assertAnswerCtrlC, execute, spawn} from '@junobuild/cli-tools'; import {type EmulatorConfig, EmulatorConfigSchema, type EmulatorPorts} from '@junobuild/config'; import type {PartialConfigFile} from '@junobuild/config-loader'; import {red, yellow} from 'kleur'; +import {mkdir} from 'node:fs/promises'; import {basename, join} from 'node:path'; import prompts from 'prompts'; import { @@ -25,45 +26,62 @@ import { EMULATOR_SKYLAB } from '../../../constants/emulator.constants'; import {ENV} from '../../../env'; -import { - assertDockerRunning, - checkDockerVersion, - hasExistingDockerContainer, - isDockerContainerRunning -} from '../../../utils/env.utils'; +import {type ContainerRunner} from '../../../types/runner'; import {copyTemplateFile} from '../../../utils/fs.utils'; import {readPackageJson} from '../../../utils/pkg.utils'; import {isHeadless} from '../../../utils/process.utils'; import {confirmAndExit} from '../../../utils/prompt.utils'; +import { + assertContainerRunnerRunning, + checkDockerVersion, + hasExistingContainer, + isContainerRunning +} from '../../../utils/runner.utils'; import {initConfigNoneInteractive, promptConfigType} from '../../init.services'; const TEMPLATE_PATH = '../templates/docker'; const DESTINATION_PATH = process.cwd(); export const startContainer = async () => { - const {valid} = await checkDockerVersion(); + const parsedResult = await readEmulatorConfig(); + + if (!parsedResult.success) { + return; + } + + const {config} = parsedResult; + + const {valid} = config.runner === 'docker' ? await checkDockerVersion() : {valid: true}; if (valid === 'error' || !valid) { return; } - await assertDockerRunning(); + await assertContainerRunnerRunning({runner: config.runner}); await assertAndInitConfig(); - await startEmulator(); + await startEmulator({config}); }; export const stop = async () => { - const {valid} = await checkDockerVersion(); + const parsedResult = await readEmulatorConfig(); + + if (!parsedResult.success) { + return; + } + + const {config} = parsedResult; + + const {valid} = config.runner === 'docker' ? await checkDockerVersion() : {valid: true}; if (valid === 'error' || !valid) { return; } - await assertDockerRunning(); + await assertContainerRunnerRunning({runner: config.runner}); - await stopEmulator(); + await stopEmulator({config}); }; const initJunoDevConfigFile = async () => { @@ -150,26 +168,20 @@ const initConfigFile = async (skylab: boolean) => { await initJunoDevConfigFile(); }; -const startEmulator = async () => { - const parsedResult = await parseEmulatorConfig(); +const startEmulator = async ({config: extendedConfig}: {config: ExtendedEmulatorConfig}) => { + const {containerName, runner, config, emulatorType} = extendedConfig; - if (!parsedResult.success) { - return; - } - - const {containerName, config, emulatorType} = parsedResult; - - const {running} = await assertDockerContainerRunning({containerName}); + const {running} = await assertContainerRunning({containerName, runner}); if (running) { - console.log(yellow(`The Docker container ${containerName} is already running.`)); + console.log(yellow(`The ${runner} container ${containerName} is already running.`)); return; } - const status = await hasExistingDockerContainer({containerName}); + const status = await hasExistingContainer({containerName, runner}); if ('err' in status) { - console.log(red(`Unable to check if Docker container ${containerName} already exists.`)); + console.log(red(`Unable to check if ${runner} container ${containerName} already exists.`)); return; } @@ -180,7 +192,7 @@ const startEmulator = async () => { // -a: Attach STDOUT/STDERR. Equivalent to `--attach`. // -i: Keep STDIN open even if not attached. Equivalent to `--interactive`. await execute({ - command: 'docker', + command: runner, args: ['start', '-a', ...(isHeadless() ? [] : ['-i']), containerName] }); return; @@ -220,12 +232,16 @@ const startEmulator = async () => { const targetDeploy = config.runner?.target ?? join(process.cwd(), 'target', 'deploy'); + // TODO: add support for runner config in build functions + // Podman does not auto create the path folders. + await mkdir(targetDeploy, {recursive: true}); + const image = config.runner?.image ?? `junobuild/${emulatorType}:latest`; const platform = config.runner?.platform; await execute({ - command: 'docker', + command: runner, args: [ 'run', ...(isHeadless() ? [] : ['-it']), @@ -254,39 +270,36 @@ const startEmulator = async () => { }); }; -const stopEmulator = async () => { - const parsedResult = await parseEmulatorConfig(); +const stopEmulator = async ({config: extendedConfig}: {config: ExtendedEmulatorConfig}) => { + const {containerName, runner} = extendedConfig; - if (!parsedResult.success) { - return; - } - - const {containerName} = parsedResult; - - const {running} = await assertDockerContainerRunning({containerName}); + const {running} = await assertContainerRunning({containerName, runner}); if (!running) { - console.log(yellow(`The Docker container ${containerName} is already stopped.`)); + console.log(yellow(`The ${runner} container ${containerName} is already stopped.`)); return; } await spawn({ - command: 'docker', + command: runner, args: ['stop', containerName], silentOut: true }); }; -const parseEmulatorConfig = async (): Promise< +interface ExtendedEmulatorConfig extends ContainerRunner { + config: EmulatorConfig; + emulatorType: 'skylab' | 'satellite' | 'console'; +} + +const readEmulatorConfig = async (): Promise< | { success: true; - config: EmulatorConfig; - containerName: string; - emulatorType: 'skylab' | 'satellite' | 'console'; + config: ExtendedEmulatorConfig; } | {success: false} > => { - const normalizeDockerName = (pkgName: string): string => + const normalizeContainerName = (pkgName: string): string => pkgName .replace(/^@[^/]+\//, '') .replace(/[^a-zA-Z0-9_.-]/g, '-') @@ -332,28 +345,32 @@ const parseEmulatorConfig = async (): Promise< const emulatorType = 'satellite' in config ? 'satellite' : 'console' in config ? 'console' : 'skylab'; - const containerName = normalizeDockerName( + const containerName = normalizeContainerName( config.runner?.name ?? (await readProjectName()) ?? `juno-${emulatorType}` ); + const runner = config.runner?.type ?? 'docker'; + return { success: true, - config, - containerName, - emulatorType + config: { + config, + containerName, + emulatorType, + runner + } }; }; -const assertDockerContainerRunning = async ({ - containerName -}: { - containerName: string; -}): Promise<{running: boolean}> => { - const result = await isDockerContainerRunning({containerName}); +const assertContainerRunning = async ({ + containerName, + runner +}: ContainerRunner): Promise<{running: boolean}> => { + const result = await isContainerRunning({containerName, runner}); if ('err' in result) { console.log( - red(`Unable to verify if container ${containerName} is running. Is Docker installed?`) + red(`Unable to verify if container ${containerName} is running. Is ${runner} installed?`) ); process.exit(1); } diff --git a/src/types/runner.ts b/src/types/runner.ts new file mode 100644 index 00000000..4e2ce993 --- /dev/null +++ b/src/types/runner.ts @@ -0,0 +1,6 @@ +import type {EmulatorRunner} from '@junobuild/config'; + +export interface ContainerRunner { + containerName: string; + runner: EmulatorRunner['type']; +} diff --git a/src/utils/env.utils.ts b/src/utils/env.utils.ts index 4a048c39..396ff1c1 100644 --- a/src/utils/env.utils.ts +++ b/src/utils/env.utils.ts @@ -1,12 +1,8 @@ import {spawn} from '@junobuild/cli-tools'; -import {green, red, yellow} from 'kleur'; +import {green, yellow} from 'kleur'; import {lt, major} from 'semver'; import {NODE_VERSION} from '../constants/constants'; -import { - DOCKER_MIN_VERSION, - IC_WASM_MIN_VERSION, - RUST_MIN_VERSION -} from '../constants/dev.constants'; +import {IC_WASM_MIN_VERSION, RUST_MIN_VERSION} from '../constants/dev.constants'; export const checkNodeVersion = (): {valid: boolean | 'error'} => { try { @@ -81,86 +77,6 @@ export const checkIcWasmVersion = async (): Promise<{valid: boolean | 'error'}> return {valid: true}; }; -export const checkDockerVersion = async (): Promise<{valid: boolean | 'error'}> => { - try { - let output = ''; - await spawn({ - command: 'docker', - args: ['--version'], - stdout: (o) => (output += o) - }); - - const version = output.replaceAll(',', '').trim().split(' ')[2]; - - if (lt(version, DOCKER_MIN_VERSION)) { - console.log( - `Your version of Docker is ${yellow(version.trim())}. Juno CLI requires ${green( - DOCKER_MIN_VERSION - )} or a more recent version.` - ); - return {valid: false}; - } - } catch (_e: unknown) { - console.log(`${red('Cannot detect Docker version.')} Is Docker installed on your machine?`); - return {valid: 'error'}; - } - - return {valid: true}; -}; - -export const assertDockerRunning = async () => { - try { - await spawn({ - command: 'docker', - args: ['ps', '--quiet'], - silentOut: true - }); - } catch (_e: unknown) { - console.log(red('Docker does not appear to be running.')); - process.exit(1); - } -}; - -export const isDockerContainerRunning = async ({ - containerName -}: { - containerName: string; -}): Promise<{running: boolean} | {err: unknown}> => { - try { - let output = ''; - await spawn({ - command: 'docker', - args: ['ps', '--quiet', '-f', `name=^/${containerName}$`], - stdout: (o) => (output += o), - silentOut: true - }); - - return {running: output.trim().length > 0}; - } catch (err: unknown) { - return {err}; - } -}; - -export const hasExistingDockerContainer = async ({ - containerName -}: { - containerName: string; -}): Promise<{exist: boolean} | {err: unknown}> => { - try { - let output = ''; - await spawn({ - command: 'docker', - args: ['ps', '-aq', '-f', `name=^/${containerName}$`], - stdout: (o) => (output += o), - silentOut: true - }); - - return {exist: output.trim().length > 0}; - } catch (err: unknown) { - return {err}; - } -}; - export const checkCargoBinInstalled = async ({ command, args diff --git a/src/utils/runner.utils.ts b/src/utils/runner.utils.ts new file mode 100644 index 00000000..c338c6d0 --- /dev/null +++ b/src/utils/runner.utils.ts @@ -0,0 +1,83 @@ +import {spawn} from '@junobuild/cli-tools'; +import {green, red, yellow} from 'kleur'; +import {lt} from 'semver'; +import {DOCKER_MIN_VERSION} from '../constants/dev.constants'; +import {type ContainerRunner} from '../types/runner'; + +export const checkDockerVersion = async (): Promise<{valid: boolean | 'error'}> => { + try { + let output = ''; + await spawn({ + command: 'docker', + args: ['--version'], + stdout: (o) => (output += o) + }); + + const version = output.replaceAll(',', '').trim().split(' ')[2]; + + if (lt(version, DOCKER_MIN_VERSION)) { + console.log( + `Your version of Docker is ${yellow(version.trim())}. Juno CLI requires ${green( + DOCKER_MIN_VERSION + )} or a more recent version.` + ); + return {valid: false}; + } + } catch (_e: unknown) { + console.log(`${red('Cannot detect Docker version.')} Is Docker installed on your machine?`); + return {valid: 'error'}; + } + + return {valid: true}; +}; + +export const assertContainerRunnerRunning = async ({runner}: Pick) => { + try { + await spawn({ + command: runner, + args: ['ps', '--quiet'], + silentOut: true + }); + } catch (_e: unknown) { + console.log(red(`It looks like ${runner} does not appear to be running.`)); + process.exit(1); + } +}; + +export const hasExistingContainer = async ({ + containerName, + runner +}: ContainerRunner): Promise<{exist: boolean} | {err: unknown}> => { + try { + let output = ''; + await spawn({ + command: runner, + args: ['ps', '-aq', '-f', `name=^/${containerName}$`], + stdout: (o) => (output += o), + silentOut: true + }); + + return {exist: output.trim().length > 0}; + } catch (err: unknown) { + return {err}; + } +}; + +export const isContainerRunning = async ({ + containerName, + runner +}: ContainerRunner): Promise<{running: boolean} | {err: unknown}> => { + try { + let output = ''; + await spawn({ + command: runner, + args: ['ps', '--quiet', '-f', `name=^/${containerName}$`], + stdout: (o) => (output += o), + silentOut: true + }); + + return {running: output.trim().length > 0}; + } catch (err: unknown) { + return {err}; + } +};