@effect-app/cli 1.29.1 → 2.0.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.
@@ -1,31 +1,27 @@
1
1
  /* eslint-disable no-constant-binary-expression */
2
2
  /* eslint-disable no-empty-pattern */
3
- // import necessary modules from the libraries
4
- import { Command } from "@effect/platform";
5
- import { CommandExecutor } from "@effect/platform/CommandExecutor";
6
- import { Effect, identity } from "effect";
3
+ import { Effect, Layer, ServiceMap } from "effect";
4
+ import { ChildProcess } from "effect/unstable/process";
5
+ import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
7
6
  /**
8
7
  * Service for executing shell commands using the Effect platform's Command API.
9
8
  * Provides methods to run shell commands with different output handling strategies.
10
9
  * All commands are executed through the system shell (/bin/sh) for proper command parsing.
11
10
  */
12
- // @effect-diagnostics-next-line missingEffectServiceDependency:off
13
- export class RunCommandService extends Effect.Service()("RunCommandService", {
14
- dependencies: [],
15
- effect: Effect.gen(function* () {
11
+ export class RunCommandService extends ServiceMap.Service()("RunCommandService", {
12
+ make: Effect.gen(function* () {
16
13
  // will be provided by the main CLI pipeline setup
17
- const commandExecutor = yield* CommandExecutor;
14
+ const spawner = yield* ChildProcessSpawner;
18
15
  /**
19
16
  * Executes a shell command using Command API with inherited stdio streams.
20
- * The command is rn through the system shell (/bin/sh) for proper command parsing.
17
+ * The command is run through the system shell (/bin/sh) for proper command parsing.
21
18
  *
22
19
  * @param cmd - The shell command to execute
23
20
  * @param cwd - Optional working directory to execute the command in
24
21
  * @returns An Effect that succeeds with the exit code or fails with a PlatformError
25
22
  */
26
- const runGetExitCode = (cmd, cwd) => Command
27
- .make("sh", "-c", cmd)
28
- .pipe(Command.stdout("inherit"), Command.stderr("inherit"), cwd ? Command.workingDirectory(cwd) : identity, Command.exitCode, Effect.provideService(CommandExecutor, commandExecutor));
23
+ const runGetExitCode = (cmd, cwd) => spawner
24
+ .exitCode(ChildProcess.make("sh", ["-c", cmd], { stdout: "inherit", stderr: "inherit", cwd }));
29
25
  /**
30
26
  * Executes a shell command using Command API and returns the output as a string.
31
27
  * The command is run through the system shell (/bin/sh) for proper command parsing.
@@ -34,14 +30,14 @@ export class RunCommandService extends Effect.Service()("RunCommandService", {
34
30
  * @param cwd - Optional working directory to execute the command in
35
31
  * @returns An Effect that succeeds with the command's stdout output as string or fails with a PlatformError
36
32
  */
37
- const runGetString = (cmd, cwd) => Command
38
- .make("sh", "-c", cmd)
39
- .pipe(cwd ? Command.workingDirectory(cwd) : identity, Command.string, Effect.provideService(CommandExecutor, commandExecutor));
33
+ const runGetString = (cmd, cwd) => spawner
34
+ .string(ChildProcess.make("sh", ["-c", cmd], { cwd }));
40
35
  return {
41
36
  runGetExitCode,
42
37
  runGetString
43
38
  };
44
39
  })
45
40
  }) {
41
+ static Default = Layer.effect(this, this.make);
46
42
  }
47
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3MtY29tbWFuZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9vcy1jb21tYW5kLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGtEQUFrRDtBQUNsRCxxQ0FBcUM7QUFDckMsOENBQThDO0FBQzlDLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQTtBQUUxQyxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sa0NBQWtDLENBQUE7QUFDbEUsT0FBTyxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsTUFBTSxRQUFRLENBQUE7QUFFekM7Ozs7R0FJRztBQUNILG1FQUFtRTtBQUNuRSxNQUFNLE9BQU8saUJBQWtCLFNBQVEsTUFBTSxDQUFDLE9BQU8sRUFBcUIsQ0FBQyxtQkFBbUIsRUFBRTtJQUM5RixZQUFZLEVBQUUsRUFBRTtJQUNoQixNQUFNLEVBQUUsTUFBTSxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUM7UUFDMUIsa0RBQWtEO1FBQ2xELE1BQU0sZUFBZSxHQUFHLEtBQUssQ0FBQyxDQUFDLGVBQWUsQ0FBQTtRQUU5Qzs7Ozs7OztXQU9HO1FBQ0gsTUFBTSxjQUFjLEdBQUcsQ0FBQyxHQUFXLEVBQUUsR0FBWSxFQUFFLEVBQUUsQ0FDbkQsT0FBTzthQUNKLElBQUksQ0FBQyxJQUFJLEVBQUUsSUFBSSxFQUFFLEdBQUcsQ0FBQzthQUNyQixJQUFJLENBQ0gsT0FBTyxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsRUFDekIsT0FBTyxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsRUFDekIsR0FBRyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLFFBQVEsRUFDOUMsT0FBTyxDQUFDLFFBQVEsRUFDaEIsTUFBTSxDQUFDLGNBQWMsQ0FBQyxlQUFlLEVBQUUsZUFBZSxDQUFDLENBQ3hELENBQUE7UUFFTDs7Ozs7OztXQU9HO1FBQ0gsTUFBTSxZQUFZLEdBQUcsQ0FBQyxHQUFXLEVBQUUsR0FBWSxFQUFFLEVBQUUsQ0FDakQsT0FBTzthQUNKLElBQUksQ0FBQyxJQUFJLEVBQUUsSUFBSSxFQUFFLEdBQUcsQ0FBQzthQUNyQixJQUFJLENBQ0gsR0FBRyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLFFBQVEsRUFDOUMsT0FBTyxDQUFDLE1BQU0sRUFDZCxNQUFNLENBQUMsY0FBYyxDQUFDLGVBQWUsRUFBRSxlQUFlLENBQUMsQ0FDeEQsQ0FBQTtRQUVMLE9BQU87WUFDTCxjQUFjO1lBQ2QsWUFBWTtTQUNiLENBQUE7SUFDSCxDQUFDLENBQUM7Q0FDSCxDQUFDO0NBQ0QifQ==
43
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3MtY29tbWFuZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9vcy1jb21tYW5kLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGtEQUFrRDtBQUNsRCxxQ0FBcUM7QUFDckMsT0FBTyxFQUFFLE1BQU0sRUFBRSxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sUUFBUSxDQUFBO0FBQ2xELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQTtBQUN0RCxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSw2Q0FBNkMsQ0FBQTtBQUVqRjs7OztHQUlHO0FBQ0gsTUFBTSxPQUFPLGlCQUFrQixTQUFRLFVBQVUsQ0FBQyxPQUFPLEVBQXFCLENBQUMsbUJBQW1CLEVBQUU7SUFDbEcsSUFBSSxFQUFFLE1BQU0sQ0FBQyxHQUFHLENBQUMsUUFBUSxDQUFDO1FBQ3hCLGtEQUFrRDtRQUNsRCxNQUFNLE9BQU8sR0FBRyxLQUFLLENBQUMsQ0FBQyxtQkFBbUIsQ0FBQTtRQUUxQzs7Ozs7OztXQU9HO1FBQ0gsTUFBTSxjQUFjLEdBQUcsQ0FBQyxHQUFXLEVBQUUsR0FBWSxFQUFFLEVBQUUsQ0FDbkQsT0FBTzthQUNKLFFBQVEsQ0FDUCxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDLElBQUksRUFBRSxHQUFHLENBQUMsRUFBRSxFQUFFLE1BQU0sRUFBRSxTQUFTLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUNwRixDQUFBO1FBRUw7Ozs7Ozs7V0FPRztRQUNILE1BQU0sWUFBWSxHQUFHLENBQUMsR0FBVyxFQUFFLEdBQVksRUFBRSxFQUFFLENBQ2pELE9BQU87YUFDSixNQUFNLENBQ0wsWUFBWSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQyxJQUFJLEVBQUUsR0FBRyxDQUFDLEVBQUUsRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUM5QyxDQUFBO1FBRUwsT0FBTztZQUNMLGNBQWM7WUFDZCxZQUFZO1NBQ2IsQ0FBQTtJQUNILENBQUMsQ0FBQztDQUNILENBQUM7SUFDQSxNQUFNLENBQUMsT0FBTyxHQUFHLEtBQUssQ0FBQyxNQUFNLENBQUMsSUFBSSxFQUFFLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQSJ9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-app/cli",
3
- "version": "1.29.1",
3
+ "version": "2.0.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,19 +9,19 @@
9
9
  "effect-app-cli": "./bin.js"
10
10
  },
11
11
  "dependencies": {
12
- "@effect/cli": "^0.73.0",
13
- "@effect/platform-node": "^0.104.0",
12
+ "@effect/platform-node": "^4.0.0-beta.23",
13
+ "effect": "^4.0.0-beta.23",
14
14
  "js-yaml": "4.1.1",
15
15
  "node-watch": "^0.7.4"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@types/js-yaml": "^4.0.9",
19
- "@types/node": "25.0.8",
19
+ "@types/node": "25.3.3",
20
20
  "json5": "^2.2.3",
21
21
  "typescript": "~5.9.3",
22
- "vitest": "^4.0.17",
22
+ "vitest": "^4.0.18",
23
23
  "effect-app": "3.16.0",
24
- "@effect-app/eslint-shared-config": "0.5.1"
24
+ "@effect-app/eslint-shared-config": "0.5.6"
25
25
  },
26
26
  "typesVersions": {
27
27
  "*": {
@@ -63,7 +63,8 @@
63
63
  ],
64
64
  "scripts": {
65
65
  "watch": "pnpm build:tsc -w",
66
- "build:tsc": "pnpm clean-dist && tsc --build",
66
+ "build:tsc": "pnpm clean-dist && pnpm check",
67
+ "check": "tsc --build",
67
68
  "build": "pnpm build:tsc",
68
69
  "watch2": "pnpm clean-dist && NODE_OPTIONS=--max-old-space-size=6144 tsc -w",
69
70
  "clean": "rm -rf dist",
@@ -74,7 +75,7 @@
74
75
  "compile": "NODE_OPTIONS=--max-old-space-size=6144 tsc --noEmit",
75
76
  "lint": "NODE_OPTIONS=--max-old-space-size=6144 ESLINT_TS=1 eslint ./src",
76
77
  "lint:watch": "ESLINT_TS=1 esw -w --changed --clear --ext ts,tsx .",
77
- "autofix": "pnpm lint --fix",
78
+ "lint-fix": "pnpm lint --fix",
78
79
  "test": "vitest",
79
80
  "test:run": "pnpm run test run --passWithNoTests",
80
81
  "testsuite": "pnpm lint && pnpm circular && pnpm run test:run",
package/src/extract.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { type Error as PlatformError, FileSystem, Path } from "@effect/platform"
2
- import { Array as EffectArray, Effect, Order, pipe } from "effect"
1
+ import { Array as EffectArray, Effect, FileSystem, Order, Path, pipe, type PlatformError } from "effect"
3
2
 
4
3
  /**
5
4
  * Generates package.json exports mappings for TypeScript modules
@@ -61,7 +60,7 @@ export const ExtractExportMappingsService = Effect.fn("effa-cli.extractExportMap
61
60
 
62
61
  const sortedMappings = pipe(
63
62
  exportMappings,
64
- EffectArray.sort(Order.string),
63
+ EffectArray.sort(Order.String),
65
64
  EffectArray.join(",\n")
66
65
  )
67
66
 
package/src/gist.ts CHANGED
@@ -1,9 +1,7 @@
1
1
  /* eslint-disable no-constant-binary-expression */
2
2
  /* eslint-disable no-empty-pattern */
3
3
  // import necessary modules from the libraries
4
- import { FileSystem, Path } from "@effect/platform"
5
-
6
- import { Array, Config, Data, Effect, Option, ParseResult, pipe, Redacted, Schema, SynchronizedRef } from "effect"
4
+ import { Array, Config, Data, Effect, FileSystem, Layer, Option, Path, pipe, Redacted, Result, Schema, SchemaIssue, SchemaTransformation, ServiceMap, SynchronizedRef } from "effect"
7
5
 
8
6
  import * as yaml from "js-yaml"
9
7
  import path from "path"
@@ -44,71 +42,64 @@ export class GistEntry extends Schema.Class<GistEntry>("GistEntry")({
44
42
  * @see {@link https://docs.github.com/articles/creating-gists | GitHub Gist Documentation}
45
43
  * @see {@link https://github.com/orgs/community/discussions/29584 | Community Discussion on Gist Folder Support}
46
44
  */
47
- export class GistEntryDecoded extends GistEntry.transformOrFail<GistEntryDecoded>("GistEntryDecoded")({
48
- files_with_name: Schema.Array(Schema.Struct({
49
- path: Schema.String,
50
- name: Schema.String
51
- }))
52
- }, {
53
- decode: Effect.fnUntraced(function*(entry, _, ast) {
54
- const files_with_name = entry.files.map((file) => ({
55
- path: file,
56
- name: path.basename(file) // <-- I'm using Node's path module here so that this schema works without requirements on Effect's Path module
57
- }))
58
-
59
- // check for duplicate file names
60
- const nameMap = new Map<string, string[]>()
61
- for (const { name, path: filePath } of files_with_name) {
62
- if (!nameMap.has(name)) {
63
- nameMap.set(name, [])
64
- }
65
- nameMap.get(name)!.push(filePath)
66
- }
45
+ export class GistEntryDecoded extends Schema.Opaque<GistEntryDecoded>()(
46
+ GistEntry.pipe(
47
+ Schema.decodeTo(
48
+ Schema.Struct({
49
+ description: Schema.String,
50
+ public: Schema.Boolean,
51
+ company: Schema.String,
52
+ files: Schema.Array(Schema.String),
53
+ files_with_name: Schema.Array(Schema.Struct({
54
+ path: Schema.String,
55
+ name: Schema.String
56
+ }))
57
+ }),
58
+ SchemaTransformation.transformOrFail({
59
+ decode: Effect.fnUntraced(function*(entry) {
60
+ const files_with_name = entry.files.map((file) => ({
61
+ path: file,
62
+ name: path.basename(file) // <-- I'm using Node's path module here so that this schema works without requirements on Effect's Path module
63
+ }))
64
+
65
+ // check for duplicate file names
66
+ const nameMap = new Map<string, string[]>()
67
+ for (const { name, path: filePath } of files_with_name) {
68
+ if (!nameMap.has(name)) {
69
+ nameMap.set(name, [])
70
+ }
71
+ nameMap.get(name)!.push(filePath)
72
+ }
67
73
 
68
- // find duplicates and collect all collisions
69
- const collisions: ParseResult.ParseIssue[] = []
70
- for (const [fileName, paths] of nameMap.entries()) {
71
- if (paths.length > 1) {
72
- collisions.push(
73
- new ParseResult.Type(
74
- ast,
75
- paths,
76
- `Duplicate file name detected: "${fileName}". Colliding paths: ${paths.join(", ")}`
77
- )
78
- )
79
- }
80
- }
74
+ // find duplicates and collect all collision messages
75
+ const messages: string[] = []
76
+ for (const [fileName, paths] of nameMap.entries()) {
77
+ if (paths.length > 1) {
78
+ messages.push(
79
+ `Duplicate file name detected: "${fileName}". Colliding paths: ${paths.join(", ")}`
80
+ )
81
+ }
82
+ }
81
83
 
82
- // if there are any collisions, fail with all of them
83
- if (Array.isNonEmptyArray(collisions)) {
84
- return yield* Effect.fail(
85
- new ParseResult.Composite(
86
- ast,
87
- entry.files,
88
- collisions
89
- )
90
- )
91
- }
84
+ // if there are any collisions, fail with a combined message
85
+ if (messages.length > 0) {
86
+ return yield* Effect.fail(
87
+ new SchemaIssue.InvalidValue(Option.some(entry.files), { message: messages.join("; ") })
88
+ )
89
+ }
92
90
 
93
- return yield* Effect.succeed({
94
- ...entry,
95
- files_with_name
96
- })
97
- }),
98
- encode: (({ files_with_name, ...entry }) => ParseResult.succeed(entry))
99
- }) {}
91
+ return yield* Effect.succeed({ ...entry, files_with_name })
92
+ }),
93
+ encode: ({ files_with_name: _, ...entry }) => Effect.succeed(entry)
94
+ })
95
+ )
96
+ )
97
+ ) {}
100
98
 
101
99
  export class GistYAML extends Schema.Class<GistYAML>("GistYAML")({
102
- gists: Schema
103
- .Record({
104
- key: Schema.String,
105
- value: GistEntryDecoded
106
- })
107
- .pipe(Schema.optionalWith({
108
- default: () => ({}),
109
- nullable: true,
110
- exact: true
111
- })),
100
+ gists: Schema.optional(Schema.NullOr(
101
+ Schema.Record(Schema.String, GistEntryDecoded)
102
+ )),
112
103
  settings: Schema.Struct({
113
104
  token_env: Schema.String,
114
105
  base_directory: Schema.String
@@ -176,16 +167,16 @@ class GistYAMLError extends Data.TaggedError("GistYAMLError")<{
176
167
  // Services
177
168
  //
178
169
 
179
- class GHGistService extends Effect.Service<GHGistService>()("GHGistService", {
180
- dependencies: [RunCommandService.Default],
181
- effect: Effect.gen(function*() {
170
+ class GHGistService extends ServiceMap.Service<GHGistService>()("GHGistService", {
171
+ make: Effect.gen(function*() {
182
172
  const CACHE_GIST_DESCRIPTION = "GIST_CACHE_DO_NOT_EDIT_effa_cli_internal"
183
173
  const { runGetExitCode, runGetString } = yield* RunCommandService
184
174
 
185
175
  // the client cannot recover from PlatformErrors, so we convert failures into defects to clean up the signatures
186
176
  const runGetExitCodeSuppressed = (...args: Parameters<typeof runGetExitCode>) => {
187
177
  return runGetExitCode(...args).pipe(
188
- Effect.catchAll((e) => Effect.dieMessage(`Command failed: ${args.join(" ")}\nError: ${e.message}`)),
178
+ Effect.mapError((e) => `Command failed: ${args.join(" ")}\nError: ${e.message}`),
179
+ Effect.orDie,
189
180
  Effect.asVoid
190
181
  )
191
182
  }
@@ -193,7 +184,8 @@ class GHGistService extends Effect.Service<GHGistService>()("GHGistService", {
193
184
  // the client cannot recover from PlatformErrors, so we convert failures into defects to clean up the signatures
194
185
  const runGetStringSuppressed = (...args: Parameters<typeof runGetString>) => {
195
186
  return runGetString(...args).pipe(
196
- Effect.catchAll((e) => Effect.dieMessage(`Command failed: ${args.join(" ")}\nError: ${e.message}`))
187
+ Effect.mapError((e) => `Command failed: ${args.join(" ")}\nError: ${e.message}`),
188
+ Effect.orDie
197
189
  )
198
190
  }
199
191
 
@@ -218,7 +210,6 @@ class GHGistService extends Effect.Service<GHGistService>()("GHGistService", {
218
210
  ) {
219
211
  // search for existing cache gist
220
212
  const output = yield* runGetStringSuppressed(`gh gist list --filter "${CACHE_GIST_DESCRIPTION}"`)
221
- .pipe(Effect.orElse(() => Effect.succeed("")))
222
213
 
223
214
  const lines = output.trim().split("\n").filter((line: string) => line.trim())
224
215
 
@@ -233,7 +224,7 @@ class GHGistService extends Effect.Service<GHGistService>()("GHGistService", {
233
224
 
234
225
  if (!gist_id) {
235
226
  if (recCache) {
236
- return yield* Effect.dieMessage("Failed to create or locate cache gist after creation attempt")
227
+ return yield* Effect.die("Failed to create or locate cache gist after creation attempt")
237
228
  }
238
229
  return yield* new GistCacheNotFound({ message: "No gist ID found in output" })
239
230
  } else {
@@ -252,7 +243,7 @@ class GHGistService extends Effect.Service<GHGistService>()("GHGistService", {
252
243
 
253
244
  if (!filesInCache.includes(`${company}.json`)) {
254
245
  if (recCacheCompany) {
255
- return yield* Effect.dieMessage(
246
+ return yield* Effect.die(
256
247
  `Failed to create or locate cache entry for company ${company} after creation attempt`
257
248
  )
258
249
  }
@@ -265,7 +256,7 @@ class GHGistService extends Effect.Service<GHGistService>()("GHGistService", {
265
256
 
266
257
  const entries = yield* pipe(
267
258
  cacheContent,
268
- pipe(Schema.parseJson(GistCacheEntries), Schema.decodeUnknown),
259
+ Schema.decodeUnknownEffect(Schema.fromJsonString(GistCacheEntries)),
269
260
  Effect.orDie
270
261
  )
271
262
 
@@ -310,7 +301,7 @@ class GHGistService extends Effect.Service<GHGistService>()("GHGistService", {
310
301
  function*(cache: GistCache) {
311
302
  const cacheJson = yield* pipe(
312
303
  cache.entries,
313
- pipe(Schema.parseJson(GistCacheEntries), Schema.encodeUnknown),
304
+ Schema.encodeUnknownEffect(Schema.fromJsonString(GistCacheEntries)),
314
305
  // cannot recover from parse errors in any case, better to die here instead of cluttering the signature
315
306
  Effect.orDie
316
307
  )
@@ -353,7 +344,7 @@ class GHGistService extends Effect.Service<GHGistService>()("GHGistService", {
353
344
  gistUrl,
354
345
  extractGistIdFromUrl,
355
346
  Option.match({
356
- onNone: () => Effect.dieMessage(`Failed to extract gist ID from URL: ${gistUrl}`),
347
+ onNone: () => Effect.die(`Failed to extract gist ID from URL: ${gistUrl}`),
357
348
  onSome: (id) =>
358
349
  Effect
359
350
  .succeed(
@@ -396,20 +387,11 @@ class GHGistService extends Effect.Service<GHGistService>()("GHGistService", {
396
387
 
397
388
  // filter file names by environment prefix and remove the prefix
398
389
  // files in gists are prefixed with "env." to support multiple environments
399
- return Array.filterMap(
400
- output
401
- .trim()
402
- .split("\n"),
403
- (fn) => {
404
- const fnTrimmed = fn.trim()
405
- if (!fnTrimmed.startsWith(env + ".")) {
406
- return Option.none()
407
- }
408
- return Option.some(
409
- fnTrimmed.substring(env.length + 1) // remove env prefix and dot
410
- )
411
- }
412
- )
390
+ return Array.filterMap(output.trim().split("\n"), (fn) => {
391
+ const fnTrimmed = fn.trim()
392
+ if (!fnTrimmed.startsWith(env + ".")) return Result.fail(fn)
393
+ return Result.succeed(fnTrimmed.substring(env.length + 1)) // remove env prefix and dot
394
+ })
413
395
  }
414
396
  )
415
397
 
@@ -500,7 +482,7 @@ class GHGistService extends Effect.Service<GHGistService>()("GHGistService", {
500
482
 
501
483
  const login = Effect.fn("GHGistService.login")(function*(token: string) {
502
484
  if ((yield* runGetExitCode("gh --version").pipe(Effect.orDie)) !== 0) {
503
- return yield* Effect.dieMessage(
485
+ return yield* Effect.die(
504
486
  "GitHub CLI (gh) is not installed or not found in PATH. Please install it to use the gist command."
505
487
  )
506
488
  }
@@ -608,13 +590,15 @@ class GHGistService extends Effect.Service<GHGistService>()("GHGistService", {
608
590
  deleteGist
609
591
  }
610
592
  })
611
- }) {}
593
+ }) {
594
+ static DefaultWithoutDependencies = Layer.effect(this, this.make)
595
+ static Default = this.DefaultWithoutDependencies.pipe(
596
+ Layer.provide(RunCommandService.Default)
597
+ )
598
+ }
612
599
 
613
- // @effect-diagnostics-next-line missingEffectServiceDependency:off
614
- export class GistHandler extends Effect.Service<GistHandler>()("GistHandler", {
615
- accessors: true,
616
- dependencies: [GHGistService.Default],
617
- effect: Effect.gen(function*() {
600
+ export class GistHandler extends ServiceMap.Service<GistHandler>()("GistHandler", {
601
+ make: Effect.gen(function*() {
618
602
  const GH = yield* GHGistService
619
603
 
620
604
  // I prefer to provide these two only once during the main CLI pipeline setup
@@ -624,7 +608,7 @@ export class GistHandler extends Effect.Service<GistHandler>()("GistHandler", {
624
608
  return {
625
609
  handler: Effect.fn("effa-cli.gist.GistHandler")(function*({ YAMLPath }: { YAMLPath: string }) {
626
610
  // load company and environment from environment variables
627
- const CONFIG = yield* Effect.all({
611
+ const CONFIG = yield* Config.all({
628
612
  company: Config.string("COMPANY"),
629
613
  env: Config.string("ENV").pipe(Config.withDefault("local-dev"))
630
614
  })
@@ -649,7 +633,7 @@ export class GistHandler extends Effect.Service<GistHandler>()("GistHandler", {
649
633
  }
650
634
  })
651
635
  ),
652
- Effect.andThen(Schema.decodeUnknown(GistYAML))
636
+ Effect.andThen(Schema.decodeUnknownEffect(GistYAML))
653
637
  )
654
638
 
655
639
  // load GitHub token securely from environment variable
@@ -665,7 +649,7 @@ export class GistHandler extends Effect.Service<GistHandler>()("GistHandler", {
665
649
  // filter YAML gists by company to ensure isolation between different organizations
666
650
  // this prevents cross-company gist operations and maintains data separation
667
651
  const thisCompanyGistsFromYaml = Object
668
- .entries(configFromYaml.gists)
652
+ .entries(configFromYaml.gists ?? {})
669
653
  .filter(([, v]) => v.company === CONFIG.company)
670
654
 
671
655
  for (
@@ -815,4 +799,9 @@ export class GistHandler extends Effect.Service<GistHandler>()("GistHandler", {
815
799
  })
816
800
  }
817
801
  })
818
- }) {}
802
+ }) {
803
+ static DefaultWithoutDependencies = Layer.effect(this, this.make)
804
+ static Default = this.DefaultWithoutDependencies.pipe(
805
+ Layer.provide(GHGistService.Default)
806
+ )
807
+ }