From bce2479bc13e173bc50d4f842a7dbe9fbb645f42 Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Thu, 26 May 2022 17:39:13 +0200 Subject: [PATCH 1/5] simplify getFileCommitDate --- packages/docusaurus-utils/src/gitUtils.ts | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/docusaurus-utils/src/gitUtils.ts b/packages/docusaurus-utils/src/gitUtils.ts index 4d7ffde633ee..8d5687f546ab 100644 --- a/packages/docusaurus-utils/src/gitUtils.ts +++ b/packages/docusaurus-utils/src/gitUtils.ts @@ -97,20 +97,16 @@ export function getFileCommitDate( ); } - let formatArg = '--format=%ct'; - if (includeAuthor) { - formatArg += ',%an'; - } + const formatArg = `--format=%ct${includeAuthor ? ',%an' : ''}`; - let extraArgs = '--max-count=1'; - if (age === 'oldest') { - // --follow is necessary to follow file renames - // --diff-filter=A ensures we only get the commit which (A)dded the file - extraArgs += ' --follow --diff-filter=A'; - } + const countArgs = '--max-count=1'; + + const oldestArgs = age === 'oldest' ? '--follow --diff-filter=A' : undefined; + + const args = [formatArg, countArgs, oldestArgs].filter(Boolean).join(' '); const result = shell.exec( - `git log ${extraArgs} ${formatArg} -- "${path.basename(file)}"`, + `git log ${args} ${formatArg} -- "${path.basename(file)}"`, { // Setting cwd is important, see: https://github.com/facebook/docusaurus/pull/5048 cwd: path.dirname(file), From f6345de125126d178b5da6917a6d94e49660bd36 Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Thu, 26 May 2022 17:41:01 +0200 Subject: [PATCH 2/5] simplify getFileCommitDate --- packages/docusaurus-utils/src/gitUtils.ts | 29 ++++++++++------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/packages/docusaurus-utils/src/gitUtils.ts b/packages/docusaurus-utils/src/gitUtils.ts index 8d5687f546ab..00e15658c9a4 100644 --- a/packages/docusaurus-utils/src/gitUtils.ts +++ b/packages/docusaurus-utils/src/gitUtils.ts @@ -97,22 +97,19 @@ export function getFileCommitDate( ); } - const formatArg = `--format=%ct${includeAuthor ? ',%an' : ''}`; - - const countArgs = '--max-count=1'; - - const oldestArgs = age === 'oldest' ? '--follow --diff-filter=A' : undefined; - - const args = [formatArg, countArgs, oldestArgs].filter(Boolean).join(' '); - - const result = shell.exec( - `git log ${args} ${formatArg} -- "${path.basename(file)}"`, - { - // Setting cwd is important, see: https://github.com/facebook/docusaurus/pull/5048 - cwd: path.dirname(file), - silent: true, - }, - ); + const args = [ + `--format=%ct${includeAuthor ? ',%an' : ''}`, + '--max-count=1', + age === 'oldest' ? '--follow --diff-filter=A' : undefined, + ] + .filter(Boolean) + .join(' '); + + const result = shell.exec(`git log ${args} -- "${path.basename(file)}"`, { + // Setting cwd is important, see: https://github.com/facebook/docusaurus/pull/5048 + cwd: path.dirname(file), + silent: true, + }); if (result.code !== 0) { throw new Error( `Failed to retrieve the git history for file "${file}" with exit code ${result.code}: ${result.stderr}`, From aac5f29f8e2303086a950ed03401ead8f460b465 Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Thu, 26 May 2022 18:03:57 +0200 Subject: [PATCH 3/5] add shellUtils --- .../src/__tests__/shellUtils.test.ts | 19 +++++++++++++++ packages/docusaurus-utils/src/index.ts | 1 + packages/docusaurus-utils/src/shellUtils.ts | 24 +++++++++++++++++++ project-words.txt | 2 ++ 4 files changed, 46 insertions(+) create mode 100644 packages/docusaurus-utils/src/__tests__/shellUtils.test.ts create mode 100644 packages/docusaurus-utils/src/shellUtils.ts diff --git a/packages/docusaurus-utils/src/__tests__/shellUtils.test.ts b/packages/docusaurus-utils/src/__tests__/shellUtils.test.ts new file mode 100644 index 000000000000..3503bffc868c --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/shellUtils.test.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {escapeShellArg} from '../shellUtils'; + +describe('shellUtils', () => { + it('escapeShellArg', () => { + expect(escapeShellArg('hello')).toBe('hello'); + expect(escapeShellArg('*')).toBe('"*"'); + expect(escapeShellArg('hello world')).toBe('"hello world"'); + expect(escapeShellArg("'hello'")).toBe('"\'hello\'"'); + expect(escapeShellArg('$(pwd)')).toBe('"$(pwd)"'); + expect(escapeShellArg('hello$(pwd)')).toBe('"hello$(pwd)"'); + }); +}); diff --git a/packages/docusaurus-utils/src/index.ts b/packages/docusaurus-utils/src/index.ts index fb2c66435ea2..520f5f73aab2 100644 --- a/packages/docusaurus-utils/src/index.ts +++ b/packages/docusaurus-utils/src/index.ts @@ -96,6 +96,7 @@ export { createAbsoluteFilePathMatcher, } from './globUtils'; export {getFileLoaderUtils} from './webpackUtils'; +export {escapeShellArg} from './shellUtils'; export { getDataFilePath, getDataFileData, diff --git a/packages/docusaurus-utils/src/shellUtils.ts b/packages/docusaurus-utils/src/shellUtils.ts new file mode 100644 index 000000000000..ca615134ac55 --- /dev/null +++ b/packages/docusaurus-utils/src/shellUtils.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// TODO move from shelljs to execa later? +// Execa is well maintained and widely used +// Even shelljs recommends execa for security / escaping: +// https://github.com/shelljs/shelljs/wiki/Security-guidelines + +const NO_ESCAPE_REGEXP = /^[\w.-]+$/; +const DOUBLE_QUOTES_REGEXP = /"/g; + +// Inspired from Execa escaping function +// https://github.com/sindresorhus/execa/blob/main/lib/command.js#L12 +export function escapeShellArg(arg: string): string { + if (NO_ESCAPE_REGEXP.test(arg)) { + return arg; + } + + return `"${arg.replace(DOUBLE_QUOTES_REGEXP, '\\"')}"`; +} diff --git a/project-words.txt b/project-words.txt index 2e029e7f7c3f..9ca37195ee29 100644 --- a/project-words.txt +++ b/project-words.txt @@ -87,6 +87,8 @@ esbuild eslintcache estree evaluable +execa +Execa externalwaiting failfast fbid From 1f574b6f4c0e5cccf59564ea134e857473298873 Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Thu, 26 May 2022 18:43:06 +0200 Subject: [PATCH 4/5] fix shell escape --- .../src/__tests__/shellUtils.test.ts | 12 ++++++------ packages/docusaurus-utils/src/shellUtils.ts | 16 +++++----------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/packages/docusaurus-utils/src/__tests__/shellUtils.test.ts b/packages/docusaurus-utils/src/__tests__/shellUtils.test.ts index 3503bffc868c..adc64d944350 100644 --- a/packages/docusaurus-utils/src/__tests__/shellUtils.test.ts +++ b/packages/docusaurus-utils/src/__tests__/shellUtils.test.ts @@ -9,11 +9,11 @@ import {escapeShellArg} from '../shellUtils'; describe('shellUtils', () => { it('escapeShellArg', () => { - expect(escapeShellArg('hello')).toBe('hello'); - expect(escapeShellArg('*')).toBe('"*"'); - expect(escapeShellArg('hello world')).toBe('"hello world"'); - expect(escapeShellArg("'hello'")).toBe('"\'hello\'"'); - expect(escapeShellArg('$(pwd)')).toBe('"$(pwd)"'); - expect(escapeShellArg('hello$(pwd)')).toBe('"hello$(pwd)"'); + expect(escapeShellArg('hello')).toBe("'hello'"); + expect(escapeShellArg('*')).toBe("'*'"); + expect(escapeShellArg('hello world')).toBe("'hello world'"); + expect(escapeShellArg("'hello'")).toBe("\\''hello'\\'"); + expect(escapeShellArg('$(pwd)')).toBe("'$(pwd)'"); + expect(escapeShellArg('hello$(pwd)')).toBe("'hello$(pwd)'"); }); }); diff --git a/packages/docusaurus-utils/src/shellUtils.ts b/packages/docusaurus-utils/src/shellUtils.ts index ca615134ac55..25536a3c1fed 100644 --- a/packages/docusaurus-utils/src/shellUtils.ts +++ b/packages/docusaurus-utils/src/shellUtils.ts @@ -10,15 +10,9 @@ // Even shelljs recommends execa for security / escaping: // https://github.com/shelljs/shelljs/wiki/Security-guidelines -const NO_ESCAPE_REGEXP = /^[\w.-]+$/; -const DOUBLE_QUOTES_REGEXP = /"/g; - -// Inspired from Execa escaping function -// https://github.com/sindresorhus/execa/blob/main/lib/command.js#L12 -export function escapeShellArg(arg: string): string { - if (NO_ESCAPE_REGEXP.test(arg)) { - return arg; - } - - return `"${arg.replace(DOUBLE_QUOTES_REGEXP, '\\"')}"`; +// Inspired by https://github.com/xxorax/node-shell-escape/blob/master/shell-escape.js +export function escapeShellArg(s: string): string { + let res = `'${s.replace(/'/g, "'\\''")}'`; + res = res.replace(/^(?:'')+/g, '').replace(/\\'''/g, "\\'"); + return res; } From 1cc0b486ad91a4abe795fc3fd16defbe66faaa0e Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Thu, 26 May 2022 18:50:13 +0200 Subject: [PATCH 5/5] escape shell exec command args --- packages/create-docusaurus/package.json | 1 + packages/create-docusaurus/src/index.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/create-docusaurus/package.json b/packages/create-docusaurus/package.json index 46ae2253e1b4..f4ca79dcd026 100755 --- a/packages/create-docusaurus/package.json +++ b/packages/create-docusaurus/package.json @@ -23,6 +23,7 @@ "license": "MIT", "dependencies": { "@docusaurus/logger": "2.0.0-beta.20", + "@docusaurus/utils": "2.0.0-beta.20", "commander": "^5.1.0", "fs-extra": "^10.1.0", "lodash": "^4.17.21", diff --git a/packages/create-docusaurus/src/index.ts b/packages/create-docusaurus/src/index.ts index 27d53f50fd59..b996c3e90b6b 100755 --- a/packages/create-docusaurus/src/index.ts +++ b/packages/create-docusaurus/src/index.ts @@ -13,6 +13,7 @@ import logger from '@docusaurus/logger'; import shell from 'shelljs'; import prompts, {type Choice} from 'prompts'; import supportsColor from 'supports-color'; +import {escapeShellArg} from '@docusaurus/utils'; type CLIOptions = { packageManager?: PackageManager; @@ -463,9 +464,11 @@ export default async function init( logger.info('Creating new Docusaurus project...'); if (source.type === 'git') { - logger.info`Cloning Git template url=${source.url}...`; - const command = await getGitCommand(source.strategy); - if (shell.exec(`${command} ${source.url} ${dest}`).code !== 0) { + const gitCommand = await getGitCommand(source.strategy); + const gitCloneCommand = `${gitCommand} ${escapeShellArg( + source.url, + )} ${escapeShellArg(dest)}`; + if (shell.exec(gitCloneCommand).code !== 0) { logger.error`Cloning Git template failed!`; process.exit(1); }