@getvitops/cli 8.0.1 → 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 +31 -0
- package/dist/cli.mjs +489 -1
- package/dist/cli.mjs.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# @getvitops/cli
|
|
2
2
|
|
|
3
|
+
## 8.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **Added — `vitops init --site` and `vitops setup`, the scripted client-onboarding path.**
|
|
8
|
+
|
|
9
|
+
`vitops init --site [--name] [--url]` scaffolds a starter `site.json` (the umbrella `Config`,
|
|
10
|
+
embedding a starter design system at `designSystem.themes.default`) instead of a bare
|
|
11
|
+
`design-system.json` — exported as `defaultSiteConfig()` from `@getvitops/generator`, alongside a
|
|
12
|
+
new `CONFIG_SCHEMA_LOCAL_PATH` and an optional `$schema` field on `ConfigSchema` so a scaffolded
|
|
13
|
+
config gets editor autocomplete the same way `design-system.json` already does.
|
|
14
|
+
|
|
15
|
+
`vitops setup <repo|hosting|domain|search|ads|all>` (new `@getvitops/utils/hosting` subpath: a
|
|
16
|
+
pure planner + `wrangler`/`gh`-shelling executors, following the same idempotent
|
|
17
|
+
plan/observe/apply split as `onboarding`/`ads`) automates GitHub repo + branch policy and
|
|
18
|
+
Cloudflare dev D1/R2 + repo secrets, and plans/checks the workers.dev-vs-custom-domain
|
|
19
|
+
consistency without ever applying the cutover itself — see AGENTS.md's new "Client onboarding"
|
|
20
|
+
section for the full design rationale. `search`/`ads` forward to the existing `search setup`/
|
|
21
|
+
`ads setup` subcommands rather than reimplementing them.
|
|
22
|
+
|
|
23
|
+
No consumer-facing change in `@getvitops/vite` or `@getvitops/astro` in this release — bumped
|
|
24
|
+
only because they are in the `fixed` release group with `generator`/`utils`/`cli`.
|
|
25
|
+
`@getvitops/core` does have a real fix in this release; see the separate `banner-hidden-fix`
|
|
26
|
+
changeset.
|
|
27
|
+
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- Updated dependencies
|
|
31
|
+
- @getvitops/generator@8.1.0
|
|
32
|
+
- @getvitops/utils@8.1.0
|
|
33
|
+
|
|
3
34
|
## 8.0.1
|
|
4
35
|
|
|
5
36
|
### Patch Changes
|
package/dist/cli.mjs
CHANGED
|
@@ -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:
|
|
@@ -574,6 +577,61 @@ Ads lint options:
|
|
|
574
577
|
off (every conversion arrives unattributed), and a configured property with no
|
|
575
578
|
tag ID. Exits non-zero on a finding.
|
|
576
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
|
+
|
|
577
635
|
Media options:
|
|
578
636
|
--raw <dir> Directory of unprocessed video, walked recursively
|
|
579
637
|
(default: ./raw)
|
|
@@ -615,6 +673,18 @@ const ADS_HELP = `vitops ads — link this site to its ad properties
|
|
|
615
673
|
vitops ads tags [opts] Print the consent-gated pixel snippets
|
|
616
674
|
vitops ads lint [opts] Report ad properties the rest of the config can't see
|
|
617
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
|
+
|
|
618
688
|
Run \`vitops --help\` for the full option list for each subcommand.
|
|
619
689
|
`;
|
|
620
690
|
/**
|
|
@@ -636,6 +706,7 @@ const COMMANDS = [
|
|
|
636
706
|
"icons",
|
|
637
707
|
"search",
|
|
638
708
|
"ads",
|
|
709
|
+
"setup",
|
|
639
710
|
"media"
|
|
640
711
|
];
|
|
641
712
|
/**
|
|
@@ -665,7 +736,7 @@ const wantsHelp = (argv) => argv.includes("-h") || argv.includes("--help");
|
|
|
665
736
|
/** The help text to print for `vitops <command> [sub] --help`. */
|
|
666
737
|
function helpFor(command, argv) {
|
|
667
738
|
const sub = argv[0] && !argv[0].startsWith("-") ? argv[0] : void 0;
|
|
668
|
-
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;
|
|
669
740
|
}
|
|
670
741
|
|
|
671
742
|
//#endregion
|
|
@@ -1480,6 +1551,421 @@ async function cmdAds(argv) {
|
|
|
1480
1551
|
}
|
|
1481
1552
|
}
|
|
1482
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
|
+
|
|
1483
1969
|
//#endregion
|
|
1484
1970
|
//#region src/agents.ts
|
|
1485
1971
|
/**
|
|
@@ -2397,6 +2883,7 @@ function lintSource(files, v, format) {
|
|
|
2397
2883
|
* vitops reviews [--input <site.json>] [--location <key>] [--dry] [--check] [--out <dir>]
|
|
2398
2884
|
* vitops search setup [--domain <name>] [--dry] [--check] | notify [--dry] [--all] [--check]
|
|
2399
2885
|
* vitops ads setup [--provider <name>] [--dry] [--check] | tags | lint
|
|
2886
|
+
* vitops setup repo | hosting | domain | search | ads | all [--dry] [--check]
|
|
2400
2887
|
* vitops media [--raw <dir>] [--out <dir>] [--force] [--dry]
|
|
2401
2888
|
*
|
|
2402
2889
|
* Thin wrapper over @getvitops/generator (generation) and @getvitops/utils (favicons).
|
|
@@ -3349,6 +3836,7 @@ async function main() {
|
|
|
3349
3836
|
case "icons": return cmdIcons(rest);
|
|
3350
3837
|
case "search": return cmdSearch(rest);
|
|
3351
3838
|
case "ads": return cmdAds(rest);
|
|
3839
|
+
case "setup": return cmdSetup(rest);
|
|
3352
3840
|
case "media": return cmdMedia(rest);
|
|
3353
3841
|
default: fail(`unknown command "${command}" (expected: ${COMMANDS.join(" | ")}). Try: vitops --help`);
|
|
3354
3842
|
}
|