@gjsify/oxfmt-native 0.22.0 → 0.24.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @gjsify/oxfmt-native
2
2
 
3
- A native Rust cdylib + Vala/GObject bridge that wraps oxc's formatter ([oxfmt](https://oxc.rs/docs/guide/usage/formatter)) and exposes it to GJS via `gi://`. This is the formatter engine used by `gjsify format` / `gjsify fix` under GJS — npm's `oxfmt` is a Rust N-API binary that cannot load in GJS, so this bridge is how gjsify formats without a Node runtime. The bridge links oxfmt's pure-Rust CLI core, so the full CLI surface runs in-process: `.oxfmtrc(.json)` + `.editorconfig` resolution, ignore handling, parallel file walking, `--write` / `--check` / `--list-different`. Ships prebuilt `.so` + `.gir` + `.typelib` for Linux.
3
+ A native Rust cdylib + Vala/GObject bridge that wraps oxc's formatter ([oxfmt](https://oxc.rs/docs/guide/usage/formatter)) and exposes it to GJS via `gi://`. This is the formatter engine used by `gjsify format` / `gjsify fix` under GJS — npm's `oxfmt` is a Rust N-API binary that cannot load in GJS, so this bridge is how gjsify formats without a Node runtime. The bridge links oxfmt's pure-Rust CLI core, so the full CLI surface runs in-process: `.oxfmtrc(.json)` + `.editorconfig` resolution, ignore handling, parallel file walking, `--write` / `--check` / `--list-different`. Ships prebuilt native libraries + `.gir` + `.typelib` for Linux and macOS — see [Platform coverage](#platform-coverage).
4
4
 
5
5
  Part of the [gjsify](https://github.com/gjsify/gjsify) project — Node.js and Web APIs for GJS (GNOME JavaScript).
6
6
 
@@ -40,6 +40,26 @@ git submodule update --init refs/oxc
40
40
  gjsify workspace @gjsify/oxfmt-native build:prebuilds # needs meson + vala + cargo
41
41
  ```
42
42
 
43
+ ## Platform coverage
44
+
45
+ | Platform | Prebuild | Built by |
46
+ |---|---|---|
47
+ | `linux-x64` | ✅ `.so` + `.gir` + `.typelib` | native runner |
48
+ | `linux-arm64` | ✅ | native runner |
49
+ | `linux-ppc64`, `linux-s390x`, `linux-riscv64` | ❌ | not built — the `refs/oxc` crate graph is too slow under QEMU |
50
+ | `darwin-arm64` (macOS, Apple silicon) | ✅ `.dylib` + `.gir` + `.typelib` | `macos-latest` runner |
51
+ | `darwin-x64` (macOS, Intel) | ❌ | — no runner leg yet |
52
+ | Windows | ❌ | — no Vala/GI bridge in this repo targets Windows |
53
+
54
+ All prebuilds are produced by [`.github/workflows/prebuilds.yml`](../../../.github/workflows/prebuilds.yml)
55
+ and committed back to the repository.
56
+
57
+ **Known gap — a `darwin-arm64` prebuild is built and shipped, but the CLI does not
58
+ load it yet.** `detectNativePackages()` (`packages/infra/cli/src/utils/detect-native-packages.ts`)
59
+ hardcodes a `linux-` directory prefix, and `buildNativeEnv()` exports `LD_LIBRARY_PATH`,
60
+ which macOS `dyld` ignores in favour of `DYLD_LIBRARY_PATH`. Both are CLI-side fixes;
61
+ until they land, `gjsify format` on macOS falls back to spawning the npm `oxfmt` Node launcher.
62
+
43
63
  ## License
44
64
 
45
65
  MIT
package/meson.build CHANGED
@@ -17,6 +17,48 @@ vala_deps = [
17
17
  dependency('gobject-2.0'),
18
18
  ]
19
19
 
20
+ # ----------------------------------------------------------------------------
21
+ # Host-dependent naming + runtime-lookup conventions
22
+ #
23
+ # Three things differ per host and MUST agree, or the prebuild loads on one
24
+ # platform and silently fails on the other (mirrors @gjsify/napi's meson
25
+ # branch):
26
+ #
27
+ # 1. cargo's cdylib leaf — libgjsify_oxfmt.{so,dylib}
28
+ # 2. meson's library() leaf — libgjsifyoxfmt.{so,dylib}; the typelib records
29
+ # this name and gjs' g_module_open() resolves exactly it at load.
30
+ # 3. how the Vala library finds its sibling Rust cdylib at runtime. Both
31
+ # files ship side by side in prebuilds/<platform>-<arch>/, so the lookup
32
+ # must be relative to the loading binary: $ORIGIN on ELF, @loader_path on
33
+ # Mach-O. On macOS this is load-bearing — unlike Linux (where the CLI
34
+ # exports LD_LIBRARY_PATH for the prebuild dir) nothing sets
35
+ # DYLD_LIBRARY_PATH, so the rpath is the ONLY way the cdylib is found.
36
+ #
37
+ # Windows is deliberately absent: no Vala bridge here has had any Windows
38
+ # portability work, so `else` (ELF naming) is the honest fallback rather than a
39
+ # .dll branch that would imply support that does not exist.
40
+ # ----------------------------------------------------------------------------
41
+ host_system = host_machine.system()
42
+
43
+ if host_system == 'darwin'
44
+ rust_cdylib_name = 'libgjsify_oxfmt.dylib'
45
+ shared_lib_name = 'libgjsifyoxfmt.dylib'
46
+ lib_rpath = '@loader_path'
47
+ # Normalise the cdylib's LC_ID_DYLIB to @rpath/<leaf> BEFORE the Vala
48
+ # library links against it: ld64 copies the dependency's install name
49
+ # verbatim into the dependent's LC_LOAD_DYLIB. rustc's own install name has
50
+ # varied across releases (absolute build path in older rustc, @rpath/<leaf>
51
+ # in current ones); pinning it here makes the recorded dependency
52
+ # rustc-version-independent, and @rpath/ is never longer than an absolute
53
+ # path so install_name_tool can always rewrite in place.
54
+ rust_cdylib_post = ' && install_name_tool -id "@rpath/' + rust_cdylib_name + '" "$3"'
55
+ else
56
+ rust_cdylib_name = 'libgjsify_oxfmt.so'
57
+ shared_lib_name = 'libgjsifyoxfmt.so'
58
+ lib_rpath = '$ORIGIN'
59
+ rust_cdylib_post = ''
60
+ endif
61
+
20
62
  # ----------------------------------------------------------------------------
21
63
  # Rust cdylib (cargo)
22
64
  #
@@ -35,9 +77,10 @@ rust_target_dir = meson.current_build_dir() / 'cargo-target'
35
77
 
36
78
  rust_cdylib = custom_target('gjsify-oxfmt-rust',
37
79
  input: rust_sources,
38
- output: 'libgjsify_oxfmt.so',
80
+ output: rust_cdylib_name,
39
81
  command: ['sh', '-c',
40
- 'cargo build --release --manifest-path "$1" --target-dir "$2" && cp "$2/release/libgjsify_oxfmt.so" "$3"',
82
+ 'cargo build --release --manifest-path "$1" --target-dir "$2" && cp "$2/release/'
83
+ + rust_cdylib_name + '" "$3"' + rust_cdylib_post,
41
84
  'sh',
42
85
  meson.current_source_dir() / 'src/rust/Cargo.toml',
43
86
  rust_target_dir,
@@ -68,7 +111,11 @@ libGjsifyOxfmt = library('gjsifyoxfmt', sources,
68
111
  link_depends: rust_cdylib,
69
112
  install: true,
70
113
  install_dir: [true, true, true, true],
71
- install_rpath: '$ORIGIN',
114
+ # `build_rpath` (not just `install_rpath`) is what matters for the prebuild:
115
+ # the artifact shipped in prebuilds/ is copied straight out of the build
116
+ # dir, never through `meson install`, so only the build-tree rpath survives.
117
+ build_rpath: lib_rpath,
118
+ install_rpath: lib_rpath,
72
119
  )
73
120
 
74
121
  g_ir_compiler = find_program('g-ir-compiler')
@@ -76,7 +123,7 @@ g_ir_compiler = find_program('g-ir-compiler')
76
123
  custom_target(meson.project_name() + '-1.0.typelib',
77
124
  command: [
78
125
  g_ir_compiler,
79
- '--shared-library', 'libgjsifyoxfmt.so',
126
+ '--shared-library', shared_lib_name,
80
127
  '--output', '@OUTPUT@',
81
128
  meson.current_build_dir() / meson.project_name() + '-1.0.gir',
82
129
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/oxfmt-native",
3
- "version": "0.22.0",
3
+ "version": "0.24.1",
4
4
  "description": "Vala/GObject + Rust cdylib bridge to oxc's formatter (oxfmt) for GJS. Formats JavaScript/TypeScript/JSX under GJS without a Node runtime — the Node-free counterpart to the npm `oxfmt` native napi binary, backing `gjsify format` under GJS. Wraps oxc_formatter (Prettier-compatible) via a single-shot FFI; the embedded CSS/HTML/Markdown ExternalFormatter (Prettier-NAPI) is out of scope.",
5
5
  "type": "module",
6
6
  "main": "lib/esm/index.js",
@@ -20,6 +20,11 @@
20
20
  "src/vala"
21
21
  ],
22
22
  "gjsify": {
23
+ "platforms": [
24
+ "linux-x64",
25
+ "linux-arm64",
26
+ "darwin-arm64"
27
+ ],
23
28
  "prebuilds": "prebuilds",
24
29
  "runtimes": {
25
30
  "gjs": "polyfill",
@@ -36,9 +41,9 @@
36
41
  "init:meson:wipe": "gjsify run init:meson --wipe",
37
42
  "build": "gjsify run build:gjsify && gjsify run build:types",
38
43
  "build:gjsify": "gjsify build --library 'src/ts/**/*.{ts,js}'",
39
- "build:meson": "gjsify run init:meson && meson compile -C build",
44
+ "build:meson": "node ../../../scripts/check-refs-pin.mjs . && gjsify run init:meson && meson compile -C build",
40
45
  "build:types": "gjsify tsc",
41
- "build:prebuilds": "gjsify run build:meson && mkdir -p prebuilds/linux-x86_64 && cp build/libgjsifyoxfmt.so build/libgjsify_oxfmt.so build/GjsifyOxfmt-1.0.gir build/GjsifyOxfmt-1.0.typelib prebuilds/linux-x86_64/"
46
+ "build:prebuilds": "gjsify run build:meson && node ../../../scripts/stage-prebuild.mjs ."
42
47
  },
43
48
  "keywords": [
44
49
  "gjs",
@@ -57,7 +62,7 @@
57
62
  "@girs/gobject-2.0": "^4.1.0"
58
63
  },
59
64
  "devDependencies": {
60
- "@gjsify/cli": "^0.22.0",
65
+ "@gjsify/cli": "^0.24.1",
61
66
  "@types/node": "^25.9.2",
62
67
  "typescript": "^6.0.3"
63
68
  },
@@ -0,0 +1,65 @@
1
+ <?xml version="1.0"?>
2
+ <!-- GjsifyOxfmt-1.0.gir generated by valac 0.56.19, do not modify. -->
3
+ <repository version="1.2" xmlns="http://www.gtk.org/introspection/core/1.0" xmlns:c="http://www.gtk.org/introspection/c/1.0" xmlns:doc="http://www.gtk.org/introspection/doc/1.0" xmlns:glib="http://www.gtk.org/introspection/glib/1.0">
4
+ <include name="GObject" version="2.0"/>
5
+ <include name="GLib" version="2.0"/>
6
+ <package name="gjsifyoxfmt"/>
7
+ <c:include name="gjsify-oxfmt-glue.h"/>
8
+ <c:include name="gjsifyoxfmt.h"/>
9
+ <doc:format name="unknown"/>
10
+ <namespace name="GjsifyOxfmt" version="1.0" shared-library="libgjsifyoxfmt.dylib" c:prefix="GjsifyOxfmt" c:identifier-prefixes="GjsifyOxfmt" c:symbol-prefixes="gjsify_oxfmt">
11
+ <class name="Formatter" c:type="GjsifyOxfmtFormatter" c:symbol-prefix="formatter" glib:type-name="GjsifyOxfmtFormatter" glib:get-type="gjsify_oxfmt_formatter_get_type" glib:type-struct="FormatterClass" parent="GObject.Object">
12
+ <field name="parent_instance" readable="0" private="1">
13
+ <type name="GObject.Object" c:type="GObject"/>
14
+ </field>
15
+ <field name="priv" readable="0" private="1">
16
+ <type name="FormatterPrivate" c:type="GjsifyOxfmtFormatterPrivate*"/>
17
+ </field>
18
+ <method name="format" c:identifier="gjsify_oxfmt_formatter_format" throws="1">
19
+ <return-value transfer-ownership="full">
20
+ <type name="GLib.Bytes" c:type="GBytes*"/>
21
+ </return-value>
22
+ <parameters>
23
+ <instance-parameter name="self" transfer-ownership="none">
24
+ <type name="GjsifyOxfmt.Formatter" c:type="GjsifyOxfmtFormatter*"/>
25
+ </instance-parameter>
26
+ <parameter name="filename" transfer-ownership="none" nullable="1">
27
+ <type name="utf8" c:type="const gchar*"/>
28
+ </parameter>
29
+ <parameter name="code" transfer-ownership="none">
30
+ <type name="GLib.Bytes" c:type="GBytes*"/>
31
+ </parameter>
32
+ </parameters>
33
+ </method>
34
+ <method name="run" c:identifier="gjsify_oxfmt_formatter_run">
35
+ <return-value transfer-ownership="full">
36
+ <type name="gint" c:type="gint"/>
37
+ </return-value>
38
+ <parameters>
39
+ <instance-parameter name="self" transfer-ownership="none">
40
+ <type name="GjsifyOxfmt.Formatter" c:type="GjsifyOxfmtFormatter*"/>
41
+ </instance-parameter>
42
+ <parameter name="args" transfer-ownership="none">
43
+ <array length="1" c:type="gchar**">
44
+ <type name="utf8" c:type="gchar*"/>
45
+ </array>
46
+ </parameter>
47
+ <parameter name="args_length1" transfer-ownership="none">
48
+ <type name="gint" c:type="gint"/>
49
+ </parameter>
50
+ </parameters>
51
+ </method>
52
+ <constructor name="new" c:identifier="gjsify_oxfmt_formatter_new">
53
+ <return-value transfer-ownership="full">
54
+ <type name="GjsifyOxfmt.Formatter" c:type="GjsifyOxfmtFormatter*"/>
55
+ </return-value>
56
+ </constructor>
57
+ </class>
58
+ <record name="FormatterClass" c:type="GjsifyOxfmtFormatterClass" glib:is-gtype-struct-for="Formatter">
59
+ <field name="parent_class" readable="0" private="1">
60
+ <type name="GObject.ObjectClass" c:type="GObjectClass"/>
61
+ </field>
62
+ </record>
63
+ <record name="FormatterPrivate" c:type="GjsifyOxfmtFormatterPrivate" disguised="1"/>
64
+ </namespace>
65
+ </repository>
@@ -0,0 +1,65 @@
1
+ <?xml version="1.0"?>
2
+ <!-- GjsifyOxfmt-1.0.gir generated by valac 0.56.19, do not modify. -->
3
+ <repository version="1.2" xmlns="http://www.gtk.org/introspection/core/1.0" xmlns:c="http://www.gtk.org/introspection/c/1.0" xmlns:doc="http://www.gtk.org/introspection/doc/1.0" xmlns:glib="http://www.gtk.org/introspection/glib/1.0">
4
+ <include name="GObject" version="2.0"/>
5
+ <include name="GLib" version="2.0"/>
6
+ <package name="gjsifyoxfmt"/>
7
+ <c:include name="gjsify-oxfmt-glue.h"/>
8
+ <c:include name="gjsifyoxfmt.h"/>
9
+ <doc:format name="unknown"/>
10
+ <namespace name="GjsifyOxfmt" version="1.0" shared-library="libgjsifyoxfmt.so" c:prefix="GjsifyOxfmt" c:identifier-prefixes="GjsifyOxfmt" c:symbol-prefixes="gjsify_oxfmt">
11
+ <class name="Formatter" c:type="GjsifyOxfmtFormatter" c:symbol-prefix="formatter" glib:type-name="GjsifyOxfmtFormatter" glib:get-type="gjsify_oxfmt_formatter_get_type" glib:type-struct="FormatterClass" parent="GObject.Object">
12
+ <field name="parent_instance" readable="0" private="1">
13
+ <type name="GObject.Object" c:type="GObject"/>
14
+ </field>
15
+ <field name="priv" readable="0" private="1">
16
+ <type name="FormatterPrivate" c:type="GjsifyOxfmtFormatterPrivate*"/>
17
+ </field>
18
+ <method name="format" c:identifier="gjsify_oxfmt_formatter_format" throws="1">
19
+ <return-value transfer-ownership="full">
20
+ <type name="GLib.Bytes" c:type="GBytes*"/>
21
+ </return-value>
22
+ <parameters>
23
+ <instance-parameter name="self" transfer-ownership="none">
24
+ <type name="GjsifyOxfmt.Formatter" c:type="GjsifyOxfmtFormatter*"/>
25
+ </instance-parameter>
26
+ <parameter name="filename" transfer-ownership="none" nullable="1">
27
+ <type name="utf8" c:type="const gchar*"/>
28
+ </parameter>
29
+ <parameter name="code" transfer-ownership="none">
30
+ <type name="GLib.Bytes" c:type="GBytes*"/>
31
+ </parameter>
32
+ </parameters>
33
+ </method>
34
+ <method name="run" c:identifier="gjsify_oxfmt_formatter_run">
35
+ <return-value transfer-ownership="full">
36
+ <type name="gint" c:type="gint"/>
37
+ </return-value>
38
+ <parameters>
39
+ <instance-parameter name="self" transfer-ownership="none">
40
+ <type name="GjsifyOxfmt.Formatter" c:type="GjsifyOxfmtFormatter*"/>
41
+ </instance-parameter>
42
+ <parameter name="args" transfer-ownership="none">
43
+ <array length="1" c:type="gchar**">
44
+ <type name="utf8" c:type="gchar*"/>
45
+ </array>
46
+ </parameter>
47
+ <parameter name="args_length1" transfer-ownership="none">
48
+ <type name="gint" c:type="gint"/>
49
+ </parameter>
50
+ </parameters>
51
+ </method>
52
+ <constructor name="new" c:identifier="gjsify_oxfmt_formatter_new">
53
+ <return-value transfer-ownership="full">
54
+ <type name="GjsifyOxfmt.Formatter" c:type="GjsifyOxfmtFormatter*"/>
55
+ </return-value>
56
+ </constructor>
57
+ </class>
58
+ <record name="FormatterClass" c:type="GjsifyOxfmtFormatterClass" glib:is-gtype-struct-for="Formatter">
59
+ <field name="parent_class" readable="0" private="1">
60
+ <type name="GObject.ObjectClass" c:type="GObjectClass"/>
61
+ </field>
62
+ </record>
63
+ <record name="FormatterPrivate" c:type="GjsifyOxfmtFormatterPrivate" disguised="1"/>
64
+ </namespace>
65
+ </repository>
@@ -32,6 +32,27 @@ oxc_formatter = { path = "../../../../../refs/oxc/crates/oxc_formatter" }
32
32
  oxc_span = { path = "../../../../../refs/oxc/crates/oxc_span" }
33
33
  oxfmt = { path = "../../../../../refs/oxc/apps/oxfmt", default-features = false }
34
34
 
35
+ # `oxc_allocator` is dogfooded in-tree (the path deps above) AND published to
36
+ # crates.io. Two of the formatter's leaf parsers are only ON crates.io — the
37
+ # `oxc_formatter_{css,graphql}` wrappers live in refs/oxc, but the parsers they
38
+ # call (`oxc-css-parser`, `oxc-graphql-parser`) do not, so they pull
39
+ # `oxc_allocator` from the REGISTRY. Cargo treats registry and path copies as
40
+ # separate crate instances even at the same version, so `&oxc_allocator::Allocator`
41
+ # from the path copy cannot be passed to a parser typed against the registry one
42
+ # (E0308 `there are multiple different versions of crate oxc_allocator`).
43
+ #
44
+ # refs/oxc solves this for its OWN workspace with the identical patch, but a
45
+ # `[patch]` table is only honoured from the ROOT of the workspace being built —
46
+ # path-depping INTO refs/oxc does not inherit it. `gjsify_oxfmt` is a standalone
47
+ # package (no enclosing [workspace]), so it is its own root and must repeat it.
48
+ #
49
+ # Without this the resolution also floats: the parsers require
50
+ # `oxc_allocator >=0.137.0`, so cargo re-picks the newest published version on
51
+ # every build (0.141.0 today) with nothing pinning it. Patching to the path copy
52
+ # collapses the duplicate class AND removes the float in one step.
53
+ [patch.crates-io]
54
+ oxc_allocator = { path = "../../../../../refs/oxc/crates/oxc_allocator" }
55
+
35
56
  [profile.release]
36
57
  lto = true
37
58
  codegen-units = 1