target-supabase-sdk 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -6
- package/dist/auth/auth.api.d.ts.map +1 -1
- package/dist/auth/auth.api.js +2 -1
- package/dist/auth/auth.api.js.map +1 -1
- package/dist/browser.d.ts +52 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +45 -0
- package/dist/browser.js.map +1 -0
- package/dist/command/command.api.d.ts +43 -0
- package/dist/command/command.api.d.ts.map +1 -0
- package/dist/command/command.api.js +58 -0
- package/dist/command/command.api.js.map +1 -0
- package/dist/command/command.interface.d.ts +14 -0
- package/dist/command/command.interface.d.ts.map +1 -0
- package/dist/command/command.interface.js +9 -0
- package/dist/command/command.interface.js.map +1 -0
- package/dist/core.api.d.ts +70 -6
- package/dist/core.api.d.ts.map +1 -1
- package/dist/core.api.js +191 -13
- package/dist/core.api.js.map +1 -1
- package/dist/file/file-manager.d.ts +1 -1
- package/dist/file/file-manager.d.ts.map +1 -1
- package/dist/file/file-manager.js +1 -1
- package/dist/file/file-manager.js.map +1 -1
- package/dist/index.d.ts +2 -40
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -34
- package/dist/index.js.map +1 -1
- package/dist/link/link.interface.d.ts +1 -1
- package/dist/link/link.interface.d.ts.map +1 -1
- package/dist/node/node-log-scope.d.ts +11 -0
- package/dist/node/node-log-scope.d.ts.map +1 -0
- package/dist/node/node-log-scope.js +7 -0
- package/dist/node/node-log-scope.js.map +1 -0
- package/dist/node/node-runtime.base.d.ts +42 -0
- package/dist/node/node-runtime.base.d.ts.map +1 -0
- package/dist/node/node-runtime.base.js +397 -0
- package/dist/node/node-runtime.base.js.map +1 -0
- package/dist/node/node-task.preset.d.ts +1 -0
- package/dist/node/node-task.preset.d.ts.map +1 -0
- package/dist/node/node-task.preset.js +14 -0
- package/dist/node/node-task.preset.js.map +1 -0
- package/dist/node/node.api.d.ts +67 -31
- package/dist/node/node.api.d.ts.map +1 -1
- package/dist/node/node.api.js +65 -102
- package/dist/node/node.api.js.map +1 -1
- package/dist/node/node.interface.d.ts +7 -0
- package/dist/node/node.interface.d.ts.map +1 -1
- package/dist/node/node.interface.js +2 -0
- package/dist/node/node.interface.js.map +1 -1
- package/dist/node/node.utils.d.ts +5 -0
- package/dist/node/node.utils.d.ts.map +1 -0
- package/dist/node/node.utils.js +36 -0
- package/dist/node/node.utils.js.map +1 -0
- package/dist/node/task-node.d.ts +20 -0
- package/dist/node/task-node.d.ts.map +1 -0
- package/dist/node/task-node.js +245 -0
- package/dist/node/task-node.js.map +1 -0
- package/dist/node/trigger-node.d.ts +18 -0
- package/dist/node/trigger-node.d.ts.map +1 -0
- package/dist/node/trigger-node.js +150 -0
- package/dist/node/trigger-node.js.map +1 -0
- package/dist/node.d.ts +22 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +19 -0
- package/dist/node.js.map +1 -0
- package/dist/parcel/parcel-manager.d.ts +1 -1
- package/dist/parcel/parcel-manager.d.ts.map +1 -1
- package/dist/parcel/parcel-manager.js +1 -1
- package/dist/parcel/parcel-manager.js.map +1 -1
- package/dist/repo/repo-context.utils.d.ts +24 -0
- package/dist/repo/repo-context.utils.d.ts.map +1 -0
- package/dist/repo/repo-context.utils.js +81 -0
- package/dist/repo/repo-context.utils.js.map +1 -0
- package/dist/repo/repo-manager.d.ts +42 -0
- package/dist/repo/repo-manager.d.ts.map +1 -0
- package/dist/repo/repo-manager.js +166 -0
- package/dist/repo/repo-manager.js.map +1 -0
- package/dist/repo/repo.api.d.ts +16 -0
- package/dist/repo/repo.api.d.ts.map +1 -0
- package/dist/repo/repo.api.js +40 -0
- package/dist/repo/repo.api.js.map +1 -0
- package/dist/repo/repo.interface.d.ts +12 -5
- package/dist/repo/repo.interface.d.ts.map +1 -1
- package/dist/repo/repo.interface.js +2 -2
- package/dist/repo/repo.interface.js.map +1 -1
- package/dist/repo/repo.script-loader.d.ts +19 -0
- package/dist/repo/repo.script-loader.d.ts.map +1 -0
- package/dist/repo/repo.script-loader.js +108 -0
- package/dist/repo/repo.script-loader.js.map +1 -0
- package/dist/shared/log/create-api-logger.d.ts +21 -0
- package/dist/shared/log/create-api-logger.d.ts.map +1 -0
- package/dist/shared/log/create-api-logger.js +19 -0
- package/dist/shared/log/create-api-logger.js.map +1 -0
- package/dist/shared/log/index.d.ts +10 -0
- package/dist/shared/log/index.d.ts.map +1 -0
- package/dist/shared/log/index.js +10 -0
- package/dist/shared/log/index.js.map +1 -0
- package/dist/shared/log/log-manager.d.ts +93 -0
- package/dist/shared/log/log-manager.d.ts.map +1 -0
- package/dist/shared/log/log-manager.js +237 -0
- package/dist/shared/log/log-manager.js.map +1 -0
- package/dist/shared/log/log-scope.d.ts +49 -0
- package/dist/shared/log/log-scope.d.ts.map +1 -0
- package/dist/shared/log/log-scope.js +70 -0
- package/dist/shared/log/log-scope.js.map +1 -0
- package/dist/shared/utils/config-path.utils.d.ts +38 -0
- package/dist/shared/utils/config-path.utils.d.ts.map +1 -0
- package/dist/shared/utils/config-path.utils.js +85 -0
- package/dist/shared/utils/config-path.utils.js.map +1 -0
- package/dist/shared/utils/error.utils.d.ts +11 -0
- package/dist/shared/utils/error.utils.d.ts.map +1 -0
- package/dist/shared/utils/error.utils.js +15 -0
- package/dist/shared/utils/error.utils.js.map +1 -0
- package/dist/shared/utils/id.utils.d.ts +3 -0
- package/dist/shared/utils/id.utils.d.ts.map +1 -0
- package/dist/shared/utils/id.utils.js +5 -0
- package/dist/shared/utils/id.utils.js.map +1 -0
- package/dist/supabase.js +1 -1
- package/dist/supabase.js.map +1 -1
- package/dist/task/index.d.ts +15 -0
- package/dist/task/index.d.ts.map +1 -0
- package/dist/task/index.js +14 -0
- package/dist/task/index.js.map +1 -0
- package/dist/task/local-task-registry.d.ts +37 -0
- package/dist/task/local-task-registry.d.ts.map +1 -0
- package/dist/task/local-task-registry.js +214 -0
- package/dist/task/local-task-registry.js.map +1 -0
- package/dist/task/task-manager.d.ts +64 -0
- package/dist/task/task-manager.d.ts.map +1 -0
- package/dist/task/task-manager.js +196 -0
- package/dist/task/task-manager.js.map +1 -0
- package/dist/task/task-post.api.d.ts +49 -0
- package/dist/task/task-post.api.d.ts.map +1 -0
- package/dist/task/task-post.api.js +91 -0
- package/dist/task/task-post.api.js.map +1 -0
- package/dist/task/task-repo-context.d.ts +97 -0
- package/dist/task/task-repo-context.d.ts.map +1 -0
- package/dist/task/task-repo-context.js +12 -0
- package/dist/task/task-repo-context.js.map +1 -0
- package/dist/task/task-repo-validation.d.ts +46 -0
- package/dist/task/task-repo-validation.d.ts.map +1 -0
- package/dist/task/task-repo-validation.js +96 -0
- package/dist/task/task-repo-validation.js.map +1 -0
- package/dist/task/task.api.d.ts +198 -0
- package/dist/task/task.api.d.ts.map +1 -1
- package/dist/task/task.api.js +302 -1
- package/dist/task/task.api.js.map +1 -1
- package/dist/task/task.interface.d.ts +11 -2
- package/dist/task/task.interface.d.ts.map +1 -1
- package/dist/task/task.interface.js +10 -0
- package/dist/task/task.interface.js.map +1 -1
- package/dist/trigger/trigger.api.d.ts +140 -0
- package/dist/trigger/trigger.api.d.ts.map +1 -0
- package/dist/trigger/trigger.api.js +125 -0
- package/dist/trigger/trigger.api.js.map +1 -0
- package/dist/trigger/trigger.interface.d.ts +43 -0
- package/dist/trigger/trigger.interface.d.ts.map +1 -0
- package/dist/trigger/trigger.interface.js +11 -0
- package/dist/trigger/trigger.interface.js.map +1 -0
- package/dist/trigger/trigger.utils.d.ts +6 -0
- package/dist/trigger/trigger.utils.d.ts.map +1 -0
- package/dist/trigger/trigger.utils.js +39 -0
- package/dist/trigger/trigger.utils.js.map +1 -0
- package/package.json +30 -8
- package/src/auth/auth.api.ts +3 -1
- package/src/browser.ts +100 -0
- package/src/command/command.api.ts +88 -0
- package/src/command/command.interface.ts +17 -0
- package/src/core.api.ts +305 -26
- package/src/file/file-manager.ts +1 -1
- package/src/index.ts +2 -64
- package/src/link/link.interface.ts +1 -1
- package/src/node/node-log-scope.ts +15 -0
- package/src/node/node-runtime.base.ts +450 -0
- package/src/node/node-task.preset.ts +13 -0
- package/src/node/node.api.ts +95 -117
- package/src/node/node.interface.ts +8 -0
- package/src/node/node.utils.ts +36 -0
- package/src/node/task-node.ts +284 -0
- package/src/node/trigger-node.ts +180 -0
- package/src/node.ts +62 -0
- package/src/parcel/parcel-manager.ts +1 -1
- package/src/repo/repo-context.utils.ts +111 -0
- package/src/repo/repo-manager.ts +245 -0
- package/src/repo/repo.api.ts +50 -0
- package/src/repo/repo.interface.ts +13 -5
- package/src/repo/repo.script-loader.ts +140 -0
- package/src/shared/log/create-api-logger.ts +36 -0
- package/src/shared/log/index.ts +23 -0
- package/src/shared/log/log-manager.ts +335 -0
- package/src/shared/log/log-scope.ts +123 -0
- package/src/shared/utils/config-path.utils.ts +104 -0
- package/src/shared/utils/error.utils.ts +15 -0
- package/src/shared/utils/id.utils.ts +4 -0
- package/src/supabase.ts +1 -1
- package/src/task/index.ts +48 -0
- package/src/task/local-task-registry.ts +282 -0
- package/src/task/task-manager.ts +301 -0
- package/src/task/task-post.api.ts +116 -0
- package/src/task/task-repo-context.ts +105 -0
- package/src/task/task-repo-validation.ts +171 -0
- package/src/task/task.api.ts +369 -0
- package/src/task/task.interface.ts +12 -2
- package/src/trigger/trigger.api.ts +166 -0
- package/src/trigger/trigger.interface.ts +50 -0
- package/src/trigger/trigger.utils.ts +56 -0
- package/dist/task/task-node-manager.d.ts +0 -1
- package/dist/task/task-node-manager.d.ts.map +0 -1
- package/dist/task/task-node-manager.js +0 -2
- package/dist/task/task-node-manager.js.map +0 -1
- package/src/task/task-node-manager.ts +0 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Directory containing a config file — anchor for paths declared **inside** that file.
|
|
7
|
+
*/
|
|
8
|
+
export function getConfigFileDir(configFilePath: string): string {
|
|
9
|
+
return dirname(configFilePath);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolve a path from a config file's directory.
|
|
14
|
+
*
|
|
15
|
+
* - Absolute `pathInConfig` → returned as-is
|
|
16
|
+
* - Relative `pathInConfig` → `resolve(dirname(configFilePath), pathInConfig)`
|
|
17
|
+
*
|
|
18
|
+
* **Not** relative to `process.cwd()`. See `.cursor/skills/config-file-relative-paths/SKILL.md`.
|
|
19
|
+
*/
|
|
20
|
+
export function resolvePathFromConfigFile(configFilePath: string, pathInConfig: string): string {
|
|
21
|
+
const trimmed = pathInConfig.trim();
|
|
22
|
+
if (isAbsolute(trimmed)) {
|
|
23
|
+
return trimmed;
|
|
24
|
+
}
|
|
25
|
+
return resolve(getConfigFileDir(configFilePath), trimmed);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolve a path from an explicit base directory (e.g. task package dir for `entry`).
|
|
30
|
+
* Same rules as {@link resolvePathFromConfigFile}, but base is already known.
|
|
31
|
+
*/
|
|
32
|
+
export function resolvePathFromBaseDir(baseDir: string, path: string): string {
|
|
33
|
+
const trimmed = path.trim();
|
|
34
|
+
if (isAbsolute(trimmed)) {
|
|
35
|
+
return trimmed;
|
|
36
|
+
}
|
|
37
|
+
return resolve(baseDir, trimmed);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Resolve a path from `cwd` — for **discovering** config files, not for fields inside them.
|
|
42
|
+
*/
|
|
43
|
+
export function resolvePathFromCwd(cwd: string, path: string): string {
|
|
44
|
+
const trimmed = path.trim();
|
|
45
|
+
if (isAbsolute(trimmed)) {
|
|
46
|
+
return trimmed;
|
|
47
|
+
}
|
|
48
|
+
return resolve(cwd, trimmed);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function pathExists(path: string): Promise<boolean> {
|
|
52
|
+
try {
|
|
53
|
+
await access(path);
|
|
54
|
+
return true;
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ResolveFirstExistingPathOptions {
|
|
61
|
+
/** When set, only this path is tried (resolved from `cwd` if relative). */
|
|
62
|
+
explicitPath?: string;
|
|
63
|
+
/** Relative to `cwd`, tried in order when `explicitPath` is omitted. */
|
|
64
|
+
candidatePaths: string[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Find the first existing file path: optional explicit path, else scan `candidatePaths` under `cwd`.
|
|
69
|
+
*/
|
|
70
|
+
export async function resolveFirstExistingPath(
|
|
71
|
+
cwd: string,
|
|
72
|
+
options: ResolveFirstExistingPathOptions
|
|
73
|
+
): Promise<string | null> {
|
|
74
|
+
const { explicitPath, candidatePaths } = options;
|
|
75
|
+
|
|
76
|
+
if (explicitPath != null && explicitPath.trim() !== "") {
|
|
77
|
+
const resolved = resolvePathFromCwd(cwd, explicitPath);
|
|
78
|
+
return (await pathExists(resolved)) ? resolved : null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const candidate of candidatePaths) {
|
|
82
|
+
const resolved = join(cwd, candidate);
|
|
83
|
+
if (await pathExists(resolved)) {
|
|
84
|
+
return resolved;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** `file://` href for Node dynamic `import()` of a local module. */
|
|
92
|
+
export function toFileImportHref(filePath: string): string {
|
|
93
|
+
const trimmed = filePath.trim();
|
|
94
|
+
if (trimmed.includes("://")) {
|
|
95
|
+
return trimmed;
|
|
96
|
+
}
|
|
97
|
+
return pathToFileURL(trimmed).href;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Native ESM import of a `.js` / `.mjs` config; supports `export default` or named export object. */
|
|
101
|
+
export async function importJsConfigModule(configPath: string): Promise<unknown> {
|
|
102
|
+
const mod = await import(toFileImportHref(configPath));
|
|
103
|
+
return mod.default ?? mod;
|
|
104
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize unknown catch / rejection values to {@link Error}.
|
|
3
|
+
*
|
|
4
|
+
* `catch` and Promise rejections are typed `unknown` because `throw` can be
|
|
5
|
+
* anything (string, number, plain object). Call sites that need `.message`,
|
|
6
|
+
* re-throw, or `instanceof Error` checks should normalize first.
|
|
7
|
+
*/
|
|
8
|
+
export function toError(value: unknown): Error {
|
|
9
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Error message safe for log `context` fields (JSON-serializable). */
|
|
13
|
+
export function getErrorMessage(value: unknown): string {
|
|
14
|
+
return toError(value).message;
|
|
15
|
+
}
|
package/src/supabase.ts
CHANGED
|
@@ -91,7 +91,7 @@ export class SupabaseInitializer {
|
|
|
91
91
|
const authAnonKey = supabaseNeedAuthAnonKey;
|
|
92
92
|
|
|
93
93
|
if (!mainUrl || !mainAnonKey) {
|
|
94
|
-
throw new Error("Missing required Supabase environment variables:
|
|
94
|
+
throw new Error("Missing required Supabase environment variables: SUPABASE_URL, SUPABASE_ANON_KEY");
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
return {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task domain public API — curated re-exports only.
|
|
3
|
+
* Internal modules (local-task-registry, task-repo-context, task.utils) stay private.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// task.interface
|
|
7
|
+
export {
|
|
8
|
+
CategoryTask,
|
|
9
|
+
ResultCode,
|
|
10
|
+
TaskStatus,
|
|
11
|
+
TaskStatusAction,
|
|
12
|
+
} from "./task.interface";
|
|
13
|
+
export type { Task, TaskDetails, TaskFlow } from "./task.interface";
|
|
14
|
+
|
|
15
|
+
// task.api
|
|
16
|
+
export {
|
|
17
|
+
patchChangeTaskStatus,
|
|
18
|
+
patchChangeTaskStatusSchema,
|
|
19
|
+
patchClaimTask,
|
|
20
|
+
patchClaimTaskSchema,
|
|
21
|
+
patchTaskProgress,
|
|
22
|
+
patchTaskProgressSchema,
|
|
23
|
+
} from "./task.api";
|
|
24
|
+
export type {
|
|
25
|
+
PatchChangeTaskStatusPayload,
|
|
26
|
+
PatchClaimTaskPayload,
|
|
27
|
+
PatchTaskProgressPayload,
|
|
28
|
+
} from "./task.api";
|
|
29
|
+
export { postTask, postTaskSchema } from "./task-post.api";
|
|
30
|
+
export type { PostTaskPayload } from "./task-post.api";
|
|
31
|
+
|
|
32
|
+
// task-manager
|
|
33
|
+
export { TaskManager } from "./task-manager";
|
|
34
|
+
export type {
|
|
35
|
+
PrepareTaskFailureReason,
|
|
36
|
+
PrepareTaskParams,
|
|
37
|
+
PrepareTaskResponse,
|
|
38
|
+
RegisterTasksOptions,
|
|
39
|
+
RegisterTasksResult,
|
|
40
|
+
} from "./task-manager";
|
|
41
|
+
|
|
42
|
+
// task-repo-validation
|
|
43
|
+
export { TaskRepoValidation } from "./task-repo-validation";
|
|
44
|
+
export type {
|
|
45
|
+
TaskRepoValidationFailureReason,
|
|
46
|
+
ValidateTaskRepoAndParamsInput,
|
|
47
|
+
ValidateTaskRepoAndParamsResult,
|
|
48
|
+
} from "./task-repo-validation";
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { readdir, stat } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { RepoManager } from "../repo/repo-manager";
|
|
5
|
+
import {
|
|
6
|
+
importJsConfigModule,
|
|
7
|
+
pathExists,
|
|
8
|
+
resolveFirstExistingPath,
|
|
9
|
+
resolvePathFromBaseDir,
|
|
10
|
+
resolvePathFromConfigFile,
|
|
11
|
+
} from "../shared/utils/config-path.utils";
|
|
12
|
+
import {
|
|
13
|
+
BootstrapLocalTasksResult,
|
|
14
|
+
TASK_LOCAL_PACKAGE_CONFIG_FILENAME,
|
|
15
|
+
TASK_RUNNER_ROOT_CONFIG_LEGACY_RELATIVE_PATH,
|
|
16
|
+
TASK_RUNNER_ROOT_CONFIG_RELATIVE_PATH,
|
|
17
|
+
TaskLocalPackageConfig,
|
|
18
|
+
TaskRunnerRootConfig,
|
|
19
|
+
} from "./task-repo-context";
|
|
20
|
+
|
|
21
|
+
export interface BootstrapLocalTasksOptions {
|
|
22
|
+
/** Absolute or cwd-relative path to root config; if omitted, tries default paths under `cwd` */
|
|
23
|
+
rootConfigPath?: string;
|
|
24
|
+
/** Base directory for resolving root config (default `process.cwd()`) */
|
|
25
|
+
cwd?: string;
|
|
26
|
+
/**
|
|
27
|
+
* When set with an unchanged config fingerprint, skip full scan if this key is already
|
|
28
|
+
* registered in {@link RepoManager}.
|
|
29
|
+
*/
|
|
30
|
+
forTaskTypeKey?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const EMPTY_RESULT: BootstrapLocalTasksResult = {
|
|
34
|
+
status: "not_configured",
|
|
35
|
+
registered: [],
|
|
36
|
+
skipped: [],
|
|
37
|
+
errors: [],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type BootstrapCacheState = {
|
|
41
|
+
fingerprint: string | null;
|
|
42
|
+
lastResult: BootstrapLocalTasksResult | null;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
let bootstrapCache: BootstrapCacheState = { fingerprint: null, lastResult: null };
|
|
46
|
+
let bootstrapInFlight: Promise<BootstrapLocalTasksResult> | null = null;
|
|
47
|
+
|
|
48
|
+
function parseRootConfig(raw: unknown, configPath: string): TaskRunnerRootConfig {
|
|
49
|
+
if (raw == null || typeof raw !== "object") {
|
|
50
|
+
throw new Error(`Root config must export an object: ${configPath}`);
|
|
51
|
+
}
|
|
52
|
+
const record = raw as Record<string, unknown>;
|
|
53
|
+
const taskDir = record.taskDir;
|
|
54
|
+
if (typeof taskDir !== "string" || taskDir.trim() === "") {
|
|
55
|
+
throw new Error(`Root config.taskDir must be a non-empty string: ${configPath}`);
|
|
56
|
+
}
|
|
57
|
+
return { taskDir: taskDir.trim() };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseTaskPackageConfig(raw: unknown, configPath: string): TaskLocalPackageConfig {
|
|
61
|
+
if (raw == null || typeof raw !== "object") {
|
|
62
|
+
throw new Error(`Task config must export an object: ${configPath}`);
|
|
63
|
+
}
|
|
64
|
+
const record = raw as Record<string, unknown>;
|
|
65
|
+
const taskTypeKey = record.taskTypeKey;
|
|
66
|
+
const entry = record.entry;
|
|
67
|
+
|
|
68
|
+
if (typeof taskTypeKey !== "string" || taskTypeKey.trim() === "") {
|
|
69
|
+
throw new Error(`taskTypeKey must be a non-empty string: ${configPath}`);
|
|
70
|
+
}
|
|
71
|
+
if (typeof entry !== "string" || entry.trim() === "") {
|
|
72
|
+
throw new Error(`entry must be a non-empty string: ${configPath}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
taskTypeKey: taskTypeKey.trim(),
|
|
77
|
+
entry: entry.trim(),
|
|
78
|
+
exportName: typeof record.exportName === "string" ? record.exportName : undefined,
|
|
79
|
+
displayName: typeof record.displayName === "string" ? record.displayName : undefined,
|
|
80
|
+
enabled: record.enabled === false ? false : true,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function finalizeStatus(result: Omit<BootstrapLocalTasksResult, "status">): BootstrapLocalTasksResult["status"] {
|
|
85
|
+
if (result.errors.length > 0 && result.registered.length === 0) {
|
|
86
|
+
return "failed";
|
|
87
|
+
}
|
|
88
|
+
if (result.registered.length === 0) {
|
|
89
|
+
return "empty";
|
|
90
|
+
}
|
|
91
|
+
return "loaded";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function computeConfigFingerprint(rootConfigPath: string, tasksRoot: string): Promise<string> {
|
|
95
|
+
const rootStat = await stat(rootConfigPath);
|
|
96
|
+
const tasksRootStat = await stat(tasksRoot);
|
|
97
|
+
const entries = await readdir(tasksRoot, { withFileTypes: true });
|
|
98
|
+
const configMtims: number[] = [];
|
|
99
|
+
|
|
100
|
+
for (const entry of entries) {
|
|
101
|
+
if (!entry.isDirectory()) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const configPath = join(tasksRoot, entry.name, TASK_LOCAL_PACKAGE_CONFIG_FILENAME);
|
|
105
|
+
if (await pathExists(configPath)) {
|
|
106
|
+
const configStat = await stat(configPath);
|
|
107
|
+
configMtims.push(configStat.mtimeMs);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
configMtims.sort((a, b) => a - b);
|
|
112
|
+
return `${rootConfigPath}:${rootStat.mtimeMs}:${tasksRoot}:${tasksRootStat.mtimeMs}:${configMtims.join(",")}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function canUseCachedBootstrap(
|
|
116
|
+
fingerprint: string,
|
|
117
|
+
forTaskTypeKey: string | undefined
|
|
118
|
+
): BootstrapLocalTasksResult | null {
|
|
119
|
+
if (bootstrapCache.fingerprint !== fingerprint || bootstrapCache.lastResult == null) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (forTaskTypeKey != null && forTaskTypeKey !== "") {
|
|
124
|
+
if (!RepoManager.hasLocalRepo(forTaskTypeKey)) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return { ...bootstrapCache.lastResult, cached: true };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function runBootstrapScan(
|
|
133
|
+
options: BootstrapLocalTasksOptions
|
|
134
|
+
): Promise<BootstrapLocalTasksResult> {
|
|
135
|
+
const cwd = options.cwd ?? process.cwd();
|
|
136
|
+
|
|
137
|
+
const rootConfigPath = await resolveFirstExistingPath(cwd, {
|
|
138
|
+
explicitPath: options.rootConfigPath,
|
|
139
|
+
candidatePaths: [
|
|
140
|
+
TASK_RUNNER_ROOT_CONFIG_RELATIVE_PATH,
|
|
141
|
+
TASK_RUNNER_ROOT_CONFIG_LEGACY_RELATIVE_PATH,
|
|
142
|
+
],
|
|
143
|
+
});
|
|
144
|
+
if (rootConfigPath == null) {
|
|
145
|
+
const result: BootstrapLocalTasksResult = {
|
|
146
|
+
...EMPTY_RESULT,
|
|
147
|
+
message: `No root config (tried ${TASK_RUNNER_ROOT_CONFIG_RELATIVE_PATH}, ${TASK_RUNNER_ROOT_CONFIG_LEGACY_RELATIVE_PATH})`,
|
|
148
|
+
};
|
|
149
|
+
bootstrapCache = { fingerprint: null, lastResult: result };
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const result: Omit<BootstrapLocalTasksResult, "status"> = {
|
|
154
|
+
registered: [],
|
|
155
|
+
skipped: [],
|
|
156
|
+
errors: [],
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const rootRaw = await importJsConfigModule(rootConfigPath);
|
|
161
|
+
const { taskDir } = parseRootConfig(rootRaw, rootConfigPath);
|
|
162
|
+
|
|
163
|
+
const tasksRoot = resolvePathFromConfigFile(rootConfigPath, taskDir);
|
|
164
|
+
if (!(await pathExists(tasksRoot))) {
|
|
165
|
+
const failed: BootstrapLocalTasksResult = {
|
|
166
|
+
status: "failed",
|
|
167
|
+
message: `taskDir does not exist: ${tasksRoot}`,
|
|
168
|
+
...result,
|
|
169
|
+
};
|
|
170
|
+
bootstrapCache = { fingerprint: null, lastResult: failed };
|
|
171
|
+
return failed;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const fingerprint = await computeConfigFingerprint(rootConfigPath, tasksRoot);
|
|
175
|
+
const cached = canUseCachedBootstrap(fingerprint, options.forTaskTypeKey);
|
|
176
|
+
if (cached != null) {
|
|
177
|
+
return cached;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const entries = await readdir(tasksRoot, { withFileTypes: true });
|
|
181
|
+
for (const entry of entries) {
|
|
182
|
+
if (!entry.isDirectory()) {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const taskPackageDir = join(tasksRoot, entry.name);
|
|
187
|
+
const taskConfigPath = join(taskPackageDir, TASK_LOCAL_PACKAGE_CONFIG_FILENAME);
|
|
188
|
+
|
|
189
|
+
if (!(await pathExists(taskConfigPath))) {
|
|
190
|
+
result.skipped.push({ taskDir: taskPackageDir, reason: "missing task.config.js" });
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const taskRaw = await importJsConfigModule(taskConfigPath);
|
|
196
|
+
const taskConfig = parseTaskPackageConfig(taskRaw, taskConfigPath);
|
|
197
|
+
|
|
198
|
+
if (taskConfig.enabled === false) {
|
|
199
|
+
result.skipped.push({ taskDir: taskPackageDir, reason: "disabled in task.config.js" });
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const entryPath = resolvePathFromBaseDir(taskPackageDir, taskConfig.entry);
|
|
204
|
+
if (!(await pathExists(entryPath))) {
|
|
205
|
+
throw new Error(`entry module not found: ${entryPath}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
RepoManager.registerLocalModule(taskConfig.taskTypeKey, entryPath, taskConfig.exportName);
|
|
209
|
+
result.registered.push(taskConfig.taskTypeKey);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
result.errors.push({
|
|
212
|
+
taskDir: taskPackageDir,
|
|
213
|
+
error: error instanceof Error ? error.message : String(error),
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const status = finalizeStatus(result);
|
|
219
|
+
const finalResult: BootstrapLocalTasksResult = {
|
|
220
|
+
status,
|
|
221
|
+
message:
|
|
222
|
+
status === "empty"
|
|
223
|
+
? `Root config loaded but no tasks registered under ${tasksRoot}`
|
|
224
|
+
: undefined,
|
|
225
|
+
...result,
|
|
226
|
+
};
|
|
227
|
+
bootstrapCache = { fingerprint, lastResult: finalResult };
|
|
228
|
+
return finalResult;
|
|
229
|
+
} catch (error) {
|
|
230
|
+
const failed: BootstrapLocalTasksResult = {
|
|
231
|
+
status: "failed",
|
|
232
|
+
message: error instanceof Error ? error.message : String(error),
|
|
233
|
+
...result,
|
|
234
|
+
};
|
|
235
|
+
bootstrapCache = { fingerprint: null, lastResult: failed };
|
|
236
|
+
return failed;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Discover local task packages from config files and register them with {@link RepoManager}.
|
|
242
|
+
*
|
|
243
|
+
* **Never throws** for missing root config — returns `{ status: "not_configured" }`.
|
|
244
|
+
*
|
|
245
|
+
* Re-scans are skipped when the config fingerprint is unchanged (root config + taskDir mtimes).
|
|
246
|
+
* Concurrent calls share a single in-flight scan (single-flight).
|
|
247
|
+
*
|
|
248
|
+
* Host app layout (recommended):
|
|
249
|
+
* ```
|
|
250
|
+
* ./config/task.config.js → { taskDir: "../tasks" }
|
|
251
|
+
* ./tasks/my-task/task.config.js
|
|
252
|
+
* ./tasks/my-task/index.mjs
|
|
253
|
+
* ```
|
|
254
|
+
*
|
|
255
|
+
* `taskDir` / `entry` paths are relative to **their config file's directory** (see config-file-relative-paths skill).
|
|
256
|
+
*
|
|
257
|
+
* Legacy fallback: `./task.config.js` at project root (`taskDir: "./tasks"`).
|
|
258
|
+
*/
|
|
259
|
+
export async function bootstrapLocalTasks(
|
|
260
|
+
options: BootstrapLocalTasksOptions = {}
|
|
261
|
+
): Promise<BootstrapLocalTasksResult> {
|
|
262
|
+
if (bootstrapInFlight != null) {
|
|
263
|
+
return bootstrapInFlight;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
bootstrapInFlight = runBootstrapScan(options).finally(() => {
|
|
267
|
+
bootstrapInFlight = null;
|
|
268
|
+
});
|
|
269
|
+
return bootstrapInFlight;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Clear fingerprint cache (tests / hot-reload tooling). */
|
|
273
|
+
export function clearBootstrapLocalTasksCache(): void {
|
|
274
|
+
bootstrapCache = { fingerprint: null, lastResult: null };
|
|
275
|
+
bootstrapInFlight = null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function getRegisteredLocalTaskTypeKeys(): string[] {
|
|
279
|
+
return RepoManager.getRegisteredLocalTaskTypeKeys();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export { TASK_RUNNER_ROOT_CONFIG_RELATIVE_PATH };
|