toolaby 1.4.1 → 1.4.3

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.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, BUILD_COPY_NOTE } from './lib/project.js';
35
+ import { detectProject, CODE_FILES, filesToWrite, STALE_FILES, BUILD_COPY_NOTE } from './lib/project.js';
36
36
 
37
- const VERSION = '1.4.1';
37
+ const VERSION = '1.4.3';
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.
@@ -203,16 +203,25 @@ async function wire() {
203
203
  // Nothing recognised: nothing is written and nothing guessed. A manifest below the top is where to point --into; otherwise the Set up page's Do it myself has the files and the three things to paste.
204
204
  if (project.kind === 'unknown') fail(`${dir} has no manifest.json at the top and is not a WXT, CRXJS or Plasmo project${project.foundManifest ? ` — there is one at ${project.foundManifest}: run this with --into ${dirname(project.foundManifest) || '.'}` : ''}.\n Nothing was written. For a layout of your own, the tool's Set up page → Do it myself has the files and the three things to paste.`);
205
205
 
206
- // The extension's own popup: the manifest's, or — wired before — the one the existing configuration remembers.
206
+ // The extension's own popup and side panel: the manifest's, or — wired before — what the existing configuration remembers.
207
207
  const configPath = join(dir, `${project.codeDir}toolaby.config.js`);
208
- const remembered = existsSync(configPath) ? readFileSync(configPath, 'utf8').match(/appPopup:\s*'([^']*)'/)?.[1] || null : null;
209
- const wiring = project.manifestFile && manifest ? wireManifest(manifest, { site, has, remembered, manifestKey }) : null;
208
+ const oldConfig = existsSync(configPath) ? readFileSync(configPath, 'utf8') : '';
209
+ const remembered = oldConfig.match(/appPopup:\s*'([^']*)'/)?.[1] || null;
210
+ const rememberedSidePanel = oldConfig.match(/appSidePanel:\s*'([^']*)'/)?.[1] || null;
211
+ const wiring = project.manifestFile && manifest ? wireManifest(manifest, { site, has, remembered, rememberedSidePanel, manifestKey }) : null;
210
212
  const appPopup = wiring ? wiring.appPopup : project.wrapper ? (project.appPopup ?? remembered) : null;
211
- const targets = filesToWrite(project, appPopup);
213
+ const appSidePanel = wiring ? wiring.appSidePanel : project.wrapper ? (project.appSidePanel ?? rememberedSidePanel) : null;
214
+ const targets = filesToWrite(project, appPopup, appSidePanel);
212
215
  const present = targets.filter((f) => existsSync(join(dir, f)));
213
216
  if (present.length && !flag('--force')) fail(`${present.join(', ')} already in ${dir}; add --force to replace them.`);
214
217
  const write = (rel, content) => { mkdirSync(dirname(join(dir, rel)), { recursive: true }); writeFileSync(join(dir, rel), content); };
215
- for (const path of targets) write(path, plan.files[path.split('/').pop()].replaceAll('__APP_POPUP__', appPopup ?? ''));
218
+ // A command older than the Wall asks for files the Wall no longer hands out: said, with the way to the current one, rather than a crash.
219
+ const missing = targets.map((p) => p.split('/').pop()).filter((name) => typeof plan.files[name] !== 'string');
220
+ if (missing.length) fail(`This command (${VERSION}) is older than the Wall at ${wall}: it expects ${missing.join(', ')}, which the Wall no longer hands out.\n Update it: npm install -g toolaby@latest — or run the Wall's own copy: npx --yes ${wall}/toolaby.tgz ${command} ${slug}`);
221
+ for (const path of targets) write(path, plan.files[path.split('/').pop()].replaceAll('__APP_POPUP__', appPopup ?? '').replaceAll('__APP_SIDE_PANEL__', appSidePanel ?? ''));
222
+ // What an earlier wiring wrote and this one does not, wherever it put them: taken away, so nothing stale is shipped.
223
+ const removed = [];
224
+ for (const at of new Set([project.codeDir, project.popupDir, 'public/', 'src/public/'])) for (const f of STALE_FILES) { const p = join(dir, `${at}${f}`); if (existsSync(p)) { unlinkSync(p); removed.push(`${at}${f}`); } }
216
225
  const did = [];
217
226
  let ownWorker = null;
218
227
  if (wiring) {
@@ -223,19 +232,21 @@ async function wire() {
223
232
  } else if (project.createWorker) {
224
233
  write(project.createWorker.file, project.createWorker.content);
225
234
  did.push(`a background entry, ${project.createWorker.file} (it starts the platform)`);
226
- } else if (project.worker) {
235
+ } else if (project.worker && !project.extraFiles) {
236
+ // A framework with its own extension point (WXT's module) starts the platform in the background itself.
227
237
  ownWorker = { file: project.worker, classic: false };
228
238
  }
229
239
 
230
240
  out(`\n ${plan.name} → ${dir}${project.kind === 'plain' ? '' : ` (${project.label})`} · ${side}${side === 'Test' ? ' (the tk_test_ key; `--live` for the store build)' : ''}\n`);
231
241
  out(` Written: ${targets.join(', ')}${did.length ? `,\n ${did.join(',\n ')}` : ''}.\n`);
242
+ if (removed.length) out(` Removed: ${removed.join(', ')} — earlier files, now inside toolaby.js.\n`);
232
243
  for (const sn of project.snippets) out(` Add to ${sn.file} — yours to paste, the manifest being generated:\n\n${sn.code.split('\n').map((l) => ` ${l}`).join('\n')}\n`);
233
244
  if (ownWorker) out(` Your background, ${ownWorker.file}: add${ownWorker.classic ? ' — after giving "background" a "type": "module" in manifest.json (importScripts() is not available in a module worker)' : ''}:\n\n${project.workerLines.trimEnd().split('\n').map((l) => ` ${l}`).join('\n')}\n`);
234
245
  if (project.built) out(` ${BUILD_COPY_NOTE}\n`);
235
246
  out(` Next: ${project.kind === 'plain' && !project.built ? '' : 'build or start the dev server, then '}chrome://extensions → reload it. The tool's Set up page says "checked in" the moment it starts.`);
236
247
  if (project.note) out(` ${project.note}\n`);
237
- else if (appPopup) out(` Your popup opens as before; what the Free plan holds (Pricing, in the dashboard) decides when the Wall's panel takes its place.\n`);
238
- else out(` No popup to stand in front of: count a use where your feature runs — the Set up page shows the line.\n`);
248
+ else if (appPopup || appSidePanel) out(` Your ${appPopup && appSidePanel ? 'popup and side panel open' : appSidePanel ? 'side panel opens' : 'popup opens'} as before; what the Free plan holds (Pricing, in the dashboard) decides when the Wall's panel takes its place.\n`);
249
+ else out(` No popup or side panel to stand in front of: count a use where your feature runs — the Set up page shows the line.\n`);
239
250
  }
240
251
 
241
252
  async function create() {
@@ -245,7 +256,7 @@ async function create() {
245
256
  if (existsSync(dir)) fail(`${dir} already exists`);
246
257
  const plan = await filesFor(slug, true);
247
258
  const write = (rel, content) => { mkdirSync(dirname(join(dir, rel)), { recursive: true }); writeFileSync(join(dir, rel), content); };
248
- for (const f of ['manifest.json', ...CODE_FILES, 'background.js', 'popup.html', 'popup.css', 'popup.js', 'README.md']) write(f, plan.files[f].replaceAll('__APP_POPUP__', ''));
259
+ for (const f of ['manifest.json', ...CODE_FILES, 'background.js', 'popup.html', 'popup.css', 'popup.js', 'README.md']) write(f, plan.files[f].replaceAll('__APP_POPUP__', '').replaceAll('__APP_SIDE_PANEL__', ''));
249
260
  for (const s of [16, 48, 128]) {
250
261
  const res = await fetch(`${wall}/boilerplate/icons/icon${s}.png`);
251
262
  write(join('icons', `icon${s}.png`), Buffer.from(await res.arrayBuffer()));
package/lib/manifest.js CHANGED
@@ -6,7 +6,8 @@
6
6
  * the storage permission; the platform's address as a host permission and
7
7
  * for externally_connectable (the site's sign-in and purchase handoffs); a
8
8
  * module service worker when the manifest names none, or names one that is
9
- * not there; the Wall's popup in front of the extension's own, which the
9
+ * not there; the Wall's popup in front of the extension's own — and its
10
+ * side panel in front of the extension's side panel — which the
10
11
  * configuration then remembers; and the extension's identity — the `key`
11
12
  * that fixes its id, which the tool key is bound to (identity.ts) — unless
12
13
  * the manifest has one of its own, which is then the identity the platform
@@ -14,6 +15,7 @@
14
15
  * write.
15
16
  */
16
17
  export const WALL_POPUP = 'toolaby-popup.html';
18
+ export const WALL_SIDE_PANEL = 'toolaby-sidepanel.html';
17
19
  export const WORKER_LINES = "import { toolaby } from './toolaby.js';\n\ntoolaby.startBackground();\n";
18
20
  export function wireManifest(input, o) {
19
21
  const manifest = JSON.parse(JSON.stringify(input));
@@ -47,6 +49,15 @@ export function wireManifest(input, o) {
47
49
  manifest.action = { ...manifest.action, default_popup: WALL_POPUP };
48
50
  did.push(`the Wall's popup in front of ${appPopup}: it opens on to yours while the device may, and shows the panel when it may not`);
49
51
  }
52
+ // The extension's own side panel, the same way: the Wall's in front, with the permission a side panel needs.
53
+ const declaredPanel = typeof manifest.side_panel?.default_path === 'string' ? manifest.side_panel.default_path : null;
54
+ const appSidePanel = declaredPanel && declaredPanel !== WALL_SIDE_PANEL ? declaredPanel : declaredPanel === WALL_SIDE_PANEL ? (o.rememberedSidePanel || null) : null;
55
+ if (appSidePanel && declaredPanel !== WALL_SIDE_PANEL) {
56
+ manifest.side_panel = { ...manifest.side_panel, default_path: WALL_SIDE_PANEL };
57
+ if (!(Array.isArray(manifest.permissions) && manifest.permissions.includes('sidePanel')))
58
+ manifest.permissions = add(manifest.permissions, 'sidePanel');
59
+ did.push(`the Wall's side panel in front of ${appSidePanel}: the same as the popup, at the panel's width`);
60
+ }
50
61
  const named = typeof manifest.background?.service_worker === 'string' && manifest.background.service_worker.trim() ? manifest.background.service_worker.trim() : null;
51
62
  let writeWorker = null;
52
63
  let ownWorker = null;
@@ -58,5 +69,5 @@ export function wireManifest(input, o) {
58
69
  else {
59
70
  ownWorker = { file: named, classic: manifest.background?.type !== 'module' };
60
71
  }
61
- return { manifest, did, writeWorker, ownWorker, appPopup, manifestKey };
72
+ return { manifest, did, writeWorker, ownWorker, appPopup, appSidePanel, manifestKey };
62
73
  }
package/lib/project.js CHANGED
@@ -1,18 +1,23 @@
1
1
  // Generated from wall/src/packages/wiring/project.ts by wall/scripts/pack-create.mjs — edit there.
2
2
  /** What to tell a developer whose build ships the extension — the Wire button, the command and the Set up page all say it in these words. */
3
- export const BUILD_COPY_NOTE = 'A build ships this extension: make sure it copies the platform\'s files to its output — toolaby.js, toolaby.config.js, toolaby-client.module.js, paywall.js, paywall.css and toolaby-popup.* — the way it copies your popup.html.';
4
- /** Always written, where the build imports from: the client, its config and types, and the panel a popup of the developer's own can draw. */
5
- export const CODE_FILES = ['toolaby.config.js', 'toolaby.js', 'toolaby-client.module.js', 'toolaby.d.ts', 'paywall.js', 'paywall.css'];
3
+ export const BUILD_COPY_NOTE = 'A build ships this extension: make sure it copies the platform\'s files to its output — toolaby.js, toolaby.config.js and toolaby-popup.html (toolaby-sidepanel.html too, with a side panel) — the way it copies your popup.html.';
4
+ /** Always written, where the build imports from: the platform as one file (the client and the popup UI), its config, its types. */
5
+ export const CODE_FILES = ['toolaby.config.js', 'toolaby.js', 'toolaby.d.ts'];
6
6
  /** Beside the client when the build generates the manifest: the fragment it spreads in, derived from the key (boilerplate/toolaby.manifest.js). */
7
7
  export const MANIFEST_HELPER = ['toolaby.manifest.js', 'toolaby.manifest.d.ts'];
8
- /** The Wall's popup, where the build copies files as they are; it imports the panel beside it, so the panel goes there too when that is elsewhere. */
9
- export const POPUP_FILES = ['toolaby-popup.html', 'toolaby-popup.js', 'toolaby-popup.css'];
10
- export const PANEL_FILES = ['paywall.js', 'paywall.css'];
11
- /** Every file a wiring writes, with its path: the code files, and — with a popup to stand in front of — the Wall's popup and, beside it, the panel. */
12
- export const filesToWrite = (p, appPopup) => [
8
+ /** The Wall's popup: a shell, and the line that mounts it from toolaby.js beside it (an extension page may not run a script inline). */
9
+ export const POPUP_FILES = ['toolaby-popup.html', 'toolaby-popup.js'];
10
+ /** The Wall's side panel: the same page at the panel's width, the same two files. */
11
+ export const SIDE_PANEL_FILES = ['toolaby-sidepanel.html', 'toolaby-sidepanel.js'];
12
+ /** What earlier wirings wrote and this one does not: an upgrade takes them away, wherever a wiring put them. */
13
+ export const STALE_FILES = ['toolaby-client.module.js', 'paywall.js', 'paywall.css', 'toolaby-popup.css', 'toolaby-account.js'];
14
+ /** Every file a wiring writes, with its path: the code files, and — with a popup or a side panel to stand in front of — the Wall's shell for it. */
15
+ export const filesToWrite = (p, appPopup, appSidePanel = null) => [
13
16
  ...CODE_FILES.map((f) => `${p.codeDir}${f}`),
14
17
  ...(p.manifestFile ? [] : MANIFEST_HELPER.map((f) => `${p.codeDir}${f}`)),
15
- ...(appPopup ? [...POPUP_FILES, ...(p.popupDir !== p.codeDir ? PANEL_FILES : [])].map((f) => `${p.popupDir}${f}`) : []),
18
+ ...(appPopup ? POPUP_FILES.map((f) => `${p.popupDir}${f}`) : []),
19
+ ...(appSidePanel ? SIDE_PANEL_FILES.map((f) => `${p.popupDir}${f}`) : []),
20
+ ...(p.extraFiles ?? []).map((f) => f.to),
16
21
  ];
17
22
  /** The import of toolaby.js from a file at `from`, both relative to the root. */
18
23
  export function importPath(from, codeDir = '') {
@@ -35,41 +40,30 @@ export function detectProject(o) {
35
40
  const worker = first(has, [`${src}entrypoints/background.ts`, `${src}entrypoints/background.js`, `${src}entrypoints/background/index.ts`, `${src}entrypoints/background/index.js`]);
36
41
  const popup = first(has, [`${src}entrypoints/popup.html`, `${src}entrypoints/popup/index.html`]);
37
42
  const appPopup = popup ? 'popup.html' : null;
43
+ const sidePanel = first(has, [`${src}entrypoints/sidepanel.html`, `${src}entrypoints/sidepanel/index.html`]);
44
+ const appSidePanel = sidePanel ? 'sidepanel.html' : null;
38
45
  const workerFile = worker ?? `${src}entrypoints/background.ts`;
39
46
  const workerLines = `import { toolaby } from '${importPath(workerFile)}';\n\nexport default defineBackground(() => {\n toolaby.startBackground();\n});\n`;
40
- const code = [
41
- `// wxt.config.ts — the Wall's needs in the manifest come from toolaby.config.js${appPopup ? ', and its popup goes in front of yours' : ''}`,
42
- `import { manifest as toolaby } from './toolaby.manifest.js';`,
43
- ``,
44
- `export default defineConfig({`,
45
- ` manifest: { ...toolaby }, // the extension's id, the storage permission, the Wall's address`,
46
- ...(appPopup ? [
47
- ` hooks: {`,
48
- ` // WXT names your popup entrypoint after merging the manifest above; the hook runs after that.`,
49
- ` 'build:manifestGenerated': (_, manifest) => {`,
50
- ` manifest.action = { ...manifest.action, default_popup: 'toolaby-popup.html' };`,
51
- ` },`,
52
- ` },`,
53
- ] : []),
54
- `});`,
55
- ].join('\n');
47
+ // WXT's extension point: a module in modules/, loaded by WXT itself. It merges the manifest, puts the Wall's popup in
48
+ // front, and starts the platform in the background — yours, or its own when there is none. Nothing to paste.
56
49
  return {
57
- kind: 'wxt', label: 'WXT', codeDir: '', popupDir: `${src}public/`, manifestFile: null, wrapper: true, appPopup, worker, built: false, foundManifest: null,
58
- createWorker: worker ? null : { file: workerFile, content: workerLines },
59
- workerLines, snippets: [{ file: first(has, ['wxt.config.ts', 'wxt.config.js', 'wxt.config.mts', 'wxt.config.mjs']) ?? 'wxt.config.ts', language: 'ts', code }],
50
+ kind: 'wxt', label: 'WXT', codeDir: '', popupDir: '', manifestFile: null, wrapper: true, appPopup, appSidePanel, worker, built: false, foundManifest: null,
51
+ createWorker: null,
52
+ workerLines, snippets: [],
53
+ extraFiles: [{ from: 'toolaby.wxt.ts', to: 'modules/toolaby.wxt.ts' }, { from: 'toolaby.background.js', to: 'toolaby.background.js' }],
60
54
  };
61
55
  }
62
56
  if (plasmo) {
63
57
  // Plasmo builds only the pages its own manifest names (popup.tsx → popup.html, from .plasmo/), and its manifest
64
58
  // override replaces `action` whole: a page of ours in front of the popup would leave the popup unbuilt. So no
65
- // wrapper here — the gate goes in the popup's code, one call, and the panel is a function away (paywall.js).
59
+ // wrapper here — the gate goes in the popup's code, one call, and the panel is a function away (toolaby.js).
66
60
  const src = has('src') && !first(has, ['popup.tsx', 'popup.html', 'background.ts', 'background.js']) ? 'src/' : '';
67
61
  const worker = first(has, [`${src}background.ts`, `${src}background.js`, `${src}background/index.ts`, `${src}background/index.js`]);
68
62
  const workerFile = worker ?? `${src}background.ts`;
69
63
  const workerLines = `import { toolaby } from '${importPath(workerFile)}';\n\ntoolaby.startBackground();\n`;
70
64
  const code = JSON.stringify({ manifest: { ...(o.manifestKey ? { key: o.manifestKey } : {}), permissions: ['storage'], host_permissions: [`${site}/*`], externally_connectable: { matches: [`${site}/*`] } } }, null, 2).replace(/^\{\n|\n\}$/g, '');
71
65
  return {
72
- kind: 'plasmo', label: 'Plasmo', codeDir: '', popupDir: '', manifestFile: null, wrapper: false, appPopup: null, worker, built: false, foundManifest: null,
66
+ kind: 'plasmo', label: 'Plasmo', codeDir: '', popupDir: '', manifestFile: null, wrapper: false, appPopup: null, appSidePanel: null, worker, built: false, foundManifest: null,
73
67
  createWorker: worker ? null : { file: workerFile, content: workerLines },
74
68
  workerLines, snippets: [{ file: 'package.json', language: 'json', code }],
75
69
  note: 'Plasmo builds only the pages its manifest names, so the Wall\'s popup cannot stand in front of yours: ask the gate in your popup — the lines are in the docs, under Plasmo.',
@@ -80,6 +74,8 @@ export function detectProject(o) {
80
74
  const source = o.manifestSource ?? '';
81
75
  const declared = source.match(/default_popup:\s*['"]([^'"]+)['"]/)?.[1] ?? null;
82
76
  const appPopup = declared && declared !== 'toolaby-popup.html' ? declared : null;
77
+ const declaredPanel = source.match(/default_path:\s*['"]([^'"]+)['"]/)?.[1] ?? null;
78
+ const appSidePanel = declaredPanel && declaredPanel !== 'toolaby-sidepanel.html' ? declaredPanel : null;
83
79
  const worker = source.match(/service_worker:\s*['"]([^'"]+)['"]/)?.[1] ?? first(has, ['src/background.ts', 'src/background.js', 'background.ts', 'background.js']);
84
80
  const workerFile = worker ?? 'src/background.ts';
85
81
  const workerLines = `import { toolaby } from '${importPath(workerFile)}';\n\ntoolaby.startBackground();\n`;
@@ -89,21 +85,23 @@ export function detectProject(o) {
89
85
  ``,
90
86
  `...toolaby, // the extension's id, the storage permission, the Wall's address`,
91
87
  ...(appPopup ? [`action: { default_popup: 'toolaby-popup.html' },`] : []),
88
+ ...(appSidePanel ? [`side_panel: { default_path: 'toolaby-sidepanel.html' }, // and 'sidePanel' among the permissions`] : []),
92
89
  ...(worker ? [] : [`background: { service_worker: '${workerFile}', type: 'module' },`]),
93
90
  ].join('\n');
94
- // CRXJS builds only the pages the manifest names; once ours is the popup, theirs stays built as a Vite input.
91
+ // CRXJS builds only the pages the manifest names; once ours are in front, theirs stay built as Vite inputs.
95
92
  const viteFile = first(has, ['vite.config.ts', 'vite.config.js', 'vite.config.mts', 'vite.config.mjs']) ?? 'vite.config.ts';
93
+ const inputs = [appPopup ? `popup: '${appPopup}'` : null, appSidePanel ? `sidepanel: '${appSidePanel}'` : null].filter(Boolean).join(', ');
96
94
  const vite = [
97
- `// ${viteFile} — CRXJS builds only the pages the manifest names: this keeps your popup built, for the Wall's to hand over to`,
95
+ `// ${viteFile} — CRXJS builds only the pages the manifest names: this keeps ${appPopup && appSidePanel ? 'your popup and side panel' : appSidePanel ? 'your side panel' : 'your popup'} built, for the Wall's to hand over to`,
98
96
  `export default defineConfig({`,
99
97
  ` plugins: [crx({ manifest })],`,
100
- ` build: { rollupOptions: { input: { popup: '${appPopup}' } } },`,
98
+ ` build: { rollupOptions: { input: { ${inputs} } } },`,
101
99
  `});`,
102
100
  ].join('\n');
103
101
  return {
104
- kind: 'crxjs', label: 'CRXJS', codeDir: '', popupDir: '', manifestFile: null, wrapper: true, appPopup, worker: worker && has(worker) ? worker : null, built: false, foundManifest: null,
102
+ kind: 'crxjs', label: 'CRXJS', codeDir: '', popupDir: '', manifestFile: null, wrapper: true, appPopup, appSidePanel, worker: worker && has(worker) ? worker : null, built: false, foundManifest: null,
105
103
  createWorker: worker && has(worker) ? null : { file: workerFile, content: workerLines },
106
- workerLines, snippets: [{ file: configFile ?? 'manifest.config.ts', language: 'ts', code }, ...(appPopup ? [{ file: viteFile, language: 'ts', code: vite }] : [])],
104
+ workerLines, snippets: [{ file: configFile ?? 'manifest.config.ts', language: 'ts', code }, ...(appPopup || appSidePanel ? [{ file: viteFile, language: 'ts', code: vite }] : [])],
107
105
  };
108
106
  }
109
107
  if (has('manifest.json')) {
@@ -111,16 +109,17 @@ export function detectProject(o) {
111
109
  // A plain extension with a build (webpack, Rollup, Parcel, esbuild…) is wired the same way — the manifest is a file and
112
110
  // the files go beside it — but only what the build copies reaches the output, so that is said.
113
111
  const declared = typeof manifest?.action?.default_popup === 'string' ? manifest.action.default_popup : null;
112
+ const declaredPanel = typeof manifest?.side_panel?.default_path === 'string' ? manifest.side_panel.default_path : null;
114
113
  const named = typeof manifest?.background?.service_worker === 'string' ? manifest.background.service_worker : null;
115
114
  const workerFile = named && has(named) ? named : 'background.js';
116
115
  return {
117
116
  kind: crxjs ? 'crxjs' : 'plain', label: crxjs ? 'CRXJS' : 'a plain extension', codeDir: '', popupDir: '', manifestFile: 'manifest.json', wrapper: true, built: crxjs ? false : built, foundManifest: null,
118
- appPopup: declared && declared !== 'toolaby-popup.html' ? declared : null, worker: named && has(named) ? named : null, createWorker: null,
117
+ appPopup: declared && declared !== 'toolaby-popup.html' ? declared : null, appSidePanel: declaredPanel && declaredPanel !== 'toolaby-sidepanel.html' ? declaredPanel : null, worker: named && has(named) ? named : null, createWorker: null,
119
118
  workerLines: `import { toolaby } from '${importPath(workerFile)}';\n\ntoolaby.startBackground();\n`, snippets: [],
120
119
  };
121
120
  }
122
121
  // Nothing recognised. A manifest below the root — src/, public/, a package of a monorepo — is where the wiring should have
123
122
  // been pointed, and is said; the shallowest one, since a fixture or a dependency may carry another.
124
123
  const foundManifest = [...(o.tree ?? [])].filter((p) => p.endsWith('/manifest.json') && !p.includes('node_modules/')).sort((a, b) => a.split('/').length - b.split('/').length || a.localeCompare(b))[0] ?? null;
125
- return { kind: 'unknown', label: 'unknown', codeDir: '', popupDir: '', manifestFile: null, wrapper: false, appPopup: null, worker: null, createWorker: null, workerLines: '', snippets: [], built, foundManifest };
124
+ return { kind: 'unknown', label: 'unknown', codeDir: '', popupDir: '', manifestFile: null, wrapper: false, appPopup: null, appSidePanel: null, worker: null, createWorker: null, workerLines: '', snippets: [], built, foundManifest };
126
125
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolaby",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
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": {