-
-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' of https://github.com/ghiscoding/slickgrid-univ…
- Loading branch information
Showing
9 changed files
with
948 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import conventionalChangelog from 'conventional-changelog'; | ||
import { existsSync, readFileSync, writeFileSync } from 'node:fs'; | ||
import path from 'node:path'; | ||
|
||
const projectRootLocation = process.cwd(); | ||
const EOL = '\n'; | ||
const BLANK_LINE = EOL + EOL; | ||
const CHANGELOG_HEADER = [ | ||
'# Change Log', | ||
'All notable changes to this project will be documented in this file.', | ||
'See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.' | ||
].join(EOL); | ||
|
||
/** | ||
* Insert/Update "CHANGELOG.md" with conventional commits since last tagged version | ||
* @param { { infile: String, preset: String, tagPrefix: String } } args | ||
* @param {String} newVersion | ||
* @returns | ||
*/ | ||
export function updateChangelog(args, newVersion) { | ||
const default_args = { preset: 'angular' }; | ||
args = Object.assign({}, default_args, args); | ||
const { infile, preset, tagPrefix } = args; | ||
|
||
return new Promise((resolve, reject) => { | ||
let content = ''; | ||
let oldContent = ''; | ||
|
||
// read changelog.md if it exist or else we'll create it | ||
const changelogLocation = path.resolve(projectRootLocation, infile); | ||
const fileExist = existsSync(changelogLocation); | ||
if (fileExist) { | ||
oldContent = readFileSync(path.resolve(projectRootLocation, infile), 'utf8'); | ||
} | ||
|
||
// find the position of the last release and remove header since we'll append it back on top | ||
let oldContentWithoutHeader = oldContent; | ||
if (oldContent.includes(CHANGELOG_HEADER)) { | ||
oldContentWithoutHeader = oldContent.substring(CHANGELOG_HEADER.length); | ||
} | ||
|
||
const context = { version: newVersion }; | ||
const changelogStream = conventionalChangelog( | ||
{ preset, tagPrefix }, | ||
context, | ||
{ merges: null, path: args.path } | ||
).on('error', (err) => { | ||
return reject(err); | ||
}); | ||
|
||
changelogStream.on('data', (buffer) => { | ||
content += buffer.toString(); | ||
}); | ||
|
||
changelogStream.on('end', () => { | ||
const newContent = [CHANGELOG_HEADER, content, oldContentWithoutHeader] | ||
.join(BLANK_LINE) | ||
.trim() | ||
.replace(/[\r\n]{2,}/gm, '\n\n'); // conventional-changelog adds way too many extra line breaks, let's remove a few of them | ||
|
||
writeFileSync(changelogLocation, newContent, 'utf8'); | ||
|
||
return resolve({ location: changelogLocation, newEntry: content }); | ||
}); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
import os from 'node:os'; | ||
import logTransformer from 'strong-log-transformer'; | ||
import { x } from 'tinyexec'; | ||
import c from 'tinyrainbow'; | ||
|
||
// bookkeeping for spawned processes | ||
const children = new Set(); | ||
|
||
/** | ||
* Execute a command asynchronously, piping stdio by default. | ||
* @param {String} command - shell command | ||
* @param {String[]} execArgs - shell command arguments | ||
* @param {import("tinyexec").Options} [opts] - tinyexec node options | ||
* @param {Boolean} [cmdDryRun] | ||
*/ | ||
export function execAsyncPiped(command, execArgs, execOpts, cmdDryRun) { | ||
const options = { | ||
nodeOptions: { | ||
...execOpts, | ||
stdio: ['pipe'], | ||
} | ||
}; | ||
const spawned = spawnProcess(command, execArgs, options, cmdDryRun); | ||
|
||
return cmdDryRun ? Promise.resolve() : wrapError(spawned); | ||
} | ||
|
||
/** | ||
* Execute a command synchronously. | ||
* @param {String} command - shell command | ||
* @param {String[]} args - shell command arguments | ||
* @param {import("tinyexec").Options} [opts] - tinyexec options | ||
* @param {Boolean} [cmdDryRun] - dry-run flag | ||
*/ | ||
export async function execAsync(command, args, opts, cmdDryRun = false) { | ||
return cmdDryRun | ||
? logExecDryRunCommand(command, args) | ||
: (await x('git', args, opts)).stdout.trim(); | ||
} | ||
|
||
/** | ||
* Log the exec command without actually executing the actual command | ||
* @param {String} command - shell command | ||
* @param {String[]} args - shell command arguments | ||
* @returns {String} output | ||
*/ | ||
export function logExecDryRunCommand(command, args) { | ||
const argStr = (Array.isArray(args) ? args.join(' ') : args) ?? ''; | ||
|
||
const cmdList = []; | ||
for (const cmd of [command, argStr]) { | ||
cmdList.push(Array.isArray(cmd) ? cmd.join(' ') : cmd); | ||
} | ||
|
||
console.info(c.magenta(c.bold('[dry-run] >')), cmdList.join(' ')); | ||
return ''; | ||
} | ||
|
||
/** | ||
* @param {String} command - shell command | ||
* @param {String[]} args - shell command arguments | ||
* @param {import("tinyexec").Options} execOpts - tinyexec options | ||
* @returns {Promise<any>} | ||
*/ | ||
export async function spawnProcess( | ||
command, | ||
args, | ||
execOpts, | ||
cmdDryRun = false | ||
) { | ||
if (cmdDryRun) { | ||
return logExecDryRunCommand(command, args); | ||
} | ||
const child = x(command, args, execOpts); | ||
const drain = (_code, signal) => { | ||
children.delete(child); | ||
|
||
// don't run repeatedly if this is the error event | ||
if (signal === undefined) { | ||
child.process.removeListener('exit', drain); | ||
} | ||
}; | ||
|
||
child.process.once('exit', drain); | ||
child.process.once('error', drain); | ||
children.add(child); | ||
|
||
return child; | ||
} | ||
|
||
/** | ||
* Spawn a command asynchronously, streaming stdio with optional prefix. | ||
* @param {String} command | ||
* @param {String[]} args | ||
* @param {import("tinyexec").Options} [opts] | ||
* @param {String} [prefix] | ||
* @param {Boolean} [cmdDryRun=false] | ||
*/ | ||
// istanbul ignore next | ||
export function spawnStreaming( | ||
command, | ||
args, | ||
opts, | ||
prefix, | ||
cmdDryRun = false | ||
) { | ||
const options = { | ||
...opts, | ||
nodeOptions: { | ||
stdio: ['ignore', 'pipe'], | ||
} | ||
}; | ||
|
||
if (cmdDryRun) { | ||
return logExecDryRunCommand(command, args); | ||
} | ||
const spawned = spawnProcess(command, args, options, cmdDryRun); | ||
|
||
const stdoutOpts = {}; | ||
const stderrOpts = {}; // mergeMultiline causes escaped newlines :P | ||
|
||
if (prefix) { | ||
const color = c['magenta']; | ||
stdoutOpts.tag = `${color.bold(prefix)}:`; | ||
stderrOpts.tag = `${color(prefix)}:`; | ||
} | ||
|
||
// Avoid 'Possible EventEmitter memory leak detected' warning due to piped stdio | ||
if (children.size > process.stdout.listenerCount('close')) { | ||
process.stdout.setMaxListeners(children.size); | ||
process.stderr.setMaxListeners(children.size); | ||
} | ||
|
||
spawned.stdout?.pipe(logTransformer(stdoutOpts)).pipe(process.stdout); | ||
spawned.stderr?.pipe(logTransformer(stderrOpts)).pipe(process.stderr); | ||
|
||
return wrapError(spawned); | ||
} | ||
|
||
// -- | ||
// private functions | ||
|
||
/** | ||
* Return the exitCode when possible or else throw an error | ||
* @param {*} result | ||
* @returns | ||
*/ | ||
function getExitCode(result) { | ||
// https://nodejs.org/docs/latest-v6.x/api/child_process.html#child_process_event_close | ||
if (typeof result.code === 'number' || typeof result.exitCode === 'number') { | ||
return result.code ?? result.exitCode; | ||
} | ||
|
||
// https://nodejs.org/docs/latest-v6.x/api/errors.html#errors_error_code | ||
// istanbul ignore else | ||
if (typeof result.code === 'string' || typeof result.exitCode === 'string') { | ||
return os.constants.errno[result.code ?? result.exitCode]; | ||
} | ||
|
||
// istanbul ignore next: extremely weird | ||
throw new Error(`Received unexpected exit code value ${JSON.stringify(result.code ?? result.exitCode)}`); | ||
} | ||
|
||
/** | ||
* Spawn a command asynchronously, _always_ inheriting stdio. | ||
* @param {import("tinyexec").Output} spawned | ||
*/ | ||
function wrapError(spawned) { | ||
return spawned.catch((err) => { | ||
// ensure exit code is always a number | ||
err.exitCode = getExitCode(err); | ||
|
||
console.error('SPAWN PROCESS ERROR'); | ||
throw err; | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/** | ||
* Copy some fs-extra util implementations | ||
* https://github.com/jprichardson/node-fs-extra | ||
*/ | ||
|
||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; | ||
import { dirname } from 'node:path'; | ||
|
||
export function outputFileSync(file, ...args) { | ||
const dir = dirname(file); | ||
if (!existsSync(dir)) { | ||
mkdirSync(dir, { recursive: true }); | ||
} | ||
|
||
writeFileSync(file, ...args); | ||
} | ||
|
||
export function readJSONSync(file, options = {}) { | ||
if (typeof options === 'string') { | ||
options = { encoding: options }; | ||
} | ||
|
||
const shouldThrow = 'throws' in options ? options.throws : true; | ||
|
||
try { | ||
let content = readFileSync(file, options); | ||
content = stripBom(content); | ||
return JSON.parse(content, options.reviver); | ||
} catch (err) { | ||
if (shouldThrow) { | ||
err.message = `${file}: ${err.message}`; | ||
throw err; | ||
} else { | ||
return null; | ||
} | ||
} | ||
} | ||
|
||
export function stringify(obj, { EOL = '\n', finalEOL = true, replacer = null, spaces } = {}) { | ||
const EOF = finalEOL ? EOL : ''; | ||
const str = JSON.stringify(obj, replacer, spaces); | ||
|
||
return str.replace(/\n/g, EOL) + EOF; | ||
} | ||
|
||
export function stripBom(content) { | ||
// we do this because JSON.parse would convert it to a utf8 string if encoding wasn't specified | ||
if (Buffer.isBuffer(content)) { | ||
content = content.toString('utf8'); | ||
} | ||
return content.replace(/^\uFEFF/, ''); | ||
} | ||
|
||
export function writeJsonSync(file, obj, options = {}) { | ||
const str = stringify(obj, options); | ||
// not sure if fs.writeFileSync returns anything, but just in case | ||
return writeFileSync(file, str, options); | ||
} |
Oops, something went wrong.