@santi020k/lumen 0.4.0 → 1.0.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.
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,KAAK,EACV,aAAa,EACb,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,wBAAwB,EACzB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,aAAa,EAAE,CAAA;AAExB,YAAY,EACV,aAAa,EACb,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,wBAAwB,EACzB,CAAA;AAOD,eAAO,MAAM,uBAAuB,GAAI,WAAU,aAA6B,KAAG,kBAAkB,EAGnG,CAAA;AAED,eAAO,MAAM,oBAAoB,GAC/B,MAAM,MAAM,EACZ,WAAU,aAA6B,KACtC,kBAAkB,GAAG,SAMvB,CAAA;AAgFD,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,MAAM,GAAG,GAAG,EACpB,UAAS,wBAA6B,KACrC,OAAO,CAAC,aAAa,CAcvB,CAAA;AAED,OAAO,EACL,oBAAoB,EACpB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAA"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,KAAK,EACV,aAAa,EACb,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,wBAAwB,EACzB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,aAAa,EAAE,CAAA;AAExB,YAAY,EACV,aAAa,EACb,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,wBAAwB,EACzB,CAAA;AASD,eAAO,MAAM,uBAAuB,GAAI,WAAU,aAA6B,KAAG,kBAAkB,EAGnG,CAAA;AAED,eAAO,MAAM,oBAAoB,GAC/B,MAAM,MAAM,EACZ,WAAU,aAA6B,KACtC,kBAAkB,GAAG,SAQvB,CAAA;AAmFD,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,MAAM,GAAG,GAAG,EACpB,UAAS,wBAA6B,KACrC,OAAO,CAAC,aAAa,CAevB,CAAA;AAED,OAAO,EACL,oBAAoB,EACpB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAA"}
package/dist/registry.js CHANGED
@@ -3,7 +3,10 @@ import { readFile } from 'node:fs/promises';
3
3
  import { lumenRegistry } from './registry-data.js';
4
4
  export { lumenRegistry };
5
5
  const defaultRegistryTimeoutMs = 30_000;
6
- const normalizeRegistryEntryName = (name) => name.replaceAll(/([a-z0-9])([A-Z])/g, '$1-$2').replaceAll(/[\s_]+/g, '-').toLowerCase();
6
+ const normalizeRegistryEntryName = (name) => name
7
+ .replaceAll(/([a-z0-9])([A-Z])/g, '$1-$2')
8
+ .replaceAll(/[\s_]+/g, '-')
9
+ .toLowerCase();
7
10
  export const getLumenRegistryEntries = (registry = lumenRegistry) => [
8
11
  ...(registry.components ?? []),
9
12
  ...registry.items
@@ -11,8 +14,8 @@ export const getLumenRegistryEntries = (registry = lumenRegistry) => [
11
14
  export const getLumenRegistryItem = (name, registry = lumenRegistry) => {
12
15
  const normalizedName = normalizeRegistryEntryName(name);
13
16
  const entries = getLumenRegistryEntries(registry);
14
- return entries.find(item => item.name === name) ??
15
- entries.find(item => normalizeRegistryEntryName(item.name) === normalizedName);
17
+ return (entries.find(item => item.name === name) ??
18
+ entries.find(item => normalizeRegistryEntryName(item.name) === normalizedName));
16
19
  };
17
20
  const isRegistryFile = (value) => {
18
21
  if (typeof value === 'string')
@@ -22,42 +25,43 @@ const isRegistryFile = (value) => {
22
25
  const file = value;
23
26
  return typeof file.path === 'string' && typeof file.source === 'string';
24
27
  };
25
- const isStringArray = (value) => Array.isArray(value) && value.every(entry => typeof entry === 'string');
26
- const isRegistryFileArray = (value) => Array.isArray(value) && value.every(entry => isRegistryFile(entry));
28
+ const isString = (entry) => typeof entry === 'string';
29
+ const isStringArray = (value) => Array.isArray(value) && value.every(isString);
30
+ const isRegistryFileArray = (value) => Array.isArray(value) && value.every(isRegistryFile);
27
31
  const registryItemTypes = new Set(['component-set', 'documentation', 'recipe']);
28
32
  const isRegistryItem = (value) => {
29
33
  if (!value || typeof value !== 'object')
30
34
  return false;
31
35
  const item = value;
32
- return typeof item.name === 'string' &&
36
+ return (typeof item.name === 'string' &&
33
37
  typeof item.type === 'string' &&
34
38
  registryItemTypes.has(item.type) &&
35
39
  (item.components === undefined || isStringArray(item.components)) &&
36
- (item.files === undefined || isRegistryFileArray(item.files));
40
+ (item.files === undefined || isRegistryFileArray(item.files)));
37
41
  };
38
42
  const isRegistryComponent = (value) => {
39
43
  if (!value || typeof value !== 'object')
40
44
  return false;
41
45
  const component = value;
42
- return typeof component.name === 'string' &&
46
+ return (typeof component.name === 'string' &&
43
47
  component.type === 'component' &&
44
48
  typeof component.description === 'string' &&
45
49
  typeof component.category === 'string' &&
46
50
  isRegistryFileArray(component.files) &&
47
- (component.dependencies === undefined || isStringArray(component.dependencies));
51
+ (component.dependencies === undefined || isStringArray(component.dependencies)));
48
52
  };
49
53
  const isLumenRegistry = (value) => {
50
54
  if (!value || typeof value !== 'object')
51
55
  return false;
52
56
  const registry = value;
53
- return typeof registry.name === 'string' &&
57
+ return (typeof registry.name === 'string' &&
54
58
  typeof registry.version === 'number' &&
55
59
  typeof registry.description === 'string' &&
56
60
  isStringArray(registry.packages) &&
57
61
  Array.isArray(registry.items) &&
58
62
  registry.items.every(item => isRegistryItem(item)) &&
59
63
  (registry.components === undefined ||
60
- (Array.isArray(registry.components) && registry.components.every(component => isRegistryComponent(component))));
64
+ (Array.isArray(registry.components) && registry.components.every(component => isRegistryComponent(component)))));
61
65
  };
62
66
  const fetchRegistrySource = async (source, options) => {
63
67
  if (options.token && source.startsWith('http://')) {
@@ -77,9 +81,9 @@ const fetchRegistrySource = async (source, options) => {
77
81
  };
78
82
  export const loadLumenRegistry = async (source, options = {}) => {
79
83
  const sourceValue = String(source);
80
- const raw = sourceValue.startsWith('http://') || sourceValue.startsWith('https://')
81
- ? await fetchRegistrySource(sourceValue, options)
82
- : await readFile(source, 'utf8');
84
+ const raw = sourceValue.startsWith('http://') || sourceValue.startsWith('https://') ?
85
+ await fetchRegistrySource(sourceValue, options) :
86
+ await readFile(source, 'utf8');
83
87
  const parsed = JSON.parse(raw);
84
88
  if (!isLumenRegistry(parsed)) {
85
89
  throw new Error('Invalid Lumen registry manifest');
@@ -1 +1 @@
1
- {"version":3,"file":"token-audit.d.ts","sourceRoot":"","sources":["../src/token-audit.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd;AAMD,eAAO,MAAM,kBAAkB,GAAI,QAAQ,MAAM,EAAE,aAAgB,KAAG,sBAAsB,EAwB3F,CAAA"}
1
+ {"version":3,"file":"token-audit.d.ts","sourceRoot":"","sources":["../src/token-audit.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd;AAkFD,eAAO,MAAM,kBAAkB,GAAI,QAAQ,MAAM,EAAE,aAAgB,KAAG,sBAAsB,EA+B3F,CAAA"}
@@ -1,18 +1,73 @@
1
1
  import { lumenSemanticColorTokenNames } from '@santi020k/lumen-core';
2
- const declarationPattern = /--([a-z][\w-]*)\s*:\s*([^;}\n]+)/giu;
3
- const channelPattern = /^(?:var\(.+\)|-?(?:\d+(?:\.\d+)?|\.\d+)(?:deg|grad|rad|turn)?\s+-?(?:\d+(?:\.\d+)?|\.\d+)%\s+-?(?:\d+(?:\.\d+)?|\.\d+)%(?:\s*\/\s*(?:\d+(?:\.\d+)?|\.\d+)%?)?)$/iu;
2
+ const channelPattern = new RegExp([
3
+ String.raw `^(?:var\(.+\)|-?(?:\d+(?:\.\d+)?|\.\d+)`,
4
+ String.raw `(?:deg|grad|rad|turn)?\s+-?(?:\d+(?:\.\d+)?|\.\d+)%`,
5
+ String.raw `\s+-?(?:\d+(?:\.\d+)?|\.\d+)%`,
6
+ String.raw `(?:\s*\/\s*(?:\d+(?:\.\d+)?|\.\d+)%?)?)$`
7
+ ].join(''), 'iu');
4
8
  const semanticTokens = new Set(lumenSemanticColorTokenNames);
9
+ const isAsciiLetter = (character) => character !== undefined && /[a-z]/iu.test(character);
10
+ const isTokenCharacter = (character) => character !== undefined && /[\w-]/u.test(character);
11
+ const isWhitespace = (character) => character !== undefined && /\s/u.test(character);
12
+ const scanTokenDeclaration = (source, index) => {
13
+ let tokenEnd = index + 2;
14
+ if (!isAsciiLetter(source[tokenEnd]))
15
+ return { cursor: index + 1 };
16
+ tokenEnd += 1;
17
+ while (isTokenCharacter(source[tokenEnd]))
18
+ tokenEnd += 1;
19
+ const token = source.slice(index + 2, tokenEnd);
20
+ let colon = tokenEnd;
21
+ while (isWhitespace(source[colon]))
22
+ colon += 1;
23
+ if (source[colon] !== ':')
24
+ return { cursor: colon };
25
+ const valueStart = colon + 1;
26
+ let valueEnd = valueStart;
27
+ while (valueEnd < source.length &&
28
+ source[valueEnd] !== ';' &&
29
+ source[valueEnd] !== '}' &&
30
+ source[valueEnd] !== '\n') {
31
+ valueEnd += 1;
32
+ }
33
+ return {
34
+ cursor: valueEnd,
35
+ declaration: {
36
+ index,
37
+ token,
38
+ value: source.slice(valueStart, valueEnd).trim()
39
+ }
40
+ };
41
+ };
42
+ const findTokenDeclarations = (source) => {
43
+ const declarations = [];
44
+ let cursor = 0;
45
+ while (cursor < source.length) {
46
+ const index = source.indexOf('--', cursor);
47
+ if (index === -1)
48
+ break;
49
+ const scan = scanTokenDeclaration(source, index);
50
+ cursor = scan.cursor;
51
+ if (scan.declaration)
52
+ declarations.push(scan.declaration);
53
+ }
54
+ return declarations;
55
+ };
5
56
  export const auditLumenTokenCss = (source, file = '<input>') => {
6
57
  const findings = [];
7
- for (const match of source.matchAll(declarationPattern)) {
8
- const token = match[1];
9
- const value = match[2]?.trim();
58
+ let line = 1;
59
+ let lineCursor = 0;
60
+ let lineStart = 0;
61
+ for (const { index, token, value } of findTokenDeclarations(source)) {
10
62
  if (!token || !value || !semanticTokens.has(token) || channelPattern.test(value))
11
63
  continue;
12
- const index = match.index;
13
- const before = source.slice(0, index);
14
- const line = before.split('\n').length;
15
- const lineStart = before.lastIndexOf('\n') + 1;
64
+ let newline = source.indexOf('\n', lineCursor);
65
+ while (newline !== -1 && newline < index) {
66
+ line += 1;
67
+ lineStart = newline + 1;
68
+ lineCursor = lineStart;
69
+ newline = source.indexOf('\n', lineCursor);
70
+ }
16
71
  findings.push({
17
72
  column: index - lineStart + 1,
18
73
  file,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@santi020k/lumen",
3
- "version": "0.4.0",
3
+ "version": "1.0.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -39,12 +39,14 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "semver": "^7.8.0",
42
- "@santi020k/lumen-core": "^0.4.0"
42
+ "@santi020k/lumen-core": "^1.0.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^26.1.1",
46
+ "@types/react": "^19.2.17",
46
47
  "@types/semver": "^7.7.1",
47
48
  "@vitest/coverage-v8": "^4.1.10",
49
+ "react": "^19.2.8",
48
50
  "typescript": "^6.0.3",
49
51
  "tailwindcss": "^4.3.3",
50
52
  "vitest": "^4.1.10"
@@ -72,8 +74,8 @@
72
74
  "typecheck": "tsc -p tsconfig.json --noEmit",
73
75
  "test": "vitest run --config ../../vitest.config.ts --project lumen",
74
76
  "test:coverage": "vitest run --config ../../vitest.config.ts --project lumen --coverage",
75
- "lint": "eslint . --no-warn-ignored",
76
- "lint:fix": "eslint . --fix --no-warn-ignored",
77
+ "lint": "eslint . --no-warn-ignored --max-warnings=0",
78
+ "lint:fix": "eslint . --fix --no-warn-ignored --max-warnings=0",
77
79
  "clean": "rm -rf dist coverage"
78
80
  }
79
81
  }