Some script【免费下载链接】zxA tool for writing better scripts项目地址: https://gitcode.com/GitHub_Trending/zx/zxls— is an unix command to get directory contents. Lets see how to use it inzx:// ts, js, cjs, mjs, etc const {stdout} await $ls -l console.log(directory contents:, stdout)This part invokes the same command in a different way:# bash syntax ls -l执行方式就是一行命令 bash zx script.md 在 [src/cli.ts](https://link.gitcode.com/i/5d647a6721c5d3526b80ce7516315880) 的 readScript() 中可以看到触发点当脚本扩展名是 .md 时先调用 transformMarkdown(script) 转换内容然后 tempPath getFilepath(dir, base, EXT) 生成一个临时 .mjs 文件见 [src/cli.ts#L238-L241](https://link.gitcode.com/i/5d647a6721c5d3526b80ce7516315880#L238-L241)转换后的代码写入该临时文件后被 import() 动态加载。 ## 逐块解析测试夹具 test/fixtures/markdown.md [test/fixtures/markdown.md](https://link.gitcode.com/i/48c2530950b90b179e053e9a1d0a78d8) 是 CI 中反复运行的 Markdown 脚本它几乎覆盖了所有转换分支正文注释、引用块、js 块、tilde 围栏、bash 块、未知语言块、缩进代码块。逐块看它的“命运” ### 1. 第一行 ignore正文自动变成注释 markdown # Markdown ignore 夹具开头的 # Markdown 标题和 ignore 这样的普通行在转换后都变成 // # Markdown、// ignore 形式的注释行。transformMarkdown() 的 root 状态对围栏之外的行统一执行 out.push(// line)见 [src/md.ts#L69-L71](https://link.gitcode.com/i/98ab81ca97a872aa9dbf9a46db45a863)空行也变成 // 。这意味着 Markdown 里任何散文都可以自由书写永远不会被执行——写脚本时可以放心地用标题、表格、引用做文档。 ### 2. 引用块里的代码因为 前缀而不被识别为围栏 markdown echo ignore 这段引用块看起来是一个代码围栏但每行都带 前缀。围栏识别正则 fenceRe 要求围栏前最多 3 个空格(?indent {0,3})(?fence({3,20}|~{3,20}))...见 [src/md.ts#L21-L22](https://link.gitcode.com/i/677dc746334bd2c170bb543b72163510) 不满足这个条件因此这五行全部落入 root 状态被注释掉echo ignore 永远不会运行。[test/cli.test.js#L93-L98](https://link.gitcode.com/i/9f7ed2cb6b6dd271a861e30ce0ae339c) 的 --quiet 测试正验证了这一点运行 node build/cli.js --quiet test/fixtures/markdown.md 后stderr 中不出现 ignore——如果引用块被误执行zx 的命令回显会把 echo ignore 打到 stderr 上。 ### 3. js 与 ~~~js 围栏按原始代码执行 js await $whoami await $echo ${__dirname} ~~~js await $echo tilde ~~~ 夹具中同时出现了反引号围栏和 tilde 围栏两种写法这对应 fenceRe 中 ({3,20}|~{3,20} ) 的分支。对 js 系代码块转换器不添加任何前缀代码原样进入输出linePrefix 见 [src/md.ts#L44-L47](https://link.gitcode.com/i/0c175eeca9b6e2de365352cf168d1a44)因此模板字符串里的 ${__dirname} 会在 Node 运行时求值。注意 __dirname 不是 zx 魔法变量而是 injectGlobalRequire() 注入的全局变量见下文。 ### 4. bash 围栏整块包进 await $... bash VAR$(echo hello) echo $VAR bash/sh/shell 代码块的处理与 js 块不同转换器在块首输出 await $ 、块尾输出反引号closeOut 见 [src/md.ts#L48-L51](https://link.gitcode.com/i/37fb82afbb12b0d4e210f6c66df9a4fc)整块 shell 语句作为一个模板字符串交给 zx 的 $ 执行。多行 shell 语法如这里的变量赋值与展开因此被完整保留。test/md.test.ts 中的断言展示了转换结果~~~sh\necho foo\n~~~ 变成 await $\\necho foo\n\见 [test/md.test.ts#L77-L80](https://link.gitcode.com/i/ca95bea1eca7dacde54c9ea40f44a159)。 ### 5. 动态 import 与 __filename 夹具后两个 js 块 js console.log(chalk.yellowBright(__filename)) js await import(chalk) 它们演示了 Markdown 脚本中可以使用动态 import 引入依赖[docs/markdown.md](https://link.gitcode.com/i/b4d1895487886ea6772e230afa098c19) 明确说 “You can use imports here as well”以及 __filename 指向 Markdown 文件所在位置。[docs/markdown.md](https://link.gitcode.com/i/b4d1895487886ea6772e230afa098c19) 专门说明 “The __filename will be pointed to **markdown.md**”。从源码结构看机制是.md 脚本转换后写入与原文件同目录、同名主基的临时 .mjs 文件[src/cli.ts#L238-L241](https://link.gitcode.com/i/5d647a6721c5d3526b80ce7516315880#L238-L241)随后 injectGlobalRequire(scriptPath) 用该路径设置全局 __filename、__dirname 和 require见 [src/cli.ts#L261-L266](https://link.gitcode.com/i/5d647a6721c5d3526b80ce7516315880#L261-L266)。所以夹具里 await $echo ${__dirname} 打印出来的正是原 Markdown 文件所在的目录。 ### 6. 缩进代码块作为“遗留行为”原样执行 markdown // ignore console.log(world) 夹具第 32–33 行是缩进 4 个空格的代码。Markdown 语义上这算“缩进代码块”但 zx 的转换器把“空白行之后、以两个以上空格或 Tab 开头”的行视为可执行代码保留原样输出tab 状态见 [src/md.ts#L63-L82](https://link.gitcode.com/i/b5cd63c65df7c26fc433d86e8d19a9a4)。因此 console.log(world) 会被执行并在 stdout 打印 world——--quiet 测试中 assert.ok(p.stdout.includes(world))[test/cli.test.js#L97](https://link.gitcode.com/i/a2bcc986a875747d041aee115ca2d7fb)验证的正是这条路径。test/md.test.ts 也把这一行为命名为 “preserves tab-indented blocks after a blank line (legacy behavior)”。 ### 7. 未知语言围栏整体注释 markdown Other code blocks are ignored: css .ignore {} 对既不是 js 系也不是 bash 系的围栏此处是 css转换器把块内每行加上 // 前缀linePrefix // 围栏本身不产生任何输出。这与 [docs/markdown.md](https://link.gitcode.com/i/b4d1895487886ea6772e230afa098c19) “Other kinds are ignored” 的说明一致test/md.test.ts 中 ~~~\nunknown code block\n~~~ 的断言给出了同样的转换结果。 ## 底层原理transformMarkdown 的三态状态机 以上每一块的命运都由 [src/md.ts](https://link.gitcode.com/i/bd674b21e0a18d628702fef8a821f6e4) 中 transformMarkdown() 的一个逐行状态机决定状态只有三种 - **root**处理围栏外的内容。每行匹配 fenceRe命中 js 组则把后续行原样输出命中 bash 组则用 await $ 包裹其余组全部注释化未命中围栏时行被加 // 前缀空行变 // 。 - **tab**root 状态下若“前一行为空”且当前行满足 ^( |\t)进入该状态并原样保留行内容遇到空行保留空行遇到非缩进行则注释化并回到 root。 - **fence**围栏内部。用 endRe与开围栏同字符、长度不小于开围栏、前缀最多 3 个空格判断结束行内部行按 stripRe 去除与开围栏相同的缩进后输出。CommonMark 允许围栏缩进 3 个空格test/md.test.ts 中 “accepts fences indented up to 3 spaces” 的测试用三级列表嵌套围栏验证了这一点。 另外两个工程细节值得注意 - **换行符处理**文件按 /\r\n|[\n\r\u2028\u2029]/ 切分[src/md.ts#L35](https://link.gitcode.com/i/bd674b21e0a18d628702fef8a821f6e4#L35)覆盖 CRLF、CR 以及 ES 的行分隔符对应夹具 [test/fixtures/markdown-crlf.md](https://link.gitcode.com/i/b91ff2e48c1b5baec3a0d913a93d5de6) 和 [test/cli.test.js#L347-L350](https://link.gitcode.com/i/b52eb128838d5e1c62669e965d685f0e) 的 CRLF 测试断言输出包含 Hello, world!以及 test/md.test.ts 中 “handles all ECMAScript line terminators” 的用例。 - **执行链路**zx script.md → readScript() 判定 .md 扩展名 → transformMarkdown() 转换 → 写入临时 .mjsgetFilepath 会先探测 name.ext、再 name-randomid.ext避免覆盖已有文件见 [src/cli.ts#L289-L298](https://link.gitcode.com/i/5d647a6721c5d3526b80ce7516315880#L289-L298)→ runScript() 中 injectGlobalRequire() 注入 __filename/__dirname/require 后 import() 该文件退出时清理临时文件[src/cli.ts#L143-L174](https://link.gitcode.com/i/5d647a6721c5d3526b80ce7516315880#L143-L174)。除了本地文件stdin配合 --ext .md和 HTTP URL 拉取的脚本同样会走这条 .md 转换路径[src/cli.ts#L204-L245](https://link.gitcode.com/i/5d647a6721c5d3526b80ce7516315880#L204-L245)。 ## 测试如何验证这份夹具 test/cli.test.js 中与该夹具相关的用例构成端到端验证 js // test/cli.test.js test(markdown scripts are working, async () { await $node build/cli.js test/fixtures/markdown.md }) test(markdown scripts are working for CRLF, async () { const p await $node build/cli.js test/fixtures/markdown-crlf.md assert.ok(p.stdout.includes(Hello, world!)) }) test(markdown scripts from stdin with --ext .md, async () { const md # Test\n\njs\necho(md-stdin-ok)\n\n const p await $node build/cli.js --ext.md ${md} assert.match(p.stdout, /md-stdin-ok/) })【免费下载链接】zxA tool for writing better scripts项目地址: https://gitcode.com/GitHub_Trending/zx/zx创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考