@jujulego/jill 3.0.0-alpha.1 → 3.0.0-alpha.3

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.
Files changed (104) hide show
  1. package/README.md +1 -2
  2. package/bin/jill.js +1 -0
  3. package/dist/TaskName.js +33 -0
  4. package/dist/TaskName.js.map +1 -0
  5. package/dist/inked.js +66 -0
  6. package/dist/inked.js.map +1 -0
  7. package/dist/instrument.js +8 -0
  8. package/dist/instrument.js.map +1 -0
  9. package/dist/list.ink.js +50 -0
  10. package/dist/list.ink.js.map +1 -0
  11. package/dist/main.js +44 -35
  12. package/dist/main.js.map +1 -1
  13. package/dist/parser.js +2104 -0
  14. package/dist/parser.js.map +1 -0
  15. package/dist/planner.service.js +58 -0
  16. package/dist/planner.service.js.map +1 -0
  17. package/dist/task-exec.ink.js +539 -0
  18. package/dist/task-exec.ink.js.map +1 -0
  19. package/dist/task-plan.ink.js +114 -0
  20. package/dist/task-plan.ink.js.map +1 -0
  21. package/dist/tree.ink.js +185 -0
  22. package/dist/tree.ink.js.map +1 -0
  23. package/dist/tsconfig.build.tsbuildinfo +1 -1
  24. package/package.json +46 -45
  25. package/dist/ajv.config.d.ts +0 -3
  26. package/dist/commands/each.d.ts +0 -25
  27. package/dist/commands/exec.d.ts +0 -26
  28. package/dist/commands/group.d.ts +0 -16
  29. package/dist/commands/list.d.ts +0 -30
  30. package/dist/commands/run.d.ts +0 -24
  31. package/dist/commands/tree.d.ts +0 -6
  32. package/dist/commons/context.service.d.ts +0 -23
  33. package/dist/commons/git.service.d.ts +0 -62
  34. package/dist/commons/logger/log.gateway.d.ts +0 -18
  35. package/dist/commons/logger/parameters.d.ts +0 -2
  36. package/dist/commons/logger/thread.gateway.d.ts +0 -12
  37. package/dist/commons/logger/types.d.ts +0 -2
  38. package/dist/commons/logger.service.d.ts +0 -1
  39. package/dist/config/config-loader.d.ts +0 -4
  40. package/dist/config/config-options.d.ts +0 -5
  41. package/dist/config/types.d.ts +0 -8
  42. package/dist/config/utils.d.ts +0 -5
  43. package/dist/constants.d.ts +0 -1
  44. package/dist/core.plugin-CxgfxFUI.js +0 -642
  45. package/dist/core.plugin-CxgfxFUI.js.map +0 -1
  46. package/dist/core.plugin.d.ts +0 -2
  47. package/dist/filters/affected.filter.d.ts +0 -12
  48. package/dist/filters/pipeline.d.ts +0 -11
  49. package/dist/filters/private.filter.d.ts +0 -7
  50. package/dist/filters/scripts.filter.d.ts +0 -8
  51. package/dist/index.d.ts +0 -45
  52. package/dist/index.js +0 -35
  53. package/dist/index.js.map +0 -1
  54. package/dist/ink-command-CsbkuRbm.js +0 -2071
  55. package/dist/ink-command-CsbkuRbm.js.map +0 -1
  56. package/dist/ink.config.d.ts +0 -3
  57. package/dist/inversify.config.d.ts +0 -4
  58. package/dist/jill.application-DNJpmnCF.js +0 -637
  59. package/dist/jill.application-DNJpmnCF.js.map +0 -1
  60. package/dist/jill.application.d.ts +0 -19
  61. package/dist/main.d.ts +0 -1
  62. package/dist/middlewares/load-project.d.ts +0 -21
  63. package/dist/middlewares/load-workspace.d.ts +0 -20
  64. package/dist/modules/command.d.ts +0 -20
  65. package/dist/modules/ink-command.d.ts +0 -11
  66. package/dist/modules/middleware.d.ts +0 -8
  67. package/dist/modules/module.d.ts +0 -7
  68. package/dist/modules/plugin-loader.service.d.ts +0 -10
  69. package/dist/modules/plugin.d.ts +0 -14
  70. package/dist/modules/service.d.ts +0 -8
  71. package/dist/modules/task-command.d.ts +0 -14
  72. package/dist/project/project.d.ts +0 -27
  73. package/dist/project/project.repository.d.ts +0 -15
  74. package/dist/project/types.d.ts +0 -1
  75. package/dist/project/workspace.d.ts +0 -41
  76. package/dist/tasks/command-task.d.ts +0 -15
  77. package/dist/tasks/errors.d.ts +0 -4
  78. package/dist/tasks/script-task.d.ts +0 -27
  79. package/dist/tasks/task-expression.service.d.ts +0 -25
  80. package/dist/tasks/task-manager.config.d.ts +0 -3
  81. package/dist/types.d.ts +0 -11
  82. package/dist/ui/hooks/useFlatTaskTree.d.ts +0 -14
  83. package/dist/ui/hooks/useIsVerbose.d.ts +0 -1
  84. package/dist/ui/hooks/useStdoutDimensions.d.ts +0 -4
  85. package/dist/ui/layout.d.ts +0 -5
  86. package/dist/ui/list.d.ts +0 -5
  87. package/dist/ui/static-logs.d.ts +0 -1
  88. package/dist/ui/task-name.d.ts +0 -5
  89. package/dist/ui/task-spinner.d.ts +0 -5
  90. package/dist/ui/task-tree-completed.d.ts +0 -5
  91. package/dist/ui/task-tree-full-spinner.d.ts +0 -5
  92. package/dist/ui/task-tree-scrollable-spinner.d.ts +0 -5
  93. package/dist/ui/task-tree-spinner.d.ts +0 -5
  94. package/dist/ui/task-tree-stats.d.ts +0 -5
  95. package/dist/ui/workspace-tree.d.ts +0 -8
  96. package/dist/utils/events.d.ts +0 -3
  97. package/dist/utils/exit.d.ts +0 -4
  98. package/dist/utils/import.d.ts +0 -4
  99. package/dist/utils/json.d.ts +0 -1
  100. package/dist/utils/streams.d.ts +0 -3
  101. package/dist/utils/string.d.ts +0 -2
  102. package/dist/utils/worker-cache.d.ts +0 -3
  103. package/dist/workspace-tree-VWKE0B6b.js +0 -1120
  104. package/dist/workspace-tree-VWKE0B6b.js.map +0 -1
package/dist/parser.js ADDED
@@ -0,0 +1,2104 @@
1
+ ;{try{(function(){var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="870e33b2-51a5-4b3d-aff5-43027ce19814",e._sentryDebugIdIdentifier="sentry-dbid-870e33b2-51a5-4b3d-aff5-43027ce19814");})();}catch(e){}};!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"3.0.0-alpha.3"};}catch(e){}}();import { TaskManager, plan, SpawnTask, GroupTask, TaskSet, ParallelGroup, FallbackGroup, SequenceGroup } from '@jujulego/tasks';
2
+ import { token$, inject$, asyncScope$ } from '@kyrielle/injector';
3
+ import { pipe$, waitFor$, filter$, observable$, off$, once$, map$, collect$, asyncIterator$, var$, flow$ } from 'kyrielle';
4
+ import { logger$, withTimestamp, withLabel, qLogDelay, LogLevel, LogGateway, logDelay$, toStderr } from '@kyrielle/logger';
5
+ import { startSpan, getActiveSpan, updateSpanName, getRootSpan, startInactiveSpan } from '@sentry/node';
6
+ import path from 'node:path';
7
+ import cp from 'node:child_process';
8
+ import process$1 from 'node:process';
9
+ import chalk from 'chalk';
10
+ import { satisfies, compare, parse } from 'semver';
11
+ import slugify from 'slugify';
12
+ import yargs from 'yargs';
13
+ import fs from 'node:fs';
14
+ import { PathScurry } from 'path-scurry';
15
+ import { _ } from '@swc/helpers/_/_apply_decs_2203_r';
16
+ import { Glob } from 'glob';
17
+ import normalize from 'normalize-package-data';
18
+ import moo from 'moo';
19
+ import { qjson, qprop, q$, defineQuickFormat, qerror, qarg, qwrap } from '@jujulego/quick-tag';
20
+ import Ajv from 'ajv';
21
+ import os from 'node:os';
22
+ import { cosmiconfig, defaultLoaders } from 'cosmiconfig';
23
+ import { chalkTemplateStderr } from 'chalk-template';
24
+
25
+ var version = "3.0.0-alpha.3";
26
+
27
+ function trace(fun, opts) {
28
+ const name = typeof opts === 'string' ? opts : opts.name;
29
+ const use = typeof opts === 'object' ? opts.use : (_, r)=>r;
30
+ return function(...args) {
31
+ return pipe$(startSpan({
32
+ name
33
+ }, ()=>fun.call(this, ...args)), (r)=>use(name, r));
34
+ };
35
+ }
36
+ function instrument(opts) {
37
+ return (target, context)=>{
38
+ const name = typeof opts === 'string' ? opts : opts?.name ?? context.name.toString();
39
+ const use = typeof opts === 'object' ? opts.use : (_, r)=>r;
40
+ return trace(target, {
41
+ name,
42
+ use
43
+ });
44
+ };
45
+ }
46
+ function traceAsyncGenerator(name, generator) {
47
+ const instrumented = {
48
+ ...generator,
49
+ next: async ()=>startSpan({
50
+ name,
51
+ op: 'iterator.next'
52
+ }, ()=>generator.next()),
53
+ [Symbol.asyncIterator]: ()=>instrumented
54
+ };
55
+ return instrumented;
56
+ }
57
+ function traceLoad(name, loader) {
58
+ return startSpan({
59
+ name: `load ${name}`,
60
+ op: 'import'
61
+ }, loader);
62
+ }
63
+
64
+ function command$5(module) {
65
+ const name = getCommandName(module);
66
+ const handler = trace(module.handler, 'cli.handler');
67
+ return (parser)=>parser.command({
68
+ ...module,
69
+ async handler (args) {
70
+ const activeSpan = getActiveSpan();
71
+ if (activeSpan) {
72
+ updateSpanName(getRootSpan(activeSpan), `jill ${name}`);
73
+ }
74
+ await handler(args);
75
+ }
76
+ });
77
+ }
78
+ function getCommandName(module) {
79
+ if (!module.command) {
80
+ return '[unknown]';
81
+ }
82
+ if (typeof module.command === 'string') {
83
+ return module.command;
84
+ }
85
+ return module.command[0];
86
+ }
87
+
88
+ // Tokens
89
+ const CONFIG = token$('Config', async ()=>{
90
+ const { ConfigService } = await Promise.resolve().then(function () { return config_service; });
91
+ return waitFor$(inject$(ConfigService, asyncScope$()).config$);
92
+ });
93
+ const CWD = token$('cwd', ()=>process$1.cwd());
94
+ const LOGGER = token$('Logger', ()=>logger$(withTimestamp()));
95
+ const PATH_SCURRY = token$('PathScurry', ()=>new PathScurry('/', {
96
+ fs
97
+ }));
98
+ const TASK_MANAGER = token$('TaskManager', async ()=>{
99
+ const manager = new TaskManager({
100
+ jobs: (await inject$(CONFIG, asyncScope$())).jobs,
101
+ logger: inject$(LOGGER)
102
+ });
103
+ let rootSpan = getActiveSpan();
104
+ if (rootSpan) rootSpan = getRootSpan(rootSpan);
105
+ const spans = new Map();
106
+ manager.events$.on('added', (task)=>{
107
+ // Main span
108
+ const span = startInactiveSpan({
109
+ name: task.name,
110
+ parentSpan: (task.group && spans.get(task.group.id)) ?? rootSpan,
111
+ op: 'task'
112
+ });
113
+ task.events$.on('completed', ({ status })=>{
114
+ span.setStatus({
115
+ code: status === 'done' ? 1 : 2
116
+ });
117
+ span.end();
118
+ });
119
+ spans.set(task.id, span);
120
+ // Status spans
121
+ let statusSpan = startInactiveSpan({
122
+ name: task.status,
123
+ parentSpan: span,
124
+ op: 'task.status'
125
+ });
126
+ task.events$.on('status', ({ status })=>{
127
+ statusSpan.end();
128
+ if (!task.completed) {
129
+ statusSpan = startInactiveSpan({
130
+ name: status,
131
+ parentSpan: span,
132
+ op: 'task.status'
133
+ });
134
+ }
135
+ });
136
+ });
137
+ return manager;
138
+ });
139
+
140
+ // Utils
141
+ function printJson(data, stream = process.stdout) {
142
+ if (stream.isTTY) {
143
+ stream.write(JSON.stringify(data, null, 2));
144
+ } else {
145
+ stream.write(JSON.stringify(data));
146
+ }
147
+ }
148
+
149
+ // Utils
150
+ function executeCommand(module) {
151
+ const prepare = trace(module.prepare, 'cli.prepare');
152
+ const execute = module.execute && trace(module.execute, 'cli.execute');
153
+ return command$5({
154
+ ...module,
155
+ builder (base) {
156
+ const parser = withPlanMode(base);
157
+ if (module.builder) {
158
+ return module.builder(parser);
159
+ } else {
160
+ return parser;
161
+ }
162
+ },
163
+ async handler (args) {
164
+ const tasks = await prepare(args);
165
+ if (args.plan) {
166
+ if (args.planMode === 'json') {
167
+ printJson(Array.from(plan(tasks)));
168
+ } else {
169
+ const { default: TaskPlanInk } = await traceLoad('TaskPlanInk', ()=>import('./task-plan.ink.js'));
170
+ await TaskPlanInk({
171
+ tasks
172
+ });
173
+ }
174
+ } else {
175
+ if (execute) {
176
+ await execute(args, tasks);
177
+ } else if (tasks.tasks.length > 0) {
178
+ const { default: TaskExecInk } = await traceLoad('TaskExecInk', ()=>import('./task-exec.ink.js'));
179
+ await TaskExecInk({
180
+ tasks,
181
+ verbose: [
182
+ 'verbose',
183
+ 'debug'
184
+ ].includes(args.verbose)
185
+ });
186
+ } else {
187
+ const logger = inject$(LOGGER);
188
+ logger.warning('No task found');
189
+ process$1.exitCode = 1;
190
+ }
191
+ }
192
+ }
193
+ });
194
+ }
195
+ function planCommand(module, tasks$) {
196
+ if ('prepare' in module) {
197
+ const prepare = trace(module.prepare, 'cli.prepare');
198
+ return command$5({
199
+ ...module,
200
+ builder (base) {
201
+ const parser = withPlanMode(base);
202
+ if (module.builder) {
203
+ return module.builder(parser);
204
+ } else {
205
+ return parser;
206
+ }
207
+ },
208
+ async handler (args) {
209
+ tasks$.mutate(await prepare(args));
210
+ }
211
+ });
212
+ } else {
213
+ return command$5({
214
+ ...module,
215
+ handler: ()=>undefined
216
+ });
217
+ }
218
+ }
219
+ // Utils
220
+ function withPlanMode(parser) {
221
+ return parser.option('plan', {
222
+ type: 'boolean',
223
+ default: false,
224
+ describe: 'Only prints tasks to be run'
225
+ }).option('plan-mode', {
226
+ type: 'string',
227
+ desc: 'Plan output mode',
228
+ choices: [
229
+ 'json',
230
+ 'list'
231
+ ],
232
+ default: 'list'
233
+ });
234
+ }
235
+
236
+ // Filter
237
+ function hasSomeScript$(scripts) {
238
+ return filter$((wks)=>{
239
+ if (!wks.manifest.scripts) {
240
+ return false;
241
+ }
242
+ return scripts.some((script)=>script in wks.manifest.scripts);
243
+ });
244
+ }
245
+ function hasEveryScript$(scripts) {
246
+ return filter$((wks)=>{
247
+ if (!wks.manifest.scripts) {
248
+ return false;
249
+ }
250
+ return scripts.every((script)=>script in wks.manifest.scripts);
251
+ });
252
+ }
253
+
254
+ // Utils
255
+ async function* combine(...generators) {
256
+ for (const gen of generators){
257
+ yield* gen;
258
+ }
259
+ }
260
+ function streamLines$(task, stream = 'stdout') {
261
+ return observable$((observer, signal)=>{
262
+ const off = off$();
263
+ let current = '';
264
+ // End
265
+ off.add(once$(task.events$, 'completed', ()=>{
266
+ if (current) observer.next(current);
267
+ observer.complete();
268
+ off.unsubscribe();
269
+ }));
270
+ // Abort
271
+ signal.addEventListener('abort', ()=>off.unsubscribe(), {
272
+ once: true
273
+ });
274
+ // Steam
275
+ off.add(task.events$.on(`stream.${stream}`, (chunk)=>{
276
+ const data = current + chunk.data.toString('utf-8');
277
+ const lines = data.split(/\r?\n/);
278
+ current = lines.pop() ?? '';
279
+ for (const line of lines){
280
+ observer.next(line);
281
+ }
282
+ }));
283
+ });
284
+ }
285
+
286
+ var _dec$4, _dec1$3, _dec2$1, _initProto$4;
287
+ _dec$4 = instrument('GitService.isAffected'), _dec1$3 = instrument('GitService.listBranches'), _dec2$1 = instrument('GitService.listTags');
288
+ class GitService {
289
+ static{
290
+ ({ e: [_initProto$4] } = _(this, [
291
+ [
292
+ _dec$4,
293
+ 2,
294
+ "isAffected"
295
+ ],
296
+ [
297
+ _dec1$3,
298
+ 2,
299
+ "listBranches"
300
+ ],
301
+ [
302
+ _dec2$1,
303
+ 2,
304
+ "listTags"
305
+ ]
306
+ ], []));
307
+ }
308
+ // Attributes
309
+ _manager = (_initProto$4(this), inject$(TASK_MANAGER));
310
+ _logger = inject$(LOGGER);
311
+ // Methods
312
+ /**
313
+ * Runs a git command inside a SpawnTask
314
+ *
315
+ * @param cmd
316
+ * @param args
317
+ * @param options
318
+ */ async command(cmd, args, options = {}) {
319
+ const opts = {
320
+ logger: this._logger,
321
+ ...options
322
+ };
323
+ // Create task
324
+ const task = new SpawnTask('git', [
325
+ cmd,
326
+ ...args
327
+ ], {
328
+ command: cmd,
329
+ hidden: true
330
+ }, opts);
331
+ task.events$.on('stream', ({ data })=>opts.logger.debug(data.toString('utf-8')));
332
+ (await this._manager).add(task);
333
+ return task;
334
+ }
335
+ /**
336
+ * Runs git branch
337
+ *
338
+ * @param args
339
+ * @param options
340
+ */ branch(args, options) {
341
+ return this.command('branch', args, options);
342
+ }
343
+ /**
344
+ * Runs git diff
345
+ *
346
+ * @param args
347
+ * @param options
348
+ */ diff(args, options) {
349
+ return this.command('diff', args, options);
350
+ }
351
+ /**
352
+ * Runs git tag
353
+ *
354
+ * @param args
355
+ * @param options
356
+ */ tag(args, options) {
357
+ return this.command('tag', args, options);
358
+ }
359
+ /**
360
+ * Uses git diff to detect if given files have been affected since given reference
361
+ *
362
+ * @param reference
363
+ * @param files
364
+ * @param opts
365
+ */ async isAffected(reference, files = [], opts) {
366
+ const task = await this.diff([
367
+ '--quiet',
368
+ reference,
369
+ '--',
370
+ ...files
371
+ ], opts);
372
+ return new Promise((resolve, reject)=>{
373
+ once$(task.events$, 'status.done', ()=>resolve(false));
374
+ once$(task.events$, 'status.failed', ()=>{
375
+ if (task.exitCode) {
376
+ resolve(true);
377
+ } else {
378
+ reject(new Error(`Task ${task.name} failed`));
379
+ }
380
+ });
381
+ });
382
+ }
383
+ /**
384
+ * List git branches
385
+ *
386
+ * @param args
387
+ * @param opts
388
+ */ async listBranches(args = [], opts) {
389
+ const task = await this.branch([
390
+ '-l',
391
+ ...args
392
+ ], opts);
393
+ return waitFor$(pipe$(streamLines$(task), map$((line)=>line.replace(/^[ *] /, '')), collect$()));
394
+ }
395
+ /**
396
+ * List git tags
397
+ *
398
+ * @param args
399
+ * @param opts
400
+ */ async listTags(args = [], opts) {
401
+ const task = await this.tag([
402
+ '-l',
403
+ ...args
404
+ ], opts);
405
+ return waitFor$(pipe$(streamLines$(task), collect$()));
406
+ }
407
+ }
408
+
409
+ // Filter
410
+ function isAffected$(opts) {
411
+ return (origin)=>{
412
+ return asyncIterator$(async function*() {
413
+ for await (const wks of origin){
414
+ const revision = await formatRevision(opts, wks);
415
+ if (await wks.isAffected(revision)) {
416
+ yield wks;
417
+ }
418
+ }
419
+ }());
420
+ };
421
+ }
422
+ async function formatRevision(opts, workspace) {
423
+ const git = inject$(GitService);
424
+ const logger = inject$(LOGGER).child(withLabel(workspace.name));
425
+ // Format revision
426
+ let result = opts.format;
427
+ result = result.replace(/(?<!\\)((?:\\\\)*)%name/g, `$1${workspace.name}`);
428
+ result = result.replace(/\\(.)/g, '$1');
429
+ // Ask git to complete it
430
+ const sortArgs = opts.sort ? [
431
+ '--sort',
432
+ opts.sort
433
+ ] : [];
434
+ // - search in branches
435
+ if (result.includes('*')) {
436
+ const branches = await git.listBranches([
437
+ ...sortArgs,
438
+ result
439
+ ], {
440
+ cwd: workspace.root,
441
+ logger: logger
442
+ });
443
+ if (branches.length > 0) {
444
+ result = branches[branches.length - 1];
445
+ }
446
+ }
447
+ // - search in tags
448
+ if (result.includes('*')) {
449
+ const tags = await git.listTags([
450
+ ...sortArgs,
451
+ result
452
+ ], {
453
+ cwd: workspace.root,
454
+ logger: logger
455
+ });
456
+ if (tags.length > 0) {
457
+ result = tags[tags.length - 1];
458
+ }
459
+ }
460
+ if (result !== opts.format) {
461
+ logger.verbose(`resolved ${opts.format} into ${result}`);
462
+ }
463
+ if (result.includes('*')) {
464
+ logger.warning(`no revision found matching ${result}, using fallback ${opts.fallback}`);
465
+ return opts.fallback;
466
+ }
467
+ return result;
468
+ }
469
+
470
+ // Filter
471
+ function isPrivate$(value) {
472
+ return filter$((wks)=>(wks.manifest.private ?? false) === value);
473
+ }
474
+
475
+ function mutex$() {
476
+ const count$ = var$(0);
477
+ return {
478
+ async acquire () {
479
+ let cnt = count$.defer();
480
+ while(cnt > 0){
481
+ cnt = await waitFor$(count$);
482
+ }
483
+ count$.mutate(cnt + 1);
484
+ },
485
+ release () {
486
+ const cnt = count$.defer();
487
+ if (cnt > 0) {
488
+ count$.mutate(cnt - 1);
489
+ }
490
+ }
491
+ };
492
+ }
493
+ async function with$(lock, fn) {
494
+ try {
495
+ await lock.acquire();
496
+ return await fn();
497
+ } finally{
498
+ lock.release();
499
+ }
500
+ }
501
+
502
+ // Class
503
+ class CommandTask extends SpawnTask {
504
+ workspace;
505
+ // Constructor
506
+ constructor(workspace, command, args, opts = {}){
507
+ let cmd = command;
508
+ if (opts.superCommand) {
509
+ if (typeof opts.superCommand === 'string') {
510
+ opts.superCommand = [
511
+ opts.superCommand
512
+ ];
513
+ }
514
+ if (opts.superCommand.length > 0) {
515
+ cmd = opts.superCommand[0];
516
+ args = [
517
+ ...opts.superCommand.slice(1),
518
+ command,
519
+ ...args
520
+ ];
521
+ }
522
+ }
523
+ super(cmd, args, {
524
+ workspace,
525
+ command
526
+ }, {
527
+ ...opts,
528
+ cwd: workspace.root,
529
+ env: {
530
+ FORCE_COLOR: '1',
531
+ ...opts.env
532
+ }
533
+ }), this.workspace = workspace;
534
+ this._logStreams();
535
+ }
536
+ // Methods
537
+ _logStreams() {
538
+ const off = off$(streamLines$(this, 'stdout').subscribe((line)=>this.logger$.info(line)), streamLines$(this, 'stderr').subscribe((line)=>this.logger$.info(line)));
539
+ once$(this.events$, 'completed', ()=>{
540
+ off.unsubscribe();
541
+ });
542
+ }
543
+ }
544
+ // Utils
545
+ function isCommandCtx(ctx) {
546
+ return 'workspace' in ctx && 'command' in ctx;
547
+ }
548
+
549
+ class ClientError extends Error {
550
+ name = 'ClientError';
551
+ constructor(message){
552
+ super(message);
553
+ }
554
+ }
555
+
556
+ // Utils
557
+ function capitalize(txt) {
558
+ return txt.charAt(0).toUpperCase() + txt.substring(1).toLowerCase();
559
+ }
560
+ function splitCommandLine(line) {
561
+ line = line.trim();
562
+ const parts = [];
563
+ let current_cote = '';
564
+ let last = 0;
565
+ for(let i = 1; i < line.length; ++i){
566
+ const c = line[i];
567
+ if (current_cote) {
568
+ if (c === current_cote) {
569
+ current_cote = '';
570
+ }
571
+ } else {
572
+ if ([
573
+ '"',
574
+ '\''
575
+ ].includes(c)) {
576
+ current_cote = c;
577
+ } else if (c === ' ') {
578
+ parts.push(line.slice(last, i));
579
+ last = i + 1;
580
+ }
581
+ }
582
+ }
583
+ parts.push(line.slice(last));
584
+ return parts;
585
+ }
586
+
587
+ // Class
588
+ class ScriptTask extends GroupTask {
589
+ workspace;
590
+ script;
591
+ args;
592
+ // Attributes
593
+ _preHookTasks = null;
594
+ _postHookTasks = null;
595
+ _scriptTasks = null;
596
+ _runHooks;
597
+ // Constructor
598
+ constructor(workspace, script, args, opts){
599
+ super(script, {
600
+ workspace,
601
+ script
602
+ }, opts), this.workspace = workspace, this.script = script, this.args = args;
603
+ this._runHooks = opts?.runHooks ?? true;
604
+ }
605
+ // Methods
606
+ async _runScript(script, args) {
607
+ const line = this.workspace.getScript(script);
608
+ if (!line) {
609
+ return null;
610
+ }
611
+ // Create command task for script
612
+ const [command, ...commandArgs] = splitCommandLine(line);
613
+ const set = new TaskSet();
614
+ if (!command) {
615
+ return set;
616
+ }
617
+ if (command === 'jill') {
618
+ const argv = commandArgs.map((arg)=>arg.replace(/^["'](.+)["']$/, '$1'));
619
+ const { PlannerService } = await import('./planner.service.js');
620
+ const plannerService = inject$(PlannerService);
621
+ const tasks = await plannerService.plan(argv, this.workspace.root);
622
+ if (tasks) {
623
+ return tasks;
624
+ }
625
+ }
626
+ const pm = await this.workspace.project.packageManager();
627
+ set.add(new CommandTask(this.workspace, command, [
628
+ ...commandArgs,
629
+ ...args
630
+ ], {
631
+ logger: this.logger$,
632
+ superCommand: pm === 'yarn' ? [
633
+ 'yarn',
634
+ 'exec'
635
+ ] : undefined
636
+ }));
637
+ return set;
638
+ }
639
+ async prepare() {
640
+ // Prepare script run
641
+ this._scriptTasks = await this._runScript(this.script, this.args);
642
+ if (!this._scriptTasks) {
643
+ throw new ScriptNotFound(`No script ${this.script} in ${this.workspace.name}`);
644
+ }
645
+ // Prepare hooks run
646
+ if (this._runHooks) {
647
+ this._preHookTasks = await this._runScript(`pre${this.script}`, []);
648
+ this._postHookTasks = await this._runScript(`post${this.script}`, []);
649
+ }
650
+ // Add tasks to group
651
+ if (this._preHookTasks) {
652
+ this.logger$.verbose(`found pre-hook script "pre${this.script}"`);
653
+ for (const tsk of this._preHookTasks){
654
+ this.add(tsk);
655
+ }
656
+ }
657
+ for (const tsk of this._scriptTasks){
658
+ this.add(tsk);
659
+ }
660
+ if (this._postHookTasks) {
661
+ this.logger$.verbose(`found post-hook script "post${this.script}"`);
662
+ for (const tsk of this._postHookTasks){
663
+ this.add(tsk);
664
+ }
665
+ }
666
+ }
667
+ async *onOrchestrate() {
668
+ if (!this._scriptTasks) {
669
+ throw new Error('ScriptTask needs to be prepared. Call prepare before starting it');
670
+ }
671
+ // Run pre-hook
672
+ if (this._preHookTasks) {
673
+ yield* this._preHookTasks;
674
+ if (await this._hasFailed(this._preHookTasks)) {
675
+ return this.setStatus('failed');
676
+ }
677
+ }
678
+ // Run script
679
+ yield* this._scriptTasks;
680
+ if (await this._hasFailed(this._scriptTasks)) {
681
+ return this.setStatus('failed');
682
+ }
683
+ // Run post-hook
684
+ if (this._postHookTasks) {
685
+ yield* this._postHookTasks;
686
+ if (await this._hasFailed(this._postHookTasks)) {
687
+ return this.setStatus('failed');
688
+ }
689
+ }
690
+ this.setStatus('done');
691
+ }
692
+ async _hasFailed(set) {
693
+ const results = await waitFor$(set.events$, 'finished');
694
+ return results.failed > 0;
695
+ }
696
+ async onStop() {
697
+ if (!this._scriptTasks) return;
698
+ for (const tsk of this._scriptTasks){
699
+ await tsk.stop();
700
+ }
701
+ }
702
+ complexity(cache = new Map()) {
703
+ let complexity = super.complexity(cache);
704
+ if (this._scriptTasks) {
705
+ complexity += this._scriptTasks.tasks.reduce((cpl, tsk)=>cpl + tsk.complexity(cache), 0);
706
+ }
707
+ cache.set(this.id, complexity);
708
+ return complexity;
709
+ }
710
+ // Properties
711
+ get project() {
712
+ return this.workspace.project;
713
+ }
714
+ }
715
+ // Utils
716
+ function isScriptCtx(ctx) {
717
+ return 'workspace' in ctx && 'script' in ctx;
718
+ }
719
+ class ScriptNotFound extends ClientError {
720
+ name = 'ScriptNotFound';
721
+ }
722
+
723
+ var _dec$3, _dec1$2, _initProto$3;
724
+ _dec$3 = instrument({
725
+ name: 'Workspace.dependencies',
726
+ use: traceAsyncGenerator
727
+ }), _dec1$2 = instrument({
728
+ name: 'Workspace.devDependencies',
729
+ use: traceAsyncGenerator
730
+ });
731
+ class Workspace {
732
+ manifest;
733
+ project;
734
+ static{
735
+ ({ e: [_initProto$3] } = _(this, [
736
+ [
737
+ _dec$3,
738
+ 2,
739
+ "dependencies"
740
+ ],
741
+ [
742
+ _dec1$2,
743
+ 2,
744
+ "devDependencies"
745
+ ]
746
+ ], []));
747
+ }
748
+ // Attributes
749
+ _affectedCache = (_initProto$3(this), new Map());
750
+ _logger;
751
+ _git = inject$(GitService);
752
+ _root;
753
+ _tasks = new Map();
754
+ // Constructor
755
+ constructor(root, manifest, project){
756
+ this.manifest = manifest;
757
+ this.project = project;
758
+ this._root = root;
759
+ this._logger = inject$(LOGGER).child(withLabel(manifest.name));
760
+ }
761
+ // Methods
762
+ async _buildDependencies(task, opts) {
763
+ const generators = [];
764
+ switch(opts.buildDeps ?? 'all'){
765
+ case 'all':
766
+ generators.unshift(this.devDependencies());
767
+ // eslint-disable-next no-fallthrough
768
+ case 'prod':
769
+ generators.unshift(this.dependencies());
770
+ }
771
+ // Build deps
772
+ for await (const dep of combine(...generators)){
773
+ const build = await dep.build(opts);
774
+ if (build) {
775
+ task.dependsOn(build);
776
+ }
777
+ }
778
+ }
779
+ async *_loadDependencies(dependencies, kind) {
780
+ for (const [dep, range] of Object.entries(dependencies)){
781
+ const ws = await this.project.workspace(dep);
782
+ if (ws) {
783
+ if (ws._satisfies(this, range)) {
784
+ yield ws;
785
+ } else {
786
+ this._logger.warning(`ignoring ${kind} ${ws.reference} as it does not match requirement ${range}`);
787
+ }
788
+ }
789
+ }
790
+ }
791
+ async _isAffected(reference) {
792
+ const isAffected = await this._git.isAffected(reference, [
793
+ this.root
794
+ ], {
795
+ cwd: this.project.root,
796
+ logger: this._logger
797
+ });
798
+ if (isAffected) {
799
+ return true;
800
+ }
801
+ // Test dependencies
802
+ const proms = [];
803
+ for await (const dep of combine(this.dependencies(), this.devDependencies())){
804
+ proms.push(dep.isAffected(reference));
805
+ }
806
+ const results = await Promise.all(proms);
807
+ return results.some((r)=>r);
808
+ }
809
+ _satisfies(from, range) {
810
+ if (range.startsWith('file:')) {
811
+ return path.resolve(from.root, range.substring(5)) === this.root;
812
+ }
813
+ if (range.startsWith('workspace:')) {
814
+ range = range.substring(10);
815
+ }
816
+ return !this.version || satisfies(this.version, range);
817
+ }
818
+ async isAffected(reference) {
819
+ let isAffected = this._affectedCache.get(reference);
820
+ if (!isAffected) {
821
+ isAffected = this._isAffected(reference);
822
+ this._affectedCache.set(reference, isAffected);
823
+ }
824
+ return await isAffected;
825
+ }
826
+ async *dependencies() {
827
+ if (!this.manifest.dependencies) return;
828
+ for await (const ws of this._loadDependencies(this.manifest.dependencies, 'dependency')){
829
+ yield ws;
830
+ }
831
+ }
832
+ async *devDependencies() {
833
+ if (!this.manifest.devDependencies) return;
834
+ for await (const ws of this._loadDependencies(this.manifest.devDependencies, 'devDependency')){
835
+ yield ws;
836
+ }
837
+ }
838
+ async build(opts = {}) {
839
+ const script = opts.buildScript ?? 'build';
840
+ const task = await this.run(script, [], opts);
841
+ if (!task) {
842
+ this._logger.warning(`will not be built (no "${script}" script found)`);
843
+ }
844
+ return task;
845
+ }
846
+ async exec(command, args = [], opts = {}) {
847
+ const pm = await this.project.packageManager();
848
+ const task = new CommandTask(this, command, args, {
849
+ ...opts,
850
+ logger: this._logger.child(withLabel(`${this.name}$${command}`)),
851
+ superCommand: pm === 'yarn' ? [
852
+ 'yarn',
853
+ 'exec'
854
+ ] : undefined
855
+ });
856
+ await this._buildDependencies(task, opts);
857
+ return task;
858
+ }
859
+ getScript(script) {
860
+ const { scripts = {} } = this.manifest;
861
+ return scripts[script] || null;
862
+ }
863
+ async run(script, args = [], opts = {}) {
864
+ // Script not found
865
+ if (!this.getScript(script)) {
866
+ return null;
867
+ }
868
+ // Create task if it doesn't exist yet
869
+ let task = this._tasks.get(script);
870
+ if (!task) {
871
+ const config = await inject$(CONFIG, asyncScope$());
872
+ task = new ScriptTask(this, script, args, {
873
+ ...opts,
874
+ logger: this._logger.child(withLabel(`${this.name}#${script}`)),
875
+ runHooks: config.hooks
876
+ });
877
+ await task.prepare();
878
+ await this._buildDependencies(task, opts);
879
+ this._tasks.set(script, task);
880
+ }
881
+ return task;
882
+ }
883
+ toJSON() {
884
+ return {
885
+ name: this.name,
886
+ version: this.version,
887
+ root: this.root
888
+ };
889
+ }
890
+ // Properties
891
+ get name() {
892
+ return this.manifest.name;
893
+ }
894
+ get reference() {
895
+ return this.version ? `${this.name}@${this.version}` : this.name;
896
+ }
897
+ get root() {
898
+ return path.resolve(this.project.root, this._root);
899
+ }
900
+ get version() {
901
+ return this.manifest.version;
902
+ }
903
+ }
904
+
905
+ var _dec$2, _dec1$1, _dec2, _dec3, _dec4, _initProto$2;
906
+ _dec$2 = instrument('Project.currentWorkspace'), _dec1$1 = instrument('Project.mainWorkspace'), _dec2 = instrument('Project.packageManager'), _dec3 = instrument('Project.workspace'), _dec4 = instrument({
907
+ name: 'Project.workspaces',
908
+ use: traceAsyncGenerator
909
+ });
910
+ class Project {
911
+ static{
912
+ ({ e: [_initProto$2] } = _(this, [
913
+ [
914
+ _dec$2,
915
+ 2,
916
+ "currentWorkspace"
917
+ ],
918
+ [
919
+ _dec1$1,
920
+ 2,
921
+ "mainWorkspace"
922
+ ],
923
+ [
924
+ _dec2,
925
+ 2,
926
+ "packageManager"
927
+ ],
928
+ [
929
+ _dec3,
930
+ 2,
931
+ "workspace"
932
+ ],
933
+ [
934
+ _dec4,
935
+ 2,
936
+ "workspaces"
937
+ ]
938
+ ], []));
939
+ }
940
+ // Attributes
941
+ _isFullyLoaded = (_initProto$2(this), false);
942
+ _lock = mutex$();
943
+ _mainWorkspace;
944
+ _packageManager;
945
+ _workspaceGlob;
946
+ _names = new Map();
947
+ _logger = inject$(LOGGER).child(withLabel('project'));
948
+ _root;
949
+ _scurry = inject$(PATH_SCURRY);
950
+ _workspaces = new Map();
951
+ // Constructor
952
+ constructor(root, opts = {}){
953
+ this._root = root;
954
+ if (opts.packageManager) {
955
+ this._logger.verbose`Forced use of ${opts.packageManager} in ${root}`;
956
+ this._packageManager = opts.packageManager;
957
+ }
958
+ }
959
+ // Methods
960
+ async _loadManifest(dir) {
961
+ const file = path.resolve(this.root, dir, 'package.json');
962
+ const relative = path.relative(this.root, path.dirname(file));
963
+ const logger = this._logger.child(withLabel(relative ? `project@${relative}` : 'project'));
964
+ logger.debug('loading package.json ...');
965
+ const data = await fs.promises.readFile(file, 'utf-8');
966
+ const mnf = JSON.parse(data);
967
+ normalize(mnf, (msg)=>logger.verbose(msg));
968
+ return mnf;
969
+ }
970
+ _loadWorkspace(dir) {
971
+ return with$(this._lock, async ()=>{
972
+ let wks = this._workspaces.get(dir);
973
+ if (!wks) {
974
+ const manifest = await this._loadManifest(dir);
975
+ wks = new Workspace(dir, manifest, this);
976
+ this._workspaces.set(dir, wks);
977
+ this._names.set(wks.name, wks);
978
+ }
979
+ return wks;
980
+ });
981
+ }
982
+ async currentWorkspace(cwd = inject$(CWD, asyncScope$())) {
983
+ let workspace = null;
984
+ cwd = path.resolve(cwd);
985
+ for await (const wks of this.workspaces()){
986
+ if (cwd.startsWith(wks.root)) {
987
+ workspace = wks;
988
+ if (wks.root !== this.root) return wks;
989
+ }
990
+ }
991
+ return workspace;
992
+ }
993
+ async mainWorkspace() {
994
+ if (!this._mainWorkspace) {
995
+ const manifest = await this._loadManifest('.');
996
+ this._mainWorkspace = new Workspace('.', manifest, this);
997
+ this._names.set(this._mainWorkspace.name, this._mainWorkspace);
998
+ }
999
+ return this._mainWorkspace;
1000
+ }
1001
+ async packageManager() {
1002
+ if (!this._packageManager) {
1003
+ this._logger.debug`searching lockfile in ${this.root}`;
1004
+ const files = await this._scurry.readdir(this.root, {
1005
+ withFileTypes: false
1006
+ });
1007
+ if (files.includes('yarn.lock')) {
1008
+ this._logger.debug`detected yarn in ${this.root}`;
1009
+ this._packageManager = 'yarn';
1010
+ } else if (files.includes('package-lock.json')) {
1011
+ this._logger.debug`detected npm in ${this.root}`;
1012
+ this._packageManager = 'npm';
1013
+ } else {
1014
+ this._logger.debug`no package manager recognized in ${this.root}, defaults to npm`;
1015
+ this._packageManager = 'npm';
1016
+ }
1017
+ }
1018
+ return this._packageManager;
1019
+ }
1020
+ async workspace(name) {
1021
+ // With current directory
1022
+ if (!name) {
1023
+ const dir = path.relative(this.root, inject$(CWD, asyncScope$()));
1024
+ return this._loadWorkspace(dir);
1025
+ }
1026
+ // Try name index
1027
+ const wks = this._names.get(name);
1028
+ if (wks) {
1029
+ return wks;
1030
+ }
1031
+ // Load workspaces
1032
+ if (!this._isFullyLoaded) {
1033
+ for await (const ws of this.workspaces()){
1034
+ if (ws.name === name) {
1035
+ return ws;
1036
+ }
1037
+ }
1038
+ this._isFullyLoaded = true;
1039
+ }
1040
+ return null;
1041
+ }
1042
+ async *workspaces() {
1043
+ const main = await this.mainWorkspace();
1044
+ yield main;
1045
+ if (this._isFullyLoaded) {
1046
+ for (const wks of this._names.values()){
1047
+ if (wks.name !== main.name) yield wks;
1048
+ }
1049
+ } else {
1050
+ // Load child workspaces
1051
+ const patterns = main.manifest.workspaces ?? [];
1052
+ this._scurry.chdir(this.root);
1053
+ this._workspaceGlob ??= new Glob(patterns, {
1054
+ scurry: this._scurry,
1055
+ withFileTypes: true
1056
+ });
1057
+ for await (const dir of this._workspaceGlob){
1058
+ try {
1059
+ // Check if dir is a directory
1060
+ if (dir.isDirectory()) {
1061
+ yield await this._loadWorkspace(dir.fullpath());
1062
+ }
1063
+ } catch (error) {
1064
+ if (error.code === 'ENOENT') {
1065
+ continue;
1066
+ }
1067
+ throw error;
1068
+ }
1069
+ }
1070
+ this._isFullyLoaded = true;
1071
+ }
1072
+ }
1073
+ // Properties
1074
+ get root() {
1075
+ return path.resolve(this._root);
1076
+ }
1077
+ }
1078
+
1079
+ var _dec$1, _initProto$1;
1080
+ _dec$1 = instrument();
1081
+ /**
1082
+ * Helps detecting projects folders
1083
+ */ class ProjectsRepository {
1084
+ static{
1085
+ ({ e: [_initProto$1] } = _(this, [
1086
+ [
1087
+ _dec$1,
1088
+ 2,
1089
+ "searchProjectRoot"
1090
+ ]
1091
+ ], []));
1092
+ }
1093
+ // Attributes
1094
+ _cache = (_initProto$1(this), new Map());
1095
+ _logger = inject$(LOGGER).child(withLabel('projects'));
1096
+ _scurry = inject$(PATH_SCURRY);
1097
+ // Methods
1098
+ async isProjectRoot(dir) {
1099
+ this._logger.debug`testing ${dir}`;
1100
+ const files = await this._scurry.readdir(dir, {
1101
+ withFileTypes: false
1102
+ });
1103
+ return {
1104
+ hasManifest: files.includes(MANIFEST),
1105
+ hasLockFile: LOCK_FILES.some((lock)=>files.includes(lock))
1106
+ };
1107
+ }
1108
+ async searchProjectRoot(directory) {
1109
+ directory = path.resolve(directory);
1110
+ let foundManifest = false;
1111
+ let projectRoot = directory;
1112
+ let dir = directory;
1113
+ let prev = dir;
1114
+ do {
1115
+ // Look for files
1116
+ const { hasManifest, hasLockFile } = await this.isProjectRoot(dir);
1117
+ if (hasManifest) {
1118
+ projectRoot = dir;
1119
+ foundManifest = true;
1120
+ }
1121
+ if (hasLockFile) {
1122
+ break;
1123
+ }
1124
+ prev = dir;
1125
+ dir = path.dirname(dir);
1126
+ }while (prev !== dir);
1127
+ // Log it
1128
+ if (foundManifest) {
1129
+ this._logger.verbose`project root found at ${projectRoot}`;
1130
+ } else {
1131
+ this._logger.verbose`project root not found, keeping ${projectRoot}`;
1132
+ }
1133
+ return projectRoot;
1134
+ }
1135
+ getProject(root, opts) {
1136
+ let project = this._cache.get(root);
1137
+ if (!project) {
1138
+ project = new Project(root, opts);
1139
+ this._cache.set(root, project);
1140
+ }
1141
+ return project;
1142
+ }
1143
+ }
1144
+ // Constants
1145
+ const MANIFEST = 'package.json';
1146
+ const LOCK_FILES = [
1147
+ 'package-lock.json',
1148
+ 'yarn.lock'
1149
+ ];
1150
+
1151
+ /**
1152
+ * Adds arguments to load a project.
1153
+ */ function withProject(parser) {
1154
+ return parser.option('project', {
1155
+ alias: 'p',
1156
+ type: 'string',
1157
+ default: '',
1158
+ defaultDescription: 'current working directory',
1159
+ description: 'Project root directory',
1160
+ normalize: true
1161
+ }).option('package-manager', {
1162
+ choices: [
1163
+ 'yarn',
1164
+ 'npm'
1165
+ ],
1166
+ type: 'string',
1167
+ description: 'Force package manager'
1168
+ }).middleware((args)=>startSpan({
1169
+ name: 'project',
1170
+ op: 'cli.middleware'
1171
+ }, async ()=>{
1172
+ const repository = inject$(ProjectsRepository);
1173
+ const directory = path.resolve(inject$(CWD, asyncScope$()), args.project);
1174
+ args.project = await repository.searchProjectRoot(directory);
1175
+ }));
1176
+ }
1177
+ /**
1178
+ * Loads a project, based on arguments.
1179
+ */ function loadProject(args) {
1180
+ const repository = inject$(ProjectsRepository);
1181
+ return repository.getProject(args.project, {
1182
+ packageManager: args.packageManager
1183
+ });
1184
+ }
1185
+
1186
+ class TaskExpressionError extends ClientError {
1187
+ name = 'TaskExpressionError';
1188
+ }
1189
+ class TaskSyntaxError extends ClientError {
1190
+ name = 'TaskSyntaxError';
1191
+ }
1192
+
1193
+ var _dec, _dec1, _initProto;
1194
+ _dec = instrument('TaskParserService.parse'), _dec1 = instrument('TaskParserService.buildTask');
1195
+ // Service
1196
+ class TaskParserService {
1197
+ static{
1198
+ ({ e: [_initProto] } = _(this, [
1199
+ [
1200
+ _dec,
1201
+ 2,
1202
+ "parse"
1203
+ ],
1204
+ [
1205
+ _dec1,
1206
+ 2,
1207
+ "buildTask"
1208
+ ]
1209
+ ], []));
1210
+ }
1211
+ // Attributes
1212
+ _logger = (_initProto(this), inject$(LOGGER).child(withLabel('task-parser')));
1213
+ // Statics
1214
+ static isTaskNode(node) {
1215
+ return 'script' in node;
1216
+ }
1217
+ static _sequenceOperatorWarn = true;
1218
+ // Methods
1219
+ _lexer() {
1220
+ return moo.states({
1221
+ task: {
1222
+ lparen: '(',
1223
+ whitespace: /[ \t]+/,
1224
+ script: {
1225
+ match: /[-_:a-zA-Z0-9]+/,
1226
+ push: 'operatorOrArgument'
1227
+ },
1228
+ string: [
1229
+ {
1230
+ match: /'(?:\\['\\]|[^\r\n'\\])+'/,
1231
+ push: 'operator',
1232
+ value: (x)=>x.slice(1, -1).replace(/\\(['\\])/g, '$1')
1233
+ },
1234
+ {
1235
+ match: /"(?:\\["\\]|[^\r\n"\\])+"/,
1236
+ push: 'operator',
1237
+ value: (x)=>x.slice(1, -1).replace(/\\(["\\])/g, '$1')
1238
+ }
1239
+ ]
1240
+ },
1241
+ operator: {
1242
+ rparen: ')',
1243
+ whitespace: /[ \t]+/,
1244
+ operator: {
1245
+ match: [
1246
+ '->',
1247
+ '&&',
1248
+ '//',
1249
+ '||'
1250
+ ],
1251
+ pop: 1
1252
+ }
1253
+ },
1254
+ operatorOrArgument: {
1255
+ rparen: ')',
1256
+ whitespace: /[ \t]+/,
1257
+ operator: {
1258
+ match: [
1259
+ '->',
1260
+ '&&',
1261
+ '//',
1262
+ '||'
1263
+ ],
1264
+ pop: 1
1265
+ },
1266
+ argument: [
1267
+ {
1268
+ match: /[-_:a-zA-Z0-9]+/
1269
+ },
1270
+ {
1271
+ match: /'(?:\\['\\]|[^\r\n'\\])+'/,
1272
+ value: (x)=>x.slice(1, -1).replace(/\\(['\\])/g, '$1')
1273
+ },
1274
+ {
1275
+ match: /"(?:\\["\\]|[^\r\n"\\])+"/,
1276
+ value: (x)=>x.slice(1, -1).replace(/\\(["\\])/g, '$1')
1277
+ }
1278
+ ]
1279
+ }
1280
+ });
1281
+ }
1282
+ _nextNode(lexer, i = 0) {
1283
+ let node = null;
1284
+ for (const token of lexer){
1285
+ // Ignore whitespaces
1286
+ if (token.type === 'whitespace') {
1287
+ continue;
1288
+ }
1289
+ // rparen = end of group
1290
+ if (token.type === 'rparen') {
1291
+ break;
1292
+ }
1293
+ // Handle argument
1294
+ if (token.type === 'argument') {
1295
+ if (!node) {
1296
+ throw new TaskSyntaxError(lexer.formatError(token, 'Unexpected argument'));
1297
+ } else if (TaskParserService.isTaskNode(node)) {
1298
+ node.args.push(token.value);
1299
+ } else {
1300
+ const lastTask = node.tasks[node.tasks.length - 1];
1301
+ if (!lastTask || !TaskParserService.isTaskNode(lastTask)) {
1302
+ throw new TaskSyntaxError(lexer.formatError(token, 'Unexpected argument'));
1303
+ } else {
1304
+ lastTask.args.push(token.value);
1305
+ }
1306
+ }
1307
+ continue;
1308
+ }
1309
+ // Handle operator
1310
+ if (token.type === 'operator') {
1311
+ const operator = token.value;
1312
+ if (!node) {
1313
+ throw new TaskSyntaxError(lexer.formatError(token, 'Unexpected operator'));
1314
+ } else if (TaskParserService.isTaskNode(node)) {
1315
+ node = {
1316
+ operator,
1317
+ tasks: [
1318
+ node
1319
+ ]
1320
+ };
1321
+ continue;
1322
+ } else {
1323
+ if (node.operator !== operator) {
1324
+ node = {
1325
+ operator,
1326
+ tasks: [
1327
+ node
1328
+ ]
1329
+ };
1330
+ }
1331
+ continue;
1332
+ }
1333
+ }
1334
+ // Build "child"
1335
+ let child;
1336
+ if (token.type === 'script') {
1337
+ child = {
1338
+ script: token.value,
1339
+ args: []
1340
+ };
1341
+ } else if (token.type === 'string') {
1342
+ const [script, ...args] = token.value.split(/ +/);
1343
+ child = {
1344
+ script,
1345
+ args
1346
+ };
1347
+ } else if (token.type === 'lparen') {
1348
+ const res = this._nextNode(lexer, i + 1);
1349
+ if (!res) {
1350
+ throw new TaskSyntaxError(lexer.formatError(token, 'Empty group found'));
1351
+ }
1352
+ child = res;
1353
+ } else {
1354
+ throw new TaskSyntaxError(lexer.formatError(token, 'Unexpected token'));
1355
+ }
1356
+ if (!node) {
1357
+ node = child;
1358
+ } else if (TaskParserService.isTaskNode(node)) {
1359
+ throw new TaskSyntaxError(lexer.formatError(token, 'Unexpected token, expected an operator'));
1360
+ } else {
1361
+ node.tasks.push(child);
1362
+ }
1363
+ }
1364
+ return node;
1365
+ }
1366
+ parse(expr) {
1367
+ const lexer = this._lexer().reset(expr);
1368
+ const tree = {
1369
+ roots: []
1370
+ };
1371
+ while(true){
1372
+ const node = this._nextNode(lexer);
1373
+ if (node) {
1374
+ tree.roots.push(node);
1375
+ } else {
1376
+ break;
1377
+ }
1378
+ }
1379
+ return tree;
1380
+ }
1381
+ *extractScripts(node) {
1382
+ if ('roots' in node) {
1383
+ for (const child of node.roots){
1384
+ yield* this.extractScripts(child);
1385
+ }
1386
+ } else if (TaskParserService.isTaskNode(node)) {
1387
+ yield node.script;
1388
+ } else {
1389
+ for (const child of node.tasks){
1390
+ yield* this.extractScripts(child);
1391
+ }
1392
+ }
1393
+ }
1394
+ async buildTask(node, workspace, opts) {
1395
+ if (TaskParserService.isTaskNode(node)) {
1396
+ const task = await workspace.run(node.script, node.args, opts);
1397
+ if (!task) {
1398
+ throw new TaskExpressionError(`Workspace ${workspace.name} have no ${node.script} script`);
1399
+ }
1400
+ return task;
1401
+ } else {
1402
+ let group;
1403
+ if (node.operator === '//') {
1404
+ group = new ParallelGroup('In parallel', {
1405
+ workspace
1406
+ }, {
1407
+ logger: this._logger
1408
+ });
1409
+ } else if (node.operator === '||') {
1410
+ group = new FallbackGroup('Fallbacks', {
1411
+ workspace
1412
+ }, {
1413
+ logger: this._logger
1414
+ });
1415
+ } else {
1416
+ if (node.operator === '->' && TaskParserService._sequenceOperatorWarn) {
1417
+ this._logger.warn('Sequence operator -> is deprecated in favor of &&. It will be removed in a next major release.');
1418
+ TaskParserService._sequenceOperatorWarn = true;
1419
+ }
1420
+ group = new SequenceGroup('In sequence', {
1421
+ workspace
1422
+ }, {
1423
+ logger: this._logger
1424
+ });
1425
+ }
1426
+ for (const child of node.tasks){
1427
+ group.add(await this.buildTask(child, workspace, opts));
1428
+ }
1429
+ return group;
1430
+ }
1431
+ }
1432
+ }
1433
+
1434
+ function pipeline$() {
1435
+ const steps = [];
1436
+ return {
1437
+ add (step) {
1438
+ steps.push(step);
1439
+ return this;
1440
+ },
1441
+ build () {
1442
+ return (value)=>steps.reduce((val, step)=>step(val), value);
1443
+ }
1444
+ };
1445
+ }
1446
+
1447
+ // Command
1448
+ const command$4 = {
1449
+ command: 'each <expr>',
1450
+ describe: 'Run a task expression in many workspace, after having built all theirs dependencies.',
1451
+ builder: (parser)=>withProject(parser).positional('expr', {
1452
+ type: 'string',
1453
+ demandOption: true,
1454
+ desc: 'Script or task expression'
1455
+ }).option('affected', {
1456
+ alias: 'a',
1457
+ type: 'string',
1458
+ coerce: (rev)=>rev === '' ? 'master' : rev,
1459
+ group: 'Filters:',
1460
+ desc: 'Print only affected workspaces towards given git revision. If no revision is given, it will check towards master. Replaces %name by workspace name.'
1461
+ }).option('affected-rev-fallback', {
1462
+ type: 'string',
1463
+ default: 'master',
1464
+ group: 'Filters:',
1465
+ desc: 'Fallback revision, used if no revision matching the given format is found'
1466
+ }).option('affected-rev-sort', {
1467
+ type: 'string',
1468
+ group: 'Filters:',
1469
+ desc: 'Sort applied to git tag / git branch command'
1470
+ }).option('allow-no-workspaces', {
1471
+ type: 'boolean',
1472
+ default: false,
1473
+ desc: 'Allow no matching workspaces. Without it jill will exit with code 1 if no workspace matches'
1474
+ }).option('build-script', {
1475
+ default: 'build',
1476
+ desc: 'Script to use to build dependencies'
1477
+ }).option('deps-mode', {
1478
+ alias: 'd',
1479
+ choice: [
1480
+ 'all',
1481
+ 'prod',
1482
+ 'none'
1483
+ ],
1484
+ default: 'all',
1485
+ desc: 'Dependency selection mode:\n' + ' - all = dependencies AND devDependencies\n' + ' - prod = dependencies\n' + ' - none = nothing'
1486
+ }).option('private', {
1487
+ type: 'boolean',
1488
+ group: 'Filters:',
1489
+ describe: 'Print only private workspaces'
1490
+ })// Config
1491
+ .strict(false).parserConfiguration({
1492
+ 'unknown-options-as-args': true
1493
+ }),
1494
+ async prepare (args) {
1495
+ // Extract expression
1496
+ const expr = args._.map((arg)=>arg.toString());
1497
+ if (expr[0] === 'each') {
1498
+ expr.splice(0, 1);
1499
+ }
1500
+ expr.unshift(args.expr);
1501
+ // Prepare filters
1502
+ let filters = pipeline$();
1503
+ if (args.private !== undefined) {
1504
+ filters = filters.add(isPrivate$(args.private));
1505
+ }
1506
+ if (args.affected !== undefined) {
1507
+ filters = filters.add(isAffected$({
1508
+ format: args.affected,
1509
+ fallback: args.affectedRevFallback,
1510
+ sort: args.affectedRevSort
1511
+ }));
1512
+ }
1513
+ // Parse task expression
1514
+ const taskParser = inject$(TaskParserService);
1515
+ const tree = taskParser.parse(expr.join(' '));
1516
+ const scripts = Array.from(taskParser.extractScripts(tree));
1517
+ // Load workspaces
1518
+ const project = loadProject(args);
1519
+ const workspaces = pipe$(asyncIterator$(project.workspaces()), hasEveryScript$(scripts), filters.build());
1520
+ // Prepare tasks
1521
+ const tasks = new TaskSet();
1522
+ for await (const wks of workspaces){
1523
+ tasks.add(await taskParser.buildTask(tree.roots[0], wks, {
1524
+ buildScript: args.buildScript,
1525
+ buildDeps: args.depsMode
1526
+ }));
1527
+ }
1528
+ return tasks;
1529
+ }
1530
+ };
1531
+
1532
+ /**
1533
+ * Adds arguments to load a workspace.
1534
+ */ function withWorkspace(parser) {
1535
+ return withProject(parser).option('workspace', {
1536
+ alias: 'w',
1537
+ type: 'string',
1538
+ desc: 'Workspace to use'
1539
+ });
1540
+ }
1541
+ /**
1542
+ * Loads a workspace, based on arguments.
1543
+ */ async function loadWorkspace(args) {
1544
+ const logger = inject$(LOGGER);
1545
+ const project = loadProject(args);
1546
+ let workspace;
1547
+ if (args.workspace) {
1548
+ logger.debug(`loading workspace "${args.workspace}"`);
1549
+ workspace = await project.workspace(args.workspace);
1550
+ } else if (inject$(CWD, asyncScope$()).startsWith(project.root)) {
1551
+ logger.debug('loading workspace containing current directory');
1552
+ workspace = await project.currentWorkspace(inject$(CWD, asyncScope$()));
1553
+ } else {
1554
+ logger.debug('loading main workspace');
1555
+ workspace = await project.mainWorkspace();
1556
+ }
1557
+ if (!workspace) {
1558
+ throw new WorkspaceNotFound(args.workspace || '.');
1559
+ }
1560
+ return workspace;
1561
+ }
1562
+ class WorkspaceNotFound extends ClientError {
1563
+ name = 'WorkspaceNotFound';
1564
+ constructor(workspace){
1565
+ super(`Workspace "${workspace}" not found`);
1566
+ }
1567
+ }
1568
+
1569
+ // Command
1570
+ const command$3 = {
1571
+ command: 'exec <command>',
1572
+ aliases: [
1573
+ '$0'
1574
+ ],
1575
+ describe: 'Run command inside workspace, after all its dependencies has been built.',
1576
+ builder: (args)=>withWorkspace(args).positional('command', {
1577
+ type: 'string',
1578
+ demandOption: true
1579
+ }).option('build-script', {
1580
+ default: 'build',
1581
+ desc: 'Script to use to build dependencies'
1582
+ }).option('deps-mode', {
1583
+ alias: 'd',
1584
+ choice: [
1585
+ 'all',
1586
+ 'prod',
1587
+ 'none'
1588
+ ],
1589
+ default: 'all',
1590
+ desc: 'Dependency selection mode:\n' + ' - all = dependencies AND devDependencies\n' + ' - prod = dependencies\n' + ' - none = nothing'
1591
+ })// Documentation
1592
+ .example('jill exec eslint', '').example('jill exec eslint --env-info', 'Unknown arguments are passed down to command. Here it will run "eslint --env-info"').example('jill exec eslint -- -v', 'You can use -- to stop argument parsing. Here it will run "eslint -v"')// Config
1593
+ .strict(false).parserConfiguration({
1594
+ 'unknown-options-as-args': true
1595
+ }),
1596
+ async prepare (args) {
1597
+ const workspace = await loadWorkspace(args);
1598
+ // Extract arguments
1599
+ const rest = args._.map((arg)=>arg.toString());
1600
+ if (rest[0] === 'exec') {
1601
+ rest.splice(0, 1);
1602
+ }
1603
+ // Run script in workspace
1604
+ const tasks = new TaskSet();
1605
+ tasks.add(await workspace.exec(args.command, rest, {
1606
+ buildScript: args.buildScript,
1607
+ buildDeps: args.depsMode
1608
+ }));
1609
+ return tasks;
1610
+ },
1611
+ async execute (args, tasks) {
1612
+ const task = tasks.tasks[0];
1613
+ if (task.dependencies.length > 0) {
1614
+ const dependencies = new TaskSet();
1615
+ for (const dep of task.dependencies){
1616
+ dependencies.add(dep);
1617
+ }
1618
+ // Run dependencies first with spinners
1619
+ const { default: TaskExecInk } = await traceLoad('TaskExecInk', ()=>import('./task-exec.ink.js'));
1620
+ await TaskExecInk({
1621
+ tasks: dependencies,
1622
+ verbose: [
1623
+ 'verbose',
1624
+ 'debug'
1625
+ ].includes(args.verbose)
1626
+ });
1627
+ } else {
1628
+ const logger = inject$(LOGGER);
1629
+ logger.verbose('No dependency to build');
1630
+ }
1631
+ const child = cp.spawn(task.cmd, task.args, {
1632
+ stdio: 'inherit',
1633
+ cwd: task.cwd,
1634
+ env: {
1635
+ ...process$1.env,
1636
+ ...task.env
1637
+ },
1638
+ shell: true,
1639
+ windowsHide: true
1640
+ });
1641
+ process$1.exitCode = await new Promise((resolve)=>{
1642
+ child.on('close', (code)=>{
1643
+ resolve(code ?? 0);
1644
+ });
1645
+ });
1646
+ }
1647
+ };
1648
+
1649
+ // Command
1650
+ const command$2 = {
1651
+ command: 'list',
1652
+ aliases: [
1653
+ 'ls'
1654
+ ],
1655
+ describe: 'List project workspaces',
1656
+ builder: (parser)=>withProject(parser).option('affected', {
1657
+ alias: 'a',
1658
+ type: 'string',
1659
+ coerce: (rev)=>rev === '' ? 'master' : rev,
1660
+ group: 'Filters:',
1661
+ desc: `Print only affected workspaces towards given git revision. If no revision is given, it will check towards master. Replaces ${chalk.bold('%name')} by workspace name.`
1662
+ }).option('affected-rev-fallback', {
1663
+ type: 'string',
1664
+ default: 'master',
1665
+ group: 'Filters:',
1666
+ desc: 'Fallback revision, used if no revision matching the given format is found'
1667
+ }).option('affected-rev-sort', {
1668
+ type: 'string',
1669
+ group: 'Filters:',
1670
+ desc: 'Sort applied to git tag / git branch command'
1671
+ }).option('attribute', {
1672
+ alias: [
1673
+ 'attr',
1674
+ 'attrs'
1675
+ ],
1676
+ type: 'array',
1677
+ choices: [
1678
+ 'name',
1679
+ 'version',
1680
+ 'root',
1681
+ 'slug'
1682
+ ],
1683
+ group: 'Format:',
1684
+ default: [],
1685
+ description: 'Select printed attributes',
1686
+ defaultDescription: '"name" only'
1687
+ }).option('headers', {
1688
+ type: 'boolean',
1689
+ group: 'Format:',
1690
+ default: false,
1691
+ desc: 'Prints columns headers'
1692
+ }).option('long', {
1693
+ alias: 'l',
1694
+ type: 'boolean',
1695
+ group: 'Format:',
1696
+ default: false,
1697
+ desc: 'Prints name, version and root of all workspaces'
1698
+ }).option('json', {
1699
+ type: 'boolean',
1700
+ group: 'Format:',
1701
+ default: false,
1702
+ desc: 'Prints data as a JSON array'
1703
+ }).option('private', {
1704
+ type: 'boolean',
1705
+ group: 'Filters:',
1706
+ describe: 'Print only private workspaces'
1707
+ }).option('sort-by', {
1708
+ alias: 's',
1709
+ type: 'array',
1710
+ choices: [
1711
+ 'name',
1712
+ 'version',
1713
+ 'root',
1714
+ 'slug'
1715
+ ],
1716
+ group: 'Sort:',
1717
+ default: [],
1718
+ description: 'Sort output by given attribute',
1719
+ defaultDescription: 'first attribute'
1720
+ }).option('sort-order', {
1721
+ alias: [
1722
+ 'o',
1723
+ 'order'
1724
+ ],
1725
+ type: 'string',
1726
+ choices: [
1727
+ 'asc',
1728
+ 'desc'
1729
+ ],
1730
+ default: 'asc',
1731
+ group: 'Sort:',
1732
+ desc: 'Sort order'
1733
+ }).option('with-script', {
1734
+ type: 'array',
1735
+ string: true,
1736
+ group: 'Filters:',
1737
+ desc: 'Print only workspaces having the given script'
1738
+ }).middleware((argv)=>{
1739
+ // Compute attributes
1740
+ if (!argv.attribute.length) {
1741
+ if (argv.json) {
1742
+ argv.attrs = argv.attr = argv.attribute = [
1743
+ 'name',
1744
+ 'version',
1745
+ 'slug',
1746
+ 'root'
1747
+ ];
1748
+ } else if (argv.long) {
1749
+ argv.attrs = argv.attr = argv.attribute = [
1750
+ 'name',
1751
+ 'version',
1752
+ 'root'
1753
+ ];
1754
+ } else if (argv.sortBy?.length) {
1755
+ argv.attrs = argv.attr = argv.attribute = argv.sortBy;
1756
+ } else {
1757
+ argv.attrs = argv.attr = argv.attribute = [
1758
+ 'name'
1759
+ ];
1760
+ }
1761
+ }
1762
+ }, true).check((argv)=>{
1763
+ if (argv.attribute.length > 0 && argv['sort-by']?.length) {
1764
+ const miss = argv['sort-by'].filter((attr)=>!argv.attribute.includes(attr));
1765
+ if (miss.length > 0) {
1766
+ throw new ClientError(`Cannot sort by non printed attributes. Missing ${miss.join(', ')}.`);
1767
+ }
1768
+ }
1769
+ if (!argv['sort-by']?.length && argv.attribute.length > 0) {
1770
+ argv['sort-by'] = argv.sortBy = argv.s = [
1771
+ argv.attribute[0]
1772
+ ];
1773
+ }
1774
+ return true;
1775
+ }),
1776
+ async handler (args) {
1777
+ // Prepare filters
1778
+ let filters = pipeline$();
1779
+ if (args.private !== undefined) {
1780
+ filters = filters.add(isPrivate$(args.private));
1781
+ }
1782
+ if (args.withScript) {
1783
+ filters = filters.add(hasSomeScript$(args.withScript));
1784
+ }
1785
+ if (args.affected !== undefined) {
1786
+ filters = filters.add(isAffected$({
1787
+ format: args.affected,
1788
+ fallback: args.affectedRevFallback,
1789
+ sort: args.affectedRevSort
1790
+ }));
1791
+ }
1792
+ // Load workspaces
1793
+ const project = loadProject(args);
1794
+ const workspaces = await waitFor$(pipe$(asyncIterator$(project.workspaces()), filters.build(), map$(buildExtractor(args)), collect$()));
1795
+ if (args.sortBy.length > 0) {
1796
+ workspaces.sort(buildComparator(args));
1797
+ }
1798
+ if (args.json) {
1799
+ printJson(workspaces);
1800
+ } else if (workspaces.length > 0) {
1801
+ for (const data of workspaces){
1802
+ if (data.root) {
1803
+ data.root = path.relative(process.cwd(), data.root) || '.';
1804
+ }
1805
+ }
1806
+ const { default: ListInk } = await traceLoad('ListInk', ()=>import('./list.ink.js'));
1807
+ await ListInk({
1808
+ attributes: args.attribute,
1809
+ headers: args.headers,
1810
+ workspaces
1811
+ });
1812
+ }
1813
+ }
1814
+ };
1815
+ const EXTRACTORS = {
1816
+ name: (wks)=>wks.name,
1817
+ version: (wks, json)=>wks.manifest.version || (json ? undefined : chalk.grey('unset')),
1818
+ root: (wks)=>wks.root,
1819
+ slug: (wks)=>slugify(wks.name)
1820
+ };
1821
+ const COMPARATORS = {
1822
+ name: (a = '', b = '')=>a.localeCompare(b),
1823
+ version: (a, b)=>compare(parse(a) ?? '0.0.0', parse(b) ?? '0.0.0'),
1824
+ root: (a = '', b = '')=>a.localeCompare(b),
1825
+ slug: (a = '', b = '')=>a.localeCompare(b)
1826
+ };
1827
+ function buildExtractor(args) {
1828
+ return (wks)=>{
1829
+ const data = {};
1830
+ for (const attr of args.attribute){
1831
+ data[attr] = EXTRACTORS[attr](wks, args.json);
1832
+ }
1833
+ return data;
1834
+ };
1835
+ }
1836
+ function buildComparator(args) {
1837
+ const factor = args.sortOrder === 'asc' ? 1 : -1;
1838
+ return (a, b)=>{
1839
+ for (const attr of args.sortBy){
1840
+ const diff = COMPARATORS[attr](a[attr], b[attr]);
1841
+ if (diff !== 0) {
1842
+ return diff * factor;
1843
+ }
1844
+ }
1845
+ return 0;
1846
+ };
1847
+ }
1848
+
1849
+ // Command
1850
+ const command$1 = {
1851
+ command: 'run <expr>',
1852
+ describe: 'Run a task expression in a workspace, after having built all its dependencies.',
1853
+ builder: (parser)=>withWorkspace(parser).positional('expr', {
1854
+ type: 'string',
1855
+ demandOption: true,
1856
+ desc: 'Script or task expression'
1857
+ }).option('build-script', {
1858
+ default: 'build',
1859
+ desc: 'Script to use to build dependencies'
1860
+ }).option('deps-mode', {
1861
+ alias: 'd',
1862
+ choice: [
1863
+ 'all',
1864
+ 'prod',
1865
+ 'none'
1866
+ ],
1867
+ default: 'all',
1868
+ desc: 'Dependency selection mode:\n' + ' - all = dependencies AND devDependencies\n' + ' - prod = dependencies\n' + ' - none = nothing'
1869
+ })// Config
1870
+ .strict(false).parserConfiguration({
1871
+ 'unknown-options-as-args': true
1872
+ }),
1873
+ async prepare (args) {
1874
+ const workspace = await loadWorkspace(args);
1875
+ // Extract expression
1876
+ const expr = args._.map((arg)=>arg.toString());
1877
+ if (expr[0] === 'run') {
1878
+ expr.splice(0, 1);
1879
+ }
1880
+ expr.unshift(args.expr);
1881
+ // Parse task expression
1882
+ const taskParser = inject$(TaskParserService);
1883
+ const tree = taskParser.parse(expr.join(' '));
1884
+ const tasks = new TaskSet();
1885
+ tasks.add(await taskParser.buildTask(tree.roots[0], workspace, {
1886
+ buildScript: args.buildScript,
1887
+ buildDeps: args.depsMode
1888
+ }));
1889
+ return tasks;
1890
+ }
1891
+ };
1892
+
1893
+ // Command
1894
+ const command = {
1895
+ command: 'tree',
1896
+ describe: 'Print workspace dependency tree',
1897
+ builder: withWorkspace,
1898
+ async handler (args) {
1899
+ const workspace = await loadWorkspace(args);
1900
+ const { default: TreeInk } = await traceLoad('TreeInk', ()=>import('./tree.ink.js'));
1901
+ await TreeInk({
1902
+ workspace
1903
+ });
1904
+ }
1905
+ };
1906
+
1907
+ // Utils
1908
+ function dynamicImport(filepath) {
1909
+ return import(/* webpackIgnore: true */ process.platform === 'win32' ? `file://${filepath}` : filepath);
1910
+ }
1911
+
1912
+ const ConfigExplorer = token$('ConfigExplorer', ()=>cosmiconfig('jill', {
1913
+ searchStrategy: 'global',
1914
+ loaders: {
1915
+ '.cjs': (filepath)=>dynamicImport(filepath).then((mod)=>mod.default),
1916
+ '.js': (filepath)=>dynamicImport(filepath).then((mod)=>mod.default),
1917
+ '.json': defaultLoaders['.json'],
1918
+ '.yaml': defaultLoaders['.yaml'],
1919
+ '.yml': defaultLoaders['.yml'],
1920
+ noExt: defaultLoaders.noExt
1921
+ }
1922
+ }));
1923
+
1924
+ var $schema = "http://json-schema.org/draft-07/schema";
1925
+ var type = "object";
1926
+ var additionalProperties = false;
1927
+ var properties = {
1928
+ hooks: {
1929
+ type: "boolean",
1930
+ "default": true,
1931
+ description: "Instructs jill to run hook scripts."
1932
+ },
1933
+ jobs: {
1934
+ type: "number",
1935
+ "default": 0,
1936
+ description: "Number of allowed parallel tasks, defaults to CPU number."
1937
+ }
1938
+ };
1939
+ var schema = {
1940
+ $schema: $schema,
1941
+ type: type,
1942
+ additionalProperties: additionalProperties,
1943
+ properties: properties
1944
+ };
1945
+
1946
+ // Constants
1947
+ const CPU_COUNT = os.cpus().length;
1948
+ /**
1949
+ * Loads and make configuration accessible
1950
+ */ class ConfigService {
1951
+ // Attributes
1952
+ _filepath;
1953
+ _config = var$();
1954
+ _logger = inject$(LOGGER).child(withLabel('config'));
1955
+ _explorer = inject$(ConfigExplorer);
1956
+ // Constructor
1957
+ constructor(state = {}){
1958
+ if (state.filepath) {
1959
+ this._filepath = state.filepath;
1960
+ }
1961
+ if (state.config) {
1962
+ this._config.mutate(state.config);
1963
+ }
1964
+ }
1965
+ // Methods
1966
+ _validateConfig(config) {
1967
+ // Validate config
1968
+ const ajv = new Ajv({
1969
+ allErrors: true,
1970
+ useDefaults: true,
1971
+ logger: this._logger.child(withLabel('ajv')),
1972
+ strict: process$1.env.NODE_ENV === 'development' ? 'log' : true
1973
+ });
1974
+ const validator = ajv.compile(schema);
1975
+ if (!validator(config)) {
1976
+ const errors = ajv.errorsText(validator.errors, {
1977
+ separator: '\n- ',
1978
+ dataVar: 'config'
1979
+ });
1980
+ this._logger.error(`errors in config file:\n- ${errors}`);
1981
+ throw new Error('Error in config file');
1982
+ }
1983
+ // Correct jobs value
1984
+ if (config.jobs <= 0) {
1985
+ Object.assign(config, {
1986
+ jobs: Math.max(CPU_COUNT - 1, 1)
1987
+ });
1988
+ }
1989
+ this._logger.debug`loaded config:\n${qjson(config, {
1990
+ pretty: true
1991
+ })}`;
1992
+ return config;
1993
+ }
1994
+ async searchConfig() {
1995
+ const loaded = await this._explorer.search(inject$(CWD, asyncScope$()));
1996
+ if (loaded) {
1997
+ this._logger.verbose`loaded file ${loaded.filepath}`;
1998
+ this._filepath = loaded.filepath;
1999
+ const config = this._validateConfig(loaded.config);
2000
+ this._config.mutate(config);
2001
+ }
2002
+ }
2003
+ async loadConfig(filepath) {
2004
+ const loaded = await this._explorer.load(filepath);
2005
+ if (loaded) {
2006
+ this._logger.verbose`loaded file ${loaded.filepath}`;
2007
+ this._filepath = loaded.filepath;
2008
+ const config = this._validateConfig(loaded.config);
2009
+ this._config.mutate(config);
2010
+ }
2011
+ }
2012
+ // Attributes
2013
+ get baseDir() {
2014
+ return this._filepath ? path.dirname(this._filepath) : inject$(CWD, asyncScope$());
2015
+ }
2016
+ get config$() {
2017
+ return this._config;
2018
+ }
2019
+ get config() {
2020
+ return this._config.defer();
2021
+ }
2022
+ get state() {
2023
+ return {
2024
+ filepath: this._filepath,
2025
+ config: this.config
2026
+ };
2027
+ }
2028
+ }
2029
+
2030
+ var config_service = /*#__PURE__*/Object.freeze({
2031
+ __proto__: null,
2032
+ ConfigService: ConfigService
2033
+ });
2034
+
2035
+ // Middleware
2036
+ function withConfig(parser) {
2037
+ return parser.option('config-file', {
2038
+ alias: 'c',
2039
+ type: 'string',
2040
+ description: 'Configuration file'
2041
+ }).middleware((args)=>startSpan({
2042
+ name: 'config',
2043
+ op: 'cli.middleware'
2044
+ }, async ()=>{
2045
+ const configService = inject$(ConfigService, asyncScope$());
2046
+ if (args.configFile) {
2047
+ await configService.loadConfig(args.configFile);
2048
+ } else {
2049
+ await configService.searchConfig();
2050
+ }
2051
+ }));
2052
+ }
2053
+
2054
+ const LEVEL_COLORS = {
2055
+ [LogLevel.debug]: 'grey',
2056
+ [LogLevel.verbose]: 'blue',
2057
+ [LogLevel.info]: 'reset',
2058
+ [LogLevel.warning]: 'yellow',
2059
+ [LogLevel.error]: 'red'
2060
+ };
2061
+ const logColor = defineQuickFormat((level)=>LEVEL_COLORS[level])(qprop('level'));
2062
+ const logFormat = qwrap(chalkTemplateStderr).fun`#?:${qprop('label')}{grey [${q$}]} ?#{${logColor} ${qprop('message')} {grey +${qLogDelay(qarg())}}#?:${qerror(qprop('error'))}${os.EOL}${q$}?#}`;
2063
+
2064
+ // Utils
2065
+ const VERBOSITY_LEVEL = {
2066
+ 1: 'verbose',
2067
+ 2: 'debug'
2068
+ };
2069
+ // Middleware
2070
+ function withLogger(parser) {
2071
+ return parser.option('verbose', {
2072
+ alias: 'v',
2073
+ default: 'info',
2074
+ type: 'count',
2075
+ description: 'Set verbosity level',
2076
+ coerce: (cnt)=>VERBOSITY_LEVEL[Math.min(cnt, 2)]
2077
+ }).middleware((args)=>startSpan({
2078
+ name: 'logger',
2079
+ op: 'cli.middleware'
2080
+ }, ()=>{
2081
+ const logLevel = args.verbose ? LogLevel[args.verbose] : LogLevel.info;
2082
+ const logGateway = inject$(LogGateway);
2083
+ flow$(inject$(LOGGER), filter$((log)=>log.level >= logLevel), logDelay$(), logGateway);
2084
+ logGateway.connect('console', toStderr(logFormat));
2085
+ }));
2086
+ }
2087
+
2088
+ // Utils
2089
+ function baseParser() {
2090
+ return pipe$(yargs().scriptName('jill').version(version).demandCommand().recommendCommands(), withLogger, withConfig);
2091
+ }
2092
+ /**
2093
+ * Prepare parser executing commands
2094
+ */ function executeParser() {
2095
+ return pipe$(baseParser(), executeCommand(command$4), executeCommand(command$3), command$5(command$2), executeCommand(command$1), command$5(command));
2096
+ }
2097
+ /**
2098
+ * Prepare parser planning commands
2099
+ */ function planParser(tasks$) {
2100
+ return pipe$(baseParser(), planCommand(command$4, tasks$), planCommand(command$3, tasks$), planCommand(command$2, tasks$), planCommand(command$1, tasks$), planCommand(command, tasks$));
2101
+ }
2102
+
2103
+ export { ClientError as C, LOGGER as L, ScriptTask as S, TASK_MANAGER as T, isScriptCtx as a, CommandTask as b, capitalize as c, CWD as d, executeParser as e, ConfigService as f, instrument as g, isCommandCtx as i, logFormat as l, planParser as p };
2104
+ //# sourceMappingURL=parser.js.map