@nlabs/lex 2.0.3 → 2.0.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nlabs/lex",
3
- "version": "2.0.3",
3
+ "version": "2.0.4",
4
4
  "description": "Lex",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -53,6 +53,7 @@
53
53
  "build:all": "npm run build && npm run build:ai",
54
54
  "build:ai": "NODE_ENV=production && npx swc 'src/commands/ai/**/*.{ts,tsx}' --out-dir ./lib/commands/ai --source-maps inline",
55
55
  "declarations": "tsc -p tsconfig.build.json",
56
+ "docs:check": "npm run build && node scripts/validate-docs.js",
56
57
  "clean": "rm -rf lib node_modules package-lock.json *.log coverage",
57
58
  "compile": "tsc -p tsconfig.build.json",
58
59
  "env": "LEX_CONFIG='{\"useTypescript\":true}'",
@@ -86,20 +87,20 @@
86
87
  "dependencies": {
87
88
  "@playwright/test": "^1.62.1",
88
89
  "@rollup/plugin-inject": "^5.0.5",
89
- "@storybook/addon-docs": "^10.5.8",
90
- "@storybook/addon-links": "^10.5.8",
91
- "@storybook/addon-themes": "^10.5.8",
92
- "@storybook/react": "^10.5.8",
93
- "@storybook/react-vite": "^10.5.8",
90
+ "@storybook/addon-docs": "^10.5.10",
91
+ "@storybook/addon-links": "^10.5.10",
92
+ "@storybook/addon-themes": "^10.5.10",
93
+ "@storybook/react": "^10.5.10",
94
+ "@storybook/react-vite": "^10.5.10",
94
95
  "@swc/cli": "^0.8.1",
95
- "@swc/core": "^1.16.0",
96
- "@swc/react-compiler": "^1.16.0",
96
+ "@swc/core": "^1.16.1",
97
+ "@swc/react-compiler": "^1.16.1",
97
98
  "@tailwindcss/forms": "^0.5.11",
98
99
  "@tailwindcss/nesting": "^0.0.0-insiders.565cd3e",
99
100
  "@tailwindcss/postcss": "4.3.3",
100
101
  "@testing-library/jest-dom": "^7.0.1",
101
102
  "@testing-library/react": "^16.3.2",
102
- "@vitest/coverage-v8": "^4.1.10",
103
+ "@vitest/coverage-v8": "4.1.11",
103
104
  "assert": "^2.1.0",
104
105
  "autoprefixer": "^10.5.4",
105
106
  "boxen": "8.0.1",
@@ -109,9 +110,9 @@
109
110
  "commander": "^15.0.0",
110
111
  "compare-versions": "^6.1.1",
111
112
  "core-js": "^3.50.0",
112
- "cssnano": "^8.0.6",
113
+ "cssnano": "^8.0.8",
113
114
  "esbuild": "^0.28.2",
114
- "eslint": "^10.8.1",
115
+ "eslint": "^10.9.0",
115
116
  "eslint-config-styleguidejs": "^5.0.7",
116
117
  "execa": "10.0.1",
117
118
  "expect": "^30.4.1",
@@ -129,7 +130,7 @@
129
130
  "luxon": "^3.7.2",
130
131
  "math-expression-evaluator": "^2.0.7",
131
132
  "npm-check-updates": "^23.0.2",
132
- "openai": "^7.4.0",
133
+ "openai": "^7.5.0",
133
134
  "ora": "9.4.1",
134
135
  "os-browserify": "^0.3.0",
135
136
  "pacote": "^22.0.0",
@@ -156,7 +157,7 @@
156
157
  "rimraf": "^6.1.3",
157
158
  "semver": "^7.8.5",
158
159
  "sharp": "^0.35.3",
159
- "storybook": "^10.5.8",
160
+ "storybook": "^10.5.10",
160
161
  "stream-browserify": "^3.0.0",
161
162
  "stream-http": "^3.2.0",
162
163
  "svgo": "4.0.2",
@@ -164,14 +165,14 @@
164
165
  "ts-node": "^10.9.2",
165
166
  "typescript": "^7.0.2",
166
167
  "util": "^0.12.5",
167
- "vite": "^8.2.1",
168
- "vitest": "^4.1.10",
168
+ "vite": "^8.2.2",
169
+ "vitest": "4.1.11",
169
170
  "vm-browserify": "^1.1.2",
170
171
  "ws": "^8.21.3"
171
172
  },
172
173
  "devDependencies": {
173
174
  "@types/express": "^5.0.6",
174
- "@types/luxon": "^3.7.4",
175
+ "@types/luxon": "^3.7.5",
175
176
  "@types/node": "^26.2.0",
176
177
  "@types/react": "^19.2.18",
177
178
  "@types/semver": "^7.8.0"
@@ -0,0 +1,141 @@
1
+ import {execFileSync} from 'child_process';
2
+ import {transformSync} from 'esbuild';
3
+ import {existsSync, readFileSync, readdirSync, statSync} from 'fs';
4
+ import {dirname, extname, resolve} from 'path';
5
+
6
+ const root = process.cwd();
7
+ const docs = ['README.md', 'CHANGELOG.md', 'MIGRATION_V2.md'];
8
+
9
+ const collectMarkdown = (directory) => {
10
+ for(const entry of readdirSync(directory)) {
11
+ const path = resolve(directory, entry);
12
+ if(statSync(path).isDirectory()) {
13
+ collectMarkdown(path);
14
+ } else if(extname(path) === '.md') {
15
+ docs.push(path.slice(root.length + 1));
16
+ }
17
+ }
18
+ };
19
+
20
+ collectMarkdown(resolve(root, 'examples'));
21
+ collectMarkdown(resolve(root, 'src/commands'));
22
+
23
+ const rootHelp = execFileSync(process.execPath, ['lib/lex.js', '--help'], {encoding: 'utf8'});
24
+ const commands = new Set([...rootHelp.matchAll(/^ ([\w-]+)(?:\s|$)/gm)].map((match) => match[1]));
25
+ const commandOptions = new Map();
26
+ const errors = [];
27
+ const lexExports = await import('../lib/index.js');
28
+ const documentationCommands = {
29
+ ai: 'ai',
30
+ build: 'build',
31
+ clean: 'clean',
32
+ compile: 'compile',
33
+ config: 'config',
34
+ create: 'create',
35
+ dev: 'dev',
36
+ init: 'init',
37
+ link: 'linked',
38
+ lint: 'lint',
39
+ migrate: 'migrate',
40
+ publish: 'publish',
41
+ 'serverless-deploy': 'serverless-deploy',
42
+ 'serverless-dev': 'serverless-dev',
43
+ storybook: 'storybook',
44
+ test: 'test',
45
+ update: 'update',
46
+ upgrade: 'upgrade',
47
+ versions: 'versions'
48
+ };
49
+
50
+ for(const command of commands) {
51
+ if(command === 'help') continue;
52
+ const help = execFileSync(process.execPath, ['lib/lex.js', command, '--help'], {encoding: 'utf8'});
53
+ commandOptions.set(command, new Set([...help.matchAll(/--[A-Za-z][\w-]*/g)].map((match) => match[0])));
54
+ }
55
+
56
+ for(const relativePath of docs) {
57
+ const filePath = resolve(root, relativePath);
58
+ const content = readFileSync(filePath, 'utf8');
59
+
60
+ for(const match of content.matchAll(/!?\[[^\]]*\]\(([^)]+)\)/g)) {
61
+ const target = match[1].split('#')[0].trim();
62
+ if(!target || /^(?:https?:|mailto:)/.test(target)) continue;
63
+ const decodedTarget = decodeURIComponent(target.replace(/^<|>$/g, ''));
64
+ if(!existsSync(resolve(dirname(filePath), decodedTarget))) {
65
+ errors.push(`${relativePath}: broken local link ${match[1]}`);
66
+ }
67
+ }
68
+
69
+ for(const match of content.matchAll(/```json\s*\n([\s\S]*?)```/g)) {
70
+ try {
71
+ JSON.parse(match[1]);
72
+ } catch(error) {
73
+ errors.push(`${relativePath}: invalid JSON example (${error.message})`);
74
+ }
75
+ }
76
+
77
+ for(const match of content.matchAll(/```(javascript|js|typescript|ts)\s*\n([\s\S]*?)```/g)) {
78
+ try {
79
+ transformSync(match[2], {
80
+ format: 'esm',
81
+ loader: match[1].startsWith('t') ? 'ts' : 'js',
82
+ target: 'es2023'
83
+ });
84
+ } catch(error) {
85
+ const message = error.errors?.[0]?.text || error.message;
86
+ errors.push(`${relativePath}: invalid ${match[1]} example (${message})`);
87
+ }
88
+ }
89
+
90
+ for(const match of content.matchAll(/import\s*\{([^}]+)\}\s*from\s*['"]@nlabs\/lex['"]/g)) {
91
+ const names = match[1].split(',').map((name) => name.trim().split(/\s+as\s+/)[0]);
92
+ for(const name of names) {
93
+ if(name && !(name in lexExports)) {
94
+ errors.push(`${relativePath}: @nlabs/lex does not export ${name}`);
95
+ }
96
+ }
97
+ }
98
+
99
+ if(relativePath.endsWith('.test.md')) {
100
+ for(const match of content.matchAll(/`([\w.-]+\.test\.[jt]sx?)`/g)) {
101
+ if(!existsSync(resolve(dirname(filePath), match[1]))) {
102
+ errors.push(`${relativePath}: referenced test file does not exist: ${match[1]}`);
103
+ }
104
+ }
105
+ }
106
+
107
+ const commandDirectory = relativePath.match(/^src\/commands\/([^/]+)\/[^/]+\.docs\.md$/)?.[1];
108
+ const documentedCommand = commandDirectory ? documentationCommands[commandDirectory] : undefined;
109
+ if(documentedCommand) {
110
+ const validOptions = commandOptions.get(documentedCommand) || new Set();
111
+ for(const match of content.matchAll(/(?<![\w])(--[A-Za-z][\w-]*)/g)) {
112
+ if(!validOptions.has(match[1])) {
113
+ errors.push(`${relativePath}: unknown documented option ${match[1]} for lex ${documentedCommand}`);
114
+ }
115
+ }
116
+ }
117
+
118
+ for(const [index, line] of content.split('\n').entries()) {
119
+ for(const commandMatch of line.matchAll(/\blex\s+([\w-]+)([^`|]*)/g)) {
120
+ const [, command, remainder] = commandMatch;
121
+ if(!commands.has(command)) {
122
+ errors.push(`${relativePath}:${index + 1}: unknown command lex ${command}`);
123
+ continue;
124
+ }
125
+
126
+ const validOptions = commandOptions.get(command) || new Set();
127
+ for(const option of remainder.match(/--[A-Za-z][\w-]*/g) || []) {
128
+ if(!validOptions.has(option)) {
129
+ errors.push(`${relativePath}:${index + 1}: unknown option ${option} for lex ${command}`);
130
+ }
131
+ }
132
+ }
133
+ }
134
+ }
135
+
136
+ if(errors.length > 0) {
137
+ console.error([...new Set(errors)].join('\n'));
138
+ process.exitCode = 1;
139
+ } else {
140
+ console.log(`Documentation validation passed for ${docs.length} Markdown files.`);
141
+ }