Skip to content
Draft
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@devcontainers/cli",
"description": "Dev Containers CLI",
"version": "0.80.2",
"version": "0.80.3",
"bin": {
"devcontainer": "devcontainer.js"
},
Expand Down
20 changes: 10 additions & 10 deletions src/spec-node/devContainersSpecCLI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ function buildOptions(y: Argv) {
'user-data-folder': { type: 'string', description: 'Host path to a directory that is intended to be persisted and share state between sessions.' },
'docker-path': { type: 'string', description: 'Docker CLI path.' },
'docker-compose-path': { type: 'string', description: 'Docker Compose CLI path.' },
'workspace-folder': { type: 'string', required: true, description: 'Workspace folder path. The devcontainer.json will be looked up relative to this path.' },
'workspace-folder': { type: 'string', description: 'Workspace folder path. The devcontainer.json will be looked up relative to this path. If not provided, defaults to the current directory.' },
'config': { type: 'string', description: 'devcontainer.json path. The default is to use .devcontainer/devcontainer.json or, if that does not exist, .devcontainer.json in the workspace folder.' },
'log-level': { choices: ['info' as 'info', 'debug' as 'debug', 'trace' as 'trace'], default: 'info' as 'info', description: 'Log level.' },
'log-format': { choices: ['text' as 'text', 'json' as 'json'], default: 'text' as 'text', description: 'Log format.' },
Expand Down Expand Up @@ -574,7 +574,7 @@ async function doBuild({
await Promise.all(disposables.map(d => d()));
};
try {
const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg);
const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : process.cwd();
const configFile: URI | undefined = configParam ? URI.file(path.resolve(process.cwd(), configParam)) : undefined;
const overrideConfigFile: URI | undefined = /* overrideConfig ? URI.file(path.resolve(process.cwd(), overrideConfig)) : */ undefined;
const addCacheFroms = addCacheFrom ? (Array.isArray(addCacheFrom) ? addCacheFrom as string[] : [addCacheFrom]) : [];
Expand Down Expand Up @@ -752,7 +752,7 @@ function runUserCommandsOptions(y: Argv) {
'docker-compose-path': { type: 'string', description: 'Docker Compose CLI path.' },
'container-data-folder': { type: 'string', description: 'Container data folder where user data inside the container will be stored.' },
'container-system-data-folder': { type: 'string', description: 'Container system data folder where system data inside the container will be stored.' },
'workspace-folder': { type: 'string', description: 'Workspace folder path. The devcontainer.json will be looked up relative to this path.' },
'workspace-folder': { type: 'string', description: 'Workspace folder path.The devcontainer.json will be looked up relative to this path. If --container-id, --id-label, and --workspace-folder are not provided, this defaults to the current directory.' },
'mount-workspace-git-root': { type: 'boolean', default: true, description: 'Mount the workspace using its Git root.' },
'container-id': { type: 'string', description: 'Id of the container to run the user commands for.' },
'id-label': { type: 'string', description: 'Id label(s) of the format name=value. If no --container-id is given the id labels will be used to look up the container. If no --id-label is given, one will be inferred from the --workspace-folder path.' },
Expand Down Expand Up @@ -785,7 +785,7 @@ function runUserCommandsOptions(y: Argv) {
throw new Error('Unmatched argument format: remote-env must match <name>=<value>');
}
if (!argv['container-id'] && !idLabels?.length && !argv['workspace-folder']) {
throw new Error('Missing required argument: One of --container-id, --id-label or --workspace-folder is required.');
argv['workspace-folder'] = process.cwd();
}
return true;
});
Expand Down Expand Up @@ -954,7 +954,7 @@ function readConfigurationOptions(y: Argv) {
'user-data-folder': { type: 'string', description: 'Host path to a directory that is intended to be persisted and share state between sessions.' },
'docker-path': { type: 'string', description: 'Docker CLI path.' },
'docker-compose-path': { type: 'string', description: 'Docker Compose CLI path.' },
'workspace-folder': { type: 'string', description: 'Workspace folder path. The devcontainer.json will be looked up relative to this path.' },
'workspace-folder': { type: 'string', description: 'Workspace folder path. The devcontainer.json will be looked up relative to this path. If --container-id, --id-label, and --workspace-folder are not provided, this defaults to the current directory.' },
'mount-workspace-git-root': { type: 'boolean', default: true, description: 'Mount the workspace using its Git root.' },
'container-id': { type: 'string', description: 'Id of the container to run the user commands for.' },
'id-label': { type: 'string', description: 'Id label(s) of the format name=value. If no --container-id is given the id labels will be used to look up the container. If no --id-label is given, one will be inferred from the --workspace-folder path.' },
Expand All @@ -975,7 +975,7 @@ function readConfigurationOptions(y: Argv) {
throw new Error('Unmatched argument format: id-label must match <name>=<value>');
}
if (!argv['container-id'] && !idLabels?.length && !argv['workspace-folder']) {
throw new Error('Missing required argument: One of --container-id, --id-label or --workspace-folder is required.');
argv['workspace-folder'] = process.cwd();
}
return true;
});
Expand Down Expand Up @@ -1107,7 +1107,7 @@ async function readConfiguration({
function outdatedOptions(y: Argv) {
return y.options({
'user-data-folder': { type: 'string', description: 'Host path to a directory that is intended to be persisted and share state between sessions.' },
'workspace-folder': { type: 'string', required: true, description: 'Workspace folder path. The devcontainer.json will be looked up relative to this path.' },
'workspace-folder': { type: 'string', description: 'Workspace folder path. The devcontainer.json will be looked up relative to this path. If --workspace-folder is not provided, defaults to the current directory.' },
'config': { type: 'string', description: 'devcontainer.json path. The default is to use .devcontainer/devcontainer.json or, if that does not exist, .devcontainer.json in the workspace folder.' },
'output-format': { choices: ['text' as 'text', 'json' as 'json'], default: 'text', description: 'Output format.' },
'log-level': { choices: ['info' as 'info', 'debug' as 'debug', 'trace' as 'trace'], default: 'info' as 'info', description: 'Log level for the --terminal-log-file. When set to trace, the log level for --log-file will also be set to trace.' },
Expand Down Expand Up @@ -1139,7 +1139,7 @@ async function outdated({
};
let output: Log | undefined;
try {
const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg);
const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : process.cwd();
const configFile = configParam ? URI.file(path.resolve(process.cwd(), configParam)) : undefined;
const cliHost = await getCLIHost(workspaceFolder, loadNativeModule, logFormat === 'text');
const extensionPath = path.join(__dirname, '..', '..');
Expand Down Expand Up @@ -1209,7 +1209,7 @@ function execOptions(y: Argv) {
'docker-compose-path': { type: 'string', description: 'Docker Compose CLI path.' },
'container-data-folder': { type: 'string', description: 'Container data folder where user data inside the container will be stored.' },
'container-system-data-folder': { type: 'string', description: 'Container system data folder where system data inside the container will be stored.' },
'workspace-folder': { type: 'string', description: 'Workspace folder path. The devcontainer.json will be looked up relative to this path.' },
'workspace-folder': { type: 'string', description: 'Workspace folder path. The devcontainer.json will be looked up relative to this path. If --container-id, --id-label, and --workspace-folder are not provided, this defaults to the current directory.' },
'mount-workspace-git-root': { type: 'boolean', default: true, description: 'Mount the workspace using its Git root.' },
'container-id': { type: 'string', description: 'Id of the container to run the user commands for.' },
'id-label': { type: 'string', description: 'Id label(s) of the format name=value. If no --container-id is given the id labels will be used to look up the container. If no --id-label is given, one will be inferred from the --workspace-folder path.' },
Expand Down Expand Up @@ -1243,7 +1243,7 @@ function execOptions(y: Argv) {
throw new Error('Unmatched argument format: remote-env must match <name>=<value>');
}
if (!argv['container-id'] && !idLabels?.length && !argv['workspace-folder']) {
throw new Error('Missing required argument: One of --container-id, --id-label or --workspace-folder is required.');
argv['workspace-folder'] = process.cwd();
}
return true;
});
Expand Down
6 changes: 4 additions & 2 deletions src/spec-node/featuresCLI/resolveDependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function featuresResolveDependenciesOptions(y: Argv) {
return y
.options({
'log-level': { choices: ['error' as 'error', 'info' as 'info', 'debug' as 'debug', 'trace' as 'trace'], default: 'error' as 'error', description: 'Log level.' },
'workspace-folder': { type: 'string', description: 'Workspace folder to use for the configuration.', demandOption: true },
'workspace-folder': { type: 'string', description: 'Workspace folder to use for the configuration.' },
});
}

Expand All @@ -41,7 +41,7 @@ export function featuresResolveDependenciesHandler(args: featuresResolveDependen
}

async function featuresResolveDependencies({
'workspace-folder': workspaceFolder,
'workspace-folder': workspaceFolderArg,
'log-level': inputLogLevel,
}: featuresResolveDependenciesArgs) {
const disposables: (() => Promise<unknown> | undefined)[] = [];
Expand All @@ -62,6 +62,8 @@ async function featuresResolveDependencies({

let jsonOutput: JsonOutput = {};

const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : process.cwd();

// Detect path to dev container config
let configPath = path.join(workspaceFolder, '.devcontainer.json');
if (!(await isLocalFile(configPath))) {
Expand Down
6 changes: 4 additions & 2 deletions src/spec-node/templatesCLI/apply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import * as jsonc from 'jsonc-parser';
import { UnpackArgv } from '../devContainersSpecCLI';
import { fetchTemplate, SelectedTemplate, TemplateFeatureOption, TemplateOptions } from '../../spec-configuration/containerTemplatesOCI';
import { runAsyncHandler } from '../utils';
import path from 'path';

export function templateApplyOptions(y: Argv) {
return y
.options({
'workspace-folder': { type: 'string', alias: 'w', demandOption: true, default: '.', description: 'Target workspace folder to apply Template' },
'workspace-folder': { type: 'string', alias: 'w', default: '.', description: 'Target workspace folder to apply Template' },
'template-id': { type: 'string', alias: 't', demandOption: true, description: 'Reference to a Template in a supported OCI registry' },
'template-args': { type: 'string', alias: 'a', default: '{}', description: 'Arguments to replace within the provided Template, provided as JSON' },
'features': { type: 'string', alias: 'f', default: '[]', description: 'Features to add to the provided Template, provided as JSON.' },
Expand All @@ -30,7 +31,7 @@ export function templateApplyHandler(args: TemplateApplyArgs) {
}

async function templateApply({
'workspace-folder': workspaceFolder,
'workspace-folder': workspaceFolderArg,
'template-id': templateId,
'template-args': templateArgs,
'features': featuresArgs,
Expand All @@ -42,6 +43,7 @@ async function templateApply({
const dispose = async () => {
await Promise.all(disposables.map(d => d()));
};
const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : process.cwd();

const pkg = getPackageConfig();

Expand Down
5 changes: 2 additions & 3 deletions src/spec-node/upgradeCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { mapNodeArchitectureToGOARCH, mapNodeOSToGOOS } from '../spec-configurat
export function featuresUpgradeOptions(y: Argv) {
return y
.options({
'workspace-folder': { type: 'string', description: 'Workspace folder.', demandOption: true },
'workspace-folder': { type: 'string', description: 'Workspace folder. If --workspace-folder is not provided defaults to the current directory.' },
'docker-path': { type: 'string', description: 'Path to docker executable.', default: 'docker' },
'docker-compose-path': { type: 'string', description: 'Path to docker-compose executable.', default: 'docker-compose' },
'config': { type: 'string', description: 'devcontainer.json path. The default is to use .devcontainer/devcontainer.json or, if that does not exist, .devcontainer.json in the workspace folder.' },
Expand All @@ -37,7 +37,6 @@ export function featuresUpgradeOptions(y: Argv) {
if (argv.feature && !argv['target-version'] || !argv.feature && argv['target-version']) {
throw new Error('The \'--target-version\' and \'--feature\' flag must be used together.');
}

if (argv['target-version']) {
const targetVersion = argv['target-version'];
if (!targetVersion.match(/^\d+(\.\d+(\.\d+)?)?$/)) {
Expand Down Expand Up @@ -70,7 +69,7 @@ async function featuresUpgrade({
};
let output: Log | undefined;
try {
const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg);
const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : process.cwd();
const configFile = configArg ? URI.file(path.resolve(process.cwd(), configArg)) : undefined;
const cliHost = await getCLIHost(workspaceFolder, loadNativeModule, true);
const extensionPath = path.join(__dirname, '..', '..');
Expand Down
16 changes: 16 additions & 0 deletions src/test/cli.exec.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,22 @@ export function describeTests1({ text, options }: BuildKitOption) {
assert.strictEqual(env.FOO, 'BAR');
assert.strictEqual(env.BAZ, '');
});
it('should exec with default workspace folder (current directory)', async () => {
const originalCwd = process.cwd();
const absoluteTmpPath = path.resolve(__dirname, 'tmp');
const absoluteCli = `npx --prefix ${absoluteTmpPath} devcontainer`;
process.chdir(testFolder);

try {
// Exec without --workspace-folder should use current directory as default
const execRes = await shellExec(`${absoluteCli} exec echo "default workspace test"`);
assert.strictEqual(execRes.error, null);
assert.match(execRes.stdout, /default workspace test/);
} finally {
// Restore original directory
process.chdir(originalCwd);
}
});
});
describe(`with valid (image) config containing features [${text}]`, () => {
let containerId: string | null = null;
Expand Down