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