@tryvitris/website-harness 0.1.0-beta.8 → 1.0.1
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/README.md +5 -1
- package/agent/refs/admin.md +11 -0
- package/agent/refs/launch.md +13 -11
- package/bin/api/agent/evaluate.js +1 -1
- package/bin/api/agent/grade.js +1 -1
- package/bin/api/cli/api.js +9 -9
- package/bin/api/cli.js +11 -11
- package/bin/api/engine.js +2 -2
- package/bin/api/hosting.js +3 -3
- package/bin/chunks/{chunk-QQOMS2DL.js → chunk-445FZZEC.js} +125 -19
- package/bin/chunks/{chunk-PYCRUMCT.js → chunk-54QPYUM2.js} +1 -1
- package/bin/chunks/{chunk-R2WRQDVE.js → chunk-65LRZIBP.js} +1 -1
- package/bin/chunks/{chunk-FGSQE3DU.js → chunk-72Y4YMTV.js} +1 -1
- package/bin/chunks/{chunk-XY73ZLHB.js → chunk-GQPUILFR.js} +33 -5
- package/bin/chunks/{chunk-A2XMAPVW.js → chunk-JBSGJR7X.js} +1 -1
- package/bin/chunks/{chunk-WNE543KF.js → chunk-KIWEFRNG.js} +5 -5
- package/bin/chunks/{chunk-DYKLM4I3.js → chunk-M72KKZIB.js} +24 -6
- package/bin/chunks/{chunk-FEFLSJM5.js → chunk-RE4GOHSU.js} +3 -3
- package/bin/chunks/{chunk-WMJVLMU2.js → chunk-UR4NWYVW.js} +2 -2
- package/bin/chunks/{chunk-3OKR5AZJ.js → chunk-VKU5TY5C.js} +1 -1
- package/bin/chunks/{design-context-store-W3VTZ4HA.js → design-context-store-ETVS4N6W.js} +1 -1
- package/bin/chunks/{packets-LBMDIFPO.js → packets-XFXKHSEY.js} +1 -1
- package/bin/chunks/{preparation-AV4PDQY6.js → preparation-3OR7LVPQ.js} +1 -1
- package/bin/cloudflare/backend.js +143 -61
- package/bin/types/src/admin/connection.d.ts +3 -0
- package/bin/types/src/agent/refine-document-checks.d.ts +5 -0
- package/bin/types/src/cli/admin.d.ts +21 -0
- package/bin/types/src/cli/site-create.d.ts +2 -0
- package/bin/vitris-art.js +1 -1
- package/bin/vitris-create-site.js +5 -3
- package/bin/vitris-cursor-hook.js +1 -1
- package/bin/vitris-eval.js +3 -3
- package/bin/vitris-go.js +3 -3
- package/bin/vitris-harvest.js +2 -2
- package/bin/vitris-knowledge.js +2 -2
- package/bin/vitris-launch-preflight.js +1 -1
- package/bin/vitris-motion-view.js +1 -1
- package/bin/vitris-refresh.js +2 -2
- package/bin/vitris-stock.js +1 -1
- package/bin/vitris-themes.js +1 -1
- package/bin/vitris-upgrade.js +2 -2
- package/bin/vitris-verify-browser.js +1 -1
- package/bin/vitris.js +11 -11
- package/docs/ADMIN.md +85 -0
- package/docs/CLI.md +11 -5
- package/docs/CLOUDFLARE.md +24 -2
- package/docs/DISTRIBUTION.md +5 -3
- package/docs/PRESERVE.md +1 -1
- package/docs/SECURITY.md +2 -2
- package/package.json +28 -23
- package/templates/site/AGENTS.md +3 -1
- package/templates/site/README.md +5 -3
- package/templates/site/deployment/README.md +24 -0
- package/templates/site/{.github/workflows/deploy.yml → deployment/github-actions.yml.example} +5 -6
- package/templates/site/gitignore +3 -0
- package/templates/site/package.json +2 -1
package/README.md
CHANGED
|
@@ -6,8 +6,10 @@ The package is [`@tryvitris/website-harness`](https://www.npmjs.com/package/@try
|
|
|
6
6
|
|
|
7
7
|
## Create a website
|
|
8
8
|
|
|
9
|
+
An optional separate business admin is available through `vitris admin setup|status|verify`; new-site `--admin` prepares follow-up instructions. See the [admin integration guide](docs/ADMIN.md) for pinned packages, local plan/apply, backend grants and the separate customer-account Cloudflare rollout.
|
|
10
|
+
|
|
9
11
|
```bash
|
|
10
|
-
npx --package @tryvitris/website-harness@
|
|
12
|
+
npx --package @tryvitris/website-harness@1.0.0 vitris site create \
|
|
11
13
|
--dir acme --slug acme --name "Acme Shop" \
|
|
12
14
|
--from https://old-site.example --template "Still & Field" \
|
|
13
15
|
--content-file ./acme-notes.md
|
|
@@ -50,6 +52,8 @@ For example, ask your agent to “improve the mobile layout,” “review the vi
|
|
|
50
52
|
|
|
51
53
|
## Develop the harness
|
|
52
54
|
|
|
55
|
+
Customer sites build and verify locally, then upload the exact finished bundle directly to Cloudflare Pages. Use `npm run deploy:preview` for a hosted preview and `npm run deploy` for explicitly requested production publication. GitHub stores source; hosted deployment automation is opt-in. See [Cloudflare setup](docs/CLOUDFLARE.md) for new sites and migration of existing sites.
|
|
56
|
+
|
|
53
57
|
```bash
|
|
54
58
|
npm ci
|
|
55
59
|
npx playwright install chromium
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Optional business admin
|
|
2
|
+
|
|
3
|
+
Only prepare an admin when requested or when the operator opts in with site creation `--admin`. Continue the website's prepare → CURRENT → work → complete workflow and verified local-preview stop. Admin rollout does not bypass website consent or release gates.
|
|
4
|
+
|
|
5
|
+
Read the installed harness's `docs/ADMIN.md` and the published admin's packaged `agent/WORKFLOW.md`, `agent/refs/harness.md`, and `agent/refs/cloudflare.md`. Use the pinned **0.1.0-alpha.1** release. Run `vitris admin setup --site WEBSITE --dir SIBLING_ADMIN --domain admin.customer-domain.com --website HTTPS_SITE_ORIGIN --tenant EXISTING_TENANT_ID --backend-origin HTTPS_BACKEND --plan`. Review `artifacts/admin/setup-plan.json`, then repeat with `--apply` and a dedicated environment `VITRIS_ADMIN_API_TOKEN`. Reuse the published installer; never reproduce it or silently overwrite an installation.
|
|
6
|
+
|
|
7
|
+
Setup is local scaffolding and public connection metadata only. Preserve forms, origins, tenants, existing accounts/records, authored source and workflow progress. Never infer compatibility from scaffolding: an older deployed backend needs its own reviewed API upgrade. `vitris admin status` is local metadata; `vitris admin verify --site WEBSITE --dir ADMIN` checks deployed identity and authoritative reads with the dedicated credential, without printing records. Failures must remain visible, with no demo fallback.
|
|
8
|
+
|
|
9
|
+
Default scopes: leads:read and analytics:read. Add --lead-write to both setup commands only for explicit status-write authorization. Prepare a complete hashed grant map with `vitris admin grant`; preserve unrelated grants. Store only SHA-256 in backend ADMIN_CONNECTIONS, and upload the raw token to the admin as HARNESS_API_TOKEN through secret stdin. Never commit configuration or credentials.
|
|
10
|
+
|
|
11
|
+
Prepare explicit, separate customer-account provisioning, D1 migrations, owner bootstrap, Cloudflare Email Service onboarding, authentication, DNS/TLS and deployment steps. Never deploy/migrate a shared backend as a side effect. Sites without collection need an explicitly provisioned backend and separately reviewed form/analytics activation; they have no historical data to import. Production demo login is forbidden. Test submissions and notification captures remain isolated and read-only; preview traffic is excluded from analytics. Clearly label synthetic fixtures and stub services. Pages, Media, page builders and arbitrary HTML editing remain outside scope.
|
package/agent/refs/launch.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
# Launch (optional)
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
# Launch (optional)
|
|
2
|
+
|
|
3
|
+
For a separately requested business admin at `admin.customer-domain.com`, read `admin.md` and the installed harness's `docs/ADMIN.md`. Use `vitris admin setup --plan` and reviewed `--apply`; verify the actual deployed backend with `vitris admin verify`. Local scaffolding does not enable collection/authentication or deploy infrastructure. Keep the sibling admin's Cloudflare provisioning and release separate from website publication.
|
|
4
|
+
|
|
5
|
+
Only when the operator asks after `preview_ready`. Build and verify locally, then upload the exact staged bundle directly to Cloudflare Pages. GitHub stores source and history; hosted deployment automation is opt-in.
|
|
6
|
+
|
|
7
|
+
1. Commit authored source and the lockfile to the customer's private repository when requested. Keep `dist/`, `agent/release-bundles/`, `agent/release-candidate.json`, `agent/releases/`, captures and credentials out of Git.
|
|
8
|
+
2. Authenticate with the operator's own `npx wrangler login` and confirm the account. For a new site create a Pages Direct Upload project with `npx wrangler pages project create PROJECT`. Set `launch.json.pagesProject` and the real production `launch.json.publicOrigin`. Install Chromium locally for preflight. Run `npm run deploy:preview` and accept the hosted preview; run `npm run deploy` only for explicit production publication. Ordinary deployment builds and verifies locally, stages the exact checked files, uploads them, records their identity and checks the hosted URL. Preservation sites support temporary previews only.
|
|
9
|
+
3. For existing Git-connected Pages projects, disable automatic production and preview branch deployments before local uploads. Review and disable existing automatic GitHub deployment workflows as well. Preserve project names, domains, launch configuration, authored files and workflow progress. Installing or upgrading the harness does not migrate package scripts or remote settings. See the installed harness's `docs/CLOUDFLARE.md`.
|
|
10
|
+
4. Backend tenant registration, contact forms, administration sign-in, custom domains and DNS are separate configured features. If publishing through the authenticated backend Worker, retain the exact verified bundle signing and attestation contract and review that path before migration. Direct Pages uploads do not provision tenants or forms.
|
|
11
|
+
5. Hosted automation is opt-in. `deployment/github-actions.yml.example` is inactive until an operator chooses and installs it under `.github/workflows/` with protected secrets. Cloudflare Git builds are another optional alternative. Enable one publication path per site; preserve preflight and bundle integrity checks in every ordinary path.
|
|
12
|
+
|
|
13
|
+
Do not merge secrets into git. Use `.env` for Cloudflare tokens.
|
|
12
14
|
|
|
13
15
|
For an explicitly requested quick publish, use `vitris deploy --quick --environment production` (`npm run deploy:quick` in newly scaffolded sites). This rebuilds and deploys without browser, graph or launch review gates. Compilation, Cloudflare credentials, staged-file integrity and an HTTP smoke check still run. Receipts record build-only verification. Preservation sites use `--environment preview` and also skip screenshot comparisons. Ordinary deployment retains full preflight.
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
parentDiversitySourceSubject,
|
|
12
12
|
reviewSubject,
|
|
13
13
|
validateParentDiversityReview
|
|
14
|
-
} from "../../chunks/chunk-
|
|
14
|
+
} from "../../chunks/chunk-GQPUILFR.js";
|
|
15
15
|
import "../../chunks/chunk-KVMZSQ3D.js";
|
|
16
16
|
import "../../chunks/chunk-WOORR67N.js";
|
|
17
17
|
import "../../chunks/chunk-PRLL7B63.js";
|
package/bin/api/agent/grade.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
gradePages,
|
|
10
10
|
gradePreview,
|
|
11
11
|
gradeResearch
|
|
12
|
-
} from "../../chunks/chunk-
|
|
12
|
+
} from "../../chunks/chunk-GQPUILFR.js";
|
|
13
13
|
import "../../chunks/chunk-KVMZSQ3D.js";
|
|
14
14
|
import "../../chunks/chunk-WOORR67N.js";
|
|
15
15
|
import "../../chunks/chunk-PRLL7B63.js";
|
package/bin/api/cli/api.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
runDoctor,
|
|
12
12
|
shareSite,
|
|
13
13
|
synthesizeSite
|
|
14
|
-
} from "../../chunks/chunk-
|
|
14
|
+
} from "../../chunks/chunk-KIWEFRNG.js";
|
|
15
15
|
import {
|
|
16
16
|
applyOperations,
|
|
17
17
|
buildSiteDraft,
|
|
@@ -19,35 +19,35 @@ import {
|
|
|
19
19
|
inspectSite,
|
|
20
20
|
prepareChange,
|
|
21
21
|
verifyChange
|
|
22
|
-
} from "../../chunks/chunk-
|
|
22
|
+
} from "../../chunks/chunk-72Y4YMTV.js";
|
|
23
23
|
import {
|
|
24
24
|
deployVerifiedArtifact,
|
|
25
25
|
prepareQuickRelease,
|
|
26
26
|
prepareVerifiedRelease,
|
|
27
27
|
verifySite
|
|
28
|
-
} from "../../chunks/chunk-
|
|
28
|
+
} from "../../chunks/chunk-UR4NWYVW.js";
|
|
29
29
|
import {
|
|
30
30
|
upgradeSite
|
|
31
|
-
} from "../../chunks/chunk-
|
|
31
|
+
} from "../../chunks/chunk-JBSGJR7X.js";
|
|
32
32
|
import {
|
|
33
33
|
createSite
|
|
34
|
-
} from "../../chunks/chunk-
|
|
34
|
+
} from "../../chunks/chunk-M72KKZIB.js";
|
|
35
35
|
import "../../chunks/chunk-2NW7KSPA.js";
|
|
36
36
|
import {
|
|
37
37
|
buildAdapterConfig,
|
|
38
38
|
runPreserve,
|
|
39
39
|
runSite,
|
|
40
40
|
statusSite
|
|
41
|
-
} from "../../chunks/chunk-
|
|
42
|
-
import "../../chunks/chunk-
|
|
41
|
+
} from "../../chunks/chunk-RE4GOHSU.js";
|
|
42
|
+
import "../../chunks/chunk-65LRZIBP.js";
|
|
43
43
|
import "../../chunks/chunk-34H54KEY.js";
|
|
44
44
|
import {
|
|
45
45
|
EXIT_CODES
|
|
46
46
|
} from "../../chunks/chunk-JCRGE6AL.js";
|
|
47
47
|
import "../../chunks/chunk-XL2DRAYI.js";
|
|
48
|
-
import "../../chunks/chunk-
|
|
48
|
+
import "../../chunks/chunk-54QPYUM2.js";
|
|
49
49
|
import "../../chunks/chunk-XGTHGWPW.js";
|
|
50
|
-
import "../../chunks/chunk-
|
|
50
|
+
import "../../chunks/chunk-GQPUILFR.js";
|
|
51
51
|
import "../../chunks/chunk-KVMZSQ3D.js";
|
|
52
52
|
import "../../chunks/chunk-WOORR67N.js";
|
|
53
53
|
import "../../chunks/chunk-PRLL7B63.js";
|
package/bin/api/cli.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runCli
|
|
3
|
-
} from "../chunks/chunk-
|
|
4
|
-
import "../chunks/chunk-
|
|
5
|
-
import "../chunks/chunk-
|
|
6
|
-
import "../chunks/chunk-
|
|
7
|
-
import "../chunks/chunk-
|
|
8
|
-
import "../chunks/chunk-
|
|
3
|
+
} from "../chunks/chunk-445FZZEC.js";
|
|
4
|
+
import "../chunks/chunk-KIWEFRNG.js";
|
|
5
|
+
import "../chunks/chunk-72Y4YMTV.js";
|
|
6
|
+
import "../chunks/chunk-UR4NWYVW.js";
|
|
7
|
+
import "../chunks/chunk-JBSGJR7X.js";
|
|
8
|
+
import "../chunks/chunk-M72KKZIB.js";
|
|
9
9
|
import "../chunks/chunk-2NW7KSPA.js";
|
|
10
|
-
import "../chunks/chunk-
|
|
11
|
-
import "../chunks/chunk-
|
|
12
|
-
import "../chunks/chunk-
|
|
10
|
+
import "../chunks/chunk-VKU5TY5C.js";
|
|
11
|
+
import "../chunks/chunk-RE4GOHSU.js";
|
|
12
|
+
import "../chunks/chunk-65LRZIBP.js";
|
|
13
13
|
import "../chunks/chunk-34H54KEY.js";
|
|
14
14
|
import "../chunks/chunk-JCRGE6AL.js";
|
|
15
15
|
import "../chunks/chunk-XL2DRAYI.js";
|
|
16
|
-
import "../chunks/chunk-
|
|
16
|
+
import "../chunks/chunk-54QPYUM2.js";
|
|
17
17
|
import "../chunks/chunk-XGTHGWPW.js";
|
|
18
|
-
import "../chunks/chunk-
|
|
18
|
+
import "../chunks/chunk-GQPUILFR.js";
|
|
19
19
|
import "../chunks/chunk-KVMZSQ3D.js";
|
|
20
20
|
import "../chunks/chunk-WOORR67N.js";
|
|
21
21
|
import "../chunks/chunk-PRLL7B63.js";
|
package/bin/api/engine.js
CHANGED
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
prepareChange,
|
|
8
8
|
publicEditManifest,
|
|
9
9
|
verifyChange
|
|
10
|
-
} from "../chunks/chunk-
|
|
10
|
+
} from "../chunks/chunk-72Y4YMTV.js";
|
|
11
11
|
import {
|
|
12
12
|
changedSourceFiles,
|
|
13
13
|
sourceRevision
|
|
14
|
-
} from "../chunks/chunk-
|
|
14
|
+
} from "../chunks/chunk-GQPUILFR.js";
|
|
15
15
|
import "../chunks/chunk-KVMZSQ3D.js";
|
|
16
16
|
import "../chunks/chunk-WOORR67N.js";
|
|
17
17
|
import "../chunks/chunk-PRLL7B63.js";
|
package/bin/api/hosting.js
CHANGED
|
@@ -2,12 +2,12 @@ import {
|
|
|
2
2
|
deployVerifiedArtifact,
|
|
3
3
|
prepareQuickRelease,
|
|
4
4
|
prepareVerifiedRelease
|
|
5
|
-
} from "../chunks/chunk-
|
|
5
|
+
} from "../chunks/chunk-UR4NWYVW.js";
|
|
6
6
|
import "../chunks/chunk-2NW7KSPA.js";
|
|
7
|
-
import "../chunks/chunk-
|
|
7
|
+
import "../chunks/chunk-65LRZIBP.js";
|
|
8
8
|
import "../chunks/chunk-JCRGE6AL.js";
|
|
9
9
|
import "../chunks/chunk-XL2DRAYI.js";
|
|
10
|
-
import "../chunks/chunk-
|
|
10
|
+
import "../chunks/chunk-GQPUILFR.js";
|
|
11
11
|
import "../chunks/chunk-KVMZSQ3D.js";
|
|
12
12
|
import "../chunks/chunk-WOORR67N.js";
|
|
13
13
|
import "../chunks/chunk-PRLL7B63.js";
|
|
@@ -11,30 +11,30 @@ import {
|
|
|
11
11
|
runDoctor,
|
|
12
12
|
shareSite,
|
|
13
13
|
synthesizeSite
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-KIWEFRNG.js";
|
|
15
15
|
import {
|
|
16
16
|
verifySite
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-UR4NWYVW.js";
|
|
18
18
|
import {
|
|
19
19
|
upgradeSite
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-JBSGJR7X.js";
|
|
21
21
|
import {
|
|
22
22
|
createSite
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-M72KKZIB.js";
|
|
24
24
|
import {
|
|
25
25
|
explainStage
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-VKU5TY5C.js";
|
|
27
27
|
import {
|
|
28
28
|
runSite,
|
|
29
29
|
statusSite,
|
|
30
30
|
workflowArgvFromFlags
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-RE4GOHSU.js";
|
|
32
32
|
import {
|
|
33
33
|
EXIT_CODES
|
|
34
34
|
} from "./chunk-JCRGE6AL.js";
|
|
35
35
|
import {
|
|
36
36
|
siteInputsFromFlags
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-GQPUILFR.js";
|
|
38
38
|
import {
|
|
39
39
|
buildVerified,
|
|
40
40
|
findHarnessRoot
|
|
@@ -50,9 +50,9 @@ import {
|
|
|
50
50
|
} from "./chunk-T6PE4KN4.js";
|
|
51
51
|
|
|
52
52
|
// src/cli/index.ts
|
|
53
|
-
import { existsSync as existsSync2, readFileSync as
|
|
54
|
-
import { resolve as
|
|
55
|
-
import { spawnSync } from "node:child_process";
|
|
53
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
54
|
+
import { resolve as resolve3 } from "node:path";
|
|
55
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
56
56
|
|
|
57
57
|
// src/cli/events.ts
|
|
58
58
|
var EventStream = class {
|
|
@@ -270,6 +270,106 @@ async function formsCommand(command, opts) {
|
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
+
// src/cli/admin.ts
|
|
274
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
275
|
+
import { resolve as resolve2 } from "node:path";
|
|
276
|
+
import { createRequire } from "node:module";
|
|
277
|
+
import { spawnSync } from "node:child_process";
|
|
278
|
+
import { parseManifest } from "@tryvitris/website-admin-contracts";
|
|
279
|
+
|
|
280
|
+
// src/admin/connection.ts
|
|
281
|
+
async function readConnection(manifest, token, resource, test = false, transport = fetch) {
|
|
282
|
+
if (!manifest.backend || !/^[a-zA-Z0-9_-]{32,200}$/.test(token)) throw Error("A dedicated VITRIS_ADMIN_API_TOKEN and backend connection are required");
|
|
283
|
+
const url = new URL(`/api/admin/v1/${encodeURIComponent(manifest.backend.tenantId)}/${resource}`, manifest.backend.origin);
|
|
284
|
+
if (test) url.searchParams.set("mode", "test");
|
|
285
|
+
const response = await transport(url, { redirect: "manual", signal: AbortSignal.timeout(12e3), headers: { authorization: `Bearer ${token}`, "x-vitris-installation": manifest.installationId, "x-vitris-site-origin": manifest.site.origin } });
|
|
286
|
+
if (!response.ok) throw Error(`Admin v1 ${resource} rejected (${response.status}); connection is not verified`);
|
|
287
|
+
const reader = response.body?.getReader();
|
|
288
|
+
if (!reader) throw Error("Empty backend response");
|
|
289
|
+
const chunks = [];
|
|
290
|
+
let size = 0;
|
|
291
|
+
for (; ; ) {
|
|
292
|
+
const { done, value } = await reader.read();
|
|
293
|
+
if (done) break;
|
|
294
|
+
size += value.length;
|
|
295
|
+
if (size > 4e6) {
|
|
296
|
+
await reader.cancel();
|
|
297
|
+
throw Error("Backend response too large");
|
|
298
|
+
}
|
|
299
|
+
chunks.push(value);
|
|
300
|
+
}
|
|
301
|
+
const bytes = new Uint8Array(size);
|
|
302
|
+
let offset = 0;
|
|
303
|
+
for (const chunk of chunks) {
|
|
304
|
+
bytes.set(chunk, offset);
|
|
305
|
+
offset += chunk.length;
|
|
306
|
+
}
|
|
307
|
+
const data = JSON.parse(new TextDecoder().decode(bytes));
|
|
308
|
+
if (data?.schemaVersion !== 1 || data.installationId !== manifest.installationId || data.tenantId !== manifest.backend.tenantId || data.siteOrigin !== manifest.site.origin) throw Error("Backend contract or installation identity mismatch");
|
|
309
|
+
if (resource === "status") {
|
|
310
|
+
if (!Array.isArray(data.scopes)) throw Error("Invalid backend scopes");
|
|
311
|
+
} else {
|
|
312
|
+
if (data.source !== "connected" || !Array.isArray(data.rows) || data.rows.length > 200) throw Error("Invalid backend records");
|
|
313
|
+
if (resource === "leads" && data.rows.some((r) => ["id", "name", "email", "message", "created_at", "status"].some((k) => typeof r?.[k] !== "string"))) throw Error("Invalid lead response");
|
|
314
|
+
if (resource === "analytics" && data.rows.some((r) => typeof r?.day !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(r.day) || ["views", "visitors", "enquiries"].some((k) => typeof r[k] !== "number" || !Number.isFinite(r[k]) || r[k] < 0))) throw Error("Invalid analytics response");
|
|
315
|
+
}
|
|
316
|
+
return data;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// src/cli/admin.ts
|
|
320
|
+
function mergeAdminGrant(existing, addition) {
|
|
321
|
+
const object = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
322
|
+
if (!object(existing) || !object(addition) || Object.keys(addition).length !== 1) throw Error("Expected complete grant map and one installation grant");
|
|
323
|
+
for (const [id, raw] of Object.entries({ ...existing, ...addition })) {
|
|
324
|
+
const g = raw;
|
|
325
|
+
if (!/^[a-z][a-z0-9-]{0,50}$/.test(id) || !object(g) || typeof g.tenantId !== "string" || !/^[a-zA-Z0-9_-]{1,100}$/.test(g.tenantId) || typeof g.tokenHash !== "string" || !/^[a-f0-9]{64}$/.test(g.tokenHash) || typeof g.siteOrigin !== "string" || new URL(g.siteOrigin).origin !== g.siteOrigin || !g.siteOrigin.startsWith("https://") || !Array.isArray(g.scopes) || g.scopes.some((s) => !["leads:read", "analytics:read", "leads:write"].includes(s)) || Object.keys(g).some((k) => !["tenantId", "siteOrigin", "tokenHash", "scopes"].includes(k))) throw Error("Invalid hashed grant; raw credentials are forbidden");
|
|
326
|
+
}
|
|
327
|
+
for (const id of Object.keys(addition)) if (Object.hasOwn(existing, id) && JSON.stringify(existing[id]) !== JSON.stringify(addition[id])) throw Error("Grant already exists with different authority; rotate explicitly");
|
|
328
|
+
return { ...existing, ...addition };
|
|
329
|
+
}
|
|
330
|
+
async function adminCommand(command, options) {
|
|
331
|
+
try {
|
|
332
|
+
const site = resolve2(options.site || ".");
|
|
333
|
+
if (command === "grant") {
|
|
334
|
+
if (!options.dir || !options.grants || !options.out || options.dryRun) throw Error("admin grant requires --dir, --grants COMPLETE_MAP_FILE and --out NEW_IGNORED_FILE");
|
|
335
|
+
const addition = JSON.parse(readFileSync2(resolve2(options.dir, ".vitris/backend-grant.json"), "utf8"));
|
|
336
|
+
const manifest2 = parseManifest(JSON.parse(readFileSync2(resolve2(options.dir, "admin.config.json"), "utf8")));
|
|
337
|
+
const grant = addition[manifest2.installationId];
|
|
338
|
+
if (!grant || grant.tenantId !== manifest2.backend?.tenantId || grant.siteOrigin !== manifest2.site.origin) throw Error("Grant and installation identity differ");
|
|
339
|
+
const merged = mergeAdminGrant(JSON.parse(readFileSync2(resolve2(options.grants), "utf8")), addition);
|
|
340
|
+
writeFileSync2(resolve2(options.out), JSON.stringify(merged, null, 2) + "\n", { flag: "wx", mode: 384 });
|
|
341
|
+
return { ok: true, exitCode: 0, summary: "Merged hashed grants into the new local file. No backend secret changed; review and install explicitly through secret stdin." };
|
|
342
|
+
}
|
|
343
|
+
if (command === "setup") {
|
|
344
|
+
if (!options.dir || !options.domain || !options.tenant || !!options.plan === !!options.apply) throw Error("admin setup requires --dir, --domain, --tenant and exactly one of --plan or --apply");
|
|
345
|
+
if (options.dryRun) throw Error("Use --plan for local setup planning; --dry-run does not write a setup plan");
|
|
346
|
+
const args = ["setup", "--site", site, "--dir", resolve2(options.dir), "--domain", options.domain, "--tenant", options.tenant, options.apply ? "--apply" : "--plan"];
|
|
347
|
+
if (options.website) args.push("--website", options.website);
|
|
348
|
+
if (options.backendOrigin) args.push("--backend-origin", options.backendOrigin);
|
|
349
|
+
if (options.leadWrite) args.push("--lead-write");
|
|
350
|
+
const entry = createRequire(import.meta.url).resolve("@tryvitris/website-admin-harness/cli");
|
|
351
|
+
const output = spawnSync(process.execPath, [entry, ...args], { encoding: "utf8", windowsHide: true, maxBuffer: 1024 * 1024 });
|
|
352
|
+
if (output.status !== 0) throw Error(output.error?.message || output.stderr.trim() || "Admin setup failed");
|
|
353
|
+
return { ok: true, exitCode: 0, summary: output.stdout.trim() };
|
|
354
|
+
}
|
|
355
|
+
if (!["status", "verify"].includes(command)) throw Error("Use vitris admin setup|status|verify|grant");
|
|
356
|
+
const launch = JSON.parse(readFileSync2(resolve2(site, "launch.json"), "utf8"));
|
|
357
|
+
if (command === "status") return { ok: true, exitCode: 0, summary: launch.admin ? "Local admin metadata exists. Backend, authentication and deployment are unverified; run admin verify with --dir." : "No admin connection. Read docs/ADMIN.md; scaffolding alone does not enable collection.", data: { connection: launch.admin || null, verified: false } };
|
|
358
|
+
if (!options.dir) throw Error("admin verify requires --dir ADMIN_DIRECTORY");
|
|
359
|
+
const manifest = parseManifest(JSON.parse(readFileSync2(resolve2(options.dir, "admin.config.json"), "utf8")));
|
|
360
|
+
if (!launch.admin || manifest.installationId !== launch.admin.installationId || manifest.site.id !== launch.admin.siteId || manifest.adminOrigin !== launch.admin.url || manifest.site.origin !== launch.admin.siteOrigin || manifest.backend?.tenantId !== launch.admin.tenantId) throw Error("Website and admin installation identities differ");
|
|
361
|
+
const token = process.env.VITRIS_ADMIN_API_TOKEN || "";
|
|
362
|
+
const status = await readConnection(manifest, token, "status");
|
|
363
|
+
if (!["leads:read", "analytics:read"].every((scope) => status.scopes.includes(scope))) throw Error("Default read scopes are missing");
|
|
364
|
+
const leads = await readConnection(manifest, token, "leads");
|
|
365
|
+
const tests = await readConnection(manifest, token, "leads", true);
|
|
366
|
+
const analytics = await readConnection(manifest, token, "analytics");
|
|
367
|
+
return { ok: true, exitCode: 0, summary: "Admin v1 data connection verified. Authentication, collection and deployment require separate acceptance checks.", data: { verified: true, productionLeads: leads.rows.length, testSubmissions: tests.rows.length, analyticsDays: analytics.rows.length, scopes: status.scopes } };
|
|
368
|
+
} catch (error) {
|
|
369
|
+
return { ok: false, exitCode: EXIT_CODES.PRECONDITION_FAILED, error: error instanceof Error ? error.message : String(error) };
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
273
373
|
// src/cli/index.ts
|
|
274
374
|
function parseArgs(args) {
|
|
275
375
|
const command = [];
|
|
@@ -309,9 +409,9 @@ function parseArgs(args) {
|
|
|
309
409
|
}
|
|
310
410
|
function resolvePrecedence(flags, siteRoot) {
|
|
311
411
|
const merged = {};
|
|
312
|
-
if (siteRoot && existsSync2(
|
|
412
|
+
if (siteRoot && existsSync2(resolve3(siteRoot, "launch.json"))) {
|
|
313
413
|
try {
|
|
314
|
-
const launch = JSON.parse(
|
|
414
|
+
const launch = JSON.parse(readFileSync3(resolve3(siteRoot, "launch.json"), "utf8"));
|
|
315
415
|
if (typeof launch.ownerEmail === "string") merged.owner = launch.ownerEmail;
|
|
316
416
|
if (typeof launch.slug === "string") merged.slug = launch.slug;
|
|
317
417
|
if (typeof launch.name === "string") merged.name = launch.name;
|
|
@@ -320,9 +420,9 @@ function resolvePrecedence(flags, siteRoot) {
|
|
|
320
420
|
}
|
|
321
421
|
if (flags.config && typeof flags.config === "string") {
|
|
322
422
|
try {
|
|
323
|
-
const configPath =
|
|
423
|
+
const configPath = resolve3(flags.config);
|
|
324
424
|
if (existsSync2(configPath)) {
|
|
325
|
-
const cfg = JSON.parse(
|
|
425
|
+
const cfg = JSON.parse(readFileSync3(configPath, "utf8"));
|
|
326
426
|
for (const [k, v] of Object.entries(cfg)) {
|
|
327
427
|
if (typeof v === "string" || typeof v === "boolean") merged[k] = v;
|
|
328
428
|
}
|
|
@@ -354,7 +454,7 @@ async function runCli(argv) {
|
|
|
354
454
|
}
|
|
355
455
|
const stream = new EventStream(outputMode);
|
|
356
456
|
if (flags.version || flags.v) {
|
|
357
|
-
stream.log(`@tryvitris/website-harness v${JSON.parse(
|
|
457
|
+
stream.log(`@tryvitris/website-harness v${JSON.parse(readFileSync3(resolve3(findHarnessRoot(), "package.json"), "utf8")).version}`);
|
|
358
458
|
return EXIT_CODES.SUCCESS;
|
|
359
459
|
}
|
|
360
460
|
if (flags.help || flags.h || command.length === 0) {
|
|
@@ -363,6 +463,7 @@ async function runCli(argv) {
|
|
|
363
463
|
Usage: vitris <command> [options]
|
|
364
464
|
|
|
365
465
|
Commands:
|
|
466
|
+
admin setup|status|verify Separate business admin; explicit cloud rollout
|
|
366
467
|
forms inspect|setup|status Inventory, provision, and check contact forms
|
|
367
468
|
doctor Check environment, dependencies, and site health
|
|
368
469
|
site create --dir DIR --slug SLUG Scaffold a new customer site repo (--from URL or --blank)
|
|
@@ -391,6 +492,7 @@ Options:
|
|
|
391
492
|
--template-release RELEASE Optional template revision
|
|
392
493
|
--content TEXT Additional business content or instructions
|
|
393
494
|
--content-file FILE UTF-8 content file (instead of --content)
|
|
495
|
+
--admin New-site opt-in: prepare sibling admin next steps
|
|
394
496
|
--backend-origin URL Backend origin for a new site
|
|
395
497
|
--site DIR Site directory path (default: current directory)
|
|
396
498
|
--json Output single structured JSON result at completion
|
|
@@ -413,7 +515,10 @@ Options:
|
|
|
413
515
|
const secondary = command[1];
|
|
414
516
|
let result;
|
|
415
517
|
try {
|
|
416
|
-
if (primary === "
|
|
518
|
+
if (primary === "admin") {
|
|
519
|
+
const value = (key) => typeof opts[key] === "string" ? opts[key] : void 0;
|
|
520
|
+
result = await adminCommand(secondary || "status", { site: siteRoot, dir: value("dir"), domain: value("domain"), website: value("website"), tenant: value("tenant"), backendOrigin: value("backend-origin"), plan: !!opts.plan, apply: !!opts.apply, leadWrite: !!opts["lead-write"], grants: value("grants"), out: value("out"), dryRun });
|
|
521
|
+
} else if (primary === "forms") {
|
|
417
522
|
const list = (name) => typeof opts[name] === "string" ? opts[name].split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
418
523
|
result = await formsCommand(secondary || "status", { site: siteRoot, tenant: typeof opts.tenant === "string" ? opts.tenant : void 0, backendOrigin: typeof opts["backend-origin"] === "string" ? opts["backend-origin"] : void 0, origins: list("origins"), recipients: list("recipients"), mode: typeof opts.mode === "string" ? opts.mode : void 0, apply: !!opts.apply, testKeys: !!opts["test-keys"], cloudflare: !!opts.cloudflare, worker: typeof opts.worker === "string" ? opts.worker : void 0, config: typeof opts["forms-config"] === "string" ? opts["forms-config"] : void 0, dryRun });
|
|
419
524
|
} else if (primary === "doctor") {
|
|
@@ -425,6 +530,7 @@ Options:
|
|
|
425
530
|
} else if (primary === "site" && secondary === "create") {
|
|
426
531
|
result = await createSite({
|
|
427
532
|
...siteInputsFromFlags(opts),
|
|
533
|
+
admin: !!opts.admin,
|
|
428
534
|
dir: typeof opts.dir === "string" ? opts.dir : "",
|
|
429
535
|
slug: typeof opts.slug === "string" ? opts.slug : "",
|
|
430
536
|
name: typeof opts.name === "string" ? opts.name : void 0,
|
|
@@ -559,14 +665,14 @@ Options:
|
|
|
559
665
|
if (sub === "doctor") {
|
|
560
666
|
result = await runDoctor({ provider: "cloudflare", operation: "pages" });
|
|
561
667
|
} else if (sub === "whoami") {
|
|
562
|
-
const probe =
|
|
668
|
+
const probe = spawnSync2(process.platform === "win32" ? "npx.cmd" : "npx", ["wrangler", "whoami"], { encoding: "utf8" });
|
|
563
669
|
result = {
|
|
564
670
|
ok: probe.status === 0,
|
|
565
671
|
exitCode: probe.status === 0 ? EXIT_CODES.SUCCESS : EXIT_CODES.CONFIG_OR_CLI_ERROR,
|
|
566
672
|
summary: probe.stdout || probe.stderr
|
|
567
673
|
};
|
|
568
674
|
} else if (sub === "login") {
|
|
569
|
-
const probe =
|
|
675
|
+
const probe = spawnSync2(process.platform === "win32" ? "npx.cmd" : "npx", ["wrangler", "login"], { stdio: "inherit" });
|
|
570
676
|
result = {
|
|
571
677
|
ok: probe.status === 0,
|
|
572
678
|
exitCode: probe.status === 0 ? EXIT_CODES.SUCCESS : EXIT_CODES.GENERIC_ERROR,
|
|
@@ -580,7 +686,7 @@ Options:
|
|
|
580
686
|
};
|
|
581
687
|
}
|
|
582
688
|
} else if (primary === "knowledge" && secondary === "explain") {
|
|
583
|
-
const explained = explainStage(
|
|
689
|
+
const explained = explainStage(resolve3(typeof opts.site === "string" ? opts.site : "."), typeof opts.stage === "string" ? opts.stage : "direction");
|
|
584
690
|
result = { ok: true, exitCode: EXIT_CODES.SUCCESS, summary: explained.text, data: explained };
|
|
585
691
|
} else if (primary === "share") {
|
|
586
692
|
result = await shareSite({
|
|
@@ -5172,7 +5172,7 @@ ${message}`;
|
|
|
5172
5172
|
}
|
|
5173
5173
|
|
|
5174
5174
|
// src/agent/refine.ts
|
|
5175
|
-
import { parseFragment as
|
|
5175
|
+
import { parseFragment as parseFragment4 } from "parse5";
|
|
5176
5176
|
|
|
5177
5177
|
// src/editing/identity.ts
|
|
5178
5178
|
function attribute(attrs, name) {
|
|
@@ -5182,6 +5182,33 @@ function elementMatchesIdentity(attrs, identity) {
|
|
|
5182
5182
|
return attribute(attrs, "data-vitris-field") === identity || attribute(attrs, "id") === identity;
|
|
5183
5183
|
}
|
|
5184
5184
|
|
|
5185
|
+
// src/agent/refine-document-checks.ts
|
|
5186
|
+
import { parseFragment as parseFragment3 } from "parse5";
|
|
5187
|
+
function hasRefinementPlaceholder(html) {
|
|
5188
|
+
const token = /\[TODO\b|\[INSERT\b|\[TBD\b|\bundefined\b|\bNaN\b/i;
|
|
5189
|
+
const contains = (node) => {
|
|
5190
|
+
if (["script", "style", "#comment"].includes(node.nodeName)) return false;
|
|
5191
|
+
if (node.nodeName === "#text" && token.test(node.value || "")) return true;
|
|
5192
|
+
if (node.attrs?.some((attr2) => !/^on/i.test(attr2.name) && attr2.name !== "style" && token.test(attr2.value))) return true;
|
|
5193
|
+
return (node.childNodes || []).some(contains) || !!(node.content && contains(node.content));
|
|
5194
|
+
};
|
|
5195
|
+
return contains(parseFragment3(html));
|
|
5196
|
+
}
|
|
5197
|
+
async function settleRefinementImages(page, timeoutMs = 5e3) {
|
|
5198
|
+
await page.evaluate(`(async () => {
|
|
5199
|
+
let timer;
|
|
5200
|
+
try {
|
|
5201
|
+
await Promise.race([
|
|
5202
|
+
Promise.all([...document.images].filter(image => image.getClientRects().length).map(image => {
|
|
5203
|
+
image.loading = 'eager';
|
|
5204
|
+
return image.decode().catch(() => {});
|
|
5205
|
+
})),
|
|
5206
|
+
new Promise(resolve => { timer = setTimeout(resolve, ${Math.max(0, timeoutMs)}); })
|
|
5207
|
+
]);
|
|
5208
|
+
} finally { clearTimeout(timer); }
|
|
5209
|
+
})()`);
|
|
5210
|
+
}
|
|
5211
|
+
|
|
5185
5212
|
// src/agent/refine.ts
|
|
5186
5213
|
var CHANGE_SCOPES = ["business", "copy", "pages", "direction", "media"];
|
|
5187
5214
|
var COPY_REQUEST_NODES = ["copy", "pages"];
|
|
@@ -6350,7 +6377,7 @@ function sourceTargetText(root, target) {
|
|
|
6350
6377
|
const matches = [];
|
|
6351
6378
|
const content = (node) => node.nodeName === "#text" ? node.value || "" : (node.childNodes || []).map(content).join("");
|
|
6352
6379
|
for (const file of candidates.filter((file2) => existsSync23(file2))) {
|
|
6353
|
-
const tree =
|
|
6380
|
+
const tree = parseFragment4(readFileSync18(file, "utf8"));
|
|
6354
6381
|
const walk = (node) => {
|
|
6355
6382
|
if (elementMatchesIdentity(node.attrs, id)) matches.push(content(node).trim());
|
|
6356
6383
|
for (const child of node.childNodes || []) walk(child);
|
|
@@ -6518,7 +6545,7 @@ function verifyFulfillment(root, request, baseline) {
|
|
|
6518
6545
|
const htmlFiles = files(distDir).filter((f) => f.endsWith(".html"));
|
|
6519
6546
|
for (const file of htmlFiles) {
|
|
6520
6547
|
const content = readFileSync18(file, "utf8");
|
|
6521
|
-
if (
|
|
6548
|
+
if (hasRefinementPlaceholder(content)) {
|
|
6522
6549
|
errors.push(`Unresolved placeholder content found in ${file.slice(distDir.length).replace(/\\/g, "/")}`);
|
|
6523
6550
|
}
|
|
6524
6551
|
if (/<h[1-6]>\s*<\/h[1-6]>/i.test(content)) {
|
|
@@ -6543,7 +6570,7 @@ function verifyFulfillment(root, request, baseline) {
|
|
|
6543
6570
|
errors.push(`Criterion ${criterion.id} route ${route} was not built`);
|
|
6544
6571
|
continue;
|
|
6545
6572
|
}
|
|
6546
|
-
const tree =
|
|
6573
|
+
const tree = parseFragment4(readFileSync18(output, "utf8"));
|
|
6547
6574
|
const matches = [];
|
|
6548
6575
|
const walk = (node) => {
|
|
6549
6576
|
if (elementMatchesIdentity(node.attrs, id)) matches.push(node);
|
|
@@ -6634,7 +6661,7 @@ function verifyFulfillment(root, request, baseline) {
|
|
|
6634
6661
|
if (intentKeywords.length > 0) {
|
|
6635
6662
|
const isRemoval = /\b(remove|delete|drop|hide|eliminate)\b/i.test(request.instruction);
|
|
6636
6663
|
const visibleText = (node) => ["script", "style", "svg", "noscript"].includes(node.tagName) ? "" : node.nodeName === "#text" ? node.value : (node.childNodes || []).map(visibleText).join(" ");
|
|
6637
|
-
const currentAllText = htmlFiles.map((f) => visibleText(
|
|
6664
|
+
const currentAllText = htmlFiles.map((f) => visibleText(parseFragment4(readFileSync18(f, "utf8"))).toLowerCase()).join(" ");
|
|
6638
6665
|
const copyPlanPath = join22(root, "onboarding/copy-plan.json");
|
|
6639
6666
|
const copyText = existsSync23(copyPlanPath) ? readFileSync18(copyPlanPath, "utf8").toLowerCase() : "";
|
|
6640
6667
|
const fullText = currentAllText + " " + copyText;
|
|
@@ -6818,6 +6845,7 @@ async function verifyRefine(opts) {
|
|
|
6818
6845
|
const url = `http://127.0.0.1:${address.port}${route === "/" ? "" : route}`;
|
|
6819
6846
|
try {
|
|
6820
6847
|
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 15e3 });
|
|
6848
|
+
await settleRefinementImages(page);
|
|
6821
6849
|
const observed = await page.evaluate(`(() => {
|
|
6822
6850
|
const errors = [];
|
|
6823
6851
|
if (document.documentElement.scrollWidth > innerWidth + 2) errors.push('Live browser horizontal overflow detected at ${width}px');
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
prepareChange,
|
|
3
3
|
verifyChange
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-72Y4YMTV.js";
|
|
5
5
|
import {
|
|
6
6
|
deployVerifiedArtifact,
|
|
7
7
|
prepareQuickRelease,
|
|
8
8
|
prepareVerifiedRelease
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-UR4NWYVW.js";
|
|
10
10
|
import {
|
|
11
11
|
creativeRunStatus,
|
|
12
12
|
currentRunId,
|
|
@@ -14,10 +14,10 @@ import {
|
|
|
14
14
|
runCreativeProfile,
|
|
15
15
|
startDesignReview,
|
|
16
16
|
submitFeedback
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-RE4GOHSU.js";
|
|
18
18
|
import {
|
|
19
19
|
verifyPreservedSite
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-65LRZIBP.js";
|
|
21
21
|
import {
|
|
22
22
|
EXIT_CODES
|
|
23
23
|
} from "./chunk-JCRGE6AL.js";
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
prepareRefine,
|
|
36
36
|
runWorkflow,
|
|
37
37
|
verifyRefine
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-GQPUILFR.js";
|
|
39
39
|
import {
|
|
40
40
|
parseGraphYaml
|
|
41
41
|
} from "./chunk-BBV7PMFC.js";
|