@flare-desktop/cli 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +76 -2
- package/bin/flare.js +4 -0
- package/package.json +42 -4
- package/src/cli.js +93 -0
- package/src/commands/build.js +37 -0
- package/src/commands/bundle.js +44 -0
- package/src/commands/completions.js +45 -0
- package/src/commands/dev.js +91 -0
- package/src/commands/doctor.js +88 -0
- package/src/commands/icon.js +31 -0
- package/src/commands/info.js +65 -0
- package/src/commands/init.js +108 -0
- package/src/commands/inspect.js +35 -0
- package/src/help.js +32 -0
- package/src/index.js +17 -0
- package/src/lib/args.js +65 -0
- package/src/lib/build.js +29 -0
- package/src/lib/cmake.js +102 -0
- package/src/lib/commands.js +15 -0
- package/src/lib/errors.js +7 -0
- package/src/lib/icons.js +127 -0
- package/src/lib/logger.js +19 -0
- package/src/lib/packaging/common.js +131 -0
- package/src/lib/packaging/index.js +228 -0
- package/src/lib/packaging/linux.js +283 -0
- package/src/lib/packaging/macos.js +84 -0
- package/src/lib/packaging/msi.js +143 -0
- package/src/lib/packaging/nsis.js +106 -0
- package/src/lib/packaging/staging.js +123 -0
- package/src/lib/packaging/tools.js +75 -0
- package/src/lib/png.js +288 -0
- package/src/lib/process.js +57 -0
- package/src/lib/project.js +285 -0
- package/src/schema.js +2 -0
- package/src/templates/project.js +284 -0
- package/src/version.js +6 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { FlareError } from "../errors.js";
|
|
4
|
+
|
|
5
|
+
export function numericVersion(version) {
|
|
6
|
+
const match = String(version).match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
7
|
+
if (!match) throw new FlareError(`'${version}' is not a numeric semantic version`);
|
|
8
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function msiVersion(version) {
|
|
12
|
+
const [major, minor, patch] = numericVersion(version);
|
|
13
|
+
if (major > 255 || minor > 255 || patch > 65535) {
|
|
14
|
+
throw new FlareError(`'${version}' cannot be represented as an msi version`);
|
|
15
|
+
}
|
|
16
|
+
return `${major}.${minor}.${patch}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function fourPartVersion(version) {
|
|
20
|
+
const [major, minor, patch] = numericVersion(version);
|
|
21
|
+
return `${major}.${minor}.${patch}.0`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function debVersion(version) {
|
|
25
|
+
return String(version).replace(/-/g, "~");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function rpmVersion(version) {
|
|
29
|
+
return String(version).replace(/-/g, "~");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function safeName(value) {
|
|
33
|
+
const name = String(value)
|
|
34
|
+
.toLowerCase()
|
|
35
|
+
.replace(/[^a-z0-9+.-]+/g, "-")
|
|
36
|
+
.replace(/^[^a-z0-9]+/, "")
|
|
37
|
+
.replace(/-{2,}/g, "-")
|
|
38
|
+
.replace(/[.-]+$/, "");
|
|
39
|
+
return name || "flare-app";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function artifactName(value) {
|
|
43
|
+
return String(value)
|
|
44
|
+
.replace(/[^A-Za-z0-9._-]+/g, "-")
|
|
45
|
+
.replace(/-{2,}/g, "-")
|
|
46
|
+
.replace(/^-+|-+$/g, "");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function windowsArchitecture(architecture = process.arch) {
|
|
50
|
+
if (architecture === "x64") return "x64";
|
|
51
|
+
if (architecture === "arm64") return "arm64";
|
|
52
|
+
if (architecture === "ia32") return "x86";
|
|
53
|
+
return architecture;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function debArchitecture(architecture = process.arch) {
|
|
57
|
+
if (architecture === "x64") return "amd64";
|
|
58
|
+
if (architecture === "arm64") return "arm64";
|
|
59
|
+
if (architecture === "ia32") return "i386";
|
|
60
|
+
if (architecture === "arm") return "armhf";
|
|
61
|
+
return architecture;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function rpmArchitecture(architecture = process.arch) {
|
|
65
|
+
if (architecture === "x64") return "x86_64";
|
|
66
|
+
if (architecture === "arm64") return "aarch64";
|
|
67
|
+
if (architecture === "ia32") return "i686";
|
|
68
|
+
return architecture;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function appImageArchitecture(architecture = process.arch) {
|
|
72
|
+
if (architecture === "x64") return "x86_64";
|
|
73
|
+
if (architecture === "arm64") return "aarch64";
|
|
74
|
+
if (architecture === "ia32") return "i686";
|
|
75
|
+
return architecture;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function stableGuid(seed) {
|
|
79
|
+
const bytes = createHash("sha1").update(String(seed)).digest().subarray(0, 16);
|
|
80
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x50;
|
|
81
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
82
|
+
const hex = bytes.toString("hex").toUpperCase();
|
|
83
|
+
return `{${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function escapeXml(value) {
|
|
87
|
+
return String(value)
|
|
88
|
+
.replace(/&/g, "&")
|
|
89
|
+
.replace(/</g, "<")
|
|
90
|
+
.replace(/>/g, ">")
|
|
91
|
+
.replace(/"/g, """)
|
|
92
|
+
.replace(/'/g, "'");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function escapeNsis(value) {
|
|
96
|
+
return String(value).replace(/\$/g, "$$").replace(/"/g, '$\\"');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function escapeDesktop(value) {
|
|
100
|
+
return String(value).replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/\r/g, "");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function desktopCategory(category) {
|
|
104
|
+
const categories = {
|
|
105
|
+
DeveloperTool: "Development;",
|
|
106
|
+
Development: "Development;",
|
|
107
|
+
Utility: "Utility;",
|
|
108
|
+
Productivity: "Office;",
|
|
109
|
+
Graphics: "Graphics;",
|
|
110
|
+
Music: "Audio;",
|
|
111
|
+
Video: "AudioVideo;",
|
|
112
|
+
Network: "Network;",
|
|
113
|
+
Game: "Game;",
|
|
114
|
+
Education: "Education;",
|
|
115
|
+
Science: "Science;"
|
|
116
|
+
};
|
|
117
|
+
return categories[category] || "Utility;";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function maintainer(config) {
|
|
121
|
+
const value = config.bundle?.publisher || config.app?.authors?.[0] || "flare-desktop";
|
|
122
|
+
return value.includes("@") ? value : `${value} <noreply@flare-desktop.dev>`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function description(config) {
|
|
126
|
+
return config.bundle?.shortDescription || config.app?.description || `${config.app?.productName || "flare"} built with flare`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function longDescription(config) {
|
|
130
|
+
return config.bundle?.longDescription || description(config);
|
|
131
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { FlareError } from "../errors.js";
|
|
5
|
+
import { formatsForPlatform, platformDisplayName } from "../project.js";
|
|
6
|
+
import {
|
|
7
|
+
appImageArchitecture,
|
|
8
|
+
debArchitecture,
|
|
9
|
+
rpmArchitecture,
|
|
10
|
+
windowsArchitecture
|
|
11
|
+
} from "./common.js";
|
|
12
|
+
import { buildNsis } from "./nsis.js";
|
|
13
|
+
import { buildMsi } from "./msi.js";
|
|
14
|
+
import { buildAppBundle, buildDmg, signApp } from "./macos.js";
|
|
15
|
+
import { buildAppImage, buildDeb, buildRpm } from "./linux.js";
|
|
16
|
+
import { cleanStage, stageIcons, stagePayload } from "./staging.js";
|
|
17
|
+
import { requireTool } from "./tools.js";
|
|
18
|
+
|
|
19
|
+
const HOST_TARGETS = {
|
|
20
|
+
win32: ["msi", "nsis"],
|
|
21
|
+
darwin: ["app", "dmg"],
|
|
22
|
+
linux: ["deb", "rpm", "appimage"]
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const TOOL_HINTS = {
|
|
26
|
+
makensis: "install NSIS 3 from https://nsis.sourceforge.io",
|
|
27
|
+
candle: "install WiX Toolset 3.14 and put candle.exe on PATH",
|
|
28
|
+
light: "install WiX Toolset 3.14 and put light.exe on PATH",
|
|
29
|
+
"dpkg-deb": "install dpkg-dev on Debian/Ubuntu",
|
|
30
|
+
rpmbuild: "install rpm-build on Fedora/RHEL or rpm on Debian/Ubuntu",
|
|
31
|
+
appimagetool: "download appimagetool from https://github.com/AppImage/appimagetool",
|
|
32
|
+
hdiutil: "run packaging on macOS",
|
|
33
|
+
codesign: "install Xcode command line tools"
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function bundleTargets(config, requested, platform = process.platform) {
|
|
37
|
+
if (!config.bundle?.active) throw new FlareError("bundle.active is false for this project");
|
|
38
|
+
|
|
39
|
+
const available = HOST_TARGETS[platform] ?? [];
|
|
40
|
+
const source =
|
|
41
|
+
requested === undefined ||
|
|
42
|
+
requested === null ||
|
|
43
|
+
requested === "" ||
|
|
44
|
+
(Array.isArray(requested) && requested.length === 0)
|
|
45
|
+
? config.bundle.targets ?? []
|
|
46
|
+
: requested;
|
|
47
|
+
const values = (Array.isArray(source) ? source : String(source).split(","))
|
|
48
|
+
.map((value) => String(value).trim().toLowerCase())
|
|
49
|
+
.filter(Boolean);
|
|
50
|
+
const selected = values.length === 0 || values.includes("all") ? formatsForPlatform(platform) : values;
|
|
51
|
+
|
|
52
|
+
if (selected.length === 0) {
|
|
53
|
+
throw new FlareError(`no bundle targets are available for ${platformDisplayName(platform)}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const unsupported = selected.filter((target) => !available.includes(target));
|
|
57
|
+
if (unsupported.length > 0) {
|
|
58
|
+
throw new FlareError(
|
|
59
|
+
`bundle target${unsupported.length === 1 ? "" : "s"} ${unsupported.map((target) => `'${target}'`).join(", ")} ` +
|
|
60
|
+
`not available on ${platformDisplayName(platform)}; available: ${available.join(", ")}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return [...new Set(selected)];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function bundleTools(targets, config, platform = process.platform) {
|
|
68
|
+
const tools = [];
|
|
69
|
+
for (const target of targets) {
|
|
70
|
+
if (target === "nsis") tools.push("makensis");
|
|
71
|
+
if (target === "msi") tools.push("candle", "light");
|
|
72
|
+
if (target === "deb") tools.push("dpkg-deb");
|
|
73
|
+
if (target === "rpm") tools.push("rpmbuild");
|
|
74
|
+
if (target === "appimage") tools.push("appimagetool");
|
|
75
|
+
if (target === "dmg") tools.push("hdiutil");
|
|
76
|
+
if (target === "app" && config.bundle?.macos?.signingIdentity) tools.push("codesign");
|
|
77
|
+
}
|
|
78
|
+
return [...new Set(tools)];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function assertBundleTools(targets, config, platform = process.platform) {
|
|
82
|
+
for (const tool of bundleTools(targets, config, platform)) {
|
|
83
|
+
requireTool(tool, TOOL_HINTS[tool]);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function bundleArchitecture(platform = process.platform) {
|
|
88
|
+
if (platform === "win32") return windowsArchitecture();
|
|
89
|
+
if (platform === "darwin") return process.arch === "arm64" ? "arm64" : "x64";
|
|
90
|
+
if (platform === "linux") return debArchitecture();
|
|
91
|
+
return process.arch;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function createBundles({
|
|
95
|
+
config,
|
|
96
|
+
projectDirectory,
|
|
97
|
+
binary,
|
|
98
|
+
targets,
|
|
99
|
+
output,
|
|
100
|
+
logger
|
|
101
|
+
}) {
|
|
102
|
+
const outputRoot = path.resolve(projectDirectory, output || path.join("target", "bundle"));
|
|
103
|
+
const stageRoot = path.join(outputRoot, ".stage");
|
|
104
|
+
const icons = stageIcons({ config, projectDirectory, stageDirectory: stageRoot });
|
|
105
|
+
const payload = stagePayload({ config, projectDirectory, binary, stageDirectory: stageRoot, logger });
|
|
106
|
+
const platform = process.platform;
|
|
107
|
+
const artifacts = [];
|
|
108
|
+
let macApp = null;
|
|
109
|
+
|
|
110
|
+
mkdirSync(outputRoot, { recursive: true });
|
|
111
|
+
logger.info(`bundling '${config.app.productName}' ${config.app.version} for ${platformDisplayName(platform)}`);
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
for (const target of targets) {
|
|
115
|
+
const outputDirectory = path.join(outputRoot, target);
|
|
116
|
+
const stageDirectory = path.join(stageRoot, target);
|
|
117
|
+
mkdirSync(stageDirectory, { recursive: true });
|
|
118
|
+
mkdirSync(outputDirectory, { recursive: true });
|
|
119
|
+
|
|
120
|
+
let artifact = null;
|
|
121
|
+
if (target === "nsis") {
|
|
122
|
+
artifact = await buildNsis({
|
|
123
|
+
config,
|
|
124
|
+
productName: config.app.productName,
|
|
125
|
+
version: config.app.version,
|
|
126
|
+
payload: payload.directory,
|
|
127
|
+
binaryName: payload.binaryName,
|
|
128
|
+
icons,
|
|
129
|
+
licenseSource: payload.licenseSource,
|
|
130
|
+
outputDirectory,
|
|
131
|
+
stageDirectory,
|
|
132
|
+
logger
|
|
133
|
+
});
|
|
134
|
+
} else if (target === "msi") {
|
|
135
|
+
artifact = await buildMsi({
|
|
136
|
+
config,
|
|
137
|
+
productName: config.app.productName,
|
|
138
|
+
version: config.app.version,
|
|
139
|
+
payload: payload.directory,
|
|
140
|
+
binaryName: payload.binaryName,
|
|
141
|
+
icons,
|
|
142
|
+
outputDirectory,
|
|
143
|
+
stageDirectory,
|
|
144
|
+
logger
|
|
145
|
+
});
|
|
146
|
+
} else if (target === "deb") {
|
|
147
|
+
artifact = await buildDeb({
|
|
148
|
+
config,
|
|
149
|
+
productName: config.app.productName,
|
|
150
|
+
version: config.app.version,
|
|
151
|
+
payload: payload.directory,
|
|
152
|
+
binaryName: payload.binaryName,
|
|
153
|
+
icons,
|
|
154
|
+
licenseSource: payload.licenseSource,
|
|
155
|
+
outputDirectory,
|
|
156
|
+
stageDirectory,
|
|
157
|
+
logger
|
|
158
|
+
});
|
|
159
|
+
} else if (target === "rpm") {
|
|
160
|
+
artifact = await buildRpm({
|
|
161
|
+
config,
|
|
162
|
+
productName: config.app.productName,
|
|
163
|
+
version: config.app.version,
|
|
164
|
+
payload: payload.directory,
|
|
165
|
+
binaryName: payload.binaryName,
|
|
166
|
+
icons,
|
|
167
|
+
licenseSource: payload.licenseSource,
|
|
168
|
+
outputDirectory,
|
|
169
|
+
stageDirectory,
|
|
170
|
+
logger
|
|
171
|
+
});
|
|
172
|
+
} else if (target === "appimage") {
|
|
173
|
+
artifact = await buildAppImage({
|
|
174
|
+
config,
|
|
175
|
+
productName: config.app.productName,
|
|
176
|
+
version: config.app.version,
|
|
177
|
+
payload: payload.directory,
|
|
178
|
+
binaryName: payload.binaryName,
|
|
179
|
+
icons,
|
|
180
|
+
licenseSource: payload.licenseSource,
|
|
181
|
+
outputDirectory,
|
|
182
|
+
stageDirectory,
|
|
183
|
+
logger
|
|
184
|
+
});
|
|
185
|
+
} else if (target === "app" || target === "dmg") {
|
|
186
|
+
if (!macApp) {
|
|
187
|
+
macApp = buildAppBundle({
|
|
188
|
+
config,
|
|
189
|
+
productName: config.app.productName,
|
|
190
|
+
version: config.app.version,
|
|
191
|
+
payload: payload.directory,
|
|
192
|
+
binaryName: payload.binaryName,
|
|
193
|
+
icons,
|
|
194
|
+
licenseSource: payload.licenseSource,
|
|
195
|
+
outputDirectory: path.join(outputRoot, "app"),
|
|
196
|
+
logger
|
|
197
|
+
});
|
|
198
|
+
if (config.bundle.macos?.signingIdentity) {
|
|
199
|
+
await signApp({
|
|
200
|
+
app: macApp,
|
|
201
|
+
identity: config.bundle.macos.signingIdentity,
|
|
202
|
+
logger
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
artifact =
|
|
207
|
+
target === "app"
|
|
208
|
+
? macApp
|
|
209
|
+
: await buildDmg({
|
|
210
|
+
app: macApp,
|
|
211
|
+
productName: config.app.productName,
|
|
212
|
+
outputDirectory,
|
|
213
|
+
logger
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
artifacts.push({ target, path: artifact });
|
|
218
|
+
logger.step(`prepared ${target}: ${artifact}`);
|
|
219
|
+
}
|
|
220
|
+
} finally {
|
|
221
|
+
if (artifacts.length === targets.length) cleanStage(stageRoot);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
logger.info(`artifacts written to '${outputRoot}'`);
|
|
225
|
+
return artifacts;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export { appImageArchitecture, debArchitecture, rpmArchitecture, windowsArchitecture };
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { chmodSync, cpSync, mkdirSync, readdirSync, symlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
appImageArchitecture,
|
|
6
|
+
debArchitecture,
|
|
7
|
+
debVersion,
|
|
8
|
+
description,
|
|
9
|
+
desktopCategory,
|
|
10
|
+
escapeDesktop,
|
|
11
|
+
longDescription,
|
|
12
|
+
maintainer,
|
|
13
|
+
rpmArchitecture,
|
|
14
|
+
rpmVersion,
|
|
15
|
+
safeName
|
|
16
|
+
} from "./common.js";
|
|
17
|
+
import { requireTool, runTool } from "./tools.js";
|
|
18
|
+
import { directorySize } from "./staging.js";
|
|
19
|
+
|
|
20
|
+
function copyContents(source, destination) {
|
|
21
|
+
mkdirSync(destination, { recursive: true });
|
|
22
|
+
for (const entry of readdirSync(source)) {
|
|
23
|
+
cpSync(path.join(source, entry), path.join(destination, entry), { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function desktopEntry({ config, packageName, binaryName, exec }) {
|
|
28
|
+
return [
|
|
29
|
+
"[Desktop Entry]",
|
|
30
|
+
"Type=Application",
|
|
31
|
+
`Name=${escapeDesktop(config.app.productName)}`,
|
|
32
|
+
`Comment=${escapeDesktop(description(config))}`,
|
|
33
|
+
`Exec=${escapeDesktop(exec)}`,
|
|
34
|
+
`Icon=${packageName}`,
|
|
35
|
+
"Terminal=false",
|
|
36
|
+
`Categories=${desktopCategory(config.bundle.category)}`,
|
|
37
|
+
""
|
|
38
|
+
].join("\n");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function rpmSafe(value) {
|
|
42
|
+
return String(value).replace(/%/g, "%%").replace(/\n/g, " ");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function buildDeb({
|
|
46
|
+
config,
|
|
47
|
+
productName,
|
|
48
|
+
version,
|
|
49
|
+
payload,
|
|
50
|
+
binaryName,
|
|
51
|
+
icons,
|
|
52
|
+
licenseSource,
|
|
53
|
+
outputDirectory,
|
|
54
|
+
stageDirectory,
|
|
55
|
+
logger
|
|
56
|
+
}) {
|
|
57
|
+
const dpkgDeb = requireTool("dpkg-deb", "install dpkg-dev on Debian/Ubuntu");
|
|
58
|
+
const packageName = safeName(productName);
|
|
59
|
+
const architecture = debArchitecture();
|
|
60
|
+
const root = path.join(stageDirectory, "deb");
|
|
61
|
+
const packageRoot = path.join(root, "package");
|
|
62
|
+
const installRoot = path.join(packageRoot, "opt", packageName);
|
|
63
|
+
const artifact = path.join(outputDirectory, `${packageName}_${debVersion(version)}_${architecture}.deb`);
|
|
64
|
+
|
|
65
|
+
mkdirSync(installRoot, { recursive: true });
|
|
66
|
+
copyContents(payload, installRoot);
|
|
67
|
+
|
|
68
|
+
mkdirSync(path.join(packageRoot, "usr", "bin"), { recursive: true });
|
|
69
|
+
symlinkSync(path.join("..", "..", "opt", packageName, binaryName), path.join(packageRoot, "usr", "bin", packageName));
|
|
70
|
+
|
|
71
|
+
const applications = path.join(packageRoot, "usr", "share", "applications");
|
|
72
|
+
mkdirSync(applications, { recursive: true });
|
|
73
|
+
writeFileSync(
|
|
74
|
+
path.join(applications, `${packageName}.desktop`),
|
|
75
|
+
desktopEntry({ config, packageName, binaryName, exec: path.join("/opt", packageName, binaryName) })
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const iconDirectory = path.join(packageRoot, "usr", "share", "icons", "hicolor", "512x512", "apps");
|
|
79
|
+
mkdirSync(iconDirectory, { recursive: true });
|
|
80
|
+
cpSync(icons.png, path.join(iconDirectory, `${packageName}.png`));
|
|
81
|
+
|
|
82
|
+
const docDirectory = path.join(packageRoot, "usr", "share", "doc", packageName);
|
|
83
|
+
mkdirSync(docDirectory, { recursive: true });
|
|
84
|
+
if (licenseSource) cpSync(licenseSource, path.join(docDirectory, "copyright"));
|
|
85
|
+
|
|
86
|
+
const size = Math.ceil(directorySize(packageRoot) / 1024);
|
|
87
|
+
const descriptionLines = String(longDescription(config)).split(/\r?\n/);
|
|
88
|
+
const control = [
|
|
89
|
+
`Package: ${packageName}`,
|
|
90
|
+
`Version: ${debVersion(version)}`,
|
|
91
|
+
`Architecture: ${architecture}`,
|
|
92
|
+
`Maintainer: ${maintainer(config)}`,
|
|
93
|
+
`Installed-Size: ${size}`,
|
|
94
|
+
"Section: utils",
|
|
95
|
+
"Priority: optional",
|
|
96
|
+
...(config.bundle.homepage ? [`Homepage: ${config.bundle.homepage}`] : []),
|
|
97
|
+
`Description: ${description(config)}`,
|
|
98
|
+
...descriptionLines.slice(1).map((line) => ` ${line || "."}`),
|
|
99
|
+
""
|
|
100
|
+
].join("\n");
|
|
101
|
+
|
|
102
|
+
mkdirSync(path.join(packageRoot, "DEBIAN"), { recursive: true });
|
|
103
|
+
writeFileSync(path.join(packageRoot, "DEBIAN", "control"), control);
|
|
104
|
+
|
|
105
|
+
if (logger) logger.info(`[deb] compiling ${path.basename(artifact)}`);
|
|
106
|
+
await runTool(dpkgDeb, ["--build", "--root-owner-group", packageRoot, artifact], {
|
|
107
|
+
cwd: stageDirectory,
|
|
108
|
+
logger
|
|
109
|
+
});
|
|
110
|
+
return artifact;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function findRpm(directory) {
|
|
114
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
115
|
+
const target = path.join(directory, entry.name);
|
|
116
|
+
if (entry.isDirectory()) {
|
|
117
|
+
const found = findRpm(target);
|
|
118
|
+
if (found) return found;
|
|
119
|
+
} else if (entry.isFile() && entry.name.endsWith(".rpm")) {
|
|
120
|
+
return target;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function buildRpm({
|
|
127
|
+
config,
|
|
128
|
+
productName,
|
|
129
|
+
version,
|
|
130
|
+
payload,
|
|
131
|
+
binaryName,
|
|
132
|
+
icons,
|
|
133
|
+
licenseSource,
|
|
134
|
+
outputDirectory,
|
|
135
|
+
stageDirectory,
|
|
136
|
+
logger
|
|
137
|
+
}) {
|
|
138
|
+
const rpmbuild = requireTool("rpmbuild", "install rpm-build on Fedora/RHEL or rpm on Debian/Ubuntu");
|
|
139
|
+
const packageName = safeName(productName);
|
|
140
|
+
const architecture = rpmArchitecture();
|
|
141
|
+
const root = path.join(stageDirectory, "rpm");
|
|
142
|
+
const top = path.join(root, "rpmbuild");
|
|
143
|
+
const specDirectory = path.join(top, "SPECS");
|
|
144
|
+
const buildDirectory = path.join(top, "BUILD");
|
|
145
|
+
const rpmDirectory = path.join(outputDirectory, "rpms");
|
|
146
|
+
const spec = path.join(specDirectory, `${packageName}.spec`);
|
|
147
|
+
const docLine = licenseSource ? `/usr/share/doc/${packageName}/LICENSE` : "";
|
|
148
|
+
const size = Math.ceil(directorySize(payload) / 1024);
|
|
149
|
+
|
|
150
|
+
for (const directory of [specDirectory, buildDirectory, path.join(top, "SOURCES"), path.join(top, "BUILDROOT"), path.join(top, "SRPMS"), rpmDirectory]) {
|
|
151
|
+
mkdirSync(directory, { recursive: true });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const lines = [
|
|
155
|
+
`%define _binary_payload w9.gzdio`,
|
|
156
|
+
`%define _unpackaged_files_terminate_build 1`,
|
|
157
|
+
`Name: ${packageName}`,
|
|
158
|
+
`Version: ${rpmVersion(version)}`,
|
|
159
|
+
"Release: 1",
|
|
160
|
+
`Summary: ${rpmSafe(description(config))}`,
|
|
161
|
+
"License: MIT",
|
|
162
|
+
...(config.bundle.homepage ? [`URL: ${config.bundle.homepage}`] : []),
|
|
163
|
+
`BuildArch: ${architecture}`,
|
|
164
|
+
"AutoReqProv: no",
|
|
165
|
+
"Group: Applications/Development",
|
|
166
|
+
"",
|
|
167
|
+
"%description",
|
|
168
|
+
rpmSafe(longDescription(config)),
|
|
169
|
+
"",
|
|
170
|
+
"%install",
|
|
171
|
+
`mkdir -p "%{buildroot}/opt/${packageName}"`,
|
|
172
|
+
`cp -R "${payload}/." "%{buildroot}/opt/${packageName}/"`,
|
|
173
|
+
`mkdir -p "%{buildroot}/usr/bin"`,
|
|
174
|
+
`ln -sf "../../opt/${packageName}/${binaryName}" "%{buildroot}/usr/bin/${packageName}"`,
|
|
175
|
+
`mkdir -p "%{buildroot}/usr/share/applications"`,
|
|
176
|
+
`cat > "%{buildroot}/usr/share/applications/${packageName}.desktop" <<'FLARE_DESKTOP_EOF'`,
|
|
177
|
+
desktopEntry({ config, packageName, binaryName, exec: path.join("/opt", packageName, binaryName) }),
|
|
178
|
+
"FLARE_DESKTOP_EOF",
|
|
179
|
+
`mkdir -p "%{buildroot}/usr/share/icons/hicolor/512x512/apps"`,
|
|
180
|
+
`cp "${icons.png}" "%{buildroot}/usr/share/icons/hicolor/512x512/apps/${packageName}.png"`,
|
|
181
|
+
...(licenseSource
|
|
182
|
+
? [
|
|
183
|
+
`mkdir -p "%{buildroot}/usr/share/doc/${packageName}"`,
|
|
184
|
+
`cp "${licenseSource}" "%{buildroot}/usr/share/doc/${packageName}/LICENSE"`
|
|
185
|
+
]
|
|
186
|
+
: []),
|
|
187
|
+
"",
|
|
188
|
+
"%files",
|
|
189
|
+
`/opt/${packageName}`,
|
|
190
|
+
`/usr/bin/${packageName}`,
|
|
191
|
+
`/usr/share/applications/${packageName}.desktop`,
|
|
192
|
+
`/usr/share/icons/hicolor/512x512/apps/${packageName}.png`,
|
|
193
|
+
...(docLine ? [docLine] : []),
|
|
194
|
+
"",
|
|
195
|
+
`# installed size: ${size} KiB`
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
writeFileSync(spec, `${lines.join("\n")}\n`, "utf8");
|
|
199
|
+
|
|
200
|
+
if (logger) logger.info(`[rpm] compiling ${packageName}-${rpmVersion(version)}-1.${architecture}.rpm`);
|
|
201
|
+
await runTool(
|
|
202
|
+
rpmbuild,
|
|
203
|
+
[
|
|
204
|
+
"-bb",
|
|
205
|
+
"--define",
|
|
206
|
+
`_topdir ${top}`,
|
|
207
|
+
"--define",
|
|
208
|
+
`_builddir ${buildDirectory}`,
|
|
209
|
+
"--define",
|
|
210
|
+
`_rpmdir ${rpmDirectory}`,
|
|
211
|
+
"--define",
|
|
212
|
+
`_srcrpmdir ${path.join(top, "SRPMS")}`,
|
|
213
|
+
"--define",
|
|
214
|
+
`_specdir ${specDirectory}`,
|
|
215
|
+
"--define",
|
|
216
|
+
`_sourcedir ${path.join(top, "SOURCES")}`,
|
|
217
|
+
"--define",
|
|
218
|
+
`_buildrootdir ${path.join(top, "BUILDROOT")}`,
|
|
219
|
+
spec
|
|
220
|
+
],
|
|
221
|
+
{ cwd: stageDirectory, logger }
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const artifact = findRpm(rpmDirectory);
|
|
225
|
+
if (!artifact) throw new Error(`rpmbuild did not produce an rpm in '${rpmDirectory}'`);
|
|
226
|
+
return artifact;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export async function buildAppImage({
|
|
230
|
+
config,
|
|
231
|
+
productName,
|
|
232
|
+
version,
|
|
233
|
+
payload,
|
|
234
|
+
binaryName,
|
|
235
|
+
icons,
|
|
236
|
+
licenseSource,
|
|
237
|
+
outputDirectory,
|
|
238
|
+
stageDirectory,
|
|
239
|
+
logger
|
|
240
|
+
}) {
|
|
241
|
+
const appimagetool = requireTool("appimagetool", "download appimagetool from https://github.com/AppImage/appimagetool");
|
|
242
|
+
const packageName = safeName(productName);
|
|
243
|
+
const architecture = appImageArchitecture();
|
|
244
|
+
const appDir = path.join(stageDirectory, "appimage", "AppDir");
|
|
245
|
+
const binaryDirectory = path.join(appDir, "usr", "bin");
|
|
246
|
+
const applications = path.join(appDir, "usr", "share", "applications");
|
|
247
|
+
const iconDirectory = path.join(appDir, "usr", "share", "icons", "hicolor", "512x512", "apps");
|
|
248
|
+
const artifact = path.join(outputDirectory, `${packageName}_${version}_${architecture}.AppImage`);
|
|
249
|
+
|
|
250
|
+
copyContents(payload, binaryDirectory);
|
|
251
|
+
mkdirSync(applications, { recursive: true });
|
|
252
|
+
mkdirSync(iconDirectory, { recursive: true });
|
|
253
|
+
|
|
254
|
+
const desktop = desktopEntry({ config, packageName, binaryName, exec: packageName });
|
|
255
|
+
writeFileSync(path.join(applications, `${packageName}.desktop`), desktop);
|
|
256
|
+
writeFileSync(path.join(appDir, `${packageName}.desktop`), desktop);
|
|
257
|
+
cpSync(icons.png, path.join(iconDirectory, `${packageName}.png`));
|
|
258
|
+
cpSync(icons.png, path.join(appDir, `${packageName}.png`));
|
|
259
|
+
symlinkSync(`${packageName}.png`, path.join(appDir, ".DirIcon"));
|
|
260
|
+
|
|
261
|
+
if (licenseSource) {
|
|
262
|
+
const doc = path.join(appDir, "usr", "share", "doc", packageName);
|
|
263
|
+
mkdirSync(doc, { recursive: true });
|
|
264
|
+
cpSync(licenseSource, path.join(doc, "LICENSE"));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const appRun = path.join(appDir, "AppRun");
|
|
268
|
+
writeFileSync(
|
|
269
|
+
appRun,
|
|
270
|
+
`#!/bin/sh\nHERE="$(dirname "$(readlink -f "$0")")"\nexec "$HERE/usr/bin/${binaryName}" "$@"\n`
|
|
271
|
+
);
|
|
272
|
+
chmodSync(appRun, 0o755);
|
|
273
|
+
chmodSync(path.join(binaryDirectory, binaryName), 0o755);
|
|
274
|
+
|
|
275
|
+
mkdirSync(outputDirectory, { recursive: true });
|
|
276
|
+
if (logger) logger.info(`[appimage] compiling ${path.basename(artifact)}`);
|
|
277
|
+
await runTool(appimagetool, [appDir, artifact], {
|
|
278
|
+
cwd: stageDirectory,
|
|
279
|
+
env: { ARCH: architecture },
|
|
280
|
+
logger
|
|
281
|
+
});
|
|
282
|
+
return artifact;
|
|
283
|
+
}
|