@tideworthy/tidysites 0.1.15 → 0.1.17
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/bin/tidysites.mjs +29 -3
- package/lib/validate.mjs +2 -2
- package/package.json +1 -1
package/bin/tidysites.mjs
CHANGED
|
@@ -87,11 +87,15 @@ function writeFiles(dir, files) {
|
|
|
87
87
|
function readLocal(dir, only = null) {
|
|
88
88
|
const local = {};
|
|
89
89
|
const problems = [];
|
|
90
|
+
// Only the website's own collections are entries; a folder of the person's own (notes, assets, source) is left alone.
|
|
91
|
+
const schema = readJson(join(dir, META, 'schema.json'));
|
|
92
|
+
const known = schema ? new Set((schema.collections?.items ?? schema.collections ?? []).map((c) => c.slug).filter(Boolean)) : null;
|
|
90
93
|
for (const name of readdirSync(dir)) {
|
|
91
94
|
if (name.startsWith('.') || name === 'node_modules') continue;
|
|
92
95
|
const colDir = join(dir, name);
|
|
93
96
|
if (!statSync(colDir).isDirectory()) continue;
|
|
94
97
|
if (!/^[a-z][a-z0-9_]*$/.test(name) || name === 'menus') continue;
|
|
98
|
+
if (known && known.size && !known.has(name)) continue;
|
|
95
99
|
if (only && !only.includes(name)) continue;
|
|
96
100
|
const sidecarExts = Object.keys(SIDECARS[name] ?? SIDECARS.default);
|
|
97
101
|
for (const file of readdirSync(colDir)) {
|
|
@@ -352,6 +356,7 @@ async function push(ctx) {
|
|
|
352
356
|
if (removeKeys.length) { const rem = readRemoved(ctx.dir); for (const k of removeKeys) rem.delete(k); writeRemoved(ctx.dir, rem); }
|
|
353
357
|
if (r.missing?.length) say(`Not found to remove: ${r.missing.map((m) => `${m.collection}/${m.slug}`).join(', ')}`);
|
|
354
358
|
}
|
|
359
|
+
const menuProblems = [];
|
|
355
360
|
for (const name of menus) {
|
|
356
361
|
const m = s.menus[name];
|
|
357
362
|
let items;
|
|
@@ -360,9 +365,22 @@ async function push(ctx) {
|
|
|
360
365
|
} catch (e) {
|
|
361
366
|
fail(`menus/${name}.json: ${e.message}`);
|
|
362
367
|
}
|
|
363
|
-
|
|
364
|
-
|
|
368
|
+
try {
|
|
369
|
+
try {
|
|
370
|
+
await tool(ctx.cfg, 'call_website_site_tool', { site_id: ctx.cfg.site, tool: 'menu_set_items', arguments: { name, items } });
|
|
371
|
+
} catch (e) {
|
|
372
|
+
// A menu the website does not have yet (a phone drawer, a footer column) is created from the file, then filled.
|
|
373
|
+
if (!/not.?found/i.test(e.message ?? '')) throw e;
|
|
374
|
+
await tool(ctx.cfg, 'call_website_site_tool', { site_id: ctx.cfg.site, tool: 'menu_create', arguments: { name, label: m.label || name } });
|
|
375
|
+
await tool(ctx.cfg, 'call_website_site_tool', { site_id: ctx.cfg.site, tool: 'menu_set_items', arguments: { name, items } });
|
|
376
|
+
say(`Menu ${name}: created.`);
|
|
377
|
+
}
|
|
378
|
+
say(`Menu ${name}: ${items.length} item${items.length === 1 ? '' : 's'} written.`);
|
|
379
|
+
} catch (e) {
|
|
380
|
+
menuProblems.push(`menus/${name}.json: ${e.message}`);
|
|
381
|
+
}
|
|
365
382
|
}
|
|
383
|
+
if (menuProblems.length) fail(menuProblems.join('\n'));
|
|
366
384
|
// Refresh only what went, and take the website's version of exactly those entries; every other file stays as it is.
|
|
367
385
|
const pushed = new Set([...keys, ...menus.map((m) => `menus/${m}`)]);
|
|
368
386
|
if (touched.length) await pull(ctx, touched, { overwrite: pushed, menus: menus.length > 0 });
|
|
@@ -678,7 +696,15 @@ async function main() {
|
|
|
678
696
|
writeFileSync(join(dir, META, 'key'), `${key}\n`, { mode: 0o600 });
|
|
679
697
|
writeFileSync(join(dir, META, '.gitignore'), 'key\nshots/\nsource/\npages/\n');
|
|
680
698
|
say(`Connected ${relative(process.cwd(), dir) || '.'} to ${site} on ${host}.`);
|
|
681
|
-
|
|
699
|
+
// A website that is still being set up answers 404 on its content model for a few minutes; the first pull waits for it.
|
|
700
|
+
for (let attempt = 1; ; attempt++) {
|
|
701
|
+
try { await pull(config()); break; }
|
|
702
|
+
catch (e) {
|
|
703
|
+
if (e?.code !== 'website_unreachable' || attempt >= 20) throw e;
|
|
704
|
+
say(`The website is still being set up (${e.message.slice(0, 120)}); trying again in 30 seconds (${attempt}/20).`);
|
|
705
|
+
await new Promise((r) => setTimeout(r, 30_000));
|
|
706
|
+
}
|
|
707
|
+
}
|
|
682
708
|
return;
|
|
683
709
|
}
|
|
684
710
|
case 'pull':
|
package/lib/validate.mjs
CHANGED
|
@@ -54,8 +54,8 @@ function checkScalar(type, value, at, errors) {
|
|
|
54
54
|
case 'file':
|
|
55
55
|
if (!isMedia(value)) errors.push(`${at}: an image is the stored value (from the pull or add_website_image) or { "$media": { "url": "https://..." | "file": "assets/x.png", "alt": "..." } }`);
|
|
56
56
|
else if (value.$media && typeof value.$media.file === 'string') {
|
|
57
|
-
const ok =
|
|
58
|
-
if (!ok.test(value.$media.file) || value.$media.file.startsWith('/') || value.$media.file.includes('..')) errors.push(`${at}: $media.file must be a file inside the folder (${
|
|
57
|
+
const ok = type === 'file' ? /\.(png|jpe?g|gif|webp|avif|svg|ico|mp4|webm|m4v|mov|woff2?|ttf|otf|pdf)$/i : /\.(png|jpe?g|gif|webp|avif|svg|ico)$/i;
|
|
58
|
+
if (!ok.test(value.$media.file) || value.$media.file.startsWith('/') || value.$media.file.includes('..')) errors.push(`${at}: $media.file must be a file inside the folder (${type === 'file' ? 'a picture, a video (mp4, webm), a font (woff2) or a pdf' : 'a picture: png, jpg, gif, webp, avif, svg, ico'})`);
|
|
59
59
|
}
|
|
60
60
|
else if (value.$media && (typeof value.$media.url !== 'string' || !/^https?:\/\//.test(value.$media.url))) errors.push(`${at}: $media.url must be an http(s) address (or $media.file for a picture in the folder)`);
|
|
61
61
|
else if (value.$media && typeof value.$media.alt !== 'string') errors.push(`${at}: $media.alt is required (what the image shows; "" for a decorative one)`);
|
package/package.json
CHANGED