@platformos/platformos-check-node 0.0.19 → 0.0.20

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/README.md CHANGED
@@ -4,6 +4,48 @@ This is the Node.js wrapper of the runtime-agnostic [`@platformos/platformos-che
4
4
 
5
5
  See the [@platformos/platformos-check-common README](../platformos-check-common) for more details.
6
6
 
7
+ ## Entrypoints
8
+
9
+ | Function | Use |
10
+ |---|---|
11
+ | `check(root, configPath?)` | Lint a whole project on disk; returns `Offense[]`. |
12
+ | `appCheckRun(root, configPath?, log?)` | Same, but also returns the resolved `App` and `Config`. |
13
+ | `checkAndAutofix(root, configPath?)` | Lint then apply safe autofixes to disk. |
14
+ | `lintBuffer(params)` | Lint a single in-memory buffer in the context of its on-disk project. |
15
+
16
+ ### `lintBuffer` — lint one buffer with project overlay
17
+
18
+ `lintBuffer` is the typed seam an embedding tool (e.g. the MCP supervisor) lints
19
+ through — a **direct library call, not an LSP and not a subprocess**. It loads
20
+ the project from disk so cross-file checks (`MissingPartial`, `MissingPage`,
21
+ `OrphanedPartial`, …) resolve against real files, overlays the buffer under edit
22
+ in memory so the **unsaved** content is what gets linted and cross-referenced,
23
+ and returns the structured check-common `Offense[]` for that file with `fix` /
24
+ `suggest` and every typed field intact — no message-string round-trip.
25
+
26
+ ```ts
27
+ import { lintBuffer } from '@platformos/platformos-check-node';
28
+
29
+ const offenses = await lintBuffer({
30
+ root: '/abs/path/to/project', // project root (absolute)
31
+ filePath: '/abs/path/to/project/app/views/pages/contact.liquid', // file under edit (absolute)
32
+ content: editorBufferContents, // unsaved buffer
33
+ // configPath?: explicit config; resolved from root when omitted
34
+ });
35
+
36
+ for (const offense of offenses) {
37
+ offense.check; // e.g. 'MissingPartial'
38
+ offense.severity; // Severity
39
+ offense.start.index; offense.end.index; // 0-based offsets
40
+ offense.fix; // optional Fixer (safe autofix)
41
+ offense.suggest; // optional Suggestion[] (manual fixes)
42
+ }
43
+ ```
44
+
45
+ When `filePath` already exists in the project its on-disk source is replaced by
46
+ the buffer; when it is new (not yet saved) the buffer is added so it is still
47
+ linted. Only offenses for the buffer's file are returned.
48
+
7
49
  ## License
8
50
 
9
51
  MIT.
package/configs/all.yml CHANGED
@@ -42,6 +42,9 @@ MatchingTranslations:
42
42
  MissingAsset:
43
43
  enabled: true
44
44
  severity: 0
45
+ MissingContentForLayout:
46
+ enabled: true
47
+ severity: 0
45
48
  MissingPage:
46
49
  enabled: true
47
50
  severity: 1
@@ -64,6 +67,9 @@ ParserBlockingScript:
64
67
  PartialCallArguments:
65
68
  enabled: true
66
69
  severity: 0
70
+ ReservedVariableName:
71
+ enabled: true
72
+ severity: 0
67
73
  TranslationKeyExists:
68
74
  enabled: true
69
75
  severity: 0
@@ -42,6 +42,9 @@ MatchingTranslations:
42
42
  MissingAsset:
43
43
  enabled: true
44
44
  severity: 0
45
+ MissingContentForLayout:
46
+ enabled: true
47
+ severity: 0
45
48
  MissingPage:
46
49
  enabled: true
47
50
  severity: 1
@@ -64,6 +67,9 @@ ParserBlockingScript:
64
67
  PartialCallArguments:
65
68
  enabled: true
66
69
  severity: 0
70
+ ReservedVariableName:
71
+ enabled: true
72
+ severity: 0
67
73
  TranslationKeyExists:
68
74
  enabled: true
69
75
  severity: 0
package/dist/index.d.ts CHANGED
@@ -16,6 +16,33 @@ export declare function toSourceCode(absolutePath: string): Promise<LiquidSource
16
16
  export declare function check(root: string, configPath?: string): Promise<Offense[]>;
17
17
  export declare function checkAndAutofix(root: string, configPath?: string): Promise<void>;
18
18
  export declare function appCheckRun(root: string, configPath?: string, log?: (message: string) => void): Promise<AppCheckRun>;
19
+ export interface LintBufferParams {
20
+ /** Absolute path to the project root. */
21
+ root: string;
22
+ /** Absolute path to the file under edit. */
23
+ filePath: string;
24
+ /** In-memory contents of the file under edit (may differ from, or not yet exist on, disk). */
25
+ content: string;
26
+ /** Explicit config path; resolved from `root` when omitted. */
27
+ configPath?: string;
28
+ log?: (message: string) => void;
29
+ }
30
+ /**
31
+ * Lint a single in-memory buffer in the context of its on-disk project.
32
+ *
33
+ * This is the typed seam the MCP supervisor lints through — NOT an LSP, NOT a
34
+ * subprocess. The on-disk project is loaded so cross-file checks
35
+ * (`MissingPartial`, `MissingPage`, `OrphanedPartial`, …) resolve against real
36
+ * files, and the buffer under edit is overlaid in memory so the UNSAVED content
37
+ * is what gets linted and cross-referenced. Returns the structured
38
+ * check-common `Offense[]` for the buffer's file, with `fix` / `suggest` and all
39
+ * typed fields preserved end to end (no message-string round-trip).
40
+ *
41
+ * `filePath` must be absolute. When it already exists in the project its
42
+ * on-disk `SourceCode` is replaced by the buffer; when it is new (not yet
43
+ * saved) the buffer is added so it is still linted.
44
+ */
45
+ export declare function lintBuffer(params: LintBufferParams): Promise<Offense[]>;
19
46
  export declare function getAppAndConfig(root: string, configPath?: string): Promise<{
20
47
  app: App;
21
48
  config: Config;
package/dist/index.js CHANGED
@@ -23,6 +23,7 @@ exports.toSourceCode = toSourceCode;
23
23
  exports.check = check;
24
24
  exports.checkAndAutofix = checkAndAutofix;
25
25
  exports.appCheckRun = appCheckRun;
26
+ exports.lintBuffer = lintBuffer;
26
27
  exports.getAppAndConfig = getAppAndConfig;
27
28
  exports.getApp = getApp;
28
29
  exports.getAppFilesPathPattern = getAppFilesPathPattern;
@@ -70,6 +71,24 @@ async function checkAndAutofix(root, configPath) {
70
71
  }
71
72
  async function appCheckRun(root, configPath, log = () => { }) {
72
73
  const { app, config } = await getAppAndConfig(root, configPath);
74
+ const offenses = await lintApp(root, app, config, log);
75
+ return {
76
+ app,
77
+ config,
78
+ offenses,
79
+ };
80
+ }
81
+ /**
82
+ * Run the configured checks over an in-memory `App` and return the structured
83
+ * `Offense[]` (with `fix` / `suggest` and all typed fields intact).
84
+ *
85
+ * Shared by {@link appCheckRun} (whole project on disk) and
86
+ * {@link lintBuffer} (project on disk + one buffer overlaid). Building the
87
+ * `getDocDefinition` map from the passed `app` is what lets the overlaid buffer
88
+ * be cross-referenced with its UNSAVED `{% doc %}` params rather than the
89
+ * stale on-disk version.
90
+ */
91
+ async function lintApp(root, app, config, log = () => { }) {
73
92
  const platformOSLiquidDocsManager = new platformos_check_docs_updater_1.PlatformOSLiquidDocsManager(log);
74
93
  const validator = await platformos_check_common_1.JSONValidator.create(platformOSLiquidDocsManager, config);
75
94
  const docDefinitions = new Map(app.map((file) => [
@@ -85,17 +104,52 @@ async function appCheckRun(root, configPath, log = () => { }) {
85
104
  return (0, platformos_check_common_1.extractDocDefinition)(file.uri, ast);
86
105
  }),
87
106
  ]));
88
- const offenses = await (0, platformos_check_common_1.check)(app, config, {
107
+ return (0, platformos_check_common_1.check)(app, config, {
89
108
  fs: NodeFileSystem_1.NodeFileSystem,
90
109
  platformosDocset: platformOSLiquidDocsManager,
91
110
  jsonValidationSet: platformOSLiquidDocsManager,
92
111
  getDocDefinition: async (relativePath) => docDefinitions.get(relativePath)?.(),
93
112
  });
94
- return {
95
- app,
96
- config,
97
- offenses,
98
- };
113
+ }
114
+ /**
115
+ * Lint a single in-memory buffer in the context of its on-disk project.
116
+ *
117
+ * This is the typed seam the MCP supervisor lints through — NOT an LSP, NOT a
118
+ * subprocess. The on-disk project is loaded so cross-file checks
119
+ * (`MissingPartial`, `MissingPage`, `OrphanedPartial`, …) resolve against real
120
+ * files, and the buffer under edit is overlaid in memory so the UNSAVED content
121
+ * is what gets linted and cross-referenced. Returns the structured
122
+ * check-common `Offense[]` for the buffer's file, with `fix` / `suggest` and all
123
+ * typed fields preserved end to end (no message-string round-trip).
124
+ *
125
+ * `filePath` must be absolute. When it already exists in the project its
126
+ * on-disk `SourceCode` is replaced by the buffer; when it is new (not yet
127
+ * saved) the buffer is added so it is still linted.
128
+ */
129
+ async function lintBuffer(params) {
130
+ const { root, filePath, content, configPath, log = () => { } } = params;
131
+ const { app, config } = await getAppAndConfig(root, configPath);
132
+ const uri = platformos_check_common_1.path.normalize(vscode_uri_1.URI.file(filePath));
133
+ const overlaidApp = overlayBuffer(app, uri, content);
134
+ const offenses = await lintApp(root, overlaidApp, config, log);
135
+ return offenses.filter((offense) => offense.uri === uri);
136
+ }
137
+ /**
138
+ * Return a copy of `app` with the `SourceCode` for `uri` replaced by one built
139
+ * from `content`, appending it when the file is not already present.
140
+ */
141
+ function overlayBuffer(app, uri, content) {
142
+ const overlay = (0, platformos_check_common_1.toSourceCode)(uri, content);
143
+ let replaced = false;
144
+ const next = app.map((file) => {
145
+ if (file.uri !== uri)
146
+ return file;
147
+ replaced = true;
148
+ return overlay;
149
+ });
150
+ if (!replaced)
151
+ next.push(overlay);
152
+ return next;
99
153
  }
100
154
  async function getAppAndConfig(root, configPath) {
101
155
  const config = await (0, exports.loadConfig)(configPath, root);
@@ -127,13 +181,20 @@ async function getApp(config) {
127
181
  // Schema files, generator templates (e.g. ERB .graphql), etc. are excluded.
128
182
  if (filePath.endsWith('.graphql') && !(0, platformos_check_common_1.isKnownGraphQLFile)(filePath))
129
183
  return false;
184
+ // Only lint .yml/.yaml files that belong to a recognized platformOS YAML
185
+ // directory (translations, custom model types, etc.). Config files like
186
+ // config.yml or .platformos-check.yml are excluded.
187
+ if ((filePath.endsWith('.yml') || filePath.endsWith('.yaml')) &&
188
+ !(0, platformos_check_common_1.isKnownYAMLFile)(filePath)) {
189
+ return false;
190
+ }
130
191
  return true;
131
192
  }));
132
193
  const sourceCodes = await Promise.all(paths.map(toSourceCode));
133
194
  return sourceCodes.filter((x) => x !== undefined);
134
195
  }
135
196
  function getAppFilesPathPattern(rootUri) {
136
- return (0, normalize_path_1.default)(node_path_1.default.join((0, node_url_1.fileURLToPath)(rootUri), '**/*.{liquid,json,graphql,yml,yaml}'));
197
+ return (0, normalize_path_1.default)(node_path_1.default.join((0, node_url_1.fileURLToPath)(rootUri), '**/*.{liquid,graphql,yml,yaml}'));
137
198
  }
138
199
  /** @deprecated Use appCheckRun instead */
139
200
  exports.runCheck = appCheckRun;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformos/platformos-check-node",
3
- "version": "0.0.19",
3
+ "version": "0.0.20",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "author": "platformOS",
@@ -33,8 +33,8 @@
33
33
  "type-check": "tsc --noEmit"
34
34
  },
35
35
  "dependencies": {
36
- "@platformos/platformos-check-common": "0.0.19",
37
- "@platformos/platformos-check-docs-updater": "0.0.19",
36
+ "@platformos/platformos-check-common": "0.0.20",
37
+ "@platformos/platformos-check-docs-updater": "0.0.20",
38
38
  "glob": "^13.0.0",
39
39
  "normalize-path": "^3.0.0",
40
40
  "vscode-uri": "^3.1.0",