@medeo-plugin/cli 0.8.0 → 0.9.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/locate.mjs CHANGED
@@ -1,7 +1,5 @@
1
- // Where the CLI finds its own files. Installed from npm, the verifier and the
2
- // templates sit inside the package (`scripts/stage-cli.mjs` puts them there);
3
- // in this repository they are read from the checkout, so tests and the release
4
- // script need no staging step.
1
+ // Templates live beside the CLI in both source and published installations.
2
+ // The verifier is read from spec in the checkout, or the staged npm copy.
5
3
  import { createRequire } from 'node:module';
6
4
  import { existsSync, readFileSync } from 'node:fs';
7
5
  import path from 'node:path';
@@ -9,16 +7,17 @@ import { fileURLToPath } from 'node:url';
9
7
 
10
8
  const here = path.dirname(fileURLToPath(import.meta.url));
11
9
  const repo = path.resolve(here, '../..');
12
- const TEMPLATES = { vanilla: 'examples/i18n-demo', react: 'examples/react-demo' };
10
+ const TEMPLATES = ['vanilla', 'react'];
13
11
 
14
12
  export function verifierPath() {
15
13
  // In this repository the checkout's copy is the current one; a published package only has its own.
16
- for (const candidate of [path.join(repo, 'verifier/medeo-verify.wasm'), path.join(here, 'verifier/medeo-verify.wasm')]) if (existsSync(candidate)) return candidate;
14
+ for (const candidate of [path.join(repo, 'spec/medeo-verify.wasm'), path.join(here, 'verifier/medeo-verify.wasm')]) if (existsSync(candidate)) return candidate;
17
15
  throw new Error('The package verifier (verifier/medeo-verify.wasm) is missing from this installation');
18
16
  }
19
17
  export function templatePath(name) {
20
- if (!Object.hasOwn(TEMPLATES, name)) throw new Error(`Unknown template "${name}"; use ${Object.keys(TEMPLATES).join(' or ')}`);
21
- for (const candidate of [path.join(repo, TEMPLATES[name]), path.join(here, 'templates', name)]) if (existsSync(candidate)) return candidate;
18
+ if (!TEMPLATES.includes(name)) throw new Error(`Unknown template "${name}"; use ${TEMPLATES.join(' or ')}`);
19
+ const candidate = path.join(here, 'templates', name);
20
+ if (existsSync(candidate)) return candidate;
22
21
  throw new Error(`Template "${name}" is missing from this installation`);
23
22
  }
24
23
  const SDK_ENTRIES = { '@medeo-plugin/sdk': 'index', '@medeo-plugin/sdk/ui': 'ui', '@medeo-plugin/sdk/ui/react': 'react', '@medeo-plugin/sdk/rpc': 'rpc', '@medeo-plugin/sdk/testing': 'testing' };
package/pack.mjs CHANGED
@@ -13,7 +13,23 @@ const STORED=new Set(['png','jpg','jpeg','gif','webp','woff','woff2','mp3','mp4'
13
13
  // Mirrors platform_contract: archive and unpacked size, file count, index entry.
14
14
  export const LIMITS={size:256*1024*1024,files:4096},INDEX_ENTRY='_package.json';
15
15
  // Mirrors platform_core::ICON_PATHS / MAX_ICON / validate_icon.
16
- export const ICON_PATHS=['icon.svg','icon.png'],MAX_ICON=256*1024;const PNG=Buffer.from([0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a]);
16
+ export const ICON_PATHS=['icon.svg','icon.png'],MAX_ICON=256*1024;
17
+ // Mirrors platform_core::parse_about; Medeo drops an invalid field silently, so pack refuses it here.
18
+ export const ABOUT_FILE='about.json',MAX_ABOUT=16*1024;
19
+ export function checkAbout(text){
20
+ let value;try{value=JSON.parse(text)}catch{return ['not valid JSON']}
21
+ if(!value||typeof value!=='object'||Array.isArray(value))return ['must be a JSON object'];
22
+ const problems=[],plain=(v,max)=>typeof v==='string'&&v.trim()!==''&&[...v].length<=max&&!/[\u0000-\u001f\u007f-\u009f]/.test(v);
23
+ const link=v=>{if(typeof v!=='string'||v.length>2048||/[\s\u0000-\u001f]/.test(v)||!v.startsWith('https://'))return false;const host=v.slice(8).split(/[/?#]/)[0];return host!==''&&!host.includes('@')};
24
+ for(const key of Object.keys(value))if(!['author','license','homepage','repository','bugs'].includes(key))problems.push(`unknown field ${key}`);
25
+ if('author' in value&&!plain(value.author,80))problems.push('author must be one line of at most 80 characters');
26
+ if('license' in value&&!plain(value.license,64))problems.push('license must be one line of at most 64 characters');
27
+ for(const key of ['homepage','repository','bugs'])if(key in value&&!link(value[key]))problems.push(`${key} must be an https:// link`);
28
+ return problems;
29
+ }
30
+ export const README=/^README(\.[A-Za-z0-9_-]{1,16})?\.md$/,MAX_README=256*1024;
31
+ // Screenshots a README shows, under media/: images only, each ≤ 2 MiB and ≤ 8 MiB together.
32
+ export const MEDIA=/^media\/(?:[A-Za-z0-9_.-]+\/)*[A-Za-z0-9_.-]+\.(?:png|jpe?g|gif|webp)$/i,MAX_MEDIA=2*1024*1024,MAX_MEDIA_TOTAL=8*1024*1024;const PNG=Buffer.from([0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a]);
17
33
  const UI_ENTRIES=['ui/entry.ts','ui/entry.tsx'];
18
34
  function assetKind(file){const ext=file.split('.').pop().toLowerCase();return TEXT.has(ext)?'text':BINARY.has(ext)?'binary':UI_ENTRIES.includes(file)?'text':null}
19
35
  const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
@@ -78,6 +94,31 @@ export async function pack(directory,keyFile,output,options={}){
78
94
  if(manifest.icon==='icon.png'?!bytes.subarray(0,8).equals(PNG):!bytes.toString('utf8').includes('<svg'))throw Error('icon does not match its type: '+manifest.icon);
79
95
  files[manifest.icon]=bytes;
80
96
  }
97
+ // The introduction Medeo's plugin center shows: README.md and README.<locale>.md at the root.
98
+ for(const item of (await readdir(source)).sort()){
99
+ if(!README.test(item))continue;
100
+ const full=path.join(source,item),stat=await lstat(full);
101
+ if(stat.isSymbolicLink()||!stat.isFile()||stat.size>MAX_README)throw Error(`${item} must be a regular file of at most 256 KiB`);
102
+ files[item]=Buffer.from((await readFile(full,'utf8')).replace(/\r\n/g,'\n'));
103
+ }
104
+ // About: author, license and https links for the plugin detail. A separate file rather than
105
+ // manifest fields, so every Medeo installs the package (older ones simply ignore it).
106
+ const aboutFile=path.join(source,ABOUT_FILE),aboutStat=await lstat(aboutFile).catch(()=>null);
107
+ if(aboutStat){
108
+ if(aboutStat.isSymbolicLink()||!aboutStat.isFile()||aboutStat.size>MAX_ABOUT)throw Error(`${ABOUT_FILE} must be a regular file of at most 16 KiB`);
109
+ const text=(await readFile(aboutFile,'utf8')).replace(/\r\n/g,'\n');
110
+ const problems=checkAbout(text);if(problems.length)throw Error(`${ABOUT_FILE}: ${problems.join('; ')}`);
111
+ files[ABOUT_FILE]=Buffer.from(text);
112
+ }
113
+ if(await access(path.join(source,'media')).then(()=>true,()=>false)){
114
+ await collect(path.join(source,'media'),'media/');
115
+ let total=0;
116
+ for(const p of Object.keys(files).filter(p=>p.startsWith('media/'))){
117
+ if(!MEDIA.test(p))throw Error('media/ holds only png, jpg, gif or webp images: '+p);
118
+ if(files[p].length>MAX_MEDIA)throw Error(p+' is larger than 2 MiB');total+=files[p].length;
119
+ }
120
+ if(total>MAX_MEDIA_TOTAL)throw Error('media/ is larger than 8 MiB in total');
121
+ }
81
122
  if(node&&await access(path.join(source,'resources')).then(()=>true,()=>false))await collect(path.join(source,'resources'),'resources/');
82
123
  if(manifest.localization){
83
124
  for(const relative of Object.values(manifest.localization.catalogs??{})){
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@medeo-plugin/cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Command line for Medeo desktop plugins: scaffold, build, sign, check and live-reload a plugin in a running Medeo.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -33,6 +33,9 @@
33
33
  "!*.test.mjs",
34
34
  "verifier/",
35
35
  "templates/",
36
+ "!templates/**/tests/**",
37
+ "!templates/**/dist/**",
38
+ "!templates/**/node_modules/**",
36
39
  "README.md"
37
40
  ],
38
41
  "scripts": {
@@ -40,7 +43,7 @@
40
43
  "prepack": "node ../../scripts/stage-cli.mjs"
41
44
  },
42
45
  "dependencies": {
43
- "@medeo-plugin/sdk": "0.8.0",
46
+ "@medeo-plugin/sdk": "0.9.0",
44
47
  "esbuild": "^0.25.0"
45
48
  },
46
49
  "engines": {
@@ -1,4 +1,4 @@
1
1
  # React counter
2
2
 
3
- The `init --template react` template: the same counter as `i18n-demo`, with the window written in React.
3
+ The `init --template react` template: the same counter as the vanilla template, with the window written in React.
4
4
  `ui/entry.tsx` is the only compiled UI entry; components live in `src/ui/` so nothing unbuilt is packaged.
Binary file