@flytedesk/app-kit 3.2.2 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +157 -10
  2. package/dist/auth/client/httpClient.d.ts +79 -1
  3. package/dist/auth/client/httpClient.js +185 -16
  4. package/dist/auth/client/httpClient.js.map +1 -1
  5. package/dist/auth/client/index.d.ts +4 -4
  6. package/dist/auth/client/index.js +3 -3
  7. package/dist/auth/client/index.js.map +1 -1
  8. package/dist/auth/client/useAuthSession.d.ts +61 -13
  9. package/dist/auth/client/useAuthSession.js +185 -42
  10. package/dist/auth/client/useAuthSession.js.map +1 -1
  11. package/dist/auth/errors.d.ts +30 -7
  12. package/dist/auth/errors.js +38 -8
  13. package/dist/auth/errors.js.map +1 -1
  14. package/dist/auth/guards.d.ts +12 -0
  15. package/dist/auth/guards.js +24 -6
  16. package/dist/auth/guards.js.map +1 -1
  17. package/dist/auth/index.d.ts +4 -4
  18. package/dist/auth/index.js +2 -2
  19. package/dist/auth/index.js.map +1 -1
  20. package/dist/auth/oidc-client.js +12 -2
  21. package/dist/auth/oidc-client.js.map +1 -1
  22. package/dist/auth/plugin.d.ts +6 -0
  23. package/dist/auth/plugin.js +532 -304
  24. package/dist/auth/plugin.js.map +1 -1
  25. package/dist/auth/shared-types.d.ts +28 -1
  26. package/dist/auth/shared-types.js +35 -0
  27. package/dist/auth/shared-types.js.map +1 -1
  28. package/dist/auth/silentAuthPage.d.ts +20 -0
  29. package/dist/auth/silentAuthPage.js +75 -0
  30. package/dist/auth/silentAuthPage.js.map +1 -0
  31. package/dist/auth/testing/fake-idp.d.ts +35 -0
  32. package/dist/auth/testing/fake-idp.js +86 -2
  33. package/dist/auth/testing/fake-idp.js.map +1 -1
  34. package/dist/auth/testing/index.d.ts +3 -1
  35. package/dist/auth/testing/index.js +3 -1
  36. package/dist/auth/testing/index.js.map +1 -1
  37. package/dist/auth/testing/memory-store.d.ts +44 -0
  38. package/dist/auth/testing/memory-store.js +120 -0
  39. package/dist/auth/testing/memory-store.js.map +1 -0
  40. package/dist/auth/tokens.d.ts +24 -0
  41. package/dist/auth/tokens.js +31 -1
  42. package/dist/auth/tokens.js.map +1 -1
  43. package/dist/auth/types.d.ts +231 -96
  44. package/dist/auth/unavailable.d.ts +10 -0
  45. package/dist/auth/unavailable.js +11 -0
  46. package/dist/auth/unavailable.js.map +1 -0
  47. package/dist/bigquery/errors.d.ts +21 -3
  48. package/dist/bigquery/errors.js +66 -12
  49. package/dist/bigquery/errors.js.map +1 -1
  50. package/dist/bigquery/index.d.ts +5 -3
  51. package/dist/bigquery/index.js +4 -2
  52. package/dist/bigquery/index.js.map +1 -1
  53. package/dist/bigquery/query.d.ts +9 -2
  54. package/dist/bigquery/query.js +38 -16
  55. package/dist/bigquery/query.js.map +1 -1
  56. package/dist/bigquery/types.d.ts +45 -1
  57. package/dist/flags/plugin.js +4 -20
  58. package/dist/flags/plugin.js.map +1 -1
  59. package/dist/postgres/advisoryLock.d.ts +58 -0
  60. package/dist/postgres/advisoryLock.js +71 -0
  61. package/dist/postgres/advisoryLock.js.map +1 -0
  62. package/dist/postgres/index.d.ts +6 -0
  63. package/dist/postgres/index.js +7 -0
  64. package/dist/postgres/index.js.map +1 -0
  65. package/dist/profile/plugin.js +3 -20
  66. package/dist/profile/plugin.js.map +1 -1
  67. package/package.json +8 -2
  68. package/scripts/pending-release-count.mjs +0 -37
  69. package/scripts/release.sh +0 -126
  70. package/scripts/release.test.ts +0 -205
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flytedesk/app-kit",
3
- "version": "3.2.2",
3
+ "version": "4.0.0",
4
4
  "description": "Shared platform kit for flytedesk apps: flytedesk-id auth (BFF/OIDC client) and a Postgres-native trace/audit layer.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -25,7 +25,7 @@
25
25
  "files": [
26
26
  "dist",
27
27
  "prisma",
28
- "scripts"
28
+ "scripts/postinstall-eslint-ts6-shim.cjs"
29
29
  ],
30
30
  "bin": {
31
31
  "trace-sync": "./dist/cli/trace-sync.js",
@@ -77,6 +77,10 @@
77
77
  "./spa-server": {
78
78
  "types": "./dist/spa-server/index.d.ts",
79
79
  "import": "./dist/spa-server/index.js"
80
+ },
81
+ "./postgres": {
82
+ "types": "./dist/postgres/index.d.ts",
83
+ "import": "./dist/postgres/index.js"
80
84
  }
81
85
  },
82
86
  "scripts": {
@@ -116,10 +120,12 @@
116
120
  "@arethetypeswrong/cli": "^0.18.5",
117
121
  "@changesets/changelog-github": "^1.0.0",
118
122
  "@changesets/cli": "^3.0.1",
123
+ "@google-cloud/bigquery": "^9.1.0",
119
124
  "@prisma/client": "^7.10.0",
120
125
  "@testcontainers/postgresql": "^12.1.0",
121
126
  "@testing-library/react": "^16.3.3",
122
127
  "@types/cross-spawn": "^6.0.6",
128
+ "@types/jsdom": "^30.0.0",
123
129
  "@types/node": "^24.0.0",
124
130
  "@types/pg": "^8.23.1",
125
131
  "@types/react": "^19.3.0",
@@ -1,37 +0,0 @@
1
- #!/usr/bin/env node
2
- // Prints how many package releases the changesets committed in the current
3
- // directory would produce. scripts/release.sh uses it to decide between
4
- // "version + publish" and "no-op".
5
- //
6
- // It counts every changeset on disk. It deliberately does NOT pass
7
- // `--since=origin/main`: a release runs from a clone of main, where the
8
- // pending changesets are already on origin/main, so `--since` always
9
- // reports zero. That is exactly how the first real release run failed
10
- // (AK-13, 2026-09-24).
11
- import { spawnSync } from "node:child_process";
12
- import { mkdtempSync, readFileSync, rmSync } from "node:fs";
13
- import { createRequire } from "node:module";
14
- import { tmpdir } from "node:os";
15
- import { join } from "node:path";
16
-
17
- const require = createRequire(import.meta.url);
18
- const changesetBin = require.resolve("@changesets/cli/bin.js");
19
-
20
- const outDir = mkdtempSync(join(tmpdir(), "changeset-status-"));
21
- const outFile = join(outDir, "status.json");
22
-
23
- try {
24
- const result = spawnSync(
25
- process.execPath,
26
- [changesetBin, "status", `--output=${outFile}`],
27
- { cwd: process.cwd(), encoding: "utf8" },
28
- );
29
- if (result.status !== 0) {
30
- process.stderr.write(result.stderr || result.stdout);
31
- process.exit(result.status ?? 1);
32
- }
33
- const status = JSON.parse(readFileSync(outFile, "utf8"));
34
- process.stdout.write(String(status.releases.length));
35
- } finally {
36
- rmSync(outDir, { recursive: true, force: true });
37
- }
@@ -1,126 +0,0 @@
1
- #!/usr/bin/env bash
2
- #
3
- # app-kit release script.
4
- #
5
- # GitHub Actions no longer starts jobs for the Flytedesk org (billing
6
- # refusal), so this script replaces .github/workflows/ci.yml + release.yml.
7
- # It runs the exact gates CI used to run, then versions/publishes/tags
8
- # exactly like the old two-stage changesets/action flow did — but from a
9
- # human-triggered local run in a dedicated deploy clone, instead of GitHub.
10
- #
11
- # Usage:
12
- # bash scripts/release.sh [<commit-or-ref>]
13
- #
14
- # <commit-or-ref> defaults to origin/main's tip. It must be reachable from
15
- # origin/main (i.e. already merged) — this script never releases unmerged
16
- # work.
17
- #
18
- # Run this ONLY from the sanctioned deploy clone at
19
- # C:/Users/newms/deploy/app-kit (see .danxbot/config/workflow.md
20
- # "Publishing" for one-time setup). It refuses to run anywhere else.
21
- set -euo pipefail
22
-
23
- log() { printf '[release] %s\n' "$1"; }
24
- die() { printf '[release] ERROR: %s\n' "$1" >&2; exit 1; }
25
-
26
- # --- Guard 1: this must be the sanctioned deploy clone ---------------------
27
- REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" \
28
- || die "not inside a git repository"
29
-
30
- MARKER="$REPO_ROOT/.deploy-clone-marker"
31
- [[ -f "$MARKER" ]] \
32
- || die "missing $MARKER — this is not the sanctioned deploy clone. \
33
- Release runs only happen from a dedicated clone at C:/Users/newms/deploy/app-kit; \
34
- see .danxbot/config/workflow.md 'Publishing' for one-time setup."
35
-
36
- cd "$REPO_ROOT"
37
-
38
- # --- Guard 2: refuse a dirty tree ------------------------------------------
39
- if [[ -n "$(git status --porcelain)" ]]; then
40
- die "working tree is dirty — refusing to release from an unclean clone. \
41
- Run 'git status' in $REPO_ROOT and clean it up first."
42
- fi
43
-
44
- # --- Guard 3: take the lock -------------------------------------------------
45
- LOCKFILE="$REPO_ROOT/.release.lock"
46
- if ! ( set -o noclobber; printf '%s\n' "$$" > "$LOCKFILE" ) 2>/dev/null; then
47
- die "lock file $LOCKFILE already exists — another release run is in \
48
- progress (or a previous run crashed without cleaning up; if you're sure \
49
- none is running, delete the lock file and retry)."
50
- fi
51
- trap 'rm -f "$LOCKFILE"' EXIT
52
-
53
- # --- Fetch + resolve the target commit --------------------------------------
54
- log "fetching origin..."
55
- git fetch origin
56
-
57
- TARGET_REF="${1:-origin/main}"
58
- TARGET_SHA="$(git rev-parse "$TARGET_REF")" \
59
- || die "could not resolve '$TARGET_REF' to a commit"
60
-
61
- git merge-base --is-ancestor "$TARGET_SHA" origin/main \
62
- || die "$TARGET_REF ($TARGET_SHA) is not reachable from origin/main — \
63
- refusing to release unmerged work."
64
-
65
- log "target commit: $TARGET_SHA"
66
-
67
- # Move local main onto the target commit so a version-bump commit (if any)
68
- # lands on top of it and can be pushed straight to origin/main.
69
- git checkout -B main "$TARGET_SHA"
70
-
71
- # --- Install + gates ---------------------------------------------------------
72
- log "npm ci..."
73
- npm ci
74
-
75
- log "running gates (npm run check: lint, typecheck, build, test, test:integration, check:exports)..."
76
- npm run check
77
-
78
- log "gates passed."
79
-
80
- # --- Version / publish ------------------------------------------------------
81
- PENDING_COUNT="$(node scripts/pending-release-count.mjs)"
82
-
83
- CURRENT_VERSION="$(node -p "require('./package.json').version")"
84
-
85
- if [[ "$PENDING_COUNT" -eq 0 ]]; then
86
- PUBLISHED_VERSION="$(npm view @flytedesk/app-kit version)"
87
- if [[ "$PUBLISHED_VERSION" == "$CURRENT_VERSION" ]]; then
88
- log "no changesets pending and $CURRENT_VERSION is already on npm — no-op."
89
- printf 'RESULT: sha=%s version=%s status=no-op\n' "$TARGET_SHA" "$CURRENT_VERSION"
90
- exit 0
91
- else
92
- die "no changesets pending, but package.json ($CURRENT_VERSION) does not \
93
- match the published npm version ($PUBLISHED_VERSION) — refusing to guess \
94
- what to do; this needs a human to reconcile versions."
95
- fi
96
- fi
97
-
98
- log "$PENDING_COUNT changeset(s) pending — versioning..."
99
- # @changesets/changelog-github looks up each changeset's commit and author on
100
- # GitHub, so `changeset version` needs a token. Take it from gh-as for this one
101
- # command only: it is never written to disk or exported to later steps.
102
- CHANGELOG_TOKEN="$(gh-as auth token)" \
103
- || die "could not get a GitHub token from gh-as for the changelog step."
104
- [[ -n "$CHANGELOG_TOKEN" ]] \
105
- || die "gh-as returned an empty GitHub token for the changelog step."
106
- GITHUB_TOKEN="$CHANGELOG_TOKEN" npx changeset version
107
- unset CHANGELOG_TOKEN
108
-
109
- NEW_VERSION="$(node -p "require('./package.json').version")"
110
- [[ "$NEW_VERSION" != "$CURRENT_VERSION" ]] \
111
- || die "changeset version did not bump the version — aborting before publish."
112
-
113
- log "bumped to $NEW_VERSION. Committing..."
114
- git add -A
115
- git commit -m "chore(release): v$NEW_VERSION"
116
-
117
- log "pushing version-bump commit to origin/main (fails on non-fast-forward)..."
118
- git push origin main
119
-
120
- log "publishing to npm (account flytedan, token from ~/.npmrc)..."
121
- npx changeset publish
122
-
123
- log "pushing tag v$NEW_VERSION..."
124
- git push origin "v$NEW_VERSION"
125
-
126
- printf 'RESULT: sha=%s version=%s status=published\n' "$(git rev-parse HEAD)" "$NEW_VERSION"
@@ -1,205 +0,0 @@
1
- import { describe, expect, it, beforeAll } from "vitest";
2
- import { spawnSync } from "node:child_process";
3
- import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync } from "node:fs";
4
- import { tmpdir } from "node:os";
5
- import { join, resolve } from "node:path";
6
-
7
- // scripts/release.sh refuses to do anything except from the sanctioned
8
- // deploy clone, with a clean tree, without a live lock, and only for a
9
- // commit reachable from origin/main. Those guards run before npm ci / the
10
- // gates, so they're cheap and fully testable against throwaway git repos
11
- // (no Docker, no network, no real npm publish).
12
-
13
- const SCRIPT_PATH = resolve(__dirname, "release.sh");
14
-
15
- // On Windows, a bare `bash` on PATH can resolve to the WSL launcher in
16
- // System32 instead of Git Bash, which can't reliably run against a
17
- // C:/Users/... working tree. Find Git Bash explicitly when it exists;
18
- // fall back to `bash` (e.g. on Linux CI, where `bash` is unambiguous).
19
- function resolveBash(): string {
20
- const candidates = [
21
- "C:\\Program Files\\Git\\bin\\bash.exe",
22
- "C:\\Program Files\\Git\\usr\\bin\\bash.exe",
23
- ];
24
- for (const c of candidates) {
25
- if (existsSync(c)) return c;
26
- }
27
- return "bash";
28
- }
29
-
30
- const BASH = resolveBash();
31
-
32
- function run(cwd: string, args: string[] = []) {
33
- const result = spawnSync(BASH, [SCRIPT_PATH, ...args], {
34
- cwd,
35
- encoding: "utf8",
36
- env: { ...process.env },
37
- });
38
- return result;
39
- }
40
-
41
- function git(cwd: string, args: string[]) {
42
- const result = spawnSync("git", args, { cwd, encoding: "utf8" });
43
- if (result.status !== 0) {
44
- throw new Error(
45
- `git ${args.join(" ")} failed in ${cwd}: ${result.stderr}${result.stdout}`,
46
- );
47
- }
48
- return result.stdout;
49
- }
50
-
51
- function initRepoWithCommit(dir: string) {
52
- git(dir, ["init", "-q", "-b", "main"]);
53
- git(dir, ["config", "user.email", "test@example.com"]);
54
- git(dir, ["config", "user.name", "Test"]);
55
- // Ignore the script's own local-only marker/lock files so creating them
56
- // doesn't itself count as "dirty" in these throwaway repos.
57
- writeFileSync(join(dir, ".gitignore"), ".deploy-clone-marker\n.release.lock\n");
58
- writeFileSync(join(dir, "README.md"), "test repo\n");
59
- git(dir, ["add", "."]);
60
- git(dir, ["commit", "-q", "-m", "initial commit"]);
61
- }
62
-
63
- function addMarker(dir: string) {
64
- writeFileSync(join(dir, ".deploy-clone-marker"), "test marker\n");
65
- }
66
-
67
- describe("scripts/release.sh guards", () => {
68
- let workDir: string;
69
-
70
- beforeAll(() => {
71
- workDir = mkdtempSync(join(tmpdir(), "release-sh-test-"));
72
- });
73
-
74
- it("refuses to run outside the deploy clone (no marker file)", () => {
75
- const dir = mkdtempSync(join(workDir, "no-marker-"));
76
- initRepoWithCommit(dir);
77
- // deliberately no .deploy-clone-marker
78
-
79
- const result = run(dir);
80
-
81
- expect(result.status).not.toBe(0);
82
- expect(result.stderr).toMatch(/not the sanctioned deploy clone/i);
83
- });
84
-
85
- it("refuses a dirty working tree even with the marker present", () => {
86
- const dir = mkdtempSync(join(workDir, "dirty-"));
87
- initRepoWithCommit(dir);
88
- addMarker(dir);
89
- // Make an uncommitted, non-ignored change.
90
- writeFileSync(join(dir, "README.md"), "modified, uncommitted\n");
91
-
92
- const result = run(dir);
93
-
94
- expect(result.status).not.toBe(0);
95
- expect(result.stderr).toMatch(/dirty/i);
96
- });
97
-
98
- it("refuses to run when the lock file is already held", () => {
99
- const dir = mkdtempSync(join(workDir, "locked-"));
100
- initRepoWithCommit(dir);
101
- addMarker(dir);
102
- writeFileSync(join(dir, ".release.lock"), "99999\n");
103
-
104
- const result = run(dir);
105
-
106
- expect(result.status).not.toBe(0);
107
- expect(result.stderr).toMatch(/lock file .* already exists/i);
108
- // The guard must not have deleted a lock file it didn't create itself
109
- // (the trap only fires once *this* run successfully takes the lock).
110
- expect(existsSync(join(dir, ".release.lock"))).toBe(true);
111
- rmSync(join(dir, ".release.lock"));
112
- });
113
-
114
- it("refuses a target commit that is not reachable from origin/main", () => {
115
- // A bare "origin" with just the initial commit on main.
116
- const originDir = mkdtempSync(join(workDir, "origin-"));
117
- git(originDir, ["init", "-q", "--bare", "-b", "main"]);
118
-
119
- const seedDir = mkdtempSync(join(workDir, "seed-"));
120
- initRepoWithCommit(seedDir);
121
- git(seedDir, ["remote", "add", "origin", originDir]);
122
- git(seedDir, ["push", "-q", "origin", "main"]);
123
-
124
- // The deploy clone: clone of origin, plus a local commit on a throwaway
125
- // branch that is never pushed, so it is not an ancestor of origin/main.
126
- // main itself stays exactly at origin/main's tip, so the tree the
127
- // guards see is clean.
128
- const cloneDir = join(workDir, "clone-unreachable");
129
- git(workDir, ["clone", "-q", originDir, cloneDir]);
130
- addMarker(cloneDir);
131
- git(cloneDir, ["checkout", "-q", "-b", "throwaway"]);
132
- writeFileSync(join(cloneDir, "unmerged.txt"), "local-only change\n");
133
- git(cloneDir, ["add", "unmerged.txt"]);
134
- git(cloneDir, ["commit", "-q", "-m", "unmerged local commit"]);
135
- const unmergedSha = git(cloneDir, ["rev-parse", "HEAD"]).trim();
136
- git(cloneDir, ["checkout", "-q", "main"]);
137
-
138
- const result = run(cloneDir, [unmergedSha]);
139
-
140
- expect(result.status).not.toBe(0);
141
- expect(result.stderr).toMatch(/not reachable from origin\/main/i);
142
- });
143
- });
144
-
145
- // scripts/pending-release-count.mjs decides "version + publish" vs "no-op".
146
- // A release runs from a clone of main, so pending changesets are already on
147
- // origin/main. The first real release (AK-13, 2026-09-24) counted zero because
148
- // it asked `changeset status --since=origin/main`; these tests pin the fix by
149
- // reproducing that exact shape: changesets committed AND pushed.
150
- describe("scripts/pending-release-count.mjs", () => {
151
- const COUNT_SCRIPT = resolve(__dirname, "pending-release-count.mjs");
152
- let workDir: string;
153
-
154
- beforeAll(() => {
155
- workDir = mkdtempSync(join(tmpdir(), "pending-count-test-"));
156
- });
157
-
158
- function cloneOfPushedPackage(name: string, changesets: Record<string, string>) {
159
- const originDir = mkdtempSync(join(workDir, `${name}-origin-`));
160
- git(originDir, ["init", "-q", "--bare", "-b", "main"]);
161
-
162
- const seedDir = mkdtempSync(join(workDir, `${name}-seed-`));
163
- initRepoWithCommit(seedDir);
164
- writeFileSync(
165
- join(seedDir, "package.json"),
166
- JSON.stringify({ name: "@test/pkg", version: "0.3.2" }, null, 2),
167
- );
168
- mkdirSync(join(seedDir, ".changeset"));
169
- writeFileSync(
170
- join(seedDir, ".changeset", "config.json"),
171
- JSON.stringify({ changelog: false, commit: false, baseBranch: "main", access: "public" }),
172
- );
173
- for (const [file, body] of Object.entries(changesets)) {
174
- writeFileSync(join(seedDir, ".changeset", file), body);
175
- }
176
- git(seedDir, ["add", "."]);
177
- git(seedDir, ["commit", "-q", "-m", "package with changesets"]);
178
- git(seedDir, ["remote", "add", "origin", originDir]);
179
- git(seedDir, ["push", "-q", "origin", "main"]);
180
-
181
- const cloneDir = join(workDir, `${name}-clone`);
182
- git(workDir, ["clone", "-q", originDir, cloneDir]);
183
- return cloneDir;
184
- }
185
-
186
- function count(cwd: string) {
187
- const result = spawnSync(process.execPath, [COUNT_SCRIPT], { cwd, encoding: "utf8" });
188
- if (result.status !== 0) throw new Error(result.stderr || result.stdout);
189
- return result.stdout;
190
- }
191
-
192
- it("counts changesets that are already on origin/main", () => {
193
- const clone = cloneOfPushedPackage("pending", {
194
- "minor-feature.md": '---\n"@test/pkg": minor\n---\n\nA feature.\n',
195
- });
196
-
197
- expect(count(clone)).toBe("1");
198
- });
199
-
200
- it("reports zero when no changesets are pending", () => {
201
- const clone = cloneOfPushedPackage("none", {});
202
-
203
- expect(count(clone)).toBe("0");
204
- });
205
- });