correctly 0.0.0 → 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.
Files changed (68) hide show
  1. package/AGENTS.md +6 -2
  2. package/README.md +21 -15
  3. package/dist/ajv.d.mts +11 -0
  4. package/dist/ajv.d.mts.map +1 -0
  5. package/dist/ajv.mjs +221 -0
  6. package/dist/ajv.mjs.map +1 -0
  7. package/dist/check-BI7U3ZLc.mjs +88 -0
  8. package/dist/check-BI7U3ZLc.mjs.map +1 -0
  9. package/dist/cli.mjs +14 -62
  10. package/dist/cli.mjs.map +1 -1
  11. package/dist/config-Bx1-XXeo.d.mts +189 -0
  12. package/dist/config-Bx1-XXeo.d.mts.map +1 -0
  13. package/dist/config-Cb6o4YnJ.mjs +348 -0
  14. package/dist/config-Cb6o4YnJ.mjs.map +1 -0
  15. package/dist/core.d.mts +27 -125
  16. package/dist/core.d.mts.map +1 -1
  17. package/dist/core.mjs +5 -2
  18. package/dist/diagnostics-BqD1WOy4.mjs +70 -0
  19. package/dist/diagnostics-BqD1WOy4.mjs.map +1 -0
  20. package/dist/engine-VST0hoSk.mjs +111 -0
  21. package/dist/engine-VST0hoSk.mjs.map +1 -0
  22. package/dist/extensions-ClclWslY.d.mts +11 -0
  23. package/dist/extensions-ClclWslY.d.mts.map +1 -0
  24. package/dist/hints-HvSIhuMM.mjs +57 -0
  25. package/dist/hints-HvSIhuMM.mjs.map +1 -0
  26. package/dist/lsp.mjs +40 -91
  27. package/dist/lsp.mjs.map +1 -1
  28. package/dist/renovate.d.mts +5 -0
  29. package/dist/renovate.d.mts.map +1 -0
  30. package/dist/renovate.mjs +11 -0
  31. package/dist/renovate.mjs.map +1 -0
  32. package/dist/schemas-UxCtIdo2.mjs +265 -0
  33. package/dist/schemas-UxCtIdo2.mjs.map +1 -0
  34. package/dist/session-DCFav56x.mjs +113 -0
  35. package/dist/session-DCFav56x.mjs.map +1 -0
  36. package/dist/worker.mjs +80 -0
  37. package/dist/worker.mjs.map +1 -0
  38. package/docs/guide.md +166 -34
  39. package/examples/basic/correctly.config.ts +13 -0
  40. package/package.json +69 -60
  41. package/src/cli/check.ts +41 -11
  42. package/src/cli/main.ts +3 -2
  43. package/src/cli/readable.ts +24 -5
  44. package/src/cli/vsix.ts +1 -0
  45. package/src/core/adapters.ts +54 -0
  46. package/src/core/config.ts +201 -64
  47. package/src/core/engine.ts +118 -218
  48. package/src/core/extensions.ts +144 -0
  49. package/src/core/index.ts +3 -0
  50. package/src/core/parse.ts +26 -2
  51. package/src/core/parsers.ts +22 -0
  52. package/src/core/schema-walk.ts +43 -0
  53. package/src/core/schemas.ts +21 -41
  54. package/src/core/types.ts +33 -6
  55. package/src/extensions/renovate.ts +4 -0
  56. package/src/lsp/hints.ts +22 -12
  57. package/src/lsp/server.ts +9 -2
  58. package/src/lsp/workspace.ts +49 -54
  59. package/src/runtime/session.ts +173 -0
  60. package/src/runtime/worker.ts +99 -0
  61. package/src/validators/ajv.ts +257 -0
  62. package/src/vscode/README.md +1 -1
  63. package/src/vscode/extension.ts +2 -4
  64. package/src/vscode/package.json +9 -2
  65. package/dist/engine-BgWBnb1A.mjs +0 -746
  66. package/dist/engine-BgWBnb1A.mjs.map +0 -1
  67. package/examples/basic/correctly.config.json +0 -10
  68. package/src/core/config.schema.json +0 -47
package/package.json CHANGED
@@ -1,61 +1,70 @@
1
1
  {
2
- "name": "correctly",
3
- "version": "0.0.0",
4
- "description": "Shared JSON Schema validation for CI and editors",
5
- "repository": {
6
- "type": "git",
7
- "url": "git+https://github.com/jeremybanka/correctly.git",
8
- "directory": "packages/correctly"
9
- },
10
- "license": "MPL-2.0",
11
- "type": "module",
12
- "exports": {
13
- ".": {
14
- "types": "./dist/core.d.mts",
15
- "import": "./dist/core.mjs"
16
- },
17
- "./config.schema.json": "./src/core/config.schema.json"
18
- },
19
- "bin": {
20
- "correctly": "./dist/cli.mjs",
21
- "correctly-lsp": "./dist/lsp.mjs"
22
- },
23
- "files": [
24
- "dist",
25
- "src",
26
- "docs",
27
- "LICENSE",
28
- "README.md",
29
- "AGENTS.md",
30
- "examples"
31
- ],
32
- "scripts": {
33
- "build": "vp pack",
34
- "build:vsix": "node src/cli/vsix.ts",
35
- "test": "vp test",
36
- "test:public": "vp test tests/public",
37
- "test:private": "vp test tests/private",
38
- "test:watch": "vp test watch"
39
- },
40
- "dependencies": {
41
- "ajv": "8.20.0",
42
- "ajv-formats": "3.0.1",
43
- "jsonc-parser": "3.3.1",
44
- "picomatch": "4.0.7",
45
- "tinyglobby": "0.2.17",
46
- "vscode-json-languageservice": "5.7.2",
47
- "vscode-languageserver": "10.1.2",
48
- "vscode-languageserver-textdocument": "1.0.15"
49
- },
50
- "devDependencies": {
51
- "@types/node": "26.6.3",
52
- "@types/picomatch": "4.0.3",
53
- "@types/vscode": "1.138.0",
54
- "@vscode/vsce": "4.0.0",
55
- "rolldown": "1.2.11",
56
- "vscode-languageclient": "10.1.2"
57
- },
58
- "engines": {
59
- "node": ">=22.18.0"
60
- }
61
- }
2
+ "name": "correctly",
3
+ "version": "0.1.0",
4
+ "description": "Shared JSON Schema validation for CI and editors",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/jeremybanka/correctly.git",
8
+ "directory": "packages/correctly"
9
+ },
10
+ "license": "MPL-2.0",
11
+ "type": "module",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/core.d.mts",
15
+ "import": "./dist/core.mjs"
16
+ },
17
+ "./validators/ajv": {
18
+ "types": "./dist/ajv.d.mts",
19
+ "import": "./dist/ajv.mjs"
20
+ },
21
+ "./extensions/renovate": {
22
+ "types": "./dist/renovate.d.mts",
23
+ "import": "./dist/renovate.mjs"
24
+ }
25
+ },
26
+ "bin": {
27
+ "correctly": "./dist/cli.mjs",
28
+ "correctly-lsp": "./dist/lsp.mjs"
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "src",
33
+ "docs",
34
+ "LICENSE",
35
+ "README.md",
36
+ "AGENTS.md",
37
+ "examples"
38
+ ],
39
+ "dependencies": {
40
+ "ajv": "8.20.0",
41
+ "ajv-formats": "3.0.1",
42
+ "comline": "0.9.0",
43
+ "jsonc-parser": "3.3.1",
44
+ "picomatch": "4.0.7",
45
+ "tinyglobby": "0.2.17",
46
+ "vscode-json-languageservice": "5.7.2",
47
+ "vscode-languageserver": "10.1.2",
48
+ "vscode-languageserver-textdocument": "1.0.15"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "26.6.4",
52
+ "@types/picomatch": "4.0.3",
53
+ "@types/vscode": "1.140.0",
54
+ "@vscode/vsce": "4.0.0",
55
+ "rolldown": "1.2.12",
56
+ "vscode-languageclient": "10.1.2"
57
+ },
58
+ "engines": {
59
+ "node": ">=22.18.0"
60
+ },
61
+ "scripts": {
62
+ "build": "vp pack",
63
+ "build:vsix": "node src/cli/vsix.ts",
64
+ "test": "vp test",
65
+ "test:public": "vp test tests/public",
66
+ "test:private": "vp test tests/private",
67
+ "test:watch": "vp test watch",
68
+ "test:distribution": "vp test tests/private/distribution.test.ts"
69
+ }
70
+ }
package/src/cli/check.ts CHANGED
@@ -1,12 +1,8 @@
1
1
  import { readFile } from "node:fs/promises"
2
2
  import path from "node:path"
3
3
  import { glob } from "tinyglobby"
4
- import {
5
- discoverConfig,
6
- exclusions,
7
- isIncluded,
8
- loadProject,
9
- } from "../core/config.ts"
4
+ import { discoverConfig, exclusions, isIncluded } from "../core/config.ts"
5
+ import { ProjectSession } from "../runtime/session.ts"
10
6
  import { Engine } from "../core/engine.ts"
11
7
  import { failure, type Report } from "../core/types.ts"
12
8
 
@@ -17,14 +13,15 @@ export type CheckOptions = {
17
13
  files?: string[]
18
14
  onRead?: (file: string, text: string) => void
19
15
  }
20
- export async function check(options: CheckOptions = {}): Promise<Report> {
16
+ function emptyReport(): Report {
21
17
  const report: Report = {
22
- reportVersion: 1,
18
+ reportVersion: 2,
23
19
  config: null,
24
20
  files: [],
25
21
  failures: [],
26
22
  summary: {
27
23
  checked: 0,
24
+ validated: 0,
28
25
  schemaCovered: 0,
29
26
  syntaxOnly: 0,
30
27
  invalid: 0,
@@ -32,17 +29,47 @@ export async function check(options: CheckOptions = {}): Promise<Report> {
32
29
  },
33
30
  exitCode: 0,
34
31
  }
32
+ return report
33
+ }
34
+ export async function check(options: CheckOptions = {}): Promise<Report> {
35
+ const report = emptyReport()
36
+ let session: ProjectSession | undefined
35
37
  try {
36
38
  const cwd = path.resolve(options.cwd ?? process.cwd())
37
39
  const configPath = options.config
38
40
  ? path.resolve(cwd, options.config)
39
41
  : await discoverConfig(cwd)
40
42
  report.config = configPath
41
- const project = await loadProject(configPath)
42
- const engine = new Engine(
43
- project,
43
+ session = new ProjectSession(
44
+ configPath,
44
45
  options.offline === undefined ? {} : { offline: options.offline },
45
46
  )
47
+ const result = await session.check({
48
+ cwd,
49
+ ...(options.files ? { files: options.files } : {}),
50
+ sources: Boolean(options.onRead),
51
+ })
52
+ for (const [file, text] of result.sources) options.onRead?.(file, text)
53
+ return result.report
54
+ } catch (error) {
55
+ report.failures.push(failure(error))
56
+ report.summary.failures = 1
57
+ report.exitCode = 2
58
+ return report
59
+ } finally {
60
+ await session?.dispose()
61
+ }
62
+ }
63
+ export async function checkProject(
64
+ engine: Engine,
65
+ options: CheckOptions,
66
+ ): Promise<Report> {
67
+ const report = emptyReport()
68
+ const project = engine.project
69
+ report.config = project.configPath
70
+
71
+ try {
72
+ const cwd = path.resolve(options.cwd ?? process.cwd())
46
73
  await engine.prepare()
47
74
  const files = options.files?.length
48
75
  ? options.files.map((f) => path.resolve(cwd, f))
@@ -69,6 +96,9 @@ export async function check(options: CheckOptions = {}): Promise<Report> {
69
96
  const checked = report.files.filter((f) => f.coverage !== "excluded")
70
97
  report.summary = {
71
98
  checked: checked.length,
99
+ validated: checked.filter(
100
+ (f) => f.coverage === "schema" || f.coverage === "validated",
101
+ ).length,
72
102
  schemaCovered: checked.filter((f) => f.coverage === "schema").length,
73
103
  syntaxOnly: checked.filter((f) => f.coverage === "syntax-only").length,
74
104
  invalid: checked.filter((f) => f.diagnostics.length > 0).length,
package/src/cli/main.ts CHANGED
@@ -6,7 +6,7 @@ import { failure, type Report } from "../core/types.ts"
6
6
  const HELP = `correctly check [files...] [--config path] [--offline] [--format readable|json]
7
7
  correctly-lsp --stdio
8
8
 
9
- Validate JSON/JSONC using associations in correctly.config.json.
9
+ Validate JSON/JSONC using associations in correctly.config.ts.
10
10
  Exit 0: valid; 1: document errors; 2: configuration, schema, or execution errors.
11
11
  Formatting does not affect validation. See the package guide for configuration.
12
12
  `
@@ -59,12 +59,13 @@ export async function run(
59
59
  }
60
60
  } catch (error) {
61
61
  const report: Report = {
62
- reportVersion: 1,
62
+ reportVersion: 2,
63
63
  config: null,
64
64
  files: [],
65
65
  failures: [failure(error)],
66
66
  summary: {
67
67
  checked: 0,
68
+ validated: 0,
68
69
  schemaCovered: 0,
69
70
  syntaxOnly: 0,
70
71
  invalid: 0,
@@ -117,6 +117,19 @@ function excerpt(
117
117
  return { lines: output, width }
118
118
  }
119
119
 
120
+ function renderFailure(
121
+ error: Failure,
122
+ styler: Styler,
123
+ prefix = "",
124
+ continuation = "",
125
+ ): string {
126
+ const [title, ...details] = error.message.split("\n")
127
+ return [
128
+ `${prefix}${styler.bad(error.code)}: ${title}`,
129
+ ...details.map((line) => `${styler.dim(continuation + "│")} ${line}`),
130
+ ].join("\n")
131
+ }
132
+
120
133
  type FileGroup = { file: string; result?: FileResult; failures: Failure[] }
121
134
 
122
135
  function renderDiagnostic(
@@ -201,7 +214,9 @@ function fileSection(
201
214
  ? format
202
215
  : schema
203
216
  ? `${format} against ${association.name} (${displaySchema(cwd, schema)})`
204
- : `${format} syntax only (${association ? `association: ${association.name}` : "no schema association"})`
217
+ : association?.validator
218
+ ? `${format} validated by ${association.validator}`
219
+ : `${format} syntax only (${association ? `association: ${association.name}` : "no schema association"})`
205
220
  header += ` ${styler.dim(`· ${description}`)}`
206
221
  }
207
222
  const output = [header]
@@ -230,7 +245,12 @@ function fileSection(
230
245
  )
231
246
  } else
232
247
  output.push(
233
- `${styler.dim(branch)} ${styler.bad(issue.code)}: ${issue.message}`,
248
+ renderFailure(
249
+ issue,
250
+ styler,
251
+ `${styler.dim(branch)} `,
252
+ last ? " " : "│ ",
253
+ ),
234
254
  )
235
255
  if (!last) output.push(styler.dim("│"))
236
256
  }
@@ -265,8 +285,7 @@ export function readableReport(
265
285
  const output = [...groups.values()]
266
286
  .toSorted((a, b) => (a.file < b.file ? -1 : a.file > b.file ? 1 : 0))
267
287
  .map((group) => fileSection(group, options, styler))
268
- for (const error of globalFailures)
269
- output.push(`${styler.bad(error.code)}: ${error.message}`)
288
+ for (const error of globalFailures) output.push(renderFailure(error, styler))
270
289
  const errors = report.files.reduce(
271
290
  (count, file) => count + diagnosticViews(file.diagnostics).length,
272
291
  0,
@@ -288,7 +307,7 @@ export function readableReport(
288
307
  ? styler.warning
289
308
  : styler.success)(status),
290
309
  "",
291
- `${s.checked} checked, ${s.schemaCovered} schema-covered, ${s.syntaxOnly} syntax-only, ${s.invalid} invalid, ${s.failures} failures`,
310
+ `${s.checked} checked, ${s.validated} validated, ${s.schemaCovered} schema-covered, ${s.syntaxOnly} syntax-only, ${s.invalid} invalid, ${s.failures} failures`,
292
311
  ].join("\n"),
293
312
  )
294
313
  return output.join("\n\n")
package/src/cli/vsix.ts CHANGED
@@ -15,6 +15,7 @@ export async function buildVsix(
15
15
  for (const [entry, outfile] of [
16
16
  ["vscode/extension", "extension"],
17
17
  ["lsp/server", "server"],
18
+ ["runtime/worker", "worker"],
18
19
  ]) {
19
20
  const bundle = await rolldown({
20
21
  input: path.join(packageRoot, `src/${entry}.ts`),
@@ -0,0 +1,54 @@
1
+ import type { Diagnostic } from "./types.ts"
2
+ import type { ParsedDocument } from "./parse.ts"
3
+ import type { ReadText, RemoteOptions } from "./config.ts"
4
+
5
+ export type Parser = {
6
+ id: string
7
+ /** The parsed value representation, not the source file extension. */
8
+ valueModel: string
9
+ editorLanguage?: "json" | "jsonc"
10
+ parse(
11
+ text: string,
12
+ context: { file: string; signal?: AbortSignal },
13
+ ): ParsedDocument | Promise<ParsedDocument>
14
+ }
15
+ export type ValidationContext = {
16
+ root: string
17
+ configPath: string
18
+ remote?: RemoteOptions
19
+ read: ReadText
20
+ fetch?: typeof fetch
21
+ signal?: AbortSignal
22
+ /** Project-scoped services; use a versioned Symbol.for key for cross-package sharing. */
23
+ services: Map<symbol, unknown>
24
+ /** Register a file or URI which affects this adapter's prepared state. */
25
+ watch(uri: string): void
26
+ }
27
+ export type ValidationInput = {
28
+ file: string
29
+ text: string
30
+ parsed: ParsedDocument
31
+ signal?: AbortSignal
32
+ }
33
+ export type JsonSchemaEditorSupport = {
34
+ kind: "json-schema"
35
+ uri: string
36
+ readSchema(uri: string): Promise<string>
37
+ }
38
+ export type PreparedValidator = {
39
+ validate(input: ValidationInput): Diagnostic[] | Promise<Diagnostic[]>
40
+ editor?: JsonSchemaEditorSupport
41
+ dispose?(): void | Promise<void>
42
+ }
43
+ export type Validator = {
44
+ id: string
45
+ accepts: readonly string[]
46
+ /** Optional JSON Schema location for coverage reporting. */
47
+ schema?: string
48
+ extensions?: readonly { readonly id: string }[]
49
+ /** Register resources before any association is prepared (e.g. schema IDs). */
50
+ register?(context: ValidationContext): void | Promise<void>
51
+ prepare(
52
+ context: ValidationContext,
53
+ ): PreparedValidator | Promise<PreparedValidator>
54
+ }