-module(lightspeed@tooling@generator).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/lightspeed/tooling/generator.gleam").
-export([parse/1, parse_error_label/1, generate_auth/1, new/1, generate_json/2, generate_html/2, generate_live/2, scaffold/1, app_name/1, module_name/1, files/1, features/1, has_file/2, file_content/2, fixture_signature/1, boot_ci_contract/1, production_contract/1, resource_contract/2, auth_contract/1, fixture_snapshots/0, snapshot_signature/0]).
-export_type([command/0, parse_error/0, file/0, project/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(" Deterministic project and resource scaffold generator for M25.\n").
-type command() :: {new, binary()} |
{gen_live, binary(), binary()} |
{gen_html, binary(), binary()} |
{gen_json, binary(), binary()} |
{gen_auth, binary()}.
-type parse_error() :: {missing_arguments, binary()} |
{unsupported_command, binary()}.
-type file() :: {file, binary(), binary()}.
-opaque project() :: {project, binary(), binary(), list(file()), list(binary())}.
-file("src/lightspeed/tooling/generator.gleam", 49).
?DOC(
" Parse CLI-like command args.\n"
"\n"
" Supported shapes:\n"
"\n"
" - `[\"new\", \"<app>\"]`\n"
" - `[\"gen.live\", \"<app>\", \"<resource>\"]`\n"
" - `[\"gen.html\", \"<app>\", \"<resource>\"]`\n"
" - `[\"gen.json\", \"<app>\", \"<resource>\"]`\n"
" - `[\"gen.auth\", \"<app>\"]`\n"
).
-spec parse(list(binary())) -> {ok, command()} | {error, parse_error()}.
parse(Args) ->
case Args of
[<<"new"/utf8>>, App_name] ->
{ok, {new, App_name}};
[<<"gen.live"/utf8>>, App_name@1, Resource] ->
{ok, {gen_live, App_name@1, Resource}};
[<<"gen.html"/utf8>>, App_name@2, Resource@1] ->
{ok, {gen_html, App_name@2, Resource@1}};
[<<"gen.json"/utf8>>, App_name@3, Resource@2] ->
{ok, {gen_json, App_name@3, Resource@2}};
[<<"gen.auth"/utf8>>, App_name@4] ->
{ok, {gen_auth, App_name@4}};
[<<"new"/utf8>>] ->
{error, {missing_arguments, <<"new"/utf8>>}};
[<<"gen.live"/utf8>>, _ | _] ->
{error, {missing_arguments, <<"gen.live"/utf8>>}};
[<<"gen.html"/utf8>>, _ | _] ->
{error, {missing_arguments, <<"gen.html"/utf8>>}};
[<<"gen.json"/utf8>>, _ | _] ->
{error, {missing_arguments, <<"gen.json"/utf8>>}};
[<<"gen.auth"/utf8>>] ->
{error, {missing_arguments, <<"gen.auth"/utf8>>}};
[] ->
{error, {missing_arguments, <<"command"/utf8>>}};
[Command | _] ->
{error, {unsupported_command, Command}}
end.
-file("src/lightspeed/tooling/generator.gleam", 70).
?DOC(" Stable parse-error label for fixtures.\n").
-spec parse_error_label(parse_error()) -> binary().
parse_error_label(Error) ->
case Error of
{missing_arguments, Command} ->
<<"missing_arguments:"/utf8, Command/binary>>;
{unsupported_command, Command@1} ->
<<"unsupported_command:"/utf8, Command@1/binary>>
end.
-file("src/lightspeed/tooling/generator.gleam", 686).
-spec contains(list(binary()), binary()) -> boolean().
contains(Values, Needle) ->
case Values of
[] ->
false;
[Value | Rest] ->
case Value =:= Needle of
true ->
true;
false ->
contains(Rest, Needle)
end
end.
-file("src/lightspeed/tooling/generator.gleam", 636).
-spec add_feature(project(), binary()) -> project().
add_feature(Project, Feature) ->
case contains(erlang:element(5, Project), Feature) of
true ->
Project;
false ->
{project,
erlang:element(2, Project),
erlang:element(3, Project),
erlang:element(4, Project),
[Feature | erlang:element(5, Project)]}
end.
-file("src/lightspeed/tooling/generator.gleam", 653).
-spec remove_path(list(file()), binary()) -> list(file()).
remove_path(Files_rev, Path) ->
case Files_rev of
[] ->
[];
[Entry | Rest] ->
case erlang:element(2, Entry) =:= Path of
true ->
remove_path(Rest, Path);
false ->
[Entry | remove_path(Rest, Path)]
end
end.
-file("src/lightspeed/tooling/generator.gleam", 649).
-spec upsert_file(list(file()), file()) -> list(file()).
upsert_file(Files_rev, Next) ->
[Next | remove_path(Files_rev, erlang:element(2, Next))].
-file("src/lightspeed/tooling/generator.gleam", 643).
-spec put_file(project(), binary(), binary()) -> project().
put_file(Project, Path, Content) ->
Files_rev = upsert_file(erlang:element(4, Project), {file, Path, Content}),
{project,
erlang:element(2, Project),
erlang:element(3, Project),
Files_rev,
erlang:element(5, Project)}.
-file("src/lightspeed/tooling/generator.gleam", 380).
?DOC(" Generate a `phx.gen.auth`-style auth scaffold.\n").
-spec generate_auth(project()) -> project().
generate_auth(Project) ->
App = erlang:element(2, Project),
_pipe = Project,
_pipe@1 = put_file(
_pipe,
<<<<"src/"/utf8, App/binary>>/binary, "/auth.gleam"/utf8>>,
<<"pub type User {\n User(id: String, email: String, tenant_id: String)\n}\n"/utf8>>
),
_pipe@2 = put_file(
_pipe@1,
<<<<"src/"/utf8, App/binary>>/binary, "/auth/password.gleam"/utf8>>,
<<"import gleam/string\n\npub fn validate(password: String) -> Bool {\n string.length(password) >= 12\n && string.contains(password, \"0\")\n && string.contains(password, \"A\")\n}\n"/utf8>>
),
_pipe@3 = put_file(
_pipe@2,
<<<<"src/"/utf8, App/binary>>/binary, "/auth/policy.gleam"/utf8>>,
<<<<"import "/utf8, App/binary>>/binary,
"/auth\n\npub fn can_impersonate(_user: auth.User) -> Bool {\n False\n}\n"/utf8>>
),
_pipe@4 = put_file(
_pipe@3,
<<<<"src/"/utf8, App/binary>>/binary, "/auth/session.gleam"/utf8>>,
<<<<"import "/utf8, App/binary>>/binary,
"/auth\n\npub fn login(email: String, tenant_id: String) -> auth.User {\n auth.User(id: \"u-1\", email: email, tenant_id: tenant_id)\n}\n"/utf8>>
),
_pipe@5 = put_file(
_pipe@4,
<<<<"test/"/utf8, App/binary>>/binary, "/auth_test.gleam"/utf8>>,
<<<<"import gleeunit/should\nimport "/utf8, App/binary>>/binary,
"/auth/password\n\npub fn generated_auth_defaults_test() {\n password.validate(\"A1234567890x\")\n |> should.equal(True)\n\n password.validate(\"short\")\n |> should.equal(False)\n}\n"/utf8>>
),
_pipe@6 = put_file(
_pipe@5,
<<<<"test/"/utf8, App/binary>>/binary, "/auth_policy_test.gleam"/utf8>>,
<<<<<<<<"import gleeunit/should\nimport "/utf8, App/binary>>/binary,
"/auth\nimport "/utf8>>/binary,
App/binary>>/binary,
"/auth/policy\n\npub fn generated_auth_policy_test() {\n let user = auth.User(id: \"u-1\", email: \"a@example.com\", tenant_id: \"t-1\")\n policy.can_impersonate(user)\n |> should.equal(False)\n}\n"/utf8>>
),
_pipe@7 = put_file(
_pipe@6,
<<<<"test/"/utf8, App/binary>>/binary,
"/integration/auth_flow_test.gleam"/utf8>>,
<<<<"import gleeunit/should\nimport "/utf8, App/binary>>/binary,
"/auth/session\n\npub fn generated_auth_session_flow_test() {\n session.login(\"a@example.com\", \"t-1\")\n |> should.equal(session.login(\"a@example.com\", \"t-1\"))\n}\n"/utf8>>
),
_pipe@8 = put_file(
_pipe@7,
<<"examples/auth/README.md"/utf8>>,
<<"# Auth Example\n\nGenerated auth fixture with password, policy, and session scaffolding.\n"/utf8>>
),
add_feature(_pipe@8, <<"gen.auth"/utf8>>).
-file("src/lightspeed/tooling/generator.gleam", 697).
-spec normalize_name(binary()) -> binary().
normalize_name(Value) ->
_pipe = Value,
_pipe@1 = string:lowercase(_pipe),
_pipe@2 = gleam@string:replace(_pipe@1, <<"-"/utf8>>, <<"_"/utf8>>),
gleam@string:replace(_pipe@2, <<" "/utf8>>, <<"_"/utf8>>).
-file("src/lightspeed/tooling/generator.gleam", 728).
-spec join_with(binary(), list(binary())) -> binary().
join_with(Separator, Values) ->
case Values of
[] ->
<<""/utf8>>;
[Value] ->
Value;
[Value@1 | Rest] ->
<<<<Value@1/binary, Separator/binary>>/binary,
(join_with(Separator, Rest))/binary>>
end.
-file("src/lightspeed/tooling/generator.gleam", 721).
-spec capitalize(binary()) -> binary().
capitalize(Value) ->
case gleam_stdlib:string_pop_grapheme(Value) of
{error, _} ->
<<""/utf8>>;
{ok, {First, Rest}} ->
<<(string:uppercase(First))/binary, Rest/binary>>
end.
-file("src/lightspeed/tooling/generator.gleam", 714).
-spec capitalize_parts(list(binary()), list(binary())) -> list(binary()).
capitalize_parts(Parts, Rev) ->
case Parts of
[] ->
lists:reverse(Rev);
[Part | Rest] ->
capitalize_parts(Rest, [capitalize(Part) | Rev])
end.
-file("src/lightspeed/tooling/generator.gleam", 704).
-spec to_module_name(binary()) -> binary().
to_module_name(Value) ->
Parts = begin
_pipe = Value,
_pipe@1 = normalize_name(_pipe),
_pipe@2 = gleam@string:split(_pipe@1, <<"_"/utf8>>),
capitalize_parts(_pipe@2, [])
end,
join_with(<<""/utf8>>, Parts).
-file("src/lightspeed/tooling/generator.gleam", 103).
?DOC(
" Build a new project scaffold comparable to `phx.new`.\n"
"\n"
" M25 baseline deepens output toward production defaults:\n"
" - data scope contracts\n"
" - migration-ready directory layout\n"
" - integration-test scaffolds\n"
" - governance/testing defaults\n"
).
-spec new(binary()) -> project().
new(App_name) ->
Module_name = to_module_name(App_name),
App = normalize_name(App_name),
_pipe = {project, App, Module_name, [], []},
_pipe@1 = put_file(
_pipe,
<<".tool-versions"/utf8>>,
<<"erlang 28.5\ngleam 1.16.0\nrebar 3.27.0\n"/utf8>>
),
_pipe@2 = put_file(
_pipe@1,
<<".gitignore"/utf8>>,
<<"build/\n*.beam\nerl_crash.dump\n"/utf8>>
),
_pipe@3 = put_file(
_pipe@2,
<<"gleam.toml"/utf8>>,
<<<<"name = \""/utf8, App/binary>>/binary,
"\"\nversion = \"0.1.0\"\ndescription = \"Generated by lightspeed tooling\"\nlicences = [\"Unlicense\"]\ntarget = \"erlang\"\ngleam = \">= 1.16.0\"\n\n[dependencies]\ngleam_stdlib = \">= 1.0.0 and < 2.0.0\"\ngleam_otp = \">= 1.2.0 and < 2.0.0\"\n\n[dev-dependencies]\ngleeunit = \">= 1.10.0 and < 2.0.0\"\n"/utf8>>
),
_pipe@4 = put_file(
_pipe@3,
<<"Makefile"/utf8>>,
<<"setup:\n\tgleam deps download\n\nfmt:\n\tgleam format src test\n\nfmt-check:\n\tgleam format --check src test\n\ncheck:\n\tgleam check --target erlang\n\ntest:\n\tgleam test --target erlang\n\nci: fmt-check check test\n"/utf8>>
),
_pipe@5 = put_file(
_pipe@4,
<<"AGENTS.md"/utf8>>,
<<"# AGENTS\n\nGenerated project policy:\n\n1. Follow Gleam best practices.\n2. Add tests for every code change.\n3. Require RFC + ADR for every code change.\n4. Prefer simplicity first, then performance.\n"/utf8>>
),
_pipe@6 = put_file(
_pipe@5,
<<"README.md"/utf8>>,
<<<<"# "/utf8, Module_name/binary>>/binary,
"\n\nGenerated by Lightspeed M25 scaffold.\n\n## Commands\n\n- make setup\n- make ci\n\n## Governance\n\nEvery code change requires an RFC and ADR.\n"/utf8>>
),
_pipe@7 = put_file(
_pipe@6,
<<"docs/testing.md"/utf8>>,
<<"# Testing\n\nRun `make ci` before commit.\nAdd tests for every new or changed behavior.\n"/utf8>>
),
_pipe@8 = put_file(
_pipe@7,
<<"docs/data_policy.md"/utf8>>,
<<"# Data Policy\n\nAll repository operations must be scoped by tenant context.\n"/utf8>>
),
_pipe@9 = put_file(
_pipe@8,
<<"docs/migration_playbook.md"/utf8>>,
<<"# Migration Playbook\n\nAdd one migration per resource and keep rollback notes beside each migration.\n"/utf8>>
),
_pipe@10 = put_file(
_pipe@9,
<<"rfcs/0000-template.md"/utf8>>,
<<"# RFC-0000 Template\n"/utf8>>
),
_pipe@11 = put_file(
_pipe@10,
<<"adrs/0000-template.md"/utf8>>,
<<"# ADR-0000 Template\n"/utf8>>
),
_pipe@12 = put_file(
_pipe@11,
<<"priv/repo/migrations/.keep"/utf8>>,
<<""/utf8>>
),
_pipe@13 = put_file(_pipe@12, <<"priv/static/.keep"/utf8>>, <<""/utf8>>),
_pipe@14 = put_file(
_pipe@13,
<<<<"src/"/utf8, App/binary>>/binary, ".gleam"/utf8>>,
<<<<"pub fn banner() -> String {\n \""/utf8, Module_name/binary>>/binary,
"\"\n}\n"/utf8>>
),
_pipe@15 = put_file(
_pipe@14,
<<<<"src/"/utf8, App/binary>>/binary, "/app.gleam"/utf8>>,
<<<<<<<<"import "/utf8, App/binary>>/binary,
"\n\npub fn start() -> String {\n "/utf8>>/binary,
App/binary>>/binary,
".banner()\n}\n"/utf8>>
),
_pipe@16 = put_file(
_pipe@15,
<<<<"src/"/utf8, App/binary>>/binary, "/web/router.gleam"/utf8>>,
<<"pub fn routes() -> List(#(String, String)) {\n [#(\"GET\", \"/health\"), #(\"GET\", \"/\")]\n}\n"/utf8>>
),
_pipe@17 = put_file(
_pipe@16,
<<<<"src/"/utf8, App/binary>>/binary, "/web/endpoint.gleam"/utf8>>,
<<<<"import "/utf8, App/binary>>/binary,
"/web/router\n\npub fn route_count() -> Int {\n list.length(router.routes())\n}\n"/utf8>>
),
_pipe@18 = put_file(
_pipe@17,
<<<<"src/"/utf8, App/binary>>/binary, "/data/scope.gleam"/utf8>>,
<<"pub type Role {\n Reader\n Editor\n Admin\n}\n\npub type Scope {\n Scope(tenant_id: String, role: Role)\n}\n\npub fn can_write(scope: Scope) -> Bool {\n case scope.role {\n Editor -> True\n Admin -> True\n Reader -> False\n }\n}\n"/utf8>>
),
_pipe@19 = put_file(
_pipe@18,
<<<<"src/"/utf8, App/binary>>/binary, "/data/repository.gleam"/utf8>>,
<<<<"import "/utf8, App/binary>>/binary,
"/data/scope\n\npub fn authorize_read(_scope: scope.Scope, _tenant_id: String) -> Bool {\n True\n}\n\npub fn authorize_write(scope: scope.Scope, tenant_id: String) -> Bool {\n scope.tenant_id == tenant_id && scope.can_write(scope)\n}\n"/utf8>>
),
_pipe@20 = put_file(
_pipe@19,
<<<<"test/"/utf8, App/binary>>/binary, "_test.gleam"/utf8>>,
<<<<<<<<<<<<"import gleeunit/should\nimport "/utf8, App/binary>>/binary,
"\n\npub fn generated_banner_test() {\n "/utf8>>/binary,
App/binary>>/binary,
".banner()\n |> should.equal(\""/utf8>>/binary,
Module_name/binary>>/binary,
"\")\n}\n"/utf8>>
),
_pipe@21 = put_file(
_pipe@20,
<<<<"test/"/utf8, App/binary>>/binary,
"/integration/app_boot_test.gleam"/utf8>>,
<<<<"import gleeunit/should\nimport "/utf8, App/binary>>/binary,
"/web/endpoint\n\npub fn generated_boot_routes_test() {\n endpoint.route_count()\n |> should.equal(2)\n}\n"/utf8>>
),
_pipe@22 = put_file(
_pipe@21,
<<<<"test/"/utf8, App/binary>>/binary,
"/integration/scope_policy_test.gleam"/utf8>>,
<<<<<<<<"import gleeunit/should\nimport "/utf8, App/binary>>/binary,
"/data/repository\nimport "/utf8>>/binary,
App/binary>>/binary,
"/data/scope\n\npub fn generated_scope_policy_test() {\n let reader = scope.Scope(tenant_id: \"t-1\", role: scope.Reader)\n let editor = scope.Scope(tenant_id: \"t-1\", role: scope.Editor)\n\n repository.authorize_write(reader, \"t-1\")\n |> should.equal(False)\n\n repository.authorize_write(editor, \"t-1\")\n |> should.equal(True)\n\n repository.authorize_write(editor, \"t-2\")\n |> should.equal(False)\n}\n"/utf8>>
),
add_feature(_pipe@22, <<"new"/utf8>>).
-file("src/lightspeed/tooling/generator.gleam", 327).
?DOC(" Generate a `phx.gen.json`-style resource scaffold.\n").
-spec generate_json(project(), binary()) -> project().
generate_json(Project, Resource) ->
App = erlang:element(2, Project),
Name = normalize_name(Resource),
_pipe = Project,
_pipe@1 = put_file(
_pipe,
<<<<<<<<"src/"/utf8, App/binary>>/binary, "/api/"/utf8>>/binary,
Name/binary>>/binary,
"_serializer.gleam"/utf8>>,
<<<<<<<<"pub fn encode_list() -> String {\n \"{\\\"data\\\":[],\\\"resource\\\":\\\""/utf8,
Name/binary>>/binary,
"\\\"}\"\n}\n\npub fn encode_item(id: String) -> String {\n \"{\\\"data\\\":{\\\"id\\\":\\\"\" <> id <> \"\\\"},\\\"resource\\\":\\\""/utf8>>/binary,
Name/binary>>/binary,
"\\\"}\"\n}\n"/utf8>>
),
_pipe@2 = put_file(
_pipe@1,
<<<<<<<<"src/"/utf8, App/binary>>/binary, "/controllers/"/utf8>>/binary,
Name/binary>>/binary,
"_json.gleam"/utf8>>,
<<<<<<<<<<<<<<<<"import "/utf8, App/binary>>/binary, "/api/"/utf8>>/binary,
Name/binary>>/binary,
"_serializer\n\npub fn index() -> String {\n "/utf8>>/binary,
Name/binary>>/binary,
"_serializer.encode_list()\n}\n\npub fn show(id: String) -> String {\n "/utf8>>/binary,
Name/binary>>/binary,
"_serializer.encode_item(id)\n}\n"/utf8>>
),
_pipe@3 = put_file(
_pipe@2,
<<<<<<<<"test/"/utf8, App/binary>>/binary, "/"/utf8>>/binary,
Name/binary>>/binary,
"_json_test.gleam"/utf8>>,
<<<<<<<<<<<<<<<<"import gleeunit/should\nimport "/utf8, App/binary>>/binary,
"/controllers/"/utf8>>/binary,
Name/binary>>/binary,
"_json\n\npub fn generated_json_resource_test() {\n "/utf8>>/binary,
Name/binary>>/binary,
"_json.index()\n |> should.equal(\"{\\\"data\\\":[],\\\"resource\\\":\\\""/utf8>>/binary,
Name/binary>>/binary,
"\\\"}\")\n}\n"/utf8>>
),
_pipe@4 = put_file(
_pipe@3,
<<<<<<<<"test/"/utf8, App/binary>>/binary, "/integration/"/utf8>>/binary,
Name/binary>>/binary,
"_json_flow_test.gleam"/utf8>>,
<<<<<<<<<<<<<<<<"import gleeunit/should\nimport "/utf8, App/binary>>/binary,
"/controllers/"/utf8>>/binary,
Name/binary>>/binary,
"_json\n\npub fn generated_json_show_test() {\n "/utf8>>/binary,
Name/binary>>/binary,
"_json.show(\"42\")\n |> should.equal(\"{\\\"data\\\":{\\\"id\\\":\\\"42\\\"},\\\"resource\\\":\\\""/utf8>>/binary,
Name/binary>>/binary,
"\\\"}\")\n}\n"/utf8>>
),
add_feature(_pipe@4, <<"gen.json:"/utf8, Name/binary>>).
-file("src/lightspeed/tooling/generator.gleam", 281).
?DOC(" Generate a `phx.gen.html`-style resource scaffold.\n").
-spec generate_html(project(), binary()) -> project().
generate_html(Project, Resource) ->
App = erlang:element(2, Project),
Name = normalize_name(Resource),
Module_name = to_module_name(Name),
_pipe = Project,
_pipe@1 = put_file(
_pipe,
<<<<<<<<"src/"/utf8, App/binary>>/binary, "/controllers/"/utf8>>/binary,
Name/binary>>/binary,
"_html.gleam"/utf8>>,
<<<<<<<<"pub fn index() -> String {\n \"<h1>"/utf8,
Module_name/binary>>/binary,
" Index</h1>\"\n}\n\npub fn show(id: String) -> String {\n \"<article data-id=\\\"\" <> id <> \"\\\">"/utf8>>/binary,
Module_name/binary>>/binary,
" Show</article>\"\n}\n"/utf8>>
),
_pipe@2 = put_file(
_pipe@1,
<<<<"priv/templates/"/utf8, Name/binary>>/binary, "/index.html"/utf8>>,
<<<<"<h1>"/utf8, Module_name/binary>>/binary, " Index</h1>\n"/utf8>>
),
_pipe@3 = put_file(
_pipe@2,
<<<<<<<<"test/"/utf8, App/binary>>/binary, "/"/utf8>>/binary,
Name/binary>>/binary,
"_html_test.gleam"/utf8>>,
<<<<<<<<<<<<<<<<"import gleeunit/should\nimport "/utf8, App/binary>>/binary,
"/controllers/"/utf8>>/binary,
Name/binary>>/binary,
"_html\n\npub fn generated_html_resource_test() {\n "/utf8>>/binary,
Name/binary>>/binary,
"_html.index()\n |> should.equal(\"<h1>"/utf8>>/binary,
Module_name/binary>>/binary,
" Index</h1>\")\n}\n"/utf8>>
),
_pipe@4 = put_file(
_pipe@3,
<<<<<<<<"test/"/utf8, App/binary>>/binary, "/integration/"/utf8>>/binary,
Name/binary>>/binary,
"_html_flow_test.gleam"/utf8>>,
<<<<<<<<<<<<<<<<"import gleeunit/should\nimport "/utf8, App/binary>>/binary,
"/controllers/"/utf8>>/binary,
Name/binary>>/binary,
"_html\n\npub fn generated_html_show_test() {\n "/utf8>>/binary,
Name/binary>>/binary,
"_html.show(\"42\")\n |> should.equal(\"<article data-id=\\\"42\\\">"/utf8>>/binary,
Module_name/binary>>/binary,
" Show</article>\")\n}\n"/utf8>>
),
add_feature(_pipe@4, <<"gen.html:"/utf8, Name/binary>>).
-file("src/lightspeed/tooling/generator.gleam", 211).
?DOC(" Generate a `phx.gen.live`-style resource scaffold.\n").
-spec generate_live(project(), binary()) -> project().
generate_live(Project, Resource) ->
App = erlang:element(2, Project),
Name = normalize_name(Resource),
Module_name = to_module_name(Name),
_pipe = Project,
_pipe@1 = put_file(
_pipe,
<<<<<<<<"src/"/utf8, App/binary>>/binary, "/live/"/utf8>>/binary,
Name/binary>>/binary,
"_live.gleam"/utf8>>,
<<<<<<<<"pub type Model {\n Model(count: Int)\n}\n\npub type Msg {\n Increment\n Decrement\n}\n\npub fn init() -> Model {\n Model(count: 0)\n}\n\npub fn update(model: Model, msg: Msg) -> Model {\n case msg {\n Increment -> Model(..model, count: model.count + 1)\n Decrement -> Model(..model, count: model.count - 1)\n }\n}\n\npub fn render(model: Model) -> String {\n \"<section data-resource=\\\""/utf8,
Name/binary>>/binary,
"\\\"><h1>"/utf8>>/binary,
Module_name/binary>>/binary,
"</h1><span>\" <> int.to_string(model.count) <> \"</span></section>\"\n}\n"/utf8>>
),
_pipe@2 = put_file(
_pipe@1,
<<<<<<<<"src/"/utf8, App/binary>>/binary, "/data/"/utf8>>/binary,
Name/binary>>/binary,
"_repository.gleam"/utf8>>,
<<<<<<<<"import "/utf8, App/binary>>/binary,
"/data/repository\nimport "/utf8>>/binary,
App/binary>>/binary,
"/data/scope\n\npub fn list(scope scope: scope.Scope) -> Result(List(String), String) {\n case repository.authorize_read(scope, scope.tenant_id) {\n True -> Ok([])\n False -> Error(\"forbidden\")\n }\n}\n\npub fn insert(scope scope: scope.Scope, tenant_id: String, _attrs: String) -> Result(String, String) {\n case repository.authorize_write(scope, tenant_id) {\n True -> Ok(\"created\")\n False -> Error(\"forbidden\")\n }\n}\n"/utf8>>
),
_pipe@3 = put_file(
_pipe@2,
<<<<"priv/repo/migrations/20260508000000_create_"/utf8, Name/binary>>/binary,
".sql"/utf8>>,
<<<<<<<<"-- up\n-- create table "/utf8, Name/binary>>/binary,
" (\n-- id text primary key,\n-- tenant_id text not null,\n-- inserted_at text not null\n-- );\n\n-- down\n-- drop table "/utf8>>/binary,
Name/binary>>/binary,
";\n"/utf8>>
),
_pipe@4 = put_file(
_pipe@3,
<<<<<<<<"test/"/utf8, App/binary>>/binary, "/"/utf8>>/binary,
Name/binary>>/binary,
"_live_test.gleam"/utf8>>,
<<<<<<<<<<<<<<<<<<<<<<<<"import gleeunit/should\nimport "/utf8,
App/binary>>/binary,
"/live/"/utf8>>/binary,
Name/binary>>/binary,
"_live\n\npub fn generated_live_render_test() {\n "/utf8>>/binary,
Name/binary>>/binary,
"_live.init()\n |> "/utf8>>/binary,
Name/binary>>/binary,
"_live.render\n |> should.equal("/utf8>>/binary,
Name/binary>>/binary,
"_live.init() |> "/utf8>>/binary,
Name/binary>>/binary,
"_live.render)\n}\n"/utf8>>
),
_pipe@5 = put_file(
_pipe@4,
<<<<<<<<"test/"/utf8, App/binary>>/binary, "/integration/"/utf8>>/binary,
Name/binary>>/binary,
"_live_flow_test.gleam"/utf8>>,
<<<<<<<<<<<<<<<<<<<<"import gleeunit/should\nimport "/utf8, App/binary>>/binary,
"/data/"/utf8>>/binary,
Name/binary>>/binary,
"_repository\nimport "/utf8>>/binary,
App/binary>>/binary,
"/data/scope\n\npub fn generated_live_resource_scope_test() {\n let scope_reader = scope.Scope(tenant_id: \"t-1\", role: scope.Reader)\n let scope_editor = scope.Scope(tenant_id: \"t-1\", role: scope.Editor)\n\n "/utf8>>/binary,
Name/binary>>/binary,
"_repository.insert(scope_reader, \"t-1\", \"{}\")\n |> should.equal(Error(\"forbidden\"))\n\n "/utf8>>/binary,
Name/binary>>/binary,
"_repository.insert(scope_editor, \"t-1\", \"{}\")\n |> should.equal(Ok(\"created\"))\n}\n"/utf8>>
),
_pipe@6 = put_file(
_pipe@5,
<<<<"examples/crud/"/utf8, Name/binary>>/binary, ".md"/utf8>>,
<<<<"# CRUD Example "/utf8, Module_name/binary>>/binary,
"\n\nGenerated live resource fixture with scope-aware repository and migration scaffold.\n"/utf8>>
),
add_feature(_pipe@6, <<"gen.live:"/utf8, Name/binary>>).
-file("src/lightspeed/tooling/generator.gleam", 78).
?DOC(" Execute one command into a generated project tree.\n").
-spec scaffold(command()) -> project().
scaffold(Command) ->
case Command of
{new, App_name} ->
new(App_name);
{gen_live, App_name@1, Resource} ->
_pipe = new(App_name@1),
generate_live(_pipe, Resource);
{gen_html, App_name@2, Resource@1} ->
_pipe@1 = new(App_name@2),
generate_html(_pipe@1, Resource@1);
{gen_json, App_name@3, Resource@2} ->
_pipe@2 = new(App_name@3),
generate_json(_pipe@2, Resource@2);
{gen_auth, App_name@4} ->
_pipe@3 = new(App_name@4),
generate_auth(_pipe@3)
end.
-file("src/lightspeed/tooling/generator.gleam", 432).
?DOC(" Generated app name.\n").
-spec app_name(project()) -> binary().
app_name(Project) ->
erlang:element(2, Project).
-file("src/lightspeed/tooling/generator.gleam", 437).
?DOC(" Generated module name.\n").
-spec module_name(project()) -> binary().
module_name(Project) ->
erlang:element(3, Project).
-file("src/lightspeed/tooling/generator.gleam", 442).
?DOC(" Generated files in stable order.\n").
-spec files(project()) -> list(file()).
files(Project) ->
lists:reverse(erlang:element(4, Project)).
-file("src/lightspeed/tooling/generator.gleam", 447).
?DOC(" Generated feature labels in stable order.\n").
-spec features(project()) -> list(binary()).
features(Project) ->
lists:reverse(erlang:element(5, Project)).
-file("src/lightspeed/tooling/generator.gleam", 664).
-spec has_file_in(list(file()), binary()) -> boolean().
has_file_in(Files_rev, Path) ->
case Files_rev of
[] ->
false;
[Entry | Rest] ->
case erlang:element(2, Entry) =:= Path of
true ->
true;
false ->
has_file_in(Rest, Path)
end
end.
-file("src/lightspeed/tooling/generator.gleam", 452).
?DOC(" Check for a generated file.\n").
-spec has_file(project(), binary()) -> boolean().
has_file(Project, Path) ->
has_file_in(erlang:element(4, Project), Path).
-file("src/lightspeed/tooling/generator.gleam", 675).
-spec read_file(list(file()), binary()) -> gleam@option:option(binary()).
read_file(Files_rev, Path) ->
case Files_rev of
[] ->
none;
[Entry | Rest] ->
case erlang:element(2, Entry) =:= Path of
true ->
{some, erlang:element(3, Entry)};
false ->
read_file(Rest, Path)
end
end.
-file("src/lightspeed/tooling/generator.gleam", 457).
?DOC(" Read generated file content when present.\n").
-spec file_content(project(), binary()) -> gleam@option:option(binary()).
file_content(Project, Path) ->
read_file(erlang:element(4, Project), Path).
-file("src/lightspeed/tooling/generator.gleam", 629).
-spec file_paths(list(file())) -> list(binary()).
file_paths(Files) ->
case Files of
[] ->
[];
[{file, Path, _} | Rest] ->
[Path | file_paths(Rest)]
end.
-file("src/lightspeed/tooling/generator.gleam", 462).
?DOC(" Stable fixture signature for reproducible generator outputs.\n").
-spec fixture_signature(project()) -> binary().
fixture_signature(Project) ->
<<<<<<<<<<<<<<"m25.v"/utf8, (erlang:integer_to_binary(2))/binary>>/binary,
"|app="/utf8>>/binary,
(erlang:element(2, Project))/binary>>/binary,
"|features="/utf8>>/binary,
(join_with(<<","/utf8>>, features(Project)))/binary>>/binary,
"|files="/utf8>>/binary,
(join_with(<<","/utf8>>, file_paths(files(Project))))/binary>>.
-file("src/lightspeed/tooling/generator.gleam", 622).
-spec all_paths(list(binary()), project()) -> boolean().
all_paths(Paths, Project) ->
case Paths of
[] ->
true;
[Path | Rest] ->
has_file(Project, Path) andalso all_paths(Rest, Project)
end.
-file("src/lightspeed/tooling/generator.gleam", 614).
-spec has_rfc_adr_mentions(project()) -> boolean().
has_rfc_adr_mentions(Project) ->
case file_content(Project, <<"README.md"/utf8>>) of
none ->
false;
{some, Content} ->
gleam_stdlib:contains_string(Content, <<"RFC"/utf8>>) andalso gleam_stdlib:contains_string(
Content,
<<"ADR"/utf8>>
)
end.
-file("src/lightspeed/tooling/generator.gleam", 601).
-spec makefile_has_targets(project()) -> boolean().
makefile_has_targets(Project) ->
case file_content(Project, <<"Makefile"/utf8>>) of
none ->
false;
{some, Content} ->
((((gleam_stdlib:contains_string(Content, <<"setup:"/utf8>>) andalso gleam_stdlib:contains_string(
Content,
<<"fmt:"/utf8>>
))
andalso gleam_stdlib:contains_string(Content, <<"fmt-check:"/utf8>>))
andalso gleam_stdlib:contains_string(Content, <<"check:"/utf8>>))
andalso gleam_stdlib:contains_string(Content, <<"test:"/utf8>>))
andalso gleam_stdlib:contains_string(Content, <<"ci:"/utf8>>)
end.
-file("src/lightspeed/tooling/generator.gleam", 474).
?DOC(" Validate that generated project satisfies baseline boot/CI constraints.\n").
-spec boot_ci_contract(project()) -> boolean().
boot_ci_contract(Project) ->
App = erlang:element(2, Project),
Required_files = [<<".tool-versions"/utf8>>,
<<".gitignore"/utf8>>,
<<"gleam.toml"/utf8>>,
<<"Makefile"/utf8>>,
<<"AGENTS.md"/utf8>>,
<<"README.md"/utf8>>,
<<"docs/testing.md"/utf8>>,
<<"docs/data_policy.md"/utf8>>,
<<"docs/migration_playbook.md"/utf8>>,
<<"rfcs/0000-template.md"/utf8>>,
<<"adrs/0000-template.md"/utf8>>,
<<<<"src/"/utf8, App/binary>>/binary, ".gleam"/utf8>>,
<<<<"src/"/utf8, App/binary>>/binary, "/data/scope.gleam"/utf8>>,
<<<<"src/"/utf8, App/binary>>/binary, "/data/repository.gleam"/utf8>>,
<<<<"test/"/utf8, App/binary>>/binary, "_test.gleam"/utf8>>,
<<<<"test/"/utf8, App/binary>>/binary,
"/integration/app_boot_test.gleam"/utf8>>,
<<<<"test/"/utf8, App/binary>>/binary,
"/integration/scope_policy_test.gleam"/utf8>>],
Required_targets = makefile_has_targets(Project),
Governance = has_rfc_adr_mentions(Project),
(all_paths(Required_files, Project) andalso Required_targets) andalso Governance.
-file("src/lightspeed/tooling/generator.gleam", 596).
-spec has_migration_ready_layout(project()) -> boolean().
has_migration_ready_layout(Project) ->
has_file(Project, <<"priv/repo/migrations/.keep"/utf8>>) andalso has_file(
Project,
<<"docs/migration_playbook.md"/utf8>>
).
-file("src/lightspeed/tooling/generator.gleam", 579).
-spec has_data_scope_controls(project()) -> boolean().
has_data_scope_controls(Project) ->
App = erlang:element(2, Project),
case {file_content(
Project,
<<<<"src/"/utf8, App/binary>>/binary, "/data/scope.gleam"/utf8>>
),
file_content(
Project,
<<<<"src/"/utf8, App/binary>>/binary,
"/data/repository.gleam"/utf8>>
)} of
{{some, Scope_file}, {some, Repo_file}} ->
((gleam_stdlib:contains_string(
Scope_file,
<<"pub type Scope"/utf8>>
)
andalso gleam_stdlib:contains_string(
Scope_file,
<<"pub fn can_write"/utf8>>
))
andalso gleam_stdlib:contains_string(
Repo_file,
<<"authorize_write"/utf8>>
))
andalso gleam_stdlib:contains_string(
Repo_file,
<<"scope.tenant_id == tenant_id"/utf8>>
);
{_, _} ->
false
end.
-file("src/lightspeed/tooling/generator.gleam", 502).
?DOC(" Validate production-ready scaffold defaults from M25.\n").
-spec production_contract(project()) -> boolean().
production_contract(Project) ->
(boot_ci_contract(Project) andalso has_data_scope_controls(Project)) andalso has_migration_ready_layout(
Project
).
-file("src/lightspeed/tooling/generator.gleam", 509).
?DOC(" Validate that generated resource files include migration/integration scaffolds.\n").
-spec resource_contract(project(), binary()) -> boolean().
resource_contract(Project, Resource) ->
App = erlang:element(2, Project),
Name = normalize_name(Resource),
((((has_file(
Project,
<<<<<<<<"src/"/utf8, App/binary>>/binary, "/live/"/utf8>>/binary,
Name/binary>>/binary,
"_live.gleam"/utf8>>
)
andalso has_file(
Project,
<<<<<<<<"src/"/utf8, App/binary>>/binary, "/data/"/utf8>>/binary,
Name/binary>>/binary,
"_repository.gleam"/utf8>>
))
andalso has_file(
Project,
<<<<"priv/repo/migrations/20260508000000_create_"/utf8, Name/binary>>/binary,
".sql"/utf8>>
))
andalso has_file(
Project,
<<<<<<<<"test/"/utf8, App/binary>>/binary, "/integration/"/utf8>>/binary,
Name/binary>>/binary,
"_live_flow_test.gleam"/utf8>>
))
andalso has_file(
Project,
<<<<<<<<"test/"/utf8, App/binary>>/binary, "/integration/"/utf8>>/binary,
Name/binary>>/binary,
"_html_flow_test.gleam"/utf8>>
))
andalso has_file(
Project,
<<<<<<<<"test/"/utf8, App/binary>>/binary, "/integration/"/utf8>>/binary,
Name/binary>>/binary,
"_json_flow_test.gleam"/utf8>>
).
-file("src/lightspeed/tooling/generator.gleam", 534).
?DOC(" Validate auth scaffold defaults from M25.\n").
-spec auth_contract(project()) -> boolean().
auth_contract(Project) ->
App = erlang:element(2, Project),
(((has_file(
Project,
<<<<"src/"/utf8, App/binary>>/binary, "/auth/password.gleam"/utf8>>
)
andalso has_file(
Project,
<<<<"src/"/utf8, App/binary>>/binary, "/auth/policy.gleam"/utf8>>
))
andalso has_file(
Project,
<<<<"src/"/utf8, App/binary>>/binary, "/auth/session.gleam"/utf8>>
))
andalso has_file(
Project,
<<<<"test/"/utf8, App/binary>>/binary, "/auth_policy_test.gleam"/utf8>>
))
andalso has_file(
Project,
<<<<"test/"/utf8, App/binary>>/binary,
"/integration/auth_flow_test.gleam"/utf8>>
).
-file("src/lightspeed/tooling/generator.gleam", 545).
?DOC(" Deterministic fixture snapshots used by drift guards.\n").
-spec fixture_snapshots() -> list({binary(), binary()}).
fixture_snapshots() ->
Project = new(<<"demo_app"/utf8>>),
Full_stack = begin
_pipe = Project,
_pipe@1 = generate_live(_pipe, <<"posts"/utf8>>),
_pipe@2 = generate_html(_pipe@1, <<"posts"/utf8>>),
_pipe@3 = generate_json(_pipe@2, <<"posts"/utf8>>),
generate_auth(_pipe@3)
end,
Auth_only = begin
_pipe@4 = Project,
generate_auth(_pipe@4)
end,
[{<<"project"/utf8>>, fixture_signature(Project)},
{<<"auth_only"/utf8>>, fixture_signature(Auth_only)},
{<<"full_stack"/utf8>>, fixture_signature(Full_stack)}].
-file("src/lightspeed/tooling/generator.gleam", 565).
?DOC(" Deterministic snapshot signature over M25 generator fixtures.\n").
-spec snapshot_signature() -> binary().
snapshot_signature() ->
Entries = begin
_pipe = fixture_snapshots(),
gleam@list:map(
_pipe,
fun(Entry) ->
{Name, Signature} = Entry,
<<<<Name/binary, "="/utf8>>/binary, Signature/binary>>
end
)
end,
<<<<<<"generator_snapshots.v"/utf8, (erlang:integer_to_binary(2))/binary>>/binary,
"|"/utf8>>/binary,
(join_with(<<";"/utf8>>, Entries))/binary>>.