create-sitekick 0.2.0 → 0.2.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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +101 -88
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -110,7 +110,7 @@ The CLI handles authentication and resource creation for each service:
110
110
  | **Neon** (`neonctl`) | Provisions a Postgres database, returns the connection string |
111
111
  | **Vercel** (`vercel`) | Creates a project, links to GitHub, pushes env vars |
112
112
  | **Sanity** (`sanity`) | Creates a Sanity project and production dataset |
113
- | **OpenAI** | Stores your API key for AI-powered alt text generation |
113
+ | **OpenAI** | Stores your API key for AI-powered features |
114
114
 
115
115
  Each step checks if the CLI tool is installed (and offers to install it), verifies authentication (and prompts login if needed), and asks whether to create a new resource or link an existing one.
116
116
 
package/dist/index.js CHANGED
@@ -1269,57 +1269,100 @@ function runInteractive(cmd, opts) {
1269
1269
  function isInstalled(cmd) {
1270
1270
  return run(`which ${cmd}`) !== null;
1271
1271
  }
1272
+ var scaffoldedDir = null;
1273
+ function abort(message = "Cancelled.") {
1274
+ if (scaffoldedDir && fs.existsSync(scaffoldedDir)) {
1275
+ fs.rmSync(scaffoldedDir, { recursive: true, force: true });
1276
+ }
1277
+ Ne(message);
1278
+ process.exit(0);
1279
+ }
1272
1280
  async function cancelOrContinue(message) {
1273
1281
  const result = await Re({ message, initialValue: true });
1274
- if (Ct(result)) {
1275
- Ne("Cancelled.");
1276
- process.exit(0);
1277
- }
1282
+ if (Ct(result))
1283
+ abort();
1278
1284
  return result;
1279
1285
  }
1280
1286
  var TOOLS = {
1281
1287
  gh: {
1282
1288
  name: "GitHub CLI",
1283
1289
  cmd: "gh",
1284
- installHint: "Install from https://cli.github.com or run: brew install gh"
1290
+ brewPkg: "gh"
1285
1291
  },
1286
1292
  vercel: {
1287
1293
  name: "Vercel CLI",
1288
1294
  cmd: "vercel",
1289
- installCmd: "bun add -g vercel"
1295
+ bunPkg: "vercel"
1290
1296
  },
1291
1297
  neonctl: {
1292
1298
  name: "Neon CLI",
1293
1299
  cmd: "neonctl",
1294
- installCmd: "bun add -g neonctl"
1300
+ bunPkg: "neonctl"
1295
1301
  },
1296
1302
  sanity: {
1297
1303
  name: "Sanity CLI",
1298
1304
  cmd: "sanity",
1299
- installCmd: "bun add -g sanity"
1305
+ bunPkg: "sanity"
1300
1306
  }
1301
1307
  };
1308
+ async function ensureBrew(s) {
1309
+ if (isInstalled("brew"))
1310
+ return true;
1311
+ const shouldInstall = await cancelOrContinue("Homebrew is needed to install some tools. Install it now?");
1312
+ if (!shouldInstall)
1313
+ return false;
1314
+ s.start("Installing Homebrew...");
1315
+ try {
1316
+ execSync('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"', { stdio: "inherit" });
1317
+ } catch {
1318
+ s.stop("Failed to install Homebrew");
1319
+ return false;
1320
+ }
1321
+ const brewPaths = ["/opt/homebrew/bin/brew", "/usr/local/bin/brew"];
1322
+ for (const brewPath of brewPaths) {
1323
+ try {
1324
+ const shellEnv = execSync(`${brewPath} shellenv`, { encoding: "utf-8" });
1325
+ for (const line of shellEnv.split(`
1326
+ `)) {
1327
+ const match = line.match(/export\s+PATH="([^"]+)"/);
1328
+ if (match)
1329
+ process.env.PATH = `${match[1]}:${process.env.PATH}`;
1330
+ }
1331
+ break;
1332
+ } catch {}
1333
+ }
1334
+ if (!isInstalled("brew")) {
1335
+ s.stop("Homebrew installed but not found on PATH. Restart your terminal and try again.");
1336
+ return false;
1337
+ }
1338
+ s.stop("Homebrew installed");
1339
+ return true;
1340
+ }
1302
1341
  async function ensureTool(key, s) {
1303
1342
  const tool = TOOLS[key];
1304
1343
  if (isInstalled(tool.cmd))
1305
1344
  return true;
1306
- if (tool.installCmd) {
1307
- const shouldInstall = await cancelOrContinue(`${tool.name} (${tool.cmd}) is not installed. Install it now?`);
1308
- if (!shouldInstall)
1309
- return false;
1310
- s.start(`Installing ${tool.name}...`);
1311
- const result = run(tool.installCmd);
1312
- if (result === null) {
1313
- s.stop(`Failed to install ${tool.name}`);
1345
+ const shouldInstall = await cancelOrContinue(`${tool.name} (${tool.cmd}) is not installed. Install it now?`);
1346
+ if (!shouldInstall)
1347
+ return false;
1348
+ let installCmd;
1349
+ if (tool.bunPkg) {
1350
+ installCmd = `bun add -g ${tool.bunPkg}`;
1351
+ } else if (tool.brewPkg) {
1352
+ if (!await ensureBrew(s))
1314
1353
  return false;
1315
- }
1316
- s.stop(`${tool.name} installed`);
1317
- return true;
1354
+ installCmd = `brew install ${tool.brewPkg}`;
1355
+ } else {
1356
+ return false;
1357
+ }
1358
+ s.start(`Installing ${tool.name}...`);
1359
+ const result = run(installCmd);
1360
+ if (result === null) {
1361
+ s.stop(`Failed to install ${tool.name}`);
1362
+ return false;
1318
1363
  }
1319
- R2.warn(`${tool.name} (${import_picocolors3.default.cyan(tool.cmd)}) is not installed.
1320
- ${tool.installHint}`);
1321
- const ready = await cancelOrContinue(`Have you installed ${tool.cmd}? Continue?`);
1322
- return ready && isInstalled(tool.cmd);
1364
+ s.stop(`${tool.name} installed`);
1365
+ return true;
1323
1366
  }
1324
1367
  async function ensureAuth(toolName, checkCmd, authCmd, s) {
1325
1368
  s.start(`Checking ${toolName} authentication...`);
@@ -1360,10 +1403,8 @@ async function selectGitHubOrg() {
1360
1403
  ...orgs.map((org) => ({ value: org, label: org }))
1361
1404
  ]
1362
1405
  });
1363
- if (Ct(selected)) {
1364
- Ne("Cancelled.");
1365
- process.exit(0);
1366
- }
1406
+ if (Ct(selected))
1407
+ abort();
1367
1408
  return selected === "__personal__" ? null : selected;
1368
1409
  }
1369
1410
  async function selectVercelTeam() {
@@ -1402,10 +1443,8 @@ async function selectVercelTeam() {
1402
1443
  }))
1403
1444
  ]
1404
1445
  });
1405
- if (Ct(selected)) {
1406
- Ne("Cancelled.");
1407
- process.exit(0);
1408
- }
1446
+ if (Ct(selected))
1447
+ abort();
1409
1448
  return selected === "__personal__" ? null : selected;
1410
1449
  }
1411
1450
  async function selectNeonOrg() {
@@ -1427,10 +1466,8 @@ async function selectNeonOrg() {
1427
1466
  }))
1428
1467
  ]
1429
1468
  });
1430
- if (Ct(selected)) {
1431
- Ne("Cancelled.");
1432
- process.exit(0);
1433
- }
1469
+ if (Ct(selected))
1470
+ abort();
1434
1471
  return selected === "__personal__" ? null : selected;
1435
1472
  } catch {
1436
1473
  return null;
@@ -1449,10 +1486,8 @@ async function setupGitHub(projectName, targetDir, s) {
1449
1486
  { value: "skip", label: "Skip GitHub setup" }
1450
1487
  ]
1451
1488
  });
1452
- if (Ct(existing)) {
1453
- Ne("Cancelled.");
1454
- process.exit(0);
1455
- }
1489
+ if (Ct(existing))
1490
+ abort();
1456
1491
  if (existing === "skip")
1457
1492
  return null;
1458
1493
  if (existing === "existing") {
@@ -1464,10 +1499,8 @@ async function setupGitHub(projectName, targetDir, s) {
1464
1499
  return "Repo URL is required";
1465
1500
  }
1466
1501
  });
1467
- if (Ct(repoUrl2)) {
1468
- Ne("Cancelled.");
1469
- process.exit(0);
1470
- }
1502
+ if (Ct(repoUrl2))
1503
+ abort();
1471
1504
  s.start("Linking to existing repo...");
1472
1505
  run(`git remote add origin ${repoUrl2}`, { cwd: targetDir });
1473
1506
  s.stop("Linked to existing repo");
@@ -1481,10 +1514,8 @@ async function setupGitHub(projectName, targetDir, s) {
1481
1514
  { value: "public", label: "Public" }
1482
1515
  ]
1483
1516
  });
1484
- if (Ct(visibility)) {
1485
- Ne("Cancelled.");
1486
- process.exit(0);
1487
- }
1517
+ if (Ct(visibility))
1518
+ abort();
1488
1519
  const repoFullName = ghOrg ? `${ghOrg}/${projectName}` : projectName;
1489
1520
  s.start(`Creating GitHub repo ${import_picocolors3.default.cyan(repoFullName)}...`);
1490
1521
  const result = run(`gh repo create ${repoFullName} --${visibility} --source . --remote origin`, { cwd: targetDir });
@@ -1509,10 +1540,8 @@ async function setupNeon(projectName, s) {
1509
1540
  { value: "skip", label: "Skip database setup" }
1510
1541
  ]
1511
1542
  });
1512
- if (Ct(existing)) {
1513
- Ne("Cancelled.");
1514
- process.exit(0);
1515
- }
1543
+ if (Ct(existing))
1544
+ abort();
1516
1545
  if (existing === "skip")
1517
1546
  return null;
1518
1547
  if (existing === "existing") {
@@ -1526,10 +1555,8 @@ async function setupNeon(projectName, s) {
1526
1555
  return "Must be a PostgreSQL connection string";
1527
1556
  }
1528
1557
  });
1529
- if (Ct(connStr)) {
1530
- Ne("Cancelled.");
1531
- process.exit(0);
1532
- }
1558
+ if (Ct(connStr))
1559
+ abort();
1533
1560
  return connStr;
1534
1561
  }
1535
1562
  const neonOrgId = await selectNeonOrg();
@@ -1583,10 +1610,8 @@ async function setupSanityProject(projectName, s) {
1583
1610
  { value: "skip", label: "Skip Sanity setup" }
1584
1611
  ]
1585
1612
  });
1586
- if (Ct(existing)) {
1587
- Ne("Cancelled.");
1588
- process.exit(0);
1589
- }
1613
+ if (Ct(existing))
1614
+ abort();
1590
1615
  if (existing === "skip")
1591
1616
  return null;
1592
1617
  if (existing === "existing") {
@@ -1598,19 +1623,15 @@ async function setupSanityProject(projectName, s) {
1598
1623
  return "Project ID is required";
1599
1624
  }
1600
1625
  });
1601
- if (Ct(projectId)) {
1602
- Ne("Cancelled.");
1603
- process.exit(0);
1604
- }
1626
+ if (Ct(projectId))
1627
+ abort();
1605
1628
  const dataset = await Ze({
1606
1629
  message: "Dataset name:",
1607
1630
  placeholder: "production",
1608
1631
  initialValue: "production"
1609
1632
  });
1610
- if (Ct(dataset)) {
1611
- Ne("Cancelled.");
1612
- process.exit(0);
1613
- }
1633
+ if (Ct(dataset))
1634
+ abort();
1614
1635
  return { projectId, dataset };
1615
1636
  }
1616
1637
  s.start(`Creating Sanity project ${import_picocolors3.default.cyan(projectName)}...`);
@@ -1647,10 +1668,8 @@ async function setupVercel(projectName, targetDir, repoUrl, s) {
1647
1668
  { value: "skip", label: "Skip Vercel setup" }
1648
1669
  ]
1649
1670
  });
1650
- if (Ct(existing)) {
1651
- Ne("Cancelled.");
1652
- process.exit(0);
1653
- }
1671
+ if (Ct(existing))
1672
+ abort();
1654
1673
  if (existing === "skip")
1655
1674
  return { url: null, scope: null };
1656
1675
  const vercelTeam = await selectVercelTeam();
@@ -1705,16 +1724,14 @@ async function pushEnvToVercel(envVars, targetDir, scope, s) {
1705
1724
  }
1706
1725
  async function askOpenAIKey() {
1707
1726
  const setup = await Je({
1708
- message: "OpenAI API key (for AI-generated alt text):",
1727
+ message: "OpenAI API key:",
1709
1728
  options: [
1710
1729
  { value: "enter", label: "Enter API key now" },
1711
1730
  { value: "skip", label: "Skip — set up later" }
1712
1731
  ]
1713
1732
  });
1714
- if (Ct(setup)) {
1715
- Ne("Cancelled.");
1716
- process.exit(0);
1717
- }
1733
+ if (Ct(setup))
1734
+ abort();
1718
1735
  if (setup === "skip")
1719
1736
  return null;
1720
1737
  const key = await Ze({
@@ -1727,10 +1744,8 @@ async function askOpenAIKey() {
1727
1744
  return "OpenAI keys start with sk-";
1728
1745
  }
1729
1746
  });
1730
- if (Ct(key)) {
1731
- Ne("Cancelled.");
1732
- process.exit(0);
1733
- }
1747
+ if (Ct(key))
1748
+ abort();
1734
1749
  return key;
1735
1750
  }
1736
1751
  function configureTemplate(targetDir, cms, projectName) {
@@ -1862,10 +1877,8 @@ async function main() {
1862
1877
  return "Use lowercase letters, numbers, and hyphens only";
1863
1878
  }
1864
1879
  });
1865
- if (Ct(projectName)) {
1866
- Ne("Cancelled.");
1867
- process.exit(0);
1868
- }
1880
+ if (Ct(projectName))
1881
+ abort();
1869
1882
  const cms = await Je({
1870
1883
  message: "Which CMS do you want to use?",
1871
1884
  options: [
@@ -1881,19 +1894,19 @@ async function main() {
1881
1894
  }
1882
1895
  ]
1883
1896
  });
1884
- if (Ct(cms)) {
1885
- Ne("Cancelled.");
1886
- process.exit(0);
1887
- }
1897
+ if (Ct(cms))
1898
+ abort();
1888
1899
  const targetDir = path.resolve(process.cwd(), projectName);
1889
1900
  if (fs.existsSync(targetDir)) {
1890
1901
  Ne(`Directory ${import_picocolors3.default.cyan(projectName)} already exists.`);
1891
1902
  process.exit(1);
1892
1903
  }
1893
1904
  const s = bt2();
1905
+ await ensureBrew(s);
1894
1906
  s.start("Cloning sitekick-starter...");
1895
1907
  execSync(`git clone --depth 1 https://github.com/sitekickcodes/sitekick-starter.git ${targetDir}`, { stdio: "pipe" });
1896
1908
  fs.rmSync(path.join(targetDir, ".git"), { recursive: true, force: true });
1909
+ scaffoldedDir = targetDir;
1897
1910
  s.stop("Cloned template");
1898
1911
  s.start(`Configuring for ${cms === "payload" ? "Payload CMS" : "Sanity"}...`);
1899
1912
  configureTemplate(targetDir, cms, projectName);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-sitekick",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Scaffold a new Sitekick project with your choice of CMS",
5
5
  "type": "module",
6
6
  "bin": {