Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions docs/src/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,10 @@ You can customize this behavior using the following options.
- **Default:** `false`
- **Env Var:** `VITE_RUBY_DEV_SERVER_CONNECTION_CHECK`

By default the dev server is detected by reading `tmp/vite-ruby.json`, a
metadata file the Vite plugin writes with the dev server URL when it starts,
and removes when it stops.
By default the dev server is detected by reading `tmp/vite-ruby-<mode>.json`
(for example, `tmp/vite-ruby-development.json`), a metadata file the Vite
plugin writes when the dev server starts and removes when it stops. The
metadata must have the configured port for the current mode.

Enable this to detect the dev server by opening a TCP connection to the
configured `host` and `port` instead. Useful if the metadata file can become
Expand Down
4 changes: 2 additions & 2 deletions docs/src/guide/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ First, verify that the dev server is reachable by starting a new console session
> ViteRuby.instance.dev_server_running?
```

By default this reads `tmp/vite-ruby.json`, written by the Vite plugin while the dev server runs.
If it returns `false` while the dev server is running, make sure you are on a recent [vite-plugin-ruby] that writes this file, and that `tmp/` is writable.
By default this reads `tmp/vite-ruby-<mode>.json` (for example, `tmp/vite-ruby-development.json`), written by the Vite plugin while the server runs.
If it returns `false` while the dev server is running, make sure you are using vite-plugin-ruby 5.2.5 or later, that `tmp/` is writable, and that the dev server's port matches the configured port for this mode.

If you have enabled <kbd>[devServerConnectionCheck]</kbd>, try increasing the <kbd>[devServerConnectTimeout]</kbd>, restart the console and retry.
In systems with constrained resources the [default timeout][devServerConnectTimeout] might not be enough.
Expand Down
6 changes: 6 additions & 0 deletions test/compatibility_check_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ def test_compatible_plugin
assert compatible_plugin?(nil, nil)
end

def test_requires_plugin_version_with_mode_specific_metadata
assert_equal "^5.2.5", ViteRuby::DEFAULT_PLUGIN_VERSION
refute compatible_plugin?("^5.2.4", ViteRuby::DEFAULT_PLUGIN_VERSION)
assert compatible_plugin?("^5.2.5", ViteRuby::DEFAULT_PLUGIN_VERSION)
end

def test_raise_unless_satisfied
assert_raises(ArgumentError) { raise_unless_satisfied("^4.1.0", "^3.0") }
raise_unless_satisfied("3.1.0", "^3.0")
Expand Down
6 changes: 6 additions & 0 deletions test/config_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ def test_ruby_config_file
assert_equal Gem.loaded_specs["rails"].full_gem_path, ViteRuby.config.to_env["EXAMPLE_PATH"]
end

def test_dev_server_meta_path_is_scoped_to_mode
assert_path "test_app/tmp/vite-ruby-production.json", @config.dev_server_meta_path
assert_path "test_app/tmp/vite-ruby-development.json", resolve_config(mode: "development").dev_server_meta_path
assert_path "test_app/tmp/vite-ruby-test.json", resolve_config(mode: "test").dev_server_meta_path
end

def test_manifest_path
assert_path "test_app/public/vite-production/.vite/manifest.json", @config.manifest_paths.first
end
Expand Down
53 changes: 51 additions & 2 deletions test/dev_server_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,59 @@ def test_not_running_without_meta_file

def test_running_with_meta_file
refresh_config(mode: "development")
write_meta_file(url: "http://localhost:3036", host: "localhost", port: 3036, https: false, pid: 1234)
write_meta_file(url: "http://localhost:3535", host: "localhost", port: ViteRuby.config.port, https: false, pid: 1234)

assert_predicate ViteRuby.instance, :dev_server_running?
assert_equal "http://localhost:3036", ViteRuby.instance.send(:dev_server_meta)["url"]
assert_equal "http://localhost:3535", ViteRuby.instance.send(:dev_server_meta)["url"]
ensure
remove_meta_file
end

def test_development_meta_does_not_mark_test_server_as_running
refresh_config(mode: "test")
remove_meta_file
test_port = ViteRuby.config.port
refresh_config(mode: "development", port: test_port)
write_meta_file(port: test_port)

refresh_config(mode: "test")

ENV.stub(:[], ->(key) { ENV.fetch(key, nil) unless key == "CI" }) do
refute_predicate ViteRuby.instance, :dev_server_running?
end
ensure
remove_meta_file if ViteRuby.config.mode == "test"
refresh_config(mode: "development")
remove_meta_file
end

def test_running_with_test_meta_file
refresh_config(mode: "test")
write_meta_file(port: ViteRuby.config.port)

ENV.stub(:[], ->(key) { ENV.fetch(key, nil) unless key == "CI" }) do
assert_predicate ViteRuby.instance, :dev_server_running?
end
ensure
remove_meta_file
end

def test_not_running_when_meta_port_does_not_match
refresh_config(mode: "development")
write_meta_file(port: ViteRuby.config.port + 1)

refute_predicate ViteRuby.instance, :dev_server_running?
ensure
remove_meta_file
end

def test_not_running_with_invalid_meta_shape
refresh_config(mode: "development")
path = ViteRuby.config.dev_server_meta_path
path.dirname.mkpath
path.write(JSON.generate("not a metadata object"))

refute_predicate ViteRuby.instance, :dev_server_running?
ensure
remove_meta_file
end
Expand Down
3 changes: 0 additions & 3 deletions vite-plugin-ruby/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ export const ENV_PREFIX = 'VITE_RUBY'
// Internal: Key of the vite.json file that is applied to all environments.
export const ALL_ENVS_KEY = 'all'

// Internal: Path, relative to the project root, of the dev server metadata file.
export const DEV_SERVER_META_FILE = 'tmp/vite-ruby.json'

// Internal: Extensions of CSS files or known precompilers.
export const KNOWN_CSS_EXTENSIONS = [
'css',
Expand Down
7 changes: 6 additions & 1 deletion vite-plugin-ruby/src/dev-server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs'
import { dirname } from 'path'
import { dirname, resolve } from 'path'
import type { AddressInfo } from 'net'
import type { ResolvedConfig } from 'vite'

Expand All @@ -18,6 +18,11 @@ const WILDCARD_HOSTS = new Set(['', '0.0.0.0', '::', '::1'])
let exitHandlersBound = false
let ownedMetaPath: string | null = null

// Internal: Keeps metadata for each Ruby mode separate.
export function devServerMetaPath (root: string, mode: string): string {
return resolve(root, 'tmp', `vite-ruby-${mode}.json`)
}

// Internal: Returns true when the address is a resolved TCP address.
function isAddressInfo (address: string | AddressInfo | null | undefined): address is AddressInfo {
return Boolean(address) && typeof address === 'object'
Expand Down
11 changes: 6 additions & 5 deletions vite-plugin-ruby/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import type { ConfigEnv, PluginOption, UserConfig, ViteDevServer } from 'vite'
import { createDebug } from 'obug'

import { cleanConfig, configOptionFromEnv } from './utils'
import type { UnifiedConfig } from './types'
import { filterEntrypointsForRollup, loadConfiguration, resolveGlobs } from './config'
import { assetsManifestPlugin } from './manifest'
import { bindDevServerCleanup, resolveDevServerMeta, writeDevServerMeta } from './dev-server'
import { DEV_SERVER_META_FILE } from './constants'
import { bindDevServerCleanup, devServerMetaPath, resolveDevServerMeta, writeDevServerMeta } from './dev-server'

export * from './types'

Expand Down Expand Up @@ -105,10 +105,11 @@ function config (userConfig: UserConfig, env: ConfigEnv): UserConfig {
function configureServer (server: ViteDevServer) {
server.watcher.add(watchAdditionalPaths)

const devServerMetaPath = resolve(projectRoot, DEV_SERVER_META_FILE)
const rubyConfig = server.config as typeof server.config & { viteRuby: UnifiedConfig }
const metaPath = devServerMetaPath(projectRoot, rubyConfig.viteRuby.mode)
server.httpServer?.once('listening', () => {
writeDevServerMeta(devServerMetaPath, resolveDevServerMeta(server.httpServer?.address(), server.config))
bindDevServerCleanup(devServerMetaPath)
writeDevServerMeta(metaPath, resolveDevServerMeta(server.httpServer?.address(), server.config))
bindDevServerCleanup(metaPath)
})

return () => server.middlewares.use((req, res, next) => {
Expand Down
25 changes: 24 additions & 1 deletion vite-plugin-ruby/tests/dev-server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { tmpdir } from 'os'
import { join } from 'path'
import { describe, it, expect } from 'vitest'

import { removeOwnedMeta, resolveDevServerMeta, writeDevServerMeta } from '../src/dev-server'
import { devServerMetaPath, removeOwnedMeta, resolveDevServerMeta, writeDevServerMeta } from '../src/dev-server'

const withServer = (server: Record<string, unknown>) => ({ server }) as any

Expand All @@ -23,6 +23,15 @@ describe('resolveDevServerMeta', () => {
})
})

describe('devServerMetaPath', () => {
it('uses separate metadata files for the resolved Ruby modes', () => {
const root = join(tmpdir(), 'vite-app')

expect(devServerMetaPath(root, 'development')).toBe(join(root, 'tmp/vite-ruby-development.json'))
expect(devServerMetaPath(root, 'test')).toBe(join(root, 'tmp/vite-ruby-test.json'))
})
})

describe('removeOwnedMeta', () => {
it('only removes the file when the pid matches', () => {
const path = join(mkdtempSync(join(tmpdir(), 'vpr-')), 'vite-ruby.json')
Expand All @@ -34,4 +43,18 @@ describe('removeOwnedMeta', () => {
removeOwnedMeta(path, 4242)
expect(existsSync(path)).toBe(false)
})

it('does not remove another mode\'s metadata when a server stops', () => {
const root = mkdtempSync(join(tmpdir(), 'vpr-'))
const devPath = devServerMetaPath(root, 'development')
const testPath = devServerMetaPath(root, 'test')
writeDevServerMeta(devPath, { url: 'x', host: 'h', port: 1, https: false, pid: 4242 })
writeDevServerMeta(testPath, { url: 'x', host: 'h', port: 2, https: false, pid: 4243 })

removeOwnedMeta(devPath, 4242)

expect(existsSync(devPath)).toBe(false)
expect(existsSync(testPath)).toBe(true)
removeOwnedMeta(testPath, 4243)
})
})
3 changes: 2 additions & 1 deletion vite_ruby/lib/vite_ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ def dev_server_running?
return false unless dev_mode?
return dev_server_connected? if config.dev_server_connection_check

!dev_server_meta.nil?
meta = dev_server_meta
meta.is_a?(Hash) && meta["port"] == config.port
end

# Public: Additional environment variables to pass to Vite.
Expand Down
6 changes: 3 additions & 3 deletions vite_ruby/lib/vite_ruby/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
# Public: Allows to resolve configuration sourced from `config/vite.json` and
# environment variables, combining them with the default options.
class ViteRuby::Config
# Internal: Name of the metadata file written by the Vite dev server.
DEV_SERVER_META_FILENAME = "vite-ruby.json"
# Internal: Base name of the metadata file written by the Vite dev server.
DEV_SERVER_META_BASENAME = "vite-ruby"

def origin
"#{protocol}://#{host_with_port}"
Expand Down Expand Up @@ -43,7 +43,7 @@ def build_output_dir

# Internal: Path to the metadata file written by the Vite dev server.
def dev_server_meta_path
root.join("tmp", DEV_SERVER_META_FILENAME)
root.join("tmp", "#{DEV_SERVER_META_BASENAME}-#{mode}.json")
end

# Public: The directory where the entries are located.
Expand Down
2 changes: 1 addition & 1 deletion vite_ruby/lib/vite_ruby/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ class ViteRuby

# Internal: Versions used by default when running `vite install`.
DEFAULT_VITE_VERSION = "^8.0.0"
DEFAULT_PLUGIN_VERSION = "^5.2.0"
DEFAULT_PLUGIN_VERSION = "^5.2.5"
end
Loading