@lzear/forge 0.0.1
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/dist/bin.js +239 -0
- package/dist/eslint.d.ts +6 -0
- package/dist/eslint.js +5 -0
- package/dist/repo-lint.d.ts +1 -0
- package/dist/repo-lint.js +6 -0
- package/dist/tsup.d.ts +1 -0
- package/dist/tsup.js +6 -0
- package/dist/vite.d.ts +1 -0
- package/dist/vite.js +5 -0
- package/dist/vitest.d.ts +1 -0
- package/dist/vitest.js +5 -0
- package/dist/vitest.react.d.ts +1 -0
- package/dist/vitest.react.js +5 -0
- package/package.json +105 -0
- package/tsconfig/app.json +4 -0
- package/tsconfig/lib.json +4 -0
- package/tsconfig/react.json +4 -0
package/dist/bin.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/bin.ts
|
|
4
|
+
import { spawnSync } from "child_process";
|
|
5
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
6
|
+
import { createRequire } from "module";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import * as clack from "@clack/prompts";
|
|
10
|
+
import { checkLocal, checkRepo } from "@lzear/repo-lint";
|
|
11
|
+
import { Command } from "commander";
|
|
12
|
+
import pc from "picocolors";
|
|
13
|
+
var require2 = createRequire(import.meta.url);
|
|
14
|
+
var { version } = require2("../package.json");
|
|
15
|
+
var isTTY = process.stdout.isTTY ?? false;
|
|
16
|
+
var isDebug = process.env.DEBUG?.includes("forge") ?? false;
|
|
17
|
+
var debug = (...args) => {
|
|
18
|
+
if (isDebug) console.error(pc.dim(`[debug] ${args.join(" ")}`));
|
|
19
|
+
};
|
|
20
|
+
var log2 = {
|
|
21
|
+
intro: (msg) => isTTY ? clack.intro(msg) : console.log(msg),
|
|
22
|
+
outro: (msg) => isTTY ? clack.outro(msg) : console.log(msg),
|
|
23
|
+
success: (msg) => isTTY ? clack.log.success(msg) : console.log(`\u2713 ${msg}`),
|
|
24
|
+
error: (msg) => isTTY ? clack.log.error(msg) : console.error(`\u2717 ${msg}`),
|
|
25
|
+
warn: (msg) => isTTY ? clack.log.warn(msg) : console.warn(`! ${msg}`),
|
|
26
|
+
spinner: () => isTTY ? clack.spinner() : {
|
|
27
|
+
start: (msg) => {
|
|
28
|
+
debug(msg);
|
|
29
|
+
},
|
|
30
|
+
stop: (msg) => {
|
|
31
|
+
debug(msg);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var program = new Command();
|
|
36
|
+
program.name("forge").description("lzear repo tooling").version(version, "-v, --version").helpOption("-h, --help", "show help");
|
|
37
|
+
var DEFAULT_DIR = path.join(homedir(), ".cache", "forge");
|
|
38
|
+
var printResults = (report, json) => {
|
|
39
|
+
const anyFail = report.results.some((r) => !r.pass);
|
|
40
|
+
if (json) {
|
|
41
|
+
console.log(
|
|
42
|
+
JSON.stringify(
|
|
43
|
+
{ repo: report.repo, results: report.results, pass: !anyFail },
|
|
44
|
+
null,
|
|
45
|
+
2
|
|
46
|
+
)
|
|
47
|
+
);
|
|
48
|
+
return anyFail;
|
|
49
|
+
}
|
|
50
|
+
const pass = report.results.filter((r) => r.pass).length;
|
|
51
|
+
const total = report.results.length;
|
|
52
|
+
for (const r of report.results) {
|
|
53
|
+
process.stdout.write(
|
|
54
|
+
` ${r.pass ? pc.green("\u2713") : pc.red("\u2717")} ${r.desc}
|
|
55
|
+
`
|
|
56
|
+
);
|
|
57
|
+
if (!r.pass && r.detail)
|
|
58
|
+
for (const line of r.detail.split("\n"))
|
|
59
|
+
process.stdout.write(` ${pc.dim(line)}
|
|
60
|
+
`);
|
|
61
|
+
}
|
|
62
|
+
const score = pass === total ? pc.green(`${pass}/${total}`) : pc.yellow(`${pass}/${total}`);
|
|
63
|
+
process.stdout.write(`
|
|
64
|
+
${score} checks passed
|
|
65
|
+
`);
|
|
66
|
+
return anyFail;
|
|
67
|
+
};
|
|
68
|
+
var runCheck = async (repos, opts) => {
|
|
69
|
+
if (!opts.json) process.stdout.write(`
|
|
70
|
+
${pc.bold("forge check")}
|
|
71
|
+
|
|
72
|
+
`);
|
|
73
|
+
if (!repos) {
|
|
74
|
+
debug("checkLocal", process.cwd());
|
|
75
|
+
const result = await checkLocal({ skipRemote: opts.skipRemote });
|
|
76
|
+
process.exit(printResults(result, opts.json) ? 1 : 0);
|
|
77
|
+
}
|
|
78
|
+
const repoList = repos.split(",").map((r) => r.trim());
|
|
79
|
+
let anyFail = false;
|
|
80
|
+
for (const repo of repoList) {
|
|
81
|
+
if (!opts.json)
|
|
82
|
+
process.stdout.write(` ${pc.dim("checking " + repo + "\u2026")}
|
|
83
|
+
`);
|
|
84
|
+
debug("checkRepo", repo);
|
|
85
|
+
try {
|
|
86
|
+
const result = await checkRepo(repo, {
|
|
87
|
+
baseDir: opts.dir,
|
|
88
|
+
skipRemote: opts.skipRemote
|
|
89
|
+
});
|
|
90
|
+
if (printResults(result, opts.json)) anyFail = true;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
debug(String(error));
|
|
93
|
+
process.stderr.write(` ${pc.red("\u2717 " + repo + " \u2014 could not clone")}
|
|
94
|
+
`);
|
|
95
|
+
anyFail = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
process.exit(anyFail ? 1 : 0);
|
|
99
|
+
};
|
|
100
|
+
program.command("check").description("audit repo(s) against forge standards").option(
|
|
101
|
+
"--repos <repos>",
|
|
102
|
+
"comma-separated list of repos (default: current repo)"
|
|
103
|
+
).option("--skip-remote", "skip secret checks", false).option("--dir <dir>", "cache dir for cloned repos", DEFAULT_DIR).option("--json", "output results as JSON", false).action(
|
|
104
|
+
(opts) => runCheck(opts.repos, opts)
|
|
105
|
+
);
|
|
106
|
+
var promptAndSet = async (s, repo) => {
|
|
107
|
+
if (!isTTY) {
|
|
108
|
+
log2.error(
|
|
109
|
+
`${s.name}: non-interactive shell \u2014 run in a TTY or: gh secret set ${s.name} --repo ${repo}`
|
|
110
|
+
);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const value = await clack.password({
|
|
114
|
+
message: `${s.name}: ${pc.dim(s.desc)}`
|
|
115
|
+
});
|
|
116
|
+
if (clack.isCancel(value) || !String(value).trim()) {
|
|
117
|
+
log2.warn(`${s.name} skipped.`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
spawnSync(
|
|
121
|
+
"gh",
|
|
122
|
+
["secret", "set", s.name, "--repo", repo, "--body", String(value).trim()],
|
|
123
|
+
{ stdio: "ignore" }
|
|
124
|
+
);
|
|
125
|
+
log2.success(`${s.name} set.`);
|
|
126
|
+
};
|
|
127
|
+
var REQUIRED_SECRETS = (repo) => [
|
|
128
|
+
{ name: "NPM_TOKEN", desc: "npm publish token (same for all repos)" },
|
|
129
|
+
{
|
|
130
|
+
name: "CODACY_PROJECT_TOKEN",
|
|
131
|
+
desc: `Codacy project token \u2014 find at app.codacy.com/gh/${repo}/settings/coverage`
|
|
132
|
+
}
|
|
133
|
+
];
|
|
134
|
+
var detectRepo = () => {
|
|
135
|
+
const result = spawnSync("git", ["remote", "get-url", "origin"], {
|
|
136
|
+
encoding: "utf8"
|
|
137
|
+
});
|
|
138
|
+
if (result.status !== 0) return void 0;
|
|
139
|
+
const url = result.stdout.trim();
|
|
140
|
+
const match = /github\.com[:/]([^/]+\/[^/.]+)(?:\.git)?$/.exec(url);
|
|
141
|
+
return match?.[1];
|
|
142
|
+
};
|
|
143
|
+
program.command("setup").description("check and set required GitHub secrets for a repo").option("--repo <repo>", "repo to set up (e.g. lzear/my-repo)").option("--dry", "check only, do not prompt for values", false).option("--json", "output results as JSON", false).action(async (opts) => {
|
|
144
|
+
const repo = opts.repo ?? detectRepo();
|
|
145
|
+
if (!repo) {
|
|
146
|
+
log2.error("Could not detect repo. Pass --repo <owner/repo>.");
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
const { dry, json } = opts;
|
|
150
|
+
if (!json) log2.intro(pc.bold(`forge setup \xB7 ${pc.cyan(repo)}`));
|
|
151
|
+
if (spawnSync("gh", ["--version"], { stdio: "ignore" }).status !== 0) {
|
|
152
|
+
log2.error("gh CLI not found \u2014 install at https://cli.github.com");
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
let existing;
|
|
156
|
+
try {
|
|
157
|
+
debug("gh secret list --repo", repo);
|
|
158
|
+
const result = spawnSync(
|
|
159
|
+
"gh",
|
|
160
|
+
["secret", "list", "--repo", repo, "--json", "name"],
|
|
161
|
+
{ encoding: "utf8" }
|
|
162
|
+
);
|
|
163
|
+
if (result.status !== 0) throw new Error(result.stderr);
|
|
164
|
+
existing = JSON.parse(result.stdout).map(
|
|
165
|
+
(s) => s.name
|
|
166
|
+
);
|
|
167
|
+
} catch {
|
|
168
|
+
log2.error("Failed to list secrets. Run: gh auth login");
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
const secrets = REQUIRED_SECRETS(repo);
|
|
172
|
+
const present = secrets.filter((s) => existing.includes(s.name));
|
|
173
|
+
const missing = secrets.filter((s) => !existing.includes(s.name));
|
|
174
|
+
if (json) {
|
|
175
|
+
console.log(
|
|
176
|
+
JSON.stringify(
|
|
177
|
+
{
|
|
178
|
+
repo,
|
|
179
|
+
present: present.map((s) => s.name),
|
|
180
|
+
missing: missing.map((s) => s.name)
|
|
181
|
+
},
|
|
182
|
+
null,
|
|
183
|
+
2
|
|
184
|
+
)
|
|
185
|
+
);
|
|
186
|
+
process.exit(missing.length > 0 ? 1 : 0);
|
|
187
|
+
}
|
|
188
|
+
for (const s of present) log2.success(s.name);
|
|
189
|
+
for (const s of missing) log2.error(`${s.name} \u2014 missing`);
|
|
190
|
+
if (missing.length === 0) {
|
|
191
|
+
log2.outro(pc.green("All secrets present."));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (dry) {
|
|
195
|
+
log2.outro(pc.yellow("Dry run \u2014 skipping."));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
for (const s of missing) {
|
|
199
|
+
await promptAndSet(s, repo);
|
|
200
|
+
}
|
|
201
|
+
log2.outro(pc.green("Done."));
|
|
202
|
+
});
|
|
203
|
+
var SYNC_FILES = [
|
|
204
|
+
{ src: "template/.editorconfig", dest: ".editorconfig" },
|
|
205
|
+
{ src: "template/.codacy.yml", dest: ".codacy.yml" }
|
|
206
|
+
];
|
|
207
|
+
var RAW_BASE = "https://raw.githubusercontent.com/lzear/forge/main";
|
|
208
|
+
program.command("sync").description("sync template files from forge into this repo").option("--dry", "print what would change, do not write", false).action(async (opts) => {
|
|
209
|
+
log2.intro(pc.bold("forge sync"));
|
|
210
|
+
let anyFail = false;
|
|
211
|
+
for (const { src, dest } of SYNC_FILES) {
|
|
212
|
+
const url = `${RAW_BASE}/${src}`;
|
|
213
|
+
const destPath = path.join(process.cwd(), dest);
|
|
214
|
+
try {
|
|
215
|
+
const res = await fetch(url);
|
|
216
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
217
|
+
const content = await res.text();
|
|
218
|
+
if (opts.dry) {
|
|
219
|
+
log2.success(`${dest} ${pc.dim("(dry)")}`);
|
|
220
|
+
} else {
|
|
221
|
+
await mkdir(path.dirname(destPath), { recursive: true });
|
|
222
|
+
await writeFile(destPath, content, "utf8");
|
|
223
|
+
log2.success(dest);
|
|
224
|
+
}
|
|
225
|
+
} catch (error) {
|
|
226
|
+
log2.error(`${dest} \u2014 ${String(error)}`);
|
|
227
|
+
anyFail = true;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
log2.outro(anyFail ? pc.red("Done with errors.") : pc.green("Done."));
|
|
231
|
+
if (anyFail) process.exit(1);
|
|
232
|
+
});
|
|
233
|
+
var handleSignal = () => {
|
|
234
|
+
if (isTTY) clack.cancel("Cancelled.");
|
|
235
|
+
process.exit(130);
|
|
236
|
+
};
|
|
237
|
+
process.on("SIGINT", handleSignal);
|
|
238
|
+
process.on("SIGTERM", handleSignal);
|
|
239
|
+
program.parse();
|
package/dist/eslint.d.ts
ADDED
package/dist/eslint.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Check, CheckLocalOptions, CheckRepoOptions, CheckResult, LocalCheck, RemoteCheck, RepoReport, checkLocal, checkRepo } from '@lzear/repo-lint';
|
package/dist/tsup.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { defineBinConfig, defineLibConfig } from '@lzear/configs/tsup';
|
package/dist/tsup.js
ADDED
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { defineReactConfig } from '@lzear/configs/vite';
|
package/dist/vite.js
ADDED
package/dist/vitest.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@lzear/configs/vitest';
|
package/dist/vitest.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@lzear/configs/vitest/react';
|
package/package.json
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lzear/forge",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Umbrella package for all lzear dev tooling",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/lzear/forge.git",
|
|
8
|
+
"directory": "packages/forge"
|
|
9
|
+
},
|
|
10
|
+
"author": "lzear",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"type": "module",
|
|
14
|
+
"exports": {
|
|
15
|
+
"./eslint": {
|
|
16
|
+
"types": "./dist/eslint.d.ts",
|
|
17
|
+
"default": "./dist/eslint.js"
|
|
18
|
+
},
|
|
19
|
+
"./repo-lint": {
|
|
20
|
+
"types": "./dist/repo-lint.d.ts",
|
|
21
|
+
"default": "./dist/repo-lint.js"
|
|
22
|
+
},
|
|
23
|
+
"./tsconfig/app": "./tsconfig/app.json",
|
|
24
|
+
"./tsconfig/lib": "./tsconfig/lib.json",
|
|
25
|
+
"./tsconfig/react": "./tsconfig/react.json",
|
|
26
|
+
"./tsup": {
|
|
27
|
+
"types": "./dist/tsup.d.ts",
|
|
28
|
+
"default": "./dist/tsup.js"
|
|
29
|
+
},
|
|
30
|
+
"./vite": {
|
|
31
|
+
"types": "./dist/vite.d.ts",
|
|
32
|
+
"default": "./dist/vite.js"
|
|
33
|
+
},
|
|
34
|
+
"./vitest": {
|
|
35
|
+
"types": "./dist/vitest.d.ts",
|
|
36
|
+
"default": "./dist/vitest.js"
|
|
37
|
+
},
|
|
38
|
+
"./vitest/react": {
|
|
39
|
+
"types": "./dist/vitest.react.d.ts",
|
|
40
|
+
"default": "./dist/vitest.react.js"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"bin": "./dist/bin.js",
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"tsconfig"
|
|
47
|
+
],
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsup",
|
|
50
|
+
"dev": "tsup --watch",
|
|
51
|
+
"typecheck": "tsc --noEmit"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@clack/prompts": "^1",
|
|
55
|
+
"@lzear/configs": "workspace:*",
|
|
56
|
+
"@lzear/eslint-config": "workspace:*",
|
|
57
|
+
"@lzear/repo-lint": "workspace:*",
|
|
58
|
+
"commander": "^14",
|
|
59
|
+
"picocolors": "^1"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@types/node": "^25",
|
|
63
|
+
"@vitejs/plugin-react": "^6",
|
|
64
|
+
"eslint": "^9",
|
|
65
|
+
"jsdom": "^29",
|
|
66
|
+
"tsup": "^8",
|
|
67
|
+
"typescript": "^6",
|
|
68
|
+
"vite": "^8",
|
|
69
|
+
"vitest": "^4"
|
|
70
|
+
},
|
|
71
|
+
"peerDependencies": {
|
|
72
|
+
"@vitejs/plugin-react": "^6",
|
|
73
|
+
"eslint": "^9",
|
|
74
|
+
"jsdom": "^29",
|
|
75
|
+
"tsup": "^8",
|
|
76
|
+
"vite": "^8",
|
|
77
|
+
"vitest": "^4"
|
|
78
|
+
},
|
|
79
|
+
"peerDependenciesMeta": {
|
|
80
|
+
"@vitejs/plugin-react": {
|
|
81
|
+
"optional": true
|
|
82
|
+
},
|
|
83
|
+
"eslint": {
|
|
84
|
+
"optional": true
|
|
85
|
+
},
|
|
86
|
+
"jsdom": {
|
|
87
|
+
"optional": true
|
|
88
|
+
},
|
|
89
|
+
"tsup": {
|
|
90
|
+
"optional": true
|
|
91
|
+
},
|
|
92
|
+
"vite": {
|
|
93
|
+
"optional": true
|
|
94
|
+
},
|
|
95
|
+
"vitest": {
|
|
96
|
+
"optional": true
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"engines": {
|
|
100
|
+
"node": ">=20"
|
|
101
|
+
},
|
|
102
|
+
"publishConfig": {
|
|
103
|
+
"access": "public"
|
|
104
|
+
}
|
|
105
|
+
}
|