mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-03-11 15:04:00 +00:00
Add support for auto-conversion of CRLF line endings.
Applies to commands: balena push balena build balena deploy --build Change-type: minor Resolves: #1273 Signed-off-by: Scott Lowe <scott@balena.io>
This commit is contained in:
parent
041823189f
commit
58e7880f1d
@ -1802,6 +1802,10 @@ separated by a colon, e.g:
|
|||||||
Note that if the service name cannot be found in the composition, the entire
|
Note that if the service name cannot be found in the composition, the entire
|
||||||
left hand side of the = character will be treated as the variable name.
|
left hand side of the = character will be treated as the variable name.
|
||||||
|
|
||||||
|
#### --convert-eol, -l
|
||||||
|
|
||||||
|
Convert line endings from CRLF (Windows format) to LF (Unix format). Source files are not modified.
|
||||||
|
|
||||||
# Settings
|
# Settings
|
||||||
|
|
||||||
## settings
|
## settings
|
||||||
@ -1927,6 +1931,10 @@ Display full log output
|
|||||||
|
|
||||||
Path to a YAML or JSON file with passwords for a private Docker registry
|
Path to a YAML or JSON file with passwords for a private Docker registry
|
||||||
|
|
||||||
|
#### --convert-eol, -l
|
||||||
|
|
||||||
|
Convert line endings from CRLF (Windows format) to LF (Unix format). Source files are not modified.
|
||||||
|
|
||||||
#### --docker, -P <docker>
|
#### --docker, -P <docker>
|
||||||
|
|
||||||
Path to a local docker socket (e.g. /var/run/docker.sock)
|
Path to a local docker socket (e.g. /var/run/docker.sock)
|
||||||
@ -2052,6 +2060,10 @@ Display full log output
|
|||||||
|
|
||||||
Path to a YAML or JSON file with passwords for a private Docker registry
|
Path to a YAML or JSON file with passwords for a private Docker registry
|
||||||
|
|
||||||
|
#### --convert-eol, -l
|
||||||
|
|
||||||
|
Convert line endings from CRLF (Windows format) to LF (Unix format). Source files are not modified.
|
||||||
|
|
||||||
#### --docker, -P <docker>
|
#### --docker, -P <docker>
|
||||||
|
|
||||||
Path to a local docker socket (e.g. /var/run/docker.sock)
|
Path to a local docker socket (e.g. /var/run/docker.sock)
|
||||||
|
@ -41,8 +41,10 @@ buildProject = (docker, logger, composeOpts, opts) ->
|
|||||||
opts.buildEmulated
|
opts.buildEmulated
|
||||||
opts.buildOpts
|
opts.buildOpts
|
||||||
composeOpts.inlineLogs
|
composeOpts.inlineLogs
|
||||||
|
opts.convertEol
|
||||||
)
|
)
|
||||||
.then ->
|
.then ->
|
||||||
|
logger.outputDeferredMessages()
|
||||||
logger.logSuccess('Build succeeded!')
|
logger.logSuccess('Build succeeded!')
|
||||||
.tapCatch (e) ->
|
.tapCatch (e) ->
|
||||||
logger.logError('Build failed')
|
logger.logError('Build failed')
|
||||||
@ -117,6 +119,9 @@ module.exports =
|
|||||||
options.source ?= params.source
|
options.source ?= params.source
|
||||||
delete params.source
|
delete params.source
|
||||||
|
|
||||||
|
options.convertEol = options['convert-eol'] || false
|
||||||
|
delete options['convert-eol']
|
||||||
|
|
||||||
Promise.resolve(validateComposeOptions(sdk, options))
|
Promise.resolve(validateComposeOptions(sdk, options))
|
||||||
.then ->
|
.then ->
|
||||||
{ application, arch, deviceType } = options
|
{ application, arch, deviceType } = options
|
||||||
@ -150,6 +155,7 @@ module.exports =
|
|||||||
deviceType
|
deviceType
|
||||||
buildEmulated: !!options.emulated
|
buildEmulated: !!options.emulated
|
||||||
buildOpts
|
buildOpts
|
||||||
|
convertEol: options.convertEol
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.asCallback(done)
|
.asCallback(done)
|
||||||
|
@ -4,6 +4,7 @@ Promise = require('bluebird')
|
|||||||
dockerUtils = require('../utils/docker')
|
dockerUtils = require('../utils/docker')
|
||||||
compose = require('../utils/compose')
|
compose = require('../utils/compose')
|
||||||
{ registrySecretsHelp } = require('../utils/messages')
|
{ registrySecretsHelp } = require('../utils/messages')
|
||||||
|
{ ExpectedError } = require('../errors')
|
||||||
|
|
||||||
###
|
###
|
||||||
Opts must be an object with the following keys:
|
Opts must be an object with the following keys:
|
||||||
@ -60,6 +61,7 @@ deployProject = (docker, logger, composeOpts, opts) ->
|
|||||||
opts.buildEmulated
|
opts.buildEmulated
|
||||||
opts.buildOpts
|
opts.buildOpts
|
||||||
composeOpts.inlineLogs
|
composeOpts.inlineLogs
|
||||||
|
opts.convertEol
|
||||||
)
|
)
|
||||||
.then (builtImages) ->
|
.then (builtImages) ->
|
||||||
_.keyBy(builtImages, 'serviceName')
|
_.keyBy(builtImages, 'serviceName')
|
||||||
@ -114,6 +116,7 @@ deployProject = (docker, logger, composeOpts, opts) ->
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
.then (release) ->
|
.then (release) ->
|
||||||
|
logger.outputDeferredMessages()
|
||||||
logger.logSuccess('Deploy succeeded!')
|
logger.logSuccess('Deploy succeeded!')
|
||||||
logger.logSuccess("Release: #{release.commit}")
|
logger.logSuccess("Release: #{release.commit}")
|
||||||
console.log()
|
console.log()
|
||||||
@ -175,7 +178,7 @@ module.exports =
|
|||||||
signature: 'nologupload'
|
signature: 'nologupload'
|
||||||
description: "Don't upload build logs to the dashboard with image (if building)"
|
description: "Don't upload build logs to the dashboard with image (if building)"
|
||||||
boolean: true
|
boolean: true
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
action: (params, options, done) ->
|
action: (params, options, done) ->
|
||||||
# compositions with many services trigger misleading warnings
|
# compositions with many services trigger misleading warnings
|
||||||
@ -196,6 +199,11 @@ module.exports =
|
|||||||
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 and not options.build
|
||||||
|
return done(new ExpectedError('The --eol-conversion flag is only valid with --build.'))
|
||||||
|
|
||||||
Promise.resolve(validateComposeOptions(sdk, options))
|
Promise.resolve(validateComposeOptions(sdk, options))
|
||||||
.then ->
|
.then ->
|
||||||
if not appName?
|
if not appName?
|
||||||
@ -213,9 +221,9 @@ module.exports =
|
|||||||
return app
|
return app
|
||||||
)
|
)
|
||||||
.then (app) ->
|
.then (app) ->
|
||||||
[ app, image, !!options.build, !options.nologupload ]
|
[ app, image, !!options.build, !options.nologupload]
|
||||||
|
|
||||||
.then ([ app, image, shouldPerformBuild, shouldUploadLogs ]) ->
|
.then ([ app, image, shouldPerformBuild, shouldUploadLogs, convertEol ]) ->
|
||||||
Promise.join(
|
Promise.join(
|
||||||
dockerUtils.getDocker(options)
|
dockerUtils.getDocker(options)
|
||||||
dockerUtils.generateBuildOpts(options)
|
dockerUtils.generateBuildOpts(options)
|
||||||
@ -229,6 +237,7 @@ module.exports =
|
|||||||
shouldUploadLogs
|
shouldUploadLogs
|
||||||
buildEmulated: !!options.emulated
|
buildEmulated: !!options.emulated
|
||||||
buildOpts
|
buildOpts
|
||||||
|
convertEol: options.convertEol
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.asCallback(done)
|
.asCallback(done)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2016-2019 Balena Ltd.
|
Copyright 2016-2020 Balena Ltd.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -113,6 +113,7 @@ export const push: CommandDefinition<
|
|||||||
service?: string | string[];
|
service?: string | string[];
|
||||||
system?: boolean;
|
system?: boolean;
|
||||||
env?: string | string[];
|
env?: string | string[];
|
||||||
|
'convert-eol'?: boolean;
|
||||||
}
|
}
|
||||||
> = {
|
> = {
|
||||||
signature: 'push <applicationOrDevice>',
|
signature: 'push <applicationOrDevice>',
|
||||||
@ -243,6 +244,13 @@ export const push: CommandDefinition<
|
|||||||
left hand side of the = character will be treated as the variable name.
|
left hand side of the = character will be treated as the variable name.
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
signature: 'convert-eol',
|
||||||
|
alias: 'l',
|
||||||
|
description: stripIndent`
|
||||||
|
Convert line endings from CRLF (Windows format) to LF (Unix format). Source files are not modified.`,
|
||||||
|
boolean: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
async action(params, options, done) {
|
async action(params, options, done) {
|
||||||
const sdk = (await import('balena-sdk')).fromSharedOptions();
|
const sdk = (await import('balena-sdk')).fromSharedOptions();
|
||||||
@ -317,6 +325,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,
|
||||||
};
|
};
|
||||||
const args = {
|
const args = {
|
||||||
app,
|
app,
|
||||||
@ -327,7 +336,6 @@ export const push: CommandDefinition<
|
|||||||
sdk,
|
sdk,
|
||||||
opts,
|
opts,
|
||||||
};
|
};
|
||||||
|
|
||||||
return await remote.startRemoteBuild(args);
|
return await remote.startRemoteBuild(args);
|
||||||
},
|
},
|
||||||
).nodeify(done);
|
).nodeify(done);
|
||||||
@ -356,6 +364,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,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.catch(BuildError, e => {
|
.catch(BuildError, e => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
###*
|
###*
|
||||||
# @license
|
# @license
|
||||||
# Copyright 2017-2019 Balena Ltd.
|
# Copyright 2017-2020 Balena Ltd.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -52,6 +52,12 @@ exports.appendOptions = (opts) ->
|
|||||||
parameter: 'secrets.yml|.json'
|
parameter: 'secrets.yml|.json'
|
||||||
description: 'Path to a YAML or JSON file with passwords for a private Docker registry'
|
description: 'Path to a YAML or JSON file with passwords for a private Docker registry'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
signature: 'convert-eol'
|
||||||
|
description: 'Convert line endings from CRLF (Windows format) to LF (Unix format). Source files are not modified.'
|
||||||
|
boolean: true
|
||||||
|
alias: 'l'
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
exports.generateOpts = (options) ->
|
exports.generateOpts = (options) ->
|
||||||
@ -131,7 +137,11 @@ exports.loadProject = (logger, projectPath, projectName, image, dockerfilePath)
|
|||||||
logger.logDebug('Creating project...')
|
logger.logDebug('Creating project...')
|
||||||
createProject(projectPath, composeStr, projectName)
|
createProject(projectPath, composeStr, projectName)
|
||||||
|
|
||||||
exports.tarDirectory = tarDirectory = (dir, preFinalizeCallback = null) ->
|
|
||||||
|
exports.tarDirectory = tarDirectory = (dir, { preFinalizeCallback, convertEol } = {}) ->
|
||||||
|
preFinalizeCallback ?= null
|
||||||
|
convertEol ?= false
|
||||||
|
|
||||||
tar = require('tar-stream')
|
tar = require('tar-stream')
|
||||||
klaw = require('klaw')
|
klaw = require('klaw')
|
||||||
path = require('path')
|
path = require('path')
|
||||||
@ -139,6 +149,7 @@ exports.tarDirectory = tarDirectory = (dir, preFinalizeCallback = null) ->
|
|||||||
streamToPromise = require('stream-to-promise')
|
streamToPromise = require('stream-to-promise')
|
||||||
{ FileIgnorer } = require('./ignore')
|
{ FileIgnorer } = require('./ignore')
|
||||||
{ toPosixPath } = require('resin-multibuild').PathUtils
|
{ toPosixPath } = require('resin-multibuild').PathUtils
|
||||||
|
{ readFileWithEolConversion } = require('./eol-conversion')
|
||||||
|
|
||||||
getFiles = ->
|
getFiles = ->
|
||||||
streamToPromise(klaw(dir))
|
streamToPromise(klaw(dir))
|
||||||
@ -155,7 +166,7 @@ exports.tarDirectory = tarDirectory = (dir, preFinalizeCallback = null) ->
|
|||||||
.filter(ignore.filter)
|
.filter(ignore.filter)
|
||||||
.map (file) ->
|
.map (file) ->
|
||||||
relPath = path.relative(path.resolve(dir), file)
|
relPath = path.relative(path.resolve(dir), file)
|
||||||
Promise.join relPath, fs.stat(file), fs.readFile(file),
|
Promise.join relPath, fs.stat(file), readFileWithEolConversion(file, convertEol),
|
||||||
(filename, stats, data) ->
|
(filename, stats, data) ->
|
||||||
pack.entry({ name: toPosixPath(filename), size: stats.size, mode: stats.mode }, data)
|
pack.entry({ name: toPosixPath(filename), size: stats.size, mode: stats.mode }, data)
|
||||||
.then ->
|
.then ->
|
||||||
@ -179,7 +190,8 @@ exports.buildProject = (
|
|||||||
projectPath, projectName, composition,
|
projectPath, projectName, composition,
|
||||||
arch, deviceType,
|
arch, deviceType,
|
||||||
emulated, buildOpts,
|
emulated, buildOpts,
|
||||||
inlineLogs
|
inlineLogs,
|
||||||
|
convertEol
|
||||||
) ->
|
) ->
|
||||||
_ = require('lodash')
|
_ = require('lodash')
|
||||||
humanize = require('humanize')
|
humanize = require('humanize')
|
||||||
@ -214,7 +226,7 @@ exports.buildProject = (
|
|||||||
return qemu.copyQemu(path.join(projectPath, d.image.context), arch)
|
return qemu.copyQemu(path.join(projectPath, d.image.context), arch)
|
||||||
.then (needsQemu) ->
|
.then (needsQemu) ->
|
||||||
# Tar up the directory, ready for the build stream
|
# Tar up the directory, ready for the build stream
|
||||||
tarDirectory(projectPath)
|
tarDirectory(projectPath, { convertEol })
|
||||||
.then (tarStream) ->
|
.then (tarStream) ->
|
||||||
Promise.resolve(makeBuildTasks(composition, tarStream, { arch, deviceType }, logger))
|
Promise.resolve(makeBuildTasks(composition, tarStream, { arch, deviceType }, logger))
|
||||||
.map (task) ->
|
.map (task) ->
|
||||||
|
9
lib/utils/compose.d.ts
vendored
9
lib/utils/compose.d.ts
vendored
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2018 Balena Ltd.
|
* Copyright 2018-2020 Balena Ltd.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -49,7 +49,12 @@ export function loadProject(
|
|||||||
dockerfilePath?: string,
|
dockerfilePath?: string,
|
||||||
): Bluebird<ComposeProject>;
|
): Bluebird<ComposeProject>;
|
||||||
|
|
||||||
|
interface TarDirectoryOptions {
|
||||||
|
preFinalizeCallback?: (pack: Pack) => void;
|
||||||
|
convertEol?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export function tarDirectory(
|
export function tarDirectory(
|
||||||
source: string,
|
source: string,
|
||||||
preFinalizeCallback?: (pack: Pack) => void,
|
options?: TarDirectoryOptions,
|
||||||
): Promise<Stream.Readable>;
|
): Promise<Stream.Readable>;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2018 Balena Ltd.
|
* Copyright 2018-2020 Balena Ltd.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -54,6 +54,7 @@ export interface DeviceDeployOptions {
|
|||||||
services?: string[];
|
services?: string[];
|
||||||
system: boolean;
|
system: boolean;
|
||||||
env: string[];
|
env: string[];
|
||||||
|
convertEol: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ParsedEnvironment {
|
interface ParsedEnvironment {
|
||||||
@ -186,7 +187,9 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
|
|||||||
|
|
||||||
await checkBuildSecretsRequirements(docker, opts.source);
|
await checkBuildSecretsRequirements(docker, opts.source);
|
||||||
globalLogger.logDebug('Tarring all non-ignored files...');
|
globalLogger.logDebug('Tarring all non-ignored files...');
|
||||||
const tarStream = await tarDirectory(opts.source);
|
const tarStream = await tarDirectory(opts.source, {
|
||||||
|
convertEol: opts.convertEol,
|
||||||
|
});
|
||||||
|
|
||||||
// Try to detect the device information
|
// Try to detect the device information
|
||||||
const deviceInfo = await api.getDeviceInformation();
|
const deviceInfo = await api.getDeviceInformation();
|
||||||
@ -261,6 +264,7 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
globalLogger.logLivepush('Watching for file changes...');
|
globalLogger.logLivepush('Watching for file changes...');
|
||||||
|
globalLogger.outputDeferredMessages();
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
} else {
|
} else {
|
||||||
if (opts.detached) {
|
if (opts.detached) {
|
||||||
@ -272,6 +276,7 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
|
|||||||
// Now all we need to do is stream back the logs
|
// Now all we need to do is stream back the logs
|
||||||
const logStream = await api.getLogStream();
|
const logStream = await api.getLogStream();
|
||||||
globalLogger.logInfo('Streaming device logs...');
|
globalLogger.logInfo('Streaming device logs...');
|
||||||
|
globalLogger.outputDeferredMessages();
|
||||||
await displayDeviceLogs(
|
await displayDeviceLogs(
|
||||||
logStream,
|
logStream,
|
||||||
globalLogger,
|
globalLogger,
|
||||||
|
139
lib/utils/eol-conversion.ts
Normal file
139
lib/utils/eol-conversion.ts
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2019-2020 Balena Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import mmmagic = require('mmmagic');
|
||||||
|
import fs = require('mz/fs');
|
||||||
|
import Logger = require('./logger');
|
||||||
|
|
||||||
|
const globalLogger = Logger.getLogger();
|
||||||
|
|
||||||
|
// Define file size threshold (bytes) over which analysis/conversion is not performed.
|
||||||
|
const LARGE_FILE_THRESHOLD = 10 * 1000 * 1000;
|
||||||
|
|
||||||
|
// The list of encodings to convert is intentionally conservative for now
|
||||||
|
const CONVERTIBLE_ENCODINGS = ['ascii', 'utf-8'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to detect the encoding of a data buffer
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
async function detectEncoding(data: Buffer): Promise<string> {
|
||||||
|
// Instantiate mmmagic for mime encoding analysis
|
||||||
|
const magic = new mmmagic.Magic(mmmagic.MAGIC_MIME_ENCODING);
|
||||||
|
|
||||||
|
// Promisify magic.detect
|
||||||
|
// For some reason, got 'Illegal Invocation' when using:
|
||||||
|
// const detectEncoding = promisify(magic.detect);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
magic.detect(data, (err, encoding) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
// mmmagic reports ascii as 'us-ascii', but node Buffer uses 'ascii'
|
||||||
|
encoding = encoding === 'us-ascii' ? 'ascii' : encoding;
|
||||||
|
return resolve(encoding);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert EOL (CRLF → LF) in place, i.e. modifying the input buffer.
|
||||||
|
* Safe for UTF-8, ASCII and 8-bit encodings (like 'latin-1', 'iso-8859-1', ...),
|
||||||
|
* but not safe for UTF-16 or UTF-32.
|
||||||
|
* Return a new buffer object sharing the same contents memory space as the
|
||||||
|
* input buffer (using Buffer.slice()), in order to safely reflect the new
|
||||||
|
* buffer size.
|
||||||
|
* @param buf
|
||||||
|
*/
|
||||||
|
function convertEolInPlace(buf: Buffer): Buffer {
|
||||||
|
const CR = 13;
|
||||||
|
const LF = 10;
|
||||||
|
let foundCR = false;
|
||||||
|
let j;
|
||||||
|
// Algorithm gist:
|
||||||
|
// - i and j are running indexes over the same buffer, but think of it as
|
||||||
|
// i pointing to the input buffer, and j pointing to the output buffer.
|
||||||
|
// - i and j are incremented by 1 in every loop iteration, but if a LF is found
|
||||||
|
// after a CR, then j is decremented by 1, and LF is written. Invariant: j <= i.
|
||||||
|
for (let i = (j = 0); i < buf.length; i++, j++) {
|
||||||
|
const b = (buf[j] = buf[i]);
|
||||||
|
if (b === CR) {
|
||||||
|
foundCR = true;
|
||||||
|
} else {
|
||||||
|
if (foundCR && b === LF) {
|
||||||
|
j--; // decrement index of "output buffer"
|
||||||
|
buf[j] = LF; // overwrite previous CR with LF
|
||||||
|
}
|
||||||
|
foundCR = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.slice(0, j);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop-in replacement for promisified fs.readFile(<string>)
|
||||||
|
* Attempts to convert EOLs from CRLF to LF for supported encodings,
|
||||||
|
* or otherwise logs warnings.
|
||||||
|
* @param filepath
|
||||||
|
* @param convertEol When true, performs conversions, otherwise just warns.
|
||||||
|
*/
|
||||||
|
export async function readFileWithEolConversion(
|
||||||
|
filepath: string,
|
||||||
|
convertEol: boolean,
|
||||||
|
): Promise<Buffer> {
|
||||||
|
const fileBuffer = await fs.readFile(filepath);
|
||||||
|
|
||||||
|
// Skip processing of very large files
|
||||||
|
const fileStats = await fs.stat(filepath);
|
||||||
|
if (fileStats.size > LARGE_FILE_THRESHOLD) {
|
||||||
|
globalLogger.logWarn(`CRLF detection skipped for large file: ${filepath}`);
|
||||||
|
return fileBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analyse encoding
|
||||||
|
const encoding = await detectEncoding(fileBuffer);
|
||||||
|
|
||||||
|
// Skip further processing of non-convertible encodings
|
||||||
|
if (!CONVERTIBLE_ENCODINGS.includes(encoding)) {
|
||||||
|
return fileBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip further processing of files that don't contain CRLF
|
||||||
|
if (!fileBuffer.includes('\r\n', 0, encoding)) {
|
||||||
|
return fileBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (convertEol) {
|
||||||
|
// Convert CRLF->LF
|
||||||
|
globalLogger.logInfo(
|
||||||
|
`Converting line endings CRLF -> LF for file: ${filepath}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return convertEolInPlace(fileBuffer);
|
||||||
|
} else {
|
||||||
|
// Immediate warning
|
||||||
|
globalLogger.logWarn(
|
||||||
|
`CRLF (Windows) line endings detected in file: ${filepath}`,
|
||||||
|
);
|
||||||
|
// And summary warning later
|
||||||
|
globalLogger.deferredLog(
|
||||||
|
'Windows-format line endings were detected in some files. Consider using the `--convert-eol` option.',
|
||||||
|
Logger.Level.WARN,
|
||||||
|
);
|
||||||
|
|
||||||
|
return fileBuffer;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2016-2019 Balena
|
Copyright 2016-2020 Balena
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -354,7 +354,7 @@ function windowsCmdExeEscapeArg(arg: string): string {
|
|||||||
return `"${arg.replace(/["]/g, '""')}"`;
|
return `"${arg.replace(/["]/g, '""')}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Workaround a window system bug which causes multiple rapid DNS lookups
|
* Workaround a window system bug which causes multiple rapid DNS lookups
|
||||||
* to fail for mDNS.
|
* to fail for mDNS.
|
||||||
*
|
*
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2016-2018 Balena Ltd.
|
Copyright 2016-2020 Balena Ltd.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -23,10 +23,13 @@ import * as Stream from 'stream';
|
|||||||
import streamToPromise = require('stream-to-promise');
|
import streamToPromise = require('stream-to-promise');
|
||||||
import { Pack } from 'tar-stream';
|
import { Pack } from 'tar-stream';
|
||||||
import { TypedError } from 'typed-error';
|
import { TypedError } from 'typed-error';
|
||||||
|
import Logger = require('./logger');
|
||||||
|
|
||||||
import { exitWithExpectedError } from '../utils/patterns';
|
import { exitWithExpectedError } from '../utils/patterns';
|
||||||
import { tarDirectory } from './compose';
|
import { tarDirectory } from './compose';
|
||||||
|
|
||||||
|
const globalLogger = Logger.getLogger();
|
||||||
|
|
||||||
const DEBUG_MODE = !!process.env.DEBUG;
|
const DEBUG_MODE = !!process.env.DEBUG;
|
||||||
|
|
||||||
const CURSOR_METADATA_REGEX = /([a-z]+)([0-9]+)?/;
|
const CURSOR_METADATA_REGEX = /([a-z]+)([0-9]+)?/;
|
||||||
@ -38,6 +41,7 @@ export interface BuildOpts {
|
|||||||
nocache: boolean;
|
nocache: boolean;
|
||||||
registrySecrets: RegistrySecrets;
|
registrySecrets: RegistrySecrets;
|
||||||
headless: boolean;
|
headless: boolean;
|
||||||
|
convertEol: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RemoteBuild {
|
export interface RemoteBuild {
|
||||||
@ -136,6 +140,7 @@ export async function startRemoteBuild(build: RemoteBuild): Promise<void> {
|
|||||||
if (build.hadError) {
|
if (build.hadError) {
|
||||||
throw new RemoteBuildFailedError();
|
throw new RemoteBuildFailedError();
|
||||||
}
|
}
|
||||||
|
globalLogger.outputDeferredMessages();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,12 +294,14 @@ async function getTarStream(build: RemoteBuild): Promise<Stream.Readable> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
tarSpinner.start();
|
tarSpinner.start();
|
||||||
return await tarDirectory(
|
const preFinalizeCb =
|
||||||
path.resolve(build.source),
|
|
||||||
Object.keys(build.opts.registrySecrets).length > 0
|
Object.keys(build.opts.registrySecrets).length > 0
|
||||||
? preFinalizeCallback
|
? preFinalizeCallback
|
||||||
: undefined,
|
: undefined;
|
||||||
);
|
return await tarDirectory(path.resolve(build.source), {
|
||||||
|
preFinalizeCallback: preFinalizeCb,
|
||||||
|
convertEol: build.opts.convertEol,
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
tarSpinner.stop();
|
tarSpinner.stop();
|
||||||
}
|
}
|
||||||
|
17
npm-shrinkwrap.json
generated
17
npm-shrinkwrap.json
generated
@ -775,6 +775,15 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/mmmagic": {
|
||||||
|
"version": "0.4.16-alpha",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/mmmagic/-/mmmagic-0.4.16-alpha.tgz",
|
||||||
|
"integrity": "sha1-zM66vnBpBmPWRaMdTLzxzZ3+UIE=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/mocha": {
|
"@types/mocha": {
|
||||||
"version": "5.2.7",
|
"version": "5.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz",
|
||||||
@ -8926,6 +8935,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"mmmagic": {
|
||||||
|
"version": "0.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/mmmagic/-/mmmagic-0.5.3.tgz",
|
||||||
|
"integrity": "sha512-xLqCu7GJYTzJczg0jafXFuh+iPzQL/ru0YYf4GiTTz8Cehru/wiXtUS8Pp8Xi77zNaiVndJ0OO1yAFci6iHyFg==",
|
||||||
|
"requires": {
|
||||||
|
"nan": "^2.13.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"mocha": {
|
"mocha": {
|
||||||
"version": "6.2.2",
|
"version": "6.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.2.tgz",
|
||||||
|
@ -109,6 +109,7 @@
|
|||||||
"@types/lodash": "4.14.112",
|
"@types/lodash": "4.14.112",
|
||||||
"@types/mixpanel": "2.14.0",
|
"@types/mixpanel": "2.14.0",
|
||||||
"@types/mkdirp": "0.5.2",
|
"@types/mkdirp": "0.5.2",
|
||||||
|
"@types/mmmagic": "0.4.16-alpha",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^5.2.7",
|
||||||
"@types/mz": "0.0.32",
|
"@types/mz": "0.0.32",
|
||||||
"@types/net-keepalive": "^0.4.0",
|
"@types/net-keepalive": "^0.4.0",
|
||||||
@ -203,6 +204,7 @@
|
|||||||
"minimatch": "^3.0.4",
|
"minimatch": "^3.0.4",
|
||||||
"mixpanel": "^0.10.3",
|
"mixpanel": "^0.10.3",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
|
"mmmagic": "^0.5.3",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"moment-duration-format": "^2.3.2",
|
"moment-duration-format": "^2.3.2",
|
||||||
"mz": "^2.7.0",
|
"mz": "^2.7.0",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user