careervivid 1.7.0 → 1.9.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/dist/api.d.ts CHANGED
@@ -20,6 +20,7 @@ export interface PublishResult {
20
20
  postId: string;
21
21
  url: string;
22
22
  message: string;
23
+ title?: string;
23
24
  }
24
25
  export interface VerifyResult {
25
26
  userId: string;
@@ -39,6 +40,7 @@ export interface ApiError {
39
40
  }[];
40
41
  }
41
42
  export declare function publishPost(payload: PublishPayload, dryRun?: boolean): Promise<PublishResult | ApiError>;
43
+ export declare function getPost(postId: string): Promise<PublishResult | ApiError>;
42
44
  export declare function updatePost(postId: string, payload: Partial<PublishPayload>): Promise<PublishResult | ApiError>;
43
45
  /**
44
46
  * Verify an API key against the /verifyAuth endpoint.
package/dist/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
package/dist/api.js CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { getApiKey, getApiUrl } from "./config.js";
8
8
  // ── Helpers ───────────────────────────────────────────────────────────────────
9
- const CLI_VERSION = "1.7.0";
9
+ const CLI_VERSION = "1.9.0";
10
10
  function requireApiKey() {
11
11
  const key = getApiKey();
12
12
  if (!key) {
@@ -73,6 +73,9 @@ export async function publishPost(payload, dryRun = false) {
73
73
  }
74
74
  return apiRequest("POST", "publish", payload);
75
75
  }
76
+ export async function getPost(postId) {
77
+ return apiRequest("GET", `publish/${postId}`);
78
+ }
76
79
  export async function updatePost(postId, payload) {
77
80
  return apiRequest("PATCH", "publish", { ...payload, postId });
78
81
  }
@@ -1 +1 @@
1
- {"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../../src/commands/publish.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8KpC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6J7D"}
1
+ {"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../../src/commands/publish.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuOpC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6J7D"}
@@ -55,15 +55,37 @@ async function publishSingleFile(filePath, content, opts, jsonMode) {
55
55
  const dryRun = !!opts.dryRun;
56
56
  // ── Parse Frontmatter ───────────────────────────────────────────
57
57
  let postId = undefined;
58
+ let frontmatterTags = [];
58
59
  let cleanContent = content;
59
60
  // Robust frontmatter extraction (multiline, flexible spacing)
60
- const frontmatterMatch = content.match(/^---\s*[\r\n]+([\s\S]*?)[\r\n]+---\s*[\r\n]+/);
61
+ // Matches --- start, any non-greedy content, and --- end with optional whitespace/newlines
62
+ const frontmatterMatch = content.match(/^---\s*[\r\n]+([\s\S]*?)[\r\n]+---/);
61
63
  if (frontmatterMatch) {
62
64
  const fm = frontmatterMatch[1];
63
65
  // Match postId with potential leading/trailing whitespace or quotes
64
66
  const idMatch = fm.match(/postId:\s*["']?([a-zA-Z0-9_-]+)["']?/i);
65
67
  postId = idMatch?.[1];
66
- cleanContent = content.slice(frontmatterMatch[0].length);
68
+ // Match tags: [tag1, tag2] or tags: tag1, tag2
69
+ const tagsMatch = fm.match(/tags:\s*(?:\[(.*?)\]|(.*))/i);
70
+ if (tagsMatch) {
71
+ const tagsStr = tagsMatch[1] || tagsMatch[2];
72
+ frontmatterTags = tagsStr
73
+ ?.split(",")
74
+ .map(t => t.trim().replace(/^["']|["']$/g, ""))
75
+ .filter(Boolean) || [];
76
+ }
77
+ // Remove the frontmatter block AND any trailing whitespace/newlines
78
+ const afterFm = content.slice(frontmatterMatch[0].length);
79
+ cleanContent = afterFm.replace(/^\s+/, "");
80
+ }
81
+ // Best Practice: Also look for "Tags: #tag1, #tag2" at the bottom of the article
82
+ const footerTagsMatch = cleanContent.match(/Tags:\s*(.*)$/im);
83
+ let footerTags = [];
84
+ if (footerTagsMatch) {
85
+ footerTags = footerTagsMatch[1]
86
+ .split(/[,\s]+/)
87
+ .map(t => t.trim().replace(/^#/, ""))
88
+ .filter(Boolean);
67
89
  }
68
90
  // Fail if onlyUpdate is set but no postId is found
69
91
  if (opts.onlyUpdate && !postId) {
@@ -77,7 +99,7 @@ async function publishSingleFile(filePath, content, opts, jsonMode) {
77
99
  const type = opts.type || inferType(format);
78
100
  let title = opts.title || "";
79
101
  if (!title && format === "markdown") {
80
- const firstHeading = cleanContent.match(/^#\s+(.+)$/m);
102
+ const firstHeading = cleanContent.match(/^[#]{1,2}\s+(.+)$/m);
81
103
  if (firstHeading) {
82
104
  title = firstHeading[1].trim();
83
105
  }
@@ -98,21 +120,50 @@ async function publishSingleFile(filePath, content, opts, jsonMode) {
98
120
  title = answers.title.trim();
99
121
  }
100
122
  }
101
- const tags = opts.tags
102
- ? opts.tags.split(",").map((t) => t.trim()).filter(Boolean)
103
- : [];
123
+ const tags = [
124
+ ...frontmatterTags,
125
+ ...footerTags,
126
+ ...(opts.tags ? opts.tags.split(",").map((t) => t.trim()).filter(Boolean) : [])
127
+ ];
128
+ // De-duplicate and limit to 5 tags
129
+ const finalTags = [...new Set(tags)].slice(0, 5);
104
130
  const payload = {
105
131
  type,
106
132
  dataFormat: format,
107
133
  title,
108
134
  content: cleanContent,
109
- tags,
135
+ tags: finalTags,
110
136
  ...(opts.cover ? { coverImage: opts.cover } : {}),
111
137
  ...(opts.official ? { isOfficialPost: true } : {}),
112
138
  };
113
139
  let result;
114
140
  if (postId && !dryRun) {
115
- const { updatePost } = await import("../api.js");
141
+ const { updatePost, getPost } = await import("../api.js");
142
+ // Best Practice: Fetch remote title to verify identity before overwrite
143
+ const remotePost = await getPost(postId);
144
+ if (!isApiError(remotePost)) {
145
+ const remoteTitle = remotePost.title;
146
+ // Case-insensitive title comparison to catch potential mixups
147
+ if (title && remoteTitle && title.toLowerCase() !== remoteTitle.toLowerCase()) {
148
+ if (jsonMode) {
149
+ console.error(chalk.red(`\n ✖ Title mismatch for ${postId}. Local: "${title}", Remote: "${remoteTitle}". Aborting.`));
150
+ return { success: false };
151
+ }
152
+ else {
153
+ const enquirer = (await import("enquirer"));
154
+ const prompt = enquirer.default?.prompt || enquirer.prompt;
155
+ const answers = await prompt({
156
+ type: "confirm",
157
+ name: "confirm",
158
+ message: `Title Mismatch! Local: "${chalk.cyan(title)}", Remote: "${chalk.yellow(remoteTitle)}". Continue?`,
159
+ initial: false
160
+ });
161
+ if (!answers.confirm) {
162
+ return { success: false };
163
+ }
164
+ }
165
+ }
166
+ }
116
167
  result = await updatePost(postId, payload);
117
168
  }
118
169
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "careervivid",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "description": "Official CLI for CareerVivid — publish articles, diagrams, and portfolio updates from your terminal or AI agent",
5
5
  "type": "module",
6
6
  "bin": {