toolaby 1.0.1 → 1.0.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/README.md +4 -3
- package/bin.mjs +14 -7
- package/lib/project.js +15 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
The Toolaby command line. It puts the Wall — sign-in, a Free plan, paid plans, licences and subscriptions — into a Chrome extension you already have, or makes a new one for you.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
npm i -g toolaby # once (or run every command through `npx -y toolaby …`)
|
|
7
|
+
toolaby login # once: a code to approve in your browser
|
|
8
|
+
toolaby wire <tool-id> # in your extension's folder; `toolaby tools` lists your ids
|
|
8
9
|
```
|
|
9
10
|
|
|
10
11
|
## What it does
|
|
11
12
|
|
|
12
|
-
**`toolaby login`** signs you in. It shows a code and opens the Toolaby dashboard; you approve the code there
|
|
13
|
+
**`toolaby login`** signs you in. It shows a code and opens the Toolaby dashboard; you approve the code there — as the account the dashboard is signed in to — and the command is signed in as that account from then on. It works the way GitHub's and Stripe's command lines sign in, and only ever acts on that account's own tools.
|
|
13
14
|
|
|
14
15
|
**`toolaby wire <tool-id>`**, run in your extension's folder, wires that extension to your tool:
|
|
15
16
|
|
package/bin.mjs
CHANGED
|
@@ -22,9 +22,9 @@ import { join, resolve, dirname } from 'node:path';
|
|
|
22
22
|
import { homedir } from 'node:os';
|
|
23
23
|
import { spawn } from 'node:child_process';
|
|
24
24
|
import { wireManifest, WORKER_LINES } from './lib/manifest.js';
|
|
25
|
-
import { detectProject, CODE_FILES, filesToWrite } from './lib/project.js';
|
|
25
|
+
import { detectProject, CODE_FILES, filesToWrite, BUILD_COPY_NOTE } from './lib/project.js';
|
|
26
26
|
|
|
27
|
-
const VERSION = '1.0.
|
|
27
|
+
const VERSION = '1.0.3';
|
|
28
28
|
const CLIENT_ID = 'toolaby-cli';
|
|
29
29
|
const argv = process.argv.slice(2);
|
|
30
30
|
// `--version` and `--help` are commands of their own; every other command is the first bare word.
|
|
@@ -88,7 +88,7 @@ async function login() {
|
|
|
88
88
|
const all = readCredentials();
|
|
89
89
|
all[wall] = { token, email, at: new Date().toISOString() };
|
|
90
90
|
writeCredentials(all);
|
|
91
|
-
out(`\n\n Signed in${email ? ` as ${email}` : ''}
|
|
91
|
+
out(`\n\n Signed in${email ? ` as ${email}` : ''}.\n`);
|
|
92
92
|
return token;
|
|
93
93
|
}
|
|
94
94
|
const why = poll.json.error;
|
|
@@ -159,7 +159,12 @@ async function filesFor(slug, scaffold) {
|
|
|
159
159
|
const token = await tokenOrLogin();
|
|
160
160
|
const res = await api(`/api/cli/wiring/${slug}${scaffold ? '?scaffold=1' : ''}`, { token });
|
|
161
161
|
if (res.status === 401) { out(' The stored session is no longer valid; signing in again.'); await login(); return filesFor(slug, scaffold); }
|
|
162
|
-
if (res.status === 404)
|
|
162
|
+
if (res.status === 404) {
|
|
163
|
+
// Say who the command is: the tool may well exist — in a workspace of another account of theirs.
|
|
164
|
+
const me = await api('/api/cli/me', { token }).catch(() => null);
|
|
165
|
+
const who = me?.ok ? `\n\n You are signed in as ${me.json.email}${me.json.workspaces.length ? ` (workspaces: ${me.json.workspaces.map((w) => w.slug).join(', ')})` : ' (no workspace yet)'}.\n Another account owns it? \`toolaby logout\`, sign in to the dashboard as that account, then run this again.` : '';
|
|
166
|
+
fail(`${res.json.message ?? `No tool "${slug}" in your workspaces.`}${who}`);
|
|
167
|
+
}
|
|
163
168
|
if (!res.ok) fail(res.json.message ?? `The Wall answered HTTP ${res.status}.`);
|
|
164
169
|
return res.json;
|
|
165
170
|
}
|
|
@@ -176,8 +181,9 @@ async function wire() {
|
|
|
176
181
|
const plan = await filesFor(slug, false);
|
|
177
182
|
const site = plan.site;
|
|
178
183
|
const configSource = ['manifest.config.ts', 'manifest.config.js', 'manifest.ts', 'src/manifest.ts'].find(has);
|
|
179
|
-
const project = detectProject({ has, packageJson: has('package.json') ? readJson(join(dir, 'package.json')) : null, manifest, manifestSource: configSource ? readFileSync(join(dir, configSource), 'utf8') : null, site });
|
|
180
|
-
|
|
184
|
+
const project = detectProject({ has, packageJson: has('package.json') ? readJson(join(dir, 'package.json')) : null, manifest, manifestSource: configSource ? readFileSync(join(dir, configSource), 'utf8') : null, site, tree });
|
|
185
|
+
// 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.
|
|
186
|
+
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.`);
|
|
181
187
|
|
|
182
188
|
// The extension's own popup: the manifest's, or — wired before — the one the existing configuration remembers.
|
|
183
189
|
const configPath = join(dir, `${project.codeDir}toolaby.config.js`);
|
|
@@ -207,7 +213,8 @@ async function wire() {
|
|
|
207
213
|
out(` Written: ${targets.join(', ')}${did.length ? `,\n ${did.join(',\n ')}` : ''}.\n`);
|
|
208
214
|
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`);
|
|
209
215
|
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`);
|
|
210
|
-
out(`
|
|
216
|
+
if (project.built) out(` ${BUILD_COPY_NOTE}\n`);
|
|
217
|
+
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.`);
|
|
211
218
|
if (project.note) out(` ${project.note}\n`);
|
|
212
219
|
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`);
|
|
213
220
|
else out(` No popup to stand in front of: count a use where your feature runs — the Set up page shows the line.\n`);
|
package/lib/project.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// Generated from wall/src/packages/wiring/project.ts by wall/scripts/pack-create.mjs — edit there.
|
|
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.';
|
|
2
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. */
|
|
3
5
|
export const CODE_FILES = ['toolaby.config.js', 'toolaby.js', 'toolaby-client.module.js', 'toolaby.d.ts', 'paywall.js', 'paywall.css'];
|
|
4
6
|
/** 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. */
|
|
@@ -19,6 +21,8 @@ const dep = (pkg, name) => !!(pkg?.dependencies?.[name] || pkg?.devDependencies?
|
|
|
19
21
|
const first = (has, candidates) => candidates.find(has) ?? null;
|
|
20
22
|
export function detectProject(o) {
|
|
21
23
|
const { has, packageJson: pkg, manifest, site } = o;
|
|
24
|
+
const scripts = pkg?.scripts ?? {};
|
|
25
|
+
const built = !!scripts.build || has('dist') || has('build');
|
|
22
26
|
const wxt = dep(pkg, 'wxt') || !!first(has, ['wxt.config.ts', 'wxt.config.js', 'wxt.config.mts', 'wxt.config.mjs']);
|
|
23
27
|
const plasmo = dep(pkg, 'plasmo');
|
|
24
28
|
const crxjs = dep(pkg, '@crxjs/vite-plugin');
|
|
@@ -49,7 +53,7 @@ export function detectProject(o) {
|
|
|
49
53
|
`});`,
|
|
50
54
|
].join('\n');
|
|
51
55
|
return {
|
|
52
|
-
kind: 'wxt', label: 'WXT', codeDir: '', popupDir: `${src}public/`, manifestFile: null, wrapper: true, appPopup, worker,
|
|
56
|
+
kind: 'wxt', label: 'WXT', codeDir: '', popupDir: `${src}public/`, manifestFile: null, wrapper: true, appPopup, worker, built: false, foundManifest: null,
|
|
53
57
|
createWorker: worker ? null : { file: workerFile, content: workerLines },
|
|
54
58
|
workerLines, snippets: [{ file: first(has, ['wxt.config.ts', 'wxt.config.js', 'wxt.config.mts', 'wxt.config.mjs']) ?? 'wxt.config.ts', language: 'ts', code }],
|
|
55
59
|
};
|
|
@@ -64,10 +68,10 @@ export function detectProject(o) {
|
|
|
64
68
|
const workerLines = `import { toolaby } from '${importPath(workerFile)}';\n\ntoolaby.startBackground();\n`;
|
|
65
69
|
const code = JSON.stringify({ manifest: { permissions: ['storage'], host_permissions: [`${site}/*`], externally_connectable: { matches: [`${site}/*`] } } }, null, 2).replace(/^\{\n|\n\}$/g, '');
|
|
66
70
|
return {
|
|
67
|
-
kind: 'plasmo', label: 'Plasmo', codeDir: '', popupDir: '', manifestFile: null, wrapper: false, appPopup: null, worker,
|
|
71
|
+
kind: 'plasmo', label: 'Plasmo', codeDir: '', popupDir: '', manifestFile: null, wrapper: false, appPopup: null, worker, built: false, foundManifest: null,
|
|
68
72
|
createWorker: worker ? null : { file: workerFile, content: workerLines },
|
|
69
73
|
workerLines, snippets: [{ file: 'package.json', language: 'json', code }],
|
|
70
|
-
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 under
|
|
74
|
+
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.',
|
|
71
75
|
};
|
|
72
76
|
}
|
|
73
77
|
if (crxjs && !has('manifest.json')) {
|
|
@@ -96,21 +100,26 @@ export function detectProject(o) {
|
|
|
96
100
|
`});`,
|
|
97
101
|
].join('\n');
|
|
98
102
|
return {
|
|
99
|
-
kind: 'crxjs', label: 'CRXJS', codeDir: '', popupDir: '', manifestFile: null, wrapper: true, appPopup, worker: worker && has(worker) ? worker : null,
|
|
103
|
+
kind: 'crxjs', label: 'CRXJS', codeDir: '', popupDir: '', manifestFile: null, wrapper: true, appPopup, worker: worker && has(worker) ? worker : null, built: false, foundManifest: null,
|
|
100
104
|
createWorker: worker && has(worker) ? null : { file: workerFile, content: workerLines },
|
|
101
105
|
workerLines, snippets: [{ file: configFile ?? 'manifest.config.ts', language: 'ts', code }, ...(appPopup ? [{ file: viteFile, language: 'ts', code: vite }] : [])],
|
|
102
106
|
};
|
|
103
107
|
}
|
|
104
108
|
if (has('manifest.json')) {
|
|
105
109
|
// A plain extension, or CRXJS with a real manifest.json: the manifest is edited (wireManifest), everything at the root.
|
|
110
|
+
// A plain extension with a build (webpack, Rollup, Parcel, esbuild…) is wired the same way — the manifest is a file and
|
|
111
|
+
// the files go beside it — but only what the build copies reaches the output, so that is said.
|
|
106
112
|
const declared = typeof manifest?.action?.default_popup === 'string' ? manifest.action.default_popup : null;
|
|
107
113
|
const named = typeof manifest?.background?.service_worker === 'string' ? manifest.background.service_worker : null;
|
|
108
114
|
const workerFile = named && has(named) ? named : 'background.js';
|
|
109
115
|
return {
|
|
110
|
-
kind: crxjs ? 'crxjs' : 'plain', label: crxjs ? 'CRXJS' : 'a plain extension', codeDir: '', popupDir: '', manifestFile: 'manifest.json', wrapper: true,
|
|
116
|
+
kind: crxjs ? 'crxjs' : 'plain', label: crxjs ? 'CRXJS' : 'a plain extension', codeDir: '', popupDir: '', manifestFile: 'manifest.json', wrapper: true, built: crxjs ? false : built, foundManifest: null,
|
|
111
117
|
appPopup: declared && declared !== 'toolaby-popup.html' ? declared : null, worker: named && has(named) ? named : null, createWorker: null,
|
|
112
118
|
workerLines: `import { toolaby } from '${importPath(workerFile)}';\n\ntoolaby.startBackground();\n`, snippets: [],
|
|
113
119
|
};
|
|
114
120
|
}
|
|
115
|
-
|
|
121
|
+
// Nothing recognised. A manifest below the root — src/, public/, a package of a monorepo — is where the wiring should have
|
|
122
|
+
// been pointed, and is said; the shallowest one, since a fixture or a dependency may carry another.
|
|
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;
|
|
124
|
+
return { kind: 'unknown', label: 'unknown', codeDir: '', popupDir: '', manifestFile: null, wrapper: false, appPopup: null, worker: null, createWorker: null, workerLines: '', snippets: [], built, foundManifest };
|
|
116
125
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolaby",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.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": { "toolaby": "./bin.mjs" },
|