git ssb

0+

cel-desktop / ssb-pkg



Tree: 4c58f1c6c3ddb86a97f27f0c84681d201ef1f75f

Files: 4c58f1c6c3ddb86a97f27f0c84681d201ef1f75f / lib / index.js

11599 bytesRaw
1/* eslint-disable require-atomic-updates */
2
3import { exists, mkdirp, readFile, remove, stat } from 'fs-extra';
4import { log, wasReported } from './log.js';
5import { need, system } from 'pkg-fetch';
6import assert from 'assert';
7import help from './help';
8import { isPackageJson } from '../prelude/common.js';
9import minimist from 'minimist';
10import packer from './packer.js';
11import path from 'path';
12import { plusx } from './chmod.js';
13import producer from './producer.js';
14import refine from './refiner.js';
15import { shutdown } from './fabricator.js';
16import { version } from '../package.json';
17import walk from './walker.js';
18
19function isConfiguration (file) {
20 return isPackageJson(file) || file.endsWith('.config.json');
21}
22
23// http://www.openwall.com/lists/musl/2012/12/08/4
24
25const { hostArch, hostPlatform, isValidNodeRange, knownArchs,
26 knownPlatforms, toFancyArch, toFancyPlatform } = system;
27const hostNodeRange = 'node' + process.version.match(/^v(\d+)/)[1];
28
29function 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
63function stringifyTarget (target) {
64 const { nodeRange, platform, arch } = target;
65 return `${nodeRange}-${platform}-${arch}`;
66}
67
68function 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
90function 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
98function fabricatorForTarget (target) {
99 const { nodeRange, arch } = target;
100 return { nodeRange, platform: hostPlatform, arch };
101}
102
103const dryRunResults = {};
104
105async 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
112const targetsCache = {};
113
114async 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
123export 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