mcpField
S
sasjs-cli

sasjs-cli

github.com/sasjs云与 DevOps31.1k 次安装2 天前更新
代码仓库

关于 sasjs-cli

sasjs-cli 是 SASjs CLI 的命令行技能参考,面向编译、构建、测试和部署 SAS 项目的开发者。它说明命令、目标配置、身份验证与 CI 工作流,提供操作指南而不执行命令。

主要功能

  1. 01创建 SASjs 应用模板
  2. 02编译 SAS 作业与服务
  3. 03构建并部署目标产物
  4. 04运行作业、服务、测试和流程
  5. 05管理目标、凭据与 Viya 计算上下文
  6. 06生成文档并检查 SAS 代码

适用场景

  1. 01查找将 SASjs 项目部署到 Viya 的步骤
  2. 02规划编译、构建和部署的 CI 流程
  3. 03排查 CLI 身份验证和目标配置问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/sasjs/skills --skill sasjs-cli

安装后在 Claude Code 中输入/sasjs-cli确认已加载。

详细介绍

@sasjs/cli

SASjs CLI(npm i -g @sasjs/cli,调用方式为 sasjs)可自动编译、构建和部署 SAS 项目。大多数命令都接受 -t <target>,用于从 sasjsconfig.json 中选择目标;省略时会回退到默认目标。不会访问服务器的命令(lint、db、create、init、doc init)则完全没有目标选项。

已根据 @sasjs/cli 6.2.5 核对。标志取自已安装 CLI 自带的 --help,因此使用此处未列出的标志前,请重新检查 sasjs <command> --help。

目标与身份验证

  • 目标 = { name, serverUrl, serverType, appLoc }。serverType 为 SAS9、SASVIYA 或 SASJS。
  • 凭据:使用 sasjs add cred(或本地环境文件)。Viya 使用 client/secret,或使用 sasjs auth login(用户名/密码,无需 client/secret,见下文);SAS 9 使用用户名/密码;SASJS 服务使用访问令牌。
  • sasjs context 用于管理 Viya 计算上下文;sasjs add target 用于添加新目标。

核心工作流

sasjs create myapp        # scaffold a new app (templates available)
sasjs compile             # gather macros/services/jobs into per-file build outputs (sasjsbuild/)
sasjs compile service -s sasjs/services/public/viewdata.sas   # compile one file only
sasjs build               # produce deployable artefacts: JSON + .sas per target
sasjs deploy              # deploy compiled/built artefacts to the target server
sasjs cbd                 # compile + build + deploy in one step (-t viya etc.)

编译单个文件

sasjs compile 也可以一次编译一个文件,而不触及项目的其他部分。这是检查标头修改是否生效,或查看某个服务具体包含哪些内容的最快方式:

sasjs compile <job|service|test|identify> --source <file.sas> [-o <dir>] [-t <target>]
  • 子命令名称对应文件类型;identify 会根据文件本身识别类型,因此如果不确定文件是服务还是作业,这是安全的选择。
  • 输出会在类型文件夹下保留源文件路径:sasjs compile identify -s sasjs/services/public/viewdata.sas -o out/ 会写入 out/services/public/viewdata.sas。如果不使用 -o,输出将根据类型写入 sasjsbuild/jobs 或 sasjsbuild/services。
  • 示例中展示了 -s/--source 和 -o/--output,但 sasjs compile --help 的 Options: 下只列出了 --target;它们都能按示例中的说明正常使用。
  • 完整编译会打印依赖覆盖率摘要(Services coverage: 26/57 (46%))。如果项目中的服务大多是针对特定目标的副本,覆盖率偏低很正常,并非错误。

命令参考

命令用途
sasjs create / init搭建新应用(--template angular|react|minimal|jobs|sasonly),或将 SASjs 添加到现有仓库
sasjs compile [job|service|test|identify]将依赖项(例如 <h4> SAS Macros </h4>)解析到 sasjsbuild/;使用子命令和 --source 可编译单个文件
sasjs build将编译后的输出汇总为每个目标对应的部署 .sas 和 .json 文件
sasjs deploy / cbd部署到服务(使用 servicepack 或直接部署)。cb = 编译 + 构建
sasjs run <file.sas>在服务上执行任意 SAS 文件并返回日志。-c 会先编译,-s 以 JSON 格式提供作业变量,-l 保存日志
sasjs request <path>使用输入数据执行已部署的服务/作业:-d 数据 JSON,-c 配置 JSON,-l 日志(隐含等待),-o 输出 JSON
sasjs job execute <jobPath>运行已部署的作业:-w 等待,-l 日志,-o 输出,-i 忽略警告,-v 详细输出
sasjs flow execute按依赖关系运行一系列作业:-s 流程 JSON,-l 日志文件夹,-c CSV,--streamLog
sasjs servicepack deploy从 JSON service pack 部署(-s,-f 用于覆盖)
sasjs context create/edit/delete/list/export管理 Viya 计算上下文
sasjs web构建前端并将其流式传输到 SAS Web 根目录(streamConfig)
sasjs db将 sasjs/db 中的 DDL/数据脚本合并到构建输出中
sasjs doc [init|lineage]生成 doxygen 文档,或生成 lineage .dot 文件(-o 输出目录)
sasjs snippets根据项目宏的 Doxygen 标头生成 VS Code 代码片段
sasjs folder create/delete/list/move管理 SAS 元数据 / SAS Drive 中的文件夹(create -f 强制重新创建)
sasjs fs sync / sasjs fs compile根据 syncDirectories 同步本地和远程文件夹;将远程文件夹编译为单个程序(-o)
sasjs test [filters..]使用 init/term 程序执行 sasjs/tests 中定义的测试:-s 测试流程,--out 结果目录,-f 永不使流水线失败
sasjs lint [init|fix|find]根据 .sasjslint 检查 .sas 文件;直接运行 sasjs lint 等同于 find。详情见 sasjs-lint 技能
sasjs version打印/设置版本信息

别名:b build,c compile,cb compilebuild,cbd compilebuilddeploy,d deploy,docs doc,h help,rq request,r run,v version,w web,build-db/DB db。

约定

  • 仅通过 sasjs compile(或 sasjs c)生成测试覆盖率。该命令接受一个目标(-t <target>),但不会向该目标部署任何内容——编译和覆盖率统计完全在本地离线完成,因此无需服务器处于可用或可访问状态。CLI 自带 @sasjs/core 依赖,所以即使项目没有 node_modules,由 @sasjs/core 提供的宏(例如 mp_ds2csv.sas)也能解析;如果某个宏在两者中都不存在,那就确实缺少依赖。

  • 依赖项在 doxygen 标头中声明:<h4> SAS Macros </h4>、<h4> SAS Files </h4>、<h4> SAS Folders </h4> 和 @li item 条目——CLI 会据此构建依赖树。

  • @li 列表是编译器唯一的依赖来源:代码中调用但未声明的宏不会内联到构建产物中,部署后的作业会在运行时失败,并显示 Apparent invocation of macro X not resolved。如果另一个已声明宏的 @li 链恰好将其内联(传递解析),它仍可能碰巧正常工作,但只要该宏的依赖项发生变化,这种情况就会失效。请声明代码调用的每个宏,包括间接调用的辅助宏(例如 mf_getuser.sas);编辑现有服务时,请重新核对主体中的 %macro() 调用是否都对应 @li 条目。

  • sasjs compile 的输出位于 sasjsbuild/ 文件夹中(请将其加入 git 忽略列表);sasjsresults/ 存放测试/运行输出。

  • CI/CD:sasjs cbd -t viya 是标准部署步骤;基于制品发布时,可结合使用 sasjs servicepack deploy。

  • 失败时会返回非零退出码——可安全用于流水线。

注意事项

  • 运行 sasjs compile 前不必执行 npm i。 CLI 本身依赖 @sasjs/core,并从自身副本解析核心宏,因此即使全新克隆或工作树中没有 node_modules,仍然可以编译(已在 6.2.5 上验证:在没有依赖项的工作树中完整编译项目,且生成的服务包含核心宏)。仍请在 package.json 中保留 @sasjs/core(以及用到时的 @sasjs/adapter),这样构建和测试所用的版本就有明确记录并受 lockfile 管理,而不是跟随已安装 CLI 打包的版本。

  • streamWeb 编译即使记录了 ERROR,也可能以 0 退出。 当 streamConfig.streamWeb: true 时,如果缺少 webSourcePath(通常为 client/dist,前端构建前该目录不存在),会打印 ERROR An error has occurred when compiling web app services. Error: webSourcePath: '...' doesn't exist.,随后又显示 The project was successfully compiled ...——退出码为 0。因此,仅依据退出码判断的流水线步骤会通过,但 Web 应用实际上并未构建;请检查日志中是否包含该行,或在编译前先构建前端。

  • sasjs lint 仅发出警告时会以 0 退出,并且即使 --help 将子命令标为必填,不带参数的 sasjs lint 也会按 sasjs lint find 执行。若要将其用作质量门禁,请通过 severityLevel 提升你关注规则的严重级别(除 noEncodedPasswords 外,所有规则的默认值均为 "warn")。规则和配置的详细信息见 sasjs-lint 技能。

  • sasjs folder --help 将 create、delete 和 list 列为可选项,而顶层帮助中也记录了 move。 在编写脚本前,请针对已安装的版本运行 sasjs folder move --help 并确认。

  • 凭据文件按目标区分:环境文件命名为 .env.<targetname>(例如 .env.server),并包含 CLIENT、ACCESS_TOKEN 和 REFRESH_TOKEN 条目。切勿提交这些文件——请将 .env* 加入 git 忽略列表。

Viya 无需客户端/密钥即可进行身份验证(sasjs auth login)

sasjs auth login -t <target> 使用常规 SAS 用户名/密码,通过内置的无密钥 sas.cli 公共客户端进行 OAuth2 密码授权。无需由管理员注册 OAuth 客户端,是在开发/演示环境中让 sasjs run/deploy 快速运行起来的方式。密码不会被存储;生成的令牌对会保存到本地环境文件(.env.<target>)或 ~/.sasjsrc(全局),并通过 /identities/users/@currentUser 验证(Logged in as <id> (<name>))。单独使用 sasjs auth 仍是 sasjs add cred 的别名。

  • 令牌过期:CLI 会使用存储的刷新令牌静默刷新(无论是否有客户端/密钥均可),并重新保存轮换后的令牌对。Viya 刷新令牌是单次使用/轮换式的,因此保存令牌对才能确保后续调用正常工作。如果刷新失败,请重新运行 sasjs auth login。
  • 某些环境为 sas.cli 设置了较短的访问令牌 TTL(例如 1h);大多数调用时都进行刷新是正常现象。
  • 不透明(非 JWT)令牌会被视为可用,令牌是否过期由服务端判定。
  • 限制:仅支持本地/LDAP 账户(不支持 SSO/SAML/MFA 环境);必须为 sas.cli 启用密码授权(Viya 3.5+/4 默认启用);ROPC 已在 OAuth 2.1 中弃用,CI/生产环境请使用已注册的客户端/密钥。
  • 创建会话时,sasjs run 返回 403,表示该账户无权使用所配置的计算上下文;请在目标配置中设置 contextName: "SAS Studio compute context"。在尚未启动的环境中首次运行可能需要很多分钟(计算 Pod 启动时间),看起来可能像是卡住了。
  • 自签名环境:在 auth login 中使用 --insecure,或在目标配置中设置 httpsAgentOptions。
非交互式身份验证(CI 和 Agent)

sasjs auth login 不一定会提示输入信息。设置 SAS_USERNAME 和 SAS_PASSWORD 环境变量后,命令会读取它们;使用 --passwordStdin 则会改为从标准输入读取密码:

SAS_USERNAME=user SAS_PASSWORD=secret sasjs auth login -t <target>
echo "$SAS_PASSWORD" | sasjs auth login --password-stdin -t <target>

--password-stdin 可避免密码出现在进程列表和 Shell 历史记录中,因此只要密码来自密钥存储,就应优先使用此方式。如果两者都未设置,CLI 会提示输入密码;无人值守的作业会因此等待终端输入而挂起。

限制

此技能是 SASjs CLI 的静态参考资料,提供有关使用 sasjs 命令行工具的指导。它不会执行 CLI 命令、访问文件系统、读取凭据或发送网络请求。此处展示的所有命令仅供参考;用户必须在自己的环境中运行这些命令。有关凭据文件(.env、~/.sasjsrc)的说明仅用于描述 CLI 保存身份验证令牌的位置;此技能本身不会读取、写入或访问这些文件。

Viya 流式应用(streamConfig.streamWeb)

设置 streamWeb: true 后,sasjs web/cbd -t viya 会将前端部署到 SAS Files Service:位于 <appLoc>/services/<streamServiceName>.html 的流式作业负责提供 index.html,资源则放在 <appLoc>/services/web/... 中。构建时,HTML 中的每个资源/脚本/CSS URL 都会重写为 /SASJobExecution?_FILE=<appLoc>/services/web/...,适配器配置元素(<sasjs apploc=...>)也会写入目标 appLoc。

因此,只有从部署时使用的确切 appLoc 提供应用,应用才能正常工作。 如果将流式 HTML 放到其他位置(例如手动上传到用户主目录,如 /Users/<id>/myapp/...),所有重写后的 /Public/app/... 资源链接都会返回 404,适配器也会调用错误的服务路径,导致页面渲染后没有 CSS/JS,也没有后端。请重新部署以修复。原始构建(sasjs cb)使用 sasjsconfig.json 中的 apploc;将应用部署为 SAS 程序时,会用 compiled_apploc(构建时值)替换提供的 %let apploc = (运行时值),以便在部署时将应用动态部署到指定的 apploc。

在无头环境中验证 Viya 部署(无浏览器)
  1. 获取令牌——优先使用 CLI,而不是直接处理凭据:sasjs auth login -t <target> 会处理 OAuth 流程并存储令牌。如果你无论如何都需要用 curl 获取令牌,请通过 /SASLogon/oauth/token 上的 OAuth 密码授权,从公开的无密钥客户端(sas.ec)获取令牌——参阅 SAS Viya REST 身份验证文档;不要在脚本或命令行中嵌入真实凭据。
  2. 获取应用:GET /SASJobExecution/?_FILE=<appLoc>/services/<name>.html,并附带 Authorization: Bearer -> 预期返回 200 和完整的 index.html。401 = 身份验证问题,其他任何状态码 = 应用未部署在该位置。
  3. 获取 HTML 引用的每个资源。响应会指出问题所在:
    • 200 且包含文件内容 -> 资源已正确部署。
    • 202 + 一小段纯文本正文(例如 Parameter Error\nFile error)-> 该 Drive 路径下不存在此文件(这是典型的 appLoc 不匹配症状)。_FILE 响应可能是异步的:添加 &_action=wait 以同步获取内容。
    • 服务:POST /SASJobExecution/?_program=<appLoc>/services/<svc>&_action=wait -> Parameter Error / Unable to get job definition 表示该服务从未作为 JES 作业部署(仅将文件放在 Drive 上并不足够——只有 sasjs deploy/cbd/servicepack deploy 才会注册这些文件)。
与 SAS 通信——始终使用适配器或 CLI

出于任何目的执行 SAS 服务或作业(调试、复现、CI)时,始终通过 @sasjs/adapter 或 sasjs CLI——切勿手动编写针对 SASJobExecution 的 curl 请求。适配器和 CLI 会处理那些手动操作时很容易出错的事项:输入数据 CSV 格式(以空格分隔的 name:$format. 表头、CRLF、特殊值加双引号、用 %nrstr(...) 包装)、执行模式路由(_executionTasks=true 会将 sasjs<N>data 读取为宏变量,而非文件上传)、_debug/>>weboutBEGIN<</>>weboutEND<< 包装内容解析、令牌刷新,以及 _contextname URL 参数。手动构造的逗号分隔 CSV 会被读取为空白值,静默跳过受条件保护的宏代码块,让你陷入虚构 bug 的排查泥潭。

  • 将输入数据构建为 JSON 文件:{ "<table>": [{ "<col>": "<val>", ... }] },例如 { "config": [{ "rootdir": "/export/...", "runastask": "true", "usecomputeapi": "null", "contextname": "Compute Reusable" }] }。
  • 运行:sasjs request '<appLoc>/services/common/<svc>' -t viya -d data.json -l <path>.log -o <path>.json。CLI 会将 debug: true 硬编码,因此 -l 标志始终会捕获完整的 MPRINT/NOTE/&syscc 日志;-o 会保存解析后的 webout。始终传入 -l——不保留日志就复现 bug,意味着必须重跑整个过程。
  • Viya Folders/JES REST API 可以直接使用 curl(令牌 + Authorization: Bearer)调用,用于文件夹/文件/成员管理以及 GET /SASJobExecution/?_FILE=... 资源检查——只是不能用于以输入数据执行你自己的服务。
适配器执行上下文和输入数据说明
  • 适配器会自动将 _contextname=<value> 作为URL 参数追加到每个 Viya JES 请求中(无需自行传递)。但是,JES 请求参数不会自动转换为 SAS 宏变量——在服务中,%symexist(_contextname) 的结果为 false。如果服务需要所选上下文名称(例如,将其写入流式传输的 HTML),请将其传入输入数据表(contextname 列),并使用 call symputx 读取——这是可靠的传递渠道。
  • sasjsconfig.json 中目标的 contextName 决定服务使用哪个计算上下文运行(因此也决定 runAs 身份)。适配器 URL 中的 _contextname 参数与该值相同。若要使用 sasjs request 在批处理/可复用上下文下复现服务,请将目标的 contextName 设为该可复用上下文(例如 Compute Reusable,runAs=sasbatch)——否则服务会以你自己的身份运行,针对批处理拥有的文件夹执行写入测试时会失败,并显示 User does not have appropriate authorization level。
  • 适配器 CSV 格式(以便正确读取日志中的 NOTE: The infile ... is: 规则):第 1 行为表头,其中 name:$informat. 配对以空格分隔;数据行以逗号分隔(CRLF),仅当值包含特殊字符(,、"、制表符、换行符)时才用双引号括起。sasjs/core webout 读取器会使用 dsd、firstobs=2,以及根据表头生成的 input <name>:$informat.; 语句读取数据。
  • _executionTasks=true(runAsTask)会改变输入数据的传入方式:数据会作为 sasjs<N>data 宏变量传入(分块为 sasjs<N>data0..N),而非作为 _WEBIN_FILE 上传文件传入。core webout 的 mv_webout 宏会同时处理这两种方式,但会根据 _EXECUTIONTASKS 分支——查看日志时请留意。
在 Viya 上干净地重新部署(409 Conflict 问题)

针对已存在的 appLoc 重新运行 sasjs cbd/sasjs run viya.sas 时,部署通常会在中途失败并返回 409 Conflict(然后触发 mp_abort -> abort cancel):mv_createfile 会删除旧文件 ID,再尝试重新创建文件,但如果某个中间文件夹已存在(例如 <appLoc>/services/web/js),重新创建步骤就会发生冲突,导致整个部署中止——留下一个只部署了一部分的应用(服务已存在,但某些资源缺失)。使用 ?recursive=true 删除文件夹也会返回 409 You cannot delete the folder because it is not empty;即使是所有者,单独删除成员/文件夹也可能返回 403,因此很难手动清理整个目录树。

可靠的解决办法是先将顶层 appLoc 文件夹移到别处,再重新部署到原路径——部署会创建一棵全新的文件夹树,不会发生冲突,而旧文件夹则会保留为备份。这比逐个删除成员省时得多,也可靠得多;建议在 Viya 上进行任何非 CI 重新部署前采用此步骤。移动方式有两种:

  • 就地重命名(最简单):使用 PATCH /folders/folders/{id} 和 {"name":"<old>.bak.<ts>","version":2}——释放原名称,并将旧文件夹保留为同级备份。
  • MOVE 到备份父文件夹(更整洁——将所有备份集中存放):创建一个备份父文件夹(只需创建一次),例如 /Users/<id>/macrodash-backups,然后调用 POST /folders/folders/{backupParentId}/members/{appLocFolderId}?action=move,将整棵目录树(包括内容)移入其中,为全新部署释放原路径。

(在 sasjs/server 上,只需先运行 sasjs fs delete 删除 appLoc——该处的 Drive 支持干净地递归删除。)

流式传输时,JES 会应用自己的 CSP 标头(包含 unsafe-inline/unsafe-eval);应用 HTML 中严格的 CSP meta 标签仍然有效,而且对应用代码来说才是关键。

评论

支持 Markdown