UNPKG

6.92 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6// @ts-ignore
7const builtin_modules_1 = __importDefault(require("builtin-modules"));
8// @ts-ignore
9const detective_1 = __importDefault(require("detective"));
10const path_1 = __importDefault(require("path"));
11const semver_1 = __importDefault(require("semver"));
12const Parser_1 = __importDefault(require("./Parser"));
13const schema_1 = require("@stencila/schema");
14/**
15 * Dockter `Parser` class for Node.js.
16 */
17class JavascriptParser extends Parser_1.default {
18 /**
19 * Parse a folder to detect any `package.json` or `*.js` source code files
20 * and return a `SoftwarePackage` instance
21 */
22 async parse() {
23 if (this.exists('package.json')) {
24 let data = JSON.parse(this.read('package.json'));
25 return this.createPackage(data);
26 }
27 else {
28 const files = this.glob(['**/*.js']);
29 if (files.length) {
30 const data = {
31 name: path_1.default.basename(this.folder),
32 dependencies: {}
33 };
34 for (let file of files) {
35 const code = this.read(file);
36 const requires = detective_1.default(code);
37 for (let require of requires) {
38 if (!builtin_modules_1.default.includes(require)) {
39 // @ts-ignore
40 data.dependencies[require] = 'latest';
41 }
42 }
43 }
44 return this.createPackage(data);
45 }
46 else {
47 return null;
48 }
49 }
50 }
51 /**
52 * Create a `SoftwarePackage` instance from a Node.js package meta-data object
53 *
54 * Meta-data for a packages dependencies is obtained from https://registry.npmjs.org/ using the
55 * JSON API documented at https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md
56 * and https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md.
57 * Currently we fetch the abbreviated metadata because the full meta data can be very large.
58 *
59 * The column "NodeJS" in https://github.com/codemeta/codemeta/blob/master/crosswalk.csv
60 * is used to translate package meta-data into a `SoftwarePackage` instance.
61 *
62 * @param data Package object
63 */
64 async createPackage(data) {
65 // Create new package instance and populate it's
66 // properties in order of type hierarchy: Thing > CreativeWork > SoftwareSourceCode > SoftwarePackage
67 const pkg = new schema_1.SoftwarePackage();
68 // schema:Thing
69 pkg.name = data.name;
70 // schema:CreativeWork
71 pkg.version = data.version;
72 // schema:SoftwareSourceCode
73 pkg.runtimePlatform = 'Node.js';
74 pkg.license = data.license;
75 pkg.description = data.description;
76 if (data.author) {
77 if (typeof data.author === 'string') {
78 pkg.authors = [schema_1.Person.fromText(data.author)];
79 }
80 else {
81 let authorStr = '';
82 if (data.author.name)
83 authorStr = data.author.name;
84 if (data.author.email)
85 authorStr += ` <${data.author.email}>`;
86 if (data.author.url)
87 authorStr += ` (${data.author.url})`;
88 pkg.authors = [schema_1.Person.fromText(authorStr)];
89 }
90 }
91 if (data.repository) {
92 if (typeof data.repository === 'string') {
93 if (data.repository.match(/github:/)) {
94 pkg.codeRepository = data.repository.replace(/github:/, 'https://github.com') + '/';
95 }
96 else if (data.repository.match(/gitlab:/)) {
97 pkg.codeRepository = data.repository.replace(/gitlab:/, 'https://gitlab.com') + '/';
98 }
99 else if (data.repository.match(/bitbucket:/)) {
100 pkg.codeRepository = data.repository.replace(/bitbucket:/, 'https://bitbucket.com') + '/';
101 }
102 else if (data.repository.match(/^[^\/]*\/[^\/]*$/)) {
103 pkg.codeRepository = data.repository.replace(/^([^\/]*)\/([^\/]*)$/, 'https://www.npmjs.com/package/$1/$2') + '/';
104 }
105 else {
106 pkg.codeRepository = data.repository;
107 }
108 }
109 else {
110 pkg.codeRepository = data.repository.url;
111 }
112 }
113 // stencila:SoftwarePackage
114 if (data.dependencies) {
115 pkg.softwareRequirements = await Promise.all(Object.entries(data.dependencies).map(async ([name, versionRange]) => {
116 // Determine the minimum version that satisfies the range specified in the
117 // If we can't determine a minimum version from the versionRange
118 // (e.g. because it's a github url) then try to get latest
119 let version = 'latest';
120 if (versionRange !== 'latest' && versionRange !== '*') {
121 const range = semver_1.default.validRange(versionRange);
122 if (range) {
123 const match = range.match(/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)/);
124 if (match)
125 version = match[0];
126 }
127 }
128 // For scoped packages (e.g. `@types/node`) replace any slashes in the package name
129 // and fetch the latest version (see https://github.com/stencila/dockter/issues/87).
130 if (name[0] === '@') {
131 name = name.replace('/', '%2f');
132 version = '*';
133 }
134 // Fetch meta-data from NPM
135 const data = await this.fetch(`https://registry.npmjs.org/${name}/${version}`, {
136 json: true,
137 headers: {
138 'Accept': 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*'
139 }
140 });
141 if (data) {
142 return this.createPackage(data);
143 }
144 else {
145 // All we know is name and version, so return that
146 const dependency = new schema_1.SoftwarePackage();
147 dependency.name = name;
148 dependency.version = versionRange;
149 dependency.runtimePlatform = 'Node.js';
150 return dependency;
151 }
152 }));
153 }
154 return pkg;
155 }
156}
157exports.default = JavascriptParser;