@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.
- package/CHANGELOG.md +20 -0
- package/build/.spruce/errors/errors.types.d.ts +19 -0
- package/build/.spruce/errors/errors.types.js.map +1 -1
- package/build/.spruce/errors/options.types.d.ts +4 -1
- package/build/.spruce/errors/options.types.js.map +1 -1
- package/build/.spruce/errors/spruceCli/viewPluginAlreadyExists.schema.d.ts +3 -0
- package/build/.spruce/errors/spruceCli/viewPluginAlreadyExists.schema.js +23 -0
- package/build/.spruce/errors/spruceCli/viewPluginAlreadyExists.schema.js.map +1 -0
- package/build/__tests__/behavioral/views/CreatingASkillView.test.d.ts +1 -1
- package/build/__tests__/behavioral/views/CreatingASkillView.test.js +6 -6
- package/build/__tests__/behavioral/views/CreatingASkillView.test.js.map +1 -1
- package/build/__tests__/behavioral/views/KeepingViewsInSync.test.d.ts +1 -1
- package/build/__tests__/behavioral/views/KeepingViewsInSync.test.js +6 -6
- package/build/__tests__/behavioral/views/KeepingViewsInSync.test.js.map +1 -1
- package/build/__tests__/behavioral/views/plugins/CreatingAViewPlugin.test.d.ts +23 -0
- package/build/__tests__/behavioral/views/plugins/CreatingAViewPlugin.test.js +355 -0
- package/build/__tests__/behavioral/views/plugins/CreatingAViewPlugin.test.js.map +1 -0
- package/build/errors/SpruceError.js +3 -0
- package/build/errors/SpruceError.js.map +1 -1
- package/build/errors/viewPluginAlreadyExists.builder.d.ts +11 -0
- package/build/errors/viewPluginAlreadyExists.builder.js +18 -0
- package/build/errors/viewPluginAlreadyExists.builder.js.map +1 -0
- package/build/features/view/actions/CreateAction.d.ts +6 -6
- package/build/features/view/actions/CreateAction.js +59 -59
- package/build/features/view/actions/CreateAction.js.map +1 -1
- package/build/features/view/actions/CreatePluginAction.d.ts +56 -0
- package/build/features/view/actions/CreatePluginAction.js +95 -0
- package/build/features/view/actions/CreatePluginAction.js.map +1 -0
- package/build/features/view/actions/SyncAction.d.ts +6 -6
- package/build/features/view/actions/SyncAction.js +29 -15
- package/build/features/view/actions/SyncAction.js.map +1 -1
- package/build/features/view/writers/ViewWriter.d.ts +5 -0
- package/build/features/view/writers/ViewWriter.js +44 -7
- package/build/features/view/writers/ViewWriter.js.map +1 -1
- package/package.json +25 -25
- package/src/.spruce/errors/errors.types.ts +29 -0
- package/src/.spruce/errors/options.types.ts +4 -1
- package/src/.spruce/errors/spruceCli/viewPluginAlreadyExists.schema.ts +22 -0
- package/src/__tests__/behavioral/views/CreatingASkillView.test.ts +1 -1
- package/src/__tests__/behavioral/views/KeepingViewsInSync.test.ts +1 -1
- package/src/__tests__/behavioral/views/plugins/CreatingAViewPlugin.test.ts +161 -0
- package/src/errors/SpruceError.ts +4 -0
- package/src/errors/viewPluginAlreadyExists.builder.ts +12 -0
- package/src/features/view/actions/CreateAction.ts +72 -72
- package/src/features/view/actions/CreatePluginAction.ts +54 -0
- package/src/features/view/actions/SyncAction.ts +30 -14
- 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 {
|
|
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(
|
|
29
|
-
|
|
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 } =
|
|
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 } =
|
|
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
|
-
|
|
59
|
-
|
|
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,
|