@craft-native/svelte 0.0.490 → 0.0.491
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 +4 -4
- package/src/peer-range.test.ts +123 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@craft-native/svelte",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.491",
|
|
4
4
|
"homepage": "https://github.com/craft-native/craft/tree/main/packages/svelte#readme",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -41,14 +41,14 @@
|
|
|
41
41
|
"author": "Craft Team",
|
|
42
42
|
"license": "MIT",
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"svelte": "^3.0.0 || ^4.0.0"
|
|
44
|
+
"svelte": "^3.0.0 || ^4.0.0 || ^5.0.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"svelte": "^
|
|
47
|
+
"svelte": "^5.0.0",
|
|
48
48
|
"tsup": "^8.0.0",
|
|
49
49
|
"typescript": "7.1.0-dev.20261011.1"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"craft-native": "0.0.
|
|
52
|
+
"craft-native": "0.0.491"
|
|
53
53
|
}
|
|
54
54
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test'
|
|
2
|
+
import { readFileSync, readdirSync } from 'node:fs'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The peer range admits every Svelte that provides what this adapter uses.
|
|
7
|
+
*
|
|
8
|
+
* The range used to stop at `^4.0.0`. Six of the Svelte advisories on the
|
|
9
|
+
* dependency dashboard (#399) are fixed only in 5.51.5, 5.53.5 and 5.55.7, so
|
|
10
|
+
* a consumer honouring that range could not reach a fixed Svelte at all —
|
|
11
|
+
* the cap, not the dependency, was what held them on a vulnerable major.
|
|
12
|
+
*
|
|
13
|
+
* Nothing technical required the cap. This adapter's entire Svelte surface is
|
|
14
|
+
* `writable`, `derived` and `get` from `svelte/store`, and Svelte 5 still
|
|
15
|
+
* exports all three — checked against the published 5.57.3 tarball, whose
|
|
16
|
+
* `src/store/index-client.js` re-exports `derived`, `get`, `readable`,
|
|
17
|
+
* `readonly` and `writable` from `shared/index.js`.
|
|
18
|
+
*
|
|
19
|
+
* So this test ties the range to the surface rather than to a version
|
|
20
|
+
* someone picked. If the adapter starts importing runes, the compiler, or
|
|
21
|
+
* anything outside `svelte/store`, that assumption no longer holds and this
|
|
22
|
+
* fails — which is the moment to re-examine the range, not after a consumer's
|
|
23
|
+
* build breaks.
|
|
24
|
+
*/
|
|
25
|
+
const root = join(import.meta.dir, '..')
|
|
26
|
+
|
|
27
|
+
/** Every `… from 'svelte…'` specifier in the adapter's sources. */
|
|
28
|
+
function svelteImports(): { specifier: string, named: string[] }[] {
|
|
29
|
+
const found: { specifier: string, named: string[] }[] = []
|
|
30
|
+
const walk = (directory: string): void => {
|
|
31
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
32
|
+
const path = join(directory, entry.name)
|
|
33
|
+
if (entry.isDirectory()) { walk(path); continue }
|
|
34
|
+
if (!entry.name.endsWith('.ts')) continue
|
|
35
|
+
const source = readFileSync(path, 'utf8')
|
|
36
|
+
for (const match of source.matchAll(/import\s+(type\s+)?\{([^}]*)\}\s+from\s+'(svelte[^']*)'/g)) {
|
|
37
|
+
found.push({ specifier: match[3]!, named: match[2]!.split(',').map(name => name.trim()).filter(Boolean) })
|
|
38
|
+
}
|
|
39
|
+
for (const match of source.matchAll(/import\s+[^{][^\n]*from\s+'(svelte[^']*)'/g)) {
|
|
40
|
+
found.push({ specifier: match[1]!, named: [] })
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
walk(join(root, 'src'))
|
|
45
|
+
return found
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* What `svelte/store` has exported continuously through Svelte 3, 4 and 5.
|
|
50
|
+
*
|
|
51
|
+
* Svelte 3 and 4 cannot be resolved from here — only one version is
|
|
52
|
+
* installed — so this half is a claim. The half that matters is not:
|
|
53
|
+
* `the adapter's imports exist in the installed Svelte` below asks the
|
|
54
|
+
* installed package what it actually exports, so widening the range cannot
|
|
55
|
+
* rest on my reading of a changelog.
|
|
56
|
+
*/
|
|
57
|
+
const storeSurface = new Set(['writable', 'readable', 'derived', 'readonly', 'get', 'Readable', 'Writable', 'Unsubscriber', 'StartStopNotifier'])
|
|
58
|
+
|
|
59
|
+
test('the adapter only uses svelte/store', () => {
|
|
60
|
+
const imports = svelteImports()
|
|
61
|
+
expect(imports.length, 'the adapter must import something from Svelte').toBeGreaterThan(0)
|
|
62
|
+
const specifiers = [...new Set(imports.map(entry => entry.specifier))]
|
|
63
|
+
// `svelte` itself, `svelte/compiler`, `svelte/internal` and the rune APIs
|
|
64
|
+
// are all majors-sensitive in a way `svelte/store` is not.
|
|
65
|
+
expect(specifiers, `the peer range below assumes svelte/store only, found ${specifiers.join(', ')}`)
|
|
66
|
+
.toEqual(['svelte/store'])
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('every name it imports survived into Svelte 5', () => {
|
|
70
|
+
for (const entry of svelteImports()) {
|
|
71
|
+
for (const name of entry.named) {
|
|
72
|
+
expect(storeSurface.has(name), `${name} is not in the store surface this range was widened on`).toBe(true)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('the peer range reaches the majors where the advisories are fixed', () => {
|
|
78
|
+
const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')) as {
|
|
79
|
+
peerDependencies?: Record<string, string>
|
|
80
|
+
}
|
|
81
|
+
const range = manifest.peerDependencies?.svelte
|
|
82
|
+
expect(range, 'the adapter must declare a Svelte peer range').toBeString()
|
|
83
|
+
// The point of the change: a consumer can resolve a Svelte 5 that carries
|
|
84
|
+
// the fixes for GHSA-crpf-4hrx-3jrp, -f7gr-6p89-r883, -m56q-vw4c-c2cp,
|
|
85
|
+
// -phwv-c562-gvmh, -pr6f-5x2q-rwfp and -rcqx-6q8c-2c42.
|
|
86
|
+
expect(range, 'the range must not cap below the majors carrying the fixes').toContain('^5.0.0')
|
|
87
|
+
// Without dropping the consumers who are still on 3 or 4.
|
|
88
|
+
expect(range).toContain('^3.0.0')
|
|
89
|
+
expect(range).toContain('^4.0.0')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('the adapter builds against the major its range points consumers at', async () => {
|
|
93
|
+
// The defect this closes: the peer range was widened to `^5.0.0` while the
|
|
94
|
+
// devDependency stayed `^4.0.0`, so nothing ever built or tested the adapter
|
|
95
|
+
// against the major being advertised — and the installed Svelte still
|
|
96
|
+
// carried the six advisories the widening was for (#399).
|
|
97
|
+
const installed = JSON.parse(readFileSync(join(root, '../../node_modules/svelte/package.json'), 'utf8')) as { version: string }
|
|
98
|
+
const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')) as {
|
|
99
|
+
devDependencies?: Record<string, string>
|
|
100
|
+
peerDependencies?: Record<string, string>
|
|
101
|
+
}
|
|
102
|
+
const range = manifest.peerDependencies?.svelte ?? ''
|
|
103
|
+
const highest = [...range.matchAll(/\^(\d+)\./g)].map(found => Number(found[1])).sort((a, b) => b - a)[0]
|
|
104
|
+
expect(highest, 'the peer range must name a major').toBeGreaterThan(0)
|
|
105
|
+
expect(Number(installed.version.split('.')[0]),
|
|
106
|
+
`the range offers ^${highest}.x but the adapter is built against Svelte ${installed.version}`).toBe(highest)
|
|
107
|
+
// And the devDependency, not just whatever happens to be hoisted.
|
|
108
|
+
expect(manifest.devDependencies?.svelte).toBe(`^${highest}.0.0`)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test("the adapter's imports exist in the installed Svelte", async () => {
|
|
112
|
+
// Asked of the package rather than asserted from a list, so the range rests
|
|
113
|
+
// on what Svelte exports instead of on what I believed it exports.
|
|
114
|
+
const store = await import('svelte/store') as Record<string, unknown>
|
|
115
|
+
const exported = new Set(Object.keys(store))
|
|
116
|
+
expect(exported.size, 'svelte/store must export something').toBeGreaterThan(0)
|
|
117
|
+
const wanted = [...new Set(svelteImports().flatMap(entry => entry.named))]
|
|
118
|
+
// Type-only names are not runtime exports.
|
|
119
|
+
.filter(name => !/^[A-Z]/.test(name))
|
|
120
|
+
expect(wanted.length, 'the adapter must import runtime names').toBeGreaterThan(0)
|
|
121
|
+
const missing = wanted.filter(name => !exported.has(name))
|
|
122
|
+
expect(missing, `the installed Svelte does not export ${missing.join(', ')}`).toEqual([])
|
|
123
|
+
})
|