Packages

CLI tool used to scaffold a web application

Current section

Files

Jump to
scaffold_gleam src scaffold_gleam.erl
Raw

src/scaffold_gleam.erl

-module(scaffold_gleam).
-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_styles_template(binary()) -> nil.
make_styles_template(Filepath) ->
_ = begin
_pipe = <<"
"/utf8>>,
simplifile:write(Filepath, _pipe)
end,
nil.
-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 = <<"
<!doctype html>
<html lang='en'>
<head>
<meta charset='utf-8' />
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<title>🚧 {app_name}</title>
<link rel='stylesheet' href='/priv/static/{app_name}.css'>
<script type='module' src='/priv/static/entry.mjs'></script>
</head>
<body>
<div id='app'></div>
</body>
</html>
"/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 => <<"scaffold_gleam"/utf8>>,
function => <<"main"/utf8>>,
line => 8})
end,
_ = simplifile:create_directory(<<"scripts"/utf8>>),
_ = simplifile:create_directory(<<"src/styles"/utf8>>),
make_styles_template(<<"src/styles/app.scss"/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>>).