create-pkgbld 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ export default function getGitRoot(): Promise<string>;
package/dist/index.cjs CHANGED
@@ -6,70 +6,95 @@ var path = require('path');
6
6
  var fs = require('fs/promises');
7
7
  var userName = require('git-user-name');
8
8
  var gitConfig = require('parse-git-config');
9
- var minimist = require('minimist');
9
+ var cleye = require('cleye');
10
10
  var kleur = require('kleur');
11
+ require('typia');
12
+ var stringArgv = require('string-argv');
13
+ var childProcess = require('node:child_process');
14
+ var util = require('node:util');
11
15
 
12
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
16
+ async function getGitRoot() {
17
+ const exec = util.promisify(childProcess.exec);
18
+ const { stdout } = await exec('git rev-parse --show-toplevel');
19
+ return stdout.trim();
20
+ }
21
+
22
+ const done = Symbol('done');
23
+ const formats = ['amd', 'cjs', 'es', 'iife', 'system', 'umd'];
24
+ const pkgbldBinaries = ['pkgbld', 'pkgbld-internal', 'node ../pkgbld/dist/index.js'];
13
25
  async function execute() {
14
- await reportVersion();
15
- const targetDir = getTargetDir();
26
+ const version = await reportVersion();
27
+ const args = cleye.cli({
28
+ name: 'create-pkgbld',
29
+ version,
30
+ parameters: [
31
+ '[package name]'
32
+ ],
33
+ flags: {
34
+ quite: {
35
+ type: Boolean,
36
+ description: 'Quite mode',
37
+ default: false
38
+ }
39
+ }
40
+ });
41
+ args.showVersion();
42
+ const targetDir = path.join(process.cwd(), args._.packageName ?? '.');
16
43
  console.log(kleur.grey(pad16plus('Target Directory', 0)) + kleur.white(targetDir));
17
44
  const pkg = await readPackage(targetDir);
18
45
  console.log(kleur.grey(pad16plus('Mode', 0)) + kleur.white(pkg.mode));
19
46
  const packageName = path.basename(targetDir);
20
47
  let cancelled = false;
21
- const options = getBasicOptions(packageName);
22
- options.push(...await getGitOptions(packageName));
48
+ const options = getBasicOptions(packageName, pkg);
49
+ options.push(...await getGitOptions(targetDir));
50
+ options.push(...getPkgbldOptions(pkg.pkg));
23
51
  const state = getOptionsValue(options);
24
- for (;;) {
25
- const topLevelAction = await prompts({
26
- type: 'select',
27
- name: 'value',
28
- message: 'Select an option to change, Done to execute, Ctrl+C to cancel',
29
- choices: [
30
- { title: kleur.green('Done'), description: `${pkg.mode === 'update' ? 'Update' : 'Create'} package`, value: 'done' },
31
- ...options.map(mapOption(state))
32
- ],
33
- initial: 0
34
- }, { onCancel });
35
- if (cancelled) {
36
- process.exit(-1);
37
- }
38
- if (topLevelAction.value === 'done') {
39
- break;
40
- }
41
- let option = options.find(item => item.field === topLevelAction.value);
42
- let mutateObject = state;
43
- if ('items' in option) {
44
- const nextLevelAction = await prompts([{
45
- type: 'select',
46
- name: 'value',
47
- message: option.title,
48
- choices: option.items.map(mapOption(state[option.field]))
49
- }], { onCancel });
52
+ if (!args.flags.quite) {
53
+ for (;;) {
54
+ const topLevelAction = await prompts({
55
+ type: 'select',
56
+ name: 'value',
57
+ message: 'Select an option to change, Done to execute, Escape to cancel',
58
+ choices: [
59
+ { title: kleur.green('Done'), description: `${pkg.mode === 'update' ? 'Update' : 'Create'} package`, value: done },
60
+ ...options.map(mapOption(state))
61
+ ],
62
+ initial: 0
63
+ }, { onCancel });
50
64
  if (cancelled) {
51
65
  process.exit(-1);
52
66
  }
53
- if (option.mutateInnerObject) {
54
- mutateObject = state[option.field];
67
+ if (topLevelAction.value === done) {
68
+ break;
55
69
  }
56
- option = option.items.find(item => item.field === nextLevelAction.value);
70
+ let option = options.find(item => item.field === topLevelAction.value);
71
+ let mutateObject = state;
72
+ while ('items' in option) {
73
+ const nextLevelAction = await prompts([{
74
+ type: 'select',
75
+ name: 'value',
76
+ message: option.title,
77
+ choices: option.items.map(mapOption(option.mutateInnerObject ? state[option.field] : state))
78
+ }], { onCancel });
79
+ if (cancelled) {
80
+ process.exit(-1);
81
+ }
82
+ if (option.mutateInnerObject) {
83
+ mutateObject = state[option.field];
84
+ }
85
+ option = option.items.find(item => item.field === nextLevelAction.value);
86
+ }
87
+ const action = await prompts(getPromptOption(option, mutateObject), { onCancel });
88
+ if (cancelled) {
89
+ process.exit(-1);
90
+ }
91
+ mutateObject[option.field] = action[option.field];
57
92
  }
58
- const action = await prompts([{
59
- type: 'text',
60
- name: option.field,
61
- message: option.title,
62
- initial: state[option.field]
63
- }], { onCancel });
64
93
  if (cancelled) {
65
94
  process.exit(-1);
66
95
  }
67
- mutateObject[option.field] = action[option.field];
68
- }
69
- if (cancelled) {
70
- process.exit(-1);
71
96
  }
72
- Object.assign(pkg.pkg, state);
97
+ updatePackage(pkg, state);
73
98
  pkg.readme ??= `# ${state.name}`;
74
99
  await writePackage(targetDir, pkg);
75
100
  function onCancel() {
@@ -77,121 +102,430 @@ async function execute() {
77
102
  }
78
103
  }
79
104
  execute();
105
+ function updatePackage(pkg, options) {
106
+ const order = new Set([
107
+ 'private',
108
+ 'type',
109
+ 'version',
110
+ 'name',
111
+ 'description',
112
+ 'license',
113
+ 'author',
114
+ 'bin',
115
+ 'main',
116
+ 'module',
117
+ 'exports',
118
+ 'types',
119
+ 'typings',
120
+ 'files',
121
+ 'engines',
122
+ 'repository',
123
+ 'bugs',
124
+ 'homepage',
125
+ 'keywords',
126
+ 'scripts',
127
+ 'dependencies',
128
+ 'devDependencies',
129
+ 'peerDependencies',
130
+ 'optionalDependencies',
131
+ 'publishConfig'
132
+ ]);
133
+ const newPkg = {};
134
+ for (const key of order) {
135
+ if (key in options || key in pkg.pkg) {
136
+ newPkg[key] = treatKey(key);
137
+ }
138
+ }
139
+ for (const key in pkg.pkg) {
140
+ if (!order.has(key)) {
141
+ newPkg[key] = pkg.pkg[key];
142
+ }
143
+ }
144
+ pkg.pkg = newPkg;
145
+ function treatKey(key) {
146
+ if (key === 'scripts') {
147
+ ({ ...pkg.pkg.scripts });
148
+ console.log(options);
149
+ getScriptValue(options.pkgbld);
150
+ }
151
+ return options[key] ?? pkg.pkg[key];
152
+ }
153
+ }
154
+ function getScriptValue(pkgbld) {
155
+ const binary = pkgbld.pkgbldBinary;
156
+ const extraArgs = pkgbld.extraParameters;
157
+ const pkgBldCopy = { ...pkgbld };
158
+ delete pkgBldCopy['pkgbldBinary'];
159
+ delete pkgBldCopy['extraParameters'];
160
+ return `${binary} ${asCommandLineArgs(pkgBldCopy)} ${extraArgs}`;
161
+ }
162
+ function getPromptOption(option, mutateObject) {
163
+ const value = mutateObject[option.field];
164
+ const type = option.type ?? 'text';
165
+ const promptOption = {
166
+ type,
167
+ name: option.field,
168
+ message: option.title,
169
+ initial: (Array.isArray(value) ? value.join(',') : value) ?? ''
170
+ };
171
+ if (type === 'multiselect') {
172
+ promptOption.choices = 'list' in option ? option.list.map(item => ({
173
+ title: item,
174
+ value: item,
175
+ selected: value.includes(item)
176
+ })) : [];
177
+ }
178
+ if (type === 'select') {
179
+ promptOption.choices = 'list' in option ? option.list.map(item => ({
180
+ title: item,
181
+ value: item
182
+ })) : [];
183
+ promptOption.initial = promptOption.choices.findIndex(item => item.value === promptOption.initial);
184
+ }
185
+ return promptOption;
186
+ }
80
187
  function mapOption(state) {
81
188
  return (option) => {
82
189
  const fieldValue = state[option.field];
83
190
  return {
84
- title: pad16plus(option.title) + kleur.magenta('items' in option ? viewObject(option.items, fieldValue) : fieldValue),
191
+ title: pad16plus(option.title) + kleur.grey('items' in option ? getPrintString(option.items, (option.mutateInnerObject ? fieldValue : state)) : fieldValue ?? ''),
85
192
  value: option.field
86
193
  };
87
194
  };
88
195
  }
89
- function viewObject(items, json) {
196
+ function getPrintString(items, json) {
90
197
  return items
91
- .map(item => kleur.grey(item.title) + ' ' + kleur.white(json[item.field]))
198
+ .filter(item => item.field in json && json[item.field] && (Array.isArray(json[item.field]) ? json[item.field].length > 0 : true))
199
+ .map(item => kleur.grey(item.title) + ' ' + (kleur.white('items' in item ?
200
+ `[${getPrintString(item.items, (item.mutateInnerObject ? json[item.field] :
201
+ json))}]` : json[item.field])))
92
202
  .join(', ');
93
203
  }
94
204
  function getOptionsValue(options) {
95
- return options.reduce((accumulator, item) => ({
96
- ...accumulator,
97
- [item.field]: 'items' in item ? getOptionsValue(item.items) : item.initialValue
98
- }), {});
205
+ const result = {};
206
+ for (const item of options) {
207
+ if ('items' in item) {
208
+ const value = getOptionsValue(item.items);
209
+ if (item.mutateInnerObject) {
210
+ result[item.field] = value;
211
+ }
212
+ else {
213
+ Object.assign(result, value);
214
+ }
215
+ }
216
+ else {
217
+ result[item.field] = item.initialValue;
218
+ }
219
+ }
220
+ return result;
99
221
  }
100
222
  async function reportVersion() {
101
223
  const createPkgBldPackage = await readPackage(path.resolve(__dirname, '..'));
102
- console.log(kleur.grey(pad16plus('create-pkgbld', 0)) + kleur.white('v' + createPkgBldPackage.pkg.version));
224
+ const version = createPkgBldPackage.pkg.version ?? '<unknown>';
225
+ return version;
103
226
  }
104
- function getTargetDir() {
105
- const parsedArgs = minimist(process.argv.slice(2));
106
- const targetDir = path.join(process.cwd(), parsedArgs._[0] ?? '.');
107
- return targetDir;
108
- }
109
- async function getGitOptions(packageName) {
110
- const options = [];
227
+ async function getGitOptions(targetDir) {
111
228
  try {
112
229
  const gitCfg = await gitConfig();
113
230
  if (gitCfg) {
114
231
  const url = gitCfg['remote "origin"'].url;
115
- options.push({
116
- title: 'Homepage',
117
- field: 'homepage',
118
- initialValue: url.replace('.git', `/blob/main/${packageName}/README.md`)
119
- });
120
- options.push({
121
- title: 'Repository',
122
- field: 'repository',
123
- items: [{
124
- title: 'Type',
125
- field: 'type',
126
- initialValue: 'git'
127
- }, {
128
- title: 'Url',
129
- field: 'url',
130
- initialValue: `git+${url}`
131
- }],
132
- mutateInnerObject: true
133
- });
134
- options.push({
135
- title: 'Bugs',
136
- field: 'bugs',
137
- initialValue: url.replace('.git', '/issues')
138
- });
232
+ const root = await getGitRoot();
233
+ const directory = path.relative(root, targetDir);
234
+ return [{
235
+ title: 'Git',
236
+ field: 'git',
237
+ mutateInnerObject: false,
238
+ items: [{
239
+ title: 'Homepage',
240
+ field: 'homepage',
241
+ initialValue: url.replace('.git', `/blob/main${directory ? '/' + directory : ''}/README.md`)
242
+ }, {
243
+ title: 'Repository',
244
+ field: 'repository',
245
+ items: [{
246
+ title: 'Type',
247
+ field: 'type',
248
+ initialValue: 'git'
249
+ }, {
250
+ title: 'Url',
251
+ field: 'url',
252
+ initialValue: `git+${url}`
253
+ }, {
254
+ title: 'Directory',
255
+ field: 'directory',
256
+ initialValue: directory || undefined
257
+ }],
258
+ mutateInnerObject: true
259
+ }, {
260
+ title: 'Bugs',
261
+ field: 'bugs',
262
+ initialValue: url.replace('.git', '/issues')
263
+ }]
264
+ }];
139
265
  }
140
266
  }
141
267
  catch (e) {
142
268
  /* ignore */
143
269
  }
144
- return options;
270
+ return [];
145
271
  }
146
- function getBasicOptions(packageName) {
272
+ function getPkgbldOptions(pkg) {
273
+ let args = {
274
+ formats: ['es', 'cjs'],
275
+ umd: [],
276
+ compress: ['umd'],
277
+ sourcemaps: ['umd'],
278
+ preprocess: [],
279
+ dir: 'dist',
280
+ sourceDir: 'src',
281
+ bin: undefined,
282
+ includeExternals: false,
283
+ eject: false,
284
+ noTsConfig: false,
285
+ noUpdatePackageJson: false
286
+ };
287
+ function CommaSeparatedString(value) {
288
+ return value.split(',').map((arg) => arg.trim());
289
+ }
290
+ const cmd = pkg.scripts?.build ?? '';
291
+ let binary = 'pkgbld';
292
+ if (cmd) {
293
+ if (cmd.startsWith('pkgbld-internal')) {
294
+ binary = 'pkgbld-internal';
295
+ }
296
+ else if (cmd.startsWith('node ../pkgbld/dist/index.js')) {
297
+ binary = 'node ../pkgbld/dist/index.js';
298
+ }
299
+ else if (!cmd.startsWith('pkgbld')) {
300
+ const naiveArgs = cmd.split(' ');
301
+ if (naiveArgs.length > 1 && naiveArgs[0] === 'node') {
302
+ binary = naiveArgs[0] + ' ' + naiveArgs[1];
303
+ }
304
+ else if (naiveArgs.length > 0) {
305
+ binary = naiveArgs[0]; // ?????
306
+ }
307
+ }
308
+ const parsedArgs = cleye.cli({
309
+ help: false,
310
+ flags: {
311
+ umd: {
312
+ type: CommaSeparatedString,
313
+ default: args.umd
314
+ },
315
+ compress: {
316
+ type: CommaSeparatedString,
317
+ default: args.compress
318
+ },
319
+ sourcemaps: {
320
+ type: CommaSeparatedString,
321
+ default: args.sourcemaps
322
+ },
323
+ formats: {
324
+ type: CommaSeparatedString,
325
+ default: args.formats
326
+ },
327
+ preprocess: {
328
+ type: CommaSeparatedString,
329
+ default: args.preprocess
330
+ },
331
+ dir: {
332
+ type: String,
333
+ default: args.dir
334
+ },
335
+ sourceDir: {
336
+ type: String,
337
+ default: args.sourceDir
338
+ },
339
+ bin: {
340
+ type: CommaSeparatedString,
341
+ default: args.bin
342
+ },
343
+ includeExternals: {
344
+ type: Boolean,
345
+ default: args.includeExternals
346
+ },
347
+ eject: {
348
+ type: Boolean,
349
+ default: args.eject
350
+ },
351
+ noTsConfig: {
352
+ type: Boolean,
353
+ default: args.noTsConfig
354
+ },
355
+ noUpdatePackageJson: {
356
+ type: Boolean,
357
+ default: args.noUpdatePackageJson
358
+ }
359
+ }
360
+ }, undefined, stringArgv.parseArgsStringToArgv(cmd));
361
+ args = parsedArgs.flags;
362
+ args.extraParameters = asCommandLineArgs(parsedArgs.unknownFlags);
363
+ }
147
364
  return [{
148
- title: 'Package Name',
149
- field: 'name',
150
- initialValue: packageName
151
- }, {
152
- title: 'Version',
153
- field: 'version',
154
- initialValue: '0.0.1'
155
- }, {
156
- title: 'Description',
157
- field: 'description',
158
- initialValue: ''
159
- }, {
160
- title: 'License',
161
- field: 'license',
162
- initialValue: 'MIT'
163
- }, {
164
- title: 'Author',
165
- field: 'author',
166
- initialValue: userName() ?? ''
167
- }, {
168
- title: 'Destination folder',
169
- field: 'dest',
170
- initialValue: 'dest'
171
- }, {
172
- title: 'Source folder',
173
- field: 'src',
174
- initialValue: 'src'
365
+ title: 'pkgbld',
366
+ field: 'pkgbld',
367
+ mutateInnerObject: true,
368
+ items: [{
369
+ title: 'Destination folder',
370
+ field: 'dest',
371
+ initialValue: args.dir
372
+ }, {
373
+ title: 'Source folder',
374
+ field: 'src',
375
+ initialValue: args.sourceDir
376
+ }, {
377
+ title: 'UMD exports',
378
+ field: 'umd',
379
+ initialValue: args.umd,
380
+ type: 'list'
381
+ }, {
382
+ title: 'Compress formats',
383
+ field: 'compress',
384
+ initialValue: args.compress,
385
+ type: 'multiselect',
386
+ list: formats
387
+ }, {
388
+ title: 'Sorcemaps formats',
389
+ field: 'sourcemaps',
390
+ initialValue: args.sourcemaps,
391
+ type: 'multiselect',
392
+ list: formats
393
+ }, {
394
+ title: 'Formats',
395
+ field: 'formats',
396
+ initialValue: args.formats,
397
+ type: 'multiselect',
398
+ list: formats
399
+ }, {
400
+ title: 'Preprocess formats',
401
+ field: 'preprocess',
402
+ initialValue: args.preprocess,
403
+ type: 'multiselect',
404
+ list: formats
405
+ }, {
406
+ title: 'Binaries',
407
+ field: 'bin',
408
+ initialValue: args.bin,
409
+ type: 'list'
410
+ }, {
411
+ title: 'Include externals',
412
+ field: 'includeExternals',
413
+ type: 'toggle',
414
+ initialValue: args.includeExternals
415
+ }, {
416
+ title: 'Eject config',
417
+ field: 'eject',
418
+ type: 'toggle',
419
+ initialValue: args.eject
420
+ }, {
421
+ title: 'Create tsconfig',
422
+ field: 'tsConfig',
423
+ type: 'toggle',
424
+ initialValue: !args.noTsConfig
425
+ }, {
426
+ title: 'Update package.json',
427
+ field: 'updatePackageJson',
428
+ type: 'toggle',
429
+ initialValue: !args.noUpdatePackageJson
430
+ }, {
431
+ title: 'Extra parameters',
432
+ field: 'extraParameters',
433
+ type: 'text',
434
+ initialValue: args.extraParameters
435
+ }, {
436
+ title: 'Pkgbld Binary',
437
+ field: 'pkgbldBinary',
438
+ type: 'select',
439
+ list: pkgbldBinaries.includes(binary) ? pkgbldBinaries : [...pkgbldBinaries, binary],
440
+ initialValue: binary
441
+ }]
175
442
  }];
176
443
  }
444
+ function asCommandLineArgs(parsedArgs) {
445
+ return Object.entries(parsedArgs)
446
+ .flatMap(([key, value]) => asArray(value)
447
+ .filter(Boolean)
448
+ .map(value => `--${key}${typeof value === 'string' ? `=${value}` : ''}`)).join(' ');
449
+ }
450
+ function asArray(value) {
451
+ return Array.isArray(value) ? value : [value];
452
+ }
453
+ function getBasicOptions(packageName, pkg) {
454
+ return [{
455
+ title: 'General',
456
+ field: 'general',
457
+ items: [{
458
+ title: 'Package Name',
459
+ field: 'name',
460
+ initialValue: chooseValue(pkg.pkg.name, packageName)
461
+ }, {
462
+ title: 'Version',
463
+ field: 'version',
464
+ initialValue: chooseValue(pkg.pkg.version, '0.0.1')
465
+ }, {
466
+ title: 'Description',
467
+ field: 'description',
468
+ initialValue: chooseValue(pkg.pkg.description, '')
469
+ }, {
470
+ title: 'License',
471
+ field: 'license',
472
+ initialValue: chooseValue(pkg.pkg.license, 'MIT')
473
+ }, {
474
+ title: 'Author',
475
+ field: 'author',
476
+ initialValue: chooseValue(pkg.pkg.author, userName() ?? '')
477
+ }],
478
+ mutateInnerObject: false
479
+ }];
480
+ function chooseValue(pkgValue, defaultValue) {
481
+ return pkg.mode === 'update' ? pkgValue : defaultValue;
482
+ }
483
+ }
177
484
  function pad16plus(value, indent = 4, offset = 3) {
178
485
  return value + ''.padEnd(offset - Math.floor((value.length + indent) / 8), '\t');
179
486
  }
180
487
  async function readPackage(dir) {
181
488
  const packageFileName = path.resolve(dir, 'package.json');
182
489
  const readmeFileName = path.resolve(dir, 'README.md');
490
+ const defaultPkg = {};
183
491
  try {
184
492
  const pkgFile = await fs.readFile(packageFileName);
185
493
  const readmeFile = await fs.readFile(readmeFileName);
494
+ const pkg = JSON.parse(pkgFile.toString());
495
+ const isValidPackageJson = (input => {
496
+ const $io0 = input => (undefined === input.private || "boolean" === typeof input.private) && (undefined === input.version || "string" === typeof input.version) && (undefined === input.name || "string" === typeof input.name) && (undefined === input.license || "string" === typeof input.license) && (undefined === input.author || "string" === typeof input.author) && (undefined === input.description || "string" === typeof input.description) && (undefined === input.scripts || "object" === typeof input.scripts && null !== input.scripts && false === Array.isArray(input.scripts) && $io1(input.scripts)) && (undefined === input.dependencies || "object" === typeof input.dependencies && null !== input.dependencies && false === Array.isArray(input.dependencies) && $io2(input.dependencies)) && (undefined === input.devDependencies || "object" === typeof input.devDependencies && null !== input.devDependencies && false === Array.isArray(input.devDependencies) && $io2(input.devDependencies)) && (undefined === input.peerDependencies || "object" === typeof input.peerDependencies && null !== input.peerDependencies && false === Array.isArray(input.peerDependencies) && $io2(input.peerDependencies)) && Object.keys(input).every(key => {
497
+ if (["private", "version", "name", "license", "author", "description", "scripts", "dependencies", "devDependencies", "peerDependencies"].some(prop => key === prop))
498
+ return true;
499
+ const value = input[key];
500
+ if (undefined === value)
501
+ return true;
502
+ if (RegExp(/(.*)/).test(key))
503
+ return true;
504
+ return true;
505
+ });
506
+ const $io1 = input => (undefined === input.build || "string" === typeof input.build) && (undefined === input.lint || "string" === typeof input.lint);
507
+ const $io2 = input => Object.keys(input).every(key => {
508
+ const value = input[key];
509
+ if (undefined === value)
510
+ return true;
511
+ if (RegExp(/(.*)/).test(key))
512
+ return "string" === typeof value;
513
+ return true;
514
+ });
515
+ return "object" === typeof input && null !== input && false === Array.isArray(input) && $io0(input);
516
+ })(pkg);
517
+ if (!isValidPackageJson) {
518
+ console.error('Invalid package.json');
519
+ }
186
520
  return {
187
- pkg: JSON.parse(pkgFile.toString()),
521
+ pkg: isValidPackageJson ? pkg : defaultPkg,
188
522
  readme: readmeFile.toString(),
189
523
  mode: 'update'
190
524
  };
191
525
  }
192
526
  catch (e) { /**/ }
193
527
  return {
194
- pkg: {},
528
+ pkg: defaultPkg,
195
529
  readme: '',
196
530
  mode: 'create'
197
531
  };