sasjs-cli

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

@sasjs/cli

@sasjs/cli

The SASjs CLI (
npm i -g @sasjs/cli
, invoked as
sasjs
) automates compiling, building, and deploying SAS projects. All commands support
-t <target>
to select a target from
sasjsconfig.json
.
SASjs CLI(
npm i -g @sasjs/cli
,调用方式为
sasjs
)可自动执行 SAS 项目的编译、构建和部署。所有命令都支持
-t <target>
,用于从
sasjsconfig.json
中选择 target。

Targets and auth

Target 与身份验证

  • A target =
    { name, serverUrl, serverType, appLoc }
    .
    serverType
    is one of
    SAS9
    ,
    SASVIYA
    ,
    SASJS
    .
  • Credentials:
    sasjs add cred
    (or a local env file). Viya uses client/secret or
    sasjs auth login
    (user/pass, no client/secret needed - see below); SAS 9 uses user/pass; SASJS server uses an access token.
  • sasjs context
    manages Viya compute contexts;
    sasjs add target
    adds a new target.
  • 一个 target =
    { name, serverUrl, serverType, appLoc }
    。
    serverType
    的可选值为
    SAS9
    、
    SASVIYA
    、
    SASJS
    。
  • 凭据:
    sasjs add cred
    (或使用本地 env 文件)。Viya 使用 client/secret,或者使用
    sasjs auth login
    (user/pass,无需 client/secret,详见下文);SAS 9 使用 user/pass;SASJS server 使用 access token。
  • sasjs context
    用于管理 Viya compute context;
    sasjs add target
    用于添加新 target。

Core workflow

核心工作流

sasjs create myapp        # scaffold a new app (templates available)
sasjs compile             # gather macros/services/jobs into per-file build outputs (sasjsbuild/)
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 create myapp        # scaffold a new app (templates available)
sasjs compile             # gather macros/services/jobs into per-file build outputs (sasjsbuild/)
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.)

Command reference

命令参考

CommandPurpose
sasjs create / init
Scaffold new app or add SASjs to existing repo
sasjs compile
Resolve dependencies (
<h4> SAS Macros </h4>
etc.) into
sasjsbuild/
sasjs build
Create build JSON / service pack per target
sasjs deploy
/
cbd
Deploy to server (servicepack or direct)
sasjs run <file.sas>
Execute an arbitrary SAS file on the server, return log
sasjs request <path>
Execute a deployed service/job with input data (
-d
)
sasjs job execute
Run a deployed job
sasjs flow execute
Run a sequence of jobs with dependencies (CSV-defined flows)
sasjs servicepack deploy
Deploy from a JSON service pack
sasjs web
Build the frontend and stream it into the SAS web root (streamConfig)
sasjs db
Build database DDL/data load scripts from
sasjs/db
sasjs doc
Generate doxygen documentation + lineage
sasjs folder create/delete/move
Manage folders in SAS metadata / SAS Drive
sasjs fs
File-system operations against the server (sync folders)
sasjs test
Execute tests defined in
sasjs/tests
with init/term programs
sasjs lint
Lint
.sas
files per
.sasjslint
config
sasjs version
Print/set version info
命令用途
sasjs create / init
为新应用搭建脚手架,或将 SASjs 添加到现有 repo
sasjs compile
将依赖项(
<h4> SAS Macros </h4>
等)解析到
sasjsbuild/
sasjs build
为每个 target 创建构建 JSON / service pack
sasjs deploy
/
cbd
部署到 server(通过 servicepack 或直接部署)
sasjs run <file.sas>
在 server 上执行任意 SAS 文件并返回日志
sasjs request <path>
使用输入数据(
-d
)执行已部署的 service/job
sasjs job execute
运行已部署的 job
sasjs flow execute
运行一系列具有依赖关系的 job(由 CSV 定义 flow)
sasjs servicepack deploy
从 JSON service pack 部署
sasjs web
构建 frontend,并将其流式传输至 SAS web root(streamConfig)
sasjs db
根据
sasjs/db
构建数据库 DDL / 数据加载脚本
sasjs doc
生成 doxygen 文档和 lineage
sasjs folder create/delete/move
管理 SAS metadata / SAS Drive 中的 folder
sasjs fs
对 server 执行文件系统操作(同步 folder)
sasjs test
执行
sasjs/tests
中定义的测试,并运行 init/term program
sasjs lint
根据
.sasjslint
配置检查
.sas
文件
sasjs version
打印或设置版本信息

Conventions

约定

  • Test coverage is generated only from a
    sasjs compile
    (or
    sasjs c
    ). It accepts a target (
    -t <target>
    ), but nothing is deployed to that target - compilation and coverage are fully local/offline, so no server needs to be available or reachable. Missing macro dependencies (e.g.
    mp_ds2csv.sas
    ) mean
    @sasjs/core
    isn't installed - run
    npm i
    first.
  • Dependencies are declared in doxygen headers:
    <h4> SAS Macros </h4>
    ,
    <h4> SAS Files </h4>
    ,
    <h4> SAS Folders </h4>
    , and
    @li item
    entries - the CLI builds the dependency tree from these.
  • The
    @li
    list is the compiler's only dependency source: a macro that is called in the code but not declared is not inlined into the build, and the deployed job fails at runtime with
    Apparent invocation of macro X not resolved
    . It can still work by accident when another declared macro's own
    @li
    chain happens to inline it (transitive resolution) - which breaks as soon as that macro's dependencies change. Declare every macro the code calls, indirect helpers (e.g.
    mf_getuser.sas
    ) included; when editing an existing service, re-audit the
    %macro()
    calls in the body against the
    @li
    entries.
  • sasjs compile
    output goes to the
    sasjsbuild/
    folder (git-ignore it);
    sasjsresults/
    holds test/run outputs.
  • CI/CD:
    sasjs cbd -t viya
    is the standard deploy step; combine with
    sasjs servicepack deploy
    for artefact-based releases.
  • Exit codes are non-zero on failure - safe for pipelines.
  • 测试覆盖率只能通过
    sasjs compile
    (或
    sasjs c
    )生成。该命令接受 target(
    -t <target>
    ),但不会向该 target 部署任何内容。编译和覆盖率生成完全在本地离线完成,因此无需 server 可用或可访问。如果缺少 macro 依赖项(例如
    mp_ds2csv.sas
    ),说明尚未安装
    @sasjs/core
    ,请先运行
    npm i
    。
  • 依赖项在 doxygen header 中声明:
    <h4> SAS Macros </h4>
    、
    <h4> SAS Files </h4>
    、
    <h4> SAS Folders </h4>
    和
    @li item
    条目。CLI 根据这些声明构建依赖树。
  • @li
    列表是编译器唯一的依赖来源:如果代码中调用了某个 macro,但未对其声明,该 macro 就不会被内联到构建中,已部署的 job 在运行时会出现
    Apparent invocation of macro X not resolved
    错误。如果另一个已声明 macro 的
    @li
    链恰好将其传递性内联,它可能偶然正常工作,但一旦该 macro 的依赖项发生变化,这种方式就会失效。请声明代码调用的每一个 macro,包括间接 helper(例如
    mf_getuser.sas
    );编辑现有 service 时,应根据
    @li
    条目重新检查正文中的所有
    %macro()
    调用。
  • sasjs compile
    的输出位于
    sasjsbuild/
    folder(应将其加入 git ignore);
    sasjsresults/
    用于保存测试和运行输出。
  • CI/CD:
    sasjs cbd -t viya
    是标准部署步骤;可结合
    sasjs servicepack deploy
    执行基于构建产物的发布。
  • 失败时会返回非零 exit code,因此可安全用于 pipeline。

Gotchas

易错点

  • Run
    npm i
    before
    sasjs cb
    - macro dependency resolution needs
    node_modules/@sasjs/core
    present, and
    @sasjs/core
    (and
    @sasjs/adapter
    if used) must be listed in
    package.json
    .
  • Credentials files are per-target: env files named
    .env.<targetname>
    (e.g.
    .env.server
    ) with
    CLIENT
    ,
    ACCESS_TOKEN
    , and
    REFRESH_TOKEN
    entries. Never commit them - gitignore
    .env*
    .
  • 运行
    sasjs cb
    前请先执行
    npm i
    。Macro 依赖解析要求存在
    node_modules/@sasjs/core
    ,并且
    package.json
    中必须列出
    @sasjs/core
    (如使用
    @sasjs/adapter
    ,也必须列出)。
  • 凭据文件按 target 隔离:env 文件名为
    .env.<targetname>
    (例如
    .env.server
    ),其中包含
    CLIENT
    、
    ACCESS_TOKEN
    和
    REFRESH_TOKEN
    条目。切勿提交这些文件,应通过 gitignore 忽略
    .env*
    。

Viya auth without a client/secret (
sasjs auth login
)

无需 client/secret 的 Viya 身份验证(
sasjs auth login
)

sasjs auth login -t <target>
authenticates with a regular SAS username/password via the OAuth2 password grant against the built-in, secret-less
sas.cli
public client. No admin-registered OAuth client is needed - the fastest way to get
sasjs run
/
deploy
working on dev/demo estates. The password is never stored; the minted token pair is persisted to a local env file (
.env.<target>
) or
~/.sasjsrc
(global) and verified via
/identities/users/@currentUser
(
Logged in as <id> (<name>)
). Bare
sasjs auth
is still an alias for
sasjs add cred
.
  • Token expiry: the CLI silently refreshes via the stored refresh token (works with and without a client/secret), and re-persists the rotated pair - Viya refresh tokens are single-use/rotating, so this persistence is what keeps later invocations working. If refresh fails, re-run
    sasjs auth login
    .
  • Some estates give
    sas.cli
    a short access-token TTL (e.g. 1h); a refresh on most invocations is normal.
  • Opaque (non-JWT) tokens are treated as usable - the server is the authority on expiry.
  • Limitations: local/LDAP accounts only (no SSO/SAML/MFA estates); password grant must be enabled for
    sas.cli
    (default on Viya 3.5+/4); ROPC is deprecated in OAuth 2.1 - use a registered client/secret for CI/production.
  • sasjs run
    403 on session creation = the account isn't authorised for the configured compute context - set
    contextName: "SAS Studio compute context"
    on the target. First run on a cold estate can take many minutes (compute pod spin-up) and may appear to hang.
  • Self-signed estates: use
    --insecure
    on
    auth login
    , or configure
    httpsAgentOptions
    on the target.
sasjs auth login -t <target>
使用常规 SAS username/password,通过 OAuth2 password grant 对内置、无 secret 的
sas.cli
public client 进行身份验证。无需注册由管理员管理的 OAuth client,这是让
sasjs run
/
deploy
在开发或演示环境中快速运行的最便捷方式。密码不会被存储;生成的 token 对会持久化到本地 env 文件(
.env.<target>
)或
~/.sasjsrc
(全局配置),并通过
/identities/users/@currentUser
验证(
Logged in as <id> (<name>)
)。单独使用
sasjs auth
仍然是
sasjs add cred
的别名。
  • Token 过期:CLI 会通过已存储的 refresh token 静默刷新(有或没有 client/secret 均可),并重新持久化轮换后的 token 对。Viya refresh token 是单次使用且会轮换的,因此持久化机制是后续调用持续可用的关键。如果刷新失败,请重新运行
    sasjs auth login
    。
  • 某些环境会为
    sas.cli
    设置较短的 access token TTL(例如 1 小时),因此大多数调用都刷新 token 属于正常现象。
  • Opaque(非 JWT)token 会被视为可用,是否过期由 server 判定。
  • 限制:仅支持 local/LDAP account(不支持 SSO/SAML/MFA 环境);必须为
    sas.cli
    启用 password grant(Viya 3.5+/4 默认启用);ROPC 在 OAuth 2.1 中已被弃用,CI/生产环境应使用已注册的 client/secret。
  • sasjs run
    在创建 session 时返回 403,表示该 account 未获授权使用配置的 compute context。请在 target 上设置
    contextName: "SAS Studio compute context"
    。首次在冷启动环境中运行时,compute pod 启动可能需要数分钟,看起来像是卡住。
  • 对于自签名证书环境,请在
    auth login
    时使用
    --insecure
    ,或在 target 上配置
    httpsAgentOptions
    。

Limitations

限制

This skill is a static reference for the SASjs CLI - it provides guidance on using the
sasjs
command-line tool. It does not execute CLI commands, access the filesystem, read credentials, or make network requests. All commands shown are illustrative; the user must run them in their own environment. References to credential files (
.env
,
~/.sasjsrc
) describe where the CLI stores authentication tokens - this skill does not read, write, or access those files itself.
此 skill 是 SASjs CLI 的静态参考,用于指导如何使用
sasjs
命令行工具。它不会执行 CLI 命令、访问文件系统、读取凭据或发起网络请求。展示的所有命令仅供示例,用户必须在自己的环境中运行。文中提到的凭据文件(
.env
、
~/.sasjsrc
)仅用于说明 CLI 存储 authentication token 的位置;此 skill 本身不会读取、写入或访问这些文件。

Viya streaming apps (streamConfig.streamWeb)

Viya streaming app(streamConfig.streamWeb)

With
streamWeb: true
,
sasjs web
/
cbd -t viya
deploys the frontend into SAS Files Service: a streaming job at
<appLoc>/services/<streamServiceName>.html
serves
index.html
, and assets land in
<appLoc>/services/web/...
. At build time every asset/script/css URL in the HTML is rewritten to
/SASJobExecution?_FILE=<appLoc>/services/web/...
and the adapter config element (
<sasjs apploc=...>
) is stamped with the target
appLoc
.
Consequence: the app only works when served from the exact
appLoc
it was deployed against.
If the streaming HTML is placed anywhere else (e.g. manually uploaded to a user home folder like
/Users/<id>/myapp/...
), all rewritten
/Public/app/...
asset links 404 and the adapter calls the wrong service paths - the page renders with no CSS/JS and no backend. Fix by redeploying. The original build (
sasjs cb
) has the apploc from the sasjsconfig.json - when the app is deployed as a SAS program, the supplied
%let apploc = 
(runtime value) is swapped with the
compiled_apploc
(build time value) to allow apps to be dynamically deployed to a given apploc at deploy time.
当
streamWeb: true
时,
sasjs web
/
cbd -t viya
会将 frontend 部署到 SAS Files Service:
<appLoc>/services/<streamServiceName>.html
处的 streaming job 会提供
index.html
,资源文件则位于
<appLoc>/services/web/...
。构建时,HTML 中每个资源、script 和 CSS URL 都会被重写为
/SASJobExecution?_FILE=<appLoc>/services/web/...
,adapter 配置元素(
<sasjs apploc=...>
)也会写入 target 的
appLoc
。
**后果:应用仅能在部署时所使用的准确
appLoc
下运行。**如果将 streaming HTML 放到其他位置(例如手动上传到
/Users/<id>/myapp/...
等用户 home folder),所有重写后的
/Public/app/...
资源链接都会返回 404,adapter 也会调用错误的 service path,最终页面将无法呈现 CSS/JS,也无法访问 backend。解决方法是重新部署。原始构建(
sasjs cb
)中的 apploc 来自
sasjsconfig.json
。当应用以 SAS program 形式部署时,传入的
%let apploc = 
(运行时值)会与
compiled_apploc
(构建时值)交换,从而允许应用在部署时动态部署到指定 apploc。

Verifying a Viya deployment headlessly (no browser)

在无浏览器情况下验证 Viya 部署

  1. Get a token - prefer the CLI rather than handling credentials directly:
    sasjs auth login -t <target>
    takes care of the OAuth flow and token storage. If you need the token for curl anyway, mint it from the public secret-less client (
    sas.ec
    ) via the OAuth password grant on
    /SASLogon/oauth/token
    - see the SAS Viya REST auth docs; do not embed real credentials in scripts or command lines.
  2. Fetch the app:
    GET /SASJobExecution/?_FILE=<appLoc>/services/<name>.html
    with
    Authorization: Bearer
    -> expect
    200
    and the full
    index.html
    .
    401
    = auth, anything else = not deployed there.
  3. Fetch each asset the HTML references. Response tells you what's wrong:
    • 200
      with file content -> asset deployed correctly.
    • 202
      + a tiny plain-text body (e.g.
      Parameter Error\nFile error
      ) -> file does not exist at that Drive path (classic appLoc-mismatch symptom). Note
      _FILE
      responses can be async: add
      &_action=wait
      to get content synchronously.
    • Services:
      POST /SASJobExecution/?_program=<appLoc>/services/<svc>&_action=wait
      ->
      Parameter Error / Unable to get job definition
      means the service was never deployed as a JES job (files on Drive alone are not enough - only
      sasjs deploy
      /
      cbd
      /
      servicepack deploy
      registers them).
  1. 获取 token:优先使用 CLI,而不是直接处理凭据。
    sasjs auth login -t <target>
    会处理 OAuth flow 和 token 存储。如果确实需要将 token 用于 curl,可通过无 secret 的 public client(
    sas.ec
    ),在
    /SASLogon/oauth/token
    上使用 OAuth password grant 生成 token,详见 SAS Viya REST auth 文档;不要在 script 或命令行中嵌入真实凭据。
  2. 获取应用:发送
    GET /SASJobExecution/?_FILE=<appLoc>/services/<name>.html
    ,并带上
    Authorization: Bearer
    ,预期结果是
    200
    和完整的
    index.html
    。
    401
    表示身份验证问题,其他结果则表示应用未部署到该位置。
  3. 获取 HTML 引用的每个资源。响应可帮助判断问题所在:
    • 200
      且包含文件内容:资源已正确部署。
    • 202
      且响应体是很小的纯文本(例如
      Parameter Error\nFile error
      ):说明该 Drive path 下不存在文件,这是典型的 appLoc 不匹配症状。注意,
      _FILE
      响应可能是异步的;添加
      &_action=wait
      可同步获取内容。
    • Service:发送
      POST /SASJobExecution/?_program=<appLoc>/services/<svc>&_action=wait
      。如果出现
      Parameter Error / Unable to get job definition
      ,说明该 service 从未作为 JES job 部署。仅将文件放在 Drive 上并不足够,只有
      sasjs deploy
      /
      cbd
      /
      servicepack deploy
      才会注册它们。

Talking to SAS - ALWAYS use the adapter or the CLI

与 SAS 通信——始终使用 adapter 或 CLI

When executing a SAS service or job for any purpose (debugging, reproduction, CI), always go through the
@sasjs/adapter
or
sasjs
CLI
- never hand-roll curl against
SASJobExecution
. The adapter and CLI handle the things that are trivial to get wrong by hand: the input-data CSV format (space-separated
name:$format.
headers, CRLF, double-quoted special values,
%nrstr(...)
wrapping), the execution-mode routing (
_executionTasks=true
reads
sasjs<N>data
as macro variables, not file uploads), the
_debug
/
>>weboutBEGIN<<
/
>>weboutEND<<
wrapper parsing, the token refresh, and the
_contextname
URL param. A hand-built comma-separated CSV will read as blanks, silently skip guarded macro blocks, and send you down a rabbit hole of phantom bugs.
  • Build input data as a JSON file:
    { "<table>": [{ "<col>": "<val>", ... }] }
    , e.g.
    { "config": [{ "rootdir": "/export/...", "runastask": "true", "usecomputeapi": "null", "contextname": "Compute Reusable" }] }
    .
  • Run:
    sasjs request '<appLoc>/services/common/<svc>' -t viya -d data.json -l <path>.log -o <path>.json
    . The CLI hardcodes
    debug: true
    , so the
    -l
    flag always captures the full MPRINT/NOTE/
    &syscc
    log;
    -o
    saves the parsed webout. Always pass
    -l
    - reproducing a bug without the log means re-running the whole thing.
  • The Viya Folders/JES REST APIs ARE fine to hit directly with curl (token +
    Authorization: Bearer
    ) for folder/file/member management and for
    GET /SASJobExecution/?_FILE=...
    asset checks - just not for executing your own services with input data.
出于任何目的执行 SAS service 或 job(调试、复现、CI 等)时,始终应通过
@sasjs/adapter
或
sasjs
CLI
,绝不要自行拼接针对
SASJobExecution
的 curl 请求。Adapter 和 CLI 会处理手工操作时容易出错的细节:输入数据 CSV 格式(以空格分隔的
name:$format.
header、CRLF、特殊值使用双引号、
%nrstr(...)
包装)、execution mode 路由(
_executionTasks=true
会将
sasjs<N>data
读取为 macro variable,而不是文件上传)、
_debug
/
>>weboutBEGIN<<
/
>>weboutEND<<
wrapper 解析、token 刷新以及
_contextname
URL 参数。手工构建的逗号分隔 CSV 会被读取为空值,静默跳过受保护的 macro block,并让你陷入排查虚假 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
    ,否则复现问题时缺少日志,就只能重新运行整个流程。
  • 可以直接使用 curl 调用 Viya Folders/JES REST API(token 加
    Authorization: Bearer
    )来管理 folder、file 和 member,也可通过
    GET /SASJobExecution/?_FILE=...
    检查资源;但不要使用这种方式执行带有输入数据的自有 service。

Adapter execution-context and input-data facts

Adapter 的 execution context 与输入数据要点

  • The adapter automatically appends
    _contextname=<value>
    as a URL parameter to every Viya JES request (you do NOT need to pass it yourself). BUT JES request params are NOT auto-promoted to SAS macro variables -
    %symexist(_contextname)
    is false inside the service. If the service needs the chosen context name (e.g. to stamp it into the streamed HTML), pass it in the input data table (a
    contextname
    column) and read it with
    call symputx
    - that is the reliable channel.
  • The target's
    contextName
    in
    sasjsconfig.json
    decides which compute context the service runs under (and thus the
    runAs
    identity). The adapter's URL
    _contextname
    param is the same value. To reproduce a service under a batch/reusable context via
    sasjs request
    , set the target's
    contextName
    to that reusable context (e.g.
    Compute Reusable
    , runAs=sasbatch) - otherwise it runs as your own identity and write-test steps to batch-owned folders fail with
    User does not have appropriate authorization level
    .
  • Adapter CSV format (so you can read the
    NOTE: The infile ... is:
    RULE in the log correctly): row 1 is the header with
    name:$informat.
    pairs space-separated; data rows are comma-separated (CRLF), with values double-quoted only if they contain a special char (
    ,
    ,
    "
    , tab, newline). The sasjs/core webout reader reads it back with
    dsd
    +
    firstobs=2
    + an
    input <name>:$informat.;
    statement derived from the header.
  • _executionTasks=true
    (runAsTask) changes how input data arrives: as
    sasjs<N>data
    macro variables (chunked into
    sasjs<N>data0..N
    ), NOT as
    _WEBIN_FILE
    uploads. The core webout
    mv_webout
    macro handles both, but it branches on
    _EXECUTIONTASKS
    - be aware when reading logs.
  • Adapter 会将
    _contextname=<value>
    作为 URL parameter 自动追加到每个 Viya JES 请求中(无需自行传入)。但是,JES 请求参数不会自动提升为 SAS macro variable,因此在 service 内部
    %symexist(_contextname)
    为 false。如果 service 需要所选 context name(例如将其写入 streaming HTML),请通过输入数据表传递
    contextname
    列,并使用
    call symputx
    读取;这是最可靠的传递方式。
  • sasjsconfig.json
    中 target 的
    contextName
    决定 service 所使用的 compute context(以及对应的
    runAs
    identity)。Adapter 的 URL
    _contextname
    参数使用相同值。要通过
    sasjs request
    在 batch/reusable context 下复现 service,请将 target 的
    contextName
    设置为该 reusable context(例如
    Compute Reusable
    、
    runAs=sasbatch
    );否则它将以你自己的 identity 运行,写入测试步骤会因 batch 所有的 folder 而失败,并提示
    User does not have appropriate authorization level
    。
  • Adapter CSV 格式(这样才能正确读取日志中的
    NOTE: The infile ... is:
    RULE):第 1 行是以空格分隔的
    name:$informat.
    对组成的 header;数据行以逗号分隔(CRLF),仅当值包含特殊字符(
    ,
    、
    "
    、tab、newline)时才使用双引号。sasjs/core webout reader 会通过
    dsd
    +
    firstobs=2
    + 根据 header 生成的
    input <name>:$informat.;
    statement 读取数据。
  • _executionTasks=true
    (runAsTask)会改变输入数据的传递方式:数据以
    sasjs<N>data
    macro variable 的形式到达(分块为
    sasjs<N>data0..N
    ),而不是
    _WEBIN_FILE
    上传。Core webout 的
    mv_webout
    macro 同时支持两种方式,但它会根据
    _EXECUTIONTASKS
    分支处理,因此阅读日志时需要注意这一点。

Redeploying cleanly on Viya (the 409 Conflict problem)

在 Viya 上干净地重新部署(409 Conflict 问题)

Re-running
sasjs cbd
/
sasjs run viya.sas
against an existing appLoc often fails mid-deploy with
409 Conflict
(and an
mp_abort
->
abort cancel
):
mv_createfile
DELETEs the old file id then tries to recreate it, but when an intermediate folder already exists (e.g.
<appLoc>/services/web/js
) the recreate step conflicts and the whole deploy aborts - leaving a half-deployed app (services present, some assets missing). The
?recursive=true
folder DELETE also returns
409 You cannot delete the folder because it is not empty
, and individual member/folder DELETEs can return
403
even as the owner, so you cannot easily tear the tree down by hand.
The reliable workaround is to move the top appLoc folder out of the way and redeploy to the original path - the deploy creates a fresh folder tree with no conflicts, and the old folder is retained as a backup. This is far faster and more reliable than fighting per-member deletes, and is the recommended pre-deploy step for any non-CI redeploy on Viya. Two ways to move it:
  • Rename in place (simplest):
    PATCH /folders/folders/{id}
    with
    {"name":"<old>.bak.<ts>","version":2}
    - frees the original name, keeps the old folder as a sibling backup.
  • MOVE into a backup parent (tidier - keeps all backups in one place): create (once) a backup parent folder e.g.
    /Users/<id>/macrodash-backups
    , then
    POST /folders/folders/{backupParentId}/members/{appLocFolderId}?action=move
    moves the whole tree (with contents) into it, freeing the original path for the fresh deploy.
(On sasjs/server just
sasjs fs delete
the appLoc first - Drive there supports clean recursive deletes.)
JES applies its own CSP header when streaming (includes
unsafe-inline
/
unsafe-eval
); a strict CSP meta tag in the app's HTML still applies and is the one that matters for the app code.
针对已有 appLoc 再次运行
sasjs cbd
/
sasjs run viya.sas
时,部署过程经常中途出现
409 Conflict
(并触发
mp_abort
->
abort cancel
):
mv_createfile
会先 DELETE 旧 file ID,然后尝试重新创建;但如果中间某个 folder 已经存在(例如
<appLoc>/services/web/js
),重新创建时就会发生冲突,整个部署随之中止,并留下一个仅部署了一半的应用(service 已存在,但部分资源缺失)。带
?recursive=true
的 folder DELETE 也会返回
409 You cannot delete the folder because it is not empty
,而单独删除 member/folder 即便以 owner 身份操作也可能返回
403
,因此很难通过手工方式彻底删除整棵目录树。
可靠的变通方案是将顶层 appLoc folder 移至其他位置,然后重新部署到原路径。部署过程会创建一棵全新的 folder tree,不会发生冲突,而旧 folder 会保留为备份。这比逐个处理 member DELETE 更快也更可靠,是 Viya 上任何非 CI 重新部署的推荐前置步骤。移动方式有两种:
  • 原地重命名(最简单):发送
    PATCH /folders/folders/{id}
    ,请求体为
    {"name":"<old>.bak.<ts>","version":2}
    。这样可释放原名称,并将旧 folder 作为同级备份保留。
  • 移入备份父 folder(更整洁,可集中管理所有备份):先创建一次备份父 folder,例如
    /Users/<id>/macrodash-backups
    ,然后发送
    POST /folders/folders/{backupParentId}/members/{appLocFolderId}?action=move
    ,将整棵目录树及其内容移入其中,从而释放原路径供全新部署使用。
(在 sasjs/server 上,只需先使用
sasjs fs delete
删除 appLoc;那里的 Drive 支持干净的递归删除。)
JES 在进行 streaming 时会应用自己的 CSP header(包含
unsafe-inline
/
unsafe-eval
);应用 HTML 中严格的 CSP meta tag 仍然有效,并且它才是真正约束应用代码的策略。