refactor: standardize runtime guard patterns with lodash-es (#154)
* refactor(ui): replace runtime type guards with lodash-es predicates * refactor(runtime): migrate typeof checks to lodash-es in core modules * refactor(ipc): standardize runtime guards with lodash-es * test(refactor): align runtime guard checks with lodash-es * docs(agents): add lodash-es runtime guard skill summary
This commit is contained in:
33 files changed
+303
-208
No files matched your search
@@ -6,6 +6,7 @@ import { logger } from '../../utils/logger';
|
||||
|
||||
import { shell } from 'electron';
|
||||
import fs from 'fs';
|
||||
import { isEmpty, isString } from 'lodash-es';
|
||||
import { updateTrayMenu } from '../../ipc/tray/handler';
|
||||
import {
|
||||
ensureGlobalOriginalFromCurrentStorage,
|
||||
@@ -46,7 +47,7 @@ function isEnterpriseClient(clientKey?: string): boolean {
|
||||
}
|
||||
|
||||
function normalizeProjectId(projectId?: string): string | null {
|
||||
if (typeof projectId !== 'string') {
|
||||
if (!isString(projectId)) {
|
||||
return null;
|
||||
}
|
||||
const normalized = projectId.trim();
|
||||
@@ -148,7 +149,7 @@ async function clearAccountStatus(account: CloudAccount): Promise<void> {
|
||||
|
||||
function hydrateActiveOAuthClientFromSettings(): void {
|
||||
const preferredClientKey = CloudAccountRepo.getSetting<string>(ACTIVE_OAUTH_CLIENT_KEY_SETTING, '');
|
||||
if (preferredClientKey && preferredClientKey.trim() !== '') {
|
||||
if (isString(preferredClientKey) && !isEmpty(preferredClientKey.trim())) {
|
||||
try {
|
||||
GoogleAPIService.setActiveOAuthClientKey(preferredClientKey);
|
||||
} catch (error) {
|
||||
@@ -195,7 +196,7 @@ async function backfillMissingOAuthClientKeyForLegacyAccounts(
|
||||
}
|
||||
|
||||
const projectMissing =
|
||||
typeof account.token.project_id !== 'string' || account.token.project_id.trim() === '';
|
||||
!isString(account.token.project_id) || isEmpty(account.token.project_id.trim());
|
||||
if (activeClientKey === ENTERPRISE_OAUTH_CLIENT_KEY && projectMissing) {
|
||||
skippedEnterpriseGuardCount += 1;
|
||||
continue;
|
||||
@@ -232,7 +233,7 @@ export async function addGoogleAccount(
|
||||
oauthClientKey?: string,
|
||||
): Promise<CloudAccount> {
|
||||
try {
|
||||
if (oauthClientKey && oauthClientKey.trim() !== '') {
|
||||
if (isString(oauthClientKey) && !isEmpty(oauthClientKey.trim())) {
|
||||
setActiveOAuthClient(oauthClientKey);
|
||||
} else {
|
||||
hydrateActiveOAuthClientFromSettings();
|
||||
@@ -684,7 +685,7 @@ export async function forcePollCloudMonitor(): Promise<void> {
|
||||
}
|
||||
|
||||
export async function startAuthFlow(oauthClientKey?: string): Promise<void> {
|
||||
if (oauthClientKey && oauthClientKey.trim() !== '') {
|
||||
if (isString(oauthClientKey) && !isEmpty(oauthClientKey.trim())) {
|
||||
setActiveOAuthClient(oauthClientKey);
|
||||
} else {
|
||||
hydrateActiveOAuthClientFromSettings();
|
||||
|
||||
@@ -4,6 +4,7 @@ import fs from 'fs';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { desc, eq } from 'drizzle-orm';
|
||||
import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
|
||||
import { isNumber, isObjectLike, isPlainObject, isString } from 'lodash-es';
|
||||
import { getCloudAccountsDbPath, getAntigravityDbPaths } from '../../utils/paths';
|
||||
import { logger } from '../../utils/logger';
|
||||
import {
|
||||
@@ -35,14 +36,14 @@ type DrizzleExecutor = Pick<
|
||||
>;
|
||||
|
||||
function isSqliteBusyError(error: unknown): boolean {
|
||||
if (!error || typeof error !== 'object') {
|
||||
if (!isObjectLike(error)) {
|
||||
return false;
|
||||
}
|
||||
const err = error as { code?: string; message?: string };
|
||||
if (err.code && SQLITE_BUSY_CODES.has(err.code)) {
|
||||
return true;
|
||||
}
|
||||
if (typeof err.message === 'string') {
|
||||
if (isString(err.message)) {
|
||||
return err.message.includes('SQLITE_BUSY') || err.message.includes('SQLITE_LOCKED');
|
||||
}
|
||||
return false;
|
||||
@@ -168,17 +169,13 @@ interface MigrationStats {
|
||||
failedFields: number;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function readStringCandidate(
|
||||
source: Record<string, unknown>,
|
||||
...keys: string[]
|
||||
): string | undefined {
|
||||
for (const key of keys) {
|
||||
const candidate = source[key];
|
||||
if (typeof candidate === 'string' && candidate.length > 0) {
|
||||
if (isString(candidate) && candidate.length > 0) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
@@ -186,14 +183,15 @@ function readStringCandidate(
|
||||
}
|
||||
|
||||
function normalizeDeviceProfile(value: unknown): DeviceProfile | undefined {
|
||||
if (!isRecord(value)) {
|
||||
if (!isPlainObject(value)) {
|
||||
return undefined;
|
||||
}
|
||||
const valueRecord = value as Record<string, unknown>;
|
||||
|
||||
const machineId = readStringCandidate(value, 'machineId', 'machine_id');
|
||||
const macMachineId = readStringCandidate(value, 'macMachineId', 'mac_machine_id');
|
||||
const devDeviceId = readStringCandidate(value, 'devDeviceId', 'dev_device_id');
|
||||
const sqmId = readStringCandidate(value, 'sqmId', 'sqm_id');
|
||||
const machineId = readStringCandidate(valueRecord, 'machineId', 'machine_id');
|
||||
const macMachineId = readStringCandidate(valueRecord, 'macMachineId', 'mac_machine_id');
|
||||
const devDeviceId = readStringCandidate(valueRecord, 'devDeviceId', 'dev_device_id');
|
||||
const sqmId = readStringCandidate(valueRecord, 'sqmId', 'sqm_id');
|
||||
|
||||
if (!machineId || !macMachineId || !devDeviceId || !sqmId) {
|
||||
return undefined;
|
||||
@@ -217,45 +215,47 @@ function areDeviceProfilesEqual(left: DeviceProfile, right: DeviceProfile): bool
|
||||
}
|
||||
|
||||
function readVersionedProfilePayload(value: unknown): unknown {
|
||||
if (!isRecord(value)) {
|
||||
if (!isPlainObject(value)) {
|
||||
return value;
|
||||
}
|
||||
if (!('schemaVersion' in value)) {
|
||||
const valueRecord = value as Record<string, unknown>;
|
||||
if (!('schemaVersion' in valueRecord)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const schemaVersion = value.schemaVersion;
|
||||
if (typeof schemaVersion !== 'number' || !Number.isFinite(schemaVersion)) {
|
||||
const schemaVersion = valueRecord.schemaVersion;
|
||||
if (!isNumber(schemaVersion) || !Number.isFinite(schemaVersion)) {
|
||||
throw new Error('invalid_device_profile_schema_version');
|
||||
}
|
||||
if (schemaVersion !== DEVICE_PAYLOAD_SCHEMA_VERSION) {
|
||||
throw new Error(`unsupported_device_profile_schema_version:${schemaVersion}`);
|
||||
}
|
||||
if (!('profile' in value)) {
|
||||
if (!('profile' in valueRecord)) {
|
||||
throw new Error('invalid_device_profile_payload');
|
||||
}
|
||||
return value.profile;
|
||||
return valueRecord.profile;
|
||||
}
|
||||
|
||||
function readVersionedHistoryPayload(value: unknown): unknown {
|
||||
if (!isRecord(value)) {
|
||||
if (!isPlainObject(value)) {
|
||||
return value;
|
||||
}
|
||||
if (!('schemaVersion' in value)) {
|
||||
const valueRecord = value as Record<string, unknown>;
|
||||
if (!('schemaVersion' in valueRecord)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const schemaVersion = value.schemaVersion;
|
||||
if (typeof schemaVersion !== 'number' || !Number.isFinite(schemaVersion)) {
|
||||
const schemaVersion = valueRecord.schemaVersion;
|
||||
if (!isNumber(schemaVersion) || !Number.isFinite(schemaVersion)) {
|
||||
throw new Error('invalid_device_history_schema_version');
|
||||
}
|
||||
if (schemaVersion !== DEVICE_PAYLOAD_SCHEMA_VERSION) {
|
||||
throw new Error(`unsupported_device_history_schema_version:${schemaVersion}`);
|
||||
}
|
||||
if (!('history' in value)) {
|
||||
if (!('history' in valueRecord)) {
|
||||
throw new Error('invalid_device_history_payload');
|
||||
}
|
||||
return value.history;
|
||||
return valueRecord.history;
|
||||
}
|
||||
|
||||
function serializeDeviceProfile(profile: DeviceProfile | undefined): string | null {
|
||||
@@ -285,23 +285,25 @@ function normalizeDeviceHistory(value: unknown): DeviceProfileVersion[] | undefi
|
||||
|
||||
const normalized: DeviceProfileVersion[] = [];
|
||||
for (const item of value) {
|
||||
if (!isRecord(item)) {
|
||||
if (!isPlainObject(item)) {
|
||||
continue;
|
||||
}
|
||||
const itemRecord = item as Record<string, unknown>;
|
||||
|
||||
const profile = normalizeDeviceProfile(item.profile);
|
||||
const profile = normalizeDeviceProfile(itemRecord.profile);
|
||||
if (!profile) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const id = typeof item.id === 'string' && item.id.length > 0 ? item.id : uuidv4();
|
||||
const createdAtCandidate = item.createdAt;
|
||||
const id = isString(itemRecord.id) && itemRecord.id.length > 0 ? itemRecord.id : uuidv4();
|
||||
const createdAtCandidate = itemRecord.createdAt;
|
||||
const createdAt =
|
||||
typeof createdAtCandidate === 'number' && Number.isFinite(createdAtCandidate)
|
||||
isNumber(createdAtCandidate) && Number.isFinite(createdAtCandidate)
|
||||
? Math.floor(createdAtCandidate)
|
||||
: Math.floor(Date.now() / 1000);
|
||||
const label = typeof item.label === 'string' && item.label.length > 0 ? item.label : 'legacy';
|
||||
const isCurrent = item.isCurrent === true;
|
||||
const label =
|
||||
isString(itemRecord.label) && itemRecord.label.length > 0 ? itemRecord.label : 'legacy';
|
||||
const isCurrent = itemRecord.isCurrent === true;
|
||||
|
||||
normalized.push({
|
||||
id,
|
||||
|
||||
@@ -2,6 +2,7 @@ import Database from 'better-sqlite3';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { isString } from 'lodash-es';
|
||||
import { AccountBackupData, AccountInfo } from '../../types/account';
|
||||
import { ItemTableValueRowSchema, type ItemTableKey } from '../../types/db';
|
||||
import { logger } from '../../utils/logger';
|
||||
@@ -192,8 +193,8 @@ export function getCurrentAccountInfo(): AccountInfo {
|
||||
// Helper to find email in object
|
||||
const findEmail = (obj: { email?: string; user?: { email?: string } }): string => {
|
||||
if (!obj) return '';
|
||||
if (typeof obj.email === 'string') return obj.email;
|
||||
if (obj.user && typeof obj.user.email === 'string') return obj.user.email;
|
||||
if (isString(obj.email)) return obj.email;
|
||||
if (obj.user && isString(obj.user.email)) return obj.user.email;
|
||||
return '';
|
||||
};
|
||||
|
||||
@@ -323,7 +324,7 @@ function _restoreSingleDb(dbPath: string, backup: AccountBackupData): boolean {
|
||||
for (const key of KEYS_TO_BACKUP) {
|
||||
if (key in backup.data) {
|
||||
const value = backup.data[key];
|
||||
const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
|
||||
const stringValue = isString(value) ? value : JSON.stringify(value);
|
||||
tx.insert(itemTable)
|
||||
.values({ key, value: stringValue })
|
||||
.onConflictDoUpdate({
|
||||
|
||||
+11
-10
@@ -2,6 +2,7 @@ import Database from 'better-sqlite3';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { createHash, randomBytes } from 'crypto';
|
||||
import { isBoolean, isNumber, isObjectLike, isString } from 'lodash-es';
|
||||
import { type DeviceProfile } from '../../types/account';
|
||||
import { logger } from '../../utils/logger';
|
||||
import { getAgentDir, getAntigravityDbPaths, getAntigravityStoragePaths } from '../../utils/paths';
|
||||
@@ -76,14 +77,14 @@ const deviceHardeningState: DeviceHardeningState = {
|
||||
};
|
||||
|
||||
function isSqliteBusyError(error: unknown): boolean {
|
||||
if (!error || typeof error !== 'object') {
|
||||
if (!isObjectLike(error)) {
|
||||
return false;
|
||||
}
|
||||
const candidate = error as { code?: string; message?: string };
|
||||
if (candidate.code === 'SQLITE_BUSY' || candidate.code === 'SQLITE_LOCKED') {
|
||||
return true;
|
||||
}
|
||||
if (typeof candidate.message === 'string') {
|
||||
if (isString(candidate.message)) {
|
||||
return candidate.message.includes('SQLITE_BUSY') || candidate.message.includes('SQLITE_LOCKED');
|
||||
}
|
||||
return false;
|
||||
@@ -139,8 +140,8 @@ function readLastKnownGoodMarker(): LastKnownGoodMarker | null {
|
||||
if (
|
||||
parsed &&
|
||||
parsed.version === 1 &&
|
||||
typeof parsed.savedAt === 'number' &&
|
||||
typeof parsed.hasStateDb === 'boolean'
|
||||
isNumber(parsed.savedAt) &&
|
||||
isBoolean(parsed.hasStateDb)
|
||||
) {
|
||||
return {
|
||||
version: 1,
|
||||
@@ -157,7 +158,7 @@ function readLastKnownGoodMarker(): LastKnownGoodMarker | null {
|
||||
function parseStorageJson(storagePath: string): Record<string, unknown> {
|
||||
const content = fs.readFileSync(storagePath, 'utf-8');
|
||||
const parsed = JSON.parse(content);
|
||||
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||
if (!isObjectLike(parsed) || Array.isArray(parsed)) {
|
||||
throw new Error('storage.json top-level value must be an object');
|
||||
}
|
||||
return parsed as Record<string, unknown>;
|
||||
@@ -165,15 +166,15 @@ function parseStorageJson(storagePath: string): Record<string, unknown> {
|
||||
|
||||
function getTelemetryField(storage: Record<string, unknown>, key: string): string | null {
|
||||
const telemetry = storage.telemetry;
|
||||
if (telemetry && typeof telemetry === 'object' && !Array.isArray(telemetry)) {
|
||||
if (isObjectLike(telemetry) && !Array.isArray(telemetry)) {
|
||||
const nestedValue = (telemetry as Record<string, unknown>)[key];
|
||||
if (typeof nestedValue === 'string' && nestedValue.length > 0) {
|
||||
if (isString(nestedValue) && nestedValue.length > 0) {
|
||||
return nestedValue;
|
||||
}
|
||||
}
|
||||
|
||||
const flatValue = storage[`telemetry.${key}`];
|
||||
if (typeof flatValue === 'string' && flatValue.length > 0) {
|
||||
if (isString(flatValue) && flatValue.length > 0) {
|
||||
return flatValue;
|
||||
}
|
||||
|
||||
@@ -183,7 +184,7 @@ function getTelemetryField(storage: Record<string, unknown>, key: string): strin
|
||||
function ensureTelemetryObject(storage: Record<string, unknown>): Record<string, unknown> {
|
||||
if (
|
||||
!storage.telemetry ||
|
||||
typeof storage.telemetry !== 'object' ||
|
||||
!isObjectLike(storage.telemetry) ||
|
||||
Array.isArray(storage.telemetry)
|
||||
) {
|
||||
storage.telemetry = {};
|
||||
@@ -223,7 +224,7 @@ function readStateServiceMachineIdValue(dbPath: string): string | null {
|
||||
const row = db
|
||||
.prepare("SELECT value FROM ItemTable WHERE key = 'storage.serviceMachineId' LIMIT 1")
|
||||
.get() as { value?: unknown } | undefined;
|
||||
if (!row || typeof row.value !== 'string' || row.value.length === 0) {
|
||||
if (!row || !isString(row.value) || row.value.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return row.value;
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
import { IPC_CHANNELS } from '@/constants';
|
||||
import { isString } from 'lodash-es';
|
||||
|
||||
// Custom IPC Client that correctly builds method paths
|
||||
function createIPCClient(port: MessagePort) {
|
||||
@@ -10,7 +11,7 @@ function createIPCClient(port: MessagePort) {
|
||||
|
||||
port.onmessage = (event) => {
|
||||
try {
|
||||
const data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
|
||||
const data = isString(event.data) ? JSON.parse(event.data) : event.data;
|
||||
|
||||
const id = data.i || data.id;
|
||||
const pending = pendingRequests.get(id);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { exec, execSync, spawn } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import findProcess, { ProcessInfo } from 'find-process';
|
||||
import { isNumber } from 'lodash-es';
|
||||
import { getAntigravityExecutablePath, isWsl } from '../../utils/paths';
|
||||
import { logger } from '../../utils/logger';
|
||||
|
||||
@@ -98,7 +99,7 @@ export async function isProcessRunning(): Promise<boolean> {
|
||||
|
||||
const processMap = new Map<number, ProcessInfo>();
|
||||
for (const proc of allMatches) {
|
||||
if (typeof proc.pid === 'number') {
|
||||
if (isNumber(proc.pid)) {
|
||||
processMap.set(proc.pid, proc);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user