@effect-app/cli 1.23.0 → 1.23.2
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/CHANGELOG.md +12 -0
- package/dist/extract.d.ts +15 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +52 -0
- package/dist/index.js +465 -204
- package/dist/old.d.ts +2 -0
- package/dist/old.d.ts.map +1 -0
- package/dist/old.js +246 -0
- package/package.json +15 -5
- package/src/extract.ts +71 -0
- package/src/index.ts +663 -229
- package/src/old.ts +283 -0
- package/vitest.config.ts.timestamp-1709838404819-f2fb28517168c.mjs +0 -33
- package/vitest.config.ts.timestamp-1709838418683-9c399c96f9d78.mjs +0 -33
- package/vitest.config.ts.timestamp-1709838649058-0e8f9431c893d.mjs +0 -33
- package/vitest.config.ts.timestamp-1711724061889-4985ba59def8.mjs +0 -37
- package/vitest.config.ts.timestamp-1711743471019-3c5e0c6ca2188.mjs +0 -37
- package/vitest.config.ts.timestamp-1711743489536-5ca18d3f67759.mjs +0 -37
- package/vitest.config.ts.timestamp-1711743593444-e40a8dcd4fc31.mjs +0 -37
- package/vitest.config.ts.timestamp-1711744615239-6a156fd39b9c9.mjs +0 -37
- package/wallaby.cjs +0 -1
package/src/index.ts
CHANGED
|
@@ -1,241 +1,675 @@
|
|
|
1
|
-
/* eslint-disable
|
|
2
|
-
/* eslint-disable
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
1
|
+
/* eslint-disable no-constant-binary-expression */
|
|
2
|
+
/* eslint-disable no-empty-pattern */
|
|
3
|
+
// import necessary modules from the libraries
|
|
4
|
+
import { Args, Command, Options, Prompt } from "@effect/cli"
|
|
5
|
+
import { Command as NodeCommand, FileSystem, Path } from "@effect/platform"
|
|
6
|
+
import { NodeContext, NodeRuntime } from "@effect/platform-node"
|
|
7
|
+
import { Effect, identity, Stream } from "effect"
|
|
8
|
+
import { ExtractExportMappingsService } from "./extract.js"
|
|
9
|
+
import { packages } from "./shared.js"
|
|
10
|
+
|
|
11
|
+
Effect
|
|
12
|
+
.fn("effa-cli")(function*() {
|
|
13
|
+
const fs = yield* FileSystem.FileSystem
|
|
14
|
+
const path = yield* Path.Path
|
|
15
|
+
const extractExportMappings = yield* ExtractExportMappingsService
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Executes a shell command using Node.js Command API with inherited stdio streams.
|
|
19
|
+
* The command is run through the system shell (/bin/sh) for proper command parsing.
|
|
20
|
+
*
|
|
21
|
+
* @param cmd - The shell command to execute
|
|
22
|
+
* @param cwd - Optional working directory to execute the command in
|
|
23
|
+
* @returns An Effect that succeeds with the exit code or fails with a PlatformError
|
|
24
|
+
*/
|
|
25
|
+
const runNodeCommand = (cmd: string, cwd?: string) =>
|
|
26
|
+
NodeCommand
|
|
27
|
+
.make("sh", "-c", cmd)
|
|
28
|
+
.pipe(
|
|
29
|
+
NodeCommand.stdout("inherit"),
|
|
30
|
+
NodeCommand.stderr("inherit"),
|
|
31
|
+
cwd ? NodeCommand.workingDirectory(cwd) : identity,
|
|
32
|
+
NodeCommand.exitCode
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Executes a bash script file using Node.js Command API with inherited stdio streams.
|
|
37
|
+
* The script file is executed directly through the shell (/bin/sh).
|
|
38
|
+
*
|
|
39
|
+
* @param file - The path to the bash script file to execute
|
|
40
|
+
* @param cwd - Optional working directory to execute the script in
|
|
41
|
+
* @returns An Effect that succeeds with the output or fails with a PlatformError
|
|
42
|
+
*/
|
|
43
|
+
// const runBashFile = (file: string, cwd?: string) =>
|
|
44
|
+
// NodeCommand
|
|
45
|
+
// .make("sh", file)
|
|
46
|
+
// .pipe(
|
|
47
|
+
// NodeCommand.stdout("inherit"),
|
|
48
|
+
// NodeCommand.stderr("inherit"),
|
|
49
|
+
// cwd ? NodeCommand.workingDirectory(cwd) : identity,
|
|
50
|
+
// NodeCommand.string
|
|
51
|
+
// )
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Creates a file if it doesn't exist or updates the access and modification times of an existing file.
|
|
55
|
+
* This is the effectful equivalent of the Unix `touch` command.
|
|
56
|
+
*
|
|
57
|
+
* @param path - The path to the file to touch
|
|
58
|
+
* @returns An Effect that succeeds with void or fails with a FileSystem error
|
|
59
|
+
*/
|
|
60
|
+
const touch = Effect.fn("touch")(function*(path: string) {
|
|
61
|
+
const time = new Date()
|
|
62
|
+
|
|
63
|
+
yield* fs.utimes(path, time, time).pipe(
|
|
64
|
+
Effect.catchTag("SystemError", (err) =>
|
|
65
|
+
err.reason === "NotFound"
|
|
66
|
+
? fs.writeFileString(path, "")
|
|
67
|
+
: Effect.fail(err))
|
|
68
|
+
)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Updates effect-app packages to their latest versions using npm-check-updates.
|
|
73
|
+
* Runs both at workspace root and recursively in all workspace packages.
|
|
74
|
+
*/
|
|
75
|
+
const updateEffectAppPackages = Effect.fn("effa-cli.ue.updateEffectAppPackages")(function*() {
|
|
76
|
+
const filters = ["effect-app", "@effect-app/*"]
|
|
77
|
+
for (const filter of filters) {
|
|
78
|
+
yield* runNodeCommand(`pnpm exec ncu -u --filter "${filter}"`)
|
|
79
|
+
yield* runNodeCommand(`pnpm -r exec ncu -u --filter "${filter}"`)
|
|
80
|
+
}
|
|
81
|
+
})()
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Updates Effect ecosystem packages to their latest versions using npm-check-updates.
|
|
85
|
+
* Covers core Effect packages, Effect ecosystem packages, and Effect Atom packages.
|
|
86
|
+
* Runs both at workspace root and recursively in all workspace packages.
|
|
87
|
+
*/
|
|
88
|
+
const updateEffectPackages = Effect.fn("effa-cli.ue.updateEffectPackages")(function*() {
|
|
89
|
+
const effectFilters = ["effect", "@effect/*", "@effect-atom/*"]
|
|
90
|
+
for (const filter of effectFilters) {
|
|
91
|
+
yield* runNodeCommand(`pnpm exec ncu -u --filter "${filter}"`)
|
|
92
|
+
yield* runNodeCommand(`pnpm -r exec ncu -u --filter "${filter}"`)
|
|
93
|
+
}
|
|
94
|
+
})()
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Links local effect-app packages by adding file resolutions to package.json.
|
|
98
|
+
* Updates the package.json with file: protocol paths pointing to the local effect-app-libs directory,
|
|
99
|
+
* then runs pnpm install to apply the changes.
|
|
100
|
+
*
|
|
101
|
+
* @param effectAppLibsPath - Path to the local effect-app-libs directory
|
|
102
|
+
* @returns An Effect that succeeds when linking is complete
|
|
103
|
+
*/
|
|
104
|
+
const linkPackages = Effect.fnUntraced(function*(effectAppLibsPath: string) {
|
|
105
|
+
yield* Effect.log("Linking local effect-app packages...")
|
|
106
|
+
|
|
107
|
+
const packageJsonPath = "./package.json"
|
|
108
|
+
const packageJsonContent = yield* fs.readFileString(packageJsonPath)
|
|
109
|
+
const pj = JSON.parse(packageJsonContent)
|
|
110
|
+
|
|
111
|
+
const resolutions = {
|
|
112
|
+
...pj.resolutions,
|
|
113
|
+
"@effect-app/eslint-codegen-model": "file:" + effectAppLibsPath + "/packages/eslint-codegen-model",
|
|
114
|
+
"effect-app": "file:" + effectAppLibsPath + "/packages/effect-app",
|
|
115
|
+
"@effect-app/infra": "file:" + effectAppLibsPath + "/packages/infra",
|
|
116
|
+
"@effect-app/vue": "file:" + effectAppLibsPath + "/packages/vue",
|
|
117
|
+
"@effect-app/vue-components": "file:" + effectAppLibsPath + "/packages/vue-components",
|
|
118
|
+
...packages.reduce((acc, p) => ({ ...acc, [p]: `file:${effectAppLibsPath}/node_modules/${p}` }), {})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
pj.resolutions = resolutions
|
|
122
|
+
|
|
123
|
+
yield* fs.writeFileString(packageJsonPath, JSON.stringify(pj, null, 2))
|
|
124
|
+
yield* Effect.log("Updated package.json with local file resolutions")
|
|
125
|
+
|
|
126
|
+
yield* runNodeCommand("pnpm i")
|
|
127
|
+
|
|
128
|
+
yield* Effect.log("Successfully linked local packages")
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Unlinks local effect-app packages by removing file resolutions from package.json.
|
|
133
|
+
* Filters out all effect-app related file: protocol resolutions from package.json,
|
|
134
|
+
* then runs pnpm install to restore registry packages.
|
|
135
|
+
*
|
|
136
|
+
* @returns An Effect that succeeds when unlinking is complete
|
|
137
|
+
*/
|
|
138
|
+
const unlinkPackages = Effect.fnUntraced(function*() {
|
|
139
|
+
yield* Effect.log("Unlinking local effect-app packages...")
|
|
140
|
+
|
|
141
|
+
const packageJsonPath = "./package.json"
|
|
142
|
+
const packageJsonContent = yield* fs.readFileString(packageJsonPath)
|
|
143
|
+
const pj = JSON.parse(packageJsonContent)
|
|
144
|
+
|
|
145
|
+
const filteredResolutions = Object.entries(pj.resolutions as Record<string, string>).reduce(
|
|
146
|
+
(acc, [k, v]) => {
|
|
147
|
+
if (k.startsWith("@effect-app/") || k === "effect-app" || packages.includes(k)) return acc
|
|
148
|
+
acc[k] = v
|
|
149
|
+
return acc
|
|
150
|
+
},
|
|
151
|
+
{} as Record<string, string>
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
pj.resolutions = filteredResolutions
|
|
155
|
+
|
|
156
|
+
yield* fs.writeFileString(packageJsonPath, JSON.stringify(pj, null, 2))
|
|
157
|
+
yield* Effect.log("Removed effect-app file resolutions from package.json")
|
|
158
|
+
|
|
159
|
+
yield* runNodeCommand("pnpm i")
|
|
160
|
+
yield* Effect.log("Successfully unlinked local packages")
|
|
161
|
+
})()
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Monitors controller files for changes and runs eslint on related controllers.ts/routes.ts files.
|
|
165
|
+
* Watches for .controllers. files and triggers eslint fixes on parent directory's controller files.
|
|
166
|
+
*
|
|
167
|
+
* @param watchPath - The path to watch for controller changes
|
|
168
|
+
* @param debug - Whether to enable debug logging
|
|
169
|
+
* @returns An Effect that sets up controller file monitoring
|
|
170
|
+
*/
|
|
171
|
+
const monitorChildIndexes = Effect.fn("effa-cli.index-multi.monitorChildIndexes")(
|
|
172
|
+
function*(watchPath: string, debug: boolean) {
|
|
173
|
+
const fileSystem = yield* FileSystem.FileSystem
|
|
174
|
+
|
|
175
|
+
if (debug) {
|
|
176
|
+
yield* Effect.logInfo(`Starting controller monitoring for: ${watchPath}`)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const watchStream = fileSystem.watch(watchPath, { recursive: true })
|
|
180
|
+
|
|
181
|
+
yield* watchStream.pipe(
|
|
182
|
+
Stream.runForEach(
|
|
183
|
+
Effect.fn("effa-cli.monitorChildIndexes.handleEvent")(function*(event) {
|
|
184
|
+
const pathParts = event.path.split("/")
|
|
185
|
+
const fileName = pathParts[pathParts.length - 1]
|
|
186
|
+
const isController = fileName?.toLowerCase().includes(".controllers.")
|
|
187
|
+
|
|
188
|
+
if (!isController) return
|
|
189
|
+
|
|
190
|
+
let i = 1
|
|
191
|
+
const reversedParts = pathParts.toReversed()
|
|
192
|
+
|
|
193
|
+
while (i < reversedParts.length) {
|
|
194
|
+
const candidateFiles = ["controllers.ts", "routes.ts"]
|
|
195
|
+
.map((f) => [...pathParts.slice(0, pathParts.length - i), f].join("/"))
|
|
196
|
+
|
|
197
|
+
const existingFiles: string[] = []
|
|
198
|
+
for (const file of candidateFiles) {
|
|
199
|
+
const exists = yield* fileSystem.exists(file)
|
|
200
|
+
if (exists) existingFiles.push(file)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (existingFiles.length > 0) {
|
|
204
|
+
if (debug) {
|
|
205
|
+
yield* Effect.logInfo(
|
|
206
|
+
`Controller change detected: ${event.path}, fixing files: ${existingFiles.join(", ")}`
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const eslintArgs = existingFiles.map((f) => `"../${f}"`).join(" ")
|
|
211
|
+
yield* runNodeCommand(`cd api && pnpm eslint --fix ${eslintArgs}`)
|
|
212
|
+
break
|
|
213
|
+
}
|
|
214
|
+
i++
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
)
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Monitors a directory for changes and runs eslint on the specified index file.
|
|
224
|
+
* Triggers eslint fixes when any file in the directory changes (except the index file itself).
|
|
225
|
+
*
|
|
226
|
+
* @param watchPath - The path to watch for changes
|
|
227
|
+
* @param indexFile - The index file to run eslint on when changes occur
|
|
228
|
+
* @param debug - Whether to enable debug logging
|
|
229
|
+
* @returns An Effect that sets up root index monitoring
|
|
230
|
+
*/
|
|
231
|
+
const monitorRootIndexes = Effect.fn("effa-cli.index-multi.monitorRootIndexes")(
|
|
232
|
+
function*(watchPath: string, indexFile: string, debug: boolean) {
|
|
233
|
+
const fileSystem = yield* FileSystem.FileSystem
|
|
234
|
+
|
|
76
235
|
if (debug) {
|
|
77
|
-
|
|
236
|
+
yield* Effect.logInfo(`Starting root index monitoring for: ${watchPath} -> ${indexFile}`)
|
|
78
237
|
}
|
|
79
|
-
|
|
80
|
-
|
|
238
|
+
|
|
239
|
+
const watchStream = fileSystem.watch(watchPath)
|
|
240
|
+
|
|
241
|
+
yield* watchStream.pipe(
|
|
242
|
+
Stream.runForEach(
|
|
243
|
+
Effect.fn("effa-cli.index-multi.monitorRootIndexes.handleEvent")(function*(event) {
|
|
244
|
+
if (event.path.endsWith(indexFile)) return
|
|
245
|
+
|
|
246
|
+
if (debug) {
|
|
247
|
+
yield* Effect.logInfo(`Root change detected: ${event.path}, fixing: ${indexFile}`)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
yield* runNodeCommand(`pnpm eslint --fix "${indexFile}"`)
|
|
251
|
+
})
|
|
252
|
+
)
|
|
253
|
+
)
|
|
81
254
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
process.chdir(curDir)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const s = r.split("\n").sort((a, b) => a < b ? -1 : 1).join("\n")
|
|
112
|
-
const items = JSON.parse(`{${s.substring(0, s.length - 1)} }`) as Record<string, unknown>
|
|
113
|
-
|
|
114
|
-
const pkg = JSON.parse(fs.readFileSync(p + "/package.json", "utf-8"))
|
|
115
|
-
const t = levels
|
|
116
|
-
? Object
|
|
117
|
-
.keys(items)
|
|
118
|
-
.filter((_) => _.split("/").length <= (levels + 1 /* `./` */))
|
|
119
|
-
.reduce((prev, cur) => {
|
|
120
|
-
prev[cur] = items[cur]
|
|
121
|
-
return prev
|
|
122
|
-
}, {} as Record<string, unknown>)
|
|
123
|
-
: items
|
|
124
|
-
|
|
125
|
-
const exps = {
|
|
126
|
-
...(fs.existsSync(p + "/src/index.ts")
|
|
127
|
-
? {
|
|
128
|
-
".": {
|
|
129
|
-
"types": "./dist/index.d.ts",
|
|
130
|
-
"default": "./dist/index.js"
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Sets up comprehensive index monitoring for a given path.
|
|
259
|
+
* Combines both child controller monitoring and root index monitoring.
|
|
260
|
+
*
|
|
261
|
+
* @param watchPath - The path to monitor
|
|
262
|
+
* @param debug - Whether to enable debug logging
|
|
263
|
+
* @returns An Effect that sets up all index monitoring for the path
|
|
264
|
+
*/
|
|
265
|
+
const monitorIndexes = Effect.fn("effa-cli.index-multi.monitorIndexes")(
|
|
266
|
+
function*(watchPath: string, debug: boolean) {
|
|
267
|
+
const fileSystem = yield* FileSystem.FileSystem
|
|
268
|
+
|
|
269
|
+
if (debug) {
|
|
270
|
+
yield* Effect.logInfo(`Setting up index monitoring for path: ${watchPath}`)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const indexFile = watchPath + "/index.ts"
|
|
274
|
+
|
|
275
|
+
const monitors = [monitorChildIndexes(watchPath, debug)]
|
|
276
|
+
|
|
277
|
+
if (yield* fileSystem.exists(indexFile)) {
|
|
278
|
+
monitors.push(monitorRootIndexes(watchPath, indexFile, debug))
|
|
279
|
+
} else {
|
|
280
|
+
yield* Effect.logInfo(`Index file ${indexFile} does not exist`)
|
|
131
281
|
}
|
|
282
|
+
|
|
283
|
+
if (debug) {
|
|
284
|
+
yield* Effect.logInfo(`Starting ${monitors.length} monitor(s) for ${watchPath}`)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
yield* Effect.all(monitors, { concurrency: monitors.length })
|
|
288
|
+
}
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Watches directories for file changes and updates tsconfig.json and vite.config.ts accordingly.
|
|
293
|
+
* Monitors API resources and models directories for changes using Effect's native file watching.
|
|
294
|
+
*
|
|
295
|
+
* @returns An Effect that sets up file watching streams
|
|
296
|
+
*/
|
|
297
|
+
const watcher = Effect.fn("watch")(function*(debug: boolean) {
|
|
298
|
+
yield* Effect.log("Watch API resources and models for changes")
|
|
299
|
+
|
|
300
|
+
const dirs = ["../api/src/resources", "../api/src/models"]
|
|
301
|
+
const viteConfigFile = "./vite.config.ts"
|
|
302
|
+
const fileSystem = yield* FileSystem.FileSystem
|
|
303
|
+
|
|
304
|
+
const viteConfigExists = yield* fileSystem.exists(viteConfigFile)
|
|
305
|
+
|
|
306
|
+
if (debug) {
|
|
307
|
+
yield* Effect.logInfo("watcher debug mode is enabled")
|
|
132
308
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
let cmds = process.argv.slice(3).filter((_) => _ !== "--debug")
|
|
156
|
-
switch (cmd) {
|
|
157
|
-
case "link":
|
|
158
|
-
await import("./link.js")
|
|
159
|
-
break
|
|
160
|
-
case "unlink":
|
|
161
|
-
await import("./unlink.js")
|
|
162
|
-
break
|
|
163
|
-
case "watch": {
|
|
164
|
-
const dirs = ["../api/src/resources", "../api/src/models"]
|
|
165
|
-
const viteConfigFile = "./vite.config.ts"
|
|
166
|
-
const viteConfigExists = fs.existsSync(viteConfigFile)
|
|
167
|
-
dirs.forEach((d) => {
|
|
168
|
-
if (fs.existsSync(d)) {
|
|
169
|
-
const files: string[] = []
|
|
170
|
-
w.default(d, { recursive: true }, (t, f) => {
|
|
171
|
-
// console.log("change!", d)
|
|
172
|
-
touch("./tsconfig.json")
|
|
173
|
-
if (viteConfigExists && t === "update" && !files.includes(f)) {
|
|
174
|
-
// TODO: only on new files
|
|
175
|
-
touch(viteConfigFile)
|
|
176
|
-
files.push(f)
|
|
309
|
+
|
|
310
|
+
// validate directories and filter out non-existing ones
|
|
311
|
+
const existingDirs: string[] = []
|
|
312
|
+
for (const dir of dirs) {
|
|
313
|
+
const dirExists = yield* fileSystem.exists(dir)
|
|
314
|
+
if (dirExists) {
|
|
315
|
+
existingDirs.push(dir)
|
|
316
|
+
} else {
|
|
317
|
+
yield* Effect.logWarning(`Directory ${dir} does not exist - skipping`)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (existingDirs.length === 0) {
|
|
322
|
+
return yield* Effect.logWarning("No directories to watch - exiting")
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// start watching all existing directories concurrently
|
|
326
|
+
const watchStreams = existingDirs.map((dir) =>
|
|
327
|
+
Effect.gen(function*() {
|
|
328
|
+
if (debug) {
|
|
329
|
+
yield* Effect.logInfo(`Starting to watch directory: ${dir}`)
|
|
177
330
|
}
|
|
331
|
+
|
|
332
|
+
const files: string[] = []
|
|
333
|
+
const watchStream = fileSystem.watch(dir, { recursive: true })
|
|
334
|
+
|
|
335
|
+
yield* watchStream.pipe(
|
|
336
|
+
Stream.runForEach(
|
|
337
|
+
Effect.fn("effa-cli.watch.handleEvent")(function*(event) {
|
|
338
|
+
if (debug) {
|
|
339
|
+
yield* Effect.logInfo(`File ${event._tag.toLowerCase()}: ${event.path}`)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// touch tsconfig.json on any file change
|
|
343
|
+
yield* touch("./tsconfig.json")
|
|
344
|
+
if (debug) {
|
|
345
|
+
yield* Effect.logInfo("Updated tsconfig.json")
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// touch vite config only on file updates (not creates/deletes)
|
|
349
|
+
if (
|
|
350
|
+
viteConfigExists
|
|
351
|
+
&& event._tag === "Update"
|
|
352
|
+
&& !files.includes(event.path)
|
|
353
|
+
) {
|
|
354
|
+
yield* touch(viteConfigFile)
|
|
355
|
+
if (debug) {
|
|
356
|
+
yield* Effect.logInfo("Updated vite.config.ts")
|
|
357
|
+
}
|
|
358
|
+
files.push(event.path)
|
|
359
|
+
}
|
|
360
|
+
})
|
|
361
|
+
)
|
|
362
|
+
)
|
|
178
363
|
})
|
|
179
|
-
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
// run all watch streams concurrently
|
|
367
|
+
yield* Effect.all(watchStreams, { concurrency: existingDirs.length })
|
|
180
368
|
})
|
|
181
369
|
|
|
182
|
-
|
|
183
|
-
|
|
370
|
+
/**
|
|
371
|
+
* Updates a package.json file with generated exports mappings for TypeScript modules.
|
|
372
|
+
* Scans TypeScript source files and creates export entries that map module paths
|
|
373
|
+
* to their compiled JavaScript and TypeScript declaration files.
|
|
374
|
+
*
|
|
375
|
+
* @param startDir - The starting directory path for resolving relative paths
|
|
376
|
+
* @param p - The package directory path to process
|
|
377
|
+
* @param levels - Optional depth limit for export filtering (0 = no limit)
|
|
378
|
+
* @returns An Effect that succeeds when the package.json is updated
|
|
379
|
+
*/
|
|
380
|
+
const packagejsonUpdater = Effect.fn("effa-cli.packagejsonUpdater")(
|
|
381
|
+
function*(startDir: string, p: string, levels = 0) {
|
|
382
|
+
yield* Effect.log(`Generating exports for ${p}`)
|
|
383
|
+
|
|
384
|
+
const exportMappings = yield* extractExportMappings(path.resolve(startDir, p))
|
|
385
|
+
|
|
386
|
+
// if exportMappings is empty skip export generation
|
|
387
|
+
if (exportMappings === "") {
|
|
388
|
+
yield* Effect.log(`No src directory found for ${p}, skipping export generation`)
|
|
389
|
+
return
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const sortedExportEntries = JSON.parse(
|
|
393
|
+
`{ ${exportMappings} }`
|
|
394
|
+
) as Record<
|
|
395
|
+
string,
|
|
396
|
+
unknown
|
|
397
|
+
>
|
|
184
398
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
399
|
+
const filteredExportEntries = levels
|
|
400
|
+
? Object
|
|
401
|
+
.keys(sortedExportEntries)
|
|
402
|
+
// filter exports by directory depth - only include paths up to specified levels deep
|
|
403
|
+
.filter((_) => _.split("/").length <= (levels + 1 /* `./` */))
|
|
404
|
+
.reduce(
|
|
405
|
+
(prev, cur) => ({ ...prev, [cur]: sortedExportEntries[cur] }),
|
|
406
|
+
{} as Record<string, unknown>
|
|
407
|
+
)
|
|
408
|
+
: sortedExportEntries
|
|
409
|
+
|
|
410
|
+
const packageExports = {
|
|
411
|
+
...((yield* fs.exists(p + "/src/index.ts"))
|
|
412
|
+
&& {
|
|
413
|
+
".": {
|
|
414
|
+
"types": "./dist/index.d.ts",
|
|
415
|
+
"default": "./dist/index.js"
|
|
416
|
+
}
|
|
417
|
+
}),
|
|
418
|
+
...Object
|
|
419
|
+
.keys(filteredExportEntries)
|
|
420
|
+
.reduce(
|
|
421
|
+
(prev, cur) => ({
|
|
422
|
+
...prev,
|
|
423
|
+
// exclude index files and internal modules from package exports:
|
|
424
|
+
// - skip "./index" to avoid conflicts with the main "." export
|
|
425
|
+
// - skip "/internal/" paths to keep internal modules private
|
|
426
|
+
...cur !== "./index" && !cur.includes("/internal/") && { [cur]: filteredExportEntries[cur] }
|
|
427
|
+
}),
|
|
428
|
+
{} as Record<string, unknown>
|
|
429
|
+
)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const pkgJson = JSON.parse(yield* fs.readFileString(p + "/package.json", "utf-8"))
|
|
433
|
+
pkgJson.exports = packageExports
|
|
434
|
+
|
|
435
|
+
yield* Effect.log(`Writing updated package.json for ${p}`)
|
|
436
|
+
|
|
437
|
+
return yield* fs.writeFileString(
|
|
438
|
+
p + "/package.json",
|
|
439
|
+
JSON.stringify(pkgJson, null, 2)
|
|
440
|
+
)
|
|
441
|
+
}
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
/*
|
|
445
|
+
* CLI
|
|
446
|
+
*/
|
|
447
|
+
|
|
448
|
+
const EffectAppLibsPath = Args
|
|
449
|
+
.directory({
|
|
450
|
+
exists: "yes",
|
|
451
|
+
name: "effect-app-libs-path"
|
|
452
|
+
})
|
|
453
|
+
.pipe(
|
|
454
|
+
Args.withDefault("../../effect-app/libs"),
|
|
455
|
+
Args.withDescription("Path to the effect-app-libs directory")
|
|
191
456
|
)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
case "packagejson": {
|
|
202
|
-
monitorPackagejson(".")
|
|
203
|
-
break
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
case "packagejson-target": {
|
|
207
|
-
const target = process.argv[3]!
|
|
208
|
-
target.split(",").forEach((_) => monitorPackagejson(_, 1))
|
|
209
|
-
cmds = process.argv.slice(4)
|
|
210
|
-
break
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
case "packagejson-packages": {
|
|
214
|
-
fs
|
|
215
|
-
.readdirSync(startDir + "/packages")
|
|
216
|
-
.map((_) => startDir + "/packages/" + _)
|
|
217
|
-
.filter((_) =>
|
|
218
|
-
fs.existsSync(_ + "/package.json")
|
|
219
|
-
&& fs.existsSync(_ + "/src")
|
|
220
|
-
&& !_.endsWith("eslint-codegen-model")
|
|
221
|
-
&& !_.endsWith("vue-components")
|
|
457
|
+
|
|
458
|
+
const link = Command
|
|
459
|
+
.make(
|
|
460
|
+
"link",
|
|
461
|
+
{ effectAppLibsPath: EffectAppLibsPath },
|
|
462
|
+
Effect.fn("effa-cli.link")(function*({ effectAppLibsPath }) {
|
|
463
|
+
return yield* linkPackages(effectAppLibsPath)
|
|
464
|
+
})
|
|
222
465
|
)
|
|
223
|
-
.
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
466
|
+
.pipe(Command.withDescription("Link local effect-app packages using file resolutions"))
|
|
467
|
+
|
|
468
|
+
const unlink = Command
|
|
469
|
+
.make(
|
|
470
|
+
"unlink",
|
|
471
|
+
{},
|
|
472
|
+
Effect.fn("effa-cli.unlink")(function*({}) {
|
|
473
|
+
return yield* unlinkPackages
|
|
474
|
+
})
|
|
475
|
+
)
|
|
476
|
+
.pipe(Command.withDescription("Remove effect-app file resolutions and restore npm registry packages"))
|
|
477
|
+
|
|
478
|
+
const ue = Command
|
|
479
|
+
.make(
|
|
480
|
+
"ue",
|
|
481
|
+
{},
|
|
482
|
+
Effect.fn("effa-cli.ue")(function*({}) {
|
|
483
|
+
yield* Effect.log("Update effect-app and/or effect packages")
|
|
484
|
+
|
|
485
|
+
const prompted = yield* Prompt.select({
|
|
486
|
+
choices: [
|
|
487
|
+
{
|
|
488
|
+
title: "effect-app",
|
|
489
|
+
description: "Update only effect-app packages",
|
|
490
|
+
value: "effect-app"
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
title: "effect",
|
|
494
|
+
description: "Update only effect packages",
|
|
495
|
+
value: "effect"
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
title: "both",
|
|
499
|
+
description: "Update both effect-app and effect packages",
|
|
500
|
+
value: "both"
|
|
501
|
+
}
|
|
502
|
+
],
|
|
503
|
+
message: "Select an option"
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
switch (prompted) {
|
|
507
|
+
case "effect-app":
|
|
508
|
+
return yield* updateEffectAppPackages.pipe(
|
|
509
|
+
Effect.andThen(runNodeCommand("pnpm i"))
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
case "effect":
|
|
513
|
+
return yield* updateEffectPackages.pipe(
|
|
514
|
+
Effect.andThen(runNodeCommand("pnpm i"))
|
|
515
|
+
)
|
|
516
|
+
case "both":
|
|
517
|
+
return yield* updateEffectPackages.pipe(
|
|
518
|
+
Effect.andThen(updateEffectAppPackages),
|
|
519
|
+
Effect.andThen(runNodeCommand("pnpm i"))
|
|
520
|
+
)
|
|
521
|
+
}
|
|
522
|
+
})
|
|
523
|
+
)
|
|
524
|
+
.pipe(Command.withDescription("Update effect-app and/or effect packages"))
|
|
525
|
+
|
|
526
|
+
const DebugOption = Options.boolean("debug").pipe(
|
|
527
|
+
Options.withAlias("d"),
|
|
528
|
+
Options.withDescription("Enable debug logging")
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
const watch = Command
|
|
532
|
+
.make(
|
|
533
|
+
"watch",
|
|
534
|
+
{ debug: DebugOption },
|
|
535
|
+
Effect.fn("effa-cli.watch")(function*({ debug }) {
|
|
536
|
+
return yield* watcher(debug)
|
|
537
|
+
})
|
|
538
|
+
)
|
|
539
|
+
.pipe(
|
|
540
|
+
Command.withDescription(
|
|
541
|
+
"Watch API resources and models for changes and update tsconfig.json and vite.config.ts accordingly"
|
|
542
|
+
)
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
const indexMulti = Command
|
|
546
|
+
.make(
|
|
547
|
+
"index-multi",
|
|
548
|
+
{ debug: DebugOption },
|
|
549
|
+
Effect.fn("effa-cli.index-multi")(function*({ debug }) {
|
|
550
|
+
yield* Effect.log("Starting multi-index monitoring")
|
|
551
|
+
|
|
552
|
+
const dirs = ["./api/src"]
|
|
553
|
+
const fileSystem = yield* FileSystem.FileSystem
|
|
554
|
+
|
|
555
|
+
const existingDirs: string[] = []
|
|
556
|
+
for (const dir of dirs) {
|
|
557
|
+
const dirExists = yield* fileSystem.exists(dir)
|
|
558
|
+
if (dirExists) {
|
|
559
|
+
existingDirs.push(dir)
|
|
560
|
+
} else {
|
|
561
|
+
yield* Effect.logWarning(`Directory ${dir} does not exist - skipping`)
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (existingDirs.length === 0) {
|
|
566
|
+
return yield* Effect.logWarning("No directories to monitor - exiting")
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const monitors = existingDirs.map((dir) => monitorIndexes(dir, debug))
|
|
570
|
+
yield* Effect.all(monitors, { concurrency: monitors.length })
|
|
571
|
+
})
|
|
572
|
+
)
|
|
573
|
+
.pipe(
|
|
574
|
+
Command.withDescription(
|
|
575
|
+
"Monitor multiple directories for index and controller file changes"
|
|
576
|
+
)
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
const packagejson = Command
|
|
580
|
+
.make(
|
|
581
|
+
"packagejson",
|
|
582
|
+
{},
|
|
583
|
+
Effect.fn("effa-cli.packagejson")(function*({}) {
|
|
584
|
+
// https://nodejs.org/api/path.html#pathresolvepaths
|
|
585
|
+
const startDir = path.resolve()
|
|
586
|
+
|
|
587
|
+
return yield* packagejsonUpdater(startDir, ".")
|
|
588
|
+
})
|
|
589
|
+
)
|
|
590
|
+
.pipe(
|
|
591
|
+
Command.withDescription("Generate and update root-level package.json exports mappings for TypeScript modules")
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
const packagejsonPackages = Command
|
|
595
|
+
.make(
|
|
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()
|
|
601
|
+
|
|
602
|
+
const packagesDir = path.join(startDir, "packages")
|
|
603
|
+
|
|
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("vue-components")
|
|
623
|
+
|
|
624
|
+
if (packageJsonExists && srcExists && !shouldExclude) {
|
|
625
|
+
validPackages.push(packagePath)
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (validPackages.length === 0) {
|
|
630
|
+
return yield* Effect.logWarning("No valid packages found to update")
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
yield* Effect.log(`Found ${validPackages.length} packages to update`)
|
|
634
|
+
|
|
635
|
+
// update each package sequentially
|
|
636
|
+
yield* Effect.all(
|
|
637
|
+
validPackages.map((packagePath) =>
|
|
638
|
+
Effect.gen(function*() {
|
|
639
|
+
const relativePackagePath = path.relative(startDir, packagePath)
|
|
640
|
+
yield* Effect.log(`Updating ${relativePackagePath}`)
|
|
641
|
+
return yield* packagejsonUpdater(startDir, relativePackagePath)
|
|
642
|
+
})
|
|
643
|
+
)
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
yield* Effect.log("All packages updated successfully")
|
|
647
|
+
})
|
|
648
|
+
)
|
|
649
|
+
.pipe(Command.withDescription("Generate and update package.json exports mappings for all packages in monorepo"))
|
|
650
|
+
|
|
651
|
+
// configure CLI
|
|
652
|
+
const cli = Command.run(
|
|
653
|
+
Command
|
|
654
|
+
.make("effa")
|
|
655
|
+
.pipe(Command.withSubcommands([
|
|
656
|
+
ue,
|
|
657
|
+
link,
|
|
658
|
+
unlink,
|
|
659
|
+
watch,
|
|
660
|
+
indexMulti,
|
|
661
|
+
packagejson,
|
|
662
|
+
packagejsonPackages
|
|
663
|
+
])),
|
|
664
|
+
{
|
|
665
|
+
name: "Effect-App CLI by jfet97 ❤️",
|
|
666
|
+
version: "v1.0.0"
|
|
667
|
+
}
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
return yield* cli(process.argv)
|
|
671
|
+
})()
|
|
672
|
+
.pipe(
|
|
673
|
+
Effect.provide(NodeContext.layer),
|
|
674
|
+
NodeRuntime.runMain
|
|
675
|
+
)
|