create-kedge 0.10.1 → 0.11.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/package.json +1 -1
- package/src/create.mjs +55 -9
- package/template.json +1 -1
package/package.json
CHANGED
package/src/create.mjs
CHANGED
|
@@ -80,6 +80,31 @@ export function render(template, { name, suffix: sfx, packs }) {
|
|
|
80
80
|
return files;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/** The folder's entries, none when it doesn't exist yet. */
|
|
84
|
+
const existing = (dir) => (existsSync(dir) ? readdirSync(dir) : []);
|
|
85
|
+
|
|
86
|
+
/** The top-level names the template writes. */
|
|
87
|
+
const ownedTop = (files) => new Set(Object.keys(files).map((p) => p.split("/")[0]));
|
|
88
|
+
|
|
89
|
+
/** An existing .gitignore with each of the starter's lines it lacks appended, its own lines as they were. */
|
|
90
|
+
/** Where the CLI and kedge dev write credentials and keys: never tracked (p0.10 §4, §6.2). */
|
|
91
|
+
const CREDENTIAL_PATHS = [".env.local", ".env.development.local", ".dev.vars", ".kedge/state.db"];
|
|
92
|
+
|
|
93
|
+
export function mergeIgnore(mine, starter) {
|
|
94
|
+
const lines = mine.split("\n").map((l) => l.trim());
|
|
95
|
+
// A rule counts as present only if no exception follows it: `.env*` then `!.env.local` would commit
|
|
96
|
+
// the credentials the starter's `.env*` keeps out (codex code r4), so such a rule is appended again, last.
|
|
97
|
+
const holds = (rule) => {
|
|
98
|
+
const at = lines.lastIndexOf(rule);
|
|
99
|
+
return at >= 0 && !lines.slice(at + 1).some((l) => l.startsWith("!"));
|
|
100
|
+
};
|
|
101
|
+
const add = starter
|
|
102
|
+
.split("\n")
|
|
103
|
+
.filter((l) => l.trim() && (l.trim().startsWith("#") ? !lines.includes(l.trim()) : !holds(l.trim())));
|
|
104
|
+
if (!add.length) return mine;
|
|
105
|
+
return `${mine.replace(/\n*$/, "\n")}# Kedge's starter (create-kedge):\n${add.join("\n")}\n`;
|
|
106
|
+
}
|
|
107
|
+
|
|
83
108
|
export function write(dir, files) {
|
|
84
109
|
for (const [path, text] of Object.entries(files)) {
|
|
85
110
|
if (isAbsolute(path) || path.split("/").includes("..")) throw new Error(`template path ${path} escapes the store`);
|
|
@@ -130,10 +155,6 @@ export function main(
|
|
|
130
155
|
const positional = argv.filter((a, i) => !a.startsWith("--") && !["--name", "--from-packs"].includes(argv[i - 1]));
|
|
131
156
|
// No directory: the folder it's run in, as `npm create kedge@latest` alone leaves it (p0.10.1 §1, 1).
|
|
132
157
|
const target = positional[0] ?? ".";
|
|
133
|
-
if (positional[0] === undefined && readdirSync(resolve(target)).length > 0) {
|
|
134
|
-
error(`this folder isn't empty: run it in an empty one, or name a new one: npm create kedge@latest my-store`);
|
|
135
|
-
return 2;
|
|
136
|
-
}
|
|
137
158
|
// Before anything is written: a half-made store on a machine that can't install it helps no one.
|
|
138
159
|
const pnpm = flags.has("--no-install") ? null : run("pnpm", ["--version"], { encoding: "utf8" });
|
|
139
160
|
const problem = environmentProblem({
|
|
@@ -146,10 +167,6 @@ export function main(
|
|
|
146
167
|
return 1;
|
|
147
168
|
}
|
|
148
169
|
const dir = resolve(target);
|
|
149
|
-
if (existsSync(dir) && readdirSync(dir).length > 0) {
|
|
150
|
-
error(`${dir} isn't empty`);
|
|
151
|
-
return 1;
|
|
152
|
-
}
|
|
153
170
|
const chosen = value("--name");
|
|
154
171
|
const name = chosen ?? storeNameOf(basename(dir));
|
|
155
172
|
if (!chosen) log(`store name ${name}, from the folder; --name to choose another`);
|
|
@@ -162,10 +179,39 @@ export function main(
|
|
|
162
179
|
error(e.message);
|
|
163
180
|
return 1;
|
|
164
181
|
}
|
|
182
|
+
// A workspace's own dot-entries (.git, .salve, .vscode) may already be here: a platform like salve
|
|
183
|
+
// loads AGENTS.md and skills only from its root (p0.11 row 11). Anything the template writes may not.
|
|
184
|
+
const clash = existing(dir).filter((e) => !e.startsWith(".") || (e !== ".gitignore" && ownedTop(files).has(e)));
|
|
185
|
+
if (clash.length) {
|
|
186
|
+
error(
|
|
187
|
+
`${dir} already holds ${clash.join(", ")}: run it in an empty folder, or one holding only its own dot-files, or name a new one: npm create kedge@latest my-store`,
|
|
188
|
+
);
|
|
189
|
+
return positional[0] === undefined ? 2 : 1;
|
|
190
|
+
}
|
|
165
191
|
mkdirSync(dir, { recursive: true });
|
|
192
|
+
const ignore = join(dir, ".gitignore");
|
|
193
|
+
if (existsSync(ignore) && files[".gitignore"] !== undefined)
|
|
194
|
+
files[".gitignore"] = mergeIgnore(readFileSync(ignore, "utf8"), files[".gitignore"]);
|
|
166
195
|
write(dir, files);
|
|
167
196
|
log(`created ${name} in ${dir} (Kedge ${t.version})`);
|
|
168
|
-
|
|
197
|
+
// Inside a repository already (a workspace's), the store is part of it: no repository inside another (p0.11 row 12).
|
|
198
|
+
const inRepo =
|
|
199
|
+
String(run("git", ["rev-parse", "--is-inside-work-tree"], { cwd: dir, encoding: "utf8" })?.stdout ?? "").trim() ===
|
|
200
|
+
"true";
|
|
201
|
+
if (!flags.has("--no-git") && !inRepo) run("git", ["init", "-q"], { cwd: dir, stdio: "inherit" });
|
|
202
|
+
// Whatever else the repository's ignore files say, the credentials the CLI saves stay out of Git.
|
|
203
|
+
if (inRepo || !flags.has("--no-git")) {
|
|
204
|
+
const kept = CREDENTIAL_PATHS.filter(
|
|
205
|
+
// 1 is "not ignored"; no git, or no repository (128), is no answer.
|
|
206
|
+
(p) => run("git", ["check-ignore", "-q", p], { cwd: dir, stdio: "ignore" })?.status === 1,
|
|
207
|
+
);
|
|
208
|
+
if (kept.length) {
|
|
209
|
+
error(
|
|
210
|
+
`Git here wouldn't ignore ${kept.join(", ")}, where the CLI saves credentials and keys: add them to .gitignore (an exception such as !.env.local overrides the starter's rule) before anything is committed`,
|
|
211
|
+
);
|
|
212
|
+
return 1;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
169
215
|
if (!flags.has("--no-install")) {
|
|
170
216
|
const r = run("pnpm", ["install"], { cwd: dir, stdio: "inherit" });
|
|
171
217
|
if (r.status !== 0) {
|