mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-09 07:25:53 +00:00
refactor(scripts): dedupe guard checks and smoke helpers
This commit is contained in:
@@ -2,10 +2,16 @@
|
||||
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import ts from "typescript";
|
||||
import {
|
||||
collectTypeScriptFiles,
|
||||
getPropertyNameText,
|
||||
resolveRepoRoot,
|
||||
runAsScript,
|
||||
toLine,
|
||||
} from "./lib/ts-guard-utils.mjs";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
|
||||
const acpCoreProtectedSources = [
|
||||
path.join(repoRoot, "src", "acp"),
|
||||
@@ -57,50 +63,6 @@ const comparisonOperators = new Set([
|
||||
|
||||
const allowedViolations = new Set([]);
|
||||
|
||||
function isTestLikeFile(filePath) {
|
||||
return (
|
||||
filePath.endsWith(".test.ts") ||
|
||||
filePath.endsWith(".test-utils.ts") ||
|
||||
filePath.endsWith(".test-harness.ts") ||
|
||||
filePath.endsWith(".e2e-harness.ts")
|
||||
);
|
||||
}
|
||||
|
||||
async function collectTypeScriptFiles(targetPath) {
|
||||
const stat = await fs.stat(targetPath);
|
||||
if (stat.isFile()) {
|
||||
if (!targetPath.endsWith(".ts") || isTestLikeFile(targetPath)) {
|
||||
return [];
|
||||
}
|
||||
return [targetPath];
|
||||
}
|
||||
|
||||
const entries = await fs.readdir(targetPath, { withFileTypes: true });
|
||||
const files = [];
|
||||
for (const entry of entries) {
|
||||
const entryPath = path.join(targetPath, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
files.push(...(await collectTypeScriptFiles(entryPath)));
|
||||
continue;
|
||||
}
|
||||
if (!entry.isFile()) {
|
||||
continue;
|
||||
}
|
||||
if (!entryPath.endsWith(".ts")) {
|
||||
continue;
|
||||
}
|
||||
if (isTestLikeFile(entryPath)) {
|
||||
continue;
|
||||
}
|
||||
files.push(entryPath);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
function toLine(sourceFile, node) {
|
||||
return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
||||
}
|
||||
|
||||
function isChannelsPropertyAccess(node) {
|
||||
if (ts.isPropertyAccessExpression(node)) {
|
||||
return node.name.text === "channels";
|
||||
@@ -130,13 +92,6 @@ function matchesChannelModuleSpecifier(specifier) {
|
||||
return channelSegmentRe.test(specifier.replaceAll("\\", "/"));
|
||||
}
|
||||
|
||||
function getPropertyNameText(name) {
|
||||
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
|
||||
return name.text;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const userFacingChannelNameRe =
|
||||
/\b(?:discord|telegram|slack|signal|imessage|whatsapp|google\s*chat|irc|line|zalo|matrix|msteams|bluebubbles)\b/i;
|
||||
const systemMarkLiteral = "⚙️";
|
||||
@@ -348,16 +303,12 @@ export async function main() {
|
||||
for (const ruleSet of boundaryRuleSets) {
|
||||
const files = (
|
||||
await Promise.all(
|
||||
ruleSet.sources.map(async (sourcePath) => {
|
||||
try {
|
||||
return await collectTypeScriptFiles(sourcePath);
|
||||
} catch (error) {
|
||||
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
||||
return [];
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
ruleSet.sources.map(
|
||||
async (sourcePath) =>
|
||||
await collectTypeScriptFiles(sourcePath, {
|
||||
ignoreMissing: true,
|
||||
}),
|
||||
),
|
||||
)
|
||||
).flat();
|
||||
for (const filePath of files) {
|
||||
@@ -389,17 +340,4 @@ export async function main() {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const isDirectExecution = (() => {
|
||||
const entry = process.argv[1];
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
return path.resolve(entry) === fileURLToPath(import.meta.url);
|
||||
})();
|
||||
|
||||
if (isDirectExecution) {
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
runAsScript(import.meta.url, main);
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import ts from "typescript";
|
||||
import {
|
||||
collectFileViolations,
|
||||
getPropertyNameText,
|
||||
resolveRepoRoot,
|
||||
runAsScript,
|
||||
toLine,
|
||||
} from "./lib/ts-guard-utils.mjs";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const sourceRoots = [path.join(repoRoot, "src"), path.join(repoRoot, "extensions")];
|
||||
|
||||
const allowedFiles = new Set([
|
||||
@@ -31,43 +36,6 @@ const allowedResolverCallNames = new Set([
|
||||
"resolveIrcEffectiveAllowlists",
|
||||
]);
|
||||
|
||||
function isTestLikeFile(filePath) {
|
||||
return (
|
||||
filePath.endsWith(".test.ts") ||
|
||||
filePath.endsWith(".test-utils.ts") ||
|
||||
filePath.endsWith(".test-harness.ts") ||
|
||||
filePath.endsWith(".e2e-harness.ts")
|
||||
);
|
||||
}
|
||||
|
||||
async function collectTypeScriptFiles(dir) {
|
||||
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||
const out = [];
|
||||
for (const entry of entries) {
|
||||
const entryPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
out.push(...(await collectTypeScriptFiles(entryPath)));
|
||||
continue;
|
||||
}
|
||||
if (!entry.isFile() || !entryPath.endsWith(".ts") || isTestLikeFile(entryPath)) {
|
||||
continue;
|
||||
}
|
||||
out.push(entryPath);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function toLine(sourceFile, node) {
|
||||
return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
||||
}
|
||||
|
||||
function getPropertyNameText(name) {
|
||||
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
|
||||
return name.text;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getDeclarationNameText(name) {
|
||||
if (ts.isIdentifier(name)) {
|
||||
return name.text;
|
||||
@@ -190,24 +158,12 @@ function findViolations(content, filePath) {
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const files = (
|
||||
await Promise.all(sourceRoots.map(async (root) => await collectTypeScriptFiles(root)))
|
||||
).flat();
|
||||
|
||||
const violations = [];
|
||||
for (const filePath of files) {
|
||||
if (allowedFiles.has(filePath)) {
|
||||
continue;
|
||||
}
|
||||
const content = await fs.readFile(filePath, "utf8");
|
||||
const fileViolations = findViolations(content, filePath);
|
||||
for (const violation of fileViolations) {
|
||||
violations.push({
|
||||
path: path.relative(repoRoot, filePath),
|
||||
...violation,
|
||||
});
|
||||
}
|
||||
}
|
||||
const violations = await collectFileViolations({
|
||||
sourceRoots,
|
||||
repoRoot,
|
||||
findViolations,
|
||||
skipFile: (filePath) => allowedFiles.has(filePath),
|
||||
});
|
||||
|
||||
if (violations.length === 0) {
|
||||
return;
|
||||
@@ -223,17 +179,4 @@ async function main() {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const isDirectExecution = (() => {
|
||||
const entry = process.argv[1];
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
return path.resolve(entry) === fileURLToPath(import.meta.url);
|
||||
})();
|
||||
|
||||
if (isDirectExecution) {
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
runAsScript(import.meta.url, main);
|
||||
|
||||
@@ -2,10 +2,16 @@
|
||||
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import ts from "typescript";
|
||||
import {
|
||||
collectTypeScriptFiles,
|
||||
resolveRepoRoot,
|
||||
runAsScript,
|
||||
toLine,
|
||||
unwrapExpression,
|
||||
} from "./lib/ts-guard-utils.mjs";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const sourceRoots = [
|
||||
path.join(repoRoot, "src", "channels"),
|
||||
path.join(repoRoot, "src", "infra", "outbound"),
|
||||
@@ -15,38 +21,6 @@ const sourceRoots = [
|
||||
];
|
||||
const allowedCallsites = new Set([path.join(repoRoot, "extensions", "feishu", "src", "dedup.ts")]);
|
||||
|
||||
function isTestLikeFile(filePath) {
|
||||
return (
|
||||
filePath.endsWith(".test.ts") ||
|
||||
filePath.endsWith(".test-utils.ts") ||
|
||||
filePath.endsWith(".test-harness.ts") ||
|
||||
filePath.endsWith(".e2e-harness.ts")
|
||||
);
|
||||
}
|
||||
|
||||
async function collectTypeScriptFiles(dir) {
|
||||
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||
const out = [];
|
||||
for (const entry of entries) {
|
||||
const entryPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
out.push(...(await collectTypeScriptFiles(entryPath)));
|
||||
continue;
|
||||
}
|
||||
if (!entry.isFile()) {
|
||||
continue;
|
||||
}
|
||||
if (!entryPath.endsWith(".ts")) {
|
||||
continue;
|
||||
}
|
||||
if (isTestLikeFile(entryPath)) {
|
||||
continue;
|
||||
}
|
||||
out.push(entryPath);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function collectOsTmpdirImports(sourceFile) {
|
||||
const osModuleSpecifiers = new Set(["node:os", "os"]);
|
||||
const osNamespaceOrDefault = new Set();
|
||||
@@ -81,25 +55,6 @@ function collectOsTmpdirImports(sourceFile) {
|
||||
return { osNamespaceOrDefault, namedTmpdir };
|
||||
}
|
||||
|
||||
function unwrapExpression(expression) {
|
||||
let current = expression;
|
||||
while (true) {
|
||||
if (ts.isParenthesizedExpression(current)) {
|
||||
current = current.expression;
|
||||
continue;
|
||||
}
|
||||
if (ts.isAsExpression(current) || ts.isTypeAssertionExpression(current)) {
|
||||
current = current.expression;
|
||||
continue;
|
||||
}
|
||||
if (ts.isNonNullExpression(current)) {
|
||||
current = current.expression;
|
||||
continue;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
export function findMessagingTmpdirCallLines(content, fileName = "source.ts") {
|
||||
const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
|
||||
const { osNamespaceOrDefault, namedTmpdir } = collectOsTmpdirImports(sourceFile);
|
||||
@@ -114,11 +69,9 @@ export function findMessagingTmpdirCallLines(content, fileName = "source.ts") {
|
||||
ts.isIdentifier(callee.expression) &&
|
||||
osNamespaceOrDefault.has(callee.expression.text)
|
||||
) {
|
||||
const line = sourceFile.getLineAndCharacterOfPosition(callee.getStart(sourceFile)).line + 1;
|
||||
lines.push(line);
|
||||
lines.push(toLine(sourceFile, callee));
|
||||
} else if (ts.isIdentifier(callee) && namedTmpdir.has(callee.text)) {
|
||||
const line = sourceFile.getLineAndCharacterOfPosition(callee.getStart(sourceFile)).line + 1;
|
||||
lines.push(line);
|
||||
lines.push(toLine(sourceFile, callee));
|
||||
}
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
@@ -130,7 +83,14 @@ export function findMessagingTmpdirCallLines(content, fileName = "source.ts") {
|
||||
|
||||
export async function main() {
|
||||
const files = (
|
||||
await Promise.all(sourceRoots.map(async (dir) => await collectTypeScriptFiles(dir)))
|
||||
await Promise.all(
|
||||
sourceRoots.map(
|
||||
async (dir) =>
|
||||
await collectTypeScriptFiles(dir, {
|
||||
ignoreMissing: true,
|
||||
}),
|
||||
),
|
||||
)
|
||||
).flat();
|
||||
const violations = [];
|
||||
|
||||
@@ -158,17 +118,4 @@ export async function main() {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const isDirectExecution = (() => {
|
||||
const entry = process.argv[1];
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
return path.resolve(entry) === fileURLToPath(import.meta.url);
|
||||
})();
|
||||
|
||||
if (isDirectExecution) {
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
runAsScript(import.meta.url, main);
|
||||
|
||||
@@ -2,10 +2,16 @@
|
||||
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import ts from "typescript";
|
||||
import {
|
||||
collectTypeScriptFiles,
|
||||
resolveRepoRoot,
|
||||
runAsScript,
|
||||
toLine,
|
||||
unwrapExpression,
|
||||
} from "./lib/ts-guard-utils.mjs";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const sourceRoots = [
|
||||
path.join(repoRoot, "src", "telegram"),
|
||||
path.join(repoRoot, "src", "discord"),
|
||||
@@ -65,69 +71,6 @@ const allowedRawFetchCallsites = new Set([
|
||||
"src/slack/monitor/media.ts:108",
|
||||
]);
|
||||
|
||||
function isTestLikeFile(filePath) {
|
||||
return (
|
||||
filePath.endsWith(".test.ts") ||
|
||||
filePath.endsWith(".test-utils.ts") ||
|
||||
filePath.endsWith(".test-harness.ts") ||
|
||||
filePath.endsWith(".e2e-harness.ts") ||
|
||||
filePath.endsWith(".browser.test.ts") ||
|
||||
filePath.endsWith(".node.test.ts")
|
||||
);
|
||||
}
|
||||
|
||||
async function collectTypeScriptFiles(targetPath) {
|
||||
const stat = await fs.stat(targetPath);
|
||||
if (stat.isFile()) {
|
||||
if (!targetPath.endsWith(".ts") || isTestLikeFile(targetPath)) {
|
||||
return [];
|
||||
}
|
||||
return [targetPath];
|
||||
}
|
||||
const entries = await fs.readdir(targetPath, { withFileTypes: true });
|
||||
const files = [];
|
||||
for (const entry of entries) {
|
||||
const entryPath = path.join(targetPath, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
if (entry.name === "node_modules") {
|
||||
continue;
|
||||
}
|
||||
files.push(...(await collectTypeScriptFiles(entryPath)));
|
||||
continue;
|
||||
}
|
||||
if (!entry.isFile()) {
|
||||
continue;
|
||||
}
|
||||
if (!entryPath.endsWith(".ts")) {
|
||||
continue;
|
||||
}
|
||||
if (isTestLikeFile(entryPath)) {
|
||||
continue;
|
||||
}
|
||||
files.push(entryPath);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
function unwrapExpression(expression) {
|
||||
let current = expression;
|
||||
while (true) {
|
||||
if (ts.isParenthesizedExpression(current)) {
|
||||
current = current.expression;
|
||||
continue;
|
||||
}
|
||||
if (ts.isAsExpression(current) || ts.isTypeAssertionExpression(current)) {
|
||||
current = current.expression;
|
||||
continue;
|
||||
}
|
||||
if (ts.isNonNullExpression(current)) {
|
||||
current = current.expression;
|
||||
continue;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
function isRawFetchCall(expression) {
|
||||
const callee = unwrapExpression(expression);
|
||||
if (ts.isIdentifier(callee)) {
|
||||
@@ -148,9 +91,7 @@ export function findRawFetchCallLines(content, fileName = "source.ts") {
|
||||
const lines = [];
|
||||
const visit = (node) => {
|
||||
if (ts.isCallExpression(node) && isRawFetchCall(node.expression)) {
|
||||
const line =
|
||||
sourceFile.getLineAndCharacterOfPosition(node.expression.getStart(sourceFile)).line + 1;
|
||||
lines.push(line);
|
||||
lines.push(toLine(sourceFile, node.expression));
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
@@ -161,13 +102,13 @@ export function findRawFetchCallLines(content, fileName = "source.ts") {
|
||||
export async function main() {
|
||||
const files = (
|
||||
await Promise.all(
|
||||
sourceRoots.map(async (sourceRoot) => {
|
||||
try {
|
||||
return await collectTypeScriptFiles(sourceRoot);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}),
|
||||
sourceRoots.map(
|
||||
async (sourceRoot) =>
|
||||
await collectTypeScriptFiles(sourceRoot, {
|
||||
extraTestSuffixes: [".browser.test.ts", ".node.test.ts"],
|
||||
ignoreMissing: true,
|
||||
}),
|
||||
),
|
||||
)
|
||||
).flat();
|
||||
|
||||
@@ -198,17 +139,4 @@ export async function main() {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const isDirectExecution = (() => {
|
||||
const entry = process.argv[1];
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
return path.resolve(entry) === fileURLToPath(import.meta.url);
|
||||
})();
|
||||
|
||||
if (isDirectExecution) {
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
runAsScript(import.meta.url, main);
|
||||
|
||||
@@ -2,63 +2,19 @@
|
||||
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import ts from "typescript";
|
||||
import {
|
||||
collectTypeScriptFiles,
|
||||
resolveRepoRoot,
|
||||
runAsScript,
|
||||
toLine,
|
||||
unwrapExpression,
|
||||
} from "./lib/ts-guard-utils.mjs";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const uiSourceDir = path.join(repoRoot, "ui", "src", "ui");
|
||||
const allowedCallsites = new Set([path.join(uiSourceDir, "open-external-url.ts")]);
|
||||
|
||||
function isTestFile(filePath) {
|
||||
return (
|
||||
filePath.endsWith(".test.ts") ||
|
||||
filePath.endsWith(".browser.test.ts") ||
|
||||
filePath.endsWith(".node.test.ts")
|
||||
);
|
||||
}
|
||||
|
||||
async function collectTypeScriptFiles(dir) {
|
||||
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||
const out = [];
|
||||
for (const entry of entries) {
|
||||
const entryPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
out.push(...(await collectTypeScriptFiles(entryPath)));
|
||||
continue;
|
||||
}
|
||||
if (!entry.isFile()) {
|
||||
continue;
|
||||
}
|
||||
if (!entryPath.endsWith(".ts")) {
|
||||
continue;
|
||||
}
|
||||
if (isTestFile(entryPath)) {
|
||||
continue;
|
||||
}
|
||||
out.push(entryPath);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function unwrapExpression(expression) {
|
||||
let current = expression;
|
||||
while (true) {
|
||||
if (ts.isParenthesizedExpression(current)) {
|
||||
current = current.expression;
|
||||
continue;
|
||||
}
|
||||
if (ts.isAsExpression(current) || ts.isTypeAssertionExpression(current)) {
|
||||
current = current.expression;
|
||||
continue;
|
||||
}
|
||||
if (ts.isNonNullExpression(current)) {
|
||||
current = current.expression;
|
||||
continue;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
function asPropertyAccess(expression) {
|
||||
if (ts.isPropertyAccessExpression(expression)) {
|
||||
return expression;
|
||||
@@ -87,9 +43,7 @@ export function findRawWindowOpenLines(content, fileName = "source.ts") {
|
||||
|
||||
const visit = (node) => {
|
||||
if (ts.isCallExpression(node) && isRawWindowOpenCall(node.expression)) {
|
||||
const line =
|
||||
sourceFile.getLineAndCharacterOfPosition(node.expression.getStart(sourceFile)).line + 1;
|
||||
lines.push(line);
|
||||
lines.push(toLine(sourceFile, node.expression));
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
@@ -99,7 +53,10 @@ export function findRawWindowOpenLines(content, fileName = "source.ts") {
|
||||
}
|
||||
|
||||
export async function main() {
|
||||
const files = await collectTypeScriptFiles(uiSourceDir);
|
||||
const files = await collectTypeScriptFiles(uiSourceDir, {
|
||||
extraTestSuffixes: [".browser.test.ts", ".node.test.ts"],
|
||||
ignoreMissing: true,
|
||||
});
|
||||
const violations = [];
|
||||
|
||||
for (const filePath of files) {
|
||||
@@ -126,17 +83,4 @@ export async function main() {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const isDirectExecution = (() => {
|
||||
const entry = process.argv[1];
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
return path.resolve(entry) === fileURLToPath(import.meta.url);
|
||||
})();
|
||||
|
||||
if (isDirectExecution) {
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
runAsScript(import.meta.url, main);
|
||||
|
||||
@@ -1,50 +1,18 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import ts from "typescript";
|
||||
import {
|
||||
collectFileViolations,
|
||||
getPropertyNameText,
|
||||
resolveRepoRoot,
|
||||
runAsScript,
|
||||
toLine,
|
||||
} from "./lib/ts-guard-utils.mjs";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const sourceRoots = [path.join(repoRoot, "src"), path.join(repoRoot, "extensions")];
|
||||
|
||||
function isTestLikeFile(filePath) {
|
||||
return (
|
||||
filePath.endsWith(".test.ts") ||
|
||||
filePath.endsWith(".test-utils.ts") ||
|
||||
filePath.endsWith(".test-harness.ts") ||
|
||||
filePath.endsWith(".e2e-harness.ts")
|
||||
);
|
||||
}
|
||||
|
||||
async function collectTypeScriptFiles(dir) {
|
||||
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||
const out = [];
|
||||
for (const entry of entries) {
|
||||
const entryPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
out.push(...(await collectTypeScriptFiles(entryPath)));
|
||||
continue;
|
||||
}
|
||||
if (!entry.isFile() || !entryPath.endsWith(".ts") || isTestLikeFile(entryPath)) {
|
||||
continue;
|
||||
}
|
||||
out.push(entryPath);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function toLine(sourceFile, node) {
|
||||
return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
||||
}
|
||||
|
||||
function getPropertyNameText(name) {
|
||||
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
|
||||
return name.text;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function isUndefinedLikeExpression(node) {
|
||||
if (ts.isIdentifier(node) && node.text === "undefined") {
|
||||
return true;
|
||||
@@ -114,21 +82,11 @@ function findViolations(content, filePath) {
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const files = (
|
||||
await Promise.all(sourceRoots.map(async (root) => await collectTypeScriptFiles(root)))
|
||||
).flat();
|
||||
const violations = [];
|
||||
|
||||
for (const filePath of files) {
|
||||
const content = await fs.readFile(filePath, "utf8");
|
||||
const fileViolations = findViolations(content, filePath);
|
||||
for (const violation of fileViolations) {
|
||||
violations.push({
|
||||
path: path.relative(repoRoot, filePath),
|
||||
...violation,
|
||||
});
|
||||
}
|
||||
}
|
||||
const violations = await collectFileViolations({
|
||||
sourceRoots,
|
||||
repoRoot,
|
||||
findViolations,
|
||||
});
|
||||
|
||||
if (violations.length === 0) {
|
||||
return;
|
||||
@@ -141,17 +99,4 @@ async function main() {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const isDirectExecution = (() => {
|
||||
const entry = process.argv[1];
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
return path.resolve(entry) === fileURLToPath(import.meta.url);
|
||||
})();
|
||||
|
||||
if (isDirectExecution) {
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
runAsScript(import.meta.url, main);
|
||||
|
||||
@@ -340,39 +340,17 @@ async function discordApi<T>(params: {
|
||||
body?: unknown;
|
||||
retries?: number;
|
||||
}): Promise<T> {
|
||||
const retries = params.retries ?? 6;
|
||||
for (let attempt = 0; attempt <= retries; attempt += 1) {
|
||||
const response = await fetch(`${DISCORD_API_BASE}${params.path}`, {
|
||||
method: params.method,
|
||||
headers: {
|
||||
Authorization: params.authHeader,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: params.body === undefined ? undefined : JSON.stringify(params.body),
|
||||
});
|
||||
|
||||
if (response.status === 429) {
|
||||
const body = (await response.json().catch(() => ({}))) as { retry_after?: number };
|
||||
const waitSeconds = typeof body.retry_after === "number" ? body.retry_after : 1;
|
||||
await sleep(Math.ceil(waitSeconds * 1000));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text().catch(() => "");
|
||||
throw new Error(
|
||||
`Discord API ${params.method} ${params.path} failed: ${response.status} ${response.statusText}${text ? ` :: ${text}` : ""}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (response.status === 204) {
|
||||
return undefined as T;
|
||||
}
|
||||
|
||||
return (await response.json()) as T;
|
||||
}
|
||||
|
||||
throw new Error(`Discord API ${params.method} ${params.path} exceeded retry budget.`);
|
||||
return requestDiscordJson<T>({
|
||||
method: params.method,
|
||||
path: params.path,
|
||||
headers: {
|
||||
Authorization: params.authHeader,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: params.body,
|
||||
retries: params.retries,
|
||||
errorPrefix: "Discord API",
|
||||
});
|
||||
}
|
||||
|
||||
async function discordWebhookApi<T>(params: {
|
||||
@@ -383,15 +361,33 @@ async function discordWebhookApi<T>(params: {
|
||||
query?: string;
|
||||
retries?: number;
|
||||
}): Promise<T> {
|
||||
const retries = params.retries ?? 6;
|
||||
const suffix = params.query ? `?${params.query}` : "";
|
||||
const path = `/webhooks/${encodeURIComponent(params.webhookId)}/${encodeURIComponent(params.webhookToken)}${suffix}`;
|
||||
return requestDiscordJson<T>({
|
||||
method: params.method,
|
||||
path,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: params.body,
|
||||
retries: params.retries,
|
||||
errorPrefix: "Discord webhook API",
|
||||
});
|
||||
}
|
||||
|
||||
async function requestDiscordJson<T>(params: {
|
||||
method: string;
|
||||
path: string;
|
||||
headers: Record<string, string>;
|
||||
body?: unknown;
|
||||
retries?: number;
|
||||
errorPrefix: string;
|
||||
}): Promise<T> {
|
||||
const retries = params.retries ?? 6;
|
||||
for (let attempt = 0; attempt <= retries; attempt += 1) {
|
||||
const response = await fetch(`${DISCORD_API_BASE}${path}`, {
|
||||
const response = await fetch(`${DISCORD_API_BASE}${params.path}`, {
|
||||
method: params.method,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
headers: params.headers,
|
||||
body: params.body === undefined ? undefined : JSON.stringify(params.body),
|
||||
});
|
||||
|
||||
@@ -405,7 +401,7 @@ async function discordWebhookApi<T>(params: {
|
||||
if (!response.ok) {
|
||||
const text = await response.text().catch(() => "");
|
||||
throw new Error(
|
||||
`Discord webhook API ${params.method} ${path} failed: ${response.status} ${response.statusText}${text ? ` :: ${text}` : ""}`,
|
||||
`${params.errorPrefix} ${params.method} ${params.path} failed: ${response.status} ${response.statusText}${text ? ` :: ${text}` : ""}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -416,7 +412,7 @@ async function discordWebhookApi<T>(params: {
|
||||
return (await response.json()) as T;
|
||||
}
|
||||
|
||||
throw new Error(`Discord webhook API ${params.method} ${path} exceeded retry budget.`);
|
||||
throw new Error(`${params.errorPrefix} ${params.method} ${params.path} exceeded retry budget.`);
|
||||
}
|
||||
|
||||
async function readThreadBindings(filePath: string): Promise<ThreadBindingRecord[]> {
|
||||
@@ -487,6 +483,24 @@ function toRecentMessageRow(message: DiscordMessage) {
|
||||
};
|
||||
}
|
||||
|
||||
async function loadParentRecentMessages(params: {
|
||||
args: Args;
|
||||
readAuthHeader: string;
|
||||
}): Promise<DiscordMessage[]> {
|
||||
if (params.args.driverMode === "openclaw") {
|
||||
return await readMessagesWithOpenclaw({
|
||||
openclawBin: params.args.openclawBin,
|
||||
target: params.args.channelId,
|
||||
limit: 20,
|
||||
});
|
||||
}
|
||||
return await discordApi<DiscordMessage[]>({
|
||||
method: "GET",
|
||||
path: `/channels/${encodeURIComponent(params.args.channelId)}/messages?limit=20`,
|
||||
authHeader: params.readAuthHeader,
|
||||
});
|
||||
}
|
||||
|
||||
function printOutput(params: { json: boolean; payload: SuccessResult | FailureResult }) {
|
||||
if (params.json) {
|
||||
// eslint-disable-next-line no-console
|
||||
@@ -714,18 +728,7 @@ async function run(): Promise<SuccessResult | FailureResult> {
|
||||
if (!winningBinding?.threadId || !winningBinding?.targetSessionKey) {
|
||||
let parentRecent: DiscordMessage[] = [];
|
||||
try {
|
||||
parentRecent =
|
||||
args.driverMode === "openclaw"
|
||||
? await readMessagesWithOpenclaw({
|
||||
openclawBin: args.openclawBin,
|
||||
target: args.channelId,
|
||||
limit: 20,
|
||||
})
|
||||
: await discordApi<DiscordMessage[]>({
|
||||
method: "GET",
|
||||
path: `/channels/${encodeURIComponent(args.channelId)}/messages?limit=20`,
|
||||
authHeader: readAuthHeader,
|
||||
});
|
||||
parentRecent = await loadParentRecentMessages({ args, readAuthHeader });
|
||||
} catch {
|
||||
// Best effort diagnostics only.
|
||||
}
|
||||
@@ -782,18 +785,7 @@ async function run(): Promise<SuccessResult | FailureResult> {
|
||||
if (!ackMessage) {
|
||||
let parentRecent: DiscordMessage[] = [];
|
||||
try {
|
||||
parentRecent =
|
||||
args.driverMode === "openclaw"
|
||||
? await readMessagesWithOpenclaw({
|
||||
openclawBin: args.openclawBin,
|
||||
target: args.channelId,
|
||||
limit: 20,
|
||||
})
|
||||
: await discordApi<DiscordMessage[]>({
|
||||
method: "GET",
|
||||
path: `/channels/${encodeURIComponent(args.channelId)}/messages?limit=20`,
|
||||
authHeader: readAuthHeader,
|
||||
});
|
||||
parentRecent = await loadParentRecentMessages({ args, readAuthHeader });
|
||||
} catch {
|
||||
// Best effort diagnostics only.
|
||||
}
|
||||
|
||||
147
scripts/lib/ts-guard-utils.mjs
Normal file
147
scripts/lib/ts-guard-utils.mjs
Normal file
@@ -0,0 +1,147 @@
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import ts from "typescript";
|
||||
|
||||
const baseTestSuffixes = [".test.ts", ".test-utils.ts", ".test-harness.ts", ".e2e-harness.ts"];
|
||||
|
||||
export function resolveRepoRoot(importMetaUrl) {
|
||||
return path.resolve(path.dirname(fileURLToPath(importMetaUrl)), "..", "..");
|
||||
}
|
||||
|
||||
export function isTestLikeTypeScriptFile(filePath, options = {}) {
|
||||
const extraTestSuffixes = options.extraTestSuffixes ?? [];
|
||||
return [...baseTestSuffixes, ...extraTestSuffixes].some((suffix) => filePath.endsWith(suffix));
|
||||
}
|
||||
|
||||
export async function collectTypeScriptFiles(targetPath, options = {}) {
|
||||
const includeTests = options.includeTests ?? false;
|
||||
const extraTestSuffixes = options.extraTestSuffixes ?? [];
|
||||
const skipNodeModules = options.skipNodeModules ?? true;
|
||||
const ignoreMissing = options.ignoreMissing ?? false;
|
||||
|
||||
let stat;
|
||||
try {
|
||||
stat = await fs.stat(targetPath);
|
||||
} catch (error) {
|
||||
if (
|
||||
ignoreMissing &&
|
||||
error &&
|
||||
typeof error === "object" &&
|
||||
"code" in error &&
|
||||
error.code === "ENOENT"
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (stat.isFile()) {
|
||||
if (!targetPath.endsWith(".ts")) {
|
||||
return [];
|
||||
}
|
||||
if (!includeTests && isTestLikeTypeScriptFile(targetPath, { extraTestSuffixes })) {
|
||||
return [];
|
||||
}
|
||||
return [targetPath];
|
||||
}
|
||||
|
||||
const entries = await fs.readdir(targetPath, { withFileTypes: true });
|
||||
const out = [];
|
||||
for (const entry of entries) {
|
||||
const entryPath = path.join(targetPath, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
if (skipNodeModules && entry.name === "node_modules") {
|
||||
continue;
|
||||
}
|
||||
out.push(...(await collectTypeScriptFiles(entryPath, options)));
|
||||
continue;
|
||||
}
|
||||
if (!entry.isFile() || !entryPath.endsWith(".ts")) {
|
||||
continue;
|
||||
}
|
||||
if (!includeTests && isTestLikeTypeScriptFile(entryPath, { extraTestSuffixes })) {
|
||||
continue;
|
||||
}
|
||||
out.push(entryPath);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export async function collectFileViolations(params) {
|
||||
const files = (
|
||||
await Promise.all(
|
||||
params.sourceRoots.map(
|
||||
async (root) =>
|
||||
await collectTypeScriptFiles(root, {
|
||||
ignoreMissing: true,
|
||||
extraTestSuffixes: params.extraTestSuffixes,
|
||||
}),
|
||||
),
|
||||
)
|
||||
).flat();
|
||||
|
||||
const violations = [];
|
||||
for (const filePath of files) {
|
||||
if (params.skipFile?.(filePath)) {
|
||||
continue;
|
||||
}
|
||||
const content = await fs.readFile(filePath, "utf8");
|
||||
const fileViolations = params.findViolations(content, filePath);
|
||||
for (const violation of fileViolations) {
|
||||
violations.push({
|
||||
path: path.relative(params.repoRoot, filePath),
|
||||
...violation,
|
||||
});
|
||||
}
|
||||
}
|
||||
return violations;
|
||||
}
|
||||
|
||||
export function toLine(sourceFile, node) {
|
||||
return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
||||
}
|
||||
|
||||
export function getPropertyNameText(name) {
|
||||
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
|
||||
return name.text;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function unwrapExpression(expression) {
|
||||
let current = expression;
|
||||
while (true) {
|
||||
if (ts.isParenthesizedExpression(current)) {
|
||||
current = current.expression;
|
||||
continue;
|
||||
}
|
||||
if (ts.isAsExpression(current) || ts.isTypeAssertionExpression(current)) {
|
||||
current = current.expression;
|
||||
continue;
|
||||
}
|
||||
if (ts.isNonNullExpression(current)) {
|
||||
current = current.expression;
|
||||
continue;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
export function isDirectExecution(importMetaUrl) {
|
||||
const entry = process.argv[1];
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
return path.resolve(entry) === fileURLToPath(importMetaUrl);
|
||||
}
|
||||
|
||||
export function runAsScript(importMetaUrl, main) {
|
||||
if (!isDirectExecution(importMetaUrl)) {
|
||||
return;
|
||||
}
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -6,12 +6,45 @@ import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const SCRIPT = path.join(process.cwd(), "scripts", "ios-team-id.sh");
|
||||
const XCODE_PLIST_PATH = path.join("Library", "Preferences", "com.apple.dt.Xcode.plist");
|
||||
|
||||
const DEFAULTS_WITH_ACCOUNT_SCRIPT = `#!/usr/bin/env bash
|
||||
if [[ "$3" == "DVTDeveloperAccountManagerAppleIDLists" ]]; then
|
||||
echo '(identifier = "dev@example.com";)'
|
||||
exit 0
|
||||
fi
|
||||
exit 0`;
|
||||
|
||||
async function writeExecutable(filePath: string, body: string): Promise<void> {
|
||||
await writeFile(filePath, body, "utf8");
|
||||
chmodSync(filePath, 0o755);
|
||||
}
|
||||
|
||||
async function setupFixture(params?: {
|
||||
provisioningProfiles?: Record<string, string>;
|
||||
}): Promise<{ homeDir: string; binDir: string }> {
|
||||
const homeDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-ios-team-id-"));
|
||||
const binDir = path.join(homeDir, "bin");
|
||||
await mkdir(binDir, { recursive: true });
|
||||
await mkdir(path.join(homeDir, "Library", "Preferences"), { recursive: true });
|
||||
await writeFile(path.join(homeDir, XCODE_PLIST_PATH), "");
|
||||
|
||||
const provisioningProfiles = params?.provisioningProfiles;
|
||||
if (provisioningProfiles) {
|
||||
const profilesDir = path.join(homeDir, "Library", "MobileDevice", "Provisioning Profiles");
|
||||
await mkdir(profilesDir, { recursive: true });
|
||||
for (const [name, body] of Object.entries(provisioningProfiles)) {
|
||||
await writeFile(path.join(profilesDir, name), body);
|
||||
}
|
||||
}
|
||||
|
||||
return { homeDir, binDir };
|
||||
}
|
||||
|
||||
async function writeDefaultsWithSignedInAccount(binDir: string): Promise<void> {
|
||||
await writeExecutable(path.join(binDir, "defaults"), DEFAULTS_WITH_ACCOUNT_SCRIPT);
|
||||
}
|
||||
|
||||
function runScript(
|
||||
homeDir: string,
|
||||
extraEnv: Record<string, string> = {},
|
||||
@@ -47,33 +80,18 @@ function runScript(
|
||||
|
||||
describe("scripts/ios-team-id.sh", () => {
|
||||
it("falls back to Xcode-managed provisioning profiles when preference teams are empty", async () => {
|
||||
const homeDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-ios-team-id-"));
|
||||
const binDir = path.join(homeDir, "bin");
|
||||
await mkdir(binDir, { recursive: true });
|
||||
await mkdir(path.join(homeDir, "Library", "Preferences"), { recursive: true });
|
||||
await mkdir(path.join(homeDir, "Library", "MobileDevice", "Provisioning Profiles"), {
|
||||
recursive: true,
|
||||
const { homeDir, binDir } = await setupFixture({
|
||||
provisioningProfiles: {
|
||||
"one.mobileprovision": "stub",
|
||||
},
|
||||
});
|
||||
await writeFile(path.join(homeDir, "Library", "Preferences", "com.apple.dt.Xcode.plist"), "");
|
||||
await writeFile(
|
||||
path.join(homeDir, "Library", "MobileDevice", "Provisioning Profiles", "one.mobileprovision"),
|
||||
"stub",
|
||||
);
|
||||
|
||||
await writeExecutable(
|
||||
path.join(binDir, "plutil"),
|
||||
`#!/usr/bin/env bash
|
||||
echo '{}'`,
|
||||
);
|
||||
await writeExecutable(
|
||||
path.join(binDir, "defaults"),
|
||||
`#!/usr/bin/env bash
|
||||
if [[ "$3" == "DVTDeveloperAccountManagerAppleIDLists" ]]; then
|
||||
echo '(identifier = "dev@example.com";)'
|
||||
exit 0
|
||||
fi
|
||||
exit 0`,
|
||||
);
|
||||
await writeDefaultsWithSignedInAccount(binDir);
|
||||
await writeExecutable(
|
||||
path.join(binDir, "security"),
|
||||
`#!/usr/bin/env bash
|
||||
@@ -101,11 +119,7 @@ exit 0`,
|
||||
});
|
||||
|
||||
it("prints actionable guidance when Xcode account exists but no Team ID is resolvable", async () => {
|
||||
const homeDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-ios-team-id-"));
|
||||
const binDir = path.join(homeDir, "bin");
|
||||
await mkdir(binDir, { recursive: true });
|
||||
await mkdir(path.join(homeDir, "Library", "Preferences"), { recursive: true });
|
||||
await writeFile(path.join(homeDir, "Library", "Preferences", "com.apple.dt.Xcode.plist"), "");
|
||||
const { homeDir, binDir } = await setupFixture();
|
||||
|
||||
await writeExecutable(
|
||||
path.join(binDir, "plutil"),
|
||||
@@ -135,37 +149,19 @@ exit 1`,
|
||||
});
|
||||
|
||||
it("honors IOS_PREFERRED_TEAM_ID when multiple profile teams are available", async () => {
|
||||
const homeDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-ios-team-id-"));
|
||||
const binDir = path.join(homeDir, "bin");
|
||||
await mkdir(binDir, { recursive: true });
|
||||
await mkdir(path.join(homeDir, "Library", "Preferences"), { recursive: true });
|
||||
await mkdir(path.join(homeDir, "Library", "MobileDevice", "Provisioning Profiles"), {
|
||||
recursive: true,
|
||||
const { homeDir, binDir } = await setupFixture({
|
||||
provisioningProfiles: {
|
||||
"one.mobileprovision": "stub1",
|
||||
"two.mobileprovision": "stub2",
|
||||
},
|
||||
});
|
||||
await writeFile(path.join(homeDir, "Library", "Preferences", "com.apple.dt.Xcode.plist"), "");
|
||||
await writeFile(
|
||||
path.join(homeDir, "Library", "MobileDevice", "Provisioning Profiles", "one.mobileprovision"),
|
||||
"stub1",
|
||||
);
|
||||
await writeFile(
|
||||
path.join(homeDir, "Library", "MobileDevice", "Provisioning Profiles", "two.mobileprovision"),
|
||||
"stub2",
|
||||
);
|
||||
|
||||
await writeExecutable(
|
||||
path.join(binDir, "plutil"),
|
||||
`#!/usr/bin/env bash
|
||||
echo '{}'`,
|
||||
);
|
||||
await writeExecutable(
|
||||
path.join(binDir, "defaults"),
|
||||
`#!/usr/bin/env bash
|
||||
if [[ "$3" == "DVTDeveloperAccountManagerAppleIDLists" ]]; then
|
||||
echo '(identifier = "dev@example.com";)'
|
||||
exit 0
|
||||
fi
|
||||
exit 0`,
|
||||
);
|
||||
await writeDefaultsWithSignedInAccount(binDir);
|
||||
await writeExecutable(
|
||||
path.join(binDir, "security"),
|
||||
`#!/usr/bin/env bash
|
||||
@@ -194,26 +190,14 @@ exit 0`,
|
||||
});
|
||||
|
||||
it("matches preferred team IDs even when parser output uses CRLF line endings", async () => {
|
||||
const homeDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-ios-team-id-"));
|
||||
const binDir = path.join(homeDir, "bin");
|
||||
await mkdir(binDir, { recursive: true });
|
||||
await mkdir(path.join(homeDir, "Library", "Preferences"), { recursive: true });
|
||||
await writeFile(path.join(homeDir, "Library", "Preferences", "com.apple.dt.Xcode.plist"), "");
|
||||
const { homeDir, binDir } = await setupFixture();
|
||||
|
||||
await writeExecutable(
|
||||
path.join(binDir, "plutil"),
|
||||
`#!/usr/bin/env bash
|
||||
echo '{}'`,
|
||||
);
|
||||
await writeExecutable(
|
||||
path.join(binDir, "defaults"),
|
||||
`#!/usr/bin/env bash
|
||||
if [[ "$3" == "DVTDeveloperAccountManagerAppleIDLists" ]]; then
|
||||
echo '(identifier = "dev@example.com";)'
|
||||
exit 0
|
||||
fi
|
||||
exit 0`,
|
||||
);
|
||||
await writeDefaultsWithSignedInAccount(binDir);
|
||||
await writeExecutable(
|
||||
path.join(binDir, "fake-python"),
|
||||
`#!/usr/bin/env bash
|
||||
|
||||
Reference in New Issue
Block a user