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.
- package/dist/cli.js +133 -58
- 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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
|
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(
|
|
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(
|
|
1414
|
-
return
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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(
|
|
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(
|
|
1617
|
+
return content.replace(MOBILE_DOCS_SOURCE_LINE_RE, "");
|
|
1578
1618
|
}
|
|
1579
1619
|
function stripMobileDocsReferences(content) {
|
|
1580
|
-
return content.replace(
|
|
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(
|
|
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(
|
|
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") &&
|
|
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 (
|
|
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 (
|
|
1846
|
+
if (!INTERNAL_CLAUDE_REFERENCE_RE.test(content))
|
|
1797
1847
|
return content;
|
|
1798
1848
|
const rewritten = removeParagraphContaining(content, "`internal/landing/AGENTS.md`");
|
|
1799
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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(
|
|
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 (
|
|
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 (
|
|
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(
|
|
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") ||
|
|
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 (
|
|
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(
|
|
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(
|
|
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(
|
|
2043
|
+
return content.replace(START_TABLE_ROW_RE, "").replace(START_PARAGRAPH_RE, "");
|
|
1988
2044
|
}
|
|
1989
2045
|
function rewriteStartWorkerIdentifiers(content, appName) {
|
|
1990
|
-
return content.replace(
|
|
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 (
|
|
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 (
|
|
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(
|
|
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(
|
|
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 (
|
|
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 (
|
|
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 ||
|
|
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 (
|
|
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 (
|
|
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 ||
|
|
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(
|
|
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(
|
|
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) {
|