@tideworthy/tidysites 0.1.11 → 0.1.13
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 +2 -1
- package/bin/tidysites.mjs +38 -0
- package/lib/fetch.mjs +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,12 +24,13 @@ nothing else; it lives in `.tidysites/key`, which is kept out of git.
|
|
|
24
24
|
| `tidysites push [--prune] [--force] [--dry] [--only pages]` | validate, then write the created and updated entries back (with `--prune`, delete the entries whose files are gone), then pull |
|
|
25
25
|
| `tidysites shot <path or url> [--mobile] [--tablet] [--desktop] [--wide] [--all] [--first-screen] [--live]` | a screenshot of a page of the preview site, the live site, or any address, saved under `.tidysites/shots/`; none of the width flags means desktop and mobile; a port compares all four. A page taller than 2400px comes in 1600px bands (`<name>-band-01.jpg`, …), top to bottom, that an image reader can take in one at a time. `--menu` opens every navigation menu (burgers, `<details>`, hover dropdowns) before the capture; `--open a,b` clicks those selectors and `--hover a,b` shows the submenus under them, for a popup, a tab or one dropdown |
|
|
26
26
|
| `tidysites measure <path or url> [--mobile] [--tablet] [--desktop] [--wide] [--all] [--full]` | the page as the browser lays it out: every heading, paragraph, button, picture and section box with its computed type (size, line height, weight, family, colour) and box (position, size, padding, margin, background), saved as JSON under `.tidysites/measures/` and printed. Measure the old page and the new one at the same width and style to the numbers. The same `--menu`, `--open` and `--hover` as `shot`, so a dropdown's open type is measured |
|
|
27
|
+
| `tidysites speed <path or url> [--mobile\|--desktop] [--live] [--against <url>]` | PageSpeed Insights (Google's Lighthouse) for a page of the preview or live site, or any address: scores, lab and field metrics, the largest-paint element and the opportunities with the files they name, saved under `.tidysites/speed/`. `--against <url>` tests the old page in the same call for a side-by-side. Run it on the live site before calling a build done; what the content cannot fix goes in the report |
|
|
27
28
|
| `tidysites redirect [add <old path> <new path> \| rm <old path>]` | the website's redirects: an old address that must keep working after a port goes here; they ship with the next publish |
|
|
28
29
|
| `tidysites pages` | every address the website renders |
|
|
29
30
|
| `tidysites page <path> [--html]` | the rendered page, section by section, with the key each section carries |
|
|
30
31
|
| `tidysites schema [type or collection]` | the section types and layouts, or one type's or collection's fields |
|
|
31
32
|
| `tidysites gallery [type]` | a screenshot of every section type and layout in this website's theme, or the first screen of one type |
|
|
32
|
-
| `tidysites fetch <url> [--out dir]` | a page of the site being ported: cleaned HTML, its outline (headings, navigation, images, phones, fonts, the stylesheet's own variables and colours) and its stylesheets, inline style blocks included |
|
|
33
|
+
| `tidysites fetch <url> [--out dir]` | a page of the site being ported: cleaned HTML, its outline (headings, navigation, images, phones, fonts, an Adobe Fonts kit and the families it serves, the stylesheet's own variables and colours) and its stylesheets, inline style blocks included |
|
|
33
34
|
| `tidysites rm <collection>/<slug> ...` | delete entry files with their sidecars (`services/*` for every entry of a collection); `push --prune` then removes them from the website |
|
|
34
35
|
| `tidysites publish --yes` | the live site becomes a copy of the preview site. A website that is live on the client's own domain is never published from the command line: the website refuses and says so; the person publishes it (Publish now on the Website tab), or an assistant with them does through the connector after they confirm |
|
|
35
36
|
|
package/bin/tidysites.mjs
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* push [--prune] [--force] [--dry] write the changes back, then pull
|
|
10
10
|
* shot <path|url> [--mobile] [--tablet] [--desktop] [--wide] [--all] [--first-screen] [--menu] [--open sel,..] [--hover sel,..] screenshot to .tidysites/shots (none = desktop and mobile); a tall page comes in 1600px bands; --menu opens every navigation menu first
|
|
11
11
|
* measure <path|url> [--mobile] [--tablet] [--desktop] [--wide] [--all] [--full] [--menu] [--open sel,..] [--hover sel,..] the computed type and boxes of a page: the numbers to style to and diff
|
|
12
|
+
* speed <path|url> [--mobile|--desktop] [--live] [--against <url>] PageSpeed Insights for a page, and the old page beside it
|
|
12
13
|
* redirect [add <old> <new> | rm <old>] the website's redirects (an old address that must keep working goes here)
|
|
13
14
|
* pages every address the website renders
|
|
14
15
|
* page <path> [--html] the rendered page, section by section
|
|
@@ -434,6 +435,39 @@ async function measure(ctx) {
|
|
|
434
435
|
}
|
|
435
436
|
}
|
|
436
437
|
|
|
438
|
+
/** PageSpeed Insights for a page of the website (or any address), and the old page beside it with --against. */
|
|
439
|
+
async function speed(ctx) {
|
|
440
|
+
const target = positional[0] ?? '/';
|
|
441
|
+
const isUrl = /^https?:\/\//.test(target);
|
|
442
|
+
const strategy = flags.mobile && !flags.desktop ? 'mobile' : flags.desktop && !flags.mobile ? 'desktop' : 'both';
|
|
443
|
+
const args = { site_id: ctx.cfg.site, strategy, ...(isUrl ? { url: target } : { path: target, site: flags.live ? 'live' : 'preview' }), ...(flags.against ? { compare_url: String(flags.against) } : {}) };
|
|
444
|
+
say(`Testing${flags.against ? ' both pages' : ''} with PageSpeed Insights (a minute or two)...`);
|
|
445
|
+
const { json } = await tool(ctx.cfg, 'speed_website', args);
|
|
446
|
+
if (!json?.page) fail('No result came back.');
|
|
447
|
+
const outDir = join(ctx.dir, META, 'speed');
|
|
448
|
+
mkdirSync(outDir, { recursive: true });
|
|
449
|
+
const stamp = new Date().toISOString().replace(/[-:.]/g, '').slice(4, 18);
|
|
450
|
+
const base = (isUrl ? new URL(target).hostname + new URL(target).pathname : target).replace(/[^a-z0-9]+/gi, '-').replace(/^-|-$/g, '') || 'home';
|
|
451
|
+
const file = join(outDir, `${base}-${stamp}.json`);
|
|
452
|
+
writeFileSync(file, JSON.stringify(json, null, '\t'));
|
|
453
|
+
const n = (v, unit = '') => (v === null || v === undefined ? '—' : `${v}${unit}`);
|
|
454
|
+
const line = (r) => r.error ? ` ${r.strategy.padEnd(8)} ${r.error}` : ` ${r.strategy.padEnd(8)} perf ${n(r.scores.performance)} a11y ${n(r.scores.accessibility)} best ${n(r.scores.best_practices)} seo ${n(r.scores.seo)} LCP ${n(r.lab.lcp_ms, 'ms')} FCP ${n(r.lab.fcp_ms, 'ms')} TBT ${n(r.lab.tbt_ms, 'ms')} CLS ${n(r.lab.cls)} ${r.lab.total_bytes ? Math.round(r.lab.total_bytes / 1024) + ' KB' : '—'} in ${n(r.lab.requests)} requests${r.field ? ` field: LCP ${n(r.field.lcp_ms, 'ms')} INP ${n(r.field.inp_ms, 'ms')} CLS ${n(r.field.cls)} (${r.field.overall})` : ''}`;
|
|
455
|
+
say(`\n${json.target}`);
|
|
456
|
+
for (const r of json.page) say(line(r));
|
|
457
|
+
if (json.compare) { say(`\n${json.compare_url} (the page it is compared with)`); for (const r of json.compare) say(line(r)); }
|
|
458
|
+
for (const r of json.page) {
|
|
459
|
+
if (r.error) continue;
|
|
460
|
+
say(`\n ${r.strategy}: what to change`);
|
|
461
|
+
if (r.lcp_element) say(` largest paint: ${r.lcp_element.replace(/\s+/g, ' ').slice(0, 160)}`);
|
|
462
|
+
for (const o of r.opportunities.slice(0, 8)) {
|
|
463
|
+
say(` • ${o.title}${o.savings_ms ? ` (${o.savings_ms} ms)` : ''}${o.savings_kb ? ` (${o.savings_kb} KB)` : ''}`);
|
|
464
|
+
for (const it of o.items.slice(0, 4)) say(` ${it}`);
|
|
465
|
+
}
|
|
466
|
+
for (const d of r.diagnostics.slice(0, 8)) say(` · ${d}`);
|
|
467
|
+
}
|
|
468
|
+
say(`\nSaved to ${relative(process.cwd(), file)}. Full reports: ${json.page.filter((r) => !r.error).map((r) => r.report_url).join(' ')}`);
|
|
469
|
+
}
|
|
470
|
+
|
|
437
471
|
/** The website's redirects: list them, add one, remove one. They ship with the next publish. */
|
|
438
472
|
async function redirect(ctx) {
|
|
439
473
|
const sub = positional[0];
|
|
@@ -535,6 +569,8 @@ async function fetchCmd(ctx) {
|
|
|
535
569
|
const css = await r.text();
|
|
536
570
|
cssTexts.push(css);
|
|
537
571
|
writeFileSync(join(outDir, `${base}-${cssTexts.length}.css`), css);
|
|
572
|
+
// The kit's CSS names the families it serves, which the page's HTML does not.
|
|
573
|
+
if (/use\.typekit\.net/i.test(href)) o.adobe_families = [...new Set([...o.adobe_families ?? [], ...[...css.matchAll(/font-family\s*:\s*["']([^"';]+)["']/gi)].map((m) => m[1])])];
|
|
538
574
|
}
|
|
539
575
|
} catch {
|
|
540
576
|
/* a stylesheet that will not download is not the port's problem */
|
|
@@ -642,6 +678,8 @@ async function main() {
|
|
|
642
678
|
}
|
|
643
679
|
case 'measure':
|
|
644
680
|
return measure(config());
|
|
681
|
+
case 'speed':
|
|
682
|
+
return speed(config());
|
|
645
683
|
case 'redirect':
|
|
646
684
|
case 'redirects':
|
|
647
685
|
return redirect(config());
|
package/lib/fetch.mjs
CHANGED
|
@@ -105,6 +105,9 @@ export function outline(html, base) {
|
|
|
105
105
|
out.fonts.push(...fams);
|
|
106
106
|
}
|
|
107
107
|
out.fonts = [...new Set(out.fonts)];
|
|
108
|
+
// Adobe Fonts (Typekit) kits belong to the old site or its builder, so a port cannot load them; the families are named from the kit's CSS by the fetch command.
|
|
109
|
+
out.adobe_kits = [...new Set([...html.matchAll(/use\.typekit\.net\/([a-z0-9]+)\.css/gi)].map((m) => m[1].toLowerCase()))];
|
|
110
|
+
out.adobe_families = [];
|
|
108
111
|
return out;
|
|
109
112
|
}
|
|
110
113
|
|
|
@@ -162,6 +165,10 @@ export function outlineText(o) {
|
|
|
162
165
|
if (o.phones.length) lines.push('', `Phones: ${o.phones.join(', ')}`);
|
|
163
166
|
if (o.emails.length) lines.push(`Emails: ${o.emails.join(', ')}`);
|
|
164
167
|
if (o.fonts.length) lines.push(`Google fonts: ${o.fonts.join(', ')}`);
|
|
168
|
+
if (o.adobe_kits?.length) {
|
|
169
|
+
lines.push(`Adobe Fonts kit: ${o.adobe_kits.join(', ')} (the old site's own kit; this site cannot load it)${o.adobe_families?.length ? ` serving ${o.adobe_families.join(', ')}` : ''}.`);
|
|
170
|
+
lines.push(' Each family from it needs a Google Fonts stand-in on the Business record that matches its shape and weights as closely as possible, and the report must name the original and the stand-in. A font is never left unset.');
|
|
171
|
+
}
|
|
165
172
|
if (o.css) {
|
|
166
173
|
const vars = Object.entries(o.css.variables ?? {});
|
|
167
174
|
if (vars.length) { lines.push('', 'Stylesheet variables (the site\'s own names; trust these over the counts below):'); for (const [k, v] of vars.slice(0, 40)) lines.push(` --${k}: ${v}`); }
|
package/package.json
CHANGED