Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/typecheck.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ jobs:
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 24
- name: Install dependencies
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/unit-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ jobs:
- saflib
# END WORKFLOW AREA
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 24
cache: "npm"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/workflow-checklist.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ jobs:
workflow-integration-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 24

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/workflow-git.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ jobs:
workflow-git-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 24

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/workflow-print.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ jobs:
workflow-integration-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 24

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/workflow-run.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ jobs:
workflow-integration-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 24

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/workflow-script.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ jobs:
workflow-integration-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 24

Expand Down
9 changes: 8 additions & 1 deletion analytics-service/AnalyticsServiceBase.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getSafContext } from "@saflib/node";
import { getSafContext, getSafReporters } from "@saflib/node";
import type { SafContext } from "@saflib/node";
import type { AnalyticsService, CommonEvent, IdentifyProps } from "./types.ts";

Expand Down Expand Up @@ -52,6 +52,13 @@ export abstract class AnalyticsServiceBase implements AnalyticsService {
event: event.event,
context: mergedContext,
});

const { log } = getSafReporters();
log.info(`Product event: ${event.event}`, {
event: event.event,
distinct_id: distinctId,
...(mergedContext !== undefined ? { context: mergedContext } : {}),
});
}

protected abstract emitCapture(event: {
Expand Down
3 changes: 1 addition & 2 deletions backup/backup-sdk/Dockerfile.template
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ RUN npm install -g npm@11.14.1
WORKDIR /app

#{ copy_packages }#
RUN npm install
RUN npm install @rollup/rollup-linux-arm64-gnu
RUN npm ci

#{ copy_src }#

Expand Down
2 changes: 1 addition & 1 deletion cron/cron/Dockerfile.template
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ FROM node:alpine3.19
WORKDIR /app

#{ copy_packages }#
RUN npm install --omit=dev
RUN npm ci --omit=dev
#{ copy_src }#

WORKDIR /app/services/cron
Expand Down
2 changes: 2 additions & 0 deletions drizzle/types/file-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const fileMetadataColumns = {
file_original_name: text("file_original_name").notNull(),
mimetype: text("mimetype").notNull(),
size: integer("size").notNull(),
md5_hash: text("md5_hash"),
created_at: text("created_at")
.notNull()
.$defaultFn(() => new Date().toISOString()),
Expand Down Expand Up @@ -47,6 +48,7 @@ export interface FileMetadataFields {
file_original_name: string;
mimetype: string;
size: number;
md5_hash: string | null;
created_at: string;
updated_at: string;
}
3 changes: 2 additions & 1 deletion express/src/middleware/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
AUTH_ERROR_EMAIL_VERIFICATION_REQUIRED,
AUTH_ERROR_MFA_REQUIRED,
} from "@saflib/sdk/auth-error-codes";
import { typedEnv } from "@saflib/env";

interface AuthMiddlewareOptions {
adminRequired?: boolean;
Expand Down Expand Up @@ -57,7 +58,7 @@ export const makeAuthMiddleware = (
const routeRequiresMfa =
Boolean(mfaRequired) ||
tags?.includes("mfa-required") === true ||
Boolean(adminRequired);
(Boolean(adminRequired) && typedEnv.NODE_ENV === "production");

if (tags?.includes("no-auth")) {
return next();
Expand Down
5 changes: 4 additions & 1 deletion express/src/middleware/csrf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
/**
* Enforce CSRF double-submit token validation on state-changing requests.
* Skips routes tagged `no-auth` (same convention as auth middleware).
* Skips `csrf-exempt` for browser-initiated posts that cannot attach our token
* (e.g. Content-Security-Policy violation reports).
*/
export const makeCsrfMiddleware = (): Handler => {
return (req, res, next): void => {
if (typedEnv.NODE_ENV === "test") {
return next();
}

if (req.openapi?.schema?.tags?.includes("no-auth")) {
const tags = req.openapi?.schema?.tags;
if (tags?.includes("no-auth") || tags?.includes("csrf-exempt")) {
return next();
}

Expand Down
12 changes: 9 additions & 3 deletions express/src/middleware/errors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import createError, { HttpError } from "http-errors";
import type { Request, Response, NextFunction, Handler } from "express";
import { safReportersStorage } from "@saflib/node";
import { getErrorCollectors, safReportersStorage } from "@saflib/node";
import { typedEnv } from "@saflib/env";
/**
* 404 Handler
Expand All @@ -24,13 +24,19 @@ export const errorHandler = (
const status = err.status || 500;

if (status >= 500) {
if (typedEnv.NODE_ENV === "test") {
if (
typedEnv.NODE_ENV === "test" &&
getErrorCollectors().length === 0
) {
console.error(err.stack);
}
const store = safReportersStorage.getStore();
if (store) {
store.logError(err);
} else {
} else if (
typedEnv.NODE_ENV !== "test" ||
getErrorCollectors().length === 0
) {
console.log("Error", err);
}
}
Expand Down
2 changes: 1 addition & 1 deletion node/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const addErrorCollector = (collector: ErrorCollector) => {
errorCollectors.push(collector);
};

const getErrorCollectors = () => {
export const getErrorCollectors = () => {
return errorCollectors.slice();
};

Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
"@saflib/vue": "*",
"@vue/tsconfig": "^0.9.1",
"typescript": "~6.0.0",
"vite": "^8.0.9",
"vue-router": "^5.0.0",
"vue-tsc": "^3.0.6"
},
Expand Down
2 changes: 2 additions & 0 deletions processes/workflows/spec-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ export const SpecProjectWorkflowDefinition = defineWorkflow<

Now that you have a plan, you can write the workflows per the aligned plan to implement the spec. Only one workflow has been generated, but make as many as the plan dictates. Have the main one run the others (orchestrating them).

**Step shape:** Do not start a phase workflow (or the orchestrator) with a review-only \`PromptStepMachine\` that only tells the agent to read the spec/plan. The first step should be real work (\`CdStepMachine\`, a sub-workflow, or an implementation prompt). Put context in that first step's prompt with \`Use workflow docFiles (**<name>.spec.md**, **<name>.plan.md** Phase N)\` — the workflow's \`docFiles\` block already wires those paths; reading them is not a separate step. Follow \`daemon/plans/notes/2026-05-21-questionnaire-mappings/\` phase workflows as the reference shape.

For each workflow, run "npm exec saf-workflow dry-run ./path/to/workflow.ts" to make sure everything is wired up correctly. One of the more common errors is for the workflow to not include "CdStepMachine" to move into the right directory before running the workflow. Location matters.
`,
})),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Setup Node dependencies
description: >-
Setup Node.js, restore ~/.npm and workspace node_modules caches, and run npm ci
only on cache miss. The monorepo installs into nested node_modules (e.g.
saflib/node_modules); caching only the repo root tree breaks typecheck.
inputs:
node-version:
description: Node.js version
required: false
default: "24"
runs:
using: composite
steps:
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: ${{ inputs.node-version }}
cache: npm

- name: Cache node_modules
id: node-modules-cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: |
node_modules
saflib/**/node_modules
daemon/**/node_modules
deploy/**/node_modules
key: node-modules-v2-${{ runner.os }}-node-${{ inputs.node-version }}-${{ hashFiles('package-lock.json') }}
restore-keys: |
node-modules-v2-${{ runner.os }}-node-${{ inputs.node-version }}-

- name: Install dependencies
if: steps.node-modules-cache.outputs.cache-hit != 'true'
shell: bash
run: npm ci --prefer-offline --no-audit --no-fund
15 changes: 5 additions & 10 deletions product/workflows/templates/.github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,10 @@ jobs:
run:
working-directory: .
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
node-version: lts/*
cache: "npm"
- name: Install submodules
run: git submodule update --init
- name: Install dependencies
run: npm ci
submodules: recursive
- uses: ./.github/actions/setup-node-deps
# For some reason, github actions hang when I do `npm exec saf-docker generate`. So we run the script directly.
- name: Generate Docker
run: node --experimental-strip-types --disable-warning=ExperimentalWarning saflib/dev-tools/src/saf-docker-cli.ts generate
Expand All @@ -36,14 +31,14 @@ jobs:
- name: Shut down
run: docker stop $(docker ps -a -q)
# TODO: gather all logs and upload them as artifacts
# - uses: actions/upload-artifact@v4
# - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
# if: ${{ !cancelled() }}
# with:
# name: playwright-report
# path: ./clients/web-auth/playwright-report/
# retention-days: 30
- name: Upload docker compose logs
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: ${{ !cancelled() }}
with:
name: last-docker-compose-log
Expand Down
16 changes: 4 additions & 12 deletions product/workflows/templates/.github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,13 @@ jobs:
env:
CONTAINER_REGISTRY: ghcr.io/${{ github.repository_owner }}
steps:
- uses: actions/checkout@v6
- name: Install submodules
run: git submodule update --init

- name: Set up Node.js
uses: actions/setup-node@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
node-version: 24
cache: npm

- name: Install dependencies
run: npm ci
submodules: recursive
- uses: ./.github/actions/setup-node-deps

- name: Log in to GHCR
uses: docker/login-action@v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
Expand Down
12 changes: 3 additions & 9 deletions product/workflows/templates/.github/workflows/typecheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,9 @@ jobs:
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
node-version: 24
cache: "npm"
- name: Install submodules
run: git submodule update --init
- name: Install dependencies
run: npm ci
submodules: recursive
- uses: ./.github/actions/setup-node-deps
- name: Run Typecheck
run: npm run typecheck
16 changes: 3 additions & 13 deletions product/workflows/templates/.github/workflows/unit-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,10 @@ jobs:
- __product-name__
# END WORKFLOW AREA
steps:
- uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
node-version: 24
cache: "npm"
- name: Install submodules
run: git submodule update --init
- name: Install dependencies
run: npm ci

# Tests fail to run if this one package isn't installed
- name: Install rollup for linux
run: npm install @rollup/rollup-linux-x64-gnu
submodules: recursive
- uses: ./.github/actions/setup-node-deps

- name: Run Unit Tests
run: npm run test -- --project='*${{ matrix.project }}*'
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ RUN npm install -g npm@11.14.1
WORKDIR /app

#{ copy_packages }#
RUN npm install --omit=dev
RUN npm ci --omit=dev

#{ copy_src }#

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ serve:
selfservice:
default_browser_return_url: http://auth.docker.localhost/

# Permit return_to from any app on this dev domain (e.g. app.recipes.docker.localhost).
# Root host is listed separately; *.docker.localhost does not match the apex.
# Only apex and product hosts (no subdomain wildcard — T-9 open redirect).
allowed_return_urls:
- http://docker.localhost/
- http://*.docker.localhost/
- http://app.docker.localhost/
- http://admin.docker.localhost/
- http://auth.docker.localhost/
- http://account.docker.localhost/

flows:
registration:
Expand Down
Loading
Loading