@tideworthy/tidysites 0.1.3 → 0.1.5
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 +3 -2
- package/bin/tidysites.mjs +32 -0
- package/lib/fetch.mjs +11 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,7 +27,8 @@ nothing else; it lives in `.tidysites/key`, which is kept out of git.
|
|
|
27
27
|
| `tidysites page <path> [--html]` | the rendered page, section by section, with the key each section carries |
|
|
28
28
|
| `tidysites schema [type or collection]` | the section types and layouts, or one type's or collection's fields |
|
|
29
29
|
| `tidysites gallery [type]` | a screenshot of every section type and layout in this website's theme, or the first screen of one type |
|
|
30
|
-
| `tidysites fetch <url> [--out dir]` | a page of the site being ported: cleaned HTML, its outline (headings, navigation, images, phones, fonts, colours) and its stylesheets |
|
|
30
|
+
| `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 |
|
|
31
|
+
| `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 |
|
|
31
32
|
| `tidysites publish --yes` | the live site becomes a copy of the preview site |
|
|
32
33
|
|
|
33
34
|
A section keeps its type with its key: to replace an intro with a row, give the row its own `_key`
|
|
@@ -50,7 +51,7 @@ menus/<name>.json a menu as a tree: { "label", "items": [{ "label",
|
|
|
50
51
|
|
|
51
52
|
A new entry is a new `<collection>/<slug>.json` with at least `"data"`; the
|
|
52
53
|
slug is the file name, the status is `published` unless the file says
|
|
53
|
-
`draft`. Delete a file and `push --prune` removes the entry. A field taken
|
|
54
|
+
`draft`. Delete a file (`tidysites rm services/old-one`, or `services/*` for all) and `push --prune` removes the entry. A field taken
|
|
54
55
|
out of a file is cleared on the website at the next push. `id` and
|
|
55
56
|
`updated_at` are the website's; leave them as pulled. A pull never overwrites
|
|
56
57
|
a file you changed and have not pushed; it keeps the file and the difference
|
package/bin/tidysites.mjs
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* schema [name] section types and layouts, or one type's or collection's fields
|
|
14
14
|
* gallery [type] screenshot of every section type in this theme, or the first screen of one type
|
|
15
15
|
* fetch <url> [--out dir] a page of the site being ported: cleaned HTML and its outline
|
|
16
|
+
* rm <collection>/<slug> ... delete entry files (globs allowed: services/*); push --prune then removes them from the website
|
|
16
17
|
* publish --yes the live site becomes a copy of the preview site
|
|
17
18
|
*
|
|
18
19
|
* The site is the remote: pull, edit, push. A push that would overwrite an
|
|
@@ -455,6 +456,9 @@ async function fetchCmd(ctx) {
|
|
|
455
456
|
/* a stylesheet that will not download is not the port's problem */
|
|
456
457
|
}
|
|
457
458
|
}
|
|
459
|
+
// A page that carries its styles inline (Wix, many builders) has no stylesheet to download: read the <style> blocks too.
|
|
460
|
+
const inline = [...html.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style>/gi)].map((m) => m[1]).join('\n');
|
|
461
|
+
if (inline.trim()) { cssTexts.push(inline); writeFileSync(join(outDir, `${base}-inline.css`), inline); }
|
|
458
462
|
if (cssTexts.length) o.css = cssSummary(cssTexts.join('\n'));
|
|
459
463
|
writeFileSync(join(outDir, `${base}.raw.html`), html);
|
|
460
464
|
writeFileSync(join(outDir, `${base}.html`), cleanHtml(html));
|
|
@@ -465,6 +469,32 @@ async function fetchCmd(ctx) {
|
|
|
465
469
|
say(`Saved to ${relative(process.cwd(), outDir)}/: ${base}.html (cleaned), ${base}.raw.html, ${base}.outline.json${cssTexts.length ? `, ${cssTexts.length} stylesheet${cssTexts.length === 1 ? '' : 's'}` : ''}`);
|
|
466
470
|
}
|
|
467
471
|
|
|
472
|
+
/**
|
|
473
|
+
* Delete entry files inside the folder, with their sidecars, so nobody has to reach for the shell's rm (which a
|
|
474
|
+
* harness may stop to ask about). Takes collection/slug paths; a trailing * takes every entry of the collection.
|
|
475
|
+
*/
|
|
476
|
+
function rmCmd({ dir }) {
|
|
477
|
+
if (!positional.length) fail('Usage: tidysites rm <collection>/<slug> [...] (services/* for every entry of a collection)');
|
|
478
|
+
let removed = 0;
|
|
479
|
+
for (const arg of positional) {
|
|
480
|
+
const m = /^([a-z][a-z0-9_]*)\/(\*|[A-Za-z0-9][A-Za-z0-9_-]*)(?:\.json)?$/.exec(arg.replace(/^\.\//, ''));
|
|
481
|
+
if (!m) fail(`${arg}: write collection/slug or collection/*`);
|
|
482
|
+
const [, collection, slug] = m;
|
|
483
|
+
const colDir = join(dir, collection);
|
|
484
|
+
if (!existsSync(colDir)) { say(`${collection}/: no such collection folder`); continue; }
|
|
485
|
+
const slugs = slug === '*' ? readdirSync(colDir).filter((f) => f.endsWith('.json')).map((f) => f.slice(0, -5)) : [slug];
|
|
486
|
+
for (const sl of slugs) {
|
|
487
|
+
let hit = false;
|
|
488
|
+
for (const ext of ['json', ...Object.keys(SIDECARS[collection] ?? SIDECARS.default)]) {
|
|
489
|
+
const f = join(colDir, `${sl}.${ext}`);
|
|
490
|
+
if (existsSync(f)) { rmSync(f); hit = true; }
|
|
491
|
+
}
|
|
492
|
+
if (hit) { removed++; say(`- ${collection}/${sl}`); } else say(`${collection}/${sl}: no such entry file`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
say(`${removed} entr${removed === 1 ? 'y' : 'ies'} removed from the folder; \`tidysites push --prune\` removes them from the website.`);
|
|
496
|
+
}
|
|
497
|
+
|
|
468
498
|
async function publish(ctx) {
|
|
469
499
|
if (!flags.yes) fail('Publishing makes the live site a copy of the preview site. Look at it first (`tidysites shot /`), then run `tidysites publish --yes`.');
|
|
470
500
|
const { json, text } = await tool(ctx.cfg, 'publish_website', { site_id: ctx.cfg.site });
|
|
@@ -524,6 +554,8 @@ async function main() {
|
|
|
524
554
|
return schema(config());
|
|
525
555
|
case 'fetch':
|
|
526
556
|
return fetchCmd(config(false));
|
|
557
|
+
case 'rm':
|
|
558
|
+
return rmCmd(config());
|
|
527
559
|
case 'publish':
|
|
528
560
|
return publish(config());
|
|
529
561
|
case 'help':
|
package/lib/fetch.mjs
CHANGED
|
@@ -108,11 +108,18 @@ export function cssSummary(css) {
|
|
|
108
108
|
const f = m[1].split(',')[0].replace(/["']/g, '').trim();
|
|
109
109
|
if (f && !/^(inherit|initial|unset|var\()/.test(f)) fonts.set(f, (fonts.get(f) ?? 0) + 1);
|
|
110
110
|
}
|
|
111
|
-
// The site's own names for its colours and fonts, when its stylesheet defines them; a framework's defaults sit below
|
|
111
|
+
// The site's own names for its colours and fonts, when its stylesheet defines them; a framework's defaults sit below
|
|
112
|
+
// these. A builder often defines a token as another token (--button-fill: rgb(var(--color_48)); --color_48: 254,96,47):
|
|
113
|
+
// the references are followed so the list reads as colours, and bare channel triplets become rgb().
|
|
114
|
+
const defined = {};
|
|
115
|
+
for (const m of css.matchAll(/--([a-zA-Z][\w-]*)\s*:\s*([^;}]+)/g)) if (!(m[1] in defined)) defined[m[1]] = m[2].trim();
|
|
116
|
+
const resolve = (v, depth = 0) => (depth > 4 ? v : v.replace(/var\(\s*--([\w-]+)\s*(?:,[^)]*)?\)/g, (_, name) => (name in defined ? resolve(defined[name], depth + 1) : `var(--${name})`)));
|
|
112
117
|
const variables = {};
|
|
113
|
-
for (const
|
|
114
|
-
|
|
115
|
-
if (
|
|
118
|
+
for (const [name, raw] of Object.entries(defined)) {
|
|
119
|
+
let v = resolve(raw).replace(/rgba?\(\s*(\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\s*\)/g, 'rgb($1,$2,$3)');
|
|
120
|
+
if (/^\d{1,3},\s*\d{1,3},\s*\d{1,3}$/.test(v)) v = `rgb(${v.replace(/\s/g, '')})`;
|
|
121
|
+
if (/var\(/.test(v)) continue;
|
|
122
|
+
if (/#[0-9a-f]{3,8}\b|rgba?\(|hsla?\(|font|["'][A-Z][\w ]+["']/i.test(v)) variables[name] = v;
|
|
116
123
|
}
|
|
117
124
|
const top = (map, n) => [...map.entries()].sort((a, b) => b[1] - a[1]).slice(0, n).map(([value, count]) => ({ value, count }));
|
|
118
125
|
return { colors: top(colors, 12), fonts: top(fonts, 6), variables };
|
package/package.json
CHANGED