@coreframe/vite 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,13 @@
1
+ import { Plugin } from "vite";
2
+
3
+ //#region assets.d.ts
4
+ type CoreframeAssetsPluginOptions = {
5
+ devMaterialize?: boolean;
6
+ cwd?: string;
7
+ };
8
+ declare function coreframeAssetsPlugin({
9
+ cwd,
10
+ devMaterialize
11
+ }?: CoreframeAssetsPluginOptions): Plugin;
12
+ //#endregion
13
+ export { CoreframeAssetsPluginOptions, coreframeAssetsPlugin };
package/dist/assets.js ADDED
@@ -0,0 +1,83 @@
1
+ import { checkAssets, formatAssetProblems, materializeAsset, readAssetManifest } from "@coreframe/scripts/assets";
2
+
3
+ //#region assets.ts
4
+ const manifestPath = "assets/manifest.json";
5
+ const markdownAssetReferencePattern = /@asset\s+([a-z0-9][a-z0-9/-]*)/g;
6
+ function coreframeAssetsPlugin({ cwd = process.cwd(), devMaterialize = true } = {}) {
7
+ const virtualId = "virtual:assets";
8
+ const resolvedVirtualId = `\0${virtualId}`;
9
+ return {
10
+ name: "coreframe:assets",
11
+ async buildStart() {
12
+ if (this.meta.watchMode) return;
13
+ const status = await checkAssets({ cwd });
14
+ if (!status.ok) throw new Error(formatAssetProblems(status.problems));
15
+ },
16
+ async resolveId(id) {
17
+ if (id === virtualId) return resolvedVirtualId;
18
+ },
19
+ async load(id) {
20
+ if (id !== resolvedVirtualId) return;
21
+ const { manifest, problems } = await readAssetManifest(cwd);
22
+ if (problems.length > 0) throw new Error(formatAssetProblems(problems));
23
+ return [
24
+ `const manifest = ${JSON.stringify(manifest)};`,
25
+ "export { manifest };",
26
+ "export function asset(id) {",
27
+ " const entry = manifest[id];",
28
+ " if (!entry) throw new Error(`Unknown Coreframe asset: ${id}`);",
29
+ " return entry;",
30
+ "}"
31
+ ].join("\n");
32
+ },
33
+ async transform(code, id) {
34
+ const assetIds = [...code.matchAll(markdownAssetReferencePattern)].map((match) => match[1]);
35
+ if (assetIds.length === 0) return;
36
+ const loaded = await readAssetManifest(cwd);
37
+ const problems = [...loaded.problems];
38
+ let transformed = code;
39
+ for (const assetId of assetIds) {
40
+ const entry = loaded.manifest[assetId];
41
+ if (!entry) {
42
+ problems.push({
43
+ code: "INVALID_REFERENCE",
44
+ message: `Asset reference "${assetId}" in "${id}" is not present in ${manifestPath}.`,
45
+ assetId,
46
+ file: id
47
+ });
48
+ continue;
49
+ }
50
+ if (this.meta.watchMode && devMaterialize && entry.variants.length === 0) {
51
+ this.warn(`Materializing Coreframe asset "${assetId}" for dev.`);
52
+ const result = await materializeAsset(assetId, {
53
+ cwd,
54
+ updateManifest: false
55
+ });
56
+ problems.push(...result.problems);
57
+ const materialized = result.assets.find((asset) => asset.assetId === assetId);
58
+ if (materialized) loaded.manifest[assetId] = {
59
+ ...entry,
60
+ variants: materialized.variants
61
+ };
62
+ }
63
+ const variant = loaded.manifest[assetId]?.variants[0];
64
+ if (!variant) {
65
+ problems.push({
66
+ code: "GENERATED_VARIANT_MISSING",
67
+ message: `Asset "${assetId}" has no generated variant.`,
68
+ assetId,
69
+ file: id,
70
+ fix: `Run: coreframe assets build --id ${assetId}`
71
+ });
72
+ continue;
73
+ }
74
+ transformed = transformed.replaceAll(`@asset ${assetId}`, `/${variant.path}`);
75
+ }
76
+ if (problems.length > 0) throw new Error(formatAssetProblems(problems));
77
+ return transformed;
78
+ }
79
+ };
80
+ }
81
+
82
+ //#endregion
83
+ export { coreframeAssetsPlugin };
@@ -0,0 +1,18 @@
1
+ import { ConfigEnv, FSWatcher } from "vite";
2
+
3
+ //#region image-generator.d.ts
4
+ declare function imageGenerator(): {
5
+ name: string;
6
+ configResolved(config: {
7
+ command: ConfigEnv["command"];
8
+ }): void;
9
+ configureServer(server: {
10
+ watcher: FSWatcher;
11
+ }): Promise<void>;
12
+ applyToEnvironment(environment: {
13
+ name: string;
14
+ }): boolean;
15
+ buildStart(): Promise<void>;
16
+ };
17
+ //#endregion
18
+ export { imageGenerator };
@@ -0,0 +1,79 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { glob } from "tinyglobby";
4
+
5
+ //#region image-generator.ts
6
+ function imageGenerator() {
7
+ let command;
8
+ return {
9
+ name: "image-generator",
10
+ configResolved(config) {
11
+ command = config.command;
12
+ },
13
+ async configureServer(server) {
14
+ await generateAllDensityImages();
15
+ server.watcher.on("add", (file) => {
16
+ if (isSourceDensityImage(file)) generateDensityImages(file);
17
+ });
18
+ server.watcher.on("change", (file) => {
19
+ if (isSourceDensityImage(file)) generateDensityImages(file, { overwrite: true });
20
+ });
21
+ },
22
+ applyToEnvironment(environment) {
23
+ return environment.name === "client";
24
+ },
25
+ async buildStart() {
26
+ if (command === "build") await generateAllDensityImages();
27
+ }
28
+ };
29
+ }
30
+ async function generateAllDensityImages() {
31
+ const files = await glob(["src/**/*@3x.{png,webp}", "public/**/*@3x.{png,webp}"]);
32
+ console.log(`[image-generator] Found ${files.length} @3x images.`);
33
+ await Promise.all(files.map((file) => generateDensityImages(file)));
34
+ }
35
+ function isSourceDensityImage(file) {
36
+ return /@3x\.(png|webp)$/.test(file);
37
+ }
38
+ async function generateDensityImages(file, options = {}) {
39
+ const dir = path.dirname(file);
40
+ const ext = path.extname(file);
41
+ const base = path.basename(file, `@3x${ext}`);
42
+ const outputImages = [{
43
+ outputPath: path.join(dir, `${base}@2x${ext}`),
44
+ scale: 2 / 3
45
+ }, {
46
+ outputPath: path.join(dir, `${base}@1x${ext}`),
47
+ scale: 1 / 3
48
+ }].filter((image) => options.overwrite || !fs.existsSync(image.outputPath));
49
+ if (outputImages.length === 0) return;
50
+ const { default: sharp } = await import("sharp");
51
+ let width;
52
+ try {
53
+ width = (await sharp(file).metadata()).width;
54
+ } catch (error) {
55
+ console.error(`[image-generator] Failed to read metadata for ${file}`, error);
56
+ return;
57
+ }
58
+ if (!width) {
59
+ console.error(`[image-generator] Skipping ${file}; image width could not be read.`);
60
+ return;
61
+ }
62
+ await Promise.all(outputImages.map((image) => generateDensityImage({
63
+ file,
64
+ outputPath: image.outputPath,
65
+ sharp,
66
+ width: Math.round(width * image.scale)
67
+ })));
68
+ }
69
+ async function generateDensityImage(options) {
70
+ try {
71
+ console.log(`[image-generator] Generating ${options.outputPath} (${options.width}w)`);
72
+ await options.sharp(options.file).resize({ width: options.width }).toFile(options.outputPath);
73
+ } catch (error) {
74
+ console.error(`[image-generator] Failed to process ${options.file} for ${options.outputPath}`, error);
75
+ }
76
+ }
77
+
78
+ //#endregion
79
+ export { imageGenerator };
@@ -0,0 +1,19 @@
1
+ import { ConfigEnv, PluginOption } from "vite";
2
+
3
+ //#region index.d.ts
4
+ type CoreframeRunner = "tauri" | "vitest" | "react-router-ssg" | null;
5
+ type CoreframeConfigEnv = ConfigEnv & {
6
+ runner: CoreframeRunner;
7
+ };
8
+ type PluginFilter = (env: CoreframeConfigEnv) => boolean;
9
+ type PluginSelector = (env: CoreframeConfigEnv) => PluginOption;
10
+ type SelectablePlugin = PluginOption | PluginSelector;
11
+ declare function extendConfigEnv(env: ConfigEnv, processEnv?: NodeJS.ProcessEnv): CoreframeConfigEnv;
12
+ declare function selectPlugins(env: CoreframeConfigEnv, plugins: readonly SelectablePlugin[]): PluginOption[];
13
+ declare function apply(filter: PluginFilter, plugin: SelectablePlugin): PluginSelector;
14
+ declare function buildOnly(plugin: SelectablePlugin): PluginSelector;
15
+ declare function devOnly(plugin: SelectablePlugin): PluginSelector;
16
+ declare function runnerOnly(runners: CoreframeRunner | readonly CoreframeRunner[], plugin: SelectablePlugin): PluginSelector;
17
+ declare function exceptRunner(runners: CoreframeRunner | readonly CoreframeRunner[], plugin: SelectablePlugin): PluginSelector;
18
+ //#endregion
19
+ export { CoreframeConfigEnv, CoreframeRunner, PluginFilter, PluginSelector, SelectablePlugin, apply, buildOnly, devOnly, exceptRunner, extendConfigEnv, runnerOnly, selectPlugins };
package/dist/index.js ADDED
@@ -0,0 +1,34 @@
1
+ //#region index.ts
2
+ function extendConfigEnv(env, processEnv = process.env) {
3
+ return {
4
+ ...env,
5
+ runner: processEnv.TAURI_ENV_PLATFORM !== void 0 ? "tauri" : processEnv.VITEST === "true" ? "vitest" : processEnv.IS_RR_BUILD_REQUEST === "yes" ? "react-router-ssg" : null
6
+ };
7
+ }
8
+ function selectPlugins(env, plugins) {
9
+ return plugins.map((plugin) => selectPlugin(env, plugin));
10
+ }
11
+ function apply(filter, plugin) {
12
+ return (env) => filter(env) ? selectPlugin(env, plugin) : null;
13
+ }
14
+ function buildOnly(plugin) {
15
+ return apply((env) => env.command === "build", plugin);
16
+ }
17
+ function devOnly(plugin) {
18
+ return apply((env) => env.command === "serve", plugin);
19
+ }
20
+ function runnerOnly(runners, plugin) {
21
+ return apply((env) => includesRunner(runners, env.runner), plugin);
22
+ }
23
+ function exceptRunner(runners, plugin) {
24
+ return apply((env) => !includesRunner(runners, env.runner), plugin);
25
+ }
26
+ function selectPlugin(env, plugin) {
27
+ return typeof plugin === "function" ? plugin(env) : plugin;
28
+ }
29
+ function includesRunner(runners, runner) {
30
+ return (typeof runners === "string" || runners === null ? [runners] : runners).includes(runner);
31
+ }
32
+
33
+ //#endregion
34
+ export { apply, buildOnly, devOnly, exceptRunner, extendConfigEnv, runnerOnly, selectPlugins };
@@ -0,0 +1,11 @@
1
+ import { Plugin } from "vite";
2
+
3
+ //#region markdown-frontmatter.d.ts
4
+ type MarkdownFrontmatterPluginOptions = {
5
+ contentDirectory: string;
6
+ };
7
+ declare function markdownFrontmatterPlugin({
8
+ contentDirectory
9
+ }: MarkdownFrontmatterPluginOptions): Plugin;
10
+ //#endregion
11
+ export { MarkdownFrontmatterPluginOptions, markdownFrontmatterPlugin };
@@ -0,0 +1,20 @@
1
+ import { normalizePath } from "vite";
2
+
3
+ //#region markdown-frontmatter.ts
4
+ const markdownFrontmatterPattern = /^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/;
5
+ function markdownFrontmatterPlugin({ contentDirectory }) {
6
+ const normalizedContentDirectory = `${normalizePath(contentDirectory).replace(/\/+$/, "")}/`;
7
+ return {
8
+ name: "coreframe:markdown-frontmatter",
9
+ enforce: "pre",
10
+ transform(code, id) {
11
+ const [path = "", query] = id.split("?");
12
+ const normalizedPath = normalizePath(path);
13
+ if (query || !normalizedPath.startsWith(normalizedContentDirectory) || !path.endsWith(".md")) return;
14
+ return code.replace(markdownFrontmatterPattern, "");
15
+ }
16
+ };
17
+ }
18
+
19
+ //#endregion
20
+ export { markdownFrontmatterPlugin };
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@coreframe/vite",
3
+ "version": "0.1.0",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/airlock-labs/coreframe.git",
7
+ "directory": "vite"
8
+ },
9
+ "files": [
10
+ "dist/"
11
+ ],
12
+ "type": "module",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ },
18
+ "./assets": {
19
+ "types": "./dist/assets.d.ts",
20
+ "default": "./dist/assets.js"
21
+ },
22
+ "./image-generator": {
23
+ "types": "./dist/image-generator.d.ts",
24
+ "default": "./dist/image-generator.js"
25
+ },
26
+ "./markdown-frontmatter": {
27
+ "types": "./dist/markdown-frontmatter.d.ts",
28
+ "default": "./dist/markdown-frontmatter.js"
29
+ }
30
+ },
31
+ "dependencies": {
32
+ "tinyglobby": "^0.2.17",
33
+ "@coreframe/scripts": "^0.1.11"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^26.1.0",
37
+ "sharp": "^0.34.5",
38
+ "tsdown": "0.22.3",
39
+ "typescript": "6.0.3",
40
+ "vite": "^8.1.0",
41
+ "vitest": "^4.1.9"
42
+ },
43
+ "peerDependencies": {
44
+ "sharp": "^0.34.5 || ^0.35.0",
45
+ "vite": "^8.0.0"
46
+ },
47
+ "peerDependenciesMeta": {
48
+ "sharp": {
49
+ "optional": true
50
+ }
51
+ },
52
+ "scripts": {
53
+ "build": "tsdown",
54
+ "test": "vitest run",
55
+ "typecheck": "tsc --noEmit -p tsconfig.json",
56
+ "format": "oxfmt",
57
+ "lint": "oxlint"
58
+ }
59
+ }