@getvitops/cli 8.0.0 → 8.1.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/CHANGELOG.md +45 -0
- package/dist/cli.mjs +531 -9
- package/dist/cli.mjs.map +1 -1
- package/package.json +3 -3
package/dist/cli.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
4
|
import { existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { dirname, extname, join, parse, relative, resolve } from "node:path";
|
|
6
|
-
import { JURISDICTION_COUNTRIES, NUMERIC_STEPS, SCHEMA_LOCAL_PATH, buildIconSprite, defaultConfig, enabledDocs, expandPalette, functionalRole, generate, generateDocs, generateLegal, isConfig, movedTokens, processorsMissingLocation, renderRobotsTxt, resolveConfig, resolveInput, resolveTheme, roleColorUtilities, roleHue, roleKind, validate } from "@getvitops/generator";
|
|
6
|
+
import { CONFIG_SCHEMA_LOCAL_PATH, JURISDICTION_COUNTRIES, NUMERIC_STEPS, SCHEMA_LOCAL_PATH, buildIconSprite, defaultConfig, defaultSiteConfig, enabledDocs, expandPalette, functionalRole, generate, generateDocs, generateLegal, isConfig, movedTokens, processorsMissingLocation, renderRobotsTxt, resolveConfig, resolveInput, resolveTheme, roleColorUtilities, roleHue, roleKind, validate } from "@getvitops/generator";
|
|
7
7
|
import { collectIconRefs, generateFavicons, generateIconInclude, resolveIcon, scanFiles } from "@getvitops/utils";
|
|
8
8
|
import { SCOPES, SNAPSHOT_PATH, adcCredentialsPath, adcQuotaProject, collectEntries, formatPlan, googleAccessToken, inspectUrl, keyFileContents, newKey, parseAdcUser, parseServiceAccount, plan, readSnapshot, resolveKeyLocation, resolveSitemapUrl, submitBatch, submitSitemap, toSnapshot, verifyKeyFile, writeSnapshot } from "@getvitops/utils/indexing";
|
|
9
9
|
import { REVIEWS_CACHE_PATH, fetchReviews, formatPlan as formatPlan$1, mergeCache, plan as plan$1, readCache, selectReviews, toCache, writeCache } from "@getvitops/utils/reviews";
|
|
@@ -14,6 +14,8 @@ import { addSite, backoffSchedule, createApexTxt, findZoneId, formatPlan as form
|
|
|
14
14
|
import { AD_PLATFORMS, AD_PROVIDER_KEYS, formatPlan as formatPlan$4, formatSummary as formatSummary$1, hasDrift as hasDrift$1, isAdProvider, missingFields, plan as plan$3, renderTag, tagId } from "@getvitops/utils/ads";
|
|
15
15
|
import { PLATFORM_PARAMS } from "@getvitops/utils/tracking";
|
|
16
16
|
import { createInterface } from "node:readline/promises";
|
|
17
|
+
import { execFileSync } from "node:child_process";
|
|
18
|
+
import { adminCollaboratorIds, branches, createD1, createDevEnvironment, createProdEnvironment, createR2Bucket, createRepo, defaultBranch, environments, findD1, findR2Bucket, formatPlan as formatPlan$5, formatSummary as formatSummary$2, ghAuthed, mainProtected, originRemote, plan as plan$4, planDomainConsistency, protectMain, pushBranches, repoNwo, secretsPresent, setDefaultBranch, setSecret, wranglerWhoami } from "@getvitops/utils/hosting";
|
|
17
19
|
|
|
18
20
|
//#region src/args.ts
|
|
19
21
|
/**
|
|
@@ -326,6 +328,7 @@ Usage:
|
|
|
326
328
|
vitops icons [options] Report which icons your source uses, and build the sprite
|
|
327
329
|
vitops search <sub> [opts] Search Console: onboard domains (setup) + notify deploys (notify)
|
|
328
330
|
vitops ads <sub> [opts] Ad properties: verify domains (setup) + emit pixels (tags) + lint
|
|
331
|
+
vitops setup <sub> [opts] Scripted onboarding: repo, hosting, domain, search, ads, all
|
|
329
332
|
vitops media [options] Encode raw video into web-ready WebM + MP4 + poster
|
|
330
333
|
|
|
331
334
|
Generate options:
|
|
@@ -344,8 +347,15 @@ Generate options:
|
|
|
344
347
|
--site-env <env> Environment whose A/B variant applies (default: production)
|
|
345
348
|
|
|
346
349
|
Init options:
|
|
347
|
-
-o, --out <path> Where to write (default: ./design-system.json
|
|
350
|
+
-o, --out <path> Where to write (default: ./design-system.json, or
|
|
351
|
+
./site.json with --site)
|
|
348
352
|
--force Overwrite an existing file
|
|
353
|
+
--site Write a site config (Config) instead of a bare
|
|
354
|
+
design-system.json — embeds a starter design system at
|
|
355
|
+
designSystem.themes.default, since a Config can't
|
|
356
|
+
merely reference a sibling file
|
|
357
|
+
--name <name> organization.name, when --site is set
|
|
358
|
+
--url <url> site.environments.{production,dev}.url, when --site is set
|
|
349
359
|
|
|
350
360
|
Validate options:
|
|
351
361
|
<file> Config to check (default: ./design-system.json). Either
|
|
@@ -381,6 +391,7 @@ Docs options:
|
|
|
381
391
|
|
|
382
392
|
Icons options:
|
|
383
393
|
-i, --input <path> Site config carrying the "icons" block (default: ./site.json)
|
|
394
|
+
--site-env <env> Environment whose A/B variant applies (default: production)
|
|
384
395
|
--src <dir> Source to scan for icon usage (default: ./src)
|
|
385
396
|
--sprite Also build the SVG sprite
|
|
386
397
|
-o, --out <dir> Where to write icons.svg with --sprite (default: ./dist)
|
|
@@ -566,6 +577,61 @@ Ads lint options:
|
|
|
566
577
|
off (every conversion arrives unattributed), and a configured property with no
|
|
567
578
|
tag ID. Exits non-zero on a finding.
|
|
568
579
|
|
|
580
|
+
Setup repo options:
|
|
581
|
+
--dry Print the plan and exit
|
|
582
|
+
--check Report drift/blocked steps and exit non-zero, mutating nothing
|
|
583
|
+
--public Create the repo public (default: private)
|
|
584
|
+
--platform <p> cloudflare (default) | node — shown for parity with the hosting stage's plan
|
|
585
|
+
--no-dev-resources Same — this stage never provisions D1/R2 either way
|
|
586
|
+
|
|
587
|
+
Creates the GitHub repo (via \`gh\`), pushes main + dev, makes dev the default
|
|
588
|
+
branch, and requests main branch protection + the production/dev environments.
|
|
589
|
+
Idempotent — a re-run of a fully set-up repo is all skips. Branch protection and
|
|
590
|
+
environment reviewers 403 on a private repo without GitHub Pro/Team; that is
|
|
591
|
+
reported as blocked, with the manual Settings path, never as a failure.
|
|
592
|
+
|
|
593
|
+
Setup hosting options:
|
|
594
|
+
--dry Print the plan and exit
|
|
595
|
+
--check Report drift/blocked steps and exit non-zero, mutating nothing
|
|
596
|
+
--platform <p> cloudflare (default) | node
|
|
597
|
+
--no-dev-resources Skip D1/R2 — a pure static Workers Assets project has
|
|
598
|
+
nothing to provision, just the account id + token
|
|
599
|
+
|
|
600
|
+
Provisions the isolated dev D1 database + R2 bucket (cloudflare) and sets the
|
|
601
|
+
DEV_D1_ID / CLOUDFLARE_ACCOUNT_ID repo secrets, once \`vitops setup repo\` has run.
|
|
602
|
+
CLOUDFLARE_API_TOKEN is always reported as blocked — no API can create one; see
|
|
603
|
+
the printed dashboard link. Requires \`npx wrangler login\` first.
|
|
604
|
+
|
|
605
|
+
Setup domain options:
|
|
606
|
+
-i, --input <path> Site config (default: ./site.json)
|
|
607
|
+
--site-env <env> Environment to check (default: production)
|
|
608
|
+
--dry Print the plan and exit
|
|
609
|
+
--check Exit non-zero if the domain state is inconsistent
|
|
610
|
+
--domain <apex> Print the manual cutover steps for this apex
|
|
611
|
+
|
|
612
|
+
Plans and checks the consistency between wrangler.jsonc's workers.dev/custom-domain
|
|
613
|
+
state and site.json's environments/domains/robots — it NEVER edits wrangler.jsonc
|
|
614
|
+
or deploys. Cutover overwrites the zone's DNS record and can take an existing live
|
|
615
|
+
site down; that step is manual, on purpose. See SETUP.html's "Domain strategy" stage.
|
|
616
|
+
|
|
617
|
+
Setup search options:
|
|
618
|
+
Forwards every option to \`vitops search setup\` unchanged — see "Search setup
|
|
619
|
+
options:" above.
|
|
620
|
+
|
|
621
|
+
Setup ads options:
|
|
622
|
+
Forwards every option to \`vitops ads setup\` unchanged — see "Ads setup options:"
|
|
623
|
+
above.
|
|
624
|
+
|
|
625
|
+
Setup all options:
|
|
626
|
+
--dry Print every stage's plan and exit
|
|
627
|
+
--check Report drift across every stage and exit non-zero
|
|
628
|
+
--public Create the repo public, for the repo stage
|
|
629
|
+
--platform <p> cloudflare (default) | node, for the repo + hosting stages
|
|
630
|
+
--no-dev-resources Skip D1/R2 in the hosting stage (repo stage never provisions them)
|
|
631
|
+
|
|
632
|
+
Runs repo, hosting, search, ads in order. Stops at the first stage that fails;
|
|
633
|
+
a blocked step (the API token, GitHub Pro/Team gating) is reported, not fatal.
|
|
634
|
+
|
|
569
635
|
Media options:
|
|
570
636
|
--raw <dir> Directory of unprocessed video, walked recursively
|
|
571
637
|
(default: ./raw)
|
|
@@ -607,6 +673,18 @@ const ADS_HELP = `vitops ads — link this site to its ad properties
|
|
|
607
673
|
vitops ads tags [opts] Print the consent-gated pixel snippets
|
|
608
674
|
vitops ads lint [opts] Report ad properties the rest of the config can't see
|
|
609
675
|
|
|
676
|
+
Run \`vitops --help\` for the full option list for each subcommand.
|
|
677
|
+
`;
|
|
678
|
+
const SETUP_HELP$1 = `vitops setup — the scripted path from an empty scaffold to a live site
|
|
679
|
+
|
|
680
|
+
vitops setup repo [opts] GitHub repo + branch policy (main protected, dev open)
|
|
681
|
+
vitops setup hosting [opts] Dev D1 + R2 + repo secrets (cloudflare) or a Node target
|
|
682
|
+
vitops setup domain [opts] Plan/check the workers.dev-vs-custom-domain consistency
|
|
683
|
+
(never applies the cutover — see SETUP.html)
|
|
684
|
+
vitops setup search [opts] Forwards to \`vitops search setup\`
|
|
685
|
+
vitops setup ads [opts] Forwards to \`vitops ads setup\`
|
|
686
|
+
vitops setup all [opts] Run repo, hosting, search, ads in order
|
|
687
|
+
|
|
610
688
|
Run \`vitops --help\` for the full option list for each subcommand.
|
|
611
689
|
`;
|
|
612
690
|
/**
|
|
@@ -628,6 +706,7 @@ const COMMANDS = [
|
|
|
628
706
|
"icons",
|
|
629
707
|
"search",
|
|
630
708
|
"ads",
|
|
709
|
+
"setup",
|
|
631
710
|
"media"
|
|
632
711
|
];
|
|
633
712
|
/**
|
|
@@ -657,7 +736,7 @@ const wantsHelp = (argv) => argv.includes("-h") || argv.includes("--help");
|
|
|
657
736
|
/** The help text to print for `vitops <command> [sub] --help`. */
|
|
658
737
|
function helpFor(command, argv) {
|
|
659
738
|
const sub = argv[0] && !argv[0].startsWith("-") ? argv[0] : void 0;
|
|
660
|
-
return (sub ? helpSection(command, sub) : void 0) ?? helpSection(command) ?? (command === "search" ? SEARCH_HELP : command === "ads" ? ADS_HELP : void 0) ?? HELP;
|
|
739
|
+
return (sub ? helpSection(command, sub) : void 0) ?? helpSection(command) ?? (command === "search" ? SEARCH_HELP : command === "ads" ? ADS_HELP : command === "setup" ? SETUP_HELP$1 : void 0) ?? HELP;
|
|
661
740
|
}
|
|
662
741
|
|
|
663
742
|
//#endregion
|
|
@@ -1472,6 +1551,421 @@ async function cmdAds(argv) {
|
|
|
1472
1551
|
}
|
|
1473
1552
|
}
|
|
1474
1553
|
|
|
1554
|
+
//#endregion
|
|
1555
|
+
//#region src/commands/setup.ts
|
|
1556
|
+
/**
|
|
1557
|
+
* `vitops setup` — the scripted path from an empty scaffold to a live site.
|
|
1558
|
+
* Subcommands: `repo | hosting | domain | search | ads | all`.
|
|
1559
|
+
*
|
|
1560
|
+
* Absorbs what each template's `scripts/init-{github,hosting}.mjs` used to do
|
|
1561
|
+
* template-locally, so the capability reaches every consumer, not just a fresh
|
|
1562
|
+
* `vp create` scaffold. `repo`/`hosting` follow the pure-planner/executor split in
|
|
1563
|
+
* `@getvitops/utils/hosting` — see that module's docblock. `search`/`ads` are not
|
|
1564
|
+
* reimplemented: this shells out to the CLI's own `search setup`/`ads setup`
|
|
1565
|
+
* subcommands as child processes, so their behaviour (prompting, `--dry`,
|
|
1566
|
+
* `--check`, writing the config patch back) stays exactly what it already is,
|
|
1567
|
+
* with no risk of the two copies drifting.
|
|
1568
|
+
*
|
|
1569
|
+
* `domain` only ever plans and checks the workers.dev-vs-custom-domain
|
|
1570
|
+
* consistency — it never touches `wrangler.jsonc` or deploys anything. Cutover
|
|
1571
|
+
* overwrites the zone's DNS record and can take an existing live site down; that
|
|
1572
|
+
* is a human's call, made in `SETUP.html`, not a step this command applies.
|
|
1573
|
+
*/
|
|
1574
|
+
const SETUP_HELP = `vitops setup <sub> [options]
|
|
1575
|
+
|
|
1576
|
+
vitops setup repo [--dry] [--check] [--public]
|
|
1577
|
+
vitops setup hosting [--dry] [--check] [--platform cloudflare|node]
|
|
1578
|
+
vitops setup domain [--dry] [--check] [--domain <apex>]
|
|
1579
|
+
vitops setup search [options] (forwards to \`vitops search setup\`)
|
|
1580
|
+
vitops setup ads [options] (forwards to \`vitops ads setup\`)
|
|
1581
|
+
vitops setup all [--dry] [--check] [--public] [--platform <p>]
|
|
1582
|
+
|
|
1583
|
+
--dry prints the plan and stops. --check reports drift/blocked steps and exits
|
|
1584
|
+
non-zero, mutating nothing. Neither creates the Cloudflare API token or performs
|
|
1585
|
+
a custom-domain cutover — both are named as manual steps; see SETUP.html.`;
|
|
1586
|
+
function projectName() {
|
|
1587
|
+
const path = resolve("package.json");
|
|
1588
|
+
if (!existsSync(path)) fail("package.json not found — run this from the project root");
|
|
1589
|
+
const pkg = JSON.parse(readFileSync(path, "utf8"));
|
|
1590
|
+
if (!pkg.name) fail("package.json has no \"name\" field");
|
|
1591
|
+
return pkg.name.replace(/^@[^/]+\//, "");
|
|
1592
|
+
}
|
|
1593
|
+
async function gatherHostingState(name, platform) {
|
|
1594
|
+
const [wrangler, gh, origin] = await Promise.all([
|
|
1595
|
+
platform === "cloudflare" ? wranglerWhoami() : Promise.resolve({ authed: false }),
|
|
1596
|
+
ghAuthed(),
|
|
1597
|
+
originRemote()
|
|
1598
|
+
]);
|
|
1599
|
+
const nwo = origin ? await repoNwo() : void 0;
|
|
1600
|
+
const [branchesList, def, protectedMain, envs, secrets, d1Id, r2] = await Promise.all([
|
|
1601
|
+
branches(),
|
|
1602
|
+
nwo ? defaultBranch(nwo) : Promise.resolve(void 0),
|
|
1603
|
+
nwo ? mainProtected(nwo) : Promise.resolve(false),
|
|
1604
|
+
nwo ? environments(nwo) : Promise.resolve([]),
|
|
1605
|
+
nwo ? secretsPresent(nwo) : Promise.resolve([]),
|
|
1606
|
+
platform === "cloudflare" ? findD1(`${name}-dev`) : Promise.resolve(void 0),
|
|
1607
|
+
platform === "cloudflare" ? findR2Bucket(`${name}-media-dev`) : Promise.resolve(false)
|
|
1608
|
+
]);
|
|
1609
|
+
return {
|
|
1610
|
+
wranglerAuthed: wrangler.authed,
|
|
1611
|
+
..."accountId" in wrangler && wrangler.accountId ? { accountId: wrangler.accountId } : {},
|
|
1612
|
+
d1Present: Boolean(d1Id),
|
|
1613
|
+
...d1Id ? { d1Id } : {},
|
|
1614
|
+
r2Present: r2,
|
|
1615
|
+
ghAuthed: gh,
|
|
1616
|
+
...origin ? { originRemote: origin } : {},
|
|
1617
|
+
...nwo ? { repoNwo: nwo } : {},
|
|
1618
|
+
branches: branchesList,
|
|
1619
|
+
...def ? { defaultBranch: def } : {},
|
|
1620
|
+
mainProtected: protectedMain,
|
|
1621
|
+
environments: envs,
|
|
1622
|
+
secretsPresent: secrets
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
async function cmdSetupRepo(argv) {
|
|
1626
|
+
const a = parse$1(argv, {
|
|
1627
|
+
dry: {
|
|
1628
|
+
type: "boolean",
|
|
1629
|
+
default: false
|
|
1630
|
+
},
|
|
1631
|
+
check: {
|
|
1632
|
+
type: "boolean",
|
|
1633
|
+
default: false
|
|
1634
|
+
},
|
|
1635
|
+
public: {
|
|
1636
|
+
type: "boolean",
|
|
1637
|
+
default: false
|
|
1638
|
+
},
|
|
1639
|
+
platform: {
|
|
1640
|
+
type: "string",
|
|
1641
|
+
default: "cloudflare"
|
|
1642
|
+
},
|
|
1643
|
+
"no-dev-resources": {
|
|
1644
|
+
type: "boolean",
|
|
1645
|
+
default: false
|
|
1646
|
+
}
|
|
1647
|
+
});
|
|
1648
|
+
const platform = a.str("platform");
|
|
1649
|
+
if (platform !== "cloudflare" && platform !== "node") fail(`--platform "${platform}" must be cloudflare or node`);
|
|
1650
|
+
const name = projectName();
|
|
1651
|
+
const setup = {
|
|
1652
|
+
platform,
|
|
1653
|
+
projectName: name,
|
|
1654
|
+
repoVisibility: a.flag("public") ? "public" : "private",
|
|
1655
|
+
devResources: !a.flag("no-dev-resources")
|
|
1656
|
+
};
|
|
1657
|
+
const state = await gatherHostingState(name, setup.platform);
|
|
1658
|
+
const p = plan$4(setup, state);
|
|
1659
|
+
const dry = a.flag("dry");
|
|
1660
|
+
const check = a.flag("check");
|
|
1661
|
+
if (dry || check) {
|
|
1662
|
+
console.log(formatPlan$5(p));
|
|
1663
|
+
if (check && (p.repo.action !== "skip" || p.branches.action !== "skip" || p.defaultBranch.action !== "skip")) {
|
|
1664
|
+
console.error("\n✖ repo/branch setup is not complete");
|
|
1665
|
+
process.exit(1);
|
|
1666
|
+
}
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
const results = [];
|
|
1670
|
+
if (p.repo.action === "create") {
|
|
1671
|
+
const r = await createRepo(name, setup.repoVisibility);
|
|
1672
|
+
results.push({
|
|
1673
|
+
step: "repo",
|
|
1674
|
+
status: r.ok ? "created" : "failed",
|
|
1675
|
+
detail: r.ok ? name : r.message ?? "failed"
|
|
1676
|
+
});
|
|
1677
|
+
if (r.ok) {
|
|
1678
|
+
const push = await pushBranches();
|
|
1679
|
+
results.push({
|
|
1680
|
+
step: "branches",
|
|
1681
|
+
status: push.ok ? "created" : "failed",
|
|
1682
|
+
detail: push.ok ? "main + dev pushed" : push.message ?? "failed"
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1685
|
+
} else results.push({
|
|
1686
|
+
step: "repo",
|
|
1687
|
+
status: "present",
|
|
1688
|
+
detail: p.repo.detail
|
|
1689
|
+
});
|
|
1690
|
+
const nwo = state.repoNwo ?? await repoNwo();
|
|
1691
|
+
if (nwo) {
|
|
1692
|
+
if (p.defaultBranch.action !== "skip") {
|
|
1693
|
+
const r = await setDefaultBranch(nwo, "dev");
|
|
1694
|
+
results.push({
|
|
1695
|
+
step: "default branch",
|
|
1696
|
+
status: r.ok ? "updated" : "failed",
|
|
1697
|
+
detail: r.ok ? "dev" : r.message ?? "failed"
|
|
1698
|
+
});
|
|
1699
|
+
} else results.push({
|
|
1700
|
+
step: "default branch",
|
|
1701
|
+
status: "present",
|
|
1702
|
+
detail: "dev"
|
|
1703
|
+
});
|
|
1704
|
+
if (p.mainProtection.action === "blocked") {
|
|
1705
|
+
const r = await protectMain(nwo);
|
|
1706
|
+
results.push({
|
|
1707
|
+
step: "main protection",
|
|
1708
|
+
status: r.ok ? "created" : "blocked",
|
|
1709
|
+
detail: r.ok ? "protected" : r.message ?? p.mainProtection.detail
|
|
1710
|
+
});
|
|
1711
|
+
} else results.push({
|
|
1712
|
+
step: "main protection",
|
|
1713
|
+
status: "present",
|
|
1714
|
+
detail: p.mainProtection.detail
|
|
1715
|
+
});
|
|
1716
|
+
if (p.prodEnvironment.action === "blocked") {
|
|
1717
|
+
const ids = await adminCollaboratorIds(nwo);
|
|
1718
|
+
const r = await createProdEnvironment(nwo, ids);
|
|
1719
|
+
results.push({
|
|
1720
|
+
step: "production env",
|
|
1721
|
+
status: r.ok ? "created" : "blocked",
|
|
1722
|
+
detail: r.ok ? `${ids.length} reviewer(s)` : r.message ?? p.prodEnvironment.detail
|
|
1723
|
+
});
|
|
1724
|
+
} else results.push({
|
|
1725
|
+
step: "production env",
|
|
1726
|
+
status: "present",
|
|
1727
|
+
detail: p.prodEnvironment.detail
|
|
1728
|
+
});
|
|
1729
|
+
if (p.devEnvironment.action === "create") {
|
|
1730
|
+
const r = await createDevEnvironment(nwo);
|
|
1731
|
+
results.push({
|
|
1732
|
+
step: "dev env",
|
|
1733
|
+
status: r.ok ? "created" : "failed",
|
|
1734
|
+
detail: r.ok ? "no required reviewers" : r.message ?? "failed"
|
|
1735
|
+
});
|
|
1736
|
+
} else results.push({
|
|
1737
|
+
step: "dev env",
|
|
1738
|
+
status: "present",
|
|
1739
|
+
detail: p.devEnvironment.detail
|
|
1740
|
+
});
|
|
1741
|
+
} else results.push({
|
|
1742
|
+
step: "branch policy",
|
|
1743
|
+
status: "blocked",
|
|
1744
|
+
detail: "no GitHub repo yet — run this again once \"repo\" succeeds"
|
|
1745
|
+
});
|
|
1746
|
+
console.log(formatSummary$2(results));
|
|
1747
|
+
if (results.some((r) => r.status === "failed")) process.exit(1);
|
|
1748
|
+
}
|
|
1749
|
+
async function cmdSetupHosting(argv) {
|
|
1750
|
+
const a = parse$1(argv, {
|
|
1751
|
+
dry: {
|
|
1752
|
+
type: "boolean",
|
|
1753
|
+
default: false
|
|
1754
|
+
},
|
|
1755
|
+
check: {
|
|
1756
|
+
type: "boolean",
|
|
1757
|
+
default: false
|
|
1758
|
+
},
|
|
1759
|
+
platform: {
|
|
1760
|
+
type: "string",
|
|
1761
|
+
default: "cloudflare"
|
|
1762
|
+
},
|
|
1763
|
+
"no-dev-resources": {
|
|
1764
|
+
type: "boolean",
|
|
1765
|
+
default: false
|
|
1766
|
+
}
|
|
1767
|
+
});
|
|
1768
|
+
const platform = a.str("platform");
|
|
1769
|
+
if (platform !== "cloudflare" && platform !== "node") fail(`--platform "${platform}" must be cloudflare or node`);
|
|
1770
|
+
const name = projectName();
|
|
1771
|
+
const setup = {
|
|
1772
|
+
platform,
|
|
1773
|
+
projectName: name,
|
|
1774
|
+
repoVisibility: "private",
|
|
1775
|
+
devResources: !a.flag("no-dev-resources")
|
|
1776
|
+
};
|
|
1777
|
+
const state = await gatherHostingState(name, platform);
|
|
1778
|
+
const p = plan$4(setup, state);
|
|
1779
|
+
const dry = a.flag("dry");
|
|
1780
|
+
const check = a.flag("check");
|
|
1781
|
+
if (platform === "node") {
|
|
1782
|
+
console.log("Hosting target \"node\" has no cloud resources to provision — see the template README's \"Hosting\" section.");
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
if (!state.wranglerAuthed && !dry) fail("wrangler is not logged in — run `npx wrangler login` first");
|
|
1786
|
+
if (dry || check) {
|
|
1787
|
+
console.log(formatPlan$5(p));
|
|
1788
|
+
if (check && (p.d1.action !== "skip" || p.r2.action !== "skip")) {
|
|
1789
|
+
console.error("\n✖ hosting resources are not fully provisioned");
|
|
1790
|
+
process.exit(1);
|
|
1791
|
+
}
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
const results = [];
|
|
1795
|
+
let d1Id = state.d1Id;
|
|
1796
|
+
if (p.d1.action === "create") {
|
|
1797
|
+
const r = await createD1(`${name}-dev`);
|
|
1798
|
+
results.push({
|
|
1799
|
+
step: "dev D1",
|
|
1800
|
+
status: r.ok ? "created" : "failed",
|
|
1801
|
+
detail: r.ok ? r.id ?? "" : r.message ?? "failed"
|
|
1802
|
+
});
|
|
1803
|
+
if (r.ok) d1Id = r.id;
|
|
1804
|
+
} else results.push({
|
|
1805
|
+
step: "dev D1",
|
|
1806
|
+
status: "present",
|
|
1807
|
+
detail: p.d1.detail
|
|
1808
|
+
});
|
|
1809
|
+
if (p.r2.action === "create") {
|
|
1810
|
+
const r = await createR2Bucket(`${name}-media-dev`);
|
|
1811
|
+
results.push({
|
|
1812
|
+
step: "dev R2",
|
|
1813
|
+
status: r.ok ? "created" : "failed",
|
|
1814
|
+
detail: r.ok ? `${name}-media-dev` : r.message ?? "failed"
|
|
1815
|
+
});
|
|
1816
|
+
} else results.push({
|
|
1817
|
+
step: "dev R2",
|
|
1818
|
+
status: "present",
|
|
1819
|
+
detail: p.r2.detail
|
|
1820
|
+
});
|
|
1821
|
+
if ((state.repoNwo ?? await repoNwo()) && state.ghAuthed) {
|
|
1822
|
+
if (d1Id && !state.secretsPresent.includes("DEV_D1_ID")) {
|
|
1823
|
+
const r = await setSecret("DEV_D1_ID", d1Id);
|
|
1824
|
+
results.push({
|
|
1825
|
+
step: "secret DEV_D1_ID",
|
|
1826
|
+
status: r.ok ? "created" : "failed",
|
|
1827
|
+
detail: r.ok ? "set" : r.message ?? "failed"
|
|
1828
|
+
});
|
|
1829
|
+
} else results.push({
|
|
1830
|
+
step: "secret DEV_D1_ID",
|
|
1831
|
+
status: state.secretsPresent.includes("DEV_D1_ID") ? "present" : "blocked",
|
|
1832
|
+
detail: d1Id ?? "D1 id unknown"
|
|
1833
|
+
});
|
|
1834
|
+
if (state.accountId && !state.secretsPresent.includes("CLOUDFLARE_ACCOUNT_ID")) {
|
|
1835
|
+
const r = await setSecret("CLOUDFLARE_ACCOUNT_ID", state.accountId);
|
|
1836
|
+
results.push({
|
|
1837
|
+
step: "secret CLOUDFLARE_ACCOUNT_ID",
|
|
1838
|
+
status: r.ok ? "created" : "failed",
|
|
1839
|
+
detail: r.ok ? "set" : r.message ?? "failed"
|
|
1840
|
+
});
|
|
1841
|
+
} else results.push({
|
|
1842
|
+
step: "secret CLOUDFLARE_ACCOUNT_ID",
|
|
1843
|
+
status: state.secretsPresent.includes("CLOUDFLARE_ACCOUNT_ID") ? "present" : "blocked",
|
|
1844
|
+
detail: state.accountId ?? "unknown"
|
|
1845
|
+
});
|
|
1846
|
+
} else results.push({
|
|
1847
|
+
step: "secrets",
|
|
1848
|
+
status: "blocked",
|
|
1849
|
+
detail: "no GitHub repo wired up yet — run `vitops setup repo` first"
|
|
1850
|
+
});
|
|
1851
|
+
results.push({
|
|
1852
|
+
step: "secret CLOUDFLARE_API_TOKEN",
|
|
1853
|
+
status: "blocked",
|
|
1854
|
+
detail: p.secrets["CLOUDFLARE_API_TOKEN"]?.detail ?? "create it by hand"
|
|
1855
|
+
});
|
|
1856
|
+
console.log(formatSummary$2(results));
|
|
1857
|
+
for (const n of p.notes) console.log(`! ${n}`);
|
|
1858
|
+
if (results.some((r) => r.status === "failed")) process.exit(1);
|
|
1859
|
+
}
|
|
1860
|
+
async function cmdSetupDomain(argv) {
|
|
1861
|
+
const a = parse$1(argv, {
|
|
1862
|
+
...SITE_OPTS,
|
|
1863
|
+
dry: {
|
|
1864
|
+
type: "boolean",
|
|
1865
|
+
default: false
|
|
1866
|
+
},
|
|
1867
|
+
check: {
|
|
1868
|
+
type: "boolean",
|
|
1869
|
+
default: false
|
|
1870
|
+
},
|
|
1871
|
+
domain: { type: "string" }
|
|
1872
|
+
});
|
|
1873
|
+
const wranglerPath = resolve("wrangler.jsonc");
|
|
1874
|
+
if (!existsSync(wranglerPath)) fail("wrangler.jsonc not found — run this from the project root");
|
|
1875
|
+
const raw = readFileSync(wranglerPath, "utf8");
|
|
1876
|
+
const customDomainMatch = /^\s*"routes":\s*\[\s*\{\s*"pattern":\s*"([^"]+)"/m.exec(raw.split("\n").filter((l) => !l.trim().startsWith("//")).join("\n"));
|
|
1877
|
+
const customDomain = customDomainMatch?.[1] ?? (a.opt("domain") ? void 0 : void 0);
|
|
1878
|
+
const cfg = await loadConfig(a.path("input"), a.str("site-env"));
|
|
1879
|
+
const env = cfg.site.environments[a.str("site-env")] ?? cfg.site.environments["production"];
|
|
1880
|
+
const step = planDomainConsistency({
|
|
1881
|
+
wranglerDev: !raw.includes("\"routes\"") || Boolean(customDomainMatch) === false,
|
|
1882
|
+
...customDomain ? { customDomain } : {},
|
|
1883
|
+
...env?.url ? { configuredUrl: env.url } : {},
|
|
1884
|
+
...cfg.site.domains?.canonical ? { canonical: cfg.site.domains.canonical } : {},
|
|
1885
|
+
...env?.robots ? { robots: env.robots } : {}
|
|
1886
|
+
});
|
|
1887
|
+
console.log(`${step.action === "skip" ? "·" : "?"} domain consistency ${step.detail}`);
|
|
1888
|
+
if (a.opt("domain")) console.log(`\nCutover is never applied by this command — see SETUP.html stage 4b. To cut over: uncomment routes in wrangler.jsonc with "pattern": "${a.opt("domain")}", deploy, then update site.domains.canonical and site.environments.${a.str("site-env")}.url to match.`);
|
|
1889
|
+
if (a.flag("check") && step.action !== "skip") process.exit(1);
|
|
1890
|
+
}
|
|
1891
|
+
/** Forward to the CLI's own `search setup` / `ads setup` as a child process — see
|
|
1892
|
+
* this module's docblock for why (their behaviour must not be re-derived). */
|
|
1893
|
+
function forward(sub, argv) {
|
|
1894
|
+
try {
|
|
1895
|
+
execFileSync(process.execPath, [
|
|
1896
|
+
process.argv[1],
|
|
1897
|
+
sub,
|
|
1898
|
+
"setup",
|
|
1899
|
+
...argv
|
|
1900
|
+
], { stdio: "inherit" });
|
|
1901
|
+
return true;
|
|
1902
|
+
} catch (err) {
|
|
1903
|
+
if (err.status != null) return false;
|
|
1904
|
+
fail(msg(err));
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
async function cmdSetup(argv) {
|
|
1908
|
+
const [sub, ...rest] = argv;
|
|
1909
|
+
switch (sub) {
|
|
1910
|
+
case "repo": return cmdSetupRepo(rest);
|
|
1911
|
+
case "hosting": return cmdSetupHosting(rest);
|
|
1912
|
+
case "domain": return cmdSetupDomain(rest);
|
|
1913
|
+
case "search":
|
|
1914
|
+
if (!forward("search", rest)) process.exit(1);
|
|
1915
|
+
return;
|
|
1916
|
+
case "ads":
|
|
1917
|
+
if (!forward("ads", rest)) process.exit(1);
|
|
1918
|
+
return;
|
|
1919
|
+
case "all": {
|
|
1920
|
+
const a = parse$1(rest, {
|
|
1921
|
+
dry: {
|
|
1922
|
+
type: "boolean",
|
|
1923
|
+
default: false
|
|
1924
|
+
},
|
|
1925
|
+
check: {
|
|
1926
|
+
type: "boolean",
|
|
1927
|
+
default: false
|
|
1928
|
+
},
|
|
1929
|
+
public: {
|
|
1930
|
+
type: "boolean",
|
|
1931
|
+
default: false
|
|
1932
|
+
},
|
|
1933
|
+
platform: {
|
|
1934
|
+
type: "string",
|
|
1935
|
+
default: "cloudflare"
|
|
1936
|
+
},
|
|
1937
|
+
"no-dev-resources": {
|
|
1938
|
+
type: "boolean",
|
|
1939
|
+
default: false
|
|
1940
|
+
}
|
|
1941
|
+
});
|
|
1942
|
+
const dryCheckFlags = [...a.flag("dry") ? ["--dry"] : [], ...a.flag("check") ? ["--check"] : []];
|
|
1943
|
+
const hostingFlags = [
|
|
1944
|
+
...dryCheckFlags,
|
|
1945
|
+
"--platform",
|
|
1946
|
+
a.str("platform"),
|
|
1947
|
+
...a.flag("no-dev-resources") ? ["--no-dev-resources"] : []
|
|
1948
|
+
];
|
|
1949
|
+
console.log("── repo ──");
|
|
1950
|
+
await cmdSetupRepo([...hostingFlags, ...a.flag("public") ? ["--public"] : []]);
|
|
1951
|
+
console.log("\n── hosting ──");
|
|
1952
|
+
await cmdSetupHosting(hostingFlags);
|
|
1953
|
+
console.log("\n── search ──");
|
|
1954
|
+
const searchOk = forward("search", dryCheckFlags);
|
|
1955
|
+
console.log("\n── ads ──");
|
|
1956
|
+
const adsOk = forward("ads", dryCheckFlags);
|
|
1957
|
+
if (!searchOk || !adsOk) process.exit(1);
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1960
|
+
case void 0:
|
|
1961
|
+
case "-h":
|
|
1962
|
+
case "--help":
|
|
1963
|
+
console.log(SETUP_HELP);
|
|
1964
|
+
return;
|
|
1965
|
+
default: fail(`unknown "setup" subcommand "${sub}" (expected: repo | hosting | domain | search | ads | all). Try: vitops --help`);
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1475
1969
|
//#endregion
|
|
1476
1970
|
//#region src/agents.ts
|
|
1477
1971
|
/**
|
|
@@ -2378,7 +2872,7 @@ function lintSource(files, v, format) {
|
|
|
2378
2872
|
* `vitops` — CLI for the Vitops design-system generator.
|
|
2379
2873
|
*
|
|
2380
2874
|
* vitops generate --input <json> --format <bricks|css|tailwind|design[,…]> --out <dir>
|
|
2381
|
-
* vitops init [--out design-system.json] [--force]
|
|
2875
|
+
* vitops init [--out design-system.json] [--force] [--site [--name <n>] [--url <u>]]
|
|
2382
2876
|
* vitops validate <design-system.json|site.json>
|
|
2383
2877
|
* vitops favicon --input <svg|png> --out <dir> [--low-res <svg|png>] [--background <hex>]
|
|
2384
2878
|
* vitops agents [--input <json>] [--out AGENTS.md] [--docs-dir <dir>]
|
|
@@ -2389,6 +2883,7 @@ function lintSource(files, v, format) {
|
|
|
2389
2883
|
* vitops reviews [--input <site.json>] [--location <key>] [--dry] [--check] [--out <dir>]
|
|
2390
2884
|
* vitops search setup [--domain <name>] [--dry] [--check] | notify [--dry] [--all] [--check]
|
|
2391
2885
|
* vitops ads setup [--provider <name>] [--dry] [--check] | tags | lint
|
|
2886
|
+
* vitops setup repo | hosting | domain | search | ads | all [--dry] [--check]
|
|
2392
2887
|
* vitops media [--raw <dir>] [--out <dir>] [--force] [--dry]
|
|
2393
2888
|
*
|
|
2394
2889
|
* Thin wrapper over @getvitops/generator (generation) and @getvitops/utils (favicons).
|
|
@@ -2464,27 +2959,53 @@ async function cmdGenerate(argv) {
|
|
|
2464
2959
|
fail(msg(err));
|
|
2465
2960
|
}
|
|
2466
2961
|
}
|
|
2962
|
+
/**
|
|
2963
|
+
* `vitops init` writes a starter `design-system.json`; `--site` writes a starter
|
|
2964
|
+
* `site.json` instead — the umbrella `Config` that embeds the design system under
|
|
2965
|
+
* `designSystem.themes.default`, since there is no file-level `extends` and a
|
|
2966
|
+
* scaffolded config can't merely reference a sibling file (see `defaultSiteConfig`'s
|
|
2967
|
+
* docblock in `@getvitops/generator`). `--name`/`--url` fill the two facts a
|
|
2968
|
+
* consumer almost certainly has on hand; everything else stays the documented
|
|
2969
|
+
* placeholder, edited by hand or via `vitops setup` (see AGENTS.md's "Client
|
|
2970
|
+
* onboarding" section).
|
|
2971
|
+
*/
|
|
2467
2972
|
function cmdInit(argv) {
|
|
2468
2973
|
const { values } = parseArgs({
|
|
2469
2974
|
args: argv,
|
|
2470
2975
|
options: {
|
|
2471
2976
|
out: {
|
|
2472
2977
|
type: "string",
|
|
2473
|
-
short: "o"
|
|
2474
|
-
default: "design-system.json"
|
|
2978
|
+
short: "o"
|
|
2475
2979
|
},
|
|
2476
2980
|
force: {
|
|
2477
2981
|
type: "boolean",
|
|
2478
2982
|
default: false
|
|
2479
|
-
}
|
|
2983
|
+
},
|
|
2984
|
+
site: {
|
|
2985
|
+
type: "boolean",
|
|
2986
|
+
default: false
|
|
2987
|
+
},
|
|
2988
|
+
name: { type: "string" },
|
|
2989
|
+
url: { type: "string" }
|
|
2480
2990
|
},
|
|
2481
2991
|
allowPositionals: false
|
|
2482
2992
|
});
|
|
2483
|
-
const out = resolve(values.out);
|
|
2993
|
+
const out = resolve(values.out ?? (values.site ? "site.json" : "design-system.json"));
|
|
2484
2994
|
if (existsSync(out) && !values.force) fail(`${out} already exists (use --force to overwrite)`);
|
|
2995
|
+
if (values.site) {
|
|
2996
|
+
const config = defaultSiteConfig({
|
|
2997
|
+
name: values.name,
|
|
2998
|
+
url: values.url
|
|
2999
|
+
});
|
|
3000
|
+
writeFileSync(out, JSON.stringify(config, null, 2) + "\n");
|
|
3001
|
+
console.log(`✓ wrote ${out}`);
|
|
3002
|
+
console.log(` Edit it, then: vitops validate ${relative(".", out)}`);
|
|
3003
|
+
console.log(` Editors read the "$schema" (${CONFIG_SCHEMA_LOCAL_PATH}) for autocomplete + validation — the installed copy, so it matches the toolchain you build with.`);
|
|
3004
|
+
return;
|
|
3005
|
+
}
|
|
2485
3006
|
writeFileSync(out, JSON.stringify(defaultConfig(), null, 2) + "\n");
|
|
2486
3007
|
console.log(`✓ wrote ${out}`);
|
|
2487
|
-
console.log(` Edit it, then: vitops generate --input ${
|
|
3008
|
+
console.log(` Edit it, then: vitops generate --input ${relative(".", out)} --format tailwind --out src/styles`);
|
|
2488
3009
|
console.log(` Editors read the "$schema" (${SCHEMA_LOCAL_PATH}) for autocomplete + validation — the installed copy, so it matches the toolchain you build with.`);
|
|
2489
3010
|
}
|
|
2490
3011
|
/**
|
|
@@ -3315,6 +3836,7 @@ async function main() {
|
|
|
3315
3836
|
case "icons": return cmdIcons(rest);
|
|
3316
3837
|
case "search": return cmdSearch(rest);
|
|
3317
3838
|
case "ads": return cmdAds(rest);
|
|
3839
|
+
case "setup": return cmdSetup(rest);
|
|
3318
3840
|
case "media": return cmdMedia(rest);
|
|
3319
3841
|
default: fail(`unknown command "${command}" (expected: ${COMMANDS.join(" | ")}). Try: vitops --help`);
|
|
3320
3842
|
}
|