@sprucelabs/spruce-cli 19.1.42 → 19.2.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 (47) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/build/.spruce/errors/errors.types.d.ts +19 -0
  3. package/build/.spruce/errors/errors.types.js.map +1 -1
  4. package/build/.spruce/errors/options.types.d.ts +4 -1
  5. package/build/.spruce/errors/options.types.js.map +1 -1
  6. package/build/.spruce/errors/spruceCli/viewPluginAlreadyExists.schema.d.ts +3 -0
  7. package/build/.spruce/errors/spruceCli/viewPluginAlreadyExists.schema.js +23 -0
  8. package/build/.spruce/errors/spruceCli/viewPluginAlreadyExists.schema.js.map +1 -0
  9. package/build/__tests__/behavioral/views/CreatingASkillView.test.d.ts +1 -1
  10. package/build/__tests__/behavioral/views/CreatingASkillView.test.js +6 -6
  11. package/build/__tests__/behavioral/views/CreatingASkillView.test.js.map +1 -1
  12. package/build/__tests__/behavioral/views/KeepingViewsInSync.test.d.ts +1 -1
  13. package/build/__tests__/behavioral/views/KeepingViewsInSync.test.js +6 -6
  14. package/build/__tests__/behavioral/views/KeepingViewsInSync.test.js.map +1 -1
  15. package/build/__tests__/behavioral/views/plugins/CreatingAViewPlugin.test.d.ts +23 -0
  16. package/build/__tests__/behavioral/views/plugins/CreatingAViewPlugin.test.js +355 -0
  17. package/build/__tests__/behavioral/views/plugins/CreatingAViewPlugin.test.js.map +1 -0
  18. package/build/errors/SpruceError.js +3 -0
  19. package/build/errors/SpruceError.js.map +1 -1
  20. package/build/errors/viewPluginAlreadyExists.builder.d.ts +11 -0
  21. package/build/errors/viewPluginAlreadyExists.builder.js +18 -0
  22. package/build/errors/viewPluginAlreadyExists.builder.js.map +1 -0
  23. package/build/features/view/actions/CreateAction.d.ts +6 -6
  24. package/build/features/view/actions/CreateAction.js +59 -59
  25. package/build/features/view/actions/CreateAction.js.map +1 -1
  26. package/build/features/view/actions/CreatePluginAction.d.ts +56 -0
  27. package/build/features/view/actions/CreatePluginAction.js +95 -0
  28. package/build/features/view/actions/CreatePluginAction.js.map +1 -0
  29. package/build/features/view/actions/SyncAction.d.ts +6 -6
  30. package/build/features/view/actions/SyncAction.js +29 -15
  31. package/build/features/view/actions/SyncAction.js.map +1 -1
  32. package/build/features/view/writers/ViewWriter.d.ts +5 -0
  33. package/build/features/view/writers/ViewWriter.js +44 -7
  34. package/build/features/view/writers/ViewWriter.js.map +1 -1
  35. package/package.json +25 -25
  36. package/src/.spruce/errors/errors.types.ts +29 -0
  37. package/src/.spruce/errors/options.types.ts +4 -1
  38. package/src/.spruce/errors/spruceCli/viewPluginAlreadyExists.schema.ts +22 -0
  39. package/src/__tests__/behavioral/views/CreatingASkillView.test.ts +1 -1
  40. package/src/__tests__/behavioral/views/KeepingViewsInSync.test.ts +1 -1
  41. package/src/__tests__/behavioral/views/plugins/CreatingAViewPlugin.test.ts +161 -0
  42. package/src/errors/SpruceError.ts +4 -0
  43. package/src/errors/viewPluginAlreadyExists.builder.ts +12 -0
  44. package/src/features/view/actions/CreateAction.ts +72 -72
  45. package/src/features/view/actions/CreatePluginAction.ts +54 -0
  46. package/src/features/view/actions/SyncAction.ts +30 -14
  47. package/src/features/view/writers/ViewWriter.ts +45 -5
@@ -7,6 +7,78 @@ import formUtil from '../../../utilities/form.utility'
7
7
  import AbstractAction from '../../AbstractAction'
8
8
  import { FeatureActionResponse } from '../../features.types'
9
9
 
10
+ export default class CreateAction extends AbstractAction<OptionsSchema> {
11
+ public optionsSchema: OptionsSchema = optionsSchema
12
+ public commandAliases = ['create.view']
13
+ public invocationMessage = 'Creating your new view controller... 🌲'
14
+
15
+ public async execute(
16
+ options: SchemaValues<OptionsSchema>
17
+ ): Promise<FeatureActionResponse> {
18
+ let { viewType, isRoot, nameReadable, namePascal, viewModel } =
19
+ this.validateAndNormalizeOptions(options)
20
+
21
+ const writer = this.Writer('view')
22
+
23
+ if (
24
+ viewType === 'skillView' &&
25
+ !isRoot &&
26
+ !writer.doesRootControllerExist(this.cwd)
27
+ ) {
28
+ isRoot = await this.ui.confirm(
29
+ 'Do you want to create a root view controller?'
30
+ )
31
+ }
32
+
33
+ if (!isRoot && !nameReadable) {
34
+ const form = new FormComponent({
35
+ ui: this.ui,
36
+ schema: followUpSchema,
37
+ onWillAskQuestion: formUtil.onWillAskQuestionHandler,
38
+ })
39
+ const answers = await form.present()
40
+
41
+ namePascal = answers.namePascal
42
+ nameReadable = answers.nameReadable
43
+ }
44
+
45
+ if (isRoot) {
46
+ nameReadable = 'Root'
47
+ }
48
+
49
+ if (!viewModel && viewType === 'view') {
50
+ viewModel = await this.ui.prompt({
51
+ ...optionsSchema.fields.viewModel,
52
+ isRequired: true,
53
+ })
54
+ }
55
+
56
+ namePascal = namePascal ?? namesUtil.toPascal(nameReadable as string)
57
+
58
+ const files = await writer[
59
+ viewType === 'skillView'
60
+ ? 'writeSkillViewController'
61
+ : 'writeViewController'
62
+ ](this.cwd, {
63
+ viewType,
64
+ namePascal,
65
+ viewModel: viewModel as string,
66
+ nameKebab: namesUtil.toKebab(namePascal),
67
+ })
68
+
69
+ const syncResults = await this.Action('view', 'sync').execute({})
70
+
71
+ const merged = actionUtil.mergeActionResults(
72
+ {
73
+ files,
74
+ },
75
+ syncResults
76
+ )
77
+
78
+ return merged
79
+ }
80
+ }
81
+
10
82
  const viewTypeChoices = [
11
83
  {
12
84
  value: 'skillView',
@@ -83,75 +155,3 @@ const followUpSchema = buildSchema({
83
155
  })
84
156
 
85
157
  type OptionsSchema = typeof optionsSchema
86
-
87
- export default class CreateAction extends AbstractAction<OptionsSchema> {
88
- public optionsSchema: OptionsSchema = optionsSchema
89
- public commandAliases = ['create.view']
90
- public invocationMessage = 'Creating your new view controller... 🌲'
91
-
92
- public async execute(
93
- options: SchemaValues<OptionsSchema>
94
- ): Promise<FeatureActionResponse> {
95
- let { viewType, isRoot, nameReadable, namePascal, viewModel } =
96
- this.validateAndNormalizeOptions(options)
97
-
98
- const writer = this.Writer('view')
99
-
100
- if (
101
- viewType === 'skillView' &&
102
- !isRoot &&
103
- !writer.doesRootControllerExist(this.cwd)
104
- ) {
105
- isRoot = await this.ui.confirm(
106
- 'Do you want to create a root view controller?'
107
- )
108
- }
109
-
110
- if (!isRoot && !nameReadable) {
111
- const form = new FormComponent({
112
- ui: this.ui,
113
- schema: followUpSchema,
114
- onWillAskQuestion: formUtil.onWillAskQuestionHandler,
115
- })
116
- const answers = await form.present()
117
-
118
- namePascal = answers.namePascal
119
- nameReadable = answers.nameReadable
120
- }
121
-
122
- if (isRoot) {
123
- nameReadable = 'Root'
124
- }
125
-
126
- if (!viewModel && viewType === 'view') {
127
- viewModel = await this.ui.prompt({
128
- ...optionsSchema.fields.viewModel,
129
- isRequired: true,
130
- })
131
- }
132
-
133
- namePascal = namePascal ?? namesUtil.toPascal(nameReadable as string)
134
-
135
- const files = await writer[
136
- viewType === 'skillView'
137
- ? 'writeSkillViewController'
138
- : 'writeViewController'
139
- ](this.cwd, {
140
- viewType,
141
- namePascal,
142
- viewModel: viewModel as string,
143
- nameKebab: namesUtil.toKebab(namePascal),
144
- })
145
-
146
- const syncResults = await this.Action('view', 'sync').execute({})
147
-
148
- const merged = actionUtil.mergeActionResults(
149
- {
150
- files,
151
- },
152
- syncResults
153
- )
154
-
155
- return merged
156
- }
157
- }
@@ -0,0 +1,54 @@
1
+ import { SchemaValues, buildSchema } from '@sprucelabs/schema'
2
+ import { namesUtil } from '@sprucelabs/spruce-skill-utils'
3
+ import namedTemplateItemBuilder from '../../../schemas/v2020_07_22/namedTemplateItem.builder'
4
+ import actionUtil from '../../../utilities/action.utility'
5
+ import AbstractAction from '../../AbstractAction'
6
+ import { FeatureActionResponse } from '../../features.types'
7
+
8
+ export default class CreatePluginAction extends AbstractAction<OptionsSchema> {
9
+ public optionsSchema = optionsSchema
10
+ public invocationMessage = 'Creating a new view plugin... 🖼️'
11
+ public commandAliases: string[] = ['create.view.plugin']
12
+
13
+ public async execute(
14
+ options: SchemaValues<OptionsSchema>
15
+ ): Promise<FeatureActionResponse> {
16
+ const { nameReadable, nameCamel, namePascal } =
17
+ this.validateAndNormalizeOptions(options)
18
+
19
+ const normalizedNameCamel = nameCamel ?? namesUtil.toCamel(nameReadable)
20
+ const normalizedNamePascal =
21
+ namePascal ?? namesUtil.toPascal(normalizedNameCamel)
22
+
23
+ const writer = this.Writer('view')
24
+ const files = await writer.writeViewControllerPlugin({
25
+ cwd: this.cwd,
26
+ nameCamel: normalizedNameCamel,
27
+ namePascal: normalizedNamePascal,
28
+ })
29
+
30
+ const sync = await this.Action('view', 'sync').execute({})
31
+
32
+ return actionUtil.mergeActionResults({ files }, sync)
33
+ }
34
+ }
35
+
36
+ const optionsSchema = buildSchema({
37
+ id: 'createViewPluginOptions',
38
+ fields: {
39
+ nameReadable: {
40
+ ...namedTemplateItemBuilder.fields.nameReadable,
41
+ label: 'Plugin name',
42
+ },
43
+ nameCamel: {
44
+ ...namedTemplateItemBuilder.fields.nameCamel,
45
+ isRequired: false,
46
+ },
47
+ namePascal: {
48
+ ...namedTemplateItemBuilder.fields.namePascal,
49
+ isRequired: false,
50
+ },
51
+ },
52
+ })
53
+
54
+ type OptionsSchema = typeof optionsSchema
@@ -1,21 +1,16 @@
1
1
  import globby from '@sprucelabs/globby'
2
2
  import { buildSchema, SchemaValues } from '@sprucelabs/schema'
3
3
  import { diskUtil, namesUtil } from '@sprucelabs/spruce-skill-utils'
4
- import { VcTemplateItem } from '../../../../../spruce-templates/build'
4
+ import {
5
+ VcTemplateItem,
6
+ ViewControllerPluginItem,
7
+ } from '../../../../../spruce-templates/build'
5
8
  import introspectionUtil, {
6
9
  IntrospectionClass,
7
10
  } from '../../../utilities/introspection.utility'
8
11
  import AbstractAction from '../../AbstractAction'
9
12
  import { FeatureActionResponse } from '../../features.types'
10
13
 
11
- const optionsSchema = buildSchema({
12
- id: 'syncViewsOptions',
13
- description: 'Keep types and generated files based on views in sync.',
14
- fields: {},
15
- })
16
-
17
- type OptionsSchema = typeof optionsSchema
18
-
19
14
  export default class SyncAction extends AbstractAction<OptionsSchema> {
20
15
  public optionsSchema: OptionsSchema = optionsSchema
21
16
  public commandAliases = ['sync.views']
@@ -25,9 +20,12 @@ export default class SyncAction extends AbstractAction<OptionsSchema> {
25
20
  _options: SchemaValues<OptionsSchema>
26
21
  ): Promise<FeatureActionResponse> {
27
22
  const targetDir = diskUtil.resolvePath(this.cwd, 'src')
28
- const matches = await globby(['**/*.svc.ts', '**/*.vc.ts'], {
29
- cwd: targetDir,
30
- })
23
+ const matches = await globby(
24
+ ['**/*.svc.ts', '**/*.vc.ts', '**/*.view.plugin.ts'],
25
+ {
26
+ cwd: targetDir,
27
+ }
28
+ )
31
29
 
32
30
  if (matches.length === 0) {
33
31
  return {}
@@ -38,13 +36,17 @@ export default class SyncAction extends AbstractAction<OptionsSchema> {
38
36
 
39
37
  const vcTemplateItems: VcTemplateItem[] = []
40
38
  const svcTemplateItems: VcTemplateItem[] = []
39
+ const viewPluginItems: ViewControllerPluginItem[] = []
41
40
 
42
41
  introspect.forEach(({ classes }) => {
43
42
  for (const thisClass of classes) {
44
- const { vc, svc } = this.mapIntrospectedClassToTemplateItem(thisClass)
43
+ const { vc, svc, plugin } =
44
+ this.mapIntrospectedClassToTemplateItem(thisClass)
45
45
 
46
46
  if (vc) {
47
47
  vcTemplateItems.push(vc)
48
+ } else if (plugin) {
49
+ viewPluginItems.push(plugin)
48
50
  } else if (svc) {
49
51
  svcTemplateItems.push(svc)
50
52
  }
@@ -56,6 +58,7 @@ export default class SyncAction extends AbstractAction<OptionsSchema> {
56
58
  namespaceKebab: namesUtil.toKebab(namespace),
57
59
  vcTemplateItems,
58
60
  svcTemplateItems,
61
+ viewPluginItems,
59
62
  })
60
63
 
61
64
  return {
@@ -66,6 +69,7 @@ export default class SyncAction extends AbstractAction<OptionsSchema> {
66
69
  private mapIntrospectedClassToTemplateItem(c: IntrospectionClass): {
67
70
  vc?: VcTemplateItem
68
71
  svc?: VcTemplateItem
72
+ plugin?: ViewControllerPluginItem
69
73
  } {
70
74
  const item = {
71
75
  id: c.staticProperties.id,
@@ -75,13 +79,25 @@ export default class SyncAction extends AbstractAction<OptionsSchema> {
75
79
 
76
80
  let vc: VcTemplateItem | undefined
77
81
  let svc: VcTemplateItem | undefined
82
+ let plugin: ViewControllerPluginItem | undefined
78
83
 
79
84
  if (c.classPath.endsWith('.svc.ts')) {
80
85
  svc = item
86
+ } else if (c.classPath.endsWith('view.plugin.ts')) {
87
+ const nameCamel = c.classPath.match(/([^/]+).view.plugin.ts$/)![1]
88
+ plugin = { ...item, nameCamel }
81
89
  } else {
82
90
  vc = item
83
91
  }
84
92
 
85
- return { svc, vc }
93
+ return { svc, vc, plugin }
86
94
  }
87
95
  }
96
+
97
+ const optionsSchema = buildSchema({
98
+ id: 'syncViewsOptions',
99
+ description: 'Keep types and generated files based on views in sync.',
100
+ fields: {},
101
+ })
102
+
103
+ type OptionsSchema = typeof optionsSchema
@@ -1,6 +1,7 @@
1
1
  import { diskUtil } from '@sprucelabs/spruce-skill-utils'
2
2
  import {
3
3
  VcTemplateItem,
4
+ ViewControllerPluginItem,
4
5
  ViewsOptions,
5
6
  } from '../../../../../spruce-templates/build'
6
7
  import SpruceError from '../../../errors/SpruceError'
@@ -24,7 +25,8 @@ export default class ViewWriter extends AbstractWriter {
24
25
  }
25
26
 
26
27
  public async writeCombinedViewsFile(cwd: string, options: ViewsOptions) {
27
- let { vcTemplateItems, svcTemplateItems, ...rest } = options
28
+ let { vcTemplateItems, svcTemplateItems, viewPluginItems, ...rest } =
29
+ options
28
30
 
29
31
  const destinationDir = diskUtil.resolveHashSprucePath(cwd, 'views')
30
32
  const destination = diskUtil.resolvePath(destinationDir, 'views.ts')
@@ -39,9 +41,15 @@ export default class ViewWriter extends AbstractWriter {
39
41
  destinationDir
40
42
  )
41
43
 
44
+ viewPluginItems = this.removeFileExtensionsFromTemplateItems(
45
+ viewPluginItems,
46
+ destinationDir
47
+ )
48
+
42
49
  const contents = this.templates.views({
43
50
  vcTemplateItems,
44
51
  svcTemplateItems,
52
+ viewPluginItems,
45
53
  ...rest,
46
54
  })
47
55
 
@@ -54,10 +62,9 @@ export default class ViewWriter extends AbstractWriter {
54
62
  return results
55
63
  }
56
64
 
57
- private removeFileExtensionsFromTemplateItems(
58
- vcTemplateItems: VcTemplateItem[],
59
- destinationDir: string
60
- ): VcTemplateItem[] {
65
+ private removeFileExtensionsFromTemplateItems<
66
+ T extends VcTemplateItem | ViewControllerPluginItem,
67
+ >(vcTemplateItems: T[], destinationDir: string): T[] {
61
68
  return vcTemplateItems.map((i) => ({
62
69
  ...i,
63
70
  path: diskUtil
@@ -111,6 +118,39 @@ export default class ViewWriter extends AbstractWriter {
111
118
  return diskUtil.doesFileExist(path)
112
119
  }
113
120
 
121
+ public async writeViewControllerPlugin(options: {
122
+ cwd: string
123
+ nameCamel: string
124
+ namePascal: string
125
+ }) {
126
+ const { nameCamel, namePascal, cwd } = options
127
+
128
+ const destination = diskUtil.resolvePath(
129
+ cwd,
130
+ 'src',
131
+ 'viewPlugins',
132
+ `${nameCamel}.view.plugin.ts`
133
+ )
134
+
135
+ const contents = this.templates.viewControllerPlugin({
136
+ nameCamel,
137
+ namePascal,
138
+ })
139
+
140
+ if (diskUtil.doesFileExist(destination)) {
141
+ throw new SpruceError({
142
+ code: 'VIEW_PLUGIN_ALREADY_EXISTS',
143
+ name: nameCamel,
144
+ })
145
+ }
146
+
147
+ return this.writeFileIfChangedMixinResults(
148
+ destination,
149
+ contents,
150
+ `Your new view plugin!`
151
+ )
152
+ }
153
+
114
154
  public writePlugin(cwd: string) {
115
155
  const destination = diskUtil.resolveHashSprucePath(
116
156
  cwd,