toolaby 1.0.3 → 1.1.0

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
@@ -24,7 +24,7 @@ import { spawn } from 'node:child_process';
24
24
  import { wireManifest, WORKER_LINES } from './lib/manifest.js';
25
25
  import { detectProject, CODE_FILES, filesToWrite, BUILD_COPY_NOTE } from './lib/project.js';
26
26
 
27
- const VERSION = '1.0.3';
27
+ const VERSION = '1.1.0';
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.
@@ -155,10 +155,11 @@ const readJson = (path) => { try { return JSON.parse(readFileSync(path, 'utf8'))
155
155
  const isId = (s) => typeof s === 'string' && /^[a-z0-9][a-z0-9-]{1,118}$/.test(s);
156
156
 
157
157
  /** The tool's files from the Wall, as the signed-in developer: a tool of theirs, or a clear no. */
158
- async function filesFor(slug, scaffold) {
158
+ async function filesFor(slug, scaffold, manifestKey = null) {
159
159
  const token = await tokenOrLogin();
160
- const res = await api(`/api/cli/wiring/${slug}${scaffold ? '?scaffold=1' : ''}`, { token });
161
- if (res.status === 401) { out(' The stored session is no longer valid; signing in again.'); await login(); return filesFor(slug, scaffold); }
160
+ const q = new URLSearchParams(); if (scaffold) q.set('scaffold', '1'); if (manifestKey) q.set('manifestKey', manifestKey);
161
+ const res = await api(`/api/cli/wiring/${slug}${q.size ? `?${q}` : ''}`, { token });
162
+ if (res.status === 401) { out(' The stored session is no longer valid; signing in again.'); await login(); return filesFor(slug, scaffold, manifestKey); }
162
163
  if (res.status === 404) {
163
164
  // Say who the command is: the tool may well exist — in a workspace of another account of theirs.
164
165
  const me = await api('/api/cli/me', { token }).catch(() => null);
@@ -178,17 +179,19 @@ async function wire() {
178
179
  const has = (p) => tree.has(p.replace(/\/$/, ''));
179
180
  const manifest = has('manifest.json') ? readJson(join(dir, 'manifest.json')) : null;
180
181
  if (has('manifest.json') && !manifest) fail('manifest.json could not be read as JSON.');
181
- const plan = await filesFor(slug, false);
182
+ // A key the manifest already has is the identity the platform adopts; otherwise the platform's goes in.
183
+ const plan = await filesFor(slug, false, typeof manifest?.key === 'string' ? manifest.key : null);
182
184
  const site = plan.site;
185
+ const manifestKey = plan.identity?.manifestKey ?? null;
183
186
  const configSource = ['manifest.config.ts', 'manifest.config.js', 'manifest.ts', 'src/manifest.ts'].find(has);
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 });
187
+ 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, manifestKey });
185
188
  // 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
189
  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.`);
187
190
 
188
191
  // The extension's own popup: the manifest's, or — wired before — the one the existing configuration remembers.
189
192
  const configPath = join(dir, `${project.codeDir}toolaby.config.js`);
190
193
  const remembered = existsSync(configPath) ? readFileSync(configPath, 'utf8').match(/appPopup:\s*'([^']*)'/)?.[1] || null : null;
191
- const wiring = project.manifestFile && manifest ? wireManifest(manifest, { site, has, remembered }) : null;
194
+ const wiring = project.manifestFile && manifest ? wireManifest(manifest, { site, has, remembered, manifestKey }) : null;
192
195
  const appPopup = wiring ? wiring.appPopup : project.wrapper ? (project.appPopup ?? remembered) : null;
193
196
  const targets = filesToWrite(project, appPopup);
194
197
  const present = targets.filter((f) => existsSync(join(dir, f)));
package/lib/manifest.js CHANGED
@@ -6,15 +6,25 @@
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; and the Wall's popup in front of the extension's own, which
10
- * the configuration then remembers. Pure: given the manifest and what is
11
- * on disk, it says what to write.
9
+ * not there; the Wall's popup in front of the extension's own, which the
10
+ * configuration then remembers; and the extension's identity — the `key`
11
+ * that fixes its id, which the tool key is bound to (identity.ts) — unless
12
+ * the manifest has one of its own, which is then the identity the platform
13
+ * adopts. Pure: given the manifest and what is on disk, it says what to
14
+ * write.
12
15
  */
13
16
  export const WALL_POPUP = 'toolaby-popup.html';
14
17
  export const WORKER_LINES = "import { toolaby } from './toolaby.js';\n\ntoolaby.startBackground();\n";
15
18
  export function wireManifest(input, o) {
16
19
  const manifest = JSON.parse(JSON.stringify(input));
17
20
  const did = [];
21
+ // The identity: the manifest's own key stays (the platform adopts it); otherwise the platform's goes in, and the unpacked extension's id is known before it loads.
22
+ const own = typeof manifest.key === 'string' && manifest.key.trim() ? manifest.key.trim() : null;
23
+ if (!own && o.manifestKey) {
24
+ manifest.key = o.manifestKey;
25
+ did.push('a "key", so the extension keeps one id and the platform knows it');
26
+ }
27
+ const manifestKey = own ?? o.manifestKey ?? null;
18
28
  const add = (list, value) => { const arr = Array.isArray(list) ? [...list] : []; if (!arr.includes(value))
19
29
  arr.push(value); return arr; };
20
30
  if (!(Array.isArray(manifest.permissions) && manifest.permissions.includes('storage'))) {
@@ -48,5 +58,5 @@ export function wireManifest(input, o) {
48
58
  else {
49
59
  ownWorker = { file: named, classic: manifest.background?.type !== 'module' };
50
60
  }
51
- return { manifest, did, writeWorker, ownWorker, appPopup };
61
+ return { manifest, did, writeWorker, ownWorker, appPopup, manifestKey };
52
62
  }
package/lib/project.js CHANGED
@@ -21,6 +21,7 @@ const dep = (pkg, name) => !!(pkg?.dependencies?.[name] || pkg?.devDependencies?
21
21
  const first = (has, candidates) => candidates.find(has) ?? null;
22
22
  export function detectProject(o) {
23
23
  const { has, packageJson: pkg, manifest, site } = o;
24
+ const keyLine = o.manifestKey ? `key: '${o.manifestKey}',` : null;
24
25
  const scripts = pkg?.scripts ?? {};
25
26
  const built = !!scripts.build || has('dist') || has('build');
26
27
  const wxt = dep(pkg, 'wxt') || !!first(has, ['wxt.config.ts', 'wxt.config.js', 'wxt.config.mts', 'wxt.config.mjs']);
@@ -38,6 +39,7 @@ export function detectProject(o) {
38
39
  `// wxt.config.ts — what the Wall needs in the manifest${appPopup ? ', and its popup in front of yours' : ''}`,
39
40
  `export default defineConfig({`,
40
41
  ` manifest: {`,
42
+ ...(keyLine ? [` ${keyLine} // the extension's id, known to the platform`] : []),
41
43
  ` permissions: ['storage'],`,
42
44
  ` host_permissions: ['${site}/*'],`,
43
45
  ` externally_connectable: { matches: ['${site}/*'] },`,
@@ -66,7 +68,7 @@ export function detectProject(o) {
66
68
  const worker = first(has, [`${src}background.ts`, `${src}background.js`, `${src}background/index.ts`, `${src}background/index.js`]);
67
69
  const workerFile = worker ?? `${src}background.ts`;
68
70
  const workerLines = `import { toolaby } from '${importPath(workerFile)}';\n\ntoolaby.startBackground();\n`;
69
- const code = JSON.stringify({ manifest: { permissions: ['storage'], host_permissions: [`${site}/*`], externally_connectable: { matches: [`${site}/*`] } } }, null, 2).replace(/^\{\n|\n\}$/g, '');
71
+ 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, '');
70
72
  return {
71
73
  kind: 'plasmo', label: 'Plasmo', codeDir: '', popupDir: '', manifestFile: null, wrapper: false, appPopup: null, worker, built: false, foundManifest: null,
72
74
  createWorker: worker ? null : { file: workerFile, content: workerLines },
@@ -84,6 +86,7 @@ export function detectProject(o) {
84
86
  const workerLines = `import { toolaby } from '${importPath(workerFile)}';\n\ntoolaby.startBackground();\n`;
85
87
  const code = [
86
88
  `// ${configFile ?? 'manifest.config.ts'} — add inside defineManifest({ … })`,
89
+ ...(keyLine && !/\bkey:/.test(source) ? [`${keyLine} // the extension's id, known to the platform`] : []),
87
90
  `permissions: ['storage'],`,
88
91
  `host_permissions: ['${site}/*'],`,
89
92
  `externally_connectable: { matches: ['${site}/*'] },`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolaby",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
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" },