@frontman-ai/astro 0.1.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.
Files changed (36) hide show
  1. package/README.md +53 -0
  2. package/dist/cli.js +2744 -0
  3. package/package.json +66 -0
  4. package/src/FrontmanAstro.res +20 -0
  5. package/src/FrontmanAstro.res.mjs +36 -0
  6. package/src/FrontmanAstro__AstroBindings.res +46 -0
  7. package/src/FrontmanAstro__AstroBindings.res.mjs +2 -0
  8. package/src/FrontmanAstro__Config.res +85 -0
  9. package/src/FrontmanAstro__Config.res.mjs +44 -0
  10. package/src/FrontmanAstro__Integration.res +35 -0
  11. package/src/FrontmanAstro__Integration.res.mjs +36 -0
  12. package/src/FrontmanAstro__Middleware.res +149 -0
  13. package/src/FrontmanAstro__Middleware.res.mjs +141 -0
  14. package/src/FrontmanAstro__Server.res +196 -0
  15. package/src/FrontmanAstro__Server.res.mjs +241 -0
  16. package/src/FrontmanAstro__ToolRegistry.res +21 -0
  17. package/src/FrontmanAstro__ToolRegistry.res.mjs +41 -0
  18. package/src/FrontmanAstro__ToolbarApp.res +50 -0
  19. package/src/FrontmanAstro__ToolbarApp.res.mjs +39 -0
  20. package/src/cli/FrontmanAstro__Cli.res +126 -0
  21. package/src/cli/FrontmanAstro__Cli.res.mjs +180 -0
  22. package/src/cli/FrontmanAstro__Cli__AutoEdit.res +300 -0
  23. package/src/cli/FrontmanAstro__Cli__AutoEdit.res.mjs +266 -0
  24. package/src/cli/FrontmanAstro__Cli__Detect.res +298 -0
  25. package/src/cli/FrontmanAstro__Cli__Detect.res.mjs +345 -0
  26. package/src/cli/FrontmanAstro__Cli__Files.res +244 -0
  27. package/src/cli/FrontmanAstro__Cli__Files.res.mjs +321 -0
  28. package/src/cli/FrontmanAstro__Cli__Install.res +224 -0
  29. package/src/cli/FrontmanAstro__Cli__Install.res.mjs +194 -0
  30. package/src/cli/FrontmanAstro__Cli__Style.res +22 -0
  31. package/src/cli/FrontmanAstro__Cli__Style.res.mjs +61 -0
  32. package/src/cli/FrontmanAstro__Cli__Templates.res +226 -0
  33. package/src/cli/FrontmanAstro__Cli__Templates.res.mjs +237 -0
  34. package/src/cli/cli.mjs +3 -0
  35. package/src/tools/FrontmanAstro__Tool__GetPages.res +164 -0
  36. package/src/tools/FrontmanAstro__Tool__GetPages.res.mjs +180 -0
@@ -0,0 +1,321 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Fs from "fs";
4
+ import * as Nodepath from "node:path";
5
+ import * as FrontmanAstro__Cli__AutoEdit$FrontmanAiAstro from "./FrontmanAstro__Cli__AutoEdit.res.mjs";
6
+ import * as FrontmanAstro__Cli__Templates$FrontmanAiAstro from "./FrontmanAstro__Cli__Templates.res.mjs";
7
+
8
+ let hostPattern = /host:\s*['\"]([^'\"]+)['\"]/;
9
+
10
+ let escapeReplacement = (function(str) { return str.replace(/\$/g, '$$$$'); });
11
+
12
+ function updateHostInContent(content, newHost) {
13
+ let safeHost = escapeReplacement(newHost);
14
+ return content.replace(hostPattern, `host: '` + safeHost + `'`);
15
+ }
16
+
17
+ async function readFile(path) {
18
+ try {
19
+ return await Fs.promises.readFile(path, "utf8");
20
+ } catch (exn) {
21
+ return;
22
+ }
23
+ }
24
+
25
+ async function writeFile(path, content) {
26
+ try {
27
+ await Fs.promises.writeFile(path, content, "utf8");
28
+ return {
29
+ TAG: "Ok",
30
+ _0: undefined
31
+ };
32
+ } catch (exn) {
33
+ return {
34
+ TAG: "Error",
35
+ _0: `Failed to write ` + path
36
+ };
37
+ }
38
+ }
39
+
40
+ async function handleNeedsManualEdit(filePath, fileName, host, fileType, dryRun, autoEdit, manualDetails) {
41
+ if (dryRun) {
42
+ return {
43
+ TAG: "Ok",
44
+ _0: {
45
+ TAG: "ManualEditRequired",
46
+ fileName: fileName,
47
+ details: manualDetails
48
+ }
49
+ };
50
+ }
51
+ if (!autoEdit) {
52
+ return {
53
+ TAG: "Ok",
54
+ _0: {
55
+ TAG: "ManualEditRequired",
56
+ fileName: fileName,
57
+ details: manualDetails
58
+ }
59
+ };
60
+ }
61
+ let existingContent = await readFile(filePath);
62
+ if (existingContent === undefined) {
63
+ return {
64
+ TAG: "Ok",
65
+ _0: {
66
+ TAG: "ManualEditRequired",
67
+ fileName: fileName,
68
+ details: manualDetails
69
+ }
70
+ };
71
+ }
72
+ let name = await FrontmanAstro__Cli__AutoEdit$FrontmanAiAstro.autoEditFile(filePath, fileName, existingContent, fileType, host);
73
+ if (name.TAG === "AutoEdited") {
74
+ return {
75
+ TAG: "Ok",
76
+ _0: {
77
+ TAG: "AutoEdited",
78
+ _0: name._0
79
+ }
80
+ };
81
+ }
82
+ console.log(FrontmanAstro__Cli__Templates$FrontmanAiAstro.SuccessMessages.autoEditFailed(fileName, name._0));
83
+ console.log(` Falling back to manual instructions.`);
84
+ return {
85
+ TAG: "Ok",
86
+ _0: {
87
+ TAG: "ManualEditRequired",
88
+ fileName: fileName,
89
+ details: manualDetails
90
+ }
91
+ };
92
+ }
93
+
94
+ function getPendingAutoEdit(existingFile, filePath, fileName, fileType, manualDetails) {
95
+ if (typeof existingFile !== "object" && existingFile !== "NotFound") {
96
+ return {
97
+ filePath: filePath,
98
+ fileName: fileName,
99
+ fileType: fileType,
100
+ manualDetails: manualDetails
101
+ };
102
+ }
103
+ }
104
+
105
+ async function handleConfig(projectDir, host, configFileName, existingFile, dryRun, autoEditOpt) {
106
+ let autoEdit = autoEditOpt !== undefined ? autoEditOpt : false;
107
+ let filePath = Nodepath.join(projectDir, configFileName);
108
+ if (typeof existingFile !== "object") {
109
+ if (existingFile !== "NotFound") {
110
+ return await handleNeedsManualEdit(filePath, configFileName, host, "Config", dryRun, autoEdit, FrontmanAstro__Cli__Templates$FrontmanAiAstro.ManualInstructions.config(configFileName, host));
111
+ }
112
+ if (dryRun) {
113
+ return {
114
+ TAG: "Ok",
115
+ _0: {
116
+ TAG: "Created",
117
+ _0: configFileName
118
+ }
119
+ };
120
+ }
121
+ let content = FrontmanAstro__Cli__Templates$FrontmanAiAstro.configTemplate(host);
122
+ let e = await writeFile(filePath, content);
123
+ if (e.TAG === "Ok") {
124
+ return {
125
+ TAG: "Ok",
126
+ _0: {
127
+ TAG: "Created",
128
+ _0: configFileName
129
+ }
130
+ };
131
+ } else {
132
+ return {
133
+ TAG: "Error",
134
+ _0: e._0
135
+ };
136
+ }
137
+ } else {
138
+ let existingHost = existingFile.host;
139
+ if (existingHost === host || existingHost === "") {
140
+ return {
141
+ TAG: "Ok",
142
+ _0: {
143
+ TAG: "Skipped",
144
+ _0: configFileName
145
+ }
146
+ };
147
+ }
148
+ if (dryRun) {
149
+ return {
150
+ TAG: "Ok",
151
+ _0: {
152
+ TAG: "Updated",
153
+ fileName: configFileName,
154
+ oldHost: existingHost,
155
+ newHost: host
156
+ }
157
+ };
158
+ }
159
+ let content$1 = await readFile(filePath);
160
+ if (content$1 === undefined) {
161
+ return {
162
+ TAG: "Error",
163
+ _0: `Failed to read ` + configFileName
164
+ };
165
+ }
166
+ let newContent = updateHostInContent(content$1, host);
167
+ let e$1 = await writeFile(filePath, newContent);
168
+ if (e$1.TAG === "Ok") {
169
+ return {
170
+ TAG: "Ok",
171
+ _0: {
172
+ TAG: "Updated",
173
+ fileName: configFileName,
174
+ oldHost: existingHost,
175
+ newHost: host
176
+ }
177
+ };
178
+ } else {
179
+ return {
180
+ TAG: "Error",
181
+ _0: e$1._0
182
+ };
183
+ }
184
+ }
185
+ }
186
+
187
+ async function handleMiddleware(projectDir, host, middlewareFileName, existingFile, dryRun, autoEditOpt) {
188
+ let autoEdit = autoEditOpt !== undefined ? autoEditOpt : false;
189
+ let filePath = Nodepath.join(projectDir, middlewareFileName);
190
+ if (typeof existingFile !== "object") {
191
+ if (existingFile !== "NotFound") {
192
+ return await handleNeedsManualEdit(filePath, middlewareFileName, host, "Middleware", dryRun, autoEdit, FrontmanAstro__Cli__Templates$FrontmanAiAstro.ManualInstructions.middleware(middlewareFileName, host));
193
+ }
194
+ if (dryRun) {
195
+ return {
196
+ TAG: "Ok",
197
+ _0: {
198
+ TAG: "Created",
199
+ _0: middlewareFileName
200
+ }
201
+ };
202
+ }
203
+ let srcDir = Nodepath.join(projectDir, "src");
204
+ await Fs.promises.mkdir(srcDir, {
205
+ recursive: true
206
+ });
207
+ let content = FrontmanAstro__Cli__Templates$FrontmanAiAstro.middlewareTemplate(host);
208
+ let e = await writeFile(filePath, content);
209
+ if (e.TAG === "Ok") {
210
+ return {
211
+ TAG: "Ok",
212
+ _0: {
213
+ TAG: "Created",
214
+ _0: middlewareFileName
215
+ }
216
+ };
217
+ } else {
218
+ return {
219
+ TAG: "Error",
220
+ _0: e._0
221
+ };
222
+ }
223
+ } else {
224
+ let existingHost = existingFile.host;
225
+ if (existingHost === host || existingHost === "") {
226
+ return {
227
+ TAG: "Ok",
228
+ _0: {
229
+ TAG: "Skipped",
230
+ _0: middlewareFileName
231
+ }
232
+ };
233
+ }
234
+ if (dryRun) {
235
+ return {
236
+ TAG: "Ok",
237
+ _0: {
238
+ TAG: "Updated",
239
+ fileName: middlewareFileName,
240
+ oldHost: existingHost,
241
+ newHost: host
242
+ }
243
+ };
244
+ }
245
+ let content$1 = await readFile(filePath);
246
+ if (content$1 === undefined) {
247
+ return {
248
+ TAG: "Error",
249
+ _0: `Failed to read ` + middlewareFileName
250
+ };
251
+ }
252
+ let newContent = updateHostInContent(content$1, host);
253
+ let e$1 = await writeFile(filePath, newContent);
254
+ if (e$1.TAG === "Ok") {
255
+ return {
256
+ TAG: "Ok",
257
+ _0: {
258
+ TAG: "Updated",
259
+ fileName: middlewareFileName,
260
+ oldHost: existingHost,
261
+ newHost: host
262
+ }
263
+ };
264
+ } else {
265
+ return {
266
+ TAG: "Error",
267
+ _0: e$1._0
268
+ };
269
+ }
270
+ }
271
+ }
272
+
273
+ function formatResult(result) {
274
+ switch (result.TAG) {
275
+ case "Created" :
276
+ return FrontmanAstro__Cli__Templates$FrontmanAiAstro.SuccessMessages.fileCreated(result._0);
277
+ case "Updated" :
278
+ return FrontmanAstro__Cli__Templates$FrontmanAiAstro.SuccessMessages.hostUpdated(result.fileName, result.oldHost, result.newHost);
279
+ case "Skipped" :
280
+ return FrontmanAstro__Cli__Templates$FrontmanAiAstro.SuccessMessages.fileSkipped(result._0);
281
+ case "ManualEditRequired" :
282
+ return FrontmanAstro__Cli__Templates$FrontmanAiAstro.SuccessMessages.manualEditRequired(result.fileName);
283
+ case "AutoEdited" :
284
+ return FrontmanAstro__Cli__Templates$FrontmanAiAstro.SuccessMessages.fileAutoEdited(result._0);
285
+ }
286
+ }
287
+
288
+ let Bindings;
289
+
290
+ let Fs$1;
291
+
292
+ let Path;
293
+
294
+ let Detect;
295
+
296
+ let Templates;
297
+
298
+ let AutoEdit;
299
+
300
+ let Style;
301
+
302
+ export {
303
+ Bindings,
304
+ Fs$1 as Fs,
305
+ Path,
306
+ Detect,
307
+ Templates,
308
+ AutoEdit,
309
+ Style,
310
+ hostPattern,
311
+ escapeReplacement,
312
+ updateHostInContent,
313
+ readFile,
314
+ writeFile,
315
+ handleNeedsManualEdit,
316
+ getPendingAutoEdit,
317
+ handleConfig,
318
+ handleMiddleware,
319
+ formatResult,
320
+ }
321
+ /* fs Not a pure module */
@@ -0,0 +1,224 @@
1
+ // Install command implementation
2
+ module Bindings = FrontmanBindings
3
+ module ChildProcess = Bindings.ChildProcess
4
+ module Path = Bindings.Path
5
+ module Process = Bindings.Process
6
+
7
+ module AutoEdit = FrontmanAstro__Cli__AutoEdit
8
+ module Detect = FrontmanAstro__Cli__Detect
9
+ module Files = FrontmanAstro__Cli__Files
10
+ module Templates = FrontmanAstro__Cli__Templates
11
+ module Style = FrontmanAstro__Cli__Style
12
+
13
+ type installOptions = {
14
+ server: string,
15
+ prefix: option<string>,
16
+ dryRun: bool,
17
+ skipDeps: bool,
18
+ }
19
+
20
+ type installResult =
21
+ | Success
22
+ | PartialSuccess({manualStepsRequired: array<string>})
23
+ | Failure(string)
24
+
25
+ // Install dependencies using detected package manager
26
+ let installDependencies = async (
27
+ ~projectDir: string,
28
+ ~packageManager: Detect.packageManager,
29
+ ~dryRun: bool,
30
+ ): result<unit, string> => {
31
+ let pm = Detect.getPackageManagerCommand(packageManager)
32
+ let args = Detect.getInstallArgs(packageManager)
33
+ let packages = ["@frontman-ai/astro", "@astrojs/node"]
34
+ // Deno requires npm: prefix for npm packages (otherwise it looks them up on JSR)
35
+ let packages = switch packageManager {
36
+ | Deno => packages->Array.map(p => "npm:" ++ p)
37
+ | _ => packages
38
+ }
39
+ let cmd = `${pm} ${args->Array.join(" ")} ${packages->Array.join(" ")}`
40
+
41
+ switch dryRun {
42
+ | true =>
43
+ Console.log(` ${Style.dim(`Would run: ${cmd}`)}`)
44
+ Ok()
45
+ | false =>
46
+ Console.log(` ${Style.purple("Installing dependencies with " ++ pm ++ "...")}`)
47
+
48
+ switch await ChildProcess.execWithOptions(cmd, {cwd: projectDir}) {
49
+ | Ok(_) =>
50
+ Console.log(` ${Style.check} Dependencies installed`)
51
+ Ok()
52
+ | Error(err) =>
53
+ let stderr = switch err.stderr == "" {
54
+ | true => "Unknown error"
55
+ | false => err.stderr
56
+ }
57
+ Error(`Failed to install dependencies: ${stderr}`)
58
+ }
59
+ }
60
+ }
61
+
62
+ // Helper to process a file result and collect manual steps
63
+ let processFileResult = (
64
+ result: result<Files.fileResult, string>,
65
+ manualSteps: array<string>,
66
+ ): result<unit, string> => {
67
+ switch result {
68
+ | Ok(fileResult) =>
69
+ Console.log(Files.formatResult(fileResult))
70
+ switch fileResult {
71
+ | Files.ManualEditRequired({details, _}) => manualSteps->Array.push(details)->ignore
72
+ | _ => ()
73
+ }
74
+ Ok()
75
+ | Error(msg) =>
76
+ Console.error(` ${Style.warn} ${Style.bold("Error:")} ${msg}`)
77
+ Error(msg)
78
+ }
79
+ }
80
+
81
+ // Collect which files need auto-editing (without prompting the user)
82
+ let collectPendingAutoEdits = (
83
+ ~projectDir: string,
84
+ ~host: string,
85
+ ~info: Detect.projectInfo,
86
+ ): array<Files.pendingAutoEdit> => {
87
+ let pending = []
88
+
89
+ // Check astro config
90
+ switch Files.getPendingAutoEdit(
91
+ ~existingFile=info.config,
92
+ ~filePath=Path.join([projectDir, info.configFileName]),
93
+ ~fileName=info.configFileName,
94
+ ~fileType=AutoEdit.Config,
95
+ ~manualDetails=Templates.ManualInstructions.config(info.configFileName, host),
96
+ ) {
97
+ | Some(p) => pending->Array.push(p)->ignore
98
+ | None => ()
99
+ }
100
+
101
+ // Check middleware
102
+ switch Files.getPendingAutoEdit(
103
+ ~existingFile=info.middleware,
104
+ ~filePath=Path.join([projectDir, info.middlewareFileName]),
105
+ ~fileName=info.middlewareFileName,
106
+ ~fileType=AutoEdit.Middleware,
107
+ ~manualDetails=Templates.ManualInstructions.middleware(info.middlewareFileName, host),
108
+ ) {
109
+ | Some(p) => pending->Array.push(p)->ignore
110
+ | None => ()
111
+ }
112
+
113
+ pending
114
+ }
115
+
116
+ // Main install function
117
+ let run = async (options: installOptions): installResult => {
118
+ let projectDir = options.prefix->Option.getOr(Process.cwd())
119
+ let host = options.server
120
+
121
+ Console.log(Templates.banner())
122
+ Console.log(` ${Style.bullet} ${Style.bold("Server:")} ${host}`)
123
+
124
+ switch options.dryRun {
125
+ | true =>
126
+ Console.log("")
127
+ Console.log(Templates.SuccessMessages.dryRunHeader)
128
+ | false => ()
129
+ }
130
+
131
+ // Step 1: Detect project info
132
+ switch await Detect.detect(projectDir) {
133
+ | Error(msg) =>
134
+ Console.error(` ${Style.warn} ${Style.bold("Error:")} ${msg}`)
135
+ Failure(msg)
136
+
137
+ | Ok(info) =>
138
+ let version = (info.astroVersion->Option.getOrThrow).raw
139
+
140
+ Console.log(` ${Style.bullet} ${Style.bold("Detected:")} Astro ${version}`)
141
+ Console.log("")
142
+
143
+ // Step 2: Install dependencies (unless skipped)
144
+ switch options.skipDeps {
145
+ | true => ()
146
+ | false =>
147
+ switch await installDependencies(
148
+ ~projectDir,
149
+ ~packageManager=info.packageManager,
150
+ ~dryRun=options.dryRun,
151
+ ) {
152
+ | Error(msg) =>
153
+ Console.error(` ${Style.warn} ${msg}`)
154
+ // Continue anyway - user might have deps already
155
+ ()
156
+ | Ok() => ()
157
+ }
158
+ Console.log("")
159
+ }
160
+
161
+ // Step 3: Collect files needing auto-edit and prompt once (if any)
162
+ let pendingEdits = collectPendingAutoEdits(~projectDir, ~host, ~info)
163
+ let shouldAutoEdit = switch (pendingEdits->Array.length > 0, options.dryRun) {
164
+ | (true, false) =>
165
+ let fileNames = pendingEdits->Array.map(p => p.fileName)
166
+ await AutoEdit.promptUserForAutoEdit(~fileNames)
167
+ | _ => false
168
+ }
169
+
170
+ // Step 4: Handle files
171
+ let manualSteps = []
172
+
173
+ // Handle astro config
174
+ let configResult = await Files.handleConfig(
175
+ ~projectDir,
176
+ ~host,
177
+ ~configFileName=info.configFileName,
178
+ ~existingFile=info.config,
179
+ ~dryRun=options.dryRun,
180
+ ~autoEdit=shouldAutoEdit,
181
+ )
182
+
183
+ switch processFileResult(configResult, manualSteps) {
184
+ | Error(msg) => Failure(msg)
185
+ | Ok() =>
186
+ // Handle middleware
187
+ let middlewareResult = await Files.handleMiddleware(
188
+ ~projectDir,
189
+ ~host,
190
+ ~middlewareFileName=info.middlewareFileName,
191
+ ~existingFile=info.middleware,
192
+ ~dryRun=options.dryRun,
193
+ ~autoEdit=shouldAutoEdit,
194
+ )
195
+
196
+ switch processFileResult(middlewareResult, manualSteps) {
197
+ | Error(msg) => Failure(msg)
198
+ | Ok() =>
199
+ // Summary
200
+ switch manualSteps->Array.length > 0 {
201
+ | true =>
202
+ Console.log("")
203
+ Console.log(` ${Style.divider}`)
204
+ Console.log("")
205
+ Console.log(` ${Style.yellowBold("Manual steps required:")}`)
206
+ Console.log("")
207
+ manualSteps->Array.forEach(step => Console.log(step))
208
+ Console.log("")
209
+ PartialSuccess({manualStepsRequired: manualSteps})
210
+ | false =>
211
+ switch options.dryRun {
212
+ | true => ()
213
+ | false =>
214
+ let devCommand = Detect.getDevCommand(info.packageManager)
215
+ Console.log("")
216
+ Console.log(` ${Style.divider}`)
217
+ Console.log(Templates.SuccessMessages.installComplete(~devCommand))
218
+ }
219
+ Success
220
+ }
221
+ }
222
+ }
223
+ }
224
+ }