169 lines
5.6 KiB
TypeScript
169 lines
5.6 KiB
TypeScript
import path from 'node:path';
|
|
import { access, mkdir, rm } from 'node:fs/promises';
|
|
import { pathToFileURL } from 'node:url';
|
|
import { getEnv } from '../config/env.js';
|
|
import { sendManagedStockAlertWebPush } from './stock-alert-service.js';
|
|
|
|
export type ManagedScheduleServiceResult = {
|
|
ok: boolean;
|
|
skipped: boolean;
|
|
title: string;
|
|
body: string;
|
|
itemCount: number;
|
|
lines: string[];
|
|
ios: {
|
|
ok: boolean;
|
|
skipped: boolean;
|
|
reason?: string;
|
|
sentCount: number;
|
|
failedCount: number;
|
|
};
|
|
web: {
|
|
ok: boolean;
|
|
skipped: boolean;
|
|
reason?: string;
|
|
sentCount: number;
|
|
failedCount: number;
|
|
};
|
|
};
|
|
|
|
export type ManagedScheduleServiceMetadata = {
|
|
scheduleId: number;
|
|
serviceKey: string;
|
|
packageName: string;
|
|
relativeDirectory: string;
|
|
manifestPath: string;
|
|
readmePath: string;
|
|
sourcePath: string;
|
|
runtimePath: string;
|
|
};
|
|
|
|
function sanitizeManagedServiceToken(value: string) {
|
|
return value
|
|
.trim()
|
|
.toLowerCase()
|
|
.replace(/[^a-z0-9]+/g, '-')
|
|
.replace(/^-+|-+$/g, '')
|
|
.slice(0, 60);
|
|
}
|
|
|
|
function appendScheduleSuffixOnce(value: string, suffix: string) {
|
|
const normalizedValue = value.trim();
|
|
const normalizedSuffix = suffix.trim().toLowerCase();
|
|
|
|
if (!normalizedValue || !normalizedSuffix) {
|
|
return normalizedValue;
|
|
}
|
|
|
|
return normalizedValue.toLowerCase().endsWith(`-${normalizedSuffix}`)
|
|
? normalizedValue
|
|
: `${normalizedValue}-${suffix.trim()}`;
|
|
}
|
|
|
|
function getScheduleRepoRoot() {
|
|
const env = getEnv();
|
|
return env.SERVER_COMMAND_MAIN_PROJECT_ROOT || env.PLAN_MAIN_PROJECT_REPO_PATH || process.cwd();
|
|
}
|
|
|
|
export function buildManagedScheduleServiceMetadata(scheduleId: number, workId: string): ManagedScheduleServiceMetadata {
|
|
const workToken = sanitizeManagedServiceToken(appendScheduleSuffixOnce(workId, 'service')) || 'task-service';
|
|
const serviceKey = `schedule-${scheduleId}-${workToken}`;
|
|
const relativeDirectory = `.auto_codex/schedule/${scheduleId}`;
|
|
const packageName = workToken || `schedule-${scheduleId}-service`;
|
|
|
|
return {
|
|
scheduleId,
|
|
serviceKey,
|
|
packageName,
|
|
relativeDirectory,
|
|
manifestPath: `${relativeDirectory}/service-manifest.json`,
|
|
readmePath: `${relativeDirectory}/README.md`,
|
|
sourcePath: `${relativeDirectory}/service.ts`,
|
|
runtimePath: `${relativeDirectory}/service.mjs`,
|
|
};
|
|
}
|
|
|
|
export async function prepareManagedScheduleServiceDirectory(scheduleId: number) {
|
|
const repoRoot = getScheduleRepoRoot();
|
|
const absoluteDirectory = path.join(repoRoot, '.auto_codex', 'schedule', String(scheduleId));
|
|
|
|
await mkdir(absoluteDirectory, { recursive: true });
|
|
await rm(path.join(absoluteDirectory, 'managed-service'), { recursive: true, force: true });
|
|
}
|
|
|
|
export async function removeManagedScheduleServiceArtifacts(scheduleId: number) {
|
|
const repoRoot = getScheduleRepoRoot();
|
|
const scheduleDirectory = path.join(repoRoot, '.auto_codex', 'schedule', String(scheduleId));
|
|
|
|
await rm(path.join(scheduleDirectory, 'managed-service'), { recursive: true, force: true });
|
|
await rm(path.join(scheduleDirectory, 'README.md'), { force: true });
|
|
await rm(path.join(scheduleDirectory, 'service-manifest.json'), { force: true });
|
|
await rm(path.join(scheduleDirectory, 'service.ts'), { force: true });
|
|
await rm(path.join(scheduleDirectory, 'service.mjs'), { force: true });
|
|
}
|
|
|
|
export async function hasManagedScheduleServicePackage(relativeDirectory: string | null | undefined) {
|
|
const trimmedDirectory = String(relativeDirectory ?? '').trim();
|
|
|
|
if (!trimmedDirectory) {
|
|
return false;
|
|
}
|
|
|
|
const repoRoot = getScheduleRepoRoot();
|
|
const runtimePath = path.join(repoRoot, trimmedDirectory, 'service.mjs');
|
|
const manifestPath = path.join(repoRoot, trimmedDirectory, 'service-manifest.json');
|
|
|
|
try {
|
|
await Promise.all([access(runtimePath), access(manifestPath)]);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export async function runManagedScheduleService(relativeDirectory: string) {
|
|
const repoRoot = getScheduleRepoRoot();
|
|
const runtimePath = path.join(repoRoot, relativeDirectory, 'service.mjs');
|
|
const importedModule = await import(`${pathToFileURL(runtimePath).href}?t=${Date.now()}`);
|
|
const run =
|
|
typeof importedModule.run === 'function'
|
|
? importedModule.run
|
|
: typeof importedModule.default?.run === 'function'
|
|
? importedModule.default.run
|
|
: null;
|
|
|
|
if (!run) {
|
|
throw new Error(`스케줄 서비스 실행 함수를 찾을 수 없습니다: ${relativeDirectory}/service.mjs`);
|
|
}
|
|
|
|
return run({
|
|
scheduleRoot: path.join(repoRoot, trimmedRelativeDirectory(relativeDirectory)),
|
|
scheduleDirectory: trimmedRelativeDirectory(relativeDirectory),
|
|
repoRoot,
|
|
now: new Date().toISOString(),
|
|
runCurrentPriceStockAlertService(definition: { scheduleId: number; serviceKey: string; title: string }) {
|
|
return sendManagedStockAlertWebPush({
|
|
scheduleId: definition.scheduleId,
|
|
serviceKey: definition.serviceKey,
|
|
title: definition.title,
|
|
mode: 'price',
|
|
});
|
|
},
|
|
runChangeRateThresholdStockAlertService(
|
|
definition: { scheduleId: number; serviceKey: string; title: string; thresholdPercent: number },
|
|
) {
|
|
return sendManagedStockAlertWebPush({
|
|
scheduleId: definition.scheduleId,
|
|
serviceKey: definition.serviceKey,
|
|
title: definition.title,
|
|
mode: 'change-threshold',
|
|
thresholdPercent: definition.thresholdPercent,
|
|
});
|
|
},
|
|
}) as Promise<ManagedScheduleServiceResult>;
|
|
}
|
|
|
|
function trimmedRelativeDirectory(relativeDirectory: string) {
|
|
return String(relativeDirectory ?? '').trim().replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
|
|
}
|