toolaby 1.4.2 → 1.4.4
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 +25 -11
- package/lib/manifest.js +22 -2
- package/lib/project.js +43 -12
- 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, STALE_FILES, BUILD_COPY_NOTE } from './lib/project.js';
|
|
35
|
+
import { detectProject, CODE_FILES, filesToWrite, fillPages, scaffoldManifest, STALE_FILES, BUILD_COPY_NOTE } from './lib/project.js';
|
|
36
36
|
|
|
37
|
-
const VERSION = '1.4.
|
|
37
|
+
const VERSION = '1.4.4';
|
|
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,19 +203,22 @@ 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 —
|
|
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
|
|
209
|
-
const
|
|
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
|
|
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
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.
|
|
216
219
|
const missing = targets.map((p) => p.split('/').pop()).filter((name) => typeof plan.files[name] !== 'string');
|
|
217
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}`);
|
|
218
|
-
for (const path of targets) write(path, plan.files[path.split('/').pop()]
|
|
221
|
+
for (const path of targets) write(path, fillPages(plan.files[path.split('/').pop()], { appPopup, appSidePanel }));
|
|
219
222
|
// What an earlier wiring wrote and this one does not, wherever it put them: taken away, so nothing stale is shipped.
|
|
220
223
|
const removed = [];
|
|
221
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}`); } }
|
|
@@ -242,8 +245,8 @@ async function wire() {
|
|
|
242
245
|
if (project.built) out(` ${BUILD_COPY_NOTE}\n`);
|
|
243
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.`);
|
|
244
247
|
if (project.note) out(` ${project.note}\n`);
|
|
245
|
-
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`);
|
|
246
|
-
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`);
|
|
247
250
|
}
|
|
248
251
|
|
|
249
252
|
async function create() {
|
|
@@ -253,12 +256,23 @@ async function create() {
|
|
|
253
256
|
if (existsSync(dir)) fail(`${dir} already exists`);
|
|
254
257
|
const plan = await filesFor(slug, true);
|
|
255
258
|
const write = (rel, content) => { mkdirSync(dirname(join(dir, rel)), { recursive: true }); writeFileSync(join(dir, rel), content); };
|
|
256
|
-
|
|
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
|
+
// minimal extension is written by this command too, instead of a manifest naming a file that never arrived.
|
|
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];
|
|
263
|
+
const absent = wanted.filter((f) => typeof plan.files[f] !== 'string');
|
|
264
|
+
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
|
+
// Both surfaces, and the panel drawn inside them: no page of the Wall's stands in front, so both names stay empty.
|
|
266
|
+
for (const f of wanted) write(f, fillPages(plan.files[f]));
|
|
267
|
+
// The manifest names the side panel only now that the page is beside it — an extension whose side_panel points at a
|
|
268
|
+
// 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');
|
|
257
271
|
for (const s of [16, 48, 128]) {
|
|
258
272
|
const res = await fetch(`${wall}/boilerplate/icons/icon${s}.png`);
|
|
259
273
|
write(join('icons', `icon${s}.png`), Buffer.from(await res.arrayBuffer()));
|
|
260
274
|
}
|
|
261
|
-
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\n Edit background.js: your work goes behind toolaby.gate().\n`);
|
|
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`);
|
|
262
276
|
}
|
|
263
277
|
|
|
264
278
|
// ── webhooks: the catalogue, and a delivery on demand ───────────────────────────────────────────────────────────────
|
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
|
|
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,24 @@ 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
|
+
// Chrome spells it side_panel.default_path, Firefox sidebar_action.default_panel; a manifest may carry both.
|
|
54
|
+
const theirs = (v) => (typeof v === 'string' && v !== WALL_SIDE_PANEL ? v : null);
|
|
55
|
+
const declaredPanel = typeof manifest.side_panel?.default_path === 'string' ? manifest.side_panel.default_path : null;
|
|
56
|
+
const declaredSidebar = typeof manifest.sidebar_action?.default_panel === 'string' ? manifest.sidebar_action.default_panel : null;
|
|
57
|
+
const ours = declaredPanel === WALL_SIDE_PANEL || declaredSidebar === WALL_SIDE_PANEL;
|
|
58
|
+
const appSidePanel = theirs(declaredPanel) ?? theirs(declaredSidebar) ?? (ours ? o.rememberedSidePanel || null : null);
|
|
59
|
+
if (theirs(declaredPanel)) {
|
|
60
|
+
manifest.side_panel = { ...manifest.side_panel, default_path: WALL_SIDE_PANEL };
|
|
61
|
+
if (!(Array.isArray(manifest.permissions) && manifest.permissions.includes('sidePanel')))
|
|
62
|
+
manifest.permissions = add(manifest.permissions, 'sidePanel');
|
|
63
|
+
did.push(`the Wall's side panel in front of ${declaredPanel}: the same as the popup, at the panel's width`);
|
|
64
|
+
}
|
|
65
|
+
// Firefox's sidebar, when it names the same page — one that names another is left alone rather than pointed elsewhere.
|
|
66
|
+
if (theirs(declaredSidebar) && (!declaredPanel || declaredSidebar === declaredPanel)) {
|
|
67
|
+
manifest.sidebar_action = { ...manifest.sidebar_action, default_panel: WALL_SIDE_PANEL };
|
|
68
|
+
did.push(`the Wall's side panel in front of ${declaredSidebar} in Firefox's sidebar_action too`);
|
|
69
|
+
}
|
|
50
70
|
const named = typeof manifest.background?.service_worker === 'string' && manifest.background.service_worker.trim() ? manifest.background.service_worker.trim() : null;
|
|
51
71
|
let writeWorker = null;
|
|
52
72
|
let ownWorker = null;
|
|
@@ -58,5 +78,5 @@ export function wireManifest(input, o) {
|
|
|
58
78
|
else {
|
|
59
79
|
ownWorker = { file: named, classic: manifest.background?.type !== 'module' };
|
|
60
80
|
}
|
|
61
|
-
return { manifest, did, writeWorker, ownWorker, appPopup, manifestKey };
|
|
81
|
+
return { manifest, did, writeWorker, ownWorker, appPopup, appSidePanel, manifestKey };
|
|
62
82
|
}
|
package/lib/project.js
CHANGED
|
@@ -1,21 +1,44 @@
|
|
|
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 and toolaby-popup.html — the way it copies your popup.html.';
|
|
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
4
|
/** Always written, where the build imports from: the platform as one file (the client and the popup UI), its config, its types. */
|
|
5
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
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
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'];
|
|
10
12
|
/** What earlier wirings wrote and this one does not: an upgrade takes them away, wherever a wiring put them. */
|
|
11
13
|
export const STALE_FILES = ['toolaby-client.module.js', 'paywall.js', 'paywall.css', 'toolaby-popup.css', 'toolaby-account.js'];
|
|
12
|
-
/** Every file a wiring writes, with its path: the code files, and — with a popup to stand in front of — the Wall's
|
|
13
|
-
export const filesToWrite = (p, appPopup) => [
|
|
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) => [
|
|
14
16
|
...CODE_FILES.map((f) => `${p.codeDir}${f}`),
|
|
15
17
|
...(p.manifestFile ? [] : MANIFEST_HELPER.map((f) => `${p.codeDir}${f}`)),
|
|
16
18
|
...(appPopup ? POPUP_FILES.map((f) => `${p.popupDir}${f}`) : []),
|
|
19
|
+
...(appSidePanel ? SIDE_PANEL_FILES.map((f) => `${p.popupDir}${f}`) : []),
|
|
17
20
|
...(p.extraFiles ?? []).map((f) => f.to),
|
|
18
21
|
];
|
|
22
|
+
/**
|
|
23
|
+
* The scaffold's manifest, once it is known which pages were really written. Chrome refuses to load an extension
|
|
24
|
+
* 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.
|
|
26
|
+
*/
|
|
27
|
+
export function scaffoldManifest(manifest, wrote) {
|
|
28
|
+
if (!wrote.sidePanel)
|
|
29
|
+
return manifest;
|
|
30
|
+
const permissions = Array.isArray(manifest.permissions) ? [...manifest.permissions] : [];
|
|
31
|
+
if (!permissions.includes('sidePanel'))
|
|
32
|
+
permissions.push('sidePanel');
|
|
33
|
+
return { ...manifest, permissions, side_panel: { default_path: 'sidepanel.html' } };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The last two placeholders in a handed-out file — the extension's own popup and side panel, which only the
|
|
37
|
+
* writer knows, since it is the one that read the manifest. Everything else is filled where the files are built
|
|
38
|
+
* (wiring/files.ts). Every hand-out goes through this: the Wire button, the command, and the zip on the Set up
|
|
39
|
+
* page, which once shipped `appSidePanel: '__APP_SIDE_PANEL__'` because it filled the popup's name and not this one.
|
|
40
|
+
*/
|
|
41
|
+
export const fillPages = (text, pages = {}) => text.replaceAll('__APP_POPUP__', pages.appPopup ?? '').replaceAll('__APP_SIDE_PANEL__', pages.appSidePanel ?? '');
|
|
19
42
|
/** The import of toolaby.js from a file at `from`, both relative to the root. */
|
|
20
43
|
export function importPath(from, codeDir = '') {
|
|
21
44
|
const depth = from.split('/').length - 1;
|
|
@@ -37,12 +60,14 @@ export function detectProject(o) {
|
|
|
37
60
|
const worker = first(has, [`${src}entrypoints/background.ts`, `${src}entrypoints/background.js`, `${src}entrypoints/background/index.ts`, `${src}entrypoints/background/index.js`]);
|
|
38
61
|
const popup = first(has, [`${src}entrypoints/popup.html`, `${src}entrypoints/popup/index.html`]);
|
|
39
62
|
const appPopup = popup ? 'popup.html' : null;
|
|
63
|
+
const sidePanel = first(has, [`${src}entrypoints/sidepanel.html`, `${src}entrypoints/sidepanel/index.html`]);
|
|
64
|
+
const appSidePanel = sidePanel ? 'sidepanel.html' : null;
|
|
40
65
|
const workerFile = worker ?? `${src}entrypoints/background.ts`;
|
|
41
66
|
const workerLines = `import { toolaby } from '${importPath(workerFile)}';\n\nexport default defineBackground(() => {\n toolaby.startBackground();\n});\n`;
|
|
42
67
|
// WXT's extension point: a module in modules/, loaded by WXT itself. It merges the manifest, puts the Wall's popup in
|
|
43
68
|
// front, and starts the platform in the background — yours, or its own when there is none. Nothing to paste.
|
|
44
69
|
return {
|
|
45
|
-
kind: 'wxt', label: 'WXT', codeDir: '', popupDir: '', manifestFile: null, wrapper: true, appPopup, worker, built: false, foundManifest: null,
|
|
70
|
+
kind: 'wxt', label: 'WXT', codeDir: '', popupDir: '', manifestFile: null, wrapper: true, appPopup, appSidePanel, worker, built: false, foundManifest: null,
|
|
46
71
|
createWorker: null,
|
|
47
72
|
workerLines, snippets: [],
|
|
48
73
|
extraFiles: [{ from: 'toolaby.wxt.ts', to: 'modules/toolaby.wxt.ts' }, { from: 'toolaby.background.js', to: 'toolaby.background.js' }],
|
|
@@ -58,7 +83,7 @@ export function detectProject(o) {
|
|
|
58
83
|
const workerLines = `import { toolaby } from '${importPath(workerFile)}';\n\ntoolaby.startBackground();\n`;
|
|
59
84
|
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, '');
|
|
60
85
|
return {
|
|
61
|
-
kind: 'plasmo', label: 'Plasmo', codeDir: '', popupDir: '', manifestFile: null, wrapper: false, appPopup: null, worker, built: false, foundManifest: null,
|
|
86
|
+
kind: 'plasmo', label: 'Plasmo', codeDir: '', popupDir: '', manifestFile: null, wrapper: false, appPopup: null, appSidePanel: null, worker, built: false, foundManifest: null,
|
|
62
87
|
createWorker: worker ? null : { file: workerFile, content: workerLines },
|
|
63
88
|
workerLines, snippets: [{ file: 'package.json', language: 'json', code }],
|
|
64
89
|
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.',
|
|
@@ -69,6 +94,8 @@ export function detectProject(o) {
|
|
|
69
94
|
const source = o.manifestSource ?? '';
|
|
70
95
|
const declared = source.match(/default_popup:\s*['"]([^'"]+)['"]/)?.[1] ?? null;
|
|
71
96
|
const appPopup = declared && declared !== 'toolaby-popup.html' ? declared : null;
|
|
97
|
+
const declaredPanel = source.match(/default_path:\s*['"]([^'"]+)['"]/)?.[1] ?? null;
|
|
98
|
+
const appSidePanel = declaredPanel && declaredPanel !== 'toolaby-sidepanel.html' ? declaredPanel : null;
|
|
72
99
|
const worker = source.match(/service_worker:\s*['"]([^'"]+)['"]/)?.[1] ?? first(has, ['src/background.ts', 'src/background.js', 'background.ts', 'background.js']);
|
|
73
100
|
const workerFile = worker ?? 'src/background.ts';
|
|
74
101
|
const workerLines = `import { toolaby } from '${importPath(workerFile)}';\n\ntoolaby.startBackground();\n`;
|
|
@@ -78,21 +105,23 @@ export function detectProject(o) {
|
|
|
78
105
|
``,
|
|
79
106
|
`...toolaby, // the extension's id, the storage permission, the Wall's address`,
|
|
80
107
|
...(appPopup ? [`action: { default_popup: 'toolaby-popup.html' },`] : []),
|
|
108
|
+
...(appSidePanel ? [`side_panel: { default_path: 'toolaby-sidepanel.html' }, // and 'sidePanel' among the permissions`] : []),
|
|
81
109
|
...(worker ? [] : [`background: { service_worker: '${workerFile}', type: 'module' },`]),
|
|
82
110
|
].join('\n');
|
|
83
|
-
// CRXJS builds only the pages the manifest names; once ours
|
|
111
|
+
// CRXJS builds only the pages the manifest names; once ours are in front, theirs stay built as Vite inputs.
|
|
84
112
|
const viteFile = first(has, ['vite.config.ts', 'vite.config.js', 'vite.config.mts', 'vite.config.mjs']) ?? 'vite.config.ts';
|
|
113
|
+
const inputs = [appPopup ? `popup: '${appPopup}'` : null, appSidePanel ? `sidepanel: '${appSidePanel}'` : null].filter(Boolean).join(', ');
|
|
85
114
|
const vite = [
|
|
86
|
-
`// ${viteFile} — CRXJS builds only the pages the manifest names: this keeps your popup built, for the Wall's to hand over to`,
|
|
115
|
+
`// ${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`,
|
|
87
116
|
`export default defineConfig({`,
|
|
88
117
|
` plugins: [crx({ manifest })],`,
|
|
89
|
-
` build: { rollupOptions: { input: {
|
|
118
|
+
` build: { rollupOptions: { input: { ${inputs} } } },`,
|
|
90
119
|
`});`,
|
|
91
120
|
].join('\n');
|
|
92
121
|
return {
|
|
93
|
-
kind: 'crxjs', label: 'CRXJS', codeDir: '', popupDir: '', manifestFile: null, wrapper: true, appPopup, worker: worker && has(worker) ? worker : null, built: false, foundManifest: null,
|
|
122
|
+
kind: 'crxjs', label: 'CRXJS', codeDir: '', popupDir: '', manifestFile: null, wrapper: true, appPopup, appSidePanel, worker: worker && has(worker) ? worker : null, built: false, foundManifest: null,
|
|
94
123
|
createWorker: worker && has(worker) ? null : { file: workerFile, content: workerLines },
|
|
95
|
-
workerLines, snippets: [{ file: configFile ?? 'manifest.config.ts', language: 'ts', code }, ...(appPopup ? [{ file: viteFile, language: 'ts', code: vite }] : [])],
|
|
124
|
+
workerLines, snippets: [{ file: configFile ?? 'manifest.config.ts', language: 'ts', code }, ...(appPopup || appSidePanel ? [{ file: viteFile, language: 'ts', code: vite }] : [])],
|
|
96
125
|
};
|
|
97
126
|
}
|
|
98
127
|
if (has('manifest.json')) {
|
|
@@ -100,16 +129,18 @@ export function detectProject(o) {
|
|
|
100
129
|
// A plain extension with a build (webpack, Rollup, Parcel, esbuild…) is wired the same way — the manifest is a file and
|
|
101
130
|
// the files go beside it — but only what the build copies reaches the output, so that is said.
|
|
102
131
|
const declared = typeof manifest?.action?.default_popup === 'string' ? manifest.action.default_popup : null;
|
|
132
|
+
// Chrome's side_panel, else Firefox's sidebar_action: the same page, two spellings.
|
|
133
|
+
const declaredPanel = typeof manifest?.side_panel?.default_path === 'string' ? manifest.side_panel.default_path : typeof manifest?.sidebar_action?.default_panel === 'string' ? manifest.sidebar_action.default_panel : null;
|
|
103
134
|
const named = typeof manifest?.background?.service_worker === 'string' ? manifest.background.service_worker : null;
|
|
104
135
|
const workerFile = named && has(named) ? named : 'background.js';
|
|
105
136
|
return {
|
|
106
137
|
kind: crxjs ? 'crxjs' : 'plain', label: crxjs ? 'CRXJS' : 'a plain extension', codeDir: '', popupDir: '', manifestFile: 'manifest.json', wrapper: true, built: crxjs ? false : built, foundManifest: null,
|
|
107
|
-
appPopup: declared && declared !== 'toolaby-popup.html' ? declared : null, worker: named && has(named) ? named : null, createWorker: null,
|
|
138
|
+
appPopup: declared && declared !== 'toolaby-popup.html' ? declared : null, appSidePanel: declaredPanel && declaredPanel !== 'toolaby-sidepanel.html' ? declaredPanel : null, worker: named && has(named) ? named : null, createWorker: null,
|
|
108
139
|
workerLines: `import { toolaby } from '${importPath(workerFile)}';\n\ntoolaby.startBackground();\n`, snippets: [],
|
|
109
140
|
};
|
|
110
141
|
}
|
|
111
142
|
// Nothing recognised. A manifest below the root — src/, public/, a package of a monorepo — is where the wiring should have
|
|
112
143
|
// been pointed, and is said; the shallowest one, since a fixture or a dependency may carry another.
|
|
113
144
|
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;
|
|
114
|
-
return { kind: 'unknown', label: 'unknown', codeDir: '', popupDir: '', manifestFile: null, wrapper: false, appPopup: null, worker: null, createWorker: null, workerLines: '', snippets: [], built, foundManifest };
|
|
145
|
+
return { kind: 'unknown', label: 'unknown', codeDir: '', popupDir: '', manifestFile: null, wrapper: false, appPopup: null, appSidePanel: null, worker: null, createWorker: null, workerLines: '', snippets: [], built, foundManifest };
|
|
115
146
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolaby",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.4",
|
|
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": {
|