@sprucelabs/spruce-cli 29.1.1 → 29.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/build/__tests__/behavioral/upgrading/UpgradingAMonorepo.test.d.ts +26 -0
  3. package/build/__tests__/behavioral/upgrading/UpgradingAMonorepo.test.js +135 -0
  4. package/build/__tests__/behavioral/upgrading/UpgradingAMonorepo.test.js.map +1 -0
  5. package/build/components/QuizComponent.js.map +1 -1
  6. package/build/features/AbstractFeature.js.map +1 -1
  7. package/build/features/ActionQuestionAsker.js.map +1 -1
  8. package/build/features/onboard/stores/OnboardingStore.js.map +1 -1
  9. package/build/features/test/TestReporter.d.ts +2 -2
  10. package/build/features/test/TestReporter.js +118 -52
  11. package/build/features/test/TestReporter.js.map +1 -1
  12. package/build/features/test/TestRunner.d.ts +1 -0
  13. package/build/features/test/TestRunner.js +4 -1
  14. package/build/features/test/TestRunner.js.map +1 -1
  15. package/build/features/test/actions/TestAction.d.ts +1 -0
  16. package/build/features/test/actions/TestAction.js +59 -6
  17. package/build/features/test/actions/TestAction.js.map +1 -1
  18. package/build/services/PkgService.d.ts +4 -0
  19. package/build/services/PkgService.js +39 -1
  20. package/build/services/PkgService.js.map +1 -1
  21. package/build/tests/AbstractCliTest.js +2 -1
  22. package/build/tests/AbstractCliTest.js.map +1 -1
  23. package/build/tests/staticToInstanceMigration/StaticToInstanceMigrator.js.map +1 -1
  24. package/build/tests/staticToInstanceMigration/StaticToInstanceTestFileMigrator.js.map +1 -1
  25. package/build/utilities/form.utility.js.map +1 -1
  26. package/package.json +3 -3
  27. package/src/__tests__/behavioral/upgrading/UpgradingAMonorepo.test.ts +166 -0
  28. package/src/components/QuizComponent.ts +8 -4
  29. package/src/features/AbstractFeature.ts +1 -2
  30. package/src/features/ActionFactory.ts +4 -5
  31. package/src/features/ActionQuestionAsker.ts +1 -2
  32. package/src/features/onboard/stores/OnboardingStore.ts +2 -1
  33. package/src/features/test/TestReporter.ts +95 -53
  34. package/src/features/test/TestRunner.ts +5 -1
  35. package/src/features/test/actions/TestAction.ts +86 -6
  36. package/src/services/PkgService.ts +47 -1
  37. package/src/tests/AbstractCliTest.ts +1 -0
  38. package/src/tests/staticToInstanceMigration/StaticToInstanceMigrator.ts +1 -3
  39. package/src/tests/staticToInstanceMigration/StaticToInstanceTestFileMigrator.ts +1 -3
  40. package/src/utilities/form.utility.ts +4 -3
  41. package/src/widgets/types/widgets.types.ts +3 -2
@@ -0,0 +1,166 @@
1
+ import { diskUtil } from '@sprucelabs/spruce-skill-utils'
2
+ import { test, assert } from '@sprucelabs/test-utils'
3
+ import CommandServiceImpl from '../../../services/CommandService'
4
+ import PkgService from '../../../services/PkgService'
5
+ import AbstractCliTest from '../../../tests/AbstractCliTest'
6
+
7
+ export default class UpgradingAMonorepoTest extends AbstractCliTest {
8
+ private static monorepoDir: string
9
+ private static packageADir: string
10
+ private static packageBDir: string
11
+ private static wackyPackageName: string
12
+ private static pkg: PkgService
13
+ private static monoRepoPkg: PkgService
14
+ private static passedYarnArgs: string[] = []
15
+ private static yarnHitCount = 0
16
+
17
+ protected static async beforeAll() {
18
+ await super.beforeAll()
19
+ await this.setupMonorepo()
20
+ }
21
+
22
+ protected static async beforeEach() {
23
+ await super.beforeEach()
24
+
25
+ this.passedYarnArgs = []
26
+ this.yarnHitCount = 0
27
+
28
+ this.cwd = this.monorepoDir
29
+ this.monoRepoPkg = this.Service('pkg', this.monorepoDir)
30
+ }
31
+
32
+ @test()
33
+ protected static async canUpgradePackageAInMonorepo() {
34
+ this.setCwd(this.packageADir)
35
+ this.fakeYarnCommands()
36
+ await this.assertUpgradePasses()
37
+ }
38
+
39
+ @test()
40
+ protected static async canUpgradePackageBWithCrossDependency() {
41
+ this.setCwd(this.packageBDir)
42
+
43
+ this.fakeYarnCommands()
44
+
45
+ await this.assertUpgradePasses()
46
+
47
+ assert.isEqual(
48
+ this.yarnHitCount,
49
+ 3,
50
+ 'Should not try and install if only module is ignored'
51
+ )
52
+
53
+ this.assertDidNotUpgradePackageA()
54
+ }
55
+
56
+ @test()
57
+ protected static async canUpgradeWithRandomlyNamedSiblingPackage() {
58
+ this.wackyPackageName = `package-${Math.random()}`
59
+ this.createMinimalPackage(this.wackyPackageName)
60
+
61
+ this.addDependency(this.packageADir, this.wackyPackageName, '^1.0.0')
62
+ this.addDependency(this.packageBDir, this.wackyPackageName, '^1.0.0')
63
+
64
+ this.setCwd(this.packageADir)
65
+ this.fakeYarnCommands()
66
+
67
+ await this.assertUpgradePasses()
68
+
69
+ this.assertPackageSkipped(this.wackyPackageName)
70
+ }
71
+
72
+ private static assertDidNotUpgradePackageA() {
73
+ this.assertPackageSkipped('package-a')
74
+ }
75
+
76
+ private static assertPackageSkipped(packageName: string) {
77
+ assert.doesNotInclude(
78
+ this.passedYarnArgs,
79
+ packageName,
80
+ `Should not try to upgrade ${packageName}`
81
+ )
82
+ }
83
+
84
+ private static fakeYarnCommands() {
85
+ CommandServiceImpl.fakeCommand(/yarn*/, {
86
+ code: 0,
87
+ callback: (_, args) => {
88
+ this.passedYarnArgs.push(...args)
89
+ this.yarnHitCount++
90
+ },
91
+ })
92
+ }
93
+
94
+ private static async assertUpgradePasses() {
95
+ const results = await this.Action('node', 'upgrade').execute({})
96
+ assert.isFalsy(results.errors, 'Should not have errors')
97
+ }
98
+
99
+ private static async setupMonorepo() {
100
+ this.monorepoDir = this.freshTmpDir()
101
+ this.setCwd(this.monorepoDir)
102
+
103
+ this.createRootPackageJson()
104
+
105
+ this.packageADir = await this.createModule('package-a')
106
+ this.packageBDir = await this.createModule('package-b')
107
+
108
+ this.addDependency(this.packageBDir, 'package-a', '^1.0.0')
109
+ }
110
+
111
+ private static createRootPackageJson() {
112
+ this.setCwd(this.monorepoDir)
113
+ const pkgPath = this.resolvePath('package.json')
114
+ diskUtil.writeFile(pkgPath, '{}')
115
+
116
+ this.monoRepoPkg = this.Service('pkg')
117
+ this.monoRepoPkg.set({ path: ['name'], value: 'test-monorepo' })
118
+ //@ts-ignore REMOVE WHEN YOU FIND THIS, TYPES UPDATED
119
+ this.monoRepoPkg.set({ path: ['private'], value: true })
120
+ this.monoRepoPkg.set({ path: ['workspaces'], value: ['packages/*'] })
121
+ }
122
+
123
+ private static async createModule(name: string) {
124
+ const packageDir = this.resolvePackageDir(name)
125
+ diskUtil.createDir(packageDir)
126
+ this.setCwd(packageDir)
127
+
128
+ await this.Action('node', 'create', {
129
+ shouldAutoHandleDependencies: true,
130
+ }).execute({
131
+ name,
132
+ description: `Test module ${name}`,
133
+ })
134
+
135
+ return packageDir
136
+ }
137
+
138
+ private static resolvePackageDir(name: string) {
139
+ return diskUtil.resolvePath(this.monorepoDir, 'packages', name)
140
+ }
141
+
142
+ private static addDependency(
143
+ packageDir: string,
144
+ depName: string,
145
+ version: string
146
+ ) {
147
+ this.pkg = this.Service('pkg', packageDir)
148
+ this.pkg.set({
149
+ path: ['dependencies', depName],
150
+ value: version,
151
+ })
152
+ }
153
+
154
+ private static createMinimalPackage(name: string) {
155
+ const packageDir = this.resolvePackageDir(name)
156
+ diskUtil.createDir(packageDir)
157
+
158
+ const pkgPath = diskUtil.resolvePath(packageDir, 'package.json')
159
+ diskUtil.writeFile(
160
+ pkgPath,
161
+ JSON.stringify({ name, version: '1.0.0' }, null, 2)
162
+ )
163
+
164
+ return packageDir
165
+ }
166
+ }
@@ -34,8 +34,10 @@ enum AnswerValidity {
34
34
  Incorrect = 'incorrect',
35
35
  }
36
36
 
37
- interface QuizPresentationOptions<T extends Schema, Q extends QuizQuestions>
38
- extends Omit<FormPresentationOptions<T>, 'fields'> {
37
+ interface QuizPresentationOptions<
38
+ T extends Schema,
39
+ Q extends QuizQuestions,
40
+ > extends Omit<FormPresentationOptions<T>, 'fields'> {
39
41
  questions?: QuizAnswerFieldNames<Q>[]
40
42
  randomizeQuestions?: boolean
41
43
  }
@@ -77,8 +79,10 @@ interface QuizPresentationResults<Q extends QuizQuestions> {
77
79
  totalQuestions: number
78
80
  }
79
81
 
80
- interface QuizOptions<T extends Schema, Q extends QuizQuestions>
81
- extends Omit<FormOptions<T>, 'schema'> {
82
+ interface QuizOptions<T extends Schema, Q extends QuizQuestions> extends Omit<
83
+ FormOptions<T>,
84
+ 'schema'
85
+ > {
82
86
  /** Should we randomize the questions */
83
87
  randomizeQuestions?: boolean
84
88
  /** The questions we are asking */
@@ -31,8 +31,7 @@ import { FeatureCode } from './features.types'
31
31
 
32
32
  export default abstract class AbstractFeature<
33
33
  S extends Schema | undefined = Schema | undefined,
34
- > implements ServiceProvider
35
- {
34
+ > implements ServiceProvider {
36
35
  public abstract description: string
37
36
  public readonly dependencies: FeatureDependency[] = []
38
37
  protected _packageDependencies: PackageDependency[] = []
@@ -121,11 +121,10 @@ export default class ActionFactory {
121
121
  }
122
122
  }
123
123
 
124
- export interface FeatureActionFactoryOptions
125
- extends Omit<
126
- ActionOptions,
127
- 'parent' | 'actionExecuter' | 'featureInstaller'
128
- > {
124
+ export interface FeatureActionFactoryOptions extends Omit<
125
+ ActionOptions,
126
+ 'parent' | 'actionExecuter' | 'featureInstaller'
127
+ > {
129
128
  emitter: GlobalEmitter
130
129
  blockedCommands?: BlockedCommands
131
130
  optionOverrides?: OptionOverrides
@@ -17,8 +17,7 @@ import {
17
17
 
18
18
  export default class ActionQuestionAskerImpl<
19
19
  F extends FeatureCode = FeatureCode,
20
- > implements ActionQuestionAsker<F>
21
- {
20
+ > implements ActionQuestionAsker<F> {
22
21
  private ui: GraphicsInterface
23
22
  public static Class?: new (
24
23
  options: ActionQuestionAskerOptions
@@ -8,7 +8,8 @@ import AbstractLocalStore, {
8
8
  import { StoreOptions } from '../../../stores/AbstractStore'
9
9
 
10
10
  export interface OnboardingStoreSettings
11
- extends LocalStoreSettings,
11
+ extends
12
+ LocalStoreSettings,
12
13
  SpruceSchemas.SpruceCli.v2020_07_22.Onboarding {}
13
14
 
14
15
  type OnboardingSchema = SpruceSchemas.SpruceCli.v2020_07_22.OnboardingSchema
@@ -53,10 +53,7 @@ export default class TestReporter {
53
53
  private handleToggleSmartWatch?: () => any
54
54
  private minWidth = 50
55
55
  private isRpTraining: boolean
56
- private trainingTokenPopup?: PopupWidget
57
56
  private shouldStripCwdFromErrors = true
58
- // private orientationWhenErrorLogWasShown: TestReporterOrientation =
59
- // 'landscape'
60
57
 
61
58
  public constructor(options?: TestReporterOptions) {
62
59
  this.cwd = options?.cwd
@@ -92,10 +89,20 @@ export default class TestReporter {
92
89
  }
93
90
 
94
91
  public setIsRpTraining(isRpTraining: boolean) {
95
- this.setLabelStatus('rp', 'Train AI', isRpTraining)
92
+ this.setRpTrainingStatus(isRpTraining ? 'on' : 'off')
96
93
  this.isRpTraining = isRpTraining
97
94
  }
98
95
 
96
+ public setRpTrainingStatus(status: 'off' | 'installing' | 'on') {
97
+ const colors: Record<string, { fg: string; bg: string }> = {
98
+ off: { fg: 'w', bg: 'r' },
99
+ installing: { fg: 'k', bg: 'y' },
100
+ on: { fg: 'k', bg: 'g' },
101
+ }
102
+ const { fg, bg } = colors[status]
103
+ this.menu.setTextForItem('rp', `Train AI ^${fg}^#^${bg} • ^`)
104
+ }
105
+
99
106
  public startCountdownTimer(durationSec: number) {
100
107
  clearInterval(this.countDownTimeInterval)
101
108
  this.countDownTimeInterval = undefined
@@ -370,38 +377,93 @@ export default class TestReporter {
370
377
  }
371
378
  }
372
379
 
373
- public async askForTrainingToken() {
374
- if (this.trainingTokenPopup) {
375
- return
376
- }
380
+ public async askForProjectName(
381
+ defaultName: string
382
+ ): Promise<string | undefined> {
383
+ return new Promise((resolve) => {
384
+ const popup = this.widgets.Widget('popup', {
385
+ parent: this.window,
386
+ top: 5,
387
+ left: 8,
388
+ width: 65,
389
+ height: 15,
390
+ })
377
391
 
378
- this.trainingTokenPopup = this.widgets.Widget('popup', {
379
- parent: this.window,
380
- top: 10,
381
- left: 10,
382
- width: 50,
383
- height: 10,
384
- })
392
+ this.widgets.Widget('text', {
393
+ parent: popup,
394
+ left: 2,
395
+ top: 1,
396
+ height: 1,
397
+ width: popup.getFrame().width - 4,
398
+ text: 'regressionproof.ai',
399
+ })
385
400
 
386
- this.widgets.Widget('text', {
387
- parent: this.trainingTokenPopup,
388
- left: 4,
389
- top: 3,
390
- height: 4,
391
- width: this.trainingTokenPopup.getFrame().width - 2,
392
- text: 'Coming soon...',
393
- })
401
+ this.widgets.Widget('text', {
402
+ parent: popup,
403
+ left: 2,
404
+ top: 3,
405
+ height: 4,
406
+ width: popup.getFrame().width - 4,
407
+ text: "Help Spruce train coding agents on how to do proper TDD.\nIf you contribute to this effort, you'll get a lifetime\nlicense to the agents for free.",
408
+ })
394
409
 
395
- const button = this.widgets.Widget('button', {
396
- parent: this.trainingTokenPopup,
397
- left: 20,
398
- top: 7,
399
- text: ' Ok ',
400
- })
410
+ const input = this.widgets.Widget('input', {
411
+ parent: popup,
412
+ left: 2,
413
+ top: 8,
414
+ label: 'Name',
415
+ width: popup.getFrame().width - 6,
416
+ height: 1,
417
+ value: defaultName,
418
+ })
401
419
 
402
- await button.on('click', async () => {
403
- await this.trainingTokenPopup?.destroy()
404
- delete this.trainingTokenPopup
420
+ const learnMoreButton = this.widgets.Widget('button', {
421
+ parent: popup,
422
+ left: 2,
423
+ top: 11,
424
+ text: ' Learn more ',
425
+ })
426
+
427
+ const cancelButton = this.widgets.Widget('button', {
428
+ parent: popup,
429
+ left: 40,
430
+ top: 11,
431
+ text: ' Cancel ',
432
+ })
433
+
434
+ const enableButton = this.widgets.Widget('button', {
435
+ parent: popup,
436
+ left: 50,
437
+ top: 11,
438
+ text: ' Enable ',
439
+ })
440
+
441
+ void learnMoreButton.on('click', async () => {
442
+ const open = await import('open')
443
+ void open.default('https://regressionproof.ai')
444
+ })
445
+
446
+ void enableButton.on('click', () => {
447
+ const value = input.getValue()
448
+ void popup.destroy()
449
+ resolve(value || undefined)
450
+ })
451
+
452
+ void cancelButton.on('click', () => {
453
+ void popup.destroy()
454
+ resolve(undefined)
455
+ })
456
+
457
+ void input.on('submit', () => {
458
+ const value = input.getValue()
459
+ void popup.destroy()
460
+ resolve(value || undefined)
461
+ })
462
+
463
+ void input.on('cancel', () => {
464
+ void popup.destroy()
465
+ resolve(undefined)
466
+ })
405
467
  })
406
468
  }
407
469
 
@@ -638,7 +700,6 @@ export default class TestReporter {
638
700
  this.testLog.setText(logContent)
639
701
 
640
702
  if (!errorContent) {
641
- // this.errorLog && this.destroyErrorLog()
642
703
  this.errorLog?.setText(' Nothing to report...')
643
704
  } else {
644
705
  !this.errorLog && this.dropInErrorLog()
@@ -678,8 +739,6 @@ export default class TestReporter {
678
739
  }
679
740
 
680
741
  private dropInErrorLog() {
681
- // this.orientationWhenErrorLogWasShown = this.orientation
682
-
683
742
  if (this.bottomLayout.getRows().length === 1) {
684
743
  if (this.orientation === 'portrait') {
685
744
  this.bottomLayout.addRow({
@@ -719,24 +778,7 @@ export default class TestReporter {
719
778
  }
720
779
  }
721
780
 
722
- private destroyErrorLog() {
723
- // if (this.errorLog) {
724
- // void this.errorLog?.destroy()
725
- // this.errorLog = undefined
726
- // if (this.orientationWhenErrorLogWasShown === 'landscape') {
727
- // this.bottomLayout.removeColumn(0, 1)
728
- // this.bottomLayout.setColumnWidth({
729
- // rowIdx: 0,
730
- // columnIdx: 0,
731
- // width: '100%',
732
- // })
733
- // } else {
734
- // this.bottomLayout.removeRow(1)
735
- // this.bottomLayout.setRowHeight(0, '100%')
736
- // }
737
- // this.bottomLayout.updateLayout()
738
- // }
739
- }
781
+ private destroyErrorLog() {}
740
782
 
741
783
  private updateProgressBar(results: SpruceTestResults) {
742
784
  if (results.totalTestFilesComplete ?? 0 > 0) {
@@ -29,6 +29,7 @@ export default class TestRunner extends AbstractEventEmitter<TestRunnerContract>
29
29
  public async run(options?: {
30
30
  pattern?: string | null
31
31
  debugPort?: number | null
32
+ isRpTraining?: boolean
32
33
  }): Promise<SpruceTestResults & { wasKilled: boolean }> {
33
34
  this.wasKilled = false
34
35
 
@@ -45,7 +46,10 @@ export default class TestRunner extends AbstractEventEmitter<TestRunnerContract>
45
46
  '"'
46
47
  )
47
48
  }
48
- const command = `node --experimental-vm-modules --unhandled-rejections=strict ${debugArgs} ${jestPath} --reporters="@sprucelabs/jest-json-reporter" --testRunner="jest-circus/runner" --passWithNoTests ${
49
+ const rpReporter = options?.isRpTraining
50
+ ? ' --reporters="@regressionproof/jest-reporter"'
51
+ : ''
52
+ const command = `node --experimental-vm-modules --unhandled-rejections=strict ${debugArgs} ${jestPath} --reporters="@sprucelabs/jest-json-reporter"${rpReporter} --testRunner="jest-circus/runner" --passWithNoTests ${
49
53
  pattern ? escapeShell(pattern) : ''
50
54
  }`
51
55
 
@@ -50,11 +50,17 @@ export default class TestAction extends AbstractAction<OptionsSchema> {
50
50
  public async execute(
51
51
  options: SchemaValues<OptionsSchema>
52
52
  ): Promise<FeatureActionResponse> {
53
+ const settings = this.Service('settings')
54
+
53
55
  if (!options.watchMode) {
54
- const settings = this.Service('settings')
55
56
  options.watchMode = settings.get('test.watchMode') ?? 'off'
56
57
  }
57
58
 
59
+ const rpSettings = settings.get('regressionproof') as
60
+ | { enabled: boolean; projectName: string }
61
+ | undefined
62
+ this.isRpTraining = rpSettings?.enabled ?? false
63
+
58
64
  const normalizedOptions = this.validateAndNormalizeOptions(options)
59
65
 
60
66
  const {
@@ -247,11 +253,84 @@ export default class TestAction extends AbstractAction<OptionsSchema> {
247
253
  }
248
254
 
249
255
  private async handleToggleRpTraining() {
250
- // this.isRpTraining = !this.isRpTraining
251
- // this.testReporter?.setIsRpTraining(this.isRpTraining)
252
- // if (this.isRpTraining) {
253
- await this.testReporter?.askForTrainingToken()
254
- // }
256
+ const settings = this.Service('settings')
257
+ const rpSettings = settings.get('regressionproof') as
258
+ | { enabled: boolean; projectName: string }
259
+ | undefined
260
+
261
+ if (!rpSettings) {
262
+ const defaultName = await this.getDefaultProjectName()
263
+ const projectName =
264
+ await this.testReporter?.askForProjectName(defaultName)
265
+
266
+ if (projectName) {
267
+ try {
268
+ this.testReporter?.setRpTrainingStatus('installing')
269
+ this.testReporter?.setStatusLabel(
270
+ 'Installing regressionproof packages...'
271
+ )
272
+
273
+ const pkgService = this.Service('pkg')
274
+ await pkgService.install(
275
+ [
276
+ '@regressionproof/cli',
277
+ '@regressionproof/jest-reporter',
278
+ ],
279
+ { isDev: true }
280
+ )
281
+
282
+ this.testReporter?.setStatusLabel(
283
+ 'Initializing regressionproof...'
284
+ )
285
+
286
+ const commandService = this.Service('command')
287
+ await commandService.execute(
288
+ `node node_modules/@regressionproof/cli/build/cli.js init ${projectName}`,
289
+ { ignoreErrors: true }
290
+ )
291
+
292
+ settings.set('regressionproof', {
293
+ enabled: true,
294
+ projectName,
295
+ })
296
+ this.isRpTraining = true
297
+ this.testReporter?.setIsRpTraining(true)
298
+ this.testReporter?.setStatusLabel('')
299
+ } catch (err: any) {
300
+ this.testReporter?.setRpTrainingStatus('off')
301
+ const message = err?.message ?? 'Unknown error'
302
+ this.testReporter?.setStatusLabel(
303
+ `Setup failed: ${message}`
304
+ )
305
+ }
306
+ }
307
+ } else {
308
+ const newEnabled = !rpSettings.enabled
309
+ settings.set('regressionproof', {
310
+ ...rpSettings,
311
+ enabled: newEnabled,
312
+ })
313
+ this.isRpTraining = newEnabled
314
+ this.testReporter?.setIsRpTraining(newEnabled)
315
+ }
316
+ }
317
+
318
+ private async getDefaultProjectName(): Promise<string> {
319
+ try {
320
+ const commandService = this.Service('command')
321
+ const result = await commandService.execute(
322
+ 'git remote get-url origin',
323
+ { ignoreErrors: true }
324
+ )
325
+ if (result.stdout) {
326
+ const match = result.stdout.match(/\/([^/]+?)(?:\.git)?$/)
327
+ if (match) {
328
+ return match[1].replace('.git', '')
329
+ }
330
+ }
331
+ } catch {}
332
+
333
+ return pathUtil.basename(this.cwd)
255
334
  }
256
335
 
257
336
  public setWatchMode(mode: WatchMode) {
@@ -381,6 +460,7 @@ export default class TestAction extends AbstractAction<OptionsSchema> {
381
460
  let testResults: SpruceTestResults = await this.testRunner.run({
382
461
  pattern: this.pattern,
383
462
  debugPort: this.inspect,
463
+ isRpTraining: this.isRpTraining,
384
464
  })
385
465
 
386
466
  if (
@@ -1,3 +1,5 @@
1
+ import fsUtil from 'fs'
2
+ import pathUtil from 'path'
1
3
  import {
2
4
  diskUtil,
3
5
  PkgService as BasePkgService,
@@ -25,7 +27,51 @@ export default class PkgService extends BasePkgService {
25
27
  }
26
28
 
27
29
  public async install(pkg?: string[] | string, options?: AddOptions) {
28
- return this.packageManager.installDependencies(pkg, options)
30
+ const filtered = this.filterOutWorkspaceSiblings(pkg)
31
+ if (Array.isArray(filtered) && filtered.length === 0) {
32
+ return { totalInstalled: 0 }
33
+ }
34
+ return this.packageManager.installDependencies(filtered, options)
35
+ }
36
+
37
+ private filterOutWorkspaceSiblings(
38
+ pkg?: string[] | string
39
+ ): string[] | string | undefined {
40
+ if (!pkg) {
41
+ return pkg
42
+ }
43
+ const siblings = this.getWorkspaceSiblingNames()
44
+ const packages = Array.isArray(pkg) ? pkg : [pkg]
45
+ const filtered = packages.filter((p) => !siblings.includes(p))
46
+ return Array.isArray(pkg) ? filtered : filtered[0]
47
+ }
48
+
49
+ private getWorkspaceSiblingNames(): string[] {
50
+ const packagesDir = pathUtil.dirname(this.cwd)
51
+ if (pathUtil.basename(packagesDir) !== 'packages') {
52
+ return []
53
+ }
54
+
55
+ const monorepoRoot = pathUtil.dirname(packagesDir)
56
+ const rootPkgPath = pathUtil.join(monorepoRoot, 'package.json')
57
+ if (!diskUtil.doesFileExist(rootPkgPath)) {
58
+ return []
59
+ }
60
+
61
+ const siblingDirs = fsUtil.readdirSync(packagesDir)
62
+ const names: string[] = []
63
+
64
+ for (const dir of siblingDirs) {
65
+ const pkgPath = pathUtil.join(packagesDir, dir, 'package.json')
66
+ if (diskUtil.doesFileExist(pkgPath)) {
67
+ const siblingPkg = JSON.parse(diskUtil.readFile(pkgPath))
68
+ if (siblingPkg.name) {
69
+ names.push(siblingPkg.name)
70
+ }
71
+ }
72
+ }
73
+
74
+ return names
29
75
  }
30
76
 
31
77
  public getSkillNamespace() {
@@ -138,6 +138,7 @@ export default abstract class AbstractCliTest extends AbstractSpruceTest {
138
138
  }
139
139
 
140
140
  if (
141
+ this.homeDir &&
141
142
  diskUtil.doesDirExist(this.homeDir) &&
142
143
  testUtil.shouldClearCache()
143
144
  ) {
@@ -4,9 +4,7 @@ import LintService from '../../services/LintService'
4
4
  import StaticTestFinder from './StaticTestFinder'
5
5
  import { StaticToInstanceTestFileMigrator } from './StaticToInstanceTestFileMigrator'
6
6
 
7
- export default class StaticToInstanceMigratorImpl
8
- implements StaticToInstanceMigrator
9
- {
7
+ export default class StaticToInstanceMigratorImpl implements StaticToInstanceMigrator {
10
8
  public static diskUtil = diskUtil
11
9
  public static Class?: new (
12
10
  options: StaticToInstanceMigratorOptions
@@ -1,8 +1,6 @@
1
1
  import { assertOptions } from '@sprucelabs/schema'
2
2
 
3
- export default class StaticToInstanceTestFileMigratorImpl
4
- implements StaticToInstanceTestFileMigrator
5
- {
3
+ export default class StaticToInstanceTestFileMigratorImpl implements StaticToInstanceTestFileMigrator {
6
4
  public static Class?: new () => StaticToInstanceTestFileMigrator
7
5
 
8
6
  public static Migrator() {