yllaw 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.
- package/README.md +121 -0
- package/bin/yllaw.js +47 -0
- package/package.json +35 -0
- package/src/installer.js +206 -0
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# yllaw
|
|
2
|
+
|
|
3
|
+
Roblox 包管理器 - 支持 Wally 包和私有 Git 仓库。
|
|
4
|
+
|
|
5
|
+
> yllaw = wally 倒过来 🙃
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g yllaw
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 使用
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# 安装所有包(Wally + Git)
|
|
17
|
+
yllaw install
|
|
18
|
+
|
|
19
|
+
# 只安装 Git 包,跳过 Wally
|
|
20
|
+
yllaw install --skip-wally
|
|
21
|
+
yllaw install -s
|
|
22
|
+
|
|
23
|
+
# 强制更新所有 Git 包
|
|
24
|
+
yllaw update
|
|
25
|
+
|
|
26
|
+
# 指定配置文件
|
|
27
|
+
yllaw install --config my-wally.toml
|
|
28
|
+
|
|
29
|
+
# 指定输出目录
|
|
30
|
+
yllaw install --output Packages
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 配置
|
|
34
|
+
|
|
35
|
+
在项目根目录创建 `wally.toml`:
|
|
36
|
+
|
|
37
|
+
```toml
|
|
38
|
+
[package]
|
|
39
|
+
name = "your-scope/your-project"
|
|
40
|
+
version = "0.1.0"
|
|
41
|
+
realm = "shared"
|
|
42
|
+
|
|
43
|
+
[dependencies]
|
|
44
|
+
# Wally 公共包
|
|
45
|
+
|
|
46
|
+
[server-dependencies]
|
|
47
|
+
# Wally 服务端包
|
|
48
|
+
|
|
49
|
+
[git-dependencies]
|
|
50
|
+
# 私有 Git 包(共享)
|
|
51
|
+
CommonLib = "https://gitlab.example.com/group/common-lib.git@1.0.0"
|
|
52
|
+
|
|
53
|
+
[server-git-dependencies]
|
|
54
|
+
# 私有 Git 包(服务端)
|
|
55
|
+
MMS = "https://gitlab.example.com/group/mms.git@1.1.0"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Git 包格式
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
包名 = "仓库地址.git@版本"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
| 部分 | 说明 | 示例 |
|
|
65
|
+
|------|------|------|
|
|
66
|
+
| 仓库地址 | HTTPS Git URL | `https://gitlab.example.com/group/repo.git` |
|
|
67
|
+
| 版本 | Git tag / 分支 / commit | `1.0.0`, `main`, `abc1234` |
|
|
68
|
+
|
|
69
|
+
## 创建私有包
|
|
70
|
+
|
|
71
|
+
你的 Git 仓库结构:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
your-package/
|
|
75
|
+
├── README.md
|
|
76
|
+
├── wally.toml
|
|
77
|
+
└── YourModule/
|
|
78
|
+
├── init.luau ← 必须有入口文件
|
|
79
|
+
├── Foo.luau
|
|
80
|
+
└── Bar.luau
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
yllaw 会自动查找包含 `init.luau` 或 `init.lua` 的目录。
|
|
84
|
+
|
|
85
|
+
### 发布版本
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
git tag 1.0.0
|
|
89
|
+
git push origin 1.0.0
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 与 Wally 的关系
|
|
93
|
+
|
|
94
|
+
yllaw **补充** Wally,而不是替代:
|
|
95
|
+
|
|
96
|
+
- Wally 负责公共包和私有 Registry
|
|
97
|
+
- yllaw 负责私有 Git 仓库的包
|
|
98
|
+
|
|
99
|
+
运行 `yllaw install` 时:
|
|
100
|
+
1. 先运行 `wally install`
|
|
101
|
+
2. 再安装 `[git-dependencies]` 和 `[server-git-dependencies]`
|
|
102
|
+
|
|
103
|
+
## 命令
|
|
104
|
+
|
|
105
|
+
| 命令 | 说明 |
|
|
106
|
+
|------|------|
|
|
107
|
+
| `yllaw install` | 安装所有包 |
|
|
108
|
+
| `yllaw install -s` | 跳过 Wally,只安装 Git 包 |
|
|
109
|
+
| `yllaw update` | 强制重新安装所有 Git 包 |
|
|
110
|
+
|
|
111
|
+
## 选项
|
|
112
|
+
|
|
113
|
+
| 选项 | 说明 | 默认值 |
|
|
114
|
+
|------|------|--------|
|
|
115
|
+
| `-s, --skip-wally` | 跳过 Wally | false |
|
|
116
|
+
| `-c, --config <path>` | 配置文件路径 | `wally.toml` |
|
|
117
|
+
| `-o, --output <dir>` | 输出目录 | `Packages` |
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
MIT
|
package/bin/yllaw.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { program } = require('commander');
|
|
4
|
+
const { version } = require('../package.json');
|
|
5
|
+
const { install } = require('../src/installer');
|
|
6
|
+
|
|
7
|
+
program
|
|
8
|
+
.name('yllaw')
|
|
9
|
+
.description('Package manager for Roblox - supports Wally and private Git repositories')
|
|
10
|
+
.version(version);
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.command('install')
|
|
14
|
+
.description('Install packages from wally.toml')
|
|
15
|
+
.option('-s, --skip-wally', 'Skip wally install, only install git packages')
|
|
16
|
+
.option('-c, --config <path>', 'Path to wally.toml', 'wally.toml')
|
|
17
|
+
.option('-o, --output <dir>', 'Output directory for packages', 'Packages')
|
|
18
|
+
.action(async (options) => {
|
|
19
|
+
try {
|
|
20
|
+
await install(options);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
console.error('Error:', err.message);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.command('update')
|
|
29
|
+
.description('Update all git packages to latest version (re-clone)')
|
|
30
|
+
.option('-s, --skip-wally', 'Skip wally install')
|
|
31
|
+
.option('-c, --config <path>', 'Path to wally.toml', 'wally.toml')
|
|
32
|
+
.option('-o, --output <dir>', 'Output directory for packages', 'Packages')
|
|
33
|
+
.action(async (options) => {
|
|
34
|
+
try {
|
|
35
|
+
await install({ ...options, force: true });
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.error('Error:', err.message);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Default command: install
|
|
43
|
+
if (process.argv.length === 2) {
|
|
44
|
+
process.argv.push('install');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "yllaw",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Package manager for Roblox - supports Wally packages and private Git repositories",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"yllaw": "./bin/yllaw.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node bin/yllaw.js install"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"roblox",
|
|
14
|
+
"wally",
|
|
15
|
+
"package-manager",
|
|
16
|
+
"luau",
|
|
17
|
+
"lua"
|
|
18
|
+
],
|
|
19
|
+
"author": "Hauru",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"commander": "^11.1.0",
|
|
23
|
+
"simple-git": "^3.22.0",
|
|
24
|
+
"toml": "^3.0.0",
|
|
25
|
+
"chalk": "^4.1.2",
|
|
26
|
+
"fs-extra": "^11.2.0"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=14.0.0"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://gitlab.showhand.win/game_develop/yllaw.git"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/installer.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const toml = require('toml');
|
|
4
|
+
const simpleGit = require('simple-git');
|
|
5
|
+
const { spawn } = require('child_process');
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
|
|
8
|
+
const log = {
|
|
9
|
+
info: (msg) => console.log(chalk.blue('[INFO]'), msg),
|
|
10
|
+
ok: (msg) => console.log(chalk.green('[OK]'), msg),
|
|
11
|
+
warn: (msg) => console.log(chalk.yellow('[WARN]'), msg),
|
|
12
|
+
error: (msg) => console.log(chalk.red('[ERROR]'), msg),
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Run wally install
|
|
17
|
+
*/
|
|
18
|
+
async function runWally() {
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
const wally = spawn('wally', ['install'], {
|
|
21
|
+
stdio: 'inherit',
|
|
22
|
+
shell: true,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
wally.on('close', (code) => {
|
|
26
|
+
resolve(code === 0);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
wally.on('error', () => {
|
|
30
|
+
resolve(false);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Parse git spec: "https://example.com/repo.git@version"
|
|
37
|
+
* @returns {{ url: string, ref: string }}
|
|
38
|
+
*/
|
|
39
|
+
function parseGitSpec(spec) {
|
|
40
|
+
const gitAtIndex = spec.lastIndexOf('.git@');
|
|
41
|
+
if (gitAtIndex > 0) {
|
|
42
|
+
return {
|
|
43
|
+
url: spec.substring(0, gitAtIndex + 4),
|
|
44
|
+
ref: spec.substring(gitAtIndex + 5),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
url: spec,
|
|
49
|
+
ref: 'main',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Find module directory (containing init.luau or init.lua)
|
|
55
|
+
*/
|
|
56
|
+
async function findModuleDir(searchDir) {
|
|
57
|
+
// Check root
|
|
58
|
+
if (await fs.pathExists(path.join(searchDir, 'init.luau')) ||
|
|
59
|
+
await fs.pathExists(path.join(searchDir, 'init.lua'))) {
|
|
60
|
+
return searchDir;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Check immediate subdirectories
|
|
64
|
+
const entries = await fs.readdir(searchDir, { withFileTypes: true });
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
if (entry.isDirectory()) {
|
|
67
|
+
const subDir = path.join(searchDir, entry.name);
|
|
68
|
+
if (await fs.pathExists(path.join(subDir, 'init.luau')) ||
|
|
69
|
+
await fs.pathExists(path.join(subDir, 'init.lua'))) {
|
|
70
|
+
return subDir;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Deep search (fallback)
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
78
|
+
const found = await findModuleDir(path.join(searchDir, entry.name));
|
|
79
|
+
if (found) return found;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Install a single git package
|
|
88
|
+
*/
|
|
89
|
+
async function installGitPackage(name, spec, outputDir, tempDir) {
|
|
90
|
+
const { url, ref } = parseGitSpec(spec);
|
|
91
|
+
const target = path.join(outputDir, name);
|
|
92
|
+
const repoDir = path.join(tempDir, 'repo');
|
|
93
|
+
|
|
94
|
+
log.info(`Installing ${name} @ ${ref}`);
|
|
95
|
+
|
|
96
|
+
// Clean temp dir
|
|
97
|
+
await fs.remove(tempDir);
|
|
98
|
+
await fs.ensureDir(tempDir);
|
|
99
|
+
|
|
100
|
+
// Clone
|
|
101
|
+
const git = simpleGit();
|
|
102
|
+
try {
|
|
103
|
+
await git.clone(url, repoDir, ['--depth', '1', '--branch', ref]);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
// Fallback: clone default branch and checkout
|
|
106
|
+
try {
|
|
107
|
+
await git.clone(url, repoDir, ['--depth', '1']);
|
|
108
|
+
const repoGit = simpleGit(repoDir);
|
|
109
|
+
await repoGit.fetch(['origin', ref, '--depth', '1']);
|
|
110
|
+
await repoGit.checkout('FETCH_HEAD');
|
|
111
|
+
} catch (err2) {
|
|
112
|
+
log.error(`Clone failed: ${name} - ${err2.message}`);
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Find module directory
|
|
118
|
+
const moduleDir = await findModuleDir(repoDir);
|
|
119
|
+
if (!moduleDir) {
|
|
120
|
+
log.error(`No init.luau found in ${name}`);
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Copy to output
|
|
125
|
+
await fs.remove(target);
|
|
126
|
+
await fs.copy(moduleDir, target);
|
|
127
|
+
|
|
128
|
+
// Clean up .git if copied
|
|
129
|
+
await fs.remove(path.join(target, '.git'));
|
|
130
|
+
|
|
131
|
+
log.ok(`${name} -> ${target}`);
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Main install function
|
|
137
|
+
*/
|
|
138
|
+
async function install(options) {
|
|
139
|
+
const { config, output, skipWally, force } = options;
|
|
140
|
+
const configPath = path.resolve(config);
|
|
141
|
+
const outputDir = path.resolve(output);
|
|
142
|
+
const tempDir = path.join(process.cwd(), '.yllaw_temp');
|
|
143
|
+
|
|
144
|
+
// Check config exists
|
|
145
|
+
if (!await fs.pathExists(configPath)) {
|
|
146
|
+
throw new Error(`Config not found: ${configPath}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
log.info(`Config: ${configPath}`);
|
|
150
|
+
await fs.ensureDir(outputDir);
|
|
151
|
+
|
|
152
|
+
// Step 1: Wally install
|
|
153
|
+
if (skipWally) {
|
|
154
|
+
log.info('Skipping wally (--skip-wally)');
|
|
155
|
+
} else {
|
|
156
|
+
log.info('Running wally install...');
|
|
157
|
+
const wallyOk = await runWally();
|
|
158
|
+
if (wallyOk) {
|
|
159
|
+
log.ok('Wally done');
|
|
160
|
+
} else {
|
|
161
|
+
log.warn('Wally skipped or not found');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Step 2: Parse config
|
|
166
|
+
const configContent = await fs.readFile(configPath, 'utf-8');
|
|
167
|
+
let parsed;
|
|
168
|
+
try {
|
|
169
|
+
parsed = toml.parse(configContent);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
throw new Error(`Failed to parse ${config}: ${err.message}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Step 3: Install git dependencies
|
|
175
|
+
const gitDeps = {
|
|
176
|
+
...parsed['git-dependencies'],
|
|
177
|
+
...parsed['server-git-dependencies'],
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const packages = Object.entries(gitDeps);
|
|
181
|
+
if (packages.length === 0) {
|
|
182
|
+
log.info('No git dependencies found');
|
|
183
|
+
} else {
|
|
184
|
+
log.info(`Found ${packages.length} git package(s)`);
|
|
185
|
+
|
|
186
|
+
for (const [name, spec] of packages) {
|
|
187
|
+
const target = path.join(outputDir, name);
|
|
188
|
+
|
|
189
|
+
// Skip if exists and not forcing update
|
|
190
|
+
if (!force && await fs.pathExists(target)) {
|
|
191
|
+
log.info(`${name} already exists, skipping (use 'yllaw update' to force)`);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
await installGitPackage(name, spec, outputDir, tempDir);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Cleanup
|
|
200
|
+
await fs.remove(tempDir);
|
|
201
|
+
|
|
202
|
+
console.log('');
|
|
203
|
+
log.ok(`Done! Packages installed to ${outputDir}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
module.exports = { install };
|