Skip to content

CLI reference ​

pidrang init reads your Supabase schema, writes the Resource definitions, the resource routes and the generic List, Create, Show and Edit pages, wires the app itself and offers to install the packages it needs. It asks for the project URL first, then how to read your table definitions, then the tables and their actions. --plan answers the questions from a JSON file instead. It must be launched from the workspace root of an Angular 22.1+ application created by ng new with the router.

Commands ​

CommandWhat it does
pidrang init [--plan <file>] [--project <name>] [--skip-install]Scaffolds Resource definitions and CRUD pages into an Angular app. Run it from the workspace root.
pidrang generate auth [--project <name>]Writes the sign-in page the pages and the layout link to, when the app has none.

Source: apps/pidrang-cli/src/run-cli.ts, function usage, lines 102–156.

Options ​

OptionApplies toWhat it does
--plan <file>initTake the answers from a JSON plan file instead of prompts.
--project <name>BothChoose the application to set up when angular.json has several.
--skip-installinitDo not offer to install the packages; print the commands instead.
-h, --helpBothShow this help message.

Source: apps/pidrang-cli/src/run-cli.ts, function usage, lines 110–114.

Requirements ​

RequirementHow it is checkedWhat is shown
Node.js 22.22.3 or a later 22.x, 24.15.0 or a later 24.x, or 26 and laterapps/pidrang-cli/src/node-version.ts reads process.version against the range `^22.22.3
Angular 22.1 or a later 22.xapps/pidrang-cli/src/preflight-app.ts reads @angular/core/package.json or package.json dependenciesListed among other problems in the "this folder is not an app" message.

Source: apps/pidrang-cli/src/node-version.ts line 1 (SUPPORTED_NODE), apps/pidrang-cli/src/preflight-app.ts lines 27, 138–166.

The questions ​

The wizard asks these questions in order:

OrderQuestionDefaultWhen asked
1Which data source do you want to scaffold from?—Only when more than one provider is registered. Source: libs/cli/src/lib/wizard.ts, line 271.
2Supabase project URL, from the Connect button at the top of the Supabase dashboard:—Always first for Supabase. Source: libs/cli-supabase/src/lib/supabase-schema-provider-definition.ts, line 38.
3How should pidrang read your table definitions?service-roleAlways after the URL. Choices: Secret key (sb_secret_), Direct database connection (host, user, password), GraphQL with the publishable key. Source: same file, line 486.
4Which tables should pidrang scaffold?—After connection is resolved. Multi-select checkbox over available tables. Source: libs/cli/src/lib/wizard.ts, line 168.
5 (per table)Which actions should "<table>" support?All checkedAfter selecting tables. Checkbox with disabled unavailable actions. Source: libs/cli/src/lib/wizard.ts lines 305–312 (actionsMessage, label at line 309), checkbox call at lines 321–329.
6 (per table)Any fields to hide from the list/edit views for "<table>"? (optional)—Per table. Checkbox listing introspected fields. Source: libs/cli/src/lib/wizard.ts, lines 332–338.
7Generate a navigation layout? It adds a header with your app name and a sign in/out link, and a side menu of the resources.trueNew app only; always "none" for existing apps. Source: libs/cli/src/lib/wizard.ts, lines 21–25.
8Generate a sign-in page at /sign-in? The app needs one to sign in and write data.trueNew app only; always true for existing apps. Source: libs/cli/src/lib/wizard.ts, lines 29–32.
9Do your tables need a signed-in user to be read? Say yes when the select policy of the tables hides their rows from anonymous visitors, so a new app opens on the sign-in page instead of an empty list.falseNot asked per-table: graphql and secret-key strategies answer globally; postgres answers per-table. Source: libs/cli/src/lib/wizard.ts, lines 36–40.
10Install the required packages?YAlways last. Confirms before running the install command. Source: libs/cli/src/lib/wizard.ts, line 350.

When the connection attempt fails, the wizard repeats the question with the rejection message and asks whether to fall back to the secret key (for graphql). Source: libs/cli-supabase/src/lib/supabase-schema-provider-definition.ts, lines 148–154.

What it writes ​

FileWhat lands thereWhen
src/app/resources.tsexport const resources: Resource[] = [...] — one entry per selected table: { name, label, fields, defaultSorters?, list, create, show, edit, delete }. Each field is { name, label, type, hidden?, readOnly?, nullable?, hasDefault? }, inferred from introspection; a field named in the plan file's hiddenFields is written with hidden: true. defaultSorters is written only when a timestamp or date created_at column exists.Every run. Edited in-place on subsequent runs.
src/app/resource-routes.tsGeneric routes :resource, :resource/create, :resource/:id/edit, :resource/:id pointing to generated pages, with canMatch guards.Every run. Regenerated; an existing guard list is extended in place.
src/app/pages/resource-list-page.tsList page using injectResourceList, ResourceTable, with actions projected.Every run, on a new or an existing app. A file already there with other content stops init before any write.
src/app/pages/resource-create-page.tsCreate page using ResourceForm, calls dataProvider.create.Same as the list page.
src/app/pages/resource-show-page.tsShow page displaying fields and links to edit/delete.Same as the list page.
src/app/pages/resource-edit-page.tsEdit page using ResourceForm, calls dataProvider.update, with a Delete button calling deleteProvider.delete.Same as the list page.
src/app/pidrang/...Helpers: provide-sign-in-path, can-match-signed-in, route utils, etc.Same as the list page.
src/app/layout/app-layout.tsNavigation layout component with header and sidebar menu.Only when layout is "sidebar" on a new app.
src/app/pages/sign-in-page.tsSign-in page component.New app: when you accept the sign-in question. Existing app in an interactive run: always. Existing app driven by a plan: when the plan sets signIn: true. Omitted when app.routes.ts already declares the route. pidrang generate auth writes it on demand.
app.config.tsAdds providePidrang(), provides provideSignInPath() if configured.Every run.
app.routes.tsAppends the resource routes import, entry redirect for new apps.Every run.
angular.jsonAdds environment file replacements.When the app had neither environment.ts nor environment.development.ts, on a new or an existing app.
src/environments/environment.ts and environment.development.tsSupabase URL and a placeholder for the publishable key.Same as angular.json. Otherwise init prints the lines to add.

How it wires the app itself is described in Getting started. The resource routes guard every :resource action with both canMatchResourceAction(...) and canMatchSignedIn([...]) when the corresponding tables need a signed-in user. A file it writes is never overwritten: one that already exists with other content stops init before any write. An existing resources.ts is edited instead: a second run appends the tables it does not list yet and leaves the others as they are.

Sources: libs/cli/src/lib/generate-pages-bundle.ts (PAGES_UI_LAYER_PACKAGES, lines 70–73), libs/cli/src/lib/generated-pages.ts (page file list), libs/cli/src/lib/generate-resources-file.ts (resources.ts generation), libs/cli/src/lib/build-sign-in-page.ts (sign-in page generation), libs/cli/src/lib/generate-layout-module.ts (layout generation), apps/pidrang-cli/src/edit-app-config.ts, apps/pidrang-cli/src/edit-app-routes.ts.

The plan file ​

json
{
  "provider": "supabase",
  "connection": { ... },
  "tables": [
    { "table": "products", "actions": ["list", "create", "show", "edit", "delete"], "hiddenFields": ["created_at"] }
  ],
  "layout": "sidebar",
  "signIn": true,
  "readsNeedSignIn": false
}

actions lists at least one of list, create, show, edit, delete. hiddenFields is optional. layout is "sidebar" to add a header and a side menu of the resources to a new app, or "none" to leave the pages bare. It defaults to "none" and an existing app never gets one. signIn is true to generate the sign-in page that the pages and the layout link to. It defaults to false and init asks for it on a new app. readsNeedSignIn is true when the select policy of the tables hides their rows from anonymous visitors: a new app then opens on the sign-in page instead of an empty list. It applies to every table, defaults to false, and the direct database connection strategy answers per table from its row level security, its select policies and its grant to the anonymous role instead. Credentials never go in the plan file: they come from environment variables.

Every key ​

KeyTypeDefaultMeaning
providerstring—Provider id, e.g. "supabase". Source: libs/cli/src/lib/init-plan.ts line 19.
connectionobject—Strategy-dependent connection object (see below). Source: libs/cli/src/lib/init-plan.ts line 20.
tables[].tablestring—Database table name, used as resource name and URL segment. Source: libs/cli/src/lib/init-plan.ts line 3 (TABLE_KEYS).
tables[].actionsstring[]—Must list at least one of list, create, show, edit, delete. Duplicates detected. Source: libs/cli/src/lib/init-plan.ts lines 95–101.
tables[].hiddenFieldsstring[][]Fields omitted from list and edit views. Source: libs/cli/src/lib/init-plan.ts lines 102–106.
layout"sidebar" | "none""none""sidebar" for new apps only; existing apps ignore it and keep theirs. Source: libs/cli/src/lib/init-plan.ts lines 22, 170.
signInbooleanfalseGenerate the sign-in page. Defaults to false; init asks on new apps. Source: libs/cli/src/lib/init-plan.ts lines 23, 171.
readsNeedSignInbooleanfalseGlobal per-table override when the postgres strategy answers per-table. Source: libs/cli/src/lib/init-plan.ts lines 24, 172.

Connection per strategy ​

Supabase plan connection, one of:
  { "strategy": "graphql", "url": "https://<ref>.supabase.co" }
      publishable key from PIDRANG_SUPABASE_PUBLISHABLE_KEY
  { "strategy": "secret-key", "url": "https://<ref>.supabase.co" }
      secret key from PIDRANG_SUPABASE_SECRET_KEY
  { "strategy": "postgres", "host": "db.<ref>.supabase.co", "port": 5432,
    "database": "postgres", "user": "postgres", "tls": true,
    "caCertificatePath": "prod-ca.crt", "url": "https://<ref>.supabase.co" }
      password from PIDRANG_DATABASE_PASSWORD; port, database, user, tls,
      caCertificatePath and url are optional, tls defaults to on for a remote
      host, a relative caCertificatePath is read from the current folder, not
      from the folder of the plan file, and url fills supabaseUrl in the app
      when the host does not name the project

Keys allowed per strategy (strict): graphql allows strategy and url; secret-key allows the same; postgres allows strategy, host, port, database, user, tls, caCertificatePath, and url. Extra keys produce validation errors (secrets must not appear in the plan, use env vars).

Source: libs/cli-supabase/src/lib/supabase-schema-provider-definition.ts lines 259–275 (PLAN_HELP).

Legacy names still work and print a warning: "service-role" for "secret-key", PIDRANG_SUPABASE_ANON_KEY for PIDRANG_SUPABASE_PUBLISHABLE_KEY, PIDRANG_SUPABASE_SERVICE_ROLE_KEY for PIDRANG_SUPABASE_SECRET_KEY. Source: same file, lines 141–142, 227–229.

Secrets from the environment ​

VariableUsed byLegacy name
PIDRANG_SUPABASE_PUBLISHABLE_KEYgraphql connectionPIDRANG_SUPABASE_ANON_KEY
PIDRANG_SUPABASE_SECRET_KEYsecret-key connectionPIDRANG_SUPABASE_SERVICE_ROLE_KEY
PIDRANG_DATABASE_PASSWORDpostgres connection—

When both the new variable and the legacy name are set, the new variable wins. The legacy name produces a deprecation warning printed to stderr.

Source: libs/cli-supabase/src/lib/supabase-schema-provider-definition.ts lines 225–237 (ENV_* constants, SECRETS_IN_ENV).

Exit codes ​

CodeMeaning
0Done or nothing to do. Covers: successful write, everything already up to date, install skipped/installed, present, unsupported manager.
1Invalid input, unsupported Node.js or app, conflicting file, failed install, unknown provider, wrong table name.
130Cancelled by the user (interrupt signal or declining install). Prints Cancelled: nothing was written. to stderr.

Source: apps/pidrang-cli/src/run-cli.ts lines 69–71 (EXIT_OK, EXIT_FAILED, EXIT_CANCELLED) and the INSTALL_STATUS map at line 566.

A second run ​

resources.ts is edited: tables not yet listed are appended, tables already listed are left as they are. A file with different content stops init before any write, listing the conflicted paths. The layout of a first-run app is preserved: a second run on an existing app never sets the layout to "sidebar". When a file cannot be edited safely, init adds a // pidrang: comment that lists the exact change to make.

Guard updates: a guard already written receives new private tables appended to its list. Guards for tables not seen in resources.ts are held back until those tables are listed.

Source: apps/pidrang-cli/src/run-cli.ts lines 772–810 (resources edit, guarded resources).

A new app and an existing app ​

On a new app, init replaces the welcome page, generates the full page bundle (list, create, show, edit), creates the layout module with a sidebar, writes the sign-in page (when requested), and configures app.html to host the layout. On an existing app it adds the resource routes to app.routes.ts and edits resources.ts and app.config.ts, writes the same pages and helpers as on a new app, and the sign-in page (always in an interactive run, or when the plan sets signIn: true). A page already there with other content stops init before any write. It adds no layout and no entry redirect: when app.routes.ts has no '' route, it prints the redirect to add.

The layout feature (app-layout.ts) is gated on four conditions met together: the app must be new (ng new's app.html has not been changed), app.html must contain only <app-layout>, app.ts must match the ng new template, and the layout must be requested (sidebar). When any condition fails, init reports the missing files and skips the layout. An existing app kept its own app.html and never receives a layout.

Source: apps/pidrang-cli/src/run-cli.ts lines 676–697 (layout conditions), libs/cli/src/lib/generate-layout-module.ts lines 14–17 (LAYOUTS map keyed on LayoutUi).

When the tables need a signed-in user ​

Tables marked with readsNeedSignIn: true receive a canMatchSignedIn([...]) guard on all their actions. The guard redirects an anonymous visitor to the sign-in path when provideSignInPath configured one; without it, the navigation goes through. The generated pages show a "You may need to sign in" prompt only after a write is refused for lack of sign-in: a delete on the list page, a create on the create page, an update on the edit page. The show page never shows it. With provideSignInPath the words "sign in" are a link; without it they stay plain text.

For new apps: init asks once ("Do your tables need a signed-in user to be read?") and applies the answer globally. The new app opens on the sign-in page when readsNeedSignIn is true. For existing apps: a new run appends private tables already listed in resources.ts to the existing guard. Tables not yet listed are held back with an error message referencing where to add them manually.

Only the postgres strategy answers readsNeedSignIn per-table (reading the actual Row Level Security policies). The graphql and secret-key strategies ask a single global question.

Source: libs/cli/src/lib/wizard.ts lines 43–55 (providerReadsNeedSignIn), apps/pidrang-cli/src/run-cli.ts lines 652–665 (guard setup and unguarded resources message).

The sign-in page ​

init writes the sign-in page at src/app/pages/sign-in-page.ts in three cases: on a new app when you accept the sign-in question, on an existing app in an interactive run where it is always written and the question is not asked, and on an existing app driven by a plan whose signIn is true. The page links to the login method provided by the app.

pidrang generate auth writes the same page when app.routes.ts has no existing sign-in route and app.config.ts does not call provideSignInPath with another path.

init writes its own sign-in route into resource-routes.ts, above the :resource routes. When app.routes.ts already declares a first segment named sign-in, it writes no sign-in route and leaves yours in place. A table whose name is already a route declared in app.routes.ts is refused, so the :resource routes cannot take the sign-in path. Source: libs/cli/src/lib/generate-pages-bundle.ts lines 5-11 and 97-110 (sign-in route placement), apps/pidrang-cli/src/preflight-app.ts lines 180-182 (declaresSignInRoute), libs/cli/src/lib/wizard.ts lines 77-86 (routeNameProblem).

Source: apps/pidrang-cli/src/run-cli.ts lines 435–443 (instruction printed without provider), libs/demo-shared/src/lib/provide-sign-in-path.ts (validation logic).

Changing what it wrote ​

  • Generated files are ordinary application files. You can edit them freely.
  • init refuses to overwrite a generated file whose content differs from what it expects, and lists it instead.
  • To regenerate the generic pages (list, create, show, edit), delete src/app/pages and src/app/pidrang together, then run init again. Individual page regeneration is not supported.
  • The helpers under src/app/pidrang/ are called by the generated pages and the layout module, so deleting them breaks the build even when resources.ts is correct.
  • pnpm run sync:pages and sync:layout serve the demo repositories in this codebase; they are not meant for consumer applications.

Source: apps/pidrang-cli/src/run-cli.ts lines 143–146 (never-overwrite rule, manual comments), apps/pidrang-cli/src/write-plan.ts (conflict detection), docs/design-notes.md lines 157, 169.

When it stops ​

ConditionWrittenWhat to do
No angular.json or not valid JSONError message listing problemsRun from the workspace root of an Angular app.
Missing app.config.ts or app.routes.tsError messageThese must be present in a standalone app set up like ng new creates it.
Unsupported Node.jsPlain error with required range, "Nothing was written."Install a supported Node.js version.
Unsupported Angular versionListed in the "not an app" messageUpgrade or downgrade @angular/core to 22.1+.
Table named as a reserved routeThrows with table JSON + reasonRename the table. Reserved: resource-unavailable. Collision with existing routes also rejected.
Invalid response (no action selected, empty table name, control character)Throws with descriptionFix the selection and try again.
Conflict: file exists with other contentLists conflicted files under resources.ts, stops before any writeResolve the conflict (the older pages need src/app/pages and src/app/pidrang deleted together).
Install failedPrinted command, remaining commands listedRun the printed command manually or fix the package manager issue.

Sources: apps/pidrang-cli/src/preflight-app.ts lines 189–226 (preflight checks), apps/pidrang-cli/src/node-version.ts line 39 (node check), apps/pidrang-cli/src/run-cli.ts lines 615–617, 893–903 (conflict reporting), apps/pidrang-cli/src/install-packages.ts lines 142–156 (missing packages detection).

MIT licensed. Inspired by Refine. Not affiliated with Angular or Refine.