Skip to content

Commit

Permalink
Implement a workaround for jsdoc issue regarding temp dirs #19.
Browse files Browse the repository at this point in the history
Increase the child_process maxBuffer to 100MB #24
  • Loading branch information
75lb committed Aug 26, 2024
1 parent a600973 commit 2a71de0
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 45 deletions.
80 changes: 51 additions & 29 deletions dist/index.cjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
'use strict';

var path$1 = require('path');
var fs$1 = require('fs');
var fs = require('fs');
var os = require('os');
var crypto = require('crypto');
var fs$1 = require('node:fs');
var node_url = require('node:url');
var path$2 = require('node:path');
var actualFS = require('node:fs');
var promises = require('node:fs/promises');
var node_events = require('node:events');
var Stream = require('node:stream');
Expand Down Expand Up @@ -35,7 +35,7 @@ function _interopNamespaceDefault(e) {
return Object.freeze(n);
}

var actualFS__namespace = /*#__PURE__*/_interopNamespaceDefault(actualFS);
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs$1);

/**
* Takes any input and guarantees an array back.
Expand Down Expand Up @@ -124,7 +124,7 @@ class Cache {
}
set dir (val) {
this._dir = val;
fs$1.mkdirSync(this.dir, { recursive: true });
fs.mkdirSync(this.dir, { recursive: true });
}

/**
Expand All @@ -135,7 +135,7 @@ class Cache {
*/
async read (keys) {
const blobPath = path$1.resolve(this._dir, this.getChecksum(keys));
return fs$1.promises.readFile(blobPath).then(JSON.parse)
return fs.promises.readFile(blobPath).then(JSON.parse)
}

/**
Expand All @@ -146,7 +146,7 @@ class Cache {
readSync (keys) {
const blobPath = path$1.resolve(this._dir, this.getChecksum(keys));
try {
const data = fs$1.readFileSync(blobPath, 'utf8');
const data = fs.readFileSync(blobPath, 'utf8');
return JSON.parse(data)
} catch (err) {
return null
Expand All @@ -161,7 +161,7 @@ class Cache {
*/
async write (keys, content) {
const blobPath = path$1.resolve(this._dir, this.getChecksum(keys));
return fs$1.promises.writeFile(blobPath, JSON.stringify(content))
return fs.promises.writeFile(blobPath, JSON.stringify(content))
}

/**
Expand All @@ -171,7 +171,7 @@ class Cache {
*/
writeSync (keys, content) {
const blobPath = path$1.resolve(this._dir, this.getChecksum(keys));
fs$1.writeFileSync(blobPath, JSON.stringify(content));
fs.writeFileSync(blobPath, JSON.stringify(content));
}

/**
Expand All @@ -190,8 +190,8 @@ class Cache {
* @returns {Promise}
*/
async clear () {
const files = await fs$1.promises.readdir(this._dir);
const promises = files.map(file => fs$1.promises.unlink(path$1.resolve(this._dir, file)));
const files = await fs.promises.readdir(this._dir);
const promises = files.map(file => fs.promises.unlink(path$1.resolve(this._dir, file)));
return Promise.all(promises)
}

Expand All @@ -201,14 +201,13 @@ class Cache {
*/
async remove () {
await this.clear();
return fs$1.promises.rmdir(this._dir)
return fs.promises.rmdir(this._dir)
}
}

class TempFile {
constructor (source) {
const tempDir = fs$1.mkdtempSync(path$1.join(os.tmpdir(), 'jsdoc-api-'));
this.path = path$1.join(tempDir, crypto.randomBytes(6).toString('hex') + '.js');
this.path = path$1.join(TempFile.tempFileDir, crypto.randomBytes(6).toString('hex') + '.js');
fs$1.writeFileSync(this.path, source);
}

Expand All @@ -219,6 +218,15 @@ class TempFile {
// already deleted
}
}

static tempFileDir = path$1.join(os.homedir(), '.jsdoc-api/temp')
static cacheDir = path$1.join(os.homedir(), '.jsdoc-api/cache')

static createTmpDirs () {
/* No longer using os.tmpdir(). See: https://github.com/jsdoc2md/jsdoc-api/issues/19 */
fs$1.mkdirSync(TempFile.tempFileDir, { recursive: true });
fs$1.mkdirSync(TempFile.cacheDir, { recursive: true });
}
}

function getDefaultExportFromCjs (x) {
Expand Down Expand Up @@ -4822,12 +4830,12 @@ class Minipass extends node_events.EventEmitter {
}
}

const realpathSync = fs$1.realpathSync.native;
const realpathSync = fs.realpathSync.native;
const defaultFS = {
lstatSync: fs$1.lstatSync,
readdir: fs$1.readdir,
readdirSync: fs$1.readdirSync,
readlinkSync: fs$1.readlinkSync,
lstatSync: fs.lstatSync,
readdir: fs.readdir,
readdirSync: fs.readdirSync,
readlinkSync: fs.readlinkSync,
realpathSync,
promises: {
lstat: promises.lstat,
Expand All @@ -4837,7 +4845,7 @@ const defaultFS = {
},
};
// if they just gave us require('fs') then use our default
const fsFromOption = (fsOption) => !fsOption || fsOption === defaultFS || fsOption === actualFS__namespace ?
const fsFromOption = (fsOption) => !fsOption || fsOption === defaultFS || fsOption === fs__namespace ?
defaultFS
: {
...defaultFS,
Expand Down Expand Up @@ -8131,7 +8139,7 @@ class FileSet {
files = arrayify(files);
for (const file of files) {
try {
const stat = await actualFS.promises.stat(file);
const stat = await fs$1.promises.stat(file);
if (stat.isFile() && !this.files.includes(file)) {
this.files.push(file);
} else if (stat.isDirectory() && !this.dirs.includes(file)) {
Expand Down Expand Up @@ -8205,14 +8213,14 @@ class FileSet {
*/
function walkBack (startAt, lookingFor) {
startAt = path$1.resolve(startAt);
if (fs$1.existsSync(startAt) && fs$1.statSync(startAt).isDirectory()) {
if (fs.existsSync(startAt) && fs.statSync(startAt).isDirectory()) {
const dirs = path$1.resolve(startAt).split(path$1.sep);
for (let i = 0; i < dirs.length; i++) {
const basedir = i < dirs.length - 1
? dirs.slice(0, dirs.length - i).join(path$1.sep)
: path$1.sep;

if (fs$1.existsSync(path$1.join(basedir, lookingFor))) {
if (fs.existsSync(path$1.join(basedir, lookingFor))) {
return path$1.join(basedir, lookingFor)
}
}
Expand Down Expand Up @@ -8279,6 +8287,7 @@ class JsdocCommand {
try {
result = await this.getOutput();
} finally {
/* run even if getOutput fails */
if (this.tempFiles) {
for (const tempFile of this.tempFiles) {
tempFile.delete();
Expand Down Expand Up @@ -8349,8 +8358,16 @@ const exec = util.promisify(cp.exec);

class Explain extends JsdocCommand {
async getOutput () {
if (this.options.cache && !this.options.source) {
return this.readCache().catch(this._runJsdoc.bind(this))
if (this.options.cache && !this.options.source.length) {
try {
return await this.readCache()
} catch (err) {
if (err.code === 'ENOENT') {
return this._runJsdoc()
} else {
throw err
}
}
} else {
return this._runJsdoc()
}
Expand All @@ -8363,7 +8380,7 @@ class Explain extends JsdocCommand {

let jsdocOutput = { stdout: '', stderr: '' };
try {
jsdocOutput = await exec(cmd);
jsdocOutput = await exec(cmd, { maxBuffer: 1024 * 1024 * 100 }); /* 100MB */
const explainOutput = JSON.parse(jsdocOutput.stdout);
if (this.options.cache) {
await this.cache.write(this.cacheKey, explainOutput);
Expand All @@ -8380,7 +8397,12 @@ class Explain extends JsdocCommand {

async readCache () {
if (this.cache) {
const promises = this.inputFileSet.files.map(file => fs.readFile(file, 'utf8'));
/* Create the cache key then check the cache for a match, returning pre-generated output if so.
The current cache key is a union of the input file names plus their content - this could be expensive when processing a lot of files.
*/
const promises = this.inputFileSet.files.map(file => {
return fs$1.promises.readFile(file, 'utf8')
});
const contents = await Promise.all(promises);
this.cacheKey = contents.concat(this.inputFileSet.files);
return this.cache.read(this.cacheKey)
Expand Down Expand Up @@ -8408,16 +8430,17 @@ class Render extends JsdocCommand {
* @typicalname jsdoc
*/

TempFile.createTmpDirs();

/**
* @external cache-point
* @see https://github.com/75lb/cache-point
*/

/**
* The [cache-point](https://github.com/75lb/cache-point) instance used when `cache: true` is specified on `.explain()`.
* @type {external:cache-point}
*/
const cache = new Cache({ dir: path$1.join(os$1.tmpdir(), 'jsdoc-api') });
const cache = new Cache({ dir: TempFile.cacheDir });

/**
* @alias module:jsdoc-api
Expand Down Expand Up @@ -8459,7 +8482,6 @@ const jsdoc = {
*/
class JsdocOptions {
constructor (options = {}) {

/**
* One or more filenames to process. Either `files`, `source` or `configure` must be supplied.
* @type {string|string[]}
Expand Down
7 changes: 4 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@ import Explain from './lib/explain.js'
import Render from './lib/render.js'
import arrayify from 'array-back'
import os from 'node:os'
import TempFile from './lib/temp-file.js'

TempFile.createTmpDirs()

/**
* @external cache-point
* @see https://github.com/75lb/cache-point
*/

/**
* The [cache-point](https://github.com/75lb/cache-point) instance used when `cache: true` is specified on `.explain()`.
* @type {external:cache-point}
*/
const cache = new Cache({ dir: path.join(os.tmpdir(), 'jsdoc-api') })
const cache = new Cache({ dir: TempFile.cacheDir })

/**
* @alias module:jsdoc-api
Expand Down Expand Up @@ -60,7 +62,6 @@ const jsdoc = {
*/
class JsdocOptions {
constructor (options = {}) {

/**
* One or more filenames to process. Either `files`, `source` or `configure` must be supplied.
* @type {string|string[]}
Expand Down
23 changes: 18 additions & 5 deletions lib/explain.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import JsdocCommand from './jsdoc-command.js'
import arrayify from 'array-back'
import toSpawnArgs from 'object-to-spawn-args'
import cp from 'child_process'
import util from 'node:util'
import { promises as fs } from 'node:fs'
const exec = util.promisify(cp.exec)

class Explain extends JsdocCommand {
async getOutput () {
if (this.options.cache && !this.options.source) {
return this.readCache().catch(this._runJsdoc.bind(this))
if (this.options.cache && !this.options.source.length) {
try {
return await this.readCache()
} catch (err) {
if (err.code === 'ENOENT') {
return this._runJsdoc()
} else {
throw err
}
}
} else {
return this._runJsdoc()
}
Expand All @@ -21,7 +29,7 @@ class Explain extends JsdocCommand {

let jsdocOutput = { stdout: '', stderr: '' }
try {
jsdocOutput = await exec(cmd)
jsdocOutput = await exec(cmd, { maxBuffer: 1024 * 1024 * 100 }) /* 100MB */
const explainOutput = JSON.parse(jsdocOutput.stdout)
if (this.options.cache) {
await this.cache.write(this.cacheKey, explainOutput)
Expand All @@ -38,7 +46,12 @@ class Explain extends JsdocCommand {

async readCache () {
if (this.cache) {
const promises = this.inputFileSet.files.map(file => fs.readFile(file, 'utf8'))
/* Create the cache key then check the cache for a match, returning pre-generated output if so.
The current cache key is a union of the input file names plus their content - this could be expensive when processing a lot of files.
*/
const promises = this.inputFileSet.files.map(file => {
return fs.readFile(file, 'utf8')
})
const contents = await Promise.all(promises)
this.cacheKey = contents.concat(this.inputFileSet.files)
return this.cache.read(this.cacheKey)
Expand Down
2 changes: 1 addition & 1 deletion lib/jsdoc-command.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import TempFile from './temp-file.js'
import FileSet from 'file-set'
import assert from 'assert'
import walkBack from 'walk-back'
import { promises as fs } from 'node:fs'
import currentModulePaths from 'current-module-paths'

const { __dirname } = currentModulePaths(import.meta.url)
Expand Down Expand Up @@ -57,6 +56,7 @@ class JsdocCommand {
try {
result = await this.getOutput()
} finally {
/* run even if getOutput fails */
if (this.tempFiles) {
for (const tempFile of this.tempFiles) {
tempFile.delete()
Expand Down
14 changes: 11 additions & 3 deletions lib/temp-file.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import fs from 'fs'
import fs from 'node:fs'
import os from 'os'
import crypto from 'crypto'
import path from 'path'

class TempFile {
constructor (source) {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'jsdoc-api-'))
this.path = path.join(tempDir, crypto.randomBytes(6).toString('hex') + '.js')
this.path = path.join(TempFile.tempFileDir, crypto.randomBytes(6).toString('hex') + '.js')
fs.writeFileSync(this.path, source)
}

Expand All @@ -17,6 +16,15 @@ class TempFile {
// already deleted
}
}

static tempFileDir = path.join(os.homedir(), '.jsdoc-api/temp')
static cacheDir = path.join(os.homedir(), '.jsdoc-api/cache')

static createTmpDirs () {
/* No longer using os.tmpdir(). See: https://github.com/jsdoc2md/jsdoc-api/issues/19 */
fs.mkdirSync(TempFile.tempFileDir, { recursive: true })
fs.mkdirSync(TempFile.cacheDir, { recursive: true })
}
}

export default TempFile
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
"standard": {
"ignore": [
"tmp",
"test/fixture"
"test/fixture",
"dist"
]
},
"files": [
Expand Down
3 changes: 1 addition & 2 deletions test/caching.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import jsdoc from 'jsdoc-api'
import Fixture from './lib/fixture.js'
import { statSync } from 'fs'
import { readdirSync, readFileSync } from 'fs'
import { strict as a } from 'assert'
import path from 'path'
import { readdirSync, readFileSync } from 'fs'

/* tests need to run with a maxConcurrency of 1 as `jsdoc.cache` is shared between tests */
const [test, only, skip] = [new Map(), new Map(), new Map()]
Expand Down
1 change: 0 additions & 1 deletion test/explain.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import jsdoc from 'jsdoc-api'
import Fixture from './lib/fixture.js'
import { statSync } from 'fs'
import { strict as a } from 'assert'
import path from 'path'

Expand Down

0 comments on commit 2a71de0

Please sign in to comment.