@contedra/md-importer 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 +106 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +38 -0
- package/dist/cli.js.map +1 -0
- package/dist/images.d.ts +32 -0
- package/dist/images.d.ts.map +1 -0
- package/dist/images.js +58 -0
- package/dist/images.js.map +1 -0
- package/dist/importer.d.ts +11 -0
- package/dist/importer.d.ts.map +1 -0
- package/dist/importer.js +88 -0
- package/dist/importer.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/mapper.d.ts +12 -0
- package/dist/mapper.d.ts.map +1 -0
- package/dist/mapper.js +58 -0
- package/dist/mapper.js.map +1 -0
- package/dist/parser.d.ts +7 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +11 -0
- package/dist/parser.js.map +1 -0
- package/dist/types.d.ts +27 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 contedra
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# @contedra/md-importer
|
|
2
|
+
|
|
3
|
+
CLI tool and library to import YAML-frontmatter Markdown files (with images) into Firestore, using Conteditor model definition JSON for schema validation.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @contedra/md-importer
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## CLI Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @contedra/md-importer \
|
|
15
|
+
--md-dir ./content \
|
|
16
|
+
--model ./models/blog_posts.json \
|
|
17
|
+
--project-id your-project-id \
|
|
18
|
+
--credential ./service-account.json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### CLI Options
|
|
22
|
+
|
|
23
|
+
| Option | Required | Description |
|
|
24
|
+
|--------|----------|-------------|
|
|
25
|
+
| `--md-dir <path>` | Yes | Directory containing `.md` files |
|
|
26
|
+
| `--model <path>` | Yes | Path to model definition JSON |
|
|
27
|
+
| `--project-id <id>` | Yes | Firebase project ID |
|
|
28
|
+
| `--credential <path>` | No | Path to service account JSON (uses ADC if omitted) |
|
|
29
|
+
| `--collection <name>` | No | Firestore collection name (defaults to `modelName`) |
|
|
30
|
+
| `--field-mapping <json>` | No | JSON mapping frontmatter keys to model properties |
|
|
31
|
+
|
|
32
|
+
### Field Mapping
|
|
33
|
+
|
|
34
|
+
When your Markdown frontmatter keys don't match the model property names, use `--field-mapping`:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx @contedra/md-importer \
|
|
38
|
+
--md-dir ./content \
|
|
39
|
+
--model ./models/blog_posts.json \
|
|
40
|
+
--project-id your-project-id \
|
|
41
|
+
--field-mapping '{"article_title":"title","article_date":"publishedAt"}'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Unmapped frontmatter keys that match model property names are auto-matched.
|
|
45
|
+
|
|
46
|
+
## Programmatic API
|
|
47
|
+
|
|
48
|
+
### `mdImporter(config): Promise<ImportResult>`
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { mdImporter } from "@contedra/md-importer";
|
|
52
|
+
|
|
53
|
+
const result = await mdImporter({
|
|
54
|
+
mdDir: "./content",
|
|
55
|
+
modelFile: "./models/blog_posts.json",
|
|
56
|
+
firebaseConfig: {
|
|
57
|
+
projectId: "your-project-id",
|
|
58
|
+
credential: "./service-account.json",
|
|
59
|
+
},
|
|
60
|
+
fieldMapping: {
|
|
61
|
+
article_title: "title",
|
|
62
|
+
article_date: "publishedAt",
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
console.log(`Imported: ${result.imported.length}`);
|
|
67
|
+
console.log(`Errors: ${result.errors.length}`);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Custom Image Resolver
|
|
71
|
+
|
|
72
|
+
By default, local images referenced in Markdown are read from the filesystem and uploaded to Firebase Storage (`assets/{modelName}/{contentId}/{fileName}`). Provide a custom resolver to change this behavior:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
const result = await mdImporter({
|
|
76
|
+
mdDir: "./content",
|
|
77
|
+
modelFile: "./models/blog_posts.json",
|
|
78
|
+
firebaseConfig: { projectId: "your-project-id" },
|
|
79
|
+
resolveImage: async (imagePath, mdFilePath) => {
|
|
80
|
+
const response = await fetch(`https://cdn.example.com/${imagePath}`);
|
|
81
|
+
return Buffer.from(await response.arrayBuffer());
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Helper Functions
|
|
87
|
+
|
|
88
|
+
- `generateDocId(filePath)` — Converts a filename to a slugified Firestore document ID
|
|
89
|
+
- `parseMarkdownFile(filePath)` — Parses a `.md` file into `{ frontmatter, body }`
|
|
90
|
+
- `parseMarkdownString(content)` — Parses a markdown string (uses gray-matter)
|
|
91
|
+
- `mapFields(frontmatter, model, fieldMapping?)` — Maps frontmatter fields to model properties, returns `{ data, unmapped }`
|
|
92
|
+
|
|
93
|
+
## Image Handling
|
|
94
|
+
|
|
95
|
+
Images referenced in Markdown (e.g., ``) are:
|
|
96
|
+
|
|
97
|
+
1. Extracted from the markdown body
|
|
98
|
+
2. Resolved via the local filesystem (or custom `resolveImage`)
|
|
99
|
+
3. Uploaded to Firebase Storage at `assets/{modelName}/{contentId}/{fileName}`
|
|
100
|
+
4. Replaced in the markdown body with `asset://` URIs
|
|
101
|
+
|
|
102
|
+
External URLs (`http://`, `https://`) and `asset://` URIs are skipped.
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { mdImporter } from "./importer.js";
|
|
4
|
+
const program = new Command();
|
|
5
|
+
program
|
|
6
|
+
.name("md-importer")
|
|
7
|
+
.description("Import YAML-frontmatter Markdown files + images into Firestore")
|
|
8
|
+
.requiredOption("--md-dir <path>", "Directory containing .md files")
|
|
9
|
+
.requiredOption("--model <path>", "Path to model definition JSON")
|
|
10
|
+
.requiredOption("--project-id <id>", "Firebase project ID")
|
|
11
|
+
.option("--credential <path>", "Path to service account JSON")
|
|
12
|
+
.option("--collection <name>", "Firestore collection name (defaults to modelName)")
|
|
13
|
+
.option("--field-mapping <json>", "JSON object mapping frontmatter keys to model property names")
|
|
14
|
+
.action(async (opts) => {
|
|
15
|
+
const fieldMapping = opts.fieldMapping
|
|
16
|
+
? JSON.parse(opts.fieldMapping)
|
|
17
|
+
: undefined;
|
|
18
|
+
const result = await mdImporter({
|
|
19
|
+
mdDir: opts.mdDir,
|
|
20
|
+
modelFile: opts.model,
|
|
21
|
+
firebaseConfig: {
|
|
22
|
+
projectId: opts.projectId,
|
|
23
|
+
credential: opts.credential,
|
|
24
|
+
},
|
|
25
|
+
collection: opts.collection,
|
|
26
|
+
fieldMapping,
|
|
27
|
+
});
|
|
28
|
+
console.log(`Imported ${result.imported.length} file(s) successfully.`);
|
|
29
|
+
if (result.errors.length > 0) {
|
|
30
|
+
console.error(`Failed to import ${result.errors.length} file(s):`);
|
|
31
|
+
for (const err of result.errors) {
|
|
32
|
+
console.error(` ${err.filePath}: ${err.error}`);
|
|
33
|
+
}
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
program.parse();
|
|
38
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,aAAa,CAAC;KACnB,WAAW,CACV,gEAAgE,CACjE;KACA,cAAc,CAAC,iBAAiB,EAAE,gCAAgC,CAAC;KACnE,cAAc,CAAC,gBAAgB,EAAE,+BAA+B,CAAC;KACjE,cAAc,CAAC,mBAAmB,EAAE,qBAAqB,CAAC;KAC1D,MAAM,CAAC,qBAAqB,EAAE,8BAA8B,CAAC;KAC7D,MAAM,CAAC,qBAAqB,EAAE,mDAAmD,CAAC;KAClF,MAAM,CACL,wBAAwB,EACxB,8DAA8D,CAC/D;KACA,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;IACrB,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY;QACpC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAA4B;QAC3D,CAAC,CAAC,SAAS,CAAC;IAEd,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC;QAC9B,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,SAAS,EAAE,IAAI,CAAC,KAAK;QACrB,cAAc,EAAE;YACd,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,UAAU,EAAE,IAAI,CAAC,UAAU;SAC5B;QACD,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,YAAY;KACb,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,CACT,YAAY,MAAM,CAAC,QAAQ,CAAC,MAAM,wBAAwB,CAC3D,CAAC;IAEF,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,KAAK,CAAC,oBAAoB,MAAM,CAAC,MAAM,CAAC,MAAM,WAAW,CAAC,CAAC;QACnE,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAChC,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,QAAQ,KAAK,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;QACnD,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
package/dist/images.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface ImageRef {
|
|
2
|
+
/** Full match string in the Markdown */
|
|
3
|
+
fullMatch: string;
|
|
4
|
+
/** Alt text */
|
|
5
|
+
alt: string;
|
|
6
|
+
/** Original image path from Markdown */
|
|
7
|
+
originalPath: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Extract all image references from Markdown content.
|
|
11
|
+
* Skips URLs (http:// or https://) and asset:// references.
|
|
12
|
+
*/
|
|
13
|
+
export declare function extractImageRefs(body: string): ImageRef[];
|
|
14
|
+
/**
|
|
15
|
+
* Default image resolver: reads file relative to the Markdown file's directory.
|
|
16
|
+
*/
|
|
17
|
+
export declare function defaultResolveImage(imagePath: string, mdFilePath: string): Promise<Buffer>;
|
|
18
|
+
/**
|
|
19
|
+
* Generate a Storage path for an asset using v2 format.
|
|
20
|
+
* Format: assets/{modelName}/{contentId}/{fileId}
|
|
21
|
+
*/
|
|
22
|
+
export declare function assetStoragePath(modelName: string, contentId: string, fileName: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Generate an asset:// URI using v2 format.
|
|
25
|
+
* Format: asset://{modelName}/{contentId}/{fileId}
|
|
26
|
+
*/
|
|
27
|
+
export declare function assetUri(modelName: string, contentId: string, fileName: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Replace image references in Markdown body with asset:// URIs.
|
|
30
|
+
*/
|
|
31
|
+
export declare function replaceImageRefs(body: string, replacements: Map<string, string>): string;
|
|
32
|
+
//# sourceMappingURL=images.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../src/images.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,QAAQ;IACvB,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,wCAAwC;IACxC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,CAkBzD;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAIjB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,MAAM,CAER;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,MAAM,CAER;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,MAAM,CAMR"}
|
package/dist/images.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/** Regex matching Markdown image syntax:  */
|
|
4
|
+
const IMAGE_REGEX = /!\[([^\]]*)\]\(([^)]+)\)/g;
|
|
5
|
+
/**
|
|
6
|
+
* Extract all image references from Markdown content.
|
|
7
|
+
* Skips URLs (http:// or https://) and asset:// references.
|
|
8
|
+
*/
|
|
9
|
+
export function extractImageRefs(body) {
|
|
10
|
+
const refs = [];
|
|
11
|
+
for (const match of body.matchAll(IMAGE_REGEX)) {
|
|
12
|
+
const imgPath = match[2];
|
|
13
|
+
if (imgPath.startsWith("http://") ||
|
|
14
|
+
imgPath.startsWith("https://") ||
|
|
15
|
+
imgPath.startsWith("asset://")) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
refs.push({
|
|
19
|
+
fullMatch: match[0],
|
|
20
|
+
alt: match[1],
|
|
21
|
+
originalPath: imgPath,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return refs;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Default image resolver: reads file relative to the Markdown file's directory.
|
|
28
|
+
*/
|
|
29
|
+
export async function defaultResolveImage(imagePath, mdFilePath) {
|
|
30
|
+
const dir = path.dirname(mdFilePath);
|
|
31
|
+
const resolved = path.resolve(dir, imagePath);
|
|
32
|
+
return readFile(resolved);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Generate a Storage path for an asset using v2 format.
|
|
36
|
+
* Format: assets/{modelName}/{contentId}/{fileId}
|
|
37
|
+
*/
|
|
38
|
+
export function assetStoragePath(modelName, contentId, fileName) {
|
|
39
|
+
return `assets/${modelName}/${contentId}/${fileName}`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Generate an asset:// URI using v2 format.
|
|
43
|
+
* Format: asset://{modelName}/{contentId}/{fileId}
|
|
44
|
+
*/
|
|
45
|
+
export function assetUri(modelName, contentId, fileName) {
|
|
46
|
+
return `asset://${modelName}/${contentId}/${fileName}`;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Replace image references in Markdown body with asset:// URIs.
|
|
50
|
+
*/
|
|
51
|
+
export function replaceImageRefs(body, replacements) {
|
|
52
|
+
let result = body;
|
|
53
|
+
for (const [fullMatch, assetUriStr] of replacements) {
|
|
54
|
+
result = result.replace(fullMatch, assetUriStr);
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=images.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"images.js","sourceRoot":"","sources":["../src/images.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,yDAAyD;AACzD,MAAM,WAAW,GAAG,2BAA2B,CAAC;AAWhD;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,MAAM,IAAI,GAAe,EAAE,CAAC;IAC5B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QAC/C,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACzB,IACE,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC;YAC7B,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC;YAC9B,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,EAC9B,CAAC;YACD,SAAS;QACX,CAAC;QACD,IAAI,CAAC,IAAI,CAAC;YACR,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;YACnB,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;YACb,YAAY,EAAE,OAAO;SACtB,CAAC,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,SAAiB,EACjB,UAAkB;IAElB,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IAC9C,OAAO,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC5B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,SAAiB,EACjB,SAAiB,EACjB,QAAgB;IAEhB,OAAO,UAAU,SAAS,IAAI,SAAS,IAAI,QAAQ,EAAE,CAAC;AACxD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CACtB,SAAiB,EACjB,SAAiB,EACjB,QAAgB;IAEhB,OAAO,WAAW,SAAS,IAAI,SAAS,IAAI,QAAQ,EAAE,CAAC;AACzD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAY,EACZ,YAAiC;IAEjC,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,KAAK,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,IAAI,YAAY,EAAE,CAAC;QACpD,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MdImporterConfig, ImportResult } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Generate a document ID from a filename.
|
|
4
|
+
* Uses the filename stem, slugified for safe Firestore doc IDs.
|
|
5
|
+
*/
|
|
6
|
+
export declare function generateDocId(filePath: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Import Markdown files into Firestore using a model definition.
|
|
9
|
+
*/
|
|
10
|
+
export declare function mdImporter(config: MdImporterConfig): Promise<ImportResult>;
|
|
11
|
+
//# sourceMappingURL=importer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"importer.d.ts","sourceRoot":"","sources":["../src/importer.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAWjE;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGtD;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,YAAY,CAAC,CAkFvB"}
|
package/dist/importer.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { loadModel, detectBodyField, initFirestore } from "@contedra/core";
|
|
4
|
+
import { getStorage } from "firebase-admin/storage";
|
|
5
|
+
import { getApps } from "firebase-admin/app";
|
|
6
|
+
import slugify from "slugify";
|
|
7
|
+
import { parseMarkdownFile } from "./parser.js";
|
|
8
|
+
import { mapFields } from "./mapper.js";
|
|
9
|
+
import { extractImageRefs, defaultResolveImage, assetStoragePath, assetUri, replaceImageRefs, } from "./images.js";
|
|
10
|
+
/**
|
|
11
|
+
* Generate a document ID from a filename.
|
|
12
|
+
* Uses the filename stem, slugified for safe Firestore doc IDs.
|
|
13
|
+
*/
|
|
14
|
+
export function generateDocId(filePath) {
|
|
15
|
+
const stem = path.basename(filePath, path.extname(filePath));
|
|
16
|
+
return slugify(stem, { lower: true, strict: true });
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Import Markdown files into Firestore using a model definition.
|
|
20
|
+
*/
|
|
21
|
+
export async function mdImporter(config) {
|
|
22
|
+
const model = await loadModel(config.modelFile);
|
|
23
|
+
const bodyField = detectBodyField(model);
|
|
24
|
+
const collectionName = config.collection ?? model.modelName;
|
|
25
|
+
const resolveImage = config.resolveImage ?? defaultResolveImage;
|
|
26
|
+
const firestore = initFirestore(config.firebaseConfig);
|
|
27
|
+
const appName = `contedra-${config.firebaseConfig.projectId}`;
|
|
28
|
+
const app = getApps().find((a) => a.name === appName);
|
|
29
|
+
const bucket = getStorage(app).bucket();
|
|
30
|
+
const mdFiles = await findMarkdownFiles(config.mdDir);
|
|
31
|
+
const result = { imported: [], errors: [] };
|
|
32
|
+
for (const mdFile of mdFiles) {
|
|
33
|
+
try {
|
|
34
|
+
const absolutePath = path.resolve(config.mdDir, mdFile);
|
|
35
|
+
const docId = generateDocId(mdFile);
|
|
36
|
+
const { frontmatter, body } = await parseMarkdownFile(absolutePath);
|
|
37
|
+
const { data } = mapFields(frontmatter, model, config.fieldMapping);
|
|
38
|
+
let processedBody = body;
|
|
39
|
+
// Process images
|
|
40
|
+
if (bodyField && body) {
|
|
41
|
+
const imageRefs = extractImageRefs(body);
|
|
42
|
+
const replacements = new Map();
|
|
43
|
+
for (const ref of imageRefs) {
|
|
44
|
+
const fileName = path.basename(ref.originalPath);
|
|
45
|
+
const storagePath = assetStoragePath(model.modelName, docId, fileName);
|
|
46
|
+
const imageBuffer = await resolveImage(ref.originalPath, absolutePath);
|
|
47
|
+
const file = bucket.file(storagePath);
|
|
48
|
+
await file.save(imageBuffer);
|
|
49
|
+
const uri = assetUri(model.modelName, docId, fileName);
|
|
50
|
+
replacements.set(ref.fullMatch, ``);
|
|
51
|
+
}
|
|
52
|
+
processedBody = replaceImageRefs(body, replacements);
|
|
53
|
+
}
|
|
54
|
+
// Build the Firestore document
|
|
55
|
+
const docData = { ...data };
|
|
56
|
+
if (bodyField) {
|
|
57
|
+
docData[bodyField] = processedBody;
|
|
58
|
+
}
|
|
59
|
+
// Convert Date objects to Firestore Timestamps
|
|
60
|
+
const { Timestamp } = await import("firebase-admin/firestore");
|
|
61
|
+
for (const [key, value] of Object.entries(docData)) {
|
|
62
|
+
if (value instanceof Date) {
|
|
63
|
+
docData[key] = Timestamp.fromDate(value);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
await firestore.collection(collectionName).doc(docId).set(docData);
|
|
67
|
+
result.imported.push({ docId, filePath: mdFile });
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
result.errors.push({
|
|
71
|
+
filePath: mdFile,
|
|
72
|
+
error: err instanceof Error ? err.message : String(err),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
async function findMarkdownFiles(dir) {
|
|
79
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
80
|
+
const mdFiles = [];
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
83
|
+
mdFiles.push(entry.name);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return mdFiles.sort();
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=importer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"importer.js","sourceRoot":"","sources":["../src/importer.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { mdImporter } from "./importer.js";
|
|
2
|
+
export { generateDocId } from "./importer.js";
|
|
3
|
+
export { parseMarkdownFile, parseMarkdownString } from "./parser.js";
|
|
4
|
+
export { mapFields } from "./mapper.js";
|
|
5
|
+
export { extractImageRefs, defaultResolveImage, assetStoragePath, assetUri, replaceImageRefs, } from "./images.js";
|
|
6
|
+
export type { MdImporterConfig, ImportResult, ImportedDocument, } from "./types.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EAChB,QAAQ,EACR,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,GACjB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { mdImporter } from "./importer.js";
|
|
2
|
+
export { generateDocId } from "./importer.js";
|
|
3
|
+
export { parseMarkdownFile, parseMarkdownString } from "./parser.js";
|
|
4
|
+
export { mapFields } from "./mapper.js";
|
|
5
|
+
export { extractImageRefs, defaultResolveImage, assetStoragePath, assetUri, replaceImageRefs, } from "./images.js";
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EAChB,QAAQ,EACR,gBAAgB,GACjB,MAAM,aAAa,CAAC"}
|
package/dist/mapper.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ModelDefinition } from "@contedra/core";
|
|
2
|
+
export interface MappedFields {
|
|
3
|
+
data: Record<string, unknown>;
|
|
4
|
+
unmapped: string[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Map frontmatter fields to model properties.
|
|
8
|
+
* 1. Apply explicit fieldMapping (frontmatter key -> model property name)
|
|
9
|
+
* 2. Auto-match remaining frontmatter keys by name equality with model properties
|
|
10
|
+
*/
|
|
11
|
+
export declare function mapFields(frontmatter: Record<string, unknown>, model: ModelDefinition, fieldMapping?: Record<string, string>): MappedFields;
|
|
12
|
+
//# sourceMappingURL=mapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mapper.d.ts","sourceRoot":"","sources":["../src/mapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAiB,MAAM,gBAAgB,CAAC;AAErE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,KAAK,EAAE,eAAe,EACtB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACpC,YAAY,CAyCd"}
|
package/dist/mapper.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map frontmatter fields to model properties.
|
|
3
|
+
* 1. Apply explicit fieldMapping (frontmatter key -> model property name)
|
|
4
|
+
* 2. Auto-match remaining frontmatter keys by name equality with model properties
|
|
5
|
+
*/
|
|
6
|
+
export function mapFields(frontmatter, model, fieldMapping) {
|
|
7
|
+
const data = {};
|
|
8
|
+
const unmapped = [];
|
|
9
|
+
const propertyNames = new Set(model.properties.map((p) => p.propertyName));
|
|
10
|
+
const resolvedMapping = new Map();
|
|
11
|
+
// Build explicit mapping
|
|
12
|
+
if (fieldMapping) {
|
|
13
|
+
for (const [fmKey, propName] of Object.entries(fieldMapping)) {
|
|
14
|
+
resolvedMapping.set(fmKey, propName);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// Auto-match remaining keys
|
|
18
|
+
for (const fmKey of Object.keys(frontmatter)) {
|
|
19
|
+
if (!resolvedMapping.has(fmKey) && propertyNames.has(fmKey)) {
|
|
20
|
+
resolvedMapping.set(fmKey, fmKey);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
for (const [fmKey, propName] of resolvedMapping) {
|
|
24
|
+
if (fmKey in frontmatter && propertyNames.has(propName)) {
|
|
25
|
+
const prop = model.properties.find((p) => p.propertyName === propName);
|
|
26
|
+
data[propName] = coerceValue(frontmatter[fmKey], prop);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Identify unmapped frontmatter keys
|
|
30
|
+
for (const fmKey of Object.keys(frontmatter)) {
|
|
31
|
+
if (!resolvedMapping.has(fmKey)) {
|
|
32
|
+
unmapped.push(fmKey);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { data, unmapped };
|
|
36
|
+
}
|
|
37
|
+
function coerceValue(value, prop) {
|
|
38
|
+
if (value == null)
|
|
39
|
+
return undefined;
|
|
40
|
+
switch (prop.dataType) {
|
|
41
|
+
case "datetime":
|
|
42
|
+
if (value instanceof Date)
|
|
43
|
+
return value;
|
|
44
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
45
|
+
return new Date(value);
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
case "relatedMany":
|
|
49
|
+
if (Array.isArray(value))
|
|
50
|
+
return value.map(String);
|
|
51
|
+
return [String(value)];
|
|
52
|
+
case "relatedOne":
|
|
53
|
+
return String(value);
|
|
54
|
+
default:
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=mapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mapper.js","sourceRoot":"","sources":["../src/mapper.ts"],"names":[],"mappings":"AAOA;;;;GAIG;AACH,MAAM,UAAU,SAAS,CACvB,WAAoC,EACpC,KAAsB,EACtB,YAAqC;IAErC,MAAM,IAAI,GAA4B,EAAE,CAAC;IACzC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,MAAM,aAAa,GAAG,IAAI,GAAG,CAC3B,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAC5C,CAAC;IAEF,MAAM,eAAe,GAAG,IAAI,GAAG,EAAkB,CAAC;IAElD,yBAAyB;IACzB,IAAI,YAAY,EAAE,CAAC;QACjB,KAAK,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;YAC7D,eAAe,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IAED,4BAA4B;IAC5B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7C,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5D,eAAe,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,KAAK,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,eAAe,EAAE,CAAC;QAChD,IAAI,KAAK,IAAI,WAAW,IAAI,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YACxD,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,IAAI,CAChC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,QAAQ,CAClB,CAAC;YACnB,IAAI,CAAC,QAAQ,CAAC,GAAG,WAAW,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;IAED,qCAAqC;IACrC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7C,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAChC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC5B,CAAC;AAED,SAAS,WAAW,CAAC,KAAc,EAAE,IAAmB;IACtD,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,SAAS,CAAC;IAEpC,QAAQ,IAAI,CAAC,QAAQ,EAAE,CAAC;QACtB,KAAK,UAAU;YACb,IAAI,KAAK,YAAY,IAAI;gBAAE,OAAO,KAAK,CAAC;YACxC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC3D,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;YACzB,CAAC;YACD,OAAO,KAAK,CAAC;QACf,KAAK,aAAa;YAChB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACzB,KAAK,YAAY;YACf,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;QACvB;YACE,OAAO,KAAK,CAAC;IACjB,CAAC;AACH,CAAC"}
|
package/dist/parser.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface ParsedMarkdown {
|
|
2
|
+
frontmatter: Record<string, unknown>;
|
|
3
|
+
body: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function parseMarkdownFile(filePath: string): Promise<ParsedMarkdown>;
|
|
6
|
+
export declare function parseMarkdownString(content: string): ParsedMarkdown;
|
|
7
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,cAAc,CAAC,CAGzB;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAGnE"}
|
package/dist/parser.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import matter from "gray-matter";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
export async function parseMarkdownFile(filePath) {
|
|
4
|
+
const raw = await readFile(filePath, "utf-8");
|
|
5
|
+
return parseMarkdownString(raw);
|
|
6
|
+
}
|
|
7
|
+
export function parseMarkdownString(content) {
|
|
8
|
+
const { data, content: body } = matter(content);
|
|
9
|
+
return { frontmatter: data, body: body.trim() };
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAO5C,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAAgB;IAEhB,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC9C,OAAO,mBAAmB,CAAC,GAAG,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAAe;IACjD,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAChD,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;AAClD,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { FirebaseConfig } from "@contedra/core";
|
|
2
|
+
export interface MdImporterConfig {
|
|
3
|
+
/** Directory containing .md files to import */
|
|
4
|
+
mdDir: string;
|
|
5
|
+
/** Path to model definition JSON file */
|
|
6
|
+
modelFile: string;
|
|
7
|
+
/** Firebase configuration */
|
|
8
|
+
firebaseConfig: FirebaseConfig;
|
|
9
|
+
/** Firestore collection name (defaults to model's modelName) */
|
|
10
|
+
collection?: string;
|
|
11
|
+
/** Mapping from frontmatter keys to model property names */
|
|
12
|
+
fieldMapping?: Record<string, string>;
|
|
13
|
+
/** Custom image resolver. Default: reads relative to the .md file */
|
|
14
|
+
resolveImage?: (imagePath: string, mdFilePath: string) => Promise<Buffer>;
|
|
15
|
+
}
|
|
16
|
+
export interface ImportedDocument {
|
|
17
|
+
docId: string;
|
|
18
|
+
filePath: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ImportResult {
|
|
21
|
+
imported: ImportedDocument[];
|
|
22
|
+
errors: Array<{
|
|
23
|
+
filePath: string;
|
|
24
|
+
error: string;
|
|
25
|
+
}>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,MAAM,WAAW,gBAAgB;IAC/B,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,6BAA6B;IAC7B,cAAc,EAAE,cAAc,CAAC;IAC/B,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,qEAAqE;IACrE,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAC3E;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpD"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@contedra/md-importer",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI tool to import YAML-frontmatter Markdown files + images into Firestore using model definition JSON",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"md-importer": "./dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"conteditor",
|
|
22
|
+
"firestore",
|
|
23
|
+
"firebase",
|
|
24
|
+
"markdown",
|
|
25
|
+
"importer",
|
|
26
|
+
"cli"
|
|
27
|
+
],
|
|
28
|
+
"author": "Contedra",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/contedra/toolkit",
|
|
33
|
+
"directory": "packages/md-importer"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"commander": "^13.0.0",
|
|
37
|
+
"firebase-admin": "^13.0.0",
|
|
38
|
+
"gray-matter": "^4.0.3",
|
|
39
|
+
"slugify": "^1.6.6",
|
|
40
|
+
"@contedra/core": "0.1.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^22.0.0",
|
|
44
|
+
"oxlint": "^1.57.0",
|
|
45
|
+
"typescript": "^5.7.0",
|
|
46
|
+
"vitest": "^3.0.0"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsc",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
52
|
+
"lint": "oxlint src"
|
|
53
|
+
}
|
|
54
|
+
}
|