@gtkx/cli 1.2.0 → 1.2.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/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +2 -0
- package/dist/builder.js.map +1 -1
- package/dist/deploy/freedesktop/copyright.d.ts +2 -2
- package/dist/deploy/freedesktop/copyright.d.ts.map +1 -1
- package/dist/deploy/freedesktop/copyright.js +105 -14
- package/dist/deploy/freedesktop/copyright.js.map +1 -1
- package/dist/deploy/node-runtime/download.d.ts +5 -1
- package/dist/deploy/node-runtime/download.d.ts.map +1 -1
- package/dist/deploy/node-runtime/download.js +6 -3
- package/dist/deploy/node-runtime/download.js.map +1 -1
- package/dist/deploy/node-runtime/index.d.ts +2 -1
- package/dist/deploy/node-runtime/index.d.ts.map +1 -1
- package/dist/deploy/node-runtime/index.js +10 -9
- package/dist/deploy/node-runtime/index.js.map +1 -1
- package/dist/deploy/node-runtime/license.d.ts +4 -0
- package/dist/deploy/node-runtime/license.d.ts.map +1 -0
- package/dist/deploy/node-runtime/license.js +19 -0
- package/dist/deploy/node-runtime/license.js.map +1 -0
- package/dist/deploy/notices/collect.d.ts +8 -0
- package/dist/deploy/notices/collect.d.ts.map +1 -0
- package/dist/deploy/notices/collect.js +17 -0
- package/dist/deploy/notices/collect.js.map +1 -0
- package/dist/deploy/notices/dependencies.d.ts +5 -0
- package/dist/deploy/notices/dependencies.d.ts.map +1 -0
- package/dist/deploy/notices/dependencies.js +39 -0
- package/dist/deploy/notices/dependencies.js.map +1 -0
- package/dist/deploy/notices/gtkx.d.ts +6 -0
- package/dist/deploy/notices/gtkx.d.ts.map +1 -0
- package/dist/deploy/notices/gtkx.js +68 -0
- package/dist/deploy/notices/gtkx.js.map +1 -0
- package/dist/deploy/notices/libraries.d.ts +4 -0
- package/dist/deploy/notices/libraries.d.ts.map +1 -0
- package/dist/deploy/notices/libraries.js +60 -0
- package/dist/deploy/notices/libraries.js.map +1 -0
- package/dist/deploy/notices/node-runtime.d.ts +4 -0
- package/dist/deploy/notices/node-runtime.d.ts.map +1 -0
- package/dist/deploy/notices/node-runtime.js +78 -0
- package/dist/deploy/notices/node-runtime.js.map +1 -0
- package/dist/deploy/notices/packages.d.ts +13 -0
- package/dist/deploy/notices/packages.d.ts.map +1 -0
- package/dist/deploy/notices/packages.js +115 -0
- package/dist/deploy/notices/packages.js.map +1 -0
- package/dist/deploy/notices/render.d.ts +4 -0
- package/dist/deploy/notices/render.d.ts.map +1 -0
- package/dist/deploy/notices/render.js +41 -0
- package/dist/deploy/notices/render.js.map +1 -0
- package/dist/deploy/notices/text.d.ts +5 -0
- package/dist/deploy/notices/text.d.ts.map +1 -0
- package/dist/deploy/notices/text.js +34 -0
- package/dist/deploy/notices/text.js.map +1 -0
- package/dist/deploy/payload/copy-tree.d.ts +2 -1
- package/dist/deploy/payload/copy-tree.d.ts.map +1 -1
- package/dist/deploy/payload/copy-tree.js +3 -1
- package/dist/deploy/payload/copy-tree.js.map +1 -1
- package/dist/deploy/payload/stage.d.ts +7 -3
- package/dist/deploy/payload/stage.d.ts.map +1 -1
- package/dist/deploy/payload/stage.js +25 -14
- package/dist/deploy/payload/stage.js.map +1 -1
- package/dist/deploy/run-deploy.d.ts.map +1 -1
- package/dist/deploy/run-deploy.js +22 -5
- package/dist/deploy/run-deploy.js.map +1 -1
- package/dist/deploy/settings/extra-files.d.ts +4 -0
- package/dist/deploy/settings/extra-files.d.ts.map +1 -0
- package/dist/deploy/settings/extra-files.js +11 -0
- package/dist/deploy/settings/extra-files.js.map +1 -0
- package/dist/deploy/settings/index.d.ts.map +1 -1
- package/dist/deploy/settings/index.js +2 -1
- package/dist/deploy/settings/index.js.map +1 -1
- package/dist/deploy/settings/package-manifest.d.ts +1 -0
- package/dist/deploy/settings/package-manifest.d.ts.map +1 -1
- package/dist/deploy/settings/package-manifest.js +7 -0
- package/dist/deploy/settings/package-manifest.js.map +1 -1
- package/dist/deploy/settings/paths.d.ts +2 -1
- package/dist/deploy/settings/paths.d.ts.map +1 -1
- package/dist/deploy/settings/paths.js +1 -1
- package/dist/deploy/settings/paths.js.map +1 -1
- package/dist/deploy/targets/flatpak-manifest.d.ts +3 -1
- package/dist/deploy/targets/flatpak-manifest.d.ts.map +1 -1
- package/dist/deploy/targets/flatpak-manifest.js +50 -4
- package/dist/deploy/targets/flatpak-manifest.js.map +1 -1
- package/dist/deploy/targets/flatpak-source.d.ts.map +1 -1
- package/dist/deploy/targets/flatpak-source.js +55 -1
- package/dist/deploy/targets/flatpak-source.js.map +1 -1
- package/dist/deploy/types.d.ts +22 -2
- package/dist/deploy/types.d.ts.map +1 -1
- package/dist/deploy/types.js.map +1 -1
- package/dist/vite-plugins/bundled-packages.d.ts +5 -0
- package/dist/vite-plugins/bundled-packages.d.ts.map +1 -0
- package/dist/vite-plugins/bundled-packages.js +63 -0
- package/dist/vite-plugins/bundled-packages.js.map +1 -0
- package/package.json +9 -9
- package/src/builder.ts +2 -0
- package/src/deploy/freedesktop/copyright.ts +153 -18
- package/src/deploy/node-runtime/download.ts +13 -4
- package/src/deploy/node-runtime/index.ts +17 -11
- package/src/deploy/node-runtime/license.ts +24 -0
- package/src/deploy/notices/collect.ts +26 -0
- package/src/deploy/notices/dependencies.ts +53 -0
- package/src/deploy/notices/gtkx.ts +92 -0
- package/src/deploy/notices/libraries.ts +81 -0
- package/src/deploy/notices/node-runtime.ts +101 -0
- package/src/deploy/notices/packages.ts +168 -0
- package/src/deploy/notices/render.ts +52 -0
- package/src/deploy/notices/text.ts +42 -0
- package/src/deploy/payload/copy-tree.ts +5 -1
- package/src/deploy/payload/stage.ts +40 -16
- package/src/deploy/run-deploy.ts +27 -5
- package/src/deploy/settings/extra-files.ts +21 -0
- package/src/deploy/settings/index.ts +2 -1
- package/src/deploy/settings/package-manifest.ts +10 -0
- package/src/deploy/settings/paths.ts +1 -1
- package/src/deploy/targets/flatpak-manifest.ts +69 -4
- package/src/deploy/targets/flatpak-source.ts +87 -1
- package/src/deploy/types.ts +27 -1
- package/src/vite-plugins/bundled-packages.ts +95 -0
- package/LICENSE +0 -373
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { warn } from "@gtkx/utils";
|
|
2
|
+
import type { DeploySettings, NodeRuntime, NoticeSection } from "../types.js";
|
|
3
|
+
import { sourcePathFor } from "../node-runtime/index.js";
|
|
4
|
+
import { licenseBesideNode } from "../node-runtime/license.js";
|
|
5
|
+
import { NODE_FILENAME } from "../payload/launcher.js";
|
|
6
|
+
import { nodeLicenseDestination } from "../payload/stage.js";
|
|
7
|
+
import { copyrightLines, readLicenseText } from "./text.js";
|
|
8
|
+
|
|
9
|
+
const TITLE = "Node.js runtime";
|
|
10
|
+
const LICENSE_NAME = "Node.js";
|
|
11
|
+
const SOURCE_URL = "https://github.com/nodejs/node";
|
|
12
|
+
const LICENSE_URL = "https://github.com/nodejs/node/blob/main/LICENSE";
|
|
13
|
+
|
|
14
|
+
const SUMMARY = [
|
|
15
|
+
"This package carries the Node.js runtime that runs the application. Its license is the one Node.js",
|
|
16
|
+
"publishes as a single document, covering Node.js itself under the MIT terms together with the",
|
|
17
|
+
"components it embeds under theirs, among them V8, OpenSSL, ICU, libuv, zlib, brotli and llhttp.",
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const CARRIED_SUMMARY = ["The notice below is that document, reproduced from the release this package bundles."];
|
|
21
|
+
|
|
22
|
+
const MISSING_SUMMARY = [
|
|
23
|
+
"This run had no copy of that document at hand, so the notice below names it and it is published at",
|
|
24
|
+
`${LICENSE_URL}.`,
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const MISSING_LICENSE =
|
|
28
|
+
"Cannot read a license file for the Node.js runtime this deploy bundles. An official release unpacks it " +
|
|
29
|
+
'next to the binary, so point `deploy.node.path` at one, or use `deploy.node.source: "download"`, and ' +
|
|
30
|
+
"the notices carry it.";
|
|
31
|
+
|
|
32
|
+
const isSourceMode = (settings: DeploySettings): boolean => settings.deploy.flatpak?.mode === "source";
|
|
33
|
+
|
|
34
|
+
const versionFor = (settings: DeploySettings, node: NodeRuntime | null): string =>
|
|
35
|
+
node?.version ?? settings.deploy.node?.version ?? process.versions.node;
|
|
36
|
+
|
|
37
|
+
const licenseFileFor = (settings: DeploySettings, node: NodeRuntime | null): string | null => {
|
|
38
|
+
if (node !== null) {
|
|
39
|
+
return node.licenseFile;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if ((settings.deploy.node?.source ?? "download") === "download") {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return licenseBesideNode(sourcePathFor(settings));
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const textFor = (settings: DeploySettings, node: NodeRuntime | null): string | null => {
|
|
50
|
+
const licenseFile = licenseFileFor(settings, node);
|
|
51
|
+
|
|
52
|
+
return licenseFile === null ? null : readLicenseText(licenseFile);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const extensionSummary = (settings: DeploySettings): string[] => [
|
|
56
|
+
"The flatpak built from source copies its runtime out of the Node SDK extension in the build sandbox.",
|
|
57
|
+
`That build installs the license file the extension ships as ${nodeLicenseDestination(settings)}, and`,
|
|
58
|
+
"installs nothing when the extension ships none, so read the text at the address above if that file is",
|
|
59
|
+
"not in the package you received.",
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const carriedSummary = (settings: DeploySettings, text: string | null): string[] => {
|
|
63
|
+
if (isSourceMode(settings)) {
|
|
64
|
+
return [...MISSING_SUMMARY, ...extensionSummary(settings)];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return text === null ? MISSING_SUMMARY : CARRIED_SUMMARY;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const summaryFor = (settings: DeploySettings, text: string | null): string[] => [
|
|
71
|
+
...SUMMARY,
|
|
72
|
+
...carriedSummary(settings, text),
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const warnMissingLicense = (node: NodeRuntime | null, text: string | null): void => {
|
|
76
|
+
if (node === null || text !== null) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
warn(MISSING_LICENSE);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const nodeNotices = (settings: DeploySettings, node: NodeRuntime | null): NoticeSection => {
|
|
84
|
+
const text = textFor(settings, node);
|
|
85
|
+
warnMissingLicense(node, text);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
title: TITLE,
|
|
89
|
+
files: [`lib/${settings.binaryName}/${NODE_FILENAME}`],
|
|
90
|
+
summary: summaryFor(settings, text),
|
|
91
|
+
notices: [{
|
|
92
|
+
subject: `Node.js ${versionFor(settings, node)}`,
|
|
93
|
+
license: LICENSE_NAME,
|
|
94
|
+
source: SOURCE_URL,
|
|
95
|
+
copyright: copyrightLines(text),
|
|
96
|
+
text,
|
|
97
|
+
}],
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export { nodeNotices };
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { isRecord, sortStringsBy, warn } from "@gtkx/utils";
|
|
2
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import type { DeploySettings } from "../types.js";
|
|
5
|
+
import { BUNDLED_PACKAGES_FILENAME } from "../../vite-plugins/bundled-packages.js";
|
|
6
|
+
import { type PackageManifest, readPackageManifest } from "../settings/package-manifest.js";
|
|
7
|
+
import { copyrightLines, licenseTextIn } from "./text.js";
|
|
8
|
+
|
|
9
|
+
type RecordedPackage = {
|
|
10
|
+
name: string;
|
|
11
|
+
version: string | null;
|
|
12
|
+
dir: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type BundledPackage = {
|
|
16
|
+
name: string;
|
|
17
|
+
version: string | null;
|
|
18
|
+
license: string | null;
|
|
19
|
+
source: string | null;
|
|
20
|
+
copyright: string[];
|
|
21
|
+
text: string | null;
|
|
22
|
+
isPresent: boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const MANIFEST_FILENAME = "package.json";
|
|
26
|
+
const GIT_PREFIX = /^git\+/;
|
|
27
|
+
const GIT_SUFFIX = /\.git$/;
|
|
28
|
+
const GIT_SCHEME = /^git:\/\//;
|
|
29
|
+
const GITHUB_SLUG = /^(?:github:)?(?<slug>[\w.-]+\/[\w.-]+)$/;
|
|
30
|
+
|
|
31
|
+
const realPath = (path: string): string => {
|
|
32
|
+
try {
|
|
33
|
+
return realpathSync(path);
|
|
34
|
+
} catch {
|
|
35
|
+
return path;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const optionalString = (value: unknown): string | null => (typeof value === "string" ? value : null);
|
|
40
|
+
|
|
41
|
+
const recordedPackage = (entry: unknown): RecordedPackage | null => {
|
|
42
|
+
if (!isRecord(entry) || typeof entry.name !== "string" || typeof entry.dir !== "string") {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { name: entry.name, version: optionalString(entry.version), dir: entry.dir };
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const recordedList = (value: unknown): RecordedPackage[] => {
|
|
50
|
+
if (!Array.isArray(value)) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const entries: unknown[] = value;
|
|
55
|
+
|
|
56
|
+
return entries.map((entry) => recordedPackage(entry)).filter((entry) => entry !== null);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const readRecordedPackages = (settings: DeploySettings): RecordedPackage[] => {
|
|
60
|
+
const path = join(settings.paths.dist, BUNDLED_PACKAGES_FILENAME);
|
|
61
|
+
|
|
62
|
+
if (!existsSync(path)) {
|
|
63
|
+
throw new Error(`Cannot deploy: ${path} is missing. Run \`gtkx build\` first.`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
67
|
+
|
|
68
|
+
return recordedList(isRecord(parsed) ? parsed.packages : null);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const authorLine = (manifest: PackageManifest): string[] => {
|
|
72
|
+
const { name, email } = manifest.author;
|
|
73
|
+
|
|
74
|
+
if (name === null) {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return [email === null ? name : `${name} <${email}>`];
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const sourceUrl = (manifest: PackageManifest): string | null => {
|
|
82
|
+
const configured = manifest.repository ?? manifest.homepage;
|
|
83
|
+
|
|
84
|
+
if (configured === null) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const url = configured.replace(GIT_PREFIX, "").replace(GIT_SUFFIX, "").replace(GIT_SCHEME, "https://");
|
|
89
|
+
const slug = GITHUB_SLUG.exec(url)?.groups?.slug;
|
|
90
|
+
|
|
91
|
+
if (slug !== undefined) {
|
|
92
|
+
return `https://github.com/${slug}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return url.startsWith("http") ? url : null;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const missingPackage = (entry: RecordedPackage): BundledPackage => ({
|
|
99
|
+
name: entry.name,
|
|
100
|
+
version: entry.version,
|
|
101
|
+
license: null,
|
|
102
|
+
source: null,
|
|
103
|
+
copyright: [],
|
|
104
|
+
text: null,
|
|
105
|
+
isPresent: false,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const packageIn = (entry: RecordedPackage, dir: string): BundledPackage => {
|
|
109
|
+
if (!existsSync(join(dir, MANIFEST_FILENAME))) {
|
|
110
|
+
return missingPackage(entry);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const manifest = readPackageManifest(dir);
|
|
114
|
+
const text = licenseTextIn(dir);
|
|
115
|
+
const copyright = copyrightLines(text);
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
name: manifest.name ?? entry.name,
|
|
119
|
+
version: manifest.version ?? entry.version,
|
|
120
|
+
license: manifest.license,
|
|
121
|
+
source: sourceUrl(manifest),
|
|
122
|
+
copyright: copyright.length === 0 ? authorLine(manifest) : copyright,
|
|
123
|
+
text,
|
|
124
|
+
isPresent: true,
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const keyFor = (entry: BundledPackage): string => `${entry.name}@${entry.version ?? ""}`;
|
|
129
|
+
|
|
130
|
+
const dedupe = (entries: BundledPackage[]): BundledPackage[] => {
|
|
131
|
+
const unique: Map<string, BundledPackage> = new Map();
|
|
132
|
+
|
|
133
|
+
for (const entry of entries) {
|
|
134
|
+
unique.set(keyFor(entry), entry);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return sortStringsBy(unique.values(), keyFor);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const warnMissing = (entries: BundledPackage[]): void => {
|
|
141
|
+
const missing = entries.filter((entry) => !entry.isPresent);
|
|
142
|
+
|
|
143
|
+
if (missing.length === 0) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
warn(
|
|
148
|
+
`The third-party notices name ${missing.map((entry) => keyFor(entry)).join(", ")} without any license: ` +
|
|
149
|
+
"`gtkx build` recorded where each package was installed and those directories are no longer there. " +
|
|
150
|
+
"Deploy from the tree the build ran in, or build again, so the notices carry their terms.",
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const bundledPackages = (settings: DeploySettings): BundledPackage[] => {
|
|
155
|
+
const root = realPath(settings.paths.root);
|
|
156
|
+
|
|
157
|
+
const entries = readRecordedPackages(settings)
|
|
158
|
+
.map((entry) => ({ entry, dir: resolve(settings.paths.dist, entry.dir) }))
|
|
159
|
+
.filter(({ dir }) => realPath(dir) !== root)
|
|
160
|
+
.map(({ entry, dir }) => packageIn(entry, dir));
|
|
161
|
+
|
|
162
|
+
const bundled = dedupe(entries);
|
|
163
|
+
warnMissing(bundled);
|
|
164
|
+
|
|
165
|
+
return bundled;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export { type BundledPackage, bundledPackages };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { DeploySettings, Notice, NoticeSection } from "../types.js";
|
|
2
|
+
|
|
3
|
+
const RULE_WIDTH = 100;
|
|
4
|
+
const HEADING = "THIRD-PARTY NOTICES";
|
|
5
|
+
const SECTION_RULE = "=".repeat(RULE_WIDTH);
|
|
6
|
+
const NOTICE_RULE = "-".repeat(RULE_WIDTH);
|
|
7
|
+
const FILE_SEPARATOR = ", ";
|
|
8
|
+
|
|
9
|
+
const introFor = (settings: DeploySettings): string[] => [
|
|
10
|
+
HEADING,
|
|
11
|
+
`${settings.name} ${settings.versions.upstream}`,
|
|
12
|
+
"",
|
|
13
|
+
`This file lists the software that ships inside ${settings.name} without having been written by its`,
|
|
14
|
+
"authors, and the terms each piece of it is distributed under.",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const headerFor = (section: NoticeSection): string[] => [
|
|
18
|
+
SECTION_RULE,
|
|
19
|
+
section.title,
|
|
20
|
+
...(section.files.length === 0 ? [] : [`Files: ${section.files.join(FILE_SEPARATOR)}`]),
|
|
21
|
+
SECTION_RULE,
|
|
22
|
+
"",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const detailLines = (notice: Notice): string[] => [
|
|
26
|
+
`License: ${notice.license}`,
|
|
27
|
+
...(notice.source === null ? [] : [`Source: ${notice.source}`]),
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const bodyLines = (notice: Notice): string[] =>
|
|
31
|
+
notice.text === null ? notice.copyright : ["", notice.text];
|
|
32
|
+
|
|
33
|
+
const renderNotice = (notice: Notice): string[] => [
|
|
34
|
+
NOTICE_RULE,
|
|
35
|
+
notice.subject,
|
|
36
|
+
...detailLines(notice),
|
|
37
|
+
NOTICE_RULE,
|
|
38
|
+
...bodyLines(notice),
|
|
39
|
+
"",
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const renderSection = (section: NoticeSection): string[] => [
|
|
43
|
+
...headerFor(section),
|
|
44
|
+
...section.summary,
|
|
45
|
+
"",
|
|
46
|
+
...section.notices.flatMap((notice) => renderNotice(notice)),
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const renderNotices = (settings: DeploySettings, sections: NoticeSection[]): string =>
|
|
50
|
+
[...introFor(settings), "", ...sections.flatMap((section) => renderSection(section)), ""].join("\n");
|
|
51
|
+
|
|
52
|
+
export { renderNotices };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
const LICENSE_FILE = /^(?:licen[cs]e|copying|notice)(?:[-._].*)?$/i;
|
|
5
|
+
const COPYRIGHT_LINE = /^(?:Copyright\b|©)/;
|
|
6
|
+
const MAX_COPYRIGHT_LINES = 20;
|
|
7
|
+
const MORE_COPYRIGHT = "and the other holders named in the license text";
|
|
8
|
+
|
|
9
|
+
const readLicenseText = (path: string): string | null => {
|
|
10
|
+
try {
|
|
11
|
+
return readFileSync(path, "utf8").trimEnd();
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const licenseFilesIn = (dir: string): string[] =>
|
|
18
|
+
readdirSync(dir, { withFileTypes: true })
|
|
19
|
+
.filter((entry) => entry.isFile() && LICENSE_FILE.test(entry.name))
|
|
20
|
+
.map((entry) => entry.name)
|
|
21
|
+
.toSorted((left, right) => left.localeCompare(right));
|
|
22
|
+
|
|
23
|
+
const licenseTextIn = (dir: string): string | null => {
|
|
24
|
+
const texts = licenseFilesIn(dir)
|
|
25
|
+
.map((name) => readLicenseText(join(dir, name)))
|
|
26
|
+
.filter((text) => text !== null);
|
|
27
|
+
|
|
28
|
+
return texts.length === 0 ? null : texts.join("\n\n");
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const copyrightLines = (text: string | null): string[] => {
|
|
32
|
+
const found = (text ?? "")
|
|
33
|
+
.split("\n")
|
|
34
|
+
.map((line) => line.trim())
|
|
35
|
+
.filter((line) => COPYRIGHT_LINE.test(line));
|
|
36
|
+
|
|
37
|
+
const unique = [...new Set(found)];
|
|
38
|
+
|
|
39
|
+
return unique.length > MAX_COPYRIGHT_LINES ? [...unique.slice(0, MAX_COPYRIGHT_LINES), MORE_COPYRIGHT] : unique;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export { copyrightLines, licenseTextIn, readLicenseText };
|
|
@@ -7,6 +7,7 @@ const EXECUTABLE_MODE = 0o755;
|
|
|
7
7
|
const READABLE_MODE = 0o644;
|
|
8
8
|
const EXECUTABLE_SUFFIXES = [".node", ".so"];
|
|
9
9
|
const MODE_MASK = 0o7777;
|
|
10
|
+
const EXECUTE_MASK = 0o111;
|
|
10
11
|
|
|
11
12
|
const modeFor = (rel: string): number => {
|
|
12
13
|
const name = rel.split("/").at(-1) ?? rel;
|
|
@@ -44,8 +45,11 @@ const writeInto = (root: string, rel: string, contents: string, mode = READABLE_
|
|
|
44
45
|
|
|
45
46
|
const sourceMode = (path: string): number => statSync(path).mode & MODE_MASK;
|
|
46
47
|
|
|
48
|
+
const executableModeFor = (path: string): number =>
|
|
49
|
+
(sourceMode(path) & EXECUTE_MASK) === 0 ? READABLE_MODE : EXECUTABLE_MODE;
|
|
50
|
+
|
|
47
51
|
const copyTree = (root: string, relBase: string, sourceDir: string): StagedFile[] =>
|
|
48
52
|
listFilesRecursive(sourceDir)
|
|
49
53
|
.map((file) => copyInto(root, join(relBase, file.rel), file.absPath, sourceMode(file.absPath)));
|
|
50
54
|
|
|
51
|
-
export { copyInto, copyTree, EXECUTABLE_MODE, writeInto };
|
|
55
|
+
export { copyInto, copyTree, EXECUTABLE_MODE, executableModeFor, writeInto };
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { sortStringsBy } from "@gtkx/utils";
|
|
2
2
|
import { existsSync, rmSync } from "node:fs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
|
-
import type { DeploySettings, DeployTargetName, NodeRuntime, StagedFile } from "../types.js";
|
|
4
|
+
import type { DeploySettings, DeployTargetName, NodeRuntime, NoticeSection, StagedFile } from "../types.js";
|
|
5
5
|
import { listFilesRecursive } from "../../internal/list-files.js";
|
|
6
|
+
import { BUNDLED_PACKAGES_FILENAME } from "../../vite-plugins/bundled-packages.js";
|
|
6
7
|
import { BUNDLE_FILENAME } from "../../vite-plugins/esm-extension.js";
|
|
7
8
|
import { renderCopyright } from "../freedesktop/copyright.js";
|
|
8
9
|
import { renderDbusService } from "../freedesktop/dbus-service.js";
|
|
9
10
|
import { renderDesktopEntry } from "../freedesktop/desktop-entry.js";
|
|
10
|
-
import {
|
|
11
|
+
import { renderNotices } from "../notices/render.js";
|
|
12
|
+
import { copyInto, EXECUTABLE_MODE, executableModeFor, writeInto } from "./copy-tree.js";
|
|
11
13
|
import { stageIcons } from "./icons.js";
|
|
12
14
|
import { NODE_FILENAME, renderLauncher } from "./launcher.js";
|
|
13
15
|
import { stageSchemas } from "./schemas.js";
|
|
@@ -25,6 +27,8 @@ type StagedMetadata = {
|
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
const DIST_ICONS_DIR = "icons";
|
|
30
|
+
const NOTICES_FILENAME = "THIRD-PARTY-NOTICES";
|
|
31
|
+
const NODE_LICENSE_FILENAME = "LICENSE";
|
|
28
32
|
const SHARE_APPLICATIONS = "share/applications";
|
|
29
33
|
const SHARE_METAINFO = "share/metainfo";
|
|
30
34
|
const SHARE_MIME_PACKAGES = "share/mime/packages";
|
|
@@ -40,7 +44,14 @@ const PREFIX_FOR: Record<DeployTargetName, string> = {
|
|
|
40
44
|
};
|
|
41
45
|
|
|
42
46
|
const libDirFor = (settings: DeploySettings): string => `lib/${settings.binaryName}`;
|
|
47
|
+
const licenseDirFor = (settings: DeploySettings): string => `${SHARE_LICENSES}/${settings.binaryName}`;
|
|
48
|
+
const licenseDestination = (settings: DeploySettings): string => `${licenseDirFor(settings)}/LICENSE`;
|
|
49
|
+
const noticesDestination = (settings: DeploySettings): string => `${licenseDirFor(settings)}/${NOTICES_FILENAME}`;
|
|
43
50
|
const isIconAsset = (rel: string): boolean => rel === DIST_ICONS_DIR || rel.startsWith(`${DIST_ICONS_DIR}/`);
|
|
51
|
+
const isBuildMetadata = (rel: string): boolean => rel === BUNDLED_PACKAGES_FILENAME;
|
|
52
|
+
|
|
53
|
+
const nodeLicenseDestination = (settings: DeploySettings): string =>
|
|
54
|
+
`${licenseDirFor(settings)}/${NODE_FILENAME}/${NODE_LICENSE_FILENAME}`;
|
|
44
55
|
|
|
45
56
|
const stageRuntimeFiles = (settings: DeploySettings, root: string): StagedFile[] => {
|
|
46
57
|
const dist = settings.paths.dist;
|
|
@@ -50,7 +61,7 @@ const stageRuntimeFiles = (settings: DeploySettings, root: string): StagedFile[]
|
|
|
50
61
|
}
|
|
51
62
|
|
|
52
63
|
return listFilesRecursive(dist)
|
|
53
|
-
.filter((file) => !isIconAsset(file.rel))
|
|
64
|
+
.filter((file) => !isIconAsset(file.rel) && !isBuildMetadata(file.rel))
|
|
54
65
|
.map((file) => copyInto(root, join(libDirFor(settings), file.rel), file.absPath));
|
|
55
66
|
};
|
|
56
67
|
|
|
@@ -66,8 +77,11 @@ const stageMetadata = (settings: DeploySettings, root: string, metadata: StagedM
|
|
|
66
77
|
];
|
|
67
78
|
|
|
68
79
|
const stageExtraFiles = (settings: DeploySettings, root: string): StagedFile[] =>
|
|
69
|
-
|
|
70
|
-
|
|
80
|
+
settings.extraFiles.map((file) => {
|
|
81
|
+
const source = resolve(settings.paths.root, file.source);
|
|
82
|
+
|
|
83
|
+
return copyInto(root, file.destination, source, file.mode ?? executableModeFor(source));
|
|
84
|
+
});
|
|
71
85
|
|
|
72
86
|
const byRelativePath = (files: StagedFile[]): StagedFile[] => {
|
|
73
87
|
const latest: Map<string, StagedFile> = new Map();
|
|
@@ -114,7 +128,7 @@ const stageActivation = (settings: DeploySettings, root: string, target: DeployT
|
|
|
114
128
|
)];
|
|
115
129
|
};
|
|
116
130
|
|
|
117
|
-
const stageOverlay = (settings: DeploySettings, target: DeployTargetName): StagedFile[] => {
|
|
131
|
+
const stageOverlay = (settings: DeploySettings, target: DeployTargetName, notices: NoticeSection[]): StagedFile[] => {
|
|
118
132
|
const root = join(settings.paths.overlay, target);
|
|
119
133
|
rmSync(root, { recursive: true, force: true, maxRetries: 5 });
|
|
120
134
|
const licenseFile = settings.paths.licenseFile;
|
|
@@ -123,23 +137,33 @@ const stageOverlay = (settings: DeploySettings, target: DeployTargetName): Stage
|
|
|
123
137
|
if (target === "deb") {
|
|
124
138
|
return [
|
|
125
139
|
...activation,
|
|
126
|
-
writeInto(root, join(SHARE_DOC, settings.binaryName, "copyright"), renderCopyright(settings)),
|
|
140
|
+
writeInto(root, join(SHARE_DOC, settings.binaryName, "copyright"), renderCopyright(settings, notices)),
|
|
127
141
|
];
|
|
128
142
|
}
|
|
129
143
|
|
|
130
144
|
return [
|
|
131
145
|
...activation,
|
|
132
|
-
...(licenseFile === null
|
|
133
|
-
|
|
134
|
-
: [copyInto(root, join(SHARE_LICENSES, settings.binaryName, "LICENSE"), licenseFile)]),
|
|
146
|
+
...(licenseFile === null ? [] : [copyInto(root, licenseDestination(settings), licenseFile)]),
|
|
147
|
+
writeInto(root, noticesDestination(settings), renderNotices(settings, notices)),
|
|
135
148
|
];
|
|
136
149
|
};
|
|
137
150
|
|
|
138
|
-
const stageOverlays = (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
151
|
+
const stageOverlays = (
|
|
152
|
+
settings: DeploySettings,
|
|
153
|
+
notices: NoticeSection[],
|
|
154
|
+
): Record<DeployTargetName, StagedFile[]> => ({
|
|
155
|
+
appimage: stageOverlay(settings, "appimage", notices),
|
|
156
|
+
deb: stageOverlay(settings, "deb", notices),
|
|
157
|
+
flatpak: stageOverlay(settings, "flatpak", notices),
|
|
158
|
+
rpm: stageOverlay(settings, "rpm", notices),
|
|
143
159
|
});
|
|
144
160
|
|
|
145
|
-
export {
|
|
161
|
+
export {
|
|
162
|
+
licenseDestination,
|
|
163
|
+
nodeLicenseDestination,
|
|
164
|
+
NOTICES_FILENAME,
|
|
165
|
+
noticesDestination,
|
|
166
|
+
type StagedMetadata,
|
|
167
|
+
stageOverlays,
|
|
168
|
+
stagePayload,
|
|
169
|
+
};
|
package/src/deploy/run-deploy.ts
CHANGED
|
@@ -17,11 +17,13 @@ import { renderMetainfo } from "./freedesktop/metainfo.js";
|
|
|
17
17
|
import { renderMimePackage } from "./freedesktop/mime-package.js";
|
|
18
18
|
import { validateDesktopEntry, validateMetainfo } from "./freedesktop/validate.js";
|
|
19
19
|
import { resolveNodeRuntime } from "./node-runtime/index.js";
|
|
20
|
+
import { collectNotices } from "./notices/collect.js";
|
|
20
21
|
import { type StagedMetadata, stageOverlays, stagePayload } from "./payload/stage.js";
|
|
21
22
|
import { DEFAULT_TARGETS, parseTargetList, targetsFor } from "./registry.js";
|
|
22
23
|
import { resolveDeploySettings } from "./settings/index.js";
|
|
23
24
|
import { readPackageManifest } from "./settings/package-manifest.js";
|
|
24
25
|
import { missingDeployError } from "./settings/starter.js";
|
|
26
|
+
import { finishArgsFor, hasDisplaySocket } from "./targets/flatpak-manifest.js";
|
|
25
27
|
import { detectPackageManager } from "./targets/flatpak-sources.js";
|
|
26
28
|
import {
|
|
27
29
|
APPSTREAMCLI,
|
|
@@ -78,10 +80,19 @@ const validateMetadata = (settings: DeploySettings, metadata: StagedMetadata, ar
|
|
|
78
80
|
info("Validated the desktop entry and the metainfo");
|
|
79
81
|
};
|
|
80
82
|
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
+
const warnMissingDisplay = (settings: DeploySettings, finishArgs: string[]): void => {
|
|
84
|
+
if (hasDisplaySocket(finishArgs)) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
83
87
|
|
|
84
|
-
|
|
88
|
+
warn(
|
|
89
|
+
`The flatpak permissions grant no display socket, so ${settings.name} will start without a window. ` +
|
|
90
|
+
"Grant one through `deploy.flatpak.finishArgs`, such as `--socket=wayland`.",
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const warnMissingNetwork = (settings: DeploySettings, finishArgs: string[]): void => {
|
|
95
|
+
if (settings.deploy.flatpak?.finishArgs === undefined || finishArgs.includes(NETWORK_ARG)) {
|
|
85
96
|
return;
|
|
86
97
|
}
|
|
87
98
|
|
|
@@ -92,6 +103,16 @@ const warnMissingNetwork = (settings: DeploySettings): void => {
|
|
|
92
103
|
warn(`This app declares ${WEBKIT_LIBRARY} but its flatpak permissions omit ${NETWORK_ARG}, so pages will not load`);
|
|
93
104
|
};
|
|
94
105
|
|
|
106
|
+
const warnFlatpakPermissions = (targets: DeployTarget[], settings: DeploySettings): void => {
|
|
107
|
+
if (targets.every((target) => target.name !== "flatpak")) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const finishArgs = finishArgsFor(settings);
|
|
112
|
+
warnMissingDisplay(settings, finishArgs);
|
|
113
|
+
warnMissingNetwork(settings, finishArgs);
|
|
114
|
+
};
|
|
115
|
+
|
|
95
116
|
const sourceModeTools = (targets: DeployTarget[], settings: DeploySettings): DeployTool[] => {
|
|
96
117
|
const isFlatpakSource = settings.deploy.flatpak?.mode === "source";
|
|
97
118
|
|
|
@@ -125,7 +146,7 @@ const preflight = (targets: DeployTarget[], settings: DeploySettings, shouldPrin
|
|
|
125
146
|
const report = probeTools([...required, ...packagerTools]);
|
|
126
147
|
assertTools(report);
|
|
127
148
|
warnMissingOptional(report);
|
|
128
|
-
|
|
149
|
+
warnFlatpakPermissions(targets, settings);
|
|
129
150
|
};
|
|
130
151
|
|
|
131
152
|
const resolveTargetNames = (options: DeployOptions, settings: DeploySettings): string[] => {
|
|
@@ -165,8 +186,9 @@ const buildPayload = async (
|
|
|
165
186
|
|
|
166
187
|
const stage = stagePayload({ settings, node, metadata });
|
|
167
188
|
info(`Staged ${String(stage.length)} files into ${displayPath(settings, settings.paths.stage)}`);
|
|
189
|
+
const notices = collectNotices({ settings, node });
|
|
168
190
|
|
|
169
|
-
return { settings, node, stage, overlays: stageOverlays(settings) };
|
|
191
|
+
return { settings, node, stage, notices, overlays: stageOverlays(settings, notices) };
|
|
170
192
|
};
|
|
171
193
|
|
|
172
194
|
const renderTargetManifests = (
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { DeployConfig, DeployExtraFile } from "../types.js";
|
|
2
|
+
|
|
3
|
+
type ExtraFiles = NonNullable<DeployConfig["extraFiles"]>;
|
|
4
|
+
type ExtraFileEntry = NonNullable<ExtraFiles[string]>;
|
|
5
|
+
|
|
6
|
+
const OCTAL_RADIX = 8;
|
|
7
|
+
|
|
8
|
+
const resolveExtraFile = (destination: string, entry: ExtraFileEntry): DeployExtraFile => {
|
|
9
|
+
if (typeof entry === "string") {
|
|
10
|
+
return { destination, source: entry, mode: null };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const mode = entry.mode === undefined ? null : Number.parseInt(entry.mode, OCTAL_RADIX);
|
|
14
|
+
|
|
15
|
+
return { destination, source: entry.source, mode };
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const resolveExtraFiles = (deploy: DeployConfig): DeployExtraFile[] =>
|
|
19
|
+
Object.entries(deploy.extraFiles ?? {}).map(([destination, entry]) => resolveExtraFile(destination, entry));
|
|
20
|
+
|
|
21
|
+
export { resolveExtraFiles };
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
} from "../types.js";
|
|
10
10
|
import { resolveArch } from "./arch.js";
|
|
11
11
|
import { resolveExecToken, resolveFileAssociations, resolveMimeTypes } from "./exec.js";
|
|
12
|
+
import { resolveExtraFiles } from "./extra-files.js";
|
|
12
13
|
import {
|
|
13
14
|
type IdentityRequest,
|
|
14
15
|
resolveBinaryName,
|
|
@@ -177,7 +178,7 @@ const resolveDeploySettings = (request: SettingsRequest): DeploySettings => {
|
|
|
177
178
|
...identitySlice(core),
|
|
178
179
|
...metadataSlice(request.root, core),
|
|
179
180
|
...desktopSlice(deploy),
|
|
180
|
-
extraFiles: deploy
|
|
181
|
+
extraFiles: resolveExtraFiles(deploy),
|
|
181
182
|
versions: core.versions,
|
|
182
183
|
arch: resolveArch(),
|
|
183
184
|
paths: resolvePaths({ root: request.root, deploy, outDirOverride: request.outDirOverride }),
|
|
@@ -13,6 +13,7 @@ type PackageManifest = {
|
|
|
13
13
|
description: string | null;
|
|
14
14
|
license: string | null;
|
|
15
15
|
homepage: string | null;
|
|
16
|
+
repository: string | null;
|
|
16
17
|
packageManager: string | null;
|
|
17
18
|
author: ManifestAuthor;
|
|
18
19
|
};
|
|
@@ -29,6 +30,14 @@ const parseAuthorString = (author: string): ManifestAuthor => {
|
|
|
29
30
|
return { name: optionalString(groups?.name), email: optionalString(groups?.email) };
|
|
30
31
|
};
|
|
31
32
|
|
|
33
|
+
const parseRepository = (repository: unknown): string | null => {
|
|
34
|
+
if (isRecord(repository)) {
|
|
35
|
+
return optionalString(repository.url);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return optionalString(repository);
|
|
39
|
+
};
|
|
40
|
+
|
|
32
41
|
const parseAuthor = (author: unknown): ManifestAuthor => {
|
|
33
42
|
if (typeof author === "string") {
|
|
34
43
|
return parseAuthorString(author);
|
|
@@ -60,6 +69,7 @@ const readPackageManifest = (root: string): PackageManifest => {
|
|
|
60
69
|
description: optionalString(manifest.description),
|
|
61
70
|
license: optionalString(manifest.license),
|
|
62
71
|
homepage: optionalString(manifest.homepage),
|
|
72
|
+
repository: parseRepository(manifest.repository),
|
|
63
73
|
packageManager: optionalString(manifest.packageManager),
|
|
64
74
|
author: parseAuthor(manifest.author),
|
|
65
75
|
};
|