@hexium-softworks/inputservice 1.0.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.
@@ -0,0 +1,168 @@
1
+ --!strict
2
+ --[=[
3
+ Validation helpers for InputService configs.
4
+
5
+ @class InputServiceConfigUtils
6
+ ]=]
7
+
8
+ local require = require(script.Parent.loader).load(script)
9
+
10
+ local Table = require("Table")
11
+
12
+ local InputServiceConfigUtils = {}
13
+
14
+ type FieldMap = { [string]: string }
15
+
16
+ local CONTEXT_FIELDS: FieldMap = Table.readonly({
17
+ Name = "string",
18
+ Enabled = "boolean",
19
+ Priority = "number",
20
+ Sink = "boolean",
21
+ Actions = "table",
22
+ })
23
+
24
+ local ACTION_FIELDS: FieldMap = Table.readonly({
25
+ Name = "string",
26
+ DisplayName = "string",
27
+ Enabled = "boolean",
28
+ Type = "InputActionType",
29
+ Bindings = "table",
30
+ })
31
+
32
+ local BINDING_FIELDS: FieldMap = Table.readonly({
33
+ Name = "string",
34
+ Type = "InputBindingType",
35
+ KeyCode = "KeyCode",
36
+ UIButton = "GuiButton",
37
+ UIModifier = "GuiButton",
38
+ PrimaryModifier = "KeyCode",
39
+ SecondaryModifier = "KeyCode",
40
+ DisplayName = "string",
41
+ DisplayImage = "Content",
42
+ PressedThreshold = "number",
43
+ ReleasedThreshold = "number",
44
+ ResponseCurve = "number",
45
+ Scale = "number",
46
+ Vector2Scale = "Vector2",
47
+ Vector3Scale = "Vector3",
48
+ PointerIndex = "number",
49
+ ClampMagnitudeToOne = "boolean",
50
+ Up = "KeyCode",
51
+ Down = "KeyCode",
52
+ Left = "KeyCode",
53
+ Right = "KeyCode",
54
+ Forward = "KeyCode",
55
+ Backward = "KeyCode",
56
+ })
57
+
58
+ local function validateName(value: any, path: string): ()
59
+ assert(typeof(value) == "string", string.format("%s.Name must be a string", path))
60
+ assert(#value > 0, string.format("%s.Name must not be empty", path))
61
+ assert(#value <= 100, string.format("%s.Name must be 100 characters or fewer", path))
62
+ assert(not string.find(value, "[%c/]"), string.format("%s.Name contains unsupported characters", path))
63
+ end
64
+
65
+ local function validateEnumItem(value: any, enumType: any, fieldName: string): ()
66
+ assert(typeof(value) == "EnumItem", string.format("%s must be an EnumItem", fieldName))
67
+ assert(value.EnumType == enumType, string.format("%s must be a member of %s", fieldName, tostring(enumType)))
68
+ end
69
+
70
+ local function validateGuiButton(value: any, fieldName: string): ()
71
+ assert(typeof(value) == "Instance", string.format("%s must be a GuiButton", fieldName))
72
+ assert(value:IsA("GuiButton"), string.format("%s must be a GuiButton", fieldName))
73
+ end
74
+
75
+ local function validateField(value: any, expectedType: string, fieldName: string): ()
76
+ if expectedType == "KeyCode" then
77
+ validateEnumItem(value, Enum.KeyCode, fieldName)
78
+ elseif expectedType == "InputActionType" then
79
+ validateEnumItem(value, Enum.InputActionType, fieldName)
80
+ elseif expectedType == "InputBindingType" then
81
+ validateEnumItem(value, Enum.InputBindingType, fieldName)
82
+ elseif expectedType == "GuiButton" then
83
+ validateGuiButton(value, fieldName)
84
+ elseif expectedType == "Content" then
85
+ assert(typeof(value) == "string" or typeof(value) == "Content", string.format("%s must be Content", fieldName))
86
+ else
87
+ assert(typeof(value) == expectedType, string.format("%s must be %s", fieldName, expectedType))
88
+ end
89
+ end
90
+
91
+ local function validateKnownFields(config: { [any]: any }, allowedFields: FieldMap, path: string): ()
92
+ for key, value in config do
93
+ assert(typeof(key) == "string", string.format("%s contains a non-string key", path))
94
+
95
+ local expectedType = allowedFields[key]
96
+ assert(expectedType ~= nil, string.format("%s.%s is not a supported config field", path, key))
97
+
98
+ if value ~= nil then
99
+ validateField(value, expectedType, string.format("%s.%s", path, key))
100
+ end
101
+ end
102
+ end
103
+
104
+ function InputServiceConfigUtils.validateBindingConfig(config: any, path: string?): ()
105
+ local bindingPath = path or "Binding"
106
+ assert(typeof(config) == "table", string.format("%s must be a table", bindingPath))
107
+ validateKnownFields(config, BINDING_FIELDS, bindingPath)
108
+ validateName(config.Name, bindingPath)
109
+
110
+ if config.PressedThreshold ~= nil and config.ReleasedThreshold ~= nil then
111
+ assert(
112
+ config.PressedThreshold >= config.ReleasedThreshold,
113
+ string.format("%s.PressedThreshold must be greater than or equal to ReleasedThreshold", bindingPath)
114
+ )
115
+ end
116
+ end
117
+
118
+ function InputServiceConfigUtils.validateActionConfig(config: any, path: string?): ()
119
+ local actionPath = path or "Action"
120
+ assert(typeof(config) == "table", string.format("%s must be a table", actionPath))
121
+ validateKnownFields(config, ACTION_FIELDS, actionPath)
122
+ validateName(config.Name, actionPath)
123
+
124
+ if config.Bindings ~= nil then
125
+ for index, bindingConfig in config.Bindings do
126
+ assert(typeof(index) == "number", string.format("%s.Bindings must be an array", actionPath))
127
+ InputServiceConfigUtils.validateBindingConfig(
128
+ bindingConfig,
129
+ string.format("%s.Bindings[%d]", actionPath, index)
130
+ )
131
+ end
132
+ end
133
+ end
134
+
135
+ function InputServiceConfigUtils.validateContextConfig(config: any, path: string?): ()
136
+ local contextPath = path or "Context"
137
+ assert(typeof(config) == "table", string.format("%s must be a table", contextPath))
138
+ validateKnownFields(config, CONTEXT_FIELDS, contextPath)
139
+ validateName(config.Name, contextPath)
140
+
141
+ if config.Actions ~= nil then
142
+ for index, actionConfig in config.Actions do
143
+ assert(typeof(index) == "number", string.format("%s.Actions must be an array", contextPath))
144
+ InputServiceConfigUtils.validateActionConfig(
145
+ actionConfig,
146
+ string.format("%s.Actions[%d]", contextPath, index)
147
+ )
148
+ end
149
+ end
150
+ end
151
+
152
+ function InputServiceConfigUtils.assertCanCreateInputClass(className: string): ()
153
+ local ok, instance = pcall(Instance.new, className, nil)
154
+ if ok and typeof(instance) == "Instance" then
155
+ instance:Destroy()
156
+ return
157
+ end
158
+
159
+ error(
160
+ string.format(
161
+ "%s is not available in this Roblox runtime. Enable the Input Action System beta or run in a newer engine.",
162
+ className
163
+ ),
164
+ 2
165
+ )
166
+ end
167
+
168
+ return InputServiceConfigUtils
@@ -0,0 +1,86 @@
1
+ --!strict
2
+ --[=[
3
+ @class InputServiceConfigUtils.spec
4
+ ]=]
5
+
6
+ local require = require(script.Parent.loader).load(script)
7
+
8
+ local Jest = require("Jest")
9
+ local InputServiceConfigUtils = require("InputServiceConfigUtils")
10
+
11
+ local describe = Jest.Globals.describe
12
+ local expect = Jest.Globals.expect
13
+ local it = Jest.Globals.it
14
+
15
+ local function getBoolInputActionType(): any
16
+ local ok, value = pcall(function()
17
+ return Enum.InputActionType.Bool
18
+ end)
19
+
20
+ if ok then
21
+ return value
22
+ end
23
+
24
+ return nil
25
+ end
26
+
27
+ describe("InputServiceConfigUtils", function()
28
+ it("accepts a valid context config", function()
29
+ expect(function()
30
+ InputServiceConfigUtils.validateContextConfig({
31
+ Name = "PlayContext",
32
+ Enabled = true,
33
+ Priority = 2000,
34
+ Sink = true,
35
+ Actions = {
36
+ {
37
+ Name = "Sprint",
38
+ Bindings = {
39
+ {
40
+ Name = "Keyboard",
41
+ KeyCode = Enum.KeyCode.LeftShift,
42
+ },
43
+ },
44
+ },
45
+ },
46
+ })
47
+ end).never.toThrow()
48
+ end)
49
+
50
+ if getBoolInputActionType() ~= nil then
51
+ it("accepts InputActionType enum values", function()
52
+ expect(function()
53
+ InputServiceConfigUtils.validateActionConfig({
54
+ Name = "Sprint",
55
+ Type = getBoolInputActionType(),
56
+ })
57
+ end).never.toThrow()
58
+ end)
59
+ end
60
+
61
+ it("rejects unknown fields", function()
62
+ expect(function()
63
+ InputServiceConfigUtils.validateContextConfig({
64
+ Name = "PlayContext",
65
+ RemoteEventName = "Nope",
66
+ })
67
+ end).toThrow()
68
+ end)
69
+
70
+ it("rejects unsafe names", function()
71
+ expect(function()
72
+ InputServiceConfigUtils.validateActionConfig({
73
+ Name = "Bad/Action",
74
+ })
75
+ end).toThrow()
76
+ end)
77
+
78
+ it("rejects mismatched enum types", function()
79
+ expect(function()
80
+ InputServiceConfigUtils.validateActionConfig({
81
+ Name = "Sprint",
82
+ Type = Enum.KeyCode.E,
83
+ })
84
+ end).toThrow()
85
+ end)
86
+ end)
@@ -0,0 +1,28 @@
1
+ --!strict
2
+ --[=[
3
+ Constants for the input action system wrapper.
4
+
5
+ @class InputServiceConstants
6
+ ]=]
7
+
8
+ local require = require(script.Parent.loader).load(script)
9
+
10
+ local Table = require("Table")
11
+
12
+ export type InputServiceConstants = {
13
+ ROOT_FOLDER_NAME: string,
14
+ ROOT_FOLDER_CLASS_NAME: string,
15
+ CONTEXT_CLASS_NAME: string,
16
+ ACTION_CLASS_NAME: string,
17
+ BINDING_CLASS_NAME: string,
18
+ DEFAULT_CONTEXT_PRIORITY: number,
19
+ }
20
+
21
+ return Table.readonly({
22
+ ROOT_FOLDER_NAME = "Inputs",
23
+ ROOT_FOLDER_CLASS_NAME = "Folder",
24
+ CONTEXT_CLASS_NAME = "InputContext",
25
+ ACTION_CLASS_NAME = "InputAction",
26
+ BINDING_CLASS_NAME = "InputBinding",
27
+ DEFAULT_CONTEXT_PRIORITY = 0,
28
+ } :: InputServiceConstants)
@@ -0,0 +1,194 @@
1
+ --!strict
2
+ --[=[
3
+ Instance creation and upsert utilities for InputService.
4
+
5
+ @class InputServiceInstanceUtils
6
+ ]=]
7
+
8
+ local require = require(script.Parent.loader).load(script)
9
+
10
+ local InputServiceConfigUtils = require("InputServiceConfigUtils")
11
+ local InputServiceConstants = require("InputServiceConstants")
12
+ local InputServiceTypes = require("InputServiceTypes")
13
+
14
+ type InputContextConfig = InputServiceTypes.InputContextConfig
15
+ type InputActionConfig = InputServiceTypes.InputActionConfig
16
+ type InputBindingConfig = InputServiceTypes.InputBindingConfig
17
+
18
+ local InputServiceInstanceUtils = {}
19
+
20
+ local CONTEXT_PROPERTIES = { "Enabled", "Priority", "Sink" }
21
+ local ACTION_PROPERTIES = { "DisplayName", "Enabled", "Type" }
22
+ local BINDING_PROPERTIES = {
23
+ "Type",
24
+ "KeyCode",
25
+ "UIButton",
26
+ "UIModifier",
27
+ "PrimaryModifier",
28
+ "SecondaryModifier",
29
+ "DisplayName",
30
+ "DisplayImage",
31
+ "PressedThreshold",
32
+ "ReleasedThreshold",
33
+ "ResponseCurve",
34
+ "Scale",
35
+ "Vector2Scale",
36
+ "Vector3Scale",
37
+ "PointerIndex",
38
+ "ClampMagnitudeToOne",
39
+ "Up",
40
+ "Down",
41
+ "Left",
42
+ "Right",
43
+ "Forward",
44
+ "Backward",
45
+ }
46
+
47
+ local function findTypedChild(parent: Instance, name: string, className: string): Instance?
48
+ local child = parent:FindFirstChild(name)
49
+ if child == nil then
50
+ return nil
51
+ end
52
+
53
+ assert(
54
+ child:IsA(className),
55
+ string.format("%s exists but is a %s, not %s", child:GetFullName(), child.ClassName, className)
56
+ )
57
+ return child
58
+ end
59
+
60
+ local function upsertChild(parent: Instance, name: string, className: string): Instance
61
+ local child = findTypedChild(parent, name, className)
62
+ if child then
63
+ return child
64
+ end
65
+
66
+ InputServiceConfigUtils.assertCanCreateInputClass(className)
67
+
68
+ local instance = Instance.new(className)
69
+ instance.Name = name
70
+ instance.Parent = parent
71
+ return instance
72
+ end
73
+
74
+ local function applyProperties(instance: Instance, config: { [string]: any }, propertyNames: { string }): ()
75
+ local writableInstance = instance :: any
76
+ for _, propertyName in propertyNames do
77
+ local value = config[propertyName]
78
+ if value ~= nil then
79
+ writableInstance[propertyName] = value
80
+ end
81
+ end
82
+ end
83
+
84
+ function InputServiceInstanceUtils.getOrCreateRootFolder(parent: Instance): Folder
85
+ local child = parent:FindFirstChild(InputServiceConstants.ROOT_FOLDER_NAME)
86
+ if child then
87
+ assert(
88
+ child:IsA(InputServiceConstants.ROOT_FOLDER_CLASS_NAME),
89
+ string.format(
90
+ "%s must be a %s",
91
+ child:GetFullName(),
92
+ InputServiceConstants.ROOT_FOLDER_CLASS_NAME
93
+ )
94
+ )
95
+ return child :: Folder
96
+ end
97
+
98
+ local folder = Instance.new(InputServiceConstants.ROOT_FOLDER_CLASS_NAME)
99
+ folder.Name = InputServiceConstants.ROOT_FOLDER_NAME
100
+ folder.Parent = parent
101
+ return folder :: Folder
102
+ end
103
+
104
+ function InputServiceInstanceUtils.getContext(rootFolder: Instance, contextName: string): InputContext?
105
+ local context = findTypedChild(rootFolder, contextName, InputServiceConstants.CONTEXT_CLASS_NAME)
106
+ return context :: InputContext?
107
+ end
108
+
109
+ function InputServiceInstanceUtils.getAction(context: Instance, actionName: string): InputAction?
110
+ assert(context:IsA(InputServiceConstants.CONTEXT_CLASS_NAME), "Context must be an InputContext")
111
+
112
+ local action = findTypedChild(context, actionName, InputServiceConstants.ACTION_CLASS_NAME)
113
+ return action :: InputAction?
114
+ end
115
+
116
+ function InputServiceInstanceUtils.getBinding(action: Instance, bindingName: string): InputBinding?
117
+ assert(action:IsA(InputServiceConstants.ACTION_CLASS_NAME), "Action must be an InputAction")
118
+
119
+ local binding = findTypedChild(action, bindingName, InputServiceConstants.BINDING_CLASS_NAME)
120
+ return binding :: InputBinding?
121
+ end
122
+
123
+ function InputServiceInstanceUtils.upsertBinding(action: Instance, config: InputBindingConfig): InputBinding
124
+ assert(action:IsA(InputServiceConstants.ACTION_CLASS_NAME), "Binding parent must be an InputAction")
125
+ InputServiceConfigUtils.validateBindingConfig(config)
126
+
127
+ local binding = upsertChild(action, config.Name, InputServiceConstants.BINDING_CLASS_NAME) :: InputBinding
128
+ applyProperties(binding, config :: any, BINDING_PROPERTIES)
129
+ return binding
130
+ end
131
+
132
+ function InputServiceInstanceUtils.upsertAction(context: Instance, config: InputActionConfig): InputAction
133
+ assert(context:IsA(InputServiceConstants.CONTEXT_CLASS_NAME), "Action parent must be an InputContext")
134
+ InputServiceConfigUtils.validateActionConfig(config)
135
+
136
+ local action = upsertChild(context, config.Name, InputServiceConstants.ACTION_CLASS_NAME) :: InputAction
137
+ applyProperties(action, config :: any, ACTION_PROPERTIES)
138
+
139
+ if config.Bindings then
140
+ for _, bindingConfig in config.Bindings do
141
+ InputServiceInstanceUtils.upsertBinding(action, bindingConfig)
142
+ end
143
+ end
144
+
145
+ return action
146
+ end
147
+
148
+ function InputServiceInstanceUtils.upsertContext(rootFolder: Instance, config: InputContextConfig): InputContext
149
+ assert(rootFolder:IsA(InputServiceConstants.ROOT_FOLDER_CLASS_NAME), "Context parent must be the input root Folder")
150
+ InputServiceConfigUtils.validateContextConfig(config)
151
+
152
+ local context = upsertChild(rootFolder, config.Name, InputServiceConstants.CONTEXT_CLASS_NAME) :: InputContext
153
+ applyProperties(context, config :: any, CONTEXT_PROPERTIES)
154
+
155
+ if config.Actions then
156
+ for _, actionConfig in config.Actions do
157
+ InputServiceInstanceUtils.upsertAction(context, actionConfig)
158
+ end
159
+ end
160
+
161
+ return context
162
+ end
163
+
164
+ function InputServiceInstanceUtils.removeContext(rootFolder: Instance, contextName: string): boolean
165
+ local context = InputServiceInstanceUtils.getContext(rootFolder, contextName)
166
+ if not context then
167
+ return false
168
+ end
169
+
170
+ context:Destroy()
171
+ return true
172
+ end
173
+
174
+ function InputServiceInstanceUtils.removeAction(context: Instance, actionName: string): boolean
175
+ local action = InputServiceInstanceUtils.getAction(context, actionName)
176
+ if not action then
177
+ return false
178
+ end
179
+
180
+ action:Destroy()
181
+ return true
182
+ end
183
+
184
+ function InputServiceInstanceUtils.removeBinding(action: Instance, bindingName: string): boolean
185
+ local binding = InputServiceInstanceUtils.getBinding(action, bindingName)
186
+ if not binding then
187
+ return false
188
+ end
189
+
190
+ binding:Destroy()
191
+ return true
192
+ end
193
+
194
+ return InputServiceInstanceUtils
@@ -0,0 +1,100 @@
1
+ --!strict
2
+ --[=[
3
+ @class InputServiceInstanceUtils.spec
4
+ ]=]
5
+
6
+ local require = require(script.Parent.loader).load(script)
7
+
8
+ local InputServiceConstants = require("InputServiceConstants")
9
+ local InputServiceInstanceUtils = require("InputServiceInstanceUtils")
10
+ local Jest = require("Jest")
11
+
12
+ local describe = Jest.Globals.describe
13
+ local expect = Jest.Globals.expect
14
+ local it = Jest.Globals.it
15
+
16
+ local function canCreateInputActionInstances(): boolean
17
+ local ok, instance = pcall(Instance.new, InputServiceConstants.CONTEXT_CLASS_NAME, nil)
18
+ if ok and typeof(instance) == "Instance" then
19
+ instance:Destroy()
20
+ return true
21
+ end
22
+
23
+ return false
24
+ end
25
+
26
+ describe("InputServiceInstanceUtils", function()
27
+ it("guards unsupported runtimes", function()
28
+ expect(typeof(canCreateInputActionInstances())).toEqual("boolean")
29
+ end)
30
+
31
+ if canCreateInputActionInstances() then
32
+ it("upserts contexts without duplicating actions or bindings", function()
33
+ local root = Instance.new("Folder")
34
+ root.Name = InputServiceConstants.ROOT_FOLDER_NAME
35
+
36
+ local context = InputServiceInstanceUtils.upsertContext(root, {
37
+ Name = "PlayContext",
38
+ Enabled = true,
39
+ Priority = 10,
40
+ Sink = false,
41
+ Actions = {
42
+ {
43
+ Name = "Sprint",
44
+ Type = Enum.InputActionType.Bool,
45
+ Bindings = {
46
+ {
47
+ Name = "Keyboard",
48
+ KeyCode = Enum.KeyCode.LeftShift,
49
+ },
50
+ },
51
+ },
52
+ },
53
+ })
54
+
55
+ InputServiceInstanceUtils.upsertContext(root, {
56
+ Name = "PlayContext",
57
+ Priority = 25,
58
+ Actions = {
59
+ {
60
+ Name = "Sprint",
61
+ DisplayName = "Run",
62
+ Bindings = {
63
+ {
64
+ Name = "Keyboard",
65
+ KeyCode = Enum.KeyCode.E,
66
+ },
67
+ },
68
+ },
69
+ },
70
+ })
71
+
72
+ local sprint = context:FindFirstChild("Sprint")
73
+ expect(context.Priority).toEqual(25)
74
+ expect(#root:GetChildren()).toEqual(1)
75
+ expect(sprint ~= nil).toEqual(true)
76
+ expect(#(sprint :: Instance):GetChildren()).toEqual(1)
77
+
78
+ root:Destroy()
79
+ end)
80
+
81
+ it("removes only targeted objects", function()
82
+ local root = Instance.new("Folder")
83
+ root.Name = InputServiceConstants.ROOT_FOLDER_NAME
84
+
85
+ local context = InputServiceInstanceUtils.upsertContext(root, {
86
+ Name = "PlayContext",
87
+ Actions = {
88
+ { Name = "Sprint" },
89
+ { Name = "Jump" },
90
+ },
91
+ })
92
+
93
+ expect(InputServiceInstanceUtils.removeAction(context, "Sprint")).toEqual(true)
94
+ expect(context:FindFirstChild("Sprint")).toEqual(nil)
95
+ expect(context:FindFirstChild("Jump") ~= nil).toEqual(true)
96
+
97
+ root:Destroy()
98
+ end)
99
+ end
100
+ end)
@@ -0,0 +1,63 @@
1
+ --!strict
2
+ --[=[
3
+ Shared public types for InputService.
4
+
5
+ @class InputServiceTypes
6
+ ]=]
7
+
8
+ local InputServiceTypes = {}
9
+
10
+ export type InputActionState = boolean | number | Vector2 | Vector3
11
+
12
+ export type InputBindingConfig = {
13
+ Name: string,
14
+ Type: Enum.InputBindingType?,
15
+ KeyCode: Enum.KeyCode?,
16
+ UIButton: GuiButton?,
17
+ UIModifier: GuiButton?,
18
+ PrimaryModifier: Enum.KeyCode?,
19
+ SecondaryModifier: Enum.KeyCode?,
20
+ DisplayName: string?,
21
+ DisplayImage: any?,
22
+ PressedThreshold: number?,
23
+ ReleasedThreshold: number?,
24
+ ResponseCurve: number?,
25
+ Scale: number?,
26
+ Vector2Scale: Vector2?,
27
+ Vector3Scale: Vector3?,
28
+ PointerIndex: number?,
29
+ ClampMagnitudeToOne: boolean?,
30
+ Up: Enum.KeyCode?,
31
+ Down: Enum.KeyCode?,
32
+ Left: Enum.KeyCode?,
33
+ Right: Enum.KeyCode?,
34
+ Forward: Enum.KeyCode?,
35
+ Backward: Enum.KeyCode?,
36
+ }
37
+
38
+ export type InputActionConfig = {
39
+ Name: string,
40
+ DisplayName: string?,
41
+ Enabled: boolean?,
42
+ Type: Enum.InputActionType?,
43
+ Bindings: { InputBindingConfig }?,
44
+ }
45
+
46
+ export type InputContextConfig = {
47
+ Name: string,
48
+ Enabled: boolean?,
49
+ Priority: number?,
50
+ Sink: boolean?,
51
+ Actions: { InputActionConfig }?,
52
+ }
53
+
54
+ export type ActionSignalBundle = {
55
+ Pressed: any,
56
+ Released: any,
57
+ StateChanged: any,
58
+ EnabledChanged: any,
59
+ PreferredBindingChanged: any,
60
+ Destroy: (ActionSignalBundle) -> (),
61
+ }
62
+
63
+ return InputServiceTypes
@@ -0,0 +1,3 @@
1
+ return {
2
+ testMatch = { "**/*.spec" },
3
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "node_modules",
3
+ "globIgnorePaths": [ "**/.package-lock.json" ],
4
+ "tree": {
5
+ "$path": { "optional": "../node_modules" }
6
+ }
7
+ }