@strifeapp/astro 1.0.30-beta.0 → 1.3.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/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 Strife
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md CHANGED
@@ -1,86 +1,179 @@
1
1
  # @strifeapp/astro
2
2
 
3
- ## ⚠️ Important Notes
3
+ [![npm version](https://img.shields.io/npm/v/@strifeapp/astro.svg)](https://www.npmjs.com/package/@strifeapp/astro)
4
4
 
5
- 2. **indexMapping.js**: This file should NOT be converted to TypeScript as it runs inside RavenDB's JavaScript indexing engine. Keep it as plain JavaScript.
5
+ Official [Strife](https://strife.app) integration for [Astro](https://astro.build). It connects your Astro site to a Strife (RavenDB-backed) content store and exposes an initialized document store to your pages through a `strife:store` virtual module — resolved at runtime, so the same build runs against any environment and secrets are never baked into the bundle.
6
6
 
7
- 3. **Module References**: The vite plugin reads `indexMapping.js` at build time using `readFileSync`, so make sure the path resolution in `vite-plugin-strife-store.ts` is correct for your build setup.
7
+ ## Contents
8
8
 
9
- ## Structure
9
+ - [Installation](#installation)
10
+ - [Requirements](#requirements)
11
+ - [Usage](#usage)
12
+ - [Reading content](#reading-content)
13
+ - [TypeScript: typing `strife:store`](#typescript-typing-strifestore)
14
+ - [Direct Vite plugin](#direct-vite-plugin)
15
+ - [Configuration](#configuration)
16
+ - [Environment variables](#environment-variables)
17
+ - [Generating `STRIFE_SECRET`](#generating-strife_secret)
18
+ - [Platform size limits](#platform-size-limits)
19
+ - [How it works](#how-it-works)
20
+ - [License](#license)
10
21
 
11
- ```
12
- src/
13
- ├── index.ts # Main Astro integration export
14
- ├── vite-plugin-strife-store.ts # Vite plugin for virtual module
15
- ├── vite-plugin-strife-store-entry.ts # Entry point for standalone plugin export
16
- ├── env.ts # Environment variable loader
17
- ├── indexMapping.js # RavenDB index mapping functions (JavaScript!)
18
- └── services/
19
- └── ImageService.ts # Image service (needs implementation)
22
+ ## Installation
23
+
24
+ ```bash
25
+ npx astro add @strifeapp/astro
20
26
  ```
21
27
 
22
- ## Building
28
+ Or install manually and add the integration to your config yourself:
23
29
 
24
30
  ```bash
25
- npm install
26
- npm run build
31
+ npm install @strifeapp/astro
27
32
  ```
28
33
 
29
- This will compile TypeScript and generate type definitions in the `dist/` folder.
34
+ ## Requirements
30
35
 
31
- ## Environment Variables
36
+ - **Astro 5 or 6** — `astro` is a peer dependency (`^5.0.0 || ^6.0.0`). Astro 5+ is required because the integration uses [`astro:env`](https://docs.astro.build/en/guides/environment-variables/) for runtime-safe secret resolution.
37
+ - **Node 22.12+**.
38
+ - A **Strife / RavenDB content store** and a **client certificate** registered with it (PFX, any key type — RSA or ECDSA).
32
39
 
33
- Required environment variables:
34
-
35
- - `STRIFE_DATABASE_URLS` - Comma-separated list of RavenDB URLs
36
- - `STRIFE_DATABASE` - Database name
37
- - `STRIFE_CERTIFICATE` - Base64-encoded PFX certificate (optional for development)
38
- - `STRIFE_CERTIFICATE_PASSWORD` - Certificate password (optional for development)
40
+ `ravendb` is installed as a dependency; you do not need to install it yourself.
39
41
 
40
42
  ## Usage
41
43
 
42
- In your Astro project:
44
+ Register the integration in your Astro config:
43
45
 
44
46
  ```typescript
45
47
  import { defineConfig } from 'astro/config';
46
- import strifeIntegration from '@strifeapp/astro';
48
+ import strife from '@strifeapp/astro';
47
49
 
48
50
  export default defineConfig({
49
51
  integrations: [
50
- strifeIntegration({
51
- // Optional: Override environment variables
52
- urls: ['https://your-ravendb.com'],
52
+ strife({
53
+ // All fields are optional; environment variables take precedence (see Configuration).
54
+ urls: ['https://your-ravendb.example.com'],
53
55
  database: 'your-database',
54
- collections: [
55
- { name: 'Posts' },
56
- { name: 'Pages' },
57
- ],
56
+ collections: [{ name: 'Posts' }, { name: 'Pages' }],
58
57
  }),
59
58
  ],
60
59
  });
61
60
  ```
62
61
 
63
- ## What This Integration Does
62
+ ### Reading content
63
+
64
+ Read from the store anywhere in your site through the `strife:store` virtual module:
65
+
66
+ ```typescript
67
+ ---
68
+ import { store } from 'strife:store';
69
+
70
+ const session = store.openSession();
71
+ // The integration deploys a `Content/ByUrl` index for URL-based lookups.
72
+ const page = await session
73
+ .query({ indexName: 'Content/ByUrl' })
74
+ .whereEquals('url', Astro.url.pathname)
75
+ .firstOrNull();
76
+ ---
77
+ ```
78
+
79
+ ### TypeScript: typing `strife:store`
80
+
81
+ Typed ambient declarations for the `strife:store` virtual module are not bundled in this release. Until a typed surface is published, add your own declaration (e.g. in `src/env.d.ts`):
82
+
83
+ ```typescript
84
+ declare module 'strife:store' {
85
+ import type { DocumentStore } from 'ravendb';
86
+ export const store: DocumentStore;
87
+ }
88
+ ```
89
+
90
+ ### Direct Vite plugin
91
+
92
+ The underlying Vite plugin is also exported standalone:
93
+
94
+ ```typescript
95
+ import { vitePluginStrifeStore } from '@strifeapp/astro/vite-plugin-strife-store';
96
+ ```
97
+
98
+ ## Configuration
99
+
100
+ `strife(options?)` accepts:
101
+
102
+ | Option | Type | Description |
103
+ | --- | --- | --- |
104
+ | `urls` | `string[]` | RavenDB server URLs. |
105
+ | `database` | `string` | Database name. |
106
+ | `collections` | `{ name: string }[]` | Collections to index (default: `Posts`). |
107
+
108
+ > **Credentials are not build-time options.** `certificate` and `password` are **not** accepted as integration options — passing them would bake the secret into the built bundle. Provide them at runtime via `STRIFE_SECRET` or the `STRIFE_CERTIFICATE` / `STRIFE_CERTIFICATE_PASSWORD` env vars (below). The integration strips any `certificate`/`password` it receives as options before generating the store module.
109
+
110
+ ### Environment variables
111
+
112
+ The integration registers `STRIFE_*` as [`astro:env`](https://docs.astro.build/en/guides/environment-variables/) **server secret** variables, so you do not need `import 'dotenv/config'` or any other loader — just set them:
113
+
114
+ | Variable | Maps to |
115
+ | --- | --- |
116
+ | `STRIFE_SECRET` | All of the below, packed into one value (see [Generating `STRIFE_SECRET`](#generating-strife_secret)) |
117
+ | `STRIFE_DATABASE_URLS` | `urls` (comma-separated) |
118
+ | `STRIFE_DATABASE` | `database` |
119
+ | `STRIFE_CERTIFICATE` | base64-encoded PFX (optional in development) |
120
+ | `STRIFE_CERTIFICATE_PASSWORD` | certificate password |
121
+
122
+ Set them in a local `.env` for development, or in your host's environment (Vercel project settings, Kubernetes secrets, etc.) for deployment. Because they are `astro:env` server secrets, they are **read at runtime via `getSecret` and never inlined into the built bundle**.
123
+
124
+ `STRIFE_SECRET` is a single, compact value that bundles the URLs, database, certificate, and password — convenient when one secret is easier to manage than four. It is **additive**: when set, each field it carries takes priority; when unset, the four individual variables work exactly as before.
125
+
126
+ **Resolution order, per field:** `STRIFE_SECRET` → individual `STRIFE_*` env var → non-secret integration option (`urls`/`database` only) → built-in default.
127
+
128
+ > Reading via `astro:env` (rather than `process.env`) is deliberate: in `astro dev`, Astro does **not** populate `process.env` from `.env`, so a `process.env`-based read would be empty locally. `astro:env`'s `getSecret` reads `.env` in dev and the host environment in production, with no extra setup.
129
+
130
+ ### Generating `STRIFE_SECRET`
131
+
132
+ `STRIFE_SECRET` has the form `v1.<meta>.<cert>`: a version tag, your `urls`/`database`/`password` as base64url-encoded JSON, and your PFX certificate as base64url-encoded **raw bytes** (encoded once — ~33% smaller than embedding an already-base64 cert in JSON). The format is key-algorithm-agnostic: the same `STRIFE_SECRET` works whether the certificate inside is RSA or ECDSA.
133
+
134
+ The blob format is stable, so you can generate it yourself in a few lines: base64url-encode `JSON.stringify({ urls, database, password })`, base64url-encode the raw `.pfx` bytes, and join them as `v1.<meta>.<cert>`. Contributors working in the [repository](https://github.com/wieldyapp/wieldy) can use the bundled helper:
135
+
136
+ ```bash
137
+ # password via env var (recommended)
138
+ STRIFE_PFX_PASSWORD='your-cert-password' node --experimental-strip-types \
139
+ scripts/pack-secrets.mjs \
140
+ --pfx ./client.pfx \
141
+ --urls https://your-ravendb.example.com \
142
+ --database your-database
143
+
144
+ # …or pipe the password instead of putting it in the environment
145
+ printf '%s' "$PFX_PW" | node --experimental-strip-types \
146
+ scripts/pack-secrets.mjs --pfx ./client.pfx --urls … --database … --password-stdin
147
+ ```
148
+
149
+ The password is read only from `STRIFE_PFX_PASSWORD` or `--password-stdin` — never a CLI flag, since process arguments are world-readable. The helper prints `STRIFE_SECRET=<blob>`; pipe it straight into your host's secret store rather than echoing it (it otherwise lands in shell history). `--experimental-strip-types` is unnecessary on Node ≥ 23.6 / 22.18. (The helper script ships with the repository, not the published package; a `strife secrets pack` CLI command is planned.)
150
+
151
+ ### Platform size limits
152
+
153
+ The certificate dominates the value's size, and base64 of an encrypted PFX is incompressible — so a large certificate may not fit a platform's per-variable env limit. This is a property of the certificate, not of `STRIFE_SECRET` (the standalone `STRIFE_CERTIFICATE` has the same ceiling).
154
+
155
+ | Platform | Per-variable limit | A ~2 KB ECDSA cert (~1.8 KB blob) | A ~4 KB RSA-4096 cert (~5.5 KB blob) |
156
+ | --- | --- | --- | --- |
157
+ | Kubernetes secret | ~1 MiB | ✅ | ✅ |
158
+ | Vercel (serverless) | 64 KB total | ✅ | ✅ |
159
+ | Vercel (edge) / Cloudflare Workers | ~5 KB | ✅ | ❌ |
160
+ | Netlify build-time | 5,000 chars | ✅ | ❌ |
161
+ | AWS Lambda / Netlify Functions (SSR) | 256 chars / 4 KB total | ❌ | ❌ |
162
+
163
+ If you hit a limit, the fix is a **smaller certificate** — RavenDB authenticates clients by thumbprint, not key algorithm, so you can register a compact **ECDSA P-256** client certificate (≈1.2 KB PFX) and use it instead of an RSA-4096 one. No encoding change helps a large cert; a smaller key does.
64
164
 
65
- 1. **Connects to RavenDB** using environment variables or provided config
66
- 2. **Deploys a search index** called `Content_ByUrl` for content search
67
- 3. **Indexes documents** from specified collections (default: Posts)
68
- 4. **Creates a virtual module** `strife:store` that exports the RavenDB DocumentStore
69
- 5. **Bulk inserts templates** into the Templates collection
165
+ ## How it works
70
166
 
71
- ## Recovery Process
167
+ On `astro:config:setup` the integration registers the `STRIFE_*` env schema and a Vite plugin. When the `strife:store` module is first evaluated (at build for prerendered pages, at request time for SSR), the plugin:
72
168
 
73
- The source was recovered by:
169
+ 1. Resolves configuration at runtime via `getSecret` (never baked into the bundle).
170
+ 2. Connects to RavenDB using the resolved URLs, database, and client certificate.
171
+ 3. Deploys a `Content/ByUrl` multi-map index (`deploymentMode: 'Rolling'`) for URL-based content lookup.
172
+ 4. Bulk-inserts the configured collections into the `Templates` collection.
173
+ 5. Exposes the initialized RavenDB `DocumentStore` through the `strife:store` virtual module.
74
174
 
75
- 1. Analyzing the minified compiled output
76
- 2. Identifying variable names and function patterns
77
- 3. Extracting the embedded index mapping source code
78
- 4. Reconstructing TypeScript types from `.d.ts` files
79
- 5. Reverse engineering the build configuration
175
+ > The `Content/ByUrl` index is deployed with a bundled helper source (`localized-content-index.js`) that runs inside RavenDB's Jint engine. It is intentionally plain ES5 — do not transpile it.
80
176
 
81
- ## Next Steps
177
+ ## License
82
178
 
83
- - [ ] Implement the ImageService if needed
84
- - [ ] Verify the build output matches the original
85
- - [ ] Add tests
86
- - [ ] Update version number if making changes
179
+ ISC © [Strife](https://strife.app)
@@ -0,0 +1,17 @@
1
+ ---
2
+ /**
3
+ * Loads the Strife live-preview client (`@strifeapp/strife`) — but only when the
4
+ * request is an authenticated editor preview, i.e. `Astro.locals.editMode` is
5
+ * `true` (set by the edit-mode middleware). Place it once in your layout, the
6
+ * same way you'd add Astro's <ClientRouter />:
7
+ *
8
+ * ---
9
+ * import LivePreview from '@strifeapp/astro/LivePreview.astro';
10
+ * ---
11
+ * <head>…<LivePreview /></head>
12
+ *
13
+ * Normal visitors get nothing; the SDK chunk is fetched only in edit mode.
14
+ */
15
+ const editMode = Astro.locals.editMode ?? false;
16
+ ---
17
+ {editMode && <script>import '@strifeapp/strife';</script>}
@@ -0,0 +1,9 @@
1
+ import { MiddlewareHandler } from 'astro';
2
+ /**
3
+ * Sets `Astro.locals.editMode` on every request so pages can branch their SSR
4
+ * output (draft vs published). Registered automatically by the integration via
5
+ * `addMiddleware` (see src/index.ts); also exported for manual wiring if a
6
+ * consumer prefers to compose middleware explicitly.
7
+ */
8
+ export declare const onRequest: MiddlewareHandler;
9
+ //# sourceMappingURL=edit-mode-middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit-mode-middleware.d.ts","sourceRoot":"","sources":["../src/edit-mode-middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAG/C;;;;;GAKG;AACH,eAAO,MAAM,SAAS,EAAE,iBAGvB,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { editMode as t } from "./edit-mode.js";
2
+ const a = async (e, o) => (e.locals.editMode = await t(e), o());
3
+ export {
4
+ a as onRequest
5
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Verify a Strife preview (edit-mode) token.
3
+ *
4
+ * Mirrors `Strife.Security.Tokenizer.Verify` on the backend: an HS256 JWT
5
+ * signed with the team's secret, carrying a `workspace` claim equal to the
6
+ * team id. A valid signature AND a matching workspace claim means the request
7
+ * is an authenticated editor previewing THIS team's content.
8
+ *
9
+ * Pure (no Astro / `astro:env` imports) so it is unit-testable in isolation.
10
+ *
11
+ * Note: the backend signs with ASCII bytes; for ASCII secrets `TextEncoder`
12
+ * (UTF-8) produces identical bytes, so verification matches.
13
+ */
14
+ export declare function verifyEditModeToken(token: string, secret: string, teamId: string): Promise<boolean>;
15
+ /**
16
+ * Resolve the (secret, teamId) pair used to verify a preview token, from either
17
+ * source the integration supports — blob first, then the legacy env vars:
18
+ *
19
+ * 1. the consolidated `STRIFE_SECRET` blob, when it packs `teamId` + `previewSecret`;
20
+ * 2. the legacy individual `SECRET` + `TEAM_ID` env vars (the older format).
21
+ *
22
+ * Falls through to (2) when the blob is absent, malformed, or does not carry the
23
+ * preview fields — mirroring the store's blob-first / individual-vars resolution.
24
+ * Pure (only `decodeSecrets`) so it is unit-testable in isolation.
25
+ */
26
+ export declare function resolveEditModeCreds(sources: {
27
+ blob?: string | null;
28
+ envSecret?: string | null;
29
+ envTeamId?: string | null;
30
+ }): {
31
+ secret: string;
32
+ teamId: string;
33
+ } | null;
34
+ //# sourceMappingURL=edit-mode-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit-mode-token.d.ts","sourceRoot":"","sources":["../src/edit-mode-token.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAC5C,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAiB5C"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Resolve edit mode for the current request, server-side.
3
+ *
4
+ * Reads the `?token=` preview token from the URL and verifies it against the
5
+ * team's secret + id, resolved from EITHER the consolidated `STRIFE_SECRET`
6
+ * blob (when it packs `teamId` + `previewSecret`) OR the legacy individual
7
+ * `SECRET` + `TEAM_ID` env vars. Returns `false` for normal visitors (no token,
8
+ * bad token) and `true` for an authenticated Strife editor previewing this team:
9
+ *
10
+ * const edit = await editMode(Astro);
11
+ * { edit ? <DraftView /> : <PublishedView /> }
12
+ */
13
+ export declare function editMode(astro: {
14
+ url: URL;
15
+ }): Promise<boolean>;
16
+ //# sourceMappingURL=edit-mode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit-mode.d.ts","sourceRoot":"","sources":["../src/edit-mode.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;GAWG;AACH,wBAAsB,QAAQ,CAAC,KAAK,EAAE;IAAE,GAAG,EAAE,GAAG,CAAA;CAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAYpE"}
@@ -0,0 +1,36 @@
1
+ import { getSecret as n } from "astro:env/server";
2
+ import { jwtVerify as c } from "jose";
3
+ import { decodeSecrets as o } from "@strifeapp/strife/secrets";
4
+ async function d(e, t, r) {
5
+ try {
6
+ const { payload: a } = await c(e, new TextEncoder().encode(t), {
7
+ algorithms: ["HS256"]
8
+ });
9
+ return a.workspace === r;
10
+ } catch {
11
+ return !1;
12
+ }
13
+ }
14
+ function i(e) {
15
+ if (e.blob)
16
+ try {
17
+ const t = o(e.blob);
18
+ if (t?.previewSecret && t.teamId)
19
+ return { secret: t.previewSecret, teamId: t.teamId };
20
+ } catch {
21
+ }
22
+ return e.envSecret && e.envTeamId ? { secret: e.envSecret, teamId: e.envTeamId } : null;
23
+ }
24
+ async function S(e) {
25
+ const t = e.url.searchParams.get("token");
26
+ if (!t) return !1;
27
+ const r = i({
28
+ blob: n("STRIFE_SECRET"),
29
+ envSecret: n("SECRET"),
30
+ envTeamId: n("TEAM_ID")
31
+ });
32
+ return r ? d(t, r.secret, r.teamId) : !1;
33
+ }
34
+ export {
35
+ S as editMode
36
+ };
package/dist/index.d.ts CHANGED
@@ -11,5 +11,18 @@ export interface Collection {
11
11
  name: string;
12
12
  [key: string]: any;
13
13
  }
14
+ declare global {
15
+ namespace App {
16
+ interface Locals {
17
+ /**
18
+ * `true` when the current request is an authenticated Strife editor
19
+ * previewing this team's content (a valid `?token=` was verified). Set on
20
+ * every request by the edit-mode middleware. Use it to branch SSR output,
21
+ * e.g. render draft vs published content.
22
+ */
23
+ editMode?: boolean;
24
+ }
25
+ }
26
+ }
14
27
  export default function strifeIntegration(options?: IntegrationOptions): AstroIntegration;
15
28
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG5C,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAMD,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,gBAAgB,CAwBxF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAI5C,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,GAAG,CAAC;QACZ,UAAU,MAAM;YACd;;;;;eAKG;YACH,QAAQ,CAAC,EAAE,OAAO,CAAC;SACpB;KACF;CACF;AAMD,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,gBAAgB,CAwDxF"}
package/dist/index.js CHANGED
@@ -1,27 +1,47 @@
1
- import { v as n } from "./vite-plugin-strife-store-DkxIFvv2.js";
2
- const o = {
1
+ import { envField as e } from "astro/config";
2
+ import { v as c } from "./vite-plugin-strife-store-B-B_HXbA.js";
3
+ const i = {
3
4
  type: "pfx"
4
5
  };
5
- function s(t) {
6
+ function p(t) {
6
7
  return {
7
8
  name: "@strife/astro",
8
9
  hooks: {
9
- "astro:config:setup": ({ updateConfig: i }) => {
10
- const r = {
11
- ...o,
10
+ "astro:config:setup": ({ updateConfig: r, addMiddleware: s }) => {
11
+ const n = {
12
+ ...i,
12
13
  ...t ? Object.fromEntries(
13
- Object.entries(t).filter(([, e]) => e != null)
14
+ Object.entries(t).filter(([, o]) => o != null)
14
15
  ) : {}
15
16
  };
16
- i({
17
+ r({
18
+ env: {
19
+ schema: {
20
+ // Consolidated single-var secret (packs urls/database/certificate/password,
21
+ // plus teamId/previewSecret for edit-mode token verification).
22
+ // Resolved first, ahead of the individual vars below; see vite-plugin-strife-store.
23
+ STRIFE_SECRET: e.string({ context: "server", access: "secret", optional: !0 }),
24
+ STRIFE_DATABASE_URLS: e.string({ context: "server", access: "secret", optional: !0 }),
25
+ STRIFE_DATABASE: e.string({ context: "server", access: "secret", optional: !0 }),
26
+ STRIFE_CERTIFICATE: e.string({ context: "server", access: "secret", optional: !0 }),
27
+ STRIFE_CERTIFICATE_PASSWORD: e.string({ context: "server", access: "secret", optional: !0 }),
28
+ // Legacy individual edit-mode token vars (the older format). editMode falls
29
+ // back to these when STRIFE_SECRET does not pack teamId/previewSecret.
30
+ SECRET: e.string({ context: "server", access: "secret", optional: !0 }),
31
+ TEAM_ID: e.string({ context: "server", access: "secret", optional: !0 })
32
+ }
33
+ },
17
34
  vite: {
18
- plugins: [n(r)]
35
+ plugins: [c(n)]
19
36
  }
37
+ }), s({
38
+ entrypoint: "@strifeapp/astro/edit-mode-middleware",
39
+ order: "pre"
20
40
  });
21
41
  }
22
42
  }
23
43
  };
24
44
  }
25
45
  export {
26
- s as default
46
+ p as default
27
47
  };
@@ -0,0 +1,105 @@
1
+ import a from "serialize-javascript";
2
+ const s = "strife:store", r = "\0" + s;
3
+ function n(o) {
4
+ const e = { ...o };
5
+ return delete e.certificate, delete e.password, {
6
+ name: "strife:store",
7
+ resolveId(t) {
8
+ if (t === s)
9
+ return r;
10
+ },
11
+ load(t) {
12
+ if (t === r)
13
+ return `
14
+ import { DocumentStore } from "ravendb";
15
+ import { getSecret } from "astro:env/server";
16
+
17
+ const defaultConfig = ${a(e)};
18
+
19
+ // --- STRIFE_SECRET decode (keep in sync with @strifeapp/strife/secrets; format v1.<meta>.<cert>) ---
20
+ function decodeStrifeSecrets(value) {
21
+ const sections = value.split('.');
22
+ if (sections.length !== 3) {
23
+ throw new Error('STRIFE_SECRET: expected 3 dot-separated sections, got ' + sections.length);
24
+ }
25
+ const version = sections[0];
26
+ if (version !== 'v1') {
27
+ if (/^v\\d+$/.test(version)) return null; // known-shape future version -> treat as unset
28
+ throw new Error('STRIFE_SECRET: unrecognised format (missing version prefix)');
29
+ }
30
+ const b64url = /^[A-Za-z0-9_-]+$/;
31
+ if (!b64url.test(sections[1])) throw new Error('STRIFE_SECRET: meta section is not valid base64url');
32
+ if (!b64url.test(sections[2])) throw new Error('STRIFE_SECRET: cert section is not valid base64url');
33
+ let meta;
34
+ try {
35
+ meta = JSON.parse(Buffer.from(sections[1], 'base64url').toString('utf8'));
36
+ } catch (e) {
37
+ throw new Error('STRIFE_SECRET: meta section is not valid JSON'); // never echo the (secret) content
38
+ }
39
+ if (typeof meta !== 'object' || meta === null || !Array.isArray(meta.urls) || typeof meta.database !== 'string') {
40
+ throw new Error('STRIFE_SECRET: meta section missing required fields (urls, database)');
41
+ }
42
+ return {
43
+ urls: meta.urls,
44
+ database: meta.database,
45
+ password: meta.password,
46
+ type: meta.type,
47
+ certificate: Buffer.from(sections[2], 'base64url'), // raw PFX bytes, decoded once
48
+ };
49
+ }
50
+
51
+ const strifeSecretsBlob = getSecret('STRIFE_SECRET');
52
+ // null = known-shape future version -> treat as unset and fall back to the four vars.
53
+ const secrets = strifeSecretsBlob ? (decodeStrifeSecrets(strifeSecretsBlob) || {}) : {};
54
+
55
+ // Resolution priority: STRIFE_SECRET field -> individual STRIFE_* var -> integration option.
56
+ // Operator pinned to || with an empty string treated as unset (matches prior behaviour).
57
+ const urlsFromEnv = getSecret('STRIFE_DATABASE_URLS');
58
+ const urls = (secrets.urls && secrets.urls.length)
59
+ ? secrets.urls
60
+ : (urlsFromEnv ? urlsFromEnv.split(',') : (defaultConfig.urls || []));
61
+
62
+ const database = secrets.database || getSecret('STRIFE_DATABASE') || defaultConfig.database || '';
63
+
64
+ // From the blob the certificate is already raw PFX bytes (Buffer); from an env var or
65
+ // option it is a base64 string, decoded once here. Normalise to a Buffer either way.
66
+ let certificate;
67
+ if (secrets.certificate) {
68
+ certificate = secrets.certificate;
69
+ } else {
70
+ const certBase64 = getSecret('STRIFE_CERTIFICATE') || defaultConfig.certificate;
71
+ certificate = certBase64 ? Buffer.from(certBase64, 'base64') : undefined;
72
+ }
73
+
74
+ const password = secrets.password || getSecret('STRIFE_CERTIFICATE_PASSWORD') || defaultConfig.password;
75
+
76
+ // R9: a certificate without a password must not silently initialise an unauthenticated store.
77
+ if (certificate && !password) {
78
+ throw new Error('STRIFE_SECRET: a certificate was provided without a password (set STRIFE_CERTIFICATE_PASSWORD or include the password in STRIFE_SECRET)');
79
+ }
80
+
81
+ const hasAuth = certificate && password;
82
+
83
+ const authOptions = hasAuth ? {
84
+ type: secrets.type || defaultConfig.type || 'pfx',
85
+ certificate: certificate,
86
+ password,
87
+ } : null;
88
+
89
+ const store = new DocumentStore(
90
+ urls,
91
+ database,
92
+ authOptions || undefined,
93
+ ).initialize();
94
+
95
+ // Content/ByUrl + templates are deployed by 'strife push' (the versioned,
96
+ // operator-controlled path). The store only connects and reads — Astro no
97
+ // longer deploys the index or seeds templates.
98
+ export { store };
99
+ `;
100
+ }
101
+ };
102
+ }
103
+ export {
104
+ n as v
105
+ };
@@ -1,4 +1,4 @@
1
- import { v as t } from "./vite-plugin-strife-store-DkxIFvv2.js";
1
+ import { v as t } from "./vite-plugin-strife-store-B-B_HXbA.js";
2
2
  export {
3
3
  t as vitePluginStrifeStore
4
4
  };
@@ -1 +1 @@
1
- {"version":3,"file":"vite-plugin-strife-store.d.ts","sourceRoot":"","sources":["../src/vite-plugin-strife-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAEzC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAKlD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,kBAAkB,GAAG,YAAY,CAsF9E"}
1
+ {"version":3,"file":"vite-plugin-strife-store.d.ts","sourceRoot":"","sources":["../src/vite-plugin-strife-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAEzC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAKlD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,kBAAkB,GAAG,YAAY,CA6G9E"}