fix: agent skills and nextviya deploys
This commit is contained in:
1 parent
772a359da7
commit
f7db8719f5
37 files changed
+1683
-134
No files matched your search
@@ -1,73 +0,0 @@
|
||||
---
|
||||
name: sas
|
||||
description: >
|
||||
Use this skill whenever writing, modifying, or debugging SAS code in this repository — SAS
|
||||
macros, sasjs services, hooks, or test files (*.sas). Triggers include: SAS macro compilation
|
||||
errors, parameter parsing issues, writing sasjs tests with mp_assert / mp_assertdsobs /
|
||||
mp_assertscope, macro-quoting questions (%str, %nrstr, %superq), %mp_abort usage, or unexpected
|
||||
behaviour when passing free-text values (descriptions, messages) to macros. Also covers repo
|
||||
conventions such as running `sasjs lint` after touching .sas files and the maximum line length
|
||||
rule.
|
||||
---
|
||||
|
||||
# SAS Development
|
||||
|
||||
Conventions and pitfalls for SAS code in this repository (macros in `sas/sasjs/macros/`, services
|
||||
in `sas/sasjs/services/`, tests alongside them as `*.test.sas`).
|
||||
|
||||
## Always `%str()` free-text macro parameters
|
||||
|
||||
A comma inside a macro parameter value is parsed as a **parameter delimiter**. Free-text
|
||||
parameters such as `desc=` (in `%mp_assert`, `%mp_assertdsobs`, `%mp_assertscope`) and `msg=` (in
|
||||
`%mp_abort`) must be wrapped in `%str()` — even when they currently contain no comma, so later
|
||||
edits cannot silently break the call.
|
||||
|
||||
Wrong — the text after the comma becomes an unexpected positional parameter and the compilation
|
||||
fails (or worse, is misparsed):
|
||||
|
||||
```sas
|
||||
%mp_assert(iftrue=(&oldrows=0),
|
||||
desc=Test 2 - all staged records loaded, delete flags ignored,
|
||||
outds=work.test_results
|
||||
)
|
||||
```
|
||||
|
||||
Right:
|
||||
|
||||
```sas
|
||||
%mp_assert(iftrue=(&oldrows=0),
|
||||
desc=%str(Test 2 - all staged records loaded, delete flags ignored),
|
||||
outds=work.test_results
|
||||
)
|
||||
```
|
||||
|
||||
The same applies to any macro parameter that carries user-facing text: `%mp_abort(msg=%str(...))`,
|
||||
`etlsource=` values containing punctuation (use `%superq()` when passing macro variables that may
|
||||
contain special characters), etc.
|
||||
|
||||
Related quoting rules of thumb:
|
||||
|
||||
- `%str()` masks commas, parentheses, semicolons and quotes at compile time — sufficient for
|
||||
static text like `desc=`.
|
||||
- Use `%nrstr()` if the text must also mask `%` and `&` (rare in descriptions).
|
||||
- Use `%superq(var)` when forwarding a macro **variable** whose value may contain special
|
||||
characters (e.g. `etlsource=` in `mpe_targetloader.sas`).
|
||||
|
||||
## Tests must be idempotent
|
||||
|
||||
A test file must pass when run repeatedly (including after a run that failed partway). Conventions:
|
||||
|
||||
- Start the file with `%let syscc=0;` — many macros (e.g. `%mp_lockanytable(LOCK)`) abort on entry if `&syscc>0`, and any `WARNING` in a previous test bumps `syscc` to 4.
|
||||
- Clean up **all** persistent state, not just the obvious one: `MPE_TABLES` registrations, `MPE_LOCKANYTABLE` lock records (a run dying between LOCK and UNLOCK leaves a stale `LOCKED` row), physical tables in `dctest` (`proc datasets ... delete`), and global macro variables created via `select ... into:` (`%symdel`).
|
||||
- Make prep defensive: delete-then-insert config records (handles leftovers from an aborted run), and recreate physical tables rather than assuming they are absent.
|
||||
|
||||
## Repo conventions for .sas files
|
||||
|
||||
- Run `npx sasjs lint` from the `sas/` directory after creating or modifying any `.sas` file; the
|
||||
files you touched must have zero warnings (pre-existing warnings in other files can be ignored).
|
||||
- Maximum line length is 80 characters (lint-enforced) — this is why long `msg=%str(...)` values
|
||||
sometimes need shortening.
|
||||
- Test files are named `<thing>.test.sas` (or `<thing>.test.N.sas`) and run via
|
||||
`npm run 4gl && sasjs test -t 4gl` from `sas/` — see `.agent/docs/testing.md`.
|
||||
- `sas/sasjsbuild/` is generated build output — never hand-edit it; edit sources under
|
||||
`sas/sasjs/` only.
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,61 @@
|
||||
---
|
||||
name: dc-sas
|
||||
description: >
|
||||
Use this skill alongside the sasjs/skills collection (sas, sasjs-core, sasjs-cli) when working on
|
||||
Data Controller SAS code. Covers the repo-specific SAS knowledge that is NOT in those skills —
|
||||
DC test-state conventions (MPE control tables, dctest, %let syscc) and the chunked-deploy
|
||||
strategy for the huge generated build script (sasjsbuild/viya.sas). For general SAS language,
|
||||
@sasjs/core macro, and @sasjs/cli guidance, rely on the sas, sasjs-core and sasjs-cli skills.
|
||||
---
|
||||
|
||||
# SAS Development in Data Controller
|
||||
|
||||
The sas, sasjs-core and sasjs-cli skills cover general SAS language and SASjs conventions (macro
|
||||
quoting, `%mp_assert*` testing style, `sasjs lint`, `sasjs run`, `sasjsbuild/` being generated
|
||||
output). These notes only cover the parts specific to Data Controller.
|
||||
|
||||
|
||||
## Tests must be idempotent
|
||||
|
||||
A test file must pass when run repeatedly (including after a run that failed partway).
|
||||
|
||||
- Clean up **all** persistent DC state, not just the obvious one: `MPE_TABLES` registrations,
|
||||
`MPE_LOCKANYTABLE` lock records (a run dying between LOCK and UNLOCK leaves a stale `LOCKED`
|
||||
row), physical tables in `dctest` (`proc datasets ... delete`), and global macro variables
|
||||
created via `select ... into:` (`%symdel`).
|
||||
|
||||
## Chunked deploy to Viya
|
||||
|
||||
The full DC build script (`sasjsbuild/viya.sas`) is very large (11+ MB / 300k+ lines). The Viya
|
||||
compute API can hang or fail when submitting a file this size in a single request.
|
||||
|
||||
**Chunked deploy.** Split the build script at `%let path=<folder>` boundaries — these are clean
|
||||
section breaks between service/test folders (always preceded by blank lines, never inside a data
|
||||
step). Each sub-chunk must be prepended with the shared header (the first ~3827 lines containing
|
||||
macro definitions, `appLoc` setup, and logging options:
|
||||
|
||||
`options ps=max nonotes nosgen nomprint nomlogic nosource2 nosource noquotelenmax;`
|
||||
|
||||
Logging is already suppressed in the header — no extra action needed. Keep each chunk under ~1.5
|
||||
MB. The `tests/macros`, `tests/services`, and streaming-app tail sections are the largest and may
|
||||
need further sub-splitting at `%let service=` boundaries.
|
||||
|
||||
When splitting mid-folder (between services in the same folder), the sub-chunk that starts
|
||||
mid-folder must re-assert `%let path=<folder>;` before its first `%let service=` line — otherwise
|
||||
`&path` is unresolved and the `%mv_createwebservice(path=&appLoc/&path, ...)` call will abort with
|
||||
a recursive reference error.
|
||||
|
||||
Deploy each chunk in order with `sasjs run <chunk>.sas -t <target>`. After all chunks are
|
||||
deployed, run `sasjs request services/admin/makedata -d deploy/makeDataViya.json -t <target>` to
|
||||
create the database.
|
||||
|
||||
**Non-fatal errors.** `ERROR: Unauthorized` on `/dataSources/providers/Compute/` lines in the
|
||||
log are expected for non-admin Viya users — they come from the Data Sources API during
|
||||
lineage/catalog refresh and do not affect the deploy.
|
||||
|
||||
## Repo conventions for .sas files
|
||||
|
||||
- Test files are named `<thing>.test.sas` (or `<thing>.test.N.sas`) and run via
|
||||
`npm run 4gl && sasjs test -t 4gl` from `sas/` — see `.agents/docs/testing.md`.
|
||||
- `sas/sasjsbuild/` is generated build output — never hand-edit it; edit sources under
|
||||
`sas/sasjs/` only.
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,63 @@
|
||||
---
|
||||
name: sas
|
||||
description: Expert guidance for the SAS programming language — DATA step, PROC SQL, macro language, formats, ODS, and common procedures. Pure SAS syntax only, no SASjs-framework content. Use when writing, reviewing, or debugging .sas programs, or answering SAS language questions.
|
||||
---
|
||||
|
||||
# SAS Language
|
||||
|
||||
Write idiomatic, production-quality SAS code. This skill covers the SAS language itself, independent of any framework.
|
||||
|
||||
## Scope
|
||||
|
||||
- DATA step programming (SET/MERGE/BY, RETAIN, arrays, DO loops, hash objects, first./last. processing)
|
||||
- PROC SQL (joins, subqueries, views, pass-through with CONNECT TO)
|
||||
- Macro language (%macro/%mend, macro variables, %local/%global, conditional %IF logic, quoting functions %STR/%NRSTR/%BQUOTE, CALL SYMPUTX)
|
||||
- Common PROCs: SORT, MEANS/SUMMARY, FREQ, TRANSPOSE, REPORT, PRINT, CONTENTS, DATASETS, FORMAT, IMPORT/EXPORT
|
||||
- Formats and informats (user-defined via PROC FORMAT, picture formats)
|
||||
- ODS output (HTML, PDF, RTF, Excel, OUTPUT destination)
|
||||
- File I/O: LIBNAME, FILENAME, INFILE/INPUT, FILE/PUT
|
||||
|
||||
## Non-negotiable: no WARNINGs
|
||||
|
||||
Generated SAS code must run cleanly — **zero WARNINGs (and zero ERRORs) in the log**. Treat every `WARNING:` as a defect: uninitialized variables, implicit type conversions, truncation notes that should be warnings, "no observations", unresolved macro references, etc. If a warning is truly unavoidable, suppress it deliberately (e.g. an explicit option) and comment why. Likewise avoid superfluous NOTEs where reasonable.
|
||||
|
||||
## Style rules
|
||||
|
||||
These follow the @sasjs/core coding standards — apply them to all SAS code:
|
||||
|
||||
- One statement per line; indentation = 2 spaces, no tabs, no trailing whitespace
|
||||
- Lines no longer than 80 characters; unix (LF) line endings; UTF-8
|
||||
- Avoid non-ASCII / special characters entirely — maximum compatibility across SAS installations and encodings
|
||||
- Always end steps with `run;`; for `proc sql` (and CAS-connected procs) `quit;` is essential to avoid `WARNING: You cannot disconnect or terminate session ...` on Viya
|
||||
- All dataset references must be 2-level (`work.blah`, not `blah`) — protects against `DATASTMTCHK=ALLKEYWORDS` and an active `USER` library
|
||||
- Explicit `length` / `attrib` for character variables rather than relying on defaults (avoids implicit length=8 truncation)
|
||||
- Use literal suffixes for clarity (`'01JAN2020'd`, `'12:30't`)
|
||||
- Prefer `proc sort` with `nodupkey` over manual dedup logic
|
||||
- Macros:
|
||||
- Define with parentheses, even with no parameters: `%macro x();` not `%macro x;`
|
||||
- Closing `%mend;` must repeat the macro name
|
||||
- Macro calls are not terminated with a semicolon: `%my_macro()` not `%my_macro();`
|
||||
- Mandatory parameters positional; optional parameters keyword (`var=`) style
|
||||
- Macro names lowercase, verb-noun convention
|
||||
- Macro variables without trailing dot (`&var` not `&var.`) unless needed to prevent incorrect resolution
|
||||
- Macro variable NAMES are case-insensitive: `&Foo`, `&FOO`, and `&foo` all resolve to the same symbol (same for `%symexist`/`%superq`/`symget` arguments, which take a NAME not a value). Don't chase case mismatches as a bug — it's never the cause.
|
||||
- ALL macro variables must be `%local` unless deliberately global (globals should use an application prefix to avoid collisions); use `call symputx` (not `symput`) in DATA steps
|
||||
- Comment with `/* */` inside macros (asterisk comments are compiled into the macro)
|
||||
- Guard macro logic with `%length(&var)=0` checks rather than `&var=` (empty comparisons are unsafe)
|
||||
- Avoid naming collisions: use `%sysfunc`-/`&syslast`-based work tables (e.g. `data &output; set &syslast; run;`) rather than hard-coded names
|
||||
- No open (non-macro) conditional code: wrap platform-branching or conditionally-executed blocks (e.g. `%if %mf_getplatform()=VIYA %then %do; ... %end;`) in a `%macro ... %mend` and invoke the macro. Open `%if` at program level fails in some execution contexts (job/scheduler/test harnesses) and hides scope leaks.
|
||||
|
||||
## Portability awareness
|
||||
|
||||
- Note when code differs between SAS 9.4 and Viya (e.g. CAS actions vs procs, `proc casutil` for sashdat loading, no X command on locked-down servers)
|
||||
- Avoid hard-coded physical paths and engine-specific options unless asked
|
||||
- No open macro code with if/else logic: wrap branching blocks in `%macro ... %mend` and call them — open `%if`/`%else` does not behave as expected in all SAS environments.
|
||||
|
||||
## Common pitfalls to flag when reviewing
|
||||
|
||||
- Unintended many-to-many MERGEs
|
||||
- Automatic variable `_ERROR_` / implicit RETAIN surprises
|
||||
- Macro timing issues: referencing `¯ovar` before it exists, `%if` evaluating data-step variables (use `if`/`symget` instead)
|
||||
- Automatic macro variables (`&syscc`, `&syswarningtext`, `&syserrortext`, `&sysdate`, `&sysuserid`, etc.) are READ-ONLY — attempting to overwrite them (e.g. `%let syswarningtext=;` or `call symput('syscc',...)`) raises `ERROR: Unable to assign value to a macro variable that is read only` (or similar). Never try to "clear" them.
|
||||
- Truncation from implicit length=8 on first assignment
|
||||
- `proc sql` cartesian product warnings
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
name: sasjs-adapter
|
||||
description: Frontend/Node integration with SAS backends using @sasjs/adapter — configuring the SASjs class, authentication (SAS 9, Viya, SASjs server), requests with input/output tables, file upload, and session management. Use when writing TypeScript/JavaScript that calls SAS services or jobs.
|
||||
---
|
||||
|
||||
# @sasjs/adapter
|
||||
|
||||
`@sasjs/adapter` is the TypeScript library for calling SAS services/jobs from browsers or Node, with a unified API across three server types: `SAS9`, `SASVIYA`, `SASJS`.
|
||||
|
||||
## Basic setup
|
||||
|
||||
```ts
|
||||
import SASjs from '@sasjs/adapter'
|
||||
|
||||
const sasjs = new SASjs({
|
||||
serverUrl: 'https://sas.example.com',
|
||||
serverType: 'SASVIYA', // SAS9 | SASVIYA | SASJS
|
||||
appLoc: '/Public/app/myapp', // root folder of deployed services
|
||||
contextName: 'SAS Job Execution compute context', // Viya only
|
||||
debug: false
|
||||
})
|
||||
```
|
||||
|
||||
## Executing a request
|
||||
|
||||
```ts
|
||||
const response = await sasjs.request('services/common/getdata', {
|
||||
mytable: [{ col1: 'value', col2: 42 }] // input tables as JS arrays of objects
|
||||
})
|
||||
// response.result contains output tables sent back from SAS (_webout JSON)
|
||||
```
|
||||
|
||||
- Input tables become SAS datasets via the `sasjs_tables` mechanism (work tables named after the JS keys).
|
||||
- The SAS service must write JSON to `_webout` — conventionally with the `mp_jsonout` macro from @sasjs/core, wrapped in `proc stp`-style begin/end macros.
|
||||
- Responses follow the `SASjsRequest`/`SASjsResponse` types; check `response.result` for tables and `response.log` where available.
|
||||
|
||||
## Authentication
|
||||
|
||||
- **SAS 9**: `sasjs.logIn(username, password)` (form-based against the stored process server). Session cookie is managed automatically.
|
||||
- **Viya**: OAuth client/secret (client credentials grant) or authorization code flow; tokens are refreshed automatically. Configure via CLI (`sasjs add cred`) for Node usage.
|
||||
- **SASJS server**: token-based auth against the sasjs/server API.
|
||||
|
||||
## Key classes / modules
|
||||
|
||||
- `SASjs` — main facade: `request()`, `logIn()/logOut()`, `uploadFile()`, `executeScript()`
|
||||
- `SessionManager` — Viya compute session lifecycle
|
||||
- `ContextManager` — Viya compute context selection
|
||||
- `SASViyaApiClient` / `SAS9ApiClient` / `SASjsApiClient` — low-level per-platform clients (rarely needed directly)
|
||||
- `file/` utilities — file upload to SAS (binary content handling)
|
||||
|
||||
## Tips
|
||||
|
||||
- Set `debug: true` to surface the SAS log in responses while developing.
|
||||
- Always handle `response.status` / error responses — SAS-side errors (e.g. from `%mp_abort`) come back in the JSON, not necessarily as HTTP errors.
|
||||
- For large payloads prefer CSV upload or streamed files over JSON input tables.
|
||||
- Keep `appLoc` consistent with the `appLoc` in `sasjsconfig.json` used to deploy.
|
||||
|
||||
## Important: request() inputs are ALWAYS tables
|
||||
|
||||
Every key in the `data` object of `sasjs.request(path, data)` is serialized via the `sasjs_tables` CSV mechanism and arrives in SAS as a **work dataset named after the key** — even scalar values. You cannot pass ad-hoc macro variables this way; services must read inputs from the work table (e.g. `data _null_; set work.config; call symputx('rootdir', rootdir); run;`). Output column names in `response.result.<table>` come back UPPERCASE (SAS dataset semantics).
|
||||
|
||||
## Using the adapter without a bundler (zero-build / strict CSP frontends)
|
||||
|
||||
The package root `index.js` is a UMD bundle exposing a global `SASjs`. Pattern (from the minimal seed app):
|
||||
|
||||
1. `"prepare": "cp node_modules/@sasjs/adapter/index.js src/sasjs.js"` in package.json (runs on `npm i`).
|
||||
2. `<script src="sasjs.js"></script>` before your app script.
|
||||
3. Configure via a hidden custom element: `<sasjs serverType="SASJS" appLoc="/Public/app/myapp" debug="false"></sasjs>` and read attributes with `document.querySelector('sasjs')`. When the app is streamed by SAS itself, omit `serverUrl` — same-origin requests just work (CSP `default-src 'self'` safe).
|
||||
@@ -0,0 +1,115 @@
|
||||
---
|
||||
name: sasjs-cli
|
||||
description: Using the SASjs CLI (@sasjs/cli) to create, compile, build, deploy, run, and test SASjs projects against SAS 9, Viya, and SASjs server targets. Use for any `sasjs <command>` usage, CI/CD pipelines, target/auth config, sasjsconfig.json, service packs, or frontend streaming builds.
|
||||
---
|
||||
|
||||
# @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`.
|
||||
|
||||
## Targets and auth
|
||||
|
||||
- A **target** = `{ name, serverUrl, serverType, appLoc }`. `serverType` is one of `SAS9`, `SASVIYA`, `SASJS`.
|
||||
- Credentials: `sasjs add cred` (or `.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.
|
||||
|
||||
## 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.)
|
||||
```
|
||||
|
||||
## Command reference
|
||||
|
||||
| Command | Purpose |
|
||||
|---|---|
|
||||
| `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 |
|
||||
|
||||
## 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.
|
||||
- `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.
|
||||
|
||||
## 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.<targetname>` (e.g. `.env.server`) with `CLIENT`, `ACCESS_TOKEN`, `REFRESH_TOKEN`. Never commit them — gitignore `.env*`.
|
||||
|
||||
## Viya auth without a client/secret (`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 ACCESS_TOKEN/REFRESH_TOKEN pair is persisted to `.env.<target>` (local) 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.
|
||||
|
||||
## Viya streaming apps (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.
|
||||
|
||||
### Verifying a Viya deployment headlessly (no browser)
|
||||
|
||||
1. Get a token (password grant works out of the box with the built-in `sas.ec` client, empty secret):
|
||||
`curl -X POST <server>/SASLogon/oauth/token -u 'sas.ec:' -d 'grant_type=password&username=U&password=P'`
|
||||
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).
|
||||
|
||||
### Talking to SAS — ALWAYS use the adapter or the 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*.
|
||||
|
||||
#### Adapter execution-context and input-data facts
|
||||
|
||||
- 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.
|
||||
|
||||
### Redeploying cleanly on Viya (the 409 Conflict problem)
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,99 @@
|
||||
---
|
||||
name: sasjs-core
|
||||
description: Standards and conventions for the @sasjs/core SAS macro library (mf_*, mp_*, mm*, ms_*, mv_* macros). Use when writing or editing SAS macros in a sasjs/core-style repo, picking an existing macro over reinventing one, or the sasjs/core build, lint, doxygen, and testing conventions.
|
||||
---
|
||||
|
||||
# @sasjs/core — SAS Macro Library
|
||||
|
||||
@sasjs/core is an MIT-licensed library of production-quality SAS macros for SAS application development, portable across SAS 9 (meta), Viya, and SASjs server.
|
||||
|
||||
## Coding standards (mandatory)
|
||||
|
||||
- One macro per file; filename must match the macro name (lowercase, no spaces)
|
||||
- Macro definitions must use parentheses: `%macro x();` not `%macro x;`
|
||||
- Macro *calls* are NOT terminated with a semicolon: `%my_macro()` not `%my_macro();`
|
||||
- All macro variables must be declared `%local` to prevent scope leakage
|
||||
- Always use `mf_getuniquefileref` when assigning filerefs, and `mf_getuniquelibref` when assigning librefs (never hardcode or hand-roll unique references)
|
||||
- 2-space indentation, no tabs, no trailing spaces, no invisible characters, max line length 300 (hard lint limit) but keep lines to 80 chars max where possible
|
||||
- Every file must have a Doxygen header:
|
||||
|
||||
```sas
|
||||
/**
|
||||
@file
|
||||
@brief One-line description of the macro
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_othermacro.sas
|
||||
|
||||
@param [in] paramname Description
|
||||
@param [out] outparam Description
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_related.sas
|
||||
|
||||
@version 9.4
|
||||
@author Your Name
|
||||
**/
|
||||
```
|
||||
|
||||
## Folder / prefix conventions
|
||||
|
||||
| Folder | Prefix | Platform |
|
||||
|---|---|---|
|
||||
| `base/` | `mf_` (function-style), `mp_` (procedure-style) | All platforms |
|
||||
| `meta/` | `mm_` | SAS 9 metadata |
|
||||
| `metax/` | `mmx_` | SAS 9 metadata (OS command dependent) |
|
||||
| `viya/` | `mv_` | Viya |
|
||||
| `server/` | `ms_` | SASjs server |
|
||||
| `xplatform/` | `mx_` | Runtime platform detection |
|
||||
| `fcmp/`, `lua/`, `ddl/` | — | PROC FCMP functions, LUA wrappers, DDL |
|
||||
|
||||
Use `mf_` macros when the macro returns a value usable in an expression; use `mp_` for procedural macros that generate code/statements.
|
||||
|
||||
**Cross-suite rule:** `mp_` macros must never reference `mx_` macros. Platform dispatching (SAS 9 / Viya / SASjs server) belongs in the `mx_` suite, which delegates to `ms_`/`mv_`/PROC STP per platform. If an `mp_` macro seems to need platform-specific behaviour, the macro itself belongs in `xplatform/` as an `mx_` macro instead.
|
||||
|
||||
## Reuse before writing
|
||||
|
||||
Before writing a new macro, check the library for an existing one — common utilities already exist, e.g. `mp_abort` (the deprecated `mf_abort` is retained for backwards compatibility — don't use it in new code), `mf_existds`, `mf_existvar`, `mf_existfileref`, `mf_getuser`, `mp_jsonout` (SAS datasets → JSON for `_webout`), `mp_ds2ddl`, `mp_hashdataset`. Platform-specific variants exist under `meta/`, `viya/`, `server/` and are selected at compile time by the CLI.
|
||||
|
||||
## Aborting safely
|
||||
|
||||
Never invoke `%mp_abort` from inside an `%if/%else` block — as a procedural macro, the macro processor can continue executing statements after it before the abort takes effect. Use the `iftrue=` condition parameter instead:
|
||||
|
||||
```sas
|
||||
%mp_abort(iftrue= (&syscc ne 0)
|
||||
,mac=&_program
|
||||
,msg=%str(Something went wrong)
|
||||
)
|
||||
```
|
||||
|
||||
When `%mp_abort` is called from within a `%include` block, SAS cannot exit cleanly (e.g. to `_webout`). Call `%mp_abort(mode=INCLUDE)` after the include (OUTSIDE any macro wrapper) — it checks `work.mp_abort_errds` for an abort status:
|
||||
|
||||
```sas
|
||||
%mp_abort(mode=INCLUDE)
|
||||
```
|
||||
|
||||
Note: `%include`s inside macros should be performed with `%mp_include()` so the `_SYSINCLUDEFILEDEVICE` indicator is set and the abort dataset (`work.mp_abort_errds`) is passed back to the calling program.
|
||||
|
||||
## Testing macros (mandatory conventions)
|
||||
|
||||
- **Always apply `%mp_assertscope` around the macro under test** to catch scope leakage (macro variables must stay `%local`):
|
||||
|
||||
```sas
|
||||
%mp_assertscope(SNAPSHOT)
|
||||
%mx_foo(args)
|
||||
%mp_assertscope(COMPARE,
|
||||
desc=Test 1: mx_foo does not leak scope,
|
||||
outds=work.test_results
|
||||
)
|
||||
```
|
||||
|
||||
- Assertions go to `work.test_results` via `%mp_assert(iftrue=(...), desc=..., outds=work.test_results)`.
|
||||
|
||||
## Lint and build
|
||||
|
||||
- Run `sasjs lint` after every change; do not consider work done until it passes
|
||||
- NEVER bump the version in `package.json` (semantic-release handles it)
|
||||
- Do NOT edit generated files by hand: `all.sas`, `mc_*.sas`, the `lua/` wrappers, and `sasjsbuild/` outputs are produced by the CI build
|
||||
- Markdown files: never hard-wrap; one paragraph per line
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
---
|
||||
name: sasjs-framework
|
||||
description: Building full SASjs applications — project structure, sasjsconfig.json, services/jobs/macros folders, multi-target (SAS 9 / Viya / SASjs server) configuration, streaming frontends, mocks and tests. Use when creating or modifying a SASjs app, editing sasjsconfig.json, or writing backend services returning JSON to a web frontend.
|
||||
---
|
||||
|
||||
# SASjs Framework — Building SASjs Applications
|
||||
|
||||
A SASjs app = a web frontend (any framework: Angular, React, vanilla) + SAS backend code organised in a standard layout, compiled and deployed by `@sasjs/cli` to SAS 9, Viya, or SASjs server. Frontend talks to SAS via `@sasjs/adapter`; backend services return JSON via `_webout`.
|
||||
|
||||
## Standard project layout
|
||||
|
||||
```
|
||||
sasjs/
|
||||
sasjsconfig.json # project + target configuration
|
||||
macros/ # project-specific macros (macroFolders)
|
||||
services/ # web services called from the frontend
|
||||
jobs/ # jobs (scheduled / flow / long-running)
|
||||
programs/ # plain programs (initProgram, termProgram, utilities)
|
||||
db/ # DDL + static data per library (sasjs db)
|
||||
tests/ # tests run by `sasjs test`
|
||||
mocks/ # mock responses for offline frontend dev (syncFolder)
|
||||
doxy/ # extra doxygen content for `sasjs doc`
|
||||
```
|
||||
|
||||
## sasjsconfig.json
|
||||
|
||||
Root config holds defaults; each entry in `targets[]` can override them. Key sections:
|
||||
|
||||
- `macroFolders`, `binaryFolders` — where the CLI finds macros/binaries
|
||||
- `serviceConfig.serviceFolders` — service source folders; `initProgram` runs before every service (set up libnames, options)
|
||||
- `jobConfig.jobFolders` — job source folders
|
||||
- `programFolders` — programs compiled/deployed with the app
|
||||
- `streamConfig` — `streamWeb: true` streams the built frontend into SAS so it is served by the platform itself (no separate web server needed); `webSourcePath` points at the frontend build output
|
||||
- `syncFolder` — folder synced to the server (e.g. mocks)
|
||||
- `testConfig` — init/term programs for `sasjs test`
|
||||
- `targets[]` — per-environment overrides: `serverUrl`, `serverType` (`SAS9`/`SASVIYA`/`SASJS`), `appLoc` (deploy root, e.g. `/Public/app/myapp`), target-specific macroFolders (e.g. `targets/viya/macros_viya` for platform shims), `httpsAgentOptions`, `deployConfig`
|
||||
|
||||
The full JSON schema is bundled at `sasjsconfig-schema.json` next to this file — validate config changes against it. Reference it with `"$schema": "https://cli.sasjs.io/sasjsconfig-schema.json"`.
|
||||
|
||||
## Streamed frontend files on Viya (mime types)
|
||||
|
||||
When `streamWeb: true`, the CLI uploads the frontend (`index.html`, renamed per `streamServiceName`, plus css/js) to the Viya Files service using the `%mv_createfile` macro. That macro creates the file in a very particular way to ensure it streams correctly:
|
||||
|
||||
- POSTs to `/files/files` with the content type derived from the extension (`%mf_mimetype`)
|
||||
- sets `typeDefName=file_html` (via `%mv_getViyaFileExtParms`) so the file is recognised as HTML
|
||||
- sends `Content-Disposition` **without** `attachment` for HTML/SVG so it renders in the browser
|
||||
|
||||
**Never update a streamed frontend file in place** with a `filename filesrvc` fileref + data step rewrite — the Files service then treats it as a generic blob and the mime type is lost, so the app no longer streams (browser downloads it or shows raw text). To modify a streamed file at runtime (eg patching the compute `contextname` in the html), read it (a `filesrvc` fileref is fine for *reading*), write the modified content to a temp fileref, and **re-create the file with `%mv_createfile(path=..., name=..., inref=...)`** (it deletes the old file and re-POSTs with the correct mime type).
|
||||
|
||||
## Service contract (frontend ↔ SAS)
|
||||
|
||||
1. Adapter POSTs to `services/<folder>/<name>` with input tables (arrays of objects) → work datasets named after the JS keys.
|
||||
2. Service SAS code runs after `initProgram`; it reads inputs, does work, and writes output JSON to `_webout`.
|
||||
3. Conventional pattern using @sasjs/core macros:
|
||||
|
||||
```sas
|
||||
/**
|
||||
@file
|
||||
@brief Example service returning data
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_jsonout.sas
|
||||
@li mp_abort.sas
|
||||
**/
|
||||
|
||||
/* validation / logic here */
|
||||
|
||||
%mp_jsonout(OPEN)
|
||||
%mp_jsonout(OBJ,results,dslabel=results)
|
||||
%mp_jsonout(CLOSE)
|
||||
```
|
||||
|
||||
4. On error, abort cleanly with `%mp_abort(...)` (`mf_abort` is deprecated) so the adapter receives a structured error in the JSON, not a half-written response. Do **not** call `%mp_abort` inside an `%if/%else` block — the macro processor may keep executing beyond the abort. Use the conditional `iftrue=` parameter instead, e.g.:
|
||||
|
||||
```sas
|
||||
%mp_abort(iftrue= (%mf_existds(work.results)=0)
|
||||
,mac=&_program
|
||||
,msg=%str(No results found)
|
||||
)
|
||||
```
|
||||
|
||||
If the abort happens inside a `%include` block, SAS cannot exit to `_webout` cleanly — after the include, call `%mp_abort(mode=INCLUDE)` (outside any macro wrapper), which checks `work.mp_abort_errds` for an abort status.
|
||||
|
||||
## Multi-target discipline
|
||||
|
||||
- Keep backend code platform-neutral in shared folders; put platform-specific shims in `targets/<name>/macros_*` folders and register them only on that target.
|
||||
- Platform capability macros exist in @sasjs/core (`mm_*` metadata, `mv_*` Viya, `ms_*` server) — don't branch on server type by hand.
|
||||
|
||||
## Quality gates (follow the conventions of mature apps like Data Controller)
|
||||
|
||||
- Run `sasjs lint` after touching any `.sas` file; fix all warnings in files you touched.
|
||||
- The linter enforces 2-space indentation everywhere, including continuation lines inside `/* ... */` block comments — never align comment text with 3+ spaces.
|
||||
- Add tests and run `sasjs test` for backend logic changes. When testing macros, always wrap the macro under test with `%mp_assertscope(SNAPSHOT)` / `%mp_assertscope(COMPARE, ...)` to catch macro-variable scope leakage, and wrap any platform-branching code in `%macro` wrappers (no open conditional macro code in test programs).
|
||||
- Provide mocks in `sasjs/mocks` so the frontend can be developed without a live SAS server.
|
||||
- Never auto-commit or bump versions; releases are pipeline-driven (conventional commits).
|
||||
- Markdown files: no hard wrapping — one paragraph per line.
|
||||
- Apps must work offline/on-prem: no external CDN assets in the frontend bundle.
|
||||
|
||||
## Reference implementations
|
||||
|
||||
Look at existing apps for patterns: folder layouts, `sasjsconfig.json` multi-target setups, service structure, streaming builds, and test/mock conventions, eg:
|
||||
* https://git.datacontroller.io/dc/dc
|
||||
* https://github.com/sasjs/react-seed-app
|
||||
* https://github.com/sasjs/macro-dash
|
||||
@@ -0,0 +1,966 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://github.com/sasjs/utils/blob/main/src/types/sasjsconfig-schema.json",
|
||||
"type": "object",
|
||||
"title": "SASjs Config File",
|
||||
"description": "The SASjs Config file provides the settings and structure for your SASjs project. ",
|
||||
"default": {},
|
||||
"examples": [
|
||||
{
|
||||
"macroFolders": ["macros"],
|
||||
"programFolders": ["programs"],
|
||||
"binaryFolders": ["binaries"],
|
||||
"defaultTarget": "viya",
|
||||
"targets": [
|
||||
{
|
||||
"name": "viya",
|
||||
"serverType": "SASVIYA",
|
||||
"serverUrl": "https://sas.sasjs.com",
|
||||
"appLoc": "/Public/app",
|
||||
"contextName": "SAS Job Execution compute context",
|
||||
"deployConfig": {
|
||||
"deployServicePack": true,
|
||||
"deployScripts": ["sasjsbuild/myviyadeploy.sas"]
|
||||
},
|
||||
"serviceConfig": {
|
||||
"serviceFolders": ["targets/viya/services/admin"],
|
||||
"initProgram": "build/serviceinit.sas",
|
||||
"termProgram": "build/serviceinit.sas",
|
||||
"macroVars": {
|
||||
"name": "viyavalue",
|
||||
"extravar": "this too"
|
||||
}
|
||||
},
|
||||
"jobConfig": {
|
||||
"jobFolders": [],
|
||||
"initProgram": "",
|
||||
"termProgram": "",
|
||||
"macroVars": {}
|
||||
},
|
||||
"streamConfig": {
|
||||
"assetPaths": [],
|
||||
"streamWeb": false,
|
||||
"streamWebFolder": "webv",
|
||||
"webSourcePath": "dist"
|
||||
},
|
||||
"testConfig": {
|
||||
"initProgram": "sasjs/tests/testinit.sas",
|
||||
"termProgram": "sasjs/tests/testterm.sas",
|
||||
"macroVars": {
|
||||
"testVar": "testValue"
|
||||
},
|
||||
"testSetUp": "sasjs/tests/testsetup.sas",
|
||||
"testTearDown": "sasjs/tests/testteardown.sas"
|
||||
},
|
||||
"macroFolders": ["targets/viya/macros"],
|
||||
"programFolders": [],
|
||||
"binaryFolders": ["binaries"]
|
||||
},
|
||||
{
|
||||
"name": "sas9",
|
||||
"serverType": "SAS9",
|
||||
"serverUrl": "https://sas.sasjs.com:7980",
|
||||
"appLoc": "/User Folders/&sysuserid/My Folder",
|
||||
"serverName": "Foundation",
|
||||
"repositoryName": "SASApp",
|
||||
"buildConfig": {
|
||||
"buildOutputFileName": "mysas9deploy.sas",
|
||||
"initProgram": "",
|
||||
"termProgram": "",
|
||||
"macroVars": {}
|
||||
},
|
||||
"deployConfig": {
|
||||
"deployScripts": ["build/deploysas9.sh"],
|
||||
"deployServicePack": false
|
||||
},
|
||||
"serviceConfig": {
|
||||
"serviceFolders": ["targets/sas9/services/admin"],
|
||||
"initProgram": "",
|
||||
"termProgram": "build/servicetermother.sas",
|
||||
"macroVars": {}
|
||||
},
|
||||
"streamConfig": {
|
||||
"assetPaths": [],
|
||||
"streamWeb": false,
|
||||
"streamWebFolder": "web9",
|
||||
"webSourcePath": "dist"
|
||||
},
|
||||
"testConfig": {
|
||||
"initProgram": "sasjs/tests/testinit.sas",
|
||||
"termProgram": "sasjs/tests/testterm.sas",
|
||||
"macroVars": {
|
||||
"testVar": "testValue"
|
||||
},
|
||||
"testSetUp": "sasjs/tests/testsetup.sas",
|
||||
"testTearDown": "sasjs/tests/testteardown.sas"
|
||||
},
|
||||
"macroFolders": ["targets/sas9/macros"],
|
||||
"programFolders": [],
|
||||
"binaryFolders": ["binaries"]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"binaryFolders": {
|
||||
"$id": "#/properties/binaryFolders",
|
||||
"type": "array",
|
||||
"title": "The binaryFolders array",
|
||||
"description": "These local folders are searched for Binary Files when running sasjs compile. Folders are relative to the sasjs/sasjsconfig.json file.",
|
||||
"examples": [["binaries", "../../more_binaries"]]
|
||||
},
|
||||
"sasjsBuildFolder": {
|
||||
"$id": "#/properties/sasjsBuildFolder",
|
||||
"type": "string",
|
||||
"title": "sasjsBuildFolder",
|
||||
"description": "The name of the folder containing the compiled output. The `sasjs build` command will take all of the subfolders here as inputs to create the build pack. By default this will be named `sasjsbuild`. In global, the default is `~/.sasjsbuild`.",
|
||||
"default": "sasjsbuild",
|
||||
"examples": ["sasjsbuild", ".sasjsbuild"]
|
||||
},
|
||||
"sasjsResultsFolder": {
|
||||
"$id": "#/properties/sasjsResultsFolder",
|
||||
"type": "string",
|
||||
"title": "sasjsResultsFolder",
|
||||
"description": "The name of the folder containing the output (eg logs, ODS output) from `sasjs run`. By default this will be named `sasjsresults`. In global, the default is `~/.sasjsresults`.",
|
||||
"default": "sasjsresults",
|
||||
"examples": ["sasjsresults", ".sasjsresults"]
|
||||
},
|
||||
"defaultTarget": {
|
||||
"$id": "#/properties/defaultTarget",
|
||||
"type": "string",
|
||||
"title": "Default Target",
|
||||
"description": "If a target is not specified, this target is used by default. The default target must exist in the (local) targets array.",
|
||||
"default": "viya",
|
||||
"examples": ["viya"]
|
||||
},
|
||||
"docConfig": {
|
||||
"$id": "#/properties/docConfig",
|
||||
"type": "object",
|
||||
"title": "The docConfig schema",
|
||||
"description": "SASjs uses doxygen to auto-generate HTML documentation using the headers in your SAS programs, macros, services & jobs. For more info, see [https://cli.sasjs.io/doc](https://cli.sasjs.io/doc). Some properties are taken from package.json (such as the project Name).",
|
||||
"default": {
|
||||
"dataControllerUrl": "https://mysasserver.com/web/datacontroller/#",
|
||||
"enableLineage": true,
|
||||
"doxyContent": {
|
||||
"readMe": "../../README.md"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"displayMacroCore": true,
|
||||
"outDirectory": "/some/output/directory",
|
||||
"dataControllerUrl": "https://mysasserver.com/web/datacontroller/#",
|
||||
"enableLineage": true,
|
||||
"doxyContent": {
|
||||
"readMe": "../../my/custom/homepage.md",
|
||||
"path": "my/custom/doxy/folder"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"displayMacroCore": {
|
||||
"$id": "#/properties/docConfig/properties/displayMacroCore",
|
||||
"type": "boolean",
|
||||
"title": "The displayMacroCore docConfig option",
|
||||
"description": "The CLI will autocompile macro dependencies that exist in the SASjs Macro Core library. These will also show in the documentation under 'node_modules'. If you'd prefer not to show these in the rendered docs, set this value to false.",
|
||||
"default": true,
|
||||
"examples": [true]
|
||||
},
|
||||
"outDirectory": {
|
||||
"$id": "#/properties/docConfig/properties/outDirectory",
|
||||
"type": "string",
|
||||
"title": "The outDirectory docConfig option",
|
||||
"description": "The location to which the generated HTML SAS documentation is written. If missing, or left blank, the files will be written to the `sasjsbuild/doc` directory (default behaviour).",
|
||||
"default": "sasjsbuild/doc",
|
||||
"examples": ["/my/preferred/docs/directory"]
|
||||
},
|
||||
"dataControllerUrl": {
|
||||
"$id": "#/properties/docConfig/properties/dataControllerUrl",
|
||||
"type": "string",
|
||||
"title": "The dataControllerUrl docConfig option",
|
||||
"description": "Provide the full URL to Data Controller so that `sasjs doc` can link the lineage diagram directly to the table viewer in [Data Controller](https://datacontroller.io).\nIf left blank, or undefined, no links will be generated.",
|
||||
"default": "",
|
||||
"examples": ["https://yourserver.co.uk/dcviya/#"]
|
||||
},
|
||||
"enableLineage": {
|
||||
"$id": "#/properties/docConfig/properties/enableLineage",
|
||||
"type": "boolean",
|
||||
"title": "Enable Lineage",
|
||||
"description": "If true, sasjs doc will generate and display Data Lineage from Jobs and Services.",
|
||||
"default": true
|
||||
},
|
||||
"doxyContent": {
|
||||
"$id": "#/properties/docConfig/properties/doxyContent",
|
||||
"type": "object",
|
||||
"title": "doxyContent",
|
||||
"description": "Configuration of the Doxyfile variables",
|
||||
"default": {},
|
||||
"examples": [
|
||||
{
|
||||
"favIcon": "favicon.ico",
|
||||
"footer": "new_footer.html",
|
||||
"header": "new_header.html",
|
||||
"layout": "DoxygenLayout.xml",
|
||||
"logo": "logo.png",
|
||||
"readMe": "../../README.md",
|
||||
"stylesheet": "new_stylesheet.css",
|
||||
"path": "sasjs/doxy"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"favIcon": {
|
||||
"$id": "#/properties/docConfig/properties/doxyContent/properties/favIcon",
|
||||
"type": "string",
|
||||
"title": "Doxygen favicon",
|
||||
"description": "The favicon used in the doxygen documentation",
|
||||
"default": "favicon.ico"
|
||||
},
|
||||
"footer": {
|
||||
"$id": "#/properties/docConfig/properties/doxyContent/properties/footer",
|
||||
"type": "string",
|
||||
"title": "Doxygen footer",
|
||||
"description": "The footer HTML file used in the doxygen documentation",
|
||||
"default": "new_footer.html"
|
||||
},
|
||||
"header": {
|
||||
"$id": "#/properties/docConfig/properties/doxyContent/properties/header",
|
||||
"type": "string",
|
||||
"title": "Doxygen header",
|
||||
"description": "The header HTML file used in the doxygen documentation",
|
||||
"default": "new_header.html"
|
||||
},
|
||||
"layout": {
|
||||
"$id": "#/properties/docConfig/properties/doxyContent/properties/layout",
|
||||
"type": "string",
|
||||
"title": "Doxygen Layout XML File",
|
||||
"description": "The layout XML file used to build the doxygen documentation",
|
||||
"default": "DoxygenLayout.xml"
|
||||
},
|
||||
"logo": {
|
||||
"$id": "#/properties/docConfig/properties/doxyContent/properties/logo",
|
||||
"type": "string",
|
||||
"title": "Doxygen Logo",
|
||||
"description": "The logo file used by doxygen",
|
||||
"default": "logo.png"
|
||||
},
|
||||
"readMe": {
|
||||
"$id": "#/properties/docConfig/properties/doxyContent/properties/readMe",
|
||||
"type": "string",
|
||||
"title": "Doxygen Homepage",
|
||||
"description": "The file used to generate the doxygen homepage (defaults to the readme.md)",
|
||||
"default": "../../README.md"
|
||||
},
|
||||
"stylesheet": {
|
||||
"$id": "#/properties/docConfig/properties/doxyContent/properties/stylesheet",
|
||||
"type": "string",
|
||||
"title": "Doxygen CSS",
|
||||
"description": "The CSS file used to extend Doxygen",
|
||||
"default": "new_stylesheet.css"
|
||||
},
|
||||
"path": {
|
||||
"$id": "#/properties/docConfig/properties/doxyContent/properties/path",
|
||||
"type": "string",
|
||||
"title": "Doxygen Path",
|
||||
"description": "The path to the Doxygen configuration files (relative to sasjs/sasjsconfig.json)",
|
||||
"default": "sasjs/doxy"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"httpsAgentOptions": {
|
||||
"$id": "#/properties/httpsAgentOptions",
|
||||
"type": "object",
|
||||
"title": "httpsAgentOptions",
|
||||
"description": "Configure https agent by setting all supported attribute such as `key`, `cert`, `ca`, `rejectUnauthorized` and `requestCert`",
|
||||
"examples": [
|
||||
{
|
||||
"allowInsecureRequests": false,
|
||||
"caPath": "path/to/caFile",
|
||||
"keyPath": "path/to/keyFile",
|
||||
"certPath": "path/to/certFile",
|
||||
"requestCert": false,
|
||||
"rejectUnauthorized": true
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"allowInsecureRequests": {
|
||||
"$id": "#/properties/httpsAgentOptions/properties/allowInsecureRequests",
|
||||
"type": "boolean",
|
||||
"title": "allowInsecureRequests",
|
||||
"description": "If you are having certificate errors connecting to SAS, that cannot be properly resolved, try setting this value to true. This option only has an effect if rejectUnauthorized is not present.",
|
||||
"default": false,
|
||||
"examples": [true, false]
|
||||
},
|
||||
"caPath": {
|
||||
"$id": "#/properties/httpsAgentOptions/properties/caPath",
|
||||
"type": "string",
|
||||
"title": "caPath",
|
||||
"description": "Optionally override the trusted CA certificates. Default is to trust the well-known CAs curated by Mozilla. Mozilla's CAs are completely replaced when CAs are explicitly specified using this option.",
|
||||
"examples": ["path/to/caFile"]
|
||||
},
|
||||
"keyFile": {
|
||||
"$id": "#/properties/httpsAgentOptions/properties/keyFile",
|
||||
"type": "string",
|
||||
"title": "keyFile",
|
||||
"description": "Private keys in PEM format. PEM allows the option of private keys being encrypted. Encrypted keys will be decrypted with options.passphrase. Multiple keys using different algorithms can be provided either as an array of unencrypted key strings or buffers, or an array of objects in the form {pem: <string|buffer>[, passphrase: <string>]}. The object form can only occur in an array. object.passphrase is optional. Encrypted keys will be decrypted with object.passphrase if provided, or options.passphrase if it is not.",
|
||||
"examples": ["path/to/keyFile"]
|
||||
},
|
||||
"certFile": {
|
||||
"$id": "#/properties/httpsAgentOptions/properties/certFile",
|
||||
"type": "string",
|
||||
"title": "certFile",
|
||||
"description": "Cert chains in PEM format. One cert chain should be provided per private key. Each cert chain should consist of the PEM formatted certificate for a provided private key, followed by the PEM formatted intermediate certificates (if any), in order, and not including the root CA (the root CA must be pre-known to the peer, see ca). When providing multiple cert chains, they do not have to be in the same order as their private keys in key. If the intermediate certificates are not provided, the peer will not be able to validate the certificate, and the handshake will fail.",
|
||||
"examples": ["path/to/certFile"]
|
||||
},
|
||||
"requestCert": {
|
||||
"$id": "#/properties/httpsAgentOptions/properties/requestCert",
|
||||
"type": "boolean",
|
||||
"title": "requestCert",
|
||||
"description": "If true the server will request a certificate from clients that connect and attempt to verify that certificate. Defaults to false.",
|
||||
"default": false,
|
||||
"examples": [true, false]
|
||||
},
|
||||
"rejectUnauthorized": {
|
||||
"$id": "#/properties/httpsAgentOptions/properties/rejectUnauthorized",
|
||||
"type": "boolean",
|
||||
"title": "rejectUnauthorized",
|
||||
"description": "If true the server will reject any connection which is not authorized with the list of supplied CAs. This option only has an effect if requestCert is true.",
|
||||
"default": true,
|
||||
"examples": [true, false]
|
||||
}
|
||||
}
|
||||
},
|
||||
"buildConfig": {
|
||||
"$id": "#/properties/buildConfig",
|
||||
"type": "object",
|
||||
"title": "buildConfig",
|
||||
"description": "Dictates which files get compiled into the build program (.sas), used to deploy services into SAS 9 or Viya environments (without a client/secret). You may use this config to include build specific macros, programs or macro variables - which is run a single time, on deployment - for things like database creation, or exporting a SAS 9 SPK after service creation.",
|
||||
"examples": [
|
||||
{
|
||||
"initProgram": "build/buildinit.sas",
|
||||
"termProgram": "build/buildterm.sas",
|
||||
"macroVars": {
|
||||
"name": "value",
|
||||
"numvar": "42"
|
||||
},
|
||||
"buildOutputFileName": "buildpack.sas"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"buildOutputFileName": {
|
||||
"$id": "#/properties/buildConfig/properties/buildOutputFileName",
|
||||
"type": "string",
|
||||
"title": "buildOutputFileName",
|
||||
"description": "The name of the generated .sas program, which can be used to deploy the app using only SAS Studio. By default, this will be the name of the target.",
|
||||
"examples": ["viya.sas", "sas9.sas"]
|
||||
},
|
||||
"initProgram": {
|
||||
"$id": "#/properties/buildConfig/properties/initProgram",
|
||||
"type": "string",
|
||||
"title": "The buildConfig initProgram",
|
||||
"description": "The path to a .sas program that will be inserted at the start of the build .sas program (created when running `sasjs build`).",
|
||||
"default": "sasjs/buildinit.sas"
|
||||
},
|
||||
"termProgram": {
|
||||
"$id": "#/properties/buildConfig/properties/termProgram",
|
||||
"type": "string",
|
||||
"title": "The buildConfig termProgram",
|
||||
"description": "The path to a .sas program that will be inserted at the end of the build .sas program (created when running `sasjs build`).",
|
||||
"default": "sasjs/buildterm.sas"
|
||||
},
|
||||
"macroVars": {
|
||||
"$id": "#/properties/buildConfig/properties/macroVars",
|
||||
"type": "object",
|
||||
"title": "The buildConfig macro variables",
|
||||
"description": "A series of name value pairs that will be turned into SAS macro variables in the build .sas program (generated when running `sasjs build`). The example provided will generate sas code as follows:\n```\n%let name=value;\n%let numvar=42;\n```",
|
||||
"default": {},
|
||||
"examples": [
|
||||
{
|
||||
"name": "value",
|
||||
"numvar": "42"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"deployConfig": {
|
||||
"$id": "#/properties/deployConfig",
|
||||
"type": "object",
|
||||
"title": "The deployConfig schema",
|
||||
"description": "The deployConfig object enables settings that relate to the deployment of a SAS app - be that Viya, or SAS 9, or a pure Base environment.",
|
||||
"default": {},
|
||||
"examples": [
|
||||
{
|
||||
"deployScripts": ["build/deployscript.sh"]
|
||||
},
|
||||
{
|
||||
"deployScripts": ["build/deployscript.sh"],
|
||||
"deployServicePack": true
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"deployScripts": {
|
||||
"$id": "#/properties/deployConfig/properties/deployScripts",
|
||||
"type": "array",
|
||||
"title": "The deployConfig deployScripts array",
|
||||
"description": "These scripts are executed when running `sasjs deploy`. If the file is a .sas file, it is executed on the SAS server (Viya only). Otherwise it is executed locally. These scripts are run AFTER the deployment of the servicepack, if `deployServicePack:true` (Viya only).",
|
||||
"default": [],
|
||||
"examples": [["build/deployscript.sh", "build/myprogram.sas"]]
|
||||
},
|
||||
"deployServicePack": {
|
||||
"$id": "#/properties/deployConfig/properties/deployServicePack",
|
||||
"type": "boolean",
|
||||
"title": "The deployConfig deployServicePack flag",
|
||||
"description": "If set to `true` the json pack produced by `sasjs build` will be auto-deployed to the `appLoc` of the specified target (creating all jobs and services in the SAS folder tree). Currently only Viya is supported for this flag.",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"serviceConfig": {
|
||||
"$id": "#/properties/serviceConfig",
|
||||
"type": "object",
|
||||
"title": "The serviceConfig schema",
|
||||
"description": "The serviceConfig object defines how SASjs web services are compiled. Web services differ from jobs in that they include some fixed pre-code (eg the macros to stream out the result json).",
|
||||
"default": {},
|
||||
"examples": [
|
||||
{
|
||||
"serviceFolders": ["services/common", "services/admin"],
|
||||
"initProgram": "build/serviceinit.sas",
|
||||
"termProgram": "build/serviceterm.sas",
|
||||
"macroVars": {
|
||||
"mac1": "value",
|
||||
"mac2": "42"
|
||||
}
|
||||
}
|
||||
],
|
||||
"required": [],
|
||||
"properties": {
|
||||
"serviceFolders": {
|
||||
"$id": "#/properties/serviceConfig/properties/serviceFolders",
|
||||
"type": "array",
|
||||
"title": "The serviceConfig serviceFolders array",
|
||||
"description": "When running `sasjs compile`, all programs in the folders defined in this array are compiled and placed into same-named folders under `sasjsbuild/services`. They will be compiled as services (so, with the service pre-code). Folders can be absolute, or relative to the `sasjs` folder.",
|
||||
"default": [],
|
||||
"examples": [["services/common", "services/admin"]]
|
||||
},
|
||||
"initProgram": {
|
||||
"$id": "#/properties/serviceConfig/properties/initProgram",
|
||||
"type": "string",
|
||||
"title": "The serviceConfig initProgram",
|
||||
"description": "The serviceConfig `initProgram` is a .sas file that is inserted at the start of every SAS service (after compiled macros and any `macroVars`, and before the service itself). ",
|
||||
"default": "",
|
||||
"examples": ["build/serviceinit.sas"]
|
||||
},
|
||||
"termProgram": {
|
||||
"$id": "#/properties/serviceConfig/properties/termProgram",
|
||||
"type": "string",
|
||||
"title": "The serviceConfig termProgram",
|
||||
"description": "The serviceConfig termProgram is inserted at the end of every service as part of `sasjs compile`.",
|
||||
"default": "",
|
||||
"examples": ["build/serviceterm.sas"]
|
||||
},
|
||||
"macroVars": {
|
||||
"$id": "#/properties/serviceConfig/properties/macroVars",
|
||||
"type": "object",
|
||||
"title": "The `serviceConfig` macroVars",
|
||||
"description": "This object allows `sasjs compile` to insert specific macro variables at the start of every service. In this case, the code generated would be:\n```\n%let mac1=value;\n%let mac2=42;\n```",
|
||||
"default": {},
|
||||
"examples": [
|
||||
{
|
||||
"mac1": "value",
|
||||
"mac2": "42"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"jobConfig": {
|
||||
"$id": "#/properties/jobConfig",
|
||||
"type": "object",
|
||||
"title": "The jobConfig schema",
|
||||
"description": "The jobConfig object defines how SASjs Jobs are compiled. ",
|
||||
"default": {},
|
||||
"examples": [
|
||||
{
|
||||
"jobFolders": ["jobs/extract", "jobs/load"],
|
||||
"initProgram": "jobs/jobinit.sas",
|
||||
"termProgram": "jobs/jobterm.sas",
|
||||
"macroVars": {
|
||||
"mac1": "value",
|
||||
"mac2": "42"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"jobFolders": {
|
||||
"$id": "#/properties/jobConfig/properties/jobFolders",
|
||||
"type": "array",
|
||||
"title": "The jobConfig jobFolders array",
|
||||
"description": "When running `sasjs compile`, all programs in the local folders defined in this array are compiled and placed into same-named folders under `sasjsbuild/jobs`. Folders can be absolute, or relative to the local project `/sasjs` folder.",
|
||||
"default": [],
|
||||
"examples": [["jobs/extract", "jobs/transform", "jobs/load"]]
|
||||
},
|
||||
"initProgram": {
|
||||
"$id": "#/properties/jobConfig/properties/initProgram",
|
||||
"type": "string",
|
||||
"title": "The jobConfig initProgram",
|
||||
"description": "The jobConfig `initProgram` is a local .sas file that is inserted at the start of every SAS Job (after compiled macros and any `macroVars`, and before the Job itself). ",
|
||||
"default": "",
|
||||
"examples": ["jobs/jobinit.sas"]
|
||||
},
|
||||
"termProgram": {
|
||||
"$id": "#/properties/jobConfig/properties/termProgram",
|
||||
"type": "string",
|
||||
"title": "The jobConfig termProgram",
|
||||
"description": "The jobConfig termProgram is inserted at the end of every Job as part of `sasjs compile`.",
|
||||
"default": "",
|
||||
"examples": ["jobs/jobterm.sas"]
|
||||
},
|
||||
"macroVars": {
|
||||
"$id": "#/properties/jobConfig/properties/macroVars",
|
||||
"type": "object",
|
||||
"title": "The `jobConfig` macroVars",
|
||||
"description": "This object allows `sasjs compile` to insert specific macro variables at the start of every Job. In this case, the code generated would be:\n```\n%let mac1=value;\n%let mac2=42;\n```",
|
||||
"default": {},
|
||||
"examples": [
|
||||
{
|
||||
"mac1": "value",
|
||||
"mac2": "42"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"streamConfig": {
|
||||
"$id": "#/properties/streamConfig",
|
||||
"type": "object",
|
||||
"title": "streamConfig",
|
||||
"description": "SASjs allows a local web app to be compiled such that all html, css, javascript, and other assets such as png or mp4 are converted into web services and streamed directly from SAS.\n\nThis approach is convenient as it bypasses the need to deploy to a web server. ",
|
||||
"default": {},
|
||||
"examples": [
|
||||
{
|
||||
"assetPaths": [],
|
||||
"streamWeb": false,
|
||||
"streamWebFolder": "webv",
|
||||
"webSourcePath": "dist",
|
||||
"streamLogo": "logo.png"
|
||||
}
|
||||
],
|
||||
"required": ["streamWeb"],
|
||||
"properties": {
|
||||
"assetPaths": {
|
||||
"$id": "#/properties/streamConfig/properties/assetPaths",
|
||||
"type": "array",
|
||||
"title": "The streamConfig assetPaths array",
|
||||
"description": "An array of local folders. All assets placed in these folders are converted into web services - example file types could be png, svg, mp3, mp4, excel - anything really.",
|
||||
"default": [],
|
||||
"examples": [["/myassets"]]
|
||||
},
|
||||
"streamLogo": {
|
||||
"$id": "#/properties/streamConfig/properties/streamLogo",
|
||||
"type": "string",
|
||||
"title": "Icon shown in AppStream (sasjs/server)",
|
||||
"description": "Provide the location of a square image, under the webSourcePath. Used as the display icon on the appStream page.",
|
||||
"default": "logo.png",
|
||||
"examples": ["logo.png", "favicon.ico"]
|
||||
},
|
||||
"streamWeb": {
|
||||
"$id": "#/properties/streamConfig/properties/streamWeb",
|
||||
"type": "boolean",
|
||||
"title": "The streamConfig streamWeb flag",
|
||||
"description": "When set to `true`, frontend files saved in the `webSourcePath` will be converted to streaming services in the `streamWebFolder` in SAS.",
|
||||
"default": false,
|
||||
"examples": [true]
|
||||
},
|
||||
"streamWebFolder": {
|
||||
"$id": "#/properties/streamConfig/properties/streamWebFolder",
|
||||
"type": "string",
|
||||
"title": "The streamConfig streamWebFolder",
|
||||
"description": "This is the target SAS folder (relative to the appLoc) where the compiled web assets will be created.",
|
||||
"default": "webv"
|
||||
},
|
||||
"webSourcePath": {
|
||||
"$id": "#/properties/streamConfig/properties/webSourcePath",
|
||||
"type": "string",
|
||||
"title": "The webSourcePath schema",
|
||||
"description": "Active when `streamConfig` is `true`. Is the source (or build, or dist) LOCAL folder, relative to the `sasjs` folder, which contains the frontend to be deployed. All assets (PNG, JS, CSS, HTML etc) are taken from here and converted to streaming services in `streamWebFolder`. In Viya and SAS 9, any relative URLS will be modified such that the links still work and the assets still load.",
|
||||
"default": "dist",
|
||||
"examples": ["dist", "build"]
|
||||
},
|
||||
"streamServiceName": {
|
||||
"$id": "#/properties/streamConfig/properties/streamServiceName",
|
||||
"type": "string",
|
||||
"title": "streamServiceName Schema",
|
||||
"description": "The name of the service containing the `index.html` for a streaming web app. Defaults to `clickme` and is always deployed under the appLoc/services SAS Folder.",
|
||||
"default": "clickme.sas"
|
||||
}
|
||||
}
|
||||
},
|
||||
"testConfig": {
|
||||
"$id": "#/properties/testConfig",
|
||||
"type": "object",
|
||||
"title": "testConfig",
|
||||
"description": "Create tests for Macros, Services & Jobs by simply adding a '.test.sas' extension. ",
|
||||
"default": {
|
||||
"initProgram": "sasjs/tests/testinit.sas",
|
||||
"termProgram": "sasjs/tests/testterm.sas",
|
||||
"macroVars": {
|
||||
"testVar": "testValue"
|
||||
},
|
||||
"testSetUp": "sasjs/tests/testsetup.sas",
|
||||
"testTearDown": "sasjs/tests/testteardown.sas"
|
||||
},
|
||||
"properties": {
|
||||
"initProgram": {
|
||||
"$id": "#/properties/testConfig/properties/initProgram",
|
||||
"type": "string",
|
||||
"title": "The jobConfig initProgram",
|
||||
"description": "The testConfig `initProgram` is a local .sas file that is inserted at the start of every Test (after compiled macros and any `macroVars`, and before the Test itself). ",
|
||||
"default": "",
|
||||
"examples": ["sasjs/tests/testinit.sas"]
|
||||
},
|
||||
"termProgram": {
|
||||
"$id": "#/properties/testConfig/properties/termProgram",
|
||||
"type": "string",
|
||||
"title": "The jobConfig termProgram",
|
||||
"description": "The testConfig termProgram is inserted at the end of every Test as part of `sasjs compile`.",
|
||||
"default": "",
|
||||
"examples": ["jobs/jobterm.sas"]
|
||||
},
|
||||
"macroVars": {
|
||||
"$id": "#/properties/testConfig/properties/macroVars",
|
||||
"type": "object",
|
||||
"title": "The `jobConfig` macroVars",
|
||||
"description": "This object allows `sasjs compile` to insert specific macro variables at the start of every Test. In this case, the code generated would be:\n```\n%let mac1=value;\n%let mac2=42;\n```",
|
||||
"default": {},
|
||||
"examples": [
|
||||
{
|
||||
"mac1": "value",
|
||||
"mac2": "42"
|
||||
}
|
||||
]
|
||||
},
|
||||
"testSetUp": {
|
||||
"$id": "#/properties/testConfig/properties/testsetup",
|
||||
"type": "string",
|
||||
"title": "The jobConfig testSetUp",
|
||||
"description": "This program is the first to execute as part of 'sasjs test'. It does not contain the testInit, testTerm or macroVariables. It IS compiled.",
|
||||
"default": "sasjs/tests/testsetup.sas",
|
||||
"examples": ["sasjs/tests/testsetup.sas"]
|
||||
},
|
||||
"testTearDown": {
|
||||
"$id": "#/properties/testConfig/properties/testTearDown",
|
||||
"type": "string",
|
||||
"title": "The jobConfig testTearDown",
|
||||
"description": "The last program to execute as part of 'sasjs test'.",
|
||||
"default": "sasjs/tests/testteardown.sas",
|
||||
"examples": ["sasjs/tests/testteardown.sas"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"macroFolders": {
|
||||
"$id": "#/properties/macroFolders",
|
||||
"type": "array",
|
||||
"title": "The macroFolders array",
|
||||
"description": "These local folders are searched for SAS Macros when running `sasjs compile`. Folders are relative to the `sasjs/sasjsconfig.json` file.",
|
||||
"default": [],
|
||||
"examples": [["macros", "../../more_macros"]]
|
||||
},
|
||||
"programFolders": {
|
||||
"$id": "#/properties/programFolders",
|
||||
"type": "array",
|
||||
"title": "The programFolders array",
|
||||
"description": "These local folders are searched for SAS Programs when running `sasjs compile`. Folders are relative to the `sasjs/sasjsconfig.json` file.",
|
||||
"default": [],
|
||||
"examples": [["programs", "../../more_programs"]]
|
||||
},
|
||||
"syncFolder": {
|
||||
"$id": "#/properties/syncFolder",
|
||||
"type": "string",
|
||||
"title": "Sync Folder",
|
||||
"description": "The contents of this folder are simply copied to the sasjsbuild directory AFTER the rest of the project is compiled. Useful for synchronising random / generic content with SAS logical folders.",
|
||||
"default": "sasjs/static_files"
|
||||
},
|
||||
"syncDirectories": {
|
||||
"$id": "#/properties/syncDirectories",
|
||||
"type": "array",
|
||||
"title": "syncDirectories",
|
||||
"description": "Maps the local filesystem to remote (SAS) physical directories.",
|
||||
"examples": [
|
||||
[
|
||||
{
|
||||
"local": "C:\\temp\\local\\fs1",
|
||||
"remote": "/opt/data/fs1"
|
||||
},
|
||||
{
|
||||
"local": "C:\\temp\\elsewhere",
|
||||
"remote": "/opt/somewhere"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"targets": {
|
||||
"$id": "#/properties/targets",
|
||||
"type": "array",
|
||||
"title": "The targets array",
|
||||
"description": "A target is an alias for a deployment location, and includes at a minimum, the `serverUrl`, `serverType` and `appLoc`. This array allows multiple targets to be defined (eg dev / test / prod). Any properties defined here will override same-named properties in the sasjsconfig root.",
|
||||
"default": [],
|
||||
"examples": [
|
||||
[
|
||||
{
|
||||
"name": "sas9target",
|
||||
"serverType": "SAS9",
|
||||
"serverUrl": "https://mysas9server",
|
||||
"appLoc": "/Shared Folders/myApp"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"name": "viya",
|
||||
"serverType": "SASVIYA",
|
||||
"serverUrl": "https://sas.sasjs.com",
|
||||
"appLoc": "/Public/app",
|
||||
"contextName": "SAS Job Execution compute context",
|
||||
"buildConfig": {
|
||||
"buildOutputFileName": "myviyadeploy.sas",
|
||||
"initProgram": "build/buildinitviya.sas",
|
||||
"termProgram": "targets/viya/viyabuildterm.sas",
|
||||
"macroVars": {
|
||||
"name": "viyavalue",
|
||||
"extravar": "this too"
|
||||
}
|
||||
},
|
||||
"deployConfig": {
|
||||
"deployServicePack": true,
|
||||
"deployScripts": ["sasjsbuild/myviyadeploy.sas"]
|
||||
},
|
||||
"serviceConfig": {
|
||||
"serviceFolders": ["targets/viya/services/admin"],
|
||||
"initProgram": "build/serviceinit.sas",
|
||||
"termProgram": "build/serviceinit.sas",
|
||||
"macroVars": {
|
||||
"name": "viyavalue",
|
||||
"extravar": "this too"
|
||||
}
|
||||
},
|
||||
"streamConfig": {
|
||||
"assetPaths": [],
|
||||
"streamWeb": false,
|
||||
"streamWebFolder": "webv",
|
||||
"webSourcePath": "dist"
|
||||
},
|
||||
"testConfig": {
|
||||
"initProgram": "sasjs/tests/testinit.sas",
|
||||
"termProgram": "sasjs/tests/testterm.sas",
|
||||
"macroVars": {
|
||||
"testVar": "testValue"
|
||||
},
|
||||
"testSetUp": "sasjs/tests/testsetup.sas",
|
||||
"testTearDown": "sasjs/tests/testteardown.sas"
|
||||
},
|
||||
"macroFolders": ["targets/viya/macros"]
|
||||
},
|
||||
{
|
||||
"name": "sas9",
|
||||
"serverType": "SAS9",
|
||||
"serverUrl": "https://sas.sasjs.com:7980",
|
||||
"appLoc": "/User Folders/&sysuserid/My Folder",
|
||||
"serverName": "Foundation",
|
||||
"repositoryName": "SASApp",
|
||||
"buildConfig": {
|
||||
"buildOutputFileName": "mysas9deploy.sas",
|
||||
"initProgram": "",
|
||||
"termProgram": "",
|
||||
"macroVars": {}
|
||||
},
|
||||
"deployConfig": {
|
||||
"deployScripts": ["build/deploysas9.sh"],
|
||||
"deployServicePack": false
|
||||
},
|
||||
"serviceConfig": {
|
||||
"serviceFolders": ["targets/sas9/services/admin"],
|
||||
"initProgram": "",
|
||||
"termProgram": "build/servicetermother.sas",
|
||||
"macroVars": {}
|
||||
},
|
||||
"streamConfig": {
|
||||
"assetPaths": [],
|
||||
"streamWeb": false,
|
||||
"streamWebFolder": "web9",
|
||||
"webSourcePath": "dist"
|
||||
},
|
||||
"testConfig": {
|
||||
"initProgram": "sasjs/tests/testinit.sas",
|
||||
"termProgram": "sasjs/tests/testterm.sas",
|
||||
"macroVars": {
|
||||
"testVar": "testValue"
|
||||
},
|
||||
"testSetUp": "sasjs/tests/testsetup.sas",
|
||||
"testTearDown": "sasjs/tests/testteardown.sas"
|
||||
},
|
||||
"macroFolders": ["targets/sas9/macros"],
|
||||
"programFolders": []
|
||||
}
|
||||
]
|
||||
],
|
||||
"items": {
|
||||
"$id": "#/properties/targets/items",
|
||||
"anyOf": [
|
||||
{
|
||||
"$id": "#/properties/targets/items/anyOf/0",
|
||||
"type": "object",
|
||||
"title": "SASjs Targets",
|
||||
"description": "A target provides the configuration specific to a particular deployment, eg DEV / TEST, or SAS9 / SASVIYA.",
|
||||
"default": {},
|
||||
"examples": [
|
||||
{
|
||||
"name": "viya",
|
||||
"serverType": "SASVIYA",
|
||||
"serverUrl": "https://sas.sasjs.com",
|
||||
"appLoc": "/Public/app",
|
||||
"contextName": "SAS Job Execution compute context",
|
||||
"buildConfig": {
|
||||
"buildOutputFileName": "myviyadeploy.sas",
|
||||
"initProgram": "build/buildinitviya.sas",
|
||||
"termProgram": "targets/viya/viyabuildterm.sas",
|
||||
"macroVars": {
|
||||
"name": "viyavalue",
|
||||
"extravar": "this too"
|
||||
}
|
||||
},
|
||||
"deployConfig": {
|
||||
"deployServicePack": true,
|
||||
"deployScripts": ["sasjsbuild/myviyadeploy.sas"]
|
||||
},
|
||||
"serviceConfig": {
|
||||
"serviceFolders": ["targets/viya/services/admin"],
|
||||
"initProgram": "build/serviceinit.sas",
|
||||
"termProgram": "build/serviceinit.sas",
|
||||
"macroVars": {
|
||||
"name": "viyavalue",
|
||||
"extravar": "this too"
|
||||
}
|
||||
},
|
||||
"jobConfig": {
|
||||
"jobFolders": [],
|
||||
"initProgram": "",
|
||||
"termProgram": "",
|
||||
"macroVars": {}
|
||||
},
|
||||
"streamConfig": {
|
||||
"assetPaths": [],
|
||||
"streamWeb": false,
|
||||
"streamWebFolder": "webv",
|
||||
"webSourcePath": "dist"
|
||||
},
|
||||
"testConfig": {
|
||||
"initProgram": "sasjs/tests/testinit.sas",
|
||||
"termProgram": "sasjs/tests/testterm.sas",
|
||||
"macroVars": {
|
||||
"testVar": "testValue"
|
||||
},
|
||||
"testSetUp": "sasjs/tests/testsetup.sas",
|
||||
"testTearDown": "sasjs/tests/testteardown.sas"
|
||||
},
|
||||
"macroFolders": ["targets/viya/macros"],
|
||||
"programFolders": []
|
||||
}
|
||||
],
|
||||
"required": ["name", "serverType", "appLoc"],
|
||||
"properties": {
|
||||
"name": {
|
||||
"$id": "#/properties/targets/items/anyOf/0/properties/name",
|
||||
"type": "string",
|
||||
"title": "Target name property",
|
||||
"description": "A target name can only contain alphanumeric characters and dashes. It cannot contain spaces. It is used as the alias when referencing the target using the `-t` attribute in many of the SASjs commands.",
|
||||
"default": "",
|
||||
"examples": ["viya"]
|
||||
},
|
||||
"serverType": {
|
||||
"$id": "#/properties/targets/items/anyOf/0/properties/serverType",
|
||||
"type": "string",
|
||||
"title": "The Target serverType",
|
||||
"description": "The serverType can be either SAS9, SASVIYA or SASJS.",
|
||||
"default": "",
|
||||
"examples": ["SASVIYA"]
|
||||
},
|
||||
"appLoc": {
|
||||
"$id": "#/properties/targets/items/anyOf/0/properties/appLoc",
|
||||
"type": "string",
|
||||
"title": "The Target appLoc",
|
||||
"description": "The appLoc provides the root SAS folder location under which all jobs and services are deployed and executed. The SAS folder could be metadata in SAS 9, or SAS Drive in Viya. ",
|
||||
"default": "",
|
||||
"examples": ["/Public/app"]
|
||||
},
|
||||
"binaryFolders": {
|
||||
"$ref": "#/properties/binaryFolders"
|
||||
},
|
||||
"buildConfig": {
|
||||
"$ref": "#/properties/buildConfig"
|
||||
},
|
||||
"contextName": {
|
||||
"$id": "#/properties/targets/items/anyOf/0/properties/contextName",
|
||||
"type": "string",
|
||||
"title": "The Target contextName",
|
||||
"description": "The name of the compute context used to execute SAS code. The context determines the way in which the SAS session is spawned (eg user credentials, autoexec code, system options etc).\nContexts can be created / modified / deleted using the `sasjs context` command.",
|
||||
"default": "SAS Job Execution compute context",
|
||||
"examples": ["SAS Job Execution compute context"]
|
||||
},
|
||||
"deployConfig": {
|
||||
"$ref": "#/properties/deployConfig"
|
||||
},
|
||||
"httpsAgentOptions": {
|
||||
"$ref": "#/properties/httpsAgentOptions"
|
||||
},
|
||||
"serverUrl": {
|
||||
"$id": "#/properties/targets/items/anyOf/0/properties/serverUrl",
|
||||
"type": "string",
|
||||
"title": "The Target serverUrl",
|
||||
"description": "The serverUrl is the location to which the app is deployed, and against which any server based operations are performed. If SAS is served from a particular port, that port should also be included here.",
|
||||
"default": "",
|
||||
"examples": [
|
||||
"https://sas.sasjs.com",
|
||||
"https://sas.sasjs.com:8080"
|
||||
]
|
||||
},
|
||||
"serviceConfig": {
|
||||
"$ref": "#/properties/serviceConfig"
|
||||
},
|
||||
"jobConfig": {
|
||||
"$ref": "#/properties/jobConfig"
|
||||
},
|
||||
"docConfig": {
|
||||
"$ref": "#/properties/docConfig"
|
||||
},
|
||||
"streamConfig": {
|
||||
"$ref": "#/properties/streamConfig"
|
||||
},
|
||||
"syncDirectories": {
|
||||
"$ref": "#/properties/syncDirectories"
|
||||
},
|
||||
"syncFolder": {
|
||||
"$ref": "#/properties/syncFolder"
|
||||
},
|
||||
"testConfig": {
|
||||
"$ref": "#/properties/testConfig"
|
||||
},
|
||||
"macroFolders": {
|
||||
"$ref": "#/properties/macroFolders"
|
||||
},
|
||||
"programFolders": {
|
||||
"$ref": "#/properties/programFolders"
|
||||
},
|
||||
"sasjsBuildFolder": {
|
||||
"$ref": "#properties/sasjsBuildFolder"
|
||||
},
|
||||
"sasjsResultsFolder": {
|
||||
"$ref": "#properties/sasjsResultsFolder"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
name: sasjs-server
|
||||
description: Installing, configuring, and running @sasjs/server — the open-source NodeJS wrapper around the SAS binary that provides a REST API, filesystem (SASjs Drive), Stored Program execution, and web app streaming. Covers desktop vs server modes, runtimes (SAS/JS/Python/R), env vars, auth (tokens, LDAP), and mock servers. Use when deploying, troubleshooting, or developing against sasjs/server.
|
||||
---
|
||||
|
||||
# @sasjs/server
|
||||
|
||||
SASjs Server is an open-source NodeJS wrapper for calling the SAS binary executable. It runs on a real SAS server or a local desktop and provides:
|
||||
|
||||
- A filesystem (SASjs Drive) for storing SAS programs and content
|
||||
- Execution of Stored Programs from a URL (equivalent to SAS 9 Stored Processes / Viya Jobs)
|
||||
- Web app streaming (serve frontend apps straight from SAS content)
|
||||
- A REST API with Swagger docs
|
||||
- Portability: apps built for SASjs Server deploy unchanged to SAS 9 / Viya via @sasjs/cli
|
||||
|
||||
## Modes
|
||||
|
||||
- **Desktop mode** (`MODE=desktop`, default): single-user, no authentication, no database. CORS enabled by default.
|
||||
- **Server mode** (`MODE=server`): multi-user with authentication, requires a database (`DB_CONNECT`, `DB_TYPE=mongodb|cosmos_mongodb`). CORS disabled by default — configure `WHITELIST` if enabling.
|
||||
|
||||
## Installation
|
||||
|
||||
Download the relevant zip from GitHub releases and run the packaged executable (`api-linux`, etc.):
|
||||
|
||||
```bash
|
||||
curl -L https://github.com/sasjs/server/releases/latest/download/linux.zip > linux.zip
|
||||
unzip linux.zip && ./api-linux
|
||||
```
|
||||
|
||||
On first run it prompts (unless set as env vars) for the SAS executable path and the filesystem location for Stored Programs/temp files. Docker is also supported (`DockerfileApi`, docker-compose files in the repo).
|
||||
|
||||
## Configuration via environment variables
|
||||
|
||||
Set in `/etc/environment`, exported, prepended to the command, or in a `.env` file alongside the executable. Key variables:
|
||||
|
||||
| Variable | Purpose |
|
||||
|---|---|
|
||||
| `MODE` | `desktop` (default) or `server` |
|
||||
| `SAS_PATH` | Path to `sas.exe` / `sas.sh` |
|
||||
| `RUN_TIMES` | Comma-separated runtime priority, e.g. `sas,js,py` — options: `sas`, `js`, `py`, `r`. Each needs its path: `SAS_PATH`, `NODE_PATH`, `PYTHON_PATH`, `R_PATH` |
|
||||
| `SASJS_ROOT` | Working directory: SAS WORK, staged files, drive, config |
|
||||
| `DRIVE_LOCATION` | Location for files, sasjs packages, `appStreamConfig.json` |
|
||||
| `PROTOCOL` / `PORT` | `http` (default) or `https` (needs `PRIVATE_KEY`, `CERT_CHAIN`, optional `CA_ROOT`); default port 5000 |
|
||||
| `SAS_OPTIONS` / `SASV9_OPTIONS` | Extra SAS system options auto-applied to sessions (Windows vs Unix), e.g. `-NOXCMD` |
|
||||
| `DB_CONNECT` / `DB_TYPE` | MongoDB connection string / type — required for server mode |
|
||||
| `AUTH_PROVIDERS` + `LDAP_*` | LDAP auth: `LDAP_URL`, `LDAP_BIND_DN`, `LDAP_BIND_PASSWORD`, `LDAP_USERS_BASE_DN`, `LDAP_GROUPS_BASE_DN` |
|
||||
| `CORS` / `WHITELIST` | CORS is only applied when `CORS=enable`, and only origins in `WHITELIST` (space-separated) receive `Access-Control-Allow-Origin` — an empty whitelist means NO cross-origin calls work |
|
||||
| `MOCK_SERVERTYPE` / `STATIC_MOCK_LOCATION` | Emulate `sas9`/`sasviya` API responses for frontend testing against a sasjs server (static canned files only — no logic) |
|
||||
|
||||
## Developing against the API
|
||||
|
||||
- Server type for @sasjs/adapter / CLI targets is `SASJS` (`serverType: 'SASJS'`); auth is token-based.
|
||||
- The REST API is self-documented via Swagger on the running instance.
|
||||
- Server-side execution uses `ms_*` macros from @sasjs/core (e.g. `ms_createfile`, `ms_adduser2group`) — services/jobs deployed by the CLI work as on other platforms.
|
||||
- Repo layout (for contributors): `api/` (Express/TypeScript backend: controllers, routes, middlewares, model), `web/` (frontend), `restClient/` (REST examples), `mongo-seed/` (server-mode DB seed).
|
||||
|
||||
## Mock services with the JS runtime (no SAS required)
|
||||
|
||||
With `RUN_TIMES=js` (and `NODE_PATH` set), any `.js` file on SASjs Drive is an executable Stored Program — this is how react-seed-app / Data Controller provide **mock backends** for frontend development. Desktop mode (`MODE=desktop`) has no auth, which makes local mocking trivial.
|
||||
|
||||
Writing a JS stored program (docs: https://server.sasjs.io/storedprograms/#js-programs):
|
||||
|
||||
- The runtime template predeclares `const fs = require('fs')`, `_program`, `weboutPath`, `_SASJS_TOKENFILE`, `_SASJS_WEBOUT_HEADERS`, `_SASJS_USERNAME` / `_SASJS_USERID` / `_SASJS_DISPLAYNAME`, `_METAPERSON`, `_METAUSER`, `SASJSPROCESSMODE`. **Do NOT redeclare `fs`** — `const fs = require('fs')` in your program crashes it with `Identifier 'fs' has already been declared`.
|
||||
- Output: assign a JSON **string** to `_webout` (e.g. `_webout = JSON.stringify({...})`); it is written back only if non-empty. `console.log()` output is returned in the response `log` (like a SAS log). Custom response headers can be written as lines to the `_SASJS_WEBOUT_HEADERS` file.
|
||||
- Mimic real services by including the standard SASjs automatic fields in the JSON: `_PROGRAM` (from `_program`), `SYSDATE` / `SYSTIME` (format `DDMMMYY` / `HH:mm`), `_METAUSER`, `SASJSPROCESSMODE`.
|
||||
- URL/body parameters arrive as `const <name> = \`<value>\`` strings.
|
||||
- Input tables (the `sasjs_tables` mechanism) arrive **either** as an inline CSV const **or** — when the adapter sends multipart — as an uploaded `<name>.csv` file in the session folder, referenced by generated module-scope consts (handle BOTH):
|
||||
- `_WEBIN_FILE_COUNT` (always created), `_WEBIN_NAME<n>` (table/field name), `_WEBIN_FILENAME<n>` (original filename), `_WEBIN_FILEREF<n>` (file **contents**, a Buffer from `fs.readFileSync` — call `.toString('utf8')`)
|
||||
- these consts are **not on `globalThis`** — look them up with `typeof` guards or direct `eval()` in module scope (server-side JS, no CSP)
|
||||
- adapter CSV quirks: header row is **space-separated** `name:format.` entries (e.g. `rootdir:$char256.`) — strip the `:format` suffix; lines end CRLF; values containing special characters are wrapped in double quotes with `""` escaping
|
||||
- Adapter response shape: `sasjs.request()` resolves with the webout JSON **already unwrapped** — output tables are arrays of row objects directly on the response (`res.mytable[0].COL`). A table named `result` is perfectly fine (`res.result` is then that array); do NOT add your own `res.result`-unwrapping layer, it breaks exactly that case.
|
||||
- Third-party npm packages are NOT resolvable at runtime — bundle the service first (e.g. `npx webpack --mode none --target node --entry <file> --output-path sasjsbuild/... --output-filename <name>.js`), then `sasjs build` / `sasjs deploy`.
|
||||
|
||||
Deploying mocks:
|
||||
|
||||
- `sasjs fs sync` does NOT work on a JS-only server (it generates and executes SAS code to hash remote files). Upload files directly via the Drive API instead: `DELETE` then `POST /SASjsApi/drive/file?_filePath=<appLoc>/services/<folder>/<name>.js` (multipart `file` field). In desktop mode no auth headers are needed; in server mode read the `Authorization` header line from `_SASJS_TOKENFILE`.
|
||||
- Mocks can be stateful with the predeclared `fs`. Prefer real locations over `/tmp`: the SASjs Drive root is derivable from `weboutPath` (`<root>/sessions/<id>/webout.txt` → `path.resolve(weboutPath, '..', '..', '..', 'drive')`), and a mock `configure`-style service can treat a configured folder as a real local path (the server IS local). `require('path')` and other core modules work (only `fs` is predeclared).
|
||||
- A JS program can even call the server's own REST API (`http://127.0.0.1:$PORT/SASjsApi/...`) — e.g. to rewrite a streamed `index.html` on the Drive (`GET` + `PATCH /SASjsApi/drive/file`).
|
||||
|
||||
Gotchas:
|
||||
|
||||
- The packaged binaries (`api-linux` etc.) reject some globally-exported `NODE_OPTIONS` (e.g. `--network-family-autoselection`) — start with `NODE_OPTIONS="" ./api-linux`.
|
||||
- AppStream URLs redirect to a trailing slash (`/AppStream/MyApp` → 301 → `/AppStream/MyApp/`) — test/automation scripts should use the trailing-slash URL directly.
|
||||
@@ -7,7 +7,7 @@ Read **`CONTEXT.md`** at the repo root first - it is the domain glossary and ori
|
||||
Data Controller spans three sibling repos (usually checked out side by side):
|
||||
|
||||
- **`dc`** (this repo) - the product source (Angular client + SAS backend).
|
||||
- **`docs.datacontroller.io`** - the user-facing product documentation (MkDocs). User-facing behaviour is documented there; the `.agent/docs/` deep-dives here link out to it.
|
||||
- **`docs.datacontroller.io`** - the user-facing product documentation (MkDocs). User-facing behaviour is documented there; the `.agents/docs/` deep-dives here link out to it.
|
||||
- **`datacontroller.io`** - the marketing site, blog and feed (Gatsby).
|
||||
|
||||
## Git
|
||||
@@ -16,7 +16,7 @@ Do NOT auto-commit. Never run `git commit` (or `git push`) unless the user expli
|
||||
|
||||
## CHANGELOG and versioning
|
||||
|
||||
Do NOT edit `CHANGELOG.md` by hand, and do NOT bump the `version` in `package.json`. The release pipeline (semantic-release, in `.gitea/workflows/release.yaml`) generates both automatically from Conventional Commit messages on every push to `main`. Manual entries collide with the pipeline's release commit and are overwritten anyway. Control the changelog through your commit messages instead. See `.agent/docs/releases-and-changelog.md`.
|
||||
Do NOT edit `CHANGELOG.md` by hand, and do NOT bump the `version` in `package.json`. The release pipeline (semantic-release, in `.gitea/workflows/release.yaml`) generates both automatically from Conventional Commit messages on every push to `main`. Manual entries collide with the pipeline's release commit and are overwritten anyway. Control the changelog through your commit messages instead. See `.agents/docs/releases-and-changelog.md`.
|
||||
|
||||
## Markdown files
|
||||
|
||||
@@ -37,9 +37,11 @@ Never consider a change complete until the relevant linters pass on the files yo
|
||||
|
||||
Data Controller must run entirely locally (offline / on-prem, no internet access). The built product must never fetch assets from remote servers — no external fonts, images, scripts, stylesheets, CDN links, or remote URLs in meta tags (e.g. `og:image`, `og:url`, `itemprop="image"`). All assets must be bundled and served locally.
|
||||
|
||||
## The .agent folder
|
||||
## The .agents folder
|
||||
|
||||
Agent-related content lives in `.agent/`: technical/agent-facing documentation goes in `.agent/docs/` (not `docs/`), and skills in `.agent/skills/`. When writing explanatory or technical docs about the codebase, put them in `.agent/docs/`.
|
||||
Agent-related content lives in `.agents/`: technical/agent-facing documentation goes in `.agents/docs/` (not `docs/`), and skills in `.agents/skills/`. When writing explanatory or technical docs about the codebase, put them in `.agents/docs/`.
|
||||
|
||||
Skills installed via `npx skills add` are tracked in `skills-lock.json` and live under `.agents/skills/`. Repo-local custom skills (e.g. `dc-sas` for Data Controller SAS development, `handsontable`, `hyperformula`) are kept here too. Avoid editing npx-managed skill files directly — use `npx skills update` instead.
|
||||
|
||||
## Code comments and test names
|
||||
|
||||
|
||||
+9
-9
@@ -6,9 +6,9 @@ This file is the shared glossary and orientation for the repo. When your output
|
||||
|
||||
## Repository layout
|
||||
|
||||
- `client/` - the Angular frontend (TypeScript). Uses [Handsontable](.agent/skills/handsontable/SKILL.md) for the editable grid and [HyperFormula](.agent/skills/hyperformula/SKILL.md) for Excel-formula support. Lint with `npm run lint:check` from `client/`.
|
||||
- `client/` - the Angular frontend (TypeScript). Uses [Handsontable](.agents/skills/handsontable/SKILL.md) for the editable grid and [HyperFormula](.agents/skills/hyperformula/SKILL.md) for Excel-formula support. Lint with `npm run lint:check` from `client/`.
|
||||
- `sas/` - the SAS backend. Services, macros, hooks and tests live under `sas/sasjs/**`; `sas/sasjsbuild/` is generated output (never hand-edit). Lint `.sas` files with `sasjs lint` from `sas/`.
|
||||
- `.agent/docs/` - technical deep-dives (see below). `.agent/skills/` - task skills (handsontable, hyperformula, sas).
|
||||
- `.agents/docs/` - technical deep-dives (see below). `.agents/skills/` - task skills (handsontable, hyperformula, dc-sas for Data Controller SAS development, plus npx-installed sasjs/ sas skills).
|
||||
|
||||
## Roles
|
||||
|
||||
@@ -32,10 +32,10 @@ Admins are listed in `&mpeadmins`; admin membership bypasses Row Level Security.
|
||||
|
||||
## Load types
|
||||
|
||||
Set per table in `MPE_TABLES.LOADTYPE`. Determines the loader and how history is kept (see [.agent/docs/bitemporal-dataloader.md](.agent/docs/bitemporal-dataloader.md)):
|
||||
Set per table in `MPE_TABLES.LOADTYPE`. Determines the loader and how history is kept (see [.agents/docs/bitemporal-dataloader.md](.agents/docs/bitemporal-dataloader.md)):
|
||||
|
||||
- **UPDATE** - no history; changed records deleted and re-appended (via `%bitemporal_dataloader`).
|
||||
- **REPLACE** - no history; whole table wiped and reloaded. Implemented inline in `%mpe_targetloader`, deliberately *not* via `%bitemporal_dataloader` (see [.agent/docs/replace-load-type.md](.agent/docs/replace-load-type.md)).
|
||||
- **REPLACE** - no history; whole table wiped and reloaded. Implemented inline in `%mpe_targetloader`, deliberately *not* via `%bitemporal_dataloader` (see [.agents/docs/replace-load-type.md](.agents/docs/replace-load-type.md)).
|
||||
- **TXTEMPORAL** - SCD2-style history on technical (transaction) time.
|
||||
- **BITEMPORAL** - full two-dimensional history (business time + technical time).
|
||||
- **FORMAT_CAT** - format-catalog load (via `%mp_loadformat`).
|
||||
@@ -64,11 +64,11 @@ Selectbox seed values for these tables are defined in `sas/sasjs/macros/mpe_make
|
||||
- **HARDREGEX** - submission-blocking regex; failing cells are painted red and cannot be submitted.
|
||||
- **SOFTREGEX** - display-only warning regex; failing cells are painted yellow but submission is allowed.
|
||||
|
||||
Regex rule values are authored in SAS PRX form `/pattern/flags` and limited to 128 chars. See [.agent/docs/regex-validations.md](.agent/docs/regex-validations.md).
|
||||
Regex rule values are authored in SAS PRX form `/pattern/flags` and limited to 128 chars. See [.agents/docs/regex-validations.md](.agents/docs/regex-validations.md).
|
||||
|
||||
## Security
|
||||
|
||||
- **Row Level Security (RLS)** - server-side `WHERE`-clause generation via `%mpe_filtermaster` / `%mp_filtergenerate`; no data leaves SAS without passing the filter. Works on any engine because it's expressed as a standard SAS `WHERE`. On write, the *inverse* filter is applied to reject out-of-scope rows. See [.agent/docs/row-level-security.md](.agent/docs/row-level-security.md).
|
||||
- **Row Level Security (RLS)** - server-side `WHERE`-clause generation via `%mpe_filtermaster` / `%mp_filtergenerate`; no data leaves SAS without passing the filter. Works on any engine because it's expressed as a standard SAS `WHERE`. On write, the *inverse* filter is applied to reject out-of-scope rows. See [.agents/docs/row-level-security.md](.agents/docs/row-level-security.md).
|
||||
- **Column Level Security (CLS)** - restricts visibility/editability at column level.
|
||||
|
||||
## Key macros / services
|
||||
@@ -79,7 +79,7 @@ Regex rule values are authored in SAS PRX form `/pattern/flags` and limited to 1
|
||||
|
||||
## Testing
|
||||
|
||||
Backend tests run with the sasjs CLI from `sas/`. `npm run 4gl` compiles+deploys+seeds then `sasjs test -t 4gl` runs tests. Assertions use `%mp_assert()`. See [.agent/docs/testing.md](.agent/docs/testing.md).
|
||||
Backend tests run with the sasjs CLI from `sas/`. `npm run 4gl` compiles+deploys+seeds then `sasjs test -t 4gl` runs tests. Assertions use `%mp_assert()`. See [.agents/docs/testing.md](.agents/docs/testing.md).
|
||||
|
||||
## Conventions
|
||||
|
||||
@@ -88,6 +88,6 @@ Backend tests run with the sasjs CLI from `sas/`. `npm run 4gl` compiles+deploys
|
||||
- No external assets - everything must be bundled and served locally.
|
||||
- Comments and test names must be self-contained (no "original bug", "this PR", etc.); the only exception is a literal issue-tracker link.
|
||||
- Do not auto-commit or push.
|
||||
- Never edit `CHANGELOG.md` or bump `package.json` version by hand - the release pipeline generates both from Conventional Commit messages. See [.agent/docs/releases-and-changelog.md](.agent/docs/releases-and-changelog.md).
|
||||
- Never edit `CHANGELOG.md` or bump `package.json` version by hand - the release pipeline generates both from Conventional Commit messages. See [.agents/docs/releases-and-changelog.md](.agents/docs/releases-and-changelog.md).
|
||||
|
||||
See `AGENTS.md` for the full set of enforced rules (git, linting, no-wrap, no-external-assets, `.agent/` layout).
|
||||
See `AGENTS.md` for the full set of enforced rules (git, linting, no-wrap, no-external-assets, `.agents/` layout).
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"contextName": "Compute Reusable"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"fromjs": [
|
||||
{
|
||||
"ADMIN": "viyagroup18",
|
||||
"DCPATH": "/export/pvs/sasdata/sasbatch",
|
||||
"_CONTEXTNAME": "Compute Reusable"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -21,6 +21,7 @@
|
||||
"serverdata-mihajlo": "sasjs request services/admin/makedata -d deploy/makeDataServer.json -l sasjsresults/makedata_server.log -o sasjsresults/makedata_server.json -t server-mihajlo",
|
||||
"serverdata": "sasjs request services/admin/makedata -d deploy/makeDataServer.json -l sasjsresults/makedata_server.log -o sasjsresults/makedata_server.json -t server",
|
||||
"nextviya": "sasjs cb -t nextviya && sasjs run sasjsbuild/viya.sas -t nextviya",
|
||||
"nextviyamakedata": "sasjs request services/admin/makedata -d deploy/makeDataNextViya.json -c deploy/configNextViya.json -l sasjsresults/makedata_nextviya.log -o sasjsresults/makedata_nextviya.json -t nextviya",
|
||||
"viya": "npm run viyacbd && sasjs test",
|
||||
"viyacbd": "sasjs folder delete /Public/app/viya && sasjs cbd -t viya && npm run viyadata",
|
||||
"viyadata": "sasjs request services/admin/makedata -d deploy/makeDataViya.json -l sasjsresults/makedata.log -o sasjsresults/output.json -t viya",
|
||||
|
||||
@@ -2146,6 +2146,16 @@ insert into &lib..MPE_VALIDATIONS set
|
||||
,some_time=412
|
||||
,some_shortnum=8
|
||||
,some_bestnum=44;
|
||||
insert into &lib..mpe_x_test
|
||||
set primary_key_field=5
|
||||
,some_char='= testing the formula situation'
|
||||
,some_dropdown='Option 1'
|
||||
,some_num=42
|
||||
,some_date=42
|
||||
,some_datetime=42
|
||||
,some_time=42
|
||||
,some_shortnum=8
|
||||
,some_bestnum=44;
|
||||
%do x=10 %to 500;
|
||||
insert into &lib..mpe_x_test
|
||||
set primary_key_field=10&x
|
||||
|
||||
@@ -185,7 +185,7 @@
|
||||
"streamLogo": "favicon.ico",
|
||||
"assetPaths": []
|
||||
},
|
||||
"contextName": "SAS Studio compute context"
|
||||
"contextName": "Compute Reusable"
|
||||
},
|
||||
{
|
||||
"name": "vtest",
|
||||
@@ -522,4 +522,4 @@
|
||||
"contextName": "SAS Job Execution compute context"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -25,17 +25,19 @@
|
||||
**/
|
||||
|
||||
|
||||
%global dcpath ADMIN ;
|
||||
%global dcpath ADMIN _contextname ;
|
||||
|
||||
%webout(FETCH)
|
||||
|
||||
/* enable vars to be passed as url params */
|
||||
%let exist=%sysfunc(exist(work.fromjs));
|
||||
%let inds=%sysfunc(ifc(&exist=1,fromjs,_null_));
|
||||
|
||||
data _null_;
|
||||
set &inds;
|
||||
call symputx('dcpath',dcpath);
|
||||
call symputx('ADMIN',ADMIN);
|
||||
call symputx('_contextname',_contextname);
|
||||
run;
|
||||
|
||||
|
||||
@@ -218,57 +220,53 @@ run;
|
||||
(the same macro used to stream the frontend at deploy time) rather than
|
||||
updated in place with a filesrvc fileref - only the Files service POST
|
||||
assigns the correct mime type (text/html / file_html), without which the
|
||||
frontend will not stream. */
|
||||
frontend will not stream. This service is always invoked from a URL so
|
||||
the _contextname parameter is always present. */
|
||||
%global _contextname;
|
||||
%if %symexist(_contextname) and %length(%superq(_contextname))>0 %then %do;
|
||||
%local dchtml_in dchtml_out;
|
||||
%let dchtml_in=%mf_getuniquefileref();
|
||||
%let dchtml_out=%mf_getuniquefileref();
|
||||
%let dchtml_in=%mf_getuniquefileref();
|
||||
%let dchtml_out=%mf_getuniquefileref();
|
||||
|
||||
/* Escape regex metacharacters (\ and $) in the context name so they are
|
||||
treated as literals when embedded in the prxchange replacement string.
|
||||
In a Perl regex replacement, \ introduces a backreference and $ introduces
|
||||
variable interpolation; prefixing each with \ quotes them as literals. */
|
||||
%local _contextname_escaped;
|
||||
data _null_;
|
||||
length _ctxname $256 _ctxesc $512;
|
||||
_ctxname=symget('_contextname');
|
||||
_ctxesc=tranwrd(tranwrd(_ctxname,'\','\\'),'$','\$');
|
||||
call symputx('_contextname_escaped',_ctxesc);
|
||||
run;
|
||||
/* Escape regex metacharacters (\ and $) in the context name so they are
|
||||
treated as literals when embedded in the prxchange replacement string.
|
||||
In a Perl regex replacement, \ introduces a backreference and $ introduces
|
||||
variable interpolation; prefixing each with \ quotes them as literals. */
|
||||
data _null_;
|
||||
length _ctxname $256 _ctxesc $512;
|
||||
_ctxname=symget('_contextname');
|
||||
_ctxesc=tranwrd(tranwrd(_ctxname,'\','\\'),'$','\$');
|
||||
call symputx('_contextname_escaped',_ctxesc);
|
||||
run;
|
||||
|
||||
/* read the existing frontend */
|
||||
filename &dchtml_in filesrvc folderpath="&root" filename="DC.html"
|
||||
lrecl=32767;
|
||||
%mp_abort(iftrue= (&syscc ne 0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Unable to open DC.html in &root)
|
||||
)
|
||||
/* read the existing frontend */
|
||||
filename &dchtml_in filesrvc folderpath="&root" filename="DC.html"
|
||||
lrecl=32767;
|
||||
%mp_abort(iftrue= (&syscc ne 0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Unable to open DC.html in &root)
|
||||
)
|
||||
|
||||
/* write the updated html to a temporary file */
|
||||
data _null_;
|
||||
file &dchtml_out lrecl=32767;
|
||||
infile &dchtml_in lrecl=32767 truncover;
|
||||
input;
|
||||
length line $32767;
|
||||
line=prxchange(cats('s|contextname="[^"]*"|contextname="'
|
||||
,symget('_contextname_escaped')
|
||||
,'"|'
|
||||
),-1,_infile_);
|
||||
put line;
|
||||
run;
|
||||
filename &dchtml_in clear;
|
||||
/* write the updated html to a temporary file */
|
||||
data _null_;
|
||||
file &dchtml_out lrecl=32767;
|
||||
infile &dchtml_in lrecl=32767 truncover;
|
||||
input;
|
||||
length line $32767;
|
||||
line=prxchange(cats('s|contextname="[^"]*"|contextname="'
|
||||
,symget('_contextname_escaped')
|
||||
,'"|'
|
||||
),-1,_infile_);
|
||||
put line;
|
||||
run;
|
||||
filename &dchtml_in clear;
|
||||
|
||||
%mp_abort(iftrue= (&syscc ne 0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Unable to update compute context in DC.html)
|
||||
)
|
||||
%mp_abort(iftrue= (&syscc ne 0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Unable to update compute context in DC.html)
|
||||
)
|
||||
|
||||
/* re-create DC.html via the Files service to retain the html mime type */
|
||||
%mv_createfile(path=&root, name=DC.html, inref=&dchtml_out)
|
||||
filename &dchtml_out clear;
|
||||
%end;
|
||||
%else %put NOTE: &_program: _contextname not provided, DC.html not updated;
|
||||
/* re-create DC.html via the Files service to retain the html mime type */
|
||||
%mv_createfile(path=&root, name=DC.html, inref=&dchtml_out)
|
||||
filename &dchtml_out clear;
|
||||
|
||||
|
||||
%webout(OPEN)
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"version": 1,
|
||||
"skills": {
|
||||
"sas": {
|
||||
"source": "sasjs/skills",
|
||||
"sourceType": "github",
|
||||
"skillPath": "sas/SKILL.md",
|
||||
"computedHash": "7b2f3dfb807618a8f0c07c0c0972733b649093020ad3da67f5550ee6ccb68dc0"
|
||||
},
|
||||
"sasjs-adapter": {
|
||||
"source": "sasjs/skills",
|
||||
"sourceType": "github",
|
||||
"skillPath": "sasjs-adapter/SKILL.md",
|
||||
"computedHash": "b23e90aba8f19f5acc0de8c17c9ce6904eaadac2c27aef2628adc15f4eca78c3"
|
||||
},
|
||||
"sasjs-cli": {
|
||||
"source": "sasjs/skills",
|
||||
"sourceType": "github",
|
||||
"skillPath": "sasjs-cli/SKILL.md",
|
||||
"computedHash": "fc9e07959990e0b39cd29408319dd5cb846e237b4b12abaeb9491a136b67cc26"
|
||||
},
|
||||
"sasjs-core": {
|
||||
"source": "sasjs/skills",
|
||||
"sourceType": "github",
|
||||
"skillPath": "sasjs-core/SKILL.md",
|
||||
"computedHash": "2a58fe2c1de287dc5b05d8048d1e05edeccb24a60cd622ee0b207237f3f3b7e9"
|
||||
},
|
||||
"sasjs-framework": {
|
||||
"source": "sasjs/skills",
|
||||
"sourceType": "github",
|
||||
"skillPath": "sasjs-framework/SKILL.md",
|
||||
"computedHash": "7c70d44de35cb50ecd0e4e05316812d6b7790a16a64fd6fa73435205f9029ee2"
|
||||
},
|
||||
"sasjs-server": {
|
||||
"source": "sasjs/skills",
|
||||
"sourceType": "github",
|
||||
"skillPath": "sasjs-server/SKILL.md",
|
||||
"computedHash": "e3ddff92bf2c22c5dafe1935a9db7ecff0e166af78f98c5aea8126b960c96126"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user