toolaby 1.4.4 → 1.4.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.
Files changed (3) hide show
  1. package/bin.mjs +12 -6
  2. package/lib/project.js +18 -4
  3. package/package.json +1 -1
package/bin.mjs CHANGED
@@ -32,9 +32,9 @@ import { homedir } from 'node:os';
32
32
  import { spawn } from 'node:child_process';
33
33
  import { createHmac, randomBytes } from 'node:crypto';
34
34
  import { wireManifest, WORKER_LINES } from './lib/manifest.js';
35
- import { detectProject, CODE_FILES, filesToWrite, fillPages, scaffoldManifest, STALE_FILES, BUILD_COPY_NOTE } from './lib/project.js';
35
+ import { detectProject, CODE_FILES, filesToWrite, fillPages, scaffoldManifest, scaffoldFilesFor, surfacesOf, hasPopup, hasSidePanel, SURFACES, STALE_FILES, BUILD_COPY_NOTE } from './lib/project.js';
36
36
 
37
- const VERSION = '1.4.4';
37
+ const VERSION = '1.4.5';
38
38
  const CLIENT_ID = 'toolaby-cli';
39
39
  const argv = process.argv.slice(2);
40
40
  // `--version` and `--help` are commands of their own; every other command is the first bare word.
@@ -259,20 +259,25 @@ async function create() {
259
259
  // The scaffold's own files as the Wall names them (older Walls did not, hence the list): a Wall that adds one to the
260
260
  // minimal extension is written by this command too, instead of a manifest naming a file that never arrived.
261
261
  const scaffold = Array.isArray(plan.scaffold) && plan.scaffold.length ? plan.scaffold : ['manifest.json', 'background.js', 'popup.html', 'sidepanel.html', 'popup.css', 'popup.js', 'README.md'];
262
- const wanted = [...CODE_FILES, ...scaffold];
262
+ // Which surfaces this extension is made with: both (the default), one of them, or none at all for a tool that works
263
+ // from the background. Asked for once here, rather than written and then deleted by hand.
264
+ const asked = opt('--surfaces');
265
+ if (asked && !SURFACES.includes(asked)) fail(`--surfaces takes ${SURFACES.join(', ')} — not "${asked}".`);
266
+ const surfaces = surfacesOf(asked);
267
+ const wanted = [...CODE_FILES, ...scaffoldFilesFor(surfaces, scaffold)];
263
268
  const absent = wanted.filter((f) => typeof plan.files[f] !== 'string');
264
269
  if (absent.length) fail(`The Wall at ${wall} named ${absent.join(', ')} for a new extension but did not hand ${absent.length > 1 ? 'them' : 'it'} over.\n Nothing was written. Update this command: npm install -g toolaby@latest — or run the Wall's own copy: npx --yes ${wall}/toolaby.tgz create ${slug}`);
265
270
  // Both surfaces, and the panel drawn inside them: no page of the Wall's stands in front, so both names stay empty.
266
271
  for (const f of wanted) write(f, fillPages(plan.files[f]));
267
272
  // The manifest names the side panel only now that the page is beside it — an extension whose side_panel points at a
268
273
  // missing file does not load at all, so a Wall that stops serving the page must not leave the key behind either.
269
- const wroteSidePanel = wanted.includes('sidepanel.html');
270
- write('manifest.json', JSON.stringify(scaffoldManifest(JSON.parse(readFileSync(join(dir, 'manifest.json'), 'utf8')), { sidePanel: wroteSidePanel }), null, 2) + '\n');
274
+ write('manifest.json', JSON.stringify(scaffoldManifest(JSON.parse(readFileSync(join(dir, 'manifest.json'), 'utf8')), { sidePanel: hasSidePanel(surfaces), popup: hasPopup(surfaces) }), null, 2) + '\n');
271
275
  for (const s of [16, 48, 128]) {
272
276
  const res = await fetch(`${wall}/boilerplate/icons/icon${s}.png`);
273
277
  write(join('icons', `icon${s}.png`), Buffer.from(await res.arrayBuffer()));
274
278
  }
275
- out(`\n ${plan.name} → ${dir}\n\n 1. chrome://extensions → Developer mode → Load unpacked → ${dir}\n 2. Click the icon: the popup shows what the Free plan holds; Buy opens ${plan.site}/tools/${slug}\n${wroteSidePanel ? " 3. Open side panel, in that popup: the same page at the panel's width (README.md says how to keep only one)\n" : ''}\n Edit background.js: your work goes behind toolaby.gate().\n`);
279
+ const clicking = { both: 'Click the icon: the popup shows what the Free plan holds. Open side panel, in it, is the same page at the panel\'s width', popup: 'Click the icon: the popup shows what the Free plan holds', 'side-panel': 'Click the icon: the side panel opens — it has no popup, so the click is the panel\'s', none: 'No popup and no side panel: nothing opens on a click. Your code says when to sell — toolaby.openPaymentPage()' }[surfaces];
280
+ out(`\n ${plan.name} → ${dir}${surfaces === 'both' ? '' : ` (--surfaces ${surfaces})`}\n\n 1. chrome://extensions → Developer mode → Load unpacked → ${dir}\n 2. ${clicking}; Buy opens ${plan.site}/tools/${slug}\n\n Edit background.js: your work goes behind toolaby.gate().\n Surfaces, and how to add or drop one later: ${plan.site}/docs/surfaces\n`);
276
281
  }
277
282
 
278
283
  // ── webhooks: the catalogue, and a delivery on demand ───────────────────────────────────────────────────────────────
@@ -368,6 +373,7 @@ function help() {
368
373
  --into <folder> another folder --force replace the platform's files
369
374
  toolaby upgrade <tool-id> the platform's files replaced with the current ones — the popup, the client — yours untouched
370
375
  toolaby create <tool-id> a new, minimal extension for your tool, in <tool-id>/ --dir <folder>
376
+ --surfaces both|popup|side-panel|none what it opens (default: both)
371
377
  toolaby tools your tools, with their ids
372
378
  toolaby webhooks events the six events, with an example of each
373
379
  toolaby webhooks trigger <event> an example delivery to your endpoints; --to <url> sends it to a local server instead
package/lib/project.js CHANGED
@@ -19,18 +19,32 @@ export const filesToWrite = (p, appPopup, appSidePanel = null) => [
19
19
  ...(appSidePanel ? SIDE_PANEL_FILES.map((f) => `${p.popupDir}${f}`) : []),
20
20
  ...(p.extraFiles ?? []).map((f) => f.to),
21
21
  ];
22
+ export const SURFACES = ['both', 'popup', 'side-panel', 'none'];
23
+ export const surfacesOf = (v) => SURFACES.includes(v ?? '') ? v : 'both';
24
+ export const hasPopup = (s) => s === 'both' || s === 'popup';
25
+ export const hasSidePanel = (s) => s === 'both' || s === 'side-panel';
26
+ /** The scaffold's own files for those surfaces: the manifest and the background always, the pages as asked for. */
27
+ export const scaffoldFilesFor = (s, all) => all.filter((f) => (f === 'popup.html' ? hasPopup(s) : f === 'sidepanel.html' ? hasSidePanel(s) : f === 'popup.js' || f === 'popup.css' ? s !== 'none' : true));
22
28
  /**
23
29
  * The scaffold's manifest, once it is known which pages were really written. Chrome refuses to load an extension
24
30
  * whose `side_panel.default_path` names a file that is not there — the whole extension, not just the panel — so the
25
- * minimal extension ships a manifest that names only its popup, and whatever writes the side panel adds it here.
31
+ * minimal extension ships a manifest that names only its popup, and whatever writes the pages settles it here: the
32
+ * side panel added with its page, the popup taken out when there is none (the icon stays; the click then opens the
33
+ * side panel, or nothing, which is what a background-only tool wants).
26
34
  */
27
35
  export function scaffoldManifest(manifest, wrote) {
36
+ const out = { ...manifest };
37
+ if (wrote.popup === false) {
38
+ const action = { ...out.action };
39
+ delete action.default_popup;
40
+ out.action = action;
41
+ }
28
42
  if (!wrote.sidePanel)
29
- return manifest;
30
- const permissions = Array.isArray(manifest.permissions) ? [...manifest.permissions] : [];
43
+ return out;
44
+ const permissions = Array.isArray(out.permissions) ? [...out.permissions] : [];
31
45
  if (!permissions.includes('sidePanel'))
32
46
  permissions.push('sidePanel');
33
- return { ...manifest, permissions, side_panel: { default_path: 'sidepanel.html' } };
47
+ return { ...out, permissions, side_panel: { default_path: 'sidepanel.html' } };
34
48
  }
35
49
  /**
36
50
  * The last two placeholders in a handed-out file — the extension's own popup and side panel, which only the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolaby",
3
- "version": "1.4.4",
3
+ "version": "1.4.5",
4
4
  "description": "The Toolaby command line: sign in once, then wire any Chrome extension to your tool on the Wall — sign-in, the Free plan, licences and subscriptions — or start a new one.",
5
5
  "type": "module",
6
6
  "bin": {