@terreno/announcements 57.20.0 → 57.21.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.
- package/CHANGELOG.md +50 -0
- package/biome.jsonc +1 -0
- package/dist/types.d.ts +2 -2
- package/e2e/smoke.ts +188 -0
- package/package.json +8 -6
- package/src/tests/pending.test.ts +2 -1
- package/src/types.ts +4 -2
- package/tsconfig.typecheck.json +18 -0
package/CHANGELOG.md
CHANGED
|
@@ -20,6 +20,56 @@ Upgrade notes for consumer action live in [`mcp-server/src/docs/upgrades/`](mcp-
|
|
|
20
20
|
|
|
21
21
|
Unreleased changes live in [`changelog/unreleased/`](changelog/unreleased/). Add one Markdown file per feature (see that directory's README) instead of editing this section.
|
|
22
22
|
|
|
23
|
+
## [57.21.0] - 2026-11-01
|
|
24
|
+
|
|
25
|
+
Upgrade note: [`mcp-server/src/docs/upgrades/57.21.0.md`](mcp-server/src/docs/upgrades/57.21.0.md).
|
|
26
|
+
|
|
27
|
+
### Breaking
|
|
28
|
+
|
|
29
|
+
- The `terreno-scan` plugin is now `terreno-loop`, and its skills are renamed from `terreno-scan-*` to `terreno-loop-*`. The resident loop `terreno-scan-loop` is now `terreno-loop-run`: `/terreno-loop:run` in Claude Code, `/terreno-loop-run` in Cursor, `$terreno-loop-run` in Codex. Claude Code moves existing installs over through the marketplace `renames` entry. In Cursor and Codex, uninstall `terreno-scan` and install `terreno-loop`. Campaign state stays at `.terreno/scan/<goal>.json`, so campaigns already in progress resume.
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- `@terreno/admin-spa` serves an MCP OAuth consent screen at `/console/oauth/consent`, so a backend with no product frontend can set `mcpOAuth.consentUrl` to `${issuer}/console/oauth/consent`. Any signed-in user can approve or deny a request with their Better Auth session; signed-out users sign in and return through the login screen's new `returnTo` parameter, which accepts only in-app paths.
|
|
34
|
+
- Claude Code plugins now publish to npm with every release, as `@terreno/agent-plugin` and `@terreno/agent-plugin-loop`. A new `terreno-releases` marketplace installs them at the latest stable release. To pin one release, add `TerrenoLabs/terreno#<version>` instead. See [Install agent skills](docs/how-to/install-agent-skills.md#pick-a-channel).
|
|
35
|
+
- The generated Claude plugin manifest no longer pins `version`. Before this change, plugins installed from the `TerrenoLabs/terreno` marketplace stayed on the `2.12.0` copy, because their version never changed. They now update on every commit. Run `/plugin update terreno@terreno-plugins` once to leave `2.12.0`.
|
|
36
|
+
- The `terreno-planning` lifecycle plugin (2.13.0) plans e2e tests and manual regression cases in every IP:
|
|
37
|
+
|
|
38
|
+
- Grow requires `## E2E tests` and `## Regression cases` sections (either may be `None — <reason>`), read from the e2e guide at `docs/testing/e2e.md` or a `## Testing conventions` override in AGENTS.md or CLAUDE.md.
|
|
39
|
+
- Regression cases stay minimal: most features add none. A case is only for what a developer or QA must check by hand, such as push notifications, and Roast fails one that repeats an existing case.
|
|
40
|
+
- Pick writes each planned spec in the task that delivers it, and Roast runs it; a missing or failing row is `FAIL`.
|
|
41
|
+
- Brew gates on every planned spec and merges regression cases into `docs/regression/<area>.md` with the next free ID.
|
|
42
|
+
- Taste re-checks both sections after behaviour-changing fixes and renumbers regression ID collisions with `master`.
|
|
43
|
+
- `bun run check:lifecycle-skills` enforces these phrases in every stage.
|
|
44
|
+
- New `backfill-regression` skill proposes manual regression cases one area at a time and writes only the sets the human approves.
|
|
45
|
+
- A consumer opts out of manual regression suites with `Regression suites | none` in its `## Testing conventions` table.
|
|
46
|
+
- The `terreno-planning` lifecycle plugin (2.14.0) grows an end-user guide with every feature:
|
|
47
|
+
|
|
48
|
+
- Every IP has a `## User guide` section listing the guide pages it adds, updates, or retires, or `None — <reason>`. Pages live in `docs/user-guide/<area>/<feature>.md`, one feature per page, with `title`, `audience`, `platforms`, and `questions` frontmatter that chat agents use to answer "how do I…" questions.
|
|
49
|
+
- Pick writes each page in the task that ships the feature, Roast checks it against the shipped screens, Brew gates on it and the index, and Taste keeps it current after fixes.
|
|
50
|
+
- A repo opts out with `| User guide | none — <reason> |` in a `## User guide conventions` table in AGENTS.md or CLAUDE.md.
|
|
51
|
+
- New `backfill-user-guide` skill drafts pages for features that shipped without one, one area at a time, and writes only the pages the human approves.
|
|
52
|
+
- `bun run check:user-guide` validates the pages and index (CI `repo-policies`); `--uncovered <app dir>` lists screens no page names.
|
|
53
|
+
- Terreno's own guide covers the example app's main features.
|
|
54
|
+
- `@terreno/api`: opt-in `mcpRequestLog` on `TerrenoApp` records one metadata-only `McpRequestLog` row per MCP tool call (tool, user, credential, service token or OAuth client, duration, outcome, error class; never arguments, results, or error text), expiring after `retentionDays` (default 30).
|
|
55
|
+
- `@terreno/api`: opt-in `mcpToolSettings` on `TerrenoApp` registers every MCP tool in `McpToolSetting` at startup; tools an admin switches off are hidden from `tools/list` and rejected by `tools/call`.
|
|
56
|
+
- `@terreno/api`: deleting an `McpOAuthToken` row revokes its whole connection (token family) and keeps the rows. New `McpOAuthToken.revokeAllForUser` and `McpServiceToken.revokeAllForUser`.
|
|
57
|
+
- `@terreno/admin-backend`: `mcpAdminModels()` adds MCP admin screens (service tokens, OAuth clients, OAuth connections, request log, tool switches), `mcpAdminStatements` for RBAC, and `mcpAdminScripts()` adds `mcp-sign-out-everywhere`. See [Manage MCP from the admin panel](docs/how-to/manage-mcp-in-admin.md).
|
|
58
|
+
- Docs: [Expose an MCP server safely](docs/how-to/expose-mcp-server-safely.md) (OAuth, service tokens, Cloudflare Tunnel, Access) and the `bootstrap-mcp-server` skill for a single-binary MCP server with the admin console embedded.
|
|
59
|
+
- Every workspace package has a real-process smoke test and an e2e page:
|
|
60
|
+
|
|
61
|
+
- Each package's `e2e/smoke.ts` (or `smoke.spec.ts` for browser packages) runs its built output through its public entry: a server on a real port, the compiled binary, or a page in Chromium. `bun run --cwd <package> smoke` runs one; `bun run smoke` runs them all and prints each package's time. The CircleCI `smoke` job runs them on every package change.
|
|
62
|
+
- `@terreno/test` adds the smoke helpers `smokeMongoUri()`, `spawnUntilReady()`, `spawnExampleBackend()`, `runBin()`, and `freePort()`.
|
|
63
|
+
- The [E2E testing guide](docs/testing/e2e.md) explains the test tiers and links one page per package under `docs/testing/e2e/`.
|
|
64
|
+
- Manual regression cases live in `docs/regression/<area>.md`, one file per area, and `bun run check:regression-suites` validates them.
|
|
65
|
+
- example-frontend's smoke serves a web export that `bun run --cwd example-frontend export:smoke` builds once with a placeholder API URL; the smoke swaps in a `freePort()` API URL as it serves the bundle, so the 30 s smoke no longer rebuilds the export. `spawnUntilReady()` takes `stopGraceMs`, and `spawnExampleBackend()` stops example-backend after 0.5 s instead of 5 s.
|
|
66
|
+
- Every workspace package has a `typecheck` script that runs `tsc` over source and test files through a new `tsconfig.typecheck.json`. Root `bun run lint` ends with `bun run typecheck`, and CircleCI runs it in a new `typecheck` job. Test files that had type errors were fixed.
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
|
|
70
|
+
- `@terreno/api`: deleting an `McpOAuthToken` row with document or query `deleteOne` now revokes that row's whole token family (the connection) and keeps the rows instead of removing one pair. `deleteMany` is unchanged.
|
|
71
|
+
- `@terreno/ui`: new `theme.field` tokens (`background`, `backgroundDisabled`, `border`, `borderFocus`, `borderError`, `borderDisabled`) style form inputs. `TextField`, `SelectField`, `DateTimeField`, `HeightField`, and `OtpField` read them instead of `surface.base` and `border.dark`. Defaults equal the old colors, but overriding `surface` or `border` no longer recolors those fields; use `setTheme({field: ...})`.
|
|
72
|
+
|
|
23
73
|
## [57.20.0] - 2026-10-30
|
|
24
74
|
|
|
25
75
|
Upgrade note: [`mcp-server/src/docs/upgrades/57.20.0.md`](mcp-server/src/docs/upgrades/57.20.0.md).
|
package/biome.jsonc
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PermissionMethod } from "@terreno/api";
|
|
1
|
+
import type { FindExactlyOnePlugin, FindOneOrNonePlugin, PermissionMethod } from "@terreno/api";
|
|
2
2
|
import type { Model, Types } from "mongoose";
|
|
3
3
|
export type AnnouncementStatus = "draft" | "published" | "archived";
|
|
4
4
|
export type AnnouncementPlatform = "ios" | "android" | "web";
|
|
@@ -39,7 +39,7 @@ export interface AnnouncementDocument {
|
|
|
39
39
|
created: Date;
|
|
40
40
|
updated: Date;
|
|
41
41
|
}
|
|
42
|
-
export type AnnouncementModel = Model<AnnouncementDocument>;
|
|
42
|
+
export type AnnouncementModel = Model<AnnouncementDocument> & FindExactlyOnePlugin<AnnouncementDocument> & FindOneOrNonePlugin<AnnouncementDocument>;
|
|
43
43
|
export interface AnnouncementAcknowledgementDocument {
|
|
44
44
|
_id: Types.ObjectId;
|
|
45
45
|
userId: Types.ObjectId;
|
package/e2e/smoke.ts
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Real-process smoke for @terreno/announcements. Run `bun e2e/smoke.ts` after `bun run compile`.
|
|
3
|
+
*
|
|
4
|
+
* The smoke re-runs this file with `serve` as a child process: a minimal TerrenoApp from the
|
|
5
|
+
* compiled `@terreno/api` with the compiled `AnnouncementsApp` registered, on a free port, against
|
|
6
|
+
* `smokeMongoUri()`. The parent then checks over real HTTP that a release pipeline holding the
|
|
7
|
+
* upload token can import and publish an announcement, and that a newly signed-up user sees it
|
|
8
|
+
* in their feed. Exits non-zero on the first failed check.
|
|
9
|
+
*/
|
|
10
|
+
import {
|
|
11
|
+
freePort,
|
|
12
|
+
type SpawnedProcess,
|
|
13
|
+
smokeMongoUri,
|
|
14
|
+
spawnUntilReady,
|
|
15
|
+
stopSmokeMongo,
|
|
16
|
+
} from "@terreno/test";
|
|
17
|
+
import type express from "express";
|
|
18
|
+
import {DateTime} from "luxon";
|
|
19
|
+
import type {Schema} from "mongoose";
|
|
20
|
+
|
|
21
|
+
const SERVE_ARG = "serve";
|
|
22
|
+
const UPLOAD_TOKEN = "announcements-smoke-upload-token";
|
|
23
|
+
const ANNOUNCEMENT_TITLE = "Smoke announcement";
|
|
24
|
+
|
|
25
|
+
interface ImportResult {
|
|
26
|
+
announcements: Array<{id: string; status: string}>;
|
|
27
|
+
published: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface FeedItem {
|
|
31
|
+
id: string;
|
|
32
|
+
title: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Child: the compiled API with auth, the compiled announcements plugin, and a health plugin.
|
|
37
|
+
* Both packages are imported here, not at the top, so the parent process never loads them.
|
|
38
|
+
*/
|
|
39
|
+
const serve = async (): Promise<void> => {
|
|
40
|
+
const {createdUpdatedPlugin, logger, TerrenoApp} = await import("@terreno/api");
|
|
41
|
+
const {AnnouncementsApp} = await import("@terreno/announcements");
|
|
42
|
+
const {default: mongoose} = await import("mongoose");
|
|
43
|
+
const {default: passportLocalMongoose} = await import("passport-local-mongoose");
|
|
44
|
+
|
|
45
|
+
const userSchema = new mongoose.Schema({
|
|
46
|
+
admin: {default: false, description: "Whether the user is an admin", type: Boolean},
|
|
47
|
+
email: {description: "Sign-in email", type: String},
|
|
48
|
+
name: {description: "Display name", type: String},
|
|
49
|
+
});
|
|
50
|
+
userSchema.plugin(passportLocalMongoose as unknown as (schema: Schema, options: object) => void, {
|
|
51
|
+
usernameCaseInsensitive: true,
|
|
52
|
+
usernameField: "email",
|
|
53
|
+
});
|
|
54
|
+
userSchema.plugin(createdUpdatedPlugin);
|
|
55
|
+
const User = mongoose.model("User", userSchema);
|
|
56
|
+
|
|
57
|
+
const healthPlugin = {
|
|
58
|
+
register: (app: express.Application): void => {
|
|
59
|
+
app.get("/health", (_req, res) => {
|
|
60
|
+
res.json({healthy: mongoose.connection.readyState === 1});
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
await mongoose.connect(process.env.MONGO_URI as string);
|
|
66
|
+
const app = new TerrenoApp({
|
|
67
|
+
loggingOptions: {logRequests: false},
|
|
68
|
+
userModel: User as unknown as ConstructorParameters<typeof TerrenoApp>[0]["userModel"],
|
|
69
|
+
})
|
|
70
|
+
.register(new AnnouncementsApp({uploadToken: process.env.ANNOUNCEMENTS_UPLOAD_TOKEN}))
|
|
71
|
+
.register(healthPlugin);
|
|
72
|
+
app.start();
|
|
73
|
+
await app.whenReady();
|
|
74
|
+
logger.info(`[announcementsSmoke] serving on port ${process.env.PORT}`);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const expectStatus = async (res: Response, status: number, step: string): Promise<unknown> => {
|
|
78
|
+
const body = await res.text();
|
|
79
|
+
if (res.status !== status) {
|
|
80
|
+
throw new Error(`${step}: expected ${status}, got ${res.status}: ${body}`);
|
|
81
|
+
}
|
|
82
|
+
return body ? JSON.parse(body) : undefined;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const bearer = (token: string): Record<string, string> => ({
|
|
86
|
+
Authorization: `Bearer ${token}`,
|
|
87
|
+
"Content-Type": "application/json",
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/** Parent: boots the child and walks the publish-to-feed path over HTTP. */
|
|
91
|
+
const run = async (): Promise<void> => {
|
|
92
|
+
const startedAt = DateTime.now();
|
|
93
|
+
const port = await freePort();
|
|
94
|
+
const baseUrl = `http://127.0.0.1:${port}`;
|
|
95
|
+
let server: SpawnedProcess | undefined;
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
server = await spawnUntilReady({
|
|
99
|
+
cmd: [process.execPath, __filename, SERVE_ARG],
|
|
100
|
+
env: {
|
|
101
|
+
ANNOUNCEMENTS_UPLOAD_TOKEN: UPLOAD_TOKEN,
|
|
102
|
+
MONGO_URI: await smokeMongoUri({databaseName: "announcementsSmoke"}),
|
|
103
|
+
PORT: String(port),
|
|
104
|
+
REFRESH_TOKEN_SECRET: "announcements-smoke-refresh-secret",
|
|
105
|
+
SESSION_SECRET: "announcements-smoke-session-secret",
|
|
106
|
+
TOKEN_ISSUER: "announcements-smoke",
|
|
107
|
+
TOKEN_SECRET: "announcements-smoke-token-secret",
|
|
108
|
+
},
|
|
109
|
+
timeoutMs: 30000,
|
|
110
|
+
url: `${baseUrl}/health`,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const imported = (await expectStatus(
|
|
114
|
+
await fetch(`${baseUrl}/announcements/import-release`, {
|
|
115
|
+
body: JSON.stringify({
|
|
116
|
+
announcements: [
|
|
117
|
+
{body: "Smoke test body.", slug: "smoke-announcement", title: ANNOUNCEMENT_TITLE},
|
|
118
|
+
],
|
|
119
|
+
publish: true,
|
|
120
|
+
release: {product: "announcements-smoke", version: String(startedAt.toMillis())},
|
|
121
|
+
}),
|
|
122
|
+
headers: bearer(UPLOAD_TOKEN),
|
|
123
|
+
method: "POST",
|
|
124
|
+
}),
|
|
125
|
+
200,
|
|
126
|
+
"POST /announcements/import-release"
|
|
127
|
+
)) as {data: ImportResult};
|
|
128
|
+
const announcement = imported.data.announcements[0];
|
|
129
|
+
if (imported.data.published !== 1 || announcement?.status !== "published") {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`POST /announcements/import-release: expected one published announcement, got ${JSON.stringify(imported.data)}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
console.info("✅ POST /announcements/import-release publishes an announcement");
|
|
135
|
+
|
|
136
|
+
const signup = (await expectStatus(
|
|
137
|
+
await fetch(`${baseUrl}/auth/signup`, {
|
|
138
|
+
body: JSON.stringify({
|
|
139
|
+
email: `announcements-smoke-${startedAt.toMillis()}@example.com`,
|
|
140
|
+
password: "announcements-smoke-password-1",
|
|
141
|
+
}),
|
|
142
|
+
headers: {"Content-Type": "application/json"},
|
|
143
|
+
method: "POST",
|
|
144
|
+
}),
|
|
145
|
+
200,
|
|
146
|
+
"POST /auth/signup"
|
|
147
|
+
)) as {data: {token: string}};
|
|
148
|
+
console.info("✅ POST /auth/signup signs in a user");
|
|
149
|
+
|
|
150
|
+
const feed = (await expectStatus(
|
|
151
|
+
await fetch(`${baseUrl}/announcements/feed?platform=web`, {
|
|
152
|
+
headers: bearer(signup.data.token),
|
|
153
|
+
}),
|
|
154
|
+
200,
|
|
155
|
+
"GET /announcements/feed"
|
|
156
|
+
)) as {data: FeedItem[]};
|
|
157
|
+
const item = feed.data.find((entry) => entry.id === announcement.id);
|
|
158
|
+
if (item?.title !== ANNOUNCEMENT_TITLE) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`GET /announcements/feed: the published announcement is missing: ${JSON.stringify(feed.data)}`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
console.info("✅ GET /announcements/feed lists it for the user");
|
|
164
|
+
} catch (error: unknown) {
|
|
165
|
+
if (server) {
|
|
166
|
+
console.error(`--- server output ---\n${server.output()}`);
|
|
167
|
+
}
|
|
168
|
+
throw error;
|
|
169
|
+
} finally {
|
|
170
|
+
await server?.stop();
|
|
171
|
+
await stopSmokeMongo();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.info(`announcements smoke passed in ${DateTime.now().diff(startedAt).toMillis()} ms`);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const main = process.argv[2] === SERVE_ARG ? serve : run;
|
|
178
|
+
main().then(
|
|
179
|
+
() => {
|
|
180
|
+
if (main === run) {
|
|
181
|
+
process.exit(0);
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
(error: unknown) => {
|
|
185
|
+
console.error(`❌ ${error instanceof Error ? error.message : String(error)}`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
);
|
package/package.json
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
"url": "https://github.com/TerrenoLabs/terreno/issues"
|
|
5
5
|
},
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@terreno/api": "57.
|
|
7
|
+
"@terreno/api": "57.21.0",
|
|
8
8
|
"express": "^5.2.1",
|
|
9
9
|
"luxon": "^3.7.2"
|
|
10
10
|
},
|
|
11
11
|
"description": "In-app product update announcements plugin for @terreno/api — part of Terreno, the batteries-included TypeScript framework for universal apps.",
|
|
12
12
|
"devDependencies": {
|
|
13
13
|
"@biomejs/biome": "^2.3.6",
|
|
14
|
-
"@terreno/test": "57.
|
|
14
|
+
"@terreno/test": "57.21.0",
|
|
15
15
|
"@types/bun": "^1.4.0",
|
|
16
16
|
"@types/express": "^5.0.6",
|
|
17
17
|
"@types/supertest": "^7.2.0",
|
|
@@ -45,13 +45,15 @@
|
|
|
45
45
|
"compile": "bun tsc",
|
|
46
46
|
"compile:watch": "bun tsc -w",
|
|
47
47
|
"dev": "bun tsc -w",
|
|
48
|
-
"lint": "biome check ./src",
|
|
49
|
-
"lint:fix": "biome check --write ./src",
|
|
48
|
+
"lint": "biome check ./src ./e2e",
|
|
49
|
+
"lint:fix": "biome check --write ./src ./e2e",
|
|
50
|
+
"smoke": "bun e2e/smoke.ts",
|
|
50
51
|
"test": "bun test",
|
|
51
52
|
"test:agent": "AGENT=1 bun test --only-failures",
|
|
52
53
|
"test:ci": "bun test",
|
|
53
|
-
"test:coverage": "bun run ../scripts/check-coverage.ts"
|
|
54
|
+
"test:coverage": "bun run ../scripts/check-coverage.ts",
|
|
55
|
+
"typecheck": "tsc -p tsconfig.typecheck.json"
|
|
54
56
|
},
|
|
55
57
|
"types": "dist/index.d.ts",
|
|
56
|
-
"version": "57.
|
|
58
|
+
"version": "57.21.0"
|
|
57
59
|
}
|
|
@@ -20,6 +20,7 @@ const makeAnnouncement = (overrides: Partial<AnnouncementDocument> = {}): Announ
|
|
|
20
20
|
audience: {},
|
|
21
21
|
body: "Body",
|
|
22
22
|
created: new Date(),
|
|
23
|
+
deleted: false,
|
|
23
24
|
expiresAt: undefined,
|
|
24
25
|
platforms: ["ios", "android", "web"],
|
|
25
26
|
priority: 0,
|
|
@@ -165,7 +166,7 @@ describe("pending helpers", () => {
|
|
|
165
166
|
it("matchAudienceByType honors staff, patient, and all", () => {
|
|
166
167
|
const staffUser = {admin: true};
|
|
167
168
|
const patientUser = {admin: false};
|
|
168
|
-
const isStaff = (user: {admin?: boolean})
|
|
169
|
+
const isStaff = (user: unknown): boolean => (user as {admin?: boolean}).admin === true;
|
|
169
170
|
|
|
170
171
|
assert.isTrue(
|
|
171
172
|
matchAudienceByType({
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {PermissionMethod} from "@terreno/api";
|
|
1
|
+
import type {FindExactlyOnePlugin, FindOneOrNonePlugin, PermissionMethod} from "@terreno/api";
|
|
2
2
|
import type {Model, Types} from "mongoose";
|
|
3
3
|
|
|
4
4
|
export type AnnouncementStatus = "draft" | "published" | "archived";
|
|
@@ -44,7 +44,9 @@ export interface AnnouncementDocument {
|
|
|
44
44
|
updated: Date;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
export type AnnouncementModel = Model<AnnouncementDocument
|
|
47
|
+
export type AnnouncementModel = Model<AnnouncementDocument> &
|
|
48
|
+
FindExactlyOnePlugin<AnnouncementDocument> &
|
|
49
|
+
FindOneOrNonePlugin<AnnouncementDocument>;
|
|
48
50
|
|
|
49
51
|
export interface AnnouncementAcknowledgementDocument {
|
|
50
52
|
_id: Types.ObjectId;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"noEmit": true,
|
|
5
|
+
"composite": false,
|
|
6
|
+
"incremental": false,
|
|
7
|
+
"declaration": false,
|
|
8
|
+
"declarationMap": false,
|
|
9
|
+
"types": [
|
|
10
|
+
"node",
|
|
11
|
+
"bun"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"exclude": [
|
|
15
|
+
"node_modules",
|
|
16
|
+
"dist"
|
|
17
|
+
]
|
|
18
|
+
}
|