diff --git a/.changeset/mobile-template-kind.md b/.changeset/mobile-template-kind.md new file mode 100644 index 0000000..51fec90 --- /dev/null +++ b/.changeset/mobile-template-kind.md @@ -0,0 +1,21 @@ +--- +'seamless-cli': minor +--- + +`seamless init` learns the `mobile` template kind. + +`seamless-templates` gains a third kind next to `web` and `api`, starting with an Expo (React +Native) starter. The CLI treated an unknown kind as if it were not there: the alias resolver +skipped it, the prompts never offered it, and `verify` filtered it out without a word. + +A mobile starter is optional. `init` asks "Mobile app" after the backend, defaulting to none; +`--mobile=` (or the bare alias, `--mobile` / `--expo`) includes one; `--yes` scaffolds +without, since a native app brings prerequisites (an associated domain for passkeys) an +unattended run should not opt into; and a registry that predates the kind offers nothing, so +there is no question to ask. A chosen starter lands at `mobile/` with its `.env` filled from the +manifest, is recorded as `services.mobile` in `seamless.config.json`, is checked by `seamless +check` only when recorded, and is called out in the success output with how to start it and the +Android emulator host. `verify` announces the mobile templates it cannot drive instead of +filtering them silently. Template copying skips `.expo`, `ios`, and `android`. + +The templates registry pin moves to the release that carries the starter in a follow-up. diff --git a/README.md b/README.md index d72401d..5fb7e4b 100644 --- a/README.md +++ b/README.md @@ -132,10 +132,11 @@ seamless templates list ```text ID KIND FRAMEWORK FLAGS STATUS -react-vite web react --basic, --react-vite stable -react-oauth web react --oauth, --react-oauth stable -express api express --express stable -fastify api fastify --fastify beta +react-vite web react --basic, --react-vite stable +react-oauth web react --oauth, --react-oauth stable +express api express --express stable +fastify api fastify --fastify beta +expo mobile expo --mobile, --expo beta ``` Every template answers to `--`; some also declare a shorter `--`, and the two are @@ -145,6 +146,11 @@ interchangeable. Passing a flag skips that layer's prompt: seamless init my-app --react-oauth --express ``` +A web and an api starter are always placed. A mobile starter is optional: the prompt defaults to +none, and `--mobile` (or `--expo`) includes it at `mobile/`. Its email codes and sign-in links work +against the local stack as soon as `init` finishes; passkeys need an associated domain, which the +starter's README walks through. + `--json` emits the registry entries for scripting. The command needs no login and reads the same registry `init` does, so `SEAMLESS_TEMPLATES_DIR` and `SEAMLESS_TEMPLATES_REF` apply. @@ -165,6 +171,7 @@ Each question also has its own flag, honored with or without `--yes`: | --- | --- | --- | | `--web=` | Web example | first selectable web template | | `--api=` | Backend framework | first selectable api template | +| `--mobile=` | Mobile app | none | | `--email=
` | Owner email (becomes the admin) | required | | `--auth=` | How the auth server runs | `docker` | | `--admin=` | Where the admin console is hosted | `api` | @@ -202,8 +209,9 @@ Depending on your selections, the CLI generates a project like this: ```text my-app/ ├─ auth/ # Seamless Auth server (local auth mode only) -├─ web/ # React web application (optional) -├─ api/ # Express or Fastify API server (optional) +├─ web/ # React web application +├─ api/ # Express or Fastify API server +├─ mobile/ # Expo mobile app (--mobile only) ├─ admin/ # Admin console source (--admin=source only) ├─ docker-compose.yml # not written for a managed project └─ seamless.config.json @@ -326,7 +334,8 @@ seamless verify --keep-up # leave the stack running afterwards `--local` is the pre-publish check: it builds and packs the local SDK source rather than installing from npm, so an SDK regression surfaces before a release rather than after. The browser layer runs once per web template in the registry, each scoped to the flows -its `template.json` declares. +its `template.json` declares. Mobile templates are announced and skipped: the harness has no +simulator to drive, so a native app is checked by running it against a `--keep-up` stack. Sibling repositories are resolved next to this one and can be pointed elsewhere with `SEAMLESS_API_DIR`, `SEAMLESS_SERVER_DIR`, `SEAMLESS_REACT_SDK_DIR`, and @@ -574,9 +583,9 @@ Seamless CLI scaffolds from, and conformance-tests against, these repositories: | Repository | What it provides | How the CLI uses it | | --- | --- | --- | | [seamless-auth-api](https://github.com/fells-code/seamless-auth-api) | The auth server | Run as a pinned image (`--auth=docker`) or cloned into `auth/` (`--auth=local`) | -| [seamless-templates](https://github.com/fells-code/seamless-templates) | The web and API starters | Scaffolded from its registry at a pinned ref | +| [seamless-templates](https://github.com/fells-code/seamless-templates) | The web, API, and mobile starters | Scaffolded from its registry at a pinned ref | | [seamless-auth-server](https://github.com/fells-code/seamless-auth-server) | `@seamless-auth/core`, `/express`, `/fastify` | The adapters the scaffolded `api/` runs on | -| [seamless-auth-react](https://github.com/fells-code/seamless-auth-react) | `@seamless-auth/react` | The client SDK the scaffolded `web/` runs on | +| [seamless-auth-react](https://github.com/fells-code/seamless-auth-react) | `@seamless-auth/client`, `/react`, `/react-native` | The client SDKs the scaffolded `web/` and `mobile/` run on | The starters live in the templates monorepo and are listed in its registry, so the set of frameworks the CLI offers grows there. Each project can be used independently, but the CLI connects diff --git a/src/commands/check.test.ts b/src/commands/check.test.ts index dd59ecf..d75caab 100644 --- a/src/commands/check.test.ts +++ b/src/commands/check.test.ts @@ -112,6 +112,34 @@ describe("runCheck", () => { expect(out).toContain("Check complete."); }); + it("checks the mobile project only when the config records one", async () => { + const withMobile = { + ...CONFIG, + services: { ...CONFIG.services, mobile: { framework: "expo", path: "mobile" } }, + }; + vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(withMobile)); + vi.mocked(execSync).mockReturnValue(Buffer.from("api\n") as never); + vi.mocked(fetch).mockResolvedValue({ ok: true, status: 200 } as Response); + + // Present on disk. + vi.mocked(fs.existsSync).mockReturnValue(true); + await runCheck(); + expect(output()).toContain("Mobile project detected"); + + // Recorded in the config but gone from disk. + logs.length = 0; + vi.mocked(fs.existsSync).mockImplementation((p: string) => !String(p).endsWith("mobile")); + await runCheck(); + expect(output()).toContain("Mobile project missing"); + + // Never chosen: not a finding either way. + logs.length = 0; + vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(CONFIG)); + vi.mocked(fs.existsSync).mockReturnValue(true); + await runCheck(); + expect(output()).not.toContain("Mobile project"); + }); + it("reports the unhealthy branches when everything is missing or down", async () => { // config present so we proceed, but web/api/compose paths missing. vi.mocked(fs.existsSync).mockImplementation((p: string) => { diff --git a/src/commands/check.ts b/src/commands/check.ts index e42bf1a..1c2f683 100644 --- a/src/commands/check.ts +++ b/src/commands/check.ts @@ -102,6 +102,17 @@ function checkStructure(root: string, config: any, report: Report) { } else { report.fail("API project missing"); } + + // Only projects that chose a mobile starter record one, so its absence from + // the config is not a finding; its absence from disk is. + const mobilePath = services.mobile?.path; + if (mobilePath) { + if (fs.existsSync(path.join(root, mobilePath))) { + report.ok("Mobile project detected"); + } else { + report.fail("Mobile project missing"); + } + } } function checkDocker(report: Report) { diff --git a/src/commands/helpTopics.ts b/src/commands/helpTopics.ts index bbef1d2..95650fc 100644 --- a/src/commands/helpTopics.ts +++ b/src/commands/helpTopics.ts @@ -18,7 +18,7 @@ export const COMMAND_HELP: CommandHelp[] = [ name: "init", usage: [ "seamless init [project-name] [--