@stencila/jesta
Version:
Stencila plugin for executable documents using JavaScript
110 lines (109 loc) • 4.76 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.build = void 0;
const schema_1 = require("@stencila/schema");
const child_process_1 = require("child_process");
const fs_1 = __importDefault(require("fs"));
const util_1 = require("util");
const types_1 = require("./types");
const changes_1 = require("./util/changes");
const timer = __importStar(require("./util/timer"));
const walk_1 = require("./util/walk");
const readFile = util_1.promisify(fs_1.default.readFile);
const writeFile = util_1.promisify(fs_1.default.writeFile);
/**
* Build a document
*
* This implementation walks the document and collects NPM package names
* from the `imports` property of Javascript `CodeChunk` nodes. It then
* calls `npm install` with the names of the packages missing from `package.json`.
* If there is no `package.json` file then will create one.
*
* @param entity The document to build.
*/
async function build(node, force) {
// Build code chunks (code expressions can be ignored)
if (schema_1.isA('CodeChunk', node)) {
// Skip if not not JavaScript code
const { programmingLanguage } = node;
if (!['js', 'javascript'].includes(programmingLanguage !== null && programmingLanguage !== void 0 ? programmingLanguage : ''))
return node;
// Skip if not needed
if (!force && !changes_1.needed(node, types_1.Method.build))
return node;
// Ensure node has been compiled
await this.compile(node, force);
// Start the timer
const start = timer.start();
// Collect a list of packages imported within the node
const { imports = [] } = node;
const packages = [];
for (const pkg of imports) {
const name = typeof pkg === 'string' ? pkg : pkg.name;
if (name !== undefined && !packages.includes(name))
packages.push(name);
}
// Read or create the document's package.json
if (packages.length > 0) {
let packageJson;
try {
const json = await readFile('package.json', 'utf8');
packageJson = JSON.parse(json);
}
catch (error) {
if (error.code === 'ENOENT') {
// Write out document's package.json with the `dependencies` property
// updated and defaults for other properties (mainly to prevent `npm`
// warning when they are absent)
packageJson = {
description: 'NPM package for this document',
repository: '-',
license: 'Apache-2.0',
dependencies: {},
};
await writeFile('package.json', JSON.stringify(packageJson, null, ' '), 'utf8');
}
else
throw error;
}
// Only install packages that are not yet in package.json
const missing = packages.filter((pkg) => packageJson.dependencies[pkg] === undefined);
if (missing.length > 0) {
// Ask `npm` to install the packages
child_process_1.spawnSync('npm', ['install', '--save-exact', ...missing], {
// TODO: Transform output from stdout and stderr into log entries
stdio: ['ignore', 'inherit', 'inherit'],
});
}
}
// Record the method
return changes_1.record(node, types_1.Method.build, timer.seconds(start));
}
else {
// Walk over other node types
return walk_1.mutate(node, (child) => this.build(child, force));
}
}
exports.build = build;