@platformos/platformos-common 0.0.15 → 0.0.16

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.
@@ -16,7 +16,9 @@ function extractFrontmatter(source: string): PageFrontmatter | null {
16
16
  const trimmed = source.trimStart();
17
17
  if (!trimmed.startsWith('---')) return null;
18
18
 
19
- const end = trimmed.indexOf('---', 3);
19
+ // Search for the closing delimiter as `\n---` so we don't accidentally match
20
+ // a `---` sequence that appears inside a YAML value (e.g. `slug: my---slug`).
21
+ const end = trimmed.indexOf('\n---', 3);
20
22
  if (end === -1) return null;
21
23
 
22
24
  const yamlBlock = trimmed.slice(3, end).trim();
@@ -48,7 +50,7 @@ function extractFrontmatter(source: string): PageFrontmatter | null {
48
50
  * file:///project/app/views/pages/about.html.liquid -> about.html.liquid
49
51
  * file:///project/modules/admin/public/views/pages/dashboard.html.liquid -> dashboard.html.liquid
50
52
  */
51
- function extractRelativePagePath(uri: string): string | null {
53
+ export function extractRelativePagePath(uri: string): string | null {
52
54
  const patterns = [
53
55
  // App-level pages: app/views/pages/ or marketplace_builder/pages/
54
56
  /\/(app|marketplace_builder)\/(views\/pages|pages)\//,
@@ -187,6 +189,21 @@ export class RouteTable {
187
189
  return this._built;
188
190
  }
189
191
 
192
+ /**
193
+ * Populate the route table from an in-memory collection of URI → content
194
+ * pairs, bypassing the filesystem entirely. Useful for warming the table
195
+ * from a DocumentManager without a full disk scan on startup.
196
+ *
197
+ * Only page URIs are registered; non-page URIs are silently skipped.
198
+ */
199
+ buildFromEntries(entries: Iterable<[uri: string, content: string]>): void {
200
+ this.routes.clear();
201
+ for (const [uri, content] of entries) {
202
+ this.addPageFromContent(uri, content);
203
+ }
204
+ this._built = true;
205
+ }
206
+
190
207
  async build(rootUri: URI): Promise<void> {
191
208
  this.routes.clear();
192
209
 
@@ -1,4 +1,4 @@
1
- export { RouteTable } from './RouteTable';
1
+ export { RouteTable, extractRelativePagePath } from './RouteTable';
2
2
  export { slugFromFilePath, formatFromFilePath, KNOWN_FORMATS } from './slugFromFilePath';
3
3
  export { parseSlug, calculatePrecedence } from './parseSlug';
4
4
  export type { RouteEntry, RouteSegment } from './types';
@@ -1,14 +1,34 @@
1
1
  import { AbstractFileSystem, FileType } from '../AbstractFileSystem';
2
+ import { parseModulePrefix } from '../path-utils';
2
3
  import { URI, Utils } from 'vscode-uri';
3
4
  import yaml from 'js-yaml';
4
5
 
5
- type ModuleKeyInfo =
6
- | { isModule: false; key: string }
7
- | { isModule: true; moduleName: string; key: string };
8
-
9
6
  export class TranslationProvider {
10
7
  constructor(private readonly fs: AbstractFileSystem) {}
11
8
 
9
+ /** Cache for filesystem-only translation loads (bypassed when contentOverride is set). */
10
+ private translationsCache = new Map<string, Record<string, any>>();
11
+
12
+ /**
13
+ * Invalidate cached translations. Call after any translation file is written
14
+ * to disk so subsequent calls re-read from the filesystem.
15
+ *
16
+ * Omitting `uri` clears the entire cache.
17
+ * Passing a `uri` removes only the entries whose base directory contains that file.
18
+ */
19
+ clearTranslationsCache(uri?: string): void {
20
+ if (!uri) {
21
+ this.translationsCache.clear();
22
+ return;
23
+ }
24
+ for (const key of this.translationsCache.keys()) {
25
+ const baseUri = key.slice(0, key.lastIndexOf(':'));
26
+ if (uri.startsWith(baseUri)) {
27
+ this.translationsCache.delete(key);
28
+ }
29
+ }
30
+ }
31
+
12
32
  private async isFile(path: string): Promise<boolean> {
13
33
  try {
14
34
  return (await this.fs.stat(path)).type === FileType.File;
@@ -42,16 +62,6 @@ export class TranslationProvider {
42
62
  return true;
43
63
  }
44
64
 
45
- private parseModuleKey(translationKey: string): ModuleKeyInfo {
46
- if (!translationKey.startsWith('modules/')) {
47
- return { isModule: false, key: translationKey };
48
- }
49
-
50
- const [, moduleName, key] = translationKey.split('/', 3);
51
-
52
- return key ? { isModule: true, moduleName, key } : { isModule: false, key: translationKey };
53
- }
54
-
55
65
  static getSearchPaths(moduleName?: string): string[] {
56
66
  if (!moduleName) {
57
67
  return ['app/translations'];
@@ -70,7 +80,7 @@ export class TranslationProvider {
70
80
  translationKey: string,
71
81
  defaultLocale: string,
72
82
  ): Promise<[string | undefined, string | undefined]> {
73
- const parsed = this.parseModuleKey(translationKey);
83
+ const parsed = parseModulePrefix(translationKey);
74
84
 
75
85
  if (!parsed.key) {
76
86
  return [undefined, undefined];
@@ -129,6 +139,14 @@ export class TranslationProvider {
129
139
  locale: string,
130
140
  contentOverride?: (uri: string) => string | undefined,
131
141
  ): Promise<Record<string, any>> {
142
+ const cacheKey = `${translationBaseUri.toString()}:${locale}`;
143
+
144
+ // Return cached result when the caller has no editor overrides (e.g. linter/CI).
145
+ // Skip cache when contentOverride is set — unsaved buffer content may differ from disk.
146
+ if (!contentOverride && this.translationsCache.has(cacheKey)) {
147
+ return this.translationsCache.get(cacheKey)!;
148
+ }
149
+
132
150
  const merged: Record<string, any> = {};
133
151
 
134
152
  const read = async (uri: string): Promise<string | undefined> => {
@@ -158,6 +176,10 @@ export class TranslationProvider {
158
176
  }
159
177
  }
160
178
 
179
+ if (!contentOverride) {
180
+ this.translationsCache.set(cacheKey, merged);
181
+ }
182
+
161
183
  return merged;
162
184
  }
163
185