create-softeneers-app 0.2.1 → 0.2.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.html +4 -4
- package/README.md +6 -6
- package/dist/args.js +23 -2
- package/dist/args.js.map +1 -1
- package/dist/fragments.js +1 -1
- package/dist/fragments.js.map +1 -1
- package/dist/index.js +10 -7
- package/dist/index.js.map +1 -1
- package/dist/prompts.js +12 -2
- package/dist/prompts.js.map +1 -1
- package/dist/scaffold.js +24 -1
- package/dist/scaffold.js.map +1 -1
- package/package.json +1 -1
- package/templates/express-api/.env.example +23 -0
- package/templates/express-api/README.md +68 -1
- package/templates/express-api/gitignore +7 -0
- package/templates/express-api/package.json +3 -0
- package/templates/express-api/softeneers.template.json +20 -1
- package/templates/express-api/src/email/mailer.ts +6 -0
- package/templates/express-api/src/email/routes.ts +26 -0
- package/templates/express-api/src/env.ts +18 -0
- package/templates/express-api/src/index.ts +23 -0
- package/templates/express-api/src/payments/routes.ts +44 -0
- package/templates/express-api/src/payments/stripe.ts +6 -0
- package/templates/express-api/src/payments/webhook.ts +43 -0
- package/templates/express-api/src/storage/routes.ts +28 -0
- package/templates/express-api/src/storage/store.ts +13 -0
- package/templates/hono-api/.env.example +23 -0
- package/templates/hono-api/README.md +69 -2
- package/templates/hono-api/gitignore +7 -0
- package/templates/hono-api/package.json +3 -0
- package/templates/hono-api/softeneers.template.json +20 -1
- package/templates/hono-api/src/email/mailer.ts +6 -0
- package/templates/hono-api/src/email/routes.ts +24 -0
- package/templates/hono-api/src/env.ts +18 -0
- package/templates/hono-api/src/index.ts +20 -0
- package/templates/hono-api/src/payments/routes.ts +41 -0
- package/templates/hono-api/src/payments/stripe.ts +6 -0
- package/templates/hono-api/src/payments/webhook.ts +36 -0
- package/templates/hono-api/src/storage/routes.ts +29 -0
- package/templates/hono-api/src/storage/store.ts +13 -0
- package/templates/minimal/gitignore +7 -0
- package/templates/next-fullstack/apps/web/gitignore +41 -0
- package/templates/next-fullstack/gitignore +11 -0
- package/templates/tanstack-start/.env.example +23 -0
- package/templates/tanstack-start/README.md +58 -3
- package/templates/tanstack-start/gitignore +16 -0
- package/templates/tanstack-start/package.json +4 -0
- package/templates/tanstack-start/softeneers.template.json +28 -3
- package/templates/tanstack-start/src/lib/auth-client.ts +4 -0
- package/templates/tanstack-start/src/routes/account.tsx +47 -0
- package/templates/tanstack-start/src/routes/api/webhooks/stripe.ts +43 -0
- package/templates/tanstack-start/src/routes/billing.tsx +59 -0
- package/templates/tanstack-start/src/routes/index.tsx +15 -6
- package/templates/tanstack-start/src/routes/login.tsx +77 -0
- package/templates/tanstack-start/src/server/email.ts +27 -0
- package/templates/tanstack-start/src/server/env.ts +18 -0
- package/templates/tanstack-start/src/server/payments.ts +40 -0
- package/templates/tanstack-start/src/server/storage.ts +36 -0
package/README.html
CHANGED
|
@@ -38,15 +38,15 @@ footer { margin-top: 3rem; padding-top: 1rem; border-top: 1px solid #8884; font-
|
|
|
38
38
|
<p>Five templates, each for a different kind of user, with composable toggles:</p>
|
|
39
39
|
<table><thead><tr><th>Template</th><th>What you get</th><th>Toggles</th></tr></thead><tbody>
|
|
40
40
|
<tr><td><code>next-fullstack</code></td><td>Next.js web + Express/Sequelize/MySQL API</td><td>(all-in)</td></tr>
|
|
41
|
-
<tr><td><code>express-api</code></td><td>Express 5 + TypeScript REST API (cars CRUD)</td><td>db · auth · docker</td></tr>
|
|
42
|
-
<tr><td><code>hono-api</code></td><td>Hono + TypeScript API (cars CRUD)</td><td>db · auth · docker</td></tr>
|
|
43
|
-
<tr><td><code>tanstack-start</code></td><td>TanStack Start fullstack React app</td><td>db · auth · docker</td></tr>
|
|
41
|
+
<tr><td><code>express-api</code></td><td>Express 5 + TypeScript REST API (cars CRUD)</td><td>db · auth · docker · email · storage · payments</td></tr>
|
|
42
|
+
<tr><td><code>hono-api</code></td><td>Hono + TypeScript API (cars CRUD)</td><td>db · auth · docker · email · storage · payments</td></tr>
|
|
43
|
+
<tr><td><code>tanstack-start</code></td><td>TanStack Start fullstack React app (+ auth/billing UI)</td><td>db · auth · docker · email · storage · payments</td></tr>
|
|
44
44
|
<tr><td><code>minimal</code></td><td>Zero-framework Node + TypeScript starter</td><td>(none)</td></tr>
|
|
45
45
|
</tbody></table>
|
|
46
46
|
<pre><code>npx create-softeneers-app@latest my-app # interactive
|
|
47
47
|
npx create-softeneers-app@latest api -t express-api --yes # all defaults
|
|
48
48
|
npx create-softeneers-app@latest api -t hono-api --no-auth --no-docker</code></pre>
|
|
49
|
-
<p>Flags: <code>--template <slug></code>, <code>--yes</code>/<code>-y</code>, <code>--db</code
|
|
49
|
+
<p>Flags: <code>--template <slug></code>, <code>--yes</code>/<code>-y</code>, <code>--db</code>, <code>--auth</code>, <code>--docker</code>, <code>--email</code>, <code>--storage</code>, <code>--payments</code> (each with a <code>--no-*</code> form), <code>--no-install</code>, <code>--no-git</code>, <code>--pm <pnpm|npm|yarn></code>, <code>--help</code>, <code>--version</code>. See <a href="../../docs/CLI-SPEC.html"><code>../../docs/CLI-SPEC.md</code></a> for the full contract.</p>
|
|
50
50
|
<h2>Local development</h2>
|
|
51
51
|
<pre><code>npm run build -w create-softeneers-app # tsc + bundle templates → dist/ + templates/
|
|
52
52
|
node apps/cli/dist/index.js my-app --yes # run the built CLI
|
package/README.md
CHANGED
|
@@ -14,9 +14,9 @@ Five templates, each for a different kind of user, with composable toggles:
|
|
|
14
14
|
| Template | What you get | Toggles |
|
|
15
15
|
| ---------------- | ----------------------------------------- | ------------------ |
|
|
16
16
|
| `next-fullstack` | Next.js web + Express/Sequelize/MySQL API | (all-in) |
|
|
17
|
-
| `express-api` | Express 5 + TypeScript REST API (cars CRUD) | db · auth · docker |
|
|
18
|
-
| `hono-api` | Hono + TypeScript API (cars CRUD) | db · auth · docker |
|
|
19
|
-
| `tanstack-start` | TanStack Start fullstack React app
|
|
17
|
+
| `express-api` | Express 5 + TypeScript REST API (cars CRUD) | db · auth · docker · email · storage · payments |
|
|
18
|
+
| `hono-api` | Hono + TypeScript API (cars CRUD) | db · auth · docker · email · storage · payments |
|
|
19
|
+
| `tanstack-start` | TanStack Start fullstack React app (+ auth/billing UI) | db · auth · docker · email · storage · payments |
|
|
20
20
|
| `minimal` | Zero-framework Node + TypeScript starter | (none) |
|
|
21
21
|
|
|
22
22
|
```bash
|
|
@@ -25,9 +25,9 @@ npx create-softeneers-app@latest api -t express-api --yes # all defaults
|
|
|
25
25
|
npx create-softeneers-app@latest api -t hono-api --no-auth --no-docker
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Flags: `--template <slug>`, `--yes`/`-y`, `--db
|
|
29
|
-
`--
|
|
30
|
-
`--help`, `--version`. See
|
|
28
|
+
Flags: `--template <slug>`, `--yes`/`-y`, `--db`, `--auth`, `--docker`, `--email`,
|
|
29
|
+
`--storage`, `--payments` (each with a `--no-*` form), `--no-install`, `--no-git`,
|
|
30
|
+
`--pm <pnpm|npm|yarn>`, `--help`, `--version`. See
|
|
31
31
|
[`../../docs/CLI-SPEC.md`](../../docs/CLI-SPEC.md) for the full contract.
|
|
32
32
|
|
|
33
33
|
## Local development
|
package/dist/args.js
CHANGED
|
@@ -54,6 +54,24 @@ export function parseArgs(argv) {
|
|
|
54
54
|
case "--no-docker":
|
|
55
55
|
opts.docker = false;
|
|
56
56
|
break;
|
|
57
|
+
case "--email":
|
|
58
|
+
opts.email = true;
|
|
59
|
+
break;
|
|
60
|
+
case "--no-email":
|
|
61
|
+
opts.email = false;
|
|
62
|
+
break;
|
|
63
|
+
case "--storage":
|
|
64
|
+
opts.storage = true;
|
|
65
|
+
break;
|
|
66
|
+
case "--no-storage":
|
|
67
|
+
opts.storage = false;
|
|
68
|
+
break;
|
|
69
|
+
case "--payments":
|
|
70
|
+
opts.payments = true;
|
|
71
|
+
break;
|
|
72
|
+
case "--no-payments":
|
|
73
|
+
opts.payments = false;
|
|
74
|
+
break;
|
|
57
75
|
case "--help":
|
|
58
76
|
case "-h":
|
|
59
77
|
opts.help = true;
|
|
@@ -101,8 +119,11 @@ Options:
|
|
|
101
119
|
-t, --template <name> Template to use (default: prompt)
|
|
102
120
|
-y, --yes Accept defaults, no prompts
|
|
103
121
|
--db / --no-db Include a database layer (template default if unset)
|
|
104
|
-
--auth / --no-auth Include authentication (template default if unset)
|
|
105
|
-
--docker / --no-docker
|
|
122
|
+
--auth / --no-auth Include authentication + UI (template default if unset)
|
|
123
|
+
--docker / --no-docker Include a Docker recipe
|
|
124
|
+
--email / --no-email Include transactional email (@softeneers/email)
|
|
125
|
+
--storage / --no-storage Include object storage (@softeneers/storage)
|
|
126
|
+
--payments / --no-payments Include Stripe payments (@softeneers/payments)
|
|
106
127
|
--no-install Don't install dependencies
|
|
107
128
|
--no-git Don't run "git init"
|
|
108
129
|
--pm <npm|pnpm|yarn> Package manager (default: auto-detected, else npm)
|
package/dist/args.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAAA,uEAAuE;
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAAA,uEAAuE;AA4BvE,MAAM,GAAG,GAAqB,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;AAEtD;;;;GAIG;AACH,MAAM,UAAU,oBAAoB;IAClC,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,EAAE,CAAC;IACnD,MAAM,IAAI,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACpD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAc;IACtC,MAAM,IAAI,GAAe;QACvB,GAAG,EAAE,KAAK;QACV,OAAO,EAAE,IAAI;QACb,GAAG,EAAE,IAAI;QACT,cAAc,EAAE,oBAAoB,EAAE;QACtC,IAAI,EAAE,KAAK;QACX,OAAO,EAAE,KAAK;KACf,CAAC;IAEF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,GAAG,KAAK,SAAS;YAAE,SAAS;QAChC,QAAQ,GAAG,EAAE,CAAC;YACZ,KAAK,OAAO,CAAC;YACb,KAAK,IAAI;gBACP,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC;gBAChB,MAAM;YACR,KAAK,cAAc;gBACjB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;gBACrB,MAAM;YACR,KAAK,UAAU;gBACb,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC;gBACjB,MAAM;YACR,KAAK,MAAM;gBACT,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;gBACf,MAAM;YACR,KAAK,SAAS;gBACZ,IAAI,CAAC,EAAE,GAAG,KAAK,CAAC;gBAChB,MAAM;YACR,KAAK,QAAQ;gBACX,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;gBACjB,MAAM;YACR,KAAK,WAAW;gBACd,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;gBAClB,MAAM;YACR,KAAK,UAAU;gBACb,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;gBACnB,MAAM;YACR,KAAK,aAAa;gBAChB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;gBACpB,MAAM;YACR,KAAK,SAAS;gBACZ,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;gBAClB,MAAM;YACR,KAAK,YAAY;gBACf,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;gBACnB,MAAM;YACR,KAAK,WAAW;gBACd,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;gBACpB,MAAM;YACR,KAAK,cAAc;gBACjB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;gBACrB,MAAM;YACR,KAAK,YAAY;gBACf,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACrB,MAAM;YACR,KAAK,eAAe;gBAClB,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;gBACtB,MAAM;YACR,KAAK,QAAQ,CAAC;YACd,KAAK,IAAI;gBACP,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;gBACjB,MAAM;YACR,KAAK,WAAW,CAAC;YACjB,KAAK,IAAI;gBACP,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;gBACpB,MAAM;YACR,KAAK,YAAY,CAAC;YAClB,KAAK,IAAI;gBACP,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC1B,MAAM;YACR,KAAK,MAAM,CAAC,CAAC,CAAC;gBACZ,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBACrB,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAoB,CAAC,EAAE,CAAC;oBACxC,MAAM,IAAI,QAAQ,CAAC,wBAAwB,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;gBAC7E,CAAC;gBACD,IAAI,CAAC,cAAc,GAAG,EAAoB,CAAC;gBAC3C,MAAM;YACR,CAAC;YACD;gBACE,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;oBACxB,MAAM,IAAI,QAAQ,CAAC,mBAAmB,GAAG,EAAE,CAAC,CAAC;gBAC/C,CAAC;gBACD,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;oBACjC,IAAI,CAAC,SAAS,GAAG,GAAG,CAAC;gBACvB,CAAC;qBAAM,CAAC;oBACN,MAAM,IAAI,QAAQ,CAAC,wBAAwB,GAAG,EAAE,CAAC,CAAC;gBACpD,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,+DAA+D;AAC/D,MAAM,OAAO,QAAS,SAAQ,KAAK;CAAG;AAEtC,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCxB,CAAC"}
|
package/dist/fragments.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// without a manifest (e.g. `minimal`) support no toggles and are copied as-is.
|
|
6
6
|
import { basename, join } from "node:path";
|
|
7
7
|
import { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
-
export const TOGGLE_KEYS = ["db", "auth", "docker"];
|
|
8
|
+
export const TOGGLE_KEYS = ["db", "auth", "docker", "email", "storage", "payments"];
|
|
9
9
|
const MANIFEST_NAME = "softeneers.template.json";
|
|
10
10
|
export function readManifest(dir) {
|
|
11
11
|
const p = join(dir, MANIFEST_NAME);
|
package/dist/fragments.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fragments.js","sourceRoot":"","sources":["../src/fragments.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,+EAA+E;AAC/E,iFAAiF;AACjF,8EAA8E;AAC9E,+EAA+E;AAC/E,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAEvF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"fragments.js","sourceRoot":"","sources":["../src/fragments.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import { basename, resolve } from "node:path";
|
|
4
4
|
import { intro, log, note, outro, spinner } from "@clack/prompts";
|
|
5
5
|
import { CliError, HELP_TEXT, parseArgs } from "./args.js";
|
|
6
|
-
import { applyToggles, templateToggleDefaults } from "./fragments.js";
|
|
6
|
+
import { applyToggles, TOGGLE_KEYS, templateToggleDefaults } from "./fragments.js";
|
|
7
7
|
import { promptProjectName, promptTemplate, promptToggles } from "./prompts.js";
|
|
8
8
|
import { assertTargetUsable, copyTemplate, gitInit, installDeps, toPackageName, transform, } from "./scaffold.js";
|
|
9
9
|
import { DEFAULT_TEMPLATE, findTemplate, resolveTemplateDir } from "./templates.js";
|
|
@@ -47,13 +47,16 @@ async function main() {
|
|
|
47
47
|
}
|
|
48
48
|
// 3. Feature toggles (db / auth / docker) — only those the template supports.
|
|
49
49
|
const toggleDefaults = templateToggleDefaults(templateDir);
|
|
50
|
-
const overrides = {
|
|
50
|
+
const overrides = {
|
|
51
|
+
db: opts.db,
|
|
52
|
+
auth: opts.auth,
|
|
53
|
+
docker: opts.docker,
|
|
54
|
+
email: opts.email,
|
|
55
|
+
storage: opts.storage,
|
|
56
|
+
payments: opts.payments,
|
|
57
|
+
};
|
|
51
58
|
const toggles = opts.yes
|
|
52
|
-
?
|
|
53
|
-
db: overrides.db ?? toggleDefaults.db ?? false,
|
|
54
|
-
auth: overrides.auth ?? toggleDefaults.auth ?? false,
|
|
55
|
-
docker: overrides.docker ?? toggleDefaults.docker ?? false,
|
|
56
|
-
}
|
|
59
|
+
? Object.fromEntries(TOGGLE_KEYS.map((key) => [key, overrides[key] ?? toggleDefaults[key] ?? false]))
|
|
57
60
|
: await promptToggles(toggleDefaults, overrides);
|
|
58
61
|
// 4. Generate.
|
|
59
62
|
assertTargetUsable(targetDir);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE9C,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAElE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAgB,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/prompts.js
CHANGED
|
@@ -31,8 +31,11 @@ export async function promptTemplate() {
|
|
|
31
31
|
}
|
|
32
32
|
const TOGGLE_LABELS = {
|
|
33
33
|
db: "Include a database? (CRUD persisted to MySQL via Sequelize)",
|
|
34
|
-
auth: "Include authentication? (email + password via better-auth)",
|
|
34
|
+
auth: "Include authentication? (email + password via better-auth, with UI)",
|
|
35
35
|
docker: "Include a Docker recipe? (docker-compose for local services)",
|
|
36
|
+
email: "Include transactional email? (Resend + React Email)",
|
|
37
|
+
storage: "Include object storage? (S3 / R2 / MinIO uploads)",
|
|
38
|
+
payments: "Include Stripe payments? (checkout, subscriptions, webhooks)",
|
|
36
39
|
};
|
|
37
40
|
/**
|
|
38
41
|
* Ask for each toggle the template supports (a key present in `defaults`).
|
|
@@ -40,7 +43,14 @@ const TOGGLE_LABELS = {
|
|
|
40
43
|
* skips its prompt. Unsupported toggles resolve to `false`.
|
|
41
44
|
*/
|
|
42
45
|
export async function promptToggles(defaults, overrides) {
|
|
43
|
-
const result = {
|
|
46
|
+
const result = {
|
|
47
|
+
db: false,
|
|
48
|
+
auth: false,
|
|
49
|
+
docker: false,
|
|
50
|
+
email: false,
|
|
51
|
+
storage: false,
|
|
52
|
+
payments: false,
|
|
53
|
+
};
|
|
44
54
|
for (const key of Object.keys(defaults)) {
|
|
45
55
|
if (overrides[key] !== undefined) {
|
|
46
56
|
result[key] = overrides[key];
|
package/dist/prompts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEzE,OAAO,EAAgC,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE7D,SAAS,IAAI,CAAI,KAAiB;IAChC,IAAI,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACpB,MAAM,CAAC,YAAY,CAAC,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,KAAU,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAAgB;IACtD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC;QACvB,OAAO,EAAE,eAAe;QACxB,WAAW,EAAE,QAAQ;QACrB,YAAY,EAAE,OAAO;QACrB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,8BAA8B,CAAC,CAAC,CAAC,SAAS,CAAC;KACtF,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC;QACzB,OAAO,EAAE,mCAAmC;QAC5C,YAAY,EAAE,gBAAgB;QAC9B,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC7B,KAAK,EAAE,CAAC,CAAC,IAAI;YACb,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,sBAAsB;SAC7D,CAAC,CAAC;KACJ,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,aAAa,GAA8B;IAC/C,EAAE,EAAE,6DAA6D;IACjE,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEzE,OAAO,EAAgC,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE7D,SAAS,IAAI,CAAI,KAAiB;IAChC,IAAI,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACpB,MAAM,CAAC,YAAY,CAAC,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,KAAU,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAAgB;IACtD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC;QACvB,OAAO,EAAE,eAAe;QACxB,WAAW,EAAE,QAAQ;QACrB,YAAY,EAAE,OAAO;QACrB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,8BAA8B,CAAC,CAAC,CAAC,SAAS,CAAC;KACtF,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC;QACzB,OAAO,EAAE,mCAAmC;QAC5C,YAAY,EAAE,gBAAgB;QAC9B,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC7B,KAAK,EAAE,CAAC,CAAC,IAAI;YACb,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,sBAAsB;SAC7D,CAAC,CAAC;KACJ,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,aAAa,GAA8B;IAC/C,EAAE,EAAE,6DAA6D;IACjE,IAAI,EAAE,qEAAqE;IAC3E,MAAM,EAAE,8DAA8D;IACtE,KAAK,EAAE,qDAAqD;IAC5D,OAAO,EAAE,mDAAmD;IAC5D,QAAQ,EAAE,8DAA8D;CACzE,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAA0B,EAC1B,SAA2B;IAE3B,MAAM,MAAM,GAAY;QACtB,EAAE,EAAE,KAAK;QACT,IAAI,EAAE,KAAK;QACX,MAAM,EAAE,KAAK;QACb,KAAK,EAAE,KAAK;QACZ,OAAO,EAAE,KAAK;QACd,QAAQ,EAAE,KAAK;KAChB,CAAC;IACF,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAgB,EAAE,CAAC;QACvD,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE,CAAC;YACjC,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,CAAE,CAAC;YAC9B,SAAS;QACX,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC;YAC1B,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC;YAC3B,YAAY,EAAE,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK;SACrC,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/dist/scaffold.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import { basename, join } from "node:path";
|
|
4
4
|
import { CliError } from "./args.js";
|
|
5
5
|
/** Names excluded when copying a template (docs/CLI-SPEC.md → copy exclusions). */
|
|
@@ -101,6 +101,28 @@ function generateEnvFiles(targetDir) {
|
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Restore `.gitignore` files. Templates ship them as `gitignore` (no dot) because
|
|
106
|
+
* npm strips a real `.gitignore` from a published package; on generation we rename
|
|
107
|
+
* each back so the project ignores node_modules/.env/etc. from the first commit.
|
|
108
|
+
*/
|
|
109
|
+
function restoreGitignores(targetDir) {
|
|
110
|
+
const stack = [targetDir];
|
|
111
|
+
while (stack.length) {
|
|
112
|
+
const dir = stack.pop();
|
|
113
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
114
|
+
if (entry.name === "node_modules")
|
|
115
|
+
continue;
|
|
116
|
+
const full = join(dir, entry.name);
|
|
117
|
+
if (entry.isDirectory()) {
|
|
118
|
+
stack.push(full);
|
|
119
|
+
}
|
|
120
|
+
else if (entry.name === "gitignore") {
|
|
121
|
+
renameSync(full, join(dir, ".gitignore"));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
104
126
|
/** Replace {{PROJECT_NAME}} placeholders in the README. */
|
|
105
127
|
function substitutePlaceholders(targetDir, projectName) {
|
|
106
128
|
const readme = join(targetDir, "README.md");
|
|
@@ -110,6 +132,7 @@ function substitutePlaceholders(targetDir, projectName) {
|
|
|
110
132
|
writeFileSync(readme, next);
|
|
111
133
|
}
|
|
112
134
|
export function transform(targetDir, projectName, pkgName, pm) {
|
|
135
|
+
restoreGitignores(targetDir);
|
|
113
136
|
rewriteRootPackage(targetDir, pkgName, pm);
|
|
114
137
|
generateEnvFiles(targetDir);
|
|
115
138
|
substitutePlaceholders(targetDir, projectName);
|
package/dist/scaffold.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-softeneers-app",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Scaffold a new Softeneers Framework project: 5 templates (next-fullstack, express-api, hono-api, tanstack-start, minimal) with db/auth/docker toggles.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,3 +11,26 @@ DB_PASSWORD=
|
|
|
11
11
|
AUTH_SECRET=dev-secret-change-me-to-a-long-random-string
|
|
12
12
|
AUTH_BASE_URL=http://localhost:4000
|
|
13
13
|
# #endif
|
|
14
|
+
# #if email
|
|
15
|
+
# Get a key at https://resend.com — the app boots without it; sending needs it.
|
|
16
|
+
RESEND_API_KEY=re_set_me
|
|
17
|
+
EMAIL_FROM=onboarding@resend.dev
|
|
18
|
+
# #endif
|
|
19
|
+
# #if storage
|
|
20
|
+
# S3-compatible storage (AWS S3 / Cloudflare R2 / MinIO).
|
|
21
|
+
S3_ACCESS_KEY_ID=
|
|
22
|
+
S3_SECRET_ACCESS_KEY=
|
|
23
|
+
S3_BUCKET=uploads
|
|
24
|
+
S3_REGION=auto
|
|
25
|
+
S3_ENDPOINT=
|
|
26
|
+
# #endif
|
|
27
|
+
# #if payments
|
|
28
|
+
# Stripe — the ONLY thing you must set for payments to work. Test keys from
|
|
29
|
+
# https://dashboard.stripe.com/test/apikeys ; webhook secret from `stripe listen`
|
|
30
|
+
# or the dashboard. Create two Prices and paste their ids below.
|
|
31
|
+
APP_URL=http://localhost:4000
|
|
32
|
+
STRIPE_SECRET_KEY=sk_test_set_me
|
|
33
|
+
STRIPE_WEBHOOK_SECRET=whsec_set_me
|
|
34
|
+
STRIPE_PRICE_ID=price_set_me
|
|
35
|
+
STRIPE_SUBSCRIPTION_PRICE_ID=price_set_me
|
|
36
|
+
# #endif
|
|
@@ -32,6 +32,20 @@ npm run db:reset # drop, recreate, reseed
|
|
|
32
32
|
# #if auth
|
|
33
33
|
| ALL | `/api/auth/*` | better-auth routes |
|
|
34
34
|
# #endif
|
|
35
|
+
# #if email
|
|
36
|
+
| POST | `/api/email/welcome` | Send a welcome email |
|
|
37
|
+
# #endif
|
|
38
|
+
# #if storage
|
|
39
|
+
| POST | `/api/files/:key` | Upload an object |
|
|
40
|
+
| GET | `/api/files/:key/url`| Signed download URL |
|
|
41
|
+
| DELETE | `/api/files/:key` | Delete an object |
|
|
42
|
+
# #endif
|
|
43
|
+
# #if payments
|
|
44
|
+
| POST | `/api/payments/checkout` | One-time Stripe checkout |
|
|
45
|
+
| POST | `/api/payments/subscribe` | Subscription checkout |
|
|
46
|
+
| POST | `/api/payments/portal` | Billing portal |
|
|
47
|
+
| POST | `/api/webhooks/stripe` | Stripe webhook (verified) |
|
|
48
|
+
# #endif
|
|
35
49
|
|
|
36
50
|
```bash
|
|
37
51
|
curl localhost:4000/api/cars
|
|
@@ -61,7 +75,60 @@ npm run db:migrate && npm run db:seed
|
|
|
61
75
|
## Authentication
|
|
62
76
|
|
|
63
77
|
Email + password auth via better-auth ([`@softeneers/auth`](https://www.npmjs.com/package/@softeneers/auth)),
|
|
64
|
-
mounted at `/api/auth
|
|
78
|
+
mounted at `/api/auth/*` (`sign-up/email`, `sign-in/email`, `get-session`, …).
|
|
79
|
+
Set a strong `AUTH_SECRET` in `.env` before deploying.
|
|
80
|
+
# #endif
|
|
81
|
+
# #if email
|
|
82
|
+
|
|
83
|
+
## Email
|
|
84
|
+
|
|
85
|
+
Transactional email via Resend ([`@softeneers/email`](https://www.npmjs.com/package/@softeneers/email)).
|
|
86
|
+
Set `RESEND_API_KEY` (and `EMAIL_FROM`) in `.env`, then:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
curl -X POST localhost:4000/api/email/welcome -H 'content-type: application/json' \
|
|
90
|
+
-d '{"to":"you@example.com","name":"Ada"}'
|
|
91
|
+
```
|
|
92
|
+
# #endif
|
|
93
|
+
# #if storage
|
|
94
|
+
|
|
95
|
+
## Storage
|
|
96
|
+
|
|
97
|
+
S3-compatible uploads ([`@softeneers/storage`](https://www.npmjs.com/package/@softeneers/storage))
|
|
98
|
+
— works with AWS S3, Cloudflare R2, and MinIO. Set the `S3_*` keys in `.env`, then:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
curl -X POST --data-binary @photo.png localhost:4000/api/files/photo.png
|
|
102
|
+
curl localhost:4000/api/files/photo.png/url # → a signed download URL
|
|
103
|
+
```
|
|
104
|
+
# #endif
|
|
105
|
+
# #if payments
|
|
106
|
+
|
|
107
|
+
## Payments (Stripe)
|
|
108
|
+
|
|
109
|
+
Stripe checkout, subscriptions, the billing portal, and a verified webhook
|
|
110
|
+
([`@softeneers/payments`](https://www.npmjs.com/package/@softeneers/payments)) —
|
|
111
|
+
**all pre-wired**. The only thing you do is paste your keys into `.env`:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
STRIPE_SECRET_KEY=sk_test_…
|
|
115
|
+
STRIPE_WEBHOOK_SECRET=whsec_…
|
|
116
|
+
STRIPE_PRICE_ID=price_… # one-time price
|
|
117
|
+
STRIPE_SUBSCRIPTION_PRICE_ID=price_… # recurring price
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Get test keys at <https://dashboard.stripe.com/test/apikeys> and create two
|
|
121
|
+
Prices. Locally, forward webhooks with the Stripe CLI (this also prints the
|
|
122
|
+
`whsec_…` secret):
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
stripe listen --forward-to localhost:4000/api/webhooks/stripe
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Then `POST /api/payments/checkout` (or `/subscribe`) returns a Stripe URL to
|
|
129
|
+
redirect the customer to. The webhook handles `checkout.session.completed` and
|
|
130
|
+
the `customer.subscription.*` events — add your fulfilment logic in
|
|
131
|
+
`src/payments/webhook.ts`.
|
|
65
132
|
# #endif
|
|
66
133
|
|
|
67
134
|
## Getting started
|
|
@@ -20,7 +20,10 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@softeneers/auth": "^0.1.0",
|
|
22
22
|
"@softeneers/db": "^0.1.0",
|
|
23
|
+
"@softeneers/email": "^0.1.0",
|
|
23
24
|
"@softeneers/env": "^0.1.0",
|
|
25
|
+
"@softeneers/payments": "^0.1.0",
|
|
26
|
+
"@softeneers/storage": "^0.1.0",
|
|
24
27
|
"cors": "^2.8.5",
|
|
25
28
|
"dotenv": "^16.4.5",
|
|
26
29
|
"express": "^5.0.0",
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"toggles": {
|
|
2
|
+
"toggles": {
|
|
3
|
+
"db": false,
|
|
4
|
+
"auth": false,
|
|
5
|
+
"docker": false,
|
|
6
|
+
"email": false,
|
|
7
|
+
"storage": false,
|
|
8
|
+
"payments": false
|
|
9
|
+
},
|
|
3
10
|
"fragments": {
|
|
4
11
|
"db": {
|
|
5
12
|
"removePaths": ["src/db.ts", "src/scripts", "docker-compose.yml"],
|
|
@@ -12,6 +19,18 @@
|
|
|
12
19
|
},
|
|
13
20
|
"docker": {
|
|
14
21
|
"removePaths": ["docker-compose.yml"]
|
|
22
|
+
},
|
|
23
|
+
"email": {
|
|
24
|
+
"removePaths": ["src/email"],
|
|
25
|
+
"removeDeps": ["@softeneers/email"]
|
|
26
|
+
},
|
|
27
|
+
"storage": {
|
|
28
|
+
"removePaths": ["src/storage"],
|
|
29
|
+
"removeDeps": ["@softeneers/storage"]
|
|
30
|
+
},
|
|
31
|
+
"payments": {
|
|
32
|
+
"removePaths": ["src/payments"],
|
|
33
|
+
"removeDeps": ["@softeneers/payments"]
|
|
15
34
|
}
|
|
16
35
|
}
|
|
17
36
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
|
|
3
|
+
import { sendEmail } from "@softeneers/email";
|
|
4
|
+
|
|
5
|
+
import { env } from "../env.js";
|
|
6
|
+
import { mailer } from "./mailer.js";
|
|
7
|
+
|
|
8
|
+
export const emailRouter = Router();
|
|
9
|
+
|
|
10
|
+
// Demo: send a welcome email. POST { "to": "a@b.com", "name": "Ada" }
|
|
11
|
+
emailRouter.post("/welcome", async (req, res) => {
|
|
12
|
+
const to = typeof req.body?.to === "string" ? req.body.to : "";
|
|
13
|
+
const name = typeof req.body?.name === "string" ? req.body.name : "there";
|
|
14
|
+
if (!to) {
|
|
15
|
+
res.status(400).json({ message: "to (email address) is required." });
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
await sendEmail(mailer, {
|
|
19
|
+
from: env.EMAIL_FROM,
|
|
20
|
+
to,
|
|
21
|
+
subject: "Welcome!",
|
|
22
|
+
html: `<h1>Welcome, ${name}!</h1><p>Thanks for joining.</p>`,
|
|
23
|
+
text: `Welcome, ${name}! Thanks for joining.`,
|
|
24
|
+
});
|
|
25
|
+
res.json({ sent: true });
|
|
26
|
+
});
|
|
@@ -19,5 +19,23 @@ export const env = createEnv({
|
|
|
19
19
|
AUTH_SECRET: z.string().min(16).default("dev-secret-change-me-to-a-long-random-string"),
|
|
20
20
|
AUTH_BASE_URL: z.string().default("http://localhost:4000"),
|
|
21
21
|
// #endif
|
|
22
|
+
// #if email
|
|
23
|
+
RESEND_API_KEY: z.string().default("re_set_me_in_dotenv"),
|
|
24
|
+
EMAIL_FROM: z.string().default("onboarding@resend.dev"),
|
|
25
|
+
// #endif
|
|
26
|
+
// #if storage
|
|
27
|
+
S3_ACCESS_KEY_ID: z.string().default(""),
|
|
28
|
+
S3_SECRET_ACCESS_KEY: z.string().default(""),
|
|
29
|
+
S3_BUCKET: z.string().default("uploads"),
|
|
30
|
+
S3_REGION: z.string().default("auto"),
|
|
31
|
+
S3_ENDPOINT: z.string().default(""),
|
|
32
|
+
// #endif
|
|
33
|
+
// #if payments
|
|
34
|
+
APP_URL: z.string().default("http://localhost:4000"),
|
|
35
|
+
STRIPE_SECRET_KEY: z.string().default("sk_test_set_me_in_dotenv"),
|
|
36
|
+
STRIPE_WEBHOOK_SECRET: z.string().default("whsec_set_me_in_dotenv"),
|
|
37
|
+
STRIPE_PRICE_ID: z.string().default("price_set_me_in_dotenv"),
|
|
38
|
+
STRIPE_SUBSCRIPTION_PRICE_ID: z.string().default("price_set_me_in_dotenv"),
|
|
39
|
+
// #endif
|
|
22
40
|
},
|
|
23
41
|
});
|
|
@@ -8,6 +8,16 @@ import { toNodeHandler } from "@softeneers/auth";
|
|
|
8
8
|
|
|
9
9
|
import { auth } from "./auth/auth.js";
|
|
10
10
|
// #endif
|
|
11
|
+
// #if email
|
|
12
|
+
import { emailRouter } from "./email/routes.js";
|
|
13
|
+
// #endif
|
|
14
|
+
// #if storage
|
|
15
|
+
import { storageRouter } from "./storage/routes.js";
|
|
16
|
+
// #endif
|
|
17
|
+
// #if payments
|
|
18
|
+
import { paymentsRouter } from "./payments/routes.js";
|
|
19
|
+
import { stripeWebhookHandler } from "./payments/webhook.js";
|
|
20
|
+
// #endif
|
|
11
21
|
|
|
12
22
|
const app = express();
|
|
13
23
|
|
|
@@ -15,6 +25,10 @@ const app = express();
|
|
|
15
25
|
// better-auth handles its own body parsing, so mount it before express.json().
|
|
16
26
|
app.all("/api/auth/*splat", toNodeHandler(auth));
|
|
17
27
|
// #endif
|
|
28
|
+
// #if payments
|
|
29
|
+
// Stripe webhook signature verification needs the raw body — mount before express.json().
|
|
30
|
+
app.post("/api/webhooks/stripe", express.raw({ type: "application/json" }), stripeWebhookHandler);
|
|
31
|
+
// #endif
|
|
18
32
|
|
|
19
33
|
app.use(cors({ origin: env.CORS_ORIGIN }));
|
|
20
34
|
app.use(express.json());
|
|
@@ -24,6 +38,15 @@ app.get("/health", (_req, res) => {
|
|
|
24
38
|
});
|
|
25
39
|
|
|
26
40
|
app.use("/api/cars", carRouter);
|
|
41
|
+
// #if email
|
|
42
|
+
app.use("/api/email", emailRouter);
|
|
43
|
+
// #endif
|
|
44
|
+
// #if storage
|
|
45
|
+
app.use("/api/files", storageRouter);
|
|
46
|
+
// #endif
|
|
47
|
+
// #if payments
|
|
48
|
+
app.use("/api/payments", paymentsRouter);
|
|
49
|
+
// #endif
|
|
27
50
|
|
|
28
51
|
app.use(
|
|
29
52
|
(error: unknown, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
|
|
3
|
+
import { createBillingPortalSession, createCheckoutSession } from "@softeneers/payments";
|
|
4
|
+
|
|
5
|
+
import { env } from "../env.js";
|
|
6
|
+
import { stripe } from "./stripe.js";
|
|
7
|
+
|
|
8
|
+
export const paymentsRouter = Router();
|
|
9
|
+
|
|
10
|
+
// One-time purchase → returns a Stripe Checkout URL to redirect the browser to.
|
|
11
|
+
paymentsRouter.post("/checkout", async (_req, res) => {
|
|
12
|
+
const session = await createCheckoutSession(stripe, {
|
|
13
|
+
mode: "payment",
|
|
14
|
+
priceId: env.STRIPE_PRICE_ID,
|
|
15
|
+
successUrl: `${env.APP_URL}/?paid=1`,
|
|
16
|
+
cancelUrl: `${env.APP_URL}/?canceled=1`,
|
|
17
|
+
});
|
|
18
|
+
res.json({ url: session.url });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Subscription checkout.
|
|
22
|
+
paymentsRouter.post("/subscribe", async (_req, res) => {
|
|
23
|
+
const session = await createCheckoutSession(stripe, {
|
|
24
|
+
mode: "subscription",
|
|
25
|
+
priceId: env.STRIPE_SUBSCRIPTION_PRICE_ID,
|
|
26
|
+
successUrl: `${env.APP_URL}/?subscribed=1`,
|
|
27
|
+
cancelUrl: `${env.APP_URL}/?canceled=1`,
|
|
28
|
+
});
|
|
29
|
+
res.json({ url: session.url });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Billing portal so a customer can manage their subscription. Pass ?customer=cus_…
|
|
33
|
+
paymentsRouter.post("/portal", async (req, res) => {
|
|
34
|
+
const customer = String(req.query.customer ?? "");
|
|
35
|
+
if (!customer) {
|
|
36
|
+
res.status(400).json({ message: "A Stripe customer id (?customer=cus_…) is required." });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const session = await createBillingPortalSession(stripe, {
|
|
40
|
+
customer,
|
|
41
|
+
returnUrl: `${env.APP_URL}/`,
|
|
42
|
+
});
|
|
43
|
+
res.json({ url: session.url });
|
|
44
|
+
});
|