@oneli8/tokens 1.0.0-beta.2

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/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@oneli8/tokens",
3
+ "version": "1.0.0-beta.2",
4
+ "publishConfig": {"access":"public","tag":"beta"},
5
+ "repository": {"type":"git","url":"git+https://github.com/Akshay007ad/OneLi8-Design-System.git","directory":"packages/tokens"},
6
+ "license": "Apache-2.0",
7
+ "type": "module",
8
+ "files": [
9
+ "build",
10
+ "schema",
11
+ "scripts",
12
+ "src",
13
+ "status",
14
+ "README.md",
15
+ "LICENSE",
16
+ "NOTICE"
17
+ ],
18
+ "scripts": {
19
+ "build": "node scripts/build.mjs --standalone",
20
+ "test": "node scripts/test.mjs --standalone"
21
+ },
22
+ "exports": {
23
+ ".": "./build/typescript/index.js",
24
+ "./css": "./build/css/tokens.css",
25
+ "./tailwind": "./build/tailwind/preset.mjs",
26
+ "./tailwind.css": "./build/tailwind/theme.css",
27
+ "./figma": "./build/figma/variables.json",
28
+ "./status": "./build/status/components.json"
29
+ }
30
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "$schema":"https://json-schema.org/draft/2020-12/schema",
3
+ "$id":"https://oneli8.org/schema/component-status.schema.json",
4
+ "title":"Oneli8 component status registry",
5
+ "type":"object",
6
+ "required":["version","components"],
7
+ "properties":{
8
+ "$schema":{"type":"string"},
9
+ "generated":{"type":"boolean"},
10
+ "version":{"type":"string","minLength":1},
11
+ "components":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/record"}}
12
+ },
13
+ "additionalProperties":false,
14
+ "$defs":{
15
+ "record":{
16
+ "type":"object",
17
+ "required":["name","status","designerApproved","implementationCertified","contractPath","knownEvidence","missingEvidence"],
18
+ "properties":{
19
+ "name":{"type":"string","minLength":1},
20
+ "status":{"enum":["Stable","Candidate","Labs","Experimental","Deprecated"]},
21
+ "designerApproved":{"type":"boolean"},
22
+ "implementationCertified":{"type":"boolean"},
23
+ "contractPath":{"type":"string","minLength":1},
24
+ "implementationPath":{"type":["string","null"]},
25
+ "figmaComponentKey":{"type":["string","null"]},
26
+ "knownEvidence":{"type":"array","items":{"type":"string"}},
27
+ "missingEvidence":{"type":"array","items":{"type":"string"}}
28
+ },
29
+ "additionalProperties":true
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "$schema":"https://json-schema.org/draft/2020-12/schema",
3
+ "$id":"https://oneli8.org/schema/token-file.schema.json",
4
+ "title":"Oneli8 DTCG-compatible token file",
5
+ "type":"object",
6
+ "properties":{
7
+ "$schema":{"type":"string"},
8
+ "$description":{"type":"string"},
9
+ "$extensions":{"type":"object"}
10
+ },
11
+ "additionalProperties":{"$ref":"#/$defs/node"},
12
+ "$defs":{
13
+ "node":{
14
+ "oneOf":[
15
+ {"$ref":"#/$defs/token"},
16
+ {"type":"object","additionalProperties":{"$ref":"#/$defs/node"}}
17
+ ]
18
+ },
19
+ "token":{
20
+ "type":"object",
21
+ "required":["$type","$value"],
22
+ "properties":{
23
+ "$type":{"type":"string","minLength":1},
24
+ "$value":{},
25
+ "$description":{"type":"string","minLength":1},
26
+ "$extensions":{"type":"object"}
27
+ },
28
+ "additionalProperties":false
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,191 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import {fileURLToPath} from "node:url";
4
+
5
+ const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),"..");
6
+ const read=name=>JSON.parse(fs.readFileSync(path.join(root,"src",name),"utf8"));
7
+ const primitive=read("primitive.json");
8
+ const semantic=read("semantic.json");
9
+ const component=read("component.json");
10
+ const themes=read("themes.json");
11
+ const status=JSON.parse(fs.readFileSync(path.join(root,"status/components.json"),"utf8"));
12
+ const sources={primitive,semantic,component};
13
+
14
+ const isToken=value=>value&&typeof value==="object"&&Object.hasOwn(value,"$value");
15
+ const flatten=(node,prefix="",out={})=>{
16
+ for(const [key,value] of Object.entries(node)){
17
+ if(key.startsWith("$"))continue;
18
+ const tokenPath=prefix?`${prefix}.${key}`:key;
19
+ if(isToken(value))out[tokenPath]=value;
20
+ else if(value&&typeof value==="object"&&!Array.isArray(value))flatten(value,tokenPath,out);
21
+ }
22
+ return out;
23
+ };
24
+ const byTier={primitive:flatten(primitive),semantic:flatten(semantic),component:flatten(component)};
25
+ const all={...byTier.primitive,...byTier.semantic,...byTier.component};
26
+ const referencePattern=/^\{([^}]+)\}$/;
27
+
28
+ const resolveValue=(value,stack=[])=>{
29
+ if(typeof value==="string"){
30
+ const match=value.match(referencePattern);
31
+ if(!match)return value;
32
+ const ref=match[1];
33
+ if(!all[ref])throw new Error(`Missing token reference {${ref}}`);
34
+ if(stack.includes(ref))throw new Error(`Circular token reference ${[...stack,ref].join(" -> ")}`);
35
+ return resolveValue(all[ref].$value,[...stack,ref]);
36
+ }
37
+ if(Array.isArray(value))return value.map(item=>resolveValue(item,stack));
38
+ if(value&&typeof value==="object")return Object.fromEntries(Object.entries(value).map(([key,item])=>[key,resolveValue(item,stack)]));
39
+ return value;
40
+ };
41
+
42
+ const approvedDimensions=new Set([0,3,6,9,12,15,18,24,30,36,42,48,60,72,84,96,108]);
43
+ for(const [tokenPath,token] of Object.entries(byTier.primitive)){
44
+ if(JSON.stringify(token.$value).match(/#(?:000|000000|fff|ffffff)(?![0-9a-f])/i))throw new Error(`Pure black or white in ${tokenPath}`);
45
+ if(typeof token.$value==="string"&&referencePattern.test(token.$value))throw new Error(`Primitive token ${tokenPath} cannot reference another tier`);
46
+ if(tokenPath.startsWith("dimension.scale.")&&!approvedDimensions.has(token.$value.value))throw new Error(`Off-scale primitive ${tokenPath}`);
47
+ }
48
+ for(const [tokenPath,token] of Object.entries(byTier.semantic)){
49
+ if(!token.$description)throw new Error(`Public Semantic token ${tokenPath} needs a description`);
50
+ const refs=JSON.stringify(token.$value).match(/\{[a-zA-Z0-9.-]+\}/g)||[];
51
+ for(const raw of refs){const ref=raw.slice(1,-1);if(!byTier.primitive[ref]&&!byTier.semantic[ref])throw new Error(`Semantic token ${tokenPath} has invalid reference ${raw}`)}
52
+ }
53
+ for(const [tokenPath,token] of Object.entries(byTier.component)){
54
+ if(!token.$description)throw new Error(`Public Component token ${tokenPath} needs a description`);
55
+ const refs=JSON.stringify(token.$value).match(/\{[a-zA-Z0-9.-]+\}/g)||[];
56
+ for(const raw of refs){const ref=raw.slice(1,-1);if(!byTier.semantic[ref]&&!byTier.component[ref])throw new Error(`Component token ${tokenPath} has invalid reference ${raw}`)}
57
+ if(token.$type==="color"&&refs.length===0)throw new Error(`Component color token ${tokenPath} must resolve through Semantic color`);
58
+ if(token.$type==="dimension"&&token.$value&&typeof token.$value==="object"&&typeof token.$value.value==="number"&&token.$value.unit&&!token.$extensions?.["oneli8.geometry"]&&!token.$extensions?.["oneli8.exception"])throw new Error(`Literal Component geometry ${tokenPath} needs an approval record`);
59
+ }
60
+ const allowedStatuses=new Set(["Stable","Candidate","Labs","Experimental","Deprecated"]);
61
+ if(!Array.isArray(status.components)||status.components.length===0)throw new Error("Component status registry must contain public records");
62
+ const statusNames=new Set();
63
+ for(const record of status.components){
64
+ for(const field of ["name","status","designerApproved","implementationCertified","contractPath"])if(!Object.hasOwn(record,field))throw new Error(`Component status record is missing ${field}`);
65
+ if(statusNames.has(record.name))throw new Error(`Duplicate component status ${record.name}`);
66
+ statusNames.add(record.name);
67
+ if(!allowedStatuses.has(record.status))throw new Error(`Unsupported component status ${record.status}`);
68
+ if(typeof record.designerApproved!=="boolean"||typeof record.implementationCertified!=="boolean")throw new Error(`Invalid approval flags for ${record.name}`);
69
+ if(!Array.isArray(record.knownEvidence)||!Array.isArray(record.missingEvidence))throw new Error(`Evidence arrays required for ${record.name}`);
70
+ if(record.designerApproved&&record.knownEvidence.length===0)throw new Error(`Designer approval for ${record.name} requires evidence`);
71
+ if(record.implementationCertified&&!record.implementationPath)throw new Error(`Certified implementation for ${record.name} requires a path`);
72
+ const contractFile=path.resolve(root,"..","..",record.contractPath);
73
+ // Standalone token consumers do not receive the full development documentation.
74
+ // Keep repository contract validation on by default; this flag changes no tokens.
75
+ if(!process.argv.includes("--standalone")&&!fs.existsSync(contractFile))throw new Error(`Missing component contract for ${record.name}: ${record.contractPath}`);
76
+ }
77
+ for(const tokenPath of Object.keys(all))resolveValue(all[tokenPath].$value,[tokenPath]);
78
+ for(const axis of ["colorScheme","primaryFamily","typographyViewport"]){
79
+ for(const [modeName,mode] of Object.entries(themes[axis]||{})){
80
+ for(const [tokenPath,token] of Object.entries(mode.overrides||{})){
81
+ if(!all[tokenPath])throw new Error(`Unknown ${axis}/${modeName} override target ${tokenPath}`);
82
+ const refs=JSON.stringify(token.$value).match(/\{[a-zA-Z0-9.-]+\}/g)||[];
83
+ for(const raw of refs){const ref=raw.slice(1,-1);if(!all[ref])throw new Error(`Invalid ${axis}/${modeName} reference ${raw}`)}
84
+ resolveValue(token.$value,[`${axis}.${modeName}.${tokenPath}`]);
85
+ }
86
+ }
87
+ }
88
+
89
+ const kebab=value=>value.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/\./g,"-").toLowerCase();
90
+ const cssName=tokenPath=>`--ol8-${kebab(tokenPath)}`;
91
+ const unitValue=value=>value&&typeof value==="object"&&typeof value.value==="number"&&value.unit?`${value.value}${value.unit}`:null;
92
+ const hexRgb=value=>{
93
+ const match=typeof value==="string"&&value.match(/^#([0-9a-f]{6})$/i);
94
+ return match?[0,2,4].map(offset=>Number.parseInt(match[1].slice(offset,offset+2),16)):null;
95
+ };
96
+ const alphaColor=value=>{
97
+ if(!value||typeof value!=="object"||typeof value.alpha!=="number")return null;
98
+ const channels=hexRgb(value.color);
99
+ return channels?`rgb(${channels.join(" ")} / ${value.alpha})`:null;
100
+ };
101
+ const shadowValue=value=>{
102
+ if(!Array.isArray(value))return null;
103
+ if(value.length===0)return "none";
104
+ const layers=value.map(layer=>{
105
+ const color=alphaColor({color:layer.color,alpha:layer.alpha});
106
+ const geometry=[layer.offsetX,layer.offsetY,layer.blur,layer.spread].map(unitValue);
107
+ return color&&geometry.every(Boolean)?`${geometry.join(" ")} ${color}`:null;
108
+ });
109
+ return layers.every(Boolean)?layers.join(", "):null;
110
+ };
111
+ const cssScalar=(value,token)=>{
112
+ const unit=unitValue(value);if(unit)return unit;
113
+ if(token?.$extensions?.["oneli8.unit"]==="em")return `${value}em`;
114
+ if(token?.$type==="color"){const color=alphaColor(value);if(color)return color}
115
+ if(token?.$type==="shadow"){const shadow=shadowValue(value);if(shadow!==null)return shadow}
116
+ if(Array.isArray(value)&&value.length===4&&value.every(item=>typeof item==="number"))return `cubic-bezier(${value.join(", ")})`;
117
+ if(typeof value==="string"||typeof value==="number")return String(value);
118
+ return null;
119
+ };
120
+ const cssLinesFor=(tokenPath,token,{preserveColorReference=false}={})=>{
121
+ const reference=typeof token.$value==="string"?token.$value.match(referencePattern):null;
122
+ if(preserveColorReference&&token.$type==="color"&&reference)return [` ${cssName(tokenPath)}: var(${cssName(reference[1])});`];
123
+ const compositeReference=token.$type==="color"&&token.$value&&typeof token.$value==="object"&&typeof token.$value.color==="string"?token.$value.color.match(referencePattern):null;
124
+ if(preserveColorReference&&compositeReference&&typeof token.$value.alpha==="number")return [` ${cssName(tokenPath)}: color-mix(in srgb, var(${cssName(compositeReference[1])}) ${token.$value.alpha*100}%, transparent);`];
125
+ const value=resolveValue(token.$value,[tokenPath]);
126
+ const scalar=cssScalar(value,token);
127
+ if(scalar!==null)return [` ${cssName(tokenPath)}: ${scalar};`];
128
+ if(value&&typeof value==="object")return Object.entries(value).flatMap(([key,item])=>{
129
+ const itemScalar=cssScalar(item,all[(String(token.$value?.[key]||"").match(referencePattern)||[])[1]]||token);
130
+ return itemScalar===null?[]:[` ${cssName(`${tokenPath}.${key}`)}: ${itemScalar};`];
131
+ });
132
+ return [];
133
+ };
134
+ const cssTokens={...byTier.primitive,...byTier.semantic,...byTier.component};
135
+ const css=["/* @generated by @oneli8/tokens — do not edit */",":root {",...Object.entries(cssTokens).flatMap(([p,t])=>cssLinesFor(p,t,{preserveColorReference:true})),"}"];
136
+ for(const [modeName,mode] of Object.entries(themes.colorScheme)){
137
+ if(!Object.keys(mode.overrides).length)continue;
138
+ css.push(``, `[data-ol8-color-scheme="${modeName}"] {`);
139
+ for(const [tokenPath,token] of Object.entries(mode.overrides))css.push(...cssLinesFor(tokenPath,token,{preserveColorReference:true}));
140
+ css.push("}");
141
+ }
142
+ for(const [modeName,mode] of Object.entries(themes.primaryFamily)){
143
+ css.push(``, `[data-ol8-primary="${modeName}"] {`);
144
+ for(const [tokenPath,token] of Object.entries(mode.overrides))css.push(...cssLinesFor(tokenPath,token,{preserveColorReference:true}));
145
+ css.push("}");
146
+ }
147
+ for(const mode of ["medium","expanded"]){
148
+ css.push(``, `[data-ol8-type-mode="${mode}"] {`);
149
+ for(const [tokenPath,token] of Object.entries(themes.typographyViewport[mode].overrides))css.push(...cssLinesFor(tokenPath,token));
150
+ css.push("}");
151
+ }
152
+
153
+ const resolved=Object.fromEntries(Object.entries(all).map(([tokenPath,token])=>[tokenPath,{type:token.$type,value:resolveValue(token.$value,[tokenPath]),description:token.$description||"",tier:byTier.primitive[tokenPath]?"primitive":byTier.semantic[tokenPath]?"semantic":"component"}]));
154
+ const tokenPaths=Object.keys(resolved).sort();
155
+
156
+ const dimensionScale=Object.fromEntries(Object.entries(byTier.primitive).filter(([p])=>p.startsWith("dimension.scale.")).map(([,t])=>{const value=resolveValue(t.$value);return [String(value.value/3),cssScalar(value,t)]}));
157
+ const radii=Object.fromEntries(Object.entries(byTier.semantic).filter(([p])=>p.startsWith("shape.radius.")).map(([p,t])=>[p.split(".").at(-1),cssScalar(resolveValue(t.$value),t)]));
158
+ const borderWidth=Object.fromEntries(Object.entries(byTier.semantic).filter(([p])=>p.startsWith("border.width.")).map(([p,t])=>[p.split(".").at(-1),cssScalar(resolveValue(t.$value),t)]));
159
+ const fontSize=Object.fromEntries(Object.entries(byTier.semantic).filter(([p])=>/^typography\.(display|title|body)\./.test(p)).map(([p,t])=>{const v=resolveValue(t.$value);return [p.replace("typography.","").replace(".","-"),[cssScalar(v.fontSize,t),{lineHeight:cssScalar(v.lineHeight,t),letterSpacing:`${v.letterSpacing}em`,fontWeight:String(v.fontWeight),fontFamily:v.fontFamily}]]}));
160
+ const duration=Object.fromEntries(Object.entries(byTier.primitive).filter(([p])=>p.startsWith("motion.duration.")).map(([p,t])=>[p.split(".").at(-1),cssScalar(resolveValue(t.$value),t)]));
161
+ const easing=Object.fromEntries(Object.entries(byTier.primitive).filter(([p])=>p.startsWith("motion.easing.")).map(([p,t])=>[p.split(".").at(-1),cssScalar(resolveValue(t.$value),t)]));
162
+ const boxShadow=Object.fromEntries(Object.keys(byTier.semantic).filter(p=>p.startsWith("elevation.surface.")).map(p=>[p.split(".").at(-1),`var(${cssName(p)})`]));
163
+ const zIndex=Object.fromEntries(Object.entries(byTier.semantic).filter(([p])=>p.startsWith("layer.stack.")).map(([p,t])=>[p.split(".").at(-1),String(resolveValue(t.$value))]));
164
+ const semanticColors=Object.fromEntries(Object.keys(byTier.semantic).filter(p=>p.startsWith("color.")&&!p.startsWith("color.primary.")).map(p=>[kebab(p.slice(6)),`var(${cssName(p)})`]));
165
+ const tailwind={theme:{extend:{colors:{ol8:semanticColors},spacing:dimensionScale,borderRadius:radii,borderWidth,boxShadow,zIndex,fontFamily:{display:["Syne","system-ui","sans-serif"],functional:["AR One Sans","system-ui","sans-serif"]},fontWeight:{regular:"450",semibold:"600",bold:"690"},fontSize,transitionDuration:duration,transitionTimingFunction:easing}}};
166
+
167
+ const title=value=>value.replace(/([a-z0-9])([A-Z])/g,"$1 $2").replace(/^./,char=>char.toUpperCase());
168
+ const figmaName=tokenPath=>tokenPath.split(".").map(title).join(" / ");
169
+ const modeData=Object.fromEntries(["colorScheme","primaryFamily","typographyViewport"].map(axis=>[axis,Object.fromEntries(Object.entries(themes[axis]).map(([modeName,mode])=>[modeName,Object.entries(mode.overrides).map(([tokenPath,token])=>({path:tokenPath,name:figmaName(tokenPath),type:token.$type,value:resolveValue(token.$value,[`${axis}.${modeName}.${tokenPath}`]),reference:(typeof token.$value==="string"&&token.$value.match(referencePattern)?.[1])||null,description:token.$description||""}))]))]));
170
+ const figma={generated:true,source:"@oneli8/tokens",collections:{primitive:[],semantic:[],component:[]},typographyStyles:[],elevationStyles:[],modeAxes:themes.$extensions["oneli8.axes"],modes:modeData};
171
+ for(const [tokenPath,record] of Object.entries(resolved)){
172
+ const sourceToken=all[tokenPath];
173
+ const entry={path:tokenPath,name:figmaName(tokenPath),type:record.type,value:record.value,reference:(typeof sourceToken.$value==="string"&&sourceToken.$value.match(referencePattern)?.[1])||null,description:record.description};
174
+ if(record.type==="typography")figma.typographyStyles.push(entry);
175
+ else if(record.type==="shadow")figma.elevationStyles.push(entry);
176
+ else figma.collections[record.tier].push(entry);
177
+ }
178
+
179
+ const ensure=relative=>fs.mkdirSync(path.dirname(path.join(root,relative)),{recursive:true});
180
+ const write=(relative,content)=>{ensure(relative);fs.writeFileSync(path.join(root,relative),content)};
181
+ write("build/css/tokens.css",`${css.join("\n")}\n`);
182
+ write("build/typescript/index.js",`// @generated by @oneli8/tokens — do not edit\nexport const tokens = ${JSON.stringify(resolved,null,2)};\nexport const tokenPaths = ${JSON.stringify(tokenPaths,null,2)};\nexport const themeAxes = ${JSON.stringify(themes.$extensions["oneli8.axes"],null,2)};\nexport const themeModes = ${JSON.stringify(modeData,null,2)};\nexport default tokens;\n`);
183
+ write("build/typescript/index.d.ts",`// @generated by @oneli8/tokens — do not edit\nexport type TokenPath = ${tokenPaths.map(p=>JSON.stringify(p)).join(" | ")};\nexport type ColorScheme = "light" | "dark";\nexport type PrimaryFamily = "blue" | "orange" | "green";\nexport type TypographyViewport = "compact" | "medium" | "expanded";\nexport declare const tokens: Readonly<Record<TokenPath,{readonly type:string;readonly value:unknown;readonly description:string;readonly tier:"primitive"|"semantic"|"component"}>>;\nexport declare const tokenPaths: readonly TokenPath[];\nexport declare const themeAxes: Readonly<Record<string,readonly string[]>>;\nexport declare const themeModes: Readonly<Record<string,unknown>>;\nexport default tokens;\n`);
184
+ write("build/tailwind/preset.mjs",`// @generated by @oneli8/tokens — do not edit\nexport default ${JSON.stringify(tailwind,null,2)};\n`);
185
+ const tw4=["/* @generated Tailwind CSS v4 theme bridge — do not edit */","@theme {",...Object.entries(semanticColors).map(([k,v])=>` --color-ol8-${k}: ${v};`),...Object.entries(dimensionScale).map(([k,v])=>` --spacing-${k}: ${v};`),...Object.entries(radii).map(([k,v])=>` --radius-${k}: ${v};`),...Object.entries(boxShadow).map(([k,v])=>` --shadow-ol8-${k}: ${v};`),...Object.entries(zIndex).map(([k,v])=>` --z-ol8-${k}: ${v};`),` --font-display: "Syne", system-ui, sans-serif;`,` --font-functional: "AR One Sans", system-ui, sans-serif;`,` --font-weight-regular: 450;`,` --font-weight-semibold: 600;`,` --font-weight-bold: 690;`,"}"];
186
+ write("build/tailwind/theme.css",`${tw4.join("\n")}\n`);
187
+ write("build/figma/variables.json",`${JSON.stringify(figma,null,2)}\n`);
188
+ write("build/status/components.json",`${JSON.stringify({...status,$schema:"https://oneli8.org/schema/component-status.schema.json",generated:true},null,2)}\n`);
189
+ write("build/manifest.json",`${JSON.stringify({generated:true,version:"1.0.0-beta.1",counts:{primitive:Object.keys(byTier.primitive).length,semantic:Object.keys(byTier.semantic).length,component:Object.keys(byTier.component).length,componentStatuses:status.components.length,figmaVariables:figma.collections.primitive.length+figma.collections.semantic.length+figma.collections.component.length,figmaTypographyStyles:figma.typographyStyles.length,figmaElevationStyles:figma.elevationStyles.length},outputs:["css/tokens.css","typescript/index.js","typescript/index.d.ts","tailwind/preset.mjs","tailwind/theme.css","figma/variables.json","status/components.json"]},null,2)}\n`);
190
+
191
+ console.log(`Built ${tokenPaths.length} Oneli8 tokens for CSS, TypeScript, Tailwind, and Figma.`);
@@ -0,0 +1,96 @@
1
+ import fs from "node:fs";
2
+ import assert from "node:assert/strict";
3
+ import {pathToFileURL,fileURLToPath} from "node:url";
4
+ import path from "node:path";
5
+
6
+ const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),"..");
7
+ await import(pathToFileURL(path.join(root,"scripts/build.mjs")));
8
+ const read=relative=>fs.readFileSync(path.join(root,relative),"utf8");
9
+ const json=relative=>JSON.parse(read(relative));
10
+ let checks=0;
11
+ const ok=(condition,message)=>{assert(condition,message);checks++};
12
+ const primitive=json("src/primitive.json");
13
+ const semantic=json("src/semantic.json");
14
+ const themes=json("src/themes.json");
15
+ const component=json("src/component.json");
16
+ const status=json("status/components.json");
17
+ const builtStatus=json("build/status/components.json");
18
+ const manifest=json("build/manifest.json");
19
+ const css=read("build/css/tokens.css");
20
+ const tw=read("build/tailwind/preset.mjs");
21
+ const tw4=read("build/tailwind/theme.css");
22
+ const figma=json("build/figma/variables.json");
23
+ const indices=["030","060","090","120","240","360","480","600","720","840","930"];
24
+ const families=["blue","cyan","green","yellow","orange","pink","violet","red","neutral"];
25
+
26
+ ok(primitive.dimension.scale["108"].$value.value===108,"108px primitive must ship");
27
+ ok(semantic.spacing.section.expansive.$value==="{dimension.scale.108}","Expansive section must reference 108px primitive");
28
+ ok(semantic.size.target.minimum.$value==="{dimension.scale.048}","Minimum target must reference 48px");
29
+ ok(Object.values(semantic.size.icon).map(token=>token.$value).join(",")==="{dimension.scale.009},{dimension.scale.012},{dimension.scale.018},{dimension.scale.024},{dimension.scale.030},{dimension.scale.036},{dimension.scale.048},{dimension.scale.060},{dimension.scale.072},{dimension.scale.096},{dimension.scale.108}","Icon Frame semantic scale must expose all eleven approved presentations");
30
+ ok(semantic.shape.radius.full.$extensions["oneli8.exception"]==="categorical-full-radius","Full radius exception must be explicit");
31
+ ok(semantic.border.width.standard.$extensions["oneli8.exception"]==="essential-optical-boundary","2px essential border exception must be explicit");
32
+ ok(semantic.typography.body.medium.$value.fontWeight==="{font.weight.regular}","Body Medium must resolve through Regular 450");
33
+ ok(primitive.motion.easing.standard.$value[0]===0.39,"Standard easing must retain 0.39 first control point");
34
+ ok(primitive.motion.easing.exit.$value[0]===0.48,"Exit easing must retain 0.48 first control point");
35
+ ok(semantic.material.gem.opacity.environmental.$value===0.48,"Gem Environmental tint floor must be encoded");
36
+ ok(css.includes("--ol8-spacing-section-expansive: 108px"),"CSS must contain resolved Semantic spacing");
37
+ ok(css.includes("--ol8-typography-body-medium-font-weight: 450"),"CSS must resolve typography composition");
38
+ ok(css.includes('[data-ol8-type-mode="expanded"]'),"CSS must contain independent expanded typography mode");
39
+ ok(tw.includes('"1": "3px"')&&tw.includes('"36": "108px"')&&tw.includes('"regular": "450"'),"Tailwind v3 preset must expose the familiar 3px-indexed scale and weights");
40
+ ok(tw4.includes("@theme")&&tw4.includes("--spacing-1: 3px")&&tw4.includes("--spacing-36: 108px"),"Tailwind v4 theme bridge must expose the 3px-indexed scale");
41
+ ok(figma.modeAxes.typographyViewport.join(",")==="compact,medium,expanded","Figma data must retain viewport modes");
42
+ ok(figma.typographyStyles.length===9,"Figma data must emit exactly nine default typography styles");
43
+ ok(manifest.outputs.length===7,"Build manifest must list all seven generated outputs");
44
+ ok(manifest.counts.primitive>50&&manifest.counts.semantic>70,"Initial canonical slice must contain substantial approved coverage");
45
+ ok(!/#(?:000|000000|fff|ffffff)(?![0-9a-f])/i.test([read("src/primitive.json"),read("src/semantic.json"),css].join("\n")),"Authored and generated tokens cannot contain pure black or pure white");
46
+ ok(families.every(family=>indices.every(index=>primitive.color[family][index]?.$type==="color")),"Candidate 03 must contain nine complete eleven-step color families");
47
+ ok(new Set(families.flatMap(family=>indices.map(index=>primitive.color[family][index].$value.toUpperCase()))).size===99,"Candidate 03 must retain 99 unique approved values");
48
+ ok(primitive.color.blue["600"].$value==="#2C438B"&&primitive.color.neutral["930"].$value==="#151817"&&primitive.color.cyan["840"].$value==="#163F4B","Canonical color anchors must remain exact");
49
+ ok(semantic.color.actionPrimary.default.$value==="{color.primary.600}"&&semantic.color.text.link.default.$value==="{color.primary.600}","Light Primary semantics must resolve through family aliases");
50
+ ok(semantic.color.text.link.hover.$value==="{color.primary.720}"&&semantic.color.text.link.pressed.$value==="{color.primary.840}"&&semantic.color.text.link.visited.$value==="{color.violet.600}","Light Link states must resolve through qualified semantic aliases");
51
+ ok(themes.colorScheme.dark.overrides["color.text.link.default"].$value==="{color.primary.120}"&&themes.colorScheme.dark.overrides["color.text.link.hover"].$value==="{color.primary.090}"&&themes.colorScheme.dark.overrides["color.text.link.pressed"].$value==="{color.primary.060}"&&themes.colorScheme.dark.overrides["color.text.link.visited"].$value==="{color.violet.120}","Dark Link states must retain the approved accessible family mapping");
52
+ ok(semantic.color.focus.inner.$value==="{color.primary.030}"&&semantic.color.focus.outer.$value==="{color.primary.930}","Light Focus must use the active family's 030 highlight and 930 shadow");
53
+ ok(themes.colorScheme.dark.overrides["color.focus.inner"].$value==="{color.primary.930}"&&themes.colorScheme.dark.overrides["color.focus.outer"].$value==="{color.primary.030}","Dark Focus must reverse the active family's 930 shadow and 030 highlight");
54
+ ok(semantic.color.icon.onGem.$value==="{color.neutral.030}"&&!themes.colorScheme.dark.overrides["color.icon.onGem"],"On Gem icon artwork must remain near-white across Light and Dark appearances");
55
+ ok(css.includes('[data-ol8-color-scheme="dark"]')&&css.includes('[data-ol8-primary="orange"]')&&css.includes('[data-ol8-primary="green"]'),"CSS must emit independent scheme and Primary-family axes");
56
+ ok(css.includes("--ol8-color-action-primary-default: var(--ol8-color-primary-600)")&&css.includes("--ol8-color-primary-600: var(--ol8-color-orange-600)"),"CSS color aliases must remain reference-preserving and composable");
57
+ ok(tw.includes('"background-canvas": "var(--ol8-color-background-canvas)"')&&tw.includes('"action-primary-default": "var(--ol8-color-action-primary-default)"'),"Tailwind v3 must expose Semantic colors rather than raw palette meaning");
58
+ ok(tw4.includes("--color-ol8-background-canvas: var(--ol8-color-background-canvas)")&&tw4.includes("--color-ol8-action-primary-default: var(--ol8-color-action-primary-default)"),"Tailwind v4 must expose Semantic color utilities");
59
+ ok(figma.modes.colorScheme.dark.length>60&&figma.modes.primaryFamily.orange.length===11&&figma.modes.primaryFamily.green.length===11,"Figma data must preserve independent color modes");
60
+ ok(semantic.elevation.surface.floating.$value.length===2&&semantic.elevation.surface.floating.$value[0].alpha===0.18,"Light Floating elevation must retain its approved two-shadow recipe");
61
+ ok(themes.colorScheme.dark.overrides["elevation.surface.blocking"].$value[0].alpha===0.72,"Dark Blocking elevation must retain its 72% Cyan 840 cue");
62
+ ok(Object.values(semantic.layer.stack).map(token=>token.$value).join(",")==="0,300,600,900,1200","Stacking tiers must retain the approved 300-step progression");
63
+ ok(css.includes("--ol8-elevation-surface-overlay: 0px 12px 36px -6px rgb(22 63 75 / 0.24), 0px 6px 12px 0px rgb(22 63 75 / 0.18)"),"CSS must serialize approved multi-layer Elevation");
64
+ ok(semantic.material.gem.color.quiet.environmentalFace.$value.alpha===0.48&&themes.colorScheme.dark.overrides["material.gem.color.quiet.environmentalFace"].$value.alpha===0.48&&semantic.material.gem.color.family.emerald.environmentalFace.$value.alpha===0.48&&semantic.material.gem.color.family.sapphire.stabilizedFace.$value.alpha===0.30,"Gem environmental and stabilized alpha values must remain approved");
65
+ ok(semantic.material.gem.color.family.sapphire.innerHigh.$value.alpha===0.39,"Sapphire Thin-Cut highlight must retain its 39% cap");
66
+ ok(semantic.material.control.gem.primary.start.$value==="{color.primary.480}"&&themes.colorScheme.dark.overrides["material.control.gem.primary.start"].$value==="{color.primary.600}","Gem controls must keep Light and Dark theme-aware leading stops");
67
+ ok(semantic.material.control.gem.secondary.high.$value.alpha===0.22&&semantic.material.control.gem.rim.default.$value.alpha===0.24,"Gem Secondary transparency and inset rim must retain the approved quiet material recipe");
68
+ ok(semantic.material.control.gem.primary.loadingStart.$value==="{color.primary.720}"&&semantic.material.control.gem.primary.loadingEnd.$value==="{color.primary.930}","Gem Loading must use a distinct shared deep tonal state");
69
+ ok(semantic.material.control.gem.secondary.loadingHigh.$value.alpha===0.39&&semantic.material.control.gem.secondary.loadingLow.$value.alpha===0.24,"Gem Secondary Loading must remain transparent but visibly distinct");
70
+ ok(css.includes("--ol8-material-gem-color-family-amethyst-environmental-face: color-mix(in srgb, var(--ol8-color-violet-930) 48%, transparent)"),"Gem CSS colors must preserve Primitive references and approved alpha");
71
+ ok(tw.includes('"overlay": "var(--ol8-elevation-surface-overlay)"')&&tw.includes('"notification": "1200"'),"Tailwind v3 must expose Elevation and stacking roles");
72
+ ok(tw4.includes("--shadow-ol8-blocking: var(--ol8-elevation-surface-blocking)")&&tw4.includes("--z-ol8-modal: 900"),"Tailwind v4 must expose Elevation and stacking roles");
73
+ ok(figma.elevationStyles.length===5,"Figma data must emit five Elevation styles");
74
+ ok(component.component.button.paddingInline.standard.$value.value===15&&component.component.button.gap.comfortable.$value.value===9,"Button component geometry must retain the approved 15px inset and 9px gap");
75
+ ok(component.component.opticalStroke.$value.value===1.8&&component.component.opticalStroke.$extensions["oneli8.exception"]==="universal-optical-stroke","The universal canonical-source optical stroke must remain 1.8 units");
76
+ ok(component.component.icon.stroke.$value==="{component.opticalStroke}","Icon Frame source must alias the universal optical stroke");
77
+ ok(component.component.choice.opticalStroke.$value==="{component.opticalStroke}","Choice controls must alias the universal optical stroke");
78
+ ok(component.component.checkbox.boundary.$value==="{component.choice.opticalStroke}"&&component.component.checkbox.mark.stroke.$value==="{component.choice.opticalStroke}"&&component.component.radio.boundary.$value==="{component.choice.opticalStroke}","Checkbox boundary, Checkbox mark, and Radio boundary must share the Choice optical stroke");
79
+ ok(semantic.color.selection.surface.$value==="{color.primary.060}"&&themes.colorScheme.dark.overrides["color.selection.surface"].$value==="{color.primary.120}","Checkbox and Radio selected controls must share Selection Surface through the configurable Primary family");
80
+ ok(component.component.radio.dot.compact.$value.value===9&&component.component.radio.dot.comfortable.$value.value===12,"Radio selected dots must retain the approved 9px and 12px geometry");
81
+ ok(component.component.switch.travel.compact.$value==="{size.indicator.standard}"&&component.component.switch.travel.comfortable.$value==="{size.indicator.large}","Switch travel must resolve to approved 18px and 24px geometry");
82
+ ok(semantic.color.control.thumb.$value==="{color.neutral.030}","Switch thumb must remain near-white across Light and Dark appearances");
83
+ ok(component.component.selectionPopup.maximumBlock.standard.$value.value===288&&component.component.selectionPopup.maximumBlock.large.$value.value===360,"Selection popup maximum block sizes must retain the approved recipes");
84
+ ok(component.component.selectionPopup.paddingInline.$value==="{spacing.inline.minimal}"&&component.component.selectionPopup.railInsetInline.$value==="{spacing.inline.minimal}"&&component.component.selectionPopup.paddingBlock.$value==="{spacing.inline.related}","Selection popup must retain the approved 3px + 3px visible edge and 6px block inset");
85
+ ok(status.components.length===10&&new Set(status.components.map(record=>record.name)).size===10,"Status registry must contain ten unique public component records");
86
+ ok(status.components.filter(record=>record.status==="Candidate").length===10&&status.components.filter(record=>record.status==="Labs").length===0,"All ten approved families must now have Candidate production implementations");
87
+ ok(status.components.every(record=>record.designerApproved&&!record.implementationCertified&&record.knownEvidence.length>0&&record.missingEvidence.length>0),"Visual approval must remain separate from implementation certification");
88
+ ok(builtStatus.generated===true&&builtStatus.components.length===status.components.length,"Generated status registry must preserve every authored record");
89
+ ok(component.component.choiceChip.height.compact.$value.value===30&&component.component.choiceChip.height.standard.$value==="{size.control.compact}"&&component.component.choiceChip.height.comfortable.$value==="{size.control.standard}","Soft Hexagon visible heights must retain the approved 30px, 36px, and 42px geometry");
90
+ ok(component.component.choiceChip.terminal.$value==="{spacing.inline.standard}"&&component.component.choiceChip.innerTerminal.$value.value===11,"Soft Hexagon terminal geometry must remain fixed rather than scaling with label length");
91
+ ok(component.component.choiceChip.inset.compact.$value.value===21&&component.component.choiceChip.inset.standard.$value==="{spacing.inset.comfortable}"&&component.component.choiceChip.inset.comfortable.$value.value===27,"Soft Hexagon insets must retain the approved 21px, 24px, and 27px progression");
92
+ ok(component.component.suggestionRail.gap.$value==="{component.choiceChip.gap}"&&component.component.suggestionRail.fieldSeparation.$value==="{spacing.stack.tight}","Suggested Values rail must reuse Choice Chip and structural spacing roles");
93
+ ok(component.component.tabBar.gap.$value==="{spacing.none}"&&component.component.tabBar.inlineInset.$value==="{spacing.inline.minimal}","Tab Bar spacing must map to canonical zero gap and 3px Inline protection");
94
+ ok(manifest.counts.primitive===169&&manifest.counts.semantic===266&&manifest.counts.component===151&&manifest.counts.componentStatuses===10&&manifest.counts.figmaVariables===572&&manifest.counts.figmaElevationStyles===5,"Navigation spacing and contextual token output counts must remain deterministic");
95
+
96
+ console.log(`PASS ${checks}/${checks} canonical token package checks.`);