@executor-js/plugin-file-secrets 0.0.2 → 0.2.1
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/dist/chunk-P2XE3FGE.js +107 -0
- package/dist/chunk-P2XE3FGE.js.map +1 -0
- package/dist/core.js +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-6YB3FO4B.js +0 -118
- package/dist/chunk-6YB3FO4B.js.map +0 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { Effect, Schema } from "effect";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import {
|
|
6
|
+
definePlugin,
|
|
7
|
+
StorageError
|
|
8
|
+
} from "@executor-js/sdk/core";
|
|
9
|
+
var APP_NAME = "executor";
|
|
10
|
+
var xdgDataHome = () => {
|
|
11
|
+
if (process.env.XDG_DATA_HOME?.trim()) return process.env.XDG_DATA_HOME.trim();
|
|
12
|
+
if (process.platform === "win32") {
|
|
13
|
+
return process.env.LOCALAPPDATA || process.env.APPDATA || path.join(process.env.USERPROFILE || "~", "AppData", "Local");
|
|
14
|
+
}
|
|
15
|
+
return path.join(process.env.HOME || "~", ".local", "share");
|
|
16
|
+
};
|
|
17
|
+
var authDir = (overrideDir) => overrideDir ?? path.join(xdgDataHome(), APP_NAME);
|
|
18
|
+
var authFilePath = (overrideDir) => path.join(authDir(overrideDir), "auth.json");
|
|
19
|
+
var ScopedAuthFile = Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.String));
|
|
20
|
+
var decodeScopedAuthFile = Schema.decodeUnknownEffect(Schema.fromJsonString(ScopedAuthFile));
|
|
21
|
+
var isFileNotFoundCause = (cause) => typeof cause === "object" && cause !== null && "code" in cause && cause.code === "ENOENT";
|
|
22
|
+
var toStorageError = (message) => (cause) => new StorageError({ message, cause });
|
|
23
|
+
var readFullFile = (filePath) => {
|
|
24
|
+
if (!fs.existsSync(filePath)) return Effect.succeed({});
|
|
25
|
+
return Effect.try({
|
|
26
|
+
try: () => fs.readFileSync(filePath, "utf-8"),
|
|
27
|
+
catch: toStorageError("Failed to read auth file")
|
|
28
|
+
}).pipe(
|
|
29
|
+
Effect.catchIf(
|
|
30
|
+
(error) => isFileNotFoundCause(error.cause),
|
|
31
|
+
() => Effect.succeed("")
|
|
32
|
+
),
|
|
33
|
+
Effect.flatMap(
|
|
34
|
+
(raw) => raw === "" ? Effect.succeed({}) : decodeScopedAuthFile(raw).pipe(
|
|
35
|
+
Effect.mapError(toStorageError("Failed to parse auth file"))
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
var readScopeSecrets = (filePath, scopeId) => readFullFile(filePath).pipe(Effect.map((file) => file[scopeId] ?? {}));
|
|
41
|
+
var writeScopeSecrets = (filePath, scopeId, secrets) => {
|
|
42
|
+
const dir = path.dirname(filePath);
|
|
43
|
+
const tmp = `${filePath}.tmp`;
|
|
44
|
+
return Effect.gen(function* () {
|
|
45
|
+
if (!fs.existsSync(dir)) {
|
|
46
|
+
yield* Effect.try({
|
|
47
|
+
try: () => fs.mkdirSync(dir, { recursive: true, mode: 448 }),
|
|
48
|
+
catch: toStorageError("Failed to create auth directory")
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const full = yield* readFullFile(filePath);
|
|
52
|
+
if (Object.keys(secrets).length === 0) {
|
|
53
|
+
delete full[scopeId];
|
|
54
|
+
} else {
|
|
55
|
+
full[scopeId] = secrets;
|
|
56
|
+
}
|
|
57
|
+
yield* Effect.try({
|
|
58
|
+
try: () => fs.writeFileSync(tmp, JSON.stringify(full, null, 2), { mode: 384 }),
|
|
59
|
+
catch: toStorageError("Failed to write temporary auth file")
|
|
60
|
+
});
|
|
61
|
+
yield* Effect.try({
|
|
62
|
+
try: () => fs.renameSync(tmp, filePath),
|
|
63
|
+
catch: toStorageError("Failed to replace auth file")
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
var makeFileSecretsExtension = (options) => ({
|
|
68
|
+
filePath: resolveFilePath(options)
|
|
69
|
+
});
|
|
70
|
+
var makeScopedProvider = (filePath, listScope) => ({
|
|
71
|
+
key: "file",
|
|
72
|
+
writable: true,
|
|
73
|
+
get: (secretId, scope) => readScopeSecrets(filePath, scope).pipe(Effect.map((data) => data[secretId] ?? null)),
|
|
74
|
+
has: (secretId, scope) => readScopeSecrets(filePath, scope).pipe(Effect.map((data) => secretId in data)),
|
|
75
|
+
set: (secretId, value, scope) => Effect.gen(function* () {
|
|
76
|
+
const data = yield* readScopeSecrets(filePath, scope);
|
|
77
|
+
data[secretId] = value;
|
|
78
|
+
yield* writeScopeSecrets(filePath, scope, data);
|
|
79
|
+
}),
|
|
80
|
+
delete: (secretId, scope) => Effect.gen(function* () {
|
|
81
|
+
const data = yield* readScopeSecrets(filePath, scope);
|
|
82
|
+
const had = secretId in data;
|
|
83
|
+
delete data[secretId];
|
|
84
|
+
if (had) yield* writeScopeSecrets(filePath, scope, data);
|
|
85
|
+
return had;
|
|
86
|
+
}),
|
|
87
|
+
list: () => readScopeSecrets(filePath, listScope).pipe(
|
|
88
|
+
Effect.map((data) => Object.keys(data).map((k) => ({ id: k, name: k })))
|
|
89
|
+
)
|
|
90
|
+
});
|
|
91
|
+
var resolveFilePath = (config) => authFilePath(config?.directory);
|
|
92
|
+
var fileSecretsPlugin = definePlugin((options) => ({
|
|
93
|
+
id: "fileSecrets",
|
|
94
|
+
storage: () => ({}),
|
|
95
|
+
extension: () => makeFileSecretsExtension(options),
|
|
96
|
+
secretProviders: (ctx) => [
|
|
97
|
+
// list() falls back to the innermost scope for display; per-call
|
|
98
|
+
// get/set/delete honor the scope arg threaded from the secrets facade.
|
|
99
|
+
makeScopedProvider(resolveFilePath(options), ctx.scopes[0].id)
|
|
100
|
+
]
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
export {
|
|
104
|
+
xdgDataHome,
|
|
105
|
+
fileSecretsPlugin
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=chunk-P2XE3FGE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { Effect, Schema } from \"effect\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nimport {\n definePlugin,\n StorageError,\n type PluginCtx,\n type SecretProvider,\n} from \"@executor-js/sdk/core\";\n\n// ---------------------------------------------------------------------------\n// XDG data dir resolution\n// ---------------------------------------------------------------------------\n\nconst APP_NAME = \"executor\";\n\nexport const xdgDataHome = (): string => {\n if (process.env.XDG_DATA_HOME?.trim()) return process.env.XDG_DATA_HOME.trim();\n if (process.platform === \"win32\") {\n return (\n process.env.LOCALAPPDATA ||\n process.env.APPDATA ||\n path.join(process.env.USERPROFILE || \"~\", \"AppData\", \"Local\")\n );\n }\n return path.join(process.env.HOME || \"~\", \".local\", \"share\");\n};\n\nconst authDir = (overrideDir?: string): string => overrideDir ?? path.join(xdgDataHome(), APP_NAME);\n\nconst authFilePath = (overrideDir?: string): string => path.join(authDir(overrideDir), \"auth.json\");\n\n// ---------------------------------------------------------------------------\n// Schema for the auth file\n//\n// Top-level keys are scope IDs, values are { secretId: secretValue } maps.\n// { \"web-a1b2c3d4\": { \"github-token\": \"ghp_xxx\" } }\n// ---------------------------------------------------------------------------\n\nconst ScopedAuthFile = Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.String));\nconst decodeScopedAuthFile = Schema.decodeUnknownEffect(Schema.fromJsonString(ScopedAuthFile));\n\n// ---------------------------------------------------------------------------\n// File I/O with restricted permissions\n//\n// These helpers keep real I/O and decode failures in the Effect error\n// channel as `StorageError`. Missing files are still treated as an empty\n// auth file, but malformed JSON, schema decode failures, and permission\n// errors no longer collapse into \"empty file\".\n// ---------------------------------------------------------------------------\n\nconst isFileNotFoundCause = (cause: unknown): cause is NodeJS.ErrnoException =>\n typeof cause === \"object\" && cause !== null && \"code\" in cause && cause.code === \"ENOENT\";\n\nconst toStorageError =\n (message: string) =>\n (cause: unknown): StorageError =>\n new StorageError({ message, cause });\n\nconst readFullFile = (\n filePath: string,\n): Effect.Effect<Record<string, Record<string, string>>, StorageError> => {\n if (!fs.existsSync(filePath)) return Effect.succeed({});\n return Effect.try({\n try: () => fs.readFileSync(filePath, \"utf-8\"),\n catch: toStorageError(\"Failed to read auth file\"),\n }).pipe(\n Effect.catchIf(\n (error) => isFileNotFoundCause(error.cause),\n () => Effect.succeed(\"\"),\n ),\n Effect.flatMap((raw) =>\n raw === \"\"\n ? Effect.succeed({})\n : decodeScopedAuthFile(raw).pipe(\n Effect.mapError(toStorageError(\"Failed to parse auth file\")),\n ),\n ),\n );\n};\n\nconst readScopeSecrets = (\n filePath: string,\n scopeId: string,\n): Effect.Effect<Record<string, string>, StorageError> =>\n readFullFile(filePath).pipe(Effect.map((file) => file[scopeId] ?? {}));\n\nconst writeScopeSecrets = (\n filePath: string,\n scopeId: string,\n secrets: Record<string, string>,\n): Effect.Effect<void, StorageError> => {\n const dir = path.dirname(filePath);\n const tmp = `${filePath}.tmp`;\n return Effect.gen(function* () {\n if (!fs.existsSync(dir)) {\n yield* Effect.try({\n try: () => fs.mkdirSync(dir, { recursive: true, mode: 0o700 }),\n catch: toStorageError(\"Failed to create auth directory\"),\n });\n }\n const full = yield* readFullFile(filePath);\n if (Object.keys(secrets).length === 0) {\n delete full[scopeId];\n } else {\n full[scopeId] = secrets;\n }\n yield* Effect.try({\n try: () => fs.writeFileSync(tmp, JSON.stringify(full, null, 2), { mode: 0o600 }),\n catch: toStorageError(\"Failed to write temporary auth file\"),\n });\n yield* Effect.try({\n try: () => fs.renameSync(tmp, filePath),\n catch: toStorageError(\"Failed to replace auth file\"),\n });\n });\n};\n\n// ---------------------------------------------------------------------------\n// Plugin config\n// ---------------------------------------------------------------------------\n\nexport interface FileSecretsPluginConfig {\n /** Override the directory for auth.json (default: XDG data dir) */\n readonly directory?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin extension — public API on executor.fileSecrets\n// ---------------------------------------------------------------------------\n\nconst makeFileSecretsExtension = (options: FileSecretsPluginConfig | undefined) => ({\n filePath: resolveFilePath(options),\n});\n\nexport type FileSecretsExtension = ReturnType<typeof makeFileSecretsExtension>;\n\n// ---------------------------------------------------------------------------\n// Provider factory (internal)\n// ---------------------------------------------------------------------------\n\n// Scope arg is honored at every call: the auth.json is partitioned by\n// scope id, so read/write/delete route to `file[scope][secretId]`. The\n// provider is a singleton per executor; scope routing happens via the\n// arg passed from the executor's secrets facade.\n//\n// `list` enumerates the innermost scope the provider was configured\n// for — the executor's fallback/list path passes scope separately but\n// the SecretProvider.list signature is scope-agnostic. That's fine for\n// the current use: `list` feeds `secrets.list()` which already walks\n// the stack at the caller layer. Innermost-first is the display default.\nconst makeScopedProvider = (filePath: string, listScope: string): SecretProvider => ({\n key: \"file\",\n writable: true,\n\n get: (secretId, scope) =>\n readScopeSecrets(filePath, scope).pipe(Effect.map((data) => data[secretId] ?? null)),\n\n has: (secretId, scope) =>\n readScopeSecrets(filePath, scope).pipe(Effect.map((data) => secretId in data)),\n\n set: (secretId, value, scope) =>\n Effect.gen(function* () {\n const data = yield* readScopeSecrets(filePath, scope);\n data[secretId] = value;\n yield* writeScopeSecrets(filePath, scope, data);\n }),\n\n delete: (secretId, scope) =>\n Effect.gen(function* () {\n const data = yield* readScopeSecrets(filePath, scope);\n const had = secretId in data;\n delete data[secretId];\n if (had) yield* writeScopeSecrets(filePath, scope, data);\n return had;\n }),\n\n list: () =>\n readScopeSecrets(filePath, listScope).pipe(\n Effect.map((data) => Object.keys(data).map((k) => ({ id: k, name: k }))),\n ),\n});\n\n// ---------------------------------------------------------------------------\n// Plugin definition\n//\n// Compute the scoped file path identically in `extension` (for `filePath`)\n// and `secretProviders` (for the provider's read/write). Both receive ctx\n// and both are called once per createExecutor.\n// ---------------------------------------------------------------------------\n\nconst resolveFilePath = (config: FileSecretsPluginConfig | undefined): string =>\n authFilePath(config?.directory);\n\nexport const fileSecretsPlugin = definePlugin((options?: FileSecretsPluginConfig) => ({\n id: \"fileSecrets\" as const,\n storage: () => ({}),\n\n extension: () => makeFileSecretsExtension(options),\n\n secretProviders: (ctx: PluginCtx<unknown>) => [\n // list() falls back to the innermost scope for display; per-call\n // get/set/delete honor the scope arg threaded from the secrets facade.\n makeScopedProvider(resolveFilePath(options), ctx.scopes[0]!.id),\n ],\n}));\n"],"mappings":"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","names":[]}
|
package/dist/core.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,11 @@ export interface FileSecretsPluginConfig {
|
|
|
3
3
|
/** Override the directory for auth.json (default: XDG data dir) */
|
|
4
4
|
readonly directory?: string;
|
|
5
5
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export declare const fileSecretsPlugin: import("@executor-js/sdk/core").ConfiguredPlugin<"fileSecrets",
|
|
6
|
+
declare const makeFileSecretsExtension: (options: FileSecretsPluginConfig | undefined) => {
|
|
7
|
+
filePath: string;
|
|
8
|
+
};
|
|
9
|
+
export type FileSecretsExtension = ReturnType<typeof makeFileSecretsExtension>;
|
|
10
|
+
export declare const fileSecretsPlugin: import("@executor-js/sdk/core").ConfiguredPlugin<"fileSecrets", {
|
|
11
|
+
filePath: string;
|
|
12
|
+
}, {}, FileSecretsPluginConfig, undefined, undefined, import("effect/Layer").Layer<unknown, never, never>, import("effect/unstable/httpapi/HttpApiGroup").Any>;
|
|
13
|
+
export {};
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@executor-js/plugin-file-secrets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"homepage": "https://github.com/RhysSullivan/executor/tree/main/packages/plugins/file-secrets",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/RhysSullivan/executor/issues"
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"typecheck:slow": "bunx tsc --noEmit -p tsconfig.json"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@executor-js/sdk": "0.
|
|
43
|
+
"@executor-js/sdk": "0.2.1",
|
|
44
44
|
"effect": "4.0.0-beta.59"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
package/dist/chunk-6YB3FO4B.js
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
|
-
import { Effect, Schema } from "effect";
|
|
3
|
-
import * as fs from "fs";
|
|
4
|
-
import * as path from "path";
|
|
5
|
-
import {
|
|
6
|
-
definePlugin,
|
|
7
|
-
StorageError
|
|
8
|
-
} from "@executor-js/sdk/core";
|
|
9
|
-
var APP_NAME = "executor";
|
|
10
|
-
var xdgDataHome = () => {
|
|
11
|
-
if (process.env.XDG_DATA_HOME?.trim()) return process.env.XDG_DATA_HOME.trim();
|
|
12
|
-
if (process.platform === "win32") {
|
|
13
|
-
return process.env.LOCALAPPDATA || process.env.APPDATA || path.join(process.env.USERPROFILE || "~", "AppData", "Local");
|
|
14
|
-
}
|
|
15
|
-
return path.join(process.env.HOME || "~", ".local", "share");
|
|
16
|
-
};
|
|
17
|
-
var authDir = (overrideDir) => overrideDir ?? path.join(xdgDataHome(), APP_NAME);
|
|
18
|
-
var authFilePath = (overrideDir) => path.join(authDir(overrideDir), "auth.json");
|
|
19
|
-
var ScopedAuthFile = Schema.Record(
|
|
20
|
-
Schema.String,
|
|
21
|
-
Schema.Record(Schema.String, Schema.String)
|
|
22
|
-
);
|
|
23
|
-
var decodeScopedAuthFile = Schema.decodeUnknownSync(ScopedAuthFile);
|
|
24
|
-
var readFullFile = (filePath) => {
|
|
25
|
-
if (!fs.existsSync(filePath)) return {};
|
|
26
|
-
let raw;
|
|
27
|
-
try {
|
|
28
|
-
raw = fs.readFileSync(filePath, "utf-8");
|
|
29
|
-
} catch (cause) {
|
|
30
|
-
if (cause.code === "ENOENT") return {};
|
|
31
|
-
throw cause;
|
|
32
|
-
}
|
|
33
|
-
return decodeScopedAuthFile(JSON.parse(raw));
|
|
34
|
-
};
|
|
35
|
-
var readScopeSecrets = (filePath, scopeId) => readFullFile(filePath)[scopeId] ?? {};
|
|
36
|
-
var writeScopeSecrets = (filePath, scopeId, secrets) => {
|
|
37
|
-
const dir = path.dirname(filePath);
|
|
38
|
-
if (!fs.existsSync(dir)) {
|
|
39
|
-
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
40
|
-
}
|
|
41
|
-
const full = readFullFile(filePath);
|
|
42
|
-
if (Object.keys(secrets).length === 0) {
|
|
43
|
-
delete full[scopeId];
|
|
44
|
-
} else {
|
|
45
|
-
full[scopeId] = secrets;
|
|
46
|
-
}
|
|
47
|
-
const tmp = `${filePath}.tmp`;
|
|
48
|
-
fs.writeFileSync(tmp, JSON.stringify(full, null, 2), { mode: 384 });
|
|
49
|
-
fs.renameSync(tmp, filePath);
|
|
50
|
-
};
|
|
51
|
-
var toStorageError = (cause) => new StorageError({
|
|
52
|
-
message: cause instanceof Error ? cause.message : String(cause),
|
|
53
|
-
cause
|
|
54
|
-
});
|
|
55
|
-
var makeScopedProvider = (filePath, listScope) => ({
|
|
56
|
-
key: "file",
|
|
57
|
-
writable: true,
|
|
58
|
-
get: (secretId, scope) => Effect.try({
|
|
59
|
-
try: () => {
|
|
60
|
-
const data = readScopeSecrets(filePath, scope);
|
|
61
|
-
return data[secretId] ?? null;
|
|
62
|
-
},
|
|
63
|
-
catch: toStorageError
|
|
64
|
-
}),
|
|
65
|
-
has: (secretId, scope) => Effect.try({
|
|
66
|
-
try: () => {
|
|
67
|
-
const data = readScopeSecrets(filePath, scope);
|
|
68
|
-
return secretId in data;
|
|
69
|
-
},
|
|
70
|
-
catch: toStorageError
|
|
71
|
-
}),
|
|
72
|
-
set: (secretId, value, scope) => Effect.try({
|
|
73
|
-
try: () => {
|
|
74
|
-
const data = readScopeSecrets(filePath, scope);
|
|
75
|
-
data[secretId] = value;
|
|
76
|
-
writeScopeSecrets(filePath, scope, data);
|
|
77
|
-
},
|
|
78
|
-
catch: toStorageError
|
|
79
|
-
}),
|
|
80
|
-
delete: (secretId, scope) => Effect.try({
|
|
81
|
-
try: () => {
|
|
82
|
-
const data = readScopeSecrets(filePath, scope);
|
|
83
|
-
const had = secretId in data;
|
|
84
|
-
delete data[secretId];
|
|
85
|
-
if (had) writeScopeSecrets(filePath, scope, data);
|
|
86
|
-
return had;
|
|
87
|
-
},
|
|
88
|
-
catch: toStorageError
|
|
89
|
-
}),
|
|
90
|
-
list: () => Effect.try({
|
|
91
|
-
try: () => {
|
|
92
|
-
const data = readScopeSecrets(filePath, listScope);
|
|
93
|
-
return Object.keys(data).map((k) => ({ id: k, name: k }));
|
|
94
|
-
},
|
|
95
|
-
catch: toStorageError
|
|
96
|
-
})
|
|
97
|
-
});
|
|
98
|
-
var resolveFilePath = (config) => authFilePath(config?.directory);
|
|
99
|
-
var fileSecretsPlugin = definePlugin(
|
|
100
|
-
(options) => ({
|
|
101
|
-
id: "fileSecrets",
|
|
102
|
-
storage: () => ({}),
|
|
103
|
-
extension: (_ctx) => ({
|
|
104
|
-
filePath: resolveFilePath(options)
|
|
105
|
-
}),
|
|
106
|
-
secretProviders: (ctx) => [
|
|
107
|
-
// list() falls back to the innermost scope for display; per-call
|
|
108
|
-
// get/set/delete honor the scope arg threaded from the secrets facade.
|
|
109
|
-
makeScopedProvider(resolveFilePath(options), ctx.scopes[0].id)
|
|
110
|
-
]
|
|
111
|
-
})
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
export {
|
|
115
|
-
xdgDataHome,
|
|
116
|
-
fileSecretsPlugin
|
|
117
|
-
};
|
|
118
|
-
//# sourceMappingURL=chunk-6YB3FO4B.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { Effect, Schema } from \"effect\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nimport {\n definePlugin,\n StorageError,\n type PluginCtx,\n type SecretProvider,\n} from \"@executor-js/sdk/core\";\n\n// ---------------------------------------------------------------------------\n// XDG data dir resolution\n// ---------------------------------------------------------------------------\n\nconst APP_NAME = \"executor\";\n\nexport const xdgDataHome = (): string => {\n if (process.env.XDG_DATA_HOME?.trim()) return process.env.XDG_DATA_HOME.trim();\n if (process.platform === \"win32\") {\n return (\n process.env.LOCALAPPDATA ||\n process.env.APPDATA ||\n path.join(process.env.USERPROFILE || \"~\", \"AppData\", \"Local\")\n );\n }\n return path.join(process.env.HOME || \"~\", \".local\", \"share\");\n};\n\nconst authDir = (overrideDir?: string): string =>\n overrideDir ?? path.join(xdgDataHome(), APP_NAME);\n\nconst authFilePath = (overrideDir?: string): string =>\n path.join(authDir(overrideDir), \"auth.json\");\n\n// ---------------------------------------------------------------------------\n// Schema for the auth file\n//\n// Top-level keys are scope IDs, values are { secretId: secretValue } maps.\n// { \"web-a1b2c3d4\": { \"github-token\": \"ghp_xxx\" } }\n// ---------------------------------------------------------------------------\n\nconst ScopedAuthFile = Schema.Record(\n Schema.String,\n Schema.Record(Schema.String, Schema.String),\n);\nconst decodeScopedAuthFile = Schema.decodeUnknownSync(ScopedAuthFile);\n\n// ---------------------------------------------------------------------------\n// File I/O with restricted permissions\n//\n// These helpers throw on real I/O or decode failures — the provider wraps\n// every call in `Effect.try` so those throws surface as typed\n// `StorageError` on the Effect error channel. Previously `readFullFile`\n// used a blanket `try { ... } catch { return {}; }` which masked JSON\n// parse errors, schema decode failures, and permission errors as\n// \"empty file\", making misconfigured installs silently return null from\n// every `get`.\n// ---------------------------------------------------------------------------\n\nconst readFullFile = (filePath: string): Record<string, Record<string, string>> => {\n if (!fs.existsSync(filePath)) return {};\n let raw: string;\n try {\n raw = fs.readFileSync(filePath, \"utf-8\");\n } catch (cause) {\n // Treat \"file disappeared between existsSync and readFileSync\" as\n // absence — anything else (EACCES, EISDIR, …) propagates.\n if ((cause as NodeJS.ErrnoException).code === \"ENOENT\") return {};\n throw cause;\n }\n return decodeScopedAuthFile(JSON.parse(raw));\n};\n\nconst readScopeSecrets = (filePath: string, scopeId: string): Record<string, string> =>\n readFullFile(filePath)[scopeId] ?? {};\n\nconst writeScopeSecrets = (\n filePath: string,\n scopeId: string,\n secrets: Record<string, string>,\n): void => {\n const dir = path.dirname(filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n const full = readFullFile(filePath);\n if (Object.keys(secrets).length === 0) {\n delete full[scopeId];\n } else {\n full[scopeId] = secrets;\n }\n const tmp = `${filePath}.tmp`;\n fs.writeFileSync(tmp, JSON.stringify(full, null, 2), { mode: 0o600 });\n fs.renameSync(tmp, filePath);\n};\n\n// ---------------------------------------------------------------------------\n// Plugin config\n// ---------------------------------------------------------------------------\n\nexport interface FileSecretsPluginConfig {\n /** Override the directory for auth.json (default: XDG data dir) */\n readonly directory?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin extension — public API on executor.fileSecrets\n// ---------------------------------------------------------------------------\n\nexport interface FileSecretsExtension {\n /** Path to the auth file */\n readonly filePath: string;\n}\n\n// ---------------------------------------------------------------------------\n// Provider factory (internal)\n// ---------------------------------------------------------------------------\n\nconst toStorageError = (cause: unknown) =>\n new StorageError({\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n });\n\n// Scope arg is honored at every call: the auth.json is partitioned by\n// scope id, so read/write/delete route to `file[scope][secretId]`. The\n// provider is a singleton per executor; scope routing happens via the\n// arg passed from the executor's secrets facade.\n//\n// `list` enumerates the innermost scope the provider was configured\n// for — the executor's fallback/list path passes scope separately but\n// the SecretProvider.list signature is scope-agnostic. That's fine for\n// the current use: `list` feeds `secrets.list()` which already walks\n// the stack at the caller layer. Innermost-first is the display default.\nconst makeScopedProvider = (\n filePath: string,\n listScope: string,\n): SecretProvider => ({\n key: \"file\",\n writable: true,\n\n get: (secretId, scope) =>\n Effect.try({\n try: () => {\n const data = readScopeSecrets(filePath, scope);\n return data[secretId] ?? null;\n },\n catch: toStorageError,\n }),\n\n has: (secretId, scope) =>\n Effect.try({\n try: () => {\n const data = readScopeSecrets(filePath, scope);\n return secretId in data;\n },\n catch: toStorageError,\n }),\n\n set: (secretId, value, scope) =>\n Effect.try({\n try: () => {\n const data = readScopeSecrets(filePath, scope);\n data[secretId] = value;\n writeScopeSecrets(filePath, scope, data);\n },\n catch: toStorageError,\n }),\n\n delete: (secretId, scope) =>\n Effect.try({\n try: () => {\n const data = readScopeSecrets(filePath, scope);\n const had = secretId in data;\n delete data[secretId];\n if (had) writeScopeSecrets(filePath, scope, data);\n return had;\n },\n catch: toStorageError,\n }),\n\n list: () =>\n Effect.try({\n try: () => {\n const data = readScopeSecrets(filePath, listScope);\n return Object.keys(data).map((k) => ({ id: k, name: k }));\n },\n catch: toStorageError,\n }),\n});\n\n// ---------------------------------------------------------------------------\n// Plugin definition\n//\n// Compute the scoped file path identically in `extension` (for `filePath`)\n// and `secretProviders` (for the provider's read/write). Both receive ctx\n// and both are called once per createExecutor.\n// ---------------------------------------------------------------------------\n\nconst resolveFilePath = (config: FileSecretsPluginConfig | undefined): string =>\n authFilePath(config?.directory);\n\nexport const fileSecretsPlugin = definePlugin(\n (options?: FileSecretsPluginConfig) => ({\n id: \"fileSecrets\" as const,\n storage: () => ({}),\n\n extension: (_ctx): FileSecretsExtension => ({\n filePath: resolveFilePath(options),\n }),\n\n secretProviders: (ctx: PluginCtx<unknown>) => [\n // list() falls back to the innermost scope for display; per-call\n // get/set/delete honor the scope arg threaded from the secrets facade.\n makeScopedProvider(resolveFilePath(options), ctx.scopes[0]!.id as string),\n ],\n }),\n);\n"],"mappings":"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","names":[]}
|