@effect-app/cli 1.29.2 → 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.
package/src/index.ts CHANGED
@@ -1,738 +1,724 @@
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 { Args, Command, Options, Prompt } from "@effect/cli"
5
- import { FileSystem, Path } from "@effect/platform"
6
- import { NodeContext, NodeRuntime } from "@effect/platform-node"
4
+ import { NodeRuntime, NodeServices } from "@effect/platform-node"
5
+ import { Argument, Command, Flag, Prompt } from "effect/unstable/cli"
7
6
 
8
- import { type CommandExecutor } from "@effect/platform/CommandExecutor"
9
- import { type PlatformError } from "@effect/platform/Error"
10
- import { Effect, Layer, Option, Stream, type Types } from "effect"
7
+ import { Effect, FileSystem, Layer, Option, Path, Stream } from "effect"
11
8
  import { ExtractExportMappingsService } from "./extract.js"
12
9
  import { GistHandler } from "./gist.js"
13
10
  import { RunCommandService } from "./os-command.js"
14
11
  import { packages } from "./shared.js"
15
12
 
16
- Effect
17
- .fn("effa-cli")(function*() {
18
- const fs = yield* FileSystem.FileSystem
19
- const path = yield* Path.Path
20
- const extractExportMappings = yield* ExtractExportMappingsService
21
- const { runGetExitCode } = yield* RunCommandService
22
-
23
- yield* Effect.addFinalizer(() => Effect.logInfo(`CLI has finished executing`))
24
-
25
- /**
26
- * Updates effect-app packages to their latest versions using npm-check-updates.
27
- * Runs both at workspace root and recursively in all workspace packages.
28
- */
29
- const updateEffectAppPackages = Effect.fn("effa-cli.ue.updateEffectAppPackages")(function*() {
30
- const filters = ["effect-app", "@effect-app/*"]
31
- for (const filter of filters) {
32
- yield* runGetExitCode(`pnpm exec ncu -u --filter "${filter}"`)
33
- yield* runGetExitCode(`pnpm -r exec ncu -u --filter "${filter}"`)
34
- }
35
- })()
36
-
37
- /**
38
- * Updates Effect ecosystem packages to their latest versions using npm-check-updates.
39
- * Covers core Effect packages, Effect ecosystem packages, and Effect Atom packages.
40
- * Runs both at workspace root and recursively in all workspace packages.
41
- */
42
- const updateEffectPackages = Effect.fn("effa-cli.ue.updateEffectPackages")(function*() {
43
- const effectFilters = ["effect", "@effect/*", "@effect-atom/*"]
44
- for (const filter of effectFilters) {
45
- yield* runGetExitCode(`pnpm exec ncu -u --filter "${filter}"`)
46
- yield* runGetExitCode(`pnpm -r exec ncu -u --filter "${filter}"`)
47
- }
48
- })()
13
+ NodeRuntime.runMain(
14
+ Effect
15
+ .fn("effa-cli")(function*() {
16
+ const fs = yield* FileSystem.FileSystem
17
+ const path = yield* Path.Path
18
+ const extractExportMappings = yield* ExtractExportMappingsService
19
+ const { runGetExitCode } = yield* RunCommandService
20
+
21
+ yield* Effect.addFinalizer(() => Effect.logInfo(`CLI has finished executing`))
22
+
23
+ /**
24
+ * Updates effect-app packages to their latest versions using npm-check-updates.
25
+ * Runs both at workspace root and recursively in all workspace packages.
26
+ */
27
+ const updateEffectAppPackages = Effect.fn("effa-cli.ue.updateEffectAppPackages")(function*() {
28
+ const filters = ["effect-app", "@effect-app/*"]
29
+ for (const filter of filters) {
30
+ yield* runGetExitCode(`pnpm exec ncu -u --filter "${filter}"`)
31
+ yield* runGetExitCode(`pnpm -r exec ncu -u --filter "${filter}"`)
32
+ }
33
+ })()
34
+
35
+ /**
36
+ * Updates Effect ecosystem packages to their latest versions using npm-check-updates.
37
+ * Covers core Effect packages, Effect ecosystem packages, and Effect Atom packages.
38
+ * Runs both at workspace root and recursively in all workspace packages.
39
+ */
40
+ const updateEffectPackages = Effect.fn("effa-cli.ue.updateEffectPackages")(function*() {
41
+ const effectFilters = ["effect", "@effect/*", "@effect-atom/*"]
42
+ for (const filter of effectFilters) {
43
+ yield* runGetExitCode(`pnpm exec ncu -u --filter "${filter}"`)
44
+ yield* runGetExitCode(`pnpm -r exec ncu -u --filter "${filter}"`)
45
+ }
46
+ })()
47
+
48
+ /**
49
+ * Updates all packages except Effect and Effect-App ecosystem packages to their latest versions using npm-check-updates.
50
+ * Excludes core Effect packages, Effect ecosystem packages, Effect Atom packages, and Effect-App packages.
51
+ * Preserves existing rejections from .ncurc.json configuration.
52
+ * Runs both at workspace root and recursively in all workspace packages.
53
+ */
54
+ const updatePackages = Effect.fn("effa-cli.update-packages.updatePackages")(function*() {
55
+ const effectFilters = ["effect", "@effect/*", "@effect-atom/*", "effect-app", "@effect-app/*"]
56
+
57
+ // read existing .ncurc.json to preserve existing reject patterns
58
+ let existingRejects: string[] = []
59
+ const ncurcPath = "./.ncurc.json"
60
+
61
+ if (yield* fs.exists(ncurcPath)) {
62
+ const ncurcContent = yield* fs.readFileString(ncurcPath)
63
+ const ncurc = JSON.parse(ncurcContent)
64
+ if (ncurc.reject && Array.isArray(ncurc.reject)) {
65
+ existingRejects = ncurc.reject
66
+ }
67
+ }
49
68
 
50
- /**
51
- * Updates all packages except Effect and Effect-App ecosystem packages to their latest versions using npm-check-updates.
52
- * Excludes core Effect packages, Effect ecosystem packages, Effect Atom packages, and Effect-App packages.
53
- * Preserves existing rejections from .ncurc.json configuration.
54
- * Runs both at workspace root and recursively in all workspace packages.
55
- */
56
- const updatePackages = Effect.fn("effa-cli.update-packages.updatePackages")(function*() {
57
- const effectFilters = ["effect", "@effect/*", "@effect-atom/*", "effect-app", "@effect-app/*"]
58
-
59
- // read existing .ncurc.json to preserve existing reject patterns
60
- let existingRejects: string[] = []
61
- const ncurcPath = "./.ncurc.json"
62
-
63
- if (yield* fs.exists(ncurcPath)) {
64
- const ncurcContent = yield* fs.readFileString(ncurcPath)
65
- const ncurc = JSON.parse(ncurcContent)
66
- if (ncurc.reject && Array.isArray(ncurc.reject)) {
67
- existingRejects = ncurc.reject
69
+ const allRejects = [...existingRejects, ...effectFilters]
70
+ yield* Effect.logInfo(`Excluding packages from update: ${allRejects.join(", ")}`)
71
+ const rejectArgs = allRejects.map((filter) => `--reject "${filter}"`).join(" ")
72
+
73
+ yield* runGetExitCode(`pnpm exec ncu -u ${rejectArgs}`)
74
+ yield* runGetExitCode(`pnpm -r exec ncu -u ${rejectArgs}`)
75
+ })()
76
+
77
+ /**
78
+ * Links local effect-app packages by adding file resolutions to package.json.
79
+ * Updates the package.json with file: protocol paths pointing to the local effect-app-libs directory,
80
+ * then runs pnpm install to apply the changes.
81
+ *
82
+ * @param effectAppLibsPath - Path to the local effect-app-libs directory
83
+ * @returns An Effect that succeeds when linking is complete
84
+ */
85
+ const linkPackages = Effect.fnUntraced(function*(effectAppLibsPath: string) {
86
+ yield* Effect.logInfo("Linking local effect-app packages...")
87
+
88
+ const packageJsonPath = "./package.json"
89
+ const packageJsonContent = yield* fs.readFileString(packageJsonPath)
90
+ const pj = JSON.parse(packageJsonContent)
91
+
92
+ const resolutions = {
93
+ ...pj.resolutions,
94
+ "@effect-app/eslint-codegen-model": "file:" + effectAppLibsPath + "/packages/eslint-codegen-model",
95
+ "effect-app": "file:" + effectAppLibsPath + "/packages/effect-app",
96
+ "@effect-app/infra": "file:" + effectAppLibsPath + "/packages/infra",
97
+ "@effect-app/vue": "file:" + effectAppLibsPath + "/packages/vue",
98
+ "@effect-app/vue-components": "file:" + effectAppLibsPath + "/packages/vue-components",
99
+ "@effect-app/eslint-shared-config": "file:" + effectAppLibsPath + "/packages/eslint-shared-config",
100
+ ...packages.reduce((acc, p) => ({ ...acc, [p]: `file:${effectAppLibsPath}/node_modules/${p}` }), {})
68
101
  }
69
- }
70
102
 
71
- const allRejects = [...existingRejects, ...effectFilters]
72
- yield* Effect.logInfo(`Excluding packages from update: ${allRejects.join(", ")}`)
73
- const rejectArgs = allRejects.map((filter) => `--reject "${filter}"`).join(" ")
103
+ pj.resolutions = resolutions
74
104
 
75
- yield* runGetExitCode(`pnpm exec ncu -u ${rejectArgs}`)
76
- yield* runGetExitCode(`pnpm -r exec ncu -u ${rejectArgs}`)
77
- })()
105
+ yield* fs.writeFileString(packageJsonPath, JSON.stringify(pj, null, 2))
106
+ yield* Effect.logInfo("Updated package.json with local file resolutions")
78
107
 
79
- /**
80
- * Links local effect-app packages by adding file resolutions to package.json.
81
- * Updates the package.json with file: protocol paths pointing to the local effect-app-libs directory,
82
- * then runs pnpm install to apply the changes.
83
- *
84
- * @param effectAppLibsPath - Path to the local effect-app-libs directory
85
- * @returns An Effect that succeeds when linking is complete
86
- */
87
- const linkPackages = Effect.fnUntraced(function*(effectAppLibsPath: string) {
88
- yield* Effect.logInfo("Linking local effect-app packages...")
89
-
90
- const packageJsonPath = "./package.json"
91
- const packageJsonContent = yield* fs.readFileString(packageJsonPath)
92
- const pj = JSON.parse(packageJsonContent)
93
-
94
- const resolutions = {
95
- ...pj.resolutions,
96
- "@effect-app/eslint-codegen-model": "file:" + effectAppLibsPath + "/packages/eslint-codegen-model",
97
- "effect-app": "file:" + effectAppLibsPath + "/packages/effect-app",
98
- "@effect-app/infra": "file:" + effectAppLibsPath + "/packages/infra",
99
- "@effect-app/vue": "file:" + effectAppLibsPath + "/packages/vue",
100
- "@effect-app/vue-components": "file:" + effectAppLibsPath + "/packages/vue-components",
101
- "@effect-app/eslint-shared-config": "file:" + effectAppLibsPath + "/packages/eslint-shared-config",
102
- ...packages.reduce((acc, p) => ({ ...acc, [p]: `file:${effectAppLibsPath}/node_modules/${p}` }), {})
103
- }
104
-
105
- pj.resolutions = resolutions
106
-
107
- yield* fs.writeFileString(packageJsonPath, JSON.stringify(pj, null, 2))
108
- yield* Effect.logInfo("Updated package.json with local file resolutions")
109
-
110
- yield* runGetExitCode("pnpm i")
111
-
112
- yield* Effect.logInfo("Successfully linked local packages")
113
- })
114
-
115
- /**
116
- * Unlinks local effect-app packages by removing file resolutions from package.json.
117
- * Filters out all effect-app related file: protocol resolutions from package.json,
118
- * then runs pnpm install to restore registry packages.
119
- *
120
- * @returns An Effect that succeeds when unlinking is complete
121
- */
122
- const unlinkPackages = Effect.fnUntraced(function*() {
123
- yield* Effect.logInfo("Unlinking local effect-app packages...")
124
-
125
- const packageJsonPath = "./package.json"
126
- const packageJsonContent = yield* fs.readFileString(packageJsonPath)
127
- const pj = JSON.parse(packageJsonContent)
128
-
129
- const filteredResolutions = Object.entries(pj.resolutions as Record<string, string>).reduce(
130
- (acc, [k, v]) => {
131
- if (k.startsWith("@effect-app/") || k === "effect-app" || packages.includes(k)) return acc
132
- acc[k] = v
133
- return acc
134
- },
135
- {} as Record<string, string>
136
- )
108
+ yield* runGetExitCode("pnpm i")
137
109
 
138
- pj.resolutions = filteredResolutions
110
+ yield* Effect.logInfo("Successfully linked local packages")
111
+ })
139
112
 
140
- yield* fs.writeFileString(packageJsonPath, JSON.stringify(pj, null, 2))
141
- yield* Effect.logInfo("Removed effect-app file resolutions from package.json")
113
+ /**
114
+ * Unlinks local effect-app packages by removing file resolutions from package.json.
115
+ * Filters out all effect-app related file: protocol resolutions from package.json,
116
+ * then runs pnpm install to restore registry packages.
117
+ *
118
+ * @returns An Effect that succeeds when unlinking is complete
119
+ */
120
+ const unlinkPackages = Effect.fnUntraced(function*() {
121
+ yield* Effect.logInfo("Unlinking local effect-app packages...")
122
+
123
+ const packageJsonPath = "./package.json"
124
+ const packageJsonContent = yield* fs.readFileString(packageJsonPath)
125
+ const pj = JSON.parse(packageJsonContent)
126
+
127
+ const filteredResolutions = Object.entries(pj.resolutions as Record<string, string>).reduce(
128
+ (acc, [k, v]) => {
129
+ if (k.startsWith("@effect-app/") || k === "effect-app" || packages.includes(k)) return acc
130
+ acc[k] = v
131
+ return acc
132
+ },
133
+ {} as Record<string, string>
134
+ )
142
135
 
143
- yield* runGetExitCode("pnpm i")
144
- yield* Effect.logInfo("Successfully unlinked local packages")
145
- })()
136
+ pj.resolutions = filteredResolutions
137
+
138
+ yield* fs.writeFileString(packageJsonPath, JSON.stringify(pj, null, 2))
139
+ yield* Effect.logInfo("Removed effect-app file resolutions from package.json")
140
+
141
+ yield* runGetExitCode("pnpm i")
142
+ yield* Effect.logInfo("Successfully unlinked local packages")
143
+ })()
144
+
145
+ /**
146
+ * Monitors controller files for changes and runs eslint on related controllers.ts/routes.ts files.
147
+ * Watches for .controllers. files and triggers eslint fixes on parent directory's controller files.
148
+ *
149
+ * @param watchPath - The path to watch for controller changes
150
+ * @param debug - Whether to enable debug logging
151
+ * @returns An Effect that sets up controller file monitoring
152
+ */
153
+ const monitorChildIndexes = Effect.fn("effa-cli.index-multi.monitorChildIndexes")(
154
+ function*(watchPath: string) {
155
+ yield* Effect.logInfo(`Starting controller monitoring for: ${watchPath}`)
156
+
157
+ const watchStream = fs.watch(watchPath)
158
+
159
+ yield* watchStream
160
+ .pipe(
161
+ Stream.runForEach(
162
+ Effect.fn("effa-cli.monitorChildIndexes.handleEvent")(function*(event) {
163
+ const pathParts = event.path.split("/")
164
+ const fileName = pathParts[pathParts.length - 1]
165
+ const isController = fileName?.toLowerCase().includes(".controllers.")
166
+
167
+ if (!isController) return
168
+
169
+ let i = 1
170
+ const reversedParts = pathParts.toReversed()
171
+
172
+ while (i < reversedParts.length) {
173
+ const candidateFiles = ["controllers.ts", "routes.ts"]
174
+ .map((f) => [...pathParts.slice(0, pathParts.length - i), f].join("/"))
175
+
176
+ const existingFiles: string[] = []
177
+ for (const file of candidateFiles) {
178
+ const exists = yield* fs.exists(file)
179
+ if (exists) existingFiles.push(file)
180
+ }
181
+
182
+ if (existingFiles.length > 0) {
183
+ yield* Effect.logInfo(
184
+ `Controller change detected: ${event.path}, fixing files: ${existingFiles.join(", ")}`
185
+ )
186
+
187
+ const eslintArgs = existingFiles.map((f) => `"../${f}"`).join(" ")
188
+ yield* runGetExitCode(`cd api && pnpm eslint --fix ${eslintArgs}`)
189
+ break
190
+ }
191
+ i++
192
+ }
193
+ })
194
+ ),
195
+ Effect.andThen(
196
+ Effect.addFinalizer(() => Effect.logInfo(`Stopped monitoring child indexes in: ${watchPath}`))
197
+ ),
198
+ Effect.forkScoped
199
+ )
200
+ }
201
+ )
146
202
 
147
- /**
148
- * Monitors controller files for changes and runs eslint on related controllers.ts/routes.ts files.
149
- * Watches for .controllers. files and triggers eslint fixes on parent directory's controller files.
150
- *
151
- * @param watchPath - The path to watch for controller changes
152
- * @param debug - Whether to enable debug logging
153
- * @returns An Effect that sets up controller file monitoring
154
- */
155
- const monitorChildIndexes = Effect.fn("effa-cli.index-multi.monitorChildIndexes")(
156
- function*(watchPath: string) {
157
- yield* Effect.logInfo(`Starting controller monitoring for: ${watchPath}`)
158
-
159
- const watchStream = fs.watch(watchPath, { recursive: true })
160
-
161
- yield* watchStream
162
- .pipe(
163
- Stream.runForEach(
164
- Effect.fn("effa-cli.monitorChildIndexes.handleEvent")(function*(event) {
165
- const pathParts = event.path.split("/")
166
- const fileName = pathParts[pathParts.length - 1]
167
- const isController = fileName?.toLowerCase().includes(".controllers.")
203
+ /**
204
+ * Monitors a directory for changes and runs eslint on the specified index file.
205
+ * Triggers eslint fixes when any file in the directory changes (except the index file itself).
206
+ *
207
+ * @param watchPath - The path to watch for changes
208
+ * @param indexFile - The index file to run eslint on when changes occur
209
+ * @param debug - Whether to enable debug logging
210
+ * @returns An Effect that sets up root index monitoring
211
+ */
212
+ const monitorRootIndexes = Effect.fn("effa-cli.index-multi.monitorRootIndexes")(
213
+ function*(watchPath: string, indexFile: string) {
214
+ yield* Effect.logInfo(`Starting root index monitoring for: ${watchPath} -> ${indexFile}`)
215
+
216
+ const watchStream = fs.watch(watchPath)
217
+
218
+ yield* watchStream
219
+ .pipe(
220
+ Stream.runForEach(
221
+ Effect.fn("effa-cli.index-multi.monitorRootIndexes.handleEvent")(function*(event) {
222
+ if (event.path.endsWith(indexFile)) return
223
+
224
+ yield* Effect.logInfo(`Root change detected: ${event.path}, fixing: ${indexFile}`)
225
+
226
+ yield* runGetExitCode(`pnpm eslint --fix "${indexFile}"`)
227
+ })
228
+ ),
229
+ Effect.andThen(
230
+ Effect.addFinalizer(() =>
231
+ Effect.logInfo(`Stopped monitoring root indexes in: ${watchPath} -> ${indexFile}`)
232
+ )
233
+ ),
234
+ Effect.forkScoped
235
+ )
236
+ }
237
+ )
168
238
 
169
- if (!isController) return
239
+ /**
240
+ * Sets up comprehensive index monitoring for a given path.
241
+ * Combines both child controller monitoring and root index monitoring.
242
+ *
243
+ * @param watchPath - The path to monitor
244
+ * @param debug - Whether to enable debug logging
245
+ * @returns An Effect that sets up all index monitoring for the path
246
+ */
247
+ const monitorIndexes = Effect.fn("effa-cli.index-multi.monitorIndexes")(
248
+ function*(watchPath: string) {
249
+ yield* Effect.logInfo(`Setting up index monitoring for path: ${watchPath}`)
170
250
 
171
- let i = 1
172
- const reversedParts = pathParts.toReversed()
251
+ const indexFile = watchPath + "/index.ts"
173
252
 
174
- while (i < reversedParts.length) {
175
- const candidateFiles = ["controllers.ts", "routes.ts"]
176
- .map((f) => [...pathParts.slice(0, pathParts.length - i), f].join("/"))
253
+ const monitors = [monitorChildIndexes(watchPath)]
177
254
 
178
- const existingFiles: string[] = []
179
- for (const file of candidateFiles) {
180
- const exists = yield* fs.exists(file)
181
- if (exists) existingFiles.push(file)
182
- }
255
+ if (yield* fs.exists(indexFile)) {
256
+ monitors.push(monitorRootIndexes(watchPath, indexFile))
257
+ } else {
258
+ yield* Effect.logWarning(`Index file ${indexFile} does not exist`)
259
+ }
183
260
 
184
- if (existingFiles.length > 0) {
185
- yield* Effect.logInfo(
186
- `Controller change detected: ${event.path}, fixing files: ${existingFiles.join(", ")}`
187
- )
261
+ yield* Effect.logInfo(`Starting ${monitors.length} monitor(s) for ${watchPath}`)
188
262
 
189
- const eslintArgs = existingFiles.map((f) => `"../${f}"`).join(" ")
190
- yield* runGetExitCode(`cd api && pnpm eslint --fix ${eslintArgs}`)
191
- break
192
- }
193
- i++
263
+ yield* Effect.all(monitors, { concurrency: monitors.length })
264
+ }
265
+ )
266
+
267
+ /**
268
+ * Updates a package.json file with generated exports mappings for TypeScript modules.
269
+ * Scans TypeScript source files and creates export entries that map module paths
270
+ * to their compiled JavaScript and TypeScript declaration files.
271
+ *
272
+ * @param startDir - The starting directory path for resolving relative paths
273
+ * @param p - The package directory path to process
274
+ * @param levels - Optional depth limit for export filtering (0 = no limit)
275
+ * @returns An Effect that succeeds when the package.json is updated
276
+ */
277
+ const packagejsonUpdater = Effect.fn("effa-cli.packagejsonUpdater")(
278
+ function*(startDir: string, p: string, levels = 0) {
279
+ yield* Effect.logInfo(`Generating exports for ${p}`)
280
+
281
+ const exportMappings = yield* extractExportMappings(path.resolve(startDir, p))
282
+
283
+ // if exportMappings is empty skip export generation
284
+ if (exportMappings === "") {
285
+ yield* Effect.logInfo(`No src directory found for ${p}, skipping export generation`)
286
+ return
287
+ }
288
+
289
+ const sortedExportEntries = JSON.parse(
290
+ `{ ${exportMappings} }`
291
+ ) as Record<
292
+ string,
293
+ unknown
294
+ >
295
+
296
+ const filteredExportEntries = levels
297
+ ? Object
298
+ .keys(sortedExportEntries)
299
+ // filter exports by directory depth - only include paths up to specified levels deep
300
+ .filter((_) => _.split("/").length <= (levels + 1 /* `./` */))
301
+ .reduce(
302
+ (prev, cur) => ({ ...prev, [cur]: sortedExportEntries[cur] }),
303
+ {} as Record<string, unknown>
304
+ )
305
+ : sortedExportEntries
306
+
307
+ const packageExports = {
308
+ ...((yield* fs.exists(p + "/src/index.ts"))
309
+ && {
310
+ ".": {
311
+ "types": "./dist/index.d.ts",
312
+ "default": "./dist/index.js"
194
313
  }
195
- })
196
- ),
197
- Effect.andThen(
198
- Effect.addFinalizer(() => Effect.logInfo(`Stopped monitoring child indexes in: ${watchPath}`))
199
- ),
200
- Effect.forkScoped
314
+ }),
315
+ ...Object
316
+ .keys(filteredExportEntries)
317
+ .reduce(
318
+ (prev, cur) => ({
319
+ ...prev,
320
+ // exclude index files and internal modules from package exports:
321
+ // - skip "./index" to avoid conflicts with the main "." export
322
+ // - skip "/internal/" paths to keep internal modules private
323
+ ...cur !== "./index" && !cur.includes("/internal/") && { [cur]: filteredExportEntries[cur] }
324
+ }),
325
+ {} as Record<string, unknown>
326
+ )
327
+ }
328
+
329
+ const pkgJson = JSON.parse(yield* fs.readFileString(p + "/package.json", "utf-8"))
330
+
331
+ pkgJson.exports = packageExports
332
+
333
+ yield* Effect.logInfo(`Writing updated package.json for ${p}`)
334
+
335
+ return yield* fs.writeFileString(
336
+ p + "/package.json",
337
+ JSON.stringify(pkgJson, null, 2)
201
338
  )
202
- }
203
- )
339
+ }
340
+ )
204
341
 
205
- /**
206
- * Monitors a directory for changes and runs eslint on the specified index file.
207
- * Triggers eslint fixes when any file in the directory changes (except the index file itself).
208
- *
209
- * @param watchPath - The path to watch for changes
210
- * @param indexFile - The index file to run eslint on when changes occur
211
- * @param debug - Whether to enable debug logging
212
- * @returns An Effect that sets up root index monitoring
213
- */
214
- const monitorRootIndexes = Effect.fn("effa-cli.index-multi.monitorRootIndexes")(
215
- function*(watchPath: string, indexFile: string) {
216
- yield* Effect.logInfo(`Starting root index monitoring for: ${watchPath} -> ${indexFile}`)
217
-
218
- const watchStream = fs.watch(watchPath)
219
-
220
- yield* watchStream
221
- .pipe(
222
- Stream.runForEach(
223
- Effect.fn("effa-cli.index-multi.monitorRootIndexes.handleEvent")(function*(event) {
224
- if (event.path.endsWith(indexFile)) return
342
+ /**
343
+ * Monitors a directory for TypeScript file changes and automatically updates package.json exports.
344
+ * Generates initial package.json exports, then watches the src directory for changes to regenerate exports.
345
+ *
346
+ * @param watchPath - The directory path containing the package.json and src to monitor
347
+ * @param levels - Optional depth limit for export filtering (0 = no limit)
348
+ * @returns An Effect that sets up package.json monitoring
349
+ */
350
+ const monitorPackageJson = Effect.fn("effa-cli.monitorPackageJson")(
351
+ function*(startDir: string, watchPath: string, levels = 0) {
352
+ yield* packagejsonUpdater(startDir, watchPath, levels)
353
+
354
+ const srcPath = watchPath === "." ? "./src" : `${watchPath}/src`
355
+
356
+ if (!(yield* fs.exists(srcPath))) {
357
+ yield* Effect.logWarning(`Source directory ${srcPath} does not exist - skipping monitoring`)
358
+ return
359
+ }
225
360
 
226
- yield* Effect.logInfo(`Root change detected: ${event.path}, fixing: ${indexFile}`)
361
+ const watchStream = fs.watch(srcPath)
227
362
 
228
- yield* runGetExitCode(`pnpm eslint --fix "${indexFile}"`)
363
+ yield* watchStream.pipe(
364
+ Stream.runForEach(
365
+ Effect.fn("effa-cli.monitorPackageJson.handleEvent")(function*(_) {
366
+ yield* packagejsonUpdater(startDir, watchPath, levels)
229
367
  })
230
368
  ),
231
369
  Effect.andThen(
232
- Effect.addFinalizer(() =>
233
- Effect.logInfo(`Stopped monitoring root indexes in: ${watchPath} -> ${indexFile}`)
234
- )
370
+ Effect.addFinalizer(() => Effect.logInfo(`Stopped monitoring package.json for: ${watchPath}`))
235
371
  ),
236
372
  Effect.forkScoped
237
373
  )
238
- }
239
- )
240
-
241
- /**
242
- * Sets up comprehensive index monitoring for a given path.
243
- * Combines both child controller monitoring and root index monitoring.
244
- *
245
- * @param watchPath - The path to monitor
246
- * @param debug - Whether to enable debug logging
247
- * @returns An Effect that sets up all index monitoring for the path
248
- */
249
- const monitorIndexes = Effect.fn("effa-cli.index-multi.monitorIndexes")(
250
- function*(watchPath: string) {
251
- yield* Effect.logInfo(`Setting up index monitoring for path: ${watchPath}`)
252
-
253
- const indexFile = watchPath + "/index.ts"
254
-
255
- const monitors = [monitorChildIndexes(watchPath)]
256
-
257
- if (yield* fs.exists(indexFile)) {
258
- monitors.push(monitorRootIndexes(watchPath, indexFile))
259
- } else {
260
- yield* Effect.logWarning(`Index file ${indexFile} does not exist`)
261
374
  }
375
+ )
262
376
 
263
- yield* Effect.logInfo(`Starting ${monitors.length} monitor(s) for ${watchPath}`)
264
-
265
- yield* Effect.all(monitors, { concurrency: monitors.length })
266
- }
267
- )
377
+ /*
378
+ * CLI
379
+ */
268
380
 
269
- /**
270
- * Updates a package.json file with generated exports mappings for TypeScript modules.
271
- * Scans TypeScript source files and creates export entries that map module paths
272
- * to their compiled JavaScript and TypeScript declaration files.
273
- *
274
- * @param startDir - The starting directory path for resolving relative paths
275
- * @param p - The package directory path to process
276
- * @param levels - Optional depth limit for export filtering (0 = no limit)
277
- * @returns An Effect that succeeds when the package.json is updated
278
- */
279
- const packagejsonUpdater = Effect.fn("effa-cli.packagejsonUpdater")(
280
- function*(startDir: string, p: string, levels = 0) {
281
- yield* Effect.logInfo(`Generating exports for ${p}`)
282
-
283
- const exportMappings = yield* extractExportMappings(path.resolve(startDir, p))
284
-
285
- // if exportMappings is empty skip export generation
286
- if (exportMappings === "") {
287
- yield* Effect.logInfo(`No src directory found for ${p}, skipping export generation`)
288
- return
289
- }
381
+ const WrapAsOption = Flag.string("wrap").pipe(
382
+ Flag.withAlias("w"),
383
+ Flag.optional,
384
+ Flag.withDescription(
385
+ "Wrap child bash command: the lifetime of the CLI command will be tied to the child process"
386
+ )
387
+ )
290
388
 
291
- const sortedExportEntries = JSON.parse(
292
- `{ ${exportMappings} }`
293
- ) as Record<
294
- string,
295
- unknown
296
- >
297
-
298
- const filteredExportEntries = levels
299
- ? Object
300
- .keys(sortedExportEntries)
301
- // filter exports by directory depth - only include paths up to specified levels deep
302
- .filter((_) => _.split("/").length <= (levels + 1 /* `./` */))
303
- .reduce(
304
- (prev, cur) => ({ ...prev, [cur]: sortedExportEntries[cur] }),
305
- {} as Record<string, unknown>
306
- )
307
- : sortedExportEntries
308
-
309
- const packageExports = {
310
- ...((yield* fs.exists(p + "/src/index.ts"))
311
- && {
312
- ".": {
313
- "types": "./dist/index.d.ts",
314
- "default": "./dist/index.js"
315
- }
316
- }),
317
- ...Object
318
- .keys(filteredExportEntries)
319
- .reduce(
320
- (prev, cur) => ({
321
- ...prev,
322
- // exclude index files and internal modules from package exports:
323
- // - skip "./index" to avoid conflicts with the main "." export
324
- // - skip "/internal/" paths to keep internal modules private
325
- ...cur !== "./index" && !cur.includes("/internal/") && { [cur]: filteredExportEntries[cur] }
326
- }),
327
- {} as Record<string, unknown>
328
- )
329
- }
389
+ // has prio over WrapAsOption
390
+ const WrapAsArg = Argument
391
+ .string("wrap")
392
+ .pipe(
393
+ Argument.atLeast(1),
394
+ Argument.optional,
395
+ Argument.withDescription(
396
+ "Wrap child bash command: the lifetime of the CLI command will be tied to the child process"
397
+ )
398
+ )
330
399
 
331
- const pkgJson = JSON.parse(yield* fs.readFileString(p + "/package.json", "utf-8"))
400
+ /**
401
+ * Creates a command that automatically includes wrap functionality for executing child bash commands.
402
+ * Combines both option-based (--wrap) and argument-based wrap parameters, giving priority to arguments.
403
+ * If a wrap command is provided, it will be executed **after** the main command handler.
404
+ *
405
+ * @param name - The command name
406
+ * @param config - The command configuration (options, args, etc.)
407
+ * @param handler - The main command handler function
408
+ * @param completionMessage - Optional message to log when the command completes
409
+ * @returns A Command with integrated wrap functionality
410
+ */
411
+ const makeCommandWithWrap = <Name extends string, const Config, R, E>(
412
+ name: Name,
413
+ config: Config,
414
+ handler: (_: any) => Effect.Effect<void, E, R>,
415
+ completionMessage?: string
416
+ ) =>
417
+ Command.make(
418
+ name,
419
+ { ...config, wo: WrapAsOption, wa: WrapAsArg },
420
+ Effect.fn("effa-cli.withWrapHandler")(function*(_) {
421
+ const { wa, wo, ...cfg } = _ as unknown as {
422
+ wo: Option.Option<string>
423
+ wa: Option.Option<ReadonlyArray<string>>
424
+ }
332
425
 
333
- pkgJson.exports = packageExports
426
+ if (completionMessage) {
427
+ yield* Effect.addFinalizer(() => Effect.logInfo(completionMessage))
428
+ }
334
429
 
335
- yield* Effect.logInfo(`Writing updated package.json for ${p}`)
430
+ const wrapOption = Option.orElse(wa, () => wo)
336
431
 
337
- return yield* fs.writeFileString(
338
- p + "/package.json",
339
- JSON.stringify(pkgJson, null, 2)
340
- )
341
- }
342
- )
432
+ yield* handler(cfg as any)
343
433
 
344
- /**
345
- * Monitors a directory for TypeScript file changes and automatically updates package.json exports.
346
- * Generates initial package.json exports, then watches the src directory for changes to regenerate exports.
347
- *
348
- * @param watchPath - The directory path containing the package.json and src to monitor
349
- * @param levels - Optional depth limit for export filtering (0 = no limit)
350
- * @returns An Effect that sets up package.json monitoring
351
- */
352
- const monitorPackageJson = Effect.fn("effa-cli.monitorPackageJson")(
353
- function*(startDir: string, watchPath: string, levels = 0) {
354
- yield* packagejsonUpdater(startDir, watchPath, levels)
355
-
356
- const srcPath = watchPath === "." ? "./src" : `${watchPath}/src`
357
-
358
- if (!(yield* fs.exists(srcPath))) {
359
- yield* Effect.logWarning(`Source directory ${srcPath} does not exist - skipping monitoring`)
360
- return
361
- }
434
+ if (Option.isSome(wrapOption)) {
435
+ const val: string = Array.isArray(wrapOption.value)
436
+ ? wrapOption.value.join(" ")
437
+ : wrapOption.value as string
362
438
 
363
- const watchStream = fs.watch(srcPath, { recursive: true })
439
+ yield* Effect.logInfo(`Spawning child command: ${val}`)
440
+ const exitCode = yield* runGetExitCode(val)
441
+ if (exitCode !== 0) {
442
+ return yield* Effect.sync(() => process.exit(exitCode))
443
+ }
444
+ }
364
445
 
365
- yield* watchStream.pipe(
366
- Stream.runForEach(
367
- Effect.fn("effa-cli.monitorPackageJson.handleEvent")(function*(_) {
368
- yield* packagejsonUpdater(startDir, watchPath, levels)
369
- })
370
- ),
371
- Effect.andThen(
372
- Effect.addFinalizer(() => Effect.logInfo(`Stopped monitoring package.json for: ${watchPath}`))
373
- ),
374
- Effect.forkScoped
446
+ return
447
+ }, (_) => Effect.scoped(_))
375
448
  )
376
- }
377
- )
378
-
379
- /*
380
- * CLI
381
- */
382
-
383
- const WrapAsOption = Options.text("wrap").pipe(
384
- Options.withAlias("w"),
385
- Options.optional,
386
- Options.withDescription(
387
- "Wrap child bash command: the lifetime of the CLI command will be tied to the child process"
388
- )
389
- )
390
449
 
391
- // has prio over WrapAsOption
392
- const WrapAsArg = Args
393
- .text({
394
- name: "wrap"
395
- })
396
- .pipe(
397
- Args.atLeast(1),
398
- Args.optional,
399
- Args.withDescription(
400
- "Wrap child bash command: the lifetime of the CLI command will be tied to the child process"
450
+ const EffectAppLibsPath = Argument
451
+ .directory("effect-app-libs-path", { mustExist: true })
452
+ .pipe(
453
+ Argument.withDefault("../../effect-app/libs"),
454
+ Argument.withDescription("Path to the effect-app-libs directory")
401
455
  )
402
- )
403
456
 
404
- /**
405
- * Creates a command that automatically includes wrap functionality for executing child bash commands.
406
- * Combines both option-based (--wrap) and argument-based wrap parameters, giving priority to arguments.
407
- * If a wrap command is provided, it will be executed **after** the main command handler.
408
- *
409
- * @param name - The command name
410
- * @param config - The command configuration (options, args, etc.)
411
- * @param handler - The main command handler function
412
- * @param completionMessage - Optional message to log when the command completes
413
- * @returns A Command with integrated wrap functionality
414
- */
415
- const makeCommandWithWrap = <Name extends string, const Config extends Command.Command.Config, R, E>(
416
- name: Name,
417
- config: Config,
418
- handler: (_: Types.Simplify<Command.Command.ParseConfig<Config>>) => Effect.Effect<void, E, R>,
419
- completionMessage?: string
420
- ): Command.Command<
421
- Name,
422
- CommandExecutor | R,
423
- PlatformError | E,
424
- Types.Simplify<Command.Command.ParseConfig<Config>>
425
- > =>
426
- Command.make(
427
- name,
428
- { ...config, wo: WrapAsOption, wa: WrapAsArg },
429
- Effect.fn("effa-cli.withWrapHandler")(function*(_) {
430
- const { wa, wo, ...cfg } = _ as unknown as {
431
- wo: Option.Option<string>
432
- wa: Option.Option<[string, ...string[]]>
433
- } & Types.Simplify<Command.Command.ParseConfig<Config>>
434
-
435
- if (completionMessage) {
436
- yield* Effect.addFinalizer(() => Effect.logInfo(completionMessage))
437
- }
457
+ const link = Command
458
+ .make(
459
+ "link",
460
+ { effectAppLibsPath: EffectAppLibsPath },
461
+ Effect.fn("effa-cli.link")(function*({ effectAppLibsPath }) {
462
+ return yield* linkPackages(effectAppLibsPath)
463
+ })
464
+ )
465
+ .pipe(Command.withDescription("Link local effect-app packages using file resolutions"))
466
+
467
+ const unlink = Command
468
+ .make(
469
+ "unlink",
470
+ {},
471
+ Effect.fn("effa-cli.unlink")(function*({}) {
472
+ return yield* unlinkPackages
473
+ })
474
+ )
475
+ .pipe(Command.withDescription("Remove effect-app file resolutions and restore npm registry packages"))
476
+
477
+ const ue = Command
478
+ .make(
479
+ "ue",
480
+ {},
481
+ Effect.fn("effa-cli.ue")(function*({}) {
482
+ yield* Effect.logInfo("Update effect-app and/or effect packages")
483
+
484
+ const prompted = yield* Prompt.select({
485
+ choices: [
486
+ {
487
+ title: "effect-app",
488
+ description: "Update only effect-app packages",
489
+ value: "effect-app"
490
+ },
491
+ {
492
+ title: "effect",
493
+ description: "Update only effect packages",
494
+ value: "effect"
495
+ },
496
+ {
497
+ title: "both",
498
+ description: "Update both effect-app and effect packages",
499
+ value: "both"
500
+ }
501
+ ],
502
+ message: "Select an option"
503
+ })
438
504
 
439
- const wrapOption = Option.orElse(wa, () => wo)
505
+ switch (prompted) {
506
+ case "effect-app":
507
+ return yield* updateEffectAppPackages.pipe(
508
+ Effect.andThen(runGetExitCode("pnpm i"))
509
+ )
510
+
511
+ case "effect":
512
+ return yield* updateEffectPackages.pipe(
513
+ Effect.andThen(runGetExitCode("pnpm i"))
514
+ )
515
+ case "both":
516
+ return yield* updateEffectPackages.pipe(
517
+ Effect.andThen(updateEffectAppPackages),
518
+ Effect.andThen(runGetExitCode("pnpm i"))
519
+ )
520
+ }
521
+ })
522
+ )
523
+ .pipe(Command.withDescription("Update effect-app and/or effect packages"))
440
524
 
441
- yield* handler(cfg as any)
525
+ const up = Command
526
+ .make(
527
+ "up",
528
+ {},
529
+ Effect.fn("effa-cli.update-packages")(function*({}) {
530
+ yield* Effect.logInfo("Updating all packages except Effect/Effect-App ecosystem packages...")
442
531
 
443
- if (Option.isSome(wrapOption)) {
444
- const val = Array.isArray(wrapOption.value)
445
- ? wrapOption.value.join(" ")
446
- : wrapOption.value
532
+ return yield* updatePackages.pipe(
533
+ Effect.andThen(runGetExitCode("pnpm i"))
534
+ )
535
+ })
536
+ )
537
+ .pipe(Command.withDescription("Update all packages except Effect/Effect-App ecosystem packages"))
447
538
 
448
- yield* Effect.logInfo(`Spawning child command: ${val}`)
449
- const exitCode = yield* runGetExitCode(val)
450
- if (exitCode !== 0) {
451
- return yield* Effect.sync(() => process.exit(exitCode))
539
+ const indexMulti = makeCommandWithWrap(
540
+ "index-multi",
541
+ {},
542
+ Effect.fn("effa-cli.index-multi")(function*({}) {
543
+ yield* Effect.logInfo("Starting multi-index monitoring")
544
+
545
+ const dirs = ["./api/src"]
546
+
547
+ const existingDirs: string[] = []
548
+ for (const dir of dirs) {
549
+ const dirExists = yield* fs.exists(dir)
550
+ if (dirExists) {
551
+ existingDirs.push(dir)
552
+ } else {
553
+ yield* Effect.logWarning(`Directory ${dir} does not exist - skipping`)
452
554
  }
453
555
  }
454
556
 
455
- return
456
- }, (_) => Effect.scoped(_))
457
- )
458
-
459
- const EffectAppLibsPath = Args
460
- .directory({
461
- exists: "yes",
462
- name: "effect-app-libs-path"
463
- })
464
- .pipe(
465
- Args.withDefault("../../effect-app/libs"),
466
- Args.withDescription("Path to the effect-app-libs directory")
467
- )
468
-
469
- const link = Command
470
- .make(
471
- "link",
472
- { effectAppLibsPath: EffectAppLibsPath },
473
- Effect.fn("effa-cli.link")(function*({ effectAppLibsPath }) {
474
- return yield* linkPackages(effectAppLibsPath)
475
- })
557
+ const monitors = existingDirs.map((dir) => monitorIndexes(dir))
558
+ yield* Effect.all(monitors, { concurrency: monitors.length })
559
+ }),
560
+ "Stopped multi-index monitoring"
476
561
  )
477
- .pipe(Command.withDescription("Link local effect-app packages using file resolutions"))
562
+ .pipe(
563
+ Command.withDescription(
564
+ "Monitor multiple directories for index and controller file changes"
565
+ )
566
+ )
478
567
 
479
- const unlink = Command
480
- .make(
481
- "unlink",
568
+ const packagejson = makeCommandWithWrap(
569
+ "packagejson",
482
570
  {},
483
- Effect.fn("effa-cli.unlink")(function*({}) {
484
- return yield* unlinkPackages
485
- })
486
- )
487
- .pipe(Command.withDescription("Remove effect-app file resolutions and restore npm registry packages"))
571
+ Effect.fn("effa-cli.packagejson")(function*({}) {
572
+ // https://nodejs.org/api/path.html#pathresolvepaths
573
+ const startDir = path.resolve()
488
574
 
489
- const ue = Command
490
- .make(
491
- "ue",
492
- {},
493
- Effect.fn("effa-cli.ue")(function*({}) {
494
- yield* Effect.logInfo("Update effect-app and/or effect packages")
495
-
496
- const prompted = yield* Prompt.select({
497
- choices: [
498
- {
499
- title: "effect-app",
500
- description: "Update only effect-app packages",
501
- value: "effect-app"
502
- },
503
- {
504
- title: "effect",
505
- description: "Update only effect packages",
506
- value: "effect"
507
- },
508
- {
509
- title: "both",
510
- description: "Update both effect-app and effect packages",
511
- value: "both"
512
- }
513
- ],
514
- message: "Select an option"
515
- })
516
-
517
- switch (prompted) {
518
- case "effect-app":
519
- return yield* updateEffectAppPackages.pipe(
520
- Effect.andThen(runGetExitCode("pnpm i"))
521
- )
522
-
523
- case "effect":
524
- return yield* updateEffectPackages.pipe(
525
- Effect.andThen(runGetExitCode("pnpm i"))
526
- )
527
- case "both":
528
- return yield* updateEffectPackages.pipe(
529
- Effect.andThen(updateEffectAppPackages),
530
- Effect.andThen(runGetExitCode("pnpm i"))
531
- )
532
- }
533
- })
575
+ return yield* monitorPackageJson(startDir, ".")
576
+ }),
577
+ "Stopped monitoring root package.json exports"
534
578
  )
535
- .pipe(Command.withDescription("Update effect-app and/or effect packages"))
579
+ .pipe(
580
+ Command.withDescription("Generate and update root-level package.json exports mappings for TypeScript modules")
581
+ )
536
582
 
537
- const up = Command
538
- .make(
539
- "up",
583
+ const packagejsonPackages = makeCommandWithWrap(
584
+ "packagejson-packages",
540
585
  {},
541
- Effect.fn("effa-cli.update-packages")(function*({}) {
542
- yield* Effect.logInfo("Updating all packages except Effect/Effect-App ecosystem packages...")
543
-
544
- return yield* updatePackages.pipe(
545
- Effect.andThen(runGetExitCode("pnpm i"))
546
- )
547
- })
548
- )
549
- .pipe(Command.withDescription("Update all packages except Effect/Effect-App ecosystem packages"))
586
+ Effect.fn("effa-cli.packagejson-packages")(function*({}) {
587
+ // https://nodejs.org/api/path.html#pathresolvepaths
588
+ const startDir = path.resolve()
550
589
 
551
- const indexMulti = makeCommandWithWrap(
552
- "index-multi",
553
- {},
554
- Effect.fn("effa-cli.index-multi")(function*({}) {
555
- yield* Effect.logInfo("Starting multi-index monitoring")
590
+ const packagesDir = path.join(startDir, "packages")
556
591
 
557
- const dirs = ["./api/src"]
558
-
559
- const existingDirs: string[] = []
560
- for (const dir of dirs) {
561
- const dirExists = yield* fs.exists(dir)
562
- if (dirExists) {
563
- existingDirs.push(dir)
564
- } else {
565
- yield* Effect.logWarning(`Directory ${dir} does not exist - skipping`)
592
+ const packagesExists = yield* fs.exists(packagesDir)
593
+ if (!packagesExists) {
594
+ return yield* Effect.logWarning("No packages directory found")
566
595
  }
567
- }
568
-
569
- const monitors = existingDirs.map((dir) => monitorIndexes(dir))
570
- yield* Effect.all(monitors, { concurrency: monitors.length })
571
- }),
572
- "Stopped multi-index monitoring"
573
- )
574
- .pipe(
575
- Command.withDescription(
576
- "Monitor multiple directories for index and controller file changes"
577
- )
578
- )
579
596
 
580
- const packagejson = makeCommandWithWrap(
581
- "packagejson",
582
- {},
583
- Effect.fn("effa-cli.packagejson")(function*({}) {
584
- // https://nodejs.org/api/path.html#pathresolvepaths
585
- const startDir = path.resolve()
597
+ // get all package directories
598
+ const packageDirs = yield* fs.readDirectory(packagesDir)
586
599
 
587
- return yield* monitorPackageJson(startDir, ".")
588
- }),
589
- "Stopped monitoring root package.json exports"
590
- )
591
- .pipe(
592
- Command.withDescription("Generate and update root-level package.json exports mappings for TypeScript modules")
593
- )
600
+ const validPackages: string[] = []
594
601
 
595
- const packagejsonPackages = makeCommandWithWrap(
596
- "packagejson-packages",
597
- {},
598
- Effect.fn("effa-cli.packagejson-packages")(function*({}) {
599
- // https://nodejs.org/api/path.html#pathresolvepaths
600
- const startDir = path.resolve()
602
+ // filter packages that have package.json and src directory
603
+ for (const packageName of packageDirs) {
604
+ const packagePath = path.join(packagesDir, packageName)
605
+ const packageJsonExists = yield* fs.exists(path.join(packagePath, "package.json"))
606
+ const srcExists = yield* fs.exists(path.join(packagePath, "src"))
601
607
 
602
- const packagesDir = path.join(startDir, "packages")
608
+ const shouldExclude = false
609
+ || packageName.endsWith("eslint-codegen-model")
610
+ || packageName.endsWith("eslint-shared-config")
611
+ || packageName.endsWith("vue-components")
603
612
 
604
- const packagesExists = yield* fs.exists(packagesDir)
605
- if (!packagesExists) {
606
- return yield* Effect.logWarning("No packages directory found")
607
- }
608
-
609
- // get all package directories
610
- const packageDirs = yield* fs.readDirectory(packagesDir)
611
-
612
- const validPackages: string[] = []
613
-
614
- // filter packages that have package.json and src directory
615
- for (const packageName of packageDirs) {
616
- const packagePath = path.join(packagesDir, packageName)
617
- const packageJsonExists = yield* fs.exists(path.join(packagePath, "package.json"))
618
- const srcExists = yield* fs.exists(path.join(packagePath, "src"))
619
-
620
- const shouldExclude = false
621
- || packageName.endsWith("eslint-codegen-model")
622
- || packageName.endsWith("eslint-shared-config")
623
- || packageName.endsWith("vue-components")
624
-
625
- if (packageJsonExists && srcExists && !shouldExclude) {
626
- validPackages.push(packagePath)
613
+ if (packageJsonExists && srcExists && !shouldExclude) {
614
+ validPackages.push(packagePath)
615
+ }
627
616
  }
628
- }
629
617
 
630
- yield* Effect.logInfo(`Found ${validPackages.length} packages to update`)
618
+ yield* Effect.logInfo(`Found ${validPackages.length} packages to update`)
631
619
 
632
- // update each package sequentially
633
- yield* Effect.all(
634
- validPackages.map(
635
- Effect.fnUntraced(function*(packagePath) {
636
- const relativePackagePath = path.relative(startDir, packagePath)
637
- yield* Effect.logInfo(`Updating ${relativePackagePath}`)
638
- return yield* monitorPackageJson(startDir, relativePackagePath)
639
- })
620
+ // update each package sequentially
621
+ yield* Effect.all(
622
+ validPackages.map(
623
+ Effect.fnUntraced(function*(packagePath) {
624
+ const relativePackagePath = path.relative(startDir, packagePath)
625
+ yield* Effect.logInfo(`Updating ${relativePackagePath}`)
626
+ return yield* monitorPackageJson(startDir, relativePackagePath)
627
+ })
628
+ )
640
629
  )
641
- )
642
630
 
643
- yield* Effect.logInfo("All packages updated successfully")
644
- }),
645
- "Stopped monitoring package.json exports for all packages"
646
- )
647
- .pipe(
648
- Command.withDescription("Generate and update package.json exports mappings for all packages in monorepo")
649
- )
650
-
651
- const gist = Command
652
- .make(
653
- "gist",
654
- {
655
- config: Options.file("config").pipe(
656
- Options.withDefault("gists.yaml"),
657
- Options.withDescription("Path to YAML configuration file")
658
- )
659
- },
660
- Effect.fn("effa-cli.gist")(function*({ config }) {
661
- return yield* GistHandler.handler({
662
- YAMLPath: config
663
- })
664
- })
631
+ yield* Effect.logInfo("All packages updated successfully")
632
+ }),
633
+ "Stopped monitoring package.json exports for all packages"
665
634
  )
666
- .pipe(Command.withDescription("Create GitHub gists from files specified in YAML configuration"))
667
-
668
- const nuke = Command
669
- .make(
670
- "nuke",
671
- {
672
- dryRun: Options.boolean("dry-run").pipe(
673
- Options.withDescription("Show what would be done without making changes")
674
- ),
675
- storePrune: Options.boolean("store-prune").pipe(
676
- Options.withDescription("Prune the package manager store")
677
- )
678
- },
679
- Effect.fn("effa-cli.nuke")(function*({ dryRun, storePrune }) {
680
- yield* Effect.logInfo(dryRun ? "Performing dry run cleanup..." : "Performing nuclear cleanup...")
635
+ .pipe(
636
+ Command.withDescription("Generate and update package.json exports mappings for all packages in monorepo")
637
+ )
681
638
 
682
- if (dryRun) {
683
- yield* runGetExitCode(
684
- "find . -depth \\( -type d \\( -name 'node_modules' -o -name '.nuxt' -o -name 'dist' -o -name '.output' -o -name '.nitro' -o -name '.cache' -o -name 'test-results' -o -name 'test-out' -o -name 'coverage' \\) -print \\) -o \\( -type f \\( -name '*.log' -o -name '*.tsbuildinfo' \\) -print \\)"
639
+ const gist = Command
640
+ .make(
641
+ "gist",
642
+ {
643
+ config: Flag.file("config").pipe(
644
+ Flag.withDefault("gists.yaml"),
645
+ Flag.withDescription("Path to YAML configuration file")
685
646
  )
686
- } else {
687
- yield* runGetExitCode(
688
- "find . -depth \\( -type d \\( -name 'node_modules' -o -name '.nuxt' -o -name 'dist' -o -name '.output' -o -name '.nitro' -o -name '.cache' -o -name 'test-results' -o -name 'test-out' -o -name 'coverage' \\) -exec rm -rf -- {} + \\) -o \\( -type f \\( -name '*.log' -o -name '*.tsbuildinfo' \\) -delete \\)"
647
+ },
648
+ Effect.fn("effa-cli.gist")(function*({ config }) {
649
+ const gh = yield* GistHandler
650
+ return yield* gh.handler({
651
+ YAMLPath: config
652
+ })
653
+ })
654
+ )
655
+ .pipe(Command.withDescription("Create GitHub gists from files specified in YAML configuration"))
656
+
657
+ const nuke = Command
658
+ .make(
659
+ "nuke",
660
+ {
661
+ dryRun: Flag.boolean("dry-run").pipe(
662
+ Flag.withDescription("Show what would be done without making changes")
663
+ ),
664
+ storePrune: Flag.boolean("store-prune").pipe(
665
+ Flag.withDescription("Prune the package manager store")
689
666
  )
667
+ },
668
+ Effect.fn("effa-cli.nuke")(function*({ dryRun, storePrune }) {
669
+ yield* Effect.logInfo(dryRun ? "Performing dry run cleanup..." : "Performing nuclear cleanup...")
690
670
 
691
- if (storePrune) {
671
+ if (dryRun) {
692
672
  yield* runGetExitCode(
693
- "pnpm store prune"
673
+ "find . -depth \\( -type d \\( -name 'node_modules' -o -name '.nuxt' -o -name 'dist' -o -name '.output' -o -name '.nitro' -o -name '.cache' -o -name 'test-results' -o -name 'test-out' -o -name 'coverage' \\) -print \\) -o \\( -type f \\( -name '*.log' -o -name '*.tsbuildinfo' \\) -print \\)"
694
674
  )
675
+ } else {
676
+ yield* runGetExitCode(
677
+ "find . -depth \\( -type d \\( -name 'node_modules' -o -name '.nuxt' -o -name 'dist' -o -name '.output' -o -name '.nitro' -o -name '.cache' -o -name 'test-results' -o -name 'test-out' -o -name 'coverage' \\) -exec rm -rf -- {} + \\) -o \\( -type f \\( -name '*.log' -o -name '*.tsbuildinfo' \\) -delete \\)"
678
+ )
679
+
680
+ if (storePrune) {
681
+ yield* runGetExitCode(
682
+ "pnpm store prune"
683
+ )
684
+ }
695
685
  }
696
- }
697
686
 
698
- yield* Effect.logInfo("Cleanup operation completed")
699
- })
687
+ yield* Effect.logInfo("Cleanup operation completed")
688
+ })
689
+ )
690
+ .pipe(Command.withDescription("Nuclear cleanup command: removes all generated files and cleans the workspace"))
691
+
692
+ // configure CLI
693
+ return yield* Command.run(
694
+ Command
695
+ .make("effa")
696
+ .pipe(Command.withSubcommands([
697
+ ue,
698
+ up,
699
+ link,
700
+ unlink,
701
+ indexMulti,
702
+ packagejson,
703
+ packagejsonPackages,
704
+ gist,
705
+ nuke
706
+ ])),
707
+ {
708
+ version: "v1.0.0"
709
+ }
700
710
  )
701
- .pipe(Command.withDescription("Nuclear cleanup command: removes all generated files and cleans the workspace"))
702
-
703
- // configure CLI
704
- const cli = Command.run(
705
- Command
706
- .make("effa")
707
- .pipe(Command.withSubcommands([
708
- ue,
709
- up,
710
- link,
711
- unlink,
712
- indexMulti,
713
- packagejson,
714
- packagejsonPackages,
715
- gist,
716
- nuke
717
- ])),
718
- {
719
- name: "Effect-App CLI by jfet97 ❤️",
720
- version: "v1.0.0"
721
- }
722
- )
723
-
724
- return yield* cli(process.argv)
725
- })()
726
- .pipe(
727
- Effect.scoped,
728
- Effect.provide(
729
- Layer.provideMerge(
730
- Layer.merge(
731
- GistHandler.Default,
732
- RunCommandService.Default
733
- ),
734
- NodeContext.layer
711
+ })()
712
+ .pipe(
713
+ Effect.scoped,
714
+ Effect.provide(
715
+ Layer.provideMerge(
716
+ Layer.merge(
717
+ GistHandler.Default,
718
+ RunCommandService.Default
719
+ ),
720
+ NodeServices.layer
721
+ )
735
722
  )
736
- ),
737
- NodeRuntime.runMain
738
- )
723
+ )
724
+ )