@stacklist-app/brandkit 1.2.3 → 1.2.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 +16 -0
- package/config.schema.json +1 -0
- package/dist/engine.js +11 -2
- package/lib/template.js +60 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,10 +73,26 @@ export default {
|
|
|
73
73
|
|
|
74
74
|
**Next.js / plain HTML**: run `brandkit build brand` and serve the resulting directory as a static route.
|
|
75
75
|
|
|
76
|
+
### Serving from a base path
|
|
77
|
+
|
|
78
|
+
brandkit emits page-relative paths, which resolve only when the guide is served from the root or a **trailing-slash** directory URL (`/brand/`). If you serve it at `/brand` with **no** trailing slash — e.g. a Next.js rewrite that maps `/brand` to the built `/brand/index.html` — every relative path resolves against `/` instead, so `styles.css`, `engine.js`, and `config.json` all 404 and the guide renders an empty shell.
|
|
79
|
+
|
|
80
|
+
Set `basePath` in `config.json` to fix this:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"basePath": "/brand",
|
|
85
|
+
"brand": { "name": "Acme" }
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
On `brandkit build`, the generated `index.html` then points its stylesheet, engine, and `brand.json` link at `${basePath}/…`, and the engine fetches `${basePath}/config.json` — so the guide loads fully whether or not the serving URL has a trailing slash. The value is normalized (leading slash added, trailing slash stripped). Omitting `basePath` keeps the original page-relative behavior, so existing setups are unaffected.
|
|
90
|
+
|
|
76
91
|
## Config
|
|
77
92
|
|
|
78
93
|
`config.json` is the single source of truth. Top-level keys:
|
|
79
94
|
|
|
95
|
+
- `basePath` — absolute path the guide is served from (e.g. `/brand`). Omit (or leave empty) when serving at the root or from a trailing-slash directory URL — output stays page-relative and unchanged. See [Serving from a base path](#serving-from-a-base-path).
|
|
80
96
|
- `brand` — name, tagline, description, version, date; optional `guideLabel` (renames the "Web Style Guide" header/footer label), `headerLogo` and `sidebarLogo` (logo image paths that replace the text wordmark in the header and left menu)
|
|
81
97
|
- `fonts` — display + body with Google Fonts import; each font takes an optional `fallback` web stand-in for brands whose official typeface isn't web-available (the rendered font stack becomes `'family', 'fallback', sans-serif`, while labels keep the clean family name)
|
|
82
98
|
- `theme` — CSS variable map (colors, gradients, font vars)
|
package/config.schema.json
CHANGED
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
39
|
"properties": {
|
|
40
|
+
"basePath": { "type": "string", "description": "Absolute base path the guide is served from (e.g. \"/brand\"). Empty = served at root / relative. Set this when serving from a non-trailing-slash URL (e.g. a framework rewrite)." },
|
|
40
41
|
"brand": {
|
|
41
42
|
"type": "object",
|
|
42
43
|
"required": ["name"],
|
package/dist/engine.js
CHANGED
|
@@ -6,7 +6,13 @@
|
|
|
6
6
|
/* ================================================================
|
|
7
7
|
0. Load config
|
|
8
8
|
================================================================ */
|
|
9
|
-
|
|
9
|
+
// Base path the guide is served from. The build injects
|
|
10
|
+
// window.__BRANDKIT_BASE__ when config.basePath is set (e.g. "/brand"), so a
|
|
11
|
+
// guide served from a non-trailing-slash URL still resolves its assets.
|
|
12
|
+
// Default '.' keeps the original page-relative behavior (dev + root serving).
|
|
13
|
+
var BASE = (typeof window !== 'undefined' && window.__BRANDKIT_BASE__)
|
|
14
|
+
? (String(window.__BRANDKIT_BASE__).replace(/\/+$/, '') || '.') : '.';
|
|
15
|
+
var res = await fetch(BASE + '/config.json');
|
|
10
16
|
var config = await res.json();
|
|
11
17
|
init(config);
|
|
12
18
|
|
|
@@ -139,7 +145,10 @@
|
|
|
139
145
|
// Clear any userinfo so basic-auth creds in the page URL don't leak into
|
|
140
146
|
// the copied prompt (a relative path already drops the page query/hash).
|
|
141
147
|
function exportUrl(file) {
|
|
142
|
-
|
|
148
|
+
// When served from a base path, resolve against it so the prompt
|
|
149
|
+
// carries the real export URLs (page URL may have no trailing slash).
|
|
150
|
+
var ref = (BASE && BASE !== '.') ? BASE + '/' + file : file;
|
|
151
|
+
var u = new URL(ref, location.href);
|
|
143
152
|
u.username = ''; u.password = '';
|
|
144
153
|
return u.href;
|
|
145
154
|
}
|
package/lib/template.js
CHANGED
|
@@ -42,6 +42,32 @@ function generateRootCSS(config) {
|
|
|
42
42
|
return lines.join('\n');
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Escape a string for safe interpolation inside a double-quoted HTML attribute.
|
|
47
|
+
* A legitimate URL path never contains these chars, so real basePaths pass
|
|
48
|
+
* through unchanged; a hostile value can't break out of the attribute.
|
|
49
|
+
*/
|
|
50
|
+
function escapeAttr(s) {
|
|
51
|
+
return String(s).replace(/&/g, '&').replace(/"/g, '"')
|
|
52
|
+
.replace(/</g, '<').replace(/>/g, '>');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Normalize config.basePath into an absolute, no-trailing-slash prefix.
|
|
57
|
+
* "" / undefined / "/" → "" (served at root, page-relative — unchanged).
|
|
58
|
+
* "brand" or "/brand/" → "/brand".
|
|
59
|
+
*/
|
|
60
|
+
function normalizeBasePath(bp) {
|
|
61
|
+
if (!bp || typeof bp !== 'string') return '';
|
|
62
|
+
var s = bp.trim();
|
|
63
|
+
if (!s) return '';
|
|
64
|
+
// Exactly one leading slash: collapse "//brand" (a protocol-relative URL —
|
|
65
|
+
// browsers read it as a cross-origin host) and "brand" alike to "/brand".
|
|
66
|
+
s = '/' + s.replace(/^\/+/, '');
|
|
67
|
+
s = s.replace(/\/+$/, '');
|
|
68
|
+
return s;
|
|
69
|
+
}
|
|
70
|
+
|
|
45
71
|
/**
|
|
46
72
|
* Generate a Google Fonts <link> tag from config.fonts.
|
|
47
73
|
*/
|
|
@@ -64,6 +90,11 @@ function generateFontsLink(config) {
|
|
|
64
90
|
* Writes index.html and styles.css into the target directory.
|
|
65
91
|
*/
|
|
66
92
|
function build(distDir, targetDir, config) {
|
|
93
|
+
// Absolute base path the guide is served from (e.g. "/brand"). Empty = root /
|
|
94
|
+
// page-relative, in which case nothing below is rewritten (byte-identical to
|
|
95
|
+
// the pre-basePath output). The "/" + file form (no trailing slash on basePath)
|
|
96
|
+
// is what lets a framework rewrite like /brand → /brand/index.html resolve.
|
|
97
|
+
var basePath = normalizeBasePath(config.basePath);
|
|
67
98
|
// Build styles.css: append the config :root AFTER the stylesheet's default
|
|
68
99
|
// tokens so brand overrides win the cascade (styles.css ships a baseline
|
|
69
100
|
// :root; an earlier block would be overridden by it).
|
|
@@ -81,10 +112,34 @@ function build(distDir, targetDir, config) {
|
|
|
81
112
|
var fontsLink = generateFontsLink(config);
|
|
82
113
|
var brandName = (config.brand && config.brand.displayName) || 'Brand';
|
|
83
114
|
|
|
84
|
-
// Inject fonts link + a discovery <link> for the structured brand data
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
115
|
+
// Inject fonts link + a discovery <link> for the structured brand data.
|
|
116
|
+
// When a basePath is set, prefix the brandkit-generated refs (stylesheet,
|
|
117
|
+
// engine, brand.json) so they resolve from a non-trailing-slash URL; also
|
|
118
|
+
// expose the base to the runtime engine via window.__BRANDKIT_BASE__.
|
|
119
|
+
// The Google Fonts link and author-supplied logo paths are left untouched.
|
|
120
|
+
// basePath is author-controlled config, so it's hardened before it lands in
|
|
121
|
+
// markup, each context with its own escaping:
|
|
122
|
+
// - inline <script>: JSON.stringify + "<" → <, so a "</script>" in the
|
|
123
|
+
// value can't break out of the script element (JS-string context).
|
|
124
|
+
// - href/src attributes: HTML-attribute-escaped (basePathAttr), so a stray
|
|
125
|
+
// quote can't break out of the attribute and inject a handler.
|
|
126
|
+
// - all .replace() calls below use function replacers, so a "$" in the path
|
|
127
|
+
// isn't interpreted as a $-replacement pattern.
|
|
128
|
+
var basePathLiteral = JSON.stringify(basePath).replace(/</g, '\\u003C');
|
|
129
|
+
var basePathAttr = escapeAttr(basePath);
|
|
130
|
+
var brandJsonHref = basePath ? basePathAttr + '/brand.json' : 'brand.json';
|
|
131
|
+
var headInject = (basePath ? '<script>window.__BRANDKIT_BASE__ = ' + basePathLiteral + ';</script>\n' : '') +
|
|
132
|
+
(fontsLink ? fontsLink + '\n' : '') +
|
|
133
|
+
'<link rel="alternate" type="application/json" href="' + brandJsonHref + '" title="Brand data">\n';
|
|
134
|
+
htmlTemplate = htmlTemplate.replace('</head>', function () { return headInject + '</head>'; });
|
|
135
|
+
|
|
136
|
+
if (basePath) {
|
|
137
|
+
// Tolerant of quote style / whitespace around "=" so a future reformat of
|
|
138
|
+
// the template doesn't silently no-op and ship broken (root-relative) refs.
|
|
139
|
+
htmlTemplate = htmlTemplate
|
|
140
|
+
.replace(/href\s*=\s*["']styles\.css["']/, function () { return 'href="' + basePathAttr + '/styles.css"'; })
|
|
141
|
+
.replace(/src\s*=\s*["']engine\.js["']/, function () { return 'src="' + basePathAttr + '/engine.js"'; });
|
|
142
|
+
}
|
|
88
143
|
|
|
89
144
|
// Set title
|
|
90
145
|
htmlTemplate = htmlTemplate.replace(
|
|
@@ -116,5 +171,6 @@ function build(distDir, targetDir, config) {
|
|
|
116
171
|
module.exports = {
|
|
117
172
|
generateRootCSS: generateRootCSS,
|
|
118
173
|
generateFontsLink: generateFontsLink,
|
|
174
|
+
normalizeBasePath: normalizeBasePath,
|
|
119
175
|
build: build
|
|
120
176
|
};
|