From aae6c365d623e5cf6e9920cb55399ced555cb17e Mon Sep 17 00:00:00 2001 From: Eric Kwoka <43540491+ekwoka@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:54:35 +0400 Subject: [PATCH 01/19] :test_tube: adds test for default workspace folder --- src/test/cli.up.test.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/test/cli.up.test.ts b/src/test/cli.up.test.ts index 94515e89a..0b80c25f7 100644 --- a/src/test/cli.up.test.ts +++ b/src/test/cli.up.test.ts @@ -14,7 +14,7 @@ const pkg = require('../../package.json'); describe('Dev Containers CLI', function () { this.timeout('240s'); - const tmp = path.relative(process.cwd(), path.join(__dirname, 'tmp')); + const tmp = path.join(__dirname, 'tmp'); const cli = `npx --prefix ${tmp} devcontainer`; @@ -26,7 +26,7 @@ describe('Dev Containers CLI', function () { describe('Command up', () => { - it('should execute successfully with valid config', async () => { + it.only('should execute successfully with valid config', async () => { const res = await shellExec(`${cli} up --workspace-folder ${__dirname}/configs/image --include-configuration --include-merged-configuration`); const response = JSON.parse(res.stdout); assert.equal(response.outcome, 'success'); @@ -69,6 +69,15 @@ describe('Dev Containers CLI', function () { assert.equal(success, false, 'expect non-successful call'); }); + it.only('should succeed when run without a workspace-folder in a workspace root', async () => { + const res = await shellExec(`${cli} up`, { cwd: `${__dirname}/configs/image-with-features` }); + const response = JSON.parse(res.stdout); + assert.equal(response.outcome, 'success'); + const containerId: string = response.containerId; + assert.ok(containerId, 'Container id not found.'); + await shellExec(`docker rm -f ${containerId}`); + }); + // docker-compose variations _without_ features are here (under 'up' tests) // docker-compose variations _with_ features are under 'exec' to test features are installed describe('for docker-compose with image without features', () => { @@ -283,7 +292,7 @@ describe('Dev Containers CLI', function () { assert.equal('Hello, World!', evalEnvWithCommand.stdout); const envWithTestMessage = await shellExec(`docker exec ${containerId} bash -c 'echo -n $Test_Message'`); - assert.equal('H"\\n\\ne"\'\'\'llo M:;a/t?h&^iKa%#@!``ni,sk_a-', envWithTestMessage.stdout); + assert.equal('H"\\n\\ne"\'\'\'llo M:;a/t?h&^iKa%#@!``ni,sk_a-', envWithTestMessage.stdout); const envWithFormat = await shellExec(`docker exec ${containerId} bash -c 'echo -n $ROSCONSOLE_FORMAT'`); assert.equal('[$${severity}] [$${walltime:%Y-%m-%d %H:%M:%S}] [$${node}]: $${message}', envWithFormat.stdout); @@ -295,10 +304,10 @@ describe('Dev Containers CLI', function () { assert.equal('value with $dollar sign', envWithDollar.stdout); const envWithBackSlash = await shellExec(`docker exec ${containerId} bash -c 'echo -n $VAR_WITH_BACK_SLASH'`); - assert.equal('value with \\back slash', envWithBackSlash.stdout); + assert.equal('value with \\back slash', envWithBackSlash.stdout); await shellExec(`docker rm -f ${containerId}`); - }); + }); it('should run with config in subfolder', async () => { const upRes = await shellExec(`${cli} up --workspace-folder ${__dirname}/configs/dockerfile-without-features --config ${__dirname}/configs/dockerfile-without-features/.devcontainer/subfolder/devcontainer.json`); From 5828e435a6bb9e6466d005768f5dbbd53ab47e76 Mon Sep 17 00:00:00 2001 From: Eric Kwoka <43540491+ekwoka@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:56:54 +0400 Subject: [PATCH 02/19] :sparkles: Uses cwd as default workspace folder --- src/spec-node/devContainersSpecCLI.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/spec-node/devContainersSpecCLI.ts b/src/spec-node/devContainersSpecCLI.ts index 59136695d..fe5f60a1b 100644 --- a/src/spec-node/devContainersSpecCLI.ts +++ b/src/spec-node/devContainersSpecCLI.ts @@ -144,16 +144,6 @@ function provisionOptions(y: Argv) { 'include-merged-configuration': { type: 'boolean', default: false, description: 'Include merged configuration in result.' }, }) .check(argv => { - const idLabels = (argv['id-label'] && (Array.isArray(argv['id-label']) ? argv['id-label'] : [argv['id-label']])) as string[] | undefined; - if (idLabels?.some(idLabel => !/.+=.+/.test(idLabel))) { - throw new Error('Unmatched argument format: id-label must match ='); - } - if (!(argv['workspace-folder'] || argv['id-label'])) { - throw new Error('Missing required argument: workspace-folder or id-label'); - } - if (!(argv['workspace-folder'] || argv['override-config'])) { - throw new Error('Missing required argument: workspace-folder or override-config'); - } const mounts = (argv.mount && (Array.isArray(argv.mount) ? argv.mount : [argv.mount])) as string[] | undefined; if (mounts?.some(mount => !mountRegex.test(mount))) { throw new Error('Unmatched argument format: mount must match type=,source=,target=[,external=]'); @@ -218,7 +208,7 @@ async function provision({ 'include-merged-configuration': includeMergedConfig, }: ProvisionArgs) { - const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : undefined; + const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg ?? '.'); const addRemoteEnvs = addRemoteEnv ? (Array.isArray(addRemoteEnv) ? addRemoteEnv as string[] : [addRemoteEnv]) : []; const addCacheFroms = addCacheFrom ? (Array.isArray(addCacheFrom) ? addCacheFrom as string[] : [addCacheFrom]) : []; const additionalFeatures = additionalFeaturesJson ? jsonc.parse(additionalFeaturesJson) as Record> : {}; From b512bdfac1fdd4260a378da59e7a9ff3d278713d Mon Sep 17 00:00:00 2001 From: Eric Kwoka <43540491+ekwoka@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:12:31 +0400 Subject: [PATCH 03/19] :coffin: Removes only test flags --- src/test/cli.up.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/cli.up.test.ts b/src/test/cli.up.test.ts index 0b80c25f7..1d97fe929 100644 --- a/src/test/cli.up.test.ts +++ b/src/test/cli.up.test.ts @@ -26,7 +26,7 @@ describe('Dev Containers CLI', function () { describe('Command up', () => { - it.only('should execute successfully with valid config', async () => { + it('should execute successfully with valid config', async () => { const res = await shellExec(`${cli} up --workspace-folder ${__dirname}/configs/image --include-configuration --include-merged-configuration`); const response = JSON.parse(res.stdout); assert.equal(response.outcome, 'success'); @@ -69,7 +69,7 @@ describe('Dev Containers CLI', function () { assert.equal(success, false, 'expect non-successful call'); }); - it.only('should succeed when run without a workspace-folder in a workspace root', async () => { + it('should succeed when run without a workspace-folder in a workspace root', async () => { const res = await shellExec(`${cli} up`, { cwd: `${__dirname}/configs/image-with-features` }); const response = JSON.parse(res.stdout); assert.equal(response.outcome, 'success'); From 231757ed1bbf505c63b0c0a7444a2304e314e334 Mon Sep 17 00:00:00 2001 From: Eric Kwoka <43540491+ekwoka@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:26:08 +0400 Subject: [PATCH 04/19] :test_tube: Adds Test for Build without workspace folder --- package.json | 2 +- src/test/cli.build.test.ts | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index fda19e76d..1487cd1d6 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "clean": "npm-run-all clean-dist clean-built", "clean-dist": "rimraf dist", "clean-built": "rimraf built", - "test": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/*.test.ts", + "test": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/cli.build.test.ts", "test-matrix": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit", "test-container-features": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/container-features/*.test.ts", "test-container-features-cli": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/container-features/featuresCLICommands.test.ts", diff --git a/src/test/cli.build.test.ts b/src/test/cli.build.test.ts index 0dfae0427..c319fda08 100644 --- a/src/test/cli.build.test.ts +++ b/src/test/cli.build.test.ts @@ -16,7 +16,7 @@ const pkg = require('../../package.json'); describe('Dev Containers CLI', function () { this.timeout('120s'); - const tmp = path.relative(process.cwd(), path.join(__dirname, 'tmp')); + const tmp = path.join(__dirname, 'tmp'); const cli = `npx --prefix ${tmp} devcontainer`; before('Install', async () => { @@ -424,7 +424,7 @@ describe('Dev Containers CLI', function () { assert.strictEqual(envListToObj(details.Config.Env).SUBFOLDER_CONFIG_IMAGE_ENV, 'true'); }); - it('should apply build options', async () => { + it.only('should apply build options', async () => { const testFolder = `${__dirname}/configs/dockerfile-with-target`; const res = await shellExec(`${cli} build --workspace-folder ${testFolder}`); const response = JSON.parse(res.stdout); @@ -433,5 +433,15 @@ describe('Dev Containers CLI', function () { const details = JSON.parse((await shellExec(`docker inspect ${response.imageName}`)).stdout)[0] as ImageDetails; assert.strictEqual(details.Config.Labels?.test_build_options, 'success'); }); + + it.only('should build with default workspace folder', async () => { + const testFolder = `${__dirname}/configs/dockerfile-with-target`; + const res = await shellExec(`${cli} build`, { cwd: testFolder }); + const response = JSON.parse(res.stdout); + assert.equal(response.outcome, 'success'); + assert.ok(response.imageName); + const details = JSON.parse((await shellExec(`docker inspect ${response.imageName}`)).stdout)[0] as ImageDetails; + assert.strictEqual(details.Config.Labels?.test_build_options, 'success'); + }); }); }); From f43c0504d018fe2c951991e2bd2c1cc51eb73e02 Mon Sep 17 00:00:00 2001 From: Eric Kwoka <43540491+ekwoka@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:28:18 +0400 Subject: [PATCH 05/19] :sparkles: Uses cwd as workspace for build --- src/spec-node/devContainersSpecCLI.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/spec-node/devContainersSpecCLI.ts b/src/spec-node/devContainersSpecCLI.ts index fe5f60a1b..82e4c89f7 100644 --- a/src/spec-node/devContainersSpecCLI.ts +++ b/src/spec-node/devContainersSpecCLI.ts @@ -497,7 +497,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.' }, '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.' }, @@ -564,7 +564,7 @@ async function doBuild({ await Promise.all(disposables.map(d => d())); }; try { - const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg); + const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg ?? '.'); 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]) : []; From 5751450422ab28134e1003d820e044fe60bfdb99 Mon Sep 17 00:00:00 2001 From: Eric Kwoka <43540491+ekwoka@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:36:46 +0400 Subject: [PATCH 06/19] :test_tube: Tests run-user-commands without workspace --- package.json | 2 +- src/test/cli.test.ts | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 1487cd1d6..ee0bf39b0 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "clean": "npm-run-all clean-dist clean-built", "clean-dist": "rimraf dist", "clean-built": "rimraf built", - "test": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/cli.build.test.ts", + "test": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/cli.test.ts", "test-matrix": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit", "test-container-features": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/container-features/*.test.ts", "test-container-features-cli": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/container-features/featuresCLICommands.test.ts", diff --git a/src/test/cli.test.ts b/src/test/cli.test.ts index 522c9073d..642a1f4b4 100644 --- a/src/test/cli.test.ts +++ b/src/test/cli.test.ts @@ -12,7 +12,7 @@ const pkg = require('../../package.json'); describe('Dev Containers CLI', function () { this.timeout('120s'); - const tmp = path.relative(process.cwd(), path.join(__dirname, 'tmp')); + const tmp = path.join(__dirname, 'tmp'); const cli = `npx --prefix ${tmp} devcontainer`; before('Install', async () => { @@ -27,7 +27,7 @@ describe('Dev Containers CLI', function () { }); describe('Command run-user-commands', () => { - describe('with valid config', () => { + describe.only('with valid config', () => { let containerId: string | null = null; const testFolder = `${__dirname}/configs/image`; beforeEach(async () => containerId = (await devContainerUp(cli, testFolder)).containerId); @@ -37,6 +37,11 @@ describe('Dev Containers CLI', function () { const response = JSON.parse(res.stdout); assert.equal(response.outcome, 'success'); }); + it('should execute successfully', async () => { + const res = await shellExec(`${cli} run-user-commands`, { cwd: testFolder }); + const response = JSON.parse(res.stdout); + assert.equal(response.outcome, 'success'); + }); }); it('should fail with "not found" error when config is not found', async () => { From cdd73d8ff4d3717b9b74c4e8d49ebe4932ee5b0e Mon Sep 17 00:00:00 2001 From: Eric Kwoka <43540491+ekwoka@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:50:28 +0400 Subject: [PATCH 07/19] :sparkles: Uses cwd as workspace in run-user-commands --- src/spec-node/devContainersSpecCLI.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/spec-node/devContainersSpecCLI.ts b/src/spec-node/devContainersSpecCLI.ts index 82e4c89f7..3e24e2790 100644 --- a/src/spec-node/devContainersSpecCLI.ts +++ b/src/spec-node/devContainersSpecCLI.ts @@ -774,9 +774,6 @@ function runUserCommandsOptions(y: Argv) { if (remoteEnvs?.some(remoteEnv => !/.+=.*/.test(remoteEnv))) { throw new Error('Unmatched argument format: remote-env must match ='); } - 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.'); - } return true; }); } @@ -830,7 +827,7 @@ async function doRunUserCommands({ await Promise.all(disposables.map(d => d())); }; try { - const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : undefined; + const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg ?? '.'); const providedIdLabels = idLabel ? Array.isArray(idLabel) ? idLabel as string[] : [idLabel] : undefined; const addRemoteEnvs = addRemoteEnv ? (Array.isArray(addRemoteEnv) ? addRemoteEnv as string[] : [addRemoteEnv]) : []; const configFile = configParam ? URI.file(path.resolve(process.cwd(), configParam)) : undefined; From a5b0b3459666e47f2f8f517a22fffce8d0b62b34 Mon Sep 17 00:00:00 2001 From: Eric Kwoka <43540491+ekwoka@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:50:47 +0400 Subject: [PATCH 08/19] :test_tube: Adds test for read-configuration --- package.json | 2 +- src/test/cli.set-up.test.ts | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ee0bf39b0..8ce5a6a1d 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "clean": "npm-run-all clean-dist clean-built", "clean-dist": "rimraf dist", "clean-built": "rimraf built", - "test": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/cli.test.ts", + "test": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/cli.set-up.test.ts", "test-matrix": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit", "test-container-features": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/container-features/*.test.ts", "test-container-features-cli": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/container-features/featuresCLICommands.test.ts", diff --git a/src/test/cli.set-up.test.ts b/src/test/cli.set-up.test.ts index 81f0707ff..a87a9caaf 100644 --- a/src/test/cli.set-up.test.ts +++ b/src/test/cli.set-up.test.ts @@ -12,7 +12,7 @@ const pkg = require('../../package.json'); describe('Dev Containers CLI', function () { this.timeout('120s'); - const tmp = path.relative(process.cwd(), path.join(__dirname, 'tmp')); + const tmp = path.join(__dirname, 'tmp'); const cli = `npx --prefix ${tmp} devcontainer`; before('Install', async () => { @@ -79,7 +79,7 @@ describe('Dev Containers CLI', function () { }); }); - describe('Command read-configuration', () => { + describe.only('Command read-configuration', () => { it('should succeed and return postAttachCommand from config', async () => { const containerId = (await shellExec(`docker run -d alpine:3.17 sleep inf`)).stdout.trim(); @@ -103,6 +103,18 @@ describe('Dev Containers CLI', function () { await shellExec(`docker rm -f ${containerId}`); }); + + it('should succeed without a workspace folder', async () => { + + await shellExec(`docker build -t devcontainer-set-up-test ${__dirname}/configs/set-up-with-metadata`); + const containerId = (await shellExec(`docker run -d devcontainer-set-up-test sleep inf`)).stdout.trim(); + + const res = await shellExec(`${cli} read-configuration --include-merged-configuration`, { cwd: `${__dirname}/configs/set-up-with-metadata` }); + const response = JSON.parse(res.stdout); + assert.strictEqual(response.mergedConfiguration.postCreateCommands.length, 1); + + await shellExec(`docker rm -f ${containerId}`); + }); }); describe('Command exec', () => { From 5d0778f8d82ae380c156f6f712d2f486b42e3ebd Mon Sep 17 00:00:00 2001 From: Eric Kwoka <43540491+ekwoka@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:55:34 +0400 Subject: [PATCH 09/19] :sparkles: Uses cwd as workspace for read-configuration --- src/spec-node/devContainersSpecCLI.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/spec-node/devContainersSpecCLI.ts b/src/spec-node/devContainersSpecCLI.ts index 3e24e2790..244d25790 100644 --- a/src/spec-node/devContainersSpecCLI.ts +++ b/src/spec-node/devContainersSpecCLI.ts @@ -961,9 +961,6 @@ function readConfigurationOptions(y: Argv) { if (idLabels?.some(idLabel => !/.+=.+/.test(idLabel))) { throw new Error('Unmatched argument format: id-label must match ='); } - 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.'); - } return true; }); } @@ -999,7 +996,7 @@ async function readConfiguration({ }; let output: Log | undefined; try { - const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : undefined; + const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg ?? '.'); const providedIdLabels = idLabel ? Array.isArray(idLabel) ? idLabel as string[] : [idLabel] : undefined; const configFile = configParam ? URI.file(path.resolve(process.cwd(), configParam)) : undefined; const overrideConfigFile = overrideConfig ? URI.file(path.resolve(process.cwd(), overrideConfig)) : undefined; From 8564f2ed20b41df99bff5e9a28a2c0def7857131 Mon Sep 17 00:00:00 2001 From: Eric Kwoka <43540491+ekwoka@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:59:37 +0400 Subject: [PATCH 10/19] :test_tube: Tests outdated --- package.json | 2 +- src/test/container-features/lockfile.test.ts | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 8ce5a6a1d..94e315caf 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "clean": "npm-run-all clean-dist clean-built", "clean-dist": "rimraf dist", "clean-built": "rimraf built", - "test": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/cli.set-up.test.ts", + "test": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/container-features/lockfile.test.ts", "test-matrix": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit", "test-container-features": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/container-features/*.test.ts", "test-container-features-cli": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/container-features/featuresCLICommands.test.ts", diff --git a/src/test/container-features/lockfile.test.ts b/src/test/container-features/lockfile.test.ts index d0889fed8..cd4e2c66b 100644 --- a/src/test/container-features/lockfile.test.ts +++ b/src/test/container-features/lockfile.test.ts @@ -14,7 +14,7 @@ const pkg = require('../../../package.json'); describe('Lockfile', function () { this.timeout('240s'); - const tmp = path.relative(process.cwd(), path.join(__dirname, 'tmp')); + const tmp = path.join(__dirname, 'tmp'); const cli = `npx --prefix ${tmp} devcontainer`; before('Install', async () => { @@ -125,7 +125,7 @@ describe('Lockfile', function () { assert.strictEqual(foo.latestMajor, '2'); }); - it('outdated command with text output', async () => { + it.only('outdated command with text output', async () => { const workspaceFolder = path.join(__dirname, 'configs/lockfile-outdated-command'); const res = await shellExec(`${cli} outdated --workspace-folder ${workspaceFolder} --output-format text`); @@ -151,6 +151,20 @@ describe('Lockfile', function () { assert.ok(!response.includes('terraform')); assert.ok(!response.includes('myfeatures')); }); + + it.only('outdated command without workspace', async () => { + const workspaceFolder = path.join(__dirname, 'configs/lockfile-outdated-command'); + + const res = await shellExec(`${cli} outdated --output-format text`, { cwd: workspaceFolder }); + const response = res.stdout; + // Count number of lines of output + assert.strictEqual(response.split('\n').length, 7); // 5 valid Features + header + empty line + + // Check that the header is present + assert.ok(response.includes('Current'), 'Current column is missing'); + assert.ok(response.includes('Wanted'), 'Wanted column is missing'); + assert.ok(response.includes('Latest'), 'Latest column is missing'); + }); it('upgrade command', async () => { const workspaceFolder = path.join(__dirname, 'configs/lockfile-upgrade-command'); From 0064a091ebc083506bf45964c633fdd55ebdd209 Mon Sep 17 00:00:00 2001 From: Eric Kwoka <43540491+ekwoka@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:00:35 +0400 Subject: [PATCH 11/19] :sparkles: Uses cwd as the workspace for outdate --- src/spec-node/devContainersSpecCLI.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/spec-node/devContainersSpecCLI.ts b/src/spec-node/devContainersSpecCLI.ts index 244d25790..17600bb15 100644 --- a/src/spec-node/devContainersSpecCLI.ts +++ b/src/spec-node/devContainersSpecCLI.ts @@ -1091,7 +1091,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.' }, '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.' }, @@ -1123,7 +1123,7 @@ async function outdated({ }; let output: Log | undefined; try { - const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg); + const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg ?? '.'); const configFile = configParam ? URI.file(path.resolve(process.cwd(), configParam)) : undefined; const cliHost = await getCLIHost(workspaceFolder, loadNativeModule, logFormat === 'text'); const extensionPath = path.join(__dirname, '..', '..'); From 28fc47f09d3947e552f5e5d53c139c0f3b1e22ed Mon Sep 17 00:00:00 2001 From: Eric Kwoka <43540491+ekwoka@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:12:16 +0400 Subject: [PATCH 12/19] :test_tube: Tests exec without workspace --- package.json | 2 +- src/test/cli.exec.base.ts | 10 ++++++++-- src/test/testUtils.ts | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 94e315caf..8e1962c8f 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "clean": "npm-run-all clean-dist clean-built", "clean-dist": "rimraf dist", "clean-built": "rimraf built", - "test": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/container-features/lockfile.test.ts", + "test": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/cli.exec.buildKit.1.test.ts", "test-matrix": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit", "test-container-features": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/container-features/*.test.ts", "test-container-features-cli": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/container-features/featuresCLICommands.test.ts", diff --git a/src/test/cli.exec.base.ts b/src/test/cli.exec.base.ts index 10e876595..3f912a67f 100644 --- a/src/test/cli.exec.base.ts +++ b/src/test/cli.exec.base.ts @@ -14,7 +14,7 @@ export function describeTests1({ text, options }: BuildKitOption) { describe('Dev Containers CLI', function () { this.timeout('360s'); - const tmp = path.relative(process.cwd(), path.join(__dirname, 'tmp')); + const tmp = path.join(__dirname, 'tmp'); const cli = `npx --prefix ${tmp} devcontainer`; before('Install', async () => { @@ -30,12 +30,18 @@ export function describeTests1({ text, options }: BuildKitOption) { const testFolder = `${__dirname}/configs/image`; beforeEach(async () => containerId = (await devContainerUp(cli, testFolder, options)).containerId); afterEach(async () => await devContainerDown({ containerId })); - it('should execute successfully', async () => { + it.only('should execute successfully', async () => { const res = await shellBufferExec(`${cli} exec --workspace-folder ${testFolder} echo hi`); assert.strictEqual(res.code, 0); assert.equal(res.signal, undefined); assert.strictEqual(res.stdout.toString(), 'hi\n'); }); + it.only('should execute without a workspace folder', async () => { + const res = await shellBufferExec(`${cli} exec echo hi`, { cwd: testFolder}); + assert.strictEqual(res.code, 0); + assert.equal(res.signal, undefined); + assert.strictEqual(res.stdout.toString(), 'hi\n'); + }); it('should not run in a terminal', async () => { const res = await shellBufferExec(`${cli} exec --workspace-folder ${testFolder} [ ! -t 1 ]`); assert.strictEqual(res.code, 0); diff --git a/src/test/testUtils.ts b/src/test/testUtils.ts index ca9dad16a..03484ae16 100644 --- a/src/test/testUtils.ts +++ b/src/test/testUtils.ts @@ -59,8 +59,8 @@ export interface BufferExecResult { signal?: string | null; } -export async function shellBufferExec(command: string, options: { stdin?: Buffer } = {}): Promise { - const exec = await plainExec(undefined); +export async function shellBufferExec(command: string, options: { stdin?: Buffer, cwd?: string } = {}): Promise { + const exec = await plainExec(options.cwd); return runCommandNoPty({ exec, cmd: '/bin/sh', From 352ddba1ca2f074776d37bfc5967b0d5abfc016d Mon Sep 17 00:00:00 2001 From: Eric Kwoka <43540491+ekwoka@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:13:54 +0400 Subject: [PATCH 13/19] :sparkles: Uses CWD as workspace for exec --- src/spec-node/devContainersSpecCLI.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/spec-node/devContainersSpecCLI.ts b/src/spec-node/devContainersSpecCLI.ts index 17600bb15..d12cf9698 100644 --- a/src/spec-node/devContainersSpecCLI.ts +++ b/src/spec-node/devContainersSpecCLI.ts @@ -1226,9 +1226,6 @@ function execOptions(y: Argv) { if (remoteEnvs?.some(remoteEnv => !/.+=.*/.test(remoteEnv))) { throw new Error('Unmatched argument format: remote-env must match ='); } - 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.'); - } return true; }); } @@ -1276,7 +1273,7 @@ export async function doExec({ let output: Log | undefined; const isTTY = process.stdin.isTTY && process.stdout.isTTY || logFormat === 'json'; // If stdin or stdout is a pipe, we don't want to use a PTY. try { - const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : undefined; + const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg ?? '.'); const providedIdLabels = idLabel ? Array.isArray(idLabel) ? idLabel as string[] : [idLabel] : undefined; const addRemoteEnvs = addRemoteEnv ? (Array.isArray(addRemoteEnv) ? addRemoteEnv as string[] : [addRemoteEnv]) : []; const configFile = configParam ? URI.file(path.resolve(process.cwd(), configParam)) : undefined; From 4c2a4289419210f974116949df3d9aff85bb3020 Mon Sep 17 00:00:00 2001 From: Eric Kwoka <43540491+ekwoka@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:15:46 +0400 Subject: [PATCH 14/19] :construction: Clean up only test flags and test script --- package.json | 2 +- src/test/cli.build.test.ts | 6 +++--- src/test/cli.exec.base.ts | 6 +++--- src/test/cli.set-up.test.ts | 2 +- src/test/cli.test.ts | 4 ++-- src/test/container-features/lockfile.test.ts | 10 +++++----- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 8e1962c8f..fda19e76d 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "clean": "npm-run-all clean-dist clean-built", "clean-dist": "rimraf dist", "clean-built": "rimraf built", - "test": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/cli.exec.buildKit.1.test.ts", + "test": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/*.test.ts", "test-matrix": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit", "test-container-features": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/container-features/*.test.ts", "test-container-features-cli": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/container-features/featuresCLICommands.test.ts", diff --git a/src/test/cli.build.test.ts b/src/test/cli.build.test.ts index c319fda08..10271a5a5 100644 --- a/src/test/cli.build.test.ts +++ b/src/test/cli.build.test.ts @@ -53,7 +53,7 @@ describe('Dev Containers CLI', function () { await shellExec(`${cli} build --workspace-folder ${testFolder} --image-name demo:v1`); const tags = await shellExec(`docker images --format "{{.Tag}}" demo`); const imageTags = tags.stdout.trim().split('\n').filter(tag => tag !== ''); - assert.equal(imageTags.length, 1, 'There should be only one tag for demo:v1'); + assert.equal(imageTags.length, 1, 'There should be only one tag for demo:v1'); } catch (error) { assert.equal(error.code, 'ERR_ASSERTION', 'Should fail with ERR_ASSERTION'); } @@ -424,7 +424,7 @@ describe('Dev Containers CLI', function () { assert.strictEqual(envListToObj(details.Config.Env).SUBFOLDER_CONFIG_IMAGE_ENV, 'true'); }); - it.only('should apply build options', async () => { + it'should apply build options', async () => { const testFolder = `${__dirname}/configs/dockerfile-with-target`; const res = await shellExec(`${cli} build --workspace-folder ${testFolder}`); const response = JSON.parse(res.stdout); @@ -434,7 +434,7 @@ describe('Dev Containers CLI', function () { assert.strictEqual(details.Config.Labels?.test_build_options, 'success'); }); - it.only('should build with default workspace folder', async () => { + it'should build with default workspace folder', async () => { const testFolder = `${__dirname}/configs/dockerfile-with-target`; const res = await shellExec(`${cli} build`, { cwd: testFolder }); const response = JSON.parse(res.stdout); diff --git a/src/test/cli.exec.base.ts b/src/test/cli.exec.base.ts index 3f912a67f..aea3becda 100644 --- a/src/test/cli.exec.base.ts +++ b/src/test/cli.exec.base.ts @@ -30,13 +30,13 @@ export function describeTests1({ text, options }: BuildKitOption) { const testFolder = `${__dirname}/configs/image`; beforeEach(async () => containerId = (await devContainerUp(cli, testFolder, options)).containerId); afterEach(async () => await devContainerDown({ containerId })); - it.only('should execute successfully', async () => { + it'should execute successfully', async () => { const res = await shellBufferExec(`${cli} exec --workspace-folder ${testFolder} echo hi`); assert.strictEqual(res.code, 0); assert.equal(res.signal, undefined); assert.strictEqual(res.stdout.toString(), 'hi\n'); }); - it.only('should execute without a workspace folder', async () => { + it'should execute without a workspace folder', async () => { const res = await shellBufferExec(`${cli} exec echo hi`, { cwd: testFolder}); assert.strictEqual(res.code, 0); assert.equal(res.signal, undefined); @@ -388,7 +388,7 @@ export function describeTests2({ text, options }: BuildKitOption) { assert.match(res.stdout, /howdy, node/); }); }); - + it('should fail with "not found" error when config is not found', async () => { let success = false; try { diff --git a/src/test/cli.set-up.test.ts b/src/test/cli.set-up.test.ts index a87a9caaf..1883ab466 100644 --- a/src/test/cli.set-up.test.ts +++ b/src/test/cli.set-up.test.ts @@ -79,7 +79,7 @@ describe('Dev Containers CLI', function () { }); }); - describe.only('Command read-configuration', () => { + describe'Command read-configuration', () => { it('should succeed and return postAttachCommand from config', async () => { const containerId = (await shellExec(`docker run -d alpine:3.17 sleep inf`)).stdout.trim(); diff --git a/src/test/cli.test.ts b/src/test/cli.test.ts index 642a1f4b4..a70e556a8 100644 --- a/src/test/cli.test.ts +++ b/src/test/cli.test.ts @@ -27,7 +27,7 @@ describe('Dev Containers CLI', function () { }); describe('Command run-user-commands', () => { - describe.only('with valid config', () => { + describe'with valid config', () => { let containerId: string | null = null; const testFolder = `${__dirname}/configs/image`; beforeEach(async () => containerId = (await devContainerUp(cli, testFolder)).containerId); @@ -130,4 +130,4 @@ describe('Dev Containers CLI', function () { assert.strictEqual(response.configuration.remoteEnv.SUBFOLDER_CONFIG_REMOTE_ENV, 'true'); }); }); -}); \ No newline at end of file +}); diff --git a/src/test/container-features/lockfile.test.ts b/src/test/container-features/lockfile.test.ts index cd4e2c66b..5a396b505 100644 --- a/src/test/container-features/lockfile.test.ts +++ b/src/test/container-features/lockfile.test.ts @@ -92,7 +92,7 @@ describe('Lockfile', function () { const res = await shellExec(`${cli} outdated --workspace-folder ${workspaceFolder} --output-format json`); const response = JSON.parse(res.stdout); - + const git = response.features['ghcr.io/devcontainers/features/git:1.0']; assert.ok(git); assert.strictEqual(git.current, '1.0.4'); @@ -125,7 +125,7 @@ describe('Lockfile', function () { assert.strictEqual(foo.latestMajor, '2'); }); - it.only('outdated command with text output', async () => { + it'outdated command with text output', async () => { const workspaceFolder = path.join(__dirname, 'configs/lockfile-outdated-command'); const res = await shellExec(`${cli} outdated --workspace-folder ${workspaceFolder} --output-format text`); @@ -151,8 +151,8 @@ describe('Lockfile', function () { assert.ok(!response.includes('terraform')); assert.ok(!response.includes('myfeatures')); }); - - it.only('outdated command without workspace', async () => { + + it'outdated command without workspace', async () => { const workspaceFolder = path.join(__dirname, 'configs/lockfile-outdated-command'); const res = await shellExec(`${cli} outdated --output-format text`, { cwd: workspaceFolder }); @@ -263,4 +263,4 @@ describe('Lockfile', function () { await cleanup(); } }); -}); \ No newline at end of file +}); From 426589a350a25c40308229edc2d2c1196ab54be6 Mon Sep 17 00:00:00 2001 From: Eric Kwoka <43540491+ekwoka@users.noreply.github.com> Date: Mon, 3 Mar 2025 10:11:57 +0400 Subject: [PATCH 15/19] :rewind: Reincludes id label validation --- src/spec-node/devContainersSpecCLI.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/spec-node/devContainersSpecCLI.ts b/src/spec-node/devContainersSpecCLI.ts index d12cf9698..0681a7162 100644 --- a/src/spec-node/devContainersSpecCLI.ts +++ b/src/spec-node/devContainersSpecCLI.ts @@ -144,6 +144,10 @@ function provisionOptions(y: Argv) { 'include-merged-configuration': { type: 'boolean', default: false, description: 'Include merged configuration in result.' }, }) .check(argv => { + const idLabels = (argv['id-label'] && (Array.isArray(argv['id-label']) ? argv['id-label'] : [argv['id-label']])) as string[] | undefined; + if (idLabels?.some(idLabel => !/.+=.+/.test(idLabel))) { + throw new Error('Unmatched argument format: id-label must match ='); + } const mounts = (argv.mount && (Array.isArray(argv.mount) ? argv.mount : [argv.mount])) as string[] | undefined; if (mounts?.some(mount => !mountRegex.test(mount))) { throw new Error('Unmatched argument format: mount must match type=,source=,target=[,external=]'); From 71a212a151d4358304fe0e3d4c6e31ba357051c0 Mon Sep 17 00:00:00 2001 From: Eric Kwoka <43540491+ekwoka@users.noreply.github.com> Date: Mon, 3 Mar 2025 10:23:35 +0400 Subject: [PATCH 16/19] :recycle: Use Yargs default for Build --- src/spec-node/devContainersSpecCLI.ts | 4 ++-- src/test/cli.build.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/spec-node/devContainersSpecCLI.ts b/src/spec-node/devContainersSpecCLI.ts index 0681a7162..7c1faebc9 100644 --- a/src/spec-node/devContainersSpecCLI.ts +++ b/src/spec-node/devContainersSpecCLI.ts @@ -501,7 +501,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', 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.', default: '.', defaultDescription: 'Current Working 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.' }, @@ -568,7 +568,7 @@ async function doBuild({ await Promise.all(disposables.map(d => d())); }; try { - const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg ?? '.'); + const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg); 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]) : []; diff --git a/src/test/cli.build.test.ts b/src/test/cli.build.test.ts index 10271a5a5..c1e6ca67b 100644 --- a/src/test/cli.build.test.ts +++ b/src/test/cli.build.test.ts @@ -424,7 +424,7 @@ describe('Dev Containers CLI', function () { assert.strictEqual(envListToObj(details.Config.Env).SUBFOLDER_CONFIG_IMAGE_ENV, 'true'); }); - it'should apply build options', async () => { + it('should apply build options', async () => { const testFolder = `${__dirname}/configs/dockerfile-with-target`; const res = await shellExec(`${cli} build --workspace-folder ${testFolder}`); const response = JSON.parse(res.stdout); @@ -434,7 +434,7 @@ describe('Dev Containers CLI', function () { assert.strictEqual(details.Config.Labels?.test_build_options, 'success'); }); - it'should build with default workspace folder', async () => { + it('should build with default workspace folder', async () => { const testFolder = `${__dirname}/configs/dockerfile-with-target`; const res = await shellExec(`${cli} build`, { cwd: testFolder }); const response = JSON.parse(res.stdout); From 5a7bf037b1b6818a0cc37e030f644991ec33b9ef Mon Sep 17 00:00:00 2001 From: Eric Kwoka <43540491+ekwoka@users.noreply.github.com> Date: Mon, 3 Mar 2025 10:26:25 +0400 Subject: [PATCH 17/19] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Uses=20Yargs=20Defau?= =?UTF-8?q?lt=20for=20other=20commands.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit And fixes broken test syntaxes --- src/spec-node/devContainersSpecCLI.ts | 22 ++++++++++---------- src/test/cli.exec.base.ts | 4 ++-- src/test/container-features/lockfile.test.ts | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/spec-node/devContainersSpecCLI.ts b/src/spec-node/devContainersSpecCLI.ts index 7c1faebc9..38d865051 100644 --- a/src/spec-node/devContainersSpecCLI.ts +++ b/src/spec-node/devContainersSpecCLI.ts @@ -103,7 +103,7 @@ function provisionOptions(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.', default: '.', defaultDescription: 'Current Working Directory' }, 'workspace-mount-consistency': { choices: ['consistent' as 'consistent', 'cached' as 'cached', 'delegated' as 'delegated'], default: 'cached' as 'cached', description: 'Workspace mount consistency.' }, 'gpu-availability': { choices: ['all' as 'all', 'detect' as 'detect', 'none' as 'none'], default: 'detect' as 'detect', description: 'Availability of GPUs in case the dev container requires any. `all` expects a GPU to be available.' }, 'mount-workspace-git-root': { type: 'boolean', default: true, description: 'Mount the workspace using its Git root.' }, @@ -212,7 +212,7 @@ async function provision({ 'include-merged-configuration': includeMergedConfig, }: ProvisionArgs) { - const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg ?? '.'); + const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg); const addRemoteEnvs = addRemoteEnv ? (Array.isArray(addRemoteEnv) ? addRemoteEnv as string[] : [addRemoteEnv]) : []; const addCacheFroms = addCacheFrom ? (Array.isArray(addCacheFrom) ? addCacheFrom as string[] : [addCacheFrom]) : []; const additionalFeatures = additionalFeaturesJson ? jsonc.parse(additionalFeaturesJson) as Record> : {}; @@ -674,7 +674,7 @@ async function doBuild({ if (envFile) { composeGlobalArgs.push('--env-file', envFile); } - + const composeConfig = await readDockerComposeConfig(buildParams, composeFiles, envFile); const projectName = await getProjectName(params, workspace, composeFiles, composeConfig); const services = Object.keys(composeConfig.services || {}); @@ -746,7 +746,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.', default: '.', defaultDescription: 'Current Working 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.' }, @@ -831,7 +831,7 @@ async function doRunUserCommands({ await Promise.all(disposables.map(d => d())); }; try { - const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg ?? '.'); + const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg); const providedIdLabels = idLabel ? Array.isArray(idLabel) ? idLabel as string[] : [idLabel] : undefined; const addRemoteEnvs = addRemoteEnv ? (Array.isArray(addRemoteEnv) ? addRemoteEnv as string[] : [addRemoteEnv]) : []; const configFile = configParam ? URI.file(path.resolve(process.cwd(), configParam)) : undefined; @@ -945,7 +945,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.', default: '.', defaultDescription: 'Current Working 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.' }, @@ -1000,7 +1000,7 @@ async function readConfiguration({ }; let output: Log | undefined; try { - const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg ?? '.'); + const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg); const providedIdLabels = idLabel ? Array.isArray(idLabel) ? idLabel as string[] : [idLabel] : undefined; const configFile = configParam ? URI.file(path.resolve(process.cwd(), configParam)) : undefined; const overrideConfigFile = overrideConfig ? URI.file(path.resolve(process.cwd(), overrideConfig)) : undefined; @@ -1095,7 +1095,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', 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.', default: '.', defaultDescription: 'Current Working 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.' }, @@ -1127,7 +1127,7 @@ async function outdated({ }; let output: Log | undefined; try { - const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg ?? '.'); + const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg); const configFile = configParam ? URI.file(path.resolve(process.cwd(), configParam)) : undefined; const cliHost = await getCLIHost(workspaceFolder, loadNativeModule, logFormat === 'text'); const extensionPath = path.join(__dirname, '..', '..'); @@ -1197,7 +1197,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.', default: '.', defaultDescription: 'Current Working 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.' }, @@ -1277,7 +1277,7 @@ export async function doExec({ let output: Log | undefined; const isTTY = process.stdin.isTTY && process.stdout.isTTY || logFormat === 'json'; // If stdin or stdout is a pipe, we don't want to use a PTY. try { - const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg ?? '.'); + const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg); const providedIdLabels = idLabel ? Array.isArray(idLabel) ? idLabel as string[] : [idLabel] : undefined; const addRemoteEnvs = addRemoteEnv ? (Array.isArray(addRemoteEnv) ? addRemoteEnv as string[] : [addRemoteEnv]) : []; const configFile = configParam ? URI.file(path.resolve(process.cwd(), configParam)) : undefined; diff --git a/src/test/cli.exec.base.ts b/src/test/cli.exec.base.ts index aea3becda..aa3f6b5f6 100644 --- a/src/test/cli.exec.base.ts +++ b/src/test/cli.exec.base.ts @@ -30,13 +30,13 @@ export function describeTests1({ text, options }: BuildKitOption) { const testFolder = `${__dirname}/configs/image`; beforeEach(async () => containerId = (await devContainerUp(cli, testFolder, options)).containerId); afterEach(async () => await devContainerDown({ containerId })); - it'should execute successfully', async () => { + it('should execute successfully', async () => { const res = await shellBufferExec(`${cli} exec --workspace-folder ${testFolder} echo hi`); assert.strictEqual(res.code, 0); assert.equal(res.signal, undefined); assert.strictEqual(res.stdout.toString(), 'hi\n'); }); - it'should execute without a workspace folder', async () => { + it('should execute without a workspace folder', async () => { const res = await shellBufferExec(`${cli} exec echo hi`, { cwd: testFolder}); assert.strictEqual(res.code, 0); assert.equal(res.signal, undefined); diff --git a/src/test/container-features/lockfile.test.ts b/src/test/container-features/lockfile.test.ts index 5a396b505..6977138b7 100644 --- a/src/test/container-features/lockfile.test.ts +++ b/src/test/container-features/lockfile.test.ts @@ -125,7 +125,7 @@ describe('Lockfile', function () { assert.strictEqual(foo.latestMajor, '2'); }); - it'outdated command with text output', async () => { + it('outdated command with text output', async () => { const workspaceFolder = path.join(__dirname, 'configs/lockfile-outdated-command'); const res = await shellExec(`${cli} outdated --workspace-folder ${workspaceFolder} --output-format text`); @@ -152,7 +152,7 @@ describe('Lockfile', function () { assert.ok(!response.includes('myfeatures')); }); - it'outdated command without workspace', async () => { + it('outdated command without workspace', async () => { const workspaceFolder = path.join(__dirname, 'configs/lockfile-outdated-command'); const res = await shellExec(`${cli} outdated --output-format text`, { cwd: workspaceFolder }); From 125b36926c627279d0b9eb3cc7dfd74cbe881f52 Mon Sep 17 00:00:00 2001 From: Eric Kwoka <43540491+ekwoka@users.noreply.github.com> Date: Mon, 3 Mar 2025 10:30:35 +0400 Subject: [PATCH 18/19] :bug: Fixes missing ( --- src/test/cli.set-up.test.ts | 2 +- src/test/cli.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/cli.set-up.test.ts b/src/test/cli.set-up.test.ts index 1883ab466..78aa630bf 100644 --- a/src/test/cli.set-up.test.ts +++ b/src/test/cli.set-up.test.ts @@ -79,7 +79,7 @@ describe('Dev Containers CLI', function () { }); }); - describe'Command read-configuration', () => { + describe('Command read-configuration', () => { it('should succeed and return postAttachCommand from config', async () => { const containerId = (await shellExec(`docker run -d alpine:3.17 sleep inf`)).stdout.trim(); diff --git a/src/test/cli.test.ts b/src/test/cli.test.ts index a70e556a8..e504f7ae7 100644 --- a/src/test/cli.test.ts +++ b/src/test/cli.test.ts @@ -27,7 +27,7 @@ describe('Dev Containers CLI', function () { }); describe('Command run-user-commands', () => { - describe'with valid config', () => { + describe('with valid config', () => { let containerId: string | null = null; const testFolder = `${__dirname}/configs/image`; beforeEach(async () => containerId = (await devContainerUp(cli, testFolder)).containerId); From b51a60e79cd789c6a064c2788034ae92ab0193d2 Mon Sep 17 00:00:00 2001 From: Eric Kwoka <43540491+ekwoka@users.noreply.github.com> Date: Wed, 12 Mar 2025 13:26:54 +0400 Subject: [PATCH 19/19] :rotating_light: Fix Linting Issues --- src/test/testUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/testUtils.ts b/src/test/testUtils.ts index 03484ae16..03c351f26 100644 --- a/src/test/testUtils.ts +++ b/src/test/testUtils.ts @@ -59,8 +59,8 @@ export interface BufferExecResult { signal?: string | null; } -export async function shellBufferExec(command: string, options: { stdin?: Buffer, cwd?: string } = {}): Promise { - const exec = await plainExec(options.cwd); +export async function shellBufferExec(command: string, options: { stdin?: Buffer; cwd?: string } = {}): Promise { + const exec = plainExec(options.cwd); return runCommandNoPty({ exec, cmd: '/bin/sh',