forgemap 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/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
+ [![npm Version](https://img.shields.io/npm/v/forgemap.svg?style=flat-square&color=4f46e5)](https://www.npmjs.com/package/forgemap)
8
+ [![Downloads](https://img.shields.io/npm/dm/forgemap.svg?style=flat-square&color=4f46e5)](https://www.npmjs.com/package/forgemap)
9
+ [![Tests](https://img.shields.io/github/actions/workflow/status/TitusKirch/forgemap/ci.yml?branch=main&style=flat-square&label=tests)](https://github.com/TitusKirch/forgemap/actions/workflows/ci.yml)
10
+ [![Node Version](https://img.shields.io/node/v/forgemap.svg?style=flat-square&color=8993be)](https://www.npmjs.com/package/forgemap)
11
+ [![License: MIT](https://img.shields.io/npm/l/forgemap.svg?style=flat-square&color=10b981)](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":";;;;;;;;;AAUA,MAAM,iBAAiC;AAAA,EACrC,MAAM;AAAA,EACN,cAAc;AAAA,EACd,QAAQ;AAAA,IACN,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,KAAK;AAAA,IAAA;AAAA,EACP;AAEJ;AAOA,eAAsB,mBACpB,UAAuB,IACA;AACvB,QAAM,YAAY,QAAQ,IAAI;AAC9B,QAAM,WAAW,QAAQ,cAAc;AACvC,QAAM,MAAM,WAAW,QAAQ,QAAQ,IAAK,QAAQ,OAAO,QAAQ,IAAA;AAEnE,QAAM,EAAE,QAAQ,WAAA,IAAe,MAAM,WAA+B;AAAA,IAClE,MAAM;AAAA,IACN;AAAA,IACA,YAAY,WAAW,WAAW;AAAA,IAClC,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,UAAU;AAAA,EAAA,CACX;AAED,QAAM,SAAyB;AAAA,IAC7B,MAAM,OAAO,QAAQ,eAAe;AAAA,IACpC,cAAc,OAAO,gBAAgB,eAAe;AAAA,IACpD,QAAQ;AAAA,MACN,GAAG,eAAe;AAAA,MAClB,GAAG,OAAO;AAAA,IAAA;AAAA,EACZ;AAGF,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,YAAY,cAAc;AAAA,IAC1B;AAAA,EAAA;AAEJ;ACpDO,SAAS,YACd,SACA,MACqB;AACrB,SAAO,IAAI,QAAQ,CAAC,gBAAgB,kBAAkB;AACpD,UAAM,QAAQ,MAAM,SAAS,MAAM,EAAE,OAAO,WAAW;AACvD,UAAM,GAAG,SAAS,aAAa;AAC/B,UAAM,GAAG,SAAS,CAAC,SAAS;AAC1B,qBAAe,EAAE,MAAM,QAAQ,EAAA,CAAG;AAAA,IACpC,CAAC;AAAA,EACH,CAAC;AACH;AAEO,SAAS,WAAW,SAAmC;AAC5D,SAAO,IAAI,QAAQ,CAAC,mBAAmB;AACrC,UAAM,QAAQ;AAAA,MACZ,QAAQ,aAAa,UAAU,UAAU;AAAA,MACzC,CAAC,OAAO;AAAA,MACR;AAAA,QACE,OAAO;AAAA,MAAA;AAAA,IACT;AAEF,UAAM,GAAG,SAAS,MAAM,eAAe,KAAK,CAAC;AAC7C,UAAM,GAAG,SAAS,CAAC,SAAS,eAAe,SAAS,CAAC,CAAC;AAAA,EACxD,CAAC;AACH;AC5BO,MAAM,gBAA8B;AAAA,EACzC,MAAM,MAAM,EAAE,OAAO,MAAM,QAAsB;AAC/C,QAAI,CAAE,MAAM,WAAW,IAAI,GAAI;AAC7B,YAAM,IAAI;AAAA,QACR;AAAA,MAAA;AAAA,IAEJ;AACA,UAAM,EAAE,KAAA,IAAS,MAAM,YAAY,MAAM;AAAA,MACvC;AAAA,MACA;AAAA,MACA,GAAG,KAAK,IAAI,IAAI;AAAA,MAChB;AAAA,IAAA,CACD;AACD,QAAI,SAAS,GAAG;AACd,YAAM,IAAI,MAAM,kCAAkC,IAAI,EAAE;AAAA,IAC1D;AAAA,EACF;AACF;AChBO,SAAS,gBAAgB,MAA+B;AAC7D,UAAQ,MAAA;AAAA,IACN,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,YAAM,IAAI;AAAA,QACR,eAAe,IAAI;AAAA,MAAA;AAAA,IAEvB,SAAS;AACP,YAAM,aAAoB;AAC1B,YAAM,IAAI,MAAM,uBAAuB,OAAO,UAAU,CAAC,EAAE;AAAA,IAC7D;AAAA,EAAA;AAEJ;ACVA,MAAM,WAAW;AACjB,MAAM,WAAW;AACjB,MAAM,SAAS;AAEf,SAAS,eAAe,MAAsB;AAC5C,SAAO,KAAK,SAAS,MAAM,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI;AACrD;AAEO,SAAS,UAAU,OAA2B;AACnD,QAAM,UAAU,MAAM,KAAA;AACtB,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,eAAe;AAAA,EACjC;AAGA,QAAM,MAAM,OAAO,KAAK,OAAO;AAC/B,MAAI,KAAK;AACP,WAAO;AAAA,MACL,MAAM,IAAI,CAAC;AAAA,MACX,OAAO,IAAI,CAAC;AAAA,MACZ,MAAM,eAAe,IAAI,CAAC,CAAE;AAAA,IAAA;AAAA,EAEhC;AAGA,MAAI,eAAe,KAAK,OAAO,GAAG;AAChC,QAAI;AACJ,QAAI;AACF,YAAM,IAAI,IAAI,OAAO;AAAA,IACvB,QAAQ;AACN,YAAM,IAAI,MAAM,gBAAgB,OAAO,EAAE;AAAA,IAC3C;AACA,UAAM,WAAW,IAAI,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO;AACvD,QAAI,SAAS,SAAS,GAAG;AACvB,YAAM,IAAI,MAAM,oCAAoC,OAAO,EAAE;AAAA,IAC/D;AACA,WAAO;AAAA,MACL,MAAM,IAAI;AAAA,MACV,OAAO,SAAS,CAAC;AAAA,MACjB,MAAM,eAAe,SAAS,CAAC,CAAE;AAAA,IAAA;AAAA,EAErC;AAGA,QAAM,QAAQ,SAAS,KAAK,OAAO;AACnC,MAAI,OAAO;AACT,WAAO;AAAA,MACL,WAAW,MAAM,CAAC;AAAA,MAClB,OAAO,MAAM,CAAC;AAAA,MACd,MAAM,eAAe,MAAM,CAAC,CAAE;AAAA,IAAA;AAAA,EAElC;AAGA,QAAM,QAAQ,SAAS,KAAK,OAAO;AACnC,MAAI,OAAO;AACT,WAAO;AAAA,MACL,OAAO,MAAM,CAAC;AAAA,MACd,MAAM,eAAe,MAAM,CAAC,CAAE;AAAA,IAAA;AAAA,EAElC;AAEA,QAAM,IAAI,MAAM,6BAA6B,KAAK,EAAE;AACtD;ACrEO,SAAS,YAAY,GAAmB;AAC7C,MAAI,MAAM,IAAK,QAAO,QAAA;AACtB,MAAI,EAAE,WAAW,IAAI,EAAG,QAAO,QAAQ,QAAA,GAAW,EAAE,MAAM,CAAC,CAAC;AAC5D,SAAO;AACT;AAEO,SAAS,YAAY,MAAc,WAA2B;AACnE,QAAM,WAAW,YAAY,IAAI;AACjC,MAAI,WAAW,QAAQ,EAAG,QAAO;AACjC,SAAO,QAAQ,WAAW,QAAQ;AACpC;ACKA,SAAS,gBACP,QACA,MACkD;AAClD,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AAClD,QAAI,MAAM,KAAK,YAAA,MAAkB,KAAK,eAAe;AACnD,aAAO,EAAE,MAAM,MAAA;AAAA,IACjB;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,YACd,QACA,SACc;AACd,QAAM,EAAE,QAAQ,UAAA,IAAc;AAE9B,MAAI;AACJ,MAAI;AAEJ,MAAI,OAAO,WAAW;AACpB,UAAM,YAAY,OAAO,OAAO,OAAO,SAAS;AAChD,QAAI,CAAC,WAAW;AACd,YAAM,IAAI;AAAA,QACR,UAAU,OAAO,SAAS;AAAA,MAAA;AAAA,IAE9B;AACA,gBAAY,OAAO;AACnB,YAAQ;AAAA,EACV,WAAW,OAAO,MAAM;AACtB,UAAM,QAAQ,gBAAgB,OAAO,QAAQ,OAAO,IAAI;AACxD,QAAI,CAAC,OAAO;AACV,YAAM,IAAI;AAAA,QACR,iCAAiC,OAAO,IAAI;AAAA,MAAA;AAAA,IAEhD;AACA,gBAAY,MAAM;AAClB,YAAQ,MAAM;AAAA,EAChB,OAAO;AACL,UAAM,YAAY,OAAO,OAAO,OAAO,YAAY;AACnD,QAAI,CAAC,WAAW;AACd,YAAM,IAAI;AAAA,QACR,kBAAkB,OAAO,YAAY;AAAA,MAAA;AAAA,IAEzC;AACA,gBAAY,OAAO;AACnB,YAAQ;AAAA,EACV;AAEA,QAAM,OAAO,YAAY,OAAO,MAAM,SAAS;AAC/C,QAAM,YAAY,KAAK,MAAM,MAAM,KAAK,OAAO,OAAO,OAAO,IAAI;AAEjE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO,OAAO;AAAA,IACd,MAAM,OAAO;AAAA,IACb;AAAA,EAAA;AAEJ;ACpEO,MAAM,eAAe,cAAc;AAAA,EACxC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EAAA;AAAA,EAEf,MAAM;AAAA,IACJ,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aAAa;AAAA,MACb,UAAU;AAAA,IAAA;AAAA,IAEZ,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,aAAa;AAAA,IAAA;AAAA,EACf;AAAA,EAEF,MAAM,IAAI,EAAE,QAAQ;AAClB,UAAM,SAAS,MAAM,mBAAmB,EAAE,YAAY,KAAK,QAAQ;AACnE,UAAM,SAAS,UAAU,KAAK,IAAI;AAClC,UAAM,YAAY,OAAO,aACrB,QAAQ,OAAO,UAAU,IACzB,OAAO;AACX,UAAM,WAAW,YAAY,QAAQ;AAAA,MACnC,QAAQ,OAAO;AAAA,MACf;AAAA,IAAA,CACD;AAED,QAAI,WAAW,SAAS,SAAS,GAAG;AAClC,cAAQ,KAAK,qBAAqB,SAAS,SAAS,EAAE;AACtD;AAAA,IACF;AAEA,UAAM,MAAM,QAAQ,SAAS,SAAS,GAAG,EAAE,WAAW,MAAM;AAE5D,UAAM,UAAU,gBAAgB,SAAS,MAAM,IAAI;AACnD,UAAM,QAAQ,MAAM;AAAA,MAClB,OAAO,SAAS;AAAA,MAChB,OAAO,SAAS;AAAA,MAChB,MAAM,SAAS;AAAA,MACf,MAAM,SAAS;AAAA,IAAA,CAChB;AAED,YAAQ;AAAA,MACN,UAAU,SAAS,KAAK,IAAI,SAAS,IAAI,MAAM,SAAS,SAAS;AAAA,IAAA;AAAA,EAErE;AACF,CAAC;AClDD,MAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBV,MAAM,oBAAoB,cAAc;AAAA,EAC7C,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aACE;AAAA,EAAA;AAAA,EAEJ,MAAM;AAAA,IACJ,KAAK;AAAA,MACH,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IAAA;AAAA,IAEX,OAAO;AAAA,MACL,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IAAA;AAAA,EACX;AAAA,EAEF,MAAM,IAAI,EAAE,QAAQ;AAClB,UAAM,SAAS,QAAQ,QAAQ,IAAA,GAAO,KAAK,GAAG;AAC9C,UAAM,SAAS,KAAK,QAAQ,oBAAoB;AAEhD,QAAI,WAAW,MAAM,KAAK,CAAC,KAAK,OAAO;AACrC,cAAQ,MAAM,GAAG,MAAM,4CAA4C;AACnE,cAAQ,WAAW;AACnB;AAAA,IACF;AAEA,UAAM,MAAM,QAAQ,MAAM,GAAG,EAAE,WAAW,MAAM;AAChD,UAAM,UAAU,QAAQ,UAAU,MAAM;AACxC,YAAQ,QAAQ,SAAS,MAAM,EAAE;AAAA,EACnC;AACF,CAAC;ACzDM,MAAM,oBAAoB,cAAc;AAAA,EAC7C,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EAAA;AAAA,EAEf,MAAM;AAAA,IACJ,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,aAAa;AAAA,IAAA;AAAA,EACf;AAAA,EAEF,MAAM,IAAI,EAAE,QAAQ;AAClB,UAAM,SAAS,MAAM,mBAAmB,EAAE,YAAY,KAAK,QAAQ;AACnE,YAAQ,OAAO;AAAA,MACb,KAAK;AAAA,QACH;AAAA,UACE,YAAY,OAAO,cAAc;AAAA,UACjC,KAAK,OAAO;AAAA,UACZ,QAAQ,OAAO;AAAA,QAAA;AAAA,QAEjB;AAAA,QACA;AAAA,MAAA,IACE;AAAA,IAAA;AAAA,EAER;AACF,CAAC;ACxBM,MAAM,gBAAgB,cAAc;AAAA,EACzC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EAAA;AAAA,EAEf,aAAa;AAAA,IACX,MAAM;AAAA,IACN,MAAM;AAAA,EAAA;AAEV,CAAC;ACPM,MAAM,cAAc,cAAc;AAAA,EACvC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EAAA;AAAA,EAEf,MAAM;AAAA,IACJ,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aAAa;AAAA,MACb,UAAU;AAAA,IAAA;AAAA,IAEZ,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,aAAa;AAAA,IAAA;AAAA,EACf;AAAA,EAEF,MAAM,IAAI,EAAE,QAAQ;AAClB,UAAM,SAAS,MAAM,mBAAmB,EAAE,YAAY,KAAK,QAAQ;AACnE,UAAM,SAAS,UAAU,KAAK,IAAI;AAClC,UAAM,YAAY,OAAO,aACrB,QAAQ,OAAO,UAAU,IACzB,OAAO;AACX,UAAM,WAAW,YAAY,QAAQ;AAAA,MACnC,QAAQ,OAAO;AAAA,MACf;AAAA,IAAA,CACD;AACD,YAAQ,OAAO,MAAM,GAAG,SAAS,SAAS;AAAA,CAAI;AAAA,EAChD;AACF,CAAC;AC7BM,MAAM,cAAc,cAAc;AAAA,EACvC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aACE;AAAA,EAAA;AAAA,EAEJ,aAAa;AAAA,IACX,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,EAAA;AAEZ,CAAC;ACbD,QAAQ,WAAW;"}
@@ -0,0 +1,7 @@
1
+ function defineForgeMapConfig(config) {
2
+ return config;
3
+ }
4
+ export {
5
+ defineForgeMapConfig
6
+ };
7
+ //# sourceMappingURL=define.mjs.map
@@ -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,5 @@
1
+ import { defineForgeMapConfig } from "./config/define.mjs";
2
+ export {
3
+ defineForgeMapConfig
4
+ };
5
+ //# sourceMappingURL=index.mjs.map
@@ -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",
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
+ }