@executor-js/plugin-file-secrets 1.4.33 → 1.5.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 → chunk-LOLKJR7S.js} +29 -35
- package/dist/chunk-LOLKJR7S.js.map +1 -0
- package/dist/core.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/promise.d.ts +2 -2
- package/package.json +6 -3
- package/dist/chunk-P2XE3FGE.js.map +0 -1
|
@@ -4,8 +4,10 @@ import * as fs from "fs";
|
|
|
4
4
|
import * as path from "path";
|
|
5
5
|
import {
|
|
6
6
|
definePlugin,
|
|
7
|
+
ProviderItemId,
|
|
8
|
+
ProviderKey,
|
|
7
9
|
StorageError
|
|
8
|
-
} from "@executor-js/sdk
|
|
10
|
+
} from "@executor-js/sdk";
|
|
9
11
|
var APP_NAME = "executor";
|
|
10
12
|
var xdgDataHome = () => {
|
|
11
13
|
if (process.env.XDG_DATA_HOME?.trim()) return process.env.XDG_DATA_HOME.trim();
|
|
@@ -16,11 +18,11 @@ var xdgDataHome = () => {
|
|
|
16
18
|
};
|
|
17
19
|
var authDir = (overrideDir) => overrideDir ?? path.join(xdgDataHome(), APP_NAME);
|
|
18
20
|
var authFilePath = (overrideDir) => path.join(authDir(overrideDir), "auth.json");
|
|
19
|
-
var
|
|
20
|
-
var
|
|
21
|
+
var FlatAuthFile = Schema.Record(Schema.String, Schema.String);
|
|
22
|
+
var decodeFlatAuthFile = Schema.decodeUnknownEffect(Schema.fromJsonString(FlatAuthFile));
|
|
21
23
|
var isFileNotFoundCause = (cause) => typeof cause === "object" && cause !== null && "code" in cause && cause.code === "ENOENT";
|
|
22
24
|
var toStorageError = (message) => (cause) => new StorageError({ message, cause });
|
|
23
|
-
var
|
|
25
|
+
var readAll = (filePath) => {
|
|
24
26
|
if (!fs.existsSync(filePath)) return Effect.succeed({});
|
|
25
27
|
return Effect.try({
|
|
26
28
|
try: () => fs.readFileSync(filePath, "utf-8"),
|
|
@@ -31,14 +33,13 @@ var readFullFile = (filePath) => {
|
|
|
31
33
|
() => Effect.succeed("")
|
|
32
34
|
),
|
|
33
35
|
Effect.flatMap(
|
|
34
|
-
(raw) => raw === "" ? Effect.succeed({}) :
|
|
36
|
+
(raw) => raw === "" ? Effect.succeed({}) : decodeFlatAuthFile(raw).pipe(
|
|
35
37
|
Effect.mapError(toStorageError("Failed to parse auth file"))
|
|
36
38
|
)
|
|
37
39
|
)
|
|
38
40
|
);
|
|
39
41
|
};
|
|
40
|
-
var
|
|
41
|
-
var writeScopeSecrets = (filePath, scopeId, secrets) => {
|
|
42
|
+
var writeAll = (filePath, secrets) => {
|
|
42
43
|
const dir = path.dirname(filePath);
|
|
43
44
|
const tmp = `${filePath}.tmp`;
|
|
44
45
|
return Effect.gen(function* () {
|
|
@@ -48,14 +49,8 @@ var writeScopeSecrets = (filePath, scopeId, secrets) => {
|
|
|
48
49
|
catch: toStorageError("Failed to create auth directory")
|
|
49
50
|
});
|
|
50
51
|
}
|
|
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
52
|
yield* Effect.try({
|
|
58
|
-
try: () => fs.writeFileSync(tmp, JSON.stringify(
|
|
53
|
+
try: () => fs.writeFileSync(tmp, JSON.stringify(secrets, null, 2), { mode: 384 }),
|
|
59
54
|
catch: toStorageError("Failed to write temporary auth file")
|
|
60
55
|
});
|
|
61
56
|
yield* Effect.try({
|
|
@@ -67,25 +62,26 @@ var writeScopeSecrets = (filePath, scopeId, secrets) => {
|
|
|
67
62
|
var makeFileSecretsExtension = (options) => ({
|
|
68
63
|
filePath: resolveFilePath(options)
|
|
69
64
|
});
|
|
70
|
-
var
|
|
71
|
-
|
|
65
|
+
var FILE_PROVIDER_KEY = ProviderKey.make("file");
|
|
66
|
+
var makeFileProvider = (filePath) => ({
|
|
67
|
+
key: FILE_PROVIDER_KEY,
|
|
72
68
|
writable: true,
|
|
73
|
-
get: (
|
|
74
|
-
has: (
|
|
75
|
-
set: (
|
|
76
|
-
const data = yield*
|
|
77
|
-
data[
|
|
78
|
-
yield*
|
|
69
|
+
get: (id) => readAll(filePath).pipe(Effect.map((data) => data[id] ?? null)),
|
|
70
|
+
has: (id) => readAll(filePath).pipe(Effect.map((data) => id in data)),
|
|
71
|
+
set: (id, value) => Effect.gen(function* () {
|
|
72
|
+
const data = yield* readAll(filePath);
|
|
73
|
+
data[id] = value;
|
|
74
|
+
yield* writeAll(filePath, data);
|
|
79
75
|
}),
|
|
80
|
-
delete: (
|
|
81
|
-
const data = yield*
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
76
|
+
delete: (id) => Effect.gen(function* () {
|
|
77
|
+
const data = yield* readAll(filePath);
|
|
78
|
+
if (id in data) {
|
|
79
|
+
delete data[id];
|
|
80
|
+
yield* writeAll(filePath, data);
|
|
81
|
+
}
|
|
86
82
|
}),
|
|
87
|
-
list: () =>
|
|
88
|
-
Effect.map((data) => Object.keys(data).map((k) => ({ id: k, name: k })))
|
|
83
|
+
list: () => readAll(filePath).pipe(
|
|
84
|
+
Effect.map((data) => Object.keys(data).map((k) => ({ id: ProviderItemId.make(k), name: k })))
|
|
89
85
|
)
|
|
90
86
|
});
|
|
91
87
|
var resolveFilePath = (config) => authFilePath(config?.directory);
|
|
@@ -93,10 +89,8 @@ var fileSecretsPlugin = definePlugin((options) => ({
|
|
|
93
89
|
id: "fileSecrets",
|
|
94
90
|
storage: () => ({}),
|
|
95
91
|
extension: () => makeFileSecretsExtension(options),
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
// get/set/delete honor the scope arg threaded from the secrets facade.
|
|
99
|
-
makeScopedProvider(resolveFilePath(options), ctx.scopes[0].id)
|
|
92
|
+
credentialProviders: () => [
|
|
93
|
+
makeFileProvider(resolveFilePath(options))
|
|
100
94
|
]
|
|
101
95
|
}));
|
|
102
96
|
|
|
@@ -104,4 +98,4 @@ export {
|
|
|
104
98
|
xdgDataHome,
|
|
105
99
|
fileSecretsPlugin
|
|
106
100
|
};
|
|
107
|
-
//# sourceMappingURL=chunk-
|
|
101
|
+
//# sourceMappingURL=chunk-LOLKJR7S.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 ProviderItemId,\n ProviderKey,\n StorageError,\n type CredentialProvider,\n} from \"@executor-js/sdk\";\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// v2: the file is a FLAT map of opaque provider item id -> value.\n// { \"github-token\": \"ghp_xxx\" }\n// The v1 per-scope partition (`{ scopeId: { secretId: value } }`) is gone:\n// the connection row owns the (tenant, owner, subject) partition, and the\n// provider only ever sees an opaque `ProviderItemId`.\n// ---------------------------------------------------------------------------\n\nconst FlatAuthFile = Schema.Record(Schema.String, Schema.String);\nconst decodeFlatAuthFile = Schema.decodeUnknownEffect(Schema.fromJsonString(FlatAuthFile));\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 readAll = (filePath: string): Effect.Effect<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: StorageError) => isFileNotFoundCause(error.cause),\n () => Effect.succeed(\"\"),\n ),\n Effect.flatMap((raw: string) =>\n raw === \"\"\n ? Effect.succeed<Record<string, string>>({})\n : decodeFlatAuthFile(raw).pipe(\n Effect.mapError(toStorageError(\"Failed to parse auth file\")),\n ),\n ),\n );\n};\n\nconst writeAll = (\n filePath: 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 yield* Effect.try({\n try: () => fs.writeFileSync(tmp, JSON.stringify(secrets, 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// CredentialProvider — flat opaque-id storage in auth.json.\n//\n// v2: no scope partitioning. Each `ProviderItemId` is a flat top-level key in\n// the file; the connection row that references it owns the (tenant, owner,\n// subject) partition. `delete` returns void; absence is not an error.\n// ---------------------------------------------------------------------------\n\nconst FILE_PROVIDER_KEY = ProviderKey.make(\"file\");\n\nconst makeFileProvider = (filePath: string): CredentialProvider => ({\n key: FILE_PROVIDER_KEY,\n writable: true,\n\n get: (id: ProviderItemId) => readAll(filePath).pipe(Effect.map((data) => data[id] ?? null)),\n\n has: (id: ProviderItemId) => readAll(filePath).pipe(Effect.map((data) => id in data)),\n\n set: (id: ProviderItemId, value: string) =>\n Effect.gen(function* () {\n const data = yield* readAll(filePath);\n data[id] = value;\n yield* writeAll(filePath, data);\n }),\n\n delete: (id: ProviderItemId) =>\n Effect.gen(function* () {\n const data = yield* readAll(filePath);\n if (id in data) {\n delete data[id];\n yield* writeAll(filePath, data);\n }\n }),\n\n list: () =>\n readAll(filePath).pipe(\n Effect.map((data) => Object.keys(data).map((k) => ({ id: ProviderItemId.make(k), name: k }))),\n ),\n});\n\n// ---------------------------------------------------------------------------\n// Plugin definition\n//\n// Compute the file path identically in `extension` (for `filePath`) and\n// `credentialProviders` (for the provider's read/write). Both are called once\n// 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 credentialProviders: (): readonly CredentialProvider[] => [\n makeFileProvider(resolveFilePath(options)),\n ],\n}));\n"],"mappings":"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","names":[]}
|
package/dist/core.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ declare const makeFileSecretsExtension: (options: FileSecretsPluginConfig | unde
|
|
|
7
7
|
filePath: string;
|
|
8
8
|
};
|
|
9
9
|
export type FileSecretsExtension = ReturnType<typeof makeFileSecretsExtension>;
|
|
10
|
-
export declare const fileSecretsPlugin: import("@executor-js/sdk
|
|
10
|
+
export declare const fileSecretsPlugin: import("@executor-js/sdk").ConfiguredPlugin<"fileSecrets", {
|
|
11
11
|
filePath: string;
|
|
12
|
-
}, {}, FileSecretsPluginConfig, undefined,
|
|
12
|
+
}, {}, FileSecretsPluginConfig, undefined, import("effect/Layer").Layer<unknown, never, never>, import("effect/unstable/httpapi/HttpApiGroup").Any>;
|
|
13
13
|
export {};
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/promise.ts"],"sourcesContent":["import { type Plugin } from \"@executor-js/sdk
|
|
1
|
+
{"version":3,"sources":["../src/promise.ts"],"sourcesContent":["import { type Plugin } from \"@executor-js/sdk\";\n\nimport {\n fileSecretsPlugin as fileSecretsPluginEffect,\n type FileSecretsExtension,\n type FileSecretsPluginConfig,\n} from \"./index\";\n\nexport type { FileSecretsPluginConfig } from \"./index\";\n\n// Explicit return type so the emitted dist/promise.d.ts references\n// `import(\"@executor-js/sdk\").Plugin` rather than the Promise-surface\n// root specifier (which doesn't re-export Plugin).\nexport const fileSecretsPlugin = (\n config?: FileSecretsPluginConfig,\n): Plugin<\"fileSecrets\", FileSecretsExtension, Record<string, never>> =>\n fileSecretsPluginEffect(config);\n"],"mappings":";;;;;AAaO,IAAMA,qBAAoB,CAC/B,WAEA,kBAAwB,MAAM;","names":["fileSecretsPlugin"]}
|
package/dist/promise.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Plugin } from "@executor-js/sdk
|
|
1
|
+
import { type Plugin } from "@executor-js/sdk";
|
|
2
2
|
import { type FileSecretsExtension, type FileSecretsPluginConfig } from "./index";
|
|
3
3
|
export type { FileSecretsPluginConfig } from "./index";
|
|
4
|
-
export declare const fileSecretsPlugin: (config?: FileSecretsPluginConfig) => Plugin<"fileSecrets", FileSecretsExtension, Record<string, never
|
|
4
|
+
export declare const fileSecretsPlugin: (config?: FileSecretsPluginConfig) => Plugin<"fileSecrets", FileSecretsExtension, Record<string, never>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@executor-js/plugin-file-secrets",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.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,13 +40,16 @@
|
|
|
40
40
|
"typecheck:slow": "bunx tsc --noEmit -p tsconfig.json"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@executor-js/sdk": "1.
|
|
44
|
-
"effect": "4.0.0-beta.59"
|
|
43
|
+
"@executor-js/sdk": "1.5.1"
|
|
45
44
|
},
|
|
46
45
|
"devDependencies": {
|
|
47
46
|
"@types/node": "^24.3.1",
|
|
48
47
|
"bun-types": "^1.2.22",
|
|
48
|
+
"effect": "4.0.0-beta.59",
|
|
49
49
|
"tsup": "^8.5.0",
|
|
50
50
|
"vitest": "^4.1.5"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"effect": "4.0.0-beta.59"
|
|
51
54
|
}
|
|
52
55
|
}
|
|
@@ -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 => 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":[]}
|