@sprucelabs/spruce-cli 14.28.6 → 14.29.1

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 (125) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/build/__tests__/behavioral/SettingLogTransportsInASkill.test.js +13 -9
  3. package/build/__tests__/behavioral/SettingLogTransportsInASkill.test.js.map +1 -1
  4. package/build/__tests__/behavioral/SyncingEventsOnlyFromDependencies.test.d.ts +1 -1
  5. package/build/__tests__/behavioral/SyncingEventsOnlyFromDependencies.test.js +24 -14
  6. package/build/__tests__/behavioral/SyncingEventsOnlyFromDependencies.test.js.map +1 -1
  7. package/build/__tests__/behavioral/TestingDataStores.test.js +15 -11
  8. package/build/__tests__/behavioral/TestingDataStores.test.js.map +1 -1
  9. package/build/__tests__/behavioral/eventContract/PullingMercuryEventContract.test.js +3 -1
  10. package/build/__tests__/behavioral/eventContract/PullingMercuryEventContract.test.js.map +1 -1
  11. package/build/__tests__/behavioral/events/CreatingAListener.test.d.ts +2 -0
  12. package/build/__tests__/behavioral/events/CreatingAListener.test.js +157 -81
  13. package/build/__tests__/behavioral/events/CreatingAListener.test.js.map +1 -1
  14. package/build/__tests__/behavioral/events/KeepingListenersInSync.test.d.ts +7 -0
  15. package/build/__tests__/behavioral/events/KeepingListenersInSync.test.js +171 -0
  16. package/build/__tests__/behavioral/events/KeepingListenersInSync.test.js.map +1 -0
  17. package/build/__tests__/behavioral/events/SkillEmitsBootEvents.test.d.ts +1 -0
  18. package/build/__tests__/behavioral/events/SkillEmitsBootEvents.test.js +53 -21
  19. package/build/__tests__/behavioral/events/SkillEmitsBootEvents.test.js.map +1 -1
  20. package/build/__tests__/behavioral/skill/UpgradingWithListeners.test.d.ts +5 -0
  21. package/build/__tests__/behavioral/skill/UpgradingWithListeners.test.js +137 -0
  22. package/build/__tests__/behavioral/skill/UpgradingWithListeners.test.js.map +1 -0
  23. package/build/__tests__/behavioral/stores/CreatingADataStore.test.js +4 -1
  24. package/build/__tests__/behavioral/stores/CreatingADataStore.test.js.map +1 -1
  25. package/build/__tests__/behavioral/stores/KeepingDataStoresInSync.test.d.ts +4 -1
  26. package/build/__tests__/behavioral/stores/KeepingDataStoresInSync.test.js +136 -47
  27. package/build/__tests__/behavioral/stores/KeepingDataStoresInSync.test.js.map +1 -1
  28. package/build/__tests__/behavioral/tests/CreatingATest.test.js +14 -10
  29. package/build/__tests__/behavioral/tests/CreatingATest.test.js.map +1 -1
  30. package/build/__tests__/behavioral/views/TestingViewControllers.test.d.ts +1 -0
  31. package/build/__tests__/behavioral/views/TestingViewControllers.test.js +67 -35
  32. package/build/__tests__/behavioral/views/TestingViewControllers.test.js.map +1 -1
  33. package/build/__tests__/implementation/AuthService.test.d.ts +1 -0
  34. package/build/__tests__/implementation/AuthService.test.js +8 -0
  35. package/build/__tests__/implementation/AuthService.test.js.map +1 -1
  36. package/build/__tests__/implementation/EventStore.test.js +3 -1
  37. package/build/__tests__/implementation/EventStore.test.js.map +1 -1
  38. package/build/__tests__/implementation/LintService.test.d.ts +5 -0
  39. package/build/__tests__/implementation/LintService.test.js +130 -0
  40. package/build/__tests__/implementation/LintService.test.js.map +1 -0
  41. package/build/__tests__/implementation/StoreFeature.test.d.ts +4 -0
  42. package/build/__tests__/implementation/StoreFeature.test.js +132 -0
  43. package/build/__tests__/implementation/StoreFeature.test.js.map +1 -0
  44. package/build/errors/SpruceError.js +8 -4
  45. package/build/errors/SpruceError.js.map +1 -1
  46. package/build/features/event/EventFeature.d.ts +2 -2
  47. package/build/features/event/EventFeature.js +53 -13
  48. package/build/features/event/EventFeature.js.map +1 -1
  49. package/build/features/event/actions/ListenAction.d.ts +1 -0
  50. package/build/features/event/actions/ListenAction.js +60 -31
  51. package/build/features/event/actions/ListenAction.js.map +1 -1
  52. package/build/features/event/actions/SyncListenersAction.d.ts +15 -0
  53. package/build/features/event/actions/SyncListenersAction.js +115 -0
  54. package/build/features/event/actions/SyncListenersAction.js.map +1 -0
  55. package/build/features/event/builders/ListenerTemplateItemBuilder.d.ts +8 -0
  56. package/build/features/event/builders/ListenerTemplateItemBuilder.js +65 -0
  57. package/build/features/event/builders/ListenerTemplateItemBuilder.js.map +1 -0
  58. package/build/features/event/stores/EventStore.js +2 -2
  59. package/build/features/event/stores/EventStore.js.map +1 -1
  60. package/build/features/event/stores/ListenerStore.d.ts +9 -0
  61. package/build/features/event/stores/ListenerStore.js +122 -0
  62. package/build/features/event/stores/ListenerStore.js.map +1 -0
  63. package/build/features/event/writers/EventWriter.d.ts +4 -1
  64. package/build/features/event/writers/EventWriter.js +42 -8
  65. package/build/features/event/writers/EventWriter.js.map +1 -1
  66. package/build/features/skill/SkillFeature.js +1 -1
  67. package/build/features/skill/SkillFeature.js.map +1 -1
  68. package/build/features/skill/stores/SkillStore.js +4 -8
  69. package/build/features/skill/stores/SkillStore.js.map +1 -1
  70. package/build/features/store/StoreFeature.d.ts +1 -1
  71. package/build/features/store/StoreFeature.js +16 -11
  72. package/build/features/store/StoreFeature.js.map +1 -1
  73. package/build/services/AuthService.d.ts +4 -1
  74. package/build/services/AuthService.js +15 -4
  75. package/build/services/AuthService.js.map +1 -1
  76. package/build/services/GameService.js +14 -10
  77. package/build/services/GameService.js.map +1 -1
  78. package/build/services/LintService.d.ts +5 -2
  79. package/build/services/LintService.js +137 -92
  80. package/build/services/LintService.js.map +1 -1
  81. package/build/services/ServiceFactory.js +9 -3
  82. package/build/services/ServiceFactory.js.map +1 -1
  83. package/build/stores/StoreFactory.d.ts +2 -0
  84. package/build/stores/StoreFactory.js +4 -1
  85. package/build/stores/StoreFactory.js.map +1 -1
  86. package/build/tests/AbstractCliTest.js +3 -3
  87. package/build/tests/AbstractCliTest.js.map +1 -1
  88. package/build/writers/AbstractWriter.d.ts +0 -3
  89. package/build/writers/AbstractWriter.js +2 -18
  90. package/build/writers/AbstractWriter.js.map +1 -1
  91. package/package.json +30 -28
  92. package/src/__tests__/behavioral/SettingLogTransportsInASkill.test.ts +2 -0
  93. package/src/__tests__/behavioral/SyncingEventsOnlyFromDependencies.test.ts +10 -5
  94. package/src/__tests__/behavioral/TestingDataStores.test.ts +2 -0
  95. package/src/__tests__/behavioral/eventContract/PullingMercuryEventContract.test.ts +1 -0
  96. package/src/__tests__/behavioral/events/CreatingAListener.test.ts +28 -9
  97. package/src/__tests__/behavioral/events/KeepingListenersInSync.test.ts +47 -0
  98. package/src/__tests__/behavioral/events/SkillEmitsBootEvents.test.ts +6 -0
  99. package/src/__tests__/behavioral/skill/UpgradingWithListeners.test.ts +59 -0
  100. package/src/__tests__/behavioral/stores/CreatingADataStore.test.ts +1 -0
  101. package/src/__tests__/behavioral/stores/KeepingDataStoresInSync.test.ts +35 -6
  102. package/src/__tests__/behavioral/tests/CreatingATest.test.ts +2 -0
  103. package/src/__tests__/behavioral/views/TestingViewControllers.test.ts +6 -0
  104. package/src/__tests__/implementation/AuthService.test.ts +6 -0
  105. package/src/__tests__/implementation/EventStore.test.ts +1 -0
  106. package/src/__tests__/implementation/LintService.test.ts +34 -0
  107. package/src/__tests__/implementation/StoreFeature.test.ts +40 -0
  108. package/src/errors/SpruceError.ts +9 -5
  109. package/src/features/event/EventFeature.ts +21 -4
  110. package/src/features/event/actions/ListenAction.ts +9 -3
  111. package/src/features/event/actions/SyncListenersAction.ts +38 -0
  112. package/src/features/event/builders/ListenerTemplateItemBuilder.ts +27 -0
  113. package/src/features/event/stores/EventStore.ts +1 -1
  114. package/src/features/event/stores/ListenerStore.ts +32 -0
  115. package/src/features/event/writers/EventWriter.ts +19 -0
  116. package/src/features/skill/SkillFeature.ts +1 -1
  117. package/src/features/skill/stores/SkillStore.ts +4 -3
  118. package/src/features/store/StoreFeature.ts +7 -4
  119. package/src/services/AuthService.ts +15 -5
  120. package/src/services/GameService.ts +1 -0
  121. package/src/services/LintService.ts +34 -5
  122. package/src/services/ServiceFactory.ts +8 -3
  123. package/src/stores/StoreFactory.ts +3 -0
  124. package/src/tests/AbstractCliTest.ts +2 -2
  125. package/src/writers/AbstractWriter.ts +2 -13
@@ -1,5 +1,6 @@
1
1
  import { diskUtil } from '@sprucelabs/spruce-skill-utils'
2
2
  import { test, assert } from '@sprucelabs/test'
3
+ import LintService from '../../services/LintService'
3
4
  import AbstractSkillTest from '../../tests/AbstractSkillTest'
4
5
  import testUtil from '../../tests/utilities/test.utility'
5
6
 
@@ -32,6 +33,7 @@ export default class TestingDataStoresTest extends AbstractSkillTest {
32
33
 
33
34
  @test()
34
35
  protected static async letsYouSelectAbstractStoreTest() {
36
+ LintService.enableLinting()
35
37
  this.cli.getFeature('store').isInstalled = async () => true
36
38
 
37
39
  const promise = this.Action('test', 'create').execute({
@@ -11,6 +11,7 @@ export default class GeneratingMercuryEventContractTest extends AbstractCliTest
11
11
  protected static async beforeEach() {
12
12
  await super.beforeEach()
13
13
  this.cli = await this.Cli()
14
+ diskUtil.writeFile(this.resolvePath('package.json'), '{}')
14
15
  }
15
16
 
16
17
  @test()
@@ -52,18 +52,21 @@ export default class CreatingAListenerTest extends AbstractEventTest {
52
52
  }
53
53
 
54
54
  @test()
55
- protected static async createsValidListener() {
56
- const cli = await this.installEventFeature('events')
55
+ protected static async generatesMapFile() {
56
+ const { results } = await this.installEventsAndCreateListener()
57
57
 
58
- const version = 'v2020_01_01'
58
+ const match = testUtil.assertFileByNameInGeneratedFiles(
59
+ `listeners.ts`,
60
+ results.files
61
+ )
59
62
 
60
- const results = await this.Action('event', 'listen').execute({
61
- namespace: 'skill',
62
- eventName: 'will-boot',
63
- version,
64
- })
63
+ await this.Service('typeChecker').check(match)
64
+ }
65
65
 
66
- assert.isFalsy(results.errors)
66
+ @test()
67
+ protected static async createsValidListener() {
68
+ const { version, results, cli } =
69
+ await this.installEventsAndCreateListener()
67
70
 
68
71
  const match = testUtil.assertFileByNameInGeneratedFiles(
69
72
  `will-boot.${version}.listener.ts`,
@@ -76,6 +79,7 @@ export default class CreatingAListenerTest extends AbstractEventTest {
76
79
 
77
80
  const health = await cli.checkHealth()
78
81
 
82
+ assert.isFalsy(health?.event?.errors)
79
83
  assert.isTruthy(health.skill)
80
84
 
81
85
  assert.isUndefined(health.skill.errors)
@@ -261,6 +265,21 @@ export default class CreatingAListenerTest extends AbstractEventTest {
261
265
  )
262
266
  }
263
267
 
268
+ private static async installEventsAndCreateListener() {
269
+ const cli = await this.installEventFeature('events')
270
+
271
+ const version = 'v2020_01_01'
272
+
273
+ const results = await this.Action('event', 'listen').execute({
274
+ namespace: 'skill',
275
+ eventName: 'will-boot',
276
+ version,
277
+ })
278
+
279
+ assert.isFalsy(results.errors)
280
+ return { version, results, cli }
281
+ }
282
+
264
283
  private static async setupSkillsInstallAtOrgRegisterEventContractAndGenerateListener(
265
284
  eventSignature: EventSignature
266
285
  ) {
@@ -0,0 +1,47 @@
1
+ import { diskUtil } from '@sprucelabs/spruce-skill-utils'
2
+ import { test, assert } from '@sprucelabs/test'
3
+ import { FeatureActionResponse } from '../../../features/features.types'
4
+ import AbstractEventTest from '../../../tests/AbstractEventTest'
5
+ import testUtil from '../../../tests/utilities/test.utility'
6
+
7
+ export default class KeepingListenersInSyncTest extends AbstractEventTest {
8
+ @test()
9
+ protected static async hasSyncAction() {
10
+ assert.isFunction(this.Action('event', 'sync.listeners').execute)
11
+ }
12
+
13
+ @test()
14
+ protected static async deletingAListener() {
15
+ await this.FeatureFixture().installCachedFeatures('events')
16
+
17
+ await this.createBootListener('did-boot')
18
+ const results = await this.createBootListener('will-boot')
19
+
20
+ this.deleteLastFile(results)
21
+
22
+ const syncResults = await this.Action('event', 'sync.listeners').execute({})
23
+
24
+ const listenerMap = testUtil.assertFileByNameInGeneratedFiles(
25
+ 'listeners.ts',
26
+ syncResults.files
27
+ )
28
+
29
+ await this.Service('typeChecker').check(listenerMap)
30
+ }
31
+
32
+ private static deleteLastFile(results: FeatureActionResponse) {
33
+ const path = results.files?.pop()?.path
34
+ assert.isString(path)
35
+
36
+ diskUtil.deleteFile(path)
37
+ }
38
+
39
+ private static async createBootListener(name: 'will-boot' | 'did-boot') {
40
+ const results = await this.Action('event', 'listen').execute({
41
+ namespace: 'skill',
42
+ eventName: name,
43
+ })
44
+ assert.isFalsy(results.errors)
45
+ return results
46
+ }
47
+ }
@@ -1,8 +1,14 @@
1
1
  import { test, assert } from '@sprucelabs/test'
2
2
  import { errorAssertUtil } from '@sprucelabs/test-utils'
3
+ import LintService from '../../../services/LintService'
3
4
  import AbstractEventTest from '../../../tests/AbstractEventTest'
4
5
 
5
6
  export default class SkillEmitsBootEventsTest extends AbstractEventTest {
7
+ protected static async beforeEach() {
8
+ await super.beforeEach()
9
+ LintService.enableLinting()
10
+ }
11
+
6
12
  @test()
7
13
  protected static async skillEmitsWillBootEvents() {
8
14
  await this.installEventFeature('events')
@@ -0,0 +1,59 @@
1
+ import { eventResponseUtil } from '@sprucelabs/spruce-event-utils'
2
+ import { test, assert } from '@sprucelabs/test'
3
+ import SpruceError from '../../../errors/SpruceError'
4
+ import { FeatureCode } from '../../../features/features.types'
5
+ import CommandService from '../../../services/CommandService'
6
+ import AbstractCliTest from '../../../tests/AbstractCliTest'
7
+
8
+ export default class UpgradingWithListeners extends AbstractCliTest {
9
+ @test('should sync listeners when installed', 'events', true)
10
+ @test('should not sync listeners when not installed', 'skills', false)
11
+ @test(
12
+ 'should not sync listeners when creating node when not installed',
13
+ 'testsInNodeModule',
14
+ false,
15
+ 'create'
16
+ )
17
+ @test(
18
+ 'should not sync listeners when creating node when installed',
19
+ 'events',
20
+ false,
21
+ 'create'
22
+ )
23
+ protected static async upgradingSyncsListeners(
24
+ featureCode: FeatureCode,
25
+ shouldHit: boolean,
26
+ actionCode = 'upgrade'
27
+ ) {
28
+ await this.FeatureFixture().installCachedFeatures(featureCode)
29
+
30
+ CommandService.setMockResponse(new RegExp(/yarn/), {
31
+ code: 0,
32
+ })
33
+
34
+ let wasHit = false
35
+
36
+ await this.getEmitter().on(
37
+ 'feature.will-execute',
38
+ async ({ featureCode, actionCode }) => {
39
+ if (featureCode === 'event' && actionCode === 'sync.listeners') {
40
+ wasHit = true
41
+ }
42
+ }
43
+ )
44
+
45
+ const results = await this.getEmitter().emit('feature.will-execute', {
46
+ featureCode: 'node',
47
+ actionCode,
48
+ })
49
+
50
+ const { errors } = eventResponseUtil.getAllResponsePayloadsAndErrors(
51
+ results,
52
+ SpruceError
53
+ )
54
+
55
+ assert.isFalsy(errors)
56
+
57
+ assert.isEqual(wasHit, shouldHit)
58
+ }
59
+ }
@@ -47,6 +47,7 @@ export default class CreatingDataStoresTest extends AbstractSkillTest {
47
47
  protected static async generatesAMapFile() {
48
48
  const file = this.resolveHashSprucePath('stores', 'stores.ts')
49
49
  assert.isTrue(diskUtil.doesFileExist(file))
50
+ await this.Service('typeChecker').check(file)
50
51
  }
51
52
 
52
53
  @test()
@@ -1,6 +1,7 @@
1
1
  import { diskUtil } from '@sprucelabs/spruce-skill-utils'
2
2
  import { test, assert } from '@sprucelabs/test'
3
3
  import { errorAssertUtil } from '@sprucelabs/test-utils'
4
+ import { FeatureActionResponse } from '../../../features/features.types'
4
5
  import AbstractSkillTest from '../../../tests/AbstractSkillTest'
5
6
  import testUtil from '../../../tests/utilities/test.utility'
6
7
 
@@ -29,10 +30,6 @@ export default class KeepingDataStoresInSyncTest extends AbstractSkillTest {
29
30
  assert.isLength(results.files, 0)
30
31
  }
31
32
 
32
- private static async syncStores() {
33
- return this.Action('store', 'sync').execute({})
34
- }
35
-
36
33
  @test()
37
34
  protected static async badFileReturnsError() {
38
35
  diskUtil.writeFile(this.badStoreDest, 'throw new Error("Cheese!")')
@@ -48,19 +45,51 @@ export default class KeepingDataStoresInSyncTest extends AbstractSkillTest {
48
45
 
49
46
  @test()
50
47
  protected static async generatesValidTypeFile() {
51
- await this.Action('store', 'create').execute({
48
+ const results = await this.Action('store', 'create').execute({
52
49
  nameReadable: 'Bid',
53
50
  nameReadablePlural: 'Bids',
54
51
  namePascal: 'Bid',
55
52
  })
56
53
 
54
+ await this.validateFiles(results)
55
+ }
56
+
57
+ @test()
58
+ protected static async fileIsValidAfterSync() {
57
59
  const results = await this.syncStores()
58
60
 
61
+ await this.validateFiles(results)
62
+ }
63
+
64
+ @test()
65
+ protected static async canAddSecondStore() {
66
+ const results = await this.Action('store', 'create').execute({
67
+ nameReadable: 'Person',
68
+ nameReadablePlural: 'People',
69
+ namePascal: 'Person',
70
+ })
71
+
72
+ await this.validateFiles(results)
73
+ }
74
+
75
+ private static async validateFiles(results: FeatureActionResponse) {
59
76
  const typesFile = testUtil.assertFileByNameInGeneratedFiles(
60
77
  'stores.types.ts',
61
78
  results.files
62
79
  )
63
80
 
64
- await this.Service('typeChecker').check(typesFile)
81
+ const mapFile = testUtil.assertFileByNameInGeneratedFiles(
82
+ 'stores.ts',
83
+ results.files
84
+ )
85
+
86
+ await Promise.all([
87
+ this.Service('typeChecker').check(typesFile),
88
+ this.Service('typeChecker').check(mapFile),
89
+ ])
90
+ }
91
+
92
+ private static async syncStores() {
93
+ return this.Action('store', 'sync').execute({})
65
94
  }
66
95
  }
@@ -1,4 +1,5 @@
1
1
  import { test, assert } from '@sprucelabs/test'
2
+ import LintService from '../../../services/LintService'
2
3
  import AbstractTestTest from '../../../tests/AbstractTestTest'
3
4
  import testUtil from '../../../tests/utilities/test.utility'
4
5
 
@@ -30,6 +31,7 @@ export default class CreatingBehavioralTestsTest extends AbstractTestTest {
30
31
  'AbstractStoreTest (requires install)'
31
32
  )
32
33
  protected static async canCreateBehavioralTest(testName: string) {
34
+ LintService.enableLinting()
33
35
  await this.installTests()
34
36
  const promise = this.Action('test', 'create').execute({
35
37
  type: 'behavioral',
@@ -1,11 +1,17 @@
1
1
  import { diskUtil } from '@sprucelabs/spruce-skill-utils'
2
2
  import { test, assert } from '@sprucelabs/test'
3
+ import LintService from '../../../services/LintService'
3
4
  import AbstractSkillTest from '../../../tests/AbstractSkillTest'
4
5
  import testUtil from '../../../tests/utilities/test.utility'
5
6
 
6
7
  export default class TestingViewControllersTest extends AbstractSkillTest {
7
8
  protected static skillCacheKey = 'viewsWithTests'
8
9
 
10
+ protected static async beforeEach() {
11
+ await super.beforeEach()
12
+ LintService.enableLinting()
13
+ }
14
+
9
15
  @test()
10
16
  protected static async showsNotInstalled() {
11
17
  const viewFeature = this.cli.getFeature('view')
@@ -70,11 +70,13 @@ export default class AuthServiceTest extends AbstractCliTest {
70
70
 
71
71
  @test()
72
72
  protected static getCurrentSkillReturnsNull() {
73
+ this.writePackageJson()
73
74
  assert.isNull(this.auth.getCurrentSkill())
74
75
  }
75
76
 
76
77
  @test()
77
78
  protected static canSetCurrentSkill() {
79
+ this.writePackageJson()
78
80
  const skill = {
79
81
  id: '123467aaoeuaoeu',
80
82
  apiKey: 'taco',
@@ -86,4 +88,8 @@ export default class AuthServiceTest extends AbstractCliTest {
86
88
 
87
89
  assert.isEqualDeep(this.auth.getCurrentSkill(), skill)
88
90
  }
91
+
92
+ private static writePackageJson() {
93
+ diskUtil.writeFile(this.resolvePath('package.json'), '{}')
94
+ }
89
95
  }
@@ -29,6 +29,7 @@ export default class EventStoreTest extends AbstractEventTest {
29
29
  public static async beforeEach() {
30
30
  await super.beforeEach()
31
31
  this.createAction = this.Action<CreateAction>('event', 'create')
32
+ diskUtil.writeFile(this.resolvePath('package.json'), '{}')
32
33
  }
33
34
 
34
35
  @test()
@@ -0,0 +1,34 @@
1
+ import { test, assert } from '@sprucelabs/test'
2
+ import CommandService from '../../services/CommandService'
3
+ import LintService from '../../services/LintService'
4
+ import AbstractCliTest from '../../tests/AbstractCliTest'
5
+
6
+ export default class LintServiceTest extends AbstractCliTest {
7
+ @test()
8
+ protected static async throwsWhenLintReturnsMessage() {
9
+ LintService.enableLinting()
10
+
11
+ await this.FeatureFixture().installCachedFeatures('skills')
12
+
13
+ CommandService.setMockResponse(/node/, {
14
+ code: 0,
15
+ stdout: `[{"filePath":"/Users/taylorromero/Development/SpruceLabs/spruce-appointments-skill/src/.spruce/stores/stores.types.ts","messages":[{"fatal":false,"severity":1,"message":"File ignored by default. Use a negated ignore pattern (like \\"--ignore-pattern '!<relative/path/to/filename>'\\") to override."}],"errorCount":1,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]}]\n`,
16
+ })
17
+
18
+ await assert.doesThrowAsync(() => this.Service('lint').fix('./go'))
19
+ }
20
+
21
+ @test()
22
+ protected static async worksWhenNoMessagesReturned() {
23
+ LintService.enableLinting()
24
+
25
+ await this.FeatureFixture().installCachedFeatures('skills')
26
+
27
+ CommandService.setMockResponse(/node/, {
28
+ code: 0,
29
+ stdout: `[{"filePath":"/Users/taylorromero/Development/SpruceLabs/spruce-appointments-skill/src/.spruce/stores/stores.types.ts","messages":[{"fatal":false,"severity":1,"message":"File ignored by default. Use a negated ignore pattern (like \\"--ignore-pattern '!<relative/path/to/filename>'\\") to override."}],"errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]}]\n`,
30
+ })
31
+
32
+ await this.Service('lint').fix('./go')
33
+ }
34
+ }
@@ -0,0 +1,40 @@
1
+ import { test, assert } from '@sprucelabs/test'
2
+ import CommandService from '../../services/CommandService'
3
+ import AbstractCliTest from '../../tests/AbstractCliTest'
4
+
5
+ export default class StoreFeatureTest extends AbstractCliTest {
6
+ @test()
7
+ protected static async syncsOnWillExecute() {
8
+ await this.FeatureFixture().installCachedFeatures('stores')
9
+
10
+ CommandService.setMockResponse(/yarn/, {
11
+ code: 0,
12
+ })
13
+
14
+ let hitCount = 0
15
+ const emitter = this.getEmitter()
16
+ await emitter.on(
17
+ 'feature.will-execute',
18
+ async ({ featureCode, actionCode }) => {
19
+ if (featureCode === 'store' && actionCode === 'sync') {
20
+ hitCount++
21
+ }
22
+ }
23
+ )
24
+
25
+ await emitter.emit('feature.will-execute', {
26
+ featureCode: 'node',
27
+ actionCode: 'upgrade',
28
+ })
29
+
30
+ assert.isEqual(hitCount, 1)
31
+
32
+ await emitter.emit('feature.did-execute', {
33
+ featureCode: 'node',
34
+ actionCode: 'upgrade',
35
+ results: {},
36
+ })
37
+
38
+ assert.isEqual(hitCount, 1)
39
+ }
40
+ }
@@ -68,12 +68,16 @@ export default class SpruceError extends AbstractSpruceError<ErrorOptions> {
68
68
  break
69
69
 
70
70
  case 'LINT_FAILED':
71
- message = `Lint failed on pattern ${options.pattern}.`
71
+ message = options.friendlyMessage
72
72
 
73
- if (options.originalError) {
74
- message += `\n\nOriginal error:${
75
- options.originalError.stack ?? options.originalError.message
76
- }`
73
+ if (!message) {
74
+ message = `Lint failed on pattern ${options.pattern}.`
75
+
76
+ if (options.originalError) {
77
+ message += `\n\nOriginal error:${
78
+ options.originalError.stack ?? options.originalError.message
79
+ }`
80
+ }
77
81
  }
78
82
 
79
83
  break
@@ -3,6 +3,7 @@ import { diskUtil } from '@sprucelabs/spruce-skill-utils'
3
3
  import syncEventActionSchema from '#spruce/schemas/spruceCli/v2020_07_22/syncEventOptions.schema'
4
4
  import TerminalInterface from '../../interfaces/TerminalInterface'
5
5
  import { FileDescription } from '../../types/cli.types'
6
+ import actionUtil from '../../utilities/action.utility'
6
7
  import AbstractFeature, {
7
8
  FeatureDependency,
8
9
  FeatureOptions,
@@ -73,6 +74,11 @@ export default class EventFeature extends AbstractFeature {
73
74
 
74
75
  public async afterPackageInstall() {
75
76
  diskUtil.createDir(diskUtil.resolvePath(this.cwd, 'src', 'events'))
77
+ const isSkillInstalled = await this.featureInstaller.isInstalled('skill')
78
+ if (isSkillInstalled) {
79
+ return await this.Action('event', 'sync.listeners').execute({})
80
+ }
81
+
76
82
  return {}
77
83
  }
78
84
 
@@ -82,19 +88,21 @@ export default class EventFeature extends AbstractFeature {
82
88
  }): Promise<FeatureActionResponse> {
83
89
  const { featureCode, actionCode } = payload
84
90
 
91
+ let results: FeatureActionResponse = {}
92
+
85
93
  if (!this.initiatingAction) {
86
94
  EventStore.clearCache()
87
95
  const combined = this.combineCodes(featureCode, actionCode)
88
96
  this.initiatingAction = combined
89
97
  }
90
98
 
91
- const isInstalled = await this.featureInstaller.isInstalled('event')
92
-
93
99
  if (featureCode === 'node' || featureCode === 'upgrade') {
94
100
  const settings = this.Service('eventSettings')
95
101
  settings.clearListenerCache()
96
102
  }
97
103
 
104
+ const isInstalled = await this.featureInstaller.isInstalled('event')
105
+
98
106
  if (
99
107
  isInstalled &&
100
108
  (featureCode === 'event' ||
@@ -102,10 +110,19 @@ export default class EventFeature extends AbstractFeature {
102
110
  actionCode === 'login') &&
103
111
  actionCode !== 'setRemote'
104
112
  ) {
105
- return this.appendRemoteToResultsOrPrompt()
113
+ const remoteResults = await this.appendRemoteToResultsOrPrompt()
114
+
115
+ results = actionUtil.mergeActionResults(results, remoteResults)
106
116
  }
107
117
 
108
- return {}
118
+ if (featureCode === 'node' && actionCode === 'upgrade' && isInstalled) {
119
+ const syncResults = await this.Action('event', 'sync.listeners').execute(
120
+ {}
121
+ )
122
+ results = actionUtil.mergeActionResults(results, syncResults)
123
+ }
124
+
125
+ return results
109
126
  }
110
127
 
111
128
  private async handleDidExecute(payload: {
@@ -151,8 +151,8 @@ export default class ListenAction extends AbstractAction<OptionsSchema> {
151
151
  templateItems.responsePayloadSchemaTemplateItem
152
152
  }
153
153
 
154
- const generator = this.Writer('event')
155
- const results = await generator.writeListener(resolvedDestination, {
154
+ const writer = this.Writer('event')
155
+ response.files = await writer.writeListener(resolvedDestination, {
156
156
  ...normalizedOptions,
157
157
  version: resolvedVersion,
158
158
  eventName,
@@ -168,7 +168,9 @@ export default class ListenAction extends AbstractAction<OptionsSchema> {
168
168
  schemaTypesLookupDir: resolvedSchemaTypesLookupDir,
169
169
  })
170
170
 
171
- response.files = results
171
+ const syncResults = await this.syncListeners()
172
+
173
+ response = actionUtil.mergeActionResults(syncResults, response)
172
174
 
173
175
  if (isSkillEvent) {
174
176
  const syncOptions = normalizeSchemaValues(
@@ -191,6 +193,10 @@ export default class ListenAction extends AbstractAction<OptionsSchema> {
191
193
  }
192
194
  }
193
195
 
196
+ private async syncListeners() {
197
+ return this.Action('event', 'sync.listeners').execute({})
198
+ }
199
+
194
200
  private async collectEvent(
195
201
  contracts: EventContract[],
196
202
  namespace: string
@@ -0,0 +1,38 @@
1
+ import { buildSchema } from '@sprucelabs/schema'
2
+ import { diskUtil } from '@sprucelabs/spruce-skill-utils'
3
+ import AbstractAction from '../../AbstractAction'
4
+ import ListenerTemplateItemBuilder from '../builders/ListenerTemplateItemBuilder'
5
+
6
+ const syncListenersOptionScheam = buildSchema({
7
+ id: 'syncListenersOptions',
8
+ fields: {},
9
+ })
10
+
11
+ type OptionsSchema = typeof syncListenersOptionScheam
12
+
13
+ export default class SyncListenerAction extends AbstractAction<OptionsSchema> {
14
+ public optionsSchema: OptionsSchema = syncListenersOptionScheam
15
+ public invocationMessage = 'Syncing listeners... 🎧'
16
+ public commandAliases = ['sync.listeners']
17
+
18
+ public async execute() {
19
+ const listeners = await this.Store('listener').loadListeners()
20
+ const builder = new ListenerTemplateItemBuilder()
21
+
22
+ const templateItems = builder.buildTemplateItems({
23
+ listeners,
24
+ cwd: this.cwd,
25
+ })
26
+
27
+ const files = await this.Writer('event').writeListenerMap(
28
+ diskUtil.resolveHashSprucePath(this.cwd, 'events'),
29
+ {
30
+ listeners: templateItems,
31
+ }
32
+ )
33
+
34
+ return {
35
+ files,
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,27 @@
1
+ import pathUtil from 'path'
2
+ import { diskUtil } from '@sprucelabs/spruce-skill-utils'
3
+ import { ListenerTemplateItem } from '@sprucelabs/spruce-templates'
4
+ import { Listener } from '../stores/ListenerStore'
5
+
6
+ export default class ListenerTemplateItemBuilder {
7
+ public buildTemplateItems(options: { listeners: Listener[]; cwd: string }) {
8
+ const destination = diskUtil.resolveHashSprucePath(
9
+ options.cwd,
10
+ 'events',
11
+ 'listeners.ts'
12
+ )
13
+
14
+ const listeners: ListenerTemplateItem[] = []
15
+
16
+ for (const match of options.listeners) {
17
+ listeners.push({
18
+ ...match,
19
+ path: pathUtil
20
+ .relative(pathUtil.dirname(destination), match.path)
21
+ .replace('.ts', ''),
22
+ })
23
+ }
24
+
25
+ return listeners
26
+ }
27
+ }
@@ -76,7 +76,7 @@ export default class EventStore extends AbstractStore {
76
76
  try {
77
77
  contracts = await this.fetchRemoteContracts(namespaces)
78
78
  } catch (err: any) {
79
- const error = err.options.responseErrors[0]
79
+ const error = err.options.responseErrors?.[0] ?? err
80
80
  throw error
81
81
  }
82
82
 
@@ -0,0 +1,32 @@
1
+ import { eventDiskUtil } from '@sprucelabs/spruce-event-utils'
2
+ import { diskUtil } from '@sprucelabs/spruce-skill-utils'
3
+ import { ListenerTemplateItem } from '@sprucelabs/spruce-templates'
4
+ import globby from 'globby'
5
+ import AbstractStore from '../../../stores/AbstractStore'
6
+
7
+ export type Listener = ListenerTemplateItem & {
8
+ path: string
9
+ }
10
+
11
+ export default class ListenerStore extends AbstractStore {
12
+ public name = 'listener'
13
+
14
+ public async loadListeners() {
15
+ const matches = await globby(
16
+ diskUtil.resolvePath(this.cwd, 'src', 'listeners', '**/*.listener.ts')
17
+ )
18
+
19
+ const listeners: Listener[] = []
20
+
21
+ for (const match of matches) {
22
+ const listener = eventDiskUtil.splitPathToListener(match)
23
+
24
+ listeners.push({
25
+ ...listener,
26
+ path: match,
27
+ })
28
+ }
29
+
30
+ return listeners
31
+ }
32
+ }