@jk2908/solas 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.
- package/LICENSE +21 -0
- package/README.md +333 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +219 -0
- package/dist/error-boundary.d.ts +1 -0
- package/dist/error-boundary.js +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +235 -0
- package/dist/internal/build.d.ts +104 -0
- package/dist/internal/build.js +633 -0
- package/dist/internal/codegen/config.d.ts +5 -0
- package/dist/internal/codegen/config.js +19 -0
- package/dist/internal/codegen/environments.d.ts +12 -0
- package/dist/internal/codegen/environments.js +42 -0
- package/dist/internal/codegen/manifest.d.ts +5 -0
- package/dist/internal/codegen/manifest.js +15 -0
- package/dist/internal/codegen/maps.d.ts +5 -0
- package/dist/internal/codegen/maps.js +75 -0
- package/dist/internal/codegen/utils.d.ts +1 -0
- package/dist/internal/codegen/utils.js +2 -0
- package/dist/internal/env/browser.d.ts +4 -0
- package/dist/internal/env/browser.js +58 -0
- package/dist/internal/env/request-context.d.ts +19 -0
- package/dist/internal/env/request-context.js +2 -0
- package/dist/internal/env/rsc.d.ts +39 -0
- package/dist/internal/env/rsc.js +368 -0
- package/dist/internal/env/ssr.d.ts +42 -0
- package/dist/internal/env/ssr.js +149 -0
- package/dist/internal/env/utils.d.ts +2 -0
- package/dist/internal/env/utils.js +28 -0
- package/dist/internal/metadata.d.ts +81 -0
- package/dist/internal/metadata.js +185 -0
- package/dist/internal/navigation/http-exception-boundary.d.ts +12 -0
- package/dist/internal/navigation/http-exception-boundary.js +48 -0
- package/dist/internal/navigation/http-exception.d.ts +33 -0
- package/dist/internal/navigation/http-exception.js +45 -0
- package/dist/internal/navigation/link.d.ts +13 -0
- package/dist/internal/navigation/link.js +63 -0
- package/dist/internal/navigation/redirect-boundary.d.ts +12 -0
- package/dist/internal/navigation/redirect-boundary.js +39 -0
- package/dist/internal/navigation/redirect.d.ts +21 -0
- package/dist/internal/navigation/redirect.js +63 -0
- package/dist/internal/navigation/use-search-params.d.ts +1 -0
- package/dist/internal/navigation/use-search-params.js +13 -0
- package/dist/internal/prerender.d.ts +151 -0
- package/dist/internal/prerender.js +422 -0
- package/dist/internal/render/head.d.ts +4 -0
- package/dist/internal/render/head.js +38 -0
- package/dist/internal/render/tree.d.ts +47 -0
- package/dist/internal/render/tree.js +108 -0
- package/dist/internal/router/create-router.d.ts +6 -0
- package/dist/internal/router/create-router.js +95 -0
- package/dist/internal/router/pattern.d.ts +8 -0
- package/dist/internal/router/pattern.js +31 -0
- package/dist/internal/router/prefetcher.d.ts +47 -0
- package/dist/internal/router/prefetcher.js +90 -0
- package/dist/internal/router/resolver.d.ts +174 -0
- package/dist/internal/router/resolver.js +356 -0
- package/dist/internal/router/router-context.d.ts +11 -0
- package/dist/internal/router/router-context.js +7 -0
- package/dist/internal/router/router-provider.d.ts +6 -0
- package/dist/internal/router/router-provider.js +131 -0
- package/dist/internal/router/router.d.ts +79 -0
- package/dist/internal/router/router.js +417 -0
- package/dist/internal/router/use-router.d.ts +5 -0
- package/dist/internal/router/use-router.js +5 -0
- package/dist/internal/server/cookies.d.ts +6 -0
- package/dist/internal/server/cookies.js +17 -0
- package/dist/internal/server/dynamic.d.ts +9 -0
- package/dist/internal/server/dynamic.js +22 -0
- package/dist/internal/server/headers.d.ts +5 -0
- package/dist/internal/server/headers.js +19 -0
- package/dist/internal/server/url.d.ts +5 -0
- package/dist/internal/server/url.js +16 -0
- package/dist/internal/ui/defaults/error.d.ts +4 -0
- package/dist/internal/ui/defaults/error.js +6 -0
- package/dist/internal/ui/error-boundary.d.ts +26 -0
- package/dist/internal/ui/error-boundary.js +41 -0
- package/dist/navigation.d.ts +6 -0
- package/dist/navigation.js +6 -0
- package/dist/prerender.d.ts +1 -0
- package/dist/prerender.js +1 -0
- package/dist/router.d.ts +4 -0
- package/dist/router.js +4 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +4 -0
- package/dist/solas.d.ts +32 -0
- package/dist/solas.js +125 -0
- package/dist/types.d.ts +93 -0
- package/dist/types.js +1 -0
- package/dist/utils/compress.d.ts +11 -0
- package/dist/utils/compress.js +76 -0
- package/dist/utils/context.d.ts +6 -0
- package/dist/utils/context.js +25 -0
- package/dist/utils/cookies.d.ts +3 -0
- package/dist/utils/cookies.js +35 -0
- package/dist/utils/export-reader.d.ts +29 -0
- package/dist/utils/export-reader.js +117 -0
- package/dist/utils/format.d.ts +6 -0
- package/dist/utils/format.js +72 -0
- package/dist/utils/logger.d.ts +52 -0
- package/dist/utils/logger.js +105 -0
- package/dist/utils/time.d.ts +4 -0
- package/dist/utils/time.js +29 -0
- package/package.json +111 -0
package/dist/solas.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { PluginConfig } from './types';
|
|
2
|
+
export declare namespace Solas {
|
|
3
|
+
namespace Config {
|
|
4
|
+
const NAME = "Solas";
|
|
5
|
+
const SLUG: string;
|
|
6
|
+
const PKG_NAME: string;
|
|
7
|
+
const OUT_DIR = "dist";
|
|
8
|
+
const APP_DIR = "app";
|
|
9
|
+
const GENERATED_DIR: string;
|
|
10
|
+
const ENTRY_RSC = "entry.rsc.tsx";
|
|
11
|
+
const ENTRY_SSR = "entry.ssr.tsx";
|
|
12
|
+
const ENTRY_BROWSER = "entry.browser.tsx";
|
|
13
|
+
const ASSETS_DIR = "assets";
|
|
14
|
+
const $: unique symbol;
|
|
15
|
+
const REQUEST_META: string;
|
|
16
|
+
const LOG_LEVELS: readonly ["debug", "info", "warn", "error", "fatal"];
|
|
17
|
+
const PRERENDER_MODES: readonly ["full", "ppr", false];
|
|
18
|
+
/**
|
|
19
|
+
* Validate the plugin configuration object, throwing an error if invalid
|
|
20
|
+
* @param input - the unvalidated configuration object
|
|
21
|
+
* @return the typed and validated configuration object
|
|
22
|
+
*/
|
|
23
|
+
function validate(input: unknown): PluginConfig;
|
|
24
|
+
}
|
|
25
|
+
function getVersion(): string;
|
|
26
|
+
namespace Events {
|
|
27
|
+
const names: {
|
|
28
|
+
readonly NAVIGATION: `${string}navigation`;
|
|
29
|
+
readonly NAVIGATION_ERROR: `${string}navigationerror`;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
package/dist/solas.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export { Solas };
|
|
2
|
+
var Solas;
|
|
3
|
+
(function (Solas) {
|
|
4
|
+
let Config;
|
|
5
|
+
(function (Config) {
|
|
6
|
+
Config.NAME = 'Solas';
|
|
7
|
+
Config.SLUG = Config.NAME.toLowerCase();
|
|
8
|
+
Config.PKG_NAME = `@jk2908/${Config.SLUG}`;
|
|
9
|
+
Config.OUT_DIR = 'dist';
|
|
10
|
+
Config.APP_DIR = 'app';
|
|
11
|
+
Config.GENERATED_DIR = `.${Config.SLUG}`;
|
|
12
|
+
Config.ENTRY_RSC = 'entry.rsc.tsx';
|
|
13
|
+
Config.ENTRY_SSR = 'entry.ssr.tsx';
|
|
14
|
+
Config.ENTRY_BROWSER = 'entry.browser.tsx';
|
|
15
|
+
Config.ASSETS_DIR = 'assets';
|
|
16
|
+
Config.$ = Symbol(Config.SLUG);
|
|
17
|
+
Config.REQUEST_META = `__${Config.SLUG.toUpperCase()}__`;
|
|
18
|
+
Config.LOG_LEVELS = ['debug', 'info', 'warn', 'error', 'fatal'];
|
|
19
|
+
Config.PRERENDER_MODES = ['full', 'ppr', false];
|
|
20
|
+
const CONFIG_KEYS = new Set([
|
|
21
|
+
'logger',
|
|
22
|
+
'metadata',
|
|
23
|
+
'precompress',
|
|
24
|
+
'prerender',
|
|
25
|
+
'trailingSlash',
|
|
26
|
+
'url',
|
|
27
|
+
]);
|
|
28
|
+
const LOGGER_KEYS = new Set(['level']);
|
|
29
|
+
/**
|
|
30
|
+
* Validate the plugin configuration object, throwing an error if invalid
|
|
31
|
+
* @param input - the unvalidated configuration object
|
|
32
|
+
* @return the typed and validated configuration object
|
|
33
|
+
*/
|
|
34
|
+
function validate(input) {
|
|
35
|
+
if (input === undefined)
|
|
36
|
+
return {};
|
|
37
|
+
const errors = [];
|
|
38
|
+
if (!isRecord(input)) {
|
|
39
|
+
throw new Error(`[${Config.NAME}] Invalid config:\n- Expected plugin config to be an object`);
|
|
40
|
+
}
|
|
41
|
+
for (const key of Object.keys(input)) {
|
|
42
|
+
if (!CONFIG_KEYS.has(key)) {
|
|
43
|
+
errors.push(`Unknown config key: ${key}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if ('url' in input && input.url !== undefined) {
|
|
47
|
+
if (typeof input.url !== 'string') {
|
|
48
|
+
errors.push('config.url must be a string');
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
try {
|
|
52
|
+
const url = new URL(input.url);
|
|
53
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
54
|
+
errors.push('config.url must use http:// or https://');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
errors.push('config.url must be a valid URL');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if ('precompress' in input && input.precompress !== undefined) {
|
|
63
|
+
if (typeof input.precompress !== 'boolean') {
|
|
64
|
+
errors.push('config.precompress must be a boolean');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if ('prerender' in input && input.prerender !== undefined) {
|
|
68
|
+
if (!new Set(Config.PRERENDER_MODES).has(input.prerender)) {
|
|
69
|
+
errors.push("config.prerender must be 'full', 'ppr', or false");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if ('trailingSlash' in input && input.trailingSlash !== undefined) {
|
|
73
|
+
if (typeof input.trailingSlash !== 'boolean') {
|
|
74
|
+
errors.push('config.trailingSlash must be a boolean');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if ('metadata' in input &&
|
|
78
|
+
input.metadata !== undefined &&
|
|
79
|
+
!isRecord(input.metadata)) {
|
|
80
|
+
errors.push('config.metadata must be an object when provided');
|
|
81
|
+
}
|
|
82
|
+
if ('logger' in input && input.logger !== undefined) {
|
|
83
|
+
if (!isRecord(input.logger)) {
|
|
84
|
+
errors.push('config.logger must be an object when provided');
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
for (const key of Object.keys(input.logger)) {
|
|
88
|
+
if (!LOGGER_KEYS.has(key)) {
|
|
89
|
+
errors.push(`Unknown config.logger key: ${key}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if ('level' in input.logger && input.logger.level !== undefined) {
|
|
93
|
+
if (typeof input.logger.level !== 'string' ||
|
|
94
|
+
!new Set(Config.LOG_LEVELS).has(input.logger.level)) {
|
|
95
|
+
errors.push('config.logger.level must be one of: debug, info, warn, error, fatal');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (errors.length > 0) {
|
|
101
|
+
throw new Error(`[${Config.NAME}] Invalid config:\n- ${errors.join('\n- ')}`);
|
|
102
|
+
}
|
|
103
|
+
return input;
|
|
104
|
+
}
|
|
105
|
+
Config.validate = validate;
|
|
106
|
+
})(Config = Solas.Config || (Solas.Config = {}));
|
|
107
|
+
function getVersion() {
|
|
108
|
+
const value = import.meta.env.SOLAS_VERSION;
|
|
109
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
110
|
+
throw new Error(`[${Config.NAME}] Missing ${Config.NAME} package version`);
|
|
111
|
+
}
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
Solas.getVersion = getVersion;
|
|
115
|
+
let Events;
|
|
116
|
+
(function (Events) {
|
|
117
|
+
Events.names = {
|
|
118
|
+
NAVIGATION: `${Config.SLUG}navigation`,
|
|
119
|
+
NAVIGATION_ERROR: `${Config.SLUG}navigationerror`,
|
|
120
|
+
};
|
|
121
|
+
})(Events = Solas.Events || (Solas.Events = {}));
|
|
122
|
+
})(Solas || (Solas = {}));
|
|
123
|
+
function isRecord(value) {
|
|
124
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
125
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
type BunRequest = Request & {
|
|
2
|
+
params?: Record<string, string | string[]>;
|
|
3
|
+
};
|
|
4
|
+
import { Solas } from './solas';
|
|
5
|
+
import { ExportReader } from './utils/export-reader';
|
|
6
|
+
import type { Build } from './internal/build';
|
|
7
|
+
import type { Metadata } from './internal/metadata';
|
|
8
|
+
import type { HttpException } from './internal/navigation/http-exception';
|
|
9
|
+
import type { Router } from './internal/router/router';
|
|
10
|
+
export type LogLevel = (typeof Solas.Config.LOG_LEVELS)[number];
|
|
11
|
+
export type PluginConfig = {
|
|
12
|
+
url?: `http://${string}` | `https://${string}`;
|
|
13
|
+
port?: number;
|
|
14
|
+
precompress?: boolean;
|
|
15
|
+
prerender?: Route.Prerender;
|
|
16
|
+
metadata?: Metadata.Item;
|
|
17
|
+
trailingSlash?: boolean;
|
|
18
|
+
readonly logger?: {
|
|
19
|
+
level?: LogLevel;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export type RuntimeConfig = PluginConfig & {
|
|
23
|
+
precompress: NonNullable<PluginConfig['precompress']>;
|
|
24
|
+
trailingSlash: NonNullable<PluginConfig['trailingSlash']>;
|
|
25
|
+
};
|
|
26
|
+
export type BuildContext = {
|
|
27
|
+
prerenderedRoutes: Set<string>;
|
|
28
|
+
exportReader: ExportReader;
|
|
29
|
+
};
|
|
30
|
+
export type SolasRequest = Request & {};
|
|
31
|
+
export type Segment = {
|
|
32
|
+
__id: string;
|
|
33
|
+
__path: string;
|
|
34
|
+
__params: string[];
|
|
35
|
+
__kind: typeof Build.EntryKind.PAGE;
|
|
36
|
+
__depth: number;
|
|
37
|
+
method: 'get';
|
|
38
|
+
paths: {
|
|
39
|
+
layouts: (string | null)[];
|
|
40
|
+
'401s': (string | null)[];
|
|
41
|
+
'403s': (string | null)[];
|
|
42
|
+
'404s': (string | null)[];
|
|
43
|
+
'500s': (string | null)[];
|
|
44
|
+
loaders: (string | null)[];
|
|
45
|
+
middlewares: (string | null)[];
|
|
46
|
+
page?: string | null;
|
|
47
|
+
};
|
|
48
|
+
error?: HttpException | Error;
|
|
49
|
+
prerender: Route.Prerender;
|
|
50
|
+
dynamic: boolean;
|
|
51
|
+
wildcard: boolean;
|
|
52
|
+
};
|
|
53
|
+
export type Endpoint = {
|
|
54
|
+
__id: string;
|
|
55
|
+
__path: string;
|
|
56
|
+
__params: string[];
|
|
57
|
+
__kind: typeof Build.EntryKind.ENDPOINT;
|
|
58
|
+
method: Lowercase<HttpMethod>;
|
|
59
|
+
middlewares: (string | null)[];
|
|
60
|
+
};
|
|
61
|
+
export type ManifestEntry = Segment | Endpoint;
|
|
62
|
+
export type Manifest = Awaited<ReturnType<typeof Build.Finder.prototype.process>>['manifest'];
|
|
63
|
+
export type View<TProps> = React.ComponentType<TProps> | React.LazyExoticComponent<React.ComponentType<TProps>>;
|
|
64
|
+
export type StaticImport = Record<string, unknown>;
|
|
65
|
+
export type DynamicImport<T = Record<string, unknown>> = () => Promise<T>;
|
|
66
|
+
export type MapEntry = {
|
|
67
|
+
shell?: StaticImport;
|
|
68
|
+
page?: DynamicImport;
|
|
69
|
+
layouts?: readonly (DynamicImport | null)[];
|
|
70
|
+
'401s'?: readonly (DynamicImport | null)[];
|
|
71
|
+
'403s'?: readonly (DynamicImport | null)[];
|
|
72
|
+
'404s'?: readonly (DynamicImport | null)[];
|
|
73
|
+
'500s'?: readonly (DynamicImport | null)[];
|
|
74
|
+
loaders?: readonly (DynamicImport | null)[];
|
|
75
|
+
middlewares?: readonly (Router.Middleware | null)[];
|
|
76
|
+
endpoint?: (req?: BunRequest) => unknown;
|
|
77
|
+
};
|
|
78
|
+
export type ImportMap = Record<string, MapEntry>;
|
|
79
|
+
export type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS';
|
|
80
|
+
export type Primitive = string | number | boolean | bigint | symbol | null | undefined;
|
|
81
|
+
export type LooseNumber<T extends number> = T | (number & {});
|
|
82
|
+
export type BuildManifest = {
|
|
83
|
+
prerenderedRoutes: string[];
|
|
84
|
+
precompress: boolean;
|
|
85
|
+
};
|
|
86
|
+
export declare namespace Route {
|
|
87
|
+
type Metadata = Metadata.Item | ((input: Metadata.Input<Router.Params>) => Promise<Metadata.Item> | Metadata.Item);
|
|
88
|
+
type Prerender = (typeof Solas.Config.PRERENDER_MODES)[number];
|
|
89
|
+
}
|
|
90
|
+
export type BoundaryError = Error & {
|
|
91
|
+
digest?: string;
|
|
92
|
+
};
|
|
93
|
+
export {};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { Solas } from './solas';
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { brotliCompress } from 'node:zlib';
|
|
5
|
+
export { Compress };
|
|
6
|
+
var Compress;
|
|
7
|
+
(function (Compress) {
|
|
8
|
+
const DEFAULT_CONCURRENCY = Math.max(1, Math.min(os.cpus().length, 8));
|
|
9
|
+
async function collect(input, filter, output = []) {
|
|
10
|
+
const stat = await fs.stat(input);
|
|
11
|
+
if (!stat.isDirectory()) {
|
|
12
|
+
if (filter(input))
|
|
13
|
+
output.push(input);
|
|
14
|
+
return output;
|
|
15
|
+
}
|
|
16
|
+
for (const entry of await fs.readdir(input, { withFileTypes: true })) {
|
|
17
|
+
const next = path.join(input, entry.name);
|
|
18
|
+
if (entry.isDirectory()) {
|
|
19
|
+
await collect(next, filter, output);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (entry.isFile() && filter(next))
|
|
23
|
+
output.push(next);
|
|
24
|
+
}
|
|
25
|
+
return output;
|
|
26
|
+
}
|
|
27
|
+
async function compress(input) {
|
|
28
|
+
const file = Bun.file(input);
|
|
29
|
+
const buffer = Buffer.from(await file.arrayBuffer());
|
|
30
|
+
const compressed = await new Promise((fulfill, reject) => {
|
|
31
|
+
brotliCompress(buffer, (err, res) => {
|
|
32
|
+
if (err) {
|
|
33
|
+
reject(err);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
fulfill(res);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
// return the input path plus a zero-copy Uint8Array view over the
|
|
41
|
+
// compressed Buffer using its exact offset and length
|
|
42
|
+
return {
|
|
43
|
+
input,
|
|
44
|
+
compressed: new Uint8Array(compressed.buffer, compressed.byteOffset, compressed.byteLength),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Compress a file or directory
|
|
49
|
+
*/
|
|
50
|
+
async function* run(input, config = {}) {
|
|
51
|
+
const { filter = f => /\.(js|css|html|svg|json|txt)$/.test(f) } = config;
|
|
52
|
+
const targets = await collect(input, filter);
|
|
53
|
+
if (!targets.length)
|
|
54
|
+
return;
|
|
55
|
+
let index = 0;
|
|
56
|
+
const pending = new Map();
|
|
57
|
+
function enqueue() {
|
|
58
|
+
while (index < targets.length && pending.size < DEFAULT_CONCURRENCY) {
|
|
59
|
+
const i = index++;
|
|
60
|
+
const value = targets[i];
|
|
61
|
+
pending.set(i, compress(value).then(compressed => ({
|
|
62
|
+
index: i,
|
|
63
|
+
value: compressed,
|
|
64
|
+
})));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
enqueue();
|
|
68
|
+
while (pending.size > 0) {
|
|
69
|
+
const settled = await Promise.race(pending.values());
|
|
70
|
+
pending.delete(settled.index);
|
|
71
|
+
yield settled.value;
|
|
72
|
+
enqueue();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
Compress.run = run;
|
|
76
|
+
})(Compress || (Compress = {}));
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { Logger } from './logger';
|
|
3
|
+
const logger = new Logger();
|
|
4
|
+
export { Context };
|
|
5
|
+
var Context;
|
|
6
|
+
(function (Context) {
|
|
7
|
+
function create(name) {
|
|
8
|
+
const storage = new AsyncLocalStorage();
|
|
9
|
+
return {
|
|
10
|
+
use() {
|
|
11
|
+
const r = storage.getStore();
|
|
12
|
+
if (!r) {
|
|
13
|
+
const error = new Error(`No ${name} context available`);
|
|
14
|
+
logger.error(`[Context:create] ${error.message}`, error);
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
return r;
|
|
18
|
+
},
|
|
19
|
+
write(value, fn) {
|
|
20
|
+
return storage.run(value, fn);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
Context.create = create;
|
|
25
|
+
})(Context || (Context = {}));
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export { Cookies };
|
|
2
|
+
var Cookies;
|
|
3
|
+
(function (Cookies) {
|
|
4
|
+
function decode(value) {
|
|
5
|
+
try {
|
|
6
|
+
// some clients encode spaces as '+'
|
|
7
|
+
return decodeURIComponent(value.replace(/\+/g, ' '));
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
// keep raw value if decoding fails
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function parse(header) {
|
|
15
|
+
const out = new Map();
|
|
16
|
+
if (!header)
|
|
17
|
+
return out;
|
|
18
|
+
for (const part of header.split(';')) {
|
|
19
|
+
// cookie values may contain =, so only split on the
|
|
20
|
+
// first separator
|
|
21
|
+
const separator = part.indexOf('=');
|
|
22
|
+
const raw = separator === -1 ? part : part.slice(0, separator);
|
|
23
|
+
const key = raw.trim();
|
|
24
|
+
if (!key)
|
|
25
|
+
continue;
|
|
26
|
+
// later duplicates are ignored so the first cookie value wins
|
|
27
|
+
if (out.has(key))
|
|
28
|
+
continue;
|
|
29
|
+
const value = separator === -1 ? '' : part.slice(separator + 1).trim();
|
|
30
|
+
out.set(key, decode(value));
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
Cookies.parse = parse;
|
|
35
|
+
})(Cookies || (Cookies = {}));
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare class ExportReader {
|
|
2
|
+
#private;
|
|
3
|
+
/**
|
|
4
|
+
* Read the raw text content of a file
|
|
5
|
+
*/
|
|
6
|
+
raw(filePath: string): Promise<string>;
|
|
7
|
+
/**
|
|
8
|
+
* Get the names of all exports from a file
|
|
9
|
+
*/
|
|
10
|
+
exports(filePath: string): Promise<string[]>;
|
|
11
|
+
/**
|
|
12
|
+
* Check if a file exports a specific name
|
|
13
|
+
*/
|
|
14
|
+
has(filePath: string, name: string): Promise<boolean>;
|
|
15
|
+
/**
|
|
16
|
+
* Read a simple literal export from a file without executing it
|
|
17
|
+
* @description supports string, number, boolean, and null literals.
|
|
18
|
+
* The export must be in the form of `export const|let|var name = <literal>`
|
|
19
|
+
*/
|
|
20
|
+
literal<T>(filePath: string, name: string, validate?: ExportReader.Validator<T>): Promise<T | undefined>;
|
|
21
|
+
/**
|
|
22
|
+
* Read an export from a file by executing the module
|
|
23
|
+
*/
|
|
24
|
+
value<T>(filePath: string, name: string, validate?: ExportReader.Validator<T>): Promise<T | undefined>;
|
|
25
|
+
}
|
|
26
|
+
export declare namespace ExportReader {
|
|
27
|
+
type Loader = 'js' | 'jsx' | 'ts' | 'tsx';
|
|
28
|
+
type Validator<T> = (value: unknown) => value is T;
|
|
29
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
export class ExportReader {
|
|
3
|
+
#transpilers = new Map();
|
|
4
|
+
/**
|
|
5
|
+
* Pick the Bun loader that matches the source file extension
|
|
6
|
+
*/
|
|
7
|
+
static #getLoader(filePath) {
|
|
8
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
9
|
+
if (ext === '.js' || ext === '.mjs' || ext === '.cjs')
|
|
10
|
+
return 'js';
|
|
11
|
+
if (ext === '.jsx')
|
|
12
|
+
return 'jsx';
|
|
13
|
+
if (ext === '.ts' || ext === '.mts' || ext === '.cts')
|
|
14
|
+
return 'ts';
|
|
15
|
+
if (ext === '.tsx')
|
|
16
|
+
return 'tsx';
|
|
17
|
+
throw new Error(`Unsupported module extension: ${ext || '(none)'} in ${filePath}`);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Reuse one transpiler per supported loader so scans match the module syntax
|
|
21
|
+
*/
|
|
22
|
+
#getTranspiler(filePath) {
|
|
23
|
+
const loader = ExportReader.#getLoader(filePath);
|
|
24
|
+
const cached = this.#transpilers.get(loader);
|
|
25
|
+
if (cached)
|
|
26
|
+
return cached;
|
|
27
|
+
const transpiler = new Bun.Transpiler({ loader });
|
|
28
|
+
this.#transpilers.set(loader, transpiler);
|
|
29
|
+
return transpiler;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Parse a literal value from a string
|
|
33
|
+
*/
|
|
34
|
+
static #parse(value) {
|
|
35
|
+
const trimmed = value.trim();
|
|
36
|
+
// keep quoted literals as strings without evaluating the
|
|
37
|
+
// source text
|
|
38
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
39
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'")) ||
|
|
40
|
+
(trimmed.startsWith('`') && trimmed.endsWith('`'))) {
|
|
41
|
+
return trimmed.slice(1, -1);
|
|
42
|
+
}
|
|
43
|
+
if (trimmed === 'true')
|
|
44
|
+
return true;
|
|
45
|
+
if (trimmed === 'false')
|
|
46
|
+
return false;
|
|
47
|
+
if (trimmed === 'null')
|
|
48
|
+
return null;
|
|
49
|
+
const n = Number(trimmed);
|
|
50
|
+
if (Number.isFinite(n))
|
|
51
|
+
return n;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Read the raw text content of a file
|
|
55
|
+
*/
|
|
56
|
+
async raw(filePath) {
|
|
57
|
+
return Bun.file(filePath).text();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get the names of all exports from a file
|
|
61
|
+
*/
|
|
62
|
+
async exports(filePath) {
|
|
63
|
+
// use Bun's transpiler scan so we can inspect export names
|
|
64
|
+
// without loading the module
|
|
65
|
+
return this.#getTranspiler(filePath).scan(await this.raw(filePath)).exports;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Check if a file exports a specific name
|
|
69
|
+
*/
|
|
70
|
+
async has(filePath, name) {
|
|
71
|
+
const names = await this.exports(filePath);
|
|
72
|
+
return names.includes(name);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Read a simple literal export from a file without executing it
|
|
76
|
+
* @description supports string, number, boolean, and null literals.
|
|
77
|
+
* The export must be in the form of `export const|let|var name = <literal>`
|
|
78
|
+
*/
|
|
79
|
+
async literal(filePath, name, validate) {
|
|
80
|
+
const code = await this.raw(filePath);
|
|
81
|
+
// build the matcher from escaped plain-text pieces so arbitrary export names
|
|
82
|
+
// cannot change the regex shape
|
|
83
|
+
const source =
|
|
84
|
+
// match: `export const|let|var `
|
|
85
|
+
'\\bexport\\s+(?:const|let|var)\\s+' +
|
|
86
|
+
// treat export name as plain text in regex
|
|
87
|
+
name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +
|
|
88
|
+
// capture one supported literal value (string, number, boolean, null)
|
|
89
|
+
'\\s*=\\s*(?<value>(?:"(?:[^"\\\\]|\\\\.)*"|\'(?:[^\'\\\\]|\\\\.)*\'|\\x60(?:[^\\x60\\\\]|\\\\.)*\\x60|true|false|null|-?\\d+(?:\\.\\d+)?))(?=\\s|;|$)';
|
|
90
|
+
const text = code.match(new RegExp(source))?.groups?.value;
|
|
91
|
+
if (!text)
|
|
92
|
+
return;
|
|
93
|
+
// only support cheap literal parsing here. Anything richer should go through
|
|
94
|
+
// value() so module semantics stay correct
|
|
95
|
+
const value = ExportReader.#parse(text);
|
|
96
|
+
if (value === undefined)
|
|
97
|
+
return;
|
|
98
|
+
if (!validate || validate(value))
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Read an export from a file by executing the module
|
|
103
|
+
*/
|
|
104
|
+
async value(filePath, name, validate) {
|
|
105
|
+
if (!(await this.has(filePath, name)))
|
|
106
|
+
return;
|
|
107
|
+
// resolve from the project root so generated/build-time callers can pass the
|
|
108
|
+
// same workspace-relative paths used elsewhere in the route graph
|
|
109
|
+
const abs = path.resolve(process.cwd(), filePath);
|
|
110
|
+
const mod = (await import(/* @vite-ignore */ abs));
|
|
111
|
+
const value = mod[name];
|
|
112
|
+
if (value === undefined)
|
|
113
|
+
return;
|
|
114
|
+
if (!validate || validate(value))
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { format } from 'oxfmt';
|
|
3
|
+
import { Logger } from './logger';
|
|
4
|
+
const logger = new Logger();
|
|
5
|
+
const BASE_OPTIONS = {
|
|
6
|
+
useTabs: true,
|
|
7
|
+
tabWidth: 2,
|
|
8
|
+
printWidth: 90,
|
|
9
|
+
singleQuote: true,
|
|
10
|
+
jsxSingleQuote: false,
|
|
11
|
+
quoteProps: 'as-needed',
|
|
12
|
+
trailingComma: 'all',
|
|
13
|
+
semi: false,
|
|
14
|
+
arrowParens: 'avoid',
|
|
15
|
+
bracketSameLine: true,
|
|
16
|
+
bracketSpacing: true,
|
|
17
|
+
endOfLine: 'lf',
|
|
18
|
+
};
|
|
19
|
+
const SUPPORTED_EXTENSIONS = new Set([
|
|
20
|
+
'.js',
|
|
21
|
+
'.jsx',
|
|
22
|
+
'.ts',
|
|
23
|
+
'.tsx',
|
|
24
|
+
'.mjs',
|
|
25
|
+
'.cjs',
|
|
26
|
+
'.mts',
|
|
27
|
+
'.cts',
|
|
28
|
+
'.json',
|
|
29
|
+
'.jsonc',
|
|
30
|
+
'.json5',
|
|
31
|
+
'.css',
|
|
32
|
+
'.scss',
|
|
33
|
+
'.less',
|
|
34
|
+
'.md',
|
|
35
|
+
'.mdx',
|
|
36
|
+
'.html',
|
|
37
|
+
'.yml',
|
|
38
|
+
'.yaml',
|
|
39
|
+
'.toml',
|
|
40
|
+
]);
|
|
41
|
+
export { Format };
|
|
42
|
+
var Format;
|
|
43
|
+
(function (Format) {
|
|
44
|
+
/**
|
|
45
|
+
* Format a file in-place using oxfmt with our preferred code style
|
|
46
|
+
*/
|
|
47
|
+
async function run(filePath) {
|
|
48
|
+
try {
|
|
49
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
50
|
+
if (!SUPPORTED_EXTENSIONS.has(ext)) {
|
|
51
|
+
logger.warn(`[format] Skipping unsupported file type: ${filePath}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const file = Bun.file(filePath);
|
|
55
|
+
const source = await file.text();
|
|
56
|
+
const options = ext === '.json' ? { ...BASE_OPTIONS, trailingComma: 'none' } : BASE_OPTIONS;
|
|
57
|
+
const result = await format(filePath, source, options);
|
|
58
|
+
if (result.errors.length > 0) {
|
|
59
|
+
logger.warn(`[format] oxfmt failed for ${filePath}: ${result.errors[0]?.message}`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (result.code === source)
|
|
63
|
+
return;
|
|
64
|
+
await Bun.write(filePath, result.code);
|
|
65
|
+
logger.info(`[format] Formatted file: ${filePath}`);
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
logger.error(`[format] Failed to format file: ${filePath}`, err);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
Format.run = run;
|
|
72
|
+
})(Format || (Format = {}));
|