@uxf/velo 0.1.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 +80 -0
- package/cli.js +142 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# @uxf/velo
|
|
2
|
+
|
|
3
|
+
A TypeScript dependency tree generator using Rust and WebAssembly.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- Fast TypeScript/TSX parsing using `swc`.
|
|
7
|
+
- Aggregates i18n namespaces from components and their dependencies.
|
|
8
|
+
- Generates an optimized route-to-namespace map.
|
|
9
|
+
- Can be published to npm and used as a CLI.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g @uxf/velo
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
```bash
|
|
18
|
+
uxf-velo <project-dir>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Starting from the `src/pages/` directory (standard for Next.js), it builds a dependency graph and extracts i18n namespaces (e.g., from `t("ns:key")` or `<Trans i18nKey="ns:key" />`).
|
|
22
|
+
|
|
23
|
+
## Build from Source
|
|
24
|
+
Requirements:
|
|
25
|
+
- Rust (latest)
|
|
26
|
+
- wasm-pack
|
|
27
|
+
|
|
28
|
+
### Initialization
|
|
29
|
+
Before the first build, run:
|
|
30
|
+
```bash
|
|
31
|
+
npm run init
|
|
32
|
+
```
|
|
33
|
+
This ensures the Rust target for WebAssembly is installed and all dependencies are present.
|
|
34
|
+
|
|
35
|
+
### Building
|
|
36
|
+
To build the package:
|
|
37
|
+
```bash
|
|
38
|
+
npm run build
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Production Release and Publishing
|
|
42
|
+
This package includes a full publish pipeline:
|
|
43
|
+
|
|
44
|
+
1. **Test the package**:
|
|
45
|
+
```bash
|
|
46
|
+
npm run test
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
2. **Full Release (Automated)**:
|
|
50
|
+
```bash
|
|
51
|
+
npm run release
|
|
52
|
+
```
|
|
53
|
+
This script performs the following steps:
|
|
54
|
+
- Sets the Rust target for WebAssembly.
|
|
55
|
+
- Builds the WebAssembly package.
|
|
56
|
+
- Runs all tests.
|
|
57
|
+
- Increments the version (patch).
|
|
58
|
+
- Publishes to npm with public access.
|
|
59
|
+
|
|
60
|
+
Alternatively, you can manually run `npm publish`. The `prepublishOnly` script will automatically ensure the package is built and tested before being sent to the npm registry.
|
|
61
|
+
|
|
62
|
+
## Output Format
|
|
63
|
+
The tool outputs a sorted JSON object mapping route keys to an array of unique, aggregated namespaces:
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"*": [
|
|
67
|
+
"common",
|
|
68
|
+
"auth"
|
|
69
|
+
],
|
|
70
|
+
"/": [
|
|
71
|
+
"auth",
|
|
72
|
+
"common",
|
|
73
|
+
"uxf-form-text-input"
|
|
74
|
+
],
|
|
75
|
+
"/about": [
|
|
76
|
+
"about",
|
|
77
|
+
"common"
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
```
|
package/cli.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { globSync } = require('glob');
|
|
6
|
+
const wasm = require('./pkg/velo.js');
|
|
7
|
+
require.extensions['.ts'] = () => {};
|
|
8
|
+
require.extensions['.tsx'] = () => {};
|
|
9
|
+
require.extensions['.jsx'] = () => {};
|
|
10
|
+
|
|
11
|
+
const projectDir = process.argv[2] || '.';
|
|
12
|
+
const absoluteProjectDir = path.resolve(projectDir);
|
|
13
|
+
|
|
14
|
+
if (!fs.existsSync(absoluteProjectDir)) {
|
|
15
|
+
console.error(`Error: Project directory "${projectDir}" does not exist.`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Load tsconfig.json if it exists
|
|
20
|
+
let tsConfig = null;
|
|
21
|
+
const tsConfigPath = path.join(absoluteProjectDir, 'tsconfig.json');
|
|
22
|
+
if (fs.existsSync(tsConfigPath)) {
|
|
23
|
+
try {
|
|
24
|
+
const rawContent = fs.readFileSync(tsConfigPath, 'utf8');
|
|
25
|
+
// Simple comment removal (doesn't handle all cases, but should be enough for basic ones)
|
|
26
|
+
const cleanContent = rawContent.replace(/\/\/.*/g, '');
|
|
27
|
+
tsConfig = JSON.parse(cleanContent);
|
|
28
|
+
} catch (e) {
|
|
29
|
+
console.error(`Error: Failed to parse tsconfig.json (${tsConfigPath}):`, e);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
console.error(`Error: tsconfig.json (${tsConfigPath}) not found in the project directory "${projectDir}".`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 1. Find entrypoints in 'src/pages/' directory
|
|
38
|
+
const entrypoints = globSync('src/pages/**/*.{ts,tsx}', {
|
|
39
|
+
cwd: absoluteProjectDir,
|
|
40
|
+
ignore: ['node_modules/**'],
|
|
41
|
+
nodir: true,
|
|
42
|
+
posix: true
|
|
43
|
+
}).map(f => path.resolve(absoluteProjectDir, f));
|
|
44
|
+
|
|
45
|
+
if (entrypoints.length === 0) {
|
|
46
|
+
console.warn('Warning: No .ts or .tsx files found in the "src/pages" directory.');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function readFile(filepath) {
|
|
50
|
+
try {
|
|
51
|
+
const ext = path.extname(filepath);
|
|
52
|
+
if (['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(ext) || ext === '') {
|
|
53
|
+
return fs.readFileSync(filepath, 'utf8');
|
|
54
|
+
}
|
|
55
|
+
} catch (e) {
|
|
56
|
+
// ignore
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function resolveAlias(specifier) {
|
|
62
|
+
if (!tsConfig || !tsConfig.compilerOptions || !tsConfig.compilerOptions.paths) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const { paths, baseUrl = '.' } = tsConfig.compilerOptions;
|
|
66
|
+
const absoluteBaseUrl = path.resolve(absoluteProjectDir, baseUrl);
|
|
67
|
+
|
|
68
|
+
for (const [alias, aliasPaths] of Object.entries(paths)) {
|
|
69
|
+
if (alias.endsWith('/*')) {
|
|
70
|
+
const prefix = alias.slice(0, -2);
|
|
71
|
+
if (specifier === prefix || specifier.startsWith(prefix + '/')) {
|
|
72
|
+
const subPath = specifier === prefix ? '' : specifier.slice(prefix.length + 1);
|
|
73
|
+
for (const aliasPath of aliasPaths) {
|
|
74
|
+
const resolvedAliasPath = aliasPath.replace('*', subPath);
|
|
75
|
+
const absolutePath = path.resolve(absoluteBaseUrl, resolvedAliasPath);
|
|
76
|
+
try {
|
|
77
|
+
return require.resolve(absolutePath);
|
|
78
|
+
} catch (e) {}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} else if (alias === specifier || specifier.startsWith(alias + '/')) {
|
|
82
|
+
const subPath = specifier === alias ? '' : specifier.slice(alias.length + 1);
|
|
83
|
+
for (const aliasPath of aliasPaths) {
|
|
84
|
+
const absolutePath = subPath ? path.resolve(absoluteBaseUrl, aliasPath, subPath) : path.resolve(absoluteBaseUrl, aliasPath);
|
|
85
|
+
try {
|
|
86
|
+
return require.resolve(absolutePath);
|
|
87
|
+
} catch (e) {}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function filterResolved(resolved) {
|
|
96
|
+
const nm = 'node_modules' + path.sep;
|
|
97
|
+
const uxf = nm + '@uxf' + path.sep;
|
|
98
|
+
|
|
99
|
+
const resolvedNmIndex = resolved.lastIndexOf(nm);
|
|
100
|
+
if (resolvedNmIndex !== -1 && !resolved.startsWith(uxf, resolvedNmIndex)) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return resolved;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function resolveImport(specifier, parentPath) {
|
|
108
|
+
try {
|
|
109
|
+
// 1. Try alias resolution first
|
|
110
|
+
const aliasResolved = resolveAlias(specifier);
|
|
111
|
+
if (aliasResolved) {
|
|
112
|
+
return filterResolved(aliasResolved);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 2. Try baseUrl resolution if not relative and baseUrl is set
|
|
116
|
+
if (!specifier.startsWith('.') && !path.isAbsolute(specifier) && tsConfig && tsConfig.compilerOptions && tsConfig.compilerOptions.baseUrl) {
|
|
117
|
+
const absoluteBaseUrl = path.resolve(absoluteProjectDir, tsConfig.compilerOptions.baseUrl);
|
|
118
|
+
try {
|
|
119
|
+
const baseUrlResolved = require.resolve(path.resolve(absoluteBaseUrl, specifier));
|
|
120
|
+
if (baseUrlResolved) {
|
|
121
|
+
return filterResolved(baseUrlResolved);
|
|
122
|
+
}
|
|
123
|
+
} catch (e) {}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const resolved = require.resolve(specifier, {
|
|
127
|
+
paths: [path.dirname(parentPath)]
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return filterResolved(resolved);
|
|
131
|
+
} catch (e) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const tree = wasm.build_tree_map_js(entrypoints, absoluteProjectDir, readFile, resolveImport);
|
|
138
|
+
process.stdout.write(JSON.stringify(tree, null, 4) + '\n');
|
|
139
|
+
} catch (e) {
|
|
140
|
+
console.error('Error building dependency tree:', e);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@uxf/velo",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript dependency tree generator using Rust WebAssembly",
|
|
5
|
+
"main": "./pkg/velo.js",
|
|
6
|
+
"types": "./pkg/velo.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"uxf-velo": "cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"init": "rustup target add wasm32-unknown-unknown && npm install",
|
|
12
|
+
"build": "rustup target add wasm32-unknown-unknown && wasm-pack build --target nodejs",
|
|
13
|
+
"test": "node test_structure.js && node test_cli_format.js && node test_sort.js",
|
|
14
|
+
"lint": "cargo clippy -- -D warnings",
|
|
15
|
+
"fmt": "cargo fmt -- --check",
|
|
16
|
+
"prepublishOnly": "npm run build && npm run test",
|
|
17
|
+
"release": "npm run build && npm run test && npm version patch && npm publish --access public",
|
|
18
|
+
"dev": "node cli.js ./example"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"glob": "^11.1.0"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"pkg/",
|
|
25
|
+
"cli.js"
|
|
26
|
+
],
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@headlessui/react": "^1.7.19",
|
|
29
|
+
"@uxf/form": "^11.110.0",
|
|
30
|
+
"@uxf/ui": "^11.110.0",
|
|
31
|
+
"react": "^18.3.1",
|
|
32
|
+
"typescript": "^6.0.2",
|
|
33
|
+
"wasm-pack": "^0.14.0"
|
|
34
|
+
}
|
|
35
|
+
}
|