emdash-emit 0.0.0-stage → 0.1.2
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 +798 -2
- package/dist/bin/emit.d.ts +5 -0
- package/dist/bin/emit.d.ts.map +1 -0
- package/dist/bin/emit.js +63 -0
- package/dist/bin/emit.js.map +1 -0
- package/dist/cli/commands/new.d.ts +4 -0
- package/dist/cli/commands/new.d.ts.map +1 -0
- package/dist/cli/commands/new.js +49 -0
- package/dist/cli/commands/new.js.map +1 -0
- package/dist/cli/commands/profile.d.ts +3 -0
- package/dist/cli/commands/profile.d.ts.map +1 -0
- package/dist/cli/commands/profile.js +104 -0
- package/dist/cli/commands/profile.js.map +1 -0
- package/dist/cli/commands/pull.d.ts +4 -0
- package/dist/cli/commands/pull.d.ts.map +1 -0
- package/dist/cli/commands/pull.js +157 -0
- package/dist/cli/commands/pull.js.map +1 -0
- package/dist/cli/commands/push.d.ts +4 -0
- package/dist/cli/commands/push.d.ts.map +1 -0
- package/dist/cli/commands/push.js +107 -0
- package/dist/cli/commands/push.js.map +1 -0
- package/dist/cli/commands/status.d.ts +4 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +100 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/context.d.ts +15 -0
- package/dist/cli/context.d.ts.map +1 -0
- package/dist/cli/context.js +75 -0
- package/dist/cli/context.js.map +1 -0
- package/dist/client/api.d.ts +19 -0
- package/dist/client/api.d.ts.map +1 -0
- package/dist/client/api.js +96 -0
- package/dist/client/api.js.map +1 -0
- package/dist/client/auth.d.ts +48 -0
- package/dist/client/auth.d.ts.map +1 -0
- package/dist/client/auth.js +165 -0
- package/dist/client/auth.js.map +1 -0
- package/dist/client/error.d.ts +22 -0
- package/dist/client/error.d.ts.map +1 -0
- package/dist/client/error.js +57 -0
- package/dist/client/error.js.map +1 -0
- package/dist/client/http.d.ts +31 -0
- package/dist/client/http.d.ts.map +1 -0
- package/dist/client/http.js +97 -0
- package/dist/client/http.js.map +1 -0
- package/dist/client/lock.d.ts +40 -0
- package/dist/client/lock.d.ts.map +1 -0
- package/dist/client/lock.js +104 -0
- package/dist/client/lock.js.map +1 -0
- package/dist/client/mapper.d.ts +37 -0
- package/dist/client/mapper.d.ts.map +1 -0
- package/dist/client/mapper.js +142 -0
- package/dist/client/mapper.js.map +1 -0
- package/dist/client/portable-text.d.ts +94 -0
- package/dist/client/portable-text.d.ts.map +1 -0
- package/dist/client/portable-text.js +428 -0
- package/dist/client/portable-text.js.map +1 -0
- package/dist/client/schemas.d.ts +1928 -0
- package/dist/client/schemas.d.ts.map +1 -0
- package/dist/client/schemas.js +123 -0
- package/dist/client/schemas.js.map +1 -0
- package/dist/db/cache.d.ts +60 -0
- package/dist/db/cache.d.ts.map +1 -0
- package/dist/db/cache.js +303 -0
- package/dist/db/cache.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +45 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/context.d.ts +14 -0
- package/dist/mcp/context.d.ts.map +1 -0
- package/dist/mcp/context.js +23 -0
- package/dist/mcp/context.js.map +1 -0
- package/dist/mcp/server.d.ts +12 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +106 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/profile.d.ts +6 -0
- package/dist/mcp/tools/profile.d.ts.map +1 -0
- package/dist/mcp/tools/profile.js +81 -0
- package/dist/mcp/tools/profile.js.map +1 -0
- package/dist/mcp/tools/sync-diff.d.ts +4 -0
- package/dist/mcp/tools/sync-diff.d.ts.map +1 -0
- package/dist/mcp/tools/sync-diff.js +115 -0
- package/dist/mcp/tools/sync-diff.js.map +1 -0
- package/dist/mcp/tools/sync-new.d.ts +4 -0
- package/dist/mcp/tools/sync-new.d.ts.map +1 -0
- package/dist/mcp/tools/sync-new.js +54 -0
- package/dist/mcp/tools/sync-new.js.map +1 -0
- package/dist/mcp/tools/sync-pull.d.ts +4 -0
- package/dist/mcp/tools/sync-pull.d.ts.map +1 -0
- package/dist/mcp/tools/sync-pull.js +180 -0
- package/dist/mcp/tools/sync-pull.js.map +1 -0
- package/dist/mcp/tools/sync-push.d.ts +4 -0
- package/dist/mcp/tools/sync-push.d.ts.map +1 -0
- package/dist/mcp/tools/sync-push.js +105 -0
- package/dist/mcp/tools/sync-push.js.map +1 -0
- package/dist/mcp/tools/sync-status.d.ts +4 -0
- package/dist/mcp/tools/sync-status.d.ts.map +1 -0
- package/dist/mcp/tools/sync-status.js +45 -0
- package/dist/mcp/tools/sync-status.js.map +1 -0
- package/dist/sync/bylines.d.ts +23 -0
- package/dist/sync/bylines.d.ts.map +1 -0
- package/dist/sync/bylines.js +112 -0
- package/dist/sync/bylines.js.map +1 -0
- package/dist/sync/diff.d.ts +16 -0
- package/dist/sync/diff.d.ts.map +1 -0
- package/dist/sync/diff.js +68 -0
- package/dist/sync/diff.js.map +1 -0
- package/dist/sync/media.d.ts +37 -0
- package/dist/sync/media.d.ts.map +1 -0
- package/dist/sync/media.js +191 -0
- package/dist/sync/media.js.map +1 -0
- package/dist/sync/merge.d.ts +36 -0
- package/dist/sync/merge.d.ts.map +1 -0
- package/dist/sync/merge.js +112 -0
- package/dist/sync/merge.js.map +1 -0
- package/dist/sync/new.d.ts +24 -0
- package/dist/sync/new.d.ts.map +1 -0
- package/dist/sync/new.js +75 -0
- package/dist/sync/new.js.map +1 -0
- package/dist/sync/orchestrator.d.ts +55 -0
- package/dist/sync/orchestrator.d.ts.map +1 -0
- package/dist/sync/orchestrator.js +222 -0
- package/dist/sync/orchestrator.js.map +1 -0
- package/dist/sync/preflight.d.ts +25 -0
- package/dist/sync/preflight.d.ts.map +1 -0
- package/dist/sync/preflight.js +128 -0
- package/dist/sync/preflight.js.map +1 -0
- package/dist/sync/pull.d.ts +67 -0
- package/dist/sync/pull.d.ts.map +1 -0
- package/dist/sync/pull.js +405 -0
- package/dist/sync/pull.js.map +1 -0
- package/dist/sync/push.d.ts +33 -0
- package/dist/sync/push.d.ts.map +1 -0
- package/dist/sync/push.js +432 -0
- package/dist/sync/push.js.map +1 -0
- package/dist/sync/status.d.ts +24 -0
- package/dist/sync/status.d.ts.map +1 -0
- package/dist/sync/status.js +85 -0
- package/dist/sync/status.js.map +1 -0
- package/dist/sync/taxonomies.d.ts +49 -0
- package/dist/sync/taxonomies.d.ts.map +1 -0
- package/dist/sync/taxonomies.js +220 -0
- package/dist/sync/taxonomies.js.map +1 -0
- package/dist/utils/config.d.ts +18 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +79 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/frontmatter.d.ts +115 -0
- package/dist/utils/frontmatter.d.ts.map +1 -0
- package/dist/utils/frontmatter.js +147 -0
- package/dist/utils/frontmatter.js.map +1 -0
- package/dist/utils/naming.d.ts +28 -0
- package/dist/utils/naming.d.ts.map +1 -0
- package/dist/utils/naming.js +94 -0
- package/dist/utils/naming.js.map +1 -0
- package/dist/utils/profile.d.ts +72 -0
- package/dist/utils/profile.d.ts.map +1 -0
- package/dist/utils/profile.js +180 -0
- package/dist/utils/profile.js.map +1 -0
- package/dist/utils/version.d.ts +5 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +43 -0
- package/dist/utils/version.js.map +1 -0
- package/dist/utils/workspace.d.ts +22 -0
- package/dist/utils/workspace.d.ts.map +1 -0
- package/dist/utils/workspace.js +74 -0
- package/dist/utils/workspace.js.map +1 -0
- package/package.json +68 -4
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2026 SPORT-SERVICE RS★R
|
|
3
|
+
*
|
|
4
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
* in the Software without restriction, including without limitation the rights
|
|
7
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
* furnished to do so, subject to the following conditions:
|
|
10
|
+
*
|
|
11
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
* copies or substantial portions of the Software.
|
|
13
|
+
*
|
|
14
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
* SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
import { extractUlidFromFilename } from '../utils/naming.js';
|
|
24
|
+
import { stripCodeBlocks, verifyOpaqueBlocksUntampered, detectUnsupportedMarkdownSyntax, } from '../client/portable-text.js';
|
|
25
|
+
const CONFLICT_MARKER_REGEX = /^<{7}\s|^={7}$|^>{7}\s/m;
|
|
26
|
+
const LOCAL_IMAGE_REGEX = /!\[.*?\]\((?!https?:\/\/|data:).*?\)/;
|
|
27
|
+
/**
|
|
28
|
+
* Runs Preflight Guards G2 through G9 against the article to prevent AI misbehavior.
|
|
29
|
+
* (G1 is enforced at the MCP / CLI invocation layer).
|
|
30
|
+
*/
|
|
31
|
+
export function runPreflightChecks(filePath, parsed, options) {
|
|
32
|
+
const errors = [];
|
|
33
|
+
const warnings = [];
|
|
34
|
+
let cleanedBody = parsed.body;
|
|
35
|
+
const { frontmatter } = parsed;
|
|
36
|
+
// G2: ID (ULID) immutability check
|
|
37
|
+
const filename = path.basename(filePath);
|
|
38
|
+
const filenameId = extractUlidFromFilename(filename);
|
|
39
|
+
if (filenameId) {
|
|
40
|
+
if (filenameId !== frontmatter.id) {
|
|
41
|
+
errors.push({
|
|
42
|
+
code: 'ID_MISMATCH_ERROR',
|
|
43
|
+
message: `ID in frontmatter ('${frontmatter.id}') does not match ULID in filename ('${filenameId}').`,
|
|
44
|
+
suggestion: 'Do not rename files manually with "mv". Update the slug or title in frontmatter, and Emit will rename the file automatically on push.',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// G3: Unintended publish check
|
|
49
|
+
const prevStatus = options?.previousStatus ?? 'draft';
|
|
50
|
+
if (prevStatus === 'draft' && frontmatter.status === 'published' && !options?.allowPublish) {
|
|
51
|
+
errors.push({
|
|
52
|
+
code: 'UNINTENDED_PUBLISH_GUARD',
|
|
53
|
+
message: 'Article status was changed from draft to published, but allowPublish: true was not provided.',
|
|
54
|
+
suggestion: 'To publish the article live, re-run with allowPublish: true. Otherwise, keep status as draft.',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// G4: Taxonomy hallucination check
|
|
58
|
+
if (options?.knownTaxonomies && frontmatter.taxonomies) {
|
|
59
|
+
for (const [taxName, terms] of Object.entries(frontmatter.taxonomies)) {
|
|
60
|
+
const allowed = options.knownTaxonomies[taxName];
|
|
61
|
+
if (allowed && Array.isArray(terms)) {
|
|
62
|
+
for (const term of terms) {
|
|
63
|
+
if (!allowed.includes(term)) {
|
|
64
|
+
errors.push({
|
|
65
|
+
code: 'INVALID_TAXONOMY_ERROR',
|
|
66
|
+
message: `Unknown ${taxName} term: '${term}'.`,
|
|
67
|
+
suggestion: `Use one of the allowed ${taxName}: [${allowed.join(', ')}], or create it via official MCP first.`,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// G5: Local image path detection (stripping code blocks first)
|
|
75
|
+
const bodyWithoutCode = stripCodeBlocks(cleanedBody);
|
|
76
|
+
const localImageMatch = bodyWithoutCode.match(LOCAL_IMAGE_REGEX);
|
|
77
|
+
if (localImageMatch) {
|
|
78
|
+
errors.push({
|
|
79
|
+
code: 'LOCAL_IMAGE_DETECTED',
|
|
80
|
+
message: `Local image path detected in markdown body: '${localImageMatch[0]}'.`,
|
|
81
|
+
suggestion: 'Upload the image first using emdash official MCP "media_upload", and replace the local path with the returned remote URL.',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// G6: Unresolved conflict markers check
|
|
85
|
+
if (CONFLICT_MARKER_REGEX.test(cleanedBody)) {
|
|
86
|
+
errors.push({
|
|
87
|
+
code: 'UNRESOLVED_CONFLICT_MARKERS',
|
|
88
|
+
message: 'Unresolved 3-Way Merge conflict markers (<<<<<<<, =======, >>>>>>>) found in markdown body.',
|
|
89
|
+
suggestion: 'Resolve all conflicting regions in the body and remove the conflict markers before pushing.',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
// G7: Title duplication check (auto-trim)
|
|
93
|
+
// If the body starts with an H1/H2 that duplicates frontmatter.title, trim it safely.
|
|
94
|
+
const trimmedBody = cleanedBody.trimStart();
|
|
95
|
+
const titleHeadingRegex = /^(?:#|##)\s+(.+)$/m;
|
|
96
|
+
const headingMatch = trimmedBody.match(titleHeadingRegex);
|
|
97
|
+
if (headingMatch && trimmedBody.startsWith(headingMatch[0])) {
|
|
98
|
+
const headingText = headingMatch[1].trim().toLowerCase();
|
|
99
|
+
const titleText = frontmatter.title.trim().toLowerCase();
|
|
100
|
+
if (headingText === titleText) {
|
|
101
|
+
warnings.push(`Duplicate top-level heading matching title ('${headingMatch[0]}') was automatically trimmed from body.`);
|
|
102
|
+
// Remove the top heading line and following empty line
|
|
103
|
+
cleanedBody = trimmedBody.slice(headingMatch[0].length).replace(/^\r?\n/, '');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// G9: Opaque fence tampered check
|
|
107
|
+
if (options?.baseMarkdown) {
|
|
108
|
+
const isUntampered = verifyOpaqueBlocksUntampered(options.baseMarkdown, cleanedBody);
|
|
109
|
+
if (!isUntampered) {
|
|
110
|
+
errors.push({
|
|
111
|
+
code: 'MODIFIED_OPAQUE_BLOCK',
|
|
112
|
+
message: 'One or more <!--ec:block ... --> opaque custom block fences were modified or deleted.',
|
|
113
|
+
suggestion: 'Restore the exact <!--ec:block ... --> comment fence from the base version. Custom blocks cannot be edited directly in Markdown.',
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Unsupported Markdown syntax warning (tables, raw block HTML tags)
|
|
118
|
+
const syntaxWarnings = detectUnsupportedMarkdownSyntax(cleanedBody);
|
|
119
|
+
warnings.push(...syntaxWarnings);
|
|
120
|
+
return {
|
|
121
|
+
valid: errors.length === 0,
|
|
122
|
+
cleanedBody,
|
|
123
|
+
frontmatter,
|
|
124
|
+
errors,
|
|
125
|
+
warnings,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=preflight.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../src/sync/preflight.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { CacheDatabase } from '../db/cache.js';
|
|
2
|
+
import { type MapperOptions } from '../client/mapper.js';
|
|
3
|
+
import { type TaxonomyCandidates } from '../utils/frontmatter.js';
|
|
4
|
+
export interface PullOptions extends MapperOptions {
|
|
5
|
+
workspaceRoot: string;
|
|
6
|
+
cache: CacheDatabase;
|
|
7
|
+
origin: string;
|
|
8
|
+
token: string;
|
|
9
|
+
refreshToken?: string;
|
|
10
|
+
fetchFn?: typeof fetch;
|
|
11
|
+
candidates?: TaxonomyCandidates;
|
|
12
|
+
rawItem?: Record<string, unknown>;
|
|
13
|
+
force?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface PullResult {
|
|
16
|
+
id: string;
|
|
17
|
+
slug: string;
|
|
18
|
+
status: string;
|
|
19
|
+
filePath: string;
|
|
20
|
+
updated: boolean;
|
|
21
|
+
skipped?: boolean;
|
|
22
|
+
deleted?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface RemoteArticleItem {
|
|
25
|
+
id: string;
|
|
26
|
+
slug: string;
|
|
27
|
+
status: string;
|
|
28
|
+
title: string;
|
|
29
|
+
_rev: string;
|
|
30
|
+
rawItem: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
export interface ListRemoteArticlesOptions {
|
|
33
|
+
origin: string;
|
|
34
|
+
token: string;
|
|
35
|
+
refreshToken?: string;
|
|
36
|
+
limit?: number;
|
|
37
|
+
all?: boolean;
|
|
38
|
+
status?: 'draft' | 'published' | 'all';
|
|
39
|
+
batchSize?: number;
|
|
40
|
+
fetchFn?: typeof fetch;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Calculates SHA-256 hash of a string.
|
|
44
|
+
*/
|
|
45
|
+
export declare function calculateHash(content: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* Discovers the server's maximum allowed limit for content list queries from /_emdash/api/openapi.json.
|
|
48
|
+
* Defaults to 100 if openapi.json cannot be fetched or parsed.
|
|
49
|
+
*/
|
|
50
|
+
export declare function getRemoteMaxBatchLimit(options: {
|
|
51
|
+
origin: string;
|
|
52
|
+
token?: string;
|
|
53
|
+
fetchFn?: typeof fetch;
|
|
54
|
+
}): Promise<number>;
|
|
55
|
+
/**
|
|
56
|
+
* Lists articles from EmDash API with cursor pagination and dynamic batch size detection.
|
|
57
|
+
* When status is not specified or 'all', fetches up to `limit` published articles AND
|
|
58
|
+
* up to `limit` draft articles in parallel, ensuring drafts and published posts are both covered.
|
|
59
|
+
*/
|
|
60
|
+
export declare function listRemoteArticles(collection: string, options: ListRemoteArticlesOptions): Promise<RemoteArticleItem[]>;
|
|
61
|
+
/**
|
|
62
|
+
* Pulls a content item from EmDash and updates local files and cache.
|
|
63
|
+
* Implements Differential Skip: if remote _rev matches cached rev and local file is untouched,
|
|
64
|
+
* skips disk write and AST conversion.
|
|
65
|
+
*/
|
|
66
|
+
export declare function pullArticle(collection: string, id: string, options: PullOptions): Promise<PullResult>;
|
|
67
|
+
//# sourceMappingURL=pull.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/sync/pull.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAI7E,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,yBAAyB,CAAC;AAIjC,MAAM,WAAW,WAAY,SAAQ,aAAa;IAChD,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,GAAG,WAAW,GAAG,KAAK,CAAC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE;IACpD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8BlB;AAyGD;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAe9B;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,CAAC,CA0QrB"}
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2026 SPORT-SERVICE RS★R
|
|
3
|
+
*
|
|
4
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
* in the Software without restriction, including without limitation the rights
|
|
7
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
* furnished to do so, subject to the following conditions:
|
|
10
|
+
*
|
|
11
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
* copies or substantial portions of the Software.
|
|
13
|
+
*
|
|
14
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
* SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
import fs from 'node:fs';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import crypto from 'node:crypto';
|
|
25
|
+
import { apiResponseToLocal } from '../client/mapper.js';
|
|
26
|
+
import { ApiError } from '../client/error.js';
|
|
27
|
+
import { createAuthedFetch } from '../client/http.js';
|
|
28
|
+
import { unwrapEnvelope, extractRev } from '../client/api.js';
|
|
29
|
+
import { computeArticleRelativePath, extractDatePrefixFromPath, serializeMarkdown, } from '../utils/frontmatter.js';
|
|
30
|
+
import { syncRemoteBylines } from './bylines.js';
|
|
31
|
+
/**
|
|
32
|
+
* Calculates SHA-256 hash of a string.
|
|
33
|
+
*/
|
|
34
|
+
export function calculateHash(content) {
|
|
35
|
+
return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Discovers the server's maximum allowed limit for content list queries from /_emdash/api/openapi.json.
|
|
39
|
+
* Defaults to 100 if openapi.json cannot be fetched or parsed.
|
|
40
|
+
*/
|
|
41
|
+
export async function getRemoteMaxBatchLimit(options) {
|
|
42
|
+
const fetchFn = options.fetchFn || fetch;
|
|
43
|
+
try {
|
|
44
|
+
const url = new URL('/_emdash/api/openapi.json', options.origin).toString();
|
|
45
|
+
const headers = { Accept: 'application/json' };
|
|
46
|
+
if (options.token) {
|
|
47
|
+
headers.Authorization = `Bearer ${options.token}`;
|
|
48
|
+
}
|
|
49
|
+
const res = await fetchFn(url, { headers });
|
|
50
|
+
if (!res.ok)
|
|
51
|
+
return 100;
|
|
52
|
+
const json = (await res.json());
|
|
53
|
+
const paths = (json.paths || {});
|
|
54
|
+
const getEndpoint = paths['/_emdash/api/content/{collection}']?.get || paths['/content/{collection}']?.get;
|
|
55
|
+
if (getEndpoint && Array.isArray(getEndpoint.parameters)) {
|
|
56
|
+
const params = getEndpoint.parameters;
|
|
57
|
+
const limitParam = params.find((p) => p.name === 'limit');
|
|
58
|
+
const schema = (limitParam?.schema || {});
|
|
59
|
+
if (typeof schema.maximum === 'number' && schema.maximum > 0) {
|
|
60
|
+
return schema.maximum;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Ignore network or parse error, fallback to safe 100
|
|
66
|
+
}
|
|
67
|
+
return 100;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Fetches articles for a specific status ('draft' or 'published') using cursor pagination.
|
|
71
|
+
*/
|
|
72
|
+
async function fetchStatusArticles(collection, status, options, serverMaxLimit) {
|
|
73
|
+
const fetchFn = createAuthedFetch(options);
|
|
74
|
+
const targetTotal = options.all ? Infinity : (options.limit ?? 100);
|
|
75
|
+
const items = [];
|
|
76
|
+
let cursor = undefined;
|
|
77
|
+
let batchLimit = serverMaxLimit;
|
|
78
|
+
while (items.length < targetTotal) {
|
|
79
|
+
const remaining = targetTotal - items.length;
|
|
80
|
+
const currentBatch = Math.min(remaining, batchLimit);
|
|
81
|
+
const url = new URL(`/_emdash/api/content/${collection}`, options.origin);
|
|
82
|
+
url.searchParams.set('status', status);
|
|
83
|
+
url.searchParams.set('limit', String(currentBatch));
|
|
84
|
+
if (cursor) {
|
|
85
|
+
url.searchParams.set('cursor', cursor);
|
|
86
|
+
}
|
|
87
|
+
let res = await fetchFn(url.toString(), {
|
|
88
|
+
method: 'GET',
|
|
89
|
+
headers: {
|
|
90
|
+
Authorization: `Bearer ${options.token}`,
|
|
91
|
+
Accept: 'application/json',
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
// Adaptive fallback if server responds with validation error on limit
|
|
95
|
+
if (!res.ok && res.status === 400) {
|
|
96
|
+
try {
|
|
97
|
+
const errJson = (await res.clone().json());
|
|
98
|
+
const errMsg = JSON.stringify(errJson);
|
|
99
|
+
const match = errMsg.match(/expected number to be <=(\d+)/);
|
|
100
|
+
if (match && Number(match[1]) < batchLimit) {
|
|
101
|
+
batchLimit = Number(match[1]);
|
|
102
|
+
url.searchParams.set('limit', String(Math.min(remaining, batchLimit)));
|
|
103
|
+
res = await fetchFn(url.toString(), {
|
|
104
|
+
method: 'GET',
|
|
105
|
+
headers: {
|
|
106
|
+
Authorization: `Bearer ${options.token}`,
|
|
107
|
+
Accept: 'application/json',
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Continue to normal error handling below
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (!res.ok) {
|
|
117
|
+
throw new ApiError({
|
|
118
|
+
message: `Failed to list remote articles (${status}): ${res.statusText}`,
|
|
119
|
+
code: 'ARTICLE_LIST_FAILED',
|
|
120
|
+
status: res.status,
|
|
121
|
+
endpoint: url.toString(),
|
|
122
|
+
suggestion: 'Verify network connection, permissions, or try lowering the limit.',
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const json = await res.json();
|
|
126
|
+
const data = unwrapEnvelope(json);
|
|
127
|
+
const rawItems = (Array.isArray(data.items) ? data.items : Array.isArray(data) ? data : []);
|
|
128
|
+
if (rawItems.length === 0) {
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
for (const raw of rawItems) {
|
|
132
|
+
const rawData = (raw.data || {});
|
|
133
|
+
const rev = extractRev(raw, '1');
|
|
134
|
+
items.push({
|
|
135
|
+
id: String(raw.id),
|
|
136
|
+
slug: String(raw.slug || ''),
|
|
137
|
+
status: String(raw.status || status),
|
|
138
|
+
title: String(rawData.title || raw.title || 'Untitled'),
|
|
139
|
+
_rev: rev,
|
|
140
|
+
rawItem: raw,
|
|
141
|
+
});
|
|
142
|
+
if (items.length >= targetTotal) {
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (!data.nextCursor || typeof data.nextCursor !== 'string') {
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
cursor = data.nextCursor;
|
|
150
|
+
}
|
|
151
|
+
return items;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Lists articles from EmDash API with cursor pagination and dynamic batch size detection.
|
|
155
|
+
* When status is not specified or 'all', fetches up to `limit` published articles AND
|
|
156
|
+
* up to `limit` draft articles in parallel, ensuring drafts and published posts are both covered.
|
|
157
|
+
*/
|
|
158
|
+
export async function listRemoteArticles(collection, options) {
|
|
159
|
+
const serverMaxLimit = options.batchSize || (await getRemoteMaxBatchLimit(options));
|
|
160
|
+
// If a specific status is explicitly requested
|
|
161
|
+
if (options.status === 'draft' || options.status === 'published') {
|
|
162
|
+
return fetchStatusArticles(collection, options.status, options, serverMaxLimit);
|
|
163
|
+
}
|
|
164
|
+
// Default: Fetch both published (up to limit) and draft (up to limit) in parallel
|
|
165
|
+
const [draftItems, publishedItems] = await Promise.all([
|
|
166
|
+
fetchStatusArticles(collection, 'draft', options, serverMaxLimit),
|
|
167
|
+
fetchStatusArticles(collection, 'published', options, serverMaxLimit),
|
|
168
|
+
]);
|
|
169
|
+
return [...draftItems, ...publishedItems];
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Pulls a content item from EmDash and updates local files and cache.
|
|
173
|
+
* Implements Differential Skip: if remote _rev matches cached rev and local file is untouched,
|
|
174
|
+
* skips disk write and AST conversion.
|
|
175
|
+
*/
|
|
176
|
+
export async function pullArticle(collection, id, options) {
|
|
177
|
+
const fetchFn = createAuthedFetch(options);
|
|
178
|
+
let rawItem = options.rawItem;
|
|
179
|
+
// 1. Fast-path check: If rawItem is provided and not forced, check rev & local hash
|
|
180
|
+
if (rawItem && !options.force) {
|
|
181
|
+
const existingSync = options.cache.getSyncState(id);
|
|
182
|
+
const remoteRev = String(rawItem._rev || rawItem.rev || rawItem.version || '1');
|
|
183
|
+
if (existingSync && existingSync.remoteRev === remoteRev) {
|
|
184
|
+
const fullExistingPath = path.join(options.workspaceRoot, existingSync.localFilePath);
|
|
185
|
+
if (fs.existsSync(fullExistingPath)) {
|
|
186
|
+
try {
|
|
187
|
+
const currentDiskContent = fs.readFileSync(fullExistingPath, 'utf8');
|
|
188
|
+
const currentDiskHash = calculateHash(currentDiskContent);
|
|
189
|
+
// Check if file path matches current naming standard
|
|
190
|
+
const existingPrefix = extractDatePrefixFromPath(existingSync.localFilePath);
|
|
191
|
+
const rawData = (rawItem.data || {});
|
|
192
|
+
const rawTitle = String(rawData.title || rawItem.title || rawItem.slug || '');
|
|
193
|
+
const articleDate = rawItem.publishedAt ||
|
|
194
|
+
existingPrefix ||
|
|
195
|
+
rawItem.createdAt ||
|
|
196
|
+
null;
|
|
197
|
+
const expectedRelPath = computeArticleRelativePath(collection, String(rawItem.status || 'draft'), id, rawTitle, articleDate);
|
|
198
|
+
// If path matches and contents are unchanged, safely skip
|
|
199
|
+
if (existingSync.localFilePath === expectedRelPath) {
|
|
200
|
+
if (currentDiskHash === existingSync.localHash) {
|
|
201
|
+
return {
|
|
202
|
+
id,
|
|
203
|
+
slug: existingSync.slug,
|
|
204
|
+
status: existingSync.status,
|
|
205
|
+
filePath: existingSync.localFilePath,
|
|
206
|
+
updated: false,
|
|
207
|
+
skipped: true,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
// Local file has unpushed edits -> protect local edits
|
|
211
|
+
return {
|
|
212
|
+
id,
|
|
213
|
+
slug: existingSync.slug,
|
|
214
|
+
status: existingSync.status,
|
|
215
|
+
filePath: existingSync.localFilePath,
|
|
216
|
+
updated: false,
|
|
217
|
+
skipped: true,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// If read fails, proceed to normal write
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// 2. Fetch raw item if not passed in options
|
|
228
|
+
if (!rawItem) {
|
|
229
|
+
const url = new URL(`/_emdash/api/content/${collection}/${id}`, options.origin).toString();
|
|
230
|
+
const res = await fetchFn(url, {
|
|
231
|
+
method: 'GET',
|
|
232
|
+
headers: {
|
|
233
|
+
Authorization: `Bearer ${options.token}`,
|
|
234
|
+
Accept: 'application/json',
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
if (!res.ok) {
|
|
238
|
+
if (res.status === 404) {
|
|
239
|
+
// Remote article was deleted (404)
|
|
240
|
+
const existingSync = options.cache.getSyncState(id);
|
|
241
|
+
if (existingSync) {
|
|
242
|
+
const fullPath = path.join(options.workspaceRoot, existingSync.localFilePath);
|
|
243
|
+
if (fs.existsSync(fullPath)) {
|
|
244
|
+
const diskContent = fs.readFileSync(fullPath, 'utf8');
|
|
245
|
+
const diskHash = calculateHash(diskContent);
|
|
246
|
+
// Only delete if local file has no unpushed edits
|
|
247
|
+
if (diskHash === existingSync.localHash) {
|
|
248
|
+
try {
|
|
249
|
+
fs.unlinkSync(fullPath);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
// Ignore unlink error
|
|
253
|
+
}
|
|
254
|
+
options.cache.deleteEntry(id);
|
|
255
|
+
return {
|
|
256
|
+
id,
|
|
257
|
+
slug: existingSync.slug,
|
|
258
|
+
status: existingSync.status,
|
|
259
|
+
filePath: existingSync.localFilePath,
|
|
260
|
+
updated: false,
|
|
261
|
+
deleted: true,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
throw new ApiError({
|
|
268
|
+
message: `Failed to pull article ${collection}/${id}: ${res.statusText}`,
|
|
269
|
+
code: 'ARTICLE_PULL_FAILED',
|
|
270
|
+
status: res.status,
|
|
271
|
+
endpoint: url,
|
|
272
|
+
suggestion: `Verify that article '${id}' exists in collection '${collection}'.`,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
rawItem = (await res.json());
|
|
276
|
+
}
|
|
277
|
+
// Ensure byline ID-to-slug mapping is loaded
|
|
278
|
+
let bylineIdToSlug = options.bylineIdToSlug;
|
|
279
|
+
if (!bylineIdToSlug) {
|
|
280
|
+
const cachedMaps = options.cache.getBylineMaps();
|
|
281
|
+
if (Object.keys(cachedMaps.idToSlug).length > 0) {
|
|
282
|
+
bylineIdToSlug = cachedMaps.idToSlug;
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
const freshMaps = await syncRemoteBylines({
|
|
286
|
+
origin: options.origin,
|
|
287
|
+
token: options.token,
|
|
288
|
+
refreshToken: options.refreshToken,
|
|
289
|
+
cache: options.cache,
|
|
290
|
+
fetchFn: options.fetchFn,
|
|
291
|
+
}).catch(() => ({ slugToId: {}, idToSlug: {} }));
|
|
292
|
+
bylineIdToSlug = freshMaps.idToSlug;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const effectiveOptions = {
|
|
296
|
+
...options,
|
|
297
|
+
bylineIdToSlug,
|
|
298
|
+
};
|
|
299
|
+
const { frontmatter, body, rev } = apiResponseToLocal(rawItem, effectiveOptions);
|
|
300
|
+
// Fetch remote taxonomy assignments (category & tag)
|
|
301
|
+
const fetchAssignedTerms = async (taxonomy) => {
|
|
302
|
+
try {
|
|
303
|
+
const termsUrl = new URL(`/_emdash/api/content/${collection}/${id}/terms/${taxonomy}`, options.origin).toString();
|
|
304
|
+
const termsRes = await fetchFn(termsUrl, {
|
|
305
|
+
method: 'GET',
|
|
306
|
+
headers: {
|
|
307
|
+
Authorization: `Bearer ${options.token}`,
|
|
308
|
+
Accept: 'application/json',
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
if (!termsRes.ok)
|
|
312
|
+
return null;
|
|
313
|
+
const termsJson = (await termsRes.json());
|
|
314
|
+
const assigned = termsJson.data?.terms || [];
|
|
315
|
+
return assigned.map((t) => t.slug || t.id).filter((s) => Boolean(s));
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
const [categoriesTerms, tagsTerms] = await Promise.all([
|
|
322
|
+
fetchAssignedTerms('category'),
|
|
323
|
+
fetchAssignedTerms('tag'),
|
|
324
|
+
]);
|
|
325
|
+
if (categoriesTerms !== null || tagsTerms !== null) {
|
|
326
|
+
if (!frontmatter.taxonomies) {
|
|
327
|
+
frontmatter.taxonomies = {};
|
|
328
|
+
}
|
|
329
|
+
if (categoriesTerms !== null) {
|
|
330
|
+
frontmatter.taxonomies.categories = categoriesTerms;
|
|
331
|
+
}
|
|
332
|
+
if (tagsTerms !== null) {
|
|
333
|
+
frontmatter.taxonomies.tags = tagsTerms;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// Format full Markdown
|
|
337
|
+
const markdownContent = serializeMarkdown(frontmatter, body);
|
|
338
|
+
const contentHash = calculateHash(markdownContent);
|
|
339
|
+
// Determine standard relative file path: <collection>/<status>/<yyyyMMdd>_<ulid>-<title>.md
|
|
340
|
+
const existingSync = options.cache.getSyncState(frontmatter.id);
|
|
341
|
+
const existingPrefix = existingSync?.localFilePath
|
|
342
|
+
? extractDatePrefixFromPath(existingSync.localFilePath)
|
|
343
|
+
: null;
|
|
344
|
+
const dateInput = frontmatter.publishedAt ||
|
|
345
|
+
existingPrefix ||
|
|
346
|
+
rawItem.createdAt ||
|
|
347
|
+
rawItem.updatedAt ||
|
|
348
|
+
null;
|
|
349
|
+
const relPath = computeArticleRelativePath(frontmatter.collection, frontmatter.status, frontmatter.id, frontmatter.title || frontmatter.slug, dateInput);
|
|
350
|
+
const fullFilePath = path.join(options.workspaceRoot, relPath);
|
|
351
|
+
// Path tracking: Check if the file was previously located at a different path
|
|
352
|
+
if (existingSync && existingSync.localFilePath !== relPath) {
|
|
353
|
+
const oldFullPath = path.join(options.workspaceRoot, existingSync.localFilePath);
|
|
354
|
+
if (fs.existsSync(oldFullPath)) {
|
|
355
|
+
try {
|
|
356
|
+
fs.unlinkSync(oldFullPath);
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
// Ignore removal failure
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// Ensure parent directory exists and write file
|
|
364
|
+
fs.mkdirSync(path.dirname(fullFilePath), { recursive: true });
|
|
365
|
+
fs.writeFileSync(fullFilePath, markdownContent, 'utf8');
|
|
366
|
+
// Update Base snapshot in cache
|
|
367
|
+
options.cache.saveBaseSnapshot({
|
|
368
|
+
id: frontmatter.id,
|
|
369
|
+
rev,
|
|
370
|
+
bodyMarkdown: body,
|
|
371
|
+
frontmatterYaml: JSON.stringify(frontmatter),
|
|
372
|
+
hash: contentHash,
|
|
373
|
+
savedAt: Math.floor(Date.now() / 1000),
|
|
374
|
+
});
|
|
375
|
+
// Update sync state
|
|
376
|
+
options.cache.saveSyncState({
|
|
377
|
+
id: frontmatter.id,
|
|
378
|
+
localFilePath: relPath,
|
|
379
|
+
localHash: contentHash,
|
|
380
|
+
remoteRev: rev,
|
|
381
|
+
status: frontmatter.status,
|
|
382
|
+
slug: frontmatter.slug,
|
|
383
|
+
lastSyncedAt: Math.floor(Date.now() / 1000),
|
|
384
|
+
});
|
|
385
|
+
// Update entries cache
|
|
386
|
+
options.cache.saveEntry({
|
|
387
|
+
id: frontmatter.id,
|
|
388
|
+
collection: frontmatter.collection,
|
|
389
|
+
slug: frontmatter.slug,
|
|
390
|
+
status: frontmatter.status,
|
|
391
|
+
title: frontmatter.title,
|
|
392
|
+
rev,
|
|
393
|
+
publishedAt: frontmatter.publishedAt,
|
|
394
|
+
rawJson: JSON.stringify(rawItem),
|
|
395
|
+
updatedAt: Math.floor(Date.now() / 1000),
|
|
396
|
+
});
|
|
397
|
+
return {
|
|
398
|
+
id: frontmatter.id,
|
|
399
|
+
slug: frontmatter.slug,
|
|
400
|
+
status: frontmatter.status,
|
|
401
|
+
filePath: relPath,
|
|
402
|
+
updated: true,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
//# sourceMappingURL=pull.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pull.js","sourceRoot":"","sources":["../../src/sync/pull.ts"],"names":[],"mappings":"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"}
|