diff --git a/.gitea/workflows/publish.yml b/.gitea/workflows/publish.yml index e4d011a..25b6332 100644 --- a/.gitea/workflows/publish.yml +++ b/.gitea/workflows/publish.yml @@ -31,19 +31,22 @@ jobs: apt install -y ./google-chrome*.deb; export CHROME_BIN=/usr/bin/google-chrome + # Deploy tooling is pinned to exact versions: the workflow builds whatever + # is committed, and an unpinned `latest` would let a breaking or + # compromised upstream release ship straight to the live site. - name: Install Surfer run: | - npm -g install cloudron-surfer + npm -g install cloudron-surfer@6.4.1 - name: build site run: | - pip install mkdocs - pip install mkdocs-material - pip install fontawesome_markdown - pip install mkdocs-redirects + pip install mkdocs==1.6.1 + pip install mkdocs-material==9.7.7 + pip install fontawesome_markdown==0.2.6 + pip install mkdocs-redirects==1.2.3 mkdocs build --clean mkdir site/slides - npx @marp-team/marp-cli slides/innovation/innovation.md -o ./site/slides/innovation/index.html - npx @marp-team/marp-cli slides/if/if.md -o site/if.pdf --allow-local-files --html=true + npx @marp-team/marp-cli@4.5.1 slides/innovation/innovation.md -o ./site/slides/innovation/index.html + npx @marp-team/marp-cli@4.5.1 slides/if/if.md -o site/if.pdf --allow-local-files --html=true - name: Deploy docs run: surfer put --token ${{ secrets.SURFERKEY }} --server docs.datacontroller.io site/* / diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..ca82182 --- /dev/null +++ b/.npmrc @@ -0,0 +1,11 @@ +# npm configuration for this repo's CI and local installs. +# +# ignore-scripts keeps package install scripts (an arbitrary-code execution +# point) from running during the deploy workflow. Both npm packages the +# workflow installs - cloudron-surfer and @marp-team/marp-cli - ship no +# install, preinstall or postinstall hook, so nothing is skipped. +# +# save-exact pins anything installed from here to an exact version rather than +# a caret range. +ignore-scripts=true +save-exact=true diff --git a/docs/developer-guide/two-rule-sets-one-table.md b/docs/developer-guide/two-rule-sets-one-table.md index bf8dc3b..e42ef8a 100644 --- a/docs/developer-guide/two-rule-sets-one-table.md +++ b/docs/developer-guide/two-rule-sets-one-table.md @@ -102,9 +102,9 @@ From that moment the changeset is an approval against `ORDERS.ORDERS`. The appro ![A change submitted against the mirror, raised against the real table](/img/approval-routed.png) -### The detail that will bite you +### Reassigning LIBREF and DS -Use `call symputx`, not `%let`. `LIBREF` and `DS` are not declared `%local` in `mpe_loader`, and the hook is included into that scope - so `call symputx` finds the existing variable and updates it, while a `%let` creates a new variable in the hook's own scope and is silently discarded. The hook runs, the log looks clean, and the changeset goes to the mirror anyway. +`LIBREF` and `DS` are ordinary macro variables inside `mpe_loader`, which creates them with `%let` and then updates them from the staged row. A `%let libref=ORDERS;` reassigns them just as well as the `call symputx` above: both statements assign to the *existing* variable, because it lives in an enclosing symbol table (`mpe_loader`'s local table) and SAS only creates a new local when the name is absent from every enclosing table. `call symputx` is used above because it is explicit about which variable is being set, and because it does not depend on the enclosing scope still holding the name - a wrapper macro that declared `%local libref` would turn a bare `%let` into an assignment to that shadow instead. ### Other things to watch