create-dokio 0.1.17 → 0.1.18

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.
Files changed (3) hide show
  1. package/README.md +20 -0
  2. package/dist/index.js +157 -77
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -113,6 +113,26 @@ cp tools/changelog/.env.example tools/changelog/.env
113
113
 
114
114
  ---
115
115
 
116
+ ### `create-dokio repair`
117
+
118
+ **Run this once after cloning a hub repo.** Hooks live in `.githooks/`, but Git activates them via `core.hooksPath` — a setting stored in `.git/config`, which is **never committed or cloned**. So a fresh clone has the hook files but they don't run: the commit-message check is skipped and changelogs stop auto-updating. Git cannot enable repo hooks automatically on clone (security), so every machine must opt in once.
119
+
120
+ ```bash
121
+ # from inside a cloned hub repo (e.g. bupa-sam-templates/)
122
+ npx --yes create-dokio@latest repair
123
+ ```
124
+
125
+ **What it does:**
126
+ - Sets `git config core.hooksPath .githooks` for this clone (the actual fix)
127
+ - Re-writes the managed tooling to the latest version (`.githooks/commit-msg`, `.githooks/post-commit`, `tools/changelog/update_changelog.py`)
128
+ - Ensures `.vscode/settings.json`, `tools/changelog/.env.example`, and `.gitignore` entries exist
129
+
130
+ Safe to re-run any time — it never touches your template content or `tools/changelog/.env`.
131
+
132
+ > **Teammates:** if your commit messages aren't being checked, or template `CHANGELOG.md` files aren't updating on commit, you haven't run `repair` on your clone yet.
133
+
134
+ ---
135
+
116
136
  ## Template types
117
137
 
118
138
  ### PDF
package/dist/index.js CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  // src/index.ts
4
4
  import prompts3 from "prompts";
5
- import kleur6 from "kleur";
5
+ import kleur7 from "kleur";
6
6
  import { createRequire } from "module";
7
7
 
8
8
  // src/template.ts
9
- import { join as join3 } from "path";
10
- import fse3 from "fs-extra";
9
+ import { join as join4 } from "path";
10
+ import fse4 from "fs-extra";
11
11
  import kleur4 from "kleur";
12
12
 
13
13
  // src/prompts.ts
@@ -878,10 +878,15 @@ function buildFiles(config) {
878
878
  }
879
879
 
880
880
  // src/git.ts
881
- import { join, basename } from "path";
881
+ import { join as join2, basename } from "path";
882
+ import { execSync as execSync2 } from "child_process";
883
+ import fse2 from "fs-extra";
884
+ import kleur2 from "kleur";
885
+
886
+ // src/hubSetup.ts
887
+ import { join } from "path";
882
888
  import { execSync } from "child_process";
883
889
  import fse from "fs-extra";
884
- import kleur2 from "kleur";
885
890
 
886
891
  // src/templates/changelog.ts
887
892
  function changelogScript() {
@@ -1092,91 +1097,112 @@ function changelogEnvExample() {
1092
1097
  `;
1093
1098
  }
1094
1099
 
1100
+ // src/hubSetup.ts
1101
+ function vscodeSettings() {
1102
+ return JSON.stringify({ "scss.validate": false, "css.validate": false }, null, 2) + "\n";
1103
+ }
1104
+ var REQUIRED_IGNORES = [".DS_Store", "node_modules/", "*.log", "tools/changelog/.env"];
1105
+ var MANAGED = [
1106
+ { rel: ".githooks/commit-msg", content: commitMsgHook, exec: true },
1107
+ { rel: ".githooks/post-commit", content: changelogHook, exec: true },
1108
+ { rel: "tools/changelog/update_changelog.py", content: changelogScript, exec: true }
1109
+ ];
1110
+ var ENSURE = [
1111
+ { rel: "tools/changelog/.env.example", content: changelogEnvExample },
1112
+ { rel: ".vscode/settings.json", content: vscodeSettings }
1113
+ ];
1114
+ async function writeFile(hubDir, file) {
1115
+ const fullPath = join(hubDir, file.rel);
1116
+ await fse.ensureDir(join(fullPath, ".."));
1117
+ await fse.writeFile(fullPath, file.content(), "utf8");
1118
+ if (file.exec) await fse.chmod(fullPath, 493);
1119
+ }
1120
+ async function syncHubFiles(hubDir, opts) {
1121
+ const written = [];
1122
+ for (const file of MANAGED) {
1123
+ const exists = await fse.pathExists(join(hubDir, file.rel));
1124
+ if (exists && !opts.force) continue;
1125
+ await writeFile(hubDir, file);
1126
+ written.push(`${exists ? "~" : "+"} ${file.rel}`);
1127
+ }
1128
+ for (const file of ENSURE) {
1129
+ if (await fse.pathExists(join(hubDir, file.rel))) continue;
1130
+ await writeFile(hubDir, file);
1131
+ written.push(`+ ${file.rel}`);
1132
+ }
1133
+ const gitignorePath = join(hubDir, ".gitignore");
1134
+ if (!await fse.pathExists(gitignorePath)) {
1135
+ await fse.writeFile(gitignorePath, REQUIRED_IGNORES.join("\n") + "\n", "utf8");
1136
+ written.push("+ .gitignore");
1137
+ } else {
1138
+ const existing = await fse.readFile(gitignorePath, "utf8");
1139
+ const missing = REQUIRED_IGNORES.filter((e) => !existing.includes(e));
1140
+ if (missing.length) {
1141
+ await fse.appendFile(gitignorePath, missing.join("\n") + "\n");
1142
+ written.push(`~ .gitignore (added: ${missing.join(", ")})`);
1143
+ }
1144
+ }
1145
+ return { written };
1146
+ }
1147
+ function setHooksPath(hubDir) {
1148
+ execSync("git config core.hooksPath .githooks", { cwd: hubDir, stdio: "ignore" });
1149
+ }
1150
+
1095
1151
  // src/git.ts
1096
1152
  async function ensureHubRepo(hubId) {
1097
1153
  const cwd = process.cwd();
1098
1154
  const dirName = hubRepoDirName(hubId);
1099
1155
  const alreadyInside = basename(cwd) === dirName;
1100
- const hubDir = alreadyInside ? cwd : join(cwd, dirName);
1101
- if (alreadyInside || await fse.pathExists(hubDir)) {
1156
+ const hubDir = alreadyInside ? cwd : join2(cwd, dirName);
1157
+ if (alreadyInside || await fse2.pathExists(hubDir)) {
1102
1158
  console.log(kleur2.dim(`
1103
1159
  \u21BB Pulling latest ${dirName}...`));
1104
- execSync("git pull", { cwd: hubDir, stdio: "ignore" });
1160
+ execSync2("git pull", { cwd: hubDir, stdio: "ignore" });
1105
1161
  } else {
1106
1162
  console.log(kleur2.dim(`
1107
1163
  \u2193 Cloning ${hubRepoUrl(hubId)}...`));
1108
- execSync(`git clone ${hubRepoUrl(hubId)}`, { stdio: "inherit" });
1164
+ execSync2(`git clone ${hubRepoUrl(hubId)}`, { stdio: "inherit" });
1109
1165
  }
1110
1166
  return hubDir;
1111
1167
  }
1112
1168
  async function setupHooks(hubDir) {
1113
- const commitMsgPath = join(hubDir, ".githooks", "commit-msg");
1114
- if (!await fse.pathExists(commitMsgPath)) {
1115
- await fse.ensureDir(join(commitMsgPath, ".."));
1116
- await fse.writeFile(commitMsgPath, commitMsgHook(), "utf8");
1117
- await fse.chmod(commitMsgPath, 493);
1118
- console.log(kleur2.dim(` + .githooks/commit-msg`));
1119
- }
1120
- const postCommitPath = join(hubDir, ".githooks", "post-commit");
1121
- const changelogScriptPath = join(hubDir, "tools", "changelog", "update_changelog.py");
1122
- if (!await fse.pathExists(changelogScriptPath)) {
1123
- await fse.ensureDir(join(changelogScriptPath, ".."));
1124
- await fse.writeFile(changelogScriptPath, changelogScript(), "utf8");
1125
- await fse.chmod(changelogScriptPath, 493);
1126
- console.log(kleur2.dim(` + tools/changelog/update_changelog.py`));
1127
- const envExamplePath = join(hubDir, "tools", "changelog", ".env.example");
1128
- await fse.writeFile(envExamplePath, changelogEnvExample(), "utf8");
1129
- console.log(kleur2.dim(` + tools/changelog/.env.example`));
1130
- if (!await fse.pathExists(postCommitPath)) {
1131
- await fse.writeFile(postCommitPath, changelogHook(), "utf8");
1132
- await fse.chmod(postCommitPath, 493);
1133
- console.log(kleur2.dim(` + .githooks/post-commit`));
1134
- }
1135
- }
1136
- const vscodePath = join(hubDir, ".vscode", "settings.json");
1137
- if (!await fse.pathExists(vscodePath)) {
1138
- await fse.ensureDir(join(vscodePath, ".."));
1139
- await fse.writeFile(vscodePath, JSON.stringify({ "scss.validate": false, "css.validate": false }, null, 2) + "\n", "utf8");
1140
- console.log(kleur2.dim(` + .vscode/settings.json`));
1141
- }
1142
- const gitignorePath = join(hubDir, ".gitignore");
1143
- const requiredIgnores = [".DS_Store", "node_modules/", "*.log", "tools/changelog/.env"];
1144
- if (!await fse.pathExists(gitignorePath)) {
1145
- await fse.writeFile(gitignorePath, requiredIgnores.join("\n") + "\n", "utf8");
1146
- console.log(kleur2.dim(` + .gitignore`));
1147
- } else {
1148
- const existing = await fse.readFile(gitignorePath, "utf8");
1149
- const missing = requiredIgnores.filter((e) => !existing.includes(e));
1150
- if (missing.length) {
1151
- await fse.appendFile(gitignorePath, missing.join("\n") + "\n");
1152
- console.log(kleur2.dim(` ~ .gitignore (added: ${missing.join(", ")})`));
1153
- }
1154
- }
1155
- const readmePath = join(hubDir, "README.md");
1156
- if (!await fse.pathExists(readmePath)) {
1169
+ const { written } = await syncHubFiles(hubDir, { force: false });
1170
+ for (const line of written) console.log(kleur2.dim(` ${line}`));
1171
+ const readmePath = join2(hubDir, "README.md");
1172
+ if (!await fse2.pathExists(readmePath)) {
1157
1173
  const hubName = basename(hubDir);
1158
- await fse.writeFile(readmePath, `# ${hubName}
1174
+ await fse2.writeFile(readmePath, `# ${hubName}
1159
1175
 
1160
1176
  Templates for ${hubName} on Dokio.
1161
1177
 
1178
+ ## After cloning (required, once per clone)
1179
+
1180
+ Git hooks (commit-message check + auto-changelog) are not active until you run:
1181
+
1182
+ \`\`\`
1183
+ npx create-dokio repair
1184
+ \`\`\`
1185
+
1186
+ Git cannot enable repo hooks automatically on clone, so every teammate must run this once.
1187
+
1162
1188
  ## Creating a new template
1163
1189
 
1164
1190
  Run \`create-dokio template\` from inside this repo.
1165
1191
  `, "utf8");
1166
1192
  console.log(kleur2.dim(` + README.md`));
1167
1193
  }
1168
- execSync("git config core.hooksPath .githooks", { cwd: hubDir, stdio: "ignore" });
1194
+ setHooksPath(hubDir);
1169
1195
  }
1170
1196
 
1171
1197
  // src/scaffold.ts
1172
- import { join as join2 } from "path";
1173
- import fse2 from "fs-extra";
1198
+ import { join as join3 } from "path";
1199
+ import fse3 from "fs-extra";
1174
1200
  import kleur3 from "kleur";
1175
1201
  async function writeFiles(outDir, files, fullName) {
1176
1202
  for (const [rel, content] of Object.entries(files)) {
1177
- const fullPath = join2(outDir, rel);
1178
- await fse2.ensureDir(join2(fullPath, ".."));
1179
- await fse2.writeFile(fullPath, content, "utf8");
1203
+ const fullPath = join3(outDir, rel);
1204
+ await fse3.ensureDir(join3(fullPath, ".."));
1205
+ await fse3.writeFile(fullPath, content, "utf8");
1180
1206
  console.log(kleur3.dim(` + ${fullName}/${rel}`));
1181
1207
  }
1182
1208
  }
@@ -1188,8 +1214,8 @@ async function runTemplate(nameArg) {
1188
1214
  const files = buildFiles(config);
1189
1215
  const hubDir = await ensureHubRepo(config.hubId);
1190
1216
  const hubDirName = hubRepoDirName(config.hubId);
1191
- const outDir = join3(hubDir, "templates", config.fullName);
1192
- if (await fse3.pathExists(outDir)) {
1217
+ const outDir = join4(hubDir, "templates", config.fullName);
1218
+ if (await fse4.pathExists(outDir)) {
1193
1219
  console.error(kleur4.red(`
1194
1220
  Error: "${config.fullName}" already exists in ${hubDirName}/templates/.
1195
1221
  `));
@@ -1220,9 +1246,9 @@ async function runTemplate(nameArg) {
1220
1246
  }
1221
1247
 
1222
1248
  // src/hub.ts
1223
- import { join as join4 } from "path";
1224
- import { execSync as execSync2 } from "child_process";
1225
- import fse4 from "fs-extra";
1249
+ import { join as join5 } from "path";
1250
+ import { execSync as execSync3 } from "child_process";
1251
+ import fse5 from "fs-extra";
1226
1252
  import kleur5 from "kleur";
1227
1253
  import prompts2 from "prompts";
1228
1254
  var onCancel2 = () => {
@@ -1251,8 +1277,8 @@ async function runHub() {
1251
1277
  const hubId = answers.hubId.trim();
1252
1278
  const hubName = answers.hubName.trim();
1253
1279
  const dirName = `${hubId}-templates`;
1254
- const outDir = join4(process.cwd(), dirName);
1255
- if (await fse4.pathExists(outDir)) {
1280
+ const outDir = join5(process.cwd(), dirName);
1281
+ if (await fse5.pathExists(outDir)) {
1256
1282
  console.error(kleur5.red(`
1257
1283
  Error: "${dirName}" already exists.
1258
1284
  `));
@@ -1274,6 +1300,16 @@ tools/changelog/.env
1274
1300
 
1275
1301
  Templates for ${hubName} on Dokio.
1276
1302
 
1303
+ ## After cloning (required, once per clone)
1304
+
1305
+ Git hooks (commit-message check + auto-changelog) are not active until you run:
1306
+
1307
+ \`\`\`
1308
+ npx create-dokio repair
1309
+ \`\`\`
1310
+
1311
+ Git cannot enable repo hooks automatically on clone, so every teammate must run this once.
1312
+
1277
1313
  ## Creating a new template
1278
1314
 
1279
1315
  Run \`create-dokio template\` from inside this repo.
@@ -1281,16 +1317,16 @@ Run \`create-dokio template\` from inside this repo.
1281
1317
  };
1282
1318
  console.log("");
1283
1319
  for (const [rel, content] of Object.entries(files)) {
1284
- const fullPath = join4(outDir, rel);
1285
- await fse4.ensureDir(join4(fullPath, ".."));
1286
- await fse4.writeFile(fullPath, content, "utf8");
1287
- if (rel === ".githooks/commit-msg" || rel === ".githooks/post-commit" || rel === "tools/changelog/update_changelog.py") await fse4.chmod(fullPath, 493);
1320
+ const fullPath = join5(outDir, rel);
1321
+ await fse5.ensureDir(join5(fullPath, ".."));
1322
+ await fse5.writeFile(fullPath, content, "utf8");
1323
+ if (rel === ".githooks/commit-msg" || rel === ".githooks/post-commit" || rel === "tools/changelog/update_changelog.py") await fse5.chmod(fullPath, 493);
1288
1324
  console.log(kleur5.dim(` + ${rel}`));
1289
1325
  }
1290
- execSync2("git init", { cwd: outDir, stdio: "ignore" });
1291
- execSync2("git config core.hooksPath .githooks", { cwd: outDir, stdio: "ignore" });
1292
- execSync2("git add .", { cwd: outDir, stdio: "ignore" });
1293
- execSync2('git commit -m "chore: init Dokio Hub"', { cwd: outDir, stdio: "ignore" });
1326
+ execSync3("git init", { cwd: outDir, stdio: "ignore" });
1327
+ execSync3("git config core.hooksPath .githooks", { cwd: outDir, stdio: "ignore" });
1328
+ execSync3("git add .", { cwd: outDir, stdio: "ignore" });
1329
+ execSync3('git commit -m "chore: init Dokio Hub"', { cwd: outDir, stdio: "ignore" });
1294
1330
  console.log(kleur5.green(`
1295
1331
  \u2713 Created ${kleur5.bold(dirName)}
1296
1332
  `));
@@ -1300,12 +1336,53 @@ Run \`create-dokio template\` from inside this repo.
1300
1336
  console.log(kleur5.dim(` git remote add origin https://github.com/dokioco/${dirName}`));
1301
1337
  console.log(kleur5.dim(` git push -u origin main`));
1302
1338
  console.log("");
1339
+ console.log(kleur5.dim(` Teammates \u2014 after cloning, enable git hooks once:`));
1340
+ console.log(kleur5.dim(` npx create-dokio repair`));
1341
+ console.log("");
1303
1342
  console.log(kleur5.dim(` Changelog (optional \u2014 for AI descriptions):`));
1304
1343
  console.log(kleur5.dim(` cp tools/changelog/.env.example tools/changelog/.env`));
1305
1344
  console.log(kleur5.dim(` # Add your ANTHROPIC_API_KEY to tools/changelog/.env`));
1306
1345
  console.log("");
1307
1346
  }
1308
1347
 
1348
+ // src/repair.ts
1349
+ import { execSync as execSync4 } from "child_process";
1350
+ import { basename as basename2 } from "path";
1351
+ import kleur6 from "kleur";
1352
+ function repoRoot() {
1353
+ try {
1354
+ return execSync4("git rev-parse --show-toplevel", {
1355
+ stdio: ["ignore", "pipe", "ignore"]
1356
+ }).toString().trim();
1357
+ } catch {
1358
+ return null;
1359
+ }
1360
+ }
1361
+ async function runRepair() {
1362
+ console.log(kleur6.bold().cyan("\n \u25C6 dokio repair\n"));
1363
+ const hubDir = repoRoot();
1364
+ if (!hubDir) {
1365
+ console.error(kleur6.red(" Not a git repository.\n"));
1366
+ console.error(kleur6.dim(" Run this from inside a cloned hub repo (e.g. bupa-sam-templates/).\n"));
1367
+ process.exit(1);
1368
+ }
1369
+ const { written } = await syncHubFiles(hubDir, { force: true });
1370
+ setHooksPath(hubDir);
1371
+ for (const line of written) console.log(kleur6.dim(` ${line}`));
1372
+ console.log(kleur6.dim(" \u2713 git config core.hooksPath .githooks"));
1373
+ console.log(kleur6.green(`
1374
+ \u2713 Repaired ${kleur6.bold(basename2(hubDir))}
1375
+ `));
1376
+ console.log(kleur6.dim(" Git hooks are now active for this clone:"));
1377
+ console.log(kleur6.dim(" \u2022 commit-msg \u2192 enforces Conventional Commits"));
1378
+ console.log(kleur6.dim(" \u2022 post-commit \u2192 auto-updates template CHANGELOG.md"));
1379
+ console.log("");
1380
+ console.log(kleur6.dim(" For AI changelog descriptions (optional):"));
1381
+ console.log(kleur6.dim(" cp tools/changelog/.env.example tools/changelog/.env"));
1382
+ console.log(kleur6.dim(" # add ANTHROPIC_API_KEY to tools/changelog/.env"));
1383
+ console.log("");
1384
+ }
1385
+
1309
1386
  // src/index.ts
1310
1387
  var require2 = createRequire(import.meta.url);
1311
1388
  async function main(argv) {
@@ -1321,7 +1398,10 @@ async function main(argv) {
1321
1398
  if (subcommand === "hub") {
1322
1399
  return runHub();
1323
1400
  }
1324
- console.log(kleur6.bold().cyan("\n \u25C6 dokio create\n"));
1401
+ if (subcommand === "repair") {
1402
+ return runRepair();
1403
+ }
1404
+ console.log(kleur7.bold().cyan("\n \u25C6 dokio create\n"));
1325
1405
  const { action } = await prompts3(
1326
1406
  {
1327
1407
  type: "select",
@@ -1334,7 +1414,7 @@ async function main(argv) {
1334
1414
  },
1335
1415
  {
1336
1416
  onCancel: () => {
1337
- console.log(kleur6.yellow("\n Cancelled.\n"));
1417
+ console.log(kleur7.yellow("\n Cancelled.\n"));
1338
1418
  process.exit(0);
1339
1419
  }
1340
1420
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-dokio",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "CLI scaffold for Dokio templates",
5
5
  "type": "module",
6
6
  "bin": {