Files: efcf9fd06b02fa9bcd28681b4c777224e19702bb / lib / index.js
11603 bytesRaw
1 | /* eslint-disable require-atomic-updates */ |
2 | |
3 | import { exists, mkdirp, readFile, remove, stat } from 'fs-extra'; |
4 | import { log, wasReported } from './log.js'; |
5 | import { need, system } from 'ssb-pkg-fetch'; |
6 | import assert from 'assert'; |
7 | import help from './help'; |
8 | import { isPackageJson } from '../prelude/common.js'; |
9 | import minimist from 'minimist'; |
10 | import packer from './packer.js'; |
11 | import path from 'path'; |
12 | import { plusx } from './chmod.js'; |
13 | import producer from './producer.js'; |
14 | import refine from './refiner.js'; |
15 | import { shutdown } from './fabricator.js'; |
16 | import { version } from '../package.json'; |
17 | import walk from './walker.js'; |
18 | |
19 | function isConfiguration (file) { |
20 | return isPackageJson(file) || file.endsWith('.config.json'); |
21 | } |
22 | |
23 | // http://www.openwall.com/lists/musl/2012/12/08/4 |
24 | |
25 | const { hostArch, hostPlatform, isValidNodeRange, knownArchs, |
26 | knownPlatforms, toFancyArch, toFancyPlatform } = system; |
27 | const hostNodeRange = 'node' + process.version.match(/^v(\d+)/)[1]; |
28 | |
29 | function parseTargets (items) { |
30 | // [ 'node6-macos-x64', 'node6-linux-x64' ] |
31 | const targets = []; |
32 | for (const item of items) { |
33 | const target = { |
34 | nodeRange: hostNodeRange, |
35 | platform: hostPlatform, |
36 | arch: hostArch |
37 | }; |
38 | if (item !== 'host') { |
39 | for (const token of item.split('-')) { |
40 | if (!token) continue; |
41 | if (isValidNodeRange(token)) { |
42 | target.nodeRange = token; |
43 | continue; |
44 | } |
45 | const p = toFancyPlatform(token); |
46 | if (knownPlatforms.indexOf(p) >= 0) { |
47 | target.platform = p; |
48 | continue; |
49 | } |
50 | const a = toFancyArch(token); |
51 | if (knownArchs.indexOf(a) >= 0) { |
52 | target.arch = a; |
53 | continue; |
54 | } |
55 | throw wasReported(`Unknown token '${token}' in '${item}'`); |
56 | } |
57 | } |
58 | targets.push(target); |
59 | } |
60 | return targets; |
61 | } |
62 | |
63 | function stringifyTarget (target) { |
64 | const { nodeRange, platform, arch } = target; |
65 | return `${nodeRange}-${platform}-${arch}`; |
66 | } |
67 | |
68 | function differentParts (targets) { |
69 | const nodeRanges = {}; |
70 | const platforms = {}; |
71 | const archs = {}; |
72 | for (const target of targets) { |
73 | nodeRanges[target.nodeRange] = true; |
74 | platforms[target.platform] = true; |
75 | archs[target.arch] = true; |
76 | } |
77 | const result = {}; |
78 | if (Object.keys(nodeRanges).length > 1) { |
79 | result.nodeRange = true; |
80 | } |
81 | if (Object.keys(platforms).length > 1) { |
82 | result.platform = true; |
83 | } |
84 | if (Object.keys(archs).length > 1) { |
85 | result.arch = true; |
86 | } |
87 | return result; |
88 | } |
89 | |
90 | function stringifyTargetForOutput (output, target, different) { |
91 | const a = [ output ]; |
92 | if (different.nodeRange) a.push(target.nodeRange); |
93 | if (different.platform) a.push(target.platform); |
94 | if (different.arch) a.push(target.arch); |
95 | return a.join('-'); |
96 | } |
97 | |
98 | function fabricatorForTarget (target) { |
99 | const { nodeRange, arch } = target; |
100 | return { nodeRange, platform: hostPlatform, arch }; |
101 | } |
102 | |
103 | const dryRunResults = {}; |
104 | |
105 | async function needWithDryRun (target) { |
106 | const target2 = Object.assign({ dryRun: true }, target); |
107 | const result = await need(target2); |
108 | assert([ 'exists', 'fetched', 'built' ].indexOf(result) >= 0); |
109 | dryRunResults[result] = true; |
110 | } |
111 | |
112 | const targetsCache = {}; |
113 | |
114 | async function needViaCache (target) { |
115 | const s = stringifyTarget(target); |
116 | let c = targetsCache[s]; |
117 | if (c) return c; |
118 | c = await need(target); |
119 | targetsCache[s] = c; |
120 | return c; |
121 | } |
122 | |
123 | export async function exec (argv2) { // eslint-disable-line complexity |
124 | const argv = minimist(argv2, { |
125 | boolean: [ 'b', 'build', 'bytecode', 'd', 'debug', |
126 | 'h', 'help', 'public', 'v', 'version' ], |
127 | string: [ '_', 'c', 'config', 'o', 'options', 'output', |
128 | 'public-packages', |
129 | 't', 'target', 'targets', 'bin-name' ], |
130 | default: { bytecode: true, public: true } |
131 | }); |
132 | |
133 | if (argv.h || argv.help) { |
134 | help(); |
135 | return; |
136 | } |
137 | |
138 | // version |
139 | |
140 | if (argv.v || argv.version) { |
141 | console.log(version); |
142 | return; |
143 | } |
144 | |
145 | log.info(`ssb-pkg@${version}`); |
146 | |
147 | // debug |
148 | |
149 | log.debugMode = argv.d || argv.debug; |
150 | |
151 | // forceBuild |
152 | |
153 | const forceBuild = argv.b || argv.build; |
154 | |
155 | // _ |
156 | |
157 | if (!argv._.length) { |
158 | throw wasReported('Entry file/directory is expected', [ |
159 | 'Pass --help to see usage information' ]); |
160 | } |
161 | if (argv._.length > 1) { |
162 | throw wasReported('Not more than one entry file/directory is expected'); |
163 | } |
164 | |
165 | // input |
166 | |
167 | let input = path.resolve(argv._[0]); |
168 | |
169 | if (!await exists(input)) { |
170 | throw wasReported('Input file does not exist', [ input ]); |
171 | } |
172 | if ((await stat(input)).isDirectory()) { |
173 | input = path.join(input, 'package.json'); |
174 | if (!await exists(input)) { |
175 | throw wasReported('Input file does not exist', [ input ]); |
176 | } |
177 | } |
178 | |
179 | // inputJson |
180 | |
181 | let inputJson, inputJsonName; |
182 | |
183 | if (isConfiguration(input)) { |
184 | inputJson = JSON.parse(await readFile(input)); |
185 | inputJsonName = inputJson.name; |
186 | if (inputJsonName) { |
187 | inputJsonName = inputJsonName.split('/').pop(); // @org/foo |
188 | } |
189 | } |
190 | |
191 | // inputBin, binName, otherBins |
192 | |
193 | let inputBin, binName, otherBins; |
194 | |
195 | if (inputJson) { |
196 | let bin = inputJson.bin; |
197 | if (bin) { |
198 | if (typeof bin === 'object') { |
199 | if (bin[inputJsonName]) { |
200 | binName = inputJsonName |
201 | bin = bin[inputJsonName]; |
202 | } else { |
203 | binName = Object.keys(bin)[0] |
204 | bin = bin[binName] |
205 | } |
206 | otherBins = {} |
207 | var dir = path.dirname(input) |
208 | for (var name in inputJson.bin) { |
209 | if (name !== binName) { |
210 | otherBins[name] = path.resolve(dir, inputJson.bin[name]) |
211 | } |
212 | } |
213 | } else { |
214 | binName = inputJsonName |
215 | } |
216 | inputBin = path.resolve(path.dirname(input), bin); |
217 | if (!await exists(inputBin)) { |
218 | throw wasReported('Bin file does not exist (taken from package.json ' + |
219 | '\'bin\' property)', [ inputBin ]); |
220 | } |
221 | } |
222 | } |
223 | |
224 | if (!binName) { |
225 | if (argv['bin-name']) { |
226 | binName = argv['bin-name'] |
227 | } else { |
228 | throw wasReported('Missing binary name. Use --bin-name <name> or use package.json with \'bin\' property'); |
229 | } |
230 | } |
231 | |
232 | if (inputJson && !inputBin) { |
233 | throw wasReported('Property \'bin\' does not exist in', [ input ]); |
234 | } |
235 | |
236 | // inputFin |
237 | |
238 | const inputFin = inputBin || input; |
239 | |
240 | // config |
241 | |
242 | let config = argv.c || argv.config; |
243 | |
244 | if (inputJson && config) { |
245 | throw wasReported('Specify either \'package.json\' or config. Not both'); |
246 | } |
247 | |
248 | // configJson |
249 | |
250 | let configJson; |
251 | |
252 | if (config) { |
253 | config = path.resolve(config); |
254 | if (!await exists(config)) { |
255 | throw wasReported('Config file does not exist', [ config ]); |
256 | } |
257 | configJson = require(config); // may be either json or js |
258 | if (!configJson.name && !configJson.files && |
259 | !configJson.dependencies && !configJson.pkg) { // package.json not detected |
260 | configJson = { pkg: configJson }; |
261 | } |
262 | } |
263 | |
264 | // output |
265 | |
266 | let output = argv.o || argv.output; |
267 | let autoOutput = false; |
268 | |
269 | if (!output) { |
270 | let name; |
271 | if (inputJson) { |
272 | name = inputJsonName; |
273 | if (!name) { |
274 | throw wasReported('Property \'name\' does not exist in', [ argv._[0] ]); |
275 | } |
276 | } else |
277 | if (configJson) { |
278 | name = configJson.name; |
279 | } |
280 | if (!name) { |
281 | name = path.basename(inputFin); |
282 | } |
283 | autoOutput = true; |
284 | const ext = path.extname(name); |
285 | output = name.slice(0, -ext.length || undefined); |
286 | } else { |
287 | output = path.resolve(output); |
288 | } |
289 | |
290 | // targets |
291 | |
292 | const sTargets = argv.t || argv.target || argv.targets || ''; |
293 | |
294 | if (typeof sTargets !== 'string') { |
295 | throw wasReported(`Something is wrong near ${JSON.stringify(sTargets)}`); |
296 | } |
297 | |
298 | let targets = parseTargets( |
299 | sTargets.split(',').filter((t) => t) |
300 | ); |
301 | |
302 | if (!targets.length) { |
303 | let jsonTargets; |
304 | if (inputJson && inputJson.pkg) { |
305 | jsonTargets = inputJson.pkg.targets; |
306 | } else |
307 | if (configJson && configJson.pkg) { |
308 | jsonTargets = configJson.pkg.targets; |
309 | } |
310 | if (jsonTargets) { |
311 | targets = parseTargets(jsonTargets); |
312 | } |
313 | } |
314 | |
315 | if (!targets.length) { |
316 | if (!autoOutput) { |
317 | targets = parseTargets([ 'host' ]); |
318 | assert(targets.length === 1); |
319 | } else { |
320 | targets = parseTargets([ 'linux', 'macos', 'win' ]); |
321 | } |
322 | log.info('Targets not specified. Assuming:', |
323 | `${targets.map(stringifyTarget).join(', ')}`); |
324 | } |
325 | |
326 | // differentParts |
327 | |
328 | const different = differentParts(targets); |
329 | |
330 | // targets[].output |
331 | |
332 | for (const target of targets) { |
333 | let file; |
334 | if (targets.length === 1) { |
335 | file = output; |
336 | } else { |
337 | file = stringifyTargetForOutput(output, target, different); |
338 | } |
339 | if (target.platform === 'win' && |
340 | path.extname(file) !== '.exe') file += '.exe'; |
341 | } |
342 | |
343 | // bakes |
344 | |
345 | const bakes = (argv.options || '').split(',') |
346 | .filter((bake) => bake).map((bake) => '--' + bake); |
347 | |
348 | // fetch targets |
349 | |
350 | const { bytecode } = argv; |
351 | |
352 | let fabricator; |
353 | |
354 | for (const target of targets) { |
355 | target.forceBuild = forceBuild; |
356 | await needWithDryRun(target); |
357 | const f = target.fabricator = fabricatorForTarget(target); |
358 | f.forceBuild = forceBuild; |
359 | if (bytecode) { |
360 | await needWithDryRun(f); |
361 | } |
362 | } |
363 | |
364 | if (dryRunResults.fetched && !dryRunResults.built) { |
365 | log.info('Fetching base Node.js binaries to PKG_CACHE_PATH'); |
366 | } |
367 | |
368 | for (const target of targets) { |
369 | target.binaryPath = await needViaCache(target); |
370 | const f = target.fabricator; |
371 | if (bytecode) { |
372 | if (f && fabricator && ( |
373 | f.nodeRange !== fabricator.nodeRange || |
374 | f.arch !== fabricator.arch |
375 | )) { |
376 | throw new Error('If using bytecode, Node.js version and architecture must be same for all targets') |
377 | } |
378 | f.binaryPath = await needViaCache(f); |
379 | if (f.platform !== 'win') { |
380 | await plusx(f.binaryPath); |
381 | } |
382 | } |
383 | fabricator = f |
384 | } |
385 | |
386 | // marker |
387 | |
388 | let marker; |
389 | |
390 | if (configJson) { |
391 | marker = { |
392 | config: configJson, |
393 | base: path.dirname(config), |
394 | configPath: config |
395 | }; |
396 | } else { |
397 | marker = { |
398 | config: inputJson || {}, // not `inputBin` because only `input` |
399 | base: path.dirname(input), // is the place for `inputJson` |
400 | configPath: input |
401 | }; |
402 | } |
403 | |
404 | marker.toplevel = true; |
405 | |
406 | // public |
407 | |
408 | const params = {}; |
409 | if (argv.public) { |
410 | params.publicToplevel = true; |
411 | params.publicPackages = [ '*' ]; |
412 | } |
413 | if (argv['public-packages']) { |
414 | params.publicPackages = argv['public-packages'].split(','); |
415 | if (params.publicPackages.indexOf('*') !== -1) { |
416 | params.publicPackages = [ '*' ]; |
417 | } |
418 | } |
419 | |
420 | // records |
421 | |
422 | let records; |
423 | let entrypoint = inputFin; |
424 | let otherEntrypoints = otherBins |
425 | const addition = isConfiguration(input) ? input : undefined; |
426 | |
427 | const walkResult = await walk(marker, entrypoint, addition, params, targets, otherEntrypoints); |
428 | entrypoint = walkResult.entrypoint; |
429 | records = walkResult.records; |
430 | |
431 | const refineResult = refine(records, entrypoint, otherEntrypoints); |
432 | entrypoint = refineResult.entrypoint; |
433 | otherEntrypoints = refineResult.otherEntrypoints; |
434 | records = refineResult.records; |
435 | |
436 | const backpack = packer({ records, entrypoint, bytecode, otherEntrypoints }); |
437 | |
438 | log.debug('Targets:', JSON.stringify(targets, null, 2)); |
439 | |
440 | if (await exists(output)) { |
441 | if ((await stat(output)).isFile()) { |
442 | await remove(output); |
443 | } else { |
444 | throw wasReported('Refusing to overwrite non-file output', [ output ]); |
445 | } |
446 | } else { |
447 | await mkdirp(path.dirname(output)); |
448 | } |
449 | |
450 | // TODO |
451 | // const slash = target.platform === 'win' ? '\\' : '/'; |
452 | const slash = '/'; |
453 | |
454 | await producer({ backpack, bakes, slash, targets, fabricator, output, binName }); |
455 | |
456 | if (hostPlatform !== 'win') { |
457 | await plusx(output); |
458 | } |
459 | |
460 | shutdown(); |
461 | |
462 | } |
463 |
Built with git-ssb-web