create-tilcayo-app 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,49 +1,294 @@
1
- # create-tilcayo-app
2
-
3
- Create a Tilcayo API with an interactive setup wizard:
4
-
5
- ```sh
6
- create-tilcayo-app
7
- ```
8
-
9
- Once published to your registry, bootstrap without a global install:
10
-
11
- ```sh
12
- npm create tilcayo-app@latest
13
- pnpm create tilcayo-app
14
- yarn create tilcayo-app
15
- ```
16
-
17
- For local development in the Tilcayo repository, build the workspace and run
18
- `node packages/create-tilcayo-app/dist/bin.js my-api`, or link this package to expose
19
- `create-tilcayo-app` on PATH.
20
-
21
- ```sh
22
- create-tilcayo-app my-api --type api --auth --package-manager npm --yes
23
- create-tilcayo-app tiny-api --type minimal --no-auth --no-install --yes
24
- ```
25
-
26
- The wizard offers project name, package manager (npm, pnpm, or Yarn), application
27
- type, and authentication. Use the arrow keys and Enter to choose from colored
28
- menus; project name remains a text field. Ctrl+C cancels before files are created.
29
- `api` includes MongoDB-backed note CRUD; `minimal`
30
- includes a health endpoint. Enabling authentication adds MongoDB to either type.
31
- Noninteractive runs use defaults for omitted options. Existing paths are refused.
32
-
33
- Generated projects contain controllers, routes, models, services, validators,
34
- separate app and startup modules, TypeScript configuration, `.gitignore`,
35
- `.env.example`, and a private `.env`. Authentication gets independent random
36
- secrets in `.env`; the example file leaves secrets blank.
37
-
38
- Setup installs dependencies and builds unless `--no-install` is supplied. The
39
- selected package manager must be installed. Running the creator from a built
40
- Tilcayo source checkout automatically links the local framework packages and
41
- their shared Zod and Mongoose dependencies. Keep that checkout available while
42
- developing the generated app. The published creator uses registry versions of
43
- `@tilcayo/core`, `@tilcayo/auth`, and `@tilcayo/cli` instead.
44
- Installation failures retain the generated project and report recovery steps.
45
- MongoDB must be running before starting an API that uses it.
46
-
47
- Inside the generated project, use `tilcayo dev`, `tilcayo build`, `tilcayo start`,
48
- and `tilcayo routes:list`. If the CLI is installed only in the project, invoke
49
- it through `npx tilcayo`, `pnpm exec tilcayo`, or `yarn exec tilcayo`.
1
+ # create-tilcayo-app
2
+
3
+ Create a Tilcayo API or React client with an interactive setup wizard:
4
+
5
+ ```sh
6
+ create-tilcayo-app
7
+ ```
8
+
9
+ Use Node.js 22.9+. Create your app outside the framework checkout, without a global install:
10
+
11
+ ```sh
12
+ npm create tilcayo-app@latest
13
+ pnpm create tilcayo-app
14
+ yarn create tilcayo-app
15
+ ```
16
+
17
+ For local development in the Tilcayo repository, build the workspace and run
18
+ `node packages/create-tilcayo-app/dist/bin.js my-api`, or link this package to expose
19
+ `create-tilcayo-app` on PATH.
20
+
21
+ ```sh
22
+ create-tilcayo-app my-api --type api --auth --package-manager npm --yes
23
+ create-tilcayo-app tiny-api --type minimal --no-auth --no-install --yes
24
+ ```
25
+
26
+ The wizard offers project name, package manager (npm, pnpm, or Yarn), application
27
+ type, and authentication. Use the arrow keys and Enter to choose from colored
28
+ menus; project name remains a text field. Ctrl+C cancels before files are created.
29
+ `api` includes MongoDB-backed note CRUD; `minimal`
30
+ includes a health endpoint. Enabling authentication adds MongoDB to either type.
31
+ Noninteractive runs use defaults for omitted options. Existing paths are refused.
32
+
33
+ Generated projects contain controllers, routes, models, services, validators,
34
+ separate app and startup modules, TypeScript configuration, `.gitignore`,
35
+ `.env.example`, and a private `.env`. Authentication gets independent random
36
+ secrets in `.env`; the example file leaves secrets blank.
37
+
38
+ ## Start your API
39
+
40
+ For a MongoDB API with authentication:
41
+
42
+ ```sh
43
+ npm create tilcayo-app@latest my-api -- --type api --auth --yes
44
+ cd my-api
45
+ ```
46
+
47
+ In your new app's `.env`, configure the database:
48
+
49
+ ```dotenv
50
+ PORT=9149
51
+ MONGODB_URI=mongodb://127.0.0.1:27017/my_api
52
+ ```
53
+
54
+ Keep the generated authentication secret entries. Start MongoDB locally or replace
55
+ the URI with your Atlas connection string. Then run:
56
+
57
+ ```sh
58
+ npm run dev
59
+ ```
60
+
61
+ Open `http://localhost:9149/health`:
62
+
63
+ ```json
64
+ {"success":true,"message":"Success","data":{"status":"ok"}}
65
+ ```
66
+
67
+ The `api` and `minimal` starters are backend APIs. On PowerShell, use
68
+ `npm.cmd` / `npx.cmd` if script execution is blocked.
69
+
70
+ ## Options
71
+
72
+ With `npm create`, place creator flags after `--`.
73
+
74
+ | Option | Purpose |
75
+ | --- | --- |
76
+ | `[project-name]` | New directory and package name |
77
+ | `--type api\|minimal\|react` | MongoDB API, minimal API, or React client |
78
+ | `--auth` / `--no-auth` | Include or omit authentication |
79
+ | `--package-manager npm\|pnpm\|yarn` | Installation tool |
80
+ | `--no-install` | Write files without installation or compilation |
81
+ | `--yes` | Use defaults for unanswered choices |
82
+ | `--help` | Command help |
83
+
84
+ Defaults are `my-api`, npm, `api`, and no authentication. A minimal app needs no
85
+ database unless authentication is enabled:
86
+
87
+ ```sh
88
+ npm create tilcayo-app@latest tiny-api -- --type minimal --no-auth --yes
89
+ ```
90
+
91
+ ## Continue building
92
+
93
+ For a React client, select React in the wizard or pass `--type react --yes`.
94
+ Use Node.js 22.12+ or 24+. The client includes the current Tilcayo starter pages,
95
+ responsive hamburger menu, TypeScript, Vite, React Router, and `@tilcayo/styles`.
96
+ Run `npm run dev`, `npm run build`, `npm run preview`, or `npm run lint` in the
97
+ generated client. Add `--auth` for login/register pages, a protected dashboard,
98
+ guest/auth route guards, auth bootstrap and logout navigation. This frontend connects
99
+ to a separate Tilcayo auth API; its Vite proxy targets port 9149.
100
+
101
+ To test from the repository root:
102
+
103
+ ```sh
104
+ npm run build
105
+ cd examples
106
+ node ../packages/create-tilcayo-app/dist/bin.js my-react-app --type react --yes
107
+ cd my-react-app
108
+ npm run dev
109
+ ```
110
+
111
+ The build bundles the `client` source into `dist/react-starter.json`, so the packed
112
+ creator does not need the checkout to generate a React app. Local generation
113
+ links the checkout styles package; published generation uses `@tilcayo/styles`
114
+ from npm. Publish the styles package and this updated creator before testing
115
+ `npm create tilcayo-app@latest my-react-app -- --type react --yes`.
116
+
117
+ For backend starters:
118
+
119
+ | Location | Purpose |
120
+ | --- | --- |
121
+ | `src/app.ts` | Register routes and middleware |
122
+ | `src/index.ts` | Database connection and server lifecycle |
123
+ | `src/config.ts` | Configuration |
124
+ | `src/models/` | Database schemas |
125
+ | `src/controllers/` | Request handlers |
126
+ | `src/validators/` | Zod validation |
127
+ | `src/routes/` | URLs and middleware |
128
+ | `src/services/` | Business logic |
129
+
130
+ In another terminal inside the app, generate a feature:
131
+
132
+ ```sh
133
+ npx tilcayo make:resource Product name:string price:number "description:string?"
134
+ ```
135
+
136
+ Add the import and registration to `src/app.ts`, before its default export:
137
+
138
+ ```ts
139
+ import productsRoutes from "./routes/products.routes.js";
140
+
141
+ app.routes(productsRoutes);
142
+ ```
143
+
144
+ Send `POST http://localhost:9149/products` with `Content-Type: application/json`
145
+ and body `{"name":"Notebook","price":250}` using Postman or Thunder Client.
146
+ Use `GET /products` to list records and `GET`, `PUT`, or `DELETE /products/:id`
147
+ to read, update, or delete one. Replace `:id` with the returned `data._id`.
148
+
149
+ Edit the generated files as your feature grows. Dev rebuilds and restarts on
150
+ source changes. Generators refuse to overwrite files and do not register routes
151
+ automatically.
152
+
153
+ ## Authentication
154
+
155
+ For backend starters with `--auth`, these endpoints are registered:
156
+
157
+ | Endpoint | Input |
158
+ | --- | --- |
159
+ | `POST /api/auth/register` | JSON `name`, `email`, `password` |
160
+ | `POST /api/auth/login` | JSON `email`, `password` |
161
+ | `POST /api/auth/refresh` | JSON `refreshToken` |
162
+ | `POST /api/auth/logout` | JSON `refreshToken` |
163
+ | `GET /api/auth/me` | Bearer access token |
164
+
165
+ Registration and login return tokens under `data.tokens`. Send the access token
166
+ in `Authorization: Bearer <accessToken>`. Generated notes and other CRUD routes
167
+ remain public until you add auth middleware. See the
168
+ [authentication guide](https://www.npmjs.com/package/@tilcayo/auth).
169
+
170
+ ## Build and run
171
+
172
+ | Command | Purpose |
173
+ | --- | --- |
174
+ | `npm run dev` | Develop with rebuilds and restarts |
175
+ | `npm run build` | Compile into dist |
176
+ | `npm run routes` | List routes from compiled code |
177
+ | `npm start` | Start compiled code with .env |
178
+
179
+ Build before listing changed routes or starting compiled code. Stop dev before
180
+ starting another server on the same port. Deploy the application as a Node.js
181
+ service with its runtime dependencies, environment, and database connection.
182
+ Application feature changes do not require publishing to npm.
183
+
184
+ ## Installation and local development
185
+
186
+ Setup installs dependencies and builds unless `--no-install` is supplied. The
187
+ selected package manager must be installed. Running the creator from a built
188
+ Tilcayo source checkout automatically links the local framework packages and
189
+ their shared Zod and Mongoose dependencies. Keep that checkout available while
190
+ developing the generated app. The published creator uses registry versions of
191
+ `@tilcayo/core`, `@tilcayo/auth`, and `@tilcayo/cli` instead.
192
+ Installation failures retain the generated project and report recovery steps.
193
+ MongoDB must be running before starting an API that uses it.
194
+
195
+ If installation fails or was skipped, enter the generated project and run
196
+ `npm install` then `npm run build`; do not regenerate the same directory.
197
+
198
+ Inside the generated project, use `tilcayo dev`, `tilcayo build`, `tilcayo start`,
199
+ and `tilcayo routes:list`. If the CLI is installed only in the project, invoke
200
+ it through `npx tilcayo`, `pnpm exec tilcayo`, or `yarn exec tilcayo`.
201
+
202
+ [Project guide](https://github.com/jomobrain1/tilcayo#readme) ·
203
+ [Generators](https://www.npmjs.com/package/@tilcayo/cli) ·
204
+ [Runtime](https://www.npmjs.com/package/@tilcayo/core)
205
+
206
+ Plain React starters include `@tilcayo/react` and `src/lib/api.ts`, configured with
207
+ public `VITE_API_URL` or `/api`. No API requests run on startup. Local generation
208
+ links both frontend packages; future published generation uses registry versions.
209
+ Publish `@tilcayo/react` as well before releasing this creator. Never put secrets
210
+ in VITE_* variables. See [API client](../react/README.md).
211
+
212
+ ### React auth installation (local, unpublished)
213
+
214
+ From `examples`, after building the workspace:
215
+
216
+ ```sh
217
+ node ../packages/create-tilcayo-app/dist/bin.js my-auth-app --type react --auth --yes
218
+ cd my-auth-app
219
+ npm run dev
220
+ ```
221
+
222
+ Open `/register` or `/login`. The generated README explains creating a separate
223
+ auth backend and connecting MongoDB. Frontend files live in `src/app/auth.ts`,
224
+ `src/middleware/auth.tsx`, `src/components/auth-form.tsx`, and `src/pages/`.
225
+ The creator ships the auth overlay in `dist/react-auth.json`. Tokens remain in
226
+ memory. A standalone React app needs a compatible cookie-session backend and matching frontend configuration to restore login on reload; full-stack auth starters configure this automatically. No backend secrets are generated in the frontend.
227
+ Existing generated apps are not overwritten or upgraded by this command.
228
+ ## Full-stack presets
229
+
230
+ From a built source checkout:
231
+
232
+ ```sh
233
+ node packages/create-tilcayo-app/dist/bin.js bookstore --fullstack --auth --admin --yes
234
+ cd bookstore
235
+ npm run dev
236
+ ```
237
+
238
+ `--api` preserves API-only creation. `--fullstack` creates `api/` and `client/`
239
+ workspaces with one dev command, separate dev scripts, both production builds,
240
+ an API proxy and generated Notes CRUD. Set MongoDB in `api/.env`. Auth adds
241
+ login/register/recovery/profile pages and protected routes; admin requires auth
242
+ and adds `/admin` management pages. Provision roles through trusted server code.
243
+
244
+ Run `tilcayo make:resource Book title:string author:ref:Author --fullstack` from
245
+ the generated root and follow the printed route registration steps. Production
246
+ hosts serve `client/dist` with SPA fallback and proxy `/api` to the Node API.
247
+ `npm run start` starts the API only. See the generated README for configuration.
248
+
249
+ The updated frontend packages and creator must be published before these flags
250
+ work through `npm create tilcayo-app@latest`. Verify packed artifacts externally
251
+ with `node tests/fullstack-starter-consumer.mjs` before release.
252
+
253
+
254
+ ## Tenant workspaces
255
+
256
+ Create a local full-stack auth/admin app with `--tenancy`:
257
+
258
+ ```sh
259
+ node packages/create-tilcayo-app/dist/bin.js my-saas --fullstack --auth --admin --tenancy --yes
260
+ ```
261
+
262
+ From the generated application, set `TENANCY_SUPERADMIN_EMAIL` and
263
+ `TENANCY_SUPERADMIN_PASSWORD` for the setup process, then run `npm run tenancy:setup`.
264
+ This explicit command provisions only a Super Admin. It does not create a default
265
+ tenant, assign memberships or migrate existing records. Re-running it updates
266
+ the designated account password and revokes its existing sessions. No bootstrap
267
+ password is generated into source files or run automatically on startup.
268
+
269
+ Run the app and use `http://localhost:5173/admin/workspaces` for
270
+ platform management. Create tenants and assign their administrators in Tenant administration. Open `<tenant-slug>.localhost` with the port printed by Vite.
271
+ Users can register on the main site or at a tenant address. Tenant signup creates a normal membership in that tenant. An existing account can join another tenant using its existing password; signup never grants an admin role. There is no default tenant or Main app workspace switcher. The main site supports ordinary users and public registration; tenant administrators manage their own users, roles and product catalog.
272
+ Each hostname has its own HttpOnly session cookie; sign in once per workspace.
273
+
274
+ Super Admin manages tenants and global accounts. Tenant roles belong to Memberships,
275
+ not global Users. A Super Admin must be explicitly added to enter a workspace.
276
+ Tenant administrators manage their own members, custom roles, products, orders and
277
+ Trash. Suspending a membership affects only that workspace; global user suspension
278
+ affects all workspaces. Suspended or archived tenants retain data but reject access.
279
+
280
+ `TENANCY_BASE_DOMAIN` defaults to `localhost`. Production needs wildcard DNS and
281
+ HTTPS for that domain, with a same-origin `/api` proxy preserving Host. Subdomains
282
+ are resolved from Host; forwarded tenant headers are not trusted.
283
+
284
+ Tenant-owned models use `mongoModel(name, schema, { tenantScoped: true })`.
285
+ The generator applies this to existing models and future `make:model` and
286
+ `make:resource` output. Queries, writes, counts, distinct, population and supported
287
+ aggregations use the request's tenant context. Platform requests cannot read these
288
+ records. Native `raw.collection` operations bypass Mongoose and are reserved for
289
+ explicit setup/migration code. Bulk writes, replacement updates and cross-collection
290
+ aggregate stages are rejected unless implemented with their own isolation checks.
291
+
292
+ ## Complete command reference
293
+
294
+ See the [main README](../../README.md#create-an-application) for every creator flag, app preset, generator command, environment setting and framework development command. Source checkout builds use the current code; registry creation uses the packages that have been published.
package/dist/bin.js CHANGED
@@ -51,6 +51,8 @@ async function main() {
51
51
  options: [
52
52
  { value: "api", label: color.green("API"), hint: "MongoDB with sample CRUD routes" },
53
53
  { value: "minimal", label: color.magenta("Minimal"), hint: "Health-check endpoint only" },
54
+ { value: "react", label: color.cyan("React"), hint: "React client with Tilcayo styles and responsive navigation" },
55
+ { value: "fullstack", label: color.blue("Full stack"), hint: "MongoDB API and React client in one workspace" },
54
56
  ],
55
57
  }));
56
58
  if (missing.has("auth"))
@@ -58,7 +60,7 @@ async function main() {
58
60
  message: "Include authentication?",
59
61
  initialValue: options.auth,
60
62
  options: [
61
- { value: true, label: color.green("Yes"), hint: "Register, login, refresh and logout; requires MongoDB" },
63
+ { value: true, label: color.green("Yes"), hint: options.type === "react" ? "Login/register pages, dashboard and route guards; connects to a Tilcayo auth API" : "Register, login, refresh and logout; requires MongoDB" },
62
64
  { value: false, label: color.yellow("No"), hint: "Start without authentication" },
63
65
  ],
64
66
  }));
@@ -67,15 +69,22 @@ async function main() {
67
69
  const target = await generateApp(options);
68
70
  console.log(`Created ${target}`);
69
71
  if (options.install) {
70
- console.log(`Installing dependencies with ${options.packageManager} and building the API...`);
72
+ console.log(`Installing dependencies with ${options.packageManager} and building the application...`);
71
73
  await installApp(target, options.packageManager);
72
74
  }
73
75
  console.log(`\nNext:\n cd ${options.name}`);
74
76
  if (!options.install)
75
77
  console.log(` ${options.packageManager} install`);
76
78
  const cli = options.packageManager === "npm" ? "npx tilcayo" : `${options.packageManager} exec tilcayo`;
77
- console.log(` ${cli} dev\n\nWith Tilcayo on PATH, use tilcayo dev directly.`);
78
- if (options.type === "api" || options.auth)
79
+ if (options.type === "react" || options.type === "fullstack")
80
+ console.log(` ${options.packageManager} run dev`);
81
+ else
82
+ console.log(` ${cli} dev\n\nWith Tilcayo on PATH, use tilcayo dev directly.`);
83
+ if (options.type === "react" && options.auth)
84
+ console.log("Open /login or /register. Start a Tilcayo auth API on port 9149; see README.md for setup and API URL configuration.");
85
+ else if (options.type === "fullstack")
86
+ console.log("Start MongoDB or update api/.env. The client proxies /api to the API on port 9149; see README.md.");
87
+ else if (options.type === "api" || options.auth)
79
88
  console.log("Start MongoDB or update MONGODB_URI in .env before starting the API.");
80
89
  }
81
90
  main().catch((error) => {
@@ -0,0 +1,2 @@
1
+ import type { StarterOptions } from "./options.js";
2
+ export declare function fullstackTemplates(options: StarterOptions, localPackages: Record<string, string>): Record<string, string>;
@@ -0,0 +1,107 @@
1
+ import { templates } from "./templates.js";
2
+ // Each half remains an ordinary Tilcayo API or Vite app.
3
+ export function fullstackTemplates(options, localPackages) {
4
+ const api = templates({ ...options, name: `${options.name}-api`, type: "api", admin: options.admin }, localPackages);
5
+ const client = templates({ ...options, name: `${options.name}-client`, type: "react" }, localPackages);
6
+ client["vite.config.ts"] = `import react from '@vitejs/plugin-react';
7
+ import { defineConfig } from 'vite';
8
+
9
+ export default defineConfig({
10
+ plugins: [react()],
11
+ resolve: { dedupe: ['react', 'react-dom', 'react-redux', '@reduxjs/toolkit', 'react-router'] },
12
+ server: { proxy: { '/api': { target: process.env.API_PROXY_TARGET || 'http://127.0.0.1:9149', changeOrigin: false } } },
13
+ });
14
+ `;
15
+ if (options.auth) {
16
+ api[".env"] += "\nAUTH_COOKIE_SESSION=true\n";
17
+ api[".env.example"] += "\nAUTH_COOKIE_SESSION=true\n";
18
+ client["src/app/auth.ts"] = client["src/app/auth.ts"].replace("createTilcayoAuth({", "createTilcayoAuth({ cookieSession: true,");
19
+ }
20
+ client[".env.example"] = "# Public frontend API base URL\nVITE_API_URL=/api\n";
21
+ client["src/routes.tsx"] = "import { notesRoutes } from './features/notes/notes.routes';\n" + client["src/routes.tsx"].replace("children: [", "children: [\n ...notesRoutes,");
22
+ const files = {};
23
+ for (const [name, source] of Object.entries(api))
24
+ files[`api/${name}`] = source;
25
+ for (const [name, source] of Object.entries(client))
26
+ files[`client/${name}`] = source;
27
+ files["package.json"] = JSON.stringify({
28
+ name: options.name, version: "0.0.1", private: true, type: "module",
29
+ engines: { node: "^22.12.0 || >=24.0.0" },
30
+ workspaces: ["api", "client"],
31
+ tilcayo: { type: "fullstack", auth: options.auth, admin: options.admin ?? false, packageManager: options.packageManager },
32
+ scripts: {
33
+ dev: "node scripts/dev.mjs", "dev:api": "npm run dev --workspace api", "dev:client": "npm run dev --workspace client",
34
+ build: "npm run build --workspaces", start: "npm run start --workspace api", routes: "npm run routes --workspace api",
35
+ ...(options.admin ? { 'seed:demo': 'npm run build --workspace api && npm run seed:demo --workspace api', 'seed:demo:remove': 'npm run build --workspace api && npm run seed:demo --workspace api -- --remove' } : {}),
36
+ },
37
+ devDependencies: { "@tilcayo/cli": localPackages.rootCli ?? "^0.0.3" },
38
+ }, null, 2) + "\n";
39
+ files[".gitignore"] = "node_modules/\n**/dist/\n.env\n.env.*\n**/.env\n**/.env.*\n!**/.env.example\n*.log\n";
40
+ if (options.packageManager === "yarn")
41
+ files[".yarnrc.yml"] = "nodeLinker: node-modules\n";
42
+ files["scripts/dev.mjs"] = devScript;
43
+ files["README.md"] = fullstackReadme(options, Object.keys(localPackages).length > 0);
44
+ return files;
45
+ }
46
+ function fullstackReadme(options, local) {
47
+ return `# ${options.name}
48
+
49
+ Tilcayo MongoDB API + React/Vite client${options.auth ? " with authentication" : ""}${options.admin ? " and admin pages" : ""}.
50
+
51
+ Use Node.js 22.12+ or 24+. Start MongoDB and configure api/.env (MONGODB_URI,
52
+ PORT and private auth/mail settings when enabled). Never put secrets in VITE_*.
53
+
54
+ \`\`\`sh
55
+ ${options.packageManager} install
56
+ ${options.packageManager} run dev
57
+ \`\`\`
58
+
59
+ The client runs on Vite's printed URL and proxies /api to port 9149. If you change
60
+ the API port, set API_PROXY_TARGET for the client dev process or edit
61
+ client/vite.config.ts. Visit /notes for sample CRUD${options.auth ? ", /login to sign in, /register to create an account, /dashboard for your overview and /profile for profile and settings" : ""}${options.admin ? ", and /admin for management" : ""}.
62
+ ${options.auth ? "Access tokens stay in memory; an HttpOnly refresh cookie restores the session on reload. Production requires HTTPS and a same-origin API proxy. Configure backend SMTP settings for password recovery.\n" : ""}${options.admin ? "Provision the admin role through trusted server code; registration never grants it.\n" : ""}
63
+ ${options.admin ? "Admin users and role filters are at /admin/users. The catalog at /admin/products includes inventory cards and add/edit forms. Product data starts empty; add products in the UI. Protected catalog routes and the Product model are generated in api/src.\n" : ""}
64
+ Generate both halves from this directory:
65
+
66
+ \`\`\`sh
67
+ npx tilcayo make:resource Author name:string --fullstack
68
+ npx tilcayo make:resource Book title:string author:ref:Author year:number? --fullstack
69
+ \`\`\`
70
+
71
+ Register backend routes in api/src/app.ts and spread frontend route arrays into
72
+ client/src/routes.tsx layout children. For admin resources, add their links in
73
+ client/src/features/admin/admin-layout.tsx and nest createBooksRoutes('books',
74
+ '/admin/books') in the admin route module. ${options.tenancy ? "Tenant resource routes require membership and use tenant-scoped models. Add role guards for privileged actions." : "Resource routes are public until you add backend auth.middleware (and auth.requireRole('admin') where appropriate)."}
75
+
76
+ Run ${options.packageManager} run build to build both halves. Production uses
77
+ api/dist/index.js and client/dist; serve the client with SPA fallback and proxy
78
+ /api to the API. npm run start starts the API only. Configure HTTPS on your host.
79
+ The dev runner requires npm on PATH; npm ships with Node.js even if dependencies
80
+ are installed with pnpm or Yarn. Stop both processes with Ctrl+C. Separate
81
+ dev:api and dev:client scripts are available.
82
+ ${local ? "\nDependencies link to this source checkout. Keep it available, or replace file/link dependencies with published package versions before sharing the app.\n" : ""}`;
83
+ }
84
+ const devScript = `import { spawn, spawnSync } from 'node:child_process';
85
+
86
+ const windows = process.platform === 'win32';
87
+ const children = ['api', 'client'].map(workspace => spawn('npm', ['run', 'dev', '--workspace', workspace], {
88
+ stdio: 'inherit', shell: windows, windowsHide: true, detached: !windows,
89
+ }));
90
+ let stopping = false;
91
+ function stop(code = 0) {
92
+ if (stopping) return;
93
+ stopping = true;
94
+ for (const child of children) {
95
+ if (!child.pid || child.exitCode !== null) continue;
96
+ if (windows) spawnSync('taskkill', ['/pid', String(child.pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' });
97
+ else { try { process.kill(-child.pid, 'SIGTERM'); } catch {} }
98
+ }
99
+ process.exitCode = code;
100
+ }
101
+ for (const child of children) {
102
+ child.on('error', error => { console.error(error.message); stop(1); });
103
+ child.on('exit', code => stop(code ?? 0));
104
+ }
105
+ process.on('SIGINT', () => stop());
106
+ process.on('SIGTERM', () => stop());
107
+ `;
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { installTenancy } from "./installTenancy.js";
1
2
  import { mkdir, writeFile, readFile, access } from "node:fs/promises";
2
3
  import { fileURLToPath } from "node:url";
3
4
  import path from "node:path";
@@ -18,20 +19,23 @@ async function localDependencies(target, options) {
18
19
  if (manifest.name !== "tilcayo" || manifest.private !== true || !Array.isArray(manifest.workspaces) || !manifest.workspaces.includes("packages/*"))
19
20
  return {};
20
21
  const dependencies = {};
21
- for (const name of ["core", "cli", ...(options.auth ? ["auth"] : [])]) {
22
+ for (const name of options.type === "react" ? ["styles", "ui", "react", ...(options.admin ? ["admin"] : [])] : ["core", "cli", ...(options.auth ? ["auth", "cloudinary"] : [])]) {
22
23
  const directory = path.join(checkout, "packages", name);
23
24
  const pkg = JSON.parse(await readFile(path.join(directory, "package.json"), "utf8"));
24
25
  if (pkg.name !== `@tilcayo/${name}`)
25
26
  throw new Error(`Invalid local Tilcayo package: ${directory}`);
27
+ const entry = name === "styles" ? "dist/index.css" : "dist/index.js";
26
28
  try {
27
- await access(path.join(directory, "dist/index.js"));
29
+ await access(path.join(directory, entry));
28
30
  }
29
31
  catch {
30
- throw new Error(`Build the Tilcayo workspace before creating an application: ${directory}/dist/index.js is missing.`);
32
+ throw new Error(`Build the Tilcayo workspace before creating an application: ${directory}/${entry} is missing.`);
31
33
  }
32
34
  const relative = path.relative(target, directory).split(path.sep).join("/");
33
35
  dependencies[name] = `${options.packageManager === "npm" ? "file" : "link"}:${relative}`;
34
36
  }
37
+ if (options.type === "react")
38
+ return dependencies;
35
39
  const require = createRequire(path.join(checkout, "packages/core/package.json"));
36
40
  for (const name of ["zod", ...(options.type === "api" || options.auth ? ["mongoose"] : [])]) {
37
41
  const directory = path.dirname(require.resolve(`${name}/package.json`));
@@ -43,7 +47,18 @@ async function localDependencies(target, options) {
43
47
  export async function generateApp(options, root = process.cwd()) {
44
48
  validateOptions(options);
45
49
  const target = path.resolve(root, options.name);
46
- const sources = templates(options, await localDependencies(target, options));
50
+ let packages;
51
+ if (options.type === "fullstack") {
52
+ const [api, client, app] = await Promise.all([
53
+ localDependencies(path.join(target, "api"), { ...options, type: "api" }),
54
+ localDependencies(path.join(target, "client"), { ...options, type: "react" }),
55
+ localDependencies(target, { ...options, type: "api" }),
56
+ ]);
57
+ packages = { ...api, ...client, ...(app.cli ? { rootCli: app.cli } : {}) };
58
+ }
59
+ else
60
+ packages = await localDependencies(target, options);
61
+ const sources = templates(options, packages);
47
62
  try {
48
63
  await mkdir(target);
49
64
  }
@@ -55,12 +70,27 @@ export async function generateApp(options, root = process.cwd()) {
55
70
  for (const [filename, source] of Object.entries(sources)) {
56
71
  const destination = path.join(target, filename);
57
72
  await mkdir(path.dirname(destination), { recursive: true });
58
- await writeFile(destination, source, { flag: "wx", ...(filename === ".env" ? { mode: 0o600 } : {}) });
73
+ const content = source.startsWith("__TILCAYO_BASE64__:") ? Buffer.from(source.slice("__TILCAYO_BASE64__:".length), "base64") : source;
74
+ await writeFile(destination, content, { flag: "wx", ...(filename === ".env" ? { mode: 0o600 } : {}) });
59
75
  }
60
76
  if (options.type === "api")
61
77
  await make(["make:resource", "Note", "title:string", "content?:string"], target);
62
- if (options.auth)
78
+ if (options.type === "fullstack") {
79
+ await make(["make:resource", "Note", "title:string", "content?:string", "--fullstack"], target);
80
+ if (options.auth) {
81
+ await make(["make:auth"], path.join(target, "api"));
82
+ const authFile = path.join(target, "api/src/auth.ts");
83
+ const source = await readFile(authFile, "utf8");
84
+ await writeFile(authFile, source.replace('cookieSession: process.env.AUTH_COOKIE_SESSION === "true"', "cookieSession: true"));
85
+ }
86
+ }
87
+ if (options.auth && options.type !== "react" && options.type !== "fullstack")
63
88
  await make(["make:auth"], target);
89
+ if (options.tenancy) {
90
+ await installTenancy(target);
91
+ const readme = path.join(target, "README.md");
92
+ await writeFile(readme, await readFile(readme, "utf8") + "\nTenancy enabled: set TENANCY_SUPERADMIN_EMAIL and TENANCY_SUPERADMIN_PASSWORD for npm run tenancy:setup. Setup provisions only the Super Admin. Create tenants and assign their administrators in Tenant administration on the main domain; no default tenant or automatic record migration is created. Use the Vite port in the URL. Configure TENANCY_BASE_DOMAIN, wildcard DNS, HTTPS and a same-origin Host-preserving API proxy for production. Workspace sessions are host-only. Platform admins manage tenants; tenant roles belong to memberships.\n");
93
+ }
64
94
  return target;
65
95
  }
66
96
  function run(command, args, cwd, shell = false) {
@@ -76,11 +106,9 @@ export async function installApp(target, manager) {
76
106
  try {
77
107
  // Both the command and its arguments are fixed, not user-supplied shell text.
78
108
  await run(manager, ["install"], target, process.platform === "win32");
79
- const require = createRequire(path.join(target, "package.json"));
80
- const cli = path.join(path.dirname(require.resolve("@tilcayo/cli")), "bin.js");
81
- await run(process.execPath, [cli, "build"], target);
109
+ await run(manager, ["run", "build"], target, process.platform === "win32");
82
110
  }
83
111
  catch {
84
- throw new Error(`Project created at ${target}, but dependency installation or build failed. Check that ${manager} is installed and Tilcayo packages are available in your registry, then run ${manager} install and the Tilcayo build command inside the project.`);
112
+ throw new Error(`Project created at ${target}, but dependency installation or build failed. Check that ${manager} is installed and Tilcayo packages are available in your registry, then run ${manager} install and ${manager} run build inside the project.`);
85
113
  }
86
114
  }
@@ -0,0 +1,2 @@
1
+ /** Applied only after generating a full-stack auth/admin app. Also used for explicit upgrades. */
2
+ export declare function installTenancy(root: string): Promise<void>;