git ssb

0+

cel-desktop / ssb-pkg



Tree: 1eb144e388da5f1a3466703f56ac4ab591d700fc

Files: 1eb144e388da5f1a3466703f56ac4ab591d700fc / lib / index.js

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

Built with git-ssb-web