@next-generation-collective/factory 0.3.0 → 0.3.2
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/lib/banner.js +86 -0
- package/lib/cli.js +26 -0
- package/package.json +1 -1
package/lib/banner.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { basename, join } from "node:path";
|
|
5
|
+
import { colors } from "./util.js";
|
|
6
|
+
|
|
7
|
+
// The Next-Gen mark in the terminal: an N whose diagonal runs into a G, in the brand gold.
|
|
8
|
+
const MARK = [
|
|
9
|
+
"█▄ █ ▄▀▀▀▀",
|
|
10
|
+
"█ ▀▄█ █ ▄▄",
|
|
11
|
+
"█ █ ▀▄▄▄▀",
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const truecolor = /truecolor|24bit/i.test(process.env.COLORTERM ?? "");
|
|
15
|
+
const gold = (t) => colors.paint(truecolor ? "38;2;245;166;35" : "38;5;214", t);
|
|
16
|
+
const cream = (t) => colors.paint(truecolor ? "38;2;253;251;247" : "97", t);
|
|
17
|
+
|
|
18
|
+
const width = (text) => [...text.replace(/\u001b\[[0-9;]*m/g, "")].length;
|
|
19
|
+
|
|
20
|
+
// The builder's first name, from their git identity or, on a Mac, their account's full
|
|
21
|
+
// name. Nothing is sent anywhere.
|
|
22
|
+
export function firstName() {
|
|
23
|
+
const ask = (cmd, args) => {
|
|
24
|
+
try {
|
|
25
|
+
return execFileSync(cmd, args, { encoding: "utf8", timeout: 1000, stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
26
|
+
} catch {
|
|
27
|
+
return "";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const full = ask("git", ["config", "user.name"]) || (process.platform === "darwin" ? ask("id", ["-F"]) : "");
|
|
31
|
+
return full.split(/\s+/)[0] || null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// One small file in the builder's own settings folder, so the first welcome happens once.
|
|
35
|
+
function welcomeFile() {
|
|
36
|
+
const base = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
37
|
+
return join(base, "next-gen", "welcomed");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function isFirstVisit() {
|
|
41
|
+
try {
|
|
42
|
+
return !existsSync(welcomeFile());
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function rememberVisit() {
|
|
49
|
+
try {
|
|
50
|
+
const file = welcomeFile();
|
|
51
|
+
mkdirSync(join(file, ".."), { recursive: true });
|
|
52
|
+
writeFileSync(file, `${new Date().toISOString().slice(0, 10)}\n`);
|
|
53
|
+
} catch {
|
|
54
|
+
// A read-only home folder only means the long welcome shows again.
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function welcomeLines({ name, first, version }) {
|
|
59
|
+
const you = name ? `, ${name}` : "";
|
|
60
|
+
return first
|
|
61
|
+
? [
|
|
62
|
+
colors.bold(cream(`Welcome to Next-Gen Factory${you}.`)),
|
|
63
|
+
cream("You are one of the builders of the AI economy."),
|
|
64
|
+
`${gold("Africa builds.")} ${colors.dim(`v${version}`)}`,
|
|
65
|
+
]
|
|
66
|
+
: [
|
|
67
|
+
colors.bold(cream(`Welcome back${you}.`)),
|
|
68
|
+
cream("Every build you prove makes every builder stronger."),
|
|
69
|
+
`${gold("Africa builds.")} ${colors.dim(`v${version}`)}`,
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function banner(version, root, { name = firstName(), first = isFirstVisit() } = {}) {
|
|
74
|
+
const lines = welcomeLines({ name, first, version });
|
|
75
|
+
const rows = lines.map((line, i) => ` ${gold(MARK[i] ?? " ".repeat(width(MARK[0])))} ${line}`);
|
|
76
|
+
const inner = Math.max(...rows.map(width)) + 2;
|
|
77
|
+
const edge = (l, r) => colors.dim(`${l}${"─".repeat(inner)}${r}`);
|
|
78
|
+
if (first) rememberVisit();
|
|
79
|
+
return [
|
|
80
|
+
edge("╭", "╮"),
|
|
81
|
+
...rows.map((row) => `${colors.dim("│")}${row}${" ".repeat(inner - width(row))}${colors.dim("│")}`),
|
|
82
|
+
edge("╰", "╯"),
|
|
83
|
+
colors.dim(` Reading ${basename(root) || root}...`),
|
|
84
|
+
"",
|
|
85
|
+
].join("\n");
|
|
86
|
+
}
|
package/lib/cli.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { banner } from "./banner.js";
|
|
3
|
+
import { parse as parse_, resolve } from "node:path";
|
|
1
4
|
import { createRequire } from "node:module";
|
|
2
5
|
import {
|
|
3
6
|
approveCommand,
|
|
@@ -95,6 +98,20 @@ Nothing is written into your project until you start a job. Nothing is called
|
|
|
95
98
|
done until the checks pass, and it never says "bug-free" or "compliant".`);
|
|
96
99
|
}
|
|
97
100
|
|
|
101
|
+
// Your home folder or the top of the disk is never one project. Reading it means reading
|
|
102
|
+
// every file on the computer, which takes minutes and shows nothing.
|
|
103
|
+
export function notAProjectFolder(root, home = homedir()) {
|
|
104
|
+
const here = resolve(root);
|
|
105
|
+
if (here !== resolve(home) && here !== parse_(here).root) return null;
|
|
106
|
+
return [
|
|
107
|
+
`This is ${here === resolve(home) ? "your home folder" : "the top of your disk"}, not a project.`,
|
|
108
|
+
"Go into a project folder first, then run it again. For example:",
|
|
109
|
+
"",
|
|
110
|
+
" cd my-project",
|
|
111
|
+
" npx @next-generation-collective/factory",
|
|
112
|
+
].join("\n");
|
|
113
|
+
}
|
|
114
|
+
|
|
98
115
|
export async function main(argv) {
|
|
99
116
|
const { flags, positional } = parse(argv);
|
|
100
117
|
const root = process.cwd();
|
|
@@ -110,6 +127,15 @@ export async function main(argv) {
|
|
|
110
127
|
return 0;
|
|
111
128
|
}
|
|
112
129
|
|
|
130
|
+
if (!command || (!COMMANDS.has(command) && (rest.length > 0 || /\s/.test(command)))) {
|
|
131
|
+
const refusal = notAProjectFolder(root);
|
|
132
|
+
if (refusal) {
|
|
133
|
+
console.error(refusal);
|
|
134
|
+
return 1;
|
|
135
|
+
}
|
|
136
|
+
if (process.stdout.isTTY) console.log(banner(VERSION, root));
|
|
137
|
+
}
|
|
138
|
+
|
|
113
139
|
if (!command) return start(root, flags);
|
|
114
140
|
|
|
115
141
|
if (!COMMANDS.has(command)) {
|
package/package.json
CHANGED