-module(app). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([main/0]). -spec get_app_name() -> {ok, binary()} | {error, nil}. get_app_name() -> gleam@result:'try'( begin _pipe = simplifile:read(<<"gleam.toml"/utf8>>), gleam@result:map_error(_pipe, fun(_) -> nil end) end, fun(Config) -> gleam@result:'try'( begin _pipe@1 = tom:parse(Config), gleam@result:map_error(_pipe@1, fun(_) -> nil end) end, fun(Parsed) -> gleam@result:'try'( begin _pipe@2 = tom:get_string(Parsed, [<<"name"/utf8>>]), gleam@result:map_error(_pipe@2, fun(_) -> nil end) end, fun(App_name) -> {ok, App_name} end ) end ) end ). -spec make_entry_file(binary(), binary()) -> nil. make_entry_file(Filepath, App_name) -> _ = begin _pipe = <<" import { main } from './{app_name}.mjs'; main(); "/utf8>>, _pipe@1 = gleam@string:replace(_pipe, <<"{app_name}"/utf8>>, App_name), simplifile:write(Filepath, _pipe@1) end, nil. -spec make_package_json_file(binary()) -> nil. make_package_json_file(Filepath) -> _ = begin _pipe = <<" { \"type\": \"module\", \"scripts\": { \"build\": \"node scripts/build.js\", \"watch\": \"node scripts/watch.js\" }, \"devDependencies\": { \"chokidar\": \"^3.6.0\", \"deno\": \"^0.1.1\", \"esbuild\": \"^0.21.5\", \"esbuild-sass-plugin\": \"^3.3.1\", \"toml\": \"^3.0.0\" } } "/utf8>>, simplifile:write(Filepath, _pipe) end, nil. -spec make_html_file(binary(), binary()) -> nil. make_html_file(Filepath, App_name) -> _ = begin _pipe = <<" 🚧 {app_name}
"/utf8>>, _pipe@1 = gleam@string:replace(_pipe, <<"{app_name}"/utf8>>, App_name), simplifile:write(Filepath, _pipe@1) end, nil. -spec make_compile_script(binary()) -> nil. make_compile_script(Filepath) -> _ = begin _pipe = <<" import { exec } from 'child_process'; async function compile() { return new Promise((resolve, reject) => { exec('gleam build', (error, stdout, stderr) => { if (error) { console.error(`Error: ${error.message}`); reject(error); return; } if (stderr) { console.error(`stderr: ${stderr}`); reject(new Error(stderr)); return; } console.log(`stdout: ${stdout}`); resolve(stdout); }); }); } export default compile; "/utf8>>, simplifile:write(Filepath, _pipe) end, nil. -spec make_build_script(binary()) -> nil. make_build_script(Filepath) -> _ = begin _pipe = <<" import esbuild from 'esbuild'; import { sassPlugin } from 'esbuild-sass-plugin'; import path from 'path'; import fs from 'fs'; import toml from 'toml'; import compile from './compile.js'; import { promises as fsPromises } from 'fs'; async function readConfig() { const configContent = await fsPromises.readFile('gleam.toml', 'utf-8'); return toml.parse(configContent); } async function build() { try { const config = await readConfig(); const appName = config.name; const env = config.env || 'dev'; const jsInputFile = `build/${env}/javascript/${appName}/${appName}.mjs`; const cssInputFile = 'src/styles/app.scss'; const outputDir = 'priv/static'; const jsOutputFile = path.join(outputDir, 'js', `${appName}.mjs`); const cssOutputFile = path.join(outputDir, 'css', `${appName}.css`); await compile(); // Build Javascript await esbuild.build({ entryPoints: [jsInputFile], bundle: true, minify: false, outfile: jsOutputFile, format: 'esm', loader: { '.mjs': 'js', } }); // Build CSS await esbuild.build({ entryPoints: [cssInputFile], bundle: true, minify: true, outfile: cssOutputFile, plugins: [ sassPlugin(), ] }); const htmlSource = path.resolve('src/index.html'); const htmlDest = path.resolve(outputDir, 'index.html'); if (fs.existsSync(htmlSource)) { fs.copyFileSync(htmlSource, htmlDest); } console.log('Build complete'); } catch (error) { console.error('Build failed:', error); } } build().catch(() => process.exit(1)); export default build; "/utf8>>, simplifile:write(Filepath, _pipe) end, nil. -spec make_watch_script(binary()) -> nil. make_watch_script(Filepath) -> _ = begin _pipe = <<" import chokidar from 'chokidar'; import build from './build.js'; import { exec } from 'child_process'; async function startWatching() { const watcher = chokidar.watch(['src/**/*.gleam', 'src/styles/**/*.scss', 'src/styles/**/*.css', 'src/index.html'], { persistent: true }); watcher.on('change', async (path) => { console.log(`File changed: ${path}`); try { await build(); } catch (error) { console.error('Error during build:', error); } }); console.log('Watching for changes...'); exec('deno run --allow-read=. --allow-net scripts/server.ts', (error, stdout, stderr) => { if (error) { console.error(`Error starting server: ${error.message}`); return; } if (stderr) { console.error(`Server stderr: ${stderr}`); return; } console.log(`Server stdout: ${stdout}`); }); } startWatching().catch(console.error); "/utf8>>, simplifile:write(Filepath, _pipe) end, nil. -spec make_server_script(binary()) -> nil. make_server_script(Filepath) -> _ = begin _pipe = <<"import { extname } from 'https://deno.land/std/path/mod.ts' const port = 1234; const server = Deno.listen({ port: port }); console.log(`File server running on http://localhost:${port}/`); for await (const conn of server) { handleHttp(conn).catch(console.error); } async function handleHttp(conn: Deno.Conn) { const httpConn = Deno.serveHttp(conn); for await (const requestEvent of httpConn) { // Use the request pathname as filepath const url = new URL(requestEvent.request.url); let filepath = decodeURIComponent(url.pathname); if (filepath === '/') { filepath = '/index.html'; } // Try opening the file let file; try { file = await Deno.open('priv/static' + filepath, { read: true }); } catch { // If the file cannot be opened, return a '404 Not Found' response const notFoundResponse = new Response('404 Not Found', { status: 404 }); await requestEvent.respondWith(notFoundResponse); continue; } // Build a readable stream so the file doesn't have to be fully loaded into // memory while we send it const readableStream = file.readable; const contentType = getContentType(filepath); // Build and send the response const response = new Response(readableStream, { headers: { 'content-type': contentType }, }); await requestEvent.respondWith(response); } } function getContentType(filepath) { const ext = extname(filepath); switch (ext) { case '.html': return 'text/html'; case '.mjs': return 'application/javascript'; case '.js': return 'application/javascript'; case '.css': return 'text/css'; case '.json': return 'application/json'; case '.png': return 'image/png'; case '.jpg': case '.jpeg': return 'image/jpeg'; case '.gif': return 'image/gif'; case '.svg': return 'image/svg+xml'; case '.ico': return 'image/x-icon'; default: return 'application/octet-stream'; } }"/utf8>>, simplifile:write(Filepath, _pipe) end, nil. -spec main() -> nil. main() -> _assert_subject = get_app_name(), {ok, App_name} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"app"/utf8>>, function => <<"main"/utf8>>, line => 8}) end, _ = simplifile_erl:create_directory(<<"scripts"/utf8>>), make_compile_script(<<"scripts/compile.js"/utf8>>), make_build_script(<<"scripts/build.js"/utf8>>), make_watch_script(<<"scripts/watch.js"/utf8>>), make_server_script(<<"scripts/server.ts"/utf8>>), _ = simplifile:create_directory_all(<<"priv/static/css"/utf8>>), _ = simplifile:create_directory_all(<<"priv/static/js"/utf8>>), make_entry_file(<<"priv/static/js/entry.mjs"/utf8>>, App_name), make_html_file(<<"priv/static/index.html"/utf8>>, App_name), make_package_json_file(<<"package.json"/utf8>>).