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
14 changes: 7 additions & 7 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
123 changes: 70 additions & 53 deletions src/services/dev/start/docker.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 () => {
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
Expand Down Expand Up @@ -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']),
Expand Down Expand Up @@ -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, '-')
Expand Down Expand Up @@ -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);
}
Expand Down
6 changes: 6 additions & 0 deletions src/types/runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type {EmulatorRunner} from '@junobuild/config';

export interface ContainerRunner {
containerName: string;
runner: EmulatorRunner['type'];
}
88 changes: 2 additions & 86 deletions src/utils/env.utils.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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
Expand Down
Loading