@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 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
- await tool(ctx.cfg, 'call_website_site_tool', { site_id: ctx.cfg.site, tool: 'menu_set_items', arguments: { name, items } });
364
- say(`Menu ${name}: ${items.length} item${items.length === 1 ? '' : 's'} written.`);
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
- await pull(config());
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 = field.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 (${field.type === 'file' ? 'a picture, a video (mp4, webm), a font (woff2) or a pdf' : 'a picture: png, jpg, gif, webp, avif, svg, ico'})`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tideworthy/tidysites",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "Pull a Tidysites website to files, edit, validate against its content model, push it back, screenshot the result.",
5
5
  "type": "module",
6
6
  "bin": {