mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-04 00:03:38 +00:00
chore(ios): generate release artifacts locally
This commit is contained in:
@@ -35,8 +35,6 @@ export const RELEASE_METADATA_PATHS = new Set([
|
||||
"apps/android/fastlane/metadata/android/en-US/release_notes.txt",
|
||||
"apps/android/version.json",
|
||||
"apps/ios/CHANGELOG.md",
|
||||
"apps/ios/Config/Version.xcconfig",
|
||||
"apps/ios/fastlane/metadata/en-US/release_notes.txt",
|
||||
"apps/macos/Sources/OpenClaw/Resources/Info.plist",
|
||||
"docs/.generated/config-baseline.sha256",
|
||||
"docs/install/updating.md",
|
||||
|
||||
@@ -8,7 +8,6 @@ import { RELEASE_METADATA_PATHS } from "./changed-lanes.mjs";
|
||||
const VERSION_ONLY_TEXT_PATHS = new Set([
|
||||
"apps/android/Config/Version.properties",
|
||||
"apps/android/version.json",
|
||||
"apps/ios/Config/Version.xcconfig",
|
||||
"apps/macos/Sources/OpenClaw/Resources/Info.plist",
|
||||
]);
|
||||
|
||||
|
||||
@@ -150,6 +150,7 @@ fi
|
||||
(
|
||||
bash "${VERSION_HELPER}" --version "${IOS_VERSION}" --build-number "${BUILD_NUMBER}"
|
||||
)
|
||||
node "${ROOT_DIR}/scripts/ios-write-swift-filelist.mjs"
|
||||
|
||||
write_generated_file "${RELEASE_XCCONFIG}" <<EOF
|
||||
// Auto-generated by scripts/ios-release-prepare.sh.
|
||||
|
||||
@@ -110,6 +110,7 @@ fi
|
||||
|
||||
"${ROOT_DIR}/scripts/ios-configure-signing.sh"
|
||||
"${ROOT_DIR}/scripts/ios-write-version-xcconfig.sh"
|
||||
node "${ROOT_DIR}/scripts/ios-write-swift-filelist.mjs"
|
||||
|
||||
cd "${IOS_DIR}"
|
||||
"${XCODEGEN_BIN}" generate
|
||||
|
||||
@@ -7,7 +7,7 @@ export { parseVersionSyncArgs as parseArgs } from "./lib/version-script-args.ts"
|
||||
|
||||
function printUsage(): void {
|
||||
process.stdout.write(
|
||||
"Usage: node --import tsx scripts/ios-sync-versioning.ts [--write|--check] [--version YYYY.M.D] [--root dir]\n",
|
||||
"Usage: node --import tsx scripts/ios-sync-versioning.ts [--write|--check] [--version YYYY.M.D] [--root dir]\n\nValidates that iOS versioning inputs can produce generated local artifacts.\n",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,9 +25,11 @@ function main(argv = process.argv.slice(2)): number {
|
||||
});
|
||||
|
||||
if (options.mode === "check") {
|
||||
process.stdout.write("iOS versioning artifacts are up to date.\n");
|
||||
process.stdout.write("iOS versioning inputs are valid.\n");
|
||||
} else if (result.updatedPaths.length === 0) {
|
||||
process.stdout.write("iOS versioning artifacts already up to date.\n");
|
||||
process.stdout.write(
|
||||
"iOS versioning inputs are valid; local artifacts are generated by iOS prep commands.\n",
|
||||
);
|
||||
} else {
|
||||
process.stdout.write(
|
||||
`Updated iOS versioning artifacts:\n- ${result.updatedPaths.map((filePath) => path.relative(process.cwd(), filePath)).join("\n- ")}\n`,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Ios Version script supports OpenClaw repository automation.
|
||||
import { resolveIosVersion } from "./lib/ios-version.ts";
|
||||
import { renderIosReleaseNotesForVersion, resolveIosVersion } from "./lib/ios-version.ts";
|
||||
import { parseVersionQueryArgs } from "./lib/version-script-args.ts";
|
||||
|
||||
function printUsage(): void {
|
||||
@@ -18,6 +18,16 @@ function main(argv = process.argv.slice(2)): number {
|
||||
const version = resolveIosVersion(options.rootDir, { releaseVersion: options.releaseVersion });
|
||||
|
||||
if (options.field) {
|
||||
if (options.field === "releaseNotes") {
|
||||
process.stdout.write(
|
||||
renderIosReleaseNotesForVersion({
|
||||
releaseVersion: options.releaseVersion,
|
||||
rootDir: options.rootDir,
|
||||
}),
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const value = version[options.field as keyof typeof version];
|
||||
if (value === undefined) {
|
||||
throw new Error(`Unknown iOS version field '${options.field}'.`);
|
||||
|
||||
103
scripts/ios-write-swift-filelist.mjs
Normal file
103
scripts/ios-write-swift-filelist.mjs
Normal file
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env node
|
||||
import { existsSync, lstatSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const repoRoot = path.resolve(import.meta.dirname, "..");
|
||||
const iosRoot = path.join(repoRoot, "apps", "ios");
|
||||
const outputPath = path.join(iosRoot, "SwiftSources.input.xcfilelist");
|
||||
|
||||
const iosSourceRoots = [
|
||||
"Sources",
|
||||
"ShareExtension",
|
||||
"ActivityWidget",
|
||||
path.join("WatchApp", "Sources"),
|
||||
];
|
||||
|
||||
const sharedSwiftFiles = [
|
||||
"../shared/OpenClawKit/Sources/OpenClawChatUI/ChatComposer.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownPreprocessor.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownRenderer.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawChatUI/ChatMessageViews.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawChatUI/ChatModels.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawChatUI/ChatPayloadDecoding.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawChatUI/ChatSessions.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawChatUI/ChatSheets.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawChatUI/ChatTheme.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawChatUI/ChatTransport.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawChatUI/ChatView.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel+Attachments.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel+SessionKeys.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawKit/AnyCodable.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawKit/BonjourEscapes.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawKit/BonjourTypes.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawKit/BridgeFrames.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawKit/CameraCommands.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawKit/CanvasA2UIAction.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawKit/CanvasA2UICommands.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawKit/CanvasA2UIJSONL.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawKit/CanvasCommandParams.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawKit/CanvasCommands.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawKit/Capabilities.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawKit/DeepLinks.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawKit/JPEGTranscoder.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawKit/NodeError.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawKit/OpenClawKitResources.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawKit/ScreenCommands.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawKit/StoragePaths.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawKit/SystemCommands.swift",
|
||||
"../shared/OpenClawKit/Sources/OpenClawKit/TalkDirective.swift",
|
||||
"../swabble/Sources/SwabbleKit/WakeWordGate.swift",
|
||||
];
|
||||
|
||||
function normalizeFileListPath(filePath) {
|
||||
return filePath.split(path.sep).join("/");
|
||||
}
|
||||
|
||||
function collectSwiftFiles(rootRelativePath) {
|
||||
const root = path.join(iosRoot, rootRelativePath);
|
||||
if (!existsSync(root)) {
|
||||
throw new Error(`Missing iOS Swift source root: ${rootRelativePath}`);
|
||||
}
|
||||
|
||||
const entries = [];
|
||||
const visit = (dir) => {
|
||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
visit(fullPath);
|
||||
} else if (entry.isFile() && entry.name.endsWith(".swift")) {
|
||||
entries.push(normalizeFileListPath(path.relative(iosRoot, fullPath)));
|
||||
}
|
||||
}
|
||||
};
|
||||
visit(root);
|
||||
return entries;
|
||||
}
|
||||
|
||||
function assertSharedFilesExist(filePaths) {
|
||||
for (const filePath of filePaths) {
|
||||
const absolutePath = path.resolve(iosRoot, filePath);
|
||||
if (!existsSync(absolutePath)) {
|
||||
throw new Error(`Missing shared Swift file listed for iOS lint: ${filePath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function writeGeneratedFile(filePath, contents) {
|
||||
if (existsSync(filePath) && lstatSync(filePath).isSymbolicLink()) {
|
||||
throw new Error(`Refusing to overwrite symlinked file: ${filePath}`);
|
||||
}
|
||||
mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
writeFileSync(filePath, contents, "utf8");
|
||||
}
|
||||
|
||||
assertSharedFilesExist(sharedSwiftFiles);
|
||||
|
||||
const iosFiles = iosSourceRoots.flatMap(collectSwiftFiles);
|
||||
const fileList = [...new Set([...iosFiles, ...sharedSwiftFiles])].toSorted((left, right) =>
|
||||
left.localeCompare(right),
|
||||
);
|
||||
|
||||
writeGeneratedFile(outputPath, `${fileList.join("\n")}\n`);
|
||||
process.stdout.write(`Prepared iOS Swift file list: ${path.relative(repoRoot, outputPath)}\n`);
|
||||
@@ -1,29 +1,21 @@
|
||||
// Ios Version script supports OpenClaw repository automation.
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { parseReleaseVersion } from "./npm-publish-plan.mjs";
|
||||
|
||||
const IOS_CHANGELOG_FILE = "apps/ios/CHANGELOG.md";
|
||||
const IOS_VERSION_XCCONFIG_FILE = "apps/ios/Config/Version.xcconfig";
|
||||
const IOS_RELEASE_NOTES_FILE = "apps/ios/fastlane/metadata/en-US/release_notes.txt";
|
||||
|
||||
type ResolvedIosVersion = {
|
||||
canonicalVersion: string;
|
||||
marketingVersion: string;
|
||||
buildVersion: string;
|
||||
changelogPath: string;
|
||||
versionXcconfigPath: string;
|
||||
releaseNotesPath: string;
|
||||
versionSource: "explicit" | "package";
|
||||
versionSourcePath: string | null;
|
||||
};
|
||||
|
||||
type SyncIosVersioningMode = "check" | "write";
|
||||
|
||||
function normalizeTrailingNewline(value: string): string {
|
||||
return value.endsWith("\n") ? value : `${value}\n`;
|
||||
}
|
||||
|
||||
function parsePinnedReleaseVersion(rawVersion: string): string | null {
|
||||
const parsed = parseReleaseVersion(rawVersion.trim());
|
||||
if (!parsed || parsed.version !== parsed.baseVersion) {
|
||||
@@ -92,8 +84,6 @@ export function resolveIosVersion(
|
||||
options?: { releaseVersion?: string | null },
|
||||
): ResolvedIosVersion {
|
||||
const changelogPath = path.join(rootDir, IOS_CHANGELOG_FILE);
|
||||
const versionXcconfigPath = path.join(rootDir, IOS_VERSION_XCCONFIG_FILE);
|
||||
const releaseNotesPath = path.join(rootDir, IOS_RELEASE_NOTES_FILE);
|
||||
const explicitReleaseVersion = options?.releaseVersion?.trim() ?? "";
|
||||
const canonicalVersion = explicitReleaseVersion
|
||||
? normalizePinnedIosVersion(explicitReleaseVersion)
|
||||
@@ -104,17 +94,11 @@ export function resolveIosVersion(
|
||||
marketingVersion: canonicalVersion,
|
||||
buildVersion: "1",
|
||||
changelogPath,
|
||||
versionXcconfigPath,
|
||||
releaseNotesPath,
|
||||
versionSource: explicitReleaseVersion ? "explicit" : "package",
|
||||
versionSourcePath: explicitReleaseVersion ? null : rootPackageJsonPath(rootDir),
|
||||
};
|
||||
}
|
||||
|
||||
export function renderIosVersionXcconfig(version: ResolvedIosVersion): string {
|
||||
return `// Shared iOS version defaults.\n// Source of truth: package.json or explicit release --version.\n// Generated by scripts/ios-sync-versioning.ts.\n\nOPENCLAW_IOS_VERSION = ${version.canonicalVersion}\nOPENCLAW_MARKETING_VERSION = ${version.marketingVersion}\nOPENCLAW_BUILD_VERSION = ${version.buildVersion}\n\n#include? "../build/Version.xcconfig"\n`;
|
||||
}
|
||||
|
||||
function matchChangelogHeading(line: string, heading: string): boolean {
|
||||
const normalized = line.trim();
|
||||
return normalized === `## ${heading}` || normalized.startsWith(`## ${heading} - `);
|
||||
@@ -160,26 +144,6 @@ export function renderIosReleaseNotes(
|
||||
);
|
||||
}
|
||||
|
||||
function syncFile(params: {
|
||||
mode: SyncIosVersioningMode;
|
||||
path: string;
|
||||
nextContent: string;
|
||||
label: string;
|
||||
}): boolean {
|
||||
const nextContent = normalizeTrailingNewline(params.nextContent);
|
||||
const currentContent = readFileSync(params.path, "utf8");
|
||||
if (currentContent === nextContent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (params.mode === "check") {
|
||||
throw new Error(`${params.label} is stale: ${path.relative(process.cwd(), params.path)}`);
|
||||
}
|
||||
|
||||
writeFileSync(params.path, nextContent, "utf8");
|
||||
return true;
|
||||
}
|
||||
|
||||
export function syncIosVersioning(params?: {
|
||||
mode?: SyncIosVersioningMode;
|
||||
releaseVersion?: string | null;
|
||||
@@ -187,36 +151,21 @@ export function syncIosVersioning(params?: {
|
||||
}): {
|
||||
updatedPaths: string[];
|
||||
} {
|
||||
const mode = params?.mode ?? "write";
|
||||
const rootDir = path.resolve(params?.rootDir ?? ".");
|
||||
const releaseVersion = params?.releaseVersion;
|
||||
const version = resolveIosVersion(rootDir, { releaseVersion });
|
||||
const changelogContent = readFileSync(version.changelogPath, "utf8");
|
||||
const nextVersionXcconfig = renderIosVersionXcconfig(version);
|
||||
const nextReleaseNotes = renderIosReleaseNotes(version, changelogContent);
|
||||
const updatedPaths: string[] = [];
|
||||
renderIosReleaseNotes(version, changelogContent);
|
||||
|
||||
if (
|
||||
syncFile({
|
||||
mode,
|
||||
path: version.versionXcconfigPath,
|
||||
nextContent: nextVersionXcconfig,
|
||||
label: "iOS version xcconfig",
|
||||
})
|
||||
) {
|
||||
updatedPaths.push(version.versionXcconfigPath);
|
||||
}
|
||||
|
||||
if (
|
||||
syncFile({
|
||||
mode,
|
||||
path: version.releaseNotesPath,
|
||||
nextContent: nextReleaseNotes,
|
||||
label: "iOS release notes",
|
||||
})
|
||||
) {
|
||||
updatedPaths.push(version.releaseNotesPath);
|
||||
}
|
||||
|
||||
return { updatedPaths };
|
||||
return { updatedPaths: [] };
|
||||
}
|
||||
|
||||
export function renderIosReleaseNotesForVersion(params?: {
|
||||
releaseVersion?: string | null;
|
||||
rootDir?: string;
|
||||
}): string {
|
||||
const rootDir = path.resolve(params?.rootDir ?? ".");
|
||||
const version = resolveIosVersion(rootDir, { releaseVersion: params?.releaseVersion });
|
||||
const changelogContent = readFileSync(version.changelogPath, "utf8");
|
||||
return renderIosReleaseNotes(version, changelogContent);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user