gistajs 0.1.5 → 0.1.7

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/README.md CHANGED
@@ -10,6 +10,7 @@ npx gistajs create my-app --starter website --no-install --no-git
10
10
  ```
11
11
 
12
12
  Today the CLI covers project creation, starter diffs, pin management, Turso provisioning, and Vercel provisioning.
13
+ For starters that define `pnpm prep`, `gistajs create` offers to run that setup step after dependencies install.
13
14
 
14
15
  ### Diff
15
16
 
@@ -67,7 +68,7 @@ npx gistajs provision turso
67
68
  npx gistajs provision vercel
68
69
  ```
69
70
 
70
- The command is interactive. It asks for a shared region first, defaults new projects to Oregon, reuses the saved region on later runs, creates a Turso group in that region when needed, links Vercel when needed, and asks before overwriting existing database credentials in `.env`.
71
+ The command is interactive. It asks for a shared region first, defaults new projects to Turso's nearest region when available, falls back to Oregon otherwise, reuses the saved `gistajs.region` on later runs, creates a Turso group in that region when needed, links Vercel when needed, and asks before overwriting existing database credentials in `.env`.
71
72
 
72
73
  ## Development
73
74
 
package/dist/bin.cjs CHANGED
@@ -166,6 +166,17 @@ async function runInput(command, args, cwd, input) {
166
166
  input
167
167
  });
168
168
  }
169
+ async function assertCommand(runFn, cwd, command, args, failureMessage) {
170
+ try {
171
+ await runFn(command, args, cwd);
172
+ } catch (error) {
173
+ let code = error.code;
174
+ if (code === "ENOENT") {
175
+ throw new Error(`Required command not found: ${command}`);
176
+ }
177
+ throw new Error(failureMessage);
178
+ }
179
+ }
169
180
  async function exec(command, args, cwd, options) {
170
181
  return await new Promise((resolve2, reject) => {
171
182
  let { capture, input } = options;
@@ -273,7 +284,9 @@ var UsageError = class extends Error {
273
284
  // src/commands/create.ts
274
285
  var defaultCreateProjectDeps = {
275
286
  initGit,
276
- run
287
+ promptConfirm,
288
+ run,
289
+ stdout: console
277
290
  };
278
291
  function parseCreateArgs(argv) {
279
292
  let options = {};
@@ -358,6 +371,9 @@ async function createProject(starter, options, deps = defaultCreateProjectDeps)
358
371
  }
359
372
  if (options.install !== false) {
360
373
  await installDependencies(root, deps.run);
374
+ if (await hasPrepScript(root)) {
375
+ await runPrepScript(root, deps);
376
+ }
361
377
  }
362
378
  return root;
363
379
  } finally {
@@ -420,8 +436,24 @@ async function rewritePackageName(root, projectName) {
420
436
  }
421
437
  }
422
438
  async function installDependencies(root, runCommand) {
439
+ await runPnpmCommand(root, runCommand, "install");
440
+ }
441
+ async function runPrepScript(root, deps) {
442
+ let shouldRun = await deps.promptConfirm("Run project setup now? (Y/n) ");
443
+ if (!shouldRun) return;
423
444
  try {
424
- await runCommand("corepack", ["pnpm", "install"], root);
445
+ await runPnpmCommand(root, deps.run, "prep");
446
+ } catch (error) {
447
+ let detail = error instanceof Error ? error.message : String(error);
448
+ deps.stdout.log(`Warning: Project setup failed. ${detail}`);
449
+ deps.stdout.log(
450
+ `Run ${c.path(`cd ${root} && pnpm prep`)} to retry project setup.`
451
+ );
452
+ }
453
+ }
454
+ async function runPnpmCommand(root, runCommand, script) {
455
+ try {
456
+ await runCommand("corepack", ["pnpm", script], root);
425
457
  return;
426
458
  } catch (error) {
427
459
  if (!isCommandNotFound(error)) {
@@ -429,16 +461,22 @@ async function installDependencies(root, runCommand) {
429
461
  }
430
462
  }
431
463
  try {
432
- await runCommand("pnpm", ["install"], root);
464
+ await runCommand("pnpm", [script], root);
433
465
  } catch (error) {
434
466
  if (!isCommandNotFound(error)) {
435
467
  throw error;
436
468
  }
437
469
  throw new Error(
438
- "Could not install dependencies because neither corepack nor pnpm is available. Install Node.js with corepack enabled, or install pnpm and rerun the command."
470
+ `Could not run pnpm ${script} because neither corepack nor pnpm is available. Install Node.js with corepack enabled, or install pnpm and rerun the command.`
439
471
  );
440
472
  }
441
473
  }
474
+ async function hasPrepScript(root) {
475
+ let path = (0, import_node_path.join)(root, "package.json");
476
+ let source = await (0, import_promises2.readFile)(path, "utf8");
477
+ let pkg = JSON.parse(source);
478
+ return typeof pkg.scripts?.prep === "string" && pkg.scripts.prep.length > 0;
479
+ }
442
480
  function getErrorCode(error) {
443
481
  return error?.code;
444
482
  }
@@ -553,6 +591,8 @@ function getHelpText(command) {
553
591
  ` ${c.bold("Examples:")}`,
554
592
  ` ${c.dim("$")} gistajs create my-app`,
555
593
  ` ${c.dim("$")} gistajs create my-app --starter website`,
594
+ "",
595
+ ` Some starters offer an optional setup step after dependencies install.`,
556
596
  ""
557
597
  ].join("\n");
558
598
  }
@@ -619,6 +659,9 @@ function getHelpText(command) {
619
659
  ` ${c.bold("Examples:")}`,
620
660
  ` ${c.dim("$")} gistajs create my-app`,
621
661
  ` ${c.dim("$")} gistajs create my-app --starter website`,
662
+ "",
663
+ ` Some starters offer an optional setup step after dependencies install.`,
664
+ "",
622
665
  ` ${c.dim("$")} gistajs diff --latest`,
623
666
  ` ${c.dim("$")} gistajs diff --latest --stat`,
624
667
  ` ${c.dim("$")} gistajs diff auth 2026-03-28-001 2026-03-29-001`,
@@ -797,8 +840,41 @@ function getErrorMessage(error) {
797
840
  }
798
841
 
799
842
  // src/commands/pin.ts
843
+ var import_promises5 = require("fs/promises");
844
+ var import_node_path4 = require("path");
845
+
846
+ // src/utils/package.ts
800
847
  var import_promises4 = require("fs/promises");
801
848
  var import_node_path3 = require("path");
849
+ async function readProjectPackage(root, readFileFn = (p, e) => (0, import_promises4.readFile)(p, e)) {
850
+ let path = (0, import_node_path3.join)(root, "package.json");
851
+ let source;
852
+ try {
853
+ source = await readFileFn(path, "utf8");
854
+ } catch (error) {
855
+ let code = error.code;
856
+ if (code === "ENOENT") {
857
+ throw new Error(
858
+ "No package.json found. Run this from a Gista.js project directory."
859
+ );
860
+ }
861
+ throw error;
862
+ }
863
+ try {
864
+ let pkg = JSON.parse(source);
865
+ if (!pkg || typeof pkg !== "object" || Array.isArray(pkg)) {
866
+ throw new Error("package.json must contain a JSON object");
867
+ }
868
+ return pkg;
869
+ } catch (error) {
870
+ if (error instanceof SyntaxError) {
871
+ throw new Error("Could not parse package.json in the current directory.");
872
+ }
873
+ throw error;
874
+ }
875
+ }
876
+
877
+ // src/commands/pin.ts
802
878
  function parsePinArgs(argv) {
803
879
  let options = {};
804
880
  for (let index = 0; index < argv.length; index += 1) {
@@ -859,35 +935,13 @@ async function writeProjectStarterPin(root, pin) {
859
935
  let parsed = splitProjectStarterPin(pin);
860
936
  let gistajs = pkg.gistajs && typeof pkg.gistajs === "object" && !Array.isArray(pkg.gistajs) ? { ...pkg.gistajs } : {};
861
937
  gistajs.pin = parsed.pin;
862
- await (0, import_promises4.writeFile)(
863
- (0, import_node_path3.join)(root, "package.json"),
938
+ await (0, import_promises5.writeFile)(
939
+ (0, import_node_path4.join)(root, "package.json"),
864
940
  `${JSON.stringify({ ...pkg, gistajs }, null, 2)}
865
941
  `
866
942
  );
867
943
  return parsed;
868
944
  }
869
- async function readProjectPackage(root) {
870
- let path = (0, import_node_path3.join)(root, "package.json");
871
- let source;
872
- try {
873
- source = await (0, import_promises4.readFile)(path, "utf8");
874
- } catch (error) {
875
- if (error?.code === "ENOENT") {
876
- throw new Error(`Could not find package.json in ${root}`);
877
- }
878
- throw error;
879
- }
880
- try {
881
- let pkg = JSON.parse(source);
882
- if (!pkg || typeof pkg !== "object" || Array.isArray(pkg)) {
883
- throw new Error("package.json must contain a JSON object");
884
- }
885
- return pkg;
886
- } catch (error) {
887
- let message = error instanceof Error ? error.message : String(error);
888
- throw new Error(`Invalid package.json: ${message}`);
889
- }
890
- }
891
945
 
892
946
  // src/providers/regions.ts
893
947
  var sharedRegions = [
@@ -931,11 +985,6 @@ function getSharedRegion(value) {
931
985
  function getDefaultSharedRegion() {
932
986
  return getSharedRegion(defaultSharedRegionId);
933
987
  }
934
- function parseSharedRegion(value) {
935
- let normalized = value.trim().toLowerCase();
936
- if (!normalized) return null;
937
- return getSharedRegion(normalized);
938
- }
939
988
 
940
989
  // src/utils/version.ts
941
990
  function satisfiesVersion(version, range) {
@@ -1065,7 +1114,7 @@ async function runProviderProvision(provider, deps) {
1065
1114
  if (provider !== "turso" && provider !== "vercel") {
1066
1115
  throw new UsageError(`Unknown provider: ${provider}`, "provision");
1067
1116
  }
1068
- let pkg = await readProjectPackage2(deps);
1117
+ let pkg = await readProvisionPackage(deps);
1069
1118
  let region = await resolveProjectRegion(pkg, deps);
1070
1119
  await writeProjectRegion(pkg, region.id, deps);
1071
1120
  if (provider === "turso") {
@@ -1078,7 +1127,7 @@ async function runProviderProvision(provider, deps) {
1078
1127
  }
1079
1128
  }
1080
1129
  async function runProjectProvision(deps) {
1081
- let pkg = await readProjectPackage2(deps);
1130
+ let pkg = await readProvisionPackage(deps);
1082
1131
  let providers = pkg.gistajs?.providers;
1083
1132
  if (!providers?.length) {
1084
1133
  throw new Error(
@@ -1116,23 +1165,8 @@ async function runProjectProvision(deps) {
1116
1165
  }
1117
1166
  printSummary(summary, deps);
1118
1167
  }
1119
- async function readProjectPackage2(deps) {
1120
- let path = `${deps.cwd}/package.json`;
1121
- try {
1122
- let file = await deps.readFile(path, "utf8");
1123
- return JSON.parse(file);
1124
- } catch (error) {
1125
- let code = error.code;
1126
- if (code === "ENOENT") {
1127
- throw new Error(
1128
- "No package.json found. Run this from a Gista.js project directory."
1129
- );
1130
- }
1131
- if (error instanceof SyntaxError) {
1132
- throw new Error("Could not parse package.json in the current directory.");
1133
- }
1134
- throw error;
1135
- }
1168
+ async function readProvisionPackage(deps) {
1169
+ return await readProjectPackage(deps.cwd, deps.readFile);
1136
1170
  }
1137
1171
  async function writeProjectRegion(pkg, region, deps) {
1138
1172
  let nextPkg = {
@@ -1161,7 +1195,7 @@ async function resolveProjectRegion(pkg, deps) {
1161
1195
  }
1162
1196
  while (true) {
1163
1197
  let answer = await deps.promptText(`Region (${fallback.label}): `);
1164
- let selected = parseSharedRegion(answer.trim() || fallback.id);
1198
+ let selected = getSharedRegion(answer.trim() || fallback.id);
1165
1199
  if (selected) return selected;
1166
1200
  deps.stdout.log(`Invalid region. Choose from: ${labels}`);
1167
1201
  }
@@ -1194,21 +1228,50 @@ function assertCliVersion(version, range) {
1194
1228
  }
1195
1229
 
1196
1230
  // src/utils/deps.ts
1197
- var import_promises7 = require("fs/promises");
1231
+ var import_promises8 = require("fs/promises");
1198
1232
  var import_node_process5 = __toESM(require("process"), 1);
1199
1233
 
1200
1234
  // src/providers/turso.ts
1201
- var import_promises5 = require("fs/promises");
1202
- var import_node_path4 = require("path");
1235
+ var import_promises6 = require("fs/promises");
1236
+ var import_node_path5 = require("path");
1203
1237
  var import_node_process3 = __toESM(require("process"), 1);
1238
+
1239
+ // src/utils/env.ts
1240
+ function getEnvVar(file, key) {
1241
+ return file.match(new RegExp(`^${key}=(.*)$`, "m"))?.[1]?.trim() ?? "";
1242
+ }
1243
+ function setEnvVar(file, key, value) {
1244
+ if (file.match(new RegExp(`^${key}=.*$`, "m"))) {
1245
+ return file.replace(new RegExp(`^${key}=.*$`, "m"), `${key}=${value}`);
1246
+ }
1247
+ return `${file.trimEnd()}
1248
+ ${key}=${value}
1249
+ `;
1250
+ }
1251
+ function getRequiredEnvVar(file, key) {
1252
+ let value = getEnvVar(file, key);
1253
+ if (!value) {
1254
+ throw new Error(
1255
+ `Missing ${key} in .env. Provision Turso and prep the app first.`
1256
+ );
1257
+ }
1258
+ return value;
1259
+ }
1260
+
1261
+ // src/utils/table.ts
1262
+ function parseFirstColumn(table) {
1263
+ return table.trim().split("\n").slice(1).map((line) => line.trim()).filter(Boolean).map((line) => line.split(/\s+/)[0]).filter(Boolean);
1264
+ }
1265
+
1266
+ // src/providers/turso.ts
1204
1267
  var defaultDeps2 = {
1205
1268
  run,
1206
1269
  runOutput,
1207
1270
  promptConfirm,
1208
1271
  promptText,
1209
- readFile: import_promises5.readFile,
1210
- writeFile: import_promises5.writeFile,
1211
- cp: import_promises5.cp,
1272
+ readFile: import_promises6.readFile,
1273
+ writeFile: import_promises6.writeFile,
1274
+ cp: import_promises6.cp,
1212
1275
  stdout: console,
1213
1276
  isTTY: import_node_process3.default.stdin.isTTY
1214
1277
  };
@@ -1218,8 +1281,8 @@ async function provisionTurso(cwd, region, deps = defaultDeps2) {
1218
1281
  "`gistajs provision turso` requires an interactive terminal"
1219
1282
  );
1220
1283
  }
1221
- let envPath = (0, import_node_path4.join)(cwd, ".env");
1222
- let envExamplePath = (0, import_node_path4.join)(cwd, ".env.example");
1284
+ let envPath = (0, import_node_path5.join)(cwd, ".env");
1285
+ let envExamplePath = (0, import_node_path5.join)(cwd, ".env.example");
1223
1286
  let file = await ensureEnvFile(envPath, envExamplePath, deps);
1224
1287
  let currentUrl = getEnvVar(file, "DB_URL");
1225
1288
  let currentToken = getEnvVar(file, "DB_AUTH_TOKEN");
@@ -1243,7 +1306,7 @@ async function provisionTurso(cwd, region, deps = defaultDeps2) {
1243
1306
  }
1244
1307
  }
1245
1308
  await assertCommand(
1246
- deps,
1309
+ deps.run,
1247
1310
  cwd,
1248
1311
  "turso",
1249
1312
  ["auth", "status"],
@@ -1275,7 +1338,7 @@ Available orgs: ${slugs.join(", ")}`);
1275
1338
  let groups = parseGroupTable(
1276
1339
  await deps.runOutput("turso", ["group", "list"], cwd)
1277
1340
  );
1278
- let fallback = (0, import_node_path4.basename)(cwd).replaceAll(".", "-");
1341
+ let fallback = (0, import_node_path5.basename)(cwd).replaceAll(".", "-");
1279
1342
  let name = "";
1280
1343
  while (true) {
1281
1344
  let dbName = await deps.promptText(`Database name (${fallback}): `);
@@ -1332,31 +1395,6 @@ async function ensureEnvFile(envPath, envExamplePath, deps) {
1332
1395
  deps.stdout.log("Created empty .env");
1333
1396
  return "";
1334
1397
  }
1335
- async function assertCommand(deps, cwd, command, args, failureMessage) {
1336
- try {
1337
- await deps.run(command, args, cwd);
1338
- } catch (error) {
1339
- let code = error.code;
1340
- if (code === "ENOENT") {
1341
- throw new Error(`Required command not found: ${command}`);
1342
- }
1343
- throw new Error(failureMessage);
1344
- }
1345
- }
1346
- function getEnvVar(file, key) {
1347
- return file.match(new RegExp(`^${key}=(.*)$`, "m"))?.[1]?.trim() ?? "";
1348
- }
1349
- function setEnvVar(file, key, value) {
1350
- if (file.match(new RegExp(`^${key}=.*$`, "m"))) {
1351
- return file.replace(new RegExp(`^${key}=.*$`, "m"), `${key}=${value}`);
1352
- }
1353
- return `${file.trimEnd()}
1354
- ${key}=${value}
1355
- `;
1356
- }
1357
- function parseFirstColumn(table) {
1358
- return table.trim().split("\n").slice(1).map((line) => line.trim()).filter(Boolean).map((line) => line.split(/\s+/)[0]).filter(Boolean);
1359
- }
1360
1398
  function parseGroupTable(table) {
1361
1399
  return table.trim().split("\n").slice(1).map((line) => line.trim()).filter(Boolean).map((line) => {
1362
1400
  let parts = line.split(/\s+/);
@@ -1386,14 +1424,14 @@ function matchesRegion(location, region) {
1386
1424
 
1387
1425
  // src/providers/vercel.ts
1388
1426
  var import_node_fs = require("fs");
1389
- var import_promises6 = require("fs/promises");
1390
- var import_node_path5 = require("path");
1427
+ var import_promises7 = require("fs/promises");
1428
+ var import_node_path6 = require("path");
1391
1429
  var import_node_process4 = __toESM(require("process"), 1);
1392
1430
  var defaultDeps3 = {
1393
1431
  run,
1394
1432
  runInput,
1395
1433
  runOutput,
1396
- readFile: import_promises6.readFile,
1434
+ readFile: import_promises7.readFile,
1397
1435
  existsSync: import_node_fs.existsSync,
1398
1436
  stdout: console,
1399
1437
  isTTY: import_node_process4.default.stdin.isTTY
@@ -1405,23 +1443,23 @@ async function provisionVercel(cwd, region, deps = defaultDeps3) {
1405
1443
  "`gistajs provision vercel` requires an interactive terminal"
1406
1444
  );
1407
1445
  }
1408
- await assertCommand2(
1409
- deps,
1446
+ await assertCommand(
1447
+ deps.run,
1410
1448
  cwd,
1411
1449
  "vercel",
1412
1450
  ["whoami"],
1413
1451
  "Not logged in. Run `vercel login` first."
1414
1452
  );
1415
- if (!deps.existsSync((0, import_node_path5.join)(cwd, ".vercel", "project.json"))) {
1453
+ if (!deps.existsSync((0, import_node_path6.join)(cwd, ".vercel", "project.json"))) {
1416
1454
  deps.stdout.log("Linking this directory to a Vercel project...");
1417
1455
  await deps.run("vercel", ["link"], cwd);
1418
1456
  }
1419
1457
  deps.stdout.log(`Setting the Vercel function region to ${region.label}...`);
1420
1458
  await deps.run("vercel", ["--regions", region.vercel], cwd);
1421
- let envPath = (0, import_node_path5.join)(cwd, ".env");
1459
+ let envPath = (0, import_node_path6.join)(cwd, ".env");
1422
1460
  let file = await readEnvFile(envPath, deps);
1423
1461
  let values = requiredEnvVars.map((key) => [key, getRequiredEnvVar(file, key)]);
1424
- let existing = parseFirstColumn2(
1462
+ let existing = parseFirstColumn(
1425
1463
  await deps.runOutput("vercel", ["env", "ls", "production"], cwd)
1426
1464
  );
1427
1465
  for (let [key, value] of values) {
@@ -1447,29 +1485,6 @@ async function readEnvFile(envPath, deps) {
1447
1485
  throw error;
1448
1486
  }
1449
1487
  }
1450
- async function assertCommand2(deps, cwd, command, args, failureMessage) {
1451
- try {
1452
- await deps.run(command, args, cwd);
1453
- } catch (error) {
1454
- let code = error.code;
1455
- if (code === "ENOENT") {
1456
- throw new Error(`Required command not found: ${command}`);
1457
- }
1458
- throw new Error(failureMessage);
1459
- }
1460
- }
1461
- function getRequiredEnvVar(file, key) {
1462
- let value = file.match(new RegExp(`^${key}=(.*)$`, "m"))?.[1]?.trim();
1463
- if (!value) {
1464
- throw new Error(
1465
- `Missing ${key} in .env. Provision Turso and prep the app first.`
1466
- );
1467
- }
1468
- return value;
1469
- }
1470
- function parseFirstColumn2(table) {
1471
- return table.trim().split("\n").slice(1).map((line) => line.trim()).filter(Boolean).map((line) => line.split(/\s+/)[0]).filter(Boolean);
1472
- }
1473
1488
 
1474
1489
  // src/utils/catalog.ts
1475
1490
  var DEFAULT_CATALOG_URL = "https://gistajs.com/manifests/starters.json";
@@ -1506,7 +1521,7 @@ async function readCliVersion() {
1506
1521
  if (false) {
1507
1522
  throw new Error("Could not resolve the installed gistajs version");
1508
1523
  }
1509
- return "0.1.4";
1524
+ return "0.1.7";
1510
1525
  }
1511
1526
  async function readDefaultProvisionRegion() {
1512
1527
  try {
@@ -1530,8 +1545,8 @@ var defaultDeps4 = {
1530
1545
  promptForStarter,
1531
1546
  promptConfirm,
1532
1547
  promptText,
1533
- readFile: import_promises7.readFile,
1534
- writeFile: import_promises7.writeFile,
1548
+ readFile: import_promises8.readFile,
1549
+ writeFile: import_promises8.writeFile,
1535
1550
  stdout: console,
1536
1551
  cwd: import_node_process5.default.cwd(),
1537
1552
  getCliVersion: readCliVersion,
package/dist/index.d.ts CHANGED
@@ -69,7 +69,9 @@ declare function readGitConfig(cwd: string, key: string): string;
69
69
 
70
70
  type CreateProjectDeps = {
71
71
  initGit: typeof initGit;
72
+ promptConfirm: typeof promptConfirm;
72
73
  run: typeof run;
74
+ stdout: Pick<typeof console, 'log'>;
73
75
  };
74
76
  declare function createProject(starter: StarterSpec, options: CreateOptions, deps?: CreateProjectDeps): Promise<string>;
75
77
 
package/dist/index.js CHANGED
@@ -151,6 +151,17 @@ async function runInput(command, args, cwd, input) {
151
151
  input
152
152
  });
153
153
  }
154
+ async function assertCommand(runFn, cwd, command, args, failureMessage) {
155
+ try {
156
+ await runFn(command, args, cwd);
157
+ } catch (error) {
158
+ let code = error.code;
159
+ if (code === "ENOENT") {
160
+ throw new Error(`Required command not found: ${command}`);
161
+ }
162
+ throw new Error(failureMessage);
163
+ }
164
+ }
154
165
  async function exec(command, args, cwd, options) {
155
166
  return await new Promise((resolve2, reject) => {
156
167
  let { capture, input } = options;
@@ -258,7 +269,9 @@ var UsageError = class extends Error {
258
269
  // src/commands/create.ts
259
270
  var defaultCreateProjectDeps = {
260
271
  initGit,
261
- run
272
+ promptConfirm,
273
+ run,
274
+ stdout: console
262
275
  };
263
276
  function parseCreateArgs(argv) {
264
277
  let options = {};
@@ -343,6 +356,9 @@ async function createProject(starter, options, deps = defaultCreateProjectDeps)
343
356
  }
344
357
  if (options.install !== false) {
345
358
  await installDependencies(root, deps.run);
359
+ if (await hasPrepScript(root)) {
360
+ await runPrepScript(root, deps);
361
+ }
346
362
  }
347
363
  return root;
348
364
  } finally {
@@ -405,8 +421,24 @@ async function rewritePackageName(root, projectName) {
405
421
  }
406
422
  }
407
423
  async function installDependencies(root, runCommand) {
424
+ await runPnpmCommand(root, runCommand, "install");
425
+ }
426
+ async function runPrepScript(root, deps) {
427
+ let shouldRun = await deps.promptConfirm("Run project setup now? (Y/n) ");
428
+ if (!shouldRun) return;
429
+ try {
430
+ await runPnpmCommand(root, deps.run, "prep");
431
+ } catch (error) {
432
+ let detail = error instanceof Error ? error.message : String(error);
433
+ deps.stdout.log(`Warning: Project setup failed. ${detail}`);
434
+ deps.stdout.log(
435
+ `Run ${c.path(`cd ${root} && pnpm prep`)} to retry project setup.`
436
+ );
437
+ }
438
+ }
439
+ async function runPnpmCommand(root, runCommand, script) {
408
440
  try {
409
- await runCommand("corepack", ["pnpm", "install"], root);
441
+ await runCommand("corepack", ["pnpm", script], root);
410
442
  return;
411
443
  } catch (error) {
412
444
  if (!isCommandNotFound(error)) {
@@ -414,16 +446,22 @@ async function installDependencies(root, runCommand) {
414
446
  }
415
447
  }
416
448
  try {
417
- await runCommand("pnpm", ["install"], root);
449
+ await runCommand("pnpm", [script], root);
418
450
  } catch (error) {
419
451
  if (!isCommandNotFound(error)) {
420
452
  throw error;
421
453
  }
422
454
  throw new Error(
423
- "Could not install dependencies because neither corepack nor pnpm is available. Install Node.js with corepack enabled, or install pnpm and rerun the command."
455
+ `Could not run pnpm ${script} because neither corepack nor pnpm is available. Install Node.js with corepack enabled, or install pnpm and rerun the command.`
424
456
  );
425
457
  }
426
458
  }
459
+ async function hasPrepScript(root) {
460
+ let path = join(root, "package.json");
461
+ let source = await readFile(path, "utf8");
462
+ let pkg = JSON.parse(source);
463
+ return typeof pkg.scripts?.prep === "string" && pkg.scripts.prep.length > 0;
464
+ }
427
465
  function getErrorCode(error) {
428
466
  return error?.code;
429
467
  }
@@ -538,6 +576,8 @@ function getHelpText(command) {
538
576
  ` ${c.bold("Examples:")}`,
539
577
  ` ${c.dim("$")} gistajs create my-app`,
540
578
  ` ${c.dim("$")} gistajs create my-app --starter website`,
579
+ "",
580
+ ` Some starters offer an optional setup step after dependencies install.`,
541
581
  ""
542
582
  ].join("\n");
543
583
  }
@@ -604,6 +644,9 @@ function getHelpText(command) {
604
644
  ` ${c.bold("Examples:")}`,
605
645
  ` ${c.dim("$")} gistajs create my-app`,
606
646
  ` ${c.dim("$")} gistajs create my-app --starter website`,
647
+ "",
648
+ ` Some starters offer an optional setup step after dependencies install.`,
649
+ "",
607
650
  ` ${c.dim("$")} gistajs diff --latest`,
608
651
  ` ${c.dim("$")} gistajs diff --latest --stat`,
609
652
  ` ${c.dim("$")} gistajs diff auth 2026-03-28-001 2026-03-29-001`,
@@ -782,8 +825,41 @@ function getErrorMessage(error) {
782
825
  }
783
826
 
784
827
  // src/commands/pin.ts
785
- import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
828
+ import { writeFile as writeFile2 } from "fs/promises";
829
+ import { join as join4 } from "path";
830
+
831
+ // src/utils/package.ts
832
+ import { readFile as readFile2 } from "fs/promises";
786
833
  import { join as join3 } from "path";
834
+ async function readProjectPackage(root, readFileFn = (p, e) => readFile2(p, e)) {
835
+ let path = join3(root, "package.json");
836
+ let source;
837
+ try {
838
+ source = await readFileFn(path, "utf8");
839
+ } catch (error) {
840
+ let code = error.code;
841
+ if (code === "ENOENT") {
842
+ throw new Error(
843
+ "No package.json found. Run this from a Gista.js project directory."
844
+ );
845
+ }
846
+ throw error;
847
+ }
848
+ try {
849
+ let pkg = JSON.parse(source);
850
+ if (!pkg || typeof pkg !== "object" || Array.isArray(pkg)) {
851
+ throw new Error("package.json must contain a JSON object");
852
+ }
853
+ return pkg;
854
+ } catch (error) {
855
+ if (error instanceof SyntaxError) {
856
+ throw new Error("Could not parse package.json in the current directory.");
857
+ }
858
+ throw error;
859
+ }
860
+ }
861
+
862
+ // src/commands/pin.ts
787
863
  function parsePinArgs(argv) {
788
864
  let options = {};
789
865
  for (let index = 0; index < argv.length; index += 1) {
@@ -845,34 +921,12 @@ async function writeProjectStarterPin(root, pin) {
845
921
  let gistajs = pkg.gistajs && typeof pkg.gistajs === "object" && !Array.isArray(pkg.gistajs) ? { ...pkg.gistajs } : {};
846
922
  gistajs.pin = parsed.pin;
847
923
  await writeFile2(
848
- join3(root, "package.json"),
924
+ join4(root, "package.json"),
849
925
  `${JSON.stringify({ ...pkg, gistajs }, null, 2)}
850
926
  `
851
927
  );
852
928
  return parsed;
853
929
  }
854
- async function readProjectPackage(root) {
855
- let path = join3(root, "package.json");
856
- let source;
857
- try {
858
- source = await readFile2(path, "utf8");
859
- } catch (error) {
860
- if (error?.code === "ENOENT") {
861
- throw new Error(`Could not find package.json in ${root}`);
862
- }
863
- throw error;
864
- }
865
- try {
866
- let pkg = JSON.parse(source);
867
- if (!pkg || typeof pkg !== "object" || Array.isArray(pkg)) {
868
- throw new Error("package.json must contain a JSON object");
869
- }
870
- return pkg;
871
- } catch (error) {
872
- let message = error instanceof Error ? error.message : String(error);
873
- throw new Error(`Invalid package.json: ${message}`);
874
- }
875
- }
876
930
 
877
931
  // src/providers/regions.ts
878
932
  var sharedRegions = [
@@ -916,11 +970,6 @@ function getSharedRegion(value) {
916
970
  function getDefaultSharedRegion() {
917
971
  return getSharedRegion(defaultSharedRegionId);
918
972
  }
919
- function parseSharedRegion(value) {
920
- let normalized = value.trim().toLowerCase();
921
- if (!normalized) return null;
922
- return getSharedRegion(normalized);
923
- }
924
973
 
925
974
  // src/utils/version.ts
926
975
  function satisfiesVersion(version, range) {
@@ -1050,7 +1099,7 @@ async function runProviderProvision(provider, deps) {
1050
1099
  if (provider !== "turso" && provider !== "vercel") {
1051
1100
  throw new UsageError(`Unknown provider: ${provider}`, "provision");
1052
1101
  }
1053
- let pkg = await readProjectPackage2(deps);
1102
+ let pkg = await readProvisionPackage(deps);
1054
1103
  let region = await resolveProjectRegion(pkg, deps);
1055
1104
  await writeProjectRegion(pkg, region.id, deps);
1056
1105
  if (provider === "turso") {
@@ -1063,7 +1112,7 @@ async function runProviderProvision(provider, deps) {
1063
1112
  }
1064
1113
  }
1065
1114
  async function runProjectProvision(deps) {
1066
- let pkg = await readProjectPackage2(deps);
1115
+ let pkg = await readProvisionPackage(deps);
1067
1116
  let providers = pkg.gistajs?.providers;
1068
1117
  if (!providers?.length) {
1069
1118
  throw new Error(
@@ -1101,23 +1150,8 @@ async function runProjectProvision(deps) {
1101
1150
  }
1102
1151
  printSummary(summary, deps);
1103
1152
  }
1104
- async function readProjectPackage2(deps) {
1105
- let path = `${deps.cwd}/package.json`;
1106
- try {
1107
- let file = await deps.readFile(path, "utf8");
1108
- return JSON.parse(file);
1109
- } catch (error) {
1110
- let code = error.code;
1111
- if (code === "ENOENT") {
1112
- throw new Error(
1113
- "No package.json found. Run this from a Gista.js project directory."
1114
- );
1115
- }
1116
- if (error instanceof SyntaxError) {
1117
- throw new Error("Could not parse package.json in the current directory.");
1118
- }
1119
- throw error;
1120
- }
1153
+ async function readProvisionPackage(deps) {
1154
+ return await readProjectPackage(deps.cwd, deps.readFile);
1121
1155
  }
1122
1156
  async function writeProjectRegion(pkg, region, deps) {
1123
1157
  let nextPkg = {
@@ -1146,7 +1180,7 @@ async function resolveProjectRegion(pkg, deps) {
1146
1180
  }
1147
1181
  while (true) {
1148
1182
  let answer = await deps.promptText(`Region (${fallback.label}): `);
1149
- let selected = parseSharedRegion(answer.trim() || fallback.id);
1183
+ let selected = getSharedRegion(answer.trim() || fallback.id);
1150
1184
  if (selected) return selected;
1151
1185
  deps.stdout.log(`Invalid region. Choose from: ${labels}`);
1152
1186
  }
@@ -1184,8 +1218,37 @@ import process5 from "process";
1184
1218
 
1185
1219
  // src/providers/turso.ts
1186
1220
  import { cp as cp2, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
1187
- import { basename as basename2, join as join4 } from "path";
1221
+ import { basename as basename2, join as join5 } from "path";
1188
1222
  import process3 from "process";
1223
+
1224
+ // src/utils/env.ts
1225
+ function getEnvVar(file, key) {
1226
+ return file.match(new RegExp(`^${key}=(.*)$`, "m"))?.[1]?.trim() ?? "";
1227
+ }
1228
+ function setEnvVar(file, key, value) {
1229
+ if (file.match(new RegExp(`^${key}=.*$`, "m"))) {
1230
+ return file.replace(new RegExp(`^${key}=.*$`, "m"), `${key}=${value}`);
1231
+ }
1232
+ return `${file.trimEnd()}
1233
+ ${key}=${value}
1234
+ `;
1235
+ }
1236
+ function getRequiredEnvVar(file, key) {
1237
+ let value = getEnvVar(file, key);
1238
+ if (!value) {
1239
+ throw new Error(
1240
+ `Missing ${key} in .env. Provision Turso and prep the app first.`
1241
+ );
1242
+ }
1243
+ return value;
1244
+ }
1245
+
1246
+ // src/utils/table.ts
1247
+ function parseFirstColumn(table) {
1248
+ return table.trim().split("\n").slice(1).map((line) => line.trim()).filter(Boolean).map((line) => line.split(/\s+/)[0]).filter(Boolean);
1249
+ }
1250
+
1251
+ // src/providers/turso.ts
1189
1252
  var defaultDeps2 = {
1190
1253
  run,
1191
1254
  runOutput,
@@ -1203,8 +1266,8 @@ async function provisionTurso(cwd, region, deps = defaultDeps2) {
1203
1266
  "`gistajs provision turso` requires an interactive terminal"
1204
1267
  );
1205
1268
  }
1206
- let envPath = join4(cwd, ".env");
1207
- let envExamplePath = join4(cwd, ".env.example");
1269
+ let envPath = join5(cwd, ".env");
1270
+ let envExamplePath = join5(cwd, ".env.example");
1208
1271
  let file = await ensureEnvFile(envPath, envExamplePath, deps);
1209
1272
  let currentUrl = getEnvVar(file, "DB_URL");
1210
1273
  let currentToken = getEnvVar(file, "DB_AUTH_TOKEN");
@@ -1228,7 +1291,7 @@ async function provisionTurso(cwd, region, deps = defaultDeps2) {
1228
1291
  }
1229
1292
  }
1230
1293
  await assertCommand(
1231
- deps,
1294
+ deps.run,
1232
1295
  cwd,
1233
1296
  "turso",
1234
1297
  ["auth", "status"],
@@ -1317,31 +1380,6 @@ async function ensureEnvFile(envPath, envExamplePath, deps) {
1317
1380
  deps.stdout.log("Created empty .env");
1318
1381
  return "";
1319
1382
  }
1320
- async function assertCommand(deps, cwd, command, args, failureMessage) {
1321
- try {
1322
- await deps.run(command, args, cwd);
1323
- } catch (error) {
1324
- let code = error.code;
1325
- if (code === "ENOENT") {
1326
- throw new Error(`Required command not found: ${command}`);
1327
- }
1328
- throw new Error(failureMessage);
1329
- }
1330
- }
1331
- function getEnvVar(file, key) {
1332
- return file.match(new RegExp(`^${key}=(.*)$`, "m"))?.[1]?.trim() ?? "";
1333
- }
1334
- function setEnvVar(file, key, value) {
1335
- if (file.match(new RegExp(`^${key}=.*$`, "m"))) {
1336
- return file.replace(new RegExp(`^${key}=.*$`, "m"), `${key}=${value}`);
1337
- }
1338
- return `${file.trimEnd()}
1339
- ${key}=${value}
1340
- `;
1341
- }
1342
- function parseFirstColumn(table) {
1343
- return table.trim().split("\n").slice(1).map((line) => line.trim()).filter(Boolean).map((line) => line.split(/\s+/)[0]).filter(Boolean);
1344
- }
1345
1383
  function parseGroupTable(table) {
1346
1384
  return table.trim().split("\n").slice(1).map((line) => line.trim()).filter(Boolean).map((line) => {
1347
1385
  let parts = line.split(/\s+/);
@@ -1372,7 +1410,7 @@ function matchesRegion(location, region) {
1372
1410
  // src/providers/vercel.ts
1373
1411
  import { existsSync } from "fs";
1374
1412
  import { readFile as readFileFs } from "fs/promises";
1375
- import { join as join5 } from "path";
1413
+ import { join as join6 } from "path";
1376
1414
  import process4 from "process";
1377
1415
  var defaultDeps3 = {
1378
1416
  run,
@@ -1390,23 +1428,23 @@ async function provisionVercel(cwd, region, deps = defaultDeps3) {
1390
1428
  "`gistajs provision vercel` requires an interactive terminal"
1391
1429
  );
1392
1430
  }
1393
- await assertCommand2(
1394
- deps,
1431
+ await assertCommand(
1432
+ deps.run,
1395
1433
  cwd,
1396
1434
  "vercel",
1397
1435
  ["whoami"],
1398
1436
  "Not logged in. Run `vercel login` first."
1399
1437
  );
1400
- if (!deps.existsSync(join5(cwd, ".vercel", "project.json"))) {
1438
+ if (!deps.existsSync(join6(cwd, ".vercel", "project.json"))) {
1401
1439
  deps.stdout.log("Linking this directory to a Vercel project...");
1402
1440
  await deps.run("vercel", ["link"], cwd);
1403
1441
  }
1404
1442
  deps.stdout.log(`Setting the Vercel function region to ${region.label}...`);
1405
1443
  await deps.run("vercel", ["--regions", region.vercel], cwd);
1406
- let envPath = join5(cwd, ".env");
1444
+ let envPath = join6(cwd, ".env");
1407
1445
  let file = await readEnvFile(envPath, deps);
1408
1446
  let values = requiredEnvVars.map((key) => [key, getRequiredEnvVar(file, key)]);
1409
- let existing = parseFirstColumn2(
1447
+ let existing = parseFirstColumn(
1410
1448
  await deps.runOutput("vercel", ["env", "ls", "production"], cwd)
1411
1449
  );
1412
1450
  for (let [key, value] of values) {
@@ -1432,29 +1470,6 @@ async function readEnvFile(envPath, deps) {
1432
1470
  throw error;
1433
1471
  }
1434
1472
  }
1435
- async function assertCommand2(deps, cwd, command, args, failureMessage) {
1436
- try {
1437
- await deps.run(command, args, cwd);
1438
- } catch (error) {
1439
- let code = error.code;
1440
- if (code === "ENOENT") {
1441
- throw new Error(`Required command not found: ${command}`);
1442
- }
1443
- throw new Error(failureMessage);
1444
- }
1445
- }
1446
- function getRequiredEnvVar(file, key) {
1447
- let value = file.match(new RegExp(`^${key}=(.*)$`, "m"))?.[1]?.trim();
1448
- if (!value) {
1449
- throw new Error(
1450
- `Missing ${key} in .env. Provision Turso and prep the app first.`
1451
- );
1452
- }
1453
- return value;
1454
- }
1455
- function parseFirstColumn2(table) {
1456
- return table.trim().split("\n").slice(1).map((line) => line.trim()).filter(Boolean).map((line) => line.split(/\s+/)[0]).filter(Boolean);
1457
- }
1458
1473
 
1459
1474
  // src/utils/catalog.ts
1460
1475
  var DEFAULT_CATALOG_URL = "https://gistajs.com/manifests/starters.json";
@@ -1491,7 +1506,7 @@ async function readCliVersion() {
1491
1506
  if (false) {
1492
1507
  throw new Error("Could not resolve the installed gistajs version");
1493
1508
  }
1494
- return "0.1.4";
1509
+ return "0.1.7";
1495
1510
  }
1496
1511
  async function readDefaultProvisionRegion() {
1497
1512
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gistajs",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Scaffold and manage Gista.js starter projects",
5
5
  "keywords": [
6
6
  "cli",
@@ -53,7 +53,7 @@
53
53
  "test": "vitest run",
54
54
  "test:watch": "vitest",
55
55
  "typecheck": "tsc -b",
56
- "release:prepare": "pnpm typecheck && pnpm build && pnpm test",
56
+ "release:prepare": "pnpm typecheck && pnpm test",
57
57
  "np": "pnpm release:prepare && np"
58
58
  }
59
59
  }