@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.
- package/CHANGELOG.md +19 -0
- package/build/__tests__/behavioral/upgrading/UpgradingAMonorepo.test.d.ts +26 -0
- package/build/__tests__/behavioral/upgrading/UpgradingAMonorepo.test.js +135 -0
- package/build/__tests__/behavioral/upgrading/UpgradingAMonorepo.test.js.map +1 -0
- package/build/components/QuizComponent.js.map +1 -1
- package/build/features/AbstractFeature.js.map +1 -1
- package/build/features/ActionQuestionAsker.js.map +1 -1
- package/build/features/onboard/stores/OnboardingStore.js.map +1 -1
- package/build/features/test/TestReporter.d.ts +2 -2
- package/build/features/test/TestReporter.js +118 -52
- package/build/features/test/TestReporter.js.map +1 -1
- package/build/features/test/TestRunner.d.ts +1 -0
- package/build/features/test/TestRunner.js +4 -1
- package/build/features/test/TestRunner.js.map +1 -1
- package/build/features/test/actions/TestAction.d.ts +1 -0
- package/build/features/test/actions/TestAction.js +59 -6
- package/build/features/test/actions/TestAction.js.map +1 -1
- package/build/services/PkgService.d.ts +4 -0
- package/build/services/PkgService.js +39 -1
- package/build/services/PkgService.js.map +1 -1
- package/build/tests/AbstractCliTest.js +2 -1
- package/build/tests/AbstractCliTest.js.map +1 -1
- package/build/tests/staticToInstanceMigration/StaticToInstanceMigrator.js.map +1 -1
- package/build/tests/staticToInstanceMigration/StaticToInstanceTestFileMigrator.js.map +1 -1
- package/build/utilities/form.utility.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/behavioral/upgrading/UpgradingAMonorepo.test.ts +166 -0
- package/src/components/QuizComponent.ts +8 -4
- package/src/features/AbstractFeature.ts +1 -2
- package/src/features/ActionFactory.ts +4 -5
- package/src/features/ActionQuestionAsker.ts +1 -2
- package/src/features/onboard/stores/OnboardingStore.ts +2 -1
- package/src/features/test/TestReporter.ts +95 -53
- package/src/features/test/TestRunner.ts +5 -1
- package/src/features/test/actions/TestAction.ts +86 -6
- package/src/services/PkgService.ts +47 -1
- package/src/tests/AbstractCliTest.ts +1 -0
- package/src/tests/staticToInstanceMigration/StaticToInstanceMigrator.ts +1 -3
- package/src/tests/staticToInstanceMigration/StaticToInstanceTestFileMigrator.ts +1 -3
- package/src/utilities/form.utility.ts +4 -3
- 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<
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
|
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.
|
|
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
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
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() {
|
|
@@ -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() {
|