@webority/canvas-kit 0.88.0 → 0.89.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
@@ -1,27 +1,24 @@
1
1
  # @webority/canvas-kit
2
2
 
3
- Builds a kit for Webority Canvas. A kit is a pinned Webority library, and a product kit adds the product's own
4
- presentational components. The build writes an immutable release folder that Canvas stores and draws screens with.
5
-
6
- There are two kinds of kit. A library-only kit has no `entry`: it draws the library's components and nothing else. The
7
- Webority fleet kits (`webority-web`, `webority-phone`) are built this way by Canvas Web's `publish-fleet-kit.mjs`. A
8
- product kit has an `entry` and adds its own components.
3
+ A kit is a Webority library plus, for a product, the product's own presentational components. Every component is
4
+ described by a `.meta.js` file (see Component metadata). A kit built inside its own library is a self-build: ui-react
5
+ builds the webority-web kit and mobile-ui the webority-phone kit this way. A product kit adds its own components to the
6
+ library's published kit release. The build writes an immutable release folder that Canvas stores and draws screens with.
9
7
 
10
8
  ## Build a release
11
9
 
12
10
  ```sh
13
- canvas-kit build --config canvas-kit.json --out canvas-kit-release
14
- canvas-kit build --config fleet.json --out Webority.Canvas.Kits/webority-web/<version> --style-keys Webority.Canvas.Web/scripts/styleKeyTable.json
11
+ canvas-kit build --config canvas-kit.json --out dist/canvas-kit
12
+ canvas-kit build --config canvas-kit.json --out canvas-kit-release --style-keys <file>
15
13
  ```
16
14
 
17
- `--out` must not exist. A release folder is never overwritten; build into a new folder. A library-only kit writes to
18
- `<kitKey>/<version>`, and the build refuses any other folder.
15
+ `--out` must not exist. A release folder is never overwritten, so build into a new folder. Any folder name works.
19
16
 
20
17
  The flags are:
21
18
 
22
19
  | Flag | Meaning |
23
20
  | --- | --- |
24
- | `--style-keys <file>` | The style key table the renderer draws from. A library-only kit needs it. |
21
+ | `--style-keys <file>` | Optional. The style key table the renderer draws from. The manifest carries `styleKeys` only when this flag is given. |
25
22
  | `--phone-renderer-support <file>` | The phone renderer's support file: each style key's `phoneSupported` flag and the phone kit's action lists. A Phone kit needs it. |
26
23
 
27
24
  Flag paths are relative to the working directory.
@@ -30,48 +27,107 @@ The config is a JSON object with these keys. Paths are relative to the config fi
30
27
 
31
28
  | Key | Meaning |
32
29
  | --- | --- |
33
- | `kit` | The kit key, lowercase letters, digits and hyphens, 2 to 40 characters, for example `lumex-portal`. |
34
- | `platform` | `Web` or `Phone`. A Web kit takes its tokens from the theme and its icons from the library; a Phone kit takes both from the phone library. |
35
- | `entry` | Optional. The kit's entry module, which exports the product's components. Leave it out for a library-only kit. |
30
+ | `kit` | The kit key, lowercase letters, digits and hyphens, 2 to 40 characters, for example `sample-kit`. |
31
+ | `platform` | `Web` or `Phone`. |
36
32
  | `libraryPackage` | The Webority library the kit is built on, for example `@webority/ui-react`. |
33
+ | `components` | A glob of `.meta.js` files, for example `src/components/*.meta.js`. |
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`. |
37
35
  | `rolesFile` | Optional. A JSON object from component name to `title` or `navigation`. |
38
36
  | `rendererProtocolVersion` | The renderer protocol number the kit is built for. |
39
37
 
40
- A config that names any other key, such as the retired `phoneStyleSupportFile`, fails the read.
38
+ A kit lists its components with `components`. A config that names `entry` fails the read with that message, and so does
39
+ a config that names any other key.
41
40
 
42
41
  ## What a release holds
43
42
 
44
- The release folder is `<kit>/<version>`:
43
+ The release folder holds these four files:
45
44
 
46
45
  - `kit.js`: one ES module whose default export comes from `defineKit`, with the components, the markers, the token
47
46
  values and the manifest. React and react-dom stay bare imports, so the renderer's own React draws the kit. A Phone kit
48
47
  also leaves react-native and react-native-svg bare; the phone renderer's import map resolves them.
49
48
  - `kit.css`: the styles the bundle imports, or an empty file.
50
49
  - `manifest.json`: the components with their props, sources and roles, the library's tokens and icons, and the style
51
- keys each with its `phoneSupported` flag.
50
+ keys each with its `phoneSupported` flag when a style key table is given. Each component carries its `usage` and each
51
+ prop its `description` and `default`.
52
52
  - `release.json`: the kit key as `name`, the `version`, the platform, the kit module version, the protocol version, the
53
53
  library name and version, and the SHA-256 and byte count of each file above.
54
54
 
55
- A library-only kit is stamped with the installed library's version. A product kit is stamped with the product's
56
- `package.json` version.
55
+ A release is stamped with the kit root's `package.json` version: the library's own version for a self-build, the
56
+ product's for a product kit.
57
+
58
+ ## Component metadata
59
+
60
+ A meta module imports only `defineMeta` from `@webority/canvas-kit/meta` and exports `meta`. The fields of `meta`:
61
+
62
+ | Field | Meaning |
63
+ | --- | --- |
64
+ | `name` | The catalogue name, such as `AppButton`. Letters and digits, starting with a capital letter. |
65
+ | `marker` | The `data-wui` value the component renders, or `null`. |
66
+ | `platforms` | `web`, `phone` or both. |
67
+ | `acceptsChildren` | `true` or `false`. |
68
+ | `props` | Each prop has a `type`, one of `string`, `number`, `stringOrNumber`, `boolean`, `enum`, `enumOrNumber`, `url`, `icon`, `node`, `array`, `object` or `any`, and a `description`. An enum or enumOrNumber lists its `values`; numbers are allowed in an enum, and an enumOrNumber lists its named values only and also takes any number. Optional `default` (never on a required prop), `required`, `fields` on an object and `itemFields` on an array of objects. |
69
+ | `omit` | Optional. Design props deliberately not described. |
70
+ | `examples` | Each has an `id`, a `title`, `props`, optional `children` and optional `wrap`. The id is the name for the base example and `<name>.<case>` for the others. |
71
+ | `usage` | `when` lines, `whenNot` entries with `text` and optional `instead`, `do` and `dont` pairs of `note` and `example` matched by index, and `a11y` lines. |
72
+
73
+ A node in an example, a `wrap` or a practice places kit components and the HTML tags listed in `HTML_TAGS`. Event
74
+ handlers, `ref`, `key`, `children`, and props typed as a component are never listed.
75
+
76
+ ### The type check
77
+
78
+ The build generates a `.ts` file under `node_modules/.canvas-kit-check-<pid>`, runs the TypeScript compiler on it, and
79
+ fails with one line per mismatch: `<Name>: props the component takes that the meta does not list: ...`, props the meta
80
+ lists that the component does not take, props whose type, required flag or nested fields differ, and `acceptsChildren`
81
+ against the component. The folder is removed afterwards. A product kit is checked the same way as the library. The check
82
+ covers Web metas; a phone meta is proven by mobile-ui's own type check.
83
+
84
+ ### The checks on a meta
85
+
86
+ `readMetas` and `checkMetas` refuse a meta that:
87
+
88
+ - has a duplicate or malformed `name`, or an `omit` that is not a list of prop names;
89
+ - has empty `platforms`, a `marker` that is neither a string nor `null`, or an `acceptsChildren` that is not `true` or `false`;
90
+ - has a prop type not in the list above, `values` off an enum or enumOrNumber, a repeated value, or a value of the wrong kind;
91
+ - has `fields` off an object, `itemFields` off an array, or an empty `description`;
92
+ - has a `default` that does not fit its type, or a `default` on a required prop;
93
+ - has an example id that is not the name or `<name>.<case>`, or an id used twice across metas;
94
+ - has an example prop the meta does not list, a value outside the prop's type, or no value for a required prop;
95
+ - has `children` where `acceptsChildren` is `false`;
96
+ - places a node whose component is unknown;
97
+ - has an empty `when`, `do` and `dont` lists of different lengths, or an `instead` that names an unknown component;
98
+ - has an `icon` outside the kit's icons;
99
+ - has an em or en dash in any string.
100
+
101
+ ## Guidance file
102
+
103
+ `canvas-kit guidance --config canvas-kit.json --out dist/guidance.json` writes `webority-guidance/1`:
104
+ `{ format, package: { name, version }, components: [{ name, usage }] }`, sorted by name, from the metas the config's
105
+ `components` glob lists. It reads only `components` from the config, so a package that ships guidance and no kit (for
106
+ example one whose components need a live service client) writes a config with `kit`, `platform`, `libraryPackage` and
107
+ `components` and runs only this command. Humans and agents read it instead of the showcase pages.
57
108
 
58
109
  ## Product components
59
110
 
60
- A kit component is an exported function declaration whose props are typed in JSDoc, for example
61
- `@param {SampleBadgeProps} props` with a `@typedef` beside it. The build runs the TypeScript compiler over the entry to
62
- read those types. Components of the library come from the library's own declaration files.
111
+ A product kit component is a component module with a sibling `<Name>.meta.js`. The module is the first existing of
112
+ `.jsx`, `.tsx`, `.js` or `.ts` beside the meta, and exports the component under the meta's name. The kit reads the
113
+ library's components from the library's published kit release, so the library's `package.json` must export
114
+ `./canvas-kit/*`. The product meta's `marker` is the `data-wui` value its component writes, or `null`.
63
115
 
64
116
  ## Refusals
65
117
 
66
118
  The build stops with a named fix when:
67
119
 
68
120
  - the kit imports `react-redux`, `react-router-dom`, `axios`, or a file under `src/services`;
69
- - the product's React is not major 19, the Canvas renderer's React major;
70
- - the library installed differs from the exact version the product pins;
121
+ - React is not major 19, the Canvas renderer's React major;
122
+ - the library installed differs from the exact version the product pins (product kits only);
71
123
  - a role names a component the kit does not have, or a role is not `title` or `navigation`;
72
- - a product component takes a name the library already exports;
73
- - a library-only kit is built without `--style-keys`, or a Phone kit without `--phone-renderer-support`;
74
- - a library-only kit is written to a folder that is not `<kitKey>/<version>`.
124
+ - a product component takes a name the library already has;
125
+ - a meta lists a platform other than the kit's;
126
+ - the config names `entry`, or lacks `components` or `tokens`;
127
+ - 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 meta does not match its component's typed props, or fails one of the checks on a meta;
130
+ - a Phone kit is built without `--phone-renderer-support`.
75
131
 
76
132
  ## Reading a release back
77
133
 
@@ -80,7 +136,8 @@ Any file changed after the build fails the check.
80
136
 
81
137
  ## Exports
82
138
 
83
- - `.`: `kitModuleVersion`, `KIT_MODULE_EXTERNALS`, `defineKit`, `readManifest`, `readReleaseManifest(dir)`, `buildKitRelease`, `readConfig`. Node only.
139
+ - `.` (Node only): `kitModuleVersion`, `KIT_MODULE_EXTERNALS`, `defineKit`, `readManifest` (async), `readReleaseManifest(dir)`, `buildKitRelease`, `readConfig`, `readMetas`, `HTML_TAGS`, `checkMetaTypes`, `guidanceOf`, `writeGuidance`.
140
+ - `./meta`: `defineMeta` and the meta types (`ComponentMeta`, `MetaProp`, `MetaNode`, `MetaExample`, `MetaPractice`, `ComponentUsage`).
84
141
  - `./kitModule`: the kit module contract alone.
85
142
  - `./browser`: for the renderers, with no Node built-ins. `loadKit({ kitKey, version, baseUrl })` loads the release at `<baseUrl>/<kitKey>/<version>` and returns its kit module, for a fleet kit and a product kit alike.
86
143
 
@@ -88,7 +145,5 @@ Any file changed after the build fails the check.
88
145
 
89
146
  The package builds into `dist` before publish: `pnpm run build`. Its tests run with `pnpm test`, which reads the
90
147
  source under `src`. The tests build throwaway product folders with a stub library, so they need only the
91
- package's own dependencies installed.
92
-
93
- The sample kit under `src/__fixtures__/sample-kit` resolves `@webority/ui-react` and `react` through this package's
94
- devDependencies, so it builds from the workspace after `pnpm install`.
148
+ package's own dependencies installed. The stub folders hold a stub library with its metas, a stub `@webority/tokens`
149
+ package and two `webority-theme-tokens/2` fixtures.
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{checkFleetFolder as o,digest as s,writeReleaseFolder as c}from"./release.js";import{existsSync as l}from"node:fs";import{dirname as u,join as d,resolve as f}from"node:path";import{fileURLToPath as p}from"node:url";import{build as m}from"vite";var h=19,g=[`react-redux`,`react-router-dom`,`axios`],_=[`kit`,`platform`,`entry`,`libraryPackage`,`rolesFile`,`rendererProtocolVersion`],v=[`kit`,`platform`,`libraryPackage`,`rendererProtocolVersion`],y=`canvas-kit:entry`,b=`\0${y}`,x=d(u(p(import.meta.url)),`kitModule.js`),S={"process.env.NODE_ENV":JSON.stringify(`production`),"process.env":`{}`,__DEV__:`false`},C=/(?<![\w$."'`])(process\.env|__DEV__)(?![\w$])/;function w(e){let n=t(e);if(typeof n!=`object`||!n||Array.isArray(n))throw Error(`${e} must be a JSON object.`);for(let t of Object.keys(n))if(!_.includes(t))throw Error(`${e} has the unknown key ${t}. The keys are ${_.join(`, `)}.`);for(let t of v)if(n[t]===void 0)throw Error(`${e} needs the key ${t}.`);for(let t of[`kit`,`platform`,`libraryPackage`])if(typeof n[t]!=`string`)throw Error(`${e}: ${t} must be a string.`);for(let t of[`entry`,`rolesFile`])if(n[t]!==void 0&&typeof n[t]!=`string`)throw Error(`${e}: ${t} must be a string path.`);return n}function T(n){let r=e(n,`react`);if(!r)throw Error(`react is not installed for ${n}. Run pnpm install there.`);let{version:i}=t(d(r,`package.json`));if(Number(String(i).split(`.`)[0])!==h)throw Error(`The product uses React ${i}; the Canvas renderer runs React ${h}. Pin react to ${h}.x and rebuild.`)}function E(e){let{version:n}=t(d(e,`package.json`));if(!n)throw Error(`${d(e,`package.json`)} needs a version; the release is stamped with it.`);return n}function D({entryFile:e,libraryFile:t,chromeFile:r,manifest:i,libraryNames:a,chromeNames:o,productNames:s}){let c=[`import { defineKit } from ${JSON.stringify(n(x))};`];e!==void 0&&c.push(`import * as product 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 l=[...a,...o,...s.map(e=>`${e}: product.${e}`)],u=Object.fromEntries(i.components.filter(({marker:e})=>e!==null).map(({name:e,marker:t})=>[e,t]));return c.push(`export default defineKit({ components: { ${l.join(`, `)} }, markers: ${JSON.stringify(u)}, tokenValues: ${JSON.stringify(i.tokenValues)}, manifest: ${JSON.stringify(i)} });`,``),c.join(`
2
- `)}function O(e){return{name:`canvas-kit-entry`,resolveId(e){return e===y?b:null},load(t){return t===b?e:null}}}function k(e){let t=`${n(d(e,`src`,`services`))}/`;return{name:`canvas-kit-banned-imports`,transform(e,r){let i=n(r.split(`?`)[0]),a=g.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 A({root:e,kit:t,platform:n,entryFile:i,libraryFile:a,chromeFile:o,libraryNames:s,chromeNames:c,productNames:l,manifest:u}){let d=r[n],f=await m({configFile:!1,root:e,logLevel:`warn`,publicDir:!1,envDir:!1,define:S,plugins:[O(D({entryFile:i,libraryFile:a,chromeFile:o,manifest:u,libraryNames:s,chromeNames:c,productNames:l})),k(e)],esbuild:{jsx:`automatic`,jsxImportSource:`react`,include:/\.[cm]?[jt]sx?$/},build:{write:!1,minify:!0,target:`es2022`,cssCodeSplit:!1,lib:{entry:y,formats:[`es`],fileName:()=>`kit.js`},rollupOptions:{input:y,external:e=>d.some(t=>e===t||e.startsWith(`${t}/`)),output:{inlineDynamicImports:!0}}}}),p=(Array.isArray(f)?f:[f]).flatMap(e=>e.output),h=p.find(e=>e.type===`chunk`&&e.isEntry);if(!h)throw Error(`The bundle of ${t} has no entry module.`);let g=C.exec(h.code);if(g)throw Error(`The bundle of ${t} still reads ${g[1]}, which the renderer does not define.`);let _=p.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 v=p.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:v}}async function j({configFile:e,outDir:t,styleKeysFile:n,phoneRendererSupportFile:r}){let d=f(e),p=f(t);if(l(p))throw Error(`${p} already exists. A release folder is never overwritten; choose a new folder.`);let m=w(d),h=u(d),g=m.entry===void 0,_=g?void 0:f(h,m.entry);if(_!==void 0&&!l(_))throw Error(`The kit entry ${_} does not exist.`);if(g&&n===void 0)throw Error(`A library-only kit needs --style-keys: the style key table the renderer draws from.`);if(m.platform===`Phone`&&r===void 0)throw Error(`A Phone kit needs --phone-renderer-support: the phone renderer's support file.`);if(m.platform!==`Phone`&&r!==void 0)throw Error(`Only a Phone kit reads --phone-renderer-support; a Web kit takes no phone support file.`);T(h);let v=m.rolesFile===void 0?void 0:f(h,m.rolesFile),{manifest:y,warnings:b,phoneChromeNames:x}=a({projectRoot:h,kit:m.kit,platform:m.platform,entry:_,libraryPackage:m.libraryPackage,rolesFile:v,rendererProtocolVersion:m.rendererProtocolVersion,phoneRendererSupportFile:r===void 0?void 0:f(r),styleKeysFile:n===void 0?void 0:f(n)}),S=i(h,m.libraryPackage),C=g?S.pkg.version:E(h);g&&o(p,m.kit,C);let D=y.components.filter(({source:e,name:t})=>e===m.libraryPackage&&!x.includes(t)).map(({name:e})=>e),O=y.components.filter(({source:e})=>e!==m.libraryPackage).map(({name:e})=>e),{script:k,styles:j}=await A({root:h,kit:m.kit,platform:m.platform,entryFile:_,libraryFile:S.entryFile,chromeFile:S.canvasKitFile,libraryNames:D,chromeNames:x,productNames:O,manifest:y}),M={"manifest.json":`${JSON.stringify(y,null,2)}\n`,"kit.js":k,"kit.css":j},N={name:m.kit,version:C,platform:m.platform,kitModuleVersion:2,rendererProtocolVersion:m.rendererProtocolVersion,library:y.library,files:Object.fromEntries(Object.entries(M).map(([e,t])=>[e,s(t)]))};return c(p,M,N),{outDir:p,release:N,manifest:y,warnings:b}}export{j as buildKitRelease,w 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`,`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};
package/dist/cli.js CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import{buildKitRelease as e}from"./build.js";import{resolve as t}from"node:path";var n=`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{resolve as n}from"node:path";var r=`Usage: canvas-kit build --config <file> --out <dir> [--style-keys <file>] [--phone-renderer-support <file>]
3
+ canvas-kit guidance --config <file> --out <file>
3
4
 
4
- Builds a kit release into <dir>: kit.js, kit.css, manifest.json and release.json.
5
+ build: builds a kit release into <dir>: kit.js, kit.css, manifest.json and release.json.
5
6
  <dir> must not exist; a release folder is never overwritten.
6
- --style-keys: the style key table the renderer draws from. A library-only kit (no entry) needs it.
7
- --phone-renderer-support: the phone renderer's support file. A Phone kit needs it.
8
- Flag paths are relative to the working directory; the paths in the config are relative to the config file.`,r=new Set([`--config`,`--out`,`--style-keys`,`--phone-renderer-support`]);function i(e){if(e.length===0||e.includes(`--help`)||e.includes(`-h`))return{help:!0};let[t,...n]=e,i={};for(let e=0;e<n.length;e+=1){let t=n[e];if(!r.has(t))throw Error(`Unknown argument ${t}.`);let a=n[e+1];if(a===void 0||a.startsWith(`--`))throw Error(`${t} needs a value.`);i[t.slice(2)]=a,e+=1}return{command:t,options:i}}async function a(r){let a=i(r);if(a.help){process.stdout.write(`${n}\n`);return}if(a.command!==`build`)throw Error(`Unknown command ${a.command}. The only command is build.`);if(!a.options.config||!a.options.out)throw Error(`build needs --config and --out.`);let o=a.options[`style-keys`],s=a.options[`phone-renderer-support`],c=await e({configFile:t(a.options.config),outDir:t(a.options.out),styleKeysFile:o===void 0?void 0:t(o),phoneRendererSupportFile:s===void 0?void 0:t(s)});for(let e of c.warnings)process.stderr.write(`warning: ${e}\n`);process.stdout.write(`Built ${c.release.name} (${c.release.platform}): ${c.manifest.components.length} components. Release written to ${c.outDir}.\n`)}a(process.argv.slice(2)).catch(e=>{process.stderr.write(`canvas-kit: ${e.message}\n`),process.exitCode=1});export{};
7
+ --style-keys: the style key table the renderer draws from. Optional.
8
+ --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
+ 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{};
package/dist/drawn.js ADDED
@@ -0,0 +1,4 @@
1
+ import{readJson as e,toPosix as t}from"./files.js";import{TSC as n,specifierOf as r}from"./metaCheck.js";import{existsSync as i,mkdirSync as a,rmSync as o,writeFileSync as s}from"node:fs";import{join as c}from"node:path";import{execFileSync as l}from"node:child_process";var u=`src/components/not-drawn.json`;function d({root:e,typesFile:i}){let u=c(e,`node_modules`,`.canvas-kit-exports-${process.pid}`);try{a(u,{recursive:!0});let e=[`import type * as L from "${r(i)}";`,`type IsComponent<T> = 0 extends 1 & T ? true : T extends (...args: never[]) => unknown ? true : T extends abstract new (...args: never[]) => unknown ? true : T extends { $$typeof: symbol } ? true : false;`,`type Named<K> = K extends string ? (K extends Capitalize<K> ? (K extends Uncapitalize<K> ? never : K) : never) : never;`,`type Components = { [K in keyof typeof L]: Named<K> extends never ? never : IsComponent<(typeof L)[K]> extends true ? K : never }[keyof typeof L];`,`export const components: never = null as unknown as { [K in Components]: K };`];s(c(u,`check.ts`),`${e.join(`
2
+ `)}\n`),s(c(u,`tsconfig.json`),JSON.stringify({compilerOptions:{strict:!0,noEmit:!0,allowJs:!0,checkJs:!1,jsx:`react-jsx`,module:`esnext`,moduleResolution:`bundler`,target:`es2022`,skipLibCheck:!0,noErrorTruncation:!0,types:[]},files:[`check.ts`]}));let o=``;try{l(process.execPath,[n,`-p`,c(u,`tsconfig.json`),`--pretty`,`false`],{cwd:u,encoding:`utf8`,stdio:`pipe`})}catch(e){o=String(e.stdout??``)+String(e.stderr??``)}let d=o.split(/\r?\n/).find(e=>/^check\.ts\(5,\d+\): error TS2322: /.test(e)),f=d&&/Type '(\{[^']*\})' is not assignable to type 'never'/.exec(d);if(!f)throw Error(`tsc could not list the components ${t(i)} exports: ${o.split(/\r?\n/).slice(0,20).join(`
3
+ `)}`);return[...new Set([...f[1].matchAll(/(\w+): "\w+"/g)].map(e=>e[1]))].sort()}finally{o(u,{recursive:!0,force:!0})}}function f(t){if(!i(t))return{};let n=e(t);if(typeof n!=`object`||!n||Array.isArray(n))throw Error(`${t} must be an object from component name to the reason the kit does not draw it.`);let r=Object.entries(n).filter(([,e])=>typeof e!=`string`||e.trim()===``).map(([e])=>e);if(r.length>0)throw Error(`${t}: ${r.join(`, `)} need a reason the kit does not draw them.`);return n}function p({libraryPackage:e,exported:t,metaNames:n,notDrawn:r}){let i=new Set(t),a=new Set(n),o=Object.keys(r),s=[],c=t.filter(e=>!a.has(e)&&!(e in r));c.length>0&&s.push(`${e} exports ${c.join(`, `)} with no meta. Write a meta for each, or list it in ${u} with the reason the kit does not draw it.`);let l=o.filter(e=>a.has(e));l.length>0&&s.push(`${u} lists ${l.join(`, `)}, which have a meta. A component is drawn or not drawn; remove one.`);let d=o.filter(e=>!i.has(e));d.length>0&&s.push(`${u} lists ${d.join(`, `)}, which ${e} does not export as components. Remove them.`);let f=n.filter(e=>!i.has(e));if(f.length>0&&s.push(`${f.join(`, `)} have a meta, but ${e} does not export them as components.`),s.length>0)throw Error(s.join(`
4
+ `))}export{u as NOT_DRAWN_FILE,p as checkDrawn,d as componentExports,f as readNotDrawn};
@@ -0,0 +1,10 @@
1
+ import type { ComponentMeta, ComponentUsage } from "./meta.js";
2
+
3
+ export interface GuidanceFile {
4
+ format: "webority-guidance/1";
5
+ package: { name: string; version: string };
6
+ components: { name: string; usage: ComponentUsage }[];
7
+ }
8
+
9
+ export declare function guidanceOf(metas: { meta: ComponentMeta }[], pkg: { name: string; version: string }): GuidanceFile;
10
+ export declare function writeGuidance(options: { configFile: string; outFile: string }): Promise<GuidanceFile>;
@@ -0,0 +1 @@
1
+ import{findPackageDir as e,readJson as t}from"./files.js";import{readMetas as n}from"./metas.js";import{mkdirSync as r,writeFileSync as i}from"node:fs";import{dirname as a,join as o,resolve as s}from"node:path";function c(e){return typeof e==`string`&&e.length>0}function l(e,t){return e.name<t.name?-1:+(e.name>t.name)}function u(e,t){return{format:`webority-guidance/1`,package:{name:t.name,version:t.version},components:e.map(({meta:e})=>({name:e.name,usage:e.usage})).sort(l)}}function d(n,r){let i=[];for(let a of Object.keys({...r.dependencies,...r.peerDependencies})){let r=e(n,a);if(!r)continue;let s=t(o(r,`package.json`)).exports?.[`./guidance.json`];typeof s==`string`&&i.push(...t(o(r,s)).components.map(e=>e.name))}return i}async function f({configFile:e,outFile:l}){let f=t(s(e));if(typeof f!=`object`||!f||!c(f.components))throw Error(`${e} needs the key components, a glob of .meta.js files.`);let p=a(s(e)),m=o(p,`package.json`),h=t(m);if(typeof h!=`object`||!h||!c(h.name)||!c(h.version))throw Error(`${m} needs a name and a version; the guidance file is stamped with them.`);let g=u(await n({root:p,components:f.components,knownNames:d(p,h)}),{name:h.name,version:h.version});return r(a(s(l)),{recursive:!0}),i(s(l),`${JSON.stringify(g,null,2)}\n`),g}export{u as guidanceOf,f as writeGuidance};
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{KIT_MODULE_EXTERNALS as e,defineKit as t,kitModuleVersion as n}from"./kitModule.js";import{readManifest as r}from"./manifest.js";import{readReleaseManifest as i}from"./release.js";import{buildKitRelease as a,readConfig as o}from"./build.js";export{e as KIT_MODULE_EXTERNALS,a as buildKitRelease,t as defineKit,n as kitModuleVersion,o as readConfig,r as readManifest,i as readReleaseManifest};
1
+ import{KIT_MODULE_EXTERNALS as e,defineKit as t,kitModuleVersion as n}from"./kitModule.js";import{checkMetaTypes as r}from"./metaCheck.js";import{HTML_TAGS as i,readMetas as a}from"./metas.js";import{readManifest as o}from"./manifest.js";import{readReleaseManifest as s}from"./release.js";import{buildKitRelease as c,readConfig as l}from"./build.js";import{guidanceOf as u,writeGuidance as d}from"./guidance.js";export{i as HTML_TAGS,e as KIT_MODULE_EXTERNALS,c as buildKitRelease,r as checkMetaTypes,t as defineKit,u as guidanceOf,n as kitModuleVersion,l as readConfig,o as readManifest,a as readMetas,s as readReleaseManifest,d as writeGuidance};
@@ -0,0 +1,162 @@
1
+ import type { ComponentUsage } from "./meta.js";
2
+
3
+ // The manifest a kit release folder carries as manifest.json. build.js writes it for a product kit, and
4
+ // publish-fleet-kit.mjs writes it for a fleet kit. The release row holds the same JSON, and the CLI reads an offline copy.
5
+
6
+ // Which kit a document's root carries. A product kit is "Product"; the two fleet kits are the Webority kits.
7
+ export type CanvasKitKind = "WeborityWeb" | "WeborityPhone" | "Product";
8
+
9
+ export type CanvasKitPlatform = "Web" | "Phone";
10
+
11
+ // The type a prop takes in the manifest. A "url" is a string prop named like a link; an "icon" is a node slot that
12
+ // takes a library icon.
13
+ export type CanvasManifestPropType =
14
+ | "any"
15
+ | "array"
16
+ | "boolean"
17
+ | "enum"
18
+ | "enumOrNumber"
19
+ | "icon"
20
+ | "node"
21
+ | "number"
22
+ | "object"
23
+ | "string"
24
+ | "stringOrNumber"
25
+ | "url";
26
+
27
+ export interface CanvasManifestProp {
28
+ name: string;
29
+ type: CanvasManifestPropType;
30
+ // The values an enum prop takes: strings, numbers (a numeric literal union, sorted), or both. On an enumOrNumber
31
+ // prop, its named values; the prop also takes any number.
32
+ enum?: (string | number)[];
33
+ // The fields of an object prop.
34
+ fields?: CanvasManifestProp[];
35
+ // The fields of each item of an array prop. A list whose items carry href or to takes the action on the Web.
36
+ itemFields?: CanvasManifestProp[];
37
+ required: boolean;
38
+ // The prop takes null as a value. Written only when it does.
39
+ nullable?: true;
40
+ description?: string;
41
+ // The value the component applies when the prop is left out, as JSON.
42
+ default?: unknown;
43
+ }
44
+
45
+ export interface CanvasManifestComponent {
46
+ // The component's name as a document places it.
47
+ name: string;
48
+ // The data-wui value the importer matches: the App* name on the component's outermost element. Null means the
49
+ // importer never places this component.
50
+ marker: string | null;
51
+ acceptsChildren: boolean;
52
+ props: CanvasManifestProp[];
53
+ itemsTakeAction: string[];
54
+ takesAction: string[];
55
+ // The package the component comes from. A product's own component names its source file.
56
+ source: string;
57
+ role?: "title" | "navigation";
58
+ // Present on every Web component; absent on Phone until its metas carry usage.
59
+ usage?: ComponentUsage;
60
+ }
61
+
62
+ // The token names of a kit's vocabulary, by group. The colour names carry their meanings from the theme.
63
+ export interface CanvasManifestTokens {
64
+ color: string[];
65
+ space: string[];
66
+ radius: string[];
67
+ shadow: string[];
68
+ font: string[];
69
+ text: string[];
70
+ colorMeanings?: Record<string, string>;
71
+ }
72
+
73
+ // Each token's value, so the importer can draw a kit's values into a file's theme. Null on a release built before
74
+ // this field existed; a token lift from such a release is refused until the release is rebuilt.
75
+ export interface CanvasManifestTokenValues {
76
+ color: Record<string, CanvasManifestColorValue>;
77
+ space: Record<string, number>;
78
+ radius: Record<string, number>;
79
+ shadow: Record<string, CanvasShadow | null>;
80
+ font: Record<string, CanvasManifestFontValue>;
81
+ text: Record<string, CanvasManifestTextValue>;
82
+ }
83
+
84
+ // Lengths in px at scale 1. `color` is the resolved colour with its opacity in the alpha.
85
+ export interface CanvasShadow {
86
+ x: number;
87
+ y: number;
88
+ blur: number;
89
+ spread: number;
90
+ color: string;
91
+ // Phone only: the Android elevation, when the token sets one.
92
+ elevation?: number;
93
+ }
94
+
95
+ export interface CanvasManifestColorValue {
96
+ light: string;
97
+ dark: string;
98
+ }
99
+
100
+ export interface CanvasManifestFontValue {
101
+ family: string;
102
+ fallback: string;
103
+ }
104
+
105
+ export interface CanvasManifestTextValue {
106
+ size: number;
107
+ lineHeight?: number | null;
108
+ weight?: number | null;
109
+ }
110
+
111
+ export interface CanvasManifestStyleKey {
112
+ name: string;
113
+ type: string;
114
+ values: string[];
115
+ appliesTo: string;
116
+ phoneSupported: boolean;
117
+ description: string;
118
+ }
119
+
120
+ export interface CanvasManifest {
121
+ // The document kind the release serves: a fleet kit's kind, or "Product" for a product kit.
122
+ kind: CanvasKitKind;
123
+ library: {
124
+ name: string;
125
+ version: string;
126
+ };
127
+ rendererProtocolVersion: number;
128
+ platform: CanvasKitPlatform;
129
+ kitModuleVersion: number;
130
+ productKit: null;
131
+ components: CanvasManifestComponent[];
132
+ tokens: CanvasManifestTokens;
133
+ tokenValues: CanvasManifestTokenValues | null;
134
+ icons: string[];
135
+ // A product release no longer carries style keys; the embedded Webority manifests keep them as the renderer's table.
136
+ styleKeys?: CanvasManifestStyleKey[];
137
+ }
138
+
139
+ export interface CanvasReleaseFileStamp {
140
+ sha256: string;
141
+ bytes: number;
142
+ }
143
+
144
+ // release.json: stamps each content file with its SHA-256 and byte count. A file that no longer matches its stamp
145
+ // makes the release unreadable, because a published release never changes.
146
+ export interface CanvasReleaseStamp {
147
+ // The kit key, which is also the release folder's first level under Webority.Canvas.Kits.
148
+ name: string;
149
+ version: string;
150
+ platform: CanvasKitPlatform;
151
+ kitModuleVersion: number;
152
+ rendererProtocolVersion: number;
153
+ library: {
154
+ name: string;
155
+ version: string;
156
+ };
157
+ files: {
158
+ "manifest.json": CanvasReleaseFileStamp;
159
+ "kit.js": CanvasReleaseFileStamp;
160
+ "kit.css": CanvasReleaseFileStamp;
161
+ };
162
+ }
package/dist/manifest.js CHANGED
@@ -1 +1 @@
1
- import{findPackageDir as e,readJson as t,readText as n,toPosix as r}from"./files.js";import"./kitModule.js";import{collectComponents as i}from"./declarations.js";import{PHONE_EXCLUDED_COMPONENTS as a,applyPhoneActions as o,phoneChromeEntries as s,phoneIcons as c,phoneTokenValues as l,phoneTokens as u}from"./phone.js";import{colourMeanings as ee,webIcons as te,webTokenValues as ne,webTokens as d}from"./tokens.js";import{createRequire as f}from"node:module";import{existsSync as p,mkdtempSync as m,rmSync as h}from"node:fs";import{dirname as g,join as _,relative as v,resolve as y}from"node:path";import{execFileSync as b}from"node:child_process";import{tmpdir as x}from"node:os";import S from"@babel/parser";var C=/^[a-z][a-z0-9-]{1,39}$/,w=[`Web`,`Phone`],T=[`title`,`navigation`],E=_(g(f(import.meta.url).resolve(`typescript/package.json`)),`bin`,`tsc`),D=[`.jsx`,`.js`,`.tsx`,`.ts`,`.mjs`],re=/^App[A-Z]/,ie={Web:`WeborityWeb`,Phone:`WeborityPhone`};function O(n,r){let i=e(n,r);if(!i)throw Error(`${r} is not installed for ${n}. Run pnpm install there.`);let a=t(_(i,`package.json`)),o=(e,t)=>{if(typeof e==`string`)return t==="default"?e:void 0;if(!(!e||typeof e!=`object`))return typeof e[t]==`string`?e[t]:o(e.import,t)??o(e.default,t)},s=a.exports?.[`.`],c=a.types??o(s,`types`),l=o(s,`default`)??a.main;if(!c)throw Error(`${r} declares no types, and the manifest reads its declaration files.`);if(!l)throw Error(`${r} declares no entry file for its runtime.`);let u=o(a.exports?.[`./canvasKit`],`default`);return{dir:i,pkg:a,typesFile:_(i,c),entryFile:_(i,l),canvasKitFile:u?_(i,u):null}}function k(t){let n=e(t,`@webority/theme`);if(!n)throw Error(`@webority/theme is not installed for ${t}. Run pnpm install.`);return n}function A(e,n,r){let i=t(_(e,`package.json`)),a=i.dependencies?.[n]??i.devDependencies?.[n];if(/^\d+\.\d+\.\d+$/.test(a??``)&&a!==r)throw Error(`${n} is pinned to ${a} in package.json but ${r} is installed. Run pnpm install first.`)}function j(e,t){let n=m(_(x(),`canvas-kit-declarations-`));try{b(process.execPath,[E,`--allowJs`,`--declaration`,`--emitDeclarationOnly`,`--jsx`,`react-jsx`,`--module`,`esnext`,`--moduleResolution`,`bundler`,`--target`,`es2022`,`--skipLibCheck`,`--rootDir`,e,`--outDir`,n,t],{cwd:e,encoding:`utf8`,stdio:`pipe`})}catch(e){throw h(n,{recursive:!0,force:!0}),Error(`Could not read the declarations of ${t}: ${e.stdout||e.stderr||e.message}`)}return n}function M(e,t,n){let i=v(t,n).replace(/\.d\.ts$/,``);for(let t of D)if(p(_(e,`${i}${t}`)))return r(`${i}${t}`);throw Error(`No source file for the declarations in ${n}`)}function N(e,t,r){let i=/\.tsx?$/.test(t)?[`jsx`,`typescript`]:[`jsx`],a=S.parse(n(_(e,t)),{sourceType:`module`,plugins:i});for(let e of a.program.body){let t=e.type===`ExpressionStatement`?e.expression:null;if(t?.type===`AssignmentExpression`&&t.left.type===`MemberExpression`&&!t.left.computed&&t.left.object.type===`Identifier`&&t.left.object.name===r&&t.left.property.name===`displayName`&&t.right.type===`StringLiteral`)return t.right.value}return null}function P(e){return e.filter(e=>e.type===`array`&&(e.itemFields??[]).some(e=>[`href`,`to`].includes(e.name))).map(e=>e.name)}function F(e,t){return e.filter(e=>e[t]===!0).map(e=>e.name)}function I(e){return e.map(e=>Object.fromEntries(Object.entries(e).filter(([e])=>e!==`handler`&&e!==`itemHandler`).map(([e,t])=>[e,Array.isArray(t)&&(e===`fields`||e===`itemFields`)?I(t):t])))}function L(e,n){if(!n)return;let r=t(n);if(typeof r!=`object`||!r||Array.isArray(r))throw Error(`${n} must be an object from component name to role.`);for(let[t,i]of Object.entries(r)){let r=e.find(e=>e.name===t);if(!r)throw Error(`${n} names ${t}, which the kit does not have.`);if(!T.includes(i))throw Error(`${n}: ${t} has role ${JSON.stringify(i)}; a role is title or navigation.`);r.role=i}}function R(e){let n=t(e);if(!Array.isArray(n)||n.some(e=>typeof e?.name!=`string`))throw Error(`${e} must be the style key table: a list of style keys, each with a name.`);return n}function z(e){let n=t(e);if(n?.schema!==1)throw Error(`${e} has schema ${n?.schema}; this reader reads schema 1.`);if(!n.itemActions)throw Error(`${e} has no itemActions; the phone kit's action lists come from it.`);return{flags:n.styleKeys??{},itemActions:n.itemActions}}function B(e,t,n){let r=e.map(({name:e})=>e),i=r.filter(e=>typeof t[e]!=`boolean`),a=Object.keys(t).filter(e=>!r.includes(e));if(i.length>0||a.length>0)throw Error(`${n} must list exactly the style keys of the style key table, each true or false. Missing or not a boolean: ${i.join(`, `)||`none`}. Not in the table: ${a.join(`, `)||`none`}.`)}function V(e,t){return e.map(({name:e,type:n,values:r,appliesTo:i,description:a})=>({name:e,type:n,values:r,appliesTo:i,...t?{phoneSupported:t[e]}:{},description:a}))}function H({projectRoot:e,kit:r,platform:f,entry:p,libraryPackage:m,rolesFile:g,rendererProtocolVersion:b,phoneRendererSupportFile:x,styleKeysFile:S}){if(!C.test(r??``))throw Error(`kit ${JSON.stringify(r)} must be 2 to 40 characters: lowercase letters, digits and hyphens, starting with a letter.`);if(!w.includes(f))throw Error(`platform must be Web or Phone, not ${JSON.stringify(f)}.`);if(!Number.isInteger(b)||b<1)throw Error(`rendererProtocolVersion must be a positive integer, not ${JSON.stringify(b)}.`);if(f===`Phone`&&!x)throw Error(`A Phone kit needs phoneRendererSupportFile, the phone renderer's support file.`);let T=y(e),E=O(T,m);A(T,m,E.pkg.version);let D=x?z(x):null,H=f===`Phone`?D:null,U=S?R(S):null;U&&D&&B(U,D.flags,x);let W=i(E.typesFile,{namedConsts:!0,kit:m}),G=f===`Phone`?W.components.filter(({name:e})=>!a.has(e)):W.components,K=[];if(f===`Phone`){if(!E.canvasKitFile)throw Error(`${m} exports no ./canvasKit subpath; a Phone kit takes the device frame parts from it.`);K=s(E.canvasKitFile,m);let e=new Set(G.map(({name:e})=>e)),t=K.filter(({name:t})=>e.has(t)).map(({name:e})=>e);if(t.length>0)throw Error(`${m} exports ${t.join(`, `)} from both its main entry and ./canvasKit.`)}let q={components:[],warnings:[]},J=new Map;if(p!==void 0){let e=y(p);if(v(T,e).startsWith(`..`))throw Error(`The kit entry ${e} is outside the product folder ${T}.`);let t=j(T,e);try{q=i(_(t,v(T,e)).replace(/\.[^./\\]+$/,`.d.ts`),{kit:r}),J=new Map(q.components.map(e=>[e.name,M(T,t,e.file)]))}finally{h(t,{recursive:!0,force:!0})}}let ae=new Set([...G,...K].map(({name:e})=>e)),Y=q.components.filter(({name:e})=>ae.has(e)).map(({name:e})=>e);if(Y.length>0)throw Error(`The kit names ${Y.join(`, `)} as ${m} already has them; rename the product components.`);let X=[...G.map(e=>({...e,source:m,marker:re.test(e.name)?e.name:null})),...q.components.map(e=>{let t=J.get(e.name);return{...e,source:t,marker:N(T,t,e.name)}})].map(({name:e,marker:t,acceptsChildren:n,props:r,source:i})=>({name:e,marker:t,acceptsChildren:n,props:I(r),itemsTakeAction:f===`Web`?P(r):F(r,`itemHandler`),takesAction:f===`Web`?[]:F(r,`handler`),source:i})).concat(K).sort((e,t)=>e.name<t.name?-1:+(e.name>t.name));H&&o(X,H.itemActions),L(X,g);let Z,Q,$;if(f===`Web`){let e=k(T);Z=d(n(_(e,`scss`,`_tokens.scss`))),Z.colorMeanings=ee(Z.color,t(_(e,`token-meanings.json`))),Q=ne(_(e,`dist`,`published-theme.json`),Z),$=te(_(E.dir,`dist`))}else{let e=_(E.dir,`lib`,`module`,`theme`,`tokens.js`);Z=u(e),Q=l(e),$=c(_(E.dir,`lib`,`typescript`,`module`,`components`,`AppIcon`,`catalog.g.d.ts`))}return{manifest:{kind:p===void 0?ie[f]:`Product`,library:{name:E.pkg.name,version:E.pkg.version},rendererProtocolVersion:b,platform:f,kitModuleVersion:2,productKit:null,components:X,tokens:Z,tokenValues:Q,icons:$,...U?{styleKeys:V(U,H?H.flags:null)}:{}},warnings:[...W.warnings,...q.warnings],phoneChromeNames:K.map(({name:e})=>e)}}export{O as libraryFiles,H as readManifest,z as readPhoneSupport};
1
+ import{findPackageDir as e,readJson as t,toPosix as n}from"./files.js";import"./kitModule.js";import{checkMetaTypes as r}from"./metaCheck.js";import{NOT_DRAWN_FILE as i,checkDrawn as a,componentExports as o,readNotDrawn as s}from"./drawn.js";import{componentModuleOf as c,manifestComponent as l,readMetas as u}from"./metas.js";import{applyPhoneActions as d,phoneChromeEntries as f,phoneIcons as p}from"./phone.js";import{tokensFromV2 as m,webIcons as h}from"./tokens.js";import{existsSync as g}from"node:fs";import{join as _,relative as v,resolve as y}from"node:path";var b=/^[a-z][a-z0-9-]{1,39}$/,ee=[`Web`,`Phone`],x=[`title`,`navigation`],S={Web:`WeborityWeb`,Phone:`WeborityPhone`},C={Web:`web`,Phone:`phone`};function w(n,r){let i=_(n,`package.json`),a=g(i)&&t(i).name===r,o=a?n:e(n,r);if(!o)throw Error(`${r} is not installed for ${n}. Run pnpm install there.`);let s=t(_(o,`package.json`)),c=(e,t)=>{if(typeof e==`string`)return t==="default"?e:void 0;if(!(!e||typeof e!=`object`))return typeof e[t]==`string`?e[t]:c(e.import,t)??c(e.default,t)},l=s.exports?.[`.`],u=s.types??c(l,`types`),d=c(l,`default`)??s.main;if(!u)throw Error(`${r} declares no types, and the manifest reads its declaration files.`);if(!d)throw Error(`${r} declares no entry file for its runtime.`);let f=c(s.exports?.[`./canvasKit`],`default`);return{dir:o,pkg:s,isSelf:a,typesFile:_(o,u),entryFile:_(o,d),canvasKitFile:f?_(o,f):null}}function T(e,n,r){let i=t(_(e,`package.json`)),a=i.dependencies?.[n]??i.devDependencies?.[n];if(/^\d+\.\d+\.\d+$/.test(a??``)&&a!==r)throw Error(`${n} is pinned to ${a} in package.json but ${r} is installed. Run pnpm install first.`)}function E(e,n,r){let i=e.pkg.exports?.[`./canvas-kit/*`];if(typeof i!=`string`)throw Error(`${n} exports no ./canvas-kit/*; a product kit takes the library's components from its kit release.`);let a=_(e.dir,i.replace(`*`,`manifest.json`)),o=t(a);if(!(o?.kind===S[r]&&o.platform===r&&Array.isArray(o.components)&&Array.isArray(o.icons)))throw Error(`${a} is not a ${r} library kit release.`);return o}function D(e,t){let n=C[t];for(let{meta:r,file:i}of e)if(!r.platforms?.includes(n))throw Error(`${i}: platforms: a ${t} kit lists only components that list ${n}`)}function O(e,n){if(!n)return;let r=t(n);if(typeof r!=`object`||!r||Array.isArray(r))throw Error(`${n} must be an object from component name to role.`);for(let[t,i]of Object.entries(r)){let r=e.find(e=>e.name===t);if(!r)throw Error(`${n} names ${t}, which the kit does not have.`);if(!x.includes(i))throw Error(`${n}: ${t} has role ${JSON.stringify(i)}; a role is title or navigation.`);r.role=i}}function k(e){let n=t(e);if(!Array.isArray(n)||n.some(e=>typeof e?.name!=`string`))throw Error(`${e} must be the style key table: a list of style keys, each with a name.`);return n}function A(e){let n=t(e);if(n?.schema!==1)throw Error(`${e} has schema ${n?.schema}; this reader reads schema 1.`);if(!n.itemActions)throw Error(`${e} has no itemActions; the phone kit's action lists come from it.`);return{flags:n.styleKeys??{},itemActions:n.itemActions}}function j(e,t,n){let r=e.map(({name:e})=>e),i=r.filter(e=>typeof t[e]!=`boolean`),a=Object.keys(t).filter(e=>!r.includes(e));if(i.length>0||a.length>0)throw Error(`${n} must list exactly the style keys of the style key table, each true or false. Missing or not a boolean: ${i.join(`, `)||`none`}. Not in the table: ${a.join(`, `)||`none`}.`)}function te(e,t){return e.map(({name:e,type:n,values:r,appliesTo:i,description:a})=>({name:e,type:n,values:r,appliesTo:i,...t?{phoneSupported:t[e]}:{},description:a}))}var ne=(e,t)=>e.name<t.name?-1:+(e.name>t.name);async function M({projectRoot:t,kit:g,platform:x,entry:C,components:M,tokens:N,libraryPackage:P,rolesFile:F,rendererProtocolVersion:I,phoneRendererSupportFile:L,styleKeysFile:R}){if(!b.test(g??``))throw Error(`kit ${JSON.stringify(g)} must be 2 to 40 characters: lowercase letters, digits and hyphens, starting with a letter.`);if(!ee.includes(x))throw Error(`platform must be Web or Phone, not ${JSON.stringify(x)}.`);if(!Number.isInteger(I)||I<1)throw Error(`rendererProtocolVersion must be a positive integer, not ${JSON.stringify(I)}.`);if(C!==void 0)throw Error(`A kit lists its components with components, a glob of .meta.js files; entry is retired.`);if(typeof M!=`string`||M===``||typeof N!=`string`||N===``)throw Error(`A kit needs components, a glob of .meta.js files, and tokens, the path of its webority-theme-tokens/2 file.`);let z=y(t),B=w(z,P),V=B.isSelf;if(x===`Phone`&&!L&&!V)throw Error(`A Phone product kit needs phoneRendererSupportFile, the phone renderer's support file; only a phone library's own kit release builds without it.`);V||T(z,P,B.pkg.version);let H=L?A(L):null,U=x===`Phone`?H:null,W=R?k(R):null;W&&H&&j(W,H.flags,L);let G=V?null:E(B,P,x),K;K=V?x===`Web`?h(_(B.dir,`dist`)):p(_(B.dir,`lib`,`typescript`,`module`,`components`,`AppIcon`,`catalog.g.d.ts`)):G.icons;let q=[];V&&(q=await u({root:z,components:M,icons:K}),D(q,x),a({libraryPackage:P,exported:o({root:z,typesFile:B.typesFile}),metaNames:q.map(({meta:e})=>e.name),notDrawn:s(_(B.dir,i))}),x===`Web`&&r({root:z,metas:q,componentOf:()=>B.typesFile}));let J=[],Y=new Map;if(!V){let e=G.components.map(({name:e})=>e);J=await u({root:z,components:M,knownNames:e,icons:K}),D(J,x);let t=J.map(({meta:e})=>e.name).filter(t=>e.includes(t));if(t.length>0)throw Error(`The kit names ${t.join(`, `)} as ${P} already has them; rename the product components.`);x===`Web`&&r({root:z,metas:J,componentOf:(e,t)=>c(e,t)});for(let{meta:e,file:t}of J)Y.set(e.name,{module:c(t,e.name),exportName:e.name})}let X=[];if(x===`Phone`){if(!B.canvasKitFile)throw Error(`${P} exports no ./canvasKit subpath; a Phone kit takes the device frame parts from it.`);X=f(B.canvasKitFile,P)}if(V&&x===`Phone`){let e=new Set(q.map(({meta:e})=>e.name)),t=X.filter(({name:t})=>e.has(t)).map(({name:e})=>e);if(t.length>0)throw Error(`${P} exports ${t.join(`, `)} from both its main entry and ./canvasKit.`)}let re=V?q.map(({meta:e})=>l(e,{platform:x,source:P})):G.components,ie=J.map(({meta:e})=>l(e,{platform:x,source:n(v(z,Y.get(e.name).module))})),Z=[...re,...V?X:[],...ie].sort(ne);U&&d(Z,U.itemActions),O(Z,F);let Q=e(z,`@webority/tokens`);if(!Q)throw Error(`@webority/tokens is not installed for ${z}. Run pnpm install there.`);let $=m(y(z,N),_(Q,`dist`,`meanings.json`),x);return{manifest:{kind:V?S[x]:`Product`,library:{name:B.pkg.name,version:B.pkg.version},rendererProtocolVersion:I,platform:x,kitModuleVersion:2,productKit:null,components:Z,tokens:$.tokens,tokenValues:$.tokenValues,icons:K,...W?{styleKeys:te(W,U?U.flags:null)}:{}},warnings:[],phoneChromeNames:X.map(({name:e})=>e),productModules:Y,metas:V?q:J}}export{w as libraryFiles,M as readManifest,A as readPhoneSupport};
package/dist/meta.d.ts ADDED
@@ -0,0 +1,86 @@
1
+ /** A prop type, in the kit manifest wire names (CanvasManifestProp.Type). Event handlers are never described: DesignKeys leaves them out. */
2
+ export type MetaPropType =
3
+ | "string"
4
+ | "number"
5
+ | "stringOrNumber"
6
+ | "boolean"
7
+ | "enum"
8
+ | "enumOrNumber"
9
+ | "url"
10
+ | "icon"
11
+ | "node"
12
+ | "array"
13
+ | "object"
14
+ | "any";
15
+
16
+ export interface MetaProp {
17
+ type: MetaPropType;
18
+ /**
19
+ * The values of an enum, in the order the component lists them; a numeric literal union lists numbers.
20
+ * On an enumOrNumber, the named values only: the prop also takes any number. Only on these two types, and required there.
21
+ */
22
+ values?: readonly (string | number)[];
23
+ /** The value the component applies when the prop is left out, as JSON. Never on a required prop. */
24
+ default?: unknown;
25
+ required?: boolean;
26
+ /** The component takes null for it: a value that is always sent and may be empty. Examples may then set null. */
27
+ nullable?: boolean;
28
+ description: string;
29
+ /** The fields of an object prop. Only on an object. */
30
+ fields?: Readonly<Record<string, MetaProp>>;
31
+ /** The fields of each item of a list whose items are objects. Only on an array. */
32
+ itemFields?: Readonly<Record<string, MetaProp>>;
33
+ }
34
+
35
+ /** A placed element: a component of the kit, or one of the HTML tags in HTML_TAGS. A node or icon prop value may be a MetaNode. */
36
+ export interface MetaNode {
37
+ component: string;
38
+ props?: Readonly<Record<string, unknown>>;
39
+ /** Text, or a list of placed elements and text items. */
40
+ children?: string | readonly (MetaNode | string)[];
41
+ /** Function props a renderer attaches as a no-op so the demo draws complete (a remove button, a retry action). Each must be a function prop of the component. */
42
+ handlers?: readonly string[];
43
+ }
44
+
45
+ export interface MetaExample {
46
+ /** The parity case id: the component name for its base example, "<name>.<case>" for the others. */
47
+ id: string;
48
+ title: string;
49
+ props: Readonly<Record<string, unknown>>;
50
+ children?: string | readonly (MetaNode | string)[];
51
+ /** An element the example is placed inside, for a component that only renders within a parent. */
52
+ wrap?: MetaNode;
53
+ /** Function props a renderer attaches as a no-op so the demo draws complete (a remove button, a retry action). Each must be a function prop of the component. */
54
+ handlers?: readonly string[];
55
+ }
56
+
57
+ /** One side of a do and dont pair; do[i] is shown beside dont[i]. Example props may be scalars, icons, nodes, or arrays and objects checked against the prop's itemFields or fields. */
58
+ export interface MetaPractice {
59
+ note: string;
60
+ example: readonly MetaNode[];
61
+ }
62
+
63
+ export interface ComponentUsage {
64
+ when: readonly string[];
65
+ whenNot: readonly { text: string; instead?: string }[];
66
+ do: readonly MetaPractice[];
67
+ dont: readonly MetaPractice[];
68
+ a11y: readonly string[];
69
+ }
70
+
71
+ export interface ComponentMeta {
72
+ /** The catalogue name, such as AppButton. */
73
+ name: string;
74
+ /** The data-wui value the component renders, or null when it renders none. */
75
+ marker: string | null;
76
+ platforms: readonly ("web" | "phone")[];
77
+ acceptsChildren: boolean;
78
+ props: Readonly<Record<string, MetaProp>>;
79
+ /** Design props deliberately not shown in the kit, such as a render prop no kit type fits. The type check accepts them unlisted. */
80
+ omit?: readonly string[];
81
+ examples: readonly MetaExample[];
82
+ usage: ComponentUsage;
83
+ }
84
+
85
+ /** Returns the meta unchanged; the const parameter keeps literal types for the type check. */
86
+ export declare function defineMeta<const M extends ComponentMeta>(meta: M): M;
package/dist/meta.js ADDED
@@ -0,0 +1 @@
1
+ function e(e){return e}export{e as defineMeta};
@@ -0,0 +1,118 @@
1
+ // Type rules that prove a meta equals its component's typed props. P is the props type (React ComponentProps of
2
+ // the component), N is React's ReactNode, both supplied by the generated check file so this file imports no react.
3
+ import type { ComponentMeta, MetaProp } from "./meta.js";
4
+
5
+ type IsAny<T> = 0 extends 1 & T ? true : false;
6
+ type Same<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
7
+ type Handler = (...args: never[]) => unknown;
8
+ // React's ElementType and ComponentType both carry a class constructor member; a render prop never does.
9
+ type Constructor = abstract new (...args: never[]) => unknown;
10
+ type Defined<T> = Exclude<T, null | undefined>;
11
+ type Shape = Readonly<Record<string, MetaProp>>;
12
+
13
+ /** Every field a design sets at any depth: all but event handlers and component-typed fields. */
14
+ type FieldKeys<P> = {
15
+ [K in keyof P]-?: IsAny<P[K]> extends true
16
+ ? K
17
+ : [Defined<P[K]>] extends [Handler]
18
+ ? never
19
+ : [Extract<Defined<P[K]>, Constructor>] extends [never]
20
+ ? K
21
+ : never;
22
+ }[keyof P] &
23
+ string;
24
+
25
+ /** Every prop a design sets: the field keys, less React's children, ref and key (a nested data field may be named key). */
26
+ export type DesignKeys<P> = Exclude<FieldKeys<P>, "children" | "ref" | "key">;
27
+
28
+ /** Every event handler prop of the component: the names a meta's handlers list may use. */
29
+ export type HandlerKeys<P> = { [K in keyof P]-?: NonNullable<P[K]> extends Handler ? K : never }[keyof P] & string;
30
+
31
+ type RequiredKeys<P> = { [K in keyof P]-?: Partial<Pick<P, K>> extends Pick<P, K> ? never : K }[keyof P];
32
+ type ItemOf<T> = Defined<T> extends readonly (infer I)[] ? I : Defined<T> extends ReadonlySet<infer I> ? I : never;
33
+
34
+ // "any" is a meta's explicit opt-out for a value no meta type can state (a union of a File, an object and an array):
35
+ // Canvas passes it through unchanged, so only its presence and required flag are checked.
36
+ type ValueOk<T, M extends MetaProp, N> = M["type"] extends "any"
37
+ ? true
38
+ : IsAny<T> extends true
39
+ ? false
40
+ : M["type"] extends "enum"
41
+ ? Same<Defined<T>, NonNullable<M["values"]>[number]>
42
+ : M["type"] extends "enumOrNumber"
43
+ ? Same<Defined<T>, NonNullable<M["values"]>[number] | number>
44
+ : M["type"] extends "boolean"
45
+ ? Same<Defined<T>, boolean>
46
+ : M["type"] extends "number"
47
+ ? Same<Defined<T>, number>
48
+ : M["type"] extends "stringOrNumber"
49
+ ? // A Date prop is written as an ISO string in a meta, so string | number | Date is a stringOrNumber too.
50
+ Same<Defined<T>, string | number> extends true
51
+ ? true
52
+ : Same<Defined<T>, string | number | Date>
53
+ : M["type"] extends "string" | "url"
54
+ ? Same<Defined<T>, string>
55
+ : M["type"] extends "icon"
56
+ ? string extends Defined<T>
57
+ ? true
58
+ : false
59
+ : M["type"] extends "node"
60
+ ? Defined<N> extends Defined<T>
61
+ ? true
62
+ : false
63
+ : M["type"] extends "array"
64
+ ? [Defined<T>] extends [readonly unknown[] | ReadonlySet<unknown>]
65
+ ? true
66
+ : false
67
+ : M["type"] extends "object"
68
+ ? [Defined<T>] extends [readonly unknown[] | ReadonlySet<unknown> | Handler]
69
+ ? false
70
+ : [Defined<T>] extends [object]
71
+ ? true
72
+ : false
73
+ : false;
74
+
75
+ type RequiredOk<P, K extends keyof P, M extends MetaProp> = Same<
76
+ K extends RequiredKeys<P> ? true : false,
77
+ M["required"] extends true ? true : false
78
+ >;
79
+
80
+ // ReactNode carries null itself, so a prop that takes any node (a node, or an icon given as a name or a node) is
81
+ // empty-able by its type, not nullable in its own right. "any" opts out: its value is passed through unchecked.
82
+ type OwnNull<T, N> = null extends T ? (Defined<N> extends Defined<T> ? false : true) : false;
83
+ type NullableOk<T, M extends MetaProp, N> = M["type"] extends "any" ? true : Same<OwnNull<T, N>, M["nullable"] extends true ? true : false>;
84
+
85
+ type NestedOk<T, M extends MetaProp, N> = M["type"] extends "object"
86
+ ? M["fields"] extends Shape
87
+ ? ShapeOk<Defined<T>, M["fields"], N>
88
+ : true
89
+ : M["type"] extends "array"
90
+ ? M["itemFields"] extends Shape
91
+ ? ShapeOk<ItemOf<T>, M["itemFields"], N>
92
+ : true
93
+ : true;
94
+
95
+ type ShapeOk<T, F extends Shape, N> =
96
+ Same<FieldKeys<T>, keyof F & string> extends true ? ([PropsMismatch<T, F, N>] extends [never] ? true : false) : false;
97
+
98
+ /** The names of meta props whose type, required or nullable flag, or nested fields differ from the component's. */
99
+ export type PropsMismatch<P, F extends Shape, N> = {
100
+ [K in keyof F & keyof P & string]: ValueOk<P[K], F[K], N> extends true
101
+ ? RequiredOk<P, K, F[K]> extends true
102
+ ? NullableOk<P[K], F[K], N> extends true
103
+ ? NestedOk<P[K], F[K], N> extends true
104
+ ? never
105
+ : K
106
+ : K
107
+ : K
108
+ : K;
109
+ }[keyof F & keyof P & string];
110
+
111
+ // A meta with no omit list would make NonNullable<M["omit"]>[number] collapse ExtraProps to never.
112
+ type OmitOf<M extends ComponentMeta> = M extends { readonly omit: infer O extends readonly string[] } ? O[number] : never;
113
+ /** Props the component takes that the meta neither lists nor omits. */
114
+ export type ExtraProps<P, M extends ComponentMeta> = Exclude<DesignKeys<P>, keyof M["props"] | OmitOf<M>>;
115
+ /** Props the meta lists that the component does not take. */
116
+ export type MissingProps<P, M extends ComponentMeta> = Exclude<keyof M["props"] & string, DesignKeys<P>>;
117
+ export type MismatchedProps<P, M extends ComponentMeta, N> = PropsMismatch<P, M["props"], N>;
118
+ export type ChildrenOk<P, M extends ComponentMeta> = Same<"children" extends keyof P ? true : false, M["acceptsChildren"]>;
@@ -0,0 +1,4 @@
1
+ import{toPosix as e}from"./files.js";import{createRequire as t}from"node:module";import{mkdirSync as n,rmSync as r,writeFileSync as i}from"node:fs";import{dirname as a,join as o}from"node:path";import{fileURLToPath as s}from"node:url";import{execFileSync as c}from"node:child_process";var l=o(a(t(import.meta.url).resolve(`typescript/package.json`)),`bin`,`tsc`),u=o(a(s(import.meta.url)),`metaCheck.js`),d=/^check\.ts\((\d+),\d+\): error TS\d+: (.*)$/,f=/^(.+?)\(\d+,\d+\): error TS\d+: (.*)$/,p=[`propsNotInMeta`,`metaPropsNotOnComponent`,`propsThatDiffer`,`children`],m={propsNotInMeta:`props the component takes that the meta does not list`,metaPropsNotOnComponent:`props the meta lists that the component does not take`,propsThatDiffer:`props whose type, required or nullable flag, or nested fields differ from the component`};function h(t){let n=e(t);return/\.d\.ts$/.test(n)?n.replace(/\.d\.ts$/,``):n.replace(/\.(jsx|tsx|js|ts|mjs)$/,``)}function g(e){let t=[];for(let n of e.split(/\r?\n/)){let e=d.exec(n);if(e){t.push({line:Number(e[1]),file:null,message:e[2]});continue}let r=f.exec(n);r&&t.push({line:null,file:r[1],message:r[2]})}return t}function _(e){let t=/Type '(\{[^']*\})'/.exec(e);return t?[...t[1].matchAll(/: "([^"]+)"/g)].map(e=>e[1]):[]}function v(e){let t=new Map,n=(e,n,r,i)=>{let a=`${e} ${n}`;t.has(a)||t.set(a,{component:e,handler:n,path:r,owner:i})},r=(e,t,i)=>{if(!(typeof e!=`object`||!e)){for(let r of e.handlers??[])n(e.component,r,t,i);Array.isArray(e.children)&&e.children.forEach((e,n)=>r(e,`${t}.children[${n}]`,i))}};return e.forEach(({meta:e},t)=>{(e.examples??[]).forEach((i,a)=>{let o=`examples[${a}]`;for(let r of i.handlers??[])n(e.name,r,o,t);r({children:i.children},o,t),r(i.wrap,`${o}.wrap`,t)});for(let n of[`do`,`dont`])(e.usage?.[n]??[]).forEach((e,i)=>{(e.example??[]).forEach((e,a)=>r(e,`usage.${n}[${i}].example[${a}]`,t))})}),t}function y({root:t,metas:a,componentOf:s}){let d=o(t,`node_modules`,`.canvas-kit-check-${process.pid}`);try{n(d,{recursive:!0});let t=a.map(({meta:e,file:t})=>({component:h(s(t,e.name)),meta:h(t)})),r=[],f=new Map,y=new Map,b=(e,t=-1,n=null)=>{r.push(e);let i=r.length;t>=0&&f.set(i,t),n&&y.set(i,n)};b(`import type { ExtraProps, MissingProps, MismatchedProps, ChildrenOk, HandlerKeys } from "${e(u)}";`),b(`type N = import("react").ReactNode;`),b(`type Empty = { [key: string]: never };`),a.forEach((e,n)=>{b(`import type * as C${n} from "${t[n].component}";`,n),b(`import type { meta as M${n} } from "${t[n].meta}";`,n)});let x=e=>`{ [K in ${e}]: K }`;a.forEach(({meta:e},t)=>{let n=e.name;b(`type P${t} = import("react").ComponentProps<typeof C${t}.${n}>;`,t),b(`export const ${n}__propsNotInMeta: Empty = null as unknown as ${x(`ExtraProps<P${t}, typeof M${t}>`)};`,t,`propsNotInMeta`),b(`export const ${n}__metaPropsNotOnComponent: Empty = null as unknown as ${x(`MissingProps<P${t}, typeof M${t}>`)};`,t,`metaPropsNotOnComponent`),b(`export const ${n}__propsThatDiffer: Empty = null as unknown as ${x(`MismatchedProps<P${t}, typeof M${t}, N>`)};`,t,`propsThatDiffer`),b(`export const ${n}__children: true = null as unknown as ChildrenOk<P${t}, typeof M${t}>;`,t,`children`)});let S=new Map(a.map(({meta:e},t)=>[e.name,t])),C=new Map,w=0;for(let{component:t,handler:n,path:i,owner:o}of v(a).values()){let s=S.get(t);s!==void 0&&(w+=1,b(`export const ${t.replace(/[^A-Za-z0-9_]/g,`_`)}__handler_${n}_${w}: Empty = null as unknown as { [K in Exclude<"${n}", HandlerKeys<P${s}>>]: K };`),C.set(r.length,{owner:o,where:`${e(a[o].file)}: ${i}`,component:t,handler:n}))}i(o(d,`check.ts`),`${r.join(`
2
+ `)}\n`),i(o(d,`tsconfig.json`),JSON.stringify({compilerOptions:{strict:!0,noEmit:!0,allowJs:!0,checkJs:!1,jsx:`react-jsx`,module:`esnext`,moduleResolution:`bundler`,target:`es2022`,skipLibCheck:!0,noErrorTruncation:!0,types:[]},files:[`check.ts`]},null,2));let T;try{c(process.execPath,[l,`-p`,o(d,`tsconfig.json`),`--pretty`,`false`],{cwd:d,encoding:`utf8`,stdio:`pipe`});return}catch(e){T=String(e.stdout??``)+String(e.stderr??``)}let E=new Map,D=[];for(let n of g(T)){let r=n.line===null?void 0:C.get(n.line);if(r){D.push({owner:r.owner,rank:p.length,text:`${r.where}: "${r.handler}" is not a function prop of ${r.component}`});continue}let i=n.line!==null&&f.has(n.line)?f.get(n.line):void 0,o=n.line===null?void 0:y.get(n.line);if(o&&i!==void 0){let e=`${i}:${o}`;if(E.has(e)||E.set(e,{owner:i,kind:o,names:new Set}),o!==`children`)for(let t of _(n.message))E.get(e).names.add(t);continue}let s=i??t.findIndex(e=>[n.file,n.message].some(t=>t&&(t.includes(e.component)||t.includes(e.meta)))),c=n.line===null?`${e(n.file)}: `:``;D.push({owner:s,rank:-1,text:`${s>=0?a[s].meta.name:`metaCheck`}: tsc could not read the check file: ${c}${n.message}`})}for(let{owner:e,kind:t,names:n}of E.values()){let{meta:r}=a[e],i=t===`children`?`${r.name}: acceptsChildren is ${r.acceptsChildren} but the component ${r.acceptsChildren?`does not take`:`takes`} children`:n.size>0?`${r.name}: ${m[t]}: ${[...n].sort().join(`, `)}`:`${r.name}: ${m[t]}: see the meta against the component's props`;D.push({owner:e,rank:p.indexOf(t),text:i})}throw D.length===0?Error(`metaCheck: tsc failed: ${T.split(/\r?\n/).slice(0,20).join(`
3
+ `)}`):(D.sort((e,t)=>e.owner-t.owner||e.rank-t.rank),Error(D.map(e=>e.text).join(`
4
+ `)))}finally{r(d,{recursive:!0,force:!0})}}export{l as TSC,y as checkMetaTypes,h as specifierOf};
package/dist/metas.js ADDED
@@ -0,0 +1,2 @@
1
+ import{toPosix as e}from"./files.js";import{existsSync as t,globSync as n}from"node:fs";import{resolve as r}from"node:path";import{pathToFileURL as i}from"node:url";var a=Object.freeze([`a`,`b`,`button`,`div`,`img`,`kbd`,`li`,`p`,`span`,`strong`,`table`,`tbody`,`td`,`th`,`thead`,`tr`,`ul`]),o=/^[A-Z][A-Za-z0-9]*$/,s=/^[A-Za-z0-9][A-Za-z0-9_-]*$/,c=/^App[A-Za-z0-9]*$/,l=/^on[A-Z][A-Za-z0-9]*$/,u=/[\u2013\u2014]/,d=[`string`,`number`,`stringOrNumber`,`boolean`,`enum`,`enumOrNumber`,`url`,`icon`,`node`,`array`,`object`,`any`],f=[`.jsx`,`.tsx`,`.js`,`.ts`];async function p({root:e,components:t,knownNames:a,icons:o}){let s=n(t,{cwd:e});if(s.length===0)throw Error(`${e}: no component metadata matches ${t}`);let c=s.map(t=>r(e,t)).sort(L),l=[];for(let e of c){let t=await import(i(e).href);if(!(`meta`in t))throw Error(`${e}: exports no meta; write export const meta = defineMeta({...})`);l.push({meta:t.meta,file:e})}return m(l,{knownNames:a,icons:o}),l.sort((e,t)=>L(e.meta.name,t.meta.name))}function m(e,{knownNames:t=[],icons:n}={}){let r=new Map;for(let{meta:t}of e)R(t)&&typeof t.name==`string`&&!r.has(t.name)&&r.set(t.name,t);let i={byName:r,knownNames:new Set(t),icons:n},a=new Map,o=new Map,s=[];for(let{meta:t,file:n}of e){let e=[];_(t,n,a,o,i,e);for(let[t,r]of e)s.push(`${n}: ${t}: ${r}`)}if(s.length>0)throw Error(s.join(`
2
+ `))}function h(e,{platform:t,source:n}){let r=Object.keys(e.props).sort(L);return{name:e.name,marker:e.marker,acceptsChildren:e.acceptsChildren,props:r.map(t=>j(t,e.props[t])),itemsTakeAction:t===`Web`?r.filter(t=>P(e.props[t])):[],takesAction:[],source:n,usage:e.usage}}function g(n,r){let i=n.replace(/\.meta\.[jt]s$/,``);for(let n of f)if(t(i+n))return e(i+n);throw Error(`no component module for ${r} beside ${n}`)}function _(e,t,n,r,i,a){if(!R(e)){a.push([`meta`,`must be an object`]);return}let{name:s}=e;typeof s!=`string`||!o.test(s)?a.push([`name`,`"${s}" is not a catalogue name; start with a capital letter and use letters and digits`]):n.has(s)?a.push([`name`,`"${s}" is also the name in ${n.get(s)}`]):n.set(s,t);let{platforms:c}=e;Array.isArray(c)&&c.length>0&&c.every(e=>e===`web`||e===`phone`)||a.push([`platforms`,`must list web, phone or both`]),e.marker!==null&&typeof e.marker!=`string`&&a.push([`marker`,`must be a string or null`]),typeof e.acceptsChildren!=`boolean`&&a.push([`acceptsChildren`,`must be true or false`]),e.omit!==void 0&&(!Array.isArray(e.omit)||!e.omit.every(e=>typeof e==`string`))&&a.push([`omit`,`must be a list of prop names`]);let l=R(e.props)?e.props:{};if(!R(e.props))a.push([`props`,`must be an object`]);else for(let[t,n]of Object.entries(e.props))v(n,`props.${t}`,i,a);Array.isArray(e.examples)?e.examples.forEach((n,o)=>{E(n,`examples[${o}]`,e,l,t,r,i,a)}):a.push([`examples`,`must be a list`]),D(e.usage,i,a),A(e,``,a)}function v(e,t,n,r){if(!R(e)){r.push([t,`is not a prop`]);return}if(!d.includes(e.type)){r.push([`${t}.type`,`${I(e.type)} is not a prop type`]);return}if(e.type===`enum`||e.type===`enumOrNumber`?y(e,t,r):e.values!==void 0&&r.push([`${t}.values`,`only an enum or an enumOrNumber has values`]),e.fields!==void 0)if(e.type!==`object`)r.push([`${t}.fields`,`only an object has fields`]);else for(let[i,a]of Object.entries(e.fields))v(a,`${t}.fields.${i}`,n,r);if(e.itemFields!==void 0)if(e.type!==`array`)r.push([`${t}.itemFields`,`only an array has itemFields`]);else for(let[i,a]of Object.entries(e.itemFields))v(a,`${t}.itemFields.${i}`,n,r);if(B(e.description)||r.push([`${t}.description`,`is empty`]),e.required!==void 0&&typeof e.required!=`boolean`&&r.push([`${t}.required`,`must be true or false`]),e.nullable!==void 0&&typeof e.nullable!=`boolean`&&r.push([`${t}.nullable`,`must be true or false`]),e.default!==void 0)if(e.required===!0)r.push([`${t}.default`,`a required prop has no default`]);else{let i=[];b(e.default,e,t,n,i),i.length>0&&r.push([`${t}.default`,`${I(e.default)} does not fit ${e.type}`])}}function y(e,t,n){let{type:r,values:i}=e;if(!Array.isArray(i)||i.length===0){n.push([`${t}.values`,`an ${r} lists its values`]);return}let a=new Set;for(let e of i)(r===`enum`?typeof e==`string`||z(e):typeof e==`string`)?a.has(e)&&n.push([`${t}.values`,`"${e}" is listed twice`]):n.push([`${t}.values`,`${I(e)} is not allowed here`]),a.add(e)}function b(e,t,n,r,i){if(!R(t)||e===null&&(t.nullable===!0||t.type===`node`||t.type===`icon`))return;let a=Array.isArray(t.values)?t.values:[];switch(t.type){case`string`:case`url`:typeof e!=`string`&&F(e,t.type,n,i);return;case`number`:z(e)||F(e,t.type,n,i);return;case`boolean`:typeof e!=`boolean`&&F(e,t.type,n,i);return;case`stringOrNumber`:typeof e!=`string`&&!z(e)&&F(e,t.type,n,i);return;case`enum`:a.includes(e)||i.push([n,`${I(e)} is not one of ${I(a)}`]);return;case`enumOrNumber`:!z(e)&&!a.includes(e)&&i.push([n,`${I(e)} is not one of ${I(a)}`]);return;case`icon`:V(e)?C(e,n,r,i):typeof e==`string`?Array.isArray(r.icons)&&!r.icons.includes(e)&&i.push([n,`"${e}" is not an icon of the kit`]):F(e,t.type,n,i);return;case`node`:Array.isArray(e)?e.forEach((e,t)=>x(e,`${n}[${t}]`,r,i)):x(e,n,r,i);return;case`array`:Array.isArray(e)?t.itemFields!==void 0&&e.forEach((e,a)=>{R(e)?S(e,t.itemFields,`${n}[${a}]`,r,i):i.push([`${n}[${a}]`,`${I(e)} is not an object`])}):F(e,t.type,n,i);return;case`object`:R(e)?t.fields!==void 0&&S(e,t.fields,n,r,i):F(e,t.type,n,i);return;default:return}}function x(e,t,n,r){V(e)?C(e,t,n,r):typeof e!=`string`&&!z(e)&&r.push([t,`${I(e)} is not a node`])}function S(e,t,n,r,i){for(let a of Object.keys(e))Object.hasOwn(t,a)?b(e[a],t[a],`${n}.${a}`,r,i):i.push([`${n}.${a}`,`not a field`]);for(let[r,a]of Object.entries(t))R(a)&&a.required===!0&&!Object.hasOwn(e,r)&&i.push([`${n}.${r}`,`missing, the field is required`])}function C(e,t,n,r){let{component:i}=e,o=n.byName.get(i);if(!o&&!n.knownNames.has(i)&&!a.includes(i)&&r.push([`${t}.component`,`"${i}" is neither a component of the kit nor one of the HTML tags`]),o&&e.props!==void 0)if(!R(e.props))r.push([`${t}.props`,`must be an object`]);else{let a=R(o.props)?o.props:{};for(let[o,s]of Object.entries(e.props))Object.hasOwn(a,o)?b(s,a[o],`${t}.props.${o}`,n,r):r.push([`${t}.props.${o}`,`not a prop of ${i}`])}e.children!==void 0&&(o&&o.acceptsChildren===!1&&r.push([`${t}.children`,`${i} takes no children`]),T(e.children,`${t}.children`,n,r)),e.handlers!==void 0&&w(e.handlers,`${t}.handlers`,i,o,r)}function w(e,t,n,r,i){if(!Array.isArray(e)||!e.every(e=>typeof e==`string`)){i.push([t,`must be a list of handler names`]);return}if(a.includes(n)){i.push([t,`handlers need a kit component`]);return}if(e.length===0){i.push([t,`omit handlers when empty`]);return}let o=new Set;for(let a of e)l.test(a)?o.has(a)?i.push([t,`"${a}" is listed twice`]):r&&R(r.props)&&Object.hasOwn(r.props,a)&&i.push([t,`"${a}" is a data prop of ${n}, set it in props`]):i.push([t,`"${a}" is not a handler name (on<Event>)`]),o.add(a)}function T(e,t,n,r){if(typeof e!=`string`){if(!Array.isArray(e)){r.push([t,`is text or a list of text and placed elements`]);return}e.forEach((e,i)=>{typeof e!=`string`&&(V(e)?C(e,`${t}[${i}]`,n,r):r.push([`${t}[${i}]`,`is neither text nor a placed element`]))})}}function E(e,t,n,r,i,a,o,c){if(!R(e)){c.push([t,`is not an example`]);return}let{name:l}=n,{id:u}=e;if(typeof u==`string`&&(u===l||u.startsWith(`${l}.`)&&s.test(u.slice(l.length+1)))?a.has(u)?c.push([`${t}.id`,`"${u}" is also used in ${a.get(u)}`]):a.set(u,i):c.push([`${t}.id`,`"${u}" must be ${l} or ${l}.<case>`]),B(e.title)||c.push([`${t}.title`,`is empty`]),!R(e.props))c.push([`${t}.props`,`must be an object`]);else{for(let[n,i]of Object.entries(e.props))Object.hasOwn(r,n)?b(i,r[n],`${t}.props.${n}`,o,c):c.push([`${t}.props.${n}`,`not a prop of ${l}`]);for(let[n,i]of Object.entries(r))R(i)&&i.required===!0&&!Object.hasOwn(e.props,n)&&c.push([`${t}.props.${n}`,`missing, the prop is required`])}e.children!==void 0&&(n.acceptsChildren===!1&&c.push([`${t}.children`,`${l} takes no children`]),T(e.children,`${t}.children`,o,c)),e.wrap!==void 0&&(V(e.wrap)?C(e.wrap,`${t}.wrap`,o,c):c.push([`${t}.wrap`,`is not a placed element`])),e.handlers!==void 0&&w(e.handlers,`${t}.handlers`,l,n,c)}function D(e,t,n){if(!R(e)){n.push([`usage`,`must be an object`]);return}(!Array.isArray(e.when)||e.when.length===0||!e.when.every(B))&&n.push([`usage.when`,`lists nothing; write at least one line`]),Array.isArray(e.whenNot)?e.whenNot.forEach((e,r)=>O(e,`usage.whenNot[${r}]`,t,n)):n.push([`usage.whenNot`,`must be a list`]),Array.isArray(e.do)||n.push([`usage.do`,`must be a list`]),Array.isArray(e.dont)||n.push([`usage.dont`,`must be a list`]),Array.isArray(e.do)&&Array.isArray(e.dont)&&(e.do.length!==e.dont.length&&n.push([`usage`,`${e.do.length} do against ${e.dont.length} dont; they pair up one to one`]),e.do.forEach((e,r)=>k(e,`usage.do[${r}]`,t,n)),e.dont.forEach((e,r)=>k(e,`usage.dont[${r}]`,t,n))),Array.isArray(e.a11y)?e.a11y.forEach((e,t)=>{B(e)||n.push([`usage.a11y[${t}]`,`is empty`])}):n.push([`usage.a11y`,`must be a list`])}function O(e,t,n,r){if(!R(e)){r.push([t,`is not an object`]);return}B(e.text)||r.push([`${t}.text`,`is empty`]),e.instead!==void 0&&(typeof e.instead==`string`?c.test(e.instead)&&!n.byName.has(e.instead)&&!n.knownNames.has(e.instead)&&r.push([`${t}.instead`,`"${e.instead}" is not a component of the kit`]):r.push([`${t}.instead`,`must be text`]))}function k(e,t,n,r){if(!R(e)){r.push([t,`is not a practice`]);return}if(B(e.note)||r.push([`${t}.note`,`is empty`]),!Array.isArray(e.example)||e.example.length===0){r.push([`${t}.example`,`needs at least one placed element`]);return}e.example.forEach((e,i)=>{let a=`${t}.example[${i}]`;V(e)?C(e,a,n,r):r.push([a,`is not a placed element`])})}function A(e,t,n){if(typeof e==`string`)u.test(e)&&n.push([t,`contains an em or en dash; use a comma, colon or period`]);else if(Array.isArray(e))e.forEach((e,r)=>A(e,`${t}[${r}]`,n));else if(R(e))for(let[r,i]of Object.entries(e))A(i,t?`${t}.${r}`:r,n)}function j(e,t){let n={name:e,type:t.type};return(t.type===`enum`||t.type===`enumOrNumber`)&&(n.enum=N(t.values)),t.fields!==void 0&&(n.fields=M(t.fields)),t.itemFields!==void 0&&(n.itemFields=M(t.itemFields)),n.required=t.required===!0,t.nullable===!0&&(n.nullable=!0),t.description!==void 0&&(n.description=t.description),t.default!==void 0&&(n.default=t.default),n}function M(e){return Object.keys(e).sort(L).map(t=>j(t,e[t]))}function N(e){return[...e].sort((e,t)=>{let n=typeof e==`number`,r=typeof t==`number`;return n&&r?e-t:n===r?L(e,t):n?-1:1})}function P(e){return e.type===`array`&&Object.keys(e.itemFields??{}).some(e=>e===`href`||e===`to`)}function F(e,t,n,r){r.push([n,`${I(e)} is not a ${t}`])}function I(e){return JSON.stringify(e)}function L(e,t){return e<t?-1:+(e>t)}function R(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function z(e){return typeof e==`number`&&Number.isFinite(e)}function B(e){return typeof e==`string`&&e.trim()!==``}function V(e){return R(e)&&typeof e.component==`string`}export{a as HTML_TAGS,m as checkMetas,g as componentModuleOf,h as manifestComponent,p as readMetas};
package/dist/phone.js CHANGED
@@ -1 +1 @@
1
- import{readText as e}from"./files.js";import t from"@babel/parser";var n=new Set([`Form`,`NetworkStatusBanner`,`SkeletonClockProvider`,`SkeletonProvider`,`SkeletonSkip`,`ThemeProvider`,`ToastProvider`,`Box`,`Text`]),r=[`sans`],i=`System`;function a(n){return t.parse(e(n),{sourceType:`module`,plugins:[`typescript`]})}function o(e){return[...new Set(e)].sort((e,t)=>e<t?-1:+(e>t))}function s(e){return e.type===`Identifier`?e.name:e.type===`StringLiteral`?e.value:null}function c(e){let t=new Map;for(let n of e.program.body){let e=n.type===`ExportNamedDeclaration`?n.declaration:n;if(e?.type===`VariableDeclaration`)for(let n of e.declarations)n.id.type===`Identifier`&&t.set(n.id.name,n.init)}return t}function l(e,t,n){let r=t;for(let t=0;r?.type===`Identifier`;t+=1){if(t>10||!e.has(r.name))throw Error(`${r.name} in ${n} is not a constant the reader can follow.`);r=e.get(r.name)}return r}function u(e,t,n,r){let i=n.properties.find(e=>e.type===`ObjectProperty`&&s(e.key)===r);if(!i)throw Error(`${r} is missing from the theme in ${t}`);return l(e,i.value,t)}function d(e){return e.properties.filter(e=>e.type===`ObjectProperty`).map(e=>s(e.key))}function f(e,t,n,r=``){return n.properties.flatMap(n=>{if(n.type!==`ObjectProperty`)return[];let i=`${r}${s(n.key)}`,a=l(e,n.value,t);return a.type===`StringLiteral`?[i]:a.type===`ObjectExpression`?f(e,t,a,`${i}.`):[]})}function p(e){let t=c(a(e));if(!t.has(`lightTheme`))throw Error(`lightTheme is not declared in ${e}`);let n=l(t,t.get(`lightTheme`),e),i=u(t,e,n,`typography`);return{color:o(f(t,e,u(t,e,n,`colors`))),space:o(d(u(t,e,n,`spacing`))),radius:o(d(u(t,e,n,`radius`))),shadow:o(d(u(t,e,n,`shadows`))),font:r,text:o(d(u(t,e,i,`fontSize`)))}}function m(e,t,n){let r=l(e,t,n);if(r.type===`StringLiteral`||r.type===`NumericLiteral`)return r.value;if(r.type===`UnaryExpression`&&r.operator===`-`&&r.argument.type===`NumericLiteral`)return-r.argument.value;if(r.type===`ObjectExpression`)return Object.fromEntries(r.properties.map(t=>{if(t.type!==`ObjectProperty`)throw Error(`${n} spreads an object the reader cannot follow.`);return[s(t.key),m(e,t.value,n)]}));throw Error(`${n} has a ${r.type} the reader cannot read as a value.`)}function h(e,t,n){for(let r of[[`typography`,`fontFamily`,`sans`],[`fontFamily`,`sans`]])try{let i=g(e,t,n,r);if(typeof i==`string`&&i!==``)return i}catch{}return`System`}function g(e,t,n,r){let i=l(e,n,t);for(let n of r)i=u(e,t,i,n);return m(e,i,t)}function _(e,t,n){return Object.fromEntries(Object.entries(e).map(([e,r])=>{if(typeof r!=`number`)throw Error(`${t}.${e} in ${n} is not a number.`);return[e,r]}))}function v(e,t,n){if(Object.keys(e).length===0)return`none`;let{shadowColor:r,shadowOffset:i,shadowOpacity:a,shadowRadius:o}=e,s=/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(typeof r==`string`?r:``);if(!s||typeof i?.width!=`number`||typeof i?.height!=`number`||typeof a!=`number`||typeof o!=`number`)throw Error(`shadows.${t} in ${n} is not a shadow the reader can draw.`);let c=s[1].length===3?[...s[1]].map(e=>e+e).join(``):s[1],[l,u,d]=[0,2,4].map(e=>Number.parseInt(c.slice(e,e+2),16));return`${i.width}px ${i.height}px ${o}px rgba(${l}, ${u}, ${d}, ${a})`}function y(e){let t=c(a(e));for(let n of[`lightTheme`,`darkTheme`])if(!t.has(n))throw Error(`${n} is not declared in ${e}; the phone kit's token values need the light and dark themes.`);let n=t.get(`lightTheme`),r=t.get(`darkTheme`),o=u(t,e,l(t,n,e),`colors`),s=u(t,e,l(t,r,e),`colors`),d=h(t,e,n);return{color:Object.fromEntries(f(t,e,o).map(n=>{let r=n.split(`.`);return[n,{light:g(t,e,o,r),dark:g(t,e,s,r)}]})),space:_(g(t,e,n,[`spacing`]),`spacing`,e),radius:_(g(t,e,n,[`radius`]),`radius`,e),shadow:Object.fromEntries(Object.entries(g(t,e,n,[`shadows`])).map(([t,n])=>[t,v(n,t,e)])),font:{sans:{family:d,fallback:i}},text:Object.fromEntries(Object.entries(_(g(t,e,n,[`typography`,`fontSize`]),`fontSize`,e)).map(([e,t])=>[e,{size:t,lineHeight:null,weight:null}]))}}function b(e){let t=a(e).program.body.map(e=>e.type===`ExportNamedDeclaration`?e.declaration:e).find(e=>e?.type===`TSTypeAliasDeclaration`&&e.id.name===`IconName`);if(t?.typeAnnotation.type!==`TSUnionType`)throw Error(`IconName union not found in ${e}`);return o(t.typeAnnotation.types.map(t=>{if(t.type!==`TSLiteralType`||t.literal.type!==`StringLiteral`)throw Error(`IconName in ${e} has a member that is not a string literal`);return t.literal.value}))}function x(e,t,n){let r=l(e,t,n);switch(r.type){case`StringLiteral`:case`NumericLiteral`:case`BooleanLiteral`:return r.value;case`UnaryExpression`:if(r.operator===`!`&&r.argument.type===`NumericLiteral`)return!r.argument.value;break;case`ArrayExpression`:return r.elements.map(t=>x(e,t,n));case`ObjectExpression`:return Object.fromEntries(r.properties.map(t=>{if(t.type!==`ObjectProperty`||t.computed)throw Error(`phoneChromeEntries in ${n} has an object member the reader cannot follow.`);return[s(t.key),x(e,t.value,n)]}));case`MemberExpression`:if(!r.computed&&r.property.type===`Identifier`){let t=x(e,r.object,n);if(typeof t==`object`&&t&&r.property.name in t)return t[r.property.name]}break}throw Error(`phoneChromeEntries in ${n} has a ${r.type} the reader cannot read as data.`)}function S(e){return new Set(e.program.body.filter(e=>e.type===`ExportNamedDeclaration`&&e.declaration).flatMap(({declaration:e})=>e.type===`VariableDeclaration`?e.declarations.map(e=>e.id.name):[e.id?.name]))}function C(n,r){let i=t.parse(e(n),{sourceType:`module`,plugins:[`jsx`]}),a=c(i);if(!a.has(`phoneChromeEntries`))throw Error(`phoneChromeEntries is not declared in ${n}; the phone kit's frame parts come from it.`);let o=x(a,a.get(`phoneChromeEntries`),n),s=S(i);if(!Array.isArray(o)||o.length===0)throw Error(`phoneChromeEntries in ${n} must be a list of manifest entries.`);return o.map(e=>{let{name:t,marker:i,acceptsChildren:a,props:o,itemsTakeAction:c,takesAction:l,source:u}=e??{};if(typeof t!=`string`||typeof i!=`string`||typeof a!=`boolean`||!Array.isArray(o)||!Array.isArray(c)||!Array.isArray(l))throw Error(`phoneChromeEntries in ${n} has an entry that is not a manifest component: ${JSON.stringify(e)}`);if(u!==r)throw Error(`phoneChromeEntries in ${n}: ${t} names the source ${u}, not ${r}.`);if(!s.has(t))throw Error(`phoneChromeEntries in ${n} lists ${t}, which the module does not export.`);return{name:t,marker:i,acceptsChildren:a,props:o,itemsTakeAction:c,takesAction:l,source:u}})}function w(e,t){for(let[n,r]of[[`itemsTakeAction`,`array`],[`takesAction`,`object`]])for(let[i,a]of Object.entries(t[n]??{})){let t=e.find(({name:e})=>e===i);if(!t)throw Error(`phoneStyleSupport.json ${n} names ${i}, which the kit does not have.`);for(let e of a)if(t.props.find(({name:t})=>t===e)?.type!==r)throw Error(`phoneStyleSupport.json ${n} names ${i}.${e}, which is not a prop of type ${r} in the kit.`);t[n]=o([...new Set([...t[n],...a])])}}export{n as PHONE_EXCLUDED_COMPONENTS,w as applyPhoneActions,C as phoneChromeEntries,b as phoneIcons,y as phoneTokenValues,p as phoneTokens};
1
+ import{readText as e}from"./files.js";import t from"@babel/parser";function n(n){return t.parse(e(n),{sourceType:`module`,plugins:[`typescript`]})}function r(e){return[...new Set(e)].sort((e,t)=>e<t?-1:+(e>t))}function i(e){return e.type===`Identifier`?e.name:e.type===`StringLiteral`?e.value:null}function a(e){let t=new Map;for(let n of e.program.body){let e=n.type===`ExportNamedDeclaration`?n.declaration:n;if(e?.type===`VariableDeclaration`)for(let n of e.declarations)n.id.type===`Identifier`&&t.set(n.id.name,n.init)}return t}function o(e,t,n){let r=t;for(let t=0;r?.type===`Identifier`;t+=1){if(t>10||!e.has(r.name))throw Error(`${r.name} in ${n} is not a constant the reader can follow.`);r=e.get(r.name)}return r}function s(e){let t=n(e).program.body.map(e=>e.type===`ExportNamedDeclaration`?e.declaration:e).find(e=>e?.type===`TSTypeAliasDeclaration`&&e.id.name===`IconName`);if(t?.typeAnnotation.type!==`TSUnionType`)throw Error(`IconName union not found in ${e}`);return r(t.typeAnnotation.types.map(t=>{if(t.type!==`TSLiteralType`||t.literal.type!==`StringLiteral`)throw Error(`IconName in ${e} has a member that is not a string literal`);return t.literal.value}))}function c(e,t,n){let r=o(e,t,n);switch(r.type){case`StringLiteral`:case`NumericLiteral`:case`BooleanLiteral`:return r.value;case`UnaryExpression`:if(r.operator===`!`&&r.argument.type===`NumericLiteral`)return!r.argument.value;break;case`ArrayExpression`:return r.elements.map(t=>c(e,t,n));case`ObjectExpression`:return Object.fromEntries(r.properties.map(t=>{if(t.type!==`ObjectProperty`||t.computed)throw Error(`phoneChromeEntries in ${n} has an object member the reader cannot follow.`);return[i(t.key),c(e,t.value,n)]}));case`MemberExpression`:if(!r.computed&&r.property.type===`Identifier`){let t=c(e,r.object,n);if(typeof t==`object`&&t&&r.property.name in t)return t[r.property.name]}break}throw Error(`phoneChromeEntries in ${n} has a ${r.type} the reader cannot read as data.`)}function l(e){return new Set(e.program.body.filter(e=>e.type===`ExportNamedDeclaration`&&e.declaration).flatMap(({declaration:e})=>e.type===`VariableDeclaration`?e.declarations.map(e=>e.id.name):[e.id?.name]))}function u(n,r){let i=t.parse(e(n),{sourceType:`module`,plugins:[`jsx`]}),o=a(i);if(!o.has(`phoneChromeEntries`))throw Error(`phoneChromeEntries is not declared in ${n}; the phone kit's frame parts come from it.`);let s=c(o,o.get(`phoneChromeEntries`),n),u=l(i);if(!Array.isArray(s)||s.length===0)throw Error(`phoneChromeEntries in ${n} must be a list of manifest entries.`);return s.map(e=>{let{name:t,marker:i,acceptsChildren:a,props:o,itemsTakeAction:s,takesAction:c,source:l}=e??{};if(typeof t!=`string`||typeof i!=`string`||typeof a!=`boolean`||!Array.isArray(o)||!Array.isArray(s)||!Array.isArray(c))throw Error(`phoneChromeEntries in ${n} has an entry that is not a manifest component: ${JSON.stringify(e)}`);if(l!==r)throw Error(`phoneChromeEntries in ${n}: ${t} names the source ${l}, not ${r}.`);if(!u.has(t))throw Error(`phoneChromeEntries in ${n} lists ${t}, which the module does not export.`);return{name:t,marker:i,acceptsChildren:a,props:o,itemsTakeAction:s,takesAction:c,source:l}})}function d(e,t){for(let[n,i]of[[`itemsTakeAction`,`array`],[`takesAction`,`object`]])for(let[a,o]of Object.entries(t[n]??{})){let t=e.find(({name:e})=>e===a);if(!t)throw Error(`phoneStyleSupport.json ${n} names ${a}, which the kit does not have.`);for(let e of o)if(t.props.find(({name:t})=>t===e)?.type!==i)throw Error(`phoneStyleSupport.json ${n} names ${a}.${e}, which is not a prop of type ${i} in the kit.`);t[n]=r([...new Set([...t[n],...o])])}}export{d as applyPhoneActions,u as phoneChromeEntries,s as phoneIcons};
package/dist/release.js CHANGED
@@ -1 +1 @@
1
- import{readJson as e}from"./files.js";import{RELEASE_FILE as t,changedMessage as n,checkStampFiles as r}from"./releaseStamp.js";import{existsSync as i,mkdirSync as a,readFileSync as o,renameSync as s,rmSync as c,writeFileSync as l}from"node:fs";import{basename as u,dirname as d,join as f}from"node:path";import{createHash as p}from"node:crypto";function m(e){let t=Buffer.from(e);return{sha256:p(`sha256`).update(t).digest(`hex`),bytes:t.length}}function h(e,t,n){if(u(e)!==n||u(d(e))!==t)throw Error(`A library-only kit writes its release to a folder named ${t}/${n}, not ${e}.`)}function g(e,n,r){if(i(e))throw Error(`${e} already exists. A release folder is never overwritten; choose a new folder.`);a(d(e),{recursive:!0});let o=`${e}.staging-${process.pid}-${Date.now()}`;a(o);try{for(let[e,t]of Object.entries(n))l(f(o,e),t,{flag:`wx`});l(f(o,t),`${JSON.stringify(r,null,2)}\n`,{flag:`wx`}),s(o,e)}catch(e){throw c(o,{recursive:!0,force:!0}),e}}function _(a){let s=f(a,t);if(!i(s))throw Error(`${a} has no ${t}; it is not a kit release.`);let c=e(s);r(c,a);for(let[e,t]of Object.entries(c.files)){let r=f(a,e);if(!i(r))throw Error(`${e} is missing from ${a}.`);let s=m(o(r));if(s.sha256!==t.sha256||s.bytes!==t.bytes)throw Error(n(e,a))}return{release:c,manifest:e(f(a,`manifest.json`))}}export{h as checkFleetFolder,m as digest,_ as readReleaseManifest,g as writeReleaseFolder};
1
+ import{readJson as e}from"./files.js";import{RELEASE_FILE as t,changedMessage as n,checkStampFiles as r}from"./releaseStamp.js";import{existsSync as i,mkdirSync as a,readFileSync as o,renameSync as s,rmSync as c,writeFileSync as l}from"node:fs";import{dirname as u,join as d}from"node:path";import{createHash as f}from"node:crypto";function p(e){let t=Buffer.from(e);return{sha256:f(`sha256`).update(t).digest(`hex`),bytes:t.length}}function m(e,n,r){if(i(e))throw Error(`${e} already exists. A release folder is never overwritten; choose a new folder.`);a(u(e),{recursive:!0});let o=`${e}.staging-${process.pid}-${Date.now()}`;a(o);try{for(let[e,t]of Object.entries(n))l(d(o,e),t,{flag:`wx`});l(d(o,t),`${JSON.stringify(r,null,2)}\n`,{flag:`wx`}),s(o,e)}catch(e){throw c(o,{recursive:!0,force:!0}),e}}function h(a){let s=d(a,t);if(!i(s))throw Error(`${a} has no ${t}; it is not a kit release.`);let c=e(s);r(c,a);for(let[e,t]of Object.entries(c.files)){let r=d(a,e);if(!i(r))throw Error(`${e} is missing from ${a}.`);let s=p(o(r));if(s.sha256!==t.sha256||s.bytes!==t.bytes)throw Error(n(e,a))}return{release:c,manifest:e(d(a,`manifest.json`))}}export{p as digest,h as readReleaseManifest,m as writeReleaseFolder};
package/dist/tokens.js CHANGED
@@ -1 +1 @@
1
- import{readJson as e,readText as t}from"./files.js";import{existsSync as n}from"node:fs";import{join as r}from"node:path";var i=[`none`,`xxs`,`xs`,`sm`,`md`,`lg`,`xl`,`2xl`,`3xl`,`4xl`],a=[`sans`,`display`,`mono`],o=e=>typeof e==`object`&&!!e&&!Array.isArray(e),s=e=>typeof e==`number`&&Number.isFinite(e),c=e=>typeof e==`string`,l={color:e=>o(e)&&c(e.light)&&c(e.dark),space:s,radius:s,shadow:c,font:e=>o(e)&&c(e.family)&&c(e.fallback),text:e=>o(e)&&s(e.size)&&[e.lineHeight,e.weight].every(e=>e==null||s(e))};function u(e){return[...new Set(e)].sort((e,t)=>e<t?-1:+(e>t))}function d(e){let t=e.replace(/\/\*[\s\S]*?\*\//g,``).replace(/(^|\s)\/\/.*$/gm,`$1`),n=new Map;for(let e of t.matchAll(/(?<![\w-])(--wui-[a-z0-9-]+)\s*:([^;]*);/g))n.has(e[1])||n.set(e[1],e[2].replace(/\s+/g,` `).trim());let r=(e,t=0)=>{let i=n.get(e);if(i===void 0)return!1;if(/^(#[0-9a-fA-F]|rgba?\(|hsla?\()/.test(i))return!0;let a=/^var\((--wui-[a-z0-9-]+)\)$/.exec(i);return a!==null&&t<10&&r(a[1],t+1)},o=e=>u([...n.keys()].filter(t=>t.startsWith(e)).map(t=>t.slice(e.length)));return{color:u([...n.keys()].filter(e=>r(e)&&!e.startsWith(`--wui-font-`)&&!e.startsWith(`--wui-dark-`)).map(e=>e.slice(6))),space:u(i),radius:o(`--wui-radius-`),shadow:o(`--wui-shadow-`),font:u(a),text:o(`--wui-fs-`)}}function f(e,t){return Object.fromEntries(e.map(e=>{let n=t[`--wui-${e}`];if(typeof n!=`string`||n===``)throw Error(`@webority/theme token-meanings.json has no meaning for --wui-${e}; move the theme pin to a version that publishes one.`);return[e,n]}))}function p(e){let n=r(e,`components`,`iconCatalogNames.g.js`),i=t(n),a=/`([^`]+)`\.split\(`\.`\)/.exec(i);if(!a)throw Error(`No icon name list in ${n}`);return u(a[1].split(`.`).filter(Boolean))}function m(t,r){if(!n(t))throw Error(`${t} is missing; rebuild the release with canvas-kit build.`);let i=e(t),a={};for(let[e,n]of Object.entries(l)){let s=o(i)&&o(i[e])?i[e]:null;if(!s)throw Error(`${t} has no ${e} object; rebuild the release with canvas-kit build.`);for(let[r,i]of Object.entries(s))if(!n(i))throw Error(`${t}: ${e}.${r} is not a value the kit can draw; rebuild the release with canvas-kit build.`);if(e!==`space`){let n=r[e].filter(e=>!Object.hasOwn(s,e));if(n.length>0)throw Error(`${t} has no ${e} value for ${n.join(`, `)}; rebuild the release with canvas-kit build.`)}a[e]=e===`text`?g(s,h):s}return a}function h({size:e,lineHeight:t,weight:n}){return{size:e,lineHeight:t??null,weight:n??null}}function g(e,t){return Object.fromEntries(Object.entries(e).map(([e,n])=>[e,t(n)]))}export{f as colourMeanings,p as webIcons,m as webTokenValues,d as webTokens};
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};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority/canvas-kit",
3
- "version": "0.88.0",
3
+ "version": "0.89.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",
@@ -11,7 +11,11 @@
11
11
  "exports": {
12
12
  ".": "./dist/index.js",
13
13
  "./kitModule": "./dist/kitModule.js",
14
- "./browser": "./dist/browser.js"
14
+ "./browser": "./dist/browser.js",
15
+ "./meta": {
16
+ "types": "./dist/meta.d.ts",
17
+ "default": "./dist/meta.js"
18
+ }
15
19
  },
16
20
  "bin": {
17
21
  "canvas-kit": "./dist/cli.js"
@@ -22,8 +26,7 @@
22
26
  "vite": "^8.0.12"
23
27
  },
24
28
  "devDependencies": {
25
- "react": "^19.2.6",
26
- "@webority/ui-react": "0.88.0"
29
+ "react": "^19.2.6"
27
30
  },
28
31
  "scripts": {
29
32
  "build": "vite build --config vite.cli.config.js",
@@ -1 +0,0 @@
1
- import{readText as e}from"./files.js";import{existsSync as t}from"node:fs";import{dirname as n,join as r,resolve as i}from"node:path";import a from"@babel/parser";var o=`function`,s=8,c=2,l=120,u=[`ForwardRefExoticComponent`,`FC`,`FunctionComponent`,`ComponentType`,`MemoExoticComponent`],d=[`Array`,`ReadonlyArray`,`Set`,`ReadonlySet`],f=/(^i|I)con$/,p=/(Href|href|Url|url)$/,m=new Map,h=new Set,g=new Set,_=[];function v(e,t){return e<t?-1:+(e>t)}function y(e){return[...new Set(e)].sort(v)}function b(e){return e.type===`Identifier`?e.name:e.type===`StringLiteral`?e.value:null}function x(e){let t=e.type===`TSImportType`?e.qualifier:e.typeName;return t?t.type===`TSQualifiedName`?t.right.name:t.name:null}function ee(e){switch(e.type){case`TSDeclareFunction`:case`FunctionDeclaration`:case`TSInterfaceDeclaration`:case`TSTypeAliasDeclaration`:return[[e.id.name,e]];case`VariableDeclaration`:return e.declarations.filter(e=>e.id.type===`Identifier`).map(e=>[e.id.name,e]);default:return[]}}function S(t){if(m.has(t))return m.get(t);let n=e(t),r=a.parse(n,{sourceType:`module`,plugins:[`typescript`]}),i={declarations:new Map,imports:new Map,exports:[],defaultDeclaration:null,source:n};for(let e of r.program.body)if(e.type===`ImportDeclaration`)for(let t of e.specifiers)t.type===`ImportSpecifier`&&i.imports.set(t.local.name,{source:e.source.value,imported:b(t.imported)});else if(e.type===`ExportAllDeclaration`)i.exports.push({star:!0,source:e.source.value});else if(e.type===`ExportDefaultDeclaration`)i.defaultDeclaration=e.declaration,i.exports.push({name:`default`,local:`default`});else if(e.type===`ExportNamedDeclaration`&&e.source)for(let t of e.specifiers)i.exports.push({name:t.exported.name,local:t.local.name,source:e.source.value});else{let t=e.type===`ExportNamedDeclaration`,n=t?e.declaration:e;for(let[e,r]of n?ee(n):[])i.declarations.set(e,r),t&&i.exports.push({name:e,local:e});if(t)for(let t of e.specifiers)i.exports.push({name:t.exported.name,local:t.local.name})}return m.set(t,i),i}function C(e,a){if(!a.startsWith(`.`))return null;let o=i(n(e),a).replace(/\.(jsx?|tsx?)$/,``);for(let e of[`${o}.d.ts`,r(o,`index.d.ts`)])if(t(e))return e;throw Error(`Cannot resolve ${a} from ${e}`)}function w(e,t=new Set){let n=new Map;if(t.has(e))return n;t.add(e);for(let r of S(e).exports){if(r.star){let i=C(e,r.source);for(let[e,r]of i?w(i,t):[])e!=="default"&&!n.has(e)&&n.set(e,r);continue}let i=r.source===void 0?e:C(e,r.source);i&&n.set(r.name,{file:i,local:r.local})}return n}function T(e,t=new Set){let n=`${e.file}#${e.local}`;if(t.has(n))return null;t.add(n);let r=S(e.file);if(r.declarations.has(e.local))return e;let i=r.exports.find(t=>t.name===e.local&&t.source!==void 0),a=i&&C(e.file,i.source);return a?T({file:a,local:i.local},t):null}function E(e){return e.local==="default"?e:T(e)}function te(e){let t=E(e);if(!t)return null;let{declarations:n,defaultDeclaration:r}=S(t.file),i=t.local==="default"?r?.type===`Identifier`?n.get(r.name):r:n.get(t.local);return i?.type===`TSDeclareFunction`||i?.type===`FunctionDeclaration`?i:null}function ne(e,t,n){let r=e.params[0];if(!r)return[];let i=(r.type===`AssignmentPattern`?r.left:r).typeAnnotation?.typeAnnotation;if(!i)return null;let a=F(i,t.file,new Map,0);if(a)return a;let o=x(i);if(i.type===`TSTypeReference`&&o?.endsWith(`Props`)&&S(t.file).declarations.has(o))throw Error(`${n}: ${o} is not an object type in ${t.file}`);return null}function D(e,t){let n=S(e),r=n.declarations.get(t);if(r)return{file:e,declared:r};let i=n.imports.get(t),a=i&&C(e,i.source);if(!a)return null;let o=T({file:a,local:i.imported});return o?{file:o.file,declared:S(o.file).declarations.get(o.local)}:null}function O(e,t,n,r,i){let a=new Map;return(e.typeParameters?.params??[]).forEach((e,o)=>{let s=t[o];s?a.set(e.name,{node:s,file:n,env:r}):e.default?a.set(e.name,{node:e.default,file:i,env:a}):a.set(e.name,{node:{type:`TSUnknownKeyword`},file:i,env:new Map})}),a}function k(e){if(!e?.expression)return null;let t=(e.typeParameters??e.typeArguments)?.params;return{type:`TSTypeReference`,typeName:e.expression,...t?{typeParameters:{params:t}}:{}}}function A(e,t,n){if(e.type===`TSImportType`){let t=e.argument?.value;return typeof t!=`string`||!t.startsWith(`.`)}let r=n?S(n).imports.get(t):null;return!!(r&&!r.source.startsWith(`.`))}function j(e,t,n){if(!e)return null;let r=e.type===`TSUnionType`?e.types:[e],i=[];for(let e of r){if(e.type===`TSNullKeyword`||e.type===`TSUndefinedKeyword`)continue;let r=U(e,t,n);if(r.length===0||r.includes(null))return null;i.push(...r)}return i.length>0?i:null}function M(e,t,n,r){let i=e.filter(e=>e.type!==`TSNullKeyword`&&e.type!==`TSUndefinedKeyword`);if(i.length===1)return F(i[0],t,n,r);let a=[];for(let e of i){let i=F(e,t,n,r);if(!i)return null;a.push(i)}let o=[],s=new Map;for(let e of a)for(let t of e){let e=s.get(t.name);e||(e={name:t.name,count:0,requiredEverywhere:!0,branches:[]},s.set(t.name,e),o.push(t.name)),e.count+=1,e.requiredEverywhere=e.requiredEverywhere&&!t.optional,e.branches.push(...t.branches)}return o.map(e=>{let t=s.get(e);return{name:e,optional:!(t.count===a.length&&t.requiredEverywhere),branches:t.branches}})}function N(e,t,n,r,i,a){let o=O(e,t,n,r,i),s=[],c=new Map;for(let t of e.extends??[]){let e=F(k(t),i,o,a+1);if(!e)return null;for(let t of e)c.has(t.name)||s.push(t.name),c.set(t.name,t)}let l=F({type:`TSTypeLiteral`,members:e.body.body},i,o,a);if(!l)return null;for(let e of l){let t=s.indexOf(e.name);t>=0&&s.splice(t,1),s.push(e.name),c.set(e.name,e)}return s.map(e=>c.get(e))}function P(e,t,n,r){let i=x(e),a=(e.typeParameters??e.typeArguments)?.params??[];if(i&&n.has(i)){let e=n.get(i);return F(e.node,e.file,e.env,r+1)}if(i===`RefAttributes`)return[];if([`Partial`,`Required`,`Readonly`].includes(i)&&a.length===1){let e=F(a[0],t,n,r);return e?i===`Partial`?e.map(e=>({...e,optional:!0})):i===`Required`?e.map(e=>({...e,optional:!1})):e:null}if((i===`Omit`||i===`Pick`)&&a.length===2){let e=F(a[0],t,n,r);if(!e)return null;let o=j(a[1],t,r);if(!o)return i===`Omit`?e:null;let s=new Set(o);return i===`Omit`?e.filter(e=>!s.has(e.name)):e.filter(e=>s.has(e.name))}if(A(e,i,t))return i&&g.add(i),[];let o=D(t,i);if(!o?.declared)return null;if(o.declared.type===`TSInterfaceDeclaration`)return N(o.declared,a,t,n,o.file,r);if(o.declared.type===`TSTypeAliasDeclaration`){let e=O(o.declared,a,t,n,o.file);return F(o.declared.typeAnnotation,o.file,e,r+1)}return null}function F(e,t,n,r){if(!e||r>=s)return null;switch(e.type){case`TSParenthesizedType`:return F(e.typeAnnotation,t,n,r);case`TSTypeLiteral`:return e.members.filter(e=>e.type===`TSPropertySignature`&&b(e.key)!==null).map(e=>({name:b(e.key),optional:e.optional===!0,branches:[{node:e.typeAnnotation?.typeAnnotation??{type:`TSAnyKeyword`},file:t,env:n}]}));case`TSIntersectionType`:{let i=[],a=new Map;for(let o of e.types){let e=F(o,t,n,r);if(!e)return null;for(let t of e)a.has(t.name)||i.push(t.name),a.set(t.name,t)}return i.map(e=>a.get(e))}case`TSUnionType`:return M(e.types,t,n,r);case`TSTypeReference`:case`TSImportType`:return P(e,t,n,r);default:return null}}function I(e,t,n){if(e===`ReactNode`||e===`ReactElement`)return{type:`node`};if(d.includes(e))return{type:`array`};if(e===`Record`)return{type:`object`};if(e===`Date`)return{type:`string`};if(e===`Function`)return o;if(e===`SlotIcon`)return{type:`node`};if(!t||n>=s)return null;let r=D(t,e);if(r?.declared?.type===`TSTypeAliasDeclaration`)return G(r.declared.typeAnnotation,r.file,n+1);if(r?.declared?.type===`TSInterfaceDeclaration`){let i=F({type:`TSTypeReference`,typeName:{type:`Identifier`,name:e}},t,_.at(-1)??new Map,n+1);return i?J(i,r.declared):null}return null}function L(e){return e.type===`TSBooleanKeyword`||e.type===`TSLiteralType`&&e.literal.type===`BooleanLiteral`}function R(e){return e.type===`TSLiteralType`&&e.literal.type===`StringLiteral`}function z(e){return e.type===`TSObjectKeyword`||e.type===`TSTypeLiteral`}function B(e){return e?.type===`TSQualifiedName`&&e.left.type===`Identifier`&&e.left.name===`JSX`&&e.right.name===`Element`}function V(e){return e.type===`TSTypeReference`&&[`File`,`Blob`].includes(x(e))}function H(e,t,n){let r=e.filter(e=>e.type!==`TSNullKeyword`&&e.type!==`TSUndefinedKeyword`&&!V(e)),i=e=>r.every(t=>t.type===`TSLiteralType`&&t.literal.type===e);if(r.length===0)return null;if(i(`StringLiteral`))return{type:`enum`,enum:y(r.map(e=>e.literal.value))};if(i(`BooleanLiteral`)||r.some(L)&&r.every(e=>L(e)||R(e)||z(e)))return{type:`boolean`};if(i(`NumericLiteral`))return{type:`number`};if(r.length===1&&r[0].type!==`TSLiteralType`)return G(r[0],t,n);let a=r.flatMap(e=>U(e,t,n));if(a.length>0&&!a.includes(null))return{type:`enum`,enum:y(a)};let s=new Set(r.map(e=>W(e,t,n)));return s.has(`node`)?{type:`node`}:s.size===2&&s.has(`string`)&&s.has(`number`)?{type:`stringOrNumber`}:s.size===1&&!s.has(null)?{type:[...s][0]}:r.every(e=>e.type!==`TSLiteralType`)&&r.map(e=>G(e,t,n)).every(e=>e&&e!==o&&[`object`,`array`].includes(e.type))?{type:`any`}:null}function U(e,t,n){if(e.type===`TSLiteralType`)return e.literal.type===`StringLiteral`?[e.literal.value]:[null];let r=G(e,t,n);return r?.type===`enum`?r.enum:[null]}function W(e,t,n){if(e.type===`TSLiteralType`){let t=e.literal.type;return t===`StringLiteral`?`string`:t===`NumericLiteral`?`number`:t===`BooleanLiteral`?`boolean`:null}let r=G(e,t,n);return r?.type===`enum`?`string`:r&&r!==o&&[`node`,`string`,`number`,`boolean`].includes(r.type)?r.type:null}function G(e,t,n=0){switch(e.type){case`TSParenthesizedType`:return G(e.typeAnnotation,t,n);case`TSAnyKeyword`:case`TSUnknownKeyword`:return{type:`any`,anyUnknown:!0};case`TSStringKeyword`:return{type:`string`};case`TSNumberKeyword`:return{type:`number`};case`TSBooleanKeyword`:return{type:`boolean`};case`TSArrayType`:return X(e.elementType,t,n);case`TSTypeLiteral`:return Y(e.members,t);case`TSFunctionType`:return o;case`TSLiteralType`:return H([e],t,n);case`TSUnionType`:return H(e.types,t,n);case`TSTypeReference`:{let r=x(e),i=_.at(-1);if(r&&i?.has(r)){let e=i.get(r);_.push(e.env);try{return G(e.node,e.file,n+1)}finally{_.pop()}}return B(e.typeName)?{type:`node`}:d.includes(r)&&e.typeParameters?.params.length===1?X(e.typeParameters.params[0],t,n):I(r,t,n)}case`TSImportType`:return B(e.qualifier)?{type:`node`}:I(x(e),null,n);default:return null}}function K(e,t){return p.test(e)&&(t===`string`||t===`any`)?`url`:t}function q(e,t=e){if(h.size>=c||h.has(t))return null;h.add(t);try{return Z(e)}finally{h.delete(t)}}function J(e,t=e){let n=q(e,t);return n?{type:`object`,fields:n.props,skippedFields:n.skippedProps,...n.hasHandler?{handler:!0}:{}}:{type:`object`}}function Y(e,t){let n=F({type:`TSTypeLiteral`,members:e},t,_.at(-1)??new Map,0);return n?J(n):{type:`object`}}function X(e,t,n){let r=G(e,t,n);return r?.fields?{type:`array`,itemFields:r.fields,skippedFields:r.skippedFields,...r.handler?{itemHandler:!0}:{}}:{type:`array`}}function re(e){_.push(e.env);try{return G(e.node,e.file)}finally{_.pop()}}function ie(e){let t=e.branches.map(e=>Q(e.node,e.file));return t.length===1?t[0]:t.join(` | `)}function Z(e){let t=[],n=[],r=!1,i=!1;for(let a of e){let e=a.name;if(e===`children`){r=!0;continue}let s=a.branches.map(e=>re(e));if(s.every(e=>e===o)){i=!0;continue}let c=a.branches[0],l;if(s.every(e=>e!==null&&JSON.stringify(e)===JSON.stringify(s[0])))l=s[0];else if(a.branches.every(e=>e.file===c.file&&e.env===c.env)){let e={type:`TSUnionType`,types:a.branches.map(e=>e.node)};_.push(c.env);try{l=G(e,c.file)}finally{_.pop()}}if(l===o){i=!0;continue}if(!l){n.push({name:e,type:ie(a)});continue}let{type:u,enum:d,anyUnknown:p,fields:m,itemFields:h,skippedFields:g=[]}=l;for(let t of g)n.push({...t,name:`${e}${u===`array`?`[]`:``}.${t.name}`});t.push({name:e,type:K(e,u===`node`&&f.test(e)?`icon`:u),...l.handler?{handler:!0}:{},...l.itemHandler?{itemHandler:!0}:{},...d?{enum:d}:{},...m?{fields:m}:{},...h?{itemFields:h}:{},required:!a.optional&&!p})}return t.sort((e,t)=>v(e.name,t.name)),{props:t,acceptsChildren:r,skippedProps:n,hasHandler:i}}function Q(e,t){if(e.start===void 0)return e.type===`TSUnionType`?e.types.map(e=>Q(e,t)).join(` | `):e.type;let n=t?S(t).source:null;if(!n)return e.type;let r=n.slice(e.start,e.end).replace(/\s+/g,` `);return r.length>l?`${r.slice(0,l)}...`:r}function $(e){let t=E(e);if(!t)return null;let{declarations:n,defaultDeclaration:r}=S(t.file),i=t.local==="default"?r?.type===`Identifier`&&n.get(r.name):n.get(t.local);return i?.type===`VariableDeclarator`?{node:i,declarations:n}:null}function ae(e){let t=$(e)?.node.id.typeAnnotation?.typeAnnotation;return[`TSTypeReference`,`TSImportType`].includes(t?.type)&&u.includes(x(t))}function oe(e){let t=$(e);if(!t)return null;let n=E(e)?.file,r=t.node.id.typeAnnotation?.typeAnnotation;for(;r;){if(![`TSTypeReference`,`TSImportType`].includes(r.type)||!u.includes(x(r)))return null;let e=(r.typeParameters??r.typeArguments)?.params??[];if(e.length===0)return[];if(r=e[0],![`TSTypeReference`,`TSImportType`].includes(r.type)||!u.includes(x(r)))return F(r,n,new Map,0)}return null}function se(e,{namedConsts:t=!1,kit:n=``}={}){m=new Map,h.clear(),g.clear(),_.length=0;let r=[],i=[];for(let[a,o]of w(e)){if(!/^[A-Z][A-Za-z0-9]*$/.test(a))continue;let s=te(o),c=E(o),l=s?ne(s,c,a):t?oe(o):null;if(!l){(s||t&&ae(o))&&i.push(`Component left out, its props could not be read: ${n} ${a}`);continue}let u=Z(l);if(u.skippedProps.length>0){let e=u.skippedProps.map(({name:e,type:t})=>`${e} (${t})`).join(`, `);i.push(`Skipped props, no manifest type: ${n} ${a}: ${e}`)}r.push({name:a,acceptsChildren:u.acceptsChildren,props:u.props,file:c?.file??e})}return r.sort((e,t)=>v(e.name,t.name)),g.size>0&&i.push(`Props inherited from outside the kit were not read: ${n} ${y(g).join(`, `)}`),{components:r,warnings:i}}export{se as collectComponents};