@jujulego/jill 2.5.2 → 3.0.0-alpha.10

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