@ofidj/generator-fidj 1.0.1 → 1.0.4
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 +58 -14
- package/bin/create-fidj.cjs +9 -1
- package/generators/app/index.js +57 -15
- package/generators/app/templates/typescript/README.md +12 -2
- package/generators/app/templates/typescript/app.config.json +5 -1
- package/generators/app/templates/typescript/package.json +1 -1
- package/generators/app/templates/typescript/public/fidj-logo.png +0 -0
- package/generators/app/templates/typescript/public/fonts/IBMPlexMono-400-latin-ext.woff2 +0 -0
- package/generators/app/templates/typescript/public/fonts/IBMPlexMono-400-latin.woff2 +0 -0
- package/generators/app/templates/typescript/public/fonts/IBMPlexMono-500-latin-ext.woff2 +0 -0
- package/generators/app/templates/typescript/public/fonts/IBMPlexMono-500-latin.woff2 +0 -0
- package/generators/app/templates/typescript/public/fonts/IBMPlexSans-400_600-latin-ext.woff2 +0 -0
- package/generators/app/templates/typescript/public/fonts/IBMPlexSans-400_600-latin.woff2 +0 -0
- package/generators/app/templates/typescript/public/fonts/InstrumentSerif-400-latin-ext.woff2 +0 -0
- package/generators/app/templates/typescript/public/fonts/InstrumentSerif-400-latin.woff2 +0 -0
- package/generators/app/templates/typescript/scripts/build.mjs +3 -0
- package/generators/app/templates/typescript/scripts/render-content.mjs +2 -2
- package/generators/app/templates/typescript/server/index.ts +10 -1
- package/generators/app/templates/typescript/src/content.ts +128 -37
- package/generators/app/templates/typescript/src/fonts.css +92 -0
- package/generators/app/templates/typescript/src/main.ts +31 -9
- package/generators/app/templates/typescript/src/service-agreement.ts +51 -0
- package/generators/app/templates/typescript/src/style.css +640 -363
- package/generators/app/templates/typescript/src/tokens.css +56 -0
- package/lib/app-module.cjs +6 -1
- package/lib/scaffold.cjs +72 -3
- package/package.json +2 -6
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/* The Fidj design system, v2 — every value the product paints with.
|
|
2
|
+
*
|
|
3
|
+
* This file is shared verbatim with fidj-app (src/theme/fidj-tokens.css), so a
|
|
4
|
+
* style change made here reaches Fidj, mleweb and every generated app at once.
|
|
5
|
+
* Keep it free of anything path-dependent: @font-face lives in fonts.css, and
|
|
6
|
+
* each consumer supplies its own. Nothing else in a stylesheet may introduce a
|
|
7
|
+
* literal colour, family or radius.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
:root {
|
|
11
|
+
/* Ink — the dark half of the split: brand panel, header, primary button. */
|
|
12
|
+
--fidj-ink: #14110f;
|
|
13
|
+
--fidj-ink-raised: #2a2521;
|
|
14
|
+
--fidj-ink-line: #38322c;
|
|
15
|
+
--fidj-ink-line-soft: #4a423b;
|
|
16
|
+
--fidj-on-ink: #f2eee9;
|
|
17
|
+
--fidj-on-ink-muted: #b6ada5;
|
|
18
|
+
--fidj-on-ink-faint: #8c847c;
|
|
19
|
+
|
|
20
|
+
/* Paper — the light half. */
|
|
21
|
+
--fidj-paper: #fbfaf8;
|
|
22
|
+
--fidj-surface: #ffffff;
|
|
23
|
+
--fidj-surface-sunk: #f5f1ec;
|
|
24
|
+
--fidj-surface-tint: #f2eee9;
|
|
25
|
+
--fidj-line: #e4ded7;
|
|
26
|
+
--fidj-line-soft: #ece7e1;
|
|
27
|
+
--fidj-line-strong: #dfd9d2;
|
|
28
|
+
--fidj-line-hover: #c9c1b8;
|
|
29
|
+
--fidj-text: #14110f;
|
|
30
|
+
--fidj-text-muted: #4a4540;
|
|
31
|
+
--fidj-text-faint: #6b655f;
|
|
32
|
+
--fidj-placeholder: #a8a099;
|
|
33
|
+
|
|
34
|
+
/* Accents. The red is the Fidj mark; it identifies the account and privacy
|
|
35
|
+
* provider, so it stays sparing — a dot, a rule, a hover. */
|
|
36
|
+
--fidj-accent: #d6342c;
|
|
37
|
+
--fidj-danger: #b8352c;
|
|
38
|
+
--fidj-danger-strong: #8c2a22;
|
|
39
|
+
--fidj-danger-tint: #fdf7f6;
|
|
40
|
+
--fidj-danger-line: #e8d9d6;
|
|
41
|
+
--fidj-ok: #3f7a4f;
|
|
42
|
+
--fidj-warn: #c07a1e;
|
|
43
|
+
--fidj-warn-tint: #fbf7f1;
|
|
44
|
+
|
|
45
|
+
--fidj-focus-ring: 0 0 0 3px rgb(20 17 15 / 0.07);
|
|
46
|
+
--fidj-overlay-shadow: 0 12px 32px rgb(20 17 15 / 0.18);
|
|
47
|
+
|
|
48
|
+
--fidj-font-sans: "IBM Plex Sans", system-ui, -apple-system, sans-serif;
|
|
49
|
+
--fidj-font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, monospace;
|
|
50
|
+
--fidj-font-display: "Instrument Serif", Georgia, "Times New Roman", serif;
|
|
51
|
+
|
|
52
|
+
/* Squared corners are the loudest signal of this style: 2px everywhere, and
|
|
53
|
+
* a full round only for avatars and status dots. */
|
|
54
|
+
--fidj-radius: 2px;
|
|
55
|
+
--fidj-shell-max: 1180px;
|
|
56
|
+
}
|
package/lib/app-module.cjs
CHANGED
|
@@ -54,6 +54,11 @@ function inspectModule(source, entry = "index.html", destination) {
|
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
56
|
visit(root);
|
|
57
|
-
|
|
57
|
+
// Address a directory rather than its index file: the generated server and
|
|
58
|
+
// any static host resolve it, and no index.html shows up in the address bar.
|
|
59
|
+
const address = file.endsWith("index.html")
|
|
60
|
+
? file.slice(0, -"index.html".length)
|
|
61
|
+
: file;
|
|
62
|
+
return { root, file, entry: "./module/" + address + url.hash };
|
|
58
63
|
}
|
|
59
64
|
module.exports = { inspectModule };
|
package/lib/scaffold.cjs
CHANGED
|
@@ -40,11 +40,55 @@ function scaffold(destination, options) {
|
|
|
40
40
|
}
|
|
41
41
|
const title = options.title || name;
|
|
42
42
|
if (/[\r\n]/.test(title)) throw new Error("Title must be one line.");
|
|
43
|
+
// Sign-in highlights are the app's own selling points, so they are supplied
|
|
44
|
+
// per app rather than baked in: an app that passes none simply shows none.
|
|
45
|
+
const highlights = (options.highlights || []).map((entry) => {
|
|
46
|
+
const [heading, ...rest] = String(entry).split("|");
|
|
47
|
+
const body = rest.join("|").trim();
|
|
48
|
+
if (!heading.trim() || !body)
|
|
49
|
+
throw new Error('Each --highlight must read "<heading>|<body>".');
|
|
50
|
+
if (/[\r\n]/.test(entry)) throw new Error("A highlight must be one line.");
|
|
51
|
+
return { heading: heading.trim(), body };
|
|
52
|
+
});
|
|
53
|
+
if (highlights.length > 6)
|
|
54
|
+
throw new Error("Six highlights is the most the sign-in panel can hold.");
|
|
55
|
+
// Trust badges are claims about a specific app — where it is hosted, what it
|
|
56
|
+
// audits. None are supplied by default, because an app cannot inherit them.
|
|
57
|
+
const badges = (options.badges || []).map((entry) => {
|
|
58
|
+
const text = String(entry).trim();
|
|
59
|
+
if (!text || /[\r\n]/.test(text))
|
|
60
|
+
throw new Error("Each --badge must be one line of text.");
|
|
61
|
+
if (text.length > 40)
|
|
62
|
+
throw new Error("A badge must be at most 40 characters.");
|
|
63
|
+
return text;
|
|
64
|
+
});
|
|
65
|
+
if (badges.length > 4)
|
|
66
|
+
throw new Error("Four badges is the most the sign-in footer can hold.");
|
|
43
67
|
if (
|
|
44
68
|
options.domain &&
|
|
45
69
|
!/^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/i.test(options.domain)
|
|
46
70
|
)
|
|
47
71
|
throw new Error("Domain must be a hostname, without a scheme or path.");
|
|
72
|
+
const brandImage = (value, flag) => {
|
|
73
|
+
if (!value) return null;
|
|
74
|
+
const source = path.resolve(value);
|
|
75
|
+
if (!fs.existsSync(source) || !fs.statSync(source).isFile())
|
|
76
|
+
throw new Error(`${flag} must point at an image file.`);
|
|
77
|
+
const extension = path.extname(source).toLowerCase();
|
|
78
|
+
if (
|
|
79
|
+
![".png", ".svg", ".gif", ".jpg", ".jpeg", ".webp", ".ico"].includes(
|
|
80
|
+
extension,
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
throw new Error(
|
|
84
|
+
`${flag} must be a .png, .svg, .gif, .jpg, .webp or .ico file.`,
|
|
85
|
+
);
|
|
86
|
+
if (fs.statSync(source).size > 512 * 1024)
|
|
87
|
+
throw new Error(`${flag} must be under 512KB; it is served on every visit.`);
|
|
88
|
+
return { source, extension };
|
|
89
|
+
};
|
|
90
|
+
const logo = brandImage(options.logo, "--logo");
|
|
91
|
+
const favicon = brandImage(options.favicon, "--favicon");
|
|
48
92
|
const dir = path.resolve(destination);
|
|
49
93
|
const appModule = options.module
|
|
50
94
|
? inspectModule(options.module, options.moduleEntry, dir)
|
|
@@ -99,19 +143,44 @@ function scaffold(destination, options) {
|
|
|
99
143
|
options.description ||
|
|
100
144
|
"A space to explore, with an account that puts you in control.",
|
|
101
145
|
content: appModule ? "" : options.content,
|
|
146
|
+
highlights,
|
|
147
|
+
badges,
|
|
148
|
+
// An app that supplies no mark of its own borrows Fidj's, so the sign-in
|
|
149
|
+
// and the browser tab are never blank.
|
|
150
|
+
logo: logo ? "./brand/logo" + logo.extension : "./fidj-logo.png",
|
|
151
|
+
favicon: favicon ? "./brand/favicon" + favicon.extension : "./fidj-logo.png",
|
|
102
152
|
moduleEntry: appModule?.entry || "",
|
|
103
153
|
domain: options.domain,
|
|
104
154
|
};
|
|
155
|
+
if (logo || favicon) fs.mkdirSync(path.join(dir, "public/brand"), { recursive: true });
|
|
156
|
+
if (logo)
|
|
157
|
+
fs.copyFileSync(logo.source, path.join(dir, "public/brand/logo" + logo.extension));
|
|
158
|
+
if (favicon)
|
|
159
|
+
fs.copyFileSync(favicon.source, path.join(dir, "public/brand/favicon" + favicon.extension));
|
|
160
|
+
if (favicon) {
|
|
161
|
+
// The workspace starter serves a static index.html rather than rendering
|
|
162
|
+
// one, so its icon is rewritten here.
|
|
163
|
+
const page = path.join(dir, "public/index.html");
|
|
164
|
+
fs.writeFileSync(
|
|
165
|
+
page,
|
|
166
|
+
fs
|
|
167
|
+
.readFileSync(page, "utf8")
|
|
168
|
+
.replace('href="/fidj-logo.png"', `href="/brand/favicon${favicon.extension}"`),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
105
171
|
if (appModule) {
|
|
106
172
|
fs.cpSync(appModule.root, path.join(dir, "public/module"), {
|
|
107
173
|
recursive: true,
|
|
108
174
|
});
|
|
109
175
|
const entryFile = path.join(dir, "public/module", appModule.file);
|
|
110
|
-
|
|
176
|
+
// Point back at the shell's directory, not at its index file, for the same
|
|
177
|
+
// reason the module entry does: no index.html in the address bar.
|
|
178
|
+
const toRoot =
|
|
111
179
|
path.posix.relative(
|
|
112
180
|
path.posix.dirname("module/" + appModule.file),
|
|
113
|
-
"
|
|
114
|
-
)
|
|
181
|
+
".",
|
|
182
|
+
) || ".";
|
|
183
|
+
const relativeSignin = toRoot + "/#/signin";
|
|
115
184
|
const html = fs.readFileSync(entryFile, "utf8");
|
|
116
185
|
if (!html.includes("</head>"))
|
|
117
186
|
throw new Error("Module entry must have an HTML head.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ofidj/generator-fidj",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Generate a TypeScript app with Fidj sign-in, live server-side roles and per-app privacy.",
|
|
5
5
|
"homepage": "https://fidj.ovh",
|
|
6
6
|
"bugs": {
|
|
@@ -12,15 +12,11 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
"bin",
|
|
14
14
|
"lib",
|
|
15
|
-
"generators"
|
|
16
|
-
"!generators/app/templates/app2018",
|
|
17
|
-
"!generators/app/templates/app2021"
|
|
15
|
+
"generators"
|
|
18
16
|
],
|
|
19
17
|
"main": "./lib/scaffold.cjs",
|
|
20
18
|
"keywords": [
|
|
21
19
|
"fidj",
|
|
22
|
-
"angular",
|
|
23
|
-
"ionic",
|
|
24
20
|
"yeoman-generator"
|
|
25
21
|
],
|
|
26
22
|
"dependencies": {
|