fuma-content 0.0.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,347 @@
1
+ // src/constants.ts
2
+ var defaultConfig = {
3
+ files: ["./content/**/*"]
4
+ };
5
+ var defaultConfigPath = "./fc.config.js";
6
+
7
+ // src/loader/mdx.ts
8
+ import { createProcessor } from "@mdx-js/mdx";
9
+ import grayMatter from "gray-matter";
10
+
11
+ // src/utils/git-timpstamp.ts
12
+ import path from "node:path";
13
+ import fs from "node:fs";
14
+ import { spawn } from "cross-spawn";
15
+ var cache = /* @__PURE__ */ new Map();
16
+ function getGitTimestamp(file) {
17
+ const cachedTimestamp = cache.get(file);
18
+ if (cachedTimestamp)
19
+ return Promise.resolve(cachedTimestamp);
20
+ return new Promise((resolve, reject) => {
21
+ const cwd = path.dirname(file);
22
+ if (!fs.existsSync(cwd)) {
23
+ resolve(void 0);
24
+ return;
25
+ }
26
+ const fileName = path.basename(file);
27
+ const child = spawn("git", ["log", "-1", '--pretty="%ai"', fileName], {
28
+ cwd
29
+ });
30
+ let output;
31
+ child.stdout.on("data", (d) => output = new Date(String(d)));
32
+ child.on("close", () => {
33
+ if (output)
34
+ cache.set(file, output);
35
+ resolve(output);
36
+ });
37
+ child.on("error", reject);
38
+ });
39
+ }
40
+
41
+ // src/remark-plugins/utils.ts
42
+ import { valueToEstree } from "estree-util-value-to-estree";
43
+ function getMdastExport(name, value) {
44
+ return {
45
+ type: "mdxjsEsm",
46
+ value: "",
47
+ data: {
48
+ estree: {
49
+ type: "Program",
50
+ sourceType: "module",
51
+ body: [
52
+ {
53
+ type: "ExportNamedDeclaration",
54
+ specifiers: [],
55
+ source: null,
56
+ declaration: {
57
+ type: "VariableDeclaration",
58
+ kind: "const",
59
+ declarations: [
60
+ {
61
+ type: "VariableDeclarator",
62
+ id: {
63
+ type: "Identifier",
64
+ name
65
+ },
66
+ init: valueToEstree(value)
67
+ }
68
+ ]
69
+ }
70
+ }
71
+ ]
72
+ }
73
+ }
74
+ };
75
+ }
76
+
77
+ // src/remark-plugins/remark-exports.ts
78
+ function remarkMdxExport({ values }) {
79
+ return (tree, vfile) => {
80
+ for (const name of values) {
81
+ if (!(name in vfile.data))
82
+ return;
83
+ tree.children.unshift(getMdastExport(name, vfile.data[name]));
84
+ }
85
+ };
86
+ }
87
+
88
+ // src/loader/mdx.ts
89
+ var cache2 = /* @__PURE__ */ new Map();
90
+ var loadMDX = ({
91
+ lastModifiedTime,
92
+ format: forceFormat,
93
+ remarkExports = ["frontmatter"],
94
+ ...rest
95
+ } = {}) => {
96
+ return async (filePath, source) => {
97
+ const { content, data: frontmatter } = grayMatter(source);
98
+ const detectedFormat = filePath.endsWith(".mdx") ? "mdx" : "md";
99
+ const format = forceFormat ?? detectedFormat;
100
+ let timestamp;
101
+ let processor = cache2.get(format);
102
+ if (processor === void 0) {
103
+ processor = createProcessor({
104
+ format,
105
+ development: process.env.NODE_ENV === "development",
106
+ ...rest,
107
+ remarkPlugins: [
108
+ ...rest.remarkPlugins ?? [],
109
+ [remarkMdxExport, { values: remarkExports }]
110
+ ]
111
+ });
112
+ cache2.set(format, processor);
113
+ }
114
+ if (lastModifiedTime === "git")
115
+ timestamp = (await getGitTimestamp(filePath))?.getTime();
116
+ const file = await processor.process({
117
+ value: content,
118
+ path: filePath,
119
+ data: {
120
+ lastModified: timestamp,
121
+ frontmatter
122
+ }
123
+ });
124
+ return {
125
+ content: String(file),
126
+ _mdx: {
127
+ vfile: file
128
+ }
129
+ };
130
+ };
131
+ };
132
+
133
+ // src/loader/entry-point.ts
134
+ import { pathToFileURL } from "node:url";
135
+
136
+ // src/utils/path.ts
137
+ import * as path2 from "node:path";
138
+ import FastGlob from "fast-glob";
139
+ function getAbsolutePath(cwd, relativePath) {
140
+ return FastGlob.escapePath(path2.join(cwd, relativePath));
141
+ }
142
+ function getRelativePath(cwd, absolutePath) {
143
+ return path2.join(
144
+ path2.relative(cwd, path2.dirname(absolutePath)),
145
+ path2.basename(absolutePath)
146
+ );
147
+ }
148
+ async function globFiles({
149
+ cwd,
150
+ globOptions,
151
+ files
152
+ }) {
153
+ return FastGlob.glob(files, {
154
+ cwd,
155
+ ...globOptions
156
+ }).then((result) => result.map((file) => getAbsolutePath(cwd, file)));
157
+ }
158
+ function getOutputPath({ options }, entry) {
159
+ return path2.join(
160
+ options.cwd,
161
+ options.outputDir,
162
+ path2.relative(options.cwd, path2.dirname(entry.file)),
163
+ `${path2.basename(entry.file, path2.extname(entry.file))}${options.outputExt}`
164
+ );
165
+ }
166
+
167
+ // src/loader/entry-point.ts
168
+ function loadEntryPoint(entries) {
169
+ const { mode = "import" } = this.options.entryPoint ?? {};
170
+ let content;
171
+ switch (mode) {
172
+ case "import":
173
+ content = generateImport(this, entries);
174
+ break;
175
+ default:
176
+ content = generateLazy(this, entries);
177
+ break;
178
+ }
179
+ return {
180
+ format: "js",
181
+ file: getAbsolutePath(this.options.cwd, "./index.js"),
182
+ content,
183
+ _entryPoint: {}
184
+ };
185
+ }
186
+ function generateImport(compiler, output) {
187
+ const { fullPath = false } = compiler.options.entryPoint ?? {};
188
+ const formats = /* @__PURE__ */ new Map();
189
+ output.forEach((entry, i) => {
190
+ const b = formats.get(entry.format) ?? { imports: [], entries: [] };
191
+ formats.set(entry.format, b);
192
+ const importPath = pathToFileURL(getOutputPath(compiler, entry));
193
+ const file = fullPath ? entry.file : getRelativePath(compiler.options.cwd, entry.file);
194
+ const name = `p_${i}`;
195
+ b.imports.push(`import * as ${name} from ${JSON.stringify(importPath)};`);
196
+ b.entries.push(`{
197
+ ...${name},
198
+ format: ${JSON.stringify(entry.format)},
199
+ file: ${JSON.stringify(file)},
200
+ }`);
201
+ });
202
+ const imports = Array.from(formats.values()).flatMap((f) => f.imports).join("\n");
203
+ const entires = Array.from(formats.entries()).map(([k, v]) => `${k}: [${v.entries.join(",")}]`).join(",");
204
+ return `${imports}
205
+ export default {${entires}};`;
206
+ }
207
+ function generateLazy(compiler, output) {
208
+ const entries = [];
209
+ for (const entry of output) {
210
+ const fronmatter = entry._mdx ? entry._mdx.vfile.data.frontmatter : {};
211
+ const importPath = pathToFileURL(getOutputPath(compiler, entry));
212
+ const line = `{
213
+ file: ${JSON.stringify(entry.file)},
214
+ info: ${JSON.stringify(fronmatter)},
215
+ load: () => import(${JSON.stringify(importPath)})
216
+ }`;
217
+ entries.push(line);
218
+ }
219
+ return `export default [${entries.join(",\n")}]`;
220
+ }
221
+
222
+ // src/loader/json.ts
223
+ var loadJson = () => {
224
+ return (_file, source) => {
225
+ const parsed = JSON.parse(source);
226
+ return {
227
+ content: `export default ${JSON.stringify(parsed)}`
228
+ };
229
+ };
230
+ };
231
+
232
+ // src/compiler/emit.ts
233
+ import * as path3 from "node:path";
234
+ import * as fs2 from "node:fs/promises";
235
+ async function emit() {
236
+ const entires = await this.compile();
237
+ const emits = entires.map(async (entry) => this.emitEntry(entry));
238
+ this._emit = await Promise.all(emits);
239
+ }
240
+ async function emitEntry(entry) {
241
+ const outputPath = getOutputPath(this, entry);
242
+ await fs2.mkdir(path3.dirname(outputPath), { recursive: true });
243
+ await fs2.writeFile(outputPath, entry.content);
244
+ return {
245
+ ...entry,
246
+ outputPath
247
+ };
248
+ }
249
+
250
+ // src/compiler/compile.ts
251
+ import * as fs3 from "node:fs/promises";
252
+ import * as path4 from "node:path";
253
+ async function compile() {
254
+ this._output = await Promise.all(
255
+ this.files.map((file) => this.compileFile(file))
256
+ );
257
+ this._output.push(loadEntryPoint.call(this, this._output));
258
+ return this._output;
259
+ }
260
+ async function compileFile(file) {
261
+ const cache3 = this._cache.get(file);
262
+ if (cache3)
263
+ return cache3;
264
+ const format = path4.extname(file).slice(1);
265
+ const content = (await fs3.readFile(file)).toString();
266
+ const loader = this.loaders[format];
267
+ const output = await loader?.call(this, file, content);
268
+ if (!output) {
269
+ throw new Error(`Unknown format: ${format}`);
270
+ }
271
+ const entry = {
272
+ file,
273
+ format,
274
+ ...output
275
+ };
276
+ this._cache.set(file, entry);
277
+ return entry;
278
+ }
279
+
280
+ // src/compiler/watch.ts
281
+ import { watch as watchFn } from "chokidar";
282
+ function watch() {
283
+ void this.emit();
284
+ const watcher = watchFn(this.options.files, { cwd: this.options.cwd });
285
+ watcher.on("all", (eventName, path5) => {
286
+ const absolutePath = getAbsolutePath(this.options.cwd, path5);
287
+ if (eventName === "add" && !this.files.includes(absolutePath)) {
288
+ this.files.push(absolutePath);
289
+ void this.emit();
290
+ }
291
+ if (eventName === "unlink") {
292
+ this.files = this.files.filter((file) => file !== absolutePath);
293
+ this._cache.delete(absolutePath);
294
+ void this.emit();
295
+ }
296
+ if (eventName === "change") {
297
+ console.log("update", path5);
298
+ this._cache.delete(absolutePath);
299
+ void this.compileFile(absolutePath).then(async (entry) => {
300
+ await this.emitEntry(entry);
301
+ });
302
+ }
303
+ });
304
+ return watcher;
305
+ }
306
+
307
+ // src/compiler/index.ts
308
+ var defaultOptions = {
309
+ cwd: process.cwd(),
310
+ outputDir: "./dist",
311
+ outputExt: ".js"
312
+ };
313
+ async function createCompiler(options) {
314
+ const compilerOptions = {
315
+ ...defaultOptions,
316
+ ...options
317
+ };
318
+ const files = await globFiles(compilerOptions);
319
+ return {
320
+ files,
321
+ options: compilerOptions,
322
+ compile,
323
+ emitEntry,
324
+ watch,
325
+ emit,
326
+ compileFile,
327
+ loaders: createLoaders(compilerOptions),
328
+ _cache: /* @__PURE__ */ new Map()
329
+ };
330
+ }
331
+ function createLoaders(options) {
332
+ const mdx = loadMDX(options.mdxOptions);
333
+ return {
334
+ mdx,
335
+ md: mdx,
336
+ json: loadJson(),
337
+ ...options.loaders
338
+ };
339
+ }
340
+
341
+ export {
342
+ defaultConfig,
343
+ defaultConfigPath,
344
+ loadMDX,
345
+ loadEntryPoint,
346
+ createCompiler
347
+ };
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createCompiler,
4
+ defaultConfig,
5
+ defaultConfigPath
6
+ } from "../chunk-UDFHC2Y3.js";
7
+
8
+ // src/cli/index.ts
9
+ import { Cli, Command, Option } from "clipanion";
10
+
11
+ // src/utils/load-config.ts
12
+ import * as path from "node:path";
13
+ import { pathToFileURL } from "node:url";
14
+ import * as fs from "node:fs";
15
+ async function loadConfig(configFile = defaultConfigPath) {
16
+ const configPath = path.resolve(configFile);
17
+ if (!fs.existsSync(configPath))
18
+ return defaultConfig;
19
+ const importPath = pathToFileURL(configPath).href;
20
+ const result = await import(`${importPath}?x=${Date.now()}`);
21
+ return "default" in result ? result.default : result;
22
+ }
23
+
24
+ // src/cli/index.ts
25
+ var BuildCommand = class extends Command {
26
+ static paths = [[`build`]];
27
+ config = Option.String({ required: false });
28
+ async execute() {
29
+ const config = await loadConfig(this.config);
30
+ const compiler = await createCompiler(config);
31
+ await compiler.emit();
32
+ this.context.stdout.write(`Build successful
33
+ `);
34
+ }
35
+ };
36
+ var WatchCommand = class extends Command {
37
+ static paths = [[`watch`]];
38
+ config = Option.String({ required: false });
39
+ async execute() {
40
+ const config = await loadConfig(this.config);
41
+ const compiler = await createCompiler(config);
42
+ this.context.stdout.write(`Started server
43
+ `);
44
+ compiler.watch();
45
+ }
46
+ };
47
+ var [node, app, ...args] = process.argv;
48
+ var cli = new Cli({
49
+ binaryLabel: `fuma-content-cli`,
50
+ binaryName: `${node} ${app}`,
51
+ binaryVersion: `1.0.0`
52
+ });
53
+ cli.register(BuildCommand);
54
+ cli.register(WatchCommand);
55
+ void cli.runExit(args);
@@ -0,0 +1,33 @@
1
+ import { AnyZodObject, z } from 'zod';
2
+ import { MDXContent } from 'mdx/types';
3
+
4
+ interface CreateOptions {
5
+ schema?: AnyZodObject;
6
+ /**
7
+ * Filter file paths
8
+ */
9
+ include?: string | string[];
10
+ }
11
+ interface Document<Info = unknown> {
12
+ info: Info;
13
+ /**
14
+ * Render data, should be accepted by renderer
15
+ */
16
+ renderer: MDXContent;
17
+ /**
18
+ * File Path
19
+ */
20
+ file: string;
21
+ }
22
+ interface Json<Info = unknown> {
23
+ info: Info;
24
+ /**
25
+ * File Path
26
+ */
27
+ file: string;
28
+ }
29
+ type GetInfoType<T extends CreateOptions> = T["schema"] extends AnyZodObject ? z.infer<T["schema"]> : Record<string, unknown>;
30
+ declare function document<T extends CreateOptions>(entryPoint: unknown, options?: T): Document<GetInfoType<T>>[];
31
+ declare function json<T extends CreateOptions>(entryPoint: unknown, options?: T): Json<GetInfoType<T>>[];
32
+
33
+ export { type CreateOptions, type Document, type Json, document, json };
package/dist/index.js ADDED
@@ -0,0 +1,54 @@
1
+ // src/source/index.ts
2
+ import { z } from "zod";
3
+ import micromatch from "micromatch";
4
+ var defaultSchema = z.record(z.string(), z.unknown());
5
+ function document(entryPoint, options) {
6
+ const { schema = defaultSchema, include } = options ?? {};
7
+ return read(entryPoint, ["md", "mdx"], include).map((item) => {
8
+ const {
9
+ default: render,
10
+ format: _format,
11
+ file,
12
+ frontmatter,
13
+ ...rest
14
+ } = item;
15
+ const result = schema.safeParse(frontmatter);
16
+ if (!result.success)
17
+ throw createError(file, result.error);
18
+ return {
19
+ file,
20
+ info: result.data,
21
+ renderer: render,
22
+ ...rest
23
+ };
24
+ });
25
+ }
26
+ function json(entryPoint, options) {
27
+ const { schema = defaultSchema, include } = options ?? {};
28
+ return read(entryPoint, ["json"], include).map(
29
+ ({ file, default: data }) => {
30
+ const result = schema.safeParse(data);
31
+ if (!result.success)
32
+ throw createError(file, result.error);
33
+ return {
34
+ file,
35
+ info: result.data
36
+ };
37
+ }
38
+ );
39
+ }
40
+ function createError(file, err) {
41
+ return new Error(
42
+ `${file}:
43
+ ${Object.entries(err.flatten().fieldErrors).map(([k, v]) => `${k}: ${v?.join(", ")}`).join("\n")}`
44
+ );
45
+ }
46
+ function read(entryPoint, format, include) {
47
+ const cast = entryPoint;
48
+ const entries = format.flatMap((f) => cast[f] ?? []);
49
+ return entries.filter((e) => !include || micromatch.isMatch(e.file, include));
50
+ }
51
+ export {
52
+ document,
53
+ json
54
+ };
@@ -0,0 +1,103 @@
1
+ import { Options as Options$1 } from 'fast-glob';
2
+ import { ProcessorOptions } from '@mdx-js/mdx';
3
+ import { VFile } from '@mdx-js/mdx/internal-create-format-aware-processors';
4
+ import { FSWatcher } from 'chokidar';
5
+
6
+ interface Output {
7
+ content: string;
8
+ _entryPoint?: unknown;
9
+ _mdx?: {
10
+ vfile: VFile;
11
+ };
12
+ }
13
+ type Transformer = (this: Compiler, file: string, source: string) => Output | Promise<Output>;
14
+
15
+ interface Options extends ProcessorOptions {
16
+ /**
17
+ * Fetch last modified time with specified version control
18
+ * @defaultValue 'none'
19
+ */
20
+ lastModifiedTime?: "git" | "none";
21
+ /**
22
+ * @defaultValue `['frontmatter']`
23
+ */
24
+ remarkExports?: string[];
25
+ }
26
+ /**
27
+ * Load MDX/markdown files
28
+ */
29
+ declare const loadMDX: ({ lastModifiedTime, format: forceFormat, remarkExports, ...rest }?: Options) => Transformer;
30
+
31
+ interface OutputEntry extends Output {
32
+ /**
33
+ * extension of file, like: `md`
34
+ */
35
+ format: string;
36
+ file: string;
37
+ }
38
+ declare function compile(this: Compiler): Promise<OutputEntry[]>;
39
+ declare function compileFile(this: Compiler, file: string): Promise<OutputEntry>;
40
+
41
+ interface EntryPointOptions {
42
+ /**
43
+ * Notice that `lazy` mode is not supported by `source` function
44
+ *
45
+ * @defaultValue 'import'
46
+ */
47
+ mode?: "lazy" | "import";
48
+ /**
49
+ * Use full-path for `file` property
50
+ *
51
+ * @defaultValue false
52
+ */
53
+ fullPath?: string;
54
+ }
55
+ declare function loadEntryPoint(this: Compiler, entries: OutputEntry[]): OutputEntry;
56
+
57
+ interface EmitEntry extends OutputEntry {
58
+ outputPath: string;
59
+ }
60
+ declare function emit(this: Compiler): Promise<void>;
61
+ declare function emitEntry(this: Compiler, entry: OutputEntry): Promise<EmitEntry>;
62
+
63
+ declare function watch(this: Compiler): FSWatcher;
64
+
65
+ interface CompilerOptions {
66
+ files: string[];
67
+ cwd: string;
68
+ outputDir: string;
69
+ outputExt: string;
70
+ loaders?: Record<string, Transformer>;
71
+ entryPoint?: EntryPointOptions;
72
+ mdxOptions?: Options;
73
+ globOptions?: Options$1;
74
+ }
75
+ interface Compiler {
76
+ options: CompilerOptions;
77
+ /**
78
+ * Files to compile
79
+ */
80
+ files: string[];
81
+ compile: typeof compile;
82
+ compileFile: typeof compileFile;
83
+ emit: typeof emit;
84
+ emitEntry: typeof emitEntry;
85
+ watch: typeof watch;
86
+ loaders: Record<string, Transformer>;
87
+ _output?: OutputEntry[];
88
+ _emit?: EmitEntry[];
89
+ _cache: Map<string, OutputEntry>;
90
+ }
91
+
92
+ declare const defaultOptions: {
93
+ cwd: string;
94
+ outputDir: string;
95
+ outputExt: string;
96
+ };
97
+ type CreateCompilerOptions = Pick<Partial<CompilerOptions>, keyof typeof defaultOptions> & Omit<CompilerOptions, keyof typeof defaultOptions>;
98
+ declare function createCompiler(options: CreateCompilerOptions): Promise<Compiler>;
99
+
100
+ declare const defaultConfig: CreateCompilerOptions;
101
+ declare const defaultConfigPath = "./fc.config.js";
102
+
103
+ export { type CreateCompilerOptions, type EntryPointOptions, type Options, createCompiler, defaultConfig, defaultConfigPath, loadEntryPoint, loadMDX };
@@ -0,0 +1,14 @@
1
+ import {
2
+ createCompiler,
3
+ defaultConfig,
4
+ defaultConfigPath,
5
+ loadEntryPoint,
6
+ loadMDX
7
+ } from "./chunk-UDFHC2Y3.js";
8
+ export {
9
+ createCompiler,
10
+ defaultConfig,
11
+ defaultConfigPath,
12
+ loadEntryPoint,
13
+ loadMDX
14
+ };
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "fuma-content",
3
+ "version": "0.0.0",
4
+ "description": "Write content for web apps",
5
+ "keywords": [
6
+ "Content",
7
+ "Docs"
8
+ ],
9
+ "repository": "github:fuma-nama/fuma-content",
10
+ "license": "MIT",
11
+ "type": "module",
12
+ "bin": "./dist/cli/index.js",
13
+ "author": "Fuma Nama",
14
+ "files": [
15
+ "dist/*"
16
+ ],
17
+ "main": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "import": "./dist/index.js",
22
+ "types": "./dist/index.d.ts"
23
+ },
24
+ "./internal": {
25
+ "import": "./dist/internal.js",
26
+ "types": "./dist/internal.d.ts"
27
+ }
28
+ },
29
+ "typesVersions": {
30
+ "*": {
31
+ "internal": [
32
+ "./dist/internal.d.ts"
33
+ ]
34
+ }
35
+ },
36
+ "dependencies": {
37
+ "@mdx-js/mdx": "^3.0.0",
38
+ "chokidar": "^3.5.3",
39
+ "clipanion": "4.0.0-rc.3",
40
+ "cross-spawn": "^7.0.3",
41
+ "estree-util-value-to-estree": "^3.0.1",
42
+ "fast-glob": "^3.3.2",
43
+ "gray-matter": "^4.0.3",
44
+ "micromatch": "^4.0.5",
45
+ "zod": "^3.22.4"
46
+ },
47
+ "devDependencies": {
48
+ "@types/cross-spawn": "^6.0.6",
49
+ "@types/mdast": "^4.0.3",
50
+ "@types/mdx": "^2.0.11",
51
+ "@types/micromatch": "^4.0.6",
52
+ "typescript": "^5.3.3",
53
+ "unified": "^11.0.4",
54
+ "eslint-config": "0.0.0",
55
+ "typescript-config": "0.0.0"
56
+ },
57
+ "publishConfig": {
58
+ "access": "public"
59
+ },
60
+ "scripts": {
61
+ "build": "tsup",
62
+ "dev": "tsup --watch",
63
+ "lint": "eslint ."
64
+ }
65
+ }