Skip to content
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

Bump @parcel/watcher #1269

Open
wants to merge 12 commits into
base: fix/recursive-symlinks
Choose a base branch
from
10 changes: 9 additions & 1 deletion packages/tailwindcss-language-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,15 @@
"access": "public"
},
"devDependencies": {
"@parcel/watcher": "2.0.3",
"@parcel/watcher": "2.5.1",
"@parcel/watcher-darwin-x64": "2.5.1",
"@parcel/watcher-darwin-arm64": "2.5.1",
"@parcel/watcher-win32-x64": "2.5.1",
"@parcel/watcher-win32-arm64": "2.5.1",
"@parcel/watcher-linux-x64-glibc": "2.5.1",
"@parcel/watcher-linux-x64-musl": "2.5.1",
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
"@parcel/watcher-linux-arm64-musl": "2.5.1",
"@tailwindcss/aspect-ratio": "0.4.2",
"@tailwindcss/container-queries": "0.1.0",
"@tailwindcss/forms": "0.5.3",
Expand Down
5 changes: 5 additions & 0 deletions packages/tailwindcss-language-server/src/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,11 @@ export async function createProjectService(
let elapsed = process.hrtime.bigint() - start

console.log(`---- RELOADED IN ${(Number(elapsed) / 1e6).toFixed(2)}ms ----`)

let isTestMode = params.initializationOptions?.testMode ?? false
if (!isTestMode) return

connection.sendNotification('@/tailwindCSS/projectReloaded')
},

state,
Expand Down
42 changes: 30 additions & 12 deletions packages/tailwindcss-language-server/src/testing/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { onTestFinished, test, TestOptions } from 'vitest'
import { onTestFinished, test, TestContext, TestOptions } from 'vitest'
import * as os from 'node:os'
import * as fs from 'node:fs/promises'
import * as path from 'node:path'
import * as proc from 'node:child_process'
import dedent from 'dedent'

export interface TestUtils {
export interface TestUtils<TestInput extends Record<string, any>> {
/** The "cwd" for this test */
root: string

/**
* The input for this test — taken from the `inputs` in the test config
*
* @see {TestConfig}
*/
input?: TestInput
}

export interface StorageSymlink {
Expand All @@ -21,29 +28,39 @@ export interface Storage {
[filePath: string]: string | Uint8Array | StorageSymlink
}

export interface TestConfig<Extras extends {}> {
export interface TestConfig<Extras extends {}, TestInput extends Record<string, any>> {
name: string
inputs?: TestInput[]

fs?: Storage
debug?: boolean
prepare?(utils: TestUtils): Promise<Extras>
handle(utils: TestUtils & Extras): void | Promise<void>
prepare?(utils: TestUtils<TestInput>): Promise<Extras>
handle(utils: TestUtils<TestInput> & Extras): void | Promise<void>

options?: TestOptions
}

export function defineTest<T>(config: TestConfig<T>) {
return test(config.name, config.options ?? {}, async ({ expect }) => {
let utils = await setup(config)
export function defineTest<T, I>(config: TestConfig<T, I>) {
async function runTest(ctx: TestContext, input?: I) {
let utils = await setup(config, input)
let extras = await config.prepare?.(utils)

await config.handle({
...utils,
...extras,
})
})
}

if (config.inputs) {
return test.for(config.inputs ?? [])(config.name, config.options ?? {}, (input, ctx) =>
runTest(ctx, input),
)
}

return test(config.name, config.options ?? {}, runTest)
}

async function setup<T>(config: TestConfig<T>): Promise<TestUtils> {
async function setup<T, I>(config: TestConfig<T, I>, input: I): Promise<TestUtils<I>> {
let randomId = Math.random().toString(36).substring(7)

let baseDir = path.resolve(process.cwd(), `../../.debug/${randomId}`)
Expand All @@ -56,7 +73,7 @@ async function setup<T>(config: TestConfig<T>): Promise<TestUtils> {
await installDependencies(baseDir, config.fs)
}

onTestFinished(async (result) => {
onTestFinished(async (ctx) => {
// Once done, move all the files to a new location
try {
await fs.rename(baseDir, doneDir)
Expand All @@ -66,7 +83,7 @@ async function setup<T>(config: TestConfig<T>): Promise<TestUtils> {
console.error('Failed to move test files to done directory')
}

if (result.state === 'fail') return
if (ctx.task.result?.state === 'fail') return

if (path.sep === '\\') return

Expand All @@ -79,6 +96,7 @@ async function setup<T>(config: TestConfig<T>): Promise<TestUtils> {

return {
root: baseDir,
input,
}
}

Expand Down
52 changes: 49 additions & 3 deletions packages/tailwindcss-language-server/src/tw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import { readCssFile } from './util/css'
import { ProjectLocator, type ProjectConfig } from './project-locator'
import type { TailwindCssSettings } from '@tailwindcss/language-service/src/util/state'
import { createResolver, Resolver } from './resolver'
import { retry } from './util/retry'
import { analyzeStylesheet } from './version-guesser.js'

const TRIGGER_CHARACTERS = [
// class attributes
Expand Down Expand Up @@ -382,6 +382,13 @@ export class TW {
for (let [, project] of this.projects) {
if (!project.state.v4) continue

if (
change.type === FileChangeType.Deleted &&
changeAffectsFile(normalizedFilename, [project.projectConfig.configPath])
) {
continue
}

if (!changeAffectsFile(normalizedFilename, project.dependencies())) continue

needsSoftRestart = true
Expand All @@ -405,6 +412,31 @@ export class TW {
needsRestart = true
break
}

//
else {
// If the main CSS file in a project is deleted and then re-created
// the server won't restart because the project is gone by now and
// there's no concept of a "config file" for us to compare with
//
// So we'll check if the stylesheet could *potentially* create
// a new project but we'll only do so if no projects were found
//
// If we did this all the time we'd potentially restart the server
// unncessarily a lot while the user is editing their stylesheets
if (this.projects.size > 0) continue

let content = await readCssFile(change.file)
if (!content) continue

let stylesheet = analyzeStylesheet(content)
if (!stylesheet.root) continue

if (!stylesheet.versions.includes('4')) continue

needsRestart = true
break
}
}

let isConfigFile = isConfigMatcher(normalizedFilename)
Expand Down Expand Up @@ -489,6 +521,10 @@ export class TW {
}
}
} else if (parcel.getBinding()) {
console.log(
'[Global] Your LSP client does not support watching files on behalf of the server',
)
console.log('[Global] Using bundled file watcher: @parcel/watcher')
let typeMap = {
create: FileChangeType.Created,
update: FileChangeType.Changed,
Expand All @@ -515,6 +551,10 @@ export class TW {
},
})
} else {
console.log(
'[Global] Your LSP client does not support watching files on behalf of the server',
)
console.log('[Global] Using bundled file watcher: chokidar')
let watch: typeof chokidar.watch = require('chokidar').watch
let chokidarWatcher = watch(
[`**/${CONFIG_GLOB}`, `**/${PACKAGE_LOCK_GLOB}`, `**/${CSS_GLOB}`, `**/${TSCONFIG_GLOB}`],
Expand Down Expand Up @@ -1041,11 +1081,17 @@ export class TW {
this.watched.length = 0
}

restart(): void {
async restart(): void {
let isTestMode = this.initializeParams.initializationOptions?.testMode ?? false

console.log('----------\nRESTARTING\n----------')
this.dispose()
this.initPromise = undefined
this.init()
await this.init()

if (isTestMode) {
this.connection.sendNotification('@/tailwindCSS/serverRestarted')
}
}

async softRestart(): Promise<void> {
Expand Down
21 changes: 12 additions & 9 deletions packages/tailwindcss-language-server/src/watcher/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,24 @@ const uv = (process.versions.uv || '').split('.')[0]

const prebuilds = {
'darwin-arm64': {
'node.napi.glibc.node': () =>
require('@parcel/watcher/prebuilds/darwin-arm64/node.napi.glibc.node'),
'node.napi.glibc.node': () => require('@parcel/watcher-darwin-arm64/watcher.node'),
},
'darwin-x64': {
'node.napi.glibc.node': () =>
require('@parcel/watcher/prebuilds/darwin-x64/node.napi.glibc.node'),
'node.napi.glibc.node': () => require('@parcel/watcher-darwin-x64/watcher.node'),
},
'linux-x64': {
'node.napi.glibc.node': () =>
require('@parcel/watcher/prebuilds/linux-x64/node.napi.glibc.node'),
'node.napi.musl.node': () => require('@parcel/watcher/prebuilds/linux-x64/node.napi.musl.node'),
'node.napi.glibc.node': () => require('@parcel/watcher-linux-x64-glibc/watcher.node'),
'node.napi.musl.node': () => require('@parcel/watcher-linux-x64-musl/watcher.node'),
},
'linux-arm64': {
'node.napi.glibc.node': () => require('@parcel/watcher-linux-arm64-glibc/watcher.node'),
'node.napi.musl.node': () => require('@parcel/watcher-linux-arm64-musl/watcher.node'),
},
'win32-x64': {
'node.napi.glibc.node': () =>
require('@parcel/watcher/prebuilds/win32-x64/node.napi.glibc.node'),
'node.napi.glibc.node': () => require('@parcel/watcher-win32-x64/watcher.node'),
},
'win32-arm64': {
'node.napi.glibc.node': () => require('@parcel/watcher-win32-arm64/watcher.node'),
},
}

Expand Down
Loading