-
Notifications
You must be signed in to change notification settings - Fork 27
feat: restart the app development server on file changes #310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #310 +/- ##
==========================================
- Coverage 64.93% 64.64% -0.29%
==========================================
Files 212 212
Lines 17623 17750 +127
==========================================
+ Hits 11443 11474 +31
- Misses 5104 5202 +98
+ Partials 1076 1074 -2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🌟 very exciting changes! I got it working for app.js and /listeners but I'm having trouble getting a response from changes to manifest.json. might there be any other necessary config to get it listening to manifest?
App change detected: []/bolt-js-assistant-template/app.js, restarting server...
[DEBUG] web-api:WebClient:0 initialized
[DEBUG] bolt-app Initializing SocketModeReceiver
[DEBUG] web-api:WebClient:0 initialized
[DEBUG] socket-mode:SocketModeClient:0 The Socket Mode client has successfully initialized
[DEBUG] web-api:WebClient:0 apiCall('auth.test') start
[DEBUG] web-api:WebClient:0 http request url: https://slack.com/api/auth.test
[DEBUG] web-api:WebClient:0 http request body: {"token":"[[REDACTED]]"}
[DEBUG] web-api:WebClient:0 http request headers: {"Accept":"application/json, text/plain, */*","User-Agent":"@slack:bolt/4.6.0 @slack:web-api/7.13.0 node/24.8.0 darwin/25.2.0","Authorization":"[[REDACTED]]"}
[DEBUG] socket-mode:SocketModeClient:0 Starting Socket Mode session ...
[DEBUG] socket-mode:SocketModeClient:0 Going to retrieve a new WSS URL ...
[DEBUG] web-api:WebClient:0 apiCall('apps.connections.open') start| case "on_cloud_run_watch_manifest_change_reinstalled": | ||
| cmd.Println(style.Secondary("App successfully reinstalled")) | ||
| case "on_cloud_run_watch_app_change": | ||
| path := event.DataToString("cloud_run_watch_app_change") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
praise ⭐
srtaalej
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! 👏
Update
Got it working for manifest.json ⭐ ⭐ ⭐
in .slack/config.json change source from remote to local
{
"manifest": {
"source": "local"
},output:
Manifest change detected: []/bolt-js-assistant-template/manifest.json, reinstalling app...
Updating local app install for "ale sandbox"
App successfully reinstalledThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ Love Love Love this feature! 💾 ♻️ Great working on implementing this large feature! 👏🏻
🧪 Manual testing work well on both current projects and projects using the new @slack/cli-hooks.
- Server restarts work well even when there is a syntax error that prevents the Bolt server from starting. ❤️
✏️ Can we tweak the PR Title to be something like "feat: add file watch support to restart Bolt app servers"
❓ Do we have a draft PR for Python?
| | config | Object of key-value settings. | Optional | | ||
| | config.protocol-version | Array of strings representing the named CLI-SDK protocols supported by the SDK, in descending order of support, as in the first element in the array defines the preferred protocol for use by the SDK, the second element defines the next-preferred protocol, and so on. The only supported named protocol currently is `message-boundaries`. The CLI will use the v1 protocol if this field is not provided. | Optional | | ||
| | config.watch | Object with configuration settings for file-watching. | Optional | | ||
| | config.watch | Object with configuration settings for file-watching during `slack run`. Supports updating the `manifest` on change and reloading the `app` server. Read [Watch configurations](#watch-configurations) for details. | Optional | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thought: Out-of-scope of this PR, but I feel we should start to seriously consider a config.json version field. It would allow the CLI to know whether it supports the version of the config file and help developers upgrade when using an older CLI with a newer project.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mwbrooks Great callout! I agree so much. We've been careful to make backward compatible changes but adding that field soon would give me more confidence for whatever might happen later.
| // Begin watching for app file changes | ||
| go func() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Non-Blocking. There could be issues when multiple files are saved in quick succession. This may cause the Goroutine to restart the app server multiple times in a row.
A solution may be to add a "debouncing delay" where we wait 500ms after the last file change before restarting the server.
I think we should save this for future work and only address it when it's a confirmed issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mwbrooks Debouncing is such a great idea! For now I agree this might be good to save as follow up to keep this PR implementation more minimal. As feedback arrives let's be sure to take this approach to start 🙏 ✨
| // GetManifestWatchConfig returns manifest watch config | ||
| func (w *WatchOpts) GetManifestWatchConfig() (paths []string, filterRegex string, enabled bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: Since GetManifestWatchConfig and GetAppWatchConfig access a w *WatchOpts pointer, we may want to check that w != nil otherwise the CLI will panic when accessing a nil pointer.
Non-blocker for this PR because we have a lot of unchecked pointers like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| r.clients.IO.PrintDebug(ctx, "Previous process stopped successfully") | ||
| case <-time.After(5 * time.Second): | ||
| r.clients.IO.PrintDebug(ctx, "Process did not exit in time, force killing") | ||
| _ = process.Kill() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: For completeness, I believe we should wait for the gorountine to complete after the process.Kill(). But, I'm happy to keep the code as-is in case we're concerned about indefinite hangs.
| _ = process.Kill() | |
| _ = process.Kill() | |
| <-done // Wait for goroutine to complete after kill |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mwbrooks We might escape the select statement after this "kill" instead of blocking. I think exiting after whatever attempt, failed or not might be alright to keep, but if timeouts occur let's explore this more 🔭
zimeg
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // GetManifestWatchConfig returns manifest watch config | ||
| func (w *WatchOpts) GetManifestWatchConfig() (paths []string, filterRegex string, enabled bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| r.clients.IO.PrintDebug(ctx, "Previous process stopped successfully") | ||
| case <-time.After(5 * time.Second): | ||
| r.clients.IO.PrintDebug(ctx, "Process did not exit in time, force killing") | ||
| _ = process.Kill() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mwbrooks We might escape the select statement after this "kill" instead of blocking. I think exiting after whatever attempt, failed or not might be alright to keep, but if timeouts occur let's explore this more 🔭
Changelog
Summary
This PR restarts the app development server on file changes when using the
slack runcommand. An update to the hooks file adds options for this:{ "config": { "watch": { "manifest": { "paths": ["manifest.json"] }, "app": { "paths": ["app.js", "listeners/"], "filter-regex": "\\.(ts|js)$" } } } }Preview
reload.mov
Reviewers
The changes of this PR can be built with update hook defaults of slackapi/node-slack-sdk#2480 👾
Notes
filter-regexandpathsfor manifest updates. A follow up PR might add warnings for upcoming deprecation?Requirements