feat: eslint 적용 및 관련 코드 일괄 수정
This commit is contained in:
@@ -5,11 +5,7 @@ import { createGamePostgresConnector, resolvePostgresConfigFromEnv } from '@samm
|
||||
|
||||
import type { BuildRunner } from './buildRunner.js';
|
||||
import type { ProcessManager } from './processManager.js';
|
||||
import type {
|
||||
GatewayProfileRecord,
|
||||
GatewayProfileRepository,
|
||||
GatewayProfileStatus,
|
||||
} from './profileRepository.js';
|
||||
import type { GatewayProfileRecord, GatewayProfileRepository, GatewayProfileStatus } from './profileRepository.js';
|
||||
import type { GitWorkspaceManager } from './workspaceManager.js';
|
||||
|
||||
export interface GatewayProcessConfig {
|
||||
@@ -58,12 +54,7 @@ export const planProfileReconcile = (
|
||||
status: GatewayProfileStatus,
|
||||
runtime: ProfileRuntimeState
|
||||
): { shouldStart: boolean; shouldStop: boolean } => {
|
||||
if (
|
||||
status === 'RUNNING' ||
|
||||
status === 'PREOPEN' ||
|
||||
status === 'PAUSED' ||
|
||||
status === 'COMPLETED'
|
||||
) {
|
||||
if (status === 'RUNNING' || status === 'PREOPEN' || status === 'PAUSED' || status === 'COMPLETED') {
|
||||
return {
|
||||
shouldStart: !(runtime.apiRunning && runtime.daemonRunning),
|
||||
shouldStop: false,
|
||||
@@ -97,8 +88,7 @@ interface GatewayAdminActionResult {
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
||||
|
||||
const normalizeMeta = (value: unknown): Record<string, unknown> =>
|
||||
isRecord(value) ? value : {};
|
||||
const normalizeMeta = (value: unknown): Record<string, unknown> => (isRecord(value) ? value : {});
|
||||
|
||||
const normalizeStatus = (value: unknown): GatewayAdminActionStatus | null => {
|
||||
if (typeof value === 'string') {
|
||||
@@ -108,16 +98,9 @@ const normalizeStatus = (value: unknown): GatewayAdminActionStatus | null => {
|
||||
};
|
||||
|
||||
const buildActionKey = (action: GatewayAdminActionRecord): string =>
|
||||
[
|
||||
action.action ?? '',
|
||||
action.requestedAt ?? '',
|
||||
action.scheduledAt ?? '',
|
||||
action.reason ?? '',
|
||||
].join('|');
|
||||
[action.action ?? '', action.requestedAt ?? '', action.scheduledAt ?? '', action.reason ?? ''].join('|');
|
||||
|
||||
const parseScenarioId = (
|
||||
value: string | number | null | undefined
|
||||
): number | null => {
|
||||
const parseScenarioId = (value: string | number | null | undefined): number | null => {
|
||||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||||
return Math.floor(value);
|
||||
}
|
||||
@@ -136,8 +119,10 @@ const buildProcessName = (profileName: string, role: 'api' | 'daemon'): string =
|
||||
const buildProcessDefinitions = (
|
||||
profile: GatewayProfileRecord,
|
||||
config: GatewayProcessConfig
|
||||
): { api: { name: string; script: string; cwd: string; env: Record<string, string> };
|
||||
daemon: { name: string; script: string; cwd: string; env: Record<string, string> } } => {
|
||||
): {
|
||||
api: { name: string; script: string; cwd: string; env: Record<string, string> };
|
||||
daemon: { name: string; script: string; cwd: string; env: Record<string, string> };
|
||||
} => {
|
||||
const baseEnv = { ...(config.baseEnv ?? {}) };
|
||||
const apiName = buildProcessName(profile.profileName, 'api');
|
||||
const daemonName = buildProcessName(profile.profileName, 'daemon');
|
||||
@@ -176,10 +161,7 @@ const buildProcessDefinitions = (
|
||||
};
|
||||
};
|
||||
|
||||
const mapRuntimeStates = (
|
||||
profileNames: string[],
|
||||
processNames: Map<string, boolean>
|
||||
): ProfileRuntimeSnapshot[] =>
|
||||
const mapRuntimeStates = (profileNames: string[], processNames: Map<string, boolean>): ProfileRuntimeSnapshot[] =>
|
||||
profileNames.map((profileName) => {
|
||||
const apiName = buildProcessName(profileName, 'api');
|
||||
const daemonName = buildProcessName(profileName, 'daemon');
|
||||
@@ -227,22 +209,10 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle {
|
||||
start(): void {
|
||||
void this.reconcileNow();
|
||||
void this.runAdminActionsNow();
|
||||
this.reconcileTimer = setInterval(
|
||||
() => void this.reconcileNow(),
|
||||
this.reconcileIntervalMs
|
||||
);
|
||||
this.scheduleTimer = setInterval(
|
||||
() => void this.runScheduleNow(),
|
||||
this.scheduleIntervalMs
|
||||
);
|
||||
this.buildTimer = setInterval(
|
||||
() => void this.runBuildQueueNow(),
|
||||
this.buildIntervalMs
|
||||
);
|
||||
this.adminActionTimer = setInterval(
|
||||
() => void this.runAdminActionsNow(),
|
||||
this.adminActionIntervalMs
|
||||
);
|
||||
this.reconcileTimer = setInterval(() => void this.reconcileNow(), this.reconcileIntervalMs);
|
||||
this.scheduleTimer = setInterval(() => void this.runScheduleNow(), this.scheduleIntervalMs);
|
||||
this.buildTimer = setInterval(() => void this.runBuildQueueNow(), this.buildIntervalMs);
|
||||
this.adminActionTimer = setInterval(() => void this.runAdminActionsNow(), this.adminActionIntervalMs);
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
@@ -313,8 +283,7 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const queued =
|
||||
profile.buildStatus === 'QUEUED' || profile.buildStatus === 'RUNNING';
|
||||
const queued = profile.buildStatus === 'QUEUED' || profile.buildStatus === 'RUNNING';
|
||||
if (!queued) {
|
||||
await this.repository.updateBuildStatus(profile.profileName, 'QUEUED', {
|
||||
requestedAt: now.toISOString(),
|
||||
@@ -325,11 +294,7 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle {
|
||||
}
|
||||
const profiles = await this.repository.listProfiles();
|
||||
for (const profile of profiles) {
|
||||
if (
|
||||
profile.status === 'PREOPEN' &&
|
||||
profile.openAt &&
|
||||
new Date(profile.openAt) <= now
|
||||
) {
|
||||
if (profile.status === 'PREOPEN' && profile.openAt && new Date(profile.openAt) <= now) {
|
||||
await this.repository.updateStatus(profile.profileName, 'RUNNING', {
|
||||
preopenAt: profile.preopenAt ?? null,
|
||||
openAt: profile.openAt ?? null,
|
||||
@@ -363,10 +328,7 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle {
|
||||
startedAt,
|
||||
error: null,
|
||||
});
|
||||
const result = await this.runBuildCommands(
|
||||
queued.profileName,
|
||||
queued.buildCommitSha
|
||||
);
|
||||
const result = await this.runBuildCommands(queued.profileName, queued.buildCommitSha);
|
||||
const completedAt = this.now().toISOString();
|
||||
if (result.ok) {
|
||||
await this.repository.updateBuildStatus(queued.profileName, 'SUCCEEDED', {
|
||||
@@ -376,9 +338,7 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle {
|
||||
if (queued.status === 'RESERVED') {
|
||||
await this.repository.updateStatus(
|
||||
queued.profileName,
|
||||
queued.openAt && new Date(queued.openAt) <= this.now()
|
||||
? 'RUNNING'
|
||||
: 'PREOPEN',
|
||||
queued.openAt && new Date(queued.openAt) <= this.now() ? 'RUNNING' : 'PREOPEN',
|
||||
{
|
||||
preopenAt: queued.preopenAt ?? null,
|
||||
openAt: queued.openAt ?? null,
|
||||
@@ -418,13 +378,9 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle {
|
||||
}
|
||||
}
|
||||
|
||||
private async handleProfileAdminActions(
|
||||
profile: GatewayProfileRecord
|
||||
): Promise<void> {
|
||||
private async handleProfileAdminActions(profile: GatewayProfileRecord): Promise<void> {
|
||||
const meta = normalizeMeta(profile.meta);
|
||||
const rawActions = Array.isArray(meta.adminActions)
|
||||
? meta.adminActions
|
||||
: [];
|
||||
const rawActions = Array.isArray(meta.adminActions) ? meta.adminActions : [];
|
||||
if (!rawActions.length) {
|
||||
return;
|
||||
}
|
||||
@@ -442,10 +398,7 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle {
|
||||
return;
|
||||
}
|
||||
|
||||
const updates = new Map<
|
||||
string,
|
||||
{ status: GatewayAdminActionStatus; detail?: string; handledAt: string }
|
||||
>();
|
||||
const updates = new Map<string, { status: GatewayAdminActionStatus; detail?: string; handledAt: string }>();
|
||||
|
||||
for (const action of pending) {
|
||||
if (action.action !== 'RESET_NOW' && action.action !== 'RESET_SCHEDULED') {
|
||||
@@ -528,9 +481,7 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle {
|
||||
return { status: 'FAILED', detail: 'scenarioId is missing' };
|
||||
}
|
||||
const seedTime =
|
||||
action.scheduledAt && action.action === 'RESET_SCHEDULED'
|
||||
? new Date(action.scheduledAt)
|
||||
: this.now();
|
||||
action.scheduledAt && action.action === 'RESET_SCHEDULED' ? new Date(action.scheduledAt) : this.now();
|
||||
const startedAt = this.now().toISOString();
|
||||
await this.repository.updateStatus(profile.profileName, 'STOPPED');
|
||||
await this.stopProfile(profile);
|
||||
@@ -646,10 +597,7 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle {
|
||||
async cleanupStaleWorkspaces(): Promise<{ removed: string[]; skipped: string[] }> {
|
||||
const profiles = await this.repository.listProfiles();
|
||||
const cutoff = this.computeCutoffDate(6);
|
||||
const workspaceMap = new Map<
|
||||
string,
|
||||
{ profileNames: string[]; lastUsedAt?: Date; hasActiveBuild: boolean }
|
||||
>();
|
||||
const workspaceMap = new Map<string, { profileNames: string[]; lastUsedAt?: Date; hasActiveBuild: boolean }>();
|
||||
for (const profile of profiles) {
|
||||
const workspace = profile.buildWorkspace;
|
||||
if (!workspace) {
|
||||
@@ -733,8 +681,7 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle {
|
||||
const statusMap = new Map<string, boolean>();
|
||||
for (const process of processes) {
|
||||
const status = process.status.toLowerCase();
|
||||
const running =
|
||||
status === 'online' || status === 'launching' || status === 'stopping';
|
||||
const running = status === 'online' || status === 'launching' || status === 'stopping';
|
||||
statusMap.set(process.name, running);
|
||||
}
|
||||
return statusMap;
|
||||
|
||||
@@ -9,9 +9,7 @@ import { resolveWorkspaceRoot } from './workspaceRoot.js';
|
||||
import { GitWorkspaceManager } from './workspaceManager.js';
|
||||
|
||||
export const buildEnvMap = (env: NodeJS.ProcessEnv): Record<string, string> => {
|
||||
const entries = Object.entries(env).filter(
|
||||
(entry): entry is [string, string] => typeof entry[1] === 'string'
|
||||
);
|
||||
const entries = Object.entries(env).filter((entry): entry is [string, string] => typeof entry[1] === 'string');
|
||||
return Object.fromEntries(entries);
|
||||
};
|
||||
|
||||
|
||||
@@ -9,16 +9,10 @@ import { createGatewayOrchestrator } from './orchestratorFactory.js';
|
||||
|
||||
export const runGatewayOrchestrator = async (): Promise<void> => {
|
||||
const config = resolveGatewayOrchestratorConfigFromEnv();
|
||||
const postgres = createGatewayPostgresConnector(
|
||||
resolvePostgresConfigFromEnv({ schema: config.dbSchema })
|
||||
);
|
||||
const postgres = createGatewayPostgresConnector(resolvePostgresConfigFromEnv({ schema: config.dbSchema }));
|
||||
await postgres.connect();
|
||||
|
||||
const { orchestrator } = createGatewayOrchestrator(
|
||||
postgres.prisma as GatewayPrismaClient,
|
||||
config,
|
||||
process.env
|
||||
);
|
||||
const { orchestrator } = createGatewayOrchestrator(postgres.prisma as GatewayPrismaClient, config, process.env);
|
||||
|
||||
const stop = async (reason: string): Promise<void> => {
|
||||
console.info(`[gateway-orchestrator] stopping: ${reason}`);
|
||||
|
||||
@@ -11,13 +11,7 @@ export const GATEWAY_PROFILE_STATUSES = [
|
||||
] as const;
|
||||
export type GatewayProfileStatus = (typeof GATEWAY_PROFILE_STATUSES)[number];
|
||||
|
||||
export const GATEWAY_BUILD_STATUSES = [
|
||||
'IDLE',
|
||||
'QUEUED',
|
||||
'RUNNING',
|
||||
'FAILED',
|
||||
'SUCCEEDED',
|
||||
] as const;
|
||||
export const GATEWAY_BUILD_STATUSES = ['IDLE', 'QUEUED', 'RUNNING', 'FAILED', 'SUCCEEDED'] as const;
|
||||
export type GatewayBuildStatus = (typeof GATEWAY_BUILD_STATUSES)[number];
|
||||
|
||||
export interface GatewayProfileRecord {
|
||||
@@ -81,23 +75,15 @@ export interface GatewayProfileRepository {
|
||||
lastUsedAt?: string | null;
|
||||
}
|
||||
): Promise<GatewayProfileRecord | null>;
|
||||
updateMeta(
|
||||
profileName: string,
|
||||
meta: Record<string, unknown>
|
||||
): Promise<GatewayProfileRecord | null>;
|
||||
updateMeta(profileName: string, meta: Record<string, unknown>): Promise<GatewayProfileRecord | null>;
|
||||
listReservedToStart(now: Date): Promise<GatewayProfileRecord[]>;
|
||||
findQueuedBuild(): Promise<GatewayProfileRecord | null>;
|
||||
updateLastError(profileName: string, lastError: string | null): Promise<void>;
|
||||
updateWorkspaceUsage(
|
||||
profileName: string,
|
||||
workspace: string,
|
||||
lastUsedAt: string
|
||||
): Promise<void>;
|
||||
updateWorkspaceUsage(profileName: string, workspace: string, lastUsedAt: string): Promise<void>;
|
||||
clearWorkspaceUsage(profileNames: string[]): Promise<void>;
|
||||
}
|
||||
|
||||
const toIso = (value: Date | null): string | undefined =>
|
||||
value ? value.toISOString() : undefined;
|
||||
const toIso = (value: Date | null): string | undefined => (value ? value.toISOString() : undefined);
|
||||
|
||||
type GatewayProfileRow = {
|
||||
profileName: string;
|
||||
@@ -145,12 +131,9 @@ const mapProfile = (row: GatewayProfileRow): GatewayProfileRecord => ({
|
||||
updatedAt: row.updatedAt.toISOString(),
|
||||
});
|
||||
|
||||
const buildProfileName = (profile: string, scenario: string): string =>
|
||||
`${profile}:${scenario}`;
|
||||
const buildProfileName = (profile: string, scenario: string): string => `${profile}:${scenario}`;
|
||||
|
||||
export const createGatewayProfileRepository = (
|
||||
prisma: GatewayPrismaClient
|
||||
): GatewayProfileRepository => ({
|
||||
export const createGatewayProfileRepository = (prisma: GatewayPrismaClient): GatewayProfileRepository => ({
|
||||
async listProfiles(): Promise<GatewayProfileRecord[]> {
|
||||
const rows = await prisma.gatewayProfile.findMany({
|
||||
orderBy: [{ profile: 'asc' }, { scenario: 'asc' }],
|
||||
@@ -175,34 +158,21 @@ export const createGatewayProfileRepository = (
|
||||
status: input.status ?? 'STOPPED',
|
||||
preopenAt: input.preopenAt ? new Date(input.preopenAt) : null,
|
||||
openAt: input.openAt ? new Date(input.openAt) : null,
|
||||
scheduledStartAt: input.scheduledStartAt
|
||||
? new Date(input.scheduledStartAt)
|
||||
: null,
|
||||
scheduledStartAt: input.scheduledStartAt ? new Date(input.scheduledStartAt) : null,
|
||||
buildCommitSha: input.buildCommitSha ?? null,
|
||||
meta: (input.meta ?? {}) as GatewayPrisma.JsonObject,
|
||||
},
|
||||
update: {
|
||||
apiPort: input.apiPort,
|
||||
status: input.status,
|
||||
preopenAt: input.preopenAt
|
||||
? new Date(input.preopenAt)
|
||||
: input.preopenAt === null
|
||||
? null
|
||||
: undefined,
|
||||
openAt: input.openAt
|
||||
? new Date(input.openAt)
|
||||
: input.openAt === null
|
||||
? null
|
||||
: undefined,
|
||||
preopenAt: input.preopenAt ? new Date(input.preopenAt) : input.preopenAt === null ? null : undefined,
|
||||
openAt: input.openAt ? new Date(input.openAt) : input.openAt === null ? null : undefined,
|
||||
scheduledStartAt: input.scheduledStartAt
|
||||
? new Date(input.scheduledStartAt)
|
||||
: input.scheduledStartAt === null
|
||||
? null
|
||||
: undefined,
|
||||
buildCommitSha:
|
||||
input.buildCommitSha === undefined
|
||||
? undefined
|
||||
: input.buildCommitSha,
|
||||
buildCommitSha: input.buildCommitSha === undefined ? undefined : input.buildCommitSha,
|
||||
meta: input.meta ? (input.meta as GatewayPrisma.JsonObject) : undefined,
|
||||
},
|
||||
});
|
||||
@@ -229,11 +199,7 @@ export const createGatewayProfileRepository = (
|
||||
? new Date(schedule.preopenAt)
|
||||
: null,
|
||||
openAt:
|
||||
schedule?.openAt === undefined
|
||||
? undefined
|
||||
: schedule?.openAt
|
||||
? new Date(schedule.openAt)
|
||||
: null,
|
||||
schedule?.openAt === undefined ? undefined : schedule?.openAt ? new Date(schedule.openAt) : null,
|
||||
scheduledStartAt:
|
||||
schedule?.scheduledStartAt === undefined
|
||||
? undefined
|
||||
@@ -262,10 +228,8 @@ export const createGatewayProfileRepository = (
|
||||
where: { profileName },
|
||||
data: {
|
||||
buildStatus: status,
|
||||
buildCommitSha:
|
||||
fields?.commitSha === undefined ? undefined : fields.commitSha,
|
||||
buildWorkspace:
|
||||
fields?.workspace === undefined ? undefined : fields.workspace,
|
||||
buildCommitSha: fields?.commitSha === undefined ? undefined : fields.commitSha,
|
||||
buildWorkspace: fields?.workspace === undefined ? undefined : fields.workspace,
|
||||
buildLastUsedAt:
|
||||
fields?.lastUsedAt === undefined
|
||||
? undefined
|
||||
@@ -279,11 +243,7 @@ export const createGatewayProfileRepository = (
|
||||
? new Date(fields.requestedAt)
|
||||
: null,
|
||||
buildStartedAt:
|
||||
fields?.startedAt === undefined
|
||||
? undefined
|
||||
: fields?.startedAt
|
||||
? new Date(fields.startedAt)
|
||||
: null,
|
||||
fields?.startedAt === undefined ? undefined : fields?.startedAt ? new Date(fields.startedAt) : null,
|
||||
buildCompletedAt:
|
||||
fields?.completedAt === undefined
|
||||
? undefined
|
||||
@@ -295,10 +255,7 @@ export const createGatewayProfileRepository = (
|
||||
});
|
||||
return row ? mapProfile(row) : null;
|
||||
},
|
||||
async updateMeta(
|
||||
profileName: string,
|
||||
meta: Record<string, unknown>
|
||||
): Promise<GatewayProfileRecord | null> {
|
||||
async updateMeta(profileName: string, meta: Record<string, unknown>): Promise<GatewayProfileRecord | null> {
|
||||
const gatewayProfile = prisma.gatewayProfile;
|
||||
const row = await gatewayProfile.update({
|
||||
where: { profileName },
|
||||
@@ -335,11 +292,7 @@ export const createGatewayProfileRepository = (
|
||||
data: { lastError },
|
||||
});
|
||||
},
|
||||
async updateWorkspaceUsage(
|
||||
profileName: string,
|
||||
workspace: string,
|
||||
lastUsedAt: string
|
||||
): Promise<void> {
|
||||
async updateWorkspaceUsage(profileName: string, workspace: string, lastUsedAt: string): Promise<void> {
|
||||
const gatewayProfile = prisma.gatewayProfile;
|
||||
await gatewayProfile.update({
|
||||
where: { profileName },
|
||||
|
||||
@@ -14,11 +14,7 @@ export interface WorkspaceInfo {
|
||||
needsInstall: boolean;
|
||||
}
|
||||
|
||||
const runGit = (
|
||||
args: string[],
|
||||
cwd: string,
|
||||
env?: Record<string, string>
|
||||
): Promise<{ ok: boolean; output: string }> =>
|
||||
const runGit = (args: string[], cwd: string, env?: Record<string, string>): Promise<{ ok: boolean; output: string }> =>
|
||||
new Promise((resolve) => {
|
||||
const child = spawn('git', args, {
|
||||
cwd,
|
||||
@@ -43,8 +39,7 @@ const ensureDir = (dir: string): void => {
|
||||
}
|
||||
};
|
||||
|
||||
const hasInstallMarker = (dir: string): boolean =>
|
||||
fs.existsSync(path.join(dir, 'node_modules', '.pnpm'));
|
||||
const hasInstallMarker = (dir: string): boolean => fs.existsSync(path.join(dir, 'node_modules', '.pnpm'));
|
||||
|
||||
export class GitWorkspaceManager {
|
||||
private readonly repoRoot: string;
|
||||
@@ -63,11 +58,7 @@ export class GitWorkspaceManager {
|
||||
|
||||
const exists = fs.existsSync(workspacePath);
|
||||
if (!exists) {
|
||||
const hasCommit = await runGit(
|
||||
['cat-file', '-e', `${commitSha}^{commit}`],
|
||||
this.repoRoot,
|
||||
this.baseEnv
|
||||
);
|
||||
const hasCommit = await runGit(['cat-file', '-e', `${commitSha}^{commit}`], this.repoRoot, this.baseEnv);
|
||||
if (!hasCommit.ok) {
|
||||
await runGit(['fetch', '--all', '--tags'], this.repoRoot, this.baseEnv);
|
||||
}
|
||||
@@ -97,11 +88,7 @@ export class GitWorkspaceManager {
|
||||
if (!fs.existsSync(resolved)) {
|
||||
return false;
|
||||
}
|
||||
const result = await runGit(
|
||||
['worktree', 'remove', '--force', resolved],
|
||||
this.repoRoot,
|
||||
this.baseEnv
|
||||
);
|
||||
const result = await runGit(['worktree', 'remove', '--force', resolved], this.repoRoot, this.baseEnv);
|
||||
if (!result.ok) {
|
||||
fs.rmSync(resolved, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
@@ -6,10 +6,7 @@ const WORKSPACE_MARKERS = ['pnpm-workspace.yaml', 'package.json'];
|
||||
const hasWorkspaceMarker = (dir: string): boolean =>
|
||||
WORKSPACE_MARKERS.some((marker) => fs.existsSync(path.join(dir, marker)));
|
||||
|
||||
export const resolveWorkspaceRoot = (
|
||||
startDir: string,
|
||||
maxDepth = 5
|
||||
): string => {
|
||||
export const resolveWorkspaceRoot = (startDir: string, maxDepth = 5): string => {
|
||||
let current = path.resolve(startDir);
|
||||
for (let depth = 0; depth <= maxDepth; depth += 1) {
|
||||
if (hasWorkspaceMarker(current)) {
|
||||
|
||||
Reference in New Issue
Block a user