Skip to content

Commit f92858b

Browse files
authored
Merge pull request #443 from marp-team/recursive-mkdirp-to-the-root
Prevent recursive mkdir to the root path while saving
2 parents c93f562 + 633352a commit f92858b

File tree

11 files changed

+89
-55
lines changed

11 files changed

+89
-55
lines changed

.eslintrc.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ module.exports = {
3131
'@typescript-eslint/no-explicit-any': 'off',
3232
'@typescript-eslint/explicit-function-return-type': 'off',
3333
'@typescript-eslint/explicit-module-boundary-types': 'off',
34+
'@typescript-eslint/consistent-type-assertions': [
35+
'error',
36+
{ assertionStyle: 'as' },
37+
],
3438
},
3539
},
3640
],

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## [Unreleased]
44

5+
### Fixed
6+
7+
- Cannot output the conversion result into the drive root ([#442](https://github.com/marp-team/marp-cli/issues/442), [#443](https://github.com/marp-team/marp-cli/pull/443))
8+
59
### Changed
610

711
- Upgrade Marpit to [v2.2.4](https://github.com/marp-team/marpit/releases/tag/v2.2.4) ([#441](https://github.com/marp-team/marp-cli/pull/441))

src/file.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,12 @@ export class File {
147147
}
148148

149149
private async saveToFile(savePath: string = this.path) {
150-
await fs.promises.mkdir(path.dirname(path.resolve(savePath)), {
151-
recursive: true,
152-
})
150+
const directory = path.dirname(path.resolve(savePath))
151+
152+
if (path.dirname(directory) !== directory) {
153+
await fs.promises.mkdir(directory, { recursive: true })
154+
}
155+
153156
await fs.promises.writeFile(savePath, this.buffer!) // eslint-disable-line @typescript-eslint/no-non-null-assertion
154157
}
155158

src/server/server-index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
export const showAllKey = 'marp-cli-show-all'
22

33
export default function serverIndex() {
4-
const showAll = <HTMLInputElement>document.getElementById('show-all')
5-
const index = <HTMLElement>document.getElementById('index')
4+
const showAll = document.getElementById('show-all') as HTMLInputElement
5+
const index = document.getElementById('index') as HTMLElement
66

77
const applyShowAll = (state: boolean) => {
88
showAll.checked = state

src/templates/bespoke/navigation.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ const bespokeNavigation =
6868
if (elm?.parentElement) detectScrollable(elm.parentElement, dir)
6969
}
7070

71-
if (e.deltaX !== 0) detectScrollable(<HTMLElement>e.target, 'X')
72-
if (e.deltaY !== 0) detectScrollable(<HTMLElement>e.target, 'Y')
71+
if (e.deltaX !== 0) detectScrollable(e.target as HTMLElement, 'X')
72+
if (e.deltaY !== 0) detectScrollable(e.target as HTMLElement, 'Y')
7373
if (scrollable) return
7474

7575
e.preventDefault()

test/__mocks__/mkdirp.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

test/converter.ts

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,14 @@ import { WatchNotifier } from '../src/watcher'
2020

2121
const puppeteerTimeoutMs = 60000
2222

23+
let mkdirSpy: jest.SpiedFunction<typeof fs.promises.mkdir>
24+
2325
jest.mock('fs')
2426

27+
beforeEach(() => {
28+
mkdirSpy = jest.spyOn(fs.promises, 'mkdir').mockImplementation()
29+
})
30+
2531
afterAll(() => Converter.closeBrowser())
2632
afterEach(() => jest.restoreAllMocks())
2733

@@ -58,7 +64,7 @@ describe('Converter', () => {
5864
globalDirectives: { theme: 'default' },
5965
imageScale: 2,
6066
lang: 'fr',
61-
options: <Options>{ html: true },
67+
options: { html: true } as Options,
6268
server: false,
6369
template: 'test-template',
6470
templateOption: {},
@@ -485,12 +491,12 @@ transition:
485491
describe('#convertFile', () => {
486492
it('rejects Promise when specified file is not found', () =>
487493
expect(
488-
(instance() as any).convertFile(new File('_NOT_FOUND_MARKDOWN_'))
494+
instance().convertFile(new File('_NOT_FOUND_MARKDOWN_'))
489495
).rejects.toBeTruthy())
490496

491497
it('converts markdown file and save as html file by default', async () => {
492-
const write = (<any>fs).__mockWriteFile()
493-
await (<any>instance()).convertFile(new File(onePath))
498+
const write = (fs as any).__mockWriteFile()
499+
await instance().convertFile(new File(onePath))
494500

495501
expect(write).toHaveBeenCalledWith(
496502
`${onePath.slice(0, -3)}.html`,
@@ -500,9 +506,9 @@ transition:
500506
})
501507

502508
it('converts markdown file and save to specified path when output is defined', async () => {
503-
const write = (<any>fs).__mockWriteFile()
509+
const write = (fs as any).__mockWriteFile()
504510
const output = './specified.html'
505-
await (<any>instance({ output })).convertFile(new File(twoPath))
511+
await instance({ output }).convertFile(new File(twoPath))
506512

507513
expect(write).toHaveBeenCalledWith(
508514
output,
@@ -511,19 +517,40 @@ transition:
511517
)
512518
})
513519

520+
it('tries to create the directory of output file when saving', async () => {
521+
const write = (fs as any).__mockWriteFile()
522+
const output = path.resolve(__dirname, '__test_dir__/out.html')
523+
524+
await instance({ output }).convertFile(new File(twoPath))
525+
526+
expect(write).toHaveBeenCalled()
527+
expect(mkdirSpy).toHaveBeenCalledWith(
528+
path.resolve(__dirname, '__test_dir__'),
529+
{ recursive: true }
530+
)
531+
})
532+
533+
it('does not try to create the directory of output file when saving to the root', async () => {
534+
const write = (fs as any).__mockWriteFile()
535+
const output = '/out.html'
536+
537+
await instance({ output }).convertFile(new File(twoPath))
538+
539+
expect(write).toHaveBeenCalled()
540+
expect(mkdirSpy).not.toHaveBeenCalled()
541+
})
542+
514543
it('converts markdown file but not save when output is stdout', async () => {
515-
const write = (<any>fs).__mockWriteFile()
544+
const write = (fs as any).__mockWriteFile()
516545
const stdout = jest.spyOn(process.stdout, 'write').mockImplementation()
517546

518547
const output = '-'
519-
const ret = await (<any>instance({ output })).convertFile(
520-
new File(threePath)
521-
)
548+
const ret = await instance({ output }).convertFile(new File(threePath))
522549

523550
expect(write).not.toHaveBeenCalled()
524551
expect(stdout).toHaveBeenCalledTimes(1)
525552
expect(ret.file.path).toBe(threePath)
526-
expect(ret.newFile.type).toBe(FileType.StandardIO)
553+
expect(ret.newFile?.type).toBe(FileType.StandardIO)
527554
})
528555

529556
describe('when convert type is PDF', () => {
@@ -533,7 +560,7 @@ transition:
533560
it(
534561
'converts markdown file into PDF',
535562
async () => {
536-
const write = (<any>fs).__mockWriteFile()
563+
const write = (fs as any).__mockWriteFile()
537564
const opts = { output: 'test.pdf' }
538565
const ret = await pdfInstance(opts).convertFile(new File(onePath))
539566
const pdf: Buffer = write.mock.calls[0][1]
@@ -551,7 +578,7 @@ transition:
551578
it(
552579
'assigns meta info thorugh pdf-lib',
553580
async () => {
554-
const write = (<any>fs).__mockWriteFile()
581+
const write = (fs as any).__mockWriteFile()
555582

556583
await pdfInstance({
557584
output: 'test.pdf',
@@ -580,7 +607,7 @@ transition:
580607
async () => {
581608
const file = new File(onePath)
582609

583-
const fileCleanup = jest.spyOn(<any>File.prototype, 'cleanup')
610+
const fileCleanup = jest.spyOn(File.prototype as any, 'cleanup')
584611
const fileSave = jest
585612
.spyOn(File.prototype, 'save')
586613
.mockImplementation()
@@ -614,7 +641,7 @@ transition:
614641
it(
615642
'assigns presenter notes as annotation of PDF',
616643
async () => {
617-
const write = (<any>fs).__mockWriteFile()
644+
const write = (fs as any).__mockWriteFile()
618645

619646
await pdfInstance({
620647
output: 'test.pdf',
@@ -637,7 +664,7 @@ transition:
637664
)
638665

639666
it('sets a comment author to notes if set author global directive', async () => {
640-
const write = (<any>fs).__mockWriteFile()
667+
const write = (fs as any).__mockWriteFile()
641668

642669
await pdfInstance({
643670
output: 'test.pdf',
@@ -673,7 +700,7 @@ transition:
673700
let write: jest.Mock
674701

675702
beforeEach(() => {
676-
write = (<any>fs).__mockWriteFile()
703+
write = (fs as any).__mockWriteFile()
677704
})
678705

679706
const converter = (opts: Partial<ConverterOption> = {}) =>
@@ -781,7 +808,7 @@ transition:
781808
let write: jest.Mock
782809

783810
beforeEach(() => {
784-
write = (<any>fs).__mockWriteFile()
811+
write = (fs as any).__mockWriteFile()
785812
})
786813

787814
it(
@@ -851,7 +878,7 @@ transition:
851878
let write: jest.Mock
852879

853880
beforeEach(() => {
854-
write = (<any>fs).__mockWriteFile()
881+
write = (fs as any).__mockWriteFile()
855882
})
856883

857884
it(
@@ -929,7 +956,7 @@ transition:
929956
pages: true,
930957
type: ConvertType.png,
931958
})
932-
write = (<any>fs).__mockWriteFile()
959+
write = (fs as any).__mockWriteFile()
933960
})
934961

935962
it(
@@ -950,9 +977,9 @@ transition:
950977
const notesInstance = (opts: Partial<ConverterOption> = {}) =>
951978
instance({ ...opts, type: ConvertType.notes })
952979

953-
const write = (<any>fs).__mockWriteFile()
980+
const write = (fs as any).__mockWriteFile()
954981
const output = './specified.txt'
955-
const ret = await (<any>notesInstance({ output })).convertFile(
982+
const ret = await notesInstance({ output }).convertFile(
956983
new File(threePath)
957984
)
958985
const notes: Buffer = write.mock.calls[0][1]
@@ -969,9 +996,9 @@ transition:
969996
const notesInstance = (opts: Partial<ConverterOption> = {}) =>
970997
instance({ ...opts, type: ConvertType.notes })
971998

972-
const write = (<any>fs).__mockWriteFile()
999+
const write = (fs as any).__mockWriteFile()
9731000
const output = './specified.txt'
974-
const ret = await (<any>notesInstance({ output })).convertFile(
1001+
const ret = await notesInstance({ output }).convertFile(
9751002
new File(onePath)
9761003
)
9771004
const notes: Buffer = write.mock.calls[0][1]
@@ -991,7 +1018,7 @@ transition:
9911018
describe('#convertFiles', () => {
9921019
describe('with multiple files', () => {
9931020
it('converts passed files', async () => {
994-
const write = (<any>fs).__mockWriteFile()
1021+
const write = (fs as any).__mockWriteFile()
9951022

9961023
await instance().convertFiles([new File(onePath), new File(twoPath)])
9971024
expect(write).toHaveBeenCalledTimes(2)
@@ -1008,7 +1035,7 @@ transition:
10081035
).rejects.toBeInstanceOf(CLIError))
10091036

10101037
it('converts passed files when output is stdout', async () => {
1011-
const write = (<any>fs).__mockWriteFile()
1038+
const write = (fs as any).__mockWriteFile()
10121039
const stdout = jest.spyOn(process.stdout, 'write').mockImplementation()
10131040
const files = [new File(onePath), new File(twoPath)]
10141041

test/marp-cli.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ const runForObservation = async (argv: string[]) => {
3838
}
3939

4040
jest.mock('fs')
41-
jest.mock('mkdirp')
4241
jest.mock('../src/preview')
4342
jest.mock('../src/watcher', () => jest.createMockFromModule('../src/watcher'))
4443

@@ -225,7 +224,7 @@ describe('Marp CLI', () => {
225224
const files = assetFn('_files')
226225

227226
let writeFile: jest.Mock
228-
beforeEach(() => (writeFile = (<any>fs).__mockWriteFile()))
227+
beforeEach(() => (writeFile = (fs as any).__mockWriteFile()))
229228

230229
it('converts files in specified dir', async () => {
231230
jest.spyOn(cli, 'info').mockImplementation()
@@ -373,7 +372,7 @@ describe('Marp CLI', () => {
373372
info = jest.spyOn(cli, 'info')
374373

375374
info.mockImplementation()
376-
;(<any>fs).__mockWriteFile()
375+
;(fs as any).__mockWriteFile()
377376
})
378377

379378
describe('when passed value is theme name', () => {
@@ -396,7 +395,7 @@ describe('Marp CLI', () => {
396395
const { css } = (await convert.mock.results[0].value).rendered
397396
expect(css).toContain('/* @theme a */')
398397

399-
const converter = <Converter>convert.mock.instances[0]
398+
const converter: Converter = convert.mock.instances[0]
400399
const { themeSet } = converter.options
401400
const theme = themeSet.themes.get(cssFile)
402401

@@ -442,7 +441,7 @@ describe('Marp CLI', () => {
442441
observeSpy = jest.spyOn(ThemeSet.prototype, 'observe')
443442

444443
jest.spyOn(cli, 'info').mockImplementation()
445-
;(<any>fs).__mockWriteFile()
444+
;(fs as any).__mockWriteFile()
446445
})
447446

448447
describe('with specified single file', () => {
@@ -556,7 +555,7 @@ describe('Marp CLI', () => {
556555
await marpCli(cmd)
557556
expect(cvtFiles).toHaveBeenCalled()
558557

559-
return <any>cvtFiles.mock.instances[0]
558+
return cvtFiles.mock.instances[0] as any
560559
} finally {
561560
cvtFiles.mockRestore()
562561
cliInfo.mockRestore()
@@ -565,7 +564,7 @@ describe('Marp CLI', () => {
565564

566565
it('converts file', async () => {
567566
const cliInfo = jest.spyOn(cli, 'info').mockImplementation()
568-
;(<any>fs).__mockWriteFile()
567+
;(fs as any).__mockWriteFile()
569568

570569
expect(await marpCli([onePath])).toBe(0)
571570

@@ -765,7 +764,7 @@ describe('Marp CLI', () => {
765764
describe('with -w option', () => {
766765
it('starts watching by Watcher.watch()', async () => {
767766
jest.spyOn(cli, 'info').mockImplementation()
768-
;(<any>fs).__mockWriteFile()
767+
;(fs as any).__mockWriteFile()
769768

770769
await runForObservation([onePath, '-w'])
771770
expect(Watcher.watch).toHaveBeenCalledWith([onePath], expect.anything())
@@ -1071,7 +1070,7 @@ describe('Marp CLI', () => {
10711070
.mockResolvedValue(Buffer.from('# markdown'))
10721071

10731072
// reset cached stdin buffer
1074-
;(<any>File).stdinBuffer = undefined
1073+
;(File as any).stdinBuffer = undefined
10751074
})
10761075

10771076
it('converts markdown came from stdin and outputs to stdout', async () => {

test/server/server-index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ describe('JavaScript for server index', () => {
1313
<ul id="index"></ul>
1414
`.trim()
1515

16-
index = <HTMLUListElement>document.getElementById('index')
17-
showAll = <HTMLInputElement>document.getElementById('show-all')
16+
index = document.getElementById('index') as HTMLUListElement
17+
showAll = document.getElementById('show-all') as HTMLInputElement
1818
})
1919

2020
const checkShowAll = (state: boolean) => {

test/theme.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ describe('ThemeSet', () => {
7575
expect(themeSet.onThemeUpdated).toHaveBeenCalledWith('testA2.md')
7676

7777
// It does no longer trigger after #unobserve
78-
;(<jest.Mock>themeSet.onThemeUpdated).mockClear()
78+
;(themeSet.onThemeUpdated as jest.Mock).mockClear()
7979
themeSet.unobserve('testA.md')
8080

8181
await themeSet.load(themeA)

0 commit comments

Comments
 (0)