forgemap 0.1.0-dev-main.7-64cc97e
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/LICENSE +21 -0
- package/README.md +152 -0
- package/dist/bin/forgemap.mjs +385 -0
- package/dist/bin/forgemap.mjs.map +1 -0
- package/dist/config/define.mjs +7 -0
- package/dist/config/define.mjs.map +1 -0
- package/dist/index.mjs +5 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +85 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Titus Kirch
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# 🧰 forgemap
|
|
4
|
+
|
|
5
|
+
**One consistent local layout for every repo you clone — across every forge**
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/forgemap)
|
|
8
|
+
[](https://www.npmjs.com/package/forgemap)
|
|
9
|
+
[](https://github.com/TitusKirch/forgemap/actions/workflows/ci.yml)
|
|
10
|
+
[](https://www.npmjs.com/package/forgemap)
|
|
11
|
+
[](LICENSE)
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
$ forgemap clone kirchDev/laravel-pbac
|
|
19
|
+
✔ Cloned kirchDev/laravel-pbac → ~/projects/comGithub/kirchDev/laravel-pbac
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
That's it. Every repo lands at a predictable `<root>/<forge.dir>/<owner>/<repo>` path, and `forgemap path <slug>` gives you that path back for `cd "$(forgemap path …)"` from anywhere.
|
|
23
|
+
|
|
24
|
+
## ✨ Features
|
|
25
|
+
|
|
26
|
+
- **🗂️ Predictable layout** — every clone goes to `<root>/<forge.dir>/<owner>/<repo>`, configured once.
|
|
27
|
+
- **🚪 Flexible slug syntax** — `owner/repo`, `forge:owner/repo`, full HTTPS URLs, or SSH (`git@…:…`).
|
|
28
|
+
- **🤖 Forge-aware** — uses `gh` for GitHub today; GitLab / Gitea / Codeberg adapters planned.
|
|
29
|
+
- **🧰 Typed config** — `forgemap.config.ts` with `defineForgeMapConfig()` and walk-up discovery.
|
|
30
|
+
- **🚀 Shell-friendly** — `forgemap path <slug>` is a pure resolver, perfect for `cd "$(…)"` aliases.
|
|
31
|
+
|
|
32
|
+
## 📦 Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install -g forgemap
|
|
36
|
+
# or
|
|
37
|
+
pnpm add -g forgemap
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
> [!IMPORTANT]
|
|
41
|
+
> `forgemap clone` shells out to the [GitHub CLI](https://cli.github.com/) (`gh`). Install it once and run `gh auth login` so cloning works against private repos.
|
|
42
|
+
|
|
43
|
+
## 🚀 Quick start
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# 1. Pick a directory that should hold all your repos and drop a config there.
|
|
47
|
+
cd ~/projects
|
|
48
|
+
forgemap config init
|
|
49
|
+
|
|
50
|
+
# 2. Clone — any slug form works.
|
|
51
|
+
forgemap clone kirchDev/laravel-pbac
|
|
52
|
+
forgemap clone github:TitusKirch/forgemap
|
|
53
|
+
forgemap clone https://github.com/foo/bar
|
|
54
|
+
|
|
55
|
+
# 3. Jump into a repo from anywhere.
|
|
56
|
+
cd "$(forgemap path kirchDev/laravel-pbac)"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Add a shell alias to make the jump even shorter:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
fcd() { cd "$(forgemap path "$1")"; }
|
|
63
|
+
fcd kirchDev/laravel-pbac
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## ⚙️ Configuration
|
|
67
|
+
|
|
68
|
+
`forgemap config init` writes a `forgemap.config.ts` like this:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { defineForgeMapConfig } from 'forgemap/config';
|
|
72
|
+
|
|
73
|
+
export default defineForgeMapConfig({
|
|
74
|
+
root: '.',
|
|
75
|
+
defaultForge: 'github',
|
|
76
|
+
forges: {
|
|
77
|
+
github: {
|
|
78
|
+
type: 'github',
|
|
79
|
+
host: 'github.com',
|
|
80
|
+
dir: 'comGithub'
|
|
81
|
+
},
|
|
82
|
+
work: {
|
|
83
|
+
type: 'gitlab',
|
|
84
|
+
host: 'gitlab.acme.com',
|
|
85
|
+
dir: 'comGitlabAcme'
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
| Key | What it controls |
|
|
92
|
+
| :------------- | :---------------------------------------------------------------------------------------------- |
|
|
93
|
+
| `root` | Base directory for all clones. Relative paths resolve against the config file's directory. |
|
|
94
|
+
| `defaultForge` | Forge alias used when a slug is just `owner/repo` (no host or forge prefix). |
|
|
95
|
+
| `forges.<name>` | Map of forge aliases. Each entry has `type`, `host`, and `dir` (subdirectory under `root`). |
|
|
96
|
+
|
|
97
|
+
The config file is discovered by walking up from your current directory. Override with `--config <path>` or the `FORGEMAP_CONFIG` env var.
|
|
98
|
+
|
|
99
|
+
## 🗂️ Layout
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
<root>/
|
|
103
|
+
└── <forge.dir>/
|
|
104
|
+
└── <owner>/
|
|
105
|
+
└── <repo>/
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Example with the default config rooted at `~/projects`:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
~/projects/
|
|
112
|
+
├── forgemap.config.ts
|
|
113
|
+
└── comGithub/
|
|
114
|
+
├── kirchDev/
|
|
115
|
+
│ └── laravel-pbac/
|
|
116
|
+
└── TitusKirch/
|
|
117
|
+
└── forgemap/
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## 🚪 Slug syntax
|
|
121
|
+
|
|
122
|
+
| Form | Resolves to |
|
|
123
|
+
| :---------------------------------- | :----------------------------------------------------- |
|
|
124
|
+
| `kirchDev/laravel-pbac` | Default forge, `kirchDev/laravel-pbac`. |
|
|
125
|
+
| `work:team/api` | Named forge `work`, `team/api`. |
|
|
126
|
+
| `https://github.com/foo/bar` | Host matched against `forges[].host`. |
|
|
127
|
+
| `https://github.com/foo/bar.git` | Same, `.git` suffix stripped. |
|
|
128
|
+
| `git@github.com:foo/bar.git` | SSH form, host matched against `forges[].host`. |
|
|
129
|
+
|
|
130
|
+
## 🧪 Testing
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
pnpm install
|
|
134
|
+
pnpm test # vitest
|
|
135
|
+
pnpm typecheck # tsc --noEmit
|
|
136
|
+
pnpm check # lint + format
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## 🤝 Contributing
|
|
140
|
+
|
|
141
|
+
PRs welcome. Conventional Commits required (enforced via commitlint). Husky runs lint-staged on every commit.
|
|
142
|
+
|
|
143
|
+
> [!TIP]
|
|
144
|
+
> Run `pnpm check:fix` before pushing — CI will catch what husky missed.
|
|
145
|
+
|
|
146
|
+
## 🛣️ Versioning
|
|
147
|
+
|
|
148
|
+
[Semantic Versioning](https://semver.org/) via [release-please](https://github.com/googleapis/release-please) — see [CHANGELOG.md](CHANGELOG.md).
|
|
149
|
+
|
|
150
|
+
## 📄 License
|
|
151
|
+
|
|
152
|
+
[MIT](LICENSE) © [Titus Kirch](https://github.com/TitusKirch/) / [IT-Dienstleistungen Titus Kirch](https://kirch.dev)
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { defineCommand, runMain } from "citty";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
5
|
+
import consola from "consola";
|
|
6
|
+
import { dirname, isAbsolute, resolve, join } from "pathe";
|
|
7
|
+
import { loadConfig } from "c12";
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
const DEFAULT_CONFIG = {
|
|
11
|
+
root: ".",
|
|
12
|
+
defaultForge: "github",
|
|
13
|
+
forges: {
|
|
14
|
+
github: {
|
|
15
|
+
type: "github",
|
|
16
|
+
host: "github.com",
|
|
17
|
+
dir: "comGithub"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
async function loadForgeMapConfig(options = {}) {
|
|
22
|
+
const envConfig = process.env.FORGEMAP_CONFIG;
|
|
23
|
+
const explicit = options.configFile ?? envConfig;
|
|
24
|
+
const cwd = explicit ? dirname(explicit) : options.cwd ?? process.cwd();
|
|
25
|
+
const { config, configFile } = await loadConfig({
|
|
26
|
+
name: "forgemap",
|
|
27
|
+
cwd,
|
|
28
|
+
configFile: explicit ? explicit : "forgemap.config",
|
|
29
|
+
rcFile: false,
|
|
30
|
+
globalRc: false,
|
|
31
|
+
dotenv: false,
|
|
32
|
+
defaults: DEFAULT_CONFIG
|
|
33
|
+
});
|
|
34
|
+
const merged = {
|
|
35
|
+
root: config.root ?? DEFAULT_CONFIG.root,
|
|
36
|
+
defaultForge: config.defaultForge ?? DEFAULT_CONFIG.defaultForge,
|
|
37
|
+
forges: {
|
|
38
|
+
...DEFAULT_CONFIG.forges,
|
|
39
|
+
...config.forges
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
return {
|
|
43
|
+
config: merged,
|
|
44
|
+
configFile: configFile || void 0,
|
|
45
|
+
cwd
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function execInherit(command, args) {
|
|
49
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
50
|
+
const child = spawn(command, args, { stdio: "inherit" });
|
|
51
|
+
child.on("error", rejectPromise);
|
|
52
|
+
child.on("close", (code) => {
|
|
53
|
+
resolvePromise({ code: code ?? 0 });
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function hasCommand(command) {
|
|
58
|
+
return new Promise((resolvePromise) => {
|
|
59
|
+
const child = spawn(
|
|
60
|
+
process.platform === "win32" ? "where" : "which",
|
|
61
|
+
[command],
|
|
62
|
+
{
|
|
63
|
+
stdio: "ignore"
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
child.on("error", () => resolvePromise(false));
|
|
67
|
+
child.on("close", (code) => resolvePromise(code === 0));
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const githubAdapter = {
|
|
71
|
+
async clone({ owner, repo, dest }) {
|
|
72
|
+
if (!await hasCommand("gh")) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
"GitHub CLI (`gh`) is not installed. Install it from https://cli.github.com/ and run `gh auth login`."
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
const { code } = await execInherit("gh", [
|
|
78
|
+
"repo",
|
|
79
|
+
"clone",
|
|
80
|
+
`${owner}/${repo}`,
|
|
81
|
+
dest
|
|
82
|
+
]);
|
|
83
|
+
if (code !== 0) {
|
|
84
|
+
throw new Error(`gh repo clone exited with code ${code}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
function getForgeAdapter(type) {
|
|
89
|
+
switch (type) {
|
|
90
|
+
case "github":
|
|
91
|
+
return githubAdapter;
|
|
92
|
+
case "gitlab":
|
|
93
|
+
case "gitea":
|
|
94
|
+
case "codeberg":
|
|
95
|
+
throw new Error(
|
|
96
|
+
`Forge type "${type}" is not implemented yet. Only "github" is supported in this release.`
|
|
97
|
+
);
|
|
98
|
+
default: {
|
|
99
|
+
const exhaustive = type;
|
|
100
|
+
throw new Error(`Unknown forge type: ${String(exhaustive)}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const SHORT_RE = /^([\w.-]+)\/([\w.-]+)$/;
|
|
105
|
+
const NAMED_RE = /^([\w.-]+):([\w.-]+)\/([\w.-]+)$/;
|
|
106
|
+
const SSH_RE = /^git@([\w.-]+):([\w.-]+)\/([\w.-]+?)(?:\.git)?$/;
|
|
107
|
+
function stripGitSuffix(repo) {
|
|
108
|
+
return repo.endsWith(".git") ? repo.slice(0, -4) : repo;
|
|
109
|
+
}
|
|
110
|
+
function parseSlug(input) {
|
|
111
|
+
const trimmed = input.trim();
|
|
112
|
+
if (!trimmed) {
|
|
113
|
+
throw new Error("Slug is empty");
|
|
114
|
+
}
|
|
115
|
+
const ssh = SSH_RE.exec(trimmed);
|
|
116
|
+
if (ssh) {
|
|
117
|
+
return {
|
|
118
|
+
host: ssh[1],
|
|
119
|
+
owner: ssh[2],
|
|
120
|
+
repo: stripGitSuffix(ssh[3])
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (/^https?:\/\//.test(trimmed)) {
|
|
124
|
+
let url;
|
|
125
|
+
try {
|
|
126
|
+
url = new URL(trimmed);
|
|
127
|
+
} catch {
|
|
128
|
+
throw new Error(`Invalid URL: ${trimmed}`);
|
|
129
|
+
}
|
|
130
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
131
|
+
if (segments.length < 2) {
|
|
132
|
+
throw new Error(`URL must contain owner and repo: ${trimmed}`);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
host: url.host,
|
|
136
|
+
owner: segments[0],
|
|
137
|
+
repo: stripGitSuffix(segments[1])
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const named = NAMED_RE.exec(trimmed);
|
|
141
|
+
if (named) {
|
|
142
|
+
return {
|
|
143
|
+
forgeName: named[1],
|
|
144
|
+
owner: named[2],
|
|
145
|
+
repo: stripGitSuffix(named[3])
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
const short = SHORT_RE.exec(trimmed);
|
|
149
|
+
if (short) {
|
|
150
|
+
return {
|
|
151
|
+
owner: short[1],
|
|
152
|
+
repo: stripGitSuffix(short[2])
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
throw new Error(`Unrecognized slug format: ${input}`);
|
|
156
|
+
}
|
|
157
|
+
function expandTilde(p) {
|
|
158
|
+
if (p === "~") return homedir();
|
|
159
|
+
if (p.startsWith("~/")) return resolve(homedir(), p.slice(2));
|
|
160
|
+
return p;
|
|
161
|
+
}
|
|
162
|
+
function resolveRoot(root, configDir) {
|
|
163
|
+
const expanded = expandTilde(root);
|
|
164
|
+
if (isAbsolute(expanded)) return expanded;
|
|
165
|
+
return resolve(configDir, expanded);
|
|
166
|
+
}
|
|
167
|
+
function findForgeByHost(forges, host) {
|
|
168
|
+
for (const [name, forge] of Object.entries(forges)) {
|
|
169
|
+
if (forge.host.toLowerCase() === host.toLowerCase()) {
|
|
170
|
+
return { name, forge };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return void 0;
|
|
174
|
+
}
|
|
175
|
+
function resolveSlug(parsed, options) {
|
|
176
|
+
const { config, configDir } = options;
|
|
177
|
+
let forgeName;
|
|
178
|
+
let forge;
|
|
179
|
+
if (parsed.forgeName) {
|
|
180
|
+
const candidate = config.forges[parsed.forgeName];
|
|
181
|
+
if (!candidate) {
|
|
182
|
+
throw new Error(
|
|
183
|
+
`Forge "${parsed.forgeName}" is not defined in forgemap.config`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
forgeName = parsed.forgeName;
|
|
187
|
+
forge = candidate;
|
|
188
|
+
} else if (parsed.host) {
|
|
189
|
+
const match = findForgeByHost(config.forges, parsed.host);
|
|
190
|
+
if (!match) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`No forge configured for host "${parsed.host}". Add it to forgemap.config.ts.`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
forgeName = match.name;
|
|
196
|
+
forge = match.forge;
|
|
197
|
+
} else {
|
|
198
|
+
const candidate = config.forges[config.defaultForge];
|
|
199
|
+
if (!candidate) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Default forge "${config.defaultForge}" is not defined in forgemap.config`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
forgeName = config.defaultForge;
|
|
205
|
+
forge = candidate;
|
|
206
|
+
}
|
|
207
|
+
const root = resolveRoot(config.root, configDir);
|
|
208
|
+
const localPath = join(root, forge.dir, parsed.owner, parsed.repo);
|
|
209
|
+
return {
|
|
210
|
+
forgeName,
|
|
211
|
+
forge,
|
|
212
|
+
owner: parsed.owner,
|
|
213
|
+
repo: parsed.repo,
|
|
214
|
+
localPath
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const cloneCommand = defineCommand({
|
|
218
|
+
meta: {
|
|
219
|
+
name: "clone",
|
|
220
|
+
description: "Clone a repo into the configured local layout"
|
|
221
|
+
},
|
|
222
|
+
args: {
|
|
223
|
+
slug: {
|
|
224
|
+
type: "positional",
|
|
225
|
+
description: "owner/repo, forge:owner/repo, or full URL",
|
|
226
|
+
required: true
|
|
227
|
+
},
|
|
228
|
+
config: {
|
|
229
|
+
type: "string",
|
|
230
|
+
description: "Path to forgemap.config.ts (overrides walk-up discovery)"
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
async run({ args }) {
|
|
234
|
+
const loaded = await loadForgeMapConfig({ configFile: args.config });
|
|
235
|
+
const parsed = parseSlug(args.slug);
|
|
236
|
+
const configDir = loaded.configFile ? dirname(loaded.configFile) : loaded.cwd;
|
|
237
|
+
const resolved = resolveSlug(parsed, {
|
|
238
|
+
config: loaded.config,
|
|
239
|
+
configDir
|
|
240
|
+
});
|
|
241
|
+
if (existsSync(resolved.localPath)) {
|
|
242
|
+
consola.info(`Already cloned at ${resolved.localPath}`);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
await mkdir(dirname(resolved.localPath), { recursive: true });
|
|
246
|
+
const adapter = getForgeAdapter(resolved.forge.type);
|
|
247
|
+
await adapter.clone({
|
|
248
|
+
forge: resolved.forge,
|
|
249
|
+
owner: resolved.owner,
|
|
250
|
+
repo: resolved.repo,
|
|
251
|
+
dest: resolved.localPath
|
|
252
|
+
});
|
|
253
|
+
consola.success(
|
|
254
|
+
`Cloned ${resolved.owner}/${resolved.repo} → ${resolved.localPath}`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
const TEMPLATE = `/**
|
|
259
|
+
* forgemap configuration.
|
|
260
|
+
*
|
|
261
|
+
* For type-safe authoring, install forgemap and switch to:
|
|
262
|
+
* import { defineForgeMapConfig } from 'forgemap/config';
|
|
263
|
+
* export default defineForgeMapConfig({ ... });
|
|
264
|
+
*
|
|
265
|
+
* @type {import('forgemap').ForgeMapUserConfig}
|
|
266
|
+
*/
|
|
267
|
+
export default {
|
|
268
|
+
root: '.',
|
|
269
|
+
defaultForge: 'github',
|
|
270
|
+
forges: {
|
|
271
|
+
github: {
|
|
272
|
+
type: 'github',
|
|
273
|
+
host: 'github.com',
|
|
274
|
+
dir: 'comGithub'
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
`;
|
|
279
|
+
const configInitCommand = defineCommand({
|
|
280
|
+
meta: {
|
|
281
|
+
name: "init",
|
|
282
|
+
description: "Create a forgemap.config.ts in the current (or given) directory"
|
|
283
|
+
},
|
|
284
|
+
args: {
|
|
285
|
+
out: {
|
|
286
|
+
type: "string",
|
|
287
|
+
description: "Directory to write forgemap.config.ts into",
|
|
288
|
+
default: "."
|
|
289
|
+
},
|
|
290
|
+
force: {
|
|
291
|
+
type: "boolean",
|
|
292
|
+
description: "Overwrite if forgemap.config.ts already exists",
|
|
293
|
+
default: false
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
async run({ args }) {
|
|
297
|
+
const outDir = resolve(process.cwd(), args.out);
|
|
298
|
+
const target = join(outDir, "forgemap.config.ts");
|
|
299
|
+
if (existsSync(target) && !args.force) {
|
|
300
|
+
consola.error(`${target} already exists. Use --force to overwrite.`);
|
|
301
|
+
process.exitCode = 1;
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
await mkdir(dirname(target), { recursive: true });
|
|
305
|
+
await writeFile(target, TEMPLATE, "utf8");
|
|
306
|
+
consola.success(`Wrote ${target}`);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
const configShowCommand = defineCommand({
|
|
310
|
+
meta: {
|
|
311
|
+
name: "show",
|
|
312
|
+
description: "Print the resolved forgemap config and its source path"
|
|
313
|
+
},
|
|
314
|
+
args: {
|
|
315
|
+
config: {
|
|
316
|
+
type: "string",
|
|
317
|
+
description: "Path to forgemap.config.ts (overrides walk-up discovery)"
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
async run({ args }) {
|
|
321
|
+
const loaded = await loadForgeMapConfig({ configFile: args.config });
|
|
322
|
+
process.stdout.write(
|
|
323
|
+
JSON.stringify(
|
|
324
|
+
{
|
|
325
|
+
configFile: loaded.configFile ?? null,
|
|
326
|
+
cwd: loaded.cwd,
|
|
327
|
+
config: loaded.config
|
|
328
|
+
},
|
|
329
|
+
null,
|
|
330
|
+
2
|
|
331
|
+
) + "\n"
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
const configCommand = defineCommand({
|
|
336
|
+
meta: {
|
|
337
|
+
name: "config",
|
|
338
|
+
description: "Manage the forgemap config file"
|
|
339
|
+
},
|
|
340
|
+
subCommands: {
|
|
341
|
+
init: configInitCommand,
|
|
342
|
+
show: configShowCommand
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
const pathCommand = defineCommand({
|
|
346
|
+
meta: {
|
|
347
|
+
name: "path",
|
|
348
|
+
description: "Print the local path where a repo lives (or would live)"
|
|
349
|
+
},
|
|
350
|
+
args: {
|
|
351
|
+
slug: {
|
|
352
|
+
type: "positional",
|
|
353
|
+
description: "owner/repo, forge:owner/repo, or full URL",
|
|
354
|
+
required: true
|
|
355
|
+
},
|
|
356
|
+
config: {
|
|
357
|
+
type: "string",
|
|
358
|
+
description: "Path to forgemap.config.ts (overrides walk-up discovery)"
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
async run({ args }) {
|
|
362
|
+
const loaded = await loadForgeMapConfig({ configFile: args.config });
|
|
363
|
+
const parsed = parseSlug(args.slug);
|
|
364
|
+
const configDir = loaded.configFile ? dirname(loaded.configFile) : loaded.cwd;
|
|
365
|
+
const resolved = resolveSlug(parsed, {
|
|
366
|
+
config: loaded.config,
|
|
367
|
+
configDir
|
|
368
|
+
});
|
|
369
|
+
process.stdout.write(`${resolved.localPath}
|
|
370
|
+
`);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
const rootCommand = defineCommand({
|
|
374
|
+
meta: {
|
|
375
|
+
name: "forgemap",
|
|
376
|
+
description: "Manage a local repo layout of the form <root>/<forge.dir>/<owner>/<repo>"
|
|
377
|
+
},
|
|
378
|
+
subCommands: {
|
|
379
|
+
clone: cloneCommand,
|
|
380
|
+
path: pathCommand,
|
|
381
|
+
config: configCommand
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
runMain(rootCommand);
|
|
385
|
+
//# sourceMappingURL=forgemap.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forgemap.mjs","sources":["../../src/config/load.ts","../../src/utils/exec.ts","../../src/forges/github.ts","../../src/forges/registry.ts","../../src/slug/parse.ts","../../src/utils/path.ts","../../src/slug/resolve.ts","../../src/commands/clone.ts","../../src/commands/config/init.ts","../../src/commands/config/show.ts","../../src/commands/config/index.ts","../../src/commands/path.ts","../../src/cli.ts","../../src/bin/forgemap.ts"],"sourcesContent":["import { loadConfig } from 'c12';\nimport { dirname } from 'pathe';\nimport type { ForgeMapConfig, ForgeMapUserConfig } from './schema.ts';\n\nexport interface LoadedConfig {\n config: ForgeMapConfig;\n configFile: string | undefined;\n cwd: string;\n}\n\nconst DEFAULT_CONFIG: ForgeMapConfig = {\n root: '.',\n defaultForge: 'github',\n forges: {\n github: {\n type: 'github',\n host: 'github.com',\n dir: 'comGithub'\n }\n }\n};\n\nexport interface LoadOptions {\n cwd?: string;\n configFile?: string;\n}\n\nexport async function loadForgeMapConfig(\n options: LoadOptions = {}\n): Promise<LoadedConfig> {\n const envConfig = process.env.FORGEMAP_CONFIG;\n const explicit = options.configFile ?? envConfig;\n const cwd = explicit ? dirname(explicit) : (options.cwd ?? process.cwd());\n\n const { config, configFile } = await loadConfig<ForgeMapUserConfig>({\n name: 'forgemap',\n cwd,\n configFile: explicit ? explicit : 'forgemap.config',\n rcFile: false,\n globalRc: false,\n dotenv: false,\n defaults: DEFAULT_CONFIG\n });\n\n const merged: ForgeMapConfig = {\n root: config.root ?? DEFAULT_CONFIG.root,\n defaultForge: config.defaultForge ?? DEFAULT_CONFIG.defaultForge,\n forges: {\n ...DEFAULT_CONFIG.forges,\n ...config.forges\n }\n };\n\n return {\n config: merged,\n configFile: configFile || undefined,\n cwd\n };\n}\n","import { spawn } from 'node:child_process';\n\nexport interface ExecResult {\n code: number;\n}\n\nexport function execInherit(\n command: string,\n args: string[]\n): Promise<ExecResult> {\n return new Promise((resolvePromise, rejectPromise) => {\n const child = spawn(command, args, { stdio: 'inherit' });\n child.on('error', rejectPromise);\n child.on('close', (code) => {\n resolvePromise({ code: code ?? 0 });\n });\n });\n}\n\nexport function hasCommand(command: string): Promise<boolean> {\n return new Promise((resolvePromise) => {\n const child = spawn(\n process.platform === 'win32' ? 'where' : 'which',\n [command],\n {\n stdio: 'ignore'\n }\n );\n child.on('error', () => resolvePromise(false));\n child.on('close', (code) => resolvePromise(code === 0));\n });\n}\n","import { execInherit, hasCommand } from '../utils/exec.ts';\nimport type { CloneOptions, ForgeAdapter } from './types.ts';\n\nexport const githubAdapter: ForgeAdapter = {\n async clone({ owner, repo, dest }: CloneOptions) {\n if (!(await hasCommand('gh'))) {\n throw new Error(\n 'GitHub CLI (`gh`) is not installed. Install it from https://cli.github.com/ and run `gh auth login`.'\n );\n }\n const { code } = await execInherit('gh', [\n 'repo',\n 'clone',\n `${owner}/${repo}`,\n dest\n ]);\n if (code !== 0) {\n throw new Error(`gh repo clone exited with code ${code}`);\n }\n }\n};\n","import type { ForgeType } from '../config/schema.ts';\nimport { githubAdapter } from './github.ts';\nimport type { ForgeAdapter } from './types.ts';\n\nexport function getForgeAdapter(type: ForgeType): ForgeAdapter {\n switch (type) {\n case 'github':\n return githubAdapter;\n case 'gitlab':\n case 'gitea':\n case 'codeberg':\n throw new Error(\n `Forge type \"${type}\" is not implemented yet. Only \"github\" is supported in this release.`\n );\n default: {\n const exhaustive: never = type;\n throw new Error(`Unknown forge type: ${String(exhaustive)}`);\n }\n }\n}\n","export interface ParsedSlug {\n /** Forge alias if explicitly specified via `<forge>:<owner>/<repo>` */\n forgeName?: string;\n /** Host if extracted from URL/SSH form */\n host?: string;\n owner: string;\n repo: string;\n}\n\nconst SHORT_RE = /^([\\w.-]+)\\/([\\w.-]+)$/;\nconst NAMED_RE = /^([\\w.-]+):([\\w.-]+)\\/([\\w.-]+)$/;\nconst SSH_RE = /^git@([\\w.-]+):([\\w.-]+)\\/([\\w.-]+?)(?:\\.git)?$/;\n\nfunction stripGitSuffix(repo: string): string {\n return repo.endsWith('.git') ? repo.slice(0, -4) : repo;\n}\n\nexport function parseSlug(input: string): ParsedSlug {\n const trimmed = input.trim();\n if (!trimmed) {\n throw new Error('Slug is empty');\n }\n\n // git@host:owner/repo(.git)\n const ssh = SSH_RE.exec(trimmed);\n if (ssh) {\n return {\n host: ssh[1],\n owner: ssh[2]!,\n repo: stripGitSuffix(ssh[3]!)\n };\n }\n\n // https://host/owner/repo(.git) or http://...\n if (/^https?:\\/\\//.test(trimmed)) {\n let url: URL;\n try {\n url = new URL(trimmed);\n } catch {\n throw new Error(`Invalid URL: ${trimmed}`);\n }\n const segments = url.pathname.split('/').filter(Boolean);\n if (segments.length < 2) {\n throw new Error(`URL must contain owner and repo: ${trimmed}`);\n }\n return {\n host: url.host,\n owner: segments[0]!,\n repo: stripGitSuffix(segments[1]!)\n };\n }\n\n // forge:owner/repo\n const named = NAMED_RE.exec(trimmed);\n if (named) {\n return {\n forgeName: named[1],\n owner: named[2]!,\n repo: stripGitSuffix(named[3]!)\n };\n }\n\n // owner/repo\n const short = SHORT_RE.exec(trimmed);\n if (short) {\n return {\n owner: short[1]!,\n repo: stripGitSuffix(short[2]!)\n };\n }\n\n throw new Error(`Unrecognized slug format: ${input}`);\n}\n","import { homedir } from 'node:os';\nimport { isAbsolute, resolve } from 'pathe';\n\nexport function expandTilde(p: string): string {\n if (p === '~') return homedir();\n if (p.startsWith('~/')) return resolve(homedir(), p.slice(2));\n return p;\n}\n\nexport function resolveRoot(root: string, configDir: string): string {\n const expanded = expandTilde(root);\n if (isAbsolute(expanded)) return expanded;\n return resolve(configDir, expanded);\n}\n","import { join } from 'pathe';\nimport type { ForgeConfig, ForgeMapConfig } from '../config/schema.ts';\nimport { resolveRoot } from '../utils/path.ts';\nimport type { ParsedSlug } from './parse.ts';\n\nexport interface ResolvedSlug {\n forgeName: string;\n forge: ForgeConfig;\n owner: string;\n repo: string;\n localPath: string;\n}\n\nexport interface ResolveOptions {\n config: ForgeMapConfig;\n configDir: string;\n}\n\nfunction findForgeByHost(\n forges: ForgeMapConfig['forges'],\n host: string\n): { name: string; forge: ForgeConfig } | undefined {\n for (const [name, forge] of Object.entries(forges)) {\n if (forge.host.toLowerCase() === host.toLowerCase()) {\n return { name, forge };\n }\n }\n return undefined;\n}\n\nexport function resolveSlug(\n parsed: ParsedSlug,\n options: ResolveOptions\n): ResolvedSlug {\n const { config, configDir } = options;\n\n let forgeName: string;\n let forge: ForgeConfig;\n\n if (parsed.forgeName) {\n const candidate = config.forges[parsed.forgeName];\n if (!candidate) {\n throw new Error(\n `Forge \"${parsed.forgeName}\" is not defined in forgemap.config`\n );\n }\n forgeName = parsed.forgeName;\n forge = candidate;\n } else if (parsed.host) {\n const match = findForgeByHost(config.forges, parsed.host);\n if (!match) {\n throw new Error(\n `No forge configured for host \"${parsed.host}\". Add it to forgemap.config.ts.`\n );\n }\n forgeName = match.name;\n forge = match.forge;\n } else {\n const candidate = config.forges[config.defaultForge];\n if (!candidate) {\n throw new Error(\n `Default forge \"${config.defaultForge}\" is not defined in forgemap.config`\n );\n }\n forgeName = config.defaultForge;\n forge = candidate;\n }\n\n const root = resolveRoot(config.root, configDir);\n const localPath = join(root, forge.dir, parsed.owner, parsed.repo);\n\n return {\n forgeName,\n forge,\n owner: parsed.owner,\n repo: parsed.repo,\n localPath\n };\n}\n","import { existsSync } from 'node:fs';\nimport { mkdir } from 'node:fs/promises';\nimport { defineCommand } from 'citty';\nimport consola from 'consola';\nimport { dirname } from 'pathe';\nimport { loadForgeMapConfig } from '../config/load.ts';\nimport { getForgeAdapter } from '../forges/registry.ts';\nimport { parseSlug } from '../slug/parse.ts';\nimport { resolveSlug } from '../slug/resolve.ts';\n\nexport const cloneCommand = defineCommand({\n meta: {\n name: 'clone',\n description: 'Clone a repo into the configured local layout'\n },\n args: {\n slug: {\n type: 'positional',\n description: 'owner/repo, forge:owner/repo, or full URL',\n required: true\n },\n config: {\n type: 'string',\n description: 'Path to forgemap.config.ts (overrides walk-up discovery)'\n }\n },\n async run({ args }) {\n const loaded = await loadForgeMapConfig({ configFile: args.config });\n const parsed = parseSlug(args.slug);\n const configDir = loaded.configFile\n ? dirname(loaded.configFile)\n : loaded.cwd;\n const resolved = resolveSlug(parsed, {\n config: loaded.config,\n configDir\n });\n\n if (existsSync(resolved.localPath)) {\n consola.info(`Already cloned at ${resolved.localPath}`);\n return;\n }\n\n await mkdir(dirname(resolved.localPath), { recursive: true });\n\n const adapter = getForgeAdapter(resolved.forge.type);\n await adapter.clone({\n forge: resolved.forge,\n owner: resolved.owner,\n repo: resolved.repo,\n dest: resolved.localPath\n });\n\n consola.success(\n `Cloned ${resolved.owner}/${resolved.repo} → ${resolved.localPath}`\n );\n }\n});\n","import { existsSync } from 'node:fs';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { defineCommand } from 'citty';\nimport consola from 'consola';\nimport { dirname, join, resolve } from 'pathe';\n\nconst TEMPLATE = `/**\n * forgemap configuration.\n *\n * For type-safe authoring, install forgemap and switch to:\n * import { defineForgeMapConfig } from 'forgemap/config';\n * export default defineForgeMapConfig({ ... });\n *\n * @type {import('forgemap').ForgeMapUserConfig}\n */\nexport default {\n root: '.',\n defaultForge: 'github',\n forges: {\n github: {\n type: 'github',\n host: 'github.com',\n dir: 'comGithub'\n }\n }\n};\n`;\n\nexport const configInitCommand = defineCommand({\n meta: {\n name: 'init',\n description:\n 'Create a forgemap.config.ts in the current (or given) directory'\n },\n args: {\n out: {\n type: 'string',\n description: 'Directory to write forgemap.config.ts into',\n default: '.'\n },\n force: {\n type: 'boolean',\n description: 'Overwrite if forgemap.config.ts already exists',\n default: false\n }\n },\n async run({ args }) {\n const outDir = resolve(process.cwd(), args.out);\n const target = join(outDir, 'forgemap.config.ts');\n\n if (existsSync(target) && !args.force) {\n consola.error(`${target} already exists. Use --force to overwrite.`);\n process.exitCode = 1;\n return;\n }\n\n await mkdir(dirname(target), { recursive: true });\n await writeFile(target, TEMPLATE, 'utf8');\n consola.success(`Wrote ${target}`);\n }\n});\n","import { defineCommand } from 'citty';\nimport { loadForgeMapConfig } from '../../config/load.ts';\n\nexport const configShowCommand = defineCommand({\n meta: {\n name: 'show',\n description: 'Print the resolved forgemap config and its source path'\n },\n args: {\n config: {\n type: 'string',\n description: 'Path to forgemap.config.ts (overrides walk-up discovery)'\n }\n },\n async run({ args }) {\n const loaded = await loadForgeMapConfig({ configFile: args.config });\n process.stdout.write(\n JSON.stringify(\n {\n configFile: loaded.configFile ?? null,\n cwd: loaded.cwd,\n config: loaded.config\n },\n null,\n 2\n ) + '\\n'\n );\n }\n});\n","import { defineCommand } from 'citty';\nimport { configInitCommand } from './init.ts';\nimport { configShowCommand } from './show.ts';\n\nexport const configCommand = defineCommand({\n meta: {\n name: 'config',\n description: 'Manage the forgemap config file'\n },\n subCommands: {\n init: configInitCommand,\n show: configShowCommand\n }\n});\n","import { defineCommand } from 'citty';\nimport { loadForgeMapConfig } from '../config/load.ts';\nimport { dirname } from 'pathe';\nimport { parseSlug } from '../slug/parse.ts';\nimport { resolveSlug } from '../slug/resolve.ts';\n\nexport const pathCommand = defineCommand({\n meta: {\n name: 'path',\n description: 'Print the local path where a repo lives (or would live)'\n },\n args: {\n slug: {\n type: 'positional',\n description: 'owner/repo, forge:owner/repo, or full URL',\n required: true\n },\n config: {\n type: 'string',\n description: 'Path to forgemap.config.ts (overrides walk-up discovery)'\n }\n },\n async run({ args }) {\n const loaded = await loadForgeMapConfig({ configFile: args.config });\n const parsed = parseSlug(args.slug);\n const configDir = loaded.configFile\n ? dirname(loaded.configFile)\n : loaded.cwd;\n const resolved = resolveSlug(parsed, {\n config: loaded.config,\n configDir\n });\n process.stdout.write(`${resolved.localPath}\\n`);\n }\n});\n","import { defineCommand } from 'citty';\nimport { cloneCommand } from './commands/clone.ts';\nimport { configCommand } from './commands/config/index.ts';\nimport { pathCommand } from './commands/path.ts';\n\nexport const rootCommand = defineCommand({\n meta: {\n name: 'forgemap',\n description:\n 'Manage a local repo layout of the form <root>/<forge.dir>/<owner>/<repo>'\n },\n subCommands: {\n clone: cloneCommand,\n path: pathCommand,\n config: configCommand\n }\n});\n","import { runMain } from 'citty';\nimport { rootCommand } from '../cli.ts';\n\nrunMain(rootCommand);\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define.mjs","sources":["../../src/config/define.ts"],"sourcesContent":["import type { ForgeMapUserConfig } from './schema.ts';\n\nexport type {\n ForgeMapConfig,\n ForgeMapUserConfig,\n ForgeConfig,\n ForgeType\n} from './schema.ts';\n\nexport function defineForgeMapConfig(\n config: ForgeMapUserConfig\n): ForgeMapUserConfig {\n return config;\n}\n"],"names":[],"mappings":"AASO,SAAS,qBACd,QACoB;AACpB,SAAO;AACT;"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://www.schemastore.org/package.json",
|
|
3
|
+
"name": "forgemap",
|
|
4
|
+
"version": "0.1.0-dev-main.7-64cc97e",
|
|
5
|
+
"description": "CLI that manages a local repo layout under ~/projects/<gitserver>/<org>/<repo>.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"cli",
|
|
8
|
+
"developer-tools",
|
|
9
|
+
"forge",
|
|
10
|
+
"gh",
|
|
11
|
+
"git",
|
|
12
|
+
"github",
|
|
13
|
+
"nodejs",
|
|
14
|
+
"repository-management"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/TitusKirch/forgemap#readme",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/TitusKirch/forgemap/issues"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"author": "Titus Kirch <titus.kirch@kirch.dev>",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/TitusKirch/forgemap.git"
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"forgemap": "./dist/bin/forgemap.mjs"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"type": "module",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"import": "./dist/index.mjs"
|
|
37
|
+
},
|
|
38
|
+
"./config": {
|
|
39
|
+
"types": "./dist/config/define.d.ts",
|
|
40
|
+
"import": "./dist/config/define.mjs"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public",
|
|
45
|
+
"provenance": true
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"c12": "^3.0.0",
|
|
49
|
+
"citty": "^0.1.6",
|
|
50
|
+
"consola": "^3.4.0",
|
|
51
|
+
"defu": "^6.1.4",
|
|
52
|
+
"pathe": "^2.0.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@commitlint/cli": "^21.0.1",
|
|
56
|
+
"@commitlint/config-conventional": "^21.0.1",
|
|
57
|
+
"@types/node": "^24.0.0",
|
|
58
|
+
"husky": "^9.1.7",
|
|
59
|
+
"lint-staged": "^17.0.5",
|
|
60
|
+
"oxfmt": "0.51.0",
|
|
61
|
+
"oxlint": "1.66.0",
|
|
62
|
+
"taze": "^19.13.0",
|
|
63
|
+
"typescript": "^5.6.0",
|
|
64
|
+
"vite": "^6.0.0",
|
|
65
|
+
"vitest": "^3.0.0"
|
|
66
|
+
},
|
|
67
|
+
"engines": {
|
|
68
|
+
"node": ">=24"
|
|
69
|
+
},
|
|
70
|
+
"scripts": {
|
|
71
|
+
"build": "vite build",
|
|
72
|
+
"dev": "vite build --watch",
|
|
73
|
+
"lint": "oxlint . --deny-warnings",
|
|
74
|
+
"lint:fix": "oxlint . --fix --deny-warnings",
|
|
75
|
+
"format": "oxfmt --check .",
|
|
76
|
+
"format:fix": "oxfmt .",
|
|
77
|
+
"typecheck": "tsc --noEmit",
|
|
78
|
+
"test": "vitest run",
|
|
79
|
+
"test:watch": "vitest",
|
|
80
|
+
"check": "pnpm lint && pnpm format",
|
|
81
|
+
"check:fix": "pnpm lint:fix && pnpm format:fix",
|
|
82
|
+
"taze": "taze",
|
|
83
|
+
"taze:w": "taze -w"
|
|
84
|
+
}
|
|
85
|
+
}
|