@limaformat/lima 0.5.0 → 0.5.1

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/CHANGELOG.md CHANGED
@@ -12,6 +12,27 @@ are both frozen regardless of what this file shows.
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
+ ## [0.5.1] — 2026-10-28
16
+
17
+ Core errata `1.0.10`: aligned top-level values are trimmed, as Core 1.0
18
+ always required.
19
+
20
+ ### Fixed
21
+
22
+ - A top-level inline value kept every space after `: ` but the first:
23
+ `title: Hello` read `' Hello'`, and `n: 42` the string `' 42'`
24
+ instead of the integer 42 — against §4 step 3 and the §4 and §5.1
25
+ examples. Nested keys were not affected. The top-level entry now skips the
26
+ whitespace after the separator before reading the value, as the block
27
+ mapping does; the References 2.0 columns it reports were already physical
28
+ and are unchanged.
29
+
30
+ The shared conformance corpus grows to 218 Core cases (7 new, `since:
31
+ "1.0.10"`); TypeScript, Rust, and Go pass all of them.
32
+
33
+ A unit test in `src/block.test.ts` had asserted the defect (`key: value`
34
+ → `' value'`); it now asserts the specified `'value'`.
35
+
15
36
  ## [0.5.0] — 2026-09-09
16
37
 
17
38
  Scope cleanup. The frozen **References 1.0** resolver is no longer bundled in
package/dist/block.js CHANGED
@@ -9,7 +9,7 @@
9
9
  * never resolves a reference in the first place).
10
10
  */
11
11
  import { checkKeyLength, checkDuplicateKey, NESTING_DEPTH_LIMIT } from './normalize.js';
12
- import { stripKeyQuotes, stripComment, stripCommentKeepEscapes, parseQuotedOrTyped, closingQuoteIndex, isValidKey, positionedBuilder, } from './scalars.js';
12
+ import { stripKeyQuotes, stripComment, stripCommentKeepEscapes, parseQuotedOrTyped, closingQuoteIndex, isValidKey, } from './scalars.js';
13
13
  import { parseFlowMapping, parseFlowOrScalarValue } from './flow.js';
14
14
  import { buildBlockScalar } from './block-scalar.js';
15
15
  import { LimaError } from './errors.js';
@@ -49,7 +49,7 @@ const inlineSource = (wantSource, ctx, keyLine, valueCol, rawUndecoded) => wantS
49
49
  tabAdjust: ctx.tabAdjust ? [ctx.tabAdjust[keyLine - 1] ?? 0] : undefined,
50
50
  }) : undefined;
51
51
  const inlineOrBlockScalar = (raw, keyIndent, keyLine, valueCol, rawUndecoded, cursor, ctx, builder) => {
52
- const wantSource = builder === positionedBuilder;
52
+ const wantSource = builder.positions === true;
53
53
  if (raw !== '|') {
54
54
  const value = parseFlowOrScalarValue(raw, ctx, keyLine, builder, inlineSource(wantSource, ctx, keyLine, valueCol, rawUndecoded));
55
55
  cursor.next();
@@ -129,7 +129,7 @@ const cursorAfterDash = (cursor) => {
129
129
  };
130
130
  /** Shared block grammar consuming one mutable physical-line cursor. */
131
131
  const parseCursorBlock = (cursor, baseIndent, ctx, baseLine, builder) => {
132
- const wantSource = builder === positionedBuilder;
132
+ const wantSource = builder.positions === true;
133
133
  let items = null;
134
134
  let entries = null;
135
135
  let pendingItem = null;
package/dist/builder.d.ts CHANGED
@@ -26,6 +26,8 @@
26
26
  */
27
27
  import type { ReferenceSource } from './reference-tokens2.js';
28
28
  export interface ValueBuilder<V, M = Map<string, V>> {
29
+ /** Whether scalar builders need physical source anchors for editor positions. */
30
+ readonly positions?: boolean;
29
31
  null(line: number): V;
30
32
  bool(value: boolean, line: number): V;
31
33
  int(value: number, line: number): V;
package/dist/core.d.ts CHANGED
@@ -22,6 +22,7 @@ import { type LimaValue } from './value.js';
22
22
  import { type ParseContext, type Diagnostic } from './normalize.js';
23
23
  import { type PositionedValue } from './scalars.js';
24
24
  import type { ValueBuilder } from './builder.js';
25
+ import type { ReferenceToken2 } from './reference-tokens2.js';
25
26
  export type { Diagnostic, ParseContext } from './normalize.js';
26
27
  export { NESTING_DEPTH_LIMIT, SCALAR_LENGTH_LIMIT } from './normalize.js';
27
28
  export type { PositionedValue, InsertedAt } from './scalars.js';
@@ -40,6 +41,16 @@ export type CoreOptions = {
40
41
  * primitive the References layer (`references.ts`) builds on.
41
42
  */
42
43
  export declare const parseCoreWithPositions: (frontMatter: string, ctx: ParseContext) => Map<string, PositionedValue>;
44
+ export type PositionedReferenceParse = {
45
+ document: Map<string, PositionedValue>;
46
+ references: ReferenceToken2[];
47
+ };
48
+ /**
49
+ * Position-aware Core parse plus every active References 2.0 token seen while
50
+ * values are built. Recording at construction time preserves tokens from
51
+ * earlier duplicate-key values even though the result map is last-value-wins.
52
+ */
53
+ export declare const parseCoreWithPositionedReferences: (frontMatter: string, ctx: ParseContext) => PositionedReferenceParse;
43
54
  /** The public result shape (Core §11.1): every value `toNative*` can produce. */
44
55
  export type NativeValue = null | boolean | number | string | Date | NativeValue[] | {
45
56
  [key: string]: NativeValue;
package/dist/core.js CHANGED
@@ -20,6 +20,7 @@
20
20
  */
21
21
  import { checkKeyLength, byteLength, DOCUMENT_SIZE_LIMIT, TOP_LEVEL_KEY_LIMIT, NESTING_DEPTH_LIMIT, } from './normalize.js';
22
22
  import { unescapeDQ, stripComment, stripCommentKeepEscapes, positionedBuilder, NO_SPAN_VALUE, parseSimpleScalarSpan, } from './scalars.js';
23
+ import { isTrimWhitespace } from './chars.js';
23
24
  import { parseFlowOrScalarValue } from './flow.js';
24
25
  import { parseBlockRange } from './block.js';
25
26
  import { buildBlockScalar } from './block-scalar.js';
@@ -156,8 +157,21 @@ const parseCoreGeneric = (frontMatter, ctx, builder, computeDepth) => {
156
157
  searchFrom = lineEnd + 1;
157
158
  }
158
159
  }
159
- const parseEntry = (key, line, rawStart, isBlock, inlineEnd, nextStart) => {
160
+ const parseEntry = (key, line, separatorEnd, isBlock, inlineEnd, nextStart) => {
160
161
  checkKeyLength(key, line);
162
+ // §4 step 3: an inline value begins after the spaces that follow `: `, however many there are — as in a
163
+ // block mapping (block.ts trimSlice), so that the scalar paths below and the source column see the value
164
+ // itself. The newline that ends the line is not skipped.
165
+ let rawStart = separatorEnd;
166
+ if (!isBlock) {
167
+ while (rawStart < nextStart) {
168
+ const c = frontMatter.charCodeAt(rawStart);
169
+ // printable ASCII other than SPACE is never whitespace: the common case ends here at once
170
+ if ((c > 0x20 && c < 0x7f) || c === 10 || !isTrimWhitespace(c))
171
+ break;
172
+ rawStart++;
173
+ }
174
+ }
161
175
  if ((strict || ctx.onWarning !== undefined) && builder.hasMappingKey(root, key)) {
162
176
  const diagnostic = {
163
177
  code: 'DUPLICATE_KEY', line, key,
@@ -176,7 +190,7 @@ const parseCoreGeneric = (frontMatter, ctx, builder, computeDepth) => {
176
190
  // source line — the anchor for References 2.0 token positions (§2.4).
177
191
  // References 2.0 token positions (§2.4): only build the source anchor
178
192
  // for the annotated builder; `parseCore` (native) discards it.
179
- const wantSource = builder === positionedBuilder;
193
+ const wantSource = builder.positions === true;
180
194
  const valueLineStart = wantSource ? frontMatter.lastIndexOf('\n', rawStart - 1) + 1 : 0;
181
195
  const valueCol = wantSource ? [...frontMatter.slice(valueLineStart, rawStart)].length : 0;
182
196
  const lineTabAdjust = wantSource && ctx.tabAdjust ? [ctx.tabAdjust[line - 1] ?? 0] : undefined;
@@ -315,6 +329,28 @@ const parseCoreGeneric = (frontMatter, ctx, builder, computeDepth) => {
315
329
  * primitive the References layer (`references.ts`) builds on.
316
330
  */
317
331
  export const parseCoreWithPositions = (frontMatter, ctx) => parseCoreGeneric(frontMatter, ctx, positionedBuilder, depthOfPositioned);
332
+ /**
333
+ * Position-aware Core parse plus every active References 2.0 token seen while
334
+ * values are built. Recording at construction time preserves tokens from
335
+ * earlier duplicate-key values even though the result map is last-value-wins.
336
+ */
337
+ export const parseCoreWithPositionedReferences = (frontMatter, ctx) => {
338
+ const references = [];
339
+ const recordingBuilder = {
340
+ ...positionedBuilder,
341
+ string: (value, line, quoted, source) => {
342
+ const positioned = positionedBuilder.string(value, line, quoted, source);
343
+ if (positioned.kind === 'string' && positioned.references2) {
344
+ references.push(...positioned.references2);
345
+ }
346
+ return positioned;
347
+ },
348
+ };
349
+ return {
350
+ document: parseCoreGeneric(frontMatter, ctx, recordingBuilder, depthOfPositioned),
351
+ references,
352
+ };
353
+ };
318
354
  /**
319
355
  * The `ValueBuilder<NativeValue>` — `parseCore`'s fast path. Every scalar
320
356
  * builder is the identity function: unlike `positionedBuilder`, there is no
@@ -39,5 +39,13 @@ export type ReferenceSource = {
39
39
  col: number;
40
40
  tabAdjust?: number[];
41
41
  };
42
+ export declare const SEGMENT = "[a-zA-Z0-9_][a-zA-Z0-9_:-]*";
43
+ export declare const DOC_PATH = "[a-zA-Z0-9_][a-zA-Z0-9_:-]*(?:\\.[a-zA-Z0-9_][a-zA-Z0-9_:-]*)*";
44
+ export declare const PARTIAL_NAME = "[a-zA-Z0-9_][a-zA-Z0-9_:/-]*";
45
+ export declare const PARTIAL_PATH = "[a-zA-Z0-9_][a-zA-Z0-9_:/-]*(?:\\.[a-zA-Z0-9_][a-zA-Z0-9_:-]*)*";
42
46
  export declare const PURE_REFERENCE_2: RegExp;
47
+ /** Converts a 0-based codepoint offset to JavaScript/LSP UTF-16 units. */
48
+ export declare const codepointOffsetToUtf16: (text: string, codepointOffset: number) => number;
49
+ /** Converts a 0-based JavaScript/LSP UTF-16 offset to a codepoint offset. */
50
+ export declare const utf16OffsetToCodepoint: (text: string, utf16Offset: number) => number;
43
51
  export declare const scanReferenceTokens2: (value: string, source: ReferenceSource | undefined) => ReferenceToken2[];
@@ -1,20 +1,42 @@
1
1
  /** Reference tokens recorded while the annotated Core tree is being built. */
2
- const SEGMENT = '[a-zA-Z0-9_][a-zA-Z0-9_:-]*';
3
- const DOC_PATH = `${SEGMENT}(?:\\.${SEGMENT})*`;
4
- const PARTIAL_NAME = '[a-zA-Z0-9_][a-zA-Z0-9_:/-]*';
5
- const PARTIAL_PATH = `${PARTIAL_NAME}(?:\\.${SEGMENT})*`;
2
+ export const SEGMENT = '[a-zA-Z0-9_][a-zA-Z0-9_:-]*';
3
+ export const DOC_PATH = `${SEGMENT}(?:\\.${SEGMENT})*`;
4
+ export const PARTIAL_NAME = '[a-zA-Z0-9_][a-zA-Z0-9_:/-]*';
5
+ export const PARTIAL_PATH = `${PARTIAL_NAME}(?:\\.${SEGMENT})*`;
6
6
  export const PURE_REFERENCE_2 = new RegExp(`^(?:\\$\\{(${DOC_PATH})\\}|\\$\\((${PARTIAL_PATH})\\))$`);
7
7
  const REFERENCE_2 = new RegExp(`\\$\\{(${DOC_PATH})\\}|\\$\\((${PARTIAL_PATH})\\)`, 'g');
8
8
  const NO_TOKENS = [];
9
+ const utf16WidthAt = (text, offset) => text.codePointAt(offset) > 0xffff ? 2 : 1;
9
10
  /** Codepoint length of the UTF-16 slice `[a, b)` of `s`. */
10
11
  const codepointLen = (s, a, b) => {
11
12
  let n = 0;
12
13
  for (let i = a; i < b;) {
13
- i += s.codePointAt(i) > 0xffff ? 2 : 1;
14
+ i += utf16WidthAt(s, i);
14
15
  n++;
15
16
  }
16
17
  return n;
17
18
  };
19
+ /** Converts a 0-based codepoint offset to JavaScript/LSP UTF-16 units. */
20
+ export const codepointOffsetToUtf16 = (text, codepointOffset) => {
21
+ let utf16Offset = 0;
22
+ let codepoints = 0;
23
+ while (utf16Offset < text.length && codepoints < codepointOffset) {
24
+ utf16Offset += utf16WidthAt(text, utf16Offset);
25
+ codepoints++;
26
+ }
27
+ return utf16Offset;
28
+ };
29
+ /** Converts a 0-based JavaScript/LSP UTF-16 offset to a codepoint offset. */
30
+ export const utf16OffsetToCodepoint = (text, utf16Offset) => {
31
+ let currentUtf16 = 0;
32
+ let codepoints = 0;
33
+ const limit = Math.min(Math.max(utf16Offset, 0), text.length);
34
+ while (currentUtf16 < limit) {
35
+ currentUtf16 += utf16WidthAt(text, currentUtf16);
36
+ codepoints++;
37
+ }
38
+ return codepoints;
39
+ };
18
40
  /** Splice points (decoded value order): token text, UTF-16 index, path. */
19
41
  const scanDecoded = (value) => {
20
42
  REFERENCE_2.lastIndex = 0;
@@ -1,5 +1,19 @@
1
- import { type Diagnostic } from './core.js';
1
+ import { type Diagnostic, type PositionedReferenceParse, type PositionedValue } from './core.js';
2
2
  type Meta = Record<string, unknown>;
3
+ /**
4
+ * Resolve one document-reference path with the same traversal, cycle checks,
5
+ * and edge budget used by the public References 2.0 parser. The returned
6
+ * positioned value retains the definition site's source line.
7
+ */
8
+ export type DocumentReferenceTargetResolution = {
9
+ status: 'resolved';
10
+ target: PositionedValue;
11
+ } | {
12
+ status: 'unverifiable-partials';
13
+ } | {
14
+ status: 'unresolved';
15
+ };
16
+ export declare const resolveDocumentReferenceTarget: (parsed: PositionedReferenceParse, path: string) => DocumentReferenceTargetResolution;
3
17
  export type ParseMode = 'references' | 'core';
4
18
  export type ParseOptions = {
5
19
  mode?: ParseMode;
@@ -7,6 +7,10 @@ import { collectAllParticipants, deepCopyPositioned, earliestParticipant, emptyM
7
7
  const PARTIAL_NAME = '[a-zA-Z0-9_][a-zA-Z0-9_:/-]*';
8
8
  const PARTIAL_NAME_RE = new RegExp(`^${PARTIAL_NAME}$`);
9
9
  const MAX_EDGES = 3;
10
+ // Looking up the root path is itself the first reference edge; recursion into
11
+ // that root therefore gets the public resolver's remaining two-edge budget.
12
+ const TARGET_EDGE_BUDGET = MAX_EDGES - 1;
13
+ const referenceResourceStatuses = new WeakMap();
10
14
  /** Copies a pure-reference target and stamps the new insertion without
11
15
  * discarding provenance already attached to the copied root (R-112). */
12
16
  const copyWithInsertion = (target, insertedAt) => {
@@ -44,6 +48,91 @@ const documentTarget = (root, path) => {
44
48
  }
45
49
  return { root: value, tail: segments.slice(index) };
46
50
  };
51
+ export const resolveDocumentReferenceTarget = (parsed, path) => {
52
+ const { document } = parsed;
53
+ const target = documentTarget(document, path);
54
+ if (target === undefined)
55
+ return { status: 'unresolved' };
56
+ const resourceStatus = documentReferenceResourceStatus(parsed);
57
+ if (resourceStatus === 'unverifiable-partials')
58
+ return { status: resourceStatus };
59
+ if (resourceStatus === 'invalid')
60
+ return { status: 'unresolved' };
61
+ const ctx = { diagnostics: [], cache: new WeakMap() };
62
+ const stack = new Set([target.root]);
63
+ const resolved = resolveNode(target.root, document, new Map(), ctx, TARGET_EDGE_BUDGET, stack);
64
+ if (!resolved.complete || ctx.diagnostics.length > 0)
65
+ return { status: 'unresolved' };
66
+ const selected = lookupPath(resolved.value, target.tail);
67
+ return selected === undefined
68
+ ? { status: 'unresolved' }
69
+ : { status: 'resolved', target: selected };
70
+ };
71
+ const resolveDocument = (document, partials, ctx) => {
72
+ const resolved = new Map();
73
+ for (const [key, value] of document) {
74
+ const stack = new Set([value]);
75
+ resolved.set(key, resolveNode(value, document, partials, ctx, MAX_EDGES, stack).value);
76
+ }
77
+ return resolved;
78
+ };
79
+ const assertResolvedDocumentResourceLimits = (resolved) => {
80
+ const finalized = [...resolved].map(([key, value]) => [key, finalizePositioned(value)]);
81
+ const depth = finalized.length === 0 ? 0 : Math.max(...finalized.map(([, value]) => value.depth));
82
+ if (depth > NESTING_DEPTH_LIMIT) {
83
+ const participants = finalized
84
+ .filter(([, value]) => value.depth === depth)
85
+ .flatMap(([, value]) => value.deepestParticipants);
86
+ const winner = earliestParticipant(participants);
87
+ throw new LimaError({
88
+ code: 'RESOURCE_LIMIT', line: winner?.line ?? 1, token: winner?.token,
89
+ ...(winner?.offset !== undefined ? { column: winner.offset + 1 } : {}),
90
+ message: `Lima: nesting depth exceeds maximum of ${NESTING_DEPTH_LIMIT} at line ${winner?.line ?? 1}`,
91
+ });
92
+ }
93
+ let nodeCount = 1;
94
+ for (const [, value] of finalized)
95
+ nodeCount += value.nodeCount;
96
+ if (nodeCount > RESULT_NODE_LIMIT) {
97
+ const participants = [];
98
+ for (const value of resolved.values())
99
+ collectAllParticipants(value, participants);
100
+ const winner = earliestParticipant(participants);
101
+ throw new LimaError({
102
+ code: 'RESOURCE_LIMIT', line: winner?.line ?? 1, token: winner?.token,
103
+ ...(winner?.offset !== undefined ? { column: winner.offset + 1 } : {}),
104
+ message: `Lima: result exceeds maximum size of ${RESULT_NODE_LIMIT} total nodes at line ${winner?.line ?? 1}`,
105
+ });
106
+ }
107
+ return finalized;
108
+ };
109
+ const documentReferenceResourceStatus = (parsed) => {
110
+ // Partial values are supplied by caller code and can change whole-document
111
+ // depth/node limits. With no file convention for them, editor tooling cannot
112
+ // prove that even an unrelated document reference is safe to resolve.
113
+ if (parsed.references.some((reference) => reference.partialPath !== undefined))
114
+ return 'unverifiable-partials';
115
+ const { document } = parsed;
116
+ const cached = referenceResourceStatuses.get(document);
117
+ if (cached !== undefined)
118
+ return cached;
119
+ const ctx = { diagnostics: [], cache: new WeakMap() };
120
+ const resolved = resolveDocument(document, new Map(), ctx);
121
+ let valid = !ctx.diagnostics.some((diagnostic) => diagnostic.code === 'RESOURCE_LIMIT');
122
+ if (valid) {
123
+ try {
124
+ assertResolvedDocumentResourceLimits(resolved);
125
+ }
126
+ catch (error) {
127
+ if (!(error instanceof LimaError) || error.code !== 'RESOURCE_LIMIT')
128
+ throw error;
129
+ valid = false;
130
+ }
131
+ }
132
+ const status = valid ? 'valid' : 'invalid';
133
+ referenceResourceStatuses.set(document, status);
134
+ return status;
135
+ };
47
136
  const lookupPartial = (partials, path) => {
48
137
  const dot = path.indexOf('.');
49
138
  if (dot < 0)
@@ -284,11 +373,7 @@ const parseInternal = (frontMatter, options, useResolveCache) => {
284
373
  }
285
374
  const document = parseCoreWithPositions(frontMatter, { strict: options?.strict ?? false, onWarning: options?.onWarning });
286
375
  const ctx = { diagnostics: [], ...(useResolveCache ? { cache: new WeakMap() } : {}) };
287
- const resolved = new Map();
288
- for (const [key, value] of document) {
289
- const stack = new Set([value]);
290
- resolved.set(key, resolveNode(value, document, partials, ctx, MAX_EDGES, stack).value);
291
- }
376
+ const resolved = resolveDocument(document, partials, ctx);
292
377
  if (options?.strict)
293
378
  for (const value of resolved.values())
294
379
  scanUnresolved(value, ctx);
@@ -299,31 +384,7 @@ const parseInternal = (frontMatter, options, useResolveCache) => {
299
384
  // already do; `offset` was internal-only here.
300
385
  throw new LimaError({ ...winner, column: winner.offset + 1 });
301
386
  }
302
- const finalized = [...resolved].map(([key, value]) => [key, finalizePositioned(value)]);
303
- const depth = finalized.length === 0 ? 0 : Math.max(...finalized.map(([, value]) => value.depth));
304
- if (depth > NESTING_DEPTH_LIMIT) {
305
- const participants = finalized.filter(([, value]) => value.depth === depth).flatMap(([, value]) => value.deepestParticipants);
306
- const winner = earliestParticipant(participants);
307
- throw new LimaError({
308
- code: 'RESOURCE_LIMIT', line: winner?.line ?? 1, token: winner?.token,
309
- ...(winner?.offset !== undefined ? { column: winner.offset + 1 } : {}),
310
- message: `Lima: nesting depth exceeds maximum of ${NESTING_DEPTH_LIMIT} at line ${winner?.line ?? 1}`,
311
- });
312
- }
313
- let nodeCount = 1;
314
- for (const [, value] of finalized)
315
- nodeCount += value.nodeCount;
316
- if (nodeCount > RESULT_NODE_LIMIT) {
317
- const participants = [];
318
- for (const value of resolved.values())
319
- collectAllParticipants(value, participants);
320
- const winner = earliestParticipant(participants);
321
- throw new LimaError({
322
- code: 'RESOURCE_LIMIT', line: winner?.line ?? 1, token: winner?.token,
323
- ...(winner?.offset !== undefined ? { column: winner.offset + 1 } : {}),
324
- message: `Lima: result exceeds maximum size of ${RESULT_NODE_LIMIT} total nodes at line ${winner?.line ?? 1}`,
325
- });
326
- }
387
+ const finalized = assertResolvedDocumentResourceLimits(resolved);
327
388
  const out = emptyMapping();
328
389
  for (const [key, value] of finalized)
329
390
  out[key] = value.native;
package/dist/scalars.js CHANGED
@@ -13,6 +13,7 @@ export const hasActiveReferences2 = (value) => value.kind === 'string' ? (value.
13
13
  : false;
14
14
  /** The `ValueBuilder<PositionedValue>` — reconstructs today's annotated tree exactly, for References. */
15
15
  export const positionedBuilder = {
16
+ positions: true,
16
17
  null: (line) => ({ kind: 'null', line }),
17
18
  bool: (value, line) => ({ kind: 'bool', value, line }),
18
19
  int: (value, line) => ({ kind: 'int', value, line }),
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@limaformat/lima",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Lima frontmatter parser — LIMA Is Metadata Annotation",
5
5
  "keywords": ["frontmatter", "parser", "yaml", "markdown", "metadata"],
6
6
  "homepage": "https://limaformat.dev",
7
7
  "author": "Michael Kortstiege",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "https://github.com/limaformat/lima",
10
+ "url": "git+https://github.com/limaformat/lima.git",
11
11
  "directory": "js"
12
12
  },
13
13
  "bugs": "https://github.com/limaformat/lima/issues",