effect-start 0.13.1 → 0.15.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/package.json +13 -14
- package/src/Commander.test.ts +507 -245
- package/src/ContentNegotiation.test.ts +500 -0
- package/src/ContentNegotiation.ts +535 -0
- package/src/FileRouter.ts +16 -12
- package/src/{FileRouterCodegen.test.ts → FileRouterCodegen.todo.ts} +384 -219
- package/src/FileRouterCodegen.ts +6 -6
- package/src/FileRouterPattern.test.ts +93 -62
- package/src/FileRouter_files.test.ts +6 -6
- package/src/FileRouter_path.test.ts +121 -69
- package/src/FileRouter_tree.test.ts +62 -56
- package/src/FileSystemExtra.test.ts +46 -30
- package/src/Http.test.ts +24 -0
- package/src/Http.ts +25 -0
- package/src/HttpAppExtra.test.ts +40 -21
- package/src/HttpAppExtra.ts +0 -1
- package/src/HttpUtils.test.ts +35 -18
- package/src/HttpUtils.ts +2 -0
- package/src/PathPattern.test.ts +648 -0
- package/src/PathPattern.ts +483 -0
- package/src/Route.ts +258 -1073
- package/src/RouteBody.test.ts +182 -0
- package/src/RouteBody.ts +106 -0
- package/src/RouteHook.test.ts +40 -0
- package/src/RouteHook.ts +105 -0
- package/src/RouteHttp.test.ts +443 -0
- package/src/RouteHttp.ts +219 -0
- package/src/RouteMount.test.ts +468 -0
- package/src/RouteMount.ts +313 -0
- package/src/RouteSchema.test.ts +81 -0
- package/src/RouteSchema.ts +44 -0
- package/src/RouteTree.test.ts +346 -0
- package/src/RouteTree.ts +165 -0
- package/src/RouteTrie.test.ts +322 -0
- package/src/RouteTrie.ts +224 -0
- package/src/RouterPattern.test.ts +569 -548
- package/src/RouterPattern.ts +7 -7
- package/src/Start.ts +3 -37
- package/src/StartApp.ts +20 -16
- package/src/TuplePathPattern.ts +64 -0
- package/src/Values.ts +16 -0
- package/src/bun/BunBundle.test.ts +37 -43
- package/src/bun/BunBundle.ts +2 -2
- package/src/bun/BunBundle_imports.test.ts +6 -8
- package/src/bun/BunHttpServer.test.ts +183 -6
- package/src/bun/BunHttpServer.ts +56 -32
- package/src/bun/BunHttpServer_web.ts +18 -6
- package/src/bun/BunImportTrackerPlugin.test.ts +3 -3
- package/src/bun/BunRoute.ts +29 -210
- package/src/{Bundle.ts → bundler/Bundle.ts} +0 -35
- package/src/{BundleHttp.test.ts → bundler/BundleHttp.test.ts} +36 -64
- package/src/{BundleHttp.ts → bundler/BundleHttp.ts} +1 -1
- package/src/client/index.ts +1 -1
- package/src/{Effect_HttpRouter.test.ts → effect/HttpRouter.test.ts} +69 -91
- package/src/{EncryptedCookies.test.ts → experimental/EncryptedCookies.test.ts} +125 -64
- package/src/{SseHttpResponse.ts → experimental/SseHttpResponse.ts} +1 -1
- package/src/experimental/index.ts +2 -0
- package/src/hyper/Hyper.ts +89 -0
- package/src/{HyperHtml.test.ts → hyper/HyperHtml.test.ts} +13 -13
- package/src/{HyperHtml.ts → hyper/HyperHtml.ts} +2 -2
- package/src/{jsx.d.ts → hyper/jsx.d.ts} +1 -1
- package/src/index.ts +1 -21
- package/src/middlewares/BasicAuthMiddleware.test.ts +29 -19
- package/src/middlewares/index.ts +1 -0
- package/src/{NodeFileSystem.ts → node/FileSystem.ts} +6 -2
- package/src/{TestHttpClient.test.ts → testing/TestHttpClient.test.ts} +27 -27
- package/src/{TestHttpClient.ts → testing/TestHttpClient.ts} +0 -1
- package/src/{TestLogger.test.ts → testing/TestLogger.test.ts} +27 -11
- package/src/testing/index.ts +3 -0
- package/src/x/datastar/Datastar.test.ts +47 -48
- package/src/x/datastar/Datastar.ts +1 -1
- package/src/x/tailwind/TailwindPlugin.test.ts +56 -58
- package/src/x/tailwind/TailwindPlugin.ts +23 -17
- package/src/x/tailwind/plugin.ts +1 -1
- package/src/FileHttpRouter.test.ts +0 -239
- package/src/FileHttpRouter.ts +0 -194
- package/src/Hyper.ts +0 -194
- package/src/JsModule.test.ts +0 -14
- package/src/JsModule.ts +0 -116
- package/src/PublicDirectory.test.ts +0 -280
- package/src/PublicDirectory.ts +0 -108
- package/src/Route.test.ts +0 -1370
- package/src/RouteRender.ts +0 -40
- package/src/Router.test.ts +0 -375
- package/src/Router.ts +0 -255
- package/src/StartHttp.ts +0 -42
- package/src/bun/BunRoute.test.ts +0 -480
- package/src/bun/BunRoute_bundles.test.ts +0 -219
- /package/src/{BundleFiles.ts → bundler/BundleFiles.ts} +0 -0
- /package/src/{EncryptedCookies.ts → experimental/EncryptedCookies.ts} +0 -0
- /package/src/{HyperNode.ts → hyper/HyperNode.ts} +0 -0
- /package/src/{jsx-runtime.ts → hyper/jsx-runtime.ts} +0 -0
- /package/src/{NodeUtils.ts → node/Utils.ts} +0 -0
- /package/src/{TestLogger.ts → testing/TestLogger.ts} +0 -0
- /package/src/{testing.ts → testing/utils.ts} +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
|
|
1
|
+
import * as test from "bun:test"
|
|
3
2
|
import { extractClassNames } from "./TailwindPlugin.ts"
|
|
4
3
|
|
|
5
4
|
// Keep the old broad implementation for comparison tests
|
|
@@ -17,70 +16,70 @@ function extractClassNamesBroad(source: string): Set<string> {
|
|
|
17
16
|
)
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
test.describe(`${extractClassNames.name}`, () => {
|
|
20
|
+
test.it("Basic HTML class attributes", () => {
|
|
22
21
|
const source = `<div class="bg-red-500 text-white">Hello</div>`
|
|
23
22
|
const result = extractClassNames(source)
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
test
|
|
26
25
|
.expect([...result].sort())
|
|
27
26
|
.toEqual(["bg-red-500", "text-white"])
|
|
28
27
|
})
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
test.it("Basic JSX className attributes", () => {
|
|
31
30
|
const source =
|
|
32
31
|
`<div className="flex items-center justify-between">Content</div>`
|
|
33
32
|
const result = extractClassNames(source)
|
|
34
33
|
|
|
35
|
-
|
|
34
|
+
test
|
|
36
35
|
.expect([...result].sort())
|
|
37
36
|
.toEqual(["flex", "items-center", "justify-between"])
|
|
38
37
|
})
|
|
39
38
|
|
|
40
|
-
|
|
39
|
+
test.it("Single quotes", () => {
|
|
41
40
|
const source = `<div class='bg-blue-500 hover:bg-blue-600'>Button</div>`
|
|
42
41
|
const result = extractClassNames(source)
|
|
43
42
|
|
|
44
|
-
|
|
43
|
+
test
|
|
45
44
|
.expect([...result].sort())
|
|
46
45
|
.toEqual(["bg-blue-500", "hover:bg-blue-600"])
|
|
47
46
|
})
|
|
48
47
|
|
|
49
|
-
|
|
48
|
+
test.it("Template literals in JSX", () => {
|
|
50
49
|
const source = `<div className={\`bg-\${color} text-lg\`}>Dynamic</div>`
|
|
51
50
|
const result = extractClassNames(source)
|
|
52
51
|
|
|
53
52
|
// Should extract valid static class names from template literals
|
|
54
|
-
|
|
53
|
+
test
|
|
55
54
|
.expect([...result].sort())
|
|
56
55
|
.toEqual(["text-lg"])
|
|
57
56
|
})
|
|
58
57
|
|
|
59
|
-
|
|
58
|
+
test.it("JSX with quoted strings", () => {
|
|
60
59
|
const source = `<div className={"p-4 m-2"}>Static in braces</div>`
|
|
61
60
|
const result = extractClassNames(source)
|
|
62
61
|
|
|
63
|
-
|
|
62
|
+
test
|
|
64
63
|
.expect([...result].sort())
|
|
65
64
|
.toEqual(["m-2", "p-4"])
|
|
66
65
|
})
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
const source = `<div
|
|
67
|
+
test.it("Multi-line attributes", () => {
|
|
68
|
+
const source = `<div
|
|
70
69
|
className="
|
|
71
|
-
grid
|
|
72
|
-
grid-cols-3
|
|
70
|
+
grid
|
|
71
|
+
grid-cols-3
|
|
73
72
|
gap-4
|
|
74
73
|
"
|
|
75
74
|
>Grid</div>`
|
|
76
75
|
const result = extractClassNames(source)
|
|
77
76
|
|
|
78
|
-
|
|
77
|
+
test
|
|
79
78
|
.expect([...result].sort())
|
|
80
79
|
.toEqual(["gap-4", "grid", "grid-cols-3"])
|
|
81
80
|
})
|
|
82
81
|
|
|
83
|
-
|
|
82
|
+
test.it("Whitespace variations around equals", () => {
|
|
84
83
|
const cases = [
|
|
85
84
|
`<div class="text-sm">Normal</div>`,
|
|
86
85
|
`<div class ="text-md">Space before</div>`,
|
|
@@ -91,37 +90,37 @@ t.describe(`${extractClassNames.name}`, () => {
|
|
|
91
90
|
for (const source of cases) {
|
|
92
91
|
const result = extractClassNames(source)
|
|
93
92
|
|
|
94
|
-
|
|
93
|
+
test
|
|
95
94
|
.expect(result.size)
|
|
96
95
|
.toBe(1)
|
|
97
96
|
}
|
|
98
97
|
})
|
|
99
98
|
|
|
100
|
-
|
|
99
|
+
test.it("Arbitrary value classes", () => {
|
|
101
100
|
const source =
|
|
102
101
|
`<div className="w-[32px] bg-[#ff0000] text-[1.5rem]">Arbitrary</div>`
|
|
103
102
|
const result = extractClassNames(source)
|
|
104
103
|
|
|
105
|
-
|
|
104
|
+
test
|
|
106
105
|
.expect([...result].sort())
|
|
107
106
|
.toEqual(["bg-[#ff0000]", "text-[1.5rem]", "w-[32px]"])
|
|
108
107
|
})
|
|
109
108
|
|
|
110
|
-
|
|
109
|
+
test.it("Fraction classes", () => {
|
|
111
110
|
const source = `<div className="w-1/2 h-3/4">Fractions</div>`
|
|
112
111
|
const result = extractClassNames(source)
|
|
113
112
|
|
|
114
|
-
|
|
113
|
+
test
|
|
115
114
|
.expect([...result].sort())
|
|
116
115
|
.toEqual(["h-3/4", "w-1/2"])
|
|
117
116
|
})
|
|
118
117
|
|
|
119
|
-
|
|
118
|
+
test.it("Complex Tailwind classes", () => {
|
|
120
119
|
const source =
|
|
121
120
|
`<div className="sm:w-1/2 md:w-1/3 lg:w-1/4 hover:bg-gray-100 focus:ring-2">Responsive</div>`
|
|
122
121
|
const result = extractClassNames(source)
|
|
123
122
|
|
|
124
|
-
|
|
123
|
+
test
|
|
125
124
|
.expect([...result].sort())
|
|
126
125
|
.toEqual([
|
|
127
126
|
"focus:ring-2",
|
|
@@ -132,71 +131,71 @@ t.describe(`${extractClassNames.name}`, () => {
|
|
|
132
131
|
])
|
|
133
132
|
})
|
|
134
133
|
|
|
135
|
-
|
|
134
|
+
test.it("Should ignore similar attribute names", () => {
|
|
136
135
|
const source =
|
|
137
136
|
`<div data-class="should-ignore" myclass="also-ignore" class="keep-this">Test</div>`
|
|
138
137
|
const result = extractClassNames(source)
|
|
139
138
|
|
|
140
|
-
|
|
139
|
+
test
|
|
141
140
|
.expect([...result])
|
|
142
141
|
.toEqual(["keep-this"])
|
|
143
142
|
})
|
|
144
143
|
|
|
145
|
-
|
|
144
|
+
test.it("Should handle case sensitivity", () => {
|
|
146
145
|
const source =
|
|
147
146
|
`<div Class="uppercase-class" class="lowercase-class">Mixed case</div>`
|
|
148
147
|
const result = extractClassNames(source)
|
|
149
148
|
|
|
150
149
|
// Our current implementation only matches lowercase 'class'
|
|
151
|
-
|
|
150
|
+
test
|
|
152
151
|
.expect([...result])
|
|
153
152
|
.toEqual(["lowercase-class"])
|
|
154
153
|
})
|
|
155
154
|
|
|
156
|
-
|
|
155
|
+
test.it("Empty class attributes", () => {
|
|
157
156
|
const source = `<div class="" className=''>Empty</div>`
|
|
158
157
|
const result = extractClassNames(source)
|
|
159
158
|
|
|
160
|
-
|
|
159
|
+
test
|
|
161
160
|
.expect(result.size)
|
|
162
161
|
.toBe(0)
|
|
163
162
|
})
|
|
164
163
|
|
|
165
|
-
|
|
164
|
+
test.it("Classes with special characters", () => {
|
|
166
165
|
const source =
|
|
167
166
|
`<div className="group-hover:text-blue-500 peer-focus:ring-2">Special chars</div>`
|
|
168
167
|
const result = extractClassNames(source)
|
|
169
168
|
|
|
170
|
-
|
|
169
|
+
test
|
|
171
170
|
.expect([...result].sort())
|
|
172
171
|
.toEqual(["group-hover:text-blue-500", "peer-focus:ring-2"])
|
|
173
172
|
})
|
|
174
173
|
|
|
175
|
-
|
|
174
|
+
test.it("Should not match classes in comments", () => {
|
|
176
175
|
const source = `
|
|
177
176
|
<!-- <div class="commented-out">Should not match</div> -->
|
|
178
177
|
<div class="real-class">Should match</div>
|
|
179
178
|
`
|
|
180
179
|
const result = extractClassNames(source)
|
|
181
180
|
|
|
182
|
-
|
|
181
|
+
test
|
|
183
182
|
.expect([...result])
|
|
184
183
|
.toEqual(["real-class"])
|
|
185
184
|
})
|
|
186
185
|
|
|
187
|
-
|
|
186
|
+
test.it("Should not match classes in strings", () => {
|
|
188
187
|
const source = `
|
|
189
188
|
const message = "This class='fake-class' should not match";
|
|
190
189
|
<div class="real-class">Real element</div>
|
|
191
190
|
`
|
|
192
191
|
const result = extractClassNames(source)
|
|
193
192
|
|
|
194
|
-
|
|
193
|
+
test
|
|
195
194
|
.expect([...result])
|
|
196
195
|
.toEqual(["real-class"])
|
|
197
196
|
})
|
|
198
197
|
|
|
199
|
-
|
|
198
|
+
test.it("Complex JSX expressions should be ignored", () => {
|
|
200
199
|
const source = `
|
|
201
200
|
<div className={condition ? "conditional-class" : "other-class"}>Conditional</div>
|
|
202
201
|
<div className={\`template-\${variable}\`}>Template</div>
|
|
@@ -206,12 +205,12 @@ t.describe(`${extractClassNames.name}`, () => {
|
|
|
206
205
|
const result = extractClassNames(source)
|
|
207
206
|
|
|
208
207
|
// Only the static class should match with our strict implementation
|
|
209
|
-
|
|
208
|
+
test
|
|
210
209
|
.expect([...result])
|
|
211
210
|
.toEqual(["static-class"])
|
|
212
211
|
})
|
|
213
212
|
|
|
214
|
-
|
|
213
|
+
test.it("Vue.js class bindings should be ignored", () => {
|
|
215
214
|
const source = `
|
|
216
215
|
<div :class="{ 'active': isActive }">Vue object</div>
|
|
217
216
|
<div :class="['base', condition && 'active']">Vue array</div>
|
|
@@ -220,34 +219,34 @@ t.describe(`${extractClassNames.name}`, () => {
|
|
|
220
219
|
const result = extractClassNames(source)
|
|
221
220
|
|
|
222
221
|
// Only static class should match
|
|
223
|
-
|
|
222
|
+
test
|
|
224
223
|
.expect([...result])
|
|
225
224
|
.toEqual(["static-vue-class"])
|
|
226
225
|
})
|
|
227
226
|
|
|
228
|
-
|
|
227
|
+
test.it("Svelte class directives should be ignored", () => {
|
|
229
228
|
const source = `
|
|
230
229
|
<div class:active={condition}>Svelte directive</div>
|
|
231
230
|
<div class="static-svelte-class">Static Svelte</div>
|
|
232
231
|
`
|
|
233
232
|
const result = extractClassNames(source)
|
|
234
233
|
|
|
235
|
-
|
|
234
|
+
test
|
|
236
235
|
.expect([...result])
|
|
237
236
|
.toEqual(["static-svelte-class"])
|
|
238
237
|
})
|
|
239
238
|
|
|
240
|
-
|
|
239
|
+
test.it("Escaped quotes should be handled", () => {
|
|
241
240
|
const source =
|
|
242
241
|
`<div class="text-sm before:content-['Hello']">Escaped quotes</div>`
|
|
243
242
|
const result = extractClassNames(source)
|
|
244
243
|
|
|
245
|
-
|
|
244
|
+
test
|
|
246
245
|
.expect([...result].sort())
|
|
247
246
|
.toEqual(["before:content-['Hello']", "text-sm"])
|
|
248
247
|
})
|
|
249
248
|
|
|
250
|
-
|
|
249
|
+
test.it("Current broad implementation comparison", () => {
|
|
251
250
|
const source = `
|
|
252
251
|
<div class="bg-red-500 text-white">Element</div>
|
|
253
252
|
<p>Some random-text-with-hyphens in content</p>
|
|
@@ -258,27 +257,26 @@ t.describe(`${extractClassNames.name}`, () => {
|
|
|
258
257
|
const strictResult = extractClassNames(source)
|
|
259
258
|
|
|
260
259
|
// Broad should pick up more tokens
|
|
261
|
-
|
|
260
|
+
test
|
|
262
261
|
.expect(broadResult.size)
|
|
263
262
|
.toBeGreaterThan(strictResult.size)
|
|
264
|
-
|
|
265
263
|
// Strict should only have the actual class names
|
|
266
|
-
|
|
264
|
+
test
|
|
267
265
|
.expect([...strictResult].sort())
|
|
268
266
|
.toEqual(["bg-red-500", "text-white"])
|
|
269
267
|
})
|
|
270
268
|
|
|
271
|
-
|
|
269
|
+
test.it("Component names with dots", () => {
|
|
272
270
|
const source =
|
|
273
271
|
`<Toast.Toast class="toast toast-top toast-center fixed top-8 z-10">Content</Toast.Toast>`
|
|
274
272
|
const result = extractClassNames(source)
|
|
275
273
|
|
|
276
|
-
|
|
274
|
+
test
|
|
277
275
|
.expect([...result].sort())
|
|
278
276
|
.toEqual(["fixed", "toast", "toast-center", "toast-top", "top-8", "z-10"])
|
|
279
277
|
})
|
|
280
278
|
|
|
281
|
-
|
|
279
|
+
test.it("Complex component names and attributes", () => {
|
|
282
280
|
const source = `
|
|
283
281
|
<My.Component.Name className="flex items-center">Content</My.Component.Name>
|
|
284
282
|
<Component-with-dashes class="bg-red-500">Content</Component-with-dashes>
|
|
@@ -287,7 +285,7 @@ t.describe(`${extractClassNames.name}`, () => {
|
|
|
287
285
|
`
|
|
288
286
|
const result = extractClassNames(source)
|
|
289
287
|
|
|
290
|
-
|
|
288
|
+
test
|
|
291
289
|
.expect([...result].sort())
|
|
292
290
|
.toEqual([
|
|
293
291
|
"bg-red-500",
|
|
@@ -298,7 +296,7 @@ t.describe(`${extractClassNames.name}`, () => {
|
|
|
298
296
|
])
|
|
299
297
|
})
|
|
300
298
|
|
|
301
|
-
|
|
299
|
+
test.it("Conditional JSX with Toast component", () => {
|
|
302
300
|
const source = `{toastParam !== undefined && (
|
|
303
301
|
<Toast.Toast class="toast toast-top toast-center fixed top-8 z-10">
|
|
304
302
|
<div class="alert alert-success">
|
|
@@ -310,7 +308,7 @@ t.describe(`${extractClassNames.name}`, () => {
|
|
|
310
308
|
)}`
|
|
311
309
|
const result = extractClassNames(source)
|
|
312
310
|
|
|
313
|
-
|
|
311
|
+
test
|
|
314
312
|
.expect([...result].sort())
|
|
315
313
|
.toEqual([
|
|
316
314
|
"alert",
|
|
@@ -324,11 +322,11 @@ t.describe(`${extractClassNames.name}`, () => {
|
|
|
324
322
|
])
|
|
325
323
|
})
|
|
326
324
|
|
|
327
|
-
|
|
325
|
+
test.it("Template literals with expressions", () => {
|
|
328
326
|
const source = `<div class={\`toast \${props.class ?? ""}\`}>Content</div>`
|
|
329
327
|
const result = extractClassNames(source)
|
|
330
328
|
|
|
331
|
-
|
|
329
|
+
test
|
|
332
330
|
.expect([...result].sort())
|
|
333
331
|
.toEqual(["toast"])
|
|
334
332
|
})
|
|
@@ -44,21 +44,22 @@ export const make = (opts?: {
|
|
|
44
44
|
// (imported path) -> (importer paths)
|
|
45
45
|
const importDescendants = new Map<string, Set<string>>()
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
const prepopulateCandidates = opts?.scanPath
|
|
48
|
+
? async () => {
|
|
49
|
+
const candidates = await scanFiles(opts.scanPath!)
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
}
|
|
51
|
+
scannedCandidates.clear()
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
53
|
+
candidates.forEach(candidate => scannedCandidates.add(candidate))
|
|
54
|
+
}
|
|
55
|
+
: null
|
|
56
|
+
|
|
57
|
+
// Track import relationships when dynamically scanning
|
|
58
|
+
// from tailwind entrypoints.
|
|
59
|
+
// As of Bun 1.3 this pathway break for Bun Full-Stack server.
|
|
60
|
+
// Better to pass scanPath explicitly.
|
|
61
|
+
// @see https://github.com/oven-sh/bun/issues/20877
|
|
62
|
+
if (!prepopulateCandidates) {
|
|
62
63
|
builder.onResolve({
|
|
63
64
|
filter: /.*/,
|
|
64
65
|
}, (args) => {
|
|
@@ -130,14 +131,15 @@ export const make = (opts?: {
|
|
|
130
131
|
onDependency: (path) => {},
|
|
131
132
|
})
|
|
132
133
|
|
|
134
|
+
await prepopulateCandidates?.()
|
|
135
|
+
|
|
133
136
|
// wait for other files to be loaded so we can collect class name candidates
|
|
134
137
|
await args.defer()
|
|
135
138
|
|
|
136
|
-
const candidates = new Set<string>()
|
|
139
|
+
const candidates = new Set<string>(scannedCandidates)
|
|
137
140
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
{
|
|
141
|
+
// when we scan a path, we don't need to track candidate tree
|
|
142
|
+
if (!prepopulateCandidates) {
|
|
141
143
|
const pendingModules = [
|
|
142
144
|
// get class name candidates from all modules that import this one
|
|
143
145
|
...(importAncestors.get(args.path) ?? []),
|
|
@@ -295,6 +297,10 @@ async function scanFiles(dir: string): Promise<Set<string>> {
|
|
|
295
297
|
absolute: true,
|
|
296
298
|
})
|
|
297
299
|
) {
|
|
300
|
+
if (filePath.includes("/node_modules/")) {
|
|
301
|
+
continue
|
|
302
|
+
}
|
|
303
|
+
|
|
298
304
|
const contents = await Bun.file(filePath).text()
|
|
299
305
|
const classNames = extractClassNames(contents)
|
|
300
306
|
|
package/src/x/tailwind/plugin.ts
CHANGED
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
import * as Error from "@effect/platform/Error"
|
|
2
|
-
import * as FileSystem from "@effect/platform/FileSystem"
|
|
3
|
-
import * as HttpRouter from "@effect/platform/HttpRouter"
|
|
4
|
-
import * as t from "bun:test"
|
|
5
|
-
import * as Data from "effect/Data"
|
|
6
|
-
import * as Effect from "effect/Effect"
|
|
7
|
-
import * as FileHttpRouter from "./FileHttpRouter.ts"
|
|
8
|
-
import * as FileRouter from "./FileRouter.ts"
|
|
9
|
-
import * as Route from "./Route.ts"
|
|
10
|
-
import * as TestHttpClient from "./TestHttpClient.ts"
|
|
11
|
-
import { effectFn } from "./testing.ts"
|
|
12
|
-
|
|
13
|
-
class CustomError extends Data.TaggedError("CustomError") {}
|
|
14
|
-
|
|
15
|
-
const SampleRoutes = [
|
|
16
|
-
{
|
|
17
|
-
path: "/users",
|
|
18
|
-
load: async () => ({
|
|
19
|
-
default: Route
|
|
20
|
-
.html(Effect.succeed("Users list"))
|
|
21
|
-
.post(Route.html(Effect.succeed("User created"))),
|
|
22
|
-
}),
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
path: "/articles",
|
|
26
|
-
load: async () => ({
|
|
27
|
-
default: Route.html(Effect.succeed("Articles list")),
|
|
28
|
-
}),
|
|
29
|
-
},
|
|
30
|
-
] as const
|
|
31
|
-
|
|
32
|
-
const effect = effectFn()
|
|
33
|
-
|
|
34
|
-
t.it("HttpRouter Requirement and Error types infers", () =>
|
|
35
|
-
effect(function*() {
|
|
36
|
-
const router = yield* FileHttpRouter.make(SampleRoutes)
|
|
37
|
-
|
|
38
|
-
// This should fail to compile if the router type is HttpRouter<any, any>
|
|
39
|
-
const _typeCheck: typeof router extends HttpRouter.HttpRouter<
|
|
40
|
-
Error.SystemError | "PostError" | CustomError,
|
|
41
|
-
FileSystem.FileSystem | "PostService"
|
|
42
|
-
> ? true
|
|
43
|
-
: false = true
|
|
44
|
-
}))
|
|
45
|
-
|
|
46
|
-
t.it("HTTP methods", () =>
|
|
47
|
-
effect(function*() {
|
|
48
|
-
const allMethodsRoute: FileRouter.LazyRoute = {
|
|
49
|
-
path: "/",
|
|
50
|
-
load: async () => ({
|
|
51
|
-
default: Route
|
|
52
|
-
.html(Effect.succeed("GET"))
|
|
53
|
-
.post(Route.html(Effect.succeed("POST")))
|
|
54
|
-
.put(Route.html(Effect.succeed("PUT")))
|
|
55
|
-
.patch(Route.html(Effect.succeed("PATCH")))
|
|
56
|
-
.delete(Route.html(Effect.succeed("DELETE")))
|
|
57
|
-
.options(Route.html(Effect.succeed("OPTIONS")))
|
|
58
|
-
.head(Route.html(Effect.succeed("HEAD"))),
|
|
59
|
-
}),
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const router = yield* FileHttpRouter.make([allMethodsRoute])
|
|
63
|
-
const routesList = Array.from(router.routes)
|
|
64
|
-
|
|
65
|
-
t
|
|
66
|
-
.expect(routesList)
|
|
67
|
-
.toEqual(
|
|
68
|
-
t.expect.arrayContaining([
|
|
69
|
-
t.expect.objectContaining({ path: "/", method: "GET" }),
|
|
70
|
-
t.expect.objectContaining({ path: "/", method: "POST" }),
|
|
71
|
-
t.expect.objectContaining({ path: "/", method: "PUT" }),
|
|
72
|
-
t.expect.objectContaining({ path: "/", method: "PATCH" }),
|
|
73
|
-
t.expect.objectContaining({ path: "/", method: "DELETE" }),
|
|
74
|
-
t.expect.objectContaining({ path: "/", method: "OPTIONS" }),
|
|
75
|
-
t.expect.objectContaining({ path: "/", method: "HEAD" }),
|
|
76
|
-
]),
|
|
77
|
-
)
|
|
78
|
-
}))
|
|
79
|
-
|
|
80
|
-
t.it("router handles requests correctly", () =>
|
|
81
|
-
effect(function*() {
|
|
82
|
-
const router = yield* FileHttpRouter.make(SampleRoutes)
|
|
83
|
-
const client = TestHttpClient.make(router)
|
|
84
|
-
|
|
85
|
-
const getUsersResponse = yield* client.get("/users")
|
|
86
|
-
|
|
87
|
-
t
|
|
88
|
-
.expect(getUsersResponse.status)
|
|
89
|
-
.toBe(200)
|
|
90
|
-
|
|
91
|
-
t
|
|
92
|
-
.expect(yield* getUsersResponse.text)
|
|
93
|
-
.toBe("Users list")
|
|
94
|
-
|
|
95
|
-
const postUsersResponse = yield* client.post("/users")
|
|
96
|
-
|
|
97
|
-
t
|
|
98
|
-
.expect(postUsersResponse.status)
|
|
99
|
-
.toBe(200)
|
|
100
|
-
|
|
101
|
-
t
|
|
102
|
-
.expect(yield* postUsersResponse.text)
|
|
103
|
-
.toBe("User created")
|
|
104
|
-
}))
|
|
105
|
-
|
|
106
|
-
t.it(
|
|
107
|
-
"handles routes with special characters (tilde and hyphen)",
|
|
108
|
-
() =>
|
|
109
|
-
effect(function*() {
|
|
110
|
-
const specialCharRoutes: FileRouter.LazyRoute[] = [
|
|
111
|
-
{
|
|
112
|
-
path: "/api-v1",
|
|
113
|
-
load: async () => ({
|
|
114
|
-
default: Route.text("API v1"),
|
|
115
|
-
}),
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
path: "/files~backup",
|
|
119
|
-
load: async () => ({
|
|
120
|
-
default: Route.text("Backup files"),
|
|
121
|
-
}),
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
path: "/test-route~temp",
|
|
125
|
-
load: async () => ({
|
|
126
|
-
default: Route.post(Route.text("Test route")),
|
|
127
|
-
}),
|
|
128
|
-
},
|
|
129
|
-
]
|
|
130
|
-
|
|
131
|
-
const router = yield* FileHttpRouter.make(specialCharRoutes)
|
|
132
|
-
const client = TestHttpClient.make(router)
|
|
133
|
-
|
|
134
|
-
const apiResponse = yield* client.get("/api-v1")
|
|
135
|
-
|
|
136
|
-
t
|
|
137
|
-
.expect(apiResponse.status)
|
|
138
|
-
.toBe(200)
|
|
139
|
-
|
|
140
|
-
t
|
|
141
|
-
.expect(yield* apiResponse.text)
|
|
142
|
-
.toBe("API v1")
|
|
143
|
-
|
|
144
|
-
const backupResponse = yield* client.get("/files~backup")
|
|
145
|
-
|
|
146
|
-
t
|
|
147
|
-
.expect(backupResponse.status)
|
|
148
|
-
.toBe(200)
|
|
149
|
-
|
|
150
|
-
t
|
|
151
|
-
.expect(yield* backupResponse.text)
|
|
152
|
-
.toBe("Backup files")
|
|
153
|
-
|
|
154
|
-
const testResponse = yield* client.post("/test-route~temp")
|
|
155
|
-
|
|
156
|
-
t
|
|
157
|
-
.expect(testResponse.status)
|
|
158
|
-
.toBe(200)
|
|
159
|
-
|
|
160
|
-
t
|
|
161
|
-
.expect(yield* testResponse.text)
|
|
162
|
-
.toBe("Test route")
|
|
163
|
-
}),
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
t.it(
|
|
167
|
-
"layer routes can wrap inner routes using next()",
|
|
168
|
-
() =>
|
|
169
|
-
effect(function*() {
|
|
170
|
-
const routeWithLayer: FileRouter.LazyRoute = {
|
|
171
|
-
path: "/page",
|
|
172
|
-
load: async () => ({
|
|
173
|
-
default: Route.html(Effect.succeed("<h1>Page Content</h1>")),
|
|
174
|
-
}),
|
|
175
|
-
layers: [
|
|
176
|
-
async () => ({
|
|
177
|
-
default: Route.layer(
|
|
178
|
-
Route.html(function*(context) {
|
|
179
|
-
const innerContent = yield* context.next()
|
|
180
|
-
return `<html><body>${innerContent}</body></html>`
|
|
181
|
-
}),
|
|
182
|
-
),
|
|
183
|
-
}),
|
|
184
|
-
],
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const router = yield* FileHttpRouter.make([routeWithLayer])
|
|
188
|
-
const client = TestHttpClient.make(router)
|
|
189
|
-
|
|
190
|
-
const response = yield* client.get("/page")
|
|
191
|
-
|
|
192
|
-
t.expect(response.status).toBe(200)
|
|
193
|
-
|
|
194
|
-
const html = yield* response.text
|
|
195
|
-
|
|
196
|
-
t.expect(html).toBe("<html><body><h1>Page Content</h1></body></html>")
|
|
197
|
-
}),
|
|
198
|
-
)
|
|
199
|
-
|
|
200
|
-
t.it("nested layers compose correctly with next()", () =>
|
|
201
|
-
effect(function*() {
|
|
202
|
-
const routeWithNestedLayers: FileRouter.LazyRoute = {
|
|
203
|
-
path: "/nested",
|
|
204
|
-
load: async () => ({
|
|
205
|
-
default: Route.html(Effect.succeed("content")),
|
|
206
|
-
}),
|
|
207
|
-
layers: [
|
|
208
|
-
async () => ({
|
|
209
|
-
default: Route.layer(
|
|
210
|
-
Route.html(function*(context) {
|
|
211
|
-
const inner = yield* context.next()
|
|
212
|
-
return `<div class="outer">${inner}</div>`
|
|
213
|
-
}),
|
|
214
|
-
),
|
|
215
|
-
}),
|
|
216
|
-
async () => ({
|
|
217
|
-
default: Route.layer(
|
|
218
|
-
Route.html(function*(context) {
|
|
219
|
-
const inner = yield* context.next()
|
|
220
|
-
return `<div class="inner">${inner}</div>`
|
|
221
|
-
}),
|
|
222
|
-
),
|
|
223
|
-
}),
|
|
224
|
-
],
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const router = yield* FileHttpRouter.make([routeWithNestedLayers])
|
|
228
|
-
const client = TestHttpClient.make(router)
|
|
229
|
-
|
|
230
|
-
const response = yield* client.get("/nested")
|
|
231
|
-
|
|
232
|
-
t.expect(response.status).toBe(200)
|
|
233
|
-
|
|
234
|
-
const html = yield* response.text
|
|
235
|
-
|
|
236
|
-
t.expect(html).toBe(
|
|
237
|
-
"<div class=\"outer\"><div class=\"inner\">content</div></div>",
|
|
238
|
-
)
|
|
239
|
-
}))
|