@kexi/vibe-native 0.12.5

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/README.md ADDED
@@ -0,0 +1,156 @@
1
+ # @kexi/vibe-native
2
+
3
+ Native Copy-on-Write (CoW) file cloning for Node.js using Rust (napi-rs).
4
+
5
+ ## Features
6
+
7
+ - **macOS**: Uses `clonefile()` syscall on APFS filesystems
8
+ - **Linux**: Uses `FICLONE` ioctl on Btrfs/XFS filesystems
9
+ - **Zero-copy**: Creates instant file clones without copying data
10
+ - **Async/Sync**: Both async and sync APIs available
11
+ - **Type-safe**: Full TypeScript support with auto-generated types
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @kexi/vibe-native
17
+ # or
18
+ pnpm add @kexi/vibe-native
19
+ ```
20
+
21
+ ## Platform Support
22
+
23
+ | Platform | Architecture | Filesystem |
24
+ |----------|--------------|------------|
25
+ | macOS | x64, arm64 | APFS |
26
+ | Linux | x64, arm64 | Btrfs, XFS (with reflink) |
27
+
28
+ ## Usage
29
+
30
+ ```typescript
31
+ import {
32
+ cloneAsync,
33
+ cloneSync,
34
+ isAvailable,
35
+ supportsDirectory,
36
+ getPlatform,
37
+ } from "@kexi/vibe-native";
38
+
39
+ // Check if native cloning is available
40
+ if (isAvailable()) {
41
+ console.log(`Platform: ${getPlatform()}`);
42
+ console.log(`Directory cloning: ${supportsDirectory()}`);
43
+
44
+ // Async cloning (recommended)
45
+ await cloneAsync("/path/to/source", "/path/to/dest");
46
+
47
+ // Sync cloning
48
+ cloneSync("/path/to/source", "/path/to/dest");
49
+ }
50
+ ```
51
+
52
+ ## API
53
+
54
+ ### `cloneAsync(src: string, dest: string): Promise<void>`
55
+
56
+ Clone a file or directory asynchronously using native Copy-on-Write.
57
+
58
+ ### `cloneSync(src: string, dest: string): void`
59
+
60
+ Clone a file or directory synchronously using native Copy-on-Write.
61
+
62
+ ### `clone(src: string, dest: string): void`
63
+
64
+ Alias for `cloneSync` (for backward compatibility).
65
+
66
+ ### `isAvailable(): boolean`
67
+
68
+ Check if native clone operations are available on the current platform.
69
+
70
+ ### `supportsDirectory(): boolean`
71
+
72
+ Check if directory cloning is supported:
73
+ - macOS (`clonefile`): `true`
74
+ - Linux (`FICLONE`): `false` (files only)
75
+
76
+ ### `getPlatform(): "darwin" | "linux" | "unknown"`
77
+
78
+ Get the current platform identifier.
79
+
80
+ ## Security
81
+
82
+ ### File Type Validation
83
+
84
+ Only regular files and directories are allowed. The following are rejected:
85
+ - Symlinks (to prevent path traversal)
86
+ - Device files (block/character devices)
87
+ - Sockets
88
+ - FIFOs (named pipes)
89
+
90
+ ### Path Handling
91
+
92
+ This library does not perform path normalization or validation. Callers should:
93
+ - Validate paths before calling clone functions
94
+ - Use `path.resolve()` to normalize relative paths
95
+ - Check for symlinks if path traversal is a concern
96
+
97
+ ```typescript
98
+ import { resolve, dirname } from "path";
99
+ import { realpath } from "fs/promises";
100
+
101
+ // Example: Safe path handling
102
+ async function safeClone(src: string, dest: string, allowedDir: string) {
103
+ const resolvedSrc = await realpath(resolve(src));
104
+ const resolvedDest = resolve(dest);
105
+
106
+ // Verify paths are within allowed directory
107
+ if (!resolvedSrc.startsWith(allowedDir)) {
108
+ throw new Error("Source path outside allowed directory");
109
+ }
110
+ if (!resolvedDest.startsWith(allowedDir)) {
111
+ throw new Error("Destination path outside allowed directory");
112
+ }
113
+
114
+ await cloneAsync(resolvedSrc, resolvedDest);
115
+ }
116
+ ```
117
+
118
+ ## Error Handling
119
+
120
+ Errors include system errno information for debugging:
121
+
122
+ ```typescript
123
+ try {
124
+ await cloneAsync("/nonexistent", "/dest");
125
+ } catch (error) {
126
+ // Error: open source failed: No such file or directory (errno 2)
127
+ console.error(error.message);
128
+ }
129
+ ```
130
+
131
+ ## Filesystem Requirements
132
+
133
+ ### macOS
134
+ - **APFS** is required for `clonefile()` to work
135
+ - HFS+ and other filesystems will return `ENOTSUP`
136
+
137
+ ### Linux
138
+ - **Btrfs** or **XFS** (with reflink support) is required
139
+ - ext4 and other filesystems will return `EOPNOTSUPP`
140
+
141
+ ## Building from Source
142
+
143
+ ```bash
144
+ # Install dependencies
145
+ pnpm install
146
+
147
+ # Build (requires Rust toolchain)
148
+ pnpm run build
149
+
150
+ # Run tests
151
+ cargo test
152
+ ```
153
+
154
+ ## License
155
+
156
+ Apache-2.0
package/index.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ /* auto-generated by NAPI-RS */
5
+
6
+ /**
7
+ * Clone a file or directory synchronously using native Copy-on-Write
8
+ *
9
+ * - macOS: Uses clonefile() which supports both files and directories
10
+ * - Linux: Uses FICLONE ioctl which only supports files
11
+ */
12
+ export declare function cloneSync(src: string, dest: string): void
13
+ /**
14
+ * Clone a file or directory asynchronously using native Copy-on-Write
15
+ *
16
+ * - macOS: Uses clonefile() which supports both files and directories
17
+ * - Linux: Uses FICLONE ioctl which only supports files
18
+ */
19
+ export declare function cloneAsync(src: string, dest: string): Promise<void>
20
+ /** Clone a file or directory synchronously (alias for cloneSync for backward compatibility) */
21
+ export declare function clone(src: string, dest: string): void
22
+ /** Check if native clone operations are available */
23
+ export declare function isAvailable(): boolean
24
+ /**
25
+ * Check if directory cloning is supported
26
+ * - macOS clonefile: true (supports directories)
27
+ * - Linux FICLONE: false (files only)
28
+ */
29
+ export declare function supportsDirectory(): boolean
30
+ /**
31
+ * Get the current platform
32
+ * Returns "darwin" or "linux"
33
+ */
34
+ export declare function getPlatform(): string
package/index.js ADDED
@@ -0,0 +1,320 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ /* prettier-ignore */
4
+
5
+ /* auto-generated by NAPI-RS */
6
+
7
+ const { existsSync, readFileSync } = require('fs')
8
+ const { join } = require('path')
9
+
10
+ const { platform, arch } = process
11
+
12
+ let nativeBinding = null
13
+ let localFileExisted = false
14
+ let loadError = null
15
+
16
+ function isMusl() {
17
+ // For Node 10
18
+ if (!process.report || typeof process.report.getReport !== 'function') {
19
+ try {
20
+ const lddPath = require('child_process').execSync('which ldd').toString().trim()
21
+ return readFileSync(lddPath, 'utf8').includes('musl')
22
+ } catch (e) {
23
+ return true
24
+ }
25
+ } else {
26
+ const { glibcVersionRuntime } = process.report.getReport().header
27
+ return !glibcVersionRuntime
28
+ }
29
+ }
30
+
31
+ switch (platform) {
32
+ case 'android':
33
+ switch (arch) {
34
+ case 'arm64':
35
+ localFileExisted = existsSync(join(__dirname, 'vibe-native.android-arm64.node'))
36
+ try {
37
+ if (localFileExisted) {
38
+ nativeBinding = require('./vibe-native.android-arm64.node')
39
+ } else {
40
+ nativeBinding = require('@kexi/vibe-native-android-arm64')
41
+ }
42
+ } catch (e) {
43
+ loadError = e
44
+ }
45
+ break
46
+ case 'arm':
47
+ localFileExisted = existsSync(join(__dirname, 'vibe-native.android-arm-eabi.node'))
48
+ try {
49
+ if (localFileExisted) {
50
+ nativeBinding = require('./vibe-native.android-arm-eabi.node')
51
+ } else {
52
+ nativeBinding = require('@kexi/vibe-native-android-arm-eabi')
53
+ }
54
+ } catch (e) {
55
+ loadError = e
56
+ }
57
+ break
58
+ default:
59
+ throw new Error(`Unsupported architecture on Android ${arch}`)
60
+ }
61
+ break
62
+ case 'win32':
63
+ switch (arch) {
64
+ case 'x64':
65
+ localFileExisted = existsSync(
66
+ join(__dirname, 'vibe-native.win32-x64-msvc.node')
67
+ )
68
+ try {
69
+ if (localFileExisted) {
70
+ nativeBinding = require('./vibe-native.win32-x64-msvc.node')
71
+ } else {
72
+ nativeBinding = require('@kexi/vibe-native-win32-x64-msvc')
73
+ }
74
+ } catch (e) {
75
+ loadError = e
76
+ }
77
+ break
78
+ case 'ia32':
79
+ localFileExisted = existsSync(
80
+ join(__dirname, 'vibe-native.win32-ia32-msvc.node')
81
+ )
82
+ try {
83
+ if (localFileExisted) {
84
+ nativeBinding = require('./vibe-native.win32-ia32-msvc.node')
85
+ } else {
86
+ nativeBinding = require('@kexi/vibe-native-win32-ia32-msvc')
87
+ }
88
+ } catch (e) {
89
+ loadError = e
90
+ }
91
+ break
92
+ case 'arm64':
93
+ localFileExisted = existsSync(
94
+ join(__dirname, 'vibe-native.win32-arm64-msvc.node')
95
+ )
96
+ try {
97
+ if (localFileExisted) {
98
+ nativeBinding = require('./vibe-native.win32-arm64-msvc.node')
99
+ } else {
100
+ nativeBinding = require('@kexi/vibe-native-win32-arm64-msvc')
101
+ }
102
+ } catch (e) {
103
+ loadError = e
104
+ }
105
+ break
106
+ default:
107
+ throw new Error(`Unsupported architecture on Windows: ${arch}`)
108
+ }
109
+ break
110
+ case 'darwin':
111
+ localFileExisted = existsSync(join(__dirname, 'vibe-native.darwin-universal.node'))
112
+ try {
113
+ if (localFileExisted) {
114
+ nativeBinding = require('./vibe-native.darwin-universal.node')
115
+ } else {
116
+ nativeBinding = require('@kexi/vibe-native-darwin-universal')
117
+ }
118
+ break
119
+ } catch {}
120
+ switch (arch) {
121
+ case 'x64':
122
+ localFileExisted = existsSync(join(__dirname, 'vibe-native.darwin-x64.node'))
123
+ try {
124
+ if (localFileExisted) {
125
+ nativeBinding = require('./vibe-native.darwin-x64.node')
126
+ } else {
127
+ nativeBinding = require('@kexi/vibe-native-darwin-x64')
128
+ }
129
+ } catch (e) {
130
+ loadError = e
131
+ }
132
+ break
133
+ case 'arm64':
134
+ localFileExisted = existsSync(
135
+ join(__dirname, 'vibe-native.darwin-arm64.node')
136
+ )
137
+ try {
138
+ if (localFileExisted) {
139
+ nativeBinding = require('./vibe-native.darwin-arm64.node')
140
+ } else {
141
+ nativeBinding = require('@kexi/vibe-native-darwin-arm64')
142
+ }
143
+ } catch (e) {
144
+ loadError = e
145
+ }
146
+ break
147
+ default:
148
+ throw new Error(`Unsupported architecture on macOS: ${arch}`)
149
+ }
150
+ break
151
+ case 'freebsd':
152
+ if (arch !== 'x64') {
153
+ throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
154
+ }
155
+ localFileExisted = existsSync(join(__dirname, 'vibe-native.freebsd-x64.node'))
156
+ try {
157
+ if (localFileExisted) {
158
+ nativeBinding = require('./vibe-native.freebsd-x64.node')
159
+ } else {
160
+ nativeBinding = require('@kexi/vibe-native-freebsd-x64')
161
+ }
162
+ } catch (e) {
163
+ loadError = e
164
+ }
165
+ break
166
+ case 'linux':
167
+ switch (arch) {
168
+ case 'x64':
169
+ if (isMusl()) {
170
+ localFileExisted = existsSync(
171
+ join(__dirname, 'vibe-native.linux-x64-musl.node')
172
+ )
173
+ try {
174
+ if (localFileExisted) {
175
+ nativeBinding = require('./vibe-native.linux-x64-musl.node')
176
+ } else {
177
+ nativeBinding = require('@kexi/vibe-native-linux-x64-musl')
178
+ }
179
+ } catch (e) {
180
+ loadError = e
181
+ }
182
+ } else {
183
+ localFileExisted = existsSync(
184
+ join(__dirname, 'vibe-native.linux-x64-gnu.node')
185
+ )
186
+ try {
187
+ if (localFileExisted) {
188
+ nativeBinding = require('./vibe-native.linux-x64-gnu.node')
189
+ } else {
190
+ nativeBinding = require('@kexi/vibe-native-linux-x64-gnu')
191
+ }
192
+ } catch (e) {
193
+ loadError = e
194
+ }
195
+ }
196
+ break
197
+ case 'arm64':
198
+ if (isMusl()) {
199
+ localFileExisted = existsSync(
200
+ join(__dirname, 'vibe-native.linux-arm64-musl.node')
201
+ )
202
+ try {
203
+ if (localFileExisted) {
204
+ nativeBinding = require('./vibe-native.linux-arm64-musl.node')
205
+ } else {
206
+ nativeBinding = require('@kexi/vibe-native-linux-arm64-musl')
207
+ }
208
+ } catch (e) {
209
+ loadError = e
210
+ }
211
+ } else {
212
+ localFileExisted = existsSync(
213
+ join(__dirname, 'vibe-native.linux-arm64-gnu.node')
214
+ )
215
+ try {
216
+ if (localFileExisted) {
217
+ nativeBinding = require('./vibe-native.linux-arm64-gnu.node')
218
+ } else {
219
+ nativeBinding = require('@kexi/vibe-native-linux-arm64-gnu')
220
+ }
221
+ } catch (e) {
222
+ loadError = e
223
+ }
224
+ }
225
+ break
226
+ case 'arm':
227
+ if (isMusl()) {
228
+ localFileExisted = existsSync(
229
+ join(__dirname, 'vibe-native.linux-arm-musleabihf.node')
230
+ )
231
+ try {
232
+ if (localFileExisted) {
233
+ nativeBinding = require('./vibe-native.linux-arm-musleabihf.node')
234
+ } else {
235
+ nativeBinding = require('@kexi/vibe-native-linux-arm-musleabihf')
236
+ }
237
+ } catch (e) {
238
+ loadError = e
239
+ }
240
+ } else {
241
+ localFileExisted = existsSync(
242
+ join(__dirname, 'vibe-native.linux-arm-gnueabihf.node')
243
+ )
244
+ try {
245
+ if (localFileExisted) {
246
+ nativeBinding = require('./vibe-native.linux-arm-gnueabihf.node')
247
+ } else {
248
+ nativeBinding = require('@kexi/vibe-native-linux-arm-gnueabihf')
249
+ }
250
+ } catch (e) {
251
+ loadError = e
252
+ }
253
+ }
254
+ break
255
+ case 'riscv64':
256
+ if (isMusl()) {
257
+ localFileExisted = existsSync(
258
+ join(__dirname, 'vibe-native.linux-riscv64-musl.node')
259
+ )
260
+ try {
261
+ if (localFileExisted) {
262
+ nativeBinding = require('./vibe-native.linux-riscv64-musl.node')
263
+ } else {
264
+ nativeBinding = require('@kexi/vibe-native-linux-riscv64-musl')
265
+ }
266
+ } catch (e) {
267
+ loadError = e
268
+ }
269
+ } else {
270
+ localFileExisted = existsSync(
271
+ join(__dirname, 'vibe-native.linux-riscv64-gnu.node')
272
+ )
273
+ try {
274
+ if (localFileExisted) {
275
+ nativeBinding = require('./vibe-native.linux-riscv64-gnu.node')
276
+ } else {
277
+ nativeBinding = require('@kexi/vibe-native-linux-riscv64-gnu')
278
+ }
279
+ } catch (e) {
280
+ loadError = e
281
+ }
282
+ }
283
+ break
284
+ case 's390x':
285
+ localFileExisted = existsSync(
286
+ join(__dirname, 'vibe-native.linux-s390x-gnu.node')
287
+ )
288
+ try {
289
+ if (localFileExisted) {
290
+ nativeBinding = require('./vibe-native.linux-s390x-gnu.node')
291
+ } else {
292
+ nativeBinding = require('@kexi/vibe-native-linux-s390x-gnu')
293
+ }
294
+ } catch (e) {
295
+ loadError = e
296
+ }
297
+ break
298
+ default:
299
+ throw new Error(`Unsupported architecture on Linux: ${arch}`)
300
+ }
301
+ break
302
+ default:
303
+ throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
304
+ }
305
+
306
+ if (!nativeBinding) {
307
+ if (loadError) {
308
+ throw loadError
309
+ }
310
+ throw new Error(`Failed to load native binding`)
311
+ }
312
+
313
+ const { cloneSync, cloneAsync, clone, isAvailable, supportsDirectory, getPlatform } = nativeBinding
314
+
315
+ module.exports.cloneSync = cloneSync
316
+ module.exports.cloneAsync = cloneAsync
317
+ module.exports.clone = clone
318
+ module.exports.isAvailable = isAvailable
319
+ module.exports.supportsDirectory = supportsDirectory
320
+ module.exports.getPlatform = getPlatform
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@kexi/vibe-native",
3
+ "version": "0.12.5",
4
+ "description": "Native clone operations for vibe CLI (clonefile on macOS, FICLONE on Linux)",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "napi": {
8
+ "name": "vibe-native",
9
+ "triples": {
10
+ "defaults": false,
11
+ "additional": [
12
+ "x86_64-apple-darwin",
13
+ "aarch64-apple-darwin",
14
+ "x86_64-unknown-linux-gnu",
15
+ "aarch64-unknown-linux-gnu"
16
+ ]
17
+ }
18
+ },
19
+ "files": [
20
+ "index.js",
21
+ "index.d.ts",
22
+ "README.md",
23
+ "*.node"
24
+ ],
25
+ "scripts": {
26
+ "artifacts": "napi artifacts",
27
+ "build": "napi build --platform --release",
28
+ "build:debug": "napi build --platform",
29
+ "prepublishOnly": "napi prepublish -t npm",
30
+ "version": "napi version"
31
+ },
32
+ "devDependencies": {
33
+ "@napi-rs/cli": "^2.18.0"
34
+ },
35
+ "engines": {
36
+ "node": ">=18.0.0"
37
+ },
38
+ "os": [
39
+ "darwin",
40
+ "linux"
41
+ ],
42
+ "cpu": [
43
+ "x64",
44
+ "arm64"
45
+ ],
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/kexi/vibe.git",
49
+ "directory": "packages/@kexi/vibe-native"
50
+ },
51
+ "keywords": [
52
+ "clonefile",
53
+ "ficlone",
54
+ "cow",
55
+ "copy-on-write",
56
+ "native",
57
+ "napi-rs"
58
+ ],
59
+ "author": "kexi",
60
+ "license": "Apache-2.0",
61
+ "publishConfig": {
62
+ "access": "public",
63
+ "registry": "https://registry.npmjs.org/"
64
+ }
65
+ }
Binary file
Binary file
Binary file
Binary file