@crossbind/extension-deb 0.0.0-stage → 2.0.0-beta.69

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Buğra Sarı and crossbind contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,24 @@
1
- # Temporary Holding Version
1
+ # @crossbind/extension-deb
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A Debian package for Linux, whose dependencies bring WebKitGTK.
4
+
5
+ An app has it by having `@crossbind/core-app-host`, which lists it as a dependency: there is nothing to install or list.
6
+
7
+ ## Use
8
+
9
+ ```sh
10
+ npx crossbind app build -p linux
11
+ ```
12
+
13
+ A Linux build writes a `.deb` named after the app beside its folder, which `dpkg-deb` makes in crossbind's linux image. Its
14
+ dependencies bring the WebKitGTK and GTK the app runs on, and those its extensions list.
15
+
16
+ ## Platforms
17
+
18
+ Linux, x64 and arm64.
19
+
20
+ ## Replace or leave out
21
+
22
+ An extension named `deb` with an installer takes its place, and `app: { without: ['deb'] }` in `crossbind.config.js` leaves it out. [extensions.md](https://github.com/crossbind/crossbind/blob/main/docs/api/extensions.md#installers) says what one gives.
23
+
24
+ [app.md](https://github.com/crossbind/crossbind/blob/main/docs/api/app.md#installers) has every installer, and `app.installers` names those a build runs, as `['dmg']`.
@@ -0,0 +1,129 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ const DEBIAN_ARCH = { x64: 'amd64', arm64: 'arm64' };
5
+ // What a Linux app runs on: WebKitGTK 4.1 from its 2.40 release, and the GTK its window is.
6
+ const DEBIAN_DEPENDS = 'libwebkit2gtk-4.1-0 (>= 2.40), libgtk-3-0';
7
+
8
+ // A Debian package's name: the project's, in lower case, with a hyphen for each underscore and from its first letter
9
+ // or digit.
10
+ export function debianName(name) {
11
+ const debian = name.toLowerCase().replaceAll('_', '-').replace(/^[^a-z0-9]+/, '');
12
+ if (!/^[a-z0-9][a-z0-9+.-]+$/.test(debian)) {
13
+ throw new Error(`crossbind: general.name ${JSON.stringify(name)} makes no Debian package name, which is two or more lowercase letters, digits, +, . and -, starting with a letter or digit. Change general.name in crossbind.config.js.`);
14
+ }
15
+ return debian;
16
+ }
17
+
18
+ // A Debian version sorts the pre-releases of a release before it, as semver does: 1.0.0-beta.1 is 1.0.0~beta.1.
19
+ export const debianVersion = (version) => version.replace('-', '~');
20
+
21
+ // package.json's author as Debian names a maintainer, by name and email address. A string's URL goes, from its first
22
+ // parenthesis to its last, found in one pass, as a regular expression would backtrack over many of them.
23
+ export function maintainer(author) {
24
+ if (typeof author === 'string') {
25
+ const open = author.indexOf('(');
26
+ const close = author.lastIndexOf(')');
27
+ return (open >= 0 && close > open ? author.slice(0, open) + author.slice(close + 1) : author).trim() || null;
28
+ }
29
+ if (!author?.name) return null;
30
+ return author.email ? `${author.name} <${author.email}>` : author.name;
31
+ }
32
+
33
+ // The control file of a Linux app's package, which depends on WebKitGTK and GTK and the packages its extensions name.
34
+ // dpkg warns of a package without a maintainer in every command it runs afterwards, so without package.json's author the
35
+ // app's name stands for one.
36
+ export function debianControl({ name, version, arch, author, description, label, depends = [] }) {
37
+ return [
38
+ `Package: ${name}`,
39
+ `Version: ${debianVersion(version)}`,
40
+ `Architecture: ${DEBIAN_ARCH[arch]}`,
41
+ `Maintainer: ${maintainer(author) ?? label}`,
42
+ `Depends: ${[DEBIAN_DEPENDS, ...depends].join(', ')}`,
43
+ `Description: ${(description || label).replace(/\s+/g, ' ').trim()}`,
44
+ '',
45
+ ].join('\n');
46
+ }
47
+
48
+ // The desktop entry a Linux menu shows an app by. GTK names the app's window after the command that started it, and
49
+ // GNOME on Wayland shows the icon of the entry whose file has that name.
50
+ // A link of one of its schemes opens it with the link as its argument, %u, and files of its MIME types with each one's
51
+ // path or file: URL, %U, which take the link as well. A menu shows it under its categories.
52
+ export function desktopEntry({ label, command, hasIcon, urlSchemes = [], mimeTypes = [], categories = null }) {
53
+ const handled = [...urlSchemes.map((scheme) => `x-scheme-handler/${scheme}`), ...new Set(mimeTypes)];
54
+ const field = mimeTypes.length > 0 ? ' %U' : (urlSchemes.length > 0 ? ' %u' : '');
55
+ return [
56
+ '[Desktop Entry]', 'Type=Application', `Name=${label}`, `Exec=${command}${field}`, ...(hasIcon ? [`Icon=${command}`] : []),
57
+ ...(handled.length > 0 ? [`MimeType=${handled.map((type) => `${type};`).join('')}`] : []),
58
+ ...(categories ? [`Categories=${categories};`] : []), 'Terminal=false', '',
59
+ ].join('\n');
60
+ }
61
+
62
+ // The file types of a Linux app, each with its MIME types: its own, else one of the app's for its extensions.
63
+ export const linuxFileTypes = (name, fileTypes) => fileTypes.map((type) => ({
64
+ ...type, mimeTypes: type.mimeTypes.length > 0 ? type.mimeTypes : [`application/x-${name}-${type.extensions[0]}`],
65
+ }));
66
+
67
+ // The shared MIME package of a Linux app's file types, which tells the system of their names' extensions, as Debian's
68
+ // shared-mime-info reads it from /usr/share/mime/packages once a package puts it there.
69
+ export function mimePackage(fileTypes) {
70
+ const escaped = (text) => text.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');
71
+ return [
72
+ '<?xml version="1.0" encoding="UTF-8"?>',
73
+ '<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">',
74
+ ...fileTypes.flatMap(({ name, extensions, mimeTypes }) => mimeTypes.flatMap((mime) => [
75
+ ` <mime-type type="${mime}">`,
76
+ ` <comment>${escaped(name)}</comment>`,
77
+ ...extensions.map((ending) => ` <glob pattern="*.${ending}"/>`),
78
+ ' </mime-type>',
79
+ ])),
80
+ '</mime-info>', '',
81
+ ].join('\n');
82
+ }
83
+
84
+ // crossbind's installer of a Linux app: a Debian package, which dpkg-deb builds in the linux image, with the app in
85
+ // /usr/lib/<name>, its command in /usr/bin, and the desktop entry and icon a menu shows it by. The package's name stops
86
+ // a build that could not make it before anything compiles.
87
+ export default function deb() {
88
+ return {
89
+ name: 'deb',
90
+ contract: 1,
91
+ root: import.meta.dirname,
92
+ installer: {
93
+ platform: 'linux',
94
+ check(api) {
95
+ debianName(api.project.name);
96
+ },
97
+ make(api, { app, arch }) {
98
+ const name = debianName(api.project.name);
99
+ const label = api.app.name;
100
+ const root = path.join(api.workDir, path.basename(app));
101
+ const dir = (relative) => {
102
+ fs.mkdirSync(`${root}/${relative}`, { recursive: true });
103
+ return `${root}/${relative}`;
104
+ };
105
+ fs.rmSync(root, { recursive: true, force: true });
106
+ // What it packages is gone once the package is made, or could not be.
107
+ try {
108
+ fs.cpSync(app, `${dir('usr/lib')}/${name}`, { recursive: true, verbatimSymlinks: true });
109
+ fs.symlinkSync(`../lib/${name}/${api.project.name}`, `${dir('usr/bin')}/${name}`);
110
+ const icon = `${app}/resources/icon.png`;
111
+ const hasIcon = fs.existsSync(icon);
112
+ if (hasIcon) fs.copyFileSync(icon, `${dir('usr/share/icons/hicolor/512x512/apps')}/${name}.png`);
113
+ const fileTypes = linuxFileTypes(name, api.app.fileTypes);
114
+ fs.writeFileSync(`${dir('usr/share/applications')}/${name}.desktop`, desktopEntry({
115
+ label, command: name, hasIcon, urlSchemes: api.app.urlSchemes, mimeTypes: fileTypes.flatMap(({ mimeTypes }) => mimeTypes),
116
+ categories: api.app.category?.linux,
117
+ }));
118
+ if (fileTypes.length > 0) fs.writeFileSync(`${dir('usr/share/mime/packages')}/${name}.xml`, mimePackage(fileTypes));
119
+ const { author, description } = api.project.package ?? {};
120
+ fs.writeFileSync(`${dir('DEBIAN')}/control`, debianControl({ name, version: api.app.version, arch, author, description, label, depends: api.app.depends }));
121
+ api.run(['dpkg-deb', '--build', '--root-owner-group', root, `${app}.deb`], { cwd: path.dirname(app) });
122
+ } finally {
123
+ fs.rmSync(root, { recursive: true, force: true });
124
+ }
125
+ return [`${app}.deb`];
126
+ },
127
+ },
128
+ };
129
+ }
package/package.json CHANGED
@@ -1,6 +1,28 @@
1
1
  {
2
2
  "name": "@crossbind/extension-deb",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "2.0.0-beta.69",
4
+ "description": "crossbind app extension: a Debian package for Linux, whose dependencies bring WebKitGTK.",
5
+ "homepage": "https://github.com/crossbind/crossbind/tree/main/extensions/deb#readme",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/crossbind/crossbind.git"
9
+ },
10
+ "license": "MIT",
11
+ "type": "module",
12
+ "exports": {
13
+ "./crossbind.extension.js": "./crossbind.extension.js"
14
+ },
15
+ "files": [
16
+ "crossbind.extension.js"
17
+ ],
18
+ "crossbind": {
19
+ "extension": "./crossbind.extension.js"
20
+ },
21
+ "keywords": [
22
+ "crossbind-extension",
23
+ "crossbind",
24
+ "deb",
25
+ "installer",
26
+ "app"
27
+ ]
6
28
  }