Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
164 lines
4.5 KiB
JavaScript
164 lines
4.5 KiB
JavaScript
import { readFile, readdir, writeFile } from "node:fs/promises";
|
|
import { join, relative, sep } from "node:path";
|
|
|
|
import { parseStablePackageReleaseVersion } from "./package-release-version.mjs";
|
|
|
|
const internalModulePrefix = "github.com/tutti-os/tutti/packages/";
|
|
|
|
export async function preparePackageGoModuleReleaseTree({
|
|
releaseVersion,
|
|
workspaceRoot
|
|
}) {
|
|
if (!parseStablePackageReleaseVersion(releaseVersion)) {
|
|
throw new Error(`Unsupported package release version: ${releaseVersion}`);
|
|
}
|
|
|
|
const paths = await discoverPackageGoModulePaths(workspaceRoot);
|
|
const changed = [];
|
|
for (const relativePath of paths) {
|
|
const path = join(workspaceRoot, relativePath);
|
|
const original = await readFile(path, "utf8");
|
|
const rewritten = rewriteInternalGoModuleDependencies(
|
|
original,
|
|
releaseVersion
|
|
);
|
|
if (rewritten === original) {
|
|
continue;
|
|
}
|
|
await writeFile(path, rewritten);
|
|
changed.push(relativePath);
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
export function rewriteInternalGoModuleDependencies(text, releaseVersion) {
|
|
if (!parseStablePackageReleaseVersion(releaseVersion)) {
|
|
throw new Error(`Unsupported package release version: ${releaseVersion}`);
|
|
}
|
|
|
|
const version = `v${releaseVersion}`;
|
|
const lines = text.replace(/\n$/, "").split("\n");
|
|
const withoutInternalReplaces = [];
|
|
let changed = false;
|
|
|
|
for (let index = 0; index < lines.length; index += 1) {
|
|
const line = lines[index];
|
|
if (line.trim() === "replace (") {
|
|
const block = [];
|
|
index += 1;
|
|
while (index < lines.length && lines[index].trim() !== ")") {
|
|
if (!isInternalReplaceBlockEntry(lines[index])) {
|
|
block.push(lines[index]);
|
|
} else {
|
|
changed = true;
|
|
}
|
|
index += 1;
|
|
}
|
|
if (block.length > 0) {
|
|
withoutInternalReplaces.push(line, ...block, lines[index]);
|
|
}
|
|
continue;
|
|
}
|
|
if (isInternalSingleLineReplace(line)) {
|
|
changed = true;
|
|
} else {
|
|
withoutInternalReplaces.push(line);
|
|
}
|
|
}
|
|
|
|
let inRequireBlock = false;
|
|
const rewritten = withoutInternalReplaces.map((line) => {
|
|
const trimmed = line.trim();
|
|
if (trimmed === "require (") {
|
|
inRequireBlock = true;
|
|
return line;
|
|
}
|
|
if (inRequireBlock && trimmed === ")") {
|
|
inRequireBlock = false;
|
|
return line;
|
|
}
|
|
const result = rewriteInternalRequire(line, version, inRequireBlock);
|
|
changed ||= result !== line;
|
|
return result;
|
|
});
|
|
if (!changed) {
|
|
return text;
|
|
}
|
|
return `${collapseBlankLines(rewritten).join("\n")}\n`;
|
|
}
|
|
|
|
function rewriteInternalRequire(line, version, inRequireBlock) {
|
|
const blockRequire = new RegExp(
|
|
`^(\\s*)(${escapeRegExp(internalModulePrefix)}\\S+)(\\s+)v\\S+(\\s*(?://.*)?)$`
|
|
);
|
|
const directRequire = new RegExp(
|
|
`^(\\s*require\\s+)(${escapeRegExp(internalModulePrefix)}\\S+)(\\s+)v\\S+(\\s*(?://.*)?)$`
|
|
);
|
|
const patterns = inRequireBlock ? [blockRequire] : [directRequire];
|
|
for (const pattern of patterns) {
|
|
const match = line.match(pattern);
|
|
if (match) {
|
|
return `${match[1]}${match[2]}${match[3]}${version}${match[4]}`;
|
|
}
|
|
}
|
|
return line;
|
|
}
|
|
|
|
function isInternalSingleLineReplace(line) {
|
|
return new RegExp(
|
|
`^\\s*replace\\s+${escapeRegExp(internalModulePrefix)}\\S+(?:\\s+v\\S+)?\\s+=>`
|
|
).test(line);
|
|
}
|
|
|
|
function isInternalReplaceBlockEntry(line) {
|
|
return new RegExp(
|
|
`^\\s*${escapeRegExp(internalModulePrefix)}\\S+(?:\\s+v\\S+)?\\s+=>`
|
|
).test(line);
|
|
}
|
|
|
|
function collapseBlankLines(lines) {
|
|
const result = [];
|
|
for (const line of lines) {
|
|
if (line === "" && result.at(-1) === "") {
|
|
continue;
|
|
}
|
|
result.push(line);
|
|
}
|
|
while (result.at(-1) === "") {
|
|
result.pop();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
async function discoverPackageGoModulePaths(workspaceRoot) {
|
|
const paths = [];
|
|
await collectGoModulePaths(
|
|
join(workspaceRoot, "packages"),
|
|
workspaceRoot,
|
|
paths
|
|
);
|
|
return paths.sort();
|
|
}
|
|
|
|
async function collectGoModulePaths(directory, workspaceRoot, paths) {
|
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
for (const entry of entries) {
|
|
const path = join(directory, entry.name);
|
|
if (entry.isFile() && entry.name === "go.mod") {
|
|
paths.push(toPosixPath(relative(workspaceRoot, path)));
|
|
continue;
|
|
}
|
|
if (entry.isDirectory() && entry.name !== "node_modules") {
|
|
await collectGoModulePaths(path, workspaceRoot, paths);
|
|
}
|
|
}
|
|
}
|
|
|
|
function escapeRegExp(value) {
|
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
}
|
|
|
|
function toPosixPath(path) {
|
|
return path.split(sep).join("/");
|
|
}
|