@dreamtree-org/landing-kit 0.4.0 → 0.5.0
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 +18 -0
- package/demo-server.d.ts +21 -4
- package/dist/demo-server.cjs +53 -7
- package/dist/demo-server.js +48 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -179,6 +179,24 @@ export const GET = () => demoAccountsResponse({ accounts: [...] });
|
|
|
179
179
|
| `DEMO_ACCOUNTS=1` | turn the endpoint on (off by default; leave it off in production) |
|
|
180
180
|
| `DEMO_ACCOUNTS_JSON` | `[{ label, identifier, password, company? }]`, replaces the built-in list (accounts a deployment created itself) |
|
|
181
181
|
|
|
182
|
+
#### The `default` demo company (every pro app)
|
|
183
|
+
|
|
184
|
+
Every pro app seeds a company with slug **`default`** — its branches and **one user per role** —
|
|
185
|
+
so the whole product can be demoed end to end, and the chips list exactly those users:
|
|
186
|
+
|
|
187
|
+
```js
|
|
188
|
+
const { demoUsers, demoAccountsExpress, requireDemoPassword } = require('@dreamtree-org/landing-kit/demo-server');
|
|
189
|
+
const manifest = [{ role: 'tenant-admin', label: 'Admin' }, { role: 'viewer', label: 'Viewer' }];
|
|
190
|
+
|
|
191
|
+
// demo:seed (idempotent): create `default`, upsert each user, password = requireDemoPassword()
|
|
192
|
+
// endpoint: one chip per role, emails demoEmail(product, role) → tenant-admin@taskpro.demo.test
|
|
193
|
+
app.get('/api/auth/demo-accounts', demoAccountsExpress({ accounts: (env) => demoUsers(manifest, { product: 'taskpro', env }) }));
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
| Env | Meaning |
|
|
197
|
+
|---|---|
|
|
198
|
+
| `DEMO_PASSWORD` | the demo users' password (≥ 12 chars); `demo:seed` resets every demo user to it, the chips show it. Unset → no chips |
|
|
199
|
+
|
|
182
200
|
Company-first logins pick the chip on the company step: call `stashDemoFill(account)` there and
|
|
183
201
|
`takeDemoFill()` when the password step mounts (sessionStorage, read once). Outside AuthShell, render
|
|
184
202
|
`<DemoAccounts load onSelect />` behind `useTapReveal()` yourself.
|
package/demo-server.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export interface DemoAccountsOptions {
|
|
|
6
6
|
/** The product's own gate (e.g. not a production tenant). DEMO_ACCOUNTS=1 is required as well. */
|
|
7
7
|
enabled?: boolean | undefined;
|
|
8
8
|
/** Built-in list, or a function of env (read seed passwords from the same vars the seed script uses). */
|
|
9
|
-
accounts?: DemoAccount[] | ((env: Env) => DemoAccount[]) | undefined;
|
|
9
|
+
accounts?: DemoAccount[] | ((env: Env) => DemoAccount[] | Promise<DemoAccount[]>) | undefined;
|
|
10
10
|
env?: Env | undefined;
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -16,9 +16,26 @@ export type DemoAccountsResult =
|
|
|
16
16
|
|
|
17
17
|
export declare const DEMO_ACCOUNTS_HEADERS: Record<string, string>;
|
|
18
18
|
export declare function demoAccountsEnabled(opts?: Pick<DemoAccountsOptions, 'enabled' | 'env'>): boolean;
|
|
19
|
-
/** 404 unless enabled and DEMO_ACCOUNTS=1; DEMO_ACCOUNTS_JSON replaces `accounts`. */
|
|
20
|
-
export declare function resolveDemoAccounts(opts?: DemoAccountsOptions): DemoAccountsResult
|
|
19
|
+
/** 404 unless enabled and DEMO_ACCOUNTS=1; DEMO_ACCOUNTS_JSON replaces `accounts`. A Promise when `accounts` is async. */
|
|
20
|
+
export declare function resolveDemoAccounts(opts?: DemoAccountsOptions): DemoAccountsResult | Promise<DemoAccountsResult>;
|
|
21
21
|
/** Express handler for `GET /api/auth/demo-accounts`. */
|
|
22
22
|
export declare function demoAccountsExpress(opts?: DemoAccountsOptions): (req: any, res: any) => void;
|
|
23
23
|
/** Fetch-API Response for a Next.js route handler. */
|
|
24
|
-
export declare function demoAccountsResponse(opts?: DemoAccountsOptions): Response
|
|
24
|
+
export declare function demoAccountsResponse(opts?: DemoAccountsOptions): Response | Promise<Response>;
|
|
25
|
+
|
|
26
|
+
/** One row per role in a product's demo manifest. */
|
|
27
|
+
export interface DemoRole {
|
|
28
|
+
role: string;
|
|
29
|
+
label: string;
|
|
30
|
+
/** Defaults to demoEmail(product, role). */
|
|
31
|
+
email?: string | undefined;
|
|
32
|
+
}
|
|
33
|
+
/** Slug of the shared demo company every pro app seeds: 'default'. */
|
|
34
|
+
export declare const DEMO_COMPANY: 'default';
|
|
35
|
+
export declare const DEMO_PASSWORD_MIN: number;
|
|
36
|
+
/** `${role}@${product}.demo.test` (lower-case, dashes). */
|
|
37
|
+
export declare function demoEmail(product: string, role: string): string;
|
|
38
|
+
/** Manifest → chips in the `default` company with env.DEMO_PASSWORD; [] when DEMO_PASSWORD is unset. */
|
|
39
|
+
export declare function demoUsers(manifest: DemoRole[], opts: { product: string; env?: Env | undefined; company?: string | undefined }): DemoAccount[];
|
|
40
|
+
/** Throws unless DEMO_PASSWORD is set and at least DEMO_PASSWORD_MIN characters; returns it. */
|
|
41
|
+
export declare function requireDemoPassword(env?: Env): string;
|
package/dist/demo-server.cjs
CHANGED
|
@@ -20,9 +20,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
var demo_server_exports = {};
|
|
21
21
|
__export(demo_server_exports, {
|
|
22
22
|
DEMO_ACCOUNTS_HEADERS: () => DEMO_ACCOUNTS_HEADERS,
|
|
23
|
+
DEMO_COMPANY: () => DEMO_COMPANY,
|
|
24
|
+
DEMO_PASSWORD_MIN: () => DEMO_PASSWORD_MIN,
|
|
23
25
|
demoAccountsEnabled: () => demoAccountsEnabled,
|
|
24
26
|
demoAccountsExpress: () => demoAccountsExpress,
|
|
25
27
|
demoAccountsResponse: () => demoAccountsResponse,
|
|
28
|
+
demoEmail: () => demoEmail,
|
|
29
|
+
demoUsers: () => demoUsers,
|
|
30
|
+
requireDemoPassword: () => requireDemoPassword,
|
|
26
31
|
resolveDemoAccounts: () => resolveDemoAccounts
|
|
27
32
|
});
|
|
28
33
|
module.exports = __toCommonJS(demo_server_exports);
|
|
@@ -47,29 +52,70 @@ function clean(accounts) {
|
|
|
47
52
|
function demoAccountsEnabled({ enabled = true, env = process.env } = {}) {
|
|
48
53
|
return Boolean(enabled) && env.DEMO_ACCOUNTS === "1";
|
|
49
54
|
}
|
|
50
|
-
function
|
|
51
|
-
if (!
|
|
52
|
-
const list = clean(
|
|
55
|
+
function result(accounts) {
|
|
56
|
+
if (!accounts) return { status: 404, body: { error: "Not found" } };
|
|
57
|
+
const list = clean(accounts);
|
|
53
58
|
if (!list.length) return { status: 404, body: { error: "Not found" } };
|
|
54
59
|
return { status: 200, body: { accounts: list } };
|
|
55
60
|
}
|
|
61
|
+
function resolveDemoAccounts({ enabled = true, accounts = [], env = process.env } = {}) {
|
|
62
|
+
const on = demoAccountsEnabled({ enabled, env });
|
|
63
|
+
if (!on) return result(null);
|
|
64
|
+
const override = fromEnvJson(env.DEMO_ACCOUNTS_JSON);
|
|
65
|
+
if (override) return result(override);
|
|
66
|
+
const list = typeof accounts === "function" ? accounts(env) : accounts;
|
|
67
|
+
return list && typeof list.then === "function" ? list.then((l) => result(l || []), () => result(null)) : result(list || []);
|
|
68
|
+
}
|
|
56
69
|
var DEMO_ACCOUNTS_HEADERS = { "Cache-Control": "no-store", "X-Robots-Tag": "noindex" };
|
|
57
70
|
function demoAccountsExpress(opts = {}) {
|
|
58
|
-
|
|
59
|
-
const { status, body } = resolveDemoAccounts(opts);
|
|
71
|
+
const send = (res, { status, body }) => {
|
|
60
72
|
res.set(DEMO_ACCOUNTS_HEADERS);
|
|
61
73
|
res.status(status).json(body);
|
|
62
74
|
};
|
|
75
|
+
return (req, res) => {
|
|
76
|
+
const r = resolveDemoAccounts(opts);
|
|
77
|
+
if (typeof r.then === "function") return r.then((x) => send(res, x));
|
|
78
|
+
send(res, r);
|
|
79
|
+
};
|
|
63
80
|
}
|
|
64
81
|
function demoAccountsResponse(opts = {}) {
|
|
65
|
-
const { status, body }
|
|
66
|
-
|
|
82
|
+
const toResponse = ({ status, body }) => new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json", ...DEMO_ACCOUNTS_HEADERS } });
|
|
83
|
+
const r = resolveDemoAccounts(opts);
|
|
84
|
+
return typeof r.then === "function" ? r.then(toResponse) : toResponse(r);
|
|
85
|
+
}
|
|
86
|
+
var DEMO_COMPANY = "default";
|
|
87
|
+
var DEMO_PASSWORD_MIN = 12;
|
|
88
|
+
function demoEmail(product, role) {
|
|
89
|
+
const slug = (s) => String(s).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
90
|
+
return `${slug(role)}@${slug(product)}.demo.test`;
|
|
91
|
+
}
|
|
92
|
+
function demoUsers(manifest, { product, env = process.env, company = DEMO_COMPANY } = {}) {
|
|
93
|
+
const password = env.DEMO_PASSWORD;
|
|
94
|
+
if (!password) return [];
|
|
95
|
+
return (manifest || []).map((m) => ({
|
|
96
|
+
label: m.label,
|
|
97
|
+
identifier: m.email || demoEmail(product, m.role),
|
|
98
|
+
password,
|
|
99
|
+
company,
|
|
100
|
+
role: m.role
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
function requireDemoPassword(env = process.env) {
|
|
104
|
+
const p = env.DEMO_PASSWORD;
|
|
105
|
+
if (!p) throw new Error("DEMO_PASSWORD is not set: demo:seed gives every demo user this password (the login chips show it).");
|
|
106
|
+
if (p.length < DEMO_PASSWORD_MIN) throw new Error(`DEMO_PASSWORD must be at least ${DEMO_PASSWORD_MIN} characters.`);
|
|
107
|
+
return p;
|
|
67
108
|
}
|
|
68
109
|
// Annotate the CommonJS export names for ESM import in node:
|
|
69
110
|
0 && (module.exports = {
|
|
70
111
|
DEMO_ACCOUNTS_HEADERS,
|
|
112
|
+
DEMO_COMPANY,
|
|
113
|
+
DEMO_PASSWORD_MIN,
|
|
71
114
|
demoAccountsEnabled,
|
|
72
115
|
demoAccountsExpress,
|
|
73
116
|
demoAccountsResponse,
|
|
117
|
+
demoEmail,
|
|
118
|
+
demoUsers,
|
|
119
|
+
requireDemoPassword,
|
|
74
120
|
resolveDemoAccounts
|
|
75
121
|
});
|
package/dist/demo-server.js
CHANGED
|
@@ -20,28 +20,69 @@ function clean(accounts) {
|
|
|
20
20
|
function demoAccountsEnabled({ enabled = true, env = process.env } = {}) {
|
|
21
21
|
return Boolean(enabled) && env.DEMO_ACCOUNTS === "1";
|
|
22
22
|
}
|
|
23
|
-
function
|
|
24
|
-
if (!
|
|
25
|
-
const list = clean(
|
|
23
|
+
function result(accounts) {
|
|
24
|
+
if (!accounts) return { status: 404, body: { error: "Not found" } };
|
|
25
|
+
const list = clean(accounts);
|
|
26
26
|
if (!list.length) return { status: 404, body: { error: "Not found" } };
|
|
27
27
|
return { status: 200, body: { accounts: list } };
|
|
28
28
|
}
|
|
29
|
+
function resolveDemoAccounts({ enabled = true, accounts = [], env = process.env } = {}) {
|
|
30
|
+
const on = demoAccountsEnabled({ enabled, env });
|
|
31
|
+
if (!on) return result(null);
|
|
32
|
+
const override = fromEnvJson(env.DEMO_ACCOUNTS_JSON);
|
|
33
|
+
if (override) return result(override);
|
|
34
|
+
const list = typeof accounts === "function" ? accounts(env) : accounts;
|
|
35
|
+
return list && typeof list.then === "function" ? list.then((l) => result(l || []), () => result(null)) : result(list || []);
|
|
36
|
+
}
|
|
29
37
|
var DEMO_ACCOUNTS_HEADERS = { "Cache-Control": "no-store", "X-Robots-Tag": "noindex" };
|
|
30
38
|
function demoAccountsExpress(opts = {}) {
|
|
31
|
-
|
|
32
|
-
const { status, body } = resolveDemoAccounts(opts);
|
|
39
|
+
const send = (res, { status, body }) => {
|
|
33
40
|
res.set(DEMO_ACCOUNTS_HEADERS);
|
|
34
41
|
res.status(status).json(body);
|
|
35
42
|
};
|
|
43
|
+
return (req, res) => {
|
|
44
|
+
const r = resolveDemoAccounts(opts);
|
|
45
|
+
if (typeof r.then === "function") return r.then((x) => send(res, x));
|
|
46
|
+
send(res, r);
|
|
47
|
+
};
|
|
36
48
|
}
|
|
37
49
|
function demoAccountsResponse(opts = {}) {
|
|
38
|
-
const { status, body }
|
|
39
|
-
|
|
50
|
+
const toResponse = ({ status, body }) => new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json", ...DEMO_ACCOUNTS_HEADERS } });
|
|
51
|
+
const r = resolveDemoAccounts(opts);
|
|
52
|
+
return typeof r.then === "function" ? r.then(toResponse) : toResponse(r);
|
|
53
|
+
}
|
|
54
|
+
var DEMO_COMPANY = "default";
|
|
55
|
+
var DEMO_PASSWORD_MIN = 12;
|
|
56
|
+
function demoEmail(product, role) {
|
|
57
|
+
const slug = (s) => String(s).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
58
|
+
return `${slug(role)}@${slug(product)}.demo.test`;
|
|
59
|
+
}
|
|
60
|
+
function demoUsers(manifest, { product, env = process.env, company = DEMO_COMPANY } = {}) {
|
|
61
|
+
const password = env.DEMO_PASSWORD;
|
|
62
|
+
if (!password) return [];
|
|
63
|
+
return (manifest || []).map((m) => ({
|
|
64
|
+
label: m.label,
|
|
65
|
+
identifier: m.email || demoEmail(product, m.role),
|
|
66
|
+
password,
|
|
67
|
+
company,
|
|
68
|
+
role: m.role
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
function requireDemoPassword(env = process.env) {
|
|
72
|
+
const p = env.DEMO_PASSWORD;
|
|
73
|
+
if (!p) throw new Error("DEMO_PASSWORD is not set: demo:seed gives every demo user this password (the login chips show it).");
|
|
74
|
+
if (p.length < DEMO_PASSWORD_MIN) throw new Error(`DEMO_PASSWORD must be at least ${DEMO_PASSWORD_MIN} characters.`);
|
|
75
|
+
return p;
|
|
40
76
|
}
|
|
41
77
|
export {
|
|
42
78
|
DEMO_ACCOUNTS_HEADERS,
|
|
79
|
+
DEMO_COMPANY,
|
|
80
|
+
DEMO_PASSWORD_MIN,
|
|
43
81
|
demoAccountsEnabled,
|
|
44
82
|
demoAccountsExpress,
|
|
45
83
|
demoAccountsResponse,
|
|
84
|
+
demoEmail,
|
|
85
|
+
demoUsers,
|
|
86
|
+
requireDemoPassword,
|
|
46
87
|
resolveDemoAccounts
|
|
47
88
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dreamtree-org/landing-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "The shared marketing site and auth frame (AuthShell + brands) for every Dreamtree product: content-driven landing pages, login/signup shell, Express routes, prerender and SEO files.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|