@realsee/vr-cli 0.2.0 → 0.2.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 +4 -2
- package/dist/cli.mjs +1 -1
- package/package.json +1 -1
- package/templates/base/scripts/pairing.test.mjs +36 -11
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ CI 和自动化生成流程应使用明确版本。当前版本示例:
|
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
44
|
npm exec --yes \
|
|
45
|
-
--package=@realsee/vr-cli@0.2.
|
|
45
|
+
--package=@realsee/vr-cli@0.2.1 \
|
|
46
46
|
-- vr-cli create ./my-vrapp --no-install
|
|
47
47
|
|
|
48
48
|
cd ./my-vrapp
|
|
@@ -224,7 +224,9 @@ nvm use
|
|
|
224
224
|
|
|
225
225
|
- npm 包:`@realsee/vr-cli`。
|
|
226
226
|
- 可执行命令:`vr-cli`。
|
|
227
|
-
- 当前版本:`0.2.
|
|
227
|
+
- 当前版本:`0.2.1`。
|
|
228
|
+
|
|
229
|
+
`0.2.1` 修复生成工程在 Windows 上的配对自测:POSIX 权限断言仅在对应系统执行,测试先恢复工作目录再清理自身临时目录,并增加多 DEMO 隔离清理回归。
|
|
228
230
|
|
|
229
231
|
`0.x` 版本可能调整模板、检查规则和生成内容。生产环境与 CI 应固定 CLI 版本,并在升级时审查生成差异。
|
|
230
232
|
|
package/dist/cli.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{parseArgs as Gr}from"node:util";var H="@realsee/vr-cli";var D="https://registry.npmjs.org/",$="public";var j="0.2.
|
|
1
|
+
import{parseArgs as Gr}from"node:util";var H="@realsee/vr-cli";var D="https://registry.npmjs.org/",$="public";var j="0.2.1",O=["portable","all","none"],Z="22.23.2",V="22.18.0",oe=`>=${V}`,K="11.19.0",ie=">=11.19.0",ne=`npm@${K}`,Q="node-vite-bff-v1",P="adm-zip",L="0.6.0",le=Object.freeze(["REALSEE_GATEWAY_APP_KEY","REALSEE_GATEWAY_APP_SECRET"]),z=Object.freeze({"@fastify/static":"10.1.3","@realsee/dnalogel":"3.81.2","@realsee/five":"6.8.9","@realsee/vapor":"0.2.12",fastify:"5.12.1",react:"18.3.1","react-dom":"18.3.1",three:"0.117.1"}),U=Object.freeze({three:z.three}),ce=(r,t)=>r in U?t:`>=${t}`,X=r=>{if(typeof r!="string")return null;let t=/^>=(\d+\.\d+\.\d+(?:[-+][\w.]+)?)$/.exec(r.trim());return t?t[1]:null},N=(r,t)=>{let e=n=>{if(typeof n!="string")return null;let i=/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(n.trim());return i?{core:i.slice(1,4).map(Number),prerelease:i[4]??null}:null},a=e(r),o=e(t);if(!a||!o)return!1;for(let n=0;n<3;n+=1){let i=a.core[n]-o.core[n];if(i>0)return!0;if(i<0)return!1}return a.prerelease===o.prerelease||a.prerelease===null?!0:o.prerelease===null?!1:a.prerelease.localeCompare(o.prerelease,"en",{numeric:!0})>=0},J=Object.freeze({"@eslint/js":"10.0.1","@types/node":"24.13.3","@types/react":"18.3.12","@types/react-dom":"18.3.1","@vitejs/plugin-react":"4.3.1",concurrently:"10.0.5",eslint:"10.9.1","eslint-plugin-react-hooks":"7.1.1",globals:"17.11.0",prettier:"3.9.6",typescript:"5.9.3","typescript-eslint":"8.68.0",vite:"5.4.14"}),pe=Object.freeze(["APP_ID","REALSEE_GATEWAY_ENDPOINT","REALSEE_GATEWAY_APP_KEY","REALSEE_GATEWAY_APP_SECRET","REALSEE_SOURCE"]),ue=Object.freeze(["# REALSEE_DEFAULT_RESOURCE_CODE=example-resource-code"]),fe=Object.freeze(["REALSEE_GATEWAY_APP_KEY","REALSEE_GATEWAY_APP_SECRET"]),de=Object.freeze(["REALSEE_GATEWAY_APP_SECRET"]),me=Object.freeze(["package.json","package-lock.json","deploy.json","README.md","index.html","tsconfig.json","tsconfig.server.json","vite.config.ts",".editorconfig",".nvmrc",".node-version",".npmrc",".prettierignore","eslint.config.mjs","prettier.config.mjs","scripts/setup-config.mjs","scripts/setup-config.test.mjs","scripts/setup.mjs","scripts/pairing.mjs","scripts/pairing.test.mjs","scripts/project-identity.mjs","scripts/project-identity.test.mjs","scripts/package-vibe-release.mjs","server/types.ts","server/config.ts","server/check-config.ts","server/realsee-open-api.ts","server/work-service.ts","server/work-route.ts","server/app.ts","server/index.ts","shared/model-floorplan.ts","src/main.tsx","src/app/App.tsx","src/shared/useElementSize.ts","src/vr/five/api.ts","src/vr/five/fiveConfig.ts","src/vr/five/SpatialRuntimeProvider.tsx","src/vr/five/initialState.ts","src/vr/plugins/dnalogel.ts","src/vr/plugins/SpatialPluginRegistry.ts","src/vr/plugins/examples/FeatureOwnedMeshPlugin.ts","src/vr/work/WorkSource.ts","src/features/floorplan/ModelFloorplanFeature.ts","src/features/navigation/ModeSwitcher.tsx","src/features/tags/PanoTagFeature.ts","src/ui/ViewerOverlay.tsx","src/styles.css",".env.example",".gitignore",".vr-cli.json"]);import{spawn as or}from"node:child_process";import{rename as Re,writeFile as ir}from"node:fs/promises";import A from"node:path";import{fileURLToPath as nr}from"node:url";import _ from"node:path";import{mkdir as er,readFile as rr,writeFile as we}from"node:fs/promises";import{mkdir as he,readFile as Xe,readdir as ee,stat as ge,writeFile as ve}from"node:fs/promises";import x from"node:path";var Je=new Set(["",".css",".html",".json",".md",".mjs",".ts",".tsx",".txt",".yml",".yaml"]);function re(r,t){return Object.entries(t).reduce((e,[a,o])=>e.replaceAll(`{{${a}}}`,String(o)).replaceAll(`__${a}__`,String(o)),r)}async function f(r){try{return await ge(r),!0}catch(t){if(t?.code==="ENOENT")return!1;throw t}}async function Ee(r){if(!await f(r)){await he(r,{recursive:!0});return}if(!(await ge(r)).isDirectory())throw new Error(`\u76EE\u6807\u8DEF\u5F84\u4E0D\u662F\u76EE\u5F55\uFF1A${r}`);if((await ee(r)).length)throw new Error(`\u76EE\u6807\u76EE\u5F55\u5FC5\u987B\u4E3A\u7A7A\uFF1A${r}`)}async function R(r,t,e={}){if(!await f(r))throw new Error(`\u6A21\u677F\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${r}`);await he(t,{recursive:!0});for(let a of await ee(r,{withFileTypes:!0})){let o=x.join(r,a.name),n=x.join(t,a.name);if(a.isDirectory()){await R(o,n,e);continue}if(!a.isFile())continue;let i=x.extname(a.name),v=await Xe(o);if(!Je.has(i)){await ve(n,v);continue}await ve(n,re(v.toString("utf8"),e))}}async function W(r,t=r){let e=[];for(let a of await ee(t,{withFileTypes:!0})){let o=x.join(t,a.name);a.isDirectory()?e.push(...await W(r,o)):a.isFile()&&e.push(x.relative(r,o).split(x.sep).join("/"))}return e.sort()}var Se=Object.freeze(["realsee-vr-app","realsee-image-resources","threejs-r117-five"]),tr=Object.freeze(["realsee-vr-app/SKILL.md","realsee-vr-app/references/dnalogel-api-capabilities.md","realsee-vr-app/references/dnalogel-openapi-recipes.md","realsee-vr-app/references/continuous-improvement.md","realsee-vr-app/references/five-api-capabilities.md","realsee-vr-app/references/five-runtime.md","realsee-vr-app/references/official-docs.md","realsee-vr-app/references/plugin-lifecycle.md","realsee-vr-app/references/ui-and-validation.md","realsee-vr-app/references/vapor-dom-layer.md","realsee-vr-app/scripts/verify-project.mjs","realsee-image-resources/SKILL.md","realsee-image-resources/references/cors-and-three-textures.md","realsee-image-resources/references/data-wanxiang.md","realsee-image-resources/references/five-image-url-transform.md","realsee-image-resources/references/source-and-domain-policy.md","threejs-r117-five/SKILL.md","threejs-r117-five/references/five-ownership.md","threejs-r117-five/references/r117-capabilities.md","threejs-r117-five/references/r117-version-guards.md"]),te=r=>tr.map(t=>_.posix.join(r,t)),ke=Object.freeze(["AGENTS.md",...te(".agents/skills")]),sr=Object.freeze([".agents/README.md",...te(".claude/skills"),...te(".kiro/skills"),"CLAUDE.md",".cursor/rules/realsee-vr.mdc",".github/copilot-instructions.md",".kiro/steering/realsee-vr.md"]),ye=Object.freeze([".agents",".claude/skills",".kiro/skills",".cursor/rules",".kiro/steering"]),be=Object.freeze(["AGENTS.md","CLAUDE.md",".github/copilot-instructions.md"]),_e=Object.freeze([".claude/skills",".kiro/skills",".cursor/rules",".kiro/steering"]),Ae=Object.freeze([".agents/README.md","CLAUDE.md",".github/copilot-instructions.md"]),ar=Object.freeze([...ke,...sr]);function Y(r){if(r==="none")return[];if(r==="portable")return[...ke];if(r==="all")return[...ar];throw new Error(`\u672A\u77E5 agent assets \u6A21\u5F0F\uFF1A${r}`)}async function Oe({toolRoot:r,targetRoot:t,mode:e,tokens:a}){if(e==="none")return[];for(let n of Se)await R(_.join(r,"skills",n),_.join(t,".agents","skills",n),a);let o=await rr(_.join(r,"agent-assets","AGENTS.md"),"utf8");if(await we(_.join(t,"AGENTS.md"),re(o,a)),e!=="all")return Y(e);for(let n of[".claude/skills",".kiro/skills"])for(let i of Se)await R(_.join(r,"skills",i),_.join(t,n,i),a);return await R(_.join(r,"agent-assets","adapters"),t,a),await er(_.join(t,".agents"),{recursive:!0}),await we(_.join(t,".agents","README.md"),"# Agent assets\n\n`skills/realsee-vr-app`, `skills/realsee-image-resources` and `skills/threejs-r117-five` are the three canonical portable Skills. Vendor-specific copies are generated; edit the canonical copies and regenerate adapters.\n"),Y(e)}var lr=!0,se=nr(new URL(lr?"..":"../..",import.meta.url)),cr=process.platform==="win32"?"npm.cmd":"npm";function pr(r,t){let e=t||A.basename(r),a=e.trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^-+|-+$/g,"");if(!a||!/^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/.test(a))throw new Error(`\u65E0\u6CD5\u751F\u6210\u5408\u6CD5 npm \u5305\u540D\uFF1A${e}`);return a}function ur(r){return new Promise((t,e)=>{let a=or(cr,["ci"],{cwd:r,stdio:"inherit",shell:!1});a.once("error",e),a.once("exit",(o,n)=>{o===0?t():e(new Error(`npm ci \u5931\u8D25\uFF08code=${o??"null"}, signal=${n??"none"}\uFF09`))})})}async function Te({directory:r,preset:t,name:e,agentAssets:a="portable",install:o=!0}){if(!r)throw new Error("\u7F3A\u5C11\u76EE\u6807\u76EE\u5F55\u3002\u7528\u6CD5\uFF1Avr-cli create <directory>");if(t&&t!=="viewer")throw new Error("--preset \u6682\u65F6\u53EA\u652F\u6301 viewer\u3002");if(!O.includes(a))throw new Error(`\u672A\u77E5 agent assets \u6A21\u5F0F\uFF1A${a}\u3002\u53EF\u9009\u503C\uFF1A${O.join(", ")}`);let n=A.resolve(r),i=pr(n,e),v={PACKAGE_NAME:i,TOOL_VERSION:j};await Ee(n),await R(A.join(se,"templates","base"),n,v),await Re(A.join(n,".gitignore.template"),A.join(n,".gitignore")),await Re(A.join(n,".npmrc.template"),A.join(n,".npmrc")),await R(A.join(se,"templates","viewer"),n,v);let u=await Oe({toolRoot:se,targetRoot:n,mode:a,tokens:v}),d={schemaVersion:1,toolVersion:j,preset:"viewer",packageName:i,agentAssets:a,generatedAt:new Date().toISOString()};return await ir(A.join(n,".vr-cli.json"),`${JSON.stringify(d,null,2)}
|
|
2
2
|
`),o&&await ur(n),{targetRoot:n,packageName:i,installedAgentAssets:u,installed:o}}import{execFile as fr}from"node:child_process";import{readFile as w,readdir as dr,stat as mr}from"node:fs/promises";import p from"node:path";import{promisify as vr}from"node:util";var hr=vr(fr),s=(r,t,e,a)=>({level:r,code:t,message:e,...a?{detail:a}:{}}),je=r=>/(?:^|_)(?:AK|SK|SECRET|TOKEN|PASSWORD|CREDENTIALS?)(?:_|$)/i.test(r),gr=Object.freeze([/\bAPP_ID\b/,/\bREALSEE_(?:GATEWAY_[A-Z0-9_]+|SOURCE|ACCESS_TOKEN)\b/,/\b(?:VITE_)?(?:AK|SK|ACCESS_TOKEN)\b/]),ae=r=>{for(let t of gr){let e=t.exec(r);if(e)return e[0]}return null},b=r=>r.replace(/\/\*[\s\S]*?\*\//g,"").replace(/(^|[^:])\/\/.*$/gm,"$1"),Er=Object.freeze([{pattern:/three\/addons\//,message:"r117 \u6269\u5C55\u6A21\u5757\u5FC5\u987B\u4ECE three/examples/jsm \u5BFC\u5165\u3002"},{pattern:/three\/tsl\b/,message:"Three r117 \u4E0D\u63D0\u4F9B\u73B0\u4EE3 TSL \u5165\u53E3\u3002"},{pattern:/\b(?:SRGBColorSpace|LinearSRGBColorSpace|NoColorSpace|CapsuleGeometry|WebGPURenderer)\b/,message:"\u6E90\u7801\u4F7F\u7528\u4E86 Three r117 \u4E0D\u5B58\u5728\u7684\u73B0\u4EE3\u7B26\u53F7\u3002"},{pattern:/\b(?:renderer|[\w$]*Renderer)\.outputColorSpace\b/,message:"Three r117 \u4F7F\u7528 renderer.outputEncoding\u3002"},{pattern:/\b(?:texture|[\w$]*(?:map|Map|texture|Texture))\.colorSpace\s*=/,message:"Three r117 \u7EB9\u7406\u4F7F\u7528 encoding\uFF0C\u800C\u4E0D\u662F colorSpace\u3002"}]),wr=Object.freeze(["from '@realsee/five'","from 'three'","FEATURE_OWNED_MESH_PLUGIN_NAME","example.featureOwnedMesh","new Group(","new BoxBufferGeometry(","new MeshBasicMaterial(","new Mesh(","five.scene.add(root)","root.parent?.remove(root)","five.needsRender = true","geometry.dispose()","material.dispose()","plugins.register(FEATURE_OWNED_MESH_PLUGIN_NAME, plugin)","plugin.dispose()"]),Sr=Object.freeze(["new Five(","new WebGLRenderer(","renderer.render(","requestAnimationFrame(","five.dispose(","three/addons/"]),kr=Object.freeze(["Image resources use approved Realsee CDN domains only; never use `ljcdn.com`, `lianjia.com` or their subdomains.","Prefer Five `defaultImageURLTransform` over hand-built \u6570\u636E\u4E07\u8C61 query strings when its installed contract satisfies the request.","URL transformation does not grant CORS access.","Ordinary images use the deployment's approved image-4 host.","Feature media from OpenAPI or Work data is displayed through a derived sized variant, never the original: pass ordinary images through the reviewed `defaultImageURLTransform` adapter with a bounded `size`, and do not hand a full-resolution URL to a tag, tour, card or DOM thumbnail."]),yr=Object.freeze(["When the user explicitly requests Vapor components, use matching named exports from the installed `@realsee/vapor` package for every applicable DOM control; importing `Vapor` or `vapor.css` alone does not satisfy the request.","Do not replace an available Vapor component with raw HTML or a locally styled replica; use raw DOM only for semantics or capabilities absent from the installed Vapor declarations, and record that gap.","Before implementing Vapor UI, read `node_modules/@realsee/vapor/AI_CONTEXT.md`, `llms.txt`, `vapor.d.ts`, and the selected component declarations; never invent component names or props.","Use the installed `ai_guides` only for component discovery and composition recipes; the target project's root `vapor.d.ts` and selected component declarations are the API contract, and conflicts resolve in favor of installed declarations.","Keep the Five canvas in its dedicated sibling host outside `Vapor`; Vapor layout components own DOM chrome only, even when an external recipe nests a placeholder VR component inside a stack."]),br=Object.freeze(["The current generated baseline is `@realsee/vapor@0.2.12`.","The 0.2.12 baseline requires exactly one executable `@realsee/vapor/vapor.css` import, not `vapor.scss`","Choose by product semantics before styling:","`HStack` arranges DOM horizontally, `VStack` vertically and `ZStack` overlays DOM children.","The 0.2.12 match union is exact:","if both represent the same physical inset, de-duplicate rather than adding it twice."]),_r=Object.freeze(["For an explicit Vapor request, executable source must import and render the matching non-provider Vapor component; the provider and stylesheet alone are insufficient.","exactly one executable `@realsee/vapor/vapor.css` import and no `vapor.scss` or copied internal stylesheet;","the `Vapor` provider rectangle matches the DOM overlay host","safe-area behavior uses the declared source/merge rule without double-counting","correct portal order, focus entry/return, Escape/backdrop behavior and close reasons;","no application stylesheet overrides `.vapor-*` internals","typecheck validates every component prop, compound child and symbol"]),Ar=/\bimport\s*['"]@realsee\/vapor\/vapor\.css['"]\s*;?/,Pe=r=>r.match(new RegExp(Ar.source,"g"))?.length??0,B=(r,t)=>new RegExp(`\\bimport\\s*\\{[^}]*\\b${t}\\b[^}]*\\}\\s*from\\s*['"]@realsee/vapor['"]`).test(r),q=(r,t)=>new RegExp(`<${t}(?:\\s|/?>)`).test(r),Or=Object.freeze(["Spatial capability resolution order: installed @realsee/dnalogel preset -> native Five -> Three.js.","Tags, hotspots, and anchors default to PanoTagPlugin with GET /open/v3/hot/list.","Normalize optional OpenAPI configuration fields such as `version`, `globalConfig` and `contentTypeConfig`: treat `null` as absent before validating the installed SDK contract.","Load ordinary PanoTag point/plane tags after `five.load` without waiting for `five.model.loaded`; gate only model-dependent tag content or anchors on model readiness.","Tours use Five State snapshots with GET /open/v3/observerpicture/search.","Model annotations default to ModelMakerPlugin with GET /open/v3/annotation/search.","Routes use GuideLinePlugin for rendering and shape-selected CruisePlugin or MovePlugin playback with GET /open/v3/route/search.","Tour adapters read `data.list`, map each validated record to `Partial<State>` with the active `workCode`, and never cast an observer-picture record directly to `State`.","Model annotation adapters read `data.list`, validate `object_data` for `box`, `triangles` or `prism`, and pass `{ list }` to ModelMaker only for a model-capable Work.","Feature API, schema, or plugin failures disable only the affected feature; keep the base Viewer usable and never runtime-fallback to raw data or a custom Three implementation.","Scope tour, ModelMaker, GuideLine, Cruise and Move work to an active `workCode`/generation token; abort fetches, ignore stale continuations, pause the owned playback controller, remove hooks, clear GuideLine data, wait for pending loads, then let identity-safe unregister dispose each registered controller exactly once before `five.dispose()`.","SDK-shaped custom data requires an explicit developer opt-out from Open Platform data.","VR spatial tag (spatial annotation) is the umbrella concept","Classify content, renderer, anchor, and lifecycle/data ownership","Never infer PanoTag from","Never directly edit `work.json`, downloaded Work, or customer Work"]),T=Object.freeze(["dnalogel 3.81.2 route playback is shape-selected: a validated pano group uses `CruisePlugin` `{ panoIndexList }`, while a validated model-space curve uses `MovePlugin` `{ path }`; `GuideLinePlugin` renders route geometry and is not playback input.","`CruisePlugin` accepts exactly one of `panoIndexList` or `keyframes`; its installed union has no type discriminator.","OpenAPI route records are never passed raw: rename validated style config only for GuideLine `lines`, and never pass GuideLine `PathItem` arrays or style objects to `CruisePlugin` or `MovePlugin`.","If a source record contains both `pano_group` and `path`, the route adapter must choose product semantics explicitly and pass only the selected geometry branch to GuideLine.","`GuideLinePlugin.load` waits for model readiness; `CruisePlugin` waits only for Work and observers; `MovePlugin` enters Model and awaits Five readiness when playback starts.","Reject empty routes, unknown pano indexes, unsupported curves, non-finite coordinates, timing or speed, and ambiguous multi-segment Move paths before plugin load.","Route cleanup pauses the owned playback controller before hook removal and disposal; `clear` belongs to GuideLine, is not a public Cruise operation, and GuideLine disposal does not own an external or embedded Cruise controller automatically.","Do not invent `RoutePlugin`, `PanoRove`, `stop()`, or a universal route controller; use only the exports and methods declared by the installed package."]),Le=Object.freeze(["Floorplans default to ModelFloorplanPlugin with GET /open/v3/floorplan/plugins.","`data.format_floorplan_url`, not `format_model_url`, resolves to the preferred `{ version, data: FloorplanServerData }` plugin input.","Expose `capabilities.modelFloorplan` only after validating the complete wrapper and nested floorplan shape; missing, empty, malformed or failed floorplan data must leave the base Viewer usable.","The default tabs are `\u6F2B\u6E38` for `Panorama` and `\u4E09\u7EF4\u6A21\u578B` for `Mapview`; do not expose `Topview` as `\u5730\u56FE`.","Add the `\u5E73\u9762\u56FE` tab only after the capability validates and `ModelFloorplanPlugin.load` succeeds.","Configure `ModelFloorplanPlugin` with `autoShowEnable: false`; the application tab owns explicit `show()`/`hide()`, uses `modelOpacity: 0` while the floorplan is visible, and restores the Five model surface on hide, failure, and disposal.","Resolve the Five canvas parent and call `ModelFloorplanPlugin.appendTo(wrapper)` before `load`; `five.addExtraElement` registers input handling but does not mount the plugin DOM.","For multi-floor Work, ModelFloorplanPlugin and Five pano/model floor synchronization are the floor source of truth; do not invent a React-only `floorIndex`.","Floorplan show/hide, hooks, pending load and identity-safe disposal must finish before `five.dispose()`."]),Rr=Object.freeze(Le.slice(0,3)),Tr="The complete model floorplan contract lives in",jr="The complete route playback contract lives in",Pr=Object.freeze([T[0],T[7],jr]),Ne=Object.freeze(["Vapor","Text","TitleBar","ZStack","VStack","Layer"]),xe=Object.freeze(["Button","Font","HStack","VStack"]),$e=Object.freeze(["title?: string | null",'className="viewer-vapor"','width="100%"','height="100%"','theme="arc_beauty"',"useSaveAreaInsets()","useDimension()","<Layer vignetting />","labelMaxWidth={titleMaxWidth}",'className="viewer-controls"',"align={{ horizontal: 'center' }}",'className="viewer-status"','appearance="solid"',"role={error ? 'alert' : 'status'}","aria-live={error ? 'assertive' : 'polite'}","env(safe-area-inset-${side}, 0px)"]),Me=Object.freeze(['role="group"',"useDimension()","width={controlWidth}","height={compact ? '3rem' : '5rem'}","align={{ horizontal: 'space-around' }}",'appearance="shadow"',"weight={selected ? 'bold' : 'regular'}","opacity={selected ? '100' : '80'}",'className="mode-switcher-indicator"',"onClick={() => void changeTab(option.value)}"]),Ce=Object.freeze([".dom-overlay",".viewer-controls",".mode-switcher","pointer-events: auto",".mode-switcher-indicator","bottom: -0.5rem","width: 3.375rem","height: 0.125rem","background-image: linear-gradient(",".viewer-status"]),Fe=Object.freeze(["viewer-glass",'className="viewer-topbar"',"<h1"]),Ie=Object.freeze(["Segmented","viewer-segmented","viewer-glass",'appearance="horizontal"','maxWidth="100%"']),De=Object.freeze(["--viewer-glass-","--viewer-control-size",".vapor-segmented","backdrop-filter"]);async function M(r){return JSON.parse(await w(r,"utf8"))}async function Lr(){let r=process.platform==="win32"?"npm.cmd":"npm",{stdout:t}=await hr(r,["--version"],{encoding:"utf8"});return t.trim()}async function Ge(r,t){let e=[];for(let[o,n,i]of[["packageManager",t.packageManager,ne],["engines.node",t.engines?.node,oe],["engines.npm",t.engines?.npm,ie]])n!==i&&e.push(s("error","toolchain.version",`package.json ${o} \u5FC5\u987B\u4E3A ${i}\uFF0C\u5F53\u524D\u4E3A ${n??"\u672A\u58F0\u660E"}`));for(let o of[".nvmrc",".node-version"]){let n=p.join(r,o);if(await f(n)){let i=(await w(n,"utf8")).trim();i!==Z&&e.push(s("error","toolchain.node-pin",`${o} \u5FC5\u987B\u56FA\u5B9A Node ${Z}\uFF0C\u5F53\u524D\u4E3A ${i||"\u7A7A"}`))}}let a=p.join(r,".npmrc");if(await f(a)){let o=new Set((await w(a,"utf8")).split(/\r?\n/).map(n=>n.trim()).filter(Boolean));for(let n of["engine-strict=true","package-lock=true","save-prefix=>="])o.has(n)||e.push(s("error","toolchain.npmrc",`.npmrc \u5FC5\u987B\u5305\u542B ${n}`))}return e}async function Nr(r,t){let e=p.join(r,"package-lock.json");if(!await f(e))return[];try{let a=await M(e),o=a.packages?.[""],n=[];if(a.lockfileVersion!==3||!o)return[s("error","lockfile.invalid","package-lock.json \u5FC5\u987B\u4F7F\u7528 lockfileVersion 3 \u5E76\u5305\u542B\u6839\u5305\u8BB0\u5F55\u3002")];(a.name!==t.name||o.name!==t.name||o.version!==t.version)&&n.push(s("error","lockfile.package","package-lock.json \u6839\u5305\u540D\u79F0\u6216\u7248\u672C\u4E0E package.json \u4E0D\u4E00\u81F4\u3002"));for(let[i,v,u,d]of[["dependency",o.dependencies,t.dependencies,z],["dev-dependency",o.devDependencies,t.devDependencies,J]])for(let[m,S]of Object.entries(u??{})){v?.[m]!==S&&n.push(s("error",`lockfile.${i}`,`package-lock.json \u4E0E package.json \u4E0D\u4E00\u81F4\uFF1A${m}@${S}\uFF1B\u8BF7\u8FD0\u884C npm run deps:relock\u3002`));let y=d[m];if(!y)continue;let l=a.packages?.[`node_modules/${m}`]?.version;if(!l){n.push(s("error","lockfile.resolved",`package-lock.json \u7F3A\u5C11\u76F4\u63A5\u4F9D\u8D56 ${m} \u7684\u89E3\u6790\u7248\u672C\u3002`));continue}if(m in U){l!==y&&n.push(s("error","lockfile.locked",`package-lock.json \u5FC5\u987B\u5C06 ${m} \u89E3\u6790\u4E3A ${y}\uFF0C\u5F53\u524D\u4E3A ${l}\u3002`));continue}let c=X(S);c&&(!N(l,y)||!N(l,c))&&n.push(s("error","lockfile.minimum",`${m} \u5728 package-lock.json \u4E2D\u89E3\u6790\u4E3A ${l}\uFF0C\u5FC5\u987B\u540C\u65F6\u6EE1\u8DB3\u751F\u6210\u5668\u57FA\u7EBF >=${y} \u4E0E\u58F0\u660E ${S}\u3002`))}return n}catch(a){return[s("error","lockfile.invalid","package-lock.json \u65E0\u6CD5\u89E3\u6790\u3002",String(a))]}}async function C(r){let t=[];for(let e of await dr(r,{withFileTypes:!0})){let a=p.join(r,e.name);e.isDirectory()?t.push(...await C(a)):e.isFile()&&/\.(?:[cm]?[jt]sx?)$/.test(e.name)&&t.push(a)}return t}async function Ve(r,t){let e=p.join(r,t);if(!await f(e))return[];let a=b(await w(e,"utf8")),o=[];for(let v of wr)a.includes(v)||o.push(s("error","architecture.custom-plugin-example",`${t} \u7F3A\u5C11\u81EA\u5B9A\u4E49\u63D2\u4EF6\u6240\u6709\u6743\u6807\u8BB0\uFF1A${v}`));for(let v of Sr)a.includes(v)&&o.push(s("error","architecture.custom-plugin-example",`${t} \u7981\u6B62\u62E5\u6709 Five \u8FD0\u884C\u65F6\u6216\u7B2C\u4E8C\u6E32\u67D3\u5FAA\u73AF\uFF1A${v}`));let n=t.startsWith("templates/")?"templates/viewer/src/features/tags/PanoTagFeature.ts":"src/features/tags/PanoTagFeature.ts",i=p.join(r,n);if(await f(i)){let v=b(await w(i,"utf8"));for(let u of["from '@realsee/dnalogel/libs/PanoTagPlugin'","const PLUGIN_NAME = 'dnalogel.panoTag'","const candidate = PanoTagPlugin(","plugins.register(PLUGIN_NAME, candidate)","candidate.dispose()","async load(panoTagList: Tags['tagList'])","plugin.load({ tagList: panoTagList })","const disposeError = plugins.unregister(PLUGIN_NAME, plugin)"])v.includes(u)||o.push(s("error","architecture.pano-tag-contract",`${n} \u7F3A\u5C11 PanoTag \u517C\u5BB9\u6027\u3001\u663E\u5F0F\u7C7B\u578B\u6216\u6240\u6709\u6743\u6807\u8BB0\uFF1A${u}`));for(let u of["model_file","as Tags['tagList']","from '../../vr/work/WorkSource'"])v.includes(u)&&o.push(s("error","architecture.pano-tag-contract",`${n} \u542B\u975E\u6807\u51C6 content type \u6216\u672A\u9A8C\u8BC1\u7684 PanoTag \u8F6C\u6362\uFF1A${u}`))}return o}async function Ke(r,t){let e=[];for(let a of t){let o=p.join(r,a);if(!await f(o))continue;let n=await w(o,"utf8");for(let i of kr)n.includes(i)||e.push(s("error","agents.image-resource-guidance",`${a} \u7F3A\u5C11\u56FE\u7247\u8D44\u6E90\u57DF\u540D\u3001Five URL transform\u3001\u6570\u636E\u4E07\u8C61\u6216 CORS \u89C4\u5219\uFF1A${i}`))}return e}async function ze(r,t){let e=[];for(let a of t){let o=p.join(r,a);if(!await f(o))continue;let n=await w(o,"utf8");for(let i of yr)n.includes(i)||e.push(s("error","agents.vapor-guidance",`${a} \u7F3A\u5C11 Vapor \u771F\u5B9E\u7EC4\u4EF6\u9009\u62E9\u3001\u58F0\u660E\u6838\u5BF9\u6216 raw DOM \u4F8B\u5916\u89C4\u5219\uFF1A${i}`))}return e}async function Ue(r,t,e){let a=[];for(let[o,n,i,v]of[[t,br,"agents.vapor-reference","\u7248\u672C\u5316\u8D44\u6599\u8DEF\u7531\u3001\u7EC4\u4EF6\u9009\u578B\u3001\u5E03\u5C40\u6216 safe-area \u89C4\u5219"],[e,_r,"agents.vapor-acceptance","\u8FD0\u884C\u65F6 Vapor \u9A8C\u6536\u89C4\u5219"]]){let u=p.join(r,o);if(!await f(u))continue;let d=await w(u,"utf8");for(let m of n)d.includes(m)||a.push(s("error",i,`${o} \u7F3A\u5C11 ${v}\uFF1A${m}`))}return a}async function We(r,t,e){let a=[];for(let n of t){let i=p.join(r,n);if(!await f(i))continue;let v=await w(i,"utf8");for(let u of Or)v.includes(u)||a.push(s("error","agents.spatial-tag-guidance",`${n} \u7F3A\u5C11\u7A7A\u95F4\u6807\u7B7E\u51B3\u7B56\u6216\u53EA\u8BFB\u89C4\u5219\uFF1A${u}`))}let o=p.join(r,e);if(await f(o)){let n=await w(o,"utf8");for(let i of["Spatial capability resolution order: installed @realsee/dnalogel preset -> native Five -> Three.js.","Tags, hotspots, and anchors default to PanoTagPlugin with GET /open/v3/hot/list.","Normalize optional OpenAPI configuration fields such as `version`, `globalConfig` and `contentTypeConfig`: treat `null` as absent before validating the installed SDK contract.","Load ordinary PanoTag point/plane tags after `five.load` without waiting for `five.model.loaded`; gate only model-dependent tag content or anchors on model readiness.","Tours use Five State snapshots with GET /open/v3/observerpicture/search.","Model annotations default to ModelMakerPlugin with GET /open/v3/annotation/search.","Routes use GuideLinePlugin for rendering and shape-selected CruisePlugin or MovePlugin playback with GET /open/v3/route/search.","Tour adapters read `data.list`, map each validated record to `Partial<State>` with the active `workCode`, and never cast an observer-picture record directly to `State`.","Model annotation adapters read `data.list`, validate `object_data` for `box`, `triangles` or `prism`, and pass `{ list }` to ModelMaker only for a model-capable Work.","Feature API, schema, or plugin failures disable only the affected feature; keep the base Viewer usable and never runtime-fallback to raw data or a custom Three implementation.","Scope tour, ModelMaker, GuideLine, Cruise and Move work to an active `workCode`/generation token; abort fetches, ignore stale continuations, pause the owned playback controller, remove hooks, clear GuideLine data, wait for pending loads, then let identity-safe unregister dispose each registered controller exactly once before `five.dispose()`.","SDK-shaped custom data requires an explicit developer opt-out from Open Platform data.","`model_file` is not canonical","`ModelTVVideoPlugin`","`CSS3DRenderPlugin.create3DDomContainer`","`PanoVideoPlugin`","generic `tags`, `tagList` and `tag_list`","`ServerData` with `box`, `triangles` or `prism` items"])n.includes(i)||a.push(s("error","agents.spatial-tag-capabilities",`${e} \u7F3A\u5C11\u7A7A\u95F4\u80FD\u529B\u4F18\u5148\u7EA7\u3001\u6570\u636E\u6E90\u6216\u63D2\u4EF6\u7C7B\u578B\u8DEF\u7531\uFF1A${i}`))}return a}async function Ye(r,t,e){let a=[],o=[...t.map(n=>[n,n.endsWith("SKILL.md")?Pr:T]),[`${e}/references/dnalogel-api-capabilities.md`,T],[`${e}/references/five-runtime.md`,T.slice(4,6)],[`${e}/references/plugin-lifecycle.md`,T.slice(6,8)],[`${e}/references/official-docs.md`,T.slice(2,4)]];for(let[n,i]of o){let v=p.join(r,n);if(!await f(v))continue;let u=await w(v,"utf8");for(let d of i)u.includes(d)||a.push(s("error","agents.route-guidance",`${n} \u7F3A\u5C11\u70B9\u4F4D/\u81EA\u5B9A\u4E49\u8DEF\u7EBF\u5206\u6D41\u3001\u6821\u9A8C\u6216\u751F\u547D\u5468\u671F\u89C4\u5219\uFF1A${d}`))}return a}async function Be(r,t,e){let a=[],o=[[`${e}/references/dnalogel-api-capabilities.md`,Le],...t.map(n=>[n,Rr]),...["five-runtime.md","official-docs.md","plugin-lifecycle.md","ui-and-validation.md"].map(n=>[`${e}/references/${n}`,[Tr]])];for(let[n,i]of o){let v=p.join(r,n);if(!await f(v))continue;let u=await w(v,"utf8");for(let d of i)u.includes(d)||a.push(s("error","agents.floorplan-guidance",`${n} \u7F3A\u5C11\u5E73\u9762\u56FE endpoint\u3001\u6821\u9A8C\u3001\u52A8\u6001 tab\u3001\u591A\u697C\u5C42\u6216\u6E05\u7406\u89C4\u5219\uFF1A${d}`))}return a}async function qe(r,t){let e=[],a=[[t.validator,["export function asModelFloorplanServerData(","!isInteger(value.version)","value.version < 0","!isFloorplanServerData(value.data)","value.outlines.length > 0","value.floor_datas.length > 0","isArrayOf(value.rooms, isRoom)","isRules(value.rules)","value.entrance === null || isEntrance(value.entrance)","isBounding(value.bounding)","isArrayOf(value.observers, isObserver)","isArrayOf(value.doors, isDoor)"]],[t.types,["WorkCapabilities","modelFloorplan?: ModelFloorplanServerData","ModelFloorplanClient","RealseeSpatialClient","extends WorkDetailClient, ModelFloorplanClient"]],[t.client,["implements RealseeSpatialClient","FLOORPLAN_PLUGINS_PATH = '/open/v3/floorplan/plugins'","asRecord(descriptor.data).format_floorplan_url","floorplanUrl.protocol !== 'https:'","floorplanUrl.username","floorplanUrl.password","redirect: 'error'","asModelFloorplanServerData(payload)"]],[t.service,[".getModelFloorplan(resourceCode, { signal })",".catch((error: unknown) =>","modelFloorplan ? { capabilities: { modelFloorplan } } : {}"]],[t.workSource,["modelFloorplan?: ModelFloorplanServerData","asModelFloorplanServerData(","asRecord(envelope.capabilities).modelFloorplan","modelFloorplan ? { capabilities: { modelFloorplan } } : {}"]],[t.feature,["@realsee/dnalogel/libs/floorplan/ModelFloorplanPlugin","dnalogel.modelFloorplan","ModelFloorplanPlugin(five, { autoShowEnable: false })","plugins.register(PLUGIN_NAME, candidate)","candidate.dispose()","const modelSurface = five.getElement()","const wrapper = modelSurface?.parentElement","plugin.appendTo(wrapper)","plugin.load(serverData)","modelOpacity: 0","plugin.hide({ userAction: true })",".finally(restoreModelSurface)","if (!state.visible) restoreModelSurface()","plugins.unregister(PLUGIN_NAME, plugin)","stopStateChange()","if (disposeRequested) finalize()","if (!loading) finalize()"]],[t.app,["payload.capabilities?.modelFloorplan","if (modelFloorplan)","createModelFloorplanFeature(five, plugins)","await currentFloorplan.load(modelFloorplan)","currentFloorplan.dispose()","setFloorplanFeature(floorplan)","floorplan?.dispose()"]],[t.modeSwitcher,["floorplan?.subscribeVisibility","await floorplan.show()","if (floorplan?.visible) await floorplan.hide()","floorplan?.visible ? 'floorplan' : tabFromFiveMode(five.currentMode)","Five.Mode.Panorama","Five.Mode.Mapview"]]];for(let[i,v]of a){let u=p.join(r,i);if(!await f(u)){e.push(s("error","architecture.floorplan-missing",`\u7F3A\u5C11\u5E73\u9762\u56FE\u5408\u540C\u6587\u4EF6\uFF1A${i}`));continue}let d=b(await w(u,"utf8"));for(let m of v)d.includes(m)||e.push(s("error","architecture.floorplan-contract",`${i} \u7F3A\u5C11\u5E73\u9762\u56FE\u6821\u9A8C\u3001capability \u6216\u751F\u547D\u5468\u671F\u6807\u8BB0\uFF1A${m}`))}let o=p.join(r,t.feature);if(await f(o)){let i=b(await w(o,"utf8"));for(let[h,E]of[[/plugin\.hooks\.on\(\s*['"]show['"]/,"show hook"],[/plugin\.hooks\.on\(\s*['"]hide['"]/,"hide hook"],[/plugin\.hooks\.on\(\s*['"]stateChange['"]/,"stateChange hook"]])h.test(i)||e.push(s("error","architecture.floorplan-lifecycle",`${t.feature} \u7F3A\u5C11 ${E}\u3002`));/autoShowEnable\s*:\s*true/.test(i)&&e.push(s("error","architecture.floorplan-lifecycle",`${t.feature} \u5FC5\u987B\u4FDD\u6301 autoShowEnable: false\u3002`));let v=i.indexOf("plugin.appendTo(wrapper)"),u=i.indexOf("plugin.load(serverData)");(v<0||u<v)&&e.push(s("error","architecture.floorplan-mount",`${t.feature} \u5FC5\u987B\u5148\u628A\u63D2\u4EF6 DOM appendTo Five \u5BBF\u4E3B\uFF0C\u518D\u52A0\u8F7D format_floorplan_url \u6570\u636E\u3002`));let d=i.indexOf("stopShow()"),m=i.indexOf("stopHide()"),S=i.indexOf("stopStateChange()"),y=i.indexOf("if (!loading) finalize()"),l=i.indexOf("const finalize ="),c=i.indexOf("restoreModelSurface()",l),g=i.indexOf("plugins.unregister(PLUGIN_NAME, plugin)",l);(d<0||m<d||S<m||y<S||l<0||c<l||g<c)&&e.push(s("error","architecture.floorplan-cleanup",`${t.feature} \u5FC5\u987B\u5728 hide\u3001\u5931\u8D25\u4E0E\u9500\u6BC1\u65F6\u6062\u590D Five model surface\uFF0C\u5E76\u5148\u79FB\u9664\u5168\u90E8 hooks\uFF0C\u518D\u6309 pending load \u72B6\u6001 identity-safe \u6E05\u7406\u3002`))}let n=p.join(r,t.modeSwitcher);if(await f(n)){let i=b(await w(n,"utf8"));for(let[u,d]of[[/type\s+ViewerTab\s*=\s*['"]tour['"]\s*\|\s*['"]model['"]\s*\|\s*['"]floorplan['"]/,"\u4E09\u4E2A ViewerTab"],[/value\s*:\s*['"]tour['"]\s*,\s*text\s*:\s*['"]漫游['"]/,"Panorama \u6F2B\u6E38 tab"],[/value\s*:\s*['"]model['"]\s*,\s*text\s*:\s*['"]三维模型['"]/,"Mapview \u4E09\u7EF4\u6A21\u578B tab"],[/\.\.\.\(\s*floorplan\s*\?\s*\[\{\s*value\s*:\s*['"]floorplan['"](?:\s+as\s+const)?\s*,\s*text\s*:\s*['"]平面图['"]\s*\}\]\s*:\s*\[\]\s*\)/,"\u6761\u4EF6\u5E73\u9762\u56FE tab"]])u.test(i)||e.push(s("error","architecture.floorplan-tabs",`${t.modeSwitcher} \u7F3A\u5C11 ${d}\u3002`));(i.match(/floorplan\?\.visible\s*\?\s*['"]floorplan['"]\s*:\s*tabFromFiveMode\(five\.currentMode\)/g)?.length??0)<2&&e.push(s("error","architecture.floorplan-tabs",`${t.modeSwitcher} \u5FC5\u987B\u5728\u521D\u59CB\u72B6\u6001\u548C\u5207\u6362\u5931\u8D25\u540E\u90FD\u6309\u63D2\u4EF6\u771F\u5B9E\u53EF\u89C1\u6027\u540C\u6B65 Tab\u3002`)),(i.includes("Five.Mode.Topview")||/text\s*:\s*['"]地图['"]/.test(i))&&e.push(s("error","architecture.floorplan-tabs",`${t.modeSwitcher} \u4E0D\u5F97\u628A Topview \u66B4\u9732\u4E3A\u5730\u56FE tab\u3002`))}for(let[i,v]of[[t.browserTsconfig,'"include": ["src", "shared", "vite.config.ts"]'],[t.serverTsconfig,'"include": ["server", "shared"]']]){let u=p.join(r,i);if(!await f(u))continue;(await w(u,"utf8")).includes(v)||e.push(s("error","tooling.floorplan-include",`${i} \u5FC5\u987B\u5305\u542B shared floorplan validator\uFF1A${v}`))}return e}function xr(r){let t=new Map;for(let e of r.split(/\r?\n/)){let a=e.trim();if(!a||a.startsWith("#"))continue;let o=/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(a);o&&t.set(o[1],o[2].trim())}return t}async function He(r,t){let e=[],a=p.join(r,".env.example");if(await f(a)){let u=await w(a,"utf8"),d=xr(u);for(let m of pe)d.has(m)||e.push(s("error","env.required-key",`.env.example \u7F3A\u5C11 ${m}\u3002`));for(let m of ue)u.includes(m)||e.push(s("error","env.optional-example",`.env.example \u7F3A\u5C11\u53EF\u9009\u914D\u7F6E\u793A\u4F8B\uFF1A${m}`));d.has("REALSEE_DEFAULT_RESOURCE_CODE")&&e.push(s("error","env.optional-resource",".env.example \u4E2D REALSEE_DEFAULT_RESOURCE_CODE \u5FC5\u987B\u4FDD\u6301\u4E3A\u6CE8\u91CA\u793A\u4F8B\uFF0C\u4E0D\u80FD\u8868\u73B0\u4E3A\u9ED8\u8BA4\u5FC5\u586B\u914D\u7F6E\u3002"));for(let[m,S]of d)m.startsWith("VITE_WORK_")&&e.push(s("error","env.legacy-work-url",`.env.example \u4E0D\u5F97\u58F0\u660E\u8DEF\u7531\u6A21\u5F0F\u4E0D\u9700\u8981\u7684 ${m}\u3002`)),m.startsWith("VITE_")&&je(m)&&e.push(s("error","env.client-secret",`.env.example \u7981\u6B62\u58F0\u660E\u6D4F\u89C8\u5668\u53EF\u89C1\u7684\u5BC6\u94A5\u53D8\u91CF ${m}\u3002`)),je(m)&&!de.includes(m)&&S&&S!=="*"&&e.push(s("error","env.secret",`.env.example \u7684 ${m} \u53EA\u80FD\u4E3A\u7A7A\u6216\u4F7F\u7528 * \u5360\u4F4D\uFF0C\u4E0D\u80FD\u5305\u542B\u771F\u5B9E\u5BC6\u94A5\u3002`));for(let m of fe){let S=d.get(m);S!==void 0&&S!==""&&S!=="*"&&e.push(s("error","env.credential",`.env.example \u7684 ${m} \u53EA\u80FD\u4E3A\u7A7A\u6216\u4F7F\u7528 * \u5360\u4F4D\u3002`))}}await f(p.join(r,".env.server.example"))&&e.push(s("error","env.legacy-file","\u73AF\u5883\u914D\u7F6E\u5DF2\u5408\u5E76\u5230 .env.example\uFF0C\u4E0D\u5F97\u4FDD\u7559 .env.server.example\u3002"));let o=p.join(r,"src");if(await f(o))for(let u of await C(o)){let d=b(await w(u,"utf8")),m=ae(d);m&&e.push(s("error","env.client-server-only-reference",`${p.relative(r,u)} \u4E0D\u5F97\u8BFB\u53D6\u6216\u5F15\u7528 server-only \u914D\u7F6E\u6216\u51ED\u636E\uFF1A${m}\u3002`))}let n=p.join(r,"src","vr","work","WorkSource.ts");if(await f(n)){let u=b(await w(n,"utf8"));for(let d of["resourceCodeFromPathname","window.location.pathname","segments.length === 0","segments.length !== 1",": '/api/work'","`/api/work/${encodeURIComponent(resourceCode)}`","asExplicitPanoTagList","hasCanonicalPanoTagData(value.contentType, value.data)","hasCanonicalPanoTagAnchor(value)","asRecord(envelope.presetData)","presetData: { panoTagList }","displayName: string","displayName: displayNameFrom(work.name) ?? workCode","new RouteWorkSource()"])u.includes(d)||e.push(s("error","architecture.resource-route",`WorkSource \u7F3A\u5C11\u9ED8\u8BA4/\u517C\u5BB9\u8DEF\u7531\u3001\u540C\u6E90 API \u6216\u663E\u5F0F preset \u6807\u8BB0\uFF1A${d}`));for(let d of["VITE_WORK_URL","/work.json","envelope.tags","envelope.tagList","envelope.tag_list","payload.tags"])u.includes(d)&&e.push(s("error","architecture.resource-route",`WorkSource \u4E0D\u5F97\u4F9D\u8D56\u65E7 Work URL\u3001\u672C\u5730 Work \u6587\u4EF6\u6216\u628A generic tags \u6620\u5C04\u5230 PanoTag\uFF1A${d}`))}for(let u of["vite.config.ts","vite.config.js","vite.config.mjs"]){let d=p.join(r,u);if(!await f(d))continue;let m=b(await w(d,"utf8")),S=ae(m);(S||/envPrefix\s*:\s*[^\n]*REALSEE_/.test(m))&&e.push(s("error","env.vite-secret",`${u} \u4E0D\u5F97\u628A server-only \u914D\u7F6E\u3001AK/SK/token \u6216 REALSEE_* \u524D\u7F00\u6CE8\u5165\u6D4F\u89C8\u5668${S?`\uFF1A${S}`:"\u3002"}`));let y=m.match(/\bhost\s*:\s*['"]127\.0\.0\.1['"]/g)?.length??0,l=m.match(/\bport\s*:\s*8087\b/g)?.length??0,c=m.match(/\bstrictPort\s*:\s*true\b/g)?.length??0,g=/target\s*:\s*['"]http:\/\/127\.0\.0\.1:8088['"]/.test(m);(y<2||l<2||c<2||!g)&&e.push(s("error","architecture.dev-route",`${u} \u5FC5\u987B\u8BA9 Vite server/preview \u4F7F\u7528 strict IPv4 127.0.0.1:8087\uFF0C\u5E76\u628A /api \u4EE3\u7406\u5230 Fastify 127.0.0.1:8088\u3002`))}let i=[["server/types.ts",["ServerConfig","WorkEnvelope","WorkDetailClient","WorkService","defaultResourceCode: string | undefined"]],["server/config.ts",["loadServerConfig","REALSEE_GATEWAY_APP_KEY","REALSEE_GATEWAY_APP_SECRET","REALSEE_DEFAULT_RESOURCE_CODE","optionalResourceCode","if (!nonEmptyString(value)) return undefined","^[A-Za-z0-9_-]{1,128}$","gatewayEndpoint.protocol !== 'https:'","): ServerConfig {"]],["server/check-config.ts",["loadServerConfig()","BFF server configuration is valid."]],["server/realsee-open-api.ts",["RealseeOpenApiClient","implements RealseeSpatialClient","ACCESS_TOKEN_PATH = '/auth/access_token'","'application/x-www-form-urlencoded'","WORK_INFO_PATH = '/open/v3/vr/info'","Authorization: accessToken","resource_code: resourceCode","UNAUTHORIZED_UPSTREAM_CODES","TOKEN_REFRESH_SKEW_MS"]],["server/work-service.ts",["createWorkService","): WorkService {","getWorkByResourceCode","openApiClient.getWorkDetail(resourceCode, { signal })"]],["server/work-route.ts",["'/api/work'","'/api/work/:resourceCode'","defaultResourceCode: string | undefined","error: 'RESOURCE_CODE_REQUIRED'","getWorkByResourceCode(resourceCode, {","signal: requestAbort.signal","WORK_UPSTREAM_FAILED"]],["server/app.ts",["Fastify({ logger })","'/api/health'","defaultResourceCode: string | undefined","fastifyStatic","serveFrontend","reply.sendFile('index.html')","Promise<FastifyInstance>"]],["server/index.ts",["loadServerConfig","RealseeOpenApiClient","createWorkService","createApp","app.listen({ host: config.host, port: config.port })"]]];for(let[u,d]of i){let m=p.join(r,u);if(!await f(m)){e.push(s("error","architecture.bff-missing",`\u7F3A\u5C11 Fastify BFF \u6587\u4EF6\uFF1A${u}`));continue}let S=b(await w(m,"utf8"));for(let y of d)S.includes(y)||e.push(s("error","architecture.bff-contract",`${u} \u7F3A\u5C11 BFF \u89E3\u8026\u6216 OpenAPI \u5951\u7EA6\uFF1A${y}`))}let v=p.join(r,t);if(await f(v)){let u=new Set((await w(v,"utf8")).split(/\r?\n/).map(d=>d.trim()).filter(Boolean));for(let d of[".env*","!.env.example"])u.has(d)||e.push(s("error","env.gitignore",`${t} \u5FC5\u987B\u5305\u542B ${d}\u3002`))}return e}async function $r(r,t){return t.name!==H?!1:Object.hasOwn(t.bin??{},"vr-cli")||await f(p.join(r,"bin","vr-cli.mjs"))||await f(p.join(r,"templates","base"))}async function Mr(r,t){let e=[];t.private!==!1&&e.push(s("error","tool.private","\u53EF\u53D1\u5E03\u5DE5\u5177\u5FC5\u987B\u663E\u5F0F\u58F0\u660E private: false\u3002"));let a=Object.entries(t.bin??{});(a.length!==1||a[0]?.[0]!=="vr-cli"||a[0]?.[1]!=="bin/vr-cli.mjs")&&e.push(s("error","tool.bin","\u5F00\u53D1\u5DE5\u5177\u5FC5\u987B\u53EA\u66B4\u9732 vr-cli -> bin/vr-cli.mjs\u3002")),((typeof t.repository=="string"?t.repository:t.repository?.url)??null)!==null&&e.push(s("error","tool.repository",null===null?"\u5F53\u524D\u53D1\u5E03 profile \u8981\u6C42 package.json \u7701\u7565 repository\u3002":`package.json repository \u5FC5\u987B\u4E3A ${null}\u3002`));let n=t.publishConfig?.registry??null,i=t.publishConfig?.access??null;(n!==D||i!==$)&&e.push(s("error","tool.publish-profile",`package.json publishConfig \u5FC5\u987B\u5339\u914D\u5F53\u524D\u53D1\u5E03 profile\uFF1Aregistry=${D}\uFF0Caccess=${$??"\u672A\u8BBE\u7F6E"}\u3002`)),e.push(...await Ge(r,t));let v=["README.md",...await f(p.join(r,".git"))?[".nvmrc",".node-version",".npmrc"]:[],"agent-assets/AGENTS.md","bin","bin/vr-cli.mjs",...$==="public"?["dist/cli.mjs"]:["src","src/core/spatial-policy.mjs"],"templates/base","templates/base/README.md","templates/base/.editorconfig","templates/base/.env.example","templates/base/.gitignore.template","templates/base/.nvmrc","templates/base/.node-version","templates/base/.npmrc.template","templates/base/.prettierignore","templates/base/eslint.config.mjs","templates/base/package-lock.json","templates/base/prettier.config.mjs","templates/base/tsconfig.server.json","templates/base/server/types.ts","templates/base/server/config.ts","templates/base/server/check-config.ts","templates/base/server/realsee-open-api.ts","templates/base/server/work-service.ts","templates/base/server/work-route.ts","templates/base/server/app.ts","templates/base/server/index.ts","templates/base/shared/model-floorplan.ts","templates/base/src/vr/five/api.ts","templates/base/src/vr/five/fiveConfig.ts","templates/base/src/vr/five/SpatialRuntimeProvider.tsx","templates/base/src/vr/five/initialState.ts","templates/base/src/vr/plugins/dnalogel.ts","templates/base/src/vr/plugins/SpatialPluginRegistry.ts","templates/base/src/vr/plugins/examples/FeatureOwnedMeshPlugin.ts","templates/base/src/vr/work/WorkSource.ts","templates/viewer/src/app/App.tsx","templates/viewer/src/features/floorplan/ModelFloorplanFeature.ts","templates/viewer/src/features/navigation/ModeSwitcher.tsx","templates/viewer/src/features/tags/PanoTagFeature.ts","templates/viewer/src/ui/ViewerOverlay.tsx","skills/realsee-vr-app","skills/realsee-vr-app/references/five-api-capabilities.md","skills/realsee-vr-app/references/dnalogel-api-capabilities.md","skills/realsee-vr-app/references/five-runtime.md","skills/realsee-vr-app/references/official-docs.md","skills/realsee-vr-app/references/plugin-lifecycle.md","skills/realsee-vr-app/references/ui-and-validation.md","skills/realsee-vr-app/references/vapor-dom-layer.md","skills/realsee-image-resources/SKILL.md","skills/realsee-image-resources/references/cors-and-three-textures.md","skills/realsee-image-resources/references/data-wanxiang.md","skills/realsee-image-resources/references/five-image-url-transform.md","skills/realsee-image-resources/references/source-and-domain-policy.md","skills/threejs-r117-five/SKILL.md","skills/threejs-r117-five/references/r117-capabilities.md","skills/threejs-r117-five/references/r117-version-guards.md","skills/threejs-r117-five/references/five-ownership.md"];for(let h of v)await f(p.join(r,h))||e.push(s("error","tool.missing",`\u7F3A\u5C11\u5DE5\u5177\u8DEF\u5F84\uFF1A${h}`));for(let h of["templates/base/README.md","agent-assets/AGENTS.md"]){let E=p.join(r,h);if(!await f(E))continue;let k=await w(E,"utf8");k.includes("{{TOOL_VERSION}}")||e.push(s("error","tool.token",`${h} \u5FC5\u987B\u4F7F\u7528 {{TOOL_VERSION}} \u7248\u672C token\u3002`)),k.includes("__TOOL_VERSION__")&&e.push(s("error","tool.token",`${h} \u4E0D\u5F97\u4F7F\u7528\u4E0E Markdown \u7C97\u4F53\u51B2\u7A81\u7684 __TOOL_VERSION__ token\u3002`))}let u=p.join(r,"templates","base");if(await f(u)){let h=new Set(["deploy.json","package-lock.json","package.json","tsconfig.json","tsconfig.server.json"]);for(let E of await W(u))E.endsWith(".json")&&!h.has(E)&&e.push(s("error","tool.data-asset",`\u6A21\u677F\u4E0D\u5F97\u5206\u53D1\u672A\u7ECF\u660E\u786E\u5BA1\u6838\u7684 JSON/Work \u6570\u636E\uFF1Atemplates/base/${E}`))}e.push(...await We(r,["skills/realsee-vr-app/SKILL.md","agent-assets/AGENTS.md"],"skills/realsee-vr-app/references/dnalogel-api-capabilities.md")),e.push(...await Ye(r,["skills/realsee-vr-app/SKILL.md","agent-assets/AGENTS.md"],"skills/realsee-vr-app")),e.push(...await Be(r,["agent-assets/AGENTS.md","skills/realsee-vr-app/SKILL.md"],"skills/realsee-vr-app")),e.push(...await qe(r,{validator:"templates/base/shared/model-floorplan.ts",types:"templates/base/server/types.ts",client:"templates/base/server/realsee-open-api.ts",service:"templates/base/server/work-service.ts",workSource:"templates/base/src/vr/work/WorkSource.ts",feature:"templates/viewer/src/features/floorplan/ModelFloorplanFeature.ts",app:"templates/viewer/src/app/App.tsx",modeSwitcher:"templates/viewer/src/features/navigation/ModeSwitcher.tsx",browserTsconfig:"templates/base/tsconfig.json",serverTsconfig:"templates/base/tsconfig.server.json"})),e.push(...await Ke(r,["agent-assets/AGENTS.md","skills/realsee-vr-app/SKILL.md","skills/realsee-image-resources/SKILL.md"])),e.push(...await ze(r,["agent-assets/AGENTS.md","skills/realsee-vr-app/SKILL.md","skills/realsee-vr-app/references/vapor-dom-layer.md"])),e.push(...await Ue(r,"skills/realsee-vr-app/references/vapor-dom-layer.md","skills/realsee-vr-app/references/ui-and-validation.md")),e.push(...await Ve(r,"templates/base/src/vr/plugins/examples/FeatureOwnedMeshPlugin.ts"));let d=p.join(r,"templates","base","src","main.tsx");if(await f(d)){let h=b(await w(d,"utf8"));(Pe(h)!==1||h.includes("@realsee/vapor/vapor.scss"))&&e.push(s("error","tool.vapor-css","\u9ED8\u8BA4\u6A21\u677F\u5FC5\u987B\u6070\u597D\u4E00\u6B21\u4EE5\u53EF\u6267\u884C side-effect import \u52A0\u8F7D @realsee/vapor/vapor.css\uFF0C\u4E14\u4E0D\u5F97\u4F7F\u7528 vapor.scss\u3002"))}let m=p.join(r,"templates","base","src","vr","work","WorkSource.ts");if(await f(m)){let h=b(await w(m,"utf8"));for(let E of["displayName: string","displayNameFrom(work.name) ?? workCode"])h.includes(E)||e.push(s("error","tool.work-name",`\u9ED8\u8BA4 WorkSource \u5FC5\u987B\u4ECE work.name \u5B89\u5168\u6D3E\u751F VR \u5C55\u793A\u540D\u79F0\uFF1A${E}`))}let S=p.join(r,"templates","viewer","src","ui","ViewerOverlay.tsx");if(await f(S)){let h=b(await w(S,"utf8")),E=Ne.filter(k=>!B(h,k)||!q(h,k));E.length>0&&e.push(s("error","tool.vapor-provider",`\u9ED8\u8BA4 ViewerOverlay \u5FC5\u987B\u4ECE @realsee/vapor \u5BFC\u5165\u5E76\u5B9E\u9645\u6E32\u67D3\u5B98\u65B9 Demo \u7EC4\u4EF6\uFF1A${E.join(", ")}`));for(let k of $e)h.includes(k)||e.push(s("error","tool.viewer-topbar",`\u9ED8\u8BA4 ViewerOverlay \u5FC5\u987B\u4F7F\u7528 Vapor TitleBar\u3001Layer \u4E0E\u54CD\u5E94\u5F0F Stack\uFF0C\u5E76\u6E32\u67D3\u5DE6\u4E0A\u6807\u9898\u3001\u5C45\u4E2D controls \u4E0E\u72B6\u6001\u8BED\u4E49\uFF1A${k}`));for(let k of Fe)h.includes(k)&&e.push(s("error","tool.viewer-native-vapor",`\u9ED8\u8BA4 ViewerOverlay \u4E0D\u5F97\u7528\u539F\u751F\u6807\u9898\u6216\u81EA\u5B9A\u4E49 glass \u6837\u5F0F\u66FF\u4EE3 Vapor \u7EC4\u4EF6\u89C6\u89C9\uFF1A${k}`))}let y=p.join(r,"templates","base","src","styles.css");if(await f(y)){let h=await w(y,"utf8");for(let E of Ce)h.includes(E)||e.push(s("error","tool.viewer-layout",`\u9ED8\u8BA4 Viewer \u5FC5\u987B\u4FDD\u6301 safe-area \u53CA\u684C\u9762/\u5E73\u677F/\u624B\u673A/\u77EE\u5C4F\u5916\u5C42\u54CD\u5E94\u5F0F\u5E03\u5C40\uFF1A${E}`));for(let E of De)h.includes(E)&&e.push(s("error","tool.viewer-native-vapor",`\u9ED8\u8BA4 Viewer \u6837\u5F0F\u4E0D\u5F97\u8986\u76D6 Vapor \u5185\u90E8\u7C7B\u6216\u91CD\u7ED8\u7EC4\u4EF6\u89C6\u89C9\uFF1A${E}`))}let l=p.join(r,"templates","viewer","src","features","navigation","ModeSwitcher.tsx");if(await f(l)){let h=b(await w(l,"utf8")),E=xe.filter(k=>!B(h,k)||!q(h,k));E.length>0&&e.push(s("error","tool.vapor-component",`\u9ED8\u8BA4 ModeSwitcher \u5FC5\u987B\u4ECE @realsee/vapor \u5BFC\u5165\u5E76\u5B9E\u9645\u6E32\u67D3\u5B98\u65B9 Demo \u7684\u6A21\u5F0F\u5207\u6362\u7EC4\u4EF6\uFF1A${E.join(", ")}`));for(let k of Me)h.includes(k)||e.push(s("error","tool.viewer-control-size",`\u9ED8\u8BA4 ModeSwitcher \u5FC5\u987B\u4F7F\u7528\u5B98\u65B9 Demo \u7684 Font/HStack/VStack \u4E0E shadow Button \u7EC4\u5408\uFF1A${k}`));for(let k of Ie)h.includes(k)&&e.push(s("error","tool.viewer-native-vapor",`\u9ED8\u8BA4 ModeSwitcher \u4E0D\u5F97\u9000\u56DE Segmented \u6216\u6DFB\u52A0\u65E7\u7684\u62C9\u4F38/glass \u6837\u5F0F\uFF1A${k}`))}let c=p.join(r,"templates","viewer","src");if(await f(c))for(let h of await C(c)){let E=b(await w(h,"utf8")),k=ae(E);k&&e.push(s("error","env.client-server-only-reference",`${p.relative(r,h)} \u4E0D\u5F97\u8BFB\u53D6\u6216\u5F15\u7528 server-only \u914D\u7F6E\u6216\u51ED\u636E\uFF1A${k}\u3002`))}let g=p.join(r,"templates","viewer","src","app","App.tsx");if(await f(g)){let h=b(await w(g,"utf8"));for(let E of["setWorkName(payload.displayName)","title={workName}"])h.includes(E)||e.push(s("error","tool.work-name",`\u9ED8\u8BA4 App \u5FC5\u987B\u628A WorkSource \u5C55\u793A\u540D\u79F0\u4F20\u7ED9 ViewerOverlay\uFF1A${E}`));for(let E of["FeatureOwnedMeshPlugin","registerFeatureOwnedMeshPlugin","vr/plugins/examples"])h.includes(E)&&e.push(s("error","tool.custom-plugin-enabled",`\u9ED8\u8BA4 Viewer \u4E0D\u5F97\u542F\u7528\u81EA\u5B9A\u4E49\u63D2\u4EF6\u793A\u4F8B\uFF1A${E}`))}return e.push(...await He(p.join(r,"templates","base"),".gitignore.template")),e}async function Cr(r,t,e){let a=new Set;for(let o of e)await f(p.join(r,o))&&a.add(o);for(let o of t){let n=p.join(r,o);if(!await f(n))continue;if(!(await mr(n)).isDirectory()){a.add(o);continue}for(let v of await W(n))a.add(p.posix.join(o,v))}return a}async function Fr(r){let t=[],e=p.join(r,".vr-cli.json");if(!await f(e)){let i=p.join(r,".agents"),v=await f(p.join(r,"AGENTS.md")),u=await f(i);if(!v&&!u)t.push(s("warning","agents.missing","\u672A\u5B89\u88C5\u8DE8 Agent \u6307\u4EE4\u8D44\u4EA7\u3002"));else for(let d of["skills/realsee-vr-app/SKILL.md","skills/realsee-image-resources/SKILL.md","skills/realsee-image-resources/references/cors-and-three-textures.md","skills/realsee-image-resources/references/data-wanxiang.md","skills/realsee-image-resources/references/five-image-url-transform.md","skills/realsee-image-resources/references/source-and-domain-policy.md","skills/threejs-r117-five/SKILL.md","skills/threejs-r117-five/references/r117-capabilities.md","skills/threejs-r117-five/references/r117-version-guards.md","skills/threejs-r117-five/references/five-ownership.md"])await f(p.join(i,d))||t.push(s("error","agents.skill-missing",`.agents/${d} \u7F3A\u5931\u3002`));return t}let a;try{a=await M(e)}catch(i){return[s("error","manifest.invalid",".vr-cli.json \u65E0\u6CD5\u89E3\u6790\u3002",String(i))]}if(!(a&&typeof a=="object"&&!Array.isArray(a)&&a.schemaVersion===1&&O.includes(a.agentAssets)))return[s("error","manifest.invalid",".vr-cli.json \u7F3A\u5C11\u6709\u6548\u7684 schemaVersion \u6216 agentAssets\u3002")];for(let i of Y(a.agentAssets))await f(p.join(r,i))||t.push(s("error","agents.asset-missing",`agentAssets=${a.agentAssets} \u7F3A\u5C11\uFF1A${i}`));let n=a.agentAssets==="portable"?{directoryRoots:_e,singleFiles:Ae}:a.agentAssets==="none"?{directoryRoots:ye,singleFiles:be}:null;if(n)for(let i of await Cr(r,n.directoryRoots,n.singleFiles))t.push(s("error","agents.unexpected",`agentAssets=${a.agentAssets} \u4E0D\u5E94\u5305\u542B\uFF1A${i}`));return t}async function Ir(r,t){let e=[];t.scripts?.["package:vibe-release"]!=="node scripts/package-vibe-release.mjs"&&e.push(s("error","release.script","package.json scripts.package:vibe-release \u5FC5\u987B\u8FD0\u884C node scripts/package-vibe-release.mjs\u3002")),t.devDependencies?.[P]!==L&&e.push(s("error","release.zip-dependency",`${P} \u5FC5\u987B\u4F5C\u4E3A\u5F00\u53D1\u4F9D\u8D56\u7CBE\u786E\u56FA\u5B9A\u4E3A ${L}\u3002`));try{let o=await M(p.join(r,"deploy.json")),n=d=>d!==null&&typeof d=="object"&&!Array.isArray(d),i=[],v=null,u=null;if(!n(o))i.push("\u5FC5\u987B\u662F JSON \u5BF9\u8C61");else{o.packageFormat!==Q&&i.push(`packageFormat \u5FC5\u987B\u662F ${Q}`);let d=new Set(["packageFormat","name","requiredEnv","resources","domain","healthCheck"]),m=Object.keys(o).find(y=>!d.has(y));if(m&&i.push(`\u4E0D\u652F\u6301\u5B57\u6BB5 ${m}`),!Array.isArray(o.requiredEnv)||o.requiredEnv.length>20)v="\u5FC5\u987B\u662F\u6700\u591A 20 \u9879\u7684\u6570\u7EC4";else{let y=new Set;for(let l of o.requiredEnv){let c=String(l??"").trim();if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(c)){v=`\u540D\u79F0\u4E0D\u5408\u6CD5\uFF1A${c||"(\u7A7A)"}`;break}if(y.has(c)){v=`\u5305\u542B\u91CD\u590D\u9879\uFF1A${c}`;break}y.add(c)}}let S=o.healthCheck;if(S!==void 0)if(!n(S))u="\u5FC5\u987B\u662F\u5BF9\u8C61";else{let y=new Set(["enabled","path","timeoutMs","retries"]),l=Object.keys(S).find(c=>!y.has(c));if(l)u=`\u4E0D\u652F\u6301\u5B57\u6BB5 ${l}`;else if(S.enabled!==void 0&&S.enabled!==!0)u="enabled \u5B58\u5728\u65F6\u5FC5\u987B\u4E3A true";else if(S.path!==void 0&&S.path!=="/api/health")u="path \u5B58\u5728\u65F6\u5FC5\u987B\u4E3A /api/health";else if(S.timeoutMs!==void 0){let c=Number(S.timeoutMs);(!Number.isFinite(c)||c<500||c>3e4)&&(u="timeoutMs \u5FC5\u987B\u5728 500-30000 \u4E4B\u95F4")}if(!u&&S.retries!==void 0){let c=Number(S.retries);(!Number.isInteger(c)||c<1||c>60)&&(u="retries \u5FC5\u987B\u662F 1-60 \u4E4B\u95F4\u7684\u6574\u6570")}}}i.length>0&&e.push(s("error","release.manifest",`deploy.json \u53D1\u5E03\u6E05\u5355\u65E0\u6548\uFF1A${i.join("\uFF1B")}\u3002`)),v&&e.push(s("error","release.required-env",`deploy.json.requiredEnv ${v}\uFF1B\u6A21\u677F\u9ED8\u8BA4\u503C\u4E3A ${le.join(", ")}\u3002`)),u&&e.push(s("error","release.health",`deploy.json.healthCheck ${u}\u3002`))}catch(o){e.push(s("error","release.manifest","deploy.json \u65E0\u6CD5\u89E3\u6790\u3002",String(o)))}try{let o=await M(p.join(r,"package-lock.json"));(o.packages?.[""]?.devDependencies?.[P]!==L||o.packages?.[`node_modules/${P}`]?.version!==L)&&e.push(s("error","release.zip-lock",`package-lock.json \u5FC5\u987B\u89E3\u6790\u5E76\u9501\u5B9A ${P}@${L}\u3002`))}catch(o){e.push(s("error","release.zip-lock","\u65E0\u6CD5\u68C0\u67E5\u53D1\u5E03\u6253\u5305\u4F9D\u8D56\u7684 lockfile\u3002",String(o)))}return e}async function Dr(r,t){let e=[];t.private!==!0&&e.push(s("error","project.public","\u751F\u6210\u9879\u76EE\u5FC5\u987B\u4FDD\u6301 private\u3002")),t.type!=="module"&&e.push(s("error","project.module","\u751F\u6210\u9879\u76EE\u5FC5\u987B\u4F7F\u7528 ESM\uFF1Atype=module\u3002")),e.push(...await Ge(r,t));for(let l of me)await f(p.join(r,l))||e.push(s("error","project.missing",`\u7F3A\u5C11\u751F\u6210\u6587\u4EF6\uFF1A${l}`));e.push(...await Ir(r,t));for(let[l,c,g]of[["dependency",t.dependencies,z],["dev-dependency",t.devDependencies,J]])for(let[h,E]of Object.entries(g)){let k=c?.[h];if(k===void 0){e.push(s("error",`${l}.missing`,`${h} \u5FC5\u987B\u58F0\u660E\uFF0C\u751F\u6210\u5668\u57FA\u7EBF\u4E3A ${E}`));continue}if(h in U){k!==E&&e.push(s("error",`${l}.locked`,`${h} \u5FC5\u987B\u9501\u5B9A\u4E3A ${E}\uFF0C\u5F53\u524D\u4E3A ${k}\uFF1BFive 6.8.9 \u4E0E Three \u80FD\u529B\u53C2\u8003\u5747\u9488\u5BF9\u8BE5\u7248\u672C\uFF0C\u5347\u7EA7\u9700\u6574\u4F53\u8BC4\u4F30\u5E76\u540C\u6B65\u751F\u6210\u5668\u3002`));continue}let I=X(k);if(!I){e.push(s("error",`${l}.range`,`${h} \u5FC5\u987B\u4F7F\u7528\u5F00\u653E\u6700\u4F4E\u7248\u672C\u8303\u56F4 >=x.y.z\uFF0C\u751F\u6210\u5668\u57FA\u7EBF\u4E3A >=${E}\uFF0C\u5F53\u524D\u4E3A ${k}\u3002`));continue}if(!N(I,E)){e.push(s("error",`${l}.minimum`,`${h} \u7684\u58F0\u660E\u4E0B\u9650 ${I} \u4F4E\u4E8E\u751F\u6210\u5668\u57FA\u7EBF ${E}\u3002`));continue}k!==ce(h,E)&&e.push(s("warning",`${l}.updated`,`${h} \u7684\u6700\u4F4E\u7248\u672C\u5DF2\u4ECE\u751F\u6210\u5668\u57FA\u7EBF ${E} \u63D0\u9AD8\u5230 ${I}\uFF1B\u8BF7\u786E\u8BA4 npm run check \u4E0E npm run build \u901A\u8FC7\uFF0C\u5E76\u5728\u5347\u7EA7 SDK \u65F6\u590D\u6838\u7248\u672C\u5316 fixture\u3002`))}for(let[l,c]of[["dev",["check:env","concurrently"]],["setup",["scripts/setup.mjs"]],["setup:complete",["scripts/setup.mjs","--complete"]],["test:setup",["--test","scripts/setup-config.test.mjs","scripts/project-identity.test.mjs","scripts/pairing.test.mjs"]],["dev:bff",["--env-file-if-exists=.env.development","server/index.ts"]],["dev:web",["vite"]],["check:env",["--env-file-if-exists=.env.development","server/check-config.ts"]],["typecheck",["npm run typecheck:web","npm run typecheck:bff"]],["typecheck:web",["tsc -p tsconfig.json"]],["typecheck:bff",["tsc -p tsconfig.server.json"]],["lint",["eslint . --max-warnings=0"]],["format",["prettier --write ."]],["format:check",["prettier --check ."]],["check",["npm run typecheck","npm run lint","npm run format:check"]],["check:bff",["typecheck:bff"]],["deps:relock",["npm install --package-lock-only"]],["deps:install",["npm ci"]],["start",["--env-file-if-exists=.env.development","server/index.ts","--serve-frontend"]]])for(let g of c)t.scripts?.[l]?.includes(g)||e.push(s("error","script.contract",`package.json scripts.${l} \u5FC5\u987B\u5305\u542B\uFF1A${g}`));e.push(...await Nr(r,t));for(let[l,c]of[["eslint.config.mjs",["from '@eslint/js'","from 'typescript-eslint'","from 'eslint-plugin-react-hooks'","recommendedTypeChecked","tsconfig.server.json","globals.browser","globals.node","'react-hooks/rules-of-hooks'","'react-hooks/exhaustive-deps'","no-floating-promises","disableTypeChecked"]],["prettier.config.mjs",["semi: false","singleQuote: true","trailingComma: 'all'"]],["tsconfig.json",['"strict": true','"noUncheckedIndexedAccess": true','"noEmit": true','"include": ["src", "shared", "vite.config.ts"]']],["tsconfig.server.json",['"strict": true','"erasableSyntaxOnly": true','"verbatimModuleSyntax": true','"exactOptionalPropertyTypes": true','"noUncheckedIndexedAccess": true','"types": ["node"]','"include": ["server", "shared"]']]]){let g=p.join(r,l);if(!await f(g))continue;let h=await w(g,"utf8");for(let E of c)h.includes(E)||e.push(s("error","tooling.config",`${l} \u7F3A\u5C11\u914D\u7F6E\u6807\u8BB0\uFF1A${E}`))}let a=p.join(r,"src","main.tsx");if(await f(a)){let l=await w(a,"utf8"),c=b(l);(Pe(c)!==1||c.includes("@realsee/vapor/vapor.scss"))&&e.push(s("error","vapor.css","src/main.tsx \u5FC5\u987B\u6070\u597D\u4E00\u6B21\u4EE5\u53EF\u6267\u884C side-effect import \u5BFC\u5165 @realsee/vapor/vapor.css\uFF0C\u4E14\u4E0D\u5F97\u4F7F\u7528 vapor.scss\u3002")),c.includes("./app/App")||e.push(s("error","architecture.app","src/main.tsx \u5FC5\u987B\u4ECE app \u5C42\u7EC4\u5408\u7A7A\u95F4\u8FD0\u884C\u65F6\u4E0E DOM UI\u3002"))}let o=p.join(r,"src","vr","five","api.ts");if(await f(o)){let l=await w(o,"utf8"),c=b(l);for(let g of["@realsee/five","@realsee/five/plugins","@realsee/five/line","@realsee/five/sticker","@realsee/five/vfx","@realsee/five/gltf-loader","@realsee/five/shader-lib","@realsee/five/work-downloader"])c.includes(`'${g}'`)||e.push(s("error","architecture.five-api",`Five \u80FD\u529B\u5165\u53E3\u7F3A\u5931\uFF1A${g}`))}let n=p.join(r,"src","vr","plugins","dnalogel.ts");await f(n)&&(b(await w(n,"utf8")).includes("from '@realsee/dnalogel'")||e.push(s("error","architecture.dnalogel-api","dnalogel \u9884\u8BBE\u63D2\u4EF6\u6839\u80FD\u529B\u5165\u53E3\u7F3A\u5931\u3002"))),e.push(...await Ve(r,"src/vr/plugins/examples/FeatureOwnedMeshPlugin.ts"));let i=p.join(r,"src");if(await f(i))for(let l of await C(i)){let c=b(await w(l,"utf8"));c.includes("@realsee/five/react")&&e.push(s("error","architecture.five-react",`${p.relative(r,l)} \u7981\u6B62\u4F7F\u7528\u66F4\u65B0\u6EDE\u540E\u7684 @realsee/five/react\uFF1B\u8BF7\u76F4\u63A5\u4F7F\u7528\u539F\u751F Five API\u3002`));for(let g of Er)g.pattern.test(c)&&e.push(s("error","compatibility.three-r117",`${p.relative(r,l)}\uFF1A${g.message}`))}let v=p.join(r,"src","vr","five","SpatialRuntimeProvider.tsx");if(await f(v)){let l=await w(v,"utf8"),c=b(l);(c.includes("unsafe__useFiveInstance")||c.includes("provider?.five"))&&e.push(s("error","architecture.five-instance","\u7A7A\u95F4\u8FD0\u884C\u65F6\u7981\u6B62\u901A\u8FC7 Five React wrapper \u83B7\u53D6\u5B9E\u4F8B\u3002")),/import\s*\{[^}]*\bFive\b[^}]*\}\s*from\s*['"]@realsee\/five['"]/.test(c)||e.push(s("error","architecture.five-native","SpatialRuntimeProvider \u5FC5\u987B\u4ECE @realsee/five \u5BFC\u5165\u539F\u751F Five\u3002"));for(let E of["new Five(",".appendTo(",".refresh(",".getElement()",".dispose()","new ResizeObserver","new SpatialRuntimeLifetime","runExclusive<T>(","ownedLifetime?.disposeFeatures()","ownedLifetime.afterPending(finalize)","width <= 0 || height <= 0",'className="five-surface"'])c.includes(E)||e.push(s("error","architecture.five-native",`\u539F\u751F Five \u751F\u547D\u5468\u671F\u7F3A\u5C11\uFF1A${E}`));let g=c.indexOf("ownedLifetime?.disposeFeatures()"),h=c.indexOf("ownedLifetime.afterPending(finalize)");(g<0||h<g)&&e.push(s("error","architecture.five-cleanup","\u5FC5\u987B\u5148\u540C\u6B65\u505C\u6B62 feature\uFF0C\u518D\u7B49\u5F85\u539F\u751F\u5F02\u6B65\u4EFB\u52A1\u5B8C\u6210\u540E\u6E05\u7406\u63D2\u4EF6\u548C Five\u3002"))}let u=p.join(r,"src","app","App.tsx");if(await f(u)){let l=await w(u,"utf8"),c=b(l);for(let g of["lifetime.own(","lifetime.runExclusive(","await five.load(","await waitForActiveModel(","const panoTagList = payload.presetData?.panoTagList","if (panoTagList?.length)","await currentTags.load(panoTagList)","setWorkName(payload.displayName)","title={workName}"])c.includes(g)||e.push(s("error","architecture.feature-lifetime",`App \u7684\u539F\u751F\u5F02\u6B65\u751F\u547D\u5468\u671F\u6216\u663E\u5F0F PanoTag preset \u6761\u4EF6\u7F3A\u5C11\uFF1A${g}`));c.includes("payload.tags")&&e.push(s("error","architecture.pano-tag-contract","App \u4E0D\u5F97\u628A generic payload.tags \u76F4\u63A5\u52A0\u8F7D\u4E3A PanoTag\u3002"))}let d=p.join(r,"src","ui"),m=p.join(d,"ViewerOverlay.tsx");if(await f(m)){let l=b(await w(m,"utf8")),c=Ne.filter(g=>!B(l,g)||!q(l,g));c.length>0&&e.push(s("error","architecture.vapor-provider",`ViewerOverlay \u5FC5\u987B\u4ECE @realsee/vapor \u5BFC\u5165\u5E76\u5B9E\u9645\u6E32\u67D3\u5B98\u65B9 Demo \u7EC4\u4EF6\uFF1A${c.join(", ")}`));for(let g of $e)l.includes(g)||e.push(s("error","architecture.viewer-topbar",`ViewerOverlay \u5FC5\u987B\u4F7F\u7528 Vapor TitleBar\u3001Layer \u4E0E\u54CD\u5E94\u5F0F Stack\uFF0C\u5E76\u6E32\u67D3\u5DE6\u4E0A\u6807\u9898\u3001\u5C45\u4E2D controls \u4E0E\u72B6\u6001\u8BED\u4E49\uFF1A${g}`));for(let g of Fe)l.includes(g)&&e.push(s("error","architecture.viewer-native-vapor",`ViewerOverlay \u4E0D\u5F97\u7528\u539F\u751F\u6807\u9898\u6216\u81EA\u5B9A\u4E49 glass \u6837\u5F0F\u66FF\u4EE3 Vapor \u7EC4\u4EF6\u89C6\u89C9\uFF1A${g}`))}let S=p.join(r,"src","features","navigation","ModeSwitcher.tsx");if(await f(S)){let l=b(await w(S,"utf8")),c=xe.filter(g=>!B(l,g)||!q(l,g));c.length>0&&e.push(s("error","architecture.vapor-component",`ModeSwitcher \u5FC5\u987B\u4ECE @realsee/vapor \u5BFC\u5165\u5E76\u5B9E\u9645\u6E32\u67D3\u5B98\u65B9 Demo \u7684\u6A21\u5F0F\u5207\u6362\u7EC4\u4EF6\uFF1A${c.join(", ")}`));for(let g of Me)l.includes(g)||e.push(s("error","architecture.viewer-control-size",`ModeSwitcher \u5FC5\u987B\u4F7F\u7528\u5B98\u65B9 Demo \u7684 Font/HStack/VStack \u4E0E shadow Button \u7EC4\u5408\uFF1A${g}`));for(let g of Ie)l.includes(g)&&e.push(s("error","architecture.viewer-native-vapor",`ModeSwitcher \u4E0D\u5F97\u9000\u56DE Segmented \u6216\u6DFB\u52A0\u65E7\u7684\u62C9\u4F38/glass \u6837\u5F0F\uFF1A${g}`))}if(await f(d))for(let l of await C(d)){let c=b(await w(l,"utf8")),g=/(?:from|import)\s*(?:\(\s*)?['"][^'"]*\/vr\//.test(c);(c.includes("@realsee/five")||c.includes("@realsee/dnalogel")||g)&&e.push(s("error","architecture.ui-boundary",`${p.relative(r,l)} \u4E0D\u5F97\u5BFC\u5165 src/vr\u3001Five \u6216 dnalogel \u7A7A\u95F4\u8FD0\u884C\u65F6\u3002`))}let y=p.join(r,"src","styles.css");if(await f(y)){let l=await w(y,"utf8");(!l.includes(".five-surface")||!l.includes(".dom-overlay")||!l.includes("pointer-events: none"))&&e.push(s("error","architecture.dom-overlay","\u539F\u751F Five surface \u4E0E DOM overlay \u5FC5\u987B\u4FDD\u6301\u72EC\u7ACB\u5206\u5C42\u53CA pointer-events \u9694\u79BB\u3002"));for(let c of Ce)l.includes(c)||e.push(s("error","architecture.viewer-layout",`Viewer \u5FC5\u987B\u4FDD\u6301 safe-area \u53CA\u684C\u9762/\u5E73\u677F/\u624B\u673A/\u77EE\u5C4F\u5916\u5C42\u54CD\u5E94\u5F0F\u5E03\u5C40\uFF1A${c}`));for(let c of De)l.includes(c)&&e.push(s("error","architecture.viewer-native-vapor",`Viewer \u6837\u5F0F\u4E0D\u5F97\u8986\u76D6 Vapor \u5185\u90E8\u7C7B\u6216\u91CD\u7ED8\u7EC4\u4EF6\u89C6\u89C9\uFF1A${c}`))}return e.push(...await He(r,".gitignore")),e.push(...await We(r,[".agents/skills/realsee-vr-app/SKILL.md","AGENTS.md"],".agents/skills/realsee-vr-app/references/dnalogel-api-capabilities.md")),e.push(...await Ye(r,[".agents/skills/realsee-vr-app/SKILL.md","AGENTS.md"],".agents/skills/realsee-vr-app")),e.push(...await Be(r,["AGENTS.md",".agents/skills/realsee-vr-app/SKILL.md"],".agents/skills/realsee-vr-app")),e.push(...await qe(r,{validator:"shared/model-floorplan.ts",types:"server/types.ts",client:"server/realsee-open-api.ts",service:"server/work-service.ts",workSource:"src/vr/work/WorkSource.ts",feature:"src/features/floorplan/ModelFloorplanFeature.ts",app:"src/app/App.tsx",modeSwitcher:"src/features/navigation/ModeSwitcher.tsx",browserTsconfig:"tsconfig.json",serverTsconfig:"tsconfig.server.json"})),e.push(...await Ke(r,["AGENTS.md",".agents/skills/realsee-vr-app/SKILL.md",".agents/skills/realsee-image-resources/SKILL.md"])),e.push(...await ze(r,["AGENTS.md",".agents/skills/realsee-vr-app/SKILL.md",".agents/skills/realsee-vr-app/references/vapor-dom-layer.md"])),e.push(...await Ue(r,".agents/skills/realsee-vr-app/references/vapor-dom-layer.md",".agents/skills/realsee-vr-app/references/ui-and-validation.md")),e.push(...await Fr(r)),e}async function Ze({directory:r=".",json:t=!1}){let e=p.resolve(r),a=p.join(e,"package.json"),o=[];N(process.versions.node,V)||o.push(s("error","node.version",`\u9700\u8981 Node >=${V}\uFF0C\u5F53\u524D ${process.versions.node}`));try{let i=await Lr();N(i,K)||o.push(s("error","npm.version",`\u9700\u8981 npm >=${K}\uFF0C\u5F53\u524D ${i}`))}catch(i){o.push(s("error","npm.unavailable","\u65E0\u6CD5\u6267\u884C npm --version\u3002",String(i)))}if(!await f(a))o.push(s("error","package.missing",`\u627E\u4E0D\u5230 package.json\uFF1A${a}`));else try{let i=await M(a);o.push(...await $r(e,i)?await Mr(e,i):await Dr(e,i))}catch(i){o.push(s("error","package.invalid","package.json \u65E0\u6CD5\u89E3\u6790\u3002",String(i)))}let n={ok:!o.some(i=>i.level==="error"),root:e,errors:o.filter(i=>i.level==="error").length,warnings:o.filter(i=>i.level==="warning").length,diagnostics:o};if(t)console.log(JSON.stringify(n,null,2));else{for(let i of o)console.log(`${i.level==="error"?"ERROR":"WARN "} [${i.code}] ${i.message}`);console.log(n.ok?`OK ${e}`:`FAILED ${e}`)}return n}var F=Object.freeze({version:1,capabilityOrder:["@realsee/dnalogel","@realsee/five","three"],intentRoutes:Object.freeze([Object.freeze({intent:"tag|hotspot|anchor",defaultCapability:"PanoTagPlugin",source:"GET /open/v3/hot/list"}),Object.freeze({intent:"tour",defaultCapability:"Five State snapshot",source:"GET /open/v3/observerpicture/search"}),Object.freeze({intent:"model-annotation",defaultCapability:"ModelMakerPlugin",source:"GET /open/v3/annotation/search"}),Object.freeze({intent:"floorplan|house-layout",defaultCapability:"ModelFloorplanPlugin",source:"GET /open/v3/floorplan/plugins -> data.format_floorplan_url"}),Object.freeze({intent:"route",defaultCapability:"GuideLinePlugin + CruisePlugin|MovePlugin",source:"GET /open/v3/route/search"})]),invariants:Object.freeze(["OpenAPI data is fetched by the BFF, validated, then adapted to installed SDK types.","SDK-shaped local data requires an explicit Open Platform opt-out.","A fallback records the missing preset capability before using Five or Three.","Only a completely validated { version, data: FloorplanServerData } wrapper from data.format_floorplan_url may expose the modelFloorplan capability.","Missing, empty, malformed or failed floorplan data is feature-local and leaves the base Viewer usable.","Each feature owns cancellation, registrations, hooks, media and Three resources.","Persistent Work or customer-data mutations require an authoritative write contract and confirmation."])});var Qe=`vr-cli ${j}
|
|
3
3
|
|
|
4
4
|
Usage:
|
package/package.json
CHANGED
|
@@ -41,6 +41,15 @@ async function temporaryProject() {
|
|
|
41
41
|
function prepareProcess(t, root) {
|
|
42
42
|
const previousDirectory = process.cwd()
|
|
43
43
|
const previousEnv = new Map(ENV_KEYS.map((key) => [key, process.env[key]]))
|
|
44
|
+
t.after(async () => {
|
|
45
|
+
// Windows cannot remove the process's current working directory.
|
|
46
|
+
process.chdir(previousDirectory)
|
|
47
|
+
for (const [key, value] of previousEnv) {
|
|
48
|
+
if (value === undefined) delete process.env[key]
|
|
49
|
+
else process.env[key] = value
|
|
50
|
+
}
|
|
51
|
+
await rm(root, { recursive: true, force: true })
|
|
52
|
+
})
|
|
44
53
|
process.chdir(root)
|
|
45
54
|
for (const key of ENV_KEYS) delete process.env[key]
|
|
46
55
|
process.env.REALSEE_PORTAL_BASE_URL = PORTAL_BASE_URL
|
|
@@ -55,13 +64,6 @@ function prepareProcess(t, root) {
|
|
|
55
64
|
stderr += String(chunk)
|
|
56
65
|
return true
|
|
57
66
|
})
|
|
58
|
-
t.after(() => {
|
|
59
|
-
process.chdir(previousDirectory)
|
|
60
|
-
for (const [key, value] of previousEnv) {
|
|
61
|
-
if (value === undefined) delete process.env[key]
|
|
62
|
-
else process.env[key] = value
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
67
|
return {
|
|
66
68
|
stdout: () => stdout,
|
|
67
69
|
stderr: () => stderr,
|
|
@@ -125,7 +127,6 @@ function installPortalFetch(t) {
|
|
|
125
127
|
|
|
126
128
|
test('completes the split pairing flow without printing credentials', async (t) => {
|
|
127
129
|
const root = await temporaryProject()
|
|
128
|
-
t.after(() => rm(root, { recursive: true, force: true }))
|
|
129
130
|
const output = prepareProcess(t, root)
|
|
130
131
|
const portal = installPortalFetch(t)
|
|
131
132
|
|
|
@@ -143,7 +144,10 @@ test('completes the split pairing flow without printing credentials', async (t)
|
|
|
143
144
|
assert.deepEqual(portal.observed.starts[0].capabilities, ['poll'])
|
|
144
145
|
|
|
145
146
|
const continuation = path.join(root, CONTINUATION_RELATIVE_PATH)
|
|
146
|
-
|
|
147
|
+
// Windows uses icacls in the real writer, not POSIX mode bits.
|
|
148
|
+
if (process.platform !== 'win32') {
|
|
149
|
+
assert.equal((await stat(continuation)).mode & 0o777, 0o600)
|
|
150
|
+
}
|
|
147
151
|
assert.match(await readFile(continuation, 'utf8'), /test-poll-secret/u)
|
|
148
152
|
|
|
149
153
|
output.clearOutput()
|
|
@@ -163,7 +167,9 @@ test('completes the split pairing flow without printing credentials', async (t)
|
|
|
163
167
|
assert.equal(portal.observed.polls, 1)
|
|
164
168
|
|
|
165
169
|
const localConfig = path.join(root, '.env.development')
|
|
166
|
-
|
|
170
|
+
if (process.platform !== 'win32') {
|
|
171
|
+
assert.equal((await stat(localConfig)).mode & 0o777, 0o600)
|
|
172
|
+
}
|
|
167
173
|
const config = await readFile(localConfig, 'utf8')
|
|
168
174
|
assert.match(config, /REALSEE_GATEWAY_APP_KEY="test-app-key-1234"/u)
|
|
169
175
|
assert.match(config, /REALSEE_GATEWAY_APP_SECRET="test-app-secret-value"/u)
|
|
@@ -173,7 +179,6 @@ test('completes the split pairing flow without printing credentials', async (t)
|
|
|
173
179
|
|
|
174
180
|
test('a denied pairing preserves the existing local configuration', async (t) => {
|
|
175
181
|
const root = await temporaryProject()
|
|
176
|
-
t.after(() => rm(root, { recursive: true, force: true }))
|
|
177
182
|
const output = prepareProcess(t, root)
|
|
178
183
|
installPortalFetch(t)
|
|
179
184
|
const localConfig = path.join(root, '.env.development')
|
|
@@ -199,6 +204,26 @@ test('a denied pairing preserves the existing local configuration', async (t) =>
|
|
|
199
204
|
})
|
|
200
205
|
})
|
|
201
206
|
|
|
207
|
+
test('temporary project cleanup restores cwd and preserves another demo', async (t) => {
|
|
208
|
+
const otherRoot = await temporaryProject()
|
|
209
|
+
t.after(() => rm(otherRoot, { recursive: true, force: true }))
|
|
210
|
+
const otherConfig = path.join(otherRoot, '.env.development')
|
|
211
|
+
await writeFile(otherConfig, 'other-demo-config\n')
|
|
212
|
+
const previousDirectory = process.cwd()
|
|
213
|
+
let root
|
|
214
|
+
|
|
215
|
+
await t.test('uses an isolated temporary project', async (child) => {
|
|
216
|
+
root = await temporaryProject()
|
|
217
|
+
prepareProcess(child, root)
|
|
218
|
+
assert.notEqual(root, otherRoot)
|
|
219
|
+
await writeFile(path.join(root, '.env.development'), 'temporary-config\n')
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
assert.equal(process.cwd(), previousDirectory)
|
|
223
|
+
await assert.rejects(stat(root), { code: 'ENOENT' })
|
|
224
|
+
assert.equal(await readFile(otherConfig, 'utf8'), 'other-demo-config\n')
|
|
225
|
+
})
|
|
226
|
+
|
|
202
227
|
test('rejects unsupported continuation schema versions', async () => {
|
|
203
228
|
const root = await temporaryProject()
|
|
204
229
|
try {
|