@pitlane/content 0.1.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.
@@ -0,0 +1,238 @@
1
+ import { Handle, RemixNode } from "remix/ui";
2
+ //#region src/types.d.ts
3
+ /**
4
+ * Standard Schema v1, declared here rather than depended on.
5
+ *
6
+ * The specification is frozen at version 1 and the interface is the whole of
7
+ * it, so a dependency would buy nothing this comment does not. Any conforming
8
+ * schema — `remix/data-schema`, Zod, Valibot, ArkType — satisfies it
9
+ * structurally.
10
+ *
11
+ * @see https://standardschema.dev
12
+ */
13
+ interface StandardSchemaV1<Output = unknown> {
14
+ readonly "~standard": {
15
+ readonly version: 1;
16
+ readonly vendor: string;
17
+ readonly validate: (value: unknown) => StandardSchemaResult<Output> | Promise<StandardSchemaResult<Output>>;
18
+ readonly types?: {
19
+ readonly output: Output;
20
+ } | undefined;
21
+ };
22
+ }
23
+ /** The result of validating a value against a {@link StandardSchemaV1}. */
24
+ type StandardSchemaResult<Output> = {
25
+ readonly value: Output;
26
+ readonly issues?: undefined;
27
+ } | {
28
+ readonly issues: readonly StandardSchemaIssue[];
29
+ };
30
+ /** One validation failure, with the path into the input that produced it. */
31
+ interface StandardSchemaIssue {
32
+ readonly message: string;
33
+ readonly path?: readonly (PropertyKey | {
34
+ readonly key: PropertyKey;
35
+ })[] | undefined;
36
+ }
37
+ /** The output type a schema produces. */
38
+ type InferSchema<S> = S extends StandardSchemaV1<infer Output> ? Output : never;
39
+ /**
40
+ * A resolved pointer from one entry to another, produced by
41
+ * `c.reference(collection)`.
42
+ *
43
+ * The `collection` type parameter is what stops a `Reference<"blog">` reaching
44
+ * `content.authors.getEntry`.
45
+ */
46
+ interface Reference<C extends string> {
47
+ collection: C;
48
+ id: string;
49
+ }
50
+ /** A reusable check, shaped as `remix/data-schema`'s `Check` is. */
51
+ interface SchemaCheck<Output> {
52
+ check: (value: Output) => boolean;
53
+ message?: string;
54
+ code?: string;
55
+ values?: Record<string, unknown>;
56
+ }
57
+ /** Where a value sits in the input being validated, and how it is being parsed. */
58
+ interface RunContext {
59
+ path: NonNullable<StandardSchemaIssue["path"]>;
60
+ options?: unknown;
61
+ }
62
+ /**
63
+ * The surface `remix/data-schema`'s combinators require of a schema they hold.
64
+ *
65
+ * `s.object`, `s.array`, and `s.optional` call `~run` rather than the Standard
66
+ * Schema `validate`, so a bare Standard Schema does not compose inside them and
67
+ * a reference that does not compose is not worth having. Declared here rather
68
+ * than imported: one type import would make Remix a requirement of reading a
69
+ * JSON file. Any schema satisfying it is accepted, whoever built it.
70
+ */
71
+ interface ChainableSchema<Input, Output> {
72
+ readonly "~standard": {
73
+ readonly version: 1;
74
+ readonly vendor: string;
75
+ readonly validate: (value: unknown) => StandardSchemaResult<Output>;
76
+ readonly types?: {
77
+ readonly input: Input;
78
+ readonly output: Output;
79
+ } | undefined;
80
+ };
81
+ "~run": (value: unknown, context: RunContext) => StandardSchemaResult<Output>;
82
+ pipe: (...checks: SchemaCheck<Output>[]) => ChainableSchema<Input, Output>;
83
+ refine: (predicate: (value: Output) => boolean, message?: string) => ChainableSchema<Input, Output>;
84
+ transform: <Next>(transformer: (value: Output) => Next) => ChainableSchema<Input, Next>;
85
+ }
86
+ /** The schema `c.reference(collection)` returns. */
87
+ type ReferenceSchema<C extends string> = ChainableSchema<string, Reference<C>>;
88
+ /** A heading collected from a Markdown or MDX document. */
89
+ interface Heading {
90
+ depth: number;
91
+ slug: string;
92
+ text: string;
93
+ }
94
+ /** What `entry.render()` resolves to. */
95
+ interface RenderedEntry {
96
+ Content: (handle: Handle<Record<string, unknown>>) => () => RemixNode;
97
+ headings: Heading[];
98
+ }
99
+ /** One entry of a collection, as a caller sees it. */
100
+ interface Entry<Data> {
101
+ id: string;
102
+ collection: string;
103
+ data: Data;
104
+ filePath?: string;
105
+ render(): Promise<RenderedEntry>;
106
+ }
107
+ /**
108
+ * One entry of `collection`, named the way an application thinks of it:
109
+ * `CollectionEntry<typeof content.blog>`.
110
+ *
111
+ * {@link Entry} is keyed by the shape of the data, which is what the query
112
+ * methods resolve to and all this package needs internally. An application
113
+ * has the collection rather than the shape, and pulling one out of the other
114
+ * is a conditional type every consumer would otherwise write for itself.
115
+ */
116
+ type CollectionEntry<C> = C extends Collection<string, infer Data> ? Entry<Data> : never;
117
+ /** The query surface of one collection. */
118
+ interface Collection<Name extends string, Data> {
119
+ getCollection(filter?: (entry: Entry<Data>) => unknown): Promise<Entry<Data>[]>;
120
+ getEntry(id: string | Reference<Name>): Promise<Entry<Data> | undefined>;
121
+ }
122
+ /** The raw source of an entry's document, before anything renders it. */
123
+ interface EntryBody {
124
+ format: "md" | "mdx";
125
+ source: string;
126
+ }
127
+ /** What a {@link ContentLoader} writes into the store for one entry. */
128
+ interface LoadedEntry {
129
+ id: string;
130
+ data: unknown;
131
+ filePath?: string;
132
+ body?: EntryBody;
133
+ }
134
+ /** What a {@link LiveLoader} returns for one entry. */
135
+ interface LiveEntry<Data = Record<string, unknown>> {
136
+ id: string;
137
+ data: Data;
138
+ body?: EntryBody;
139
+ }
140
+ /** What a loader is handed to read its source and validate what it found. */
141
+ interface LoaderContext {
142
+ collection: string;
143
+ /**
144
+ * The project root a relative path resolves against.
145
+ *
146
+ * `process.cwd()` at runtime, and the Vite root under `contentLayer()`, which is
147
+ * what keeps a collection pointing at the same files when the build runs
148
+ * from somewhere else.
149
+ */
150
+ root: string;
151
+ parseData<D>(input: {
152
+ id: string;
153
+ data: unknown;
154
+ filePath?: string;
155
+ }): Promise<D>;
156
+ store: {
157
+ set(entry: LoadedEntry): void;
158
+ };
159
+ }
160
+ /**
161
+ * A loader that resolves a whole collection by filling a store.
162
+ *
163
+ * Its shape is the claim that one execution produces the complete answer, which
164
+ * is what lets `contentLayer()` run it during the build and inline the result.
165
+ */
166
+ interface ContentLoader {
167
+ name: string;
168
+ load(context: LoaderContext): Promise<void> | void;
169
+ watchedPaths?(): string[];
170
+ }
171
+ /**
172
+ * A loader that answers one query at a time.
173
+ *
174
+ * There is no store to fill and so nothing to serialize: the only way to get
175
+ * entries out of it is to ask, which means asking on every read.
176
+ */
177
+ interface LiveLoader<Data = Record<string, unknown>> {
178
+ name: string;
179
+ loadCollection(): Promise<LiveEntry<Data>[]>;
180
+ loadEntry(id: string): Promise<LiveEntry<Data> | undefined>;
181
+ }
182
+ /**
183
+ * Either kind of loader.
184
+ *
185
+ * A `LiveLoader`'s own `Data` parameter is for whoever writes it; a collection
186
+ * accepts any of them, because what a loader hands back is unvalidated until
187
+ * `parseData` has seen it.
188
+ */
189
+ type Loader = ContentLoader | LiveLoader<unknown>;
190
+ /** What `c.collection({ loader, schema })` returns. */
191
+ interface CollectionDefinition<S extends StandardSchemaV1 = StandardSchemaV1> {
192
+ loader: Loader;
193
+ schema: S;
194
+ }
195
+ /** The builder `createContent` hands to its callback. */
196
+ interface ContentBuilder {
197
+ collection<S extends StandardSchemaV1>(input: {
198
+ loader: Loader;
199
+ schema: S;
200
+ }): CollectionDefinition<S>;
201
+ reference<C extends string>(collection: C): ReferenceSchema<C>;
202
+ }
203
+ /** The object `createContent` returns: one {@link Collection} per key. */
204
+ type Content<T extends Record<string, CollectionDefinition>> = { [K in keyof T]: Collection<K & string, InferSchema<T[K]["schema"]>>; };
205
+ /** How `contentLayer()` spells an entry's body in the manifest it emits. */
206
+ type PrebuiltBody = {
207
+ format: "md";
208
+ html: string;
209
+ headings?: unknown;
210
+ } | {
211
+ format: "mdx";
212
+ module: Record<string, unknown>;
213
+ };
214
+ /** One entry in the manifest `contentLayer()` emits. */
215
+ interface PrebuiltEntry {
216
+ id: string;
217
+ data: unknown;
218
+ filePath?: string;
219
+ body?: PrebuiltBody;
220
+ }
221
+ /**
222
+ * The manifest `contentLayer()` emits, keyed by collection name.
223
+ *
224
+ * A collection missing from it was not prebuilt, and its absence is what tells
225
+ * the runtime to use the loader.
226
+ */
227
+ type PrebuiltCollections = Record<string, PrebuiltEntry[]>;
228
+ /** Options handed to a `loaders.glob` `generateId` function. */
229
+ interface GenerateIdOptions {
230
+ /** The matched path, relative to `base`. */
231
+ entry: string;
232
+ /** The directory the pattern resolved against. */
233
+ base: string;
234
+ /** The parsed data of the entry, for an id derived from frontmatter. */
235
+ data: Record<string, unknown>;
236
+ }
237
+ //#endregion
238
+ export { Reference as _, ContentBuilder as a, EntryBody as c, LiveEntry as d, LiveLoader as f, PrebuiltCollections as g, LoaderContext as h, Content as i, GenerateIdOptions as l, Loader as m, CollectionDefinition as n, ContentLoader as o, LoadedEntry as p, CollectionEntry as r, Entry as s, Collection as t, Heading as u, RenderedEntry as v };
@@ -0,0 +1,18 @@
1
+ import { Plugin } from "vite";
2
+ //#region src/vite.d.ts
3
+ /**
4
+ * Resolves content collections during the build and inlines them.
5
+ *
6
+ * `entry` names the module that declares the collections, and defaults to
7
+ * `app/content.ts`. It is an ordinary application module rather than a
8
+ * configuration file this plugin owns; being told which module it is is the
9
+ * whole of the configuration.
10
+ *
11
+ * The result is a host with no filesystem serving the collections the
12
+ * application declared, with no change to the declarations.
13
+ */
14
+ declare function contentLayer(options?: {
15
+ entry?: string;
16
+ }): Plugin;
17
+ //#endregion
18
+ export { contentLayer };
package/dist/vite.mjs ADDED
@@ -0,0 +1,262 @@
1
+ import { closePrebuild, openPrebuild } from "./prebuild.mjs";
2
+ import { BODY_PREFIX, manifestModule } from "./codegen.mjs";
3
+ import { dirname, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { createRunnableDevEnvironment, createServer } from "vite";
6
+ //#region src/vite.ts
7
+ const MANIFEST_OWNER = "@pitlane/content";
8
+ const MANIFEST_SPECIFIER = `${MANIFEST_OWNER}/internal/manifest`;
9
+ const VIRTUAL_MANIFEST = "\0pitlane-content/manifest";
10
+ /**
11
+ * Resolves content collections during the build and inlines them.
12
+ *
13
+ * `entry` names the module that declares the collections, and defaults to
14
+ * `app/content.ts`. It is an ordinary application module rather than a
15
+ * configuration file this plugin owns; being told which module it is is the
16
+ * whole of the configuration.
17
+ *
18
+ * The result is a host with no filesystem serving the collections the
19
+ * application declared, with no change to the declarations.
20
+ */
21
+ function contentLayer(options) {
22
+ let entry = options?.entry ?? "app/content.ts";
23
+ let root = process.cwd();
24
+ let collections = {};
25
+ let bodies = /* @__PURE__ */ new Map();
26
+ let watched = [];
27
+ let resolution = {};
28
+ let server;
29
+ let pending;
30
+ let queue = Promise.resolve();
31
+ async function prebuild(warn) {
32
+ let loaded = await inPrebuildServer(root, entry, resolution, (paths) => {
33
+ watched = [.../* @__PURE__ */ new Set([...watched, ...paths])];
34
+ for (let path of watched) server?.watcher.add(path);
35
+ });
36
+ collections = loaded.collections;
37
+ watched = loaded.watched;
38
+ for (let path of watched) server?.watcher.add(path);
39
+ for (let name of loaded.configuredSatteri) warn(`Collection "${name}" configures loader options.satteri, but contentLayer() prebuilt it, so vite-plugin-satteri renders it and those options do nothing. Move the plugins into satteri() in your Vite config, or drop contentLayer() for this collection.`);
40
+ }
41
+ /**
42
+ * Prebuilds once, and again only after a content change invalidated it.
43
+ *
44
+ * Runs strictly one at a time. Prebuild mode is a process-global handshake
45
+ * with `createContent`, so a second run overlapping the first would close
46
+ * the channel underneath it and leave the loaders resolving against the
47
+ * wrong root.
48
+ */
49
+ function ready(warn) {
50
+ let run = () => prebuild(warn);
51
+ pending ??= queue = queue.then(run, run);
52
+ return pending;
53
+ }
54
+ return {
55
+ name: "pitlane-content",
56
+ enforce: "post",
57
+ /**
58
+ * Two ways the runtime can end up with the manifest the package ships
59
+ * — the one that registers nothing — instead of the one emitted below.
60
+ *
61
+ * A server build externalizes its dependencies, so the package has to
62
+ * be bundled for the replacement to reach the runtime at all. In dev
63
+ * nothing is bundled, but the dependency optimizer pre-bundles an
64
+ * installed dependency before any plugin runs, inlining the shipped
65
+ * manifest where `load` can no longer replace it. Excluding the
66
+ * package is what keeps the two paths agreeing.
67
+ */
68
+ config() {
69
+ return {
70
+ environments: { ssr: {
71
+ optimizeDeps: { exclude: [MANIFEST_OWNER] },
72
+ resolve: { noExternal: [MANIFEST_OWNER] }
73
+ } },
74
+ optimizeDeps: { exclude: [MANIFEST_OWNER] },
75
+ ssr: { noExternal: [MANIFEST_OWNER] }
76
+ };
77
+ },
78
+ configResolved(config) {
79
+ root = config.root;
80
+ resolution = {
81
+ alias: config.resolve.alias,
82
+ dedupe: config.resolve.dedupe,
83
+ extensions: config.resolve.extensions,
84
+ mainFields: config.resolve.mainFields,
85
+ preserveSymlinks: config.resolve.preserveSymlinks
86
+ };
87
+ },
88
+ async buildStart() {
89
+ if (this.environment.config.command === "build") await ready((message) => this.warn(message));
90
+ },
91
+ configureServer(created) {
92
+ server = created;
93
+ created.watcher.on("all", async (_event, changed) => {
94
+ if (!isWatched(watched, changed)) return;
95
+ pending = void 0;
96
+ try {
97
+ await ready((message) => created.config.logger.warn(message));
98
+ } catch (error) {
99
+ created.config.logger.error(String(error));
100
+ }
101
+ invalidate(created, entry);
102
+ created.hot.send({ type: "full-reload" });
103
+ });
104
+ },
105
+ async resolveId(source, importer) {
106
+ if (source === MANIFEST_SPECIFIER) return VIRTUAL_MANIFEST;
107
+ if (source.startsWith("\0pitlane-content/entry/")) return source;
108
+ if (importer?.startsWith("\0pitlane-content/entry/") && source.startsWith(".")) {
109
+ let filePath = bodies.get(importer)?.filePath;
110
+ if (filePath === void 0) return void 0;
111
+ let from = resolve(root, filePath);
112
+ return await this.resolve(resolve(dirname(from), source), from, { skipSelf: true });
113
+ }
114
+ },
115
+ /**
116
+ * Catches the one misconfiguration that would otherwise surface as a
117
+ * JavaScript tokenizer error on a virtual path.
118
+ *
119
+ * A body module's id ends in `.md` or `.mdx` and its contents are raw
120
+ * Markdown. If it reaches here unchanged, no plugin claimed it, and the
121
+ * bundler is about to parse prose as JavaScript. Every other
122
+ * misconfiguration in this package names its own fix; this one sits on
123
+ * the documented adoption path, so it gets the same treatment.
124
+ */
125
+ transform(code, id) {
126
+ if (!id.startsWith("\0pitlane-content/entry/") || code !== bodies.get(id)?.source) return void 0;
127
+ throw new Error(`Nothing compiled the Markdown in "${id.slice(BODY_PREFIX.length)}". Add vite-plugin-satteri to your Vite config, before remix():
128
+ satteri({ mdx: { jsxImportSource: "remix/ui" }, mdastPlugins: [headings()] })`);
129
+ },
130
+ async load(id) {
131
+ if (id === VIRTUAL_MANIFEST || isManifestModule(id)) {
132
+ await ready((message) => this.warn(message));
133
+ return manifestModule(collections, bodies, await markdownHeadings(collections));
134
+ }
135
+ if (id.startsWith("\0pitlane-content/entry/")) {
136
+ await ready((message) => this.warn(message));
137
+ return bodies.get(id)?.source;
138
+ }
139
+ }
140
+ };
141
+ }
142
+ /**
143
+ * The heading list for every Markdown entry, measured the way the runtime
144
+ * measures it.
145
+ *
146
+ * `vite-plugin-satteri` compiles a `.md` body to an HTML string and exports
147
+ * nothing else, so the list has to be taken here or the prebuilt path has none
148
+ * at all. MDX needs no such help: it compiles to a module that exports its own.
149
+ */
150
+ async function markdownHeadings(collections) {
151
+ let measured = /* @__PURE__ */ new Map();
152
+ let markdown = Object.entries(collections).flatMap(([collection, loaded]) => loaded.filter((entry) => entry.body?.format === "md").map((entry) => ({
153
+ collection,
154
+ entry
155
+ })));
156
+ if (markdown.length === 0) return measured;
157
+ let [satteri, { headings }] = await Promise.all([import("satteri"), import("./satteri.mjs")]);
158
+ for (let { collection, entry } of markdown) {
159
+ let data = (await satteri.markdownToHtml(entry.body.source, {
160
+ features: { frontmatter: true },
161
+ mdastPlugins: [headings()]
162
+ })).data;
163
+ measured.set(`${BODY_PREFIX}${collection}/${entry.id}.md`, data.headings);
164
+ }
165
+ return measured;
166
+ }
167
+ function isWatched(watched, changed) {
168
+ let path = posix(changed);
169
+ return watched.some((base) => path === posix(base) || path.startsWith(`${posix(base)}/`));
170
+ }
171
+ function posix(path) {
172
+ return path.replace(/\\/g, "/");
173
+ }
174
+ /**
175
+ * Whether a resolved id is this package's manifest module.
176
+ *
177
+ * Located relative to this module rather than matched by name: the plugin and
178
+ * the manifest ship side by side, in `src` during development and in `dist`
179
+ * once packed, so one relative lookup identifies it exactly. A suffix match
180
+ * either misses the workspace link Vite resolves through or claims an
181
+ * application's own `content/src/manifest.ts`.
182
+ */
183
+ const MANIFEST_PATHS = new Set(["./manifest.ts", "./manifest.mjs"].map((name) => posix(fileURLToPath(new URL(name, import.meta.url)))));
184
+ function isManifestModule(id) {
185
+ return MANIFEST_PATHS.has(posix(id).split("?")[0] ?? "");
186
+ }
187
+ /**
188
+ * Invalidates the manifest, the body modules, and every module that imported
189
+ * them, so the next request re-evaluates the collections rather than reusing
190
+ * the entries the previous prebuild produced.
191
+ */
192
+ function invalidate(server, entry) {
193
+ let entryPath = posix(entry).replace(/^\/+/, "");
194
+ for (let environment of Object.values(server.environments)) {
195
+ let graph = environment.moduleGraph;
196
+ let stale = [...graph.idToModuleMap.values()].filter((node) => {
197
+ let id = posix(node.id ?? "");
198
+ return isEmitted(id) || id.endsWith(`/${entryPath}`);
199
+ });
200
+ let seen = new Set(stale);
201
+ while (stale.length > 0) {
202
+ let node = stale.pop();
203
+ graph.invalidateModule(node);
204
+ for (let importer of node.importers) {
205
+ if (seen.has(importer)) continue;
206
+ seen.add(importer);
207
+ stale.push(importer);
208
+ }
209
+ }
210
+ }
211
+ }
212
+ function isEmitted(id) {
213
+ return id === VIRTUAL_MANIFEST || id.startsWith("\0pitlane-content/entry/") || isManifestModule(id);
214
+ }
215
+ /**
216
+ * Runs the content entry through Vite's own module runner, so TypeScript,
217
+ * aliases, and `vite.config.ts` resolution all apply.
218
+ *
219
+ * Declarations register deferred work. The plugin awaits it after module
220
+ * evaluation, with the filesystem available, before reading the manifest.
221
+ * Live loaders register no population work and stay untouched.
222
+ */
223
+ async function inPrebuildServer(root, entry, resolution, onWatched) {
224
+ let server = await createServer({
225
+ root,
226
+ configFile: false,
227
+ logLevel: "silent",
228
+ resolve: resolution,
229
+ server: {
230
+ middlewareMode: true,
231
+ watch: null
232
+ },
233
+ environments: { ssr: { dev: { createEnvironment: (name, config) => createRunnableDevEnvironment(name, config) } } }
234
+ });
235
+ try {
236
+ return await execute(server, root, entry, onWatched);
237
+ } finally {
238
+ await server.close();
239
+ }
240
+ }
241
+ async function execute(server, root, entry, onWatched) {
242
+ let recorded = openPrebuild(root);
243
+ try {
244
+ await server.ssrLoadModule(entry.startsWith("/") ? entry : `/${entry}`);
245
+ for (let populate of recorded.tasks) await populate();
246
+ } catch (error) {
247
+ let cause = error instanceof Error ? error.message : String(error);
248
+ throw new Error(`Failed to load the content entry "${entry}" in ${root}: ${cause}`, { cause: error });
249
+ } finally {
250
+ onWatched([...recorded.watched]);
251
+ closePrebuild();
252
+ }
253
+ let collections = {};
254
+ for (let [name, entries] of recorded.collections) collections[name] = entries;
255
+ return {
256
+ collections,
257
+ watched: [...recorded.watched],
258
+ configuredSatteri: [...recorded.configuredSatteri]
259
+ };
260
+ }
261
+ //#endregion
262
+ export { contentLayer };
package/package.json ADDED
@@ -0,0 +1,104 @@
1
+ {
2
+ "name": "@pitlane/content",
3
+ "version": "0.1.0",
4
+ "description": "createContent() — schema-validated, cross-referenced Markdown, MDX, and data collections for Remix 3, queryable at runtime and prebuildable into the bundle.",
5
+ "keywords": [
6
+ "content",
7
+ "content-collections",
8
+ "markdown",
9
+ "mdx",
10
+ "pitlane",
11
+ "remix",
12
+ "satteri"
13
+ ],
14
+ "homepage": "https://pitlane.tools/package/content/",
15
+ "bugs": {
16
+ "url": "https://github.com/pitlane-tools/pitlane/issues"
17
+ },
18
+ "license": "MIT",
19
+ "author": "Mark Malstrom <mark@malstrom.me>",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/pitlane-tools/pitlane.git",
23
+ "directory": "packages/content"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "CHANGELOG.md"
28
+ ],
29
+ "type": "module",
30
+ "types": "./dist/index.d.mts",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.mts",
34
+ "import": "./dist/index.mjs"
35
+ },
36
+ "./loaders": {
37
+ "types": "./dist/loaders.d.mts",
38
+ "import": "./dist/loaders.mjs"
39
+ },
40
+ "./satteri": {
41
+ "types": "./dist/satteri.d.mts",
42
+ "import": "./dist/satteri.mjs"
43
+ },
44
+ "./vite": {
45
+ "types": "./dist/vite.d.mts",
46
+ "import": "./dist/vite.mjs"
47
+ },
48
+ "./hot": {
49
+ "types": "./dist/hot.d.mts",
50
+ "import": "./dist/hot.mjs"
51
+ },
52
+ "./internal/manifest": {
53
+ "types": "./dist/manifest.d.mts",
54
+ "import": "./dist/manifest.mjs"
55
+ },
56
+ "./internal/prebuild": {
57
+ "types": "./dist/prebuild.d.mts",
58
+ "import": "./dist/prebuild.mjs"
59
+ },
60
+ "./internal/codegen": {
61
+ "types": "./dist/codegen.d.mts",
62
+ "import": "./dist/codegen.mjs"
63
+ },
64
+ "./internal/mdx": {
65
+ "types": "./dist/mdx.d.mts",
66
+ "import": "./dist/mdx.mjs"
67
+ }
68
+ },
69
+ "scripts": {
70
+ "prepublishOnly": "vp run build"
71
+ },
72
+ "dependencies": {
73
+ "es-module-lexer": "^2.3.1",
74
+ "yaml": "^2.8.1"
75
+ },
76
+ "devDependencies": {
77
+ "@types/node": "^25.5.0",
78
+ "remix": "3.0.0-rc.2",
79
+ "satteri": "^0.10.5",
80
+ "typescript": "^7.0.2",
81
+ "vite": "^8.1.5",
82
+ "vite-plugin-satteri": "^0.3.5",
83
+ "vite-plus": "^0.2.6"
84
+ },
85
+ "peerDependencies": {
86
+ "remix": "^3.0.0-rc.1",
87
+ "satteri": "^0.10.5",
88
+ "vite": ">=8.0.0"
89
+ },
90
+ "peerDependenciesMeta": {
91
+ "remix": {
92
+ "optional": true
93
+ },
94
+ "satteri": {
95
+ "optional": true
96
+ },
97
+ "vite": {
98
+ "optional": true
99
+ }
100
+ },
101
+ "engines": {
102
+ "node": "^20.19.0 || >=22.12.0"
103
+ }
104
+ }