Sigra.Install.Injector (Sigra v1.20.0)

Copy Markdown View Source

Idempotent code injection for Sigra install generator.

This module handles injecting authentication-related code into existing files in the host application (router, config, test support). All injection functions check for a marker comment before injecting to ensure idempotency.

Summary

Functions

Applies a %Sigra.Install.Injection{} record, routing to the appropriate marker-based injection function based on the anchor.

Injects API token configuration into config.exs.

Injects API pipeline and routes into the router file.

Injects the passkey hook import and merged hook registration into assets/js/app.js when the standard Phoenix hook shape is present.

Injects Sigra configuration into config.exs.

Injects auth test helper import into conn_case.ex.

Injects JWT authentication routes into the router file.

Injects account lifecycle routes into the router file.

Injects OAuth provider configuration into config.exs.

Injects OAuth routes into the router file.

Injects the :sigra_lifecycle queue into Oban configuration.

Injects authentication pipeline and routes into the router file.

Injects Argon2 test speedup configuration into test.exs.

Injects Vault child spec into the application supervision tree.

Returns the list of files that the generator should include for account lifecycle features, including auth_hooks.ex.

Functions

apply(injection, opts \\ [])

@spec apply(
  Sigra.Install.Injection.t(),
  keyword()
) :: {:ok, :injected | :already_present} | {:error, term()}

Applies a %Sigra.Install.Injection{} record, routing to the appropriate marker-based injection function based on the anchor.

Returns {:ok, :injected} on first apply, {:ok, :already_present} on subsequent applies (idempotency primitive behind GEN-04).

Features never call Injector.inject_* functions directly; they return %Injection{} records from the injections/1 callback in Sigra.Install.Feature and the walker passes them here.

This is a thin adapter layer added for Phase 11 Wave 1 primitives. The legacy inject_router_plugs/2 / inject_config/2 / ... functions above continue to serve the monolith until Wave 4 swaps the monolith for the walker.

inject_api_config(file_contents, config_block)

@spec inject_api_config(String.t(), String.t()) ::
  {:ok, String.t()} | {:already_injected, String.t()}

Injects API token configuration into config.exs.

Returns {:ok, new_contents} if injection succeeds, or {:already_injected, contents} if the API marker is already present.

inject_api_routes(file_contents, route_code)

@spec inject_api_routes(String.t(), String.t()) ::
  {:ok, String.t()} | {:already_injected, String.t()}

Injects API pipeline and routes into the router file.

Adds an :api_authenticated pipeline with FetchBearer and RequireAuthenticated plugs, plus API token CRUD routes.

Returns {:ok, new_contents} if injection succeeds, or {:already_injected, contents} if the API marker is already present.

inject_app_js_passkeys(file_contents, injection_template)

@spec inject_app_js_passkeys(String.t(), String.t()) ::
  {:ok, String.t()}
  | {:already_injected, String.t()}
  | {:manual_action, String.t()}

Injects the passkey hook import and merged hook registration into assets/js/app.js when the standard Phoenix hook shape is present.

Marker detection is authoritative: once // Sigra passkeys:start exists, the file is treated as already injected on re-runs.

inject_config(file_contents, config_block)

@spec inject_config(String.t(), String.t()) ::
  {:ok, String.t()} | {:already_injected, String.t()}

Injects Sigra configuration into config.exs.

Returns {:ok, new_contents} if injection succeeds, or {:already_injected, contents} if the marker is already present.

inject_conn_case(file_contents, helper_code)

@spec inject_conn_case(String.t(), String.t()) ::
  {:ok, String.t()} | {:already_injected, String.t()}

Injects auth test helper import into conn_case.ex.

Returns {:ok, new_contents} if injection succeeds, or {:already_injected, contents} if the helper import is already present.

inject_jwt_routes(file_contents, route_code)

@spec inject_jwt_routes(String.t(), String.t()) ::
  {:ok, String.t()} | {:already_injected, String.t()}

Injects JWT authentication routes into the router file.

Adds unauthenticated /api/auth scope with token create, refresh, MFA, and revoke endpoints. Only used with --jwt flag.

Returns {:ok, new_contents} if injection succeeds, or {:already_injected, contents} if the JWT marker is already present.

inject_lifecycle_routes(file_contents, route_code)

@spec inject_lifecycle_routes(String.t(), String.t()) ::
  {:ok, String.t()} | {:already_injected, String.t()}

Injects account lifecycle routes into the router file.

Adds settings, email confirmation, and reactivation routes to the authenticated scope. Also includes the auth_hooks.ex file in the generator output list.

Routes injected:

  • live "/users/settings", SettingsLive, :index
  • live "/users/settings/confirm-email/:token", SettingsLive, :confirm_email
  • live "/users/reactivation", ReactivationLive, :index

Returns {:ok, new_contents} if injection succeeds, or {:already_injected, contents} if the lifecycle marker is already present.

inject_oauth_config(file_contents, config_block)

@spec inject_oauth_config(String.t(), String.t()) ::
  {:ok, String.t()} | {:already_injected, String.t()}

Injects OAuth provider configuration into config.exs.

Returns {:ok, new_contents} if injection succeeds, or {:already_injected, contents} if the OAuth marker is already present.

inject_oauth_routes(file_contents, route_code)

@spec inject_oauth_routes(String.t(), String.t()) ::
  {:ok, String.t()} | {:already_injected, String.t()}

Injects OAuth routes into the router file.

Returns {:ok, new_contents} if injection succeeds, or {:already_injected, contents} if the OAuth marker is already present.

inject_oban_lifecycle_queue(file_contents)

@spec inject_oban_lifecycle_queue(String.t()) ::
  {:ok, String.t()} | {:already_injected, String.t()}

Injects the :sigra_lifecycle queue into Oban configuration.

Adds the lifecycle queue alongside the existing mailer queue.

Returns {:ok, new_contents} if injection succeeds, or {:already_injected, contents} if the queue is already present.

inject_router_plugs(file_contents, plug_code)

@spec inject_router_plugs(String.t(), String.t()) ::
  {:ok, String.t()} | {:already_injected, String.t()}

Injects authentication pipeline and routes into the router file.

Returns {:ok, new_contents} if injection succeeds, or {:already_injected, contents} if the marker is already present.

inject_test_config(file_contents, test_config_block)

@spec inject_test_config(String.t(), String.t()) ::
  {:ok, String.t()} | {:already_injected, String.t()}

Injects Argon2 test speedup configuration into test.exs.

Returns {:ok, new_contents} if injection succeeds, or {:already_injected, contents} if the marker is already present.

inject_vault_child(file_contents, app_module)

@spec inject_vault_child(String.t(), String.t()) ::
  {:ok, String.t()} | {:already_injected, String.t()}

Injects Vault child spec into the application supervision tree.

Finds the children = [ list in application.ex and adds {MyApp.Vault, []} to it.

Returns {:ok, new_contents} if injection succeeds, or {:already_injected, contents} if Vault is already present.

lifecycle_template_files()

@spec lifecycle_template_files() :: [String.t()]

Returns the list of files that the generator should include for account lifecycle features, including auth_hooks.ex.