Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dist/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@
* - Environment variable injection (PATH for node/npx)
*/
import type { Plugin } from "@opencode-ai/plugin";
/** @internal Exported for testing. */
export declare function listScopeIds(root?: string): string[];
export declare const SchedulerPlugin: Plugin;
export default SchedulerPlugin;
22 changes: 12 additions & 10 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12331,7 +12331,7 @@ function tool(input) {
}
tool.schema = exports_external;
// src/index.ts
import { createWriteStream, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync, unlinkSync } from "fs";
import { createWriteStream, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, unlinkSync } from "fs";
import { basename, dirname, join, resolve as resolvePath } from "path";
import { homedir, platform } from "os";
import { execFileSync, execSync, spawn } from "child_process";
Expand Down Expand Up @@ -13517,6 +13517,13 @@ function uninstallJob(job) {
function ensureScopeStorage(scopeId) {
ensureDir(SCHEDULER_DIR);
ensureDir(SCOPES_DIR);
const dir = scopeDir(scopeId);
try {
if (!existsSync(dir) || !statSync(dir).isDirectory())
return;
} catch {
return;
}
ensureDir(scopeJobsDir(scopeId));
ensureDir(scopeLocksDir(scopeId));
ensureDir(scopeRunsDir(scopeId));
Expand Down Expand Up @@ -13544,16 +13551,10 @@ function loadAllScopedJobs(scopeId) {
}
}).filter(Boolean);
}
function listScopeIds() {
ensureDir(SCOPES_DIR);
function listScopeIds(root = SCOPES_DIR) {
ensureDir(root);
try {
return readdirSync(SCOPES_DIR).filter((name) => {
try {
return existsSync(scopeDir(name));
} catch {
return false;
}
}).sort();
return readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
} catch {
return [];
}
Expand Down Expand Up @@ -14968,6 +14969,7 @@ ${logs}`, { job, logPath, logs });
};
var src_default = SchedulerPlugin;
export {
listScopeIds,
src_default as default,
SchedulerPlugin
};
24 changes: 13 additions & 11 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
*/
import type { Plugin } from "@opencode-ai/plugin"
import { tool } from "@opencode-ai/plugin"
import { createWriteStream, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync, unlinkSync } from "fs"
import { createWriteStream, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, unlinkSync } from "fs"
import { basename, dirname, join, resolve as resolvePath } from "path"
import { homedir, platform } from "os"
import { execFileSync, execSync, spawn, type ChildProcess } from "child_process"
Expand Down Expand Up @@ -1503,6 +1503,12 @@ function uninstallJob(job: Job): void {
function ensureScopeStorage(scopeId: string): void {
ensureDir(SCHEDULER_DIR)
ensureDir(SCOPES_DIR)
const dir = scopeDir(scopeId)
try {
if (!existsSync(dir) || !statSync(dir).isDirectory()) return
} catch {
return
}
ensureDir(scopeJobsDir(scopeId))
ensureDir(scopeLocksDir(scopeId))
ensureDir(scopeRunsDir(scopeId))
Expand Down Expand Up @@ -1534,17 +1540,13 @@ function loadAllScopedJobs(scopeId: string): Job[] {
.filter(Boolean) as Job[]
}

function listScopeIds(): string[] {
ensureDir(SCOPES_DIR)
/** @internal Exported for testing. */
export function listScopeIds(root: string = SCOPES_DIR): string[] {
ensureDir(root)
try {
return readdirSync(SCOPES_DIR)
.filter((name) => {
try {
return existsSync(scopeDir(name))
} catch {
return false
}
})
return readdirSync(root, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name)
.sort()
} catch {
return []
Expand Down
74 changes: 74 additions & 0 deletions tests/scope-listing.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { describe, expect, test, beforeEach, afterEach } from "bun:test"
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "fs"
import { tmpdir } from "os"
import { join } from "path"

import { listScopeIds } from "../src/index"

let root: string

beforeEach(() => {
root = mkdtempSync(join(tmpdir(), "scheduler-scope-listing-"))
})

afterEach(() => {
rmSync(root, { recursive: true, force: true })
})

describe("listScopeIds", () => {
test("returns empty array when the scopes root does not exist", () => {
const missing = join(root, "does-not-exist")
expect(listScopeIds(missing)).toEqual([])
})

test("returns empty array when the scopes root is empty", () => {
expect(listScopeIds(root)).toEqual([])
})

test("returns names of real scope subdirectories sorted ascending", () => {
mkdirSync(join(root, "beta-scope", "jobs"), { recursive: true })
mkdirSync(join(root, "alpha-scope", "jobs"), { recursive: true })

const result = listScopeIds(root)

expect(result).toEqual(["alpha-scope", "beta-scope"])
})

test("skips macOS Finder metadata files like .DS_Store", () => {
mkdirSync(join(root, "real-scope", "jobs"), { recursive: true })
writeFileSync(join(root, ".DS_Store"), Buffer.from([0x00, 0x01, 0x02, 0x03, 0x04]))

const result = listScopeIds(root)

expect(result).toEqual(["real-scope"])
})

test("skips plain files that share the scopes root with directories", () => {
mkdirSync(join(root, "real-scope", "jobs"), { recursive: true })
writeFileSync(join(root, "foo.txt"), "noise")
writeFileSync(join(root, "leftover.json"), "{}")

const result = listScopeIds(root)

expect(result).toEqual(["real-scope"])
})

test("mixed real scope, .DS_Store, and stray file returns only the real scope", () => {
mkdirSync(join(root, "real-scope", "jobs"), { recursive: true })
mkdirSync(join(root, "another-real", "jobs"), { recursive: true })
writeFileSync(join(root, ".DS_Store"), "mac noise")
writeFileSync(join(root, "stray.txt"), "stray")

const result = listScopeIds(root)

expect(result).toEqual(["another-real", "real-scope"])
})

test("does not throw when a non-directory entry is encountered", () => {
mkdirSync(join(root, "real-scope", "jobs"), { recursive: true })
writeFileSync(join(root, ".DS_Store"), "mac noise")
writeFileSync(join(root, "stray.txt"), "stray")

expect(() => listScopeIds(root)).not.toThrow()
})
})