azul-sync 1.3.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/.gitattributes +1 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +31 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/README.md +142 -0
- package/dist/build.d.ts +19 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +92 -0
- package/dist/build.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +397 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +26 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +105 -0
- package/dist/config.js.map +1 -0
- package/dist/fs/fileWriter.d.ts +100 -0
- package/dist/fs/fileWriter.d.ts.map +1 -0
- package/dist/fs/fileWriter.js +342 -0
- package/dist/fs/fileWriter.js.map +1 -0
- package/dist/fs/treeManager.d.ts +84 -0
- package/dist/fs/treeManager.d.ts.map +1 -0
- package/dist/fs/treeManager.js +365 -0
- package/dist/fs/treeManager.js.map +1 -0
- package/dist/fs/watcher.d.ts +39 -0
- package/dist/fs/watcher.d.ts.map +1 -0
- package/dist/fs/watcher.js +120 -0
- package/dist/fs/watcher.js.map +1 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +349 -0
- package/dist/index.js.map +1 -0
- package/dist/ipc/httpPolling.d.ts +56 -0
- package/dist/ipc/httpPolling.d.ts.map +1 -0
- package/dist/ipc/httpPolling.js +171 -0
- package/dist/ipc/httpPolling.js.map +1 -0
- package/dist/ipc/messages.d.ts +112 -0
- package/dist/ipc/messages.d.ts.map +1 -0
- package/dist/ipc/messages.js +5 -0
- package/dist/ipc/messages.js.map +1 -0
- package/dist/ipc/server.d.ts +50 -0
- package/dist/ipc/server.d.ts.map +1 -0
- package/dist/ipc/server.js +168 -0
- package/dist/ipc/server.js.map +1 -0
- package/dist/pack.d.ts +19 -0
- package/dist/pack.d.ts.map +1 -0
- package/dist/pack.js +225 -0
- package/dist/pack.js.map +1 -0
- package/dist/push.d.ts +43 -0
- package/dist/push.d.ts.map +1 -0
- package/dist/push.js +532 -0
- package/dist/push.js.map +1 -0
- package/dist/rojo.d.ts +9 -0
- package/dist/rojo.d.ts.map +1 -0
- package/dist/rojo.js +114 -0
- package/dist/rojo.js.map +1 -0
- package/dist/snapshot/rojo.d.ts +39 -0
- package/dist/snapshot/rojo.d.ts.map +1 -0
- package/dist/snapshot/rojo.js +364 -0
- package/dist/snapshot/rojo.js.map +1 -0
- package/dist/snapshot.d.ts +23 -0
- package/dist/snapshot.d.ts.map +1 -0
- package/dist/snapshot.js +132 -0
- package/dist/snapshot.js.map +1 -0
- package/dist/sourcemap/generator.d.ts +78 -0
- package/dist/sourcemap/generator.d.ts.map +1 -0
- package/dist/sourcemap/generator.js +351 -0
- package/dist/sourcemap/generator.js.map +1 -0
- package/dist/sourcemap/propertyLoader.d.ts +19 -0
- package/dist/sourcemap/propertyLoader.d.ts.map +1 -0
- package/dist/sourcemap/propertyLoader.js +131 -0
- package/dist/sourcemap/propertyLoader.js.map +1 -0
- package/dist/util/id.d.ts +9 -0
- package/dist/util/id.d.ts.map +1 -0
- package/dist/util/id.js +14 -0
- package/dist/util/id.js.map +1 -0
- package/dist/util/log.d.ts +13 -0
- package/dist/util/log.d.ts.map +1 -0
- package/dist/util/log.js +51 -0
- package/dist/util/log.js.map +1 -0
- package/docs/assets/azul-logo.pdn +0 -0
- package/docs/assets/logo-200px.png +0 -0
- package/docs/assets/logo.png +0 -0
- package/docs/assets/plugin/toolbox.png +0 -0
- package/docs/assets/synced.png +0 -0
- package/package.json +41 -0
- package/plugin/README.md +54 -0
- package/plugin/sourcemap.json +264 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/Actor/AzulSync.server.luau +905 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/AzulService.luau +1010 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/Config.luau +29 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/Enums.luau +11 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/CollapsibleTitledSection.luau +214 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/ColorPicker.luau +360 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/CustomTextButton.luau +170 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/DropdownMenu.luau +363 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/HorizontalLine.luau +43 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/ImageButtonWithText.luau +181 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledCheckbox.luau +295 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledColorInputPicker.luau +294 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledMultiChoice.luau +163 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledNumberInput.luau +312 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledRadioButton.luau +55 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledSlider.luau +151 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledTextInput.luau +222 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledToggleButton.luau +73 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/StatefulImageButton.luau +125 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/VerticalScrollingFrame.luau +100 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/VerticalSpacer.luau +35 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/VerticallyScalingListFrame.luau +107 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/GuiUtilities.luau +429 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/RbxGui.luau +4363 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/UI.luau +425 -0
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/WebSocketClient.luau +161 -0
- package/src/build.ts +120 -0
- package/src/cli.ts +496 -0
- package/src/config.ts +170 -0
- package/src/fs/fileWriter.ts +414 -0
- package/src/fs/treeManager.ts +458 -0
- package/src/fs/watcher.ts +142 -0
- package/src/index.ts +450 -0
- package/src/ipc/httpPolling.ts +214 -0
- package/src/ipc/messages.ts +159 -0
- package/src/ipc/server.ts +196 -0
- package/src/pack.ts +309 -0
- package/src/push.ts +726 -0
- package/src/snapshot/rojo.ts +467 -0
- package/src/snapshot.ts +161 -0
- package/src/sourcemap/generator.ts +504 -0
- package/src/sourcemap/propertyLoader.ts +195 -0
- package/src/util/id.ts +15 -0
- package/src/util/log.ts +94 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
Azul UI Module
|
|
4
|
+
Manages the companion plugin's dock widget and settings interface
|
|
5
|
+
]]
|
|
6
|
+
|
|
7
|
+
local UI = {}
|
|
8
|
+
|
|
9
|
+
local Config = require("./Config")
|
|
10
|
+
local Enums = require("./Enums")
|
|
11
|
+
|
|
12
|
+
UI.__index = UI
|
|
13
|
+
|
|
14
|
+
export type CallbackFunctions = {
|
|
15
|
+
onStartSync: () -> (),
|
|
16
|
+
onStopSync: () -> (),
|
|
17
|
+
onDebugModeChanged: (boolean) -> ()?,
|
|
18
|
+
onSilentModeChanged: (boolean) -> ()?,
|
|
19
|
+
onConfigChanged: (string, any) -> (),
|
|
20
|
+
onSettingsScopeChanged: (number) -> (),
|
|
21
|
+
onClearGuids: () -> (),
|
|
22
|
+
onSourcemapReload: () -> (),
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type SelfType = {
|
|
26
|
+
plugin: Plugin,
|
|
27
|
+
config: { any },
|
|
28
|
+
callbacks: CallbackFunctions,
|
|
29
|
+
LOGO: string,
|
|
30
|
+
LOGO_SYNCED: string,
|
|
31
|
+
settingsScope: string,
|
|
32
|
+
debugPrint: (...string) -> (),
|
|
33
|
+
infoPrint: (...string) -> (),
|
|
34
|
+
saveSettings: () -> (),
|
|
35
|
+
loadSettings: () -> (),
|
|
36
|
+
rebuildServiceSet: () -> (),
|
|
37
|
+
|
|
38
|
+
isSyncEnabled: boolean,
|
|
39
|
+
|
|
40
|
+
azulWidget: DockWidgetPluginGui,
|
|
41
|
+
connectButton: PluginToolbarButton,
|
|
42
|
+
titleImageLabel: ImageLabel,
|
|
43
|
+
syncButton: any,
|
|
44
|
+
debugModeCheckbox: any,
|
|
45
|
+
silentModeCheckbox: any,
|
|
46
|
+
settingsScopeDropdown: any,
|
|
47
|
+
websocketUrlLabel: any,
|
|
48
|
+
heartbeatIntervalLabel: any,
|
|
49
|
+
listTypeLabel: any,
|
|
50
|
+
serviceListLabel: any,
|
|
51
|
+
excludedParentsLabel: any,
|
|
52
|
+
clearGuidsButton: any,
|
|
53
|
+
reloadSourcemapButton: any,
|
|
54
|
+
|
|
55
|
+
GetSyncState: (SelfType) -> boolean,
|
|
56
|
+
UpdateSyncState: (SelfType, boolean) -> (),
|
|
57
|
+
UpdateConfig: (SelfType) -> (),
|
|
58
|
+
SetSettingsScope: (SelfType, any) -> (),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
UI.logo = "rbxassetid://134336592598474"
|
|
62
|
+
UI.syncedLogo = "rbxassetid://103599828888609"
|
|
63
|
+
|
|
64
|
+
local function createInfoLabel(text: string)
|
|
65
|
+
local label = Instance.new("TextLabel")
|
|
66
|
+
label.Name = "InfoLabel"
|
|
67
|
+
label.RichText = true
|
|
68
|
+
label.AutomaticSize = Enum.AutomaticSize.Y
|
|
69
|
+
label.TextWrapped = true
|
|
70
|
+
label.Size = UDim2.new(1, 0, 0, 15)
|
|
71
|
+
label.BackgroundTransparency = 1
|
|
72
|
+
label.TextColor3 = Color3.new(0.5, 0.5, 0.5)
|
|
73
|
+
label.Font = Enum.Font.SourceSans
|
|
74
|
+
label.TextSize = 15
|
|
75
|
+
label.Text = `{text}<br/>`
|
|
76
|
+
label.TextXAlignment = Enum.TextXAlignment.Left
|
|
77
|
+
|
|
78
|
+
local padding = Instance.new("UIPadding")
|
|
79
|
+
padding.PaddingLeft = UDim.new(0, 30)
|
|
80
|
+
padding.PaddingRight = UDim.new(0, 30)
|
|
81
|
+
padding.Parent = label
|
|
82
|
+
|
|
83
|
+
return label
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
--[=[
|
|
87
|
+
Initialize the UI widget
|
|
88
|
+
|
|
89
|
+
Parameters:
|
|
90
|
+
- plugin: The plugin object from the Roblox plugin API
|
|
91
|
+
- config: Reference to the CONFIG table
|
|
92
|
+
- settingsScope: Reference to the settingsScope variable`
|
|
93
|
+
- callbacks: Table with callbacks:
|
|
94
|
+
- onStartSync: Called when user clicks Connect
|
|
95
|
+
- onStopSync: Called when user clicks Disconnect
|
|
96
|
+
- onDebugModeChanged: Called when debug mode checkbox changes
|
|
97
|
+
- onSilentModeChanged: Called when silent mode checkbox changes
|
|
98
|
+
- onConfigChanged: Called when any config value changes (receives {key, value})
|
|
99
|
+
- onSettingsScopeChanged: Called when scope changes
|
|
100
|
+
- onClearGuids: Called when clear GUIDs button is clicked
|
|
101
|
+
- helpers: Table with helper functions:
|
|
102
|
+
- debugPrint: Debug print function
|
|
103
|
+
- infoPrint: Info print function
|
|
104
|
+
- saveSettings: Save settings function
|
|
105
|
+
- loadSettings: Load settings function
|
|
106
|
+
- rebuildServiceSet: Rebuild service set function
|
|
107
|
+
|
|
108
|
+
Returns: Table with methods:
|
|
109
|
+
- updateSyncState(enabled: boolean): Update UI to reflect sync state
|
|
110
|
+
- updateConfig(): Refresh UI with current CONFIG values
|
|
111
|
+
]=]
|
|
112
|
+
function UI.new(plugin, callbacks, settingsScope, helpers): SelfType
|
|
113
|
+
local self = {} :: SelfType
|
|
114
|
+
setmetatable(self, UI)
|
|
115
|
+
|
|
116
|
+
-- Store references
|
|
117
|
+
self.plugin = plugin
|
|
118
|
+
self.callbacks = callbacks
|
|
119
|
+
self.LOGO = UI.logo
|
|
120
|
+
self.LOGO_SYNCED = UI.syncedLogo
|
|
121
|
+
self.settingsScope = settingsScope
|
|
122
|
+
self.debugPrint = helpers.debugPrint
|
|
123
|
+
self.infoPrint = helpers.infoPrint
|
|
124
|
+
self.saveSettings = helpers.saveSettings
|
|
125
|
+
self.loadSettings = helpers.loadSettings
|
|
126
|
+
self.rebuildServiceSet = helpers.rebuildServiceSet
|
|
127
|
+
|
|
128
|
+
-- Track sync enabled state locally (PluginToolbarButton has SetActive but no GetActive)
|
|
129
|
+
self.isSyncEnabled = false
|
|
130
|
+
|
|
131
|
+
-- Import UI components
|
|
132
|
+
local VerticalScrollingFrame = require("./StudioWidgets/Components/VerticalScrollingFrame")
|
|
133
|
+
local CollapsibleTitledSection = require("./StudioWidgets/Components/CollapsibleTitledSection")
|
|
134
|
+
local CustomTextButton = require("./StudioWidgets/Components/CustomTextButton")
|
|
135
|
+
local LabeledCheckbox = require("./StudioWidgets/Components/LabeledCheckbox")
|
|
136
|
+
local LabeledMultiChoice = require("./StudioWidgets/Components/LabeledMultiChoice")
|
|
137
|
+
local LabeledTextInput = require("./StudioWidgets/Components/LabeledTextInput")
|
|
138
|
+
local DropdownMenu = require("./StudioWidgets/Components/DropdownMenu")
|
|
139
|
+
|
|
140
|
+
-- Create toolbar button
|
|
141
|
+
local toolbar = plugin:CreateToolbar("Azul")
|
|
142
|
+
self.connectButton = toolbar:CreateButton("Azul", "Connect/disconnect from sync daemon", self.LOGO)
|
|
143
|
+
|
|
144
|
+
-- Create widget
|
|
145
|
+
local widgetInfo = DockWidgetPluginGuiInfo.new(Enum.InitialDockState.Float, true, true, 345, 640, 300, 300)
|
|
146
|
+
|
|
147
|
+
local azulWidget = plugin:CreateDockWidgetPluginGuiAsync("azulWidget", widgetInfo)
|
|
148
|
+
azulWidget.Name = "AzulCompanionPlugin"
|
|
149
|
+
azulWidget.Title = "Azul"
|
|
150
|
+
self.azulWidget = azulWidget
|
|
151
|
+
|
|
152
|
+
-- Create main scroll frame
|
|
153
|
+
local mainScrollFrame = VerticalScrollingFrame.new("main")
|
|
154
|
+
mainScrollFrame:GetContentsFrame().Parent = azulWidget
|
|
155
|
+
|
|
156
|
+
local mainSectionListLayout = Instance.new("UIListLayout")
|
|
157
|
+
mainSectionListLayout.Parent = mainScrollFrame:GetSectionFrame()
|
|
158
|
+
mainSectionListLayout.Padding = UDim.new(0, 0)
|
|
159
|
+
mainSectionListLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
|
160
|
+
mainSectionListLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
|
|
161
|
+
|
|
162
|
+
-- Main section
|
|
163
|
+
local mainSection = CollapsibleTitledSection.new("mainSection", "Azul Companion Plugin", true, false, false, false)
|
|
164
|
+
mainSection:GetSectionFrame().LayoutOrder = 0
|
|
165
|
+
mainSection:GetSectionFrame().Parent = mainScrollFrame:GetContentsFrame()
|
|
166
|
+
|
|
167
|
+
-- Title image
|
|
168
|
+
local imageContainer = Instance.new("Frame")
|
|
169
|
+
imageContainer.Size = UDim2.new(1, 0, 0, 70)
|
|
170
|
+
imageContainer.BackgroundTransparency = 1
|
|
171
|
+
|
|
172
|
+
local imageContainerListLayout = Instance.new("UIListLayout")
|
|
173
|
+
imageContainerListLayout.FillDirection = Enum.FillDirection.Horizontal
|
|
174
|
+
imageContainerListLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
|
|
175
|
+
imageContainerListLayout.VerticalAlignment = Enum.VerticalAlignment.Center
|
|
176
|
+
imageContainerListLayout.Parent = imageContainer
|
|
177
|
+
|
|
178
|
+
local titleImageLabel = Instance.new("ImageLabel")
|
|
179
|
+
titleImageLabel.Size = UDim2.fromScale(0.2, 1)
|
|
180
|
+
titleImageLabel.AutomaticSize = Enum.AutomaticSize.X
|
|
181
|
+
titleImageLabel.BackgroundTransparency = 1
|
|
182
|
+
titleImageLabel.Image = self.LOGO
|
|
183
|
+
titleImageLabel.ScaleType = Enum.ScaleType.Fit
|
|
184
|
+
titleImageLabel.LayoutOrder = 0
|
|
185
|
+
titleImageLabel.Parent = imageContainer
|
|
186
|
+
|
|
187
|
+
local azulText = Instance.new("TextLabel")
|
|
188
|
+
azulText.Text = "<b>Azul</b>"
|
|
189
|
+
azulText.RichText = true
|
|
190
|
+
azulText.Font = Enum.Font.RobotoMono
|
|
191
|
+
azulText.TextSize = 36
|
|
192
|
+
azulText.TextScaled = true
|
|
193
|
+
azulText.Size = UDim2.fromScale(0.1, 1)
|
|
194
|
+
azulText.AutomaticSize = Enum.AutomaticSize.X
|
|
195
|
+
azulText.TextColor3 = Color3.fromRGB(255, 255, 255)
|
|
196
|
+
azulText.BackgroundTransparency = 1
|
|
197
|
+
azulText.LayoutOrder = 1
|
|
198
|
+
azulText.Parent = imageContainer
|
|
199
|
+
|
|
200
|
+
local padding = Instance.new("UIPadding")
|
|
201
|
+
padding.PaddingTop = UDim.new(0, 10)
|
|
202
|
+
padding.PaddingBottom = UDim.new(0, 10)
|
|
203
|
+
padding.Parent = imageContainer
|
|
204
|
+
|
|
205
|
+
mainSection:AddChild(imageContainer, 0)
|
|
206
|
+
self.titleImageLabel = titleImageLabel
|
|
207
|
+
|
|
208
|
+
-- Sync button
|
|
209
|
+
local syncButton = CustomTextButton.new("toggleSync", "Connect", false)
|
|
210
|
+
syncButton.Parent = mainScrollFrame:GetContentsFrame()
|
|
211
|
+
syncButton:SetSize(UDim2.fromScale(1, 0.2))
|
|
212
|
+
|
|
213
|
+
local syncButtonPadding = Instance.new("UIPadding")
|
|
214
|
+
syncButtonPadding.PaddingLeft = UDim.new(0.1, 0)
|
|
215
|
+
syncButtonPadding.PaddingRight = UDim.new(0.1, 0)
|
|
216
|
+
syncButtonPadding.Parent = syncButton:GetFrame()
|
|
217
|
+
|
|
218
|
+
syncButton:SetClickedFunction(function()
|
|
219
|
+
if self.isSyncEnabled then
|
|
220
|
+
syncButton:GetButton().Text = "Stopping..."
|
|
221
|
+
self.callbacks.onStopSync()
|
|
222
|
+
else
|
|
223
|
+
syncButton:GetButton().Text = "Connecting..."
|
|
224
|
+
self.callbacks.onStartSync()
|
|
225
|
+
end
|
|
226
|
+
end)
|
|
227
|
+
mainSection:AddChild(syncButton:GetFrame())
|
|
228
|
+
self.syncButton = syncButton
|
|
229
|
+
|
|
230
|
+
-- Silent Mode checkbox
|
|
231
|
+
local silentModeCheckbox = LabeledCheckbox.new("silentMode", "Silent Mode", Config.SILENT_MODE, false)
|
|
232
|
+
silentModeCheckbox:SetValue(Config.SILENT_MODE)
|
|
233
|
+
silentModeCheckbox:SetValueChangedFunction(function(newValue)
|
|
234
|
+
Config.SILENT_MODE = newValue
|
|
235
|
+
helpers.infoPrint(`[🐛 AzulUI]: Silent mode set to: {newValue}`)
|
|
236
|
+
end)
|
|
237
|
+
mainSection:AddChild(silentModeCheckbox:GetFrame())
|
|
238
|
+
mainSection:AddChild(createInfoLabel("Silent mode suppresses all console output except errors."))
|
|
239
|
+
self.silentModeCheckbox = silentModeCheckbox
|
|
240
|
+
|
|
241
|
+
-- Debug Mode checkbox
|
|
242
|
+
local debugModeCheckbox = LabeledCheckbox.new("debugMode", "Debug Mode", Config.DEBUG_MODE, false)
|
|
243
|
+
debugModeCheckbox:SetValue(Config.DEBUG_MODE)
|
|
244
|
+
debugModeCheckbox:SetValueChangedFunction(function(newValue)
|
|
245
|
+
Config.DEBUG_MODE = newValue
|
|
246
|
+
helpers.debugPrint(`[🐛 AzulUI]: Debug mode set to: {newValue}`)
|
|
247
|
+
end)
|
|
248
|
+
mainSection:AddChild(debugModeCheckbox:GetFrame())
|
|
249
|
+
mainSection:AddChild(createInfoLabel("Debug mode enables verbose logging to the output console."))
|
|
250
|
+
self.debugModeCheckbox = debugModeCheckbox
|
|
251
|
+
|
|
252
|
+
-- Settings section
|
|
253
|
+
local settingsSection = CollapsibleTitledSection.new("settings", "Plugin Settings", true, true, true)
|
|
254
|
+
settingsSection:GetSectionFrame().LayoutOrder = 1
|
|
255
|
+
settingsSection:GetSectionFrame().Parent = mainScrollFrame:GetContentsFrame()
|
|
256
|
+
|
|
257
|
+
-- Settings scope dropdown
|
|
258
|
+
local scopeChoices = {
|
|
259
|
+
{ Id = 1, Text = "Global" },
|
|
260
|
+
{ Id = 2, Text = "Project" },
|
|
261
|
+
}
|
|
262
|
+
local settingsScopeDropdown = DropdownMenu.new("settingsScope", "Scope", scopeChoices, "Select ...")
|
|
263
|
+
settingsSection:AddChild(settingsScopeDropdown:GetSectionFrame())
|
|
264
|
+
self.settingsScopeDropdown = settingsScopeDropdown
|
|
265
|
+
|
|
266
|
+
-- WebSocket URL input
|
|
267
|
+
local websocketUrlLabel = LabeledTextInput.new("websocketUrl", "WebSocket URL:", Config.WS_URL)
|
|
268
|
+
websocketUrlLabel:SetValue(Config.WS_URL)
|
|
269
|
+
websocketUrlLabel:SetValueChangedFunction(function(newValue)
|
|
270
|
+
Config.WS_URL = newValue
|
|
271
|
+
self.callbacks.onConfigChanged("WS_URL", newValue)
|
|
272
|
+
end)
|
|
273
|
+
settingsSection:AddChild(websocketUrlLabel:GetFrame())
|
|
274
|
+
self.websocketUrlLabel = websocketUrlLabel
|
|
275
|
+
|
|
276
|
+
-- Heartbeat Interval input
|
|
277
|
+
local heartbeatIntervalLabel =
|
|
278
|
+
LabeledTextInput.new("heartbeatInterval", "Heartbeat Interval (s):", tostring(Config.HEARTBEAT_INTERVAL))
|
|
279
|
+
heartbeatIntervalLabel:SetValue(tostring(Config.HEARTBEAT_INTERVAL))
|
|
280
|
+
heartbeatIntervalLabel:SetValueChangedFunction(function(newValue)
|
|
281
|
+
local interval = tonumber(newValue)
|
|
282
|
+
if interval and interval > 0 then
|
|
283
|
+
Config.HEARTBEAT_INTERVAL = interval
|
|
284
|
+
self.callbacks.onConfigChanged("HEARTBEAT_INTERVAL", interval)
|
|
285
|
+
end
|
|
286
|
+
end)
|
|
287
|
+
settingsSection:AddChild(heartbeatIntervalLabel:GetFrame())
|
|
288
|
+
settingsSection:AddChild(createInfoLabel("Interval in seconds between heartbeat pings to the daemon."))
|
|
289
|
+
self.heartbeatIntervalLabel = heartbeatIntervalLabel
|
|
290
|
+
|
|
291
|
+
-- List type dropdown
|
|
292
|
+
local listTypeLabel = LabeledMultiChoice.new("listType", "Service List Type:", {
|
|
293
|
+
{ Id = "WHITELIST", Text = "Whitelist" },
|
|
294
|
+
{ Id = "BLACKLIST", Text = "Blacklist" },
|
|
295
|
+
}, if Config.LIST_TYPE == Enums.listType.WHITELIST then 1 else 2)
|
|
296
|
+
listTypeLabel:SetValueChangedFunction(function(newIndex)
|
|
297
|
+
if newIndex == 1 then
|
|
298
|
+
Config.LIST_TYPE = Enums.listType.WHITELIST
|
|
299
|
+
else
|
|
300
|
+
Config.LIST_TYPE = Enums.listType.BLACKLIST
|
|
301
|
+
end
|
|
302
|
+
self.callbacks.onConfigChanged("LIST_TYPE", Config.LIST_TYPE)
|
|
303
|
+
end)
|
|
304
|
+
settingsSection:AddChild(listTypeLabel:GetFrame())
|
|
305
|
+
settingsSection:AddChild(
|
|
306
|
+
createInfoLabel(
|
|
307
|
+
"Whitelist: only services in the list are synced.<br />Blacklist: services in the list are excluded."
|
|
308
|
+
)
|
|
309
|
+
)
|
|
310
|
+
self.listTypeLabel = listTypeLabel
|
|
311
|
+
|
|
312
|
+
-- Service List input
|
|
313
|
+
local serviceListLabel =
|
|
314
|
+
LabeledTextInput.new("serviceList", "Service List:", table.concat(Config.SERVICE_LIST, ", "))
|
|
315
|
+
serviceListLabel:SetMaxGraphemes(9999)
|
|
316
|
+
serviceListLabel:SetValue(table.concat(Config.SERVICE_LIST, ", "))
|
|
317
|
+
serviceListLabel:SetValueChangedFunction(function(newValue)
|
|
318
|
+
local services = {}
|
|
319
|
+
for serviceName in string.gmatch(newValue, "([^,%s]+)") do
|
|
320
|
+
table.insert(services, serviceName)
|
|
321
|
+
end
|
|
322
|
+
Config.SERVICE_LIST = services
|
|
323
|
+
self.rebuildServiceSet()
|
|
324
|
+
self.callbacks.onConfigChanged("SERVICE_LIST", services)
|
|
325
|
+
end)
|
|
326
|
+
settingsSection:AddChild(serviceListLabel:GetFrame())
|
|
327
|
+
settingsSection:AddChild(createInfoLabel("List of services to include/exclude based on the selected List Type."))
|
|
328
|
+
self.serviceListLabel = serviceListLabel
|
|
329
|
+
|
|
330
|
+
-- Excluded Parents input
|
|
331
|
+
local excludedParentsLabel =
|
|
332
|
+
LabeledTextInput.new("excludedParents", "Excluded Parents:", table.concat(Config.EXCLUDED_PARENTS, ", "))
|
|
333
|
+
excludedParentsLabel:SetValue(table.concat(Config.EXCLUDED_PARENTS, ", "))
|
|
334
|
+
excludedParentsLabel:SetValueChangedFunction(function(newValue)
|
|
335
|
+
local excluded = {}
|
|
336
|
+
for parentName in string.gmatch(newValue, "([^,%s]+)") do
|
|
337
|
+
table.insert(excluded, parentName)
|
|
338
|
+
end
|
|
339
|
+
Config.EXCLUDED_PARENTS = excluded
|
|
340
|
+
self.callbacks.onConfigChanged("EXCLUDED_PARENTS", excluded)
|
|
341
|
+
end)
|
|
342
|
+
settingsSection:AddChild(excludedParentsLabel:GetFrame())
|
|
343
|
+
settingsSection:AddChild(createInfoLabel("List of parent paths to exclude from syncing, separated by commas."))
|
|
344
|
+
self.excludedParentsLabel = excludedParentsLabel
|
|
345
|
+
|
|
346
|
+
settingsScopeDropdown:SetValue(if self.settingsScope == Enums.scope.GLOBAL then 1 else 2)
|
|
347
|
+
|
|
348
|
+
-- Settings scope callback
|
|
349
|
+
settingsScopeDropdown:SetValueChangedFunction(function(newValue: number | string, newText: string)
|
|
350
|
+
helpers.debugPrint("[🐛 AzulUI]: Settings scope changed to:", newText)
|
|
351
|
+
|
|
352
|
+
local newScope
|
|
353
|
+
if newValue == 1 then
|
|
354
|
+
newScope = Enums.scope.GLOBAL
|
|
355
|
+
elseif newValue == 2 then
|
|
356
|
+
newScope = Enums.scope.PROJECT
|
|
357
|
+
else
|
|
358
|
+
return
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
self.callbacks.onSettingsScopeChanged(newScope)
|
|
362
|
+
end)
|
|
363
|
+
|
|
364
|
+
-- Reload sourcemap button
|
|
365
|
+
local reloadSourcemapButton = CustomTextButton.new("reloadSourcemap", "Reload Sourcemap", false)
|
|
366
|
+
reloadSourcemapButton:SetClickedFunction(function()
|
|
367
|
+
self.callbacks.onSourcemapReload()
|
|
368
|
+
end)
|
|
369
|
+
reloadSourcemapButton:SetSize(UDim2.fromScale(1, 0.07))
|
|
370
|
+
|
|
371
|
+
local clearButtonPadding = Instance.new("UIPadding")
|
|
372
|
+
clearButtonPadding.PaddingLeft = UDim.new(0.1, 0)
|
|
373
|
+
clearButtonPadding.PaddingRight = UDim.new(0.1, 0)
|
|
374
|
+
clearButtonPadding.Parent = reloadSourcemapButton:GetFrame()
|
|
375
|
+
|
|
376
|
+
settingsSection:AddChild(reloadSourcemapButton:GetFrame())
|
|
377
|
+
settingsSection:AddChild(
|
|
378
|
+
createInfoLabel("Something's not right? Force reload the sourcemap to reset the sync state.")
|
|
379
|
+
)
|
|
380
|
+
self.reloadSourcemapButton = reloadSourcemapButton
|
|
381
|
+
|
|
382
|
+
-- Connect button click handler
|
|
383
|
+
self.connectButton.Click:Connect(function()
|
|
384
|
+
azulWidget.Enabled = not azulWidget.Enabled
|
|
385
|
+
end)
|
|
386
|
+
|
|
387
|
+
return (self :: any) :: SelfType
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
function UI:GetSyncState()
|
|
391
|
+
return self.isSyncEnabled
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
function UI:UpdateSyncState(enabled: boolean)
|
|
395
|
+
self.isSyncEnabled = enabled
|
|
396
|
+
self.connectButton:SetActive(enabled)
|
|
397
|
+
if enabled then
|
|
398
|
+
self.connectButton.Icon = self.LOGO_SYNCED
|
|
399
|
+
self.syncButton:GetButton().Text = "Disconnect"
|
|
400
|
+
self.titleImageLabel.Image = self.LOGO_SYNCED
|
|
401
|
+
else
|
|
402
|
+
self.connectButton.Icon = self.LOGO
|
|
403
|
+
self.syncButton:GetButton().Text = "Connect"
|
|
404
|
+
self.titleImageLabel.Image = self.LOGO
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
function UI:SetSettingsScope(scope)
|
|
409
|
+
self.settingsScope = scope
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
function UI:UpdateConfig()
|
|
413
|
+
self = self :: SelfType
|
|
414
|
+
|
|
415
|
+
self.debugModeCheckbox:SetValue(Config.DEBUG_MODE)
|
|
416
|
+
self.silentModeCheckbox:SetValue(Config.SILENT_MODE)
|
|
417
|
+
self.settingsScopeDropdown:SetValue(if self.settingsScope == Enums.scope.GLOBAL then 1 else 2)
|
|
418
|
+
self.websocketUrlLabel:SetValue(Config.WS_URL)
|
|
419
|
+
self.heartbeatIntervalLabel:SetValue(tostring(Config.HEARTBEAT_INTERVAL))
|
|
420
|
+
self.listTypeLabel:SetSelectedIndex(if Config.LIST_TYPE == Enums.listType.WHITELIST then 1 else 2)
|
|
421
|
+
self.serviceListLabel:SetValue(table.concat(Config.SERVICE_LIST, ", "))
|
|
422
|
+
self.excludedParentsLabel:SetValue(table.concat(Config.EXCLUDED_PARENTS, ", "))
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
return UI
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
WebSocket Client for Roblox Studio
|
|
4
|
+
|
|
5
|
+
Uses Roblox Studio's native WebSocket support (WebStreamClient) for real-time
|
|
6
|
+
bidirectional communication with the sync daemon.
|
|
7
|
+
|
|
8
|
+
Ransomwave 2025
|
|
9
|
+
]]
|
|
10
|
+
|
|
11
|
+
local HttpService = game:GetService("HttpService")
|
|
12
|
+
|
|
13
|
+
local WebSocketClient = {}
|
|
14
|
+
WebSocketClient.__index = WebSocketClient
|
|
15
|
+
|
|
16
|
+
type configType = {
|
|
17
|
+
debugMode: boolean?,
|
|
18
|
+
silentMode: boolean?,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
-- self type
|
|
22
|
+
type WebSocketClient = {
|
|
23
|
+
url: string,
|
|
24
|
+
client: WebStreamClient,
|
|
25
|
+
connected: boolean,
|
|
26
|
+
messageHandlers: { [string]: (any) -> () },
|
|
27
|
+
config: configType,
|
|
28
|
+
|
|
29
|
+
new: (url: string?) -> WebSocketClient,
|
|
30
|
+
on: (self: WebSocketClient, event: string, handler: (any) -> ()) -> (),
|
|
31
|
+
connect: (self: WebSocketClient) -> boolean,
|
|
32
|
+
handleMessage: (self: WebSocketClient, message: string) -> (),
|
|
33
|
+
send: (self: WebSocketClient, message: string) -> boolean,
|
|
34
|
+
disconnect: (self: WebSocketClient) -> (),
|
|
35
|
+
debugPrint: (self: WebSocketClient, ...any) -> (),
|
|
36
|
+
infoPrint: (self: WebSocketClient, ...any) -> (),
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function WebSocketClient.new(url, config: configType?)
|
|
40
|
+
local self = setmetatable({}, WebSocketClient)
|
|
41
|
+
self.url = url or "ws://localhost:8080"
|
|
42
|
+
self.client = nil
|
|
43
|
+
self.connected = false
|
|
44
|
+
self.messageHandlers = {}
|
|
45
|
+
self.onClosed = Instance.new("BindableEvent")
|
|
46
|
+
self.config = config or {
|
|
47
|
+
debugMode = false,
|
|
48
|
+
silentMode = true,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if self.config.debugMode then print("[🐛 WebSocket]: Debug mode is enabled!") end
|
|
52
|
+
|
|
53
|
+
return (self :: any) :: WebSocketClient
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
function WebSocketClient:debugPrint(...)
|
|
57
|
+
self = self :: WebSocketClient
|
|
58
|
+
if self.config.silentMode or not self.config.debugMode then return end
|
|
59
|
+
print(...)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
function WebSocketClient:infoPrint(...)
|
|
63
|
+
self = self :: WebSocketClient
|
|
64
|
+
if self.config.silentMode then return end
|
|
65
|
+
print(...)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
function WebSocketClient:on(event, handler)
|
|
69
|
+
self = self :: WebSocketClient
|
|
70
|
+
self.messageHandlers[event] = handler
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
function WebSocketClient:connect()
|
|
74
|
+
self = self :: WebSocketClient
|
|
75
|
+
if self.connected then return true end
|
|
76
|
+
|
|
77
|
+
-- Create WebSocket client using CreateWebStreamClient
|
|
78
|
+
local success, result = pcall(function()
|
|
79
|
+
return HttpService:CreateWebStreamClient(Enum.WebStreamClientType.WebSocket, {
|
|
80
|
+
Url = self.url,
|
|
81
|
+
})
|
|
82
|
+
end)
|
|
83
|
+
|
|
84
|
+
if not success then
|
|
85
|
+
warn("[WebSocket]: Connection failed:", result)
|
|
86
|
+
if self.messageHandlers.error then self.messageHandlers.error(result) end
|
|
87
|
+
return false
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
self.client = result
|
|
91
|
+
self.connected = true
|
|
92
|
+
|
|
93
|
+
-- Set up message handler (only MessageReceived is documented)
|
|
94
|
+
self.client.MessageReceived:Connect(function(message)
|
|
95
|
+
local parseSuccess, parseError = pcall(function()
|
|
96
|
+
self:handleMessage(message)
|
|
97
|
+
end)
|
|
98
|
+
if not parseSuccess then warn("[WebSocket]: Error handling message:", parseError) end
|
|
99
|
+
end)
|
|
100
|
+
|
|
101
|
+
-- Notify connection established
|
|
102
|
+
self:debugPrint("[🐛 WebSocket]: Connected to", self.url)
|
|
103
|
+
if self.messageHandlers.connect then task.defer(function()
|
|
104
|
+
self.messageHandlers.connect()
|
|
105
|
+
end) end
|
|
106
|
+
|
|
107
|
+
return true
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
function WebSocketClient:handleMessage(message)
|
|
111
|
+
self = self :: WebSocketClient
|
|
112
|
+
if not message or message == "" then return end
|
|
113
|
+
|
|
114
|
+
self:debugPrint("[🐛 WebSocket]: Received message:", string.sub(message, 1, 100))
|
|
115
|
+
|
|
116
|
+
local success, data = pcall(function()
|
|
117
|
+
return HttpService:JSONDecode(message)
|
|
118
|
+
end)
|
|
119
|
+
|
|
120
|
+
if success and self.messageHandlers.message then
|
|
121
|
+
self.messageHandlers.message(data)
|
|
122
|
+
elseif not success then
|
|
123
|
+
warn("[WebSocket]: Failed to parse message:", message)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
function WebSocketClient:send(message)
|
|
128
|
+
self = self :: WebSocketClient
|
|
129
|
+
if not self.connected or not self.client then
|
|
130
|
+
warn("[WebSocket]: Cannot send: not connected")
|
|
131
|
+
return false
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
self:debugPrint("[🐛 WebSocket]: Sending:", string.sub(message, 1, 200))
|
|
135
|
+
|
|
136
|
+
local success, err = pcall(function()
|
|
137
|
+
self.client:Send(message)
|
|
138
|
+
end)
|
|
139
|
+
|
|
140
|
+
if not success then
|
|
141
|
+
warn("[WebSocket]: Send failed:", err)
|
|
142
|
+
return false
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
return true
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
function WebSocketClient:disconnect()
|
|
149
|
+
self = self :: WebSocketClient
|
|
150
|
+
|
|
151
|
+
if not self.connected or not self.client then return end
|
|
152
|
+
|
|
153
|
+
self.connected = false
|
|
154
|
+
|
|
155
|
+
self.client:Close()
|
|
156
|
+
-- self.client = nil
|
|
157
|
+
|
|
158
|
+
if self.messageHandlers.disconnect then self.messageHandlers.disconnect() end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
return WebSocketClient
|
package/src/build.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { IPCServer } from "./ipc/server.js";
|
|
4
|
+
import { config } from "./config.js";
|
|
5
|
+
import { log } from "./util/log.js";
|
|
6
|
+
import { SnapshotBuilder } from "./snapshot.js";
|
|
7
|
+
import { RojoSnapshotBuilder } from "./snapshot/rojo.js";
|
|
8
|
+
import type { InstanceData } from "./ipc/messages.js";
|
|
9
|
+
import {
|
|
10
|
+
applySourcemapProperties,
|
|
11
|
+
buildInstancesFromSourcemap,
|
|
12
|
+
loadSourcemapPropertyIndex,
|
|
13
|
+
} from "./sourcemap/propertyLoader.js";
|
|
14
|
+
|
|
15
|
+
interface BuildOptions {
|
|
16
|
+
syncDir?: string;
|
|
17
|
+
rojoMode?: boolean;
|
|
18
|
+
rojoProjectFile?: string;
|
|
19
|
+
applySourcemap?: boolean;
|
|
20
|
+
fromSourcemap?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class BuildCommand {
|
|
24
|
+
private ipc: IPCServer;
|
|
25
|
+
private syncDir: string;
|
|
26
|
+
private rojoMode: boolean;
|
|
27
|
+
private rojoProjectFile?: string;
|
|
28
|
+
private applySourcemap: boolean;
|
|
29
|
+
private fromSourcemap: boolean;
|
|
30
|
+
|
|
31
|
+
constructor(options: BuildOptions = {}) {
|
|
32
|
+
this.syncDir = path.resolve(options.syncDir ?? config.syncDir);
|
|
33
|
+
this.rojoMode = Boolean(options.rojoMode);
|
|
34
|
+
this.rojoProjectFile = options.rojoProjectFile;
|
|
35
|
+
this.applySourcemap = options.applySourcemap !== false;
|
|
36
|
+
this.fromSourcemap = options.fromSourcemap === true;
|
|
37
|
+
this.ipc = new IPCServer(config.port, undefined, {
|
|
38
|
+
requestSnapshotOnConnect: false,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public async run(): Promise<void> {
|
|
43
|
+
const builder = this.rojoMode
|
|
44
|
+
? new RojoSnapshotBuilder({
|
|
45
|
+
projectFile: this.rojoProjectFile,
|
|
46
|
+
cwd: process.cwd(),
|
|
47
|
+
destPrefix: [],
|
|
48
|
+
})
|
|
49
|
+
: new SnapshotBuilder({
|
|
50
|
+
sourceDir: this.syncDir,
|
|
51
|
+
destPrefix: [],
|
|
52
|
+
skipSymlinks: true,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (this.rojoMode) {
|
|
56
|
+
log.info(
|
|
57
|
+
`Preparing Rojo compatibility build from ${
|
|
58
|
+
this.rojoProjectFile ?? "default.project.json"
|
|
59
|
+
}`,
|
|
60
|
+
);
|
|
61
|
+
} else {
|
|
62
|
+
log.info(`Preparing build snapshot from ${this.syncDir}`);
|
|
63
|
+
}
|
|
64
|
+
let instances: InstanceData[] = [];
|
|
65
|
+
|
|
66
|
+
if (!this.rojoMode && this.fromSourcemap) {
|
|
67
|
+
const built = buildInstancesFromSourcemap(config.sourcemapPath);
|
|
68
|
+
if (!built) {
|
|
69
|
+
log.warn(
|
|
70
|
+
"Falling back to filesystem build because sourcemap import failed.",
|
|
71
|
+
);
|
|
72
|
+
} else {
|
|
73
|
+
instances = built;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (instances.length === 0) {
|
|
78
|
+
try {
|
|
79
|
+
instances = await builder.build();
|
|
80
|
+
} catch (error) {
|
|
81
|
+
log.error(`${error}`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!this.rojoMode && this.applySourcemap && !this.fromSourcemap) {
|
|
87
|
+
const index = loadSourcemapPropertyIndex(config.sourcemapPath);
|
|
88
|
+
const applied = applySourcemapProperties(instances, index);
|
|
89
|
+
if (applied > 0) {
|
|
90
|
+
log.success(
|
|
91
|
+
`Applied properties/attributes from sourcemap to ${applied} instance(s)`,
|
|
92
|
+
);
|
|
93
|
+
} else {
|
|
94
|
+
if (!index && fs.existsSync(config.sourcemapPath)) {
|
|
95
|
+
log.warn(
|
|
96
|
+
"Sourcemap present but could not be parsed; continuing without properties.",
|
|
97
|
+
);
|
|
98
|
+
} else {
|
|
99
|
+
log.info(
|
|
100
|
+
"No packed properties found in sourcemap; continuing with script/folder snapshot only.",
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
log.info(`Waiting for Studio to connect on port ${config.port}...`);
|
|
107
|
+
|
|
108
|
+
await new Promise<void>((resolve) => {
|
|
109
|
+
this.ipc.onConnection(() => {
|
|
110
|
+
log.info("Studio connected. Sending build snapshot...");
|
|
111
|
+
this.ipc.send({ type: "buildSnapshot", data: instances });
|
|
112
|
+
log.success(`Sent ${instances.length} instances`);
|
|
113
|
+
setTimeout(() => {
|
|
114
|
+
this.ipc.close();
|
|
115
|
+
resolve();
|
|
116
|
+
}, 200);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|