@webority/canvas-kit 0.90.0 → 0.91.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/README.md CHANGED
@@ -33,6 +33,7 @@ The config is a JSON object with these keys. Paths are relative to the config fi
33
33
  | `components` | A glob of `.meta.js` files, for example `src/components/*.meta.js`. |
34
34
  | `tokens` | The path of a `webority-theme-tokens/2` file: `webority-web.json` for Web, `webority-phone.json` for Phone, written by `webority brand`. |
35
35
  | `rolesFile` | Optional. A JSON object from component name to `title` or `navigation`. |
36
+ | `styles` | Optional. Style files bundled into `kit.css` ahead of the component styles, for a product whose components take their look from styles imported at the app entry. |
36
37
  | `rendererProtocolVersion` | The renderer protocol number the kit is built for. |
37
38
 
38
39
  A kit lists its components with `components`. A config that names `entry` fails the read with that message, and so does
@@ -125,7 +126,7 @@ The build stops with a named fix when:
125
126
  - a meta lists a platform other than the kit's;
126
127
  - the config names `entry`, or lacks `components` or `tokens`;
127
128
  - the tokens file is not format `webority-theme-tokens/2`, is for the other platform, or has light and dark colours that differ;
128
- - a colour has no meaning in `@webority/tokens` `dist/meanings.json`;
129
+ - a colour has no meaning in the tokens file's `colorMeanings` (where a brand's own colours carry theirs) nor in `@webority/tokens` `dist/meanings.json`, or a `colorMeanings` entry is not a description;
129
130
  - a meta does not match its component's typed props, or fails one of the checks on a meta;
130
131
  - a Phone kit is built without `--phone-renderer-support`.
131
132
 
package/dist/build.js CHANGED
@@ -1,3 +1,3 @@
1
- import{findPackageDir as e,readJson as t,toPosix as n}from"./files.js";import{KIT_MODULE_EXTERNALS as r}from"./kitModule.js";import{libraryFiles as i,readManifest as a}from"./manifest.js";import{digest as o,writeReleaseFolder as s}from"./release.js";import{existsSync as c}from"node:fs";import{dirname as l,join as u,resolve as d}from"node:path";import{fileURLToPath as f}from"node:url";import{build as p}from"vite";var m=19,h=[`react-redux`,`react-router-dom`,`axios`],g=[`kit`,`platform`,`libraryPackage`,`components`,`tokens`,`rolesFile`,`rendererProtocolVersion`],_=[`kit`,`platform`,`libraryPackage`,`components`,`tokens`,`rendererProtocolVersion`],v=`canvas-kit:entry`,y=`\0${v}`,b=u(l(f(import.meta.url)),`kitModule.js`),x={"process.env.NODE_ENV":JSON.stringify(`production`),"process.env":`{}`,__DEV__:`false`},S=/(?<![\w$."'`])(process\.env|__DEV__)(?![\w$])/;function C(e){let n=t(e);if(typeof n!=`object`||!n||Array.isArray(n))throw Error(`${e} must be a JSON object.`);if(n.entry!==void 0)throw Error(`${e}: A kit lists its components with components, a glob of .meta.js files; entry is retired.`);for(let t of Object.keys(n))if(!g.includes(t))throw Error(`${e} has the unknown key ${t}. The keys are ${g.join(`, `)}.`);for(let t of _)if(n[t]===void 0)throw Error(`${e} needs the key ${t}.`);for(let t of[`kit`,`platform`,`libraryPackage`,`components`,`tokens`])if(typeof n[t]!=`string`)throw Error(`${e}: ${t} must be a string.`);for(let t of[`rolesFile`])if(n[t]!==void 0&&typeof n[t]!=`string`)throw Error(`${e}: ${t} must be a string path.`);return n}function w(n){let r=e(n,`react`);if(!r)throw Error(`react is not installed for ${n}. Run pnpm install there.`);let{version:i}=t(u(r,`package.json`));if(Number(String(i).split(`.`)[0])!==m)throw Error(`The product uses React ${i}; the Canvas renderer runs React ${m}. Pin react to ${m}.x and rebuild.`)}function T(e){let{version:n}=t(u(e,`package.json`));if(!n)throw Error(`${u(e,`package.json`)} needs a version; the release is stamped with it.`);return n}function E({libraryFile:e,chromeFile:t,manifest:r,libraryNames:i,chromeNames:a,productModules:o}){let s=[`import { defineKit } from ${JSON.stringify(n(b))};`],c=[...o];c.forEach(([,{module:e,exportName:t}],r)=>{s.push(`import { ${t} as product${r} } from ${JSON.stringify(n(e))};`)}),i.length>0&&s.push(`import { ${i.join(`, `)} } from ${JSON.stringify(n(e))};`),a.length>0&&s.push(`import { ${a.join(`, `)} } from ${JSON.stringify(n(t))};`);let l=[...i,...a,...c.map(([e],t)=>`${e}: product${t}`)],u=Object.fromEntries(r.components.filter(({marker:e})=>e!==null).map(({name:e,marker:t})=>[e,t]));return s.push(`export default defineKit({ components: { ${l.join(`, `)} }, markers: ${JSON.stringify(u)}, tokenValues: ${JSON.stringify(r.tokenValues)}, manifest: ${JSON.stringify(r)} });`,``),s.join(`
2
- `)}function D(e){return{name:`canvas-kit-entry`,resolveId(e){return e===v?y:null},load(t){return t===y?e:null}}}function O(e){let t=`${n(u(e,`src`,`services`))}/`;return{name:`canvas-kit-banned-imports`,transform(e,r){let i=n(r.split(`?`)[0]),a=h.find(e=>i.includes(`/node_modules/${e}/`));if(a)throw Error(`The kit imports ${a} (${i}). A kit draws from its props only; the container that renders it holds the state and the calls.`);if(i.startsWith(t))throw Error(`The kit imports ${i}, which is in src/services. A kit draws from its props only; move the call into the container.`);return null}}}async function k({root:e,kit:t,platform:n,libraryFile:i,chromeFile:a,libraryNames:o,chromeNames:s,productModules:c,manifest:l}){let u=r[n],d=await p({configFile:!1,root:e,logLevel:`warn`,publicDir:!1,envDir:!1,define:x,plugins:[D(E({libraryFile:i,chromeFile:a,manifest:l,libraryNames:o,chromeNames:s,productModules:c})),O(e)],esbuild:{jsx:`automatic`,jsxImportSource:`react`,include:/\.[cm]?[jt]sx?$/},build:{write:!1,minify:!0,target:`es2022`,cssCodeSplit:!1,lib:{entry:v,formats:[`es`],fileName:()=>`kit.js`},rollupOptions:{input:v,external:e=>u.some(t=>e===t||e.startsWith(`${t}/`)),output:{inlineDynamicImports:!0}}}}),f=(Array.isArray(d)?d:[d]).flatMap(e=>e.output),m=f.find(e=>e.type===`chunk`&&e.isEntry);if(!m)throw Error(`The bundle of ${t} has no entry module.`);let h=S.exec(m.code);if(h)throw Error(`The bundle of ${t} still reads ${h[1]}, which the renderer does not define.`);let g=f.filter(e=>e.type===`chunk`&&!e.isEntry);if(g.length>0)throw Error(`The bundle of ${t} split ${g.map(e=>e.fileName).join(`, `)} out of kit.js; a kit must be one file.`);let _=f.filter(e=>e.type===`asset`&&e.fileName.endsWith(`.css`)).map(e=>typeof e.source==`string`?e.source:Buffer.from(e.source).toString(`utf8`)).join(`
3
- `);return{script:m.code,styles:_}}async function A({configFile:e,outDir:t,styleKeysFile:n,phoneRendererSupportFile:r}){let u=d(e),f=d(t);if(c(f))throw Error(`${f} already exists. A release folder is never overwritten; choose a new folder.`);let p=C(u),m=l(u);if(p.platform!==`Phone`&&r!==void 0)throw Error(`Only a Phone kit reads --phone-renderer-support; a Web kit takes no phone support file.`);w(m);let h=p.rolesFile===void 0?void 0:d(m,p.rolesFile),{manifest:g,warnings:_,phoneChromeNames:v,productModules:y}=await a({projectRoot:m,kit:p.kit,platform:p.platform,components:p.components,tokens:p.tokens,libraryPackage:p.libraryPackage,rolesFile:h,rendererProtocolVersion:p.rendererProtocolVersion,phoneRendererSupportFile:r===void 0?void 0:d(r),styleKeysFile:n===void 0?void 0:d(n)}),b=i(m,p.libraryPackage),x=T(m),S=g.components.filter(({source:e,name:t})=>e===p.libraryPackage&&!v.includes(t)).map(({name:e})=>e),{script:E,styles:D}=await k({root:m,kit:p.kit,platform:p.platform,libraryFile:b.entryFile,chromeFile:b.canvasKitFile,libraryNames:S,chromeNames:v,productModules:y,manifest:g}),O={"manifest.json":`${JSON.stringify(g,null,2)}\n`,"kit.js":E,"kit.css":D},A={name:p.kit,version:x,platform:p.platform,kitModuleVersion:2,rendererProtocolVersion:p.rendererProtocolVersion,library:g.library,files:Object.fromEntries(Object.entries(O).map(([e,t])=>[e,o(t)]))};return s(f,O,A),{outDir:f,release:A,manifest:g,warnings:_}}export{A as buildKitRelease,C as readConfig};
1
+ import{findPackageDir as e,readJson as t,toPosix as n}from"./files.js";import{KIT_MODULE_EXTERNALS as r}from"./kitModule.js";import{libraryFiles as i,readManifest as a}from"./manifest.js";import{digest as o,writeReleaseFolder as s}from"./release.js";import{existsSync as c}from"node:fs";import{dirname as l,join as u,resolve as d}from"node:path";import{fileURLToPath as f}from"node:url";import{build as p}from"vite";var m=19,h=[`react-redux`,`react-router-dom`,`axios`],g=[`kit`,`platform`,`libraryPackage`,`components`,`tokens`,`rolesFile`,`styles`,`rendererProtocolVersion`],_=[`kit`,`platform`,`libraryPackage`,`components`,`tokens`,`rendererProtocolVersion`],v=`canvas-kit:entry`,y=`\0${v}`,b=u(l(f(import.meta.url)),`kitModule.js`),x={"process.env.NODE_ENV":JSON.stringify(`production`),"process.env":`{}`,__DEV__:`false`},S=/(?<![\w$."'`])(process\.env|__DEV__)(?![\w$])/;function C(e){let n=t(e);if(typeof n!=`object`||!n||Array.isArray(n))throw Error(`${e} must be a JSON object.`);if(n.entry!==void 0)throw Error(`${e}: A kit lists its components with components, a glob of .meta.js files; entry is retired.`);for(let t of Object.keys(n))if(!g.includes(t))throw Error(`${e} has the unknown key ${t}. The keys are ${g.join(`, `)}.`);for(let t of _)if(n[t]===void 0)throw Error(`${e} needs the key ${t}.`);for(let t of[`kit`,`platform`,`libraryPackage`,`components`,`tokens`])if(typeof n[t]!=`string`)throw Error(`${e}: ${t} must be a string.`);for(let t of[`rolesFile`])if(n[t]!==void 0&&typeof n[t]!=`string`)throw Error(`${e}: ${t} must be a string path.`);if(n.styles!==void 0){if(!Array.isArray(n.styles)||n.styles.some(e=>typeof e!=`string`||e===``))throw Error(`${e}: styles must be a list of style file paths.`);for(let t of n.styles){if(!/\.(css|scss)$/.test(t))throw Error(`${e}: styles names ${t}, which is not a .css or .scss file.`);if(!c(d(l(e),t)))throw Error(`${e}: styles names ${t}, which does not exist.`)}}return n}function w(n){let r=e(n,`react`);if(!r)throw Error(`react is not installed for ${n}. Run pnpm install there.`);let{version:i}=t(u(r,`package.json`));if(Number(String(i).split(`.`)[0])!==m)throw Error(`The product uses React ${i}; the Canvas renderer runs React ${m}. Pin react to ${m}.x and rebuild.`)}function T(e){let{version:n}=t(u(e,`package.json`));if(!n)throw Error(`${u(e,`package.json`)} needs a version; the release is stamped with it.`);return n}function E({styleFiles:e,libraryFile:t,chromeFile:r,manifest:i,libraryNames:a,chromeNames:o,productModules:s}){let c=e.map(e=>`import ${JSON.stringify(n(e))};`);c.push(`import { defineKit } from ${JSON.stringify(n(b))};`);let l=[...s];l.forEach(([,{module:e,exportName:t}],r)=>{c.push(`import { ${t} as product${r} } from ${JSON.stringify(n(e))};`)}),a.length>0&&c.push(`import { ${a.join(`, `)} } from ${JSON.stringify(n(t))};`),o.length>0&&c.push(`import { ${o.join(`, `)} } from ${JSON.stringify(n(r))};`);let u=[...a,...o,...l.map(([e],t)=>`${e}: product${t}`)],d=Object.fromEntries(i.components.filter(({marker:e})=>e!==null).map(({name:e,marker:t})=>[e,t]));return c.push(`export default defineKit({ components: { ${u.join(`, `)} }, markers: ${JSON.stringify(d)}, tokenValues: ${JSON.stringify(i.tokenValues)}, manifest: ${JSON.stringify(i)} });`,``),c.join(`
2
+ `)}function D(e){return{name:`canvas-kit-entry`,resolveId(e){return e===v?y:null},load(t){return t===y?e:null}}}function O(e){let t=`${n(u(e,`src`,`services`))}/`;return{name:`canvas-kit-banned-imports`,transform(e,r){let i=n(r.split(`?`)[0]),a=h.find(e=>i.includes(`/node_modules/${e}/`));if(a)throw Error(`The kit imports ${a} (${i}). A kit draws from its props only; the container that renders it holds the state and the calls.`);if(i.startsWith(t))throw Error(`The kit imports ${i}, which is in src/services. A kit draws from its props only; move the call into the container.`);return null}}}async function k({root:e,kit:t,platform:n,styleFiles:i,libraryFile:a,chromeFile:o,libraryNames:s,chromeNames:c,productModules:l,manifest:u}){let d=r[n],f=await p({configFile:!1,root:e,logLevel:`warn`,publicDir:!1,envDir:!1,define:x,plugins:[D(E({styleFiles:i,libraryFile:a,chromeFile:o,manifest:u,libraryNames:s,chromeNames:c,productModules:l})),O(e)],esbuild:{jsx:`automatic`,jsxImportSource:`react`,include:/\.[cm]?[jt]sx?$/},build:{write:!1,minify:!0,target:`es2022`,cssCodeSplit:!1,lib:{entry:v,formats:[`es`],fileName:()=>`kit.js`},rollupOptions:{input:v,external:e=>d.some(t=>e===t||e.startsWith(`${t}/`)),output:{inlineDynamicImports:!0}}}}),m=(Array.isArray(f)?f:[f]).flatMap(e=>e.output),h=m.find(e=>e.type===`chunk`&&e.isEntry);if(!h)throw Error(`The bundle of ${t} has no entry module.`);let g=S.exec(h.code);if(g)throw Error(`The bundle of ${t} still reads ${g[1]}, which the renderer does not define.`);let _=m.filter(e=>e.type===`chunk`&&!e.isEntry);if(_.length>0)throw Error(`The bundle of ${t} split ${_.map(e=>e.fileName).join(`, `)} out of kit.js; a kit must be one file.`);let y=m.filter(e=>e.type===`asset`&&e.fileName.endsWith(`.css`)).map(e=>typeof e.source==`string`?e.source:Buffer.from(e.source).toString(`utf8`)).join(`
3
+ `);return{script:h.code,styles:y}}async function A({configFile:e,outDir:t,styleKeysFile:n,phoneRendererSupportFile:r}){let u=d(e),f=d(t);if(c(f))throw Error(`${f} already exists. A release folder is never overwritten; choose a new folder.`);let p=C(u),m=l(u);if(p.platform!==`Phone`&&r!==void 0)throw Error(`Only a Phone kit reads --phone-renderer-support; a Web kit takes no phone support file.`);w(m);let h=p.rolesFile===void 0?void 0:d(m,p.rolesFile),{manifest:g,warnings:_,phoneChromeNames:v,productModules:y}=await a({projectRoot:m,kit:p.kit,platform:p.platform,components:p.components,tokens:p.tokens,libraryPackage:p.libraryPackage,rolesFile:h,rendererProtocolVersion:p.rendererProtocolVersion,phoneRendererSupportFile:r===void 0?void 0:d(r),styleKeysFile:n===void 0?void 0:d(n)}),b=i(m,p.libraryPackage),x=T(m),S=g.components.filter(({source:e,name:t})=>e===p.libraryPackage&&!v.includes(t)).map(({name:e})=>e),{script:E,styles:D}=await k({root:m,kit:p.kit,platform:p.platform,styleFiles:(p.styles??[]).map(e=>d(m,e)),libraryFile:b.entryFile,chromeFile:b.canvasKitFile,libraryNames:S,chromeNames:v,productModules:y,manifest:g}),O={"manifest.json":`${JSON.stringify(g,null,2)}\n`,"kit.js":E,"kit.css":D},A={name:p.kit,version:x,platform:p.platform,kitModuleVersion:2,rendererProtocolVersion:p.rendererProtocolVersion,library:g.library,files:Object.fromEntries(Object.entries(O).map(([e,t])=>[e,o(t)]))};return s(f,O,A),{outDir:f,release:A,manifest:g,warnings:_}}export{A as buildKitRelease,C as readConfig};
package/dist/cli.js CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import{buildKitRelease as e}from"./build.js";import{writeGuidance as t}from"./guidance.js";import{resolve as n}from"node:path";var r=`Usage: canvas-kit build --config <file> --out <dir> [--style-keys <file>] [--phone-renderer-support <file>]
2
+ import{buildKitRelease as e}from"./build.js";import{writeGuidance as t}from"./guidance.js";import{proposeCanvasKeys as n}from"./ids.js";import{resolve as r}from"node:path";var i=`Usage: canvas-kit build --config <file> --out <dir> [--style-keys <file>] [--phone-renderer-support <file>]
3
3
  canvas-kit guidance --config <file> --out <file>
4
+ canvas-kit ids <folder> [--write] proposes Canvas keys for list items, named blocks and conditional siblings; --write writes them.
4
5
 
5
6
  build: builds a kit release into <dir>: kit.js, kit.css, manifest.json and release.json.
6
7
  <dir> must not exist; a release folder is never overwritten.
7
8
  --style-keys: the style key table the renderer draws from. Optional.
8
9
  --phone-renderer-support: the phone renderer's support file. A Phone product kit needs it; a phone library's own kit release builds without it.
9
10
  guidance: writes the usage guidance of every component the config's components glob lists to <file> (webority-guidance/1), replacing it.
10
- Flag paths are relative to the working directory; the paths in the config are relative to the config file.`,i=new Set([`--config`,`--out`,`--style-keys`,`--phone-renderer-support`]);function a(e){if(e.length===0||e.includes(`--help`)||e.includes(`-h`))return{help:!0};let[t,...n]=e,r={};for(let e=0;e<n.length;e+=1){let t=n[e];if(!i.has(t))throw Error(`Unknown argument ${t}.`);let a=n[e+1];if(a===void 0||a.startsWith(`--`))throw Error(`${t} needs a value.`);r[t.slice(2)]=a,e+=1}return{command:t,options:r}}async function o(i){let o=a(i);if(o.help){process.stdout.write(`${r}\n`);return}if(o.command!==`build`&&o.command!==`guidance`)throw Error(`Unknown command ${o.command}. The commands are build and guidance.`);if(o.command===`guidance`){if(!o.options.config||!o.options.out)throw Error(`guidance needs --config and --out.`);if(o.options[`style-keys`]!==void 0||o.options[`phone-renderer-support`]!==void 0)throw Error(`guidance takes only --config and --out.`);let e=await t({configFile:n(o.options.config),outFile:n(o.options.out)});process.stdout.write(`Guidance for ${e.components.length} components written to ${n(o.options.out)}.\n`);return}if(!o.options.config||!o.options.out)throw Error(`build needs --config and --out.`);let s=o.options[`style-keys`],c=o.options[`phone-renderer-support`],l=await e({configFile:n(o.options.config),outDir:n(o.options.out),styleKeysFile:s===void 0?void 0:n(s),phoneRendererSupportFile:c===void 0?void 0:n(c)});for(let e of l.warnings)process.stderr.write(`warning: ${e}\n`);process.stdout.write(`Built ${l.release.name} (${l.release.platform}): ${l.manifest.components.length} components. Release written to ${l.outDir}.\n`)}o(process.argv.slice(2)).catch(e=>{process.stderr.write(`canvas-kit: ${e.message}\n`),process.exitCode=1});export{};
11
+ Flag paths are relative to the working directory; the paths in the config are relative to the config file.`,a=new Set([`--config`,`--out`,`--style-keys`,`--phone-renderer-support`]);function o(e){if(e.length===0||e.includes(`--help`)||e.includes(`-h`))return{help:!0};let[t,...n]=e;if(t===`ids`){let e,r=!1;for(let t of n)if(t===`--write`)r=!0;else if(e===void 0&&!t.startsWith(`--`))e=t;else throw Error(`Unknown argument ${t}.`);return{command:t,options:{folder:e,write:r}}}let r={};for(let e=0;e<n.length;e+=1){let t=n[e];if(!a.has(t))throw Error(`Unknown argument ${t}.`);let i=n[e+1];if(i===void 0||i.startsWith(`--`))throw Error(`${t} needs a value.`);r[t.slice(2)]=i,e+=1}return{command:t,options:r}}async function s(a){let s=o(a);if(s.help){process.stdout.write(`${i}\n`);return}if(s.command!==`build`&&s.command!==`guidance`&&s.command!==`ids`)throw Error(`Unknown command ${s.command}. The commands are build, guidance and ids.`);if(s.command===`ids`){if(s.options.folder===void 0)throw Error(`ids needs a folder.`);let e=await n(r(s.options.folder),{write:s.options.write});for(let t of e.proposals)process.stdout.write(`${t.file}:${t.line} ${t.element} ${t.key}\n`);for(let t of e.noKey)process.stdout.write(`${t.file}:${t.line} ${t.element} no record key\n`);process.stdout.write(`${e.proposals.length} keys proposed, ${e.written} written, ${e.noKey.length} list items have no record key.\n`);return}if(s.command===`guidance`){if(!s.options.config||!s.options.out)throw Error(`guidance needs --config and --out.`);if(s.options[`style-keys`]!==void 0||s.options[`phone-renderer-support`]!==void 0)throw Error(`guidance takes only --config and --out.`);let e=await t({configFile:r(s.options.config),outFile:r(s.options.out)});process.stdout.write(`Guidance for ${e.components.length} components written to ${r(s.options.out)}.\n`);return}if(!s.options.config||!s.options.out)throw Error(`build needs --config and --out.`);let c=s.options[`style-keys`],l=s.options[`phone-renderer-support`],u=await e({configFile:r(s.options.config),outDir:r(s.options.out),styleKeysFile:c===void 0?void 0:r(c),phoneRendererSupportFile:l===void 0?void 0:r(l)});for(let e of u.warnings)process.stderr.write(`warning: ${e}\n`);process.stdout.write(`Built ${u.release.name} (${u.release.platform}): ${u.manifest.components.length} components. Release written to ${u.outDir}.\n`)}s(process.argv.slice(2)).catch(e=>{process.stderr.write(`canvas-kit: ${e.message}\n`),process.exitCode=1});export{};
package/dist/ids.js ADDED
@@ -0,0 +1 @@
1
+ import{basename as e,extname as t,join as n,relative as r,sep as i}from"node:path";import a from"@babel/parser";import{readFile as o,readdir as s,writeFile as c}from"node:fs/promises";var l=new Set([`node_modules`,`dist`,`build`]),u=new Set([`.jsx`,`.tsx`,`.js`]),d=new Set([`@webority/mobile-ui`,`react-native`]),f=new Set([`@webority/ui-react`,`@webority/mobile-ui`]),p=new Set([`section`,`form`,`header`,`nav`,`main`,`aside`,`article`,`fieldset`]),m=[`aria-label`,`name`,`id`,`title`,`label`,`placeholder`],h=new Set([`ArrowFunctionExpression`,`FunctionExpression`,`FunctionDeclaration`,`ObjectMethod`,`ClassMethod`]),g=new Set([`loc`,`start`,`end`,`comments`,`leadingComments`,`trailingComments`,`innerComments`,`tokens`,`extra`,`errors`]),_=40;async function v(e,{write:t=!1}={}){let n=[],a=[],s=[];for(let t of await y(e)){let c=r(e,t).split(i).join(`/`),l=await o(t,`utf8`),u=b({source:l,file:t,label:c});n.push(...u.proposals),a.push(...u.noKey),u.insertions.length>0&&s.push({file:t,source:l,insertions:u.insertions})}let l=0;if(t)for(let e of s){let t=e.source;for(let n of[...e.insertions].sort((e,t)=>t.offset-e.offset))t=`${t.slice(0,n.offset)}${n.text}${t.slice(n.offset)}`;await c(e.file,t,`utf8`),l+=e.insertions.length}return{proposals:n,noKey:a,written:l}}async function y(e){let r=await s(e,{withFileTypes:!0});r.sort((e,t)=>e.name<t.name?-1:1);let i=[];for(let a of r){let r=n(e,a.name);a.isDirectory()?l.has(a.name)||i.push(...await y(r)):a.isFile()&&u.has(t(a.name))&&!a.name.includes(`.test.`)&&i.push(r)}return i}function b({source:e,file:t,label:n}){let r;try{r=a.parse(e,{sourceType:`module`,plugins:[`jsx`,`typescript`]})}catch(e){throw Error(`${n}: ${e.message}`)}let i=new Set,o=!1;for(let e of r.program.body){if(e.type!==`ImportDeclaration`)continue;let t=e.source.value;if(o||=d.has(t),f.has(t))for(let t of e.specifiers)t.type===`ImportSpecifier`&&i.add(t.local.name)}let s=o?`testID`:`data-canvas`,c=new Map,l=[];x(r.program,null,c,l);let u=M(t),p=t=>e.slice(t.start,t.end),m=new Map,h=[],g=[];for(let e of l){let t=e.openingElement,r=k(t,s),a=E(t,i),o=S(e,c);if(a!==null&&o===null&&T(m,e,a,r,c),!r)if(o!==null){let r=t.attributes.find(e=>e.type===`JSXAttribute`&&e.name.type===`JSXIdentifier`&&e.name.name===`key`)?.value,i=r?.type===`JSXExpressionContainer`?r.expression:null,a=o.params[1]?.type===`Identifier`?o.params[1].name:null;i===null||i.type===`JSXEmptyExpression`||I(i)||i.type===`Identifier`&&i.name===a?g.push({start:e.start,entry:{file:n,line:e.loc.start.line,element:p(t.name)}}):h.push({element:e,kind:`list`,keyExpression:i})}else a===null&&D(t)&&h.push({element:e,kind:`block`})}for(let e of m.values())for(let t of e.values())if(t.length>1&&t.some(e=>e.conditional))for(let e of t)e.skipped||h.push({element:e.element,kind:`sibling`});h.sort((e,t)=>e.element.start-t.element.start);let _=new Set,v=[],y=[];for(let e of h){let{element:t,kind:r}=e,a=t.openingElement,o=p(a.name),c=E(a,i),l=`${u}.${j(t,c===null?o:N(c))}`,d=r===`list`?`${l}.\${${p(e.keyExpression)}}`:l,f=d;if(_.has(f)){let e=2;for(;_.has(`${d}-${e}`);)e+=1;f=`${d}-${e}`}_.add(f);let m=r===`list`?` ${s}={\`${f}\`}`:` ${s}="${f}"`;v.push({file:n,line:t.loc.start.line,element:o,key:f}),y.push({offset:a.name.end,text:m})}return{proposals:v,noKey:g.sort((e,t)=>e.start-t.start).map(e=>e.entry),insertions:y}}function x(e,t,n,r){t!==null&&n.set(e,t),e.type===`JSXElement`&&r.push(e);for(let t of Object.keys(e)){if(g.has(t))continue;let i=e[t];for(let t of Array.isArray(i)?i:[i])typeof t==`object`&&t&&typeof t.type==`string`&&x(t,e,n,r)}}function S(e,t){let n=t.get(e),r=null;if(n?.type===`ArrowFunctionExpression`&&n.body===e)r=n;else if(n?.type===`ReturnStatement`){let e=t.get(n),i=t.get(e);e?.type===`BlockStatement`&&h.has(i?.type)&&i.body===e&&(r=i)}if(r===null)return null;let i=t.get(r);return i?.type===`CallExpression`&&i.arguments[0]===r&&i.callee.type===`MemberExpression`&&!i.callee.computed&&i.callee.property.type===`Identifier`&&i.callee.property.name===`map`?r:null}function C(e,t){for(let n=t.get(e);n!==void 0;n=t.get(n)){if(n.type===`JSXElement`||n.type===`JSXFragment`)return n;if(h.has(n.type))return null}return null}function w(e,t,n){for(let r=n.get(e);r!==t;r=n.get(r))if(r.type===`ConditionalExpression`||r.type===`LogicalExpression`&&r.operator===`&&`)return!0;return!1}function T(e,t,n,r,i){let a=C(t,i);if(a===null)return;e.has(a)||e.set(a,new Map);let o=e.get(a);o.has(n)||o.set(n,[]),o.get(n).push({element:t,skipped:r,conditional:w(t,a,i)})}function E(e,t){return e.name.type===`JSXIdentifier`&&t.has(e.name.name)?e.name.name:null}function D(e){if(e.name.type!==`JSXIdentifier`)return!1;let t=e.name.name;return p.has(t)||t===`div`&&(O(e,`id`)||O(e,`aria-label`))}function O(e,t){return e.attributes.some(e=>e.type===`JSXAttribute`&&e.name.type===`JSXIdentifier`&&e.name.name===t)}function k(e,t){return e.attributes.some(e=>e.type===`JSXSpreadAttribute`)||O(e,t)}function A(e,t){let n=e.attributes.find(e=>e.type===`JSXAttribute`&&e.name.type===`JSXIdentifier`&&e.name.name===t)?.value;if(n){if(n.type===`StringLiteral`)return n.value;if(n.type===`JSXExpressionContainer`){let e=n.expression;if(e.type===`StringLiteral`)return e.value;if(e.type===`TemplateLiteral`&&e.expressions.length===0)return e.quasis[0].value.cooked??void 0}}}function j(e,t){for(let t of m){let n=A(e.openingElement,t),r=n===void 0?``:F(P(n));if(r!==``)return r}let n=e.children.filter(e=>e.type===`JSXText`&&e.value.trim()!==``);if(n.length===1){let e=F(P(n[0].value));if(e!==``)return e}return F(P(t))}function M(n){return P(e(n,t(n))).replace(/-(screen|page)$/,``)}function N(e){return e.replace(/^App(?=[A-Z])/,``)}function P(e){return e.replace(/([a-z0-9])([A-Z])/g,`$1-$2`).toLowerCase().replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``)}function F(e){if(e.length<=_)return e;let t=e.slice(0,41).lastIndexOf(`-`);return t>0?e.slice(0,t):e.slice(0,_)}function I(e){return e.type===`TemplateLiteral`?e.expressions.length===0:/Literal$/.test(e.type)}export{v as proposeCanvasKeys};
@@ -67,6 +67,8 @@ export interface CanvasManifestTokens {
67
67
  shadow: string[];
68
68
  font: string[];
69
69
  text: string[];
70
+ // The line height roles, such as heading and body.
71
+ leading: string[];
70
72
  colorMeanings?: Record<string, string>;
71
73
  }
72
74
 
@@ -79,6 +81,8 @@ export interface CanvasManifestTokenValues {
79
81
  shadow: Record<string, CanvasShadow | null>;
80
82
  font: Record<string, CanvasManifestFontValue>;
81
83
  text: Record<string, CanvasManifestTextValue>;
84
+ // Unitless line height per role.
85
+ leading: Record<string, number>;
82
86
  }
83
87
 
84
88
  // Lengths in px at scale 1. `color` is the resolved colour with its opacity in the alpha.
@@ -104,7 +108,6 @@ export interface CanvasManifestFontValue {
104
108
 
105
109
  export interface CanvasManifestTextValue {
106
110
  size: number;
107
- lineHeight?: number | null;
108
111
  weight?: number | null;
109
112
  }
110
113
 
package/dist/tokens.js CHANGED
@@ -1 +1 @@
1
- import{readJson as e,readText as t}from"./files.js";import{join as n}from"node:path";var r=e=>typeof e==`object`&&!!e&&!Array.isArray(e),i=e=>typeof e==`number`&&Number.isFinite(e),a=e=>typeof e==`string`,o=[`x`,`y`,`blur`,`spread`,`color`,`elevation`],s=e=>e===null||r(e)&&Object.keys(e).every(e=>o.includes(e))&&i(e.x)&&i(e.y)&&i(e.blur)&&i(e.spread)&&a(e.color)&&(e.elevation===void 0||i(e.elevation));function c(e){return[...new Set(e)].sort((e,t)=>e<t?-1:+(e>t))}function l(e,t=e=>e){return Object.fromEntries(c(Object.keys(e)).map(n=>[n,t(e[n])]))}function u(e,t,n){if(!r(t[n]))throw Error(`${e} has no ${n} object.`);return t[n]}function d(e,t,n,r){for(let[i,a]of Object.entries(n))if(!r(a))throw Error(`${e}: ${t}.${i} is not a value the kit can draw.`)}function f(t,n,o){let f=e(t);if(f.format!==`webority-theme-tokens/2`)throw Error(`${t} has format ${JSON.stringify(f.format)}; this reader reads webority-theme-tokens/2.`);if(a(o)&&f.source?.platform!==o.toLowerCase())throw Error(`${t} is the ${f.source?.platform} token file; a ${o} kit needs the ${o.toLowerCase()} one.`);let p=r(f.colors)&&r(f.colors.light)?f.colors.light:null,m=r(f.colors)&&r(f.colors.dark)?f.colors.dark:null;if(!p||!m)throw Error(`${t} has no colors.${p?`dark`:`light`} object.`);let h=Object.keys(p).filter(e=>!Object.hasOwn(m,e)),g=Object.keys(m).filter(e=>!Object.hasOwn(p,e));if(h.length>0||g.length>0)throw Error(`${t}: the light and dark colours differ. Only in light: ${h.length>0?c(h).join(`, `):`none`}. Only in dark: ${g.length>0?c(g).join(`, `):`none`}.`);let _=c(Object.keys(p));d(t,`color`,p,a),d(t,`color`,m,a);let v=u(t,f,`space`);d(t,`space`,v,i);let y=u(t,f,`radius`);d(t,`radius`,y,i);let b=u(t,f,`shadow`);d(t,`shadow`,b,s);let x=u(t,f,`fonts`);d(t,`font`,x,e=>r(e)&&a(e.family)&&a(e.fallback));let S=u(t,f,`text`);d(t,`text`,S,e=>r(e)&&i(e.size)&&i(e.lineHeight));let C=e(n);if(C.format!==`webority-meanings/1`)throw Error(`${n} has format ${JSON.stringify(C.format)}; this reader reads webority-meanings/1.`);let w={};for(let e of _){let t=C.color?.[e]?.description;if(!a(t)||t===``)throw Error(`${n} has no meaning for the colour ${e}; move the @webority/tokens pin to a version that publishes one.`);w[e]=t}return{tokens:{color:_,space:c(Object.keys(v)),radius:c(Object.keys(y)),shadow:c(Object.keys(b)),font:c(Object.keys(x)),text:c(Object.keys(S)),colorMeanings:w},tokenValues:{color:Object.fromEntries(_.map(e=>[e,{light:p[e],dark:m[e]}])),space:l(v),radius:l(y),shadow:l(b),font:l(x,({family:e,fallback:t})=>({family:e,fallback:t})),text:l(S,({size:e,lineHeight:t})=>({size:e,lineHeight:t,weight:null}))}}}function p(e){let r=n(e,`components`,`iconCatalogNames.g.js`),i=t(r),a=/`([^`]+)`\.split\(`\.`\)/.exec(i);if(!a)throw Error(`No icon name list in ${r}`);return c(a[1].split(`.`).filter(Boolean))}export{f as tokensFromV2,p as webIcons};
1
+ import{readJson as e,readText as t}from"./files.js";import{join as n}from"node:path";var r=e=>typeof e==`object`&&!!e&&!Array.isArray(e),i=e=>typeof e==`number`&&Number.isFinite(e),a=e=>typeof e==`string`,o=[`x`,`y`,`blur`,`spread`,`color`,`elevation`],s=e=>e===null||r(e)&&Object.keys(e).every(e=>o.includes(e))&&i(e.x)&&i(e.y)&&i(e.blur)&&i(e.spread)&&a(e.color)&&(e.elevation===void 0||i(e.elevation));function c(e){return[...new Set(e)].sort((e,t)=>e<t?-1:+(e>t))}function l(e,t=e=>e){return Object.fromEntries(c(Object.keys(e)).map(n=>[n,t(e[n])]))}function u(e,t,n){if(!r(t[n]))throw Error(`${e} has no ${n} object.`);return t[n]}function d(e,t,n,r){for(let[i,a]of Object.entries(n))if(!r(a))throw Error(`${e}: ${t}.${i} is not a value the kit can draw.`)}function f(t,n,o){let f=e(t);if(f.format!==`webority-theme-tokens/2`)throw Error(`${t} has format ${JSON.stringify(f.format)}; this reader reads webority-theme-tokens/2.`);if(a(o)&&f.source?.platform!==o.toLowerCase())throw Error(`${t} is the ${f.source?.platform} token file; a ${o} kit needs the ${o.toLowerCase()} one.`);let p=r(f.colors)&&r(f.colors.light)?f.colors.light:null,m=r(f.colors)&&r(f.colors.dark)?f.colors.dark:null;if(!p||!m)throw Error(`${t} has no colors.${p?`dark`:`light`} object.`);let h=Object.keys(p).filter(e=>!Object.hasOwn(m,e)),g=Object.keys(m).filter(e=>!Object.hasOwn(p,e));if(h.length>0||g.length>0)throw Error(`${t}: the light and dark colours differ. Only in light: ${h.length>0?c(h).join(`, `):`none`}. Only in dark: ${g.length>0?c(g).join(`, `):`none`}.`);let _=c(Object.keys(p));d(t,`color`,p,a),d(t,`color`,m,a);let v=u(t,f,`space`);d(t,`space`,v,i);let y=u(t,f,`radius`);d(t,`radius`,y,i);let b=u(t,f,`shadow`);d(t,`shadow`,b,s);let x=u(t,f,`fonts`);d(t,`font`,x,e=>r(e)&&a(e.family)&&a(e.fallback));let S=u(t,f,`text`);d(t,`text`,S,e=>r(e)&&i(e.size));let C=u(t,u(t,f,`typography`),`leading`);d(t,`leading`,C,e=>i(e)&&e>0&&e<=4);let w=f.colorMeanings===void 0?{}:f.colorMeanings;if(!r(w))throw Error(`${t} has a colorMeanings entry that is not a description: colorMeanings`);for(let[e,n]of Object.entries(w))if(!a(n)||n===``)throw Error(`${t} has a colorMeanings entry that is not a description: ${e}`);let T=e(n);if(T.format!==`webority-meanings/1`)throw Error(`${n} has format ${JSON.stringify(T.format)}; this reader reads webority-meanings/1.`);let E={};for(let e of _){let t=w[e],r=a(t)&&t!==``?t:T.color?.[e]?.description;if(!a(r)||r===``)throw Error(`${n} has no meaning for the colour ${e}; move the @webority/tokens pin to a version that publishes one.`);E[e]=r}return{tokens:{color:_,space:c(Object.keys(v)),radius:c(Object.keys(y)),shadow:c(Object.keys(b)),font:c(Object.keys(x)),text:c(Object.keys(S)),leading:c(Object.keys(C)),colorMeanings:E},tokenValues:{color:Object.fromEntries(_.map(e=>[e,{light:p[e],dark:m[e]}])),space:l(v),radius:l(y),shadow:l(b),font:l(x,({family:e,fallback:t})=>({family:e,fallback:t})),text:l(S,({size:e})=>({size:e,weight:null})),leading:l(C)}}}function p(e){let r=n(e,`components`,`iconCatalogNames.g.js`),i=t(r),a=/`([^`]+)`\.split\(`\.`\)/.exec(i);if(!a)throw Error(`No icon name list in ${r}`);return c(a[1].split(`.`).filter(Boolean))}export{f as tokensFromV2,p as webIcons};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority/canvas-kit",
3
- "version": "0.90.0",
3
+ "version": "0.91.0",
4
4
  "description": "Builds product kits for Webority Canvas: a kit bundle, its manifest, and the spec shape the renderers answer.",
5
5
  "private": false,
6
6
  "type": "module",