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

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