@ofidj/generator-fidj 3.7.4 → 3.8.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 CHANGED
@@ -44,6 +44,7 @@ one.
44
44
  > `test/parity.test.cjs` fails if either door ever gains an input the other
45
45
  > lacks. It is not an entry point to hand to anyone building an app.
46
46
 
47
+ - `--signin button|inline|both`: how the app asks (default: `button`). `button` hands every sign-in to Fidj, which is the only shape where this app never sees a password. `inline` keeps the app's own email-and-password form and no Fidj door, for an owner who has decided their page is to be trusted with the credential. `both` leads with the Fidj door and folds the app's form under it — what mleweb passes. The older `--credentials true|false` still says what it always said (`true` is `both`); `--signin` wins when both are given.
47
48
  - `--anonymous true|false`: show or hide anonymous entry in content apps (default: `true`). Set `--anonymous false` for a sign-in-only entry flow, as mleweb does.
48
49
  - `--highlight "<heading>|<body>"`: add a numbered cell to the sign-in panel, repeatable up to six. These are the app's own selling points, so an app that passes none simply shows its identity — mleweb passes none, Fidj passes four.
49
50
  - `--badge <text>`: add a trust badge under the sign-in form, repeatable up to four, 40 characters each. None are supplied by default — "EU-hosted" or "GDPR art. 17 · 20" are claims about a particular app, not about every app the generator makes.
@@ -101,6 +102,23 @@ picks up whatever is published at the time.
101
102
 
102
103
  The generated content app opens on `/#/signin`. Sign in, or choose **Enter anonymously** when enabled, to open `/#/content`, containing the supplied HTML. Signed-in users can open **My privacy** separately. Sign-out and departure return to the sign-in screen. Anonymous content is public; this navigation flow is not a security boundary for static assets.
103
104
 
105
+ **The entry leads with the Fidj door, and that door opens a window.** The button
106
+ is the one control on the screen wearing `--fidj-accent`, because it is the one
107
+ that belongs to Fidj rather than to the app — and it is the path where the app
108
+ never sees a password. An app generated with `--signin both` keeps its own
109
+ email-and-password form under an *Inline form* disclosure, with its service
110
+ agreement: opening it folds the Fidj door away, because the two are alternatives
111
+ rather than a list. That agreement gates the app's own door and nothing else, so
112
+ an agreement that fails to load never shuts the Fidj door.
113
+
114
+ Pressing it opens Fidj in a browser window of its own rather than navigating
115
+ away. Fidj's screens are served from another origin and refuse to be framed, so
116
+ a dialog drawn inside the page cannot hold them; a window can, and the page the
117
+ person was reading stays exactly where it was. The window says which app it will
118
+ return them to, hands the answer back when they are done, and closes itself. A
119
+ browser that will not open one falls back to the full-page redirect. Fidj's own
120
+ console takes the same door for the same reason — one journey, three apps.
121
+
104
122
  ## Compose an existing app as a module
105
123
 
106
124
  Build your application for the `/module/` base URL, then pass its public output to the same generator:
@@ -114,7 +132,7 @@ The result is one static website: the generated SDK sign-in entry and the module
114
132
 
115
133
  Hash routes other than the generated entry/content/privacy views are forwarded to the module, preserving existing public cards and console links. The module remains responsible for guarding private routes.
116
134
 
117
- The module entry receives a `meta[name="fidj-signin"]` URL, relative to its base URL. A module can send its sign-in, logout or expired-session flow there. Fidj's console implements this handoff and preserves departure status. The generated preview serves module assets from an explicit build manifest, including directory index URLs.
135
+ Sign-in, sign-out and expired sessions belong to the shell, which owns those addresses: a module reaches them by leaving the document — the site root — rather than routing inside it, because the shell starts the module in place and only hears `hashchange`. A departure status travels as `?departure=completed|pending` on that address. Fidj's console implements this handover. The shell also names itself in that document — `meta[name="fidj-shell"]`, holding the base its addresses start from — so a module can offer what only a shell has, such as the account screen at `#/account`; standalone there is no such meta and nothing to offer. The generated preview serves module assets from an explicit build manifest, including directory index URLs, and the generated entry names the module's scripts and stylesheet in its head so the browser starts them without waiting for the shell's session.
118
136
 
119
137
  `fidj-app` now exercises this path with `npm run create:local`: its owner/profile/privacy features remain an explicit Angular console module, while the generator owns the shared entry and final assembly. The local launcher serves the generated Fidj output on port 4200. The source console is maintained outside disposable `.gen` and must be rebuilt before regenerating.
120
138
 
@@ -19,6 +19,7 @@ try {
19
19
  favicon: { type: "string" },
20
20
  anonymous: { type: "string" },
21
21
  credentials: { type: "string" },
22
+ signin: { type: "string" },
22
23
  domain: { type: "string" },
23
24
  module: { type: "string" },
24
25
  "module-entry": { type: "string" },
@@ -29,7 +30,7 @@ try {
29
30
  });
30
31
  if (values.help || positionals.length !== 1) {
31
32
  console.log(
32
- "Usage: create-fidj <directory> --app-id <fidjId> [--api-endpoint <url>] [--title <text> --welcome <text> --description <text> --content <html> --domain <hostname>] [--highlight '<heading>|<body>' ...] [--badge <text> ...] [--logo <image>] [--favicon <image>] [--module <built-directory> --module-entry <index.html#/route>] [--oidc-issuer https://api.example/oidc] [--anonymous true|false] [--credentials true|false] [--local] [--replace]",
33
+ "Usage: create-fidj <directory> --app-id <fidjId> [--api-endpoint <url>] [--title <text> --welcome <text> --description <text> --content <html> --domain <hostname>] [--highlight '<heading>|<body>' ...] [--badge <text> ...] [--logo <image>] [--favicon <image>] [--module <built-directory> --module-entry <index.html#/route>] [--oidc-issuer https://api.example/oidc] [--anonymous true|false] [--signin button|inline|both] [--credentials true|false] [--local] [--replace]",
33
34
  );
34
35
  process.exitCode = values.help ? 0 : 1;
35
36
  } else {
@@ -48,6 +49,7 @@ try {
48
49
  favicon: values.favicon,
49
50
  anonymous: values.anonymous,
50
51
  credentials: values.credentials,
52
+ signin: values.signin,
51
53
  domain: values.domain,
52
54
  module: values.module,
53
55
  moduleEntry: values["module-entry"],
@@ -15,6 +15,7 @@ const TEXT_OPTIONS = [
15
15
  "content",
16
16
  "anonymous",
17
17
  "credentials",
18
+ "signin",
18
19
  "domain",
19
20
  "module",
20
21
  "module-entry",
@@ -89,6 +90,7 @@ module.exports = class extends Generator {
89
90
  content: this.options.content,
90
91
  anonymous: this.options.anonymous,
91
92
  credentials: this.options.credentials,
93
+ signin: this.options.signin,
92
94
  highlights: repeated("highlight", this.options.highlight),
93
95
  badges: repeated("badge", this.options.badge),
94
96
  logo: this.options.logo,
@@ -51,7 +51,7 @@ Set `--anonymous false` in the generator command to remove anonymous entry and r
51
51
 
52
52
  ## Application modules
53
53
 
54
- When generated with `--module`, successful sign-in opens the copied module configured by `moduleEntry`. It runs under `/module/` on the same origin and must validate sessions/permissions independently. Its entry contains a `fidj-signin` meta URL for returning to this shared sign-in page. Module code is public static output; user data belongs behind authorized APIs. Rebuild the maintained module source and regenerate to update it; never patch its copied files here.
54
+ When generated with `--module`, successful sign-in starts the copied module at the address configured by `moduleEntry`, inside this document rather than as a second page. Its assets are served from `/module/` on the same origin and it must validate sessions/permissions independently. To return to this shared sign-in page it leaves the document for the site root, optionally with `?departure=completed|pending`. Module code is public static output; user data belongs behind authorized APIs. Rebuild the maintained module source and regenerate to update it; never patch its copied files here.
55
55
 
56
56
  ## Connect app data rights
57
57
 
@@ -15,7 +15,7 @@
15
15
  "privacy:rehearse": "npm run build && node --test test/server.test.mjs"
16
16
  },
17
17
  "dependencies": {
18
- "@ofidj/node": "^3.7.3"
18
+ "@ofidj/node": "^3.8.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^22.0.0",
@@ -6,9 +6,32 @@ const escape = (value) =>
6
6
  char
7
7
  ],
8
8
  );
9
+ // The mounted app's scripts are injected by the shell once its own bundle has
10
+ // arrived and run, so the browser's preload scanner never sees them in the
11
+ // document and the larger download waits on the smaller one. Naming them here
12
+ // starts both at once. Preloaded, never applied: the module's stylesheet over
13
+ // the shell's own sign-in screen would be one design on another's markup. An app
14
+ // that carries a module is an app people open to reach that module, so this is
15
+ // worth its bytes there — and an app without one names nothing extra.
16
+ function mountedHead(config) {
17
+ const mount = config.moduleMount;
18
+ if (!mount) return "";
19
+ return [
20
+ // A hosted console cannot tell from its own code that it is hosted, and some
21
+ // of what it shows depends on it: verification and recovery are the shell's
22
+ // own screens. This says a shell is here, and where its addresses start.
23
+ `<meta name="fidj-shell" content="./">`,
24
+ ...mount.scripts.map(
25
+ (script) => `<link rel="modulepreload" href="${escape(script.src)}">`,
26
+ ),
27
+ ...mount.styles.map(
28
+ (href) => `<link rel="preload" as="style" href="${escape(href)}">`,
29
+ ),
30
+ ].join("");
31
+ }
9
32
  export function renderContent(config) {
10
33
  // HTML is supplied by the developer at generation time, never by an app visitor.
11
- return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="referrer" content="no-referrer"><title>${escape(config.title)}</title><link rel="icon" href="${escape(config.favicon)}"><link rel="stylesheet" href="./main.css"></head><body>
12
- <header class="topbar"><a class="brand" href="#/content"><img src="${escape(config.logo)}" alt="">${escape(config.title)}</a></header>
34
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="referrer" content="no-referrer"><title>${escape(config.title)}</title><link rel="icon" href="${escape(config.favicon)}"><link rel="stylesheet" href="./main.css">${mountedHead(config)}</head><body>
35
+ <header class="topbar"><a class="brand" href="#/content"><img src="${escape(config.logo)}" alt="">${escape(config.title)}</a><nav class="content-nav" id="app-nav" aria-label="App navigation" hidden></nav></header>
13
36
  <main><template id="public-content"><section class="card public-content"><h1>${escape(config.welcome)}</h1><div>${config.content}</div></section></template><div id="app" aria-live="polite"></div><footer>Built with Fidj · Your choices belong to this app.</footer></main><script type="module" src="./main.js"></script></body></html>`;
14
37
  }
@@ -333,6 +333,12 @@ if (require.main === module) {
333
333
  dashboardUrl: process.env.FIDJ_DASHBOARD_URL || "https://fidj.ovh",
334
334
  title: process.env.APP_TITLE || "My workspace",
335
335
  releaseVersion: process.env.APP_VERSION || "",
336
+ // How this app asks. The generator writes it; anything it does not
337
+ // recognise means the shape that keeps the promise — Fidj asks, and this
338
+ // app never sees a password.
339
+ signin: ["button", "inline", "both"].includes(process.env.FIDJ_SIGNIN || "")
340
+ ? (process.env.FIDJ_SIGNIN as "button" | "inline" | "both")
341
+ : "button",
336
342
  localDemo:
337
343
  process.env.LOCAL_DEMO === "true" &&
338
344
  host === "127.0.0.1" &&