superslate 0.9.0 → 0.10.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.
Files changed (2) hide show
  1. package/dist/cli.js +133 -58
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1266,20 +1266,31 @@ var LICENSE_KEY_VARIABLE2 = "SUPERSLATE_LICENSE_KEY";
1266
1266
  var LICENSE_KEY_PREFIX = "SUPERSLATE-";
1267
1267
 
1268
1268
  // src/core.ts
1269
+ var KEBAB_CASE_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
1270
+ var WHITESPACE_CHAR_RE = /\s/;
1271
+ var NPM_SCOPE_RE = /^@[a-z0-9][a-z0-9._-]*$/;
1272
+ var NEWLINE_CHAR_RE = /\r|\n/;
1273
+ var BRAND_NAME_RE = /^[\p{L}\p{N}](?:[\p{L}\p{N} ._-]*[\p{L}\p{N}])?$/u;
1274
+ var REGEX_SPECIAL_CHARS_RE = /[.*+?^${}()|[\]\\]/g;
1275
+ var ESCAPED_SOURCE_BRAND = SOURCE_BRAND_NAME.replace(REGEX_SPECIAL_CHARS_RE, "\\$&");
1276
+ var SOURCE_BRAND_RE = new RegExp(`\\b${ESCAPED_SOURCE_BRAND}\\b`, "gi");
1277
+ var ESCAPED_CLI_NAME = CLI_NAME2.replace(REGEX_SPECIAL_CHARS_RE, "\\$&");
1278
+ var SOURCE_DELIVERY_GUIDANCE_RE = new RegExp(`pnpm dlx ${ESCAPED_CLI_NAME}|bunx ${ESCAPED_CLI_NAME}|${ESCAPED_CLI_NAME} repository`, "i");
1279
+ var VENDOR_BOOTSTRAPPER_GUIDANCE_RE = new RegExp(`${ESCAPED_CLI_NAME}@\\d|public \`${ESCAPED_CLI_NAME}\` npm package`, "i");
1269
1280
  function validateAppName(value) {
1270
1281
  if (!value)
1271
1282
  return "App name is required.";
1272
- if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value)) {
1283
+ if (!KEBAB_CASE_RE.test(value)) {
1273
1284
  return "App name must be kebab-case (lowercase letters, numbers, and single hyphens).";
1274
1285
  }
1275
1286
  return;
1276
1287
  }
1277
1288
  function normalizeScope(value) {
1278
1289
  const scope = value.startsWith("@") ? value : `@${value}`;
1279
- if (/\s/.test(scope)) {
1290
+ if (WHITESPACE_CHAR_RE.test(scope)) {
1280
1291
  throw new Error("Use hyphens instead of spaces, for example test-package.");
1281
1292
  }
1282
- if (!/^@[a-z0-9][a-z0-9._-]*$/.test(scope)) {
1293
+ if (!NPM_SCOPE_RE.test(scope)) {
1283
1294
  throw new Error("Use lowercase letters and numbers, with optional periods, underscores, or hyphens.");
1284
1295
  }
1285
1296
  return scope;
@@ -1287,13 +1298,13 @@ function normalizeScope(value) {
1287
1298
  function validateBrandName(value) {
1288
1299
  if (!value.trim())
1289
1300
  return "Brand name cannot be empty.";
1290
- if (/\r|\n/.test(value))
1301
+ if (NEWLINE_CHAR_RE.test(value))
1291
1302
  return "Brand name must fit on one line.";
1292
1303
  if (value !== value.trim())
1293
1304
  return "Brand name cannot start or end with whitespace.";
1294
1305
  if (value.length > 48)
1295
1306
  return "Brand name must be 48 characters or less.";
1296
- if (!/^[\p{L}\p{N}](?:[\p{L}\p{N} ._-]*[\p{L}\p{N}])?$/u.test(value)) {
1307
+ if (!BRAND_NAME_RE.test(value)) {
1297
1308
  return "Brand name may contain letters, numbers, spaces, periods, underscores, and hyphens.";
1298
1309
  }
1299
1310
  return;
@@ -1303,12 +1314,43 @@ var SCOPE_EXTENSIONS = new Set([".json", ".jsonc", ".md", ".ts", ".tsx", ".yaml"
1303
1314
  // src/core.ts
1304
1315
  import { randomBytes } from "node:crypto";
1305
1316
  import { extname } from "node:path";
1317
+ var KEBAB_CASE_RE2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
1318
+ var WHITESPACE_CHAR_RE2 = /\s/;
1319
+ var NPM_SCOPE_RE2 = /^@[a-z0-9][a-z0-9._-]*$/;
1320
+ var NEWLINE_CHAR_RE2 = /\r|\n/;
1321
+ var BRAND_NAME_RE2 = /^[\p{L}\p{N}](?:[\p{L}\p{N} ._-]*[\p{L}\p{N}])?$/u;
1322
+ var REGEX_SPECIAL_CHARS_RE2 = /[.*+?^${}()|[\]\\]/g;
1323
+ var NON_ALPHANUMERIC_CHAR_RE = /[^a-z0-9]/g;
1324
+ var LEADING_DIGIT_RE = /^[0-9]/;
1325
+ var HTTP_PROTOCOL_RE = /^https?:\/\//;
1326
+ var APP_DOMAIN_RE = /^(?=.{1,253}$)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/;
1327
+ var BUNDLE_IDENTIFIER_RE = /^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*){2,}$/;
1328
+ var MOBILE_APP_SCHEME_LINE_RE = /^MOBILE_APP_SCHEME=.*$/m;
1329
+ var MOBILE_DOCS_SOURCE_LINE_RE = /^\s*'mobile\.md',\n/m;
1330
+ var MOBILE_EDITION_SECTION_RE = /^## Mobile edition\n[\s\S]*?(?=^## |(?![\s\S]))/m;
1331
+ var MOBILE_TABLE_ROW_RE = /^\| `apps\/mobile` .*\n/gm;
1332
+ var MOBILE_PARAGRAPH_RE = /^`apps\/mobile`[\s\S]*?(?:\n\n|\n?(?![\s\S]))/gm;
1333
+ var MOBILE_DOCS_LINK_RE = /\[([^\]]+)\]\(\.\/mobile\.md(?:#[^)]*)?\)/g;
1334
+ var NAMED_IMPORT_LINE_RE = /^(\s*)(import(?:\s+type)?\s+)\{([^}]+)\}(\s+from\s+.+)$/;
1335
+ var WHITESPACE_RE = /\s+/;
1336
+ var INTERNAL_AGENT_REFERENCE_RE = /internal\/|landing\/|seller-surface|studio-owned/i;
1337
+ var INTERNAL_CLAUDE_REFERENCE_RE = /internal\/|landing\/|studio-owned/i;
1338
+ var BETTER_AUTH_SECRET_LINE_RE = /^BETTER_AUTH_SECRET=.*$/m;
1339
+ var HEX_64_RE = /^[a-f0-9]{64}$/;
1340
+ var DATABASE_URL_LINE_RE = /^DATABASE_URL=.*$/m;
1341
+ var GHA_CREDS_FILE_RE = /^gha-creds-.*\.json$/;
1342
+ var ESCAPED_SOURCE_BRAND2 = SOURCE_BRAND_NAME.replace(REGEX_SPECIAL_CHARS_RE2, "\\$&");
1343
+ var SOURCE_BRAND_RE2 = new RegExp(`\\b${ESCAPED_SOURCE_BRAND2}\\b`, "gi");
1344
+ var ESCAPED_CLI_NAME2 = CLI_NAME2.replace(REGEX_SPECIAL_CHARS_RE2, "\\$&");
1345
+ var SOURCE_DELIVERY_GUIDANCE_RE2 = new RegExp(`pnpm dlx ${ESCAPED_CLI_NAME2}|bunx ${ESCAPED_CLI_NAME2}|${ESCAPED_CLI_NAME2} repository`, "i");
1346
+ var VENDOR_BOOTSTRAPPER_GUIDANCE_RE2 = new RegExp(`${ESCAPED_CLI_NAME2}@\\d|public \`${ESCAPED_CLI_NAME2}\` npm package`, "i");
1306
1347
  var BRAND_MANIFEST = [
1307
1348
  { path: "apps/web/src/configs/config.ts", occurrences: 2 },
1308
1349
  { path: "apps/web/index.html", occurrences: 4 },
1309
1350
  { path: "apps/web/public/favicon/site.webmanifest", occurrences: 2 },
1310
1351
  { path: "apps/web/public/offline.html", occurrences: 6 },
1311
1352
  { path: "apps/docs/lib/shared.ts", occurrences: 1 },
1353
+ { path: "apps/site/src/site.ts", occurrences: 1 },
1312
1354
  { path: "apps/docs/README.md", occurrences: 1 },
1313
1355
  { path: "README.md", occurrences: 1 },
1314
1356
  { path: "docs/meta.json", occurrences: 1 },
@@ -1351,17 +1393,17 @@ var UNIVERSAL_LINK_PATH_PREFIXES = ["/auth", "/invitations"];
1351
1393
  function validateAppName2(value) {
1352
1394
  if (!value)
1353
1395
  return "App name is required.";
1354
- if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value)) {
1396
+ if (!KEBAB_CASE_RE2.test(value)) {
1355
1397
  return "App name must be kebab-case (lowercase letters, numbers, and single hyphens).";
1356
1398
  }
1357
1399
  return;
1358
1400
  }
1359
1401
  function normalizeScope2(value) {
1360
1402
  const scope = value.startsWith("@") ? value : `@${value}`;
1361
- if (/\s/.test(scope)) {
1403
+ if (WHITESPACE_CHAR_RE2.test(scope)) {
1362
1404
  throw new Error("Use hyphens instead of spaces, for example test-package.");
1363
1405
  }
1364
- if (!/^@[a-z0-9][a-z0-9._-]*$/.test(scope)) {
1406
+ if (!NPM_SCOPE_RE2.test(scope)) {
1365
1407
  throw new Error("Use lowercase letters and numbers, with optional periods, underscores, or hyphens.");
1366
1408
  }
1367
1409
  return scope;
@@ -1369,13 +1411,13 @@ function normalizeScope2(value) {
1369
1411
  function validateBrandName2(value) {
1370
1412
  if (!value.trim())
1371
1413
  return "Brand name cannot be empty.";
1372
- if (/\r|\n/.test(value))
1414
+ if (NEWLINE_CHAR_RE2.test(value))
1373
1415
  return "Brand name must fit on one line.";
1374
1416
  if (value !== value.trim())
1375
1417
  return "Brand name cannot start or end with whitespace.";
1376
1418
  if (value.length > 48)
1377
1419
  return "Brand name must be 48 characters or less.";
1378
- if (!/^[\p{L}\p{N}](?:[\p{L}\p{N} ._-]*[\p{L}\p{N}])?$/u.test(value)) {
1420
+ if (!BRAND_NAME_RE2.test(value)) {
1379
1421
  return "Brand name may contain letters, numbers, spaces, periods, underscores, and hyphens.";
1380
1422
  }
1381
1423
  return;
@@ -1393,13 +1435,11 @@ function rewriteBrandContent(content, brandName) {
1393
1435
  const error = validateBrandName2(brandName);
1394
1436
  if (error)
1395
1437
  throw new Error(error);
1396
- const escapedSourceBrand = SOURCE_BRAND_NAME.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1397
- const sourceBrandPattern = new RegExp(`\\b${escapedSourceBrand}\\b`, "gi");
1398
- const matches = content.match(sourceBrandPattern);
1438
+ const matches = content.match(SOURCE_BRAND_RE2);
1399
1439
  if (brandName === SOURCE_BRAND_NAME || !matches)
1400
1440
  return { content, replacements: 0 };
1401
1441
  return {
1402
- content: content.replace(sourceBrandPattern, () => brandName),
1442
+ content: content.replace(SOURCE_BRAND_RE2, () => brandName),
1403
1443
  replacements: matches.length
1404
1444
  };
1405
1445
  }
@@ -1410,17 +1450,17 @@ function mobileSchemeFor(appName) {
1410
1450
  return appName;
1411
1451
  }
1412
1452
  function androidPackageSegment(appName) {
1413
- const segment = appName.replace(/[^a-z0-9]/g, "");
1414
- return /^[0-9]/.test(segment) || !segment ? `app${segment}` : segment;
1453
+ const segment = appName.replace(NON_ALPHANUMERIC_CHAR_RE, "");
1454
+ return LEADING_DIGIT_RE.test(segment) || !segment ? `app${segment}` : segment;
1415
1455
  }
1416
1456
  function validateAppDomain(value) {
1417
1457
  if (!value.trim())
1418
1458
  return "Application domain cannot be empty.";
1419
- if (/^https?:\/\//.test(value))
1459
+ if (HTTP_PROTOCOL_RE.test(value))
1420
1460
  return "Use a bare host such as app.example.com, not a URL.";
1421
1461
  if (value.includes("/"))
1422
1462
  return "Use a bare host such as app.example.com, with no path.";
1423
- if (!/^(?=.{1,253}$)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/.test(value)) {
1463
+ if (!APP_DOMAIN_RE.test(value)) {
1424
1464
  return "Use a lowercase hostname with at least two labels, for example app.example.com.";
1425
1465
  }
1426
1466
  return;
@@ -1428,7 +1468,7 @@ function validateAppDomain(value) {
1428
1468
  function validateBundleIdentifier(value) {
1429
1469
  if (!value.trim())
1430
1470
  return "Bundle identifier cannot be empty.";
1431
- if (!/^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*){2,}$/.test(value)) {
1471
+ if (!BUNDLE_IDENTIFIER_RE.test(value)) {
1432
1472
  return "Use at least three lowercase reverse-DNS segments, for example com.example.app.";
1433
1473
  }
1434
1474
  return;
@@ -1505,11 +1545,11 @@ function rewriteMobileAppConfig(content, identity) {
1505
1545
  `;
1506
1546
  }
1507
1547
  function rewriteMobileAppScheme(content, scheme) {
1508
- if (!/^MOBILE_APP_SCHEME=.*$/m.test(content)) {
1548
+ if (!MOBILE_APP_SCHEME_LINE_RE.test(content)) {
1509
1549
  throw new Error("Server .env.example must contain a MOBILE_APP_SCHEME entry.");
1510
1550
  }
1511
1551
  return {
1512
- content: content.replace(/^MOBILE_APP_SCHEME=.*$/m, `MOBILE_APP_SCHEME=${scheme}`),
1552
+ content: content.replace(MOBILE_APP_SCHEME_LINE_RE, `MOBILE_APP_SCHEME=${scheme}`),
1513
1553
  replacements: 1
1514
1554
  };
1515
1555
  }
@@ -1574,10 +1614,10 @@ function stripMobileDocsPage(content) {
1574
1614
  `;
1575
1615
  }
1576
1616
  function stripMobileDocsSource(content) {
1577
- return content.replace(/^\s*'mobile\.md',\n/m, "");
1617
+ return content.replace(MOBILE_DOCS_SOURCE_LINE_RE, "");
1578
1618
  }
1579
1619
  function stripMobileDocsReferences(content) {
1580
- return content.replace(/^## Mobile edition\n[\s\S]*?(?=^## |(?![\s\S]))/m, "").replace(/^\| `apps\/mobile` .*\n/gm, "").replace(/^`apps\/mobile`[\s\S]*?(?:\n\n|\n?(?![\s\S]))/gm, "").replace(/\[([^\]]+)\]\(\.\/mobile\.md(?:#[^)]*)?\)/g, "$1");
1620
+ return content.replace(MOBILE_EDITION_SECTION_RE, "").replace(MOBILE_TABLE_ROW_RE, "").replace(MOBILE_PARAGRAPH_RE, "").replace(MOBILE_DOCS_LINK_RE, "$1");
1581
1621
  }
1582
1622
  function formatRewrittenPackageJson(content) {
1583
1623
  const parsed = JSON.parse(content);
@@ -1600,7 +1640,7 @@ function wrapLongNamedImports(content, width = 100) {
1600
1640
  `).map((line) => {
1601
1641
  if (line.length <= width)
1602
1642
  return line;
1603
- const match = line.match(/^(\s*)(import(?:\s+type)?\s+)\{([^}]+)\}(\s+from\s+.+)$/);
1643
+ const match = line.match(NAMED_IMPORT_LINE_RE);
1604
1644
  if (!match)
1605
1645
  return line;
1606
1646
  const [, indentation = "", prefix = "", names = "", suffix = ""] = match;
@@ -1644,7 +1684,7 @@ function wrapBrandedJsxText(content, brandName, width = 100) {
1644
1684
  continue;
1645
1685
  }
1646
1686
  let current = indentation;
1647
- for (const word of joined.split(/\s+/)) {
1687
+ for (const word of joined.split(WHITESPACE_RE)) {
1648
1688
  const candidate = current === indentation ? `${indentation}${word}` : `${current} ${word}`;
1649
1689
  if (candidate.length > width && current !== indentation) {
1650
1690
  output.push(current);
@@ -1682,6 +1722,16 @@ function rewriteWorkerName(content, appName) {
1682
1722
  return { content, replacements: 0 };
1683
1723
  return { content: content.replace(WORKER_NAME_DECLARATION, replacement), replacements: 1 };
1684
1724
  }
1725
+ var JSONC_WORKER_NAME_FIELD = /("name":\s*")[^"]*(")/;
1726
+ function rewriteSiteWorkerName(content, appName) {
1727
+ const error = validateAppName2(appName);
1728
+ if (error)
1729
+ throw new Error(error);
1730
+ if (!JSONC_WORKER_NAME_FIELD.test(content)) {
1731
+ throw new Error("The site Worker configuration is missing a name field.");
1732
+ }
1733
+ return content.replace(JSONC_WORKER_NAME_FIELD, `$1${appName}-site$2`);
1734
+ }
1685
1735
  function replaceMarkdownSection(content, heading, replacement) {
1686
1736
  const marker = `## ${heading}
1687
1737
  `;
@@ -1711,7 +1761,7 @@ function removeParagraphContaining(content, fragment) {
1711
1761
  `);
1712
1762
  }
1713
1763
  function rewriteBuyerAgentInstructions(content) {
1714
- if (content.includes("Audit and remediate the complete buyer-owned SaaS application") && !/internal\/|landing\/|seller-surface|studio-owned/i.test(content)) {
1764
+ if (content.includes("Audit and remediate the complete buyer-owned SaaS application") && !INTERNAL_AGENT_REFERENCE_RE.test(content)) {
1715
1765
  return content;
1716
1766
  }
1717
1767
  let rewritten = removeParagraphContaining(content, "`internal/` holds every seller-only surface");
@@ -1729,7 +1779,7 @@ into this repository.
1729
1779
  rewritten = rewritten.split(`
1730
1780
  `).filter((line) => !line.includes("| `internal`")).join(`
1731
1781
  `).replace("real env files, `internal/`, or\n `bun.lock`", "real env files, or `bun.lock`");
1732
- if (/internal\/|landing\/|seller-surface|studio-owned/i.test(rewritten)) {
1782
+ if (INTERNAL_AGENT_REFERENCE_RE.test(rewritten)) {
1733
1783
  throw new Error("Buyer agent instructions still contain seller-only guidance.");
1734
1784
  }
1735
1785
  return rewritten;
@@ -1793,10 +1843,10 @@ function stripSellerOnlyRootScripts(content) {
1793
1843
  `;
1794
1844
  }
1795
1845
  function rewriteBuyerClaudeInstructions(content) {
1796
- if (!/internal\/|landing\/|studio-owned/i.test(content))
1846
+ if (!INTERNAL_CLAUDE_REFERENCE_RE.test(content))
1797
1847
  return content;
1798
1848
  const rewritten = removeParagraphContaining(content, "`internal/landing/AGENTS.md`");
1799
- if (/internal\/|landing\/|studio-owned/i.test(rewritten)) {
1849
+ if (INTERNAL_CLAUDE_REFERENCE_RE.test(rewritten)) {
1800
1850
  throw new Error("Buyer Claude instructions still contain seller-only guidance.");
1801
1851
  }
1802
1852
  return rewritten;
@@ -1805,8 +1855,7 @@ function rewriteBuyerSetupInstructions(content) {
1805
1855
  if (!content.includes("## Create a product repository"))
1806
1856
  return content;
1807
1857
  const rewritten = replaceMarkdownSection(content, "Create a product repository", "");
1808
- const escapedCliName = CLI_NAME2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1809
- if (new RegExp(`pnpm dlx ${escapedCliName}|bunx ${escapedCliName}|${escapedCliName} repository`, "i").test(rewritten)) {
1858
+ if (SOURCE_DELIVERY_GUIDANCE_RE2.test(rewritten)) {
1810
1859
  throw new Error("Buyer setup instructions still contain source-delivery guidance.");
1811
1860
  }
1812
1861
  return rewritten;
@@ -1815,30 +1864,29 @@ function rewriteBuyerReleaseInstructions(content) {
1815
1864
  if (!content.includes("## npm bootstrapper"))
1816
1865
  return content;
1817
1866
  const rewritten = replaceMarkdownSection(content, "npm bootstrapper", "");
1818
- const escapedCliName = CLI_NAME2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1819
- if (new RegExp(`${escapedCliName}@\\d|public \`${escapedCliName}\` npm package`, "i").test(rewritten)) {
1867
+ if (VENDOR_BOOTSTRAPPER_GUIDANCE_RE2.test(rewritten)) {
1820
1868
  throw new Error("Buyer release instructions still contain vendor bootstrapper guidance.");
1821
1869
  }
1822
1870
  return rewritten;
1823
1871
  }
1824
1872
  function generateStartDevVars(example, secret = randomBytes(32).toString("base64")) {
1825
- if (!/^BETTER_AUTH_SECRET=.*$/m.test(example)) {
1873
+ if (!BETTER_AUTH_SECRET_LINE_RE.test(example)) {
1826
1874
  throw new Error("apps/start/.dev.vars.example must contain a BETTER_AUTH_SECRET entry.");
1827
1875
  }
1828
- return example.replace(/^BETTER_AUTH_SECRET=.*$/m, `BETTER_AUTH_SECRET=${secret}`);
1876
+ return example.replace(BETTER_AUTH_SECRET_LINE_RE, `BETTER_AUTH_SECRET=${secret}`);
1829
1877
  }
1830
1878
  function generateServerEnv(example, secret = randomBytes(32).toString("hex"), databasePort = 5432) {
1831
- if (!/^[a-f0-9]{64}$/.test(secret)) {
1879
+ if (!HEX_64_RE.test(secret)) {
1832
1880
  throw new Error("Better Auth secret must be 32 bytes encoded as hex.");
1833
1881
  }
1834
- if (!/^DATABASE_URL=.*$/m.test(example) || !/^BETTER_AUTH_SECRET=.*$/m.test(example)) {
1882
+ if (!DATABASE_URL_LINE_RE.test(example) || !BETTER_AUTH_SECRET_LINE_RE.test(example)) {
1835
1883
  throw new Error("Server .env.example must contain DATABASE_URL and BETTER_AUTH_SECRET entries.");
1836
1884
  }
1837
1885
  if (!Number.isInteger(databasePort) || databasePort < 1 || databasePort > 65535) {
1838
1886
  throw new Error("Database port must be an integer between 1 and 65535.");
1839
1887
  }
1840
1888
  const databaseUrl = `postgresql://app:app@localhost:${databasePort}/app?sslmode=disable`;
1841
- return example.replace(/^DATABASE_URL=.*$/m, `DATABASE_URL=${databaseUrl}`).replace(/^BETTER_AUTH_SECRET=.*$/m, `BETTER_AUTH_SECRET=${secret}`);
1889
+ return example.replace(DATABASE_URL_LINE_RE, `DATABASE_URL=${databaseUrl}`).replace(BETTER_AUTH_SECRET_LINE_RE, `BETTER_AUTH_SECRET=${secret}`);
1842
1890
  }
1843
1891
  var SCOPE_EXTENSIONS2 = new Set([".json", ".jsonc", ".md", ".ts", ".tsx", ".yaml", ".yml"]);
1844
1892
  function isScopeRewriteFile(path) {
@@ -1871,7 +1919,7 @@ function isTemplatePathExcluded(relativePath) {
1871
1919
  if ([".expo", "android", "ios", "credentials", "dist-verify"].includes(mobileEntry))
1872
1920
  return true;
1873
1921
  }
1874
- if (parts.includes(".wrangler"))
1922
+ if (parts.includes(".wrangler") || parts.includes(".astro"))
1875
1923
  return true;
1876
1924
  if (parts.includes("dist") || parts.includes("out"))
1877
1925
  return true;
@@ -1883,7 +1931,7 @@ function isTemplatePathExcluded(relativePath) {
1883
1931
  return true;
1884
1932
  if (basename.endsWith(".tfvars") || basename === "backend.hcl")
1885
1933
  return true;
1886
- if (basename.includes(".tfstate") || /^gha-creds-.*\.json$/.test(basename))
1934
+ if (basename.includes(".tfstate") || GHA_CREDS_FILE_RE.test(basename))
1887
1935
  return true;
1888
1936
  if (normalized === "PLAN.md" || normalized === "comments.md")
1889
1937
  return true;
@@ -1907,6 +1955,13 @@ function isTemplatePathExcluded(relativePath) {
1907
1955
  }
1908
1956
 
1909
1957
  // src/editions.ts
1958
+ var NON_INDENTED_LINE_RE = /^\S/;
1959
+ var IMPORTER_KEY_LINE_RE = /^ {2}(\S[^:]*):\s*$/;
1960
+ var EXTRA_BLANK_LINES_RE = /\n{3,}/g;
1961
+ var START_TABLE_ROW_RE = /^\| `apps\/start` .*\n/gm;
1962
+ var START_PARAGRAPH_RE = /^`apps\/start`[\s\S]*?(?:\n\n|\n?(?![\s\S]))/gm;
1963
+ var WORKER_NAME_FIELD_RE = /("name":\s*")[^"]+(")/;
1964
+ var DATABASE_NAME_FIELD_RE = /("database_name":\s*")[^"]+(")/;
1910
1965
  var EDITION_NAMES = ["web", "mobile", "start"];
1911
1966
  var START_EDITION_PATHS = ["apps/start", ".github/workflows/deploy-start.yml"];
1912
1967
  var START_EDITION_OVERLAY = "apps/start/edition";
@@ -1916,6 +1971,7 @@ var START_ROOT_CONTRACTS = {
1916
1971
  };
1917
1972
  var START_ONLY_KEPT_PATHS = [
1918
1973
  "apps/start",
1974
+ "apps/site",
1919
1975
  "packages/billing",
1920
1976
  "packages/tsconfig",
1921
1977
  ".github/workflows/deploy-start.yml",
@@ -1953,14 +2009,14 @@ function pruneLockfileImporters(content, removedImporters) {
1953
2009
  let inImporters = false;
1954
2010
  let skipping = false;
1955
2011
  for (const line of lines) {
1956
- if (/^\S/.test(line)) {
2012
+ if (NON_INDENTED_LINE_RE.test(line)) {
1957
2013
  inImporters = line === "importers:";
1958
2014
  skipping = false;
1959
2015
  output.push(line);
1960
2016
  continue;
1961
2017
  }
1962
2018
  if (inImporters) {
1963
- const importer = line.match(/^ {2}(\S[^:]*):\s*$/)?.[1];
2019
+ const importer = line.match(IMPORTER_KEY_LINE_RE)?.[1];
1964
2020
  if (importer !== undefined)
1965
2021
  skipping = removed.has(importer);
1966
2022
  if (skipping)
@@ -1969,7 +2025,7 @@ function pruneLockfileImporters(content, removedImporters) {
1969
2025
  output.push(line);
1970
2026
  }
1971
2027
  return output.join(`
1972
- `).replace(/\n{3,}/g, `
2028
+ `).replace(EXTRA_BLANK_LINES_RE, `
1973
2029
 
1974
2030
  `);
1975
2031
  }
@@ -1984,10 +2040,10 @@ function stripKnipWorkspaces(content, removedWorkspaces) {
1984
2040
  `;
1985
2041
  }
1986
2042
  function stripStartReferences(content) {
1987
- return content.replace(/^\| `apps\/start` .*\n/gm, "").replace(/^`apps\/start`[\s\S]*?(?:\n\n|\n?(?![\s\S]))/gm, "");
2043
+ return content.replace(START_TABLE_ROW_RE, "").replace(START_PARAGRAPH_RE, "");
1988
2044
  }
1989
2045
  function rewriteStartWorkerIdentifiers(content, appName) {
1990
- return content.replace(/("name":\s*")[^"]+(")/, `$1${appName}$2`).replace(/("database_name":\s*")[^"]+(")/, `$1${appName}$2`);
2046
+ return content.replace(WORKER_NAME_FIELD_RE, `$1${appName}$2`).replace(DATABASE_NAME_FIELD_RE, `$1${appName}$2`);
1991
2047
  }
1992
2048
  function startOnlyRootPackage(rootPackage, overlayPackage) {
1993
2049
  const root = JSON.parse(rootPackage);
@@ -1997,12 +2053,13 @@ function startOnlyRootPackage(rootPackage, overlayPackage) {
1997
2053
  }
1998
2054
 
1999
2055
  // src/delivery.ts
2056
+ var WHITESPACE_CHAR_RE3 = /\s/;
2000
2057
  var RELEASE_TAG_PATTERN = /^v\d+\.\d+\.\d+(?:-rc\.\d+)?$/;
2001
2058
  function validateLicenseKey(value) {
2002
2059
  const key = value.trim();
2003
2060
  if (!key)
2004
2061
  return "Enter the licence key from your purchase receipt.";
2005
- if (/\s/.test(key))
2062
+ if (WHITESPACE_CHAR_RE3.test(key))
2006
2063
  return "A licence key does not contain spaces.";
2007
2064
  if (!key.startsWith(LICENSE_KEY_PREFIX) || key.length === LICENSE_KEY_PREFIX.length) {
2008
2065
  return `A ${CLI_NAME2} licence key starts with ${LICENSE_KEY_PREFIX}. Copy it from your purchase receipt or the customer portal.`;
@@ -2588,6 +2645,8 @@ function isExecutable(path) {
2588
2645
  }
2589
2646
 
2590
2647
  // src/agent-setup.ts
2648
+ var CODEX_AGENT_DOCS_TABLE_RE = /^\[mcp_servers\.(?:agent-docs|agent_docs|"agent-docs")\]$/m;
2649
+ var LINE_BREAK_RE = /\r?\n/;
2591
2650
  var CLIENTS = ["codex", "claude", "cursor"];
2592
2651
  var SERVER_NAME = "agent-docs";
2593
2652
  var CODEX_START = "# agent-context:start";
@@ -2681,7 +2740,7 @@ function mergeCodexConfig(existing, root) {
2681
2740
 
2682
2741
  ${managed}${existing.slice(after)}`.trimStart();
2683
2742
  }
2684
- if (/^\[mcp_servers\.(?:agent-docs|agent_docs|"agent-docs")\]$/m.test(existing)) {
2743
+ if (CODEX_AGENT_DOCS_TABLE_RE.test(existing)) {
2685
2744
  throw new Error("Codex already has an unmanaged agent-docs server; remove or rename it first.");
2686
2745
  }
2687
2746
  return `${existing.trimEnd()}${existing.trim() ? `
@@ -2732,14 +2791,14 @@ async function addGitInfoExcludes(root, paths) {
2732
2791
  return false;
2733
2792
  const excludePath = resolve(root, rawPath);
2734
2793
  const existing = await readFile(excludePath, "utf8").catch(() => "");
2735
- const missing = paths.filter((path) => !existing.split(/\r?\n/).includes(path));
2794
+ const missing = paths.filter((path) => !existing.split(LINE_BREAK_RE).includes(path));
2736
2795
  if (missing.length === 0)
2737
2796
  return true;
2738
2797
  const prefix = existing && !existing.endsWith(`
2739
2798
  `) ? `
2740
2799
  ` : "";
2741
2800
  const marker = "# Local agent configuration";
2742
- const heading = existing.split(/\r?\n/).includes(marker) ? "" : `${marker}
2801
+ const heading = existing.split(LINE_BREAK_RE).includes(marker) ? "" : `${marker}
2743
2802
  `;
2744
2803
  await atomicWrite(excludePath, `${existing}${prefix}${heading}${missing.join(`
2745
2804
  `)}
@@ -2889,6 +2948,9 @@ import { resolve as resolve2 } from "node:path";
2889
2948
  // src/deployment.ts
2890
2949
  import { mkdir as mkdir2, readFile as readFile2, rm, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
2891
2950
  import { join as join3 } from "node:path";
2951
+ var DEPLOYMENT_ENVIRONMENT_RE = /^[a-z][a-z0-9-]{1,30}$/;
2952
+ var REGION_RE = /^[a-z0-9][a-z0-9-]{1,31}$/;
2953
+ var DOMAIN_RE = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
2892
2954
  var deploymentClouds = ["aws", "gcp", "azure"];
2893
2955
  var frontendTargets = ["native", "vercel", "cloudflare"];
2894
2956
  var storageProviders = ["r2", "s3", "gcs"];
@@ -2933,19 +2995,19 @@ function parseStorageProvider(value) {
2933
2995
  return value;
2934
2996
  }
2935
2997
  function validateDeploymentEnvironment(value) {
2936
- if (!/^[a-z][a-z0-9-]{1,30}$/.test(value)) {
2998
+ if (!DEPLOYMENT_ENVIRONMENT_RE.test(value)) {
2937
2999
  return "Environment must use 2-31 lowercase letters, numbers, or hyphens.";
2938
3000
  }
2939
3001
  return;
2940
3002
  }
2941
3003
  function validateRegion(value) {
2942
- if (!/^[a-z0-9][a-z0-9-]{1,31}$/.test(value)) {
3004
+ if (!REGION_RE.test(value)) {
2943
3005
  return "Region must use 2-32 lowercase letters, numbers, or hyphens.";
2944
3006
  }
2945
3007
  return;
2946
3008
  }
2947
3009
  function validateDomain(value) {
2948
- if (value.length > 80 || !/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(value)) {
3010
+ if (value.length > 80 || !DOMAIN_RE.test(value)) {
2949
3011
  return "Enter a lowercase hostname such as api.example.com, without a protocol or path.";
2950
3012
  }
2951
3013
  return;
@@ -3409,6 +3471,9 @@ async function collectDeploymentSelection2(defaults = {}, nonInteractive = false
3409
3471
  // src/deployment.ts
3410
3472
  import { mkdir as mkdir3, readFile as readFile4, rm as rm2, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
3411
3473
  import { join as join5 } from "node:path";
3474
+ var DEPLOYMENT_ENVIRONMENT_RE2 = /^[a-z][a-z0-9-]{1,30}$/;
3475
+ var REGION_RE2 = /^[a-z0-9][a-z0-9-]{1,31}$/;
3476
+ var DOMAIN_RE2 = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
3412
3477
  var deploymentClouds2 = ["aws", "gcp", "azure"];
3413
3478
  var frontendTargets2 = ["native", "vercel", "cloudflare"];
3414
3479
  var storageProviders2 = ["r2", "s3", "gcs"];
@@ -3443,19 +3508,19 @@ function parseStorageProvider2(value) {
3443
3508
  return value;
3444
3509
  }
3445
3510
  function validateDeploymentEnvironment2(value) {
3446
- if (!/^[a-z][a-z0-9-]{1,30}$/.test(value)) {
3511
+ if (!DEPLOYMENT_ENVIRONMENT_RE2.test(value)) {
3447
3512
  return "Environment must use 2-31 lowercase letters, numbers, or hyphens.";
3448
3513
  }
3449
3514
  return;
3450
3515
  }
3451
3516
  function validateRegion2(value) {
3452
- if (!/^[a-z0-9][a-z0-9-]{1,31}$/.test(value)) {
3517
+ if (!REGION_RE2.test(value)) {
3453
3518
  return "Region must use 2-32 lowercase letters, numbers, or hyphens.";
3454
3519
  }
3455
3520
  return;
3456
3521
  }
3457
3522
  function validateDomain2(value) {
3458
- if (value.length > 80 || !/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(value)) {
3523
+ if (value.length > 80 || !DOMAIN_RE2.test(value)) {
3459
3524
  return "Enter a lowercase hostname such as api.example.com, without a protocol or path.";
3460
3525
  }
3461
3526
  return;
@@ -3632,12 +3697,13 @@ import { rm as rm3 } from "node:fs/promises";
3632
3697
  import { join as join6 } from "node:path";
3633
3698
  import { Readable, Transform } from "node:stream";
3634
3699
  import { pipeline } from "node:stream/promises";
3700
+ var TRAILING_SLASHES_RE = /\/+$/;
3635
3701
  var RELEASE_TAG_PATTERN2 = /^v\d+\.\d+\.\d+(?:-rc\.\d+)?$/;
3636
3702
  var INDEX_TIMEOUT_MS = 15000;
3637
3703
  var DOWNLOAD_TIMEOUT_MS = 300000;
3638
3704
  function deliveryBaseUrl(environment = process.env) {
3639
3705
  const configured = environment[DELIVERY_URL_VARIABLE]?.trim();
3640
- return (configured || DELIVERY_BASE_URL).replace(/\/+$/, "");
3706
+ return (configured || DELIVERY_BASE_URL).replace(TRAILING_SLASHES_RE, "");
3641
3707
  }
3642
3708
  function entitledEditions(index) {
3643
3709
  if (!Array.isArray(index.editions))
@@ -3756,6 +3822,7 @@ async function downloadAsset(licenseKey, releaseTag, assetName, directory, baseU
3756
3822
  }
3757
3823
 
3758
3824
  // src/scaffold.ts
3825
+ var SHA256_CHECKSUM_LINE_RE = /^([a-f0-9]{64})\s+\*?([^\s]+)$/i;
3759
3826
  var execFileAsync = promisify(execFile);
3760
3827
  async function assertEmptyTarget(targetDirectory) {
3761
3828
  try {
@@ -3825,7 +3892,7 @@ async function assertNoSymbolicLinks(directory, root = directory, isIncluded) {
3825
3892
  }
3826
3893
  }
3827
3894
  function verifyReleaseMetadata(checksum, manifest, artifactName, digest, templateRef) {
3828
- const checksumMatch = checksum.trim().match(/^([a-f0-9]{64})\s+\*?([^\s]+)$/i);
3895
+ const checksumMatch = checksum.trim().match(SHA256_CHECKSUM_LINE_RE);
3829
3896
  if (!checksumMatch || checksumMatch[2] !== artifactName) {
3830
3897
  throw new Error(`${CLI_NAME2} release checksum metadata is malformed.`);
3831
3898
  }
@@ -4041,6 +4108,9 @@ async function assembleEditions(target, selection, { keepStartOverlay = false }
4041
4108
  async function applyStartIdentity(target, appName) {
4042
4109
  await rewriteIfPresent(join7(target, "apps/start/wrangler.jsonc"), (content) => rewriteStartWorkerIdentifiers(content, appName));
4043
4110
  }
4111
+ async function renameSiteWorker(target, appName) {
4112
+ await rewriteIfPresent(join7(target, "apps/site/wrangler.jsonc"), (content) => rewriteSiteWorkerName(content, appName));
4113
+ }
4044
4114
  async function renameFrontendWorker(target, appName) {
4045
4115
  const workerPath = join7(target, "apps/web/wrangler.toml");
4046
4116
  if (!await stat4(workerPath).catch(() => {
@@ -4152,6 +4222,7 @@ async function scaffold(options) {
4152
4222
  const packagePath = join7(target, "package.json");
4153
4223
  const packageReplacements = await rewriteFile(packagePath, (content) => rewriteRootPackageName(content, options.appName));
4154
4224
  await renameFrontendWorker(target, options.appName);
4225
+ await renameSiteWorker(target, options.appName);
4155
4226
  const scope = await applyScopeRewrite(target, options.scope);
4156
4227
  if (selection.web) {
4157
4228
  await neutralizeAgentInstructions(target);
@@ -4185,6 +4256,10 @@ async function scaffold(options) {
4185
4256
  }
4186
4257
 
4187
4258
  // src/cli.ts
4259
+ var COMBINING_MARK_RE = /\p{Mark}/gu;
4260
+ var NON_ALPHANUMERIC_RE = /[^a-z0-9]+/g;
4261
+ var OUTER_DASHES_RE = /^-+|-+$/g;
4262
+
4188
4263
  class CliCancelled extends Error {
4189
4264
  }
4190
4265
  function usage() {
@@ -4334,7 +4409,7 @@ function suggestBrandName(appName) {
4334
4409
  return appName.split("-").map((segment) => productAcronyms[segment] ?? `${segment.charAt(0).toUpperCase()}${segment.slice(1)}`).join(" ");
4335
4410
  }
4336
4411
  function suggestScope(brandName, fallbackAppName) {
4337
- const namespace = brandName.normalize("NFKD").replace(/\p{Mark}/gu, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
4412
+ const namespace = brandName.normalize("NFKD").replace(COMBINING_MARK_RE, "").toLowerCase().replace(NON_ALPHANUMERIC_RE, "-").replace(OUTER_DASHES_RE, "");
4338
4413
  return namespace || fallbackAppName;
4339
4414
  }
4340
4415
  async function unwrapPrompt(value) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superslate",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Create a production-oriented SaaS application from superslate.",
5
5
  "keywords": [
6
6
  "bun",