Rename the active product, Go module, binaries, configuration, protocols and documentation to Piik while retaining the TV mascot and existing media/room behavior. Use one current private contract without old-name aliases. Preserve SQLite authority and copy compatible local settings once; other settings start fresh. Git-message cleanup and the infrastructure name cutover are coordinated with this phase, with original history and deployment data archived externally. Validated locally with Web and Go checks, Windows capture compilation, matched Client/Server builds, packaged Client control handshake and history-rewrite integrity checks. Skip duplicate hosted CI for this owner-authorized local release. [skip ci]
154 lines
7.8 KiB
JavaScript
154 lines
7.8 KiB
JavaScript
import { execFileSync } from "node:child_process";
|
|
import { createHash } from "node:crypto";
|
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
import { createRequire } from "node:module";
|
|
import { dirname, join, relative, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { clientGoEnvironment } from "./client-package-targets.mjs";
|
|
|
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
const pinned = JSON.parse(readFileSync(join(root, "licenses", "upstream.json"), "utf8"));
|
|
const licenseName = /^(licen[cs]e|copying|notice|copyright|authors)([._-]|$)/i;
|
|
const section = (label, body) => `\n===== ${label} =====\n\n${body.trim()}\n`;
|
|
|
|
// The packages the Vite bundle ships to the Browser. Nothing installs
|
|
// node_modules at runtime any more, so this list is the notice contract rather
|
|
// than package.json's dependencies field.
|
|
const WEB_BUNDLE_PACKAGES = ["react", "react-dom", "sdp-transform", "zod"];
|
|
|
|
// Both binaries embed the Web bundle, which serves its own notice file.
|
|
const WEB_NOTICE_LINE = "Web dependencies: served at /third-party-licenses.txt\n";
|
|
|
|
function pinnedNotice(key) {
|
|
const entry = pinned[key];
|
|
if (!entry) throw new Error(`Missing pinned license text for ${key}`);
|
|
const bytes = readFileSync(join(root, "licenses", entry.file));
|
|
if (createHash("sha256").update(bytes).digest("hex") !== entry.sha256) {
|
|
throw new Error(`Pinned license text changed for ${key}`);
|
|
}
|
|
return `Source: ${entry.source}\n\n${bytes.toString("utf8")}`;
|
|
}
|
|
|
|
function noticesIn(directory) {
|
|
return readdirSync(directory, { withFileTypes: true })
|
|
.filter((file) => file.isFile() && licenseName.test(file.name))
|
|
.map((file) => file.name).sort();
|
|
}
|
|
|
|
export function writeWebLicenseNotices(repositoryRoot, outputFile) {
|
|
const packagePath = join(repositoryRoot, "package.json");
|
|
const lock = JSON.parse(readFileSync(join(repositoryRoot, "package-lock.json"), "utf8"));
|
|
const queue = WEB_BUNDLE_PACKAGES.map((name) => [name, packagePath]);
|
|
const visited = new Set();
|
|
const entries = new Map();
|
|
for (const [name, parent] of queue) {
|
|
const paths = createRequire(parent).resolve.paths("piik-license-lookup");
|
|
const directory = paths.map((path) => join(path, name))
|
|
.find((path) => existsSync(join(path, "package.json")));
|
|
if (!directory) throw new Error(`Missing installed runtime dependency ${name}`);
|
|
if (visited.has(directory)) continue;
|
|
visited.add(directory);
|
|
const path = join(directory, "package.json");
|
|
const dependency = JSON.parse(readFileSync(path, "utf8"));
|
|
const installed = relative(repositoryRoot, directory).replaceAll("\\", "/");
|
|
if (lock.packages[installed]?.version !== dependency.version) {
|
|
throw new Error(`Runtime dependency differs from lockfile: ${name}`);
|
|
}
|
|
const key = `${dependency.name}@${dependency.version}`;
|
|
const files = noticesIn(directory);
|
|
const body = files.length
|
|
? files.map((file) => section(file, readFileSync(join(directory, file), "utf8"))).join("")
|
|
: pinnedNotice(key);
|
|
entries.set(key, section(`${key} (${dependency.license ?? "see license text"})`, body));
|
|
for (const child of Object.keys(dependency.dependencies ?? {})) queue.push([child, path]);
|
|
}
|
|
mkdirSync(dirname(outputFile), { recursive: true });
|
|
writeFileSync(outputFile, "Third-party software notices\n" +
|
|
"Piik's MIT license does not replace the following component licenses.\n" +
|
|
[...entries].sort(([a], [b]) => a.localeCompare(b)).map(([, body]) => body).join(""));
|
|
}
|
|
|
|
// goNotices enumerates the Go toolchain notice and every license file that
|
|
// belongs to a module actually compiled into `command` for `target`. A module
|
|
// without a recognised license filename fails closed.
|
|
function goNotices(repositoryRoot, goCommand, target, command) {
|
|
const options = { cwd: repositoryRoot, encoding: "utf8", windowsHide: true,
|
|
env: clientGoEnvironment(target) };
|
|
const runGo = (args) => execFileSync(goCommand, args, options).trim();
|
|
const goroot = runGo(["env", "GOROOT"]);
|
|
const template = '{{if .Module}}{{if not .Module.Main}}[{{printf "%q" .Module.Path}},{{printf "%q" .Module.Version}},{{printf "%q" .Module.Dir}},{{printf "%q" .Dir}}]{{end}}{{end}}';
|
|
const packages = runGo(["list", "-deps", "-f", template, command])
|
|
.split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
const entries = new Map();
|
|
for (const [name, version, directory, packageDirectory] of packages) {
|
|
const key = `${name}@${version}`;
|
|
const files = entries.get(key) ?? new Map();
|
|
if (!noticesIn(directory).length) throw new Error(`Missing Go module license: ${key}`);
|
|
for (let current = packageDirectory; ; current = dirname(current)) {
|
|
for (const file of noticesIn(current)) {
|
|
const fullPath = join(current, file);
|
|
files.set(relative(directory, fullPath).replaceAll("\\", "/"), readFileSync(fullPath, "utf8"));
|
|
}
|
|
if (current === directory) break;
|
|
if (current === dirname(current)) throw new Error(`Go package leaves module: ${key}`);
|
|
}
|
|
entries.set(key, files);
|
|
}
|
|
const toolchain = section(runGo(["version"]), readFileSync(join(goroot, "LICENSE"), "utf8"));
|
|
return { toolchain, modules: entries };
|
|
}
|
|
|
|
function moduleSections(modules) {
|
|
let text = "";
|
|
for (const [name, files] of [...modules].sort(([a], [b]) => a.localeCompare(b))) {
|
|
for (const [file, body] of [...files].sort(([a], [b]) => a.localeCompare(b))) {
|
|
text += section(`${name}/${file}`, body);
|
|
}
|
|
}
|
|
return text;
|
|
}
|
|
|
|
// writeServerLicenseNotices produces the Hosted application release notice file,
|
|
// which ships beside the single piik-server binary.
|
|
export function writeServerLicenseNotices(repositoryRoot, outputFile, goCommand, target) {
|
|
const notices = goNotices(repositoryRoot, goCommand, target, "./cmd/piik-server");
|
|
writeFileSync(outputFile, "Piik server third-party software notices\n" +
|
|
WEB_NOTICE_LINE + notices.toolchain + moduleSections(notices.modules));
|
|
}
|
|
|
|
export function writeClientLicenseNotices(repositoryRoot, packageRoot, goCommand, target, tunnelVersion) {
|
|
copyFileSync(join(repositoryRoot, "LICENSE"), join(packageRoot, "LICENSE"));
|
|
if (tunnelVersion) {
|
|
writeFileSync(join(packageRoot, "runtime", "tunnel", "THIRD-PARTY-NOTICES.txt"),
|
|
pinnedNotice(`cloudflared@${tunnelVersion}`));
|
|
}
|
|
const notices = goNotices(repositoryRoot, goCommand, target, "./cmd/piik-client");
|
|
let text = "Native Client third-party software notices\n" +
|
|
WEB_NOTICE_LINE +
|
|
(tunnelVersion ? "Cloudflared: runtime/tunnel/THIRD-PARTY-NOTICES.txt\n" : "") +
|
|
notices.toolchain;
|
|
const linuxCapture = target.goos === "linux" && target.captureName &&
|
|
existsSync(join(packageRoot, "runtime", "native", target.captureName));
|
|
if (linuxCapture) {
|
|
text += section("Linux system dependencies (not bundled)",
|
|
readFileSync(join(repositoryRoot, "licenses", "linux-system-dependencies.txt"), "utf8"));
|
|
text += section("LGPL-3.0-only (libportal)",
|
|
readFileSync(join(repositoryRoot, "licenses", "LGPL-3.0.txt"), "utf8"));
|
|
text += section("GPL-3.0 (libportal terms)",
|
|
readFileSync(join(repositoryRoot, "licenses", "GPL-3.0.txt"), "utf8"));
|
|
}
|
|
if (target.goos === "windows" &&
|
|
existsSync(join(packageRoot, "runtime", "native", target.captureName))) {
|
|
const dependencies = JSON.parse(readFileSync(join(repositoryRoot, "native", "capture",
|
|
"windows", "webrtc-dependencies.json"), "utf8"));
|
|
const name = `webrtc@${dependencies.webrtc.version}`;
|
|
text += section(name, pinnedNotice(name));
|
|
}
|
|
writeFileSync(join(packageRoot, "THIRD-PARTY-NOTICES.txt"), text + moduleSections(notices.modules));
|
|
}
|
|
|
|
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
if (process.argv.length !== 3) throw new Error("Usage: node scripts/package-licenses.mjs <web-notices-output>");
|
|
writeWebLicenseNotices(root, resolve(process.argv[2]));
|
|
}
|