@human-synthesis/norns 0.0.6 → 0.0.7
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/README.md +117 -6
- package/bin/norns.js +173 -4
- package/package.json +9 -4
- package/src/auto-import.js +638 -0
- package/src/config.js +48 -3
- package/src/index.js +2 -1
- package/src/migrate.js +212 -0
- package/src/server/boot.js +74 -0
- package/src/server/container.js +175 -0
- package/src/server/db.js +131 -0
- package/src/server/handle/context.js +24 -0
- package/src/server/handle/error.js +18 -0
- package/src/server/index.js +9 -0
- package/src/server/page.js +107 -0
- package/src/server/route.js +114 -0
- package/src/server/scope.js +50 -0
- package/src/server/validate.js +60 -0
- package/src/vite.js +24 -17
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** @typedef {import('@sveltejs/kit').HandleServerError} HandleServerError */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Default `handleError` implementation: logs the error with request context
|
|
5
|
+
* and returns a safe payload for the client.
|
|
6
|
+
*
|
|
7
|
+
* Apps can wrap this or replace it via `boot({ handleError: custom })`.
|
|
8
|
+
*
|
|
9
|
+
* @param {{ logger?: { error: (msg: string, err: unknown) => void } }} [opts]
|
|
10
|
+
* @returns {HandleServerError}
|
|
11
|
+
*/
|
|
12
|
+
export function errorHandle(opts = {}) {
|
|
13
|
+
const log = opts.logger ?? { error: (msg, err) => console.error(msg, err) };
|
|
14
|
+
return ({ error, event, status, message }) => {
|
|
15
|
+
log.error(`[norns] ${event.request.method} ${event.url.pathname} ${status}`, error);
|
|
16
|
+
return { message: message || 'Internal error' };
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { Container, createContainer } from './container.js';
|
|
2
|
+
export { withScope, getScope, getContainer } from './scope.js';
|
|
3
|
+
export { boot, createApp } from './boot.js';
|
|
4
|
+
export { contextHandle } from './handle/context.js';
|
|
5
|
+
export { errorHandle } from './handle/error.js';
|
|
6
|
+
export { route } from './route.js';
|
|
7
|
+
export { page } from './page.js';
|
|
8
|
+
export { validate, ValidationError } from './validate.js';
|
|
9
|
+
export { betterSqlite, d1, libsql, postgres, withTransaction } from './db.js';
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { fail } from '@sveltejs/kit';
|
|
2
|
+
import { validate, ValidationError } from './validate.js';
|
|
3
|
+
|
|
4
|
+
/** @typedef {import('@sveltejs/kit').ServerLoadEvent} ServerLoadEvent */
|
|
5
|
+
/** @typedef {import('@sveltejs/kit').RequestEvent} RequestEvent */
|
|
6
|
+
/** @typedef {import('./container.js').Container} Container */
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} LoadContext
|
|
10
|
+
* @property {Container} container
|
|
11
|
+
* @property {ServerLoadEvent} event
|
|
12
|
+
* @property {ServerLoadEvent['params']} params
|
|
13
|
+
* @property {ServerLoadEvent['url']} url
|
|
14
|
+
* @property {any} user
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {Object} ActionContext
|
|
19
|
+
* @property {any} input parsed form data (after validation)
|
|
20
|
+
* @property {Container} container
|
|
21
|
+
* @property {RequestEvent} event
|
|
22
|
+
* @property {any} user
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Wrappers for `+page.server.c` exports — `load` and `actions`. They mirror
|
|
27
|
+
* `route()` but: (a) `load` returns its result as data (no JSON wrapper), and
|
|
28
|
+
* (b) actions return `fail(400, ...)` on validation rather than throwing
|
|
29
|
+
* `error(400)`, which is the SvelteKit-idiomatic shape for forms.
|
|
30
|
+
*/
|
|
31
|
+
export const page = {
|
|
32
|
+
/**
|
|
33
|
+
* Wrap a SvelteKit `load`.
|
|
34
|
+
*
|
|
35
|
+
* @param {{ handler: (ctx: LoadContext) => any | Promise<any> }} opts
|
|
36
|
+
* @returns {(event: ServerLoadEvent) => Promise<any>}
|
|
37
|
+
*/
|
|
38
|
+
load(opts) {
|
|
39
|
+
if (typeof opts?.handler !== 'function') {
|
|
40
|
+
throw new Error('page.load(): `handler` is required');
|
|
41
|
+
}
|
|
42
|
+
return async (event) => {
|
|
43
|
+
return opts.handler({
|
|
44
|
+
container: event.locals.container,
|
|
45
|
+
event,
|
|
46
|
+
params: event.params,
|
|
47
|
+
url: event.url,
|
|
48
|
+
user: event.locals.user
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Wrap a SvelteKit `actions` object. Each action takes `{ input?, run }`
|
|
55
|
+
* — `input` is a schema, `run` is the handler.
|
|
56
|
+
*
|
|
57
|
+
* @param {Record<string, { input?: any, run: (ctx: ActionContext) => any | Promise<any> }>} spec
|
|
58
|
+
* @returns {Record<string, (event: RequestEvent) => Promise<any>>}
|
|
59
|
+
*/
|
|
60
|
+
actions(spec) {
|
|
61
|
+
/** @type {Record<string, (event: RequestEvent) => Promise<any>>} */
|
|
62
|
+
const out = {};
|
|
63
|
+
for (const [name, def] of Object.entries(spec)) {
|
|
64
|
+
if (typeof def?.run !== 'function') {
|
|
65
|
+
throw new Error(`page.actions(): action "${name}" missing \`run\` function`);
|
|
66
|
+
}
|
|
67
|
+
out[name] = async (event) => {
|
|
68
|
+
let raw = null;
|
|
69
|
+
let input;
|
|
70
|
+
if (def.input !== undefined) {
|
|
71
|
+
raw = await readForm(event.request);
|
|
72
|
+
try {
|
|
73
|
+
input = validate(def.input, raw);
|
|
74
|
+
} catch (e) {
|
|
75
|
+
if (e instanceof ValidationError) {
|
|
76
|
+
return fail(400, { errors: e.issues, values: raw });
|
|
77
|
+
}
|
|
78
|
+
throw e;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return def.run({
|
|
82
|
+
input,
|
|
83
|
+
container: event.locals.container,
|
|
84
|
+
event,
|
|
85
|
+
user: event.locals.user
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Read form-encoded body into a plain object. Designed for `actions` —
|
|
95
|
+
* SvelteKit only invokes them via form POST.
|
|
96
|
+
*
|
|
97
|
+
* @param {Request} request
|
|
98
|
+
* @returns {Promise<any>}
|
|
99
|
+
*/
|
|
100
|
+
async function readForm(request) {
|
|
101
|
+
try {
|
|
102
|
+
const data = await request.formData();
|
|
103
|
+
return Object.fromEntries(data);
|
|
104
|
+
} catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { json, error } from '@sveltejs/kit';
|
|
2
|
+
import { validate, ValidationError } from './validate.js';
|
|
3
|
+
|
|
4
|
+
/** @typedef {import('@sveltejs/kit').RequestEvent} RequestEvent */
|
|
5
|
+
/** @typedef {import('./container.js').Container} Container */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} RouteContext
|
|
9
|
+
* @property {any} input parsed body (after validation)
|
|
10
|
+
* @property {any} query parsed query (after validation)
|
|
11
|
+
* @property {Container} container request-scoped container
|
|
12
|
+
* @property {RequestEvent} event raw SvelteKit event
|
|
13
|
+
* @property {any} user shortcut for `event.locals.user`
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} RouteOptions
|
|
18
|
+
* @property {any} [input] body schema (Standard Schema or function)
|
|
19
|
+
* @property {any} [query] query schema (Standard Schema or function)
|
|
20
|
+
* @property {(ctx: RouteContext) => any | Promise<any>} handler
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Wrap a `+server.c` handler. Bakes in:
|
|
25
|
+
* 1. body parsing (JSON / urlencoded / multipart) + validation
|
|
26
|
+
* 2. query validation
|
|
27
|
+
* 3. container resolution from `event.locals.container`
|
|
28
|
+
* 4. JSON serialization of the return value (or pass-through if it's a Response)
|
|
29
|
+
* 5. 400 errors on validation failure (via SvelteKit `error()`)
|
|
30
|
+
*
|
|
31
|
+
* Use `throw error(...)` / `throw redirect(...)` from inside the handler for
|
|
32
|
+
* non-success outcomes; SvelteKit will surface them.
|
|
33
|
+
*
|
|
34
|
+
* @param {RouteOptions} opts
|
|
35
|
+
* @returns {(event: RequestEvent) => Promise<Response>}
|
|
36
|
+
*/
|
|
37
|
+
export function route(opts) {
|
|
38
|
+
const { input: inputSchema, query: querySchema, handler } = opts;
|
|
39
|
+
if (typeof handler !== 'function') {
|
|
40
|
+
throw new Error('route(): `handler` is required');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return async (event) => {
|
|
44
|
+
const container = event.locals.container;
|
|
45
|
+
|
|
46
|
+
let input;
|
|
47
|
+
if (inputSchema !== undefined) {
|
|
48
|
+
const raw = await readBody(event.request);
|
|
49
|
+
try {
|
|
50
|
+
input = validate(inputSchema, raw);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
if (e instanceof ValidationError) {
|
|
53
|
+
throw error(400, { message: e.message, issues: e.issues });
|
|
54
|
+
}
|
|
55
|
+
throw e;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let query;
|
|
60
|
+
if (querySchema !== undefined) {
|
|
61
|
+
const raw = Object.fromEntries(event.url.searchParams);
|
|
62
|
+
try {
|
|
63
|
+
query = validate(querySchema, raw);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
if (e instanceof ValidationError) {
|
|
66
|
+
throw error(400, { message: e.message, issues: e.issues });
|
|
67
|
+
}
|
|
68
|
+
throw e;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const result = await handler({
|
|
73
|
+
input,
|
|
74
|
+
query,
|
|
75
|
+
container,
|
|
76
|
+
event,
|
|
77
|
+
user: event.locals.user
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (result instanceof Response) return result;
|
|
81
|
+
return json(result ?? null);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Read and decode the request body based on its content-type. Returns `null`
|
|
87
|
+
* for empty bodies or unsupported types — the schema is then free to reject
|
|
88
|
+
* (or accept `null`).
|
|
89
|
+
*
|
|
90
|
+
* @param {Request} request
|
|
91
|
+
* @returns {Promise<any>}
|
|
92
|
+
*/
|
|
93
|
+
async function readBody(request) {
|
|
94
|
+
const contentType = request.headers.get('content-type')?.split(';', 1)[0]?.trim() ?? '';
|
|
95
|
+
if (contentType === 'application/json') {
|
|
96
|
+
try {
|
|
97
|
+
return await request.json();
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (
|
|
103
|
+
contentType === 'application/x-www-form-urlencoded' ||
|
|
104
|
+
contentType === 'multipart/form-data'
|
|
105
|
+
) {
|
|
106
|
+
try {
|
|
107
|
+
const data = await request.formData();
|
|
108
|
+
return Object.fromEntries(data);
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Per-request async context. Used to make `event.locals.container` (and the
|
|
5
|
+
* scoped `db`, `user`) implicitly available to callees that don't get the
|
|
6
|
+
* SvelteKit `event` passed in.
|
|
7
|
+
*
|
|
8
|
+
* On Cloudflare Workers, requires the `nodejs_als` + `nodejs_compat` compat
|
|
9
|
+
* flags in `wrangler.toml`.
|
|
10
|
+
*
|
|
11
|
+
* @template T
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** @typedef {{ container: import('./container.js').Container, [key: string]: any }} RequestScope */
|
|
15
|
+
|
|
16
|
+
/** @type {AsyncLocalStorage<RequestScope>} */
|
|
17
|
+
const storage = new AsyncLocalStorage();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Run `fn` with `scope` as the current request scope.
|
|
21
|
+
*
|
|
22
|
+
* @template T
|
|
23
|
+
* @param {RequestScope} scope
|
|
24
|
+
* @param {() => T | Promise<T>} fn
|
|
25
|
+
* @returns {T | Promise<T>}
|
|
26
|
+
*/
|
|
27
|
+
export function withScope(scope, fn) {
|
|
28
|
+
return storage.run(scope, fn);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get the current request scope. Returns `undefined` outside a request.
|
|
33
|
+
*
|
|
34
|
+
* @returns {RequestScope | undefined}
|
|
35
|
+
*/
|
|
36
|
+
export function getScope() {
|
|
37
|
+
return storage.getStore();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get the current request-scoped container, or throw if called outside a
|
|
42
|
+
* request.
|
|
43
|
+
*
|
|
44
|
+
* @returns {import('./container.js').Container}
|
|
45
|
+
*/
|
|
46
|
+
export function getContainer() {
|
|
47
|
+
const scope = storage.getStore();
|
|
48
|
+
if (!scope) throw new Error('getContainer() called outside a request scope');
|
|
49
|
+
return scope.container;
|
|
50
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation glue. Norns doesn't bundle a schema library — it speaks the
|
|
3
|
+
* Standard Schema interface (https://github.com/standard-schema/standard-schema)
|
|
4
|
+
* supported by Valibot, Zod 3.24+, ArkType, etc. A plain function (`input -> parsed`)
|
|
5
|
+
* also works, for ad-hoc cases or simple Coffee parsers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** @typedef {{ '~standard': { validate: (input: unknown) => any } }} StandardSchema */
|
|
9
|
+
/** @typedef {{ kind: 'validation', path?: any[], message: string }} Issue */
|
|
10
|
+
|
|
11
|
+
export class ValidationError extends Error {
|
|
12
|
+
/**
|
|
13
|
+
* @param {Array<Issue>} issues
|
|
14
|
+
*/
|
|
15
|
+
constructor(issues) {
|
|
16
|
+
const summary = issues
|
|
17
|
+
.map((i) => `${formatPath(i.path)}: ${i.message}`)
|
|
18
|
+
.join(', ');
|
|
19
|
+
super(`Validation failed: ${summary}`);
|
|
20
|
+
this.name = 'ValidationError';
|
|
21
|
+
this.issues = issues;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Validate `input` against `schema`. Returns the parsed value or throws
|
|
27
|
+
* `ValidationError`.
|
|
28
|
+
*
|
|
29
|
+
* Accepts:
|
|
30
|
+
* - Standard Schema instance (Valibot, Zod, ArkType, …)
|
|
31
|
+
* - a plain function `(input) => parsed` that throws on invalid input
|
|
32
|
+
* - `undefined` / `null` → passthrough (no validation)
|
|
33
|
+
*
|
|
34
|
+
* @template T
|
|
35
|
+
* @param {StandardSchema | ((input: unknown) => T) | undefined | null} schema
|
|
36
|
+
* @param {unknown} input
|
|
37
|
+
* @returns {T}
|
|
38
|
+
*/
|
|
39
|
+
export function validate(schema, input) {
|
|
40
|
+
if (schema == null) return /** @type {T} */ (input);
|
|
41
|
+
if (typeof schema === 'function') return schema(input);
|
|
42
|
+
if (schema['~standard'] && typeof schema['~standard'].validate === 'function') {
|
|
43
|
+
const result = schema['~standard'].validate(input);
|
|
44
|
+
if (result instanceof Promise) {
|
|
45
|
+
throw new Error('Async schema validation is not supported in route()/page.actions()');
|
|
46
|
+
}
|
|
47
|
+
if (result.issues) throw new ValidationError(result.issues);
|
|
48
|
+
return result.value;
|
|
49
|
+
}
|
|
50
|
+
throw new Error('validate(): schema must implement Standard Schema or be a function');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {any[] | undefined} path
|
|
55
|
+
* @returns {string}
|
|
56
|
+
*/
|
|
57
|
+
function formatPath(path) {
|
|
58
|
+
if (!path || path.length === 0) return '$';
|
|
59
|
+
return path.map((p) => (typeof p === 'object' ? p.key : p)).join('.');
|
|
60
|
+
}
|
package/src/vite.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { readFile, stat, realpath } from 'node:fs/promises';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
|
-
import
|
|
4
|
+
import { compile as compileCivet } from '@danielx/civet';
|
|
5
5
|
|
|
6
6
|
const DEFAULT_EXTENSIONS = ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json'];
|
|
7
|
-
const NORNS_EXTENSIONS = ['.svelte', '.n', '.c'];
|
|
7
|
+
const NORNS_EXTENSIONS = ['.svelte', '.n', '.civet', '.c'];
|
|
8
8
|
const RESOLVE_EXTENSIONS = [...NORNS_EXTENSIONS, '.ts', '.js'];
|
|
9
9
|
const FRAMEWORK_PKGS = ['@human-synthesis/norns-core', '@human-synthesis/norns'];
|
|
10
10
|
|
|
@@ -49,16 +49,16 @@ async function resolveWorkspaceFrameworkSrcs(root) {
|
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* Vite plugin that:
|
|
52
|
-
* - compiles `.c` files
|
|
53
|
-
* files like `+page.
|
|
54
|
-
* `+server.c` work the same as their `.js` / `.ts` counterparts;
|
|
55
|
-
* - registers `.svelte`, `.n`,
|
|
52
|
+
* - compiles `.civet` and `.c` files via @danielx/civet, so SvelteKit
|
|
53
|
+
* special files like `+page.civet`, `+page.server.c`, `hooks.server.civet`,
|
|
54
|
+
* and `+server.c` work the same as their `.js` / `.ts` counterparts;
|
|
55
|
+
* - registers `.svelte`, `.n`, `.civet`, `.c` with Vite's resolver so bare
|
|
56
56
|
* imports (`import X from './Foo'`) try those extensions in priority
|
|
57
57
|
* order, on top of Vite's defaults;
|
|
58
|
-
* - resolves bare-name imports (`import X from 'Foo'`, no `./` prefix) to
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
58
|
+
* - resolves bare-name imports (`import X from 'Foo'`, no `./` prefix) to a
|
|
59
|
+
* sibling file when one exists, in the same priority order. Real package
|
|
60
|
+
* imports (`'svelte/store'`, `'@scope/pkg'`) are unaffected because they
|
|
61
|
+
* contain a slash or scope marker;
|
|
62
62
|
* - in workspace-linked dev (sibling repos symlinked into node_modules),
|
|
63
63
|
* excludes the framework packages from `optimizeDeps` pre-bundling and
|
|
64
64
|
* lifts them out of the default `**\/node_modules\/**` watch ignore so
|
|
@@ -67,13 +67,16 @@ async function resolveWorkspaceFrameworkSrcs(root) {
|
|
|
67
67
|
* process-level respawn when framework source changes — needed because
|
|
68
68
|
* Node's ESM module cache survives `server.restart()`.
|
|
69
69
|
*
|
|
70
|
+
* `.c` is recognised as an alias for `.civet` — both compile through Civet.
|
|
71
|
+
* CoffeeScript is no longer supported.
|
|
72
|
+
*
|
|
70
73
|
* @returns {import('vite').Plugin}
|
|
71
74
|
*/
|
|
72
|
-
export function
|
|
75
|
+
export function nornsCivetPlugin() {
|
|
73
76
|
/** @type {string[]} */
|
|
74
77
|
let watchSrcs = [];
|
|
75
78
|
return {
|
|
76
|
-
name: 'norns:
|
|
79
|
+
name: 'norns:civet',
|
|
77
80
|
enforce: 'pre',
|
|
78
81
|
async config(_userConfig, { command }) {
|
|
79
82
|
if (command === 'serve') {
|
|
@@ -110,6 +113,11 @@ export function nornsCoffeePlugin() {
|
|
|
110
113
|
if (source.startsWith('.') || source.startsWith('/')) return null;
|
|
111
114
|
// Scoped package or package-with-subpath — treat as a bare module.
|
|
112
115
|
if (source.startsWith('@') || source.includes('/')) return null;
|
|
116
|
+
// Skip imports from inside node_modules — library code uses proper
|
|
117
|
+
// module resolution; we'd otherwise hijack legitimate package imports
|
|
118
|
+
// (e.g. `import { parse } from 'cookie'`) when a sibling file with
|
|
119
|
+
// the same name happens to exist in the same dir.
|
|
120
|
+
if (importer.includes(`${join('/', 'node_modules', '/')}`)) return null;
|
|
113
121
|
|
|
114
122
|
// Single bare name — try resolving as a sibling file first, falling
|
|
115
123
|
// back to the default resolver (node_modules) if nothing matches.
|
|
@@ -122,15 +130,14 @@ export function nornsCoffeePlugin() {
|
|
|
122
130
|
},
|
|
123
131
|
async load(id) {
|
|
124
132
|
const [path] = id.split('?');
|
|
125
|
-
if (!path.endsWith('.c')) return null;
|
|
133
|
+
if (!path.endsWith('.civet') && !path.endsWith('.c')) return null;
|
|
126
134
|
const source = await readFile(path, 'utf8');
|
|
127
|
-
const
|
|
128
|
-
|
|
135
|
+
const result = await compileCivet(source, {
|
|
136
|
+
js: true,
|
|
129
137
|
sourceMap: true,
|
|
130
|
-
inlineMap: false,
|
|
131
138
|
filename: path
|
|
132
139
|
});
|
|
133
|
-
return { code:
|
|
140
|
+
return { code: result.code, map: result.sourceMap?.json?.(path) ?? null };
|
|
134
141
|
}
|
|
135
142
|
};
|
|
136
143
|
}
|