@tuongaz/seeflow 0.1.64 → 0.1.68
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 -0
- package/dist/web/assets/abap-DsBKuouk.js +1 -0
- package/dist/web/assets/actionscript-3-D_z4Izcz.js +1 -0
- package/dist/web/assets/ada-727ZlQH0.js +1 -0
- package/dist/web/assets/andromeeda-C3khCPGq.js +1 -0
- package/dist/web/assets/angular-html-4alyEGLm.js +1 -0
- package/dist/web/assets/angular-ts-BixEUTMq.js +1 -0
- package/dist/web/assets/apache-Dn00JSTd.js +1 -0
- package/dist/web/assets/apex-COJ4H7py.js +1 -0
- package/dist/web/assets/apl-BBq3IX1j.js +1 -0
- package/dist/web/assets/applescript-Bu5BbsvL.js +1 -0
- package/dist/web/assets/ara-7O62HKoU.js +1 -0
- package/dist/web/assets/asciidoc-BPT9niGB.js +1 -0
- package/dist/web/assets/asm-Dhn9LcZ4.js +1 -0
- package/dist/web/assets/astro-CqkE3fuf.js +1 -0
- package/dist/web/assets/aurora-x-D-2ljcwZ.js +1 -0
- package/dist/web/assets/awk-eg146-Ew.js +1 -0
- package/dist/web/assets/ayu-dark-Cv9koXgw.js +1 -0
- package/dist/web/assets/ballerina-Du268qiB.js +1 -0
- package/dist/web/assets/bat-fje9CFhw.js +1 -0
- package/dist/web/assets/beancount-BwXTMy5W.js +1 -0
- package/dist/web/assets/berry-3xVqZejG.js +1 -0
- package/dist/web/assets/bibtex-xW4inM5L.js +1 -0
- package/dist/web/assets/bicep-DHo0CJ0O.js +1 -0
- package/dist/web/assets/blade-a8OxSdnT.js +1 -0
- package/dist/web/assets/bsl-Dgyn0ogV.js +1 -0
- package/dist/web/assets/c-C3t2pwGQ.js +1 -0
- package/dist/web/assets/cadence-DNquZEk8.js +1 -0
- package/dist/web/assets/cairo--RitsXJZ.js +1 -0
- package/dist/web/assets/catppuccin-frappe-CD_QflpE.js +1 -0
- package/dist/web/assets/catppuccin-latte-DRW-0cLl.js +1 -0
- package/dist/web/assets/catppuccin-macchiato-C-_shW-Y.js +1 -0
- package/dist/web/assets/catppuccin-mocha-LGGdnPYs.js +1 -0
- package/dist/web/assets/chart-8DxAnLoD.js +73 -0
- package/dist/web/assets/clarity-BHOwM8T6.js +1 -0
- package/dist/web/assets/clojure-DxSadP1t.js +1 -0
- package/dist/web/assets/cmake-DbXoA79R.js +1 -0
- package/dist/web/assets/cobol-PTqiYgYu.js +1 -0
- package/dist/web/assets/code-block-cx7LPXvE.js +13 -0
- package/dist/web/assets/codeowners-Bp6g37R7.js +1 -0
- package/dist/web/assets/codeql-sacFqUAJ.js +1 -0
- package/dist/web/assets/coffee-dyiR41kL.js +1 -0
- package/dist/web/assets/common-lisp-C7gG9l05.js +1 -0
- package/dist/web/assets/coq-Dsg_Bt_b.js +1 -0
- package/dist/web/assets/cpp-BksuvNSY.js +1 -0
- package/dist/web/assets/crystal-DtDmRg-F.js +1 -0
- package/dist/web/assets/csharp-D9R-vmeu.js +1 -0
- package/dist/web/assets/css-BPhBrDlE.js +1 -0
- package/dist/web/assets/csv-B0qRVHPH.js +1 -0
- package/dist/web/assets/cue-DtFQj3wx.js +1 -0
- package/dist/web/assets/cypher-m2LEI-9-.js +1 -0
- package/dist/web/assets/d-BoXegm-a.js +1 -0
- package/dist/web/assets/dark-plus-C3mMm8J8.js +1 -0
- package/dist/web/assets/dart-B9wLZaAG.js +1 -0
- package/dist/web/assets/dax-ClGRhx96.js +1 -0
- package/dist/web/assets/desktop-DEIpsLCJ.js +1 -0
- package/dist/web/assets/diff-BgYniUM_.js +1 -0
- package/dist/web/assets/docker-COcR7UxN.js +1 -0
- package/dist/web/assets/dotenv-BjQB5zDj.js +1 -0
- package/dist/web/assets/dracula-BzJJZx-M.js +1 -0
- package/dist/web/assets/dracula-soft-BXkSAIEj.js +1 -0
- package/dist/web/assets/dream-maker-C-nORZOA.js +1 -0
- package/dist/web/assets/edge-D5gP-w-T.js +1 -0
- package/dist/web/assets/elixir-CLiX3zqd.js +1 -0
- package/dist/web/assets/elm-CmHSxxaM.js +1 -0
- package/dist/web/assets/emacs-lisp-BX77sIaO.js +1 -0
- package/dist/web/assets/erb-BYTLMnw6.js +1 -0
- package/dist/web/assets/erlang-B-DoSBHF.js +1 -0
- package/dist/web/assets/everforest-dark-BgDCqdQA.js +1 -0
- package/dist/web/assets/everforest-light-C8M2exoo.js +1 -0
- package/dist/web/assets/fennel-bCA53EVm.js +1 -0
- package/dist/web/assets/fish-w-ucz2PV.js +1 -0
- package/dist/web/assets/fluent-Dayu4EKP.js +1 -0
- package/dist/web/assets/fortran-fixed-form-TqA4NnZg.js +1 -0
- package/dist/web/assets/fortran-free-form-DKXYxT9g.js +1 -0
- package/dist/web/assets/fsharp-XplgxFYe.js +1 -0
- package/dist/web/assets/gdresource-BHYsBjWJ.js +1 -0
- package/dist/web/assets/gdscript-DfxzS6Rs.js +1 -0
- package/dist/web/assets/gdshader-SKMF96pI.js +1 -0
- package/dist/web/assets/genie-ajMbGru0.js +1 -0
- package/dist/web/assets/gherkin--30QC5Em.js +1 -0
- package/dist/web/assets/git-commit-i4q6IMui.js +1 -0
- package/dist/web/assets/git-rebase-B-v9cOL2.js +1 -0
- package/dist/web/assets/github-dark-DHJKELXO.js +1 -0
- package/dist/web/assets/github-dark-default-Cuk6v7N8.js +1 -0
- package/dist/web/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- package/dist/web/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- package/dist/web/assets/github-light-DAi9KRSo.js +1 -0
- package/dist/web/assets/github-light-default-D7oLnXFd.js +1 -0
- package/dist/web/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- package/dist/web/assets/gleam-B430Bg39.js +1 -0
- package/dist/web/assets/glimmer-js-D-cwc0-E.js +1 -0
- package/dist/web/assets/glimmer-ts-pgjy16dm.js +1 -0
- package/dist/web/assets/glsl-DBO2IWDn.js +1 -0
- package/dist/web/assets/gnuplot-CM8KxXT1.js +1 -0
- package/dist/web/assets/go-B1SYOhNW.js +1 -0
- package/dist/web/assets/graphql-cDcHW_If.js +1 -0
- package/dist/web/assets/groovy-DkBy-JyN.js +1 -0
- package/dist/web/assets/hack-D1yCygmZ.js +1 -0
- package/dist/web/assets/haml-B2EZWmdv.js +1 -0
- package/dist/web/assets/handlebars-BQGss363.js +1 -0
- package/dist/web/assets/haskell-BILxekzW.js +1 -0
- package/dist/web/assets/haxe-C5wWYbrZ.js +1 -0
- package/dist/web/assets/hcl-HzYwdGDm.js +1 -0
- package/dist/web/assets/hjson-T-Tgc4AT.js +1 -0
- package/dist/web/assets/hlsl-ifBTmRxC.js +1 -0
- package/dist/web/assets/houston-DnULxvSX.js +1 -0
- package/dist/web/assets/html-C2L_23MC.js +1 -0
- package/dist/web/assets/html-derivative-CSfWNPLT.js +1 -0
- package/dist/web/assets/http-FRrOvY1W.js +1 -0
- package/dist/web/assets/hxml-TIA70rKU.js +1 -0
- package/dist/web/assets/hy-BMj5Y0dO.js +1 -0
- package/dist/web/assets/imba-bv_oIlVt.js +1 -0
- package/dist/web/assets/index-CeLShda7.css +1 -0
- package/dist/web/assets/index-Dp4QwEl0.js +8608 -0
- package/dist/web/assets/{index.es-DZEdTXNJ.js → index.es-C-uXEdZB.js} +1 -1
- package/dist/web/assets/ini-BjABl1g7.js +1 -0
- package/dist/web/assets/java-xI-RfyKK.js +1 -0
- package/dist/web/assets/javascript-ySlJ1b_l.js +1 -0
- package/dist/web/assets/jinja-DGy0s7-h.js +1 -0
- package/dist/web/assets/jison-BqZprYcd.js +1 -0
- package/dist/web/assets/json-BQoSv7ci.js +1 -0
- package/dist/web/assets/json5-w8dY5SsB.js +1 -0
- package/dist/web/assets/jsonc-TU54ms6u.js +1 -0
- package/dist/web/assets/jsonl-DREVFZK8.js +1 -0
- package/dist/web/assets/jsonnet-BfivnA6A.js +1 -0
- package/dist/web/assets/{jspdf.es.min-DT1Li8zz.js → jspdf.es.min-BFQufOcQ.js} +3 -3
- package/dist/web/assets/jssm-P4WzXJd0.js +1 -0
- package/dist/web/assets/jsx-BAng5TT0.js +1 -0
- package/dist/web/assets/julia-BBuGR-5E.js +1 -0
- package/dist/web/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
- package/dist/web/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
- package/dist/web/assets/kanagawa-wave-DWedfzmr.js +1 -0
- package/dist/web/assets/kotlin-B5lbUyaz.js +1 -0
- package/dist/web/assets/kusto-mebxcVVE.js +1 -0
- package/dist/web/assets/laserwave-DUszq2jm.js +1 -0
- package/dist/web/assets/latex-C-cWTeAZ.js +1 -0
- package/dist/web/assets/lean-XBlWyCtg.js +1 -0
- package/dist/web/assets/less-BfCpw3nA.js +1 -0
- package/dist/web/assets/light-plus-B7mTdjB0.js +1 -0
- package/dist/web/assets/liquid-D3W5UaiH.js +1 -0
- package/dist/web/assets/log-Cc5clBb7.js +1 -0
- package/dist/web/assets/logo-IuBKFhSY.js +1 -0
- package/dist/web/assets/lua-CvWAzNxB.js +1 -0
- package/dist/web/assets/luau-Du5NY7AG.js +1 -0
- package/dist/web/assets/make-Bvotw-X0.js +1 -0
- package/dist/web/assets/markdown-DK_1WFMa.js +1 -0
- package/dist/web/assets/markdown-UIAJJxZW.js +1 -0
- package/dist/web/assets/marko-z0MBrx5-.js +1 -0
- package/dist/web/assets/material-theme-D5KoaKCx.js +1 -0
- package/dist/web/assets/material-theme-darker-BfHTSMKl.js +1 -0
- package/dist/web/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- package/dist/web/assets/material-theme-ocean-CyktbL80.js +1 -0
- package/dist/web/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- package/dist/web/assets/matlab-D9-PGadD.js +1 -0
- package/dist/web/assets/mdc-DB_EDNY_.js +1 -0
- package/dist/web/assets/mdx-sdHcTMYB.js +1 -0
- package/dist/web/assets/mermaid-Ci6OQyBP.js +1 -0
- package/dist/web/assets/min-dark-CafNBF8u.js +1 -0
- package/dist/web/assets/min-light-CTRr51gU.js +1 -0
- package/dist/web/assets/mipsasm-BC5c_5Pe.js +1 -0
- package/dist/web/assets/mojo-Tz6hzZYG.js +1 -0
- package/dist/web/assets/monokai-D4h5O-jR.js +1 -0
- package/dist/web/assets/move-DB_GagMm.js +1 -0
- package/dist/web/assets/narrat-DLbgOhZU.js +1 -0
- package/dist/web/assets/nextflow-B0XVJmRM.js +1 -0
- package/dist/web/assets/nginx-D_VnBJ67.js +1 -0
- package/dist/web/assets/night-owl-C39BiMTA.js +1 -0
- package/dist/web/assets/nim-ZlGxZxc3.js +1 -0
- package/dist/web/assets/nix-shcSOmrb.js +1 -0
- package/dist/web/assets/nord-Ddv68eIx.js +1 -0
- package/dist/web/assets/nushell-D4Tzg5kh.js +1 -0
- package/dist/web/assets/objective-c-Deuh7S70.js +1 -0
- package/dist/web/assets/objective-cpp-BUEGK8hf.js +1 -0
- package/dist/web/assets/ocaml-BNioltXt.js +1 -0
- package/dist/web/assets/one-dark-pro-GBQ2dnAY.js +1 -0
- package/dist/web/assets/one-light-PoHY5YXO.js +1 -0
- package/dist/web/assets/pascal-JqZropPD.js +1 -0
- package/dist/web/assets/perl-CHQXSrWU.js +1 -0
- package/dist/web/assets/php-B5ebYQev.js +1 -0
- package/dist/web/assets/plastic-3e1v2bzS.js +1 -0
- package/dist/web/assets/plsql-LKU2TuZ1.js +1 -0
- package/dist/web/assets/po-BFLt1xDp.js +1 -0
- package/dist/web/assets/poimandres-CS3Unz2-.js +1 -0
- package/dist/web/assets/polar-DKykz6zU.js +1 -0
- package/dist/web/assets/postcss-B3ZDOciz.js +1 -0
- package/dist/web/assets/powerquery-CSHBycmS.js +1 -0
- package/dist/web/assets/powershell-BIEUsx6d.js +1 -0
- package/dist/web/assets/prisma-B48N-Iqd.js +1 -0
- package/dist/web/assets/prolog-BY-TUvya.js +1 -0
- package/dist/web/assets/proto-zocC4JxJ.js +1 -0
- package/dist/web/assets/pug-CM9l7STV.js +1 -0
- package/dist/web/assets/puppet-Cza_XSSt.js +1 -0
- package/dist/web/assets/purescript-Bg-kzb6g.js +1 -0
- package/dist/web/assets/python-DhUJRlN_.js +1 -0
- package/dist/web/assets/qml-D8XfuvdV.js +1 -0
- package/dist/web/assets/qmldir-C8lEn-DE.js +1 -0
- package/dist/web/assets/qss-DhMKtDLN.js +1 -0
- package/dist/web/assets/r-CwjWoCRV.js +1 -0
- package/dist/web/assets/racket-CzouJOBO.js +1 -0
- package/dist/web/assets/raku-B1bQXN8T.js +1 -0
- package/dist/web/assets/razor-CNLDkMZG.js +1 -0
- package/dist/web/assets/red-bN70gL4F.js +1 -0
- package/dist/web/assets/reg-5LuOXUq_.js +1 -0
- package/dist/web/assets/regexp-DWJ3fJO_.js +1 -0
- package/dist/web/assets/rel-DJlmqQ1C.js +1 -0
- package/dist/web/assets/riscv-QhoSD0DR.js +1 -0
- package/dist/web/assets/rose-pine-CmCqftbK.js +1 -0
- package/dist/web/assets/rose-pine-dawn-Ds-gbosJ.js +1 -0
- package/dist/web/assets/rose-pine-moon-CjDtw9vr.js +1 -0
- package/dist/web/assets/rst-4NLicBqY.js +1 -0
- package/dist/web/assets/ruby-DeZ3UC14.js +1 -0
- package/dist/web/assets/rust-Be6lgOlo.js +1 -0
- package/dist/web/assets/sas-BmTFh92c.js +1 -0
- package/dist/web/assets/sass-BJ4Li9vH.js +1 -0
- package/dist/web/assets/scala-DQVVAn-B.js +1 -0
- package/dist/web/assets/scheme-BJGe-b2p.js +1 -0
- package/dist/web/assets/scss-C31hgJw-.js +1 -0
- package/dist/web/assets/sdbl-BLhTXw86.js +1 -0
- package/dist/web/assets/shaderlab-B7qAK45m.js +1 -0
- package/dist/web/assets/shellscript-atvbtKCR.js +1 -0
- package/dist/web/assets/shellsession-C_rIy8kc.js +1 -0
- package/dist/web/assets/slack-dark-BthQWCQV.js +1 -0
- package/dist/web/assets/slack-ochin-DqwNpetd.js +1 -0
- package/dist/web/assets/smalltalk-DkLiglaE.js +1 -0
- package/dist/web/assets/snazzy-light-Bw305WKR.js +1 -0
- package/dist/web/assets/solarized-dark-DXbdFlpD.js +1 -0
- package/dist/web/assets/solarized-light-L9t79GZl.js +1 -0
- package/dist/web/assets/solidity-C1w2a3ep.js +1 -0
- package/dist/web/assets/soy-C-lX7w71.js +1 -0
- package/dist/web/assets/sparql-bYkjHRlG.js +1 -0
- package/dist/web/assets/splunk-Cf8iN4DR.js +1 -0
- package/dist/web/assets/sql-COK4E0Yg.js +1 -0
- package/dist/web/assets/ssh-config-BknIz3MU.js +1 -0
- package/dist/web/assets/stata-DorPZHa4.js +1 -0
- package/dist/web/assets/stylus-BeQkCIfX.js +1 -0
- package/dist/web/assets/svelte-MSaWC3Je.js +1 -0
- package/dist/web/assets/swift-BSxZ-RaX.js +1 -0
- package/dist/web/assets/synthwave-84-CbfX1IO0.js +1 -0
- package/dist/web/assets/system-verilog-C7L56vO4.js +1 -0
- package/dist/web/assets/systemd-CUnW07Te.js +1 -0
- package/dist/web/assets/talonscript-C1XDQQGZ.js +1 -0
- package/dist/web/assets/tasl-CQjiPCtT.js +1 -0
- package/dist/web/assets/tcl-DQ1-QYvQ.js +1 -0
- package/dist/web/assets/templ-dwX3ZSMB.js +1 -0
- package/dist/web/assets/terraform-BbSNqyBO.js +1 -0
- package/dist/web/assets/tex-rYs2v40G.js +1 -0
- package/dist/web/assets/tokyo-night-DBQeEorK.js +1 -0
- package/dist/web/assets/toml-CB2ApiWb.js +1 -0
- package/dist/web/assets/ts-tags-CipyTH0X.js +1 -0
- package/dist/web/assets/tsv-B_m7g4N7.js +1 -0
- package/dist/web/assets/tsx-B6W0miNI.js +1 -0
- package/dist/web/assets/turtle-BMR_PYu6.js +1 -0
- package/dist/web/assets/twig-NC5TFiHP.js +1 -0
- package/dist/web/assets/typescript-Dj6nwHGl.js +1 -0
- package/dist/web/assets/typespec-BpWG_bgh.js +1 -0
- package/dist/web/assets/typst-BVUVsWT6.js +1 -0
- package/dist/web/assets/v-CAQ2eGtk.js +1 -0
- package/dist/web/assets/vala-BFOHcciG.js +1 -0
- package/dist/web/assets/vb-CdO5JTpU.js +1 -0
- package/dist/web/assets/verilog-CJaU5se_.js +1 -0
- package/dist/web/assets/vesper-BEBZ7ncR.js +1 -0
- package/dist/web/assets/vhdl-DYoNaHQp.js +1 -0
- package/dist/web/assets/viml-m4uW47V2.js +1 -0
- package/dist/web/assets/vitesse-black-Bkuqu6BP.js +1 -0
- package/dist/web/assets/vitesse-dark-D0r3Knsf.js +1 -0
- package/dist/web/assets/vitesse-light-CVO1_9PV.js +1 -0
- package/dist/web/assets/vue-BuYVFjOK.js +1 -0
- package/dist/web/assets/vue-html-xdeiXROB.js +1 -0
- package/dist/web/assets/vyper-nyqBNV6O.js +1 -0
- package/dist/web/assets/wasm-C6j12Q_x.js +1 -0
- package/dist/web/assets/wasm-CG6Dc4jp.js +1 -0
- package/dist/web/assets/wenyan-7A4Fjokl.js +1 -0
- package/dist/web/assets/wgsl-CB0Krxn9.js +1 -0
- package/dist/web/assets/wikitext-DCE3LsBG.js +1 -0
- package/dist/web/assets/wolfram-C3FkfJm5.js +1 -0
- package/dist/web/assets/xml-e3z08dGr.js +1 -0
- package/dist/web/assets/xsl-Dd0NUgwM.js +1 -0
- package/dist/web/assets/yaml-CVw76BM1.js +1 -0
- package/dist/web/assets/zenscript-HnGAYVZD.js +1 -0
- package/dist/web/assets/zig-BVz_zdnA.js +1 -0
- package/dist/web/index.html +2 -2
- package/examples/component-showcase/README.md +29 -0
- package/examples/component-showcase/flow.json +40 -0
- package/examples/component-showcase/nodes/chart/spec.json +66 -0
- package/examples/component-showcase/nodes/counter/spec.json +57 -0
- package/examples/component-showcase/nodes/fetcher/actions/refresh.ts +35 -0
- package/examples/component-showcase/nodes/fetcher/spec.json +68 -0
- package/examples/component-showcase/nodes/form/spec.json +87 -0
- package/examples/component-showcase/package.json +6 -0
- package/examples/component-showcase/style.json +28 -0
- package/examples/ecommerce-platform/flow.json +28 -30
- package/examples/order-pipeline/flow.json +16 -16
- package/package.json +2 -1
- package/src/api.ts +73 -23
- package/src/cli-e2e.ts +6 -7
- package/src/cli-manifest.ts +3 -5
- package/src/cli.ts +27 -0
- package/src/component-action-runner.ts +188 -0
- package/src/component-spec-resolver.ts +60 -0
- package/src/diagram.ts +8 -5
- package/src/layout.ts +23 -26
- package/src/mcp.ts +2 -2
- package/src/merge.ts +8 -3
- package/src/node-files.ts +1 -1
- package/src/operations.ts +109 -52
- package/src/runtime.ts +37 -0
- package/src/schema-catalog.ts +23 -11
- package/src/schema.ts +256 -262
- package/src/server.ts +1 -1
- package/src/status-runner.ts +2 -1
- package/src/watcher.ts +46 -10
- package/dist/web/assets/index-BAEA18IR.js +0 -7838
- package/dist/web/assets/index-CwfFCUzZ.css +0 -1
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dispatches a component node's `script`-kind ComponentAction over HTTP:
|
|
3
|
+
* resolves `scriptPath` under `<cwd>/nodes/<nodeId>/` with the same realpath
|
|
4
|
+
* escape check used by `runPlay` (see proxy.ts), spawns the interpreter via the
|
|
5
|
+
* injectable `ProcessSpawner` seam, pipes the request payload to stdin as JSON,
|
|
6
|
+
* and parses stdout back as JSON (falling back to the raw string).
|
|
7
|
+
*
|
|
8
|
+
* `set`-kind actions are intentionally rejected with statusHint 400: those
|
|
9
|
+
* mutate canvas state locally and never round-trip through the API. The runner
|
|
10
|
+
* is the single seam the API route calls; HTTP status mapping lives in the
|
|
11
|
+
* route handler via `statusHint`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { realpathSync } from 'node:fs';
|
|
15
|
+
import { join, resolve, sep } from 'node:path';
|
|
16
|
+
import type { EventBus } from './events.ts';
|
|
17
|
+
import { type ProcessSpawner, type SpawnHandle, defaultProcessSpawner } from './process-spawner.ts';
|
|
18
|
+
import type { ComponentAction } from './schema.ts';
|
|
19
|
+
import { shortId } from './short-id.ts';
|
|
20
|
+
|
|
21
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
22
|
+
const SIGKILL_GRACE_MS = 2_000;
|
|
23
|
+
const SCRIPT_PATH_ESCAPE = 'scriptPath escapes node root';
|
|
24
|
+
const SET_KIND_REJECTION = 'Only script actions are dispatched over HTTP';
|
|
25
|
+
|
|
26
|
+
export interface RunComponentActionOptions {
|
|
27
|
+
events: EventBus;
|
|
28
|
+
flowId: string;
|
|
29
|
+
nodeId: string;
|
|
30
|
+
/** Project root (`<repoPath>`). Script resolves under `<cwd>/nodes/<nodeId>/`. */
|
|
31
|
+
cwd: string;
|
|
32
|
+
actionName: string;
|
|
33
|
+
action: ComponentAction;
|
|
34
|
+
payload: unknown;
|
|
35
|
+
/** Injectable for tests; defaults to `defaultProcessSpawner`. */
|
|
36
|
+
spawner?: ProcessSpawner;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ComponentActionResult {
|
|
40
|
+
ok: boolean;
|
|
41
|
+
body?: unknown;
|
|
42
|
+
error?: string;
|
|
43
|
+
/** Suggested HTTP status for the API handler. */
|
|
44
|
+
statusHint: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type Resolved = { ok: true; absPath: string } | { ok: false };
|
|
48
|
+
|
|
49
|
+
// Resolve `<cwd>/nodes/<nodeId>/<scriptPath>` and verify via realpath it stays
|
|
50
|
+
// inside the node folder. Mirrors proxy.ts:resolveScript — symlink-escape
|
|
51
|
+
// defense in line with `resolveProjectFile` in api.ts.
|
|
52
|
+
function resolveScript(cwd: string, nodeId: string, scriptPath: string): Resolved {
|
|
53
|
+
const nodeRoot = join(cwd, 'nodes', nodeId);
|
|
54
|
+
let realRoot: string;
|
|
55
|
+
try {
|
|
56
|
+
realRoot = realpathSync(nodeRoot);
|
|
57
|
+
} catch {
|
|
58
|
+
return { ok: false };
|
|
59
|
+
}
|
|
60
|
+
const target = resolve(nodeRoot, scriptPath);
|
|
61
|
+
let realTarget: string;
|
|
62
|
+
try {
|
|
63
|
+
realTarget = realpathSync(target);
|
|
64
|
+
} catch {
|
|
65
|
+
return { ok: false };
|
|
66
|
+
}
|
|
67
|
+
const rootWithSep = realRoot.endsWith(sep) ? realRoot : realRoot + sep;
|
|
68
|
+
if (realTarget !== realRoot && !realTarget.startsWith(rootWithSep)) {
|
|
69
|
+
return { ok: false };
|
|
70
|
+
}
|
|
71
|
+
return { ok: true, absPath: realTarget };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Copy `process.env` into a string-only record, then layer the per-run extras.
|
|
75
|
+
// Bun.spawn's env contract is `Record<string, string>` so the undefineds that
|
|
76
|
+
// `process.env` advertises in its type must be filtered out first.
|
|
77
|
+
function buildChildEnv(extra: Record<string, string>): Record<string, string> {
|
|
78
|
+
const env: Record<string, string> = {};
|
|
79
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
80
|
+
if (typeof v === 'string') env[k] = v;
|
|
81
|
+
}
|
|
82
|
+
return { ...env, ...extra };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function writeStdinPayload(handle: SpawnHandle, payload: unknown): Promise<void> {
|
|
86
|
+
if (!handle.stdin) return;
|
|
87
|
+
const writer = handle.stdin.getWriter();
|
|
88
|
+
try {
|
|
89
|
+
await writer.write(new TextEncoder().encode(JSON.stringify(payload)));
|
|
90
|
+
} finally {
|
|
91
|
+
await writer.close().catch(() => {
|
|
92
|
+
/* stdin already closed by child — not fatal */
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function killWithGrace(handle: SpawnHandle): Promise<void> {
|
|
98
|
+
handle.kill('SIGTERM');
|
|
99
|
+
let graceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
100
|
+
const gracePromise = new Promise<'grace'>((res) => {
|
|
101
|
+
graceTimer = setTimeout(() => res('grace'), SIGKILL_GRACE_MS);
|
|
102
|
+
});
|
|
103
|
+
const winner = await Promise.race([handle.exited.then(() => 'exited' as const), gracePromise]);
|
|
104
|
+
if (graceTimer) clearTimeout(graceTimer);
|
|
105
|
+
if (winner === 'grace') {
|
|
106
|
+
handle.kill('SIGKILL');
|
|
107
|
+
await handle.exited;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function runComponentAction(
|
|
112
|
+
opts: RunComponentActionOptions,
|
|
113
|
+
): Promise<ComponentActionResult> {
|
|
114
|
+
if (opts.action.kind !== 'script') {
|
|
115
|
+
return { ok: false, error: SET_KIND_REJECTION, statusHint: 400 };
|
|
116
|
+
}
|
|
117
|
+
const spawner = opts.spawner ?? defaultProcessSpawner;
|
|
118
|
+
const resolved = resolveScript(opts.cwd, opts.nodeId, opts.action.scriptPath);
|
|
119
|
+
if (!resolved.ok) {
|
|
120
|
+
return { ok: false, error: SCRIPT_PATH_ESCAPE, statusHint: 400 };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const env = buildChildEnv({
|
|
124
|
+
SEEFLOW_DEMO_ID: opts.flowId,
|
|
125
|
+
SEEFLOW_NODE_ID: opts.nodeId,
|
|
126
|
+
SEEFLOW_ACTION_NAME: opts.actionName,
|
|
127
|
+
SEEFLOW_RUN_ID: shortId(),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
let handle: SpawnHandle;
|
|
131
|
+
try {
|
|
132
|
+
handle = spawner.spawn({
|
|
133
|
+
cmd: [opts.action.interpreter, ...(opts.action.args ?? []), resolved.absPath],
|
|
134
|
+
cwd: opts.cwd,
|
|
135
|
+
env,
|
|
136
|
+
stdin: 'pipe',
|
|
137
|
+
});
|
|
138
|
+
} catch (err) {
|
|
139
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
140
|
+
return { ok: false, error: message, statusHint: 500 };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Drain stdout AND stderr CONCURRENTLY with the process running so OS pipe
|
|
144
|
+
// buffers (~64 KB) don't fill up and deadlock the child.
|
|
145
|
+
const stdoutPromise = new Response(handle.stdout).text();
|
|
146
|
+
const stderrPromise = new Response(handle.stderr).text();
|
|
147
|
+
|
|
148
|
+
// Write stdin and close BEFORE awaiting exit (otherwise a child blocked on
|
|
149
|
+
// `read(stdin)` and a parent blocked on `exited` deadlock each other).
|
|
150
|
+
await writeStdinPayload(handle, opts.payload);
|
|
151
|
+
|
|
152
|
+
const timeoutMs = opts.action.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
153
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
154
|
+
const timeoutPromise = new Promise<'timeout'>((res) => {
|
|
155
|
+
timer = setTimeout(() => res('timeout'), timeoutMs);
|
|
156
|
+
});
|
|
157
|
+
const exitPromise = handle.exited.then((code) => ({ code }) as const);
|
|
158
|
+
|
|
159
|
+
const race = await Promise.race([exitPromise, timeoutPromise]);
|
|
160
|
+
if (timer) clearTimeout(timer);
|
|
161
|
+
|
|
162
|
+
if (race === 'timeout') {
|
|
163
|
+
await killWithGrace(handle);
|
|
164
|
+
await Promise.allSettled([stdoutPromise, stderrPromise]);
|
|
165
|
+
return {
|
|
166
|
+
ok: false,
|
|
167
|
+
error: `action timed out after ${timeoutMs}ms`,
|
|
168
|
+
statusHint: 504,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const [stdout, stderr] = await Promise.all([stdoutPromise, stderrPromise]);
|
|
173
|
+
if (race.code !== 0) {
|
|
174
|
+
return {
|
|
175
|
+
ok: false,
|
|
176
|
+
error: stderr.trim() || `exit ${race.code}`,
|
|
177
|
+
statusHint: 500,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let body: unknown;
|
|
182
|
+
try {
|
|
183
|
+
body = JSON.parse(stdout);
|
|
184
|
+
} catch {
|
|
185
|
+
body = stdout;
|
|
186
|
+
}
|
|
187
|
+
return { ok: true, body, statusHint: 200 };
|
|
188
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import type { ResolvedFlow } from './schema.ts';
|
|
4
|
+
|
|
5
|
+
export interface SpecInlineError {
|
|
6
|
+
/** Logical path into the merged flow shape, like 'nodes/<id>/data/spec'. */
|
|
7
|
+
path: string;
|
|
8
|
+
message: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface InlineComponentSpecsResult {
|
|
12
|
+
flow: ResolvedFlow;
|
|
13
|
+
errors: SpecInlineError[];
|
|
14
|
+
/** Project-root-relative paths the watcher should track for live reload. */
|
|
15
|
+
refs: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* For every `'component'` node in `flow`, read `nodes/<id>/spec.json`,
|
|
20
|
+
* JSON.parse, and attach the result as `data.spec`. Missing files surface
|
|
21
|
+
* as a SpecInlineError; malformed JSON surfaces likewise. Non-component
|
|
22
|
+
* nodes pass through untouched.
|
|
23
|
+
*
|
|
24
|
+
* Returns a NEW flow object (no mutation of the input) so the watcher's
|
|
25
|
+
* snapshot caching stays safe.
|
|
26
|
+
*/
|
|
27
|
+
export function inlineComponentSpecs(
|
|
28
|
+
flow: ResolvedFlow,
|
|
29
|
+
projectRoot: string,
|
|
30
|
+
): InlineComponentSpecsResult {
|
|
31
|
+
const errors: SpecInlineError[] = [];
|
|
32
|
+
const refs: string[] = [];
|
|
33
|
+
|
|
34
|
+
const nodes = flow.nodes.map((node) => {
|
|
35
|
+
if (node.type !== 'component') return node;
|
|
36
|
+
const relPath = `nodes/${node.id}/spec.json`;
|
|
37
|
+
const absPath = join(projectRoot, relPath);
|
|
38
|
+
if (!existsSync(absPath)) {
|
|
39
|
+
errors.push({
|
|
40
|
+
path: `nodes/${node.id}/data/spec`,
|
|
41
|
+
message: `Missing spec file: ${relPath}`,
|
|
42
|
+
});
|
|
43
|
+
return node;
|
|
44
|
+
}
|
|
45
|
+
let parsed: unknown;
|
|
46
|
+
try {
|
|
47
|
+
parsed = JSON.parse(readFileSync(absPath, 'utf8'));
|
|
48
|
+
} catch (err) {
|
|
49
|
+
errors.push({
|
|
50
|
+
path: `nodes/${node.id}/data/spec`,
|
|
51
|
+
message: `Invalid JSON in ${relPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
52
|
+
});
|
|
53
|
+
return node;
|
|
54
|
+
}
|
|
55
|
+
refs.push(relPath);
|
|
56
|
+
return { ...node, data: { ...node.data, spec: parsed } } as typeof node;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return { flow: { ...flow, nodes } as ResolvedFlow, errors, refs };
|
|
60
|
+
}
|
package/src/diagram.ts
CHANGED
|
@@ -240,9 +240,10 @@ const normalizeConnectors = (
|
|
|
240
240
|
// Single-flow-node graphs short-circuit so callers can pin standalone
|
|
241
241
|
// nodes via `layout.positions`.
|
|
242
242
|
const isFloatingAnnotation = (n: Record<string, unknown>): boolean => {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
243
|
+
// Flat-types refactor: visual kind IS the type. The annotation shapes
|
|
244
|
+
// (sticky + text) live as top-level type tags rather than nested shape
|
|
245
|
+
// under a generic shape variant.
|
|
246
|
+
return n.type === 'sticky' || n.type === 'text';
|
|
246
247
|
};
|
|
247
248
|
|
|
248
249
|
const autoLayout = async (
|
|
@@ -354,12 +355,14 @@ export const validateDemo = (req: ValidateRequest): ValidateReport => {
|
|
|
354
355
|
|
|
355
356
|
const playable = nodes.filter((n) => {
|
|
356
357
|
const data = n.data as { playAction?: unknown } | undefined;
|
|
357
|
-
|
|
358
|
+
// Flat-types refactor: a node is playable iff it carries a playAction
|
|
359
|
+
// capability, regardless of variant.
|
|
360
|
+
return data?.playAction !== undefined;
|
|
358
361
|
});
|
|
359
362
|
if (tier !== 'static' && playable.length === 0) {
|
|
360
363
|
issues.push({
|
|
361
364
|
kind: 'tier-mismatch',
|
|
362
|
-
message: `Tier '${tier}' requires at least one playable node; found 0.
|
|
365
|
+
message: `Tier '${tier}' requires at least one playable node; found 0. Set data.playAction on a node or set tier=static.`,
|
|
363
366
|
});
|
|
364
367
|
}
|
|
365
368
|
|
package/src/layout.ts
CHANGED
|
@@ -51,7 +51,7 @@ export interface LayoutResult {
|
|
|
51
51
|
export interface LayoutNode {
|
|
52
52
|
id: string;
|
|
53
53
|
type: FlowNode['type'];
|
|
54
|
-
data?: { width?: number; height?: number
|
|
54
|
+
data?: { width?: number; height?: number };
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export interface LayoutEdge {
|
|
@@ -60,42 +60,39 @@ export interface LayoutEdge {
|
|
|
60
60
|
target: string;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
// Per-type default size used when a node has no explicit width/height. Mirrors
|
|
64
|
+
// the canvas's SHAPE_DEFAULT_SIZE so ELK's layout matches what the canvas will
|
|
65
|
+
// paint.
|
|
63
66
|
const DEFAULT_DIMENSIONS: Record<FlowNode['type'], { width: number; height: number }> = {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
iconNode: { width: 80, height: 80 },
|
|
68
|
-
htmlNode: { width: 320, height: 200 },
|
|
69
|
-
imageNode: { width: 200, height: 150 },
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const SHAPE_OVERRIDES: Record<string, { width: number; height: number }> = {
|
|
67
|
+
rectangle: { width: 200, height: 120 },
|
|
68
|
+
ellipse: { width: 200, height: 120 },
|
|
69
|
+
sticky: { width: 180, height: 180 },
|
|
73
70
|
text: { width: 160, height: 40 },
|
|
74
|
-
|
|
71
|
+
database: { width: 120, height: 140 },
|
|
72
|
+
server: { width: 140, height: 120 },
|
|
73
|
+
user: { width: 100, height: 140 },
|
|
74
|
+
queue: { width: 220, height: 80 },
|
|
75
|
+
cloud: { width: 180, height: 120 },
|
|
76
|
+
image: { width: 200, height: 150 },
|
|
77
|
+
html: { width: 320, height: 200 },
|
|
78
|
+
icon: { width: 80, height: 80 },
|
|
79
|
+
component: { width: 320, height: 240 },
|
|
75
80
|
};
|
|
76
81
|
|
|
77
|
-
|
|
82
|
+
// Sticky / text variants are floating annotations. They never participate in
|
|
83
|
+
// layered layout — they sit in a side column so the orthogonal flow stays
|
|
84
|
+
// clean.
|
|
85
|
+
const FLOATING_TYPES: ReadonlySet<FlowNode['type']> = new Set(['sticky', 'text']);
|
|
78
86
|
|
|
79
87
|
const nodeDimensions = (node: LayoutNode): { width: number; height: number } => {
|
|
80
88
|
const data = node.data ?? {};
|
|
81
89
|
if (typeof data.width === 'number' && typeof data.height === 'number') {
|
|
82
90
|
return { width: data.width, height: data.height };
|
|
83
91
|
}
|
|
84
|
-
if (node.type === 'shapeNode' && data.shape) {
|
|
85
|
-
const override = SHAPE_OVERRIDES[data.shape];
|
|
86
|
-
if (override) return override;
|
|
87
|
-
}
|
|
88
92
|
return DEFAULT_DIMENSIONS[node.type];
|
|
89
93
|
};
|
|
90
94
|
|
|
91
|
-
|
|
92
|
-
// layered layout — they sit in a side column so the orthogonal flow stays
|
|
93
|
-
// clean.
|
|
94
|
-
const isFloatingAnnotation = (node: LayoutNode): boolean => {
|
|
95
|
-
if (node.type !== 'shapeNode') return false;
|
|
96
|
-
const shape = node.data?.shape;
|
|
97
|
-
return shape !== undefined && FLOATING_SHAPES.has(shape);
|
|
98
|
-
};
|
|
95
|
+
const isFloatingAnnotation = (node: LayoutNode): boolean => FLOATING_TYPES.has(node.type);
|
|
99
96
|
|
|
100
97
|
// Schema vocabulary: SourceHandle ∈ {r, b}, TargetHandle ∈ {t, l}. After
|
|
101
98
|
// ELK lays out positions we pick handles geometrically — the layered LR
|
|
@@ -162,7 +159,7 @@ export const computeLayout = async (
|
|
|
162
159
|
'elk.separateConnectedComponents': 'true',
|
|
163
160
|
},
|
|
164
161
|
children: laidOut.map((n) => {
|
|
165
|
-
const d = dims.get(n.id) ?? DEFAULT_DIMENSIONS.
|
|
162
|
+
const d = dims.get(n.id) ?? DEFAULT_DIMENSIONS.rectangle;
|
|
166
163
|
return { id: n.id, width: d.width, height: d.height };
|
|
167
164
|
}),
|
|
168
165
|
edges: connectors
|
|
@@ -193,7 +190,7 @@ export const computeLayout = async (
|
|
|
193
190
|
const columnX = laidOut.length > 0 ? maxRight + 200 : 0;
|
|
194
191
|
let cursorY = 0;
|
|
195
192
|
for (const n of floatingNodes) {
|
|
196
|
-
const d = dims.get(n.id) ?? DEFAULT_DIMENSIONS.
|
|
193
|
+
const d = dims.get(n.id) ?? DEFAULT_DIMENSIONS.rectangle;
|
|
197
194
|
result.nodes[n.id] = { position: { x: columnX, y: cursorY } };
|
|
198
195
|
cursorY += d.height + nodeSpacing;
|
|
199
196
|
}
|
package/src/mcp.ts
CHANGED
|
@@ -461,7 +461,7 @@ const buildTools = (ops: Operations): McpTool[] => [
|
|
|
461
461
|
{
|
|
462
462
|
name: 'seeflow_add_node',
|
|
463
463
|
description:
|
|
464
|
-
|
|
464
|
+
"Append a new node to a flow (cascade-safe; id auto-generated when omitted). Text content fields (detail on every node; html on type:'html') are auto-externalized to <project>/nodes/<id>/ and stored as file:// refs in flow.json; reads inline the resolved content transparently.",
|
|
465
465
|
inputSchema: inputSchemaFromZod(AddNodeInputSchema),
|
|
466
466
|
handler: async (args) => {
|
|
467
467
|
const parsed = AddNodeInputSchema.safeParse(args);
|
|
@@ -585,7 +585,7 @@ const buildTools = (ops: Operations): McpTool[] => [
|
|
|
585
585
|
{
|
|
586
586
|
name: 'seeflow_patch_node',
|
|
587
587
|
description:
|
|
588
|
-
|
|
588
|
+
"Update fields on an existing node (position, name, description, detail, icon, colors, border, font, dimensions, autoSize, plus type:'icon'-only color/strokeWidth/alt and capabilities playAction/statusAction/stateSource). `type` can flip a node between any of the 12 visual variants (rectangle/ellipse/sticky/text/database/server/user/queue/cloud/image/html/icon); the post-merge schema reparse gates required fields on the new type (image.data.path, icon.data.icon). Setting detail (every node) or html (type:'html') writes the content to <project>/nodes/<id>/{detail.md|view.html}; the file:// ref on the node persists. Empty-string detail empties the file but keeps the ref.",
|
|
589
589
|
inputSchema: inputSchemaFromZod(PatchNodeInputSchema),
|
|
590
590
|
handler: async (args) => {
|
|
591
591
|
const parsed = PatchNodeInputSchema.safeParse(args);
|
package/src/merge.ts
CHANGED
|
@@ -39,10 +39,12 @@ export function mergeFlowAndStyle(flow: Flow, style: Style): ResolvedFlow {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// Fields that live in a node's `data` block on flow.json. Every other data
|
|
42
|
-
// field is visual and routes to style.json.
|
|
42
|
+
// field is visual and routes to style.json. The flat-types refactor folds
|
|
43
|
+
// the visual kind into `node.type` itself (no more nested data.shape / data.kind)
|
|
44
|
+
// and makes every capability (playAction / statusAction / stateSource) valid
|
|
45
|
+
// on every type.
|
|
43
46
|
const NODE_DATA_FLOW_KEYS = new Set([
|
|
44
47
|
'name',
|
|
45
|
-
'kind',
|
|
46
48
|
'stateSource',
|
|
47
49
|
'handlerModule',
|
|
48
50
|
'icon',
|
|
@@ -50,7 +52,6 @@ const NODE_DATA_FLOW_KEYS = new Set([
|
|
|
50
52
|
'detail',
|
|
51
53
|
'playAction',
|
|
52
54
|
'statusAction',
|
|
53
|
-
'shape',
|
|
54
55
|
'path',
|
|
55
56
|
'alt',
|
|
56
57
|
'html',
|
|
@@ -132,6 +133,10 @@ export function splitFlow(resolved: {
|
|
|
132
133
|
const flowData: Record<string, unknown> = {};
|
|
133
134
|
for (const [k, v] of Object.entries(data)) {
|
|
134
135
|
if (v === undefined) continue;
|
|
136
|
+
// Component nodes externalize `spec` to <project>/nodes/<id>/spec.json
|
|
137
|
+
// (the sidecar). Drop it from flow.json so the strict on-disk schema
|
|
138
|
+
// doesn't reject it and the spec stays single-sourced on disk.
|
|
139
|
+
if (node.type === 'component' && k === 'spec') continue;
|
|
135
140
|
if (NODE_DATA_FLOW_KEYS.has(k)) {
|
|
136
141
|
flowData[k] = v;
|
|
137
142
|
} else if (NODE_STYLE_KEYS.has(k)) {
|
package/src/node-files.ts
CHANGED
|
@@ -14,7 +14,7 @@ export interface ExternalizedFieldSpec {
|
|
|
14
14
|
|
|
15
15
|
export const EXTERNALIZED_NODE_FIELDS: readonly ExternalizedFieldSpec[] = [
|
|
16
16
|
{ field: 'detail', fileName: 'detail.md' },
|
|
17
|
-
{ field: 'html', fileName: 'view.html', nodeTypes: ['
|
|
17
|
+
{ field: 'html', fileName: 'view.html', nodeTypes: ['html'] },
|
|
18
18
|
];
|
|
19
19
|
|
|
20
20
|
export const externalizedFieldsForNodeType = (
|