v12 preparations: Add feature switch for default eol-converson

Change-type: patch
Connects-to: #1770
This commit is contained in:
Scott Lowe 2020-06-03 15:41:12 +02:00 committed by Paulo Castro
parent b1552f8e9b
commit 1569915fae
11 changed files with 156 additions and 83 deletions

View File

@ -60,7 +60,7 @@ const buildProject = function(docker, logger, composeOpts, opts) {
opts.buildEmulated, opts.buildEmulated,
opts.buildOpts, opts.buildOpts,
composeOpts.inlineLogs, composeOpts.inlineLogs,
opts.convertEol, composeOpts.convertEol,
composeOpts.dockerfilePath, composeOpts.dockerfilePath,
composeOpts.nogitignore, composeOpts.nogitignore,
); );
@ -152,9 +152,6 @@ Examples:
} }
delete params.source; delete params.source;
options.convertEol = options['convert-eol'] || false;
delete options['convert-eol'];
const { application, arch, deviceType } = options; const { application, arch, deviceType } = options;
return Promise.try(function() { return Promise.try(function() {
@ -203,7 +200,6 @@ Examples:
deviceType: resolvedDeviceType, deviceType: resolvedDeviceType,
buildEmulated: !!options.emulated, buildEmulated: !!options.emulated,
buildOpts, buildOpts,
convertEol: options.convertEol,
}), }),
); );
}); });

View File

@ -97,7 +97,7 @@ const deployProject = function(docker, logger, composeOpts, opts) {
opts.buildEmulated, opts.buildEmulated,
opts.buildOpts, opts.buildOpts,
composeOpts.inlineLogs, composeOpts.inlineLogs,
opts.convertEol, composeOpts.convertEol,
composeOpts.dockerfilePath, composeOpts.dockerfilePath,
composeOpts.nogitignore, composeOpts.nogitignore,
) )
@ -263,16 +263,6 @@ Examples:
appName = appName_raw || appName || options.application; appName = appName_raw || appName || options.application;
delete options.application; delete options.application;
options.convertEol = options['convert-eol'] || false;
delete options['convert-eol'];
if (options.convertEol && !options.build) {
return Promise.reject(
new ExpectedError(
'The --eol-conversion flag is only valid with --build.',
),
);
}
return Promise.try(function() { return Promise.try(function() {
if (appName == null) { if (appName == null) {
throw new ExpectedError( throw new ExpectedError(
@ -320,7 +310,6 @@ Examples:
shouldUploadLogs: !options.nologupload, shouldUploadLogs: !options.nologupload,
buildEmulated: !!options.emulated, buildEmulated: !!options.emulated,
buildOpts, buildOpts,
convertEol: options.convertEol,
}), }),
); );
}); });

View File

@ -117,6 +117,7 @@ export const push: CommandDefinition<
system?: boolean; system?: boolean;
env?: string | string[]; env?: string | string[];
'convert-eol'?: boolean; 'convert-eol'?: boolean;
'noconvert-eol'?: boolean;
} }
> = { > = {
signature: 'push <applicationOrDevice>', signature: 'push <applicationOrDevice>',
@ -258,11 +259,23 @@ export const push: CommandDefinition<
{ {
signature: 'convert-eol', signature: 'convert-eol',
alias: 'l', alias: 'l',
description: stripIndent` description: isV12()
? 'No-op and deprecated since balena CLI v12.0.0'
: stripIndent`
On Windows only, convert line endings from CRLF (Windows format) to LF (Unix format). On Windows only, convert line endings from CRLF (Windows format) to LF (Unix format).
Source files are not modified.`, Source files are not modified.`,
boolean: true, boolean: true,
}, },
...(isV12()
? [
{
signature: 'noconvert-eol',
description:
"Don't convert line endings from CRLF (Windows format) to LF (Unix format).",
boolean: true,
},
]
: []),
{ {
signature: 'nogitignore', signature: 'nogitignore',
alias: 'G', alias: 'G',
@ -307,6 +320,9 @@ export const push: CommandDefinition<
); );
const nogitignore = !!options.nogitignore || isV12(); const nogitignore = !!options.nogitignore || isV12();
const convertEol = isV12()
? !options['noconvert-eol']
: !!options['convert-eol'];
const buildTarget = getBuildTarget(appOrDevice); const buildTarget = getBuildTarget(appOrDevice);
switch (buildTarget) { switch (buildTarget) {
@ -346,7 +362,7 @@ export const push: CommandDefinition<
nocache: options.nocache || false, nocache: options.nocache || false,
registrySecrets, registrySecrets,
headless: options.detached || false, headless: options.detached || false,
convertEol: options['convert-eol'] || false, convertEol,
}; };
const args = { const args = {
app, app,
@ -388,7 +404,7 @@ export const push: CommandDefinition<
typeof options.env === 'string' typeof options.env === 'string'
? [options.env] ? [options.env]
: options.env || [], : options.env || [],
convertEol: options['convert-eol'] || false, convertEol,
}), }),
).catch(BuildError, e => { ).catch(BuildError, e => {
throw new ExpectedError(e.toString()); throw new ExpectedError(e.toString());

View File

@ -47,6 +47,7 @@ export interface TaggedImage {
} }
export interface ComposeOpts { export interface ComposeOpts {
convertEol: boolean;
dockerfilePath?: string; dockerfilePath?: string;
inlineLogs?: boolean; inlineLogs?: boolean;
noParentCheck: boolean; noParentCheck: boolean;

View File

@ -92,12 +92,24 @@ export function appendOptions(opts) {
}, },
{ {
signature: 'convert-eol', signature: 'convert-eol',
description: `\ description: isV12()
? 'No-op and deprecated since balena CLI v12.0.0'
: `\
On Windows only, convert line endings from CRLF (Windows format) to LF (Unix format). \ On Windows only, convert line endings from CRLF (Windows format) to LF (Unix format). \
Source files are not modified.`, Source files are not modified.`,
boolean: true, boolean: true,
alias: 'l', alias: 'l',
}, },
...(isV12()
? [
{
signature: 'noconvert-eol',
description:
"Don't convert line endings from CRLF (Windows format) to LF (Unix format).",
boolean: true,
},
]
: []),
]); ]);
} }
@ -111,6 +123,7 @@ export function generateOpts(options) {
projectName: options.projectName, projectName: options.projectName,
projectPath, projectPath,
inlineLogs: !options.nologs && (!!options.logs || isV12()), inlineLogs: !options.nologs && (!!options.logs || isV12()),
convertEol: isV12() ? !options['noconvert-eol'] : !!options['convert-eol'],
dockerfilePath: options.dockerfile, dockerfilePath: options.dockerfile,
nogitignore: !!options.nogitignore || isV12(), nogitignore: !!options.nogitignore || isV12(),
noParentCheck: options['noparent-check'], noParentCheck: options['noparent-check'],

View File

@ -178,6 +178,7 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
globalLogger.logInfo(`Starting build on device ${opts.deviceHost}`); globalLogger.logInfo(`Starting build on device ${opts.deviceHost}`);
const project = await loadProject(globalLogger, { const project = await loadProject(globalLogger, {
convertEol: opts.convertEol,
dockerfilePath: opts.dockerfilePath, dockerfilePath: opts.dockerfilePath,
noParentCheck: opts.noParentCheck, noParentCheck: opts.noParentCheck,
projectName: 'local', projectName: 'local',

View File

@ -17,6 +17,7 @@
import { fs } from 'mz'; import { fs } from 'mz';
import Logger = require('./logger'); import Logger = require('./logger');
import { isV12 } from './version';
const globalLogger = Logger.getLogger(); const globalLogger = Logger.getLogger();
@ -110,7 +111,9 @@ export async function readFileWithEolConversion(
); );
// And summary warning later // And summary warning later
globalLogger.deferredLog( globalLogger.deferredLog(
'Windows-format line endings were detected in some files. Consider using the `--convert-eol` option.', isV12()
? 'Windows-format line endings were detected in some files, but were not converted due to `--noconvert-eol` option.'
: 'Windows-format line endings were detected in some files. Consider using the `--convert-eol` option.',
Logger.Level.WARN, Logger.Level.WARN,
); );

View File

@ -84,9 +84,14 @@ describe('balena build', function() {
it('should create the expected tar stream (single container)', async () => { it('should create the expected tar stream (single container)', async () => {
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
const isV12W = isWindows && isV12();
const expectedFiles: ExpectedTarStreamFiles = { const expectedFiles: ExpectedTarStreamFiles = {
'src/start.sh': { fileSize: 89, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' },
'src/windows-crlf.sh': { fileSize: 70, type: 'file' }, 'src/windows-crlf.sh': {
fileSize: isV12W ? 68 : 70,
testStream: isV12W ? expectStreamNoCRLF : undefined,
type: 'file',
},
Dockerfile: { fileSize: 88, type: 'file' }, Dockerfile: { fileSize: 88, type: 'file' },
'Dockerfile-alt': { fileSize: 30, type: 'file' }, 'Dockerfile-alt': { fileSize: 30, type: 'file' },
}; };
@ -104,14 +109,17 @@ describe('balena build', function() {
: '[Build] main Image size: 1.14 MB', : '[Build] main Image size: 1.14 MB',
]; ];
if (isWindows) { if (isWindows) {
expectedResponseLines.push( const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
`[Warn] CRLF (Windows) line endings detected in file: ${path.join( if (isV12()) {
projectPath, expectedResponseLines.push(
'src', `[Info] Converting line endings CRLF -> LF for file: ${fname}`,
'windows-crlf.sh', );
)}`, } else {
'[Warn] Windows-format line endings were detected in some files. Consider using the `--convert-eol` option.', expectedResponseLines.push(
); `[Warn] CRLF (Windows) line endings detected in file: ${fname}`,
'[Warn] Windows-format line endings were detected in some files. Consider using the `--convert-eol` option.',
);
}
} }
docker.expectGetInfo({}); docker.expectGetInfo({});
await testDockerBuildStream({ await testDockerBuildStream({
@ -129,6 +137,7 @@ describe('balena build', function() {
it('should create the expected tar stream (--emulated)', async () => { it('should create the expected tar stream (--emulated)', async () => {
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
const isV12W = isWindows && isV12();
const transposedDockerfile = const transposedDockerfile =
stripIndent` stripIndent`
FROM busybox FROM busybox
@ -138,7 +147,11 @@ describe('balena build', function() {
CMD ["/usr/src/start.sh"]` + '\n'; CMD ["/usr/src/start.sh"]` + '\n';
const expectedFiles: ExpectedTarStreamFiles = { const expectedFiles: ExpectedTarStreamFiles = {
'src/start.sh': { fileSize: 89, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' },
'src/windows-crlf.sh': { fileSize: 70, type: 'file' }, 'src/windows-crlf.sh': {
fileSize: isV12W ? 68 : 70,
testStream: isV12W ? expectStreamNoCRLF : undefined,
type: 'file',
},
Dockerfile: { Dockerfile: {
fileSize: transposedDockerfile.length, fileSize: transposedDockerfile.length,
type: 'file', type: 'file',
@ -162,14 +175,17 @@ describe('balena build', function() {
'[Success] Build succeeded!', '[Success] Build succeeded!',
]; ];
if (isWindows) { if (isWindows) {
expectedResponseLines.push( const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
`[Warn] CRLF (Windows) line endings detected in file: ${path.join( if (isV12()) {
projectPath, expectedResponseLines.push(
'src', `[Info] Converting line endings CRLF -> LF for file: ${fname}`,
'windows-crlf.sh', );
)}`, } else {
'[Warn] Windows-format line endings were detected in some files. Consider using the `--convert-eol` option.', expectedResponseLines.push(
); `[Warn] CRLF (Windows) line endings detected in file: ${fname}`,
'[Warn] Windows-format line endings were detected in some files. Consider using the `--convert-eol` option.',
);
}
} }
const arch = 'rpi'; const arch = 'rpi';
const deviceType = 'raspberry-pi'; const deviceType = 'raspberry-pi';
@ -207,13 +223,14 @@ describe('balena build', function() {
} }
}); });
it('should create the expected tar stream (single container, --convert-eol)', async () => { it('should create the expected tar stream (single container, --[no]convert-eol)', async () => {
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
const eol = isWindows && !isV12();
const expectedFiles: ExpectedTarStreamFiles = { const expectedFiles: ExpectedTarStreamFiles = {
'src/start.sh': { fileSize: 89, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' },
'src/windows-crlf.sh': { 'src/windows-crlf.sh': {
fileSize: isWindows ? 68 : 70, fileSize: eol ? 68 : 70,
testStream: isWindows ? expectStreamNoCRLF : undefined, testStream: eol ? expectStreamNoCRLF : undefined,
type: 'file', type: 'file',
}, },
Dockerfile: { fileSize: 88, type: 'file' }, Dockerfile: { fileSize: 88, type: 'file' },
@ -233,17 +250,23 @@ describe('balena build', function() {
: '[Build] main Image size: 1.14 MB', : '[Build] main Image size: 1.14 MB',
]; ];
if (isWindows) { if (isWindows) {
expectedResponseLines.push( const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
`[Info] Converting line endings CRLF -> LF for file: ${path.join( if (isV12()) {
projectPath, expectedResponseLines.push(
'src', `[Warn] CRLF (Windows) line endings detected in file: ${fname}`,
'windows-crlf.sh', '[Warn] Windows-format line endings were detected in some files, but were not converted due to `--noconvert-eol` option.',
)}`, );
); } else {
expectedResponseLines.push(
`[Info] Converting line endings CRLF -> LF for file: ${fname}`,
);
}
} }
docker.expectGetInfo({}); docker.expectGetInfo({});
await testDockerBuildStream({ await testDockerBuildStream({
commandLine: `build ${projectPath} --deviceType nuc --arch amd64 --convert-eol`, commandLine: isV12()
? `build ${projectPath} --deviceType nuc --arch amd64 --noconvert-eol`
: `build ${projectPath} --deviceType nuc --arch amd64 --convert-eol`,
dockerMock: docker, dockerMock: docker,
expectedFilesByService: { main: expectedFiles }, expectedFilesByService: { main: expectedFiles },
expectedQueryParamsByService: { main: commonQueryParams }, expectedQueryParamsByService: { main: commonQueryParams },

View File

@ -25,7 +25,7 @@ import * as sinon from 'sinon';
import { isV12 } from '../../build/utils/version'; import { isV12 } from '../../build/utils/version';
import { BalenaAPIMock } from '../balena-api-mock'; import { BalenaAPIMock } from '../balena-api-mock';
import { testDockerBuildStream } from '../docker-build'; import { expectStreamNoCRLF, testDockerBuildStream } from '../docker-build';
import { DockerMock, dockerResponsePath } from '../docker-mock'; import { DockerMock, dockerResponsePath } from '../docker-mock';
import { cleanOutput, runCommand, switchSentry } from '../helpers'; import { cleanOutput, runCommand, switchSentry } from '../helpers';
import { ExpectedTarStreamFiles } from '../projects'; import { ExpectedTarStreamFiles } from '../projects';
@ -106,9 +106,14 @@ describe('balena deploy', function() {
it('should create the expected --build tar stream (single container)', async () => { it('should create the expected --build tar stream (single container)', async () => {
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
const isV12W = isWindows && isV12();
const expectedFiles: ExpectedTarStreamFiles = { const expectedFiles: ExpectedTarStreamFiles = {
'src/start.sh': { fileSize: 89, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' },
'src/windows-crlf.sh': { fileSize: 70, type: 'file' }, 'src/windows-crlf.sh': {
fileSize: isV12W ? 68 : 70,
testStream: isV12W ? expectStreamNoCRLF : undefined,
type: 'file',
},
Dockerfile: { fileSize: 88, type: 'file' }, Dockerfile: { fileSize: 88, type: 'file' },
'Dockerfile-alt': { fileSize: 30, type: 'file' }, 'Dockerfile-alt': { fileSize: 30, type: 'file' },
}; };
@ -123,14 +128,17 @@ describe('balena deploy', function() {
`[Info] Creating default composition with source: "${projectPath}"`, `[Info] Creating default composition with source: "${projectPath}"`,
]; ];
if (isWindows) { if (isWindows) {
expectedResponseLines.push( const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
`[Warn] CRLF (Windows) line endings detected in file: ${path.join( if (isV12()) {
projectPath, expectedResponseLines.push(
'src', `[Info] Converting line endings CRLF -> LF for file: ${fname}`,
'windows-crlf.sh', );
)}`, } else {
'[Warn] Windows-format line endings were detected in some files. Consider using the `--convert-eol` option.', expectedResponseLines.push(
); `[Warn] CRLF (Windows) line endings detected in file: ${fname}`,
'[Warn] Windows-format line endings were detected in some files. Consider using the `--convert-eol` option.',
);
}
} }
api.expectPatchImage({}); api.expectPatchImage({});
@ -189,7 +197,7 @@ describe('balena deploy', function() {
}); });
await testDockerBuildStream({ await testDockerBuildStream({
commandLine: `deploy testApp --build --source ${projectPath} -G`, commandLine: `deploy testApp --build --source ${projectPath} --noconvert-eol -G`,
dockerMock: docker, dockerMock: docker,
expectedFilesByService: { main: expectedFiles }, expectedFilesByService: { main: expectedFiles },
expectedQueryParamsByService: { main: commonQueryParams }, expectedQueryParamsByService: { main: commonQueryParams },

View File

@ -109,9 +109,14 @@ describe('balena push', function() {
it('should create the expected tar stream (single container)', async () => { it('should create the expected tar stream (single container)', async () => {
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
const isV12W = isWindows && isV12();
const expectedFiles: ExpectedTarStreamFiles = { const expectedFiles: ExpectedTarStreamFiles = {
'src/start.sh': { fileSize: 89, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' },
'src/windows-crlf.sh': { fileSize: 70, type: 'file' }, 'src/windows-crlf.sh': {
fileSize: isV12W ? 68 : 70,
testStream: isV12W ? expectStreamNoCRLF : undefined,
type: 'file',
},
Dockerfile: { fileSize: 88, type: 'file' }, Dockerfile: { fileSize: 88, type: 'file' },
'Dockerfile-alt': { fileSize: 30, type: 'file' }, 'Dockerfile-alt': { fileSize: 30, type: 'file' },
}; };
@ -123,14 +128,17 @@ describe('balena push', function() {
); );
const expectedResponseLines = [...commonResponseLines[responseFilename]]; const expectedResponseLines = [...commonResponseLines[responseFilename]];
if (isWindows) { if (isWindows) {
expectedResponseLines.push( const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
`[Warn] CRLF (Windows) line endings detected in file: ${path.join( if (isV12()) {
projectPath, expectedResponseLines.push(
'src', `[Info] Converting line endings CRLF -> LF for file: ${fname}`,
'windows-crlf.sh', );
)}`, } else {
'[Warn] Windows-format line endings were detected in some files. Consider using the `--convert-eol` option.', expectedResponseLines.push(
); `[Warn] CRLF (Windows) line endings detected in file: ${fname}`,
'[Warn] Windows-format line endings were detected in some files. Consider using the `--convert-eol` option.',
);
}
} }
await testPushBuildStream({ await testPushBuildStream({
@ -165,7 +173,7 @@ describe('balena push', function() {
await testPushBuildStream({ await testPushBuildStream({
builderMock: builder, builderMock: builder,
commandLine: `push testApp -s ${projectPath} -R ${regSecretsPath} --dockerfile Dockerfile-alt --nogitignore`, commandLine: `push testApp -s ${projectPath} -R ${regSecretsPath} --dockerfile Dockerfile-alt --noconvert-eol --nogitignore`,
expectedFiles, expectedFiles,
expectedQueryParams, expectedQueryParams,
expectedResponseLines: commonResponseLines[responseFilename], expectedResponseLines: commonResponseLines[responseFilename],
@ -175,14 +183,15 @@ describe('balena push', function() {
}); });
}); });
it('should create the expected tar stream (single container, --convert-eol)', async () => { it('should create the expected tar stream (single container, --[no]convert-eol)', async () => {
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
const eol = isWindows && !isV12();
const expectedFiles: ExpectedTarStreamFiles = { const expectedFiles: ExpectedTarStreamFiles = {
'src/start.sh': { fileSize: 89, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' },
'src/windows-crlf.sh': { 'src/windows-crlf.sh': {
fileSize: isWindows ? 68 : 70, fileSize: eol ? 68 : 70,
testStream: eol ? expectStreamNoCRLF : undefined,
type: 'file', type: 'file',
testStream: isWindows ? expectStreamNoCRLF : undefined,
}, },
Dockerfile: { fileSize: 88, type: 'file' }, Dockerfile: { fileSize: 88, type: 'file' },
'Dockerfile-alt': { fileSize: 30, type: 'file' }, 'Dockerfile-alt': { fileSize: 30, type: 'file' },
@ -195,18 +204,24 @@ describe('balena push', function() {
); );
const expectedResponseLines = [...commonResponseLines[responseFilename]]; const expectedResponseLines = [...commonResponseLines[responseFilename]];
if (isWindows) { if (isWindows) {
expectedResponseLines.push( const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
`[Info] Converting line endings CRLF -> LF for file: ${path.join( if (isV12()) {
projectPath, expectedResponseLines.push(
'src', `[Warn] CRLF (Windows) line endings detected in file: ${fname}`,
'windows-crlf.sh', '[Warn] Windows-format line endings were detected in some files, but were not converted due to `--noconvert-eol` option.',
)}`, );
); } else {
expectedResponseLines.push(
`[Info] Converting line endings CRLF -> LF for file: ${fname}`,
);
}
} }
await testPushBuildStream({ await testPushBuildStream({
builderMock: builder, builderMock: builder,
commandLine: `push testApp -s ${projectPath} -R ${regSecretsPath} -l`, commandLine: isV12()
? `push testApp -s ${projectPath} -R ${regSecretsPath} --noconvert-eol`
: `push testApp -s ${projectPath} -R ${regSecretsPath} -l`,
expectedFiles, expectedFiles,
expectedQueryParams: commonQueryParams, expectedQueryParams: commonQueryParams,
expectedResponseLines, expectedResponseLines,
@ -322,6 +337,10 @@ describe('balena push', function() {
}); });
}); });
// NOTE: if this test or other tests involving symbolic links fail on Windows
// (with a mismatched fileSize 13 vs 5 for 'symlink-a.txt'), ensure that the
// `core.symlinks` property is set to `true` in the `.git/config` file. Ref:
// https://git-scm.com/docs/git-config#Documentation/git-config.txt-coresymlinks
it('should create the expected tar stream (single container, symbolic links, --nogitignore)', async () => { it('should create the expected tar stream (single container, symbolic links, --nogitignore)', async () => {
const projectPath = path.join( const projectPath = path.join(
projectsPath, projectsPath,

View File

@ -57,6 +57,10 @@ describe('compare new and old tarDirectory implementations', function() {
await setupDockerignoreTestData({ cleanup: true }); await setupDockerignoreTestData({ cleanup: true });
}); });
// NOTE: if this test or other tests involving symbolic links fail on Windows
// (with a mismatched fileSize 13 vs 5 for 'symlink-a.txt'), ensure that the
// `core.symlinks` property is set to `true` in the `.git/config` file. Ref:
// https://git-scm.com/docs/git-config#Documentation/git-config.txt-coresymlinks
it('should produce the expected file list', async function() { it('should produce the expected file list', async function() {
const dockerignoreProjDir = path.join( const dockerignoreProjDir = path.join(
projectsPath, projectsPath,