@jujulego/jill 3.0.0-alpha.6 → 3.0.0-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -1,20 +1,21 @@
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]="dacd5c15-8247-4915-b30c-252fb53065d6",e._sentryDebugIdIdentifier="sentry-dbid-dacd5c15-8247-4915-b30c-252fb53065d6");})();}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.6"};}catch(e){}}();import { token$, inject$, asyncScope$ } from '@kyrielle/injector';
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]="928a37d5-5913-4d87-9031-9d670b857132",e._sentryDebugIdIdentifier="sentry-dbid-928a37d5-5913-4d87-9031-9d670b857132");})();}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.8"};}catch(e){}}();import { token$, inject$, asyncScope$ } from '@kyrielle/injector';
2
2
  import { startSpan, getActiveSpan, updateSpanName, getRootSpan, startInactiveSpan, captureException } from '@sentry/node';
3
3
  import process$1 from 'node:process';
4
4
  import { hideBin } from 'yargs/helpers';
5
- import { pipe$, waitFor$, filter$, observable$, off$, once$, map$, collect$, asyncIterator$, var$, flow$ } from 'kyrielle';
5
+ import { pipe$, waitFor$, filter$, map$, collect$, asyncIterator$, var$, flow$ } from 'kyrielle';
6
6
  import yargs from 'yargs';
7
- import { TaskManager, plan, SpawnTask, GroupTask, TaskSet, ParallelGroup, FallbackGroup, SequenceGroup } from '@jujulego/tasks';
7
+ import { scheduler$, isWorkloadEnded, WorkloadState, spawn$, sequenceFlow$, parallelFlow$, fallbackFlow$ } from '@jujulego/tasks';
8
8
  import { logger$, withTimestamp, withLabel, qLogDelay, LogLevel, LogGateway, logDelay$, toStderr } from '@kyrielle/logger';
9
9
  import fs from 'node:fs';
10
10
  import { PathScurry } from 'path-scurry';
11
11
  import { _ } from '@swc/helpers/_/_apply_decs_2203_r';
12
+ import { text } from 'node:stream/consumers';
12
13
  import path from 'node:path';
13
14
  import { Glob } from 'glob';
14
15
  import normalize from 'normalize-package-data';
15
16
  import { satisfies, compare, parse } from 'semver';
16
17
  import moo from 'moo';
17
- import cp from 'node:child_process';
18
+ import { spawn } from 'node:child_process';
18
19
  import chalk from 'chalk';
19
20
  import slugify from 'slugify';
20
21
  import { qjson, qprop, q$, defineQuickFormat, qerror, qarg, qwrap } from '@jujulego/quick-tag';
@@ -23,14 +24,16 @@ import os from 'node:os';
23
24
  import { cosmiconfig, defaultLoaders } from 'cosmiconfig';
24
25
  import { chalkTemplateStderr } from 'chalk-template';
25
26
 
26
- var version = "3.0.0-alpha.6";
27
+ var version = "3.0.0-alpha.8";
27
28
 
28
29
  function trace(fun, opts) {
29
30
  const name = typeof opts === 'string' ? opts : opts.name;
30
- const use = typeof opts === 'object' ? opts.use : (_, r)=>r;
31
+ const op = typeof opts === 'object' ? opts.op : undefined;
32
+ const use = typeof opts === 'object' && opts.use || ((_, r)=>r);
31
33
  return function(...args) {
32
34
  return pipe$(startSpan({
33
- name
35
+ name,
36
+ op
34
37
  }, ()=>fun.call(this, ...args)), (r)=>use(name, r));
35
38
  };
36
39
  }
@@ -44,27 +47,19 @@ function instrument(opts) {
44
47
  });
45
48
  };
46
49
  }
47
- function traceAsyncGenerator(name, generator) {
48
- const instrumented = {
49
- ...generator,
50
- next: async ()=>startSpan({
51
- name,
52
- op: 'iterator.next'
53
- }, ()=>generator.next()),
54
- [Symbol.asyncIterator]: ()=>instrumented
55
- };
56
- return instrumented;
57
- }
58
50
  function traceImport(name, loader) {
59
51
  return startSpan({
60
- name: `load ${name}`,
52
+ name: name,
61
53
  op: 'resource.script'
62
54
  }, loader);
63
55
  }
64
56
 
65
57
  function command$5(module) {
66
- const name = getCommandName(module);
67
- const handler = trace(module.handler, 'cli.handler');
58
+ const name = commandName(module);
59
+ const handler = trace(module.handler, {
60
+ name: commandName(module),
61
+ op: 'cli.handler'
62
+ });
68
63
  return (parser)=>parser.command({
69
64
  ...module,
70
65
  async handler (args) {
@@ -76,7 +71,7 @@ function command$5(module) {
76
71
  }
77
72
  });
78
73
  }
79
- function getCommandName(module) {
74
+ function commandName(module) {
80
75
  if (!module.command) {
81
76
  return '[unknown]';
82
77
  }
@@ -96,61 +91,69 @@ const LOGGER = token$('Logger', ()=>logger$(withTimestamp()));
96
91
  const PATH_SCURRY = token$('PathScurry', ()=>new PathScurry('/', {
97
92
  fs
98
93
  }));
99
- const TASK_MANAGER = token$('TaskManager', async ()=>{
100
- const manager = new TaskManager({
101
- jobs: (await inject$(CONFIG, asyncScope$())).jobs,
102
- logger: inject$(LOGGER)
94
+ const SCHEDULER = token$('Scheduler', async ()=>{
95
+ const config = await inject$(CONFIG, asyncScope$());
96
+ const logger = inject$(LOGGER);
97
+ const scheduler = scheduler$({
98
+ strength: config.jobs
103
99
  });
104
- let rootSpan = getActiveSpan();
105
- if (rootSpan) rootSpan = getRootSpan(rootSpan);
106
- const spans = new Map();
107
- manager.events$.on('added', (task)=>{
108
- // Main span
109
- const span = startInactiveSpan({
110
- name: task.name,
111
- parentSpan: (task.group && spans.get(task.group.id)) ?? rootSpan,
112
- op: 'task'
113
- });
114
- task.events$.on('completed', ({ status })=>{
115
- span.setStatus({
116
- code: status === 'done' ? 1 : 2
117
- });
118
- span.end();
100
+ scheduler.events$.on('started', (job)=>{
101
+ logger.verbose(`job "${job.label}" started`);
102
+ });
103
+ scheduler.events$.on('ended', (job)=>{
104
+ logger.verbose(`job "${job.label}" ended in state ${job.state()}`);
105
+ });
106
+ // Task instrumentation
107
+ scheduler.events$.on('added', (job)=>{
108
+ const jobSpan = startInactiveSpan({
109
+ op: 'job',
110
+ name: job.label,
111
+ attributes: {
112
+ 'job.id': job.id,
113
+ 'job.type': job.type,
114
+ 'job.weight': job.weight
115
+ }
119
116
  });
120
- spans.set(task.id, span);
121
117
  // Status spans
122
- let statusSpan = startInactiveSpan({
123
- name: task.status,
124
- parentSpan: span,
125
- op: 'task.status'
126
- });
127
- task.events$.on('status', ({ status })=>{
128
- statusSpan.end();
129
- if (!task.completed) {
130
- statusSpan = startInactiveSpan({
131
- name: status,
132
- parentSpan: span,
133
- op: 'task.status'
134
- });
118
+ let stateSpan;
119
+ let subscription;
120
+ job.state$.subscribe({
121
+ start: (sub)=>{
122
+ subscription = sub;
123
+ },
124
+ next: (state)=>{
125
+ stateSpan?.end();
126
+ if (isWorkloadEnded(state)) {
127
+ jobSpan.setAttribute('job.final_state', state);
128
+ jobSpan.setAttribute('job.duration', job.duration().seconds());
129
+ jobSpan.setStatus({
130
+ code: state === WorkloadState.Succeeded ? 1 : 2
131
+ });
132
+ jobSpan.end();
133
+ subscription.unsubscribe();
134
+ } else {
135
+ stateSpan = startInactiveSpan({
136
+ op: 'job.state',
137
+ name: state,
138
+ parentSpan: jobSpan
139
+ });
140
+ }
135
141
  }
136
142
  });
137
143
  });
138
- return manager;
144
+ return scheduler;
139
145
  });
140
146
 
141
- // Utils
142
- function printJson(data, stream = process.stdout) {
143
- if (stream.isTTY) {
144
- stream.write(JSON.stringify(data, null, 2));
145
- } else {
146
- stream.write(JSON.stringify(data));
147
- }
148
- }
149
-
150
147
  // Utils
151
148
  function executeCommand(module) {
152
- const prepare = trace(module.prepare, 'cli.prepare');
153
- const execute = module.execute && trace(module.execute, 'cli.execute');
149
+ const prepare = trace(module.prepare, {
150
+ name: commandName(module),
151
+ op: 'cli.prepare'
152
+ });
153
+ const execute = module.execute && trace(module.execute, {
154
+ name: commandName(module),
155
+ op: 'cli.execute'
156
+ });
154
157
  return command$5({
155
158
  ...module,
156
159
  builder (base) {
@@ -162,23 +165,14 @@ function executeCommand(module) {
162
165
  }
163
166
  },
164
167
  async handler (args) {
165
- const tasks = await prepare(args);
166
- if (args.plan) {
167
- if (args.planMode === 'json') {
168
- printJson(Array.from(plan(tasks)));
169
- } else {
170
- const { default: TaskPlanInk } = await traceImport('TaskPlanInk', ()=>import('./task-plan.ink.js'));
171
- await TaskPlanInk({
172
- tasks
173
- });
174
- }
175
- } else {
168
+ const job = await prepare(args) ?? null;
169
+ if (args.plan) ; else {
176
170
  if (execute) {
177
- await execute(args, tasks);
178
- } else if (tasks.tasks.length > 0) {
179
- const { default: TaskExecInk } = await traceImport('TaskExecInk', ()=>import('./task-exec.ink.js'));
180
- await TaskExecInk({
181
- tasks,
171
+ await execute(args, job);
172
+ } else if (job) {
173
+ const { default: JobExecInk } = await traceImport('JobExecInk', ()=>import('./job-exec.ink.js'));
174
+ await JobExecInk({
175
+ job,
182
176
  verbose: [
183
177
  'verbose',
184
178
  'debug'
@@ -193,9 +187,12 @@ function executeCommand(module) {
193
187
  }
194
188
  });
195
189
  }
196
- function planCommand(module, tasks$) {
190
+ function planCommand(module, job$) {
197
191
  if ('prepare' in module) {
198
- const prepare = trace(module.prepare, 'cli.prepare');
192
+ const prepare = trace(module.prepare, {
193
+ name: commandName(module),
194
+ op: 'cli.prepare'
195
+ });
199
196
  return command$5({
200
197
  ...module,
201
198
  builder (base) {
@@ -207,7 +204,7 @@ function planCommand(module, tasks$) {
207
204
  }
208
205
  },
209
206
  async handler (args) {
210
- tasks$.mutate(await prepare(args));
207
+ job$.mutate(await prepare(args) ?? null);
211
208
  }
212
209
  });
213
210
  } else {
@@ -252,158 +249,105 @@ function hasEveryScript$(scripts) {
252
249
  });
253
250
  }
254
251
 
255
- // Utils
256
- async function* combine(...generators) {
257
- for (const gen of generators){
258
- yield* gen;
252
+ class ClientError extends Error {
253
+ name = 'ClientError';
254
+ constructor(message){
255
+ super(message);
259
256
  }
260
257
  }
261
- function streamLines$(task, stream = 'stdout') {
262
- return observable$((observer, signal)=>{
263
- const off = off$();
264
- let current = '';
265
- // End
266
- off.add(once$(task.events$, 'completed', ()=>{
267
- if (current) observer.next(current);
268
- observer.complete();
269
- off.unsubscribe();
270
- }));
271
- // Abort
272
- signal.addEventListener('abort', ()=>off.unsubscribe(), {
273
- once: true
274
- });
275
- // Steam
276
- off.add(task.events$.on(`stream.${stream}`, (chunk)=>{
277
- const data = current + chunk.data.toString('utf-8');
278
- const lines = data.split(/\r?\n/);
279
- current = lines.pop() ?? '';
280
- for (const line of lines){
281
- observer.next(line);
282
- }
283
- }));
284
- });
285
- }
286
258
 
287
- var _dec$4, _dec1$3, _dec2$1, _initProto$4;
288
- _dec$4 = instrument('GitService.isAffected'), _dec1$3 = instrument('GitService.listBranches'), _dec2$1 = instrument('GitService.listTags');
259
+ var _dec$2, _dec1$1, _dec2, _initProto$2;
260
+ _dec$2 = instrument('GitService.isAffected'), _dec1$1 = instrument('GitService.listBranches'), _dec2 = instrument('GitService.listTags');
289
261
  class GitService {
290
262
  static{
291
- ({ e: [_initProto$4] } = _(this, [
263
+ ({ e: [_initProto$2] } = _(this, [
292
264
  [
293
- _dec$4,
265
+ _dec$2,
294
266
  2,
295
267
  "isAffected"
296
268
  ],
297
269
  [
298
- _dec1$3,
270
+ _dec1$1,
299
271
  2,
300
272
  "listBranches"
301
273
  ],
302
274
  [
303
- _dec2$1,
275
+ _dec2,
304
276
  2,
305
277
  "listTags"
306
278
  ]
307
279
  ], []));
308
280
  }
309
281
  // Attributes
310
- _manager = (_initProto$4(this), inject$(TASK_MANAGER));
282
+ _scheduler = (_initProto$2(this), inject$(SCHEDULER));
311
283
  _logger = inject$(LOGGER);
312
284
  // Methods
313
285
  /**
314
- * Runs a git command inside a SpawnTask
315
- *
316
- * @param cmd
317
- * @param args
318
- * @param options
319
- */ async command(cmd, args, options = {}) {
320
- const opts = {
321
- logger: this._logger,
322
- ...options
323
- };
324
- // Create task
325
- const task = new SpawnTask('git', [
286
+ * Runs a git command inside
287
+ */ async command(cmd, args, opts = {}) {
288
+ const { logger = this._logger, ...props } = opts;
289
+ // Create job
290
+ const job = spawn$('git', [
326
291
  cmd,
327
292
  ...args
328
- ], {
329
- command: cmd,
330
- hidden: true
331
- }, opts);
332
- task.events$.on('stream', ({ data })=>opts.logger.debug(data.toString('utf-8')));
333
- (await this._manager).add(task);
334
- return task;
293
+ ], props);
294
+ job.stdout.on('data', (data)=>logger.debug(data.toString('utf-8').trimEnd()));
295
+ job.stderr.on('data', (data)=>logger.warn(data.toString('utf-8').trimEnd()));
296
+ (await this._scheduler).register(job);
297
+ return job;
335
298
  }
336
299
  /**
337
300
  * Runs git branch
338
- *
339
- * @param args
340
- * @param options
341
- */ branch(args, options) {
342
- return this.command('branch', args, options);
301
+ */ branch(args, opts) {
302
+ return this.command('branch', args, opts);
343
303
  }
344
304
  /**
345
305
  * Runs git diff
346
- *
347
- * @param args
348
- * @param options
349
- */ diff(args, options) {
350
- return this.command('diff', args, options);
306
+ */ diff(args, opts) {
307
+ return this.command('diff', args, opts);
351
308
  }
352
309
  /**
353
310
  * Runs git tag
354
- *
355
- * @param args
356
- * @param options
357
- */ tag(args, options) {
358
- return this.command('tag', args, options);
311
+ */ tag(args, opts) {
312
+ return this.command('tag', args, opts);
359
313
  }
360
314
  /**
361
315
  * Uses git diff to detect if given files have been affected since given reference
362
- *
363
- * @param reference
364
- * @param files
365
- * @param opts
366
316
  */ async isAffected(reference, files = [], opts) {
367
- const task = await this.diff([
317
+ const job = await this.diff([
368
318
  '--quiet',
369
319
  reference,
370
320
  '--',
371
321
  ...files
372
322
  ], opts);
373
- return new Promise((resolve, reject)=>{
374
- once$(task.events$, 'status.done', ()=>resolve(false));
375
- once$(task.events$, 'status.failed', ()=>{
376
- if (task.exitCode) {
377
- resolve(true);
378
- } else {
379
- reject(new Error(`Task ${task.name} failed`));
380
- }
381
- });
382
- });
323
+ await waitFor$(pipe$(job.state$, filter$(isWorkloadEnded)));
324
+ if (job.exitCode() === 0) {
325
+ return false;
326
+ }
327
+ if (job.exitCode() === 1) {
328
+ return true;
329
+ }
330
+ throw new ClientError(`Error "git diff" command failed (exit code ${job.exitCode()})`);
383
331
  }
384
332
  /**
385
333
  * List git branches
386
- *
387
- * @param args
388
- * @param opts
389
334
  */ async listBranches(args = [], opts) {
390
- const task = await this.branch([
335
+ const job = await this.branch([
391
336
  '-l',
392
337
  ...args
393
338
  ], opts);
394
- return waitFor$(pipe$(streamLines$(task), map$((line)=>line.replace(/^[ *] /, '')), collect$()));
339
+ const output = await text(job.stdout);
340
+ return pipe$(output.split(/\r?\n/), map$((line)=>line.replace(/^[ *] /, '')), filter$((line)=>!!line), collect$());
395
341
  }
396
342
  /**
397
343
  * List git tags
398
- *
399
- * @param args
400
- * @param opts
401
344
  */ async listTags(args = [], opts) {
402
- const task = await this.tag([
345
+ const job = await this.tag([
403
346
  '-l',
404
347
  ...args
405
348
  ], opts);
406
- return waitFor$(pipe$(streamLines$(task), collect$()));
349
+ const output = await text(job.stdout);
350
+ return output.split(/\r?\n/).filter((line)=>!!line);
407
351
  }
408
352
  }
409
353
 
@@ -500,58 +444,37 @@ async function with$(lock, fn) {
500
444
  }
501
445
  }
502
446
 
503
- // Class
504
- class CommandTask extends SpawnTask {
505
- workspace;
506
- // Constructor
507
- constructor(workspace, command, args, opts = {}){
508
- let cmd = command;
509
- if (opts.superCommand) {
510
- if (typeof opts.superCommand === 'string') {
511
- opts.superCommand = [
512
- opts.superCommand
513
- ];
514
- }
515
- if (opts.superCommand.length > 0) {
516
- cmd = opts.superCommand[0];
517
- args = [
518
- ...opts.superCommand.slice(1),
519
- command,
520
- ...args
521
- ];
522
- }
447
+ function command$(workspace, cmd, args, opts = {}) {
448
+ const { superCommand, logger = inject$(LOGGER), ...rest } = opts;
449
+ // Apply super command
450
+ if (superCommand) {
451
+ if (typeof superCommand === 'string') {
452
+ args = [
453
+ cmd,
454
+ ...args
455
+ ];
456
+ cmd = superCommand;
457
+ } else if (superCommand.length) {
458
+ args = [
459
+ ...superCommand.slice(1),
460
+ cmd,
461
+ ...args
462
+ ];
463
+ cmd = superCommand[0];
464
+ }
465
+ }
466
+ // Prepare job
467
+ const job = spawn$(cmd, args, {
468
+ ...rest,
469
+ cwd: workspace.root,
470
+ env: {
471
+ FORCE_COLOR: '1',
472
+ ...rest.env
523
473
  }
524
- super(cmd, args, {
525
- workspace,
526
- command
527
- }, {
528
- ...opts,
529
- cwd: workspace.root,
530
- env: {
531
- FORCE_COLOR: '1',
532
- ...opts.env
533
- }
534
- }), this.workspace = workspace;
535
- this._logStreams();
536
- }
537
- // Methods
538
- _logStreams() {
539
- const off = off$(streamLines$(this, 'stdout').subscribe((line)=>this.logger$.info(line)), streamLines$(this, 'stderr').subscribe((line)=>this.logger$.info(line)));
540
- once$(this.events$, 'completed', ()=>{
541
- off.unsubscribe();
542
- });
543
- }
544
- }
545
- // Utils
546
- function isCommandCtx(ctx) {
547
- return 'workspace' in ctx && 'command' in ctx;
548
- }
549
-
550
- class ClientError extends Error {
551
- name = 'ClientError';
552
- constructor(message){
553
- super(message);
554
- }
474
+ });
475
+ job.stdout.on('data', (data)=>logger.info(data.toString('utf-8').trimEnd()));
476
+ job.stderr.on('data', (data)=>logger.info(data.toString('utf-8').trimEnd()));
477
+ return job;
555
478
  }
556
479
 
557
480
  // Utils
@@ -585,173 +508,84 @@ function splitCommandLine(line) {
585
508
  return parts;
586
509
  }
587
510
 
588
- // Class
589
- class ScriptTask extends GroupTask {
590
- workspace;
591
- script;
592
- args;
593
- // Attributes
594
- _preHookTasks = null;
595
- _postHookTasks = null;
596
- _scriptTasks = null;
597
- _runHooks;
598
- // Constructor
599
- constructor(workspace, script, args, opts){
600
- super(script, {
601
- workspace,
602
- script
603
- }, opts), this.workspace = workspace, this.script = script, this.args = args;
604
- this._runHooks = opts?.runHooks ?? true;
605
- }
606
- // Methods
607
- async _runScript(script, args) {
608
- const line = this.workspace.getScript(script);
609
- if (!line) {
610
- return null;
611
- }
612
- // Create command task for script
613
- const [command, ...commandArgs] = splitCommandLine(line);
614
- const set = new TaskSet();
615
- if (!command) {
616
- return set;
617
- }
618
- if (command === 'jill') {
619
- const argv = commandArgs.map((arg)=>arg.replace(/^["'](.+)["']$/, '$1'));
620
- const { PlannerService } = await import('./planner.service.js');
621
- const plannerService = inject$(PlannerService);
622
- const tasks = await plannerService.plan(argv, this.workspace.root);
623
- if (tasks) {
624
- return tasks;
625
- }
626
- }
627
- const pm = await this.workspace.project.packageManager();
628
- set.add(new CommandTask(this.workspace, command, [
629
- ...commandArgs,
630
- ...args
631
- ], {
632
- logger: this.logger$,
633
- superCommand: pm === 'yarn' ? [
634
- 'yarn',
635
- 'exec'
636
- ] : undefined
637
- }));
638
- return set;
639
- }
640
- async prepare() {
641
- // Prepare script run
642
- this._scriptTasks = await this._runScript(this.script, this.args);
643
- if (!this._scriptTasks) {
644
- throw new ScriptNotFound(`No script ${this.script} in ${this.workspace.name}`);
645
- }
646
- // Prepare hooks run
647
- if (this._runHooks) {
648
- this._preHookTasks = await this._runScript(`pre${this.script}`, []);
649
- this._postHookTasks = await this._runScript(`post${this.script}`, []);
650
- }
651
- // Add tasks to group
652
- if (this._preHookTasks) {
653
- this.logger$.verbose(`found pre-hook script "pre${this.script}"`);
654
- for (const tsk of this._preHookTasks){
655
- this.add(tsk);
656
- }
657
- }
658
- for (const tsk of this._scriptTasks){
659
- this.add(tsk);
660
- }
661
- if (this._postHookTasks) {
662
- this.logger$.verbose(`found post-hook script "post${this.script}"`);
663
- for (const tsk of this._postHookTasks){
664
- this.add(tsk);
665
- }
666
- }
667
- }
668
- async *onOrchestrate() {
669
- if (!this._scriptTasks) {
670
- throw new Error('ScriptTask needs to be prepared. Call prepare before starting it');
671
- }
672
- // Run pre-hook
673
- if (this._preHookTasks) {
674
- yield* this._preHookTasks;
675
- if (await this._hasFailed(this._preHookTasks)) {
676
- return this.setStatus('failed');
677
- }
678
- }
679
- // Run script
680
- yield* this._scriptTasks;
681
- if (await this._hasFailed(this._scriptTasks)) {
682
- return this.setStatus('failed');
683
- }
684
- // Run post-hook
685
- if (this._postHookTasks) {
686
- yield* this._postHookTasks;
687
- if (await this._hasFailed(this._postHookTasks)) {
688
- return this.setStatus('failed');
689
- }
690
- }
691
- this.setStatus('done');
692
- }
693
- async _hasFailed(set) {
694
- const results = await waitFor$(set.events$, 'finished');
695
- return results.failed > 0;
696
- }
697
- async onStop() {
698
- if (!this._scriptTasks) return;
699
- for (const tsk of this._scriptTasks){
700
- await tsk.stop();
701
- }
702
- }
703
- complexity(cache = new Map()) {
704
- let complexity = super.complexity(cache);
705
- if (this._scriptTasks) {
706
- complexity += this._scriptTasks.tasks.reduce((cpl, tsk)=>cpl + tsk.complexity(cache), 0);
707
- }
708
- cache.set(this.id, complexity);
709
- return complexity;
710
- }
711
- // Properties
712
- get project() {
713
- return this.workspace.project;
714
- }
511
+ async function runScript$(workspace, script, args, opts = {}) {
512
+ // Run script itself
513
+ const jobs = [
514
+ await planScript$(workspace, script, args, opts)
515
+ ];
516
+ if (!jobs[0]) {
517
+ throw new ScriptNotFound(`No script ${script} in ${workspace.name}`);
518
+ }
519
+ // Run hooks
520
+ if (opts.runHooks) {
521
+ jobs.unshift(await planScript$(workspace, `pre${script}`, [], opts));
522
+ jobs.push(await planScript$(workspace, `post${script}`, [], opts));
523
+ }
524
+ // Prepare workflow
525
+ const flow = pipe$(jobs, filter$((job)=>job !== null), collect$(sequenceFlow$({
526
+ label: script,
527
+ type: 'script'
528
+ })));
529
+ return {
530
+ ...flow,
531
+ script,
532
+ workspace
533
+ };
715
534
  }
716
535
  // Utils
717
- function isScriptCtx(ctx) {
718
- return 'workspace' in ctx && 'script' in ctx;
536
+ async function planScript$(workspace, script, args, opts) {
537
+ // Load script
538
+ const line = workspace?.getScript(script);
539
+ if (!line) {
540
+ return null;
541
+ }
542
+ // Parse script
543
+ const [command, ...commandArgs] = splitCommandLine(line);
544
+ if (!command) {
545
+ return null;
546
+ }
547
+ if (command === 'jill') {
548
+ const argv = commandArgs.map((arg)=>arg.replace(/^["'](.+)["']$/, '$1'));
549
+ const { PlannerService } = await import('./planner.service.js');
550
+ const plannerService = inject$(PlannerService);
551
+ const job = await plannerService.plan(argv, workspace.root);
552
+ if (job) {
553
+ return job;
554
+ }
555
+ }
556
+ // Run command
557
+ const pm = await workspace.project.packageManager();
558
+ return command$(workspace, command, [
559
+ ...commandArgs,
560
+ ...args
561
+ ], {
562
+ logger: opts.logger,
563
+ superCommand: pm === 'yarn' ? [
564
+ 'yarn',
565
+ 'exec'
566
+ ] : undefined
567
+ });
719
568
  }
720
569
  class ScriptNotFound extends ClientError {
721
570
  name = 'ScriptNotFound';
722
571
  }
723
572
 
724
- var _dec$3, _dec1$2, _initProto$3;
725
- _dec$3 = instrument({
726
- name: 'Workspace.dependencies',
727
- use: traceAsyncGenerator
728
- }), _dec1$2 = instrument({
729
- name: 'Workspace.devDependencies',
730
- use: traceAsyncGenerator
731
- });
573
+ // Utils
574
+ async function* combine(...generators) {
575
+ for (const gen of generators){
576
+ yield* gen;
577
+ }
578
+ }
579
+
732
580
  class Workspace {
733
581
  manifest;
734
582
  project;
735
- static{
736
- ({ e: [_initProto$3] } = _(this, [
737
- [
738
- _dec$3,
739
- 2,
740
- "dependencies"
741
- ],
742
- [
743
- _dec1$2,
744
- 2,
745
- "devDependencies"
746
- ]
747
- ], []));
748
- }
749
583
  // Attributes
750
- _affectedCache = (_initProto$3(this), new Map());
584
+ _affectedCache = new Map();
751
585
  _logger;
752
586
  _git = inject$(GitService);
753
587
  _root;
754
- _tasks = new Map();
588
+ _jobs = new Map();
755
589
  // Constructor
756
590
  constructor(root, manifest, project){
757
591
  this.manifest = manifest;
@@ -760,7 +594,7 @@ class Workspace {
760
594
  this._logger = inject$(LOGGER).child(withLabel(manifest.name));
761
595
  }
762
596
  // Methods
763
- async _buildDependencies(task, opts) {
597
+ async _buildDependencies(job, opts) {
764
598
  const generators = [];
765
599
  switch(opts.buildDeps ?? 'all'){
766
600
  case 'all':
@@ -773,7 +607,7 @@ class Workspace {
773
607
  for await (const dep of combine(...generators)){
774
608
  const build = await dep.build(opts);
775
609
  if (build) {
776
- task.dependsOn(build);
610
+ job.dependsOn(build);
777
611
  }
778
612
  }
779
613
  }
@@ -838,15 +672,15 @@ class Workspace {
838
672
  }
839
673
  async build(opts = {}) {
840
674
  const script = opts.buildScript ?? 'build';
841
- const task = await this.run(script, [], opts);
842
- if (!task) {
675
+ const job = await this.run(script, [], opts);
676
+ if (!job) {
843
677
  this._logger.warning(`will not be built (no "${script}" script found)`);
844
678
  }
845
- return task;
679
+ return job;
846
680
  }
847
681
  async exec(command, args = [], opts = {}) {
848
682
  const pm = await this.project.packageManager();
849
- const task = new CommandTask(this, command, args, {
683
+ const job = command$(this, command, args, {
850
684
  ...opts,
851
685
  logger: this._logger.child(withLabel(`${this.name}$${command}`)),
852
686
  superCommand: pm === 'yarn' ? [
@@ -854,8 +688,8 @@ class Workspace {
854
688
  'exec'
855
689
  ] : undefined
856
690
  });
857
- await this._buildDependencies(task, opts);
858
- return task;
691
+ await this._buildDependencies(job, opts);
692
+ return job;
859
693
  }
860
694
  getScript(script) {
861
695
  const { scripts = {} } = this.manifest;
@@ -867,19 +701,18 @@ class Workspace {
867
701
  return null;
868
702
  }
869
703
  // Create task if it doesn't exist yet
870
- let task = this._tasks.get(script);
871
- if (!task) {
704
+ let job = this._jobs.get(script);
705
+ if (!job) {
872
706
  const config = await inject$(CONFIG, asyncScope$());
873
- task = new ScriptTask(this, script, args, {
707
+ job = await runScript$(this, script, args, {
874
708
  ...opts,
875
709
  logger: this._logger.child(withLabel(`${this.name}#${script}`)),
876
710
  runHooks: config.hooks
877
711
  });
878
- await task.prepare();
879
- await this._buildDependencies(task, opts);
880
- this._tasks.set(script, task);
712
+ await this._buildDependencies(job, opts);
713
+ this._jobs.set(script, job);
881
714
  }
882
- return task;
715
+ return job;
883
716
  }
884
717
  toJSON() {
885
718
  return {
@@ -903,43 +736,9 @@ class Workspace {
903
736
  }
904
737
  }
905
738
 
906
- var _dec$2, _dec1$1, _dec2, _dec3, _dec4, _initProto$2;
907
- _dec$2 = instrument('Project.currentWorkspace'), _dec1$1 = instrument('Project.mainWorkspace'), _dec2 = instrument('Project.packageManager'), _dec3 = instrument('Project.workspace'), _dec4 = instrument({
908
- name: 'Project.workspaces',
909
- use: traceAsyncGenerator
910
- });
911
739
  class Project {
912
- static{
913
- ({ e: [_initProto$2] } = _(this, [
914
- [
915
- _dec$2,
916
- 2,
917
- "currentWorkspace"
918
- ],
919
- [
920
- _dec1$1,
921
- 2,
922
- "mainWorkspace"
923
- ],
924
- [
925
- _dec2,
926
- 2,
927
- "packageManager"
928
- ],
929
- [
930
- _dec3,
931
- 2,
932
- "workspace"
933
- ],
934
- [
935
- _dec4,
936
- 2,
937
- "workspaces"
938
- ]
939
- ], []));
940
- }
941
740
  // Attributes
942
- _isFullyLoaded = (_initProto$2(this), false);
741
+ _isFullyLoaded = false;
943
742
  _lock = mutex$();
944
743
  _mainWorkspace;
945
744
  _packageManager;
@@ -1192,7 +991,7 @@ class TaskSyntaxError extends ClientError {
1192
991
  }
1193
992
 
1194
993
  var _dec, _dec1, _initProto;
1195
- _dec = instrument('TaskParserService.parse'), _dec1 = instrument('TaskParserService.buildTask');
994
+ _dec = instrument('TaskParserService.parse'), _dec1 = instrument('TaskParserService.buildJob');
1196
995
  // Service
1197
996
  class TaskParserService {
1198
997
  static{
@@ -1205,7 +1004,7 @@ class TaskParserService {
1205
1004
  [
1206
1005
  _dec1,
1207
1006
  2,
1208
- "buildTask"
1007
+ "buildJob"
1209
1008
  ]
1210
1009
  ], []));
1211
1010
  }
@@ -1392,42 +1191,36 @@ class TaskParserService {
1392
1191
  }
1393
1192
  }
1394
1193
  }
1395
- async buildTask(node, workspace, opts) {
1194
+ async buildJob(node, workspace, opts) {
1396
1195
  if (TaskParserService.isTaskNode(node)) {
1397
- const task = await workspace.run(node.script, node.args, opts);
1398
- if (!task) {
1196
+ const job = await workspace.run(node.script, node.args, opts);
1197
+ if (!job) {
1399
1198
  throw new TaskExpressionError(`Workspace ${workspace.name} have no ${node.script} script`);
1400
1199
  }
1401
- return task;
1200
+ return job;
1402
1201
  } else {
1403
- let group;
1202
+ let flow;
1404
1203
  if (node.operator === '//') {
1405
- group = new ParallelGroup('In parallel', {
1406
- workspace
1407
- }, {
1408
- logger: this._logger
1204
+ flow = parallelFlow$({
1205
+ label: 'In parallel'
1409
1206
  });
1410
1207
  } else if (node.operator === '||') {
1411
- group = new FallbackGroup('Fallbacks', {
1412
- workspace
1413
- }, {
1414
- logger: this._logger
1208
+ flow = fallbackFlow$({
1209
+ label: 'Fallbacks'
1415
1210
  });
1416
1211
  } else {
1417
1212
  if (node.operator === '->' && TaskParserService._sequenceOperatorWarn) {
1418
1213
  this._logger.warn('Sequence operator -> is deprecated in favor of &&. It will be removed in a next major release.');
1419
1214
  TaskParserService._sequenceOperatorWarn = true;
1420
1215
  }
1421
- group = new SequenceGroup('In sequence', {
1422
- workspace
1423
- }, {
1424
- logger: this._logger
1216
+ flow = sequenceFlow$({
1217
+ label: 'In sequence'
1425
1218
  });
1426
1219
  }
1427
1220
  for (const child of node.tasks){
1428
- group.add(await this.buildTask(child, workspace, opts));
1221
+ flow.push(await this.buildJob(child, workspace, opts));
1429
1222
  }
1430
- return group;
1223
+ return flow;
1431
1224
  }
1432
1225
  }
1433
1226
  }
@@ -1517,16 +1310,22 @@ const command$4 = {
1517
1310
  const scripts = Array.from(taskParser.extractScripts(tree));
1518
1311
  // Load workspaces
1519
1312
  const project = loadProject(args);
1520
- const workspaces = pipe$(asyncIterator$(project.workspaces()), hasEveryScript$(scripts), filters.build());
1313
+ const workspaces = await waitFor$(pipe$(asyncIterator$(project.workspaces()), hasEveryScript$(scripts), filters.build(), collect$()));
1314
+ workspaces.sort((a, b)=>a.name.localeCompare(b.name));
1315
+ if (workspaces.length === 0) {
1316
+ return;
1317
+ }
1521
1318
  // Prepare tasks
1522
- const tasks = new TaskSet();
1523
- for await (const wks of workspaces){
1524
- tasks.add(await taskParser.buildTask(tree.roots[0], wks, {
1319
+ const flow = parallelFlow$({
1320
+ label: '[hidden]'
1321
+ });
1322
+ for (const wks of workspaces){
1323
+ flow.push(await taskParser.buildJob(tree.roots[0], wks, {
1525
1324
  buildScript: args.buildScript,
1526
1325
  buildDeps: args.depsMode
1527
1326
  }));
1528
1327
  }
1529
- return tasks;
1328
+ return flow;
1530
1329
  }
1531
1330
  };
1532
1331
 
@@ -1602,51 +1401,68 @@ const command$3 = {
1602
1401
  rest.splice(0, 1);
1603
1402
  }
1604
1403
  // Run script in workspace
1605
- const tasks = new TaskSet();
1606
- tasks.add(await workspace.exec(args.command, rest, {
1404
+ return await workspace.exec(args.command, rest, {
1607
1405
  buildScript: args.buildScript,
1608
1406
  buildDeps: args.depsMode
1609
- }));
1610
- return tasks;
1407
+ });
1611
1408
  },
1612
- async execute (args, tasks) {
1613
- const task = tasks.tasks[0];
1614
- if (task.dependencies.length > 0) {
1615
- const dependencies = new TaskSet();
1616
- for (const dep of task.dependencies){
1617
- dependencies.add(dep);
1618
- }
1409
+ async execute (args, arg) {
1410
+ const job = arg;
1411
+ if (job.dependencies().length > 0) {
1412
+ const dependencies = pipe$(job.dependencies(), collect$(parallelFlow$({
1413
+ label: 'build dependencies'
1414
+ })));
1619
1415
  // Run dependencies first with spinners
1620
- const { default: TaskExecInk } = await traceImport('TaskExecInk', ()=>import('./task-exec.ink.js'));
1621
- await TaskExecInk({
1622
- tasks: dependencies,
1416
+ const { default: JobExecInk } = await traceImport('JobExecInk', ()=>import('./job-exec.ink.js'));
1417
+ await JobExecInk({
1418
+ job: dependencies,
1623
1419
  verbose: [
1624
1420
  'verbose',
1625
1421
  'debug'
1626
1422
  ].includes(args.verbose)
1627
1423
  });
1424
+ if (dependencies.state() !== WorkloadState.Succeeded) {
1425
+ return;
1426
+ }
1628
1427
  } else {
1629
1428
  const logger = inject$(LOGGER);
1630
1429
  logger.verbose('No dependency to build');
1631
1430
  }
1632
- const child = cp.spawn(task.cmd, task.args, {
1633
- stdio: 'inherit',
1634
- cwd: task.cwd,
1635
- env: {
1636
- ...process$1.env,
1637
- ...task.env
1638
- },
1639
- shell: true,
1640
- windowsHide: true
1641
- });
1642
- process$1.exitCode = await new Promise((resolve)=>{
1643
- child.on('close', (code)=>{
1644
- resolve(code ?? 0);
1431
+ await startSpan({
1432
+ op: 'subprocess',
1433
+ name: [
1434
+ job.cmd,
1435
+ ...job.args
1436
+ ].join(' ')
1437
+ }, async ()=>{
1438
+ const child = spawn(job.cmd, job.args, {
1439
+ stdio: 'inherit',
1440
+ cwd: job.cwd,
1441
+ env: {
1442
+ ...process$1.env,
1443
+ ...job.env
1444
+ },
1445
+ shell: true,
1446
+ windowsHide: true
1447
+ });
1448
+ process$1.exitCode = await new Promise((resolve)=>{
1449
+ child.on('close', (code)=>{
1450
+ resolve(code ?? 0);
1451
+ });
1645
1452
  });
1646
1453
  });
1647
1454
  }
1648
1455
  };
1649
1456
 
1457
+ // Utils
1458
+ function printJson(data, stream = process.stdout) {
1459
+ if (stream.isTTY) {
1460
+ stream.write(JSON.stringify(data, null, 2));
1461
+ } else {
1462
+ stream.write(JSON.stringify(data));
1463
+ }
1464
+ }
1465
+
1650
1466
  // Command
1651
1467
  const command$2 = {
1652
1468
  command: 'list',
@@ -1882,12 +1698,10 @@ const command$1 = {
1882
1698
  // Parse task expression
1883
1699
  const taskParser = inject$(TaskParserService);
1884
1700
  const tree = taskParser.parse(expr.join(' '));
1885
- const tasks = new TaskSet();
1886
- tasks.add(await taskParser.buildTask(tree.roots[0], workspace, {
1701
+ return await taskParser.buildJob(tree.roots[0], workspace, {
1887
1702
  buildScript: args.buildScript,
1888
1703
  buildDeps: args.depsMode
1889
- }));
1890
- return tasks;
1704
+ });
1891
1705
  }
1892
1706
  };
1893
1707
 
@@ -2097,8 +1911,8 @@ function baseParser() {
2097
1911
  }
2098
1912
  /**
2099
1913
  * Prepare parser planning commands
2100
- */ function planParser(tasks$) {
2101
- return pipe$(baseParser(), planCommand(command$4, tasks$), planCommand(command$3, tasks$), planCommand(command$2, tasks$), planCommand(command$1, tasks$), planCommand(command, tasks$));
1914
+ */ function planParser(job$) {
1915
+ return pipe$(baseParser(), planCommand(command$4, job$), planCommand(command$3, job$), planCommand(command$2, job$), planCommand(command$1, job$), planCommand(command, job$));
2102
1916
  }
2103
1917
 
2104
1918
  // Bootstrap
@@ -2107,25 +1921,37 @@ const parser = executeParser();
2107
1921
  void startSpan({
2108
1922
  name: 'jill',
2109
1923
  op: 'cli.main',
1924
+ startTime: 0,
2110
1925
  attributes: {
2111
1926
  'cli.argv': argv
2112
1927
  }
2113
- }, ()=>parser.wrap(parser.terminalWidth()).fail((msg, err)=>{
2114
- const logger = inject$(LOGGER);
2115
- if (msg) {
2116
- logger.error(msg);
2117
- } else if (err instanceof ClientError) {
2118
- logger.warning(err.message);
2119
- } else {
2120
- captureException(err, {
2121
- tags: {
2122
- handled: false
2123
- }
2124
- });
2125
- logger.error(err.message);
2126
- }
2127
- process$1.exitCode = 1;
2128
- }).parseAsync(argv).catch(()=>{}));
1928
+ }, async ()=>{
1929
+ try {
1930
+ startInactiveSpan({
1931
+ name: 'bootstrap',
1932
+ op: 'cli.bootstrap',
1933
+ startTime: 0
1934
+ }).end();
1935
+ return await parser.wrap(parser.terminalWidth()).fail((msg, err)=>{
1936
+ const logger = inject$(LOGGER);
1937
+ if (msg) {
1938
+ logger.error(msg);
1939
+ } else if (err instanceof ClientError) {
1940
+ logger.error(err.message);
1941
+ } else {
1942
+ captureException(err, {
1943
+ tags: {
1944
+ handled: false
1945
+ }
1946
+ });
1947
+ logger.error(err.message);
1948
+ }
1949
+ process$1.exitCode = 1;
1950
+ }).parseAsync(argv);
1951
+ } catch {
1952
+ // Already handled
1953
+ }
1954
+ });
2129
1955
 
2130
- 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, instrument as e, isCommandCtx as i, logFormat as l, planParser as p };
1956
+ export { CWD as C, LOGGER as L, SCHEDULER as S, ConfigService as a, capitalize as c, instrument as i, logFormat as l, planParser as p };
2131
1957
  //# sourceMappingURL=main.js.map